From a9fee6b00d660dbc8508a642d62d2aac89c0530a Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Fri, 4 Dec 2020 19:02:36 -0500 Subject: [PATCH 01/16] Initial Commit of the 4.26 plugin. (r376794) --- README.md | 3 +- Source/HoudiniEngine/HoudiniEngine.Build.cs | 10 +- Source/HoudiniEngine/Private/HBSPOps.cpp | 2924 ++-- Source/HoudiniEngine/Private/HBSPOps.h | 314 +- Source/HoudiniEngine/Private/HCsgUtils.cpp | 2872 +-- Source/HoudiniEngine/Private/HCsgUtils.h | 506 +- Source/HoudiniEngine/Private/HoudiniApi.cpp | 7542 ++++---- .../HoudiniEngine/Private/HoudiniEngine.cpp | 2244 +-- Source/HoudiniEngine/Private/HoudiniEngine.h | 614 +- .../Private/HoudiniEngineCommandlet.cpp | 48 +- .../Private/HoudiniEngineCommandlet.h | 52 +- .../Private/HoudiniEngineManager.cpp | 3098 ++-- .../Private/HoudiniEngineManager.h | 368 +- .../Private/HoudiniEngineOutputStats.cpp | 68 +- .../Private/HoudiniEngineOutputStats.h | 142 +- .../Private/HoudiniEnginePrivatePCH.h | 756 +- .../Private/HoudiniEngineProcessor.cpp | 48 +- .../Private/HoudiniEngineProcessor.h | 52 +- .../Private/HoudiniEngineScheduler.cpp | 1238 +- .../Private/HoudiniEngineScheduler.h | 234 +- .../Private/HoudiniEngineString.cpp | 368 +- .../Private/HoudiniEngineString.h | 140 +- .../Private/HoudiniEngineTask.cpp | 96 +- .../HoudiniEngine/Private/HoudiniEngineTask.h | 200 +- .../Private/HoudiniEngineTaskInfo.cpp | 92 +- .../Private/HoudiniEngineTaskInfo.h | 156 +- .../Private/HoudiniEngineUtils.cpp | 9754 +++++------ .../Private/HoudiniEngineUtils.h | 1246 +- .../Private/HoudiniGeoImportCommandlet.cpp | 1554 +- .../Private/HoudiniGeoImportCommandlet.h | 304 +- .../Private/HoudiniGeoImporter.cpp | 1676 +- .../Private/HoudiniGeoImporter.h | 242 +- .../Private/HoudiniHandleTranslator.cpp | 740 +- .../Private/HoudiniHandleTranslator.h | 102 +- .../Private/HoudiniInputTranslator.cpp | 5778 +++--- .../Private/HoudiniInputTranslator.h | 402 +- .../Private/HoudiniInstanceTranslator.cpp | 6096 +++---- .../Private/HoudiniInstanceTranslator.h | 724 +- .../Private/HoudiniLandscapeTranslator.cpp | 7344 ++++---- .../Private/HoudiniLandscapeTranslator.h | 800 +- .../Private/HoudiniMaterialTranslator.cpp | 6474 +++---- .../Private/HoudiniMaterialTranslator.h | 438 +- .../Private/HoudiniMeshTranslator.cpp | 12658 +++++++------ .../Private/HoudiniMeshTranslator.h | 808 +- .../Private/HoudiniOutputTranslator.cpp | 4088 ++--- .../Private/HoudiniOutputTranslator.h | 186 +- .../Private/HoudiniPDGImporterMessages.cpp | 162 +- .../Private/HoudiniPDGImporterMessages.h | 322 +- .../Private/HoudiniPDGManager.cpp | 4112 ++--- .../HoudiniEngine/Private/HoudiniPDGManager.h | 398 +- .../Private/HoudiniPDGTranslator.cpp | 2 +- .../Private/HoudiniPDGTranslator.h | 150 +- .../Private/HoudiniPackageParams.cpp | 868 +- .../Private/HoudiniPackageParams.h | 478 +- .../Private/HoudiniParameterTranslator.cpp | 5870 ++++--- .../Private/HoudiniParameterTranslator.h | 294 +- .../Private/HoudiniSplineTranslator.cpp | 3218 ++-- .../Private/HoudiniSplineTranslator.h | 230 +- .../Private/HoudiniStringResolver.cpp | 250 +- .../Private/HoudiniStringResolver.h | 150 +- .../Private/SAssetSelectionWidget.cpp | 316 +- .../Private/SAssetSelectionWidget.h | 198 +- .../Private/UnrealBrushTranslator.cpp | 844 +- .../Private/UnrealBrushTranslator.h | 98 +- .../Private/UnrealInstanceTranslator.cpp | 422 +- .../Private/UnrealInstanceTranslator.h | 96 +- .../Private/UnrealLandscapeTranslator.cpp | 3978 ++--- .../Private/UnrealLandscapeTranslator.h | 466 +- .../Private/UnrealMeshTranslator.cpp | 8241 +++++---- .../Private/UnrealMeshTranslator.h | 326 +- .../Private/UnrealSplineTranslator.cpp | 246 +- .../Private/UnrealSplineTranslator.h | 76 +- Source/HoudiniEngine/Public/HoudiniApi.h | 1954 +-- .../HoudiniEngineEditor.Build.cs | 246 +- .../Private/AssetTypeActions_HoudiniAsset.cpp | 916 +- .../Private/AssetTypeActions_HoudiniAsset.h | 168 +- .../Private/HoudiniAssetActorFactory.cpp | 232 +- .../Private/HoudiniAssetActorFactory.h | 116 +- .../Private/HoudiniAssetBroker.cpp | 142 +- .../Private/HoudiniAssetBroker.h | 98 +- .../Private/HoudiniAssetComponentDetails.cpp | 930 +- .../Private/HoudiniAssetComponentDetails.h | 170 +- .../Private/HoudiniAssetFactory.cpp | 416 +- .../Private/HoudiniAssetFactory.h | 142 +- .../Private/HoudiniEngineBakeUtils.cpp | 10416 +++++------ .../Private/HoudiniEngineBakeUtils.h | 1242 +- .../Private/HoudiniEngineCommands.cpp | 3390 ++-- .../Private/HoudiniEngineCommands.h | 524 +- .../Private/HoudiniEngineDetails.cpp | 3898 ++--- .../Private/HoudiniEngineDetails.h | 234 +- .../Private/HoudiniEngineEditor.cpp | 2948 ++-- .../Private/HoudiniEngineEditor.h | 692 +- .../HoudiniEngineEditorLocalization.cpp | 48 +- .../Private/HoudiniEngineEditorLocalization.h | 54 +- .../Private/HoudiniEngineEditorPrivatePCH.h | 296 +- .../Private/HoudiniEngineEditorUtils.cpp | 1306 +- .../Private/HoudiniEngineEditorUtils.h | 170 +- .../Private/HoudiniEngineStyle.cpp | 620 +- .../Private/HoudiniEngineStyle.h | 82 +- .../Private/HoudiniEngineTool.h | 54 +- .../Private/HoudiniGeoFactory.cpp | 762 +- .../Private/HoudiniGeoFactory.h | 166 +- .../HoudiniHandleComponentVisualizer.cpp | 518 +- .../HoudiniHandleComponentVisualizer.h | 226 +- .../Private/HoudiniHandleDetails.cpp | 798 +- .../Private/HoudiniHandleDetails.h | 86 +- .../Private/HoudiniInputDetails.cpp | 9946 +++++------ .../Private/HoudiniInputDetails.h | 328 +- .../Private/HoudiniOutputDetails.cpp | 6488 +++---- .../Private/HoudiniOutputDetails.h | 422 +- .../Private/HoudiniPDGDetails.cpp | 5254 +++--- .../Private/HoudiniPDGDetails.h | 280 +- .../Private/HoudiniParameterDetails.cpp | 14615 ++++++++-------- .../Private/HoudiniParameterDetails.h | 966 +- .../Private/HoudiniRuntimeSettingsDetails.cpp | 644 +- .../Private/HoudiniRuntimeSettingsDetails.h | 142 +- .../HoudiniSplineComponentVisualizer.cpp | 2064 +-- .../HoudiniSplineComponentVisualizer.h | 350 +- .../Private/HoudiniTool.cpp | 52 +- .../HoudiniEngineEditor/Private/HoudiniTool.h | 112 +- .../Private/SNewFilePathPicker.cpp | 698 +- .../Private/SNewFilePathPicker.h | 294 +- .../Public/IHoudiniEngineEditor.h | 142 +- .../HoudiniEngineRuntime.Build.cs | 192 +- .../Private/HoudiniAsset.cpp | 400 +- .../Private/HoudiniAsset.h | 206 +- .../Private/HoudiniAssetActor.cpp | 290 +- .../Private/HoudiniAssetActor.h | 154 +- .../HoudiniAssetBlueprintComponent.cpp | 4738 ++--- .../Private/HoudiniAssetBlueprintComponent.h | 702 +- .../Private/HoudiniAssetComponent.cpp | 4632 ++--- .../Private/HoudiniAssetComponent.h | 1612 +- .../Private/HoudiniCompatibilityHelpers.cpp | 3596 ++-- .../Private/HoudiniCompatibilityHelpers.h | 2194 +-- .../HoudiniEngineCopyPropertiesInterface.cpp | 64 +- .../HoudiniEngineCopyPropertiesInterface.h | 100 +- .../Private/HoudiniEngineRuntime.cpp | 646 +- .../Private/HoudiniEngineRuntime.h | 212 +- .../Private/HoudiniEngineRuntimePrivatePCH.h | 438 +- .../Private/HoudiniEngineRuntimeUtils.cpp | 1070 +- .../Private/HoudiniEngineRuntimeUtils.h | 694 +- .../Private/HoudiniGenericAttribute.cpp | 2152 +-- .../Private/HoudiniGenericAttribute.h | 244 +- .../Private/HoudiniGeoAsset.cpp | 48 +- .../Private/HoudiniGeoAsset.h | 56 +- .../Private/HoudiniGeoPartObject.cpp | 368 +- .../Private/HoudiniGeoPartObject.h | 836 +- .../Private/HoudiniHandleComponent.cpp | 506 +- .../Private/HoudiniHandleComponent.h | 270 +- .../Private/HoudiniInput.cpp | 5167 +++--- .../Private/HoudiniInput.h | 1120 +- .../Private/HoudiniInputObject.cpp | 167 +- .../Private/HoudiniInputObject.h | 1608 +- .../Private/HoudiniInputTypes.h | 134 +- .../HoudiniInstancedActorComponent.cpp | 488 +- .../Private/HoudiniInstancedActorComponent.h | 182 +- .../Private/HoudiniMaterialMap.cpp | 48 +- .../Private/HoudiniMaterialMap.h | 56 +- .../HoudiniMeshSplitInstancerComponent.cpp | 486 +- .../HoudiniMeshSplitInstancerComponent.h | 170 +- .../Private/HoudiniOutput.cpp | 1716 +- .../Private/HoudiniOutput.h | 1122 +- .../Private/HoudiniOutputTypes.h | 60 +- .../Private/HoudiniPDGAssetLink.cpp | 2962 ++-- .../Private/HoudiniPDGAssetLink.h | 1240 +- .../Private/HoudiniParameter.cpp | 517 +- .../Private/HoudiniParameter.h | 650 +- .../Private/HoudiniParameterButton.cpp | 100 +- .../Private/HoudiniParameterButton.h | 86 +- .../Private/HoudiniParameterButtonStrip.cpp | 146 +- .../Private/HoudiniParameterButtonStrip.h | 130 +- .../Private/HoudiniParameterChoice.cpp | 518 +- .../Private/HoudiniParameterChoice.h | 248 +- .../Private/HoudiniParameterColor.cpp | 166 +- .../Private/HoudiniParameterColor.h | 144 +- .../Private/HoudiniParameterFile.cpp | 202 +- .../Private/HoudiniParameterFile.h | 154 +- .../Private/HoudiniParameterFloat.cpp | 316 +- .../Private/HoudiniParameterFloat.h | 328 +- .../Private/HoudiniParameterFolder.cpp | 104 +- .../Private/HoudiniParameterFolder.h | 218 +- .../Private/HoudiniParameterFolderList.cpp | 216 +- .../Private/HoudiniParameterFolderList.h | 160 +- .../Private/HoudiniParameterInt.cpp | 236 +- .../Private/HoudiniParameterInt.h | 260 +- .../Private/HoudiniParameterLabel.cpp | 124 +- .../Private/HoudiniParameterLabel.h | 110 +- .../Private/HoudiniParameterMultiParm.cpp | 288 +- .../Private/HoudiniParameterMultiParm.h | 256 +- .../Private/HoudiniParameterOperatorPath.cpp | 190 +- .../Private/HoudiniParameterOperatorPath.h | 112 +- .../Private/HoudiniParameterRamp.cpp | 1364 +- .../Private/HoudiniParameterRamp.h | 622 +- .../Private/HoudiniParameterSeparator.cpp | 102 +- .../Private/HoudiniParameterSeparator.h | 86 +- .../Private/HoudiniParameterString.cpp | 270 +- .../Private/HoudiniParameterString.h | 180 +- .../Private/HoudiniParameterToggle.cpp | 190 +- .../Private/HoudiniParameterToggle.h | 134 +- .../HoudiniPluginSerializationVersion.cpp | 66 +- .../HoudiniPluginSerializationVersion.h | 184 +- .../Private/HoudiniRuntimeSettings.cpp | 702 +- .../Private/HoudiniRuntimeSettings.h | 552 +- .../Private/HoudiniSplineComponent.cpp | 1378 +- .../Private/HoudiniSplineComponent.h | 598 +- .../Private/HoudiniStaticMesh.cpp | 606 +- .../Private/HoudiniStaticMesh.h | 452 +- .../Private/HoudiniStaticMeshComponent.cpp | 426 +- .../Private/HoudiniStaticMeshComponent.h | 194 +- .../Private/HoudiniStaticMeshSceneProxy.cpp | 1082 +- .../Private/HoudiniStaticMeshSceneProxy.h | 334 +- 211 files changed, 126506 insertions(+), 126408 deletions(-) diff --git a/README.md b/README.md index ae931b4f8..7d72075e4 100644 --- a/README.md +++ b/README.md @@ -101,4 +101,5 @@ However, the Houdini Digital Assets themselves (the HDA files), that were create 01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". 01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. 01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters in the `Details` pane. Modifying any of the parameters will force the asset to recook and possibly update its geometry. - + + diff --git a/Source/HoudiniEngine/HoudiniEngine.Build.cs b/Source/HoudiniEngine/HoudiniEngine.Build.cs index 4a6e8049f..a17effe5b 100644 --- a/Source/HoudiniEngine/HoudiniEngine.Build.cs +++ b/Source/HoudiniEngine/HoudiniEngine.Build.cs @@ -34,7 +34,7 @@ Houdini Version: 18.5.408 Houdini Engine Version: 3.5.1 - Unreal Version: 4.25.0 + Unreal Version: 4.26.0 */ @@ -75,8 +75,8 @@ private string GetHFSPath() if ( Directory.Exists( HPath ) ) return HPath; } - - HEngineRegistry = Registry32Path + string.Format(@"\Houdini Engine {0}", HoudiniVersion); + + HEngineRegistry = Registry32Path + string.Format(@"\Houdini Engine {0}", HoudiniVersion); HPath = Microsoft.Win32.Registry.GetValue(HEngineRegistry, "InstallPath", null) as string; if ( HPath != null ) { @@ -106,8 +106,8 @@ private string GetHFSPath() if ( Directory.Exists( HPath ) ) return HPath; } - - // Look for the Houdini registry install path for the version the plug-in was compiled for + + // Look for the Houdini registry install path for the version the plug-in was compiled for HoudiniRegistry = Registry32Path + string.Format(@"\Houdini {0}", HoudiniVersion); HPath = Microsoft.Win32.Registry.GetValue(HoudiniRegistry, "InstallPath", null) as string; if ( HPath != null ) diff --git a/Source/HoudiniEngine/Private/HBSPOps.cpp b/Source/HoudiniEngine/Private/HBSPOps.cpp index d5d727360..5d57e502a 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.cpp +++ b/Source/HoudiniEngine/Private/HBSPOps.cpp @@ -1,1462 +1,1462 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - - -#include "HBSPOps.h" -#include "EngineDefines.h" -#include "Model.h" -#include "Materials/Material.h" -#include "Engine/BrushBuilder.h" -#include "Editor/EditorEngine.h" -#include "Components/BrushComponent.h" -#include "GameFramework/Volume.h" - -DEFINE_LOG_CATEGORY_STATIC(LogBSPOps, Log, All); - -/** Errors encountered in Csg operation. */ -int32 FHBSPOps::GErrors = 0; -bool FHBSPOps::GFastRebuild = false; - -static void TagReferencedNodes( UModel *Model, int32 *NodeRef, int32 *PolyRef, int32 iNode ) -{ - FBspNode &Node = Model->Nodes[iNode]; - - NodeRef[iNode ] = 0; - PolyRef[Node.iSurf] = 0; - - if( Node.iFront != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iFront); - if( Node.iBack != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iBack ); - if( Node.iPlane != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iPlane); -} - -// -// Update a bounding volume by expanding it to enclose a list of polys. -// -static void UpdateBoundWithPolys( FBox& Bound, FPoly** PolyList, int32 nPolys ) -{ - for( int32 i=0; iVertices.Num(); j++ ) - Bound += PolyList[i]->Vertices[j]; -} - -// -// Update a convolution hull with a list of polys. -// -static void UpdateConvolutionWithPolys( UModel *Model, int32 iNode, FPoly **PolyList, int32 nPolys ) -{ - FBox Box(ForceInit); - - FBspNode &Node = Model->Nodes[iNode]; - Node.iCollisionBound = Model->LeafHulls.Num(); - for( int32 i=0; iiBrushPoly != INDEX_NONE ) - { - int32 j; - for( j=0; jiBrushPoly == PolyList[i]->iBrushPoly ) - break; - if( j >= i ) - Model->LeafHulls.Add(PolyList[i]->iBrushPoly); - } - for( int32 j=0; jVertices.Num(); j++ ) - Box += PolyList[i]->Vertices[j]; - } - Model->LeafHulls.Add(INDEX_NONE); - - // Add bounds. - Model->LeafHulls.Add( *(int32*)&Box.Min.X ); - Model->LeafHulls.Add( *(int32*)&Box.Min.Y ); - Model->LeafHulls.Add( *(int32*)&Box.Min.Z ); - Model->LeafHulls.Add( *(int32*)&Box.Max.X ); - Model->LeafHulls.Add( *(int32*)&Box.Max.Y ); - Model->LeafHulls.Add( *(int32*)&Box.Max.Z ); - -} - -// -// Cut a partitioning poly by a list of polys, and add the resulting inside pieces to the -// front list and back list. -// -static void SplitPartitioner -( - UModel* Model, - FPoly** PolyList, - FPoly** FrontList, - FPoly** BackList, - int32 n, - int32 nPolys, - int32& nFront, - int32& nBack, - FPoly InfiniteEdPoly, - TArray& AllocatedFPolys -) -{ - FPoly FrontPoly,BackPoly; - while( n < nPolys ) - { - FPoly* Poly = PolyList[n]; - switch( InfiniteEdPoly.SplitWithPlane(Poly->Vertices[0],Poly->Normal,&FrontPoly,&BackPoly,0) ) - { - case SP_Coplanar: - // May occasionally happen. -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got inficoplanar") ); - break; - - case SP_Front: - // Shouldn't happen if hull is correct. -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got infifront") ); - return; - - case SP_Split: - InfiniteEdPoly = BackPoly; - break; - - case SP_Back: - break; - } - n++; - } - - FPoly* New = new FPoly; - *New = InfiniteEdPoly; - New->Reverse(); - New->iBrushPoly |= 0x40000000; - FrontList[nFront++] = New; - AllocatedFPolys.Add( New ); - - New = new FPoly; - *New = InfiniteEdPoly; - BackList[nBack++] = New; - AllocatedFPolys.Add( New ); -} - -// -// Build an FPoly representing an "infinite" plane (which exceeds the maximum -// dimensions of the world in all directions) for a particular Bsp node. -// -FPoly FHBSPOps::BuildInfiniteFPoly( UModel* Model, int32 iNode ) -{ - FBspNode &Node = Model->Nodes [iNode ]; - FBspSurf &Poly = Model->Surfs [Node.iSurf ]; - FVector Base = Poly.Plane * Poly.Plane.W; - FVector Normal = Poly.Plane; - FVector Axis1,Axis2; - - // Find two non-problematic axis vectors. - Normal.FindBestAxisVectors( Axis1, Axis2 ); - - // Set up the FPoly. - FPoly EdPoly; - EdPoly.Init(); - EdPoly.Normal = Normal; - EdPoly.Base = Base; - new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX + Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX + Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX - Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX - Axis2*WORLD_MAX); - - return EdPoly; -} - -// -// Recursively filter a set of polys defining a convex hull down the Bsp, -// splitting it into two halves at each node and adding in the appropriate -// face polys at splits. -// -static void FilterBound -( - UModel* Model, - FBox* ParentBound, - int32 iNode, - FPoly** PolyList, - int32 nPolys, - int32 Outside -) -{ - FMemMark Mark(FMemStack::Get()); - FBspNode& Node = Model->Nodes [iNode]; - FBspSurf& Surf = Model->Surfs [Node.iSurf]; - FVector Base = Surf.Plane * Surf.Plane.W; - FVector& Normal = Model->Vectors[Surf.vNormal]; - FBox Bound(ForceInit); - - Bound.Min.X = Bound.Min.Y = Bound.Min.Z = +WORLD_MAX; - Bound.Max.X = Bound.Max.Y = Bound.Max.Z = -WORLD_MAX; - - // Split bound into front half and back half. - FPoly** FrontList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nFront=0; - FPoly** BackList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nBack=0; - - // Keeping track of allocated FPoly structures to delete later on. - TArray AllocatedFPolys; - - FPoly* FrontPoly = new FPoly; - FPoly* BackPoly = new FPoly; - - // Keep track of allocations. - AllocatedFPolys.Add( FrontPoly ); - AllocatedFPolys.Add( BackPoly ); - - for( int32 i=0; iSplitWithPlane( Base, Normal, FrontPoly, BackPoly, 0 ) ) - { - case SP_Coplanar: -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got coplanar") ); - FrontList[nFront++] = Poly; - BackList[nBack++] = Poly; - break; - - case SP_Front: - FrontList[nFront++] = Poly; - break; - - case SP_Back: - BackList[nBack++] = Poly; - break; - - case SP_Split: - FrontList[nFront++] = FrontPoly; - BackList [nBack++] = BackPoly; - - FrontPoly = new FPoly; - BackPoly = new FPoly; - - // Keep track of allocations. - AllocatedFPolys.Add( FrontPoly ); - AllocatedFPolys.Add( BackPoly ); - - break; - - default: - UE_LOG(LogBSPOps, Fatal, TEXT("FZoneFilter::FilterToLeaf: Unknown split code") ); - } - } - if( nFront && nBack ) - { - // Add partitioner plane to front and back. - FPoly InfiniteEdPoly = FHBSPOps::BuildInfiniteFPoly( Model, iNode ); - InfiniteEdPoly.iBrushPoly = iNode; - - SplitPartitioner(Model,PolyList,FrontList,BackList,0,nPolys,nFront,nBack,InfiniteEdPoly,AllocatedFPolys); - } - else - { -// if( !nFront ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty fronthull") ); -// if( !nBack ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty backhull") ); - } - - // Recursively update all our childrens' bounding volumes. - if( nFront > 0 ) - { - if( Node.iFront != INDEX_NONE ) - FilterBound( Model, &Bound, Node.iFront, FrontList, nFront, Outside || Node.IsCsg() ); - else if( Outside || Node.IsCsg() ) - UpdateBoundWithPolys( Bound, FrontList, nFront ); - else - UpdateConvolutionWithPolys( Model, iNode, FrontList, nFront ); - } - if( nBack > 0 ) - { - if( Node.iBack != INDEX_NONE) - FilterBound( Model, &Bound,Node.iBack, BackList, nBack, Outside && !Node.IsCsg() ); - else if( Outside && !Node.IsCsg() ) - UpdateBoundWithPolys( Bound, BackList, nBack ); - else - UpdateConvolutionWithPolys( Model, iNode, BackList, nBack ); - } - - // Update parent bound to enclose this bound. - if( ParentBound ) - *ParentBound += Bound; - - // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. - for( int32 i=0; i0); - - // No need to test if only one poly. - if( NumPolys==1 ) - return PolyList[0]; - - FPoly *Poly, *Best=NULL; - float Score, BestScore; - int32 i, Index, j, Inc; - int32 Splits, Front, Back, Coplanar, AllSemiSolids; - - //PortalBias -- added by Legend on 4/12/2000 - float PortalBias = InPortalBias / 100.0f; - Balance &= 0xFF; // keep only the low byte to recover "Balance" - //UE_LOG(LogBSPOps, Log, TEXT("Balance=%d PortalBias=%f"), Balance, PortalBias ); - - if (Opt==FHBSPOps::BSP_Optimal) Inc = 1; // Test lots of nodes. - else if (Opt==FHBSPOps::BSP_Good) Inc = FMath::Max(1,NumPolys/20); // Test 20 nodes. - else /* BSP_Lame */ Inc = FMath::Max(1,NumPolys/4); // Test 4 nodes. - - // See if there are any non-semisolid polygons here. - for( i=0; iPolyFlags & PF_AddLast) ) - break; - AllSemiSolids = (i>=NumPolys); - - // Search through all polygons in the pool and find: - // A. The number of splits each poly would make. - // B. The number of front and back nodes the polygon would create. - // C. Number of coplanars. - BestScore = 0; - for( i=0; iPolyFlags & PF_AddLast) && !(Poly->PolyFlags & PF_Portal) ) - && !AllSemiSolids ); - if( Index>=i+Inc || Index>=NumPolys ) - continue; - - for( j=0; jSplitWithPlaneFast( FPlane( Poly->Vertices[0], Poly->Normal), NULL, NULL ) ) - { - case SP_Coplanar: - Coplanar++; - break; - - case SP_Front: - Front++; - break; - - case SP_Back: - Back++; - break; - - case SP_Split: - // Disfavor splitting polys that are zone portals. - if( !(OtherPoly->PolyFlags & PF_Portal) ) - Splits++; - else - Splits += 16; - break; - } - } - // added by Legend 1/31/1999 - // Score optimization: minimize cuts vs. balance tree (as specified in BSP Rebuilder dialog) - Score = ( 100.0 - float(Balance) ) * Splits + float(Balance) * FMath::Abs( Front - Back ); - if( Poly->PolyFlags & PF_Portal ) - { - // PortalBias -- added by Legend on 4/12/2000 - // - // PortalBias enables level designers to control the effect of Portals on the BSP. - // This effect can range from 0.0 (ignore portals), to 1.0 (portals cut everything). - // - // In builds prior to this (since the 221 build dating back to 1/31/1999) the bias - // has been 1.0 causing the portals to cut the BSP in ways that will potentially - // degrade level performance, and increase the BSP complexity. - // - // By setting the bias to a value between 0.3 and 0.7 the positive effects of - // the portals are preserved without giving them unreasonable priority in the BSP. - // - // Portals should be weighted high enough in the BSP to separate major parts of the - // level from each other (pushing entire rooms down the branches of the BSP), but - // should not be so high that portals cut through adjacent geometry in a way that - // increases complexity of the room being (typically, accidentally) cut. - // - Score -= ( 100.0 - float(Balance) ) * Splits * PortalBias; // ignore PortalBias of the split polys -- bias toward portal selection for cutting planes! - } - //UE_LOG(LogBSPOps, Log, " %4d: Score = %f (Front = %4d, Back = %4d, Splits = %4d, Flags = %08X)", Index, Score, Front, Back, Splits, Poly->PolyFlags ); //LEC - - if( Score AllocatedFPolys; - - // To account for big EdPolys split up. - int32 NumPolysToAlloc = NumPolys + 8 + NumPolys/4; - int32 NumFront=0; FPoly **FrontList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; - int32 NumBack =0; FPoly **BackList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; - - FPoly *SplitPoly = FindBestSplit( NumPolys, PolyList, Opt, Balance, PortalBias ); - - // Add the splitter poly to the Bsp with either a new BspSurf or an existing one. - if( RebuildSimplePolys ) - { - SplitPoly->iLinkSurf = Model->Surfs.Num(); - } - - int32 iOurNode = bspAddNode(Model,iParent,NodePlace,0,SplitPoly, BspPoints, BspVectors); - int32 iPlaneNode = iOurNode; - - // Now divide all polygons in the pool into (A) polygons that are - // in front of Poly, and (B) polygons that are in back of Poly. - // Coplanar polys are inserted immediately, before recursing. - - // If any polygons are split by Poly, we ignrore the original poly, - // split it into two polys, and add two new polys to the pool. - FPoly *FrontEdPoly = new FPoly; - FPoly *BackEdPoly = new FPoly; - // Keep track of allocations. - AllocatedFPolys.Add( FrontEdPoly ); - AllocatedFPolys.Add( BackEdPoly ); - - for( int32 i=0; iSplitWithPlane( SplitPoly->Vertices[0], SplitPoly->Normal, FrontEdPoly, BackEdPoly, 0 ) ) - { - case SP_Coplanar: - if( RebuildSimplePolys ) - { - EdPoly->iLinkSurf = Model->Surfs.Num()-1; - } - iPlaneNode = bspAddNode( Model, iPlaneNode, NODE_Plane, 0, EdPoly, BspPoints, BspVectors ); - break; - - case SP_Front: - FrontList[NumFront++] = PolyList[i]; - break; - - case SP_Back: - BackList[NumBack++] = PolyList[i]; - break; - - case SP_Split: - - // Create front & back nodes. - FrontList[NumFront++] = FrontEdPoly; - BackList [NumBack ++] = BackEdPoly; - - FrontEdPoly = new FPoly; - BackEdPoly = new FPoly; - // Keep track of allocations. - AllocatedFPolys.Add( FrontEdPoly ); - AllocatedFPolys.Add( BackEdPoly ); - - break; - } - } - - // Recursively split the front and back pools. - if( NumFront > 0 ) SplitPolyList( Model, iOurNode, NODE_Front, NumFront, FrontList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); - if( NumBack > 0 ) SplitPolyList( Model, iOurNode, NODE_Back, NumBack, BackList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); - - // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. - for( int32 i=0; iGetName() ); // moved here so that we can easily debug when an actor has lost parts of its brush - - check(Actor->GetBrushComponent()); - check(Actor->Brush); - check(Actor->Brush->RootOutside); - - RebuildBrush(Actor->Brush, BspPoints, BspVectors); - - // Make sure simplified collision is up to date. - Actor->GetBrushComponent()->BuildSimpleBrushCollision(); - Actor->RebuildNavigationData(); -} - -/** - * Duplicates the specified brush and makes it into a CSG-able level brush. - * @return The new brush, or NULL if the original was empty. - */ -void FHBSPOps::csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - check(Src); - check(Src->GetBrushComponent()); - check(Src->Brush); - - // Handle empty brush. - if( !bAllowEmpty && !Src->Brush->Polys->Element.Num() ) - { - Dest->Brush = NULL; - Dest->GetBrushComponent()->Brush = NULL; - return; - } - - // Duplicate the brush and its polys. - Dest->PolyFlags = PolyFlags; - Dest->Brush = NewObject(Dest, NAME_None, ResFlags); - Dest->Brush->Initialize(nullptr, Src->Brush->RootOutside); - Dest->Brush->Polys = NewObject(Dest->Brush, NAME_None, ResFlags); - Dest->Brush->Polys->Element = Src->Brush->Polys->Element; - Dest->GetBrushComponent()->Brush = Dest->Brush; - if(Src->BrushBuilder != nullptr) - { - Dest->BrushBuilder = DuplicateObject(Src->BrushBuilder, Dest); - } - - // Update poly textures. - for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) - { - Dest->Brush->Polys->Element[i].iBrushPoly = INDEX_NONE; - } - - // Copy positioning, and build bounding box. - if(bCopyPosRotScale) - { - Dest->CopyPosRotScaleFrom( Src ); - } - - // If it's a moving brush, prep it. - if( bNeedsPrep ) - { - csgPrepMovingBrush( Dest, BspPoints, BspVectors ); - } -} - -/** - * Adds a brush to the list of CSG brushes in the level, using a CSG operation. - * - * @return A newly-created copy of the brush. - */ -ABrush* FHBSPOps::csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - check(Actor); - check(Actor->GetBrushComponent()); - check(Actor->Brush); - check(Actor->Brush->Polys); - check(Actor->GetWorld()); - - // Can't do this if brush has no polys. - if( !Actor->Brush->Polys->Element.Num() ) - return NULL; - - // Spawn a new actor for the brush. - - ABrush* Result = Actor->GetWorld()->SpawnBrush(); - Result->SetNotForClientOrServer(); - - // Duplicate the brush. - csgCopyBrush - ( - Result, - Actor, - PolyFlags, - RF_Transactional, - 0, - true, - false, - BspPoints, - BspVectors - ); - check(Result->Brush); - - if( Result->GetBrushBuilder() ) - { - FActorLabelUtilities::SetActorLabelUnique(Result, FText::Format(NSLOCTEXT("BSPBrushOps", "BrushName", "{0} Brush"), FText::FromString(Result->GetBrushBuilder()->GetClass()->GetDescription())).ToString()); - } - // Assign the default material to the brush's polys. - for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) - { - FPoly& CurrentPoly = Result->Brush->Polys->Element[i]; - if ( !CurrentPoly.Material ) - { - CurrentPoly.Material = UMaterial::GetDefaultMaterial(MD_Surface); - } - } - - // Set add-info. - Result->BrushType = BrushType; - - Result->ReregisterAllComponents(); - - return Result; -} - -/** Add a new point to the model (preventing duplicates) and return its index. */ -static int32 AddThing( TArray& Vectors, FVector& V, float Thresh, int32 Check ) -{ - if( Check ) - { - // See if this is very close to an existing point/vector. - for( int32 i=0; i -Thresh) && (Temp < Thresh) ) - { - Temp=(V.Y - TableVect.Y); - if( (Temp > -Thresh) && (Temp < Thresh) ) - { - Temp=(V.Z - TableVect.Z); - if( (Temp > -Thresh) && (Temp < Thresh) ) - { - // Found nearly-matching vector. - return i; - } - } - } - } - } - return Vectors.Add( V ); -} - -/** Add a new vector to the model, merging near-duplicates, and return its index. */ -int32 FHBSPOps::bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ) -{ - const float Thresh = Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR; - - if (BspVectors) - { - // If a points grid has been built for quick vector lookup, use that instead of doing a linear search - const int32 NextIndex = Model->Vectors.Num(); - const int32 ReturnedIndex = BspVectors->FindOrAddPoint(*V, NextIndex, Thresh); - if (ReturnedIndex == NextIndex) - { - Model->Vectors.Add(*V); - } - - return ReturnedIndex; - } - - return AddThing - ( - Model->Vectors, - *V, - Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR, - 1 - ); -} - -/** Add a new point to the model, merging near-duplicates, and return its index. */ -int32 FHBSPOps::bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ) -{ - const float Thresh = Exact ? THRESH_POINTS_ARE_SAME : THRESH_POINTS_ARE_NEAR; - - if (BspPoints) - { - // If a points grid has been built for quick point lookup, use that instead of doing a linear search - const int32 NextIndex = Model->Points.Num(); - // Always look for points with a low threshold; a generous threshold can result in 'leaks' in the BSP and unwanted polys being generated - const int32 ReturnedIndex = BspPoints->FindOrAddPoint(*V, NextIndex, THRESH_POINTS_ARE_SAME); - if (ReturnedIndex == NextIndex) - { - Model->Points.Add(*V); - } - - return ReturnedIndex; - } - - // Try to find a match quickly from the Bsp. This finds all potential matches - // except for any dissociated from nodes/surfaces during a rebuild. - FVector Temp; - int32 pVertex; - float NearestDist = Model->FindNearestVertex(*V,Temp,Thresh,pVertex); - if( (NearestDist >= 0.0) && (NearestDist <= Thresh) ) - { - // Found an existing point. - return pVertex; - } - else - { - // No match found; add it slowly to find duplicates. - return AddThing(Model->Points, *V, Thresh, !GFastRebuild); - } -} - - -/** - * Builds Bsp from the editor polygon set (EdPolys) of a model. - * - * Opt = Bsp optimization, BSP_Lame (fast), BSP_Good (medium), BSP_Optimal (slow) - * Balance = 0-100, 0=only worry about minimizing splits, 100=only balance tree. - */ -void FHBSPOps::bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - int32 OriginalPolys = Model->Polys->Element.Num(); - - // Empty the model's tables. - if( RebuildSimplePolys==1 ) - { - // Empty everything but polys. - Model->EmptyModel( 1, 0 ); - } - else if( RebuildSimplePolys==0 ) - { - // Empty node vertices. - for( int32 i=0; iNodes.Num(); i++ ) - Model->Nodes[i].NumVertices = 0; - - // Refresh the Bsp. - bspRefresh(Model,1); - - // Empty nodes. - Model->EmptyModel( 0, 0 ); - } - if( Model->Polys->Element.Num() ) - { - // Allocate polygon pool. - FMemMark Mark(FMemStack::Get()); - FPoly** PolyList = new( FMemStack::Get(), Model->Polys->Element.Num() )FPoly*; - - // Add all FPolys to active list. - for( int32 i=0; iPolys->Element.Num(); i++ ) - if( Model->Polys->Element[i].Vertices.Num() ) - PolyList[i] = &Model->Polys->Element[i]; - - // Now split the entire Bsp by splitting the list of all polygons. - SplitPolyList - ( - Model, - INDEX_NONE, - NODE_Root, - Model->Polys->Element.Num(), - PolyList, - Opt, - Balance, - PortalBias, - RebuildSimplePolys, - BspPoints, - BspVectors - ); - - // Now build the bounding boxes for all nodes. - if( RebuildSimplePolys==0 ) - { - // Remove unreferenced things. - bspRefresh( Model, 1 ); - - // Rebuild all bounding boxes. - bspBuildBounds( Model ); - } - - Mark.Pop(); - } - -// UE_LOG(LogBSPOps, Log, TEXT("bspBuild built %i convex polys into %i nodes"), OriginalPolys, Model->Nodes.Num() ); -} - -/** - * If the Bsp's point and vector tables are nearly full, reorder them and delete unused ones. - */ -void FHBSPOps::bspRefresh( UModel* Model, bool NoRemapSurfs ) -{ - FMemStack& MemStack = FMemStack::Get(); - - FMemMark Mark(MemStack); - - int32 NumNodes = Model->Nodes.Num(); - int32 NumSurfs = Model->Surfs.Num(); - int32 NumVectors = Model->Vectors.Num(); - int32 NumPoints = Model->Points.Num(); - - // Remove unreferenced Bsp surfs. - int32* PolyRef; - if( NoRemapSurfs ) - { - PolyRef = NewZeroed(MemStack, NumSurfs); - } - else - { - PolyRef = NewOned(MemStack, NumSurfs); - } - - int32* NodeRef = NewOned(MemStack, NumNodes); - if( NumNodes > 0 ) - { - TagReferencedNodes( Model, NodeRef, PolyRef, 0 ); - } - - // Remap Bsp surfs. - { - int32 n=0; - for( int32 i=0; iSurfs[n] = Model->Surfs[i]; - PolyRef[i]=n++; - } - } - //UE_LOG(LogBSPOps, Log, TEXT("Polys: %i -> %i"), NumSurfs, n ); - Model->Surfs.RemoveAt( n, NumSurfs-n ); - NumSurfs = n; - } - - // Remap Bsp nodes. - { - int32 n=0; - for( int32 i=0; iNodes[n] = Model->Nodes[i]; - NodeRef[i]=n++; - } - } - //UE_LOG(LogBSPOps, Log, TEXT("Nodes: %i -> %i"), NumNodes, n ); - Model->Nodes.RemoveAt( n, NumNodes-n ); - NumNodes = n; - } - - // Update Bsp nodes. - for( int32 i=0; iNodes[i]; - Node->iSurf = PolyRef[Node->iSurf]; - if (Node->iFront != INDEX_NONE) Node->iFront = NodeRef[Node->iFront]; - if (Node->iBack != INDEX_NONE) Node->iBack = NodeRef[Node->iBack]; - if (Node->iPlane != INDEX_NONE) Node->iPlane = NodeRef[Node->iPlane]; - } - - // Remove unreferenced points and vectors. - int32* VectorRef = NewOned(MemStack, NumVectors); - int32* PointRef = NewOned(MemStack, NumPoints); - - // Check Bsp surfs. - TArray VertexRef; - for( int32 i=0; iSurfs[i]; - VectorRef [Surf->vNormal ] = 0; - VectorRef [Surf->vTextureU ] = 0; - VectorRef [Surf->vTextureV ] = 0; - PointRef [Surf->pBase ] = 0; - } - - // Check Bsp nodes. - for( int32 i=0; iNodes[i]; - FVert* VertPool = &Model->Verts[Node->iVertPool]; - for( int B=0; BNumVertices; B++ ) - { - PointRef[VertPool->pVertex] = 0; - VertPool++; - } - } - - // Remap points. - { - int32 n=0; - for( int32 i=0; iPoints[n] = Model->Points[i]; - PointRef[i] = n++; - } - //UE_LOG(LogBSPOps, Log, TEXT("Points: %i -> %i"), NumPoints, n ); - Model->Points.RemoveAt( n, NumPoints-n ); - NumPoints = n; - } - - // Remap vectors. - { - int32 n=0; - for (int32 i=0; iVectors[n] = Model->Vectors[i]; - VectorRef[i] = n++; - } - //UE_LOG(LogBSPOps, Log, TEXT("Vectors: %i -> %i"), NumVectors, n ); - Model->Vectors.RemoveAt( n, NumVectors-n ); - NumVectors = n; - } - - // Update Bsp surfs. - for( int32 i=0; iSurfs[i]; - Surf->vNormal = VectorRef [Surf->vNormal ]; - Surf->vTextureU = VectorRef [Surf->vTextureU]; - Surf->vTextureV = VectorRef [Surf->vTextureV]; - Surf->pBase = PointRef [Surf->pBase ]; - } - - // Update Bsp nodes. - for( int32 i=0; iNodes[i]; - FVert* VertPool = &Model->Verts[Node->iVertPool]; - for( int B=0; BNumVertices; B++ ) - { - VertPool->pVertex = PointRef [VertPool->pVertex]; - VertPool++; - } - } - - // Shrink the objects. - Model->ShrinkModel(); - - Mark.Pop(); -} - -// Build bounding volumes for all Bsp nodes. The bounding volume of the node -// completely encloses the "outside" space occupied by the nodes. Note that -// this is not the same as representing the bounding volume of all of the -// polygons within the node. -// -// We start with a practically-infinite cube and filter it down the Bsp, -// whittling it away until all of its convex volume fragments land in leaves. -void FHBSPOps::bspBuildBounds( UModel* Model ) -{ - if( Model->Nodes.Num()==0 ) - return; - - FPoly Polys[6], *PolyList[6]; - for( int32 i=0; i<6; i++ ) - { - PolyList[i] = &Polys[i]; - PolyList[i]->Init(); - PolyList[i]->iBrushPoly = INDEX_NONE; - } - - new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); - Polys[0].Normal =FVector( 0.000000, 0.000000, 1.000000 ); - Polys[0].Base =Polys[0].Vertices[0]; - - new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[1].Normal =FVector( 0.000000, 0.000000, -1.000000 ); - Polys[1].Base =Polys[1].Vertices[0]; - - new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[2].Normal =FVector( 0.000000, 1.000000, 0.000000 ); - Polys[2].Base =Polys[2].Vertices[0]; - - new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[3].Normal =FVector( 0.000000, -1.000000, 0.000000 ); - Polys[3].Base =Polys[3].Vertices[0]; - - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[4].Normal =FVector( 1.000000, 0.000000, 0.000000 ); - Polys[4].Base =Polys[4].Vertices[0]; - - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[5].Normal =FVector(-1.000000, 0.000000, 0.000000 ); - Polys[5].Base =Polys[5].Vertices[0]; - // Empty hulls. - Model->LeafHulls.Empty(); - for( int32 i=0; iNodes.Num(); i++ ) - Model->Nodes[i].iCollisionBound = INDEX_NONE; - FilterBound( Model, NULL, 0, PolyList, 6, Model->RootOutside ); -// UE_LOG(LogBSPOps, Log, TEXT("bspBuildBounds: Generated %i hulls"), Model->LeafHulls.Num() ); -} - -/** - * Validate a brush, and set iLinks on all EdPolys to index of the - * first identical EdPoly in the list, or its index if it's the first. - * Not transactional. - */ -void FHBSPOps::bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ) -{ - check(Brush != nullptr); - Brush->Modify(); - if( ForceValidate || !Brush->Linked ) - { - Brush->Linked = 1; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - Brush->Polys->Element[i].iLink = i; - } - int32 n=0; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - FPoly* EdPoly = &Brush->Polys->Element[i]; - if( EdPoly->iLink==i ) - { - for( int32 j=i+1; jPolys->Element.Num(); j++ ) - { - FPoly* OtherPoly = &Brush->Polys->Element[j]; - if - ( OtherPoly->iLink == j - && OtherPoly->Material == EdPoly->Material - && OtherPoly->TextureU == EdPoly->TextureU - && OtherPoly->TextureV == EdPoly->TextureV - && OtherPoly->PolyFlags == EdPoly->PolyFlags - && (OtherPoly->Normal | EdPoly->Normal)>0.9999 ) - { - float Dist = FVector::PointPlaneDist( OtherPoly->Vertices[0], EdPoly->Vertices[0], EdPoly->Normal ); - if( Dist>-0.001 && Dist<0.001 ) - { - OtherPoly->iLink = i; - n++; - } - } - } - } - } -// UE_LOG(LogBSPOps, Log, TEXT("BspValidateBrush linked %i of %i polys"), n, Brush->Polys->Element.Num() ); - } - - // Build bounds. - Brush->BuildBound(); -} - -void FHBSPOps::bspUnlinkPolys( UModel* Brush ) -{ - Brush->Modify(); - Brush->Linked = 1; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - Brush->Polys->Element[i].iLink = i; - } -} - -// Add an editor polygon to the Bsp, and also stick a reference to it -// in the editor polygon's BspNodes list. If the editor polygon has more sides -// than the Bsp will allow, split it up into several sub-polygons. -// -// Returns: Index to newly-created node of Bsp. If several nodes were created because -// of split polys, returns the parent (highest one up in the Bsp). -int32 FHBSPOps::bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - if( NodePlace == NODE_Plane ) - { - // Make sure coplanars are added at the end of the coplanar list so that - // we don't insert NF_IsNew nodes with non NF_IsNew coplanar children. - while( Model->Nodes[iParent].iPlane != INDEX_NONE ) - { - iParent = Model->Nodes[iParent].iPlane; - } - } - FBspSurf* Surf = NULL; - if( EdPoly->iLinkSurf == Model->Surfs.Num() ) - { - int32 NewIndex = Model->Surfs.AddZeroed(); - Surf = &Model->Surfs[NewIndex]; - - // This node has a new polygon being added by bspBrushCSG; must set its properties here. - Surf->pBase = bspAddPoint (Model,&EdPoly->Base,1,BspPoints); - Surf->vNormal = bspAddVector (Model,&EdPoly->Normal,1,BspVectors); - Surf->vTextureU = bspAddVector (Model,&EdPoly->TextureU,0,BspVectors); - Surf->vTextureV = bspAddVector (Model,&EdPoly->TextureV,0,BspVectors); - Surf->Material = EdPoly->Material; - Surf->Actor = NULL; - - Surf->PolyFlags = EdPoly->PolyFlags & ~PF_NoAddToBSP; - Surf->LightMapScale= EdPoly->LightMapScale; - - // Find the LightmassPrimitiveSettings in the UModel... - int32 FoundLightmassIndex = INDEX_NONE; - if (Model->LightmassSettings.Find(EdPoly->LightmassSettings, FoundLightmassIndex) == false) - { - FoundLightmassIndex = Model->LightmassSettings.Add(EdPoly->LightmassSettings); - } - Surf->iLightmassIndex = FoundLightmassIndex; - - Surf->Actor = EdPoly->Actor; - Surf->iBrushPoly = EdPoly->iBrushPoly; - - if (EdPoly->Actor) - { - Surf->bHiddenEdTemporary = EdPoly->Actor->IsTemporarilyHiddenInEditor(); - Surf->bHiddenEdLevel = EdPoly->Actor->bHiddenEdLevel; - Surf->bHiddenEdLayer = EdPoly->Actor->bHiddenEdLayer; - } - - Surf->Plane = FPlane(EdPoly->Vertices[0],EdPoly->Normal); - } - else - { - check(EdPoly->iLinkSurf!=INDEX_NONE); - check(EdPoly->iLinkSurfSurfs.Num()); - Surf = &Model->Surfs[EdPoly->iLinkSurf]; - } - - // Set NodeFlags. - if( Surf->PolyFlags & PF_NotSolid ) NodeFlags |= NF_NotCsg; - if( Surf->PolyFlags & (PF_Invisible|PF_Portal) ) NodeFlags |= NF_NotVisBlocking; - - if( EdPoly->Vertices.Num() > FBspNode::MAX_NODE_VERTICES ) - { - // Split up into two coplanar sub-polygons (one with MAX_NODE_VERTICES vertices and - // one with all the remaining vertices) and recursively add them. - - // EdPoly1 is just the first MAX_NODE_VERTICES from EdPoly. - FMemMark Mark(FMemStack::Get()); - FPoly *EdPoly1 = new FPoly; - *EdPoly1 = *EdPoly; - EdPoly1->Vertices.RemoveAt(FBspNode::MAX_NODE_VERTICES,EdPoly->Vertices.Num() - FBspNode::MAX_NODE_VERTICES); - - // EdPoly2 is the first vertex from EdPoly, and the last EdPoly->Vertices.Num() - MAX_NODE_VERTICES + 1. - FPoly *EdPoly2 = new FPoly; - *EdPoly2 = *EdPoly; - EdPoly2->Vertices.RemoveAt(1,FBspNode::MAX_NODE_VERTICES - 2); - - int32 iNode = bspAddNode( Model, iParent, NodePlace, NodeFlags, EdPoly1, BspPoints, BspVectors ); // Add this poly first. - bspAddNode( Model, iNode, NODE_Plane, NodeFlags, EdPoly2, BspPoints, BspVectors ); // Then add other (may be bigger). - - delete EdPoly1; - delete EdPoly2; - - Mark.Pop(); - return iNode; // Return coplanar "parent" node (not coplanar child) - } - else - { - // Add node. - int32 iNode = Model->Nodes.AddZeroed(); - FBspNode& Node = Model->Nodes[iNode]; - - // Tell transaction tracking system that parent is about to be modified. - FBspNode* Parent=NULL; - if( NodePlace!=NODE_Root ) - Parent = &Model->Nodes[iParent]; - - // Set node properties. - Node.iSurf = EdPoly->iLinkSurf; - Node.NodeFlags = NodeFlags; - Node.iCollisionBound = INDEX_NONE; - Node.Plane = FPlane( EdPoly->Vertices[0], EdPoly->Normal ); - Node.iVertPool = Model->Verts.AddUninitialized(EdPoly->Vertices.Num()); - Node.iFront = INDEX_NONE; - Node.iBack = INDEX_NONE; - Node.iPlane = INDEX_NONE; - if( NodePlace==NODE_Root ) - { - Node.iLeaf[0] = INDEX_NONE; - Node.iLeaf[1] = INDEX_NONE; - Node.iZone[0] = 0; - Node.iZone[1] = 0; - } - else if( NodePlace==NODE_Front || NodePlace==NODE_Back ) - { - int32 ZoneFront=NodePlace==NODE_Front; - Node.iLeaf[0] = Parent->iLeaf[ZoneFront]; - Node.iLeaf[1] = Parent->iLeaf[ZoneFront]; - Node.iZone[0] = Parent->iZone[ZoneFront]; - Node.iZone[1] = Parent->iZone[ZoneFront]; - } - else - { - int32 IsFlipped = (Node.Plane|Parent->Plane)<0.0; - Node.iLeaf[0] = Parent->iLeaf[IsFlipped ]; - Node.iLeaf[1] = Parent->iLeaf[1-IsFlipped]; - Node.iZone[0] = Parent->iZone[IsFlipped ]; - Node.iZone[1] = Parent->iZone[1-IsFlipped]; - } - - // Link parent to this node. - if (NodePlace == NODE_Front) - { - Parent->iFront = iNode; - } - else if (NodePlace == NODE_Back) - { - Parent->iBack = iNode; - } - else if (NodePlace == NODE_Plane) - { - Parent->iPlane = iNode; - } - - // Add all points to point table, merging nearly-overlapping polygon points - // with other points in the poly to prevent criscrossing vertices from - // being generated. - - // Must maintain Node->NumVertices on the fly so that bspAddPoint is always - // called with the Bsp in a clean state. - Node.NumVertices = 0; - FVert* VertPool = &Model->Verts[ Node.iVertPool ]; - for( uint8 i=0; iVertices.Num(); i++ ) - { - int32 pVertex = bspAddPoint(Model,&EdPoly->Vertices[i],0, BspPoints); - if( Node.NumVertices==0 || VertPool[Node.NumVertices-1].pVertex!=pVertex ) - { - VertPool[Node.NumVertices].iSide = INDEX_NONE; - VertPool[Node.NumVertices].pVertex = pVertex; - Node.NumVertices++; - } - } - if( Node.NumVertices>=2 && VertPool[0].pVertex==VertPool[Node.NumVertices-1].pVertex ) - { - Node.NumVertices--; - } - if( Node.NumVertices < 3 ) - { - GErrors++; -// UE_LOG(LogBSPOps, Warning, TEXT("bspAddNode: Infinitesimal polygon %i (%i)"), Node.NumVertices, EdPoly->Vertices.Num() ); - Node.NumVertices = 0; - } - - return iNode; - } -} - -/** - * Rebuild some brush internals - */ -void FHBSPOps::RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - Brush->Modify(); - Brush->EmptyModel(1, 0); - - // Build bounding box. - Brush->BuildBound(); - - // Build BSP for the brush. - bspBuild(Brush, BSP_Good, 15, 70, 1, 0, BspPoints, BspVectors); - bspRefresh(Brush, 1); - bspBuildBounds(Brush); -} - -/** - * Rotates the specified brush's vertices. - */ -void FHBSPOps::RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - if(Brush->GetBrushComponent()->Brush && Brush->GetBrushComponent()->Brush->Polys) - { - for( int32 poly = 0 ; poly < Brush->GetBrushComponent()->Brush->Polys->Element.Num() ; poly++ ) - { - FPoly* Poly = &(Brush->GetBrushComponent()->Brush->Polys->Element[poly]); - - // Rotate the vertices. - const FRotationMatrix RotMatrix( Rotation ); - for( int32 vertex = 0 ; vertex < Poly->Vertices.Num() ; vertex++ ) - { - Poly->Vertices[vertex] = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Vertices[vertex] - Brush->GetPivotOffset()); - } - Poly->Base = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Base - Brush->GetPivotOffset()); - - // Rotate the texture vectors. - Poly->TextureU = RotMatrix.TransformVector( Poly->TextureU ); - Poly->TextureV = RotMatrix.TransformVector( Poly->TextureV ); - - // Recalc the normal for the poly. - Poly->Normal = FVector::ZeroVector; - Poly->Finalize(Brush,0); - } - - Brush->GetBrushComponent()->Brush->BuildBound(); - - if( !Brush->IsStaticBrush() ) - { - csgPrepMovingBrush( Brush, BspPoints, BspVectors ); - } - - if ( bClearComponents ) - { - Brush->ReregisterAllComponents(); - } - } -} - - -void FHBSPOps::HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - // The default physics volume doesn't have an associated UModel, so we need to handle that case gracefully. - if(Volume.Brush) - { - FHBSPOps::csgPrepMovingBrush( &Volume, BspPoints, BspVectors); - } -} - -UHBspPointsGrid* UHBspPointsGrid::Create(float InGranularity, float InThreshold, int32 InitialSize) -{ - check(InThreshold / InGranularity <= 0.5f); - - UHBspPointsGrid* Obj = NewObject(GetTransientPackage(), UHBspPointsGrid::StaticClass()); - Obj->OneOverGranularity = 1.0f / InGranularity; - Obj->Threshold = InThreshold; - Obj->Clear(InitialSize); - - return Obj; -} - -void UHBspPointsGrid::Clear(int32 InitialSize) -{ - GridMap.Empty(InitialSize); -} - - -// Given a grid index in one axis, a real position on the grid and a threshold radius, -// return either: -// - the additional grid index it can overlap in that axis, or -// - the original grid index if there is no overlap. -int32 UHBspPointsGrid::GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold) -{ - if (GridPos - GridIndex < GridThreshold) - { - return GridIndex - 1; - } - else if (1.0f - (GridPos - GridIndex) < GridThreshold) - { - return GridIndex + 1; - } - else - { - return GridIndex; - } -} - -int32 UHBspPointsGrid::FindOrAddPoint(const FVector& Point, int32 Index, float PointThreshold) -{ - // Offset applied to the grid coordinates so aligned vertices (the normal case) don't overlap several grid items (taking into account the threshold) - const float GridOffset = 0.12345f; - - const float AdjustedPointX = Point.X - GridOffset; - const float AdjustedPointY = Point.Y - GridOffset; - const float AdjustedPointZ = Point.Z - GridOffset; - - const float GridX = AdjustedPointX * OneOverGranularity; - const float GridY = AdjustedPointY * OneOverGranularity; - const float GridZ = AdjustedPointZ * OneOverGranularity; - - // Get the grid indices corresponding to the point coordinates - const int32 GridIndexX = FMath::FloorToInt(GridX); - const int32 GridIndexY = FMath::FloorToInt(GridY); - const int32 GridIndexZ = FMath::FloorToInt(GridZ); - - // Find grid item in map - FHBspPointsGridItem& GridItem = GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, GridIndexZ)); - - // Iterate through grid item points and return a point if it's close to the threshold - const float PointThresholdSquared = PointThreshold * PointThreshold; - for (const FHBspIndexedPoint& IndexedPoint : GridItem.IndexedPoints) - { - if (FVector::DistSquared(IndexedPoint.Point, Point) <= PointThresholdSquared) - { - return IndexedPoint.Index; - } - } - - // Otherwise, the point is new: add it to the grid item. - GridItem.IndexedPoints.Emplace(Point, Index); - - // The grid has a maximum threshold of a certain radius. If the point is near the edge of a grid cube, it may overlap into other items. - // Add it to all grid items it can be seen from. - const float GridThreshold = Threshold * OneOverGranularity; - const int32 NeighbourX = GetAdjacentIndexIfOverlapping(GridIndexX, GridX, GridThreshold); - const int32 NeighbourY = GetAdjacentIndexIfOverlapping(GridIndexY, GridY, GridThreshold); - const int32 NeighbourZ = GetAdjacentIndexIfOverlapping(GridIndexZ, GridZ, GridThreshold); - - const bool bOverlapsInX = (NeighbourX != GridIndexX); - const bool bOverlapsInY = (NeighbourY != GridIndexY); - const bool bOverlapsInZ = (NeighbourZ != GridIndexZ); - - if (bOverlapsInX) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInY) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else - { - if (bOverlapsInY) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - - return Index; -} - - +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "HBSPOps.h" +#include "EngineDefines.h" +#include "Model.h" +#include "Materials/Material.h" +#include "Engine/BrushBuilder.h" +#include "Editor/EditorEngine.h" +#include "Components/BrushComponent.h" +#include "GameFramework/Volume.h" + +DEFINE_LOG_CATEGORY_STATIC(LogBSPOps, Log, All); + +/** Errors encountered in Csg operation. */ +int32 FHBSPOps::GErrors = 0; +bool FHBSPOps::GFastRebuild = false; + +static void TagReferencedNodes( UModel *Model, int32 *NodeRef, int32 *PolyRef, int32 iNode ) +{ + FBspNode &Node = Model->Nodes[iNode]; + + NodeRef[iNode ] = 0; + PolyRef[Node.iSurf] = 0; + + if( Node.iFront != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iFront); + if( Node.iBack != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iBack ); + if( Node.iPlane != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iPlane); +} + +// +// Update a bounding volume by expanding it to enclose a list of polys. +// +static void UpdateBoundWithPolys( FBox& Bound, FPoly** PolyList, int32 nPolys ) +{ + for( int32 i=0; iVertices.Num(); j++ ) + Bound += PolyList[i]->Vertices[j]; +} + +// +// Update a convolution hull with a list of polys. +// +static void UpdateConvolutionWithPolys( UModel *Model, int32 iNode, FPoly **PolyList, int32 nPolys ) +{ + FBox Box(ForceInit); + + FBspNode &Node = Model->Nodes[iNode]; + Node.iCollisionBound = Model->LeafHulls.Num(); + for( int32 i=0; iiBrushPoly != INDEX_NONE ) + { + int32 j; + for( j=0; jiBrushPoly == PolyList[i]->iBrushPoly ) + break; + if( j >= i ) + Model->LeafHulls.Add(PolyList[i]->iBrushPoly); + } + for( int32 j=0; jVertices.Num(); j++ ) + Box += PolyList[i]->Vertices[j]; + } + Model->LeafHulls.Add(INDEX_NONE); + + // Add bounds. + Model->LeafHulls.Add( *(int32*)&Box.Min.X ); + Model->LeafHulls.Add( *(int32*)&Box.Min.Y ); + Model->LeafHulls.Add( *(int32*)&Box.Min.Z ); + Model->LeafHulls.Add( *(int32*)&Box.Max.X ); + Model->LeafHulls.Add( *(int32*)&Box.Max.Y ); + Model->LeafHulls.Add( *(int32*)&Box.Max.Z ); + +} + +// +// Cut a partitioning poly by a list of polys, and add the resulting inside pieces to the +// front list and back list. +// +static void SplitPartitioner +( + UModel* Model, + FPoly** PolyList, + FPoly** FrontList, + FPoly** BackList, + int32 n, + int32 nPolys, + int32& nFront, + int32& nBack, + FPoly InfiniteEdPoly, + TArray& AllocatedFPolys +) +{ + FPoly FrontPoly,BackPoly; + while( n < nPolys ) + { + FPoly* Poly = PolyList[n]; + switch( InfiniteEdPoly.SplitWithPlane(Poly->Vertices[0],Poly->Normal,&FrontPoly,&BackPoly,0) ) + { + case SP_Coplanar: + // May occasionally happen. +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got inficoplanar") ); + break; + + case SP_Front: + // Shouldn't happen if hull is correct. +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got infifront") ); + return; + + case SP_Split: + InfiniteEdPoly = BackPoly; + break; + + case SP_Back: + break; + } + n++; + } + + FPoly* New = new FPoly; + *New = InfiniteEdPoly; + New->Reverse(); + New->iBrushPoly |= 0x40000000; + FrontList[nFront++] = New; + AllocatedFPolys.Add( New ); + + New = new FPoly; + *New = InfiniteEdPoly; + BackList[nBack++] = New; + AllocatedFPolys.Add( New ); +} + +// +// Build an FPoly representing an "infinite" plane (which exceeds the maximum +// dimensions of the world in all directions) for a particular Bsp node. +// +FPoly FHBSPOps::BuildInfiniteFPoly( UModel* Model, int32 iNode ) +{ + FBspNode &Node = Model->Nodes [iNode ]; + FBspSurf &Poly = Model->Surfs [Node.iSurf ]; + FVector Base = Poly.Plane * Poly.Plane.W; + FVector Normal = Poly.Plane; + FVector Axis1,Axis2; + + // Find two non-problematic axis vectors. + Normal.FindBestAxisVectors( Axis1, Axis2 ); + + // Set up the FPoly. + FPoly EdPoly; + EdPoly.Init(); + EdPoly.Normal = Normal; + EdPoly.Base = Base; + new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX + Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX + Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX - Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX - Axis2*WORLD_MAX); + + return EdPoly; +} + +// +// Recursively filter a set of polys defining a convex hull down the Bsp, +// splitting it into two halves at each node and adding in the appropriate +// face polys at splits. +// +static void FilterBound +( + UModel* Model, + FBox* ParentBound, + int32 iNode, + FPoly** PolyList, + int32 nPolys, + int32 Outside +) +{ + FMemMark Mark(FMemStack::Get()); + FBspNode& Node = Model->Nodes [iNode]; + FBspSurf& Surf = Model->Surfs [Node.iSurf]; + FVector Base = Surf.Plane * Surf.Plane.W; + FVector& Normal = Model->Vectors[Surf.vNormal]; + FBox Bound(ForceInit); + + Bound.Min.X = Bound.Min.Y = Bound.Min.Z = +WORLD_MAX; + Bound.Max.X = Bound.Max.Y = Bound.Max.Z = -WORLD_MAX; + + // Split bound into front half and back half. + FPoly** FrontList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nFront=0; + FPoly** BackList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nBack=0; + + // Keeping track of allocated FPoly structures to delete later on. + TArray AllocatedFPolys; + + FPoly* FrontPoly = new FPoly; + FPoly* BackPoly = new FPoly; + + // Keep track of allocations. + AllocatedFPolys.Add( FrontPoly ); + AllocatedFPolys.Add( BackPoly ); + + for( int32 i=0; iSplitWithPlane( Base, Normal, FrontPoly, BackPoly, 0 ) ) + { + case SP_Coplanar: +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got coplanar") ); + FrontList[nFront++] = Poly; + BackList[nBack++] = Poly; + break; + + case SP_Front: + FrontList[nFront++] = Poly; + break; + + case SP_Back: + BackList[nBack++] = Poly; + break; + + case SP_Split: + FrontList[nFront++] = FrontPoly; + BackList [nBack++] = BackPoly; + + FrontPoly = new FPoly; + BackPoly = new FPoly; + + // Keep track of allocations. + AllocatedFPolys.Add( FrontPoly ); + AllocatedFPolys.Add( BackPoly ); + + break; + + default: + UE_LOG(LogBSPOps, Fatal, TEXT("FZoneFilter::FilterToLeaf: Unknown split code") ); + } + } + if( nFront && nBack ) + { + // Add partitioner plane to front and back. + FPoly InfiniteEdPoly = FHBSPOps::BuildInfiniteFPoly( Model, iNode ); + InfiniteEdPoly.iBrushPoly = iNode; + + SplitPartitioner(Model,PolyList,FrontList,BackList,0,nPolys,nFront,nBack,InfiniteEdPoly,AllocatedFPolys); + } + else + { +// if( !nFront ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty fronthull") ); +// if( !nBack ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty backhull") ); + } + + // Recursively update all our childrens' bounding volumes. + if( nFront > 0 ) + { + if( Node.iFront != INDEX_NONE ) + FilterBound( Model, &Bound, Node.iFront, FrontList, nFront, Outside || Node.IsCsg() ); + else if( Outside || Node.IsCsg() ) + UpdateBoundWithPolys( Bound, FrontList, nFront ); + else + UpdateConvolutionWithPolys( Model, iNode, FrontList, nFront ); + } + if( nBack > 0 ) + { + if( Node.iBack != INDEX_NONE) + FilterBound( Model, &Bound,Node.iBack, BackList, nBack, Outside && !Node.IsCsg() ); + else if( Outside && !Node.IsCsg() ) + UpdateBoundWithPolys( Bound, BackList, nBack ); + else + UpdateConvolutionWithPolys( Model, iNode, BackList, nBack ); + } + + // Update parent bound to enclose this bound. + if( ParentBound ) + *ParentBound += Bound; + + // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. + for( int32 i=0; i0); + + // No need to test if only one poly. + if( NumPolys==1 ) + return PolyList[0]; + + FPoly *Poly, *Best=NULL; + float Score, BestScore; + int32 i, Index, j, Inc; + int32 Splits, Front, Back, Coplanar, AllSemiSolids; + + //PortalBias -- added by Legend on 4/12/2000 + float PortalBias = InPortalBias / 100.0f; + Balance &= 0xFF; // keep only the low byte to recover "Balance" + //UE_LOG(LogBSPOps, Log, TEXT("Balance=%d PortalBias=%f"), Balance, PortalBias ); + + if (Opt==FHBSPOps::BSP_Optimal) Inc = 1; // Test lots of nodes. + else if (Opt==FHBSPOps::BSP_Good) Inc = FMath::Max(1,NumPolys/20); // Test 20 nodes. + else /* BSP_Lame */ Inc = FMath::Max(1,NumPolys/4); // Test 4 nodes. + + // See if there are any non-semisolid polygons here. + for( i=0; iPolyFlags & PF_AddLast) ) + break; + AllSemiSolids = (i>=NumPolys); + + // Search through all polygons in the pool and find: + // A. The number of splits each poly would make. + // B. The number of front and back nodes the polygon would create. + // C. Number of coplanars. + BestScore = 0; + for( i=0; iPolyFlags & PF_AddLast) && !(Poly->PolyFlags & PF_Portal) ) + && !AllSemiSolids ); + if( Index>=i+Inc || Index>=NumPolys ) + continue; + + for( j=0; jSplitWithPlaneFast( FPlane( Poly->Vertices[0], Poly->Normal), NULL, NULL ) ) + { + case SP_Coplanar: + Coplanar++; + break; + + case SP_Front: + Front++; + break; + + case SP_Back: + Back++; + break; + + case SP_Split: + // Disfavor splitting polys that are zone portals. + if( !(OtherPoly->PolyFlags & PF_Portal) ) + Splits++; + else + Splits += 16; + break; + } + } + // added by Legend 1/31/1999 + // Score optimization: minimize cuts vs. balance tree (as specified in BSP Rebuilder dialog) + Score = ( 100.0 - float(Balance) ) * Splits + float(Balance) * FMath::Abs( Front - Back ); + if( Poly->PolyFlags & PF_Portal ) + { + // PortalBias -- added by Legend on 4/12/2000 + // + // PortalBias enables level designers to control the effect of Portals on the BSP. + // This effect can range from 0.0 (ignore portals), to 1.0 (portals cut everything). + // + // In builds prior to this (since the 221 build dating back to 1/31/1999) the bias + // has been 1.0 causing the portals to cut the BSP in ways that will potentially + // degrade level performance, and increase the BSP complexity. + // + // By setting the bias to a value between 0.3 and 0.7 the positive effects of + // the portals are preserved without giving them unreasonable priority in the BSP. + // + // Portals should be weighted high enough in the BSP to separate major parts of the + // level from each other (pushing entire rooms down the branches of the BSP), but + // should not be so high that portals cut through adjacent geometry in a way that + // increases complexity of the room being (typically, accidentally) cut. + // + Score -= ( 100.0 - float(Balance) ) * Splits * PortalBias; // ignore PortalBias of the split polys -- bias toward portal selection for cutting planes! + } + //UE_LOG(LogBSPOps, Log, " %4d: Score = %f (Front = %4d, Back = %4d, Splits = %4d, Flags = %08X)", Index, Score, Front, Back, Splits, Poly->PolyFlags ); //LEC + + if( Score AllocatedFPolys; + + // To account for big EdPolys split up. + int32 NumPolysToAlloc = NumPolys + 8 + NumPolys/4; + int32 NumFront=0; FPoly **FrontList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; + int32 NumBack =0; FPoly **BackList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; + + FPoly *SplitPoly = FindBestSplit( NumPolys, PolyList, Opt, Balance, PortalBias ); + + // Add the splitter poly to the Bsp with either a new BspSurf or an existing one. + if( RebuildSimplePolys ) + { + SplitPoly->iLinkSurf = Model->Surfs.Num(); + } + + int32 iOurNode = bspAddNode(Model,iParent,NodePlace,0,SplitPoly, BspPoints, BspVectors); + int32 iPlaneNode = iOurNode; + + // Now divide all polygons in the pool into (A) polygons that are + // in front of Poly, and (B) polygons that are in back of Poly. + // Coplanar polys are inserted immediately, before recursing. + + // If any polygons are split by Poly, we ignrore the original poly, + // split it into two polys, and add two new polys to the pool. + FPoly *FrontEdPoly = new FPoly; + FPoly *BackEdPoly = new FPoly; + // Keep track of allocations. + AllocatedFPolys.Add( FrontEdPoly ); + AllocatedFPolys.Add( BackEdPoly ); + + for( int32 i=0; iSplitWithPlane( SplitPoly->Vertices[0], SplitPoly->Normal, FrontEdPoly, BackEdPoly, 0 ) ) + { + case SP_Coplanar: + if( RebuildSimplePolys ) + { + EdPoly->iLinkSurf = Model->Surfs.Num()-1; + } + iPlaneNode = bspAddNode( Model, iPlaneNode, NODE_Plane, 0, EdPoly, BspPoints, BspVectors ); + break; + + case SP_Front: + FrontList[NumFront++] = PolyList[i]; + break; + + case SP_Back: + BackList[NumBack++] = PolyList[i]; + break; + + case SP_Split: + + // Create front & back nodes. + FrontList[NumFront++] = FrontEdPoly; + BackList [NumBack ++] = BackEdPoly; + + FrontEdPoly = new FPoly; + BackEdPoly = new FPoly; + // Keep track of allocations. + AllocatedFPolys.Add( FrontEdPoly ); + AllocatedFPolys.Add( BackEdPoly ); + + break; + } + } + + // Recursively split the front and back pools. + if( NumFront > 0 ) SplitPolyList( Model, iOurNode, NODE_Front, NumFront, FrontList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); + if( NumBack > 0 ) SplitPolyList( Model, iOurNode, NODE_Back, NumBack, BackList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); + + // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. + for( int32 i=0; iGetName() ); // moved here so that we can easily debug when an actor has lost parts of its brush + + check(Actor->GetBrushComponent()); + check(Actor->Brush); + check(Actor->Brush->RootOutside); + + RebuildBrush(Actor->Brush, BspPoints, BspVectors); + + // Make sure simplified collision is up to date. + Actor->GetBrushComponent()->BuildSimpleBrushCollision(); + Actor->RebuildNavigationData(); +} + +/** + * Duplicates the specified brush and makes it into a CSG-able level brush. + * @return The new brush, or NULL if the original was empty. + */ +void FHBSPOps::csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + check(Src); + check(Src->GetBrushComponent()); + check(Src->Brush); + + // Handle empty brush. + if( !bAllowEmpty && !Src->Brush->Polys->Element.Num() ) + { + Dest->Brush = NULL; + Dest->GetBrushComponent()->Brush = NULL; + return; + } + + // Duplicate the brush and its polys. + Dest->PolyFlags = PolyFlags; + Dest->Brush = NewObject(Dest, NAME_None, ResFlags); + Dest->Brush->Initialize(nullptr, Src->Brush->RootOutside); + Dest->Brush->Polys = NewObject(Dest->Brush, NAME_None, ResFlags); + Dest->Brush->Polys->Element = Src->Brush->Polys->Element; + Dest->GetBrushComponent()->Brush = Dest->Brush; + if(Src->BrushBuilder != nullptr) + { + Dest->BrushBuilder = DuplicateObject(Src->BrushBuilder, Dest); + } + + // Update poly textures. + for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) + { + Dest->Brush->Polys->Element[i].iBrushPoly = INDEX_NONE; + } + + // Copy positioning, and build bounding box. + if(bCopyPosRotScale) + { + Dest->CopyPosRotScaleFrom( Src ); + } + + // If it's a moving brush, prep it. + if( bNeedsPrep ) + { + csgPrepMovingBrush( Dest, BspPoints, BspVectors ); + } +} + +/** + * Adds a brush to the list of CSG brushes in the level, using a CSG operation. + * + * @return A newly-created copy of the brush. + */ +ABrush* FHBSPOps::csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + check(Actor); + check(Actor->GetBrushComponent()); + check(Actor->Brush); + check(Actor->Brush->Polys); + check(Actor->GetWorld()); + + // Can't do this if brush has no polys. + if( !Actor->Brush->Polys->Element.Num() ) + return NULL; + + // Spawn a new actor for the brush. + + ABrush* Result = Actor->GetWorld()->SpawnBrush(); + Result->SetNotForClientOrServer(); + + // Duplicate the brush. + csgCopyBrush + ( + Result, + Actor, + PolyFlags, + RF_Transactional, + 0, + true, + false, + BspPoints, + BspVectors + ); + check(Result->Brush); + + if( Result->GetBrushBuilder() ) + { + FActorLabelUtilities::SetActorLabelUnique(Result, FText::Format(NSLOCTEXT("BSPBrushOps", "BrushName", "{0} Brush"), FText::FromString(Result->GetBrushBuilder()->GetClass()->GetDescription())).ToString()); + } + // Assign the default material to the brush's polys. + for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) + { + FPoly& CurrentPoly = Result->Brush->Polys->Element[i]; + if ( !CurrentPoly.Material ) + { + CurrentPoly.Material = UMaterial::GetDefaultMaterial(MD_Surface); + } + } + + // Set add-info. + Result->BrushType = BrushType; + + Result->ReregisterAllComponents(); + + return Result; +} + +/** Add a new point to the model (preventing duplicates) and return its index. */ +static int32 AddThing( TArray& Vectors, FVector& V, float Thresh, int32 Check ) +{ + if( Check ) + { + // See if this is very close to an existing point/vector. + for( int32 i=0; i -Thresh) && (Temp < Thresh) ) + { + Temp=(V.Y - TableVect.Y); + if( (Temp > -Thresh) && (Temp < Thresh) ) + { + Temp=(V.Z - TableVect.Z); + if( (Temp > -Thresh) && (Temp < Thresh) ) + { + // Found nearly-matching vector. + return i; + } + } + } + } + } + return Vectors.Add( V ); +} + +/** Add a new vector to the model, merging near-duplicates, and return its index. */ +int32 FHBSPOps::bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ) +{ + const float Thresh = Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR; + + if (BspVectors) + { + // If a points grid has been built for quick vector lookup, use that instead of doing a linear search + const int32 NextIndex = Model->Vectors.Num(); + const int32 ReturnedIndex = BspVectors->FindOrAddPoint(*V, NextIndex, Thresh); + if (ReturnedIndex == NextIndex) + { + Model->Vectors.Add(*V); + } + + return ReturnedIndex; + } + + return AddThing + ( + Model->Vectors, + *V, + Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR, + 1 + ); +} + +/** Add a new point to the model, merging near-duplicates, and return its index. */ +int32 FHBSPOps::bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ) +{ + const float Thresh = Exact ? THRESH_POINTS_ARE_SAME : THRESH_POINTS_ARE_NEAR; + + if (BspPoints) + { + // If a points grid has been built for quick point lookup, use that instead of doing a linear search + const int32 NextIndex = Model->Points.Num(); + // Always look for points with a low threshold; a generous threshold can result in 'leaks' in the BSP and unwanted polys being generated + const int32 ReturnedIndex = BspPoints->FindOrAddPoint(*V, NextIndex, THRESH_POINTS_ARE_SAME); + if (ReturnedIndex == NextIndex) + { + Model->Points.Add(*V); + } + + return ReturnedIndex; + } + + // Try to find a match quickly from the Bsp. This finds all potential matches + // except for any dissociated from nodes/surfaces during a rebuild. + FVector Temp; + int32 pVertex; + float NearestDist = Model->FindNearestVertex(*V,Temp,Thresh,pVertex); + if( (NearestDist >= 0.0) && (NearestDist <= Thresh) ) + { + // Found an existing point. + return pVertex; + } + else + { + // No match found; add it slowly to find duplicates. + return AddThing(Model->Points, *V, Thresh, !GFastRebuild); + } +} + + +/** + * Builds Bsp from the editor polygon set (EdPolys) of a model. + * + * Opt = Bsp optimization, BSP_Lame (fast), BSP_Good (medium), BSP_Optimal (slow) + * Balance = 0-100, 0=only worry about minimizing splits, 100=only balance tree. + */ +void FHBSPOps::bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + int32 OriginalPolys = Model->Polys->Element.Num(); + + // Empty the model's tables. + if( RebuildSimplePolys==1 ) + { + // Empty everything but polys. + Model->EmptyModel( 1, 0 ); + } + else if( RebuildSimplePolys==0 ) + { + // Empty node vertices. + for( int32 i=0; iNodes.Num(); i++ ) + Model->Nodes[i].NumVertices = 0; + + // Refresh the Bsp. + bspRefresh(Model,1); + + // Empty nodes. + Model->EmptyModel( 0, 0 ); + } + if( Model->Polys->Element.Num() ) + { + // Allocate polygon pool. + FMemMark Mark(FMemStack::Get()); + FPoly** PolyList = new( FMemStack::Get(), Model->Polys->Element.Num() )FPoly*; + + // Add all FPolys to active list. + for( int32 i=0; iPolys->Element.Num(); i++ ) + if( Model->Polys->Element[i].Vertices.Num() ) + PolyList[i] = &Model->Polys->Element[i]; + + // Now split the entire Bsp by splitting the list of all polygons. + SplitPolyList + ( + Model, + INDEX_NONE, + NODE_Root, + Model->Polys->Element.Num(), + PolyList, + Opt, + Balance, + PortalBias, + RebuildSimplePolys, + BspPoints, + BspVectors + ); + + // Now build the bounding boxes for all nodes. + if( RebuildSimplePolys==0 ) + { + // Remove unreferenced things. + bspRefresh( Model, 1 ); + + // Rebuild all bounding boxes. + bspBuildBounds( Model ); + } + + Mark.Pop(); + } + +// UE_LOG(LogBSPOps, Log, TEXT("bspBuild built %i convex polys into %i nodes"), OriginalPolys, Model->Nodes.Num() ); +} + +/** + * If the Bsp's point and vector tables are nearly full, reorder them and delete unused ones. + */ +void FHBSPOps::bspRefresh( UModel* Model, bool NoRemapSurfs ) +{ + FMemStack& MemStack = FMemStack::Get(); + + FMemMark Mark(MemStack); + + int32 NumNodes = Model->Nodes.Num(); + int32 NumSurfs = Model->Surfs.Num(); + int32 NumVectors = Model->Vectors.Num(); + int32 NumPoints = Model->Points.Num(); + + // Remove unreferenced Bsp surfs. + int32* PolyRef; + if( NoRemapSurfs ) + { + PolyRef = NewZeroed(MemStack, NumSurfs); + } + else + { + PolyRef = NewOned(MemStack, NumSurfs); + } + + int32* NodeRef = NewOned(MemStack, NumNodes); + if( NumNodes > 0 ) + { + TagReferencedNodes( Model, NodeRef, PolyRef, 0 ); + } + + // Remap Bsp surfs. + { + int32 n=0; + for( int32 i=0; iSurfs[n] = Model->Surfs[i]; + PolyRef[i]=n++; + } + } + //UE_LOG(LogBSPOps, Log, TEXT("Polys: %i -> %i"), NumSurfs, n ); + Model->Surfs.RemoveAt( n, NumSurfs-n ); + NumSurfs = n; + } + + // Remap Bsp nodes. + { + int32 n=0; + for( int32 i=0; iNodes[n] = Model->Nodes[i]; + NodeRef[i]=n++; + } + } + //UE_LOG(LogBSPOps, Log, TEXT("Nodes: %i -> %i"), NumNodes, n ); + Model->Nodes.RemoveAt( n, NumNodes-n ); + NumNodes = n; + } + + // Update Bsp nodes. + for( int32 i=0; iNodes[i]; + Node->iSurf = PolyRef[Node->iSurf]; + if (Node->iFront != INDEX_NONE) Node->iFront = NodeRef[Node->iFront]; + if (Node->iBack != INDEX_NONE) Node->iBack = NodeRef[Node->iBack]; + if (Node->iPlane != INDEX_NONE) Node->iPlane = NodeRef[Node->iPlane]; + } + + // Remove unreferenced points and vectors. + int32* VectorRef = NewOned(MemStack, NumVectors); + int32* PointRef = NewOned(MemStack, NumPoints); + + // Check Bsp surfs. + TArray VertexRef; + for( int32 i=0; iSurfs[i]; + VectorRef [Surf->vNormal ] = 0; + VectorRef [Surf->vTextureU ] = 0; + VectorRef [Surf->vTextureV ] = 0; + PointRef [Surf->pBase ] = 0; + } + + // Check Bsp nodes. + for( int32 i=0; iNodes[i]; + FVert* VertPool = &Model->Verts[Node->iVertPool]; + for( int B=0; BNumVertices; B++ ) + { + PointRef[VertPool->pVertex] = 0; + VertPool++; + } + } + + // Remap points. + { + int32 n=0; + for( int32 i=0; iPoints[n] = Model->Points[i]; + PointRef[i] = n++; + } + //UE_LOG(LogBSPOps, Log, TEXT("Points: %i -> %i"), NumPoints, n ); + Model->Points.RemoveAt( n, NumPoints-n ); + NumPoints = n; + } + + // Remap vectors. + { + int32 n=0; + for (int32 i=0; iVectors[n] = Model->Vectors[i]; + VectorRef[i] = n++; + } + //UE_LOG(LogBSPOps, Log, TEXT("Vectors: %i -> %i"), NumVectors, n ); + Model->Vectors.RemoveAt( n, NumVectors-n ); + NumVectors = n; + } + + // Update Bsp surfs. + for( int32 i=0; iSurfs[i]; + Surf->vNormal = VectorRef [Surf->vNormal ]; + Surf->vTextureU = VectorRef [Surf->vTextureU]; + Surf->vTextureV = VectorRef [Surf->vTextureV]; + Surf->pBase = PointRef [Surf->pBase ]; + } + + // Update Bsp nodes. + for( int32 i=0; iNodes[i]; + FVert* VertPool = &Model->Verts[Node->iVertPool]; + for( int B=0; BNumVertices; B++ ) + { + VertPool->pVertex = PointRef [VertPool->pVertex]; + VertPool++; + } + } + + // Shrink the objects. + Model->ShrinkModel(); + + Mark.Pop(); +} + +// Build bounding volumes for all Bsp nodes. The bounding volume of the node +// completely encloses the "outside" space occupied by the nodes. Note that +// this is not the same as representing the bounding volume of all of the +// polygons within the node. +// +// We start with a practically-infinite cube and filter it down the Bsp, +// whittling it away until all of its convex volume fragments land in leaves. +void FHBSPOps::bspBuildBounds( UModel* Model ) +{ + if( Model->Nodes.Num()==0 ) + return; + + FPoly Polys[6], *PolyList[6]; + for( int32 i=0; i<6; i++ ) + { + PolyList[i] = &Polys[i]; + PolyList[i]->Init(); + PolyList[i]->iBrushPoly = INDEX_NONE; + } + + new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); + Polys[0].Normal =FVector( 0.000000, 0.000000, 1.000000 ); + Polys[0].Base =Polys[0].Vertices[0]; + + new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[1].Normal =FVector( 0.000000, 0.000000, -1.000000 ); + Polys[1].Base =Polys[1].Vertices[0]; + + new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[2].Normal =FVector( 0.000000, 1.000000, 0.000000 ); + Polys[2].Base =Polys[2].Vertices[0]; + + new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[3].Normal =FVector( 0.000000, -1.000000, 0.000000 ); + Polys[3].Base =Polys[3].Vertices[0]; + + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[4].Normal =FVector( 1.000000, 0.000000, 0.000000 ); + Polys[4].Base =Polys[4].Vertices[0]; + + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[5].Normal =FVector(-1.000000, 0.000000, 0.000000 ); + Polys[5].Base =Polys[5].Vertices[0]; + // Empty hulls. + Model->LeafHulls.Empty(); + for( int32 i=0; iNodes.Num(); i++ ) + Model->Nodes[i].iCollisionBound = INDEX_NONE; + FilterBound( Model, NULL, 0, PolyList, 6, Model->RootOutside ); +// UE_LOG(LogBSPOps, Log, TEXT("bspBuildBounds: Generated %i hulls"), Model->LeafHulls.Num() ); +} + +/** + * Validate a brush, and set iLinks on all EdPolys to index of the + * first identical EdPoly in the list, or its index if it's the first. + * Not transactional. + */ +void FHBSPOps::bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ) +{ + check(Brush != nullptr); + Brush->Modify(); + if( ForceValidate || !Brush->Linked ) + { + Brush->Linked = 1; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + Brush->Polys->Element[i].iLink = i; + } + int32 n=0; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + FPoly* EdPoly = &Brush->Polys->Element[i]; + if( EdPoly->iLink==i ) + { + for( int32 j=i+1; jPolys->Element.Num(); j++ ) + { + FPoly* OtherPoly = &Brush->Polys->Element[j]; + if + ( OtherPoly->iLink == j + && OtherPoly->Material == EdPoly->Material + && OtherPoly->TextureU == EdPoly->TextureU + && OtherPoly->TextureV == EdPoly->TextureV + && OtherPoly->PolyFlags == EdPoly->PolyFlags + && (OtherPoly->Normal | EdPoly->Normal)>0.9999 ) + { + float Dist = FVector::PointPlaneDist( OtherPoly->Vertices[0], EdPoly->Vertices[0], EdPoly->Normal ); + if( Dist>-0.001 && Dist<0.001 ) + { + OtherPoly->iLink = i; + n++; + } + } + } + } + } +// UE_LOG(LogBSPOps, Log, TEXT("BspValidateBrush linked %i of %i polys"), n, Brush->Polys->Element.Num() ); + } + + // Build bounds. + Brush->BuildBound(); +} + +void FHBSPOps::bspUnlinkPolys( UModel* Brush ) +{ + Brush->Modify(); + Brush->Linked = 1; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + Brush->Polys->Element[i].iLink = i; + } +} + +// Add an editor polygon to the Bsp, and also stick a reference to it +// in the editor polygon's BspNodes list. If the editor polygon has more sides +// than the Bsp will allow, split it up into several sub-polygons. +// +// Returns: Index to newly-created node of Bsp. If several nodes were created because +// of split polys, returns the parent (highest one up in the Bsp). +int32 FHBSPOps::bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + if( NodePlace == NODE_Plane ) + { + // Make sure coplanars are added at the end of the coplanar list so that + // we don't insert NF_IsNew nodes with non NF_IsNew coplanar children. + while( Model->Nodes[iParent].iPlane != INDEX_NONE ) + { + iParent = Model->Nodes[iParent].iPlane; + } + } + FBspSurf* Surf = NULL; + if( EdPoly->iLinkSurf == Model->Surfs.Num() ) + { + int32 NewIndex = Model->Surfs.AddZeroed(); + Surf = &Model->Surfs[NewIndex]; + + // This node has a new polygon being added by bspBrushCSG; must set its properties here. + Surf->pBase = bspAddPoint (Model,&EdPoly->Base,1,BspPoints); + Surf->vNormal = bspAddVector (Model,&EdPoly->Normal,1,BspVectors); + Surf->vTextureU = bspAddVector (Model,&EdPoly->TextureU,0,BspVectors); + Surf->vTextureV = bspAddVector (Model,&EdPoly->TextureV,0,BspVectors); + Surf->Material = EdPoly->Material; + Surf->Actor = NULL; + + Surf->PolyFlags = EdPoly->PolyFlags & ~PF_NoAddToBSP; + Surf->LightMapScale= EdPoly->LightMapScale; + + // Find the LightmassPrimitiveSettings in the UModel... + int32 FoundLightmassIndex = INDEX_NONE; + if (Model->LightmassSettings.Find(EdPoly->LightmassSettings, FoundLightmassIndex) == false) + { + FoundLightmassIndex = Model->LightmassSettings.Add(EdPoly->LightmassSettings); + } + Surf->iLightmassIndex = FoundLightmassIndex; + + Surf->Actor = EdPoly->Actor; + Surf->iBrushPoly = EdPoly->iBrushPoly; + + if (EdPoly->Actor) + { + Surf->bHiddenEdTemporary = EdPoly->Actor->IsTemporarilyHiddenInEditor(); + Surf->bHiddenEdLevel = EdPoly->Actor->bHiddenEdLevel; + Surf->bHiddenEdLayer = EdPoly->Actor->bHiddenEdLayer; + } + + Surf->Plane = FPlane(EdPoly->Vertices[0],EdPoly->Normal); + } + else + { + check(EdPoly->iLinkSurf!=INDEX_NONE); + check(EdPoly->iLinkSurfSurfs.Num()); + Surf = &Model->Surfs[EdPoly->iLinkSurf]; + } + + // Set NodeFlags. + if( Surf->PolyFlags & PF_NotSolid ) NodeFlags |= NF_NotCsg; + if( Surf->PolyFlags & (PF_Invisible|PF_Portal) ) NodeFlags |= NF_NotVisBlocking; + + if( EdPoly->Vertices.Num() > FBspNode::MAX_NODE_VERTICES ) + { + // Split up into two coplanar sub-polygons (one with MAX_NODE_VERTICES vertices and + // one with all the remaining vertices) and recursively add them. + + // EdPoly1 is just the first MAX_NODE_VERTICES from EdPoly. + FMemMark Mark(FMemStack::Get()); + FPoly *EdPoly1 = new FPoly; + *EdPoly1 = *EdPoly; + EdPoly1->Vertices.RemoveAt(FBspNode::MAX_NODE_VERTICES,EdPoly->Vertices.Num() - FBspNode::MAX_NODE_VERTICES); + + // EdPoly2 is the first vertex from EdPoly, and the last EdPoly->Vertices.Num() - MAX_NODE_VERTICES + 1. + FPoly *EdPoly2 = new FPoly; + *EdPoly2 = *EdPoly; + EdPoly2->Vertices.RemoveAt(1,FBspNode::MAX_NODE_VERTICES - 2); + + int32 iNode = bspAddNode( Model, iParent, NodePlace, NodeFlags, EdPoly1, BspPoints, BspVectors ); // Add this poly first. + bspAddNode( Model, iNode, NODE_Plane, NodeFlags, EdPoly2, BspPoints, BspVectors ); // Then add other (may be bigger). + + delete EdPoly1; + delete EdPoly2; + + Mark.Pop(); + return iNode; // Return coplanar "parent" node (not coplanar child) + } + else + { + // Add node. + int32 iNode = Model->Nodes.AddZeroed(); + FBspNode& Node = Model->Nodes[iNode]; + + // Tell transaction tracking system that parent is about to be modified. + FBspNode* Parent=NULL; + if( NodePlace!=NODE_Root ) + Parent = &Model->Nodes[iParent]; + + // Set node properties. + Node.iSurf = EdPoly->iLinkSurf; + Node.NodeFlags = NodeFlags; + Node.iCollisionBound = INDEX_NONE; + Node.Plane = FPlane( EdPoly->Vertices[0], EdPoly->Normal ); + Node.iVertPool = Model->Verts.AddUninitialized(EdPoly->Vertices.Num()); + Node.iFront = INDEX_NONE; + Node.iBack = INDEX_NONE; + Node.iPlane = INDEX_NONE; + if( NodePlace==NODE_Root ) + { + Node.iLeaf[0] = INDEX_NONE; + Node.iLeaf[1] = INDEX_NONE; + Node.iZone[0] = 0; + Node.iZone[1] = 0; + } + else if( NodePlace==NODE_Front || NodePlace==NODE_Back ) + { + int32 ZoneFront=NodePlace==NODE_Front; + Node.iLeaf[0] = Parent->iLeaf[ZoneFront]; + Node.iLeaf[1] = Parent->iLeaf[ZoneFront]; + Node.iZone[0] = Parent->iZone[ZoneFront]; + Node.iZone[1] = Parent->iZone[ZoneFront]; + } + else + { + int32 IsFlipped = (Node.Plane|Parent->Plane)<0.0; + Node.iLeaf[0] = Parent->iLeaf[IsFlipped ]; + Node.iLeaf[1] = Parent->iLeaf[1-IsFlipped]; + Node.iZone[0] = Parent->iZone[IsFlipped ]; + Node.iZone[1] = Parent->iZone[1-IsFlipped]; + } + + // Link parent to this node. + if (NodePlace == NODE_Front) + { + Parent->iFront = iNode; + } + else if (NodePlace == NODE_Back) + { + Parent->iBack = iNode; + } + else if (NodePlace == NODE_Plane) + { + Parent->iPlane = iNode; + } + + // Add all points to point table, merging nearly-overlapping polygon points + // with other points in the poly to prevent criscrossing vertices from + // being generated. + + // Must maintain Node->NumVertices on the fly so that bspAddPoint is always + // called with the Bsp in a clean state. + Node.NumVertices = 0; + FVert* VertPool = &Model->Verts[ Node.iVertPool ]; + for( uint8 i=0; iVertices.Num(); i++ ) + { + int32 pVertex = bspAddPoint(Model,&EdPoly->Vertices[i],0, BspPoints); + if( Node.NumVertices==0 || VertPool[Node.NumVertices-1].pVertex!=pVertex ) + { + VertPool[Node.NumVertices].iSide = INDEX_NONE; + VertPool[Node.NumVertices].pVertex = pVertex; + Node.NumVertices++; + } + } + if( Node.NumVertices>=2 && VertPool[0].pVertex==VertPool[Node.NumVertices-1].pVertex ) + { + Node.NumVertices--; + } + if( Node.NumVertices < 3 ) + { + GErrors++; +// UE_LOG(LogBSPOps, Warning, TEXT("bspAddNode: Infinitesimal polygon %i (%i)"), Node.NumVertices, EdPoly->Vertices.Num() ); + Node.NumVertices = 0; + } + + return iNode; + } +} + +/** + * Rebuild some brush internals + */ +void FHBSPOps::RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + Brush->Modify(); + Brush->EmptyModel(1, 0); + + // Build bounding box. + Brush->BuildBound(); + + // Build BSP for the brush. + bspBuild(Brush, BSP_Good, 15, 70, 1, 0, BspPoints, BspVectors); + bspRefresh(Brush, 1); + bspBuildBounds(Brush); +} + +/** + * Rotates the specified brush's vertices. + */ +void FHBSPOps::RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + if(Brush->GetBrushComponent()->Brush && Brush->GetBrushComponent()->Brush->Polys) + { + for( int32 poly = 0 ; poly < Brush->GetBrushComponent()->Brush->Polys->Element.Num() ; poly++ ) + { + FPoly* Poly = &(Brush->GetBrushComponent()->Brush->Polys->Element[poly]); + + // Rotate the vertices. + const FRotationMatrix RotMatrix( Rotation ); + for( int32 vertex = 0 ; vertex < Poly->Vertices.Num() ; vertex++ ) + { + Poly->Vertices[vertex] = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Vertices[vertex] - Brush->GetPivotOffset()); + } + Poly->Base = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Base - Brush->GetPivotOffset()); + + // Rotate the texture vectors. + Poly->TextureU = RotMatrix.TransformVector( Poly->TextureU ); + Poly->TextureV = RotMatrix.TransformVector( Poly->TextureV ); + + // Recalc the normal for the poly. + Poly->Normal = FVector::ZeroVector; + Poly->Finalize(Brush,0); + } + + Brush->GetBrushComponent()->Brush->BuildBound(); + + if( !Brush->IsStaticBrush() ) + { + csgPrepMovingBrush( Brush, BspPoints, BspVectors ); + } + + if ( bClearComponents ) + { + Brush->ReregisterAllComponents(); + } + } +} + + +void FHBSPOps::HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + // The default physics volume doesn't have an associated UModel, so we need to handle that case gracefully. + if(Volume.Brush) + { + FHBSPOps::csgPrepMovingBrush( &Volume, BspPoints, BspVectors); + } +} + +UHBspPointsGrid* UHBspPointsGrid::Create(float InGranularity, float InThreshold, int32 InitialSize) +{ + check(InThreshold / InGranularity <= 0.5f); + + UHBspPointsGrid* Obj = NewObject(GetTransientPackage(), UHBspPointsGrid::StaticClass()); + Obj->OneOverGranularity = 1.0f / InGranularity; + Obj->Threshold = InThreshold; + Obj->Clear(InitialSize); + + return Obj; +} + +void UHBspPointsGrid::Clear(int32 InitialSize) +{ + GridMap.Empty(InitialSize); +} + + +// Given a grid index in one axis, a real position on the grid and a threshold radius, +// return either: +// - the additional grid index it can overlap in that axis, or +// - the original grid index if there is no overlap. +int32 UHBspPointsGrid::GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold) +{ + if (GridPos - GridIndex < GridThreshold) + { + return GridIndex - 1; + } + else if (1.0f - (GridPos - GridIndex) < GridThreshold) + { + return GridIndex + 1; + } + else + { + return GridIndex; + } +} + +int32 UHBspPointsGrid::FindOrAddPoint(const FVector& Point, int32 Index, float PointThreshold) +{ + // Offset applied to the grid coordinates so aligned vertices (the normal case) don't overlap several grid items (taking into account the threshold) + const float GridOffset = 0.12345f; + + const float AdjustedPointX = Point.X - GridOffset; + const float AdjustedPointY = Point.Y - GridOffset; + const float AdjustedPointZ = Point.Z - GridOffset; + + const float GridX = AdjustedPointX * OneOverGranularity; + const float GridY = AdjustedPointY * OneOverGranularity; + const float GridZ = AdjustedPointZ * OneOverGranularity; + + // Get the grid indices corresponding to the point coordinates + const int32 GridIndexX = FMath::FloorToInt(GridX); + const int32 GridIndexY = FMath::FloorToInt(GridY); + const int32 GridIndexZ = FMath::FloorToInt(GridZ); + + // Find grid item in map + FHBspPointsGridItem& GridItem = GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, GridIndexZ)); + + // Iterate through grid item points and return a point if it's close to the threshold + const float PointThresholdSquared = PointThreshold * PointThreshold; + for (const FHBspIndexedPoint& IndexedPoint : GridItem.IndexedPoints) + { + if (FVector::DistSquared(IndexedPoint.Point, Point) <= PointThresholdSquared) + { + return IndexedPoint.Index; + } + } + + // Otherwise, the point is new: add it to the grid item. + GridItem.IndexedPoints.Emplace(Point, Index); + + // The grid has a maximum threshold of a certain radius. If the point is near the edge of a grid cube, it may overlap into other items. + // Add it to all grid items it can be seen from. + const float GridThreshold = Threshold * OneOverGranularity; + const int32 NeighbourX = GetAdjacentIndexIfOverlapping(GridIndexX, GridX, GridThreshold); + const int32 NeighbourY = GetAdjacentIndexIfOverlapping(GridIndexY, GridY, GridThreshold); + const int32 NeighbourZ = GetAdjacentIndexIfOverlapping(GridIndexZ, GridZ, GridThreshold); + + const bool bOverlapsInX = (NeighbourX != GridIndexX); + const bool bOverlapsInY = (NeighbourY != GridIndexY); + const bool bOverlapsInZ = (NeighbourZ != GridIndexZ); + + if (bOverlapsInX) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInY) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else + { + if (bOverlapsInY) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + + return Index; +} + + diff --git a/Source/HoudiniEngine/Private/HBSPOps.h b/Source/HoudiniEngine/Private/HBSPOps.h index 5df41616e..0e843ecd3 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.h +++ b/Source/HoudiniEngine/Private/HBSPOps.h @@ -1,157 +1,157 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Engine/Brush.h" -#include "Engine/Polys.h" - -#include "HBSPOps.generated.h" - -class AVolume; -class UModel; - -// This codebase have been localised from UnrealEd/HBSPOps to remove static/global variables. -class FHBSPOps -{ -public: - FHBSPOps(); - - /** Quality level for rebuilding Bsp. */ - enum EBspOptimization - { - BSP_Lame, - BSP_Good, - BSP_Optimal - }; - - /** Possible positions of a child Bsp node relative to its parent (for BspAddToNode) */ - enum ENodePlace - { - NODE_Back = 0, // Node is in back of parent -> Bsp[iParent].iBack. - NODE_Front = 1, // Node is in front of parent -> Bsp[iParent].iFront. - NODE_Plane = 2, // Node is coplanar with parent -> Bsp[iParent].iPlane. - NODE_Root = 3, // Node is the Bsp root and has no parent -> Bsp[0]. - }; - - static void csgPrepMovingBrush( ABrush* Actor, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static void csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static ABrush* csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - static int32 bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ); - static int32 bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ); - static void bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static void bspRefresh( UModel* Model, bool NoRemapSurfs ); - - static void bspBuildBounds( UModel* Model ); - - static void bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ); - static void bspUnlinkPolys( UModel* Brush ); - static int32 bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - /** - * Rebuild some brush internals - */ - static void RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - static FPoly BuildInfiniteFPoly( UModel* Model, int32 iNode ); - - /** - * Rotates the specified brush's vertices. - */ - static void RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - /** Called when an AVolume shape is changed*/ - static void HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - /** Errors encountered in Csg operation. */ - static int32 GErrors; - static bool GFastRebuild; - -protected: - static void SplitPolyList - ( - UModel *Model, - int32 iParent, - FHBSPOps::ENodePlace NodePlace, - int32 NumPolys, - FPoly **PolyList, - EBspOptimization Opt, - int32 Balance, - int32 PortalBias, - int32 RebuildSimplePolys, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); -}; - - -struct FHBspPointsKey -{ - int32 X; - int32 Y; - int32 Z; - - FHBspPointsKey(int32 InX, int32 InY, int32 InZ) - : X(InX) - , Y(InY) - , Z(InZ) - {} - - friend FORCEINLINE bool operator == (const FHBspPointsKey& A, const FHBspPointsKey& B) - { - return A.X == B.X && A.Y == B.Y && A.Z == B.Z; - } - - friend FORCEINLINE uint32 GetTypeHash(const FHBspPointsKey& Key) - { - return HashCombine(static_cast(Key.X), HashCombine(static_cast(Key.Y), static_cast(Key.Z))); - } -}; - -struct FHBspIndexedPoint -{ - FHBspIndexedPoint(const FVector& InPoint, int32 InIndex) - : Point(InPoint) - , Index(InIndex) - {} - - FVector Point; - int32 Index; -}; - - -struct FHBspPointsGridItem -{ - TArray> IndexedPoints; -}; - - -// Represents a sparse granular 3D grid into which points are added for quick (~O(1)) lookup. -// The 3D space is divided into a grid with a given granularity. -// Points are considered to have a given radius (threshold) and are added to the grid cube they fall in, and to up to seven neighbours if they overlap. -UCLASS() -class HOUDINIENGINE_API UHBspPointsGrid : public UObject -{ - GENERATED_BODY() -protected: - - UHBspPointsGrid() {} - -public: - // Create a new instance of this grid with the given arguments. - static UHBspPointsGrid* Create(float InGranularity, float InThreshold, int32 InitialSize = 0); - - void Clear(int32 InitialSize = 0); - - int32 FindOrAddPoint(const FVector& Point, int32 Index, float Threshold); - - static FORCEINLINE int32 GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold); - -private: - float OneOverGranularity; - float Threshold; - - typedef TMap FGridMap; - FGridMap GridMap; -}; +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Brush.h" +#include "Engine/Polys.h" + +#include "HBSPOps.generated.h" + +class AVolume; +class UModel; + +// This codebase have been localised from UnrealEd/HBSPOps to remove static/global variables. +class FHBSPOps +{ +public: + FHBSPOps(); + + /** Quality level for rebuilding Bsp. */ + enum EBspOptimization + { + BSP_Lame, + BSP_Good, + BSP_Optimal + }; + + /** Possible positions of a child Bsp node relative to its parent (for BspAddToNode) */ + enum ENodePlace + { + NODE_Back = 0, // Node is in back of parent -> Bsp[iParent].iBack. + NODE_Front = 1, // Node is in front of parent -> Bsp[iParent].iFront. + NODE_Plane = 2, // Node is coplanar with parent -> Bsp[iParent].iPlane. + NODE_Root = 3, // Node is the Bsp root and has no parent -> Bsp[0]. + }; + + static void csgPrepMovingBrush( ABrush* Actor, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static void csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static ABrush* csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + static int32 bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ); + static int32 bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ); + static void bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static void bspRefresh( UModel* Model, bool NoRemapSurfs ); + + static void bspBuildBounds( UModel* Model ); + + static void bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ); + static void bspUnlinkPolys( UModel* Brush ); + static int32 bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + /** + * Rebuild some brush internals + */ + static void RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + static FPoly BuildInfiniteFPoly( UModel* Model, int32 iNode ); + + /** + * Rotates the specified brush's vertices. + */ + static void RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + /** Called when an AVolume shape is changed*/ + static void HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + /** Errors encountered in Csg operation. */ + static int32 GErrors; + static bool GFastRebuild; + +protected: + static void SplitPolyList + ( + UModel *Model, + int32 iParent, + FHBSPOps::ENodePlace NodePlace, + int32 NumPolys, + FPoly **PolyList, + EBspOptimization Opt, + int32 Balance, + int32 PortalBias, + int32 RebuildSimplePolys, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); +}; + + +struct FHBspPointsKey +{ + int32 X; + int32 Y; + int32 Z; + + FHBspPointsKey(int32 InX, int32 InY, int32 InZ) + : X(InX) + , Y(InY) + , Z(InZ) + {} + + friend FORCEINLINE bool operator == (const FHBspPointsKey& A, const FHBspPointsKey& B) + { + return A.X == B.X && A.Y == B.Y && A.Z == B.Z; + } + + friend FORCEINLINE uint32 GetTypeHash(const FHBspPointsKey& Key) + { + return HashCombine(static_cast(Key.X), HashCombine(static_cast(Key.Y), static_cast(Key.Z))); + } +}; + +struct FHBspIndexedPoint +{ + FHBspIndexedPoint(const FVector& InPoint, int32 InIndex) + : Point(InPoint) + , Index(InIndex) + {} + + FVector Point; + int32 Index; +}; + + +struct FHBspPointsGridItem +{ + TArray> IndexedPoints; +}; + + +// Represents a sparse granular 3D grid into which points are added for quick (~O(1)) lookup. +// The 3D space is divided into a grid with a given granularity. +// Points are considered to have a given radius (threshold) and are added to the grid cube they fall in, and to up to seven neighbours if they overlap. +UCLASS() +class HOUDINIENGINE_API UHBspPointsGrid : public UObject +{ + GENERATED_BODY() +protected: + + UHBspPointsGrid() {} + +public: + // Create a new instance of this grid with the given arguments. + static UHBspPointsGrid* Create(float InGranularity, float InThreshold, int32 InitialSize = 0); + + void Clear(int32 InitialSize = 0); + + int32 FindOrAddPoint(const FVector& Point, int32 Index, float Threshold); + + static FORCEINLINE int32 GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold); + +private: + float OneOverGranularity; + float Threshold; + + typedef TMap FGridMap; + FGridMap GridMap; +}; diff --git a/Source/HoudiniEngine/Private/HCsgUtils.cpp b/Source/HoudiniEngine/Private/HCsgUtils.cpp index d3ac93f33..35ba4e3ec 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.cpp +++ b/Source/HoudiniEngine/Private/HCsgUtils.cpp @@ -1,1436 +1,1436 @@ - -#include "HCsgUtils.h" - -#include "Engine/Engine.h" -#include "Engine/Polys.h" -#include "Engine/Selection.h" -#include "Materials/Material.h" -#include "Misc/FeedbackContext.h" - -#include "ActorEditorUtils.h" -#include "Misc/ScopedSlowTask.h" - - -DEFINE_LOG_CATEGORY_STATIC(LogHCsgUtils, Log, All); - -#if WITH_EDITOR -#include "Editor.h" -#endif - -// Magic numbers. -#define THRESH_OPTGEOM_COPLANAR (0.25) /* Threshold for Bsp geometry optimization */ -#define THRESH_OPTGEOM_COSIDAL (0.25) /* Threshold for Bsp geometry optimization */ - - -UHCsgUtils::UHCsgUtils() -{ - // A TempModel is allocated for the HCsgUtils instance to avoid reallocation during inner loops. - TempModel = NewObject(); - TempModel->Initialize(nullptr, 1); - - /*GBspPoints = NewObject(); - GBspVectors = NewObject();*/ -} - - -/*---------------------------------------------------------------------------- - CSG leaf filter callbacks. -----------------------------------------------------------------------------*/ - -void UHCsgUtils::AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); - break; - case F_COSPATIAL_FACING_OUT: - if( !(EdPoly->PolyFlags & PF_Semisolid) ) - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_IN: - break; - } -} - -void UHCsgUtils::AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - // Only affect the world poly if it has been cut. - if( EdPoly->PolyFlags & PF_EdCut ) - FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors ); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_IN: - case F_COSPATIAL_FACING_OUT: - // Discard original poly. - GDiscarded++; - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - break; - } -} - -void UHCsgUtils::SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch (Filter) - { - case F_OUTSIDE: - case F_COSPATIAL_FACING_OUT: - case F_COSPATIAL_FACING_IN: - case F_COPLANAR_OUTSIDE: - break; - case F_COPLANAR_INSIDE: - case F_INSIDE: - EdPoly->Reverse(); - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); // Add to Bsp back - EdPoly->Reverse(); - break; - } -} - -void UHCsgUtils::SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - // Only affect the world poly if it has been cut. - if( EdPoly->PolyFlags & PF_EdCut ) - FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - // Discard original poly. - GDiscarded++; - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - break; - } -} - -void UHCsgUtils::IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - case F_COSPATIAL_FACING_OUT: - // Ignore. - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - if( EdPoly->Fix()>=3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - // Ignore. - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - if( EdPoly->Fix() >= 3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - case F_COSPATIAL_FACING_IN: - // Ignore. - break; - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - if( EdPoly->Fix()>=3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_OUT: - // Ignore. - break; - case F_COPLANAR_INSIDE: - case F_INSIDE: - case F_COSPATIAL_FACING_IN: - if( EdPoly->Fix() >= 3 ) - { - EdPoly->Reverse(); - new(GModel->Polys->Element)FPoly(*EdPoly); - EdPoly->Reverse(); - } - break; - } -} - -/*---------------------------------------------------------------------------- - CSG polygon filtering routine (calls the callbacks). -----------------------------------------------------------------------------*/ - -// -// Handle a piece of a polygon that was filtered to a leaf. -// -void UHCsgUtils::FilterLeaf -( - BspFilterFunc FilterFunc, - UModel* Model, - int32 iNode, - FPoly* EdPoly, - FCoplanarInfo CoplanarInfo, - int32 LeafOutside, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - EPolyNodeFilter FilterType; - - if( CoplanarInfo.iOriginalNode == INDEX_NONE ) - { - // Processing regular, non-coplanar polygons. - FilterType = LeafOutside ? F_OUTSIDE : F_INSIDE; - (this->*FilterFunc)( Model, iNode, EdPoly, FilterType, ENodePlace, BspPoints, BspVectors ); - } - else if( CoplanarInfo.ProcessingBack ) - { - // Finished filtering polygon through tree in back of parent coplanar. - DoneFilteringBack: - if ((!LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_INSIDE; - else if (( LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_OUTSIDE; - else if ((!LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_OUT; - else if (( LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_IN; - else - { - UE_LOG(LogHCsgUtils, Fatal, TEXT("FilterLeaf: Bad Locs")); - return; - } - (this->*FilterFunc)( Model, CoplanarInfo.iOriginalNode, EdPoly, FilterType, FHBSPOps::NODE_Plane, BspPoints, BspVectors ); - } - else - { - CoplanarInfo.FrontLeafOutside = LeafOutside; - - if( CoplanarInfo.iBackNode == INDEX_NONE ) - { - // Back tree is empty. - LeafOutside = CoplanarInfo.BackNodeOutside; - goto DoneFilteringBack; - } - else - { - // Call FilterEdPoly to filter through the back. This will result in - // another call to FilterLeaf with iNode = leaf this falls into in the - // back tree and EdPoly = the final EdPoly to insert. - CoplanarInfo.ProcessingBack=1; - FilterEdPoly( FilterFunc, Model, CoplanarInfo.iBackNode, EdPoly,CoplanarInfo, CoplanarInfo.BackNodeOutside, BspPoints, BspVectors ); - } - } -} - -// -// Filter an EdPoly through the Bsp recursively, calling FilterFunc -// for all chunks that fall into leaves. FCoplanarInfo is used to -// handle the tricky case of double-recursion for polys that must be -// filtered through a node's front, then filtered through the node's back, -// in order to handle coplanar CSG properly. -// -void UHCsgUtils::FilterEdPoly -( - BspFilterFunc FilterFunc, - UModel *Model, - int32 iNode, - FPoly *EdPoly, - FCoplanarInfo CoplanarInfo, - int32 Outside, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - int32 SplitResult,iOurFront,iOurBack; - int32 NewFrontOutside,NewBackOutside; - - FilterLoop: - - // Split em. - FPoly TempFrontEdPoly,TempBackEdPoly; - SplitResult = EdPoly->SplitWithPlane - ( - Model->Points [Model->Verts[Model->Nodes[iNode].iVertPool].pVertex], - Model->Vectors[Model->Surfs[Model->Nodes[iNode].iSurf].vNormal], - &TempFrontEdPoly, - &TempBackEdPoly, - 0 - ); - - // Process split results. - if( SplitResult == SP_Front ) - { - Front: - - FBspNode *Node = &Model->Nodes[iNode]; - Outside = Outside || Node->IsCsg(); - - if( Node->iFront == INDEX_NONE ) - { - FilterLeaf(FilterFunc,Model,iNode,EdPoly,CoplanarInfo,Outside,FHBSPOps::NODE_Front, BspPoints, BspVectors); - } - else - { - iNode = Node->iFront; - goto FilterLoop; - } - } - else if( SplitResult == SP_Back ) - { - FBspNode *Node = &Model->Nodes[iNode]; - Outside = Outside && !Node->IsCsg(); - - if( Node->iBack == INDEX_NONE ) - { - FilterLeaf( FilterFunc, Model, iNode, EdPoly, CoplanarInfo, Outside, FHBSPOps::NODE_Back, BspPoints, BspVectors); - } - else - { - iNode=Node->iBack; - goto FilterLoop; - } - } - else if( SplitResult == SP_Coplanar ) - { - if( CoplanarInfo.iOriginalNode != INDEX_NONE ) - { - // This will happen once in a blue moon when a polygon is barely outside the - // coplanar threshold and is split up into a new polygon that is - // is barely inside the coplanar threshold. To handle this, just classify - // it as front and it will be handled propery. - FHBSPOps::GErrors++; -// UE_LOG(LogEditorBsp, Warning, TEXT("FilterEdPoly: Encountered out-of-place coplanar") ); - goto Front; - } - CoplanarInfo.iOriginalNode = iNode; - CoplanarInfo.iBackNode = INDEX_NONE; - CoplanarInfo.ProcessingBack = 0; - CoplanarInfo.BackNodeOutside = Outside; - NewFrontOutside = Outside; - - // See whether Node's iFront or iBack points to the side of the tree on the front - // of this polygon (will be as expected if this polygon is facing the same - // way as first coplanar in link, otherwise opposite). - if( (FVector(Model->Nodes[iNode].Plane) | EdPoly->Normal) >= 0.0 ) - { - iOurFront = Model->Nodes[iNode].iFront; - iOurBack = Model->Nodes[iNode].iBack; - - if( Model->Nodes[iNode].IsCsg() ) - { - CoplanarInfo.BackNodeOutside = 0; - NewFrontOutside = 1; - } - } - else - { - iOurFront = Model->Nodes[iNode].iBack; - iOurBack = Model->Nodes[iNode].iFront; - - if( Model->Nodes[iNode].IsCsg() ) - { - CoplanarInfo.BackNodeOutside = 1; - NewFrontOutside = 0; - } - } - - // Process front and back. - if ((iOurFront==INDEX_NONE)&&(iOurBack==INDEX_NONE)) - { - // No front or back. - CoplanarInfo.ProcessingBack = 1; - CoplanarInfo.FrontLeafOutside = NewFrontOutside; - FilterLeaf - ( - FilterFunc, - Model, - iNode, - EdPoly, - CoplanarInfo, - CoplanarInfo.BackNodeOutside, - FHBSPOps::NODE_Plane, - BspPoints, - BspVectors - ); - } - else if( iOurFront==INDEX_NONE && iOurBack!=INDEX_NONE ) - { - // Back but no front. - CoplanarInfo.ProcessingBack = 1; - CoplanarInfo.iBackNode = iOurBack; - CoplanarInfo.FrontLeafOutside = NewFrontOutside; - - iNode = iOurBack; - Outside = CoplanarInfo.BackNodeOutside; - goto FilterLoop; - } - else - { - // Has a front and maybe a back. - - // Set iOurBack up to process back on next call to FilterLeaf, and loop - // to process front. Next call to FilterLeaf will set FrontLeafOutside. - CoplanarInfo.ProcessingBack = 0; - - // May be a node or may be INDEX_NONE. - CoplanarInfo.iBackNode = iOurBack; - - iNode = iOurFront; - Outside = NewFrontOutside; - goto FilterLoop; - } - } - else if( SplitResult == SP_Split ) - { - // Front half of split. - if( Model->Nodes[iNode].IsCsg() ) - { - NewFrontOutside = 1; - NewBackOutside = 0; - } - else - { - NewFrontOutside = Outside; - NewBackOutside = Outside; - } - - if( Model->Nodes[iNode].iFront==INDEX_NONE ) - { - FilterLeaf - ( - FilterFunc, - Model, - iNode, - &TempFrontEdPoly, - CoplanarInfo, - NewFrontOutside, - FHBSPOps::NODE_Front, - BspPoints, - BspVectors - ); - } - else - { - FilterEdPoly - ( - FilterFunc, - Model, - Model->Nodes[iNode].iFront, - &TempFrontEdPoly, - CoplanarInfo, - NewFrontOutside, - BspPoints, - BspVectors - ); - } - - // Back half of split. - if( Model->Nodes[iNode].iBack==INDEX_NONE ) - { - FilterLeaf - ( - FilterFunc, - Model, - iNode, - &TempBackEdPoly, - CoplanarInfo, - NewBackOutside, - FHBSPOps::NODE_Back, - BspPoints, - BspVectors - ); - } - else - { - FilterEdPoly - ( - FilterFunc, - Model, - Model->Nodes[iNode].iBack, - &TempBackEdPoly, - CoplanarInfo, - NewBackOutside, - BspPoints, - BspVectors - ); - } - } -} - -// -// Regular entry into FilterEdPoly (so higher-level callers don't have to -// deal with unnecessary info). Filters starting at root. -// -void UHCsgUtils::BspFilterFPoly( BspFilterFunc FilterFunc, UModel *Model, FPoly *EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - FCoplanarInfo StartingCoplanarInfo; - StartingCoplanarInfo.iOriginalNode = INDEX_NONE; - if( Model->Nodes.Num() == 0 ) - { - // If Bsp is empty, process at root. - (this->*FilterFunc)( Model, 0, EdPoly, Model->RootOutside ? F_OUTSIDE : F_INSIDE, FHBSPOps::NODE_Root, BspPoints, BspVectors ); - } - else - { - // Filter through Bsp. - FilterEdPoly( FilterFunc, Model, 0, EdPoly, StartingCoplanarInfo, Model->RootOutside, BspPoints, BspVectors ); - } -} - -int UHCsgUtils::bspNodeToFPoly -( - UModel* Model, - int32 iNode, - FPoly* EdPoly -) -{ - FPoly MasterEdPoly; - - FBspNode &Node = Model->Nodes[iNode]; - FBspSurf &Poly = Model->Surfs[Node.iSurf]; - FVert *VertPool = &Model->Verts[ Node.iVertPool ]; - - EdPoly->Base = Model->Points [Poly.pBase]; - EdPoly->Normal = Model->Vectors[Poly.vNormal]; - - EdPoly->PolyFlags = Poly.PolyFlags & ~(PF_EdCut | PF_EdProcessed | PF_Selected | PF_Memorized); - EdPoly->iLinkSurf = Node.iSurf; - EdPoly->Material = Poly.Material; - - EdPoly->Actor = Poly.Actor; - EdPoly->iBrushPoly = Poly.iBrushPoly; - - if( polyFindMaster(Model,Node.iSurf,MasterEdPoly) ) - EdPoly->ItemName = MasterEdPoly.ItemName; - else - EdPoly->ItemName = NAME_None; - - EdPoly->TextureU = Model->Vectors[Poly.vTextureU]; - EdPoly->TextureV = Model->Vectors[Poly.vTextureV]; - - EdPoly->LightMapScale = Poly.LightMapScale; - - EdPoly->LightmassSettings = Model->LightmassSettings[Poly.iLightmassIndex]; - - EdPoly->Vertices.Empty(); - - for(int32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++) - { - new(EdPoly->Vertices) FVector(Model->Points[VertPool[VertexIndex].pVertex]); - } - - if(EdPoly->Vertices.Num() < 3) - { - EdPoly->Vertices.Empty(); - } - else - { - // Remove colinear points and identical points (which will appear - // if T-joints were eliminated). - EdPoly->RemoveColinears(); - } - - return EdPoly->Vertices.Num(); -} - -/*--------------------------------------------------------------------------------------- - World filtering. ----------------------------------------------------------------------------------------*/ - -// -// Filter all relevant world polys through the brush. -// -void UHCsgUtils::FilterWorldThroughBrush -( - UModel* Model, - UModel* Brush, - EBrushType BrushType, - ECsgOper CSGOper, - int32 iNode, - FSphere* BrushSphere, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - // Loop through all coplanars. - while( iNode != INDEX_NONE ) - { - // Get surface. - int32 iSurf = Model->Nodes[iNode].iSurf; - - // Skip new nodes and their children, which are guaranteed new. - if( Model->Nodes[iNode].NodeFlags & NF_IsNew ) - return; - - // Sphere reject. - int DoFront = 1, DoBack = 1; - if( BrushSphere ) - { - float Dist = Model->Nodes[iNode].Plane.PlaneDot( BrushSphere->Center ); - DoFront = (Dist >= -BrushSphere->W); - DoBack = (Dist <= +BrushSphere->W); - } - - // Process only polys that aren't empty. - FPoly TempEdPoly; - if( DoFront && DoBack && (GEditor->bspNodeToFPoly(Model,iNode,&TempEdPoly)>0) ) - { - TempEdPoly.Actor = Model->Surfs[iSurf].Actor; - TempEdPoly.iBrushPoly = Model->Surfs[iSurf].iBrushPoly; - - if( BrushType==Brush_Add || BrushType==Brush_Subtract ) - { - // Add and subtract work the same in this step. - GNode = iNode; - GModel = Model; - GDiscarded = 0; - GNumNodes = Model->Nodes.Num(); - - // Find last coplanar in chain. - GLastCoplanar = iNode; - while( Model->Nodes[GLastCoplanar].iPlane != INDEX_NONE ) - GLastCoplanar = Model->Nodes[GLastCoplanar].iPlane; - - // Do the filter operation. - BspFilterFPoly - ( - BrushType==Brush_Add ? &UHCsgUtils::AddWorldToBrushFunc : &UHCsgUtils::SubtractWorldToBrushFunc, - Brush, - &TempEdPoly, - BspPoints, - BspVectors - ); - - if( GDiscarded == 0 ) - { - // Get rid of all the fragments we added. - Model->Nodes[GLastCoplanar].iPlane = INDEX_NONE; - const bool bAllowShrinking = false; - Model->Nodes.RemoveAt( GNumNodes, Model->Nodes.Num()-GNumNodes, bAllowShrinking ); - } - else - { - // Tag original world poly for deletion; has been deleted or replaced by partial fragments. - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - } - } - else if( CSGOper == CSG_Intersect ) - { - BspFilterFPoly( &UHCsgUtils::IntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); - } - else if( CSGOper == CSG_Deintersect ) - { - BspFilterFPoly( &UHCsgUtils::DeIntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); - } - } - - // Now recurse to filter all of the world's children nodes. - if( DoFront && (Model->Nodes[iNode].iFront != INDEX_NONE)) FilterWorldThroughBrush - ( - Model, - Brush, - BrushType, - CSGOper, - Model->Nodes[iNode].iFront, - BrushSphere, - BspPoints, - BspVectors - ); - if( DoBack && (Model->Nodes[iNode].iBack != INDEX_NONE) ) FilterWorldThroughBrush - ( - Model, - Brush, - BrushType, - CSGOper, - Model->Nodes[iNode].iBack, - BrushSphere, - BspPoints, - BspVectors - ); - iNode = Model->Nodes[iNode].iPlane; - } -} - -void UHCsgUtils::RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic) -{ - if (!IsValid(Model)) - return; - - UHCsgUtils* CsgUtils = NewObject(); - int32 CsgErrors = 0; - - UHBspPointsGrid* BspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* BspVectors = UHBspPointsGrid::Create(1/16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - - // Empty the model out. - const int32 NumPoints = Model->Points.Num(); - const int32 NumNodes = Model->Nodes.Num(); - const int32 NumVerts = Model->Verts.Num(); - const int32 NumVectors = Model->Vectors.Num(); - const int32 NumSurfs = Model->Surfs.Num(); - - Model->Modify(); - Model->EmptyModel(1, 1); - - // Reserve arrays an eighth bigger than the previous allocation - Model->Points.Empty(NumPoints + NumPoints / 8); - Model->Nodes.Empty(NumNodes + NumNodes / 8); - Model->Verts.Empty(NumVerts + NumVerts / 8); - Model->Vectors.Empty(NumVectors + NumVectors / 8); - Model->Surfs.Empty(NumSurfs + NumSurfs / 8); - - // Build list of all static brushes, first structural brushes and portals - TArray StaticBrushes; - for (ABrush* Brush : Brushes) - { - if ((Brush && (Brush->IsStaticBrush() || bTreatMovableBrushesAsStatic) && !FActorEditorUtils::IsABuilderBrush(Brush)) && - (!(Brush->PolyFlags & PF_Semisolid) || (Brush->BrushType != Brush_Add) || (Brush->PolyFlags & PF_Portal))) - { - StaticBrushes.Add(Brush); - - // Treat portals as solids for cutting. - if (Brush->PolyFlags & PF_Portal) - { - Brush->PolyFlags = (Brush->PolyFlags & ~PF_Semisolid) | PF_NotSolid; - } - } - } - - // Next append all detail brushes - for (ABrush* Brush : Brushes) - { - if (Brush && Brush->IsStaticBrush() && !FActorEditorUtils::IsABuilderBrush(Brush) && - (Brush->PolyFlags & PF_Semisolid) && !(Brush->PolyFlags & PF_Portal) && (Brush->BrushType == Brush_Add)) - { - StaticBrushes.Add(Brush); - } - } - - // Build list of dynamic brushes - TArray DynamicBrushes; - if (!bTreatMovableBrushesAsStatic) - { - for (ABrush* DynamicBrush : Brushes) - { - if (DynamicBrush && DynamicBrush->Brush && !DynamicBrush->IsStaticBrush()) - { - DynamicBrushes.Add(DynamicBrush); - } - } - } - - FScopedSlowTask SlowTask(StaticBrushes.Num() + DynamicBrushes.Num()); - SlowTask.MakeDialogDelayed(3.0f); - - // Compose all static brushes - for (ABrush* Brush : StaticBrushes) - { - SlowTask.EnterProgressFrame(1); - Brush->Modify(); - int32 Errors = CsgUtils->ComposeBrushCSG(Brush, Model, Brush->PolyFlags, (EBrushType)Brush->BrushType, CSG_None, false, true, false, false, BspPoints, BspVectors); - if (Errors > 1) - CsgErrors += Errors - 1; - } - - // Rebuild dynamic brush BSP's (if they weren't handled earlier) - for (ABrush* DynamicBrush : DynamicBrushes) - { - SlowTask.EnterProgressFrame(1); - UHBspPointsGrid* LocalBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* LocalBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - - FHBSPOps::csgPrepMovingBrush(DynamicBrush, LocalBspPoints, LocalBspVectors); - } -} - - - -UModel* UHCsgUtils::BuildModelFromBrushes(TArray& Brushes) -{ - // Generally UModels are initialized using ABrush. Here we manually - // initialize using relevant parts from - UModel* OutModel = NewObject(); - OutModel->SetFlags(RF_Transactional); - OutModel->RootOutside = true; - OutModel->EmptyModel(1,1); - OutModel->UpdateVertices(); - - if (!IsValid(OutModel)) - return nullptr; - - // Can we combine the brushes without modifying the actors here ...? - - //FVector Location(0.0f, 0.0f, 0.0f); - //FRotator Rotation(0.0f, 0.0f, 0.0f); - //for(int32 BrushesIdx = 0; BrushesIdx < Brushes.Num(); ++BrushesIdx ) - //{ - // // Cache the location and rotation. - // Location = Brushes[BrushesIdx]->GetActorLocation(); - // Rotation = Brushes[BrushesIdx]->GetActorRotation(); - - - // // Leave the actor's rotation but move it to origin so the Static Mesh will generate correctly. - // Brushes[BrushesIdx]->TeleportTo(Location - InPivotLocation, Rotation, false, true); - //} - - RebuildModelFromBrushes(OutModel, Brushes, true); - //GEditor->bspBuildFPolys(OutModel, true, 0); - - //if (0 < ConversionTempModel->Polys->Element.Num()) - //{ - // UStaticMesh* NewMesh = CreateStaticMeshFromBrush(Pkg, ObjName, NULL, ConversionTempModel); - // NewActor = FActorFactoryAssetProxy::AddActorForAsset( NewMesh ); - - // NewActor->Modify(); - - // NewActor->InvalidateLightingCache(); - // NewActor->PostEditChange(); - // NewActor->PostEditMove( true ); - // NewActor->Modify(); - // ULayersSubsystem* LayersSubsystem = GetEditorSubsystem(); - // LayersSubsystem->InitializeNewActorLayers(NewActor); - - // // Teleport the new actor to the old location but not the old rotation. The static mesh is built to the rotation already. - // NewActor->TeleportTo(InPivotLocation, FRotator(0.0f, 0.0f, 0.0f), false, true); - - // // Destroy the old brushes. - // for( int32 BrushIdx = 0; BrushIdx < InBrushesToConvert.Num(); ++BrushIdx ) - // { - // LayersSubsystem->DisassociateActorFromLayers(InBrushesToConvert[BrushIdx]); - // GWorld->EditorDestroyActor( InBrushesToConvert[BrushIdx], true ); - // } - - // // Notify the asset registry - // FAssetRegistryModule::AssetCreated(NewMesh); - //} - - //ConversionTempModel->EmptyModel(1, 1); - //RebuildAlteredBSP(); - //RedrawLevelEditingViewports(); - - //return NewActor; - - return OutModel; -} - -int UHCsgUtils::ComposeBrushCSG -( - ABrush* Actor, - UModel* Model, - uint32 PolyFlags, - EBrushType BrushType, - ECsgOper CSGOper, - bool bBuildBounds, - bool bMergePolys, - bool bReplaceNULLMaterialRefs, - bool bShowProgressBar, /*=true*/ - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - uint32 NotPolyFlags = 0; - int32 NumPolysFromBrush=0,i,j,ReallyBig; - UModel* Brush = Actor->Brush; - int32 Errors = 0; - - // Make sure we're in an acceptable state. - if( !Brush ) - { - return 0; - } - - // Non-solid and semisolid stuff can only be added. - if( BrushType != Brush_Add ) - { - NotPolyFlags |= (PF_Semisolid | PF_NotSolid); - } - - TempModel->EmptyModel(1,1); - - // Update status. - ReallyBig = (Brush->Polys->Element.Num() > 200) && bShowProgressBar; - if( ReallyBig ) - { - FText Description = NSLOCTEXT("UnrealEd", "PerformingCSGOperation", "Performing CSG operation"); - - if (BrushType != Brush_MAX) - { - if (BrushType == Brush_Add) - { - Description = NSLOCTEXT("UnrealEd", "AddingBrushToWorld", "Adding brush to world"); - } - else if (BrushType == Brush_Subtract) - { - Description = NSLOCTEXT("UnrealEd", "SubtractingBrushFromWorld", "Subtracting brush from world"); - } - } - else if (CSGOper != CSG_None) - { - if (CSGOper == CSG_Intersect) - { - Description = NSLOCTEXT("UnrealEd", "IntersectingBrushWithWorld", "Intersecting brush with world"); - } - else if (CSGOper == CSG_Deintersect) - { - Description = NSLOCTEXT("UnrealEd", "DeintersectingBrushWithWorld", "Deintersecting brush with world"); - } - } - - GWarn->BeginSlowTask( Description, true ); - // Transform original brush poly into same coordinate system as world - // so Bsp filtering operations make sense. - GWarn->StatusUpdate(0, 0, NSLOCTEXT("UnrealEd", "Transforming", "Transforming")); - } - - - //UMaterialInterface* SelectedMaterialInstance = GEditor->GetSelectedObjects()->GetTop(); - UMaterialInterface* SelectedMaterialInstance = nullptr; - - const FVector Scale = Actor->GetActorScale(); - const FRotator Rotation = Actor->GetActorRotation(); - const FVector Location = Actor->GetActorLocation(); - - const bool bIsMirrored = (Scale.X * Scale.Y * Scale.Z < 0.0f); - - // Cache actor transform which is used for the geometry being built - Brush->OwnerLocationWhenLastBuilt = Location; - Brush->OwnerRotationWhenLastBuilt = Rotation; - Brush->OwnerScaleWhenLastBuilt = Scale; - Brush->bCachedOwnerTransformValid = true; - - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly& CurrentPoly = Brush->Polys->Element[i]; - - // Set texture the first time. - if ( bReplaceNULLMaterialRefs ) - { - UMaterialInterface*& PolyMat = CurrentPoly.Material; - if ( !PolyMat || PolyMat == UMaterial::GetDefaultMaterial(MD_Surface) ) - { - PolyMat = SelectedMaterialInstance; - } - } - - // Get the brush poly. - FPoly DestEdPoly = CurrentPoly; - check(CurrentPoly.iLinkPolys->Element.Num()); - - // Set its backward brush link. - DestEdPoly.Actor = Actor; - DestEdPoly.iBrushPoly = i; - - // Update its flags. - DestEdPoly.PolyFlags = (DestEdPoly.PolyFlags | PolyFlags) & ~NotPolyFlags; - - // Set its internal link. - if (DestEdPoly.iLink == INDEX_NONE) - { - DestEdPoly.iLink = i; - } - - // Transform it. - DestEdPoly.Scale( Scale ); - DestEdPoly.Rotate( Rotation ); - DestEdPoly.Transform( Location ); - - // Reverse winding and normal if the parent brush is mirrored - if (bIsMirrored) - { - DestEdPoly.Reverse(); - DestEdPoly.CalcNormal(); - } - - // Add poly to the temp model. - new(TempModel->Polys->Element)FPoly( DestEdPoly ); - } - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringBrush", "Filtering brush") ); - - // Pass the brush polys through the world Bsp. - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - // Empty the brush. - Brush->EmptyModel(1,1); - - // Intersect and deintersect. - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly EdPoly = TempModel->Polys->Element[i]; - GModel = Brush; - // TODO: iLink / iLinkSurf in EdPoly / TempModel->Polys->Element[i] ? - BspFilterFPoly( CSGOper==CSG_Intersect ? &UHCsgUtils::IntersectBrushWithWorldFunc : &UHCsgUtils::DeIntersectBrushWithWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); - } - NumPolysFromBrush = Brush->Polys->Element.Num(); - } - else - { - // Add and subtract. - TMap SurfaceIndexRemap; - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly EdPoly = TempModel->Polys->Element[i]; - - // Mark the polygon as non-cut so that it won't be harmed unless it must - // be split, and set iLink so that BspAddNode will know to add its information - // if a node is added based on this poly. - EdPoly.PolyFlags &= ~(PF_EdCut); - const int32* SurfaceIndexPtr = SurfaceIndexRemap.Find(EdPoly.iLink); - if (SurfaceIndexPtr == nullptr) - { - const int32 NewSurfaceIndex = Model->Surfs.Num(); - SurfaceIndexRemap.Add(EdPoly.iLink, NewSurfaceIndex); - EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = NewSurfaceIndex; - } - else - { - EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = *SurfaceIndexPtr; - } - - // Filter brush through the world. - BspFilterFPoly( BrushType==Brush_Add ? &UHCsgUtils::AddBrushToWorldFunc : &UHCsgUtils::SubtractBrushFromWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); - } - } - if( Model->Nodes.Num() && !(PolyFlags & (PF_NotSolid | PF_Semisolid)) ) - { - // Quickly build a Bsp for the brush, tending to minimize splits rather than balance - // the tree. We only need the cutting planes, though the entire Bsp struct (polys and - // all) is built. - - /*FHBspPointsGrid* LevelModelPointsGrid = FHBspPointsGrid::GBspPoints; - FHBspPointsGrid* LevelModelVectorsGrid = FHBspPointsGrid::GBspVectors;*/ - - // For the bspBuild call, temporarily create a new pair of BspPointsGrids for the TempModel. - UHBspPointsGrid* TempBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* TempBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - /*FHBspPointsGrid::GBspPoints = BspPoints.Get(); - FHBspPointsGrid::GBspVectors = BspVectors.Get();*/ - - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "BuildingBSP", "Building BSP") ); - - FHBSPOps::bspBuild( TempModel, FHBSPOps::BSP_Lame, 0, 70, 1, 0, TempBspPoints, TempBspVectors ); - - // Reinstate the original BspPointsGrids used for building the level Model. - /*FHBspPointsGrid::GBspPoints = LevelModelPointsGrid; - FHBspPointsGrid::GBspVectors = LevelModelVectorsGrid;*/ - - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringWorld", "Filtering world") ); - GModel = Brush; - TempModel->BuildBound(); - - FSphere BrushSphere = TempModel->Bounds.GetSphere(); - FilterWorldThroughBrush( Model, TempModel, BrushType, CSGOper, 0, &BrushSphere, BspPoints, BspVectors); - } - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "AdjustingBrush", "Adjusting brush") ); - - // Link polys obtained from the original brush. - for( i=NumPolysFromBrush-1; i>=0; i-- ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - for( j=0; jiLink == Brush->Polys->Element[j].iLink ) - { - DestEdPoly->iLink = j; - break; - } - } - if( j >= i ) DestEdPoly->iLink = i; - } - - // Link polys obtained from the world. - for( i=Brush->Polys->Element.Num()-1; i>=NumPolysFromBrush; i-- ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - for( j=NumPolysFromBrush; jiLink == Brush->Polys->Element[j].iLink ) - { - DestEdPoly->iLink = j; - break; - } - } - if( j >= i ) DestEdPoly->iLink = i; - } - Brush->Linked = 1; - - // Detransform the obtained brush back into its original coordinate system. - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - DestEdPoly->Transform(-Location); - DestEdPoly->Rotate(Rotation.GetInverse()); - DestEdPoly->Scale(FVector(1.0f) / Scale); - DestEdPoly->Fix(); - DestEdPoly->Actor = NULL; - DestEdPoly->iBrushPoly = i; - } - } - - if( BrushType==Brush_Add || BrushType==Brush_Subtract ) - { - // Clean up nodes, reset node flags. - bspCleanup( Model ); - - // Rebuild bounding volumes. - if( bBuildBounds ) - { - FHBSPOps::bspBuildBounds( Model ); - } - } - - Brush->NumUniqueVertices = TempModel->Points.Num(); - // Release TempModel. - TempModel->EmptyModel(1,1); - - // Merge coplanars if needed. - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - if( ReallyBig ) - { - GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "Merging", "Merging") ); - } - if( bMergePolys ) - { - bspMergeCoplanars( Brush, 1, 0 ); - } - } - if( ReallyBig ) - { - GWarn->EndSlowTask(); - } - - return 1 + FHBSPOps::GErrors; -} - -/*---------------------------------------------------------------------------- - EdPoly building and compacting. -----------------------------------------------------------------------------*/ - -// -// Trys to merge two polygons. If they can be merged, replaces Poly1 and emptys Poly2 -// and returns 1. Otherwise, returns 0. -// -int UHCsgUtils::TryToMerge( FPoly *Poly1, FPoly *Poly2 ) -{ - // Find one overlapping point. - int32 Start1=0, Start2=0; - for( Start1=0; Start1Vertices.Num(); Start1++ ) - for( Start2=0; Start2Vertices.Num(); Start2++ ) - if( FVector::PointsAreSame(Poly1->Vertices[Start1], Poly2->Vertices[Start2]) ) - goto FoundOverlap; - return 0; - FoundOverlap: - - // Wrap around trying to merge. - int32 End1 = Start1; - int32 End2 = Start2; - int32 Test1 = Start1+1; if (Test1>=Poly1->Vertices.Num()) Test1 = 0; - int32 Test2 = Start2-1; if (Test2<0) Test2 = Poly2->Vertices.Num()-1; - if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) - { - End1 = Test1; - Start2 = Test2; - } - else - { - Test1 = Start1-1; if (Test1<0) Test1=Poly1->Vertices.Num()-1; - Test2 = Start2+1; if (Test2>=Poly2->Vertices.Num()) Test2=0; - if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) - { - Start1 = Test1; - End2 = Test2; - } - else return 0; - } - - // Build a new edpoly containing both polygons merged. - FPoly NewPoly = *Poly1; - NewPoly.Vertices.Empty(); - int32 Vertex = End1; - for( int32 i=0; iVertices.Num(); i++ ) - { - new(NewPoly.Vertices) FVector(Poly1->Vertices[Vertex]); - if( ++Vertex >= Poly1->Vertices.Num() ) - Vertex=0; - } - Vertex = End2; - for( int32 i=0; i<(Poly2->Vertices.Num()-2); i++ ) - { - if( ++Vertex >= Poly2->Vertices.Num() ) - Vertex=0; - new(NewPoly.Vertices) FVector(Poly2->Vertices[Vertex]); - } - - // Remove colinear vertices and check convexity. - if( NewPoly.RemoveColinears() ) - { - *Poly1 = NewPoly; - Poly2->Vertices.Empty(); - return true; - } - else return 0; -} - -// -// Merge all polygons in coplanar list that can be merged convexly. -// -void UHCsgUtils::MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ) -{ - int32 MergeAgain = 1; - while( MergeAgain ) - { - MergeAgain = 0; - for( int32 i=0; iPolys->Element[PolyList[i]]; - if( Poly1.Vertices.Num() > 0 ) - { - for( int32 j=i+1; jPolys->Element[PolyList[j]]; - if( Poly2.Vertices.Num() > 0 ) - { - if( TryToMerge( &Poly1, &Poly2 ) ) - MergeAgain=1; - } - } - } - } - } -} - -void UHCsgUtils::bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ) -{ - int32 OriginalNum = Model->Polys->Element.Num(); - - // Mark all polys as unprocessed. - for( int32 i=0; iPolys->Element.Num(); i++ ) - Model->Polys->Element[i].PolyFlags &= ~PF_EdProcessed; - - // Find matching coplanars and merge them. - FMemMark Mark(FMemStack::Get()); - int32* PolyList = new(FMemStack::Get(),Model->Polys->Element.Num())int32; - int32 n=0; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - FPoly* EdPoly = &Model->Polys->Element[i]; - if( EdPoly->Vertices.Num()>0 && !(EdPoly->PolyFlags & PF_EdProcessed) ) - { - int32 PolyCount = 0; - PolyList[PolyCount++] = i; - EdPoly->PolyFlags |= PF_EdProcessed; - for( int32 j=i+1; jPolys->Element.Num(); j++ ) - { - FPoly* OtherPoly = &Model->Polys->Element[j]; - if( OtherPoly->iLink == EdPoly->iLink && OtherPoly->Vertices.Num() ) - { - float Dist = (OtherPoly->Vertices[0] - EdPoly->Vertices[0]) | EdPoly->Normal; - if - ( Dist>-0.001 - && Dist<0.001 - && (OtherPoly->Normal|EdPoly->Normal)>0.9999 - && (MergeDisparateTextures - || ( FVector::PointsAreNear(OtherPoly->TextureU,EdPoly->TextureU,THRESH_VECTORS_ARE_NEAR) - && FVector::PointsAreNear(OtherPoly->TextureV,EdPoly->TextureV,THRESH_VECTORS_ARE_NEAR) ) ) ) - { - OtherPoly->PolyFlags |= PF_EdProcessed; - PolyList[PolyCount++] = j; - } - } - } - if( PolyCount > 1 ) - { - MergeCoplanars( Model, PolyList, PolyCount ); - n++; - } - } - } -// UE_LOG(LogEditorBsp, Log, TEXT("Found %i coplanar sets in %i"), n, Model->Polys->Element.Num() ); - Mark.Pop(); - - // Get rid of empty EdPolys while remapping iLinks. - FMemMark Mark2(FMemStack::Get()); - int32 j=0; - int32* Remap = new(FMemStack::Get(),Model->Polys->Element.Num())int32; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - if( Model->Polys->Element[i].Vertices.Num() ) - { - Remap[i] = j; - Model->Polys->Element[j] = Model->Polys->Element[i]; - j++; - } - } - Model->Polys->Element.RemoveAt( j, Model->Polys->Element.Num()-j ); - if( RemapLinks ) - { - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - if (Model->Polys->Element[i].iLink != INDEX_NONE) - { - CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*Remap'. - Model->Polys->Element[i].iLink = Remap[Model->Polys->Element[i].iLink]; - } - } - } -// UE_LOG(LogEditorBsp, Log, TEXT("BspMergeCoplanars reduced %i->%i"), OriginalNum, Model->Polys->Element.Num() ); - Mark2.Pop(); -} - -bool UHCsgUtils::polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly) -{ - FBspSurf &Surf = InModel->Surfs[iSurf]; - if( !Surf.Actor || !Surf.Actor->Brush->Polys->Element.IsValidIndex(Surf.iBrushPoly) ) - { - return false; - } - else - { - Poly = Surf.Actor->Brush->Polys->Element[Surf.iBrushPoly]; - return true; - } -} - -void UHCsgUtils::CleanupNodes( UModel *Model, int32 iNode, int32 iParent ) -{ - FBspNode *Node = &Model->Nodes[iNode]; - - // Transactionally empty vertices of tag-for-empty nodes. - Node->NodeFlags &= ~(NF_IsNew | NF_IsFront | NF_IsBack); - - // Recursively clean up front, back, and plane nodes. - if( Node->iFront != INDEX_NONE ) CleanupNodes( Model, Node->iFront, iNode ); - if( Node->iBack != INDEX_NONE ) CleanupNodes( Model, Node->iBack , iNode ); - if( Node->iPlane != INDEX_NONE ) CleanupNodes( Model, Node->iPlane, iNode ); - - // Reload Node since the recusive call aliases it. - Node = &Model->Nodes[iNode]; - - // If this is an empty node with a coplanar, replace it with the coplanar. - if( Node->NumVertices==0 && Node->iPlane!=INDEX_NONE ) - { - FBspNode* PlaneNode = &Model->Nodes[ Node->iPlane ]; - - // Stick our front, back, and parent nodes on the coplanar. - if( (Node->Plane | PlaneNode->Plane) >= 0.0 ) - { - PlaneNode->iFront = Node->iFront; - PlaneNode->iBack = Node->iBack; - } - else - { - PlaneNode->iFront = Node->iBack; - PlaneNode->iBack = Node->iFront; - } - - if( iParent == INDEX_NONE ) - { - // This node is the root. - *Node = *PlaneNode; // Replace root. - PlaneNode->NumVertices = 0; // Mark as unused. - } - else - { - // This is a child node. - FBspNode *ParentNode = &Model->Nodes[iParent]; - - if ( ParentNode->iFront == iNode ) ParentNode->iFront = Node->iPlane; - else if ( ParentNode->iBack == iNode ) ParentNode->iBack = Node->iPlane; - else if ( ParentNode->iPlane == iNode ) ParentNode->iPlane = Node->iPlane; - else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); - } - } - else if( Node->NumVertices == 0 && ( Node->iFront==INDEX_NONE || Node->iBack==INDEX_NONE ) ) - { - // Delete empty nodes with no fronts or backs. - // Replace empty nodes with only fronts. - // Replace empty nodes with only backs. - int32 iReplacementNode; - if ( Node->iFront != INDEX_NONE ) iReplacementNode = Node->iFront; - else if( Node->iBack != INDEX_NONE ) iReplacementNode = Node->iBack; - else iReplacementNode = INDEX_NONE; - - if( iParent == INDEX_NONE ) - { - // Root. - if( iReplacementNode == INDEX_NONE ) - { - Model->Nodes.Empty(); - } - else - { - *Node = Model->Nodes[iReplacementNode]; - } - } - else - { - // Regular node. - FBspNode *ParentNode = &Model->Nodes[iParent]; - - if ( ParentNode->iFront == iNode ) ParentNode->iFront = iReplacementNode; - else if( ParentNode->iBack == iNode ) ParentNode->iBack = iReplacementNode; - else if( ParentNode->iPlane == iNode ) ParentNode->iPlane = iReplacementNode; - else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); - } - } -} - - -void UHCsgUtils::bspCleanup( UModel *Model ) -{ - if( Model->Nodes.Num() > 0 ) - CleanupNodes( Model, 0, INDEX_NONE ); -} + +#include "HCsgUtils.h" + +#include "Engine/Engine.h" +#include "Engine/Polys.h" +#include "Engine/Selection.h" +#include "Materials/Material.h" +#include "Misc/FeedbackContext.h" + +#include "ActorEditorUtils.h" +#include "Misc/ScopedSlowTask.h" + + +DEFINE_LOG_CATEGORY_STATIC(LogHCsgUtils, Log, All); + +#if WITH_EDITOR +#include "Editor.h" +#endif + +// Magic numbers. +#define THRESH_OPTGEOM_COPLANAR (0.25) /* Threshold for Bsp geometry optimization */ +#define THRESH_OPTGEOM_COSIDAL (0.25) /* Threshold for Bsp geometry optimization */ + + +UHCsgUtils::UHCsgUtils() +{ + // A TempModel is allocated for the HCsgUtils instance to avoid reallocation during inner loops. + TempModel = NewObject(); + TempModel->Initialize(nullptr, 1); + + /*GBspPoints = NewObject(); + GBspVectors = NewObject();*/ +} + + +/*---------------------------------------------------------------------------- + CSG leaf filter callbacks. +----------------------------------------------------------------------------*/ + +void UHCsgUtils::AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); + break; + case F_COSPATIAL_FACING_OUT: + if( !(EdPoly->PolyFlags & PF_Semisolid) ) + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_IN: + break; + } +} + +void UHCsgUtils::AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + // Only affect the world poly if it has been cut. + if( EdPoly->PolyFlags & PF_EdCut ) + FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors ); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_IN: + case F_COSPATIAL_FACING_OUT: + // Discard original poly. + GDiscarded++; + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + break; + } +} + +void UHCsgUtils::SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch (Filter) + { + case F_OUTSIDE: + case F_COSPATIAL_FACING_OUT: + case F_COSPATIAL_FACING_IN: + case F_COPLANAR_OUTSIDE: + break; + case F_COPLANAR_INSIDE: + case F_INSIDE: + EdPoly->Reverse(); + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); // Add to Bsp back + EdPoly->Reverse(); + break; + } +} + +void UHCsgUtils::SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + // Only affect the world poly if it has been cut. + if( EdPoly->PolyFlags & PF_EdCut ) + FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + // Discard original poly. + GDiscarded++; + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + break; + } +} + +void UHCsgUtils::IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + case F_COSPATIAL_FACING_OUT: + // Ignore. + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + if( EdPoly->Fix()>=3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + // Ignore. + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + if( EdPoly->Fix() >= 3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + case F_COSPATIAL_FACING_IN: + // Ignore. + break; + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + if( EdPoly->Fix()>=3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_OUT: + // Ignore. + break; + case F_COPLANAR_INSIDE: + case F_INSIDE: + case F_COSPATIAL_FACING_IN: + if( EdPoly->Fix() >= 3 ) + { + EdPoly->Reverse(); + new(GModel->Polys->Element)FPoly(*EdPoly); + EdPoly->Reverse(); + } + break; + } +} + +/*---------------------------------------------------------------------------- + CSG polygon filtering routine (calls the callbacks). +----------------------------------------------------------------------------*/ + +// +// Handle a piece of a polygon that was filtered to a leaf. +// +void UHCsgUtils::FilterLeaf +( + BspFilterFunc FilterFunc, + UModel* Model, + int32 iNode, + FPoly* EdPoly, + FCoplanarInfo CoplanarInfo, + int32 LeafOutside, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + EPolyNodeFilter FilterType; + + if( CoplanarInfo.iOriginalNode == INDEX_NONE ) + { + // Processing regular, non-coplanar polygons. + FilterType = LeafOutside ? F_OUTSIDE : F_INSIDE; + (this->*FilterFunc)( Model, iNode, EdPoly, FilterType, ENodePlace, BspPoints, BspVectors ); + } + else if( CoplanarInfo.ProcessingBack ) + { + // Finished filtering polygon through tree in back of parent coplanar. + DoneFilteringBack: + if ((!LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_INSIDE; + else if (( LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_OUTSIDE; + else if ((!LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_OUT; + else if (( LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_IN; + else + { + UE_LOG(LogHCsgUtils, Fatal, TEXT("FilterLeaf: Bad Locs")); + return; + } + (this->*FilterFunc)( Model, CoplanarInfo.iOriginalNode, EdPoly, FilterType, FHBSPOps::NODE_Plane, BspPoints, BspVectors ); + } + else + { + CoplanarInfo.FrontLeafOutside = LeafOutside; + + if( CoplanarInfo.iBackNode == INDEX_NONE ) + { + // Back tree is empty. + LeafOutside = CoplanarInfo.BackNodeOutside; + goto DoneFilteringBack; + } + else + { + // Call FilterEdPoly to filter through the back. This will result in + // another call to FilterLeaf with iNode = leaf this falls into in the + // back tree and EdPoly = the final EdPoly to insert. + CoplanarInfo.ProcessingBack=1; + FilterEdPoly( FilterFunc, Model, CoplanarInfo.iBackNode, EdPoly,CoplanarInfo, CoplanarInfo.BackNodeOutside, BspPoints, BspVectors ); + } + } +} + +// +// Filter an EdPoly through the Bsp recursively, calling FilterFunc +// for all chunks that fall into leaves. FCoplanarInfo is used to +// handle the tricky case of double-recursion for polys that must be +// filtered through a node's front, then filtered through the node's back, +// in order to handle coplanar CSG properly. +// +void UHCsgUtils::FilterEdPoly +( + BspFilterFunc FilterFunc, + UModel *Model, + int32 iNode, + FPoly *EdPoly, + FCoplanarInfo CoplanarInfo, + int32 Outside, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + int32 SplitResult,iOurFront,iOurBack; + int32 NewFrontOutside,NewBackOutside; + + FilterLoop: + + // Split em. + FPoly TempFrontEdPoly,TempBackEdPoly; + SplitResult = EdPoly->SplitWithPlane + ( + Model->Points [Model->Verts[Model->Nodes[iNode].iVertPool].pVertex], + Model->Vectors[Model->Surfs[Model->Nodes[iNode].iSurf].vNormal], + &TempFrontEdPoly, + &TempBackEdPoly, + 0 + ); + + // Process split results. + if( SplitResult == SP_Front ) + { + Front: + + FBspNode *Node = &Model->Nodes[iNode]; + Outside = Outside || Node->IsCsg(); + + if( Node->iFront == INDEX_NONE ) + { + FilterLeaf(FilterFunc,Model,iNode,EdPoly,CoplanarInfo,Outside,FHBSPOps::NODE_Front, BspPoints, BspVectors); + } + else + { + iNode = Node->iFront; + goto FilterLoop; + } + } + else if( SplitResult == SP_Back ) + { + FBspNode *Node = &Model->Nodes[iNode]; + Outside = Outside && !Node->IsCsg(); + + if( Node->iBack == INDEX_NONE ) + { + FilterLeaf( FilterFunc, Model, iNode, EdPoly, CoplanarInfo, Outside, FHBSPOps::NODE_Back, BspPoints, BspVectors); + } + else + { + iNode=Node->iBack; + goto FilterLoop; + } + } + else if( SplitResult == SP_Coplanar ) + { + if( CoplanarInfo.iOriginalNode != INDEX_NONE ) + { + // This will happen once in a blue moon when a polygon is barely outside the + // coplanar threshold and is split up into a new polygon that is + // is barely inside the coplanar threshold. To handle this, just classify + // it as front and it will be handled propery. + FHBSPOps::GErrors++; +// UE_LOG(LogEditorBsp, Warning, TEXT("FilterEdPoly: Encountered out-of-place coplanar") ); + goto Front; + } + CoplanarInfo.iOriginalNode = iNode; + CoplanarInfo.iBackNode = INDEX_NONE; + CoplanarInfo.ProcessingBack = 0; + CoplanarInfo.BackNodeOutside = Outside; + NewFrontOutside = Outside; + + // See whether Node's iFront or iBack points to the side of the tree on the front + // of this polygon (will be as expected if this polygon is facing the same + // way as first coplanar in link, otherwise opposite). + if( (FVector(Model->Nodes[iNode].Plane) | EdPoly->Normal) >= 0.0 ) + { + iOurFront = Model->Nodes[iNode].iFront; + iOurBack = Model->Nodes[iNode].iBack; + + if( Model->Nodes[iNode].IsCsg() ) + { + CoplanarInfo.BackNodeOutside = 0; + NewFrontOutside = 1; + } + } + else + { + iOurFront = Model->Nodes[iNode].iBack; + iOurBack = Model->Nodes[iNode].iFront; + + if( Model->Nodes[iNode].IsCsg() ) + { + CoplanarInfo.BackNodeOutside = 1; + NewFrontOutside = 0; + } + } + + // Process front and back. + if ((iOurFront==INDEX_NONE)&&(iOurBack==INDEX_NONE)) + { + // No front or back. + CoplanarInfo.ProcessingBack = 1; + CoplanarInfo.FrontLeafOutside = NewFrontOutside; + FilterLeaf + ( + FilterFunc, + Model, + iNode, + EdPoly, + CoplanarInfo, + CoplanarInfo.BackNodeOutside, + FHBSPOps::NODE_Plane, + BspPoints, + BspVectors + ); + } + else if( iOurFront==INDEX_NONE && iOurBack!=INDEX_NONE ) + { + // Back but no front. + CoplanarInfo.ProcessingBack = 1; + CoplanarInfo.iBackNode = iOurBack; + CoplanarInfo.FrontLeafOutside = NewFrontOutside; + + iNode = iOurBack; + Outside = CoplanarInfo.BackNodeOutside; + goto FilterLoop; + } + else + { + // Has a front and maybe a back. + + // Set iOurBack up to process back on next call to FilterLeaf, and loop + // to process front. Next call to FilterLeaf will set FrontLeafOutside. + CoplanarInfo.ProcessingBack = 0; + + // May be a node or may be INDEX_NONE. + CoplanarInfo.iBackNode = iOurBack; + + iNode = iOurFront; + Outside = NewFrontOutside; + goto FilterLoop; + } + } + else if( SplitResult == SP_Split ) + { + // Front half of split. + if( Model->Nodes[iNode].IsCsg() ) + { + NewFrontOutside = 1; + NewBackOutside = 0; + } + else + { + NewFrontOutside = Outside; + NewBackOutside = Outside; + } + + if( Model->Nodes[iNode].iFront==INDEX_NONE ) + { + FilterLeaf + ( + FilterFunc, + Model, + iNode, + &TempFrontEdPoly, + CoplanarInfo, + NewFrontOutside, + FHBSPOps::NODE_Front, + BspPoints, + BspVectors + ); + } + else + { + FilterEdPoly + ( + FilterFunc, + Model, + Model->Nodes[iNode].iFront, + &TempFrontEdPoly, + CoplanarInfo, + NewFrontOutside, + BspPoints, + BspVectors + ); + } + + // Back half of split. + if( Model->Nodes[iNode].iBack==INDEX_NONE ) + { + FilterLeaf + ( + FilterFunc, + Model, + iNode, + &TempBackEdPoly, + CoplanarInfo, + NewBackOutside, + FHBSPOps::NODE_Back, + BspPoints, + BspVectors + ); + } + else + { + FilterEdPoly + ( + FilterFunc, + Model, + Model->Nodes[iNode].iBack, + &TempBackEdPoly, + CoplanarInfo, + NewBackOutside, + BspPoints, + BspVectors + ); + } + } +} + +// +// Regular entry into FilterEdPoly (so higher-level callers don't have to +// deal with unnecessary info). Filters starting at root. +// +void UHCsgUtils::BspFilterFPoly( BspFilterFunc FilterFunc, UModel *Model, FPoly *EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + FCoplanarInfo StartingCoplanarInfo; + StartingCoplanarInfo.iOriginalNode = INDEX_NONE; + if( Model->Nodes.Num() == 0 ) + { + // If Bsp is empty, process at root. + (this->*FilterFunc)( Model, 0, EdPoly, Model->RootOutside ? F_OUTSIDE : F_INSIDE, FHBSPOps::NODE_Root, BspPoints, BspVectors ); + } + else + { + // Filter through Bsp. + FilterEdPoly( FilterFunc, Model, 0, EdPoly, StartingCoplanarInfo, Model->RootOutside, BspPoints, BspVectors ); + } +} + +int UHCsgUtils::bspNodeToFPoly +( + UModel* Model, + int32 iNode, + FPoly* EdPoly +) +{ + FPoly MasterEdPoly; + + FBspNode &Node = Model->Nodes[iNode]; + FBspSurf &Poly = Model->Surfs[Node.iSurf]; + FVert *VertPool = &Model->Verts[ Node.iVertPool ]; + + EdPoly->Base = Model->Points [Poly.pBase]; + EdPoly->Normal = Model->Vectors[Poly.vNormal]; + + EdPoly->PolyFlags = Poly.PolyFlags & ~(PF_EdCut | PF_EdProcessed | PF_Selected | PF_Memorized); + EdPoly->iLinkSurf = Node.iSurf; + EdPoly->Material = Poly.Material; + + EdPoly->Actor = Poly.Actor; + EdPoly->iBrushPoly = Poly.iBrushPoly; + + if( polyFindMaster(Model,Node.iSurf,MasterEdPoly) ) + EdPoly->ItemName = MasterEdPoly.ItemName; + else + EdPoly->ItemName = NAME_None; + + EdPoly->TextureU = Model->Vectors[Poly.vTextureU]; + EdPoly->TextureV = Model->Vectors[Poly.vTextureV]; + + EdPoly->LightMapScale = Poly.LightMapScale; + + EdPoly->LightmassSettings = Model->LightmassSettings[Poly.iLightmassIndex]; + + EdPoly->Vertices.Empty(); + + for(int32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++) + { + new(EdPoly->Vertices) FVector(Model->Points[VertPool[VertexIndex].pVertex]); + } + + if(EdPoly->Vertices.Num() < 3) + { + EdPoly->Vertices.Empty(); + } + else + { + // Remove colinear points and identical points (which will appear + // if T-joints were eliminated). + EdPoly->RemoveColinears(); + } + + return EdPoly->Vertices.Num(); +} + +/*--------------------------------------------------------------------------------------- + World filtering. +---------------------------------------------------------------------------------------*/ + +// +// Filter all relevant world polys through the brush. +// +void UHCsgUtils::FilterWorldThroughBrush +( + UModel* Model, + UModel* Brush, + EBrushType BrushType, + ECsgOper CSGOper, + int32 iNode, + FSphere* BrushSphere, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + // Loop through all coplanars. + while( iNode != INDEX_NONE ) + { + // Get surface. + int32 iSurf = Model->Nodes[iNode].iSurf; + + // Skip new nodes and their children, which are guaranteed new. + if( Model->Nodes[iNode].NodeFlags & NF_IsNew ) + return; + + // Sphere reject. + int DoFront = 1, DoBack = 1; + if( BrushSphere ) + { + float Dist = Model->Nodes[iNode].Plane.PlaneDot( BrushSphere->Center ); + DoFront = (Dist >= -BrushSphere->W); + DoBack = (Dist <= +BrushSphere->W); + } + + // Process only polys that aren't empty. + FPoly TempEdPoly; + if( DoFront && DoBack && (GEditor->bspNodeToFPoly(Model,iNode,&TempEdPoly)>0) ) + { + TempEdPoly.Actor = Model->Surfs[iSurf].Actor; + TempEdPoly.iBrushPoly = Model->Surfs[iSurf].iBrushPoly; + + if( BrushType==Brush_Add || BrushType==Brush_Subtract ) + { + // Add and subtract work the same in this step. + GNode = iNode; + GModel = Model; + GDiscarded = 0; + GNumNodes = Model->Nodes.Num(); + + // Find last coplanar in chain. + GLastCoplanar = iNode; + while( Model->Nodes[GLastCoplanar].iPlane != INDEX_NONE ) + GLastCoplanar = Model->Nodes[GLastCoplanar].iPlane; + + // Do the filter operation. + BspFilterFPoly + ( + BrushType==Brush_Add ? &UHCsgUtils::AddWorldToBrushFunc : &UHCsgUtils::SubtractWorldToBrushFunc, + Brush, + &TempEdPoly, + BspPoints, + BspVectors + ); + + if( GDiscarded == 0 ) + { + // Get rid of all the fragments we added. + Model->Nodes[GLastCoplanar].iPlane = INDEX_NONE; + const bool bAllowShrinking = false; + Model->Nodes.RemoveAt( GNumNodes, Model->Nodes.Num()-GNumNodes, bAllowShrinking ); + } + else + { + // Tag original world poly for deletion; has been deleted or replaced by partial fragments. + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + } + } + else if( CSGOper == CSG_Intersect ) + { + BspFilterFPoly( &UHCsgUtils::IntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); + } + else if( CSGOper == CSG_Deintersect ) + { + BspFilterFPoly( &UHCsgUtils::DeIntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); + } + } + + // Now recurse to filter all of the world's children nodes. + if( DoFront && (Model->Nodes[iNode].iFront != INDEX_NONE)) FilterWorldThroughBrush + ( + Model, + Brush, + BrushType, + CSGOper, + Model->Nodes[iNode].iFront, + BrushSphere, + BspPoints, + BspVectors + ); + if( DoBack && (Model->Nodes[iNode].iBack != INDEX_NONE) ) FilterWorldThroughBrush + ( + Model, + Brush, + BrushType, + CSGOper, + Model->Nodes[iNode].iBack, + BrushSphere, + BspPoints, + BspVectors + ); + iNode = Model->Nodes[iNode].iPlane; + } +} + +void UHCsgUtils::RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic) +{ + if (!IsValid(Model)) + return; + + UHCsgUtils* CsgUtils = NewObject(); + int32 CsgErrors = 0; + + UHBspPointsGrid* BspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* BspVectors = UHBspPointsGrid::Create(1/16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + + // Empty the model out. + const int32 NumPoints = Model->Points.Num(); + const int32 NumNodes = Model->Nodes.Num(); + const int32 NumVerts = Model->Verts.Num(); + const int32 NumVectors = Model->Vectors.Num(); + const int32 NumSurfs = Model->Surfs.Num(); + + Model->Modify(); + Model->EmptyModel(1, 1); + + // Reserve arrays an eighth bigger than the previous allocation + Model->Points.Empty(NumPoints + NumPoints / 8); + Model->Nodes.Empty(NumNodes + NumNodes / 8); + Model->Verts.Empty(NumVerts + NumVerts / 8); + Model->Vectors.Empty(NumVectors + NumVectors / 8); + Model->Surfs.Empty(NumSurfs + NumSurfs / 8); + + // Build list of all static brushes, first structural brushes and portals + TArray StaticBrushes; + for (ABrush* Brush : Brushes) + { + if ((Brush && (Brush->IsStaticBrush() || bTreatMovableBrushesAsStatic) && !FActorEditorUtils::IsABuilderBrush(Brush)) && + (!(Brush->PolyFlags & PF_Semisolid) || (Brush->BrushType != Brush_Add) || (Brush->PolyFlags & PF_Portal))) + { + StaticBrushes.Add(Brush); + + // Treat portals as solids for cutting. + if (Brush->PolyFlags & PF_Portal) + { + Brush->PolyFlags = (Brush->PolyFlags & ~PF_Semisolid) | PF_NotSolid; + } + } + } + + // Next append all detail brushes + for (ABrush* Brush : Brushes) + { + if (Brush && Brush->IsStaticBrush() && !FActorEditorUtils::IsABuilderBrush(Brush) && + (Brush->PolyFlags & PF_Semisolid) && !(Brush->PolyFlags & PF_Portal) && (Brush->BrushType == Brush_Add)) + { + StaticBrushes.Add(Brush); + } + } + + // Build list of dynamic brushes + TArray DynamicBrushes; + if (!bTreatMovableBrushesAsStatic) + { + for (ABrush* DynamicBrush : Brushes) + { + if (DynamicBrush && DynamicBrush->Brush && !DynamicBrush->IsStaticBrush()) + { + DynamicBrushes.Add(DynamicBrush); + } + } + } + + FScopedSlowTask SlowTask(StaticBrushes.Num() + DynamicBrushes.Num()); + SlowTask.MakeDialogDelayed(3.0f); + + // Compose all static brushes + for (ABrush* Brush : StaticBrushes) + { + SlowTask.EnterProgressFrame(1); + Brush->Modify(); + int32 Errors = CsgUtils->ComposeBrushCSG(Brush, Model, Brush->PolyFlags, (EBrushType)Brush->BrushType, CSG_None, false, true, false, false, BspPoints, BspVectors); + if (Errors > 1) + CsgErrors += Errors - 1; + } + + // Rebuild dynamic brush BSP's (if they weren't handled earlier) + for (ABrush* DynamicBrush : DynamicBrushes) + { + SlowTask.EnterProgressFrame(1); + UHBspPointsGrid* LocalBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* LocalBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + + FHBSPOps::csgPrepMovingBrush(DynamicBrush, LocalBspPoints, LocalBspVectors); + } +} + + + +UModel* UHCsgUtils::BuildModelFromBrushes(TArray& Brushes) +{ + // Generally UModels are initialized using ABrush. Here we manually + // initialize using relevant parts from + UModel* OutModel = NewObject(); + OutModel->SetFlags(RF_Transactional); + OutModel->RootOutside = true; + OutModel->EmptyModel(1,1); + OutModel->UpdateVertices(); + + if (!IsValid(OutModel)) + return nullptr; + + // Can we combine the brushes without modifying the actors here ...? + + //FVector Location(0.0f, 0.0f, 0.0f); + //FRotator Rotation(0.0f, 0.0f, 0.0f); + //for(int32 BrushesIdx = 0; BrushesIdx < Brushes.Num(); ++BrushesIdx ) + //{ + // // Cache the location and rotation. + // Location = Brushes[BrushesIdx]->GetActorLocation(); + // Rotation = Brushes[BrushesIdx]->GetActorRotation(); + + + // // Leave the actor's rotation but move it to origin so the Static Mesh will generate correctly. + // Brushes[BrushesIdx]->TeleportTo(Location - InPivotLocation, Rotation, false, true); + //} + + RebuildModelFromBrushes(OutModel, Brushes, true); + //GEditor->bspBuildFPolys(OutModel, true, 0); + + //if (0 < ConversionTempModel->Polys->Element.Num()) + //{ + // UStaticMesh* NewMesh = CreateStaticMeshFromBrush(Pkg, ObjName, NULL, ConversionTempModel); + // NewActor = FActorFactoryAssetProxy::AddActorForAsset( NewMesh ); + + // NewActor->Modify(); + + // NewActor->InvalidateLightingCache(); + // NewActor->PostEditChange(); + // NewActor->PostEditMove( true ); + // NewActor->Modify(); + // ULayersSubsystem* LayersSubsystem = GetEditorSubsystem(); + // LayersSubsystem->InitializeNewActorLayers(NewActor); + + // // Teleport the new actor to the old location but not the old rotation. The static mesh is built to the rotation already. + // NewActor->TeleportTo(InPivotLocation, FRotator(0.0f, 0.0f, 0.0f), false, true); + + // // Destroy the old brushes. + // for( int32 BrushIdx = 0; BrushIdx < InBrushesToConvert.Num(); ++BrushIdx ) + // { + // LayersSubsystem->DisassociateActorFromLayers(InBrushesToConvert[BrushIdx]); + // GWorld->EditorDestroyActor( InBrushesToConvert[BrushIdx], true ); + // } + + // // Notify the asset registry + // FAssetRegistryModule::AssetCreated(NewMesh); + //} + + //ConversionTempModel->EmptyModel(1, 1); + //RebuildAlteredBSP(); + //RedrawLevelEditingViewports(); + + //return NewActor; + + return OutModel; +} + +int UHCsgUtils::ComposeBrushCSG +( + ABrush* Actor, + UModel* Model, + uint32 PolyFlags, + EBrushType BrushType, + ECsgOper CSGOper, + bool bBuildBounds, + bool bMergePolys, + bool bReplaceNULLMaterialRefs, + bool bShowProgressBar, /*=true*/ + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + uint32 NotPolyFlags = 0; + int32 NumPolysFromBrush=0,i,j,ReallyBig; + UModel* Brush = Actor->Brush; + int32 Errors = 0; + + // Make sure we're in an acceptable state. + if( !Brush ) + { + return 0; + } + + // Non-solid and semisolid stuff can only be added. + if( BrushType != Brush_Add ) + { + NotPolyFlags |= (PF_Semisolid | PF_NotSolid); + } + + TempModel->EmptyModel(1,1); + + // Update status. + ReallyBig = (Brush->Polys->Element.Num() > 200) && bShowProgressBar; + if( ReallyBig ) + { + FText Description = NSLOCTEXT("UnrealEd", "PerformingCSGOperation", "Performing CSG operation"); + + if (BrushType != Brush_MAX) + { + if (BrushType == Brush_Add) + { + Description = NSLOCTEXT("UnrealEd", "AddingBrushToWorld", "Adding brush to world"); + } + else if (BrushType == Brush_Subtract) + { + Description = NSLOCTEXT("UnrealEd", "SubtractingBrushFromWorld", "Subtracting brush from world"); + } + } + else if (CSGOper != CSG_None) + { + if (CSGOper == CSG_Intersect) + { + Description = NSLOCTEXT("UnrealEd", "IntersectingBrushWithWorld", "Intersecting brush with world"); + } + else if (CSGOper == CSG_Deintersect) + { + Description = NSLOCTEXT("UnrealEd", "DeintersectingBrushWithWorld", "Deintersecting brush with world"); + } + } + + GWarn->BeginSlowTask( Description, true ); + // Transform original brush poly into same coordinate system as world + // so Bsp filtering operations make sense. + GWarn->StatusUpdate(0, 0, NSLOCTEXT("UnrealEd", "Transforming", "Transforming")); + } + + + //UMaterialInterface* SelectedMaterialInstance = GEditor->GetSelectedObjects()->GetTop(); + UMaterialInterface* SelectedMaterialInstance = nullptr; + + const FVector Scale = Actor->GetActorScale(); + const FRotator Rotation = Actor->GetActorRotation(); + const FVector Location = Actor->GetActorLocation(); + + const bool bIsMirrored = (Scale.X * Scale.Y * Scale.Z < 0.0f); + + // Cache actor transform which is used for the geometry being built + Brush->OwnerLocationWhenLastBuilt = Location; + Brush->OwnerRotationWhenLastBuilt = Rotation; + Brush->OwnerScaleWhenLastBuilt = Scale; + Brush->bCachedOwnerTransformValid = true; + + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly& CurrentPoly = Brush->Polys->Element[i]; + + // Set texture the first time. + if ( bReplaceNULLMaterialRefs ) + { + UMaterialInterface*& PolyMat = CurrentPoly.Material; + if ( !PolyMat || PolyMat == UMaterial::GetDefaultMaterial(MD_Surface) ) + { + PolyMat = SelectedMaterialInstance; + } + } + + // Get the brush poly. + FPoly DestEdPoly = CurrentPoly; + check(CurrentPoly.iLinkPolys->Element.Num()); + + // Set its backward brush link. + DestEdPoly.Actor = Actor; + DestEdPoly.iBrushPoly = i; + + // Update its flags. + DestEdPoly.PolyFlags = (DestEdPoly.PolyFlags | PolyFlags) & ~NotPolyFlags; + + // Set its internal link. + if (DestEdPoly.iLink == INDEX_NONE) + { + DestEdPoly.iLink = i; + } + + // Transform it. + DestEdPoly.Scale( Scale ); + DestEdPoly.Rotate( Rotation ); + DestEdPoly.Transform( Location ); + + // Reverse winding and normal if the parent brush is mirrored + if (bIsMirrored) + { + DestEdPoly.Reverse(); + DestEdPoly.CalcNormal(); + } + + // Add poly to the temp model. + new(TempModel->Polys->Element)FPoly( DestEdPoly ); + } + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringBrush", "Filtering brush") ); + + // Pass the brush polys through the world Bsp. + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + // Empty the brush. + Brush->EmptyModel(1,1); + + // Intersect and deintersect. + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly EdPoly = TempModel->Polys->Element[i]; + GModel = Brush; + // TODO: iLink / iLinkSurf in EdPoly / TempModel->Polys->Element[i] ? + BspFilterFPoly( CSGOper==CSG_Intersect ? &UHCsgUtils::IntersectBrushWithWorldFunc : &UHCsgUtils::DeIntersectBrushWithWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); + } + NumPolysFromBrush = Brush->Polys->Element.Num(); + } + else + { + // Add and subtract. + TMap SurfaceIndexRemap; + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly EdPoly = TempModel->Polys->Element[i]; + + // Mark the polygon as non-cut so that it won't be harmed unless it must + // be split, and set iLink so that BspAddNode will know to add its information + // if a node is added based on this poly. + EdPoly.PolyFlags &= ~(PF_EdCut); + const int32* SurfaceIndexPtr = SurfaceIndexRemap.Find(EdPoly.iLink); + if (SurfaceIndexPtr == nullptr) + { + const int32 NewSurfaceIndex = Model->Surfs.Num(); + SurfaceIndexRemap.Add(EdPoly.iLink, NewSurfaceIndex); + EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = NewSurfaceIndex; + } + else + { + EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = *SurfaceIndexPtr; + } + + // Filter brush through the world. + BspFilterFPoly( BrushType==Brush_Add ? &UHCsgUtils::AddBrushToWorldFunc : &UHCsgUtils::SubtractBrushFromWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); + } + } + if( Model->Nodes.Num() && !(PolyFlags & (PF_NotSolid | PF_Semisolid)) ) + { + // Quickly build a Bsp for the brush, tending to minimize splits rather than balance + // the tree. We only need the cutting planes, though the entire Bsp struct (polys and + // all) is built. + + /*FHBspPointsGrid* LevelModelPointsGrid = FHBspPointsGrid::GBspPoints; + FHBspPointsGrid* LevelModelVectorsGrid = FHBspPointsGrid::GBspVectors;*/ + + // For the bspBuild call, temporarily create a new pair of BspPointsGrids for the TempModel. + UHBspPointsGrid* TempBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* TempBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + /*FHBspPointsGrid::GBspPoints = BspPoints.Get(); + FHBspPointsGrid::GBspVectors = BspVectors.Get();*/ + + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "BuildingBSP", "Building BSP") ); + + FHBSPOps::bspBuild( TempModel, FHBSPOps::BSP_Lame, 0, 70, 1, 0, TempBspPoints, TempBspVectors ); + + // Reinstate the original BspPointsGrids used for building the level Model. + /*FHBspPointsGrid::GBspPoints = LevelModelPointsGrid; + FHBspPointsGrid::GBspVectors = LevelModelVectorsGrid;*/ + + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringWorld", "Filtering world") ); + GModel = Brush; + TempModel->BuildBound(); + + FSphere BrushSphere = TempModel->Bounds.GetSphere(); + FilterWorldThroughBrush( Model, TempModel, BrushType, CSGOper, 0, &BrushSphere, BspPoints, BspVectors); + } + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "AdjustingBrush", "Adjusting brush") ); + + // Link polys obtained from the original brush. + for( i=NumPolysFromBrush-1; i>=0; i-- ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + for( j=0; jiLink == Brush->Polys->Element[j].iLink ) + { + DestEdPoly->iLink = j; + break; + } + } + if( j >= i ) DestEdPoly->iLink = i; + } + + // Link polys obtained from the world. + for( i=Brush->Polys->Element.Num()-1; i>=NumPolysFromBrush; i-- ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + for( j=NumPolysFromBrush; jiLink == Brush->Polys->Element[j].iLink ) + { + DestEdPoly->iLink = j; + break; + } + } + if( j >= i ) DestEdPoly->iLink = i; + } + Brush->Linked = 1; + + // Detransform the obtained brush back into its original coordinate system. + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + DestEdPoly->Transform(-Location); + DestEdPoly->Rotate(Rotation.GetInverse()); + DestEdPoly->Scale(FVector(1.0f) / Scale); + DestEdPoly->Fix(); + DestEdPoly->Actor = NULL; + DestEdPoly->iBrushPoly = i; + } + } + + if( BrushType==Brush_Add || BrushType==Brush_Subtract ) + { + // Clean up nodes, reset node flags. + bspCleanup( Model ); + + // Rebuild bounding volumes. + if( bBuildBounds ) + { + FHBSPOps::bspBuildBounds( Model ); + } + } + + Brush->NumUniqueVertices = TempModel->Points.Num(); + // Release TempModel. + TempModel->EmptyModel(1,1); + + // Merge coplanars if needed. + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + if( ReallyBig ) + { + GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "Merging", "Merging") ); + } + if( bMergePolys ) + { + bspMergeCoplanars( Brush, 1, 0 ); + } + } + if( ReallyBig ) + { + GWarn->EndSlowTask(); + } + + return 1 + FHBSPOps::GErrors; +} + +/*---------------------------------------------------------------------------- + EdPoly building and compacting. +----------------------------------------------------------------------------*/ + +// +// Trys to merge two polygons. If they can be merged, replaces Poly1 and emptys Poly2 +// and returns 1. Otherwise, returns 0. +// +int UHCsgUtils::TryToMerge( FPoly *Poly1, FPoly *Poly2 ) +{ + // Find one overlapping point. + int32 Start1=0, Start2=0; + for( Start1=0; Start1Vertices.Num(); Start1++ ) + for( Start2=0; Start2Vertices.Num(); Start2++ ) + if( FVector::PointsAreSame(Poly1->Vertices[Start1], Poly2->Vertices[Start2]) ) + goto FoundOverlap; + return 0; + FoundOverlap: + + // Wrap around trying to merge. + int32 End1 = Start1; + int32 End2 = Start2; + int32 Test1 = Start1+1; if (Test1>=Poly1->Vertices.Num()) Test1 = 0; + int32 Test2 = Start2-1; if (Test2<0) Test2 = Poly2->Vertices.Num()-1; + if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) + { + End1 = Test1; + Start2 = Test2; + } + else + { + Test1 = Start1-1; if (Test1<0) Test1=Poly1->Vertices.Num()-1; + Test2 = Start2+1; if (Test2>=Poly2->Vertices.Num()) Test2=0; + if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) + { + Start1 = Test1; + End2 = Test2; + } + else return 0; + } + + // Build a new edpoly containing both polygons merged. + FPoly NewPoly = *Poly1; + NewPoly.Vertices.Empty(); + int32 Vertex = End1; + for( int32 i=0; iVertices.Num(); i++ ) + { + new(NewPoly.Vertices) FVector(Poly1->Vertices[Vertex]); + if( ++Vertex >= Poly1->Vertices.Num() ) + Vertex=0; + } + Vertex = End2; + for( int32 i=0; i<(Poly2->Vertices.Num()-2); i++ ) + { + if( ++Vertex >= Poly2->Vertices.Num() ) + Vertex=0; + new(NewPoly.Vertices) FVector(Poly2->Vertices[Vertex]); + } + + // Remove colinear vertices and check convexity. + if( NewPoly.RemoveColinears() ) + { + *Poly1 = NewPoly; + Poly2->Vertices.Empty(); + return true; + } + else return 0; +} + +// +// Merge all polygons in coplanar list that can be merged convexly. +// +void UHCsgUtils::MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ) +{ + int32 MergeAgain = 1; + while( MergeAgain ) + { + MergeAgain = 0; + for( int32 i=0; iPolys->Element[PolyList[i]]; + if( Poly1.Vertices.Num() > 0 ) + { + for( int32 j=i+1; jPolys->Element[PolyList[j]]; + if( Poly2.Vertices.Num() > 0 ) + { + if( TryToMerge( &Poly1, &Poly2 ) ) + MergeAgain=1; + } + } + } + } + } +} + +void UHCsgUtils::bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ) +{ + int32 OriginalNum = Model->Polys->Element.Num(); + + // Mark all polys as unprocessed. + for( int32 i=0; iPolys->Element.Num(); i++ ) + Model->Polys->Element[i].PolyFlags &= ~PF_EdProcessed; + + // Find matching coplanars and merge them. + FMemMark Mark(FMemStack::Get()); + int32* PolyList = new(FMemStack::Get(),Model->Polys->Element.Num())int32; + int32 n=0; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + FPoly* EdPoly = &Model->Polys->Element[i]; + if( EdPoly->Vertices.Num()>0 && !(EdPoly->PolyFlags & PF_EdProcessed) ) + { + int32 PolyCount = 0; + PolyList[PolyCount++] = i; + EdPoly->PolyFlags |= PF_EdProcessed; + for( int32 j=i+1; jPolys->Element.Num(); j++ ) + { + FPoly* OtherPoly = &Model->Polys->Element[j]; + if( OtherPoly->iLink == EdPoly->iLink && OtherPoly->Vertices.Num() ) + { + float Dist = (OtherPoly->Vertices[0] - EdPoly->Vertices[0]) | EdPoly->Normal; + if + ( Dist>-0.001 + && Dist<0.001 + && (OtherPoly->Normal|EdPoly->Normal)>0.9999 + && (MergeDisparateTextures + || ( FVector::PointsAreNear(OtherPoly->TextureU,EdPoly->TextureU,THRESH_VECTORS_ARE_NEAR) + && FVector::PointsAreNear(OtherPoly->TextureV,EdPoly->TextureV,THRESH_VECTORS_ARE_NEAR) ) ) ) + { + OtherPoly->PolyFlags |= PF_EdProcessed; + PolyList[PolyCount++] = j; + } + } + } + if( PolyCount > 1 ) + { + MergeCoplanars( Model, PolyList, PolyCount ); + n++; + } + } + } +// UE_LOG(LogEditorBsp, Log, TEXT("Found %i coplanar sets in %i"), n, Model->Polys->Element.Num() ); + Mark.Pop(); + + // Get rid of empty EdPolys while remapping iLinks. + FMemMark Mark2(FMemStack::Get()); + int32 j=0; + int32* Remap = new(FMemStack::Get(),Model->Polys->Element.Num())int32; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + if( Model->Polys->Element[i].Vertices.Num() ) + { + Remap[i] = j; + Model->Polys->Element[j] = Model->Polys->Element[i]; + j++; + } + } + Model->Polys->Element.RemoveAt( j, Model->Polys->Element.Num()-j ); + if( RemapLinks ) + { + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + if (Model->Polys->Element[i].iLink != INDEX_NONE) + { + CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*Remap'. + Model->Polys->Element[i].iLink = Remap[Model->Polys->Element[i].iLink]; + } + } + } +// UE_LOG(LogEditorBsp, Log, TEXT("BspMergeCoplanars reduced %i->%i"), OriginalNum, Model->Polys->Element.Num() ); + Mark2.Pop(); +} + +bool UHCsgUtils::polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly) +{ + FBspSurf &Surf = InModel->Surfs[iSurf]; + if( !Surf.Actor || !Surf.Actor->Brush->Polys->Element.IsValidIndex(Surf.iBrushPoly) ) + { + return false; + } + else + { + Poly = Surf.Actor->Brush->Polys->Element[Surf.iBrushPoly]; + return true; + } +} + +void UHCsgUtils::CleanupNodes( UModel *Model, int32 iNode, int32 iParent ) +{ + FBspNode *Node = &Model->Nodes[iNode]; + + // Transactionally empty vertices of tag-for-empty nodes. + Node->NodeFlags &= ~(NF_IsNew | NF_IsFront | NF_IsBack); + + // Recursively clean up front, back, and plane nodes. + if( Node->iFront != INDEX_NONE ) CleanupNodes( Model, Node->iFront, iNode ); + if( Node->iBack != INDEX_NONE ) CleanupNodes( Model, Node->iBack , iNode ); + if( Node->iPlane != INDEX_NONE ) CleanupNodes( Model, Node->iPlane, iNode ); + + // Reload Node since the recusive call aliases it. + Node = &Model->Nodes[iNode]; + + // If this is an empty node with a coplanar, replace it with the coplanar. + if( Node->NumVertices==0 && Node->iPlane!=INDEX_NONE ) + { + FBspNode* PlaneNode = &Model->Nodes[ Node->iPlane ]; + + // Stick our front, back, and parent nodes on the coplanar. + if( (Node->Plane | PlaneNode->Plane) >= 0.0 ) + { + PlaneNode->iFront = Node->iFront; + PlaneNode->iBack = Node->iBack; + } + else + { + PlaneNode->iFront = Node->iBack; + PlaneNode->iBack = Node->iFront; + } + + if( iParent == INDEX_NONE ) + { + // This node is the root. + *Node = *PlaneNode; // Replace root. + PlaneNode->NumVertices = 0; // Mark as unused. + } + else + { + // This is a child node. + FBspNode *ParentNode = &Model->Nodes[iParent]; + + if ( ParentNode->iFront == iNode ) ParentNode->iFront = Node->iPlane; + else if ( ParentNode->iBack == iNode ) ParentNode->iBack = Node->iPlane; + else if ( ParentNode->iPlane == iNode ) ParentNode->iPlane = Node->iPlane; + else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); + } + } + else if( Node->NumVertices == 0 && ( Node->iFront==INDEX_NONE || Node->iBack==INDEX_NONE ) ) + { + // Delete empty nodes with no fronts or backs. + // Replace empty nodes with only fronts. + // Replace empty nodes with only backs. + int32 iReplacementNode; + if ( Node->iFront != INDEX_NONE ) iReplacementNode = Node->iFront; + else if( Node->iBack != INDEX_NONE ) iReplacementNode = Node->iBack; + else iReplacementNode = INDEX_NONE; + + if( iParent == INDEX_NONE ) + { + // Root. + if( iReplacementNode == INDEX_NONE ) + { + Model->Nodes.Empty(); + } + else + { + *Node = Model->Nodes[iReplacementNode]; + } + } + else + { + // Regular node. + FBspNode *ParentNode = &Model->Nodes[iParent]; + + if ( ParentNode->iFront == iNode ) ParentNode->iFront = iReplacementNode; + else if( ParentNode->iBack == iNode ) ParentNode->iBack = iReplacementNode; + else if( ParentNode->iPlane == iNode ) ParentNode->iPlane = iReplacementNode; + else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); + } + } +} + + +void UHCsgUtils::bspCleanup( UModel *Model ) +{ + if( Model->Nodes.Num() > 0 ) + CleanupNodes( Model, 0, INDEX_NONE ); +} diff --git a/Source/HoudiniEngine/Private/HCsgUtils.h b/Source/HoudiniEngine/Private/HCsgUtils.h index cd6e1ce51..a14957ddb 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.h +++ b/Source/HoudiniEngine/Private/HCsgUtils.h @@ -1,253 +1,253 @@ - -#pragma once - -#include "CoreMinimal.h" - -#include "HBSPOps.h" -#include "Engine/Brush.h" -#include "Model.h" - -#include "HCsgUtils.generated.h" - -//USTRUCT() -//struct FHCsgContext -//{ -// GENERATED_BODY() -// -// int32 Errors; -// -// -// UPROPERTY() -// class UModel* TempModel; -// -// UPROPERTY() -// class UModel* ConversionTempModel; -//}; - -// This HCsgUtils is one big fork of the codebase located UnrealEd/Private/EditorBsp.cpp. -// The main purpose was to remove parts of the code that store state in global/static variables as well -// as dependency on editor state (such as retrieving selected brushes). -UCLASS() -class HOUDINIENGINE_API UHCsgUtils : public UObject -{ - GENERATED_BODY() -public: - - UHCsgUtils(); - - /** - * Builds up a model from a set of brushes. Used by RebuildLevel. - * - * @param Model The model to be rebuilt. - * @param bSelectedBrushesOnly Use all brushes in the current level or just the selected ones?. - * @param bTreatMovableBrushesAsStatic Treat moveable brushes as static?. - */ - static void RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic); - - /** - * Converts passed in brushes into a single static mesh actor. - * Note: This replaces all the brushes with a single actor. This actor will not be attached to anything unless a single brush was converted. - * - * @param InStaticMeshPackageName The name to save the brushes to. - * @param InBrushesToConvert A list of brushes being converted. - * - * @return Returns the newly created actor with the newly created static mesh. - */ - static UModel* BuildModelFromBrushes(TArray& Brushes); - - /** - * Forked version of UEditorEngine::bspBrushCSG() from UnrealEd/Private/EditorBsp.cpp. - * - * Apply the appropriate CSG operation required in order to compose the brush actor onto the given model. - * - * @param Actor The brush actor to apply. - * @param Model The model to apply the CSG operation to; typically the world's model. - * @param PolyFlags PolyFlags to set on brush's polys. - * @param BrushType The type of brush. - * @param CSGOper The CSG operation to perform. - * @param bBuildBounds If true, updates bounding volumes on Model for CSG_Add or CSG_Subtract operations. - * @param bMergePolys If true, coplanar polygons are merged for CSG_Intersect or CSG_Deintersect operations. - * @param bReplaceNULLMaterialRefs If true, replace NULL material references with a reference to the GB-selected material. - * @param bShowProgressBar If true, display progress bar for complex brushes - * @return 0 if nothing happened, 1 if the operation was error-free, or 1+N if N CSG errors occurred. - */ - int ComposeBrushCSG( - ABrush* Actor, - UModel* Model, - uint32 PolyFlags, - EBrushType BrushType, - ECsgOper CSGOper, - bool bBuildBounds, - bool bMergePolys, - bool bReplaceNULLMaterialRefs, - bool bShowProgressBar, /*=true*/ - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - -protected: - // - // Status of filtered polygons: - // - enum EPolyNodeFilter - { - F_OUTSIDE = 0, // Leaf is an exterior leaf (visible to viewers). - F_INSIDE = 1, // Leaf is an interior leaf (non-visible, hidden behind backface). - F_COPLANAR_OUTSIDE = 2, // Poly is coplanar and in the exterior (visible to viewers). - F_COPLANAR_INSIDE = 3, // Poly is coplanar and inside (invisible to viewers). - F_COSPATIAL_FACING_IN = 4, // Poly is coplanar, cospatial, and facing in. - F_COSPATIAL_FACING_OUT = 5, // Poly is coplanar, cospatial, and facing out. - }; - - - // - // Information used by FilterEdPoly. - // - class FCoplanarInfo - { - public: - int32 iOriginalNode; - int32 iBackNode; - int BackNodeOutside; - int FrontLeafOutside; - int ProcessingBack; - }; - - // - // Generic filter function called by BspFilterEdPolys. A and B are pointers - // to any integers that your specific routine requires (or NULL if not needed). - // - typedef void (UHCsgUtils::*BspFilterFunc) - ( - UModel* Model, - int32 iNode, - FPoly* EdPoly, - EPolyNodeFilter Leaf, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // State shared between bspBrushCSG and AddWorldToBrushFunc. These are very - // tightly tied into the function AddWorldToBrush, not for general use. - // - int32 GDiscarded; // Number of polys discarded and not added. - int32 GNode; // Node AddBrushToWorld is adding to. - int32 GLastCoplanar; // Last coplanar beneath GNode at start of AddWorldToBrush. - int32 GNumNodes; // Number of Bsp nodes at start of AddWorldToBrush. - - UPROPERTY() - UModel* GModel; // Level map Model we're adding to. - - UPROPERTY() - class UModel* TempModel; - - //// Globals removed from FBspPointsGrid - //UPROPERTY() - //UHBspPointsGrid* GBspPoints; - - //UPROPERTY() - //UHBspPointsGrid* GBspVectors; - - /*struct BspFilterOp { - void Apply(UHCsgUtils* Obj, UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) {}; - };*/ - - // - // Handle a piece of a polygon that was filtered to a leaf. - // - void FilterLeaf( - BspFilterFunc FilterFunc, - UModel* Model, - int32 iNode, - FPoly* EdPoly, - FCoplanarInfo CoplanarInfo, - int32 LeafOutside, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // Function to filter an EdPoly through the Bsp, calling a callback - // function for all chunks that fall into leaves. - // - void FilterEdPoly - ( - BspFilterFunc FilterFunc, - UModel *Model, - int32 iNode, - FPoly *EdPoly, - FCoplanarInfo CoplanarInfo, - int32 Outside, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // Regular entry into FilterEdPoly (so higher-level callers don't have to - // deal with unnecessary info). Filters starting at root. - // - void BspFilterFPoly - ( - BspFilterFunc FilterFunc, - UModel *Model, - FPoly *EdPoly, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - - int bspNodeToFPoly - ( - UModel* Model, - int32 iNode, - FPoly* EdPoly - ); - - - //---------------------------------------------------------------------------- - // World Filtering - //---------------------------------------------------------------------------- - - // - // Filter all relevant world polys through the brush. - // - void FilterWorldThroughBrush - ( - UModel* Model, - UModel* Brush, - EBrushType BrushType, - ECsgOper CSGOper, - int32 iNode, - FSphere* BrushSphere, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - //---------------------------------------------------------------------------- - // CSG leaf filter callbacks / operations. - // --------------------------------------------------------------------------- - void AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - - //---------------------------------------------------------------------------- - // Forked various functions located in: EditorBsp.cpp, EditorCsg.cpp - //---------------------------------------------------------------------------- - static int TryToMerge( FPoly *Poly1, FPoly *Poly2 ); - static void MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ); - void bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ); - bool polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly); - static void CleanupNodes( UModel *Model, int32 iNode, int32 iParent ); - void bspCleanup( UModel *Model ); - -}; - + +#pragma once + +#include "CoreMinimal.h" + +#include "HBSPOps.h" +#include "Engine/Brush.h" +#include "Model.h" + +#include "HCsgUtils.generated.h" + +//USTRUCT() +//struct FHCsgContext +//{ +// GENERATED_BODY() +// +// int32 Errors; +// +// +// UPROPERTY() +// class UModel* TempModel; +// +// UPROPERTY() +// class UModel* ConversionTempModel; +//}; + +// This HCsgUtils is one big fork of the codebase located UnrealEd/Private/EditorBsp.cpp. +// The main purpose was to remove parts of the code that store state in global/static variables as well +// as dependency on editor state (such as retrieving selected brushes). +UCLASS() +class HOUDINIENGINE_API UHCsgUtils : public UObject +{ + GENERATED_BODY() +public: + + UHCsgUtils(); + + /** + * Builds up a model from a set of brushes. Used by RebuildLevel. + * + * @param Model The model to be rebuilt. + * @param bSelectedBrushesOnly Use all brushes in the current level or just the selected ones?. + * @param bTreatMovableBrushesAsStatic Treat moveable brushes as static?. + */ + static void RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic); + + /** + * Converts passed in brushes into a single static mesh actor. + * Note: This replaces all the brushes with a single actor. This actor will not be attached to anything unless a single brush was converted. + * + * @param InStaticMeshPackageName The name to save the brushes to. + * @param InBrushesToConvert A list of brushes being converted. + * + * @return Returns the newly created actor with the newly created static mesh. + */ + static UModel* BuildModelFromBrushes(TArray& Brushes); + + /** + * Forked version of UEditorEngine::bspBrushCSG() from UnrealEd/Private/EditorBsp.cpp. + * + * Apply the appropriate CSG operation required in order to compose the brush actor onto the given model. + * + * @param Actor The brush actor to apply. + * @param Model The model to apply the CSG operation to; typically the world's model. + * @param PolyFlags PolyFlags to set on brush's polys. + * @param BrushType The type of brush. + * @param CSGOper The CSG operation to perform. + * @param bBuildBounds If true, updates bounding volumes on Model for CSG_Add or CSG_Subtract operations. + * @param bMergePolys If true, coplanar polygons are merged for CSG_Intersect or CSG_Deintersect operations. + * @param bReplaceNULLMaterialRefs If true, replace NULL material references with a reference to the GB-selected material. + * @param bShowProgressBar If true, display progress bar for complex brushes + * @return 0 if nothing happened, 1 if the operation was error-free, or 1+N if N CSG errors occurred. + */ + int ComposeBrushCSG( + ABrush* Actor, + UModel* Model, + uint32 PolyFlags, + EBrushType BrushType, + ECsgOper CSGOper, + bool bBuildBounds, + bool bMergePolys, + bool bReplaceNULLMaterialRefs, + bool bShowProgressBar, /*=true*/ + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + +protected: + // + // Status of filtered polygons: + // + enum EPolyNodeFilter + { + F_OUTSIDE = 0, // Leaf is an exterior leaf (visible to viewers). + F_INSIDE = 1, // Leaf is an interior leaf (non-visible, hidden behind backface). + F_COPLANAR_OUTSIDE = 2, // Poly is coplanar and in the exterior (visible to viewers). + F_COPLANAR_INSIDE = 3, // Poly is coplanar and inside (invisible to viewers). + F_COSPATIAL_FACING_IN = 4, // Poly is coplanar, cospatial, and facing in. + F_COSPATIAL_FACING_OUT = 5, // Poly is coplanar, cospatial, and facing out. + }; + + + // + // Information used by FilterEdPoly. + // + class FCoplanarInfo + { + public: + int32 iOriginalNode; + int32 iBackNode; + int BackNodeOutside; + int FrontLeafOutside; + int ProcessingBack; + }; + + // + // Generic filter function called by BspFilterEdPolys. A and B are pointers + // to any integers that your specific routine requires (or NULL if not needed). + // + typedef void (UHCsgUtils::*BspFilterFunc) + ( + UModel* Model, + int32 iNode, + FPoly* EdPoly, + EPolyNodeFilter Leaf, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // State shared between bspBrushCSG and AddWorldToBrushFunc. These are very + // tightly tied into the function AddWorldToBrush, not for general use. + // + int32 GDiscarded; // Number of polys discarded and not added. + int32 GNode; // Node AddBrushToWorld is adding to. + int32 GLastCoplanar; // Last coplanar beneath GNode at start of AddWorldToBrush. + int32 GNumNodes; // Number of Bsp nodes at start of AddWorldToBrush. + + UPROPERTY() + UModel* GModel; // Level map Model we're adding to. + + UPROPERTY() + class UModel* TempModel; + + //// Globals removed from FBspPointsGrid + //UPROPERTY() + //UHBspPointsGrid* GBspPoints; + + //UPROPERTY() + //UHBspPointsGrid* GBspVectors; + + /*struct BspFilterOp { + void Apply(UHCsgUtils* Obj, UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) {}; + };*/ + + // + // Handle a piece of a polygon that was filtered to a leaf. + // + void FilterLeaf( + BspFilterFunc FilterFunc, + UModel* Model, + int32 iNode, + FPoly* EdPoly, + FCoplanarInfo CoplanarInfo, + int32 LeafOutside, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // Function to filter an EdPoly through the Bsp, calling a callback + // function for all chunks that fall into leaves. + // + void FilterEdPoly + ( + BspFilterFunc FilterFunc, + UModel *Model, + int32 iNode, + FPoly *EdPoly, + FCoplanarInfo CoplanarInfo, + int32 Outside, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // Regular entry into FilterEdPoly (so higher-level callers don't have to + // deal with unnecessary info). Filters starting at root. + // + void BspFilterFPoly + ( + BspFilterFunc FilterFunc, + UModel *Model, + FPoly *EdPoly, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + + int bspNodeToFPoly + ( + UModel* Model, + int32 iNode, + FPoly* EdPoly + ); + + + //---------------------------------------------------------------------------- + // World Filtering + //---------------------------------------------------------------------------- + + // + // Filter all relevant world polys through the brush. + // + void FilterWorldThroughBrush + ( + UModel* Model, + UModel* Brush, + EBrushType BrushType, + ECsgOper CSGOper, + int32 iNode, + FSphere* BrushSphere, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + //---------------------------------------------------------------------------- + // CSG leaf filter callbacks / operations. + // --------------------------------------------------------------------------- + void AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + + //---------------------------------------------------------------------------- + // Forked various functions located in: EditorBsp.cpp, EditorCsg.cpp + //---------------------------------------------------------------------------- + static int TryToMerge( FPoly *Poly1, FPoly *Poly2 ); + static void MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ); + void bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ); + bool polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly); + static void CleanupNodes( UModel *Model, int32 iNode, int32 iParent ); + void bspCleanup( UModel *Model ); + +}; + diff --git a/Source/HoudiniEngine/Private/HoudiniApi.cpp b/Source/HoudiniEngine/Private/HoudiniApi.cpp index c7050344c..727e30bb4 100644 --- a/Source/HoudiniEngine/Private/HoudiniApi.cpp +++ b/Source/HoudiniEngine/Private/HoudiniApi.cpp @@ -1,3771 +1,3771 @@ -/* - * Copyright (c) <2020> 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. - * - * - * COMMENTS: - * This file is generated. Do not modify directly. - */ - -#include "HoudiniApi.h" -#include "HoudiniEnginePrivatePCH.h" - - -FHoudiniApi::AddAttributeFuncPtr -FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; - -FHoudiniApi::AddGroupFuncPtr -FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; - -FHoudiniApi::AssetInfo_CreateFuncPtr -FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; - -FHoudiniApi::AssetInfo_InitFuncPtr -FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; - -FHoudiniApi::AttributeInfo_CreateFuncPtr -FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; - -FHoudiniApi::AttributeInfo_InitFuncPtr -FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; - -FHoudiniApi::BindCustomImplementationFuncPtr -FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; - -FHoudiniApi::CancelPDGCookFuncPtr -FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; - -FHoudiniApi::CheckForSpecificErrorsFuncPtr -FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; - -FHoudiniApi::CleanupFuncPtr -FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; - -FHoudiniApi::ClearConnectionErrorFuncPtr -FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; - -FHoudiniApi::CloseSessionFuncPtr -FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; - -FHoudiniApi::CommitGeoFuncPtr -FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; - -FHoudiniApi::CommitWorkitemsFuncPtr -FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; - -FHoudiniApi::ComposeChildNodeListFuncPtr -FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; - -FHoudiniApi::ComposeNodeCookResultFuncPtr -FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; - -FHoudiniApi::ComposeObjectListFuncPtr -FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; - -FHoudiniApi::ConnectNodeInputFuncPtr -FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; - -FHoudiniApi::ConvertMatrixToEulerFuncPtr -FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; - -FHoudiniApi::ConvertMatrixToQuatFuncPtr -FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; - -FHoudiniApi::ConvertTransformFuncPtr -FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; - -FHoudiniApi::ConvertTransformEulerToMatrixFuncPtr -FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; - -FHoudiniApi::ConvertTransformQuatToMatrixFuncPtr -FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; - -FHoudiniApi::CookNodeFuncPtr -FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; - -FHoudiniApi::CookOptions_AreEqualFuncPtr -FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; - -FHoudiniApi::CookOptions_CreateFuncPtr -FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; - -FHoudiniApi::CookOptions_InitFuncPtr -FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; - -FHoudiniApi::CookPDGFuncPtr -FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; - -FHoudiniApi::CreateCustomSessionFuncPtr -FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; - -FHoudiniApi::CreateHeightFieldInputFuncPtr -FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; - -FHoudiniApi::CreateHeightfieldInputVolumeNodeFuncPtr -FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; - -FHoudiniApi::CreateInProcessSessionFuncPtr -FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; - -FHoudiniApi::CreateInputNodeFuncPtr -FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; - -FHoudiniApi::CreateNodeFuncPtr -FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; - -FHoudiniApi::CreateThriftNamedPipeSessionFuncPtr -FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; - -FHoudiniApi::CreateThriftSocketSessionFuncPtr -FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; - -FHoudiniApi::CreateWorkitemFuncPtr -FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; - -FHoudiniApi::CurveInfo_CreateFuncPtr -FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; - -FHoudiniApi::CurveInfo_InitFuncPtr -FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; - -FHoudiniApi::DeleteAttributeFuncPtr -FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; - -FHoudiniApi::DeleteGroupFuncPtr -FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; - -FHoudiniApi::DeleteNodeFuncPtr -FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; - -FHoudiniApi::DirtyPDGNodeFuncPtr -FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; - -FHoudiniApi::DisconnectNodeInputFuncPtr -FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; - -FHoudiniApi::DisconnectNodeOutputsAtFuncPtr -FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; - -FHoudiniApi::ExtractImageToFileFuncPtr -FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; - -FHoudiniApi::ExtractImageToMemoryFuncPtr -FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; - -FHoudiniApi::GeoInfo_CreateFuncPtr -FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; - -FHoudiniApi::GeoInfo_GetGroupCountByTypeFuncPtr -FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; - -FHoudiniApi::GeoInfo_InitFuncPtr -FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; - -FHoudiniApi::GetActiveCacheCountFuncPtr -FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; - -FHoudiniApi::GetActiveCacheNamesFuncPtr -FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmCountsFuncPtr -FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmInfosFuncPtr -FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmValuesFuncPtr -FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; - -FHoudiniApi::GetAssetInfoFuncPtr -FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; - -FHoudiniApi::GetAttributeFloat64ArrayDataFuncPtr -FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeFloat64DataFuncPtr -FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; - -FHoudiniApi::GetAttributeFloatArrayDataFuncPtr -FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; - -FHoudiniApi::GetAttributeFloatDataFuncPtr -FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; - -FHoudiniApi::GetAttributeInfoFuncPtr -FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; - -FHoudiniApi::GetAttributeInt64ArrayDataFuncPtr -FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeInt64DataFuncPtr -FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; - -FHoudiniApi::GetAttributeIntArrayDataFuncPtr -FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; - -FHoudiniApi::GetAttributeIntDataFuncPtr -FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; - -FHoudiniApi::GetAttributeNamesFuncPtr -FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; - -FHoudiniApi::GetAttributeStringArrayDataFuncPtr -FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; - -FHoudiniApi::GetAttributeStringDataFuncPtr -FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; - -FHoudiniApi::GetAvailableAssetCountFuncPtr -FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; - -FHoudiniApi::GetAvailableAssetsFuncPtr -FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; - -FHoudiniApi::GetBoxInfoFuncPtr -FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; - -FHoudiniApi::GetCachePropertyFuncPtr -FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; - -FHoudiniApi::GetComposedChildNodeListFuncPtr -FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; - -FHoudiniApi::GetComposedNodeCookResultFuncPtr -FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; - -FHoudiniApi::GetComposedObjectListFuncPtr -FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; - -FHoudiniApi::GetComposedObjectTransformsFuncPtr -FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; - -FHoudiniApi::GetConnectionErrorFuncPtr -FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; - -FHoudiniApi::GetConnectionErrorLengthFuncPtr -FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; - -FHoudiniApi::GetCookingCurrentCountFuncPtr -FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; - -FHoudiniApi::GetCookingTotalCountFuncPtr -FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; - -FHoudiniApi::GetCurveCountsFuncPtr -FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; - -FHoudiniApi::GetCurveInfoFuncPtr -FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; - -FHoudiniApi::GetCurveKnotsFuncPtr -FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; - -FHoudiniApi::GetCurveOrdersFuncPtr -FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; - -FHoudiniApi::GetDisplayGeoInfoFuncPtr -FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; - -FHoudiniApi::GetEnvIntFuncPtr -FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; - -FHoudiniApi::GetFaceCountsFuncPtr -FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; - -FHoudiniApi::GetFirstVolumeTileFuncPtr -FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; - -FHoudiniApi::GetGeoInfoFuncPtr -FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; - -FHoudiniApi::GetGeoSizeFuncPtr -FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; - -FHoudiniApi::GetGroupCountOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetGroupMembershipFuncPtr -FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; - -FHoudiniApi::GetGroupMembershipOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetGroupNamesFuncPtr -FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; - -FHoudiniApi::GetGroupNamesOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetHIPFileNodeCountFuncPtr -FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; - -FHoudiniApi::GetHIPFileNodeIdsFuncPtr -FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; - -FHoudiniApi::GetHandleBindingInfoFuncPtr -FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; - -FHoudiniApi::GetHandleInfoFuncPtr -FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; - -FHoudiniApi::GetHeightFieldDataFuncPtr -FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; - -FHoudiniApi::GetImageFilePathFuncPtr -FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; - -FHoudiniApi::GetImageInfoFuncPtr -FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; - -FHoudiniApi::GetImageMemoryBufferFuncPtr -FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; - -FHoudiniApi::GetImagePlaneCountFuncPtr -FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; - -FHoudiniApi::GetImagePlanesFuncPtr -FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; - -FHoudiniApi::GetInstanceTransformsOnPartFuncPtr -FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; - -FHoudiniApi::GetInstancedObjectIdsFuncPtr -FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; - -FHoudiniApi::GetInstancedPartIdsFuncPtr -FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; - -FHoudiniApi::GetInstancerPartTransformsFuncPtr -FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; - -FHoudiniApi::GetManagerNodeIdFuncPtr -FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; - -FHoudiniApi::GetMaterialInfoFuncPtr -FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; - -FHoudiniApi::GetMaterialNodeIdsOnFacesFuncPtr -FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; - -FHoudiniApi::GetNextVolumeTileFuncPtr -FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; - -FHoudiniApi::GetNodeInfoFuncPtr -FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; - -FHoudiniApi::GetNodeInputNameFuncPtr -FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; - -FHoudiniApi::GetNodeOutputNameFuncPtr -FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; - -FHoudiniApi::GetNodePathFuncPtr -FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; - -FHoudiniApi::GetNumWorkitemsFuncPtr -FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; - -FHoudiniApi::GetObjectInfoFuncPtr -FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; - -FHoudiniApi::GetObjectTransformFuncPtr -FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; - -FHoudiniApi::GetOutputNodeIdFuncPtr -FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; - -FHoudiniApi::GetPDGEventsFuncPtr -FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; - -FHoudiniApi::GetPDGGraphContextIdFuncPtr -FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; - -FHoudiniApi::GetPDGGraphContextsFuncPtr -FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; - -FHoudiniApi::GetPDGStateFuncPtr -FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; - -FHoudiniApi::GetParametersFuncPtr -FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; - -FHoudiniApi::GetParmChoiceListsFuncPtr -FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; - -FHoudiniApi::GetParmExpressionFuncPtr -FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; - -FHoudiniApi::GetParmFileFuncPtr -FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; - -FHoudiniApi::GetParmFloatValueFuncPtr -FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; - -FHoudiniApi::GetParmFloatValuesFuncPtr -FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; - -FHoudiniApi::GetParmIdFromNameFuncPtr -FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; - -FHoudiniApi::GetParmInfoFuncPtr -FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; - -FHoudiniApi::GetParmInfoFromNameFuncPtr -FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; - -FHoudiniApi::GetParmIntValueFuncPtr -FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; - -FHoudiniApi::GetParmIntValuesFuncPtr -FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; - -FHoudiniApi::GetParmNodeValueFuncPtr -FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; - -FHoudiniApi::GetParmStringValueFuncPtr -FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; - -FHoudiniApi::GetParmStringValuesFuncPtr -FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; - -FHoudiniApi::GetParmTagNameFuncPtr -FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; - -FHoudiniApi::GetParmTagValueFuncPtr -FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; - -FHoudiniApi::GetParmWithTagFuncPtr -FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; - -FHoudiniApi::GetPartInfoFuncPtr -FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; - -FHoudiniApi::GetPresetFuncPtr -FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; - -FHoudiniApi::GetPresetBufLengthFuncPtr -FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; - -FHoudiniApi::GetServerEnvIntFuncPtr -FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; - -FHoudiniApi::GetServerEnvStringFuncPtr -FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; - -FHoudiniApi::GetServerEnvVarCountFuncPtr -FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; - -FHoudiniApi::GetServerEnvVarListFuncPtr -FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; - -FHoudiniApi::GetSessionEnvIntFuncPtr -FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; - -FHoudiniApi::GetSessionSyncInfoFuncPtr -FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; - -FHoudiniApi::GetSphereInfoFuncPtr -FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; - -FHoudiniApi::GetStatusFuncPtr -FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; - -FHoudiniApi::GetStatusStringFuncPtr -FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; - -FHoudiniApi::GetStatusStringBufLengthFuncPtr -FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; - -FHoudiniApi::GetStringFuncPtr -FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; - -FHoudiniApi::GetStringBatchFuncPtr -FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; - -FHoudiniApi::GetStringBatchSizeFuncPtr -FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; - -FHoudiniApi::GetStringBufLengthFuncPtr -FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; - -FHoudiniApi::GetSupportedImageFileFormatCountFuncPtr -FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; - -FHoudiniApi::GetSupportedImageFileFormatsFuncPtr -FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; - -FHoudiniApi::GetTimeFuncPtr -FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; - -FHoudiniApi::GetTimelineOptionsFuncPtr -FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; - -FHoudiniApi::GetTotalCookCountFuncPtr -FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; - -FHoudiniApi::GetUseHoudiniTimeFuncPtr -FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; - -FHoudiniApi::GetVertexListFuncPtr -FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; - -FHoudiniApi::GetViewportFuncPtr -FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; - -FHoudiniApi::GetVolumeBoundsFuncPtr -FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; - -FHoudiniApi::GetVolumeInfoFuncPtr -FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; - -FHoudiniApi::GetVolumeTileFloatDataFuncPtr -FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; - -FHoudiniApi::GetVolumeTileIntDataFuncPtr -FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; - -FHoudiniApi::GetVolumeVisualInfoFuncPtr -FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; - -FHoudiniApi::GetVolumeVoxelFloatDataFuncPtr -FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; - -FHoudiniApi::GetVolumeVoxelIntDataFuncPtr -FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; - -FHoudiniApi::GetWorkitemDataLengthFuncPtr -FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; - -FHoudiniApi::GetWorkitemFloatDataFuncPtr -FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; - -FHoudiniApi::GetWorkitemInfoFuncPtr -FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; - -FHoudiniApi::GetWorkitemIntDataFuncPtr -FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; - -FHoudiniApi::GetWorkitemResultInfoFuncPtr -FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; - -FHoudiniApi::GetWorkitemStringDataFuncPtr -FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; - -FHoudiniApi::GetWorkitemsFuncPtr -FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; - -FHoudiniApi::HandleBindingInfo_CreateFuncPtr -FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; - -FHoudiniApi::HandleBindingInfo_InitFuncPtr -FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; - -FHoudiniApi::HandleInfo_CreateFuncPtr -FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; - -FHoudiniApi::HandleInfo_InitFuncPtr -FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; - -FHoudiniApi::ImageFileFormat_CreateFuncPtr -FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; - -FHoudiniApi::ImageFileFormat_InitFuncPtr -FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; - -FHoudiniApi::ImageInfo_CreateFuncPtr -FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; - -FHoudiniApi::ImageInfo_InitFuncPtr -FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; - -FHoudiniApi::InitializeFuncPtr -FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; - -FHoudiniApi::InsertMultiparmInstanceFuncPtr -FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; - -FHoudiniApi::InterruptFuncPtr -FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; - -FHoudiniApi::IsInitializedFuncPtr -FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; - -FHoudiniApi::IsNodeValidFuncPtr -FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; - -FHoudiniApi::IsSessionValidFuncPtr -FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; - -FHoudiniApi::Keyframe_CreateFuncPtr -FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; - -FHoudiniApi::Keyframe_InitFuncPtr -FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; - -FHoudiniApi::LoadAssetLibraryFromFileFuncPtr -FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; - -FHoudiniApi::LoadAssetLibraryFromMemoryFuncPtr -FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; - -FHoudiniApi::LoadGeoFromFileFuncPtr -FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; - -FHoudiniApi::LoadGeoFromMemoryFuncPtr -FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; - -FHoudiniApi::LoadHIPFileFuncPtr -FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; - -FHoudiniApi::LoadNodeFromFileFuncPtr -FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; - -FHoudiniApi::MaterialInfo_CreateFuncPtr -FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; - -FHoudiniApi::MaterialInfo_InitFuncPtr -FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; - -FHoudiniApi::MergeHIPFileFuncPtr -FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; - -FHoudiniApi::NodeInfo_CreateFuncPtr -FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; - -FHoudiniApi::NodeInfo_InitFuncPtr -FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; - -FHoudiniApi::ObjectInfo_CreateFuncPtr -FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; - -FHoudiniApi::ObjectInfo_InitFuncPtr -FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; - -FHoudiniApi::ParmChoiceInfo_CreateFuncPtr -FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; - -FHoudiniApi::ParmChoiceInfo_InitFuncPtr -FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; - -FHoudiniApi::ParmHasExpressionFuncPtr -FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; - -FHoudiniApi::ParmHasTagFuncPtr -FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; - -FHoudiniApi::ParmInfo_CreateFuncPtr -FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; - -FHoudiniApi::ParmInfo_GetFloatValueCountFuncPtr -FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; - -FHoudiniApi::ParmInfo_GetIntValueCountFuncPtr -FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; - -FHoudiniApi::ParmInfo_GetStringValueCountFuncPtr -FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; - -FHoudiniApi::ParmInfo_InitFuncPtr -FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; - -FHoudiniApi::ParmInfo_IsFloatFuncPtr -FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; - -FHoudiniApi::ParmInfo_IsIntFuncPtr -FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; - -FHoudiniApi::ParmInfo_IsNodeFuncPtr -FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; - -FHoudiniApi::ParmInfo_IsNonValueFuncPtr -FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; - -FHoudiniApi::ParmInfo_IsPathFuncPtr -FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; - -FHoudiniApi::ParmInfo_IsStringFuncPtr -FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; - -FHoudiniApi::PartInfo_CreateFuncPtr -FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; - -FHoudiniApi::PartInfo_GetAttributeCountByOwnerFuncPtr -FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; - -FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerFuncPtr -FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; - -FHoudiniApi::PartInfo_GetElementCountByGroupTypeFuncPtr -FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; - -FHoudiniApi::PartInfo_InitFuncPtr -FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; - -FHoudiniApi::PausePDGCookFuncPtr -FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; - -FHoudiniApi::PythonThreadInterpreterLockFuncPtr -FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; - -FHoudiniApi::QueryNodeInputFuncPtr -FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; - -FHoudiniApi::QueryNodeOutputConnectedCountFuncPtr -FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; - -FHoudiniApi::QueryNodeOutputConnectedNodesFuncPtr -FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; - -FHoudiniApi::RemoveCustomStringFuncPtr -FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; - -FHoudiniApi::RemoveMultiparmInstanceFuncPtr -FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; - -FHoudiniApi::RemoveParmExpressionFuncPtr -FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; - -FHoudiniApi::RenameNodeFuncPtr -FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; - -FHoudiniApi::RenderCOPToImageFuncPtr -FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; - -FHoudiniApi::RenderTextureToImageFuncPtr -FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; - -FHoudiniApi::ResetSimulationFuncPtr -FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; - -FHoudiniApi::RevertGeoFuncPtr -FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; - -FHoudiniApi::RevertParmToDefaultFuncPtr -FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; - -FHoudiniApi::RevertParmToDefaultsFuncPtr -FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; - -FHoudiniApi::SaveGeoToFileFuncPtr -FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; - -FHoudiniApi::SaveGeoToMemoryFuncPtr -FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; - -FHoudiniApi::SaveHIPFileFuncPtr -FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; - -FHoudiniApi::SaveNodeToFileFuncPtr -FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; - -FHoudiniApi::SessionSyncInfo_CreateFuncPtr -FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; - -FHoudiniApi::SetAnimCurveFuncPtr -FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; - -FHoudiniApi::SetAttributeFloat64DataFuncPtr -FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; - -FHoudiniApi::SetAttributeFloatDataFuncPtr -FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; - -FHoudiniApi::SetAttributeInt64DataFuncPtr -FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; - -FHoudiniApi::SetAttributeIntDataFuncPtr -FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; - -FHoudiniApi::SetAttributeStringDataFuncPtr -FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; - -FHoudiniApi::SetCachePropertyFuncPtr -FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; - -FHoudiniApi::SetCurveCountsFuncPtr -FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; - -FHoudiniApi::SetCurveInfoFuncPtr -FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; - -FHoudiniApi::SetCurveKnotsFuncPtr -FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; - -FHoudiniApi::SetCurveOrdersFuncPtr -FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; - -FHoudiniApi::SetCustomStringFuncPtr -FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; - -FHoudiniApi::SetFaceCountsFuncPtr -FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; - -FHoudiniApi::SetGroupMembershipFuncPtr -FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; - -FHoudiniApi::SetHeightFieldDataFuncPtr -FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; - -FHoudiniApi::SetImageInfoFuncPtr -FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; - -FHoudiniApi::SetNodeDisplayFuncPtr -FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; - -FHoudiniApi::SetObjectTransformFuncPtr -FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; - -FHoudiniApi::SetParmExpressionFuncPtr -FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; - -FHoudiniApi::SetParmFloatValueFuncPtr -FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; - -FHoudiniApi::SetParmFloatValuesFuncPtr -FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; - -FHoudiniApi::SetParmIntValueFuncPtr -FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; - -FHoudiniApi::SetParmIntValuesFuncPtr -FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; - -FHoudiniApi::SetParmNodeValueFuncPtr -FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; - -FHoudiniApi::SetParmStringValueFuncPtr -FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; - -FHoudiniApi::SetPartInfoFuncPtr -FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; - -FHoudiniApi::SetPresetFuncPtr -FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; - -FHoudiniApi::SetServerEnvIntFuncPtr -FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; - -FHoudiniApi::SetServerEnvStringFuncPtr -FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; - -FHoudiniApi::SetSessionSyncFuncPtr -FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; - -FHoudiniApi::SetSessionSyncInfoFuncPtr -FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; - -FHoudiniApi::SetTimeFuncPtr -FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; - -FHoudiniApi::SetTimelineOptionsFuncPtr -FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; - -FHoudiniApi::SetTransformAnimCurveFuncPtr -FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; - -FHoudiniApi::SetUseHoudiniTimeFuncPtr -FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; - -FHoudiniApi::SetVertexListFuncPtr -FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; - -FHoudiniApi::SetViewportFuncPtr -FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; - -FHoudiniApi::SetVolumeInfoFuncPtr -FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; - -FHoudiniApi::SetVolumeTileFloatDataFuncPtr -FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; - -FHoudiniApi::SetVolumeTileIntDataFuncPtr -FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; - -FHoudiniApi::SetVolumeVoxelFloatDataFuncPtr -FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; - -FHoudiniApi::SetVolumeVoxelIntDataFuncPtr -FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; - -FHoudiniApi::SetWorkitemFloatDataFuncPtr -FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; - -FHoudiniApi::SetWorkitemIntDataFuncPtr -FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; - -FHoudiniApi::SetWorkitemStringDataFuncPtr -FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; - -FHoudiniApi::StartThriftNamedPipeServerFuncPtr -FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; - -FHoudiniApi::StartThriftSocketServerFuncPtr -FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; - -FHoudiniApi::ThriftServerOptions_CreateFuncPtr -FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; - -FHoudiniApi::ThriftServerOptions_InitFuncPtr -FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; - -FHoudiniApi::TimelineOptions_CreateFuncPtr -FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; - -FHoudiniApi::TimelineOptions_InitFuncPtr -FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; - -FHoudiniApi::TransformEuler_CreateFuncPtr -FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; - -FHoudiniApi::TransformEuler_InitFuncPtr -FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; - -FHoudiniApi::Transform_CreateFuncPtr -FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; - -FHoudiniApi::Transform_InitFuncPtr -FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; - -FHoudiniApi::Viewport_CreateFuncPtr -FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; - -FHoudiniApi::VolumeInfo_CreateFuncPtr -FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; - -FHoudiniApi::VolumeInfo_InitFuncPtr -FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; - -FHoudiniApi::VolumeTileInfo_CreateFuncPtr -FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; - -FHoudiniApi::VolumeTileInfo_InitFuncPtr -FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; - - -void -FHoudiniApi::InitializeHAPI(void* LibraryHandle) -{ - if(!LibraryHandle) return; - - FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute")); - FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup")); - FHoudiniApi::AssetInfo_Create = (AssetInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Create")); - FHoudiniApi::AssetInfo_Init = (AssetInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Init")); - FHoudiniApi::AttributeInfo_Create = (AttributeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Create")); - FHoudiniApi::AttributeInfo_Init = (AttributeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Init")); - FHoudiniApi::BindCustomImplementation = (BindCustomImplementationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_BindCustomImplementation")); - FHoudiniApi::CancelPDGCook = (CancelPDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CancelPDGCook")); - FHoudiniApi::CheckForSpecificErrors = (CheckForSpecificErrorsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CheckForSpecificErrors")); - FHoudiniApi::Cleanup = (CleanupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Cleanup")); - FHoudiniApi::ClearConnectionError = (ClearConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ClearConnectionError")); - FHoudiniApi::CloseSession = (CloseSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CloseSession")); - FHoudiniApi::CommitGeo = (CommitGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitGeo")); - FHoudiniApi::CommitWorkitems = (CommitWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitWorkitems")); - FHoudiniApi::ComposeChildNodeList = (ComposeChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeChildNodeList")); - FHoudiniApi::ComposeNodeCookResult = (ComposeNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeNodeCookResult")); - FHoudiniApi::ComposeObjectList = (ComposeObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeObjectList")); - FHoudiniApi::ConnectNodeInput = (ConnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConnectNodeInput")); - FHoudiniApi::ConvertMatrixToEuler = (ConvertMatrixToEulerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToEuler")); - FHoudiniApi::ConvertMatrixToQuat = (ConvertMatrixToQuatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToQuat")); - FHoudiniApi::ConvertTransform = (ConvertTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransform")); - FHoudiniApi::ConvertTransformEulerToMatrix = (ConvertTransformEulerToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformEulerToMatrix")); - FHoudiniApi::ConvertTransformQuatToMatrix = (ConvertTransformQuatToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformQuatToMatrix")); - FHoudiniApi::CookNode = (CookNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookNode")); - FHoudiniApi::CookOptions_AreEqual = (CookOptions_AreEqualFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_AreEqual")); - FHoudiniApi::CookOptions_Create = (CookOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Create")); - FHoudiniApi::CookOptions_Init = (CookOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Init")); - FHoudiniApi::CookPDG = (CookPDGFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookPDG")); - FHoudiniApi::CreateCustomSession = (CreateCustomSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateCustomSession")); - FHoudiniApi::CreateHeightFieldInput = (CreateHeightFieldInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightFieldInput")); - FHoudiniApi::CreateHeightfieldInputVolumeNode = (CreateHeightfieldInputVolumeNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightfieldInputVolumeNode")); - FHoudiniApi::CreateInProcessSession = (CreateInProcessSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInProcessSession")); - FHoudiniApi::CreateInputNode = (CreateInputNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInputNode")); - FHoudiniApi::CreateNode = (CreateNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateNode")); - FHoudiniApi::CreateThriftNamedPipeSession = (CreateThriftNamedPipeSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftNamedPipeSession")); - FHoudiniApi::CreateThriftSocketSession = (CreateThriftSocketSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftSocketSession")); - FHoudiniApi::CreateWorkitem = (CreateWorkitemFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateWorkitem")); - FHoudiniApi::CurveInfo_Create = (CurveInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Create")); - FHoudiniApi::CurveInfo_Init = (CurveInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Init")); - FHoudiniApi::DeleteAttribute = (DeleteAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteAttribute")); - FHoudiniApi::DeleteGroup = (DeleteGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteGroup")); - FHoudiniApi::DeleteNode = (DeleteNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteNode")); - FHoudiniApi::DirtyPDGNode = (DirtyPDGNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DirtyPDGNode")); - FHoudiniApi::DisconnectNodeInput = (DisconnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeInput")); - FHoudiniApi::DisconnectNodeOutputsAt = (DisconnectNodeOutputsAtFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeOutputsAt")); - FHoudiniApi::ExtractImageToFile = (ExtractImageToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToFile")); - FHoudiniApi::ExtractImageToMemory = (ExtractImageToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToMemory")); - FHoudiniApi::GeoInfo_Create = (GeoInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Create")); - FHoudiniApi::GeoInfo_GetGroupCountByType = (GeoInfo_GetGroupCountByTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_GetGroupCountByType")); - FHoudiniApi::GeoInfo_Init = (GeoInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Init")); - FHoudiniApi::GetActiveCacheCount = (GetActiveCacheCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheCount")); - FHoudiniApi::GetActiveCacheNames = (GetActiveCacheNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheNames")); - FHoudiniApi::GetAssetDefinitionParmCounts = (GetAssetDefinitionParmCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmCounts")); - FHoudiniApi::GetAssetDefinitionParmInfos = (GetAssetDefinitionParmInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmInfos")); - FHoudiniApi::GetAssetDefinitionParmValues = (GetAssetDefinitionParmValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmValues")); - FHoudiniApi::GetAssetInfo = (GetAssetInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetInfo")); - FHoudiniApi::GetAttributeFloat64ArrayData = (GetAttributeFloat64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64ArrayData")); - FHoudiniApi::GetAttributeFloat64Data = (GetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64Data")); - FHoudiniApi::GetAttributeFloatArrayData = (GetAttributeFloatArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatArrayData")); - FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); - FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); - FHoudiniApi::GetAttributeInt64ArrayData = (GetAttributeInt64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64ArrayData")); - FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); - FHoudiniApi::GetAttributeIntArrayData = (GetAttributeIntArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntArrayData")); - FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); - FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); - FHoudiniApi::GetAttributeStringArrayData = (GetAttributeStringArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringArrayData")); - FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); - FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); - FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); - FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); - FHoudiniApi::GetCacheProperty = (GetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCacheProperty")); - FHoudiniApi::GetComposedChildNodeList = (GetComposedChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedChildNodeList")); - FHoudiniApi::GetComposedNodeCookResult = (GetComposedNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedNodeCookResult")); - FHoudiniApi::GetComposedObjectList = (GetComposedObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectList")); - FHoudiniApi::GetComposedObjectTransforms = (GetComposedObjectTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectTransforms")); - FHoudiniApi::GetConnectionError = (GetConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionError")); - FHoudiniApi::GetConnectionErrorLength = (GetConnectionErrorLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionErrorLength")); - FHoudiniApi::GetCookingCurrentCount = (GetCookingCurrentCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingCurrentCount")); - FHoudiniApi::GetCookingTotalCount = (GetCookingTotalCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingTotalCount")); - FHoudiniApi::GetCurveCounts = (GetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveCounts")); - FHoudiniApi::GetCurveInfo = (GetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveInfo")); - FHoudiniApi::GetCurveKnots = (GetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveKnots")); - FHoudiniApi::GetCurveOrders = (GetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveOrders")); - FHoudiniApi::GetDisplayGeoInfo = (GetDisplayGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetDisplayGeoInfo")); - FHoudiniApi::GetEnvInt = (GetEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEnvInt")); - FHoudiniApi::GetFaceCounts = (GetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFaceCounts")); - FHoudiniApi::GetFirstVolumeTile = (GetFirstVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFirstVolumeTile")); - FHoudiniApi::GetGeoInfo = (GetGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoInfo")); - FHoudiniApi::GetGeoSize = (GetGeoSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoSize")); - FHoudiniApi::GetGroupCountOnPackedInstancePart = (GetGroupCountOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupCountOnPackedInstancePart")); - FHoudiniApi::GetGroupMembership = (GetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembership")); - FHoudiniApi::GetGroupMembershipOnPackedInstancePart = (GetGroupMembershipOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembershipOnPackedInstancePart")); - FHoudiniApi::GetGroupNames = (GetGroupNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNames")); - FHoudiniApi::GetGroupNamesOnPackedInstancePart = (GetGroupNamesOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNamesOnPackedInstancePart")); - FHoudiniApi::GetHIPFileNodeCount = (GetHIPFileNodeCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeCount")); - FHoudiniApi::GetHIPFileNodeIds = (GetHIPFileNodeIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeIds")); - FHoudiniApi::GetHandleBindingInfo = (GetHandleBindingInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleBindingInfo")); - FHoudiniApi::GetHandleInfo = (GetHandleInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleInfo")); - FHoudiniApi::GetHeightFieldData = (GetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHeightFieldData")); - FHoudiniApi::GetImageFilePath = (GetImageFilePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageFilePath")); - FHoudiniApi::GetImageInfo = (GetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageInfo")); - FHoudiniApi::GetImageMemoryBuffer = (GetImageMemoryBufferFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageMemoryBuffer")); - FHoudiniApi::GetImagePlaneCount = (GetImagePlaneCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlaneCount")); - FHoudiniApi::GetImagePlanes = (GetImagePlanesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlanes")); - FHoudiniApi::GetInstanceTransformsOnPart = (GetInstanceTransformsOnPartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstanceTransformsOnPart")); - FHoudiniApi::GetInstancedObjectIds = (GetInstancedObjectIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedObjectIds")); - FHoudiniApi::GetInstancedPartIds = (GetInstancedPartIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedPartIds")); - FHoudiniApi::GetInstancerPartTransforms = (GetInstancerPartTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancerPartTransforms")); - FHoudiniApi::GetManagerNodeId = (GetManagerNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetManagerNodeId")); - FHoudiniApi::GetMaterialInfo = (GetMaterialInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialInfo")); - FHoudiniApi::GetMaterialNodeIdsOnFaces = (GetMaterialNodeIdsOnFacesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialNodeIdsOnFaces")); - FHoudiniApi::GetNextVolumeTile = (GetNextVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNextVolumeTile")); - FHoudiniApi::GetNodeInfo = (GetNodeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInfo")); - FHoudiniApi::GetNodeInputName = (GetNodeInputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInputName")); - FHoudiniApi::GetNodeOutputName = (GetNodeOutputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeOutputName")); - FHoudiniApi::GetNodePath = (GetNodePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodePath")); - FHoudiniApi::GetNumWorkitems = (GetNumWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNumWorkitems")); - FHoudiniApi::GetObjectInfo = (GetObjectInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectInfo")); - FHoudiniApi::GetObjectTransform = (GetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectTransform")); - FHoudiniApi::GetOutputNodeId = (GetOutputNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputNodeId")); - FHoudiniApi::GetPDGEvents = (GetPDGEventsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGEvents")); - FHoudiniApi::GetPDGGraphContextId = (GetPDGGraphContextIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContextId")); - FHoudiniApi::GetPDGGraphContexts = (GetPDGGraphContextsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContexts")); - FHoudiniApi::GetPDGState = (GetPDGStateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGState")); - FHoudiniApi::GetParameters = (GetParametersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParameters")); - FHoudiniApi::GetParmChoiceLists = (GetParmChoiceListsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmChoiceLists")); - FHoudiniApi::GetParmExpression = (GetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmExpression")); - FHoudiniApi::GetParmFile = (GetParmFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFile")); - FHoudiniApi::GetParmFloatValue = (GetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValue")); - FHoudiniApi::GetParmFloatValues = (GetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValues")); - FHoudiniApi::GetParmIdFromName = (GetParmIdFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIdFromName")); - FHoudiniApi::GetParmInfo = (GetParmInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfo")); - FHoudiniApi::GetParmInfoFromName = (GetParmInfoFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfoFromName")); - FHoudiniApi::GetParmIntValue = (GetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValue")); - FHoudiniApi::GetParmIntValues = (GetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValues")); - FHoudiniApi::GetParmNodeValue = (GetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmNodeValue")); - FHoudiniApi::GetParmStringValue = (GetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValue")); - FHoudiniApi::GetParmStringValues = (GetParmStringValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValues")); - FHoudiniApi::GetParmTagName = (GetParmTagNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagName")); - FHoudiniApi::GetParmTagValue = (GetParmTagValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagValue")); - FHoudiniApi::GetParmWithTag = (GetParmWithTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmWithTag")); - FHoudiniApi::GetPartInfo = (GetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPartInfo")); - FHoudiniApi::GetPreset = (GetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPreset")); - FHoudiniApi::GetPresetBufLength = (GetPresetBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPresetBufLength")); - FHoudiniApi::GetServerEnvInt = (GetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvInt")); - FHoudiniApi::GetServerEnvString = (GetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvString")); - FHoudiniApi::GetServerEnvVarCount = (GetServerEnvVarCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarCount")); - FHoudiniApi::GetServerEnvVarList = (GetServerEnvVarListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarList")); - FHoudiniApi::GetSessionEnvInt = (GetSessionEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionEnvInt")); - FHoudiniApi::GetSessionSyncInfo = (GetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionSyncInfo")); - FHoudiniApi::GetSphereInfo = (GetSphereInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSphereInfo")); - FHoudiniApi::GetStatus = (GetStatusFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatus")); - FHoudiniApi::GetStatusString = (GetStatusStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusString")); - FHoudiniApi::GetStatusStringBufLength = (GetStatusStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusStringBufLength")); - FHoudiniApi::GetString = (GetStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetString")); - FHoudiniApi::GetStringBatch = (GetStringBatchFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatch")); - FHoudiniApi::GetStringBatchSize = (GetStringBatchSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatchSize")); - FHoudiniApi::GetStringBufLength = (GetStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBufLength")); - FHoudiniApi::GetSupportedImageFileFormatCount = (GetSupportedImageFileFormatCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormatCount")); - FHoudiniApi::GetSupportedImageFileFormats = (GetSupportedImageFileFormatsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormats")); - FHoudiniApi::GetTime = (GetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTime")); - FHoudiniApi::GetTimelineOptions = (GetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTimelineOptions")); - FHoudiniApi::GetTotalCookCount = (GetTotalCookCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTotalCookCount")); - FHoudiniApi::GetUseHoudiniTime = (GetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetUseHoudiniTime")); - FHoudiniApi::GetVertexList = (GetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVertexList")); - FHoudiniApi::GetViewport = (GetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetViewport")); - FHoudiniApi::GetVolumeBounds = (GetVolumeBoundsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeBounds")); - FHoudiniApi::GetVolumeInfo = (GetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeInfo")); - FHoudiniApi::GetVolumeTileFloatData = (GetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileFloatData")); - FHoudiniApi::GetVolumeTileIntData = (GetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileIntData")); - FHoudiniApi::GetVolumeVisualInfo = (GetVolumeVisualInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVisualInfo")); - FHoudiniApi::GetVolumeVoxelFloatData = (GetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelFloatData")); - FHoudiniApi::GetVolumeVoxelIntData = (GetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelIntData")); - FHoudiniApi::GetWorkitemDataLength = (GetWorkitemDataLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemDataLength")); - FHoudiniApi::GetWorkitemFloatData = (GetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemFloatData")); - FHoudiniApi::GetWorkitemInfo = (GetWorkitemInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemInfo")); - FHoudiniApi::GetWorkitemIntData = (GetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemIntData")); - FHoudiniApi::GetWorkitemResultInfo = (GetWorkitemResultInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemResultInfo")); - FHoudiniApi::GetWorkitemStringData = (GetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemStringData")); - FHoudiniApi::GetWorkitems = (GetWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitems")); - FHoudiniApi::HandleBindingInfo_Create = (HandleBindingInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Create")); - FHoudiniApi::HandleBindingInfo_Init = (HandleBindingInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Init")); - FHoudiniApi::HandleInfo_Create = (HandleInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Create")); - FHoudiniApi::HandleInfo_Init = (HandleInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Init")); - FHoudiniApi::ImageFileFormat_Create = (ImageFileFormat_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Create")); - FHoudiniApi::ImageFileFormat_Init = (ImageFileFormat_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Init")); - FHoudiniApi::ImageInfo_Create = (ImageInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Create")); - FHoudiniApi::ImageInfo_Init = (ImageInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Init")); - FHoudiniApi::Initialize = (InitializeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Initialize")); - FHoudiniApi::InsertMultiparmInstance = (InsertMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_InsertMultiparmInstance")); - FHoudiniApi::Interrupt = (InterruptFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Interrupt")); - FHoudiniApi::IsInitialized = (IsInitializedFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsInitialized")); - FHoudiniApi::IsNodeValid = (IsNodeValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsNodeValid")); - FHoudiniApi::IsSessionValid = (IsSessionValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsSessionValid")); - FHoudiniApi::Keyframe_Create = (Keyframe_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Create")); - FHoudiniApi::Keyframe_Init = (Keyframe_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Init")); - FHoudiniApi::LoadAssetLibraryFromFile = (LoadAssetLibraryFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromFile")); - FHoudiniApi::LoadAssetLibraryFromMemory = (LoadAssetLibraryFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromMemory")); - FHoudiniApi::LoadGeoFromFile = (LoadGeoFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromFile")); - FHoudiniApi::LoadGeoFromMemory = (LoadGeoFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromMemory")); - FHoudiniApi::LoadHIPFile = (LoadHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadHIPFile")); - FHoudiniApi::LoadNodeFromFile = (LoadNodeFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadNodeFromFile")); - FHoudiniApi::MaterialInfo_Create = (MaterialInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Create")); - FHoudiniApi::MaterialInfo_Init = (MaterialInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Init")); - FHoudiniApi::MergeHIPFile = (MergeHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MergeHIPFile")); - FHoudiniApi::NodeInfo_Create = (NodeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Create")); - FHoudiniApi::NodeInfo_Init = (NodeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Init")); - FHoudiniApi::ObjectInfo_Create = (ObjectInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Create")); - FHoudiniApi::ObjectInfo_Init = (ObjectInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Init")); - FHoudiniApi::ParmChoiceInfo_Create = (ParmChoiceInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Create")); - FHoudiniApi::ParmChoiceInfo_Init = (ParmChoiceInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Init")); - FHoudiniApi::ParmHasExpression = (ParmHasExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasExpression")); - FHoudiniApi::ParmHasTag = (ParmHasTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasTag")); - FHoudiniApi::ParmInfo_Create = (ParmInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Create")); - FHoudiniApi::ParmInfo_GetFloatValueCount = (ParmInfo_GetFloatValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetFloatValueCount")); - FHoudiniApi::ParmInfo_GetIntValueCount = (ParmInfo_GetIntValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetIntValueCount")); - FHoudiniApi::ParmInfo_GetStringValueCount = (ParmInfo_GetStringValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetStringValueCount")); - FHoudiniApi::ParmInfo_Init = (ParmInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Init")); - FHoudiniApi::ParmInfo_IsFloat = (ParmInfo_IsFloatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsFloat")); - FHoudiniApi::ParmInfo_IsInt = (ParmInfo_IsIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsInt")); - FHoudiniApi::ParmInfo_IsNode = (ParmInfo_IsNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNode")); - FHoudiniApi::ParmInfo_IsNonValue = (ParmInfo_IsNonValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNonValue")); - FHoudiniApi::ParmInfo_IsPath = (ParmInfo_IsPathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsPath")); - FHoudiniApi::ParmInfo_IsString = (ParmInfo_IsStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsString")); - FHoudiniApi::PartInfo_Create = (PartInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Create")); - FHoudiniApi::PartInfo_GetAttributeCountByOwner = (PartInfo_GetAttributeCountByOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetAttributeCountByOwner")); - FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = (PartInfo_GetElementCountByAttributeOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByAttributeOwner")); - FHoudiniApi::PartInfo_GetElementCountByGroupType = (PartInfo_GetElementCountByGroupTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByGroupType")); - FHoudiniApi::PartInfo_Init = (PartInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Init")); - FHoudiniApi::PausePDGCook = (PausePDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PausePDGCook")); - FHoudiniApi::PythonThreadInterpreterLock = (PythonThreadInterpreterLockFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PythonThreadInterpreterLock")); - FHoudiniApi::QueryNodeInput = (QueryNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeInput")); - FHoudiniApi::QueryNodeOutputConnectedCount = (QueryNodeOutputConnectedCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedCount")); - FHoudiniApi::QueryNodeOutputConnectedNodes = (QueryNodeOutputConnectedNodesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedNodes")); - FHoudiniApi::RemoveCustomString = (RemoveCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveCustomString")); - FHoudiniApi::RemoveMultiparmInstance = (RemoveMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveMultiparmInstance")); - FHoudiniApi::RemoveParmExpression = (RemoveParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveParmExpression")); - FHoudiniApi::RenameNode = (RenameNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenameNode")); - FHoudiniApi::RenderCOPToImage = (RenderCOPToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderCOPToImage")); - FHoudiniApi::RenderTextureToImage = (RenderTextureToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderTextureToImage")); - FHoudiniApi::ResetSimulation = (ResetSimulationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ResetSimulation")); - FHoudiniApi::RevertGeo = (RevertGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertGeo")); - FHoudiniApi::RevertParmToDefault = (RevertParmToDefaultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefault")); - FHoudiniApi::RevertParmToDefaults = (RevertParmToDefaultsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefaults")); - FHoudiniApi::SaveGeoToFile = (SaveGeoToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToFile")); - FHoudiniApi::SaveGeoToMemory = (SaveGeoToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToMemory")); - FHoudiniApi::SaveHIPFile = (SaveHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveHIPFile")); - FHoudiniApi::SaveNodeToFile = (SaveNodeToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveNodeToFile")); - FHoudiniApi::SessionSyncInfo_Create = (SessionSyncInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SessionSyncInfo_Create")); - FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); - FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); - FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); - FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); - FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); - FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); - FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); - FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); - FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); - FHoudiniApi::SetCurveKnots = (SetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveKnots")); - FHoudiniApi::SetCurveOrders = (SetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveOrders")); - FHoudiniApi::SetCustomString = (SetCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCustomString")); - FHoudiniApi::SetFaceCounts = (SetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetFaceCounts")); - FHoudiniApi::SetGroupMembership = (SetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetGroupMembership")); - FHoudiniApi::SetHeightFieldData = (SetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetHeightFieldData")); - FHoudiniApi::SetImageInfo = (SetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetImageInfo")); - FHoudiniApi::SetNodeDisplay = (SetNodeDisplayFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetNodeDisplay")); - FHoudiniApi::SetObjectTransform = (SetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetObjectTransform")); - FHoudiniApi::SetParmExpression = (SetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmExpression")); - FHoudiniApi::SetParmFloatValue = (SetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValue")); - FHoudiniApi::SetParmFloatValues = (SetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValues")); - FHoudiniApi::SetParmIntValue = (SetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValue")); - FHoudiniApi::SetParmIntValues = (SetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValues")); - FHoudiniApi::SetParmNodeValue = (SetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmNodeValue")); - FHoudiniApi::SetParmStringValue = (SetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmStringValue")); - FHoudiniApi::SetPartInfo = (SetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPartInfo")); - FHoudiniApi::SetPreset = (SetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPreset")); - FHoudiniApi::SetServerEnvInt = (SetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvInt")); - FHoudiniApi::SetServerEnvString = (SetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvString")); - FHoudiniApi::SetSessionSync = (SetSessionSyncFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSync")); - FHoudiniApi::SetSessionSyncInfo = (SetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSyncInfo")); - FHoudiniApi::SetTime = (SetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTime")); - FHoudiniApi::SetTimelineOptions = (SetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTimelineOptions")); - FHoudiniApi::SetTransformAnimCurve = (SetTransformAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTransformAnimCurve")); - FHoudiniApi::SetUseHoudiniTime = (SetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetUseHoudiniTime")); - FHoudiniApi::SetVertexList = (SetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVertexList")); - FHoudiniApi::SetViewport = (SetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetViewport")); - FHoudiniApi::SetVolumeInfo = (SetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeInfo")); - FHoudiniApi::SetVolumeTileFloatData = (SetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileFloatData")); - FHoudiniApi::SetVolumeTileIntData = (SetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileIntData")); - FHoudiniApi::SetVolumeVoxelFloatData = (SetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelFloatData")); - FHoudiniApi::SetVolumeVoxelIntData = (SetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelIntData")); - FHoudiniApi::SetWorkitemFloatData = (SetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemFloatData")); - FHoudiniApi::SetWorkitemIntData = (SetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemIntData")); - FHoudiniApi::SetWorkitemStringData = (SetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemStringData")); - FHoudiniApi::StartThriftNamedPipeServer = (StartThriftNamedPipeServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftNamedPipeServer")); - FHoudiniApi::StartThriftSocketServer = (StartThriftSocketServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftSocketServer")); - FHoudiniApi::ThriftServerOptions_Create = (ThriftServerOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Create")); - FHoudiniApi::ThriftServerOptions_Init = (ThriftServerOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Init")); - FHoudiniApi::TimelineOptions_Create = (TimelineOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Create")); - FHoudiniApi::TimelineOptions_Init = (TimelineOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Init")); - FHoudiniApi::TransformEuler_Create = (TransformEuler_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Create")); - FHoudiniApi::TransformEuler_Init = (TransformEuler_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Init")); - FHoudiniApi::Transform_Create = (Transform_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Create")); - FHoudiniApi::Transform_Init = (Transform_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Init")); - FHoudiniApi::Viewport_Create = (Viewport_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Viewport_Create")); - FHoudiniApi::VolumeInfo_Create = (VolumeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Create")); - FHoudiniApi::VolumeInfo_Init = (VolumeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Init")); - FHoudiniApi::VolumeTileInfo_Create = (VolumeTileInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Create")); - FHoudiniApi::VolumeTileInfo_Init = (VolumeTileInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Init")); -} - - -void -FHoudiniApi::FinalizeHAPI() -{ - FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; - FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; - FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; - FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; - FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; - FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; - FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; - FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; - FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; - FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; - FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; - FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; - FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; - FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; - FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; - FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; - FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; - FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; - FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; - FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; - FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; - FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; - FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; - FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; - FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; - FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; - FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; - FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; - FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; - FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; - FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; - FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; - FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; - FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; - FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; - FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; - FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; - FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; - FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; - FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; - FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; - FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; - FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; - FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; - FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; - FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; - FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; - FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; - FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; - FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; - FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; - FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; - FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; - FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; - FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; - FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; - FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; - FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; - FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; - FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; - FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; - FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; - FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; - FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; - FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; - FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; - FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; - FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; - FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; - FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; - FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; - FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; - FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; - FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; - FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; - FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; - FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; - FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; - FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; - FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; - FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; - FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; - FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; - FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; - FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; - FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; - FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; - FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; - FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; - FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; - FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; - FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; - FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; - FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; - FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; - FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; - FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; - FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; - FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; - FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; - FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; - FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; - FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; - FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; - FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; - FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; - FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; - FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; - FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; - FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; - FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; - FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; - FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; - FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; - FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; - FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; - FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; - FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; - FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; - FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; - FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; - FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; - FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; - FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; - FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; - FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; - FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; - FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; - FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; - FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; - FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; - FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; - FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; - FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; - FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; - FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; - FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; - FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; - FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; - FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; - FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; - FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; - FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; - FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; - FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; - FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; - FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; - FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; - FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; - FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; - FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; - FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; - FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; - FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; - FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; - FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; - FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; - FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; - FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; - FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; - FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; - FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; - FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; - FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; - FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; - FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; - FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; - FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; - FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; - FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; - FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; - FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; - FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; - FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; - FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; - FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; - FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; - FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; - FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; - FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; - FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; - FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; - FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; - FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; - FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; - FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; - FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; - FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; - FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; - FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; - FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; - FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; - FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; - FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; - FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; - FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; - FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; - FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; - FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; - FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; - FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; - FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; - FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; - FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; - FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; - FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; - FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; - FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; - FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; - FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; - FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; - FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; - FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; - FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; - FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; - FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; - FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; - FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; - FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; - FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; - FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; - FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; - FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; - FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; - FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; - FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; - FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; - FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; - FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; - FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; - FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; - FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; - FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; - FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; - FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; - FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; - FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; - FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; - FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; - FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; - FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; - FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; - FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; - FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; - FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; - FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; - FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; - FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; - FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; - FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; - FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; - FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; - FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; - FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; - FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; - FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; - FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; - FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; - FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; - FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; - FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; - FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; - FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; - FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; - FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; - FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; - FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; - FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; - FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; - FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; - FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; - FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; - FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; - FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; - FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; - FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; - FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; - FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; - FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; - FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; - FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; - FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; - FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; - FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; - FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; - FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; - FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; - FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; - FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; - FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; - FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; - FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; - FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; - FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; - FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; - FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; - FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; - FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; - FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; - FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; - FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; - FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; - FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; - FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; - FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; - FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; - FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; - FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; - FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; - FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; -} - - -bool -FHoudiniApi::IsHAPIInitialized() -{ - return ( FHoudiniApi::IsInitialized != &FHoudiniApi::IsInitializedEmptyStub ); -} - - -HAPI_Result -FHoudiniApi::AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_AssetInfo -FHoudiniApi::AssetInfo_CreateEmptyStub() -{ - return HAPI_AssetInfo(); -} - - -void -FHoudiniApi::AssetInfo_InitEmptyStub(HAPI_AssetInfo * in) -{ - return; -} - - -HAPI_AttributeInfo -FHoudiniApi::AttributeInfo_CreateEmptyStub() -{ - return HAPI_AttributeInfo(); -} - - -void -FHoudiniApi::AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CleanupEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ClearConnectionErrorEmptyStub() -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CloseSessionEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Bool -FHoudiniApi::CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right) -{ - return HAPI_Bool(); -} - - -HAPI_CookOptions -FHoudiniApi::CookOptions_CreateEmptyStub() -{ - return HAPI_CookOptions(); -} - - -void -FHoudiniApi::CookOptions_InitEmptyStub(HAPI_CookOptions * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateInProcessSessionEmptyStub(HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_CurveInfo -FHoudiniApi::CurveInfo_CreateEmptyStub() -{ - return HAPI_CurveInfo(); -} - - -void -FHoudiniApi::CurveInfo_InitEmptyStub(HAPI_CurveInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_GeoInfo -FHoudiniApi::GeoInfo_CreateEmptyStub() -{ - return HAPI_GeoInfo(); -} - - -int -FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type) -{ - return -1; -} - - -void -FHoudiniApi::GeoInfo_InitEmptyStub(HAPI_GeoInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetConnectionErrorLengthEmptyStub(int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTimeEmptyStub(const HAPI_Session * session, float * time) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_HandleBindingInfo -FHoudiniApi::HandleBindingInfo_CreateEmptyStub() -{ - return HAPI_HandleBindingInfo(); -} - - -void -FHoudiniApi::HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in) -{ - return; -} - - -HAPI_HandleInfo -FHoudiniApi::HandleInfo_CreateEmptyStub() -{ - return HAPI_HandleInfo(); -} - - -void -FHoudiniApi::HandleInfo_InitEmptyStub(HAPI_HandleInfo * in) -{ - return; -} - - -HAPI_ImageFileFormat -FHoudiniApi::ImageFileFormat_CreateEmptyStub() -{ - return HAPI_ImageFileFormat(); -} - - -void -FHoudiniApi::ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in) -{ - return; -} - - -HAPI_ImageInfo -FHoudiniApi::ImageInfo_CreateEmptyStub() -{ - return HAPI_ImageInfo(); -} - - -void -FHoudiniApi::ImageInfo_InitEmptyStub(HAPI_ImageInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::InterruptEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsInitializedEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsSessionValidEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Keyframe -FHoudiniApi::Keyframe_CreateEmptyStub() -{ - return HAPI_Keyframe(); -} - - -void -FHoudiniApi::Keyframe_InitEmptyStub(HAPI_Keyframe * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_MaterialInfo -FHoudiniApi::MaterialInfo_CreateEmptyStub() -{ - return HAPI_MaterialInfo(); -} - - -void -FHoudiniApi::MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_NodeInfo -FHoudiniApi::NodeInfo_CreateEmptyStub() -{ - return HAPI_NodeInfo(); -} - - -void -FHoudiniApi::NodeInfo_InitEmptyStub(HAPI_NodeInfo * in) -{ - return; -} - - -HAPI_ObjectInfo -FHoudiniApi::ObjectInfo_CreateEmptyStub() -{ - return HAPI_ObjectInfo(); -} - - -void -FHoudiniApi::ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in) -{ - return; -} - - -HAPI_ParmChoiceInfo -FHoudiniApi::ParmChoiceInfo_CreateEmptyStub() -{ - return HAPI_ParmChoiceInfo(); -} - - -void -FHoudiniApi::ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_ParmInfo -FHoudiniApi::ParmInfo_CreateEmptyStub() -{ - return HAPI_ParmInfo(); -} - - -int -FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in) -{ - return -1; -} - - -int -FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in) -{ - return -1; -} - - -int -FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in) -{ - return -1; -} - - -void -FHoudiniApi::ParmInfo_InitEmptyStub(HAPI_ParmInfo * in) -{ - return; -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_PartInfo -FHoudiniApi::PartInfo_CreateEmptyStub() -{ - return HAPI_PartInfo(); -} - - -int -FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) -{ - return -1; -} - - -int -FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) -{ - return -1; -} - - -int -FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type) -{ - return -1; -} - - -void -FHoudiniApi::PartInfo_InitEmptyStub(HAPI_PartInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_SessionSyncInfo -FHoudiniApi::SessionSyncInfo_CreateEmptyStub() -{ - return HAPI_SessionSyncInfo(); -} - - -HAPI_Result -FHoudiniApi::SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTimeEmptyStub(const HAPI_Session * session, float time) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_ThriftServerOptions -FHoudiniApi::ThriftServerOptions_CreateEmptyStub() -{ - return HAPI_ThriftServerOptions(); -} - - -void -FHoudiniApi::ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in) -{ - return; -} - - -HAPI_TimelineOptions -FHoudiniApi::TimelineOptions_CreateEmptyStub() -{ - return HAPI_TimelineOptions(); -} - - -void -FHoudiniApi::TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in) -{ - return; -} - - -HAPI_TransformEuler -FHoudiniApi::TransformEuler_CreateEmptyStub() -{ - return HAPI_TransformEuler(); -} - - -void -FHoudiniApi::TransformEuler_InitEmptyStub(HAPI_TransformEuler * in) -{ - return; -} - - -HAPI_Transform -FHoudiniApi::Transform_CreateEmptyStub() -{ - return HAPI_Transform(); -} - - -void -FHoudiniApi::Transform_InitEmptyStub(HAPI_Transform * in) -{ - return; -} - - -HAPI_Viewport -FHoudiniApi::Viewport_CreateEmptyStub() -{ - return HAPI_Viewport(); -} - - -HAPI_VolumeInfo -FHoudiniApi::VolumeInfo_CreateEmptyStub() -{ - return HAPI_VolumeInfo(); -} - - -void -FHoudiniApi::VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in) -{ - return; -} - - -HAPI_VolumeTileInfo -FHoudiniApi::VolumeTileInfo_CreateEmptyStub() -{ - return HAPI_VolumeTileInfo(); -} - - -void -FHoudiniApi::VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in) -{ - return; -} - - +/* + * Copyright (c) <2020> 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. + * + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +#include "HoudiniApi.h" +#include "HoudiniEnginePrivatePCH.h" + + +FHoudiniApi::AddAttributeFuncPtr +FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; + +FHoudiniApi::AddGroupFuncPtr +FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; + +FHoudiniApi::AssetInfo_CreateFuncPtr +FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; + +FHoudiniApi::AssetInfo_InitFuncPtr +FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; + +FHoudiniApi::AttributeInfo_CreateFuncPtr +FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; + +FHoudiniApi::AttributeInfo_InitFuncPtr +FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; + +FHoudiniApi::BindCustomImplementationFuncPtr +FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; + +FHoudiniApi::CancelPDGCookFuncPtr +FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; + +FHoudiniApi::CheckForSpecificErrorsFuncPtr +FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; + +FHoudiniApi::CleanupFuncPtr +FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; + +FHoudiniApi::ClearConnectionErrorFuncPtr +FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; + +FHoudiniApi::CloseSessionFuncPtr +FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; + +FHoudiniApi::CommitGeoFuncPtr +FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; + +FHoudiniApi::CommitWorkitemsFuncPtr +FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; + +FHoudiniApi::ComposeChildNodeListFuncPtr +FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; + +FHoudiniApi::ComposeNodeCookResultFuncPtr +FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; + +FHoudiniApi::ComposeObjectListFuncPtr +FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + +FHoudiniApi::ConnectNodeInputFuncPtr +FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; + +FHoudiniApi::ConvertMatrixToEulerFuncPtr +FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; + +FHoudiniApi::ConvertMatrixToQuatFuncPtr +FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; + +FHoudiniApi::ConvertTransformFuncPtr +FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; + +FHoudiniApi::ConvertTransformEulerToMatrixFuncPtr +FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; + +FHoudiniApi::ConvertTransformQuatToMatrixFuncPtr +FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; + +FHoudiniApi::CookNodeFuncPtr +FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; + +FHoudiniApi::CookOptions_AreEqualFuncPtr +FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; + +FHoudiniApi::CookOptions_CreateFuncPtr +FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; + +FHoudiniApi::CookOptions_InitFuncPtr +FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; + +FHoudiniApi::CookPDGFuncPtr +FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; + +FHoudiniApi::CreateCustomSessionFuncPtr +FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; + +FHoudiniApi::CreateHeightFieldInputFuncPtr +FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; + +FHoudiniApi::CreateHeightfieldInputVolumeNodeFuncPtr +FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; + +FHoudiniApi::CreateInProcessSessionFuncPtr +FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; + +FHoudiniApi::CreateInputNodeFuncPtr +FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; + +FHoudiniApi::CreateNodeFuncPtr +FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; + +FHoudiniApi::CreateThriftNamedPipeSessionFuncPtr +FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; + +FHoudiniApi::CreateThriftSocketSessionFuncPtr +FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; + +FHoudiniApi::CreateWorkitemFuncPtr +FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; + +FHoudiniApi::CurveInfo_CreateFuncPtr +FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; + +FHoudiniApi::CurveInfo_InitFuncPtr +FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; + +FHoudiniApi::DeleteAttributeFuncPtr +FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; + +FHoudiniApi::DeleteGroupFuncPtr +FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; + +FHoudiniApi::DeleteNodeFuncPtr +FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; + +FHoudiniApi::DirtyPDGNodeFuncPtr +FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; + +FHoudiniApi::DisconnectNodeInputFuncPtr +FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; + +FHoudiniApi::DisconnectNodeOutputsAtFuncPtr +FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; + +FHoudiniApi::ExtractImageToFileFuncPtr +FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; + +FHoudiniApi::ExtractImageToMemoryFuncPtr +FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; + +FHoudiniApi::GeoInfo_CreateFuncPtr +FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; + +FHoudiniApi::GeoInfo_GetGroupCountByTypeFuncPtr +FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; + +FHoudiniApi::GeoInfo_InitFuncPtr +FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; + +FHoudiniApi::GetActiveCacheCountFuncPtr +FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; + +FHoudiniApi::GetActiveCacheNamesFuncPtr +FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmCountsFuncPtr +FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmInfosFuncPtr +FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmValuesFuncPtr +FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; + +FHoudiniApi::GetAssetInfoFuncPtr +FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; + +FHoudiniApi::GetAttributeFloat64ArrayDataFuncPtr +FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeFloat64DataFuncPtr +FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; + +FHoudiniApi::GetAttributeFloatArrayDataFuncPtr +FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; + +FHoudiniApi::GetAttributeFloatDataFuncPtr +FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; + +FHoudiniApi::GetAttributeInfoFuncPtr +FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + +FHoudiniApi::GetAttributeInt64ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt64DataFuncPtr +FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + +FHoudiniApi::GetAttributeIntArrayDataFuncPtr +FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; + +FHoudiniApi::GetAttributeIntDataFuncPtr +FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; + +FHoudiniApi::GetAttributeNamesFuncPtr +FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; + +FHoudiniApi::GetAttributeStringArrayDataFuncPtr +FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; + +FHoudiniApi::GetAttributeStringDataFuncPtr +FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + +FHoudiniApi::GetAvailableAssetCountFuncPtr +FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; + +FHoudiniApi::GetAvailableAssetsFuncPtr +FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; + +FHoudiniApi::GetBoxInfoFuncPtr +FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; + +FHoudiniApi::GetCachePropertyFuncPtr +FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; + +FHoudiniApi::GetComposedChildNodeListFuncPtr +FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; + +FHoudiniApi::GetComposedNodeCookResultFuncPtr +FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; + +FHoudiniApi::GetComposedObjectListFuncPtr +FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; + +FHoudiniApi::GetComposedObjectTransformsFuncPtr +FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + +FHoudiniApi::GetConnectionErrorFuncPtr +FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; + +FHoudiniApi::GetConnectionErrorLengthFuncPtr +FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; + +FHoudiniApi::GetCookingCurrentCountFuncPtr +FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; + +FHoudiniApi::GetCookingTotalCountFuncPtr +FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; + +FHoudiniApi::GetCurveCountsFuncPtr +FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; + +FHoudiniApi::GetCurveInfoFuncPtr +FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; + +FHoudiniApi::GetCurveKnotsFuncPtr +FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; + +FHoudiniApi::GetCurveOrdersFuncPtr +FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; + +FHoudiniApi::GetDisplayGeoInfoFuncPtr +FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + +FHoudiniApi::GetEnvIntFuncPtr +FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; + +FHoudiniApi::GetFaceCountsFuncPtr +FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; + +FHoudiniApi::GetFirstVolumeTileFuncPtr +FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; + +FHoudiniApi::GetGeoInfoFuncPtr +FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; + +FHoudiniApi::GetGeoSizeFuncPtr +FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; + +FHoudiniApi::GetGroupCountOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetGroupMembershipFuncPtr +FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; + +FHoudiniApi::GetGroupMembershipOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetGroupNamesFuncPtr +FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; + +FHoudiniApi::GetGroupNamesOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetHIPFileNodeCountFuncPtr +FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; + +FHoudiniApi::GetHIPFileNodeIdsFuncPtr +FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; + +FHoudiniApi::GetHandleBindingInfoFuncPtr +FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; + +FHoudiniApi::GetHandleInfoFuncPtr +FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; + +FHoudiniApi::GetHeightFieldDataFuncPtr +FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; + +FHoudiniApi::GetImageFilePathFuncPtr +FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; + +FHoudiniApi::GetImageInfoFuncPtr +FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; + +FHoudiniApi::GetImageMemoryBufferFuncPtr +FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; + +FHoudiniApi::GetImagePlaneCountFuncPtr +FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; + +FHoudiniApi::GetImagePlanesFuncPtr +FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; + +FHoudiniApi::GetInstanceTransformsOnPartFuncPtr +FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; + +FHoudiniApi::GetInstancedObjectIdsFuncPtr +FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; + +FHoudiniApi::GetInstancedPartIdsFuncPtr +FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; + +FHoudiniApi::GetInstancerPartTransformsFuncPtr +FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; + +FHoudiniApi::GetManagerNodeIdFuncPtr +FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; + +FHoudiniApi::GetMaterialInfoFuncPtr +FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; + +FHoudiniApi::GetMaterialNodeIdsOnFacesFuncPtr +FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; + +FHoudiniApi::GetNextVolumeTileFuncPtr +FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; + +FHoudiniApi::GetNodeInfoFuncPtr +FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; + +FHoudiniApi::GetNodeInputNameFuncPtr +FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; + +FHoudiniApi::GetNodeOutputNameFuncPtr +FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; + +FHoudiniApi::GetNodePathFuncPtr +FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; + +FHoudiniApi::GetNumWorkitemsFuncPtr +FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; + +FHoudiniApi::GetObjectInfoFuncPtr +FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; + +FHoudiniApi::GetObjectTransformFuncPtr +FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + +FHoudiniApi::GetOutputNodeIdFuncPtr +FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; + +FHoudiniApi::GetPDGEventsFuncPtr +FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; + +FHoudiniApi::GetPDGGraphContextIdFuncPtr +FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; + +FHoudiniApi::GetPDGGraphContextsFuncPtr +FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; + +FHoudiniApi::GetPDGStateFuncPtr +FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; + +FHoudiniApi::GetParametersFuncPtr +FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; + +FHoudiniApi::GetParmChoiceListsFuncPtr +FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; + +FHoudiniApi::GetParmExpressionFuncPtr +FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; + +FHoudiniApi::GetParmFileFuncPtr +FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; + +FHoudiniApi::GetParmFloatValueFuncPtr +FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; + +FHoudiniApi::GetParmFloatValuesFuncPtr +FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; + +FHoudiniApi::GetParmIdFromNameFuncPtr +FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; + +FHoudiniApi::GetParmInfoFuncPtr +FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; + +FHoudiniApi::GetParmInfoFromNameFuncPtr +FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; + +FHoudiniApi::GetParmIntValueFuncPtr +FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; + +FHoudiniApi::GetParmIntValuesFuncPtr +FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; + +FHoudiniApi::GetParmNodeValueFuncPtr +FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; + +FHoudiniApi::GetParmStringValueFuncPtr +FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; + +FHoudiniApi::GetParmStringValuesFuncPtr +FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; + +FHoudiniApi::GetParmTagNameFuncPtr +FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; + +FHoudiniApi::GetParmTagValueFuncPtr +FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; + +FHoudiniApi::GetParmWithTagFuncPtr +FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; + +FHoudiniApi::GetPartInfoFuncPtr +FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; + +FHoudiniApi::GetPresetFuncPtr +FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; + +FHoudiniApi::GetPresetBufLengthFuncPtr +FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; + +FHoudiniApi::GetServerEnvIntFuncPtr +FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; + +FHoudiniApi::GetServerEnvStringFuncPtr +FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; + +FHoudiniApi::GetServerEnvVarCountFuncPtr +FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; + +FHoudiniApi::GetServerEnvVarListFuncPtr +FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; + +FHoudiniApi::GetSessionEnvIntFuncPtr +FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; + +FHoudiniApi::GetSessionSyncInfoFuncPtr +FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; + +FHoudiniApi::GetSphereInfoFuncPtr +FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; + +FHoudiniApi::GetStatusFuncPtr +FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; + +FHoudiniApi::GetStatusStringFuncPtr +FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; + +FHoudiniApi::GetStatusStringBufLengthFuncPtr +FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; + +FHoudiniApi::GetStringFuncPtr +FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; + +FHoudiniApi::GetStringBatchFuncPtr +FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; + +FHoudiniApi::GetStringBatchSizeFuncPtr +FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; + +FHoudiniApi::GetStringBufLengthFuncPtr +FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; + +FHoudiniApi::GetSupportedImageFileFormatCountFuncPtr +FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; + +FHoudiniApi::GetSupportedImageFileFormatsFuncPtr +FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; + +FHoudiniApi::GetTimeFuncPtr +FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; + +FHoudiniApi::GetTimelineOptionsFuncPtr +FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; + +FHoudiniApi::GetTotalCookCountFuncPtr +FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; + +FHoudiniApi::GetUseHoudiniTimeFuncPtr +FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; + +FHoudiniApi::GetVertexListFuncPtr +FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; + +FHoudiniApi::GetViewportFuncPtr +FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; + +FHoudiniApi::GetVolumeBoundsFuncPtr +FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; + +FHoudiniApi::GetVolumeInfoFuncPtr +FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; + +FHoudiniApi::GetVolumeTileFloatDataFuncPtr +FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; + +FHoudiniApi::GetVolumeTileIntDataFuncPtr +FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; + +FHoudiniApi::GetVolumeVisualInfoFuncPtr +FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; + +FHoudiniApi::GetVolumeVoxelFloatDataFuncPtr +FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; + +FHoudiniApi::GetVolumeVoxelIntDataFuncPtr +FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; + +FHoudiniApi::GetWorkitemDataLengthFuncPtr +FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; + +FHoudiniApi::GetWorkitemFloatDataFuncPtr +FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; + +FHoudiniApi::GetWorkitemInfoFuncPtr +FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; + +FHoudiniApi::GetWorkitemIntDataFuncPtr +FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; + +FHoudiniApi::GetWorkitemResultInfoFuncPtr +FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; + +FHoudiniApi::GetWorkitemStringDataFuncPtr +FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; + +FHoudiniApi::GetWorkitemsFuncPtr +FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; + +FHoudiniApi::HandleBindingInfo_CreateFuncPtr +FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; + +FHoudiniApi::HandleBindingInfo_InitFuncPtr +FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; + +FHoudiniApi::HandleInfo_CreateFuncPtr +FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; + +FHoudiniApi::HandleInfo_InitFuncPtr +FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; + +FHoudiniApi::ImageFileFormat_CreateFuncPtr +FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; + +FHoudiniApi::ImageFileFormat_InitFuncPtr +FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; + +FHoudiniApi::ImageInfo_CreateFuncPtr +FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; + +FHoudiniApi::ImageInfo_InitFuncPtr +FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; + +FHoudiniApi::InitializeFuncPtr +FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; + +FHoudiniApi::InsertMultiparmInstanceFuncPtr +FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; + +FHoudiniApi::InterruptFuncPtr +FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; + +FHoudiniApi::IsInitializedFuncPtr +FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; + +FHoudiniApi::IsNodeValidFuncPtr +FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; + +FHoudiniApi::IsSessionValidFuncPtr +FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; + +FHoudiniApi::Keyframe_CreateFuncPtr +FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; + +FHoudiniApi::Keyframe_InitFuncPtr +FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; + +FHoudiniApi::LoadAssetLibraryFromFileFuncPtr +FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; + +FHoudiniApi::LoadAssetLibraryFromMemoryFuncPtr +FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; + +FHoudiniApi::LoadGeoFromFileFuncPtr +FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; + +FHoudiniApi::LoadGeoFromMemoryFuncPtr +FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; + +FHoudiniApi::LoadHIPFileFuncPtr +FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; + +FHoudiniApi::LoadNodeFromFileFuncPtr +FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; + +FHoudiniApi::MaterialInfo_CreateFuncPtr +FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; + +FHoudiniApi::MaterialInfo_InitFuncPtr +FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; + +FHoudiniApi::MergeHIPFileFuncPtr +FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; + +FHoudiniApi::NodeInfo_CreateFuncPtr +FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; + +FHoudiniApi::NodeInfo_InitFuncPtr +FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; + +FHoudiniApi::ObjectInfo_CreateFuncPtr +FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; + +FHoudiniApi::ObjectInfo_InitFuncPtr +FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; + +FHoudiniApi::ParmChoiceInfo_CreateFuncPtr +FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; + +FHoudiniApi::ParmChoiceInfo_InitFuncPtr +FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; + +FHoudiniApi::ParmHasExpressionFuncPtr +FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; + +FHoudiniApi::ParmHasTagFuncPtr +FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; + +FHoudiniApi::ParmInfo_CreateFuncPtr +FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; + +FHoudiniApi::ParmInfo_GetFloatValueCountFuncPtr +FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; + +FHoudiniApi::ParmInfo_GetIntValueCountFuncPtr +FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; + +FHoudiniApi::ParmInfo_GetStringValueCountFuncPtr +FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; + +FHoudiniApi::ParmInfo_InitFuncPtr +FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; + +FHoudiniApi::ParmInfo_IsFloatFuncPtr +FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; + +FHoudiniApi::ParmInfo_IsIntFuncPtr +FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; + +FHoudiniApi::ParmInfo_IsNodeFuncPtr +FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; + +FHoudiniApi::ParmInfo_IsNonValueFuncPtr +FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; + +FHoudiniApi::ParmInfo_IsPathFuncPtr +FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; + +FHoudiniApi::ParmInfo_IsStringFuncPtr +FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; + +FHoudiniApi::PartInfo_CreateFuncPtr +FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; + +FHoudiniApi::PartInfo_GetAttributeCountByOwnerFuncPtr +FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; + +FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerFuncPtr +FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; + +FHoudiniApi::PartInfo_GetElementCountByGroupTypeFuncPtr +FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; + +FHoudiniApi::PartInfo_InitFuncPtr +FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; + +FHoudiniApi::PausePDGCookFuncPtr +FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; + +FHoudiniApi::PythonThreadInterpreterLockFuncPtr +FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; + +FHoudiniApi::QueryNodeInputFuncPtr +FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; + +FHoudiniApi::QueryNodeOutputConnectedCountFuncPtr +FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; + +FHoudiniApi::QueryNodeOutputConnectedNodesFuncPtr +FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; + +FHoudiniApi::RemoveCustomStringFuncPtr +FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; + +FHoudiniApi::RemoveMultiparmInstanceFuncPtr +FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; + +FHoudiniApi::RemoveParmExpressionFuncPtr +FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; + +FHoudiniApi::RenameNodeFuncPtr +FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; + +FHoudiniApi::RenderCOPToImageFuncPtr +FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; + +FHoudiniApi::RenderTextureToImageFuncPtr +FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; + +FHoudiniApi::ResetSimulationFuncPtr +FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; + +FHoudiniApi::RevertGeoFuncPtr +FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; + +FHoudiniApi::RevertParmToDefaultFuncPtr +FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; + +FHoudiniApi::RevertParmToDefaultsFuncPtr +FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; + +FHoudiniApi::SaveGeoToFileFuncPtr +FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; + +FHoudiniApi::SaveGeoToMemoryFuncPtr +FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; + +FHoudiniApi::SaveHIPFileFuncPtr +FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; + +FHoudiniApi::SaveNodeToFileFuncPtr +FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; + +FHoudiniApi::SessionSyncInfo_CreateFuncPtr +FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; + +FHoudiniApi::SetAnimCurveFuncPtr +FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; + +FHoudiniApi::SetAttributeFloat64DataFuncPtr +FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; + +FHoudiniApi::SetAttributeFloatDataFuncPtr +FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + +FHoudiniApi::SetAttributeInt64DataFuncPtr +FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + +FHoudiniApi::SetAttributeIntDataFuncPtr +FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; + +FHoudiniApi::SetAttributeStringDataFuncPtr +FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + +FHoudiniApi::SetCachePropertyFuncPtr +FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + +FHoudiniApi::SetCurveCountsFuncPtr +FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; + +FHoudiniApi::SetCurveInfoFuncPtr +FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; + +FHoudiniApi::SetCurveKnotsFuncPtr +FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; + +FHoudiniApi::SetCurveOrdersFuncPtr +FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; + +FHoudiniApi::SetCustomStringFuncPtr +FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; + +FHoudiniApi::SetFaceCountsFuncPtr +FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; + +FHoudiniApi::SetGroupMembershipFuncPtr +FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; + +FHoudiniApi::SetHeightFieldDataFuncPtr +FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; + +FHoudiniApi::SetImageInfoFuncPtr +FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; + +FHoudiniApi::SetNodeDisplayFuncPtr +FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; + +FHoudiniApi::SetObjectTransformFuncPtr +FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; + +FHoudiniApi::SetParmExpressionFuncPtr +FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; + +FHoudiniApi::SetParmFloatValueFuncPtr +FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; + +FHoudiniApi::SetParmFloatValuesFuncPtr +FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; + +FHoudiniApi::SetParmIntValueFuncPtr +FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; + +FHoudiniApi::SetParmIntValuesFuncPtr +FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; + +FHoudiniApi::SetParmNodeValueFuncPtr +FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; + +FHoudiniApi::SetParmStringValueFuncPtr +FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; + +FHoudiniApi::SetPartInfoFuncPtr +FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; + +FHoudiniApi::SetPresetFuncPtr +FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; + +FHoudiniApi::SetServerEnvIntFuncPtr +FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; + +FHoudiniApi::SetServerEnvStringFuncPtr +FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; + +FHoudiniApi::SetSessionSyncFuncPtr +FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; + +FHoudiniApi::SetSessionSyncInfoFuncPtr +FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; + +FHoudiniApi::SetTimeFuncPtr +FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; + +FHoudiniApi::SetTimelineOptionsFuncPtr +FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; + +FHoudiniApi::SetTransformAnimCurveFuncPtr +FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; + +FHoudiniApi::SetUseHoudiniTimeFuncPtr +FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; + +FHoudiniApi::SetVertexListFuncPtr +FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; + +FHoudiniApi::SetViewportFuncPtr +FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; + +FHoudiniApi::SetVolumeInfoFuncPtr +FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; + +FHoudiniApi::SetVolumeTileFloatDataFuncPtr +FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; + +FHoudiniApi::SetVolumeTileIntDataFuncPtr +FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; + +FHoudiniApi::SetVolumeVoxelFloatDataFuncPtr +FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; + +FHoudiniApi::SetVolumeVoxelIntDataFuncPtr +FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; + +FHoudiniApi::SetWorkitemFloatDataFuncPtr +FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; + +FHoudiniApi::SetWorkitemIntDataFuncPtr +FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; + +FHoudiniApi::SetWorkitemStringDataFuncPtr +FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; + +FHoudiniApi::StartThriftNamedPipeServerFuncPtr +FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; + +FHoudiniApi::StartThriftSocketServerFuncPtr +FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; + +FHoudiniApi::ThriftServerOptions_CreateFuncPtr +FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; + +FHoudiniApi::ThriftServerOptions_InitFuncPtr +FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; + +FHoudiniApi::TimelineOptions_CreateFuncPtr +FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; + +FHoudiniApi::TimelineOptions_InitFuncPtr +FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; + +FHoudiniApi::TransformEuler_CreateFuncPtr +FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; + +FHoudiniApi::TransformEuler_InitFuncPtr +FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; + +FHoudiniApi::Transform_CreateFuncPtr +FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; + +FHoudiniApi::Transform_InitFuncPtr +FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; + +FHoudiniApi::Viewport_CreateFuncPtr +FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; + +FHoudiniApi::VolumeInfo_CreateFuncPtr +FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; + +FHoudiniApi::VolumeInfo_InitFuncPtr +FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; + +FHoudiniApi::VolumeTileInfo_CreateFuncPtr +FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; + +FHoudiniApi::VolumeTileInfo_InitFuncPtr +FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; + + +void +FHoudiniApi::InitializeHAPI(void* LibraryHandle) +{ + if(!LibraryHandle) return; + + FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute")); + FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup")); + FHoudiniApi::AssetInfo_Create = (AssetInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Create")); + FHoudiniApi::AssetInfo_Init = (AssetInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Init")); + FHoudiniApi::AttributeInfo_Create = (AttributeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Create")); + FHoudiniApi::AttributeInfo_Init = (AttributeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Init")); + FHoudiniApi::BindCustomImplementation = (BindCustomImplementationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_BindCustomImplementation")); + FHoudiniApi::CancelPDGCook = (CancelPDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CancelPDGCook")); + FHoudiniApi::CheckForSpecificErrors = (CheckForSpecificErrorsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CheckForSpecificErrors")); + FHoudiniApi::Cleanup = (CleanupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Cleanup")); + FHoudiniApi::ClearConnectionError = (ClearConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ClearConnectionError")); + FHoudiniApi::CloseSession = (CloseSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CloseSession")); + FHoudiniApi::CommitGeo = (CommitGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitGeo")); + FHoudiniApi::CommitWorkitems = (CommitWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitWorkitems")); + FHoudiniApi::ComposeChildNodeList = (ComposeChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeChildNodeList")); + FHoudiniApi::ComposeNodeCookResult = (ComposeNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeNodeCookResult")); + FHoudiniApi::ComposeObjectList = (ComposeObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeObjectList")); + FHoudiniApi::ConnectNodeInput = (ConnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConnectNodeInput")); + FHoudiniApi::ConvertMatrixToEuler = (ConvertMatrixToEulerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToEuler")); + FHoudiniApi::ConvertMatrixToQuat = (ConvertMatrixToQuatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToQuat")); + FHoudiniApi::ConvertTransform = (ConvertTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransform")); + FHoudiniApi::ConvertTransformEulerToMatrix = (ConvertTransformEulerToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformEulerToMatrix")); + FHoudiniApi::ConvertTransformQuatToMatrix = (ConvertTransformQuatToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformQuatToMatrix")); + FHoudiniApi::CookNode = (CookNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookNode")); + FHoudiniApi::CookOptions_AreEqual = (CookOptions_AreEqualFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_AreEqual")); + FHoudiniApi::CookOptions_Create = (CookOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Create")); + FHoudiniApi::CookOptions_Init = (CookOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Init")); + FHoudiniApi::CookPDG = (CookPDGFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookPDG")); + FHoudiniApi::CreateCustomSession = (CreateCustomSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateCustomSession")); + FHoudiniApi::CreateHeightFieldInput = (CreateHeightFieldInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightFieldInput")); + FHoudiniApi::CreateHeightfieldInputVolumeNode = (CreateHeightfieldInputVolumeNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightfieldInputVolumeNode")); + FHoudiniApi::CreateInProcessSession = (CreateInProcessSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInProcessSession")); + FHoudiniApi::CreateInputNode = (CreateInputNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInputNode")); + FHoudiniApi::CreateNode = (CreateNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateNode")); + FHoudiniApi::CreateThriftNamedPipeSession = (CreateThriftNamedPipeSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftNamedPipeSession")); + FHoudiniApi::CreateThriftSocketSession = (CreateThriftSocketSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftSocketSession")); + FHoudiniApi::CreateWorkitem = (CreateWorkitemFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateWorkitem")); + FHoudiniApi::CurveInfo_Create = (CurveInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Create")); + FHoudiniApi::CurveInfo_Init = (CurveInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Init")); + FHoudiniApi::DeleteAttribute = (DeleteAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteAttribute")); + FHoudiniApi::DeleteGroup = (DeleteGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteGroup")); + FHoudiniApi::DeleteNode = (DeleteNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteNode")); + FHoudiniApi::DirtyPDGNode = (DirtyPDGNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DirtyPDGNode")); + FHoudiniApi::DisconnectNodeInput = (DisconnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeInput")); + FHoudiniApi::DisconnectNodeOutputsAt = (DisconnectNodeOutputsAtFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeOutputsAt")); + FHoudiniApi::ExtractImageToFile = (ExtractImageToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToFile")); + FHoudiniApi::ExtractImageToMemory = (ExtractImageToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToMemory")); + FHoudiniApi::GeoInfo_Create = (GeoInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Create")); + FHoudiniApi::GeoInfo_GetGroupCountByType = (GeoInfo_GetGroupCountByTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_GetGroupCountByType")); + FHoudiniApi::GeoInfo_Init = (GeoInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Init")); + FHoudiniApi::GetActiveCacheCount = (GetActiveCacheCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheCount")); + FHoudiniApi::GetActiveCacheNames = (GetActiveCacheNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheNames")); + FHoudiniApi::GetAssetDefinitionParmCounts = (GetAssetDefinitionParmCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmCounts")); + FHoudiniApi::GetAssetDefinitionParmInfos = (GetAssetDefinitionParmInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmInfos")); + FHoudiniApi::GetAssetDefinitionParmValues = (GetAssetDefinitionParmValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmValues")); + FHoudiniApi::GetAssetInfo = (GetAssetInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetInfo")); + FHoudiniApi::GetAttributeFloat64ArrayData = (GetAttributeFloat64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64ArrayData")); + FHoudiniApi::GetAttributeFloat64Data = (GetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64Data")); + FHoudiniApi::GetAttributeFloatArrayData = (GetAttributeFloatArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatArrayData")); + FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); + FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); + FHoudiniApi::GetAttributeInt64ArrayData = (GetAttributeInt64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64ArrayData")); + FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); + FHoudiniApi::GetAttributeIntArrayData = (GetAttributeIntArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntArrayData")); + FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); + FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); + FHoudiniApi::GetAttributeStringArrayData = (GetAttributeStringArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringArrayData")); + FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); + FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); + FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); + FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); + FHoudiniApi::GetCacheProperty = (GetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCacheProperty")); + FHoudiniApi::GetComposedChildNodeList = (GetComposedChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedChildNodeList")); + FHoudiniApi::GetComposedNodeCookResult = (GetComposedNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedNodeCookResult")); + FHoudiniApi::GetComposedObjectList = (GetComposedObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectList")); + FHoudiniApi::GetComposedObjectTransforms = (GetComposedObjectTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectTransforms")); + FHoudiniApi::GetConnectionError = (GetConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionError")); + FHoudiniApi::GetConnectionErrorLength = (GetConnectionErrorLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionErrorLength")); + FHoudiniApi::GetCookingCurrentCount = (GetCookingCurrentCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingCurrentCount")); + FHoudiniApi::GetCookingTotalCount = (GetCookingTotalCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingTotalCount")); + FHoudiniApi::GetCurveCounts = (GetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveCounts")); + FHoudiniApi::GetCurveInfo = (GetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveInfo")); + FHoudiniApi::GetCurveKnots = (GetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveKnots")); + FHoudiniApi::GetCurveOrders = (GetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveOrders")); + FHoudiniApi::GetDisplayGeoInfo = (GetDisplayGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetDisplayGeoInfo")); + FHoudiniApi::GetEnvInt = (GetEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEnvInt")); + FHoudiniApi::GetFaceCounts = (GetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFaceCounts")); + FHoudiniApi::GetFirstVolumeTile = (GetFirstVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFirstVolumeTile")); + FHoudiniApi::GetGeoInfo = (GetGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoInfo")); + FHoudiniApi::GetGeoSize = (GetGeoSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoSize")); + FHoudiniApi::GetGroupCountOnPackedInstancePart = (GetGroupCountOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupCountOnPackedInstancePart")); + FHoudiniApi::GetGroupMembership = (GetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembership")); + FHoudiniApi::GetGroupMembershipOnPackedInstancePart = (GetGroupMembershipOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembershipOnPackedInstancePart")); + FHoudiniApi::GetGroupNames = (GetGroupNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNames")); + FHoudiniApi::GetGroupNamesOnPackedInstancePart = (GetGroupNamesOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNamesOnPackedInstancePart")); + FHoudiniApi::GetHIPFileNodeCount = (GetHIPFileNodeCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeCount")); + FHoudiniApi::GetHIPFileNodeIds = (GetHIPFileNodeIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeIds")); + FHoudiniApi::GetHandleBindingInfo = (GetHandleBindingInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleBindingInfo")); + FHoudiniApi::GetHandleInfo = (GetHandleInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleInfo")); + FHoudiniApi::GetHeightFieldData = (GetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHeightFieldData")); + FHoudiniApi::GetImageFilePath = (GetImageFilePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageFilePath")); + FHoudiniApi::GetImageInfo = (GetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageInfo")); + FHoudiniApi::GetImageMemoryBuffer = (GetImageMemoryBufferFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageMemoryBuffer")); + FHoudiniApi::GetImagePlaneCount = (GetImagePlaneCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlaneCount")); + FHoudiniApi::GetImagePlanes = (GetImagePlanesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlanes")); + FHoudiniApi::GetInstanceTransformsOnPart = (GetInstanceTransformsOnPartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstanceTransformsOnPart")); + FHoudiniApi::GetInstancedObjectIds = (GetInstancedObjectIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedObjectIds")); + FHoudiniApi::GetInstancedPartIds = (GetInstancedPartIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedPartIds")); + FHoudiniApi::GetInstancerPartTransforms = (GetInstancerPartTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancerPartTransforms")); + FHoudiniApi::GetManagerNodeId = (GetManagerNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetManagerNodeId")); + FHoudiniApi::GetMaterialInfo = (GetMaterialInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialInfo")); + FHoudiniApi::GetMaterialNodeIdsOnFaces = (GetMaterialNodeIdsOnFacesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialNodeIdsOnFaces")); + FHoudiniApi::GetNextVolumeTile = (GetNextVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNextVolumeTile")); + FHoudiniApi::GetNodeInfo = (GetNodeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInfo")); + FHoudiniApi::GetNodeInputName = (GetNodeInputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInputName")); + FHoudiniApi::GetNodeOutputName = (GetNodeOutputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeOutputName")); + FHoudiniApi::GetNodePath = (GetNodePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodePath")); + FHoudiniApi::GetNumWorkitems = (GetNumWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNumWorkitems")); + FHoudiniApi::GetObjectInfo = (GetObjectInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectInfo")); + FHoudiniApi::GetObjectTransform = (GetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectTransform")); + FHoudiniApi::GetOutputNodeId = (GetOutputNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputNodeId")); + FHoudiniApi::GetPDGEvents = (GetPDGEventsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGEvents")); + FHoudiniApi::GetPDGGraphContextId = (GetPDGGraphContextIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContextId")); + FHoudiniApi::GetPDGGraphContexts = (GetPDGGraphContextsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContexts")); + FHoudiniApi::GetPDGState = (GetPDGStateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGState")); + FHoudiniApi::GetParameters = (GetParametersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParameters")); + FHoudiniApi::GetParmChoiceLists = (GetParmChoiceListsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmChoiceLists")); + FHoudiniApi::GetParmExpression = (GetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmExpression")); + FHoudiniApi::GetParmFile = (GetParmFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFile")); + FHoudiniApi::GetParmFloatValue = (GetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValue")); + FHoudiniApi::GetParmFloatValues = (GetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValues")); + FHoudiniApi::GetParmIdFromName = (GetParmIdFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIdFromName")); + FHoudiniApi::GetParmInfo = (GetParmInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfo")); + FHoudiniApi::GetParmInfoFromName = (GetParmInfoFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfoFromName")); + FHoudiniApi::GetParmIntValue = (GetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValue")); + FHoudiniApi::GetParmIntValues = (GetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValues")); + FHoudiniApi::GetParmNodeValue = (GetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmNodeValue")); + FHoudiniApi::GetParmStringValue = (GetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValue")); + FHoudiniApi::GetParmStringValues = (GetParmStringValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValues")); + FHoudiniApi::GetParmTagName = (GetParmTagNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagName")); + FHoudiniApi::GetParmTagValue = (GetParmTagValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagValue")); + FHoudiniApi::GetParmWithTag = (GetParmWithTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmWithTag")); + FHoudiniApi::GetPartInfo = (GetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPartInfo")); + FHoudiniApi::GetPreset = (GetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPreset")); + FHoudiniApi::GetPresetBufLength = (GetPresetBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPresetBufLength")); + FHoudiniApi::GetServerEnvInt = (GetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvInt")); + FHoudiniApi::GetServerEnvString = (GetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvString")); + FHoudiniApi::GetServerEnvVarCount = (GetServerEnvVarCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarCount")); + FHoudiniApi::GetServerEnvVarList = (GetServerEnvVarListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarList")); + FHoudiniApi::GetSessionEnvInt = (GetSessionEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionEnvInt")); + FHoudiniApi::GetSessionSyncInfo = (GetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionSyncInfo")); + FHoudiniApi::GetSphereInfo = (GetSphereInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSphereInfo")); + FHoudiniApi::GetStatus = (GetStatusFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatus")); + FHoudiniApi::GetStatusString = (GetStatusStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusString")); + FHoudiniApi::GetStatusStringBufLength = (GetStatusStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusStringBufLength")); + FHoudiniApi::GetString = (GetStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetString")); + FHoudiniApi::GetStringBatch = (GetStringBatchFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatch")); + FHoudiniApi::GetStringBatchSize = (GetStringBatchSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatchSize")); + FHoudiniApi::GetStringBufLength = (GetStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBufLength")); + FHoudiniApi::GetSupportedImageFileFormatCount = (GetSupportedImageFileFormatCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormatCount")); + FHoudiniApi::GetSupportedImageFileFormats = (GetSupportedImageFileFormatsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormats")); + FHoudiniApi::GetTime = (GetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTime")); + FHoudiniApi::GetTimelineOptions = (GetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTimelineOptions")); + FHoudiniApi::GetTotalCookCount = (GetTotalCookCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTotalCookCount")); + FHoudiniApi::GetUseHoudiniTime = (GetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetUseHoudiniTime")); + FHoudiniApi::GetVertexList = (GetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVertexList")); + FHoudiniApi::GetViewport = (GetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetViewport")); + FHoudiniApi::GetVolumeBounds = (GetVolumeBoundsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeBounds")); + FHoudiniApi::GetVolumeInfo = (GetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeInfo")); + FHoudiniApi::GetVolumeTileFloatData = (GetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileFloatData")); + FHoudiniApi::GetVolumeTileIntData = (GetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileIntData")); + FHoudiniApi::GetVolumeVisualInfo = (GetVolumeVisualInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVisualInfo")); + FHoudiniApi::GetVolumeVoxelFloatData = (GetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelFloatData")); + FHoudiniApi::GetVolumeVoxelIntData = (GetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelIntData")); + FHoudiniApi::GetWorkitemDataLength = (GetWorkitemDataLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemDataLength")); + FHoudiniApi::GetWorkitemFloatData = (GetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemFloatData")); + FHoudiniApi::GetWorkitemInfo = (GetWorkitemInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemInfo")); + FHoudiniApi::GetWorkitemIntData = (GetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemIntData")); + FHoudiniApi::GetWorkitemResultInfo = (GetWorkitemResultInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemResultInfo")); + FHoudiniApi::GetWorkitemStringData = (GetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemStringData")); + FHoudiniApi::GetWorkitems = (GetWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitems")); + FHoudiniApi::HandleBindingInfo_Create = (HandleBindingInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Create")); + FHoudiniApi::HandleBindingInfo_Init = (HandleBindingInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Init")); + FHoudiniApi::HandleInfo_Create = (HandleInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Create")); + FHoudiniApi::HandleInfo_Init = (HandleInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Init")); + FHoudiniApi::ImageFileFormat_Create = (ImageFileFormat_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Create")); + FHoudiniApi::ImageFileFormat_Init = (ImageFileFormat_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Init")); + FHoudiniApi::ImageInfo_Create = (ImageInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Create")); + FHoudiniApi::ImageInfo_Init = (ImageInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Init")); + FHoudiniApi::Initialize = (InitializeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Initialize")); + FHoudiniApi::InsertMultiparmInstance = (InsertMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_InsertMultiparmInstance")); + FHoudiniApi::Interrupt = (InterruptFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Interrupt")); + FHoudiniApi::IsInitialized = (IsInitializedFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsInitialized")); + FHoudiniApi::IsNodeValid = (IsNodeValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsNodeValid")); + FHoudiniApi::IsSessionValid = (IsSessionValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsSessionValid")); + FHoudiniApi::Keyframe_Create = (Keyframe_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Create")); + FHoudiniApi::Keyframe_Init = (Keyframe_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Init")); + FHoudiniApi::LoadAssetLibraryFromFile = (LoadAssetLibraryFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromFile")); + FHoudiniApi::LoadAssetLibraryFromMemory = (LoadAssetLibraryFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromMemory")); + FHoudiniApi::LoadGeoFromFile = (LoadGeoFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromFile")); + FHoudiniApi::LoadGeoFromMemory = (LoadGeoFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromMemory")); + FHoudiniApi::LoadHIPFile = (LoadHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadHIPFile")); + FHoudiniApi::LoadNodeFromFile = (LoadNodeFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadNodeFromFile")); + FHoudiniApi::MaterialInfo_Create = (MaterialInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Create")); + FHoudiniApi::MaterialInfo_Init = (MaterialInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Init")); + FHoudiniApi::MergeHIPFile = (MergeHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MergeHIPFile")); + FHoudiniApi::NodeInfo_Create = (NodeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Create")); + FHoudiniApi::NodeInfo_Init = (NodeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Init")); + FHoudiniApi::ObjectInfo_Create = (ObjectInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Create")); + FHoudiniApi::ObjectInfo_Init = (ObjectInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Init")); + FHoudiniApi::ParmChoiceInfo_Create = (ParmChoiceInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Create")); + FHoudiniApi::ParmChoiceInfo_Init = (ParmChoiceInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Init")); + FHoudiniApi::ParmHasExpression = (ParmHasExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasExpression")); + FHoudiniApi::ParmHasTag = (ParmHasTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasTag")); + FHoudiniApi::ParmInfo_Create = (ParmInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Create")); + FHoudiniApi::ParmInfo_GetFloatValueCount = (ParmInfo_GetFloatValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetFloatValueCount")); + FHoudiniApi::ParmInfo_GetIntValueCount = (ParmInfo_GetIntValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetIntValueCount")); + FHoudiniApi::ParmInfo_GetStringValueCount = (ParmInfo_GetStringValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetStringValueCount")); + FHoudiniApi::ParmInfo_Init = (ParmInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Init")); + FHoudiniApi::ParmInfo_IsFloat = (ParmInfo_IsFloatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsFloat")); + FHoudiniApi::ParmInfo_IsInt = (ParmInfo_IsIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsInt")); + FHoudiniApi::ParmInfo_IsNode = (ParmInfo_IsNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNode")); + FHoudiniApi::ParmInfo_IsNonValue = (ParmInfo_IsNonValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNonValue")); + FHoudiniApi::ParmInfo_IsPath = (ParmInfo_IsPathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsPath")); + FHoudiniApi::ParmInfo_IsString = (ParmInfo_IsStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsString")); + FHoudiniApi::PartInfo_Create = (PartInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Create")); + FHoudiniApi::PartInfo_GetAttributeCountByOwner = (PartInfo_GetAttributeCountByOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetAttributeCountByOwner")); + FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = (PartInfo_GetElementCountByAttributeOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByAttributeOwner")); + FHoudiniApi::PartInfo_GetElementCountByGroupType = (PartInfo_GetElementCountByGroupTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByGroupType")); + FHoudiniApi::PartInfo_Init = (PartInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Init")); + FHoudiniApi::PausePDGCook = (PausePDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PausePDGCook")); + FHoudiniApi::PythonThreadInterpreterLock = (PythonThreadInterpreterLockFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PythonThreadInterpreterLock")); + FHoudiniApi::QueryNodeInput = (QueryNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeInput")); + FHoudiniApi::QueryNodeOutputConnectedCount = (QueryNodeOutputConnectedCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedCount")); + FHoudiniApi::QueryNodeOutputConnectedNodes = (QueryNodeOutputConnectedNodesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedNodes")); + FHoudiniApi::RemoveCustomString = (RemoveCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveCustomString")); + FHoudiniApi::RemoveMultiparmInstance = (RemoveMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveMultiparmInstance")); + FHoudiniApi::RemoveParmExpression = (RemoveParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveParmExpression")); + FHoudiniApi::RenameNode = (RenameNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenameNode")); + FHoudiniApi::RenderCOPToImage = (RenderCOPToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderCOPToImage")); + FHoudiniApi::RenderTextureToImage = (RenderTextureToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderTextureToImage")); + FHoudiniApi::ResetSimulation = (ResetSimulationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ResetSimulation")); + FHoudiniApi::RevertGeo = (RevertGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertGeo")); + FHoudiniApi::RevertParmToDefault = (RevertParmToDefaultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefault")); + FHoudiniApi::RevertParmToDefaults = (RevertParmToDefaultsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefaults")); + FHoudiniApi::SaveGeoToFile = (SaveGeoToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToFile")); + FHoudiniApi::SaveGeoToMemory = (SaveGeoToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToMemory")); + FHoudiniApi::SaveHIPFile = (SaveHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveHIPFile")); + FHoudiniApi::SaveNodeToFile = (SaveNodeToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveNodeToFile")); + FHoudiniApi::SessionSyncInfo_Create = (SessionSyncInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SessionSyncInfo_Create")); + FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); + FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); + FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); + FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); + FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); + FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); + FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); + FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); + FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); + FHoudiniApi::SetCurveKnots = (SetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveKnots")); + FHoudiniApi::SetCurveOrders = (SetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveOrders")); + FHoudiniApi::SetCustomString = (SetCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCustomString")); + FHoudiniApi::SetFaceCounts = (SetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetFaceCounts")); + FHoudiniApi::SetGroupMembership = (SetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetGroupMembership")); + FHoudiniApi::SetHeightFieldData = (SetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetHeightFieldData")); + FHoudiniApi::SetImageInfo = (SetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetImageInfo")); + FHoudiniApi::SetNodeDisplay = (SetNodeDisplayFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetNodeDisplay")); + FHoudiniApi::SetObjectTransform = (SetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetObjectTransform")); + FHoudiniApi::SetParmExpression = (SetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmExpression")); + FHoudiniApi::SetParmFloatValue = (SetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValue")); + FHoudiniApi::SetParmFloatValues = (SetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValues")); + FHoudiniApi::SetParmIntValue = (SetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValue")); + FHoudiniApi::SetParmIntValues = (SetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValues")); + FHoudiniApi::SetParmNodeValue = (SetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmNodeValue")); + FHoudiniApi::SetParmStringValue = (SetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmStringValue")); + FHoudiniApi::SetPartInfo = (SetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPartInfo")); + FHoudiniApi::SetPreset = (SetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPreset")); + FHoudiniApi::SetServerEnvInt = (SetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvInt")); + FHoudiniApi::SetServerEnvString = (SetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvString")); + FHoudiniApi::SetSessionSync = (SetSessionSyncFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSync")); + FHoudiniApi::SetSessionSyncInfo = (SetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSyncInfo")); + FHoudiniApi::SetTime = (SetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTime")); + FHoudiniApi::SetTimelineOptions = (SetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTimelineOptions")); + FHoudiniApi::SetTransformAnimCurve = (SetTransformAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTransformAnimCurve")); + FHoudiniApi::SetUseHoudiniTime = (SetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetUseHoudiniTime")); + FHoudiniApi::SetVertexList = (SetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVertexList")); + FHoudiniApi::SetViewport = (SetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetViewport")); + FHoudiniApi::SetVolumeInfo = (SetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeInfo")); + FHoudiniApi::SetVolumeTileFloatData = (SetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileFloatData")); + FHoudiniApi::SetVolumeTileIntData = (SetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileIntData")); + FHoudiniApi::SetVolumeVoxelFloatData = (SetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelFloatData")); + FHoudiniApi::SetVolumeVoxelIntData = (SetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelIntData")); + FHoudiniApi::SetWorkitemFloatData = (SetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemFloatData")); + FHoudiniApi::SetWorkitemIntData = (SetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemIntData")); + FHoudiniApi::SetWorkitemStringData = (SetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemStringData")); + FHoudiniApi::StartThriftNamedPipeServer = (StartThriftNamedPipeServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftNamedPipeServer")); + FHoudiniApi::StartThriftSocketServer = (StartThriftSocketServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftSocketServer")); + FHoudiniApi::ThriftServerOptions_Create = (ThriftServerOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Create")); + FHoudiniApi::ThriftServerOptions_Init = (ThriftServerOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Init")); + FHoudiniApi::TimelineOptions_Create = (TimelineOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Create")); + FHoudiniApi::TimelineOptions_Init = (TimelineOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Init")); + FHoudiniApi::TransformEuler_Create = (TransformEuler_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Create")); + FHoudiniApi::TransformEuler_Init = (TransformEuler_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Init")); + FHoudiniApi::Transform_Create = (Transform_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Create")); + FHoudiniApi::Transform_Init = (Transform_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Init")); + FHoudiniApi::Viewport_Create = (Viewport_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Viewport_Create")); + FHoudiniApi::VolumeInfo_Create = (VolumeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Create")); + FHoudiniApi::VolumeInfo_Init = (VolumeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Init")); + FHoudiniApi::VolumeTileInfo_Create = (VolumeTileInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Create")); + FHoudiniApi::VolumeTileInfo_Init = (VolumeTileInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Init")); +} + + +void +FHoudiniApi::FinalizeHAPI() +{ + FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; + FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; + FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; + FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; + FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; + FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; + FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; + FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; + FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; + FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; + FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; + FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; + FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; + FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; + FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; + FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; + FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; + FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; + FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; + FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; + FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; + FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; + FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; + FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; + FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; + FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; + FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; + FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; + FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; + FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; + FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; + FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; + FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; + FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; + FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; + FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; + FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; + FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; + FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; + FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; + FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; + FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; + FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; + FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; + FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; + FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; + FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; + FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; + FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; + FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; + FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; + FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; + FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; + FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; + FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; + FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; + FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; + FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; + FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; + FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; + FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; + FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; + FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; + FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; + FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; + FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; + FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; + FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; + FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; + FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; + FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; + FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; + FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; + FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; + FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; + FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; + FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; + FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; + FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; + FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; + FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; + FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; + FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; + FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; + FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; + FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; + FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; + FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; + FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; + FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; + FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; + FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; + FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; + FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; + FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; + FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; + FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; + FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; + FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; + FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; + FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; + FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; + FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; + FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; + FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; + FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; + FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; + FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; + FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; + FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; + FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; + FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; + FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; + FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; + FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; + FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; + FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; + FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; + FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; + FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; + FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; + FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; + FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; + FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; + FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; + FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; + FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; + FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; + FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; + FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; + FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; + FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; + FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; + FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; + FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; + FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; + FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; + FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; + FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; + FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; + FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; + FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; + FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; + FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; + FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; + FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; + FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; + FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; + FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; + FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; + FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; + FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; + FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; + FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; + FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; + FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; + FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; + FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; + FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; + FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; + FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; + FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; + FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; + FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; + FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; + FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; + FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; + FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; + FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; + FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; + FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; + FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; + FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; + FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; + FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; + FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; + FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; + FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; + FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; + FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; + FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; + FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; + FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; + FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; + FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; + FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; + FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; + FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; + FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; + FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; + FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; + FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; + FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; + FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; + FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; + FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; + FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; + FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; + FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; + FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; + FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; + FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; + FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; + FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; + FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; + FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; + FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; + FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; + FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; + FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; + FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; + FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; + FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; + FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; + FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; + FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; + FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; + FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; + FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; + FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; + FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; + FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; + FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; + FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; + FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; + FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; + FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; + FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; + FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; + FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; + FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; + FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; + FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; + FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; + FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; + FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; + FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; + FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; + FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; + FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; + FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; + FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; + FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; + FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; + FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; + FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; + FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; + FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; + FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; + FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; + FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; + FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; + FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; + FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; + FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; + FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; + FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; + FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; + FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; + FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; + FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; + FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; + FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; + FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; + FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; + FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; + FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; + FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; + FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; + FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; + FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; + FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; + FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; + FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; + FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; + FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; + FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; + FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; + FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; + FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; + FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; + FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; + FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; + FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; + FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; + FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; + FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; + FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; + FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; + FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; + FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; + FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; + FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; + FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; + FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; + FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; + FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; + FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; +} + + +bool +FHoudiniApi::IsHAPIInitialized() +{ + return ( FHoudiniApi::IsInitialized != &FHoudiniApi::IsInitializedEmptyStub ); +} + + +HAPI_Result +FHoudiniApi::AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_AssetInfo +FHoudiniApi::AssetInfo_CreateEmptyStub() +{ + return HAPI_AssetInfo(); +} + + +void +FHoudiniApi::AssetInfo_InitEmptyStub(HAPI_AssetInfo * in) +{ + return; +} + + +HAPI_AttributeInfo +FHoudiniApi::AttributeInfo_CreateEmptyStub() +{ + return HAPI_AttributeInfo(); +} + + +void +FHoudiniApi::AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CleanupEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ClearConnectionErrorEmptyStub() +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CloseSessionEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Bool +FHoudiniApi::CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right) +{ + return HAPI_Bool(); +} + + +HAPI_CookOptions +FHoudiniApi::CookOptions_CreateEmptyStub() +{ + return HAPI_CookOptions(); +} + + +void +FHoudiniApi::CookOptions_InitEmptyStub(HAPI_CookOptions * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateInProcessSessionEmptyStub(HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_CurveInfo +FHoudiniApi::CurveInfo_CreateEmptyStub() +{ + return HAPI_CurveInfo(); +} + + +void +FHoudiniApi::CurveInfo_InitEmptyStub(HAPI_CurveInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_GeoInfo +FHoudiniApi::GeoInfo_CreateEmptyStub() +{ + return HAPI_GeoInfo(); +} + + +int +FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type) +{ + return -1; +} + + +void +FHoudiniApi::GeoInfo_InitEmptyStub(HAPI_GeoInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetConnectionErrorLengthEmptyStub(int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTimeEmptyStub(const HAPI_Session * session, float * time) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_HandleBindingInfo +FHoudiniApi::HandleBindingInfo_CreateEmptyStub() +{ + return HAPI_HandleBindingInfo(); +} + + +void +FHoudiniApi::HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in) +{ + return; +} + + +HAPI_HandleInfo +FHoudiniApi::HandleInfo_CreateEmptyStub() +{ + return HAPI_HandleInfo(); +} + + +void +FHoudiniApi::HandleInfo_InitEmptyStub(HAPI_HandleInfo * in) +{ + return; +} + + +HAPI_ImageFileFormat +FHoudiniApi::ImageFileFormat_CreateEmptyStub() +{ + return HAPI_ImageFileFormat(); +} + + +void +FHoudiniApi::ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in) +{ + return; +} + + +HAPI_ImageInfo +FHoudiniApi::ImageInfo_CreateEmptyStub() +{ + return HAPI_ImageInfo(); +} + + +void +FHoudiniApi::ImageInfo_InitEmptyStub(HAPI_ImageInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::InterruptEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsInitializedEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsSessionValidEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Keyframe +FHoudiniApi::Keyframe_CreateEmptyStub() +{ + return HAPI_Keyframe(); +} + + +void +FHoudiniApi::Keyframe_InitEmptyStub(HAPI_Keyframe * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_MaterialInfo +FHoudiniApi::MaterialInfo_CreateEmptyStub() +{ + return HAPI_MaterialInfo(); +} + + +void +FHoudiniApi::MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_NodeInfo +FHoudiniApi::NodeInfo_CreateEmptyStub() +{ + return HAPI_NodeInfo(); +} + + +void +FHoudiniApi::NodeInfo_InitEmptyStub(HAPI_NodeInfo * in) +{ + return; +} + + +HAPI_ObjectInfo +FHoudiniApi::ObjectInfo_CreateEmptyStub() +{ + return HAPI_ObjectInfo(); +} + + +void +FHoudiniApi::ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in) +{ + return; +} + + +HAPI_ParmChoiceInfo +FHoudiniApi::ParmChoiceInfo_CreateEmptyStub() +{ + return HAPI_ParmChoiceInfo(); +} + + +void +FHoudiniApi::ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_ParmInfo +FHoudiniApi::ParmInfo_CreateEmptyStub() +{ + return HAPI_ParmInfo(); +} + + +int +FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in) +{ + return -1; +} + + +int +FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in) +{ + return -1; +} + + +int +FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in) +{ + return -1; +} + + +void +FHoudiniApi::ParmInfo_InitEmptyStub(HAPI_ParmInfo * in) +{ + return; +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_PartInfo +FHoudiniApi::PartInfo_CreateEmptyStub() +{ + return HAPI_PartInfo(); +} + + +int +FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) +{ + return -1; +} + + +int +FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) +{ + return -1; +} + + +int +FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type) +{ + return -1; +} + + +void +FHoudiniApi::PartInfo_InitEmptyStub(HAPI_PartInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_SessionSyncInfo +FHoudiniApi::SessionSyncInfo_CreateEmptyStub() +{ + return HAPI_SessionSyncInfo(); +} + + +HAPI_Result +FHoudiniApi::SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTimeEmptyStub(const HAPI_Session * session, float time) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_ThriftServerOptions +FHoudiniApi::ThriftServerOptions_CreateEmptyStub() +{ + return HAPI_ThriftServerOptions(); +} + + +void +FHoudiniApi::ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in) +{ + return; +} + + +HAPI_TimelineOptions +FHoudiniApi::TimelineOptions_CreateEmptyStub() +{ + return HAPI_TimelineOptions(); +} + + +void +FHoudiniApi::TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in) +{ + return; +} + + +HAPI_TransformEuler +FHoudiniApi::TransformEuler_CreateEmptyStub() +{ + return HAPI_TransformEuler(); +} + + +void +FHoudiniApi::TransformEuler_InitEmptyStub(HAPI_TransformEuler * in) +{ + return; +} + + +HAPI_Transform +FHoudiniApi::Transform_CreateEmptyStub() +{ + return HAPI_Transform(); +} + + +void +FHoudiniApi::Transform_InitEmptyStub(HAPI_Transform * in) +{ + return; +} + + +HAPI_Viewport +FHoudiniApi::Viewport_CreateEmptyStub() +{ + return HAPI_Viewport(); +} + + +HAPI_VolumeInfo +FHoudiniApi::VolumeInfo_CreateEmptyStub() +{ + return HAPI_VolumeInfo(); +} + + +void +FHoudiniApi::VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in) +{ + return; +} + + +HAPI_VolumeTileInfo +FHoudiniApi::VolumeTileInfo_CreateEmptyStub() +{ + return HAPI_VolumeTileInfo(); +} + + +void +FHoudiniApi::VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in) +{ + return; +} + + diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.cpp b/Source/HoudiniEngine/Private/HoudiniEngine.cpp index 21e046a44..abacf22dc 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngine.cpp @@ -1,1122 +1,1122 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngine.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "HoudiniApi.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineScheduler.h" -#include "HoudiniEngineManager.h" -#include "HoudiniEngineTask.h" -#include "HoudiniEngineTaskInfo.h" -#include "HoudiniAssetComponent.h" -#include "HAPI/HAPI_Version.h" - -#include "Modules/ModuleManager.h" -#include "Misc/ScopeLock.h" -#include "Engine/StaticMesh.h" -#include "Materials/Material.h" -#include "ISettingsModule.h" -#include "HAL/PlatformFilemanager.h" -#include "Async/Async.h" -#include "Logging/LogMacros.h" - -#if WITH_EDITOR - #include "Widgets/Notifications/SNotificationList.h" - #include "Framework/Notifications/NotificationManager.h" -#endif - -#define LOCTEXT_NAMESPACE "HoudiniEngine" - -IMPLEMENT_MODULE(FHoudiniEngine, HoudiniEngine) -DEFINE_LOG_CATEGORY( LogHoudiniEngine ); - -FHoudiniEngine * -FHoudiniEngine::HoudiniEngineInstance = nullptr; - -FHoudiniEngine::FHoudiniEngine() - : LicenseType(HAPI_LICENSE_NONE) - , HoudiniEngineSchedulerThread(nullptr) - , HoudiniEngineScheduler(nullptr) - , HoudiniEngineManagerThread(nullptr) - , HoudiniEngineManager(nullptr) - //, bHAPIVersionMismatch(false) - , bEnableCookingGlobal(true) - , UIRefreshCountWhenPauseCooking(0) - , bFirstSessionCreated(false) - , bEnableSessionSync(false) - , bCookUsingHoudiniTime(true) - , bSyncViewport(false) - , bSyncHoudiniViewport(true) - , bSyncUnrealViewport(false) - , HoudiniLogoStaticMesh(nullptr) - , HoudiniDefaultMaterial(nullptr) - , HoudiniTemplateMaterial(nullptr) - , HoudiniLogoBrush(nullptr) - , HoudiniDefaultReferenceMesh(nullptr) - , HoudiniDefaultReferenceMeshMaterial(nullptr) -{ - Session.type = HAPI_SESSION_MAX; - Session.id = -1; - - -#if WITH_EDITOR - HapiNotificationStarted = 0.0; -#endif -} - -FHoudiniEngine& -FHoudiniEngine::Get() -{ - check(FHoudiniEngine::HoudiniEngineInstance); - return *FHoudiniEngine::HoudiniEngineInstance; -} - -bool -FHoudiniEngine::IsInitialized() -{ - return FHoudiniEngine::HoudiniEngineInstance != nullptr && FHoudiniEngineUtils::IsInitialized(); -} - -void -FHoudiniEngine::StartupModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine module...")); - -#if WITH_EDITOR - // Register settings. - if (ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings")) - { - SettingsModule->RegisterSettings( - "Project", "Plugins", "HoudiniEngine", - LOCTEXT("RuntimeSettingsName", "Houdini Engine"), - LOCTEXT("RuntimeSettingsDescription", "Configure the HoudiniEngine plugin"), - GetMutableDefault< UHoudiniRuntimeSettings >()); - } -#endif - - // Before starting the module, we need to locate and load HAPI library. - { - void * HAPILibraryHandle = FHoudiniEngineUtils::LoadLibHAPI(LibHAPILocation); - if ( HAPILibraryHandle ) - { - FHoudiniApi::InitializeHAPI( HAPILibraryHandle ); - } - else - { - // Get platform specific name of libHAPI. - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - HOUDINI_LOG_MESSAGE(TEXT("Failed locating or loading %s"), *LibHAPIName); - } - } - - // Create static mesh Houdini logo. - HoudiniLogoStaticMesh = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_LOGO, nullptr, LOAD_None, nullptr); - if (HoudiniLogoStaticMesh.IsValid()) - HoudiniLogoStaticMesh->AddToRoot(); - - // Create default material. - HoudiniDefaultMaterial = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultMaterial.IsValid()) - HoudiniDefaultMaterial->AddToRoot(); - - HoudiniTemplateMaterial = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniTemplateMaterial.IsValid()) - HoudiniTemplateMaterial->AddToRoot(); - - // Houdini Logo Brush - FString Icon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icon128.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) - { - const FName BrushName(*Icon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Logo Brush - FString HEIcon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_logo_128.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) - { - const FName BrushName(*HEIcon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Create Houdini default reference mesh - HoudiniDefaultReferenceMesh = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultReferenceMesh.IsValid()) - HoudiniDefaultReferenceMesh->AddToRoot(); - - // Create Houdini default reference mesh material - HoudiniDefaultReferenceMeshMaterial = LoadObject - (nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultReferenceMeshMaterial.IsValid()) - HoudiniDefaultReferenceMeshMaterial->AddToRoot(); - - // We do not automatically try to start a session when starting up the module now. - bFirstSessionCreated = false; - - // Create HAPI scheduler and processing thread. - HoudiniEngineScheduler = new FHoudiniEngineScheduler(); - HoudiniEngineSchedulerThread = FRunnableThread::Create( - HoudiniEngineScheduler, TEXT("HoudiniSchedulerThread"), 0, TPri_Normal); - - // Create Houdini Asset Manager - HoudiniEngineManager = new FHoudiniEngineManager(); - - // Set the default value for pausing houdini engine cooking - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bEnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; - - // Check if a null session is set - bool bNoneSession = (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None); - - // Initialize the singleton with this instance - FHoudiniEngine::HoudiniEngineInstance = this; - - // See if we need to start the manager ticking if needed - // Don tick if we failed to load HAPI, if cooking is disabled or if we're using a null session - if (FHoudiniApi::IsHAPIInitialized()) - { - if (bEnableCookingGlobal && !bNoneSession) - { - PostEngineInitCallback = FCoreDelegates::OnPostEngineInit.AddLambda([]() - { - FHoudiniEngine& HEngine = FHoudiniEngine::Get(); - HEngine.UnregisterPostEngineInitCallback(); - FHoudiniEngineManager* const Manager = HEngine.GetHoudiniEngineManager(); - if (Manager) - Manager->StartHoudiniTicking(); - }); - } - } -} - -void -FHoudiniEngine::ShutdownModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine module.")); - - // We no longer need the Houdini logo static mesh. - if (HoudiniLogoStaticMesh.IsValid()) - { - HoudiniLogoStaticMesh->RemoveFromRoot(); - HoudiniLogoStaticMesh = nullptr; - } - - // We no longer need the Houdini default material. - if (HoudiniDefaultMaterial.IsValid()) - { - HoudiniDefaultMaterial->RemoveFromRoot(); - HoudiniDefaultMaterial = nullptr; - } - - // We no longer need the Houdini default material. - if (HoudiniTemplateMaterial.IsValid()) - { - HoudiniTemplateMaterial->RemoveFromRoot(); - HoudiniTemplateMaterial = nullptr; - } - - // We no longer need the Houdini default reference mesh - if (HoudiniDefaultReferenceMesh.IsValid()) - { - HoudiniDefaultReferenceMesh->RemoveFromRoot(); - HoudiniDefaultReferenceMesh = nullptr; - } - - // We no longer need the Houdini default reference mesh material - if (HoudiniDefaultReferenceMeshMaterial.IsValid()) - { - HoudiniDefaultReferenceMeshMaterial->RemoveFromRoot(); - HoudiniDefaultReferenceMeshMaterial = nullptr; - } - /* - // We no longer need Houdini digital asset used for loading bgeo files. - if (HoudiniBgeoAsset.IsValid()) - { - HoudiniBgeoAsset->RemoveFromRoot(); - HoudiniBgeoAsset = nullptr; - } - */ - -#if WITH_EDITOR - // Unregister settings. - ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings"); - if (SettingsModule) - SettingsModule->UnregisterSettings("Project", "Plugins", "HoudiniEngine"); -#endif - - // Do scheduler and thread clean up. - if (HoudiniEngineScheduler) - HoudiniEngineScheduler->Stop(); - - if (HoudiniEngineSchedulerThread) - { - //HoudiniEngineSchedulerThread->Kill( true ); - HoudiniEngineSchedulerThread->WaitForCompletion(); - - delete HoudiniEngineSchedulerThread; - HoudiniEngineSchedulerThread = nullptr; - } - - if ( HoudiniEngineScheduler ) - { - delete HoudiniEngineScheduler; - HoudiniEngineScheduler = nullptr; - } - - // Do manager clean up. - if (HoudiniEngineManager) - HoudiniEngineManager->StopHoudiniTicking(); - - if (HoudiniEngineManager) - { - delete HoudiniEngineManager; - HoudiniEngineManager = nullptr; - } - - // Perform HAPI finalization. - if ( FHoudiniApi::IsHAPIInitialized() ) - { - FHoudiniApi::Cleanup(GetSession()); - FHoudiniApi::CloseSession(GetSession()); - } - - FHoudiniApi::FinalizeHAPI(); - - FHoudiniEngine::HoudiniEngineInstance = nullptr; -} - -void -FHoudiniEngine::AddTask(const FHoudiniEngineTask & InTask) -{ - if ( HoudiniEngineScheduler ) - HoudiniEngineScheduler->AddTask(InTask); - - FScopeLock ScopeLock(&CriticalSection); - FHoudiniEngineTaskInfo TaskInfo; - TaskInfo.TaskType = InTask.TaskType; - TaskInfo.TaskState = EHoudiniEngineTaskState::Working; - - TaskInfos.Add(InTask.HapiGUID, TaskInfo); -} - -void -FHoudiniEngine::AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo) -{ - FScopeLock ScopeLock(&CriticalSection); - TaskInfos.Add(InHapiGUID, InTaskInfo); -} - -void -FHoudiniEngine::RemoveTaskInfo(const FGuid& InHapiGUID) -{ - FScopeLock ScopeLock(&CriticalSection); - TaskInfos.Remove(InHapiGUID); -} - -bool -FHoudiniEngine::RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo) -{ - FScopeLock ScopeLock(&CriticalSection); - - if (TaskInfos.Contains(InHapiGUID)) - { - OutTaskInfo = TaskInfos[InHapiGUID]; - return true; - } - - return false; -} - -/* -void -FHoudiniEngine::AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - if (HoudiniEngineManager) - HoudiniEngineManager->AddComponent(HAC); -} -*/ - -const FString & -FHoudiniEngine::GetLibHAPILocation() const -{ - return LibHAPILocation; -} - -const HAPI_Session * -FHoudiniEngine::GetSession() const -{ - return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; -} - -HAPI_CookOptions -FHoudiniEngine::GetDefaultCookOptions() -{ - // Default CookOptions - HAPI_CookOptions CookOptions; - FHoudiniApi::CookOptions_Init(&CookOptions); - - CookOptions.curveRefineLOD = 8.0f; - CookOptions.clearErrorsAndWarnings = false; - CookOptions.maxVerticesPerPrimitive = 3; - CookOptions.splitGeosByGroup = false; - CookOptions.splitGeosByAttribute = false; - CookOptions.splitAttrSH = 0; - CookOptions.refineCurveToLinear = true; - CookOptions.handleBoxPartTypes = false; - CookOptions.handleSpherePartTypes = false; - CookOptions.splitPointsByVertexAttributes = false; - CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; - CookOptions.cookTemplatedGeos = true; - - return CookOptions; -} - -bool -FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, - const bool& StartAutomaticServer, - const float& AutomaticServerTimeout, - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const int32& ServerPort, - const FString& ServerHost) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - // Only start a new Session if we dont already have a valid one - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) - return true; - - HAPI_Result SessionResult = HAPI_RESULT_FAILURE; - - HAPI_ThriftServerOptions ServerOptions; - FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); - ServerOptions.autoClose = true; - ServerOptions.timeoutMs = AutomaticServerTimeout; - - // Unless we automatically start the server, - // consider we're in SessionSync mode - bEnableSessionSync = true; - - auto UpdatePathForServer = [&] - { - // Modify our PATH so that HARC will find HARS.exe - const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); - - FString OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); - - FString ModifiedPath = -#if PLATFORM_MAC - // On Mac our binaries are split between two folders - LibHAPILocation + TEXT("/../Resources/bin") + PathDelimiter + -#endif - LibHAPILocation + PathDelimiter + OrigPathVar; - - FPlatformMisc::SetEnvironmentVar(TEXT("PATH"), *ModifiedPath); - }; - - switch ( SessionType ) - { - case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: - { - // Try to connect to an existing socket session first - SessionResult = FHoudiniApi::CreateThriftSocketSession( - SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort ); - - // Start a session and try to connect to it if we failed - if ( StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS ) - { - UpdatePathForServer(); - FHoudiniApi::StartThriftSocketServer( - &ServerOptions, ServerPort, nullptr); - - // We've started the server manually, disable session sync - bEnableSessionSync = false; - - SessionResult = FHoudiniApi::CreateThriftSocketSession( - SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort); - } - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: - { - // Try to connect to an existing pipe session first - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - SessionPtr, TCHAR_TO_UTF8(*ServerPipeName) ); - - // Start a session and try to connect to it if we failed - if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) - { - UpdatePathForServer(); - FHoudiniApi::StartThriftNamedPipeServer( - &ServerOptions, TCHAR_TO_UTF8(*ServerPipeName), nullptr); - - // We've started the server manually, disable session sync - bEnableSessionSync = false; - - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - SessionPtr, TCHAR_TO_UTF8(*ServerPipeName)); - } - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_None: - { - HOUDINI_LOG_MESSAGE(TEXT("Session type set to None, Cooking is disabled.")); - // Disable session sync - bEnableSessionSync = false; - break; - } - - // As of Unreal 4.19, InProcess sessions are not supported anymore - case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: - default: - HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine session type")); - // Disable session sync - bEnableSessionSync = false; - break; - } - - if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) - { - // Disable session sync as well? - bEnableSessionSync = false; - return false; - } - - // Update this session's license type - HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( - SessionPtr, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); - - return true; -} - -bool -FHoudiniEngine::SessionSyncConnect( - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const FString& ServerHost, - const int32& ServerPort) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - // Only start a new Session if we dont already have a valid one - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(&Session)) - return true; - - HAPI_Result SessionResult = HAPI_RESULT_FAILURE; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - - HAPI_ThriftServerOptions ServerOptions; - FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); - ServerOptions.autoClose = true; - ServerOptions.timeoutMs = HoudiniRuntimeSettings->AutomaticServerTimeout; - - switch (SessionType) - { - case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: - { - // Try to connect to an existing socket session first - SessionResult = FHoudiniApi::CreateThriftSocketSession( - &Session, TCHAR_TO_UTF8(*ServerHost), ServerPort); - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: - { - // Try to connect to an existing pipe session first - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - &Session, TCHAR_TO_UTF8(*ServerPipeName)); - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_None: - case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: - default: - HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine Session Sync Type!!")); - bEnableSessionSync = false; - break; - } - - if (SessionResult != HAPI_RESULT_SUCCESS) - return false; - - // Enable session sync - bEnableSessionSync = true; - - // Update this session's license type - HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( - &Session, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); - - // Update the default viewport sync settings - bSyncViewport = HoudiniRuntimeSettings->bSyncViewport; - bSyncHoudiniViewport = HoudiniRuntimeSettings->bSyncHoudiniViewport; - bSyncUnrealViewport = HoudiniRuntimeSettings->bSyncUnrealViewport; - - return true; -} - -bool -FHoudiniEngine::InitializeHAPISession() -{ - // The HAPI stubs needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The Houdini API stubs have not been properly initialized.")); - return false; - } - - // We need a Valid Session - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(GetSession())) - { - HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The session is invalid.")); - return false; - } - - // Now, initialize HAPI with the new session - // We need to make sure HAPI version is correct. - int32 RunningEngineMajor = 0; - int32 RunningEngineMinor = 0; - int32 RunningEngineApi = 0; - - // 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); - - // Compare defined and running versions. - if (RunningEngineMajor != HAPI_VERSION_HOUDINI_ENGINE_MAJOR - || RunningEngineMinor != HAPI_VERSION_HOUDINI_ENGINE_MINOR) - { - // Major or minor HAPI version differs, stop here - HOUDINI_LOG_ERROR( - TEXT("Starting up the Houdini Engine module failed: built and running versions do not match.")); - HOUDINI_LOG_ERROR( - TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), - HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, - RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - - // Display an error message - - // - return false; - - } - else if (RunningEngineApi != HAPI_VERSION_HOUDINI_ENGINE_API) - { - // Major/minor HAPIversions match, but only the API version differs, - // Allow the user to continue but warn him of possible instabilities - HOUDINI_LOG_WARNING( - TEXT("Starting up the Houdini Engine module: built and running versions do not match.")); - HOUDINI_LOG_WARNING( - TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), - HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, - RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - HOUDINI_LOG_WARNING( - TEXT("This could cause instabilities and crashes when using the Houdini Engine plugin")); - } - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Default CookOptions - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - - bool bUseCookingThread = true; - HAPI_Result Result = FHoudiniApi::Initialize( - &Session, - &CookOptions, - bUseCookingThread, - HoudiniRuntimeSettings->CookingThreadStackSize, - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->HoudiniEnvironmentFiles), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->OtlSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->DsoSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->ImageDsoSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->AudioDsoSearchPath)); - - if (Result == HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module.")); - } - else if (Result == HAPI_RESULT_ALREADY_INITIALIZED) - { - // Reused session? just notify the user - HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module - HAPI was already initialzed.")); - } - else - { - HOUDINI_LOG_ERROR( - TEXT("Starting up the Houdini Engine module failed: %s"), - *FHoudiniEngineUtils::GetErrorDescription(Result)); - - return false; - } - - // Let HAPI know we are running inside UE4 - FHoudiniApi::SetServerEnvString(&Session, HAPI_ENV_CLIENT_NAME, HAPI_UNREAL_CLIENT_NAME); - - if (bEnableSessionSync) - { - // Set the session sync infos if needed - UploadSessionSyncInfoToHoudini(); - - // Indicate that Session Sync is enabled - FString Notification = TEXT("Houdini Engine Session Sync enabled."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Session Sync enabled.")); - } - - return true; -} - - -void -FHoudiniEngine::OnSessionLost() -{ - // Mark the session as invalid - Session.id = -1; - Session.type = HAPI_SESSION_MAX; - bEnableSessionSync = false; - HoudiniEngineManager->StopHoudiniTicking(); - - // This indicates that we likely have lost the session due to a crash in HARS/Houdini - FString Notification = TEXT("Houdini Engine Session lost!"); - FHoudiniEngineUtils::CreateSlateNotification(Notification, 2.0, 4.0); - - HOUDINI_LOG_ERROR(TEXT("Houdini Engine Session lost! This could be caused by a crash in HARS.")); -} - -bool -FHoudiniEngine::StopSession() -{ - HAPI_Session* SessionPtr = &Session; - return StopSession(SessionPtr); -} - -bool -FHoudiniEngine::StopSession(HAPI_Session*& SessionPtr) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) - { - // SessionPtr is valid, clean up and close the session - FHoudiniApi::Cleanup(SessionPtr); - FHoudiniApi::CloseSession(SessionPtr); - } - - Session.id = -1; - Session.type = HAPI_SESSION_MAX; - bEnableSessionSync = false; - - HoudiniEngineManager->StopHoudiniTicking(); - - return true; -} - -bool -FHoudiniEngine::RestartSession() -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Starting the Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - if (!StopSession(SessionPtr)) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); - } - else - { - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - HoudiniRuntimeSettings->bStartAutomaticServer, - HoudiniRuntimeSettings->AutomaticServerTimeout, - HoudiniRuntimeSettings->SessionType, - HoudiniRuntimeSettings->ServerPipeName, - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to start the new Session")); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); - } - else - { - bSuccess = true; - } - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -bool -FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName) -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Create the Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - true, - HoudiniRuntimeSettings->AutomaticServerTimeout, - SessionType, - OverrideServerPipeName == NAME_None ? HoudiniRuntimeSettings->ServerPipeName : OverrideServerPipeName.ToString(), - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine Session")); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine session - Failed to initialize HAPI")); - } - else - { - bSuccess = true; - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -bool -FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType) -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Connecting to a Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - false, - HoudiniRuntimeSettings->AutomaticServerTimeout, - SessionType, - HoudiniRuntimeSettings->ServerPipeName, - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine Session")); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine session - Failed to initialize HAPI")); - } - else - { - bSuccess = true; - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -void -FHoudiniEngine::StartTicking() -{ - // Finish the notification and display the results - FString StatusText = TEXT("Houdini Engine session connected."); - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - - HoudiniEngineManager->StartHoudiniTicking(); -} - -void -FHoudiniEngine::StopTicking() -{ - // Finish the notification and display the results - FString StatusText = TEXT("Failed to start the Houdini Engine session..."); - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - - HoudiniEngineManager->StopHoudiniTicking(); - - HAPI_Session* SessionPtr = &Session; - StopSession(SessionPtr); -} - -bool -FHoudiniEngine::IsCookingEnabled() const -{ - return bEnableCookingGlobal; -} - -void -FHoudiniEngine::SetCookingEnabled(const bool& bInEnableCooking) -{ - bEnableCookingGlobal = bInEnableCooking; -} - -bool -FHoudiniEngine::GetFirstSessionCreated() const -{ - return bFirstSessionCreated; -} - -bool -FHoudiniEngine::CreateTaskSlateNotification( - const FText& InText, const bool& bForceNow, const float& NotificationExpire, const float& NotificationFadeOut) -{ -#if WITH_EDITOR - static double NotificationUpdateFrequency = 2.0f; - - // Check whether we want to display Slate cooking and instantiation notifications. - bool bDisplaySlateCookingNotifications = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (!bDisplaySlateCookingNotifications) - return false; - - if (!bForceNow) - { - if ((FPlatformTime::Seconds() - HapiNotificationStarted) < NotificationUpdateFrequency) - return false; - } - - if (!NotificationPtr.IsValid()) - { - FNotificationInfo Info(InText); - Info.bFireAndForget = false; - Info.FadeOutDuration = NotificationFadeOut; - Info.ExpireDuration = NotificationExpire; - TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); - if (HoudiniBrush.IsValid()) - Info.Image = HoudiniBrush.Get(); - - /* - if (!IsPIEActive()) - */ - - NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); - } -#endif - - return true; -} - -bool -FHoudiniEngine::UpdateTaskSlateNotification(const FText& InText) -{ -#if WITH_EDITOR - // task is till running - // Just update the slate notification - TSharedPtr NotificationItem = NotificationPtr.Pin(); - if (NotificationItem.IsValid()) - NotificationItem->SetText(InText); -#endif - - return true; -} - -bool -FHoudiniEngine::FinishTaskSlateNotification(const FText& InText) -{ -#if WITH_EDITOR - if (NotificationPtr.IsValid()) - { - TSharedPtr NotificationItem = NotificationPtr.Pin(); - if (NotificationItem.IsValid()) - { - NotificationItem->SetText(InText); - NotificationItem->ExpireAndFadeout(); - - NotificationPtr.Reset(); - } - } -#endif - - return true; -} - -void -FHoudiniEngine::UpdateSessionSyncInfoFromHoudini() -{ - if (!bEnableSessionSync) - return; - - // Set the Session Sync settings to Houdini - HAPI_SessionSyncInfo SessionSyncInfo; - //FHoudiniApi::SessionSyncInfo_Create(&SessionSyncInfo); - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetSessionSyncInfo(&Session, &SessionSyncInfo)) - { - bCookUsingHoudiniTime = SessionSyncInfo.cookUsingHoudiniTime; - bSyncViewport = SessionSyncInfo.syncViewport; - } -} - -void -FHoudiniEngine::UploadSessionSyncInfoToHoudini() -{ - // No need to set sessionsync info if we're not using session sync - if (!bEnableSessionSync) - return; - - // Set the Session Sync settings to Houdini - HAPI_SessionSyncInfo SessionSyncInfo; - SessionSyncInfo.cookUsingHoudiniTime = bCookUsingHoudiniTime; - SessionSyncInfo.syncViewport = bSyncViewport; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetSessionSyncInfo(&Session, &SessionSyncInfo)) - HOUDINI_LOG_WARNING(TEXT("Failed to set the SessionSync Infos.")); -} - -void -FHoudiniEngine::StartPDGCommandlet() -{ - if (HoudiniEngineManager) - HoudiniEngineManager->StartPDGCommandlet(); -} - -void -FHoudiniEngine::StopPDGCommandlet() -{ - if (HoudiniEngineManager) - HoudiniEngineManager->StopPDGCommandlet(); -} - -bool -FHoudiniEngine::IsPDGCommandletRunningOrConnected() -{ - if (HoudiniEngineManager) - return HoudiniEngineManager->IsPDGCommandletRunningOrConnected(); - return false; -} - -EHoudiniBGEOCommandletStatus -FHoudiniEngine::GetPDGCommandletStatus() -{ - if (HoudiniEngineManager) - return HoudiniEngineManager->GetPDGCommandletStatus(); - return EHoudiniBGEOCommandletStatus::NotStarted; -} - -void -FHoudiniEngine::UnregisterPostEngineInitCallback() -{ - if (PostEngineInitCallback.IsValid()) - FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitCallback); -} - -bool FHoudiniEngine::IsSyncWithHoudiniCookEnabled() const -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - return HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bSyncWithHoudiniCook : false; -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngine.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineScheduler.h" +#include "HoudiniEngineManager.h" +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniAssetComponent.h" +#include "HAPI/HAPI_Version.h" + +#include "Modules/ModuleManager.h" +#include "Misc/ScopeLock.h" +#include "Engine/StaticMesh.h" +#include "Materials/Material.h" +#include "ISettingsModule.h" +#include "HAL/PlatformFilemanager.h" +#include "Async/Async.h" +#include "Logging/LogMacros.h" + +#if WITH_EDITOR + #include "Widgets/Notifications/SNotificationList.h" + #include "Framework/Notifications/NotificationManager.h" +#endif + +#define LOCTEXT_NAMESPACE "HoudiniEngine" + +IMPLEMENT_MODULE(FHoudiniEngine, HoudiniEngine) +DEFINE_LOG_CATEGORY( LogHoudiniEngine ); + +FHoudiniEngine * +FHoudiniEngine::HoudiniEngineInstance = nullptr; + +FHoudiniEngine::FHoudiniEngine() + : LicenseType(HAPI_LICENSE_NONE) + , HoudiniEngineSchedulerThread(nullptr) + , HoudiniEngineScheduler(nullptr) + , HoudiniEngineManagerThread(nullptr) + , HoudiniEngineManager(nullptr) + //, bHAPIVersionMismatch(false) + , bEnableCookingGlobal(true) + , UIRefreshCountWhenPauseCooking(0) + , bFirstSessionCreated(false) + , bEnableSessionSync(false) + , bCookUsingHoudiniTime(true) + , bSyncViewport(false) + , bSyncHoudiniViewport(true) + , bSyncUnrealViewport(false) + , HoudiniLogoStaticMesh(nullptr) + , HoudiniDefaultMaterial(nullptr) + , HoudiniTemplateMaterial(nullptr) + , HoudiniLogoBrush(nullptr) + , HoudiniDefaultReferenceMesh(nullptr) + , HoudiniDefaultReferenceMeshMaterial(nullptr) +{ + Session.type = HAPI_SESSION_MAX; + Session.id = -1; + + +#if WITH_EDITOR + HapiNotificationStarted = 0.0; +#endif +} + +FHoudiniEngine& +FHoudiniEngine::Get() +{ + check(FHoudiniEngine::HoudiniEngineInstance); + return *FHoudiniEngine::HoudiniEngineInstance; +} + +bool +FHoudiniEngine::IsInitialized() +{ + return FHoudiniEngine::HoudiniEngineInstance != nullptr && FHoudiniEngineUtils::IsInitialized(); +} + +void +FHoudiniEngine::StartupModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine module...")); + +#if WITH_EDITOR + // Register settings. + if (ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->RegisterSettings( + "Project", "Plugins", "HoudiniEngine", + LOCTEXT("RuntimeSettingsName", "Houdini Engine"), + LOCTEXT("RuntimeSettingsDescription", "Configure the HoudiniEngine plugin"), + GetMutableDefault< UHoudiniRuntimeSettings >()); + } +#endif + + // Before starting the module, we need to locate and load HAPI library. + { + void * HAPILibraryHandle = FHoudiniEngineUtils::LoadLibHAPI(LibHAPILocation); + if ( HAPILibraryHandle ) + { + FHoudiniApi::InitializeHAPI( HAPILibraryHandle ); + } + else + { + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + HOUDINI_LOG_MESSAGE(TEXT("Failed locating or loading %s"), *LibHAPIName); + } + } + + // Create static mesh Houdini logo. + HoudiniLogoStaticMesh = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_LOGO, nullptr, LOAD_None, nullptr); + if (HoudiniLogoStaticMesh.IsValid()) + HoudiniLogoStaticMesh->AddToRoot(); + + // Create default material. + HoudiniDefaultMaterial = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultMaterial.IsValid()) + HoudiniDefaultMaterial->AddToRoot(); + + HoudiniTemplateMaterial = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniTemplateMaterial.IsValid()) + HoudiniTemplateMaterial->AddToRoot(); + + // Houdini Logo Brush + FString Icon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icon128.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) + { + const FName BrushName(*Icon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Logo Brush + FString HEIcon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_logo_128.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) + { + const FName BrushName(*HEIcon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Create Houdini default reference mesh + HoudiniDefaultReferenceMesh = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultReferenceMesh.IsValid()) + HoudiniDefaultReferenceMesh->AddToRoot(); + + // Create Houdini default reference mesh material + HoudiniDefaultReferenceMeshMaterial = LoadObject + (nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultReferenceMeshMaterial.IsValid()) + HoudiniDefaultReferenceMeshMaterial->AddToRoot(); + + // We do not automatically try to start a session when starting up the module now. + bFirstSessionCreated = false; + + // Create HAPI scheduler and processing thread. + HoudiniEngineScheduler = new FHoudiniEngineScheduler(); + HoudiniEngineSchedulerThread = FRunnableThread::Create( + HoudiniEngineScheduler, TEXT("HoudiniSchedulerThread"), 0, TPri_Normal); + + // Create Houdini Asset Manager + HoudiniEngineManager = new FHoudiniEngineManager(); + + // Set the default value for pausing houdini engine cooking + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bEnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; + + // Check if a null session is set + bool bNoneSession = (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None); + + // Initialize the singleton with this instance + FHoudiniEngine::HoudiniEngineInstance = this; + + // See if we need to start the manager ticking if needed + // Don tick if we failed to load HAPI, if cooking is disabled or if we're using a null session + if (FHoudiniApi::IsHAPIInitialized()) + { + if (bEnableCookingGlobal && !bNoneSession) + { + PostEngineInitCallback = FCoreDelegates::OnPostEngineInit.AddLambda([]() + { + FHoudiniEngine& HEngine = FHoudiniEngine::Get(); + HEngine.UnregisterPostEngineInitCallback(); + FHoudiniEngineManager* const Manager = HEngine.GetHoudiniEngineManager(); + if (Manager) + Manager->StartHoudiniTicking(); + }); + } + } +} + +void +FHoudiniEngine::ShutdownModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine module.")); + + // We no longer need the Houdini logo static mesh. + if (HoudiniLogoStaticMesh.IsValid()) + { + HoudiniLogoStaticMesh->RemoveFromRoot(); + HoudiniLogoStaticMesh = nullptr; + } + + // We no longer need the Houdini default material. + if (HoudiniDefaultMaterial.IsValid()) + { + HoudiniDefaultMaterial->RemoveFromRoot(); + HoudiniDefaultMaterial = nullptr; + } + + // We no longer need the Houdini default material. + if (HoudiniTemplateMaterial.IsValid()) + { + HoudiniTemplateMaterial->RemoveFromRoot(); + HoudiniTemplateMaterial = nullptr; + } + + // We no longer need the Houdini default reference mesh + if (HoudiniDefaultReferenceMesh.IsValid()) + { + HoudiniDefaultReferenceMesh->RemoveFromRoot(); + HoudiniDefaultReferenceMesh = nullptr; + } + + // We no longer need the Houdini default reference mesh material + if (HoudiniDefaultReferenceMeshMaterial.IsValid()) + { + HoudiniDefaultReferenceMeshMaterial->RemoveFromRoot(); + HoudiniDefaultReferenceMeshMaterial = nullptr; + } + /* + // We no longer need Houdini digital asset used for loading bgeo files. + if (HoudiniBgeoAsset.IsValid()) + { + HoudiniBgeoAsset->RemoveFromRoot(); + HoudiniBgeoAsset = nullptr; + } + */ + +#if WITH_EDITOR + // Unregister settings. + ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings"); + if (SettingsModule) + SettingsModule->UnregisterSettings("Project", "Plugins", "HoudiniEngine"); +#endif + + // Do scheduler and thread clean up. + if (HoudiniEngineScheduler) + HoudiniEngineScheduler->Stop(); + + if (HoudiniEngineSchedulerThread) + { + //HoudiniEngineSchedulerThread->Kill( true ); + HoudiniEngineSchedulerThread->WaitForCompletion(); + + delete HoudiniEngineSchedulerThread; + HoudiniEngineSchedulerThread = nullptr; + } + + if ( HoudiniEngineScheduler ) + { + delete HoudiniEngineScheduler; + HoudiniEngineScheduler = nullptr; + } + + // Do manager clean up. + if (HoudiniEngineManager) + HoudiniEngineManager->StopHoudiniTicking(); + + if (HoudiniEngineManager) + { + delete HoudiniEngineManager; + HoudiniEngineManager = nullptr; + } + + // Perform HAPI finalization. + if ( FHoudiniApi::IsHAPIInitialized() ) + { + FHoudiniApi::Cleanup(GetSession()); + FHoudiniApi::CloseSession(GetSession()); + } + + FHoudiniApi::FinalizeHAPI(); + + FHoudiniEngine::HoudiniEngineInstance = nullptr; +} + +void +FHoudiniEngine::AddTask(const FHoudiniEngineTask & InTask) +{ + if ( HoudiniEngineScheduler ) + HoudiniEngineScheduler->AddTask(InTask); + + FScopeLock ScopeLock(&CriticalSection); + FHoudiniEngineTaskInfo TaskInfo; + TaskInfo.TaskType = InTask.TaskType; + TaskInfo.TaskState = EHoudiniEngineTaskState::Working; + + TaskInfos.Add(InTask.HapiGUID, TaskInfo); +} + +void +FHoudiniEngine::AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo) +{ + FScopeLock ScopeLock(&CriticalSection); + TaskInfos.Add(InHapiGUID, InTaskInfo); +} + +void +FHoudiniEngine::RemoveTaskInfo(const FGuid& InHapiGUID) +{ + FScopeLock ScopeLock(&CriticalSection); + TaskInfos.Remove(InHapiGUID); +} + +bool +FHoudiniEngine::RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo) +{ + FScopeLock ScopeLock(&CriticalSection); + + if (TaskInfos.Contains(InHapiGUID)) + { + OutTaskInfo = TaskInfos[InHapiGUID]; + return true; + } + + return false; +} + +/* +void +FHoudiniEngine::AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + if (HoudiniEngineManager) + HoudiniEngineManager->AddComponent(HAC); +} +*/ + +const FString & +FHoudiniEngine::GetLibHAPILocation() const +{ + return LibHAPILocation; +} + +const HAPI_Session * +FHoudiniEngine::GetSession() const +{ + return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; +} + +HAPI_CookOptions +FHoudiniEngine::GetDefaultCookOptions() +{ + // Default CookOptions + HAPI_CookOptions CookOptions; + FHoudiniApi::CookOptions_Init(&CookOptions); + + CookOptions.curveRefineLOD = 8.0f; + CookOptions.clearErrorsAndWarnings = false; + CookOptions.maxVerticesPerPrimitive = 3; + CookOptions.splitGeosByGroup = false; + CookOptions.splitGeosByAttribute = false; + CookOptions.splitAttrSH = 0; + CookOptions.refineCurveToLinear = true; + CookOptions.handleBoxPartTypes = false; + CookOptions.handleSpherePartTypes = false; + CookOptions.splitPointsByVertexAttributes = false; + CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; + CookOptions.cookTemplatedGeos = true; + + return CookOptions; +} + +bool +FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, + const bool& StartAutomaticServer, + const float& AutomaticServerTimeout, + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const int32& ServerPort, + const FString& ServerHost) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + // Only start a new Session if we dont already have a valid one + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) + return true; + + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; + + HAPI_ThriftServerOptions ServerOptions; + FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); + ServerOptions.autoClose = true; + ServerOptions.timeoutMs = AutomaticServerTimeout; + + // Unless we automatically start the server, + // consider we're in SessionSync mode + bEnableSessionSync = true; + + auto UpdatePathForServer = [&] + { + // Modify our PATH so that HARC will find HARS.exe + const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); + + FString OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); + + FString ModifiedPath = +#if PLATFORM_MAC + // On Mac our binaries are split between two folders + LibHAPILocation + TEXT("/../Resources/bin") + PathDelimiter + +#endif + LibHAPILocation + PathDelimiter + OrigPathVar; + + FPlatformMisc::SetEnvironmentVar(TEXT("PATH"), *ModifiedPath); + }; + + switch ( SessionType ) + { + case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: + { + // Try to connect to an existing socket session first + SessionResult = FHoudiniApi::CreateThriftSocketSession( + SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort ); + + // Start a session and try to connect to it if we failed + if ( StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS ) + { + UpdatePathForServer(); + FHoudiniApi::StartThriftSocketServer( + &ServerOptions, ServerPort, nullptr); + + // We've started the server manually, disable session sync + bEnableSessionSync = false; + + SessionResult = FHoudiniApi::CreateThriftSocketSession( + SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort); + } + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: + { + // Try to connect to an existing pipe session first + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8(*ServerPipeName) ); + + // Start a session and try to connect to it if we failed + if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) + { + UpdatePathForServer(); + FHoudiniApi::StartThriftNamedPipeServer( + &ServerOptions, TCHAR_TO_UTF8(*ServerPipeName), nullptr); + + // We've started the server manually, disable session sync + bEnableSessionSync = false; + + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8(*ServerPipeName)); + } + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_None: + { + HOUDINI_LOG_MESSAGE(TEXT("Session type set to None, Cooking is disabled.")); + // Disable session sync + bEnableSessionSync = false; + break; + } + + // As of Unreal 4.19, InProcess sessions are not supported anymore + case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + default: + HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine session type")); + // Disable session sync + bEnableSessionSync = false; + break; + } + + if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) + { + // Disable session sync as well? + bEnableSessionSync = false; + return false; + } + + // Update this session's license type + HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( + SessionPtr, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); + + return true; +} + +bool +FHoudiniEngine::SessionSyncConnect( + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const FString& ServerHost, + const int32& ServerPort) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + // Only start a new Session if we dont already have a valid one + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(&Session)) + return true; + + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + + HAPI_ThriftServerOptions ServerOptions; + FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); + ServerOptions.autoClose = true; + ServerOptions.timeoutMs = HoudiniRuntimeSettings->AutomaticServerTimeout; + + switch (SessionType) + { + case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: + { + // Try to connect to an existing socket session first + SessionResult = FHoudiniApi::CreateThriftSocketSession( + &Session, TCHAR_TO_UTF8(*ServerHost), ServerPort); + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: + { + // Try to connect to an existing pipe session first + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + &Session, TCHAR_TO_UTF8(*ServerPipeName)); + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_None: + case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + default: + HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine Session Sync Type!!")); + bEnableSessionSync = false; + break; + } + + if (SessionResult != HAPI_RESULT_SUCCESS) + return false; + + // Enable session sync + bEnableSessionSync = true; + + // Update this session's license type + HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( + &Session, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); + + // Update the default viewport sync settings + bSyncViewport = HoudiniRuntimeSettings->bSyncViewport; + bSyncHoudiniViewport = HoudiniRuntimeSettings->bSyncHoudiniViewport; + bSyncUnrealViewport = HoudiniRuntimeSettings->bSyncUnrealViewport; + + return true; +} + +bool +FHoudiniEngine::InitializeHAPISession() +{ + // The HAPI stubs needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The Houdini API stubs have not been properly initialized.")); + return false; + } + + // We need a Valid Session + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(GetSession())) + { + HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The session is invalid.")); + return false; + } + + // Now, initialize HAPI with the new session + // We need to make sure HAPI version is correct. + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + // 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); + + // Compare defined and running versions. + if (RunningEngineMajor != HAPI_VERSION_HOUDINI_ENGINE_MAJOR + || RunningEngineMinor != HAPI_VERSION_HOUDINI_ENGINE_MINOR) + { + // Major or minor HAPI version differs, stop here + HOUDINI_LOG_ERROR( + TEXT("Starting up the Houdini Engine module failed: built and running versions do not match.")); + HOUDINI_LOG_ERROR( + TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), + HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, + RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + + // Display an error message + + // + return false; + + } + else if (RunningEngineApi != HAPI_VERSION_HOUDINI_ENGINE_API) + { + // Major/minor HAPIversions match, but only the API version differs, + // Allow the user to continue but warn him of possible instabilities + HOUDINI_LOG_WARNING( + TEXT("Starting up the Houdini Engine module: built and running versions do not match.")); + HOUDINI_LOG_WARNING( + TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), + HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, + RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + HOUDINI_LOG_WARNING( + TEXT("This could cause instabilities and crashes when using the Houdini Engine plugin")); + } + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Default CookOptions + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + + bool bUseCookingThread = true; + HAPI_Result Result = FHoudiniApi::Initialize( + &Session, + &CookOptions, + bUseCookingThread, + HoudiniRuntimeSettings->CookingThreadStackSize, + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->HoudiniEnvironmentFiles), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->OtlSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->DsoSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->ImageDsoSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->AudioDsoSearchPath)); + + if (Result == HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module.")); + } + else if (Result == HAPI_RESULT_ALREADY_INITIALIZED) + { + // Reused session? just notify the user + HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module - HAPI was already initialzed.")); + } + else + { + HOUDINI_LOG_ERROR( + TEXT("Starting up the Houdini Engine module failed: %s"), + *FHoudiniEngineUtils::GetErrorDescription(Result)); + + return false; + } + + // Let HAPI know we are running inside UE4 + FHoudiniApi::SetServerEnvString(&Session, HAPI_ENV_CLIENT_NAME, HAPI_UNREAL_CLIENT_NAME); + + if (bEnableSessionSync) + { + // Set the session sync infos if needed + UploadSessionSyncInfoToHoudini(); + + // Indicate that Session Sync is enabled + FString Notification = TEXT("Houdini Engine Session Sync enabled."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Session Sync enabled.")); + } + + return true; +} + + +void +FHoudiniEngine::OnSessionLost() +{ + // Mark the session as invalid + Session.id = -1; + Session.type = HAPI_SESSION_MAX; + bEnableSessionSync = false; + HoudiniEngineManager->StopHoudiniTicking(); + + // This indicates that we likely have lost the session due to a crash in HARS/Houdini + FString Notification = TEXT("Houdini Engine Session lost!"); + FHoudiniEngineUtils::CreateSlateNotification(Notification, 2.0, 4.0); + + HOUDINI_LOG_ERROR(TEXT("Houdini Engine Session lost! This could be caused by a crash in HARS.")); +} + +bool +FHoudiniEngine::StopSession() +{ + HAPI_Session* SessionPtr = &Session; + return StopSession(SessionPtr); +} + +bool +FHoudiniEngine::StopSession(HAPI_Session*& SessionPtr) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) + { + // SessionPtr is valid, clean up and close the session + FHoudiniApi::Cleanup(SessionPtr); + FHoudiniApi::CloseSession(SessionPtr); + } + + Session.id = -1; + Session.type = HAPI_SESSION_MAX; + bEnableSessionSync = false; + + HoudiniEngineManager->StopHoudiniTicking(); + + return true; +} + +bool +FHoudiniEngine::RestartSession() +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Starting the Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + if (!StopSession(SessionPtr)) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); + } + else + { + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + HoudiniRuntimeSettings->bStartAutomaticServer, + HoudiniRuntimeSettings->AutomaticServerTimeout, + HoudiniRuntimeSettings->SessionType, + HoudiniRuntimeSettings->ServerPipeName, + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to start the new Session")); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); + } + else + { + bSuccess = true; + } + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +bool +FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName) +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Create the Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + true, + HoudiniRuntimeSettings->AutomaticServerTimeout, + SessionType, + OverrideServerPipeName == NAME_None ? HoudiniRuntimeSettings->ServerPipeName : OverrideServerPipeName.ToString(), + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine Session")); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine session - Failed to initialize HAPI")); + } + else + { + bSuccess = true; + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +bool +FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType) +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Connecting to a Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + false, + HoudiniRuntimeSettings->AutomaticServerTimeout, + SessionType, + HoudiniRuntimeSettings->ServerPipeName, + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine Session")); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine session - Failed to initialize HAPI")); + } + else + { + bSuccess = true; + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +void +FHoudiniEngine::StartTicking() +{ + // Finish the notification and display the results + FString StatusText = TEXT("Houdini Engine session connected."); + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + + HoudiniEngineManager->StartHoudiniTicking(); +} + +void +FHoudiniEngine::StopTicking() +{ + // Finish the notification and display the results + FString StatusText = TEXT("Failed to start the Houdini Engine session..."); + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + + HoudiniEngineManager->StopHoudiniTicking(); + + HAPI_Session* SessionPtr = &Session; + StopSession(SessionPtr); +} + +bool +FHoudiniEngine::IsCookingEnabled() const +{ + return bEnableCookingGlobal; +} + +void +FHoudiniEngine::SetCookingEnabled(const bool& bInEnableCooking) +{ + bEnableCookingGlobal = bInEnableCooking; +} + +bool +FHoudiniEngine::GetFirstSessionCreated() const +{ + return bFirstSessionCreated; +} + +bool +FHoudiniEngine::CreateTaskSlateNotification( + const FText& InText, const bool& bForceNow, const float& NotificationExpire, const float& NotificationFadeOut) +{ +#if WITH_EDITOR + static double NotificationUpdateFrequency = 2.0f; + + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return false; + + if (!bForceNow) + { + if ((FPlatformTime::Seconds() - HapiNotificationStarted) < NotificationUpdateFrequency) + return false; + } + + if (!NotificationPtr.IsValid()) + { + FNotificationInfo Info(InText); + Info.bFireAndForget = false; + Info.FadeOutDuration = NotificationFadeOut; + Info.ExpireDuration = NotificationExpire; + TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + /* + if (!IsPIEActive()) + */ + + NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + } +#endif + + return true; +} + +bool +FHoudiniEngine::UpdateTaskSlateNotification(const FText& InText) +{ +#if WITH_EDITOR + // task is till running + // Just update the slate notification + TSharedPtr NotificationItem = NotificationPtr.Pin(); + if (NotificationItem.IsValid()) + NotificationItem->SetText(InText); +#endif + + return true; +} + +bool +FHoudiniEngine::FinishTaskSlateNotification(const FText& InText) +{ +#if WITH_EDITOR + if (NotificationPtr.IsValid()) + { + TSharedPtr NotificationItem = NotificationPtr.Pin(); + if (NotificationItem.IsValid()) + { + NotificationItem->SetText(InText); + NotificationItem->ExpireAndFadeout(); + + NotificationPtr.Reset(); + } + } +#endif + + return true; +} + +void +FHoudiniEngine::UpdateSessionSyncInfoFromHoudini() +{ + if (!bEnableSessionSync) + return; + + // Set the Session Sync settings to Houdini + HAPI_SessionSyncInfo SessionSyncInfo; + //FHoudiniApi::SessionSyncInfo_Create(&SessionSyncInfo); + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetSessionSyncInfo(&Session, &SessionSyncInfo)) + { + bCookUsingHoudiniTime = SessionSyncInfo.cookUsingHoudiniTime; + bSyncViewport = SessionSyncInfo.syncViewport; + } +} + +void +FHoudiniEngine::UploadSessionSyncInfoToHoudini() +{ + // No need to set sessionsync info if we're not using session sync + if (!bEnableSessionSync) + return; + + // Set the Session Sync settings to Houdini + HAPI_SessionSyncInfo SessionSyncInfo; + SessionSyncInfo.cookUsingHoudiniTime = bCookUsingHoudiniTime; + SessionSyncInfo.syncViewport = bSyncViewport; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetSessionSyncInfo(&Session, &SessionSyncInfo)) + HOUDINI_LOG_WARNING(TEXT("Failed to set the SessionSync Infos.")); +} + +void +FHoudiniEngine::StartPDGCommandlet() +{ + if (HoudiniEngineManager) + HoudiniEngineManager->StartPDGCommandlet(); +} + +void +FHoudiniEngine::StopPDGCommandlet() +{ + if (HoudiniEngineManager) + HoudiniEngineManager->StopPDGCommandlet(); +} + +bool +FHoudiniEngine::IsPDGCommandletRunningOrConnected() +{ + if (HoudiniEngineManager) + return HoudiniEngineManager->IsPDGCommandletRunningOrConnected(); + return false; +} + +EHoudiniBGEOCommandletStatus +FHoudiniEngine::GetPDGCommandletStatus() +{ + if (HoudiniEngineManager) + return HoudiniEngineManager->GetPDGCommandletStatus(); + return EHoudiniBGEOCommandletStatus::NotStarted; +} + +void +FHoudiniEngine::UnregisterPostEngineInitCallback() +{ + if (PostEngineInitCallback.IsValid()) + FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitCallback); +} + +bool FHoudiniEngine::IsSyncWithHoudiniCookEnabled() const +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + return HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bSyncWithHoudiniCook : false; +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.h b/Source/HoudiniEngine/Private/HoudiniEngine.h index 4b7a1f4e4..6fef65818 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.h +++ b/Source/HoudiniEngine/Private/HoudiniEngine.h @@ -1,308 +1,308 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineTaskInfo.h" -#include "HoudiniRuntimeSettings.h" - -#include "Modules/ModuleInterface.h" - -class FRunnableThread; -class FHoudiniEngineScheduler; -class FHoudiniEngineManager; -class UHoudiniAssetComponent; -class UStaticMesh; -class UMaterial; - -struct FSlateDynamicImageBrush; - -enum class EHoudiniBGEOCommandletStatus : uint8; - -// Not using the IHoudiniEngine interface for now -class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface -{ - public: - - FHoudiniEngine(); - - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // Return singleton instance of Houdini Engine, used internally. - static FHoudiniEngine & Get(); - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // Return the location of the currently loaded LibHAPI - virtual const FString & GetLibHAPILocation() const; - - // Session accessor - virtual const HAPI_Session* GetSession() const; - - // Default cook options - static HAPI_CookOptions GetDefaultCookOptions(); - - // Creates a new session - bool StartSession( - HAPI_Session*& SessionPtr, - const bool& StartAutomaticServer, - const float& AutomaticServerTimeout, - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const int32& ServerPort, - const FString& ServerHost); - - // Stop the current session if it is valid - bool StopSession(HAPI_Session*& SessionPtr); - - // Creates a session sync session - bool SessionSyncConnect( - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const FString& ServerHost, - const int32& ServerPort); - - // Stops the current session - bool StopSession(); - // Stops, then creates a new session - bool RestartSession(); - // Creates a session, start HARS - bool CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName=NAME_None); - // Connect to an existing HE session - bool ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType); - - // Starts the HoudiniEngineManager ticking - void StartTicking(); - // Stops the HoudiniEngineManager ticking and invalidate the session - void StopTicking(); - - // Initialize HAPI - bool InitializeHAPISession(); - - // Indicate to the plugin that the session is now invalid (HAPI has likely crashed...) - void OnSessionLost(); - - bool CreateTaskSlateNotification( - const FText& InText, - const bool& bForceNow = false, - const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, - const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); - - bool UpdateTaskSlateNotification(const FText& InText); - bool FinishTaskSlateNotification(const FText& InText); - - void SetHapiNotificationStartedTime(const double& InTime) { HapiNotificationStarted = InTime; }; - - // Register task for execution. - virtual void AddTask(const FHoudiniEngineTask & InTask); - // Register task info. - virtual void AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo); - // Remove task info. - virtual void RemoveTaskInfo(const FGuid& InHapiGUID); - // Remove task info. - virtual bool RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo); - // Register asset to the manager - //virtual void AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC); - - // Indicates whether or not cooking is currently enabled - bool IsCookingEnabled() const; - // Sets whether or not cooking is currently enabled - void SetCookingEnabled(const bool& bInEnableCooking); - - // Check if we need to refresh UI when cooking is paused - bool HasUIFinishRefreshingWhenPausingCooking() const { return UIRefreshCountWhenPauseCooking <= 0; }; - - // Reset number of registered HACs when cooking is paused - void SetUIRefreshCountWhenPauseCooking(const int32& bInCount) { UIRefreshCountWhenPauseCooking = bInCount; }; - // Reduce the count by 1 when an HAC UI is refreshed when cooking is paused - void RefreshUIDisplayedWhenPauseCooking() { UIRefreshCountWhenPauseCooking -= 1; }; - - // Indicates whether or not the first attempt to create a Houdini session was made - bool GetFirstSessionCreated() const; - // Sets whether or not the first attempt to create a Houdini session was made - void SetFirstSessionCreated(const bool& bInStarted) { bFirstSessionCreated = bInStarted; }; - - bool IsSessionSyncEnabled() const { return bEnableSessionSync; }; - - bool IsSyncWithHoudiniCookEnabled() const; - - bool IsCookUsingHoudiniTimeEnabled() const { return bCookUsingHoudiniTime; }; - - bool IsSyncViewportEnabled() const { return bSyncViewport; }; - - bool IsSyncHoudiniViewportEnabled() const { return bSyncHoudiniViewport; }; - - bool IsSyncUnrealViewportEnabled() const { return bSyncUnrealViewport; }; - - // Helper function to update our session sync infos from Houdini's - void UpdateSessionSyncInfoFromHoudini(); - - // Helper function to update Houdini's Session sync infos from ours - void UploadSessionSyncInfoToHoudini(); - - // Sets whether or not viewport sync is enabled - void SetSyncViewportEnabled(const bool& bInSync) { bSyncViewport = bInSync; }; - // Sets whether or not we want to sync the houdini viewport to unreal's - void SetSyncHoudiniViewportEnabled(const bool& bInSync) { bSyncHoudiniViewport = bInSync; }; - // Sets whether or not we want to sync unreal's viewport to Houdini's - void SetSyncUnrealViewportEnabled(const bool& bInSync) { bSyncUnrealViewport = bInSync; }; - - // Returns the default Houdini Logo Static Mesh - virtual TWeakObjectPtr GetHoudiniLogoStaticMesh() const { return HoudiniLogoStaticMesh; }; - - // Returns either the default Houdini material or the default template material - virtual TWeakObjectPtr GetHoudiniDefaultMaterial(const bool& bIsTemplate) const { return bIsTemplate ? HoudiniTemplateMaterial : HoudiniDefaultMaterial; }; - - // Returns the default Houdini material - virtual TWeakObjectPtr GetHoudiniDefaultMaterial() const { return HoudiniDefaultMaterial; }; - // Returns the default template Houdini material - virtual TWeakObjectPtr GetHoudiniTemplatedMaterial() const { return HoudiniTemplateMaterial; }; - // Returns a shared Ptr to the houdini logo - TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; - // Returns a shared Ptr to the houdini engine logo - TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; - - // Returns the default Houdini reference mesh - virtual TWeakObjectPtr GetHoudiniDefaultReferenceMesh() const { return HoudiniDefaultReferenceMesh; }; - // Returns the default Houdini reference mesh material - virtual TWeakObjectPtr GetHoudiniDefaultReferenceMeshMaterial() const { return HoudiniDefaultReferenceMeshMaterial; }; - - const HAPI_License GetLicenseType() const { return LicenseType; }; - - const bool IsLicenseIndie() const { return (LicenseType == HAPI_LICENSE_HOUDINI_ENGINE_INDIE || LicenseType == HAPI_LICENSE_HOUDINI_INDIE); }; - - // Session Sync ProcHandle accessor - FProcHandle GetHESSProcHandle() const { return HESS_ProcHandle; }; - void SetHESSProcHandle(const FProcHandle& InProcHandle) { HESS_ProcHandle = InProcHandle; }; - - void StartPDGCommandlet(); - - void StopPDGCommandlet(); - - bool IsPDGCommandletRunningOrConnected(); - - EHoudiniBGEOCommandletStatus GetPDGCommandletStatus(); - - FHoudiniEngineManager* GetHoudiniEngineManager() { return HoudiniEngineManager; } - - const FHoudiniEngineManager* GetHoudiniEngineManager() const { return HoudiniEngineManager; } - - void UnregisterPostEngineInitCallback(); - - private: - - // Singleton instance of Houdini Engine. - static FHoudiniEngine * HoudiniEngineInstance; - - // Location of libHAPI binary. - FString LibHAPILocation; - - // The Houdini Engine session. - HAPI_Session Session; - - // The type of HE license used by the current session - HAPI_License LicenseType; - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // Map of task statuses. - TMap TaskInfos; - - // Thread used to execute the scheduler. - FRunnableThread * HoudiniEngineSchedulerThread; - // Scheduler used to schedule HAPI instantiation and cook tasks. - FHoudiniEngineScheduler * HoudiniEngineScheduler; - - // Thread used to execute the manager. - FRunnableThread * HoudiniEngineManagerThread; - // Scheduler used to monitor and process Houdini Asset Components - FHoudiniEngineManager * HoudiniEngineManager; - - // Process Handle for session sync - FProcHandle HESS_ProcHandle; - - // Is set to true when mismatch between defined and running HAPI versions is detected. - //bool bHAPIVersionMismatch; - - // Global cooking flag, used to pause HEngine while using the editor - bool bEnableCookingGlobal; - // Counter of HACs that need to be refreshed when pause cooking - int32 UIRefreshCountWhenPauseCooking; - - // Indicates that the first attempt to create a session has been done - // This is to delay the first "automatic" session creation for the first cook - // or instantiation rather than when the module started. - bool bFirstSessionCreated; - - // Indicates if the current session is a SessionSync one - bool bEnableSessionSync; - - // If true and we're in SessionSync, keeps the assets on the plugin side synchronized with changes on the Houdini side. - //bool bSyncWithHoudiniCook; - - // If true and we're in SessionSync, use the Houdini Timeline time to cook assets. - bool bCookUsingHoudiniTime; - - // If true and we're in Session Sync, the Houdini and Unreal viewport will be synchronized. - bool bSyncViewport; - // If true and we're in Session Sync, the Houdini viewport will be synchronized to Unreal's. - bool bSyncHoudiniViewport; - // If true and we're in Session Sync, the Unreal viewport will be synchronized to Houdini's. - bool bSyncUnrealViewport; - - // Static mesh used for Houdini logo rendering. - TWeakObjectPtr HoudiniLogoStaticMesh; - - // Material used as default material. - TWeakObjectPtr HoudiniDefaultMaterial; - - // Material used as default template material. - TWeakObjectPtr HoudiniTemplateMaterial; - - // Houdini logo brush. - TSharedPtr HoudiniLogoBrush; - // Houdini logo brush. - TSharedPtr HoudiniEngineLogoBrush; - - // Static mesh used for default mesh reference - TWeakObjectPtr HoudiniDefaultReferenceMesh; - - // Material used for default mesh reference - TWeakObjectPtr HoudiniDefaultReferenceMeshMaterial; - - FDelegateHandle PostEngineInitCallback; - -#if WITH_EDITOR - /** Notification used by this component. **/ - TWeakPtr NotificationPtr; - /** Used to delay notification updates for HAPI asynchronous work. **/ - double HapiNotificationStarted; -#endif +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniRuntimeSettings.h" + +#include "Modules/ModuleInterface.h" + +class FRunnableThread; +class FHoudiniEngineScheduler; +class FHoudiniEngineManager; +class UHoudiniAssetComponent; +class UStaticMesh; +class UMaterial; + +struct FSlateDynamicImageBrush; + +enum class EHoudiniBGEOCommandletStatus : uint8; + +// Not using the IHoudiniEngine interface for now +class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface +{ + public: + + FHoudiniEngine(); + + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // Return singleton instance of Houdini Engine, used internally. + static FHoudiniEngine & Get(); + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // Return the location of the currently loaded LibHAPI + virtual const FString & GetLibHAPILocation() const; + + // Session accessor + virtual const HAPI_Session* GetSession() const; + + // Default cook options + static HAPI_CookOptions GetDefaultCookOptions(); + + // Creates a new session + bool StartSession( + HAPI_Session*& SessionPtr, + const bool& StartAutomaticServer, + const float& AutomaticServerTimeout, + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const int32& ServerPort, + const FString& ServerHost); + + // Stop the current session if it is valid + bool StopSession(HAPI_Session*& SessionPtr); + + // Creates a session sync session + bool SessionSyncConnect( + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const FString& ServerHost, + const int32& ServerPort); + + // Stops the current session + bool StopSession(); + // Stops, then creates a new session + bool RestartSession(); + // Creates a session, start HARS + bool CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName=NAME_None); + // Connect to an existing HE session + bool ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType); + + // Starts the HoudiniEngineManager ticking + void StartTicking(); + // Stops the HoudiniEngineManager ticking and invalidate the session + void StopTicking(); + + // Initialize HAPI + bool InitializeHAPISession(); + + // Indicate to the plugin that the session is now invalid (HAPI has likely crashed...) + void OnSessionLost(); + + bool CreateTaskSlateNotification( + const FText& InText, + const bool& bForceNow = false, + const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, + const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); + + bool UpdateTaskSlateNotification(const FText& InText); + bool FinishTaskSlateNotification(const FText& InText); + + void SetHapiNotificationStartedTime(const double& InTime) { HapiNotificationStarted = InTime; }; + + // Register task for execution. + virtual void AddTask(const FHoudiniEngineTask & InTask); + // Register task info. + virtual void AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo); + // Remove task info. + virtual void RemoveTaskInfo(const FGuid& InHapiGUID); + // Remove task info. + virtual bool RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo); + // Register asset to the manager + //virtual void AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC); + + // Indicates whether or not cooking is currently enabled + bool IsCookingEnabled() const; + // Sets whether or not cooking is currently enabled + void SetCookingEnabled(const bool& bInEnableCooking); + + // Check if we need to refresh UI when cooking is paused + bool HasUIFinishRefreshingWhenPausingCooking() const { return UIRefreshCountWhenPauseCooking <= 0; }; + + // Reset number of registered HACs when cooking is paused + void SetUIRefreshCountWhenPauseCooking(const int32& bInCount) { UIRefreshCountWhenPauseCooking = bInCount; }; + // Reduce the count by 1 when an HAC UI is refreshed when cooking is paused + void RefreshUIDisplayedWhenPauseCooking() { UIRefreshCountWhenPauseCooking -= 1; }; + + // Indicates whether or not the first attempt to create a Houdini session was made + bool GetFirstSessionCreated() const; + // Sets whether or not the first attempt to create a Houdini session was made + void SetFirstSessionCreated(const bool& bInStarted) { bFirstSessionCreated = bInStarted; }; + + bool IsSessionSyncEnabled() const { return bEnableSessionSync; }; + + bool IsSyncWithHoudiniCookEnabled() const; + + bool IsCookUsingHoudiniTimeEnabled() const { return bCookUsingHoudiniTime; }; + + bool IsSyncViewportEnabled() const { return bSyncViewport; }; + + bool IsSyncHoudiniViewportEnabled() const { return bSyncHoudiniViewport; }; + + bool IsSyncUnrealViewportEnabled() const { return bSyncUnrealViewport; }; + + // Helper function to update our session sync infos from Houdini's + void UpdateSessionSyncInfoFromHoudini(); + + // Helper function to update Houdini's Session sync infos from ours + void UploadSessionSyncInfoToHoudini(); + + // Sets whether or not viewport sync is enabled + void SetSyncViewportEnabled(const bool& bInSync) { bSyncViewport = bInSync; }; + // Sets whether or not we want to sync the houdini viewport to unreal's + void SetSyncHoudiniViewportEnabled(const bool& bInSync) { bSyncHoudiniViewport = bInSync; }; + // Sets whether or not we want to sync unreal's viewport to Houdini's + void SetSyncUnrealViewportEnabled(const bool& bInSync) { bSyncUnrealViewport = bInSync; }; + + // Returns the default Houdini Logo Static Mesh + virtual TWeakObjectPtr GetHoudiniLogoStaticMesh() const { return HoudiniLogoStaticMesh; }; + + // Returns either the default Houdini material or the default template material + virtual TWeakObjectPtr GetHoudiniDefaultMaterial(const bool& bIsTemplate) const { return bIsTemplate ? HoudiniTemplateMaterial : HoudiniDefaultMaterial; }; + + // Returns the default Houdini material + virtual TWeakObjectPtr GetHoudiniDefaultMaterial() const { return HoudiniDefaultMaterial; }; + // Returns the default template Houdini material + virtual TWeakObjectPtr GetHoudiniTemplatedMaterial() const { return HoudiniTemplateMaterial; }; + // Returns a shared Ptr to the houdini logo + TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; + // Returns a shared Ptr to the houdini engine logo + TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; + + // Returns the default Houdini reference mesh + virtual TWeakObjectPtr GetHoudiniDefaultReferenceMesh() const { return HoudiniDefaultReferenceMesh; }; + // Returns the default Houdini reference mesh material + virtual TWeakObjectPtr GetHoudiniDefaultReferenceMeshMaterial() const { return HoudiniDefaultReferenceMeshMaterial; }; + + const HAPI_License GetLicenseType() const { return LicenseType; }; + + const bool IsLicenseIndie() const { return (LicenseType == HAPI_LICENSE_HOUDINI_ENGINE_INDIE || LicenseType == HAPI_LICENSE_HOUDINI_INDIE); }; + + // Session Sync ProcHandle accessor + FProcHandle GetHESSProcHandle() const { return HESS_ProcHandle; }; + void SetHESSProcHandle(const FProcHandle& InProcHandle) { HESS_ProcHandle = InProcHandle; }; + + void StartPDGCommandlet(); + + void StopPDGCommandlet(); + + bool IsPDGCommandletRunningOrConnected(); + + EHoudiniBGEOCommandletStatus GetPDGCommandletStatus(); + + FHoudiniEngineManager* GetHoudiniEngineManager() { return HoudiniEngineManager; } + + const FHoudiniEngineManager* GetHoudiniEngineManager() const { return HoudiniEngineManager; } + + void UnregisterPostEngineInitCallback(); + + private: + + // Singleton instance of Houdini Engine. + static FHoudiniEngine * HoudiniEngineInstance; + + // Location of libHAPI binary. + FString LibHAPILocation; + + // The Houdini Engine session. + HAPI_Session Session; + + // The type of HE license used by the current session + HAPI_License LicenseType; + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // Map of task statuses. + TMap TaskInfos; + + // Thread used to execute the scheduler. + FRunnableThread * HoudiniEngineSchedulerThread; + // Scheduler used to schedule HAPI instantiation and cook tasks. + FHoudiniEngineScheduler * HoudiniEngineScheduler; + + // Thread used to execute the manager. + FRunnableThread * HoudiniEngineManagerThread; + // Scheduler used to monitor and process Houdini Asset Components + FHoudiniEngineManager * HoudiniEngineManager; + + // Process Handle for session sync + FProcHandle HESS_ProcHandle; + + // Is set to true when mismatch between defined and running HAPI versions is detected. + //bool bHAPIVersionMismatch; + + // Global cooking flag, used to pause HEngine while using the editor + bool bEnableCookingGlobal; + // Counter of HACs that need to be refreshed when pause cooking + int32 UIRefreshCountWhenPauseCooking; + + // Indicates that the first attempt to create a session has been done + // This is to delay the first "automatic" session creation for the first cook + // or instantiation rather than when the module started. + bool bFirstSessionCreated; + + // Indicates if the current session is a SessionSync one + bool bEnableSessionSync; + + // If true and we're in SessionSync, keeps the assets on the plugin side synchronized with changes on the Houdini side. + //bool bSyncWithHoudiniCook; + + // If true and we're in SessionSync, use the Houdini Timeline time to cook assets. + bool bCookUsingHoudiniTime; + + // If true and we're in Session Sync, the Houdini and Unreal viewport will be synchronized. + bool bSyncViewport; + // If true and we're in Session Sync, the Houdini viewport will be synchronized to Unreal's. + bool bSyncHoudiniViewport; + // If true and we're in Session Sync, the Unreal viewport will be synchronized to Houdini's. + bool bSyncUnrealViewport; + + // Static mesh used for Houdini logo rendering. + TWeakObjectPtr HoudiniLogoStaticMesh; + + // Material used as default material. + TWeakObjectPtr HoudiniDefaultMaterial; + + // Material used as default template material. + TWeakObjectPtr HoudiniTemplateMaterial; + + // Houdini logo brush. + TSharedPtr HoudiniLogoBrush; + // Houdini logo brush. + TSharedPtr HoudiniEngineLogoBrush; + + // Static mesh used for default mesh reference + TWeakObjectPtr HoudiniDefaultReferenceMesh; + + // Material used for default mesh reference + TWeakObjectPtr HoudiniDefaultReferenceMeshMaterial; + + FDelegateHandle PostEngineInitCallback; + +#if WITH_EDITOR + /** Notification used by this component. **/ + TWeakPtr NotificationPtr; + /** Used to delay notification updates for HAPI asynchronous work. **/ + double HapiNotificationStarted; +#endif }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp b/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp index c5fdea932..8a63839cc 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp @@ -1,25 +1,25 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h b/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h index 05b5eb527..bdac5a8a6 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h @@ -1,27 +1,27 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + #pragma once \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp index 2d94a68a0..7a76a16e4 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp @@ -1,1550 +1,1550 @@ - -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineManager.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniParameterTranslator.h" -#include "HoudiniPDGManager.h" -#include "HoudiniInputTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniHandleTranslator.h" -#include "HoudiniSplineTranslator.h" -#include "Misc/MessageDialog.h" -#include "Misc/ScopedSlowTask.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "EditorViewportClient.h" - #include "Kismet/KismetMathLibrary.h" - - //#include "UnrealEd.h" - #include "UnrealEdGlobals.h" - #include "Editor/UnrealEdEngine.h" - #include "IPackageAutoSaver.h" -#endif - -const float -FHoudiniEngineManager::TickTimerDelay = 0.01f; - -FHoudiniEngineManager::FHoudiniEngineManager() - : CurrentIndex(0) - , ComponentCount(0) - , bMustStopTicking(false) - , SyncedHoudiniViewportPivotPosition(FVector::ZeroVector) - , SyncedHoudiniViewportQuat(FQuat::Identity) - , SyncedHoudiniViewportOffset(0.0f) - , SyncedUnrealViewportPosition(FVector::ZeroVector) - , SyncedUnrealViewportRotation(FRotator::ZeroRotator) - , SyncedUnrealViewportLookatPosition(FVector::ZeroVector) - , ZeroOffsetValue(0.f) - , bOffsetZeroed(false) -{ - -} - -FHoudiniEngineManager::~FHoudiniEngineManager() -{ - PDGManager.StopBGEOCommandletAndEndpoint(); -} - -void -FHoudiniEngineManager::StartHoudiniTicking() -{ - // If we have no timer delegate spawned, spawn one. - if (!TimerDelegateProcess.IsBound() && GEditor) - { - TimerDelegateProcess = FTimerDelegate::CreateRaw(this, &FHoudiniEngineManager::Tick); - GEditor->GetTimerManager()->SetTimer(TimerHandleProcess, TimerDelegateProcess, TickTimerDelay, true); - - // Grab current time for delayed notification. - FHoudiniEngine::Get().SetHapiNotificationStartedTime(FPlatformTime::Seconds()); - } -} - -void -FHoudiniEngineManager::StopHoudiniTicking() -{ - if (TimerDelegateProcess.IsBound() && GEditor) - { - if (IsInGameThread()) - { - GEditor->GetTimerManager()->ClearTimer(TimerHandleProcess); - TimerDelegateProcess.Unbind(); - - // Reset time for delayed notification. - FHoudiniEngine::Get().SetHapiNotificationStartedTime(0.0); - - bMustStopTicking = false; - } - else - { - // We can't stop ticking now as we're not in the game Thread, - // and accessing the timer would crash, indicate that we want to stop ticking asap - // This can happen when loosing a session due to a Houdini crash - bMustStopTicking = true; - } - } -} - -void -FHoudiniEngineManager::Tick() -{ - EnableEditorAutoSave(nullptr); - - if (bMustStopTicking) - { - // Ticking should be stopped immediately - StopHoudiniTicking(); - return; - } - - // Process the current component if possible - while (true) - { - UHoudiniAssetComponent * CurrentComponent = nullptr; - if (FHoudiniEngineRuntime::IsInitialized()) - { - FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); - - //FScopeLock ScopeLock(&CriticalSection); - ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); - - // No work to be done - if (ComponentCount <= 0) - break; - - // Wrap around if needed - if (CurrentIndex >= ComponentCount) - CurrentIndex = 0; - - CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(CurrentIndex); - CurrentIndex++; - } - - if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) - { - // Invalid component, do not process - break; - } - else if (CurrentComponent->IsPendingKill() - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) - { - // Component being deleted, do not process - break; - } - - if (!CurrentComponent->IsFullyLoaded()) - { - // Let the component figure out whether it's fully loaded or not. - CurrentComponent->HoudiniEngineTick(); - if (!CurrentComponent->IsFullyLoaded()) - continue; // We need to wait some more. - } - - if (!CurrentComponent->IsValidComponent()) - { - // This component is no longer valid. Prevent it from being processed, and remove it. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); - continue; - } - - // We don't want to the template component processing to trigger session creation - if (CurrentComponent->GetAssetState() == EHoudiniAssetState::ProcessTemplate) - { - if (CurrentComponent->IsTemplate() && !CurrentComponent->HasOpenEditor()) - { - // This component template no longer has an open editor and can be deregistered. - // TODO: Replace this polling mechanism with an "On Asset Closed" event if we - // can find one that actually works. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); - continue; - } - - if (CurrentComponent->NeedBlueprintStructureUpdate()) - { - CurrentComponent->OnBlueprintStructureModified(); - } - - if (CurrentComponent->NeedBlueprintUpdate()) - { - CurrentComponent->OnBlueprintModified(); - } - - if (FHoudiniEngine::Get().IsCookingEnabled()) - { - // Only process component template parameter updates when cooking is enabled. - if (CurrentComponent->NeedUpdateParameters() || CurrentComponent->NeedUpdateInputs()) - { - CurrentComponent->OnTemplateParametersChanged(); - } - } - - if (CurrentComponent->NeedOutputUpdate()) - { - // TODO: Transfer template output changes over to the preview instance. - } - - break; - } - - // See if we should start the default "first" session - if(!FHoudiniEngine::Get().GetSession() && !FHoudiniEngine::Get().GetFirstSessionCreated()) - { - // Only try to start the default session if we have an "active" HAC - if (CurrentComponent->GetAssetState() == EHoudiniAssetState::PreInstantiation - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Instantiating - || CurrentComponent->GetAssetState() == EHoudiniAssetState::PreCook - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Cooking) - { - FString StatusText = TEXT("Initializing Houdini Engine..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // We want to yield for a bit. - //FPlatformProcess::Sleep(0.5f); - - // Indicates that we've tried to start the session once no matter if it failed or succeed - FHoudiniEngine::Get().SetFirstSessionCreated(true); - - // Attempt to restart the session - if (!FHoudiniEngine::Get().RestartSession()) - { - // We failed to start the session - // Stop ticking until it's manually restarted - StopHoudiniTicking(); - - StatusText = TEXT("Houdini Engine failed to initialize."); - } - else - { - StatusText = TEXT("Houdini Engine successfully initialized."); - } - - // Finish the notification and display the results - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - } - } - - // Process the component - // try to catch (apache::thrift::transport::TTransportException * e) for session loss? - ProcessComponent(CurrentComponent); - break; - } - - // Handle Asset delete - if (FHoudiniEngineRuntime::IsInitialized()) - { - int32 PendingDeleteCount = FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteCount(); - for (int32 DeleteIdx = PendingDeleteCount - 1; DeleteIdx >= 0; DeleteIdx--) - { - HAPI_NodeId NodeIdToDelete = (HAPI_NodeId)FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteAt(DeleteIdx); - FGuid HapiDeletionGUID; - bool bShouldDeleteParent = FHoudiniEngineRuntime::Get().IsParentNodePendingDelete(NodeIdToDelete); - if (StartTaskAssetDelete(NodeIdToDelete, HapiDeletionGUID, bShouldDeleteParent)) - { - FHoudiniEngineRuntime::Get().RemoveNodeIdPendingDeleteAt(DeleteIdx); - if (bShouldDeleteParent) - FHoudiniEngineRuntime::Get().RemoveParentNodePendingDelete(NodeIdToDelete); - } - } - } - - // Update PDG Contexts and asset link if needed - PDGManager.Update(); - - // Session Sync Updates - if (FHoudiniEngine::Get().IsSessionSyncEnabled()) - { - // See if the session sync settings have changed on the houdini side, update ours if they did - FHoudiniEngine::Get().UpdateSessionSyncInfoFromHoudini(); -#if WITH_EDITOR - // Update the Houdini viewport from unreal if needed - if (FHoudiniEngine::Get().IsSyncViewportEnabled()) - { - // Sync the Houdini viewport to Unreal - if (!SyncHoudiniViewportToUnreal()) - { - // If the unreal viewport hasnt changed, - // See if we need to sync the Unreal viewport from Houdini's - SyncUnrealViewportToHoudini(); - } - } -#endif - } - else - { - // reset zero offset variables when session sync is off - if (ZeroOffsetValue != 0.f) - ZeroOffsetValue = 0.f; - - if (bOffsetZeroed) - bOffsetZeroed = false; - } -} - -void -FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - // No need to process component not tied to an asset - if (!HAC->GetHoudiniAsset()) - return; - - // If cooking is paused, stay in the current state until cooking's resumed - if (!FHoudiniEngine::Get().IsCookingEnabled()) - { - // We can only handle output updates - if (HAC->GetAssetState() == EHoudiniAssetState::None && HAC->NeedOutputUpdate()) - { - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Refresh UI when pause cooking - if (!FHoudiniEngine::Get().HasUIFinishRefreshingWhenPausingCooking()) - { - // Trigger a details panel update if the Houdini asset actor is selected - if (HAC->IsOwnerSelected()) - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // Finished refreshing UI of one HDA. - FHoudiniEngine::Get().RefreshUIDisplayedWhenPauseCooking(); - } - - // Prevent any other state change to happen - return; - } - - switch (HAC->GetAssetState()) - { - case EHoudiniAssetState::NeedInstantiation: - { - // Do nothing unless the HAC has been updated - if (HAC->NeedUpdate()) - { - HAC->OnPrePreInstantiation(); - HAC->bForceNeedUpdate = false; - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::PreInstantiation; - } - else if (HAC->NeedOutputUpdate()) - { - // Output updates do not recquire the HDA to be instantiated - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Update world input if we have any - FHoudiniInputTranslator::UpdateWorldInputs(HAC); - - break; - } - - case EHoudiniAssetState::PreInstantiation: - { - // Only proceed forward if we don't need to wait for our input HoudiniAssets to finish cooking/instantiating - if (HAC->NeedsToWaitForInputHoudiniAssets()) - break; - - FGuid TaskGuid; - UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (StartTaskAssetInstantiation(HoudiniAsset, HAC->GetDisplayName(), TaskGuid)) - { - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::Instantiating; - //HAC->AssetStateResult = EHoudiniAssetStateResult::None; - - // Update the Task GUID - HAC->HapiGUID = TaskGuid; - } - else - { - // If we couldnt instantiate the asset - // Change the state to NeedInstantiating - HAC->AssetState = EHoudiniAssetState::NeedInstantiation; - //HAC->AssetStateResult = EHoudiniAssetStateResult::None; - } - break; - } - - case EHoudiniAssetState::Instantiating: - { - EHoudiniAssetState NewState = EHoudiniAssetState::Instantiating; - if (UpdateInstantiating(HAC, NewState)) - { - // We need to update the HAC's state - HAC->AssetState = NewState; - EnableEditorAutoSave(HAC); - } - else - { - DisableEditorAutoSave(HAC); - } - break; - } - - case EHoudiniAssetState::PreCook: - { - // Only proceed forward if we don't need to wait for our input - // HoudiniAssets to finish cooking/instantiating - if (HAC->NeedsToWaitForInputHoudiniAssets()) - break; - - HAC->OnPrePreCook(); - // Update all the HAPI nodes, parameters, inputs etc... - PreCook(HAC); - HAC->OnPostPreCook(); - - // Create a Cooking task only if necessary - bool bCookStarted = false; - if (IsCookingEnabledForHoudiniAsset(HAC)) - { - FGuid TaskGUID = HAC->GetHapiGUID(); - if ( StartTaskAssetCooking(HAC->GetAssetId(), HAC->GetDisplayName(), TaskGUID) ) - { - // Updates the HAC's state - HAC->AssetState = EHoudiniAssetState::Cooking; - HAC->HapiGUID = TaskGUID; - bCookStarted = true; - } - } - - if(!bCookStarted) - { - // Just refresh editor properties? - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // TODO: Check! update state? - HAC->AssetState = EHoudiniAssetState::None; - } - break; - } - - case EHoudiniAssetState::Cooking: - { - EHoudiniAssetState NewState = EHoudiniAssetState::Cooking; - bool state = UpdateCooking(HAC, NewState); - if (state) - { - // We need to update the HAC's state - HAC->AssetState = NewState; - EnableEditorAutoSave(HAC); - } - else - { - DisableEditorAutoSave(HAC); - } - break; - } - - case EHoudiniAssetState::PostCook: - { - // Handle PostCook - EHoudiniAssetState NewState = EHoudiniAssetState::None; - bool bSuccess = HAC->bLastCookSuccess; - HAC->OnPreOutputProcessing(); - if (PostCook(HAC, bSuccess, HAC->GetAssetId())) - { - // Cook was successful, process the results - NewState = EHoudiniAssetState::PreProcess; - } - else - { - // Cook failed, skip output processing - NewState = EHoudiniAssetState::None; - } - HAC->AssetState = NewState; - break; - } - - case EHoudiniAssetState::PreProcess: - { - StartTaskAssetProcess(HAC); - break; - } - - case EHoudiniAssetState::Processing: - { - UpdateProcess(HAC); - - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - - HAC->OnPostOutputProcessing(); - FHoudiniEngineUtils::UpdateBlueprintEditor(HAC); - break; - } - - case EHoudiniAssetState::None: - { - // Do nothing unless the HAC has been updated - if (HAC->NeedUpdate()) - { - HAC->bForceNeedUpdate = false; - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::PreCook; - } - else if (HAC->NeedTransformUpdate()) - { - FHoudiniEngineUtils::UploadHACTransform(HAC); - } - else if (HAC->NeedOutputUpdate()) - { - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Update world inputs if we have any - FHoudiniInputTranslator::UpdateWorldInputs(HAC); - - // See if we need to get an update from Session Sync - if(FHoudiniEngine::Get().IsSessionSyncEnabled() - && FHoudiniEngine::Get().IsSyncWithHoudiniCookEnabled() - && HAC->AssetState == EHoudiniAssetState::None) - { - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - if (CookCount >= 0 && CookCount != HAC->GetAssetCookCount()) - { - // The cook count has changed on the Houdini side, - // this indicates that the user has changed something in Houdini so we need to trigger an update - HAC->AssetState = EHoudiniAssetState::PreCook; - } - } - break; - } - - case EHoudiniAssetState::NeedRebuild: - { - StartTaskAssetRebuild(HAC->AssetId, HAC->HapiGUID); - - HAC->MarkAsNeedCook(); - HAC->AssetState = EHoudiniAssetState::PreInstantiation; - break; - } - - case EHoudiniAssetState::NeedDelete: - { - FGuid HapiDeletionGUID; - StartTaskAssetDelete(HAC->GetAssetId(), HapiDeletionGUID, true); - //HAC->AssetId = -1; - - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::Deleting; - break; - } - - case EHoudiniAssetState::Deleting: - { - break; - } - } -} - - - -bool -FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID) -{ - // Make sure we have a valid session before attempting anything - if (!FHoudiniEngine::Get().GetSession()) - return false; - - OutTaskGUID.Invalidate(); - - // Load the HDA file - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - null or invalid Houdini Asset.")); - return false; - } - - HAPI_AssetLibraryId AssetLibraryId = -1; - if (!FHoudiniEngineUtils::LoadHoudiniAsset(HoudiniAsset, AssetLibraryId) ) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - could not load Houdini Asset.")); - return false; - } - - // Handle hda files that contain multiple assets - TArray< HAPI_StringHandle > AssetNames; - if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - unable to retrieve asset names.")); - return false; - } - - // By default, assume we want to load the first Asset - HAPI_StringHandle PickedAssetName = AssetNames[0]; - -#if WITH_EDITOR - // Should we show the multi asset dialog? - bool bShowMultiAssetDialog = false; - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && AssetNames.Num() > 1) - bShowMultiAssetDialog = HoudiniRuntimeSettings->bShowMultiAssetDialog; - - // TODO: Add multi selection dialog - if (bShowMultiAssetDialog ) - { - // TODO: Implement - FHoudiniEngineUtils::OpenSubassetSelectionWindow(AssetNames, PickedAssetName); - } -#endif - - // Give the HAC a new GUID to identify this request. - OutTaskGUID = FGuid::NewGuid(); - - // Create a new instantiation task - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetInstantiation, OutTaskGUID); - Task.Asset = HoudiniAsset; - Task.ActorName = DisplayName; - //Task.bLoadedComponent = bLocalLoadedComponent; - Task.AssetLibraryId = AssetLibraryId; - Task.AssetHapiName = PickedAssetName; - - // Add the task to the stack - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState ) -{ - check(HAC); - - // Will return true if the asset's state need to be updated - NewState = HAC->GetAssetState(); - bool bUpdateState = false; - - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - // Get the current task's progress - FHoudiniEngineTaskInfo TaskInfo; - if ( !UpdateTaskStatus(HAC->HapiGUID, TaskInfo) - || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetInstantiation) - { - // Couldnt get a valid task info - HOUDINI_LOG_ERROR(TEXT(" %s Failed to instantiate - invalid task"), *DisplayName); - NewState = EHoudiniAssetState::NeedInstantiation; - bUpdateState = true; - return bUpdateState; - } - - bool bSuccess = false; - bool bFinished = false; - switch (TaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Success: - { - bSuccess = true; - bFinished = true; - break; - } - - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::FinishedWithError: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - bSuccess = false; - bFinished = true; - break; - } - - case EHoudiniEngineTaskState::None: - case EHoudiniEngineTaskState::Working: - { - bFinished = false; - break; - } - } - - if ( !bFinished ) - { - // Task is still in progress, nothing to do for now - return false; - } - - if ( bSuccess && (TaskInfo.AssetId < 0) ) - { - // Task finished successfully but we received an invalid asset ID, error out - HOUDINI_LOG_ERROR(TEXT(" %s Finished Instantiation but received invalid asset id."), *DisplayName); - bSuccess = false; - } - - if ( bSuccess ) - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedInstantiation."), *DisplayName); - - // Set the new Asset ID - HAC->AssetId = TaskInfo.AssetId; - - // Assign a unique name to the actor if needed - FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(HAC); - - // TODO: Create default preset buffer. - /*TArray< char > DefaultPresetBuffer; - if (!FHoudiniEngineUtils::GetAssetPreset(TaskInfo.AssetId, DefaultPresetBuffer)) - DefaultPresetBuffer.Empty();*/ - - // Reset the cook counter. - HAC->SetAssetCookCount(0); - - // If necessary, set asset transform. - if (HAC->bUploadTransformsToHoudiniEngine) - { - // Retrieve the current component-to-world transform for this component. - if (!FHoudiniEngineUtils::HapiSetAssetTransform(TaskInfo.AssetId, HAC->GetComponentTransform())) - HOUDINI_LOG_MESSAGE(TEXT("Failed to upload the initial Transform back to HAPI.")); - } - - // Only initalize the PDG Asset Link if this Asset is a PDG Asset - // InitializePDGAssetLink may take a while to execute on non PDG HDA, - // So we want to avoid calling it if possible - if (FHoudiniPDGManager::IsPDGAsset(HAC->AssetId)) - { - PDGManager.InitializePDGAssetLink(HAC); - } - - // Update the HAC's state - NewState = EHoudiniAssetState::PreCook; - return true; - } - else - { - 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); - } - - // Reset the cook counter. - HAC->SetAssetCookCount(0); - - // Make sure the asset ID is invalid - HAC->AssetId = -1; - - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::NeedInstantiation; - //HAC->AssetStateResult = EHoudiniAssetStateResult::Success; - - return true; - } -} - -bool -FHoudiniEngineManager::StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID) -{ - // Make sure we have a valid session before attempting anything - if (!FHoudiniEngine::Get().GetSession()) - return false; - - // Check we have a valid AssetId - if (AssetId < 0) - return false; - - // Check this HAC doesn't already have a running task - if (OutTaskGUID.IsValid()) - return false; - - // Generate a GUID for our new task. - OutTaskGUID = FGuid::NewGuid(); - - // Add a new cook task - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetCooking, OutTaskGUID); - Task.ActorName = DisplayName; - Task.AssetId = AssetId; - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState) -{ - check(HAC); - - // Will return true if the asset's state need to be updated - NewState = HAC->GetAssetState(); - bool bUpdateState = false; - - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - // Get the current task's progress - FHoudiniEngineTaskInfo TaskInfo; - if (!UpdateTaskStatus(HAC->HapiGUID, TaskInfo) - || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetCooking) - { - // Couldnt get a valid task info - HOUDINI_LOG_ERROR(TEXT(" %s Failed to cook - invalid task"), *DisplayName); - NewState = EHoudiniAssetState::None; - bUpdateState = true; - return bUpdateState; - } - - bool bSuccess = false; - switch (TaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Success: - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking."), *DisplayName); - bSuccess = true; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::FinishedWithError: - { - // We finished with cook error, will still try to process the results - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with errors - will try to process the available results."), *DisplayName); - bSuccess = true; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with fatal errors - aborting."), *DisplayName); - bSuccess = false; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::None: - case EHoudiniEngineTaskState::Working: - { - // Task is still in progress, nothing to do for now - // return false so we do not update the state - bUpdateState = false; - } - break; - } - - // If the task is still in progress, return now - if (!bUpdateState) - return false; - - // Handle PostCook - NewState = EHoudiniAssetState::PostCook; - HAC->bLastCookSuccess = bSuccess; - - //if (PostCook(HAC, bSuccess, TaskInfo.AssetId)) - //{ - // // Cook was successfull, process the results - // NewState = EHoudiniAssetState::PreProcess; - // HAC->BroadcastCookFinished(); - //} - //else - //{ - // // Cook failed, skip output processing - // NewState = EHoudiniAssetState::None; - //} - - return true; -} - -bool -FHoudiniEngineManager::PreCook(UHoudiniAssetComponent* HAC) -{ - // Handle duplicated HAC - // We need to clean/duplicate some of the HAC's output data manually here - if (HAC->HasBeenDuplicated()) - { - HAC->UpdatePostDuplicate(); - } - - FHoudiniParameterTranslator::OnPreCookParameters(HAC); - - // Upload the changed/parameters back to HAPI - // If cooking is disabled, we still try to upload parameters - if (HAC->HasBeenLoaded()) - { - // Handle loaded parameters - FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); - - // Handle loaded inputs - FHoudiniInputTranslator::UpdateLoadedInputs(HAC); - - // Handle loaded outputs - FHoudiniOutputTranslator::UpdateLoadedOutputs(HAC); - - // TODO: Handle loaded curve - // TODO: Handle editable node - // TODO: Restore parameter preset data - } - - // Try to upload changed parameters - FHoudiniParameterTranslator::UploadChangedParameters(HAC); - - // Try to upload changed inputs - FHoudiniInputTranslator::UploadChangedInputs(HAC); - - // Try to upload changed editable nodes - FHoudiniOutputTranslator::UploadChangedEditableOutput(HAC, false); - - // Upload the asset's transform if needed - if (HAC->NeedTransformUpdate()) - FHoudiniEngineUtils::UploadHACTransform(HAC); - - HAC->ClearRefineMeshesTimer(); - - return true; -} - -bool -FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId) -{ - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - bool bCookSuccess = bSuccess; - if (bCookSuccess && (TaskAssetId < 0)) - { - // Task finished successfully but we received an invalid asset ID, error out - HOUDINI_LOG_ERROR(TEXT(" %s received an invalid asset id - aborting."), *DisplayName); - bCookSuccess = false; - } - - // Update the asset cook count using the node infos - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - HAC->SetAssetCookCount(CookCount); - /* - if(CookCount >= 0 ) - HAC->SetAssetCookCount(CookCount); - else - HAC->SetAssetCookCount(HAC->GetAssetCookCount()+1); - */ - - bool bNeedsToTriggerViewportUpdate = false; - if (bCookSuccess) - { - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Processing outputs...")); - - // Set new asset id. - HAC->AssetId = TaskAssetId; - - FHoudiniParameterTranslator::UpdateParameters(HAC); - - FHoudiniInputTranslator::UpdateInputs(HAC); - - bool bHasHoudiniStaticMeshOutput = false; - bool ForceUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); - FHoudiniOutputTranslator::UpdateOutputs(HAC, ForceUpdate, bHasHoudiniStaticMeshOutput); - HAC->SetNoProxyMeshNextCookRequested(false); - - // Handles have to be updated after parameters - FHoudiniHandleTranslator::UpdateHandles(HAC); - - // Clear the HasBeenLoaded flag - if (HAC->HasBeenLoaded()) - { - HAC->SetHasBeenLoaded(false); - } - - // Clear the HasBeenDuplicated flag - if (HAC->HasBeenDuplicated()) - { - HAC->SetHasBeenDuplicated(false); - } - - // TODO: Need to update rendering information. - // UpdateRenderingInformation(); - HAC->UpdateBounds(); - - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString("Finished processing outputs")); - - // Trigger a details panel update - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // If any outputs have HoudiniStaticMeshes, and if timer based refinement is enabled on the HAC, - // set the RefineMeshesTimer and ensure BuildStaticMeshesForAllHoudiniStaticMeshes is bound to - // the RefineMeshesTimerFired delegate of the HAC - if (bHasHoudiniStaticMeshOutput && HAC->IsProxyStaticMeshRefinementByTimerEnabled()) - { - if (!HAC->GetOnRefineMeshesTimerDelegate().IsBoundToObject(this)) - HAC->GetOnRefineMeshesTimerDelegate().AddRaw(this, &FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes); - HAC->SetRefineMeshesTimer(); - } - - if (bHasHoudiniStaticMeshOutput) - bNeedsToTriggerViewportUpdate = true; - - UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); - if (OnPostCookBakeDelegate.IsBound()) - { - OnPostCookBakeDelegate.Execute(HAC); - if (!HAC->IsBakeAfterNextCookEnabled()) - OnPostCookBakeDelegate.Unbind(); - } - } - else - { - // TODO: Create parameters inputs and handles inputs. - //CreateParameters(); - //CreateInputs(); - //CreateHandles(); - - // Clear the bake after cook delegate if - UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); - if (OnPostCookBakeDelegate.IsBound() && !HAC->IsBakeAfterNextCookEnabled()) - { - OnPostCookBakeDelegate.Unbind(); - // Notify the user that the bake failed since the cook failed. - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Cook failed, therefore the bake also failed...")); - } - } - - if (HAC->InputPresets.Num() > 0) - { - HAC->ApplyInputPresets(); - } - - // If we have downstream HDAs, we need to tell them we're done cooking - HAC->NotifyCookedToDownstreamAssets(); - - // Notify the PDG manager that the HDA is done cooking - FHoudiniPDGManager::NotifyAssetCooked(HAC->PDGAssetLink, bSuccess); - - if (bNeedsToTriggerViewportUpdate && GEditor) - { - // We need to manually update the vieport with HoudiniMeshProxies - // if not, modification made in H with the two way debugger wont be visible in Unreal until the vieports gets focus - GEditor->RedrawAllViewports(false); - } - - // Clear the rebuild/recook flags - HAC->SetRecookRequested(false); - HAC->SetRebuildRequested(false); - - //HAC->SyncToBlueprintGeneratedClass(); - - return bCookSuccess; -} - -bool -FHoudiniEngineManager::StartTaskAssetProcess(UHoudiniAssetComponent* HAC) -{ - HAC->AssetState = EHoudiniAssetState::Processing; - - return true; -} - -bool -FHoudiniEngineManager::UpdateProcess(UHoudiniAssetComponent* HAC) -{ - HAC->AssetState = EHoudiniAssetState::None; - - return true; -} - -bool -FHoudiniEngineManager::StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID) -{ - // Check this HAC doesn't already have a running task - if (OutTaskGUID.IsValid()) - return false; - - if (InAssetId >= 0) - { - /* TODO: Handle Asset Preset - if (!FHoudiniEngineUtils::GetAssetPreset(AssetId, PresetBuffer)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to get the asset's preset, rebuilt asset may have lost its parameters.")); - } - */ - // Delete the asset - if (!StartTaskAssetDelete(InAssetId, OutTaskGUID, true)) - { - return false; - } - } - - // Create a new task GUID for this asset - OutTaskGUID = FGuid::NewGuid(); - - return true; -} - -bool -FHoudiniEngineManager::StartTaskAssetDelete(const HAPI_NodeId& InNodeId, FGuid& OutTaskGUID, bool bShouldDeleteParent) -{ - if (InNodeId < 0) - return false; - - // Get the Asset's NodeInfo - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, &AssetNodeInfo)); - - HAPI_NodeId OBJNodeToDelete = InNodeId; - if (AssetNodeInfo.type == HAPI_NODETYPE_SOP) - { - // For SOP Asset, we want to delete their parent's OBJ node - if (bShouldDeleteParent) - { - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(OBJNodeToDelete); - OBJNodeToDelete = ParentId != -1 ? ParentId : OBJNodeToDelete; - } - } - - // Generate GUID for our new task. - OutTaskGUID = FGuid::NewGuid(); - - // Create asset deletion task object and submit it for processing. - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetDeletion, OutTaskGUID); - Task.AssetId = OBJNodeToDelete; - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo) -{ - if (!OutTaskGUID.IsValid()) - return false; - - if (!FHoudiniEngine::Get().RetrieveTaskInfo(OutTaskGUID, OutTaskInfo)) - { - // Task information does not exist - OutTaskGUID.Invalidate(); - return false; - } - - // Check whether we want to display Slate cooking and instantiation notifications. - bool bDisplaySlateCookingNotifications = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (EHoudiniEngineTaskState::None != OutTaskInfo.TaskState && bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().CreateTaskSlateNotification(OutTaskInfo.StatusText); - } - - switch (OutTaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::Success: - case EHoudiniEngineTaskState::FinishedWithError: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - // If the current task is finished - // Terminate the slate notification if they exist and delete/invalidate the task - if (bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().FinishTaskSlateNotification(OutTaskInfo.StatusText); - } - - FHoudiniEngine::Get().RemoveTaskInfo(OutTaskGUID); - OutTaskGUID.Invalidate(); - } - break; - - case EHoudiniEngineTaskState::Working: - { - // The current task is still running, simply update the current notification - if (bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().UpdateTaskSlateNotification(OutTaskInfo.StatusText); - } - } - break; - - case EHoudiniEngineTaskState::None: - default: - { - break; - } - } - - return true; -} - -bool -FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC) -{ - bool bManualRecook = false; - bool bComponentEnable = false; - if (HAC && !HAC->IsPendingKill()) - { - bManualRecook = HAC->HasRecookBeenRequested(); - bComponentEnable = HAC->IsCookingEnabled(); - } - - if (bManualRecook) - return true; - - if (bComponentEnable && FHoudiniEngine::Get().IsCookingEnabled()) - return true; - - return false; -} - -void -FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes called with HAC=nullptr")); - return; - } - -#if WITH_EDITOR - AActor *Owner = HAC->GetOwner(); - FString Name = Owner ? Owner->GetName() : HAC->GetName(); - - FScopedSlowTask Progress(2.0f, FText::FromString(FString::Printf(TEXT("Refining Proxy Mesh to Static Mesh on %s"), *Name))); - Progress.MakeDialog(); - Progress.EnterProgressFrame(1.0f); -#endif - - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); - -#if WITH_EDITOR - Progress.EnterProgressFrame(1.0f); -#endif -} - - -/* Unreal's viewport representation rules: - Viewport location is the actual camera location; - Lookat position is always right in front of the camera, which means the camera is looking at; - The rotator rotates the forward vector to a direction & orientation, and this dir and orientation is the camera's; - The identity direction and orientation of the camera is facing positive X-axis. -*/ - -/* Hapi's viewport representation rules: - The camera is located at a point on the sphere, which the center is the pivot position and the radius is offset; - Quat determines the location on the sphere and which direction the camera is facing towards, as well as the camera orientation; - The identity location, direction and orientation of the camera is facing positive Z-axis (in Hapi coords); -*/ - - -bool -FHoudiniEngineManager::SyncHoudiniViewportToUnreal() -{ - if (!FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled()) - return false; - -#if WITH_EDITOR - // Get the editor viewport LookAt position to spawn the new objects - if (!GEditor || !GEditor->GetActiveViewport()) - return false; - - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (!ViewportClient) - return false; - - // Get the current UE viewport location, lookat position, and rotation - FVector UnrealViewportPosition = ViewportClient->GetViewLocation(); - FRotator UnrealViewportRotation = ViewportClient->GetViewRotation(); - FVector UnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - /* Check if the Unreal viewport has changed */ - if (UnrealViewportPosition.Equals(SyncedUnrealViewportPosition) && - UnrealViewportRotation.Equals(SyncedUnrealViewportRotation) && - UnrealViewportLookatPosition.Equals(SyncedUnrealViewportLookatPosition)) - { - // No need to sync if the viewport camera hasn't changed - return false; - } - - /* Calculate Hapi Quaternion */ - // Initialize Hapi Quat with Unreal Quat. - // Note that rotations are in general non-commutative *** - FQuat HapiQuat = UnrealViewportRotation.Quaternion(); - - // We're in orbit mode, forward vector is Y-axis - if (ViewportClient->bUsingOrbitCamera) - { - // The forward vector is Y-negative direction when on orbiting mode - HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 180.f)); - - // rotations around X and Y axis are reversed - float TempX = HapiQuat.X; - HapiQuat.X = HapiQuat.Y; - HapiQuat.Y = TempX; - HapiQuat.W = -HapiQuat.W; - - } - // We're not in orbiting mode, forward vector is X-axis - else - { - // Rotate the Quat arount Z-axis by 90 degree. - HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 90.f)); - } - - - /* Update Hapi H_View */ - // Note: There are infinte number of H_View representation for current viewport - // Each choice of pivot point determines an equivalent representation. - // We just find an equivalent when the pivot position is the view position, and offset is 0 - - HAPI_Viewport H_View; - H_View.position[0] = UnrealViewportPosition.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - H_View.position[1] = UnrealViewportPosition.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - H_View.position[2] = UnrealViewportPosition.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Set HAPI_Offset always 0 when syncing Houdini to UE viewport - H_View.offset = 0.f; - - H_View.rotationQuaternion[0] = -HapiQuat.X; - H_View.rotationQuaternion[1] = -HapiQuat.Z; - H_View.rotationQuaternion[2] = -HapiQuat.Y; - H_View.rotationQuaternion[3] = HapiQuat.W; - - FHoudiniApi::SetViewport(FHoudiniEngine::Get().GetSession(), &H_View); - - /* Update the Synced viewport values - We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. - Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. */ - - // We need to get the H_Viewport again, since it is possible the value is a different equivalence of what we set. - HAPI_Viewport Cur_H_View; - FHoudiniApi::GetViewport( - FHoudiniEngine::Get().GetSession(), &Cur_H_View); - - // Hapi values are in Houdini coordinate and scale - SyncedHoudiniViewportPivotPosition = FVector(Cur_H_View.position[0], Cur_H_View.position[1], Cur_H_View.position[2]); - SyncedHoudiniViewportQuat = FQuat(Cur_H_View.rotationQuaternion[0], Cur_H_View.rotationQuaternion[1], Cur_H_View.rotationQuaternion[2], Cur_H_View.rotationQuaternion[3]); - SyncedHoudiniViewportOffset = Cur_H_View.offset; - - SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); - SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); - SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - // When sync Houdini to UE, we set offset to be 0. - // So we need to zero out offset for the next time syncing UE to Houdini - bOffsetZeroed = true; - - return true; -#endif - - return false; -} - - -bool -FHoudiniEngineManager::SyncUnrealViewportToHoudini() -{ - if (!FHoudiniEngine::Get().IsSyncUnrealViewportEnabled()) - return false; - -#if WITH_EDITOR - // Get the editor viewport LookAt position to spawn the new objects - if (!GEditor || !GEditor->GetActiveViewport()) - return false; - - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (!ViewportClient) - return false; - - // Get the current HAPI_Viewport - HAPI_Viewport H_View; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetViewport( - FHoudiniEngine::Get().GetSession(), &H_View)) - { - return false; - } - - - // Get Hapi viewport's PivotPosition, Offset and Quat, w.r.t Houdini's coordinate and scale. - FVector HapiViewportPivotPosition = FVector(H_View.position[0], H_View.position[1], H_View.position[2]); - float HapiViewportOffset = H_View.offset; - FQuat HapiViewportQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[1], H_View.rotationQuaternion[2], H_View.rotationQuaternion[3]); - - /* Check if the Houdini viewport has changed */ - if (SyncedHoudiniViewportPivotPosition.Equals(HapiViewportPivotPosition) && - SyncedHoudiniViewportQuat.Equals(HapiViewportQuat) && - SyncedHoudiniViewportOffset == HapiViewportOffset) - { - // Houdini viewport hasn't changed, nothing to do - return false; - } - - // Set zero value of offset when needed - if (bOffsetZeroed) - { - ZeroOffsetValue = H_View.offset; - bOffsetZeroed = false; - } - - - /* Translate the hapi camera transfrom to Unreal's representation system */ - - // Get pivot point in UE's coordinate and scale - FVector UnrealViewportPivotPosition = FVector(H_View.position[0], H_View.position[2], H_View.position[1]) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // HAPI bug? After we set the H_View, offset becomes a lot bigger when move the viewport just a little bit in Houdini. - // But the pivot point doesn't change. Which caused UE viewport jumping far suddenly. - // So we get rid of this problem by setting the first HAPI_offset value after syncing Houdini viewport as the base. - - // Get offset in UE's scale. The actual offset after 'zero out' - float UnrealOffset = (H_View.offset - ZeroOffsetValue) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - /* Calculate Quaternion in UE */ - // Rotate the resulting Quat around Z-axis by -90 degree. - // Note that rotation is in general non-commutative *** - FQuat UnrealQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[2], H_View.rotationQuaternion[1], -H_View.rotationQuaternion[3]); - UnrealQuat = UnrealQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)); - - FVector UnrealBaseVector(1.f, 0.f, 0.f); // Forward vector in Unreal viewport - - /* Get UE viewport location*/ - FVector UnrealViewPosition = - UnrealQuat.RotateVector(UnrealBaseVector) * UnrealOffset + UnrealViewportPivotPosition; - - /* Set the viewport's value */ - ViewportClient->SetViewLocation(UnrealViewPosition); - ViewportClient->SetViewRotation(UnrealQuat.Rotator()); - - // Invalidate the viewport - ViewportClient->Invalidate(); - - /* Update the synced viewport values */ - // We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. - // Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. - - // Hapi values are in Houdini coordinate and scale - SyncedHoudiniViewportPivotPosition = HapiViewportPivotPosition; - SyncedHoudiniViewportQuat = HapiViewportQuat; - SyncedHoudiniViewportOffset = HapiViewportOffset; - - SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); - SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); - SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - return true; -#endif - - return false; -} - - -void -FHoudiniEngineManager::DisableEditorAutoSave(const UHoudiniAssetComponent* HAC) -{ -#if WITH_EDITOR - if (!HAC || HAC->IsPendingKill()) - return; - - if (!GUnrealEd) - return; - - if (DisableAutoSavingHACs.Contains(HAC)) - return; - // Add the HAC to the set - DisableAutoSavingHACs.Add(HAC); - - // Return if auto-saving has been disabled by some other HACs. - if (DisableAutoSavingHACs.Num() > 1) - return; - - // Disable auto-saving by setting min time till auto-save to max float value - IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); - AutoSaver.ForceMinimumTimeTillAutoSave(TNumericLimits::Max()); -#endif -} - - -void -FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = nullptr) -{ -#if WITH_EDITOR - if (!GUnrealEd) - return; - - if (!HAC) - { - // When HAC is nullptr, go through all HACs in the set, - // remove it if the HAC has been deleted. - if (DisableAutoSavingHACs.Num() <= 0) - return; - - for (auto& CurHAC : DisableAutoSavingHACs) - { - if (!CurHAC || CurHAC->IsPendingKill()) - DisableAutoSavingHACs.Remove(CurHAC); - } - } - else - { - // Otherwise, remove the HAC from the set - if (DisableAutoSavingHACs.Contains(HAC)) - DisableAutoSavingHACs.Remove(HAC); - } - - if (DisableAutoSavingHACs.Num() > 0) - return; - - // When no HAC disables cooking, reset min time till auto-save to default value, then reset the timer - IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); - AutoSaver.ForceMinimumTimeTillAutoSave(); // use default value - AutoSaver.ResetAutoSaveTimer(); -#endif + +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineManager.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniParameterTranslator.h" +#include "HoudiniPDGManager.h" +#include "HoudiniInputTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniHandleTranslator.h" +#include "HoudiniSplineTranslator.h" +#include "Misc/MessageDialog.h" +#include "Misc/ScopedSlowTask.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "EditorViewportClient.h" + #include "Kismet/KismetMathLibrary.h" + + //#include "UnrealEd.h" + #include "UnrealEdGlobals.h" + #include "Editor/UnrealEdEngine.h" + #include "IPackageAutoSaver.h" +#endif + +const float +FHoudiniEngineManager::TickTimerDelay = 0.01f; + +FHoudiniEngineManager::FHoudiniEngineManager() + : CurrentIndex(0) + , ComponentCount(0) + , bMustStopTicking(false) + , SyncedHoudiniViewportPivotPosition(FVector::ZeroVector) + , SyncedHoudiniViewportQuat(FQuat::Identity) + , SyncedHoudiniViewportOffset(0.0f) + , SyncedUnrealViewportPosition(FVector::ZeroVector) + , SyncedUnrealViewportRotation(FRotator::ZeroRotator) + , SyncedUnrealViewportLookatPosition(FVector::ZeroVector) + , ZeroOffsetValue(0.f) + , bOffsetZeroed(false) +{ + +} + +FHoudiniEngineManager::~FHoudiniEngineManager() +{ + PDGManager.StopBGEOCommandletAndEndpoint(); +} + +void +FHoudiniEngineManager::StartHoudiniTicking() +{ + // If we have no timer delegate spawned, spawn one. + if (!TimerDelegateProcess.IsBound() && GEditor) + { + TimerDelegateProcess = FTimerDelegate::CreateRaw(this, &FHoudiniEngineManager::Tick); + GEditor->GetTimerManager()->SetTimer(TimerHandleProcess, TimerDelegateProcess, TickTimerDelay, true); + + // Grab current time for delayed notification. + FHoudiniEngine::Get().SetHapiNotificationStartedTime(FPlatformTime::Seconds()); + } +} + +void +FHoudiniEngineManager::StopHoudiniTicking() +{ + if (TimerDelegateProcess.IsBound() && GEditor) + { + if (IsInGameThread()) + { + GEditor->GetTimerManager()->ClearTimer(TimerHandleProcess); + TimerDelegateProcess.Unbind(); + + // Reset time for delayed notification. + FHoudiniEngine::Get().SetHapiNotificationStartedTime(0.0); + + bMustStopTicking = false; + } + else + { + // We can't stop ticking now as we're not in the game Thread, + // and accessing the timer would crash, indicate that we want to stop ticking asap + // This can happen when loosing a session due to a Houdini crash + bMustStopTicking = true; + } + } +} + +void +FHoudiniEngineManager::Tick() +{ + EnableEditorAutoSave(nullptr); + + if (bMustStopTicking) + { + // Ticking should be stopped immediately + StopHoudiniTicking(); + return; + } + + // Process the current component if possible + while (true) + { + UHoudiniAssetComponent * CurrentComponent = nullptr; + if (FHoudiniEngineRuntime::IsInitialized()) + { + FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); + + //FScopeLock ScopeLock(&CriticalSection); + ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); + + // No work to be done + if (ComponentCount <= 0) + break; + + // Wrap around if needed + if (CurrentIndex >= ComponentCount) + CurrentIndex = 0; + + CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(CurrentIndex); + CurrentIndex++; + } + + if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) + { + // Invalid component, do not process + break; + } + else if (CurrentComponent->IsPendingKill() + || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) + { + // Component being deleted, do not process + break; + } + + if (!CurrentComponent->IsFullyLoaded()) + { + // Let the component figure out whether it's fully loaded or not. + CurrentComponent->HoudiniEngineTick(); + if (!CurrentComponent->IsFullyLoaded()) + continue; // We need to wait some more. + } + + if (!CurrentComponent->IsValidComponent()) + { + // This component is no longer valid. Prevent it from being processed, and remove it. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); + continue; + } + + // We don't want to the template component processing to trigger session creation + if (CurrentComponent->GetAssetState() == EHoudiniAssetState::ProcessTemplate) + { + if (CurrentComponent->IsTemplate() && !CurrentComponent->HasOpenEditor()) + { + // This component template no longer has an open editor and can be deregistered. + // TODO: Replace this polling mechanism with an "On Asset Closed" event if we + // can find one that actually works. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); + continue; + } + + if (CurrentComponent->NeedBlueprintStructureUpdate()) + { + CurrentComponent->OnBlueprintStructureModified(); + } + + if (CurrentComponent->NeedBlueprintUpdate()) + { + CurrentComponent->OnBlueprintModified(); + } + + if (FHoudiniEngine::Get().IsCookingEnabled()) + { + // Only process component template parameter updates when cooking is enabled. + if (CurrentComponent->NeedUpdateParameters() || CurrentComponent->NeedUpdateInputs()) + { + CurrentComponent->OnTemplateParametersChanged(); + } + } + + if (CurrentComponent->NeedOutputUpdate()) + { + // TODO: Transfer template output changes over to the preview instance. + } + + break; + } + + // See if we should start the default "first" session + if(!FHoudiniEngine::Get().GetSession() && !FHoudiniEngine::Get().GetFirstSessionCreated()) + { + // Only try to start the default session if we have an "active" HAC + if (CurrentComponent->GetAssetState() == EHoudiniAssetState::PreInstantiation + || CurrentComponent->GetAssetState() == EHoudiniAssetState::Instantiating + || CurrentComponent->GetAssetState() == EHoudiniAssetState::PreCook + || CurrentComponent->GetAssetState() == EHoudiniAssetState::Cooking) + { + FString StatusText = TEXT("Initializing Houdini Engine..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // We want to yield for a bit. + //FPlatformProcess::Sleep(0.5f); + + // Indicates that we've tried to start the session once no matter if it failed or succeed + FHoudiniEngine::Get().SetFirstSessionCreated(true); + + // Attempt to restart the session + if (!FHoudiniEngine::Get().RestartSession()) + { + // We failed to start the session + // Stop ticking until it's manually restarted + StopHoudiniTicking(); + + StatusText = TEXT("Houdini Engine failed to initialize."); + } + else + { + StatusText = TEXT("Houdini Engine successfully initialized."); + } + + // Finish the notification and display the results + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + } + } + + // Process the component + // try to catch (apache::thrift::transport::TTransportException * e) for session loss? + ProcessComponent(CurrentComponent); + break; + } + + // Handle Asset delete + if (FHoudiniEngineRuntime::IsInitialized()) + { + int32 PendingDeleteCount = FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteCount(); + for (int32 DeleteIdx = PendingDeleteCount - 1; DeleteIdx >= 0; DeleteIdx--) + { + HAPI_NodeId NodeIdToDelete = (HAPI_NodeId)FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteAt(DeleteIdx); + FGuid HapiDeletionGUID; + bool bShouldDeleteParent = FHoudiniEngineRuntime::Get().IsParentNodePendingDelete(NodeIdToDelete); + if (StartTaskAssetDelete(NodeIdToDelete, HapiDeletionGUID, bShouldDeleteParent)) + { + FHoudiniEngineRuntime::Get().RemoveNodeIdPendingDeleteAt(DeleteIdx); + if (bShouldDeleteParent) + FHoudiniEngineRuntime::Get().RemoveParentNodePendingDelete(NodeIdToDelete); + } + } + } + + // Update PDG Contexts and asset link if needed + PDGManager.Update(); + + // Session Sync Updates + if (FHoudiniEngine::Get().IsSessionSyncEnabled()) + { + // See if the session sync settings have changed on the houdini side, update ours if they did + FHoudiniEngine::Get().UpdateSessionSyncInfoFromHoudini(); +#if WITH_EDITOR + // Update the Houdini viewport from unreal if needed + if (FHoudiniEngine::Get().IsSyncViewportEnabled()) + { + // Sync the Houdini viewport to Unreal + if (!SyncHoudiniViewportToUnreal()) + { + // If the unreal viewport hasnt changed, + // See if we need to sync the Unreal viewport from Houdini's + SyncUnrealViewportToHoudini(); + } + } +#endif + } + else + { + // reset zero offset variables when session sync is off + if (ZeroOffsetValue != 0.f) + ZeroOffsetValue = 0.f; + + if (bOffsetZeroed) + bOffsetZeroed = false; + } +} + +void +FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + // No need to process component not tied to an asset + if (!HAC->GetHoudiniAsset()) + return; + + // If cooking is paused, stay in the current state until cooking's resumed + if (!FHoudiniEngine::Get().IsCookingEnabled()) + { + // We can only handle output updates + if (HAC->GetAssetState() == EHoudiniAssetState::None && HAC->NeedOutputUpdate()) + { + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Refresh UI when pause cooking + if (!FHoudiniEngine::Get().HasUIFinishRefreshingWhenPausingCooking()) + { + // Trigger a details panel update if the Houdini asset actor is selected + if (HAC->IsOwnerSelected()) + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // Finished refreshing UI of one HDA. + FHoudiniEngine::Get().RefreshUIDisplayedWhenPauseCooking(); + } + + // Prevent any other state change to happen + return; + } + + switch (HAC->GetAssetState()) + { + case EHoudiniAssetState::NeedInstantiation: + { + // Do nothing unless the HAC has been updated + if (HAC->NeedUpdate()) + { + HAC->OnPrePreInstantiation(); + HAC->bForceNeedUpdate = false; + // Update the HAC's state + HAC->AssetState = EHoudiniAssetState::PreInstantiation; + } + else if (HAC->NeedOutputUpdate()) + { + // Output updates do not recquire the HDA to be instantiated + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Update world input if we have any + FHoudiniInputTranslator::UpdateWorldInputs(HAC); + + break; + } + + case EHoudiniAssetState::PreInstantiation: + { + // Only proceed forward if we don't need to wait for our input HoudiniAssets to finish cooking/instantiating + if (HAC->NeedsToWaitForInputHoudiniAssets()) + break; + + FGuid TaskGuid; + UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); + if (StartTaskAssetInstantiation(HoudiniAsset, HAC->GetDisplayName(), TaskGuid)) + { + // Update the HAC's state + HAC->AssetState = EHoudiniAssetState::Instantiating; + //HAC->AssetStateResult = EHoudiniAssetStateResult::None; + + // Update the Task GUID + HAC->HapiGUID = TaskGuid; + } + else + { + // If we couldnt instantiate the asset + // Change the state to NeedInstantiating + HAC->AssetState = EHoudiniAssetState::NeedInstantiation; + //HAC->AssetStateResult = EHoudiniAssetStateResult::None; + } + break; + } + + case EHoudiniAssetState::Instantiating: + { + EHoudiniAssetState NewState = EHoudiniAssetState::Instantiating; + if (UpdateInstantiating(HAC, NewState)) + { + // We need to update the HAC's state + HAC->AssetState = NewState; + EnableEditorAutoSave(HAC); + } + else + { + DisableEditorAutoSave(HAC); + } + break; + } + + case EHoudiniAssetState::PreCook: + { + // Only proceed forward if we don't need to wait for our input + // HoudiniAssets to finish cooking/instantiating + if (HAC->NeedsToWaitForInputHoudiniAssets()) + break; + + HAC->OnPrePreCook(); + // Update all the HAPI nodes, parameters, inputs etc... + PreCook(HAC); + HAC->OnPostPreCook(); + + // Create a Cooking task only if necessary + bool bCookStarted = false; + if (IsCookingEnabledForHoudiniAsset(HAC)) + { + FGuid TaskGUID = HAC->GetHapiGUID(); + if ( StartTaskAssetCooking(HAC->GetAssetId(), HAC->GetDisplayName(), TaskGUID) ) + { + // Updates the HAC's state + HAC->AssetState = EHoudiniAssetState::Cooking; + HAC->HapiGUID = TaskGUID; + bCookStarted = true; + } + } + + if(!bCookStarted) + { + // Just refresh editor properties? + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // TODO: Check! update state? + HAC->AssetState = EHoudiniAssetState::None; + } + break; + } + + case EHoudiniAssetState::Cooking: + { + EHoudiniAssetState NewState = EHoudiniAssetState::Cooking; + bool state = UpdateCooking(HAC, NewState); + if (state) + { + // We need to update the HAC's state + HAC->AssetState = NewState; + EnableEditorAutoSave(HAC); + } + else + { + DisableEditorAutoSave(HAC); + } + break; + } + + case EHoudiniAssetState::PostCook: + { + // Handle PostCook + EHoudiniAssetState NewState = EHoudiniAssetState::None; + bool bSuccess = HAC->bLastCookSuccess; + HAC->OnPreOutputProcessing(); + if (PostCook(HAC, bSuccess, HAC->GetAssetId())) + { + // Cook was successful, process the results + NewState = EHoudiniAssetState::PreProcess; + } + else + { + // Cook failed, skip output processing + NewState = EHoudiniAssetState::None; + } + HAC->AssetState = NewState; + break; + } + + case EHoudiniAssetState::PreProcess: + { + StartTaskAssetProcess(HAC); + break; + } + + case EHoudiniAssetState::Processing: + { + UpdateProcess(HAC); + + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + + HAC->OnPostOutputProcessing(); + FHoudiniEngineUtils::UpdateBlueprintEditor(HAC); + break; + } + + case EHoudiniAssetState::None: + { + // Do nothing unless the HAC has been updated + if (HAC->NeedUpdate()) + { + HAC->bForceNeedUpdate = false; + // Update the HAC's state + HAC->AssetState = EHoudiniAssetState::PreCook; + } + else if (HAC->NeedTransformUpdate()) + { + FHoudiniEngineUtils::UploadHACTransform(HAC); + } + else if (HAC->NeedOutputUpdate()) + { + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Update world inputs if we have any + FHoudiniInputTranslator::UpdateWorldInputs(HAC); + + // See if we need to get an update from Session Sync + if(FHoudiniEngine::Get().IsSessionSyncEnabled() + && FHoudiniEngine::Get().IsSyncWithHoudiniCookEnabled() + && HAC->AssetState == EHoudiniAssetState::None) + { + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + if (CookCount >= 0 && CookCount != HAC->GetAssetCookCount()) + { + // The cook count has changed on the Houdini side, + // this indicates that the user has changed something in Houdini so we need to trigger an update + HAC->AssetState = EHoudiniAssetState::PreCook; + } + } + break; + } + + case EHoudiniAssetState::NeedRebuild: + { + StartTaskAssetRebuild(HAC->AssetId, HAC->HapiGUID); + + HAC->MarkAsNeedCook(); + HAC->AssetState = EHoudiniAssetState::PreInstantiation; + break; + } + + case EHoudiniAssetState::NeedDelete: + { + FGuid HapiDeletionGUID; + StartTaskAssetDelete(HAC->GetAssetId(), HapiDeletionGUID, true); + //HAC->AssetId = -1; + + // Update the HAC's state + HAC->AssetState = EHoudiniAssetState::Deleting; + break; + } + + case EHoudiniAssetState::Deleting: + { + break; + } + } +} + + + +bool +FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID) +{ + // Make sure we have a valid session before attempting anything + if (!FHoudiniEngine::Get().GetSession()) + return false; + + OutTaskGUID.Invalidate(); + + // Load the HDA file + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - null or invalid Houdini Asset.")); + return false; + } + + HAPI_AssetLibraryId AssetLibraryId = -1; + if (!FHoudiniEngineUtils::LoadHoudiniAsset(HoudiniAsset, AssetLibraryId) ) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - could not load Houdini Asset.")); + return false; + } + + // Handle hda files that contain multiple assets + TArray< HAPI_StringHandle > AssetNames; + if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - unable to retrieve asset names.")); + return false; + } + + // By default, assume we want to load the first Asset + HAPI_StringHandle PickedAssetName = AssetNames[0]; + +#if WITH_EDITOR + // Should we show the multi asset dialog? + bool bShowMultiAssetDialog = false; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && AssetNames.Num() > 1) + bShowMultiAssetDialog = HoudiniRuntimeSettings->bShowMultiAssetDialog; + + // TODO: Add multi selection dialog + if (bShowMultiAssetDialog ) + { + // TODO: Implement + FHoudiniEngineUtils::OpenSubassetSelectionWindow(AssetNames, PickedAssetName); + } +#endif + + // Give the HAC a new GUID to identify this request. + OutTaskGUID = FGuid::NewGuid(); + + // Create a new instantiation task + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetInstantiation, OutTaskGUID); + Task.Asset = HoudiniAsset; + Task.ActorName = DisplayName; + //Task.bLoadedComponent = bLocalLoadedComponent; + Task.AssetLibraryId = AssetLibraryId; + Task.AssetHapiName = PickedAssetName; + + // Add the task to the stack + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState ) +{ + check(HAC); + + // Will return true if the asset's state need to be updated + NewState = HAC->GetAssetState(); + bool bUpdateState = false; + + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + // Get the current task's progress + FHoudiniEngineTaskInfo TaskInfo; + if ( !UpdateTaskStatus(HAC->HapiGUID, TaskInfo) + || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetInstantiation) + { + // Couldnt get a valid task info + HOUDINI_LOG_ERROR(TEXT(" %s Failed to instantiate - invalid task"), *DisplayName); + NewState = EHoudiniAssetState::NeedInstantiation; + bUpdateState = true; + return bUpdateState; + } + + bool bSuccess = false; + bool bFinished = false; + switch (TaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Success: + { + bSuccess = true; + bFinished = true; + break; + } + + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::FinishedWithError: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + bSuccess = false; + bFinished = true; + break; + } + + case EHoudiniEngineTaskState::None: + case EHoudiniEngineTaskState::Working: + { + bFinished = false; + break; + } + } + + if ( !bFinished ) + { + // Task is still in progress, nothing to do for now + return false; + } + + if ( bSuccess && (TaskInfo.AssetId < 0) ) + { + // Task finished successfully but we received an invalid asset ID, error out + HOUDINI_LOG_ERROR(TEXT(" %s Finished Instantiation but received invalid asset id."), *DisplayName); + bSuccess = false; + } + + if ( bSuccess ) + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedInstantiation."), *DisplayName); + + // Set the new Asset ID + HAC->AssetId = TaskInfo.AssetId; + + // Assign a unique name to the actor if needed + FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(HAC); + + // TODO: Create default preset buffer. + /*TArray< char > DefaultPresetBuffer; + if (!FHoudiniEngineUtils::GetAssetPreset(TaskInfo.AssetId, DefaultPresetBuffer)) + DefaultPresetBuffer.Empty();*/ + + // Reset the cook counter. + HAC->SetAssetCookCount(0); + + // If necessary, set asset transform. + if (HAC->bUploadTransformsToHoudiniEngine) + { + // Retrieve the current component-to-world transform for this component. + if (!FHoudiniEngineUtils::HapiSetAssetTransform(TaskInfo.AssetId, HAC->GetComponentTransform())) + HOUDINI_LOG_MESSAGE(TEXT("Failed to upload the initial Transform back to HAPI.")); + } + + // Only initalize the PDG Asset Link if this Asset is a PDG Asset + // InitializePDGAssetLink may take a while to execute on non PDG HDA, + // So we want to avoid calling it if possible + if (FHoudiniPDGManager::IsPDGAsset(HAC->AssetId)) + { + PDGManager.InitializePDGAssetLink(HAC); + } + + // Update the HAC's state + NewState = EHoudiniAssetState::PreCook; + return true; + } + else + { + 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); + } + + // Reset the cook counter. + HAC->SetAssetCookCount(0); + + // Make sure the asset ID is invalid + HAC->AssetId = -1; + + // Update the HAC's state + HAC->AssetState = EHoudiniAssetState::NeedInstantiation; + //HAC->AssetStateResult = EHoudiniAssetStateResult::Success; + + return true; + } +} + +bool +FHoudiniEngineManager::StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID) +{ + // Make sure we have a valid session before attempting anything + if (!FHoudiniEngine::Get().GetSession()) + return false; + + // Check we have a valid AssetId + if (AssetId < 0) + return false; + + // Check this HAC doesn't already have a running task + if (OutTaskGUID.IsValid()) + return false; + + // Generate a GUID for our new task. + OutTaskGUID = FGuid::NewGuid(); + + // Add a new cook task + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetCooking, OutTaskGUID); + Task.ActorName = DisplayName; + Task.AssetId = AssetId; + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState) +{ + check(HAC); + + // Will return true if the asset's state need to be updated + NewState = HAC->GetAssetState(); + bool bUpdateState = false; + + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + // Get the current task's progress + FHoudiniEngineTaskInfo TaskInfo; + if (!UpdateTaskStatus(HAC->HapiGUID, TaskInfo) + || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetCooking) + { + // Couldnt get a valid task info + HOUDINI_LOG_ERROR(TEXT(" %s Failed to cook - invalid task"), *DisplayName); + NewState = EHoudiniAssetState::None; + bUpdateState = true; + return bUpdateState; + } + + bool bSuccess = false; + switch (TaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Success: + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking."), *DisplayName); + bSuccess = true; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::FinishedWithError: + { + // We finished with cook error, will still try to process the results + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with errors - will try to process the available results."), *DisplayName); + bSuccess = true; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with fatal errors - aborting."), *DisplayName); + bSuccess = false; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::None: + case EHoudiniEngineTaskState::Working: + { + // Task is still in progress, nothing to do for now + // return false so we do not update the state + bUpdateState = false; + } + break; + } + + // If the task is still in progress, return now + if (!bUpdateState) + return false; + + // Handle PostCook + NewState = EHoudiniAssetState::PostCook; + HAC->bLastCookSuccess = bSuccess; + + //if (PostCook(HAC, bSuccess, TaskInfo.AssetId)) + //{ + // // Cook was successfull, process the results + // NewState = EHoudiniAssetState::PreProcess; + // HAC->BroadcastCookFinished(); + //} + //else + //{ + // // Cook failed, skip output processing + // NewState = EHoudiniAssetState::None; + //} + + return true; +} + +bool +FHoudiniEngineManager::PreCook(UHoudiniAssetComponent* HAC) +{ + // Handle duplicated HAC + // We need to clean/duplicate some of the HAC's output data manually here + if (HAC->HasBeenDuplicated()) + { + HAC->UpdatePostDuplicate(); + } + + FHoudiniParameterTranslator::OnPreCookParameters(HAC); + + // Upload the changed/parameters back to HAPI + // If cooking is disabled, we still try to upload parameters + if (HAC->HasBeenLoaded()) + { + // Handle loaded parameters + FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); + + // Handle loaded inputs + FHoudiniInputTranslator::UpdateLoadedInputs(HAC); + + // Handle loaded outputs + FHoudiniOutputTranslator::UpdateLoadedOutputs(HAC); + + // TODO: Handle loaded curve + // TODO: Handle editable node + // TODO: Restore parameter preset data + } + + // Try to upload changed parameters + FHoudiniParameterTranslator::UploadChangedParameters(HAC); + + // Try to upload changed inputs + FHoudiniInputTranslator::UploadChangedInputs(HAC); + + // Try to upload changed editable nodes + FHoudiniOutputTranslator::UploadChangedEditableOutput(HAC, false); + + // Upload the asset's transform if needed + if (HAC->NeedTransformUpdate()) + FHoudiniEngineUtils::UploadHACTransform(HAC); + + HAC->ClearRefineMeshesTimer(); + + return true; +} + +bool +FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId) +{ + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + bool bCookSuccess = bSuccess; + if (bCookSuccess && (TaskAssetId < 0)) + { + // Task finished successfully but we received an invalid asset ID, error out + HOUDINI_LOG_ERROR(TEXT(" %s received an invalid asset id - aborting."), *DisplayName); + bCookSuccess = false; + } + + // Update the asset cook count using the node infos + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + HAC->SetAssetCookCount(CookCount); + /* + if(CookCount >= 0 ) + HAC->SetAssetCookCount(CookCount); + else + HAC->SetAssetCookCount(HAC->GetAssetCookCount()+1); + */ + + bool bNeedsToTriggerViewportUpdate = false; + if (bCookSuccess) + { + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Processing outputs...")); + + // Set new asset id. + HAC->AssetId = TaskAssetId; + + FHoudiniParameterTranslator::UpdateParameters(HAC); + + FHoudiniInputTranslator::UpdateInputs(HAC); + + bool bHasHoudiniStaticMeshOutput = false; + bool ForceUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); + FHoudiniOutputTranslator::UpdateOutputs(HAC, ForceUpdate, bHasHoudiniStaticMeshOutput); + HAC->SetNoProxyMeshNextCookRequested(false); + + // Handles have to be updated after parameters + FHoudiniHandleTranslator::UpdateHandles(HAC); + + // Clear the HasBeenLoaded flag + if (HAC->HasBeenLoaded()) + { + HAC->SetHasBeenLoaded(false); + } + + // Clear the HasBeenDuplicated flag + if (HAC->HasBeenDuplicated()) + { + HAC->SetHasBeenDuplicated(false); + } + + // TODO: Need to update rendering information. + // UpdateRenderingInformation(); + HAC->UpdateBounds(); + + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString("Finished processing outputs")); + + // Trigger a details panel update + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // If any outputs have HoudiniStaticMeshes, and if timer based refinement is enabled on the HAC, + // set the RefineMeshesTimer and ensure BuildStaticMeshesForAllHoudiniStaticMeshes is bound to + // the RefineMeshesTimerFired delegate of the HAC + if (bHasHoudiniStaticMeshOutput && HAC->IsProxyStaticMeshRefinementByTimerEnabled()) + { + if (!HAC->GetOnRefineMeshesTimerDelegate().IsBoundToObject(this)) + HAC->GetOnRefineMeshesTimerDelegate().AddRaw(this, &FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes); + HAC->SetRefineMeshesTimer(); + } + + if (bHasHoudiniStaticMeshOutput) + bNeedsToTriggerViewportUpdate = true; + + UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); + if (OnPostCookBakeDelegate.IsBound()) + { + OnPostCookBakeDelegate.Execute(HAC); + if (!HAC->IsBakeAfterNextCookEnabled()) + OnPostCookBakeDelegate.Unbind(); + } + } + else + { + // TODO: Create parameters inputs and handles inputs. + //CreateParameters(); + //CreateInputs(); + //CreateHandles(); + + // Clear the bake after cook delegate if + UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); + if (OnPostCookBakeDelegate.IsBound() && !HAC->IsBakeAfterNextCookEnabled()) + { + OnPostCookBakeDelegate.Unbind(); + // Notify the user that the bake failed since the cook failed. + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Cook failed, therefore the bake also failed...")); + } + } + + if (HAC->InputPresets.Num() > 0) + { + HAC->ApplyInputPresets(); + } + + // If we have downstream HDAs, we need to tell them we're done cooking + HAC->NotifyCookedToDownstreamAssets(); + + // Notify the PDG manager that the HDA is done cooking + FHoudiniPDGManager::NotifyAssetCooked(HAC->PDGAssetLink, bSuccess); + + if (bNeedsToTriggerViewportUpdate && GEditor) + { + // We need to manually update the vieport with HoudiniMeshProxies + // if not, modification made in H with the two way debugger wont be visible in Unreal until the vieports gets focus + GEditor->RedrawAllViewports(false); + } + + // Clear the rebuild/recook flags + HAC->SetRecookRequested(false); + HAC->SetRebuildRequested(false); + + //HAC->SyncToBlueprintGeneratedClass(); + + return bCookSuccess; +} + +bool +FHoudiniEngineManager::StartTaskAssetProcess(UHoudiniAssetComponent* HAC) +{ + HAC->AssetState = EHoudiniAssetState::Processing; + + return true; +} + +bool +FHoudiniEngineManager::UpdateProcess(UHoudiniAssetComponent* HAC) +{ + HAC->AssetState = EHoudiniAssetState::None; + + return true; +} + +bool +FHoudiniEngineManager::StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID) +{ + // Check this HAC doesn't already have a running task + if (OutTaskGUID.IsValid()) + return false; + + if (InAssetId >= 0) + { + /* TODO: Handle Asset Preset + if (!FHoudiniEngineUtils::GetAssetPreset(AssetId, PresetBuffer)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get the asset's preset, rebuilt asset may have lost its parameters.")); + } + */ + // Delete the asset + if (!StartTaskAssetDelete(InAssetId, OutTaskGUID, true)) + { + return false; + } + } + + // Create a new task GUID for this asset + OutTaskGUID = FGuid::NewGuid(); + + return true; +} + +bool +FHoudiniEngineManager::StartTaskAssetDelete(const HAPI_NodeId& InNodeId, FGuid& OutTaskGUID, bool bShouldDeleteParent) +{ + if (InNodeId < 0) + return false; + + // Get the Asset's NodeInfo + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, &AssetNodeInfo)); + + HAPI_NodeId OBJNodeToDelete = InNodeId; + if (AssetNodeInfo.type == HAPI_NODETYPE_SOP) + { + // For SOP Asset, we want to delete their parent's OBJ node + if (bShouldDeleteParent) + { + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(OBJNodeToDelete); + OBJNodeToDelete = ParentId != -1 ? ParentId : OBJNodeToDelete; + } + } + + // Generate GUID for our new task. + OutTaskGUID = FGuid::NewGuid(); + + // Create asset deletion task object and submit it for processing. + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetDeletion, OutTaskGUID); + Task.AssetId = OBJNodeToDelete; + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo) +{ + if (!OutTaskGUID.IsValid()) + return false; + + if (!FHoudiniEngine::Get().RetrieveTaskInfo(OutTaskGUID, OutTaskInfo)) + { + // Task information does not exist + OutTaskGUID.Invalidate(); + return false; + } + + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (EHoudiniEngineTaskState::None != OutTaskInfo.TaskState && bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().CreateTaskSlateNotification(OutTaskInfo.StatusText); + } + + switch (OutTaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::Success: + case EHoudiniEngineTaskState::FinishedWithError: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + // If the current task is finished + // Terminate the slate notification if they exist and delete/invalidate the task + if (bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().FinishTaskSlateNotification(OutTaskInfo.StatusText); + } + + FHoudiniEngine::Get().RemoveTaskInfo(OutTaskGUID); + OutTaskGUID.Invalidate(); + } + break; + + case EHoudiniEngineTaskState::Working: + { + // The current task is still running, simply update the current notification + if (bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().UpdateTaskSlateNotification(OutTaskInfo.StatusText); + } + } + break; + + case EHoudiniEngineTaskState::None: + default: + { + break; + } + } + + return true; +} + +bool +FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC) +{ + bool bManualRecook = false; + bool bComponentEnable = false; + if (HAC && !HAC->IsPendingKill()) + { + bManualRecook = HAC->HasRecookBeenRequested(); + bComponentEnable = HAC->IsCookingEnabled(); + } + + if (bManualRecook) + return true; + + if (bComponentEnable && FHoudiniEngine::Get().IsCookingEnabled()) + return true; + + return false; +} + +void +FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes called with HAC=nullptr")); + return; + } + +#if WITH_EDITOR + AActor *Owner = HAC->GetOwner(); + FString Name = Owner ? Owner->GetName() : HAC->GetName(); + + FScopedSlowTask Progress(2.0f, FText::FromString(FString::Printf(TEXT("Refining Proxy Mesh to Static Mesh on %s"), *Name))); + Progress.MakeDialog(); + Progress.EnterProgressFrame(1.0f); +#endif + + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); + +#if WITH_EDITOR + Progress.EnterProgressFrame(1.0f); +#endif +} + + +/* Unreal's viewport representation rules: + Viewport location is the actual camera location; + Lookat position is always right in front of the camera, which means the camera is looking at; + The rotator rotates the forward vector to a direction & orientation, and this dir and orientation is the camera's; + The identity direction and orientation of the camera is facing positive X-axis. +*/ + +/* Hapi's viewport representation rules: + The camera is located at a point on the sphere, which the center is the pivot position and the radius is offset; + Quat determines the location on the sphere and which direction the camera is facing towards, as well as the camera orientation; + The identity location, direction and orientation of the camera is facing positive Z-axis (in Hapi coords); +*/ + + +bool +FHoudiniEngineManager::SyncHoudiniViewportToUnreal() +{ + if (!FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled()) + return false; + +#if WITH_EDITOR + // Get the editor viewport LookAt position to spawn the new objects + if (!GEditor || !GEditor->GetActiveViewport()) + return false; + + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (!ViewportClient) + return false; + + // Get the current UE viewport location, lookat position, and rotation + FVector UnrealViewportPosition = ViewportClient->GetViewLocation(); + FRotator UnrealViewportRotation = ViewportClient->GetViewRotation(); + FVector UnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + /* Check if the Unreal viewport has changed */ + if (UnrealViewportPosition.Equals(SyncedUnrealViewportPosition) && + UnrealViewportRotation.Equals(SyncedUnrealViewportRotation) && + UnrealViewportLookatPosition.Equals(SyncedUnrealViewportLookatPosition)) + { + // No need to sync if the viewport camera hasn't changed + return false; + } + + /* Calculate Hapi Quaternion */ + // Initialize Hapi Quat with Unreal Quat. + // Note that rotations are in general non-commutative *** + FQuat HapiQuat = UnrealViewportRotation.Quaternion(); + + // We're in orbit mode, forward vector is Y-axis + if (ViewportClient->bUsingOrbitCamera) + { + // The forward vector is Y-negative direction when on orbiting mode + HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 180.f)); + + // rotations around X and Y axis are reversed + float TempX = HapiQuat.X; + HapiQuat.X = HapiQuat.Y; + HapiQuat.Y = TempX; + HapiQuat.W = -HapiQuat.W; + + } + // We're not in orbiting mode, forward vector is X-axis + else + { + // Rotate the Quat arount Z-axis by 90 degree. + HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 90.f)); + } + + + /* Update Hapi H_View */ + // Note: There are infinte number of H_View representation for current viewport + // Each choice of pivot point determines an equivalent representation. + // We just find an equivalent when the pivot position is the view position, and offset is 0 + + HAPI_Viewport H_View; + H_View.position[0] = UnrealViewportPosition.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + H_View.position[1] = UnrealViewportPosition.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + H_View.position[2] = UnrealViewportPosition.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Set HAPI_Offset always 0 when syncing Houdini to UE viewport + H_View.offset = 0.f; + + H_View.rotationQuaternion[0] = -HapiQuat.X; + H_View.rotationQuaternion[1] = -HapiQuat.Z; + H_View.rotationQuaternion[2] = -HapiQuat.Y; + H_View.rotationQuaternion[3] = HapiQuat.W; + + FHoudiniApi::SetViewport(FHoudiniEngine::Get().GetSession(), &H_View); + + /* Update the Synced viewport values + We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. + Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. */ + + // We need to get the H_Viewport again, since it is possible the value is a different equivalence of what we set. + HAPI_Viewport Cur_H_View; + FHoudiniApi::GetViewport( + FHoudiniEngine::Get().GetSession(), &Cur_H_View); + + // Hapi values are in Houdini coordinate and scale + SyncedHoudiniViewportPivotPosition = FVector(Cur_H_View.position[0], Cur_H_View.position[1], Cur_H_View.position[2]); + SyncedHoudiniViewportQuat = FQuat(Cur_H_View.rotationQuaternion[0], Cur_H_View.rotationQuaternion[1], Cur_H_View.rotationQuaternion[2], Cur_H_View.rotationQuaternion[3]); + SyncedHoudiniViewportOffset = Cur_H_View.offset; + + SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); + SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); + SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + // When sync Houdini to UE, we set offset to be 0. + // So we need to zero out offset for the next time syncing UE to Houdini + bOffsetZeroed = true; + + return true; +#endif + + return false; +} + + +bool +FHoudiniEngineManager::SyncUnrealViewportToHoudini() +{ + if (!FHoudiniEngine::Get().IsSyncUnrealViewportEnabled()) + return false; + +#if WITH_EDITOR + // Get the editor viewport LookAt position to spawn the new objects + if (!GEditor || !GEditor->GetActiveViewport()) + return false; + + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (!ViewportClient) + return false; + + // Get the current HAPI_Viewport + HAPI_Viewport H_View; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetViewport( + FHoudiniEngine::Get().GetSession(), &H_View)) + { + return false; + } + + + // Get Hapi viewport's PivotPosition, Offset and Quat, w.r.t Houdini's coordinate and scale. + FVector HapiViewportPivotPosition = FVector(H_View.position[0], H_View.position[1], H_View.position[2]); + float HapiViewportOffset = H_View.offset; + FQuat HapiViewportQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[1], H_View.rotationQuaternion[2], H_View.rotationQuaternion[3]); + + /* Check if the Houdini viewport has changed */ + if (SyncedHoudiniViewportPivotPosition.Equals(HapiViewportPivotPosition) && + SyncedHoudiniViewportQuat.Equals(HapiViewportQuat) && + SyncedHoudiniViewportOffset == HapiViewportOffset) + { + // Houdini viewport hasn't changed, nothing to do + return false; + } + + // Set zero value of offset when needed + if (bOffsetZeroed) + { + ZeroOffsetValue = H_View.offset; + bOffsetZeroed = false; + } + + + /* Translate the hapi camera transfrom to Unreal's representation system */ + + // Get pivot point in UE's coordinate and scale + FVector UnrealViewportPivotPosition = FVector(H_View.position[0], H_View.position[2], H_View.position[1]) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // HAPI bug? After we set the H_View, offset becomes a lot bigger when move the viewport just a little bit in Houdini. + // But the pivot point doesn't change. Which caused UE viewport jumping far suddenly. + // So we get rid of this problem by setting the first HAPI_offset value after syncing Houdini viewport as the base. + + // Get offset in UE's scale. The actual offset after 'zero out' + float UnrealOffset = (H_View.offset - ZeroOffsetValue) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + /* Calculate Quaternion in UE */ + // Rotate the resulting Quat around Z-axis by -90 degree. + // Note that rotation is in general non-commutative *** + FQuat UnrealQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[2], H_View.rotationQuaternion[1], -H_View.rotationQuaternion[3]); + UnrealQuat = UnrealQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)); + + FVector UnrealBaseVector(1.f, 0.f, 0.f); // Forward vector in Unreal viewport + + /* Get UE viewport location*/ + FVector UnrealViewPosition = - UnrealQuat.RotateVector(UnrealBaseVector) * UnrealOffset + UnrealViewportPivotPosition; + + /* Set the viewport's value */ + ViewportClient->SetViewLocation(UnrealViewPosition); + ViewportClient->SetViewRotation(UnrealQuat.Rotator()); + + // Invalidate the viewport + ViewportClient->Invalidate(); + + /* Update the synced viewport values */ + // We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. + // Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. + + // Hapi values are in Houdini coordinate and scale + SyncedHoudiniViewportPivotPosition = HapiViewportPivotPosition; + SyncedHoudiniViewportQuat = HapiViewportQuat; + SyncedHoudiniViewportOffset = HapiViewportOffset; + + SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); + SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); + SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + return true; +#endif + + return false; +} + + +void +FHoudiniEngineManager::DisableEditorAutoSave(const UHoudiniAssetComponent* HAC) +{ +#if WITH_EDITOR + if (!HAC || HAC->IsPendingKill()) + return; + + if (!GUnrealEd) + return; + + if (DisableAutoSavingHACs.Contains(HAC)) + return; + // Add the HAC to the set + DisableAutoSavingHACs.Add(HAC); + + // Return if auto-saving has been disabled by some other HACs. + if (DisableAutoSavingHACs.Num() > 1) + return; + + // Disable auto-saving by setting min time till auto-save to max float value + IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); + AutoSaver.ForceMinimumTimeTillAutoSave(TNumericLimits::Max()); +#endif +} + + +void +FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = nullptr) +{ +#if WITH_EDITOR + if (!GUnrealEd) + return; + + if (!HAC) + { + // When HAC is nullptr, go through all HACs in the set, + // remove it if the HAC has been deleted. + if (DisableAutoSavingHACs.Num() <= 0) + return; + + for (auto& CurHAC : DisableAutoSavingHACs) + { + if (!CurHAC || CurHAC->IsPendingKill()) + DisableAutoSavingHACs.Remove(CurHAC); + } + } + else + { + // Otherwise, remove the HAC from the set + if (DisableAutoSavingHACs.Contains(HAC)) + DisableAutoSavingHACs.Remove(HAC); + } + + if (DisableAutoSavingHACs.Num() > 0) + return; + + // When no HAC disables cooking, reset min time till auto-save to default value, then reset the timer + IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); + AutoSaver.ForceMinimumTimeTillAutoSave(); // use default value + AutoSaver.ResetAutoSaveTimer(); +#endif } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.h b/Source/HoudiniEngine/Private/HoudiniEngineManager.h index 58fd0690d..770997109 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.h @@ -1,185 +1,185 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "TimerManager.h" - -//#include "HAL/Runnable.h" -//#include "HAL/RunnableThread.h" -//#include "Misc/SingleThreadRunnable.h" - -#include "HoudiniPDGManager.h" - -class UHoudiniAsset; -class UHoudiniAssetComponent; - -struct FHoudiniEngineTaskInfo; -struct FGuid; - -enum class EHoudiniAssetState : uint8; - -class FHoudiniEngineManager -{ -public: - - FHoudiniEngineManager(); - virtual ~FHoudiniEngineManager(); - - void StartHoudiniTicking(); - void StopHoudiniTicking(); - void Tick(); - - // Updates / Process a component - void ProcessComponent(UHoudiniAssetComponent* HAC); - - // Build UStaticMesh for all UHoudiniStaticMesh in a HAC. - // This is fired by the OnRefinedMeshesTimerDelegate on a HAC - void BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC); - - void StartPDGCommandlet() - { - if (!IsPDGCommandletRunningOrConnected()) - PDGManager.CreateBGEOCommandletAndEndpoint(); - } - - void StopPDGCommandlet() - { - if (IsPDGCommandletRunningOrConnected()) - PDGManager.StopBGEOCommandletAndEndpoint(); - } - - bool IsPDGCommandletRunningOrConnected() - { - const EHoudiniBGEOCommandletStatus Status = PDGManager.UpdateAndGetBGEOCommandletStatus(); - return Status == EHoudiniBGEOCommandletStatus::Running || Status == EHoudiniBGEOCommandletStatus::Connected; - } - - EHoudiniBGEOCommandletStatus GetPDGCommandletStatus() { return PDGManager.UpdateAndGetBGEOCommandletStatus(); } - - -protected: - - // Updates a given task's status - // Returns true if the given task's status was properly found - bool UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo); - - // Start a task to instantiate the given HoudiniAsset - // Return true if the task was successfully created - bool StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID); - - // Updates progress of the instantiation task - // Returns true if a state change should be made - bool UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); - - // Start a task to instantiate the Houdini Asset with the given node Id - // Returns true if the task was successfully created - bool StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID); - - // Updates progress of the cooking task - // Returns true if a state change should be made - bool UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); - - // Called to update template components. - bool PreCookTemplate(UHoudiniAssetComponent* HAC); - - // Called to update all houdini nodes/params/inputs before a cook has started - bool PreCook(UHoudiniAssetComponent* HAC); - - // Called after a cook has finished - bool PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId); - - bool StartTaskAssetProcess(UHoudiniAssetComponent* HAC); - - bool UpdateProcess(UHoudiniAssetComponent* HAC); - - // Starts a rebuild task (delete then re instantiate) - // The NodeID should be invalidated after a successful call - bool StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID); - - // Starts a node delete task - // The NodeID should be invalidated after a successful call - bool StartTaskAssetDelete(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID, bool bShouldDeleteParent); - - bool IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC); - - // Syncs the houdini viewport to Unreal's viewport - // Returns true if the Houdini viewport has been modified - bool SyncHoudiniViewportToUnreal(); - - // Syncs the unreal viewport to Houdini's viewport - // Returns true if the Unreal viewport has been modified - bool SyncUnrealViewportToHoudini(); - - // Disable auto save by setting min time till auto save to the max value - void DisableEditorAutoSave(const UHoudiniAssetComponent* HAC); - - void EnableEditorAutoSave(const UHoudiniAssetComponent* HAC); - -private: - - // Delay between each update of the manager - static const float TickTimerDelay; - - // Timer handle, this timer is used for processing HAC. - FTimerHandle TimerHandleProcess; - - // Timer delegate, we use it for ticking during processing. - FTimerDelegate TimerDelegateProcess; - - // Current position in the array - uint32 CurrentIndex; - - // Current number of components in the array - uint32 ComponentCount; - - // Stopping flag. - // Indicates that we should stop ticking asap - bool bMustStopTicking; - - // The PDG Manager, handles all registered PDG Asset Links - FHoudiniPDGManager PDGManager; - - // For ViewportSync: The camera transform that Hapi and Unreal currently agree with. - FVector SyncedHoudiniViewportPivotPosition; - FQuat SyncedHoudiniViewportQuat; - float SyncedHoudiniViewportOffset; - - FVector SyncedUnrealViewportPosition; - FRotator SyncedUnrealViewportRotation; - FVector SyncedUnrealViewportLookatPosition; - - // We need these two variables to get rid of a HAPI bug - // Note: When sync Houdini to UE, we set the pivot position to be the view position, and offset to be 0.0. - // but when we switch to control viewport in Houdini, HAPI returns an H_View with a large offset, but pivot unchanged. - // so, we need these two variables to 'zero out' offset. - float ZeroOffsetValue; // in HAPI scale - bool bOffsetZeroed; - - // Indicates which HACs disable auto-saving - TSet DisableAutoSavingHACs; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "TimerManager.h" + +//#include "HAL/Runnable.h" +//#include "HAL/RunnableThread.h" +//#include "Misc/SingleThreadRunnable.h" + +#include "HoudiniPDGManager.h" + +class UHoudiniAsset; +class UHoudiniAssetComponent; + +struct FHoudiniEngineTaskInfo; +struct FGuid; + +enum class EHoudiniAssetState : uint8; + +class FHoudiniEngineManager +{ +public: + + FHoudiniEngineManager(); + virtual ~FHoudiniEngineManager(); + + void StartHoudiniTicking(); + void StopHoudiniTicking(); + void Tick(); + + // Updates / Process a component + void ProcessComponent(UHoudiniAssetComponent* HAC); + + // Build UStaticMesh for all UHoudiniStaticMesh in a HAC. + // This is fired by the OnRefinedMeshesTimerDelegate on a HAC + void BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC); + + void StartPDGCommandlet() + { + if (!IsPDGCommandletRunningOrConnected()) + PDGManager.CreateBGEOCommandletAndEndpoint(); + } + + void StopPDGCommandlet() + { + if (IsPDGCommandletRunningOrConnected()) + PDGManager.StopBGEOCommandletAndEndpoint(); + } + + bool IsPDGCommandletRunningOrConnected() + { + const EHoudiniBGEOCommandletStatus Status = PDGManager.UpdateAndGetBGEOCommandletStatus(); + return Status == EHoudiniBGEOCommandletStatus::Running || Status == EHoudiniBGEOCommandletStatus::Connected; + } + + EHoudiniBGEOCommandletStatus GetPDGCommandletStatus() { return PDGManager.UpdateAndGetBGEOCommandletStatus(); } + + +protected: + + // Updates a given task's status + // Returns true if the given task's status was properly found + bool UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo); + + // Start a task to instantiate the given HoudiniAsset + // Return true if the task was successfully created + bool StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID); + + // Updates progress of the instantiation task + // Returns true if a state change should be made + bool UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); + + // Start a task to instantiate the Houdini Asset with the given node Id + // Returns true if the task was successfully created + bool StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID); + + // Updates progress of the cooking task + // Returns true if a state change should be made + bool UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); + + // Called to update template components. + bool PreCookTemplate(UHoudiniAssetComponent* HAC); + + // Called to update all houdini nodes/params/inputs before a cook has started + bool PreCook(UHoudiniAssetComponent* HAC); + + // Called after a cook has finished + bool PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId); + + bool StartTaskAssetProcess(UHoudiniAssetComponent* HAC); + + bool UpdateProcess(UHoudiniAssetComponent* HAC); + + // Starts a rebuild task (delete then re instantiate) + // The NodeID should be invalidated after a successful call + bool StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID); + + // Starts a node delete task + // The NodeID should be invalidated after a successful call + bool StartTaskAssetDelete(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID, bool bShouldDeleteParent); + + bool IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC); + + // Syncs the houdini viewport to Unreal's viewport + // Returns true if the Houdini viewport has been modified + bool SyncHoudiniViewportToUnreal(); + + // Syncs the unreal viewport to Houdini's viewport + // Returns true if the Unreal viewport has been modified + bool SyncUnrealViewportToHoudini(); + + // Disable auto save by setting min time till auto save to the max value + void DisableEditorAutoSave(const UHoudiniAssetComponent* HAC); + + void EnableEditorAutoSave(const UHoudiniAssetComponent* HAC); + +private: + + // Delay between each update of the manager + static const float TickTimerDelay; + + // Timer handle, this timer is used for processing HAC. + FTimerHandle TimerHandleProcess; + + // Timer delegate, we use it for ticking during processing. + FTimerDelegate TimerDelegateProcess; + + // Current position in the array + uint32 CurrentIndex; + + // Current number of components in the array + uint32 ComponentCount; + + // Stopping flag. + // Indicates that we should stop ticking asap + bool bMustStopTicking; + + // The PDG Manager, handles all registered PDG Asset Links + FHoudiniPDGManager PDGManager; + + // For ViewportSync: The camera transform that Hapi and Unreal currently agree with. + FVector SyncedHoudiniViewportPivotPosition; + FQuat SyncedHoudiniViewportQuat; + float SyncedHoudiniViewportOffset; + + FVector SyncedUnrealViewportPosition; + FRotator SyncedUnrealViewportRotation; + FVector SyncedUnrealViewportLookatPosition; + + // We need these two variables to get rid of a HAPI bug + // Note: When sync Houdini to UE, we set the pivot position to be the view position, and offset to be 0.0. + // but when we switch to control viewport in Houdini, HAPI returns an H_View with a large offset, but pivot unchanged. + // so, we need these two variables to 'zero out' offset. + float ZeroOffsetValue; // in HAPI scale + bool bOffsetZeroed; + + // Indicates which HACs disable auto-saving + TSet DisableAutoSavingHACs; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp index a8648c131..908540b9f 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp @@ -1,35 +1,35 @@ - -#include "HoudiniEngineOutputStats.h" - -FHoudiniEngineOutputStats::FHoudiniEngineOutputStats() - : NumPackagesCreated(0) - , NumPackagesUpdated(0) -{ } - -void FHoudiniEngineOutputStats::NotifyPackageCreated(int32 NumCreated) -{ - NumPackagesCreated += NumCreated; -} - -void FHoudiniEngineOutputStats::NotifyPackageUpdated(int32 NumUpdated) -{ - NumPackagesUpdated += NumUpdated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated) -{ - const int32 Count = OutputObjectsCreated.FindOrAdd(ObjectTypeName, 0); - OutputObjectsCreated[ObjectTypeName] = Count + NumCreated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated) -{ - const int32 Count = OutputObjectsUpdated.FindOrAdd(ObjectTypeName, 0); - OutputObjectsUpdated[ObjectTypeName] = Count + NumUpdated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced) -{ - const int32 Count = OutputObjectsReplaced.FindOrAdd(ObjectTypeName, 0); - OutputObjectsReplaced[ObjectTypeName] = Count + NumReplaced; + +#include "HoudiniEngineOutputStats.h" + +FHoudiniEngineOutputStats::FHoudiniEngineOutputStats() + : NumPackagesCreated(0) + , NumPackagesUpdated(0) +{ } + +void FHoudiniEngineOutputStats::NotifyPackageCreated(int32 NumCreated) +{ + NumPackagesCreated += NumCreated; +} + +void FHoudiniEngineOutputStats::NotifyPackageUpdated(int32 NumUpdated) +{ + NumPackagesUpdated += NumUpdated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated) +{ + const int32 Count = OutputObjectsCreated.FindOrAdd(ObjectTypeName, 0); + OutputObjectsCreated[ObjectTypeName] = Count + NumCreated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated) +{ + const int32 Count = OutputObjectsUpdated.FindOrAdd(ObjectTypeName, 0); + OutputObjectsUpdated[ObjectTypeName] = Count + NumUpdated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced) +{ + const int32 Count = OutputObjectsReplaced.FindOrAdd(ObjectTypeName, 0); + OutputObjectsReplaced[ObjectTypeName] = Count + NumReplaced; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h index edeae4726..26c6f022f 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Class.h" - -struct HOUDINIENGINE_API FHoudiniEngineOutputStats -{ - FHoudiniEngineOutputStats(); - - int32 NumPackagesCreated; - int32 NumPackagesUpdated; - - // These FStrings should preferably be EHoudiniOutputType enum - // Move the OUtput enums into a separate header to avoid circular dependencies. - TMap OutputObjectsCreated; - TMap OutputObjectsUpdated; - TMap OutputObjectsReplaced; - - void NotifyPackageCreated(int32 NumCreated); - void NotifyPackageUpdated(int32 NumUpdated); - - // Objects created - void NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated); - template - void NotifyObjectsCreated(EnumT EnumValue, int32 NumCreated) - { - NotifyObjectsCreated( UEnum::GetValueAsString(EnumValue), NumCreated ); - } - - // Object updated - void NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated); - template - void NotifyObjectsUpdated(EnumT EnumValue, int32 NumUpdated) - { - NotifyObjectsUpdated( UEnum::GetValueAsString(EnumValue), NumUpdated ); - } - - // Objects replaced - void NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced); - template - void NotifyObjectsReplaced(EnumT EnumValue, int32 NumReplaced) - { - NotifyObjectsReplaced( UEnum::GetValueAsString(EnumValue), NumReplaced ); - } -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Class.h" + +struct HOUDINIENGINE_API FHoudiniEngineOutputStats +{ + FHoudiniEngineOutputStats(); + + int32 NumPackagesCreated; + int32 NumPackagesUpdated; + + // These FStrings should preferably be EHoudiniOutputType enum + // Move the OUtput enums into a separate header to avoid circular dependencies. + TMap OutputObjectsCreated; + TMap OutputObjectsUpdated; + TMap OutputObjectsReplaced; + + void NotifyPackageCreated(int32 NumCreated); + void NotifyPackageUpdated(int32 NumUpdated); + + // Objects created + void NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated); + template + void NotifyObjectsCreated(EnumT EnumValue, int32 NumCreated) + { + NotifyObjectsCreated( UEnum::GetValueAsString(EnumValue), NumCreated ); + } + + // Object updated + void NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated); + template + void NotifyObjectsUpdated(EnumT EnumValue, int32 NumUpdated) + { + NotifyObjectsUpdated( UEnum::GetValueAsString(EnumValue), NumUpdated ); + } + + // Objects replaced + void NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced); + template + void NotifyObjectsReplaced(EnumT EnumValue, int32 NumReplaced) + { + NotifyObjectsReplaced( UEnum::GetValueAsString(EnumValue), NumReplaced ); + } +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h index 0955274f4..f2cd4eee3 100644 --- a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h +++ b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h @@ -1,374 +1,382 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -// Indicate we're in the HoudiniEngine module -#define HOUDINI_ENGINE -#include "HoudiniEngineRuntimePrivatePCH.h" - -// HFS path definition coming from UBT/build.cs files. -#ifndef HOUDINI_ENGINE_HFS_PATH_DEFINE - #define HOUDINI_ENGINE_HFS_PATH "" -#else - #define HOUDINI_ENGINE_STRINGIFY_HELPER(X) #X - #define HOUDINI_ENGINE_STRINGIFY(X) HOUDINI_ENGINE_STRINGIFY_HELPER(X) - #define HOUDINI_ENGINE_HFS_PATH HOUDINI_ENGINE_STRINGIFY(HOUDINI_ENGINE_HFS_PATH_DEFINE) -#endif - -// HFS subfolder containing HAPI lib. -#define HAPI_HFS_SUBFOLDER_WINDOWS TEXT( "bin" ) -#define HAPI_HFS_SUBFOLDER_MAC TEXT( "dsolib" ) -#define HAPI_HFS_SUBFOLDER_LINUX TEXT( "dsolib" ) - -// Unreal HAPI Resources. -#define HAPI_UNREAL_RESOURCE_HOUDINI_LOGO TEXT( "/HoudiniEngine/houdini_logo.houdini_logo" ) -#define HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL TEXT( "/HoudiniEngine/houdini_default_material.houdini_default_material" ) -#define HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL TEXT( "/HoudiniEngine/houdini_templated_material.houdini_templated_material") -#define HAPI_UNREAL_RESOURCE_BGEO_IMPORT TEXT( "/HoudiniEngine/houdini_bgeo_import.houdini_bgeo_import" ) - -#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH TEXT("/HoudiniEngine/default_reference_static_mesh.default_reference_static_mesh") -#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL TEXT("/HoudiniEngine/default_reference_static_mesh_material.default_reference_static_mesh_material") - -// Client name so HAPI knows we're running inside unreal -#define HAPI_UNREAL_CLIENT_NAME "unreal" - -// Error checking - this macro will check the status and return specified parameter. -#define HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HAPI_LOG_ROUTINE ) \ - do \ - { \ - HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ - if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - return HAPI_PARAM_RETURN; \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR_RETURN( HAPI_PARAM_CALL, HAPI_PARAM_RETURN ) \ - HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HOUDINI_LOG_ERROR ) - -// Simple Error checking - this macro will check the status. -#define HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ - do \ - { \ - HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ - if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR( HAPI_PARAM_CALL ) \ - HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) - -// Error checking - this macro will check the status and returns it. -#define HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ - do \ - { \ - *HAPI_PARAM_RESULT = HAPI_PARAM_CALL; \ - if ( *HAPI_PARAM_RESULT != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR_GET( HAPI_PARAM_RESULT, HAPI_PARAM_CALL ) \ - HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) - -// For Transform conversion between UE4 / Houdini -// Set to 0 to stop converting Houdini's coordinate space to unreal's -#define HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM 1 - -#define HAPI_UNREAL_SCALE_FACTOR_POSITION 100.0f -#define HAPI_UNREAL_SCALE_FACTOR_TRANSLATION 100.0f -#define HAPI_UNREAL_SCALE_FACTOR_SCALE 100.0f - -#define HAPI_UNREAL_SCALE_SMALL_VALUE KINDA_SMALL_NUMBER * 2.0f - -#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) - -// Attributes -#define HAPI_UNREAL_ATTRIB_POSITION HAPI_ATTRIB_POSITION -#define HAPI_UNREAL_ATTRIB_ROTATION "rot" -#define HAPI_UNREAL_ATTRIB_SCALE "scale" -#define HAPI_UNREAL_ATTRIB_UNIFORM_SCALE "pscale" -#define HAPI_UNREAL_ATTRIB_COLOR HAPI_ATTRIB_COLOR -#define HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR "unreal_lightmap_color" -#define HAPI_UNREAL_ATTRIB_ALPHA "Alpha" -#define HAPI_UNREAL_ATTRIB_UV HAPI_ATTRIB_UV -#define HAPI_UNREAL_ATTRIB_NORMAL HAPI_ATTRIB_NORMAL -#define HAPI_UNREAL_ATTRIB_TANGENTU HAPI_ATTRIB_TANGENT -#define HAPI_UNREAL_ATTRIB_TANGENTV HAPI_ATTRIB_TANGENT2 - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE "tile" -// Always the name of the main landscape actor. -// Names for landscape tile actors will be taken from 'unreal_output_name'. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME "unreal_landscape_shared_actor_name" -// This tile_actor_type succeeds the 'unreal_landscape_streaming_proxy' (v1) attribute. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE "unreal_landscape_tile_actor_type" -// This attribute is for backwards compatibility only. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN "unreal_landscape_layer_min" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX "unreal_landscape_layer_max" - -// Path to the level in which an actor should be generated or which contained the input data -// "." - (Default) Generate geometry in the the current persistent world -// "Junk/Background" - Path to a Map that is relative to the current persistent world's Map. -// "/Game/Maps/Level01/Junk/Background" - Absolute path to the map in which the primitive should be output -#define HAPI_UNREAL_ATTRIB_LEVEL_PATH "unreal_level_path" - -// Path to the actor that contained the input data/should be generated -#define HAPI_UNREAL_ATTRIB_ACTOR_PATH "unreal_actor_path" - -// Path to the object plugged in a geo in -#define HAPI_UNREAL_ATTRIB_OBJECT_PATH "unreal_object_path" - -// Attributes used for data exchange between UE4 and Houdini -#define HAPI_UNREAL_ATTRIB_MATERIAL "unreal_material" -#define HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK "unreal_face_material" -#define HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE "unreal_material_instance" -#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE "unreal_material_hole" -#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE "unreal_material_hole_instance" -#define HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL "unreal_physical_material" -#define HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK "unreal_face_smoothing_mask" -#define HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION "unreal_lightmap_resolution" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE "lod_screensize" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX "lod" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX "_screensize" -#define HAPI_UNREAL_ATTRIB_TAG_PREFIX "unreal_tag_" - -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX "mesh_socket" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME "mesh_socket_name" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD "unreal_mesh_socket_name" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR "mesh_socket_actor" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD "unreal_mesh_socket_actor" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG "mesh_socket_tag" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD "unreal_mesh_socket_tag" - -#define HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME "unreal_input_mesh_name" -#define HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE "unreal_input_source_file" - -#define HAPI_UNREAL_ATTRIB_INSTANCE "instance" -#define HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE "unreal_instance" -#define HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES "unreal_split_instances" -#define HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER "unreal_foliage" -#define HAPI_UNREAL_ATTRIB_INSTANCE_ROTATION "rot" -#define HAPI_UNREAL_ATTRIB_INSTANCE_SCALE "scale" -#define HAPI_UNREAL_ATTRIB_INSTANCE_POSITION HAPI_ATTRIB_POSITION -#define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" -#define HAPI_UNREAL_ATTRIB_SPLIT_ATTR "unreal_split_attr" -#define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" - - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX "unreal_vertex_index" -#define HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER "unreal_unit_landscape_layer" -#define HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS "unreal_landscape_layer_nonweightblended" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO "unreal_landscape_layer_info" - -#define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" -#define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" - -#define HAPI_UNREAL_ATTRIB_BAKE_FOLDER "unreal_bake_folder" -#define HAPI_UNREAL_ATTRIB_TEMP_FOLDER "unreal_temp_folder" -#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1 "unreal_generated_mesh_name" -#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2 "unreal_output_name" -#define HAPI_UNREAL_ATTRIB_BAKE_ACTOR "unreal_bake_actor" -#define HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER "unreal_bake_outliner_folder" - -// data tables -#define HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX "unreal_data_table_" -#define HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT "unreal_datatable_rowstruct" - -// Attributes for Curve Outputs -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE "unreal_output_curve" -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR "unreal_output_curve_linear" -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED "unreal_output_curve_closed" -// We only support Unreal spline outputs for now -//#define HAPI_UNREAL_ATTRIB_OUTPUT_HOUDINI_CURVE "houdini_output_curve" - -// Geometry Node -#define HAPI_UNREAL_PARAM_TRANSLATE "t" -#define HAPI_UNREAL_PARAM_ROTATE "r" -#define HAPI_UNREAL_PARAM_SCALE "s" -#define HAPI_UNREAL_PARAM_PIVOT "p" -#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" - -// Houdini Curve -#define HAPI_UNREAL_PARAM_CURVE_TYPE "type" -#define HAPI_UNREAL_PARAM_CURVE_METHOD "method" -#define HAPI_UNREAL_PARAM_CURVE_COORDS "coords" -#define HAPI_UNREAL_PARAM_CURVE_CLOSED "close" -#define HAPI_UNREAL_PARAM_CURVE_REVERSED "reverse" - -#define HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT "0.0, 0.0, 3.0 3.0, 0.0, 3.0" - -// String Params tags -#define HOUDINI_PARAMETER_STRING_REF_TAG TEXT("unreal_ref") -#define HOUDINI_PARAMETER_STRING_REF_CLASS_TAG TEXT("unreal_ref_class") -#define HOUDINI_PARAMETER_STRING_MULTILINE_TAG TEXT("editor") - -// Groups -#define HAPI_UNREAL_GROUP_LOD_PREFIX TEXT("lod") -#define HAPI_UNREAL_GROUP_SOCKET_PREFIX TEXT("mesh_socket") -#define HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD TEXT("socket") -//#define HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX TEXT("unreal_split") - -#define HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION TEXT("main_geo") - -#define HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX TEXT("collision_geo") -#define HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX TEXT("rendered_collision_geo") - -#define HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX TEXT("collision_geo_ucx") -#define HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX TEXT("rendered_collision_geo_ucx") - -#define HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX TEXT("collision_geo_simple") -#define HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX TEXT("rendered_collision_geo_simple") - -// Default material name. -#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) - -// Various variable names used to store meta information in generated packages. -#define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE TEXT( "HoudiniGeneratedTextureType" ) -#define HAPI_UNREAL_PACKAGE_META_NODE_PATH TEXT( "HoudiniNodePath" ) -#define HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER TEXT( "HoudiniPackageBakeCounter" ) -#define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) - -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL TEXT( "N" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE TEXT( "C_A" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR TEXT( "S" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS TEXT( "R" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC TEXT( "M" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE TEXT( "E" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK TEXT( "O" ) - -// Texture planes. -#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA "C A" -#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR "C" -#define HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA "A" -#define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" - -// Materials Diffuse. -#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" - -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 "ogl_tex1" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 "basecolor_texture" - -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0 "ogl_diff" -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1 "basecolor" - -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" - -// Materials Normal. -#define HAPI_UNREAL_PARAM_MAP_NORMAL_0 "ogl_normalmap" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_1 "normalTexture" - -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" - -#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" - -// Materials Specular. -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_0 "ogl_specmap" -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_1 "reflect_texture" - -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_0 "ogl_spec" -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_1 "reflect" - -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" - -// Materials Roughness. -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0 "ogl_roughmap" -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1 "rough_texture" - -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0 "ogl_rough" -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1 "rough" - -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" - -// Materials Metallic. -#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" -#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" -#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" - -// Materials Emissive. -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" - -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0 "ogl_emit" -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1 "emitcolor" - -// Materials Opacity. -#define HAPI_UNREAL_PARAM_ALPHA_0 "ogl_alpha" -#define HAPI_UNREAL_PARAM_ALPHA_1 "opac" - -#define HAPI_UNREAL_PARAM_MAP_OPACITY_0 "ogl_opacitymap" -#define HAPI_UNREAL_PARAM_MAP_OPACITY_1 "opaccolor_texture" - -// Number of GUID characters to keep for packages -#define PACKAGE_GUID_LENGTH 8 -#define PACKAGE_GUID_COMPONENT_LENGTH 12 - -/** Ramp related defines. **/ -#define HAPI_UNREAL_RAMP_FLOAT_AXIS_X "position" -#define HAPI_UNREAL_RAMP_FLOAT_AXIS_Y "value" -#define HAPI_UNREAL_RAMP_COLOR_AXIS_X "position" -#define HAPI_UNREAL_RAMP_COLOR_AXIS_Y "color" - -/** Handle types. **/ -#define HAPI_UNREAL_HANDLE_TRANSFORM "xform" -#define HAPI_UNREAL_HANDLE_BOUNDER "bound" - -#define HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME "height" - -#define HAPI_UNREAL_NOTIFICATION_FADEOUT 2.0f -#define HAPI_UNREAL_NOTIFICATION_EXPIRE 2.0f - -// Struct to enable global silent flag - this will force dialogs to not show up. -struct FHoudiniScopedGlobalSilence -{ - FHoudiniScopedGlobalSilence() - { - bGlobalSilent = GIsSilent; - GIsSilent = true; - } - - ~FHoudiniScopedGlobalSilence() - { - GIsSilent = bGlobalSilent; - } - - bool bGlobalSilent; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +// Indicate we're in the HoudiniEngine module +#define HOUDINI_ENGINE +#include "HoudiniEngineRuntimePrivatePCH.h" + +// HFS path definition coming from UBT/build.cs files. +#ifndef HOUDINI_ENGINE_HFS_PATH_DEFINE + #define HOUDINI_ENGINE_HFS_PATH "" +#else + #define HOUDINI_ENGINE_STRINGIFY_HELPER(X) #X + #define HOUDINI_ENGINE_STRINGIFY(X) HOUDINI_ENGINE_STRINGIFY_HELPER(X) + #define HOUDINI_ENGINE_HFS_PATH HOUDINI_ENGINE_STRINGIFY(HOUDINI_ENGINE_HFS_PATH_DEFINE) +#endif + +// HFS subfolder containing HAPI lib. +#define HAPI_HFS_SUBFOLDER_WINDOWS TEXT( "bin" ) +#define HAPI_HFS_SUBFOLDER_MAC TEXT( "dsolib" ) +#define HAPI_HFS_SUBFOLDER_LINUX TEXT( "dsolib" ) + +// Unreal HAPI Resources. +#define HAPI_UNREAL_RESOURCE_HOUDINI_LOGO TEXT( "/HoudiniEngine/houdini_logo.houdini_logo" ) +#define HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL TEXT( "/HoudiniEngine/houdini_default_material.houdini_default_material" ) +#define HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL TEXT( "/HoudiniEngine/houdini_templated_material.houdini_templated_material") +#define HAPI_UNREAL_RESOURCE_BGEO_IMPORT TEXT( "/HoudiniEngine/houdini_bgeo_import.houdini_bgeo_import" ) + +#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH TEXT("/HoudiniEngine/default_reference_static_mesh.default_reference_static_mesh") +#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL TEXT("/HoudiniEngine/default_reference_static_mesh_material.default_reference_static_mesh_material") + +// Client name so HAPI knows we're running inside unreal +#define HAPI_UNREAL_CLIENT_NAME "unreal" + +// Error checking - this macro will check the status and return specified parameter. +#define HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HAPI_LOG_ROUTINE ) \ + do \ + { \ + HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ + if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + return HAPI_PARAM_RETURN; \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR_RETURN( HAPI_PARAM_CALL, HAPI_PARAM_RETURN ) \ + HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HOUDINI_LOG_ERROR ) + +// Simple Error checking - this macro will check the status. +#define HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ + do \ + { \ + HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ + if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR( HAPI_PARAM_CALL ) \ + HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) + +// Error checking - this macro will check the status and returns it. +#define HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ + do \ + { \ + *HAPI_PARAM_RESULT = HAPI_PARAM_CALL; \ + if ( *HAPI_PARAM_RESULT != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR_GET( HAPI_PARAM_RESULT, HAPI_PARAM_CALL ) \ + HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) + +// For Transform conversion between UE4 / Houdini +// Set to 0 to stop converting Houdini's coordinate space to unreal's +#define HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM 1 + +#define HAPI_UNREAL_SCALE_FACTOR_POSITION 100.0f +#define HAPI_UNREAL_SCALE_FACTOR_TRANSLATION 100.0f +#define HAPI_UNREAL_SCALE_FACTOR_SCALE 100.0f + +#define HAPI_UNREAL_SCALE_SMALL_VALUE KINDA_SMALL_NUMBER * 2.0f + +#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) + +// Attributes +#define HAPI_UNREAL_ATTRIB_POSITION HAPI_ATTRIB_POSITION +#define HAPI_UNREAL_ATTRIB_ROTATION "rot" +#define HAPI_UNREAL_ATTRIB_SCALE "scale" +#define HAPI_UNREAL_ATTRIB_UNIFORM_SCALE "pscale" +#define HAPI_UNREAL_ATTRIB_COLOR HAPI_ATTRIB_COLOR +#define HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR "unreal_lightmap_color" +#define HAPI_UNREAL_ATTRIB_ALPHA "Alpha" +#define HAPI_UNREAL_ATTRIB_UV HAPI_ATTRIB_UV +#define HAPI_UNREAL_ATTRIB_NORMAL HAPI_ATTRIB_NORMAL +#define HAPI_UNREAL_ATTRIB_TANGENTU HAPI_ATTRIB_TANGENT +#define HAPI_UNREAL_ATTRIB_TANGENTV HAPI_ATTRIB_TANGENT2 + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE "tile" +// Always the name of the main landscape actor. +// Names for landscape tile actors will be taken from 'unreal_output_name'. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME "unreal_landscape_shared_actor_name" +// This tile_actor_type succeeds the 'unreal_landscape_streaming_proxy' (v1) attribute. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE "unreal_landscape_tile_actor_type" +// This attribute is for backwards compatibility only. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN "unreal_landscape_layer_min" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX "unreal_landscape_layer_max" + +// Path to the level in which an actor should be generated or which contained the input data +// "." - (Default) Generate geometry in the the current persistent world +// "Junk/Background" - Path to a Map that is relative to the current persistent world's Map. +// "/Game/Maps/Level01/Junk/Background" - Absolute path to the map in which the primitive should be output +#define HAPI_UNREAL_ATTRIB_LEVEL_PATH "unreal_level_path" + +// Path to the actor that contained the input data/should be generated +#define HAPI_UNREAL_ATTRIB_ACTOR_PATH "unreal_actor_path" + +// Path to the object plugged in a geo in +#define HAPI_UNREAL_ATTRIB_OBJECT_PATH "unreal_object_path" + +// Attributes used for data exchange between UE4 and Houdini +#define HAPI_UNREAL_ATTRIB_MATERIAL "unreal_material" +#define HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK "unreal_face_material" +#define HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE "unreal_material_instance" +#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE "unreal_material_hole" +#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE "unreal_material_hole_instance" +#define HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL "unreal_physical_material" +#define HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK "unreal_face_smoothing_mask" +#define HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION "unreal_lightmap_resolution" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE "lod_screensize" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX "lod" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX "_screensize" +#define HAPI_UNREAL_ATTRIB_TAG_PREFIX "unreal_tag_" + +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX "mesh_socket" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME "mesh_socket_name" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD "unreal_mesh_socket_name" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR "mesh_socket_actor" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD "unreal_mesh_socket_actor" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG "mesh_socket_tag" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD "unreal_mesh_socket_tag" + +#define HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME "unreal_input_mesh_name" +#define HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE "unreal_input_source_file" + +#define HAPI_UNREAL_ATTRIB_INSTANCE "instance" +#define HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE "unreal_instance" +#define HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES "unreal_split_instances" +#define HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER "unreal_foliage" +#define HAPI_UNREAL_ATTRIB_INSTANCE_ROTATION "rot" +#define HAPI_UNREAL_ATTRIB_INSTANCE_SCALE "scale" +#define HAPI_UNREAL_ATTRIB_INSTANCE_POSITION HAPI_ATTRIB_POSITION +#define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" +#define HAPI_UNREAL_ATTRIB_SPLIT_ATTR "unreal_split_attr" +#define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" + + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX "unreal_vertex_index" +#define HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER "unreal_unit_landscape_layer" +#define HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS "unreal_landscape_layer_nonweightblended" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO "unreal_landscape_layer_info" + +#define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" +#define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" + +#define HAPI_UNREAL_ATTRIB_BAKE_FOLDER "unreal_bake_folder" +#define HAPI_UNREAL_ATTRIB_TEMP_FOLDER "unreal_temp_folder" +#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1 "unreal_generated_mesh_name" +#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2 "unreal_output_name" +#define HAPI_UNREAL_ATTRIB_BAKE_ACTOR "unreal_bake_actor" +#define HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER "unreal_bake_outliner_folder" + +// data tables +#define HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX "unreal_data_table_" +#define HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT "unreal_datatable_rowstruct" + +// Attributes for Curve Outputs +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE "unreal_output_curve" +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR "unreal_output_curve_linear" +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED "unreal_output_curve_closed" +// We only support Unreal spline outputs for now +//#define HAPI_UNREAL_ATTRIB_OUTPUT_HOUDINI_CURVE "houdini_output_curve" + +// Geometry Node +#define HAPI_UNREAL_PARAM_TRANSLATE "t" +#define HAPI_UNREAL_PARAM_ROTATE "r" +#define HAPI_UNREAL_PARAM_SCALE "s" +#define HAPI_UNREAL_PARAM_PIVOT "p" +#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" + +// Houdini Curve +#define HAPI_UNREAL_PARAM_CURVE_TYPE "type" +#define HAPI_UNREAL_PARAM_CURVE_METHOD "method" +#define HAPI_UNREAL_PARAM_CURVE_COORDS "coords" +#define HAPI_UNREAL_PARAM_CURVE_CLOSED "close" +#define HAPI_UNREAL_PARAM_CURVE_REVERSED "reverse" + +#define HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT "0.0, 0.0, 3.0 3.0, 0.0, 3.0" + +// String Params tags +#define HOUDINI_PARAMETER_STRING_REF_TAG TEXT("unreal_ref") +#define HOUDINI_PARAMETER_STRING_REF_CLASS_TAG TEXT("unreal_ref_class") +#define HOUDINI_PARAMETER_STRING_MULTILINE_TAG TEXT("editor") + +// Parameter tags +#define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" +#define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" +#define HAPI_PARAM_TAG_UNITS "units" + +// TODO: unused, remove! +#define HAPI_PARAM_TAG_ASSET_REF "asset_ref" + +// Groups +#define HAPI_UNREAL_GROUP_LOD_PREFIX TEXT("lod") +#define HAPI_UNREAL_GROUP_SOCKET_PREFIX TEXT("mesh_socket") +#define HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD TEXT("socket") +//#define HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX TEXT("unreal_split") + +#define HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION TEXT("main_geo") + +#define HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX TEXT("collision_geo") +#define HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX TEXT("rendered_collision_geo") + +#define HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX TEXT("collision_geo_ucx") +#define HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX TEXT("rendered_collision_geo_ucx") + +#define HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX TEXT("collision_geo_simple") +#define HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX TEXT("rendered_collision_geo_simple") + +// Default material name. +#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) + +// Various variable names used to store meta information in generated packages. +#define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE TEXT( "HoudiniGeneratedTextureType" ) +#define HAPI_UNREAL_PACKAGE_META_NODE_PATH TEXT( "HoudiniNodePath" ) +#define HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER TEXT( "HoudiniPackageBakeCounter" ) +#define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) + +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL TEXT( "N" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE TEXT( "C_A" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR TEXT( "S" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS TEXT( "R" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC TEXT( "M" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE TEXT( "E" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK TEXT( "O" ) + +// Texture planes. +#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA "C A" +#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR "C" +#define HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA "A" +#define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" + +// Materials Diffuse. +#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 "ogl_tex1" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 "basecolor_texture" + +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0 "ogl_diff" +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1 "basecolor" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" + +// Materials Normal. +#define HAPI_UNREAL_PARAM_MAP_NORMAL_0 "ogl_normalmap" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_1 "normalTexture" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" + +// Materials Specular. +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_0 "ogl_specmap" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_1 "reflect_texture" + +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_0 "ogl_spec" +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_1 "reflect" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" + +// Materials Roughness. +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0 "ogl_roughmap" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1 "rough_texture" + +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0 "ogl_rough" +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1 "rough" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" + +// Materials Metallic. +#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" +#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" + +// Materials Emissive. +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" + +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0 "ogl_emit" +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1 "emitcolor" + +// Materials Opacity. +#define HAPI_UNREAL_PARAM_ALPHA_0 "ogl_alpha" +#define HAPI_UNREAL_PARAM_ALPHA_1 "opac" + +#define HAPI_UNREAL_PARAM_MAP_OPACITY_0 "ogl_opacitymap" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_1 "opaccolor_texture" + +// Number of GUID characters to keep for packages +#define PACKAGE_GUID_LENGTH 8 +#define PACKAGE_GUID_COMPONENT_LENGTH 12 + +/** Ramp related defines. **/ +#define HAPI_UNREAL_RAMP_FLOAT_AXIS_X "position" +#define HAPI_UNREAL_RAMP_FLOAT_AXIS_Y "value" +#define HAPI_UNREAL_RAMP_COLOR_AXIS_X "position" +#define HAPI_UNREAL_RAMP_COLOR_AXIS_Y "color" + +/** Handle types. **/ +#define HAPI_UNREAL_HANDLE_TRANSFORM "xform" +#define HAPI_UNREAL_HANDLE_BOUNDER "bound" + +#define HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME "height" + +#define HAPI_UNREAL_NOTIFICATION_FADEOUT 2.0f +#define HAPI_UNREAL_NOTIFICATION_EXPIRE 2.0f + +// Struct to enable global silent flag - this will force dialogs to not show up. +struct FHoudiniScopedGlobalSilence +{ + FHoudiniScopedGlobalSilence() + { + bGlobalSilent = GIsSilent; + GIsSilent = true; + } + + ~FHoudiniScopedGlobalSilence() + { + GIsSilent = bGlobalSilent; + } + + bool bGlobalSilent; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp b/Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp index c5fdea932..8a63839cc 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp @@ -1,25 +1,25 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.h b/Source/HoudiniEngine/Private/HoudiniEngineProcessor.h index 05b5eb527..bdac5a8a6 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineProcessor.h @@ -1,27 +1,27 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + #pragma once \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp index 4b73aed40..6eeeeddcf 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp @@ -1,619 +1,619 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineScheduler.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngineString.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngine.h" - -const uint32 -FHoudiniEngineScheduler::InitialTaskSize = 256u; - -const float -FHoudiniEngineScheduler::UpdateFrequency = 0.1f; - -FHoudiniEngineScheduler::FHoudiniEngineScheduler() - : Tasks(nullptr) - , PositionWrite(0u) - , PositionRead(0u) - , bStopping(false) -{ - // Make sure size is power of two. - TaskCount = FPlatformMath::RoundUpToPowerOfTwo(FHoudiniEngineScheduler::InitialTaskSize); - - if (TaskCount) - { - // Allocate buffer to store all tasks. - Tasks = static_cast(FMemory::Malloc(TaskCount * sizeof(FHoudiniEngineTask))); - - if (Tasks) - { - // Zero memory. - FMemory::Memset(Tasks, 0x0, TaskCount * sizeof(FHoudiniEngineTask)); - } - } -} - -FHoudiniEngineScheduler::~FHoudiniEngineScheduler() -{ - if (TaskCount) - { - FMemory::Free(Tasks); - Tasks = nullptr; - } -} - -void -FHoudiniEngineScheduler::TaskDescription( - FHoudiniEngineTaskInfo & TaskInfo, - const FString & ActorName, - const FString & StatusString) -{ - FFormatNamedArguments Args; - - if (!ActorName.IsEmpty()) - { - Args.Add(TEXT("AssetName"), FText::FromString(ActorName)); - Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); - TaskInfo.StatusText = - FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetName}) : ({AssetStatus})"), Args); - } - else - { - Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); - TaskInfo.StatusText = - FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetStatus})"), Args); - } -} - -void -FHoudiniEngineScheduler::TaskInstantiateAsset(const FHoudiniEngineTask & Task) -{ - FString AssetN; - FHoudiniEngineString(Task.AssetHapiName).ToFString(AssetN); - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Instantiation Started for %s: Asset=%s, HoudiniAsset = 0x%x"), - *Task.ActorName, *AssetN, Task.Asset.Get()); - - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskInstantiateAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - if (!Task.Asset.IsValid()) - { - // Asset is no longer valid, return. - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset is no longer valid.")); - - return; - } - - if (Task.AssetHapiName < 0) - { - // Asset is no longer valid, return. - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset name is invalid.")); - - return; - } - - HAPI_Result Result = HAPI_RESULT_SUCCESS; - int32 AssetCount = 0; - HAPI_NodeId AssetId = -1; - std::string AssetNameString; - double LastUpdateTime; - - FHoudiniEngineString HoudiniEngineString(Task.AssetHapiName); - if (!HoudiniEngineString.ToStdString(AssetNameString)) - { - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Error retrieving asset name.")); - - return; - } - - // Translate asset name into Unreal string. - FString AssetName = ANSI_TO_TCHAR(AssetNameString.c_str()); - - // Initialize last update time. - LastUpdateTime = FPlatformTime::Seconds(); - - // We instantiate without cooking. - Result = FHoudiniApi::CreateNode( - FHoudiniEngine::Get().GetSession(), -1, &AssetNameString[0], nullptr, false, &AssetId); - if (Result != HAPI_RESULT_SUCCESS) - { - AddResponseMessageTaskInfo( - Result, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Error instantiating asset.")); - - return; - } - - // Add processing notification. - FHoudiniEngineTaskInfo TaskInfo( - HAPI_RESULT_SUCCESS, -1, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Working); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - TaskDescription(TaskInfo, Task.ActorName, TEXT("Started Instantiation")); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); - - // We need to spin until instantiation is finished. - while (true) - { - int Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // Cooking has been successful. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Success, AssetId, Task, - TEXT("Finished Instantiation.")); - - break; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // There was an error while instantiating. - FString CookResultString = FHoudiniEngineUtils::GetCookResult(); - int32 CookResult = static_cast(HAPI_RESULT_SUCCESS); - FHoudiniApi::GetStatus(FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_RESULT, &CookResult); - - EHoudiniEngineTaskState TaskStateResult = EHoudiniEngineTaskState::FinishedWithFatalError; - if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - TaskStateResult = EHoudiniEngineTaskState::FinishedWithError; - - AddResponseMessageTaskInfo( - static_cast(CookResult), - EHoudiniEngineTaskType::AssetInstantiation, - TaskStateResult, - AssetId, Task, - FString::Printf(TEXT("Finished Instantiation with Errors: %s"), *CookResultString)); - - break; - } - - static const double NotificationUpdateFrequency = 0.5; - if ((FPlatformTime::Seconds() - LastUpdateTime) >= NotificationUpdateFrequency) - { - // Reset update time. - LastUpdateTime = FPlatformTime::Seconds(); - const FString& CookStateMessage = FHoudiniEngineUtils::GetCookState(); - - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Working, - AssetId, Task, CookStateMessage); - } - - // We want to yield. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } -} - -void -FHoudiniEngineScheduler::TaskCookAsset(const FHoudiniEngineTask & Task) -{ - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskCookAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - // Retrieve asset id. - HAPI_NodeId AssetId = Task.AssetId; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Cooking Started for %s., AssetId = %d"), - *Task.ActorName, AssetId); - - if (AssetId == -1) - { - // We have an invalid asset id. - HOUDINI_LOG_ERROR(TEXT("TaskCookAsset failed for %s: Invalid Asset Id."), *Task.ActorName); - - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset has invalid id.")); - - return; - } - - // Default CookOptions - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - Result = FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), AssetId, &CookOptions); - if (Result != HAPI_RESULT_SUCCESS) - { - AddResponseMessageTaskInfo( - Result, EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - AssetId, Task, TEXT("Error cooking asset.")); - - return; - } - - // Add processing notification. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Working, - AssetId, Task, TEXT("Started Cooking")); - - // Initialize last update time. - double LastUpdateTime = FPlatformTime::Seconds(); - - // We need to spin until cooking is finished. - while (true) - { - int32 Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET( &Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // Cooking has been successful. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Success, - AssetId, Task, TEXT("Finished Cooking")); - - break; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - EHoudiniEngineTaskState TaskResult = EHoudiniEngineTaskState::FinishedWithFatalError; - if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - TaskResult = EHoudiniEngineTaskState::FinishedWithError; - - // There was an error while instantiating. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - TaskResult, - AssetId, Task, - TEXT("Finished Cooking with Errors")); - - break; - } - - static const double NotificationUpdateFrequency = 0.5; - if (FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency) - { - // Reset update time. - LastUpdateTime = FPlatformTime::Seconds(); - - // Retrieve status string. - const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); - - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Working, - AssetId, Task, CookStateMessage); - } - - // We want to yield. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } -} - -void -FHoudiniEngineScheduler::TaskDeleteAsset(const FHoudiniEngineTask & Task) -{ - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Destruction Started for %s. ") - TEXT("AssetId = %d"), - *Task.ActorName, Task.AssetId); - - if (FHoudiniEngineUtils::IsHoudiniNodeValid(Task.AssetId)) - FHoudiniEngineUtils::DestroyHoudiniAsset(Task.AssetId); - - // We do not insert task info as this is a fire and forget operation. - // At this point component most likely does not exist. -} - -void -FHoudiniEngineScheduler::AddResponseTaskInfo( - HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, const FHoudiniEngineTask & Task) -{ - FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); - FString StatusString = FHoudiniEngineUtils::GetErrorDescription(); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - - TaskDescription(TaskInfo, Task.ActorName, StatusString); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); -} - -void -FHoudiniEngineScheduler::AddResponseMessageTaskInfo( - HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, const FString & ErrorMessage) -{ - FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - - TaskDescription(TaskInfo, Task.ActorName, ErrorMessage); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); -} - -void -FHoudiniEngineScheduler::ProcessQueuedTasks() -{ - while (!bStopping) - { - while (true) - { - FHoudiniEngineTask Task; - - { - FScopeLock ScopeLock(&CriticalSection); - - // We have no tasks left. - if (PositionWrite == PositionRead) - break; - - // Retrieve task. - Task = Tasks[PositionRead]; - PositionRead++; - - // Wrap around if required. - PositionRead &= (TaskCount - 1); - } - - bool bTaskProcessed = true; - - switch (Task.TaskType) - { - case EHoudiniEngineTaskType::AssetInstantiation: - { - TaskInstantiateAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetCooking: - { - TaskCookAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetDeletion: - { - TaskDeleteAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetProcess: - { - TaskProccessAsset(Task); - break; - } - - default: - { - bTaskProcessed = false; - break; - } - } - - if (!bTaskProcessed) - break; - } - - if (FPlatformProcess::SupportsMultithreading()) - { - // We want to yield for a bit. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } - else - { - // If we are running in single threaded mode, return so we don't block everything else. - return; - } - } -} - -void -FHoudiniEngineScheduler::TaskProccessAsset(const FHoudiniEngineTask & Task) -{ - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskProccessAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - // Retrieve asset id. - HAPI_NodeId AssetId = Task.AssetId; - if (AssetId == -1) - { - // We have an invalid asset id. - HOUDINI_LOG_ERROR(TEXT("TaskProcessAsset failed for %s: Invalid Asset Id."), *Task.ActorName); - - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset has invalid id.")); - - return; - } - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Processing started for %s., AssetId = %d"), - *Task.ActorName, AssetId); - - // Add processing notification. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::Working, - AssetId, Task, TEXT("Started Cooking")); - - // - - - // TODO: Process results! -} - -void -FHoudiniEngineScheduler::AddTask(const FHoudiniEngineTask & Task) -{ - FScopeLock ScopeLock(&CriticalSection); - - // Check if we need to grow our circular buffer. - if (PositionWrite + 1 == PositionRead) - { - // Calculate next size (next power of two). - uint32 NextTaskCount = FPlatformMath::RoundUpToPowerOfTwo(TaskCount + 1); - - // Allocate new buffer. - FHoudiniEngineTask * Buffer = static_cast( - FMemory::Malloc(NextTaskCount * sizeof(FHoudiniEngineTask))); - - if (!Buffer) - return; - - // Zero memory. - FMemory::Memset(Buffer, 0x0, NextTaskCount * sizeof(FHoudiniEngineTask)); - - // Copy elements from old buffer to new one. - if (PositionRead < PositionWrite) - { - FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (PositionWrite - PositionRead)); - - // Update index positions. - PositionRead = 0; - PositionWrite = PositionWrite - PositionRead; - } - else - { - FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (TaskCount - PositionRead)); - FMemory::Memcpy(Buffer + TaskCount - PositionRead, Tasks, sizeof(FHoudiniEngineTask) * PositionWrite); - - // Update index positions. - PositionRead = 0; - PositionWrite = TaskCount - PositionRead + PositionWrite; - } - - // Deallocate old buffer. - FMemory::Free(Tasks); - - // Bookkeeping. - Tasks = Buffer; - TaskCount = NextTaskCount; - } - - // Store task. - Tasks[PositionWrite] = Task; - PositionWrite++; - - // Wrap around if required. - PositionWrite &= (TaskCount - 1); -} - -uint32 -FHoudiniEngineScheduler::Run() -{ - ProcessQueuedTasks(); - return 0; -} - -void -FHoudiniEngineScheduler::Stop() -{ - bStopping = true; -} - -void -FHoudiniEngineScheduler::Tick() -{ - ProcessQueuedTasks(); -} - -FSingleThreadRunnable * -FHoudiniEngineScheduler::GetSingleThreadInterface() -{ - return this; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineScheduler.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" + +const uint32 +FHoudiniEngineScheduler::InitialTaskSize = 256u; + +const float +FHoudiniEngineScheduler::UpdateFrequency = 0.1f; + +FHoudiniEngineScheduler::FHoudiniEngineScheduler() + : Tasks(nullptr) + , PositionWrite(0u) + , PositionRead(0u) + , bStopping(false) +{ + // Make sure size is power of two. + TaskCount = FPlatformMath::RoundUpToPowerOfTwo(FHoudiniEngineScheduler::InitialTaskSize); + + if (TaskCount) + { + // Allocate buffer to store all tasks. + Tasks = static_cast(FMemory::Malloc(TaskCount * sizeof(FHoudiniEngineTask))); + + if (Tasks) + { + // Zero memory. + FMemory::Memset(Tasks, 0x0, TaskCount * sizeof(FHoudiniEngineTask)); + } + } +} + +FHoudiniEngineScheduler::~FHoudiniEngineScheduler() +{ + if (TaskCount) + { + FMemory::Free(Tasks); + Tasks = nullptr; + } +} + +void +FHoudiniEngineScheduler::TaskDescription( + FHoudiniEngineTaskInfo & TaskInfo, + const FString & ActorName, + const FString & StatusString) +{ + FFormatNamedArguments Args; + + if (!ActorName.IsEmpty()) + { + Args.Add(TEXT("AssetName"), FText::FromString(ActorName)); + Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); + TaskInfo.StatusText = + FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetName}) : ({AssetStatus})"), Args); + } + else + { + Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); + TaskInfo.StatusText = + FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetStatus})"), Args); + } +} + +void +FHoudiniEngineScheduler::TaskInstantiateAsset(const FHoudiniEngineTask & Task) +{ + FString AssetN; + FHoudiniEngineString(Task.AssetHapiName).ToFString(AssetN); + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Instantiation Started for %s: Asset=%s, HoudiniAsset = 0x%x"), + *Task.ActorName, *AssetN, Task.Asset.Get()); + + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskInstantiateAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + if (!Task.Asset.IsValid()) + { + // Asset is no longer valid, return. + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset is no longer valid.")); + + return; + } + + if (Task.AssetHapiName < 0) + { + // Asset is no longer valid, return. + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset name is invalid.")); + + return; + } + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + int32 AssetCount = 0; + HAPI_NodeId AssetId = -1; + std::string AssetNameString; + double LastUpdateTime; + + FHoudiniEngineString HoudiniEngineString(Task.AssetHapiName); + if (!HoudiniEngineString.ToStdString(AssetNameString)) + { + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Error retrieving asset name.")); + + return; + } + + // Translate asset name into Unreal string. + FString AssetName = ANSI_TO_TCHAR(AssetNameString.c_str()); + + // Initialize last update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // We instantiate without cooking. + Result = FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), -1, &AssetNameString[0], nullptr, false, &AssetId); + if (Result != HAPI_RESULT_SUCCESS) + { + AddResponseMessageTaskInfo( + Result, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Error instantiating asset.")); + + return; + } + + // Add processing notification. + FHoudiniEngineTaskInfo TaskInfo( + HAPI_RESULT_SUCCESS, -1, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Working); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + TaskDescription(TaskInfo, Task.ActorName, TEXT("Started Instantiation")); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); + + // We need to spin until instantiation is finished. + while (true) + { + int Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // Cooking has been successful. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Success, AssetId, Task, + TEXT("Finished Instantiation.")); + + break; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // There was an error while instantiating. + FString CookResultString = FHoudiniEngineUtils::GetCookResult(); + int32 CookResult = static_cast(HAPI_RESULT_SUCCESS); + FHoudiniApi::GetStatus(FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_RESULT, &CookResult); + + EHoudiniEngineTaskState TaskStateResult = EHoudiniEngineTaskState::FinishedWithFatalError; + if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + TaskStateResult = EHoudiniEngineTaskState::FinishedWithError; + + AddResponseMessageTaskInfo( + static_cast(CookResult), + EHoudiniEngineTaskType::AssetInstantiation, + TaskStateResult, + AssetId, Task, + FString::Printf(TEXT("Finished Instantiation with Errors: %s"), *CookResultString)); + + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if ((FPlatformTime::Seconds() - LastUpdateTime) >= NotificationUpdateFrequency) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + const FString& CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Working, + AssetId, Task, CookStateMessage); + } + + // We want to yield. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } +} + +void +FHoudiniEngineScheduler::TaskCookAsset(const FHoudiniEngineTask & Task) +{ + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskCookAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + // Retrieve asset id. + HAPI_NodeId AssetId = Task.AssetId; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Cooking Started for %s., AssetId = %d"), + *Task.ActorName, AssetId); + + if (AssetId == -1) + { + // We have an invalid asset id. + HOUDINI_LOG_ERROR(TEXT("TaskCookAsset failed for %s: Invalid Asset Id."), *Task.ActorName); + + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset has invalid id.")); + + return; + } + + // Default CookOptions + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + Result = FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), AssetId, &CookOptions); + if (Result != HAPI_RESULT_SUCCESS) + { + AddResponseMessageTaskInfo( + Result, EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + AssetId, Task, TEXT("Error cooking asset.")); + + return; + } + + // Add processing notification. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Working, + AssetId, Task, TEXT("Started Cooking")); + + // Initialize last update time. + double LastUpdateTime = FPlatformTime::Seconds(); + + // We need to spin until cooking is finished. + while (true) + { + int32 Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET( &Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // Cooking has been successful. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Success, + AssetId, Task, TEXT("Finished Cooking")); + + break; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + EHoudiniEngineTaskState TaskResult = EHoudiniEngineTaskState::FinishedWithFatalError; + if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + TaskResult = EHoudiniEngineTaskState::FinishedWithError; + + // There was an error while instantiating. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + TaskResult, + AssetId, Task, + TEXT("Finished Cooking with Errors")); + + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if (FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // Retrieve status string. + const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Working, + AssetId, Task, CookStateMessage); + } + + // We want to yield. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } +} + +void +FHoudiniEngineScheduler::TaskDeleteAsset(const FHoudiniEngineTask & Task) +{ + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Destruction Started for %s. ") + TEXT("AssetId = %d"), + *Task.ActorName, Task.AssetId); + + if (FHoudiniEngineUtils::IsHoudiniNodeValid(Task.AssetId)) + FHoudiniEngineUtils::DestroyHoudiniAsset(Task.AssetId); + + // We do not insert task info as this is a fire and forget operation. + // At this point component most likely does not exist. +} + +void +FHoudiniEngineScheduler::AddResponseTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task) +{ + FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); + FString StatusString = FHoudiniEngineUtils::GetErrorDescription(); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + + TaskDescription(TaskInfo, Task.ActorName, StatusString); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); +} + +void +FHoudiniEngineScheduler::AddResponseMessageTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, const FString & ErrorMessage) +{ + FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + + TaskDescription(TaskInfo, Task.ActorName, ErrorMessage); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); +} + +void +FHoudiniEngineScheduler::ProcessQueuedTasks() +{ + while (!bStopping) + { + while (true) + { + FHoudiniEngineTask Task; + + { + FScopeLock ScopeLock(&CriticalSection); + + // We have no tasks left. + if (PositionWrite == PositionRead) + break; + + // Retrieve task. + Task = Tasks[PositionRead]; + PositionRead++; + + // Wrap around if required. + PositionRead &= (TaskCount - 1); + } + + bool bTaskProcessed = true; + + switch (Task.TaskType) + { + case EHoudiniEngineTaskType::AssetInstantiation: + { + TaskInstantiateAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetCooking: + { + TaskCookAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetDeletion: + { + TaskDeleteAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetProcess: + { + TaskProccessAsset(Task); + break; + } + + default: + { + bTaskProcessed = false; + break; + } + } + + if (!bTaskProcessed) + break; + } + + if (FPlatformProcess::SupportsMultithreading()) + { + // We want to yield for a bit. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } + else + { + // If we are running in single threaded mode, return so we don't block everything else. + return; + } + } +} + +void +FHoudiniEngineScheduler::TaskProccessAsset(const FHoudiniEngineTask & Task) +{ + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskProccessAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + // Retrieve asset id. + HAPI_NodeId AssetId = Task.AssetId; + if (AssetId == -1) + { + // We have an invalid asset id. + HOUDINI_LOG_ERROR(TEXT("TaskProcessAsset failed for %s: Invalid Asset Id."), *Task.ActorName); + + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset has invalid id.")); + + return; + } + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Processing started for %s., AssetId = %d"), + *Task.ActorName, AssetId); + + // Add processing notification. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::Working, + AssetId, Task, TEXT("Started Cooking")); + + // + + + // TODO: Process results! +} + +void +FHoudiniEngineScheduler::AddTask(const FHoudiniEngineTask & Task) +{ + FScopeLock ScopeLock(&CriticalSection); + + // Check if we need to grow our circular buffer. + if (PositionWrite + 1 == PositionRead) + { + // Calculate next size (next power of two). + uint32 NextTaskCount = FPlatformMath::RoundUpToPowerOfTwo(TaskCount + 1); + + // Allocate new buffer. + FHoudiniEngineTask * Buffer = static_cast( + FMemory::Malloc(NextTaskCount * sizeof(FHoudiniEngineTask))); + + if (!Buffer) + return; + + // Zero memory. + FMemory::Memset(Buffer, 0x0, NextTaskCount * sizeof(FHoudiniEngineTask)); + + // Copy elements from old buffer to new one. + if (PositionRead < PositionWrite) + { + FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (PositionWrite - PositionRead)); + + // Update index positions. + PositionRead = 0; + PositionWrite = PositionWrite - PositionRead; + } + else + { + FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (TaskCount - PositionRead)); + FMemory::Memcpy(Buffer + TaskCount - PositionRead, Tasks, sizeof(FHoudiniEngineTask) * PositionWrite); + + // Update index positions. + PositionRead = 0; + PositionWrite = TaskCount - PositionRead + PositionWrite; + } + + // Deallocate old buffer. + FMemory::Free(Tasks); + + // Bookkeeping. + Tasks = Buffer; + TaskCount = NextTaskCount; + } + + // Store task. + Tasks[PositionWrite] = Task; + PositionWrite++; + + // Wrap around if required. + PositionWrite &= (TaskCount - 1); +} + +uint32 +FHoudiniEngineScheduler::Run() +{ + ProcessQueuedTasks(); + return 0; +} + +void +FHoudiniEngineScheduler::Stop() +{ + bStopping = true; +} + +void +FHoudiniEngineScheduler::Tick() +{ + ProcessQueuedTasks(); +} + +FSingleThreadRunnable * +FHoudiniEngineScheduler::GetSingleThreadInterface() +{ + return this; +} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h index 24f326dc6..db786f178 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h @@ -1,117 +1,117 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineTask.h" -#include "HoudiniEngineTaskInfo.h" -#include "HAL/Runnable.h" -#include "HAL/RunnableThread.h" -#include "Misc/SingleThreadRunnable.h" - -class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable -{ -public: - - FHoudiniEngineScheduler(); - virtual ~FHoudiniEngineScheduler(); - - // FRunnable methods. - virtual uint32 Run() override; - virtual void Stop() override; - FSingleThreadRunnable * GetSingleThreadInterface() override; - - // FSingleThreadRunnable methods. - virtual void Tick() override; - - // Adds a task. - void AddTask(const FHoudiniEngineTask & Task); - - // Adds instantiation response task info. - void AddResponseTaskInfo( - HAPI_Result Result, - EHoudiniEngineTaskType TaskType, - EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, - const FHoudiniEngineTask & Task); - - void AddResponseMessageTaskInfo( - HAPI_Result Result, - EHoudiniEngineTaskType TaskType, - EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, - const FHoudiniEngineTask & Task, - const FString & ErrorMessage); - -protected: - - // Process queued tasks. - void ProcessQueuedTasks(); - - // Task : instantiate an asset. - void TaskInstantiateAsset(const FHoudiniEngineTask & Task); - - // Task : cook an asset. - void TaskCookAsset(const FHoudiniEngineTask & Task); - - // Create description of task's state. - void TaskDescription( - FHoudiniEngineTaskInfo & Task, - const FString & ActorName, - const FString & StatusString); - - // Delete an asset. - void TaskDeleteAsset(const FHoudiniEngineTask & Task); - - // Process the result of a sucesfull cook - void TaskProccessAsset(const FHoudiniEngineTask & Task); - -private: - - // Initial number of tasks in our circular queue. - static const uint32 InitialTaskSize; - - // Frequency update (sleep time between each update) - static const float UpdateFrequency; - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // List of scheduled tasks. - FHoudiniEngineTask* Tasks; - - // Head of the circular queue. - uint32 PositionWrite; - - // Tail of the circular queue. - uint32 PositionRead; - - // Size of the circular queue. - uint32 TaskCount; - - // Stopping flag. - bool bStopping; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HAL/Runnable.h" +#include "HAL/RunnableThread.h" +#include "Misc/SingleThreadRunnable.h" + +class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable +{ +public: + + FHoudiniEngineScheduler(); + virtual ~FHoudiniEngineScheduler(); + + // FRunnable methods. + virtual uint32 Run() override; + virtual void Stop() override; + FSingleThreadRunnable * GetSingleThreadInterface() override; + + // FSingleThreadRunnable methods. + virtual void Tick() override; + + // Adds a task. + void AddTask(const FHoudiniEngineTask & Task); + + // Adds instantiation response task info. + void AddResponseTaskInfo( + HAPI_Result Result, + EHoudiniEngineTaskType TaskType, + EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, + const FHoudiniEngineTask & Task); + + void AddResponseMessageTaskInfo( + HAPI_Result Result, + EHoudiniEngineTaskType TaskType, + EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, + const FHoudiniEngineTask & Task, + const FString & ErrorMessage); + +protected: + + // Process queued tasks. + void ProcessQueuedTasks(); + + // Task : instantiate an asset. + void TaskInstantiateAsset(const FHoudiniEngineTask & Task); + + // Task : cook an asset. + void TaskCookAsset(const FHoudiniEngineTask & Task); + + // Create description of task's state. + void TaskDescription( + FHoudiniEngineTaskInfo & Task, + const FString & ActorName, + const FString & StatusString); + + // Delete an asset. + void TaskDeleteAsset(const FHoudiniEngineTask & Task); + + // Process the result of a sucesfull cook + void TaskProccessAsset(const FHoudiniEngineTask & Task); + +private: + + // Initial number of tasks in our circular queue. + static const uint32 InitialTaskSize; + + // Frequency update (sleep time between each update) + static const float UpdateFrequency; + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // List of scheduled tasks. + FHoudiniEngineTask* Tasks; + + // Head of the circular queue. + uint32 PositionWrite; + + // Tail of the circular queue. + uint32 PositionRead; + + // Size of the circular queue. + uint32 TaskCount; + + // Stopping flag. + bool bStopping; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineString.cpp b/Source/HoudiniEngine/Private/HoudiniEngineString.cpp index c0738fddc..be55a272d 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineString.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineString.cpp @@ -1,185 +1,185 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineString.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include - -FHoudiniEngineString::FHoudiniEngineString() - : StringId(-1) -{} - -FHoudiniEngineString::FHoudiniEngineString(int32 InStringId) - : StringId(InStringId) -{} - -FHoudiniEngineString::FHoudiniEngineString(const FHoudiniEngineString & Other) - : StringId(Other.StringId) -{} - -FHoudiniEngineString & -FHoudiniEngineString::operator=(const FHoudiniEngineString & Other) -{ - if (this != &Other) - StringId = Other.StringId; - - return *this; -} - -bool -FHoudiniEngineString::operator==(const FHoudiniEngineString & Other) const -{ - return Other.StringId == StringId; -} - -bool -FHoudiniEngineString::operator!=(const FHoudiniEngineString & Other) const -{ - return Other.StringId != StringId; -} - -int32 -FHoudiniEngineString::GetId() const -{ - return StringId; -} - -bool -FHoudiniEngineString::HasValidId() const -{ - return StringId > 0; -} - -bool -FHoudiniEngineString::ToStdString(std::string & String) const -{ - String = ""; - - // Null string ID / zero should be considered invalid - // (or we'd get the "null string, should never see this!" text) - if (StringId <= 0) - { - return false; - } - - int32 NameLength = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBufLength( - FHoudiniEngine::Get().GetSession(), StringId, &NameLength)) - { - return false; - } - - if (NameLength <= 0) - return false; - - std::vector< char > NameBuffer(NameLength, '\0'); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( - FHoudiniEngine::Get().GetSession(), - StringId, &NameBuffer[0], NameLength ) ) - { - return false; - } - - String = std::string(NameBuffer.begin(), NameBuffer.end()); - - return true; -} - -bool -FHoudiniEngineString::ToFName(FName & Name) const -{ - Name = NAME_None; - FString NameString = TEXT(""); - if (ToFString(NameString)) - { - Name = FName(*NameString); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToFString(FString & String) const -{ - String = TEXT(""); - std::string NamePlain = ""; - - if (ToStdString(NamePlain)) - { - String = UTF8_TO_TCHAR(NamePlain.c_str()); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToFText(FText & Text) const -{ - Text = FText::GetEmpty(); - FString NameString = TEXT(""); - - if (ToFString(NameString)) - { - Text = FText::FromString(NameString); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToStdString(const int32& InStringId, std::string& OutStdString) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToStdString(OutStdString); -} - -bool -FHoudiniEngineString::ToFName(const int32& InStringId, FName& OutName) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFName(OutName); -} - -bool -FHoudiniEngineString::ToFString(const int32& InStringId, FString& OutString) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFString(OutString); -} - -bool -FHoudiniEngineString::ToFText(const int32& InStringId, FText& OutText) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFText(OutText); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineString.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include + +FHoudiniEngineString::FHoudiniEngineString() + : StringId(-1) +{} + +FHoudiniEngineString::FHoudiniEngineString(int32 InStringId) + : StringId(InStringId) +{} + +FHoudiniEngineString::FHoudiniEngineString(const FHoudiniEngineString & Other) + : StringId(Other.StringId) +{} + +FHoudiniEngineString & +FHoudiniEngineString::operator=(const FHoudiniEngineString & Other) +{ + if (this != &Other) + StringId = Other.StringId; + + return *this; +} + +bool +FHoudiniEngineString::operator==(const FHoudiniEngineString & Other) const +{ + return Other.StringId == StringId; +} + +bool +FHoudiniEngineString::operator!=(const FHoudiniEngineString & Other) const +{ + return Other.StringId != StringId; +} + +int32 +FHoudiniEngineString::GetId() const +{ + return StringId; +} + +bool +FHoudiniEngineString::HasValidId() const +{ + return StringId > 0; +} + +bool +FHoudiniEngineString::ToStdString(std::string & String) const +{ + String = ""; + + // Null string ID / zero should be considered invalid + // (or we'd get the "null string, should never see this!" text) + if (StringId <= 0) + { + return false; + } + + int32 NameLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBufLength( + FHoudiniEngine::Get().GetSession(), StringId, &NameLength)) + { + return false; + } + + if (NameLength <= 0) + return false; + + std::vector< char > NameBuffer(NameLength, '\0'); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( + FHoudiniEngine::Get().GetSession(), + StringId, &NameBuffer[0], NameLength ) ) + { + return false; + } + + String = std::string(NameBuffer.begin(), NameBuffer.end()); + + return true; +} + +bool +FHoudiniEngineString::ToFName(FName & Name) const +{ + Name = NAME_None; + FString NameString = TEXT(""); + if (ToFString(NameString)) + { + Name = FName(*NameString); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToFString(FString & String) const +{ + String = TEXT(""); + std::string NamePlain = ""; + + if (ToStdString(NamePlain)) + { + String = UTF8_TO_TCHAR(NamePlain.c_str()); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToFText(FText & Text) const +{ + Text = FText::GetEmpty(); + FString NameString = TEXT(""); + + if (ToFString(NameString)) + { + Text = FText::FromString(NameString); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToStdString(const int32& InStringId, std::string& OutStdString) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToStdString(OutStdString); +} + +bool +FHoudiniEngineString::ToFName(const int32& InStringId, FName& OutName) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFName(OutName); +} + +bool +FHoudiniEngineString::ToFString(const int32& InStringId, FString& OutString) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFString(OutString); +} + +bool +FHoudiniEngineString::ToFText(const int32& InStringId, FText& OutText) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFText(OutText); } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineString.h b/Source/HoudiniEngine/Private/HoudiniEngineString.h index 7d443fd6e..47e9fba50 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineString.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineString.h @@ -1,70 +1,70 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -class FText; -class FString; -class FName; - -#include - -class HOUDINIENGINE_API FHoudiniEngineString -{ - public: - - FHoudiniEngineString(); - FHoudiniEngineString(int32 InStringId); - FHoudiniEngineString(const FHoudiniEngineString & Other); - - FHoudiniEngineString & operator=(const FHoudiniEngineString & Other); - - bool operator==(const FHoudiniEngineString & Other) const; - bool operator!=(const FHoudiniEngineString & Other) const; - - // Conversion functions - bool ToStdString(std::string & String) const; - bool ToFName(FName & Name) const; - bool ToFString(FString & String) const; - bool ToFText(FText & Text) const; - - // Static converters - static bool ToStdString(const int32& InStringId, std::string & String); - static bool ToFName(const int32& InStringId, FName & Name); - static bool ToFString(const int32& InStringId, FString & String); - static bool ToFText(const int32& InStringId, FText & Text); - - // Return id of this string. - int32 GetId() const; - - // Return true if this string has a valid id. - bool HasValidId() const; - - protected: - - // Id of the underlying Houdini Engine string. - int32 StringId; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +class FText; +class FString; +class FName; + +#include + +class HOUDINIENGINE_API FHoudiniEngineString +{ + public: + + FHoudiniEngineString(); + FHoudiniEngineString(int32 InStringId); + FHoudiniEngineString(const FHoudiniEngineString & Other); + + FHoudiniEngineString & operator=(const FHoudiniEngineString & Other); + + bool operator==(const FHoudiniEngineString & Other) const; + bool operator!=(const FHoudiniEngineString & Other) const; + + // Conversion functions + bool ToStdString(std::string & String) const; + bool ToFName(FName & Name) const; + bool ToFString(FString & String) const; + bool ToFText(FText & Text) const; + + // Static converters + static bool ToStdString(const int32& InStringId, std::string & String); + static bool ToFName(const int32& InStringId, FName & Name); + static bool ToFString(const int32& InStringId, FString & String); + static bool ToFText(const int32& InStringId, FText & Text); + + // Return id of this string. + int32 GetId() const; + + // Return true if this string has a valid id. + bool HasValidId() const; + + protected: + + // Id of the underlying Houdini Engine string. + int32 StringId; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp index 6bd564493..ed8b79122 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp @@ -1,48 +1,48 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineTask.h" - -#include "HoudiniApi.h" - -FHoudiniEngineTask::FHoudiniEngineTask() - : TaskType(EHoudiniEngineTaskType::None) - , ActorName(TEXT("")) - , AssetId(-1) - , AssetLibraryId(-1) - , AssetHapiName(-1) -{ - HapiGUID.Invalidate(); -} - -FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID) - : HapiGUID(InHapiGUID) - , TaskType(InTaskType) - , ActorName(TEXT("")) - , AssetId(-1) - , AssetLibraryId(-1) - , AssetHapiName(-1) -{} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineTask.h" + +#include "HoudiniApi.h" + +FHoudiniEngineTask::FHoudiniEngineTask() + : TaskType(EHoudiniEngineTaskType::None) + , ActorName(TEXT("")) + , AssetId(-1) + , AssetLibraryId(-1) + , AssetHapiName(-1) +{ + HapiGUID.Invalidate(); +} + +FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID) + : HapiGUID(InHapiGUID) + , TaskType(InTaskType) + , ActorName(TEXT("")) + , AssetId(-1) + , AssetLibraryId(-1) + , AssetHapiName(-1) +{} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.h b/Source/HoudiniEngine/Private/HoudiniEngineTask.h index 60916a342..ad5fa5166 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.h @@ -1,100 +1,100 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniApi.h" -#include "CoreMinimal.h" -#include "Misc/Guid.h" -#include "UObject/WeakObjectPtr.h" - -/* -namespace EHoudiniEngineTaskType -{ - enum Type - { - None, - - // This type corresponds to Houdini asset instantiation (without cooking). - AssetInstantiation, - - // This type corresponds to Houdini asset cooking request. - AssetCooking, - - // This type is used for asynchronous asset deletion. - AssetDeletion - }; -} -*/ - -UENUM() -enum class EHoudiniEngineTaskType : uint8 -{ - None, - - // This type corresponds to Houdini asset instantiation (without cooking). - AssetInstantiation, - - // This type corresponds to Houdini asset cooking request. - AssetCooking, - - // This type is used for asynchronous asset deletion. - AssetDeletion, - - // This type is used when processing the results of a sucessful cook - AssetProcess, -}; - -struct HOUDINIENGINE_API FHoudiniEngineTask -{ - // Constructors. - FHoudiniEngineTask(); - FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID); - - // GUID of this request. - FGuid HapiGUID; - - // Type of this task. - EHoudiniEngineTaskType TaskType; - - // Houdini asset for instantiation. - TWeakObjectPtr< class UHoudiniAsset > Asset; - - // Name of the actor requesting this task. - FString ActorName; - - // Asset Id. - HAPI_NodeId AssetId; - - // Library Id. - HAPI_AssetLibraryId AssetLibraryId; - - // HAPI name of the asset. - int32 AssetHapiName; - - // Is set to true if component has been loaded. - //bool bLoadedComponent; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniApi.h" +#include "CoreMinimal.h" +#include "Misc/Guid.h" +#include "UObject/WeakObjectPtr.h" + +/* +namespace EHoudiniEngineTaskType +{ + enum Type + { + None, + + // This type corresponds to Houdini asset instantiation (without cooking). + AssetInstantiation, + + // This type corresponds to Houdini asset cooking request. + AssetCooking, + + // This type is used for asynchronous asset deletion. + AssetDeletion + }; +} +*/ + +UENUM() +enum class EHoudiniEngineTaskType : uint8 +{ + None, + + // This type corresponds to Houdini asset instantiation (without cooking). + AssetInstantiation, + + // This type corresponds to Houdini asset cooking request. + AssetCooking, + + // This type is used for asynchronous asset deletion. + AssetDeletion, + + // This type is used when processing the results of a sucessful cook + AssetProcess, +}; + +struct HOUDINIENGINE_API FHoudiniEngineTask +{ + // Constructors. + FHoudiniEngineTask(); + FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID); + + // GUID of this request. + FGuid HapiGUID; + + // Type of this task. + EHoudiniEngineTaskType TaskType; + + // Houdini asset for instantiation. + TWeakObjectPtr< class UHoudiniAsset > Asset; + + // Name of the actor requesting this task. + FString ActorName; + + // Asset Id. + HAPI_NodeId AssetId; + + // Library Id. + HAPI_AssetLibraryId AssetLibraryId; + + // HAPI name of the asset. + int32 AssetHapiName; + + // Is set to true if component has been loaded. + //bool bLoadedComponent; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp index 14e3283c1..c962a952e 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp @@ -1,47 +1,47 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineTaskInfo.h" - -#include "HAPI/HAPI_Common.h" - -FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo() - : Result(HAPI_RESULT_SUCCESS) - , AssetId(-1) - , TaskType(EHoudiniEngineTaskType::None) - , TaskState(EHoudiniEngineTaskState::None) -{} - -FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo( - HAPI_Result InResult, - HAPI_NodeId InAssetId, - EHoudiniEngineTaskType InTaskType, - EHoudiniEngineTaskState InTaskState) - : Result(InResult) - , AssetId(InAssetId) - , TaskType(InTaskType) - , TaskState(InTaskState) +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineTaskInfo.h" + +#include "HAPI/HAPI_Common.h" + +FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo() + : Result(HAPI_RESULT_SUCCESS) + , AssetId(-1) + , TaskType(EHoudiniEngineTaskType::None) + , TaskState(EHoudiniEngineTaskState::None) +{} + +FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo( + HAPI_Result InResult, + HAPI_NodeId InAssetId, + EHoudiniEngineTaskType InTaskType, + EHoudiniEngineTaskState InTaskState) + : Result(InResult) + , AssetId(InAssetId) + , TaskType(InTaskType) + , TaskState(InTaskState) {} \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h index 31d11e127..8ca452972 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h @@ -1,78 +1,78 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineTask.h" - -UENUM() -enum class EHoudiniEngineTaskState : uint8 -{ - None, - - // Indicates the current task is still running - Working, - - // Indicates the task has successfully finished - Success, - - // Indicates the task has finished with non fatal errors - FinishedWithError, - - // Indicates the task has finished with fatal errors and should be terminated - FinishedWithFatalError, - - // Indicates the task has been aborted (unused) - Aborted -}; - -struct HOUDINIENGINE_API FHoudiniEngineTaskInfo -{ - // Constructors. - FHoudiniEngineTaskInfo(); - FHoudiniEngineTaskInfo( - HAPI_Result InResult, HAPI_NodeId InAssetId, - EHoudiniEngineTaskType InTaskType, - EHoudiniEngineTaskState InTaskState); - - // Current HAPI result. - HAPI_Result Result; - - // Current Asset Id. - HAPI_NodeId AssetId; - - // Type of task. - EHoudiniEngineTaskType TaskType; - - // Current status. - EHoudiniEngineTaskState TaskState; - - // String used for status / progress bar. - FText StatusText; - - // Is set to true if corresponding task was issued for loaded component. - //bool bLoadedComponent; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineTask.h" + +UENUM() +enum class EHoudiniEngineTaskState : uint8 +{ + None, + + // Indicates the current task is still running + Working, + + // Indicates the task has successfully finished + Success, + + // Indicates the task has finished with non fatal errors + FinishedWithError, + + // Indicates the task has finished with fatal errors and should be terminated + FinishedWithFatalError, + + // Indicates the task has been aborted (unused) + Aborted +}; + +struct HOUDINIENGINE_API FHoudiniEngineTaskInfo +{ + // Constructors. + FHoudiniEngineTaskInfo(); + FHoudiniEngineTaskInfo( + HAPI_Result InResult, HAPI_NodeId InAssetId, + EHoudiniEngineTaskType InTaskType, + EHoudiniEngineTaskState InTaskState); + + // Current HAPI result. + HAPI_Result Result; + + // Current Asset Id. + HAPI_NodeId AssetId; + + // Type of task. + EHoudiniEngineTaskType TaskType; + + // Current status. + EHoudiniEngineTaskState TaskState; + + // String used for status / progress bar. + FText StatusText; + + // Is set to true if corresponding task was issued for loaded component. + //bool bLoadedComponent; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp index 61764f81d..39d9ab726 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp @@ -1,4878 +1,4878 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineUtils.h" -#include "Misc/StringFormatArg.h" - -#if PLATFORM_WINDOWS - #include "Windows/WindowsHWrapper.h" - - // Of course, Windows defines its own GetGeoInfo, - // So we need to undefine that before including HoudiniApi.h to avoid collision... - #ifdef GetGeoInfo - #undef GetGeoInfo - #endif -#endif - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniEngineString.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInput.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineRuntimeUtils.h" - -#if WITH_EDITOR - #include "SAssetSelectionWidget.h" -#endif - -#include "HAPI/HAPI_Version.h" - -#include "Misc/Paths.h" -#include "Editor/EditorEngine.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" -#include "PropertyEditorModule.h" -#include "Modules/ModuleManager.h" -#include "Engine/StaticMeshSocket.h" -#include "Async/Async.h" -#include "BlueprintEditor.h" -#include "Toolkits/AssetEditorManager.h" -#include "Engine/BlueprintGeneratedClass.h" -#include "UObject/MetaData.h" -#include "RawMesh.h" -#include "Widgets/Notifications/SNotificationList.h" -#include "Framework/Notifications/NotificationManager.h" -#include "Interfaces/IPluginManager.h" -//#include "Kismet/BlueprintEditor.h" -#include "SSCSEditor.h" -#include "Engine/WorldComposition.h" - -#if WITH_EDITOR - #include "Interfaces/IMainFrameModule.h" -#endif - -#include - -#include "AssetRegistryModule.h" -#include "FileHelpers.h" -#include "Factories/WorldFactory.h" -#include "HAL/FileManager.h" - -#if WITH_EDITOR - #include "EditorModeManager.h" - #include "EditorModes.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// HAPI_Result strings -const FString kResultStringSuccess(TEXT("Success")); -const FString kResultStringFailure(TEXT("Generic Failure")); -const FString kResultStringAlreadyInitialized(TEXT("Already Initialized")); -const FString kResultStringNotInitialized(TEXT("Not Initialized")); -const FString kResultStringCannotLoadFile(TEXT("Unable to Load File")); -const FString kResultStringParmSetFailed(TEXT("Failed Setting Parameter")); -const FString kResultStringInvalidArgument(TEXT("Invalid Argument")); -const FString kResultStringCannotLoadGeo(TEXT("Uneable to Load Geometry")); -const FString kResultStringCannotGeneratePreset(TEXT("Uneable to Generate Preset")); -const FString kResultStringCannotLoadPreset(TEXT("Uneable to Load Preset")); -const FString kResultStringAssetDefAlrealdyLoaded(TEXT("Asset definition already loaded")); -const FString kResultStringNoLicenseFound(TEXT("No License Found")); -const FString kResultStringDisallowedNCLicenseFound(TEXT("Disallowed Non Commercial License found")); -const FString kResultStringDisallowedNCAssetWithCLicense(TEXT("Disallowed Non Commercial Asset With Commercial License")); -const FString kResultStringDisallowedNCAssetWithLCLicense(TEXT("Disallowed Non Commercial Asset With Limited Commercial License")); -const FString kResultStringDisallowedLCAssetWithCLicense(TEXT("Disallowed Limited Commercial Asset With Commercial License")); -const FString kResultStringDisallowedHengineIndieWith3PartyPlugin(TEXT("Disallowed Houdini Engine Indie With 3rd Party Plugin")); -const FString kResultStringAssetInvalid(TEXT("Invalid Asset")); -const FString kResultStringNodeInvalid(TEXT("Invalid Node")); -const FString kResultStringUserInterrupted(TEXT("User Interrupt")); -const FString kResultStringInvalidSession(TEXT("Invalid Session")); -const FString kResultStringUnknowFailure(TEXT("Unknown Failure")); - -#define DebugTextLine TEXT("===================================") - -const int32 -FHoudiniEngineUtils::PackageGUIDComponentNameLength = 12; - -const int32 -FHoudiniEngineUtils::PackageGUIDItemNameLength = 8; - -const FString -FHoudiniEngineUtils::GetErrorDescription(HAPI_Result Result) -{ - if (Result == HAPI_RESULT_SUCCESS) - { - return kResultStringSuccess; - } - else - { - switch (Result) - { - case HAPI_RESULT_FAILURE: - { - return kResultStringFailure; - } - - case HAPI_RESULT_ALREADY_INITIALIZED: - { - return kResultStringAlreadyInitialized; - } - - case HAPI_RESULT_NOT_INITIALIZED: - { - return kResultStringNotInitialized; - } - - case HAPI_RESULT_CANT_LOADFILE: - { - return kResultStringCannotLoadFile; - } - - case HAPI_RESULT_PARM_SET_FAILED: - { - return kResultStringParmSetFailed; - } - - case HAPI_RESULT_INVALID_ARGUMENT: - { - return kResultStringInvalidArgument; - } - - case HAPI_RESULT_CANT_LOAD_GEO: - { - return kResultStringCannotLoadGeo; - } - - case HAPI_RESULT_CANT_GENERATE_PRESET: - { - return kResultStringCannotGeneratePreset; - } - - case HAPI_RESULT_CANT_LOAD_PRESET: - { - return kResultStringCannotLoadPreset; - } - - case HAPI_RESULT_ASSET_DEF_ALREADY_LOADED: - { - return kResultStringAssetDefAlrealdyLoaded; - } - - case HAPI_RESULT_NO_LICENSE_FOUND: - { - return kResultStringNoLicenseFound; - } - - case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: - { - return kResultStringDisallowedNCLicenseFound; - } - - case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: - { - return kResultStringDisallowedNCAssetWithCLicense; - } - - case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: - { - return kResultStringDisallowedNCAssetWithLCLicense; - } - - case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: - { - return kResultStringDisallowedLCAssetWithCLicense; - } - - case HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN: - { - return kResultStringDisallowedHengineIndieWith3PartyPlugin; - } - - case HAPI_RESULT_ASSET_INVALID: - { - return kResultStringAssetInvalid; - } - - case HAPI_RESULT_NODE_INVALID: - { - return kResultStringNodeInvalid; - } - - case HAPI_RESULT_USER_INTERRUPTED: - { - return kResultStringUserInterrupted; - } - - case HAPI_RESULT_INVALID_SESSION: - { - return kResultStringInvalidSession; - } - - default: - { - return kResultStringUnknowFailure; - } - }; - } -} - -const FString -FHoudiniEngineUtils::GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity) -{ - const HAPI_Session* SessionPtr = FHoudiniEngine::Get().GetSession(); - if (!SessionPtr) - { - // No valid session - return FString(TEXT("No valid Houdini Engine session.")); - } - - int32 StatusBufferLength = 0; - HAPI_Result Result = FHoudiniApi::GetStatusStringBufLength( - SessionPtr, status_type, verbosity, &StatusBufferLength); - - if (Result == HAPI_RESULT_INVALID_SESSION) - { - // Let FHoudiniEngine know that the sesion is now invalid to "Stop" the invalid session - // and clean things up - FHoudiniEngine::Get().OnSessionLost(); - } - - if (StatusBufferLength > 0) - { - TArray< char > StatusStringBuffer; - StatusStringBuffer.SetNumZeroed(StatusBufferLength); - FHoudiniApi::GetStatusString( - SessionPtr, status_type, &StatusStringBuffer[0], StatusBufferLength); - - return FString(UTF8_TO_TCHAR(&StatusStringBuffer[0])); - } - - return FString(TEXT("")); -} - -const FString -FHoudiniEngineUtils::GetCookResult() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_MESSAGES); -} - -const FString -FHoudiniEngineUtils::GetCookState() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); -} - -const FString -FHoudiniEngineUtils::GetErrorDescription() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS); -} - -const FString -FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId) -{ - int32 NodeErrorLength = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeNodeCookResult( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_StatusVerbosity::HAPI_STATUSVERBOSITY_ALL, &NodeErrorLength)) - { - NodeErrorLength = 0; - } - - FString NodeError; - if (NodeErrorLength > 0) - { - TArray NodeErrorBuffer; - NodeErrorBuffer.SetNumZeroed(NodeErrorLength); - FHoudiniApi::GetComposedNodeCookResult( - FHoudiniEngine::Get().GetSession(), &NodeErrorBuffer[0], NodeErrorLength); - - NodeError = FString(UTF8_TO_TCHAR(&NodeErrorBuffer[0])); - } - - return NodeError; -} - -const FString -FHoudiniEngineUtils::GetCookLog(TArray& InHACs) -{ - FString CookLog; - - // Get fetch cook status. - FString CookResult = FHoudiniEngineUtils::GetCookResult(); - if (!CookResult.IsEmpty()) - CookLog += TEXT("Cook Results:\n") + CookResult + TEXT("\n\n"); - - // Add the cook state - FString CookState = FHoudiniEngineUtils::GetCookState(); - if (!CookState.IsEmpty()) - CookLog += TEXT("Cook State:\n") + CookState + TEXT("\n\n"); - - // Error Description - FString Error = FHoudiniEngineUtils::GetErrorDescription(); - if (!Error.IsEmpty()) - CookLog += TEXT("Error Description:\n") + Error + TEXT("\n\n"); - - // Iterates on all the selected HAC and get their node errors - for (auto& HAC : InHACs) - { - if (!HAC || HAC->IsPendingKill()) - continue; - - // Get the node errors, warnings and messages - FString NodeErrors = FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(HAC->GetAssetId()); - if (NodeErrors.IsEmpty()) - continue; - - CookLog += NodeErrors; - } - - if (CookLog.IsEmpty()) - { - // See if a failed HAPI initialization / invalid session is preventing us from getting the cook log - if (!FHoudiniApi::IsHAPIInitialized()) - { - CookLog += TEXT("\n\nThe Houdini Engine API Library (HAPI) has not been initialized properly.\n\n"); - } - else - { - const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) - { - CookLog += TEXT("\n\nThe current Houdini Engine Session is not valid.\n\n"); - } - else if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) - { - CookLog += TEXT("\n\nThe current Houdini Engine Session has not been initialized properly.\n\n"); - } - } - - if (!CookLog.IsEmpty()) - { - CookLog += TEXT("Please try to restart the current Houdini Engine session via File > Restart Houdini Engine Session.\n\n"); - } - else - { - CookLog = TEXT("\n\nThe cook log is empty...\n\n"); - } - } - - return CookLog; -} - -const FString -FHoudiniEngineUtils::GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - FString HelpString = TEXT(""); - if (!HoudiniAssetComponent) - return HelpString; - - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HAPI_NodeId AssetId = HoudiniAssetComponent->GetAssetId(); - if (AssetId < 0) - return HelpString; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), HelpString); - - if (!FHoudiniEngineString::ToFString(AssetInfo.helpTextSH, HelpString)) - return HelpString; - - if (HelpString.IsEmpty()) - HelpString = TEXT("No Asset Help Found"); - - return HelpString; -} - -void -FHoudiniEngineUtils::ConvertUnrealString(const FString & UnrealString, std::string & String) -{ - String = TCHAR_TO_UTF8(*UnrealString); -} - -UWorld* -FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreateMissingPackage, bool& bOutCreatedPackage) -{ - AActor* Result = nullptr; - UWorld* PackageWorld = nullptr; - - bOutCreatedPackage = false; - - // Try to load existing UWorld from the tile package path. - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!Package) - Package = LoadPackage(nullptr, *PackagePath, LOAD_None); - if (Package) - { - // If the package is not valid (pending kill) rename it - if (Package->IsPendingKill()) - { - if (bCreateMissingPackage) - { - Package->Rename( - *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); - } - } - else - { - PackageWorld = UWorld::FindWorldInPackage(Package); - } - } - - if (!IsValid(PackageWorld) && bCreateMissingPackage) - { - // The map for this tile does not exist. Create one - UWorldFactory* Factory = NewObject(); - Factory->WorldType = EWorldType::Inactive; // World that is being loaded but not currently edited by editor. - PackageWorld = CastChecked(Factory->FactoryCreateNew(UWorld::StaticClass(), Package, NAME_None, RF_Public | RF_Standalone, NULL, GWarn)); - - if (IsValid(PackageWorld)) - { - PackageWorld->PostEditChange(); - PackageWorld->MarkPackageDirty(); - - if(FPackageName::IsValidLongPackageName(PackagePath)) - { - const FString PackageFilename = FPackageName::LongPackageNameToFilename(PackagePath); - bool bSaved = FEditorFileUtils::SaveLevel(PackageWorld->PersistentLevel, *PackageFilename); - } - - FAssetRegistryModule::AssetCreated(PackageWorld); - - bOutCreatedPackage = true; - } - } - - return PackageWorld; -} - -bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - UWorld* CurrentWorld, - const FString& PackagePath, - bool bCreateMissingPackage, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bOutPackageCreated, - bool& bPackageInWorld) -{ - UWorld* PackageWorld = FindWorldInPackage(PackagePath, bCreateMissingPackage, bOutPackageCreated); - if (!IsValid(PackageWorld)) - return false; - - if (PackageWorld->PersistentLevel == CurrentWorld->PersistentLevel) - { - // The loaded world and the package world is one and the same. - OutWorld = CurrentWorld; - OutLevel = CurrentWorld->PersistentLevel; - bPackageInWorld = true; - return true; - } - - if (CurrentWorld->GetLevels().Contains(PackageWorld->PersistentLevel)) - { - // The package level is loaded into CurrentWorld. - OutWorld = CurrentWorld; - OutLevel = PackageWorld->PersistentLevel; - bPackageInWorld = true; - return true; - } - - // The package level is not loaded at all. Send back the on-disk assets. - OutWorld = PackageWorld; - OutLevel = PackageWorld->PersistentLevel; - bPackageInWorld = false; - return true; -} - -void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) -{ - FString WorldPath = FPaths::GetPath(InWorld->GetPathName()); - IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); - TArray Packages; - Packages.Add(WorldPath); - AssetRegistry.ScanPathsSynchronous(Packages, true); -} - -AActor* FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) -{ - // AActor* NamedActor = FindObject(Outer, *InName, false); - // Find ANY actor in the world matching the given name. - AActor* NamedActor = FindActorInWorld(InWorld, FName(InName)); - OutFoundActor = NamedActor; - bool bShouldRename = false; - if (NamedActor) - { - if (NamedActor->GetClass()->IsChildOf(InClass) && !NamedActor->IsPendingKill()) - { - return NamedActor; - } - else - { - FString Suffix; - bool bShouldUpdateLabel = false; - if (NamedActor->IsPendingKill()) - Suffix = "_pendingkill"; - else - Suffix = "_0"; // A previous actor that had the same name. - const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName+Suffix); - } - } - return nullptr; -} - -void FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) -{ - LogPackageInfo( LoadPackage(nullptr, *InLongPackageName, 0) ); -} - -void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) -{ - HOUDINI_LOG_MESSAGE(DebugTextLine); - HOUDINI_LOG_MESSAGE(TEXT("= LogPackageInfo")); - if (!IsValid(InPackage)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Invalid package.")); - HOUDINI_LOG_MESSAGE(DebugTextLine); - return; - } - - HOUDINI_LOG_MESSAGE(TEXT(" = Filename: %s"), *(InPackage->FileName.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = Package Id: %d"), InPackage->GetPackageId().ToIndexForDebugging()); - HOUDINI_LOG_MESSAGE(TEXT(" = File size: %d"), InPackage->FileSize); - HOUDINI_LOG_MESSAGE(TEXT(" = Contains map: %d"), InPackage->ContainsMap()); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Fully Loaded: %d"), InPackage->IsFullyLoaded()); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Dirty: %d"), InPackage->IsDirty()); - - if (InPackage->WorldTileInfo.IsValid()) - { - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Position: %s"), *(InPackage->WorldTileInfo->Position.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Absolute Position: %s"), *(InPackage->WorldTileInfo->AbsolutePosition.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Bounds: %s"), *(InPackage->WorldTileInfo->Bounds.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - HidInTileView: %d"), InPackage->WorldTileInfo->bHideInTileView); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - ZOrder: %d"), InPackage->WorldTileInfo->ZOrder); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Parent tile package: %s"), *(InPackage->WorldTileInfo->ParentTilePackageName)); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo: NULL")); - } - - HOUDINI_LOG_MESSAGE(DebugTextLine); -} - -void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) -{ - UPackage* Package = LoadPackage(nullptr, *InLongPackageName, 0); - UWorld* World = nullptr; - - if (IsValid(Package)) - { - World = UWorld::FindWorldInPackage(Package); - } - - LogWorldInfo(World); -} - -void FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) -{ - - HOUDINI_LOG_MESSAGE(DebugTextLine); - HOUDINI_LOG_MESSAGE(TEXT("= LogWorldInfo")); - if (!IsValid(InWorld)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Invalid world.")); - HOUDINI_LOG_MESSAGE(DebugTextLine); - return; - } - - // UWorld lacks const-correctness on certain accessors - UWorld* NonConstWorld = const_cast(InWorld); - - HOUDINI_LOG_MESSAGE(TEXT(" = Path Name: %s"), *(InWorld->GetPathName())); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Editor World: %d"), InWorld->IsEditorWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Game World: %d"), InWorld->IsGameWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Preview World: %d"), InWorld->IsPreviewWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Actor Count: %d"), NonConstWorld->GetActorCount() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Num Levels: %d"), InWorld->GetNumLevels() ); - - if (IsValid(InWorld->WorldComposition)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Composition - Num Tiles: %d"), InWorld->WorldComposition->GetTilesList().Num() ); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT(" = World Composition NULL") ); - } - - - - HOUDINI_LOG_MESSAGE(DebugTextLine); -} - -FString -FHoudiniEngineUtils::HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType) -{ - switch (InEventType) - { - case HAPI_PDG_EVENT_NULL: - return TEXT("HAPI_PDG_EVENT_NULL"); - - case HAPI_PDG_EVENT_WORKITEM_ADD: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE"); - case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_DEP"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_PARENT"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT"); - - case HAPI_PDG_EVENT_NODE_CLEAR: - return TEXT("HAPI_PDG_EVENT_NODE_CLEAR"); - - case HAPI_PDG_EVENT_COOK_ERROR: - return TEXT("HAPI_PDG_EVENT_COOK_ERROR"); - case HAPI_PDG_EVENT_COOK_WARNING: - return TEXT("HAPI_PDG_EVENT_COOK_WARNING"); - - case HAPI_PDG_EVENT_COOK_COMPLETE: - return TEXT("HAPI_PDG_EVENT_COOK_COMPLETE"); - - case HAPI_PDG_EVENT_DIRTY_START: - return TEXT("HAPI_PDG_EVENT_DIRTY_START"); - case HAPI_PDG_EVENT_DIRTY_STOP: - return TEXT("HAPI_PDG_EVENT_DIRTY_STOP"); - - case HAPI_PDG_EVENT_DIRTY_ALL: - return TEXT("HAPI_PDG_EVENT_DIRTY_ALL"); - - case HAPI_PDG_EVENT_UI_SELECT: - return TEXT("HAPI_PDG_EVENT_UI_SELECT"); - - case HAPI_PDG_EVENT_NODE_CREATE: - return TEXT("HAPI_PDG_EVENT_NODE_CREATE"); - case HAPI_PDG_EVENT_NODE_REMOVE: - return TEXT("HAPI_PDG_EVENT_NODE_REMOVE"); - case HAPI_PDG_EVENT_NODE_RENAME: - return TEXT("HAPI_PDG_EVENT_NODE_RENAME"); - case HAPI_PDG_EVENT_NODE_CONNECT: - return TEXT("HAPI_PDG_EVENT_NODE_CONNECT"); - case HAPI_PDG_EVENT_NODE_DISCONNECT: - return TEXT("HAPI_PDG_EVENT_NODE_DISCONNECT"); - - case HAPI_PDG_EVENT_WORKITEM_SET_INT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_INT"); - case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FLOAT"); - case HAPI_PDG_EVENT_WORKITEM_SET_STRING: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_STRING"); - case HAPI_PDG_EVENT_WORKITEM_SET_FILE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FILE"); - case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT"); - case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY"); - case HAPI_PDG_EVENT_WORKITEM_MERGE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_MERGE"); - case HAPI_PDG_EVENT_WORKITEM_RESULT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_RESULT"); - - case HAPI_PDG_EVENT_WORKITEM_PRIORITY: - return TEXT("HAPI_PDG_EVENT_WORKITEM_PRIORITY"); - - case HAPI_PDG_EVENT_COOK_START: - return TEXT("HAPI_PDG_EVENT_COOK_START"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR"); - - case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: - return TEXT("HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE"); - - case HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED: - return TEXT("HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED"); - - case HAPI_PDG_EVENT_ALL: - return TEXT("HAPI_PDG_EVENT_ALL"); - case HAPI_PDG_EVENT_LOG: - return TEXT("HAPI_PDG_EVENT_LOG"); - - case HAPI_PDG_EVENT_SCHEDULER_ADDED: - return TEXT("HAPI_PDG_EVENT_SCHEDULER_ADDED"); - case HAPI_PDG_EVENT_SCHEDULER_REMOVED: - return TEXT("HAPI_PDG_EVENT_SCHEDULER_REMOVED"); - case HAPI_PDG_EVENT_SET_SCHEDULER: - return TEXT("HAPI_PDG_EVENT_SET_SCHEDULER"); - - case HAPI_PDG_EVENT_SERVICE_MANAGER_ALL: - return TEXT("HAPI_PDG_EVENT_SERVICE_MANAGER_ALL"); - - case HAPI_PDG_CONTEXT_EVENTS: - return TEXT("HAPI_PDG_CONTEXT_EVENTS"); - default: - break; - } - - return FString::Printf(TEXT("Unknown HAPI_PDG_EventType %d"), InEventType); -} - -FString -FHoudiniEngineUtils::HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState) -{ - switch (InWorkitemState) - { - case HAPI_PDG_WORKITEM_UNDEFINED: - return TEXT("HAPI_PDG_WORKITEM_UNDEFINED"); - case HAPI_PDG_WORKITEM_UNCOOKED: - return TEXT("HAPI_PDG_WORKITEM_UNCOOKED"); - case HAPI_PDG_WORKITEM_WAITING: - return TEXT("HAPI_PDG_WORKITEM_WAITING"); - case HAPI_PDG_WORKITEM_SCHEDULED: - return TEXT("HAPI_PDG_WORKITEM_SCHEDULED"); - case HAPI_PDG_WORKITEM_COOKING: - return TEXT("HAPI_PDG_WORKITEM_COOKING"); - case HAPI_PDG_WORKITEM_COOKED_SUCCESS: - return TEXT("HAPI_PDG_WORKITEM_COOKED_SUCCESS"); - case HAPI_PDG_WORKITEM_COOKED_CACHE: - return TEXT("HAPI_PDG_WORKITEM_COOKED_CACHE"); - case HAPI_PDG_WORKITEM_COOKED_FAIL: - return TEXT("HAPI_PDG_WORKITEM_COOKED_FAIL"); - case HAPI_PDG_WORKITEM_COOKED_CANCEL: - return TEXT("HAPI_PDG_WORKITEM_COOKED_CANCEL"); - case HAPI_PDG_WORKITEM_DIRTY: - return TEXT("HAPI_PDG_WORKITEM_DIRTY"); - default: - break; - } - - return FString::Printf(TEXT("Unknown HAPI_PDG_WorkitemState %d"), InWorkitemState); -} - -FName -FHoudiniEngineUtils::RenameToUniqueActor(AActor* InActor, const FString& InName) -{ - const FName NewName = MakeUniqueObjectName(InActor->GetOuter(), InActor->GetClass(), FName(InName)); - InActor->Rename( *(NewName.ToString()) ); - // TODO: Can we set actor label when actor is pending kill? - InActor->SetActorLabel(NewName.ToString()); - return NewName; -} - -UObject* FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) -{ - check(InActor); - - UObject* PrevObj = nullptr; - UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, InActor->GetOuter(), *InName, true); - if (ExistingObject && ExistingObject != InActor) - { - // Rename the existing object - const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName+TEXT("_old")) ); - ExistingObject->Rename(*(NewName.ToString())); - PrevObj = ExistingObject; - } - InActor->Rename(*InName); - if (UpdateLabel) - InActor->SetActorLabel(InName, true); - return PrevObj; -} - -void -FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - FHoudiniPackageParams& OutPackageParams, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FString &BakeFolder, - const FString &ObjectName, - const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode) -{ - OutPackageParams.GeoId = InIdentifier.GeoId; - OutPackageParams.ObjectId = InIdentifier.ObjectId; - OutPackageParams.PartId = InIdentifier.PartId; - OutPackageParams.BakeFolder = BakeFolder; - OutPackageParams.PackageMode = EPackageMode::Bake; - OutPackageParams.ReplaceMode = InReplaceMode; - OutPackageParams.HoudiniAssetName = HoudiniAssetName; - OutPackageParams.HoudiniAssetActorName = HoudiniAssetName; - OutPackageParams.ObjectName = ObjectName; -} - -bool -FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() -{ - // Update / repopulate the foliage editor mode's mesh list if the foliage editor mode is active. - // TODO: find a better way to do this, the relevant functions are in FEdModeFoliage and FFoliageEdModeToolkit are not API exported - FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); - if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) - { - EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); - EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::IsOuterHoudiniAssetComponent(UObject* Obj) -{ - if (!Obj) - return false; - return Obj->GetOuter() && Obj->GetOuter()->IsA(); -} - -UHoudiniAssetComponent* -FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(UObject* Obj) -{ - return Cast(Obj->GetOuter()); -} - -FString -FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) -{ - // Compute Houdini version string. - FString HoudiniVersionString = FString::Printf( - TEXT("%d.%d.%s%d"), HAPI_VERSION_HOUDINI_MAJOR, - HAPI_VERSION_HOUDINI_MINOR, - (ExtraDigit ? (TEXT("0.")) : TEXT("")), - HAPI_VERSION_HOUDINI_BUILD); - - // If we have a patch version, we need to append it. - if (HAPI_VERSION_HOUDINI_PATCH > 0) - HoudiniVersionString = FString::Printf(TEXT("%s.%d"), *HoudiniVersionString, HAPI_VERSION_HOUDINI_PATCH); - return HoudiniVersionString; -} - -void * -FHoudiniEngineUtils::LoadLibHAPI(FString & StoredLibHAPILocation) -{ - FString HFSPath = TEXT(""); - void * HAPILibraryHandle = nullptr; - - // Look up HAPI_PATH environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . - FString HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HAPI_PATH")); - if (!HFS_ENV_VAR.IsEmpty()) - HFSPath = HFS_ENV_VAR; - - // Look up environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . - HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HFS")); - if (!HFS_ENV_VAR.IsEmpty()) - HFSPath = HFS_ENV_VAR; - - // Get platform specific name of libHAPI. - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - - // If we have a custom location specified through settings, attempt to use that. - bool bCustomPathFound = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->bUseCustomHoudiniLocation) - { - // Create full path to libHAPI binary. - FString CustomHoudiniLocationPath = HoudiniRuntimeSettings->CustomHoudiniLocation.Path; - if (!CustomHoudiniLocationPath.IsEmpty()) - { - // Convert path to absolute if it is relative. - if (FPaths::IsRelative(CustomHoudiniLocationPath)) - CustomHoudiniLocationPath = FPaths::ConvertRelativePathToFull(CustomHoudiniLocationPath); - - FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPICustomPath)) - { - HFSPath = CustomHoudiniLocationPath; - bCustomPathFound = true; - } - } - } - - // We have HFS environment variable defined (or custom location), attempt to load libHAPI from it. - if (!HFSPath.IsEmpty()) - { - if (!bCustomPathFound) - { -#if PLATFORM_WINDOWS - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_WINDOWS); -#elif PLATFORM_MAC - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_MAC); -#elif PLATFORM_LINUX - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_LINUX); -#endif - } - - // Create full path to libHAPI binary. - FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - // libHAPI binary exists at specified location, attempt to load it. - FPlatformProcess::PushDllDirectory(*HFSPath); -#if PLATFORM_WINDOWS - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); -#elif PLATFORM_MAC || PLATFORM_LINUX - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); -#endif - FPlatformProcess::PopDllDirectory(*HFSPath); - - // If library has been loaded successfully we can stop. - if ( HAPILibraryHandle ) - { - if (bCustomPathFound) - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from custom path %s"), *LibHAPIName, *HFSPath); - else - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from HFS environment path %s"), *LibHAPIName, *HFSPath); - - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - } - - // Otherwise, we will attempt to detect Houdini installation. - FString HoudiniLocation = TEXT(HOUDINI_ENGINE_HFS_PATH); - FString LibHAPIPath; - - // Compute Houdini version string. - FString HoudiniVersionString = ComputeVersionString(false); - -#if PLATFORM_WINDOWS - - // On Windows, we have also hardcoded HFS path in plugin configuration file; attempt to load from it. - HFSPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, HAPI_HFS_SUBFOLDER_WINDOWS); - - // Create full path to libHAPI binary. - LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HFSPath); - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); - FPlatformProcess::PopDllDirectory(*HFSPath); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from Plugin defined HFS path %s"), *LibHAPIName, *HFSPath); - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - - // As a second attempt, on Windows, we try to look up location of Houdini Engine in the registry. - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini Engine"), StoredLibHAPILocation, false); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // As a third attempt, we try to look up location of Houdini installation (not Houdini Engine) in the registry. - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini"), StoredLibHAPILocation, false); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // Do similar registry lookups for the 32 bits registry - // Look for the Houdini Engine registry install path - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini Engine"), StoredLibHAPILocation, true); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // ... and for the Houdini registry install path - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini"), StoredLibHAPILocation, true); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // Finally, try to load from a hardcoded program files path. - HoudiniLocation = FString::Printf( - TEXT("C:\\Program Files\\Side Effects Software\\Houdini %s\\%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_WINDOWS); - -#else - -# if PLATFORM_MAC - - // Attempt to load from standard Mac OS X installation. - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/Current/Libraries"), *HoudiniVersionString); - - // Fallback in case the previous one doesnt exist - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/%s/Libraries"), *HoudiniVersionString, *HoudiniVersionString); - - // Fallback in case we're using the steam version - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); - - // Backup Fallback in case we're using the steam version - // (this could probably be removed as paths have changed) - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); - -# elif PLATFORM_LINUX - - // Attempt to load from standard Linux installation. - HoudiniLocation = FString::Printf( - TEXT("/opt/hfs%s/%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_LINUX); - -# endif - -#endif - - // Create full path to libHAPI binary. - LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HoudiniLocation); - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); - FPlatformProcess::PopDllDirectory(*HoudiniLocation); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from expected installation %s"), *LibHAPIName, *HoudiniLocation); - StoredLibHAPILocation = HoudiniLocation; - return HAPILibraryHandle; - } - } - - StoredLibHAPILocation = TEXT(""); - return HAPILibraryHandle; -} - -bool -FHoudiniEngineUtils::IsInitialized() -{ - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) - return false; - - return (FHoudiniApi::IsInitialized(SessionPtr) == HAPI_RESULT_SUCCESS); -} - -bool -FHoudiniEngineUtils::IsHoudiniNodeValid(const HAPI_NodeId& NodeId) -{ - if (NodeId < 0) - return false; - - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - bool ValidationAnswer = 0; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) - { - return false; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsNodeValid( - FHoudiniEngine::Get().GetSession(), NodeId, - NodeInfo.uniqueHoudiniNodeId, &ValidationAnswer)) - { - return false; - } - - return ValidationAnswer; -} - -bool -FHoudiniEngineUtils::HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex) -{ - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), HostAssetId, InputIndex), false); - - return true; -} - -bool -FHoudiniEngineUtils::DestroyHoudiniAsset(const HAPI_NodeId& AssetId) -{ - if (HAPI_RESULT_SUCCESS == FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), AssetId)) - { - return true; - } - - return false; -} - -#if PLATFORM_WINDOWS -void * -FHoudiniEngineUtils::LocateLibHAPIInRegistry( - const FString & HoudiniInstallationType, - FString & StoredLibHAPILocation, - bool LookIn32bitRegistry) -{ - auto FindDll = [&](const FString& InHoudiniInstallationPath) - { - FString HFSPath = FString::Printf(TEXT("%s/%s"), *InHoudiniInstallationPath, HAPI_HFS_SUBFOLDER_WINDOWS); - - // Create full path to libHAPI binary. - FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, HAPI_LIB_OBJECT_WINDOWS); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HFSPath); - void* HAPILibraryHandle = FPlatformProcess::GetDllHandle(HAPI_LIB_OBJECT_WINDOWS); - FPlatformProcess::PopDllDirectory(*HFSPath); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE( - TEXT("Loaded %s from Registry path %s"), HAPI_LIB_OBJECT_WINDOWS, - *HFSPath); - - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - return (void*)0; - }; - - FString HoudiniInstallationPath; - FString HoudiniVersionString = ComputeVersionString(true); - FString RegistryKey = FString::Printf( - TEXT("Software\\%sSide Effects Software\\%s"), - (LookIn32bitRegistry ? TEXT("WOW6432Node\\") : TEXT("")), *HoudiniInstallationType); - - if (FWindowsPlatformMisc::QueryRegKey( - HKEY_LOCAL_MACHINE, *RegistryKey, *HoudiniVersionString, HoudiniInstallationPath)) - { - FPaths::NormalizeDirectoryName(HoudiniInstallationPath); - return FindDll(HoudiniInstallationPath); - } - - return nullptr; -} -#endif - -bool -FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId & OutAssetLibraryId) -{ - OutAssetLibraryId = -1; - - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return false; - - if (!FHoudiniEngineUtils::IsInitialized()) - return false; - - // Get the HDA's file path - // We need to convert relative file path to absolute - FString AssetFileName = HoudiniAsset->GetAssetFileName(); - if (FPaths::IsRelative(AssetFileName)) - AssetFileName = FPaths::ConvertRelativePathToFull(AssetFileName); - - // We need to modify the file name for expanded .hdas - FString FileExtension = FPaths::GetExtension(AssetFileName); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) - { - // the .hda directory is what we should be loading - AssetFileName = FPaths::GetPath(AssetFileName); - } - - // If the hda file exists, we can simply load it directly the file - HAPI_Result Result = HAPI_RESULT_FAILURE; - if ( !AssetFileName.IsEmpty() ) - { - if ( FPaths::FileExists(AssetFileName) - || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName) ) ) - { - // Load the asset from file. - std::string AssetFileNamePlain; - FHoudiniEngineUtils::ConvertUnrealString(AssetFileName, AssetFileNamePlain); - Result = FHoudiniApi::LoadAssetLibraryFromFile( - FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); - } - } - - // Detect license issues - // HoudiniEngine aquires a license when creating/loading a node, not when creating a session - if (Result >= HAPI_RESULT_NO_LICENSE_FOUND && Result < HAPI_RESULT_ASSET_INVALID) - { - FString ErrorDesc = GetErrorDescription(Result); - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: License failed: %s."), *AssetFileName, *ErrorDesc); - - // We must stop the session to prevent further attempts at loading an HDA - // as this could lead to unreal becoming stuck and unresponsive due to license timeout - FHoudiniEngine::Get().StopSession(); - - return false; - } - - // If loading from file failed, try to load using the memory copy - if (Result != HAPI_RESULT_SUCCESS) - { - // Expanded hdas cannot be loaded from Memory - if (HoudiniAsset->IsExpandedHDA() || HoudiniAsset->GetAssetBytesCount() <= 0) - { - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); - return false; - } - else - { - // Warn the user that we are loading from memory - HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from Memory: source asset file not found."), *AssetFileName); - - // Otherwise we will try to load from buffer we've cached. - Result = FHoudiniApi::LoadAssetLibraryFromMemory( - FHoudiniEngine::Get().GetSession(), - reinterpret_cast(HoudiniAsset->GetAssetBytes()), - HoudiniAsset->GetAssetBytesCount(), true, &OutAssetLibraryId); - } - } - - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_MESSAGE(TEXT("Error loading asset library for %s: %s"), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - return true; -} - -bool -FHoudiniEngineUtils::GetSubAssetNames( - const HAPI_AssetLibraryId& AssetLibraryId, - TArray< HAPI_StringHandle >& OutAssetNames) -{ - if (AssetLibraryId < 0) - return false; - - int32 AssetCount = 0; - HAPI_Result Result = HAPI_RESULT_FAILURE; - Result = FHoudiniApi::GetAvailableAssetCount(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetCount); - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Error getting asset count: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - if (AssetCount <= 0) - { - HOUDINI_LOG_ERROR(TEXT("Could not find an asset.")); - return false; - } - - OutAssetNames.SetNumUninitialized(AssetCount); - Result = FHoudiniApi::GetAvailableAssets(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &OutAssetNames[0], AssetCount); - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Unable to retrieve sub asset names: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - if (!AssetCount) - { - HOUDINI_LOG_ERROR(TEXT("No assets found")); - return false; - } - - return true; -} - - -bool -FHoudiniEngineUtils::OpenSubassetSelectionWindow(TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ) -{ - OutPickedAssetName = -1; - - if (AssetNames.Num() <= 0) - return false; - - // Default to the first asset - OutPickedAssetName = AssetNames[0]; - -#if WITH_EDITOR - // Present the user with a dialog for choosing which asset to instantiate. - TSharedPtr ParentWindow; - if (FModuleManager::Get().IsModuleLoaded("MainFrame")) - { - // Check if the main frame is loaded. When using the old main frame it may not be. - IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); - ParentWindow = MainFrame.GetParentWindow(); - } - - if (!ParentWindow.IsValid()) - { - return false; - } - - TSharedPtr AssetSelectionWidget; - TSharedRef 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()) - { - return false; - } - - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - - int32 DialogPickedAssetName = AssetSelectionWidget->GetSelectedAssetName(); - if (DialogPickedAssetName != -1) - { - OutPickedAssetName = DialogPickedAssetName; - return true; - } - else - { - return false; - } -#endif - - return true; -} - -/* -bool -FHoudiniEngineUtils::IsValidNodeId(HAPI_NodeId NodeId) -{ - return NodeId != -1; -} -*/ - -bool -FHoudiniEngineUtils::GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString) -{ - HAPI_AssetInfo AssetInfo; - if (FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo) == HAPI_RESULT_SUCCESS) - { - FHoudiniEngineString HoudiniEngineString(AssetInfo.nameSH); - return HoudiniEngineString.ToFString(NameString); - } - - return false; -} - -bool -FHoudiniEngineUtils::GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer) -{ - PresetBuffer.Empty(); - - HAPI_NodeId NodeId; - HAPI_AssetInfo AssetInfo; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo)) - { - NodeId = AssetInfo.nodeId; - } - else - NodeId = AssetNodeId; - - int32 BufferLength = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPresetBufLength( - FHoudiniEngine::Get().GetSession(), NodeId, - HAPI_PRESETTYPE_BINARY, NULL, &BufferLength), false); - - PresetBuffer.SetNumZeroed(BufferLength); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPreset( - FHoudiniEngine::Get().GetSession(), NodeId, - &PresetBuffer[0], PresetBuffer.Num()), false); - - return true; -} - - -bool -FHoudiniEngineUtils::HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath) -{ - // Retrieve Path to the given Node, relative to the other given Node - if ((InNodeId < 0) || (InRelativeToNodeId < 0)) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InNodeId)) - return false; - - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodePath( - FHoudiniEngine::Get().GetSession(), - InNodeId, InRelativeToNodeId, &StringHandle)) - { - if(FHoudiniEngineString::ToFString(StringHandle, OutPath)) - { - return true; - } - } - return false; -} - -bool -FHoudiniEngineUtils::HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath) -{ - // Do the HAPI query only on first-use - if (!InHGPO.NodePath.IsEmpty()) - return true; - - FString NodePathTemp; - if (InHGPO.AssetId == InHGPO.GeoId) - { - // This is a SOP asset, just return the asset name in this case - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InHGPO.AssetId, &AssetInfo)) - { - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo)) - { - if (FHoudiniEngineString::ToFString(AssetNodeInfo.nameSH, NodePathTemp)) - { - OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); - } - } - } - } - else - { - // This is an OBJ asset, return the path to this geo relative to the asset - if (FHoudiniEngineUtils::HapiGetNodePath(InHGPO.GeoId, InHGPO.AssetId, NodePathTemp)) - { - OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); - } - } - - /*if (OutPath.IsEmpty()) - { - OutPath = TEXT("Empty"); - } - - return NodePath; - */ - - return !OutPath.IsEmpty(); -} - - -bool -FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), - InNodeId, &NodeInfo), false); - - int32 ObjectCount = 0; - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - ObjectCount = 1; - OutObjectInfos.SetNumUninitialized(1); - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( - FHoudiniEngine::Get().GetSession(), - NodeInfo.parentId, &OutObjectInfos[0]), false); - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( - FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); - - if (ObjectCount <= 0) - { - ObjectCount = 1; - OutObjectInfos.SetNumUninitialized(1); - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[0]), false); - } - else - { - OutObjectInfos.SetNumUninitialized(ObjectCount); - for (int32 Idx = 0; Idx < OutObjectInfos.Num(); Idx++) - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( - FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[0], 0, ObjectCount), false); - } - } - else - return false; - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId,&NodeInfo), false); - - int32 ObjectCount = 1; - OutObjectTransforms.SetNumUninitialized(1); - FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); - - OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; - OutObjectTransforms[0].scale[0] = 1.0f; - OutObjectTransforms[0].scale[1] = 1.0f; - OutObjectTransforms[0].scale[2] = 1.0f; - OutObjectTransforms[0].rstOrder = HAPI_SRT; - - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - // Do nothing. Identity transform will be used for the main parent object. - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( - FHoudiniEngine::Get().GetSession(), - InNodeId, nullptr, &ObjectCount), false); - - if (ObjectCount <= 0) - { - // Do nothing. Identity transform will be used for the main asset object. - } - else - { - OutObjectTransforms.SetNumUninitialized(ObjectCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_SRT, &OutObjectTransforms[0], 0, ObjectCount), false); - } - } - else - return false; - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, - &NodeInfo), false); - - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - NodeInfo.parentId, -1, HAPI_SRT, &HapiTransform), false); - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InNodeId, -1, HAPI_SRT, &HapiTransform), false); - } - else - return false; - - // Convert HAPI transform to Unreal one. - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, OutTransform); - - return true; -} - -void -FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform) -{ - if ( HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM ) - { - // Swap Y/Z, invert W - FQuat ObjectRotation( - HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[2], - HapiTransform.rotationQuaternion[1], -HapiTransform.rotationQuaternion[3]); - - // Swap Y/Z and scale - FVector ObjectTranslation(HapiTransform.position[0], HapiTransform.position[2], HapiTransform.position[1]); - ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[2], HapiTransform.scale[1]); - - UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); - } - else - { - FQuat ObjectRotation( - HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[1], - HapiTransform.rotationQuaternion[2], HapiTransform.rotationQuaternion[3]); - - FVector ObjectTranslation( - HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2]); - ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[1], HapiTransform.scale[2]); - - UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); - } -} - -void -FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform) -{ - float HapiMatrix[16]; - FHoudiniApi::ConvertTransformEulerToMatrix(FHoudiniEngine::Get().GetSession(), &HapiTransformEuler, HapiMatrix); - - HAPI_Transform HapiTransformQuat; - FMemory::Memzero< HAPI_Transform >(HapiTransformQuat); - FHoudiniApi::ConvertMatrixToQuat(FHoudiniEngine::Get().GetSession(), HapiMatrix, HAPI_SRT, &HapiTransformQuat); - - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransformQuat, UnrealTransform); -} - -void -FHoudiniEngineUtils::TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform) -{ - FMemory::Memzero< HAPI_Transform >(HapiTransform); - HapiTransform.rstOrder = HAPI_SRT; - - FQuat UnrealRotation = UnrealTransform.GetRotation(); - FVector UnrealTranslation = UnrealTransform.GetTranslation(); - FVector UnrealScale = UnrealTransform.GetScale3D(); - - if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) - { - // Swap Y/Z, invert XYZ - HapiTransform.rotationQuaternion[0] = -UnrealRotation.X; - HapiTransform.rotationQuaternion[1] = -UnrealRotation.Z; - HapiTransform.rotationQuaternion[2] = -UnrealRotation.Y; - HapiTransform.rotationQuaternion[3] = UnrealRotation.W; - - // Swap Y/Z, scale - HapiTransform.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransform.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransform.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - HapiTransform.scale[0] = UnrealScale.X; - HapiTransform.scale[1] = UnrealScale.Z; - HapiTransform.scale[2] = UnrealScale.Y; - } - else - { - HapiTransform.rotationQuaternion[0] = UnrealRotation.X; - HapiTransform.rotationQuaternion[1] = UnrealRotation.Y; - HapiTransform.rotationQuaternion[2] = UnrealRotation.Z; - HapiTransform.rotationQuaternion[3] = UnrealRotation.W; - - HapiTransform.position[0] = UnrealTranslation.X; - HapiTransform.position[1] = UnrealTranslation.Y; - HapiTransform.position[2] = UnrealTranslation.Z; - - HapiTransform.scale[0] = UnrealScale.X; - HapiTransform.scale[1] = UnrealScale.Y; - HapiTransform.scale[2] = UnrealScale.Z; - } -} - -void -FHoudiniEngineUtils::TranslateUnrealTransform( - const FTransform & UnrealTransform, - HAPI_TransformEuler & HapiTransformEuler) -{ - FMemory::Memzero< HAPI_TransformEuler >(HapiTransformEuler); - - HapiTransformEuler.rstOrder = HAPI_SRT; - HapiTransformEuler.rotationOrder = HAPI_XYZ; - - FQuat UnrealRotation = UnrealTransform.GetRotation(); - FVector UnrealTranslation = UnrealTransform.GetTranslation(); - FVector UnrealScale = UnrealTransform.GetScale3D(); - - if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) - { - // switch the quaternion to Y-up, LHR by Swapping Y/Z and negating W - Swap(UnrealRotation.Y, UnrealRotation.Z); - UnrealRotation.W = -UnrealRotation.W; - const FRotator Rotator = UnrealRotation.Rotator(); - - // Negate roll and pitch since they are actually RHR - HapiTransformEuler.rotationEuler[0] = -Rotator.Roll; - HapiTransformEuler.rotationEuler[1] = -Rotator.Pitch; - HapiTransformEuler.rotationEuler[2] = Rotator.Yaw; - - // Swap Y/Z, scale - HapiTransformEuler.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransformEuler.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransformEuler.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - HapiTransformEuler.scale[0] = UnrealScale.X; - HapiTransformEuler.scale[1] = UnrealScale.Z; - HapiTransformEuler.scale[2] = UnrealScale.Y; - } - else - { - const FRotator Rotator = UnrealRotation.Rotator(); - HapiTransformEuler.rotationEuler[0] = Rotator.Roll; - HapiTransformEuler.rotationEuler[1] = Rotator.Yaw; - HapiTransformEuler.rotationEuler[2] = Rotator.Pitch; - - HapiTransformEuler.position[0] = UnrealTranslation.X; - HapiTransformEuler.position[1] = UnrealTranslation.Y; - HapiTransformEuler.position[2] = UnrealTranslation.Z; - - HapiTransformEuler.scale[0] = UnrealScale.X; - HapiTransformEuler.scale[1] = UnrealScale.Y; - HapiTransformEuler.scale[2] = UnrealScale.Z; - } -} - -bool -FHoudiniEngineUtils::UploadHACTransform(UHoudiniAssetComponent* HAC) -{ - if (!HAC || !HAC->bUploadTransformsToHoudiniEngine) - return false; - - // Indicates the HAC has been fully loaded - // TODO: Check! (replaces fullyloaded) - if (!HAC->IsFullyLoaded()) - return false; - - if (HAC->GetAssetCookCount() > 0 && HAC->GetAssetId() >= 0) - { - if (!FHoudiniEngineUtils::HapiSetAssetTransform(HAC->GetAssetId(), HAC->GetComponentTransform())) - return false; - } - - HAC->SetHasComponentTransformChanged(false); - - return true; -} - -bool -FHoudiniEngineUtils::HapiSetAssetTransform(const HAPI_NodeId& AssetId, const FTransform & Transform) -{ - if (AssetId < 0) - return false; - - // Translate Unreal transform to HAPI Euler one. - HAPI_TransformEuler TransformEuler; - FMemory::Memzero< HAPI_TransformEuler >(TransformEuler); - FHoudiniEngineUtils::TranslateUnrealTransform(Transform, TransformEuler); - - // Get the NodeInfo - HAPI_NodeInfo LocalAssetNodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetId, - &LocalAssetNodeInfo), false); - - if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - LocalAssetNodeInfo.parentId, - &TransformEuler), false); - } - else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - AssetId, &TransformEuler), false); - } - else - return false; - - return true; -} - -HAPI_NodeId -FHoudiniEngineUtils::HapiGetParentNodeId(const HAPI_NodeId& NodeId) -{ - HAPI_NodeId ParentId = -1; - if (NodeId >= 0) - { - HAPI_NodeInfo NodeInfo; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) - ParentId = NodeInfo.parentId; - } - - return ParentId; -} - - -// Assign a unique Actor Label if needed -void -FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - // TODO: Necessary?? - -#if WITH_EDITOR - HAPI_NodeId AssetId = HAC->GetAssetId(); - if (AssetId < 0) - return; - - AActor* OwnerActor = HAC->GetOwner(); - if (!OwnerActor) - return; - - if (!OwnerActor->GetName().StartsWith(AHoudiniAssetActor::StaticClass()->GetName())) - return; - - // Assign unique actor label based on asset name if it seems to have not been renamed already - FString UniqueName; - if (FHoudiniEngineUtils::GetHoudiniAssetName(AssetId, UniqueName)) - FActorLabelUtilities::SetActorLabelUnique(OwnerActor, UniqueName); -#endif -} - -bool -FHoudiniEngineUtils::GetLicenseType(FString & LicenseType) -{ - LicenseType = TEXT(""); - HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetSessionEnvInt( - FHoudiniEngine::Get().GetSession(), HAPI_SESSIONENVINT_LICENSE, - (int32 *)&LicenseTypeValue), false); - - switch (LicenseTypeValue) - { - case HAPI_LICENSE_NONE: - { - LicenseType = TEXT("No License Acquired"); - break; - } - - case HAPI_LICENSE_HOUDINI_ENGINE: - { - LicenseType = TEXT("Houdini Engine"); - break; - } - - case HAPI_LICENSE_HOUDINI: - { - LicenseType = TEXT("Houdini"); - break; - } - - case HAPI_LICENSE_HOUDINI_FX: - { - LicenseType = TEXT("Houdini FX"); - break; - } - - case HAPI_LICENSE_HOUDINI_ENGINE_INDIE: - { - LicenseType = TEXT("Houdini Engine Indie"); - break; - } - - case HAPI_LICENSE_HOUDINI_INDIE: - { - LicenseType = TEXT("Houdini Indie"); - break; - } - - case HAPI_LICENSE_MAX: - default: - { - return false; - } - } - - return true; -} - -// Check if the Houdini asset component (or parent HAC of a parameter) is being cooked -bool -FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(UObject* InObj) -{ - if (!InObj) - return false; - - UHoudiniAssetComponent* HoudiniAssetComponent = nullptr; - - if (InObj->IsA()) - { - HoudiniAssetComponent = Cast(InObj); - } - else if (InObj->IsA()) - { - UHoudiniParameter* Parameter = Cast(InObj); - if (!Parameter) - return false; - - HoudiniAssetComponent = Cast(Parameter->GetOuter()); - } - - if (!HoudiniAssetComponent) - return false; - - EHoudiniAssetState AssetState = HoudiniAssetComponent->GetAssetState(); - - return AssetState >= EHoudiniAssetState::PreCook && AssetState <= EHoudiniAssetState::PostCook; -} - -void -FHoudiniEngineUtils::UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate) -{ - TArray ObjectsToUpdate; - ObjectsToUpdate.Add(InObjectToUpdate); - - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() - { - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - } -} - -void -FHoudiniEngineUtils::UpdateEditorProperties(TArray ObjectsToUpdate, const bool& InForceFullUpdate) -{ - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() - { - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - } -} - -void FHoudiniEngineUtils::UpdateBlueprintEditor(UHoudiniAssetComponent* HAC) -{ - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [HAC]() - { - FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); - } -} - -void -FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate) -{ - // TODO: Don't use this method. Prefer using IDetailLayoutBuilder::ForceRefreshDetails(). - // Example to correctly update details panel through IDetailCategoryBuilder / IDetailLayoutBuilder - // IDetailCategoryBuilder &CategoryBuilder = StructBuilder.GetParentCategory(); - // IDetailLayoutBuilder &LayoutBuilder = CategoryBuilder.GetParentLayout(); - // LayoutBuilder.ForceRefreshDetails(); - -#if WITH_EDITOR - if (!bInForceFullUpdate) - { - // bNeedFullUpdate is false only when small changes (parameters value) have been made - // We do not reselect the actor to avoid loosing the currently selected parameter - if(GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); - - return; - } - - // We now want to get all the components/actors owning the objects to update - TArray AllSceneComponents; - for (auto CurrentObject : ObjectsToUpdate) - { - if (!CurrentObject || CurrentObject->IsPendingKill()) - continue; - - // In some case, the object itself is the component - USceneComponent* SceneComp = Cast(CurrentObject); - if (!SceneComp) - { - SceneComp = Cast(CurrentObject->GetOuter()); - } - - if (SceneComp && !SceneComp->IsPendingKill()) - { - AllSceneComponents.Add(SceneComp); - continue; - } - } - - TArray AllActors; - for (auto CurrentSceneComp : AllSceneComponents) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill()) - continue; - - AActor* Actor = CurrentSceneComp->GetOwner(); - if (Actor && !Actor->IsPendingKill()) - AllActors.Add(Actor); - } - - // Updating the editor properties can be done in two ways, depending if we're in the BP editor or not - // If we have a parent actor, we're not in the BP Editor, so update via the property editor module - if (AllActors.Num() > 0) - { - // Get the property editor module - FPropertyEditorModule& PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // This will actually force a refresh of all the details view - //PropertyModule.NotifyCustomizationModuleChanged(); - - TArray SelectedActors; - for (auto Actor : AllActors) - { - if (Actor && Actor->IsSelected()) - SelectedActors.Add(Actor); - } - - if (SelectedActors.Num() > 0) - { - PropertyModule.UpdatePropertyViews(SelectedActors); - } - - // 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. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - { - // We have no details panel, nothing to update. - continue; - } - - // Get the selected actors for this details panels and check if one of ours belongs to it - const TArray>& SelectedDetailActors = DetailsView->GetSelectedActors(); - bool bFoundActor = false; - for (int32 ActorIdx = 0; ActorIdx < SelectedDetailActors.Num(); ActorIdx++) - { - TWeakObjectPtr SelectedActor = SelectedDetailActors[ActorIdx]; - if (SelectedActor.IsValid() && AllActors.Contains(SelectedActor.Get())) - { - bFoundActor = true; - break; - } - } - - // None of our actors belongs to this detail panel, no need to update it - if (!bFoundActor) - continue; - - // Refresh that details panels using its current selection - TArray Selection; - for (auto DetailsActor : SelectedDetailActors) - { - if (DetailsActor.IsValid()) - Selection.Add(DetailsActor.Get()); - } - - // Reset selected actors, force refresh and override the lock. - DetailsView->SetObjects(SelectedActors, bInForceFullUpdate, true); - - if (GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); - } - } - else - { - // TODO: Do we need to do Blueprint Editor updates here or can we confine it to "post output processing"? - - } - - /* - // Reset the full update flag - if (bNeedFullUpdate) - HAC->SetEditorPropertiesNeedFullUpdate(false); - */ - - return; -#endif -} - -void FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC) -{ - //UHoudiniAssetComponent* HACTemplate = HAC->GetCachedTemplate(); - //UBlueprintGeneratedClass* OwnerBPClass = Cast(HACTemplate->GetOuter()); - //if (!OwnerBPClass) - // return; - - ///* - //FBlueprintEditor* BlueprintEditor = static_cast(FAssetEditorManager::Get().FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); - //if (!BlueprintEditor) - // return; - //*/ - - //// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built. - //UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - //FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); - //if (!BlueprintEditor) - // return; - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(HAC); - if (!BlueprintEditor) - return; - - TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); - if (SCSEditor.IsValid()) - { - SCSEditor->UpdateTree(true); - SCSEditor->DumpTree(); - } - BlueprintEditor->RefreshMyBlueprint(); - - //BlueprintEditor->RefreshMyBlueprint(); - //BlueprintEditor->RefreshInspector(); - //BlueprintEditor->RefreshEditors(); - - // Also somehow reselect ? -} - -HAPI_Result -FHoudiniEngineUtils::SetAttributeStringData( - const FString& InString, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo) -{ - TArray StringArray; - StringArray.Add(InString); - - return SetAttributeStringData(StringArray, InNodeId, InPartId, InAttributeName, InAttributeInfo); -} - -HAPI_Result -FHoudiniEngineUtils::SetAttributeStringData( - const TArray& InStringArray, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo ) -{ - TArray StringDataArray; - for (auto CurrentString : InStringArray) - { - // Append the converted string to the string array - StringDataArray.Add(FHoudiniEngineUtils::ExtractRawString(CurrentString)); - } - - // Set the attribute's string data - HAPI_Result result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - TCHAR_TO_ANSI(*InAttributeName), &InAttributeInfo, - StringDataArray.GetData(), 0, InAttributeInfo.count); - - // ExtractRawString allocates memory using malloc, free it! - FreeRawStringMemory(StringDataArray); - - return result; -} - -char * -FHoudiniEngineUtils::ExtractRawString(const FString& InString) -{ - if (InString.IsEmpty()) - return nullptr; - - std::string ConvertedString = TCHAR_TO_UTF8(*InString); - - // Allocate space for unique string. - int32 UniqueStringBytes = ConvertedString.size() + 1; - char * UniqueString = static_cast(FMemory::Malloc(UniqueStringBytes)); - - FMemory::Memzero(UniqueString, UniqueStringBytes); - FMemory::Memcpy(UniqueString, ConvertedString.c_str(), ConvertedString.size()); - - return UniqueString; -} - -void -FHoudiniEngineUtils::FreeRawStringMemory(const char*& InRawString) -{ - if (InRawString == nullptr) - return; - - // Do not attempt to free empty strings! - if (!InRawString[0]) - return; - - FMemory::Free((void*)InRawString); - InRawString = nullptr; -} - -void -FHoudiniEngineUtils::FreeRawStringMemory(TArray& InRawStringArray) -{ - // ExtractRawString allocates memory using malloc, free it! - for (auto CurrentStrPtr : InRawStringArray) - { - FreeRawStringMemory(CurrentStrPtr); - } - InRawStringArray.Empty(); -} - -bool -FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // No need to add another component if we already show the logo - if (FHoudiniEngineUtils::HasHoudiniLogo(HAC)) - return true; - - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - UStaticMeshComponent * HoudiniLogoSMC = NewObject< UStaticMeshComponent >( - HAC, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!HoudiniLogoSMC) - return false; - - HoudiniLogoSMC->SetStaticMesh(HoudiniLogoSM); - HoudiniLogoSMC->SetVisibility(true); - HoudiniLogoSMC->SetHiddenInGame(true); - // Attach created static mesh component to our Houdini component. - HoudiniLogoSMC->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniLogoSMC->RegisterComponent(); - - return true; -} - -bool -FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the Houdini Logo SM - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - // Iterate on the HAC's component - for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) - continue; - - // Get the static mesh component - UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) - continue; - - // Check if the SMC is the Houdini Logo - if (SMC->GetStaticMesh() != HoudiniLogoSM) - continue; - - SMC->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SMC->UnregisterComponent(); - SMC->DestroyComponent(); - - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the Houdini Logo SM - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - // Iterate on the HAC's component - for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) - continue; - - // Get the static mesh component - UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) - continue; - - // Check if the SMC is the Houdini Logo - if (SMC->GetStaticMesh() == HoudiniLogoSM) - return true; - } - - return false; -} - -int32 -FHoudiniEngineUtils::HapiGetVertexListForGroup( - const HAPI_NodeId& GeoId, - const HAPI_PartInfo& PartInfo, - const FString& GroupName, - const TArray& FullVertexList, - TArray& NewVertexList, - TArray& AllVertexList, - TArray& AllFaceList, - TArray& AllGroupFaceIndices, - int32& FirstValidVertex, - int32& FirstValidPrim, - const bool& isPackedPrim) -{ - int32 ProcessedWedges = 0; - AllFaceList.Empty(); - FirstValidPrim = 0; - FirstValidVertex = 0; - NewVertexList.Init(-1, FullVertexList.Num()); - - // Get the faces membership for this group - bool bAllEquals = false; - TArray PartGroupMembership; - if (!FHoudiniEngineUtils::HapiGetGroupMembership( - GeoId, PartInfo, HAPI_GROUPTYPE_PRIM, GroupName, PartGroupMembership, bAllEquals)) - return false; - - // Go through all primitives. - for (int32 FaceIdx = 0; FaceIdx < PartGroupMembership.Num(); ++FaceIdx) - { - if (PartGroupMembership[FaceIdx] <= 0) - { - // The face is not in the group, skip - continue; - } - - // Add the face's index. - AllFaceList.Add(FaceIdx); - - // Get the index of this face's vertices - int32 FirstVertexIdx = FaceIdx * 3; - int32 SecondVertexIdx = FirstVertexIdx + 1; - int32 LastVertexIdx = FirstVertexIdx + 2; - - // This face is a member of specified group. - // Add all 3 vertices - if (FullVertexList.IsValidIndex(LastVertexIdx)) - { - NewVertexList[FirstVertexIdx] = FullVertexList[FirstVertexIdx]; - NewVertexList[SecondVertexIdx] = FullVertexList[SecondVertexIdx]; - NewVertexList[LastVertexIdx] = FullVertexList[LastVertexIdx]; - } - - // Mark these vertex indices as used. - if (AllVertexList.IsValidIndex(LastVertexIdx)) - { - AllVertexList[FirstVertexIdx] = 1; - AllVertexList[SecondVertexIdx] = 1; - AllVertexList[LastVertexIdx] = 1; - } - - // Mark this face as used. - if (AllGroupFaceIndices.IsValidIndex(FaceIdx)) - AllGroupFaceIndices[FaceIdx] = 1; - - if (ProcessedWedges == 0) - { - // Keep track of the first valid vertex/face indices for this group - // This will be useful later on when extracting attributes - FirstValidVertex = FirstVertexIdx; - FirstValidPrim = FaceIdx; - } - - ProcessedWedges += 3; - } - - return ProcessedWedges; -} - -bool -FHoudiniEngineUtils::HapiGetGroupNames( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const HAPI_GroupType& GroupType, const bool& isPackedPrim, - TArray& OutGroupNames) -{ - int32 GroupCount = 0; - if (!isPackedPrim) - { - // Get group count on the geo - HAPI_GeoInfo GeoInfo; - FHoudiniApi::GeoInfo_Init(&GeoInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo), false); - - if (GroupType == HAPI_GROUPTYPE_POINT) - GroupCount = GeoInfo.pointGroupCount; - else if (GroupType == HAPI_GROUPTYPE_PRIM) - GroupCount = GeoInfo.primitiveGroupCount; - } - else - { - // We need the group count for this packed prim - int32 PointGroupCount = 0, PrimGroupCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupCountOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PointGroupCount, &PrimGroupCount), false); - - if (GroupType == HAPI_GROUPTYPE_POINT) - GroupCount = PointGroupCount; - else if (GroupType == HAPI_GROUPTYPE_PRIM) - GroupCount = PrimGroupCount; - } - - if (GroupCount <= 0) - return true; - - TArray GroupNameStringHandles; - GroupNameStringHandles.SetNumZeroed(GroupCount); - if (!isPackedPrim) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNames( - FHoudiniEngine::Get().GetSession(), - GeoId, GroupType, &GroupNameStringHandles[0], GroupCount), false); - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNamesOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, GroupType, &GroupNameStringHandles[0], GroupCount), false); - } - - OutGroupNames.SetNum(GroupCount); - for (int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx) - { - FString CurrentGroupName = TEXT(""); - FHoudiniEngineString::ToFString(GroupNameStringHandles[NameIdx], CurrentGroupName); - OutGroupNames[NameIdx] = CurrentGroupName; - } - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetGroupMembership( - const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, - const HAPI_GroupType& GroupType, const FString & GroupName, - TArray& OutGroupMembership, bool& OutAllEquals) -{ - int32 ElementCount = (GroupType == HAPI_GROUPTYPE_POINT) ? PartInfo.pointCount : PartInfo.faceCount; - if (ElementCount < 1) - return false; - OutGroupMembership.SetNum(ElementCount); - - OutAllEquals = false; - std::string ConvertedGroupName = TCHAR_TO_UTF8(*GroupName); - if (!PartInfo.isInstanced) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembership( - FHoudiniEngine::Get().GetSession(), - GeoId, PartInfo.id, GroupType,ConvertedGroupName.c_str(), - &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembershipOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), GeoId, PartInfo.id, GroupType, - ConvertedGroupName.c_str(), &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); - } - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize, - HAPI_AttributeOwner InOwner) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniEngineUtils::HapiGetAttributeDataAsFloat")); - - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Allocate sufficient buffer for data. - OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); - - return true; - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Expected Float, found an int, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray IntData; - IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(IntData.Num()); - for (int32 Idx = 0; Idx < IntData.Num(); Idx++) - { - OutData[Idx] = (float)IntData[Idx]; - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from integer."), *FString(InAttribName)); - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - // Expected Float, found a string, try to convert the attribute - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) - { - bool bConversionError = false; - OutData.SetNum(StringData.Num()); - for (int32 Idx = 0; Idx < StringData.Num(); Idx++) - { - if (StringData[Idx].IsNumeric()) - OutData[Idx] = FCString::Atof(*StringData[Idx]); - else - bConversionError = true; - } - - if (!bConversionError) - { - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from string."), *FString(InAttribName)); - return true; - } - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a float attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - const int32& InTupleSize, - const HAPI_AttributeOwner& InOwner) -{ - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Allocate sufficient buffer for data. - OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); - - return true; - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Expected Int, found a float, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray FloatData; - FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the float values - if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(FloatData.Num()); - for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) - { - OutData[Idx] = (int32)FloatData[Idx]; - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from float."), *FString(InAttribName)); - - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - // Expected Int, found a string, try to convert the attribute - TArray StringData; - if(FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) - { - bool bConversionError = false; - OutData.SetNum(StringData.Num()); - for (int32 Idx = 0; Idx < StringData.Num(); Idx++) - { - if (StringData[Idx].IsNumeric()) - OutData[Idx] = FCString::Atoi(*StringData[Idx]); - else - bConversionError = true; - } - - if (!bConversionError) - { - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from string."), *FString(InAttribName)); - return true; - } - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be an integer attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsString( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize, - HAPI_AttributeOwner InOwner) -{ - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - return FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, OutData); - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Expected string, found a float, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray FloatData; - FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the float values - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(FloatData.Num()); - for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) - { - OutData[Idx] = FString::SanitizeFloat(FloatData[Idx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from float."), *FString(InAttribName)); - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Expected String, found an int, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray IntData; - IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(IntData.Num()); - for (int32 Idx = 0; Idx < IntData.Num(); Idx++) - { - OutData[Idx] = FString::FromInt(IntData[Idx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from integer."), *FString(InAttribName)); - return true; - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a string attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& InAttributeInfo, - TArray& OutData) -{ - if (!InAttributeInfo.exists) - return false; - - // Extract the StringHandles - TArray StringHandles; - StringHandles.Init(-1, InAttributeInfo.count * InAttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, &InAttributeInfo, - &StringHandles[0], 0, InAttributeInfo.count), false); - - // Set the output data size - OutData.SetNum(StringHandles.Num()); - - // Convert the StringHandles to FString. - // We'll use a map to minimize the number of HAPI calls - TMap StringHandleToStringMap; - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - const HAPI_StringHandle& CurrentSH = StringHandles[Idx]; - if (CurrentSH < 0) - { - OutData[Idx] = TEXT(""); - continue; - } - - FString* FoundString = StringHandleToStringMap.Find(CurrentSH); - if (FoundString) - { - OutData[Idx] = *FoundString; - } - else - { - FString HapiString = TEXT(""); - FHoudiniEngineString::ToFString(CurrentSH, HapiString); - - StringHandleToStringMap.Add(CurrentSH, HapiString); - OutData[Idx] = HapiString; - } - } - - return true; -} - -bool -FHoudiniEngineUtils::HapiCheckAttributeExists( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const char * AttribName, HAPI_AttributeOwner Owner) -{ - if (Owner == HAPI_ATTROWNER_INVALID) - { - for (int32 OwnerIdx = 0; OwnerIdx < HAPI_ATTROWNER_MAX; OwnerIdx++) - { - if (HapiCheckAttributeExists(GeoId, PartId, AttribName, (HAPI_AttributeOwner)OwnerIdx)) - { - return true; - } - } - } - else - { - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, AttribName, Owner, &AttribInfo), false); - - return AttribInfo.exists; - } - - return false; -} - -bool -FHoudiniEngineUtils::IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType) -{ - // Check for - // - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE (unreal_instance) on points/detail - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT)) - { - OutInstancerType = EHoudiniInstancerType::AttributeInstancer; - return true; - } - - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL)) - { - OutInstancerType = EHoudiniInstancerType::AttributeInstancer; - return true; - } - - // - HAPI_UNREAL_ATTRIB_INSTANCE (instance) on points - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT)) - { - OutInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsString( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const FString& DefaultValue, - FString& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParamInfo; - FHoudiniApi::ParmInfo_Init(&FoundParamInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParamInfo), false); - - // .. and value - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), NodeId, false, - &StringHandle, FoundParamInfo.stringValuesIndex, 1), false); - - // Convert the string handle to FString - return FHoudiniEngineString::ToFString(StringHandle, OutValue); -} - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const int32& DefaultValue, - int32& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParmInfo; - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), false); - - // .. and value - int32 Value = DefaultValue; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), NodeId, &Value, - FoundParmInfo.intValuesIndex, 1), false); - - OutValue = Value; - - return true; -} - - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsFloat( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const float& DefaultValue, - float& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParmInfo; - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), false); - - // .. and value - float Value = DefaultValue; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), NodeId, &Value, - FoundParmInfo.floatValuesIndex, 1), false); - - OutValue = Value; - - return true; -} - -HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo) -{ - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo); - if (NodeInfo.parmCount <= 0) - return -1; - - HAPI_ParmId ParmId = HapiFindParameterByNameOrTag(NodeInfo.id, ParmName); - if ((ParmId < 0) || (ParmId >= NodeInfo.parmCount)) - return -1; - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), -1); - - return ParmId; -} - - -HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName) -{ - // First, try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), -1); - - if (ParmId >= 0) - return ParmId; - - // Second, try to find it by its tag - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmWithTag( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), -1); - - if (ParmId >= 0) - return ParmId; - - return -1; -} - -int32 -FHoudiniEngineUtils::HapiGetAttributeOfType( - const HAPI_NodeId& GeoId, - const HAPI_NodeId& PartId, - const HAPI_AttributeOwner& AttributeOwner, - const HAPI_AttributeTypeInfo& AttributeType, - TArray< HAPI_AttributeInfo >& MatchingAttributesInfo, - TArray< FString >& MatchingAttributesName) -{ - int32 NumberOfAttributeFound = 0; - - // Get the part infos - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, &PartInfo), NumberOfAttributeFound); - - // Get All attribute names for that part - int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; - - TArray AttribNameSHArray; - AttribNameSHArray.SetNum(nAttribCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, AttributeOwner, - AttribNameSHArray.GetData(), nAttribCount), NumberOfAttributeFound); - - // Iterate on all the attributes, and get their part infos to get their type - for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) - { - // Get the name ... - FString HapiString = TEXT(""); - FHoudiniEngineString::ToFString(AttribNameSHArray[Idx], HapiString); - - // ... then the attribute info - HAPI_AttributeInfo AttrInfo; - FHoudiniApi::AttributeInfo_Init(&AttrInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, TCHAR_TO_UTF8(*HapiString), - AttributeOwner, &AttrInfo)) - continue; - - if (!AttrInfo.exists) - continue; - - // ... check the type - if (AttrInfo.typeInfo != AttributeType) - continue; - - MatchingAttributesInfo.Add(AttrInfo); - MatchingAttributesName.Add(HapiString); - - NumberOfAttributeFound++; - } - - return NumberOfAttributeFound; -} - -HAPI_PartInfo -FHoudiniEngineUtils::ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo) -{ - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - - PartInfo.id = InHPartInfo.PartId; - //PartInfo.nameSH = InHPartInfo.Name; - - switch (InHPartInfo.Type) - { - case EHoudiniPartType::Mesh: - PartInfo.type = HAPI_PARTTYPE_MESH; - break; - case EHoudiniPartType::Curve: - PartInfo.type = HAPI_PARTTYPE_CURVE; - break; - case EHoudiniPartType::Instancer: - PartInfo.type = HAPI_PARTTYPE_INSTANCER; - break; - case EHoudiniPartType::Volume: - PartInfo.type = HAPI_PARTTYPE_VOLUME; - break; - default: - case EHoudiniPartType::Invalid: - PartInfo.type = HAPI_PARTTYPE_INVALID; - break; - } - - PartInfo.faceCount = InHPartInfo.FaceCount; - PartInfo.vertexCount = InHPartInfo.VertexCount; - PartInfo.pointCount = InHPartInfo.PointCount; - - PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = InHPartInfo.PointAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX] = InHPartInfo.VertexAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_PRIM] = InHPartInfo.PrimitiveAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL] = InHPartInfo.DetailAttributeCounts; - - PartInfo.isInstanced = InHPartInfo.bIsInstanced; - - PartInfo.instancedPartCount = InHPartInfo.InstancedPartCount; - PartInfo.instanceCount = InHPartInfo.InstanceCount; - - PartInfo.hasChanged = InHPartInfo.bHasChanged; - - return PartInfo; -} - -int32 -FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - TArray< FHoudiniMeshSocket >& AllSockets, - const bool& isPackedPrim) -{ - int32 FoundSocketCount = 0; - - // Attributes we are interested in. - // Position - TArray Positions; - HAPI_AttributeInfo AttribInfoPositions; - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bool bHasRotation = false; - TArray Rotations; - HAPI_AttributeInfo AttribInfoRotations; - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bool bHasScale = false; - TArray Scales; - HAPI_AttributeInfo AttribInfoScales; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // Socket Name - bool bHasNames = false; - TArray Names; - HAPI_AttributeInfo AttribInfoNames; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bool bHasActors = false; - TArray Actors; - HAPI_AttributeInfo AttribInfoActors; - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bool bHasTags = false; - TArray Tags; - HAPI_AttributeInfo AttribInfoTags; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - - // Lambda function for creating the socket and adding it to the array - // Shared between the by Attribute / by Group methods - auto AddSocketToArray = [&](const int32& PointIdx) - { - FHoudiniMeshSocket CurrentSocket; - FVector currentPosition = FVector::ZeroVector; - if (Positions.IsValidIndex(PointIdx * 3 + 2)) - { - currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - FVector currentScale = FVector::OneVector; - if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) - { - currentScale.X = Scales[PointIdx * 3]; - currentScale.Y = Scales[PointIdx * 3 + 2]; - currentScale.Z = Scales[PointIdx * 3 + 1]; - } - - FQuat currentRotation = FQuat::Identity; - if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) - { - currentRotation.X = Rotations[PointIdx * 4]; - currentRotation.Y = Rotations[PointIdx * 4 + 2]; - currentRotation.Z = Rotations[PointIdx * 4 + 1]; - currentRotation.W = -Rotations[PointIdx * 4 + 3]; - } - - if (bHasNames && Names.IsValidIndex(PointIdx)) - CurrentSocket.Name = Names[PointIdx]; - - if (bHasActors && Actors.IsValidIndex(PointIdx)) - CurrentSocket.Actor = Actors[PointIdx]; - - if (bHasTags && Tags.IsValidIndex(PointIdx)) - CurrentSocket.Tag = Tags[PointIdx]; - - // If the scale attribute wasn't set on all socket, we might end up - // with a zero scale socket, avoid that. - if (currentScale == FVector::ZeroVector) - currentScale = FVector::OneVector; - - CurrentSocket.Transform.SetLocation(currentPosition); - CurrentSocket.Transform.SetRotation(currentRotation); - CurrentSocket.Transform.SetScale3D(currentScale); - - // We want to make sure we're not adding the same socket multiple times - AllSockets.AddUnique(CurrentSocket); - - FoundSocketCount++; - - return true; - }; - - - // Lambda function for reseting the arrays/attributes - auto ResetArraysAndAttr = [&]() - { - // Position - Positions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bHasRotation = false; - Rotations.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bHasScale = false; - Scales.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // Socket Name - bHasNames = false; - Names.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bHasActors = false; - Actors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bHasTags = false; - Tags.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - }; - - //------------------------------------------------------------------------- - // FIND SOCKETS BY DETAIL ATTRIBUTES - //------------------------------------------------------------------------- - - int32 SocketIdx = 0; - bool HasSocketAttributes = true; - while (HasSocketAttributes) - { - // Build the current socket's prefix - FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(SocketIdx); - - // Reset the arrays and attributes - ResetArraysAndAttr(); - - // Retrieve position data. - FString SocketPosAttr = SocketAttrPrefix + TEXT("_pos"); - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, TCHAR_TO_ANSI(*SocketPosAttr), - AttribInfoPositions, Positions, 0, HAPI_ATTROWNER_DETAIL)) - break; - - if (!AttribInfoPositions.exists) - { - // No need to keep looking for socket attributes - HasSocketAttributes = false; - break; - } - - // Retrieve rotation data. - FString SocketRotAttr = SocketAttrPrefix + TEXT("_rot"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketRotAttr), AttribInfoRotations, Rotations, 0, HAPI_ATTROWNER_DETAIL)) - bHasRotation = true; - - // Retrieve scale data. - FString SocketScaleAttr = SocketAttrPrefix + TEXT("_scale"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketScaleAttr), AttribInfoScales, Scales, 0, HAPI_ATTROWNER_DETAIL)) - bHasScale = true; - - // Retrieve mesh socket names. - FString SocketNameAttr = SocketAttrPrefix + TEXT("_name"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketNameAttr), AttribInfoNames, Names)) - bHasNames = true; - - // Retrieve mesh socket actor. - FString SocketActorAttr = SocketAttrPrefix + TEXT("_actor"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketActorAttr), AttribInfoActors, Actors)) - bHasActors = true; - - // Retrieve mesh socket tags. - FString SocketTagAttr = SocketAttrPrefix + TEXT("_tag"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketTagAttr), AttribInfoTags, Tags)) - bHasTags = true; - - // Add the socket to the array - AddSocketToArray(0); - - // Try to find the next socket - SocketIdx++; - } - - return FoundSocketCount; -} - - -int32 -FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - TArray& AllSockets, - const bool& isPackedPrim) -{ - // Attributes we are interested in. - // Position - TArray Positions; - HAPI_AttributeInfo AttribInfoPositions; - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bool bHasRotation = false; - TArray Rotations; - HAPI_AttributeInfo AttribInfoRotations; - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bool bHasScale = false; - TArray Scales; - HAPI_AttributeInfo AttribInfoScales; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // We can also get the sockets rotation from the normal - bool bHasNormals = false; - TArray Normals; - HAPI_AttributeInfo AttribInfoNormals; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Socket Name - bool bHasNames = false; - TArray Names; - HAPI_AttributeInfo AttribInfoNames; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bool bHasActors = false; - TArray Actors; - HAPI_AttributeInfo AttribInfoActors; - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bool bHasTags = false; - TArray Tags; - HAPI_AttributeInfo AttribInfoTags; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - - // Lambda function for creating the socket and adding it to the array - // Shared between the by Attribute / by Group methods - int32 FoundSocketCount = 0; - auto AddSocketToArray = [&](const int32& PointIdx) - { - FHoudiniMeshSocket CurrentSocket; - FVector currentPosition = FVector::ZeroVector; - if (Positions.IsValidIndex(PointIdx * 3 + 2)) - { - currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - FVector currentScale = FVector::OneVector; - if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) - { - currentScale.X = Scales[PointIdx * 3]; - currentScale.Y = Scales[PointIdx * 3 + 2]; - currentScale.Z = Scales[PointIdx * 3 + 1]; - } - - FQuat currentRotation = FQuat::Identity; - if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) - { - currentRotation.X = Rotations[PointIdx * 4]; - currentRotation.Y = Rotations[PointIdx * 4 + 2]; - currentRotation.Z = Rotations[PointIdx * 4 + 1]; - currentRotation.W = -Rotations[PointIdx * 4 + 3]; - } - else if (bHasNormals && Normals.IsValidIndex(PointIdx * 3 + 2)) - { - FVector vNormal; - vNormal.X = Normals[PointIdx * 3]; - vNormal.Y = Normals[PointIdx * 3 + 2]; - vNormal.Z = Normals[PointIdx * 3 + 1]; - - if (vNormal != FVector::ZeroVector) - currentRotation = FQuat::FindBetween(FVector::UpVector, vNormal); - } - - if (bHasNames && Names.IsValidIndex(PointIdx)) - CurrentSocket.Name = Names[PointIdx]; - - if (bHasActors && Actors.IsValidIndex(PointIdx)) - CurrentSocket.Actor = Actors[PointIdx]; - - if (bHasTags && Tags.IsValidIndex(PointIdx)) - CurrentSocket.Tag = Tags[PointIdx]; - - // If the scale attribute wasn't set on all socket, we might end up - // with a zero scale socket, avoid that. - if (currentScale == FVector::ZeroVector) - currentScale = FVector::OneVector; - - CurrentSocket.Transform.SetLocation(currentPosition); - CurrentSocket.Transform.SetRotation(currentRotation); - CurrentSocket.Transform.SetScale3D(currentScale); - - // We want to make sure we're not adding the same socket multiple times - AllSockets.AddUnique(CurrentSocket); - - FoundSocketCount++; - - return true; - }; - - - // Lambda function for reseting the arrays/attributes - auto ResetArraysAndAttr = [&]() - { - // Position - Positions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bHasRotation = false; - Rotations.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bHasScale = false; - Scales.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // When using socket groups, we can also get the sockets rotation from the normal - bHasNormals = false; - Normals.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Socket Name - bHasNames = false; - Names.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bHasActors = false; - Actors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bHasTags = false; - Tags.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - }; - - //------------------------------------------------------------------------- - // FIND SOCKETS BY POINT GROUPS - //------------------------------------------------------------------------- - - // Get object / geo group memberships for primitives. - TArray GroupNames; - if (!FHoudiniEngineUtils::HapiGetGroupNames( - GeoId, PartId, HAPI_GROUPTYPE_POINT, isPackedPrim, GroupNames)) - { - HOUDINI_LOG_MESSAGE(TEXT("GetMeshSocketList: Geo [%d] Part [%d] non-fatal error reading point group names"), GeoId, PartId); - } - - // First, we want to make sure we have at least one socket group before continuing - bool bHasSocketGroup = false; - for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) - { - const FString & GroupName = GroupNames[GeoGroupNameIdx]; - if (GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) - || GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) - { - bHasSocketGroup = true; - break; - } - } - - if (!bHasSocketGroup) - return FoundSocketCount; - - // Get the part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PartInfo)) - return false; - - // Reset the data arrays and attributes - ResetArraysAndAttr(); - - // Retrieve position data. - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, Positions)) - return false; - - // Retrieve rotation data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_ROTATION, AttribInfoRotations, Rotations)) - bHasRotation = true; - - // Retrieve normal data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, Normals)) - bHasNormals = true; - - // Retrieve scale data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_SCALE, AttribInfoScales, Scales)) - bHasScale = true; - - // Retrieve mesh socket names. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, AttribInfoNames, Names)) - bHasNames = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD, AttribInfoNames, Names)) - bHasNames = true; - - // Retrieve mesh socket actor. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR, AttribInfoActors, Actors)) - bHasActors = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD, AttribInfoActors, Actors)) - bHasActors = true; - - // Retrieve mesh socket tags. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, AttribInfoTags, Tags)) - bHasTags = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD, AttribInfoTags, Tags)) - bHasTags = true; - - // Extracting Sockets vertices - for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) - { - const FString & GroupName = GroupNames[GeoGroupNameIdx]; - if (!GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) - && !GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) - continue; - - bool AllEquals = false; - TArray< int32 > PointGroupMembership; - FHoudiniEngineUtils::HapiGetGroupMembership( - GeoId, PartInfo, HAPI_GROUPTYPE_POINT, GroupName, PointGroupMembership, AllEquals); - - // Go through all primitives. - for (int32 PointIdx = 0; PointIdx < PointGroupMembership.Num(); ++PointIdx) - { - if (PointGroupMembership[PointIdx] == 0) - { - if (AllEquals) - break; - else - continue; - } - - // Add the corresponding socket to the array - AddSocketToArray(PointIdx); - } - } - - return FoundSocketCount; -} - -bool -FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( - UStaticMesh* StaticMesh, - TArray& AllSockets, - const bool& CleanImportSockets) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Remove the sockets from the previous cook! - if (CleanImportSockets) - { - StaticMesh->Sockets.RemoveAll([=](UStaticMeshSocket* Socket) { return Socket ? Socket->bSocketCreatedAtImport : true; }); - } - - if (AllSockets.Num() <= 0) - return true; - - // Having sockets with empty names can lead to various issues, so we'll create one now - for (int32 Idx = 0; Idx < AllSockets.Num(); ++Idx) - { - // Assign the unnamed sockets with default names - if (AllSockets[Idx].Name.IsEmpty()) - AllSockets[Idx].Name = TEXT("Socket ") + FString::FromInt(Idx); - } - - // ensure the socket names are unique. (Unreal will use the first socket if multiple socket have the same name) - for (int32 Idx_i = 0; Idx_i < AllSockets.Num(); ++Idx_i) - { - int32 Count = 0; - for (int32 Idx_j = Idx_i + 1; Idx_j < AllSockets.Num(); ++Idx_j) - { - if (AllSockets[Idx_i].Name.Equals(AllSockets[Idx_j].Name)) - { - Count += 1; - AllSockets[Idx_j].Name = AllSockets[Idx_j].Name + "_" + FString::FromInt(Count); - } - } - } - - // Clear all the sockets of the output static mesh. - StaticMesh->Sockets.Empty(); - - for (int32 nSocket = 0; nSocket < AllSockets.Num(); nSocket++) - { - // Create a new Socket - UStaticMeshSocket* Socket = NewObject(StaticMesh); - if (!Socket || Socket->IsPendingKill()) - continue; - - Socket->RelativeLocation = AllSockets[nSocket].Transform.GetLocation(); - Socket->RelativeRotation = FRotator(AllSockets[nSocket].Transform.GetRotation()); - Socket->RelativeScale = AllSockets[nSocket].Transform.GetScale3D(); - Socket->SocketName = FName(*AllSockets[nSocket].Name); - - // Socket Tag - FString Tag; - if (!AllSockets[nSocket].Tag.IsEmpty()) - Tag = AllSockets[nSocket].Tag; - - // The actor will be stored temporarily in the socket's Tag as we need a StaticMeshComponent to add an actor to the socket - Tag += TEXT("|") + AllSockets[nSocket].Actor; - - Socket->Tag = Tag; - Socket->bSocketCreatedAtImport = true; - - StaticMesh->Sockets.Add(Socket); - } - - return true; -} - -bool -FHoudiniEngineUtils::CreateAttributesFromTags( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const TArray& Tags ) -{ - if (Tags.Num() <= 0) - return false; - - HAPI_Result Result = HAPI_RESULT_FAILURE; - - // Get the destination part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); - - bool NeedToCommitGeo = false; - for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) - { - FString TagString; - Tags[TagIdx].ToString(TagString); - SanitizeHAPIVariableName(TagString); - - // Create a primitive attribute for the tag - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - AttributeInfo.count = PartInfo.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; - - FString AttributeName = TEXT(HAPI_UNREAL_ATTRIB_TAG_PREFIX) + FString::FromInt(TagIdx); - AttributeName.RemoveSpacesInline(); - - Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo); - - if (Result != HAPI_RESULT_SUCCESS) - continue; - - TArray TagStr; - TagStr.Add(FHoudiniEngineUtils::ExtractRawString(TagString)); - - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo, - TagStr.GetData(), 0, AttributeInfo.count); - - if (HAPI_RESULT_SUCCESS == Result) - NeedToCommitGeo = true; - - // Free memory for allocated by ExtractRawString - FHoudiniEngineUtils::FreeRawStringMemory(TagStr); - } - - return NeedToCommitGeo; -} - -bool -FHoudiniEngineUtils::CreateGroupsFromTags( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const TArray& Tags ) -{ - if (Tags.Num() <= 0) - return true; - - HAPI_Result Result = HAPI_RESULT_FAILURE; - - // Get the destination part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); - - bool NeedToCommitGeo = false; - for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) - { - FString TagString; - Tags[TagIdx].ToString(TagString); - SanitizeHAPIVariableName(TagString); - - const char * TagStr = FHoudiniEngineUtils::ExtractRawString(TagString); - - // Create a primitive group for this tag - if ( HAPI_RESULT_SUCCESS == FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, TagStr) ) - { - // Set the group's Memberships - TArray GroupArray; - GroupArray.Init(1, PartInfo.faceCount); - - if ( HAPI_RESULT_SUCCESS == FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_GROUPTYPE_PRIM, TagStr, - GroupArray.GetData(), 0, PartInfo.faceCount) ) - { - NeedToCommitGeo = true; - } - } - - // Free memory allocated by ExtractRawString() - FHoudiniEngineUtils::FreeRawStringMemory(TagStr); - } - - return NeedToCommitGeo; -} - - -bool -FHoudiniEngineUtils::SanitizeHAPIVariableName(FString& String) -{ - // Only keep alphanumeric characters, underscores - // Also, if the first character is a digit, append an underscore at the beginning - TArray& StrArray = String.GetCharArray(); - if (StrArray.Num() <= 0) - return false; - - for (auto& CurChar : StrArray) - { - const bool bIsValid = (CurChar >= TEXT('A') && CurChar <= TEXT('Z')) - || (CurChar >= TEXT('a') && CurChar <= TEXT('z')) - || (CurChar >= TEXT('0') && CurChar <= TEXT('9')) - || (CurChar == TEXT('_')) || (CurChar == TEXT('\0')); - - if(bIsValid) - continue; - - CurChar = TEXT('_'); - } - - if (StrArray.Num() > 0) - { - TCHAR FirstChar = StrArray[0]; - if (FirstChar >= TEXT('0') && FirstChar <= TEXT('9')) - StrArray.Insert(TEXT('_'), 0); - } - - return true; -} - -bool -FHoudiniEngineUtils::GetUnrealTagAttributes( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags) -{ - FString TagAttribBase = TEXT("unreal_tag_"); - bool bAttributeFound = true; - int32 TagIdx = 0; - while (bAttributeFound) - { - FString CurrentTagAttr = TagAttribBase + FString::FromInt(TagIdx++); - bAttributeFound = HapiCheckAttributeExists(GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), HAPI_ATTROWNER_PRIM); - if (!bAttributeFound) - break; - - // found the unreal_tag_X attribute, get its value and add it to the array - FString TagValue = FString(); - - // Create an AttributeInfo - { - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) - { - TagValue = StringData[0]; - } - } - - FName NameTag = *TagValue; - OutTags.Add(NameTag); - } - - return true; -} - - -int32 -FHoudiniEngineUtils::GetPropertyAttributeList( - const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundPropertyAttributes) -{ - // Get all the detail uprop attributes on the HGPO - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive uprop attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_PRIM); - - return FoundCount; -} - - -int32 -FHoudiniEngineUtils::GetGenericAttributeList( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FString& InGenericAttributePrefix, - TArray& OutFoundAttributes, - const HAPI_AttributeOwner& AttributeOwner, - const int32& InAttribIndex) -{ - // Get the part info to get the attribute counts for the specified owner - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), InGeoNodeId, InPartId, &PartInfo), false); - - int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; - - // Get all attribute names for that part - TArray AttribNameSHArray; - AttribNameSHArray.SetNum(nAttribCount); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, AttributeOwner, - AttribNameSHArray.GetData(), nAttribCount)) - { - return 0; - } - - // For everything but detail attribute, - // if an attribute index was specified, only extract the attribute value for that specific index - // if not, extract all values for the given attribute - bool HandleSplit = false; - int32 AttribIndex = -1; - if ((AttributeOwner != HAPI_ATTROWNER_DETAIL) && (InAttribIndex != -1)) - { - // The index has already been specified so we'll use it - HandleSplit = true; - AttribIndex = InAttribIndex; - } - - int32 FoundCount = 0; - for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) - { - int32 AttribNameSH = (int32)AttribNameSHArray[Idx]; - FString AttribName = TEXT(""); - FHoudiniEngineString::ToFString(AttribNameSH, AttribName); - if (!AttribName.StartsWith(InGenericAttributePrefix, ESearchCase::IgnoreCase)) - continue; - - // Get the Attribute Info - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), AttributeOwner, &AttribInfo)) - { - // failed to get that attribute's info - continue; - } - - int32 AttribStart = 0; - int32 AttribCount = AttribInfo.count; - if (HandleSplit) - { - // For split primitives, we need to only get only one value for the proper split prim - // Make sure that the split index is valid - if (AttribIndex >= 0 && AttribIndex < AttribInfo.count) - { - AttribStart = AttribIndex; - AttribCount = 1; - } - } - - // - FHoudiniGenericAttribute CurrentGenericAttribute; - // Remove the generic attribute prefix - CurrentGenericAttribute.AttributeName = AttribName.Right(AttribName.Len() - InGenericAttributePrefix.Len()); - - CurrentGenericAttribute.AttributeOwner = (EAttribOwner)AttribInfo.owner; - - // Get the attribute type and tuple size - CurrentGenericAttribute.AttributeType = (EAttribStorageType)AttribInfo.storage; - CurrentGenericAttribute.AttributeCount = AttribInfo.count; - CurrentGenericAttribute.AttributeTupleSize = AttribInfo.tupleSize; - - if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT64) - { - // Initialize the value array - CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloat64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, 0, - CurrentGenericAttribute.DoubleValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT) - { - // Initialize the value array - TArray FloatValues; - FloatValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, FloatValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to double - CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - for (int32 n = 0; n < FloatValues.Num(); n++) - CurrentGenericAttribute.DoubleValues[n] = (double)FloatValues[n]; - - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT64) - { - // Initialize the value array - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, CurrentGenericAttribute.IntValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT) - { - // Initialize the value array - TArray IntValues; - IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, IntValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to int64 - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - for (int32 n = 0; n < IntValues.Num(); n++) - CurrentGenericAttribute.IntValues[n] = (int64)IntValues[n]; - - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - // Initialize a string handle array - TArray HapiSHArray; - HapiSHArray.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the string handle(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - HapiSHArray.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to FString - CurrentGenericAttribute.StringValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - for (int32 IdxSH = 0; IdxSH < HapiSHArray.Num(); IdxSH++) - { - FString CurrentString; - FHoudiniEngineString::ToFString(HapiSHArray[IdxSH], CurrentString); - CurrentGenericAttribute.StringValues[IdxSH] = CurrentString; - } - } - else - { - // Unsupported type, skipping! - continue; - } - - // We can add the UPropertyAttribute to the array - OutFoundAttributes.Add(CurrentGenericAttribute); - FoundCount++; - } - - return FoundCount; -} - - -void -FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject( - UObject* InObject, const FHoudiniGeoPartObject& InHGPO) -{ - if (!InObject || InObject->IsPendingKill()) - return; - - // Get the list of all the Properties to modify from the HGPO's attributes - TArray PropertiesAttributesToModify; - if (!FHoudiniEngineUtils::GetPropertyAttributeList(InHGPO, PropertiesAttributesToModify)) - return; - - // Iterate over the found Property attributes - for (auto CurrentPropAttribute : PropertiesAttributesToModify) - { - // Get the current Property Attribute - const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; - if (CurrentPropertyName.IsEmpty()) - continue; - - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropertyName, *ClassName, *ObjectName); - } -} - - -void -FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const FString& Key, const FString& Value) -{ - if (!Package || Package->IsPendingKill()) - return; - - UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) - MetaData->SetValue(Object, *Key, *Value); -} - - -bool -FHoudiniEngineUtils::AddLevelPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - ULevel* InLevel, - const int32& InCount) -{ - if (InNodeId < 0 || InCount <= 0) - return false; - - if (!InLevel || InLevel->IsPendingKill()) - return false; - - // Extract the level path from the level - FString LevelPath = InLevel->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = InCount; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(InCount); - for (int32 Idx = 0; Idx < InCount; ++Idx) - { - PrimitiveAttrs[Idx] = LevelPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} - - -bool -FHoudiniEngineUtils::AddActorPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - AActor* InActor, - const int32& InCount) -{ - if (InNodeId < 0 || InCount <= 0) - return false; - - if (!InActor || InActor->IsPendingKill()) - return false; - - // Extract the actor path - FString ActorPath = InActor->GetPathName(); - - // Get name of attribute used for Actor path - std::string MarshallingAttributeActorPath = HAPI_UNREAL_ATTRIB_ACTOR_PATH; - - // Marshall in Actor path. - HAPI_AttributeInfo AttributeInfoActorPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoActorPath); - AttributeInfoActorPath.count = InCount; - AttributeInfoActorPath.tupleSize = 1; - AttributeInfoActorPath.exists = true; - AttributeInfoActorPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoActorPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoActorPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string ActorPathCStr = TCHAR_TO_ANSI(*ActorPath); - const char* ActorPathCStrRaw = ActorPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(InCount); - for (int32 Idx = 0; Idx < InCount; ++Idx) - { - PrimitiveAttrs[Idx] = ActorPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, - MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoActorPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_actor_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} - - -bool -FHoudiniEngineUtils::ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx) -{ - const TArray< FVector2D > & LightmapUVs = RawMesh.WedgeTexCoords[LightmapSourceIdx]; - const TArray< uint32 > & Indices = RawMesh.WedgeIndices; - - if (LightmapUVs.Num() != Indices.Num()) - { - // This is invalid raw mesh; by design we consider that it contains invalid lightmap faces. - return true; - } - - for (int32 Idx = 0; Idx < Indices.Num(); Idx += 3) - { - const FVector2D & uv0 = LightmapUVs[Idx + 0]; - const FVector2D & uv1 = LightmapUVs[Idx + 1]; - const FVector2D & uv2 = LightmapUVs[Idx + 2]; - - if (uv0 == uv1 && uv1 == uv2) - { - // Detect invalid lightmap face, can stop. - return true; - } - } - - // Otherwise there are no invalid lightmap faces. - return false; -} - -void -FHoudiniEngineUtils::CreateSlateNotification( - const FString& NotificationString, const float& NotificationExpire, const float& NotificationFadeOut ) -{ -#if WITH_EDITOR - // Trying to display SlateNotifications while in a background thread will crash UE - if (!IsInGameThread() && !IsInSlateThread() && !IsInAsyncLoadingThread()) - return; - - // Check whether we want to display Slate notifications. - bool bDisplaySlateCookingNotifications = true; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (!bDisplaySlateCookingNotifications) - return; - - FText NotificationText = FText::FromString(NotificationString); - FNotificationInfo Info(NotificationText); - - Info.bFireAndForget = true; - Info.FadeOutDuration = NotificationFadeOut; - Info.ExpireDuration = NotificationExpire; - - TSharedPtr HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); - if (HoudiniBrush.IsValid()) - Info.Image = HoudiniBrush.Get(); - - FSlateNotificationManager::Get().AddNotification(Info); -#endif - - return; -} - -FString -FHoudiniEngineUtils::GetHoudiniEnginePluginDir() -{ - FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(EnginePluginDir)) - return EnginePluginDir; - - FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(ProjectPluginDir)) - return ProjectPluginDir; - - TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); - FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; - if (FPaths::DirectoryExists(PluginBaseDir)) - return PluginBaseDir; - - HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); - - return EnginePluginDir; -} - - -HAPI_Result -FHoudiniEngineUtils::CreateNode( - const HAPI_NodeId& InParentNodeId, - const FString& InOperatorName, - const FString& InNodeLabel, - const HAPI_Bool& bInCookOnCreation, - HAPI_NodeId* OutNewNodeId) -{ - // Call HAPI::CreateNode - HAPI_Result Result = FHoudiniApi::CreateNode( - FHoudiniEngine::Get().GetSession(), - InParentNodeId, TCHAR_TO_UTF8(*InOperatorName), TCHAR_TO_UTF8(*InNodeLabel), bInCookOnCreation, OutNewNodeId); - - // Return now if CreateNode fialed - if (Result != HAPI_RESULT_SUCCESS) - return Result; - - // Loop on the cook_state status until it's ready - int CurrentStatus = HAPI_State::HAPI_STATE_STARTING_LOAD; - while (CurrentStatus > HAPI_State::HAPI_STATE_MAX_READY_STATE) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), - HAPI_StatusType::HAPI_STATUS_COOK_STATE, &CurrentStatus)) - { - // Exit the loop if GetStatus somehow fails - break; - } - } - - if (CurrentStatus == HAPI_STATE_READY_WITH_FATAL_ERRORS) - { - // Fatal errors - failed - HOUDINI_LOG_ERROR(TEXT("Failed to create node %s - %s"), *InOperatorName, *InNodeLabel); - return HAPI_RESULT_FAILURE; - } - else if (CurrentStatus == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // Mention the errors - still return success - HOUDINI_LOG_WARNING(TEXT("Cook errors when creating node %s - %s"), *InOperatorName, *InNodeLabel); - } - - return HAPI_RESULT_SUCCESS; -} - - -int32 -FHoudiniEngineUtils::HapiGetCookCount(const HAPI_NodeId& InNodeId) -{ - int32 CookCount = -1; - - FHoudiniApi::GetTotalCookCount( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_ANY, true, &CookCount); - - /* - // TODO: - // Use HAPI_GetCookingTotalCount() when available - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - - int32 CookCount = -1; - HAPI_Result Result = FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), InNodeId, &NodeInfo); - - if (Result != HAPI_RESULT_FAILURE) - { - if (NodeInfo.type != HAPI_NODETYPE_OBJ) - { - // For SOP assets, get the cook count straight from the Asset Node - CookCount = NodeInfo.totalCookCount; - } - else - { - // For OBJ nodes, get the cook count from the display geos - // Retrieve information about each object contained within our asset. - TArray< HAPI_ObjectInfo > ObjectInfos; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(InNodeId, ObjectInfos)) - return false; - - for (auto CurrentHapiObjectInfo : ObjectInfos) - { - // Get the Display Geo's info - HAPI_GeoInfo DisplayHapiGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) - { - continue; - } - - HAPI_NodeInfo DisplayNodeInfo; - FHoudiniApi::NodeInfo_Init(&DisplayNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), DisplayHapiGeoInfo.nodeId, &DisplayNodeInfo)) - { - continue; - } - - CookCount += DisplayNodeInfo.totalCookCount; - } - } - } - */ - - return CookCount; -} - -bool -FHoudiniEngineUtils::GetLevelPathAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutLevelPaths, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_level_path - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_LEVEL_PATH, AttributeInfo, OutLevelPaths, 1, InAttributeOwner)) - { - if (OutLevelPaths.Num() > 0) - return true; - } - - OutLevelPaths.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetOutputNameAttribute(const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutOutputNames) -{ - // --------------------------------------------- - // Attribute: unreal_output_name - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, AttributeInfo, OutOutputNames, 1)) - { - if (OutOutputNames.Num() > 0) - return true; - } - - OutOutputNames.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, AttributeInfo, OutOutputNames, 1)) - { - if (OutOutputNames.Num() > 0) - return true; - } - - OutOutputNames.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetTileAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutTileValues, - const HAPI_AttributeOwner& InAttribOwner) -{ - // --------------------------------------------- - // Attribute: tile - // --------------------------------------------- - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, - AttribInfoTile, - OutTileValues, - 0, - InAttribOwner)) - { - if (OutTileValues.Num() > 0) - return true; - } - - OutTileValues.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - TArray& OutBakeFolder, - HAPI_PartId InPartId) -{ - OutBakeFolder.Empty(); - - HAPI_AttributeInfo BakeFolderAttribInfo; - FHoudiniApi::AttributeInfo_Init(&BakeFolderAttribInfo); - if (HapiGetAttributeDataAsString( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_DETAIL)) - { - if (OutBakeFolder.Num() > 0) - return true; - } - - if (HapiGetAttributeDataAsString( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_PRIM)) - { - if (OutBakeFolder.Num() > 0) - return true; - } - - OutBakeFolder.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeActorAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeActorNames, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_bake_actor - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_BAKE_ACTOR, AttributeInfo, OutBakeActorNames, 1, InAttributeOwner)) - { - if (OutBakeActorNames.Num() > 0) - return true; - } - - OutBakeActorNames.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeOutlinerFolders, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_bake_outliner_folder - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, AttributeInfo, OutBakeOutlinerFolders, 1, InAttributeOwner)) - { - if (OutBakeOutlinerFolders.Num() > 0) - return true; - } - - OutBakeOutlinerFolders.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeFolderOverridePath( - const HAPI_NodeId& InGeoId, - FString& OutBakeFolder, - HAPI_PartId InPartId) -{ - FString BakeFolderOverride; - - TArray StringData; - if (GetBakeFolderAttribute(InGeoId, StringData, InPartId)) - { - BakeFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - - if (BakeFolderOverride.StartsWith("Game/")) - { - BakeFolderOverride = "/" + BakeFolderOverride; - } - - FString AbsoluteOverridePath; - if (BakeFolderOverride.StartsWith("/Game/")) - { - const FString RelativePath = FPaths::ProjectContentDir() + BakeFolderOverride.Mid(6, BakeFolderOverride.Len() - 6); - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - if (!BakeFolderOverride.IsEmpty()) - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolderOverride); - } - - // Check Validity of the path - if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) - { - // Only display a warning if the path is invalid, empty is fine - if (!AbsoluteOverridePath.IsEmpty()) - HOUDINI_LOG_WARNING(TEXT("Invalid override bake path: %s"), *BakeFolderOverride); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - OutBakeFolder = HoudiniRuntimeSettings->DefaultBakeFolder; - - return false; - } - - OutBakeFolder = BakeFolderOverride; - return true; -} - -bool -FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) -{ - if (!InActor || !InDesiredLevel) - return false; - - ULevel* PreviousLevel = InActor->GetLevel(); - if (PreviousLevel == InDesiredLevel) - return true; - - UWorld* CurrentWorld = InActor->GetWorld(); - if(CurrentWorld) - CurrentWorld->RemoveActor(InActor, true); - - //Set the outer of Actor to NewLevel - InActor->Rename((const TCHAR *)0, InDesiredLevel); - InDesiredLevel->Actors.Add(InActor); - - return true; -} - -bool -FHoudiniEngineUtils::HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions, const bool& bWaitForCompletion) -{ - // Check for an invalid node id - if (InNodeId < 0) - return false; - - // No Cook Options were specified, use the default one - if (InCookOptions == nullptr) - { - // Use the default cook options - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); - } - else - { - // Use the provided CookOptions - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, InCookOptions), false); - } - - // If we don't need to wait for completion, return now - if (!bWaitForCompletion) - return true; - - // Wait for the cook to finish - HAPI_Result Result = HAPI_RESULT_SUCCESS; - while (true) - { - // Get the current cook status - int Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // The cook has been successful. - return true; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // There was an error while cooking the node. - //FString CookResultString = FHoudiniEngineUtils::GetCookResult(); - //HOUDINI_LOG_ERROR(); - return false; - } - - // We want to yield a bit. - FPlatformProcess::Sleep(0.1f); - } -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineUtils.h" +#include "Misc/StringFormatArg.h" + +#if PLATFORM_WINDOWS + #include "Windows/WindowsHWrapper.h" + + // Of course, Windows defines its own GetGeoInfo, + // So we need to undefine that before including HoudiniApi.h to avoid collision... + #ifdef GetGeoInfo + #undef GetGeoInfo + #endif +#endif + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniEngineString.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInput.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineRuntimeUtils.h" + +#if WITH_EDITOR + #include "SAssetSelectionWidget.h" +#endif + +#include "HAPI/HAPI_Version.h" + +#include "Misc/Paths.h" +#include "Editor/EditorEngine.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "PropertyEditorModule.h" +#include "Modules/ModuleManager.h" +#include "Engine/StaticMeshSocket.h" +#include "Async/Async.h" +#include "BlueprintEditor.h" +#include "Toolkits/AssetEditorManager.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "UObject/MetaData.h" +#include "RawMesh.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Interfaces/IPluginManager.h" +//#include "Kismet/BlueprintEditor.h" +#include "SSCSEditor.h" +#include "Engine/WorldComposition.h" + +#if WITH_EDITOR + #include "Interfaces/IMainFrameModule.h" +#endif + +#include + +#include "AssetRegistryModule.h" +#include "FileHelpers.h" +#include "Factories/WorldFactory.h" +#include "HAL/FileManager.h" + +#if WITH_EDITOR + #include "EditorModeManager.h" + #include "EditorModes.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// HAPI_Result strings +const FString kResultStringSuccess(TEXT("Success")); +const FString kResultStringFailure(TEXT("Generic Failure")); +const FString kResultStringAlreadyInitialized(TEXT("Already Initialized")); +const FString kResultStringNotInitialized(TEXT("Not Initialized")); +const FString kResultStringCannotLoadFile(TEXT("Unable to Load File")); +const FString kResultStringParmSetFailed(TEXT("Failed Setting Parameter")); +const FString kResultStringInvalidArgument(TEXT("Invalid Argument")); +const FString kResultStringCannotLoadGeo(TEXT("Uneable to Load Geometry")); +const FString kResultStringCannotGeneratePreset(TEXT("Uneable to Generate Preset")); +const FString kResultStringCannotLoadPreset(TEXT("Uneable to Load Preset")); +const FString kResultStringAssetDefAlrealdyLoaded(TEXT("Asset definition already loaded")); +const FString kResultStringNoLicenseFound(TEXT("No License Found")); +const FString kResultStringDisallowedNCLicenseFound(TEXT("Disallowed Non Commercial License found")); +const FString kResultStringDisallowedNCAssetWithCLicense(TEXT("Disallowed Non Commercial Asset With Commercial License")); +const FString kResultStringDisallowedNCAssetWithLCLicense(TEXT("Disallowed Non Commercial Asset With Limited Commercial License")); +const FString kResultStringDisallowedLCAssetWithCLicense(TEXT("Disallowed Limited Commercial Asset With Commercial License")); +const FString kResultStringDisallowedHengineIndieWith3PartyPlugin(TEXT("Disallowed Houdini Engine Indie With 3rd Party Plugin")); +const FString kResultStringAssetInvalid(TEXT("Invalid Asset")); +const FString kResultStringNodeInvalid(TEXT("Invalid Node")); +const FString kResultStringUserInterrupted(TEXT("User Interrupt")); +const FString kResultStringInvalidSession(TEXT("Invalid Session")); +const FString kResultStringUnknowFailure(TEXT("Unknown Failure")); + +#define DebugTextLine TEXT("===================================") + +const int32 +FHoudiniEngineUtils::PackageGUIDComponentNameLength = 12; + +const int32 +FHoudiniEngineUtils::PackageGUIDItemNameLength = 8; + +const FString +FHoudiniEngineUtils::GetErrorDescription(HAPI_Result Result) +{ + if (Result == HAPI_RESULT_SUCCESS) + { + return kResultStringSuccess; + } + else + { + switch (Result) + { + case HAPI_RESULT_FAILURE: + { + return kResultStringFailure; + } + + case HAPI_RESULT_ALREADY_INITIALIZED: + { + return kResultStringAlreadyInitialized; + } + + case HAPI_RESULT_NOT_INITIALIZED: + { + return kResultStringNotInitialized; + } + + case HAPI_RESULT_CANT_LOADFILE: + { + return kResultStringCannotLoadFile; + } + + case HAPI_RESULT_PARM_SET_FAILED: + { + return kResultStringParmSetFailed; + } + + case HAPI_RESULT_INVALID_ARGUMENT: + { + return kResultStringInvalidArgument; + } + + case HAPI_RESULT_CANT_LOAD_GEO: + { + return kResultStringCannotLoadGeo; + } + + case HAPI_RESULT_CANT_GENERATE_PRESET: + { + return kResultStringCannotGeneratePreset; + } + + case HAPI_RESULT_CANT_LOAD_PRESET: + { + return kResultStringCannotLoadPreset; + } + + case HAPI_RESULT_ASSET_DEF_ALREADY_LOADED: + { + return kResultStringAssetDefAlrealdyLoaded; + } + + case HAPI_RESULT_NO_LICENSE_FOUND: + { + return kResultStringNoLicenseFound; + } + + case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: + { + return kResultStringDisallowedNCLicenseFound; + } + + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: + { + return kResultStringDisallowedNCAssetWithCLicense; + } + + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: + { + return kResultStringDisallowedNCAssetWithLCLicense; + } + + case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: + { + return kResultStringDisallowedLCAssetWithCLicense; + } + + case HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN: + { + return kResultStringDisallowedHengineIndieWith3PartyPlugin; + } + + case HAPI_RESULT_ASSET_INVALID: + { + return kResultStringAssetInvalid; + } + + case HAPI_RESULT_NODE_INVALID: + { + return kResultStringNodeInvalid; + } + + case HAPI_RESULT_USER_INTERRUPTED: + { + return kResultStringUserInterrupted; + } + + case HAPI_RESULT_INVALID_SESSION: + { + return kResultStringInvalidSession; + } + + default: + { + return kResultStringUnknowFailure; + } + }; + } +} + +const FString +FHoudiniEngineUtils::GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity) +{ + const HAPI_Session* SessionPtr = FHoudiniEngine::Get().GetSession(); + if (!SessionPtr) + { + // No valid session + return FString(TEXT("No valid Houdini Engine session.")); + } + + int32 StatusBufferLength = 0; + HAPI_Result Result = FHoudiniApi::GetStatusStringBufLength( + SessionPtr, status_type, verbosity, &StatusBufferLength); + + if (Result == HAPI_RESULT_INVALID_SESSION) + { + // Let FHoudiniEngine know that the sesion is now invalid to "Stop" the invalid session + // and clean things up + FHoudiniEngine::Get().OnSessionLost(); + } + + if (StatusBufferLength > 0) + { + TArray< char > StatusStringBuffer; + StatusStringBuffer.SetNumZeroed(StatusBufferLength); + FHoudiniApi::GetStatusString( + SessionPtr, status_type, &StatusStringBuffer[0], StatusBufferLength); + + return FString(UTF8_TO_TCHAR(&StatusStringBuffer[0])); + } + + return FString(TEXT("")); +} + +const FString +FHoudiniEngineUtils::GetCookResult() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_MESSAGES); +} + +const FString +FHoudiniEngineUtils::GetCookState() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); +} + +const FString +FHoudiniEngineUtils::GetErrorDescription() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS); +} + +const FString +FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId) +{ + int32 NodeErrorLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeNodeCookResult( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_StatusVerbosity::HAPI_STATUSVERBOSITY_ALL, &NodeErrorLength)) + { + NodeErrorLength = 0; + } + + FString NodeError; + if (NodeErrorLength > 0) + { + TArray NodeErrorBuffer; + NodeErrorBuffer.SetNumZeroed(NodeErrorLength); + FHoudiniApi::GetComposedNodeCookResult( + FHoudiniEngine::Get().GetSession(), &NodeErrorBuffer[0], NodeErrorLength); + + NodeError = FString(UTF8_TO_TCHAR(&NodeErrorBuffer[0])); + } + + return NodeError; +} + +const FString +FHoudiniEngineUtils::GetCookLog(TArray& InHACs) +{ + FString CookLog; + + // Get fetch cook status. + FString CookResult = FHoudiniEngineUtils::GetCookResult(); + if (!CookResult.IsEmpty()) + CookLog += TEXT("Cook Results:\n") + CookResult + TEXT("\n\n"); + + // Add the cook state + FString CookState = FHoudiniEngineUtils::GetCookState(); + if (!CookState.IsEmpty()) + CookLog += TEXT("Cook State:\n") + CookState + TEXT("\n\n"); + + // Error Description + FString Error = FHoudiniEngineUtils::GetErrorDescription(); + if (!Error.IsEmpty()) + CookLog += TEXT("Error Description:\n") + Error + TEXT("\n\n"); + + // Iterates on all the selected HAC and get their node errors + for (auto& HAC : InHACs) + { + if (!HAC || HAC->IsPendingKill()) + continue; + + // Get the node errors, warnings and messages + FString NodeErrors = FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(HAC->GetAssetId()); + if (NodeErrors.IsEmpty()) + continue; + + CookLog += NodeErrors; + } + + if (CookLog.IsEmpty()) + { + // See if a failed HAPI initialization / invalid session is preventing us from getting the cook log + if (!FHoudiniApi::IsHAPIInitialized()) + { + CookLog += TEXT("\n\nThe Houdini Engine API Library (HAPI) has not been initialized properly.\n\n"); + } + else + { + const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) + { + CookLog += TEXT("\n\nThe current Houdini Engine Session is not valid.\n\n"); + } + else if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) + { + CookLog += TEXT("\n\nThe current Houdini Engine Session has not been initialized properly.\n\n"); + } + } + + if (!CookLog.IsEmpty()) + { + CookLog += TEXT("Please try to restart the current Houdini Engine session via File > Restart Houdini Engine Session.\n\n"); + } + else + { + CookLog = TEXT("\n\nThe cook log is empty...\n\n"); + } + } + + return CookLog; +} + +const FString +FHoudiniEngineUtils::GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + FString HelpString = TEXT(""); + if (!HoudiniAssetComponent) + return HelpString; + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HAPI_NodeId AssetId = HoudiniAssetComponent->GetAssetId(); + if (AssetId < 0) + return HelpString; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), HelpString); + + if (!FHoudiniEngineString::ToFString(AssetInfo.helpTextSH, HelpString)) + return HelpString; + + if (HelpString.IsEmpty()) + HelpString = TEXT("No Asset Help Found"); + + return HelpString; +} + +void +FHoudiniEngineUtils::ConvertUnrealString(const FString & UnrealString, std::string & String) +{ + String = TCHAR_TO_UTF8(*UnrealString); +} + +UWorld* +FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreateMissingPackage, bool& bOutCreatedPackage) +{ + AActor* Result = nullptr; + UWorld* PackageWorld = nullptr; + + bOutCreatedPackage = false; + + // Try to load existing UWorld from the tile package path. + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!Package) + Package = LoadPackage(nullptr, *PackagePath, LOAD_None); + if (Package) + { + // If the package is not valid (pending kill) rename it + if (Package->IsPendingKill()) + { + if (bCreateMissingPackage) + { + Package->Rename( + *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); + } + } + else + { + PackageWorld = UWorld::FindWorldInPackage(Package); + } + } + + if (!IsValid(PackageWorld) && bCreateMissingPackage) + { + // The map for this tile does not exist. Create one + UWorldFactory* Factory = NewObject(); + Factory->WorldType = EWorldType::Inactive; // World that is being loaded but not currently edited by editor. + PackageWorld = CastChecked(Factory->FactoryCreateNew(UWorld::StaticClass(), Package, NAME_None, RF_Public | RF_Standalone, NULL, GWarn)); + + if (IsValid(PackageWorld)) + { + PackageWorld->PostEditChange(); + PackageWorld->MarkPackageDirty(); + + if(FPackageName::IsValidLongPackageName(PackagePath)) + { + const FString PackageFilename = FPackageName::LongPackageNameToFilename(PackagePath); + bool bSaved = FEditorFileUtils::SaveLevel(PackageWorld->PersistentLevel, *PackageFilename); + } + + FAssetRegistryModule::AssetCreated(PackageWorld); + + bOutCreatedPackage = true; + } + } + + return PackageWorld; +} + +bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + UWorld* CurrentWorld, + const FString& PackagePath, + bool bCreateMissingPackage, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bOutPackageCreated, + bool& bPackageInWorld) +{ + UWorld* PackageWorld = FindWorldInPackage(PackagePath, bCreateMissingPackage, bOutPackageCreated); + if (!IsValid(PackageWorld)) + return false; + + if (PackageWorld->PersistentLevel == CurrentWorld->PersistentLevel) + { + // The loaded world and the package world is one and the same. + OutWorld = CurrentWorld; + OutLevel = CurrentWorld->PersistentLevel; + bPackageInWorld = true; + return true; + } + + if (CurrentWorld->GetLevels().Contains(PackageWorld->PersistentLevel)) + { + // The package level is loaded into CurrentWorld. + OutWorld = CurrentWorld; + OutLevel = PackageWorld->PersistentLevel; + bPackageInWorld = true; + return true; + } + + // The package level is not loaded at all. Send back the on-disk assets. + OutWorld = PackageWorld; + OutLevel = PackageWorld->PersistentLevel; + bPackageInWorld = false; + return true; +} + +void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) +{ + FString WorldPath = FPaths::GetPath(InWorld->GetPathName()); + IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); + TArray Packages; + Packages.Add(WorldPath); + AssetRegistry.ScanPathsSynchronous(Packages, true); +} + +AActor* FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) +{ + // AActor* NamedActor = FindObject(Outer, *InName, false); + // Find ANY actor in the world matching the given name. + AActor* NamedActor = FindActorInWorld(InWorld, FName(InName)); + OutFoundActor = NamedActor; + bool bShouldRename = false; + if (NamedActor) + { + if (NamedActor->GetClass()->IsChildOf(InClass) && !NamedActor->IsPendingKill()) + { + return NamedActor; + } + else + { + FString Suffix; + bool bShouldUpdateLabel = false; + if (NamedActor->IsPendingKill()) + Suffix = "_pendingkill"; + else + Suffix = "_0"; // A previous actor that had the same name. + const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName+Suffix); + } + } + return nullptr; +} + +void FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) +{ + LogPackageInfo( LoadPackage(nullptr, *InLongPackageName, 0) ); +} + +void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) +{ + HOUDINI_LOG_MESSAGE(DebugTextLine); + HOUDINI_LOG_MESSAGE(TEXT("= LogPackageInfo")); + if (!IsValid(InPackage)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Invalid package.")); + HOUDINI_LOG_MESSAGE(DebugTextLine); + return; + } + + HOUDINI_LOG_MESSAGE(TEXT(" = Filename: %s"), *(InPackage->FileName.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = Package Id: %d"), InPackage->GetPackageId().ValueForDebugging()); + HOUDINI_LOG_MESSAGE(TEXT(" = File size: %d"), InPackage->FileSize); + HOUDINI_LOG_MESSAGE(TEXT(" = Contains map: %d"), InPackage->ContainsMap()); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Fully Loaded: %d"), InPackage->IsFullyLoaded()); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Dirty: %d"), InPackage->IsDirty()); + + if (InPackage->WorldTileInfo.IsValid()) + { + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Position: %s"), *(InPackage->WorldTileInfo->Position.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Absolute Position: %s"), *(InPackage->WorldTileInfo->AbsolutePosition.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Bounds: %s"), *(InPackage->WorldTileInfo->Bounds.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - HidInTileView: %d"), InPackage->WorldTileInfo->bHideInTileView); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - ZOrder: %d"), InPackage->WorldTileInfo->ZOrder); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Parent tile package: %s"), *(InPackage->WorldTileInfo->ParentTilePackageName)); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo: NULL")); + } + + HOUDINI_LOG_MESSAGE(DebugTextLine); +} + +void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) +{ + UPackage* Package = LoadPackage(nullptr, *InLongPackageName, 0); + UWorld* World = nullptr; + + if (IsValid(Package)) + { + World = UWorld::FindWorldInPackage(Package); + } + + LogWorldInfo(World); +} + +void FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) +{ + + HOUDINI_LOG_MESSAGE(DebugTextLine); + HOUDINI_LOG_MESSAGE(TEXT("= LogWorldInfo")); + if (!IsValid(InWorld)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Invalid world.")); + HOUDINI_LOG_MESSAGE(DebugTextLine); + return; + } + + // UWorld lacks const-correctness on certain accessors + UWorld* NonConstWorld = const_cast(InWorld); + + HOUDINI_LOG_MESSAGE(TEXT(" = Path Name: %s"), *(InWorld->GetPathName())); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Editor World: %d"), InWorld->IsEditorWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Game World: %d"), InWorld->IsGameWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Preview World: %d"), InWorld->IsPreviewWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Actor Count: %d"), NonConstWorld->GetActorCount() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Num Levels: %d"), InWorld->GetNumLevels() ); + + if (IsValid(InWorld->WorldComposition)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Composition - Num Tiles: %d"), InWorld->WorldComposition->GetTilesList().Num() ); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT(" = World Composition NULL") ); + } + + + + HOUDINI_LOG_MESSAGE(DebugTextLine); +} + +FString +FHoudiniEngineUtils::HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType) +{ + switch (InEventType) + { + case HAPI_PDG_EVENT_NULL: + return TEXT("HAPI_PDG_EVENT_NULL"); + + case HAPI_PDG_EVENT_WORKITEM_ADD: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE"); + case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_DEP"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_PARENT"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT"); + + case HAPI_PDG_EVENT_NODE_CLEAR: + return TEXT("HAPI_PDG_EVENT_NODE_CLEAR"); + + case HAPI_PDG_EVENT_COOK_ERROR: + return TEXT("HAPI_PDG_EVENT_COOK_ERROR"); + case HAPI_PDG_EVENT_COOK_WARNING: + return TEXT("HAPI_PDG_EVENT_COOK_WARNING"); + + case HAPI_PDG_EVENT_COOK_COMPLETE: + return TEXT("HAPI_PDG_EVENT_COOK_COMPLETE"); + + case HAPI_PDG_EVENT_DIRTY_START: + return TEXT("HAPI_PDG_EVENT_DIRTY_START"); + case HAPI_PDG_EVENT_DIRTY_STOP: + return TEXT("HAPI_PDG_EVENT_DIRTY_STOP"); + + case HAPI_PDG_EVENT_DIRTY_ALL: + return TEXT("HAPI_PDG_EVENT_DIRTY_ALL"); + + case HAPI_PDG_EVENT_UI_SELECT: + return TEXT("HAPI_PDG_EVENT_UI_SELECT"); + + case HAPI_PDG_EVENT_NODE_CREATE: + return TEXT("HAPI_PDG_EVENT_NODE_CREATE"); + case HAPI_PDG_EVENT_NODE_REMOVE: + return TEXT("HAPI_PDG_EVENT_NODE_REMOVE"); + case HAPI_PDG_EVENT_NODE_RENAME: + return TEXT("HAPI_PDG_EVENT_NODE_RENAME"); + case HAPI_PDG_EVENT_NODE_CONNECT: + return TEXT("HAPI_PDG_EVENT_NODE_CONNECT"); + case HAPI_PDG_EVENT_NODE_DISCONNECT: + return TEXT("HAPI_PDG_EVENT_NODE_DISCONNECT"); + + case HAPI_PDG_EVENT_WORKITEM_SET_INT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_INT"); + case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FLOAT"); + case HAPI_PDG_EVENT_WORKITEM_SET_STRING: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_STRING"); + case HAPI_PDG_EVENT_WORKITEM_SET_FILE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FILE"); + case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT"); + case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY"); + case HAPI_PDG_EVENT_WORKITEM_MERGE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_MERGE"); + case HAPI_PDG_EVENT_WORKITEM_RESULT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_RESULT"); + + case HAPI_PDG_EVENT_WORKITEM_PRIORITY: + return TEXT("HAPI_PDG_EVENT_WORKITEM_PRIORITY"); + + case HAPI_PDG_EVENT_COOK_START: + return TEXT("HAPI_PDG_EVENT_COOK_START"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR"); + + case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: + return TEXT("HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE"); + + case HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED: + return TEXT("HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED"); + + case HAPI_PDG_EVENT_ALL: + return TEXT("HAPI_PDG_EVENT_ALL"); + case HAPI_PDG_EVENT_LOG: + return TEXT("HAPI_PDG_EVENT_LOG"); + + case HAPI_PDG_EVENT_SCHEDULER_ADDED: + return TEXT("HAPI_PDG_EVENT_SCHEDULER_ADDED"); + case HAPI_PDG_EVENT_SCHEDULER_REMOVED: + return TEXT("HAPI_PDG_EVENT_SCHEDULER_REMOVED"); + case HAPI_PDG_EVENT_SET_SCHEDULER: + return TEXT("HAPI_PDG_EVENT_SET_SCHEDULER"); + + case HAPI_PDG_EVENT_SERVICE_MANAGER_ALL: + return TEXT("HAPI_PDG_EVENT_SERVICE_MANAGER_ALL"); + + case HAPI_PDG_CONTEXT_EVENTS: + return TEXT("HAPI_PDG_CONTEXT_EVENTS"); + default: + break; + } + + return FString::Printf(TEXT("Unknown HAPI_PDG_EventType %d"), InEventType); +} + +FString +FHoudiniEngineUtils::HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState) +{ + switch (InWorkitemState) + { + case HAPI_PDG_WORKITEM_UNDEFINED: + return TEXT("HAPI_PDG_WORKITEM_UNDEFINED"); + case HAPI_PDG_WORKITEM_UNCOOKED: + return TEXT("HAPI_PDG_WORKITEM_UNCOOKED"); + case HAPI_PDG_WORKITEM_WAITING: + return TEXT("HAPI_PDG_WORKITEM_WAITING"); + case HAPI_PDG_WORKITEM_SCHEDULED: + return TEXT("HAPI_PDG_WORKITEM_SCHEDULED"); + case HAPI_PDG_WORKITEM_COOKING: + return TEXT("HAPI_PDG_WORKITEM_COOKING"); + case HAPI_PDG_WORKITEM_COOKED_SUCCESS: + return TEXT("HAPI_PDG_WORKITEM_COOKED_SUCCESS"); + case HAPI_PDG_WORKITEM_COOKED_CACHE: + return TEXT("HAPI_PDG_WORKITEM_COOKED_CACHE"); + case HAPI_PDG_WORKITEM_COOKED_FAIL: + return TEXT("HAPI_PDG_WORKITEM_COOKED_FAIL"); + case HAPI_PDG_WORKITEM_COOKED_CANCEL: + return TEXT("HAPI_PDG_WORKITEM_COOKED_CANCEL"); + case HAPI_PDG_WORKITEM_DIRTY: + return TEXT("HAPI_PDG_WORKITEM_DIRTY"); + default: + break; + } + + return FString::Printf(TEXT("Unknown HAPI_PDG_WorkitemState %d"), InWorkitemState); +} + +FName +FHoudiniEngineUtils::RenameToUniqueActor(AActor* InActor, const FString& InName) +{ + const FName NewName = MakeUniqueObjectName(InActor->GetOuter(), InActor->GetClass(), FName(InName)); + InActor->Rename( *(NewName.ToString()) ); + // TODO: Can we set actor label when actor is pending kill? + InActor->SetActorLabel(NewName.ToString()); + return NewName; +} + +UObject* FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) +{ + check(InActor); + + UObject* PrevObj = nullptr; + UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, InActor->GetOuter(), *InName, true); + if (ExistingObject && ExistingObject != InActor) + { + // Rename the existing object + const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName+TEXT("_old")) ); + ExistingObject->Rename(*(NewName.ToString())); + PrevObj = ExistingObject; + } + InActor->Rename(*InName); + if (UpdateLabel) + InActor->SetActorLabel(InName, true); + return PrevObj; +} + +void +FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + FHoudiniPackageParams& OutPackageParams, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FString &BakeFolder, + const FString &ObjectName, + const FString &HoudiniAssetName, + EPackageReplaceMode InReplaceMode) +{ + OutPackageParams.GeoId = InIdentifier.GeoId; + OutPackageParams.ObjectId = InIdentifier.ObjectId; + OutPackageParams.PartId = InIdentifier.PartId; + OutPackageParams.BakeFolder = BakeFolder; + OutPackageParams.PackageMode = EPackageMode::Bake; + OutPackageParams.ReplaceMode = InReplaceMode; + OutPackageParams.HoudiniAssetName = HoudiniAssetName; + OutPackageParams.HoudiniAssetActorName = HoudiniAssetName; + OutPackageParams.ObjectName = ObjectName; +} + +bool +FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() +{ + // Update / repopulate the foliage editor mode's mesh list if the foliage editor mode is active. + // TODO: find a better way to do this, the relevant functions are in FEdModeFoliage and FFoliageEdModeToolkit are not API exported + FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); + if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) + { + EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); + EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::IsOuterHoudiniAssetComponent(UObject* Obj) +{ + if (!Obj) + return false; + return Obj->GetOuter() && Obj->GetOuter()->IsA(); +} + +UHoudiniAssetComponent* +FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(UObject* Obj) +{ + return Cast(Obj->GetOuter()); +} + +FString +FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) +{ + // Compute Houdini version string. + FString HoudiniVersionString = FString::Printf( + TEXT("%d.%d.%s%d"), HAPI_VERSION_HOUDINI_MAJOR, + HAPI_VERSION_HOUDINI_MINOR, + (ExtraDigit ? (TEXT("0.")) : TEXT("")), + HAPI_VERSION_HOUDINI_BUILD); + + // If we have a patch version, we need to append it. + if (HAPI_VERSION_HOUDINI_PATCH > 0) + HoudiniVersionString = FString::Printf(TEXT("%s.%d"), *HoudiniVersionString, HAPI_VERSION_HOUDINI_PATCH); + return HoudiniVersionString; +} + +void * +FHoudiniEngineUtils::LoadLibHAPI(FString & StoredLibHAPILocation) +{ + FString HFSPath = TEXT(""); + void * HAPILibraryHandle = nullptr; + + // Look up HAPI_PATH environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . + FString HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HAPI_PATH")); + if (!HFS_ENV_VAR.IsEmpty()) + HFSPath = HFS_ENV_VAR; + + // Look up environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . + HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HFS")); + if (!HFS_ENV_VAR.IsEmpty()) + HFSPath = HFS_ENV_VAR; + + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + + // If we have a custom location specified through settings, attempt to use that. + bool bCustomPathFound = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->bUseCustomHoudiniLocation) + { + // Create full path to libHAPI binary. + FString CustomHoudiniLocationPath = HoudiniRuntimeSettings->CustomHoudiniLocation.Path; + if (!CustomHoudiniLocationPath.IsEmpty()) + { + // Convert path to absolute if it is relative. + if (FPaths::IsRelative(CustomHoudiniLocationPath)) + CustomHoudiniLocationPath = FPaths::ConvertRelativePathToFull(CustomHoudiniLocationPath); + + FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPICustomPath)) + { + HFSPath = CustomHoudiniLocationPath; + bCustomPathFound = true; + } + } + } + + // We have HFS environment variable defined (or custom location), attempt to load libHAPI from it. + if (!HFSPath.IsEmpty()) + { + if (!bCustomPathFound) + { +#if PLATFORM_WINDOWS + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_WINDOWS); +#elif PLATFORM_MAC + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_MAC); +#elif PLATFORM_LINUX + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_LINUX); +#endif + } + + // Create full path to libHAPI binary. + FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + // libHAPI binary exists at specified location, attempt to load it. + FPlatformProcess::PushDllDirectory(*HFSPath); +#if PLATFORM_WINDOWS + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); +#elif PLATFORM_MAC || PLATFORM_LINUX + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); +#endif + FPlatformProcess::PopDllDirectory(*HFSPath); + + // If library has been loaded successfully we can stop. + if ( HAPILibraryHandle ) + { + if (bCustomPathFound) + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from custom path %s"), *LibHAPIName, *HFSPath); + else + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from HFS environment path %s"), *LibHAPIName, *HFSPath); + + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + } + + // Otherwise, we will attempt to detect Houdini installation. + FString HoudiniLocation = TEXT(HOUDINI_ENGINE_HFS_PATH); + FString LibHAPIPath; + + // Compute Houdini version string. + FString HoudiniVersionString = ComputeVersionString(false); + +#if PLATFORM_WINDOWS + + // On Windows, we have also hardcoded HFS path in plugin configuration file; attempt to load from it. + HFSPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, HAPI_HFS_SUBFOLDER_WINDOWS); + + // Create full path to libHAPI binary. + LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HFSPath); + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); + FPlatformProcess::PopDllDirectory(*HFSPath); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from Plugin defined HFS path %s"), *LibHAPIName, *HFSPath); + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + + // As a second attempt, on Windows, we try to look up location of Houdini Engine in the registry. + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini Engine"), StoredLibHAPILocation, false); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // As a third attempt, we try to look up location of Houdini installation (not Houdini Engine) in the registry. + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini"), StoredLibHAPILocation, false); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // Do similar registry lookups for the 32 bits registry + // Look for the Houdini Engine registry install path + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini Engine"), StoredLibHAPILocation, true); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // ... and for the Houdini registry install path + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini"), StoredLibHAPILocation, true); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // Finally, try to load from a hardcoded program files path. + HoudiniLocation = FString::Printf( + TEXT("C:\\Program Files\\Side Effects Software\\Houdini %s\\%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_WINDOWS); + +#else + +# if PLATFORM_MAC + + // Attempt to load from standard Mac OS X installation. + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/Current/Libraries"), *HoudiniVersionString); + + // Fallback in case the previous one doesnt exist + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/%s/Libraries"), *HoudiniVersionString, *HoudiniVersionString); + + // Fallback in case we're using the steam version + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); + + // Backup Fallback in case we're using the steam version + // (this could probably be removed as paths have changed) + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); + +# elif PLATFORM_LINUX + + // Attempt to load from standard Linux installation. + HoudiniLocation = FString::Printf( + TEXT("/opt/hfs%s/%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_LINUX); + +# endif + +#endif + + // Create full path to libHAPI binary. + LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HoudiniLocation); + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); + FPlatformProcess::PopDllDirectory(*HoudiniLocation); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from expected installation %s"), *LibHAPIName, *HoudiniLocation); + StoredLibHAPILocation = HoudiniLocation; + return HAPILibraryHandle; + } + } + + StoredLibHAPILocation = TEXT(""); + return HAPILibraryHandle; +} + +bool +FHoudiniEngineUtils::IsInitialized() +{ + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) + return false; + + return (FHoudiniApi::IsInitialized(SessionPtr) == HAPI_RESULT_SUCCESS); +} + +bool +FHoudiniEngineUtils::IsHoudiniNodeValid(const HAPI_NodeId& NodeId) +{ + if (NodeId < 0) + return false; + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + bool ValidationAnswer = 0; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) + { + return false; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsNodeValid( + FHoudiniEngine::Get().GetSession(), NodeId, + NodeInfo.uniqueHoudiniNodeId, &ValidationAnswer)) + { + return false; + } + + return ValidationAnswer; +} + +bool +FHoudiniEngineUtils::HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex) +{ + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), HostAssetId, InputIndex), false); + + return true; +} + +bool +FHoudiniEngineUtils::DestroyHoudiniAsset(const HAPI_NodeId& AssetId) +{ + if (HAPI_RESULT_SUCCESS == FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), AssetId)) + { + return true; + } + + return false; +} + +#if PLATFORM_WINDOWS +void * +FHoudiniEngineUtils::LocateLibHAPIInRegistry( + const FString & HoudiniInstallationType, + FString & StoredLibHAPILocation, + bool LookIn32bitRegistry) +{ + auto FindDll = [&](const FString& InHoudiniInstallationPath) + { + FString HFSPath = FString::Printf(TEXT("%s/%s"), *InHoudiniInstallationPath, HAPI_HFS_SUBFOLDER_WINDOWS); + + // Create full path to libHAPI binary. + FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, HAPI_LIB_OBJECT_WINDOWS); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HFSPath); + void* HAPILibraryHandle = FPlatformProcess::GetDllHandle(HAPI_LIB_OBJECT_WINDOWS); + FPlatformProcess::PopDllDirectory(*HFSPath); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE( + TEXT("Loaded %s from Registry path %s"), HAPI_LIB_OBJECT_WINDOWS, + *HFSPath); + + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + return (void*)0; + }; + + FString HoudiniInstallationPath; + FString HoudiniVersionString = ComputeVersionString(true); + FString RegistryKey = FString::Printf( + TEXT("Software\\%sSide Effects Software\\%s"), + (LookIn32bitRegistry ? TEXT("WOW6432Node\\") : TEXT("")), *HoudiniInstallationType); + + if (FWindowsPlatformMisc::QueryRegKey( + HKEY_LOCAL_MACHINE, *RegistryKey, *HoudiniVersionString, HoudiniInstallationPath)) + { + FPaths::NormalizeDirectoryName(HoudiniInstallationPath); + return FindDll(HoudiniInstallationPath); + } + + return nullptr; +} +#endif + +bool +FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId & OutAssetLibraryId) +{ + OutAssetLibraryId = -1; + + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return false; + + if (!FHoudiniEngineUtils::IsInitialized()) + return false; + + // Get the HDA's file path + // We need to convert relative file path to absolute + FString AssetFileName = HoudiniAsset->GetAssetFileName(); + if (FPaths::IsRelative(AssetFileName)) + AssetFileName = FPaths::ConvertRelativePathToFull(AssetFileName); + + // We need to modify the file name for expanded .hdas + FString FileExtension = FPaths::GetExtension(AssetFileName); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) + { + // the .hda directory is what we should be loading + AssetFileName = FPaths::GetPath(AssetFileName); + } + + // If the hda file exists, we can simply load it directly the file + HAPI_Result Result = HAPI_RESULT_FAILURE; + if ( !AssetFileName.IsEmpty() ) + { + if ( FPaths::FileExists(AssetFileName) + || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName) ) ) + { + // Load the asset from file. + std::string AssetFileNamePlain; + FHoudiniEngineUtils::ConvertUnrealString(AssetFileName, AssetFileNamePlain); + Result = FHoudiniApi::LoadAssetLibraryFromFile( + FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); + } + } + + // Detect license issues + // HoudiniEngine aquires a license when creating/loading a node, not when creating a session + if (Result >= HAPI_RESULT_NO_LICENSE_FOUND && Result < HAPI_RESULT_ASSET_INVALID) + { + FString ErrorDesc = GetErrorDescription(Result); + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: License failed: %s."), *AssetFileName, *ErrorDesc); + + // We must stop the session to prevent further attempts at loading an HDA + // as this could lead to unreal becoming stuck and unresponsive due to license timeout + FHoudiniEngine::Get().StopSession(); + + return false; + } + + // If loading from file failed, try to load using the memory copy + if (Result != HAPI_RESULT_SUCCESS) + { + // Expanded hdas cannot be loaded from Memory + if (HoudiniAsset->IsExpandedHDA() || HoudiniAsset->GetAssetBytesCount() <= 0) + { + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); + return false; + } + else + { + // Warn the user that we are loading from memory + HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from Memory: source asset file not found."), *AssetFileName); + + // Otherwise we will try to load from buffer we've cached. + Result = FHoudiniApi::LoadAssetLibraryFromMemory( + FHoudiniEngine::Get().GetSession(), + reinterpret_cast(HoudiniAsset->GetAssetBytes()), + HoudiniAsset->GetAssetBytesCount(), true, &OutAssetLibraryId); + } + } + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_MESSAGE(TEXT("Error loading asset library for %s: %s"), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + return true; +} + +bool +FHoudiniEngineUtils::GetSubAssetNames( + const HAPI_AssetLibraryId& AssetLibraryId, + TArray< HAPI_StringHandle >& OutAssetNames) +{ + if (AssetLibraryId < 0) + return false; + + int32 AssetCount = 0; + HAPI_Result Result = HAPI_RESULT_FAILURE; + Result = FHoudiniApi::GetAvailableAssetCount(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetCount); + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Error getting asset count: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + if (AssetCount <= 0) + { + HOUDINI_LOG_ERROR(TEXT("Could not find an asset.")); + return false; + } + + OutAssetNames.SetNumUninitialized(AssetCount); + Result = FHoudiniApi::GetAvailableAssets(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &OutAssetNames[0], AssetCount); + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Unable to retrieve sub asset names: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + if (!AssetCount) + { + HOUDINI_LOG_ERROR(TEXT("No assets found")); + return false; + } + + return true; +} + + +bool +FHoudiniEngineUtils::OpenSubassetSelectionWindow(TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ) +{ + OutPickedAssetName = -1; + + if (AssetNames.Num() <= 0) + return false; + + // Default to the first asset + OutPickedAssetName = AssetNames[0]; + +#if WITH_EDITOR + // Present the user with a dialog for choosing which asset to instantiate. + TSharedPtr ParentWindow; + if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + // Check if the main frame is loaded. When using the old main frame it may not be. + IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (!ParentWindow.IsValid()) + { + return false; + } + + TSharedPtr AssetSelectionWidget; + TSharedRef 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()) + { + return false; + } + + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + + int32 DialogPickedAssetName = AssetSelectionWidget->GetSelectedAssetName(); + if (DialogPickedAssetName != -1) + { + OutPickedAssetName = DialogPickedAssetName; + return true; + } + else + { + return false; + } +#endif + + return true; +} + +/* +bool +FHoudiniEngineUtils::IsValidNodeId(HAPI_NodeId NodeId) +{ + return NodeId != -1; +} +*/ + +bool +FHoudiniEngineUtils::GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString) +{ + HAPI_AssetInfo AssetInfo; + if (FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo) == HAPI_RESULT_SUCCESS) + { + FHoudiniEngineString HoudiniEngineString(AssetInfo.nameSH); + return HoudiniEngineString.ToFString(NameString); + } + + return false; +} + +bool +FHoudiniEngineUtils::GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer) +{ + PresetBuffer.Empty(); + + HAPI_NodeId NodeId; + HAPI_AssetInfo AssetInfo; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo)) + { + NodeId = AssetInfo.nodeId; + } + else + NodeId = AssetNodeId; + + int32 BufferLength = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPresetBufLength( + FHoudiniEngine::Get().GetSession(), NodeId, + HAPI_PRESETTYPE_BINARY, NULL, &BufferLength), false); + + PresetBuffer.SetNumZeroed(BufferLength); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPreset( + FHoudiniEngine::Get().GetSession(), NodeId, + &PresetBuffer[0], PresetBuffer.Num()), false); + + return true; +} + + +bool +FHoudiniEngineUtils::HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath) +{ + // Retrieve Path to the given Node, relative to the other given Node + if ((InNodeId < 0) || (InRelativeToNodeId < 0)) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InNodeId)) + return false; + + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodePath( + FHoudiniEngine::Get().GetSession(), + InNodeId, InRelativeToNodeId, &StringHandle)) + { + if(FHoudiniEngineString::ToFString(StringHandle, OutPath)) + { + return true; + } + } + return false; +} + +bool +FHoudiniEngineUtils::HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath) +{ + // Do the HAPI query only on first-use + if (!InHGPO.NodePath.IsEmpty()) + return true; + + FString NodePathTemp; + if (InHGPO.AssetId == InHGPO.GeoId) + { + // This is a SOP asset, just return the asset name in this case + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), InHGPO.AssetId, &AssetInfo)) + { + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo)) + { + if (FHoudiniEngineString::ToFString(AssetNodeInfo.nameSH, NodePathTemp)) + { + OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); + } + } + } + } + else + { + // This is an OBJ asset, return the path to this geo relative to the asset + if (FHoudiniEngineUtils::HapiGetNodePath(InHGPO.GeoId, InHGPO.AssetId, NodePathTemp)) + { + OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); + } + } + + /*if (OutPath.IsEmpty()) + { + OutPath = TEXT("Empty"); + } + + return NodePath; + */ + + return !OutPath.IsEmpty(); +} + + +bool +FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + InNodeId, &NodeInfo), false); + + int32 ObjectCount = 0; + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + ObjectCount = 1; + OutObjectInfos.SetNumUninitialized(1); + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), + NodeInfo.parentId, &OutObjectInfos[0]), false); + } + else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); + + if (ObjectCount <= 0) + { + ObjectCount = 1; + OutObjectInfos.SetNumUninitialized(1); + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[0]), false); + } + else + { + OutObjectInfos.SetNumUninitialized(ObjectCount); + for (int32 Idx = 0; Idx < OutObjectInfos.Num(); Idx++) + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[0], 0, ObjectCount), false); + } + } + else + return false; + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InNodeId,&NodeInfo), false); + + int32 ObjectCount = 1; + OutObjectTransforms.SetNumUninitialized(1); + FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); + + OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; + OutObjectTransforms[0].scale[0] = 1.0f; + OutObjectTransforms[0].scale[1] = 1.0f; + OutObjectTransforms[0].scale[2] = 1.0f; + OutObjectTransforms[0].rstOrder = HAPI_SRT; + + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + // Do nothing. Identity transform will be used for the main parent object. + } + else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), + InNodeId, nullptr, &ObjectCount), false); + + if (ObjectCount <= 0) + { + // Do nothing. Identity transform will be used for the main asset object. + } + else + { + OutObjectTransforms.SetNumUninitialized(ObjectCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_SRT, &OutObjectTransforms[0], 0, ObjectCount), false); + } + } + else + return false; + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &NodeInfo), false); + + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + NodeInfo.parentId, -1, HAPI_SRT, &HapiTransform), false); + } + else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InNodeId, -1, HAPI_SRT, &HapiTransform), false); + } + else + return false; + + // Convert HAPI transform to Unreal one. + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, OutTransform); + + return true; +} + +void +FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform) +{ + if ( HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM ) + { + // Swap Y/Z, invert W + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[2], + HapiTransform.rotationQuaternion[1], -HapiTransform.rotationQuaternion[3]); + + // Swap Y/Z and scale + FVector ObjectTranslation(HapiTransform.position[0], HapiTransform.position[2], HapiTransform.position[1]); + ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[2], HapiTransform.scale[1]); + + UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); + } + else + { + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[1], + HapiTransform.rotationQuaternion[2], HapiTransform.rotationQuaternion[3]); + + FVector ObjectTranslation( + HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2]); + ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[1], HapiTransform.scale[2]); + + UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); + } +} + +void +FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform) +{ + float HapiMatrix[16]; + FHoudiniApi::ConvertTransformEulerToMatrix(FHoudiniEngine::Get().GetSession(), &HapiTransformEuler, HapiMatrix); + + HAPI_Transform HapiTransformQuat; + FMemory::Memzero< HAPI_Transform >(HapiTransformQuat); + FHoudiniApi::ConvertMatrixToQuat(FHoudiniEngine::Get().GetSession(), HapiMatrix, HAPI_SRT, &HapiTransformQuat); + + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransformQuat, UnrealTransform); +} + +void +FHoudiniEngineUtils::TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform) +{ + FMemory::Memzero< HAPI_Transform >(HapiTransform); + HapiTransform.rstOrder = HAPI_SRT; + + FQuat UnrealRotation = UnrealTransform.GetRotation(); + FVector UnrealTranslation = UnrealTransform.GetTranslation(); + FVector UnrealScale = UnrealTransform.GetScale3D(); + + if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) + { + // Swap Y/Z, invert XYZ + HapiTransform.rotationQuaternion[0] = -UnrealRotation.X; + HapiTransform.rotationQuaternion[1] = -UnrealRotation.Z; + HapiTransform.rotationQuaternion[2] = -UnrealRotation.Y; + HapiTransform.rotationQuaternion[3] = UnrealRotation.W; + + // Swap Y/Z, scale + HapiTransform.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransform.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransform.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + HapiTransform.scale[0] = UnrealScale.X; + HapiTransform.scale[1] = UnrealScale.Z; + HapiTransform.scale[2] = UnrealScale.Y; + } + else + { + HapiTransform.rotationQuaternion[0] = UnrealRotation.X; + HapiTransform.rotationQuaternion[1] = UnrealRotation.Y; + HapiTransform.rotationQuaternion[2] = UnrealRotation.Z; + HapiTransform.rotationQuaternion[3] = UnrealRotation.W; + + HapiTransform.position[0] = UnrealTranslation.X; + HapiTransform.position[1] = UnrealTranslation.Y; + HapiTransform.position[2] = UnrealTranslation.Z; + + HapiTransform.scale[0] = UnrealScale.X; + HapiTransform.scale[1] = UnrealScale.Y; + HapiTransform.scale[2] = UnrealScale.Z; + } +} + +void +FHoudiniEngineUtils::TranslateUnrealTransform( + const FTransform & UnrealTransform, + HAPI_TransformEuler & HapiTransformEuler) +{ + FMemory::Memzero< HAPI_TransformEuler >(HapiTransformEuler); + + HapiTransformEuler.rstOrder = HAPI_SRT; + HapiTransformEuler.rotationOrder = HAPI_XYZ; + + FQuat UnrealRotation = UnrealTransform.GetRotation(); + FVector UnrealTranslation = UnrealTransform.GetTranslation(); + FVector UnrealScale = UnrealTransform.GetScale3D(); + + if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) + { + // switch the quaternion to Y-up, LHR by Swapping Y/Z and negating W + Swap(UnrealRotation.Y, UnrealRotation.Z); + UnrealRotation.W = -UnrealRotation.W; + const FRotator Rotator = UnrealRotation.Rotator(); + + // Negate roll and pitch since they are actually RHR + HapiTransformEuler.rotationEuler[0] = -Rotator.Roll; + HapiTransformEuler.rotationEuler[1] = -Rotator.Pitch; + HapiTransformEuler.rotationEuler[2] = Rotator.Yaw; + + // Swap Y/Z, scale + HapiTransformEuler.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransformEuler.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransformEuler.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + HapiTransformEuler.scale[0] = UnrealScale.X; + HapiTransformEuler.scale[1] = UnrealScale.Z; + HapiTransformEuler.scale[2] = UnrealScale.Y; + } + else + { + const FRotator Rotator = UnrealRotation.Rotator(); + HapiTransformEuler.rotationEuler[0] = Rotator.Roll; + HapiTransformEuler.rotationEuler[1] = Rotator.Yaw; + HapiTransformEuler.rotationEuler[2] = Rotator.Pitch; + + HapiTransformEuler.position[0] = UnrealTranslation.X; + HapiTransformEuler.position[1] = UnrealTranslation.Y; + HapiTransformEuler.position[2] = UnrealTranslation.Z; + + HapiTransformEuler.scale[0] = UnrealScale.X; + HapiTransformEuler.scale[1] = UnrealScale.Y; + HapiTransformEuler.scale[2] = UnrealScale.Z; + } +} + +bool +FHoudiniEngineUtils::UploadHACTransform(UHoudiniAssetComponent* HAC) +{ + if (!HAC || !HAC->bUploadTransformsToHoudiniEngine) + return false; + + // Indicates the HAC has been fully loaded + // TODO: Check! (replaces fullyloaded) + if (!HAC->IsFullyLoaded()) + return false; + + if (HAC->GetAssetCookCount() > 0 && HAC->GetAssetId() >= 0) + { + if (!FHoudiniEngineUtils::HapiSetAssetTransform(HAC->GetAssetId(), HAC->GetComponentTransform())) + return false; + } + + HAC->SetHasComponentTransformChanged(false); + + return true; +} + +bool +FHoudiniEngineUtils::HapiSetAssetTransform(const HAPI_NodeId& AssetId, const FTransform & Transform) +{ + if (AssetId < 0) + return false; + + // Translate Unreal transform to HAPI Euler one. + HAPI_TransformEuler TransformEuler; + FMemory::Memzero< HAPI_TransformEuler >(TransformEuler); + FHoudiniEngineUtils::TranslateUnrealTransform(Transform, TransformEuler); + + // Get the NodeInfo + HAPI_NodeInfo LocalAssetNodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, + &LocalAssetNodeInfo), false); + + if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + LocalAssetNodeInfo.parentId, + &TransformEuler), false); + } + else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + AssetId, &TransformEuler), false); + } + else + return false; + + return true; +} + +HAPI_NodeId +FHoudiniEngineUtils::HapiGetParentNodeId(const HAPI_NodeId& NodeId) +{ + HAPI_NodeId ParentId = -1; + if (NodeId >= 0) + { + HAPI_NodeInfo NodeInfo; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) + ParentId = NodeInfo.parentId; + } + + return ParentId; +} + + +// Assign a unique Actor Label if needed +void +FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + // TODO: Necessary?? + +#if WITH_EDITOR + HAPI_NodeId AssetId = HAC->GetAssetId(); + if (AssetId < 0) + return; + + AActor* OwnerActor = HAC->GetOwner(); + if (!OwnerActor) + return; + + if (!OwnerActor->GetName().StartsWith(AHoudiniAssetActor::StaticClass()->GetName())) + return; + + // Assign unique actor label based on asset name if it seems to have not been renamed already + FString UniqueName; + if (FHoudiniEngineUtils::GetHoudiniAssetName(AssetId, UniqueName)) + FActorLabelUtilities::SetActorLabelUnique(OwnerActor, UniqueName); +#endif +} + +bool +FHoudiniEngineUtils::GetLicenseType(FString & LicenseType) +{ + LicenseType = TEXT(""); + HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetSessionEnvInt( + FHoudiniEngine::Get().GetSession(), HAPI_SESSIONENVINT_LICENSE, + (int32 *)&LicenseTypeValue), false); + + switch (LicenseTypeValue) + { + case HAPI_LICENSE_NONE: + { + LicenseType = TEXT("No License Acquired"); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE: + { + LicenseType = TEXT("Houdini Engine"); + break; + } + + case HAPI_LICENSE_HOUDINI: + { + LicenseType = TEXT("Houdini"); + break; + } + + case HAPI_LICENSE_HOUDINI_FX: + { + LicenseType = TEXT("Houdini FX"); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE_INDIE: + { + LicenseType = TEXT("Houdini Engine Indie"); + break; + } + + case HAPI_LICENSE_HOUDINI_INDIE: + { + LicenseType = TEXT("Houdini Indie"); + break; + } + + case HAPI_LICENSE_MAX: + default: + { + return false; + } + } + + return true; +} + +// Check if the Houdini asset component (or parent HAC of a parameter) is being cooked +bool +FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(UObject* InObj) +{ + if (!InObj) + return false; + + UHoudiniAssetComponent* HoudiniAssetComponent = nullptr; + + if (InObj->IsA()) + { + HoudiniAssetComponent = Cast(InObj); + } + else if (InObj->IsA()) + { + UHoudiniParameter* Parameter = Cast(InObj); + if (!Parameter) + return false; + + HoudiniAssetComponent = Cast(Parameter->GetOuter()); + } + + if (!HoudiniAssetComponent) + return false; + + EHoudiniAssetState AssetState = HoudiniAssetComponent->GetAssetState(); + + return AssetState >= EHoudiniAssetState::PreCook && AssetState <= EHoudiniAssetState::PostCook; +} + +void +FHoudiniEngineUtils::UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate) +{ + TArray ObjectsToUpdate; + ObjectsToUpdate.Add(InObjectToUpdate); + + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() + { + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + } +} + +void +FHoudiniEngineUtils::UpdateEditorProperties(TArray ObjectsToUpdate, const bool& InForceFullUpdate) +{ + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() + { + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + } +} + +void FHoudiniEngineUtils::UpdateBlueprintEditor(UHoudiniAssetComponent* HAC) +{ + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [HAC]() + { + FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); + } +} + +void +FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate) +{ + // TODO: Don't use this method. Prefer using IDetailLayoutBuilder::ForceRefreshDetails(). + // Example to correctly update details panel through IDetailCategoryBuilder / IDetailLayoutBuilder + // IDetailCategoryBuilder &CategoryBuilder = StructBuilder.GetParentCategory(); + // IDetailLayoutBuilder &LayoutBuilder = CategoryBuilder.GetParentLayout(); + // LayoutBuilder.ForceRefreshDetails(); + +#if WITH_EDITOR + if (!bInForceFullUpdate) + { + // bNeedFullUpdate is false only when small changes (parameters value) have been made + // We do not reselect the actor to avoid loosing the currently selected parameter + if(GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); + + return; + } + + // We now want to get all the components/actors owning the objects to update + TArray AllSceneComponents; + for (auto CurrentObject : ObjectsToUpdate) + { + if (!CurrentObject || CurrentObject->IsPendingKill()) + continue; + + // In some case, the object itself is the component + USceneComponent* SceneComp = Cast(CurrentObject); + if (!SceneComp) + { + SceneComp = Cast(CurrentObject->GetOuter()); + } + + if (SceneComp && !SceneComp->IsPendingKill()) + { + AllSceneComponents.Add(SceneComp); + continue; + } + } + + TArray AllActors; + for (auto CurrentSceneComp : AllSceneComponents) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill()) + continue; + + AActor* Actor = CurrentSceneComp->GetOwner(); + if (Actor && !Actor->IsPendingKill()) + AllActors.Add(Actor); + } + + // Updating the editor properties can be done in two ways, depending if we're in the BP editor or not + // If we have a parent actor, we're not in the BP Editor, so update via the property editor module + if (AllActors.Num() > 0) + { + // Get the property editor module + FPropertyEditorModule& PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // This will actually force a refresh of all the details view + //PropertyModule.NotifyCustomizationModuleChanged(); + + TArray SelectedActors; + for (auto Actor : AllActors) + { + if (Actor && Actor->IsSelected()) + SelectedActors.Add(Actor); + } + + if (SelectedActors.Num() > 0) + { + PropertyModule.UpdatePropertyViews(SelectedActors); + } + + // 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. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + { + // We have no details panel, nothing to update. + continue; + } + + // Get the selected actors for this details panels and check if one of ours belongs to it + const TArray>& SelectedDetailActors = DetailsView->GetSelectedActors(); + bool bFoundActor = false; + for (int32 ActorIdx = 0; ActorIdx < SelectedDetailActors.Num(); ActorIdx++) + { + TWeakObjectPtr SelectedActor = SelectedDetailActors[ActorIdx]; + if (SelectedActor.IsValid() && AllActors.Contains(SelectedActor.Get())) + { + bFoundActor = true; + break; + } + } + + // None of our actors belongs to this detail panel, no need to update it + if (!bFoundActor) + continue; + + // Refresh that details panels using its current selection + TArray Selection; + for (auto DetailsActor : SelectedDetailActors) + { + if (DetailsActor.IsValid()) + Selection.Add(DetailsActor.Get()); + } + + // Reset selected actors, force refresh and override the lock. + DetailsView->SetObjects(SelectedActors, bInForceFullUpdate, true); + + if (GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); + } + } + else + { + // TODO: Do we need to do Blueprint Editor updates here or can we confine it to "post output processing"? + + } + + /* + // Reset the full update flag + if (bNeedFullUpdate) + HAC->SetEditorPropertiesNeedFullUpdate(false); + */ + + return; +#endif +} + +void FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC) +{ + //UHoudiniAssetComponent* HACTemplate = HAC->GetCachedTemplate(); + //UBlueprintGeneratedClass* OwnerBPClass = Cast(HACTemplate->GetOuter()); + //if (!OwnerBPClass) + // return; + + ///* + //FBlueprintEditor* BlueprintEditor = static_cast(FAssetEditorManager::Get().FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); + //if (!BlueprintEditor) + // return; + //*/ + + //// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built. + //UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + //FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); + //if (!BlueprintEditor) + // return; + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(HAC); + if (!BlueprintEditor) + return; + + TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); + if (SCSEditor.IsValid()) + { + SCSEditor->UpdateTree(true); + SCSEditor->DumpTree(); + } + BlueprintEditor->RefreshMyBlueprint(); + + //BlueprintEditor->RefreshMyBlueprint(); + //BlueprintEditor->RefreshInspector(); + //BlueprintEditor->RefreshEditors(); + + // Also somehow reselect ? +} + +HAPI_Result +FHoudiniEngineUtils::SetAttributeStringData( + const FString& InString, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo) +{ + TArray StringArray; + StringArray.Add(InString); + + return SetAttributeStringData(StringArray, InNodeId, InPartId, InAttributeName, InAttributeInfo); +} + +HAPI_Result +FHoudiniEngineUtils::SetAttributeStringData( + const TArray& InStringArray, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo ) +{ + TArray StringDataArray; + for (auto CurrentString : InStringArray) + { + // Append the converted string to the string array + StringDataArray.Add(FHoudiniEngineUtils::ExtractRawString(CurrentString)); + } + + // Set the attribute's string data + HAPI_Result result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + TCHAR_TO_ANSI(*InAttributeName), &InAttributeInfo, + StringDataArray.GetData(), 0, InAttributeInfo.count); + + // ExtractRawString allocates memory using malloc, free it! + FreeRawStringMemory(StringDataArray); + + return result; +} + +char * +FHoudiniEngineUtils::ExtractRawString(const FString& InString) +{ + if (InString.IsEmpty()) + return nullptr; + + std::string ConvertedString = TCHAR_TO_UTF8(*InString); + + // Allocate space for unique string. + int32 UniqueStringBytes = ConvertedString.size() + 1; + char * UniqueString = static_cast(FMemory::Malloc(UniqueStringBytes)); + + FMemory::Memzero(UniqueString, UniqueStringBytes); + FMemory::Memcpy(UniqueString, ConvertedString.c_str(), ConvertedString.size()); + + return UniqueString; +} + +void +FHoudiniEngineUtils::FreeRawStringMemory(const char*& InRawString) +{ + if (InRawString == nullptr) + return; + + // Do not attempt to free empty strings! + if (!InRawString[0]) + return; + + FMemory::Free((void*)InRawString); + InRawString = nullptr; +} + +void +FHoudiniEngineUtils::FreeRawStringMemory(TArray& InRawStringArray) +{ + // ExtractRawString allocates memory using malloc, free it! + for (auto CurrentStrPtr : InRawStringArray) + { + FreeRawStringMemory(CurrentStrPtr); + } + InRawStringArray.Empty(); +} + +bool +FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // No need to add another component if we already show the logo + if (FHoudiniEngineUtils::HasHoudiniLogo(HAC)) + return true; + + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + UStaticMeshComponent * HoudiniLogoSMC = NewObject< UStaticMeshComponent >( + HAC, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!HoudiniLogoSMC) + return false; + + HoudiniLogoSMC->SetStaticMesh(HoudiniLogoSM); + HoudiniLogoSMC->SetVisibility(true); + HoudiniLogoSMC->SetHiddenInGame(true); + // Attach created static mesh component to our Houdini component. + HoudiniLogoSMC->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniLogoSMC->RegisterComponent(); + + return true; +} + +bool +FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the Houdini Logo SM + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + // Iterate on the HAC's component + for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + continue; + + // Get the static mesh component + UStaticMeshComponent* SMC = Cast(CurrentSceneComp); + if (!SMC || SMC->IsPendingKill()) + continue; + + // Check if the SMC is the Houdini Logo + if (SMC->GetStaticMesh() != HoudiniLogoSM) + continue; + + SMC->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SMC->UnregisterComponent(); + SMC->DestroyComponent(); + + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the Houdini Logo SM + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + // Iterate on the HAC's component + for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + continue; + + // Get the static mesh component + UStaticMeshComponent* SMC = Cast(CurrentSceneComp); + if (!SMC || SMC->IsPendingKill()) + continue; + + // Check if the SMC is the Houdini Logo + if (SMC->GetStaticMesh() == HoudiniLogoSM) + return true; + } + + return false; +} + +int32 +FHoudiniEngineUtils::HapiGetVertexListForGroup( + const HAPI_NodeId& GeoId, + const HAPI_PartInfo& PartInfo, + const FString& GroupName, + const TArray& FullVertexList, + TArray& NewVertexList, + TArray& AllVertexList, + TArray& AllFaceList, + TArray& AllGroupFaceIndices, + int32& FirstValidVertex, + int32& FirstValidPrim, + const bool& isPackedPrim) +{ + int32 ProcessedWedges = 0; + AllFaceList.Empty(); + FirstValidPrim = 0; + FirstValidVertex = 0; + NewVertexList.Init(-1, FullVertexList.Num()); + + // Get the faces membership for this group + bool bAllEquals = false; + TArray PartGroupMembership; + if (!FHoudiniEngineUtils::HapiGetGroupMembership( + GeoId, PartInfo, HAPI_GROUPTYPE_PRIM, GroupName, PartGroupMembership, bAllEquals)) + return false; + + // Go through all primitives. + for (int32 FaceIdx = 0; FaceIdx < PartGroupMembership.Num(); ++FaceIdx) + { + if (PartGroupMembership[FaceIdx] <= 0) + { + // The face is not in the group, skip + continue; + } + + // Add the face's index. + AllFaceList.Add(FaceIdx); + + // Get the index of this face's vertices + int32 FirstVertexIdx = FaceIdx * 3; + int32 SecondVertexIdx = FirstVertexIdx + 1; + int32 LastVertexIdx = FirstVertexIdx + 2; + + // This face is a member of specified group. + // Add all 3 vertices + if (FullVertexList.IsValidIndex(LastVertexIdx)) + { + NewVertexList[FirstVertexIdx] = FullVertexList[FirstVertexIdx]; + NewVertexList[SecondVertexIdx] = FullVertexList[SecondVertexIdx]; + NewVertexList[LastVertexIdx] = FullVertexList[LastVertexIdx]; + } + + // Mark these vertex indices as used. + if (AllVertexList.IsValidIndex(LastVertexIdx)) + { + AllVertexList[FirstVertexIdx] = 1; + AllVertexList[SecondVertexIdx] = 1; + AllVertexList[LastVertexIdx] = 1; + } + + // Mark this face as used. + if (AllGroupFaceIndices.IsValidIndex(FaceIdx)) + AllGroupFaceIndices[FaceIdx] = 1; + + if (ProcessedWedges == 0) + { + // Keep track of the first valid vertex/face indices for this group + // This will be useful later on when extracting attributes + FirstValidVertex = FirstVertexIdx; + FirstValidPrim = FaceIdx; + } + + ProcessedWedges += 3; + } + + return ProcessedWedges; +} + +bool +FHoudiniEngineUtils::HapiGetGroupNames( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const HAPI_GroupType& GroupType, const bool& isPackedPrim, + TArray& OutGroupNames) +{ + int32 GroupCount = 0; + if (!isPackedPrim) + { + // Get group count on the geo + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo), false); + + if (GroupType == HAPI_GROUPTYPE_POINT) + GroupCount = GeoInfo.pointGroupCount; + else if (GroupType == HAPI_GROUPTYPE_PRIM) + GroupCount = GeoInfo.primitiveGroupCount; + } + else + { + // We need the group count for this packed prim + int32 PointGroupCount = 0, PrimGroupCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupCountOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PointGroupCount, &PrimGroupCount), false); + + if (GroupType == HAPI_GROUPTYPE_POINT) + GroupCount = PointGroupCount; + else if (GroupType == HAPI_GROUPTYPE_PRIM) + GroupCount = PrimGroupCount; + } + + if (GroupCount <= 0) + return true; + + TArray GroupNameStringHandles; + GroupNameStringHandles.SetNumZeroed(GroupCount); + if (!isPackedPrim) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNames( + FHoudiniEngine::Get().GetSession(), + GeoId, GroupType, &GroupNameStringHandles[0], GroupCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNamesOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, GroupType, &GroupNameStringHandles[0], GroupCount), false); + } + + OutGroupNames.SetNum(GroupCount); + for (int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx) + { + FString CurrentGroupName = TEXT(""); + FHoudiniEngineString::ToFString(GroupNameStringHandles[NameIdx], CurrentGroupName); + OutGroupNames[NameIdx] = CurrentGroupName; + } + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetGroupMembership( + const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, + const HAPI_GroupType& GroupType, const FString & GroupName, + TArray& OutGroupMembership, bool& OutAllEquals) +{ + int32 ElementCount = (GroupType == HAPI_GROUPTYPE_POINT) ? PartInfo.pointCount : PartInfo.faceCount; + if (ElementCount < 1) + return false; + OutGroupMembership.SetNum(ElementCount); + + OutAllEquals = false; + std::string ConvertedGroupName = TCHAR_TO_UTF8(*GroupName); + if (!PartInfo.isInstanced) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembership( + FHoudiniEngine::Get().GetSession(), + GeoId, PartInfo.id, GroupType,ConvertedGroupName.c_str(), + &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembershipOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), GeoId, PartInfo.id, GroupType, + ConvertedGroupName.c_str(), &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); + } + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize, + HAPI_AttributeOwner InOwner) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniEngineUtils::HapiGetAttributeDataAsFloat")); + + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Allocate sufficient buffer for data. + OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); + + return true; + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Expected Float, found an int, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray IntData; + IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(IntData.Num()); + for (int32 Idx = 0; Idx < IntData.Num(); Idx++) + { + OutData[Idx] = (float)IntData[Idx]; + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from integer."), *FString(InAttribName)); + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + // Expected Float, found a string, try to convert the attribute + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) + { + bool bConversionError = false; + OutData.SetNum(StringData.Num()); + for (int32 Idx = 0; Idx < StringData.Num(); Idx++) + { + if (StringData[Idx].IsNumeric()) + OutData[Idx] = FCString::Atof(*StringData[Idx]); + else + bConversionError = true; + } + + if (!bConversionError) + { + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from string."), *FString(InAttribName)); + return true; + } + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a float attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + const int32& InTupleSize, + const HAPI_AttributeOwner& InOwner) +{ + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Allocate sufficient buffer for data. + OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); + + return true; + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Expected Int, found a float, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray FloatData; + FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the float values + if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(FloatData.Num()); + for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) + { + OutData[Idx] = (int32)FloatData[Idx]; + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from float."), *FString(InAttribName)); + + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + // Expected Int, found a string, try to convert the attribute + TArray StringData; + if(FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) + { + bool bConversionError = false; + OutData.SetNum(StringData.Num()); + for (int32 Idx = 0; Idx < StringData.Num(); Idx++) + { + if (StringData[Idx].IsNumeric()) + OutData[Idx] = FCString::Atoi(*StringData[Idx]); + else + bConversionError = true; + } + + if (!bConversionError) + { + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from string."), *FString(InAttribName)); + return true; + } + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be an integer attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsString( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize, + HAPI_AttributeOwner InOwner) +{ + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + return FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, OutData); + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Expected string, found a float, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray FloatData; + FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the float values + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(FloatData.Num()); + for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) + { + OutData[Idx] = FString::SanitizeFloat(FloatData[Idx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from float."), *FString(InAttribName)); + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Expected String, found an int, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray IntData; + IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(IntData.Num()); + for (int32 Idx = 0; Idx < IntData.Num(); Idx++) + { + OutData[Idx] = FString::FromInt(IntData[Idx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from integer."), *FString(InAttribName)); + return true; + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a string attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& InAttributeInfo, + TArray& OutData) +{ + if (!InAttributeInfo.exists) + return false; + + // Extract the StringHandles + TArray StringHandles; + StringHandles.Init(-1, InAttributeInfo.count * InAttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, &InAttributeInfo, + &StringHandles[0], 0, InAttributeInfo.count), false); + + // Set the output data size + OutData.SetNum(StringHandles.Num()); + + // Convert the StringHandles to FString. + // We'll use a map to minimize the number of HAPI calls + TMap StringHandleToStringMap; + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + const HAPI_StringHandle& CurrentSH = StringHandles[Idx]; + if (CurrentSH < 0) + { + OutData[Idx] = TEXT(""); + continue; + } + + FString* FoundString = StringHandleToStringMap.Find(CurrentSH); + if (FoundString) + { + OutData[Idx] = *FoundString; + } + else + { + FString HapiString = TEXT(""); + FHoudiniEngineString::ToFString(CurrentSH, HapiString); + + StringHandleToStringMap.Add(CurrentSH, HapiString); + OutData[Idx] = HapiString; + } + } + + return true; +} + +bool +FHoudiniEngineUtils::HapiCheckAttributeExists( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const char * AttribName, HAPI_AttributeOwner Owner) +{ + if (Owner == HAPI_ATTROWNER_INVALID) + { + for (int32 OwnerIdx = 0; OwnerIdx < HAPI_ATTROWNER_MAX; OwnerIdx++) + { + if (HapiCheckAttributeExists(GeoId, PartId, AttribName, (HAPI_AttributeOwner)OwnerIdx)) + { + return true; + } + } + } + else + { + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, AttribName, Owner, &AttribInfo), false); + + return AttribInfo.exists; + } + + return false; +} + +bool +FHoudiniEngineUtils::IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType) +{ + // Check for + // - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE (unreal_instance) on points/detail + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT)) + { + OutInstancerType = EHoudiniInstancerType::AttributeInstancer; + return true; + } + + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL)) + { + OutInstancerType = EHoudiniInstancerType::AttributeInstancer; + return true; + } + + // - HAPI_UNREAL_ATTRIB_INSTANCE (instance) on points + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT)) + { + OutInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsString( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const FString& DefaultValue, + FString& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParamInfo), false); + + // .. and value + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), NodeId, false, + &StringHandle, FoundParamInfo.stringValuesIndex, 1), false); + + // Convert the string handle to FString + return FHoudiniEngineString::ToFString(StringHandle, OutValue); +} + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const int32& DefaultValue, + int32& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParmInfo; + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo), false); + + // .. and value + int32 Value = DefaultValue; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Value, + FoundParmInfo.intValuesIndex, 1), false); + + OutValue = Value; + + return true; +} + + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsFloat( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const float& DefaultValue, + float& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParmInfo; + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo), false); + + // .. and value + float Value = DefaultValue; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Value, + FoundParmInfo.floatValuesIndex, 1), false); + + OutValue = Value; + + return true; +} + +HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo) +{ + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo); + if (NodeInfo.parmCount <= 0) + return -1; + + HAPI_ParmId ParmId = HapiFindParameterByNameOrTag(NodeInfo.id, ParmName); + if ((ParmId < 0) || (ParmId >= NodeInfo.parmCount)) + return -1; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo), -1); + + return ParmId; +} + + +HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName) +{ + // First, try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), -1); + + if (ParmId >= 0) + return ParmId; + + // Second, try to find it by its tag + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmWithTag( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), -1); + + if (ParmId >= 0) + return ParmId; + + return -1; +} + +int32 +FHoudiniEngineUtils::HapiGetAttributeOfType( + const HAPI_NodeId& GeoId, + const HAPI_NodeId& PartId, + const HAPI_AttributeOwner& AttributeOwner, + const HAPI_AttributeTypeInfo& AttributeType, + TArray< HAPI_AttributeInfo >& MatchingAttributesInfo, + TArray< FString >& MatchingAttributesName) +{ + int32 NumberOfAttributeFound = 0; + + // Get the part infos + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, &PartInfo), NumberOfAttributeFound); + + // Get All attribute names for that part + int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; + + TArray AttribNameSHArray; + AttribNameSHArray.SetNum(nAttribCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, AttributeOwner, + AttribNameSHArray.GetData(), nAttribCount), NumberOfAttributeFound); + + // Iterate on all the attributes, and get their part infos to get their type + for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) + { + // Get the name ... + FString HapiString = TEXT(""); + FHoudiniEngineString::ToFString(AttribNameSHArray[Idx], HapiString); + + // ... then the attribute info + HAPI_AttributeInfo AttrInfo; + FHoudiniApi::AttributeInfo_Init(&AttrInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, TCHAR_TO_UTF8(*HapiString), + AttributeOwner, &AttrInfo)) + continue; + + if (!AttrInfo.exists) + continue; + + // ... check the type + if (AttrInfo.typeInfo != AttributeType) + continue; + + MatchingAttributesInfo.Add(AttrInfo); + MatchingAttributesName.Add(HapiString); + + NumberOfAttributeFound++; + } + + return NumberOfAttributeFound; +} + +HAPI_PartInfo +FHoudiniEngineUtils::ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo) +{ + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + + PartInfo.id = InHPartInfo.PartId; + //PartInfo.nameSH = InHPartInfo.Name; + + switch (InHPartInfo.Type) + { + case EHoudiniPartType::Mesh: + PartInfo.type = HAPI_PARTTYPE_MESH; + break; + case EHoudiniPartType::Curve: + PartInfo.type = HAPI_PARTTYPE_CURVE; + break; + case EHoudiniPartType::Instancer: + PartInfo.type = HAPI_PARTTYPE_INSTANCER; + break; + case EHoudiniPartType::Volume: + PartInfo.type = HAPI_PARTTYPE_VOLUME; + break; + default: + case EHoudiniPartType::Invalid: + PartInfo.type = HAPI_PARTTYPE_INVALID; + break; + } + + PartInfo.faceCount = InHPartInfo.FaceCount; + PartInfo.vertexCount = InHPartInfo.VertexCount; + PartInfo.pointCount = InHPartInfo.PointCount; + + PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = InHPartInfo.PointAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX] = InHPartInfo.VertexAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_PRIM] = InHPartInfo.PrimitiveAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL] = InHPartInfo.DetailAttributeCounts; + + PartInfo.isInstanced = InHPartInfo.bIsInstanced; + + PartInfo.instancedPartCount = InHPartInfo.InstancedPartCount; + PartInfo.instanceCount = InHPartInfo.InstanceCount; + + PartInfo.hasChanged = InHPartInfo.bHasChanged; + + return PartInfo; +} + +int32 +FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + TArray< FHoudiniMeshSocket >& AllSockets, + const bool& isPackedPrim) +{ + int32 FoundSocketCount = 0; + + // Attributes we are interested in. + // Position + TArray Positions; + HAPI_AttributeInfo AttribInfoPositions; + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bool bHasRotation = false; + TArray Rotations; + HAPI_AttributeInfo AttribInfoRotations; + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bool bHasScale = false; + TArray Scales; + HAPI_AttributeInfo AttribInfoScales; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // Socket Name + bool bHasNames = false; + TArray Names; + HAPI_AttributeInfo AttribInfoNames; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bool bHasActors = false; + TArray Actors; + HAPI_AttributeInfo AttribInfoActors; + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bool bHasTags = false; + TArray Tags; + HAPI_AttributeInfo AttribInfoTags; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + + // Lambda function for creating the socket and adding it to the array + // Shared between the by Attribute / by Group methods + auto AddSocketToArray = [&](const int32& PointIdx) + { + FHoudiniMeshSocket CurrentSocket; + FVector currentPosition = FVector::ZeroVector; + if (Positions.IsValidIndex(PointIdx * 3 + 2)) + { + currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + FVector currentScale = FVector::OneVector; + if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) + { + currentScale.X = Scales[PointIdx * 3]; + currentScale.Y = Scales[PointIdx * 3 + 2]; + currentScale.Z = Scales[PointIdx * 3 + 1]; + } + + FQuat currentRotation = FQuat::Identity; + if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) + { + currentRotation.X = Rotations[PointIdx * 4]; + currentRotation.Y = Rotations[PointIdx * 4 + 2]; + currentRotation.Z = Rotations[PointIdx * 4 + 1]; + currentRotation.W = -Rotations[PointIdx * 4 + 3]; + } + + if (bHasNames && Names.IsValidIndex(PointIdx)) + CurrentSocket.Name = Names[PointIdx]; + + if (bHasActors && Actors.IsValidIndex(PointIdx)) + CurrentSocket.Actor = Actors[PointIdx]; + + if (bHasTags && Tags.IsValidIndex(PointIdx)) + CurrentSocket.Tag = Tags[PointIdx]; + + // If the scale attribute wasn't set on all socket, we might end up + // with a zero scale socket, avoid that. + if (currentScale == FVector::ZeroVector) + currentScale = FVector::OneVector; + + CurrentSocket.Transform.SetLocation(currentPosition); + CurrentSocket.Transform.SetRotation(currentRotation); + CurrentSocket.Transform.SetScale3D(currentScale); + + // We want to make sure we're not adding the same socket multiple times + AllSockets.AddUnique(CurrentSocket); + + FoundSocketCount++; + + return true; + }; + + + // Lambda function for reseting the arrays/attributes + auto ResetArraysAndAttr = [&]() + { + // Position + Positions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bHasRotation = false; + Rotations.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bHasScale = false; + Scales.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // Socket Name + bHasNames = false; + Names.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bHasActors = false; + Actors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bHasTags = false; + Tags.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + }; + + //------------------------------------------------------------------------- + // FIND SOCKETS BY DETAIL ATTRIBUTES + //------------------------------------------------------------------------- + + int32 SocketIdx = 0; + bool HasSocketAttributes = true; + while (HasSocketAttributes) + { + // Build the current socket's prefix + FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(SocketIdx); + + // Reset the arrays and attributes + ResetArraysAndAttr(); + + // Retrieve position data. + FString SocketPosAttr = SocketAttrPrefix + TEXT("_pos"); + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, TCHAR_TO_ANSI(*SocketPosAttr), + AttribInfoPositions, Positions, 0, HAPI_ATTROWNER_DETAIL)) + break; + + if (!AttribInfoPositions.exists) + { + // No need to keep looking for socket attributes + HasSocketAttributes = false; + break; + } + + // Retrieve rotation data. + FString SocketRotAttr = SocketAttrPrefix + TEXT("_rot"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketRotAttr), AttribInfoRotations, Rotations, 0, HAPI_ATTROWNER_DETAIL)) + bHasRotation = true; + + // Retrieve scale data. + FString SocketScaleAttr = SocketAttrPrefix + TEXT("_scale"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketScaleAttr), AttribInfoScales, Scales, 0, HAPI_ATTROWNER_DETAIL)) + bHasScale = true; + + // Retrieve mesh socket names. + FString SocketNameAttr = SocketAttrPrefix + TEXT("_name"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketNameAttr), AttribInfoNames, Names)) + bHasNames = true; + + // Retrieve mesh socket actor. + FString SocketActorAttr = SocketAttrPrefix + TEXT("_actor"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketActorAttr), AttribInfoActors, Actors)) + bHasActors = true; + + // Retrieve mesh socket tags. + FString SocketTagAttr = SocketAttrPrefix + TEXT("_tag"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketTagAttr), AttribInfoTags, Tags)) + bHasTags = true; + + // Add the socket to the array + AddSocketToArray(0); + + // Try to find the next socket + SocketIdx++; + } + + return FoundSocketCount; +} + + +int32 +FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + TArray& AllSockets, + const bool& isPackedPrim) +{ + // Attributes we are interested in. + // Position + TArray Positions; + HAPI_AttributeInfo AttribInfoPositions; + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bool bHasRotation = false; + TArray Rotations; + HAPI_AttributeInfo AttribInfoRotations; + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bool bHasScale = false; + TArray Scales; + HAPI_AttributeInfo AttribInfoScales; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // We can also get the sockets rotation from the normal + bool bHasNormals = false; + TArray Normals; + HAPI_AttributeInfo AttribInfoNormals; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Socket Name + bool bHasNames = false; + TArray Names; + HAPI_AttributeInfo AttribInfoNames; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bool bHasActors = false; + TArray Actors; + HAPI_AttributeInfo AttribInfoActors; + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bool bHasTags = false; + TArray Tags; + HAPI_AttributeInfo AttribInfoTags; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + + // Lambda function for creating the socket and adding it to the array + // Shared between the by Attribute / by Group methods + int32 FoundSocketCount = 0; + auto AddSocketToArray = [&](const int32& PointIdx) + { + FHoudiniMeshSocket CurrentSocket; + FVector currentPosition = FVector::ZeroVector; + if (Positions.IsValidIndex(PointIdx * 3 + 2)) + { + currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + FVector currentScale = FVector::OneVector; + if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) + { + currentScale.X = Scales[PointIdx * 3]; + currentScale.Y = Scales[PointIdx * 3 + 2]; + currentScale.Z = Scales[PointIdx * 3 + 1]; + } + + FQuat currentRotation = FQuat::Identity; + if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) + { + currentRotation.X = Rotations[PointIdx * 4]; + currentRotation.Y = Rotations[PointIdx * 4 + 2]; + currentRotation.Z = Rotations[PointIdx * 4 + 1]; + currentRotation.W = -Rotations[PointIdx * 4 + 3]; + } + else if (bHasNormals && Normals.IsValidIndex(PointIdx * 3 + 2)) + { + FVector vNormal; + vNormal.X = Normals[PointIdx * 3]; + vNormal.Y = Normals[PointIdx * 3 + 2]; + vNormal.Z = Normals[PointIdx * 3 + 1]; + + if (vNormal != FVector::ZeroVector) + currentRotation = FQuat::FindBetween(FVector::UpVector, vNormal); + } + + if (bHasNames && Names.IsValidIndex(PointIdx)) + CurrentSocket.Name = Names[PointIdx]; + + if (bHasActors && Actors.IsValidIndex(PointIdx)) + CurrentSocket.Actor = Actors[PointIdx]; + + if (bHasTags && Tags.IsValidIndex(PointIdx)) + CurrentSocket.Tag = Tags[PointIdx]; + + // If the scale attribute wasn't set on all socket, we might end up + // with a zero scale socket, avoid that. + if (currentScale == FVector::ZeroVector) + currentScale = FVector::OneVector; + + CurrentSocket.Transform.SetLocation(currentPosition); + CurrentSocket.Transform.SetRotation(currentRotation); + CurrentSocket.Transform.SetScale3D(currentScale); + + // We want to make sure we're not adding the same socket multiple times + AllSockets.AddUnique(CurrentSocket); + + FoundSocketCount++; + + return true; + }; + + + // Lambda function for reseting the arrays/attributes + auto ResetArraysAndAttr = [&]() + { + // Position + Positions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bHasRotation = false; + Rotations.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bHasScale = false; + Scales.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // When using socket groups, we can also get the sockets rotation from the normal + bHasNormals = false; + Normals.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Socket Name + bHasNames = false; + Names.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bHasActors = false; + Actors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bHasTags = false; + Tags.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + }; + + //------------------------------------------------------------------------- + // FIND SOCKETS BY POINT GROUPS + //------------------------------------------------------------------------- + + // Get object / geo group memberships for primitives. + TArray GroupNames; + if (!FHoudiniEngineUtils::HapiGetGroupNames( + GeoId, PartId, HAPI_GROUPTYPE_POINT, isPackedPrim, GroupNames)) + { + HOUDINI_LOG_MESSAGE(TEXT("GetMeshSocketList: Geo [%d] Part [%d] non-fatal error reading point group names"), GeoId, PartId); + } + + // First, we want to make sure we have at least one socket group before continuing + bool bHasSocketGroup = false; + for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) + { + const FString & GroupName = GroupNames[GeoGroupNameIdx]; + if (GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) + || GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) + { + bHasSocketGroup = true; + break; + } + } + + if (!bHasSocketGroup) + return FoundSocketCount; + + // Get the part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PartInfo)) + return false; + + // Reset the data arrays and attributes + ResetArraysAndAttr(); + + // Retrieve position data. + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, Positions)) + return false; + + // Retrieve rotation data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_ROTATION, AttribInfoRotations, Rotations)) + bHasRotation = true; + + // Retrieve normal data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, Normals)) + bHasNormals = true; + + // Retrieve scale data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_SCALE, AttribInfoScales, Scales)) + bHasScale = true; + + // Retrieve mesh socket names. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, AttribInfoNames, Names)) + bHasNames = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD, AttribInfoNames, Names)) + bHasNames = true; + + // Retrieve mesh socket actor. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR, AttribInfoActors, Actors)) + bHasActors = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD, AttribInfoActors, Actors)) + bHasActors = true; + + // Retrieve mesh socket tags. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, AttribInfoTags, Tags)) + bHasTags = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD, AttribInfoTags, Tags)) + bHasTags = true; + + // Extracting Sockets vertices + for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) + { + const FString & GroupName = GroupNames[GeoGroupNameIdx]; + if (!GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) + && !GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) + continue; + + bool AllEquals = false; + TArray< int32 > PointGroupMembership; + FHoudiniEngineUtils::HapiGetGroupMembership( + GeoId, PartInfo, HAPI_GROUPTYPE_POINT, GroupName, PointGroupMembership, AllEquals); + + // Go through all primitives. + for (int32 PointIdx = 0; PointIdx < PointGroupMembership.Num(); ++PointIdx) + { + if (PointGroupMembership[PointIdx] == 0) + { + if (AllEquals) + break; + else + continue; + } + + // Add the corresponding socket to the array + AddSocketToArray(PointIdx); + } + } + + return FoundSocketCount; +} + +bool +FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( + UStaticMesh* StaticMesh, + TArray& AllSockets, + const bool& CleanImportSockets) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Remove the sockets from the previous cook! + if (CleanImportSockets) + { + StaticMesh->Sockets.RemoveAll([=](UStaticMeshSocket* Socket) { return Socket ? Socket->bSocketCreatedAtImport : true; }); + } + + if (AllSockets.Num() <= 0) + return true; + + // Having sockets with empty names can lead to various issues, so we'll create one now + for (int32 Idx = 0; Idx < AllSockets.Num(); ++Idx) + { + // Assign the unnamed sockets with default names + if (AllSockets[Idx].Name.IsEmpty()) + AllSockets[Idx].Name = TEXT("Socket ") + FString::FromInt(Idx); + } + + // ensure the socket names are unique. (Unreal will use the first socket if multiple socket have the same name) + for (int32 Idx_i = 0; Idx_i < AllSockets.Num(); ++Idx_i) + { + int32 Count = 0; + for (int32 Idx_j = Idx_i + 1; Idx_j < AllSockets.Num(); ++Idx_j) + { + if (AllSockets[Idx_i].Name.Equals(AllSockets[Idx_j].Name)) + { + Count += 1; + AllSockets[Idx_j].Name = AllSockets[Idx_j].Name + "_" + FString::FromInt(Count); + } + } + } + + // Clear all the sockets of the output static mesh. + StaticMesh->Sockets.Empty(); + + for (int32 nSocket = 0; nSocket < AllSockets.Num(); nSocket++) + { + // Create a new Socket + UStaticMeshSocket* Socket = NewObject(StaticMesh); + if (!Socket || Socket->IsPendingKill()) + continue; + + Socket->RelativeLocation = AllSockets[nSocket].Transform.GetLocation(); + Socket->RelativeRotation = FRotator(AllSockets[nSocket].Transform.GetRotation()); + Socket->RelativeScale = AllSockets[nSocket].Transform.GetScale3D(); + Socket->SocketName = FName(*AllSockets[nSocket].Name); + + // Socket Tag + FString Tag; + if (!AllSockets[nSocket].Tag.IsEmpty()) + Tag = AllSockets[nSocket].Tag; + + // The actor will be stored temporarily in the socket's Tag as we need a StaticMeshComponent to add an actor to the socket + Tag += TEXT("|") + AllSockets[nSocket].Actor; + + Socket->Tag = Tag; + Socket->bSocketCreatedAtImport = true; + + StaticMesh->Sockets.Add(Socket); + } + + return true; +} + +bool +FHoudiniEngineUtils::CreateAttributesFromTags( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const TArray& Tags ) +{ + if (Tags.Num() <= 0) + return false; + + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Get the destination part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); + + bool NeedToCommitGeo = false; + for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) + { + FString TagString; + Tags[TagIdx].ToString(TagString); + SanitizeHAPIVariableName(TagString); + + // Create a primitive attribute for the tag + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + AttributeInfo.count = PartInfo.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; + + FString AttributeName = TEXT(HAPI_UNREAL_ATTRIB_TAG_PREFIX) + FString::FromInt(TagIdx); + AttributeName.RemoveSpacesInline(); + + Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo); + + if (Result != HAPI_RESULT_SUCCESS) + continue; + + TArray TagStr; + TagStr.Add(FHoudiniEngineUtils::ExtractRawString(TagString)); + + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo, + TagStr.GetData(), 0, AttributeInfo.count); + + if (HAPI_RESULT_SUCCESS == Result) + NeedToCommitGeo = true; + + // Free memory for allocated by ExtractRawString + FHoudiniEngineUtils::FreeRawStringMemory(TagStr); + } + + return NeedToCommitGeo; +} + +bool +FHoudiniEngineUtils::CreateGroupsFromTags( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const TArray& Tags ) +{ + if (Tags.Num() <= 0) + return true; + + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Get the destination part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); + + bool NeedToCommitGeo = false; + for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) + { + FString TagString; + Tags[TagIdx].ToString(TagString); + SanitizeHAPIVariableName(TagString); + + const char * TagStr = FHoudiniEngineUtils::ExtractRawString(TagString); + + // Create a primitive group for this tag + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, TagStr) ) + { + // Set the group's Memberships + TArray GroupArray; + GroupArray.Init(1, PartInfo.faceCount); + + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_GROUPTYPE_PRIM, TagStr, + GroupArray.GetData(), 0, PartInfo.faceCount) ) + { + NeedToCommitGeo = true; + } + } + + // Free memory allocated by ExtractRawString() + FHoudiniEngineUtils::FreeRawStringMemory(TagStr); + } + + return NeedToCommitGeo; +} + + +bool +FHoudiniEngineUtils::SanitizeHAPIVariableName(FString& String) +{ + // Only keep alphanumeric characters, underscores + // Also, if the first character is a digit, append an underscore at the beginning + TArray& StrArray = String.GetCharArray(); + if (StrArray.Num() <= 0) + return false; + + for (auto& CurChar : StrArray) + { + const bool bIsValid = (CurChar >= TEXT('A') && CurChar <= TEXT('Z')) + || (CurChar >= TEXT('a') && CurChar <= TEXT('z')) + || (CurChar >= TEXT('0') && CurChar <= TEXT('9')) + || (CurChar == TEXT('_')) || (CurChar == TEXT('\0')); + + if(bIsValid) + continue; + + CurChar = TEXT('_'); + } + + if (StrArray.Num() > 0) + { + TCHAR FirstChar = StrArray[0]; + if (FirstChar >= TEXT('0') && FirstChar <= TEXT('9')) + StrArray.Insert(TEXT('_'), 0); + } + + return true; +} + +bool +FHoudiniEngineUtils::GetUnrealTagAttributes( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags) +{ + FString TagAttribBase = TEXT("unreal_tag_"); + bool bAttributeFound = true; + int32 TagIdx = 0; + while (bAttributeFound) + { + FString CurrentTagAttr = TagAttribBase + FString::FromInt(TagIdx++); + bAttributeFound = HapiCheckAttributeExists(GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), HAPI_ATTROWNER_PRIM); + if (!bAttributeFound) + break; + + // found the unreal_tag_X attribute, get its value and add it to the array + FString TagValue = FString(); + + // Create an AttributeInfo + { + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) + { + TagValue = StringData[0]; + } + } + + FName NameTag = *TagValue; + OutTags.Add(NameTag); + } + + return true; +} + + +int32 +FHoudiniEngineUtils::GetPropertyAttributeList( + const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundPropertyAttributes) +{ + // Get all the detail uprop attributes on the HGPO + int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_DETAIL); + + // .. then the primitive uprop attributes + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_PRIM); + + return FoundCount; +} + + +int32 +FHoudiniEngineUtils::GetGenericAttributeList( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FString& InGenericAttributePrefix, + TArray& OutFoundAttributes, + const HAPI_AttributeOwner& AttributeOwner, + const int32& InAttribIndex) +{ + // Get the part info to get the attribute counts for the specified owner + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), InGeoNodeId, InPartId, &PartInfo), false); + + int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; + + // Get all attribute names for that part + TArray AttribNameSHArray; + AttribNameSHArray.SetNum(nAttribCount); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, AttributeOwner, + AttribNameSHArray.GetData(), nAttribCount)) + { + return 0; + } + + // For everything but detail attribute, + // if an attribute index was specified, only extract the attribute value for that specific index + // if not, extract all values for the given attribute + bool HandleSplit = false; + int32 AttribIndex = -1; + if ((AttributeOwner != HAPI_ATTROWNER_DETAIL) && (InAttribIndex != -1)) + { + // The index has already been specified so we'll use it + HandleSplit = true; + AttribIndex = InAttribIndex; + } + + int32 FoundCount = 0; + for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) + { + int32 AttribNameSH = (int32)AttribNameSHArray[Idx]; + FString AttribName = TEXT(""); + FHoudiniEngineString::ToFString(AttribNameSH, AttribName); + if (!AttribName.StartsWith(InGenericAttributePrefix, ESearchCase::IgnoreCase)) + continue; + + // Get the Attribute Info + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), AttributeOwner, &AttribInfo)) + { + // failed to get that attribute's info + continue; + } + + int32 AttribStart = 0; + int32 AttribCount = AttribInfo.count; + if (HandleSplit) + { + // For split primitives, we need to only get only one value for the proper split prim + // Make sure that the split index is valid + if (AttribIndex >= 0 && AttribIndex < AttribInfo.count) + { + AttribStart = AttribIndex; + AttribCount = 1; + } + } + + // + FHoudiniGenericAttribute CurrentGenericAttribute; + // Remove the generic attribute prefix + CurrentGenericAttribute.AttributeName = AttribName.Right(AttribName.Len() - InGenericAttributePrefix.Len()); + + CurrentGenericAttribute.AttributeOwner = (EAttribOwner)AttribInfo.owner; + + // Get the attribute type and tuple size + CurrentGenericAttribute.AttributeType = (EAttribStorageType)AttribInfo.storage; + CurrentGenericAttribute.AttributeCount = AttribInfo.count; + CurrentGenericAttribute.AttributeTupleSize = AttribInfo.tupleSize; + + if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT64) + { + // Initialize the value array + CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloat64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, 0, + CurrentGenericAttribute.DoubleValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT) + { + // Initialize the value array + TArray FloatValues; + FloatValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, FloatValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to double + CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < FloatValues.Num(); n++) + CurrentGenericAttribute.DoubleValues[n] = (double)FloatValues[n]; + + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT64) + { + // Initialize the value array + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, CurrentGenericAttribute.IntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT) + { + // Initialize the value array + TArray IntValues; + IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, IntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to int64 + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < IntValues.Num(); n++) + CurrentGenericAttribute.IntValues[n] = (int64)IntValues[n]; + + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + // Initialize a string handle array + TArray HapiSHArray; + HapiSHArray.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the string handle(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + HapiSHArray.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to FString + CurrentGenericAttribute.StringValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + for (int32 IdxSH = 0; IdxSH < HapiSHArray.Num(); IdxSH++) + { + FString CurrentString; + FHoudiniEngineString::ToFString(HapiSHArray[IdxSH], CurrentString); + CurrentGenericAttribute.StringValues[IdxSH] = CurrentString; + } + } + else + { + // Unsupported type, skipping! + continue; + } + + // We can add the UPropertyAttribute to the array + OutFoundAttributes.Add(CurrentGenericAttribute); + FoundCount++; + } + + return FoundCount; +} + + +void +FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject( + UObject* InObject, const FHoudiniGeoPartObject& InHGPO) +{ + if (!InObject || InObject->IsPendingKill()) + return; + + // Get the list of all the Properties to modify from the HGPO's attributes + TArray PropertiesAttributesToModify; + if (!FHoudiniEngineUtils::GetPropertyAttributeList(InHGPO, PropertiesAttributesToModify)) + return; + + // Iterate over the found Property attributes + for (auto CurrentPropAttribute : PropertiesAttributesToModify) + { + // Get the current Property Attribute + const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; + if (CurrentPropertyName.IsEmpty()) + continue; + + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) + continue; + + // Success! + FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropertyName, *ClassName, *ObjectName); + } +} + + +void +FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const FString& Key, const FString& Value) +{ + if (!Package || Package->IsPendingKill()) + return; + + UMetaData * MetaData = Package->GetMetaData(); + if (MetaData && !MetaData->IsPendingKill()) + MetaData->SetValue(Object, *Key, *Value); +} + + +bool +FHoudiniEngineUtils::AddLevelPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + ULevel* InLevel, + const int32& InCount) +{ + if (InNodeId < 0 || InCount <= 0) + return false; + + if (!InLevel || InLevel->IsPendingKill()) + return false; + + // Extract the level path from the level + FString LevelPath = InLevel->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = InCount; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(InCount); + for (int32 Idx = 0; Idx < InCount; ++Idx) + { + PrimitiveAttrs[Idx] = LevelPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} + + +bool +FHoudiniEngineUtils::AddActorPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + AActor* InActor, + const int32& InCount) +{ + if (InNodeId < 0 || InCount <= 0) + return false; + + if (!InActor || InActor->IsPendingKill()) + return false; + + // Extract the actor path + FString ActorPath = InActor->GetPathName(); + + // Get name of attribute used for Actor path + std::string MarshallingAttributeActorPath = HAPI_UNREAL_ATTRIB_ACTOR_PATH; + + // Marshall in Actor path. + HAPI_AttributeInfo AttributeInfoActorPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoActorPath); + AttributeInfoActorPath.count = InCount; + AttributeInfoActorPath.tupleSize = 1; + AttributeInfoActorPath.exists = true; + AttributeInfoActorPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoActorPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoActorPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string ActorPathCStr = TCHAR_TO_ANSI(*ActorPath); + const char* ActorPathCStrRaw = ActorPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(InCount); + for (int32 Idx = 0; Idx < InCount; ++Idx) + { + PrimitiveAttrs[Idx] = ActorPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, + MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoActorPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_actor_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} + + +bool +FHoudiniEngineUtils::ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx) +{ + const TArray< FVector2D > & LightmapUVs = RawMesh.WedgeTexCoords[LightmapSourceIdx]; + const TArray< uint32 > & Indices = RawMesh.WedgeIndices; + + if (LightmapUVs.Num() != Indices.Num()) + { + // This is invalid raw mesh; by design we consider that it contains invalid lightmap faces. + return true; + } + + for (int32 Idx = 0; Idx < Indices.Num(); Idx += 3) + { + const FVector2D & uv0 = LightmapUVs[Idx + 0]; + const FVector2D & uv1 = LightmapUVs[Idx + 1]; + const FVector2D & uv2 = LightmapUVs[Idx + 2]; + + if (uv0 == uv1 && uv1 == uv2) + { + // Detect invalid lightmap face, can stop. + return true; + } + } + + // Otherwise there are no invalid lightmap faces. + return false; +} + +void +FHoudiniEngineUtils::CreateSlateNotification( + const FString& NotificationString, const float& NotificationExpire, const float& NotificationFadeOut ) +{ +#if WITH_EDITOR + // Trying to display SlateNotifications while in a background thread will crash UE + if (!IsInGameThread() && !IsInSlateThread() && !IsInAsyncLoadingThread()) + return; + + // Check whether we want to display Slate notifications. + bool bDisplaySlateCookingNotifications = true; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return; + + FText NotificationText = FText::FromString(NotificationString); + FNotificationInfo Info(NotificationText); + + Info.bFireAndForget = true; + Info.FadeOutDuration = NotificationFadeOut; + Info.ExpireDuration = NotificationExpire; + + TSharedPtr HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + FSlateNotificationManager::Get().AddNotification(Info); +#endif + + return; +} + +FString +FHoudiniEngineUtils::GetHoudiniEnginePluginDir() +{ + FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(EnginePluginDir)) + return EnginePluginDir; + + FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(ProjectPluginDir)) + return ProjectPluginDir; + + TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); + FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; + if (FPaths::DirectoryExists(PluginBaseDir)) + return PluginBaseDir; + + HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); + + return EnginePluginDir; +} + + +HAPI_Result +FHoudiniEngineUtils::CreateNode( + const HAPI_NodeId& InParentNodeId, + const FString& InOperatorName, + const FString& InNodeLabel, + const HAPI_Bool& bInCookOnCreation, + HAPI_NodeId* OutNewNodeId) +{ + // Call HAPI::CreateNode + HAPI_Result Result = FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), + InParentNodeId, TCHAR_TO_UTF8(*InOperatorName), TCHAR_TO_UTF8(*InNodeLabel), bInCookOnCreation, OutNewNodeId); + + // Return now if CreateNode fialed + if (Result != HAPI_RESULT_SUCCESS) + return Result; + + // Loop on the cook_state status until it's ready + int CurrentStatus = HAPI_State::HAPI_STATE_STARTING_LOAD; + while (CurrentStatus > HAPI_State::HAPI_STATE_MAX_READY_STATE) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), + HAPI_StatusType::HAPI_STATUS_COOK_STATE, &CurrentStatus)) + { + // Exit the loop if GetStatus somehow fails + break; + } + } + + if (CurrentStatus == HAPI_STATE_READY_WITH_FATAL_ERRORS) + { + // Fatal errors - failed + HOUDINI_LOG_ERROR(TEXT("Failed to create node %s - %s"), *InOperatorName, *InNodeLabel); + return HAPI_RESULT_FAILURE; + } + else if (CurrentStatus == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // Mention the errors - still return success + HOUDINI_LOG_WARNING(TEXT("Cook errors when creating node %s - %s"), *InOperatorName, *InNodeLabel); + } + + return HAPI_RESULT_SUCCESS; +} + + +int32 +FHoudiniEngineUtils::HapiGetCookCount(const HAPI_NodeId& InNodeId) +{ + int32 CookCount = -1; + + FHoudiniApi::GetTotalCookCount( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_ANY, true, &CookCount); + + /* + // TODO: + // Use HAPI_GetCookingTotalCount() when available + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + + int32 CookCount = -1; + HAPI_Result Result = FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), InNodeId, &NodeInfo); + + if (Result != HAPI_RESULT_FAILURE) + { + if (NodeInfo.type != HAPI_NODETYPE_OBJ) + { + // For SOP assets, get the cook count straight from the Asset Node + CookCount = NodeInfo.totalCookCount; + } + else + { + // For OBJ nodes, get the cook count from the display geos + // Retrieve information about each object contained within our asset. + TArray< HAPI_ObjectInfo > ObjectInfos; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(InNodeId, ObjectInfos)) + return false; + + for (auto CurrentHapiObjectInfo : ObjectInfos) + { + // Get the Display Geo's info + HAPI_GeoInfo DisplayHapiGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) + { + continue; + } + + HAPI_NodeInfo DisplayNodeInfo; + FHoudiniApi::NodeInfo_Init(&DisplayNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), DisplayHapiGeoInfo.nodeId, &DisplayNodeInfo)) + { + continue; + } + + CookCount += DisplayNodeInfo.totalCookCount; + } + } + } + */ + + return CookCount; +} + +bool +FHoudiniEngineUtils::GetLevelPathAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutLevelPaths, + HAPI_AttributeOwner InAttributeOwner) +{ + // --------------------------------------------- + // Attribute: unreal_level_path + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_LEVEL_PATH, AttributeInfo, OutLevelPaths, 1, InAttributeOwner)) + { + if (OutLevelPaths.Num() > 0) + return true; + } + + OutLevelPaths.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetOutputNameAttribute(const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutOutputNames) +{ + // --------------------------------------------- + // Attribute: unreal_output_name + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, AttributeInfo, OutOutputNames, 1)) + { + if (OutOutputNames.Num() > 0) + return true; + } + + OutOutputNames.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, AttributeInfo, OutOutputNames, 1)) + { + if (OutOutputNames.Num() > 0) + return true; + } + + OutOutputNames.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetTileAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutTileValues, + const HAPI_AttributeOwner& InAttribOwner) +{ + // --------------------------------------------- + // Attribute: tile + // --------------------------------------------- + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, + AttribInfoTile, + OutTileValues, + 0, + InAttribOwner)) + { + if (OutTileValues.Num() > 0) + return true; + } + + OutTileValues.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + TArray& OutBakeFolder, + HAPI_PartId InPartId) +{ + OutBakeFolder.Empty(); + + HAPI_AttributeInfo BakeFolderAttribInfo; + FHoudiniApi::AttributeInfo_Init(&BakeFolderAttribInfo); + if (HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_DETAIL)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + + if (HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_PRIM)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + + OutBakeFolder.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeActorAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeActorNames, + HAPI_AttributeOwner InAttributeOwner) +{ + // --------------------------------------------- + // Attribute: unreal_bake_actor + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_BAKE_ACTOR, AttributeInfo, OutBakeActorNames, 1, InAttributeOwner)) + { + if (OutBakeActorNames.Num() > 0) + return true; + } + + OutBakeActorNames.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeOutlinerFolders, + HAPI_AttributeOwner InAttributeOwner) +{ + // --------------------------------------------- + // Attribute: unreal_bake_outliner_folder + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, AttributeInfo, OutBakeOutlinerFolders, 1, InAttributeOwner)) + { + if (OutBakeOutlinerFolders.Num() > 0) + return true; + } + + OutBakeOutlinerFolders.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeFolderOverridePath( + const HAPI_NodeId& InGeoId, + FString& OutBakeFolder, + HAPI_PartId InPartId) +{ + FString BakeFolderOverride; + + TArray StringData; + if (GetBakeFolderAttribute(InGeoId, StringData, InPartId)) + { + BakeFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); + } + + if (BakeFolderOverride.StartsWith("Game/")) + { + BakeFolderOverride = "/" + BakeFolderOverride; + } + + FString AbsoluteOverridePath; + if (BakeFolderOverride.StartsWith("/Game/")) + { + const FString RelativePath = FPaths::ProjectContentDir() + BakeFolderOverride.Mid(6, BakeFolderOverride.Len() - 6); + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + if (!BakeFolderOverride.IsEmpty()) + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolderOverride); + } + + // Check Validity of the path + if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) + { + // Only display a warning if the path is invalid, empty is fine + if (!AbsoluteOverridePath.IsEmpty()) + HOUDINI_LOG_WARNING(TEXT("Invalid override bake path: %s"), *BakeFolderOverride); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + OutBakeFolder = HoudiniRuntimeSettings->DefaultBakeFolder; + + return false; + } + + OutBakeFolder = BakeFolderOverride; + return true; +} + +bool +FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) +{ + if (!InActor || !InDesiredLevel) + return false; + + ULevel* PreviousLevel = InActor->GetLevel(); + if (PreviousLevel == InDesiredLevel) + return true; + + UWorld* CurrentWorld = InActor->GetWorld(); + if(CurrentWorld) + CurrentWorld->RemoveActor(InActor, true); + + //Set the outer of Actor to NewLevel + InActor->Rename((const TCHAR *)0, InDesiredLevel); + InDesiredLevel->Actors.Add(InActor); + + return true; +} + +bool +FHoudiniEngineUtils::HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions, const bool& bWaitForCompletion) +{ + // Check for an invalid node id + if (InNodeId < 0) + return false; + + // No Cook Options were specified, use the default one + if (InCookOptions == nullptr) + { + // Use the default cook options + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); + } + else + { + // Use the provided CookOptions + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, InCookOptions), false); + } + + // If we don't need to wait for completion, return now + if (!bWaitForCompletion) + return true; + + // Wait for the cook to finish + HAPI_Result Result = HAPI_RESULT_SUCCESS; + while (true) + { + // Get the current cook status + int Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // The cook has been successful. + return true; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // There was an error while cooking the node. + //FString CookResultString = FHoudiniEngineUtils::GetCookResult(); + //HOUDINI_LOG_ERROR(); + return false; + } + + // We want to yield a bit. + FPlatformProcess::Sleep(0.1f); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h index 8acd1ba74..caa5fd392 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h @@ -1,624 +1,624 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniEnginePrivatePCH.h" -#include "EngineUtils.h" -#include - -#include "HoudiniOutput.h" -#include "HoudiniPackageParams.h" -#include "Containers/UnrealString.h" - -#include "SSCSEditor.h" - - -class FString; -class UStaticMesh; -class UHoudiniAsset; -class UHoudiniAssetComponent; - -struct FHoudiniPartInfo; -struct FHoudiniMeshSocket; -struct FHoudiniGeoPartObject; -struct FHoudiniGenericAttribute; - -struct FRawMesh; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniInstancerType : uint8; - -struct HOUDINIENGINE_API FHoudiniEngineUtils -{ - friend struct FUnrealMeshTranslator; - - public: - // Load libHAPI and return handle to it, also store location of loaded libHAPI in passed argument. - static void* LoadLibHAPI(FString& StoredLibHAPILocation); - - // Return true if module has been properly initialized. - static bool IsInitialized(); - - // Return type of license used. - static bool GetLicenseType(FString & LicenseType); - - // Cook the specified node id - // if the cook options are null, the defualt one will be used - // if bWaitForCompletion is true, this call will be blocking until the cook is finished - static bool HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions = nullptr, const bool& bWaitForCompletion = false); - - // Return a specified HAPI status string. - static const FString GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity); - - // Return a string representing cooking result. - static const FString GetCookResult(); - - // Return a string indicating cook state. - static const FString GetCookState(); - - // Return a string error description. - static const FString GetErrorDescription(); - - // Return a string description of error from a given error code. - static const FString GetErrorDescription(HAPI_Result Result); - - // Return the errors, warning and messages on a specified node - static const FString GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId); - - static const FString GetCookLog(TArray& InHACs); - - static const FString GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent); - - // Updates the Object transform of a Houdini Asset Component - static bool UploadHACTransform(UHoudiniAssetComponent* HAC); - - // Convert FString to std::string - static void ConvertUnrealString(const FString & UnrealString, std::string& String); - - // Wrapper for the CreateNode function - // As HAPI_CreateNode is an async call, this function actually waits for the node creation to be done before returning - static HAPI_Result CreateNode( - const HAPI_NodeId& InParentNodeId, - const FString& InOperatorName, - const FString& InNodeLabel, - const HAPI_Bool& bInCookOnCreation, - HAPI_NodeId* OutNewNodeId); - - static int32 HapiGetCookCount(const HAPI_NodeId& InNodeId); - - // HAPI : Retrieve the asset node's object transform. **/ - static bool HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform); - - // HAPI : Retrieve object transforms from given asset node id. - static bool HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms); - - // HAPI : Translate HAPI transform to Unreal one. - static void TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform); - - // HAPI : Translate HAPI Euler transform to Unreal one. - static void TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform); - - // HAPI : Translate Unreal transform to HAPI one. - static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform); - - // HAPI : Translate Unreal transform to HAPI Euler one. - static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_TransformEuler & HapiTransformEuler); - - // Return true if asset is valid. - static bool IsHoudiniNodeValid(const HAPI_NodeId& AssetId); - - // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. - static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos); - - // HAPI: Retrieve Path to the given Node, relative to the given Node - static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); - - // HAPI: Retrieve the relative for the given HGPO Node - static bool HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath); - - // HAPI : Return all group names for a given Geo. - static bool HapiGetGroupNames( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const HAPI_GroupType& GroupType, const bool& isPackedPrim, - TArray& OutGroupNames ); - - // HAPI : Retrieve group membership. - static bool HapiGetGroupMembership( - const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, - const HAPI_GroupType& GroupType, const FString & GroupName, - TArray& OutGroupMembership, bool& OutAllEquals); - - // HAPI : Given vertex list, retrieve new vertex list for a specified group. - // Return number of processed valid index vertices for this split. - static int32 HapiGetVertexListForGroup( - const HAPI_NodeId& GeoId, - const HAPI_PartInfo& PartInfo, - const FString& GroupName, - const TArray& FullVertexList, - TArray& NewVertexList, - TArray& AllVertexList, - TArray& AllFaceList, - TArray& AllGroupFaceIndices, - int32& FirstValidVertex, - int32& FirstValidPrim, - const bool& isPackedPrim); - - // HAPI : Get attribute data as float. - static bool HapiGetAttributeDataAsFloat( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize = 0, - HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as Integer. - static bool HapiGetAttributeDataAsInteger( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - const int32& InTupleSize = 0, - const HAPI_AttributeOwner& InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as strings. - static bool HapiGetAttributeDataAsString( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize = 0, - HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as strings. - static bool HapiGetAttributeDataAsStringFromInfo( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& InAttributeInfo, - TArray& OutData); - - // HAPI : Check if given attribute exists. - static bool HapiCheckAttributeExists( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - const char * AttribName, - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID); - - // HAPI: Returns all the attributes of a given type for a given owner - static int32 HapiGetAttributeOfType( - const HAPI_NodeId& GeoId, - const HAPI_NodeId& PartId, - const HAPI_AttributeOwner& AttributeOwner, - const HAPI_AttributeTypeInfo& AttributeType, - TArray& MatchingAttributesInfo, - TArray& MatchingAttributesName); - - // HAPI : Look for a parameter by name or tag and returns its index. Returns -1 if not found. - static HAPI_ParmId HapiFindParameterByNameOrTag( - const HAPI_NodeId& NodeId, const std::string& ParmName); - static HAPI_ParmId HapiFindParameterByNameOrTag( - const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo); - - // Returns true is the given Geo-Part is an attribute instancer - static bool IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); - - // Return true if given asset id is valid. - //static bool IsValidNodeId(HAPI_NodeId AssetId); - - // HAPI : Return a give node's parent ID, -1 if none - static HAPI_NodeId HapiGetParentNodeId(const HAPI_NodeId& NodeId); - - /** HAPI : Marshaling, disconnect input asset from a given slot. **/ - static bool HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex); - - // Destroy asset, returns the status. - static bool DestroyHoudiniAsset(const HAPI_NodeId& AssetId); - - // Loads an HDA file and returns its AssetLibraryId - static bool LoadHoudiniAsset( - UHoudiniAsset * HoudiniAsset, - HAPI_AssetLibraryId & OutAssetLibraryId); - - // Returns the name of the available subassets in a loaded HDA - static bool GetSubAssetNames( - const HAPI_AssetLibraryId& AssetLibraryId, - TArray< HAPI_StringHandle > & OutAssetNames); - - static bool OpenSubassetSelectionWindow( - TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ); - - // Returns the name of a Houdini asset. - static bool GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString); - - // Gets preset data for a given asset. - static bool GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer); - - // HAPI : Set asset transform. - static bool HapiSetAssetTransform(const HAPI_NodeId& AssetNodeId, const FTransform & Transform); - - // TODO: Move me somewhere else - static void AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC); - - // Triggers an update the details panel - // Will use an AsyncTask if we're not in the game thread - // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. - static void UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate); - - // Triggers an update the details panel - // Will use an AsyncTask if we're not in the game thread - // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. - static void UpdateEditorProperties(TArray InObjectsToUpdate, const bool& InForceFullUpdate); - - // Triggers an update the details panel - static void UpdateBlueprintEditor(UHoudiniAssetComponent* HAC); - - // Check if the Houdini asset component is being cooked - static bool IsHoudiniAssetComponentCooking(UObject* InObj); - - // Helper function to set attribute string data for a single FString - static HAPI_Result SetAttributeStringData( - const FString& InString, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo); - - // Helper function to set attribute string data for a FString array - static HAPI_Result SetAttributeStringData( - const TArray& InStringArray, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo); - - static bool HapiGetParameterDataAsString( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const FString& DefaultValue, - FString& OutValue); - - static bool HapiGetParameterDataAsInteger( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const int32& DefaultValue, - int32 & OutValue); - - static bool HapiGetParameterDataAsFloat( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const float& DefaultValue, - float& OutValue); - - // Returns a list with all the Property attributes found on a HGPO - static int32 GetPropertyAttributeList( - const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundUProps); - - // Updates all FProperty attributes found on a given object - static void UpdateAllPropertyAttributesOnObject( - UObject* InObject, const FHoudiniGeoPartObject& InHGPO); - - // Returns a list of all the generic attributes for a given attribute owner - static int32 GetGenericAttributeList( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FString& InGenericAttributePrefix, - TArray& OutFoundAttributes, - const HAPI_AttributeOwner& AttributeOwner, - const int32& InAttribIndex = -1); - - /* - // Tries to update values for all the UProperty attributes to apply on the object. - static void ApplyUPropertyAttributesOnObject( - UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify ); - */ - /* - static bool TryToFindInStructProperty( - UObject* Object, FString UPropertyNameToFind, UStructProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); - */ - /* - static bool TryToFindInArrayProperty( - UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); - */ - - static void AddHoudiniMetaInformationToPackage( - UPackage* Package, UObject* Object, const FString& Key, const FString& Value); - - // Adds the HoudiniLogo mesh to a Houdini Asset Component - static bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); - - // Removes the default Houdini logo mesh from a HAC - static bool RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC); - - // Indicates if a HAC has the Houdini logo mesh - static bool HasHoudiniLogo(UHoudiniAssetComponent* HAC); - - // - static HAPI_PartInfo ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo); - - // - static int32 AddMeshSocketsToArray_Group( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - TArray& AllSockets, const bool& isPackedPrim); - - // - static int32 AddMeshSocketsToArray_DetailAttribute( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - TArray& AllSockets, const bool& isPackedPrim); - - static bool AddMeshSocketsToStaticMesh( - UStaticMesh* StaticMesh, - TArray& AllSockets, - const bool& CleanImportSockets); - - // - static bool CreateGroupsFromTags( - const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); - - // - static bool CreateAttributesFromTags( - const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); - - static bool GetUnrealTagAttributes(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags); - - // Helper function to access the "unreal_level_path" attribute - static bool GetLevelPathAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutLevelPath, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to access the custom output name attribute - static bool GetOutputNameAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutOutputName); - - // Helper function to access the "tile" attribute - static bool GetTileAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutTileValue, - const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); - - // Helper function to access the "unreal_bake_folder" attribute - static bool GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - TArray& OutBakeFolder, - HAPI_PartId InPartId=0); - - // Helper function to access the bake output actor attribute (unreal_bake_actor) - static bool GetBakeActorAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeActorNames, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to access the bake output actor attribute (unreal_bake_outliner_folder) - static bool GetBakeOutlinerFolderAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeOutlinerFolders, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to get the bake folder override path. This is the "unreal_bake_folder" attribute, or if this - // does not exist or is invalid, the default bake folder path configured in the settings. - static bool GetBakeFolderOverridePath( - const HAPI_NodeId& InGeoId, - FString& OutBakeFolder, - HAPI_PartId InPartId=0); - - // Adds the "unreal_level_path" primitive attribute - static bool AddLevelPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - ULevel* InLevel, - const int32& InCount); - - // Adds the "unreal_actor_path" primitive attribute - static bool AddActorPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - AActor* InActor, - const int32& InCount); - - // Helper function used to extract a const char* from a FString - // !! Allocates memory using malloc that will need to be freed afterwards! - static char * ExtractRawString(const FString& Name); - - // Frees memory allocated by ExtractRawString() - static void FreeRawStringMemory(const char*& InRawString); - - // Frees memory allocated by ExtractRawString() - static void FreeRawStringMemory(TArray& InRawStringArray); - - // Make sure a string complies with Houdini's internal variable naming convention (group, attr etc..) - static bool SanitizeHAPIVariableName(FString& String); - - /** How many GUID symbols are used for package component name generation. **/ - static const int32 PackageGUIDComponentNameLength; - - /** How many GUID symbols are used for package item name generation. **/ - static const int32 PackageGUIDItemNameLength; - - /** Helper routine to check invalid lightmap faces. **/ - static bool ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx); - - // Helper function for creating a temporary Slate notification. - static void CreateSlateNotification( - const FString& NotificationString, - const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, - const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); - - static FString GetHoudiniEnginePluginDir(); - - // ------------------------------------------------- - // UWorld and UPackage utilities - // ------------------------------------------------- - - // Find actor in a given world by name - // Note that by default this will return all actors - template - static T* FindActorInWorld(UWorld* InWorld, FName ActorName, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) - { - T* OutActor = nullptr; - for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) - { - OutActor = *ActorIt; - if (!OutActor) - continue; - if (OutActor->GetFName().Compare(ActorName)==0) - return OutActor; - } - return nullptr; - } - - // Find an actor by name - static UWorld* FindWorldInPackage(const FString& PackagePath, bool bCreatedMissingPackage, bool& bOutPackageCreated); - - // Determine the appropriate world and level in which to spawn a new actor. - static bool FindWorldAndLevelForSpawning( - UWorld* CurrentWorld, - const FString& PackagePath, - bool bCreateMissingPackage, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bOutPackageCreated, - bool& bPackageInWorld); - - template - static T* SpawnActorInLevel(UWorld* InWorld, ULevel* InLevel) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = InLevel; - return InWorld->SpawnActor(SpawnParams); - } - - // Force the AssetRegistry to recursively rescan a path for - // any new packages that it may not know about, starting at the directory - // in which the given world package is located. This is typically useful - // for WorldComposition to detect new packages immediately after they - // were created. - static void RescanWorldPath(UWorld* InWorld); - - // ------------------------------------------------- - // Actor Utilities - // ------------------------------------------------- - - // Find in actor that belongs to the given outer matching the specified name. - // If the actor doesn't match the type, or is in a PendingKill state, rename it - // so that a new actor can be created with the given name. - // Note that if an actor with the give name was found, it will be returned via `OutFoundActor`. - static AActor* FindOrRenameInvalidActorGeneric(UClass* Class, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor); - - template - static T* FindOrRenameInvalidActor(UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) - { - return Cast( FindOrRenameInvalidActorGeneric(T::StaticClass(), InWorld, InName, OutFoundActor) ); - } - - // Moves an actor to the specified level - static bool MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel); - - // ------------------------------------------------- - // Debug Utilities - // ------------------------------------------------- - - // Log debug info for the given package - static void LogPackageInfo(const FString& InLongPackageName); - static void LogPackageInfo(const UPackage* InPackage); - - static void LogWorldInfo(const FString& InLongPackageName); - static void LogWorldInfo(const UWorld* InWorld); - - static FString HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType); - static FString HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState); - - // ------------------------------------------------- - // Generic naming / pathing utilities - // ------------------------------------------------- - - // Rename the actor to a unique / generated name. - static FName RenameToUniqueActor(AActor* InActor, const FString& InName); - - // Safely rename the actor by ensuring that there aren't any existing objects left - // in the actor's outer with the same name. If an existing object was found, rename it and return it. - static UObject* SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel=true); - - // ------------------------------------------------- - // PackageParam utilities - // ------------------------------------------------- - - static void FillInPackageParamsForBakingOutput( - FHoudiniPackageParams& OutPackageParams, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FString &BakeFolder, - const FString &ObjectName, - const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets); - - // ------------------------------------------------- - // Foliage utilities - // ------------------------------------------------- - - // If the foliage editor mode is active, repopulate the list of foliage types in the UI. - // NOTE: this is a currently a bit of a hack: we deactive and reactive the foliage mode (if it was active), - // since the relevant functions are not API exported. - // Returns true if the list was repopulated. - static bool RepopulateFoliageTypeListInUI(); - - public: - - static bool IsOuterHoudiniAssetComponent(UObject* Obj); - static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(UObject* Obj); - - protected: - - // Computes the XX.YY.ZZZ version string using HAPI_Version - static FString ComputeVersionString(bool ExtraDigit); - -#if PLATFORM_WINDOWS - // Attempt to locate libHAPI on Windows in the registry. Return handle if located and return location. - static void* LocateLibHAPIInRegistry( - const FString& HoudiniInstallationType, FString& StoredLibHAPILocation, bool LookIn32bitRegistry); -#endif - - // Triggers an update the details panel - //static void UpdateEditorProperties_Internal(UObject* ObjectToUpdate, const bool& bInForceFullUpdate); - - // Triggers an update the details panel - static void UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate); - - // Trigger an update of the Blueprint Editor on the game thread - static void UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC); - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniEnginePrivatePCH.h" +#include "EngineUtils.h" +#include + +#include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" +#include "Containers/UnrealString.h" + +#include "SSCSEditor.h" + + +class FString; +class UStaticMesh; +class UHoudiniAsset; +class UHoudiniAssetComponent; + +struct FHoudiniPartInfo; +struct FHoudiniMeshSocket; +struct FHoudiniGeoPartObject; +struct FHoudiniGenericAttribute; + +struct FRawMesh; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniInstancerType : uint8; + +struct HOUDINIENGINE_API FHoudiniEngineUtils +{ + friend struct FUnrealMeshTranslator; + + public: + // Load libHAPI and return handle to it, also store location of loaded libHAPI in passed argument. + static void* LoadLibHAPI(FString& StoredLibHAPILocation); + + // Return true if module has been properly initialized. + static bool IsInitialized(); + + // Return type of license used. + static bool GetLicenseType(FString & LicenseType); + + // Cook the specified node id + // if the cook options are null, the defualt one will be used + // if bWaitForCompletion is true, this call will be blocking until the cook is finished + static bool HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions = nullptr, const bool& bWaitForCompletion = false); + + // Return a specified HAPI status string. + static const FString GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity); + + // Return a string representing cooking result. + static const FString GetCookResult(); + + // Return a string indicating cook state. + static const FString GetCookState(); + + // Return a string error description. + static const FString GetErrorDescription(); + + // Return a string description of error from a given error code. + static const FString GetErrorDescription(HAPI_Result Result); + + // Return the errors, warning and messages on a specified node + static const FString GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId); + + static const FString GetCookLog(TArray& InHACs); + + static const FString GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent); + + // Updates the Object transform of a Houdini Asset Component + static bool UploadHACTransform(UHoudiniAssetComponent* HAC); + + // Convert FString to std::string + static void ConvertUnrealString(const FString & UnrealString, std::string& String); + + // Wrapper for the CreateNode function + // As HAPI_CreateNode is an async call, this function actually waits for the node creation to be done before returning + static HAPI_Result CreateNode( + const HAPI_NodeId& InParentNodeId, + const FString& InOperatorName, + const FString& InNodeLabel, + const HAPI_Bool& bInCookOnCreation, + HAPI_NodeId* OutNewNodeId); + + static int32 HapiGetCookCount(const HAPI_NodeId& InNodeId); + + // HAPI : Retrieve the asset node's object transform. **/ + static bool HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform); + + // HAPI : Retrieve object transforms from given asset node id. + static bool HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms); + + // HAPI : Translate HAPI transform to Unreal one. + static void TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform); + + // HAPI : Translate HAPI Euler transform to Unreal one. + static void TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform); + + // HAPI : Translate Unreal transform to HAPI one. + static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform); + + // HAPI : Translate Unreal transform to HAPI Euler one. + static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_TransformEuler & HapiTransformEuler); + + // Return true if asset is valid. + static bool IsHoudiniNodeValid(const HAPI_NodeId& AssetId); + + // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. + static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos); + + // HAPI: Retrieve Path to the given Node, relative to the given Node + static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); + + // HAPI: Retrieve the relative for the given HGPO Node + static bool HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath); + + // HAPI : Return all group names for a given Geo. + static bool HapiGetGroupNames( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const HAPI_GroupType& GroupType, const bool& isPackedPrim, + TArray& OutGroupNames ); + + // HAPI : Retrieve group membership. + static bool HapiGetGroupMembership( + const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, + const HAPI_GroupType& GroupType, const FString & GroupName, + TArray& OutGroupMembership, bool& OutAllEquals); + + // HAPI : Given vertex list, retrieve new vertex list for a specified group. + // Return number of processed valid index vertices for this split. + static int32 HapiGetVertexListForGroup( + const HAPI_NodeId& GeoId, + const HAPI_PartInfo& PartInfo, + const FString& GroupName, + const TArray& FullVertexList, + TArray& NewVertexList, + TArray& AllVertexList, + TArray& AllFaceList, + TArray& AllGroupFaceIndices, + int32& FirstValidVertex, + int32& FirstValidPrim, + const bool& isPackedPrim); + + // HAPI : Get attribute data as float. + static bool HapiGetAttributeDataAsFloat( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize = 0, + HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); + + // HAPI : Get attribute data as Integer. + static bool HapiGetAttributeDataAsInteger( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + const int32& InTupleSize = 0, + const HAPI_AttributeOwner& InOwner = HAPI_ATTROWNER_INVALID); + + // HAPI : Get attribute data as strings. + static bool HapiGetAttributeDataAsString( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize = 0, + HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); + + // HAPI : Get attribute data as strings. + static bool HapiGetAttributeDataAsStringFromInfo( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& InAttributeInfo, + TArray& OutData); + + // HAPI : Check if given attribute exists. + static bool HapiCheckAttributeExists( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + const char * AttribName, + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID); + + // HAPI: Returns all the attributes of a given type for a given owner + static int32 HapiGetAttributeOfType( + const HAPI_NodeId& GeoId, + const HAPI_NodeId& PartId, + const HAPI_AttributeOwner& AttributeOwner, + const HAPI_AttributeTypeInfo& AttributeType, + TArray& MatchingAttributesInfo, + TArray& MatchingAttributesName); + + // HAPI : Look for a parameter by name or tag and returns its index. Returns -1 if not found. + static HAPI_ParmId HapiFindParameterByNameOrTag( + const HAPI_NodeId& NodeId, const std::string& ParmName); + static HAPI_ParmId HapiFindParameterByNameOrTag( + const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo); + + // Returns true is the given Geo-Part is an attribute instancer + static bool IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); + + // Return true if given asset id is valid. + //static bool IsValidNodeId(HAPI_NodeId AssetId); + + // HAPI : Return a give node's parent ID, -1 if none + static HAPI_NodeId HapiGetParentNodeId(const HAPI_NodeId& NodeId); + + /** HAPI : Marshaling, disconnect input asset from a given slot. **/ + static bool HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex); + + // Destroy asset, returns the status. + static bool DestroyHoudiniAsset(const HAPI_NodeId& AssetId); + + // Loads an HDA file and returns its AssetLibraryId + static bool LoadHoudiniAsset( + UHoudiniAsset * HoudiniAsset, + HAPI_AssetLibraryId & OutAssetLibraryId); + + // Returns the name of the available subassets in a loaded HDA + static bool GetSubAssetNames( + const HAPI_AssetLibraryId& AssetLibraryId, + TArray< HAPI_StringHandle > & OutAssetNames); + + static bool OpenSubassetSelectionWindow( + TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ); + + // Returns the name of a Houdini asset. + static bool GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString); + + // Gets preset data for a given asset. + static bool GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer); + + // HAPI : Set asset transform. + static bool HapiSetAssetTransform(const HAPI_NodeId& AssetNodeId, const FTransform & Transform); + + // TODO: Move me somewhere else + static void AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC); + + // Triggers an update the details panel + // Will use an AsyncTask if we're not in the game thread + // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. + static void UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate); + + // Triggers an update the details panel + // Will use an AsyncTask if we're not in the game thread + // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. + static void UpdateEditorProperties(TArray InObjectsToUpdate, const bool& InForceFullUpdate); + + // Triggers an update the details panel + static void UpdateBlueprintEditor(UHoudiniAssetComponent* HAC); + + // Check if the Houdini asset component is being cooked + static bool IsHoudiniAssetComponentCooking(UObject* InObj); + + // Helper function to set attribute string data for a single FString + static HAPI_Result SetAttributeStringData( + const FString& InString, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo); + + // Helper function to set attribute string data for a FString array + static HAPI_Result SetAttributeStringData( + const TArray& InStringArray, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo); + + static bool HapiGetParameterDataAsString( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const FString& DefaultValue, + FString& OutValue); + + static bool HapiGetParameterDataAsInteger( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const int32& DefaultValue, + int32 & OutValue); + + static bool HapiGetParameterDataAsFloat( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const float& DefaultValue, + float& OutValue); + + // Returns a list with all the Property attributes found on a HGPO + static int32 GetPropertyAttributeList( + const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundUProps); + + // Updates all FProperty attributes found on a given object + static void UpdateAllPropertyAttributesOnObject( + UObject* InObject, const FHoudiniGeoPartObject& InHGPO); + + // Returns a list of all the generic attributes for a given attribute owner + static int32 GetGenericAttributeList( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FString& InGenericAttributePrefix, + TArray& OutFoundAttributes, + const HAPI_AttributeOwner& AttributeOwner, + const int32& InAttribIndex = -1); + + /* + // Tries to update values for all the UProperty attributes to apply on the object. + static void ApplyUPropertyAttributesOnObject( + UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify ); + */ + /* + static bool TryToFindInStructProperty( + UObject* Object, FString UPropertyNameToFind, UStructProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); + */ + /* + static bool TryToFindInArrayProperty( + UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); + */ + + static void AddHoudiniMetaInformationToPackage( + UPackage* Package, UObject* Object, const FString& Key, const FString& Value); + + // Adds the HoudiniLogo mesh to a Houdini Asset Component + static bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); + + // Removes the default Houdini logo mesh from a HAC + static bool RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC); + + // Indicates if a HAC has the Houdini logo mesh + static bool HasHoudiniLogo(UHoudiniAssetComponent* HAC); + + // + static HAPI_PartInfo ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo); + + // + static int32 AddMeshSocketsToArray_Group( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + TArray& AllSockets, const bool& isPackedPrim); + + // + static int32 AddMeshSocketsToArray_DetailAttribute( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + TArray& AllSockets, const bool& isPackedPrim); + + static bool AddMeshSocketsToStaticMesh( + UStaticMesh* StaticMesh, + TArray& AllSockets, + const bool& CleanImportSockets); + + // + static bool CreateGroupsFromTags( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); + + // + static bool CreateAttributesFromTags( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); + + static bool GetUnrealTagAttributes(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags); + + // Helper function to access the "unreal_level_path" attribute + static bool GetLevelPathAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutLevelPath, + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + + // Helper function to access the custom output name attribute + static bool GetOutputNameAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutOutputName); + + // Helper function to access the "tile" attribute + static bool GetTileAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutTileValue, + const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); + + // Helper function to access the "unreal_bake_folder" attribute + static bool GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + TArray& OutBakeFolder, + HAPI_PartId InPartId=0); + + // Helper function to access the bake output actor attribute (unreal_bake_actor) + static bool GetBakeActorAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeActorNames, + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + + // Helper function to access the bake output actor attribute (unreal_bake_outliner_folder) + static bool GetBakeOutlinerFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeOutlinerFolders, + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + + // Helper function to get the bake folder override path. This is the "unreal_bake_folder" attribute, or if this + // does not exist or is invalid, the default bake folder path configured in the settings. + static bool GetBakeFolderOverridePath( + const HAPI_NodeId& InGeoId, + FString& OutBakeFolder, + HAPI_PartId InPartId=0); + + // Adds the "unreal_level_path" primitive attribute + static bool AddLevelPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + ULevel* InLevel, + const int32& InCount); + + // Adds the "unreal_actor_path" primitive attribute + static bool AddActorPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + AActor* InActor, + const int32& InCount); + + // Helper function used to extract a const char* from a FString + // !! Allocates memory using malloc that will need to be freed afterwards! + static char * ExtractRawString(const FString& Name); + + // Frees memory allocated by ExtractRawString() + static void FreeRawStringMemory(const char*& InRawString); + + // Frees memory allocated by ExtractRawString() + static void FreeRawStringMemory(TArray& InRawStringArray); + + // Make sure a string complies with Houdini's internal variable naming convention (group, attr etc..) + static bool SanitizeHAPIVariableName(FString& String); + + /** How many GUID symbols are used for package component name generation. **/ + static const int32 PackageGUIDComponentNameLength; + + /** How many GUID symbols are used for package item name generation. **/ + static const int32 PackageGUIDItemNameLength; + + /** Helper routine to check invalid lightmap faces. **/ + static bool ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx); + + // Helper function for creating a temporary Slate notification. + static void CreateSlateNotification( + const FString& NotificationString, + const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, + const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); + + static FString GetHoudiniEnginePluginDir(); + + // ------------------------------------------------- + // UWorld and UPackage utilities + // ------------------------------------------------- + + // Find actor in a given world by name + // Note that by default this will return all actors + template + static T* FindActorInWorld(UWorld* InWorld, FName ActorName, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) + { + T* OutActor = nullptr; + for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) + { + OutActor = *ActorIt; + if (!OutActor) + continue; + if (OutActor->GetFName().Compare(ActorName)==0) + return OutActor; + } + return nullptr; + } + + // Find an actor by name + static UWorld* FindWorldInPackage(const FString& PackagePath, bool bCreatedMissingPackage, bool& bOutPackageCreated); + + // Determine the appropriate world and level in which to spawn a new actor. + static bool FindWorldAndLevelForSpawning( + UWorld* CurrentWorld, + const FString& PackagePath, + bool bCreateMissingPackage, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bOutPackageCreated, + bool& bPackageInWorld); + + template + static T* SpawnActorInLevel(UWorld* InWorld, ULevel* InLevel) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = InLevel; + return InWorld->SpawnActor(SpawnParams); + } + + // Force the AssetRegistry to recursively rescan a path for + // any new packages that it may not know about, starting at the directory + // in which the given world package is located. This is typically useful + // for WorldComposition to detect new packages immediately after they + // were created. + static void RescanWorldPath(UWorld* InWorld); + + // ------------------------------------------------- + // Actor Utilities + // ------------------------------------------------- + + // Find in actor that belongs to the given outer matching the specified name. + // If the actor doesn't match the type, or is in a PendingKill state, rename it + // so that a new actor can be created with the given name. + // Note that if an actor with the give name was found, it will be returned via `OutFoundActor`. + static AActor* FindOrRenameInvalidActorGeneric(UClass* Class, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor); + + template + static T* FindOrRenameInvalidActor(UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) + { + return Cast( FindOrRenameInvalidActorGeneric(T::StaticClass(), InWorld, InName, OutFoundActor) ); + } + + // Moves an actor to the specified level + static bool MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel); + + // ------------------------------------------------- + // Debug Utilities + // ------------------------------------------------- + + // Log debug info for the given package + static void LogPackageInfo(const FString& InLongPackageName); + static void LogPackageInfo(const UPackage* InPackage); + + static void LogWorldInfo(const FString& InLongPackageName); + static void LogWorldInfo(const UWorld* InWorld); + + static FString HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType); + static FString HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState); + + // ------------------------------------------------- + // Generic naming / pathing utilities + // ------------------------------------------------- + + // Rename the actor to a unique / generated name. + static FName RenameToUniqueActor(AActor* InActor, const FString& InName); + + // Safely rename the actor by ensuring that there aren't any existing objects left + // in the actor's outer with the same name. If an existing object was found, rename it and return it. + static UObject* SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel=true); + + // ------------------------------------------------- + // PackageParam utilities + // ------------------------------------------------- + + static void FillInPackageParamsForBakingOutput( + FHoudiniPackageParams& OutPackageParams, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FString &BakeFolder, + const FString &ObjectName, + const FString &HoudiniAssetName, + EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets); + + // ------------------------------------------------- + // Foliage utilities + // ------------------------------------------------- + + // If the foliage editor mode is active, repopulate the list of foliage types in the UI. + // NOTE: this is a currently a bit of a hack: we deactive and reactive the foliage mode (if it was active), + // since the relevant functions are not API exported. + // Returns true if the list was repopulated. + static bool RepopulateFoliageTypeListInUI(); + + public: + + static bool IsOuterHoudiniAssetComponent(UObject* Obj); + static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(UObject* Obj); + + protected: + + // Computes the XX.YY.ZZZ version string using HAPI_Version + static FString ComputeVersionString(bool ExtraDigit); + +#if PLATFORM_WINDOWS + // Attempt to locate libHAPI on Windows in the registry. Return handle if located and return location. + static void* LocateLibHAPIInRegistry( + const FString& HoudiniInstallationType, FString& StoredLibHAPILocation, bool LookIn32bitRegistry); +#endif + + // Triggers an update the details panel + //static void UpdateEditorProperties_Internal(UObject* ObjectToUpdate, const bool& bInForceFullUpdate); + + // Triggers an update the details panel + static void UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate); + + // Trigger an update of the Blueprint Editor on the game thread + static void UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp index 54e4dbce6..aadcda693 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp @@ -1,777 +1,777 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoImportCommandlet.h" - -#include "DirectoryWatcherModule.h" -#include "Modules/ModuleManager.h" -#include "Misc/Guid.h" -#include "EditorFramework/AssetImportData.h" - -#include "Editor.h" -#include "FileHelpers.h" - -#include "MessageEndpointBuilder.h" - -#include "PackageTools.h" - -#include "IDirectoryWatcher.h" - -#include "Internationalization/Regex.h" - -#include "Interfaces/ISlateNullRendererModule.h" -#include "Rendering/SlateRenderer.h" -#include "Framework/Application/SlateApplication.h" - -#include "HoudiniPackageParams.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniOutput.h" -#include "HoudiniPDGImporterMessages.h" -#include "HoudiniMeshTranslator.h" -#include "HAL/ThreadManager.h" - - -UHoudiniGeoImportCommandlet::UHoudiniGeoImportCommandlet() -{ - HelpDescription = TEXT("Import BGEOs as UAssets. Includes an option to watch a directories and include new .bgeos created there."); - - HelpUsage = TEXT("HoudiniGeoImport Usage: HoudiniGeoImport {options} [filename.bgeo]"); - // "Options:\n" - // "\t-help or -?\n" - // "\t\tDisplays this help.\n\n" - // "\t-listen=manager_messaging_address\n\n" - // "\t\tListen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager.\n\n" - // "\t-watch=directory\n\n" - // "\t\tA directory to watch for new .bgeo files to import.\n\n" - // "\t-managerpid=owner_pid\n\n" - // "\t\tThe PID of the owner/manager process. If the manager process dies the commandlet also quits.\n\n" - // "\t-bake\n\n" - // "\t\tBake generated assets. Instancers are baked to blueprints. Not supported in -listen mode.\n\n" - // "\t[filename.bgeo]\n" - // "\t\tWhen not using -listen or -watch, the path to a .bgeo file must be specified for import.\n" - //); - - HelpParamNames = { - "help", - "listen", - "guid", - "watch", - "managerpid", - "bake" - }; - - HelpParamDescriptions = { - "Displays this help.", - "Listen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager. Expects the owning process' PID.", - "Specify a GUID for the commandlet. Useful to identify the commandlet when the messaging system is used.", - "A directory to watch for new .bgeo files to import.", - "The PID of the owner/manager process. If the manager process dies the commandlet also quits.", - "Bake generated assets. Instancers are baked to blueprints. Not supported in -listen mode." - }; - - IsClient = false; - IsEditor = true; - IsServer = false; - LogToConsole = true; - ShowProgress = false; - ShowErrorCount = false; - - // LogToConsole = false; - - Mode = EHoudiniGeoImportCommandletMode::None; - bBakeOutputs = false; -} - -void UHoudiniGeoImportCommandlet::PrintUsage() const -{ - HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpDescription); - HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpUsage); - const int32 NumOptions = HelpParamNames.Num(); - for (int32 Idx = 0; Idx < NumOptions; ++Idx) - { - HOUDINI_LOG_DISPLAY(TEXT("-%s\t%s"), *HelpParamNames[Idx], *HelpParamDescriptions[Idx]); - } -} - -void UHoudiniGeoImportCommandlet::PopulatePackageParams(const FString &InBGEOFilename, FHoudiniPackageParams& OutPackageParams) -{ - UObject* InParent = this; - - if (bBakeOutputs) - { - OutPackageParams.PackageMode = EPackageMode::Bake; - } - else - { - OutPackageParams.PackageMode = EPackageMode::CookToTemp; - } - OutPackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - OutPackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - OutPackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - OutPackageParams.HoudiniAssetName = FPaths::GetBaseFilename(InBGEOFilename); - OutPackageParams.HoudiniAssetActorName = FString(); - OutPackageParams.ObjectName = FPaths::GetBaseFilename(InBGEOFilename); - - if (!OutPackageParams.OuterPackage) - { - OutPackageParams.OuterPackage = InParent; - } - - if (!OutPackageParams.ComponentGUID.IsValid()) - { - // TODO: will need to reuse the GUID when reimporting? - OutPackageParams.ComponentGUID = FGuid::NewGuid(); - } - - OutPackageParams.bAttemptToLoadMissingPackages = true; -} - -void UHoudiniGeoImportCommandlet::TickDiscoveredFiles() -{ - for (auto &FileDataEntry : DiscoveredFiles) - { - FDiscoveredFileData &FileData = FileDataEntry.Value; - if (FileData.bImportNextTick && !FileData.bImported) - { - FileData.bImportNextTick = false; - FileData.ImportAttempts++; - - FHoudiniPackageParams PackageParams; - PopulatePackageParams(FileData.FileName, PackageParams); - TArray Outputs; - int32 Error = ImportBGEO(FileData.FileName, PackageParams, Outputs); - if (Error == 0) - { - FileData.bImported = true; - HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Done"), *FileData.FileName); - } - else - { - FileData.bImported = false; - HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Failed (%d)"), *FileData.FileName, Error); - } - } - } -} - -int32 UHoudiniGeoImportCommandlet::MainLoop() -{ - GIsRunning = true; - - IDirectoryWatcher* DirectoryWatcher = nullptr; - - if (Mode == EHoudiniGeoImportCommandletMode::Listen) - { - PDGEndpoint = FMessageEndpoint::Builder("PDG/BGEO Commandlet") - .Handling(this, &UHoudiniGeoImportCommandlet::HandleImportBGEOMessage) - .ReceivingOnThread(ENamedThreads::GameThread); - if (!PDGEndpoint.IsValid()) - { - GIsRunning = false; - return 3; - } - // Notify the manager that we are running - HOUDINI_LOG_DISPLAY(TEXT("Notifying the manager (%s) that we are running"), *ManagerAddress.ToString()); - // Try to send directly to the manager - // TODO: this initially direct message does not work, the address looks to be correct, perhaps there is some - // additional set up needed to connect / discover the endpoints? - PDGEndpoint->Send(new FHoudiniPDGImportBGEODiscoverMessage(Guid), ManagerAddress); - } - else if (Mode == EHoudiniGeoImportCommandletMode::Watch) - { - FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::Get().LoadModuleChecked(TEXT("DirectoryWatcher")); - DirectoryWatcher = DirectoryWatcherModule.Get(); - } - - // In UnrealEngine 4.25 and older we cannot tick the editor engine without slate being initialized. - if (!FSlateApplication::IsInitialized()) - { - FSlateApplication::InitHighDPI(false); - FSlateApplication::Create(); - } - - // If slate is initialized, make sure it has a renderer. If we have to create a renderer, create the null renderer. - if (FSlateApplication::IsInitialized() && !FSlateApplication::Get().GetRenderer()) - { - const TSharedPtr SlateRenderer = FModuleManager::Get().LoadModuleChecked("SlateNullRenderer").CreateSlateNullRenderer(); - const TSharedRef SlateRendererSharedRef = SlateRenderer.ToSharedRef(); - FSlateApplication::Get().InitializeRenderer(SlateRendererSharedRef); - } - - // in listen mode broadcast our presence every 60 seconds - // This is an attempt to test if it solves a rare issue where the endpoints appear to get - // "disconnected" and sending a message to a previously valid message address stops working, even though - // both processes are still running (happens especially when debugging with breakpoints) - const float BroadcastIntervalSeconds = 60.0f; - float LastbroadcastTimeSeconds = 0.0f; - - // main loop - while (GIsRunning && !IsEngineExitRequested()) - { - GEngine->UpdateTimeAndHandleMaxTickRate(); - GEngine->Tick(FApp::GetDeltaTime(), false); - - if (FSlateApplication::IsInitialized()) - { - FSlateApplication::Get().PumpMessages(); - FSlateApplication::Get().Tick(); - } - - // Required for FTimerManager to function - as it blocks ticks, if the frame counter doesn't change - GFrameCounter++; - - // update task graph - FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); - - FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()); - FThreadManager::Get().Tick(); - GEngine->TickDeferredCommands(); - - if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) - { - // DirectoryWatcher->Tick(FApp::GetDeltaTime()); - - // Process the discovered files - TickDiscoveredFiles(); - } - - if (OwnerProcHandle.IsValid() && !FPlatformProcess::IsProcRunning(OwnerProcHandle)) - { - // Our once valid owner has disappeared, so quit. - RequestEngineExit(TEXT("OwnerDisappeared")); - } - - if (Mode == EHoudiniGeoImportCommandletMode::Listen && PDGEndpoint.IsValid()) - { - const float TimeSeconds = FPlatformTime::Seconds(); - if (TimeSeconds - LastbroadcastTimeSeconds >= BroadcastIntervalSeconds) - { - LastbroadcastTimeSeconds = TimeSeconds; - // Broadcast a discover message to notify that we are still available - PDGEndpoint->Publish(new FHoudiniPDGImportBGEODiscoverMessage(Guid)); - - HOUDINI_LOG_MESSAGE(TEXT("Publishing FHoudiniPDGImportBGEODiscoverMessage(%s)"), *Guid.ToString()); - } - } - - FPlatformProcess::Sleep(0); - } - - PDGEndpoint.Reset(); - if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) - { - DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(DirectoryToWatch, DirectoryWatcherHandle); - } - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Shutdown(); - - GIsRunning = false; - - return 0; -} - -void UHoudiniGeoImportCommandlet::HandleImportBGEOMessage( - const FHoudiniPDGImportBGEOMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_DISPLAY(TEXT("Received BGEO import request from %s"), *InContext->GetSender().ToString()); - - FHoudiniPackageParams PackageParams; - InMessage.PopulatePackageParams(PackageParams); - - // The commandlet must try to load packages if FindPackage fails, since we unload packages when done - PackageParams.bAttemptToLoadMissingPackages = true; - - TArray Outputs; - TMap> OutputObjectAttributes; - TMap InstancedOutputPartData; - if (ImportBGEO(InMessage.FilePath, PackageParams, Outputs, &OutputObjectAttributes, &InstancedOutputPartData) == 0) - { - FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); - (*Reply) = InMessage; - // Reply->PopulateFromPackageParams(PackageParams); - Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Success; - - const int32 NumOutputs = Outputs.Num(); - Reply->Outputs.Init(FHoudiniPDGImportNodeOutput(), NumOutputs); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - FHoudiniPDGImportNodeOutput &MessageOutput = Reply->Outputs[Index]; - UHoudiniOutput* Output = Outputs[Index]; - for (const FHoudiniGeoPartObject& HGPO : Output->GetHoudiniGeoPartObjects()) - { - HOUDINI_LOG_WARNING(TEXT("HGPO %d %d %d"), HGPO.ObjectId, HGPO.GeoId, HGPO.PartId); - MessageOutput.HoudiniGeoPartObjects.Add(HGPO); - - // Get instancer data if this is an instancer output - if (Output->GetType() == EHoudiniOutputType::Instancer) - { - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = HGPO.ObjectId; - OutputIdentifier.GeoId = HGPO.GeoId; - OutputIdentifier.PartId = HGPO.PartId; - OutputIdentifier.PartName = HGPO.PartName; - - FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = InstancedOutputPartData.Find(OutputIdentifier); - if (InstancedOutputPartDataPtr) - { - InstancedOutputPartDataPtr->BuildFlatInstancedTransformsAndObjectPaths(); - MessageOutput.InstancedOutputPartData.Add(*InstancedOutputPartDataPtr); - } - else - { - MessageOutput.InstancedOutputPartData.Add(FHoudiniInstancedOutputPartData()); - } - } - } - for (const auto& Entry : Output->GetOutputObjects()) - { - HOUDINI_LOG_WARNING(TEXT("Identifier %d %d %d"), Entry.Key.ObjectId, Entry.Key.GeoId, Entry.Key.PartId); - - MessageOutput.OutputObjects.AddDefaulted(); - FHoudiniPDGImportNodeOutputObject& MessageOutputObject = MessageOutput.OutputObjects.Last(); - - FString PackagePath = IsValid(Entry.Value.OutputObject) ? Entry.Value.OutputObject->GetPathName() : ""; - MessageOutputObject.Identifier = Entry.Key; - MessageOutputObject.PackagePath = PackagePath; - const TArray* PropertyAttributes = OutputObjectAttributes.Find(Entry.Key); - if (PropertyAttributes) - MessageOutputObject.GenericAttributes = *PropertyAttributes; - MessageOutputObject.CachedAttributes = Entry.Value.CachedAttributes; - } - } - - PDGEndpoint->Send(Reply, InContext->GetSender()); - } - else - { - HOUDINI_LOG_WARNING(TEXT("BGEO import failed.")); - FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); - Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Failed; - PDGEndpoint->Send(Reply, InContext->GetSender()); - } - - // Cleanup the outputs (remove from root) - TArray PackagesToUnload; - for (UHoudiniOutput *CurOutput : Outputs) - { - if (!IsValid(CurOutput)) - continue; - - for (const auto& Entry : CurOutput->GetOutputObjects()) - { - if (IsValid(Entry.Value.OutputObject)) - { - UPackage *Outermost = Entry.Value.OutputObject->GetOutermost(); - if (IsValid(Outermost)) - { - PackagesToUnload.Add(Outermost); - } - - Entry.Value.OutputObject->RemoveFromRoot(); - } - } - - CurOutput->RemoveFromRoot(); - } - Outputs.Empty(); - OutputObjectAttributes.Empty(); - - if (PackagesToUnload.Num() > 0) - { - HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ..."), PackagesToUnload.Num()); - FText ErrorMessage; - if (!UPackageTools::UnloadPackages(PackagesToUnload, ErrorMessage)) - { - HOUDINI_LOG_WARNING(TEXT("Unload packages failed: %s"), *ErrorMessage.ToString()); - } - else - { - HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ... Success"), PackagesToUnload.Num()); - } - PackagesToUnload.Empty(); - } - - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); -} - -bool UHoudiniGeoImportCommandlet::StartHoudiniEngineSession() -{ - // Start Houdini Engine session - HOUDINI_LOG_DISPLAY(TEXT("Starting Houdini Engine session...")); - FHoudiniEngine& HoudiniEngine = FHoudiniEngine::Get(); - if (!HoudiniEngine.CreateSession( - EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe, - "hapi_bgeo_cmdlet")) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start Houdini Engine session.")); - return false; - } - - return true; -} - -int32 UHoudiniGeoImportCommandlet::ImportBGEO( - const FString &InFilename, - const FHoudiniPackageParams &InPackageParams, - TArray& OutOutputs, - TMap>* OutGenericAttributes, - TMap* OutInstancedOutputPartData) -{ - if (!IsHoudiniEngineSessionRunning() && !StartHoudiniEngineSession()) - { - return 2; - } - - FHoudiniPackageParams PackageParams = InPackageParams; - UHoudiniGeoImporter* GeoImporter = NewObject(this); - - TArray OldOutputs; - OutOutputs.Empty(); - - // 2. Update the file paths - HOUDINI_LOG_DISPLAY(TEXT("SetFilePath %s"), *InFilename); - if (!GeoImporter->SetFilePath(InFilename)) - return 1; - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - HOUDINI_LOG_DISPLAY(TEXT("LoadBGEOFileInHAPI")); - if (!GeoImporter->LoadBGEOFileInHAPI(NodeId)) - return 1; - - // Look for a bake folder override in the BGEO file - if (PackageParams.PackageMode == EPackageMode::Bake) - { - HOUDINI_LOG_DISPLAY(TEXT("Looking for bake folder override attribute...")); - // Get the geo id for the node id - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), NodeId, &DisplayGeoInfo)) - { - FString BakeFolderOverride; - const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); - if (bFoundOverride && !BakeFolderOverride.IsEmpty()) - { - PackageParams.BakeFolder = BakeFolderOverride; - HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override: %s"), *PackageParams.BakeFolder); - } - else - { - HOUDINI_LOG_DISPLAY(TEXT("No bake folder override, using: %s"), *PackageParams.BakeFolder); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could not find display geo node id (when looking for bake folder override).")); - } - } - - auto CleanUpAndExit = [&OutOutputs, GeoImporter, NodeId](int32 InExitCode) - { - GeoImporter->GetOutputObjects().Empty(); - for (UHoudiniOutput* Output : OutOutputs) - { - Output->RemoveFromRoot(); - } - OutOutputs.Empty(); - - if (NodeId >= 0) - GeoImporter->DeleteCreatedNode(NodeId); - - return InExitCode; - }; - - // 4. Get the output from the file node - HOUDINI_LOG_DISPLAY(TEXT("BuildOutputsForNode %d"), NodeId); - if (!GeoImporter->BuildOutputsForNode(NodeId, OldOutputs, OutOutputs)) - return CleanUpAndExit(1); - - // Create uniquely named packages, commandlet runs in conjunction - // with a main editor instance, so we cannot modify existing files - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - // FString PackageName; - // UPackage* Outer = PackageParams.CreatePackageForObject(PackageName); - UObject* Outer = this; - - // 5. Create the static meshes in the outputs - HOUDINI_LOG_DISPLAY(TEXT("Create Static Meshes")); - if (!GeoImporter->CreateStaticMeshes(OutOutputs, Outer, PackageParams)) - return CleanUpAndExit(1); - - //// 6. Create the landscape in the outputs - //if (!GeoImporter->CreateLandscapes(NewOutputs, Outer, PackageParams)) - // return CleanUpAndExit(1); - - // 7. Create the instancers in the outputs - if (OutInstancedOutputPartData) - { - if (!GeoImporter->CreateInstancerOutputPartData(OutOutputs, *OutInstancedOutputPartData)) - return CleanUpAndExit(1); - } - else - { - if (!GeoImporter->CreateInstancers(OutOutputs, Outer, PackageParams)) - return CleanUpAndExit(1); - } - - if (OutGenericAttributes) - { - // Collect all generic properties from Houdini, we need to pass these - // through to PDG manager - HOUDINI_LOG_DISPLAY(TEXT("Get Generic Attributes for static meshes")); - for (UHoudiniOutput* CurOutput : OutOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (const auto& Entry : CurOutput->GetOutputObjects()) - { - const FHoudiniOutputObjectIdentifier OutputIdentifier = Entry.Key; - TArray PropertyAttributes; - FHoudiniMeshTranslator::GetGenericPropertiesAttributes( - OutputIdentifier.GeoId, OutputIdentifier.PartId, - OutputIdentifier.PointIndex, OutputIdentifier.PrimitiveIndex, - PropertyAttributes); - OutGenericAttributes->Add(OutputIdentifier, PropertyAttributes); - } - } - } - - // 8. Delete the created node in Houdini - HOUDINI_LOG_DISPLAY(TEXT("DeleteCreatedNode %d"), NodeId); - if (!GeoImporter->DeleteCreatedNode(NodeId)) - { - // Not good, but not fatal.. - //return false; - } - - TArray PackagesToSave; - TArray& OutputObjects = GeoImporter->GetOutputObjects(); - for (UObject* Object : OutputObjects) - { - if (!IsValid(Object)) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("Created object: %s"), *Object->GetFullName()); - - UAssetImportData* AssetImportData = nullptr; - if (Object->IsA()) - { - UStaticMesh* SM = Cast(Object); - AssetImportData = SM->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); - SM->AssetImportData = AssetImportData; - } - } - - if (AssetImportData) - AssetImportData->Update(InFilename); - - Object->MarkPackageDirty(); - Object->PostEditChange(); - - UPackage* Package = Object->GetOutermost(); - if (IsValid(Package)) - { - PackagesToSave.AddUnique(Package); - } - } - - if (PackagesToSave.Num() > 0) - { - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); - } - - PackagesToSave.Empty(); - OutputObjects.Empty(); - - return 0; -} - -void UHoudiniGeoImportCommandlet::HandleDirectoryChanged(const TArray& InFileChangeDatas) -{ - const FRegexPattern BGEOPattern(TEXT(R"((.*)\.(bgeo(\.[^\.]*)?)$)")); - - for (const FFileChangeData& FileChangeData : InFileChangeDatas) - { - HOUDINI_LOG_MESSAGE(TEXT("HandleDirectoryChanged %d %s"), FileChangeData.Action, *FileChangeData.Filename); - - FRegexMatcher BGEOMatcher(BGEOPattern, FileChangeData.Filename.ToLower()); - if (BGEOMatcher.FindNext() && BGEOMatcher.GetCaptureGroup(2).StartsWith(TEXT("bgeo"))) - { - HOUDINI_LOG_DISPLAY(TEXT("Updating entry for %s..."), *FileChangeData.Filename); - const uint32 MaxImportAttempts = 3; - switch(FileChangeData.Action) - { - case FFileChangeData::FCA_Added: - case FFileChangeData::FCA_Modified: - if (DiscoveredFiles.Contains(FileChangeData.Filename)) - { - FDiscoveredFileData &FileData = DiscoveredFiles[FileChangeData.Filename]; - if (!FileData.bImported && FileData.ImportAttempts < MaxImportAttempts) - FileData.bImportNextTick = true; - else if (FileData.ImportAttempts >= MaxImportAttempts) - HOUDINI_LOG_WARNING(TEXT("Not importing %s, max attempts exceeded %d"), *FileData.FileName, FileData.ImportAttempts); - } - else - { - DiscoveredFiles.Add(FileChangeData.Filename, FDiscoveredFileData(FileChangeData.Filename, true)); - } - break; - case FFileChangeData::FCA_Removed: - DiscoveredFiles.Remove(FileChangeData.Filename); - break; - default: - HOUDINI_LOG_WARNING(TEXT("Unknown file change event %d for %s"), FileChangeData.Action, *FileChangeData.Filename); - } - } - } -} - -int32 UHoudiniGeoImportCommandlet::Main(const FString& InParams) -{ - TArray Tokens; - TArray Switches; - TMap Params; - ParseCommandLine(*InParams, Tokens, Switches, Params); - - if (Switches.Contains(TEXT("help")) || Switches.Contains(TEXT("?"))) - { - PrintUsage(); - return 0; - } - - if (Params.Contains(TEXT("guid"))) - { - const FString GuidStr = Params.FindChecked(TEXT("guid")); - FGuid::Parse(GuidStr, Guid); - - HOUDINI_LOG_DISPLAY(TEXT("GUID received: %s"), *Guid.ToString()); - } - else - { - Guid = FGuid::NewGuid(); - } - - // Set bake mode - if (Switches.Contains(TEXT("bake"))) - bBakeOutputs = true; - else - bBakeOutputs = false; - - if (Params.Contains(TEXT("listen"))) - { - Mode = EHoudiniGeoImportCommandletMode::Listen; - - if (!Params.Contains(TEXT("managerpid"))) - { - HOUDINI_LOG_ERROR(TEXT("'managerpid' is required when in -listen mode.")); - return 1; - } - - if (bBakeOutputs) - { - HOUDINI_LOG_ERROR(TEXT("'listen' mode does not support baking outputs (-bake).")); - return 1; - } - - // Get the manager's messaging address from the -listen param - const FString ManagerAddressStr = Params.FindChecked(TEXT("listen")); - if (!FMessageAddress::Parse(ManagerAddressStr, ManagerAddress)) - { - HOUDINI_LOG_ERROR(TEXT("The manager messaging address passed to -listen=%s is invalid."), *ManagerAddressStr); - return 1; - } - - // Get the manager pid and proc handle - uint32 OwnerProcessId = FCString::Strtoi(*Params.FindChecked(TEXT("managerpid")), nullptr, 10); - HOUDINI_LOG_DISPLAY(TEXT("Owner process Id: %d"), OwnerProcessId); - OwnerProcHandle = FPlatformProcess::OpenProcess(OwnerProcessId); - - return MainLoop(); - } - else if (Params.Contains(TEXT("watch"))) - { - Mode = EHoudiniGeoImportCommandletMode::Watch; - - HOUDINI_LOG_DISPLAY(TEXT("directory watch mode")); - FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); - IDirectoryWatcher* DirectoryWatcher = DirectoryWatcherModule.Get(); - if (DirectoryWatcher) - { - DirectoryToWatch = Params.FindChecked(TEXT("watch")); - if (FPaths::IsRelative(DirectoryToWatch)) - DirectoryToWatch = FPaths::ConvertRelativePathToFull(DirectoryToWatch); - - HOUDINI_LOG_DISPLAY(TEXT("Watching %s"), *DirectoryToWatch); - - DirectoryWatcher->RegisterDirectoryChangedCallback_Handle( - DirectoryToWatch, - IDirectoryWatcher::FDirectoryChanged::CreateUObject(this, &UHoudiniGeoImportCommandlet::HandleDirectoryChanged), - DirectoryWatcherHandle); - - return MainLoop(); - } - else - { - return 10; - } - } - else if (Tokens.Num() > 0) - { - Mode = EHoudiniGeoImportCommandletMode::SpecifiedFiles; - - if (!StartHoudiniEngineSession()) - return 2; - - const FString Filename = FPaths::IsRelative(Tokens[0]) ? FPaths::ConvertRelativePathToFull(Tokens[0]) : Tokens[0]; - FHoudiniPackageParams PackageParams; - PopulatePackageParams(Filename, PackageParams); - - TArray Outputs; - const int32 Result = ImportBGEO(Tokens[0], PackageParams, Outputs); - - for (UHoudiniOutput* Output : Outputs) - { - Output->RemoveFromRoot(); - } - Outputs.Empty(); - - return Result; - } - - return 0; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoImportCommandlet.h" + +#include "DirectoryWatcherModule.h" +#include "Modules/ModuleManager.h" +#include "Misc/Guid.h" +#include "EditorFramework/AssetImportData.h" + +#include "Editor.h" +#include "FileHelpers.h" + +#include "MessageEndpointBuilder.h" + +#include "PackageTools.h" + +#include "IDirectoryWatcher.h" + +#include "Internationalization/Regex.h" + +#include "Interfaces/ISlateNullRendererModule.h" +#include "Rendering/SlateRenderer.h" +#include "Framework/Application/SlateApplication.h" + +#include "HoudiniPackageParams.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniOutput.h" +#include "HoudiniPDGImporterMessages.h" +#include "HoudiniMeshTranslator.h" +#include "HAL/ThreadManager.h" + + +UHoudiniGeoImportCommandlet::UHoudiniGeoImportCommandlet() +{ + HelpDescription = TEXT("Import BGEOs as UAssets. Includes an option to watch a directories and include new .bgeos created there."); + + HelpUsage = TEXT("HoudiniGeoImport Usage: HoudiniGeoImport {options} [filename.bgeo]"); + // "Options:\n" + // "\t-help or -?\n" + // "\t\tDisplays this help.\n\n" + // "\t-listen=manager_messaging_address\n\n" + // "\t\tListen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager.\n\n" + // "\t-watch=directory\n\n" + // "\t\tA directory to watch for new .bgeo files to import.\n\n" + // "\t-managerpid=owner_pid\n\n" + // "\t\tThe PID of the owner/manager process. If the manager process dies the commandlet also quits.\n\n" + // "\t-bake\n\n" + // "\t\tBake generated assets. Instancers are baked to blueprints. Not supported in -listen mode.\n\n" + // "\t[filename.bgeo]\n" + // "\t\tWhen not using -listen or -watch, the path to a .bgeo file must be specified for import.\n" + //); + + HelpParamNames = { + "help", + "listen", + "guid", + "watch", + "managerpid", + "bake" + }; + + HelpParamDescriptions = { + "Displays this help.", + "Listen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager. Expects the owning process' PID.", + "Specify a GUID for the commandlet. Useful to identify the commandlet when the messaging system is used.", + "A directory to watch for new .bgeo files to import.", + "The PID of the owner/manager process. If the manager process dies the commandlet also quits.", + "Bake generated assets. Instancers are baked to blueprints. Not supported in -listen mode." + }; + + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = true; + ShowProgress = false; + ShowErrorCount = false; + + // LogToConsole = false; + + Mode = EHoudiniGeoImportCommandletMode::None; + bBakeOutputs = false; +} + +void UHoudiniGeoImportCommandlet::PrintUsage() const +{ + HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpDescription); + HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpUsage); + const int32 NumOptions = HelpParamNames.Num(); + for (int32 Idx = 0; Idx < NumOptions; ++Idx) + { + HOUDINI_LOG_DISPLAY(TEXT("-%s\t%s"), *HelpParamNames[Idx], *HelpParamDescriptions[Idx]); + } +} + +void UHoudiniGeoImportCommandlet::PopulatePackageParams(const FString &InBGEOFilename, FHoudiniPackageParams& OutPackageParams) +{ + UObject* InParent = this; + + if (bBakeOutputs) + { + OutPackageParams.PackageMode = EPackageMode::Bake; + } + else + { + OutPackageParams.PackageMode = EPackageMode::CookToTemp; + } + OutPackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + OutPackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + OutPackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + OutPackageParams.HoudiniAssetName = FPaths::GetBaseFilename(InBGEOFilename); + OutPackageParams.HoudiniAssetActorName = FString(); + OutPackageParams.ObjectName = FPaths::GetBaseFilename(InBGEOFilename); + + if (!OutPackageParams.OuterPackage) + { + OutPackageParams.OuterPackage = InParent; + } + + if (!OutPackageParams.ComponentGUID.IsValid()) + { + // TODO: will need to reuse the GUID when reimporting? + OutPackageParams.ComponentGUID = FGuid::NewGuid(); + } + + OutPackageParams.bAttemptToLoadMissingPackages = true; +} + +void UHoudiniGeoImportCommandlet::TickDiscoveredFiles() +{ + for (auto &FileDataEntry : DiscoveredFiles) + { + FDiscoveredFileData &FileData = FileDataEntry.Value; + if (FileData.bImportNextTick && !FileData.bImported) + { + FileData.bImportNextTick = false; + FileData.ImportAttempts++; + + FHoudiniPackageParams PackageParams; + PopulatePackageParams(FileData.FileName, PackageParams); + TArray Outputs; + int32 Error = ImportBGEO(FileData.FileName, PackageParams, Outputs); + if (Error == 0) + { + FileData.bImported = true; + HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Done"), *FileData.FileName); + } + else + { + FileData.bImported = false; + HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Failed (%d)"), *FileData.FileName, Error); + } + } + } +} + +int32 UHoudiniGeoImportCommandlet::MainLoop() +{ + GIsRunning = true; + + IDirectoryWatcher* DirectoryWatcher = nullptr; + + if (Mode == EHoudiniGeoImportCommandletMode::Listen) + { + PDGEndpoint = FMessageEndpoint::Builder("PDG/BGEO Commandlet") + .Handling(this, &UHoudiniGeoImportCommandlet::HandleImportBGEOMessage) + .ReceivingOnThread(ENamedThreads::GameThread); + if (!PDGEndpoint.IsValid()) + { + GIsRunning = false; + return 3; + } + // Notify the manager that we are running + HOUDINI_LOG_DISPLAY(TEXT("Notifying the manager (%s) that we are running"), *ManagerAddress.ToString()); + // Try to send directly to the manager + // TODO: this initially direct message does not work, the address looks to be correct, perhaps there is some + // additional set up needed to connect / discover the endpoints? + PDGEndpoint->Send(new FHoudiniPDGImportBGEODiscoverMessage(Guid), ManagerAddress); + } + else if (Mode == EHoudiniGeoImportCommandletMode::Watch) + { + FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::Get().LoadModuleChecked(TEXT("DirectoryWatcher")); + DirectoryWatcher = DirectoryWatcherModule.Get(); + } + + // In UnrealEngine 4.25 and older we cannot tick the editor engine without slate being initialized. + if (!FSlateApplication::IsInitialized()) + { + FSlateApplication::InitHighDPI(false); + FSlateApplication::Create(); + } + + // If slate is initialized, make sure it has a renderer. If we have to create a renderer, create the null renderer. + if (FSlateApplication::IsInitialized() && !FSlateApplication::Get().GetRenderer()) + { + const TSharedPtr SlateRenderer = FModuleManager::Get().LoadModuleChecked("SlateNullRenderer").CreateSlateNullRenderer(); + const TSharedRef SlateRendererSharedRef = SlateRenderer.ToSharedRef(); + FSlateApplication::Get().InitializeRenderer(SlateRendererSharedRef); + } + + // in listen mode broadcast our presence every 60 seconds + // This is an attempt to test if it solves a rare issue where the endpoints appear to get + // "disconnected" and sending a message to a previously valid message address stops working, even though + // both processes are still running (happens especially when debugging with breakpoints) + const float BroadcastIntervalSeconds = 60.0f; + float LastbroadcastTimeSeconds = 0.0f; + + // main loop + while (GIsRunning && !IsEngineExitRequested()) + { + GEngine->UpdateTimeAndHandleMaxTickRate(); + GEngine->Tick(FApp::GetDeltaTime(), false); + + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().PumpMessages(); + FSlateApplication::Get().Tick(); + } + + // Required for FTimerManager to function - as it blocks ticks, if the frame counter doesn't change + GFrameCounter++; + + // update task graph + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + + FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()); + FThreadManager::Get().Tick(); + GEngine->TickDeferredCommands(); + + if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) + { + // DirectoryWatcher->Tick(FApp::GetDeltaTime()); + + // Process the discovered files + TickDiscoveredFiles(); + } + + if (OwnerProcHandle.IsValid() && !FPlatformProcess::IsProcRunning(OwnerProcHandle)) + { + // Our once valid owner has disappeared, so quit. + RequestEngineExit(TEXT("OwnerDisappeared")); + } + + if (Mode == EHoudiniGeoImportCommandletMode::Listen && PDGEndpoint.IsValid()) + { + const float TimeSeconds = FPlatformTime::Seconds(); + if (TimeSeconds - LastbroadcastTimeSeconds >= BroadcastIntervalSeconds) + { + LastbroadcastTimeSeconds = TimeSeconds; + // Broadcast a discover message to notify that we are still available + PDGEndpoint->Publish(new FHoudiniPDGImportBGEODiscoverMessage(Guid)); + + HOUDINI_LOG_MESSAGE(TEXT("Publishing FHoudiniPDGImportBGEODiscoverMessage(%s)"), *Guid.ToString()); + } + } + + FPlatformProcess::Sleep(0); + } + + PDGEndpoint.Reset(); + if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) + { + DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(DirectoryToWatch, DirectoryWatcherHandle); + } + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Shutdown(); + + GIsRunning = false; + + return 0; +} + +void UHoudiniGeoImportCommandlet::HandleImportBGEOMessage( + const FHoudiniPDGImportBGEOMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_DISPLAY(TEXT("Received BGEO import request from %s"), *InContext->GetSender().ToString()); + + FHoudiniPackageParams PackageParams; + InMessage.PopulatePackageParams(PackageParams); + + // The commandlet must try to load packages if FindPackage fails, since we unload packages when done + PackageParams.bAttemptToLoadMissingPackages = true; + + TArray Outputs; + TMap> OutputObjectAttributes; + TMap InstancedOutputPartData; + if (ImportBGEO(InMessage.FilePath, PackageParams, Outputs, &OutputObjectAttributes, &InstancedOutputPartData) == 0) + { + FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); + (*Reply) = InMessage; + // Reply->PopulateFromPackageParams(PackageParams); + Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Success; + + const int32 NumOutputs = Outputs.Num(); + Reply->Outputs.Init(FHoudiniPDGImportNodeOutput(), NumOutputs); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + FHoudiniPDGImportNodeOutput &MessageOutput = Reply->Outputs[Index]; + UHoudiniOutput* Output = Outputs[Index]; + for (const FHoudiniGeoPartObject& HGPO : Output->GetHoudiniGeoPartObjects()) + { + HOUDINI_LOG_WARNING(TEXT("HGPO %d %d %d"), HGPO.ObjectId, HGPO.GeoId, HGPO.PartId); + MessageOutput.HoudiniGeoPartObjects.Add(HGPO); + + // Get instancer data if this is an instancer output + if (Output->GetType() == EHoudiniOutputType::Instancer) + { + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = HGPO.ObjectId; + OutputIdentifier.GeoId = HGPO.GeoId; + OutputIdentifier.PartId = HGPO.PartId; + OutputIdentifier.PartName = HGPO.PartName; + + FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = InstancedOutputPartData.Find(OutputIdentifier); + if (InstancedOutputPartDataPtr) + { + InstancedOutputPartDataPtr->BuildFlatInstancedTransformsAndObjectPaths(); + MessageOutput.InstancedOutputPartData.Add(*InstancedOutputPartDataPtr); + } + else + { + MessageOutput.InstancedOutputPartData.Add(FHoudiniInstancedOutputPartData()); + } + } + } + for (const auto& Entry : Output->GetOutputObjects()) + { + HOUDINI_LOG_WARNING(TEXT("Identifier %d %d %d"), Entry.Key.ObjectId, Entry.Key.GeoId, Entry.Key.PartId); + + MessageOutput.OutputObjects.AddDefaulted(); + FHoudiniPDGImportNodeOutputObject& MessageOutputObject = MessageOutput.OutputObjects.Last(); + + FString PackagePath = IsValid(Entry.Value.OutputObject) ? Entry.Value.OutputObject->GetPathName() : ""; + MessageOutputObject.Identifier = Entry.Key; + MessageOutputObject.PackagePath = PackagePath; + const TArray* PropertyAttributes = OutputObjectAttributes.Find(Entry.Key); + if (PropertyAttributes) + MessageOutputObject.GenericAttributes = *PropertyAttributes; + MessageOutputObject.CachedAttributes = Entry.Value.CachedAttributes; + } + } + + PDGEndpoint->Send(Reply, InContext->GetSender()); + } + else + { + HOUDINI_LOG_WARNING(TEXT("BGEO import failed.")); + FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); + Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Failed; + PDGEndpoint->Send(Reply, InContext->GetSender()); + } + + // Cleanup the outputs (remove from root) + TArray PackagesToUnload; + for (UHoudiniOutput *CurOutput : Outputs) + { + if (!IsValid(CurOutput)) + continue; + + for (const auto& Entry : CurOutput->GetOutputObjects()) + { + if (IsValid(Entry.Value.OutputObject)) + { + UPackage *Outermost = Entry.Value.OutputObject->GetOutermost(); + if (IsValid(Outermost)) + { + PackagesToUnload.Add(Outermost); + } + + Entry.Value.OutputObject->RemoveFromRoot(); + } + } + + CurOutput->RemoveFromRoot(); + } + Outputs.Empty(); + OutputObjectAttributes.Empty(); + + if (PackagesToUnload.Num() > 0) + { + HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ..."), PackagesToUnload.Num()); + FText ErrorMessage; + if (!UPackageTools::UnloadPackages(PackagesToUnload, ErrorMessage)) + { + HOUDINI_LOG_WARNING(TEXT("Unload packages failed: %s"), *ErrorMessage.ToString()); + } + else + { + HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ... Success"), PackagesToUnload.Num()); + } + PackagesToUnload.Empty(); + } + + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); +} + +bool UHoudiniGeoImportCommandlet::StartHoudiniEngineSession() +{ + // Start Houdini Engine session + HOUDINI_LOG_DISPLAY(TEXT("Starting Houdini Engine session...")); + FHoudiniEngine& HoudiniEngine = FHoudiniEngine::Get(); + if (!HoudiniEngine.CreateSession( + EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe, + "hapi_bgeo_cmdlet")) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start Houdini Engine session.")); + return false; + } + + return true; +} + +int32 UHoudiniGeoImportCommandlet::ImportBGEO( + const FString &InFilename, + const FHoudiniPackageParams &InPackageParams, + TArray& OutOutputs, + TMap>* OutGenericAttributes, + TMap* OutInstancedOutputPartData) +{ + if (!IsHoudiniEngineSessionRunning() && !StartHoudiniEngineSession()) + { + return 2; + } + + FHoudiniPackageParams PackageParams = InPackageParams; + UHoudiniGeoImporter* GeoImporter = NewObject(this); + + TArray OldOutputs; + OutOutputs.Empty(); + + // 2. Update the file paths + HOUDINI_LOG_DISPLAY(TEXT("SetFilePath %s"), *InFilename); + if (!GeoImporter->SetFilePath(InFilename)) + return 1; + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + HOUDINI_LOG_DISPLAY(TEXT("LoadBGEOFileInHAPI")); + if (!GeoImporter->LoadBGEOFileInHAPI(NodeId)) + return 1; + + // Look for a bake folder override in the BGEO file + if (PackageParams.PackageMode == EPackageMode::Bake) + { + HOUDINI_LOG_DISPLAY(TEXT("Looking for bake folder override attribute...")); + // Get the geo id for the node id + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), NodeId, &DisplayGeoInfo)) + { + FString BakeFolderOverride; + const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); + if (bFoundOverride && !BakeFolderOverride.IsEmpty()) + { + PackageParams.BakeFolder = BakeFolderOverride; + HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override: %s"), *PackageParams.BakeFolder); + } + else + { + HOUDINI_LOG_DISPLAY(TEXT("No bake folder override, using: %s"), *PackageParams.BakeFolder); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could not find display geo node id (when looking for bake folder override).")); + } + } + + auto CleanUpAndExit = [&OutOutputs, GeoImporter, NodeId](int32 InExitCode) + { + GeoImporter->GetOutputObjects().Empty(); + for (UHoudiniOutput* Output : OutOutputs) + { + Output->RemoveFromRoot(); + } + OutOutputs.Empty(); + + if (NodeId >= 0) + GeoImporter->DeleteCreatedNode(NodeId); + + return InExitCode; + }; + + // 4. Get the output from the file node + HOUDINI_LOG_DISPLAY(TEXT("BuildOutputsForNode %d"), NodeId); + if (!GeoImporter->BuildOutputsForNode(NodeId, OldOutputs, OutOutputs)) + return CleanUpAndExit(1); + + // Create uniquely named packages, commandlet runs in conjunction + // with a main editor instance, so we cannot modify existing files + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + // FString PackageName; + // UPackage* Outer = PackageParams.CreatePackageForObject(PackageName); + UObject* Outer = this; + + // 5. Create the static meshes in the outputs + HOUDINI_LOG_DISPLAY(TEXT("Create Static Meshes")); + if (!GeoImporter->CreateStaticMeshes(OutOutputs, Outer, PackageParams)) + return CleanUpAndExit(1); + + //// 6. Create the landscape in the outputs + //if (!GeoImporter->CreateLandscapes(NewOutputs, Outer, PackageParams)) + // return CleanUpAndExit(1); + + // 7. Create the instancers in the outputs + if (OutInstancedOutputPartData) + { + if (!GeoImporter->CreateInstancerOutputPartData(OutOutputs, *OutInstancedOutputPartData)) + return CleanUpAndExit(1); + } + else + { + if (!GeoImporter->CreateInstancers(OutOutputs, Outer, PackageParams)) + return CleanUpAndExit(1); + } + + if (OutGenericAttributes) + { + // Collect all generic properties from Houdini, we need to pass these + // through to PDG manager + HOUDINI_LOG_DISPLAY(TEXT("Get Generic Attributes for static meshes")); + for (UHoudiniOutput* CurOutput : OutOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (const auto& Entry : CurOutput->GetOutputObjects()) + { + const FHoudiniOutputObjectIdentifier OutputIdentifier = Entry.Key; + TArray PropertyAttributes; + FHoudiniMeshTranslator::GetGenericPropertiesAttributes( + OutputIdentifier.GeoId, OutputIdentifier.PartId, + OutputIdentifier.PointIndex, OutputIdentifier.PrimitiveIndex, + PropertyAttributes); + OutGenericAttributes->Add(OutputIdentifier, PropertyAttributes); + } + } + } + + // 8. Delete the created node in Houdini + HOUDINI_LOG_DISPLAY(TEXT("DeleteCreatedNode %d"), NodeId); + if (!GeoImporter->DeleteCreatedNode(NodeId)) + { + // Not good, but not fatal.. + //return false; + } + + TArray PackagesToSave; + TArray& OutputObjects = GeoImporter->GetOutputObjects(); + for (UObject* Object : OutputObjects) + { + if (!IsValid(Object)) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("Created object: %s"), *Object->GetFullName()); + + UAssetImportData* AssetImportData = nullptr; + if (Object->IsA()) + { + UStaticMesh* SM = Cast(Object); + AssetImportData = SM->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); + SM->AssetImportData = AssetImportData; + } + } + + if (AssetImportData) + AssetImportData->Update(InFilename); + + Object->MarkPackageDirty(); + Object->PostEditChange(); + + UPackage* Package = Object->GetOutermost(); + if (IsValid(Package)) + { + PackagesToSave.AddUnique(Package); + } + } + + if (PackagesToSave.Num() > 0) + { + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); + } + + PackagesToSave.Empty(); + OutputObjects.Empty(); + + return 0; +} + +void UHoudiniGeoImportCommandlet::HandleDirectoryChanged(const TArray& InFileChangeDatas) +{ + const FRegexPattern BGEOPattern(TEXT(R"((.*)\.(bgeo(\.[^\.]*)?)$)")); + + for (const FFileChangeData& FileChangeData : InFileChangeDatas) + { + HOUDINI_LOG_MESSAGE(TEXT("HandleDirectoryChanged %d %s"), FileChangeData.Action, *FileChangeData.Filename); + + FRegexMatcher BGEOMatcher(BGEOPattern, FileChangeData.Filename.ToLower()); + if (BGEOMatcher.FindNext() && BGEOMatcher.GetCaptureGroup(2).StartsWith(TEXT("bgeo"))) + { + HOUDINI_LOG_DISPLAY(TEXT("Updating entry for %s..."), *FileChangeData.Filename); + const uint32 MaxImportAttempts = 3; + switch(FileChangeData.Action) + { + case FFileChangeData::FCA_Added: + case FFileChangeData::FCA_Modified: + if (DiscoveredFiles.Contains(FileChangeData.Filename)) + { + FDiscoveredFileData &FileData = DiscoveredFiles[FileChangeData.Filename]; + if (!FileData.bImported && FileData.ImportAttempts < MaxImportAttempts) + FileData.bImportNextTick = true; + else if (FileData.ImportAttempts >= MaxImportAttempts) + HOUDINI_LOG_WARNING(TEXT("Not importing %s, max attempts exceeded %d"), *FileData.FileName, FileData.ImportAttempts); + } + else + { + DiscoveredFiles.Add(FileChangeData.Filename, FDiscoveredFileData(FileChangeData.Filename, true)); + } + break; + case FFileChangeData::FCA_Removed: + DiscoveredFiles.Remove(FileChangeData.Filename); + break; + default: + HOUDINI_LOG_WARNING(TEXT("Unknown file change event %d for %s"), FileChangeData.Action, *FileChangeData.Filename); + } + } + } +} + +int32 UHoudiniGeoImportCommandlet::Main(const FString& InParams) +{ + TArray Tokens; + TArray Switches; + TMap Params; + ParseCommandLine(*InParams, Tokens, Switches, Params); + + if (Switches.Contains(TEXT("help")) || Switches.Contains(TEXT("?"))) + { + PrintUsage(); + return 0; + } + + if (Params.Contains(TEXT("guid"))) + { + const FString GuidStr = Params.FindChecked(TEXT("guid")); + FGuid::Parse(GuidStr, Guid); + + HOUDINI_LOG_DISPLAY(TEXT("GUID received: %s"), *Guid.ToString()); + } + else + { + Guid = FGuid::NewGuid(); + } + + // Set bake mode + if (Switches.Contains(TEXT("bake"))) + bBakeOutputs = true; + else + bBakeOutputs = false; + + if (Params.Contains(TEXT("listen"))) + { + Mode = EHoudiniGeoImportCommandletMode::Listen; + + if (!Params.Contains(TEXT("managerpid"))) + { + HOUDINI_LOG_ERROR(TEXT("'managerpid' is required when in -listen mode.")); + return 1; + } + + if (bBakeOutputs) + { + HOUDINI_LOG_ERROR(TEXT("'listen' mode does not support baking outputs (-bake).")); + return 1; + } + + // Get the manager's messaging address from the -listen param + const FString ManagerAddressStr = Params.FindChecked(TEXT("listen")); + if (!FMessageAddress::Parse(ManagerAddressStr, ManagerAddress)) + { + HOUDINI_LOG_ERROR(TEXT("The manager messaging address passed to -listen=%s is invalid."), *ManagerAddressStr); + return 1; + } + + // Get the manager pid and proc handle + uint32 OwnerProcessId = FCString::Strtoi(*Params.FindChecked(TEXT("managerpid")), nullptr, 10); + HOUDINI_LOG_DISPLAY(TEXT("Owner process Id: %d"), OwnerProcessId); + OwnerProcHandle = FPlatformProcess::OpenProcess(OwnerProcessId); + + return MainLoop(); + } + else if (Params.Contains(TEXT("watch"))) + { + Mode = EHoudiniGeoImportCommandletMode::Watch; + + HOUDINI_LOG_DISPLAY(TEXT("directory watch mode")); + FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); + IDirectoryWatcher* DirectoryWatcher = DirectoryWatcherModule.Get(); + if (DirectoryWatcher) + { + DirectoryToWatch = Params.FindChecked(TEXT("watch")); + if (FPaths::IsRelative(DirectoryToWatch)) + DirectoryToWatch = FPaths::ConvertRelativePathToFull(DirectoryToWatch); + + HOUDINI_LOG_DISPLAY(TEXT("Watching %s"), *DirectoryToWatch); + + DirectoryWatcher->RegisterDirectoryChangedCallback_Handle( + DirectoryToWatch, + IDirectoryWatcher::FDirectoryChanged::CreateUObject(this, &UHoudiniGeoImportCommandlet::HandleDirectoryChanged), + DirectoryWatcherHandle); + + return MainLoop(); + } + else + { + return 10; + } + } + else if (Tokens.Num() > 0) + { + Mode = EHoudiniGeoImportCommandletMode::SpecifiedFiles; + + if (!StartHoudiniEngineSession()) + return 2; + + const FString Filename = FPaths::IsRelative(Tokens[0]) ? FPaths::ConvertRelativePathToFull(Tokens[0]) : Tokens[0]; + FHoudiniPackageParams PackageParams; + PopulatePackageParams(Filename, PackageParams); + + TArray Outputs; + const int32 Result = ImportBGEO(Tokens[0], PackageParams, Outputs); + + for (UHoudiniOutput* Output : Outputs) + { + Output->RemoveFromRoot(); + } + Outputs.Empty(); + + return Result; + } + + return 0; +} diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h index d4293bb64..7ddae2e48 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h @@ -1,153 +1,153 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "Commandlets/Commandlet.h" -#include "MessageEndpoint.h" - -#include "HoudiniEngine.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPDGImporterMessages.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniGeoImportCommandlet.generated.h" - -class FSocket; - -class UHoudiniGeoImporter; -class UHoudiniOutput; - -struct FHoudiniPackageParams; - -enum class EHoudiniGeoImportCommandletMode : uint8 -{ - // Unspecified - None, - // Import of specified file - SpecifiedFiles, - // Directory watch mode - Watch, - // Listen mode (via PDGManager) - Listen -}; - -struct FDiscoveredFileData -{ -public: - FDiscoveredFileData() : FileName(), bImportNextTick(false), ImportAttempts(0), bImported(false) {} - - FDiscoveredFileData(const FString& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} - - FDiscoveredFileData(FString&& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} - - // Full/absolute file path - FString FileName; - - // Try to import this file on the next tick - bool bImportNextTick; - - // Number of attempts at importing this file - uint32 ImportAttempts; - - // The file has been imported successfully - bool bImported; -}; - -UCLASS() -class HOUDINIENGINE_API UHoudiniGeoImportCommandlet : public UCommandlet -{ - GENERATED_BODY() - -public: - - UHoudiniGeoImportCommandlet(); - - void PrintUsage() const; - - /** - * Entry point for your commandlet - * - * @param Params the string containing the parameters for the commandlet - */ - virtual int32 Main(const FString& Params) override; - - void HandleImportBGEOMessage( - const struct FHoudiniPDGImportBGEOMessage& InMessage, - const TSharedRef& InContext); - - void HandleDirectoryChanged(const TArray& InFileChangeDatas); - -protected: - - void PopulatePackageParams(const FString& InBGEOFilename, FHoudiniPackageParams& OutPackageParams); - - bool StartHoudiniEngineSession(); - - bool IsHoudiniEngineSessionRunning() { return FHoudiniEngine::Get().GetSession() != nullptr; }; - - int32 MainLoop(); - - int32 ImportBGEO( - const FString& InFilename, - const FHoudiniPackageParams& InPackageParams, - TArray& OutOutputs, - TMap>* OutGenericAttributes=nullptr, - TMap* OutInstancedOutputPartData=nullptr); - - void TickDiscoveredFiles(); - -private: - - // Messaging end point for receiving messages from PDG manager - TSharedPtr PDGEndpoint; - - // The messaging address of the manager - FMessageAddress ManagerAddress; - - // Unique ID of the commandlet. - FGuid Guid; - - // The proc handle of our owner (if in listen mode, quit when the owner stops running). - FProcHandle OwnerProcHandle; - - // TODO: Map so that we can watch multiple directories? - // Directory to watch - FString DirectoryToWatch; - // Handle if we are watching a directory for changes. - FDelegateHandle DirectoryWatcherHandle; - - // Keep track of files discovered by the watcher, and their state - TMap DiscoveredFiles; - - // Mode in which commandlet is running - EHoudiniGeoImportCommandletMode Mode; - - // Bake outputs via FHoudiniEngineBakeUtils - bool bBakeOutputs; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Commandlets/Commandlet.h" +#include "MessageEndpoint.h" + +#include "HoudiniEngine.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPDGImporterMessages.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniGeoImportCommandlet.generated.h" + +class FSocket; + +class UHoudiniGeoImporter; +class UHoudiniOutput; + +struct FHoudiniPackageParams; + +enum class EHoudiniGeoImportCommandletMode : uint8 +{ + // Unspecified + None, + // Import of specified file + SpecifiedFiles, + // Directory watch mode + Watch, + // Listen mode (via PDGManager) + Listen +}; + +struct FDiscoveredFileData +{ +public: + FDiscoveredFileData() : FileName(), bImportNextTick(false), ImportAttempts(0), bImported(false) {} + + FDiscoveredFileData(const FString& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} + + FDiscoveredFileData(FString&& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} + + // Full/absolute file path + FString FileName; + + // Try to import this file on the next tick + bool bImportNextTick; + + // Number of attempts at importing this file + uint32 ImportAttempts; + + // The file has been imported successfully + bool bImported; +}; + +UCLASS() +class HOUDINIENGINE_API UHoudiniGeoImportCommandlet : public UCommandlet +{ + GENERATED_BODY() + +public: + + UHoudiniGeoImportCommandlet(); + + void PrintUsage() const; + + /** + * Entry point for your commandlet + * + * @param Params the string containing the parameters for the commandlet + */ + virtual int32 Main(const FString& Params) override; + + void HandleImportBGEOMessage( + const struct FHoudiniPDGImportBGEOMessage& InMessage, + const TSharedRef& InContext); + + void HandleDirectoryChanged(const TArray& InFileChangeDatas); + +protected: + + void PopulatePackageParams(const FString& InBGEOFilename, FHoudiniPackageParams& OutPackageParams); + + bool StartHoudiniEngineSession(); + + bool IsHoudiniEngineSessionRunning() { return FHoudiniEngine::Get().GetSession() != nullptr; }; + + int32 MainLoop(); + + int32 ImportBGEO( + const FString& InFilename, + const FHoudiniPackageParams& InPackageParams, + TArray& OutOutputs, + TMap>* OutGenericAttributes=nullptr, + TMap* OutInstancedOutputPartData=nullptr); + + void TickDiscoveredFiles(); + +private: + + // Messaging end point for receiving messages from PDG manager + TSharedPtr PDGEndpoint; + + // The messaging address of the manager + FMessageAddress ManagerAddress; + + // Unique ID of the commandlet. + FGuid Guid; + + // The proc handle of our owner (if in listen mode, quit when the owner stops running). + FProcHandle OwnerProcHandle; + + // TODO: Map so that we can watch multiple directories? + // Directory to watch + FString DirectoryToWatch; + // Handle if we are watching a directory for changes. + FDelegateHandle DirectoryWatcherHandle; + + // Keep track of files discovered by the watcher, and their state + TMap DiscoveredFiles; + + // Mode in which commandlet is running + EHoudiniGeoImportCommandletMode Mode; + + // Bake outputs via FHoudiniEngineBakeUtils + bool bBakeOutputs; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp index 69424ffcf..0452b892d 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp @@ -1,834 +1,842 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoImporter.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniPackageParams.h" -#include "HoudiniOutput.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniSplineComponent.h" - -#include "CoreMinimal.h" -#include "Misc/Paths.h" -#include "Misc/PackageName.h" -#include "UObject/Package.h" -#include "PackageTools.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "Editor.h" - -#include "Materials/MaterialInterface.h" -#include "Materials/Material.h" - - -UHoudiniGeoImporter::UHoudiniGeoImporter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , SourceFilePath() - , AbsoluteFilePath() - , AbsoluteFileDirectory() - , FileName() - , FileExtension() - , BakeRootFolder(TEXT("/Game/HoudiniEngine/Bake/")) -{ - /* - SourceFilePath = FString(); - - AbsoluteFilePath = FString(); - AbsoluteFileDirectory = FString(); - FileName = FString(); - FileExtension = FString(); - - OutputFilename = FString(); - BakeRootFolder = TEXT("/Game/HoudiniEngine/Bake/"); - */ -} - -bool -UHoudiniGeoImporter::SetFilePath(const FString& InFilePath) -{ - SourceFilePath = InFilePath; - if (!FPaths::FileExists(SourceFilePath)) - { - // Cant find BGEO file - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InFilePath); - return false; - } - - // Make sure we're using absolute path! - AbsoluteFilePath = FPaths::ConvertRelativePathToFull(SourceFilePath); - - // Split the file path - FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); - - // Handle .bgeo.sc correctly - if (FileExtension.Equals(TEXT("sc"))) - { - // append the bgeo to .sc - FileExtension = FPaths::GetExtension(FileName) +TEXT(".") + FileExtension; - // update the filename - FileName = FPaths::GetBaseFilename(FileName); - } - - if (FileExtension.IsEmpty()) - FileExtension = TEXT("bgeo"); - - if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) - { - // Not a bgeo file! - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *SourceFilePath); - return false; - } - - //BGEOFilePath = BGEOPath + TEXT("/") + BGEOFileName + TEXT(".") + BGEOExtension; - - // Only use "/" for the output file path - BakeRootFolder.ReplaceInline(TEXT("\\"), TEXT("/")); - // Make sure the output folder ends with a "/" - if (!BakeRootFolder.EndsWith("/")) - BakeRootFolder += TEXT("/"); - - // If we have't specified an outpout file name yet, use the input file name - if (OutputFilename.IsEmpty()) - OutputFilename = FileName; - - return true; -} - -bool -UHoudiniGeoImporter::AutoStartHoudiniEngineSessionIfNeeded() -{ - if (FHoudiniEngine::Get().GetSession()) - return true; - - // Default first session already attempted to be created ? stop here? - /* - if (FHoudiniEngine::Get().GetFirstSessionCreated()) - return false; - */ - - // Indicates that we've tried to start a session once no matter if it failed or succeed - FHoudiniEngine::Get().SetFirstSessionCreated(true); - if (!FHoudiniEngine::Get().RestartSession()) - { - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Couldn't start the default HoudiniEngine session!")); - return false; - } - - return true; -} - -bool -UHoudiniGeoImporter::BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs) -{ - FString Notification = TEXT("BGEO Importer: Getting output geos..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - const bool bInAddOutputsToRootSet = true; - return BuildAllOutputsForNode(InNodeId, this, InOldOutputs, OutNewOutputs, bInAddOutputsToRootSet); -} - -bool -UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - FString Notification = TEXT("BGEO Importer: Creating Static Meshes..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - //FHoudiniMeshTranslator::CreateAllMeshesFromHoudiniOutput(CurOutput, OuterPackage, OuterComponent, OuterAsset); - - TMap NewOutputObjects; - TMap OldOutputObjects = CurOutput->GetOutputObjects(); - TMap& AssignementMaterials = CurOutput->GetAssignementMaterials(); - TMap& ReplacementMaterials = CurOutput->GetReplacementMaterials(); - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - // Not a mesh, skip - if (CurHGPO.Type != EHoudiniPartType::Mesh) - continue; - - // Check for a unreal_output_name if we are in bake mode - FHoudiniPackageParams PackageParams(InPackageParams); - if (PackageParams.PackageMode == EPackageMode::Bake) - { - TArray OutputNames; - if (FHoudiniEngineUtils::GetOutputNameAttribute(CurHGPO.GeoId, CurHGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - PackageParams.ObjectName = OutputNames[0]; - } - } - } - - FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( - CurHGPO, - PackageParams, - OldOutputObjects, - NewOutputObjects, - AssignementMaterials, - ReplacementMaterials, - true, - EHoudiniStaticMeshMethod::RawMesh); - } - - // Add all output objects and materials - for (auto CurOutputPair : NewOutputObjects) - { - UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - - // Do the same for materials - for (auto CurAssignmentMatPair : AssignementMaterials) - { - UObject* CurObj = CurAssignmentMatPair.Value; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - - // Also assign to the output objects map as we may need the meshes to create instancers later - CurOutput->SetOutputObjects(NewOutputObjects); - } - - return true; -} - - -bool -UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - TArray CurveOutputs; - CurveOutputs.Reserve(InOutputs.Num()); - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Curve) - continue; - - CurveOutputs.Add(CurOutput); - break; - } - - FString Notification = TEXT("BGEO Importer: Creating Curves..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - // Look for the first unreal_output_name attribute on the curve outputs and use that - // for ObjectName - FHoudiniPackageParams PackageParams(InPackageParams); - for (auto& CurOutput : CurveOutputs) - { - bool bFoundOutputName = false; - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Curve) - continue; - - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - break; - } - } - } - - if (bFoundOutputName) - break; - } - - // Create a Package for the BP - PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - FString PackageName; - UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); - check(BPPackage); - - // Create and init a new Blueprint Actor - UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); - if (!Blueprint) - return false; - - // Create a fake outer component that we'll use as a temporary outer for our curves - UWorld* TempWorld = UWorld::CreateWorld(EWorldType::Inactive, false, TEXT("BGEOImporterTemp"), GetTransientPackage(), false); - const FActorSpawnParameters ActorSpawnParameters; - AActor* OuterActor = TempWorld->SpawnActor(ActorSpawnParameters); - USceneComponent* OuterComponent = - NewObject(OuterActor, USceneComponent::GetDefaultSceneRootVariableName()); - - for (auto& CurOutput : CurveOutputs) - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); - - // Prepare an ActorComponent array for AddComponentsToBlueprint() - TArray OutputComp; - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputComp.Add(CurObj); - } - - // Transfer all the instancer components to the BP - if (OutputComp.Num() > 0) - { - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, FKismetEditorUtilities::EAddComponentToBPHarvestMode::None, nullptr, false); - } - } - - // Compile the blueprint - FKismetEditorUtilities::CompileBlueprint(Blueprint); - - // Add it to our output objects - OutputObjects.Add(Blueprint); - - return true; -} - - -bool -UHoudiniGeoImporter::CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - HOUDINI_LOG_WARNING(TEXT("Importing a landscape directly from BGEOs is not currently supported.")); - return false; - } - - return true; - - /* - // Before processing any of the output, - // we need to get the min/max value for all Height volumes in this output (if any) - float HoudiniHeightfieldOutputsGlobalMin = 0.f; - float HoudiniHeightfieldOutputsGlobalMax = 0.f; - FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax(InOutputs, HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax); - - UWorld* PersistentWorld = InParent->GetWorld(); - if(!PersistentWorld) - PersistentWorld = GEditor ? GEditor->GetEditorWorldContext(false).World() : nullptr; - - if (!PersistentWorld) - return false; - - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - FString Notification = TEXT("BGEO Importer: Creating Landscapes..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - TArray EmptyInputLandscapes; - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(CurOutput); - - bool bCreatedNewMaps = false; - ERuntimePackageMode RuntimePackageMode = ERuntimePackageMode::CookToTemp; - switch(InPackageParams.PackageMode) - { - - case EPackageMode::Bake: - RuntimePackageMode = ERuntimePackageMode::Bake; - break; - case EPackageMode::CookToLevel: - case EPackageMode::CookToTemp: - default: - RuntimePackageMode = ERuntimePackageMode::CookToTemp; - break; - } - TArray> CreatedUntrackedOutputs; - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - CreatedUntrackedOutputs, - EmptyInputLandscapes, - EmptyInputLandscapes, - HAC, - TEXT("{object_name}_"), - PersistentWorld, - HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax, - InPackageParams, bCreatedNewMaps, - RuntimePackageMode); - - // Add all output objects - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - } - - return true; - */ -} - - -bool -UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - bool HasInstancer = false; - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - HasInstancer = true; - break; - } - - if (!HasInstancer) - return true; - - FString Notification = TEXT("BGEO Importer: Creating Instancers..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - // Look for the first unreal_output_name attribute on the instancer outputs and use that - // for ObjectName - FHoudiniPackageParams PackageParams(InPackageParams); - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - bool bFoundOutputName = false; - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Instancer) - continue; - - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - break; - } - } - } - - if (bFoundOutputName) - break; - } - - // Create a Package for the BP - PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - FString PackageName; - UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); - check(BPPackage); - - // Create and init a new Blueprint Actor - UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); - if (!Blueprint) - return false; - - // Create a fake outer component that we'll use as a temporary outer for our instancers - USceneComponent* OuterComponent = NewObject(); - - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - // Create all the instancers and attach them to a fake outer component - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - CurOutput, InOutputs, OuterComponent); - - // Prepare an ActorComponent array for AddComponentsToBlueprint() - TArray OutputComp; - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputComp.Add(CurObj); - } - - // Transfer all the instancer components to the BP - if (OutputComp.Num() > 0) - { - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, FKismetEditorUtilities::EAddComponentToBPHarvestMode::None, nullptr, false); - } - } - - // Compile the blueprint - FKismetEditorUtilities::CompileBlueprint(Blueprint); - - // Add it to our output objects - OutputObjects.Add(Blueprint); - - return true; -} - -bool -UHoudiniGeoImporter::CreateInstancerOutputPartData( - TArray& InOutputs, - TMap& OutInstancedOutputPartData) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = HGPO.ObjectId; - OutputIdentifier.GeoId = HGPO.GeoId; - OutputIdentifier.PartId = HGPO.PartId; - OutputIdentifier.PartName = HGPO.PartName; - - OutInstancedOutputPartData.Add(OutputIdentifier, FHoudiniInstancedOutputPartData()); - FHoudiniInstancedOutputPartData *InstancedOutputData = OutInstancedOutputPartData.Find(OutputIdentifier); - // Create all the instancers and attach them to a fake outer component - if (!FHoudiniInstanceTranslator::PopulateInstancedOutputPartData(HGPO, InOutputs, *InstancedOutputData)) - return false; - } - } - - return true; -} - -bool -UHoudiniGeoImporter::DeleteCreatedNode(const HAPI_NodeId& InNodeId) -{ - if (InNodeId < 0) - return false; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), InNodeId)) - { - // Could not delete the bgeo's file sop ! - HOUDINI_LOG_WARNING(TEXT("Houdini GEO Importer: Could not delete HAPI File SOP.")); - return false; - } - - return true; -} - -bool -UHoudiniGeoImporter::ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams) -{ - if (InBGEOFile.IsEmpty()) - return false; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!AutoStartHoudiniEngineSessionIfNeeded()) - return false; - - // 2. Update the file paths - if (!SetFilePath(InBGEOFile)) - return false; - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - if (!LoadBGEOFileInHAPI(NodeId)) - return false; - - // 4. Get the output from the file node - TArray NewOutputs; - TArray OldOutputs; - if (!BuildOutputsForNode(NodeId, OldOutputs, NewOutputs)) - return false; - - // Failure lambda - auto CleanUpAndReturn = [&NewOutputs](const bool& bReturnValue) - { - // Remove the output objects from the root set before returning false - for (auto Out : NewOutputs) - Out->RemoveFromRoot(); - - return bReturnValue; - }; - - // Prepare the package used for creating the mesh, landscape and instancer pacakges - FHoudiniPackageParams PackageParams; - if (InPackageParams) - { - PackageParams = *InPackageParams; - } - else - { - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.HoudiniAssetName = FString(); - PackageParams.HoudiniAssetActorName = FString(); - PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); - } - - if (!PackageParams.OuterPackage) - { - PackageParams.OuterPackage = InParent; - } - - if (!PackageParams.ComponentGUID.IsValid()) - { - // TODO: will need to reuse the GUID when reimporting? - PackageParams.ComponentGUID = FGuid::NewGuid(); - } - - // 5. Create the static meshes in the outputs - if (!CreateStaticMeshes(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 6. Create the static meshes in the outputs - if (!CreateCurves(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 7. Create the landscape in the outputs - if (!CreateLandscapes(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 8. Create the instancers in the outputs - if (!CreateInstancers(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 9. Delete the created node in Houdini - if (!DeleteCreatedNode(NodeId)) - return CleanUpAndReturn(false); - - // Clean up and return true - return CleanUpAndReturn(true); -} - -bool -UHoudiniGeoImporter::OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition) -{ - if (InBGEOFile.IsEmpty()) - return false; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!AutoStartHoudiniEngineSessionIfNeeded()) - return false; - - if (!FPaths::FileExists(InBGEOFile)) - { - // Cant find BGEO file - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InBGEOFile); - return false; - } - - // Make sure we're using absolute path! - const FString AbsoluteFilePath = FPaths::ConvertRelativePathToFull(InBGEOFile); - - if (AbsoluteFilePath.IsEmpty()) - return false; - - FString AbsoluteFileDirectory; - FString FileName; - FString FileExtension; - - // Split the file path - FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); - - // Handle .bgeo.sc correctly - if (FileExtension.Equals(TEXT("sc"))) - { - // append the bgeo to .sc - FileExtension = FPaths::GetExtension(FileName) + TEXT(".") + FileExtension; - // update the filename - FileName = FPaths::GetBaseFilename(FileName); - } - - if (FileExtension.IsEmpty()) - FileExtension = TEXT("bgeo"); - - if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) - { - // Not a bgeo file! - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *InBGEOFile); - return false; - } - - OutNodeId = -1; - - // Check HoudiniEngine / HAPI init? - if (!FHoudiniEngine::IsInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); - return false; - } - - FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); - - // Create a file SOP - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, "SOP/file", "bgeo", true, &OutNodeId), false); - - // Set the file path parameter - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - OutNodeId, "file", &ParmId), false); - - const std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str(), ParmId, 0), false); - - return true; -} - -bool -UHoudiniGeoImporter::CloseBGEOFile(const HAPI_NodeId& InNodeId) -{ - // 8. Delete the created node in Houdini - if (!DeleteCreatedNode(InNodeId)) - return false; - - return true; -} - -bool -UHoudiniGeoImporter::LoadBGEOFileInHAPI(HAPI_NodeId& NodeId) -{ - NodeId = -1; - - if (AbsoluteFilePath.IsEmpty()) - return false; - - // Check HoudiniEngine / HAPI init? - if (!FHoudiniEngine::IsInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); - return false; - } - - FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); - - // Create a file SOP - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, "SOP/file", "bgeo", true, &NodeId), false); - - // Set the file path parameter - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, "file", &ParmId), false); - - std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, 0), false); - - return CookFileNode(NodeId); -} - -bool -UHoudiniGeoImporter::CookFileNode(const HAPI_NodeId& InNodeId) -{ - // Cook the node - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); - - // Wait for the cook to finish - int32 status = HAPI_STATE_MAX_READY_STATE + 1; - while (status > HAPI_STATE_MAX_READY_STATE) - { - // Retrieve the status - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), - HAPI_STATUS_COOK_STATE, &status), false); - - FString StatusString = FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); - HOUDINI_LOG_MESSAGE(TEXT("Still Cooking, current status: %s."), *StatusString); - - // Go to bed.. - if (status > HAPI_STATE_MAX_READY_STATE) - FPlatformProcess::Sleep(0.5f); - } - - if (status != HAPI_STATE_READY) - { - // There was some cook errors - HOUDINI_LOG_ERROR(TEXT("Finished Cooking with errors!")); - return false; - } - - HOUDINI_LOG_MESSAGE(TEXT("Finished Cooking!")); - - return true; -} - -bool -UHoudiniGeoImporter::BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet) -{ - // TArray OldOutputs; - if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, InOldOutputs, OutNewOutputs, false)) - { - // Couldn't create the package - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Failed to process the File SOP's outputs!")); - return false; - } - - if (bInAddOutputsToRootSet) - { - // Add the output objects to the RootSet to prevent them from being GCed - for (auto& Out : OutNewOutputs) - Out->AddToRoot(); - } - - return true; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoImporter.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniPackageParams.h" +#include "HoudiniOutput.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniSplineComponent.h" + +#include "CoreMinimal.h" +#include "Misc/Paths.h" +#include "Misc/PackageName.h" +#include "UObject/Package.h" +#include "PackageTools.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "Editor.h" + +#include "Materials/MaterialInterface.h" +#include "Materials/Material.h" + + +UHoudiniGeoImporter::UHoudiniGeoImporter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , SourceFilePath() + , AbsoluteFilePath() + , AbsoluteFileDirectory() + , FileName() + , FileExtension() + , BakeRootFolder(TEXT("/Game/HoudiniEngine/Bake/")) +{ + /* + SourceFilePath = FString(); + + AbsoluteFilePath = FString(); + AbsoluteFileDirectory = FString(); + FileName = FString(); + FileExtension = FString(); + + OutputFilename = FString(); + BakeRootFolder = TEXT("/Game/HoudiniEngine/Bake/"); + */ +} + +bool +UHoudiniGeoImporter::SetFilePath(const FString& InFilePath) +{ + SourceFilePath = InFilePath; + if (!FPaths::FileExists(SourceFilePath)) + { + // Cant find BGEO file + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InFilePath); + return false; + } + + // Make sure we're using absolute path! + AbsoluteFilePath = FPaths::ConvertRelativePathToFull(SourceFilePath); + + // Split the file path + FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); + + // Handle .bgeo.sc correctly + if (FileExtension.Equals(TEXT("sc"))) + { + // append the bgeo to .sc + FileExtension = FPaths::GetExtension(FileName) +TEXT(".") + FileExtension; + // update the filename + FileName = FPaths::GetBaseFilename(FileName); + } + + if (FileExtension.IsEmpty()) + FileExtension = TEXT("bgeo"); + + if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) + { + // Not a bgeo file! + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *SourceFilePath); + return false; + } + + //BGEOFilePath = BGEOPath + TEXT("/") + BGEOFileName + TEXT(".") + BGEOExtension; + + // Only use "/" for the output file path + BakeRootFolder.ReplaceInline(TEXT("\\"), TEXT("/")); + // Make sure the output folder ends with a "/" + if (!BakeRootFolder.EndsWith("/")) + BakeRootFolder += TEXT("/"); + + // If we have't specified an outpout file name yet, use the input file name + if (OutputFilename.IsEmpty()) + OutputFilename = FileName; + + return true; +} + +bool +UHoudiniGeoImporter::AutoStartHoudiniEngineSessionIfNeeded() +{ + if (FHoudiniEngine::Get().GetSession()) + return true; + + // Default first session already attempted to be created ? stop here? + /* + if (FHoudiniEngine::Get().GetFirstSessionCreated()) + return false; + */ + + // Indicates that we've tried to start a session once no matter if it failed or succeed + FHoudiniEngine::Get().SetFirstSessionCreated(true); + if (!FHoudiniEngine::Get().RestartSession()) + { + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Couldn't start the default HoudiniEngine session!")); + return false; + } + + return true; +} + +bool +UHoudiniGeoImporter::BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs) +{ + FString Notification = TEXT("BGEO Importer: Getting output geos..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + const bool bInAddOutputsToRootSet = true; + return BuildAllOutputsForNode(InNodeId, this, InOldOutputs, OutNewOutputs, bInAddOutputsToRootSet); +} + +bool +UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + FString Notification = TEXT("BGEO Importer: Creating Static Meshes..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + //FHoudiniMeshTranslator::CreateAllMeshesFromHoudiniOutput(CurOutput, OuterPackage, OuterComponent, OuterAsset); + + TMap NewOutputObjects; + TMap OldOutputObjects = CurOutput->GetOutputObjects(); + TMap& AssignementMaterials = CurOutput->GetAssignementMaterials(); + TMap& ReplacementMaterials = CurOutput->GetReplacementMaterials(); + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + // Not a mesh, skip + if (CurHGPO.Type != EHoudiniPartType::Mesh) + continue; + + // Check for a unreal_output_name if we are in bake mode + FHoudiniPackageParams PackageParams(InPackageParams); + if (PackageParams.PackageMode == EPackageMode::Bake) + { + TArray OutputNames; + if (FHoudiniEngineUtils::GetOutputNameAttribute(CurHGPO.GeoId, CurHGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + PackageParams.ObjectName = OutputNames[0]; + } + } + } + + FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( + CurHGPO, + PackageParams, + OldOutputObjects, + NewOutputObjects, + AssignementMaterials, + ReplacementMaterials, + true, + EHoudiniStaticMeshMethod::RawMesh); + } + + // Add all output objects and materials + for (auto CurOutputPair : NewOutputObjects) + { + UObject* CurObj = CurOutputPair.Value.OutputObject; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + + // Do the same for materials + for (auto CurAssignmentMatPair : AssignementMaterials) + { + UObject* CurObj = CurAssignmentMatPair.Value; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + + // Also assign to the output objects map as we may need the meshes to create instancers later + CurOutput->SetOutputObjects(NewOutputObjects); + } + + return true; +} + + +bool +UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + TArray CurveOutputs; + CurveOutputs.Reserve(InOutputs.Num()); + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Curve) + continue; + + CurveOutputs.Add(CurOutput); + break; + } + + FString Notification = TEXT("BGEO Importer: Creating Curves..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + // Look for the first unreal_output_name attribute on the curve outputs and use that + // for ObjectName + FHoudiniPackageParams PackageParams(InPackageParams); + for (auto& CurOutput : CurveOutputs) + { + bool bFoundOutputName = false; + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Curve) + continue; + + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + break; + } + } + } + + if (bFoundOutputName) + break; + } + + // Create a Package for the BP + PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + FString PackageName; + UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); + check(BPPackage); + + // Create and init a new Blueprint Actor + UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); + if (!Blueprint) + return false; + + // Create a fake outer component that we'll use as a temporary outer for our curves + UWorld* TempWorld = UWorld::CreateWorld(EWorldType::Inactive, false, TEXT("BGEOImporterTemp"), GetTransientPackage(), false); + const FActorSpawnParameters ActorSpawnParameters; + AActor* OuterActor = TempWorld->SpawnActor(ActorSpawnParameters); + USceneComponent* OuterComponent = + NewObject(OuterActor, USceneComponent::GetDefaultSceneRootVariableName()); + + for (auto& CurOutput : CurveOutputs) + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); + + // Prepare an ActorComponent array for AddComponentsToBlueprint() + TArray OutputComp; + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputComp.Add(CurObj); + } + + // Transfer all the instancer components to the BP + if (OutputComp.Num() > 0) + { + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; + Params.OptionalNewRootNode = nullptr; + Params.bKeepMobility = false; + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); + } + } + + // Compile the blueprint + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + // Add it to our output objects + OutputObjects.Add(Blueprint); + + return true; +} + + +bool +UHoudiniGeoImporter::CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + HOUDINI_LOG_WARNING(TEXT("Importing a landscape directly from BGEOs is not currently supported.")); + return false; + } + + return true; + + /* + // Before processing any of the output, + // we need to get the min/max value for all Height volumes in this output (if any) + float HoudiniHeightfieldOutputsGlobalMin = 0.f; + float HoudiniHeightfieldOutputsGlobalMax = 0.f; + FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax(InOutputs, HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax); + + UWorld* PersistentWorld = InParent->GetWorld(); + if(!PersistentWorld) + PersistentWorld = GEditor ? GEditor->GetEditorWorldContext(false).World() : nullptr; + + if (!PersistentWorld) + return false; + + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + FString Notification = TEXT("BGEO Importer: Creating Landscapes..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + TArray EmptyInputLandscapes; + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(CurOutput); + + bool bCreatedNewMaps = false; + ERuntimePackageMode RuntimePackageMode = ERuntimePackageMode::CookToTemp; + switch(InPackageParams.PackageMode) + { + + case EPackageMode::Bake: + RuntimePackageMode = ERuntimePackageMode::Bake; + break; + case EPackageMode::CookToLevel: + case EPackageMode::CookToTemp: + default: + RuntimePackageMode = ERuntimePackageMode::CookToTemp; + break; + } + TArray> CreatedUntrackedOutputs; + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + CreatedUntrackedOutputs, + EmptyInputLandscapes, + EmptyInputLandscapes, + HAC, + TEXT("{object_name}_"), + PersistentWorld, + HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax, + InPackageParams, bCreatedNewMaps, + RuntimePackageMode); + + // Add all output objects + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UObject* CurObj = CurOutputPair.Value.OutputObject; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + } + + return true; + */ +} + + +bool +UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + bool HasInstancer = false; + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + HasInstancer = true; + break; + } + + if (!HasInstancer) + return true; + + FString Notification = TEXT("BGEO Importer: Creating Instancers..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + // Look for the first unreal_output_name attribute on the instancer outputs and use that + // for ObjectName + FHoudiniPackageParams PackageParams(InPackageParams); + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + bool bFoundOutputName = false; + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Instancer) + continue; + + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + break; + } + } + } + + if (bFoundOutputName) + break; + } + + // Create a Package for the BP + PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + FString PackageName; + UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); + check(BPPackage); + + // Create and init a new Blueprint Actor + UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); + if (!Blueprint) + return false; + + // Create a fake outer component that we'll use as a temporary outer for our instancers + USceneComponent* OuterComponent = NewObject(); + + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + // Create all the instancers and attach them to a fake outer component + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + CurOutput, InOutputs, OuterComponent); + + // Prepare an ActorComponent array for AddComponentsToBlueprint() + TArray OutputComp; + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputComp.Add(CurObj); + } + + // Transfer all the instancer components to the BP + if (OutputComp.Num() > 0) + { + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; + Params.OptionalNewRootNode = nullptr; + Params.bKeepMobility = false; + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); + } + } + + // Compile the blueprint + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + // Add it to our output objects + OutputObjects.Add(Blueprint); + + return true; +} + +bool +UHoudiniGeoImporter::CreateInstancerOutputPartData( + TArray& InOutputs, + TMap& OutInstancedOutputPartData) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = HGPO.ObjectId; + OutputIdentifier.GeoId = HGPO.GeoId; + OutputIdentifier.PartId = HGPO.PartId; + OutputIdentifier.PartName = HGPO.PartName; + + OutInstancedOutputPartData.Add(OutputIdentifier, FHoudiniInstancedOutputPartData()); + FHoudiniInstancedOutputPartData *InstancedOutputData = OutInstancedOutputPartData.Find(OutputIdentifier); + // Create all the instancers and attach them to a fake outer component + if (!FHoudiniInstanceTranslator::PopulateInstancedOutputPartData(HGPO, InOutputs, *InstancedOutputData)) + return false; + } + } + + return true; +} + +bool +UHoudiniGeoImporter::DeleteCreatedNode(const HAPI_NodeId& InNodeId) +{ + if (InNodeId < 0) + return false; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), InNodeId)) + { + // Could not delete the bgeo's file sop ! + HOUDINI_LOG_WARNING(TEXT("Houdini GEO Importer: Could not delete HAPI File SOP.")); + return false; + } + + return true; +} + +bool +UHoudiniGeoImporter::ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams) +{ + if (InBGEOFile.IsEmpty()) + return false; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!AutoStartHoudiniEngineSessionIfNeeded()) + return false; + + // 2. Update the file paths + if (!SetFilePath(InBGEOFile)) + return false; + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + if (!LoadBGEOFileInHAPI(NodeId)) + return false; + + // 4. Get the output from the file node + TArray NewOutputs; + TArray OldOutputs; + if (!BuildOutputsForNode(NodeId, OldOutputs, NewOutputs)) + return false; + + // Failure lambda + auto CleanUpAndReturn = [&NewOutputs](const bool& bReturnValue) + { + // Remove the output objects from the root set before returning false + for (auto Out : NewOutputs) + Out->RemoveFromRoot(); + + return bReturnValue; + }; + + // Prepare the package used for creating the mesh, landscape and instancer pacakges + FHoudiniPackageParams PackageParams; + if (InPackageParams) + { + PackageParams = *InPackageParams; + } + else + { + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.HoudiniAssetName = FString(); + PackageParams.HoudiniAssetActorName = FString(); + PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); + } + + if (!PackageParams.OuterPackage) + { + PackageParams.OuterPackage = InParent; + } + + if (!PackageParams.ComponentGUID.IsValid()) + { + // TODO: will need to reuse the GUID when reimporting? + PackageParams.ComponentGUID = FGuid::NewGuid(); + } + + // 5. Create the static meshes in the outputs + if (!CreateStaticMeshes(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 6. Create the static meshes in the outputs + if (!CreateCurves(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 7. Create the landscape in the outputs + if (!CreateLandscapes(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 8. Create the instancers in the outputs + if (!CreateInstancers(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 9. Delete the created node in Houdini + if (!DeleteCreatedNode(NodeId)) + return CleanUpAndReturn(false); + + // Clean up and return true + return CleanUpAndReturn(true); +} + +bool +UHoudiniGeoImporter::OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition) +{ + if (InBGEOFile.IsEmpty()) + return false; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!AutoStartHoudiniEngineSessionIfNeeded()) + return false; + + if (!FPaths::FileExists(InBGEOFile)) + { + // Cant find BGEO file + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InBGEOFile); + return false; + } + + // Make sure we're using absolute path! + const FString AbsoluteFilePath = FPaths::ConvertRelativePathToFull(InBGEOFile); + + if (AbsoluteFilePath.IsEmpty()) + return false; + + FString AbsoluteFileDirectory; + FString FileName; + FString FileExtension; + + // Split the file path + FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); + + // Handle .bgeo.sc correctly + if (FileExtension.Equals(TEXT("sc"))) + { + // append the bgeo to .sc + FileExtension = FPaths::GetExtension(FileName) + TEXT(".") + FileExtension; + // update the filename + FileName = FPaths::GetBaseFilename(FileName); + } + + if (FileExtension.IsEmpty()) + FileExtension = TEXT("bgeo"); + + if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) + { + // Not a bgeo file! + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *InBGEOFile); + return false; + } + + OutNodeId = -1; + + // Check HoudiniEngine / HAPI init? + if (!FHoudiniEngine::IsInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); + return false; + } + + FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); + + // Create a file SOP + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, "SOP/file", "bgeo", true, &OutNodeId), false); + + // Set the file path parameter + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + OutNodeId, "file", &ParmId), false); + + const std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str(), ParmId, 0), false); + + return true; +} + +bool +UHoudiniGeoImporter::CloseBGEOFile(const HAPI_NodeId& InNodeId) +{ + // 8. Delete the created node in Houdini + if (!DeleteCreatedNode(InNodeId)) + return false; + + return true; +} + +bool +UHoudiniGeoImporter::LoadBGEOFileInHAPI(HAPI_NodeId& NodeId) +{ + NodeId = -1; + + if (AbsoluteFilePath.IsEmpty()) + return false; + + // Check HoudiniEngine / HAPI init? + if (!FHoudiniEngine::IsInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); + return false; + } + + FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); + + // Create a file SOP + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, "SOP/file", "bgeo", true, &NodeId), false); + + // Set the file path parameter + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, "file", &ParmId), false); + + std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, 0), false); + + return CookFileNode(NodeId); +} + +bool +UHoudiniGeoImporter::CookFileNode(const HAPI_NodeId& InNodeId) +{ + // Cook the node + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); + + // Wait for the cook to finish + int32 status = HAPI_STATE_MAX_READY_STATE + 1; + while (status > HAPI_STATE_MAX_READY_STATE) + { + // Retrieve the status + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), + HAPI_STATUS_COOK_STATE, &status), false); + + FString StatusString = FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); + HOUDINI_LOG_MESSAGE(TEXT("Still Cooking, current status: %s."), *StatusString); + + // Go to bed.. + if (status > HAPI_STATE_MAX_READY_STATE) + FPlatformProcess::Sleep(0.5f); + } + + if (status != HAPI_STATE_READY) + { + // There was some cook errors + HOUDINI_LOG_ERROR(TEXT("Finished Cooking with errors!")); + return false; + } + + HOUDINI_LOG_MESSAGE(TEXT("Finished Cooking!")); + + return true; +} + +bool +UHoudiniGeoImporter::BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet) +{ + // TArray OldOutputs; + if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, InOldOutputs, OutNewOutputs, false)) + { + // Couldn't create the package + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Failed to process the File SOP's outputs!")); + return false; + } + + if (bInAddOutputsToRootSet) + { + // Add the output objects to the RootSet to prevent them from being GCed + for (auto& Out : OutNewOutputs) + Out->AddToRoot(); + } + + return true; +} diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.h b/Source/HoudiniEngine/Private/HoudiniGeoImporter.h index 8cf5422ef..4ba3217d3 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.h +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.h @@ -1,122 +1,122 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniGeoImporter.generated.h" - -class UHoudiniOutput; - -struct FHoudiniPackageParams; - -UCLASS() -class HOUDINIENGINE_API UHoudiniGeoImporter : public UObject -{ -public: - - GENERATED_UCLASS_BODY() - - public: - //UHoudiniGeoImporter(); - //~UHoudiniGeoImporter(); - - void SetBakeRootFolder(const FString& InFolder) { BakeRootFolder = InFolder; }; - void SetOutputFilename(const FString& InOutFilename) { OutputFilename = InOutFilename; }; - - TArray& GetOutputObjects() { return OutputObjects; }; - - // BEGIN: Static API - // Open a BGEO file: create a file node in HAPI and cook it - static bool OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition=false); - // Cook the file node specified by the valid NodeId. - static bool CookFileNode(const HAPI_NodeId& InNodeId); - // Extract the outputs for a given node ID - static bool BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet=false); - // Delete the HAPI node and remove InOutputs from the root set. - static bool CloseBGEOFile(const HAPI_NodeId& InNodeId); - // END: Static API - - // Import the BGEO file - bool ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams=nullptr); - - // 1. Start a HE session if needed - static bool AutoStartHoudiniEngineSessionIfNeeded(); - // 2. Update our file members fromn the input file path - bool SetFilePath(const FString& InFilePath); - // 3. Creates a new file node and loads the bgeo file in HAPI - bool LoadBGEOFileInHAPI(HAPI_NodeId& NodeId); - // 4. Extract the outputs for a given node ID - bool BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs); - // 5. Creates the static meshes object found in the output - bool CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 6. Create the output curves - bool CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 7. Create the output landscapes - bool CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 8. Create the output instancers - bool CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 9. Clean up the created node - static bool DeleteCreatedNode(const HAPI_NodeId& InNodeId); - - static bool CreateInstancerOutputPartData( - TArray& InOutputs, - TMap& OutInstancedOutputPartData); - - private: - - // - // Input file - // - // Path how the file we're currently loading - FString SourceFilePath; - // Absolute Path to the file - FString AbsoluteFilePath; - FString AbsoluteFileDirectory; - // File Name / Extension - FString FileName; - FString FileExtension; - - - // - // Output file - // - // Output filename, if empty, will be set to the input filename - FString OutputFilename; - // Root Folder for storing the created files - FString BakeRootFolder; - - // - // Output Objects - // - TArray OutputObjects; - - //TArray OutputStaticMeshes; - //TArray OutputLandscapes; - //TArray OutputInstancers; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniGeoImporter.generated.h" + +class UHoudiniOutput; + +struct FHoudiniPackageParams; + +UCLASS() +class HOUDINIENGINE_API UHoudiniGeoImporter : public UObject +{ +public: + + GENERATED_UCLASS_BODY() + + public: + //UHoudiniGeoImporter(); + //~UHoudiniGeoImporter(); + + void SetBakeRootFolder(const FString& InFolder) { BakeRootFolder = InFolder; }; + void SetOutputFilename(const FString& InOutFilename) { OutputFilename = InOutFilename; }; + + TArray& GetOutputObjects() { return OutputObjects; }; + + // BEGIN: Static API + // Open a BGEO file: create a file node in HAPI and cook it + static bool OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition=false); + // Cook the file node specified by the valid NodeId. + static bool CookFileNode(const HAPI_NodeId& InNodeId); + // Extract the outputs for a given node ID + static bool BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet=false); + // Delete the HAPI node and remove InOutputs from the root set. + static bool CloseBGEOFile(const HAPI_NodeId& InNodeId); + // END: Static API + + // Import the BGEO file + bool ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams=nullptr); + + // 1. Start a HE session if needed + static bool AutoStartHoudiniEngineSessionIfNeeded(); + // 2. Update our file members fromn the input file path + bool SetFilePath(const FString& InFilePath); + // 3. Creates a new file node and loads the bgeo file in HAPI + bool LoadBGEOFileInHAPI(HAPI_NodeId& NodeId); + // 4. Extract the outputs for a given node ID + bool BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs); + // 5. Creates the static meshes object found in the output + bool CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 6. Create the output curves + bool CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 7. Create the output landscapes + bool CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 8. Create the output instancers + bool CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 9. Clean up the created node + static bool DeleteCreatedNode(const HAPI_NodeId& InNodeId); + + static bool CreateInstancerOutputPartData( + TArray& InOutputs, + TMap& OutInstancedOutputPartData); + + private: + + // + // Input file + // + // Path how the file we're currently loading + FString SourceFilePath; + // Absolute Path to the file + FString AbsoluteFilePath; + FString AbsoluteFileDirectory; + // File Name / Extension + FString FileName; + FString FileExtension; + + + // + // Output file + // + // Output filename, if empty, will be set to the input filename + FString OutputFilename; + // Root Folder for storing the created files + FString BakeRootFolder; + + // + // Output Objects + // + TArray OutputObjects; + + //TArray OutputStaticMeshes; + //TArray OutputLandscapes; + //TArray OutputInstancers; + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp index 964f28a2f..51b11a814 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp @@ -1,371 +1,371 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniHandleTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniHandleComponent.h" - - -bool -FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray NewHandles; - - if (FHoudiniHandleTranslator::BuildAllHandles(HAC->GetAssetId(), HAC, HAC->HandleComponents, NewHandles)) - { - - HAC->HandleComponents = NewHandles; - - } - - return true; -} - -bool -FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAssetComponent* OuterObject, - TArray& CurrentHandles, - TArray& NewHandles) -{ - if (AssetId < 0) - return false; - - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo)) - return false; - - TMap CurrentHandlesByName; - - for (auto& Handle : CurrentHandles) - { - if (!Handle) - continue; - - CurrentHandlesByName.Add(Handle->GetHandleName(), Handle); - } - - // If there are handles - if (AssetInfo.handleCount > 0) - { - TArray 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) - { - 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"), - OuterObject ? *(OuterObject->GetName()) : *(OuterObject->GetName()), *TypeName, *HandleName); - continue; - } - - UHoudiniHandleComponent* HandleComponent = nullptr; - UHoudiniHandleComponent** FoundHandleComponent = CurrentHandlesByName.Find(HandleName); - - if (FoundHandleComponent) - { - HandleComponent = *FoundHandleComponent; - - CurrentHandles.Remove(*FoundHandleComponent); - CurrentHandlesByName.Remove(HandleName); - } - else - { - HandleComponent = NewObject(OuterObject, - UHoudiniHandleComponent::StaticClass(), - NAME_None, RF_Public | RF_Transactional); - - HandleComponent->SetHandleName(HandleName); - HandleComponent->SetHandleType(HandleType); - - // Change the creation method so the component is listed in the details panels - HandleComponent->CreationMethod = EComponentCreationMethod::Instance; - - } - - if (!HandleComponent) - continue; - - // If we have no parent, we need to re-attach. - if (!HandleComponent->GetAttachParent()) - { - HandleComponent->AttachToComponent(OuterObject, FAttachmentTransformRules::KeepRelativeTransform); - } - - HandleComponent->SetVisibility(true); - - // If component is not registered, register it - if (!HandleComponent->IsRegistered()) - HandleComponent->RegisterComponent(); - - // Get handle's bindings - TArray BindingInfos; - BindingInfos.SetNumZeroed(HandleInfo.bindingsCount); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetHandleBindingInfo(FHoudiniEngine::Get().GetSession(), - AssetId, HandleIdx, &BindingInfos[0], 0, HandleInfo.bindingsCount)) - continue; - - HAPI_TransformEuler HapiEulerXform; - FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); - HapiEulerXform.position[0] = HapiEulerXform.position[1] = HapiEulerXform.position[2] = 0.0f; - HapiEulerXform.rotationEuler[0] = HapiEulerXform.rotationEuler[1] = HapiEulerXform.rotationEuler[2] = 0.0f; - HapiEulerXform.scale[0] = HapiEulerXform.scale[1] = HapiEulerXform.scale[2] = 1.0f; - - TSharedPtr< FString > RSTOrderStrPtr, XYZOrderStrPtr; - - for (const auto& BindingInfo : BindingInfos) - { - FString HandleParmName = TEXT(""); - FHoudiniEngineString HoudiniEngineString(BindingInfo.handleParmNameSH); - HoudiniEngineString.ToFString(HandleParmName); - - const HAPI_ParmId ParamId = BindingInfo.assetParmId; - - TArray &XformParms = HandleComponent->XformParms; - - UHoudiniParameter* FoundParam = nullptr; - - for (auto Param : OuterObject->Parameters) - { - if (Param->GetParmId() == ParamId) - { - FoundParam = Param; - break; - } - } - - HandleComponent->InitializeHandleParameters(); - - if (!HandleComponent->CheckHandleValid()) - continue; - - (void)(XformParms[int32(EXformParameter::TX)]->Bind(HapiEulerXform.position[0], "tx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::TY)]->Bind(HapiEulerXform.position[1], "ty", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::TZ)]->Bind(HapiEulerXform.position[2], "tz", 2, HandleParmName, FoundParam) - - || XformParms[int32(EXformParameter::RX)]->Bind(HapiEulerXform.rotationEuler[0], "rx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::RY)]->Bind(HapiEulerXform.rotationEuler[1], "ry", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::RZ)]->Bind(HapiEulerXform.rotationEuler[2], "rz", 2, HandleParmName, FoundParam) - - || XformParms[int32(EXformParameter::SX)]->Bind(HapiEulerXform.scale[0], "sx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::SY)]->Bind(HapiEulerXform.scale[1], "sy", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::SZ)]->Bind(HapiEulerXform.scale[2], "sz", 2, HandleParmName, FoundParam) - - || HandleComponent->RSTParm->Bind(RSTOrderStrPtr, "trs_order", 0, HandleParmName, FoundParam) - || HandleComponent->RotOrderParm->Bind(XYZOrderStrPtr, "xyz_order", 0, HandleParmName, FoundParam) - ); - } - - - HapiEulerXform.rstOrder = GetHapiRSTOrder(RSTOrderStrPtr); - HapiEulerXform.rotationOrder = GetHapiXYZOrder(XYZOrderStrPtr); - - constexpr float MaxFloat = TNumericLimits::Max(); - constexpr float MinFloat = TNumericLimits::Min(); - - HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); - HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); - HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); - - FTransform UnrealXform; - FHoudiniEngineUtils::TranslateHapiTransform(HapiEulerXform, UnrealXform); - - //HandleComponent->SetRelativeTransform(UnrealXform); - - NewHandles.Add(HandleComponent); - - } - } - - return true; -} - - -void -FHoudiniHandleTranslator::ClearHandles(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - for (auto& HandleComponent : HAC->HandleComponents) - { - if (!HandleComponent) - continue; - - HandleComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - HandleComponent->UnregisterComponent(); - HandleComponent->DestroyComponent(); - } - - HAC->HandleComponents.Empty(); -} - -HAPI_RSTOrder -FHoudiniHandleTranslator::GetHapiRSTOrder(const TSharedPtr & StrPtr) -{ - if (StrPtr.Get()) - { - FString & Str = *StrPtr; - - if (Str == "trs") return HAPI_TRS; - else if (Str == "tsr") return HAPI_TSR; - else if (Str == "rts") return HAPI_RTS; - else if (Str == "rst") return HAPI_RST; - else if (Str == "str") return HAPI_STR; - else if (Str == "srt") return HAPI_SRT; - } - - return HAPI_SRT; -} - -HAPI_XYZOrder -FHoudiniHandleTranslator::GetHapiXYZOrder(const TSharedPtr & StrPtr) -{ - if (StrPtr.Get()) - { - FString & Str = *StrPtr; - - if (Str == "xyz") return HAPI_XYZ; - else if (Str == "xzy") return HAPI_XZY; - else if (Str == "yxz") return HAPI_YXZ; - else if (Str == "yzx") return HAPI_YZX; - else if (Str == "zxy") return HAPI_ZXY; - else if (Str == "zyx") return HAPI_ZYX; - } - - return HAPI_XYZ; -} - - -void -FHoudiniHandleTranslator::UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent) -{ - if (!HandleComponent || HandleComponent->IsPendingKill()) - return; - - if (!HandleComponent->CheckHandleValid()) - return; - - TArray &XformParms = HandleComponent->XformParms; - - if (XformParms.Num() < (int32)EXformParameter::COUNT) - return; - - HAPI_Transform HapiXform; - FMemory::Memzero< HAPI_Transform >(HapiXform); - FHoudiniEngineUtils::TranslateUnrealTransform(HandleComponent->GetRelativeTransform(), HapiXform); - - const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); - - float HapiMatrix[16]; - FHoudiniApi::ConvertTransformQuatToMatrix(Session, &HapiXform, HapiMatrix); - - HAPI_TransformEuler HapiEulerXform; - FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); - FHoudiniApi::ConvertMatrixToEuler( - Session, - HapiMatrix, - GetHapiRSTOrder(HandleComponent->RSTParm->Get(TSharedPtr< FString >())), - GetHapiXYZOrder(HandleComponent->RotOrderParm->Get(TSharedPtr< FString >())), - &HapiEulerXform - ); - - *XformParms[int32(EXformParameter::TX)] = HapiEulerXform.position[0]; - *XformParms[int32(EXformParameter::TY)] = HapiEulerXform.position[1]; - *XformParms[int32(EXformParameter::TZ)] = HapiEulerXform.position[2]; - - *XformParms[int32(EXformParameter::RX)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[0]); - *XformParms[int32(EXformParameter::RY)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[1]); - *XformParms[int32(EXformParameter::RZ)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[2]); - - constexpr float MaxFloat = TNumericLimits::Max(); - constexpr float MinFloat = TNumericLimits::Min(); - HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); - HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); - HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); - - *XformParms[int32(EXformParameter::SX)] = HapiEulerXform.scale[0]; - *XformParms[int32(EXformParameter::SY)] = HapiEulerXform.scale[1]; - *XformParms[int32(EXformParameter::SZ)] = HapiEulerXform.scale[2]; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniHandleTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniHandleComponent.h" + + +bool +FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray NewHandles; + + if (FHoudiniHandleTranslator::BuildAllHandles(HAC->GetAssetId(), HAC, HAC->HandleComponents, NewHandles)) + { + + HAC->HandleComponents = NewHandles; + + } + + return true; +} + +bool +FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAssetComponent* OuterObject, + TArray& CurrentHandles, + TArray& NewHandles) +{ + if (AssetId < 0) + return false; + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo)) + return false; + + TMap CurrentHandlesByName; + + for (auto& Handle : CurrentHandles) + { + if (!Handle) + continue; + + CurrentHandlesByName.Add(Handle->GetHandleName(), Handle); + } + + // If there are handles + if (AssetInfo.handleCount > 0) + { + TArray 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) + { + 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"), + OuterObject ? *(OuterObject->GetName()) : *(OuterObject->GetName()), *TypeName, *HandleName); + continue; + } + + UHoudiniHandleComponent* HandleComponent = nullptr; + UHoudiniHandleComponent** FoundHandleComponent = CurrentHandlesByName.Find(HandleName); + + if (FoundHandleComponent) + { + HandleComponent = *FoundHandleComponent; + + CurrentHandles.Remove(*FoundHandleComponent); + CurrentHandlesByName.Remove(HandleName); + } + else + { + HandleComponent = NewObject(OuterObject, + UHoudiniHandleComponent::StaticClass(), + NAME_None, RF_Public | RF_Transactional); + + HandleComponent->SetHandleName(HandleName); + HandleComponent->SetHandleType(HandleType); + + // Change the creation method so the component is listed in the details panels + HandleComponent->CreationMethod = EComponentCreationMethod::Instance; + + } + + if (!HandleComponent) + continue; + + // If we have no parent, we need to re-attach. + if (!HandleComponent->GetAttachParent()) + { + HandleComponent->AttachToComponent(OuterObject, FAttachmentTransformRules::KeepRelativeTransform); + } + + HandleComponent->SetVisibility(true); + + // If component is not registered, register it + if (!HandleComponent->IsRegistered()) + HandleComponent->RegisterComponent(); + + // Get handle's bindings + TArray BindingInfos; + BindingInfos.SetNumZeroed(HandleInfo.bindingsCount); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetHandleBindingInfo(FHoudiniEngine::Get().GetSession(), + AssetId, HandleIdx, &BindingInfos[0], 0, HandleInfo.bindingsCount)) + continue; + + HAPI_TransformEuler HapiEulerXform; + FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); + HapiEulerXform.position[0] = HapiEulerXform.position[1] = HapiEulerXform.position[2] = 0.0f; + HapiEulerXform.rotationEuler[0] = HapiEulerXform.rotationEuler[1] = HapiEulerXform.rotationEuler[2] = 0.0f; + HapiEulerXform.scale[0] = HapiEulerXform.scale[1] = HapiEulerXform.scale[2] = 1.0f; + + TSharedPtr< FString > RSTOrderStrPtr, XYZOrderStrPtr; + + for (const auto& BindingInfo : BindingInfos) + { + FString HandleParmName = TEXT(""); + FHoudiniEngineString HoudiniEngineString(BindingInfo.handleParmNameSH); + HoudiniEngineString.ToFString(HandleParmName); + + const HAPI_ParmId ParamId = BindingInfo.assetParmId; + + TArray &XformParms = HandleComponent->XformParms; + + UHoudiniParameter* FoundParam = nullptr; + + for (auto Param : OuterObject->Parameters) + { + if (Param->GetParmId() == ParamId) + { + FoundParam = Param; + break; + } + } + + HandleComponent->InitializeHandleParameters(); + + if (!HandleComponent->CheckHandleValid()) + continue; + + (void)(XformParms[int32(EXformParameter::TX)]->Bind(HapiEulerXform.position[0], "tx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::TY)]->Bind(HapiEulerXform.position[1], "ty", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::TZ)]->Bind(HapiEulerXform.position[2], "tz", 2, HandleParmName, FoundParam) + + || XformParms[int32(EXformParameter::RX)]->Bind(HapiEulerXform.rotationEuler[0], "rx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::RY)]->Bind(HapiEulerXform.rotationEuler[1], "ry", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::RZ)]->Bind(HapiEulerXform.rotationEuler[2], "rz", 2, HandleParmName, FoundParam) + + || XformParms[int32(EXformParameter::SX)]->Bind(HapiEulerXform.scale[0], "sx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::SY)]->Bind(HapiEulerXform.scale[1], "sy", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::SZ)]->Bind(HapiEulerXform.scale[2], "sz", 2, HandleParmName, FoundParam) + + || HandleComponent->RSTParm->Bind(RSTOrderStrPtr, "trs_order", 0, HandleParmName, FoundParam) + || HandleComponent->RotOrderParm->Bind(XYZOrderStrPtr, "xyz_order", 0, HandleParmName, FoundParam) + ); + } + + + HapiEulerXform.rstOrder = GetHapiRSTOrder(RSTOrderStrPtr); + HapiEulerXform.rotationOrder = GetHapiXYZOrder(XYZOrderStrPtr); + + constexpr float MaxFloat = TNumericLimits::Max(); + constexpr float MinFloat = TNumericLimits::Min(); + + HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); + HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); + HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); + + FTransform UnrealXform; + FHoudiniEngineUtils::TranslateHapiTransform(HapiEulerXform, UnrealXform); + + //HandleComponent->SetRelativeTransform(UnrealXform); + + NewHandles.Add(HandleComponent); + + } + } + + return true; +} + + +void +FHoudiniHandleTranslator::ClearHandles(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + for (auto& HandleComponent : HAC->HandleComponents) + { + if (!HandleComponent) + continue; + + HandleComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + HandleComponent->UnregisterComponent(); + HandleComponent->DestroyComponent(); + } + + HAC->HandleComponents.Empty(); +} + +HAPI_RSTOrder +FHoudiniHandleTranslator::GetHapiRSTOrder(const TSharedPtr & StrPtr) +{ + if (StrPtr.Get()) + { + FString & Str = *StrPtr; + + if (Str == "trs") return HAPI_TRS; + else if (Str == "tsr") return HAPI_TSR; + else if (Str == "rts") return HAPI_RTS; + else if (Str == "rst") return HAPI_RST; + else if (Str == "str") return HAPI_STR; + else if (Str == "srt") return HAPI_SRT; + } + + return HAPI_SRT; +} + +HAPI_XYZOrder +FHoudiniHandleTranslator::GetHapiXYZOrder(const TSharedPtr & StrPtr) +{ + if (StrPtr.Get()) + { + FString & Str = *StrPtr; + + if (Str == "xyz") return HAPI_XYZ; + else if (Str == "xzy") return HAPI_XZY; + else if (Str == "yxz") return HAPI_YXZ; + else if (Str == "yzx") return HAPI_YZX; + else if (Str == "zxy") return HAPI_ZXY; + else if (Str == "zyx") return HAPI_ZYX; + } + + return HAPI_XYZ; +} + + +void +FHoudiniHandleTranslator::UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent) +{ + if (!HandleComponent || HandleComponent->IsPendingKill()) + return; + + if (!HandleComponent->CheckHandleValid()) + return; + + TArray &XformParms = HandleComponent->XformParms; + + if (XformParms.Num() < (int32)EXformParameter::COUNT) + return; + + HAPI_Transform HapiXform; + FMemory::Memzero< HAPI_Transform >(HapiXform); + FHoudiniEngineUtils::TranslateUnrealTransform(HandleComponent->GetRelativeTransform(), HapiXform); + + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + + float HapiMatrix[16]; + FHoudiniApi::ConvertTransformQuatToMatrix(Session, &HapiXform, HapiMatrix); + + HAPI_TransformEuler HapiEulerXform; + FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); + FHoudiniApi::ConvertMatrixToEuler( + Session, + HapiMatrix, + GetHapiRSTOrder(HandleComponent->RSTParm->Get(TSharedPtr< FString >())), + GetHapiXYZOrder(HandleComponent->RotOrderParm->Get(TSharedPtr< FString >())), + &HapiEulerXform + ); + + *XformParms[int32(EXformParameter::TX)] = HapiEulerXform.position[0]; + *XformParms[int32(EXformParameter::TY)] = HapiEulerXform.position[1]; + *XformParms[int32(EXformParameter::TZ)] = HapiEulerXform.position[2]; + + *XformParms[int32(EXformParameter::RX)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[0]); + *XformParms[int32(EXformParameter::RY)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[1]); + *XformParms[int32(EXformParameter::RZ)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[2]); + + constexpr float MaxFloat = TNumericLimits::Max(); + constexpr float MinFloat = TNumericLimits::Min(); + HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); + HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); + HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); + + *XformParms[int32(EXformParameter::SX)] = HapiEulerXform.scale[0]; + *XformParms[int32(EXformParameter::SY)] = HapiEulerXform.scale[1]; + *XformParms[int32(EXformParameter::SZ)] = HapiEulerXform.scale[2]; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h index 92945fc9e..2c4827ef8 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h @@ -1,52 +1,52 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -class UHoudiniAssetComponent; -class UHoudiniHandleComponent; - -struct HOUDINIENGINE_API FHoudiniHandleTranslator -{ - static bool UpdateHandles(UHoudiniAssetComponent* HAC); - - - static bool BuildAllHandles(const HAPI_NodeId& AssetId, - UHoudiniAssetComponent* OuterObject, - TArray& CurrentHandles, - TArray& NewHandles); - - static void ClearHandles(UHoudiniAssetComponent* HAC); - - static HAPI_RSTOrder GetHapiRSTOrder(const TSharedPtr & StrPtr); - - static HAPI_XYZOrder GetHapiXYZOrder(const TSharedPtr & StrPtr); - - static void UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent); - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +class UHoudiniAssetComponent; +class UHoudiniHandleComponent; + +struct HOUDINIENGINE_API FHoudiniHandleTranslator +{ + static bool UpdateHandles(UHoudiniAssetComponent* HAC); + + + static bool BuildAllHandles(const HAPI_NodeId& AssetId, + UHoudiniAssetComponent* OuterObject, + TArray& CurrentHandles, + TArray& NewHandles); + + static void ClearHandles(UHoudiniAssetComponent* HAC); + + static HAPI_RSTOrder GetHapiRSTOrder(const TSharedPtr & StrPtr); + + static HAPI_XYZOrder GetHapiXYZOrder(const TSharedPtr & StrPtr); + + static void UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp index 140ccaf1d..b5bd82583 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp @@ -1,2890 +1,2890 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInputTranslator.h" - -#include "HoudiniInput.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInputObject.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniAssetActor.h" -#include "HoudiniOutputTranslator.h" -#include "UnrealBrushTranslator.h" -#include "UnrealSplineTranslator.h" -#include "UnrealMeshTranslator.h" -#include "UnrealInstanceTranslator.h" -#include "UnrealLandscapeTranslator.h" - -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "Engine/Brush.h" -#include "Engine/DataTable.h" -#include "Camera/CameraComponent.h" - -#include "Engine/SimpleConstructionScript.h" -#include "Engine/SCS_Node.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Editor/UnrealEdEngine.h" - #include "UnrealEdGlobals.h" -#endif - -#include "HCsgUtils.h" - -#include "Async/Async.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#if WITH_EDITOR -// Allows checking of objects currently being dragged around -struct FHoudiniMoveTracker -{ - FHoudiniMoveTracker() : IsObjectMoving(false) - { - GEditor->OnBeginObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = true; }); - GEditor->OnEndObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - - GEditor->OnBeginCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - GEditor->OnEndCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - } - static FHoudiniMoveTracker& Get() { static FHoudiniMoveTracker Instance; return Instance; } - - bool IsObjectMoving; -}; -#endif - -// -bool -FHoudiniInputTranslator::UpdateInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) - { - // Failed to create the inputs - return false; - } - - return true; -} - -bool -FHoudiniInputTranslator::BuildAllInputs( - const HAPI_NodeId& AssetId, - class UObject* InOuterObject, - TArray& Inputs, - TArray& Parameters) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Start by getting the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - // Get the number of geo (SOP) inputs - int32 InputCount = AssetInfo.geoInputCount; - /* - // It's best to update the input count even if the hda hasnt cooked - // as it can cause loaded geo inputs to disappear upon loading the level - if ( AssetInfo.hasEverCooked ) - { - InputCount = AssetInfo.geoInputCount; - } - */ - // Also look for object path parameters inputs - TArray> InputParameters; - for (auto Param : Parameters) - { - if (Param->GetParameterType() == EHoudiniParameterType::Input) - InputParameters.Add(Param); - } - - InputCount += InputParameters.Num(); - - // Append new inputs as needed - if (InputCount > Inputs.Num()) - { - int32 NumNewInputs = InputCount - Inputs.Num(); - for (int32 InputIdx = Inputs.Num(); InputIdx < InputCount; ++InputIdx) - { - FString InputObjectName = TEXT("Input") + FString::FromInt(InputIdx + 1); - UHoudiniInput * NewInput = NewObject< UHoudiniInput >( - InOuterObject, - UHoudiniInput::StaticClass(), - FName(*InputObjectName), - RF_Transactional); - - if (!NewInput || NewInput->IsPendingKill()) - { - //HOUDINI_LOG_WARNING("Failed to create asset input"); - continue; - } - // Create a default curve object here to avoid Transaction issue - //NewInput->CreateDefaultCurveInputObject(); - - Inputs.Add(NewInput); - } - } - else if (InputCount < Inputs.Num()) - { - // TODO: Properly clean up the input object + created nodes? - for (int32 InputIdx = Inputs.Num() - 1; InputIdx >= InputCount; InputIdx--) - { - UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - FHoudiniInputTranslator::DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); - - // DO NOT MANUALLY DESTROY THE OLD/DANGLING INPUTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - //CurrentInput->ConditionalBeginDestroy(); - //CurrentInput = nullptr; - } - - Inputs.SetNum(InputCount); - } - - // Now, check the inputs in the array match the geo inputs - //for (int32 GeoInIdx = 0; GeoInIdx < AssetInfo.geoInputCount; GeoInIdx++) - bool bBlueprintStructureChanged = false; - for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) - { - UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // Create default Name/Label/Help - FString CurrentInputName = TEXT("Input") + FString::FromInt(InputIdx + 1); - FString CurrentInputLabel = CurrentInputName; - FString CurrentInputHelp; - - // Set the nodeId - CurrentInput->SetAssetNodeId(AssetId); - - // Is this an object path parameter input? - bool bIsObjectPath = InputIdx >= AssetInfo.geoInputCount; - if (!bIsObjectPath) - { - // Mark this input as a SOP input - CurrentInput->SetSOPInput(InputIdx); - - // Get and set the name - HAPI_StringHandle InputStringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInputName( - FHoudiniEngine::Get().GetSession(), - AssetId, InputIdx, &InputStringHandle)) - { - FHoudiniEngineString HoudiniEngineString(InputStringHandle); - HoudiniEngineString.ToFString(CurrentInputLabel); - } - } - else - { - // Get this input's parameter index in the objpath param array - int32 CurrentParmIdx = InputIdx - AssetInfo.geoInputCount; - - UHoudiniParameter* CurrentParm = nullptr; - if (InputParameters.IsValidIndex(CurrentParmIdx)) - { - if (InputParameters[CurrentParmIdx].IsValid()) - CurrentParm = InputParameters[CurrentParmIdx].Get(); - } - - int32 ParmId = -1; - if (CurrentParm && !CurrentParm->IsPendingKill()) - { - ParmId = CurrentParm->GetParmId(); - CurrentInputName = CurrentParm->GetParameterName(); - CurrentInputLabel = CurrentParm->GetParameterLabel(); - CurrentInputHelp = CurrentParm->GetParameterHelp(); - } - - UHoudiniParameterOperatorPath* CurrentObjPathParm = Cast(CurrentParm); - if (CurrentObjPathParm && !CurrentObjPathParm->IsPendingKill()) - { - CurrentObjPathParm->HoudiniInput = CurrentInput; - } - - // Mark this input as an object path parameter input - CurrentInput->SetObjectPathParameter(ParmId); - } - - CurrentInput->SetName(CurrentInputName); - CurrentInput->SetLabel(CurrentInputLabel); - - if ( CurrentInputHelp.IsEmpty() ) - { - CurrentInputHelp = CurrentInputLabel + TEXT("(") + CurrentInputName + TEXT(")"); - } - CurrentInput->SetHelp(CurrentInputHelp); - - // If the input type is invalid, - // We need to initialize its default - if (CurrentInput->GetInputType() == EHoudiniInputType::Invalid) - { - // Initialize it to the default corresponding to its name - CurrentInput->SetInputType(GetDefaultInputTypeFromLabel(CurrentInputLabel), bBlueprintStructureChanged); - - // Preset the default HDA for objpath input - SetDefaultAssetFromHDA(CurrentInput); - } - - // Update input objects data on UE side for all types of inputs. - switch (CurrentInput->GetInputType()) - { - case EHoudiniInputType::Curve: - FHoudiniSplineTranslator::UpdateHoudiniInputCurves(CurrentInput); - break; - case EHoudiniInputType::Landscape: - //FUnrealLandscapeTranslator::UpdateHoudiniInputLandscapes(CurrentInput); - break; - case EHoudiniInputType::Asset: - break; - case EHoudiniInputType::Geometry: - break; - case EHoudiniInputType::Skeletal: - break; - case EHoudiniInputType::World: - break; - default: - break; - } - } - - return true; -} - -bool -FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - if (!InputToDestroy || InputToDestroy->IsPendingKill()) - return false; - - // Start by disconnecting the input / nullifying the object path parameter - if (InputToDestroy->IsObjectPathParameter()) - { - // Just set the objpath parameter to null - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - InputToDestroy->GetAssetNodeId(), "", - InputToDestroy->GetParameterId(), 0); - } - else - { - // Get the asset / created input node ID - HAPI_NodeId HostAssetId = InputToDestroy->GetAssetNodeId(); - HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); - - // Only disconnect if both are valid - if (HostAssetId >= 0 && CreatedInputId >= 0) - { - FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), - HostAssetId, InputToDestroy->GetInputIndex()); - } - } - - if (InputType == EHoudiniInputType::Asset) - { - // TODO: - // If we're an asset input, just remove us from the downstream connection on the input HDA - // then reset this input's flag - - // TODO: Check this? Clean our DS assets?? why?? likely uneeded - UHoudiniAssetComponent* OuterHAC = Cast(InputToDestroy->GetOuter()); - if (OuterHAC) - OuterHAC->ClearDownstreamHoudiniAsset(); - - InputToDestroy->SetInputNodeId(-1); - } - - return true; -} - -bool -FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - if (!InputToDestroy || InputToDestroy->IsPendingKill()) - return false; - - if (!InputToDestroy->CanDeleteHoudiniNodes()) - return false; - - // If we're destroying an asset input, don't destroy anything as we don't want to destroy the input HDA - // a simple disconnect is sufficient - if (InputType == EHoudiniInputType::Asset) - return true; - - // Destroy the nodes created by all the input objects - TArray CreatedInputDataAssetIds = InputToDestroy->GetCreatedDataNodeIds(); - TArray* InputObjectNodes = InputToDestroy->GetHoudiniInputObjectArray(InputType); - if (InputObjectNodes) - { - for (auto CurInputObject : *InputObjectNodes) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - if (CurInputObject->Type == EHoudiniInputObjectType::HoudiniAssetComponent) - { - // Remove this input object's node Id from the - // CreatedInputDataAssetIds array to avoid its deletion further down - CreatedInputDataAssetIds.Remove(CurInputObject->InputNodeId); - CurInputObject->InputNodeId = -1; - CurInputObject->InputObjectNodeId = -1; - continue; - } - - // For Actor input objects, set the input node id for all component objects to -1, - if (CurInputObject->Type == EHoudiniInputObjectType::Actor) - { - UHoudiniInputActor* CurActorInputObject = Cast(CurInputObject); - if (CurActorInputObject) - { - for (auto & CurActorComponent : CurActorInputObject->ActorComponents) - { - if (!CurActorComponent || CurActorComponent->IsPendingKill()) - continue; - - // No need to delete the nodes created for an asset component manually here, - // As they will be deleted when we clean up the CreateNodeIds array - CurActorComponent->InputNodeId = -1; - } - } - } - // No need to delete the nodes created for an asset component manually here, - // As they will be deleted when we clean up the CreateNodeIds array - - if (CurInputObject->InputNodeId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputNodeId); - CurInputObject->InputNodeId = -1; - } - - if(CurInputObject->InputObjectNodeId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputObjectNodeId); - CurInputObject->InputObjectNodeId = -1; - - // TODO: CHECK ME! - HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CurInputObject->InputObjectNodeId); - - // Delete its parent node as well - if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentNodeId)) - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentNodeId); - } - - // Also directly invalidate HoudiniSplineComponent's node IDs. - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast(CurInputObject); - if (IsValid(HoudiniSplineInputObject) && !IsGarbageCollecting()) - { - UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (SplineComponent && !SplineComponent->IsPendingKill()) - { - SplineComponent->SetNodeId(-1); - } - } - - CurInputObject->MarkChanged(true); - } - } - - // Destroy all the input assets - for (HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds) - { - if (AssetNodeId < 0) - continue; - - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), AssetNodeId); - } - CreatedInputDataAssetIds.Empty(); - - // Then simply destroy the input's parent OBJ node - if (InputToDestroy->GetInputNodeId() >= 0) - { - HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedInputId); - - if (CreatedInputId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CreatedInputId); - InputToDestroy->SetInputNodeId(-1); - } - - if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentId)) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentId); - } - } - - return true; -} - -bool -FHoudiniInputTranslator::DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - // Start by disconnecting the input/object merge - bool bSuccess = DisconnectInput(InputToDestroy, InputType); - - // Then destroy the created input nodes - bSuccess &= DestroyInputNodes(InputToDestroy, InputType); - - return bSuccess; -} - - -EHoudiniInputType -FHoudiniInputTranslator::GetDefaultInputTypeFromLabel(const FString& InputName) -{ - // We'll try to find these magic words to try to detect the default input type - //FString geoPrefix = TEXT("geo"); - FString curvePrefix = TEXT("curve"); - - FString landscapePrefix = TEXT("landscape"); - FString landscapePrefix2 = TEXT("terrain"); - FString landscapePrefix3 = TEXT("heightfield"); - - FString worldPrefix = TEXT("world"); - FString worldPrefix2 = TEXT("outliner"); - - FString assetPrefix = TEXT("asset"); - FString assetPrefix2 = TEXT("hda"); - - // By default, geometry input is chosen. - EHoudiniInputType InputType = EHoudiniInputType::Geometry; - - if (InputName.Contains(curvePrefix, ESearchCase::IgnoreCase)) - InputType = EHoudiniInputType::Curve; - - else if ((InputName.Contains(landscapePrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(landscapePrefix2, ESearchCase::IgnoreCase)) - || (InputName.Contains(landscapePrefix3, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::Landscape; - - else if ((InputName.Contains(worldPrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(worldPrefix2, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::World; - - else if ((InputName.Contains(assetPrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(assetPrefix2, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::Asset; - - return InputType; -} - -bool -FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bForce) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (!InInput->HasInputTypeChanged() && !bForce) - return true; - - // - Handle switching AWAY from an input type - DisconnectAndDestroyInput(InInput, InInput->GetPreviousInputType()); - - // Invalidate the previous input type now that we've actually changed - //InInput->SetPreviousInputType(EHoudiniInputType::Invalid); - - //ChangeInputType(InInput, NewType); - - // TODO: - // - Handle updating to the new input type - // downstream asset connection, static mesh update, curve creation... - - // Mark all the objects from this input has changed so they upload themselves - InInput->MarkAllInputObjectsChanged(true); - - return true; -} - -bool -FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input) -{ - // - if (!Input || Input->IsPendingKill()) - return false; - - // We just handle geo inputs - if (EHoudiniInputType::Geometry != Input->GetInputType()) - return false; - - // Make sure we're linked to a valid object path parameter - if (Input->GetParameterId() < 0) - return false; - - // There is a default slot, don't add if slot is already filled - //if (InputObjects.Num() > 1) - // return false; - - // Get our ParmInfo - HAPI_ParmInfo FoundParamInfo; - FHoudiniApi::ParmInfo_Init(&FoundParamInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - Input->GetAssetNodeId(), Input->GetParameterId(), &FoundParamInfo)) - { - return false; - } - - // TODO: FINISH ME! - - /* - // Get our string value - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - Input->GetInputNodeId(), false, - &StringHandle, FoundParamInfo.stringValuesIndex, 1) ) - { - FString OutValue; - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(OutValue)) - { - // Set default object on the HDA instance - will override the parameter string - // and apply the object input local-path thing for the HDA cook. - if (OutValue.Len() > 0) - { - UObject * pObject = LoadObject(nullptr, *OutValue); - if (pObject) - { - return AddInputObject(pObject); - } - } - } - } - */ - - return false; -} - -bool -FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - //for (auto CurrentInput : HAC->Inputs) - for(int32 InputIdx = 0; InputIdx < HAC->GetNumInputs(); InputIdx++) - { - UHoudiniInput*& CurrentInput = HAC->Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill() || !CurrentInput->HasChanged()) - continue; - - // First thing, see if we need to change the input type - if (CurrentInput->HasInputTypeChanged()) - { - ChangeInputType(CurrentInput, false); - } - - if (CurrentInput->GetInputType() == EHoudiniInputType::Landscape && CurrentInput->HasLandscapeExportTypeChanged()) - { - DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); - CurrentInput->MarkAllInputObjectsChanged(true); - CurrentInput->SetHasLandscapeExportTypeChanged(false); - } - - bool bSuccess = true; - if (CurrentInput->IsDataUploadNeeded()) - { - bSuccess &= UploadInputData(CurrentInput); - CurrentInput->MarkDataUploadNeeded(!bSuccess); - } - - if (CurrentInput->IsTransformUploadNeeded()) - { - bSuccess &= UploadInputTransform(CurrentInput); - } - - // Update the input properties AFTER eventually uploading it - bSuccess = UpdateInputProperties(CurrentInput); - - if (bSuccess) - { - CurrentInput->MarkChanged(false); - CurrentInput->MarkAllInputObjectsChanged(false); - } - - if (CurrentInput->HasInputTypeChanged()) - CurrentInput->SetPreviousInputType(EHoudiniInputType::Invalid); - - // Even if we failed, no need to try updating again. - CurrentInput->SetNeedsToTriggerUpdate(false); - } - - return true; -} - -bool -FHoudiniInputTranslator::UpdateInputProperties(UHoudiniInput* InInput) -{ - bool bSucess = UpdateTransformType(InInput); - - bSucess &= UpdatePackBeforeMerge(InInput); - - bSucess &= UpdateTransformOffset(InInput); - - return bSucess; -} - -bool -FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - bool nTransformType = InInput->GetKeepWorldTransform(); - - // Geometry inputs are always set to none - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType == EHoudiniInputType::Geometry) - nTransformType = 0; - - // Get the Input node ID from the host ID - HAPI_NodeId InputNodeId = -1; - HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); - - bool bSuccess = true; - const std::string sXformType = "xformtype"; - if (InInput->IsObjectPathParameter()) - { - // Directly change the Parameter xformtype - // (This will only work if the object merge is editable/unlocked) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), - HostAssetId, sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - else - { - // Query the object merge's node ID via the input - if (HAPI_RESULT_SUCCESS == FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - HostAssetId, InInput->GetInputIndex(), &InputNodeId)) - { - // Change its Parameter xformtype - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), - InputNodeId, sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - } - - // Since our input objects are all plugged into a merge node - // We want to also update the transform type on the object merge plugged into the merge node - HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); - if ((ParentNodeId >= 0) && (InputType != EHoudiniInputType::Geometry) && (InputType != EHoudiniInputType::Asset)) - { - HAPI_NodeId InputObjectNodeId = -1; - int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); - for (int n = 0; n < NumberOfInputMeshes; n++) - { - // Get the Input node ID from the host ID - InputObjectNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ParentNodeId, n, &InputObjectNodeId)) - continue; - - if (InputObjectNodeId == -1) - continue; - - // Change the xformtype parameter on the object merge - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), InputObjectNodeId, - sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - // Pack before merge is only available for Geo/World input - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType != EHoudiniInputType::World - && InputType != EHoudiniInputType::Geometry) - { - // Nothing to change - return true; - } - - uint32 nPackValue = InInput->GetPackBeforeMerge() ? 1 : 0; - - // Get the Input node ID from the host ID - HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); - - bool bSuccess = true; - const std::string sPack = "pack"; - - // We'll be going through each input object plugged in the input's merge node - // and change the pack parameter there - HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); - if (ParentNodeId >= 0) - { - HAPI_NodeId InputObjectNodeId = -1; - int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); - for (int n = 0; n < NumberOfInputMeshes; n++) - { - // Get the Input node ID from the host ID - InputObjectNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ParentNodeId, n, &InputObjectNodeId)) - continue; - - if (InputObjectNodeId == -1) - continue; - - // Change the pack parameter on the object merge - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), InputObjectNodeId, - sPack.c_str(), 0, nPackValue)) - bSuccess = false; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - // Transform offsets are only for geometry inputs - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType != EHoudiniInputType::Geometry) - { - // Nothing to change - return true; - } - - // Get the input objects - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Update each object's transform offset - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - // If the Input mesh has a Transform offset - FTransform TransformOffset = CurrentInputObject->Transform; - if (!TransformOffset.Equals(FTransform::Identity)) - { - // Updating the Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CurrentInputObject->InputObjectNodeId, &HapiTransform), false); - } - } - - return true; -} - -bool -FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - EHoudiniInputType InputType = InInput->GetInputType(); - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Iterate on all the input objects and see if they need to be uploaded - bool bSuccess = true; - TArray CreatedNodeIds; - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; - if (!CurrentInputObject->HasChanged() && CurrentInputObjectNodeId >= 0) - { - // If this object hasn't changed, no need to upload it - // but we need to keep its created input node - if (CurrentInputObject->Type == EHoudiniInputObjectType::Actor) - { - // If this input object is an actor, it actually contains other input - // objects for each of his components, keep them as well - UHoudiniInputActor* InputActor = Cast(CurrentInputObject); - if (InputActor && !InputActor->IsPendingKill()) - { - for (auto CurrentComp : InputActor->ActorComponents) - { - if (!CurrentComp || CurrentComp->IsPendingKill()) - continue; - - int32& CurrentCompNodeId = CurrentComp->InputObjectNodeId; - if (!CurrentComp->HasChanged() && CurrentCompNodeId >= 0) - { - // If the component hasnt changed and is valid, keep it - CreatedNodeIds.Add(CurrentCompNodeId); - } - else - { - // Upload the component input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentComp, CreatedNodeIds)) - bSuccess = false; - } - } - } - } - else - { - // No changes, keep it - CreatedNodeIds.Add(CurrentInputObjectNodeId); - } - } - else - { - // Upload the current input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentInputObject, CreatedNodeIds)) - bSuccess = false; - } - } - - // If we haven't created any input, invalidate our input node id - if (CreatedNodeIds.Num() == 0) - { - if (!InInput->HasInputTypeChanged()) - { - int32 InputNodeId = InInput->GetInputNodeId(); - TArray PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); - - if (InInput->GetInputType() == EHoudiniInputType::Asset) - { - UHoudiniAssetComponent * OuterHAC = Cast(InInput->GetOuter()); - HAPI_NodeId AssetId = OuterHAC->GetAssetId(); - - // Disconnect the asset input - if (InputNodeId >= 0 && InInput->GetInputIndex() >= 0) - { - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), AssetId, InInput->GetInputIndex())); - } - } - else if (InInput->GetInputType() == EHoudiniInputType::World) - { - // World nodes are handled by InputObjects () (with FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete) - } - else - { - if (InputNodeId >= 0) - { - for (int32 Idx = 0; Idx < PreviousInputObjectNodeIds.Num(); Idx++) - { - - // Get the object merge connected to the merge node - HAPI_NodeId InputObjectMergeId = -1; - HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); - - // Disconnect the two nodes - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); - - // Destroy the object merge node, do not delete other HDA (Asset input type) - HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); - } - } - } - } - InInput->GetCreatedDataNodeIds().Empty(); - InInput->SetInputNodeId(-1); - return bSuccess; - } - - // Get the current input's NodeId - HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); - // Check that the current input's node ID is still valid - if (InputNodeId < 0 || !FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - { - // This input doesn't have a valid NodeId yet, - // we need to create this input's merge node and update this input's node ID - FString MergeName = InInput->GetNodeBaseName() + TEXT("_Merge"); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/merge"), MergeName, true, &InputNodeId), false); - - InInput->SetInputNodeId(InputNodeId); - } - - //TODO: - // Do we want to update the input's transform? - if (false) - { - FTransform ComponentTransform = FTransform::Identity; - USceneComponent* OuterComp = Cast(InInput->GetOuter()); - if (OuterComp && !OuterComp->IsPendingKill()) - ComponentTransform = OuterComp->GetComponentTransform(); - - FHoudiniEngineUtils::HapiSetAssetTransform(InputNodeId, ComponentTransform); - //HapiUpdateInputNodeTransform(InputNodeId, ComponentTransform); - } - - // Connect all the input objects to the merge node now - int32 InputIndex = 0; - for (auto CurrentNodeId : CreatedNodeIds) - { - if (CurrentNodeId < 0) - continue; - - if (InputNodeId == CurrentNodeId) - continue; - - // Connect the current input object to the merge node - HOUDINI_CHECK_ERROR(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - InputNodeId, InputIndex++, CurrentNodeId, 0)); - } - - // Check if we need to disconnect extra input objects nodes from the merge - // This can be needed when the input had more input objects on the previous cook - TArray& PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); - if (!InInput->HasInputTypeChanged()) - { - for (int32 Idx = CreatedNodeIds.Num(); Idx < PreviousInputObjectNodeIds.Num(); Idx++) - { - // Get the object merge connected to the merge node - HAPI_NodeId InputObjectMergeId = -1; - if (InInput->GetInputType() != EHoudiniInputType::Asset) - HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); - - // Disconnect the two nodes - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); - - // Destroy the object merge node, do not destroy other HDA (Asset input type) - if (InInput->GetInputType() != EHoudiniInputType::Asset) - { - HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); - } - } - } - - // Keep track of all the nodes plugged into our input's merge - PreviousInputObjectNodeIds = CreatedNodeIds; - - // Finally, connect our main input node to the asset - bSuccess = ConnectInputNode(InInput); - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - EHoudiniInputType InputType = InInput->GetInputType(); - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Iterate on all the input objects and see if their transform needs to be uploaded - bool bSuccess = true; - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; - if (!CurrentInputObject->HasTransformChanged()) - continue; - - // Upload the current input object's transform to Houdini - if (!UploadHoudiniInputTransform(InInput, CurrentInputObject)) - { - bSuccess = false; - continue; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::ConnectInputNode(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - HAPI_NodeId AssetNodeId = InInput->GetAssetNodeId(); - if (AssetNodeId < 0) - return false; - - HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); - if (InputNodeId < 0) - return false; - - // Helper for connecting our input or setting the object path parameter - if (InInput->IsObjectPathParameter()) - { - // Now we can assign the input node path to the parameter - std::string ParamNameString = TCHAR_TO_UTF8(*(InInput->GetName())); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue( - FHoudiniEngine::Get().GetSession(), AssetNodeId, - ParamNameString.c_str(), InputNodeId), false); - } - else - { - // TODO: CHECK ME! - //if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - // return false; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), AssetNodeId, - InInput->GetInputIndex(), InputNodeId, 0), false); - } - - return true; -} - -bool -FHoudiniInputTranslator::UploadHoudiniInputObject( - UHoudiniInput* InInput, - UHoudiniInputObject* InInputObject, - TArray& OutCreatedNodeIds) -{ - if (!InInput || !InInputObject) - return false; - - FString ObjBaseName = InInput->GetNodeBaseName(); - - bool bSuccess = true; - switch (InInputObject->Type) - { - case EHoudiniInputObjectType::Object: - { - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForObject(ObjBaseName, InInputObject); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::StaticMesh: - { - UHoudiniInputStaticMesh* InputSM = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( - ObjBaseName, InputSM, InInput->GetExportLODs(), InInput->GetExportSockets(), - InInput->GetExportColliders(), InInput->GetImportAsReference()); - - if (bSuccess) - { - // If this SM input object takes in a BP, add all its BP StaticMesh components input object node id to the created id list. - if (InputSM->bIsBlueprint()) - { - for (auto & CurSMObj : InputSM->BlueprintStaticMeshes) - OutCreatedNodeIds.Add(CurSMObj->InputObjectNodeId); - } - else - { - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - } - } - - break; - } - - case EHoudiniInputObjectType::SkeletalMesh: - { - UHoudiniInputSkeletalMesh* InputSkelMes = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(ObjBaseName, InputSkelMes); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::SceneComponent: - { - UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(ObjBaseName, InputSceneComp); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::StaticMeshComponent: - { - UHoudiniInputMeshComponent* InputSMC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( - ObjBaseName, InputSMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders(), InInput->GetImportAsReference()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - { - UHoudiniInputInstancedMeshComponent* InputISMC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( - ObjBaseName, InputISMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::SplineComponent: - { - UHoudiniInputSplineComponent* InputSpline = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(ObjBaseName, InputSpline, InInput->GetUnrealSplineResolution()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::HoudiniSplineComponent: - { - UHoudiniInputHoudiniSplineComponent* InputCurve = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::HoudiniAssetActor: - case EHoudiniInputObjectType::HoudiniAssetComponent: - { - UHoudiniInputHoudiniAsset* InputHAC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetImportAsReference()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Actor: - { - UHoudiniInputActor* InputActor = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, OutCreatedNodeIds); - break; - } - - case EHoudiniInputObjectType::Landscape: - { - UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForLandscape(ObjBaseName, InputLandscape, InInput); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Brush: - { - UHoudiniInputBrush* InputBrush = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForBrush(ObjBaseName, InputBrush, InInput->GetBoundSelectorObjectArray()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::CameraComponent: - { - UHoudiniInputCameraComponent* InputCamera = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForCamera(ObjBaseName, InputCamera); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::DataTable: - { - UHoudiniInputDataTable* InputDT = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(ObjBaseName, InputDT); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Invalid: - //default: - break; - } - - // Mark that input object as not changed - if (bSuccess) - { - InInputObject->MarkChanged(false); - InInputObject->SetNeedsToTriggerUpdate(false); - } - else - { - // We couldn't update/create that input object, keep it changed but prevent it from trigger updates - InInputObject->SetNeedsToTriggerUpdate(false); - } - - return bSuccess; -} - - -// Upload transform for an input's InputObject -bool -FHoudiniInputTranslator::UploadHoudiniInputTransform( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject) -{ - if (!InInput || !InInputObject) - return false; - - auto UpdateTransform = [](const FTransform& InTransform, const HAPI_NodeId& InNodeId) - { - // Translate the Transform to HAPI - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(InTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InNodeId, &HapiTransform), false); - - return true; - }; - - bool bSuccess = true; - switch (InInputObject->Type) - { - case EHoudiniInputObjectType::StaticMesh: - { - // Simply update the Input mesh's Transform offset - if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) - bSuccess = false; - - break; - } - - case EHoudiniInputObjectType::StaticMeshComponent: - { - // Update using the static mesh component's transform - UHoudiniInputMeshComponent* InSMC = Cast(InInputObject); - if (!InSMC || InSMC->IsPendingKill()) - { - bSuccess = false; - break; - } - - FTransform NewTransform = InSMC->GetStaticMeshComponent() ? InSMC->GetStaticMeshComponent()->GetComponentTransform() : InInputObject->Transform; - if(!UpdateTransform(NewTransform, InInputObject->InputObjectNodeId)) - bSuccess = false; - - // Update the InputObject's transform - InInputObject->Transform = NewTransform; - - break; - } - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - { - // TODO: Only update the instances transform - break; - } - - case EHoudiniInputObjectType::HoudiniSplineComponent: - { - // TODO: Simply update the curve's transform? - break; - } - - case EHoudiniInputObjectType::HoudiniAssetActor: - case EHoudiniInputObjectType::HoudiniAssetComponent: - { - // TODO: Check, nothing to do? - break; - } - - case EHoudiniInputObjectType::Actor: - { - UHoudiniInputActor* InputActor = Cast(InInputObject); - if (!InputActor || InputActor->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Update the actor's transform - // To avoid further updates - if (InputActor->GetActor()) - InputActor->Transform = InputActor->GetActor()->GetTransform(); - - // Iterate on all the actor input objects and see if their transform needs to be uploaded - // TODO? Also update the component's actor transform?? - for (auto& CurrentComponent : InputActor->ActorComponents) - { - if (!CurrentComponent || CurrentComponent->IsPendingKill()) - continue; - - if (!CurrentComponent->HasTransformChanged()) - continue; - - // Upload the current input object's transform to Houdini - if (!UploadHoudiniInputTransform(InInput, CurrentComponent)) - { - bSuccess = false; - continue; - } - } - break; - } - - case EHoudiniInputObjectType::SceneComponent: - { - UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); - if (!InputSceneComp || InputSceneComp->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Update the component transform to avoid further updates - if (InputSceneComp->GetSceneComponent()) - InputSceneComp->Transform = InputSceneComp->GetSceneComponent()->GetComponentTransform(); - - break; - } - - case EHoudiniInputObjectType::Landscape: - { - // - UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) - { - bSuccess = false; - break; - } - - // - ALandscapeProxy* Landscape = InputLandscape->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Only apply diff for landscape since the HF's transform is used for value conversion as well - FTransform CurrentTransform = InputLandscape->Transform; - FTransform NewTransform = Landscape->ActorToWorld(); - - // Only handle position/rotation differences - FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); - FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); - - // Now get the HF's current transform - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - - if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) - { - bSuccess = false; - break; - } - - // Convert it to unreal - FTransform HFTransform; - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); - - // Apply the position offset if needed - if (!PosDiff.IsZero()) - HFTransform.AddToTranslation(PosDiff); - - // Apply the rotation offset if needed - if (!RotDiff.IsIdentity()) - HFTransform.ConcatenateRotation(RotDiff); - - // Convert back to a HAPI Transform and update the HF's transform - HAPI_TransformEuler NewHAPITransform; - FHoudiniApi::TransformEuler_Init(&NewHAPITransform); - FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); - NewHAPITransform.position[1] = 0.0f; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InputLandscape->InputObjectNodeId, &NewHAPITransform)) - { - bSuccess = false; - break; - } - - // Update the cached transform - InputLandscape->Transform = NewTransform; - } - - case EHoudiniInputObjectType::Brush: - { - // TODO: Update the Brush's transform - break; - } - - // Unsupported - case EHoudiniInputObjectType::Object: - case EHoudiniInputObjectType::SkeletalMesh: - case EHoudiniInputObjectType::SplineComponent: - { - break; - } - - case EHoudiniInputObjectType::Invalid: - default: - break; - } - - // Mark that input object as not changed - if (bSuccess) - { - InInputObject->MarkTransformChanged(false); - InInputObject->SetNeedsToTriggerUpdate(false); - } - else - { - // We couldn't update/create that input object, keep it changed but prevent it from trigger updates - InInputObject->SetNeedsToTriggerUpdate(false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForObject(const FString& InObjNodeName, UHoudiniInputObject* InObject) -{ - if (!InObject) - return false; - - UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) - return true; - - FString NodeName = InObjNodeName + TEXT("_") + Object->GetName(); - - // For UObjects we can't upload much, but can still create an input node - // with a single point, with an attribute pointing to the input object's path - HAPI_NodeId InputNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); - - // Update this input object's NodeId and ObjectNodeId - InObject->InputNodeId = (int32)InputNodeId; - InObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 2; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = 1; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); - - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - FVector ObjectPosition = InObject->Transform.GetLocation(); - TArray Position = - { - ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION - }; - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Position.GetData(), 0, - AttributeInfoPoint.count), false); - } - - { - // Create point attribute info for the path. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); - - // Set the point's path attribute - FString ObjectPathName = Object->GetPathName(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - ObjectPathName, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InputNodeId), false); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( - const FString& InObjNodeName, - UHoudiniInputStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UBlueprint* BP = nullptr; - UStaticMesh* SM = nullptr; - - FString SMName = InObjNodeName + TEXT("_"); - - // Get Blueprint or StaticMesh - if (InObject->bIsBlueprint()) - { - BP = InObject->GetBlueprint(); - if (!BP || BP->IsPendingKill()) - return true; - - SMName += BP->GetName(); - } - else - { - SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - SMName += SM->GetName(); - } - - // Marshall the Static Mesh to Houdini - bool bSuccess = true; - - if (bImportAsReference) - { - // Start by getting the Object's full name - FString AssetReference; - if (SM) - AssetReference += SM->GetFullName(); - - if (BP) - AssetReference += BP->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, SMName, InObject->Transform); - } - else - { - TArray StaticMeshComponents; - - // The input object is a Blueprint, Get all its StaticMeshes - if (BP) - { - USimpleConstructionScript* SCS = BP->SimpleConstructionScript; - if (SCS && !SCS->IsPendingKill()) - { - const TArray& Nodes = SCS->GetAllNodes(); - for (auto & CurNode : Nodes) - { - if (!CurNode || CurNode->IsPendingKill()) - continue; - - UActorComponent * CurComp = CurNode->ComponentTemplate; - if (!CurComp || CurComp->IsPendingKill()) - continue; - - UStaticMeshComponent* CurSMC = Cast(CurComp); - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - UStaticMesh* CurSM = CurSMC->GetStaticMesh(); - if (CurSM && !CurSM->IsPendingKill()) - StaticMeshComponents.Add(CurSMC); - - } - } - } - - // Clear previous Blueprint Static Mesh Comps (if there is any) - InObject->BlueprintStaticMeshes.Empty(); - - // This is a BP, add all the BP SM comps to its BlueprintStaticMeshes list. - if (InObject->bIsBlueprint()) - { - for (auto & CurSMC : StaticMeshComponents) - { - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - UHoudiniInputStaticMesh* SMObject = Cast( - UHoudiniInputObject::CreateTypedInputObject(CurSMC->GetStaticMesh(), InObject, InObject->GetName() + TEXT("_") + CurSMC->GetName())); - - if (!SMObject || SMObject->IsPendingKill()) - continue; - - bSuccess &= FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - CurSMC->GetStaticMesh(), SMObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); - - InObject->SetImportAsReference(false); - - // Update this input object's OBJ NodeId - SMObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(SMObject->InputNodeId); - - // Update the component's transform - FTransform ComponentTransform = CurSMC->GetRelativeTransform(); - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), SMObject->InputObjectNodeId, &HapiTransform), false); - } - - InObject->BlueprintStaticMeshes.Add(SMObject); - } - - return true; - } - // This is a normal static mesh input, process it normally as a static mesh Input Object - else - { - bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, InObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); - } - } - - InObject->SetImportAsReference(bImportAsReference); - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - // If the Input mesh has a Transform offset - FTransform TransformOffset = InObject->Transform; - if (!TransformOffset.Equals(FTransform::Identity)) - { - // Updating the Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USkeletalMesh* SkelMesh = InObject->GetSkeletalMesh(); - if (!SkelMesh || SkelMesh->IsPendingKill()) - return true; - - // Get the SM's transform offset - FTransform TransformOffset = InObject->Transform; - - // TODO - // Support this type of input object - // FUnrealMeshTranslator::CreateInputNodeForSkeletalMesh(...) - - return HapiCreateInputNodeForObject(InObjNodeName, InObject); -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USceneComponent* SceneComp = InObject->GetSceneComponent(); - if (!SceneComp || SceneComp->IsPendingKill()) - return true; - - // Get the Scene Component's transform - FTransform TransformOffset = InObject->Transform; - - // Get the parent Actor's transform - FTransform ParentTransform = InObject->ActorTransform; - - // Dont do that! - return false; - - // TODO - // Support this type of input object - return HapiCreateInputNodeForObject(InObjNodeName, InObject); -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UStaticMeshComponent* SMC = InObject->GetStaticMeshComponent(); - if (!SMC || SMC->IsPendingKill()) - return true; - - // Get the component's Static Mesh - UStaticMesh* SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - // Marshall the Static Mesh to Houdini - FString SMCName = InObjNodeName + TEXT("_") + SMC->GetName(); - - bool bSuccess = true; - if (bImportAsReference) - { - // Start by getting the Object's full name - FString AssetReference = SM->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, SMCName, AssetReference, InObject->Transform); - - } - else - { - bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, InObject->InputNodeId, SMCName, SMC, bExportLODs, bExportSockets, bExportColliders); - } - - InObject->SetImportAsReference(bImportAsReference); - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - // Update this input object's cache data - InObject->Update(SMC); - - // Update the component's transform - FTransform ComponentTransform = InObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputInstancedMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) - return true; - - // Get the ISMC - UInstancedStaticMeshComponent* ISMC = InObject->GetInstancedStaticMeshComponent(); - if (!ISMC || ISMC->IsPendingKill()) - return true; - - HAPI_NodeId NewNodeId = -1; - if (!FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( - ISMC, InObjNodeName, NewNodeId, bExportLODs, bExportSockets, bExportColliders, false)) - return false; - - // Update this input object's node IDs - InObject->InputNodeId = NewNodeId; - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); - - // Update the component's cached instances - InObject->Update(ISMC); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USplineComponent* Spline = InObject->GetSplineComponent(); - if (!Spline || Spline->IsPendingKill()) - return true; - - - int32 NumberOfSplineControlPoints = InObject->NumberOfSplineControlPoints; - - TArray SplineControlPoints = InObject->SplineControlPoints; - - FString NodeName = InObjNodeName + TEXT("_") + InObject->GetName(); - - if (!FUnrealSplineTranslator::CreateInputNodeForSplineComponent(Spline, SplineResolution, InObject->InputNodeId, NodeName)) - return false; - - // Cache the exported curve's data to the input object - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - InObject->MarkChanged(true); - - //if (!HapiCreateInputNodeForObject(InObjNodeName, InObject)) - // return false; - - // Update the component's cached data - InObject->Update(Spline); - - // Update the component's transform - FTransform ComponentTransform = InObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UHoudiniSplineComponent* Curve = InObject->GetCurveComponent(); - if (!Curve || Curve->IsPendingKill()) - return true; - - if (!FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent(InObjNodeName, Curve)) - return false; - - // See if the component needs it node Id invalidated - //if (InObject->InputNodeId < 0) - // Curve->SetNodeId(InObject->InputNodeId); - - // Cache the exported curve's data to the input object - InObject->InputNodeId = Curve->GetNodeId(); - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - //InObject->CurveType = Curve->GetCurveType(); - //InObject->CurveMethod = Curve->GetCurveMethod(); - //InObject->Reversed = Curve->IsReversed(); - InObject->Update(Curve); - - InObject->MarkChanged(true); - - return true; -} - -bool -FHoudiniInputTranslator:: -HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UHoudiniAssetComponent* InputHAC = InObject->GetHoudiniAssetComponent(); - if (!InputHAC || InputHAC->IsPendingKill()) - return true; - - if (!InputHAC->CanDeleteHoudiniNodes()) - return true; - - UHoudiniInput* HoudiniInput = Cast(InObject->GetOuter()); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - return true; - - UHoudiniAssetComponent* OuterHAC = Cast(HoudiniInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return true; - - // Do not allow using ourself as an input, terrible things would happen - if (InputHAC->GetAssetId() == OuterHAC->GetAssetId()) - return false; - - // If previously imported as ref, delete the input node. - if (InObject->InputNodeId > -1 && InObject->GetImportAsReference()) - { - int32 PreviousInputNodeId = InObject->InputNodeId; - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InObject->GetName()); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InObject->GetName()); - } - } - - InObject->SetImportAsReference(bImportAsReference); - - // If this object is in an Asset input, we need to set the InputNodeId directl - // to avoid creating extra merge nodes. World inputs should not do that! - bool bIsAssetInput = HoudiniInput->GetInputType() == EHoudiniInputType::Asset; - - if (bImportAsReference) - { - InObject->InputNodeId = -1; - InObject->InputObjectNodeId = -1; - - if(bIsAssetInput) - HoudiniInput->SetInputNodeId(-1); - - // Start by getting the Object's full name - FString AssetReference = InputHAC->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - if (!FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform)) // do not delete previous node if it was HAC - return false; - - if (bIsAssetInput) - HoudiniInput->SetInputNodeId(InObject->InputNodeId); - } - - InputHAC->AddDownstreamHoudiniAsset(OuterHAC); - - //if (HAC->NeedsInitialization()) - // HAC->MarkAsNeedInstantiation(); - - //HoudiniInput->SetAssetNodeId(HAC->GetAssetId()); - - // TODO: This might be uneeded as this function should only be called - // after we're not wiating on the input asset... - if (InputHAC->AssetState == EHoudiniAssetState::NeedInstantiation) - { - // If the input HAC needs to be instantiated, tell it do so - InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; - // Mark this object's input as changed so we can properly update after the input HDA's done instantiating/cooking - HoudiniInput->MarkChanged(true); - } - - if (InputHAC->NeedsInitialization() || InputHAC->NeedUpdate()) - return false; - - if (!bImportAsReference) - { - if (bIsAssetInput) - HoudiniInput->SetInputNodeId(InputHAC->GetAssetId()); - - InObject->InputNodeId = InputHAC->GetAssetId(); - } - - InObject->InputObjectNodeId = InObject->InputNodeId; - - bool bReturn = InObject->InputNodeId > -1; - - if(bIsAssetInput) - bReturn = FHoudiniInputTranslator::ConnectInputNode(HoudiniInput); - - return bReturn; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (!InObject || InObject->IsPendingKill()) - return false; - - AActor* Actor = InObject->GetActor(); - if (!Actor || Actor->IsPendingKill()) - return true; - - // Check if this is a world input and if this is a HoudiniAssetActor - // If so we need to build static meshes for any proxy meshes - if (InInput->GetInputType() == EHoudiniInputType::World && Actor->IsA()) - { - AHoudiniAssetActor *HAA = Cast(Actor); - UHoudiniAssetComponent *HAC = HAA->GetHoudiniAssetComponent(); - if (HAC && !HAC->IsPendingKill()) - { - if (HAC->HasAnyCurrentProxyOutput()) - { - bool bPendingDeleteOrRebuild = false; - bool bInvalidState = false; - const bool bIsHoudiniCookedDataAvailable = HAC->IsHoudiniCookedDataAvailable(bPendingDeleteOrRebuild, bInvalidState); - if (bIsHoudiniCookedDataAvailable) - { - // Build the static mesh - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); - // Update the input object since a new StaticMeshComponent could have been created - UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) - { - InObject->Update(InputObject); - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - } - } - else if (!bPendingDeleteOrRebuild && !bInvalidState) - { - // Request a cook with no proxy output - HAC->MarkAsNeedCook(); - HAC->SetNoProxyMeshNextCookRequested(true); - } - } - else if (InObject->ActorComponents.Num() == 0 && HAC->HasAnyOutputComponent()) - { - // The HAC has non-proxy output components, but the InObject does not have any - // actor components. This can arise after a cook if previously there were only - // proxies and the input was created when there were only proxies - // Try to update the input to find new components - UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) - { - InObject->Update(InputObject); - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - } - } - } - } - - // Now, commit all of this actor's component - int32 ComponentIdx = 0; - for (UHoudiniInputSceneComponent* CurComponent : InObject->ActorComponents) - { - if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) - ComponentIdx++; - } - - // TODO: We should call Update here... - // needs to be fixed - - // Cache our transformn - InObject->Transform = Actor->GetTransform(); - - // Do something for our actor's transform? - /* - // TODO - // Support this type of input object - FString ObjNodeName = InInput->GetNodeBaseName(); - return HapiCreateInputNodeForObject(ObjNodeName, InObject); - */ - - //TODO? Check - // return true if we have at least uploaded one component - // return (ComponentIdx > 0); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( - const FString& InObjNodeName, UHoudiniInputLandscape* InObject, UHoudiniInput* InInput) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - if (!InInput || InInput->IsPendingKill()) - return false; - - ALandscapeProxy* Landscape = InObject->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) - return true; - - EHoudiniLandscapeExportType ExportType = InInput->GetLandscapeExportType(); - - bool bSucess = false; - if (ExportType == EHoudiniLandscapeExportType::Heightfield) - { - bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); - } - else - { - bool bExportLighting = InInput->bLandscapeExportLighting; - bool bExportMaterials = InInput->bLandscapeExportMaterials; - bool bExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; - bool bExportTileUVs = InInput->bLandscapeExportTileUVs; - bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; - bool bExportAsMesh = InInput->LandscapeExportType == EHoudiniLandscapeExportType::Mesh; - - bSucess = FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( - Landscape, InObject->InputNodeId, InObjNodeName, - bExportAsMesh, bExportTileUVs, bExportNormalizedUVs, bExportLighting, bExportMaterials); - } - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - InObject->Update(Landscape); - - return bSucess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForBrush(const FString& InObjNodeName, UHoudiniInputBrush* InObject, TArray* ExcludeActors) -{ - if (!IsValid(InObject)) - return false; - - ABrush* BrushActor = InObject->GetBrush(); - if (!IsValid(BrushActor)) - return true; - - if (!FUnrealBrushTranslator::CreateInputNodeForBrush(InObject, BrushActor, ExcludeActors, InObject->InputNodeId, InObjNodeName)) - return false; - - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - InObject->Update(BrushActor); - - return true; -} - - - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, UHoudiniInputCameraComponent* InInputObject) -{ - if (!InInputObject || InInputObject->IsPendingKill()) - return false; - - UCameraComponent* Camera = InInputObject->GetCameraComponent(); - if (!Camera || Camera->IsPendingKill()) - return true; - - FString NodeName = InNodeName + TEXT("_") + Camera->GetName(); - - // Create the camera OBJ. - int32 CameraNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( - -1, TEXT("Object/cam"), InNodeName, true, &CameraNodeId), false); - - // set "Pixel Aspect Ratio" (aspect) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "aspect", 0, InInputObject->AspectRatio), false); - - // set "Projection" (projection) (0 persp, 1 ortho) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "projection", 0, InInputObject->bIsOrthographic ? 1 : 0), false); - - // set Ortho Width (orthowidth) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "orthowidth", 0, InInputObject->OrthoWidth), false); - - // set Near Clippin (near) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "near", 0, InInputObject->OrthoNearClipPlane), false); - - // set far clipping (far) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "far", 0, InInputObject->OrthoFarClipPlane), false); - - // Set the transform - HAPI_TransformEuler H_Transform; - FHoudiniApi::TransformEuler_Init(&H_Transform); - FHoudiniEngineUtils::TranslateUnrealTransform(Camera->GetComponentTransform(), H_Transform); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CameraNodeId, &H_Transform), false); - - // Update the component's transform - FTransform ComponentTransform = InInputObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Camera orientation need to be adjusted - HapiTransform.rotationEuler[1] += -90.0f; - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CameraNodeId, &HapiTransform), false); - } - - // Update this input's NodeId and ObjectNodeId - InInputObject->InputNodeId = -1;// (int32)CameraNodeId; - InInputObject->InputObjectNodeId = (int32)CameraNodeId; - - // Update this input object's cache data - InInputObject->Update(Camera); - - return true; -} - -bool -FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // We need to call BuildAllInputs here to update all the inputs, - // and make sure that the object path parameter inputs' parameter ids are up to date - if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) - return false; - - // We need to update the AssetID stored on all the inputs - // and mark all the input objects for this input type as changed - int32 HACAssetId = HAC->GetAssetId(); - for (auto CurrentInput : HAC->Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // - CurrentInput->SetAssetNodeId(HACAssetId); - - // We need to delete the nodes created for the input objects if they are valid - // (since the node IDs are transients, this likely means we're handling a recook/rebuild - // and therefore expect to recreate the input nodes) - DestroyInputNodes(CurrentInput, CurrentInput->GetInputType()); - } - - return true; -} - - - -bool -FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Only tick/cook when in Editor - // This prevents PIE cooks or runtime cooks due to inputs moving - AActor* ActorOwner = HAC->GetOwner(); - if (ActorOwner) - { - if (!ActorOwner->GetWorld() || (ActorOwner->GetWorld()->WorldType != EWorldType::Editor)) - return false; - } - -#if WITH_EDITOR - // Stop outliner objects from causing recooks while input objects are dragged around - if (FHoudiniMoveTracker::Get().IsObjectMoving) - { - //HOUDINI_LOG_MESSAGE(TEXT("Object moving, not updating world inputs!")); - return false; - } -#endif - - for (auto CurrentInput : HAC->Inputs) - { - if (!CurrentInput) - continue; - if (CurrentInput->GetInputType() != EHoudiniInputType::World) - continue; - - UpdateWorldInput(CurrentInput); - } - - return true; -} - -bool -FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (InInput->GetInputType() != EHoudiniInputType::World) - return false; - - TArray* InputObjectsPtr = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjectsPtr) - return false; - - bool bHasChanged = false; - if (InInput->IsWorldInputBoundSelector() && InInput->GetWorldInputBoundSelectorAutoUpdates()) - { - // If the input is in bound selector mode, and auto-update is enabled - // update the actors selected by the bounds first - bHasChanged = InInput->UpdateWorldSelectionFromBoundSelectors(); - } - - // See if we need to update the components for this input - // look for deleted actors/components - TArray ObjectToDeleteIndices; - for(int32 InputObjIdx = 0; InputObjIdx < InputObjectsPtr->Num(); InputObjIdx++) - { - UHoudiniInputActor* ActorObject = Cast((*InputObjectsPtr)[InputObjIdx]); - if (!ActorObject || ActorObject->IsPendingKill()) - continue; - - // Make sure the actor is still valid - bool bValidActorObject = ActorObject->GetActor() && !ActorObject->GetActor()->IsPendingKill(); - - // For BrushActors, the brush and actors must be valid as well - UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); - if (bValidActorObject && BrushActorObject) - { - ABrush* BrushActor = BrushActorObject->GetBrush(); - if (!IsValid(BrushActor)) - bValidActorObject = false; - else if (!IsValid(BrushActor->Brush)) - bValidActorObject = false; - } - - // The actor is no longer valid, mark it for deletion - if (!bValidActorObject) - { - if ((ActorObject->InputNodeId > 0) || (ActorObject->InputObjectNodeId > 0)) - { - ActorObject->InvalidateData(); - // We only need to update the input if the actors nodes were created in Houdini - bHasChanged = true; - } - - // Delete the Actor object - ObjectToDeleteIndices.Add(InputObjIdx); - continue; - } - - if (ActorObject->HasActorTransformChanged()) - { - ActorObject->MarkTransformChanged(true); - bHasChanged = true; - } - - if (ActorObject->HasContentChanged()) - { - ActorObject->MarkChanged(true); - bHasChanged = true; - } - - // Iterates on all of the actor's component - TArray ComponentToDeleteIndices; - for (int32 CompIdx = 0; CompIdx < ActorObject->ActorComponents.Num(); CompIdx++) - { - UHoudiniInputSceneComponent* CurActorComp = ActorObject->ActorComponents[CompIdx]; - if (!CurActorComp || CurActorComp->IsPendingKill()) - continue; - - // Make sure the actor is still valid - if (!CurActorComp->InputObject || CurActorComp->InputObject->IsPendingKill()) - { - // If it's not, mark it for deletion - if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) - { - CurActorComp->InvalidateData(); - - // We only need to update the input if the object were created in Houdini - bHasChanged = true; - } - - // Delete the component object - ComponentToDeleteIndices.Add(CompIdx); - - continue; - } - - if (CurActorComp->HasComponentTransformChanged()) - { - CurActorComp->MarkTransformChanged(true); - bHasChanged = true; - } - - if (CurActorComp->HasComponentChanged()) - { - CurActorComp->MarkChanged(true); - bHasChanged = true; - } - } - - // Delete the components objects on the actor that were marked for deletion - for (int32 ToDeleteIdx = ComponentToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) - ActorObject->ActorComponents.RemoveAt(ComponentToDeleteIndices[ToDeleteIdx]); - } - - // Delete the actor objects that were marked for deletion - for (int32 ToDeleteIdx = ObjectToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) - InputObjectsPtr->RemoveAt(ObjectToDeleteIndices[ToDeleteIdx]); - - // Mark the input as changed if need so it will trigger an upload - if (bHasChanged) - InInput->MarkChanged(true); - - return true; -} - - -bool -FHoudiniInputTranslator::CreateInputNodeForReference( - HAPI_NodeId& InputNodeId, - const FString & InRef, - const FString & InputNodeName, - const FTransform& InTransform) -{ - HAPI_NodeId NewNodeId = -1; - - // Create a single input node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_UTF8(*InputNodeName)), false); - - /* - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, nullptr), false); - */ - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) - return false; - - // We have now created a valid new input node, delete the previous one - HAPI_NodeId PreviousInputNodeId = InputNodeId; - if (PreviousInputNodeId >= 0) - { - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); - } - } - - // Create and initialize a part containing one point with a point attribute - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - - PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = 1; - PartInfo.vertexCount = 0; - PartInfo.faceCount = 0; - PartInfo.pointCount = 1; - PartInfo.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, &PartInfo), false); - - // Point Position Attribute - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - FVector ObjectPosition = InTransform.GetLocation(); - TArray Position = - { - ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION - }; - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Position.GetData(), 0, - AttributeInfoPoint.count), false); - } - - // String Attribute - { - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint), false); - - // Set string attribute - std::string AttriString = TCHAR_TO_ANSI(*InRef); - const char* AttriStringRaw = AttriString.c_str(); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NewNodeId, 0, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint, - &AttriStringRaw, 0, 1), false); - } - - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NewNodeId), false); - - InputNodeId = NewNodeId; - return true; -} - - - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeName, UHoudiniInputDataTable* InInputObject) -{ - //TODO - if (!InInputObject || InInputObject->IsPendingKill()) - return false; - - UDataTable* DataTable = InInputObject->GetDataTable(); - if (!DataTable || DataTable->IsPendingKill()) - return true; - - // Get the DataTable data as string - TArray> TableData = DataTable->GetTableData(EDataTableExportFlags::None); - if (TableData.Num() <= 1) - return true; - - int32 NumRows = TableData.Num() - 1; - int32 NumAttributes = TableData[0].Num(); - if (NumRows <= 0 || NumAttributes <= 0) - return true; - - // Create the input node - FString NodeName = InNodeName + TEXT("_") + DataTable->GetName(); - HAPI_NodeId InputNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); - - // Update this input object's NodeId and ObjectNodeId - InInputObject->InputNodeId = (int32)InputNodeId; - InInputObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = NumAttributes; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = NumRows; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); - - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - TArray Positions; - Positions.SetNum(NumRows * 3); - for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) - { - Positions[RowIdx * 3] = 0.0f; - Positions[RowIdx * 3 + 1] = (float)RowIdx; - Positions[RowIdx * 3 + 2] = 0.0f; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Positions.GetData(), 0, - AttributeInfoPoint.count), false); - } - - { - // Create point attribute info for the path. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); - - // Get the object path - FString ObjectPathName = DataTable->GetPathName(); - - // Create an array - TArray ObjectPaths; - ObjectPaths.Init(ObjectPathName, NumRows); - - // Set the point's path attribute - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - ObjectPaths, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); - } - - { - // Create point attribute info for data table RowTable class name - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, &AttributeInfoPoint), false); - - // Get the object path - FString RowStructName = DataTable->GetRowStructName().ToString(); - - // Create an array - TArray RowStructNames; - RowStructNames.Init(RowStructName, NumRows); - - // Set the point's path attribute - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - RowStructNames, InputNodeId, 0, - HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, AttributeInfoPoint), false); - } - - // Now set the attributes values for each "point" of the data table - for (int32 ColIdx = 0; ColIdx < NumAttributes; ColIdx++) - { - // attribute name is "unreal_data_table_COL_NAME" - FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + TableData[0][ColIdx]; - - // We need to gt all values for that attribute - TArray AttributeValues; - AttributeValues.SetNum(NumRows); - for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) - { - AttributeValues[RowIdx] = TableData[RowIdx + 1][ColIdx]; - } - - // Create a point attribute info - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = NumRows; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_POINT; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - TCHAR_TO_ANSI(*CurAttrName), &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - AttributeValues, InputNodeId, 0, - CurAttrName, AttributeInfo), false); - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InputNodeId), false); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr), false); - - return true; -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputTranslator.h" + +#include "HoudiniInput.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInputObject.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniAssetActor.h" +#include "HoudiniOutputTranslator.h" +#include "UnrealBrushTranslator.h" +#include "UnrealSplineTranslator.h" +#include "UnrealMeshTranslator.h" +#include "UnrealInstanceTranslator.h" +#include "UnrealLandscapeTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "Engine/Brush.h" +#include "Engine/DataTable.h" +#include "Camera/CameraComponent.h" + +#include "Engine/SimpleConstructionScript.h" +#include "Engine/SCS_Node.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Editor/UnrealEdEngine.h" + #include "UnrealEdGlobals.h" +#endif + +#include "HCsgUtils.h" + +#include "Async/Async.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#if WITH_EDITOR +// Allows checking of objects currently being dragged around +struct FHoudiniMoveTracker +{ + FHoudiniMoveTracker() : IsObjectMoving(false) + { + GEditor->OnBeginObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = true; }); + GEditor->OnEndObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + + GEditor->OnBeginCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + GEditor->OnEndCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + } + static FHoudiniMoveTracker& Get() { static FHoudiniMoveTracker Instance; return Instance; } + + bool IsObjectMoving; +}; +#endif + +// +bool +FHoudiniInputTranslator::UpdateInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) + { + // Failed to create the inputs + return false; + } + + return true; +} + +bool +FHoudiniInputTranslator::BuildAllInputs( + const HAPI_NodeId& AssetId, + class UObject* InOuterObject, + TArray& Inputs, + TArray& Parameters) +{ + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Start by getting the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // Get the number of geo (SOP) inputs + int32 InputCount = AssetInfo.geoInputCount; + /* + // It's best to update the input count even if the hda hasnt cooked + // as it can cause loaded geo inputs to disappear upon loading the level + if ( AssetInfo.hasEverCooked ) + { + InputCount = AssetInfo.geoInputCount; + } + */ + // Also look for object path parameters inputs + TArray> InputParameters; + for (auto Param : Parameters) + { + if (Param->GetParameterType() == EHoudiniParameterType::Input) + InputParameters.Add(Param); + } + + InputCount += InputParameters.Num(); + + // Append new inputs as needed + if (InputCount > Inputs.Num()) + { + int32 NumNewInputs = InputCount - Inputs.Num(); + for (int32 InputIdx = Inputs.Num(); InputIdx < InputCount; ++InputIdx) + { + FString InputObjectName = TEXT("Input") + FString::FromInt(InputIdx + 1); + UHoudiniInput * NewInput = NewObject< UHoudiniInput >( + InOuterObject, + UHoudiniInput::StaticClass(), + FName(*InputObjectName), + RF_Transactional); + + if (!NewInput || NewInput->IsPendingKill()) + { + //HOUDINI_LOG_WARNING("Failed to create asset input"); + continue; + } + // Create a default curve object here to avoid Transaction issue + //NewInput->CreateDefaultCurveInputObject(); + + Inputs.Add(NewInput); + } + } + else if (InputCount < Inputs.Num()) + { + // TODO: Properly clean up the input object + created nodes? + for (int32 InputIdx = Inputs.Num() - 1; InputIdx >= InputCount; InputIdx--) + { + UHoudiniInput* CurrentInput = Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + FHoudiniInputTranslator::DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); + + // DO NOT MANUALLY DESTROY THE OLD/DANGLING INPUTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + //CurrentInput->ConditionalBeginDestroy(); + //CurrentInput = nullptr; + } + + Inputs.SetNum(InputCount); + } + + // Now, check the inputs in the array match the geo inputs + //for (int32 GeoInIdx = 0; GeoInIdx < AssetInfo.geoInputCount; GeoInIdx++) + bool bBlueprintStructureChanged = false; + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) + { + UHoudiniInput* CurrentInput = Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // Create default Name/Label/Help + FString CurrentInputName = TEXT("Input") + FString::FromInt(InputIdx + 1); + FString CurrentInputLabel = CurrentInputName; + FString CurrentInputHelp; + + // Set the nodeId + CurrentInput->SetAssetNodeId(AssetId); + + // Is this an object path parameter input? + bool bIsObjectPath = InputIdx >= AssetInfo.geoInputCount; + if (!bIsObjectPath) + { + // Mark this input as a SOP input + CurrentInput->SetSOPInput(InputIdx); + + // Get and set the name + HAPI_StringHandle InputStringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInputName( + FHoudiniEngine::Get().GetSession(), + AssetId, InputIdx, &InputStringHandle)) + { + FHoudiniEngineString HoudiniEngineString(InputStringHandle); + HoudiniEngineString.ToFString(CurrentInputLabel); + } + } + else + { + // Get this input's parameter index in the objpath param array + int32 CurrentParmIdx = InputIdx - AssetInfo.geoInputCount; + + UHoudiniParameter* CurrentParm = nullptr; + if (InputParameters.IsValidIndex(CurrentParmIdx)) + { + if (InputParameters[CurrentParmIdx].IsValid()) + CurrentParm = InputParameters[CurrentParmIdx].Get(); + } + + int32 ParmId = -1; + if (CurrentParm && !CurrentParm->IsPendingKill()) + { + ParmId = CurrentParm->GetParmId(); + CurrentInputName = CurrentParm->GetParameterName(); + CurrentInputLabel = CurrentParm->GetParameterLabel(); + CurrentInputHelp = CurrentParm->GetParameterHelp(); + } + + UHoudiniParameterOperatorPath* CurrentObjPathParm = Cast(CurrentParm); + if (CurrentObjPathParm && !CurrentObjPathParm->IsPendingKill()) + { + CurrentObjPathParm->HoudiniInput = CurrentInput; + } + + // Mark this input as an object path parameter input + CurrentInput->SetObjectPathParameter(ParmId); + } + + CurrentInput->SetName(CurrentInputName); + CurrentInput->SetLabel(CurrentInputLabel); + + if ( CurrentInputHelp.IsEmpty() ) + { + CurrentInputHelp = CurrentInputLabel + TEXT("(") + CurrentInputName + TEXT(")"); + } + CurrentInput->SetHelp(CurrentInputHelp); + + // If the input type is invalid, + // We need to initialize its default + if (CurrentInput->GetInputType() == EHoudiniInputType::Invalid) + { + // Initialize it to the default corresponding to its name + CurrentInput->SetInputType(GetDefaultInputTypeFromLabel(CurrentInputLabel), bBlueprintStructureChanged); + + // Preset the default HDA for objpath input + SetDefaultAssetFromHDA(CurrentInput); + } + + // Update input objects data on UE side for all types of inputs. + switch (CurrentInput->GetInputType()) + { + case EHoudiniInputType::Curve: + FHoudiniSplineTranslator::UpdateHoudiniInputCurves(CurrentInput); + break; + case EHoudiniInputType::Landscape: + //FUnrealLandscapeTranslator::UpdateHoudiniInputLandscapes(CurrentInput); + break; + case EHoudiniInputType::Asset: + break; + case EHoudiniInputType::Geometry: + break; + case EHoudiniInputType::Skeletal: + break; + case EHoudiniInputType::World: + break; + default: + break; + } + } + + return true; +} + +bool +FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + if (!InputToDestroy || InputToDestroy->IsPendingKill()) + return false; + + // Start by disconnecting the input / nullifying the object path parameter + if (InputToDestroy->IsObjectPathParameter()) + { + // Just set the objpath parameter to null + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + InputToDestroy->GetAssetNodeId(), "", + InputToDestroy->GetParameterId(), 0); + } + else + { + // Get the asset / created input node ID + HAPI_NodeId HostAssetId = InputToDestroy->GetAssetNodeId(); + HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); + + // Only disconnect if both are valid + if (HostAssetId >= 0 && CreatedInputId >= 0) + { + FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), + HostAssetId, InputToDestroy->GetInputIndex()); + } + } + + if (InputType == EHoudiniInputType::Asset) + { + // TODO: + // If we're an asset input, just remove us from the downstream connection on the input HDA + // then reset this input's flag + + // TODO: Check this? Clean our DS assets?? why?? likely uneeded + UHoudiniAssetComponent* OuterHAC = Cast(InputToDestroy->GetOuter()); + if (OuterHAC) + OuterHAC->ClearDownstreamHoudiniAsset(); + + InputToDestroy->SetInputNodeId(-1); + } + + return true; +} + +bool +FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + if (!InputToDestroy || InputToDestroy->IsPendingKill()) + return false; + + if (!InputToDestroy->CanDeleteHoudiniNodes()) + return false; + + // If we're destroying an asset input, don't destroy anything as we don't want to destroy the input HDA + // a simple disconnect is sufficient + if (InputType == EHoudiniInputType::Asset) + return true; + + // Destroy the nodes created by all the input objects + TArray CreatedInputDataAssetIds = InputToDestroy->GetCreatedDataNodeIds(); + TArray* InputObjectNodes = InputToDestroy->GetHoudiniInputObjectArray(InputType); + if (InputObjectNodes) + { + for (auto CurInputObject : *InputObjectNodes) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + if (CurInputObject->Type == EHoudiniInputObjectType::HoudiniAssetComponent) + { + // Remove this input object's node Id from the + // CreatedInputDataAssetIds array to avoid its deletion further down + CreatedInputDataAssetIds.Remove(CurInputObject->InputNodeId); + CurInputObject->InputNodeId = -1; + CurInputObject->InputObjectNodeId = -1; + continue; + } + + // For Actor input objects, set the input node id for all component objects to -1, + if (CurInputObject->Type == EHoudiniInputObjectType::Actor) + { + UHoudiniInputActor* CurActorInputObject = Cast(CurInputObject); + if (CurActorInputObject) + { + for (auto & CurActorComponent : CurActorInputObject->ActorComponents) + { + if (!CurActorComponent || CurActorComponent->IsPendingKill()) + continue; + + // No need to delete the nodes created for an asset component manually here, + // As they will be deleted when we clean up the CreateNodeIds array + CurActorComponent->InputNodeId = -1; + } + } + } + // No need to delete the nodes created for an asset component manually here, + // As they will be deleted when we clean up the CreateNodeIds array + + if (CurInputObject->InputNodeId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputNodeId); + CurInputObject->InputNodeId = -1; + } + + if(CurInputObject->InputObjectNodeId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputObjectNodeId); + CurInputObject->InputObjectNodeId = -1; + + // TODO: CHECK ME! + HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CurInputObject->InputObjectNodeId); + + // Delete its parent node as well + if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentNodeId)) + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentNodeId); + } + + // Also directly invalidate HoudiniSplineComponent's node IDs. + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast(CurInputObject); + if (IsValid(HoudiniSplineInputObject) && !IsGarbageCollecting()) + { + UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (SplineComponent && !SplineComponent->IsPendingKill()) + { + SplineComponent->SetNodeId(-1); + } + } + + CurInputObject->MarkChanged(true); + } + } + + // Destroy all the input assets + for (HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds) + { + if (AssetNodeId < 0) + continue; + + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), AssetNodeId); + } + CreatedInputDataAssetIds.Empty(); + + // Then simply destroy the input's parent OBJ node + if (InputToDestroy->GetInputNodeId() >= 0) + { + HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedInputId); + + if (CreatedInputId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CreatedInputId); + InputToDestroy->SetInputNodeId(-1); + } + + if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentId)) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentId); + } + } + + return true; +} + +bool +FHoudiniInputTranslator::DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + // Start by disconnecting the input/object merge + bool bSuccess = DisconnectInput(InputToDestroy, InputType); + + // Then destroy the created input nodes + bSuccess &= DestroyInputNodes(InputToDestroy, InputType); + + return bSuccess; +} + + +EHoudiniInputType +FHoudiniInputTranslator::GetDefaultInputTypeFromLabel(const FString& InputName) +{ + // We'll try to find these magic words to try to detect the default input type + //FString geoPrefix = TEXT("geo"); + FString curvePrefix = TEXT("curve"); + + FString landscapePrefix = TEXT("landscape"); + FString landscapePrefix2 = TEXT("terrain"); + FString landscapePrefix3 = TEXT("heightfield"); + + FString worldPrefix = TEXT("world"); + FString worldPrefix2 = TEXT("outliner"); + + FString assetPrefix = TEXT("asset"); + FString assetPrefix2 = TEXT("hda"); + + // By default, geometry input is chosen. + EHoudiniInputType InputType = EHoudiniInputType::Geometry; + + if (InputName.Contains(curvePrefix, ESearchCase::IgnoreCase)) + InputType = EHoudiniInputType::Curve; + + else if ((InputName.Contains(landscapePrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(landscapePrefix2, ESearchCase::IgnoreCase)) + || (InputName.Contains(landscapePrefix3, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::Landscape; + + else if ((InputName.Contains(worldPrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(worldPrefix2, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::World; + + else if ((InputName.Contains(assetPrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(assetPrefix2, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::Asset; + + return InputType; +} + +bool +FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bForce) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (!InInput->HasInputTypeChanged() && !bForce) + return true; + + // - Handle switching AWAY from an input type + DisconnectAndDestroyInput(InInput, InInput->GetPreviousInputType()); + + // Invalidate the previous input type now that we've actually changed + //InInput->SetPreviousInputType(EHoudiniInputType::Invalid); + + //ChangeInputType(InInput, NewType); + + // TODO: + // - Handle updating to the new input type + // downstream asset connection, static mesh update, curve creation... + + // Mark all the objects from this input has changed so they upload themselves + InInput->MarkAllInputObjectsChanged(true); + + return true; +} + +bool +FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input) +{ + // + if (!Input || Input->IsPendingKill()) + return false; + + // We just handle geo inputs + if (EHoudiniInputType::Geometry != Input->GetInputType()) + return false; + + // Make sure we're linked to a valid object path parameter + if (Input->GetParameterId() < 0) + return false; + + // There is a default slot, don't add if slot is already filled + //if (InputObjects.Num() > 1) + // return false; + + // Get our ParmInfo + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + Input->GetAssetNodeId(), Input->GetParameterId(), &FoundParamInfo)) + { + return false; + } + + // TODO: FINISH ME! + + /* + // Get our string value + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + Input->GetInputNodeId(), false, + &StringHandle, FoundParamInfo.stringValuesIndex, 1) ) + { + FString OutValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(OutValue)) + { + // Set default object on the HDA instance - will override the parameter string + // and apply the object input local-path thing for the HDA cook. + if (OutValue.Len() > 0) + { + UObject * pObject = LoadObject(nullptr, *OutValue); + if (pObject) + { + return AddInputObject(pObject); + } + } + } + } + */ + + return false; +} + +bool +FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + //for (auto CurrentInput : HAC->Inputs) + for(int32 InputIdx = 0; InputIdx < HAC->GetNumInputs(); InputIdx++) + { + UHoudiniInput*& CurrentInput = HAC->Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill() || !CurrentInput->HasChanged()) + continue; + + // First thing, see if we need to change the input type + if (CurrentInput->HasInputTypeChanged()) + { + ChangeInputType(CurrentInput, false); + } + + if (CurrentInput->GetInputType() == EHoudiniInputType::Landscape && CurrentInput->HasLandscapeExportTypeChanged()) + { + DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); + CurrentInput->MarkAllInputObjectsChanged(true); + CurrentInput->SetHasLandscapeExportTypeChanged(false); + } + + bool bSuccess = true; + if (CurrentInput->IsDataUploadNeeded()) + { + bSuccess &= UploadInputData(CurrentInput); + CurrentInput->MarkDataUploadNeeded(!bSuccess); + } + + if (CurrentInput->IsTransformUploadNeeded()) + { + bSuccess &= UploadInputTransform(CurrentInput); + } + + // Update the input properties AFTER eventually uploading it + bSuccess = UpdateInputProperties(CurrentInput); + + if (bSuccess) + { + CurrentInput->MarkChanged(false); + CurrentInput->MarkAllInputObjectsChanged(false); + } + + if (CurrentInput->HasInputTypeChanged()) + CurrentInput->SetPreviousInputType(EHoudiniInputType::Invalid); + + // Even if we failed, no need to try updating again. + CurrentInput->SetNeedsToTriggerUpdate(false); + } + + return true; +} + +bool +FHoudiniInputTranslator::UpdateInputProperties(UHoudiniInput* InInput) +{ + bool bSucess = UpdateTransformType(InInput); + + bSucess &= UpdatePackBeforeMerge(InInput); + + bSucess &= UpdateTransformOffset(InInput); + + return bSucess; +} + +bool +FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + bool nTransformType = InInput->GetKeepWorldTransform(); + + // Geometry inputs are always set to none + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType == EHoudiniInputType::Geometry) + nTransformType = 0; + + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); + + bool bSuccess = true; + const std::string sXformType = "xformtype"; + if (InInput->IsObjectPathParameter()) + { + // Directly change the Parameter xformtype + // (This will only work if the object merge is editable/unlocked) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + HostAssetId, sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + else + { + // Query the object merge's node ID via the input + if (HAPI_RESULT_SUCCESS == FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + HostAssetId, InInput->GetInputIndex(), &InputNodeId)) + { + // Change its Parameter xformtype + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + InputNodeId, sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + } + + // Since our input objects are all plugged into a merge node + // We want to also update the transform type on the object merge plugged into the merge node + HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); + if ((ParentNodeId >= 0) && (InputType != EHoudiniInputType::Geometry) && (InputType != EHoudiniInputType::Asset)) + { + HAPI_NodeId InputObjectNodeId = -1; + int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); + for (int n = 0; n < NumberOfInputMeshes; n++) + { + // Get the Input node ID from the host ID + InputObjectNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ParentNodeId, n, &InputObjectNodeId)) + continue; + + if (InputObjectNodeId == -1) + continue; + + // Change the xformtype parameter on the object merge + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId, + sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + // Pack before merge is only available for Geo/World input + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType != EHoudiniInputType::World + && InputType != EHoudiniInputType::Geometry) + { + // Nothing to change + return true; + } + + uint32 nPackValue = InInput->GetPackBeforeMerge() ? 1 : 0; + + // Get the Input node ID from the host ID + HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); + + bool bSuccess = true; + const std::string sPack = "pack"; + + // We'll be going through each input object plugged in the input's merge node + // and change the pack parameter there + HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); + if (ParentNodeId >= 0) + { + HAPI_NodeId InputObjectNodeId = -1; + int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); + for (int n = 0; n < NumberOfInputMeshes; n++) + { + // Get the Input node ID from the host ID + InputObjectNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ParentNodeId, n, &InputObjectNodeId)) + continue; + + if (InputObjectNodeId == -1) + continue; + + // Change the pack parameter on the object merge + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId, + sPack.c_str(), 0, nPackValue)) + bSuccess = false; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + // Transform offsets are only for geometry inputs + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType != EHoudiniInputType::Geometry) + { + // Nothing to change + return true; + } + + // Get the input objects + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Update each object's transform offset + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + // If the Input mesh has a Transform offset + FTransform TransformOffset = CurrentInputObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CurrentInputObject->InputObjectNodeId, &HapiTransform), false); + } + } + + return true; +} + +bool +FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + EHoudiniInputType InputType = InInput->GetInputType(); + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Iterate on all the input objects and see if they need to be uploaded + bool bSuccess = true; + TArray CreatedNodeIds; + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; + if (!CurrentInputObject->HasChanged() && CurrentInputObjectNodeId >= 0) + { + // If this object hasn't changed, no need to upload it + // but we need to keep its created input node + if (CurrentInputObject->Type == EHoudiniInputObjectType::Actor) + { + // If this input object is an actor, it actually contains other input + // objects for each of his components, keep them as well + UHoudiniInputActor* InputActor = Cast(CurrentInputObject); + if (InputActor && !InputActor->IsPendingKill()) + { + for (auto CurrentComp : InputActor->ActorComponents) + { + if (!CurrentComp || CurrentComp->IsPendingKill()) + continue; + + int32& CurrentCompNodeId = CurrentComp->InputObjectNodeId; + if (!CurrentComp->HasChanged() && CurrentCompNodeId >= 0) + { + // If the component hasnt changed and is valid, keep it + CreatedNodeIds.Add(CurrentCompNodeId); + } + else + { + // Upload the component input object to Houdini + if (!UploadHoudiniInputObject(InInput, CurrentComp, CreatedNodeIds)) + bSuccess = false; + } + } + } + } + else + { + // No changes, keep it + CreatedNodeIds.Add(CurrentInputObjectNodeId); + } + } + else + { + // Upload the current input object to Houdini + if (!UploadHoudiniInputObject(InInput, CurrentInputObject, CreatedNodeIds)) + bSuccess = false; + } + } + + // If we haven't created any input, invalidate our input node id + if (CreatedNodeIds.Num() == 0) + { + if (!InInput->HasInputTypeChanged()) + { + int32 InputNodeId = InInput->GetInputNodeId(); + TArray PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); + + if (InInput->GetInputType() == EHoudiniInputType::Asset) + { + UHoudiniAssetComponent * OuterHAC = Cast(InInput->GetOuter()); + HAPI_NodeId AssetId = OuterHAC->GetAssetId(); + + // Disconnect the asset input + if (InputNodeId >= 0 && InInput->GetInputIndex() >= 0) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), AssetId, InInput->GetInputIndex())); + } + } + else if (InInput->GetInputType() == EHoudiniInputType::World) + { + // World nodes are handled by InputObjects () (with FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete) + } + else + { + if (InputNodeId >= 0) + { + for (int32 Idx = 0; Idx < PreviousInputObjectNodeIds.Num(); Idx++) + { + + // Get the object merge connected to the merge node + HAPI_NodeId InputObjectMergeId = -1; + HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); + + // Disconnect the two nodes + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); + + // Destroy the object merge node, do not delete other HDA (Asset input type) + HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); + } + } + } + } + InInput->GetCreatedDataNodeIds().Empty(); + InInput->SetInputNodeId(-1); + return bSuccess; + } + + // Get the current input's NodeId + HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); + // Check that the current input's node ID is still valid + if (InputNodeId < 0 || !FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + { + // This input doesn't have a valid NodeId yet, + // we need to create this input's merge node and update this input's node ID + FString MergeName = InInput->GetNodeBaseName() + TEXT("_Merge"); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/merge"), MergeName, true, &InputNodeId), false); + + InInput->SetInputNodeId(InputNodeId); + } + + //TODO: + // Do we want to update the input's transform? + if (false) + { + FTransform ComponentTransform = FTransform::Identity; + USceneComponent* OuterComp = Cast(InInput->GetOuter()); + if (OuterComp && !OuterComp->IsPendingKill()) + ComponentTransform = OuterComp->GetComponentTransform(); + + FHoudiniEngineUtils::HapiSetAssetTransform(InputNodeId, ComponentTransform); + //HapiUpdateInputNodeTransform(InputNodeId, ComponentTransform); + } + + // Connect all the input objects to the merge node now + int32 InputIndex = 0; + for (auto CurrentNodeId : CreatedNodeIds) + { + if (CurrentNodeId < 0) + continue; + + if (InputNodeId == CurrentNodeId) + continue; + + // Connect the current input object to the merge node + HOUDINI_CHECK_ERROR(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + InputNodeId, InputIndex++, CurrentNodeId, 0)); + } + + // Check if we need to disconnect extra input objects nodes from the merge + // This can be needed when the input had more input objects on the previous cook + TArray& PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); + if (!InInput->HasInputTypeChanged()) + { + for (int32 Idx = CreatedNodeIds.Num(); Idx < PreviousInputObjectNodeIds.Num(); Idx++) + { + // Get the object merge connected to the merge node + HAPI_NodeId InputObjectMergeId = -1; + if (InInput->GetInputType() != EHoudiniInputType::Asset) + HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); + + // Disconnect the two nodes + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); + + // Destroy the object merge node, do not destroy other HDA (Asset input type) + if (InInput->GetInputType() != EHoudiniInputType::Asset) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); + } + } + } + + // Keep track of all the nodes plugged into our input's merge + PreviousInputObjectNodeIds = CreatedNodeIds; + + // Finally, connect our main input node to the asset + bSuccess = ConnectInputNode(InInput); + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + EHoudiniInputType InputType = InInput->GetInputType(); + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Iterate on all the input objects and see if their transform needs to be uploaded + bool bSuccess = true; + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; + if (!CurrentInputObject->HasTransformChanged()) + continue; + + // Upload the current input object's transform to Houdini + if (!UploadHoudiniInputTransform(InInput, CurrentInputObject)) + { + bSuccess = false; + continue; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::ConnectInputNode(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + HAPI_NodeId AssetNodeId = InInput->GetAssetNodeId(); + if (AssetNodeId < 0) + return false; + + HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); + if (InputNodeId < 0) + return false; + + // Helper for connecting our input or setting the object path parameter + if (InInput->IsObjectPathParameter()) + { + // Now we can assign the input node path to the parameter + std::string ParamNameString = TCHAR_TO_UTF8(*(InInput->GetName())); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue( + FHoudiniEngine::Get().GetSession(), AssetNodeId, + ParamNameString.c_str(), InputNodeId), false); + } + else + { + // TODO: CHECK ME! + //if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + // return false; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), AssetNodeId, + InInput->GetInputIndex(), InputNodeId, 0), false); + } + + return true; +} + +bool +FHoudiniInputTranslator::UploadHoudiniInputObject( + UHoudiniInput* InInput, + UHoudiniInputObject* InInputObject, + TArray& OutCreatedNodeIds) +{ + if (!InInput || !InInputObject) + return false; + + FString ObjBaseName = InInput->GetNodeBaseName(); + + bool bSuccess = true; + switch (InInputObject->Type) + { + case EHoudiniInputObjectType::Object: + { + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForObject(ObjBaseName, InInputObject); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::StaticMesh: + { + UHoudiniInputStaticMesh* InputSM = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( + ObjBaseName, InputSM, InInput->GetExportLODs(), InInput->GetExportSockets(), + InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + { + // If this SM input object takes in a BP, add all its BP StaticMesh components input object node id to the created id list. + if (InputSM->bIsBlueprint()) + { + for (auto & CurSMObj : InputSM->BlueprintStaticMeshes) + OutCreatedNodeIds.Add(CurSMObj->InputObjectNodeId); + } + else + { + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + } + } + + break; + } + + case EHoudiniInputObjectType::SkeletalMesh: + { + UHoudiniInputSkeletalMesh* InputSkelMes = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(ObjBaseName, InputSkelMes); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::SceneComponent: + { + UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(ObjBaseName, InputSceneComp); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::StaticMeshComponent: + { + UHoudiniInputMeshComponent* InputSMC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( + ObjBaseName, InputSMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + { + UHoudiniInputInstancedMeshComponent* InputISMC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( + ObjBaseName, InputISMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::SplineComponent: + { + UHoudiniInputSplineComponent* InputSpline = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(ObjBaseName, InputSpline, InInput->GetUnrealSplineResolution()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::HoudiniSplineComponent: + { + UHoudiniInputHoudiniSplineComponent* InputCurve = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::HoudiniAssetActor: + case EHoudiniInputObjectType::HoudiniAssetComponent: + { + UHoudiniInputHoudiniAsset* InputHAC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Actor: + { + UHoudiniInputActor* InputActor = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, OutCreatedNodeIds); + break; + } + + case EHoudiniInputObjectType::Landscape: + { + UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForLandscape(ObjBaseName, InputLandscape, InInput); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Brush: + { + UHoudiniInputBrush* InputBrush = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForBrush(ObjBaseName, InputBrush, InInput->GetBoundSelectorObjectArray()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::CameraComponent: + { + UHoudiniInputCameraComponent* InputCamera = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForCamera(ObjBaseName, InputCamera); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::DataTable: + { + UHoudiniInputDataTable* InputDT = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(ObjBaseName, InputDT); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Invalid: + //default: + break; + } + + // Mark that input object as not changed + if (bSuccess) + { + InInputObject->MarkChanged(false); + InInputObject->SetNeedsToTriggerUpdate(false); + } + else + { + // We couldn't update/create that input object, keep it changed but prevent it from trigger updates + InInputObject->SetNeedsToTriggerUpdate(false); + } + + return bSuccess; +} + + +// Upload transform for an input's InputObject +bool +FHoudiniInputTranslator::UploadHoudiniInputTransform( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject) +{ + if (!InInput || !InInputObject) + return false; + + auto UpdateTransform = [](const FTransform& InTransform, const HAPI_NodeId& InNodeId) + { + // Translate the Transform to HAPI + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(InTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InNodeId, &HapiTransform), false); + + return true; + }; + + bool bSuccess = true; + switch (InInputObject->Type) + { + case EHoudiniInputObjectType::StaticMesh: + { + // Simply update the Input mesh's Transform offset + if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + break; + } + + case EHoudiniInputObjectType::StaticMeshComponent: + { + // Update using the static mesh component's transform + UHoudiniInputMeshComponent* InSMC = Cast(InInputObject); + if (!InSMC || InSMC->IsPendingKill()) + { + bSuccess = false; + break; + } + + FTransform NewTransform = InSMC->GetStaticMeshComponent() ? InSMC->GetStaticMeshComponent()->GetComponentTransform() : InInputObject->Transform; + if(!UpdateTransform(NewTransform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + // Update the InputObject's transform + InInputObject->Transform = NewTransform; + + break; + } + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + { + // TODO: Only update the instances transform + break; + } + + case EHoudiniInputObjectType::HoudiniSplineComponent: + { + // TODO: Simply update the curve's transform? + break; + } + + case EHoudiniInputObjectType::HoudiniAssetActor: + case EHoudiniInputObjectType::HoudiniAssetComponent: + { + // TODO: Check, nothing to do? + break; + } + + case EHoudiniInputObjectType::Actor: + { + UHoudiniInputActor* InputActor = Cast(InInputObject); + if (!InputActor || InputActor->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Update the actor's transform + // To avoid further updates + if (InputActor->GetActor()) + InputActor->Transform = InputActor->GetActor()->GetTransform(); + + // Iterate on all the actor input objects and see if their transform needs to be uploaded + // TODO? Also update the component's actor transform?? + for (auto& CurrentComponent : InputActor->ActorComponents) + { + if (!CurrentComponent || CurrentComponent->IsPendingKill()) + continue; + + if (!CurrentComponent->HasTransformChanged()) + continue; + + // Upload the current input object's transform to Houdini + if (!UploadHoudiniInputTransform(InInput, CurrentComponent)) + { + bSuccess = false; + continue; + } + } + break; + } + + case EHoudiniInputObjectType::SceneComponent: + { + UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); + if (!InputSceneComp || InputSceneComp->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Update the component transform to avoid further updates + if (InputSceneComp->GetSceneComponent()) + InputSceneComp->Transform = InputSceneComp->GetSceneComponent()->GetComponentTransform(); + + break; + } + + case EHoudiniInputObjectType::Landscape: + { + // + UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); + if (!InputLandscape || InputLandscape->IsPendingKill()) + { + bSuccess = false; + break; + } + + // + ALandscapeProxy* Landscape = InputLandscape->GetLandscapeProxy(); + if (!Landscape || Landscape->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Only apply diff for landscape since the HF's transform is used for value conversion as well + FTransform CurrentTransform = InputLandscape->Transform; + FTransform NewTransform = Landscape->ActorToWorld(); + + // Only handle position/rotation differences + FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); + FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); + + // Now get the HF's current transform + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) + { + bSuccess = false; + break; + } + + // Convert it to unreal + FTransform HFTransform; + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); + + // Apply the position offset if needed + if (!PosDiff.IsZero()) + HFTransform.AddToTranslation(PosDiff); + + // Apply the rotation offset if needed + if (!RotDiff.IsIdentity()) + HFTransform.ConcatenateRotation(RotDiff); + + // Convert back to a HAPI Transform and update the HF's transform + HAPI_TransformEuler NewHAPITransform; + FHoudiniApi::TransformEuler_Init(&NewHAPITransform); + FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); + NewHAPITransform.position[1] = 0.0f; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InputLandscape->InputObjectNodeId, &NewHAPITransform)) + { + bSuccess = false; + break; + } + + // Update the cached transform + InputLandscape->Transform = NewTransform; + } + + case EHoudiniInputObjectType::Brush: + { + // TODO: Update the Brush's transform + break; + } + + // Unsupported + case EHoudiniInputObjectType::Object: + case EHoudiniInputObjectType::SkeletalMesh: + case EHoudiniInputObjectType::SplineComponent: + { + break; + } + + case EHoudiniInputObjectType::Invalid: + default: + break; + } + + // Mark that input object as not changed + if (bSuccess) + { + InInputObject->MarkTransformChanged(false); + InInputObject->SetNeedsToTriggerUpdate(false); + } + else + { + // We couldn't update/create that input object, keep it changed but prevent it from trigger updates + InInputObject->SetNeedsToTriggerUpdate(false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForObject(const FString& InObjNodeName, UHoudiniInputObject* InObject) +{ + if (!InObject) + return false; + + UObject* Object = InObject->GetObject(); + if (!Object || Object->IsPendingKill()) + return true; + + FString NodeName = InObjNodeName + TEXT("_") + Object->GetName(); + + // For UObjects we can't upload much, but can still create an input node + // with a single point, with an attribute pointing to the input object's path + HAPI_NodeId InputNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); + + // Update this input object's NodeId and ObjectNodeId + InObject->InputNodeId = (int32)InputNodeId; + InObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 2; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = 1; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); + + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + FVector ObjectPosition = InObject->Transform.GetLocation(); + TArray Position = + { + ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION + }; + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Position.GetData(), 0, + AttributeInfoPoint.count), false); + } + + { + // Create point attribute info for the path. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); + + // Set the point's path attribute + FString ObjectPathName = Object->GetPathName(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + ObjectPathName, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputNodeId), false); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( + const FString& InObjNodeName, + UHoudiniInputStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UBlueprint* BP = nullptr; + UStaticMesh* SM = nullptr; + + FString SMName = InObjNodeName + TEXT("_"); + + // Get Blueprint or StaticMesh + if (InObject->bIsBlueprint()) + { + BP = InObject->GetBlueprint(); + if (!BP || BP->IsPendingKill()) + return true; + + SMName += BP->GetName(); + } + else + { + SM = InObject->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + SMName += SM->GetName(); + } + + // Marshall the Static Mesh to Houdini + bool bSuccess = true; + + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference; + if (SM) + AssetReference += SM->GetFullName(); + + if (BP) + AssetReference += BP->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference( + InObject->InputNodeId, AssetReference, SMName, InObject->Transform); + } + else + { + TArray StaticMeshComponents; + + // The input object is a Blueprint, Get all its StaticMeshes + if (BP) + { + USimpleConstructionScript* SCS = BP->SimpleConstructionScript; + if (SCS && !SCS->IsPendingKill()) + { + const TArray& Nodes = SCS->GetAllNodes(); + for (auto & CurNode : Nodes) + { + if (!CurNode || CurNode->IsPendingKill()) + continue; + + UActorComponent * CurComp = CurNode->ComponentTemplate; + if (!CurComp || CurComp->IsPendingKill()) + continue; + + UStaticMeshComponent* CurSMC = Cast(CurComp); + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + UStaticMesh* CurSM = CurSMC->GetStaticMesh(); + if (CurSM && !CurSM->IsPendingKill()) + StaticMeshComponents.Add(CurSMC); + + } + } + } + + // Clear previous Blueprint Static Mesh Comps (if there is any) + InObject->BlueprintStaticMeshes.Empty(); + + // This is a BP, add all the BP SM comps to its BlueprintStaticMeshes list. + if (InObject->bIsBlueprint()) + { + for (auto & CurSMC : StaticMeshComponents) + { + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + UHoudiniInputStaticMesh* SMObject = Cast( + UHoudiniInputObject::CreateTypedInputObject(CurSMC->GetStaticMesh(), InObject, InObject->GetName() + TEXT("_") + CurSMC->GetName())); + + if (!SMObject || SMObject->IsPendingKill()) + continue; + + bSuccess &= FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + CurSMC->GetStaticMesh(), SMObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); + + InObject->SetImportAsReference(false); + + // Update this input object's OBJ NodeId + SMObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(SMObject->InputNodeId); + + // Update the component's transform + FTransform ComponentTransform = CurSMC->GetRelativeTransform(); + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), SMObject->InputObjectNodeId, &HapiTransform), false); + } + + InObject->BlueprintStaticMeshes.Add(SMObject); + } + + return true; + } + // This is a normal static mesh input, process it normally as a static mesh Input Object + else + { + bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, InObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); + } + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // If the Input mesh has a Transform offset + FTransform TransformOffset = InObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USkeletalMesh* SkelMesh = InObject->GetSkeletalMesh(); + if (!SkelMesh || SkelMesh->IsPendingKill()) + return true; + + // Get the SM's transform offset + FTransform TransformOffset = InObject->Transform; + + // TODO + // Support this type of input object + // FUnrealMeshTranslator::CreateInputNodeForSkeletalMesh(...) + + return HapiCreateInputNodeForObject(InObjNodeName, InObject); +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USceneComponent* SceneComp = InObject->GetSceneComponent(); + if (!SceneComp || SceneComp->IsPendingKill()) + return true; + + // Get the Scene Component's transform + FTransform TransformOffset = InObject->Transform; + + // Get the parent Actor's transform + FTransform ParentTransform = InObject->ActorTransform; + + // Dont do that! + return false; + + // TODO + // Support this type of input object + return HapiCreateInputNodeForObject(InObjNodeName, InObject); +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UStaticMeshComponent* SMC = InObject->GetStaticMeshComponent(); + if (!SMC || SMC->IsPendingKill()) + return true; + + // Get the component's Static Mesh + UStaticMesh* SM = InObject->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + // Marshall the Static Mesh to Houdini + FString SMCName = InObjNodeName + TEXT("_") + SMC->GetName(); + + bool bSuccess = true; + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference = SM->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, SMCName, AssetReference, InObject->Transform); + + } + else + { + bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, InObject->InputNodeId, SMCName, SMC, bExportLODs, bExportSockets, bExportColliders); + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // Update this input object's cache data + InObject->Update(SMC); + + // Update the component's transform + FTransform ComponentTransform = InObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputInstancedMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UObject* Object = InObject->GetObject(); + if (!Object || Object->IsPendingKill()) + return true; + + // Get the ISMC + UInstancedStaticMeshComponent* ISMC = InObject->GetInstancedStaticMeshComponent(); + if (!ISMC || ISMC->IsPendingKill()) + return true; + + HAPI_NodeId NewNodeId = -1; + if (!FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( + ISMC, InObjNodeName, NewNodeId, bExportLODs, bExportSockets, bExportColliders, false)) + return false; + + // Update this input object's node IDs + InObject->InputNodeId = NewNodeId; + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); + + // Update the component's cached instances + InObject->Update(ISMC); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USplineComponent* Spline = InObject->GetSplineComponent(); + if (!Spline || Spline->IsPendingKill()) + return true; + + + int32 NumberOfSplineControlPoints = InObject->NumberOfSplineControlPoints; + + TArray SplineControlPoints = InObject->SplineControlPoints; + + FString NodeName = InObjNodeName + TEXT("_") + InObject->GetName(); + + if (!FUnrealSplineTranslator::CreateInputNodeForSplineComponent(Spline, SplineResolution, InObject->InputNodeId, NodeName)) + return false; + + // Cache the exported curve's data to the input object + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + InObject->MarkChanged(true); + + //if (!HapiCreateInputNodeForObject(InObjNodeName, InObject)) + // return false; + + // Update the component's cached data + InObject->Update(Spline); + + // Update the component's transform + FTransform ComponentTransform = InObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UHoudiniSplineComponent* Curve = InObject->GetCurveComponent(); + if (!Curve || Curve->IsPendingKill()) + return true; + + if (!FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent(InObjNodeName, Curve)) + return false; + + // See if the component needs it node Id invalidated + //if (InObject->InputNodeId < 0) + // Curve->SetNodeId(InObject->InputNodeId); + + // Cache the exported curve's data to the input object + InObject->InputNodeId = Curve->GetNodeId(); + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + //InObject->CurveType = Curve->GetCurveType(); + //InObject->CurveMethod = Curve->GetCurveMethod(); + //InObject->Reversed = Curve->IsReversed(); + InObject->Update(Curve); + + InObject->MarkChanged(true); + + return true; +} + +bool +FHoudiniInputTranslator:: +HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UHoudiniAssetComponent* InputHAC = InObject->GetHoudiniAssetComponent(); + if (!InputHAC || InputHAC->IsPendingKill()) + return true; + + if (!InputHAC->CanDeleteHoudiniNodes()) + return true; + + UHoudiniInput* HoudiniInput = Cast(InObject->GetOuter()); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + return true; + + UHoudiniAssetComponent* OuterHAC = Cast(HoudiniInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return true; + + // Do not allow using ourself as an input, terrible things would happen + if (InputHAC->GetAssetId() == OuterHAC->GetAssetId()) + return false; + + // If previously imported as ref, delete the input node. + if (InObject->InputNodeId > -1 && InObject->GetImportAsReference()) + { + int32 PreviousInputNodeId = InObject->InputNodeId; + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InObject->GetName()); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InObject->GetName()); + } + } + + InObject->SetImportAsReference(bImportAsReference); + + // If this object is in an Asset input, we need to set the InputNodeId directl + // to avoid creating extra merge nodes. World inputs should not do that! + bool bIsAssetInput = HoudiniInput->GetInputType() == EHoudiniInputType::Asset; + + if (bImportAsReference) + { + InObject->InputNodeId = -1; + InObject->InputObjectNodeId = -1; + + if(bIsAssetInput) + HoudiniInput->SetInputNodeId(-1); + + // Start by getting the Object's full name + FString AssetReference = InputHAC->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + if (!FHoudiniInputTranslator::CreateInputNodeForReference( + InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform)) // do not delete previous node if it was HAC + return false; + + if (bIsAssetInput) + HoudiniInput->SetInputNodeId(InObject->InputNodeId); + } + + InputHAC->AddDownstreamHoudiniAsset(OuterHAC); + + //if (HAC->NeedsInitialization()) + // HAC->MarkAsNeedInstantiation(); + + //HoudiniInput->SetAssetNodeId(HAC->GetAssetId()); + + // TODO: This might be uneeded as this function should only be called + // after we're not wiating on the input asset... + if (InputHAC->AssetState == EHoudiniAssetState::NeedInstantiation) + { + // If the input HAC needs to be instantiated, tell it do so + InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; + // Mark this object's input as changed so we can properly update after the input HDA's done instantiating/cooking + HoudiniInput->MarkChanged(true); + } + + if (InputHAC->NeedsInitialization() || InputHAC->NeedUpdate()) + return false; + + if (!bImportAsReference) + { + if (bIsAssetInput) + HoudiniInput->SetInputNodeId(InputHAC->GetAssetId()); + + InObject->InputNodeId = InputHAC->GetAssetId(); + } + + InObject->InputObjectNodeId = InObject->InputNodeId; + + bool bReturn = InObject->InputNodeId > -1; + + if(bIsAssetInput) + bReturn = FHoudiniInputTranslator::ConnectInputNode(HoudiniInput); + + return bReturn; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForActor( + UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (!InObject || InObject->IsPendingKill()) + return false; + + AActor* Actor = InObject->GetActor(); + if (!Actor || Actor->IsPendingKill()) + return true; + + // Check if this is a world input and if this is a HoudiniAssetActor + // If so we need to build static meshes for any proxy meshes + if (InInput->GetInputType() == EHoudiniInputType::World && Actor->IsA()) + { + AHoudiniAssetActor *HAA = Cast(Actor); + UHoudiniAssetComponent *HAC = HAA->GetHoudiniAssetComponent(); + if (HAC && !HAC->IsPendingKill()) + { + if (HAC->HasAnyCurrentProxyOutput()) + { + bool bPendingDeleteOrRebuild = false; + bool bInvalidState = false; + const bool bIsHoudiniCookedDataAvailable = HAC->IsHoudiniCookedDataAvailable(bPendingDeleteOrRebuild, bInvalidState); + if (bIsHoudiniCookedDataAvailable) + { + // Build the static mesh + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); + // Update the input object since a new StaticMeshComponent could have been created + UObject *InputObject = InObject->GetObject(); + if (InputObject && !InputObject->IsPendingKill()) + { + InObject->Update(InputObject); + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } + } + else if (!bPendingDeleteOrRebuild && !bInvalidState) + { + // Request a cook with no proxy output + HAC->MarkAsNeedCook(); + HAC->SetNoProxyMeshNextCookRequested(true); + } + } + else if (InObject->ActorComponents.Num() == 0 && HAC->HasAnyOutputComponent()) + { + // The HAC has non-proxy output components, but the InObject does not have any + // actor components. This can arise after a cook if previously there were only + // proxies and the input was created when there were only proxies + // Try to update the input to find new components + UObject *InputObject = InObject->GetObject(); + if (InputObject && !InputObject->IsPendingKill()) + { + InObject->Update(InputObject); + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } + } + } + } + + // Now, commit all of this actor's component + int32 ComponentIdx = 0; + for (UHoudiniInputSceneComponent* CurComponent : InObject->ActorComponents) + { + if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) + ComponentIdx++; + } + + // TODO: We should call Update here... + // needs to be fixed + + // Cache our transformn + InObject->Transform = Actor->GetTransform(); + + // Do something for our actor's transform? + /* + // TODO + // Support this type of input object + FString ObjNodeName = InInput->GetNodeBaseName(); + return HapiCreateInputNodeForObject(ObjNodeName, InObject); + */ + + //TODO? Check + // return true if we have at least uploaded one component + // return (ComponentIdx > 0); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( + const FString& InObjNodeName, UHoudiniInputLandscape* InObject, UHoudiniInput* InInput) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + if (!InInput || InInput->IsPendingKill()) + return false; + + ALandscapeProxy* Landscape = InObject->GetLandscapeProxy(); + if (!Landscape || Landscape->IsPendingKill()) + return true; + + EHoudiniLandscapeExportType ExportType = InInput->GetLandscapeExportType(); + + bool bSucess = false; + if (ExportType == EHoudiniLandscapeExportType::Heightfield) + { + bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); + } + else + { + bool bExportLighting = InInput->bLandscapeExportLighting; + bool bExportMaterials = InInput->bLandscapeExportMaterials; + bool bExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; + bool bExportTileUVs = InInput->bLandscapeExportTileUVs; + bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; + bool bExportAsMesh = InInput->LandscapeExportType == EHoudiniLandscapeExportType::Mesh; + + bSucess = FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( + Landscape, InObject->InputNodeId, InObjNodeName, + bExportAsMesh, bExportTileUVs, bExportNormalizedUVs, bExportLighting, bExportMaterials); + } + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + InObject->Update(Landscape); + + return bSucess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForBrush(const FString& InObjNodeName, UHoudiniInputBrush* InObject, TArray* ExcludeActors) +{ + if (!IsValid(InObject)) + return false; + + ABrush* BrushActor = InObject->GetBrush(); + if (!IsValid(BrushActor)) + return true; + + if (!FUnrealBrushTranslator::CreateInputNodeForBrush(InObject, BrushActor, ExcludeActors, InObject->InputNodeId, InObjNodeName)) + return false; + + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + InObject->Update(BrushActor); + + return true; +} + + + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, UHoudiniInputCameraComponent* InInputObject) +{ + if (!InInputObject || InInputObject->IsPendingKill()) + return false; + + UCameraComponent* Camera = InInputObject->GetCameraComponent(); + if (!Camera || Camera->IsPendingKill()) + return true; + + FString NodeName = InNodeName + TEXT("_") + Camera->GetName(); + + // Create the camera OBJ. + int32 CameraNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( + -1, TEXT("Object/cam"), InNodeName, true, &CameraNodeId), false); + + // set "Pixel Aspect Ratio" (aspect) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "aspect", 0, InInputObject->AspectRatio), false); + + // set "Projection" (projection) (0 persp, 1 ortho) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "projection", 0, InInputObject->bIsOrthographic ? 1 : 0), false); + + // set Ortho Width (orthowidth) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "orthowidth", 0, InInputObject->OrthoWidth), false); + + // set Near Clippin (near) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "near", 0, InInputObject->OrthoNearClipPlane), false); + + // set far clipping (far) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "far", 0, InInputObject->OrthoFarClipPlane), false); + + // Set the transform + HAPI_TransformEuler H_Transform; + FHoudiniApi::TransformEuler_Init(&H_Transform); + FHoudiniEngineUtils::TranslateUnrealTransform(Camera->GetComponentTransform(), H_Transform); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CameraNodeId, &H_Transform), false); + + // Update the component's transform + FTransform ComponentTransform = InInputObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Camera orientation need to be adjusted + HapiTransform.rotationEuler[1] += -90.0f; + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CameraNodeId, &HapiTransform), false); + } + + // Update this input's NodeId and ObjectNodeId + InInputObject->InputNodeId = -1;// (int32)CameraNodeId; + InInputObject->InputObjectNodeId = (int32)CameraNodeId; + + // Update this input object's cache data + InInputObject->Update(Camera); + + return true; +} + +bool +FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // We need to call BuildAllInputs here to update all the inputs, + // and make sure that the object path parameter inputs' parameter ids are up to date + if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) + return false; + + // We need to update the AssetID stored on all the inputs + // and mark all the input objects for this input type as changed + int32 HACAssetId = HAC->GetAssetId(); + for (auto CurrentInput : HAC->Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // + CurrentInput->SetAssetNodeId(HACAssetId); + + // We need to delete the nodes created for the input objects if they are valid + // (since the node IDs are transients, this likely means we're handling a recook/rebuild + // and therefore expect to recreate the input nodes) + DestroyInputNodes(CurrentInput, CurrentInput->GetInputType()); + } + + return true; +} + + + +bool +FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Only tick/cook when in Editor + // This prevents PIE cooks or runtime cooks due to inputs moving + AActor* ActorOwner = HAC->GetOwner(); + if (ActorOwner) + { + if (!ActorOwner->GetWorld() || (ActorOwner->GetWorld()->WorldType != EWorldType::Editor)) + return false; + } + +#if WITH_EDITOR + // Stop outliner objects from causing recooks while input objects are dragged around + if (FHoudiniMoveTracker::Get().IsObjectMoving) + { + //HOUDINI_LOG_MESSAGE(TEXT("Object moving, not updating world inputs!")); + return false; + } +#endif + + for (auto CurrentInput : HAC->Inputs) + { + if (!CurrentInput) + continue; + if (CurrentInput->GetInputType() != EHoudiniInputType::World) + continue; + + UpdateWorldInput(CurrentInput); + } + + return true; +} + +bool +FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (InInput->GetInputType() != EHoudiniInputType::World) + return false; + + TArray* InputObjectsPtr = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjectsPtr) + return false; + + bool bHasChanged = false; + if (InInput->IsWorldInputBoundSelector() && InInput->GetWorldInputBoundSelectorAutoUpdates()) + { + // If the input is in bound selector mode, and auto-update is enabled + // update the actors selected by the bounds first + bHasChanged = InInput->UpdateWorldSelectionFromBoundSelectors(); + } + + // See if we need to update the components for this input + // look for deleted actors/components + TArray ObjectToDeleteIndices; + for(int32 InputObjIdx = 0; InputObjIdx < InputObjectsPtr->Num(); InputObjIdx++) + { + UHoudiniInputActor* ActorObject = Cast((*InputObjectsPtr)[InputObjIdx]); + if (!ActorObject || ActorObject->IsPendingKill()) + continue; + + // Make sure the actor is still valid + bool bValidActorObject = ActorObject->GetActor() && !ActorObject->GetActor()->IsPendingKill(); + + // For BrushActors, the brush and actors must be valid as well + UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); + if (bValidActorObject && BrushActorObject) + { + ABrush* BrushActor = BrushActorObject->GetBrush(); + if (!IsValid(BrushActor)) + bValidActorObject = false; + else if (!IsValid(BrushActor->Brush)) + bValidActorObject = false; + } + + // The actor is no longer valid, mark it for deletion + if (!bValidActorObject) + { + if ((ActorObject->InputNodeId > 0) || (ActorObject->InputObjectNodeId > 0)) + { + ActorObject->InvalidateData(); + // We only need to update the input if the actors nodes were created in Houdini + bHasChanged = true; + } + + // Delete the Actor object + ObjectToDeleteIndices.Add(InputObjIdx); + continue; + } + + if (ActorObject->HasActorTransformChanged()) + { + ActorObject->MarkTransformChanged(true); + bHasChanged = true; + } + + if (ActorObject->HasContentChanged()) + { + ActorObject->MarkChanged(true); + bHasChanged = true; + } + + // Iterates on all of the actor's component + TArray ComponentToDeleteIndices; + for (int32 CompIdx = 0; CompIdx < ActorObject->ActorComponents.Num(); CompIdx++) + { + UHoudiniInputSceneComponent* CurActorComp = ActorObject->ActorComponents[CompIdx]; + if (!CurActorComp || CurActorComp->IsPendingKill()) + continue; + + // Make sure the actor is still valid + if (!CurActorComp->InputObject || CurActorComp->InputObject->IsPendingKill()) + { + // If it's not, mark it for deletion + if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) + { + CurActorComp->InvalidateData(); + + // We only need to update the input if the object were created in Houdini + bHasChanged = true; + } + + // Delete the component object + ComponentToDeleteIndices.Add(CompIdx); + + continue; + } + + if (CurActorComp->HasComponentTransformChanged()) + { + CurActorComp->MarkTransformChanged(true); + bHasChanged = true; + } + + if (CurActorComp->HasComponentChanged()) + { + CurActorComp->MarkChanged(true); + bHasChanged = true; + } + } + + // Delete the components objects on the actor that were marked for deletion + for (int32 ToDeleteIdx = ComponentToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) + ActorObject->ActorComponents.RemoveAt(ComponentToDeleteIndices[ToDeleteIdx]); + } + + // Delete the actor objects that were marked for deletion + for (int32 ToDeleteIdx = ObjectToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) + InputObjectsPtr->RemoveAt(ObjectToDeleteIndices[ToDeleteIdx]); + + // Mark the input as changed if need so it will trigger an upload + if (bHasChanged) + InInput->MarkChanged(true); + + return true; +} + + +bool +FHoudiniInputTranslator::CreateInputNodeForReference( + HAPI_NodeId& InputNodeId, + const FString & InRef, + const FString & InputNodeName, + const FTransform& InTransform) +{ + HAPI_NodeId NewNodeId = -1; + + // Create a single input node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_UTF8(*InputNodeName)), false); + + /* + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, nullptr), false); + */ + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) + return false; + + // We have now created a valid new input node, delete the previous one + HAPI_NodeId PreviousInputNodeId = InputNodeId; + if (PreviousInputNodeId >= 0) + { + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); + } + } + + // Create and initialize a part containing one point with a point attribute + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + + PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = 1; + PartInfo.vertexCount = 0; + PartInfo.faceCount = 0; + PartInfo.pointCount = 1; + PartInfo.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, &PartInfo), false); + + // Point Position Attribute + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + FVector ObjectPosition = InTransform.GetLocation(); + TArray Position = + { + ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION + }; + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Position.GetData(), 0, + AttributeInfoPoint.count), false); + } + + // String Attribute + { + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint), false); + + // Set string attribute + std::string AttriString = TCHAR_TO_ANSI(*InRef); + const char* AttriStringRaw = AttriString.c_str(); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint, + &AttriStringRaw, 0, 1), false); + } + + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NewNodeId), false); + + InputNodeId = NewNodeId; + return true; +} + + + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeName, UHoudiniInputDataTable* InInputObject) +{ + //TODO + if (!InInputObject || InInputObject->IsPendingKill()) + return false; + + UDataTable* DataTable = InInputObject->GetDataTable(); + if (!DataTable || DataTable->IsPendingKill()) + return true; + + // Get the DataTable data as string + TArray> TableData = DataTable->GetTableData(EDataTableExportFlags::None); + if (TableData.Num() <= 1) + return true; + + int32 NumRows = TableData.Num() - 1; + int32 NumAttributes = TableData[0].Num(); + if (NumRows <= 0 || NumAttributes <= 0) + return true; + + // Create the input node + FString NodeName = InNodeName + TEXT("_") + DataTable->GetName(); + HAPI_NodeId InputNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); + + // Update this input object's NodeId and ObjectNodeId + InInputObject->InputNodeId = (int32)InputNodeId; + InInputObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = NumAttributes; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = NumRows; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); + + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + TArray Positions; + Positions.SetNum(NumRows * 3); + for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) + { + Positions[RowIdx * 3] = 0.0f; + Positions[RowIdx * 3 + 1] = (float)RowIdx; + Positions[RowIdx * 3 + 2] = 0.0f; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Positions.GetData(), 0, + AttributeInfoPoint.count), false); + } + + { + // Create point attribute info for the path. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); + + // Get the object path + FString ObjectPathName = DataTable->GetPathName(); + + // Create an array + TArray ObjectPaths; + ObjectPaths.Init(ObjectPathName, NumRows); + + // Set the point's path attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + ObjectPaths, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); + } + + { + // Create point attribute info for data table RowTable class name + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, &AttributeInfoPoint), false); + + // Get the object path + FString RowStructName = DataTable->GetRowStructName().ToString(); + + // Create an array + TArray RowStructNames; + RowStructNames.Init(RowStructName, NumRows); + + // Set the point's path attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + RowStructNames, InputNodeId, 0, + HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, AttributeInfoPoint), false); + } + + // Now set the attributes values for each "point" of the data table + for (int32 ColIdx = 0; ColIdx < NumAttributes; ColIdx++) + { + // attribute name is "unreal_data_table_COL_NAME" + FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + TableData[0][ColIdx]; + + // We need to gt all values for that attribute + TArray AttributeValues; + AttributeValues.SetNum(NumRows); + for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) + { + AttributeValues[RowIdx] = TableData[RowIdx + 1][ColIdx]; + } + + // Create a point attribute info + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = NumRows; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_POINT; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + TCHAR_TO_ANSI(*CurAttrName), &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + AttributeValues, InputNodeId, 0, + CurAttrName, AttributeInfo), false); + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputNodeId), false); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr), false); + + return true; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h index 5737604f9..1598c6b6f 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h @@ -1,202 +1,202 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -class AActor; - -class UHoudiniInput; -class UHoudiniParameter; -class UHoudiniAssetComponent; - -class UHoudiniInputObject; -class UHoudiniInputStaticMesh; -class UHoudiniInputSkeletalMesh; -class UHoudiniInputSceneComponent; -class UHoudiniInputMeshComponent; -class UHoudiniInputInstancedMeshComponent; -class UHoudiniInputSplineComponent; -class UHoudiniInputHoudiniSplineComponent; -class UHoudiniInputHoudiniAsset; -class UHoudiniInputActor; -class UHoudiniInputLandscape; -class UHoudiniInputBrush; -class UHoudiniSplineComponent; -class UHoudiniInputCameraComponent; -class UHoudiniInputDataTable; - -class AActor; - -enum class EHoudiniInputType : uint8; -enum class EHoudiniLandscapeExportType : uint8; - -struct HOUDINIENGINE_API FHoudiniInputTranslator -{ - // - static bool UpdateInputs(UHoudiniAssetComponent* HAC); - - // Update inputs from the asset - // @AssetId: NodeId of the digital asset - // @OuterObject: Object to use for transactions and as Outer for new inputs - // @CurrentInputs: pre: current & post: invalid inputs - // @NewParameters: pre: empty & post: new inputs - // On Return: CurrentInputs are the old inputs that are no longer valid, - // NewInputs are new and re-used inputs. - static bool BuildAllInputs( - const HAPI_NodeId& AssetId, - class UObject* OuterObject, - TArray& Inputs, - TArray& Parameters); - - // Update loaded inputs and their input objects so they can be uploaded properly - static bool UpdateLoadedInputs(UHoudiniAssetComponent * HAC); - - // Update all the inputs that have been marked as change - static bool UploadChangedInputs(UHoudiniAssetComponent * HAC); - - // Only update simple input properties - static bool UpdateInputProperties(UHoudiniInput* InInput); - - // Update the KeepWorldTransform / Object merge transform type property - static bool UpdateTransformType(UHoudiniInput* InInput); - - // Update the pack before merge parameter for World/Geometry inputs - static bool UpdatePackBeforeMerge(UHoudiniInput* InInput); - - // Update the transform offset for geometry inputs - static bool UpdateTransformOffset(UHoudiniInput* InInput); - - // Upload all the input's data to Houdini - static bool UploadInputData(UHoudiniInput* InInput); - - // Upload all the input's transforms to Houdini - static bool UploadInputTransform(UHoudiniInput* InInput); - - // Upload data for an input's InputObject - static bool UploadHoudiniInputObject( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, TArray& OutCreatedNodeIds); - - // Upload transform for an input's InputObject - static bool UploadHoudiniInputTransform( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject); - - // Updates/ticks world inputs in the given HAC - static bool UpdateWorldInputs(UHoudiniAssetComponent* HAC); - - // Updates/ticks the given world input - static bool UpdateWorldInput(UHoudiniInput* InInput); - - // Connect an input's nodes to its linked HDA node - static bool ConnectInputNode(UHoudiniInput* InInput); - - // Destroys an input - static bool DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - static bool DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - static bool DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - - static EHoudiniInputType GetDefaultInputTypeFromLabel(const FString& InputName); - - static bool SetDefaultAssetFromHDA(UHoudiniInput* Input); - - static bool ChangeInputType(UHoudiniInput* Input, const bool& bForce); - - static bool HapiCreateInputNodeForObject( - const FString& InObjNodeName, UHoudiniInputObject* InObject); - - static bool HapiCreateInputNodeForStaticMesh( - const FString& InObjNodeName, - UHoudiniInputStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference = false); - - static bool HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, - UHoudiniInputHoudiniSplineComponent* InObject); - - static bool HapiCreateInputNodeForLandscape( - const FString& InObjNodeName, - UHoudiniInputLandscape* InObject, - UHoudiniInput* InInput); - - static bool HapiCreateInputNodeForSkeletalMesh( - const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject); - - static bool HapiCreateInputNodeForSceneComponent( - const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject); - - static bool HapiCreateInputNodeForStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference); - - static bool HapiCreateInputNodeForInstancedStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputInstancedMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders); - - static bool HapiCreateInputNodeForSplineComponent( - const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution); - - static bool HapiCreateInputNodeForHoudiniAssetComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference); - - static bool HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds); - - static bool HapiCreateInputNodeForCamera( - const FString& InObjNodeName, UHoudiniInputCameraComponent* InObject); - - // Create input node for Brush. Optionally exclude actors when combining - // brush with other intersecting brushes. This is typically used to - // exclude Selector objects. - static bool HapiCreateInputNodeForBrush( - const FString& InObjNodeName, - UHoudiniInputBrush* InObject, - TArray* ExcludeActors - ); - - static bool HapiCreateInputNodeForDataTable( - const FString& InNodeName, UHoudiniInputDataTable* InInputObject); - - // HAPI: Create an input node for reference - static bool CreateInputNodeForReference( - HAPI_NodeId& InputNodeId, - const FString & InRef, - const FString & InputNodeName, - const FTransform & InTransform); - - //static bool HapiUpdateInputNodeTransform(const HAPI_NodeId InputNodeId, const FTransform& Transform); - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +class AActor; + +class UHoudiniInput; +class UHoudiniParameter; +class UHoudiniAssetComponent; + +class UHoudiniInputObject; +class UHoudiniInputStaticMesh; +class UHoudiniInputSkeletalMesh; +class UHoudiniInputSceneComponent; +class UHoudiniInputMeshComponent; +class UHoudiniInputInstancedMeshComponent; +class UHoudiniInputSplineComponent; +class UHoudiniInputHoudiniSplineComponent; +class UHoudiniInputHoudiniAsset; +class UHoudiniInputActor; +class UHoudiniInputLandscape; +class UHoudiniInputBrush; +class UHoudiniSplineComponent; +class UHoudiniInputCameraComponent; +class UHoudiniInputDataTable; + +class AActor; + +enum class EHoudiniInputType : uint8; +enum class EHoudiniLandscapeExportType : uint8; + +struct HOUDINIENGINE_API FHoudiniInputTranslator +{ + // + static bool UpdateInputs(UHoudiniAssetComponent* HAC); + + // Update inputs from the asset + // @AssetId: NodeId of the digital asset + // @OuterObject: Object to use for transactions and as Outer for new inputs + // @CurrentInputs: pre: current & post: invalid inputs + // @NewParameters: pre: empty & post: new inputs + // On Return: CurrentInputs are the old inputs that are no longer valid, + // NewInputs are new and re-used inputs. + static bool BuildAllInputs( + const HAPI_NodeId& AssetId, + class UObject* OuterObject, + TArray& Inputs, + TArray& Parameters); + + // Update loaded inputs and their input objects so they can be uploaded properly + static bool UpdateLoadedInputs(UHoudiniAssetComponent * HAC); + + // Update all the inputs that have been marked as change + static bool UploadChangedInputs(UHoudiniAssetComponent * HAC); + + // Only update simple input properties + static bool UpdateInputProperties(UHoudiniInput* InInput); + + // Update the KeepWorldTransform / Object merge transform type property + static bool UpdateTransformType(UHoudiniInput* InInput); + + // Update the pack before merge parameter for World/Geometry inputs + static bool UpdatePackBeforeMerge(UHoudiniInput* InInput); + + // Update the transform offset for geometry inputs + static bool UpdateTransformOffset(UHoudiniInput* InInput); + + // Upload all the input's data to Houdini + static bool UploadInputData(UHoudiniInput* InInput); + + // Upload all the input's transforms to Houdini + static bool UploadInputTransform(UHoudiniInput* InInput); + + // Upload data for an input's InputObject + static bool UploadHoudiniInputObject( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, TArray& OutCreatedNodeIds); + + // Upload transform for an input's InputObject + static bool UploadHoudiniInputTransform( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject); + + // Updates/ticks world inputs in the given HAC + static bool UpdateWorldInputs(UHoudiniAssetComponent* HAC); + + // Updates/ticks the given world input + static bool UpdateWorldInput(UHoudiniInput* InInput); + + // Connect an input's nodes to its linked HDA node + static bool ConnectInputNode(UHoudiniInput* InInput); + + // Destroys an input + static bool DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + static bool DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + static bool DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + + static EHoudiniInputType GetDefaultInputTypeFromLabel(const FString& InputName); + + static bool SetDefaultAssetFromHDA(UHoudiniInput* Input); + + static bool ChangeInputType(UHoudiniInput* Input, const bool& bForce); + + static bool HapiCreateInputNodeForObject( + const FString& InObjNodeName, UHoudiniInputObject* InObject); + + static bool HapiCreateInputNodeForStaticMesh( + const FString& InObjNodeName, + UHoudiniInputStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference = false); + + static bool HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, + UHoudiniInputHoudiniSplineComponent* InObject); + + static bool HapiCreateInputNodeForLandscape( + const FString& InObjNodeName, + UHoudiniInputLandscape* InObject, + UHoudiniInput* InInput); + + static bool HapiCreateInputNodeForSkeletalMesh( + const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject); + + static bool HapiCreateInputNodeForSceneComponent( + const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject); + + static bool HapiCreateInputNodeForStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference); + + static bool HapiCreateInputNodeForInstancedStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputInstancedMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders); + + static bool HapiCreateInputNodeForSplineComponent( + const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution); + + static bool HapiCreateInputNodeForHoudiniAssetComponent( + const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference); + + static bool HapiCreateInputNodeForActor( + UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds); + + static bool HapiCreateInputNodeForCamera( + const FString& InObjNodeName, UHoudiniInputCameraComponent* InObject); + + // Create input node for Brush. Optionally exclude actors when combining + // brush with other intersecting brushes. This is typically used to + // exclude Selector objects. + static bool HapiCreateInputNodeForBrush( + const FString& InObjNodeName, + UHoudiniInputBrush* InObject, + TArray* ExcludeActors + ); + + static bool HapiCreateInputNodeForDataTable( + const FString& InNodeName, UHoudiniInputDataTable* InInputObject); + + // HAPI: Create an input node for reference + static bool CreateInputNodeForReference( + HAPI_NodeId& InputNodeId, + const FString & InRef, + const FString & InputNodeName, + const FTransform & InTransform); + + //static bool HapiUpdateInputNodeTransform(const HAPI_NodeId InputNodeId, const FTransform& Transform); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp index 73386f040..9cd38e2a3 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp @@ -1,3049 +1,3049 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInstanceTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniStaticMesh.h" - -//#include "HAPI/HAPI_Common.h" - -#include "Engine/StaticMesh.h" -#include "ComponentReregisterContext.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#if WITH_EDITOR - //#include "ScopedTransaction.h" - #include "LevelEditorViewport.h" - #include "MeshPaintHelpers.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// Fastrand is a faster alternative to std::rand() -// and doesn't oscillate when looking for 2 values like Unreal's. -inline int fastrand(int& nSeed) -{ - nSeed = (214013 * nSeed + 2531011); - return (nSeed >> 16) & 0x7FFF; -} - -// -bool -FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) -{ - // Get if force to use HISM from attribute - OutInstancedOutputPartData.bForceHISM = HasHISMAttribute(InHGPO.GeoId, InHGPO.PartId); - - // Extract the object and transforms for this instancer - if (!GetInstancerObjectsAndTransforms( - InHGPO, - InAllOutputs, - OutInstancedOutputPartData.OriginalInstancedObjects, - OutInstancedOutputPartData.OriginalInstancedTransforms, - OutInstancedOutputPartData.SplitAttributeName, - OutInstancedOutputPartData.SplitAttributeValues, - OutInstancedOutputPartData.PerSplitAttributes)) - return false; - - // Check if this is a No-Instancers ( unreal_split_instances ) - OutInstancedOutputPartData.bSplitMeshInstancer = IsSplitInstancer(InHGPO.GeoId, InHGPO.PartId); - - OutInstancedOutputPartData.bIsFoliageInstancer = IsFoliageInstancer(InHGPO.GeoId, InHGPO.PartId); - - // Extract the generic attributes - GetGenericPropertiesAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllPropertyAttributes); - - //Get the level path attribute on the instancer - if (!FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllLevelPaths)) - { - // No attribute specified - OutInstancedOutputPartData.AllLevelPaths.Empty(); - } - - // Get the output name attribute - if (!FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.OutputNames)) - { - // No attribute specified - OutInstancedOutputPartData.OutputNames.Empty(); - } - - // See if we have a tile attribute - if (!FHoudiniEngineUtils::GetTileAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.TileValues)) - { - // No attribute specified - OutInstancedOutputPartData.TileValues.Empty(); - } - - // Get the bake actor attribute - if (!FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeActorNames)) - { - // No attribute specified - OutInstancedOutputPartData.AllBakeActorNames.Empty(); - } - - // Get the bake outliner folder attribute - if (!FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeOutlinerFolders)) - { - // No attribute specified - OutInstancedOutputPartData.AllBakeOutlinerFolders.Empty(); - } - - // See if we have instancer material overrides - if (!GetMaterialOverridesFromAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.MaterialAttributes)) - OutInstancedOutputPartData.MaterialAttributes.Empty(); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - UHoudiniOutput* InOutput, - const TArray& InAllOutputs, - UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - // Keep track of the previous cook's component to clean them up after - TMap NewOutputObjects; - TMap OldOutputObjects = InOutput->GetOutputObjects(); - - TMap& InstancedOutputs = InOutput->GetInstancedOutputs(); - // Mark all the current instanced output as stale - for (auto& InstOut : InstancedOutputs) - InstOut.Value.bStale = true; - - USceneComponent* ParentComponent = Cast(InOuterComponent); - if (!ParentComponent) - return false; - - // Keep track of if we remove, create or update any foliage, so that we can repopulate the foliage type list in - // the UI (foliage mode) at the end - bool bHaveAnyFoliageInstancers = false; - - // We also need to cleanup the previous foliages instances (if we have any) - for (auto& CurrentPair : OldOutputObjects) - { - // Foliage instancers store a HISMC in the components - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentPair.Value.OutputComponent); - if (!FoliageHISMC || FoliageHISMC->IsPendingKill()) - continue; - - CleanupFoliageInstances(FoliageHISMC, ParentComponent); - bHaveAnyFoliageInstancers = true; - } - - // The default SM to be used if the instanced object has not been found (when using attribute instancers) - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) - { - // Not an instancer, skip - if (CurHGPO.Type != EHoudiniPartType::Instancer) - continue; - - // Prepare this output object's output identifier - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = CurHGPO.ObjectId; - OutputIdentifier.GeoId = CurHGPO.GeoId; - OutputIdentifier.PartId = CurHGPO.PartId; - OutputIdentifier.PartName = CurHGPO.PartName; - - FHoudiniInstancedOutputPartData InstancedOutputPartDataTmp; - const FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = nullptr; - if (InPreBuiltInstancedOutputPartData) - { - InstancedOutputPartDataPtr = InPreBuiltInstancedOutputPartData->Find(OutputIdentifier); - } - if (!InstancedOutputPartDataPtr) - { - if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs,InstancedOutputPartDataTmp)) - continue; - InstancedOutputPartDataPtr = &InstancedOutputPartDataTmp; - } - - const FHoudiniInstancedOutputPartData& InstancedOutputPartData = *InstancedOutputPartDataPtr; - - TArray InstancerMaterials; - if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes,InstancerMaterials)) - InstancerMaterials.Empty(); - - if (InstancedOutputPartData.bIsFoliageInstancer) - bHaveAnyFoliageInstancers = true; - - // - // TODO: REFACTOR THIS! - // - // We create an instanced output per original object - // These original object can then potentially be replaced by variations - // Each variations will create a instance component / OutputObject - // Currently we process all original objects AND their variations at the same time - // we should instead loop on the original objects - // - get their variations objects/transform - // - create the appropriate instancer - // This means modifying UpdateInstanceVariationsObjects so that it works using - // a single OriginalObject instead of using an array - // Also, apply the same logic to UpdateChangedInstanceOutput - // - - // Array containing all the variations objects for all the original objects - TArray> VariationInstancedObjects; - // Array containing all the variations transforms - TArray> VariationInstancedTransforms; - // Array indicate the original object index for each variation - TArray VariationOriginalObjectIndices; - // Array indicate the variation number for each variation - TArray VariationIndices; - // Update our variations using the instanced outputs - UpdateInstanceVariationObjects( - OutputIdentifier, - InstancedOutputPartData.OriginalInstancedObjects, - InstancedOutputPartData.OriginalInstancedTransforms, InOutput->GetInstancedOutputs(), - VariationInstancedObjects, VariationInstancedTransforms, - VariationOriginalObjectIndices, VariationIndices); - - // Create the instancer components now - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) - { - UObject* InstancedObject = VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - continue; - - if (!VariationInstancedTransforms.IsValidIndex(InstanceObjectIdx)) - continue; - - const TArray& InstancedObjectTransforms = VariationInstancedTransforms[InstanceObjectIdx]; - if (InstancedObjectTransforms.Num() <= 0) - continue; - - // Find the matching instance output now - FHoudiniInstancedOutput* FoundInstancedOutput = nullptr; - { - // Instanced output only use the original object index for their split identifier - FHoudiniOutputObjectIdentifier InstancedOutputIdentifier = OutputIdentifier; - InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]); - FoundInstancedOutput = InstancedOutputs.Find(InstancedOutputIdentifier); - } - - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - OutputIdentifier.SplitIdentifier = - FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]) - + TEXT("_") - + FString::FromInt(VariationIndices[InstanceObjectIdx]); - - // Get the OutputObj for this variation - FHoudiniOutputObject* FoundOutputObject = OldOutputObjects.Find(OutputIdentifier); - // See if we can find an preexisting component for this obj to try to reuse it - USceneComponent* OldInstancerComponent = nullptr; - const bool bIsProxyMesh = InstancedObject->IsA(); - if (FoundOutputObject) - { - if (bIsProxyMesh) - { - OldInstancerComponent = Cast(FoundOutputObject->ProxyComponent); - } - else - { - OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); - } - } - - // Extract the material for this variation - TArray VariationMaterials; - if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) - VariationMaterials.Empty(); - - USceneComponent* NewInstancerComponent = nullptr; - if (!CreateOrUpdateInstanceComponent( - InstancedObject, InstancedObjectTransforms, - InstancedOutputPartData.AllPropertyAttributes, CurHGPO, - ParentComponent, OldInstancerComponent, NewInstancerComponent, - InstancedOutputPartData.bSplitMeshInstancer, - InstancedOutputPartData.bIsFoliageInstancer, - VariationMaterials, - InstancedOutputPartData.bForceHISM)) - { - // TODO?? - continue; - } - - if (!NewInstancerComponent) - continue; - - // If the instanced object (by ref) wasn't found, hide the component - if(InstancedObject == DefaultReferenceSM) - NewInstancerComponent->SetHiddenInGame(true); - else - NewInstancerComponent->SetHiddenInGame(false); - - FHoudiniOutputObject& NewOutputObject = NewOutputObjects.FindOrAdd(OutputIdentifier); - if (bIsProxyMesh) - { - NewOutputObject.ProxyComponent = NewInstancerComponent; - } - else - { - NewOutputObject.OutputComponent = NewInstancerComponent; - } - - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - NewOutputObject.CachedAttributes.Empty(); - NewOutputObject.CachedTokens.Empty(); - - // Todo: get the proper attribute value per variation... - // Cache the level path, output name and tile attributes on the output object - // So they can be reused for baking - if(InstancedOutputPartData.AllLevelPaths.Num() > 0 && !InstancedOutputPartData.AllLevelPaths[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[0]); - - if(InstancedOutputPartData.OutputNames.Num() > 0 && !InstancedOutputPartData.OutputNames[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[0]); - - if(InstancedOutputPartData.TileValues.Num() > 0 && InstancedOutputPartData.TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[0])); - } - - if (InstancedOutputPartData.AllBakeActorNames.Num() > 0 && !InstancedOutputPartData.AllBakeActorNames[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[0]); - - if (InstancedOutputPartData.AllBakeOutlinerFolders.Num() > 0 && !InstancedOutputPartData.AllBakeOutlinerFolders[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[0]); - - if (InstancedOutputPartData.SplitAttributeValues.Num() > 0 - && !InstancedOutputPartData.SplitAttributeName.IsEmpty() - && InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalObjectIndices[InstanceObjectIdx])) - { - FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalObjectIndices[InstanceObjectIdx]]; - - // Cache the split attribute both as attribute and token - NewOutputObject.CachedAttributes.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); - NewOutputObject.CachedTokens.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); - - // If we have a split name that is non-empty, override attributes that can differ by split based - // on the split name - if (!SplitValue.IsEmpty()) - { - const FHoudiniInstancedOutputPerSplitAttributes* PerSplitAttributes = InstancedOutputPartData.PerSplitAttributes.Find(SplitValue); - if (PerSplitAttributes) - { - if (!PerSplitAttributes->LevelPath.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, PerSplitAttributes->LevelPath); - if (!PerSplitAttributes->BakeActorName.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, PerSplitAttributes->BakeActorName); - if (!PerSplitAttributes->BakeOutlinerFolder.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, PerSplitAttributes->BakeOutlinerFolder); - } - } - } - } - } - - // Remove reused components from the old map to avoid their deletion - for (const auto& CurNewPair : NewOutputObjects) - { - // Get the new Identifier / StaticMesh - const FHoudiniOutputObjectIdentifier& OutputIdentifier = CurNewPair.Key; - - // See if we already had that pair in the old map - FHoudiniOutputObject* FoundOldOutputObject = OldOutputObjects.Find(OutputIdentifier); - if (!FoundOldOutputObject) - continue; - - bool bKeep = false; - - UObject* NewComponent = CurNewPair.Value.OutputComponent; - if (NewComponent) - { - UObject* FoundOldComponent = FoundOldOutputObject->OutputComponent; - if (FoundOldComponent && !FoundOldComponent->IsPendingKill()) - { - bKeep = (FoundOldComponent == NewComponent); - } - } - - UObject* NewProxyComponent = CurNewPair.Value.ProxyComponent; - if (NewProxyComponent) - { - UObject* FoundOldProxyComponent = FoundOldOutputObject->ProxyComponent; - if (FoundOldProxyComponent && !FoundOldProxyComponent->IsPendingKill()) - { - bKeep = (FoundOldProxyComponent == NewProxyComponent); - } - } - - if (bKeep) - { - // Remove the reused component from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - - // The Old map now only contains unused/stale components, delete them - for (auto& OldPair : OldOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; - UObject* OldComponent = OldPair.Value.OutputComponent; - if (OldComponent) - { - bool bDestroy = true; - if (OldComponent->IsA()) - { - // When destroying a component, we have to be sure it's not an HISMC owned by an InstanceFoliageActor - UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OldComponent); - if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) - bDestroy = false; - } - - if(bDestroy) - RemoveAndDestroyComponent(OldComponent); - - OldPair.Value.OutputComponent = nullptr; - } - - UObject* OldProxyComponent = OldPair.Value.ProxyComponent; - if (OldProxyComponent) - { - RemoveAndDestroyComponent(OldProxyComponent); - OldPair.Value.ProxyComponent = nullptr; - } - } - OldOutputObjects.Empty(); - - // Update the output's object map - // Instancer do not create objects, clean the map - InOutput->SetOutputObjects(NewOutputObjects); - - // If we removed, created or updated any foliage instancers, repopulate the list of foliage types in the UI (foliage - // mode) - if (bHaveAnyFoliageInstancers) - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - - return true; -} - - -bool -FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( - FHoudiniInstancedOutput& InInstancedOutput, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - UHoudiniOutput* InParentOutput, - USceneComponent* InParentComponent) -{ - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = InOutputIdentifier.ObjectId; - OutputIdentifier.GeoId = InOutputIdentifier.GeoId; - OutputIdentifier.PartId = InOutputIdentifier.PartId; - OutputIdentifier.SplitIdentifier = InOutputIdentifier.SplitIdentifier; - OutputIdentifier.PartName = InOutputIdentifier.PartName; - - // Get if force using HISM from attribute - bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); - - TArray OriginalInstancedObjects; - OriginalInstancedObjects.Add(InInstancedOutput.OriginalObject.LoadSynchronous()); - - TArray> OriginalInstancedTransforms; - OriginalInstancedTransforms.Add(InInstancedOutput.OriginalTransforms); - - // Update our variations using the changed instancedoutputs objects - TArray> InstancedObjects; - TArray> InstancedTransforms; - TArray VariationOriginalObjectIndices; - TArray VariationIndices; - UpdateInstanceVariationObjects( - OutputIdentifier, - OriginalInstancedObjects, OriginalInstancedTransforms, - InParentOutput->GetInstancedOutputs(), - InstancedObjects, InstancedTransforms, - VariationOriginalObjectIndices, VariationIndices); - - // Find the HGPO for this instanced output - bool FoundHGPO = false; - FHoudiniGeoPartObject HGPO; - for (const auto& curHGPO : InParentOutput->GetHoudiniGeoPartObjects()) - { - if (OutputIdentifier.Matches(curHGPO)) - { - HGPO = curHGPO; - FoundHGPO = true; - break; - } - } - - if (!FoundHGPO) - { - // TODO check failure - ensure(FoundHGPO); - } - - // Extract the generic attributes for that HGPO - TArray AllPropertyAttributes; - GetGenericPropertiesAttributes(OutputIdentifier.GeoId, OutputIdentifier.PartId, AllPropertyAttributes); - - // Check if this is a No-Instancers ( unreal_split_instances ) - bool bSplitMeshInstancer = IsSplitInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); - - bool bIsFoliageInstancer = IsFoliageInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); - - // See if we have instancer material overrides - TArray InstancerMaterials; - if (!GetInstancerMaterials(OutputIdentifier.GeoId, OutputIdentifier.PartId, InstancerMaterials)) - InstancerMaterials.Empty(); - - // Keep track of the new instancer component in order to be able to clean up the unused/stale ones after. - TMap& OutputObjects = InParentOutput->GetOutputObjects(); - TMap ToDeleteOutputObjects = InParentOutput->GetOutputObjects(); - - // Create the instancer components now - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) - { - UObject* InstancedObject = InstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - continue; - - if (!InstancedTransforms.IsValidIndex(InstanceObjectIdx)) - continue; - - const TArray& InstancedObjectTransforms = InstancedTransforms[InstanceObjectIdx]; - if (InstancedObjectTransforms.Num() <= 0) - continue; - - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - // the original object index is used for the instanced outputs split identifier - OutputIdentifier.SplitIdentifier = - InOutputIdentifier.SplitIdentifier - + TEXT("_") - + FString::FromInt(VariationIndices[InstanceObjectIdx]); - - // See if we can find an preexisting component for this obj to try to reuse it - USceneComponent* OldInstancerComponent = nullptr; - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(OutputIdentifier); - if (FoundOutputObject) - { - OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); - } - - // Extract the material for this variation -// FHoudiniInstancedOutput* FoundInstancedOutput = InstancedOutputs.Find(OutputIdentifier); - TArray VariationMaterials; - if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) - VariationMaterials.Empty(); - - USceneComponent* NewInstancerComponent = nullptr; - if (!CreateOrUpdateInstanceComponent( - InstancedObject, InstancedObjectTransforms, - AllPropertyAttributes, HGPO, - InParentComponent, OldInstancerComponent, NewInstancerComponent, - bSplitMeshInstancer, bIsFoliageInstancer, InstancerMaterials, bForceHISM)) - { - // TODO?? - continue; - } - - if (!NewInstancerComponent) - continue; - - if (OldInstancerComponent != NewInstancerComponent) - { - // Previous component wasn't reused, detach and delete it - RemoveAndDestroyComponent(OldInstancerComponent); - - // Replace it with the new component - if (FoundOutputObject) - { - FoundOutputObject->OutputComponent = NewInstancerComponent; - } - else - { - FHoudiniOutputObject& NewOutputObject = OutputObjects.Add(OutputIdentifier); - NewOutputObject.OutputComponent = NewInstancerComponent; - } - } - - // Remove this output object from the todelete map - ToDeleteOutputObjects.Remove(OutputIdentifier); - } - - // Clean up the output objects that are not "reused" by the instanced outs - // The ToDelete map now only contains unused/stale components, delete them - for (auto& ToDeletePair : ToDeleteOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& ToDeleteIdentifier = ToDeletePair.Key; - UObject* OldComponent = ToDeletePair.Value.OutputComponent; - if (OldComponent) - { - RemoveAndDestroyComponent(OldComponent); - ToDeletePair.Value.OutputComponent = nullptr; - } - - UObject* OldProxyComponent = ToDeletePair.Value.ProxyComponent; - if (OldProxyComponent) - { - RemoveAndDestroyComponent(OldProxyComponent); - ToDeletePair.Value.ProxyComponent = nullptr; - } - - // Make sure the stale output object is not in the output map anymore - OutputObjects.Remove(ToDeleteIdentifier); - } - ToDeleteOutputObjects.Empty(); - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValues, - TMap& OutPerSplitAttributes) -{ - TArray InstancedObjects; - TArray> InstancedTransforms; - - TArray InstancedHGPOs; - TArray> InstancedHGPOTransforms; - - bool bSuccess = false; - switch (InHGPO.InstancerType) - { - case EHoudiniInstancerType::PackedPrimitive: - { - // Packed primitives instances - bSuccess = GetPackedPrimitiveInstancerHGPOsAndTransforms( - InHGPO, - InstancedHGPOs, - InstancedHGPOTransforms, - OutSplitAttributeName, - OutSplitAttributeValues, - OutPerSplitAttributes); - } - break; - - case EHoudiniInstancerType::AttributeInstancer: - { - // "Modern" attribute instancer - "unreal_instance" - bSuccess = GetAttributeInstancerObjectsAndTransforms( - InHGPO, - InstancedObjects, - InstancedTransforms, - OutSplitAttributeName, - OutSplitAttributeValues, - OutPerSplitAttributes); - } - break; - - case EHoudiniInstancerType::OldSchoolAttributeInstancer: - { - // Old school attribute override instancer - instance attribute w/ a HoudiniPath - bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); - } - break; - - case EHoudiniInstancerType::ObjectInstancer: - { - // Old School object instancer - bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); - } - break; - } - - if (!bSuccess) - return false; - - // Fetch the UOBject that correspond to the instanced parts - // Attribute instancers don't need to do this since they refer UObjects directly - if (InstancedHGPOs.Num() > 0) - { - for (int32 HGPOIdx = 0; HGPOIdx < InstancedHGPOs.Num(); HGPOIdx++) - { - const FHoudiniGeoPartObject& CurrentHGPO = InstancedHGPOs[HGPOIdx]; - - // Get the UObject that was generated for that HGPO - TArray ObjectsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - if (Output->OutputObjects.Num() <= 0) - continue; - - for (const auto& OutObjPair : Output->OutputObjects) - { - if (!OutObjPair.Key.Matches(CurrentHGPO)) - continue; - - const FHoudiniOutputObject& CurrentOutputObject = OutObjPair.Value; - - // In the case of a single-instance we can use the proxy (if it is current) - // FHoudiniOutputTranslator::UpdateOutputs doesn't allow proxies if there is more than one instance in an output - if (InstancedHGPOTransforms[HGPOIdx].Num() <= 1 && CurrentOutputObject.bProxyIsCurrent - && CurrentOutputObject.ProxyObject && !CurrentOutputObject.ProxyObject->IsPendingKill()) - { - ObjectsToInstance.Add(CurrentOutputObject.ProxyObject); - } - else if (CurrentOutputObject.OutputObject && !CurrentOutputObject.OutputObject->IsPendingKill()) - { - ObjectsToInstance.Add(CurrentOutputObject.OutputObject); - } - } - } - - // Add the UObject and the HGPO transforms to the output arrays - for (const auto& MatchingOutputObj : ObjectsToInstance) - { - InstancedObjects.Add(MatchingOutputObj); - InstancedTransforms.Add(InstancedHGPOTransforms[HGPOIdx]); - } - } - } - - // - if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() ) - { - // TODO - // Error / warning - return false; - } - - OutInstancedObjects = InstancedObjects; - OutInstancedTransforms = InstancedTransforms; - - return true; -} - - -void -FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TArray& InOriginalObjects, - const TArray>& InOriginalTransforms, - TMap& InstancedOutputs, - TArray>& OutVariationsInstancedObjects, - TArray>& OutVariationsInstancedTransforms, - TArray& OutVariationOriginalObjectIdx, - TArray& OutVariationIndices) -{ - FHoudiniOutputObjectIdentifier Identifier = InOutputIdentifier; - for (int32 InstObjIdx = 0; InstObjIdx < InOriginalObjects.Num(); InstObjIdx++) - { - UObject* OriginalObj = InOriginalObjects[InstObjIdx]; - if (!OriginalObj || OriginalObj->IsPendingKill()) - continue; - - // Build this output object's split identifier - Identifier.SplitIdentifier = FString::FromInt(InstObjIdx); - - // Do we have an instanced output object for this one? - FHoudiniInstancedOutput * FoundInstancedOutput = nullptr; - for (auto& Iter : InstancedOutputs) - { - FHoudiniOutputObjectIdentifier& FoundIdentifier = Iter.Key; - if (!(FoundIdentifier == Identifier)) - continue; - - // We found an existing instanced output for this identifier - FoundInstancedOutput = &(Iter.Value); - - if (FoundIdentifier.bLoaded) - { - // The output object identifier we found is marked as loaded, - // so uses old node IDs, we must update them, or the next cook - // will fail to locate the output back - FoundIdentifier.ObjectId = Identifier.ObjectId; - FoundIdentifier.GeoId = Identifier.GeoId; - FoundIdentifier.PartId = Identifier.PartId; - } - } - - if (!FoundInstancedOutput) - { - // Create a new one - FHoudiniInstancedOutput CurInstancedOutput; - CurInstancedOutput.OriginalObject = OriginalObj; - CurInstancedOutput.OriginalObjectIndex = InstObjIdx; - CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; - - CurInstancedOutput.VariationObjects.Add(OriginalObj); - CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); - CurInstancedOutput.TransformVariationIndices.SetNumZeroed(InOriginalTransforms[InstObjIdx].Num()); - CurInstancedOutput.MarkChanged(false); - CurInstancedOutput.bStale = false; - - // No variations, simply assign the object/transforms - OutVariationsInstancedObjects.Add(OriginalObj); - OutVariationsInstancedTransforms.Add(InOriginalTransforms[InstObjIdx]); - OutVariationOriginalObjectIdx.Add(InstObjIdx); - OutVariationIndices.Add(0); - - InstancedOutputs.Add(Identifier, CurInstancedOutput); - } - else - { - // Process the potential variations - FHoudiniInstancedOutput& CurInstancedOutput = *FoundInstancedOutput; - UObject *ReplacedOriginalObject = nullptr; - if (CurInstancedOutput.OriginalObject != OriginalObj) - { - ReplacedOriginalObject = CurInstancedOutput.OriginalObject.LoadSynchronous(); - CurInstancedOutput.OriginalObject = OriginalObj; - } - - CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; - - // Shouldnt be needed... - CurInstancedOutput.OriginalObjectIndex = InstObjIdx; - - // Remove any null or deleted variation objects - TArray ObjsToRemove; - for (int32 VarIdx = CurInstancedOutput.VariationObjects.Num() - 1; VarIdx >= 0; --VarIdx) - { - UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill() || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) - { - ObjsToRemove.Add(VarIdx); - } - } - if (ObjsToRemove.Num() > 0) - { - for (const int32 &VarIdx : ObjsToRemove) - { - CurInstancedOutput.VariationObjects.RemoveAt(VarIdx); - CurInstancedOutput.VariationTransformOffsets.RemoveAt(VarIdx); - } - // Force a recompute of variation assignments - CurInstancedOutput.TransformVariationIndices.SetNum(0); - } - - // If we don't have variations, simply use the original object - if (CurInstancedOutput.VariationObjects.Num() <= 0) - { - // No variations? add the original one - CurInstancedOutput.VariationObjects.Add(OriginalObj); - CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); - CurInstancedOutput.TransformVariationIndices.SetNum(0); - } - - // If the number of transforms has changed since the previous cook, - // we need to recompute the variation assignments - if (CurInstancedOutput.TransformVariationIndices.Num() != CurInstancedOutput.OriginalTransforms.Num()) - UpdateVariationAssignements(CurInstancedOutput); - - // Assign variations and their transforms - for (int32 VarIdx = 0; VarIdx < CurInstancedOutput.VariationObjects.Num(); VarIdx++) - { - UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill()) - continue; - - // Get the transforms assigned to that variation - TArray ProcessedTransforms; - ProcessInstanceTransforms(CurInstancedOutput, VarIdx, ProcessedTransforms); - if (ProcessedTransforms.Num() > 0) - { - OutVariationsInstancedObjects.Add(CurrentVariationObject); - OutVariationsInstancedTransforms.Add(ProcessedTransforms); - OutVariationOriginalObjectIdx.Add(InstObjIdx); - OutVariationIndices.Add(VarIdx); - } - } - - CurInstancedOutput.MarkChanged(false); - CurInstancedOutput.bStale = false; - } - } -} - - -void -FHoudiniInstanceTranslator::UpdateVariationAssignements(FHoudiniInstancedOutput& InstancedOutput) -{ - int32 TransformCount = InstancedOutput.OriginalTransforms.Num(); - InstancedOutput.TransformVariationIndices.SetNumZeroed(TransformCount); - - int32 VariationCount = InstancedOutput.VariationObjects.Num(); - if (VariationCount <= 1) - return; - - int nSeed = 1234; - for (int32 Idx = 0; Idx < TransformCount; Idx++) - { - InstancedOutput.TransformVariationIndices[Idx] = fastrand(nSeed) % VariationCount; - } -} - -void -FHoudiniInstanceTranslator::ProcessInstanceTransforms( - FHoudiniInstancedOutput& InstancedOutput, const int32& VariationIdx, TArray& OutProcessedTransforms) -{ - if (!InstancedOutput.VariationObjects.IsValidIndex(VariationIdx)) - return; - - if (!InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx)) - return; - - bool bHasVariations = InstancedOutput.VariationObjects.Num() > 1; - bool bHasTransformOffset = InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx) - ? !InstancedOutput.VariationTransformOffsets[VariationIdx].Equals(FTransform::Identity) - : false; - - if (!bHasVariations && !bHasTransformOffset) - { - // We dont have variations or transform offset, so we can reuse the original transforms as is - OutProcessedTransforms = InstancedOutput.OriginalTransforms; - return; - } - - if (bHasVariations) - { - // We simply need to extract the transforms for this variation - for (int32 TransformIndex = 0; TransformIndex < InstancedOutput.TransformVariationIndices.Num(); TransformIndex++) - { - if (InstancedOutput.TransformVariationIndices[TransformIndex] != VariationIdx) - continue; - - OutProcessedTransforms.Add(InstancedOutput.OriginalTransforms[TransformIndex]); - } - } - else - { - // No variations, we can reuse the original transforms - OutProcessedTransforms = InstancedOutput.OriginalTransforms; - } - - if (bHasTransformOffset) - { - // Get the transform offset for this variation - FVector PositionOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetLocation(); - FQuat RotationOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetRotation(); - FVector ScaleOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetScale3D(); - - FTransform CurrentTransform = FTransform::Identity; - for (int32 TransformIndex = 0; TransformIndex < OutProcessedTransforms.Num(); TransformIndex++) - { - CurrentTransform = OutProcessedTransforms[TransformIndex]; - - // Compute new rotation and scale. - FVector Position = CurrentTransform.GetLocation() + PositionOffset; - FQuat TransformRotation = CurrentTransform.GetRotation() * RotationOffset; - FVector TransformScale3D = CurrentTransform.GetScale3D() * ScaleOffset; - - // Make sure inverse matrix exists - seems to be a bug in Unreal when submitting instances. - // Happens in blueprint as well. - // We want to make sure the scale is not too small, but keep negative values! (Bug 90876) - if (FMath::Abs(TransformScale3D.X) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.X = (TransformScale3D.X > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - if (FMath::Abs(TransformScale3D.Y) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.Y = (TransformScale3D.Y > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - if (FMath::Abs(TransformScale3D.Z) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.Z = (TransformScale3D.Z > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - CurrentTransform.SetLocation(Position); - CurrentTransform.SetRotation(TransformRotation); - CurrentTransform.SetScale3D(TransformScale3D); - - if (CurrentTransform.IsValid()) - OutProcessedTransforms[TransformIndex] = CurrentTransform; - } - } -} - -bool -FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::PackedPrimitive) - return false; - - // Get transforms for each instance - TArray InstancerPartTransforms; - InstancerPartTransforms.SetNumZeroed(InHGPO.PartInfo.InstanceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancerPartTransforms( - FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, - HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, InHGPO.PartInfo.InstanceCount), false); - - // Convert the transform to Unreal's coordinate system - TArray InstancerUnrealTransforms; - InstancerUnrealTransforms.SetNumUninitialized(InstancerPartTransforms.Num()); - for (int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); InstanceIdx++) - { - const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; - FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, InstancerUnrealTransforms[InstanceIdx]); - } - - // Get the part ids for parts being instanced - TArray InstancedPartIds; - InstancedPartIds.SetNumZeroed(InHGPO.PartInfo.InstancedPartCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancedPartIds( - FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, - InstancedPartIds.GetData(), 0, InHGPO.PartInfo.InstancedPartCount), false); - - // See if the user has specified an attribute for splitting the instances - // and get the values - FString SplitAttribName = FString(); - TArray AllSplitAttributeValues; - bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( - InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_PRIM, SplitAttribName, AllSplitAttributeValues); - - // Get the level path attribute on the instancer - TArray AllLevelPaths; - const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( - InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_PRIM); - - // Get the bake actor attribute - TArray AllBakeActorNames; - const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( - InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_PRIM); - - // Get the bake outliner folder attribute - TArray AllBakeOutlinerFolders; - const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_PRIM); - - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; - - for (const auto& InstancedPartId : InstancedPartIds) - { - // Create a GeoPartObject corresponding to the instanced part - FHoudiniGeoPartObject InstancedHGPO; - InstancedHGPO.AssetId = InHGPO.AssetId; - InstancedHGPO.AssetName = InHGPO.AssetName; - InstancedHGPO.ObjectId = InHGPO.ObjectId; - InstancedHGPO.ObjectName = InHGPO.ObjectName; - InstancedHGPO.GeoId = InHGPO.GeoId; - InstancedHGPO.PartId = InstancedPartId; - InstancedHGPO.PartName = InHGPO.PartName; - InstancedHGPO.TransformMatrix = InHGPO.TransformMatrix; - - // TODO: Copy more cached data? - - OutInstancedHGPO.Add(InstancedHGPO); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - } - - // If we don't need to split the instances, we're done - if (!bHasSplitAttribute) - return true; - - // TODO: Optimize this! - // Split the instances using the split attribute's values - - // Move the output arrays to temp arrays - TArray UnsplitInstancedHGPOs = OutInstancedHGPO; - TArray> UnsplitInstancedTransforms = OutInstancedTransforms; - - // Empty the output arrays - OutInstancedHGPO.Empty(); - OutInstancedTransforms.Empty(); - OutSplitAttributeValue.Empty(); - for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedHGPOs.Num(); ObjIdx++) - { - // Map of split values to transform arrays - TMap> SplitTransformMap; - - TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; - - int32 NumInstances = CurrentTransforms.Num(); - if (AllSplitAttributeValues.Num() != NumInstances) - continue; - - // Split the transforms using the split values - for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) - { - const FString& SplitAttrValue = AllSplitAttributeValues[InstIdx]; - SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); - - // Record attributes for any split value we have not yet seen - if (bHasAnyPerSplitAttributes) - { - FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); - if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) - { - PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; - } - if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; - } - if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; - } - } - } - - // Add the objects, transform, split values to the final arrays - for (auto& Iterator : SplitTransformMap) - { - OutSplitAttributeValue.Add(Iterator.Key); - OutInstancedHGPO.Add(UnsplitInstancedHGPOs[ObjIdx]); - OutInstancedTransforms.Add(Iterator.Value); - } - } - - OutSplitAttributeName = SplitAttribName; - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::AttributeInstancer) - return false; - - // Look for the unreal instance attribute - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - - // instance attribute on points - bool is_override_attr = false; - HAPI_Result Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT, &AttribInfo); - - // unreal_instance attribute on points - if (Result != HAPI_RESULT_SUCCESS || AttribInfo.exists == false) - { - is_override_attr = true; - Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT, &AttribInfo); - } - - // unreal_instance attribute on detail - if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) - { - is_override_attr = true; - Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL, &AttribInfo); - } - - // Attribute does not exist. - if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the settings indicating if we want to use a default object when the referenced mesh is invalid - bool bDefaultObjectEnabled = true; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bDefaultObjectEnabled = HoudiniRuntimeSettings->bShowDefaultMesh; - } - - // See if the user has specified an attribute for splitting the instances, and get the values - FString SplitAttribName = FString(); - TArray AllSplitAttributeValues; - bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( - InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_POINT, SplitAttribName, AllSplitAttributeValues); - - // Get the level path attribute on the instancer - TArray AllLevelPaths; - const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( - InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_POINT); - - // Get the bake actor attribute - TArray AllBakeActorNames; - const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( - InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_POINT); - - // Get the bake outliner folder attribute - TArray AllBakeOutlinerFolders; - const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_POINT); - - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; - - // Array used to store the split values per objects - // Will only be used if we have a split attribute - TArray> SplitAttributeValuesPerObject; - - if (AttribInfo.owner == HAPI_ATTROWNER_DETAIL) - { - // If the attribute is on the detail, then its value is applied to all points - TArray DetailInstanceValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InHGPO.GeoId, - InHGPO.PartId, - is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, - AttribInfo, - DetailInstanceValues)) - { - // This should not happen - attribute exists, but there was an error retrieving it. - return false; - } - - if (DetailInstanceValues.Num() <= 0) - { - // No values specified. - return false; - } - - // Attempt to load specified asset. - const FString & AssetName = DetailInstanceValues[0]; - UObject * AttributeObject = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetName, nullptr, LOAD_None, nullptr); - - if (!AttributeObject) - { - // See if the ref is a class that we can instantiate - UClass * FoundClass = FindObject(ANY_PACKAGE, *AssetName); - if (FoundClass != nullptr) - { - // TODO: ensure we'll be able to create an actor from this class! - AttributeObject = FoundClass; - } - } - - if (!AttributeObject && bDefaultObjectEnabled) - { - HOUDINI_LOG_WARNING(TEXT("Failed to load instanced object '%s', using default instance mesh (hidden in game)."), *(AssetName)); - - // Couldn't load the referenced object, use the default reference mesh - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (!DefaultReferenceSM || DefaultReferenceSM->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to load the default instance mesh.")); - return false; - } - AttributeObject = DefaultReferenceSM; - } - - // Attach the objectPtr/transforms/bHiddenInGame if the attributeObject is created successfully - // (with either the actual referenced object or the default placeholder object) - if (AttributeObject) - { - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - - if(bHasSplitAttribute) - SplitAttributeValuesPerObject.Add(AllSplitAttributeValues); - } - } - else - { - // Attribute is on points, so we may have different values for each of them - TArray PointInstanceValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InHGPO.GeoId, - InHGPO.PartId, - is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, - AttribInfo, - PointInstanceValues)) - { - // This should not happen - attribute exists, but there was an error retrieving it. - return false; - } - - // The attribute is on points, so the number of points must match number of transforms. - if (!ensure(PointInstanceValues.Num() == InstancerUnrealTransforms.Num())) - { - // This should not happen, we have mismatch between number of instance values and transforms. - return false; - } - - // If instance attribute exists on points, we need to get all the unique values. - // This will give us all the unique object we want to instance - TMap ObjectsToInstance; - for (const auto& Iter : PointInstanceValues) - { - if (!ObjectsToInstance.Contains(Iter)) - { - // To avoid trying to load an object that fails multiple times, - // still add it to the array if null so we can still skip further attempts - UObject * AttributeObject = StaticLoadObject( - UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); - - if (!AttributeObject) - { - // See if the ref is a class that we can instantiate - UClass * FoundClass = FindObject(ANY_PACKAGE, *Iter); - if (FoundClass != nullptr) - { - // TODO: ensure we'll be able to create an actor from this class! - AttributeObject = FoundClass; - } - } - - ObjectsToInstance.Add(Iter, AttributeObject); - } - } - - // Iterates through all the unique objects and get their corresponding transforms - bool Success = false; - for (auto Iter : ObjectsToInstance) - { - bool bHiddenInGame = false; - // Check that we managed to load this object - UObject * AttributeObject = Iter.Value; - if (!AttributeObject && bDefaultObjectEnabled) - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load instanced object '%s', use default mesh (hidden in game)."), *(Iter.Key)); - - // If failed to load this object, add default reference mesh - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) - { - AttributeObject = DefaultReferenceSM; - bHiddenInGame = true; - } - else// Failed to load default reference mesh object - { - HOUDINI_LOG_WARNING(TEXT("Failed to load default mesh.")); - continue; - } - } - - if (!AttributeObject) - continue; - - if (!bHasSplitAttribute) - { - // No Split attribute: - // Extract the transform values that correspond to this object, and add them to the output arrays - const FString & InstancePath = Iter.Key; - TArray ObjectTransforms; - for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) - { - if (InstancePath.Equals(PointInstanceValues[Idx])) - ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); - } - - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(ObjectTransforms); - Success = true; - } - else - { - // We have a split attribute: - // Extract the transform values and split attribute values for this object, - // add them to the output arrays, and we will process the splits after - const FString & InstancePath = Iter.Key; - TArray ObjectTransforms; - TArray ObjectSplitValues; - for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) - { - if (InstancePath.Equals(PointInstanceValues[Idx])) - { - ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); - ObjectSplitValues.Add(AllSplitAttributeValues[Idx]); - } - } - - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(ObjectTransforms); - SplitAttributeValuesPerObject.Add(ObjectSplitValues); - Success = true; - } - } - - if (!Success) - return false; - } - - // If we don't need to split the instances, we're done - if (!bHasSplitAttribute) - return true; - - // Split the instances one more time, this time using the split values - - // Move the output arrays to temp arrays - TArray UnsplitInstancedObjects = OutInstancedObjects; - TArray> UnsplitInstancedTransforms = OutInstancedTransforms; - - // Empty the output arrays - OutInstancedObjects.Empty(); - OutInstancedTransforms.Empty(); - - // TODO: Output the split values as well! - OutSplitAttributeValue.Empty(); - for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedObjects.Num(); ObjIdx++) - { - UObject* InstancedObject = UnsplitInstancedObjects[ObjIdx]; - - // Map of split values to transform arrays - TMap> SplitTransformMap; - - TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; - TArray& CurrentSplits = SplitAttributeValuesPerObject[ObjIdx]; - - int32 NumInstances = CurrentTransforms.Num(); - if (CurrentSplits.Num() != NumInstances) - continue; - - // Split the transforms using the split values - for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) - { - const FString& SplitAttrValue = CurrentSplits[InstIdx]; - SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); - - // Record attributes for any split value we have not yet seen - if (bHasAnyPerSplitAttributes) - { - FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); - if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) - { - PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; - } - if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; - } - if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; - } - } - } - - // Add the objects, transform, split values to the final arrays - for (auto& Iterator : SplitTransformMap) - { - OutSplitAttributeValue.Add(Iterator.Key); - OutInstancedObjects.Add(InstancedObject); - OutInstancedTransforms.Add(Iterator.Value); - } - } - - OutSplitAttributeName = SplitAttribName; - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::OldSchoolAttributeInstancer) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the objects IDs to instanciate - int32 NumPoints = InHGPO.PartInfo.PointCount; - TArray InstancedObjectIds; - InstancedObjectIds.SetNumUninitialized(NumPoints); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedObjectIds( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InstancedObjectIds.GetData(), 0, NumPoints), false); - - // Find the set of instanced object ids and locate the corresponding parts - TSet UniqueInstancedObjectIds(InstancedObjectIds); - - // Locate all the HoudiniGeoPartObject that corresponds to the instanced object IDs - for (int32 InstancedObjectId : UniqueInstancedObjectIds) - { - // Get the parts that correspond to that object Id - TArray PartsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) - { - if (OutHGPO.Type != EHoudiniPartType::Mesh) - continue; - - if (OutHGPO.bIsInstanced) - continue; - - if (InstancedObjectId != OutHGPO.ObjectId) - continue; - - PartsToInstance.Add(OutHGPO); - } - } - - // Extract only the transforms that correspond to that specific object ID - TArray InstanceTransforms; - for (int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix) - { - if ((InstancedObjectIds[Ix] == InstancedObjectId) && (InstancerUnrealTransforms.IsValidIndex(Ix))) - { - InstanceTransforms.Add(InstancerUnrealTransforms[Ix]); - } - } - - // Add the instanced parts and their transforms to the output arrays - for (const auto& PartToInstance : PartsToInstance) - { - OutInstancedHGPO.Add(PartToInstance); - OutInstancedTransforms.Add(InstanceTransforms); - } - } - - if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0) - return true; - - return false; -} - - -bool -FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::ObjectInstancer) - return false; - - if (InHGPO.ObjectInfo.ObjectToInstanceID < 0) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the parts that correspond to that Object Id - TArray PartsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) - { - if (OutHGPO.Type != EHoudiniPartType::Mesh) - continue; - - /* - // But the instanced geo is actually not marked as instanced - if (!OutHGPO.bIsInstanced) - continue; - */ - - if (InHGPO.ObjectInfo.ObjectToInstanceID != OutHGPO.ObjectId) - continue; - - PartsToInstance.Add(OutHGPO); - } - } - - // Add found HGPO and transforms to the output arrays - for (auto& InstanceHGPO : PartsToInstance) - { - InstanceHGPO.TransformMatrix = InHGPO.TransformMatrix; - - // TODO: - //InstanceHGPO.UpdateCustomName(); - - OutInstancedHGPO.Add(InstanceHGPO); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent* OldComponent, - USceneComponent*& NewComponent, - const bool& InIsSplitMeshInstancer, - const bool& InIsFoliageInstancer, - const TArray& InstancerMaterials, - const int32& InstancerObjectIdx, - const bool& bForceHISM) -{ - enum InstancerComponentType - { - Invalid = -1, - InstancedStaticMeshComponent = 0, - HierarchicalInstancedStaticMeshComponent = 1, - MeshSplitInstancerComponent = 2, - HoudiniInstancedActorComponent = 3, - StaticMeshComponent = 4, - HoudiniStaticMeshComponent = 5, - Foliage = 6 - }; - - // See if we can reuse the old component - InstancerComponentType OldType = InstancerComponentType::Invalid; - if (OldComponent && !OldComponent->IsPendingKill()) - { - if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) - OldType = Foliage; - else if (OldComponent->IsA()) - OldType = HierarchicalInstancedStaticMeshComponent; - else if (OldComponent->IsA()) - OldType = InstancedStaticMeshComponent; - else if (OldComponent->IsA()) - OldType = MeshSplitInstancerComponent; - else if (OldComponent->IsA()) - OldType = HoudiniInstancedActorComponent; - else if (OldComponent->IsA()) - OldType = StaticMeshComponent; - else if (OldComponent->IsA()) - OldType = HoudiniStaticMeshComponent; - } - - // See what type of component we want to create - InstancerComponentType NewType = InstancerComponentType::Invalid; - - UStaticMesh * StaticMesh = Cast(InstancedObject); - UFoliageType * FoliageType = Cast(InstancedObject); - - UHoudiniStaticMesh * HSM = nullptr; - if (!StaticMesh && !FoliageType) - HSM = Cast(InstancedObject); - - if (StaticMesh) - { - if (InstancedObjectTransforms.Num() == 1) - NewType = StaticMeshComponent; - else if (InIsFoliageInstancer) - NewType = Foliage; - else if (InIsSplitMeshInstancer) - NewType = MeshSplitInstancerComponent; - else if(StaticMesh->GetNumLODs() > 1 || bForceHISM) - NewType = HierarchicalInstancedStaticMeshComponent; - else - NewType = InstancedStaticMeshComponent; - } - else if (HSM) - { - if (InstancedObjectTransforms.Num() == 1) - NewType = HoudiniStaticMeshComponent; - else - { - HOUDINI_LOG_ERROR(TEXT("More than one instance transform encountered for UHoudiniStaticMesh: %s"), *(HSM->GetPathName())); - NewType = Invalid; - return false; - } - } - else if (FoliageType) - { - NewType = Foliage; - } - else - { - NewType = HoudiniInstancedActorComponent; - } - - if (OldType == NewType) - NewComponent = OldComponent; - - UMaterialInterface* InstancerMaterial = nullptr; - if (InstancerMaterials.Num() > 0) - { - if (InstancerMaterials.IsValidIndex(InstancerObjectIdx)) - InstancerMaterial = InstancerMaterials[InstancerObjectIdx]; - else - InstancerMaterial = InstancerMaterials[0]; - } - - bool bSuccess = false; - switch (NewType) - { - case InstancedStaticMeshComponent: - case HierarchicalInstancedStaticMeshComponent: - { - // Create an Instanced Static Mesh Component - bSuccess = CreateOrUpdateInstancedStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM); - } - break; - - case MeshSplitInstancerComponent: - { - bSuccess = CreateOrUpdateMeshSplitInstancerComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterials); - } - break; - - case HoudiniInstancedActorComponent: - { - bSuccess = CreateOrUpdateInstancedActorComponent( - InstancedObject, InstancedObjectTransforms, AllPropertyAttributes, ParentComponent, NewComponent); - } - break; - - case StaticMeshComponent: - { - // Create a Static Mesh Component - bSuccess = CreateOrUpdateStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - break; - - case HoudiniStaticMeshComponent: - { - // Create a Houdini Static Mesh Component - bSuccess = CreateOrUpdateHoudiniStaticMeshComponent( - HSM, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - break; - - case Foliage: - { - bSuccess = CreateOrUpdateFoliageInstances( - StaticMesh, FoliageType, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - } - - if (!NewComponent) - return false; - - NewComponent->SetMobility(ParentComponent->Mobility); - NewComponent->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepRelativeTransform); - - // For single instance, that generates a SMC, the transform is already set on the component - // TODO: Should cumulate transform in that case? - if(NewType != StaticMeshComponent && NewType != HoudiniStaticMeshComponent) - NewComponent->SetRelativeTransform(InstancerGeoPartObject.TransformMatrix); - - // Only register if we have a valid component - if (NewComponent->GetOwner() && NewComponent->GetWorld()) - NewComponent->RegisterComponent(); - - // If the old component couldn't be reused, dettach/ destroy it - if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) - { - RemoveAndDestroyComponent(OldComponent); - } - - return bSuccess; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial, /*=nullptr*/ - const bool & bForceHISM) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast(CreatedInstancedComponent); - if (!InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill()) - { - if (InstancedStaticMesh->GetNumLODs() > 1 || bForceHISM) - { - // If the mesh has LODs, use Hierarchical ISMC - InstancedStaticMeshComponent = NewObject( - ComponentOuter, UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - } - else - { - // If the mesh doesnt have LOD, we can use a regular ISMC - InstancedStaticMeshComponent = NewObject( - ComponentOuter, UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - } - - // Change the creation method so the component is listed in the details panels - InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!InstancedStaticMeshComponent) - return false; - - InstancedStaticMeshComponent->SetStaticMesh(InstancedStaticMesh); - InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; - - InstancedStaticMeshComponent->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances themselves - // TODO: We should be calling UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( ... ) - InstancedStaticMeshComponent->ClearInstances(); - InstancedStaticMeshComponent->PreAllocateInstancesMemory(InstancedObjectTransforms.Num()); - for (const auto& Transform : InstancedObjectTransforms) - { - InstancedStaticMeshComponent->AddInstance(Transform); - } - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, 0); - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if(bCreatedNewComponent) - CreatedInstancedComponent = InstancedStaticMeshComponent; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent) -{ - if (!InstancedObject) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniInstancedActorComponent* InstancedActorComponent = Cast(CreatedInstancedComponent); - if (!InstancedActorComponent || InstancedActorComponent->IsPendingKill()) - { - // If the mesh doesnt have LOD, we can use a regular ISMC - InstancedActorComponent = NewObject( - ComponentOuter, UHoudiniInstancedActorComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - InstancedActorComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!InstancedActorComponent) - return false; - - // See if the instanced object has changed - bool bInstancedObjectHasChanged = (InstancedObject != InstancedActorComponent->GetInstancedObject()); - if (bInstancedObjectHasChanged) - { - // All actors will need to be respawned, invalidate all of them - InstancedActorComponent->ClearAllInstances(); - - // Update the HIAC's instanced asset - InstancedActorComponent->SetInstancedObject(InstancedObject); - } - - // Get the level where we want to spawn the actors - ULevel* SpawnLevel = ParentComponent->GetOwner() ? ParentComponent->GetOwner()->GetLevel() : nullptr; - if (!SpawnLevel) - return false; - - // Set the number of needed instances - InstancedActorComponent->SetNumberOfInstances(InstancedObjectTransforms.Num()); - for (int32 Idx = 0; Idx < InstancedObjectTransforms.Num(); Idx++) - { - // if we already have an actor, we can reuse it - const FTransform& CurTransform = InstancedObjectTransforms[Idx]; - - // Get the current instance - // If null, we need to create a new one, else we can reuse the actor - AActor* CurInstance = InstancedActorComponent->GetInstancedActorAt(Idx); - if (!CurInstance || CurInstance->IsPendingKill()) - { - CurInstance = SpawnInstanceActor(CurTransform, SpawnLevel, InstancedActorComponent); - InstancedActorComponent->SetInstanceAt(Idx, CurTransform, CurInstance); - } - else - { - // We can simply update the actor's transform - InstancedActorComponent->SetInstanceTransformAt(Idx, CurTransform); - } - - // Update the generic properties for that instance if any - // TODO: Handle instance variations w/ Idx - UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, Idx); - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - { - CreatedInstancedComponent = InstancedActorComponent; - } - - return true; -} - -// Create or update a MSIC -bool -FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - const TArray& InInstancerMaterials) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniMeshSplitInstancerComponent* MeshSplitComponent = Cast(CreatedInstancedComponent); - if (!MeshSplitComponent || MeshSplitComponent->IsPendingKill()) - { - // If the mesh doesn't have LOD, we can use a regular ISMC - MeshSplitComponent = NewObject( - ComponentOuter, UHoudiniMeshSplitInstancerComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - MeshSplitComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!MeshSplitComponent) - return false; - - MeshSplitComponent->SetStaticMesh(InstancedStaticMesh); - MeshSplitComponent->SetOverrideMaterials(InInstancerMaterials); - - // Now add the instances - MeshSplitComponent->SetInstanceTransforms(InstancedObjectTransforms); - - // Check for instance colors - TArray InstanceColorOverrides; - bool ColorOverrideAttributeFound = false; - - // Look for the unreal_instance_color attribute on points - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_POINT, &AttributeInfo)) - { - ColorOverrideAttributeFound = AttributeInfo.exists; - } - - // Look for the unreal_instance_color attribute on prims? (why? original code) - if (!ColorOverrideAttributeFound) - { - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_PRIM, &AttributeInfo)) - { - ColorOverrideAttributeFound = AttributeInfo.exists; - } - } - - if (ColorOverrideAttributeFound) - { - if (AttributeInfo.tupleSize == 4) - { - // Allocate sufficient buffer for data. - InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)InstanceColorOverrides.GetData(), 0, AttributeInfo.count)) - { - InstanceColorOverrides.Empty(); - } - } - else if (AttributeInfo.tupleSize == 3) - { - // Allocate sufficient buffer for data. - TArray FloatValues; - FloatValues.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)FloatValues.GetData(), 0, AttributeInfo.count)) - { - - // Allocate sufficient buffer for data. - InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); - - // Convert float to FLinearColors - for (int32 ColorIdx = 0; ColorIdx < InstanceColorOverrides.Num(); ColorIdx++) - { - InstanceColorOverrides[ColorIdx].R = FloatValues[ColorIdx * AttributeInfo.tupleSize + 0]; - InstanceColorOverrides[ColorIdx].G = FloatValues[ColorIdx * AttributeInfo.tupleSize + 1]; - InstanceColorOverrides[ColorIdx].B = FloatValues[ColorIdx * AttributeInfo.tupleSize + 2]; - InstanceColorOverrides[ColorIdx].A = 1.0; - } - FloatValues.Empty(); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_COLOR " must be a float[4] or float[3] prim/point attribute")); - } - } - - // if we have vertex color overrides, apply them now -#if WITH_EDITOR - if (InstanceColorOverrides.Num() > 0) - { - // Convert the color attribute to FColor - TArray InstanceColors; - InstanceColors.SetNumUninitialized(InstanceColorOverrides.Num()); - for (int32 ix = 0; ix < InstanceColors.Num(); ++ix) - { - InstanceColors[ix] = InstanceColorOverrides[ix].GetClamped().ToFColor(false); - } - - // Apply them to the instances - TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); - for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) - { - UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - if (!InstanceColors.IsValidIndex(InstIndex)) - continue; - - MeshPaintHelpers::FillStaticMeshVertexColors(CurSMC, -1, InstanceColors[InstIndex], FColor::White); - - //CurSMC->UnregisterComponent(); - //CurSMC->ReregisterComponent(); - - { - // We're only changing instanced vertices on this specific mesh component, so we - // only need to detach our mesh component - FComponentReregisterContext ComponentReregisterContext(CurSMC); - for (auto& CurLODData : CurSMC->LODData) - { - BeginInitResource(CurLODData.OverrideVertexColors); - } - } - - //FIXME: How to get rid of the warning about fixup vertex colors on load? - //SMC->FixupOverrideColorsIfNecessary(); - } - } -#endif - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - // TODO: Optimize - // Loop on attributes first, then components, - // if failing to find the attrib on a component, skip the rest - if (AllPropertyAttributes.Num() > 0) - { - TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); - for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) - { - UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); - } - } - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = MeshSplitComponent; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UStaticMeshComponent* SMC = Cast(CreatedInstancedComponent); - if (!SMC || SMC->IsPendingKill()) - { - // Create a new StaticMeshComponent - SMC = NewObject( - ComponentOuter, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - SMC->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!SMC) - return false; - - SMC->SetStaticMesh(InstancedStaticMesh); - SMC->GetBodyInstance()->bAutoWeld = false; - - SMC->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - SMC->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances Transform - SMC->SetRelativeTransform(InstancedObjectTransforms[0]); - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, 0); - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = SMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( - UHoudiniStaticMesh* InstancedProxyStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - if (!InstancedProxyStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniStaticMeshComponent* HSMC = Cast(CreatedInstancedComponent); - if (!HSMC || HSMC->IsPendingKill()) - { - // Create a new StaticMeshComponent - HSMC = NewObject( - ComponentOuter, UHoudiniStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - HSMC->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!HSMC) - return false; - - HSMC->SetMesh(InstancedProxyStaticMesh); - - HSMC->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedProxyStaticMesh->GetNumStaticMaterials(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - HSMC->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances Transform - HSMC->SetRelativeTransform(InstancedObjectTransforms[0]); - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, 0); - } - - // Assign the new HSMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = HSMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - - -bool -FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( - UStaticMesh* InstancedStaticMesh, - UFoliageType* InFoliageType, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - // We need either a valid SM or a valid Foliage Type - if ((!InstancedStaticMesh || InstancedStaticMesh->IsPendingKill()) - && (!InFoliageType || InFoliageType->IsPendingKill())) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - AActor* OwnerActor = ParentComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return false; - - // See if we already have a FoliageType for that static mesh - bool bCreatedNew = false; - UFoliageType *FoliageType = InFoliageType; - if (!FoliageType || FoliageType->IsPendingKill()) - { - // Foliage Type wasnt specified, only the mesh, try to find an existing foliage for that SM - FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InstancedStaticMesh); - } - else - { - // Foliage Type was specified, see if we can get its static mesh - UFoliageType_InstancedStaticMesh* FoliageISM = Cast(InFoliageType); - if (FoliageISM) - { - InstancedStaticMesh = FoliageISM->GetStaticMesh(); - } - - // See a component already exist on the actor - // If we cant find Foliage info for that foliage type, a new one will be created. - // when we call FindOrAddMesh - bCreatedNew = InstancedFoliageActor->FindInfo(FoliageType) == nullptr; - } - - if (!FoliageType || FoliageType->IsPendingKill()) - { - // We need to create a new FoliageType for this Static Mesh - // TODO: Add foliage default settings - InstancedFoliageActor->AddMesh(InstancedStaticMesh, &FoliageType); - bCreatedNew = true; - } - - if (!bCreatedNew && CreatedInstancedComponent) - { - // TODO: Shouldnt be needed anymore - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); - } - - // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it - FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); - if (!FoliageInfo) - return false; - - FTransform HoudiniAssetTransform = ParentComponent->GetComponentTransform(); - FFoliageInstance FoliageInstance; - int32 CurrentInstanceCount = 0; - for (auto CurrentTransform : InstancedObjectTransforms) - { - // Use our parent component for the base component of the instances, - // this will allow us to clean the instances by component - FoliageInstance.BaseComponent = ParentComponent; - - // TODO: FIX ME! - // Somehow, the first time when we create the Foliage type, instances need to be added with relative transform - // On subsequent cooks, they are actually expecting world transform - if (bCreatedNew) - { - FoliageInstance.Location = CurrentTransform.GetLocation(); - FoliageInstance.Rotation = CurrentTransform.GetRotation().Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D(); - } - else - { - FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); - FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); - } - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - CurrentInstanceCount++; - } - - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); - // TODO: This was due to a bug in UE4.22-20, check if still needed! - if (FoliageHISMC) - FoliageHISMC->BuildTreeIfOutdated(true, true); - - if (InstancerMaterial) - { - FoliageHISMC->OverrideMaterials.Empty(); - int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->StaticMaterials.Num() : 1; - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - FoliageHISMC->SetMaterial(Idx, InstancerMaterial); - } - - // Apply generic attributes if we have any - /* - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); - } - */ - - // Try to aplly generic properties attributes - // either on the instancer, mesh or foliage type - // TODO: Use proper atIndex!! - UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); - UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, 0); - UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, 0); - - if (bCreatedNew && FoliageHISMC) - CreatedInstancedComponent = FoliageHISMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::HapiGetInstanceTransforms( - const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancerUnrealTransforms) -{ - // Get the instance transforms - int32 PointCount = InHGPO.PartInfo.PointCount; - if (PointCount <= 0) - return false; - - TArray InstanceTransforms; - InstanceTransforms.SetNum(PointCount); - for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++) - FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx])); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetInstanceTransformsOnPart( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, HAPI_SRT, - &InstanceTransforms[0], 0, PointCount)) - { - InstanceTransforms.SetNum(0); - - // TODO: Warning? error? - return false; - } - - // Convert the transform to Unreal's coordinate system - OutInstancerUnrealTransforms.SetNumZeroed(InstanceTransforms.Num()); - for (int32 InstanceIdx = 0; InstanceIdx < InstanceTransforms.Num(); InstanceIdx++) - { - const auto& InstanceTransform = InstanceTransforms[InstanceIdx]; - FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, OutInstancerUnrealTransforms[InstanceIdx]); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::GetGenericPropertiesAttributes( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then get all the values for the primitive property attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, -1); - - // .. then finally, all values for point uproperty attributes - // TODO: !! get the correct Index here? - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, -1); - - return FoundCount > 0; -} - -bool -FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes, const int32& AtIndex) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (auto CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current property for the given instance index - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute, AtIndex)) - continue; - - // Success! - NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); - } - - return (NumSuccess > 0); -} - -bool -FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent) -{ - if (!InComponent || InComponent->IsPendingKill()) - return false; - - UFoliageInstancedStaticMeshComponent* FISMC = Cast(InComponent); - if (FISMC && !FISMC->IsPendingKill()) - { - // Make sure foliage our foliage instances have been removed - USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) - CleanupFoliageInstances(FISMC, ParentComponent); - - // do not delete FISMC that still have instances left - // as we have cleaned up our instances before, these have been hand-placed - if (FISMC->GetInstanceCount() > 0) - return false; - } - - USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - return true; - } - - return false; -} - - -bool -FHoudiniInstanceTranslator::GetMaterialOverridesFromAttributes( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutMaterialAttributes) -{ - HAPI_AttributeInfo MaterialAttributeInfo; - FHoudiniApi::AttributeInfo_Init(&MaterialAttributeInfo); - - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL, MaterialAttributeInfo, OutMaterialAttributes); - - /* - // TODO: Support material instances on instancers... - // see FHoudiniMaterialTranslator::CreateMaterialInstances() - // If material attribute and fallbacks were not found, check the material instance attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, MaterialAttributeInfo, OutMaterialAttributes); - } - */ - - if (!MaterialAttributeInfo.exists - /*&& MaterialAttributeInfo.owner != HAPI_ATTROWNER_PRIM - && MaterialAttributeInfo.owner != HAPI_ATTROWNER_DETAIL*/) - { - //HOUDINI_LOG_WARNING(TEXT("Instancer: the unreal_material attribute must be a primitive or detail attribute, ignoring the attribute.")); - OutMaterialAttributes.Empty(); - return false; - } - - return true; -} - -bool -FHoudiniInstanceTranslator::GetInstancerMaterials( - const TArray& MaterialAttributes, TArray& OutInstancerMaterials) -{ - // Use a map to avoid attempting to load the object for each instance - TMap MaterialMap; - - bool bHasValidMaterial = false; - for (auto& CurrentMatString : MaterialAttributes) - { - UMaterialInterface* CurrentMaterialInterface = nullptr; - UMaterialInterface** FoundMaterial = MaterialMap.Find(CurrentMatString); - if (!FoundMaterial) - { - // See if we can find a material interface that matches the attribute - CurrentMaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentMatString, nullptr, LOAD_NoWarn, nullptr)); - - // Check validity - if (!CurrentMaterialInterface || CurrentMaterialInterface->IsPendingKill()) - CurrentMaterialInterface = nullptr; - else - bHasValidMaterial = true; - - // Add what we found to the material map to avoid unnecessary loads - MaterialMap.Add(CurrentMatString, CurrentMaterialInterface); - } - else - { - // Reuse what we previously found - CurrentMaterialInterface = *FoundMaterial; - } - - OutInstancerMaterials.Add(CurrentMaterialInterface); - } - - // IF we couldn't find at least one valid material interface, empty the array - if (!bHasValidMaterial) - OutInstancerMaterials.Empty(); - - return true; -} - -bool -FHoudiniInstanceTranslator::GetInstancerMaterials( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutInstancerMaterials) -{ - TArray MaterialAttributes; - if (!GetMaterialOverridesFromAttributes(InGeoNodeId, InPartId, MaterialAttributes)) - MaterialAttributes.Empty(); - - return GetInstancerMaterials(MaterialAttributes, OutInstancerMaterials); -} - -bool -FHoudiniInstanceTranslator::GetVariationMaterials( - FHoudiniInstancedOutput* InInstancedOutput , const int32& InVariationIndex, - const TArray& InInstancerMaterials, TArray& OutVariationMaterials) -{ - if (!InInstancedOutput || InInstancerMaterials.Num() <= 0) - return false; - - // TODO: This also need to be improved and wont work 100%!! - // Use the instancedoutputs original object index? - if(!InInstancedOutput->VariationObjects.IsValidIndex(InVariationIndex)) - return false; - /* - // No variations, reuse the array - if (InInstancedOutput->VariationObjects.Num() == 1) - { - OutVariationMaterials = InInstancerMaterials; - return true; - } - */ - - if (InInstancedOutput->TransformVariationIndices.Num() == InInstancerMaterials.Num()) - { - for (int32 Idx = 0; Idx < InInstancedOutput->TransformVariationIndices.Num(); Idx++) - { - int32 VariationAssignment = InInstancedOutput->TransformVariationIndices[Idx]; - if (VariationAssignment != InVariationIndex) - continue; - - OutVariationMaterials.Add(InInstancerMaterials[Idx]); - } - } - else - { - if (InInstancerMaterials.IsValidIndex(InVariationIndex)) - OutVariationMaterials.Add(InInstancerMaterials[InVariationIndex]); - else - OutVariationMaterials.Add(InInstancerMaterials[0]); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::IsSplitInstancer(const int32& InGeoId, const int32& InPartId) -{ - bool bSplitMeshInstancer = false; - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; - bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); - - if (!bSplitMeshInstancer) - { - // Try on primitive - Owner = HAPI_ATTROWNER_PRIM; - bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); - } - - if (!bSplitMeshInstancer) - return false; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, - Owner, &AttributeInfo), false); - - if (!AttributeInfo.exists || AttributeInfo.count <= 0) - return false; - - TArray IntData; - // Allocate sufficient buffer for data. - IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, - &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); - - return (IntData[0] != 0); -} - -bool -FHoudiniInstanceTranslator::IsFoliageInstancer(const int32& InGeoId, const int32& InPartId) -{ - bool bIsFoliageInstancer = false; - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - - if (!bIsFoliageInstancer) - { - // Try on primitive - Owner = HAPI_ATTROWNER_PRIM; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - } - - if (!bIsFoliageInstancer) - { - // Finally, try on points - Owner = HAPI_ATTROWNER_POINT; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - } - - if (!bIsFoliageInstancer) - return false; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - Owner, &AttributeInfo), false); - - if (!AttributeInfo.exists || AttributeInfo.count <= 0) - return false; - - // We only support int/float attributes - if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - TArray IntData; - // Allocate sufficient buffer for data. - IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); - - return (IntData[0] != 0); - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - TArray FloatData; - // Allocate sufficient buffer for data. - FloatData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - &AttributeInfo, 0, &FloatData[0], 0, AttributeInfo.count), false); - - return (FloatData[0] != 0); - } - - return false; -} - - -AActor* -FHoudiniInstanceTranslator::SpawnInstanceActor(const FTransform& InTransform, ULevel* InSpawnLevel, UHoudiniInstancedActorComponent* InIAC) -{ - if (!InIAC || InIAC->IsPendingKill()) - return nullptr; - - UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - return nullptr; - - AActor* NewActor = nullptr; - -#if WITH_EDITOR - // Try to spawn a new actor for the given transform - GEditor->ClickLocation = InTransform.GetTranslation(); - GEditor->ClickPlane = FPlane(GEditor->ClickLocation, FVector::UpVector); - - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(InSpawnLevel, InstancedObject, false, RF_Transactional, nullptr); - if (NewActors.Num() > 0) - { - if (NewActors[0] && !NewActors[0]->IsPendingKill()) - { - NewActor = NewActors[0]; - } - } -#endif - - // Make sure that the actor was spawned in the proper level - FHoudiniEngineUtils::MoveActorToLevel(NewActor, InSpawnLevel); - - return NewActor; -} - - -void -FHoudiniInstanceTranslator::CleanupFoliageInstances(/*const FHoudiniInstancedOutput& InInstancedOutput,*/ UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, USceneComponent* InParentComponent) -{ - if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) - return; - - UStaticMesh* FoliageSM = InFoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - return; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(InFoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return; - - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - return; - - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(InParentComponent, FoliageType); - - // Remove the foliage type if it doesn't have any more instances - if(InFoliageHISMC->GetInstanceCount() == 0) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - return; -} - - -FString -FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(UObject* InObject) -{ - USceneComponent* InComponent = Cast(InObject); - - FString InstancerType = TEXT("Instancer"); - if (InComponent && !InComponent->IsPendingKill()) - { - if (InComponent->IsA()) - { - InstancerType = TEXT("(Split Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Actor Instancer)"); - } - else if (InComponent->IsA()) - { - if (InComponent->GetOwner() && InComponent->GetOwner()->IsA()) - InstancerType = TEXT("(Foliage Instancer)"); - else - InstancerType = TEXT("(Hierarchical Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Mesh Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Static Mesh Component)"); - } - } - - return InstancerType; -} - -bool -FHoudiniInstanceTranslator::GetInstancerSplitAttributesAndValues( - const int32& InGeoId, - const int32& InPartId, - const HAPI_AttributeOwner& InSplitAttributeOwner, - FString& OutSplitAttributeName, - TArray& OutAllSplitAttributeValues) -{ - // See if the user has specified an attribute to split the instancers. - bool bHasSplitAttribute = false; - //FString SplitAttribName = FString(); - OutSplitAttributeName = FString(); - - // Look for the unreal_split_attr attribute - // This attribute indicates the name of the point attribute that we'll use to split the instances further - HAPI_AttributeInfo SplitAttribInfo; - FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); - - TArray StringData; - bHasSplitAttribute = FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_SPLIT_ATTR, SplitAttribInfo, StringData, 1); - - if (!bHasSplitAttribute || !SplitAttribInfo.exists || StringData.Num() <= 0) - return false; - - OutSplitAttributeName = StringData[0]; - - // We have specified a split attribute, try to get its values. - OutAllSplitAttributeValues.Empty(); - if (!OutSplitAttributeName.IsEmpty()) - { - //HAPI_AttributeInfo SplitAttribInfo; - FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); - bool bSplitAttrFound = FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoId, - InPartId, - TCHAR_TO_ANSI(*OutSplitAttributeName), - SplitAttribInfo, - OutAllSplitAttributeValues, - 1, - InSplitAttributeOwner); - - if (!bSplitAttrFound || OutAllSplitAttributeValues.Num() <= 0) - { - // We couldn't properly get the point values, clean up everything - // to ensure that we'll ignore the split attribute - bHasSplitAttribute = false; - OutAllSplitAttributeValues.Empty(); - OutSplitAttributeName = FString(); - } - } - - return bHasSplitAttribute; -} - -bool -FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId) -{ - bool bHISM = false; - HAPI_AttributeInfo AttriInfo; - FHoudiniApi::AttributeInfo_Init(&AttriInfo); - TArray IntData; - IntData.Empty(); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM, AttriInfo, IntData, 1)) - { - if (IntData.Num() > 0) - bHISM = IntData[0] == 1; - } - - return bHISM; -} - -void FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() -{ - NumInstancedTransformsPerObject.Empty(); - OriginalInstancedTransformsFlat.Empty(); - for (const TArray& Transforms : OriginalInstancedTransforms) - { - NumInstancedTransformsPerObject.Add(Transforms.Num()); - OriginalInstancedTransformsFlat.Append(Transforms); - } - - OriginalInstanceObjectPackagePaths.Empty(); - for (const UObject* Obj : OriginalInstancedObjects) - { - if (IsValid(Obj)) - { - OriginalInstanceObjectPackagePaths.Add(Obj->GetPathName()); - } - else - { - OriginalInstanceObjectPackagePaths.Add(FString()); - } - } -} - -void FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() -{ - const int32 NumObjects = NumInstancedTransformsPerObject.Num(); - OriginalInstancedTransforms.Init(TArray(), NumObjects); - int32 ObjectIndexOffset = 0; - for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) - { - TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; - const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; - for (int32 Index = 0; Index < NumInstances; ++Index) - { - Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); - } - ObjectIndexOffset += NumInstances; - } - - OriginalInstancedObjects.Empty(); - for (const FString& PackageFullPath : OriginalInstanceObjectPackagePaths) - { - FString PackagePath; - FString PackageName; - const bool bDidSplit = PackageFullPath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); - if (!bDidSplit) - PackagePath = PackageFullPath; - - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!IsValid(Package)) - { - // Editor might have picked up the package yet, try to load it - Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); - } - if (IsValid(Package)) - { - OriginalInstancedObjects.Add(FindObject(Package, *PackageName)); - } - else - { - OriginalInstancedObjects.Add(nullptr); - } - } -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInstanceTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniStaticMesh.h" + +//#include "HAPI/HAPI_Common.h" + +#include "Engine/StaticMesh.h" +#include "ComponentReregisterContext.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#if WITH_EDITOR + //#include "ScopedTransaction.h" + #include "LevelEditorViewport.h" + #include "MeshPaintHelpers.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// Fastrand is a faster alternative to std::rand() +// and doesn't oscillate when looking for 2 values like Unreal's. +inline int fastrand(int& nSeed) +{ + nSeed = (214013 * nSeed + 2531011); + return (nSeed >> 16) & 0x7FFF; +} + +// +bool +FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) +{ + // Get if force to use HISM from attribute + OutInstancedOutputPartData.bForceHISM = HasHISMAttribute(InHGPO.GeoId, InHGPO.PartId); + + // Extract the object and transforms for this instancer + if (!GetInstancerObjectsAndTransforms( + InHGPO, + InAllOutputs, + OutInstancedOutputPartData.OriginalInstancedObjects, + OutInstancedOutputPartData.OriginalInstancedTransforms, + OutInstancedOutputPartData.SplitAttributeName, + OutInstancedOutputPartData.SplitAttributeValues, + OutInstancedOutputPartData.PerSplitAttributes)) + return false; + + // Check if this is a No-Instancers ( unreal_split_instances ) + OutInstancedOutputPartData.bSplitMeshInstancer = IsSplitInstancer(InHGPO.GeoId, InHGPO.PartId); + + OutInstancedOutputPartData.bIsFoliageInstancer = IsFoliageInstancer(InHGPO.GeoId, InHGPO.PartId); + + // Extract the generic attributes + GetGenericPropertiesAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllPropertyAttributes); + + //Get the level path attribute on the instancer + if (!FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllLevelPaths)) + { + // No attribute specified + OutInstancedOutputPartData.AllLevelPaths.Empty(); + } + + // Get the output name attribute + if (!FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.OutputNames)) + { + // No attribute specified + OutInstancedOutputPartData.OutputNames.Empty(); + } + + // See if we have a tile attribute + if (!FHoudiniEngineUtils::GetTileAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.TileValues)) + { + // No attribute specified + OutInstancedOutputPartData.TileValues.Empty(); + } + + // Get the bake actor attribute + if (!FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeActorNames)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeActorNames.Empty(); + } + + // Get the bake outliner folder attribute + if (!FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeOutlinerFolders)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeOutlinerFolders.Empty(); + } + + // See if we have instancer material overrides + if (!GetMaterialOverridesFromAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.MaterialAttributes)) + OutInstancedOutputPartData.MaterialAttributes.Empty(); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + UHoudiniOutput* InOutput, + const TArray& InAllOutputs, + UObject* InOuterComponent, + const TMap* InPreBuiltInstancedOutputPartData) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + // Keep track of the previous cook's component to clean them up after + TMap NewOutputObjects; + TMap OldOutputObjects = InOutput->GetOutputObjects(); + + TMap& InstancedOutputs = InOutput->GetInstancedOutputs(); + // Mark all the current instanced output as stale + for (auto& InstOut : InstancedOutputs) + InstOut.Value.bStale = true; + + USceneComponent* ParentComponent = Cast(InOuterComponent); + if (!ParentComponent) + return false; + + // Keep track of if we remove, create or update any foliage, so that we can repopulate the foliage type list in + // the UI (foliage mode) at the end + bool bHaveAnyFoliageInstancers = false; + + // We also need to cleanup the previous foliages instances (if we have any) + for (auto& CurrentPair : OldOutputObjects) + { + // Foliage instancers store a HISMC in the components + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentPair.Value.OutputComponent); + if (!FoliageHISMC || FoliageHISMC->IsPendingKill()) + continue; + + CleanupFoliageInstances(FoliageHISMC, ParentComponent); + bHaveAnyFoliageInstancers = true; + } + + // The default SM to be used if the instanced object has not been found (when using attribute instancers) + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) + { + // Not an instancer, skip + if (CurHGPO.Type != EHoudiniPartType::Instancer) + continue; + + // Prepare this output object's output identifier + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = CurHGPO.ObjectId; + OutputIdentifier.GeoId = CurHGPO.GeoId; + OutputIdentifier.PartId = CurHGPO.PartId; + OutputIdentifier.PartName = CurHGPO.PartName; + + FHoudiniInstancedOutputPartData InstancedOutputPartDataTmp; + const FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = nullptr; + if (InPreBuiltInstancedOutputPartData) + { + InstancedOutputPartDataPtr = InPreBuiltInstancedOutputPartData->Find(OutputIdentifier); + } + if (!InstancedOutputPartDataPtr) + { + if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs,InstancedOutputPartDataTmp)) + continue; + InstancedOutputPartDataPtr = &InstancedOutputPartDataTmp; + } + + const FHoudiniInstancedOutputPartData& InstancedOutputPartData = *InstancedOutputPartDataPtr; + + TArray InstancerMaterials; + if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes,InstancerMaterials)) + InstancerMaterials.Empty(); + + if (InstancedOutputPartData.bIsFoliageInstancer) + bHaveAnyFoliageInstancers = true; + + // + // TODO: REFACTOR THIS! + // + // We create an instanced output per original object + // These original object can then potentially be replaced by variations + // Each variations will create a instance component / OutputObject + // Currently we process all original objects AND their variations at the same time + // we should instead loop on the original objects + // - get their variations objects/transform + // - create the appropriate instancer + // This means modifying UpdateInstanceVariationsObjects so that it works using + // a single OriginalObject instead of using an array + // Also, apply the same logic to UpdateChangedInstanceOutput + // + + // Array containing all the variations objects for all the original objects + TArray> VariationInstancedObjects; + // Array containing all the variations transforms + TArray> VariationInstancedTransforms; + // Array indicate the original object index for each variation + TArray VariationOriginalObjectIndices; + // Array indicate the variation number for each variation + TArray VariationIndices; + // Update our variations using the instanced outputs + UpdateInstanceVariationObjects( + OutputIdentifier, + InstancedOutputPartData.OriginalInstancedObjects, + InstancedOutputPartData.OriginalInstancedTransforms, InOutput->GetInstancedOutputs(), + VariationInstancedObjects, VariationInstancedTransforms, + VariationOriginalObjectIndices, VariationIndices); + + // Create the instancer components now + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) + { + UObject* InstancedObject = VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + continue; + + if (!VariationInstancedTransforms.IsValidIndex(InstanceObjectIdx)) + continue; + + const TArray& InstancedObjectTransforms = VariationInstancedTransforms[InstanceObjectIdx]; + if (InstancedObjectTransforms.Num() <= 0) + continue; + + // Find the matching instance output now + FHoudiniInstancedOutput* FoundInstancedOutput = nullptr; + { + // Instanced output only use the original object index for their split identifier + FHoudiniOutputObjectIdentifier InstancedOutputIdentifier = OutputIdentifier; + InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]); + FoundInstancedOutput = InstancedOutputs.Find(InstancedOutputIdentifier); + } + + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + OutputIdentifier.SplitIdentifier = + FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]) + + TEXT("_") + + FString::FromInt(VariationIndices[InstanceObjectIdx]); + + // Get the OutputObj for this variation + FHoudiniOutputObject* FoundOutputObject = OldOutputObjects.Find(OutputIdentifier); + // See if we can find an preexisting component for this obj to try to reuse it + USceneComponent* OldInstancerComponent = nullptr; + const bool bIsProxyMesh = InstancedObject->IsA(); + if (FoundOutputObject) + { + if (bIsProxyMesh) + { + OldInstancerComponent = Cast(FoundOutputObject->ProxyComponent); + } + else + { + OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); + } + } + + // Extract the material for this variation + TArray VariationMaterials; + if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) + VariationMaterials.Empty(); + + USceneComponent* NewInstancerComponent = nullptr; + if (!CreateOrUpdateInstanceComponent( + InstancedObject, InstancedObjectTransforms, + InstancedOutputPartData.AllPropertyAttributes, CurHGPO, + ParentComponent, OldInstancerComponent, NewInstancerComponent, + InstancedOutputPartData.bSplitMeshInstancer, + InstancedOutputPartData.bIsFoliageInstancer, + VariationMaterials, + InstancedOutputPartData.bForceHISM)) + { + // TODO?? + continue; + } + + if (!NewInstancerComponent) + continue; + + // If the instanced object (by ref) wasn't found, hide the component + if(InstancedObject == DefaultReferenceSM) + NewInstancerComponent->SetHiddenInGame(true); + else + NewInstancerComponent->SetHiddenInGame(false); + + FHoudiniOutputObject& NewOutputObject = NewOutputObjects.FindOrAdd(OutputIdentifier); + if (bIsProxyMesh) + { + NewOutputObject.ProxyComponent = NewInstancerComponent; + } + else + { + NewOutputObject.OutputComponent = NewInstancerComponent; + } + + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + NewOutputObject.CachedAttributes.Empty(); + NewOutputObject.CachedTokens.Empty(); + + // Todo: get the proper attribute value per variation... + // Cache the level path, output name and tile attributes on the output object + // So they can be reused for baking + if(InstancedOutputPartData.AllLevelPaths.Num() > 0 && !InstancedOutputPartData.AllLevelPaths[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[0]); + + if(InstancedOutputPartData.OutputNames.Num() > 0 && !InstancedOutputPartData.OutputNames[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[0]); + + if(InstancedOutputPartData.TileValues.Num() > 0 && InstancedOutputPartData.TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[0])); + } + + if (InstancedOutputPartData.AllBakeActorNames.Num() > 0 && !InstancedOutputPartData.AllBakeActorNames[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[0]); + + if (InstancedOutputPartData.AllBakeOutlinerFolders.Num() > 0 && !InstancedOutputPartData.AllBakeOutlinerFolders[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[0]); + + if (InstancedOutputPartData.SplitAttributeValues.Num() > 0 + && !InstancedOutputPartData.SplitAttributeName.IsEmpty() + && InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalObjectIndices[InstanceObjectIdx])) + { + FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalObjectIndices[InstanceObjectIdx]]; + + // Cache the split attribute both as attribute and token + NewOutputObject.CachedAttributes.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); + NewOutputObject.CachedTokens.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); + + // If we have a split name that is non-empty, override attributes that can differ by split based + // on the split name + if (!SplitValue.IsEmpty()) + { + const FHoudiniInstancedOutputPerSplitAttributes* PerSplitAttributes = InstancedOutputPartData.PerSplitAttributes.Find(SplitValue); + if (PerSplitAttributes) + { + if (!PerSplitAttributes->LevelPath.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, PerSplitAttributes->LevelPath); + if (!PerSplitAttributes->BakeActorName.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, PerSplitAttributes->BakeActorName); + if (!PerSplitAttributes->BakeOutlinerFolder.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, PerSplitAttributes->BakeOutlinerFolder); + } + } + } + } + } + + // Remove reused components from the old map to avoid their deletion + for (const auto& CurNewPair : NewOutputObjects) + { + // Get the new Identifier / StaticMesh + const FHoudiniOutputObjectIdentifier& OutputIdentifier = CurNewPair.Key; + + // See if we already had that pair in the old map + FHoudiniOutputObject* FoundOldOutputObject = OldOutputObjects.Find(OutputIdentifier); + if (!FoundOldOutputObject) + continue; + + bool bKeep = false; + + UObject* NewComponent = CurNewPair.Value.OutputComponent; + if (NewComponent) + { + UObject* FoundOldComponent = FoundOldOutputObject->OutputComponent; + if (FoundOldComponent && !FoundOldComponent->IsPendingKill()) + { + bKeep = (FoundOldComponent == NewComponent); + } + } + + UObject* NewProxyComponent = CurNewPair.Value.ProxyComponent; + if (NewProxyComponent) + { + UObject* FoundOldProxyComponent = FoundOldOutputObject->ProxyComponent; + if (FoundOldProxyComponent && !FoundOldProxyComponent->IsPendingKill()) + { + bKeep = (FoundOldProxyComponent == NewProxyComponent); + } + } + + if (bKeep) + { + // Remove the reused component from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + + // The Old map now only contains unused/stale components, delete them + for (auto& OldPair : OldOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; + UObject* OldComponent = OldPair.Value.OutputComponent; + if (OldComponent) + { + bool bDestroy = true; + if (OldComponent->IsA()) + { + // When destroying a component, we have to be sure it's not an HISMC owned by an InstanceFoliageActor + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OldComponent); + if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) + bDestroy = false; + } + + if(bDestroy) + RemoveAndDestroyComponent(OldComponent); + + OldPair.Value.OutputComponent = nullptr; + } + + UObject* OldProxyComponent = OldPair.Value.ProxyComponent; + if (OldProxyComponent) + { + RemoveAndDestroyComponent(OldProxyComponent); + OldPair.Value.ProxyComponent = nullptr; + } + } + OldOutputObjects.Empty(); + + // Update the output's object map + // Instancer do not create objects, clean the map + InOutput->SetOutputObjects(NewOutputObjects); + + // If we removed, created or updated any foliage instancers, repopulate the list of foliage types in the UI (foliage + // mode) + if (bHaveAnyFoliageInstancers) + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + + return true; +} + + +bool +FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( + FHoudiniInstancedOutput& InInstancedOutput, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + UHoudiniOutput* InParentOutput, + USceneComponent* InParentComponent) +{ + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = InOutputIdentifier.ObjectId; + OutputIdentifier.GeoId = InOutputIdentifier.GeoId; + OutputIdentifier.PartId = InOutputIdentifier.PartId; + OutputIdentifier.SplitIdentifier = InOutputIdentifier.SplitIdentifier; + OutputIdentifier.PartName = InOutputIdentifier.PartName; + + // Get if force using HISM from attribute + bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); + + TArray OriginalInstancedObjects; + OriginalInstancedObjects.Add(InInstancedOutput.OriginalObject.LoadSynchronous()); + + TArray> OriginalInstancedTransforms; + OriginalInstancedTransforms.Add(InInstancedOutput.OriginalTransforms); + + // Update our variations using the changed instancedoutputs objects + TArray> InstancedObjects; + TArray> InstancedTransforms; + TArray VariationOriginalObjectIndices; + TArray VariationIndices; + UpdateInstanceVariationObjects( + OutputIdentifier, + OriginalInstancedObjects, OriginalInstancedTransforms, + InParentOutput->GetInstancedOutputs(), + InstancedObjects, InstancedTransforms, + VariationOriginalObjectIndices, VariationIndices); + + // Find the HGPO for this instanced output + bool FoundHGPO = false; + FHoudiniGeoPartObject HGPO; + for (const auto& curHGPO : InParentOutput->GetHoudiniGeoPartObjects()) + { + if (OutputIdentifier.Matches(curHGPO)) + { + HGPO = curHGPO; + FoundHGPO = true; + break; + } + } + + if (!FoundHGPO) + { + // TODO check failure + ensure(FoundHGPO); + } + + // Extract the generic attributes for that HGPO + TArray AllPropertyAttributes; + GetGenericPropertiesAttributes(OutputIdentifier.GeoId, OutputIdentifier.PartId, AllPropertyAttributes); + + // Check if this is a No-Instancers ( unreal_split_instances ) + bool bSplitMeshInstancer = IsSplitInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); + + bool bIsFoliageInstancer = IsFoliageInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); + + // See if we have instancer material overrides + TArray InstancerMaterials; + if (!GetInstancerMaterials(OutputIdentifier.GeoId, OutputIdentifier.PartId, InstancerMaterials)) + InstancerMaterials.Empty(); + + // Keep track of the new instancer component in order to be able to clean up the unused/stale ones after. + TMap& OutputObjects = InParentOutput->GetOutputObjects(); + TMap ToDeleteOutputObjects = InParentOutput->GetOutputObjects(); + + // Create the instancer components now + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) + { + UObject* InstancedObject = InstancedObjects[InstanceObjectIdx].LoadSynchronous(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + continue; + + if (!InstancedTransforms.IsValidIndex(InstanceObjectIdx)) + continue; + + const TArray& InstancedObjectTransforms = InstancedTransforms[InstanceObjectIdx]; + if (InstancedObjectTransforms.Num() <= 0) + continue; + + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + // the original object index is used for the instanced outputs split identifier + OutputIdentifier.SplitIdentifier = + InOutputIdentifier.SplitIdentifier + + TEXT("_") + + FString::FromInt(VariationIndices[InstanceObjectIdx]); + + // See if we can find an preexisting component for this obj to try to reuse it + USceneComponent* OldInstancerComponent = nullptr; + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(OutputIdentifier); + if (FoundOutputObject) + { + OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); + } + + // Extract the material for this variation +// FHoudiniInstancedOutput* FoundInstancedOutput = InstancedOutputs.Find(OutputIdentifier); + TArray VariationMaterials; + if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) + VariationMaterials.Empty(); + + USceneComponent* NewInstancerComponent = nullptr; + if (!CreateOrUpdateInstanceComponent( + InstancedObject, InstancedObjectTransforms, + AllPropertyAttributes, HGPO, + InParentComponent, OldInstancerComponent, NewInstancerComponent, + bSplitMeshInstancer, bIsFoliageInstancer, InstancerMaterials, bForceHISM)) + { + // TODO?? + continue; + } + + if (!NewInstancerComponent) + continue; + + if (OldInstancerComponent != NewInstancerComponent) + { + // Previous component wasn't reused, detach and delete it + RemoveAndDestroyComponent(OldInstancerComponent); + + // Replace it with the new component + if (FoundOutputObject) + { + FoundOutputObject->OutputComponent = NewInstancerComponent; + } + else + { + FHoudiniOutputObject& NewOutputObject = OutputObjects.Add(OutputIdentifier); + NewOutputObject.OutputComponent = NewInstancerComponent; + } + } + + // Remove this output object from the todelete map + ToDeleteOutputObjects.Remove(OutputIdentifier); + } + + // Clean up the output objects that are not "reused" by the instanced outs + // The ToDelete map now only contains unused/stale components, delete them + for (auto& ToDeletePair : ToDeleteOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& ToDeleteIdentifier = ToDeletePair.Key; + UObject* OldComponent = ToDeletePair.Value.OutputComponent; + if (OldComponent) + { + RemoveAndDestroyComponent(OldComponent); + ToDeletePair.Value.OutputComponent = nullptr; + } + + UObject* OldProxyComponent = ToDeletePair.Value.ProxyComponent; + if (OldProxyComponent) + { + RemoveAndDestroyComponent(OldProxyComponent); + ToDeletePair.Value.ProxyComponent = nullptr; + } + + // Make sure the stale output object is not in the output map anymore + OutputObjects.Remove(ToDeleteIdentifier); + } + ToDeleteOutputObjects.Empty(); + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValues, + TMap& OutPerSplitAttributes) +{ + TArray InstancedObjects; + TArray> InstancedTransforms; + + TArray InstancedHGPOs; + TArray> InstancedHGPOTransforms; + + bool bSuccess = false; + switch (InHGPO.InstancerType) + { + case EHoudiniInstancerType::PackedPrimitive: + { + // Packed primitives instances + bSuccess = GetPackedPrimitiveInstancerHGPOsAndTransforms( + InHGPO, + InstancedHGPOs, + InstancedHGPOTransforms, + OutSplitAttributeName, + OutSplitAttributeValues, + OutPerSplitAttributes); + } + break; + + case EHoudiniInstancerType::AttributeInstancer: + { + // "Modern" attribute instancer - "unreal_instance" + bSuccess = GetAttributeInstancerObjectsAndTransforms( + InHGPO, + InstancedObjects, + InstancedTransforms, + OutSplitAttributeName, + OutSplitAttributeValues, + OutPerSplitAttributes); + } + break; + + case EHoudiniInstancerType::OldSchoolAttributeInstancer: + { + // Old school attribute override instancer - instance attribute w/ a HoudiniPath + bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); + } + break; + + case EHoudiniInstancerType::ObjectInstancer: + { + // Old School object instancer + bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); + } + break; + } + + if (!bSuccess) + return false; + + // Fetch the UOBject that correspond to the instanced parts + // Attribute instancers don't need to do this since they refer UObjects directly + if (InstancedHGPOs.Num() > 0) + { + for (int32 HGPOIdx = 0; HGPOIdx < InstancedHGPOs.Num(); HGPOIdx++) + { + const FHoudiniGeoPartObject& CurrentHGPO = InstancedHGPOs[HGPOIdx]; + + // Get the UObject that was generated for that HGPO + TArray ObjectsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + if (Output->OutputObjects.Num() <= 0) + continue; + + for (const auto& OutObjPair : Output->OutputObjects) + { + if (!OutObjPair.Key.Matches(CurrentHGPO)) + continue; + + const FHoudiniOutputObject& CurrentOutputObject = OutObjPair.Value; + + // In the case of a single-instance we can use the proxy (if it is current) + // FHoudiniOutputTranslator::UpdateOutputs doesn't allow proxies if there is more than one instance in an output + if (InstancedHGPOTransforms[HGPOIdx].Num() <= 1 && CurrentOutputObject.bProxyIsCurrent + && CurrentOutputObject.ProxyObject && !CurrentOutputObject.ProxyObject->IsPendingKill()) + { + ObjectsToInstance.Add(CurrentOutputObject.ProxyObject); + } + else if (CurrentOutputObject.OutputObject && !CurrentOutputObject.OutputObject->IsPendingKill()) + { + ObjectsToInstance.Add(CurrentOutputObject.OutputObject); + } + } + } + + // Add the UObject and the HGPO transforms to the output arrays + for (const auto& MatchingOutputObj : ObjectsToInstance) + { + InstancedObjects.Add(MatchingOutputObj); + InstancedTransforms.Add(InstancedHGPOTransforms[HGPOIdx]); + } + } + } + + // + if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() ) + { + // TODO + // Error / warning + return false; + } + + OutInstancedObjects = InstancedObjects; + OutInstancedTransforms = InstancedTransforms; + + return true; +} + + +void +FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TArray& InOriginalObjects, + const TArray>& InOriginalTransforms, + TMap& InstancedOutputs, + TArray>& OutVariationsInstancedObjects, + TArray>& OutVariationsInstancedTransforms, + TArray& OutVariationOriginalObjectIdx, + TArray& OutVariationIndices) +{ + FHoudiniOutputObjectIdentifier Identifier = InOutputIdentifier; + for (int32 InstObjIdx = 0; InstObjIdx < InOriginalObjects.Num(); InstObjIdx++) + { + UObject* OriginalObj = InOriginalObjects[InstObjIdx]; + if (!OriginalObj || OriginalObj->IsPendingKill()) + continue; + + // Build this output object's split identifier + Identifier.SplitIdentifier = FString::FromInt(InstObjIdx); + + // Do we have an instanced output object for this one? + FHoudiniInstancedOutput * FoundInstancedOutput = nullptr; + for (auto& Iter : InstancedOutputs) + { + FHoudiniOutputObjectIdentifier& FoundIdentifier = Iter.Key; + if (!(FoundIdentifier == Identifier)) + continue; + + // We found an existing instanced output for this identifier + FoundInstancedOutput = &(Iter.Value); + + if (FoundIdentifier.bLoaded) + { + // The output object identifier we found is marked as loaded, + // so uses old node IDs, we must update them, or the next cook + // will fail to locate the output back + FoundIdentifier.ObjectId = Identifier.ObjectId; + FoundIdentifier.GeoId = Identifier.GeoId; + FoundIdentifier.PartId = Identifier.PartId; + } + } + + if (!FoundInstancedOutput) + { + // Create a new one + FHoudiniInstancedOutput CurInstancedOutput; + CurInstancedOutput.OriginalObject = OriginalObj; + CurInstancedOutput.OriginalObjectIndex = InstObjIdx; + CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + + CurInstancedOutput.VariationObjects.Add(OriginalObj); + CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); + CurInstancedOutput.TransformVariationIndices.SetNumZeroed(InOriginalTransforms[InstObjIdx].Num()); + CurInstancedOutput.MarkChanged(false); + CurInstancedOutput.bStale = false; + + // No variations, simply assign the object/transforms + OutVariationsInstancedObjects.Add(OriginalObj); + OutVariationsInstancedTransforms.Add(InOriginalTransforms[InstObjIdx]); + OutVariationOriginalObjectIdx.Add(InstObjIdx); + OutVariationIndices.Add(0); + + InstancedOutputs.Add(Identifier, CurInstancedOutput); + } + else + { + // Process the potential variations + FHoudiniInstancedOutput& CurInstancedOutput = *FoundInstancedOutput; + UObject *ReplacedOriginalObject = nullptr; + if (CurInstancedOutput.OriginalObject != OriginalObj) + { + ReplacedOriginalObject = CurInstancedOutput.OriginalObject.LoadSynchronous(); + CurInstancedOutput.OriginalObject = OriginalObj; + } + + CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + + // Shouldnt be needed... + CurInstancedOutput.OriginalObjectIndex = InstObjIdx; + + // Remove any null or deleted variation objects + TArray ObjsToRemove; + for (int32 VarIdx = CurInstancedOutput.VariationObjects.Num() - 1; VarIdx >= 0; --VarIdx) + { + UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); + if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill() || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) + { + ObjsToRemove.Add(VarIdx); + } + } + if (ObjsToRemove.Num() > 0) + { + for (const int32 &VarIdx : ObjsToRemove) + { + CurInstancedOutput.VariationObjects.RemoveAt(VarIdx); + CurInstancedOutput.VariationTransformOffsets.RemoveAt(VarIdx); + } + // Force a recompute of variation assignments + CurInstancedOutput.TransformVariationIndices.SetNum(0); + } + + // If we don't have variations, simply use the original object + if (CurInstancedOutput.VariationObjects.Num() <= 0) + { + // No variations? add the original one + CurInstancedOutput.VariationObjects.Add(OriginalObj); + CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); + CurInstancedOutput.TransformVariationIndices.SetNum(0); + } + + // If the number of transforms has changed since the previous cook, + // we need to recompute the variation assignments + if (CurInstancedOutput.TransformVariationIndices.Num() != CurInstancedOutput.OriginalTransforms.Num()) + UpdateVariationAssignements(CurInstancedOutput); + + // Assign variations and their transforms + for (int32 VarIdx = 0; VarIdx < CurInstancedOutput.VariationObjects.Num(); VarIdx++) + { + UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); + if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill()) + continue; + + // Get the transforms assigned to that variation + TArray ProcessedTransforms; + ProcessInstanceTransforms(CurInstancedOutput, VarIdx, ProcessedTransforms); + if (ProcessedTransforms.Num() > 0) + { + OutVariationsInstancedObjects.Add(CurrentVariationObject); + OutVariationsInstancedTransforms.Add(ProcessedTransforms); + OutVariationOriginalObjectIdx.Add(InstObjIdx); + OutVariationIndices.Add(VarIdx); + } + } + + CurInstancedOutput.MarkChanged(false); + CurInstancedOutput.bStale = false; + } + } +} + + +void +FHoudiniInstanceTranslator::UpdateVariationAssignements(FHoudiniInstancedOutput& InstancedOutput) +{ + int32 TransformCount = InstancedOutput.OriginalTransforms.Num(); + InstancedOutput.TransformVariationIndices.SetNumZeroed(TransformCount); + + int32 VariationCount = InstancedOutput.VariationObjects.Num(); + if (VariationCount <= 1) + return; + + int nSeed = 1234; + for (int32 Idx = 0; Idx < TransformCount; Idx++) + { + InstancedOutput.TransformVariationIndices[Idx] = fastrand(nSeed) % VariationCount; + } +} + +void +FHoudiniInstanceTranslator::ProcessInstanceTransforms( + FHoudiniInstancedOutput& InstancedOutput, const int32& VariationIdx, TArray& OutProcessedTransforms) +{ + if (!InstancedOutput.VariationObjects.IsValidIndex(VariationIdx)) + return; + + if (!InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx)) + return; + + bool bHasVariations = InstancedOutput.VariationObjects.Num() > 1; + bool bHasTransformOffset = InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx) + ? !InstancedOutput.VariationTransformOffsets[VariationIdx].Equals(FTransform::Identity) + : false; + + if (!bHasVariations && !bHasTransformOffset) + { + // We dont have variations or transform offset, so we can reuse the original transforms as is + OutProcessedTransforms = InstancedOutput.OriginalTransforms; + return; + } + + if (bHasVariations) + { + // We simply need to extract the transforms for this variation + for (int32 TransformIndex = 0; TransformIndex < InstancedOutput.TransformVariationIndices.Num(); TransformIndex++) + { + if (InstancedOutput.TransformVariationIndices[TransformIndex] != VariationIdx) + continue; + + OutProcessedTransforms.Add(InstancedOutput.OriginalTransforms[TransformIndex]); + } + } + else + { + // No variations, we can reuse the original transforms + OutProcessedTransforms = InstancedOutput.OriginalTransforms; + } + + if (bHasTransformOffset) + { + // Get the transform offset for this variation + FVector PositionOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetLocation(); + FQuat RotationOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetRotation(); + FVector ScaleOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetScale3D(); + + FTransform CurrentTransform = FTransform::Identity; + for (int32 TransformIndex = 0; TransformIndex < OutProcessedTransforms.Num(); TransformIndex++) + { + CurrentTransform = OutProcessedTransforms[TransformIndex]; + + // Compute new rotation and scale. + FVector Position = CurrentTransform.GetLocation() + PositionOffset; + FQuat TransformRotation = CurrentTransform.GetRotation() * RotationOffset; + FVector TransformScale3D = CurrentTransform.GetScale3D() * ScaleOffset; + + // Make sure inverse matrix exists - seems to be a bug in Unreal when submitting instances. + // Happens in blueprint as well. + // We want to make sure the scale is not too small, but keep negative values! (Bug 90876) + if (FMath::Abs(TransformScale3D.X) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.X = (TransformScale3D.X > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + if (FMath::Abs(TransformScale3D.Y) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.Y = (TransformScale3D.Y > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + if (FMath::Abs(TransformScale3D.Z) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.Z = (TransformScale3D.Z > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + CurrentTransform.SetLocation(Position); + CurrentTransform.SetRotation(TransformRotation); + CurrentTransform.SetScale3D(TransformScale3D); + + if (CurrentTransform.IsValid()) + OutProcessedTransforms[TransformIndex] = CurrentTransform; + } + } +} + +bool +FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::PackedPrimitive) + return false; + + // Get transforms for each instance + TArray InstancerPartTransforms; + InstancerPartTransforms.SetNumZeroed(InHGPO.PartInfo.InstanceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancerPartTransforms( + FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, + HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, InHGPO.PartInfo.InstanceCount), false); + + // Convert the transform to Unreal's coordinate system + TArray InstancerUnrealTransforms; + InstancerUnrealTransforms.SetNumUninitialized(InstancerPartTransforms.Num()); + for (int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); InstanceIdx++) + { + const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, InstancerUnrealTransforms[InstanceIdx]); + } + + // Get the part ids for parts being instanced + TArray InstancedPartIds; + InstancedPartIds.SetNumZeroed(InHGPO.PartInfo.InstancedPartCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancedPartIds( + FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, + InstancedPartIds.GetData(), 0, InHGPO.PartInfo.InstancedPartCount), false); + + // See if the user has specified an attribute for splitting the instances + // and get the values + FString SplitAttribName = FString(); + TArray AllSplitAttributeValues; + bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( + InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_PRIM, SplitAttribName, AllSplitAttributeValues); + + // Get the level path attribute on the instancer + TArray AllLevelPaths; + const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( + InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_PRIM); + + // Get the bake actor attribute + TArray AllBakeActorNames; + const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( + InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_PRIM); + + // Get the bake outliner folder attribute + TArray AllBakeOutlinerFolders; + const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_PRIM); + + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; + + for (const auto& InstancedPartId : InstancedPartIds) + { + // Create a GeoPartObject corresponding to the instanced part + FHoudiniGeoPartObject InstancedHGPO; + InstancedHGPO.AssetId = InHGPO.AssetId; + InstancedHGPO.AssetName = InHGPO.AssetName; + InstancedHGPO.ObjectId = InHGPO.ObjectId; + InstancedHGPO.ObjectName = InHGPO.ObjectName; + InstancedHGPO.GeoId = InHGPO.GeoId; + InstancedHGPO.PartId = InstancedPartId; + InstancedHGPO.PartName = InHGPO.PartName; + InstancedHGPO.TransformMatrix = InHGPO.TransformMatrix; + + // TODO: Copy more cached data? + + OutInstancedHGPO.Add(InstancedHGPO); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + } + + // If we don't need to split the instances, we're done + if (!bHasSplitAttribute) + return true; + + // TODO: Optimize this! + // Split the instances using the split attribute's values + + // Move the output arrays to temp arrays + TArray UnsplitInstancedHGPOs = OutInstancedHGPO; + TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + + // Empty the output arrays + OutInstancedHGPO.Empty(); + OutInstancedTransforms.Empty(); + OutSplitAttributeValue.Empty(); + for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedHGPOs.Num(); ObjIdx++) + { + // Map of split values to transform arrays + TMap> SplitTransformMap; + + TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + + int32 NumInstances = CurrentTransforms.Num(); + if (AllSplitAttributeValues.Num() != NumInstances) + continue; + + // Split the transforms using the split values + for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) + { + const FString& SplitAttrValue = AllSplitAttributeValues[InstIdx]; + SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + + // Record attributes for any split value we have not yet seen + if (bHasAnyPerSplitAttributes) + { + FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); + if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) + { + PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; + } + if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; + } + if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; + } + } + } + + // Add the objects, transform, split values to the final arrays + for (auto& Iterator : SplitTransformMap) + { + OutSplitAttributeValue.Add(Iterator.Key); + OutInstancedHGPO.Add(UnsplitInstancedHGPOs[ObjIdx]); + OutInstancedTransforms.Add(Iterator.Value); + } + } + + OutSplitAttributeName = SplitAttribName; + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::AttributeInstancer) + return false; + + // Look for the unreal instance attribute + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + // instance attribute on points + bool is_override_attr = false; + HAPI_Result Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT, &AttribInfo); + + // unreal_instance attribute on points + if (Result != HAPI_RESULT_SUCCESS || AttribInfo.exists == false) + { + is_override_attr = true; + Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT, &AttribInfo); + } + + // unreal_instance attribute on detail + if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) + { + is_override_attr = true; + Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL, &AttribInfo); + } + + // Attribute does not exist. + if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the settings indicating if we want to use a default object when the referenced mesh is invalid + bool bDefaultObjectEnabled = true; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bDefaultObjectEnabled = HoudiniRuntimeSettings->bShowDefaultMesh; + } + + // See if the user has specified an attribute for splitting the instances, and get the values + FString SplitAttribName = FString(); + TArray AllSplitAttributeValues; + bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( + InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_POINT, SplitAttribName, AllSplitAttributeValues); + + // Get the level path attribute on the instancer + TArray AllLevelPaths; + const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( + InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_POINT); + + // Get the bake actor attribute + TArray AllBakeActorNames; + const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( + InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_POINT); + + // Get the bake outliner folder attribute + TArray AllBakeOutlinerFolders; + const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_POINT); + + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; + + // Array used to store the split values per objects + // Will only be used if we have a split attribute + TArray> SplitAttributeValuesPerObject; + + if (AttribInfo.owner == HAPI_ATTROWNER_DETAIL) + { + // If the attribute is on the detail, then its value is applied to all points + TArray DetailInstanceValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InHGPO.GeoId, + InHGPO.PartId, + is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, + AttribInfo, + DetailInstanceValues)) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + if (DetailInstanceValues.Num() <= 0) + { + // No values specified. + return false; + } + + // Attempt to load specified asset. + const FString & AssetName = DetailInstanceValues[0]; + UObject * AttributeObject = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetName, nullptr, LOAD_None, nullptr); + + if (!AttributeObject) + { + // See if the ref is a class that we can instantiate + UClass * FoundClass = FindObject(ANY_PACKAGE, *AssetName); + if (FoundClass != nullptr) + { + // TODO: ensure we'll be able to create an actor from this class! + AttributeObject = FoundClass; + } + } + + if (!AttributeObject && bDefaultObjectEnabled) + { + HOUDINI_LOG_WARNING(TEXT("Failed to load instanced object '%s', using default instance mesh (hidden in game)."), *(AssetName)); + + // Couldn't load the referenced object, use the default reference mesh + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (!DefaultReferenceSM || DefaultReferenceSM->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to load the default instance mesh.")); + return false; + } + AttributeObject = DefaultReferenceSM; + } + + // Attach the objectPtr/transforms/bHiddenInGame if the attributeObject is created successfully + // (with either the actual referenced object or the default placeholder object) + if (AttributeObject) + { + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + + if(bHasSplitAttribute) + SplitAttributeValuesPerObject.Add(AllSplitAttributeValues); + } + } + else + { + // Attribute is on points, so we may have different values for each of them + TArray PointInstanceValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InHGPO.GeoId, + InHGPO.PartId, + is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, + AttribInfo, + PointInstanceValues)) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + // The attribute is on points, so the number of points must match number of transforms. + if (!ensure(PointInstanceValues.Num() == InstancerUnrealTransforms.Num())) + { + // This should not happen, we have mismatch between number of instance values and transforms. + return false; + } + + // If instance attribute exists on points, we need to get all the unique values. + // This will give us all the unique object we want to instance + TMap ObjectsToInstance; + for (const auto& Iter : PointInstanceValues) + { + if (!ObjectsToInstance.Contains(Iter)) + { + // To avoid trying to load an object that fails multiple times, + // still add it to the array if null so we can still skip further attempts + UObject * AttributeObject = StaticLoadObject( + UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); + + if (!AttributeObject) + { + // See if the ref is a class that we can instantiate + UClass * FoundClass = FindObject(ANY_PACKAGE, *Iter); + if (FoundClass != nullptr) + { + // TODO: ensure we'll be able to create an actor from this class! + AttributeObject = FoundClass; + } + } + + ObjectsToInstance.Add(Iter, AttributeObject); + } + } + + // Iterates through all the unique objects and get their corresponding transforms + bool Success = false; + for (auto Iter : ObjectsToInstance) + { + bool bHiddenInGame = false; + // Check that we managed to load this object + UObject * AttributeObject = Iter.Value; + if (!AttributeObject && bDefaultObjectEnabled) + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load instanced object '%s', use default mesh (hidden in game)."), *(Iter.Key)); + + // If failed to load this object, add default reference mesh + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + { + AttributeObject = DefaultReferenceSM; + bHiddenInGame = true; + } + else// Failed to load default reference mesh object + { + HOUDINI_LOG_WARNING(TEXT("Failed to load default mesh.")); + continue; + } + } + + if (!AttributeObject) + continue; + + if (!bHasSplitAttribute) + { + // No Split attribute: + // Extract the transform values that correspond to this object, and add them to the output arrays + const FString & InstancePath = Iter.Key; + TArray ObjectTransforms; + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) + { + if (InstancePath.Equals(PointInstanceValues[Idx])) + ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + } + + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(ObjectTransforms); + Success = true; + } + else + { + // We have a split attribute: + // Extract the transform values and split attribute values for this object, + // add them to the output arrays, and we will process the splits after + const FString & InstancePath = Iter.Key; + TArray ObjectTransforms; + TArray ObjectSplitValues; + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) + { + if (InstancePath.Equals(PointInstanceValues[Idx])) + { + ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + ObjectSplitValues.Add(AllSplitAttributeValues[Idx]); + } + } + + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(ObjectTransforms); + SplitAttributeValuesPerObject.Add(ObjectSplitValues); + Success = true; + } + } + + if (!Success) + return false; + } + + // If we don't need to split the instances, we're done + if (!bHasSplitAttribute) + return true; + + // Split the instances one more time, this time using the split values + + // Move the output arrays to temp arrays + TArray UnsplitInstancedObjects = OutInstancedObjects; + TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + + // Empty the output arrays + OutInstancedObjects.Empty(); + OutInstancedTransforms.Empty(); + + // TODO: Output the split values as well! + OutSplitAttributeValue.Empty(); + for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedObjects.Num(); ObjIdx++) + { + UObject* InstancedObject = UnsplitInstancedObjects[ObjIdx]; + + // Map of split values to transform arrays + TMap> SplitTransformMap; + + TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + TArray& CurrentSplits = SplitAttributeValuesPerObject[ObjIdx]; + + int32 NumInstances = CurrentTransforms.Num(); + if (CurrentSplits.Num() != NumInstances) + continue; + + // Split the transforms using the split values + for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) + { + const FString& SplitAttrValue = CurrentSplits[InstIdx]; + SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + + // Record attributes for any split value we have not yet seen + if (bHasAnyPerSplitAttributes) + { + FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); + if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) + { + PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; + } + if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; + } + if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; + } + } + } + + // Add the objects, transform, split values to the final arrays + for (auto& Iterator : SplitTransformMap) + { + OutSplitAttributeValue.Add(Iterator.Key); + OutInstancedObjects.Add(InstancedObject); + OutInstancedTransforms.Add(Iterator.Value); + } + } + + OutSplitAttributeName = SplitAttribName; + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::OldSchoolAttributeInstancer) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the objects IDs to instanciate + int32 NumPoints = InHGPO.PartInfo.PointCount; + TArray InstancedObjectIds; + InstancedObjectIds.SetNumUninitialized(NumPoints); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedObjectIds( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InstancedObjectIds.GetData(), 0, NumPoints), false); + + // Find the set of instanced object ids and locate the corresponding parts + TSet UniqueInstancedObjectIds(InstancedObjectIds); + + // Locate all the HoudiniGeoPartObject that corresponds to the instanced object IDs + for (int32 InstancedObjectId : UniqueInstancedObjectIds) + { + // Get the parts that correspond to that object Id + TArray PartsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) + { + if (OutHGPO.Type != EHoudiniPartType::Mesh) + continue; + + if (OutHGPO.bIsInstanced) + continue; + + if (InstancedObjectId != OutHGPO.ObjectId) + continue; + + PartsToInstance.Add(OutHGPO); + } + } + + // Extract only the transforms that correspond to that specific object ID + TArray InstanceTransforms; + for (int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix) + { + if ((InstancedObjectIds[Ix] == InstancedObjectId) && (InstancerUnrealTransforms.IsValidIndex(Ix))) + { + InstanceTransforms.Add(InstancerUnrealTransforms[Ix]); + } + } + + // Add the instanced parts and their transforms to the output arrays + for (const auto& PartToInstance : PartsToInstance) + { + OutInstancedHGPO.Add(PartToInstance); + OutInstancedTransforms.Add(InstanceTransforms); + } + } + + if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0) + return true; + + return false; +} + + +bool +FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::ObjectInstancer) + return false; + + if (InHGPO.ObjectInfo.ObjectToInstanceID < 0) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the parts that correspond to that Object Id + TArray PartsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) + { + if (OutHGPO.Type != EHoudiniPartType::Mesh) + continue; + + /* + // But the instanced geo is actually not marked as instanced + if (!OutHGPO.bIsInstanced) + continue; + */ + + if (InHGPO.ObjectInfo.ObjectToInstanceID != OutHGPO.ObjectId) + continue; + + PartsToInstance.Add(OutHGPO); + } + } + + // Add found HGPO and transforms to the output arrays + for (auto& InstanceHGPO : PartsToInstance) + { + InstanceHGPO.TransformMatrix = InHGPO.TransformMatrix; + + // TODO: + //InstanceHGPO.UpdateCustomName(); + + OutInstancedHGPO.Add(InstanceHGPO); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent* OldComponent, + USceneComponent*& NewComponent, + const bool& InIsSplitMeshInstancer, + const bool& InIsFoliageInstancer, + const TArray& InstancerMaterials, + const int32& InstancerObjectIdx, + const bool& bForceHISM) +{ + enum InstancerComponentType + { + Invalid = -1, + InstancedStaticMeshComponent = 0, + HierarchicalInstancedStaticMeshComponent = 1, + MeshSplitInstancerComponent = 2, + HoudiniInstancedActorComponent = 3, + StaticMeshComponent = 4, + HoudiniStaticMeshComponent = 5, + Foliage = 6 + }; + + // See if we can reuse the old component + InstancerComponentType OldType = InstancerComponentType::Invalid; + if (OldComponent && !OldComponent->IsPendingKill()) + { + if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) + OldType = Foliage; + else if (OldComponent->IsA()) + OldType = HierarchicalInstancedStaticMeshComponent; + else if (OldComponent->IsA()) + OldType = InstancedStaticMeshComponent; + else if (OldComponent->IsA()) + OldType = MeshSplitInstancerComponent; + else if (OldComponent->IsA()) + OldType = HoudiniInstancedActorComponent; + else if (OldComponent->IsA()) + OldType = StaticMeshComponent; + else if (OldComponent->IsA()) + OldType = HoudiniStaticMeshComponent; + } + + // See what type of component we want to create + InstancerComponentType NewType = InstancerComponentType::Invalid; + + UStaticMesh * StaticMesh = Cast(InstancedObject); + UFoliageType * FoliageType = Cast(InstancedObject); + + UHoudiniStaticMesh * HSM = nullptr; + if (!StaticMesh && !FoliageType) + HSM = Cast(InstancedObject); + + if (StaticMesh) + { + if (InstancedObjectTransforms.Num() == 1) + NewType = StaticMeshComponent; + else if (InIsFoliageInstancer) + NewType = Foliage; + else if (InIsSplitMeshInstancer) + NewType = MeshSplitInstancerComponent; + else if(StaticMesh->GetNumLODs() > 1 || bForceHISM) + NewType = HierarchicalInstancedStaticMeshComponent; + else + NewType = InstancedStaticMeshComponent; + } + else if (HSM) + { + if (InstancedObjectTransforms.Num() == 1) + NewType = HoudiniStaticMeshComponent; + else + { + HOUDINI_LOG_ERROR(TEXT("More than one instance transform encountered for UHoudiniStaticMesh: %s"), *(HSM->GetPathName())); + NewType = Invalid; + return false; + } + } + else if (FoliageType) + { + NewType = Foliage; + } + else + { + NewType = HoudiniInstancedActorComponent; + } + + if (OldType == NewType) + NewComponent = OldComponent; + + UMaterialInterface* InstancerMaterial = nullptr; + if (InstancerMaterials.Num() > 0) + { + if (InstancerMaterials.IsValidIndex(InstancerObjectIdx)) + InstancerMaterial = InstancerMaterials[InstancerObjectIdx]; + else + InstancerMaterial = InstancerMaterials[0]; + } + + bool bSuccess = false; + switch (NewType) + { + case InstancedStaticMeshComponent: + case HierarchicalInstancedStaticMeshComponent: + { + // Create an Instanced Static Mesh Component + bSuccess = CreateOrUpdateInstancedStaticMeshComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM); + } + break; + + case MeshSplitInstancerComponent: + { + bSuccess = CreateOrUpdateMeshSplitInstancerComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterials); + } + break; + + case HoudiniInstancedActorComponent: + { + bSuccess = CreateOrUpdateInstancedActorComponent( + InstancedObject, InstancedObjectTransforms, AllPropertyAttributes, ParentComponent, NewComponent); + } + break; + + case StaticMeshComponent: + { + // Create a Static Mesh Component + bSuccess = CreateOrUpdateStaticMeshComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + break; + + case HoudiniStaticMeshComponent: + { + // Create a Houdini Static Mesh Component + bSuccess = CreateOrUpdateHoudiniStaticMeshComponent( + HSM, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + break; + + case Foliage: + { + bSuccess = CreateOrUpdateFoliageInstances( + StaticMesh, FoliageType, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + } + + if (!NewComponent) + return false; + + NewComponent->SetMobility(ParentComponent->Mobility); + NewComponent->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepRelativeTransform); + + // For single instance, that generates a SMC, the transform is already set on the component + // TODO: Should cumulate transform in that case? + if(NewType != StaticMeshComponent && NewType != HoudiniStaticMeshComponent) + NewComponent->SetRelativeTransform(InstancerGeoPartObject.TransformMatrix); + + // Only register if we have a valid component + if (NewComponent->GetOwner() && NewComponent->GetWorld()) + NewComponent->RegisterComponent(); + + // If the old component couldn't be reused, dettach/ destroy it + if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) + { + RemoveAndDestroyComponent(OldComponent); + } + + return bSuccess; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial, /*=nullptr*/ + const bool & bForceHISM) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast(CreatedInstancedComponent); + if (!InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill()) + { + if (InstancedStaticMesh->GetNumLODs() > 1 || bForceHISM) + { + // If the mesh has LODs, use Hierarchical ISMC + InstancedStaticMeshComponent = NewObject( + ComponentOuter, UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + else + { + // If the mesh doesnt have LOD, we can use a regular ISMC + InstancedStaticMeshComponent = NewObject( + ComponentOuter, UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + + // Change the creation method so the component is listed in the details panels + InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!InstancedStaticMeshComponent) + return false; + + InstancedStaticMeshComponent->SetStaticMesh(InstancedStaticMesh); + InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; + + InstancedStaticMeshComponent->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances themselves + // TODO: We should be calling UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( ... ) + InstancedStaticMeshComponent->ClearInstances(); + InstancedStaticMeshComponent->PreAllocateInstancesMemory(InstancedObjectTransforms.Num()); + for (const auto& Transform : InstancedObjectTransforms) + { + InstancedStaticMeshComponent->AddInstance(Transform); + } + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + for (const auto& CurrentAttrib : AllPropertyAttributes) + { + UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, 0); + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if(bCreatedNewComponent) + CreatedInstancedComponent = InstancedStaticMeshComponent; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent) +{ + if (!InstancedObject) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniInstancedActorComponent* InstancedActorComponent = Cast(CreatedInstancedComponent); + if (!InstancedActorComponent || InstancedActorComponent->IsPendingKill()) + { + // If the mesh doesnt have LOD, we can use a regular ISMC + InstancedActorComponent = NewObject( + ComponentOuter, UHoudiniInstancedActorComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + InstancedActorComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!InstancedActorComponent) + return false; + + // See if the instanced object has changed + bool bInstancedObjectHasChanged = (InstancedObject != InstancedActorComponent->GetInstancedObject()); + if (bInstancedObjectHasChanged) + { + // All actors will need to be respawned, invalidate all of them + InstancedActorComponent->ClearAllInstances(); + + // Update the HIAC's instanced asset + InstancedActorComponent->SetInstancedObject(InstancedObject); + } + + // Get the level where we want to spawn the actors + ULevel* SpawnLevel = ParentComponent->GetOwner() ? ParentComponent->GetOwner()->GetLevel() : nullptr; + if (!SpawnLevel) + return false; + + // Set the number of needed instances + InstancedActorComponent->SetNumberOfInstances(InstancedObjectTransforms.Num()); + for (int32 Idx = 0; Idx < InstancedObjectTransforms.Num(); Idx++) + { + // if we already have an actor, we can reuse it + const FTransform& CurTransform = InstancedObjectTransforms[Idx]; + + // Get the current instance + // If null, we need to create a new one, else we can reuse the actor + AActor* CurInstance = InstancedActorComponent->GetInstancedActorAt(Idx); + if (!CurInstance || CurInstance->IsPendingKill()) + { + CurInstance = SpawnInstanceActor(CurTransform, SpawnLevel, InstancedActorComponent); + InstancedActorComponent->SetInstanceAt(Idx, CurTransform, CurInstance); + } + else + { + // We can simply update the actor's transform + InstancedActorComponent->SetInstanceTransformAt(Idx, CurTransform); + } + + // Update the generic properties for that instance if any + // TODO: Handle instance variations w/ Idx + UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, Idx); + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + { + CreatedInstancedComponent = InstancedActorComponent; + } + + return true; +} + +// Create or update a MSIC +bool +FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + const TArray& InInstancerMaterials) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniMeshSplitInstancerComponent* MeshSplitComponent = Cast(CreatedInstancedComponent); + if (!MeshSplitComponent || MeshSplitComponent->IsPendingKill()) + { + // If the mesh doesn't have LOD, we can use a regular ISMC + MeshSplitComponent = NewObject( + ComponentOuter, UHoudiniMeshSplitInstancerComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + MeshSplitComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!MeshSplitComponent) + return false; + + MeshSplitComponent->SetStaticMesh(InstancedStaticMesh); + MeshSplitComponent->SetOverrideMaterials(InInstancerMaterials); + + // Now add the instances + MeshSplitComponent->SetInstanceTransforms(InstancedObjectTransforms); + + // Check for instance colors + TArray InstanceColorOverrides; + bool ColorOverrideAttributeFound = false; + + // Look for the unreal_instance_color attribute on points + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_POINT, &AttributeInfo)) + { + ColorOverrideAttributeFound = AttributeInfo.exists; + } + + // Look for the unreal_instance_color attribute on prims? (why? original code) + if (!ColorOverrideAttributeFound) + { + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_PRIM, &AttributeInfo)) + { + ColorOverrideAttributeFound = AttributeInfo.exists; + } + } + + if (ColorOverrideAttributeFound) + { + if (AttributeInfo.tupleSize == 4) + { + // Allocate sufficient buffer for data. + InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)InstanceColorOverrides.GetData(), 0, AttributeInfo.count)) + { + InstanceColorOverrides.Empty(); + } + } + else if (AttributeInfo.tupleSize == 3) + { + // Allocate sufficient buffer for data. + TArray FloatValues; + FloatValues.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)FloatValues.GetData(), 0, AttributeInfo.count)) + { + + // Allocate sufficient buffer for data. + InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); + + // Convert float to FLinearColors + for (int32 ColorIdx = 0; ColorIdx < InstanceColorOverrides.Num(); ColorIdx++) + { + InstanceColorOverrides[ColorIdx].R = FloatValues[ColorIdx * AttributeInfo.tupleSize + 0]; + InstanceColorOverrides[ColorIdx].G = FloatValues[ColorIdx * AttributeInfo.tupleSize + 1]; + InstanceColorOverrides[ColorIdx].B = FloatValues[ColorIdx * AttributeInfo.tupleSize + 2]; + InstanceColorOverrides[ColorIdx].A = 1.0; + } + FloatValues.Empty(); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_COLOR " must be a float[4] or float[3] prim/point attribute")); + } + } + + // if we have vertex color overrides, apply them now +#if WITH_EDITOR + if (InstanceColorOverrides.Num() > 0) + { + // Convert the color attribute to FColor + TArray InstanceColors; + InstanceColors.SetNumUninitialized(InstanceColorOverrides.Num()); + for (int32 ix = 0; ix < InstanceColors.Num(); ++ix) + { + InstanceColors[ix] = InstanceColorOverrides[ix].GetClamped().ToFColor(false); + } + + // Apply them to the instances + TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); + for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) + { + UStaticMeshComponent* CurSMC = Instances[InstIndex]; + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + if (!InstanceColors.IsValidIndex(InstIndex)) + continue; + + MeshPaintHelpers::FillStaticMeshVertexColors(CurSMC, -1, InstanceColors[InstIndex], FColor::White); + + //CurSMC->UnregisterComponent(); + //CurSMC->ReregisterComponent(); + + { + // We're only changing instanced vertices on this specific mesh component, so we + // only need to detach our mesh component + FComponentReregisterContext ComponentReregisterContext(CurSMC); + for (auto& CurLODData : CurSMC->LODData) + { + BeginInitResource(CurLODData.OverrideVertexColors); + } + } + + //FIXME: How to get rid of the warning about fixup vertex colors on load? + //SMC->FixupOverrideColorsIfNecessary(); + } + } +#endif + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + // TODO: Optimize + // Loop on attributes first, then components, + // if failing to find the attrib on a component, skip the rest + if (AllPropertyAttributes.Num() > 0) + { + TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); + for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) + { + UStaticMeshComponent* CurSMC = Instances[InstIndex]; + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + for (const auto& CurrentAttrib : AllPropertyAttributes) + { + UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); + } + } + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = MeshSplitComponent; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UStaticMeshComponent* SMC = Cast(CreatedInstancedComponent); + if (!SMC || SMC->IsPendingKill()) + { + // Create a new StaticMeshComponent + SMC = NewObject( + ComponentOuter, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + SMC->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!SMC) + return false; + + SMC->SetStaticMesh(InstancedStaticMesh); + SMC->GetBodyInstance()->bAutoWeld = false; + + SMC->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + SMC->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances Transform + SMC->SetRelativeTransform(InstancedObjectTransforms[0]); + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + for (const auto& CurrentAttrib : AllPropertyAttributes) + { + UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, 0); + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = SMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( + UHoudiniStaticMesh* InstancedProxyStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + if (!InstancedProxyStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniStaticMeshComponent* HSMC = Cast(CreatedInstancedComponent); + if (!HSMC || HSMC->IsPendingKill()) + { + // Create a new StaticMeshComponent + HSMC = NewObject( + ComponentOuter, UHoudiniStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + HSMC->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!HSMC) + return false; + + HSMC->SetMesh(InstancedProxyStaticMesh); + + HSMC->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedProxyStaticMesh->GetNumStaticMaterials(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + HSMC->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances Transform + HSMC->SetRelativeTransform(InstancedObjectTransforms[0]); + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + for (const auto& CurrentAttrib : AllPropertyAttributes) + { + UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, 0); + } + + // Assign the new HSMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = HSMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + + +bool +FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( + UStaticMesh* InstancedStaticMesh, + UFoliageType* InFoliageType, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + // We need either a valid SM or a valid Foliage Type + if ((!InstancedStaticMesh || InstancedStaticMesh->IsPendingKill()) + && (!InFoliageType || InFoliageType->IsPendingKill())) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + AActor* OwnerActor = ParentComponent->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return false; + + // See if we already have a FoliageType for that static mesh + bool bCreatedNew = false; + UFoliageType *FoliageType = InFoliageType; + if (!FoliageType || FoliageType->IsPendingKill()) + { + // Foliage Type wasnt specified, only the mesh, try to find an existing foliage for that SM + FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InstancedStaticMesh); + } + else + { + // Foliage Type was specified, see if we can get its static mesh + UFoliageType_InstancedStaticMesh* FoliageISM = Cast(InFoliageType); + if (FoliageISM) + { + InstancedStaticMesh = FoliageISM->GetStaticMesh(); + } + + // See a component already exist on the actor + // If we cant find Foliage info for that foliage type, a new one will be created. + // when we call FindOrAddMesh + bCreatedNew = InstancedFoliageActor->FindInfo(FoliageType) == nullptr; + } + + if (!FoliageType || FoliageType->IsPendingKill()) + { + // We need to create a new FoliageType for this Static Mesh + // TODO: Add foliage default settings + InstancedFoliageActor->AddMesh(InstancedStaticMesh, &FoliageType); + bCreatedNew = true; + } + + if (!bCreatedNew && CreatedInstancedComponent) + { + // TODO: Shouldnt be needed anymore + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); + } + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if (!FoliageInfo) + return false; + + FTransform HoudiniAssetTransform = ParentComponent->GetComponentTransform(); + FFoliageInstance FoliageInstance; + int32 CurrentInstanceCount = 0; + for (auto CurrentTransform : InstancedObjectTransforms) + { + // Use our parent component for the base component of the instances, + // this will allow us to clean the instances by component + FoliageInstance.BaseComponent = ParentComponent; + + // TODO: FIX ME! + // Somehow, the first time when we create the Foliage type, instances need to be added with relative transform + // On subsequent cooks, they are actually expecting world transform + if (bCreatedNew) + { + FoliageInstance.Location = CurrentTransform.GetLocation(); + FoliageInstance.Rotation = CurrentTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D(); + } + else + { + FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); + FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); + } + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + CurrentInstanceCount++; + } + + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); + // TODO: This was due to a bug in UE4.22-20, check if still needed! + if (FoliageHISMC) + FoliageHISMC->BuildTreeIfOutdated(true, true); + + if (InstancerMaterial) + { + FoliageHISMC->OverrideMaterials.Empty(); + int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->StaticMaterials.Num() : 1; + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + FoliageHISMC->SetMaterial(Idx, InstancerMaterial); + } + + // Apply generic attributes if we have any + /* + // TODO: Handle variations w/ index + for (const auto& CurrentAttrib : AllPropertyAttributes) + { + UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); + } + */ + + // Try to aplly generic properties attributes + // either on the instancer, mesh or foliage type + // TODO: Use proper atIndex!! + UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, 0); + + if (bCreatedNew && FoliageHISMC) + CreatedInstancedComponent = FoliageHISMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::HapiGetInstanceTransforms( + const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancerUnrealTransforms) +{ + // Get the instance transforms + int32 PointCount = InHGPO.PartInfo.PointCount; + if (PointCount <= 0) + return false; + + TArray InstanceTransforms; + InstanceTransforms.SetNum(PointCount); + for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++) + FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx])); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetInstanceTransformsOnPart( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, HAPI_SRT, + &InstanceTransforms[0], 0, PointCount)) + { + InstanceTransforms.SetNum(0); + + // TODO: Warning? error? + return false; + } + + // Convert the transform to Unreal's coordinate system + OutInstancerUnrealTransforms.SetNumZeroed(InstanceTransforms.Num()); + for (int32 InstanceIdx = 0; InstanceIdx < InstanceTransforms.Num(); InstanceIdx++) + { + const auto& InstanceTransform = InstanceTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, OutInstancerUnrealTransforms[InstanceIdx]); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::GetGenericPropertiesAttributes( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutPropertyAttributes) +{ + // List all the generic property detail attributes ... + int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + + // .. then get all the values for the primitive property attributes + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, -1); + + // .. then finally, all values for point uproperty attributes + // TODO: !! get the correct Index here? + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, -1); + + return FoundCount > 0; +} + +bool +FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes, const int32& AtIndex) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (auto CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current property for the given instance index + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute, AtIndex)) + continue; + + // Success! + NumSuccess++; + FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); + } + + return (NumSuccess > 0); +} + +bool +FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent) +{ + if (!InComponent || InComponent->IsPendingKill()) + return false; + + UFoliageInstancedStaticMeshComponent* FISMC = Cast(InComponent); + if (FISMC && !FISMC->IsPendingKill()) + { + // Make sure foliage our foliage instances have been removed + USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); + if (ParentComponent && !ParentComponent->IsPendingKill()) + CleanupFoliageInstances(FISMC, ParentComponent); + + // do not delete FISMC that still have instances left + // as we have cleaned up our instances before, these have been hand-placed + if (FISMC->GetInstanceCount() > 0) + return false; + } + + USceneComponent* SceneComponent = Cast(InComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + return true; + } + + return false; +} + + +bool +FHoudiniInstanceTranslator::GetMaterialOverridesFromAttributes( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutMaterialAttributes) +{ + HAPI_AttributeInfo MaterialAttributeInfo; + FHoudiniApi::AttributeInfo_Init(&MaterialAttributeInfo); + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL, MaterialAttributeInfo, OutMaterialAttributes); + + /* + // TODO: Support material instances on instancers... + // see FHoudiniMaterialTranslator::CreateMaterialInstances() + // If material attribute and fallbacks were not found, check the material instance attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, MaterialAttributeInfo, OutMaterialAttributes); + } + */ + + if (!MaterialAttributeInfo.exists + /*&& MaterialAttributeInfo.owner != HAPI_ATTROWNER_PRIM + && MaterialAttributeInfo.owner != HAPI_ATTROWNER_DETAIL*/) + { + //HOUDINI_LOG_WARNING(TEXT("Instancer: the unreal_material attribute must be a primitive or detail attribute, ignoring the attribute.")); + OutMaterialAttributes.Empty(); + return false; + } + + return true; +} + +bool +FHoudiniInstanceTranslator::GetInstancerMaterials( + const TArray& MaterialAttributes, TArray& OutInstancerMaterials) +{ + // Use a map to avoid attempting to load the object for each instance + TMap MaterialMap; + + bool bHasValidMaterial = false; + for (auto& CurrentMatString : MaterialAttributes) + { + UMaterialInterface* CurrentMaterialInterface = nullptr; + UMaterialInterface** FoundMaterial = MaterialMap.Find(CurrentMatString); + if (!FoundMaterial) + { + // See if we can find a material interface that matches the attribute + CurrentMaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentMatString, nullptr, LOAD_NoWarn, nullptr)); + + // Check validity + if (!CurrentMaterialInterface || CurrentMaterialInterface->IsPendingKill()) + CurrentMaterialInterface = nullptr; + else + bHasValidMaterial = true; + + // Add what we found to the material map to avoid unnecessary loads + MaterialMap.Add(CurrentMatString, CurrentMaterialInterface); + } + else + { + // Reuse what we previously found + CurrentMaterialInterface = *FoundMaterial; + } + + OutInstancerMaterials.Add(CurrentMaterialInterface); + } + + // IF we couldn't find at least one valid material interface, empty the array + if (!bHasValidMaterial) + OutInstancerMaterials.Empty(); + + return true; +} + +bool +FHoudiniInstanceTranslator::GetInstancerMaterials( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutInstancerMaterials) +{ + TArray MaterialAttributes; + if (!GetMaterialOverridesFromAttributes(InGeoNodeId, InPartId, MaterialAttributes)) + MaterialAttributes.Empty(); + + return GetInstancerMaterials(MaterialAttributes, OutInstancerMaterials); +} + +bool +FHoudiniInstanceTranslator::GetVariationMaterials( + FHoudiniInstancedOutput* InInstancedOutput , const int32& InVariationIndex, + const TArray& InInstancerMaterials, TArray& OutVariationMaterials) +{ + if (!InInstancedOutput || InInstancerMaterials.Num() <= 0) + return false; + + // TODO: This also need to be improved and wont work 100%!! + // Use the instancedoutputs original object index? + if(!InInstancedOutput->VariationObjects.IsValidIndex(InVariationIndex)) + return false; + /* + // No variations, reuse the array + if (InInstancedOutput->VariationObjects.Num() == 1) + { + OutVariationMaterials = InInstancerMaterials; + return true; + } + */ + + if (InInstancedOutput->TransformVariationIndices.Num() == InInstancerMaterials.Num()) + { + for (int32 Idx = 0; Idx < InInstancedOutput->TransformVariationIndices.Num(); Idx++) + { + int32 VariationAssignment = InInstancedOutput->TransformVariationIndices[Idx]; + if (VariationAssignment != InVariationIndex) + continue; + + OutVariationMaterials.Add(InInstancerMaterials[Idx]); + } + } + else + { + if (InInstancerMaterials.IsValidIndex(InVariationIndex)) + OutVariationMaterials.Add(InInstancerMaterials[InVariationIndex]); + else + OutVariationMaterials.Add(InInstancerMaterials[0]); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::IsSplitInstancer(const int32& InGeoId, const int32& InPartId) +{ + bool bSplitMeshInstancer = false; + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; + bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); + + if (!bSplitMeshInstancer) + { + // Try on primitive + Owner = HAPI_ATTROWNER_PRIM; + bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); + } + + if (!bSplitMeshInstancer) + return false; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, + Owner, &AttributeInfo), false); + + if (!AttributeInfo.exists || AttributeInfo.count <= 0) + return false; + + TArray IntData; + // Allocate sufficient buffer for data. + IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, + &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); + + return (IntData[0] != 0); +} + +bool +FHoudiniInstanceTranslator::IsFoliageInstancer(const int32& InGeoId, const int32& InPartId) +{ + bool bIsFoliageInstancer = false; + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + + if (!bIsFoliageInstancer) + { + // Try on primitive + Owner = HAPI_ATTROWNER_PRIM; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + } + + if (!bIsFoliageInstancer) + { + // Finally, try on points + Owner = HAPI_ATTROWNER_POINT; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + } + + if (!bIsFoliageInstancer) + return false; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, + Owner, &AttributeInfo), false); + + if (!AttributeInfo.exists || AttributeInfo.count <= 0) + return false; + + // We only support int/float attributes + if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + TArray IntData; + // Allocate sufficient buffer for data. + IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, + &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); + + return (IntData[0] != 0); + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + TArray FloatData; + // Allocate sufficient buffer for data. + FloatData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, + &AttributeInfo, 0, &FloatData[0], 0, AttributeInfo.count), false); + + return (FloatData[0] != 0); + } + + return false; +} + + +AActor* +FHoudiniInstanceTranslator::SpawnInstanceActor(const FTransform& InTransform, ULevel* InSpawnLevel, UHoudiniInstancedActorComponent* InIAC) +{ + if (!InIAC || InIAC->IsPendingKill()) + return nullptr; + + UObject* InstancedObject = InIAC->GetInstancedObject(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + return nullptr; + + AActor* NewActor = nullptr; + +#if WITH_EDITOR + // Try to spawn a new actor for the given transform + GEditor->ClickLocation = InTransform.GetTranslation(); + GEditor->ClickPlane = FPlane(GEditor->ClickLocation, FVector::UpVector); + + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(InSpawnLevel, InstancedObject, false, RF_Transactional, nullptr); + if (NewActors.Num() > 0) + { + if (NewActors[0] && !NewActors[0]->IsPendingKill()) + { + NewActor = NewActors[0]; + } + } +#endif + + // Make sure that the actor was spawned in the proper level + FHoudiniEngineUtils::MoveActorToLevel(NewActor, InSpawnLevel); + + return NewActor; +} + + +void +FHoudiniInstanceTranslator::CleanupFoliageInstances(/*const FHoudiniInstancedOutput& InInstancedOutput,*/ UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, USceneComponent* InParentComponent) +{ + if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) + return; + + UStaticMesh* FoliageSM = InFoliageHISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + return; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(InFoliageHISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return; + + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + if (!FoliageType || FoliageType->IsPendingKill()) + return; + + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(InParentComponent, FoliageType); + + // Remove the foliage type if it doesn't have any more instances + if(InFoliageHISMC->GetInstanceCount() == 0) + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + + return; +} + + +FString +FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(UObject* InObject) +{ + USceneComponent* InComponent = Cast(InObject); + + FString InstancerType = TEXT("Instancer"); + if (InComponent && !InComponent->IsPendingKill()) + { + if (InComponent->IsA()) + { + InstancerType = TEXT("(Split Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Actor Instancer)"); + } + else if (InComponent->IsA()) + { + if (InComponent->GetOwner() && InComponent->GetOwner()->IsA()) + InstancerType = TEXT("(Foliage Instancer)"); + else + InstancerType = TEXT("(Hierarchical Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Mesh Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Static Mesh Component)"); + } + } + + return InstancerType; +} + +bool +FHoudiniInstanceTranslator::GetInstancerSplitAttributesAndValues( + const int32& InGeoId, + const int32& InPartId, + const HAPI_AttributeOwner& InSplitAttributeOwner, + FString& OutSplitAttributeName, + TArray& OutAllSplitAttributeValues) +{ + // See if the user has specified an attribute to split the instancers. + bool bHasSplitAttribute = false; + //FString SplitAttribName = FString(); + OutSplitAttributeName = FString(); + + // Look for the unreal_split_attr attribute + // This attribute indicates the name of the point attribute that we'll use to split the instances further + HAPI_AttributeInfo SplitAttribInfo; + FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); + + TArray StringData; + bHasSplitAttribute = FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_SPLIT_ATTR, SplitAttribInfo, StringData, 1); + + if (!bHasSplitAttribute || !SplitAttribInfo.exists || StringData.Num() <= 0) + return false; + + OutSplitAttributeName = StringData[0]; + + // We have specified a split attribute, try to get its values. + OutAllSplitAttributeValues.Empty(); + if (!OutSplitAttributeName.IsEmpty()) + { + //HAPI_AttributeInfo SplitAttribInfo; + FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); + bool bSplitAttrFound = FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, + InPartId, + TCHAR_TO_ANSI(*OutSplitAttributeName), + SplitAttribInfo, + OutAllSplitAttributeValues, + 1, + InSplitAttributeOwner); + + if (!bSplitAttrFound || OutAllSplitAttributeValues.Num() <= 0) + { + // We couldn't properly get the point values, clean up everything + // to ensure that we'll ignore the split attribute + bHasSplitAttribute = false; + OutAllSplitAttributeValues.Empty(); + OutSplitAttributeName = FString(); + } + } + + return bHasSplitAttribute; +} + +bool +FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId) +{ + bool bHISM = false; + HAPI_AttributeInfo AttriInfo; + FHoudiniApi::AttributeInfo_Init(&AttriInfo); + TArray IntData; + IntData.Empty(); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM, AttriInfo, IntData, 1)) + { + if (IntData.Num() > 0) + bHISM = IntData[0] == 1; + } + + return bHISM; +} + +void FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() +{ + NumInstancedTransformsPerObject.Empty(); + OriginalInstancedTransformsFlat.Empty(); + for (const TArray& Transforms : OriginalInstancedTransforms) + { + NumInstancedTransformsPerObject.Add(Transforms.Num()); + OriginalInstancedTransformsFlat.Append(Transforms); + } + + OriginalInstanceObjectPackagePaths.Empty(); + for (const UObject* Obj : OriginalInstancedObjects) + { + if (IsValid(Obj)) + { + OriginalInstanceObjectPackagePaths.Add(Obj->GetPathName()); + } + else + { + OriginalInstanceObjectPackagePaths.Add(FString()); + } + } +} + +void FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() +{ + const int32 NumObjects = NumInstancedTransformsPerObject.Num(); + OriginalInstancedTransforms.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) + { + TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; + const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; + for (int32 Index = 0; Index < NumInstances; ++Index) + { + Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumInstances; + } + + OriginalInstancedObjects.Empty(); + for (const FString& PackageFullPath : OriginalInstanceObjectPackagePaths) + { + FString PackagePath; + FString PackageName; + const bool bDidSplit = PackageFullPath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bDidSplit) + PackagePath = PackageFullPath; + + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!IsValid(Package)) + { + // Editor might have picked up the package yet, try to load it + Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); + } + if (IsValid(Package)) + { + OriginalInstancedObjects.Add(FindObject(Package, *PackageName)); + } + else + { + OriginalInstancedObjects.Add(nullptr); + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h index 7ec309048..91d70f84b 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h @@ -1,363 +1,363 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniOutput.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniGenericAttribute.h" - -#include "HoudiniInstanceTranslator.generated.h" - -class UStaticMesh; -class UFoliageType; -class UHoudiniStaticMesh; -class UHoudiniInstancedActorComponent; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes -{ -public: - - GENERATED_BODY() - - // level path attribute value - UPROPERTY() - FString LevelPath; - - // Bake actor name attribute value - UPROPERTY() - FString BakeActorName; - - // bake outliner folder attribute value - UPROPERTY() - FString BakeOutlinerFolder; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData -{ -public: - - GENERATED_BODY() - - UPROPERTY() - bool bForceHISM; - - UPROPERTY() - TArray OriginalInstancedObjects; - - UPROPERTY() - TArray OriginalInstanceObjectPackagePaths; - - TArray> OriginalInstancedTransforms; - - UPROPERTY() - TArray NumInstancedTransformsPerObject; - - UPROPERTY() - TArray OriginalInstancedTransformsFlat; - - UPROPERTY() - FString SplitAttributeName; - - UPROPERTY() - TArray SplitAttributeValues; - - UPROPERTY() - bool bSplitMeshInstancer; - - UPROPERTY() - bool bIsFoliageInstancer; - - UPROPERTY() - TArray AllPropertyAttributes; - - // All level path attributes from the first attribute owner we could find - UPROPERTY() - TArray AllLevelPaths; - - // All bake actor name attributes from the first attribute owner we could find - UPROPERTY() - TArray AllBakeActorNames; - - // All bake outliner folder attributes from the first attribute owner we could find - UPROPERTY() - TArray AllBakeOutlinerFolders; - - // A map of split value to attribute values that are valid per split (unreal_bake_actor, unreal_level_path, - // unreal_bake_outliner_folder) - UPROPERTY() - TMap PerSplitAttributes; - - UPROPERTY() - TArray OutputNames; - - UPROPERTY() - TArray TileValues; - - UPROPERTY() - TArray MaterialAttributes; - - void BuildFlatInstancedTransformsAndObjectPaths(); - - void BuildOriginalInstancedTransformsAndObjectArrays(); -}; - -struct HOUDINIENGINE_API FHoudiniInstanceTranslator -{ - public: - - static bool PopulateInstancedOutputPartData( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); - - static bool CreateAllInstancersFromHoudiniOutput( - UHoudiniOutput* InOutput, - const TArray& InAllOutputs, - UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); - - static bool GetInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValues, - TMap& OutPerSplitAttributes); - - static bool GetPackedPrimitiveInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes); - - static bool GetAttributeInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes); - - static bool GetOldSchoolAttributeInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms); - - static bool GetObjectInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms); - - // Updates the variations array using the instanced outputs - static void UpdateInstanceVariationObjects( - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TArray& InOriginalObjects, - const TArray>& InOriginalTransforms, - TMap& InstancedOutputs, - TArray>& OutVariationsInstancedObjects, - TArray>& OutVariationsInstancedTransforms, - TArray& OutVariationOriginalObjectIdx, - TArray& OutVariationIndices); - - // Recreates the components after an instanced outputs has been changed - static bool UpdateChangedInstancedOutput( - FHoudiniInstancedOutput& InInstancedOutput, - const FHoudiniOutputObjectIdentifier& OutputIdentifier, - UHoudiniOutput* InParentOutput, - USceneComponent* InParentComponent); - - // Recomputes the variation assignements for a given instanced output - static void UpdateVariationAssignements( - FHoudiniInstancedOutput& InstancedOutput); - - // Extracts the final transforms (with the transform offset applied) for a given variation - static void ProcessInstanceTransforms( - FHoudiniInstancedOutput& InstancedOutput, - const int32& VariationIdx, - TArray& OutProcessedTransforms); - - // Creates a new component or updates the previous one if possible - static bool CreateOrUpdateInstanceComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent* OldComponent, - USceneComponent*& NewComponent, - const bool& InIsSplitMeshInstancer, - const bool& InIsFoliageInstancer, - const TArray& InstancerMaterials, - const int32& InstancerObjectIdx = 0, - const bool& bForceHISM = false); - - // Create or update an ISMC / HISMC - static bool CreateOrUpdateInstancedStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr, - const bool& bForceHISM = false); - - // Create or update an IAC - static bool CreateOrUpdateInstancedActorComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent); - - // Create or update a MeshSplitInstancer - static bool CreateOrUpdateMeshSplitInstancerComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - const TArray& InstancerMaterials); - - // Create or update a StaticMeshComponent (when we have only one instance) - static bool CreateOrUpdateStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr); - - // Create or update a HoudiniStaticMeshComponent (when we have only one instance) - static bool CreateOrUpdateHoudiniStaticMeshComponent( - UHoudiniStaticMesh* InstancedProxyStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr); - - // Create or update a Foliage instances - static bool CreateOrUpdateFoliageInstances( - UStaticMesh* InstancedStaticMesh, - UFoliageType* InFoliageType, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/); - - // Helper fumction to properly remove/destroy a component - static bool RemoveAndDestroyComponent( - UObject* InComponent); - - // Utility function - // Fetches instance transforms and convert them to ue4 coordinates - static bool HapiGetInstanceTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancerUnrealTransforms); - - // Helper function used to spawn a new Actor for UHoudiniInstancedActorComponent - // Relies on editor-only functionalities, so this function is not on the IAC itself - static AActor* SpawnInstanceActor( - const FTransform& InTransform, - ULevel* InSpawnLevel, - UHoudiniInstancedActorComponent* InIAC); - - // Helper functions for generic property attributes - static bool GetGenericPropertiesAttributes( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, - const TArray& InAllPropertyAttributes, - const int32& AtIndex); - - static bool GetMaterialOverridesFromAttributes( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutMaterialAttributes); - - static bool GetInstancerMaterials( - const TArray& MaterialAttributes, - TArray& OutInstancerMaterials); - - static bool GetInstancerMaterials( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutInstancerMaterials); - - static bool GetVariationMaterials( - FHoudiniInstancedOutput* InInstancedOutput, - const int32& InVariationIndex, - const TArray& InInstancerMaterials, - TArray& OutVariationMaterials); - - static bool IsSplitInstancer( - const int32& InGeoId, - const int32& InPartId); - - static bool IsFoliageInstancer( - const int32& InGeoId, - const int32& InPartId); - - static void CleanupFoliageInstances( - UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, - USceneComponent* InParentComponent); - - static FString GetInstancerTypeFromComponent( - UObject* InComponent); - - // Returns the name and values of the attribute that has been specified to split the instances - // returns false if the attribute is invalid or hasn't been specified - static bool GetInstancerSplitAttributesAndValues( - const int32& InGeoId, - const int32& InPartId, - const HAPI_AttributeOwner& InSplitAttributeOwner, - FString& OutSplitAttributeName, - TArray& OutAllSplitAttributeValues); - - // Get if force using HISM from attribute - static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniOutput.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniGenericAttribute.h" + +#include "HoudiniInstanceTranslator.generated.h" + +class UStaticMesh; +class UFoliageType; +class UHoudiniStaticMesh; +class UHoudiniInstancedActorComponent; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes +{ +public: + + GENERATED_BODY() + + // level path attribute value + UPROPERTY() + FString LevelPath; + + // Bake actor name attribute value + UPROPERTY() + FString BakeActorName; + + // bake outliner folder attribute value + UPROPERTY() + FString BakeOutlinerFolder; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData +{ +public: + + GENERATED_BODY() + + UPROPERTY() + bool bForceHISM; + + UPROPERTY() + TArray OriginalInstancedObjects; + + UPROPERTY() + TArray OriginalInstanceObjectPackagePaths; + + TArray> OriginalInstancedTransforms; + + UPROPERTY() + TArray NumInstancedTransformsPerObject; + + UPROPERTY() + TArray OriginalInstancedTransformsFlat; + + UPROPERTY() + FString SplitAttributeName; + + UPROPERTY() + TArray SplitAttributeValues; + + UPROPERTY() + bool bSplitMeshInstancer; + + UPROPERTY() + bool bIsFoliageInstancer; + + UPROPERTY() + TArray AllPropertyAttributes; + + // All level path attributes from the first attribute owner we could find + UPROPERTY() + TArray AllLevelPaths; + + // All bake actor name attributes from the first attribute owner we could find + UPROPERTY() + TArray AllBakeActorNames; + + // All bake outliner folder attributes from the first attribute owner we could find + UPROPERTY() + TArray AllBakeOutlinerFolders; + + // A map of split value to attribute values that are valid per split (unreal_bake_actor, unreal_level_path, + // unreal_bake_outliner_folder) + UPROPERTY() + TMap PerSplitAttributes; + + UPROPERTY() + TArray OutputNames; + + UPROPERTY() + TArray TileValues; + + UPROPERTY() + TArray MaterialAttributes; + + void BuildFlatInstancedTransformsAndObjectPaths(); + + void BuildOriginalInstancedTransformsAndObjectArrays(); +}; + +struct HOUDINIENGINE_API FHoudiniInstanceTranslator +{ + public: + + static bool PopulateInstancedOutputPartData( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); + + static bool CreateAllInstancersFromHoudiniOutput( + UHoudiniOutput* InOutput, + const TArray& InAllOutputs, + UObject* InOuterComponent, + const TMap* InPreBuiltInstancedOutputPartData=nullptr); + + static bool GetInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValues, + TMap& OutPerSplitAttributes); + + static bool GetPackedPrimitiveInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes); + + static bool GetAttributeInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes); + + static bool GetOldSchoolAttributeInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms); + + static bool GetObjectInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms); + + // Updates the variations array using the instanced outputs + static void UpdateInstanceVariationObjects( + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TArray& InOriginalObjects, + const TArray>& InOriginalTransforms, + TMap& InstancedOutputs, + TArray>& OutVariationsInstancedObjects, + TArray>& OutVariationsInstancedTransforms, + TArray& OutVariationOriginalObjectIdx, + TArray& OutVariationIndices); + + // Recreates the components after an instanced outputs has been changed + static bool UpdateChangedInstancedOutput( + FHoudiniInstancedOutput& InInstancedOutput, + const FHoudiniOutputObjectIdentifier& OutputIdentifier, + UHoudiniOutput* InParentOutput, + USceneComponent* InParentComponent); + + // Recomputes the variation assignements for a given instanced output + static void UpdateVariationAssignements( + FHoudiniInstancedOutput& InstancedOutput); + + // Extracts the final transforms (with the transform offset applied) for a given variation + static void ProcessInstanceTransforms( + FHoudiniInstancedOutput& InstancedOutput, + const int32& VariationIdx, + TArray& OutProcessedTransforms); + + // Creates a new component or updates the previous one if possible + static bool CreateOrUpdateInstanceComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent* OldComponent, + USceneComponent*& NewComponent, + const bool& InIsSplitMeshInstancer, + const bool& InIsFoliageInstancer, + const TArray& InstancerMaterials, + const int32& InstancerObjectIdx = 0, + const bool& bForceHISM = false); + + // Create or update an ISMC / HISMC + static bool CreateOrUpdateInstancedStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr, + const bool& bForceHISM = false); + + // Create or update an IAC + static bool CreateOrUpdateInstancedActorComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent); + + // Create or update a MeshSplitInstancer + static bool CreateOrUpdateMeshSplitInstancerComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + const TArray& InstancerMaterials); + + // Create or update a StaticMeshComponent (when we have only one instance) + static bool CreateOrUpdateStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr); + + // Create or update a HoudiniStaticMeshComponent (when we have only one instance) + static bool CreateOrUpdateHoudiniStaticMeshComponent( + UHoudiniStaticMesh* InstancedProxyStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr); + + // Create or update a Foliage instances + static bool CreateOrUpdateFoliageInstances( + UStaticMesh* InstancedStaticMesh, + UFoliageType* InFoliageType, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/); + + // Helper fumction to properly remove/destroy a component + static bool RemoveAndDestroyComponent( + UObject* InComponent); + + // Utility function + // Fetches instance transforms and convert them to ue4 coordinates + static bool HapiGetInstanceTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancerUnrealTransforms); + + // Helper function used to spawn a new Actor for UHoudiniInstancedActorComponent + // Relies on editor-only functionalities, so this function is not on the IAC itself + static AActor* SpawnInstanceActor( + const FTransform& InTransform, + ULevel* InSpawnLevel, + UHoudiniInstancedActorComponent* InIAC); + + // Helper functions for generic property attributes + static bool GetGenericPropertiesAttributes( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, + const TArray& InAllPropertyAttributes, + const int32& AtIndex); + + static bool GetMaterialOverridesFromAttributes( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutMaterialAttributes); + + static bool GetInstancerMaterials( + const TArray& MaterialAttributes, + TArray& OutInstancerMaterials); + + static bool GetInstancerMaterials( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutInstancerMaterials); + + static bool GetVariationMaterials( + FHoudiniInstancedOutput* InInstancedOutput, + const int32& InVariationIndex, + const TArray& InInstancerMaterials, + TArray& OutVariationMaterials); + + static bool IsSplitInstancer( + const int32& InGeoId, + const int32& InPartId); + + static bool IsFoliageInstancer( + const int32& InGeoId, + const int32& InPartId); + + static void CleanupFoliageInstances( + UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, + USceneComponent* InParentComponent); + + static FString GetInstancerTypeFromComponent( + UObject* InComponent); + + // Returns the name and values of the attribute that has been specified to split the instances + // returns false if the attribute is invalid or hasn't been specified + static bool GetInstancerSplitAttributesAndValues( + const int32& InGeoId, + const int32& InPartId, + const HAPI_AttributeOwner& InSplitAttributeOwner, + FString& OutSplitAttributeName, + TArray& OutAllSplitAttributeValues); + + // Get if force using HISM from attribute + static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp index 88113c562..3333e2c0f 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp @@ -1,3672 +1,3672 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#include "HoudiniLandscapeTranslator.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniEngineString.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPackageParams.h" -#include "HoudiniStringResolver.h" -#include "HoudiniInput.h" - -#include "ObjectTools.h" -#include "FileHelpers.h" -#include "Editor.h" -#include "LandscapeLayerInfoObject.h" -#include "LandscapeStreamingProxy.h" -#include "LandscapeInfo.h" -#include "LandscapeEdit.h" -#include "AssetRegistryModule.h" -#include "PackageTools.h" -#include "PhysicalMaterials/PhysicalMaterial.h" -#include "UObject/UnrealType.h" - -#include "GameFramework/WorldSettings.h" -#include "HAL/PlatformFilemanager.h" -#include "Misc/Paths.h" -#include "Engine/LevelStreamingDynamic.h" -#include "Modules/ModuleManager.h" -#include "AssetToolsModule.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "LevelUtils.h" -#include "Factories/WorldFactory.h" -#include "Misc/Guid.h" -#include "Engine/LevelBounds.h" - -#include "HAL/IConsoleManager.h" -#include "Engine/AssetManager.h" -#include "Engine/LevelStreamingAlwaysLoaded.h" -#include "LandscapeEditor/Private/LandscapeEdMode.h" -#include "Misc/AssetRegistryInterface.h" -#include "Misc/StringFormatArg.h" -#include "Engine/WorldComposition.h" - -#if WITH_EDITOR - #include "LandscapeEditorModule.h" - #include "LandscapeFileFormatInterface.h" - #include "EditorLevelUtils.h" - #include "WorldBrowserModule.h" - #include "EditorLevelUtils.h" - #include "Misc/WorldCompositionUtility.h" -#endif - -static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( - TEXT("HoudiniEngine.ExportLandscapeTextures"), - 0, - TEXT("If enabled, landscape layers and heightmap will be exported as textures in the temp directory when converting a Heightfield to a Landscape.\n") - TEXT("0: Disabled\n") - TEXT("1: Enabled\n") -); - -typedef FHoudiniEngineUtils FHUtils; - -bool -FHoudiniLandscapeTranslator::CreateLandscape( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedOutputs, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* InWorld, // Persistent / root world for the landscape - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniPackageParams InPackageParams, - TArray& OutCreatedPackages -) -{ - check(LayerMinimums.Contains(TEXT("height"))); - check(LayerMaximums.Contains(TEXT("height"))); - - float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); - float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); - - if (!InOutput || InOutput->IsPendingKill()) - return false; - - // Get the height map. - const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); - if (!Heightfield) - return false; - - if (Heightfield->Type != EHoudiniPartType::Volume) - return false; - - const HAPI_NodeId GeoId = Heightfield->GeoId; - const HAPI_PartId PartId = Heightfield->PartId; - - // Construct the identifier of the Heightfield geo part. - FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); - HeightfieldIdentifier.PartName = Heightfield->PartName; - - FString NodeNameSuffix = GetActorNameSuffix(InPackageParams.PackageMode); - - TArray IntData; - TArray StrData; - // Output attributes will be stored on the Output object and will be used again during baking to determine - // where content should be baked to and what they should be named, etc. - // At the end of this function, the output attributes and tokens will be copied to the output object. - TMap OutputAttributes; - TMap OutputTokens; - FHoudiniAttributeResolver Resolver; - InPackageParams.UpdateTokensFromParams(InWorld, OutputTokens); - - bool bHasTile = Heightfield->VolumeTileIndex >= 0; - - // --------------------------------------------- - // Attribute: unreal_landscape_tile_actor_type, unreal_landscape_streaming_proxy (v1) - // --------------------------------------------- - // Determine the actor type for the tile - bool bCreateLandscapeStreamingProxy = false; - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - LandscapeActorType TileActorType = LandscapeActorType::LandscapeActor; - IntData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, AttributeInfo, IntData, 1)) - { - if (IntData.Num() > 0) - { - TileActorType = static_cast(IntData[0]); - } - } - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY, AttributeInfo, IntData, 1)) - { - if (IntData.Num() > 0 && IntData[0] != 0) - TileActorType = LandscapeActorType::LandscapeStreamingProxy; - } - - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, FString::FromInt(static_cast(TileActorType))); - - // --------------------------------------------- - // Attribute: unreal_landscape_actor_name - // --------------------------------------------- - // Retrieve the name of the main Landscape actor to look for - FString SharedLandscapeActorName = DefaultLandscapeActorPrefix + "SharedLandscape"; // If this is an empty string, don't affirm a root landscape actor? - StrData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, AttributeInfo, StrData, 1)) - { - if (StrData.Num() > 0 && !StrData[0].IsEmpty()) - SharedLandscapeActorName = StrData[0]; - } - - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); - - // --------------------------------------------- - // Attribute: unreal_level_path - // --------------------------------------------- - // FString LevelPath = bHasTile ? "{world}/Landscape/Tile{tile}" : "{world}/Landscape"; - FString LevelPath; - TArray LevelPaths; - if (FHoudiniEngineUtils::GetLevelPathAttribute(GeoId, PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - LevelPath = LevelPaths[0]; - } - if (!LevelPath.IsEmpty()) - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); - - // --------------------------------------------- - // Attribute: unreal_output_name - // --------------------------------------------- - FString LandscapeTileActorName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; - TArray AllOutputNames; - if (!FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames)) - { - if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) - LandscapeTileActorName = AllOutputNames[0]; - } - OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); - - // Streaming proxy actors/tiles requires a "main" landscape actor - // that contains the shared landscape state. - bool bRequiresSharedLandscape = false; - if (TileActorType == LandscapeActorType::LandscapeStreamingProxy) - bRequiresSharedLandscape = true; - - // ---------------------------------- - // Inject landscape specific tokens - // ---------------------------------- - if (bHasTile) - { - const FString TileValue = FString::FromInt(Heightfield->VolumeTileIndex); - // Tile value needs to go into Output arguments to be available during the bake. - OutputTokens.Add(TEXT("tile"), TileValue); - } - - // ---------------------------------- - // Expand string arguments for various landscape naming aspects. - // ---------------------------------- - - // Update resolver attributes and tokens before we start resolving attributes. - Resolver.SetCachedAttributes(OutputAttributes); - Resolver.SetTokensFromStringMap(OutputTokens); - - SharedLandscapeActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); - SharedLandscapeActorName += NodeNameSuffix; - - LandscapeTileActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, LandscapeTileActorName); - LandscapeTileActorName += NodeNameSuffix; - - LevelPath = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); - - FString TileName = LandscapeTileActorName; - - // Note that relative level paths are always interpreted as relative to the default output directory (temp / bake). - // FString TilePackagePath = FPaths::Combine(DefaultOutputPath, LevelPath); - FString TilePackagePath = Resolver.ResolveFullLevelPath(); - - // This crashes UE if the package name does not resolve - //FString TileMapFileName = FPackageName::LongPackageNameToFilename(TilePackagePath, FPackageName::GetMapPackageExtension()); - - FText NotValidReason; - bool bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); - if (!bIsValidLongName) - { - // Try a more naive approach - TilePackagePath = FPaths::Combine(InPackageParams.BakeFolder, LevelPath); - bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); - } - - if (!bIsValidLongName) - { - HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] TilePackagePath is not a valid long name. Reason: %s"), *(NotValidReason.ToString())); - return false; - } - - FString TileMapFileName; - if (!FPackageName::TryConvertLongPackageNameToFilename(TilePackagePath, TileMapFileName, FPackageName::GetMapPackageExtension())) - { - // Rather stop here than crash! - HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] Failed to resolve the TilePackagePath: %s"), *(TilePackagePath)); - return false; - } - - // Find the package for both the world and the tile. - // The world should contain the main landscape actor while - // the tile will contain a Landscape, LandscapeProxy or LandscapeStreamingProxy depending on user settings. - - bool bTileisStreamingProxy = (TileActorType == LandscapeActorType::LandscapeStreamingProxy); - UWorld* TileWorld = nullptr; // World from which to spawn tile actor - ULevel* TileLevel = nullptr; // Level in which to spawn tile actor - ALandscapeProxy* TileActor = nullptr; // Spawned tile actor. - - // ---------------------------------- - // Update package parameters for this tile - // ---------------------------------- - - // NOTE: we don't manually inject a tile number in the object name. This should - // already be encoded in the TileName string. - FHoudiniPackageParams TilePackageParams = InPackageParams; - TilePackageParams.ObjectName = TileName; - - FHoudiniPackageParams LayerPackageParams = InPackageParams; - if (bRequiresSharedLandscape) - { - // Note that layers are shared amongst all the tiles for a given landscape. - LayerPackageParams.ObjectName = SharedLandscapeActorName; - } - else - { - // This landscape tile is a standalone landscape and should have its own material layers. - LayerPackageParams.ObjectName = TileName; - } - - // See if the current heightfield has an unreal_material or unreal_hole_material assigned to it - UMaterialInterface* LandscapeMaterial = nullptr; - UMaterialInterface* LandscapeHoleMaterial = nullptr; - UPhysicalMaterial* LandscapePhysicalMaterial = nullptr; - FHoudiniLandscapeTranslator::GetLandscapeMaterials(*Heightfield, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial); - - // Extract the float data from the Heightfield. - const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; - TArray FloatValues; - float FloatMin, FloatMax; - if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) - return false; - - // Heightfield conversions should always use the global float min/max - // since they need to be calculated externally, potentially across multiple tiles. - FloatMin = fGlobalMin; - FloatMax = fGlobalMax; - - // Get the Unreal landscape size - int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; - int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; - int32 UnrealTileSizeX = -1; - int32 UnrealTileSizeY = -1; - int32 NumSectionPerLandscapeComponent = -1; - int32 NumQuadsPerLandscapeSection = -1; - - if (!FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - HoudiniHeightfieldXSize, HoudiniHeightfieldYSize, - UnrealTileSizeX, UnrealTileSizeY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection)) - { - return false; - } - - // ---------------------------------------------------- - // Export of layer textures - // ---------------------------------------------------- - // Export textures, if enabled. Mostly used for debugging at the moment. - bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; - if (bExportTexture) - { - // Export raw height data to texture - FString TextureName = TilePackageParams.ObjectName + TEXT("_height_raw"); - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - HoudiniHeightfieldXSize, - HoudiniHeightfieldYSize, - FloatValues, - FloatMin, - FloatMax); - } - - // Look for all the layers/masks corresponding to the current heightfield. - TArray< const FHoudiniGeoPartObject* > FoundLayers; - FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); - - // Get the updated layers. - TArray LayerInfos; - - if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, - LayerMinimums, LayerMaximums, LayerInfos, false, - TilePackageParams, - LayerPackageParams, - OutCreatedPackages)) - return false; - - // Convert Houdini's heightfield data to Unreal's landscape data - TArray IntHeightData; - FTransform TileTransform; - if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( - FloatValues, VolumeInfo, - UnrealTileSizeX, UnrealTileSizeY, - FloatMin, FloatMax, - IntHeightData, TileTransform)) - return false; - - // ---------------------------------------------------- - // Property changes that we want to track - // ---------------------------------------------------- - - bool bModifiedLandscapeActor = false; - bool bModifiedSharedLandscapeActor = false; - bool bSharedLandscapeMaterialChanged = false; - bool bSharedLandscapeHoleMaterialChanged = false; - bool bSharedPhysicalMaterialChanged = false; - bool bTileLandscapeMaterialChanged = false; - bool bTileLandscapeHoleMaterialChanged = false; - bool bTilePhysicalMaterialChanged = false; - bool bCreatedMap = false; - bool bCreatedTileActor = false; - bool bHeightLayerDataChanged = false; - bool bCustomLayerDataChanged = false; - - // ---------------------------------------------------- - // Calculate Tile location and landscape offset - // ---------------------------------------------------- - FTransform LandscapeTransform, NewTileTransform; - FIntPoint TileLoc; - - // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate - // for any landscape shifts due to section base aligment offsets. - CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeTransform, NewTileTransform, TileLoc); - - // ---------------------------------------------------- - // Find or create *shared* landscape - // ---------------------------------------------------- - - ALandscape* SharedLandscapeActor = nullptr; - bool bCreatedSharedLandscape = false; - - if (bRequiresSharedLandscape) - { - // Streaming proxy tiles always require a "shared landscape" that contains the - // various landscape properties to be shared amongst all the tiles. - AActor* FoundActor = nullptr; - SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, SharedLandscapeActorName, FoundActor); - - bool bIsValidSharedLandscape = IsValid(SharedLandscapeActor); - - if (bIsValidSharedLandscape) - { - // We have a possible valid shared landscape. Check whether it is compatible with the Houdini volume. - bool bIsCompatible = IsLandscapeInfoCompatible( - SharedLandscapeActor->GetLandscapeInfo(), - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection); - bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); - if (!bIsCompatible) - { - // Current landscape actor is not compatible. Destroy it. - SharedLandscapeActor->Destroy(); - SharedLandscapeActor = nullptr; - bIsValidSharedLandscape = false; - } - } - - if (!bIsValidSharedLandscape) - { - // Create and configure the main landscape actor. - // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos - SharedLandscapeActor = InWorld->SpawnActor(); - if (SharedLandscapeActor) - { - CreatedUntrackedOutputs.Add( SharedLandscapeActor ); - - // NOTE that share landscape is always located at the origin, but not the tile actors. The - // tiles are properly transformed. - SharedLandscapeActor->SetActorTransform(LandscapeTransform); - // If we working with landscape tiles, this actor will become the "Main landscape" actor but - // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. - SharedLandscapeActor->bCanHaveLayersContent = false; - SharedLandscapeActor->ComponentSizeQuads = NumQuadsPerLandscapeSection*NumSectionPerLandscapeComponent; - SharedLandscapeActor->NumSubsections = NumSectionPerLandscapeComponent; - SharedLandscapeActor->SubsectionSizeQuads = NumQuadsPerLandscapeSection; - SharedLandscapeActor->SetLandscapeGuid( FGuid::NewGuid() ); - SharedLandscapeActor->bCastStaticShadow = false; - for (const auto& ImportLayerInfo : LayerInfos) - { - SharedLandscapeActor->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(ImportLayerInfo.LayerInfo)); - } - SharedLandscapeActor->CreateLandscapeInfo(); - bCreatedSharedLandscape = true; - - // Ensure the landscape actor name and label matches `LandscapeActorName`. - FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, SharedLandscapeActorName); - SharedLandscapeActor->MarkPackageDirty(); - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(InWorld->GetPathName()) ); - return false; - } - } - else - { - // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Reusing existing shared landscape...")); - } - } - - if (SharedLandscapeActor) - { - // Ensure the existing landscape actor transform is correct. - SharedLandscapeActor->SetActorTransform(LandscapeTransform); - - bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; - if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) - { - DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - } - - if (bSharedLandscapeMaterialChanged) - { - SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; - - } - if (bSharedLandscapeHoleMaterialChanged) - { - SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - - } - - bSharedPhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? (SharedLandscapeActor->DefaultPhysMaterial != LandscapePhysicalMaterial) : false; - if (bSharedPhysicalMaterialChanged) - { - DoPreEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); - SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - //SharedLandscapeActor->ChangedPhysMaterial(); - } - } - - // ---------------------------------------------------- - // Find Landscape actor / tile - // ---------------------------------------------------- - - // Find an actor with the given name. The TileWorld and TileLevel returned should be - // used to spawn the new actor, if the actor itself could not be found. - //bool bCreatedPackage = false; - // TileActor = FindExistingLandscapeActor( - // InWorld, InOutput, ValidLandscapes, - // UnrealLandscapeSizeX, UnrealLandscapeSizeY, LandscapeTileActorName, - // LevelPath, TileWorld, TileLevel, bCreatedPackage); - - // Currently the Temp Cook mode is not concerned with creating packages. This will, at the time of writing, - // exclusively be dealt with during Bake mode so don't bother with searching / creating other packages. - - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (IsValid(HAC)) - { - TileWorld = HAC->GetWorld(); - TileLevel = HAC->GetComponentLevel(); - } - else - { - TileWorld = InWorld; - TileLevel = InWorld->PersistentLevel; - } - - check(TileWorld); - check(TileLevel); - - AActor* FoundActor = nullptr; - if (InPackageParams.PackageMode == EPackageMode::Bake) - { - // When baking, See if we can find any landscape / proxy actors for this tile in the TileLevel. - // If we find any actors that match the name but not the type, or the actors are pending kill, then - // rename them so that we can spawn new actors. - switch (TileActorType) - { - case LandscapeActorType::LandscapeActor: - TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); - break; - case LandscapeActorType::LandscapeStreamingProxy: - TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); - break; - default: - TileActor = nullptr; - } - } - else - { - // In temp mode, only consider our previous output landscapes, - // or our input landscapes that have the "update input landscape" option enabled - ALandscapeProxy* FoundLandscapeProxy = nullptr; - - // Try to see if we have an input landscape that matches the size of the current HGPO - for (int nIdx = 0; nIdx < InputLandscapesToUpdate.Num(); nIdx++) - { - ALandscapeProxy* CurrentInputLandscape = InputLandscapesToUpdate[nIdx]; - if (!CurrentInputLandscape) - continue; - - ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); - if (!CurrentInfo) - continue; - - int32 InputMinX = 0; - int32 InputMinY = 0; - int32 InputMaxX = 0; - int32 InputMaxY = 0; - CurrentInfo->GetLandscapeExtent(InputMinX, InputMinY, InputMaxX, InputMaxY); - - // If the full size matches, we'll update that input landscape - bool SizeMatch = false; - if ((InputMaxX - InputMinX + 1) == UnrealTileSizeX && (InputMaxY - InputMinY + 1) == UnrealTileSizeY) - SizeMatch = true; - - // HF and landscape don't match, try another one - if (!SizeMatch) - continue; - - // Replace FoundLandscape by that input landscape - FoundLandscapeProxy = CurrentInputLandscape; - - // We've found a valid input landscape, remove it from the input array so we don't try to update it multiple times - InputLandscapesToUpdate.RemoveAt(nIdx); - break; - } - - if (!FoundLandscapeProxy) - { - // Try to see if we can reuse one of our previous output landscape. - // Keep track of the previous cook's landscapes - TMap& OldOutputObjects = InOutput->GetOutputObjects(); - for (auto& CurrentLandscape : OldOutputObjects) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentLandscape.Value.OutputObject); - if (!LandscapePtr) - continue; - - FoundLandscapeProxy = LandscapePtr->GetRawPtr(); - if (!FoundLandscapeProxy) - { - // We may need to manually load the object - //OldLandscapeProxy = LandscapePtr->GetSoftPtr().LoadSynchronous(); - FoundLandscapeProxy = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); - } - - if (!IsValid(FoundLandscapeProxy)) - continue; - - // We need to make sure that this landscape is not one of our input landscape - // This would happen if we were previously updating it, but just turned the option off - // In that case, the landscape would be in both our inputs and outputs, - // but with the "Update Input Data" option off - if (InAllInputLandscapes.Contains(FoundLandscapeProxy)) - { - FoundLandscapeProxy = nullptr; - continue; - } - - // If we found a possible candidate, make sure that its size matches ours as we can only update a landscape tile of the same size - if (!IsLandscapeTileCompatible( - FoundLandscapeProxy, - UnrealTileSizeX-1, - UnrealTileSizeY-1, - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection)) - { - FoundLandscapeProxy = nullptr; - continue; - } - - if (SharedLandscapeActor) - { - if (FoundLandscapeProxy->GetLandscapeGuid() != SharedLandscapeActor->GetLandscapeGuid()) - { - FoundLandscapeProxy = nullptr; - continue; - } - } - - // TODO: we probably need to do some more checks with tiled landscapes as well? - - // We found a valid Candidate! - if (FoundLandscapeProxy) - break; - } - } - - if (IsValid(FoundLandscapeProxy)) - { - TileActor = FoundLandscapeProxy; - if (TileActor->GetName() != LandscapeTileActorName) - { - // Ensure the TileActor is named correctly - FHoudiniEngineUtils::SafeRenameActor(TileActor, LandscapeTileActorName); - } - } - } - - // NOTE: We don't need to delete old landscape tiles (FoundActor != TileActor) here. That is an old - // output that should get cleaned up automatically. - - if (IsValid(TileActor)) - { - check(!(TileActor->IsPendingKill())); - - // ---------------------------------------------------- - // Check landscape compatibility - // ---------------------------------------------------- - - bool bIsCompatible = IsLandscapeTileCompatible( - TileActor, - UnrealTileSizeX-1, - UnrealTileSizeY-1, - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection); - - bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(TileActor, TileActorType); - - if (!bIsCompatible) - { - // Can't reuse this tile actor since the landscape dimensions doesn't match or the actor type has changed. - if (TileActor->IsA()) - { - // This landscape tile needs to be unregistered from the landscape info. - ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); - if (IsValid(LandscapeInfo)) - { - LandscapeInfo->UnregisterActor(TileActor); - } - } - TileActor->Destroy(); - TileActor = nullptr; - } - } - - // ---------------------------------------------------- - // Create or update landscape / tile. - // ---------------------------------------------------- - // Note that a single heightfield generated in Houdini can be treated - // as either a landscape tile (LandscapeStreamingProxy) or a standalone - // landscape (ALandscape). This determination is made purely from user specified - // attributes. No "clever logic" in here, please! - - ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; - ALandscape* CachedLandscapeActor = nullptr; - ULandscapeInfo *LandscapeInfo; - - - if (!TileActor) - { - // Create a new Landscape tile in the TileWorld - TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( - IntHeightData, LayerInfos, TileTransform, - UnrealTileSizeX, UnrealTileSizeY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, - LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, - LandscapeTileActorName, - TileActorType, - SharedLandscapeActor, - TileWorld, - TileLevel, - InPackageParams); - - if (!TileActor || !TileActor->IsValidLowLevel()) - return false; - - // Update the visibility mask / layer if we have any - for (auto CurrLayerInfo : LayerInfos) - { - if (CurrLayerInfo.LayerInfo && CurrLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - TileActor->VisibilityLayer = CurrLayerInfo.LayerInfo; - TileActor->VisibilityLayer->bNoWeightBlend = true; - TileActor->VisibilityLayer->AddToRoot(); - } - } - - LandscapeInfo = TileActor->GetLandscapeInfo(); - - bCreatedTileActor = true; - bTileLandscapeMaterialChanged = true; - bTileLandscapeHoleMaterialChanged = true; - bTilePhysicalMaterialChanged = true; - bHeightLayerDataChanged = true; - bCustomLayerDataChanged = true; - } - else - { - LandscapeInfo = TileActor->GetLandscapeInfo(); - - // Always update the transform, even if the HGPO transform hasn't changed, - // If we change the number of tiles, or switch from outputting single tile to multiple, - // then its fairly likely that the unreal transform has changed even if the - // Houdini Transform remained the same - if (!TileActor->GetTransform().Equals(TileTransform)) - { - // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Updating tile transform: %s"), *(TileTransform.ToString())); - TileActor->SetActorTransform(TileTransform); - TileActor->SetAbsoluteSectionBase(TileLoc); -#if WITH_EDITOR - GEngine->BroadcastOnActorMoved(TileActor); -#endif - LandscapeInfo->RecreateLandscapeInfo(InWorld,true); - } - - // Update existing landscape / tile - if (SharedLandscapeActor && TileActorType == LandscapeActorType::LandscapeStreamingProxy) - { - TileActor->FixupSharedData(SharedLandscapeActor); - - // This is a tile with a shared landscape. - // Check whether the LandscapeActor changed. If so, update the proxy's shared properties. - CachedStreamingProxyActor = Cast(TileActor); - if (SharedLandscapeActor) - { - if (CachedStreamingProxyActor) - bModifiedLandscapeActor = CachedStreamingProxyActor->LandscapeActor != SharedLandscapeActor; - else - bModifiedLandscapeActor = true; - - if (bModifiedLandscapeActor) - { - CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; - // We need to force a state update through PostEditChangeProperty here in order to initialize - // since we're about to perform additional data updates on this tile. - DoPostEditChangeProperty(CachedStreamingProxyActor, "LandscapeActor"); - } - } - else - { - CachedStreamingProxyActor->LandscapeActor = nullptr; - } - - } - else - { - // This is a standalone tile / landscape actor. - CachedLandscapeActor = Cast(TileActor); - } - - ULandscapeInfo* PreviousInfo = TileActor->GetLandscapeInfo(); - if (!PreviousInfo) - return false; - - FIntRect BoundingRect = TileActor->GetBoundingRect(); - FIntPoint SectionOffset = TileActor->GetSectionBaseOffset(); - - // Landscape region to update - const int32 MinX = TileLoc.X; - const int32 MaxX = TileLoc.X + UnrealTileSizeX - 1; - const int32 MinY = TileLoc.Y; - const int32 MaxY = TileLoc.Y + UnrealTileSizeY - 1; - - // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. - // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools - // though the *Accessors do additional things like update normals and foliage. - - // Update height if it has been changed. - if (Heightfield->bHasGeoChanged) - { - // It is important to update the heightmap through the this since it will properly - // update normals and foliage. - FHeightmapAccessor HeightmapAccessor(LandscapeInfo); - HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); - - bHeightLayerDataChanged = true; - } - - // Update the layers on the landscape. - for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) - { - FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); - AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - - if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - TileActor->VisibilityLayer = NextUpdatedLayerInfo.LayerInfo; - TileActor->VisibilityLayer->bNoWeightBlend = true; - TileActor->VisibilityLayer->AddToRoot(); - } - - bCustomLayerDataChanged = true; - } - - bModifiedLandscapeActor = true; - } - - // ---------------------------------------------------- - // Update tile materials - // ---------------------------------------------------- - // TODO: These material updates can possibly be skipped if we have already performed this - // check on a SharedLandscape. - bTileLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (TileActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bTileLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (TileActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); - - if (bTileLandscapeMaterialChanged) - TileActor->LandscapeMaterial = LandscapeMaterial; - - if (bTileLandscapeHoleMaterialChanged) - TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - - bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; - if (bTilePhysicalMaterialChanged) - { - DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); - TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - //TileActor->ChangedPhysMaterial(); - } - - // ---------------------------------------------------- - // Apply actor tags - // ---------------------------------------------------- - - // See if we have unreal_tag_ attribute - TArray Tags; - if (TileActor && FHoudiniEngineUtils::GetUnrealTagAttributes(GeoId, PartId, Tags)) - { - TileActor->Tags = Tags; - } - - // ---------------------------------------------------- - // Update actor states based on data updates - // ---------------------------------------------------- - // Based on ALandscape and ALandscapeStreamingProxy PostEditChangeProperty() implementations, - // effect appropriate state updates based on the property updates that was performed in - // the above code. - - if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) - { - check(SharedLandscapeActor); - DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - } - - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - { - check(TileActor); - // Tile material changes are only processed if it wasn't already done for a shared - // landscape since the shared landscape should have already propagated the changes to associated proxies. - DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); - } - - if (bSharedPhysicalMaterialChanged) - { - check(SharedLandscapeActor); - DoPostEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); - } - - if (bTilePhysicalMaterialChanged) - { - check(TileActor); - DoPostEditChangeProperty(TileActor, "DefaultPhysMaterial"); - } - - if (bModifiedSharedLandscapeActor) - { - SharedLandscapeActor->PostEditChange(); - } - - if (bModifiedLandscapeActor) - { - TileActor->PostEditChange(); - } - - { - FLandscapeEditDataInterface LandscapeEdit(TileActor->GetLandscapeInfo()); - LandscapeEdit.RecalculateNormals(); - } - - if (LandscapeInfo) - { - LandscapeInfo->RecreateLandscapeInfo(InWorld, true); - } - - // Add objects to the HAC output. - SetLandscapeActorAsOutput( - InOutput, - InAllInputLandscapes, - OutputAttributes, - OutputTokens, - SharedLandscapeActor, - SharedLandscapeActorParent, - bCreatedSharedLandscape, - HeightfieldIdentifier, - TileActor, - InPackageParams.PackageMode); - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( - const ULandscapeInfo* LandscapeInfo, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection - ) -{ - if (!IsValid(LandscapeInfo)) - return false; - - - if (LandscapeInfo->ComponentNumSubsections != InNumSectionsPerComponent) - return false; - - if (LandscapeInfo->SubsectionSizeQuads != InNumQuadsPerSection) - return false; - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsLandscapeTileCompatible( - const ALandscapeProxy* TileActor, - const int32 InTileSizeX, - const int32 InTileSizeY, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection -) -{ - check(TileActor); - - // NOTE: We can't compare landscape extents here since the Houdini only knows about the size for single tile. - // and LandscapeInfo will only return extents for the *loaded* landscape tiles. - - // TODO: Add more robust checks to determine landscape compatibility. - - if (!IsLandscapeInfoCompatible(TileActor->GetLandscapeInfo(), InNumSectionsPerComponent, InNumQuadsPerSection)) - return false; - - const FIntRect Bounds = TileActor->GetBoundingRect(); - const FIntPoint Size = Bounds.Size(); - if (Size.X != InTileSizeX && Size.Y != InTileSizeY) - return false; - - return true; -} - - -bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, LandscapeActorType ActorType) -{ - if (!IsValid(Actor)) - return false; - - switch (ActorType) - { - case LandscapeActorType::LandscapeActor: - return Actor->IsA(); - break; - case LandscapeActorType::LandscapeStreamingProxy: - return Actor->IsA(); - break; - default: - break; - } - - return false; -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage, - const EPackageMode& InPackageMode) -{ - if (InPackageMode == EPackageMode::Bake) - return FindExistingLandscapeActor_Bake(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); - else - return FindExistingLandscapeActor_Temp(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - bCreatedPackage = false; - - // // Locate landscape proxy actor when running in baked mode - // AActor* FoundActor = nullptr; - ALandscapeProxy* OutActor = nullptr; - // OutActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); - // // OutActor = FHoudiniEngineUtils::FindActorInWorld(InWorld, FName(InActorName)); - // if (FoundActor && FoundActor != OutActor) - // FoundActor->Destroy(); // nuke it! - // - // if (OutActor) - // { - // // TODO: make sure that the found is actor is actually assigned to the level defined by package path. - // // If the found actor is not from that level, it should be moved there. - // - // OutWorld = OutActor->GetWorld(); - // OutLevel = OutActor->GetLevel(); - // } - // else - { - // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. - bool bActorInWorld = false; - const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - InWorld, - InPackagePath, - true, - OutWorld, - OutLevel, - bCreatedPackage, - bActorInWorld); - - if (!bResult) - { - return nullptr; - } - - // if (!bActorInWorld) - // { - // // The OutLevel is not present in the current world which means we might - // // still find the tile actor in OutWorld. - OutActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); - // } - } - - return OutActor; -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - ALandscapeProxy* OutActor = nullptr; - FString ActorName = InActorName + TEXT("_Temp"); - TMap& PrevCookObjects = InOutput->GetOutputObjects(); - - OutWorld = InWorld; - OutLevel = InWorld->PersistentLevel; - - bCreatedPackage = false; - - // Find Landscape proxy for output when running in Temp mode - for(auto& PrevObject : PrevCookObjects) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(PrevObject.Value.OutputObject); - if (!LandscapePtr) - continue; - - OutActor = LandscapePtr->GetRawPtr(); - if (!OutActor) - { - // We may need to manually load the object - OutActor = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); - } - - if (!OutActor) - continue; - - // If we were updating the input landscape before, but arent anymore, - // we could still find it here in the output, ignore them now as we're only looking for previous output - if (ValidLandscapes.Contains(OutActor)) - continue; - - if (OutActor->GetName() != ActorName) - // This is not the droid we're looking for - continue; - - if (OutActor->IsPendingKill()) - { - FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + "_pendingkill"); - continue; - } - - // If we found a possible candidate, make sure that its size matches ours - // as we can only update a landscape of the same size - ULandscapeInfo* PreviousInfo = OutActor->GetLandscapeInfo(); - if (PreviousInfo) - { - int32 PrevMinX = 0; - int32 PrevMinY = 0; - int32 PrevMaxX = 0; - int32 PrevMaxY = 0; - PreviousInfo->GetLandscapeExtent(PrevMinX, PrevMinY, PrevMaxX, PrevMaxY); - - if ((PrevMaxX - PrevMinX + 1) == UnrealLandscapeSizeX && (PrevMaxY - PrevMinY + 1) == UnrealLandscapeSizeY) - { - // The size matches, we can reuse the old landscape. - break; - } - else - { - // We can't reuse this actor. The dimensions does not match. - // We need to rename this actor in order to create a new one with the specified name. - FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + TEXT("_old") ); - OutActor = nullptr; - } - } - } - - return OutActor; -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor, - const EPackageMode InPackageMode) -{ - if (InPackageMode == EPackageMode::Bake) - return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); - else - return SetLandscapeActorAsOutput_Temp(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor) -{ - // We are in bake mode. No outputs to register / add here. - // Do nothing, for now. -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedSharedLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor) -{ - // The main landscape is a special case here. It cannot be registered with the - // output object here, since it is possibly shared by *multiple* outputs so - // we have to deal with the attached and cleanup of the actor manually. - if (bCreatedSharedLandscape && IsValid(SharedLandscapeActorParent)) - { - AttachActorToHAC(InOutput, SharedLandscapeActor); - } - - // TODO: The OutputObject cleanup being performed here should really be part of - // the output object itself (or at the very least be encapsulated in a reusable - // static function somewhere) so that individual output objects can be cleaned - // when necessary without having to duplicate code when its needed. - - // Cleanup any stale output objects - TMap& Outputs = InOutput->GetOutputObjects(); - TArray StaleOutputs; - for (auto& Elem : Outputs) - { - UHoudiniLandscapePtr* LandscapePtr = nullptr; - bool bIsStale = false; - - if (!(Elem.Key == Identifier)) - { - // Identifiers doesn't match so this is definitely a stale output. - StaleOutputs.Add(Elem.Key); - bIsStale = true; - } - - LandscapePtr = Cast(Elem.Value.OutputObject); - if (LandscapePtr) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - - if (LandscapeProxy) - { - // We shouldn't destroy any input landscape, - // or the landscape that we are currently trying to set as output.. - if (!InAllInputLandscapes.Contains(LandscapeProxy) && (LandscapeProxy != LandscapeActor)) - { - // This landscape proxy either doesn't match the landscape identifier - // or it doesn't match the actor we're about to output. Either way, - // get rid of it. - LandscapeProxy->Destroy(); - } - } - } - - if (bIsStale) - { - Elem.Value.OutputObject = nullptr; - } - } - - for (FHoudiniOutputObjectIdentifier& StaleOutput : StaleOutputs) - { - Outputs.Remove(StaleOutput); - } - - - // Send a landscape pointer back to the Output Object for this landscape tile. - FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); - UHoudiniLandscapePtr* LandscapePtr = NewObject(InOutput); - LandscapePtr->SetSoftPtr(LandscapeActor); - OutputObj.OutputObject = LandscapePtr; - OutputObj.CachedAttributes = OutputAttributes; - OutputObj.CachedTokens = OutputTokens; -} - - -bool -FHoudiniLandscapeTranslator::AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor) -{ - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (IsValid(HAC)) - { - InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); - return true; - } - return false; -} - -FString -FHoudiniLandscapeTranslator::GetActorNameSuffix(const EPackageMode& InPackageMode) -{ - if(InPackageMode == EPackageMode::CookToTemp) - return "_Temp"; - else - return FString(); -} - -void -FHoudiniLandscapeTranslator::DoPreEditChangeProperty(UObject* Obj, FName PropertyName) -{ - Obj->PreEditChange(FindFProperty(Obj->GetClass(), PropertyName)); -} - -void -FHoudiniLandscapeTranslator::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) -{ - FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); - Obj->PostEditChangeProperty(Evt); -} - -bool -FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( - const TArray< float >& HeightfieldFloatValues, - const FHoudiniVolumeInfo& HeightfieldVolumeInfo, - const int32& FinalXSize, const int32& FinalYSize, - float FloatMin, float FloatMax, - TArray< uint16 >& IntHeightData, - FTransform& LandscapeTransform, - const bool& NoResize) -{ - IntHeightData.Empty(); - LandscapeTransform.SetIdentity(); - - // HF sizes needs an X/Y swap - // NOPE.. not anymore - int32 HoudiniXSize = HeightfieldVolumeInfo.YLength; - int32 HoudiniYSize = HeightfieldVolumeInfo.XLength; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - // Test for potential special cases... - // Just print a warning for now - if (HeightfieldVolumeInfo.MinX != 0) - HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min X is not zero.")); - - if (HeightfieldVolumeInfo.MinY != 0) - HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min Y is not zero.")); - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to uint16 using doubles to get the maximum precision during the conversion - //-------------------------------------------------------------------------------------------------- - - FTransform CurrentVolumeTransform = HeightfieldVolumeInfo.Transform; - - // The ZRange in Houdini (in m) - double MeterZRange = (double)(FloatMax - FloatMin); - - // The corresponding unreal digit range (as unreal uses uint16, max is 65535) - // We may want to not use the full range in order to be able to sculpt the landscape past the min/max values after. - const double dUINT16_MAX = (double)UINT16_MAX; - double DigitZRange = 49152.0; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution) - DigitZRange = dUINT16_MAX - 1.0; - - // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down - double DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); - - // The factor used to convert from Houdini's ZRange to the desired digit range - double ZSpacing = (MeterZRange != 0.0) ? (DigitZRange / MeterZRange) : 0.0; - - // Changes these values if the user wants to loose a lot of precision - // just to keep the same transform as the landscape input - bool bUseDefaultUE4Scaling = false; - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) - bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; - - if (bUseDefaultUE4Scaling) - { - //Check that our values are compatible with UE4's default scale values - if (FloatMin < -256.0f || FloatMin > 256.0f || FloatMax < -256.0f || FloatMax > 256.0f) - { - // Warn the user that the landscape conversion will have issues - // invite him to change that setting - HOUDINI_LOG_WARNING( - TEXT("The heightfield's min and max height values are too large for being used with the \"Use Default UE4 scaling\" option.\n \ - The generated Heightfield will likely be incorrectly converted to landscape unless you disable that option in the project settings and recook the asset.")); - } - - DigitZRange = dUINT16_MAX - 1.0; - DigitCenterOffset = 0; - - // Default unreal landscape scaling is -256m:256m at Scale = 100 - // We need to apply the scale back to - FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; - FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; - MeterZRange = (double)(FloatMax - FloatMin); - - ZSpacing = ((double)DigitZRange) / MeterZRange; - } - - // Converting the data from Houdini to Unreal - // For correct orientation in unreal, the point matrix has to be transposed. - IntHeightData.SetNumUninitialized(SizeInPoints); - - int32 nUnreal = 0; - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y - int32 nHoudini = nY + nX * HoudiniYSize; - - // Get the double values in [0 - ZRange] - double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; - - // Then convert it to [0 - DesiredRange] and center it - DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; - IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); - } - } - - //-------------------------------------------------------------------------------------------------- - // 2. Resample / Pad the int data so that if fits unreal size requirements - //-------------------------------------------------------------------------------------------------- - - // UE has specific size requirements for landscape, - // so we might need to pad/resample the heightfield data - FVector LandscapeResizeFactor = FVector::OneVector; - FVector LandscapePositionOffsetInPixels = FVector::ZeroVector; - if (!NoResize) - { - // Try to resize the data - if (!FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( - IntHeightData, - HoudiniXSize, HoudiniYSize, FinalXSize, FinalYSize, - LandscapeResizeFactor, LandscapePositionOffsetInPixels)) - return false; - } - - //-------------------------------------------------------------------------------------------------- - // 3. Calculating the proper transform for the landscape to be sized and positionned properly - //-------------------------------------------------------------------------------------------------- - - // Scale: - // Calculating the equivalent scale to match Houdini's Terrain Size in Unreal - FVector LandscapeScale; - - // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing - LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; - LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Y * 2.0f; - - // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini - // Unreal has a default Z range is 512m for a scale of a 100% - LandscapeScale.Z = (float)((double)(dUINT16_MAX / DigitZRange) * MeterZRange / 512.0); - if (bUseDefaultUE4Scaling) - LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Z * 2.0f; - LandscapeScale *= 100.f; - - // If the data was resized and not expanded, we need to modify the landscape's scale - LandscapeScale *= LandscapeResizeFactor; - - // Don't allow a zero scale, as this results in divide by 0 operations in FMatrix::InverseFast in the landscape component. - if (FMath::IsNearlyZero(LandscapeScale.Z)) - LandscapeScale.Z = 1.0f; - - // We'll use the position from Houdini, but we will need to offset the Z Position to center the - // values properly as the data has been offset by the conversion to uint16 - FVector LandscapePosition = CurrentVolumeTransform.GetLocation(); - //LandscapePosition.Z = 0.0f; - - // We need to calculate the position offset so that Houdini and Unreal have the same Zero position - // In Unreal, zero has a height value of 32768. - // These values are then divided by 128 internally, and then multiplied by the Landscape's Z scale - // ( DIGIT - 32768 ) / 128 * ZScale = ZOffset - - // We need the Digit (Unreal) value of Houdini's zero for the scale calculation - // ( float and int32 are used for this because 0 might be out of the landscape Z range! - // when using the full range, this would cause an overflow for a uint16!! ) - float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); - float ZOffset = -(HoudiniZeroValueInDigit - 32768.0f) / 128.0f * LandscapeScale.Z; - - LandscapePosition.Z += ZOffset; - - // If we have padded the data when resizing the landscape, we need to offset the position because of - // the added values on the topLeft Corner of the Landscape - if (LandscapePositionOffsetInPixels != FVector::ZeroVector) - { - FVector LandscapeOffset = LandscapePositionOffsetInPixels * LandscapeScale; - LandscapeOffset.Z = 0.0f; - - LandscapePosition += LandscapeOffset; - } - - /* - FTransform TempTransform; - TempTransform.SetIdentity(); - { - // Houdini Pivot (center of the Landscape) - FVector HoudiniPivot = FVector((FinalXSize-1) * 100.0f / 2.0f, (FinalYSize-1) * 100.0f / 2.0f, 0.0f); - - // Center the landscape - FVector CenterLocation = LandscapePosition - HoudiniPivot; - - // Rotate the vector using the H rotation - // We need to compensate for the "default" HF Transform - FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); - Rotator.Yaw -= 90.0f; - Rotator.Roll += 90.0f; - FVector RotatedLocation = Rotator.RotateVector(CenterLocation); - - FQuat LandscapeRotation = FQuat(Rotator) * FQuat::Identity; - - // Return to previous origin - FVector Uncentered = RotatedLocation + HoudiniPivot; - TempTransform = FTransform(LandscapeRotation, Uncentered, LandscapeScale); - } - - LandscapeTransform = TempTransform; - */ - - // We can now set the Landscape position - LandscapeTransform.SetLocation(LandscapePosition); - LandscapeTransform.SetScale3D(LandscapeScale); - - // Rotate the vector using the H rotation - FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); - // We need to compensate for the "default" HF Transform - Rotator.Yaw -= 90.0f; - Rotator.Roll += 90.0f; - - // Only rotate if the rotator is far from zero - if(!Rotator.IsNearlyZero()) - LandscapeTransform.SetRotation(FQuat(Rotator)); - - return true; -} - -template -TArray ResampleData(const TArray& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight) -{ - TArray Result; - Result.Empty(NewWidth * NewHeight); - Result.AddUninitialized(NewWidth * NewHeight); - - const float XScale = (float)(OldWidth - 1) / (NewWidth - 1); - const float YScale = (float)(OldHeight - 1) / (NewHeight - 1); - for (int32 Y = 0; Y < NewHeight; ++Y) - { - for (int32 X = 0; X < NewWidth; ++X) - { - const float OldY = Y * YScale; - const float OldX = X * XScale; - const int32 X0 = FMath::FloorToInt(OldX); - const int32 X1 = FMath::Min(FMath::FloorToInt(OldX) + 1, OldWidth - 1); - const int32 Y0 = FMath::FloorToInt(OldY); - const int32 Y1 = FMath::Min(FMath::FloorToInt(OldY) + 1, OldHeight - 1); - const T& Original00 = Data[Y0 * OldWidth + X0]; - const T& Original10 = Data[Y0 * OldWidth + X1]; - const T& Original01 = Data[Y1 * OldWidth + X0]; - const T& Original11 = Data[Y1 * OldWidth + X1]; - Result[Y * NewWidth + X] = FMath::BiLerp(Original00, Original10, Original01, Original11, FMath::Fractional(OldX), FMath::Fractional(OldY)); - } - } - - return Result; -} - -template -void ExpandData(T* OutData, const T* InData, - int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, - int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY) -{ - const int32 OldWidth = OldMaxX - OldMinX + 1; - const int32 OldHeight = OldMaxY - OldMinY + 1; - const int32 NewWidth = NewMaxX - NewMinX + 1; - const int32 NewHeight = NewMaxY - NewMinY + 1; - const int32 OffsetX = NewMinX - OldMinX; - const int32 OffsetY = NewMinY - OldMinY; - - for (int32 Y = 0; Y < NewHeight; ++Y) - { - const int32 OldY = FMath::Clamp(Y + OffsetY, 0, OldHeight - 1); - - // Pad anything to the left - const T PadLeft = InData[OldY * OldWidth + 0]; - for (int32 X = 0; X < -OffsetX; ++X) - { - OutData[Y * NewWidth + X] = PadLeft; - } - - // Copy one row of the old data - { - const int32 X = FMath::Max(0, -OffsetX); - const int32 OldX = FMath::Clamp(X + OffsetX, 0, OldWidth - 1); - FMemory::Memcpy(&OutData[Y * NewWidth + X], &InData[OldY * OldWidth + OldX], FMath::Min(OldWidth, NewWidth) * sizeof(T)); - } - - const T PadRight = InData[OldY * OldWidth + OldWidth - 1]; - for (int32 X = -OffsetX + OldWidth; X < NewWidth; ++X) - { - OutData[Y * NewWidth + X] = PadRight; - } - } -} - -template -TArray ExpandData(const TArray& Data, - int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, - int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY, - int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr) -{ - const int32 NewWidth = NewMaxX - NewMinX + 1; - const int32 NewHeight = NewMaxY - NewMinY + 1; - - TArray Result; - Result.Empty(NewWidth * NewHeight); - Result.AddUninitialized(NewWidth * NewHeight); - - ExpandData(Result.GetData(), Data.GetData(), - OldMinX, OldMinY, OldMaxX, OldMaxY, - NewMinX, NewMinY, NewMaxX, NewMaxY); - - // Return the padding so we can offset the terrain position after - if (PadOffsetX) - *PadOffsetX = NewMinX; - - if (PadOffsetY) - *PadOffsetY = NewMinY; - - return Result; -} - -bool -FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( - TArray& HeightData, - const int32& SizeX, const int32& SizeY, - const int32& NewSizeX, const int32& NewSizeY, - FVector& LandscapeResizeFactor, - FVector& LandscapePositionOffset) -{ - LandscapeResizeFactor = FVector::OneVector; - LandscapePositionOffset = FVector::ZeroVector; - - if (HeightData.Num() <= 4) - return false; - - if ((SizeX < 2) || (SizeY < 2)) - return false; - - // No need to resize anything - if (SizeX == NewSizeX && SizeY == NewSizeY) - return true; - - // Always resample, for now. We may enable padding functionality again at some point via - // a plugin setting. - bool bForceResample = true; - bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); - - TArray NewData; - if (!bResample) - { - // Expanding the data by padding - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - - const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; - const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; - - // Store the offset in pixel due to the padding - int32 PadOffsetX = 0; - int32 PadOffsetY = 0; - - // Expanding the Data - NewData = ExpandData( - HeightData, 0, 0, SizeX - 1, SizeY - 1, - -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1, - &PadOffsetX, &PadOffsetY); - - // We will need to offset the landscape position due to the value added by the padding - LandscapePositionOffset.X = (float)PadOffsetX; - LandscapePositionOffset.Y = (float)PadOffsetY; - - // Notify the user that the data was padded - HOUDINI_LOG_WARNING( - TEXT("Landscape data was padded from ( %d x %d ) to ( %d x %d )."), - SizeX, SizeY, NewSizeX, NewSizeY); - } - else - { - // Resampling the data - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - NewData = ResampleData(HeightData, SizeX, SizeY, NewSizeX, NewSizeY); - - // The landscape has been resized, we'll need to take that into account when sizing it - LandscapeResizeFactor.X = (float)SizeX / (float)NewSizeX; - LandscapeResizeFactor.Y = (float)SizeY / (float)NewSizeY; - LandscapeResizeFactor.Z = 1.0f; - - // Notify the user if the heightfield data was resized - HOUDINI_LOG_WARNING( - TEXT("Landscape data was resized from ( %d x %d ) to ( %d x %d )."), - SizeX, SizeY, NewSizeX, NewSizeY); - } - - // Replaces Old data with the new one - HeightData = NewData; - - return true; -} - - -bool -FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - const int32& HoudiniSizeX, const int32& HoudiniSizeY, - int32& UnrealSizeX, int32& UnrealSizeY, - int32& NumSectionsPerComponent, int32& NumQuadsPerSection) -{ - if ((HoudiniSizeX < 2) || (HoudiniSizeY < 2)) - return false; - - NumSectionsPerComponent = 1; - NumQuadsPerSection = 1; - UnrealSizeX = -1; - UnrealSizeY = -1; - - // Unreal's default sizes - int32 SectionSizes[] = { 7, 15, 31, 63, 127, 255 }; - int32 NumSections[] = { 1, 2 }; - - // Component count used to calculate the final size of the landscape - int32 ComponentsCountX = 1; - int32 ComponentsCountY = 1; - - // Lambda for clamping the number of component in X/Y - auto ClampLandscapeSize = [&]() - { - // Max size is either whole components below 8192 verts, or 32 components - ComponentsCountX = FMath::Clamp(ComponentsCountX, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); - ComponentsCountY = FMath::Clamp(ComponentsCountY, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); - }; - - // Try to find a section size and number of sections that exactly matches the dimensions of the heightfield - bool bFoundMatch = false; - for (int32 SectionSizesIdx = UE_ARRAY_COUNT(SectionSizes) - 1; SectionSizesIdx >= 0; SectionSizesIdx--) - { - for (int32 NumSectionsIdx = UE_ARRAY_COUNT(NumSections) - 1; NumSectionsIdx >= 0; NumSectionsIdx--) - { - int32 ss = SectionSizes[SectionSizesIdx]; - int32 ns = NumSections[NumSectionsIdx]; - - if (((HoudiniSizeX - 1) % (ss * ns)) == 0 && ((HoudiniSizeX - 1) / (ss * ns)) <= 32 && - ((HoudiniSizeY - 1) % (ss * ns)) == 0 && ((HoudiniSizeY - 1) / (ss * ns)) <= 32) - { - bFoundMatch = true; - NumQuadsPerSection = ss; - NumSectionsPerComponent = ns; - ComponentsCountX = (HoudiniSizeX - 1) / (ss * ns); - ComponentsCountY = (HoudiniSizeY - 1) / (ss * ns); - ClampLandscapeSize(); - break; - } - } - if (bFoundMatch) - { - break; - } - } - - if (!bFoundMatch) - { - // if there was no exact match, try increasing the section size until we encompass the whole heightmap - const int32 CurrentSectionSize = NumQuadsPerSection; - const int32 CurrentNumSections = NumSectionsPerComponent; - for (int32 SectionSizesIdx = 0; SectionSizesIdx < UE_ARRAY_COUNT(SectionSizes); SectionSizesIdx++) - { - if (SectionSizes[SectionSizesIdx] < CurrentSectionSize) - { - continue; - } - - const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); - const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); - if (ComponentsX <= 32 && ComponentsY <= 32) - { - bFoundMatch = true; - NumQuadsPerSection = SectionSizes[SectionSizesIdx]; - ComponentsCountX = ComponentsX; - ComponentsCountY = ComponentsY; - ClampLandscapeSize(); - break; - } - } - } - - if (!bFoundMatch) - { - // if the heightmap is very large, fall back to using the largest values we support - const int32 MaxSectionSize = SectionSizes[UE_ARRAY_COUNT(SectionSizes) - 1]; - const int32 MaxNumSubSections = NumSections[UE_ARRAY_COUNT(NumSections) - 1]; - const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), MaxSectionSize * MaxNumSubSections); - const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), MaxSectionSize * MaxNumSubSections); - - bFoundMatch = true; - NumQuadsPerSection = MaxSectionSize; - NumSectionsPerComponent = MaxNumSubSections; - ComponentsCountX = ComponentsX; - ComponentsCountY = ComponentsY; - ClampLandscapeSize(); - } - - if (!bFoundMatch) - { - // Using default size just to not crash.. - UnrealSizeX = 512; - UnrealSizeY = 512; - NumSectionsPerComponent = 1; - NumQuadsPerSection = 511; - ComponentsCountX = 1; - ComponentsCountY = 1; - } - else - { - // Calculating the desired size - int32 QuadsPerComponent = NumSectionsPerComponent * NumQuadsPerSection; - - UnrealSizeX = ComponentsCountX * QuadsPerComponent + 1; - UnrealSizeY = ComponentsCountY * QuadsPerComponent + 1; - } - - return bFoundMatch; -} - -const FHoudiniGeoPartObject* -FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return nullptr; - - if (InOutput->GetHoudiniGeoPartObjects().Num() < 1) - return nullptr; - - for (const FHoudiniGeoPartObject& HGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Volume) - continue; - - FHoudiniVolumeInfo CurVolumeInfo = HGPO.VolumeInfo; - if (!CurVolumeInfo.Name.Contains("height")) - continue; - - // We're only handling single values for now - if (CurVolumeInfo.TupleSize != 1) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume has an invalide tuple size!")); - return nullptr; - } - - // Terrains always have a ZSize of 1. - if (CurVolumeInfo.ZLength != 1) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume's z length is not 1!")); - return nullptr; - } - - // Values should be float - if (!CurVolumeInfo.bIsFloat) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output, the height volume's data is not stored as floats!")); - return nullptr; - } - - return &HGPO; - } - - return nullptr; -} - -void -FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers) -{ - FoundLayers.Empty(); - - // Get node id - HAPI_NodeId HeightFieldNodeId = Heightfield.GeoId; - - // We need the tile attribute if the height has it - bool bParentHeightfieldHasTile = false; - int32 HeightFieldTile = -1; - { - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - TArray< int32 > TileValues; - - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HeightFieldNodeId, Heightfield.PartId, "tile", AttribInfoTile, TileValues); - - if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) - { - HeightFieldTile = TileValues[0]; - bParentHeightfieldHasTile = true; - } - } - - for (TArray< FHoudiniGeoPartObject >::TConstIterator IterLayers(InOutput->GetHoudiniGeoPartObjects()); IterLayers; ++IterLayers) - { - const FHoudiniGeoPartObject & HoudiniGeoPartObject = *IterLayers; - - HAPI_NodeId NodeId = HoudiniGeoPartObject.GeoId; - if (NodeId == -1 || NodeId != HeightFieldNodeId) - continue; - - if (bParentHeightfieldHasTile) - { - int32 CurrentTile = -1; - - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - TArray TileValues; - - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "tile", AttribInfoTile, TileValues); - - - if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) - { - CurrentTile = TileValues[0]; - } - - // Does this layer come from the same tile as the height? - if ((CurrentTile != HeightFieldTile) || (CurrentTile == -1)) - continue; - } - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, HoudiniGeoPartObject.PartId, - &CurrentVolumeInfo)) - continue; - - // We're interesting in anything but height data - FString CurrentVolumeName; - FHoudiniEngineString(CurrentVolumeInfo.nameSH).ToFString(CurrentVolumeName); - if (CurrentVolumeName.Contains("height")) - continue; - - // We're only handling single values for now - if (CurrentVolumeInfo.tupleSize != 1) - continue; - - // Terrains always have a ZSize of 1. - if (CurrentVolumeInfo.zLength != 1) - continue; - - // Values should be float - if (CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - continue; - - FoundLayers.Add(&HoudiniGeoPartObject); - } -} - -bool -FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) -{ - OutFloatArr.Empty(); - OutFloatMin = 0.f; - OutFloatMax = 0.f; - - if (HGPO->Type != EHoudiniPartType::Volume) - return false; - - HAPI_VolumeInfo VolumeInfo; - FHoudiniApi::VolumeInfo_Init(&VolumeInfo); - - HAPI_Result Result = FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - HGPO->GeoId, HGPO->PartId, &VolumeInfo); - - // We're only handling single values for now - if (VolumeInfo.tupleSize != 1) - return false; - - // Terrains always have a ZSize of 1. - if (VolumeInfo.zLength != 1) - return false; - - // Values must be float - if (VolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - return false; - - if ((VolumeInfo.xLength < 2) || (VolumeInfo.yLength < 2)) - return false; - - const int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; - - OutFloatArr.SetNum(SizeInPoints); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetHeightFieldData( - FHoudiniEngine::Get().GetSession(), - HGPO->GeoId, HGPO->PartId, - OutFloatArr.GetData(), - 0, SizeInPoints), false); - - OutFloatMin = OutFloatArr[0]; - OutFloatMax = OutFloatMin; - - for (float NextFloatVal : OutFloatArr) - { - if (NextFloatVal > OutFloatMax) - { - OutFloatMax = NextFloatVal; - } - else if (NextFloatVal < OutFloatMin) - OutFloatMin = NextFloatVal; - } - - return true; -} - -bool -FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(const FHoudiniGeoPartObject& InHGPO, TArray& NonWeightBlendedLayerNames) -{ - // Check the attribute exists on primitive or detail - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; - if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_PRIM)) - Owner = HAPI_ATTROWNER_PRIM; - else if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_DETAIL)) - Owner = HAPI_ATTROWNER_DETAIL; - else - return false; - - // Get the values - HAPI_AttributeInfo AttribInfoNonWBLayer; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNonWBLayer); - TArray AttribValues; - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, AttribInfoNonWBLayer, AttribValues, 1, Owner); - - if (AttribValues.Num() <= 0) - return false; - - // Convert them to FString - for (int32 Idx = 0; Idx < AttribValues.Num(); Idx++) - { - TArray Tokens; - AttribValues[Idx].ParseIntoArray(Tokens, TEXT(" "), true); - - for (int32 n = 0; n < Tokens.Num(); n++) - NonWeightBlendedLayerNames.AddUnique(Tokens[n]); - } - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) -{ - // Check the attribute exists on primitive or detail - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; - if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_PRIM)) - Owner = HAPI_ATTROWNER_PRIM; - else if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_DETAIL)) - Owner = HAPI_ATTROWNER_DETAIL; - else - return false; - - // Check the value - HAPI_AttributeInfo AttribInfoUnitLayer; - FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer); - TArray< int32 > AttribValues; - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, AttribInfoUnitLayer, AttribValues, 1, Owner); - - if (AttribValues.Num() > 0 && AttribValues[0] == 1) - return true; - - return false; -} - -bool -FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( - const TArray& FoundLayers, - const FHoudiniGeoPartObject& Heightfield, - const int32& LandscapeXSize, const int32& LandscapeYSize, - const TMap& GlobalMinimums, - const TMap& GlobalMaximums, - TArray& OutLayerInfos, - bool bIsUpdate, - const FHoudiniPackageParams& InTilePackageParams, - const FHoudiniPackageParams& InLayerPackageParams, - TArray& OutCreatedPackages - ) -{ - OutLayerInfos.Empty(); - - // Get the names of all non weight blended layers - TArray NonWeightBlendedLayerNames; - FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(Heightfield, NonWeightBlendedLayerNames); - - // Used for exporting layer info objects (per landscape layer) - FHoudiniPackageParams LayerPackageParams = InLayerPackageParams; - // Used for exporting textures (per landscape tile) - FHoudiniPackageParams TilePackageParams = InTilePackageParams; - - // For Debugging, do we want to export layers as textures? - bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; - - // Try to create all the layers - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - for (TArray::TConstIterator IterLayers(FoundLayers); IterLayers; ++IterLayers) - { - const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers; - if (!LayerGeoPartObject) - continue; - - if (!LayerGeoPartObject->IsValid()) - continue; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerGeoPartObject->AssetId)) - continue; - - if (bIsUpdate && !LayerGeoPartObject->bHasGeoChanged) - { - continue; - } - - TArray FloatLayerData; - float LayerMin = 0; - float LayerMax = 0; - if (!FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(LayerGeoPartObject, FloatLayerData, LayerMin, LayerMax)) - continue; - - // No need to create flat layers as Unreal will remove them afterwards.. - if (LayerMin == LayerMax) - continue; - - const FHoudiniVolumeInfo& LayerVolumeInfo = LayerGeoPartObject->VolumeInfo; - - // Get the layer's name - FString LayerName = LayerVolumeInfo.Name; - const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerName); - - TilePackageParams.ObjectName = InTilePackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; - LayerPackageParams.ObjectName = InLayerPackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; - - if (bExportTexture) - { - // Create a raw texture export of the layer on this tile - FString TextureName = TilePackageParams.ObjectName + "_raw"; - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - LayerVolumeInfo.YLength, // Y and X inverted?? why? - LayerVolumeInfo.XLength, - FloatLayerData, - LayerMin, - LayerMax); - } - - // Check if that landscape layer has been marked as unit (range in [0-1] - if (IsUnitLandscapeLayer(*LayerGeoPartObject)) - { - LayerMin = 0.0f; - LayerMax = 1.0f; - } - else - { - // We want to convert the layer using the global Min/Max - if (GlobalMaximums.Contains(LayerName)) - LayerMax = GlobalMaximums[LayerName]; - - if (GlobalMinimums.Contains(LayerName)) - LayerMin = GlobalMinimums[LayerName]; - } - - // Get the layer package path - // FString LayerNameString = FString::Printf(TEXT("%s_%d"), LayerString.GetCharArray().GetData(), (int32)LayerGeoPartObject->PartId); - // LayerNameString = UPackageTools::SanitizePackageName(LayerNameString); - - // Build an object name for the current layer - LayerPackageParams.SplitStr = SanitizedLayerName; - - // Creating the ImportLayerInfo and LayerInfo objects - FLandscapeImportLayerInfo ImportLayerInfo(*LayerName); - - // See if the user has assigned a layer info object via attribute - UPackage * Package = nullptr; - ULandscapeLayerInfoObject* LayerInfo = GetLandscapeLayerInfoForLayer(*LayerGeoPartObject, *LayerName); - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - // No assignment, try to find or create a landscape layer info object for that layer - LayerInfo = FindOrCreateLandscapeLayerInfoObject(LayerName, LayerPackageParams.GetPackagePath(), LayerPackageParams.GetPackageName(), Package); - } - - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - continue; - } - - // Convert the float data to uint8 - // HF masks need their X/Y sizes swapped - if (!FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( - FloatLayerData, LayerVolumeInfo.YLength, LayerVolumeInfo.XLength, - LayerMin, LayerMax, - LandscapeXSize, LandscapeYSize, - ImportLayerInfo.LayerData)) - continue; - - // We will store the data used to convert from Houdini values to int in the DebugColor - // This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards... - // R = Min, G = Max, B = Spacing, A = ? - LayerInfo->LayerUsageDebugColor.R = LayerMin; - LayerInfo->LayerUsageDebugColor.G = LayerMax; - LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; - LayerInfo->LayerUsageDebugColor.A = PI; - - // Visibility are by default non weight blended - if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) - LayerInfo->bNoWeightBlend = true; - else - LayerInfo->bNoWeightBlend = false; - - if (!bIsUpdate && Package && !Package->IsPendingKill()) - { - // Mark the package dirty... - Package->MarkPackageDirty(); - OutCreatedPackages.Add(Package); - } - - if (bExportTexture) - { - // Create an export of the converted data to texture - // FString TextureName = LayerString; - // if (LayerGeoPartObject->VolumeTileIndex >= 0) - // TextureName = TEXT("Tile") + FString::FromInt(LayerGeoPartObject->VolumeTileIndex) + TEXT("_") + LayerString; - // TextureName += TEXT("_conv"); - - const FString TextureName = TilePackageParams.ObjectName + TEXT("_conv"); - - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - LandscapeXSize, LandscapeYSize, - ImportLayerInfo.LayerData); - } - - // See if there is a physical material assigned via attribute for that landscape layer - UPhysicalMaterial* PhysMaterial = FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(*LayerGeoPartObject); - if (PhysMaterial && !PhysMaterial->IsPendingKill()) - { - LayerInfo->PhysMaterial = PhysMaterial; - } - - // Assign the layer info object to the import layer infos - ImportLayerInfo.LayerInfo = LayerInfo; - OutLayerInfos.Add(ImportLayerInfo); - } - - // Autosaving the layers prevents them for being deleted with the Asset - // Save the packages created for the LayerInfos - // Do this only for when creating layers. - /* - if (!bIsUpdate) - FEditorFileUtils::PromptForCheckoutAndSave(CreatedLandscapeLayerPackage, true, false); - */ - - return true; -} - -void -FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums, - bool bShouldEmptyMaps) -{ - if (bShouldEmptyMaps) - { - GlobalMinimums.Empty(); - GlobalMaximums.Empty(); - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray FloatData; - - for (const FHoudiniGeoPartObject& CurrentHeightfield: InHeightfieldArray) - { - // Get the current Heightfield GeoPartObject - if ( CurrentHeightfield.VolumeInfo.TupleSize != 1) - continue; - - // Retrieve node id from geo part. - HAPI_NodeId NodeId = CurrentHeightfield.GeoId; - if (NodeId == -1) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - &CurrentVolumeInfo)) - continue; - - // Retrieve the volume name. - FString VolumeName; - FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); - HoudiniEngineStringPartName.ToFString(VolumeName); - - bool bHasMinAttr = false; - bool bHasMaxAttr = false; - - // If this volume has an attribute defining a minimum value use it as is. - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - GlobalMinimums.Add(VolumeName, FloatData[0]); - bHasMinAttr = true; - } - } - - // If this volume has an attribute defining maximum value use it as is. - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - GlobalMaximums.Add(VolumeName, FloatData[0]); - bHasMaxAttr = true; - } - } - - if (!(bHasMinAttr && bHasMaxAttr)) - { - // Unreal's Z values are Y in Houdini - float ymin, ymax; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - nullptr, &ymin, nullptr, - nullptr, &ymax, nullptr, - nullptr, nullptr, nullptr)) - continue; - - - if (!bHasMinAttr) - { - // Read the global min value for this volume - if (!GlobalMinimums.Contains(VolumeName)) - { - GlobalMinimums.Add(VolumeName, ymin); - } - else - { - // Update the min if necessary - if (ymin < GlobalMinimums[VolumeName]) - GlobalMinimums[VolumeName] = ymin; - } - } - - if (!bHasMaxAttr) - { - // Read the global max value for this volume - if (!GlobalMaximums.Contains(VolumeName)) - { - GlobalMaximums.Add(VolumeName, ymax); - } - else - { - // Update the max if necessary - if (ymax > GlobalMaximums[VolumeName]) - GlobalMaximums[VolumeName] = ymax; - } - } - } - } -} - -void -FHoudiniLandscapeTranslator::GetLayersZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums) - -{ - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray FloatData; - - for (const FHoudiniGeoPartObject& CurrentHeightfield : InHeightfieldArray) - { - // Get the current Heightfield GeoPartObject - if (CurrentHeightfield.VolumeInfo.TupleSize != 1) - continue; - - // Retrieve node id from geo part. - HAPI_NodeId NodeId = CurrentHeightfield.GeoId; - if (NodeId == -1) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - &CurrentVolumeInfo)) - continue; - - // Retrieve the volume name. - FString VolumeName; - FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); - HoudiniEngineStringPartName.ToFString(VolumeName); - - // Read the global min value for this volume - - float MinValue; - float MaxValue; - bool bHasMin = false; - bool bHasMax = false; - - if (!GlobalMinimums.Contains(VolumeName)) - { - // Extract min value - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - MinValue = FloatData[0]; - bHasMin = true; - } - } - if (!bHasMin) - { - if (VolumeName == TEXT("height")) - { - MinValue = -1000.f; - } - else - { - MinValue = 0.f; - } - } - GlobalMinimums.Add(VolumeName, MinValue); - } - - if (!GlobalMaximums.Contains(VolumeName)) - { - // Extract max value - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - MaxValue = FloatData[0]; - bHasMax = true; - } - } - if (!bHasMax) - { - if (VolumeName == TEXT("height")) - { - MaxValue = 1000.f; - } - else - { - MaxValue = 1.f; - } - } - GlobalMaximums.Add(VolumeName, MaxValue); - } - - - - } -} - -bool -FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( - const TArray& FloatLayerData, - const int32& HoudiniXSize, const int32& HoudiniYSize, - const float& LayerMin, const float& LayerMax, - const int32& LandscapeXSize, const int32& LandscapeYSize, - TArray& LayerData, const bool& NoResize) -{ - // Convert the float data to uint8 - LayerData.SetNumUninitialized(HoudiniXSize * HoudiniYSize); - - // Calculating the factor used to convert from Houdini's ZRange to [0 255] - double LayerZRange = (LayerMax - LayerMin); - double LayerZSpacing = (LayerZRange != 0.0) ? (255.0 / (double)(LayerZRange)) : 0.0; - - int32 nUnrealIndex = 0; - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y - int32 nHoudini = nY + nX * HoudiniYSize; - - // Get the double values in [0 - ZRange] - double DoubleValue = (double)FMath::Clamp(FloatLayerData[nHoudini], LayerMin, LayerMax) - (double)LayerMin; - - // Then convert it to [0 - 255] - DoubleValue *= LayerZSpacing; - - LayerData[nUnrealIndex++] = FMath::RoundToInt(DoubleValue); - } - } - - // Finally, resize the data to fit with the new landscape size if needed - if (NoResize) - return true; - - return FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( - LayerData, HoudiniXSize, HoudiniYSize, - LandscapeXSize, LandscapeYSize); -} - -bool -FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( - TArray< uint8 >& LayerData, - const int32& SizeX, const int32& SizeY, - const int32& NewSizeX, const int32& NewSizeY) -{ - if ((NewSizeX == SizeX) && (NewSizeY == SizeY)) - return true; - - bool bForceResample = true; - bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); - - TArray NewData; - if (!bResample) - { - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - - const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; - const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; - - // Expanding the Data - NewData = ExpandData( - LayerData, - 0, 0, SizeX - 1, SizeY - 1, - -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1); - } - else - { - // Resampling the data - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - NewData = ResampleData(LayerData, SizeX, SizeY, NewSizeX, NewSizeY); - } - - LayerData = NewData; - - return true; -} - -ALandscapeProxy * -FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( - const TArray< uint16 >& IntHeightData, - const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - const FTransform& TileTransform, - const int32& XSize, - const int32& YSize, - const int32& NumSectionPerLandscapeComponent, - const int32& NumQuadsPerLandscapeSection, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhsyicalMaterial, - const FString& LandscapeTileActorName, - LandscapeActorType ActorType, - ALandscape* SharedLandscapeActor, - UWorld* InWorld, - ULevel* InLevel, - FHoudiniPackageParams InPackageParams) -{ - if (!IsValid(InWorld)) - return nullptr; - - // if (!IsValid(MainLandscapeActor)) - // return nullptr; - - if ((XSize < 2) || (YSize < 2)) - return nullptr; - - if (IntHeightData.Num() != (XSize * YSize)) - return nullptr; - - if (!GEditor) - return nullptr; - - ALandscapeProxy* LandscapeTile = nullptr; - UPackage *CreatedPackage = nullptr; - - ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; - ALandscape* CachedLandscapeActor = nullptr; - - UWorld* NewWorld = nullptr; - FString MapFileName; - bool bBroadcastMaterialUpdate = false; - //... Create landscape tile ...// - { - // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos - if (ActorType == LandscapeActorType::LandscapeStreamingProxy) - { - CachedStreamingProxyActor = InWorld->SpawnActor(); - if (CachedStreamingProxyActor) - { - check(SharedLandscapeActor); - CachedStreamingProxyActor->PreEditChange(nullptr); - - // Update landscape tile properties from the main landscape actor. - CachedStreamingProxyActor->GetSharedProperties(SharedLandscapeActor); - CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; - CachedStreamingProxyActor->bCastStaticShadow = false; - - LandscapeTile = CachedStreamingProxyActor; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not spawn ALandscapeStreamingProxy with name: %s"), *(LandscapeTileActorName) ); - return nullptr; - } - } - else - { - // Create a normal landscape actor - CachedLandscapeActor = InWorld->SpawnActor(); - if (CachedLandscapeActor) - { - CachedLandscapeActor->PreEditChange(nullptr); - CachedLandscapeActor->SetLandscapeGuid(FGuid::NewGuid()); - CachedLandscapeActor->LandscapeMaterial = LandscapeMaterial; - CachedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - CachedLandscapeActor->bCastStaticShadow = false; - bBroadcastMaterialUpdate = true; - LandscapeTile = CachedLandscapeActor; - } - } - } - - - if (!LandscapeTile) - return nullptr; - - // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. - - // Autosaving the layers prevents them for being deleted with the Asset - // Save the packages created for the LayerInfos - //if ( CreatedLayerInfoPackage.Num() > 0 ) - // FEditorFileUtils::PromptForCheckoutAndSave( CreatedLayerInfoPackage, true, false ); - - // Import the landscape data - - // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue - LandscapeTile->bCastStaticShadow = false; - - // TODO: Check me? - //if (LandscapePhsyicalMaterial) - // LandscapeTile->DefaultPhysMaterial = LandscapePhsyicalMaterial; - - // Setting the layer type here. - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - - TMap> HeightmapDataPerLayers; - TMap> MaterialLayerDataPerLayer; - HeightmapDataPerLayers.Add(FGuid(), IntHeightData); - MaterialLayerDataPerLayer.Add(FGuid(), ImportLayerInfos); - - FTransform NewTileTransform, LandscapeOffset; - FIntPoint TileLoc; - - // NOTE: The following Import call will reregister all components, causing the actor to lose its transform. - // So we'll be importing the tile data as if the actor was located at the origin and fix up transforms afterward. - CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeOffset, NewTileTransform, TileLoc); - - // Before we import new tile data, remove previous tile data that will be overlapped by the new tile. - TSet OverlappingComponents; - const int32 DestMinX = TileLoc.X; - const int32 DestMinY = TileLoc.Y; - const int32 DestMaxX = TileLoc.X + XSize - 1; - const int32 DestMaxY = TileLoc.Y + YSize - 1; - - ULandscapeInfo* LandscapeInfo = LandscapeTile->GetLandscapeInfo(); - - if (LandscapeInfo) - { - // If there is a preexisting LandscapeInfo object, check for overlapping components. - - // Shrink bounds by 1,1 to avoid GetComponentsInRegion picking up extra components on all sides due to the overlap between components - LandscapeInfo->GetComponentsInRegion(DestMinX+1, DestMinY+1, DestMaxX-1, DestMaxY-1, OverlappingComponents); - TSet StaleActors; - - for (ULandscapeComponent* Component : OverlappingComponents) - { - // Remove the overlapped component from the LandscapeInfo and then from - LandscapeInfo->Modify(); - - ALandscapeProxy* Proxy = Component->GetLandscapeProxy(); - if (!IsValid(Proxy)) - continue; - check(Proxy); - FIntRect Bounds = Proxy->GetBoundingRect(); - // If this landscape proxy has no more components left, remove it from the LandscapeInfo. - LandscapeInfo->UnregisterActor(Proxy); - Proxy->Destroy(); - } - - ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); - } - - // Import tile data - // The Import function will correctly compute the tile section locations. No need to set it explicitly. - // TODO: Verify this with world composition!! - - bool bIsStandalone = LandscapeTile->GetLandscapeActor() == LandscapeTile; - - // We set the actor transform and absolute section base before importing heighfield data. This allows us to - // use the correct (quad-space) blitting region without causing overlaps during import. - - // NOTE: The following SetAbsoluteSectionBase() calls are very important. This tells the Landscape system - // where on the landscape, in quad space, a specific tile is located. This influences is used by various - // mechanisms and tools that tie into the Landscape system such as World Composition and Landscape editor tools. - // If the Section Offset for a landscape tile is wrong, it will appear in the wrong place in the World Composition - // viewer. Worse than that, none of Landscape Editor tools will work properly because it won't be able to - // locate the correct Landscape component when calculating the "Landscape Component Key" for the given word position. - // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blit functions use the - // same underlying Landscape Component Key calculation to find the correct landscape component to draw to. So if the - // Section Offsets are wrong ... all manner of chaos will follow. - // It is also super important to call ULandscapeInfo::RecreateLandscapeInfo() after change a LandscapeProxy's - // section offset in order to update the landscape's internal caches otherwise component key calculations - // won't work correctly. - - LandscapeTile->SetActorTransform(TileTransform); - LandscapeTile->SetAbsoluteSectionBase(TileLoc); - - LandscapeTile->Import( - LandscapeTile->GetLandscapeGuid(), - DestMinX, DestMinY, DestMaxX, DestMaxY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, - HeightmapDataPerLayers, NULL, - MaterialLayerDataPerLayer, ImportLayerType); - - LandscapeTile->RecreateComponentsState(); - - if (!LandscapeInfo) - { - LandscapeInfo = LandscapeTile->GetLandscapeInfo(); - } - - // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so - // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo, - // and only then are we able to "blit" the new alpha data into the correct place on the landscape. - - ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); - - // TODO: Verify whether we still need to manually set the LandscapeLightingLOD setting or whether - // calling PostEditChange() will properly fix the state. - - // Copied straight from UE source code to avoid crash after importing the landscape: - // automatically calculate a lighting LOD that won't crash lightmass (hopefully) - // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 - LandscapeTile->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); - - // ---------------------------------------------------- - // Rename the actor - // ---------------------------------------------------- - - // NOTE: The LandscapeProxy needs to be properly initialized before renaming (which is why the rename is taking - // place at the end) since the rename will trigger PostEditChange and can crash if the actor has not been - // correctly setup. - FHoudiniEngineUtils::SafeRenameActor(LandscapeTile, LandscapeTileActorName); - - if (!LandscapeTile->MarkPackageDirty()) - { - HOUDINI_LOG_WARNING(TEXT("Editor suppressed MarkPackageDirty() for landscape tile: %s"), *(LandscapeTile->GetPathName())); - } - - return LandscapeTile; -} - - -void -FHoudiniLandscapeTranslator::CalculateTileLocation( - int32 NumSectionsPerComponent, - int32 NumQuadsPerSection, - const FTransform& TileTransform, - FTransform& OutLandscapeOffset, - FTransform& OutTileTransform, - FIntPoint& OutTileLocation) -{ - // IMPORTANT: Tile Location (Section Base) needs to be in multiples of the landscape component's quad size - const int32 ComponentSize = NumSectionsPerComponent * NumQuadsPerSection; - - OutLandscapeOffset = FTransform(FRotator::ZeroRotator, FVector::ZeroVector, TileTransform.GetScale3D()); - OutTileTransform = TileTransform; - - // Sometimes the calculated tile coordinate falls on a half-unit so we would need to remove that offset first - // before calculating integer (quad space) tile location. - // For example, 123.5, should become 123. -456.5 should become -456. - const FVector TileScale = TileTransform.GetScale3D(); - const float TileCoordX = TileTransform.GetLocation().X / TileScale.X; - const float TileCoordY = TileTransform.GetLocation().Y / TileScale.Y; - - float NearestMultipleX = FMath::RoundHalfFromZero(TileCoordX / ComponentSize) * ComponentSize; - float NearestMultipleY = FMath::RoundHalfFromZero(TileCoordY / ComponentSize) * ComponentSize; - - // If the multiples are too close to the middle, offset by 0.5 to avoid some tiles snapping up and others snapping down. - if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleX), 0.5f, 0.1f)) - { - NearestMultipleX += 0.5f; - } - if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleY), 0.5f, 0.1f)) - { - NearestMultipleY += 0.5f; - } - - const float TileOffsetX = NearestMultipleX - TileCoordX; - const float TileOffsetY = NearestMultipleY - TileCoordY; - - OutTileLocation.X = FMath::RoundHalfFromZero(NearestMultipleX); - OutTileLocation.Y = FMath::RoundHalfFromZero(NearestMultipleY); - - // Adjust landscape offset to compensate for tile location / section base shifting. - OutLandscapeOffset.SetLocation( FVector(-TileOffsetX * TileScale.X, -TileOffsetY * TileScale.Y, 0.f) ); -} - - -void -FHoudiniLandscapeTranslator::GetLandscapeMaterials( - const FHoudiniGeoPartObject& InHeightHGPO, - UMaterialInterface*& OutLandscapeMaterial, - UMaterialInterface*& OutLandscapeHoleMaterial, - UPhysicalMaterial*& OutLandscapePhysicalMaterial) -{ - OutLandscapeMaterial = nullptr; - OutLandscapeHoleMaterial = nullptr; - OutLandscapePhysicalMaterial = nullptr; - - if (InHeightHGPO.Type != EHoudiniPartType::Volume) - return; - - TArray Materials; - HAPI_AttributeInfo AttribMaterials; - FHoudiniApi::AttributeInfo_Init(&AttribMaterials); - - // First, look for landscape material - { - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL, - AttribMaterials, Materials); - - // If the material attribute was not found, check the material instance attribute. - if (!AttribMaterials.exists) - { - Materials.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, - AttribMaterials, Materials); - } - - // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. - //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) - if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) - { - HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a point, primitive or detail attribute, ignoring attribute.")); - AttribMaterials.exists = false; - Materials.Empty(); - } - - if (AttribMaterials.exists && Materials.Num() > 0) - { - // Load the material - OutLandscapeMaterial = Cast(StaticLoadObject( - UMaterialInterface::StaticClass(), - nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); - } - } - - Materials.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribMaterials); - - // Then, for the hole_material - { - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - AttribMaterials, Materials); - - // If the material attribute was not found, check the material instance attribute. - if (!AttribMaterials.exists) - { - Materials.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE, - AttribMaterials, Materials); - } - - // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. - //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) - if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) - { - HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute.")); - AttribMaterials.exists = false; - Materials.Empty(); - } - - if (AttribMaterials.exists && Materials.Num() > 0) - { - // Load the material - OutLandscapeHoleMaterial = Cast< UMaterialInterface >(StaticLoadObject( - UMaterialInterface::StaticClass(), - nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); - } - } - - // Then for the physical material - OutLandscapePhysicalMaterial = GetLandscapePhysicalMaterial(InHeightHGPO); -} - -// Read the landscape component extent attribute from a heightfield -bool -FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( - const FHoudiniGeoPartObject& HoudiniGeoPartObject, - int32& MinX, int32& MaxX, - int32& MinY, int32& MaxY) -{ - // If we dont have minX, we likely dont have the others too - if (!FHoudiniEngineUtils::HapiCheckAttributeExists( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", HAPI_ATTROWNER_PRIM)) - return false; - - // Create an AttributeInfo - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); - - // Get MinX - TArray IntData; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MinX = IntData[0]; - - // Get MaxX - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MaxX = IntData[0]; - - // Get MinY - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MinY = IntData[0]; - - // Get MaxX - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MaxY = IntData[0]; - - return true; -} - -ULandscapeLayerInfoObject * -FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& InLayerName, const FString& InPackagePath, const FString& InPackageName, UPackage*& OutPackage) -{ - FString PackageFullName = InPackagePath + TEXT("/") + InPackageName; - - // See if package exists, if it does, reuse it - bool bCreatedPackage = false; - OutPackage = FindPackage(nullptr, *PackageFullName); - if (!OutPackage || OutPackage->IsPendingKill()) - { - // We need to create a new package - OutPackage = CreatePackage(nullptr, *PackageFullName); - bCreatedPackage = true; - } - - if (!OutPackage || OutPackage->IsPendingKill()) - return nullptr; - - if (!OutPackage->IsFullyLoaded()) - OutPackage->FullyLoad(); - - ULandscapeLayerInfoObject* LayerInfo = nullptr; - if (!bCreatedPackage) - { - // See if we can load the layer info instead of creating a new one - LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), OutPackage, FName(*InPackageName)); - } - - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - // Create a new LandscapeLayerInfoObject in the package - LayerInfo = NewObject(OutPackage, FName(*InPackageName), RF_Public | RF_Standalone /*| RF_Transactional*/); - - // Notify the asset registry - FAssetRegistryModule::AssetCreated(LayerInfo); - } - - if (LayerInfo && !LayerInfo->IsPendingKill()) - { - LayerInfo->LayerName = FName(*InLayerName); - - // Trigger update of the Layer Info - LayerInfo->PreEditChange(nullptr); - LayerInfo->PostEditChange(); - LayerInfo->MarkPackageDirty(); - - // Mark the package dirty... - OutPackage->MarkPackageDirty(); - } - - return LayerInfo; -} - -bool -FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax( - const TArray& AllOutputs, float& OutGlobalMin, float& OutGlobalMax) -{ - OutGlobalMin = 0.f; - OutGlobalMax = 0.f; - - for (const auto& CurrentOutput : AllOutputs) - { - if (!CurrentOutput) - continue; - - if (CurrentOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - const TArray& HGPOs = CurrentOutput->GetHoudiniGeoPartObjects(); - for (const FHoudiniGeoPartObject& CurrentHGPO : HGPOs) - { - if (CurrentHGPO.Type != EHoudiniPartType::Volume) - continue; - - if (!CurrentHGPO.VolumeInfo.Name.Contains("height")) - continue; - - // We're only handling single values for now - if (CurrentHGPO.VolumeInfo.TupleSize != 1) - continue; - - // Terrains always have a ZSize of 1. - if (CurrentHGPO.VolumeInfo.ZLength != 1) - continue; - - // Values should be float - if (!CurrentHGPO.VolumeInfo.bIsFloat) - continue; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CurrentHGPO.GeoId)) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHGPO.GeoId, CurrentHGPO.PartId, &CurrentVolumeInfo)) - continue; - - // Unreal's Z values are Y in Houdini - float yMin = OutGlobalMin, yMax = OutGlobalMax; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), - CurrentHGPO.GeoId, CurrentHGPO.PartId, - nullptr, &yMin, nullptr, - nullptr, &yMax, nullptr, - nullptr, nullptr, nullptr)) - continue; - - if (yMin < OutGlobalMin) - OutGlobalMin = yMin; - - if (yMax > OutGlobalMax) - OutGlobalMax = yMax; - } - - if (OutGlobalMin > OutGlobalMax) - { - OutGlobalMin = 0.f; - OutGlobalMax = 0.f; - } - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::EnableWorldComposition() -{ - HOUDINI_LOG_WARNING(TEXT("[FHoudiniLandscapeTranslator::EnableWorldComposition] We should never enable world composition from within the plugin.")); - // Get the world - UWorld* MyWorld = nullptr; - { - // We want to create the landscape in the landscape editor mode's world - FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); - MyWorld = EditorWorldContext.World(); - } - - if (!MyWorld) - return false; - - ULevel* CurrentLevel = MyWorld->GetCurrentLevel(); - - if (!CurrentLevel) - return false; - - AWorldSettings* WorldSettings = CurrentLevel->GetWorldSettings(); - if (!WorldSettings) - return false; - - // Enable world composition in WorldSettings - WorldSettings->bEnableWorldComposition = true; - - CurrentLevel->PostEditChange(); - - return true; -} - - -bool -FHoudiniLandscapeTranslator::GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InPrimIndex, TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive property attributes - // Volumes apparently dont have prim attributes because they're converted to pointmeshes somehow... - //FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - // InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InPrimIndex); - - // .. then the point property attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InPrimIndex); - - return FoundCount > 0; -} - - -bool -FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (auto CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current Property Attribute - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); - } - - return (NumSuccess > 0); -} - - -bool -FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(const FString& BaseName, ALandscapeProxy* Landscape) -{ - // We need to cache the input landscape to a file - if (!Landscape) - return false; - - ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Save Height data to file - //FString HeightSave = TEXT("/Game/HoudiniEngine/Temp/HeightCache.png"); - FString HeightSave = BaseName + TEXT("_height.png"); - LandscapeInfo->ExportHeightmap(HeightSave); - Landscape->ReimportHeightmapFilePath = HeightSave; - - // Save each layer to a file - for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) - { - FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); - //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) - continue; - - FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); - LandscapeInfo->ExportLayer(CurrentLayerInfo, LayerSave); - - // Update the file reimport path on the input landscape for this layer - LandscapeInfo->GetLayerEditorSettings(CurrentLayerInfo).ReimportLayerFilePath = LayerSave; - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy) -{ - if (!LandscapeProxy) - return false; - - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Restore Height data from the backup file - FString ReimportFile = LandscapeProxy->ReimportHeightmapFilePath; - if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, TEXT("height"))) - HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); - - // Restore each layer from the backup file - TArray< ULandscapeLayerInfoObject* > SourceLayers; - for (int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++) - { - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) - continue; - - FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); - ReimportFile = LandscapeProxy->EditorLayerSettings[LayerIndex].ReimportLayerFilePath; - - if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, CurrentLayerName, CurrentLayerInfo)) - HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); - - SourceLayers.Add(CurrentLayerInfo); - } - - // Iterate on the landscape info's layer to remove any layer that could have been added by Houdini - for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) - { - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; - if (SourceLayers.Contains(CurrentLayerInfo)) - continue; - - // Delete the added layer - FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName; - LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName); - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::ImportLandscapeData( - ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject) -{ - // - // Code copied/edited from FEdModeLandscape::ImportData as we cannot access that function - // - if (!LandscapeInfo) - return false; - - bool IsHeight = LayerName.Equals(TEXT("height"), ESearchCase::IgnoreCase); - - int32 MinX, MinY, MaxX, MaxY; - if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - { - const FLandscapeFileResolution LandscapeResolution = { (uint32)(1 + MaxX - MinX), (uint32)(1 + MaxY - MinY) }; - - ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); - - if (IsHeight) - { - const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); - - if (!HeightmapFormat) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); - return false; - } - - FLandscapeFileResolution ImportResolution = { 0, 0 }; - - const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename); - - // display error message if there is one, and abort the import - if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); - return false; - } - - // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape - if (HeightmapInfo.PossibleResolutions.Num() > 1) - { - if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined")); - return false; - } - else - { - ImportResolution = LandscapeResolution; - } - } - - // display warning message if there is one and allow user to cancel - if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); - - // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape - // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is - if (HeightmapInfo.PossibleResolutions.Num() == 1) - { - ImportResolution = HeightmapInfo.PossibleResolutions[0]; - if (ImportResolution != LandscapeResolution) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); - } - - FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution); - if (ImportData.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); - return false; - } - - TArray Data; - if (ImportResolution != LandscapeResolution) - { - // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked - // so that reimports behave the same as the initial import :) - - const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; - const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; - - Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16)); - - ExpandData(Data.GetData(), ImportData.Data.GetData(), - 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, - -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); - } - else - { - Data = MoveTemp(ImportData.Data); - } - - //FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap")); - - FHeightmapAccessor HeightmapAccessor(LandscapeInfo); - HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData()); - } - else - { - // We're importing a Landscape layer - if (!LayerInfoObject || LayerInfoObject->IsPendingKill()) - return false; - - const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); - if (!WeightmapFormat) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); - return false; - } - - FLandscapeFileResolution ImportResolution = { 0, 0 }; - - const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName)); - - // display error message if there is one, and abort the import - if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); - return false; - } - - // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape - if (WeightmapInfo.PossibleResolutions.Num() > 1) - { - if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined")); - return false; - } - else - { - ImportResolution = LandscapeResolution; - } - } - - // display warning message if there is one and allow user to cancel - if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); - - // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape - // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is - if (WeightmapInfo.PossibleResolutions.Num() == 1) - { - ImportResolution = WeightmapInfo.PossibleResolutions[0]; - if (ImportResolution != LandscapeResolution) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); - } - - FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution); - - if (ImportData.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); - return false; - } - - TArray Data; - if (ImportResolution != LandscapeResolution) - { - // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked - // so that reimports behave the same as the initial import :) - const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; - const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; - - Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8)); - - ExpandData(Data.GetData(), ImportData.Data.GetData(), - 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, - -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); - } - else - { - Data = MoveTemp(ImportData.Data); - } - - //FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer")); - FAlphamapAccessor AlphamapAccessor(LandscapeInfo, LayerInfoObject); - AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None); - } - } - - return true; -} - -UTexture2D* -FHoudiniLandscapeTranslator::CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& InFloatBuffer, - const float& InMin, - const float& InMax) -{ - - // Convert the float values to uint8 - double Range = (double)InMax - (double)InMin; - TArray IntBuffer; - IntBuffer.SetNum(InFloatBuffer.Num()); - for(int32 i = 0; i < InFloatBuffer.Num(); i++) - { - double dNormalizedValue = ((double)InFloatBuffer[i] - (double)InMin) / (double)Range; - IntBuffer[i] = (uint8)(dNormalizedValue * 255.0); - } - - return FHoudiniLandscapeTranslator::CreateUnrealTexture( - InPackageParams, LayerName, InXSize, InYSize, IntBuffer); -} - -UTexture2D* -FHoudiniLandscapeTranslator::CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& IntBuffer) -{ - FHoudiniPackageParams MyPackageParams = InPackageParams; - MyPackageParams.ObjectName = LayerName; - MyPackageParams.PackageMode = EPackageMode::CookToTemp; - MyPackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - FString CreatedPackageName; - UPackage* Package = MyPackageParams.CreatePackageForObject(CreatedPackageName); - if (!Package || Package->IsPendingKill()) - return nullptr; - - // Create new texture object. - UTexture2D * Texture = NewObject(Package, UTexture2D::StaticClass(), *LayerName, RF_Public | RF_Standalone); - - // Add/Update meta information to package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *LayerName); - - /*// Texture Settings - Texture->PlatformData = new FTexturePlatformData(); - Texture->PlatformData->SizeX = InXSize; - Texture->PlatformData->SizeY = InYSize; - Texture->PlatformData->PixelFormat = PF_R8G8B8A8;*/ - - // Initialize texture source. - Texture->Source.Init(InXSize, InYSize, 1, 1, TSF_BGRA8); - - // Lock the texture. - uint8 * MipData = Texture->Source.LockMip(0); - - // Create base map. - uint8* DestPtr = nullptr; - uint32 SrcWidth = InXSize; - uint32 SrcHeight = InYSize; - const uint8 * SrcData = &IntBuffer[0]; - - for (uint32 y = 0; y < SrcHeight; y++) - { - DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; - - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth + x; - - *DestPtr++ = *(SrcData + DataOffset); // B greyscale, same value 3 times - *DestPtr++ = *(SrcData + DataOffset); // G - *DestPtr++ = *(SrcData + DataOffset); // R - - *DestPtr++ = 0xFF; // A to 1 - } - } - - // Unlock the texture. - Texture->Source.UnlockMip(0); - - // Texture creation parameters. - //Texture->SRGB = TextureParameters.bSRGB; - Texture->CompressionSettings = TC_Grayscale; - Texture->CompressionNoAlpha = true; - Texture->MipGenSettings = TMGS_NoMipmaps; - - // Set the Source Guid/Hash if specified. - /* - if ( TextureParameters.SourceGuidHash.IsValid() ) - { - Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); - } - */ - - // Updating Texture & mark it as unsaved - //Texture->AddToRoot(); - //Texture->UpdateResource(); - Package->MarkPackageDirty(); - - Texture->PostEditChange(); - - FString PathName = Texture->GetPathName(); - HOUDINI_LOG_MESSAGE(TEXT("Created texture when for %s in %s"), *LayerName, *PathName); - - return Texture; -} - -UPhysicalMaterial* -FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO) -{ - // See if we have assigned a physical material to this layer via attribute - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - TArray AttributeValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InLayerHGPO.GeoId, InLayerHGPO.PartId, - HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL, - AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) - return nullptr; - - if (AttributeValues.Num() > 0) - { - return LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); - } - - return nullptr; -} - -ULandscapeLayerInfoObject* -FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName) -{ - // See if we have assigned a landscape layer info object to this layer via attribute - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - TArray AttributeValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InLayerHGPO.GeoId, InLayerHGPO.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO, - AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) - return nullptr; - - if (AttributeValues.Num() > 0) - { - ULandscapeLayerInfoObject* FoundLayerInfo = LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); - if (!FoundLayerInfo || FoundLayerInfo->IsPendingKill()) - return nullptr; - - // The layer info's name must match this layer's name or Unreal will not like this! - if (!FoundLayerInfo->LayerName.IsEqual(InLayerName)) - { - FString NameStr = InLayerName.ToString(); - HOUDINI_LOG_WARNING(TEXT("Failed to use the assigned layer info object for %s by the unreal_landscape_layer_info attribute as the found layer info object's layer name does not match."), *NameStr); - } - - return FoundLayerInfo; - } - - return nullptr; -} - - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "HoudiniLandscapeTranslator.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniEngineString.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPackageParams.h" +#include "HoudiniStringResolver.h" +#include "HoudiniInput.h" + +#include "ObjectTools.h" +#include "FileHelpers.h" +#include "Editor.h" +#include "LandscapeLayerInfoObject.h" +#include "LandscapeStreamingProxy.h" +#include "LandscapeInfo.h" +#include "LandscapeEdit.h" +#include "AssetRegistryModule.h" +#include "PackageTools.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "UObject/UnrealType.h" + +#include "GameFramework/WorldSettings.h" +#include "HAL/PlatformFilemanager.h" +#include "Misc/Paths.h" +#include "Engine/LevelStreamingDynamic.h" +#include "Modules/ModuleManager.h" +#include "AssetToolsModule.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "LevelUtils.h" +#include "Factories/WorldFactory.h" +#include "Misc/Guid.h" +#include "Engine/LevelBounds.h" + +#include "HAL/IConsoleManager.h" +#include "Engine/AssetManager.h" +#include "Engine/LevelStreamingAlwaysLoaded.h" +#include "LandscapeEditor/Private/LandscapeEdMode.h" +#include "Misc/AssetRegistryInterface.h" +#include "Misc/StringFormatArg.h" +#include "Engine/WorldComposition.h" + +#if WITH_EDITOR + #include "LandscapeEditorModule.h" + #include "LandscapeFileFormatInterface.h" + #include "EditorLevelUtils.h" + #include "WorldBrowserModule.h" + #include "EditorLevelUtils.h" + #include "Misc/WorldCompositionUtility.h" +#endif + +static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( + TEXT("HoudiniEngine.ExportLandscapeTextures"), + 0, + TEXT("If enabled, landscape layers and heightmap will be exported as textures in the temp directory when converting a Heightfield to a Landscape.\n") + TEXT("0: Disabled\n") + TEXT("1: Enabled\n") +); + +typedef FHoudiniEngineUtils FHUtils; + +bool +FHoudiniLandscapeTranslator::CreateLandscape( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedOutputs, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* InWorld, // Persistent / root world for the landscape + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniPackageParams InPackageParams, + TArray& OutCreatedPackages +) +{ + check(LayerMinimums.Contains(TEXT("height"))); + check(LayerMaximums.Contains(TEXT("height"))); + + float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); + float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); + + if (!InOutput || InOutput->IsPendingKill()) + return false; + + // Get the height map. + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + if (!Heightfield) + return false; + + if (Heightfield->Type != EHoudiniPartType::Volume) + return false; + + const HAPI_NodeId GeoId = Heightfield->GeoId; + const HAPI_PartId PartId = Heightfield->PartId; + + // Construct the identifier of the Heightfield geo part. + FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); + HeightfieldIdentifier.PartName = Heightfield->PartName; + + FString NodeNameSuffix = GetActorNameSuffix(InPackageParams.PackageMode); + + TArray IntData; + TArray StrData; + // Output attributes will be stored on the Output object and will be used again during baking to determine + // where content should be baked to and what they should be named, etc. + // At the end of this function, the output attributes and tokens will be copied to the output object. + TMap OutputAttributes; + TMap OutputTokens; + FHoudiniAttributeResolver Resolver; + InPackageParams.UpdateTokensFromParams(InWorld, OutputTokens); + + bool bHasTile = Heightfield->VolumeTileIndex >= 0; + + // --------------------------------------------- + // Attribute: unreal_landscape_tile_actor_type, unreal_landscape_streaming_proxy (v1) + // --------------------------------------------- + // Determine the actor type for the tile + bool bCreateLandscapeStreamingProxy = false; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + LandscapeActorType TileActorType = LandscapeActorType::LandscapeActor; + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0) + { + TileActorType = static_cast(IntData[0]); + } + } + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0 && IntData[0] != 0) + TileActorType = LandscapeActorType::LandscapeStreamingProxy; + } + + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, FString::FromInt(static_cast(TileActorType))); + + // --------------------------------------------- + // Attribute: unreal_landscape_actor_name + // --------------------------------------------- + // Retrieve the name of the main Landscape actor to look for + FString SharedLandscapeActorName = DefaultLandscapeActorPrefix + "SharedLandscape"; // If this is an empty string, don't affirm a root landscape actor? + StrData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, AttributeInfo, StrData, 1)) + { + if (StrData.Num() > 0 && !StrData[0].IsEmpty()) + SharedLandscapeActorName = StrData[0]; + } + + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); + + // --------------------------------------------- + // Attribute: unreal_level_path + // --------------------------------------------- + // FString LevelPath = bHasTile ? "{world}/Landscape/Tile{tile}" : "{world}/Landscape"; + FString LevelPath; + TArray LevelPaths; + if (FHoudiniEngineUtils::GetLevelPathAttribute(GeoId, PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + LevelPath = LevelPaths[0]; + } + if (!LevelPath.IsEmpty()) + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); + + // --------------------------------------------- + // Attribute: unreal_output_name + // --------------------------------------------- + FString LandscapeTileActorName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; + TArray AllOutputNames; + if (!FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames)) + { + if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) + LandscapeTileActorName = AllOutputNames[0]; + } + OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); + + // Streaming proxy actors/tiles requires a "main" landscape actor + // that contains the shared landscape state. + bool bRequiresSharedLandscape = false; + if (TileActorType == LandscapeActorType::LandscapeStreamingProxy) + bRequiresSharedLandscape = true; + + // ---------------------------------- + // Inject landscape specific tokens + // ---------------------------------- + if (bHasTile) + { + const FString TileValue = FString::FromInt(Heightfield->VolumeTileIndex); + // Tile value needs to go into Output arguments to be available during the bake. + OutputTokens.Add(TEXT("tile"), TileValue); + } + + // ---------------------------------- + // Expand string arguments for various landscape naming aspects. + // ---------------------------------- + + // Update resolver attributes and tokens before we start resolving attributes. + Resolver.SetCachedAttributes(OutputAttributes); + Resolver.SetTokensFromStringMap(OutputTokens); + + SharedLandscapeActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); + SharedLandscapeActorName += NodeNameSuffix; + + LandscapeTileActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, LandscapeTileActorName); + LandscapeTileActorName += NodeNameSuffix; + + LevelPath = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); + + FString TileName = LandscapeTileActorName; + + // Note that relative level paths are always interpreted as relative to the default output directory (temp / bake). + // FString TilePackagePath = FPaths::Combine(DefaultOutputPath, LevelPath); + FString TilePackagePath = Resolver.ResolveFullLevelPath(); + + // This crashes UE if the package name does not resolve + //FString TileMapFileName = FPackageName::LongPackageNameToFilename(TilePackagePath, FPackageName::GetMapPackageExtension()); + + FText NotValidReason; + bool bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); + if (!bIsValidLongName) + { + // Try a more naive approach + TilePackagePath = FPaths::Combine(InPackageParams.BakeFolder, LevelPath); + bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); + } + + if (!bIsValidLongName) + { + HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] TilePackagePath is not a valid long name. Reason: %s"), *(NotValidReason.ToString())); + return false; + } + + FString TileMapFileName; + if (!FPackageName::TryConvertLongPackageNameToFilename(TilePackagePath, TileMapFileName, FPackageName::GetMapPackageExtension())) + { + // Rather stop here than crash! + HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] Failed to resolve the TilePackagePath: %s"), *(TilePackagePath)); + return false; + } + + // Find the package for both the world and the tile. + // The world should contain the main landscape actor while + // the tile will contain a Landscape, LandscapeProxy or LandscapeStreamingProxy depending on user settings. + + bool bTileisStreamingProxy = (TileActorType == LandscapeActorType::LandscapeStreamingProxy); + UWorld* TileWorld = nullptr; // World from which to spawn tile actor + ULevel* TileLevel = nullptr; // Level in which to spawn tile actor + ALandscapeProxy* TileActor = nullptr; // Spawned tile actor. + + // ---------------------------------- + // Update package parameters for this tile + // ---------------------------------- + + // NOTE: we don't manually inject a tile number in the object name. This should + // already be encoded in the TileName string. + FHoudiniPackageParams TilePackageParams = InPackageParams; + TilePackageParams.ObjectName = TileName; + + FHoudiniPackageParams LayerPackageParams = InPackageParams; + if (bRequiresSharedLandscape) + { + // Note that layers are shared amongst all the tiles for a given landscape. + LayerPackageParams.ObjectName = SharedLandscapeActorName; + } + else + { + // This landscape tile is a standalone landscape and should have its own material layers. + LayerPackageParams.ObjectName = TileName; + } + + // See if the current heightfield has an unreal_material or unreal_hole_material assigned to it + UMaterialInterface* LandscapeMaterial = nullptr; + UMaterialInterface* LandscapeHoleMaterial = nullptr; + UPhysicalMaterial* LandscapePhysicalMaterial = nullptr; + FHoudiniLandscapeTranslator::GetLandscapeMaterials(*Heightfield, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial); + + // Extract the float data from the Heightfield. + const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; + TArray FloatValues; + float FloatMin, FloatMax; + if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) + return false; + + // Heightfield conversions should always use the global float min/max + // since they need to be calculated externally, potentially across multiple tiles. + FloatMin = fGlobalMin; + FloatMax = fGlobalMax; + + // Get the Unreal landscape size + int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; + int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; + int32 UnrealTileSizeX = -1; + int32 UnrealTileSizeY = -1; + int32 NumSectionPerLandscapeComponent = -1; + int32 NumQuadsPerLandscapeSection = -1; + + if (!FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + HoudiniHeightfieldXSize, HoudiniHeightfieldYSize, + UnrealTileSizeX, UnrealTileSizeY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection)) + { + return false; + } + + // ---------------------------------------------------- + // Export of layer textures + // ---------------------------------------------------- + // Export textures, if enabled. Mostly used for debugging at the moment. + bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; + if (bExportTexture) + { + // Export raw height data to texture + FString TextureName = TilePackageParams.ObjectName + TEXT("_height_raw"); + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + FloatValues, + FloatMin, + FloatMax); + } + + // Look for all the layers/masks corresponding to the current heightfield. + TArray< const FHoudiniGeoPartObject* > FoundLayers; + FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); + + // Get the updated layers. + TArray LayerInfos; + + if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, + LayerMinimums, LayerMaximums, LayerInfos, false, + TilePackageParams, + LayerPackageParams, + OutCreatedPackages)) + return false; + + // Convert Houdini's heightfield data to Unreal's landscape data + TArray IntHeightData; + FTransform TileTransform; + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + FloatValues, VolumeInfo, + UnrealTileSizeX, UnrealTileSizeY, + FloatMin, FloatMax, + IntHeightData, TileTransform)) + return false; + + // ---------------------------------------------------- + // Property changes that we want to track + // ---------------------------------------------------- + + bool bModifiedLandscapeActor = false; + bool bModifiedSharedLandscapeActor = false; + bool bSharedLandscapeMaterialChanged = false; + bool bSharedLandscapeHoleMaterialChanged = false; + bool bSharedPhysicalMaterialChanged = false; + bool bTileLandscapeMaterialChanged = false; + bool bTileLandscapeHoleMaterialChanged = false; + bool bTilePhysicalMaterialChanged = false; + bool bCreatedMap = false; + bool bCreatedTileActor = false; + bool bHeightLayerDataChanged = false; + bool bCustomLayerDataChanged = false; + + // ---------------------------------------------------- + // Calculate Tile location and landscape offset + // ---------------------------------------------------- + FTransform LandscapeTransform, NewTileTransform; + FIntPoint TileLoc; + + // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate + // for any landscape shifts due to section base aligment offsets. + CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeTransform, NewTileTransform, TileLoc); + + // ---------------------------------------------------- + // Find or create *shared* landscape + // ---------------------------------------------------- + + ALandscape* SharedLandscapeActor = nullptr; + bool bCreatedSharedLandscape = false; + + if (bRequiresSharedLandscape) + { + // Streaming proxy tiles always require a "shared landscape" that contains the + // various landscape properties to be shared amongst all the tiles. + AActor* FoundActor = nullptr; + SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, SharedLandscapeActorName, FoundActor); + + bool bIsValidSharedLandscape = IsValid(SharedLandscapeActor); + + if (bIsValidSharedLandscape) + { + // We have a possible valid shared landscape. Check whether it is compatible with the Houdini volume. + bool bIsCompatible = IsLandscapeInfoCompatible( + SharedLandscapeActor->GetLandscapeInfo(), + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection); + bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); + if (!bIsCompatible) + { + // Current landscape actor is not compatible. Destroy it. + SharedLandscapeActor->Destroy(); + SharedLandscapeActor = nullptr; + bIsValidSharedLandscape = false; + } + } + + if (!bIsValidSharedLandscape) + { + // Create and configure the main landscape actor. + // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos + SharedLandscapeActor = InWorld->SpawnActor(); + if (SharedLandscapeActor) + { + CreatedUntrackedOutputs.Add( SharedLandscapeActor ); + + // NOTE that share landscape is always located at the origin, but not the tile actors. The + // tiles are properly transformed. + SharedLandscapeActor->SetActorTransform(LandscapeTransform); + // If we working with landscape tiles, this actor will become the "Main landscape" actor but + // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. + SharedLandscapeActor->bCanHaveLayersContent = false; + SharedLandscapeActor->ComponentSizeQuads = NumQuadsPerLandscapeSection*NumSectionPerLandscapeComponent; + SharedLandscapeActor->NumSubsections = NumSectionPerLandscapeComponent; + SharedLandscapeActor->SubsectionSizeQuads = NumQuadsPerLandscapeSection; + SharedLandscapeActor->SetLandscapeGuid( FGuid::NewGuid() ); + SharedLandscapeActor->bCastStaticShadow = false; + for (const auto& ImportLayerInfo : LayerInfos) + { + SharedLandscapeActor->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(ImportLayerInfo.LayerInfo)); + } + SharedLandscapeActor->CreateLandscapeInfo(); + bCreatedSharedLandscape = true; + + // Ensure the landscape actor name and label matches `LandscapeActorName`. + FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, SharedLandscapeActorName); + SharedLandscapeActor->MarkPackageDirty(); + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(InWorld->GetPathName()) ); + return false; + } + } + else + { + // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Reusing existing shared landscape...")); + } + } + + if (SharedLandscapeActor) + { + // Ensure the existing landscape actor transform is correct. + SharedLandscapeActor->SetActorTransform(LandscapeTransform); + + bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; + bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; + if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + { + DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + } + + if (bSharedLandscapeMaterialChanged) + { + SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + + } + if (bSharedLandscapeHoleMaterialChanged) + { + SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + + } + + bSharedPhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? (SharedLandscapeActor->DefaultPhysMaterial != LandscapePhysicalMaterial) : false; + if (bSharedPhysicalMaterialChanged) + { + DoPreEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); + SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; + //SharedLandscapeActor->ChangedPhysMaterial(); + } + } + + // ---------------------------------------------------- + // Find Landscape actor / tile + // ---------------------------------------------------- + + // Find an actor with the given name. The TileWorld and TileLevel returned should be + // used to spawn the new actor, if the actor itself could not be found. + //bool bCreatedPackage = false; + // TileActor = FindExistingLandscapeActor( + // InWorld, InOutput, ValidLandscapes, + // UnrealLandscapeSizeX, UnrealLandscapeSizeY, LandscapeTileActorName, + // LevelPath, TileWorld, TileLevel, bCreatedPackage); + + // Currently the Temp Cook mode is not concerned with creating packages. This will, at the time of writing, + // exclusively be dealt with during Bake mode so don't bother with searching / creating other packages. + + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (IsValid(HAC)) + { + TileWorld = HAC->GetWorld(); + TileLevel = HAC->GetComponentLevel(); + } + else + { + TileWorld = InWorld; + TileLevel = InWorld->PersistentLevel; + } + + check(TileWorld); + check(TileLevel); + + AActor* FoundActor = nullptr; + if (InPackageParams.PackageMode == EPackageMode::Bake) + { + // When baking, See if we can find any landscape / proxy actors for this tile in the TileLevel. + // If we find any actors that match the name but not the type, or the actors are pending kill, then + // rename them so that we can spawn new actors. + switch (TileActorType) + { + case LandscapeActorType::LandscapeActor: + TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); + break; + case LandscapeActorType::LandscapeStreamingProxy: + TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); + break; + default: + TileActor = nullptr; + } + } + else + { + // In temp mode, only consider our previous output landscapes, + // or our input landscapes that have the "update input landscape" option enabled + ALandscapeProxy* FoundLandscapeProxy = nullptr; + + // Try to see if we have an input landscape that matches the size of the current HGPO + for (int nIdx = 0; nIdx < InputLandscapesToUpdate.Num(); nIdx++) + { + ALandscapeProxy* CurrentInputLandscape = InputLandscapesToUpdate[nIdx]; + if (!CurrentInputLandscape) + continue; + + ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); + if (!CurrentInfo) + continue; + + int32 InputMinX = 0; + int32 InputMinY = 0; + int32 InputMaxX = 0; + int32 InputMaxY = 0; + CurrentInfo->GetLandscapeExtent(InputMinX, InputMinY, InputMaxX, InputMaxY); + + // If the full size matches, we'll update that input landscape + bool SizeMatch = false; + if ((InputMaxX - InputMinX + 1) == UnrealTileSizeX && (InputMaxY - InputMinY + 1) == UnrealTileSizeY) + SizeMatch = true; + + // HF and landscape don't match, try another one + if (!SizeMatch) + continue; + + // Replace FoundLandscape by that input landscape + FoundLandscapeProxy = CurrentInputLandscape; + + // We've found a valid input landscape, remove it from the input array so we don't try to update it multiple times + InputLandscapesToUpdate.RemoveAt(nIdx); + break; + } + + if (!FoundLandscapeProxy) + { + // Try to see if we can reuse one of our previous output landscape. + // Keep track of the previous cook's landscapes + TMap& OldOutputObjects = InOutput->GetOutputObjects(); + for (auto& CurrentLandscape : OldOutputObjects) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentLandscape.Value.OutputObject); + if (!LandscapePtr) + continue; + + FoundLandscapeProxy = LandscapePtr->GetRawPtr(); + if (!FoundLandscapeProxy) + { + // We may need to manually load the object + //OldLandscapeProxy = LandscapePtr->GetSoftPtr().LoadSynchronous(); + FoundLandscapeProxy = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); + } + + if (!IsValid(FoundLandscapeProxy)) + continue; + + // We need to make sure that this landscape is not one of our input landscape + // This would happen if we were previously updating it, but just turned the option off + // In that case, the landscape would be in both our inputs and outputs, + // but with the "Update Input Data" option off + if (InAllInputLandscapes.Contains(FoundLandscapeProxy)) + { + FoundLandscapeProxy = nullptr; + continue; + } + + // If we found a possible candidate, make sure that its size matches ours as we can only update a landscape tile of the same size + if (!IsLandscapeTileCompatible( + FoundLandscapeProxy, + UnrealTileSizeX-1, + UnrealTileSizeY-1, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection)) + { + FoundLandscapeProxy = nullptr; + continue; + } + + if (SharedLandscapeActor) + { + if (FoundLandscapeProxy->GetLandscapeGuid() != SharedLandscapeActor->GetLandscapeGuid()) + { + FoundLandscapeProxy = nullptr; + continue; + } + } + + // TODO: we probably need to do some more checks with tiled landscapes as well? + + // We found a valid Candidate! + if (FoundLandscapeProxy) + break; + } + } + + if (IsValid(FoundLandscapeProxy)) + { + TileActor = FoundLandscapeProxy; + if (TileActor->GetName() != LandscapeTileActorName) + { + // Ensure the TileActor is named correctly + FHoudiniEngineUtils::SafeRenameActor(TileActor, LandscapeTileActorName); + } + } + } + + // NOTE: We don't need to delete old landscape tiles (FoundActor != TileActor) here. That is an old + // output that should get cleaned up automatically. + + if (IsValid(TileActor)) + { + check(!(TileActor->IsPendingKill())); + + // ---------------------------------------------------- + // Check landscape compatibility + // ---------------------------------------------------- + + bool bIsCompatible = IsLandscapeTileCompatible( + TileActor, + UnrealTileSizeX-1, + UnrealTileSizeY-1, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection); + + bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(TileActor, TileActorType); + + if (!bIsCompatible) + { + // Can't reuse this tile actor since the landscape dimensions doesn't match or the actor type has changed. + if (TileActor->IsA()) + { + // This landscape tile needs to be unregistered from the landscape info. + ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); + if (IsValid(LandscapeInfo)) + { + LandscapeInfo->UnregisterActor(TileActor); + } + } + TileActor->Destroy(); + TileActor = nullptr; + } + } + + // ---------------------------------------------------- + // Create or update landscape / tile. + // ---------------------------------------------------- + // Note that a single heightfield generated in Houdini can be treated + // as either a landscape tile (LandscapeStreamingProxy) or a standalone + // landscape (ALandscape). This determination is made purely from user specified + // attributes. No "clever logic" in here, please! + + ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; + ALandscape* CachedLandscapeActor = nullptr; + ULandscapeInfo *LandscapeInfo; + + + if (!TileActor) + { + // Create a new Landscape tile in the TileWorld + TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( + IntHeightData, LayerInfos, TileTransform, + UnrealTileSizeX, UnrealTileSizeY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, + LandscapeTileActorName, + TileActorType, + SharedLandscapeActor, + TileWorld, + TileLevel, + InPackageParams); + + if (!TileActor || !TileActor->IsValidLowLevel()) + return false; + + // Update the visibility mask / layer if we have any + for (auto CurrLayerInfo : LayerInfos) + { + if (CurrLayerInfo.LayerInfo && CurrLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + TileActor->VisibilityLayer = CurrLayerInfo.LayerInfo; + TileActor->VisibilityLayer->bNoWeightBlend = true; + TileActor->VisibilityLayer->AddToRoot(); + } + } + + LandscapeInfo = TileActor->GetLandscapeInfo(); + + bCreatedTileActor = true; + bTileLandscapeMaterialChanged = true; + bTileLandscapeHoleMaterialChanged = true; + bTilePhysicalMaterialChanged = true; + bHeightLayerDataChanged = true; + bCustomLayerDataChanged = true; + } + else + { + LandscapeInfo = TileActor->GetLandscapeInfo(); + + // Always update the transform, even if the HGPO transform hasn't changed, + // If we change the number of tiles, or switch from outputting single tile to multiple, + // then its fairly likely that the unreal transform has changed even if the + // Houdini Transform remained the same + if (!TileActor->GetTransform().Equals(TileTransform)) + { + // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Updating tile transform: %s"), *(TileTransform.ToString())); + TileActor->SetActorTransform(TileTransform); + TileActor->SetAbsoluteSectionBase(TileLoc); +#if WITH_EDITOR + GEngine->BroadcastOnActorMoved(TileActor); +#endif + LandscapeInfo->RecreateLandscapeInfo(InWorld,true); + } + + // Update existing landscape / tile + if (SharedLandscapeActor && TileActorType == LandscapeActorType::LandscapeStreamingProxy) + { + TileActor->FixupSharedData(SharedLandscapeActor); + + // This is a tile with a shared landscape. + // Check whether the LandscapeActor changed. If so, update the proxy's shared properties. + CachedStreamingProxyActor = Cast(TileActor); + if (SharedLandscapeActor) + { + if (CachedStreamingProxyActor) + bModifiedLandscapeActor = CachedStreamingProxyActor->LandscapeActor != SharedLandscapeActor; + else + bModifiedLandscapeActor = true; + + if (bModifiedLandscapeActor) + { + CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; + // We need to force a state update through PostEditChangeProperty here in order to initialize + // since we're about to perform additional data updates on this tile. + DoPostEditChangeProperty(CachedStreamingProxyActor, "LandscapeActor"); + } + } + else + { + CachedStreamingProxyActor->LandscapeActor = nullptr; + } + + } + else + { + // This is a standalone tile / landscape actor. + CachedLandscapeActor = Cast(TileActor); + } + + ULandscapeInfo* PreviousInfo = TileActor->GetLandscapeInfo(); + if (!PreviousInfo) + return false; + + FIntRect BoundingRect = TileActor->GetBoundingRect(); + FIntPoint SectionOffset = TileActor->GetSectionBaseOffset(); + + // Landscape region to update + const int32 MinX = TileLoc.X; + const int32 MaxX = TileLoc.X + UnrealTileSizeX - 1; + const int32 MinY = TileLoc.Y; + const int32 MaxY = TileLoc.Y + UnrealTileSizeY - 1; + + // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. + // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools + // though the *Accessors do additional things like update normals and foliage. + + // Update height if it has been changed. + if (Heightfield->bHasGeoChanged) + { + // It is important to update the heightmap through the this since it will properly + // update normals and foliage. + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); + + bHeightLayerDataChanged = true; + } + + // Update the layers on the landscape. + for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) + { + FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + + if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + TileActor->VisibilityLayer = NextUpdatedLayerInfo.LayerInfo; + TileActor->VisibilityLayer->bNoWeightBlend = true; + TileActor->VisibilityLayer->AddToRoot(); + } + + bCustomLayerDataChanged = true; + } + + bModifiedLandscapeActor = true; + } + + // ---------------------------------------------------- + // Update tile materials + // ---------------------------------------------------- + // TODO: These material updates can possibly be skipped if we have already performed this + // check on a SharedLandscape. + bTileLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (TileActor->GetLandscapeMaterial() != LandscapeMaterial) : false; + bTileLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (TileActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); + + if (bTileLandscapeMaterialChanged) + TileActor->LandscapeMaterial = LandscapeMaterial; + + if (bTileLandscapeHoleMaterialChanged) + TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + + bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; + if (bTilePhysicalMaterialChanged) + { + DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); + TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; + //TileActor->ChangedPhysMaterial(); + } + + // ---------------------------------------------------- + // Apply actor tags + // ---------------------------------------------------- + + // See if we have unreal_tag_ attribute + TArray Tags; + if (TileActor && FHoudiniEngineUtils::GetUnrealTagAttributes(GeoId, PartId, Tags)) + { + TileActor->Tags = Tags; + } + + // ---------------------------------------------------- + // Update actor states based on data updates + // ---------------------------------------------------- + // Based on ALandscape and ALandscapeStreamingProxy PostEditChangeProperty() implementations, + // effect appropriate state updates based on the property updates that was performed in + // the above code. + + if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + { + check(SharedLandscapeActor); + DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + } + + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + { + check(TileActor); + // Tile material changes are only processed if it wasn't already done for a shared + // landscape since the shared landscape should have already propagated the changes to associated proxies. + DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); + } + + if (bSharedPhysicalMaterialChanged) + { + check(SharedLandscapeActor); + DoPostEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); + } + + if (bTilePhysicalMaterialChanged) + { + check(TileActor); + DoPostEditChangeProperty(TileActor, "DefaultPhysMaterial"); + } + + if (bModifiedSharedLandscapeActor) + { + SharedLandscapeActor->PostEditChange(); + } + + if (bModifiedLandscapeActor) + { + TileActor->PostEditChange(); + } + + { + FLandscapeEditDataInterface LandscapeEdit(TileActor->GetLandscapeInfo()); + LandscapeEdit.RecalculateNormals(); + } + + if (LandscapeInfo) + { + LandscapeInfo->RecreateLandscapeInfo(InWorld, true); + } + + // Add objects to the HAC output. + SetLandscapeActorAsOutput( + InOutput, + InAllInputLandscapes, + OutputAttributes, + OutputTokens, + SharedLandscapeActor, + SharedLandscapeActorParent, + bCreatedSharedLandscape, + HeightfieldIdentifier, + TileActor, + InPackageParams.PackageMode); + + return true; +} + +bool +FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( + const ULandscapeInfo* LandscapeInfo, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection + ) +{ + if (!IsValid(LandscapeInfo)) + return false; + + + if (LandscapeInfo->ComponentNumSubsections != InNumSectionsPerComponent) + return false; + + if (LandscapeInfo->SubsectionSizeQuads != InNumQuadsPerSection) + return false; + + return true; +} + +bool +FHoudiniLandscapeTranslator::IsLandscapeTileCompatible( + const ALandscapeProxy* TileActor, + const int32 InTileSizeX, + const int32 InTileSizeY, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection +) +{ + check(TileActor); + + // NOTE: We can't compare landscape extents here since the Houdini only knows about the size for single tile. + // and LandscapeInfo will only return extents for the *loaded* landscape tiles. + + // TODO: Add more robust checks to determine landscape compatibility. + + if (!IsLandscapeInfoCompatible(TileActor->GetLandscapeInfo(), InNumSectionsPerComponent, InNumQuadsPerSection)) + return false; + + const FIntRect Bounds = TileActor->GetBoundingRect(); + const FIntPoint Size = Bounds.Size(); + if (Size.X != InTileSizeX && Size.Y != InTileSizeY) + return false; + + return true; +} + + +bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, LandscapeActorType ActorType) +{ + if (!IsValid(Actor)) + return false; + + switch (ActorType) + { + case LandscapeActorType::LandscapeActor: + return Actor->IsA(); + break; + case LandscapeActorType::LandscapeStreamingProxy: + return Actor->IsA(); + break; + default: + break; + } + + return false; +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage, + const EPackageMode& InPackageMode) +{ + if (InPackageMode == EPackageMode::Bake) + return FindExistingLandscapeActor_Bake(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); + else + return FindExistingLandscapeActor_Temp(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + bCreatedPackage = false; + + // // Locate landscape proxy actor when running in baked mode + // AActor* FoundActor = nullptr; + ALandscapeProxy* OutActor = nullptr; + // OutActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); + // // OutActor = FHoudiniEngineUtils::FindActorInWorld(InWorld, FName(InActorName)); + // if (FoundActor && FoundActor != OutActor) + // FoundActor->Destroy(); // nuke it! + // + // if (OutActor) + // { + // // TODO: make sure that the found is actor is actually assigned to the level defined by package path. + // // If the found actor is not from that level, it should be moved there. + // + // OutWorld = OutActor->GetWorld(); + // OutLevel = OutActor->GetLevel(); + // } + // else + { + // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. + bool bActorInWorld = false; + const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + InWorld, + InPackagePath, + true, + OutWorld, + OutLevel, + bCreatedPackage, + bActorInWorld); + + if (!bResult) + { + return nullptr; + } + + // if (!bActorInWorld) + // { + // // The OutLevel is not present in the current world which means we might + // // still find the tile actor in OutWorld. + OutActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); + // } + } + + return OutActor; +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + ALandscapeProxy* OutActor = nullptr; + FString ActorName = InActorName + TEXT("_Temp"); + TMap& PrevCookObjects = InOutput->GetOutputObjects(); + + OutWorld = InWorld; + OutLevel = InWorld->PersistentLevel; + + bCreatedPackage = false; + + // Find Landscape proxy for output when running in Temp mode + for(auto& PrevObject : PrevCookObjects) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(PrevObject.Value.OutputObject); + if (!LandscapePtr) + continue; + + OutActor = LandscapePtr->GetRawPtr(); + if (!OutActor) + { + // We may need to manually load the object + OutActor = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); + } + + if (!OutActor) + continue; + + // If we were updating the input landscape before, but arent anymore, + // we could still find it here in the output, ignore them now as we're only looking for previous output + if (ValidLandscapes.Contains(OutActor)) + continue; + + if (OutActor->GetName() != ActorName) + // This is not the droid we're looking for + continue; + + if (OutActor->IsPendingKill()) + { + FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + "_pendingkill"); + continue; + } + + // If we found a possible candidate, make sure that its size matches ours + // as we can only update a landscape of the same size + ULandscapeInfo* PreviousInfo = OutActor->GetLandscapeInfo(); + if (PreviousInfo) + { + int32 PrevMinX = 0; + int32 PrevMinY = 0; + int32 PrevMaxX = 0; + int32 PrevMaxY = 0; + PreviousInfo->GetLandscapeExtent(PrevMinX, PrevMinY, PrevMaxX, PrevMaxY); + + if ((PrevMaxX - PrevMinX + 1) == UnrealLandscapeSizeX && (PrevMaxY - PrevMinY + 1) == UnrealLandscapeSizeY) + { + // The size matches, we can reuse the old landscape. + break; + } + else + { + // We can't reuse this actor. The dimensions does not match. + // We need to rename this actor in order to create a new one with the specified name. + FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + TEXT("_old") ); + OutActor = nullptr; + } + } + } + + return OutActor; +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor, + const EPackageMode InPackageMode) +{ + if (InPackageMode == EPackageMode::Bake) + return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); + else + return SetLandscapeActorAsOutput_Temp(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor) +{ + // We are in bake mode. No outputs to register / add here. + // Do nothing, for now. +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedSharedLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor) +{ + // The main landscape is a special case here. It cannot be registered with the + // output object here, since it is possibly shared by *multiple* outputs so + // we have to deal with the attached and cleanup of the actor manually. + if (bCreatedSharedLandscape && IsValid(SharedLandscapeActorParent)) + { + AttachActorToHAC(InOutput, SharedLandscapeActor); + } + + // TODO: The OutputObject cleanup being performed here should really be part of + // the output object itself (or at the very least be encapsulated in a reusable + // static function somewhere) so that individual output objects can be cleaned + // when necessary without having to duplicate code when its needed. + + // Cleanup any stale output objects + TMap& Outputs = InOutput->GetOutputObjects(); + TArray StaleOutputs; + for (auto& Elem : Outputs) + { + UHoudiniLandscapePtr* LandscapePtr = nullptr; + bool bIsStale = false; + + if (!(Elem.Key == Identifier)) + { + // Identifiers doesn't match so this is definitely a stale output. + StaleOutputs.Add(Elem.Key); + bIsStale = true; + } + + LandscapePtr = Cast(Elem.Value.OutputObject); + if (LandscapePtr) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + + if (LandscapeProxy) + { + // We shouldn't destroy any input landscape, + // or the landscape that we are currently trying to set as output.. + if (!InAllInputLandscapes.Contains(LandscapeProxy) && (LandscapeProxy != LandscapeActor)) + { + // This landscape proxy either doesn't match the landscape identifier + // or it doesn't match the actor we're about to output. Either way, + // get rid of it. + LandscapeProxy->Destroy(); + } + } + } + + if (bIsStale) + { + Elem.Value.OutputObject = nullptr; + } + } + + for (FHoudiniOutputObjectIdentifier& StaleOutput : StaleOutputs) + { + Outputs.Remove(StaleOutput); + } + + + // Send a landscape pointer back to the Output Object for this landscape tile. + FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); + UHoudiniLandscapePtr* LandscapePtr = NewObject(InOutput); + LandscapePtr->SetSoftPtr(LandscapeActor); + OutputObj.OutputObject = LandscapePtr; + OutputObj.CachedAttributes = OutputAttributes; + OutputObj.CachedTokens = OutputTokens; +} + + +bool +FHoudiniLandscapeTranslator::AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor) +{ + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (IsValid(HAC)) + { + InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + return true; + } + return false; +} + +FString +FHoudiniLandscapeTranslator::GetActorNameSuffix(const EPackageMode& InPackageMode) +{ + if(InPackageMode == EPackageMode::CookToTemp) + return "_Temp"; + else + return FString(); +} + +void +FHoudiniLandscapeTranslator::DoPreEditChangeProperty(UObject* Obj, FName PropertyName) +{ + Obj->PreEditChange(FindFProperty(Obj->GetClass(), PropertyName)); +} + +void +FHoudiniLandscapeTranslator::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) +{ + FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); + Obj->PostEditChangeProperty(Evt); +} + +bool +FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + const TArray< float >& HeightfieldFloatValues, + const FHoudiniVolumeInfo& HeightfieldVolumeInfo, + const int32& FinalXSize, const int32& FinalYSize, + float FloatMin, float FloatMax, + TArray< uint16 >& IntHeightData, + FTransform& LandscapeTransform, + const bool& NoResize) +{ + IntHeightData.Empty(); + LandscapeTransform.SetIdentity(); + + // HF sizes needs an X/Y swap + // NOPE.. not anymore + int32 HoudiniXSize = HeightfieldVolumeInfo.YLength; + int32 HoudiniYSize = HeightfieldVolumeInfo.XLength; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + // Test for potential special cases... + // Just print a warning for now + if (HeightfieldVolumeInfo.MinX != 0) + HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min X is not zero.")); + + if (HeightfieldVolumeInfo.MinY != 0) + HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min Y is not zero.")); + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to uint16 using doubles to get the maximum precision during the conversion + //-------------------------------------------------------------------------------------------------- + + FTransform CurrentVolumeTransform = HeightfieldVolumeInfo.Transform; + + // The ZRange in Houdini (in m) + double MeterZRange = (double)(FloatMax - FloatMin); + + // The corresponding unreal digit range (as unreal uses uint16, max is 65535) + // We may want to not use the full range in order to be able to sculpt the landscape past the min/max values after. + const double dUINT16_MAX = (double)UINT16_MAX; + double DigitZRange = 49152.0; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution) + DigitZRange = dUINT16_MAX - 1.0; + + // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down + double DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); + + // The factor used to convert from Houdini's ZRange to the desired digit range + double ZSpacing = (MeterZRange != 0.0) ? (DigitZRange / MeterZRange) : 0.0; + + // Changes these values if the user wants to loose a lot of precision + // just to keep the same transform as the landscape input + bool bUseDefaultUE4Scaling = false; + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) + bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + + if (bUseDefaultUE4Scaling) + { + //Check that our values are compatible with UE4's default scale values + if (FloatMin < -256.0f || FloatMin > 256.0f || FloatMax < -256.0f || FloatMax > 256.0f) + { + // Warn the user that the landscape conversion will have issues + // invite him to change that setting + HOUDINI_LOG_WARNING( + TEXT("The heightfield's min and max height values are too large for being used with the \"Use Default UE4 scaling\" option.\n \ + The generated Heightfield will likely be incorrectly converted to landscape unless you disable that option in the project settings and recook the asset.")); + } + + DigitZRange = dUINT16_MAX - 1.0; + DigitCenterOffset = 0; + + // Default unreal landscape scaling is -256m:256m at Scale = 100 + // We need to apply the scale back to + FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; + FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; + MeterZRange = (double)(FloatMax - FloatMin); + + ZSpacing = ((double)DigitZRange) / MeterZRange; + } + + // Converting the data from Houdini to Unreal + // For correct orientation in unreal, the point matrix has to be transposed. + IntHeightData.SetNumUninitialized(SizeInPoints); + + int32 nUnreal = 0; + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y + int32 nHoudini = nY + nX * HoudiniYSize; + + // Get the double values in [0 - ZRange] + double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; + + // Then convert it to [0 - DesiredRange] and center it + DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; + IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); + } + } + + //-------------------------------------------------------------------------------------------------- + // 2. Resample / Pad the int data so that if fits unreal size requirements + //-------------------------------------------------------------------------------------------------- + + // UE has specific size requirements for landscape, + // so we might need to pad/resample the heightfield data + FVector LandscapeResizeFactor = FVector::OneVector; + FVector LandscapePositionOffsetInPixels = FVector::ZeroVector; + if (!NoResize) + { + // Try to resize the data + if (!FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( + IntHeightData, + HoudiniXSize, HoudiniYSize, FinalXSize, FinalYSize, + LandscapeResizeFactor, LandscapePositionOffsetInPixels)) + return false; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Calculating the proper transform for the landscape to be sized and positionned properly + //-------------------------------------------------------------------------------------------------- + + // Scale: + // Calculating the equivalent scale to match Houdini's Terrain Size in Unreal + FVector LandscapeScale; + + // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing + LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; + LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Y * 2.0f; + + // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini + // Unreal has a default Z range is 512m for a scale of a 100% + LandscapeScale.Z = (float)((double)(dUINT16_MAX / DigitZRange) * MeterZRange / 512.0); + if (bUseDefaultUE4Scaling) + LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Z * 2.0f; + LandscapeScale *= 100.f; + + // If the data was resized and not expanded, we need to modify the landscape's scale + LandscapeScale *= LandscapeResizeFactor; + + // Don't allow a zero scale, as this results in divide by 0 operations in FMatrix::InverseFast in the landscape component. + if (FMath::IsNearlyZero(LandscapeScale.Z)) + LandscapeScale.Z = 1.0f; + + // We'll use the position from Houdini, but we will need to offset the Z Position to center the + // values properly as the data has been offset by the conversion to uint16 + FVector LandscapePosition = CurrentVolumeTransform.GetLocation(); + //LandscapePosition.Z = 0.0f; + + // We need to calculate the position offset so that Houdini and Unreal have the same Zero position + // In Unreal, zero has a height value of 32768. + // These values are then divided by 128 internally, and then multiplied by the Landscape's Z scale + // ( DIGIT - 32768 ) / 128 * ZScale = ZOffset + + // We need the Digit (Unreal) value of Houdini's zero for the scale calculation + // ( float and int32 are used for this because 0 might be out of the landscape Z range! + // when using the full range, this would cause an overflow for a uint16!! ) + float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); + float ZOffset = -(HoudiniZeroValueInDigit - 32768.0f) / 128.0f * LandscapeScale.Z; + + LandscapePosition.Z += ZOffset; + + // If we have padded the data when resizing the landscape, we need to offset the position because of + // the added values on the topLeft Corner of the Landscape + if (LandscapePositionOffsetInPixels != FVector::ZeroVector) + { + FVector LandscapeOffset = LandscapePositionOffsetInPixels * LandscapeScale; + LandscapeOffset.Z = 0.0f; + + LandscapePosition += LandscapeOffset; + } + + /* + FTransform TempTransform; + TempTransform.SetIdentity(); + { + // Houdini Pivot (center of the Landscape) + FVector HoudiniPivot = FVector((FinalXSize-1) * 100.0f / 2.0f, (FinalYSize-1) * 100.0f / 2.0f, 0.0f); + + // Center the landscape + FVector CenterLocation = LandscapePosition - HoudiniPivot; + + // Rotate the vector using the H rotation + // We need to compensate for the "default" HF Transform + FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); + Rotator.Yaw -= 90.0f; + Rotator.Roll += 90.0f; + FVector RotatedLocation = Rotator.RotateVector(CenterLocation); + + FQuat LandscapeRotation = FQuat(Rotator) * FQuat::Identity; + + // Return to previous origin + FVector Uncentered = RotatedLocation + HoudiniPivot; + TempTransform = FTransform(LandscapeRotation, Uncentered, LandscapeScale); + } + + LandscapeTransform = TempTransform; + */ + + // We can now set the Landscape position + LandscapeTransform.SetLocation(LandscapePosition); + LandscapeTransform.SetScale3D(LandscapeScale); + + // Rotate the vector using the H rotation + FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); + // We need to compensate for the "default" HF Transform + Rotator.Yaw -= 90.0f; + Rotator.Roll += 90.0f; + + // Only rotate if the rotator is far from zero + if(!Rotator.IsNearlyZero()) + LandscapeTransform.SetRotation(FQuat(Rotator)); + + return true; +} + +template +TArray ResampleData(const TArray& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight) +{ + TArray Result; + Result.Empty(NewWidth * NewHeight); + Result.AddUninitialized(NewWidth * NewHeight); + + const float XScale = (float)(OldWidth - 1) / (NewWidth - 1); + const float YScale = (float)(OldHeight - 1) / (NewHeight - 1); + for (int32 Y = 0; Y < NewHeight; ++Y) + { + for (int32 X = 0; X < NewWidth; ++X) + { + const float OldY = Y * YScale; + const float OldX = X * XScale; + const int32 X0 = FMath::FloorToInt(OldX); + const int32 X1 = FMath::Min(FMath::FloorToInt(OldX) + 1, OldWidth - 1); + const int32 Y0 = FMath::FloorToInt(OldY); + const int32 Y1 = FMath::Min(FMath::FloorToInt(OldY) + 1, OldHeight - 1); + const T& Original00 = Data[Y0 * OldWidth + X0]; + const T& Original10 = Data[Y0 * OldWidth + X1]; + const T& Original01 = Data[Y1 * OldWidth + X0]; + const T& Original11 = Data[Y1 * OldWidth + X1]; + Result[Y * NewWidth + X] = FMath::BiLerp(Original00, Original10, Original01, Original11, FMath::Fractional(OldX), FMath::Fractional(OldY)); + } + } + + return Result; +} + +template +void ExpandData(T* OutData, const T* InData, + int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, + int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY) +{ + const int32 OldWidth = OldMaxX - OldMinX + 1; + const int32 OldHeight = OldMaxY - OldMinY + 1; + const int32 NewWidth = NewMaxX - NewMinX + 1; + const int32 NewHeight = NewMaxY - NewMinY + 1; + const int32 OffsetX = NewMinX - OldMinX; + const int32 OffsetY = NewMinY - OldMinY; + + for (int32 Y = 0; Y < NewHeight; ++Y) + { + const int32 OldY = FMath::Clamp(Y + OffsetY, 0, OldHeight - 1); + + // Pad anything to the left + const T PadLeft = InData[OldY * OldWidth + 0]; + for (int32 X = 0; X < -OffsetX; ++X) + { + OutData[Y * NewWidth + X] = PadLeft; + } + + // Copy one row of the old data + { + const int32 X = FMath::Max(0, -OffsetX); + const int32 OldX = FMath::Clamp(X + OffsetX, 0, OldWidth - 1); + FMemory::Memcpy(&OutData[Y * NewWidth + X], &InData[OldY * OldWidth + OldX], FMath::Min(OldWidth, NewWidth) * sizeof(T)); + } + + const T PadRight = InData[OldY * OldWidth + OldWidth - 1]; + for (int32 X = -OffsetX + OldWidth; X < NewWidth; ++X) + { + OutData[Y * NewWidth + X] = PadRight; + } + } +} + +template +TArray ExpandData(const TArray& Data, + int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, + int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY, + int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr) +{ + const int32 NewWidth = NewMaxX - NewMinX + 1; + const int32 NewHeight = NewMaxY - NewMinY + 1; + + TArray Result; + Result.Empty(NewWidth * NewHeight); + Result.AddUninitialized(NewWidth * NewHeight); + + ExpandData(Result.GetData(), Data.GetData(), + OldMinX, OldMinY, OldMaxX, OldMaxY, + NewMinX, NewMinY, NewMaxX, NewMaxY); + + // Return the padding so we can offset the terrain position after + if (PadOffsetX) + *PadOffsetX = NewMinX; + + if (PadOffsetY) + *PadOffsetY = NewMinY; + + return Result; +} + +bool +FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( + TArray& HeightData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY, + FVector& LandscapeResizeFactor, + FVector& LandscapePositionOffset) +{ + LandscapeResizeFactor = FVector::OneVector; + LandscapePositionOffset = FVector::ZeroVector; + + if (HeightData.Num() <= 4) + return false; + + if ((SizeX < 2) || (SizeY < 2)) + return false; + + // No need to resize anything + if (SizeX == NewSizeX && SizeY == NewSizeY) + return true; + + // Always resample, for now. We may enable padding functionality again at some point via + // a plugin setting. + bool bForceResample = true; + bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); + + TArray NewData; + if (!bResample) + { + // Expanding the data by padding + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + + const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; + const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; + + // Store the offset in pixel due to the padding + int32 PadOffsetX = 0; + int32 PadOffsetY = 0; + + // Expanding the Data + NewData = ExpandData( + HeightData, 0, 0, SizeX - 1, SizeY - 1, + -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1, + &PadOffsetX, &PadOffsetY); + + // We will need to offset the landscape position due to the value added by the padding + LandscapePositionOffset.X = (float)PadOffsetX; + LandscapePositionOffset.Y = (float)PadOffsetY; + + // Notify the user that the data was padded + HOUDINI_LOG_WARNING( + TEXT("Landscape data was padded from ( %d x %d ) to ( %d x %d )."), + SizeX, SizeY, NewSizeX, NewSizeY); + } + else + { + // Resampling the data + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + NewData = ResampleData(HeightData, SizeX, SizeY, NewSizeX, NewSizeY); + + // The landscape has been resized, we'll need to take that into account when sizing it + LandscapeResizeFactor.X = (float)SizeX / (float)NewSizeX; + LandscapeResizeFactor.Y = (float)SizeY / (float)NewSizeY; + LandscapeResizeFactor.Z = 1.0f; + + // Notify the user if the heightfield data was resized + HOUDINI_LOG_WARNING( + TEXT("Landscape data was resized from ( %d x %d ) to ( %d x %d )."), + SizeX, SizeY, NewSizeX, NewSizeY); + } + + // Replaces Old data with the new one + HeightData = NewData; + + return true; +} + + +bool +FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + const int32& HoudiniSizeX, const int32& HoudiniSizeY, + int32& UnrealSizeX, int32& UnrealSizeY, + int32& NumSectionsPerComponent, int32& NumQuadsPerSection) +{ + if ((HoudiniSizeX < 2) || (HoudiniSizeY < 2)) + return false; + + NumSectionsPerComponent = 1; + NumQuadsPerSection = 1; + UnrealSizeX = -1; + UnrealSizeY = -1; + + // Unreal's default sizes + int32 SectionSizes[] = { 7, 15, 31, 63, 127, 255 }; + int32 NumSections[] = { 1, 2 }; + + // Component count used to calculate the final size of the landscape + int32 ComponentsCountX = 1; + int32 ComponentsCountY = 1; + + // Lambda for clamping the number of component in X/Y + auto ClampLandscapeSize = [&]() + { + // Max size is either whole components below 8192 verts, or 32 components + ComponentsCountX = FMath::Clamp(ComponentsCountX, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); + ComponentsCountY = FMath::Clamp(ComponentsCountY, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); + }; + + // Try to find a section size and number of sections that exactly matches the dimensions of the heightfield + bool bFoundMatch = false; + for (int32 SectionSizesIdx = UE_ARRAY_COUNT(SectionSizes) - 1; SectionSizesIdx >= 0; SectionSizesIdx--) + { + for (int32 NumSectionsIdx = UE_ARRAY_COUNT(NumSections) - 1; NumSectionsIdx >= 0; NumSectionsIdx--) + { + int32 ss = SectionSizes[SectionSizesIdx]; + int32 ns = NumSections[NumSectionsIdx]; + + if (((HoudiniSizeX - 1) % (ss * ns)) == 0 && ((HoudiniSizeX - 1) / (ss * ns)) <= 32 && + ((HoudiniSizeY - 1) % (ss * ns)) == 0 && ((HoudiniSizeY - 1) / (ss * ns)) <= 32) + { + bFoundMatch = true; + NumQuadsPerSection = ss; + NumSectionsPerComponent = ns; + ComponentsCountX = (HoudiniSizeX - 1) / (ss * ns); + ComponentsCountY = (HoudiniSizeY - 1) / (ss * ns); + ClampLandscapeSize(); + break; + } + } + if (bFoundMatch) + { + break; + } + } + + if (!bFoundMatch) + { + // if there was no exact match, try increasing the section size until we encompass the whole heightmap + const int32 CurrentSectionSize = NumQuadsPerSection; + const int32 CurrentNumSections = NumSectionsPerComponent; + for (int32 SectionSizesIdx = 0; SectionSizesIdx < UE_ARRAY_COUNT(SectionSizes); SectionSizesIdx++) + { + if (SectionSizes[SectionSizesIdx] < CurrentSectionSize) + { + continue; + } + + const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); + const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); + if (ComponentsX <= 32 && ComponentsY <= 32) + { + bFoundMatch = true; + NumQuadsPerSection = SectionSizes[SectionSizesIdx]; + ComponentsCountX = ComponentsX; + ComponentsCountY = ComponentsY; + ClampLandscapeSize(); + break; + } + } + } + + if (!bFoundMatch) + { + // if the heightmap is very large, fall back to using the largest values we support + const int32 MaxSectionSize = SectionSizes[UE_ARRAY_COUNT(SectionSizes) - 1]; + const int32 MaxNumSubSections = NumSections[UE_ARRAY_COUNT(NumSections) - 1]; + const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), MaxSectionSize * MaxNumSubSections); + const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), MaxSectionSize * MaxNumSubSections); + + bFoundMatch = true; + NumQuadsPerSection = MaxSectionSize; + NumSectionsPerComponent = MaxNumSubSections; + ComponentsCountX = ComponentsX; + ComponentsCountY = ComponentsY; + ClampLandscapeSize(); + } + + if (!bFoundMatch) + { + // Using default size just to not crash.. + UnrealSizeX = 512; + UnrealSizeY = 512; + NumSectionsPerComponent = 1; + NumQuadsPerSection = 511; + ComponentsCountX = 1; + ComponentsCountY = 1; + } + else + { + // Calculating the desired size + int32 QuadsPerComponent = NumSectionsPerComponent * NumQuadsPerSection; + + UnrealSizeX = ComponentsCountX * QuadsPerComponent + 1; + UnrealSizeY = ComponentsCountY * QuadsPerComponent + 1; + } + + return bFoundMatch; +} + +const FHoudiniGeoPartObject* +FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return nullptr; + + if (InOutput->GetHoudiniGeoPartObjects().Num() < 1) + return nullptr; + + for (const FHoudiniGeoPartObject& HGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Volume) + continue; + + FHoudiniVolumeInfo CurVolumeInfo = HGPO.VolumeInfo; + if (!CurVolumeInfo.Name.Contains("height")) + continue; + + // We're only handling single values for now + if (CurVolumeInfo.TupleSize != 1) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume has an invalide tuple size!")); + return nullptr; + } + + // Terrains always have a ZSize of 1. + if (CurVolumeInfo.ZLength != 1) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume's z length is not 1!")); + return nullptr; + } + + // Values should be float + if (!CurVolumeInfo.bIsFloat) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output, the height volume's data is not stored as floats!")); + return nullptr; + } + + return &HGPO; + } + + return nullptr; +} + +void +FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers) +{ + FoundLayers.Empty(); + + // Get node id + HAPI_NodeId HeightFieldNodeId = Heightfield.GeoId; + + // We need the tile attribute if the height has it + bool bParentHeightfieldHasTile = false; + int32 HeightFieldTile = -1; + { + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + TArray< int32 > TileValues; + + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HeightFieldNodeId, Heightfield.PartId, "tile", AttribInfoTile, TileValues); + + if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) + { + HeightFieldTile = TileValues[0]; + bParentHeightfieldHasTile = true; + } + } + + for (TArray< FHoudiniGeoPartObject >::TConstIterator IterLayers(InOutput->GetHoudiniGeoPartObjects()); IterLayers; ++IterLayers) + { + const FHoudiniGeoPartObject & HoudiniGeoPartObject = *IterLayers; + + HAPI_NodeId NodeId = HoudiniGeoPartObject.GeoId; + if (NodeId == -1 || NodeId != HeightFieldNodeId) + continue; + + if (bParentHeightfieldHasTile) + { + int32 CurrentTile = -1; + + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + TArray TileValues; + + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "tile", AttribInfoTile, TileValues); + + + if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) + { + CurrentTile = TileValues[0]; + } + + // Does this layer come from the same tile as the height? + if ((CurrentTile != HeightFieldTile) || (CurrentTile == -1)) + continue; + } + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, HoudiniGeoPartObject.PartId, + &CurrentVolumeInfo)) + continue; + + // We're interesting in anything but height data + FString CurrentVolumeName; + FHoudiniEngineString(CurrentVolumeInfo.nameSH).ToFString(CurrentVolumeName); + if (CurrentVolumeName.Contains("height")) + continue; + + // We're only handling single values for now + if (CurrentVolumeInfo.tupleSize != 1) + continue; + + // Terrains always have a ZSize of 1. + if (CurrentVolumeInfo.zLength != 1) + continue; + + // Values should be float + if (CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + continue; + + FoundLayers.Add(&HoudiniGeoPartObject); + } +} + +bool +FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) +{ + OutFloatArr.Empty(); + OutFloatMin = 0.f; + OutFloatMax = 0.f; + + if (HGPO->Type != EHoudiniPartType::Volume) + return false; + + HAPI_VolumeInfo VolumeInfo; + FHoudiniApi::VolumeInfo_Init(&VolumeInfo); + + HAPI_Result Result = FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + HGPO->GeoId, HGPO->PartId, &VolumeInfo); + + // We're only handling single values for now + if (VolumeInfo.tupleSize != 1) + return false; + + // Terrains always have a ZSize of 1. + if (VolumeInfo.zLength != 1) + return false; + + // Values must be float + if (VolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + return false; + + if ((VolumeInfo.xLength < 2) || (VolumeInfo.yLength < 2)) + return false; + + const int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; + + OutFloatArr.SetNum(SizeInPoints); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetHeightFieldData( + FHoudiniEngine::Get().GetSession(), + HGPO->GeoId, HGPO->PartId, + OutFloatArr.GetData(), + 0, SizeInPoints), false); + + OutFloatMin = OutFloatArr[0]; + OutFloatMax = OutFloatMin; + + for (float NextFloatVal : OutFloatArr) + { + if (NextFloatVal > OutFloatMax) + { + OutFloatMax = NextFloatVal; + } + else if (NextFloatVal < OutFloatMin) + OutFloatMin = NextFloatVal; + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(const FHoudiniGeoPartObject& InHGPO, TArray& NonWeightBlendedLayerNames) +{ + // Check the attribute exists on primitive or detail + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; + if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_PRIM)) + Owner = HAPI_ATTROWNER_PRIM; + else if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_DETAIL)) + Owner = HAPI_ATTROWNER_DETAIL; + else + return false; + + // Get the values + HAPI_AttributeInfo AttribInfoNonWBLayer; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNonWBLayer); + TArray AttribValues; + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, AttribInfoNonWBLayer, AttribValues, 1, Owner); + + if (AttribValues.Num() <= 0) + return false; + + // Convert them to FString + for (int32 Idx = 0; Idx < AttribValues.Num(); Idx++) + { + TArray Tokens; + AttribValues[Idx].ParseIntoArray(Tokens, TEXT(" "), true); + + for (int32 n = 0; n < Tokens.Num(); n++) + NonWeightBlendedLayerNames.AddUnique(Tokens[n]); + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) +{ + // Check the attribute exists on primitive or detail + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; + if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_PRIM)) + Owner = HAPI_ATTROWNER_PRIM; + else if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_DETAIL)) + Owner = HAPI_ATTROWNER_DETAIL; + else + return false; + + // Check the value + HAPI_AttributeInfo AttribInfoUnitLayer; + FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer); + TArray< int32 > AttribValues; + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, AttribInfoUnitLayer, AttribValues, 1, Owner); + + if (AttribValues.Num() > 0 && AttribValues[0] == 1) + return true; + + return false; +} + +bool +FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( + const TArray& FoundLayers, + const FHoudiniGeoPartObject& Heightfield, + const int32& LandscapeXSize, const int32& LandscapeYSize, + const TMap& GlobalMinimums, + const TMap& GlobalMaximums, + TArray& OutLayerInfos, + bool bIsUpdate, + const FHoudiniPackageParams& InTilePackageParams, + const FHoudiniPackageParams& InLayerPackageParams, + TArray& OutCreatedPackages + ) +{ + OutLayerInfos.Empty(); + + // Get the names of all non weight blended layers + TArray NonWeightBlendedLayerNames; + FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(Heightfield, NonWeightBlendedLayerNames); + + // Used for exporting layer info objects (per landscape layer) + FHoudiniPackageParams LayerPackageParams = InLayerPackageParams; + // Used for exporting textures (per landscape tile) + FHoudiniPackageParams TilePackageParams = InTilePackageParams; + + // For Debugging, do we want to export layers as textures? + bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; + + // Try to create all the layers + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + for (TArray::TConstIterator IterLayers(FoundLayers); IterLayers; ++IterLayers) + { + const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers; + if (!LayerGeoPartObject) + continue; + + if (!LayerGeoPartObject->IsValid()) + continue; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerGeoPartObject->AssetId)) + continue; + + if (bIsUpdate && !LayerGeoPartObject->bHasGeoChanged) + { + continue; + } + + TArray FloatLayerData; + float LayerMin = 0; + float LayerMax = 0; + if (!FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(LayerGeoPartObject, FloatLayerData, LayerMin, LayerMax)) + continue; + + // No need to create flat layers as Unreal will remove them afterwards.. + if (LayerMin == LayerMax) + continue; + + const FHoudiniVolumeInfo& LayerVolumeInfo = LayerGeoPartObject->VolumeInfo; + + // Get the layer's name + FString LayerName = LayerVolumeInfo.Name; + const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerName); + + TilePackageParams.ObjectName = InTilePackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; + LayerPackageParams.ObjectName = InLayerPackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; + + if (bExportTexture) + { + // Create a raw texture export of the layer on this tile + FString TextureName = TilePackageParams.ObjectName + "_raw"; + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + LayerVolumeInfo.YLength, // Y and X inverted?? why? + LayerVolumeInfo.XLength, + FloatLayerData, + LayerMin, + LayerMax); + } + + // Check if that landscape layer has been marked as unit (range in [0-1] + if (IsUnitLandscapeLayer(*LayerGeoPartObject)) + { + LayerMin = 0.0f; + LayerMax = 1.0f; + } + else + { + // We want to convert the layer using the global Min/Max + if (GlobalMaximums.Contains(LayerName)) + LayerMax = GlobalMaximums[LayerName]; + + if (GlobalMinimums.Contains(LayerName)) + LayerMin = GlobalMinimums[LayerName]; + } + + // Get the layer package path + // FString LayerNameString = FString::Printf(TEXT("%s_%d"), LayerString.GetCharArray().GetData(), (int32)LayerGeoPartObject->PartId); + // LayerNameString = UPackageTools::SanitizePackageName(LayerNameString); + + // Build an object name for the current layer + LayerPackageParams.SplitStr = SanitizedLayerName; + + // Creating the ImportLayerInfo and LayerInfo objects + FLandscapeImportLayerInfo ImportLayerInfo(*LayerName); + + // See if the user has assigned a layer info object via attribute + UPackage * Package = nullptr; + ULandscapeLayerInfoObject* LayerInfo = GetLandscapeLayerInfoForLayer(*LayerGeoPartObject, *LayerName); + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + // No assignment, try to find or create a landscape layer info object for that layer + LayerInfo = FindOrCreateLandscapeLayerInfoObject(LayerName, LayerPackageParams.GetPackagePath(), LayerPackageParams.GetPackageName(), Package); + } + + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + continue; + } + + // Convert the float data to uint8 + // HF masks need their X/Y sizes swapped + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( + FloatLayerData, LayerVolumeInfo.YLength, LayerVolumeInfo.XLength, + LayerMin, LayerMax, + LandscapeXSize, LandscapeYSize, + ImportLayerInfo.LayerData)) + continue; + + // We will store the data used to convert from Houdini values to int in the DebugColor + // This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards... + // R = Min, G = Max, B = Spacing, A = ? + LayerInfo->LayerUsageDebugColor.R = LayerMin; + LayerInfo->LayerUsageDebugColor.G = LayerMax; + LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; + LayerInfo->LayerUsageDebugColor.A = PI; + + // Visibility are by default non weight blended + if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) + LayerInfo->bNoWeightBlend = true; + else + LayerInfo->bNoWeightBlend = false; + + if (!bIsUpdate && Package && !Package->IsPendingKill()) + { + // Mark the package dirty... + Package->MarkPackageDirty(); + OutCreatedPackages.Add(Package); + } + + if (bExportTexture) + { + // Create an export of the converted data to texture + // FString TextureName = LayerString; + // if (LayerGeoPartObject->VolumeTileIndex >= 0) + // TextureName = TEXT("Tile") + FString::FromInt(LayerGeoPartObject->VolumeTileIndex) + TEXT("_") + LayerString; + // TextureName += TEXT("_conv"); + + const FString TextureName = TilePackageParams.ObjectName + TEXT("_conv"); + + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + LandscapeXSize, LandscapeYSize, + ImportLayerInfo.LayerData); + } + + // See if there is a physical material assigned via attribute for that landscape layer + UPhysicalMaterial* PhysMaterial = FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(*LayerGeoPartObject); + if (PhysMaterial && !PhysMaterial->IsPendingKill()) + { + LayerInfo->PhysMaterial = PhysMaterial; + } + + // Assign the layer info object to the import layer infos + ImportLayerInfo.LayerInfo = LayerInfo; + OutLayerInfos.Add(ImportLayerInfo); + } + + // Autosaving the layers prevents them for being deleted with the Asset + // Save the packages created for the LayerInfos + // Do this only for when creating layers. + /* + if (!bIsUpdate) + FEditorFileUtils::PromptForCheckoutAndSave(CreatedLandscapeLayerPackage, true, false); + */ + + return true; +} + +void +FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums, + bool bShouldEmptyMaps) +{ + if (bShouldEmptyMaps) + { + GlobalMinimums.Empty(); + GlobalMaximums.Empty(); + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray FloatData; + + for (const FHoudiniGeoPartObject& CurrentHeightfield: InHeightfieldArray) + { + // Get the current Heightfield GeoPartObject + if ( CurrentHeightfield.VolumeInfo.TupleSize != 1) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = CurrentHeightfield.GeoId; + if (NodeId == -1) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + &CurrentVolumeInfo)) + continue; + + // Retrieve the volume name. + FString VolumeName; + FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); + HoudiniEngineStringPartName.ToFString(VolumeName); + + bool bHasMinAttr = false; + bool bHasMaxAttr = false; + + // If this volume has an attribute defining a minimum value use it as is. + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + GlobalMinimums.Add(VolumeName, FloatData[0]); + bHasMinAttr = true; + } + } + + // If this volume has an attribute defining maximum value use it as is. + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + GlobalMaximums.Add(VolumeName, FloatData[0]); + bHasMaxAttr = true; + } + } + + if (!(bHasMinAttr && bHasMaxAttr)) + { + // Unreal's Z values are Y in Houdini + float ymin, ymax; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + nullptr, &ymin, nullptr, + nullptr, &ymax, nullptr, + nullptr, nullptr, nullptr)) + continue; + + + if (!bHasMinAttr) + { + // Read the global min value for this volume + if (!GlobalMinimums.Contains(VolumeName)) + { + GlobalMinimums.Add(VolumeName, ymin); + } + else + { + // Update the min if necessary + if (ymin < GlobalMinimums[VolumeName]) + GlobalMinimums[VolumeName] = ymin; + } + } + + if (!bHasMaxAttr) + { + // Read the global max value for this volume + if (!GlobalMaximums.Contains(VolumeName)) + { + GlobalMaximums.Add(VolumeName, ymax); + } + else + { + // Update the max if necessary + if (ymax > GlobalMaximums[VolumeName]) + GlobalMaximums[VolumeName] = ymax; + } + } + } + } +} + +void +FHoudiniLandscapeTranslator::GetLayersZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums) + +{ + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray FloatData; + + for (const FHoudiniGeoPartObject& CurrentHeightfield : InHeightfieldArray) + { + // Get the current Heightfield GeoPartObject + if (CurrentHeightfield.VolumeInfo.TupleSize != 1) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = CurrentHeightfield.GeoId; + if (NodeId == -1) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + &CurrentVolumeInfo)) + continue; + + // Retrieve the volume name. + FString VolumeName; + FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); + HoudiniEngineStringPartName.ToFString(VolumeName); + + // Read the global min value for this volume + + float MinValue; + float MaxValue; + bool bHasMin = false; + bool bHasMax = false; + + if (!GlobalMinimums.Contains(VolumeName)) + { + // Extract min value + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + MinValue = FloatData[0]; + bHasMin = true; + } + } + if (!bHasMin) + { + if (VolumeName == TEXT("height")) + { + MinValue = -1000.f; + } + else + { + MinValue = 0.f; + } + } + GlobalMinimums.Add(VolumeName, MinValue); + } + + if (!GlobalMaximums.Contains(VolumeName)) + { + // Extract max value + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + MaxValue = FloatData[0]; + bHasMax = true; + } + } + if (!bHasMax) + { + if (VolumeName == TEXT("height")) + { + MaxValue = 1000.f; + } + else + { + MaxValue = 1.f; + } + } + GlobalMaximums.Add(VolumeName, MaxValue); + } + + + + } +} + +bool +FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( + const TArray& FloatLayerData, + const int32& HoudiniXSize, const int32& HoudiniYSize, + const float& LayerMin, const float& LayerMax, + const int32& LandscapeXSize, const int32& LandscapeYSize, + TArray& LayerData, const bool& NoResize) +{ + // Convert the float data to uint8 + LayerData.SetNumUninitialized(HoudiniXSize * HoudiniYSize); + + // Calculating the factor used to convert from Houdini's ZRange to [0 255] + double LayerZRange = (LayerMax - LayerMin); + double LayerZSpacing = (LayerZRange != 0.0) ? (255.0 / (double)(LayerZRange)) : 0.0; + + int32 nUnrealIndex = 0; + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y + int32 nHoudini = nY + nX * HoudiniYSize; + + // Get the double values in [0 - ZRange] + double DoubleValue = (double)FMath::Clamp(FloatLayerData[nHoudini], LayerMin, LayerMax) - (double)LayerMin; + + // Then convert it to [0 - 255] + DoubleValue *= LayerZSpacing; + + LayerData[nUnrealIndex++] = FMath::RoundToInt(DoubleValue); + } + } + + // Finally, resize the data to fit with the new landscape size if needed + if (NoResize) + return true; + + return FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( + LayerData, HoudiniXSize, HoudiniYSize, + LandscapeXSize, LandscapeYSize); +} + +bool +FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( + TArray< uint8 >& LayerData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY) +{ + if ((NewSizeX == SizeX) && (NewSizeY == SizeY)) + return true; + + bool bForceResample = true; + bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); + + TArray NewData; + if (!bResample) + { + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + + const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; + const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; + + // Expanding the Data + NewData = ExpandData( + LayerData, + 0, 0, SizeX - 1, SizeY - 1, + -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1); + } + else + { + // Resampling the data + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + NewData = ResampleData(LayerData, SizeX, SizeY, NewSizeX, NewSizeY); + } + + LayerData = NewData; + + return true; +} + +ALandscapeProxy * +FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( + const TArray< uint16 >& IntHeightData, + const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + const FTransform& TileTransform, + const int32& XSize, + const int32& YSize, + const int32& NumSectionPerLandscapeComponent, + const int32& NumQuadsPerLandscapeSection, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* LandscapePhsyicalMaterial, + const FString& LandscapeTileActorName, + LandscapeActorType ActorType, + ALandscape* SharedLandscapeActor, + UWorld* InWorld, + ULevel* InLevel, + FHoudiniPackageParams InPackageParams) +{ + if (!IsValid(InWorld)) + return nullptr; + + // if (!IsValid(MainLandscapeActor)) + // return nullptr; + + if ((XSize < 2) || (YSize < 2)) + return nullptr; + + if (IntHeightData.Num() != (XSize * YSize)) + return nullptr; + + if (!GEditor) + return nullptr; + + ALandscapeProxy* LandscapeTile = nullptr; + UPackage *CreatedPackage = nullptr; + + ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; + ALandscape* CachedLandscapeActor = nullptr; + + UWorld* NewWorld = nullptr; + FString MapFileName; + bool bBroadcastMaterialUpdate = false; + //... Create landscape tile ...// + { + // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos + if (ActorType == LandscapeActorType::LandscapeStreamingProxy) + { + CachedStreamingProxyActor = InWorld->SpawnActor(); + if (CachedStreamingProxyActor) + { + check(SharedLandscapeActor); + CachedStreamingProxyActor->PreEditChange(nullptr); + + // Update landscape tile properties from the main landscape actor. + CachedStreamingProxyActor->GetSharedProperties(SharedLandscapeActor); + CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; + CachedStreamingProxyActor->bCastStaticShadow = false; + + LandscapeTile = CachedStreamingProxyActor; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not spawn ALandscapeStreamingProxy with name: %s"), *(LandscapeTileActorName) ); + return nullptr; + } + } + else + { + // Create a normal landscape actor + CachedLandscapeActor = InWorld->SpawnActor(); + if (CachedLandscapeActor) + { + CachedLandscapeActor->PreEditChange(nullptr); + CachedLandscapeActor->SetLandscapeGuid(FGuid::NewGuid()); + CachedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + CachedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + CachedLandscapeActor->bCastStaticShadow = false; + bBroadcastMaterialUpdate = true; + LandscapeTile = CachedLandscapeActor; + } + } + } + + + if (!LandscapeTile) + return nullptr; + + // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. + + // Autosaving the layers prevents them for being deleted with the Asset + // Save the packages created for the LayerInfos + //if ( CreatedLayerInfoPackage.Num() > 0 ) + // FEditorFileUtils::PromptForCheckoutAndSave( CreatedLayerInfoPackage, true, false ); + + // Import the landscape data + + // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue + LandscapeTile->bCastStaticShadow = false; + + // TODO: Check me? + //if (LandscapePhsyicalMaterial) + // LandscapeTile->DefaultPhysMaterial = LandscapePhsyicalMaterial; + + // Setting the layer type here. + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + + TMap> HeightmapDataPerLayers; + TMap> MaterialLayerDataPerLayer; + HeightmapDataPerLayers.Add(FGuid(), IntHeightData); + MaterialLayerDataPerLayer.Add(FGuid(), ImportLayerInfos); + + FTransform NewTileTransform, LandscapeOffset; + FIntPoint TileLoc; + + // NOTE: The following Import call will reregister all components, causing the actor to lose its transform. + // So we'll be importing the tile data as if the actor was located at the origin and fix up transforms afterward. + CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeOffset, NewTileTransform, TileLoc); + + // Before we import new tile data, remove previous tile data that will be overlapped by the new tile. + TSet OverlappingComponents; + const int32 DestMinX = TileLoc.X; + const int32 DestMinY = TileLoc.Y; + const int32 DestMaxX = TileLoc.X + XSize - 1; + const int32 DestMaxY = TileLoc.Y + YSize - 1; + + ULandscapeInfo* LandscapeInfo = LandscapeTile->GetLandscapeInfo(); + + if (LandscapeInfo) + { + // If there is a preexisting LandscapeInfo object, check for overlapping components. + + // Shrink bounds by 1,1 to avoid GetComponentsInRegion picking up extra components on all sides due to the overlap between components + LandscapeInfo->GetComponentsInRegion(DestMinX+1, DestMinY+1, DestMaxX-1, DestMaxY-1, OverlappingComponents); + TSet StaleActors; + + for (ULandscapeComponent* Component : OverlappingComponents) + { + // Remove the overlapped component from the LandscapeInfo and then from + LandscapeInfo->Modify(); + + ALandscapeProxy* Proxy = Component->GetLandscapeProxy(); + if (!IsValid(Proxy)) + continue; + check(Proxy); + FIntRect Bounds = Proxy->GetBoundingRect(); + // If this landscape proxy has no more components left, remove it from the LandscapeInfo. + LandscapeInfo->UnregisterActor(Proxy); + Proxy->Destroy(); + } + + ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); + } + + // Import tile data + // The Import function will correctly compute the tile section locations. No need to set it explicitly. + // TODO: Verify this with world composition!! + + bool bIsStandalone = LandscapeTile->GetLandscapeActor() == LandscapeTile; + + // We set the actor transform and absolute section base before importing heighfield data. This allows us to + // use the correct (quad-space) blitting region without causing overlaps during import. + + // NOTE: The following SetAbsoluteSectionBase() calls are very important. This tells the Landscape system + // where on the landscape, in quad space, a specific tile is located. This influences is used by various + // mechanisms and tools that tie into the Landscape system such as World Composition and Landscape editor tools. + // If the Section Offset for a landscape tile is wrong, it will appear in the wrong place in the World Composition + // viewer. Worse than that, none of Landscape Editor tools will work properly because it won't be able to + // locate the correct Landscape component when calculating the "Landscape Component Key" for the given word position. + // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blit functions use the + // same underlying Landscape Component Key calculation to find the correct landscape component to draw to. So if the + // Section Offsets are wrong ... all manner of chaos will follow. + // It is also super important to call ULandscapeInfo::RecreateLandscapeInfo() after change a LandscapeProxy's + // section offset in order to update the landscape's internal caches otherwise component key calculations + // won't work correctly. + + LandscapeTile->SetActorTransform(TileTransform); + LandscapeTile->SetAbsoluteSectionBase(TileLoc); + + LandscapeTile->Import( + LandscapeTile->GetLandscapeGuid(), + DestMinX, DestMinY, DestMaxX, DestMaxY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + HeightmapDataPerLayers, NULL, + MaterialLayerDataPerLayer, ImportLayerType); + + LandscapeTile->RecreateComponentsState(); + + if (!LandscapeInfo) + { + LandscapeInfo = LandscapeTile->GetLandscapeInfo(); + } + + // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so + // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo, + // and only then are we able to "blit" the new alpha data into the correct place on the landscape. + + ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); + + // TODO: Verify whether we still need to manually set the LandscapeLightingLOD setting or whether + // calling PostEditChange() will properly fix the state. + + // Copied straight from UE source code to avoid crash after importing the landscape: + // automatically calculate a lighting LOD that won't crash lightmass (hopefully) + // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 + LandscapeTile->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + + // ---------------------------------------------------- + // Rename the actor + // ---------------------------------------------------- + + // NOTE: The LandscapeProxy needs to be properly initialized before renaming (which is why the rename is taking + // place at the end) since the rename will trigger PostEditChange and can crash if the actor has not been + // correctly setup. + FHoudiniEngineUtils::SafeRenameActor(LandscapeTile, LandscapeTileActorName); + + if (!LandscapeTile->MarkPackageDirty()) + { + HOUDINI_LOG_WARNING(TEXT("Editor suppressed MarkPackageDirty() for landscape tile: %s"), *(LandscapeTile->GetPathName())); + } + + return LandscapeTile; +} + + +void +FHoudiniLandscapeTranslator::CalculateTileLocation( + int32 NumSectionsPerComponent, + int32 NumQuadsPerSection, + const FTransform& TileTransform, + FTransform& OutLandscapeOffset, + FTransform& OutTileTransform, + FIntPoint& OutTileLocation) +{ + // IMPORTANT: Tile Location (Section Base) needs to be in multiples of the landscape component's quad size + const int32 ComponentSize = NumSectionsPerComponent * NumQuadsPerSection; + + OutLandscapeOffset = FTransform(FRotator::ZeroRotator, FVector::ZeroVector, TileTransform.GetScale3D()); + OutTileTransform = TileTransform; + + // Sometimes the calculated tile coordinate falls on a half-unit so we would need to remove that offset first + // before calculating integer (quad space) tile location. + // For example, 123.5, should become 123. -456.5 should become -456. + const FVector TileScale = TileTransform.GetScale3D(); + const float TileCoordX = TileTransform.GetLocation().X / TileScale.X; + const float TileCoordY = TileTransform.GetLocation().Y / TileScale.Y; + + float NearestMultipleX = FMath::RoundHalfFromZero(TileCoordX / ComponentSize) * ComponentSize; + float NearestMultipleY = FMath::RoundHalfFromZero(TileCoordY / ComponentSize) * ComponentSize; + + // If the multiples are too close to the middle, offset by 0.5 to avoid some tiles snapping up and others snapping down. + if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleX), 0.5f, 0.1f)) + { + NearestMultipleX += 0.5f; + } + if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleY), 0.5f, 0.1f)) + { + NearestMultipleY += 0.5f; + } + + const float TileOffsetX = NearestMultipleX - TileCoordX; + const float TileOffsetY = NearestMultipleY - TileCoordY; + + OutTileLocation.X = FMath::RoundHalfFromZero(NearestMultipleX); + OutTileLocation.Y = FMath::RoundHalfFromZero(NearestMultipleY); + + // Adjust landscape offset to compensate for tile location / section base shifting. + OutLandscapeOffset.SetLocation( FVector(-TileOffsetX * TileScale.X, -TileOffsetY * TileScale.Y, 0.f) ); +} + + +void +FHoudiniLandscapeTranslator::GetLandscapeMaterials( + const FHoudiniGeoPartObject& InHeightHGPO, + UMaterialInterface*& OutLandscapeMaterial, + UMaterialInterface*& OutLandscapeHoleMaterial, + UPhysicalMaterial*& OutLandscapePhysicalMaterial) +{ + OutLandscapeMaterial = nullptr; + OutLandscapeHoleMaterial = nullptr; + OutLandscapePhysicalMaterial = nullptr; + + if (InHeightHGPO.Type != EHoudiniPartType::Volume) + return; + + TArray Materials; + HAPI_AttributeInfo AttribMaterials; + FHoudiniApi::AttributeInfo_Init(&AttribMaterials); + + // First, look for landscape material + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL, + AttribMaterials, Materials); + + // If the material attribute was not found, check the material instance attribute. + if (!AttribMaterials.exists) + { + Materials.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, + AttribMaterials, Materials); + } + + // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. + //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) + if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) + { + HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a point, primitive or detail attribute, ignoring attribute.")); + AttribMaterials.exists = false; + Materials.Empty(); + } + + if (AttribMaterials.exists && Materials.Num() > 0) + { + // Load the material + OutLandscapeMaterial = Cast(StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); + } + } + + Materials.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribMaterials); + + // Then, for the hole_material + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + AttribMaterials, Materials); + + // If the material attribute was not found, check the material instance attribute. + if (!AttribMaterials.exists) + { + Materials.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE, + AttribMaterials, Materials); + } + + // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. + //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) + if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) + { + HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute.")); + AttribMaterials.exists = false; + Materials.Empty(); + } + + if (AttribMaterials.exists && Materials.Num() > 0) + { + // Load the material + OutLandscapeHoleMaterial = Cast< UMaterialInterface >(StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); + } + } + + // Then for the physical material + OutLandscapePhysicalMaterial = GetLandscapePhysicalMaterial(InHeightHGPO); +} + +// Read the landscape component extent attribute from a heightfield +bool +FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + int32& MinX, int32& MaxX, + int32& MinY, int32& MaxY) +{ + // If we dont have minX, we likely dont have the others too + if (!FHoudiniEngineUtils::HapiCheckAttributeExists( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", HAPI_ATTROWNER_PRIM)) + return false; + + // Create an AttributeInfo + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); + + // Get MinX + TArray IntData; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MinX = IntData[0]; + + // Get MaxX + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MaxX = IntData[0]; + + // Get MinY + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MinY = IntData[0]; + + // Get MaxX + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MaxY = IntData[0]; + + return true; +} + +ULandscapeLayerInfoObject * +FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& InLayerName, const FString& InPackagePath, const FString& InPackageName, UPackage*& OutPackage) +{ + FString PackageFullName = InPackagePath + TEXT("/") + InPackageName; + + // See if package exists, if it does, reuse it + bool bCreatedPackage = false; + OutPackage = FindPackage(nullptr, *PackageFullName); + if (!OutPackage || OutPackage->IsPendingKill()) + { + // We need to create a new package + OutPackage = CreatePackage(*PackageFullName); + bCreatedPackage = true; + } + + if (!OutPackage || OutPackage->IsPendingKill()) + return nullptr; + + if (!OutPackage->IsFullyLoaded()) + OutPackage->FullyLoad(); + + ULandscapeLayerInfoObject* LayerInfo = nullptr; + if (!bCreatedPackage) + { + // See if we can load the layer info instead of creating a new one + LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), OutPackage, FName(*InPackageName)); + } + + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + // Create a new LandscapeLayerInfoObject in the package + LayerInfo = NewObject(OutPackage, FName(*InPackageName), RF_Public | RF_Standalone /*| RF_Transactional*/); + + // Notify the asset registry + FAssetRegistryModule::AssetCreated(LayerInfo); + } + + if (LayerInfo && !LayerInfo->IsPendingKill()) + { + LayerInfo->LayerName = FName(*InLayerName); + + // Trigger update of the Layer Info + LayerInfo->PreEditChange(nullptr); + LayerInfo->PostEditChange(); + LayerInfo->MarkPackageDirty(); + + // Mark the package dirty... + OutPackage->MarkPackageDirty(); + } + + return LayerInfo; +} + +bool +FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax( + const TArray& AllOutputs, float& OutGlobalMin, float& OutGlobalMax) +{ + OutGlobalMin = 0.f; + OutGlobalMax = 0.f; + + for (const auto& CurrentOutput : AllOutputs) + { + if (!CurrentOutput) + continue; + + if (CurrentOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + const TArray& HGPOs = CurrentOutput->GetHoudiniGeoPartObjects(); + for (const FHoudiniGeoPartObject& CurrentHGPO : HGPOs) + { + if (CurrentHGPO.Type != EHoudiniPartType::Volume) + continue; + + if (!CurrentHGPO.VolumeInfo.Name.Contains("height")) + continue; + + // We're only handling single values for now + if (CurrentHGPO.VolumeInfo.TupleSize != 1) + continue; + + // Terrains always have a ZSize of 1. + if (CurrentHGPO.VolumeInfo.ZLength != 1) + continue; + + // Values should be float + if (!CurrentHGPO.VolumeInfo.bIsFloat) + continue; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CurrentHGPO.GeoId)) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHGPO.GeoId, CurrentHGPO.PartId, &CurrentVolumeInfo)) + continue; + + // Unreal's Z values are Y in Houdini + float yMin = OutGlobalMin, yMax = OutGlobalMax; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), + CurrentHGPO.GeoId, CurrentHGPO.PartId, + nullptr, &yMin, nullptr, + nullptr, &yMax, nullptr, + nullptr, nullptr, nullptr)) + continue; + + if (yMin < OutGlobalMin) + OutGlobalMin = yMin; + + if (yMax > OutGlobalMax) + OutGlobalMax = yMax; + } + + if (OutGlobalMin > OutGlobalMax) + { + OutGlobalMin = 0.f; + OutGlobalMax = 0.f; + } + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::EnableWorldComposition() +{ + HOUDINI_LOG_WARNING(TEXT("[FHoudiniLandscapeTranslator::EnableWorldComposition] We should never enable world composition from within the plugin.")); + // Get the world + UWorld* MyWorld = nullptr; + { + // We want to create the landscape in the landscape editor mode's world + FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); + MyWorld = EditorWorldContext.World(); + } + + if (!MyWorld) + return false; + + ULevel* CurrentLevel = MyWorld->GetCurrentLevel(); + + if (!CurrentLevel) + return false; + + AWorldSettings* WorldSettings = CurrentLevel->GetWorldSettings(); + if (!WorldSettings) + return false; + + // Enable world composition in WorldSettings + WorldSettings->bEnableWorldComposition = true; + + CurrentLevel->PostEditChange(); + + return true; +} + + +bool +FHoudiniLandscapeTranslator::GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, + const int32& InPrimIndex, TArray& OutPropertyAttributes) +{ + // List all the generic property detail attributes ... + int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + + // .. then the primitive property attributes + // Volumes apparently dont have prim attributes because they're converted to pointmeshes somehow... + //FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + // InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InPrimIndex); + + // .. then the point property attributes + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InPrimIndex); + + return FoundCount > 0; +} + + +bool +FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (auto CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current Property Attribute + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) + continue; + + // Success! + NumSuccess++; + FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); + } + + return (NumSuccess > 0); +} + + +bool +FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(const FString& BaseName, ALandscapeProxy* Landscape) +{ + // We need to cache the input landscape to a file + if (!Landscape) + return false; + + ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Save Height data to file + //FString HeightSave = TEXT("/Game/HoudiniEngine/Temp/HeightCache.png"); + FString HeightSave = BaseName + TEXT("_height.png"); + LandscapeInfo->ExportHeightmap(HeightSave); + Landscape->ReimportHeightmapFilePath = HeightSave; + + // Save each layer to a file + for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) + { + FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); + //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; + if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + continue; + + FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); + LandscapeInfo->ExportLayer(CurrentLayerInfo, LayerSave); + + // Update the file reimport path on the input landscape for this layer + LandscapeInfo->GetLayerEditorSettings(CurrentLayerInfo).ReimportLayerFilePath = LayerSave; + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy) +{ + if (!LandscapeProxy) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Restore Height data from the backup file + FString ReimportFile = LandscapeProxy->ReimportHeightmapFilePath; + if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, TEXT("height"))) + HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); + + // Restore each layer from the backup file + TArray< ULandscapeLayerInfoObject* > SourceLayers; + for (int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++) + { + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; + if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + continue; + + FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); + ReimportFile = LandscapeProxy->EditorLayerSettings[LayerIndex].ReimportLayerFilePath; + + if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, CurrentLayerName, CurrentLayerInfo)) + HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); + + SourceLayers.Add(CurrentLayerInfo); + } + + // Iterate on the landscape info's layer to remove any layer that could have been added by Houdini + for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) + { + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; + if (SourceLayers.Contains(CurrentLayerInfo)) + continue; + + // Delete the added layer + FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName; + LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName); + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::ImportLandscapeData( + ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject) +{ + // + // Code copied/edited from FEdModeLandscape::ImportData as we cannot access that function + // + if (!LandscapeInfo) + return false; + + bool IsHeight = LayerName.Equals(TEXT("height"), ESearchCase::IgnoreCase); + + int32 MinX, MinY, MaxX, MaxY; + if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + { + const FLandscapeFileResolution LandscapeResolution = { (uint32)(1 + MaxX - MinX), (uint32)(1 + MaxY - MinY) }; + + ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); + + if (IsHeight) + { + const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); + + if (!HeightmapFormat) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); + return false; + } + + FLandscapeFileResolution ImportResolution = { 0, 0 }; + + const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename); + + // display error message if there is one, and abort the import + if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); + return false; + } + + // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape + if (HeightmapInfo.PossibleResolutions.Num() > 1) + { + if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined")); + return false; + } + else + { + ImportResolution = LandscapeResolution; + } + } + + // display warning message if there is one and allow user to cancel + if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); + + // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape + // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is + if (HeightmapInfo.PossibleResolutions.Num() == 1) + { + ImportResolution = HeightmapInfo.PossibleResolutions[0]; + if (ImportResolution != LandscapeResolution) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); + } + + FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution); + if (ImportData.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); + return false; + } + + TArray Data; + if (ImportResolution != LandscapeResolution) + { + // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked + // so that reimports behave the same as the initial import :) + + const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; + const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; + + Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16)); + + ExpandData(Data.GetData(), ImportData.Data.GetData(), + 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, + -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); + } + else + { + Data = MoveTemp(ImportData.Data); + } + + //FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap")); + + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData()); + } + else + { + // We're importing a Landscape layer + if (!LayerInfoObject || LayerInfoObject->IsPendingKill()) + return false; + + const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); + if (!WeightmapFormat) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); + return false; + } + + FLandscapeFileResolution ImportResolution = { 0, 0 }; + + const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName)); + + // display error message if there is one, and abort the import + if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); + return false; + } + + // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape + if (WeightmapInfo.PossibleResolutions.Num() > 1) + { + if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined")); + return false; + } + else + { + ImportResolution = LandscapeResolution; + } + } + + // display warning message if there is one and allow user to cancel + if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); + + // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape + // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is + if (WeightmapInfo.PossibleResolutions.Num() == 1) + { + ImportResolution = WeightmapInfo.PossibleResolutions[0]; + if (ImportResolution != LandscapeResolution) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); + } + + FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution); + + if (ImportData.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); + return false; + } + + TArray Data; + if (ImportResolution != LandscapeResolution) + { + // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked + // so that reimports behave the same as the initial import :) + const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; + const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; + + Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8)); + + ExpandData(Data.GetData(), ImportData.Data.GetData(), + 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, + -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); + } + else + { + Data = MoveTemp(ImportData.Data); + } + + //FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer")); + FAlphamapAccessor AlphamapAccessor(LandscapeInfo, LayerInfoObject); + AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + + return true; +} + +UTexture2D* +FHoudiniLandscapeTranslator::CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& InFloatBuffer, + const float& InMin, + const float& InMax) +{ + + // Convert the float values to uint8 + double Range = (double)InMax - (double)InMin; + TArray IntBuffer; + IntBuffer.SetNum(InFloatBuffer.Num()); + for(int32 i = 0; i < InFloatBuffer.Num(); i++) + { + double dNormalizedValue = ((double)InFloatBuffer[i] - (double)InMin) / (double)Range; + IntBuffer[i] = (uint8)(dNormalizedValue * 255.0); + } + + return FHoudiniLandscapeTranslator::CreateUnrealTexture( + InPackageParams, LayerName, InXSize, InYSize, IntBuffer); +} + +UTexture2D* +FHoudiniLandscapeTranslator::CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& IntBuffer) +{ + FHoudiniPackageParams MyPackageParams = InPackageParams; + MyPackageParams.ObjectName = LayerName; + MyPackageParams.PackageMode = EPackageMode::CookToTemp; + MyPackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + FString CreatedPackageName; + UPackage* Package = MyPackageParams.CreatePackageForObject(CreatedPackageName); + if (!Package || Package->IsPendingKill()) + return nullptr; + + // Create new texture object. + UTexture2D * Texture = NewObject(Package, UTexture2D::StaticClass(), *LayerName, RF_Public | RF_Standalone); + + // Add/Update meta information to package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *LayerName); + + /*// Texture Settings + Texture->PlatformData = new FTexturePlatformData(); + Texture->PlatformData->SizeX = InXSize; + Texture->PlatformData->SizeY = InYSize; + Texture->PlatformData->PixelFormat = PF_R8G8B8A8;*/ + + // Initialize texture source. + Texture->Source.Init(InXSize, InYSize, 1, 1, TSF_BGRA8); + + // Lock the texture. + uint8 * MipData = Texture->Source.LockMip(0); + + // Create base map. + uint8* DestPtr = nullptr; + uint32 SrcWidth = InXSize; + uint32 SrcHeight = InYSize; + const uint8 * SrcData = &IntBuffer[0]; + + for (uint32 y = 0; y < SrcHeight; y++) + { + DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; + + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth + x; + + *DestPtr++ = *(SrcData + DataOffset); // B greyscale, same value 3 times + *DestPtr++ = *(SrcData + DataOffset); // G + *DestPtr++ = *(SrcData + DataOffset); // R + + *DestPtr++ = 0xFF; // A to 1 + } + } + + // Unlock the texture. + Texture->Source.UnlockMip(0); + + // Texture creation parameters. + //Texture->SRGB = TextureParameters.bSRGB; + Texture->CompressionSettings = TC_Grayscale; + Texture->CompressionNoAlpha = true; + Texture->MipGenSettings = TMGS_NoMipmaps; + + // Set the Source Guid/Hash if specified. + /* + if ( TextureParameters.SourceGuidHash.IsValid() ) + { + Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); + } + */ + + // Updating Texture & mark it as unsaved + //Texture->AddToRoot(); + //Texture->UpdateResource(); + Package->MarkPackageDirty(); + + Texture->PostEditChange(); + + FString PathName = Texture->GetPathName(); + HOUDINI_LOG_MESSAGE(TEXT("Created texture when for %s in %s"), *LayerName, *PathName); + + return Texture; +} + +UPhysicalMaterial* +FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO) +{ + // See if we have assigned a physical material to this layer via attribute + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + TArray AttributeValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InLayerHGPO.GeoId, InLayerHGPO.PartId, + HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL, + AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) + return nullptr; + + if (AttributeValues.Num() > 0) + { + return LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); + } + + return nullptr; +} + +ULandscapeLayerInfoObject* +FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName) +{ + // See if we have assigned a landscape layer info object to this layer via attribute + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + TArray AttributeValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InLayerHGPO.GeoId, InLayerHGPO.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO, + AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) + return nullptr; + + if (AttributeValues.Num() > 0) + { + ULandscapeLayerInfoObject* FoundLayerInfo = LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); + if (!FoundLayerInfo || FoundLayerInfo->IsPendingKill()) + return nullptr; + + // The layer info's name must match this layer's name or Unreal will not like this! + if (!FoundLayerInfo->LayerName.IsEqual(InLayerName)) + { + FString NameStr = InLayerName.ToString(); + HOUDINI_LOG_WARNING(TEXT("Failed to use the assigned layer info object for %s by the unreal_landscape_layer_info attribute as the found layer info object's layer name does not match."), *NameStr); + } + + return FoundLayerInfo; + } + + return nullptr; +} + + + diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h index a86f75859..5c7235d63 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h @@ -1,401 +1,401 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "Landscape.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "Engine/World.h" -#include "EngineUtils.h" -#include "HoudiniEngineOutputStats.h" -#include "HoudiniPackageParams.h" - -class UHoudiniAssetComponent; -class ULandscapeLayerInfoObject; -struct FHoudiniGenericAttribute; -struct FHoudiniPackageParams; - -struct HOUDINIENGINE_API FHoudiniLandscapeTranslator -{ - public: - enum class LandscapeActorType : uint8 - { - LandscapeActor = 0, - LandscapeStreamingProxy = 1, - }; - - static bool CreateLandscape( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedActors, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* World, - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniPackageParams InPackageParams, - TArray& OutCreatedPackages); - - static ALandscapeProxy* FindExistingLandscapeActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - protected: - - static bool IsLandscapeInfoCompatible( - const ULandscapeInfo* LandscapeInfo, - const int32 NumSectionsX, - const int32 NumSectionsY); - - static bool IsLandscapeTileCompatible( - const ALandscapeProxy* TileActor, - const int32 InTileSizeX, - const int32 InTileSizeY, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection); - - static bool IsLandscapeTypeCompatible( - const AActor* Actor, - LandscapeActorType ActorType); - - - /** - * Find a ALandscapeProxy actor that can be reused. It is important - * to note that the request landscape actor could not be found, - * `OutWorld` and `OutLevel` should be used to spawn the - * new landscape actor. - * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. - */ - static ALandscapeProxy* FindExistingLandscapeActor( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage, - const EPackageMode& InPackageMode); - - static ALandscapeProxy* FindExistingLandscapeActor_Temp( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - /** - * Attempt the given ALandscapeActor to the outer HAC. Note - * that certain package modes (such as Bake) may choose not to do so. - * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. - */ - static void SetLandscapeActorAsOutput( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor, - const EPackageMode InPackageMode); - - static void SetLandscapeActorAsOutput_Bake( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor); - - static void SetLandscapeActorAsOutput_Temp( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor); - - /** - * Attach the given actor the HoudiniAssetComponent that - * owns `InOutput`, if any. - * @returns True if the actor was attached. Otherwise, return false. - */ - static bool AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor); - - /** - * Get the actor name suffix to be used in the specific packaging mode. - * @returns Suffix for actor names, return as an FString. - */ - static FString GetActorNameSuffix(const EPackageMode& InPackageMode); - - - // Helpers to get rid of repetitive boilerplate. - static void DoPreEditChangeProperty(UObject* Obj, FName PropertyName); - static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); - - public: - - - static const FHoudiniGeoPartObject* GetHoudiniHeightFieldFromOutput( - UHoudiniOutput* InOutput); - - static void GetHeightfieldsLayersFromOutput( - const UHoudiniOutput* InOutput, - const FHoudiniGeoPartObject& Heightfield, - TArray< const FHoudiniGeoPartObject* >& FoundLayers); - - static bool GetHoudiniHeightfieldFloatData( - const FHoudiniGeoPartObject* HGPO, - TArray &OutFloatArr, - float &OutFloatMin, - float &OutFloatMax); - - static bool CalcLandscapeSizeFromHeightfieldSize( - const int32& HoudiniSizeX, - const int32& HoudiniSizeY, - int32& UnrealSizeX, - int32& UnrealSizeY, - int32& NumSectionsPerComponent, - int32& NumQuadsPerSection); - - static bool ConvertHeightfieldDataToLandscapeData( - const TArray< float >& HeightfieldFloatValues, - const FHoudiniVolumeInfo& HeightfieldVolumeInfo, - const int32& FinalXSize, - const int32& FinalYSize, - float FloatMin, - float FloatMax, - TArray< uint16 >& IntHeightData, - FTransform& LandscapeTransform, - const bool& NoResize = false); - - static bool ResizeHeightDataForLandscape( - TArray& HeightData, - const int32& SizeX, - const int32& SizeY, - const int32& NewSizeX, - const int32& NewSizeY, - FVector& LandscapeResizeFactor, - FVector& LandscapePositionOffset); - - static bool CreateOrUpdateLandscapeLayers( - const TArray& FoundLayers, - const FHoudiniGeoPartObject& HeightField, - const int32& LandscapeXSize, - const int32& LandscapeYSize, - const TMap &GlobalMinimums, - const TMap &GlobalMaximums, - TArray& OutLayerInfos, - bool bIsUpdate, - const FHoudiniPackageParams& InTilePackageParams, - const FHoudiniPackageParams& InLayerPackageParams, - TArray& OutCreatedPackages); - - static bool GetNonWeightBlendedLayerNames( - const FHoudiniGeoPartObject& HeightfieldGeoPartObject, - TArray& NonWeightBlendedLayerNames); - - static bool IsUnitLandscapeLayer( - const FHoudiniGeoPartObject& LayerGeoPartObject); - - // Return the height min/max values for all - static bool CalcHeightGlobalZminZMax( - const TArray& AllOutputs, - float& OutGlobalMin, - float& OutGlobalMax); - - // Returns the min/max values per layer/volume for an array of volumes/heightfields - static void CalcHeightfieldsArrayGlobalZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums, - bool bShouldEmptyMaps=true); - - // Iterate over layers for the heightfields and retrieve min/max values - // from attributes, otherwise return default values. - static void GetLayersZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums); - - static bool ConvertHeightfieldLayerToLandscapeLayer( - const TArray& FloatLayerData, - const int32& HoudiniXSize, - const int32& HoudiniYSize, - const float& LayerMin, - const float& LayerMax, - const int32& LandscapeXSize, - const int32& LandscapeYSize, - TArray& LayerData, - const bool& NoResize = false); - - static bool ResizeLayerDataForLandscape( - TArray< uint8 >& LayerData, - const int32& SizeX, - const int32& SizeY, - const int32& NewSizeX, - const int32& NewSizeY); - - // static ALandscapeProxy * CreateLandscape( - // const TArray< uint16 >& IntHeightData, - // const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - // const FTransform& LandscapeTransform, - // const int32& XSize, - // const int32& YSize, - // const int32& NumSectionPerLandscapeComponent, - // const int32& NumQuadsPerLandscapeSection, - // UMaterialInterface* LandscapeMaterial, - // UMaterialInterface* LandscapeHoleMaterial, - // const bool& CreateLandscapeStreamingProxy, - // bool bNeedCreateNewWorld, - // UWorld* SpawnWorld, - // FHoudiniPackageParams InPackageParams, - // bool& bOutCreatedNewMap); - - static ALandscapeProxy* CreateLandscapeTileInWorld( - const TArray< uint16 >& IntHeightData, - const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - const FTransform& TileTransform, - const int32& XSize, - const int32& YSize, - const int32& NumSectionPerLandscapeComponent, - const int32& NumQuadsPerLandscapeSection, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhsyicalMaterial, - const FString& LandscapeTileActorName, - LandscapeActorType ActorType, - ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies - UWorld* InWorld, // World in which to spawn - ULevel* InLevel, // Level, contained in World, in which to spawn. - FHoudiniPackageParams InPackageParams); - -protected: - - /** - * Calculate the location of a landscape tile. - * This location is typically used in conjunction with ALandscapeProxy::SetAbsoluteSectionBase(). - */ - static void CalculateTileLocation( - int32 NumSectionsPerComponent, - int32 NumQuadsPerSection, - const FTransform& InTileTransform, - FTransform& OutLandscapeOffset, - FTransform& OutTileTransform, - FIntPoint& OutTileLocation); - -public: - - static void GetLandscapeMaterials( - const FHoudiniGeoPartObject& InHeightHGPO, - UMaterialInterface*& OutLandscapeMaterial, - UMaterialInterface*& OutLandscapeHoleMaterial, - UPhysicalMaterial*& OutLandscapePhysicalMaterial); - - static bool GetLandscapeComponentExtentAttributes( - const FHoudiniGeoPartObject& HoudiniGeoPartObject, - int32& MinX, - int32& MaxX, - int32& MinY, - int32& MaxY); - - static ULandscapeLayerInfoObject* FindOrCreateLandscapeLayerInfoObject( - const FString& InLayerName, - const FString& InPackagePath, - const FString& InPackageName, - UPackage*& OutPackage); - - static bool EnableWorldComposition(); - - static bool GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const int32& InPrimIndex, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, - const TArray& InAllPropertyAttributes); - - static bool BackupLandscapeToImageFiles( - const FString& BaseName, ALandscapeProxy* Landscape); - - static bool RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy); - - static UPhysicalMaterial* GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO); - - static ULandscapeLayerInfoObject* GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName); - - private: - - static bool ImportLandscapeData( - ULandscapeInfo* LandscapeInfo, - const FString& Filename, - const FString& LayerName, - ULandscapeLayerInfoObject* LayerInfoObject = nullptr); - - static UTexture2D* CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& InFloatBuffer, - const float& InMin, - const float& InMax); - - static UTexture2D* CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& IntBuffer); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "Landscape.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "Engine/World.h" +#include "EngineUtils.h" +#include "HoudiniEngineOutputStats.h" +#include "HoudiniPackageParams.h" + +class UHoudiniAssetComponent; +class ULandscapeLayerInfoObject; +struct FHoudiniGenericAttribute; +struct FHoudiniPackageParams; + +struct HOUDINIENGINE_API FHoudiniLandscapeTranslator +{ + public: + enum class LandscapeActorType : uint8 + { + LandscapeActor = 0, + LandscapeStreamingProxy = 1, + }; + + static bool CreateLandscape( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniPackageParams InPackageParams, + TArray& OutCreatedPackages); + + static ALandscapeProxy* FindExistingLandscapeActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + protected: + + static bool IsLandscapeInfoCompatible( + const ULandscapeInfo* LandscapeInfo, + const int32 NumSectionsX, + const int32 NumSectionsY); + + static bool IsLandscapeTileCompatible( + const ALandscapeProxy* TileActor, + const int32 InTileSizeX, + const int32 InTileSizeY, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection); + + static bool IsLandscapeTypeCompatible( + const AActor* Actor, + LandscapeActorType ActorType); + + + /** + * Find a ALandscapeProxy actor that can be reused. It is important + * to note that the request landscape actor could not be found, + * `OutWorld` and `OutLevel` should be used to spawn the + * new landscape actor. + * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. + */ + static ALandscapeProxy* FindExistingLandscapeActor( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage, + const EPackageMode& InPackageMode); + + static ALandscapeProxy* FindExistingLandscapeActor_Temp( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + /** + * Attempt the given ALandscapeActor to the outer HAC. Note + * that certain package modes (such as Bake) may choose not to do so. + * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. + */ + static void SetLandscapeActorAsOutput( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor, + const EPackageMode InPackageMode); + + static void SetLandscapeActorAsOutput_Bake( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor); + + static void SetLandscapeActorAsOutput_Temp( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor); + + /** + * Attach the given actor the HoudiniAssetComponent that + * owns `InOutput`, if any. + * @returns True if the actor was attached. Otherwise, return false. + */ + static bool AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor); + + /** + * Get the actor name suffix to be used in the specific packaging mode. + * @returns Suffix for actor names, return as an FString. + */ + static FString GetActorNameSuffix(const EPackageMode& InPackageMode); + + + // Helpers to get rid of repetitive boilerplate. + static void DoPreEditChangeProperty(UObject* Obj, FName PropertyName); + static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); + + public: + + + static const FHoudiniGeoPartObject* GetHoudiniHeightFieldFromOutput( + UHoudiniOutput* InOutput); + + static void GetHeightfieldsLayersFromOutput( + const UHoudiniOutput* InOutput, + const FHoudiniGeoPartObject& Heightfield, + TArray< const FHoudiniGeoPartObject* >& FoundLayers); + + static bool GetHoudiniHeightfieldFloatData( + const FHoudiniGeoPartObject* HGPO, + TArray &OutFloatArr, + float &OutFloatMin, + float &OutFloatMax); + + static bool CalcLandscapeSizeFromHeightfieldSize( + const int32& HoudiniSizeX, + const int32& HoudiniSizeY, + int32& UnrealSizeX, + int32& UnrealSizeY, + int32& NumSectionsPerComponent, + int32& NumQuadsPerSection); + + static bool ConvertHeightfieldDataToLandscapeData( + const TArray< float >& HeightfieldFloatValues, + const FHoudiniVolumeInfo& HeightfieldVolumeInfo, + const int32& FinalXSize, + const int32& FinalYSize, + float FloatMin, + float FloatMax, + TArray< uint16 >& IntHeightData, + FTransform& LandscapeTransform, + const bool& NoResize = false); + + static bool ResizeHeightDataForLandscape( + TArray& HeightData, + const int32& SizeX, + const int32& SizeY, + const int32& NewSizeX, + const int32& NewSizeY, + FVector& LandscapeResizeFactor, + FVector& LandscapePositionOffset); + + static bool CreateOrUpdateLandscapeLayers( + const TArray& FoundLayers, + const FHoudiniGeoPartObject& HeightField, + const int32& LandscapeXSize, + const int32& LandscapeYSize, + const TMap &GlobalMinimums, + const TMap &GlobalMaximums, + TArray& OutLayerInfos, + bool bIsUpdate, + const FHoudiniPackageParams& InTilePackageParams, + const FHoudiniPackageParams& InLayerPackageParams, + TArray& OutCreatedPackages); + + static bool GetNonWeightBlendedLayerNames( + const FHoudiniGeoPartObject& HeightfieldGeoPartObject, + TArray& NonWeightBlendedLayerNames); + + static bool IsUnitLandscapeLayer( + const FHoudiniGeoPartObject& LayerGeoPartObject); + + // Return the height min/max values for all + static bool CalcHeightGlobalZminZMax( + const TArray& AllOutputs, + float& OutGlobalMin, + float& OutGlobalMax); + + // Returns the min/max values per layer/volume for an array of volumes/heightfields + static void CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums, + bool bShouldEmptyMaps=true); + + // Iterate over layers for the heightfields and retrieve min/max values + // from attributes, otherwise return default values. + static void GetLayersZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums); + + static bool ConvertHeightfieldLayerToLandscapeLayer( + const TArray& FloatLayerData, + const int32& HoudiniXSize, + const int32& HoudiniYSize, + const float& LayerMin, + const float& LayerMax, + const int32& LandscapeXSize, + const int32& LandscapeYSize, + TArray& LayerData, + const bool& NoResize = false); + + static bool ResizeLayerDataForLandscape( + TArray< uint8 >& LayerData, + const int32& SizeX, + const int32& SizeY, + const int32& NewSizeX, + const int32& NewSizeY); + + // static ALandscapeProxy * CreateLandscape( + // const TArray< uint16 >& IntHeightData, + // const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + // const FTransform& LandscapeTransform, + // const int32& XSize, + // const int32& YSize, + // const int32& NumSectionPerLandscapeComponent, + // const int32& NumQuadsPerLandscapeSection, + // UMaterialInterface* LandscapeMaterial, + // UMaterialInterface* LandscapeHoleMaterial, + // const bool& CreateLandscapeStreamingProxy, + // bool bNeedCreateNewWorld, + // UWorld* SpawnWorld, + // FHoudiniPackageParams InPackageParams, + // bool& bOutCreatedNewMap); + + static ALandscapeProxy* CreateLandscapeTileInWorld( + const TArray< uint16 >& IntHeightData, + const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + const FTransform& TileTransform, + const int32& XSize, + const int32& YSize, + const int32& NumSectionPerLandscapeComponent, + const int32& NumQuadsPerLandscapeSection, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* LandscapePhsyicalMaterial, + const FString& LandscapeTileActorName, + LandscapeActorType ActorType, + ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies + UWorld* InWorld, // World in which to spawn + ULevel* InLevel, // Level, contained in World, in which to spawn. + FHoudiniPackageParams InPackageParams); + +protected: + + /** + * Calculate the location of a landscape tile. + * This location is typically used in conjunction with ALandscapeProxy::SetAbsoluteSectionBase(). + */ + static void CalculateTileLocation( + int32 NumSectionsPerComponent, + int32 NumQuadsPerSection, + const FTransform& InTileTransform, + FTransform& OutLandscapeOffset, + FTransform& OutTileTransform, + FIntPoint& OutTileLocation); + +public: + + static void GetLandscapeMaterials( + const FHoudiniGeoPartObject& InHeightHGPO, + UMaterialInterface*& OutLandscapeMaterial, + UMaterialInterface*& OutLandscapeHoleMaterial, + UPhysicalMaterial*& OutLandscapePhysicalMaterial); + + static bool GetLandscapeComponentExtentAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + int32& MinX, + int32& MaxX, + int32& MinY, + int32& MaxY); + + static ULandscapeLayerInfoObject* FindOrCreateLandscapeLayerInfoObject( + const FString& InLayerName, + const FString& InPackagePath, + const FString& InPackageName, + UPackage*& OutPackage); + + static bool EnableWorldComposition(); + + static bool GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const int32& InPrimIndex, + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, + const TArray& InAllPropertyAttributes); + + static bool BackupLandscapeToImageFiles( + const FString& BaseName, ALandscapeProxy* Landscape); + + static bool RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy); + + static UPhysicalMaterial* GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO); + + static ULandscapeLayerInfoObject* GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName); + + private: + + static bool ImportLandscapeData( + ULandscapeInfo* LandscapeInfo, + const FString& Filename, + const FString& LayerName, + ULandscapeLayerInfoObject* LayerInfoObject = nullptr); + + static UTexture2D* CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& InFloatBuffer, + const float& InMin, + const float& InMax); + + static UTexture2D* CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& IntBuffer); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp index 62dd57172..99a17368b 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp @@ -1,3237 +1,3237 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniMaterialTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPackageParams.h" - -#include "Materials/Material.h" -#include "Materials/MaterialInstance.h" -#include "Materials/MaterialInstanceConstant.h" -#include "PhysicalMaterials/PhysicalMaterial.h" - -#include "Materials/MaterialExpressionTextureSample.h" -#include "Materials/MaterialExpressionTextureCoordinate.h" -#include "Materials/MaterialExpressionConstant4Vector.h" -#include "Materials/MaterialExpressionConstant.h" -#include "Materials/MaterialExpressionMultiply.h" -#include "Materials/MaterialExpressionVertexColor.h" -#include "Materials/MaterialExpressionTextureSampleParameter2D.h" -#include "Materials/MaterialExpressionVectorParameter.h" -#include "Materials/MaterialExpressionScalarParameter.h" -#include "ImageUtils.h" -#include "PackageTools.h" -#include "AssetRegistryModule.h" -#include "UObject/MetaData.h" - -#if WITH_EDITOR - #include "Factories/MaterialFactoryNew.h" - #include "Factories/MaterialInstanceConstantFactoryNew.h" -#endif - -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeX = -400; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeY = -150; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepX = 220; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepY = 220; - -bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( - const HAPI_NodeId& InAssetId, - const FHoudiniPackageParams& InPackageParams, - const TArray& InUniqueMaterialIds, - const TArray& InUniqueMaterialInfos, - const TMap& InMaterials, - TMap& OutMaterials, - TArray& OutPackages, - const bool& bForceRecookAll, - bool bInTreatExistingMaterialsAsUpToDate) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMaterialTranslator::CreateHoudiniMaterials")); - - if (InUniqueMaterialIds.Num() <= 0) - return false; - - if (InUniqueMaterialInfos.Num() != InUniqueMaterialIds.Num()) - return false; - - // Empty returned materials. - OutMaterials.Empty(); - - // Update context for generated materials (will trigger when object goes out of scope). - FMaterialUpdateContext MaterialUpdateContext; - - // Default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - OutMaterials.Add(HAPI_UNREAL_DEFAULT_MATERIAL_NAME, DefaultMaterial); - - // Factory to create materials. - UMaterialFactoryNew * MaterialFactory = NewObject(); - MaterialFactory->AddToRoot(); - - for (int32 MaterialIdx = 0; MaterialIdx < InUniqueMaterialIds.Num(); MaterialIdx++) - { - HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; - - HAPI_MaterialInfo MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; - if (!MaterialInfo.exists) - { - // The material does not exist, - // we will use the default Houdini material in this case. - continue; - } - - // Get the material node's node information. - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &NodeInfo)) - { - continue; - } - - FString MaterialName = TEXT(""); - if (!FHoudiniEngineString::ToFString(NodeInfo.nameSH, MaterialName)) - { - // shouldnt happen, give a generic name - HOUDINI_LOG_WARNING(TEXT("Failed to retrieve material name!")); - MaterialName = TEXT("Material_") + FString::FromInt(MaterialInfo.nodeId); - } - - FString MaterialPathName = TEXT(""); - if (!FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, MaterialInfo, MaterialPathName)) - continue; - - // TODO: GetAssetName! - FString AssetName = TEXT("HoudiniAsset"); - - bool bCreatedNewMaterial = false; - - // TODO: Check existing material map!! - //UMaterial * Material = HoudiniCookParams.HoudiniCookManager ? Cast< UMaterial >(HoudiniCookParams.HoudiniCookManager->GetAssignmentMaterial(MaterialShopName)) : nullptr; - UMaterial * Material = nullptr; - UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); - if (FoundMaterial) - { - Material = Cast(*FoundMaterial); - } - - if (Material && !Material->IsPendingKill()) - { - // If cached material exists and has not changed, we can reuse it. - if ((bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll) - { - // We found cached material, we can reuse it. - OutMaterials.Add(MaterialPathName, Material); - continue; - } - } - else - { - // Previous Material was not found, we need to create a new one. - // TODO: Handle this! - //EObjectFlags ObjFlags = (HoudiniCookParams.MaterialAndTextureBakeMode == EBakeMode::Intermediate) ? RF_Transactional : RF_Public | RF_Standalone; - EObjectFlags ObjFlags = RF_Public | RF_Standalone; - - // Create material package and get material name. - FString MaterialPackageName; - UPackage * MaterialPackage = FHoudiniMaterialTranslator::CreatePackageForMaterial( - MaterialInfo.nodeId, MaterialName, InPackageParams, MaterialPackageName); - - Material = (UMaterial *)MaterialFactory->FactoryCreateNew( - UMaterial::StaticClass(), MaterialPackage, *MaterialPackageName, ObjFlags, NULL, GWarn); - - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialName); - - bCreatedNewMaterial = true; - } - - if (!Material || Material->IsPendingKill()) - continue; - - // Get the package and add it to our list - UPackage* Package = Material->GetOutermost(); - OutPackages.AddUnique(Package); - - /* - // TODO: This should be handled in the mesh/instance translator - // If this is an instancer material, enable the instancing flag. - if (UniqueInstancerMaterialIds.Contains(MaterialId)) - Material->bUsedWithInstancedStaticMeshes = true; - */ - - // Reset material expressions. - Material->Expressions.Empty(); - - // Generate various components for this material. - bool bMaterialComponentCreated = false; - int32 MaterialNodeY = FHoudiniMaterialTranslator::MaterialExpressionNodeY; - - // By default we mark material as opaque. Some of component creators can change this. - Material->BlendMode = BLEND_Opaque; - - // Extract diffuse plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract opacity plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract opacity mask plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract normal plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentNormal( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract specular plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract roughness plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract metallic plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract emissive plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Set other material properties. - Material->TwoSided = true; - Material->SetShadingModel(MSM_DefaultLit); - - // Schedule this material for update. - MaterialUpdateContext.AddMaterial(Material); - - // Cache material. - OutMaterials.Add(MaterialPathName, Material); - - // Propagate and trigger material updates. - if (bCreatedNewMaterial) - FAssetRegistryModule::AssetCreated(Material); - - Material->PreEditChange(nullptr); - Material->PostEditChange(); - Material->MarkPackageDirty(); - } - - MaterialFactory->RemoveFromRoot(); - - return true; -} - -// -bool -FHoudiniMaterialTranslator::CreateMaterialInstances( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& UniqueMaterialInstanceOverrides, - const TArray& InPackages, - const TMap& InMaterials, - TMap& OutMaterials, - const bool& bForceRecookAll) -{ - // Check the node ID is valid - if (InHGPO.AssetId < 0) - return false; - - // No material instance attributes - if (UniqueMaterialInstanceOverrides.Num() <= 0) - return false; - - // TODO: Improve! - // Get the material name from the material_instance attribute - // Since the material instance attribute can be set per primitive, it's going to be very difficult to know - // exactly where to look for the nth material instance. In order for the material slot to be created, - // we used the fact that the material instance attribute had to be different - // This is pretty hacky and we should probably require an extra material_instance_index attribute instead. - // as we can only create one instance of the same material, and cant get two slots for the same "source" material. - int32 MaterialIndex = 0; - for (TMap::TConstIterator Iter(UniqueMaterialInstanceOverrides); Iter; ++Iter) - { - FString CurrentSourceMaterial = Iter->Key; - if (CurrentSourceMaterial.IsEmpty()) - continue; - - // Try to find the material we want to create an instance of - UMaterialInterface* CurrentSourceMaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentSourceMaterial, nullptr, LOAD_NoWarn, nullptr)); - - if (!CurrentSourceMaterialInterface || CurrentSourceMaterialInterface->IsPendingKill()) - { - // Couldn't find the source material - HOUDINI_LOG_WARNING(TEXT("Couldn't find the source material %s to create a material instance."), *CurrentSourceMaterial); - continue; - } - - // Create/Retrieve the package for the MI - FString MaterialInstanceName; - FString MaterialInstanceNamePrefix = UPackageTools::SanitizePackageName( - CurrentSourceMaterialInterface->GetName() + TEXT("_instance_") + FString::FromInt(MaterialIndex)); - - // Increase the material index - MaterialIndex++; - - // See if we can find an existing package for that instance - UPackage * MaterialInstancePackage = nullptr; - UMaterialInterface * const * FoundMatPtr = InMaterials.Find(MaterialInstanceNamePrefix); - if (FoundMatPtr && *FoundMatPtr) - { - // We found an already existing MI, get its package - MaterialInstancePackage = Cast((*FoundMatPtr)->GetOuter()); - } - - if (MaterialInstancePackage) - { - MaterialInstanceName = MaterialInstancePackage->GetName(); - } - else - { - // We couldnt find the corresponding M_I package, so create a new one - MaterialInstancePackage = CreatePackageForMaterial(InHGPO.AssetId, MaterialInstanceNamePrefix, InPackageParams, MaterialInstanceName); - } - - // Couldn't create a package for that Material Instance - if (!MaterialInstancePackage) - continue; - - bool bNewMaterialCreated = false; - UMaterialInstanceConstant* NewMaterialInstance = LoadObject(MaterialInstancePackage, *MaterialInstanceName, nullptr, LOAD_None, nullptr); - if (!NewMaterialInstance) - { - // Factory to create materials. - UMaterialInstanceConstantFactoryNew* MaterialInstanceFactory = NewObject< UMaterialInstanceConstantFactoryNew >(); - if (!MaterialInstanceFactory) - continue; - - // Create the new material instance - MaterialInstanceFactory->AddToRoot(); - MaterialInstanceFactory->InitialParent = CurrentSourceMaterialInterface; - NewMaterialInstance = (UMaterialInstanceConstant*)MaterialInstanceFactory->FactoryCreateNew( - UMaterialInstanceConstant::StaticClass(), MaterialInstancePackage, FName(*MaterialInstanceName), - RF_Public | RF_Standalone, NULL, GWarn); - - if (NewMaterialInstance) - bNewMaterialCreated = true; - - MaterialInstanceFactory->RemoveFromRoot(); - } - - if (!NewMaterialInstance) - { - HOUDINI_LOG_WARNING(TEXT("Couldn't access the material instance for %s"), *CurrentSourceMaterial); - continue; - } - - // Update context for generated materials (will trigger when the object goes out of scope). - FMaterialUpdateContext MaterialUpdateContext; - - bool bModifiedMaterialParameters = false; - // See if we need to override some of the material instance's parameters - TArray AllMatParams; - // Get the detail material parameters - int32 ParamCount = FHoudiniEngineUtils::GetGenericAttributeList( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, - AllMatParams, HAPI_ATTROWNER_DETAIL, -1); - - // Then the primitive material parameters - int32 MaterialIndexToAttributeIndex = Iter->Value; - ParamCount += FHoudiniEngineUtils::GetGenericAttributeList( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, - AllMatParams, HAPI_ATTROWNER_PRIM, MaterialIndexToAttributeIndex); - - for (int32 ParamIdx = 0; ParamIdx < AllMatParams.Num(); ParamIdx++) - { - // Try to update the material instance parameter corresponding to the attribute - if (UpdateMaterialInstanceParameter(AllMatParams[ParamIdx], NewMaterialInstance, InPackages)) - bModifiedMaterialParameters = true; - } - - // Schedule this material for update if needed. - if (bNewMaterialCreated || bModifiedMaterialParameters) - MaterialUpdateContext.AddMaterialInstance(NewMaterialInstance); - - if (bNewMaterialCreated) - { - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialInstanceName); - // Notify registry that we have created a new material. - FAssetRegistryModule::AssetCreated(NewMaterialInstance); - } - - if (bNewMaterialCreated || bModifiedMaterialParameters) - { - // Dirty the material - NewMaterialInstance->MarkPackageDirty(); - - // Update the material instance - NewMaterialInstance->InitStaticPermutation(); - NewMaterialInstance->PreEditChange(nullptr); - NewMaterialInstance->PostEditChange(); - /* - // Automatically save the package to avoid further issue - MaterialInstancePackage->SetDirtyFlag( true ); - MaterialInstancePackage->FullyLoad(); - UPackage::SavePackage( - MaterialInstancePackage, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, - *FPackageName::LongPackageNameToFilename( MaterialInstancePackage->GetName(), FPackageName::GetAssetPackageExtension() ) ); - */ - } - - // Add the created material to the output assignement map - // Use the "source" material name as we want the instance to replace it - OutMaterials.Add(CurrentSourceMaterial, NewMaterialInstance); - } - - return true; -} - -bool -FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath) -{ - HAPI_MaterialInfo MaterialInfo; - FHoudiniApi::MaterialInfo_Init(&MaterialInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetMaterialInfo( - FHoudiniEngine::Get().GetSession(), InMaterialNodeId, - &MaterialInfo), false); - - return GetMaterialRelativePath(InAssetId, MaterialInfo, OutRelativePath); -} -bool -FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialInfo, FString& OutRelativePath) -{ - if (InAssetId < 0 || !InMaterialInfo.exists) - return false; - - // We want to get the asset node path so we can remove it from the material name - FString AssetNodeName = TEXT(""); - { - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); - - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo), false); - - FHoudiniEngineString::ToFString(AssetNodeInfo.internalNodePathSH, AssetNodeName); - } - - // Get the material name from the info - FString MaterialNodeName = TEXT(""); - { - HAPI_NodeInfo MaterialNodeInfo; - FHoudiniApi::NodeInfo_Init(&MaterialNodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &MaterialNodeInfo), false); - - FHoudiniEngineString::ToFString(MaterialNodeInfo.internalNodePathSH, MaterialNodeName); - } - - if (AssetNodeName.Len() > 0 && MaterialNodeName.Len() > 0) - { - // Remove AssetNodeName part from MaterialNodeName. Extra position is for separator. - OutRelativePath = MaterialNodeName.Mid(AssetNodeName.Len() + 1); - return true; - } - - return false; -} - - -UPackage* -FHoudiniMaterialTranslator::CreatePackageForMaterial( - const HAPI_NodeId& InMaterialNodeId, - const FString& InMaterialName, - const FHoudiniPackageParams& InPackageParams, - FString& OutMaterialName) -{ - FString MaterialDescriptor = TEXT("_material_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InMaterialName; - - FHoudiniPackageParams MyPackageParams = InPackageParams; - if (!MyPackageParams.ObjectName.IsEmpty()) - { - MyPackageParams.ObjectName += MaterialDescriptor; - } - else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) - { - MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + MaterialDescriptor; - } - else - { - MyPackageParams.ObjectName = MaterialDescriptor; - } - MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); - - return MyPackageParams.CreatePackageForObject(OutMaterialName); -} - - -UPackage* -FHoudiniMaterialTranslator::CreatePackageForTexture( - const HAPI_NodeId& InMaterialNodeId, - const FString& InTextureType, - const FHoudiniPackageParams& InPackageParams, - FString& OutTextureName) -{ - FString TextureInfoDescriptor = TEXT("_texture_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InTextureType; - FHoudiniPackageParams MyPackageParams = InPackageParams; - if (!MyPackageParams.ObjectName.IsEmpty()) - { - MyPackageParams.ObjectName += TextureInfoDescriptor; - } - else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) - { - MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + TextureInfoDescriptor; - } - else - { - MyPackageParams.ObjectName = TextureInfoDescriptor; - } - MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); - - return MyPackageParams.CreatePackageForObject(OutTextureName); -} - - -UTexture2D * -FHoudiniMaterialTranslator::CreateUnrealTexture( - UTexture2D* ExistingTexture, - const HAPI_ImageInfo& ImageInfo, - UPackage* Package, - const FString& TextureName, - const TArray& ImageBuffer, - const FCreateTexture2DParameters& TextureParameters, - const TextureGroup& LODGroup, - const FString& TextureType, - const FString& NodePath) -{ - if (!Package || Package->IsPendingKill()) - return nullptr; - - UTexture2D * Texture = nullptr; - if (ExistingTexture) - { - Texture = ExistingTexture; - } - else - { - // Create new texture object. - Texture = NewObject< UTexture2D >( - Package, UTexture2D::StaticClass(), *TextureName, - RF_Transactional); - - // Assign texture group. - Texture->LODGroup = LODGroup; - } - - // Add/Update meta information to package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *TextureName); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_NODE_PATH, *NodePath); - - // Initialize texture source. - Texture->Source.Init(ImageInfo.xRes, ImageInfo.yRes, 1, 1, TSF_BGRA8); - - // Lock the texture. - uint8 * MipData = Texture->Source.LockMip(0); - - // Create base map. - uint8* DestPtr = nullptr; - uint32 SrcWidth = ImageInfo.xRes; - uint32 SrcHeight = ImageInfo.yRes; - const char * SrcData = &ImageBuffer[0]; - - for (uint32 y = 0; y < SrcHeight; y++) - { - DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; - - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth * 4 + x * 4; - - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 2); // B - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 1); // G - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 0); // R - - if (TextureParameters.bUseAlpha) - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 3); // A - else - *DestPtr++ = 0xFF; - } - } - - bool bHasAlphaValue = false; - if (TextureParameters.bUseAlpha) - { - // See if there is an actual alpha value in the texture or if we can ignore the texture alpha - for (uint32 y = 0; y < SrcHeight; y++) - { - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth * 4 + x * 4; - if (*(uint8*)(SrcData + DataOffset + 3) != 0xFF) - { - bHasAlphaValue = true; - break; - } - } - - if (bHasAlphaValue) - break; - } - } - - // Unlock the texture. - Texture->Source.UnlockMip(0); - - // Texture creation parameters. - Texture->SRGB = TextureParameters.bSRGB; - Texture->CompressionSettings = TextureParameters.CompressionSettings; - Texture->CompressionNoAlpha = !bHasAlphaValue; - Texture->DeferCompression = TextureParameters.bDeferCompression; - - // Set the Source Guid/Hash if specified. - /* - if ( TextureParameters.SourceGuidHash.IsValid() ) - { - Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); - } - */ - - Texture->PostEditChange(); - - return Texture; -} - - - -bool -FHoudiniMaterialTranslator::HapiExtractImage( - const HAPI_ParmId& NodeParmId, - const HAPI_MaterialInfo& MaterialInfo, - const char * PlaneType, - const HAPI_ImageDataFormat& ImageDataFormat, - HAPI_ImagePacking ImagePacking, - bool bRenderToImage, - TArray& OutImageBuffer ) -{ - if (bRenderToImage) - { - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, NodeParmId), false); - } - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImageInfo), false); - - ImageInfo.dataFormat = ImageDataFormat; - ImageInfo.interleaved = true; - ImageInfo.packing = ImagePacking; - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetImageInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImageInfo), false); - - int32 ImageBufferSize = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ExtractImageToMemory( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, HAPI_RAW_FORMAT_NAME, - PlaneType, &ImageBufferSize), false); - - if (ImageBufferSize <= 0) - return false; - - OutImageBuffer.SetNumUninitialized(ImageBufferSize); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImageMemoryBuffer( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &OutImageBuffer[0], - ImageBufferSize), false); - - return true; -} - -bool -FHoudiniMaterialTranslator::HapiGetImagePlanes( - const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes) -{ - OutImagePlanes.Empty(); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, NodeParmId), false); - - int32 ImagePlaneCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlaneCount( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImagePlaneCount), false); - - if (ImagePlaneCount <= 0) - return true; - - TArray ImagePlaneStringHandles; - ImagePlaneStringHandles.SetNumZeroed(ImagePlaneCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlanes( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImagePlaneStringHandles[0], ImagePlaneCount), false); - - for (int32 IdxPlane = 0; IdxPlane < ImagePlaneStringHandles.Num(); IdxPlane++) - { - FString ValueString; - if(FHoudiniEngineString::ToFString(ImagePlaneStringHandles[IdxPlane], ValueString)) - OutImagePlanes.Add(ValueString); - } - - return true; -} - - -UMaterialExpression * -FHoudiniMaterialTranslator::MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass) -{ - if (!Expression) - return nullptr; - -#if WITH_EDITOR - if (ExpressionClass == Expression->GetClass()) - return Expression; - - // If this is a channel multiply expression, we can recurse. - UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast< UMaterialExpressionMultiply >(Expression); - if (MaterialExpressionMultiply) - { - { - UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->A.Expression; - if (MaterialExpression) - { - if (MaterialExpression->GetClass() == ExpressionClass) - return MaterialExpression; - - MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( - Cast(MaterialExpression), ExpressionClass); - - if (MaterialExpression) - return MaterialExpression; - } - } - - { - UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->B.Expression; - if (MaterialExpression) - { - if (MaterialExpression->GetClass() == ExpressionClass) - return MaterialExpression; - - MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( - Cast(MaterialExpression), ExpressionClass); - - if (MaterialExpression) - return MaterialExpression; - } - } - } -#endif - - return nullptr; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Names of generating Houdini parameters. - FString GeneratingParameterNameDiffuseTexture = TEXT(""); - FString GeneratingParameterNameUniformColor = TEXT(""); - FString GeneratingParameterNameVertexColor = TEXT(HAPI_UNREAL_ATTRIB_COLOR); - - // Diffuse texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Default; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // Attempt to look up previously created expressions. - UMaterialExpression * MaterialExpression = Material->BaseColor.Expression; - - // Locate sampling expression. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureSample = - Cast< UMaterialExpressionTextureSampleParameter2D >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // If texture sampling expression does exist, attempt to look up corresponding texture. - UTexture2D * TextureDiffuse = nullptr; - if (ExpressionTextureSample && !ExpressionTextureSample->IsPendingKill()) - TextureDiffuse = Cast< UTexture2D >(ExpressionTextureSample->Texture); - - // Locate uniform color expression. - UMaterialExpressionVectorParameter * ExpressionConstant4Vector = - Cast< UMaterialExpressionVectorParameter >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionVectorParameter::StaticClass())); - - // If uniform color expression does not exist, create it. - if (!ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill()) - { - ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( - Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); - ExpressionConstant4Vector->DefaultValue = FLinearColor::White; - } - - // Add expression. - Material->Expressions.Add(ExpressionConstant4Vector); - - // Locate vertex color expression. - UMaterialExpressionVertexColor * ExpressionVertexColor = - Cast< UMaterialExpressionVertexColor >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionVertexColor::StaticClass())); - - // If vertex color expression does not exist, create it. - if (!ExpressionVertexColor || ExpressionVertexColor->IsPendingKill()) - { - ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( - Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag); - ExpressionVertexColor->Desc = GeneratingParameterNameVertexColor; - } - - // Add expression. - Material->Expressions.Add(ExpressionVertexColor); - - // Material should have at least one multiply expression. - UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast(MaterialExpression); - if (!MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill()) - MaterialExpressionMultiply = NewObject( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - // Add expression. - Material->Expressions.Add(MaterialExpressionMultiply); - - // See if primary multiplication has secondary multiplication as A input. - UMaterialExpressionMultiply * MaterialExpressionMultiplySecondary = nullptr; - if (MaterialExpressionMultiply->A.Expression) - MaterialExpressionMultiplySecondary = - Cast(MaterialExpressionMultiply->A.Expression); - - // See if a diffuse texture is available. - HAPI_ParmId ParmDiffuseTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - - if (ParmDiffuseTextureId >= 0) - { - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - } - else - { - ParmDiffuseTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - - if (ParmDiffuseTextureId >= 0) - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - } - - // See if uniform color is available. - HAPI_ParmInfo ParmInfoDiffuseColor; - FHoudiniApi::ParmInfo_Init(&ParmInfoDiffuseColor); - HAPI_ParmId ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0, ParmInfoDiffuseColor); - - if (ParmDiffuseColorId >= 0) - { - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0); - } - else - { - ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1, ParmInfoDiffuseColor); - - if (ParmDiffuseColorId >= 0) - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1); - } - - // If we have diffuse texture parameter. - if (ParmDiffuseTextureId >= 0) - { - TArray< char > ImageBuffer; - - // Get image planes of diffuse map. - TArray< FString > DiffuseImagePlanes; - bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( - ParmDiffuseTextureId, InMaterialInfo, DiffuseImagePlanes); - - HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; - const char * PlaneType = ""; - - if (bFoundImagePlanes && DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))) - { - if (DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA))) - { - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - - // Material does use alpha. - CreateTexture2DParameters.bUseAlpha = true; - } - else - { - // We still need to have the Alpha plane, just not the CreateTexture2DParameters - // alpha option. This is because all texture data from Houdini Engine contains - // the alpha plane by default. - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - } - } - else - { - bFoundImagePlanes = false; - } - - // Retrieve color plane. - if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( - ParmDiffuseTextureId, InMaterialInfo, PlaneType, - HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) - { - UPackage * TextureDiffusePackage = nullptr; - if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) - TextureDiffusePackage = Cast(TextureDiffuse->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureDiffuseName; - bool bCreatedNewTextureDiffuse = false; - - // Create diffuse texture package, if this is a new diffuse texture. - if (!TextureDiffusePackage) - { - TextureDiffusePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, - InPackageParams, - TextureDiffuseName); - } - else if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureDiffuseName = TextureDiffuse->GetName(); - } - else - { - TextureDiffuseName = FPaths::GetBaseFilename(TextureDiffusePackage->GetName(), true); - } - - // Create diffuse texture, if we need to create one. - if (!TextureDiffuse || TextureDiffuse->IsPendingKill()) - bCreatedNewTextureDiffuse = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing diffuse texture, or create new one. - TextureDiffuse = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureDiffuse, - ImageInfo, - TextureDiffusePackage, - TextureDiffuseName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureDiffuse->SetFlags(RF_Public | RF_Standalone); - - // Create diffuse sampling expression, if needed. - if (!ExpressionTextureSample) - { - ExpressionTextureSample = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionTextureSample->Desc = GeneratingParameterNameDiffuseTexture; - ExpressionTextureSample->ParameterName = *GeneratingParameterNameDiffuseTexture; - ExpressionTextureSample->Texture = TextureDiffuse; - ExpressionTextureSample->SamplerType = SAMPLERTYPE_Color; - - // Add expression. - Material->Expressions.Add(ExpressionTextureSample); - - // Propagate and trigger diffuse texture updates. - if (bCreatedNewTextureDiffuse) - FAssetRegistryModule::AssetCreated(TextureDiffuse); - - TextureDiffuse->PreEditChange(nullptr); - TextureDiffuse->PostEditChange(); - TextureDiffuse->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureDiffusePackage); - } - } - - // If we have uniform color parameter. - if (ParmDiffuseColorId >= 0) - { - FLinearColor Color = FLinearColor::White; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&Color.R, - ParmInfoDiffuseColor.floatValuesIndex, ParmInfoDiffuseColor.size) == HAPI_RESULT_SUCCESS) - { - if (ParmInfoDiffuseColor.size == 3) - Color.A = 1.0f; - - // Record generating parameter. - ExpressionConstant4Vector->Desc = GeneratingParameterNameUniformColor; - ExpressionConstant4Vector->ParameterName = *GeneratingParameterNameUniformColor; - ExpressionConstant4Vector->DefaultValue = Color; - } - } - - // If we have have texture sample expression present, we need a secondary multiplication expression. - if (ExpressionTextureSample) - { - if (!MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary = NewObject< UMaterialExpressionMultiply >( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - // Add expression. - Material->Expressions.Add(MaterialExpressionMultiplySecondary); - } - } - else - { - // If secondary multiplication exists, but we have no sampling, we can free it. - if (MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary->A.Expression = nullptr; - MaterialExpressionMultiplySecondary->B.Expression = nullptr; - MaterialExpressionMultiplySecondary->ConditionalBeginDestroy(); - } - } - - float SecondaryExpressionScale = 1.0f; - if (MaterialExpressionMultiplySecondary) - SecondaryExpressionScale = 1.5f; - - // Create multiplication expression which has uniform color and vertex color. - MaterialExpressionMultiply->A.Expression = ExpressionConstant4Vector; - MaterialExpressionMultiply->B.Expression = ExpressionVertexColor; - - ExpressionConstant4Vector->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionConstant4Vector->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - ExpressionVertexColor->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionVertexColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiply->MaterialExpressionEditorY = - (ExpressionVertexColor->MaterialExpressionEditorY - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; - - // Hook up secondary multiplication expression to first one. - if (MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; - MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; - - ExpressionTextureSample->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiplySecondary->MaterialExpressionEditorY = - MaterialExpressionMultiply->MaterialExpressionEditorY + FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression. - Material->BaseColor.Expression = MaterialExpressionMultiplySecondary; - } - else - { - // Assign expression. - Material->BaseColor.Expression = MaterialExpressionMultiply; - - MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiply->MaterialExpressionEditorY = - (ExpressionVertexColor->MaterialExpressionEditorY - - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; - } - - return true; -} - - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - // Name of generating Houdini parameters. - FString GeneratingParameterNameTexture = TEXT(""); - - UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Opacity expressions. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; - UTexture2D * TextureOpacity = nullptr; - - // Opacity texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // See if opacity texture is available. - HAPI_ParmId ParmOpacityTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_0); - - if (ParmOpacityTextureId >= 0) - { - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_0); - } - else - { - ParmOpacityTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_1); - - if (ParmOpacityTextureId >= 0) - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_1); - } - - // If we have opacity texture parameter. - if (ParmOpacityTextureId >= 0) - { - TArray< char > ImageBuffer; - - // Get image planes of opacity map. - TArray< FString > OpacityImagePlanes; - bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( - ParmOpacityTextureId, InMaterialInfo, OpacityImagePlanes); - - HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; - const char * PlaneType = ""; - - bool bColorAlphaFound = (OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA)) && OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))); - - if (bFoundImagePlanes && bColorAlphaFound) - { - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - CreateTexture2DParameters.bUseAlpha = true; - } - else - { - bFoundImagePlanes = false; - } - - if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( - ParmOpacityTextureId, InMaterialInfo, PlaneType, - HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) - { - // Locate sampling expression. - ExpressionTextureOpacitySample = Cast< UMaterialExpressionTextureSampleParameter2D >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // Locate opacity texture, if valid. - if (ExpressionTextureOpacitySample) - TextureOpacity = Cast< UTexture2D >(ExpressionTextureOpacitySample->Texture); - - UPackage * TextureOpacityPackage = nullptr; - if (TextureOpacity) - TextureOpacityPackage = Cast< UPackage >(TextureOpacity->GetOuter()); - - HAPI_ImageInfo ImageInfo; - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureOpacityName; - bool bCreatedNewTextureOpacity = false; - - // Create opacity texture package, if this is a new opacity texture. - if (!TextureOpacityPackage) - { - TextureOpacityPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, - InPackageParams, - TextureOpacityName); - } - else if (TextureOpacity && !TextureOpacity->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureOpacityName = TextureOpacity->GetName(); - } - else - { - TextureOpacityName = FPaths::GetBaseFilename(TextureOpacityPackage->GetName(), true); - } - - // Create opacity texture, if we need to create one. - if (!TextureOpacity) - bCreatedNewTextureOpacity = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing opacity texture, or create new one. - TextureOpacity = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureOpacity, - ImageInfo, - TextureOpacityPackage, - TextureOpacityName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, - NodePath); - - // if (BakeMode == EBakeMode::CookToTemp) - TextureOpacity->SetFlags(RF_Public | RF_Standalone); - - // Create opacity sampling expression, if needed. - if (!ExpressionTextureOpacitySample) - { - ExpressionTextureOpacitySample = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionTextureOpacitySample->Desc = GeneratingParameterNameTexture; - ExpressionTextureOpacitySample->ParameterName = *GeneratingParameterNameTexture; - ExpressionTextureOpacitySample->Texture = TextureOpacity; - ExpressionTextureOpacitySample->SamplerType = SAMPLERTYPE_Grayscale; - - // Offset node placement. - ExpressionTextureOpacitySample->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionTextureOpacitySample->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Add expression. - Material->Expressions.Add(ExpressionTextureOpacitySample); - - // We need to set material type to masked. - TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput* ExpressionOutput = ExpressionOutputs.GetData(); - - Material->OpacityMask.Expression = ExpressionTextureOpacitySample; - Material->BlendMode = BLEND_Masked; - - Material->OpacityMask.Mask = ExpressionOutput->Mask; - Material->OpacityMask.MaskR = 1; - Material->OpacityMask.MaskG = 0; - Material->OpacityMask.MaskB = 0; - Material->OpacityMask.MaskA = 0; - - // Propagate and trigger opacity texture updates. - if (bCreatedNewTextureOpacity) - FAssetRegistryModule::AssetCreated(TextureOpacity); - - TextureOpacity->PreEditChange(nullptr); - TextureOpacity->PostEditChange(); - TextureOpacity->MarkPackageDirty(); - - bExpressionCreated = true; - } - - // Cache the texture package - OutPackages.AddUnique(TextureOpacityPackage); - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - float OpacityValue = 1.0f; - bool bNeedsTranslucency = false; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameters. - FString GeneratingParameterNameScalar = TEXT(""); - FString GeneratingParameterNameTexture = TEXT(""); - - UMaterialExpression * MaterialExpression = Material->Opacity.Expression; - - // Opacity expressions. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; - UMaterialExpressionScalarParameter * ExpressionScalarOpacity = nullptr; - UTexture2D * TextureOpacity = nullptr; - - // Opacity texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // If opacity sampling expression was not created, check if diffuse contains an alpha plane. - if (!ExpressionTextureOpacitySample) - { - UMaterialExpression * MaterialExpressionDiffuse = Material->BaseColor.Expression; - if (MaterialExpressionDiffuse) - { - // Locate diffuse sampling expression. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureDiffuseSample = - Cast< UMaterialExpressionTextureSampleParameter2D >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpressionDiffuse, - UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // See if there's an alpha plane in this expression's texture. - if (ExpressionTextureDiffuseSample) - { - UTexture2D * DiffuseTexture = Cast< UTexture2D >(ExpressionTextureDiffuseSample->Texture); - if (DiffuseTexture && !DiffuseTexture->CompressionNoAlpha) - { - // The diffuse texture has an alpha channel (that wasn't discarded), so we can use it - ExpressionTextureOpacitySample = ExpressionTextureDiffuseSample; - bNeedsTranslucency = true; - } - } - } - } - - // Retrieve opacity uniform parameter. - HAPI_ParmInfo ParmInfoOpacityValue; - FHoudiniApi::ParmInfo_Init(&ParmInfoOpacityValue); - HAPI_ParmId ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_0, ParmInfoOpacityValue); - - if (ParmOpacityValueId >= 0) - { - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_0); - } - else - { - ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_1, ParmInfoOpacityValue); - - if (ParmOpacityValueId >= 0) - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_1); - } - - if (ParmOpacityValueId >= 0) - { - if (ParmInfoOpacityValue.size > 0 && ParmInfoOpacityValue.floatValuesIndex >= 0) - { - float OpacityValueRetrieved = 1.0f; - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, - (float *)&OpacityValue, ParmInfoOpacityValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - if (!ExpressionScalarOpacity) - { - ExpressionScalarOpacity = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Clamp retrieved value. - OpacityValueRetrieved = FMath::Clamp< float >(OpacityValueRetrieved, 0.0f, 1.0f); - OpacityValue = OpacityValueRetrieved; - - // Set expression fields. - ExpressionScalarOpacity->DefaultValue = OpacityValue; - ExpressionScalarOpacity->SliderMin = 0.0f; - ExpressionScalarOpacity->SliderMax = 1.0f; - ExpressionScalarOpacity->Desc = GeneratingParameterNameScalar; - ExpressionScalarOpacity->ParameterName = *GeneratingParameterNameScalar; - - // Add expression. - Material->Expressions.Add(ExpressionScalarOpacity); - - // If alpha is less than 1, we need translucency. - bNeedsTranslucency |= (OpacityValue != 1.0f); - } - } - } - - if (bNeedsTranslucency) - Material->BlendMode = BLEND_Translucent; - - if (ExpressionScalarOpacity && ExpressionTextureOpacitySample) - { - // We have both alpha and alpha uniform, attempt to locate multiply expression. - UMaterialExpressionMultiply * ExpressionMultiply = - Cast< UMaterialExpressionMultiply >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, - UMaterialExpressionMultiply::StaticClass())); - - if (!ExpressionMultiply) - ExpressionMultiply = NewObject< UMaterialExpressionMultiply >( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - Material->Expressions.Add(ExpressionMultiply); - - TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); - - ExpressionMultiply->A.Expression = ExpressionTextureOpacitySample; - ExpressionMultiply->B.Expression = ExpressionScalarOpacity; - - Material->Opacity.Expression = ExpressionMultiply; - Material->Opacity.Mask = ExpressionOutput->Mask; - Material->Opacity.MaskR = 0; - Material->Opacity.MaskG = 0; - Material->Opacity.MaskB = 0; - Material->Opacity.MaskA = 1; - - ExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMultiply->MaterialExpressionEditorY = MaterialNodeY; - - ExpressionScalarOpacity->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX; - ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - bExpressionCreated = true; - } - else if (ExpressionScalarOpacity) - { - Material->Opacity.Expression = ExpressionScalarOpacity; - - ExpressionScalarOpacity->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - bExpressionCreated = true; - } - else if (ExpressionTextureOpacitySample) - { - TArray ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); - - Material->Opacity.Expression = ExpressionTextureOpacitySample; - Material->Opacity.Mask = ExpressionOutput->Mask; - Material->Opacity.MaskR = 0; - Material->Opacity.MaskG = 0; - Material->Opacity.MaskB = 0; - Material->Opacity.MaskA = 1; - - bExpressionCreated = true; - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentNormal( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - bool bTangentSpaceNormal = true; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Normal texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Normalmap; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if separate normal texture is available. - HAPI_ParmId ParmNameNormalId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_0); - - if (ParmNameNormalId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_0); - } - else - { - ParmNameNormalId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_1); - - if (ParmNameNormalId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_1); - } - - if (ParmNameNormalId >= 0) - { - // Retrieve space for this normal texture. - HAPI_ParmInfo ParmInfoNormalType; - FHoudiniApi::ParmInfo_Init(&ParmInfoNormalType); - int32 ParmNormalTypeId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); - - // Retrieve value for normal type choice list (if exists). - - if (ParmNormalTypeId >= 0) - { - FString NormalType = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT); - - if (ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0) - { - HAPI_StringHandle StringHandle; - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, false, &StringHandle, ParmInfoNormalType.stringValuesIndex, ParmInfoNormalType.size) == HAPI_RESULT_SUCCESS) - { - // Get the actual string value. - FString NormalTypeString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(NormalTypeString)) - NormalType = NormalTypeString; - } - } - - // Check if we require world space normals. - if (NormalType.Equals(TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD), ESearchCase::IgnoreCase)) - bTangentSpaceNormal = false; - } - - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameNormalId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); - - UTexture2D * TextureNormal = nullptr; - if (ExpressionNormal) - { - TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Normal.Expression) - { - Material->Normal.Expression->ConditionalBeginDestroy(); - Material->Normal.Expression = nullptr; - } - } - - UPackage * TextureNormalPackage = nullptr; - if (TextureNormal) - TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureNormalName; - bool bCreatedNewTextureNormal = false; - - // Create normal texture package, if this is a new normal texture. - if (!TextureNormalPackage) - { - TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - InPackageParams, - TextureNormalName); - } - else if (TextureNormal && !TextureNormal->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureNormalName = TextureNormal->GetName(); - } - else - { - TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); - } - - // Create normal texture, if we need to create one. - if (!TextureNormal) - bCreatedNewTextureNormal = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing normal texture, or create new one. - TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureNormal, - ImageInfo, - TextureNormalPackage, - TextureNormalName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_WorldNormalMap, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureNormal->SetFlags(RF_Public | RF_Standalone); - - // Create normal sampling expression, if needed. - if (!ExpressionNormal) - ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionNormal->Desc = GeneratingParameterName; - ExpressionNormal->ParameterName = *GeneratingParameterName; - - ExpressionNormal->Texture = TextureNormal; - ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; - - // Offset node placement. - ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Set normal space. - Material->bTangentSpaceNormal = bTangentSpaceNormal; - - // Assign expression to material. - Material->Expressions.Add(ExpressionNormal); - Material->Normal.Expression = ExpressionNormal; - - bExpressionCreated = true; - - // Propagate and trigger normal texture updates. - if (bCreatedNewTextureNormal) - FAssetRegistryModule::AssetCreated(TextureNormal); - - TextureNormal->PreEditChange(nullptr); - TextureNormal->PostEditChange(); - TextureNormal->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureNormalPackage); - } - } - - // If separate normal map was not found, see if normal plane exists in diffuse map. - if (!bExpressionCreated) - { - // See if diffuse texture is available. - HAPI_ParmId ParmNameBaseId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - - if (ParmNameBaseId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - } - else - { - ParmNameBaseId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - - if (ParmNameBaseId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - } - - if (ParmNameBaseId >= 0) - { - // Normal plane is available in diffuse map. - - TArray< char > ImageBuffer; - - // Retrieve color plane - this will contain normal data. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameBaseId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); - - UTexture2D * TextureNormal = nullptr; - if (ExpressionNormal) - { - TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Normal.Expression) - { - Material->Normal.Expression->ConditionalBeginDestroy(); - Material->Normal.Expression = nullptr; - } - } - - UPackage * TextureNormalPackage = nullptr; - if (TextureNormal) - TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureNormalName; - bool bCreatedNewTextureNormal = false; - - // Create normal texture package, if this is a new normal texture. - if (!TextureNormalPackage) - { - TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - InPackageParams, - TextureNormalName); - } - else if (TextureNormal && !TextureNormal->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureNormalName = TextureNormal->GetName(); - } - else - { - TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); - } - - // Create normal texture, if we need to create one. - if (!TextureNormal) - bCreatedNewTextureNormal = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing normal texture, or create new one. - TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureNormal, - ImageInfo, - TextureNormalPackage, - TextureNormalName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_WorldNormalMap, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureNormal->SetFlags(RF_Public | RF_Standalone); - - // Create normal sampling expression, if needed. - if (!ExpressionNormal) - ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionNormal->Desc = GeneratingParameterName; - ExpressionNormal->ParameterName = *GeneratingParameterName; - - ExpressionNormal->Texture = TextureNormal; - ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; - - // Offset node placement. - ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Set normal space. - Material->bTangentSpaceNormal = bTangentSpaceNormal; - - // Assign expression to material. - Material->Expressions.Add(ExpressionNormal); - Material->Normal.Expression = ExpressionNormal; - - // Propagate and trigger diffuse texture updates. - if (bCreatedNewTextureNormal) - FAssetRegistryModule::AssetCreated(TextureNormal); - - TextureNormal->PreEditChange(nullptr); - TextureNormal->PostEditChange(); - TextureNormal->MarkPackageDirty(); - - bExpressionCreated = true; - } - - // Cache the texture package - OutPackages.AddUnique(TextureNormalPackage); - } - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Specular texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if specular texture is available. - HAPI_ParmId ParmNameSpecularId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_0); - - if (ParmNameSpecularId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_0); - } - else - { - ParmNameSpecularId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_1); - - if (ParmNameSpecularId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_1); - } - - if (ParmNameSpecularId >= 0) - { - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameSpecularId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Specular.Expression); - - UTexture2D * TextureSpecular = nullptr; - if (ExpressionSpecular) - { - TextureSpecular = Cast< UTexture2D >(ExpressionSpecular->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Specular.Expression) - { - Material->Specular.Expression->ConditionalBeginDestroy(); - Material->Specular.Expression = nullptr; - } - } - - UPackage * TextureSpecularPackage = nullptr; - if (TextureSpecular) - TextureSpecularPackage = Cast< UPackage >(TextureSpecular->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureSpecularName; - bool bCreatedNewTextureSpecular = false; - - // Create specular texture package, if this is a new specular texture. - if (!TextureSpecularPackage) - { - TextureSpecularPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, - InPackageParams, - TextureSpecularName); - } - else if (TextureSpecular && !TextureSpecular->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureSpecularName = TextureSpecular->GetName(); - } - else - { - TextureSpecularName = FPaths::GetBaseFilename(TextureSpecularPackage->GetName(), true); - } - - // Create specular texture, if we need to create one. - if (!TextureSpecular) - bCreatedNewTextureSpecular = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing specular texture, or create new one. - TextureSpecular = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureSpecular, - ImageInfo, - TextureSpecularPackage, - TextureSpecularName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureSpecular->SetFlags(RF_Public | RF_Standalone); - - // Create specular sampling expression, if needed. - if (!ExpressionSpecular) - { - ExpressionSpecular = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionSpecular->Desc = GeneratingParameterName; - ExpressionSpecular->ParameterName = *GeneratingParameterName; - - ExpressionSpecular->Texture = TextureSpecular; - ExpressionSpecular->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionSpecular->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionSpecular->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionSpecular); - Material->Specular.Expression = ExpressionSpecular; - - bExpressionCreated = true; - - // Propagate and trigger specular texture updates. - if (bCreatedNewTextureSpecular) - FAssetRegistryModule::AssetCreated(TextureSpecular); - - TextureSpecular->PreEditChange(nullptr); - TextureSpecular->PostEditChange(); - TextureSpecular->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureSpecularPackage); - } - } - - HAPI_ParmInfo ParmInfoSpecularColor; - FHoudiniApi::ParmInfo_Init(&ParmInfoSpecularColor); - HAPI_ParmId ParmNameSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_0, ParmInfoSpecularColor); - - if (ParmNameSpecularColorId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_0); - } - else - { - ParmNameSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_1, ParmInfoSpecularColor); - - if (ParmNameSpecularColorId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_1); - } - - if (!bExpressionCreated && ParmNameSpecularColorId >= 0) - { - // Specular color is available. - - FLinearColor Color = FLinearColor::White; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmInfoSpecularColor.floatValuesIndex, ParmInfoSpecularColor.size) == HAPI_RESULT_SUCCESS) - { - if (ParmInfoSpecularColor.size == 3) - Color.A = 1.0f; - - UMaterialExpressionVectorParameter * ExpressionSpecularColor = - Cast< UMaterialExpressionVectorParameter >(Material->Specular.Expression); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionSpecularColor) - { - // Otherwise new expression is of a different type. - if (Material->Specular.Expression) - { - Material->Specular.Expression->ConditionalBeginDestroy(); - Material->Specular.Expression = nullptr; - } - - ExpressionSpecularColor = NewObject< UMaterialExpressionVectorParameter >( - Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionSpecularColor->Desc = GeneratingParameterName; - ExpressionSpecularColor->ParameterName = *GeneratingParameterName; - - ExpressionSpecularColor->DefaultValue = Color; - - // Offset node placement. - ExpressionSpecularColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionSpecularColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionSpecularColor); - Material->Specular.Expression = ExpressionSpecularColor; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Roughness texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if roughness texture is available. - HAPI_ParmId ParmNameRoughnessId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); - - if (ParmNameRoughnessId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); - } - else - { - ParmNameRoughnessId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); - - if (ParmNameRoughnessId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); - } - - if (ParmNameRoughnessId >= 0) - { - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameRoughnessId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer ) ) - { - UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Roughness.Expression); - - UTexture2D* TextureRoughness = nullptr; - if (ExpressionRoughness) - { - TextureRoughness = Cast< UTexture2D >(ExpressionRoughness->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Roughness.Expression) - { - Material->Roughness.Expression->ConditionalBeginDestroy(); - Material->Roughness.Expression = nullptr; - } - } - - UPackage * TextureRoughnessPackage = nullptr; - if (TextureRoughness) - TextureRoughnessPackage = Cast< UPackage >(TextureRoughness->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureRoughnessName; - bool bCreatedNewTextureRoughness = false; - - // Create roughness texture package, if this is a new roughness texture. - if (!TextureRoughnessPackage) - { - TextureRoughnessPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, - InPackageParams, - TextureRoughnessName); - } - else if (TextureRoughness && !TextureRoughness->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureRoughnessName = TextureRoughness->GetName(); - } - else - { - TextureRoughnessName = FPaths::GetBaseFilename(TextureRoughnessPackage->GetName(), true); - } - - // Create roughness texture, if we need to create one. - if (!TextureRoughness) - bCreatedNewTextureRoughness = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing roughness texture, or create new one. - TextureRoughness = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureRoughness, - ImageInfo, - TextureRoughnessPackage, - TextureRoughnessName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureRoughness->SetFlags(RF_Public | RF_Standalone); - - // Create roughness sampling expression, if needed. - if (!ExpressionRoughness) - ExpressionRoughness = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionRoughness->Desc = GeneratingParameterName; - ExpressionRoughness->ParameterName = *GeneratingParameterName; - - ExpressionRoughness->Texture = TextureRoughness; - ExpressionRoughness->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionRoughness->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionRoughness->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionRoughness); - Material->Roughness.Expression = ExpressionRoughness; - - bExpressionCreated = true; - - // Propagate and trigger roughness texture updates. - if (bCreatedNewTextureRoughness) - FAssetRegistryModule::AssetCreated(TextureRoughness); - - TextureRoughness->PreEditChange(nullptr); - TextureRoughness->PostEditChange(); - TextureRoughness->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureRoughnessPackage); - } - } - - HAPI_ParmInfo ParmInfoRoughnessValue; - FHoudiniApi::ParmInfo_Init(&ParmInfoRoughnessValue); - HAPI_ParmId ParmNameRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0, ParmInfoRoughnessValue); - - if (ParmNameRoughnessValueId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0); - } - else - { - ParmNameRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1, ParmInfoRoughnessValue); - - if (ParmNameRoughnessValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1); - } - - if (!bExpressionCreated && ParmNameRoughnessValueId >= 0) - { - // Roughness value is available. - - float RoughnessValue = 0.0f; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&RoughnessValue, - ParmInfoRoughnessValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - UMaterialExpressionScalarParameter * ExpressionRoughnessValue = - Cast< UMaterialExpressionScalarParameter >(Material->Roughness.Expression); - - // Clamp retrieved value. - RoughnessValue = FMath::Clamp< float >(RoughnessValue, 0.0f, 1.0f); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionRoughnessValue) - { - // Otherwise new expression is of a different type. - if (Material->Roughness.Expression) - { - Material->Roughness.Expression->ConditionalBeginDestroy(); - Material->Roughness.Expression = nullptr; - } - - ExpressionRoughnessValue = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionRoughnessValue->Desc = GeneratingParameterName; - ExpressionRoughnessValue->ParameterName = *GeneratingParameterName; - - ExpressionRoughnessValue->DefaultValue = RoughnessValue; - ExpressionRoughnessValue->SliderMin = 0.0f; - ExpressionRoughnessValue->SliderMax = 1.0f; - - // Offset node placement. - ExpressionRoughnessValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionRoughnessValue->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionRoughnessValue); - Material->Roughness.Expression = ExpressionRoughnessValue; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Metallic texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if metallic texture is available. - HAPI_ParmId ParmNameMetallicId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_METALLIC); - - if (ParmNameMetallicId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC); - } - - if (ParmNameMetallicId >= 0) - { - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameMetallicId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Metallic.Expression); - - UTexture2D * TextureMetallic = nullptr; - if (ExpressionMetallic) - { - TextureMetallic = Cast< UTexture2D >(ExpressionMetallic->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Metallic.Expression) - { - Material->Metallic.Expression->ConditionalBeginDestroy(); - Material->Metallic.Expression = nullptr; - } - } - - UPackage * TextureMetallicPackage = nullptr; - if (TextureMetallic) - TextureMetallicPackage = Cast< UPackage >(TextureMetallic->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureMetallicName; - bool bCreatedNewTextureMetallic = false; - - // Create metallic texture package, if this is a new metallic texture. - if (!TextureMetallicPackage) - { - TextureMetallicPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, - InPackageParams, - TextureMetallicName); - } - else if (TextureMetallic && !TextureMetallic->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureMetallicName = TextureMetallic->GetName(); - } - else - { - TextureMetallicName = FPaths::GetBaseFilename(TextureMetallicPackage->GetName(), true); - } - - // Create metallic texture, if we need to create one. - if (!TextureMetallic) - bCreatedNewTextureMetallic = true; - - // Get the node path to add it to the meta data - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing metallic texture, or create new one. - TextureMetallic = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureMetallic, - ImageInfo, - TextureMetallicPackage, - TextureMetallicName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureMetallic->SetFlags(RF_Public | RF_Standalone); - - // Create metallic sampling expression, if needed. - if (!ExpressionMetallic) - ExpressionMetallic = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionMetallic->Desc = GeneratingParameterName; - ExpressionMetallic->ParameterName = *GeneratingParameterName; - - ExpressionMetallic->Texture = TextureMetallic; - ExpressionMetallic->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionMetallic->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMetallic->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionMetallic); - Material->Metallic.Expression = ExpressionMetallic; - - bExpressionCreated = true; - - // Propagate and trigger metallic texture updates. - if (bCreatedNewTextureMetallic) - FAssetRegistryModule::AssetCreated(TextureMetallic); - - TextureMetallic->PreEditChange(nullptr); - TextureMetallic->PostEditChange(); - TextureMetallic->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureMetallicPackage); - } - } - - HAPI_ParmInfo ParmInfoMetallic; - FHoudiniApi::ParmInfo_Init(&ParmInfoMetallic); - HAPI_ParmId ParmNameMetallicValueIdx = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmInfoMetallic); - - if (ParmNameMetallicValueIdx >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); - - if (!bExpressionCreated && ParmNameMetallicValueIdx >= 0) - { - // Metallic value is available. - - float MetallicValue = 0.0f; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&MetallicValue, - ParmInfoMetallic.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - UMaterialExpressionScalarParameter * ExpressionMetallicValue = - Cast< UMaterialExpressionScalarParameter >(Material->Metallic.Expression); - - // Clamp retrieved value. - MetallicValue = FMath::Clamp< float >(MetallicValue, 0.0f, 1.0f); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionMetallicValue) - { - // Otherwise new expression is of a different type. - if (Material->Metallic.Expression) - { - Material->Metallic.Expression->ConditionalBeginDestroy(); - Material->Metallic.Expression = nullptr; - } - - ExpressionMetallicValue = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionMetallicValue->Desc = GeneratingParameterName; - ExpressionMetallicValue->ParameterName = *GeneratingParameterName; - - ExpressionMetallicValue->DefaultValue = MetallicValue; - ExpressionMetallicValue->SliderMin = 0.0f; - ExpressionMetallicValue->SliderMax = 1.0f; - - // Offset node placement. - ExpressionMetallicValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMetallicValue->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionMetallicValue); - Material->Metallic.Expression = ExpressionMetallicValue; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Emissive texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if emissive texture is available. - HAPI_ParmId ParmNameEmissiveId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_EMISSIVE); - - if (ParmNameEmissiveId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE); - } - - if (ParmNameEmissiveId >= 0) - { - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameEmissiveId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->EmissiveColor.Expression); - - UTexture2D * TextureEmissive = nullptr; - if (ExpressionEmissive) - { - TextureEmissive = Cast< UTexture2D >(ExpressionEmissive->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->EmissiveColor.Expression) - { - Material->EmissiveColor.Expression->ConditionalBeginDestroy(); - Material->EmissiveColor.Expression = nullptr; - } - } - - UPackage * TextureEmissivePackage = nullptr; - if (TextureEmissive) - TextureEmissivePackage = Cast< UPackage >(TextureEmissive->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureEmissiveName; - bool bCreatedNewTextureEmissive = false; - - // Create emissive texture package, if this is a new emissive texture. - if (!TextureEmissivePackage) - { - TextureEmissivePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, - InPackageParams, - TextureEmissiveName); - } - else if (TextureEmissive && !TextureEmissive->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureEmissiveName = TextureEmissive->GetName(); - } - else - { - TextureEmissiveName = FPaths::GetBaseFilename(TextureEmissivePackage->GetName(), true); - } - - // Create emissive texture, if we need to create one. - if (!TextureEmissive) - bCreatedNewTextureEmissive = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing emissive texture, or create new one. - TextureEmissive = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureEmissive, - ImageInfo, - TextureEmissivePackage, - TextureEmissiveName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureEmissive->SetFlags(RF_Public | RF_Standalone); - - // Create emissive sampling expression, if needed. - if (!ExpressionEmissive) - ExpressionEmissive = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionEmissive->Desc = GeneratingParameterName; - ExpressionEmissive->ParameterName = *GeneratingParameterName; - - ExpressionEmissive->Texture = TextureEmissive; - ExpressionEmissive->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionEmissive->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionEmissive->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionEmissive); - Material->EmissiveColor.Expression = ExpressionEmissive; - - bExpressionCreated = true; - - // Propagate and trigger metallic texture updates. - if (bCreatedNewTextureEmissive) - FAssetRegistryModule::AssetCreated(TextureEmissive); - - TextureEmissive->PreEditChange(nullptr); - TextureEmissive->PostEditChange(); - TextureEmissive->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureEmissivePackage); - } - } - - HAPI_ParmInfo ParmInfoEmissive; - FHoudiniApi::ParmInfo_Init(&ParmInfoEmissive); - HAPI_ParmId ParmNameEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0, ParmInfoEmissive); - - if (ParmNameEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0); - else - { - ParmNameEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1, ParmInfoEmissive); - - if (ParmNameEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1); - } - - if (!bExpressionCreated && ParmNameEmissiveValueId >= 0) - { - // Emissive color is available. - - FLinearColor Color = FLinearColor::White; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmInfoEmissive.floatValuesIndex, ParmInfoEmissive.size) == HAPI_RESULT_SUCCESS) - { - if (ParmInfoEmissive.size == 3) - Color.A = 1.0f; - - UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = - Cast< UMaterialExpressionConstant4Vector >(Material->EmissiveColor.Expression); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionEmissiveColor) - { - // Otherwise new expression is of a different type. - if (Material->EmissiveColor.Expression) - { - Material->EmissiveColor.Expression->ConditionalBeginDestroy(); - Material->EmissiveColor.Expression = nullptr; - } - - ExpressionEmissiveColor = NewObject< UMaterialExpressionConstant4Vector >( - Material, UMaterialExpressionConstant4Vector::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionEmissiveColor->Desc = GeneratingParameterName; - if (ExpressionEmissiveColor->CanRenameNode()) - ExpressionEmissiveColor->SetEditableName(*GeneratingParameterName); - - ExpressionEmissiveColor->Constant = Color; - - // Offset node placement. - ExpressionEmissiveColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionEmissiveColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionEmissiveColor); - Material->EmissiveColor.Expression = ExpressionEmissiveColor; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - - -bool -FHoudiniMaterialTranslator::UpdateMaterialInstanceParameter( - FHoudiniGenericAttribute MaterialParameter, - UMaterialInstanceConstant* MaterialInstance, - const TArray& InPackages) -{ - bool bParameterUpdated = false; - -#if WITH_EDITOR - if (!MaterialInstance) - return false; - - if (MaterialParameter.AttributeName.IsEmpty()) - return false; - - // The default material instance parameters needs to be handled manually as they cant be changed via generic SetParameters functions - if (MaterialParameter.AttributeName.Compare("CastShadowAsMasked", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideCastShadowAsMasked() && (MaterialInstance->GetCastShadowAsMasked() == Value)) - return false; - - MaterialInstance->SetOverrideCastShadowAsMasked(true); - MaterialInstance->SetCastShadowAsMasked(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("EmissiveBoost", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideEmissiveBoost() && (MaterialInstance->GetEmissiveBoost() == Value)) - return false; - - MaterialInstance->SetOverrideEmissiveBoost(true); - MaterialInstance->SetEmissiveBoost(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("DiffuseBoost", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideDiffuseBoost() && (MaterialInstance->GetDiffuseBoost() == Value)) - return false; - - MaterialInstance->SetOverrideDiffuseBoost(true); - MaterialInstance->SetDiffuseBoost(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("ExportResolutionScale", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideExportResolutionScale() && (MaterialInstance->GetExportResolutionScale() == Value)) - return false; - - MaterialInstance->SetOverrideExportResolutionScale(true); - MaterialInstance->SetExportResolutionScale(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("OpacityMaskClipValue", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue && (MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue = true; - MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("BlendMode", ESearchCase::IgnoreCase) == 0) - { - EBlendMode EnumValue = (EBlendMode)MaterialParameter.GetIntValue(); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - FString StringValue = MaterialParameter.GetStringValue(); - if (StringValue.Compare("Opaque", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Opaque; - else if (StringValue.Compare("Masked", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Masked; - else if (StringValue.Compare("Translucent", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Translucent; - else if (StringValue.Compare("Additive", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Additive; - else if (StringValue.Compare("Modulate", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Modulate; - else if (StringValue.StartsWith("Alpha", ESearchCase::IgnoreCase)) - EnumValue = EBlendMode::BLEND_AlphaComposite; - } - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_BlendMode && (MaterialInstance->BasePropertyOverrides.BlendMode == EnumValue)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = true; - MaterialInstance->BasePropertyOverrides.BlendMode = EnumValue; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("ShadingModel", ESearchCase::IgnoreCase) == 0) - { - EMaterialShadingModel EnumValue = (EMaterialShadingModel)MaterialParameter.GetIntValue(); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - FString StringValue = MaterialParameter.GetStringValue(); - if (StringValue.Compare("Unlit", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Unlit; - else if (StringValue.StartsWith("Default", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_DefaultLit; - else if (StringValue.Compare("Subsurface", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Subsurface; - else if (StringValue.StartsWith("Preintegrated", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_PreintegratedSkin; - else if (StringValue.StartsWith("Clear", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_ClearCoat; - else if (StringValue.Compare("SubsurfaceProfile", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_SubsurfaceProfile; - else if (StringValue.Compare("TwoSidedFoliage", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_TwoSidedFoliage; - else if (StringValue.Compare("Hair", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Hair; - else if (StringValue.Compare("Cloth", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Cloth; - else if (StringValue.Compare("Eye", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Eye; - } - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel && (MaterialInstance->BasePropertyOverrides.ShadingModel == EnumValue)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel = true; - MaterialInstance->BasePropertyOverrides.ShadingModel = EnumValue; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("TwoSided", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_TwoSided && (MaterialInstance->BasePropertyOverrides.TwoSided == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_TwoSided = true; - MaterialInstance->BasePropertyOverrides.TwoSided = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("DitheredLODTransition", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition && (MaterialInstance->BasePropertyOverrides.DitheredLODTransition == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition = true; - MaterialInstance->BasePropertyOverrides.DitheredLODTransition = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("PhysMaterial", ESearchCase::IgnoreCase) == 0) - { - // Try to load a Material corresponding to the parameter value - FString ParamValue = MaterialParameter.GetStringValue(); - UPhysicalMaterial* FoundPhysMaterial = Cast< UPhysicalMaterial >( - StaticLoadObject(UPhysicalMaterial::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); - - // Update the parameter value if necessary - if (!FoundPhysMaterial || (MaterialInstance->PhysMaterial == FoundPhysMaterial)) - return false; - - MaterialInstance->PhysMaterial = FoundPhysMaterial; - bParameterUpdated = true; - } - - if (bParameterUpdated) - return true; - - // Handling custom parameters - FName CurrentMatParamName = FName(*MaterialParameter.AttributeName); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - // String attributes are used for textures parameters - // We need to find the texture corresponding to the param - UTexture* FoundTexture = nullptr; - FString ParamValue = MaterialParameter.GetStringValue(); - - // Texture can either be already existing texture assets in UE4, or a newly generated textures by this asset - // Try to find the texture corresponding to the param value in the existing assets first. - FoundTexture = Cast( - StaticLoadObject(UTexture::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); - - if (!FoundTexture) - { - // We couldn't find a texture corresponding to the parameter in the existing UE4 assets - // Try to find the corresponding texture in the cooked temporary package we just generated - FoundTexture = FHoudiniMaterialTranslator::FindGeneratedTexture(ParamValue, InPackages); - } - - // Do not update if unnecessary - if (FoundTexture) - { - // Do not update if unnecessary - UTexture* OldTexture = nullptr; - bool FoundOldParam = MaterialInstance->GetTextureParameterValue(CurrentMatParamName, OldTexture); - if (FoundOldParam && (OldTexture == FoundTexture)) - return false; - - MaterialInstance->SetTextureParameterValueEditorOnly(CurrentMatParamName, FoundTexture); - bParameterUpdated = true; - } - } - else if (MaterialParameter.AttributeTupleSize == 1) - { - // Single attributes are either for scalar parameters or static switches - float OldValue; - bool FoundOldScalarParam = MaterialInstance->GetScalarParameterValue(CurrentMatParamName, OldValue); - if (FoundOldScalarParam) - { - // The material parameter is a scalar - float NewValue = (float)MaterialParameter.GetDoubleValue(); - - // Do not update if unnecessary - if (OldValue == NewValue) - return false; - - MaterialInstance->SetScalarParameterValueEditorOnly(CurrentMatParamName, NewValue); - bParameterUpdated = true; - } - else - { - // See if the underlying parameter is a static switch - bool NewBoolValue = MaterialParameter.GetBoolValue(); - - // We need to iterate over the material's static parameter set - FStaticParameterSet StaticParameters; - MaterialInstance->GetStaticParameterValues(StaticParameters); - - for (int32 SwitchParameterIdx = 0; SwitchParameterIdx < StaticParameters.StaticSwitchParameters.Num(); ++SwitchParameterIdx) - { - FStaticSwitchParameter& SwitchParameter = StaticParameters.StaticSwitchParameters[SwitchParameterIdx]; - if (SwitchParameter.ParameterInfo.Name != CurrentMatParamName) - continue; - - if (SwitchParameter.Value == NewBoolValue) - return false; - - SwitchParameter.Value = NewBoolValue; - SwitchParameter.bOverride = true; - - MaterialInstance->UpdateStaticPermutation(StaticParameters); - bParameterUpdated = true; - break; - } - } - } - else - { - // Tuple attributes are for vector parameters - FLinearColor NewLinearColor; - // if the attribute is stored in an int, we'll have to convert a color to a linear color - if (MaterialParameter.AttributeType == EAttribStorageType::INT || MaterialParameter.AttributeType == EAttribStorageType::INT64) - { - FColor IntColor; - IntColor.R = (int8)MaterialParameter.GetIntValue(0); - IntColor.G = (int8)MaterialParameter.GetIntValue(1); - IntColor.B = (int8)MaterialParameter.GetIntValue(2); - if (MaterialParameter.AttributeTupleSize >= 4) - IntColor.A = (int8)MaterialParameter.GetIntValue(3); - else - IntColor.A = 1; - - NewLinearColor = FLinearColor(IntColor); - } - else - { - NewLinearColor.R = (float)MaterialParameter.GetDoubleValue(0); - NewLinearColor.G = (float)MaterialParameter.GetDoubleValue(1); - NewLinearColor.B = (float)MaterialParameter.GetDoubleValue(2); - if (MaterialParameter.AttributeTupleSize >= 4) - NewLinearColor.A = (float)MaterialParameter.GetDoubleValue(3); - } - - // Do not update if unnecessary - FLinearColor OldValue; - bool FoundOldParam = MaterialInstance->GetVectorParameterValue(CurrentMatParamName, OldValue); - if (FoundOldParam && (OldValue == NewLinearColor)) - return false; - - MaterialInstance->SetVectorParameterValueEditorOnly(CurrentMatParamName, NewLinearColor); - bParameterUpdated = true; - } -#endif - - return bParameterUpdated; -} - - -UTexture* -FHoudiniMaterialTranslator::FindGeneratedTexture(const FString& TextureString, const TArray& InPackages) -{ - if (TextureString.IsEmpty()) - return nullptr; - - // Try to find the corresponding texture in the cooked temporary package generated by an HDA - UTexture* FoundTexture = nullptr; - for(const auto& CurrentPackage : InPackages) - { - // Iterate through the cooked packages - if (!CurrentPackage || CurrentPackage->IsPendingKill()) - continue; - - // First, check if the package contains a texture - FString CurrentPackageName = CurrentPackage->GetName(); - UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); - if (!PackageTexture) - continue; - - // Then check if the package's metadata match what we're looking for - // Make sure this texture was generated by Houdini Engine - UMetaData* MetaData = CurrentPackage->GetMetaData(); - if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - continue; - - // Get the texture type from the meta data - // Texture type store has meta data will be C_A, N, S, R etc.. - const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); - if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // Convert the texture type to a "friendly" version - // C_A to diffuse, N to Normal, S to Specular etc... - FString TextureTypeFriendlyString = TextureTypeString; - FString TextureTypeFriendlyAlternateString = TEXT(""); - if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) - { - TextureTypeFriendlyString = TEXT("diffuse"); - TextureTypeFriendlyAlternateString = TEXT("basecolor"); - } - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("normal"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("emissive"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("specular"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("roughness"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("metallic"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("opacity"); - - // See if we have a match between the texture string and the friendly name - if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) - { - FoundTexture = PackageTexture; - break; - } - - // Get the node path from the meta data - const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); - if (NodePath.IsEmpty()) - continue; - - // See if we have a match with the path and texture type - FString PathAndType = NodePath + TEXT("/") + TextureTypeString; - if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // See if we have a match with the friendly path and texture type - FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; - if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // Try the alternate friendly string - if (!TextureTypeFriendlyAlternateString.IsEmpty()) - { - PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; - if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - } - } - - return FoundTexture; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniMaterialTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPackageParams.h" + +#include "Materials/Material.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceConstant.h" +#include "PhysicalMaterials/PhysicalMaterial.h" + +#include "Materials/MaterialExpressionTextureSample.h" +#include "Materials/MaterialExpressionTextureCoordinate.h" +#include "Materials/MaterialExpressionConstant4Vector.h" +#include "Materials/MaterialExpressionConstant.h" +#include "Materials/MaterialExpressionMultiply.h" +#include "Materials/MaterialExpressionVertexColor.h" +#include "Materials/MaterialExpressionTextureSampleParameter2D.h" +#include "Materials/MaterialExpressionVectorParameter.h" +#include "Materials/MaterialExpressionScalarParameter.h" +#include "ImageUtils.h" +#include "PackageTools.h" +#include "AssetRegistryModule.h" +#include "UObject/MetaData.h" + +#if WITH_EDITOR + #include "Factories/MaterialFactoryNew.h" + #include "Factories/MaterialInstanceConstantFactoryNew.h" +#endif + +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeX = -400; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeY = -150; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepX = 220; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepY = 220; + +bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( + const HAPI_NodeId& InAssetId, + const FHoudiniPackageParams& InPackageParams, + const TArray& InUniqueMaterialIds, + const TArray& InUniqueMaterialInfos, + const TMap& InMaterials, + TMap& OutMaterials, + TArray& OutPackages, + const bool& bForceRecookAll, + bool bInTreatExistingMaterialsAsUpToDate) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMaterialTranslator::CreateHoudiniMaterials")); + + if (InUniqueMaterialIds.Num() <= 0) + return false; + + if (InUniqueMaterialInfos.Num() != InUniqueMaterialIds.Num()) + return false; + + // Empty returned materials. + OutMaterials.Empty(); + + // Update context for generated materials (will trigger when object goes out of scope). + FMaterialUpdateContext MaterialUpdateContext; + + // Default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + OutMaterials.Add(HAPI_UNREAL_DEFAULT_MATERIAL_NAME, DefaultMaterial); + + // Factory to create materials. + UMaterialFactoryNew * MaterialFactory = NewObject(); + MaterialFactory->AddToRoot(); + + for (int32 MaterialIdx = 0; MaterialIdx < InUniqueMaterialIds.Num(); MaterialIdx++) + { + HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; + + HAPI_MaterialInfo MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; + if (!MaterialInfo.exists) + { + // The material does not exist, + // we will use the default Houdini material in this case. + continue; + } + + // Get the material node's node information. + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &NodeInfo)) + { + continue; + } + + FString MaterialName = TEXT(""); + if (!FHoudiniEngineString::ToFString(NodeInfo.nameSH, MaterialName)) + { + // shouldnt happen, give a generic name + HOUDINI_LOG_WARNING(TEXT("Failed to retrieve material name!")); + MaterialName = TEXT("Material_") + FString::FromInt(MaterialInfo.nodeId); + } + + FString MaterialPathName = TEXT(""); + if (!FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, MaterialInfo, MaterialPathName)) + continue; + + // TODO: GetAssetName! + FString AssetName = TEXT("HoudiniAsset"); + + bool bCreatedNewMaterial = false; + + // TODO: Check existing material map!! + //UMaterial * Material = HoudiniCookParams.HoudiniCookManager ? Cast< UMaterial >(HoudiniCookParams.HoudiniCookManager->GetAssignmentMaterial(MaterialShopName)) : nullptr; + UMaterial * Material = nullptr; + UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); + if (FoundMaterial) + { + Material = Cast(*FoundMaterial); + } + + if (Material && !Material->IsPendingKill()) + { + // If cached material exists and has not changed, we can reuse it. + if ((bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll) + { + // We found cached material, we can reuse it. + OutMaterials.Add(MaterialPathName, Material); + continue; + } + } + else + { + // Previous Material was not found, we need to create a new one. + // TODO: Handle this! + //EObjectFlags ObjFlags = (HoudiniCookParams.MaterialAndTextureBakeMode == EBakeMode::Intermediate) ? RF_Transactional : RF_Public | RF_Standalone; + EObjectFlags ObjFlags = RF_Public | RF_Standalone; + + // Create material package and get material name. + FString MaterialPackageName; + UPackage * MaterialPackage = FHoudiniMaterialTranslator::CreatePackageForMaterial( + MaterialInfo.nodeId, MaterialName, InPackageParams, MaterialPackageName); + + Material = (UMaterial *)MaterialFactory->FactoryCreateNew( + UMaterial::StaticClass(), MaterialPackage, *MaterialPackageName, ObjFlags, NULL, GWarn); + + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialName); + + bCreatedNewMaterial = true; + } + + if (!Material || Material->IsPendingKill()) + continue; + + // Get the package and add it to our list + UPackage* Package = Material->GetOutermost(); + OutPackages.AddUnique(Package); + + /* + // TODO: This should be handled in the mesh/instance translator + // If this is an instancer material, enable the instancing flag. + if (UniqueInstancerMaterialIds.Contains(MaterialId)) + Material->bUsedWithInstancedStaticMeshes = true; + */ + + // Reset material expressions. + Material->Expressions.Empty(); + + // Generate various components for this material. + bool bMaterialComponentCreated = false; + int32 MaterialNodeY = FHoudiniMaterialTranslator::MaterialExpressionNodeY; + + // By default we mark material as opaque. Some of component creators can change this. + Material->BlendMode = BLEND_Opaque; + + // Extract diffuse plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract opacity plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract opacity mask plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract normal plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentNormal( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract specular plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract roughness plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract metallic plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract emissive plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Set other material properties. + Material->TwoSided = true; + Material->SetShadingModel(MSM_DefaultLit); + + // Schedule this material for update. + MaterialUpdateContext.AddMaterial(Material); + + // Cache material. + OutMaterials.Add(MaterialPathName, Material); + + // Propagate and trigger material updates. + if (bCreatedNewMaterial) + FAssetRegistryModule::AssetCreated(Material); + + Material->PreEditChange(nullptr); + Material->PostEditChange(); + Material->MarkPackageDirty(); + } + + MaterialFactory->RemoveFromRoot(); + + return true; +} + +// +bool +FHoudiniMaterialTranslator::CreateMaterialInstances( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& UniqueMaterialInstanceOverrides, + const TArray& InPackages, + const TMap& InMaterials, + TMap& OutMaterials, + const bool& bForceRecookAll) +{ + // Check the node ID is valid + if (InHGPO.AssetId < 0) + return false; + + // No material instance attributes + if (UniqueMaterialInstanceOverrides.Num() <= 0) + return false; + + // TODO: Improve! + // Get the material name from the material_instance attribute + // Since the material instance attribute can be set per primitive, it's going to be very difficult to know + // exactly where to look for the nth material instance. In order for the material slot to be created, + // we used the fact that the material instance attribute had to be different + // This is pretty hacky and we should probably require an extra material_instance_index attribute instead. + // as we can only create one instance of the same material, and cant get two slots for the same "source" material. + int32 MaterialIndex = 0; + for (TMap::TConstIterator Iter(UniqueMaterialInstanceOverrides); Iter; ++Iter) + { + FString CurrentSourceMaterial = Iter->Key; + if (CurrentSourceMaterial.IsEmpty()) + continue; + + // Try to find the material we want to create an instance of + UMaterialInterface* CurrentSourceMaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentSourceMaterial, nullptr, LOAD_NoWarn, nullptr)); + + if (!CurrentSourceMaterialInterface || CurrentSourceMaterialInterface->IsPendingKill()) + { + // Couldn't find the source material + HOUDINI_LOG_WARNING(TEXT("Couldn't find the source material %s to create a material instance."), *CurrentSourceMaterial); + continue; + } + + // Create/Retrieve the package for the MI + FString MaterialInstanceName; + FString MaterialInstanceNamePrefix = UPackageTools::SanitizePackageName( + CurrentSourceMaterialInterface->GetName() + TEXT("_instance_") + FString::FromInt(MaterialIndex)); + + // Increase the material index + MaterialIndex++; + + // See if we can find an existing package for that instance + UPackage * MaterialInstancePackage = nullptr; + UMaterialInterface * const * FoundMatPtr = InMaterials.Find(MaterialInstanceNamePrefix); + if (FoundMatPtr && *FoundMatPtr) + { + // We found an already existing MI, get its package + MaterialInstancePackage = Cast((*FoundMatPtr)->GetOuter()); + } + + if (MaterialInstancePackage) + { + MaterialInstanceName = MaterialInstancePackage->GetName(); + } + else + { + // We couldnt find the corresponding M_I package, so create a new one + MaterialInstancePackage = CreatePackageForMaterial(InHGPO.AssetId, MaterialInstanceNamePrefix, InPackageParams, MaterialInstanceName); + } + + // Couldn't create a package for that Material Instance + if (!MaterialInstancePackage) + continue; + + bool bNewMaterialCreated = false; + UMaterialInstanceConstant* NewMaterialInstance = LoadObject(MaterialInstancePackage, *MaterialInstanceName, nullptr, LOAD_None, nullptr); + if (!NewMaterialInstance) + { + // Factory to create materials. + UMaterialInstanceConstantFactoryNew* MaterialInstanceFactory = NewObject< UMaterialInstanceConstantFactoryNew >(); + if (!MaterialInstanceFactory) + continue; + + // Create the new material instance + MaterialInstanceFactory->AddToRoot(); + MaterialInstanceFactory->InitialParent = CurrentSourceMaterialInterface; + NewMaterialInstance = (UMaterialInstanceConstant*)MaterialInstanceFactory->FactoryCreateNew( + UMaterialInstanceConstant::StaticClass(), MaterialInstancePackage, FName(*MaterialInstanceName), + RF_Public | RF_Standalone, NULL, GWarn); + + if (NewMaterialInstance) + bNewMaterialCreated = true; + + MaterialInstanceFactory->RemoveFromRoot(); + } + + if (!NewMaterialInstance) + { + HOUDINI_LOG_WARNING(TEXT("Couldn't access the material instance for %s"), *CurrentSourceMaterial); + continue; + } + + // Update context for generated materials (will trigger when the object goes out of scope). + FMaterialUpdateContext MaterialUpdateContext; + + bool bModifiedMaterialParameters = false; + // See if we need to override some of the material instance's parameters + TArray AllMatParams; + // Get the detail material parameters + int32 ParamCount = FHoudiniEngineUtils::GetGenericAttributeList( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, + AllMatParams, HAPI_ATTROWNER_DETAIL, -1); + + // Then the primitive material parameters + int32 MaterialIndexToAttributeIndex = Iter->Value; + ParamCount += FHoudiniEngineUtils::GetGenericAttributeList( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, + AllMatParams, HAPI_ATTROWNER_PRIM, MaterialIndexToAttributeIndex); + + for (int32 ParamIdx = 0; ParamIdx < AllMatParams.Num(); ParamIdx++) + { + // Try to update the material instance parameter corresponding to the attribute + if (UpdateMaterialInstanceParameter(AllMatParams[ParamIdx], NewMaterialInstance, InPackages)) + bModifiedMaterialParameters = true; + } + + // Schedule this material for update if needed. + if (bNewMaterialCreated || bModifiedMaterialParameters) + MaterialUpdateContext.AddMaterialInstance(NewMaterialInstance); + + if (bNewMaterialCreated) + { + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialInstanceName); + // Notify registry that we have created a new material. + FAssetRegistryModule::AssetCreated(NewMaterialInstance); + } + + if (bNewMaterialCreated || bModifiedMaterialParameters) + { + // Dirty the material + NewMaterialInstance->MarkPackageDirty(); + + // Update the material instance + NewMaterialInstance->InitStaticPermutation(); + NewMaterialInstance->PreEditChange(nullptr); + NewMaterialInstance->PostEditChange(); + /* + // Automatically save the package to avoid further issue + MaterialInstancePackage->SetDirtyFlag( true ); + MaterialInstancePackage->FullyLoad(); + UPackage::SavePackage( + MaterialInstancePackage, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, + *FPackageName::LongPackageNameToFilename( MaterialInstancePackage->GetName(), FPackageName::GetAssetPackageExtension() ) ); + */ + } + + // Add the created material to the output assignement map + // Use the "source" material name as we want the instance to replace it + OutMaterials.Add(CurrentSourceMaterial, NewMaterialInstance); + } + + return true; +} + +bool +FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath) +{ + HAPI_MaterialInfo MaterialInfo; + FHoudiniApi::MaterialInfo_Init(&MaterialInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), InMaterialNodeId, + &MaterialInfo), false); + + return GetMaterialRelativePath(InAssetId, MaterialInfo, OutRelativePath); +} +bool +FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialInfo, FString& OutRelativePath) +{ + if (InAssetId < 0 || !InMaterialInfo.exists) + return false; + + // We want to get the asset node path so we can remove it from the material name + FString AssetNodeName = TEXT(""); + { + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); + + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo), false); + + FHoudiniEngineString::ToFString(AssetNodeInfo.internalNodePathSH, AssetNodeName); + } + + // Get the material name from the info + FString MaterialNodeName = TEXT(""); + { + HAPI_NodeInfo MaterialNodeInfo; + FHoudiniApi::NodeInfo_Init(&MaterialNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &MaterialNodeInfo), false); + + FHoudiniEngineString::ToFString(MaterialNodeInfo.internalNodePathSH, MaterialNodeName); + } + + if (AssetNodeName.Len() > 0 && MaterialNodeName.Len() > 0) + { + // Remove AssetNodeName part from MaterialNodeName. Extra position is for separator. + OutRelativePath = MaterialNodeName.Mid(AssetNodeName.Len() + 1); + return true; + } + + return false; +} + + +UPackage* +FHoudiniMaterialTranslator::CreatePackageForMaterial( + const HAPI_NodeId& InMaterialNodeId, + const FString& InMaterialName, + const FHoudiniPackageParams& InPackageParams, + FString& OutMaterialName) +{ + FString MaterialDescriptor = TEXT("_material_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InMaterialName; + + FHoudiniPackageParams MyPackageParams = InPackageParams; + if (!MyPackageParams.ObjectName.IsEmpty()) + { + MyPackageParams.ObjectName += MaterialDescriptor; + } + else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) + { + MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + MaterialDescriptor; + } + else + { + MyPackageParams.ObjectName = MaterialDescriptor; + } + MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); + + return MyPackageParams.CreatePackageForObject(OutMaterialName); +} + + +UPackage* +FHoudiniMaterialTranslator::CreatePackageForTexture( + const HAPI_NodeId& InMaterialNodeId, + const FString& InTextureType, + const FHoudiniPackageParams& InPackageParams, + FString& OutTextureName) +{ + FString TextureInfoDescriptor = TEXT("_texture_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InTextureType; + FHoudiniPackageParams MyPackageParams = InPackageParams; + if (!MyPackageParams.ObjectName.IsEmpty()) + { + MyPackageParams.ObjectName += TextureInfoDescriptor; + } + else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) + { + MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + TextureInfoDescriptor; + } + else + { + MyPackageParams.ObjectName = TextureInfoDescriptor; + } + MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); + + return MyPackageParams.CreatePackageForObject(OutTextureName); +} + + +UTexture2D * +FHoudiniMaterialTranslator::CreateUnrealTexture( + UTexture2D* ExistingTexture, + const HAPI_ImageInfo& ImageInfo, + UPackage* Package, + const FString& TextureName, + const TArray& ImageBuffer, + const FCreateTexture2DParameters& TextureParameters, + const TextureGroup& LODGroup, + const FString& TextureType, + const FString& NodePath) +{ + if (!Package || Package->IsPendingKill()) + return nullptr; + + UTexture2D * Texture = nullptr; + if (ExistingTexture) + { + Texture = ExistingTexture; + } + else + { + // Create new texture object. + Texture = NewObject< UTexture2D >( + Package, UTexture2D::StaticClass(), *TextureName, + RF_Transactional); + + // Assign texture group. + Texture->LODGroup = LODGroup; + } + + // Add/Update meta information to package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *TextureName); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_NODE_PATH, *NodePath); + + // Initialize texture source. + Texture->Source.Init(ImageInfo.xRes, ImageInfo.yRes, 1, 1, TSF_BGRA8); + + // Lock the texture. + uint8 * MipData = Texture->Source.LockMip(0); + + // Create base map. + uint8* DestPtr = nullptr; + uint32 SrcWidth = ImageInfo.xRes; + uint32 SrcHeight = ImageInfo.yRes; + const char * SrcData = &ImageBuffer[0]; + + for (uint32 y = 0; y < SrcHeight; y++) + { + DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; + + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth * 4 + x * 4; + + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 2); // B + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 1); // G + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 0); // R + + if (TextureParameters.bUseAlpha) + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 3); // A + else + *DestPtr++ = 0xFF; + } + } + + bool bHasAlphaValue = false; + if (TextureParameters.bUseAlpha) + { + // See if there is an actual alpha value in the texture or if we can ignore the texture alpha + for (uint32 y = 0; y < SrcHeight; y++) + { + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth * 4 + x * 4; + if (*(uint8*)(SrcData + DataOffset + 3) != 0xFF) + { + bHasAlphaValue = true; + break; + } + } + + if (bHasAlphaValue) + break; + } + } + + // Unlock the texture. + Texture->Source.UnlockMip(0); + + // Texture creation parameters. + Texture->SRGB = TextureParameters.bSRGB; + Texture->CompressionSettings = TextureParameters.CompressionSettings; + Texture->CompressionNoAlpha = !bHasAlphaValue; + Texture->DeferCompression = TextureParameters.bDeferCompression; + + // Set the Source Guid/Hash if specified. + /* + if ( TextureParameters.SourceGuidHash.IsValid() ) + { + Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); + } + */ + + Texture->PostEditChange(); + + return Texture; +} + + + +bool +FHoudiniMaterialTranslator::HapiExtractImage( + const HAPI_ParmId& NodeParmId, + const HAPI_MaterialInfo& MaterialInfo, + const char * PlaneType, + const HAPI_ImageDataFormat& ImageDataFormat, + HAPI_ImagePacking ImagePacking, + bool bRenderToImage, + TArray& OutImageBuffer ) +{ + if (bRenderToImage) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, NodeParmId), false); + } + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo), false); + + ImageInfo.dataFormat = ImageDataFormat; + ImageInfo.interleaved = true; + ImageInfo.packing = ImagePacking; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo), false); + + int32 ImageBufferSize = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ExtractImageToMemory( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, HAPI_RAW_FORMAT_NAME, + PlaneType, &ImageBufferSize), false); + + if (ImageBufferSize <= 0) + return false; + + OutImageBuffer.SetNumUninitialized(ImageBufferSize); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImageMemoryBuffer( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &OutImageBuffer[0], + ImageBufferSize), false); + + return true; +} + +bool +FHoudiniMaterialTranslator::HapiGetImagePlanes( + const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes) +{ + OutImagePlanes.Empty(); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, NodeParmId), false); + + int32 ImagePlaneCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlaneCount( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImagePlaneCount), false); + + if (ImagePlaneCount <= 0) + return true; + + TArray ImagePlaneStringHandles; + ImagePlaneStringHandles.SetNumZeroed(ImagePlaneCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlanes( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImagePlaneStringHandles[0], ImagePlaneCount), false); + + for (int32 IdxPlane = 0; IdxPlane < ImagePlaneStringHandles.Num(); IdxPlane++) + { + FString ValueString; + if(FHoudiniEngineString::ToFString(ImagePlaneStringHandles[IdxPlane], ValueString)) + OutImagePlanes.Add(ValueString); + } + + return true; +} + + +UMaterialExpression * +FHoudiniMaterialTranslator::MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass) +{ + if (!Expression) + return nullptr; + +#if WITH_EDITOR + if (ExpressionClass == Expression->GetClass()) + return Expression; + + // If this is a channel multiply expression, we can recurse. + UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast< UMaterialExpressionMultiply >(Expression); + if (MaterialExpressionMultiply) + { + { + UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->A.Expression; + if (MaterialExpression) + { + if (MaterialExpression->GetClass() == ExpressionClass) + return MaterialExpression; + + MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( + Cast(MaterialExpression), ExpressionClass); + + if (MaterialExpression) + return MaterialExpression; + } + } + + { + UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->B.Expression; + if (MaterialExpression) + { + if (MaterialExpression->GetClass() == ExpressionClass) + return MaterialExpression; + + MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( + Cast(MaterialExpression), ExpressionClass); + + if (MaterialExpression) + return MaterialExpression; + } + } + } +#endif + + return nullptr; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Names of generating Houdini parameters. + FString GeneratingParameterNameDiffuseTexture = TEXT(""); + FString GeneratingParameterNameUniformColor = TEXT(""); + FString GeneratingParameterNameVertexColor = TEXT(HAPI_UNREAL_ATTRIB_COLOR); + + // Diffuse texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Default; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // Attempt to look up previously created expressions. + UMaterialExpression * MaterialExpression = Material->BaseColor.Expression; + + // Locate sampling expression. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureSample = + Cast< UMaterialExpressionTextureSampleParameter2D >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // If texture sampling expression does exist, attempt to look up corresponding texture. + UTexture2D * TextureDiffuse = nullptr; + if (ExpressionTextureSample && !ExpressionTextureSample->IsPendingKill()) + TextureDiffuse = Cast< UTexture2D >(ExpressionTextureSample->Texture); + + // Locate uniform color expression. + UMaterialExpressionVectorParameter * ExpressionConstant4Vector = + Cast< UMaterialExpressionVectorParameter >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionVectorParameter::StaticClass())); + + // If uniform color expression does not exist, create it. + if (!ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill()) + { + ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( + Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); + ExpressionConstant4Vector->DefaultValue = FLinearColor::White; + } + + // Add expression. + Material->Expressions.Add(ExpressionConstant4Vector); + + // Locate vertex color expression. + UMaterialExpressionVertexColor * ExpressionVertexColor = + Cast< UMaterialExpressionVertexColor >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionVertexColor::StaticClass())); + + // If vertex color expression does not exist, create it. + if (!ExpressionVertexColor || ExpressionVertexColor->IsPendingKill()) + { + ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( + Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag); + ExpressionVertexColor->Desc = GeneratingParameterNameVertexColor; + } + + // Add expression. + Material->Expressions.Add(ExpressionVertexColor); + + // Material should have at least one multiply expression. + UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast(MaterialExpression); + if (!MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill()) + MaterialExpressionMultiply = NewObject( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + // Add expression. + Material->Expressions.Add(MaterialExpressionMultiply); + + // See if primary multiplication has secondary multiplication as A input. + UMaterialExpressionMultiply * MaterialExpressionMultiplySecondary = nullptr; + if (MaterialExpressionMultiply->A.Expression) + MaterialExpressionMultiplySecondary = + Cast(MaterialExpressionMultiply->A.Expression); + + // See if a diffuse texture is available. + HAPI_ParmId ParmDiffuseTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + + if (ParmDiffuseTextureId >= 0) + { + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + } + else + { + ParmDiffuseTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); + + if (ParmDiffuseTextureId >= 0) + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); + } + + // See if uniform color is available. + HAPI_ParmInfo ParmInfoDiffuseColor; + FHoudiniApi::ParmInfo_Init(&ParmInfoDiffuseColor); + HAPI_ParmId ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0, ParmInfoDiffuseColor); + + if (ParmDiffuseColorId >= 0) + { + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0); + } + else + { + ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1, ParmInfoDiffuseColor); + + if (ParmDiffuseColorId >= 0) + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1); + } + + // If we have diffuse texture parameter. + if (ParmDiffuseTextureId >= 0) + { + TArray< char > ImageBuffer; + + // Get image planes of diffuse map. + TArray< FString > DiffuseImagePlanes; + bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( + ParmDiffuseTextureId, InMaterialInfo, DiffuseImagePlanes); + + HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; + const char * PlaneType = ""; + + if (bFoundImagePlanes && DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))) + { + if (DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA))) + { + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + + // Material does use alpha. + CreateTexture2DParameters.bUseAlpha = true; + } + else + { + // We still need to have the Alpha plane, just not the CreateTexture2DParameters + // alpha option. This is because all texture data from Houdini Engine contains + // the alpha plane by default. + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + } + } + else + { + bFoundImagePlanes = false; + } + + // Retrieve color plane. + if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( + ParmDiffuseTextureId, InMaterialInfo, PlaneType, + HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) + { + UPackage * TextureDiffusePackage = nullptr; + if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + TextureDiffusePackage = Cast(TextureDiffuse->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureDiffuseName; + bool bCreatedNewTextureDiffuse = false; + + // Create diffuse texture package, if this is a new diffuse texture. + if (!TextureDiffusePackage) + { + TextureDiffusePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, + InPackageParams, + TextureDiffuseName); + } + else if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureDiffuseName = TextureDiffuse->GetName(); + } + else + { + TextureDiffuseName = FPaths::GetBaseFilename(TextureDiffusePackage->GetName(), true); + } + + // Create diffuse texture, if we need to create one. + if (!TextureDiffuse || TextureDiffuse->IsPendingKill()) + bCreatedNewTextureDiffuse = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing diffuse texture, or create new one. + TextureDiffuse = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureDiffuse, + ImageInfo, + TextureDiffusePackage, + TextureDiffuseName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureDiffuse->SetFlags(RF_Public | RF_Standalone); + + // Create diffuse sampling expression, if needed. + if (!ExpressionTextureSample) + { + ExpressionTextureSample = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionTextureSample->Desc = GeneratingParameterNameDiffuseTexture; + ExpressionTextureSample->ParameterName = *GeneratingParameterNameDiffuseTexture; + ExpressionTextureSample->Texture = TextureDiffuse; + ExpressionTextureSample->SamplerType = SAMPLERTYPE_Color; + + // Add expression. + Material->Expressions.Add(ExpressionTextureSample); + + // Propagate and trigger diffuse texture updates. + if (bCreatedNewTextureDiffuse) + FAssetRegistryModule::AssetCreated(TextureDiffuse); + + TextureDiffuse->PreEditChange(nullptr); + TextureDiffuse->PostEditChange(); + TextureDiffuse->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureDiffusePackage); + } + } + + // If we have uniform color parameter. + if (ParmDiffuseColorId >= 0) + { + FLinearColor Color = FLinearColor::White; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&Color.R, + ParmInfoDiffuseColor.floatValuesIndex, ParmInfoDiffuseColor.size) == HAPI_RESULT_SUCCESS) + { + if (ParmInfoDiffuseColor.size == 3) + Color.A = 1.0f; + + // Record generating parameter. + ExpressionConstant4Vector->Desc = GeneratingParameterNameUniformColor; + ExpressionConstant4Vector->ParameterName = *GeneratingParameterNameUniformColor; + ExpressionConstant4Vector->DefaultValue = Color; + } + } + + // If we have have texture sample expression present, we need a secondary multiplication expression. + if (ExpressionTextureSample) + { + if (!MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary = NewObject< UMaterialExpressionMultiply >( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + // Add expression. + Material->Expressions.Add(MaterialExpressionMultiplySecondary); + } + } + else + { + // If secondary multiplication exists, but we have no sampling, we can free it. + if (MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary->A.Expression = nullptr; + MaterialExpressionMultiplySecondary->B.Expression = nullptr; + MaterialExpressionMultiplySecondary->ConditionalBeginDestroy(); + } + } + + float SecondaryExpressionScale = 1.0f; + if (MaterialExpressionMultiplySecondary) + SecondaryExpressionScale = 1.5f; + + // Create multiplication expression which has uniform color and vertex color. + MaterialExpressionMultiply->A.Expression = ExpressionConstant4Vector; + MaterialExpressionMultiply->B.Expression = ExpressionVertexColor; + + ExpressionConstant4Vector->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionConstant4Vector->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + ExpressionVertexColor->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionVertexColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiply->MaterialExpressionEditorY = + (ExpressionVertexColor->MaterialExpressionEditorY - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; + + // Hook up secondary multiplication expression to first one. + if (MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; + MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; + + ExpressionTextureSample->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiplySecondary->MaterialExpressionEditorY = + MaterialExpressionMultiply->MaterialExpressionEditorY + FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression. + Material->BaseColor.Expression = MaterialExpressionMultiplySecondary; + } + else + { + // Assign expression. + Material->BaseColor.Expression = MaterialExpressionMultiply; + + MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiply->MaterialExpressionEditorY = + (ExpressionVertexColor->MaterialExpressionEditorY - + ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; + } + + return true; +} + + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + // Name of generating Houdini parameters. + FString GeneratingParameterNameTexture = TEXT(""); + + UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Opacity expressions. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; + UTexture2D * TextureOpacity = nullptr; + + // Opacity texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // See if opacity texture is available. + HAPI_ParmId ParmOpacityTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_0); + + if (ParmOpacityTextureId >= 0) + { + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_0); + } + else + { + ParmOpacityTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_1); + + if (ParmOpacityTextureId >= 0) + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_1); + } + + // If we have opacity texture parameter. + if (ParmOpacityTextureId >= 0) + { + TArray< char > ImageBuffer; + + // Get image planes of opacity map. + TArray< FString > OpacityImagePlanes; + bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( + ParmOpacityTextureId, InMaterialInfo, OpacityImagePlanes); + + HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; + const char * PlaneType = ""; + + bool bColorAlphaFound = (OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA)) && OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))); + + if (bFoundImagePlanes && bColorAlphaFound) + { + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + CreateTexture2DParameters.bUseAlpha = true; + } + else + { + bFoundImagePlanes = false; + } + + if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( + ParmOpacityTextureId, InMaterialInfo, PlaneType, + HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) + { + // Locate sampling expression. + ExpressionTextureOpacitySample = Cast< UMaterialExpressionTextureSampleParameter2D >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // Locate opacity texture, if valid. + if (ExpressionTextureOpacitySample) + TextureOpacity = Cast< UTexture2D >(ExpressionTextureOpacitySample->Texture); + + UPackage * TextureOpacityPackage = nullptr; + if (TextureOpacity) + TextureOpacityPackage = Cast< UPackage >(TextureOpacity->GetOuter()); + + HAPI_ImageInfo ImageInfo; + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureOpacityName; + bool bCreatedNewTextureOpacity = false; + + // Create opacity texture package, if this is a new opacity texture. + if (!TextureOpacityPackage) + { + TextureOpacityPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, + InPackageParams, + TextureOpacityName); + } + else if (TextureOpacity && !TextureOpacity->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureOpacityName = TextureOpacity->GetName(); + } + else + { + TextureOpacityName = FPaths::GetBaseFilename(TextureOpacityPackage->GetName(), true); + } + + // Create opacity texture, if we need to create one. + if (!TextureOpacity) + bCreatedNewTextureOpacity = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing opacity texture, or create new one. + TextureOpacity = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureOpacity, + ImageInfo, + TextureOpacityPackage, + TextureOpacityName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, + NodePath); + + // if (BakeMode == EBakeMode::CookToTemp) + TextureOpacity->SetFlags(RF_Public | RF_Standalone); + + // Create opacity sampling expression, if needed. + if (!ExpressionTextureOpacitySample) + { + ExpressionTextureOpacitySample = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionTextureOpacitySample->Desc = GeneratingParameterNameTexture; + ExpressionTextureOpacitySample->ParameterName = *GeneratingParameterNameTexture; + ExpressionTextureOpacitySample->Texture = TextureOpacity; + ExpressionTextureOpacitySample->SamplerType = SAMPLERTYPE_Grayscale; + + // Offset node placement. + ExpressionTextureOpacitySample->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionTextureOpacitySample->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Add expression. + Material->Expressions.Add(ExpressionTextureOpacitySample); + + // We need to set material type to masked. + TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput* ExpressionOutput = ExpressionOutputs.GetData(); + + Material->OpacityMask.Expression = ExpressionTextureOpacitySample; + Material->BlendMode = BLEND_Masked; + + Material->OpacityMask.Mask = ExpressionOutput->Mask; + Material->OpacityMask.MaskR = 1; + Material->OpacityMask.MaskG = 0; + Material->OpacityMask.MaskB = 0; + Material->OpacityMask.MaskA = 0; + + // Propagate and trigger opacity texture updates. + if (bCreatedNewTextureOpacity) + FAssetRegistryModule::AssetCreated(TextureOpacity); + + TextureOpacity->PreEditChange(nullptr); + TextureOpacity->PostEditChange(); + TextureOpacity->MarkPackageDirty(); + + bExpressionCreated = true; + } + + // Cache the texture package + OutPackages.AddUnique(TextureOpacityPackage); + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + float OpacityValue = 1.0f; + bool bNeedsTranslucency = false; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameters. + FString GeneratingParameterNameScalar = TEXT(""); + FString GeneratingParameterNameTexture = TEXT(""); + + UMaterialExpression * MaterialExpression = Material->Opacity.Expression; + + // Opacity expressions. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; + UMaterialExpressionScalarParameter * ExpressionScalarOpacity = nullptr; + UTexture2D * TextureOpacity = nullptr; + + // Opacity texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // If opacity sampling expression was not created, check if diffuse contains an alpha plane. + if (!ExpressionTextureOpacitySample) + { + UMaterialExpression * MaterialExpressionDiffuse = Material->BaseColor.Expression; + if (MaterialExpressionDiffuse) + { + // Locate diffuse sampling expression. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureDiffuseSample = + Cast< UMaterialExpressionTextureSampleParameter2D >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpressionDiffuse, + UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // See if there's an alpha plane in this expression's texture. + if (ExpressionTextureDiffuseSample) + { + UTexture2D * DiffuseTexture = Cast< UTexture2D >(ExpressionTextureDiffuseSample->Texture); + if (DiffuseTexture && !DiffuseTexture->CompressionNoAlpha) + { + // The diffuse texture has an alpha channel (that wasn't discarded), so we can use it + ExpressionTextureOpacitySample = ExpressionTextureDiffuseSample; + bNeedsTranslucency = true; + } + } + } + } + + // Retrieve opacity uniform parameter. + HAPI_ParmInfo ParmInfoOpacityValue; + FHoudiniApi::ParmInfo_Init(&ParmInfoOpacityValue); + HAPI_ParmId ParmOpacityValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_0, ParmInfoOpacityValue); + + if (ParmOpacityValueId >= 0) + { + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_0); + } + else + { + ParmOpacityValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_1, ParmInfoOpacityValue); + + if (ParmOpacityValueId >= 0) + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_1); + } + + if (ParmOpacityValueId >= 0) + { + if (ParmInfoOpacityValue.size > 0 && ParmInfoOpacityValue.floatValuesIndex >= 0) + { + float OpacityValueRetrieved = 1.0f; + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, + (float *)&OpacityValue, ParmInfoOpacityValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + if (!ExpressionScalarOpacity) + { + ExpressionScalarOpacity = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Clamp retrieved value. + OpacityValueRetrieved = FMath::Clamp< float >(OpacityValueRetrieved, 0.0f, 1.0f); + OpacityValue = OpacityValueRetrieved; + + // Set expression fields. + ExpressionScalarOpacity->DefaultValue = OpacityValue; + ExpressionScalarOpacity->SliderMin = 0.0f; + ExpressionScalarOpacity->SliderMax = 1.0f; + ExpressionScalarOpacity->Desc = GeneratingParameterNameScalar; + ExpressionScalarOpacity->ParameterName = *GeneratingParameterNameScalar; + + // Add expression. + Material->Expressions.Add(ExpressionScalarOpacity); + + // If alpha is less than 1, we need translucency. + bNeedsTranslucency |= (OpacityValue != 1.0f); + } + } + } + + if (bNeedsTranslucency) + Material->BlendMode = BLEND_Translucent; + + if (ExpressionScalarOpacity && ExpressionTextureOpacitySample) + { + // We have both alpha and alpha uniform, attempt to locate multiply expression. + UMaterialExpressionMultiply * ExpressionMultiply = + Cast< UMaterialExpressionMultiply >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, + UMaterialExpressionMultiply::StaticClass())); + + if (!ExpressionMultiply) + ExpressionMultiply = NewObject< UMaterialExpressionMultiply >( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + Material->Expressions.Add(ExpressionMultiply); + + TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); + + ExpressionMultiply->A.Expression = ExpressionTextureOpacitySample; + ExpressionMultiply->B.Expression = ExpressionScalarOpacity; + + Material->Opacity.Expression = ExpressionMultiply; + Material->Opacity.Mask = ExpressionOutput->Mask; + Material->Opacity.MaskR = 0; + Material->Opacity.MaskG = 0; + Material->Opacity.MaskB = 0; + Material->Opacity.MaskA = 1; + + ExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMultiply->MaterialExpressionEditorY = MaterialNodeY; + + ExpressionScalarOpacity->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX; + ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + bExpressionCreated = true; + } + else if (ExpressionScalarOpacity) + { + Material->Opacity.Expression = ExpressionScalarOpacity; + + ExpressionScalarOpacity->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + bExpressionCreated = true; + } + else if (ExpressionTextureOpacitySample) + { + TArray ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); + + Material->Opacity.Expression = ExpressionTextureOpacitySample; + Material->Opacity.Mask = ExpressionOutput->Mask; + Material->Opacity.MaskR = 0; + Material->Opacity.MaskG = 0; + Material->Opacity.MaskB = 0; + Material->Opacity.MaskA = 1; + + bExpressionCreated = true; + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentNormal( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + bool bTangentSpaceNormal = true; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Normal texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Normalmap; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if separate normal texture is available. + HAPI_ParmId ParmNameNormalId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_0); + + if (ParmNameNormalId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_0); + } + else + { + ParmNameNormalId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_1); + + if (ParmNameNormalId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_1); + } + + if (ParmNameNormalId >= 0) + { + // Retrieve space for this normal texture. + HAPI_ParmInfo ParmInfoNormalType; + FHoudiniApi::ParmInfo_Init(&ParmInfoNormalType); + int32 ParmNormalTypeId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); + + // Retrieve value for normal type choice list (if exists). + + if (ParmNormalTypeId >= 0) + { + FString NormalType = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT); + + if (ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0) + { + HAPI_StringHandle StringHandle; + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, false, &StringHandle, ParmInfoNormalType.stringValuesIndex, ParmInfoNormalType.size) == HAPI_RESULT_SUCCESS) + { + // Get the actual string value. + FString NormalTypeString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(NormalTypeString)) + NormalType = NormalTypeString; + } + } + + // Check if we require world space normals. + if (NormalType.Equals(TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD), ESearchCase::IgnoreCase)) + bTangentSpaceNormal = false; + } + + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameNormalId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); + + UTexture2D * TextureNormal = nullptr; + if (ExpressionNormal) + { + TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Normal.Expression) + { + Material->Normal.Expression->ConditionalBeginDestroy(); + Material->Normal.Expression = nullptr; + } + } + + UPackage * TextureNormalPackage = nullptr; + if (TextureNormal) + TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureNormalName; + bool bCreatedNewTextureNormal = false; + + // Create normal texture package, if this is a new normal texture. + if (!TextureNormalPackage) + { + TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + InPackageParams, + TextureNormalName); + } + else if (TextureNormal && !TextureNormal->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureNormalName = TextureNormal->GetName(); + } + else + { + TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); + } + + // Create normal texture, if we need to create one. + if (!TextureNormal) + bCreatedNewTextureNormal = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing normal texture, or create new one. + TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureNormal, + ImageInfo, + TextureNormalPackage, + TextureNormalName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_WorldNormalMap, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureNormal->SetFlags(RF_Public | RF_Standalone); + + // Create normal sampling expression, if needed. + if (!ExpressionNormal) + ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionNormal->Desc = GeneratingParameterName; + ExpressionNormal->ParameterName = *GeneratingParameterName; + + ExpressionNormal->Texture = TextureNormal; + ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; + + // Offset node placement. + ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Set normal space. + Material->bTangentSpaceNormal = bTangentSpaceNormal; + + // Assign expression to material. + Material->Expressions.Add(ExpressionNormal); + Material->Normal.Expression = ExpressionNormal; + + bExpressionCreated = true; + + // Propagate and trigger normal texture updates. + if (bCreatedNewTextureNormal) + FAssetRegistryModule::AssetCreated(TextureNormal); + + TextureNormal->PreEditChange(nullptr); + TextureNormal->PostEditChange(); + TextureNormal->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureNormalPackage); + } + } + + // If separate normal map was not found, see if normal plane exists in diffuse map. + if (!bExpressionCreated) + { + // See if diffuse texture is available. + HAPI_ParmId ParmNameBaseId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + + if (ParmNameBaseId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + } + else + { + ParmNameBaseId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); + + if (ParmNameBaseId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); + } + + if (ParmNameBaseId >= 0) + { + // Normal plane is available in diffuse map. + + TArray< char > ImageBuffer; + + // Retrieve color plane - this will contain normal data. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameBaseId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); + + UTexture2D * TextureNormal = nullptr; + if (ExpressionNormal) + { + TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Normal.Expression) + { + Material->Normal.Expression->ConditionalBeginDestroy(); + Material->Normal.Expression = nullptr; + } + } + + UPackage * TextureNormalPackage = nullptr; + if (TextureNormal) + TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureNormalName; + bool bCreatedNewTextureNormal = false; + + // Create normal texture package, if this is a new normal texture. + if (!TextureNormalPackage) + { + TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + InPackageParams, + TextureNormalName); + } + else if (TextureNormal && !TextureNormal->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureNormalName = TextureNormal->GetName(); + } + else + { + TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); + } + + // Create normal texture, if we need to create one. + if (!TextureNormal) + bCreatedNewTextureNormal = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing normal texture, or create new one. + TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureNormal, + ImageInfo, + TextureNormalPackage, + TextureNormalName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_WorldNormalMap, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureNormal->SetFlags(RF_Public | RF_Standalone); + + // Create normal sampling expression, if needed. + if (!ExpressionNormal) + ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionNormal->Desc = GeneratingParameterName; + ExpressionNormal->ParameterName = *GeneratingParameterName; + + ExpressionNormal->Texture = TextureNormal; + ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; + + // Offset node placement. + ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Set normal space. + Material->bTangentSpaceNormal = bTangentSpaceNormal; + + // Assign expression to material. + Material->Expressions.Add(ExpressionNormal); + Material->Normal.Expression = ExpressionNormal; + + // Propagate and trigger diffuse texture updates. + if (bCreatedNewTextureNormal) + FAssetRegistryModule::AssetCreated(TextureNormal); + + TextureNormal->PreEditChange(nullptr); + TextureNormal->PostEditChange(); + TextureNormal->MarkPackageDirty(); + + bExpressionCreated = true; + } + + // Cache the texture package + OutPackages.AddUnique(TextureNormalPackage); + } + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Specular texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if specular texture is available. + HAPI_ParmId ParmNameSpecularId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_0); + + if (ParmNameSpecularId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_0); + } + else + { + ParmNameSpecularId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_1); + + if (ParmNameSpecularId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_1); + } + + if (ParmNameSpecularId >= 0) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameSpecularId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Specular.Expression); + + UTexture2D * TextureSpecular = nullptr; + if (ExpressionSpecular) + { + TextureSpecular = Cast< UTexture2D >(ExpressionSpecular->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Specular.Expression) + { + Material->Specular.Expression->ConditionalBeginDestroy(); + Material->Specular.Expression = nullptr; + } + } + + UPackage * TextureSpecularPackage = nullptr; + if (TextureSpecular) + TextureSpecularPackage = Cast< UPackage >(TextureSpecular->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureSpecularName; + bool bCreatedNewTextureSpecular = false; + + // Create specular texture package, if this is a new specular texture. + if (!TextureSpecularPackage) + { + TextureSpecularPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, + InPackageParams, + TextureSpecularName); + } + else if (TextureSpecular && !TextureSpecular->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureSpecularName = TextureSpecular->GetName(); + } + else + { + TextureSpecularName = FPaths::GetBaseFilename(TextureSpecularPackage->GetName(), true); + } + + // Create specular texture, if we need to create one. + if (!TextureSpecular) + bCreatedNewTextureSpecular = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing specular texture, or create new one. + TextureSpecular = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureSpecular, + ImageInfo, + TextureSpecularPackage, + TextureSpecularName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureSpecular->SetFlags(RF_Public | RF_Standalone); + + // Create specular sampling expression, if needed. + if (!ExpressionSpecular) + { + ExpressionSpecular = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionSpecular->Desc = GeneratingParameterName; + ExpressionSpecular->ParameterName = *GeneratingParameterName; + + ExpressionSpecular->Texture = TextureSpecular; + ExpressionSpecular->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionSpecular->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionSpecular->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionSpecular); + Material->Specular.Expression = ExpressionSpecular; + + bExpressionCreated = true; + + // Propagate and trigger specular texture updates. + if (bCreatedNewTextureSpecular) + FAssetRegistryModule::AssetCreated(TextureSpecular); + + TextureSpecular->PreEditChange(nullptr); + TextureSpecular->PostEditChange(); + TextureSpecular->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureSpecularPackage); + } + } + + HAPI_ParmInfo ParmInfoSpecularColor; + FHoudiniApi::ParmInfo_Init(&ParmInfoSpecularColor); + HAPI_ParmId ParmNameSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_0, ParmInfoSpecularColor); + + if (ParmNameSpecularColorId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_0); + } + else + { + ParmNameSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_1, ParmInfoSpecularColor); + + if (ParmNameSpecularColorId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_1); + } + + if (!bExpressionCreated && ParmNameSpecularColorId >= 0) + { + // Specular color is available. + + FLinearColor Color = FLinearColor::White; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, + ParmInfoSpecularColor.floatValuesIndex, ParmInfoSpecularColor.size) == HAPI_RESULT_SUCCESS) + { + if (ParmInfoSpecularColor.size == 3) + Color.A = 1.0f; + + UMaterialExpressionVectorParameter * ExpressionSpecularColor = + Cast< UMaterialExpressionVectorParameter >(Material->Specular.Expression); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionSpecularColor) + { + // Otherwise new expression is of a different type. + if (Material->Specular.Expression) + { + Material->Specular.Expression->ConditionalBeginDestroy(); + Material->Specular.Expression = nullptr; + } + + ExpressionSpecularColor = NewObject< UMaterialExpressionVectorParameter >( + Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionSpecularColor->Desc = GeneratingParameterName; + ExpressionSpecularColor->ParameterName = *GeneratingParameterName; + + ExpressionSpecularColor->DefaultValue = Color; + + // Offset node placement. + ExpressionSpecularColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionSpecularColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionSpecularColor); + Material->Specular.Expression = ExpressionSpecularColor; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Roughness texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if roughness texture is available. + HAPI_ParmId ParmNameRoughnessId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); + + if (ParmNameRoughnessId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); + } + else + { + ParmNameRoughnessId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); + + if (ParmNameRoughnessId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); + } + + if (ParmNameRoughnessId >= 0) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameRoughnessId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer ) ) + { + UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Roughness.Expression); + + UTexture2D* TextureRoughness = nullptr; + if (ExpressionRoughness) + { + TextureRoughness = Cast< UTexture2D >(ExpressionRoughness->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Roughness.Expression) + { + Material->Roughness.Expression->ConditionalBeginDestroy(); + Material->Roughness.Expression = nullptr; + } + } + + UPackage * TextureRoughnessPackage = nullptr; + if (TextureRoughness) + TextureRoughnessPackage = Cast< UPackage >(TextureRoughness->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureRoughnessName; + bool bCreatedNewTextureRoughness = false; + + // Create roughness texture package, if this is a new roughness texture. + if (!TextureRoughnessPackage) + { + TextureRoughnessPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, + InPackageParams, + TextureRoughnessName); + } + else if (TextureRoughness && !TextureRoughness->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureRoughnessName = TextureRoughness->GetName(); + } + else + { + TextureRoughnessName = FPaths::GetBaseFilename(TextureRoughnessPackage->GetName(), true); + } + + // Create roughness texture, if we need to create one. + if (!TextureRoughness) + bCreatedNewTextureRoughness = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing roughness texture, or create new one. + TextureRoughness = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureRoughness, + ImageInfo, + TextureRoughnessPackage, + TextureRoughnessName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureRoughness->SetFlags(RF_Public | RF_Standalone); + + // Create roughness sampling expression, if needed. + if (!ExpressionRoughness) + ExpressionRoughness = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionRoughness->Desc = GeneratingParameterName; + ExpressionRoughness->ParameterName = *GeneratingParameterName; + + ExpressionRoughness->Texture = TextureRoughness; + ExpressionRoughness->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionRoughness->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionRoughness->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionRoughness); + Material->Roughness.Expression = ExpressionRoughness; + + bExpressionCreated = true; + + // Propagate and trigger roughness texture updates. + if (bCreatedNewTextureRoughness) + FAssetRegistryModule::AssetCreated(TextureRoughness); + + TextureRoughness->PreEditChange(nullptr); + TextureRoughness->PostEditChange(); + TextureRoughness->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureRoughnessPackage); + } + } + + HAPI_ParmInfo ParmInfoRoughnessValue; + FHoudiniApi::ParmInfo_Init(&ParmInfoRoughnessValue); + HAPI_ParmId ParmNameRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0, ParmInfoRoughnessValue); + + if (ParmNameRoughnessValueId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0); + } + else + { + ParmNameRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1, ParmInfoRoughnessValue); + + if (ParmNameRoughnessValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1); + } + + if (!bExpressionCreated && ParmNameRoughnessValueId >= 0) + { + // Roughness value is available. + + float RoughnessValue = 0.0f; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&RoughnessValue, + ParmInfoRoughnessValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + UMaterialExpressionScalarParameter * ExpressionRoughnessValue = + Cast< UMaterialExpressionScalarParameter >(Material->Roughness.Expression); + + // Clamp retrieved value. + RoughnessValue = FMath::Clamp< float >(RoughnessValue, 0.0f, 1.0f); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionRoughnessValue) + { + // Otherwise new expression is of a different type. + if (Material->Roughness.Expression) + { + Material->Roughness.Expression->ConditionalBeginDestroy(); + Material->Roughness.Expression = nullptr; + } + + ExpressionRoughnessValue = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionRoughnessValue->Desc = GeneratingParameterName; + ExpressionRoughnessValue->ParameterName = *GeneratingParameterName; + + ExpressionRoughnessValue->DefaultValue = RoughnessValue; + ExpressionRoughnessValue->SliderMin = 0.0f; + ExpressionRoughnessValue->SliderMax = 1.0f; + + // Offset node placement. + ExpressionRoughnessValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionRoughnessValue->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionRoughnessValue); + Material->Roughness.Expression = ExpressionRoughnessValue; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Metallic texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if metallic texture is available. + HAPI_ParmId ParmNameMetallicId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_METALLIC); + + if (ParmNameMetallicId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC); + } + + if (ParmNameMetallicId >= 0) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameMetallicId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Metallic.Expression); + + UTexture2D * TextureMetallic = nullptr; + if (ExpressionMetallic) + { + TextureMetallic = Cast< UTexture2D >(ExpressionMetallic->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Metallic.Expression) + { + Material->Metallic.Expression->ConditionalBeginDestroy(); + Material->Metallic.Expression = nullptr; + } + } + + UPackage * TextureMetallicPackage = nullptr; + if (TextureMetallic) + TextureMetallicPackage = Cast< UPackage >(TextureMetallic->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureMetallicName; + bool bCreatedNewTextureMetallic = false; + + // Create metallic texture package, if this is a new metallic texture. + if (!TextureMetallicPackage) + { + TextureMetallicPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, + InPackageParams, + TextureMetallicName); + } + else if (TextureMetallic && !TextureMetallic->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureMetallicName = TextureMetallic->GetName(); + } + else + { + TextureMetallicName = FPaths::GetBaseFilename(TextureMetallicPackage->GetName(), true); + } + + // Create metallic texture, if we need to create one. + if (!TextureMetallic) + bCreatedNewTextureMetallic = true; + + // Get the node path to add it to the meta data + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing metallic texture, or create new one. + TextureMetallic = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureMetallic, + ImageInfo, + TextureMetallicPackage, + TextureMetallicName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureMetallic->SetFlags(RF_Public | RF_Standalone); + + // Create metallic sampling expression, if needed. + if (!ExpressionMetallic) + ExpressionMetallic = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionMetallic->Desc = GeneratingParameterName; + ExpressionMetallic->ParameterName = *GeneratingParameterName; + + ExpressionMetallic->Texture = TextureMetallic; + ExpressionMetallic->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionMetallic->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMetallic->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionMetallic); + Material->Metallic.Expression = ExpressionMetallic; + + bExpressionCreated = true; + + // Propagate and trigger metallic texture updates. + if (bCreatedNewTextureMetallic) + FAssetRegistryModule::AssetCreated(TextureMetallic); + + TextureMetallic->PreEditChange(nullptr); + TextureMetallic->PostEditChange(); + TextureMetallic->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureMetallicPackage); + } + } + + HAPI_ParmInfo ParmInfoMetallic; + FHoudiniApi::ParmInfo_Init(&ParmInfoMetallic); + HAPI_ParmId ParmNameMetallicValueIdx = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmInfoMetallic); + + if (ParmNameMetallicValueIdx >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); + + if (!bExpressionCreated && ParmNameMetallicValueIdx >= 0) + { + // Metallic value is available. + + float MetallicValue = 0.0f; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&MetallicValue, + ParmInfoMetallic.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + UMaterialExpressionScalarParameter * ExpressionMetallicValue = + Cast< UMaterialExpressionScalarParameter >(Material->Metallic.Expression); + + // Clamp retrieved value. + MetallicValue = FMath::Clamp< float >(MetallicValue, 0.0f, 1.0f); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionMetallicValue) + { + // Otherwise new expression is of a different type. + if (Material->Metallic.Expression) + { + Material->Metallic.Expression->ConditionalBeginDestroy(); + Material->Metallic.Expression = nullptr; + } + + ExpressionMetallicValue = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionMetallicValue->Desc = GeneratingParameterName; + ExpressionMetallicValue->ParameterName = *GeneratingParameterName; + + ExpressionMetallicValue->DefaultValue = MetallicValue; + ExpressionMetallicValue->SliderMin = 0.0f; + ExpressionMetallicValue->SliderMax = 1.0f; + + // Offset node placement. + ExpressionMetallicValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMetallicValue->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionMetallicValue); + Material->Metallic.Expression = ExpressionMetallicValue; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Emissive texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if emissive texture is available. + HAPI_ParmId ParmNameEmissiveId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_EMISSIVE); + + if (ParmNameEmissiveId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE); + } + + if (ParmNameEmissiveId >= 0) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameEmissiveId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->EmissiveColor.Expression); + + UTexture2D * TextureEmissive = nullptr; + if (ExpressionEmissive) + { + TextureEmissive = Cast< UTexture2D >(ExpressionEmissive->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->EmissiveColor.Expression) + { + Material->EmissiveColor.Expression->ConditionalBeginDestroy(); + Material->EmissiveColor.Expression = nullptr; + } + } + + UPackage * TextureEmissivePackage = nullptr; + if (TextureEmissive) + TextureEmissivePackage = Cast< UPackage >(TextureEmissive->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureEmissiveName; + bool bCreatedNewTextureEmissive = false; + + // Create emissive texture package, if this is a new emissive texture. + if (!TextureEmissivePackage) + { + TextureEmissivePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, + InPackageParams, + TextureEmissiveName); + } + else if (TextureEmissive && !TextureEmissive->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureEmissiveName = TextureEmissive->GetName(); + } + else + { + TextureEmissiveName = FPaths::GetBaseFilename(TextureEmissivePackage->GetName(), true); + } + + // Create emissive texture, if we need to create one. + if (!TextureEmissive) + bCreatedNewTextureEmissive = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing emissive texture, or create new one. + TextureEmissive = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureEmissive, + ImageInfo, + TextureEmissivePackage, + TextureEmissiveName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureEmissive->SetFlags(RF_Public | RF_Standalone); + + // Create emissive sampling expression, if needed. + if (!ExpressionEmissive) + ExpressionEmissive = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionEmissive->Desc = GeneratingParameterName; + ExpressionEmissive->ParameterName = *GeneratingParameterName; + + ExpressionEmissive->Texture = TextureEmissive; + ExpressionEmissive->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionEmissive->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionEmissive->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionEmissive); + Material->EmissiveColor.Expression = ExpressionEmissive; + + bExpressionCreated = true; + + // Propagate and trigger metallic texture updates. + if (bCreatedNewTextureEmissive) + FAssetRegistryModule::AssetCreated(TextureEmissive); + + TextureEmissive->PreEditChange(nullptr); + TextureEmissive->PostEditChange(); + TextureEmissive->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureEmissivePackage); + } + } + + HAPI_ParmInfo ParmInfoEmissive; + FHoudiniApi::ParmInfo_Init(&ParmInfoEmissive); + HAPI_ParmId ParmNameEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0, ParmInfoEmissive); + + if (ParmNameEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0); + else + { + ParmNameEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1, ParmInfoEmissive); + + if (ParmNameEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1); + } + + if (!bExpressionCreated && ParmNameEmissiveValueId >= 0) + { + // Emissive color is available. + + FLinearColor Color = FLinearColor::White; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, + ParmInfoEmissive.floatValuesIndex, ParmInfoEmissive.size) == HAPI_RESULT_SUCCESS) + { + if (ParmInfoEmissive.size == 3) + Color.A = 1.0f; + + UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = + Cast< UMaterialExpressionConstant4Vector >(Material->EmissiveColor.Expression); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionEmissiveColor) + { + // Otherwise new expression is of a different type. + if (Material->EmissiveColor.Expression) + { + Material->EmissiveColor.Expression->ConditionalBeginDestroy(); + Material->EmissiveColor.Expression = nullptr; + } + + ExpressionEmissiveColor = NewObject< UMaterialExpressionConstant4Vector >( + Material, UMaterialExpressionConstant4Vector::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionEmissiveColor->Desc = GeneratingParameterName; + if (ExpressionEmissiveColor->CanRenameNode()) + ExpressionEmissiveColor->SetEditableName(*GeneratingParameterName); + + ExpressionEmissiveColor->Constant = Color; + + // Offset node placement. + ExpressionEmissiveColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionEmissiveColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionEmissiveColor); + Material->EmissiveColor.Expression = ExpressionEmissiveColor; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + + +bool +FHoudiniMaterialTranslator::UpdateMaterialInstanceParameter( + FHoudiniGenericAttribute MaterialParameter, + UMaterialInstanceConstant* MaterialInstance, + const TArray& InPackages) +{ + bool bParameterUpdated = false; + +#if WITH_EDITOR + if (!MaterialInstance) + return false; + + if (MaterialParameter.AttributeName.IsEmpty()) + return false; + + // The default material instance parameters needs to be handled manually as they cant be changed via generic SetParameters functions + if (MaterialParameter.AttributeName.Compare("CastShadowAsMasked", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideCastShadowAsMasked() && (MaterialInstance->GetCastShadowAsMasked() == Value)) + return false; + + MaterialInstance->SetOverrideCastShadowAsMasked(true); + MaterialInstance->SetCastShadowAsMasked(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("EmissiveBoost", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideEmissiveBoost() && (MaterialInstance->GetEmissiveBoost() == Value)) + return false; + + MaterialInstance->SetOverrideEmissiveBoost(true); + MaterialInstance->SetEmissiveBoost(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("DiffuseBoost", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideDiffuseBoost() && (MaterialInstance->GetDiffuseBoost() == Value)) + return false; + + MaterialInstance->SetOverrideDiffuseBoost(true); + MaterialInstance->SetDiffuseBoost(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("ExportResolutionScale", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideExportResolutionScale() && (MaterialInstance->GetExportResolutionScale() == Value)) + return false; + + MaterialInstance->SetOverrideExportResolutionScale(true); + MaterialInstance->SetExportResolutionScale(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("OpacityMaskClipValue", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue && (MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue = true; + MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("BlendMode", ESearchCase::IgnoreCase) == 0) + { + EBlendMode EnumValue = (EBlendMode)MaterialParameter.GetIntValue(); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + FString StringValue = MaterialParameter.GetStringValue(); + if (StringValue.Compare("Opaque", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Opaque; + else if (StringValue.Compare("Masked", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Masked; + else if (StringValue.Compare("Translucent", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Translucent; + else if (StringValue.Compare("Additive", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Additive; + else if (StringValue.Compare("Modulate", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Modulate; + else if (StringValue.StartsWith("Alpha", ESearchCase::IgnoreCase)) + EnumValue = EBlendMode::BLEND_AlphaComposite; + } + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_BlendMode && (MaterialInstance->BasePropertyOverrides.BlendMode == EnumValue)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = true; + MaterialInstance->BasePropertyOverrides.BlendMode = EnumValue; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("ShadingModel", ESearchCase::IgnoreCase) == 0) + { + EMaterialShadingModel EnumValue = (EMaterialShadingModel)MaterialParameter.GetIntValue(); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + FString StringValue = MaterialParameter.GetStringValue(); + if (StringValue.Compare("Unlit", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Unlit; + else if (StringValue.StartsWith("Default", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_DefaultLit; + else if (StringValue.Compare("Subsurface", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Subsurface; + else if (StringValue.StartsWith("Preintegrated", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_PreintegratedSkin; + else if (StringValue.StartsWith("Clear", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_ClearCoat; + else if (StringValue.Compare("SubsurfaceProfile", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_SubsurfaceProfile; + else if (StringValue.Compare("TwoSidedFoliage", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_TwoSidedFoliage; + else if (StringValue.Compare("Hair", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Hair; + else if (StringValue.Compare("Cloth", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Cloth; + else if (StringValue.Compare("Eye", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Eye; + } + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel && (MaterialInstance->BasePropertyOverrides.ShadingModel == EnumValue)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel = true; + MaterialInstance->BasePropertyOverrides.ShadingModel = EnumValue; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("TwoSided", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_TwoSided && (MaterialInstance->BasePropertyOverrides.TwoSided == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_TwoSided = true; + MaterialInstance->BasePropertyOverrides.TwoSided = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("DitheredLODTransition", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition && (MaterialInstance->BasePropertyOverrides.DitheredLODTransition == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition = true; + MaterialInstance->BasePropertyOverrides.DitheredLODTransition = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("PhysMaterial", ESearchCase::IgnoreCase) == 0) + { + // Try to load a Material corresponding to the parameter value + FString ParamValue = MaterialParameter.GetStringValue(); + UPhysicalMaterial* FoundPhysMaterial = Cast< UPhysicalMaterial >( + StaticLoadObject(UPhysicalMaterial::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); + + // Update the parameter value if necessary + if (!FoundPhysMaterial || (MaterialInstance->PhysMaterial == FoundPhysMaterial)) + return false; + + MaterialInstance->PhysMaterial = FoundPhysMaterial; + bParameterUpdated = true; + } + + if (bParameterUpdated) + return true; + + // Handling custom parameters + FName CurrentMatParamName = FName(*MaterialParameter.AttributeName); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + // String attributes are used for textures parameters + // We need to find the texture corresponding to the param + UTexture* FoundTexture = nullptr; + FString ParamValue = MaterialParameter.GetStringValue(); + + // Texture can either be already existing texture assets in UE4, or a newly generated textures by this asset + // Try to find the texture corresponding to the param value in the existing assets first. + FoundTexture = Cast( + StaticLoadObject(UTexture::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); + + if (!FoundTexture) + { + // We couldn't find a texture corresponding to the parameter in the existing UE4 assets + // Try to find the corresponding texture in the cooked temporary package we just generated + FoundTexture = FHoudiniMaterialTranslator::FindGeneratedTexture(ParamValue, InPackages); + } + + // Do not update if unnecessary + if (FoundTexture) + { + // Do not update if unnecessary + UTexture* OldTexture = nullptr; + bool FoundOldParam = MaterialInstance->GetTextureParameterValue(CurrentMatParamName, OldTexture); + if (FoundOldParam && (OldTexture == FoundTexture)) + return false; + + MaterialInstance->SetTextureParameterValueEditorOnly(CurrentMatParamName, FoundTexture); + bParameterUpdated = true; + } + } + else if (MaterialParameter.AttributeTupleSize == 1) + { + // Single attributes are either for scalar parameters or static switches + float OldValue; + bool FoundOldScalarParam = MaterialInstance->GetScalarParameterValue(CurrentMatParamName, OldValue); + if (FoundOldScalarParam) + { + // The material parameter is a scalar + float NewValue = (float)MaterialParameter.GetDoubleValue(); + + // Do not update if unnecessary + if (OldValue == NewValue) + return false; + + MaterialInstance->SetScalarParameterValueEditorOnly(CurrentMatParamName, NewValue); + bParameterUpdated = true; + } + else + { + // See if the underlying parameter is a static switch + bool NewBoolValue = MaterialParameter.GetBoolValue(); + + // We need to iterate over the material's static parameter set + FStaticParameterSet StaticParameters; + MaterialInstance->GetStaticParameterValues(StaticParameters); + + for (int32 SwitchParameterIdx = 0; SwitchParameterIdx < StaticParameters.StaticSwitchParameters.Num(); ++SwitchParameterIdx) + { + FStaticSwitchParameter& SwitchParameter = StaticParameters.StaticSwitchParameters[SwitchParameterIdx]; + if (SwitchParameter.ParameterInfo.Name != CurrentMatParamName) + continue; + + if (SwitchParameter.Value == NewBoolValue) + return false; + + SwitchParameter.Value = NewBoolValue; + SwitchParameter.bOverride = true; + + MaterialInstance->UpdateStaticPermutation(StaticParameters); + bParameterUpdated = true; + break; + } + } + } + else + { + // Tuple attributes are for vector parameters + FLinearColor NewLinearColor; + // if the attribute is stored in an int, we'll have to convert a color to a linear color + if (MaterialParameter.AttributeType == EAttribStorageType::INT || MaterialParameter.AttributeType == EAttribStorageType::INT64) + { + FColor IntColor; + IntColor.R = (int8)MaterialParameter.GetIntValue(0); + IntColor.G = (int8)MaterialParameter.GetIntValue(1); + IntColor.B = (int8)MaterialParameter.GetIntValue(2); + if (MaterialParameter.AttributeTupleSize >= 4) + IntColor.A = (int8)MaterialParameter.GetIntValue(3); + else + IntColor.A = 1; + + NewLinearColor = FLinearColor(IntColor); + } + else + { + NewLinearColor.R = (float)MaterialParameter.GetDoubleValue(0); + NewLinearColor.G = (float)MaterialParameter.GetDoubleValue(1); + NewLinearColor.B = (float)MaterialParameter.GetDoubleValue(2); + if (MaterialParameter.AttributeTupleSize >= 4) + NewLinearColor.A = (float)MaterialParameter.GetDoubleValue(3); + } + + // Do not update if unnecessary + FLinearColor OldValue; + bool FoundOldParam = MaterialInstance->GetVectorParameterValue(CurrentMatParamName, OldValue); + if (FoundOldParam && (OldValue == NewLinearColor)) + return false; + + MaterialInstance->SetVectorParameterValueEditorOnly(CurrentMatParamName, NewLinearColor); + bParameterUpdated = true; + } +#endif + + return bParameterUpdated; +} + + +UTexture* +FHoudiniMaterialTranslator::FindGeneratedTexture(const FString& TextureString, const TArray& InPackages) +{ + if (TextureString.IsEmpty()) + return nullptr; + + // Try to find the corresponding texture in the cooked temporary package generated by an HDA + UTexture* FoundTexture = nullptr; + for(const auto& CurrentPackage : InPackages) + { + // Iterate through the cooked packages + if (!CurrentPackage || CurrentPackage->IsPendingKill()) + continue; + + // First, check if the package contains a texture + FString CurrentPackageName = CurrentPackage->GetName(); + UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); + if (!PackageTexture) + continue; + + // Then check if the package's metadata match what we're looking for + // Make sure this texture was generated by Houdini Engine + UMetaData* MetaData = CurrentPackage->GetMetaData(); + if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + continue; + + // Get the texture type from the meta data + // Texture type store has meta data will be C_A, N, S, R etc.. + const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); + if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // Convert the texture type to a "friendly" version + // C_A to diffuse, N to Normal, S to Specular etc... + FString TextureTypeFriendlyString = TextureTypeString; + FString TextureTypeFriendlyAlternateString = TEXT(""); + if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) + { + TextureTypeFriendlyString = TEXT("diffuse"); + TextureTypeFriendlyAlternateString = TEXT("basecolor"); + } + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("normal"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("emissive"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("specular"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("roughness"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("metallic"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("opacity"); + + // See if we have a match between the texture string and the friendly name + if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) + { + FoundTexture = PackageTexture; + break; + } + + // Get the node path from the meta data + const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); + if (NodePath.IsEmpty()) + continue; + + // See if we have a match with the path and texture type + FString PathAndType = NodePath + TEXT("/") + TextureTypeString; + if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // See if we have a match with the friendly path and texture type + FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; + if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // Try the alternate friendly string + if (!TextureTypeFriendlyAlternateString.IsEmpty()) + { + PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; + if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + } + } + + return FoundTexture; +} diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h index ed62575fb..6a7727eb1 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h @@ -1,220 +1,220 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniGeoPartObject.h" -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "Engine/TextureDefines.h" - -//#include "HoudiniMaterialTranslator.generated.h" - -class UMaterial; -class UMaterialInterface; -class UMaterialExpression; -class UMaterialInstanceConstant; -class UTexture2D; -class UTexture; -class UPackage; - -struct FHoudiniPackageParams; -struct FCreateTexture2DParameters; -struct FHoudiniGenericAttribute; - -// Forward declared enums do not work with 4.24 builds on Linux with the Clang 8.0.1 toolchain: ISO C++ forbids forward references to 'enum' types -// enum TextureGroup; - -struct HOUDINIENGINE_API FHoudiniMaterialTranslator -{ -public: - - // - static bool CreateHoudiniMaterials( - const HAPI_NodeId& InNodeId, - const FHoudiniPackageParams& InPackageParams, - const TArray& InUniqueMaterialIds, - const TArray& InUniqueMaterialInfos, - const TMap& InMaterials, - TMap& OutMaterials, - TArray& OutPackages, - const bool& bForceRecookAll, - bool bInTreatExistingMaterialsAsUpToDate=false); - - // - static bool CreateMaterialInstances( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& UniqueMaterialInstanceOverrides, - const TArray& InPackages, - const TMap& InMaterials, - TMap& OutMaterials, - const bool& bForceRecookAll); - - // - static bool UpdateMaterialInstanceParameter( - FHoudiniGenericAttribute MaterialParameter, - UMaterialInstanceConstant* MaterialInstance, - const TArray& InPackages); - - static UTexture* FindGeneratedTexture( - const FString& TextureString, - const TArray& InPackages); - - // - static UPackage* CreatePackageForTexture( - const HAPI_NodeId& InMaterialNodeId, - const FString& InTextureType, - const FHoudiniPackageParams& InPackageParams, - FString& OutTextureName); - - // - static UPackage* CreatePackageForMaterial( - const HAPI_NodeId& InMaterialNodeId, - const FString& InMaterialName, - const FHoudiniPackageParams& InPackageParams, - FString& OutMaterialName); - - - // Create a texture from given information. - static UTexture2D* CreateUnrealTexture( - UTexture2D* ExistingTexture, - const HAPI_ImageInfo& ImageInfo, - UPackage* Package, - const FString& TextureName, - const TArray& ImageBuffer, - const FCreateTexture2DParameters& TextureParameters, - const TextureGroup& LODGroup, - const FString& TextureType, - const FString& NodePath); - - // HAPI : Retrieve a list of image planes. - static bool HapiExtractImage( - const HAPI_ParmId& NodeParmId, - const HAPI_MaterialInfo& MaterialInfo, - const char * PlaneType, - const HAPI_ImageDataFormat& ImageDataFormat, - HAPI_ImagePacking ImagePacking, - bool bRenderToImage, - TArray& OutImageBuffer); - - // HAPI : Extract image data. - static bool HapiGetImagePlanes( - const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes); - - // Returns a unique name for a given material, its relative path (to the asset) - static bool GetMaterialRelativePath( - const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialNodeInfo, FString& OutRelativePath); - static bool GetMaterialRelativePath( - const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath); - -protected: - - // Helper function to locate first Material expression of given class within given expression subgraph. - static UMaterialExpression * MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass); - - // Create various material components. - static bool CreateMaterialComponentDiffuse( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentNormal( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentSpecular( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentRoughness( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentMetallic( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentEmissive( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentOpacity( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentOpacityMask( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - -public: - - // Material node construction offsets. - static const int32 MaterialExpressionNodeX; - static const int32 MaterialExpressionNodeY; - static const int32 MaterialExpressionNodeStepX; - static const int32 MaterialExpressionNodeStepY; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniGeoPartObject.h" +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Engine/TextureDefines.h" + +//#include "HoudiniMaterialTranslator.generated.h" + +class UMaterial; +class UMaterialInterface; +class UMaterialExpression; +class UMaterialInstanceConstant; +class UTexture2D; +class UTexture; +class UPackage; + +struct FHoudiniPackageParams; +struct FCreateTexture2DParameters; +struct FHoudiniGenericAttribute; + +// Forward declared enums do not work with 4.24 builds on Linux with the Clang 8.0.1 toolchain: ISO C++ forbids forward references to 'enum' types +// enum TextureGroup; + +struct HOUDINIENGINE_API FHoudiniMaterialTranslator +{ +public: + + // + static bool CreateHoudiniMaterials( + const HAPI_NodeId& InNodeId, + const FHoudiniPackageParams& InPackageParams, + const TArray& InUniqueMaterialIds, + const TArray& InUniqueMaterialInfos, + const TMap& InMaterials, + TMap& OutMaterials, + TArray& OutPackages, + const bool& bForceRecookAll, + bool bInTreatExistingMaterialsAsUpToDate=false); + + // + static bool CreateMaterialInstances( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& UniqueMaterialInstanceOverrides, + const TArray& InPackages, + const TMap& InMaterials, + TMap& OutMaterials, + const bool& bForceRecookAll); + + // + static bool UpdateMaterialInstanceParameter( + FHoudiniGenericAttribute MaterialParameter, + UMaterialInstanceConstant* MaterialInstance, + const TArray& InPackages); + + static UTexture* FindGeneratedTexture( + const FString& TextureString, + const TArray& InPackages); + + // + static UPackage* CreatePackageForTexture( + const HAPI_NodeId& InMaterialNodeId, + const FString& InTextureType, + const FHoudiniPackageParams& InPackageParams, + FString& OutTextureName); + + // + static UPackage* CreatePackageForMaterial( + const HAPI_NodeId& InMaterialNodeId, + const FString& InMaterialName, + const FHoudiniPackageParams& InPackageParams, + FString& OutMaterialName); + + + // Create a texture from given information. + static UTexture2D* CreateUnrealTexture( + UTexture2D* ExistingTexture, + const HAPI_ImageInfo& ImageInfo, + UPackage* Package, + const FString& TextureName, + const TArray& ImageBuffer, + const FCreateTexture2DParameters& TextureParameters, + const TextureGroup& LODGroup, + const FString& TextureType, + const FString& NodePath); + + // HAPI : Retrieve a list of image planes. + static bool HapiExtractImage( + const HAPI_ParmId& NodeParmId, + const HAPI_MaterialInfo& MaterialInfo, + const char * PlaneType, + const HAPI_ImageDataFormat& ImageDataFormat, + HAPI_ImagePacking ImagePacking, + bool bRenderToImage, + TArray& OutImageBuffer); + + // HAPI : Extract image data. + static bool HapiGetImagePlanes( + const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes); + + // Returns a unique name for a given material, its relative path (to the asset) + static bool GetMaterialRelativePath( + const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialNodeInfo, FString& OutRelativePath); + static bool GetMaterialRelativePath( + const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath); + +protected: + + // Helper function to locate first Material expression of given class within given expression subgraph. + static UMaterialExpression * MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass); + + // Create various material components. + static bool CreateMaterialComponentDiffuse( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentNormal( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentSpecular( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentRoughness( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentMetallic( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentEmissive( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentOpacity( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentOpacityMask( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + +public: + + // Material node construction offsets. + static const int32 MaterialExpressionNodeX; + static const int32 MaterialExpressionNodeY; + static const int32 MaterialExpressionNodeStepX; + static const int32 MaterialExpressionNodeStepY; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp index 4fe0b6568..f781b2cff 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp @@ -1,6331 +1,6329 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniMeshTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniOutput.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniMaterialTranslator.h" -#include "HoudiniAssetActor.h" - -#include "HoudiniStaticMesh.h" -#include "HoudiniStaticMeshComponent.h" -#include "Engine/StaticMeshSocket.h" - -#include "PhysicsEngine/BodySetup.h" -#include "Engine/StaticMesh.h" -#include "PackageTools.h" -#include "RawMesh.h" -#include "Materials/MaterialInterface.h" -#include "Materials/Material.h" -#include "MeshDescription.h" -#include "StaticMeshAttributes.h" -#include "MeshDescriptionOperations.h" - -#include "BSPOps.h" -#include "Model.h" -#include "Engine/Polys.h" -#include "AssetRegistryModule.h" -#include "Interfaces/ITargetPlatform.h" -#include "Interfaces/ITargetPlatformManagerModule.h" -#include "AI/Navigation/NavCollisionBase.h" -#include "ObjectTools.h" - -// #include "Async/ParallelFor.h" - -#include "ProfilingDebugging/CpuProfilerTrace.h" - -#include "EditorSupportDelegates.h" - -#if WITH_EDITOR - #include "UnrealEd/Private/ConvexDecompTool.h" - #include "Editor/UnrealEd/Private/GeomFitUtils.h" - #include "LevelEditorViewport.h" - #include "FileHelpers.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// -bool -FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - UHoudiniOutput* InOutput, - const FHoudiniPackageParams& InPackageParams, - EHoudiniStaticMeshMethod InStaticMeshMethod, - UObject* InOuterComponent, - bool bInTreatExistingMaterialsAsUpToDate, - bool bInDestroyProxies) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InPackageParams.OuterPackage || InPackageParams.OuterPackage->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - TMap NewOutputObjects; - TMap OldOutputObjects = InOutput->GetOutputObjects(); - TMap& AssignementMaterials = InOutput->GetAssignementMaterials(); - TMap& ReplacementMaterials = InOutput->GetReplacementMaterials(); - - bool InForceRebuild = false; - if (InOutput->HasAnyCurrentProxy() && InStaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh) - { - // Make sure we're not preventing refinement - InForceRebuild = true; - } - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) - { - // Not a mesh, skip - if (CurHGPO.Type != EHoudiniPartType::Mesh) - continue; - - CreateStaticMeshFromHoudiniGeoPartObject( - CurHGPO, - InPackageParams, - OldOutputObjects, - NewOutputObjects, - AssignementMaterials, - ReplacementMaterials, - InForceRebuild, - InStaticMeshMethod, - bInTreatExistingMaterialsAsUpToDate); - } - - return FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - InOutput, - InOuterComponent, - NewOutputObjects, - bInDestroyProxies); -} - -bool -FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - UHoudiniOutput* InOutput, - UObject* InOuterComponent, - TMap& InNewOutputObjects, - bool bInDestroyProxies, - bool bInApplyGenericProperties) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - TMap OldOutputObjects = InOutput->GetOutputObjects(); - - // Remove Static Meshes and their components from the old map - // to avoid their deletion if new proxies were created for them - for (auto& NewOutputObj : InNewOutputObjects) - { - FHoudiniOutputObjectIdentifier OutputIdentifier = NewOutputObj.Key; - - // See if we already had that pair in the old map of static mesh - FHoudiniOutputObject* FoundOldOutputObj = OldOutputObjects.Find(NewOutputObj.Key); - if (!FoundOldOutputObj) - continue; - - UObject* NewStaticMesh = NewOutputObj.Value.OutputObject; - UObject* NewProxyMesh = NewOutputObj.Value.ProxyObject; - - UObject* OldStaticMesh = FoundOldOutputObj->OutputObject; - if (OldStaticMesh && !OldStaticMesh->IsPendingKill()) - { - // If a proxy was created for an existing static mesh, keep the existing static - // mesh (will be hidden) - if (NewProxyMesh && NewOutputObj.Value.bProxyIsCurrent) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - else if (NewStaticMesh && NewStaticMesh == OldStaticMesh) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - - UObject* OldProxyMesh = FoundOldOutputObj->ProxyObject; - if (OldProxyMesh && !OldProxyMesh->IsPendingKill()) - { - // If a new static mesh was created for a proxy, keep the proxy (will be hidden) - // ... unless we want to explicitly destroy proxies - if (NewStaticMesh && !bInDestroyProxies) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - else if (NewProxyMesh && (NewProxyMesh == OldProxyMesh)) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - } - - // The old map now only contains unused/stale Meshes/Components, delete them - for (auto& OldPair : OldOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; - FHoudiniOutputObject& OldOutputObject = OldPair.Value; - - // Remove the old component from the map - RemoveAndDestroyComponent(OldOutputObject.OutputComponent); - OldOutputObject.OutputComponent = nullptr; - // Remove the old proxy component from the map - RemoveAndDestroyComponent(OldOutputObject.ProxyComponent); - OldOutputObject.ProxyComponent = nullptr; - - if (OldOutputObject.OutputObject && !OldOutputObject.OutputObject->IsPendingKill()) - { - OldOutputObject.OutputObject->MarkPendingKill(); - } - - if (OldOutputObject.ProxyObject && !OldOutputObject.ProxyObject->IsPendingKill()) - { - OldOutputObject.ProxyObject->MarkPendingKill(); - } - } - OldOutputObjects.Empty(); - - /* - // Remove any stale components, these are components with OutputIdentifiers that are not - // in NewOutputObjects. This seems to happen mostly with the first or second cook after a - // "Rebuild Asset" - if (OutputComponents.Num() > 0 || OutputProxyComponents.Num() > 0) - { - TArray> StaleComponents; - const uint32 MaxNumStale = FMath::Max(OutputComponents.Num(), OutputProxyComponents.Num()); - StaleComponents.Reserve(MaxNumStale); - for (auto& ComponentPair : OutputComponents) - { - if (!NewOutputObjects.Contains(ComponentPair.Key) && !OldOutputObjectsReplacedByProxy.Contains(ComponentPair.Key)) - { - StaleComponents.Add(ComponentPair); - } - } - for (auto& ComponentPair : StaleComponents) - { - RemoveAndDestroyComponent(ComponentPair.Key, OutputComponents); - } - StaleComponents.Empty(MaxNumStale); - - for (auto& ComponentPair : OutputProxyComponents) - { - if (!NewOutputProxyObjects.Contains(ComponentPair.Key) && !OldOutputProxyObjectsReplacedByStaticMesh.Contains(ComponentPair.Key)) - { - StaleComponents.Add(ComponentPair); - } - } - for (auto& ComponentPair : StaleComponents) - { - RemoveAndDestroyComponent(ComponentPair.Key, OutputProxyComponents); - } - StaleComponents.Empty(); - } - */ - - // Now create/update the new static mesh components - for (auto& NewPair : InNewOutputObjects) - { - // Get the old Identifier / StaticMesh - const FHoudiniOutputObjectIdentifier& OutputIdentifier = NewPair.Key; - FHoudiniOutputObject& OutputObject = NewPair.Value; - - // Check if we should create a Proxy/SMC - if (OutputObject.bProxyIsCurrent) - { - UObject *Mesh = OutputObject.ProxyObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) - { - HOUDINI_LOG_ERROR(TEXT("Proxy Mesh is invalid (wrong type or pending kill)...")); - continue; - } - - // Create or update a new proxy component - TSubclassOf ComponentType = UHoudiniStaticMeshComponent::StaticClass(); - const FHoudiniGeoPartObject *FoundHGPO = nullptr; - bool bCreated = false; - UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); - if (MeshComponent) - { - UHoudiniStaticMeshComponent *HSMC = Cast(MeshComponent); - - if (bCreated) - { - PostCreateHoudiniStaticMeshComponent(HSMC, Mesh); - } - else if (HSMC && !HSMC->IsPendingKill() && HSMC->GetMesh() != Mesh) - { - // We need to reassign the HSM to the component - UHoudiniStaticMesh* HSM = Cast(Mesh); - HSMC->SetMesh(HSM); - } - - UpdateMeshComponent( - MeshComponent, - OutputIdentifier, - FoundHGPO, - InOutput->HoudiniCreatedSocketActors, - InOutput->HoudiniAttachedSocketActors, - bInApplyGenericProperties); - - if (!bCreated) - { - // For proxy meshes: notify that the mesh has been updated - HSMC->NotifyMeshUpdated(); - HSMC->SetHoudiniIconVisible(true); - } - } - - // Now, ensure that meshes replaced by proxies are still kept but hidden - USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent) - { - SceneComponent->SetVisibility(false); - SceneComponent->SetHiddenInGame(true); - } - - // If the proxy mesh we just created is templated, hide it in game - if (FoundHGPO->bIsTemplated) - { - MeshComponent->SetHiddenInGame(true); - } - } - else - { - // Create a new SMC if needed - UObject* Mesh = OutputObject.OutputObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) - { - HOUDINI_LOG_ERROR(TEXT("Mesh is invalid (wrong type or pending kill)...")); - continue; - } - - TSubclassOf ComponentType = UStaticMeshComponent::StaticClass(); - const FHoudiniGeoPartObject *FoundHGPO = nullptr; - bool bCreated = false; - UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); - if (MeshComponent) - { - if (bCreated) - { - PostCreateStaticMeshComponent(Cast(MeshComponent), Mesh); - } - UpdateMeshComponent( - MeshComponent, - OutputIdentifier, - FoundHGPO, - InOutput->HoudiniCreatedSocketActors, - InOutput->HoudiniAttachedSocketActors, - bInApplyGenericProperties); - } - - // Now, ensure that proxies replaced by meshes are still kept but hidden - UHoudiniStaticMeshComponent *HSMC = Cast(OutputObject.ProxyComponent); - if (HSMC) - { - HSMC->SetVisibility(false); - HSMC->SetHiddenInGame(true); - HSMC->SetHoudiniIconVisible(false); - } - - // If the mesh we just created is templated, hide it in game - if (FoundHGPO->bIsTemplated) - { - MeshComponent->SetHiddenInGame(true); - } - } - } - - // Assign the new output objects to the output - InOutput->SetOutputObjects(InNewOutputObjects); - - return true; -} - -void -FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, - const FHoudiniGeoPartObject *InHGPO, TArray &HoudiniCreatedSocketActors, TArray &HoudiniAttachedSocketActors, - bool bInApplyGenericProperties) -{ - // Update collision/visibility - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(InOutputIdentifier.SplitIdentifier); - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider) - { - // Invisible complex collider should not be seen - InMeshComponent->SetVisibility(false); - InMeshComponent->SetHiddenInGame(true); - InMeshComponent->SetCollisionProfileName(FName(TEXT("InvisibleWall"))); - InMeshComponent->SetCastShadow(false); - } - else - { - // Update visiblity - bool bVisible = InHGPO ? InHGPO->bIsVisible : true; - InMeshComponent->SetVisibility(bVisible); - InMeshComponent->SetHiddenInGame(!bVisible); - } - - // TODO: - // Update navmesh? - - // Transform the component by transformation provided by HAPI. - InMeshComponent->SetRelativeTransform(InHGPO->TransformMatrix); - - // If the static mesh had sockets, we can assign the desired actor to them now - UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); - UStaticMesh * StaticMesh = nullptr; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - StaticMesh = StaticMeshComponent->GetStaticMesh(); - - if (StaticMesh && !StaticMesh->IsPendingKill()) - { - 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; - - AddActorsToMeshSocket(StaticMesh->Sockets[nSocket], StaticMeshComponent, HoudiniCreatedSocketActors, HoudiniAttachedSocketActors); - } - - // Iterate all remaining created socket actors, destroy the ones that are not assigned to socket after re-cook - { - for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - continue; - } - - bool bFoundSocket = false; - for (auto & CurSocket : StaticMesh->Sockets) - { - if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) - { - bFoundSocket = true; - break; - } - } - // cur actor's attaching socket is found, skip - if (bFoundSocket) - continue; - - // Destroy the previous created socket actor if not found - HoudiniCreatedSocketActors.RemoveAt(Idx); - CurActor->Destroy(); - } - } - - // Detach the in level actors which is not attached to any socket now - { - for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor* CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniAttachedSocketActors.RemoveAt(Idx); - continue; - } - - bool bFoundSocket = false; - for (auto & CurSocket : StaticMesh->Sockets) - { - if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) - { - bFoundSocket = true; - break; - } - } - - if (bFoundSocket) - continue; - - // If the attached socket name is not found in current socket, detach it and remove from the array - CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - HoudiniAttachedSocketActors.RemoveAt(Idx); - } - } - - } - - if (bInApplyGenericProperties) - { - // Clear the component tags as generic properties only add them - InMeshComponent->ComponentTags.Empty(); - // Update the property attributes on the component - TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( - InOutputIdentifier.GeoId, InOutputIdentifier.PartId, - InOutputIdentifier.PointIndex, InOutputIdentifier.PrimitiveIndex, - PropertyAttributes)) - { - UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); - } - } -} - -bool -FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& InOutputObjects, - TMap& OutOutputObjects, - TMap& AssignmentMaterialMap, - TMap& ReplacementMaterialMap, - const bool& InForceRebuild, - EHoudiniStaticMeshMethod InStaticMeshMethod, - bool bInTreatExistingMaterialsAsUpToDate) -{ - // If we're not forcing the rebuild - // No need to recreate something that hasn't changed - if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged) && InOutputObjects.Num() > 0) - { - // Simply reuse the existing meshes - OutOutputObjects = InOutputObjects; - return true; - } - - FHoudiniMeshTranslator CurrentTranslator; - CurrentTranslator.ForceRebuild = InForceRebuild; - CurrentTranslator.SetHoudiniGeoPartObject(InHGPO); - CurrentTranslator.SetInputObjects(InOutputObjects); - CurrentTranslator.SetOutputObjects(OutOutputObjects); - CurrentTranslator.SetInputAssignmentMaterials(AssignmentMaterialMap); - CurrentTranslator.SetReplacementMaterials(ReplacementMaterialMap); - CurrentTranslator.SetPackageParams(InPackageParams, true); - CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); - - // TODO: Fetch from settings/HAC - CurrentTranslator.DefaultMeshSmoothing = 1; - if (false) - CurrentTranslator.DefaultMeshSmoothing = 0; - - // TODO: mechanism to determine when to use dynamic mesh for fast updates, and when to switch to - // baking the full static mesh - switch (InStaticMeshMethod) - { - case EHoudiniStaticMeshMethod::RawMesh: - CurrentTranslator.CreateStaticMesh_RawMesh(); - break; - case EHoudiniStaticMeshMethod::FMeshDescription: - CurrentTranslator.CreateStaticMesh_MeshDescription(); - break; - case EHoudiniStaticMeshMethod::UHoudiniStaticMesh: - CurrentTranslator.CreateHoudiniStaticMesh(); - break; - } - - // Copy the output objects/materials - OutOutputObjects = CurrentTranslator.OutputObjects; - AssignmentMaterialMap = CurrentTranslator.OutputAssignmentMaterials; - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartVertexList() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartVertexList")); - - if (HGPO.PartInfo.VertexCount <= 0) - return false; - - // Get the vertex List - PartVertexList.SetNumUninitialized(HGPO.PartInfo.VertexCount); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVertexList( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, &PartVertexList[0], 0, HGPO.PartInfo.VertexCount)) - { - // Error getting the vertex list. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - - return false; - } - - return true; -} - -void -FHoudiniMeshTranslator::SortSplitGroups() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::SortSplitGroups")); - - // Sort the splits in the order that we want to process them: - // Simple/Convex invisible colliders should be treated first as they will need to be attached to the visible meshes - TArray First; - - // The main geo and its LODs should be created after. - TArray Main; - TArray LODs; - - // Finally, visible colliders and invisible complex colliders as they need their own static mesh - TArray Last; - - for (auto& curSplit : HGPO.SplitGroups) - { - EHoudiniSplitType curSplitType = GetSplitTypeFromSplitName(curSplit); - switch (curSplitType) - { - case EHoudiniSplitType::InvisibleSimpleCollider: - case EHoudiniSplitType::InvisibleUCXCollider: - First.Add(curSplit); - break; - - case EHoudiniSplitType::Normal: - Main.Add(curSplit); - break; - - case EHoudiniSplitType::LOD: - LODs.Add(curSplit); - break; - - case EHoudiniSplitType::RenderedSimpleCollider: - case EHoudiniSplitType::RenderedUCXCollider: - case EHoudiniSplitType::RenderedComplexCollider: - case EHoudiniSplitType::InvisibleComplexCollider: - Last.Add(curSplit); - break; - } - } - - // Make sure LODs are order by name - LODs.Sort(); - - // Copy the split names in order - AllSplitGroups.Empty(); - for (auto& splitName : First) - AllSplitGroups.Add(splitName); - - for (auto& splitName : Main) - AllSplitGroups.Add(splitName); - - for (auto& splitName : LODs) - AllSplitGroups.Add(splitName); - - for (auto& splitName : Last) - AllSplitGroups.Add(splitName); -} - -bool -FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices")); - - // Reset the splits faces/indices arrays - AllSplitVertexLists.Empty(); - AllSplitVertexCounts.Empty(); - AllSplitFaceIndices.Empty(); - AllSplitFirstValidVertexIndex.Empty(); - AllSplitFirstValidPrimIndex.Empty(); - - bool bHasSplit = AllSplitGroups.Num() > 0; - if (bHasSplit) - { - HAPI_PartInfo PartInfo = FHoudiniEngineUtils::ToHAPIPartInfo(HGPO.PartInfo); - - // Buffer for all vertex indices used for split groups. - // We need this to figure out all vertex indices that are not part of them. - TArray AllVertexList; - AllVertexList.SetNumZeroed(PartVertexList.Num()); - - // Buffer for all face indices used for split groups. - // We need this to figure out all face indices that are not part of them. - TArray AllGroupFaceIndices; - AllGroupFaceIndices.SetNumZeroed(HGPO.PartInfo.FaceCount); - - // Some of the groups may contain invalid geometry - // Store them here so we can remove them afterwards - TArray InvalidGroupNameIndices; - - // Extract the vertices/faces for each of the split groups - for (int32 SplitIdx = 0; SplitIdx < AllSplitGroups.Num(); SplitIdx++) - { - const FString& GroupName = AllSplitGroups[SplitIdx]; - - // New vertex list just for this group. - TArray< int32 > GroupVertexList; - TArray< int32 > AllFaceList; - - int32 FirstValidPrimIndex = 0; - int32 FirstValidVertexIndex = 0; - // Extract vertex indices for this split. - int32 GroupVertexListCount = FHoudiniEngineUtils::HapiGetVertexListForGroup( - HGPO.GeoId, PartInfo, GroupName, - PartVertexList, GroupVertexList, - AllVertexList, AllFaceList, AllGroupFaceIndices, - FirstValidVertexIndex, FirstValidPrimIndex, - HGPO.PartInfo.bIsInstanced); - - if (GroupVertexListCount <= 0) - { - // This group doesn't have vertices/faces, mark it as invalid - InvalidGroupNameIndices.Add(SplitIdx); - - // Error getting the vertex list. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list for group %s - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, *GroupName); - - continue; - } - - // If list is not empty, we store it for this group - this will define new mesh. - AllSplitVertexLists.Add(GroupName, GroupVertexList); - AllSplitVertexCounts.Add(GroupName, GroupVertexListCount); - AllSplitFaceIndices.Add(GroupName, AllFaceList); - AllSplitFirstValidVertexIndex.Add(GroupName, FirstValidPrimIndex); - AllSplitFirstValidPrimIndex.Add(GroupName, FirstValidVertexIndex); - } - - if (InvalidGroupNameIndices.Num() > 0) - { - // Remove all invalid split groups - for (int32 InvalIdx = InvalidGroupNameIndices.Num() - 1; InvalIdx >= 0; InvalIdx--) - { - int32 Index = InvalidGroupNameIndices[InvalIdx]; - AllSplitGroups.RemoveAt(Index); - } - } - - // We also need to figure out / construct the vertex list for everything that's not in a split group - TArray GroupSplitFacesRemaining; - GroupSplitFacesRemaining.Init(-1, PartVertexList.Num()); - - int32 GroupVertexListCount = 0; - bool bHasMainSplitGroup = false; - TArray< int32 > GroupSplitFaceIndicesRemaining; - int32 FistUnusedVertexIndex = -1; - for (int32 SplitVertexIdx = 0; SplitVertexIdx < AllVertexList.Num(); SplitVertexIdx++) - { - if (AllVertexList[SplitVertexIdx] == 0) - { - // This is an unused index, we need to add it to unused vertex list. - FistUnusedVertexIndex = SplitVertexIdx; - GroupSplitFacesRemaining[SplitVertexIdx] = PartVertexList[SplitVertexIdx]; - bHasMainSplitGroup = true; - GroupVertexListCount++; - } - } - - int32 FistUnusedPrimIndex = -1; - for (int32 SplitFaceIdx = 0; SplitFaceIdx < AllGroupFaceIndices.Num(); SplitFaceIdx++) - { - if (AllGroupFaceIndices[SplitFaceIdx] == 0) - { - // This is unused face, we need to add it to unused faces list. - GroupSplitFaceIndicesRemaining.Add(SplitFaceIdx); - FistUnusedPrimIndex = SplitFaceIdx; - } - } - - // We store the remaining geo vertex list as a special split named "main geo" - // and make sure its treated before the collider meshes - if (bHasMainSplitGroup) - { - static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - AllSplitGroups.Add(RemainingGroupName); - AllSplitVertexLists.Add(RemainingGroupName, GroupSplitFacesRemaining); - AllSplitVertexCounts.Add(RemainingGroupName, GroupVertexListCount); - AllSplitFaceIndices.Add(RemainingGroupName, GroupSplitFaceIndicesRemaining); - AllSplitFirstValidPrimIndex.Add(RemainingGroupName, FistUnusedPrimIndex); - AllSplitFirstValidVertexIndex.Add(RemainingGroupName, FistUnusedVertexIndex); - } - } - else - { - // No splitting required - // Mark everything as the main geo group - static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - AllSplitGroups.Add(RemainingGroupName); - AllSplitVertexLists.Add(RemainingGroupName, PartVertexList); - AllSplitVertexCounts.Add(RemainingGroupName, PartVertexList.Num()); - AllSplitFirstValidPrimIndex.Add(RemainingGroupName, 0); - AllSplitFirstValidVertexIndex.Add(RemainingGroupName, 0); - - TArray AllFaces; - for (int32 FaceIdx = 0; FaceIdx < HGPO.PartInfo.FaceCount; ++FaceIdx) - AllFaces.Add(FaceIdx); - - AllSplitFaceIndices.Add(RemainingGroupName, AllFaces); - } - - return true; -} - -void -FHoudiniMeshTranslator::ResetPartCache() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::ResetPartCache")); - - // Vertex Positions - PartPositions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Vertex Normals - PartNormals.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Vertex TangentU - PartTangentU.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentU); - - // Vertex TangentV - PartTangentV.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentV); - - // Vertex Colors - PartColors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoColors); - - // Vertex Alpha values - PartAlphas.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoAlpha); - - // FaceSmoothing values - PartFaceSmoothingMasks.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceSmoothingMasks); - - // UVs - PartUVSets.Empty(); - AttribInfoUVSets.Empty(); - - // UVs - PartLightMapResolutions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoLightmapResolution); - - // Material IDs per face - PartFaceMaterialIds.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialIds); - // Unique material IDs - PartUniqueMaterialIds.Empty(); - // Material infos for each unique Material - PartUniqueMaterialInfos.Empty(); - // - bOnlyOneFaceMaterial = false; - - // Face Materials override - PartFaceMaterialOverrides.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialOverrides); - bMaterialOverrideNeedsCreateInstance = false; - - // LOD Screensize - PartLODScreensize.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoLODScreensize); -} - -bool -FHoudiniMeshTranslator::UpdatePartPositionIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartPositionIfNeeded")); - - // Only Retrieve the vertices positions if necessary - if (PartPositions.Num() > 0) - return true; - - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve position data") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded")); - - // No need to read the normals if we want unreal to recompute them after - bool bReadNormals = true; - // TODO: Add runtime setting check! - //bool bReadNormals = HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; - if (!bReadNormals) - return true; - - // Only Retrieve the normals if we haven't already - if (PartNormals.Num() > 0) - return true; - - // Retrieve normal data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, PartNormals); - - // There is no normals to fetch - if (!AttribInfoNormals.exists) - return true; - - if (!Success && AttribInfoNormals.exists) - { - // Error retrieving normals. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve normal data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded")) - - bool bReturn = true; - if (PartTangentU.Num() <= 0) - { - // Retrieve TangentU data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_TANGENTU, AttribInfoTangentU, PartTangentU); - - if (!Success && AttribInfoTangentU.exists) - { - // Error retrieving tangent. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentU data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - bReturn = false; - } - } - - if (PartTangentV.Num() <= 0) - { - // Retrieve TangentV data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_TANGENTV, AttribInfoTangentV, PartTangentV); - - if (!Success && AttribInfoTangentV.exists) - { - // Error retrieving tangent. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentV data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - bReturn = false; - } - } - - return bReturn; -} - -bool -FHoudiniMeshTranslator::UpdatePartColorsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartColorsIfNeeded")); - - // Only Retrieve the vertices colors if necessary - if (PartColors.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_COLOR, AttribInfoColors, PartColors); - - if (!Success && AttribInfoColors.exists) - { - // Error retrieving colors. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve color data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded")); - - // Only Retrieve the vertices alphas if necessary - if (PartAlphas.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_ALPHA, AttribInfoAlpha, PartAlphas); - - if (!Success && AttribInfoAlpha.exists) - { - // Error retrieving alpha values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve alpha data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceSmoothingIfNeeded() -{ - // Only Retrieve the vertices FaceSmoothing if necessary - if (PartFaceSmoothingMasks.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, - AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks); - - if (!Success && AttribInfoFaceSmoothingMasks.exists) - { - // Error retrieving FaceSmoothing values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve FaceSmoothing data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded")); - - // Only Retrieve uvs if necessary - if (PartUVSets.Num() > 0) - return true; - - PartUVSets.SetNum(MAX_STATIC_TEXCOORDS); - AttribInfoUVSets.SetNum(MAX_STATIC_TEXCOORDS); - - // The second UV set should be called uv2, but we will still check if need to look for a uv1 set. - // If uv1 exists, we'll look for uv, uv1, uv2 etc.. if not we'll look for uv, uv2, uv3 etc.. - bool bUV1Exists = FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, "uv1"); - - // Retrieve UVs. - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (TexCoordIdx > 0) - UVAttributeName += FString::Printf(TEXT("%d"), bUV1Exists ? TexCoordIdx : TexCoordIdx + 1); - - FHoudiniApi::AttributeInfo_Init(&AttribInfoUVSets[TexCoordIdx]); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*UVAttributeName), - AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], 2); - } - - // Also look for 16.5 uvs (attributes with a Texture type) - // For that, we'll have to iterate through ALL the attributes and check their types - TArray< FString > FoundAttributeNames; - TArray< HAPI_AttributeInfo > FoundAttributeInfos; - - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - FHoudiniEngineUtils::HapiGetAttributeOfType( - HGPO.GeoId, HGPO.PartId, (HAPI_AttributeOwner)AttrIdx, - HAPI_ATTRIBUTE_TYPE_TEXTURE, FoundAttributeInfos, FoundAttributeNames); - } - - if (FoundAttributeInfos.Num() <= 0) - return true; - - // We found some additionnal uv attributes - int32 AvailableIdx = 0; - for (int32 attrIdx = 0; attrIdx < FoundAttributeInfos.Num(); attrIdx++) - { - // Ignore the old uvs - if (FoundAttributeNames[attrIdx] == TEXT("uv") - || FoundAttributeNames[attrIdx] == TEXT("uv1") - || FoundAttributeNames[attrIdx] == TEXT("uv2") - || FoundAttributeNames[attrIdx] == TEXT("uv3") - || FoundAttributeNames[attrIdx] == TEXT("uv4") - || FoundAttributeNames[attrIdx] == TEXT("uv5") - || FoundAttributeNames[attrIdx] == TEXT("uv6") - || FoundAttributeNames[attrIdx] == TEXT("uv7") - || FoundAttributeNames[attrIdx] == TEXT("uv8")) - continue; - - HAPI_AttributeInfo CurrentAttrInfo = FoundAttributeInfos[attrIdx]; - if (!CurrentAttrInfo.exists) - continue; - - // Look for the next available index in the return arrays - for (; AvailableIdx < AttribInfoUVSets.Num(); AvailableIdx++) - { - if (!AttribInfoUVSets[AvailableIdx].exists) - break; - } - - // We are limited to MAX_STATIC_TEXCOORDS uv sets! - // If we already have too many uv sets, skip the rest - if ((AvailableIdx >= MAX_STATIC_TEXCOORDS) || (AvailableIdx >= AttribInfoUVSets.Num())) - { - HOUDINI_LOG_WARNING(TEXT("Too many UV sets found. Unreal only supports %d , skipping the remaining uv sets."), (int32)MAX_STATIC_TEXCOORDS); - break; - } - - // Force the tuple size to 2 ? - CurrentAttrInfo.tupleSize = 2; - - // Add the attribute infos we found - AttribInfoUVSets[AvailableIdx] = CurrentAttrInfo; - - // Allocate sufficient buffer for the attribute's data. - PartUVSets[AvailableIdx].SetNumUninitialized(CurrentAttrInfo.count * CurrentAttrInfo.tupleSize); - - // Get the texture coordinates - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, TCHAR_TO_UTF8(*(FoundAttributeNames[attrIdx])), - &AttribInfoUVSets[AvailableIdx], -1, - &PartUVSets[AvailableIdx][0], 0, CurrentAttrInfo.count)) - { - // Something went wrong when trying to access the uv values, invalidate this set - AttribInfoUVSets[AvailableIdx].exists = false; - } - } - - // Remove unused UV sets - if (bRemoveUnused) - { - for (int32 Idx = PartUVSets.Num() - 1; Idx >= 0; Idx--) - { - if (PartUVSets[Idx].Num() > 0) - continue; - - PartUVSets.RemoveAt(Idx); - } - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartLightmapResolutionsIfNeeded() -{ - // Only Retrieve the vertices lightmap resolution if necessary - if (PartLightMapResolutions.Num() > 0) - return true; - - // Get lightmap resolution (if present). - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, - AttribInfoLightmapResolution, PartLightMapResolutions); - - if (!Success && AttribInfoLightmapResolution.exists) - { - // Error retrieving lightmap resolution values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve lightmap resolution data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded")); - - // Only Retrieve the material IDs if necessary - if (PartFaceMaterialIds.Num() > 0) - return true; - - int32 NumFaces = HGPO.PartInfo.FaceCount; - if (NumFaces <= 0) - return true; - - PartFaceMaterialIds.SetNum(NumFaces); - - // Get the materials IDs per face - HAPI_Bool bSingleFaceMaterial = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, &bSingleFaceMaterial, - &PartFaceMaterialIds[0], 0, NumFaces)) - { - // Error retrieving material face assignments. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material face assignments"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - bOnlyOneFaceMaterial = bSingleFaceMaterial; - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded")); - - // Only Retrieve the material overrides if necessary - if (PartFaceMaterialOverrides.Num() > 0) - return true; - - bMaterialOverrideNeedsCreateInstance = false; - - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - - // If material attribute was not found, check fallback compatibility attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - } - - // If material attribute and fallbacks were not found, check the material instance attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - - // We will we need to create material instances from the override attributes - bMaterialOverrideNeedsCreateInstance = AttribInfoFaceMaterialOverrides.exists; - } - - if (AttribInfoFaceMaterialOverrides.exists - && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_PRIM - && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_DETAIL) - { - HOUDINI_LOG_WARNING(TEXT("Static Mesh [%d %s], Geo [%d], Part [%d %s]: unreal_material must be a primitive or detail attribute, ignoring attribute."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - AttribInfoFaceMaterialOverrides.exists = false; - bMaterialOverrideNeedsCreateInstance = false; - PartFaceMaterialOverrides.Empty(); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartNeededMaterials() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials")); - - // Update the per face material IDs - UpdatePartFaceMaterialIDsIfNeeded(); - - // See if we have some material overides - UpdatePartFaceMaterialOverridesIfNeeded(); - - // If we have houdini materials AND overrides: - // We want to only create the Houdini materials that are not "covered" by overrides - // If we have material instance attributes, create all the houdini material anyway - // as their textures could be referenced by the material instance parameters - if (PartFaceMaterialOverrides.Num() > 0 && !bMaterialOverrideNeedsCreateInstance) - { - // If the material override was set on the detail, no need to look for houdini material IDs, as only the override will be used - if (AttribInfoFaceMaterialOverrides.exists && AttribInfoFaceMaterialOverrides.owner == HAPI_ATTROWNER_PRIM) - { - for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) - { - // Add a material ID to the unique array only if that face is not using the override - if (PartFaceMaterialOverrides[MaterialIdx].IsEmpty()) - PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); - } - } - } - else - { - // No material overrides, simply update the unique material array - for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) - PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); - } - - // Remove the invalid material ID from the unique array - PartUniqueMaterialIds.RemoveSingle(-1); - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials - Get the unique material infos")); - // Get the unique material infos - PartUniqueMaterialInfos.SetNum(PartUniqueMaterialIds.Num()); - for (int32 MaterialIdx = 0; MaterialIdx < PartUniqueMaterialIds.Num(); MaterialIdx++) - { - - FHoudiniApi::MaterialInfo_Init(&PartUniqueMaterialInfos[MaterialIdx]); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialInfo( - FHoudiniEngine::Get().GetSession(), - PartUniqueMaterialIds[MaterialIdx], - &PartUniqueMaterialInfos[MaterialIdx])) - { - // Error retrieving material face assignments. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material info for material %d"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, PartUniqueMaterialIds[MaterialIdx]); - continue; - } - } - } - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartLODScreensizeIfNeeded() -{ - // Only retrieve LOD screensizes if necessary - if (PartLODScreensize.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE, - AttribInfoLODScreensize, PartLODScreensize); - - if (!Success && AttribInfoLODScreensize.exists) - { - // Error retrieving FaceSmoothing values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve LOD screensizes"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - - -UStaticMesh* -FHoudiniMeshTranslator::CreateNewStaticMesh(const FString& InSplitIdentifier) -{ - // Update the current Obj/Geo/Part/Split IDs - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.GeoId; - PackageParams.PartId = HGPO.PartId; - PackageParams.SplitStr = InSplitIdentifier; - - UStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) - return nullptr; - - return NewStaticMesh; -} - -UHoudiniStaticMesh* -FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentifier) -{ - // Update the current Obj/Geo/Part/Split IDs - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.GeoId; - PackageParams.PartId = HGPO.PartId; - // Add _HSM suffix to the split str, to distinguish the temporary HoudiniStaticMesh - // from the UStaticMesh - PackageParams.SplitStr = InSplitIdentifier + "_HSM"; - - UHoudiniStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) - return nullptr; - - return NewStaticMesh; -} - -bool -FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() -{ - double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Prepare the object that will store UCX and simple colliders - AllAggregateCollisions.Empty(); - - // We need to know the number of LODs that will be needed for this part - int32 NumberOfLODs = 0; - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) - NumberOfLODs++; - else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - bHasMainGeo = true; - } - - // Update the part's material's IDS and info now - CreateNeededMaterials(); - - // Check now if they were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Get the current target platform for default lod policies - ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); - check(CurrentPlatform); - - // New mesh list - TMap StaticMeshToBuild; - - // Map of Houdini Material IDs to Unreal Material Indices - TMap MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap MapHoudiniMatAttributesToUnrealIndex; - - bool MeshMaterialsHaveBeenReset = false; - - // Mesh Socket array - TArray AllSockets; - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - - // Iterate through all detected split groups we care about and split geometry. - // The split are ordered in the following way: - // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - - // Get/Create the Aggregate Collisions for this mesh identifier - FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); - - // Handle UCX / Convex Hull colliders - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) - { - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the convex hull colliders and add them to the Aggregate - if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) - continue; - } - else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) - { - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the simple colliders and add them to the aggregate - if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) - continue; - } - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing SM from a previous cook - UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - // Prepare LOD Group data for this static mesh - FStaticMeshLODGroup LODGroup; - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing static mesh, create a new one - FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - - // Use the platform's default LODGroup policy - // TODO? Add setting for default LOD Group? - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); - } - else - { - // Try to reuse the existing SM's LOD group instead of the default one - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); - } - - if (!FoundOutputObject) - { - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - InputObjects.Remove(OutputObjectIdentifier); - } - else - { - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - FoundOutputObject->CachedAttributes.Empty(); - FoundOutputObject->CachedTokens.Empty(); - } - FoundOutputObject->bProxyIsCurrent = false; - - // TODO: Needed? - // Free any RHI resources for existing mesh before we re-create in place. - FoundStaticMesh->PreEditChange(NULL); - - // Check that the Static Mesh we found has the appropriate number of Source models/LODs - int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); - - // LODs are only for the "main" mesh, not for complex colliders! - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); - - if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) - { - while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) - FoundStaticMesh->AddSourceModel(); - - // We may have to remove excessive LOD levels - if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) - FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); - - // Initialize their default reduction setting - for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) - { - FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); - } - FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); - } - - // By default, always work on the first source model, unless we're a LOD - int32 SrcModelIndex = 0; - int32 LODIndex = 0; - if (SplitType == EHoudiniSplitType::LOD) - { - for (auto& curSplit : AllSplitGroups) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); - if (CurrentSplitType == EHoudiniSplitType::LOD - || CurrentSplitType == EHoudiniSplitType::Normal) - { - LODIndex++; - } - - if (curSplit == SplitGroupName) - break; - } - - // Fix for the case where we don't have a main geo - if(!bHasMainGeo) - LODIndex--; - } - - // Grab the appropriate SourceModel - FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; - if (!SrcModel) - { - HOUDINI_LOG_ERROR( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); - continue; - } - - // Load existing raw model. This will be empty as we are constructing a new mesh. - FRawMesh RawMesh; - if (!bRebuildStaticMesh) - { - // We dont need to rebuild the mesh itself: - // the geometry hasn't changed, but the materials have. - // We can just load the old data into the Raw mesh and reuse it. - SrcModel->LoadRawMesh(RawMesh); - } - else - { - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's normal if needed - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - // Check that the number of normal we retrieved is correct - int32 WedgeNormalCount = SplitNormals.Num() / 3; - if (SplitNormals.Num() < 0 || !SplitNormals.IsValidIndex((WedgeNormalCount - 1) * 3 + 2)) - { - // Ignore normals - WedgeNormalCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); - } - - // Transfer the normals to the raw mesh - RawMesh.WedgeTangentZ.SetNumZeroed(WedgeNormalCount); - for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) - { - // Swap Y/Z for Coordinates conversion - RawMesh.WedgeTangentZ[WedgeTangentZIdx].X = SplitNormals[WedgeTangentZIdx * 3 + 0]; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].Y = SplitNormals[WedgeTangentZIdx * 3 + 2]; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].Z = SplitNormals[WedgeTangentZIdx * 3 + 1]; - } - - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENTS - //--------------------------------------------------------------------------------------------------------------------- - - // No need to read the tangents if we want unreal to recompute them after - bool bReadTangents = true; - // TODO: Add runtime setting check! - //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - TArray< float > SplitTangentU; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - TArray< float > SplitTangentV; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - bool bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - - // Check that the number of tangents read matches the number of normals - int32 WedgeTangentUCount = SplitTangentU.Num() / 3; - int32 WedgeTangentVCount = SplitTangentV.Num() / 3; - if (WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount) - bGenerateTangents = true; - - /* - // TODO: Add settings check! - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - */ - - // Generate the tangents if needed - if (bGenerateTangents) - { - RawMesh.WedgeTangentX.SetNumZeroed(WedgeNormalCount); - RawMesh.WedgeTangentY.SetNumZeroed(WedgeNormalCount); - for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) - { - FVector TangentX, TangentY; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].FindBestAxisVectors(TangentX, TangentY); - - RawMesh.WedgeTangentX[WedgeTangentZIdx] = TangentX; - RawMesh.WedgeTangentY[WedgeTangentZIdx] = TangentY; - } - } - else - { - // Transfer the tangents we have read them and they're valid - RawMesh.WedgeTangentX.SetNumZeroed(WedgeTangentUCount); - for (int32 WedgeTangentUIdx = 0; WedgeTangentUIdx < WedgeTangentUCount; ++WedgeTangentUIdx) - { - // We need to flip Z and Y - RawMesh.WedgeTangentX[WedgeTangentUIdx].X = SplitTangentU[WedgeTangentUIdx * 3 + 0]; - RawMesh.WedgeTangentX[WedgeTangentUIdx].Y = SplitTangentU[WedgeTangentUIdx * 3 + 2]; - RawMesh.WedgeTangentX[WedgeTangentUIdx].Z = SplitTangentU[WedgeTangentUIdx * 3 + 1]; - } - - RawMesh.WedgeTangentY.SetNumZeroed(WedgeTangentVCount); - for (int32 WedgeTangentVIdx = 0; WedgeTangentVIdx < WedgeTangentVCount; ++WedgeTangentVIdx) - { - // We need to flip Z and Y - RawMesh.WedgeTangentY[WedgeTangentVIdx].X = SplitTangentV[WedgeTangentVIdx * 3 + 0]; - RawMesh.WedgeTangentY[WedgeTangentVIdx].Y = SplitTangentV[WedgeTangentVIdx * 3 + 2]; - RawMesh.WedgeTangentY[WedgeTangentVIdx].Z = SplitTangentV[WedgeTangentVIdx * 3 + 1]; - } - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // VERTEX COLORS AND ALPHAS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's colors if needed - UpdatePartColorsIfNeeded(); - - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract this part's alpha values if needed - UpdatePartAlphasIfNeeded(); - - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - - // Transfer colors and alphas if possible - int32 WedgeColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; - bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && WedgeColorsCount > 0; - bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == WedgeColorsCount); - if (bSplitColorValid) - { - RawMesh.WedgeColors.SetNumZeroed(WedgeColorsCount); - for (int32 WedgeColorIdx = 0; WedgeColorIdx < WedgeColorsCount; WedgeColorIdx++) - { - FLinearColor WedgeColor; - WedgeColor.R = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); - WedgeColor.G = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); - WedgeColor.B = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); - - if (bSplitAlphaValid) - { - // Use the Alpha attribute value - WedgeColor.A = FMath::Clamp(SplitAlphas[WedgeColorIdx], 0.0f, 1.0f); - } - else if (AttribInfoColors.tupleSize >= 4) - { - // Use the alpha value from the color attribute - WedgeColor.A = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); - } - else - { - WedgeColor.A = 1.0f; - } - - // Convert linear color to fixed color. - RawMesh.WedgeColors[WedgeColorIdx] = WedgeColor.ToFColor(false); - } - } - else - { - // TODO? Needed? New meshes wont have WedgeIndices yet!? - // No Colors or Alphas, init colors to White - FColor DefaultWedgeColor = FLinearColor::White.ToFColor(false); - WedgeColorsCount = RawMesh.WedgeIndices.Num(); - if (WedgeColorsCount > 0) - RawMesh.WedgeColors.Init(DefaultWedgeColor, WedgeColorsCount); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's FaceSmoothing values if needed - UpdatePartFaceSmoothingIfNeeded(); - - // Get the FaceSmoothing values for this split - TArray SplitFaceSmoothingMasks; - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); - - // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! - RawMesh.FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); - - // Check that the number of face smoothing values we retrieved is correct - int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; - if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) - { - // Ignore our face smoothing values - WedgeFaceSmoothCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); - } - - // Transfer the face smoothing masks to the raw mesh if we have any - for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) - { - RawMesh.FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; - } - - //--------------------------------------------------------------------------------------------------------------------- - // UVS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's UV sets if needed - UpdatePartUVSetsIfNeeded(); - - // See if we need to transfer uv point attributes to vertex attributes. - TArray> SplitUVSets; - SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - } - - // Transfer UVs to the Raw Mesh - int32 UVChannelCount = 0; - int32 LightMapUVChannel = 0; - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; - - int32 WedgeUVCount = SplitUVs.Num() / 2; - if (SplitUVs.Num() > 0 && SplitUVs.IsValidIndex((WedgeUVCount - 1) * 2 + 1)) - { - RawMesh.WedgeTexCoords[TexCoordIdx].SetNumZeroed(WedgeUVCount); - for (int32 WedgeUVIdx = 0; WedgeUVIdx < WedgeUVCount; ++WedgeUVIdx) - { - // We need to flip V coordinate when it's coming from HAPI. - RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].X = SplitUVs[WedgeUVIdx * 2 + 0]; - RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].Y = 1.0f - SplitUVs[WedgeUVIdx * 2 + 1]; - } - - UVChannelCount++; - if (UVChannelCount <= 2) - LightMapUVChannel = TexCoordIdx; - } - else - { - RawMesh.WedgeTexCoords[TexCoordIdx].Empty(); - } - } - - // We must have at least one UV channel. If there's none, create one filled with zero data. - if (UVChannelCount == 0) - RawMesh.WedgeTexCoords[0].SetNumZeroed(SplitVertexCount); - - // Set the lightmap Coordinate Index - // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention - // If not, the first UV set will be used - FoundStaticMesh->LightMapCoordinateIndex = LightMapUVChannel; - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's LightmapResolution values if needed - UpdatePartLightmapResolutionsIfNeeded(); - - // make sure the mesh has a new lighting guid - FoundStaticMesh->LightingGuid = FGuid::NewGuid(); - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" - // So that IndicesMapper[ oldIndex ] => newIndex - TArray IndicesMapper; - IndicesMapper.Init(-1, SplitVertexList.Num()); - int32 CurrentMapperIndex = 0; - - // NeededVertices: - // Array containing the old index of the needed vertices for the current split - // NeededVertices[ newIndex ] => oldIndex - TArray< int32 > NeededVertices; - RawMesh.WedgeIndices.SetNumZeroed(SplitVertexCount); - - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) - || !IndicesMapper.IsValidIndex(WedgeIndices[1]) - || !IndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (IndicesMapper[WedgeIndices[i]] < 0) - { - // This old index has not yet been "converted" to a new index - NeededVertices.Add(WedgeIndices[i]); - IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; - CurrentMapperIndex++; - } - - // Replace the old index with the new one - WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; - } - - if (!RawMesh.WedgeIndices.IsValidIndex(ValidVertexId + 2)) - break; - - // Flip wedge indices to fix the winding order. - RawMesh.WedgeIndices[ValidVertexId + 0] = WedgeIndices[0]; - RawMesh.WedgeIndices[ValidVertexId + 1] = WedgeIndices[2]; - RawMesh.WedgeIndices[ValidVertexId + 2] = WedgeIndices[1]; - - // Check if we need to patch UVs. - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - if (RawMesh.WedgeTexCoords[TexCoordIdx].IsValidIndex(ValidVertexId + 2)) - { - Swap(RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 1], - RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 2]); - } - } - - // Check if we need to patch colors. - if (RawMesh.WedgeColors.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeColors[ValidVertexId + 1], RawMesh.WedgeColors[ValidVertexId + 2]); - - // Check if we need to patch Normals and tangents. - if (RawMesh.WedgeTangentZ.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentZ[ValidVertexId + 1], RawMesh.WedgeTangentZ[ValidVertexId + 2]); - - if (RawMesh.WedgeTangentX.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentX[ValidVertexId + 1], RawMesh.WedgeTangentX[ValidVertexId + 2]); - - if (RawMesh.WedgeTangentY.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentY[ValidVertexId + 1], RawMesh.WedgeTangentY[ValidVertexId + 2]); - - ValidVertexId += 3; - } - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - UpdatePartPositionIfNeeded(); - - // - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - int32 VertexPositionsCount = NeededVertices.Num(); - RawMesh.VertexPositions.SetNumZeroed(VertexPositionsCount); - - for (int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx) - { - int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; - if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // We need to swap Z and Y coordinate here, and convert from m to cm. - RawMesh.VertexPositions[VertexPositionIdx].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - RawMesh.VertexPositions[VertexPositionIdx].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - RawMesh.VertexPositions[VertexPositionIdx].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - /* - // TODO: - // Check if this mesh contains only degenerate triangles. - if (FHoudiniEngineUtils::CountDegenerateTriangles(RawMesh) == SplitGroupFaceCount) - { - // This mesh contains only degenerate triangles, there's nothing we can do. - if (bStaticMeshCreated) - StaticMesh->MarkPendingKill(); - - continue; - } - */ - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL ATTRIBUTE OVERRIDES - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: These are actually per faces, not per vertices... - // Need to update!! - UpdatePartFaceMaterialOverridesIfNeeded(); - - //--------------------------------------------------------------------------------------------------------------------- - // FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Handle Materials!!!! - - // Get face indices for this split. - TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; - - // We need to reset the Static Mesh's materials once per SM: - // so, for the first lod, or the main geo... - if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) - { - FoundStaticMesh->StaticMaterials.Empty(); - MeshMaterialsHaveBeenReset = true; - } - - // .. or for each visible complex collider - if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - FoundStaticMesh->StaticMaterials.Empty(); - - // Process material overrides first - if (PartFaceMaterialOverrides.Num() > 0) - { - // If the part has material overrides - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - continue; - - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); - int32 CurrentFaceMaterialIdx = 0; - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; - - // Start by looking in our assignment map - auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface && !MaterialName.IsEmpty()) - { - // Only try to load a material if has a chance to be valid! - MaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); - } - else - { - // The Attribute Material and its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else - { - // If everything fails, we'll use the default material - MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); - } - } - } - - // Update the Face Material on the mesh - RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; - } - } - else if (PartUniqueMaterialIds.Num() > 0) - { - // The part has houdini materials - if (bOnlyOneFaceMaterial) - { - // We have only one material. - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - else - { - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - // Reset Rawmesh material face assignments. - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; - } - - UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Add the material to the Static mesh - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); - - // Update the face index - RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; - } - } - } - else - { - // No materials were found, we need to use default Houdini material. - int32 SplitFaceCount = SplitFaceIndices.Num(); - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceCount); - - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - - // TODO: - // BUILD SETTINGS - // (Using default for now) - SrcModel->BuildSettings.bRemoveDegenerates = true; - SrcModel->BuildSettings.bUseMikkTSpace = true; - SrcModel->BuildSettings.bBuildAdjacencyBuffer = false; - SrcModel->BuildSettings.MinLightmapResolution = 64; - SrcModel->BuildSettings.bUseFullPrecisionUVs = false; - SrcModel->BuildSettings.SrcLightmapIndex = 0; - SrcModel->BuildSettings.DstLightmapIndex = 1; - SrcModel->BuildSettings.bRecomputeNormals = (0 == RawMesh.WedgeTangentZ.Num()); - SrcModel->BuildSettings.bRecomputeTangents = (0 == RawMesh.WedgeTangentX.Num() || 0 == RawMesh.WedgeTangentY.Num()); - SrcModel->BuildSettings.bGenerateLightmapUVs = RawMesh.WedgeTexCoords->Num() <= 0; - - // Check for a lightmap resolution override - int32 LightMapResolutionOverride = -1; - if (PartLightMapResolutions.Num() > 0) - LightMapResolutionOverride = PartLightMapResolutions[0]; - - if (LightMapResolutionOverride > 0) - FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; - else - FoundStaticMesh->LightMapResolution = 64; - - // TODO: - // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - - // By default the distance field resolution should be set to 2.0 - // TODO should come from the HAC - //SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniCookParams.GeneratedDistanceFieldResolutionScale; - SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; - - // This is required due to the impeding deprecation of FRawMesh - // If we dont update this UE4 will crash upon deleting an asset. - SrcModel->StaticMeshOwner = FoundStaticMesh; - // Store the new raw mesh. - SrcModel->SaveRawMesh(RawMesh); - - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = GetLODSCreensizeForSplit(SplitGroupName); - if (screensize >= 0.0f) - { - // Only apply the LOD screensize if it's valid - SrcModel->ScreenSize = screensize; - //FoundStaticMesh->GetSourceModel(LODIndex).ScreenSize = screensize; - FoundStaticMesh->bAutoComputeLODScreenSize = false; - } - - // TODO: - // SET STATIC MESH GENERATION PARAM - // HANDLE COLLIDERS - // REMOVE OLD COLLIDERS - // CUSTOM BAKE NAME OVERRIDE - - // Update property attributes on the SM - TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( - HGPO.GeoId, HGPO.PartId, - AllSplitFirstValidVertexIndex[SplitGroupName], - AllSplitFirstValidPrimIndex[SplitGroupName], - PropertyAttributes)) - { - UpdateGenericPropertiesAttributes( - FoundStaticMesh, PropertyAttributes); - } - - TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - { - // cache the level path attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); - } - } - - TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - // cache the output name attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); - } - } - - TArray TileValues; - if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); - } - } - - TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) - { - if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); - } - } - - TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) - { - if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); - } - } - - // Notify that we created a new Static Mesh if needed - if (bNewStaticMeshCreated) - FAssetRegistryModule::AssetCreated(FoundStaticMesh); - - // Add the Static mesh to the output maps and the build map if we haven't already - if (FoundOutputObject) - { - FoundOutputObject->OutputObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = false; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - - StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); - } - - // Look if we only have colliders - // If we do, we'll allow attaching sockets to the collider meshes - bool bCollidersOnly = true; - for (auto& Current : StaticMeshToBuild) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) - { - bCollidersOnly = false; - break; - } - } - - FHoudiniScopedGlobalSilence ScopedGlobalSilence; - for (auto& Current : StaticMeshToBuild) - { - UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) - continue; - - UBodySetup * BodySetup = SM->BodySetup; - if (!BodySetup) - { - SM->CreateBodySetup(); - BodySetup = SM->BodySetup; - } - - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - - // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) - { - // Make sure rendering is done - so we are not changing data being used by collision drawing. - FlushRenderingCommands(); - - // Clean up old colliders from a previous cook - BodySetup->Modify(); - BodySetup->RemoveSimpleCollision(); - // Create new GUID - BodySetup->InvalidatePhysicsData(); - - FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; - FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); - if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) - { - BodySetup->AddCollisionFrom(*CurrentAggColl); - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; - } - - RefreshCollisionChange(*SM); - SM->bCustomizedCollision = true; - - // See if we need to enable collisions on the whole mesh - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - // Complex collider, enable collisions for this static mesh. - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - else - { - // TODO - // if the LODForCollision uproperty attribute is set, we need to activate complex collision - // on the static mesh for that lod to be picked up properly as a collider - if (FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, - "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) - { - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - } - } - - // Add the Sockets to the StaticMesh - // We only add them to the main geo, or to the colliders if we only generate colliders - bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; - if (bAddSocket) - { - if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); - } - } - - // BUILD the Static Mesh - // bSilent doesnt add the Build Errors... - double build_start = FPlatformTime::Seconds(); - TArray SMBuildErrors; - SM->Build(true, &SMBuildErrors); - double build_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); - - SM->GetOnMeshChanged().Broadcast(); - - /* - // Try to find the outer package so we can dirty it up - if (SM->GetOuter()) - { - SM->GetOuter()->MarkPackageDirty(); - } - else - { - SM->MarkPackageDirty(); - } - */ - - - UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - TArray PackageToSave; - PackageToSave.Add(MeshPackage); - - // Save the created package - FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); - */ - } - } - - // TODO: Still necessary ? SM->Build should actually update the navmesh... - // Now that all the meshes are built and their collisions meshes and primitives updated, - // we need to update their pre-built navigation collision used by the navmesh - for (auto& Iter : OutputObjects) - { - UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) - continue; - - UBodySetup * BodySetup = StaticMesh->BodySetup; - if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) - { - // Unreal caches the Navigation Collision and never updates it for StaticMeshes, - // so we need to manually flush and recreate the data to have proper navigation collision - BodySetup->InvalidatePhysicsData(); - BodySetup->CreatePhysicsMeshes(); - StaticMesh->NavCollision->Setup(BodySetup); - } - } - - double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() executed in %f seconds."), time_end - time_start); - - return true; -} - -bool -FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() -{ - double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - // Simple colliders first, lods and finally, invisible colliders (that are separate Static Mesh) - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Prepare the object that will store UCX and simple colliders - AllAggregateCollisions.Empty(); - - // We need to know the number of LODs that will be needed for this part - int32 NumberOfLODs = 0; - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) - NumberOfLODs++; - else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - bHasMainGeo = true; - } - - // Update the part's material's IDS and info now - CreateNeededMaterials(); - - // Check if the materials were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Get the current target platform for default lod policies - ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); - check(CurrentPlatform); - - // New mesh list - TMap StaticMeshToBuild; - - // Map of Houdini Material IDs to Unreal Material Indices - TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; - - bool MeshMaterialsHaveBeenReset = false; - - // Mesh Socket array - TArray AllSockets; - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - - double tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); - - // Iterate through all detected split groups we care about and split geometry. - // The split are ordered in the following way: - // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], - OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - - // Get/Create the Aggregate Collisions for this mesh identifier - FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); - - // Handle UCX / Convex Hull colliders - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) - { - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the convex hull colliders and add them to the Aggregate - if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) - continue; - } - else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) - { - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the simple colliders and add them to the aggregate - if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) - continue; - } - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing SM from a previous cook - UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - // Prepare LOD Group data for this static mesh - FStaticMeshLODGroup LODGroup; - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing static mesh, create a new one - FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - - // Use the platform's default LODGroup policy - // TODO? Add setting for default LOD Group? - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); - } - else - { - // Try to reuse the existing SM's LOD group instead of the default one - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); - } - - if (!FoundOutputObject) - { - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - } - else - { - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - FoundOutputObject->CachedAttributes.Empty(); - FoundOutputObject->CachedTokens.Empty(); - } - FoundOutputObject->bProxyIsCurrent = false; - - // TODO: Needed? - // Free any RHI resources for existing mesh before we re-create in place. - FoundStaticMesh->PreEditChange(NULL); - - // Check that the Static Mesh we found has the appropriate number of Source models/LODs - int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); - - // LODs are only for the "main" mesh, not for complex colliders! - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); - - if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) - { - while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) - FoundStaticMesh->AddSourceModel(); - - // We may have to remove excessive LOD levels - if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) - FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); - - // Initialize their default reduction setting - for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) - { - FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); - } - FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); - } - - // By default, always work on the first source model, unless we're a LOD - int32 SrcModelIndex = 0; - int32 LODIndex = 0; - if (SplitType == EHoudiniSplitType::LOD) - { - for (auto& curSplit : AllSplitGroups) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); - if (CurrentSplitType == EHoudiniSplitType::LOD - || CurrentSplitType == EHoudiniSplitType::Normal) - { - LODIndex++; - } - - if (curSplit == SplitGroupName) - break; - } - - // Fix for the case where we don't have a main geo - if(!bHasMainGeo) - LODIndex--; - } - - // Grab the appropriate SourceModel - FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; - if (!SrcModel) - { - HOUDINI_LOG_ERROR( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); - continue; - } - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - bool bRecomputeNormal = false; - bool bRecomputeTangent = false; - - // Load the existing mesh description if we don't need to rebuild the mesh - //FRawMesh RawMesh; - FMeshDescription* MeshDescription; - if (!bRebuildStaticMesh) - { - // We dont need to rebuild the mesh itself: - // the geometry hasn't changed, but the materials have. - // We can just reuse the old MeshDescription and reuse it. - MeshDescription = FoundStaticMesh->GetMeshDescription(LODIndex); - } - else - { - // Extract all the data needed for this split - // Start by initializing the MeshDescription for this LOD - MeshDescription = FoundStaticMesh->CreateMeshDescription(LODIndex); - FStaticMeshAttributes(*MeshDescription).Register(); - - // Mesh description uses material to create its PolygonGroups, - // so we first need to know how many different materials we have for this split - // and what vertices/indices belong to each material for remapping - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // SplitNeededVertices - // Array containing the (unique) part indices for the vertices that are needed for this split - // SplitNeededVertices[splitIndex] = PartIndex - TArray SplitNeededVertices; - //SplitNeededVertices.SetNumZeroed(SplitVertexCount); - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" so that IndicesMapper[ partIndex ] => splitIndex - TArray PartToSplitIndicesMapper; - PartToSplitIndicesMapper.Init(-1, SplitVertexList.Num()); - //TMap SplitToPartIndicesMapper; - - // SplitIndices - // Array of SplitIndices used to describe this split's polygons - TArray SplitIndices; - SplitIndices.SetNumZeroed(SplitVertexCount); - - int32 CurrentSplitIndex = 0; - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[0]) - || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[1]) - || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (PartToSplitIndicesMapper[WedgeIndices[i]] < 0) - { - // This part index has not yet been "converted" to a new split index - SplitNeededVertices.Add(WedgeIndices[i]); - PartToSplitIndicesMapper[WedgeIndices[i]] = CurrentSplitIndex; - //SplitToPartIndicesMapper.Add(CurrentSplitIndex, WedgeIndices[i]); - CurrentSplitIndex++; - } - - // Replace the old part index with the new split index - WedgeIndices[i] = PartToSplitIndicesMapper[WedgeIndices[i]]; - } - - if (!SplitIndices.IsValidIndex(ValidVertexId + 2)) - break; - - // Flip wedge indices to fix the winding order. - SplitIndices[ValidVertexId + 0] = WedgeIndices[0]; - SplitIndices[ValidVertexId + 1] = WedgeIndices[2]; - SplitIndices[ValidVertexId + 2] = WedgeIndices[1]; - - ValidVertexId += 3; - } - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract position for this part - UpdatePartPositionIfNeeded(); - - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - TVertexAttributesRef VertexPositions = - MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - - MeshDescription->ReserveNewVertices(SplitNeededVertices.Num()); - for ( const int32& NeededVertexIndex : SplitNeededVertices) - { - // Create a new Vertex - FVertexID VertexID = MeshDescription->CreateVertex(); - if (PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexPositions[VertexID].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexPositions[VertexID].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexPositions[VertexID].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - else - { - // Error when retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - } - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: Check if still needed for MeshDescription - // We need to reset the Static Mesh's materials once per SM: - // so, for the first lod, or the main geo... - if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) - { - FoundStaticMesh->StaticMaterials.Empty(); - MeshMaterialsHaveBeenReset = true; - } - - // .. or for each visible complex collider - if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - FoundStaticMesh->StaticMaterials.Empty(); - - // Get this split's faces - TArray& SplitGroupFaceIndices = AllSplitFaceIndices[SplitGroupName]; - // Array holding the materials needed for this split - //TArray SplitMaterials; - // Split Material indices per face, by default all faces are set to use the first Material - TArray SplitFaceMaterialIndices; - SplitFaceMaterialIndices.SetNumZeroed(SplitGroupFaceIndices.Num()); - - bool HasHoudiniMaterials = PartUniqueMaterialIds.Num() > 0; - bool HasMaterialOverrides = PartFaceMaterialOverrides.Num() > 0; - if (!HasHoudiniMaterials && !HasMaterialOverrides) - { - // We don't have any material override or houdini material - // we just need one polygon group using the default Houdini material. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // TODO: ? Add default mat to the assignement map? - } - else if (HasHoudiniMaterials && !HasMaterialOverrides) - { - // We have Houdini Material but no overrides - if (bOnlyOneFaceMaterial || PartUniqueMaterialIds.Num() == 1) - { - // We have only one Houdini material. - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // TODO: ? Add the mat to the assignement map? - } - else - { - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * MaterialDefault = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - // Reset Rawmesh material face assignments. - for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just use its material index - SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; - } - - UMaterialInterface * MaterialInterface = Cast(MaterialDefault); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Add the material to the Static mesh - //int32 UnrealMatIndex = SplitMaterials.Add(Material); - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); - - // Update the face index - SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; - } - } - } - else - { - // If we have material overrides - for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; - - int32 CurrentFaceMaterialIdx = -1; - if (PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - { - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); - if (FoundFaceMaterialIdx) - { - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; - if (!MaterialName.IsEmpty()) - { - // Only try to load a material if has a chance to be valid! - MaterialInterface = Cast< UMaterialInterface >( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); - } - } - - if (CurrentFaceMaterialIdx < 0) - { - // The attribute Material or its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else - { - // If everything else fails, we'll use the default material - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); - } - } - } - - // Update the Face Material on the mesh - SplitFaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; - } - } - - // Create a Polygon Group for each material slot - TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = - MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - - // We must use the number of assignment materials found to reserve the number of material slots - // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials - int32 NumberOfMaterials = OutputAssignmentMaterials.Num(); - if (NumberOfMaterials <= 0) - { - // No materials, create a polygon group for the default one - const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - } - else - { - MeshDescription->ReserveNewPolygonGroups(NumberOfMaterials); - //for (int32 MatIndex = 0; MatIndex < NumberOfMaterials; ++MatIndex) - for (auto& CurrentMatAssignement : OutputAssignmentMaterials) - { - const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = - FName(CurrentMatAssignement.Value ? *(CurrentMatAssignement.Value->GetName()) : *(CurrentMatAssignement.Key)); - } - } - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - // - // VERTEX INSTANCE ATTRIBUTES - // NORMALS, TANGENTS, COLORS, UVS, Alpha - // - - // Extract the normals - UpdatePartNormalsIfNeeded(); - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - - // Extract the tangents - // No need to read the tangents if we want unreal to recompute them after - TArray SplitTangentU; - TArray SplitTangentV; - bool bReadTangents = true; - // TODO: Add runtime setting check! - //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - int32 NormalCount = SplitNormals.Num(); - bool bGenerateTangents = (NormalCount > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - // Check that the number of tangents read matches the number of normals - if (SplitTangentU.Num() != NormalCount || SplitTangentV.Num() != NormalCount) - bGenerateTangents = true; - - /* - // TODO: Add settings check! - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - */ - - // Generate the tangents if needed - if (bGenerateTangents) - { - SplitTangentU.SetNumZeroed(NormalCount); - SplitTangentV.SetNumZeroed(NormalCount); - for (int32 Idx = 0; Idx + 2 < NormalCount; Idx += 3) - { - FVector TangentZ; - TangentZ.X = SplitNormals[Idx + 0]; - TangentZ.Y = SplitNormals[Idx + 2]; - TangentZ.Z = SplitNormals[Idx + 1]; - - FVector TangentX, TangentY; - TangentZ.FindBestAxisVectors(TangentX, TangentY); - - SplitTangentU[Idx + 0] = TangentX.X; - SplitTangentU[Idx + 2] = TangentX.Y; - SplitTangentU[Idx + 1] = TangentX.Z; - - SplitTangentV[Idx + 0] = TangentY.X; - SplitTangentV[Idx + 2] = TangentY.Y; - SplitTangentV[Idx + 1] = TangentY.Z; - } - } - } - TVertexInstanceAttributesRef VertexInstanceTangents = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - - // Extract the color values - UpdatePartColorsIfNeeded(); - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract the alpha values - UpdatePartAlphasIfNeeded(); - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - TVertexInstanceAttributesRef VertexInstanceColors = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - - // Extract UVs - UpdatePartUVSetsIfNeeded(true); - // See if we need to transfer uv point attributes to vertex attributes. - int32 UVSetCount = PartUVSets.Num(); - TArray> SplitUVSets; - SplitUVSets.SetNum(UVSetCount); - for (int32 TexCoordIdx = 0; TexCoordIdx < UVSetCount; TexCoordIdx++) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - } - TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - VertexInstanceUVs.SetNumIndices(UVSetCount); - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - // Allocate space for the vertex instances and polygons - MeshDescription->ReserveNewVertexInstances(SplitIndices.Num()); - MeshDescription->ReserveNewPolygons(SplitIndices.Num() / 3); - //Approximately 2.5 edges per polygons - MeshDescription->ReserveNewEdges(SplitIndices.Num() * 2.5f / 3); - - bool bHasNormal = SplitNormals.Num() > 0; - bool bHasTangents = SplitTangentU.Num() > 0 && SplitTangentV.Num() > 0; - bool bHasRGB = SplitColors.Num() > 0; - bool bHasRGBA = bHasRGB && AttribInfoColors.tupleSize == 4; - bool bHasAlpha = SplitAlphas.Num() > 0; - - bRecomputeNormal = !bHasNormal; - bRecomputeTangent = !bHasTangents; - - TArray HasUVSets; - HasUVSets.SetNumZeroed(PartUVSets.Num()); - for (int32 Idx = 0; Idx < PartUVSets.Num(); Idx++) - HasUVSets[Idx] = PartUVSets[Idx].Num() > 0; - - uint32 FaceCount = SplitIndices.Num() / 3; - for (uint32 FaceIndex = 0; FaceIndex < FaceCount; FaceIndex++) - { - TArray FaceVertexInstanceIDs; - FaceVertexInstanceIDs.SetNum(3); - - // Ignore degenerate triangles - FVertexID VertexIDs[3]; - for (int32 Corner = 0; Corner < 3; ++Corner) - { - VertexIDs[Corner] = FVertexID(SplitIndices[(FaceIndex * 3) + Corner]); - } - if (VertexIDs[0] == VertexIDs[1] || VertexIDs[0] == VertexIDs[2] || VertexIDs[1] == VertexIDs[2]) - continue; - - //FVertexID FaceVertexIDs[3]; - for (int32 Corner = 0; Corner < 3; Corner++) - { - uint32 SplitIndex = (FaceIndex * 3) + Corner; - uint32 SplitVertexIndex = SplitIndices[SplitIndex]; - const FVertexInstanceID& VertexInstanceID = MeshDescription->CreateVertexInstance(FVertexID(SplitVertexIndex)); - - // Fix the winding order by updating the SplitIndex (invert corner 1 and 2) - // instead of going 0 1 2 go 0 2 1 - // TODO; this slows down StaticMesh->Build() considerably! - Corner == 1 ? SplitIndex++ : Corner == 2 ? SplitIndex-- : SplitIndex; - - const uint32 SplitVertexIndex_X = SplitIndex * 3 + 0; - const uint32 SplitVertexIndex_Y = SplitIndex * 3 + 2; - const uint32 SplitVertexIndex_Z = SplitIndex * 3 + 1; - // Normals - if (bHasNormal) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexInstanceNormals[VertexInstanceID].X = SplitNormals[SplitVertexIndex_X]; - VertexInstanceNormals[VertexInstanceID].Y = SplitNormals[SplitVertexIndex_Y]; - VertexInstanceNormals[VertexInstanceID].Z = SplitNormals[SplitVertexIndex_Z]; - } - - // Tangents and binormals - if (bHasTangents) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexInstanceTangents[VertexInstanceID].X = SplitTangentU[SplitVertexIndex_X]; - VertexInstanceTangents[VertexInstanceID].Y = SplitTangentU[SplitVertexIndex_Y]; - VertexInstanceTangents[VertexInstanceID].Z = SplitTangentU[SplitVertexIndex_Z]; - - FVector TangentY; - TangentY.X = SplitTangentV[SplitVertexIndex_X]; - TangentY.Y = SplitTangentV[SplitVertexIndex_Y]; - TangentY.Z = SplitTangentV[SplitVertexIndex_Z]; - - VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign( - VertexInstanceTangents[VertexInstanceID].GetSafeNormal(), - TangentY.GetSafeNormal(), - VertexInstanceNormals[VertexInstanceID].GetSafeNormal()); - } - - // Color - FLinearColor Color = FLinearColor::White; - if (bHasRGB) - { - Color.R = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); - Color.G = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); - Color.B = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); - } - // Alpha - if (bHasAlpha) - { - Color.A = FMath::Clamp(SplitAlphas[SplitIndex], 0.0f, 1.0f); - } - else if (bHasRGBA) - { - Color.A = FMath::Clamp(SplitColors[SplitIndex * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); - } - VertexInstanceColors[VertexInstanceID] = FVector4(Color); - - // UVs - for (int32 UVIndex = 0; UVIndex < SplitUVSets.Num(); UVIndex++) - { - if (HasUVSets[UVIndex]) - { - // We need to flip V coordinate when it's coming from HAPI. - FVector2D CurrentUV; - CurrentUV.X = SplitUVSets[UVIndex][SplitIndex * 2 + 0]; - CurrentUV.Y = 1.0f - SplitUVSets[UVIndex][SplitIndex * 2 + 1]; - - VertexInstanceUVs.Set(VertexInstanceID, UVIndex, CurrentUV); - } - } - - FaceVertexInstanceIDs[Corner] = VertexInstanceID; - } - - const FPolygonGroupID PolygonGroupID(SplitFaceMaterialIndices[FaceIndex]); - - // Insert a triangle into the mesh - MeshDescription->CreateTriangle(PolygonGroupID, FaceVertexInstanceIDs); - } - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's FaceSmoothing values if needed - UpdatePartFaceSmoothingIfNeeded(); - - // Get the FaceSmoothing values for this split - TArray SplitFaceSmoothingMasks; - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); - - // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! - // TODO: Expose the default FaceSmoothing value - // 0 will make hard face - TArray FaceSmoothingMasks; - FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); - - // Check that the number of face smoothing values we retrieved is correct - int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; - if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) - { - // Ignore our face smoothing values - WedgeFaceSmoothCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); - } - - // Transfer the face smoothing masks to the raw mesh if we have any - for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) - { - FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; - } - - // TODO - // Check - FStaticMeshOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, *MeshDescription); - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - // Extract this part's LightmapResolution values if needed - UpdatePartLightmapResolutionsIfNeeded(); - - // make sure the mesh has a new lighting guid - FoundStaticMesh->LightingGuid = FGuid::NewGuid(); - } - - // TODO: - // BUILD SETTINGS - // (Using default for now) - SrcModel->BuildSettings.bRemoveDegenerates = true; - SrcModel->BuildSettings.bUseMikkTSpace = true; - SrcModel->BuildSettings.bBuildAdjacencyBuffer = false; - SrcModel->BuildSettings.MinLightmapResolution = 64; - SrcModel->BuildSettings.bUseFullPrecisionUVs = false; - SrcModel->BuildSettings.SrcLightmapIndex = 0; - SrcModel->BuildSettings.DstLightmapIndex = 1; - SrcModel->BuildSettings.bRecomputeNormals = bRecomputeNormal; - SrcModel->BuildSettings.bRecomputeTangents = bRecomputeNormal || bRecomputeTangent; - SrcModel->BuildSettings.bGenerateLightmapUVs = PartUVSets.Num() <= 0; - - // Set the lightmap Coordinate Index - // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention - FoundStaticMesh->LightMapCoordinateIndex = PartUVSets.Num() > 1 ? 1 : 0; - - // Check for a lightmapa resolution override - int32 LightMapResolutionOverride = -1; - if ( PartLightMapResolutions.Num() > 0) - LightMapResolutionOverride = PartLightMapResolutions[0]; - - if (LightMapResolutionOverride > 0) - FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; - else - FoundStaticMesh->LightMapResolution = 64; - - // TODO: - // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - - // By default the distance field resolution should be set to 2.0 - // TODO should come from the HAC - //SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniCookParams.GeneratedDistanceFieldResolutionScale; - SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; - - // RAW MESH CHECKS - - // TODO: Check not needed w/ FMeshDesc - // This is required due to the impeding deprecation of FRawMesh - // If we dont update this UE4 will crash upon deleting an asset. - //SrcModel->StaticMeshOwner = FoundStaticMesh; - - // Store the new MeshDescription - FoundStaticMesh->CommitMeshDescription(LODIndex); - //Set the Imported version before calling the build - FoundStaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; - - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = GetLODSCreensizeForSplit(SplitGroupName); - if (screensize >= 0.0f) - { - // Only apply the LOD screensize if it's valid - SrcModel->ScreenSize = screensize; - //FoundStaticMesh->GetSourceModel(LodIndex).ScreenSize = screensize; - FoundStaticMesh->bAutoComputeLODScreenSize = false; - } - - // SET STATIC MESH GENERATION PARAM - // HANDLE COLLIDERS - // REMOVE OLD COLLIDERS - // CUSTOM BAKE NAME OVERRIDE - - // UPDATE UPROPERTY ATTRIBS - // Update property attributes on the SM - TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( - HGPO.GeoId, HGPO.PartId, - AllSplitFirstValidVertexIndex[SplitGroupName], - AllSplitFirstValidPrimIndex[SplitGroupName], - PropertyAttributes)) - { - UpdateGenericPropertiesAttributes( - FoundStaticMesh, PropertyAttributes); - } - - TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - { - // cache the level path attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); - } - } - - TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - // cache the output name attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); - } - } - - TArray TileValues; - if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); - } - } - - TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) - { - if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); - } - } - - TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) - { - if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); - } - } - - // Notify that we created a new Static Mesh if needed - if(bNewStaticMeshCreated) - FAssetRegistryModule::AssetCreated(FoundStaticMesh); - - // Add the Static mesh to the output maps and the build map if we haven't already - if (FoundOutputObject) - { - FoundOutputObject->OutputObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = false; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - - StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished MD in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Look if we only have colliders - // If we do, we'll allow attaching sockets to the collider meshes - bool bCollidersOnly = true; - for (auto& Current : StaticMeshToBuild) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) - { - bCollidersOnly = false; - break; - } - } - - FHoudiniScopedGlobalSilence ScopedGlobalSilence; - for (auto& Current : StaticMeshToBuild) - { - UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) - continue; - - UBodySetup * BodySetup = SM->BodySetup; - if (!BodySetup) - { - SM->CreateBodySetup(); - BodySetup = SM->BodySetup; - } - - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - - // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) - { - // Make sure rendering is done - so we are not changing data being used by collision drawing. - FlushRenderingCommands(); - - // Clean up old colliders from a previous cook - BodySetup->Modify(); - BodySetup->RemoveSimpleCollision(); - // Create new GUID - BodySetup->InvalidatePhysicsData(); - - FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; - FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); - if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) - { - BodySetup->AddCollisionFrom(*CurrentAggColl); - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; - } - - // Moved RefreshCollisionChange to after the SM->Build call - // RefreshCollisionChange(*SM); - SM->bCustomizedCollision = true; - - // See if we need to enable collisions on the whole mesh - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - // Complex collider, enable collisions for this static mesh. - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - else - { - // TODO - // if the LODForCollision uproperty attribute is set, we need to activate complex collision - // on the static mesh for that lod to be picked up properly as a collider - if ( FHoudiniEngineUtils::HapiCheckAttributeExists( HGPO.GeoId, HGPO.PartId, - "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) - { - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - } - } - - // Add the Sockets to the StaticMesh - // We only add them to the main geo, or to the colliders if we only generate colliders - bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; - if (bAddSocket) - { - if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); - } - } - - // BUILD the Static Mesh - // bSilent doesnt add the Build Errors... - double build_start = FPlatformTime::Seconds(); - TArray SMBuildErrors; - SM->Build(true, &SMBuildErrors); - double build_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); - - // TODO: copied the content of RefreshCollision below and commented out CreateNavCollision - // it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, - // and can be expensive depending on the vert/poly count of the mesh - // TODO: also moved this to after the call to Build, since Build updates the mesh's - // physics state (calling this before Build when rebuilding an existing high poly mesh as - // low poly mesh, incurs quite a performance hit. This is likely due to processing of physics - // meshes with high vert/poly count before the Build - // RefreshCollisionChange(*SM); - { - // SM->CreateNavCollision(/*bIsUpdate=*/true); - - for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) - { - UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); - if (StaticMeshComponent->GetStaticMesh() == SM) - { - // it needs to recreate IF it already has been created - if (StaticMeshComponent->IsPhysicsStateCreated()) - { - StaticMeshComponent->RecreatePhysicsState(); - } - } - } - - FEditorSupportDelegates::RedrawAllViewports.Broadcast(); - } - - SM->GetOnMeshChanged().Broadcast(); - /* - // Try to find the outer package so we can dirty it up - if (SM->GetOuter()) - { - SM->GetOuter()->MarkPackageDirty(); - } - else - { - SM->MarkPackageDirty(); - } - */ - - UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - TArray PackageToSave; - PackageToSave.Add(MeshPackage); - - // Save the created package - FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); - */ - } - } - - // TODO: Still necessary ? SM->Build should actually update the navmesh... - // TODO: Commented out for now, since it appears that the content of the loop is - // already called in UStaticMesh::BuildInternal and UStaticMesh::PostBuildInternal - //// Now that all the meshes are built and their collisions meshes and primitives updated, - //// we need to update their pre-built navigation collision used by the navmesh - //for (auto& Iter : OutputObjects) - //{ - // UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); - // if (!StaticMesh || StaticMesh->IsPendingKill()) - // continue; - - // UBodySetup * BodySetup = StaticMesh->BodySetup; - // if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) - // { - // // Unreal caches the Navigation Collision and never updates it for StaticMeshes, - // // so we need to manually flush and recreate the data to have proper navigation collision - // // TODO: Is this still required? These two functions are called by - // // UStaticMesh::BuildInternal, which is called by UStaticMesh::Build/BatchBuild - // // BodySetup->InvalidatePhysicsData(); - // // BodySetup->CreatePhysicsMeshes(); - - // // TODO: Is this still required? This function is called by UStaticMesh::CreateNavCollision - // // which is called by the UStaticMesh::PostBuildInternal function, which is called at the - // // end of the build. - // // StaticMesh->NavCollision->Setup(BodySetup); - // } - //} - - double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() executed in %f seconds."), time_end - time_start); - - return true; -} - -bool -FHoudiniMeshTranslator::CreateHoudiniStaticMesh() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh")); - - const double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Determine if there is "main" geo, if not we'll use the first LOD - // as main geo - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - { - bHasMainGeo = true; - break; - } - } - - // Update the part's material's IDS and info now - //UpdatePartFaceMaterialsIfNeeded(); - CreateNeededMaterials(); - - // Check if the materials were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Map of Houdini Material IDs to Unreal Material Indices - TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; - - bool MeshMaterialsHaveBeenReset = false; - - double tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); - - // Iterate through all detected split groups we care about and split geometry. - bool bMainGeoOrFirstLODFound = false; - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Per Split")); - - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // We are only interested in the Normal/main geo and visible colliders - if (SplitType != EHoudiniSplitType::Normal && - SplitType != EHoudiniSplitType::LOD && - SplitType != EHoudiniSplitType::RenderedComplexCollider && - SplitType != EHoudiniSplitType::RenderedSimpleCollider && - SplitType != EHoudiniSplitType::RenderedUCXCollider) - { - continue; - } - - // We only use LOD if there is no Normal geo - if (SplitType == EHoudiniSplitType::Normal) - { - bMainGeoOrFirstLODFound = true; - HOUDINI_LOG_MESSAGE(TEXT("Found Normal geo for mesh.")); - } - else if (SplitType == EHoudiniSplitType::LOD) - { - if (bHasMainGeo) - { - HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since the mesh has Normal geo.")); - continue; - } - else if (bMainGeoOrFirstLODFound) - { - HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since we have already processed the first LOD.")); - continue; - } - else - { - bMainGeoOrFirstLODFound = true; - HOUDINI_LOG_MESSAGE(TEXT("Mesh does not have Normal geo, found first LOD.")); - } - } - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], - OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing DM from a previous cook - UHoudiniStaticMesh* FoundStaticMesh = FindExistingHoudiniStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing dynamic mesh, create a new one - FoundStaticMesh = CreateNewHoudiniStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - } - - if (!FoundOutputObject) - { - // If we couldnt find a previous output object, create a new one - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - } - FoundOutputObject->bProxyIsCurrent = true; - - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - if (bRebuildStaticMesh) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build/Rebuild UHoudiniStaticMesh")); - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" - // So that IndicesMapper[ oldIndex ] => newIndex - TArray IndicesMapper; - IndicesMapper.Init(-1, SplitVertexList.Num()); - int32 CurrentMapperIndex = 0; - - // NeededVertices: - // Array containing the old index of the needed vertices for the current split - // NeededVertices[ newIndex ] => oldIndex - TArray< int32 > NeededVertices; - NeededVertices.Reserve(SplitVertexList.Num() / 3); - TArray< int32 > TriangleIndices; - TriangleIndices.Reserve(SplitVertexList.Num()); - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build IndicesMapper and NeededVertices")); - - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) - || !IndicesMapper.IsValidIndex(WedgeIndices[1]) - || !IndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (IndicesMapper[WedgeIndices[i]] < 0) - { - // This old index has not yet been "converted" to a new index - NeededVertices.Add(WedgeIndices[i]); - IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; - CurrentMapperIndex++; - } - - // Replace the old index with the new one - WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; - } - - // Flip wedge indices to fix the winding order. - TriangleIndices.Add(WedgeIndices[0]); - TriangleIndices.Add(WedgeIndices[2]); - TriangleIndices.Add(WedgeIndices[1]); - - ValidVertexId += 3; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's normal if needed - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - // Check that the number of normal we retrieved is correct - int32 NormalCount = SplitNormals.Num() / 3; - if (NormalCount < 0 || NormalCount < NeededVertices.Num()) - { - // Ignore normals - NormalCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENTS - //--------------------------------------------------------------------------------------------------------------------- - - TArray< float > SplitTangentU; - TArray< float > SplitTangentV; - int32 TangentUCount = 0; - int32 TangentVCount = 0; - // No need to read the tangents if we want unreal to recompute them after - // TODO: Add runtime setting check! - //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; - bool bReadTangents = true; - bool bGenerateTangents = false; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - - // Check that the number of tangents read matches the number of normals - TangentUCount = SplitTangentU.Num() / 3; - TangentVCount = SplitTangentV.Num() / 3; - if (TangentUCount != NormalCount || TangentVCount != NormalCount) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh: Generate tangents due to count mismatch (# U Tangents = %d; # V Tangents = %d; # Normals = %d)"), TangentUCount, TangentVCount, NormalCount); - bGenerateTangents = true; - } - - /* - // TODO: Add settings check! - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - */ - } - - //--------------------------------------------------------------------------------------------------------------------- - // VERTEX COLORS AND ALPHAS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's colors if needed - UpdatePartColorsIfNeeded(); - - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract this part's alpha values if needed - UpdatePartAlphasIfNeeded(); - - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - - const int32 ColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; - const bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && ColorsCount > 0; - const bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == ColorsCount); - - //--------------------------------------------------------------------------------------------------------------------- - // UVS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's UV sets if needed - UpdatePartUVSetsIfNeeded(); - - // See if we need to transfer uv point attributes to vertex attributes. - int32 NumUVLayers = 0; - TArray> SplitUVSets; - SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - if (SplitUVSets[TexCoordIdx].Num() > 0) - { - NumUVLayers++; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL ATTRIBUTE OVERRIDES - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: These are actually per faces, not per vertices... - // Need to update!! - UpdatePartFaceMaterialOverridesIfNeeded(); - - // - // Initialize mesh - // - const int32 NumVertexPositions = NeededVertices.Num(); - const int32 NumTriangles = TriangleIndices.Num() / 3; - const bool bHasPerFaceMaterials = PartFaceMaterialOverrides.Num() > 0 || (PartUniqueMaterialIds.Num() > 0 && !bOnlyOneFaceMaterial); - - FoundStaticMesh->Initialize( - NumVertexPositions, - NumTriangles, - NumUVLayers, // NumUVLayers - 0, // InitialNumStaticMaterials - NormalCount > 0, // HasNormals - NormalCount > 0 && bReadTangents, // HasTangents - bSplitColorValid, // HasColors - bHasPerFaceMaterials // HasPerFaceMaterials - ); - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - UpdatePartPositionIfNeeded(); - - // - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Vertex Positions")); - - for (int32 VertexPositionIdx = 0; VertexPositionIdx < NumVertexPositions; ++VertexPositionIdx) - //ParallelFor(NumVertexPositions, [&](uint32 VertexPositionIdx) - { - int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; - if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // We need to swap Z and Y coordinate here, and convert from m to cm. - FoundStaticMesh->SetVertexPosition(VertexPositionIdx, FVector( - PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION, - PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION, - PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION - )); - }//); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACES / TRIS - // Now set Normals, UVs and Colors on mesh points and AttributeSet - //--------------------------------------------------------------------------------------------------------------------- - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Triangle Indices & Per Vertex Instance Attribute Values")); - - // Now add the triangles to the mesh - for (int32 TriangleIdx = 0; TriangleIdx < NumTriangles; ++TriangleIdx) - // ParallelFor(NumTriangles, [&](uint32 TriangleIdx) - { - - const int32 TriVertIdx0 = TriangleIdx * 3; - FoundStaticMesh->SetTriangleVertexIndices(TriangleIdx, FIntVector( - TriangleIndices[TriVertIdx0 + 0], - TriangleIndices[TriVertIdx0 + 1], - TriangleIndices[TriVertIdx0 + 2] - )); - - const int32 TriWindingIndex[3] = { 0, 2, 1 }; - if (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)) - { - // Flip Z and Y coordinate for normal, but don't scale - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - const FVector Normal( - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] - ); - - FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); - - if (bReadTangents) - { - FVector TangentU, TangentV; - if (bGenerateTangents) - { - // Generate the tangents if needed - Normal.FindBestAxisVectors(TangentU, TangentV); - } - else - { - // Transfer the tangents from Houdini - TangentU.X = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; - TangentU.Y = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; - TangentU.Z = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; - - TangentU.X = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; - TangentU.Y = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; - TangentU.Z = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; - } - - FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); - FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); - } - } - } - - if (bSplitColorValid && SplitColors.IsValidIndex(TriVertIdx0 * AttribInfoColors.tupleSize + 3 * AttribInfoColors.tupleSize - 1)) - { - FLinearColor VertexLinearColor; - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - VertexLinearColor.R = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 0], 0.0f, 1.0f); - VertexLinearColor.G = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 1], 0.0f, 1.0f); - VertexLinearColor.B = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 2], 0.0f, 1.0f); - - if (bSplitAlphaValid) - { - VertexLinearColor.A = FMath::Clamp(SplitAlphas[TriVertIdx0 + ElementIdx], 0.0f, 1.0f); - } - else if (AttribInfoColors.tupleSize >= 4) - { - VertexLinearColor.A = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 3], 0.0f, 1.0f); - } - else - { - VertexLinearColor.A = 1.0f; - } - const FColor VertexColor = VertexLinearColor.ToFColor(false); - FoundStaticMesh->SetTriangleVertexColor(TriangleIdx, TriWindingIndex[ElementIdx], VertexColor); - } - } - - if (NumUVLayers > 0) - { - // Dynamic mesh supports only 1 UV layer on the mesh it self. So we set the first layer - // on the mesh itself only, and we set all layers on the AttributeSet - for (int32 TexCoordIdx = 0; TexCoordIdx < NumUVLayers; ++TexCoordIdx) - { - const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; - if (SplitUVs.IsValidIndex(TriVertIdx0 * 2 + 3 * 2 - 1)) - { - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - const int32 UVIdx = TriVertIdx0 * 2 + ElementIdx * 2; - // We need to flip V coordinate when it's coming from HAPI. - const FVector2D UV(SplitUVs[UVIdx + 0], 1.0f - SplitUVs[UVIdx + 1]); - // Set the UV on the vertex instance in the UVLayer - FoundStaticMesh->SetTriangleVertexUV(TriangleIdx, TriWindingIndex[ElementIdx], TexCoordIdx, UV); - } - } - } - } - }//); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIALS / FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // Get face indices for this split. - TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; - - // Process material overrides first - if (PartFaceMaterialOverrides.Num() > 0) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Material Overrides")); - - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - continue; - - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); - int32 CurrentFaceMaterialIdx = 0; - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; - - // Start by looking in our assignment map - auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface && !MaterialName.IsEmpty()) - { - // Only try to load a material if has a chance to be valid! - MaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); - } - else - { - // The Attribute Material and its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else - { - // If everything fails, we'll use the default material - MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Add the material to the mesh - CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); - } - } - } - - // Update the Face Material on the mesh - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); - } - } - else if (PartUniqueMaterialIds.Num() > 0) - { - // The part has houdini materials - if (bOnlyOneFaceMaterial) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Single Material")); - - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - } - else - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Materials")); - - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); - continue; - } - - UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Add the material to the mesh - int32 UnrealMatIndex = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); - - // Update the face index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); - } - } - } - else - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Default Material")); - - // No materials were found, we need to use default Houdini material. - int32 SplitFaceCount = SplitFaceIndices.Num(); - - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - } - - //// Update property attributes on the mesh - //TArray PropertyAttributes; - //if (GetGenericPropertiesAttributes( - // HGPO.GeoId, HGPO.PartId, - // AllSplitFirstValidVertexIndex[SplitGroupName], - // AllSplitFirstValidPrimIndex[SplitGroupName], - // PropertyAttributes)) - //{ - // UpdateGenericPropertiesAttributes( - // FoundStaticMesh, PropertyAttributes); - //} - - FoundStaticMesh->Optimize(); - - //// Try to find the outer package so we can dirty it up - //if (FoundStaticMesh->GetOuter()) - //{ - // FoundStaticMesh->GetOuter()->MarkPackageDirty(); - //} - //else - //{ - // FoundStaticMesh->MarkPackageDirty(); - //} - UPackage *MeshPackage = FoundStaticMesh->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - // Save the created/updated package - FEditorFileUtils::PromptForCheckoutAndSave({ MeshPackage }, false, false); - */ - } - - // Add the Proxy mesh to the output maps - if (FoundOutputObject) - { - FoundOutputObject->ProxyObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = true; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - } - - const double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() executed in %f seconds."), time_end - time_start); - - return true; -} - -bool -FHoudiniMeshTranslator::CreateNeededMaterials() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateNeededMaterials")); - - UpdatePartNeededMaterials(); - - TArray MaterialAndTexturePackages; - FHoudiniMaterialTranslator::CreateHoudiniMaterials( - HGPO.AssetId, PackageParams, - PartUniqueMaterialIds, PartUniqueMaterialInfos, - InputAssignmentMaterials, OutputAssignmentMaterials, - MaterialAndTexturePackages, false, bTreatExistingMaterialsAsUpToDate); - - /* - // Save the created packages if needed - // DPT: deactivated, only dirty for now, as we'll save them when saving the world. - if (MaterialAndTexturePackages.Num() > 0) - FEditorFileUtils::PromptForCheckoutAndSave(MaterialAndTexturePackages, true, false); - */ - - if (bMaterialOverrideNeedsCreateInstance && PartFaceMaterialOverrides.Num() > 0) - { - // Map containing unique face materials override attribute - // and their first valid prim index - // We create only one material instance per attribute - TMap UniqueFaceMaterialOverrides; - for (int FaceIdx = 0; FaceIdx < PartFaceMaterialOverrides.Num(); FaceIdx++) - { - FString MatOverrideAttr = PartFaceMaterialOverrides[FaceIdx]; - if (UniqueFaceMaterialOverrides.Contains(MatOverrideAttr)) - continue; - - // Add the material override and face index to the map - UniqueFaceMaterialOverrides.Add(MatOverrideAttr, FaceIdx); - } - - FHoudiniMaterialTranslator::CreateMaterialInstances( - HGPO, PackageParams, - UniqueFaceMaterialOverrides, MaterialAndTexturePackages, - InputAssignmentMaterials, OutputAssignmentMaterials, - false); - } - - return true; -} - -FString -FHoudiniMeshTranslator::GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType) -{ - FString MeshIdentifier = TEXT(""); - switch (InSplitType) - { - case EHoudiniSplitType::Normal: - case EHoudiniSplitType::LOD: - case EHoudiniSplitType::InvisibleUCXCollider: - case EHoudiniSplitType::InvisibleSimpleCollider: - // LODs and Invisible simple colliders use the main mesh - MeshIdentifier = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - break; - - case EHoudiniSplitType::InvisibleComplexCollider: - case EHoudiniSplitType::RenderedComplexCollider: - case EHoudiniSplitType::RenderedUCXCollider: - case EHoudiniSplitType::RenderedSimpleCollider: - // Rendered colliders or invisible complex colliders have their own static mesh - MeshIdentifier = InSplitName; - break; - - default: - break; - } - - return MeshIdentifier; -} - -UStaticMesh* -FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - // See if we already have an input object for that output identifier - FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); - UStaticMesh* FoundStaticMesh = nullptr; - if (FoundOutputObjectPtr) - { - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - FoundStaticMesh = nullptr; - } - - if (!FoundStaticMesh) - { - // No input object matching this identifier, see if we have created an output object that matches - FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); - if (!FoundOutputObjectPtr) - return nullptr; - - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - return nullptr; - } - - /* - if (FoundStaticMesh) - { - UObject* OuterMost = FoundStaticMesh->GetOutermostObject(); - if (OuterMost->IsA()) - { - // The Outermost for this static mesh is a level - // This is likely a SM created by V1, and we should not reuse it. - // This will force the plugin to recreate a "proper" SM in the temp folder. - FoundStaticMesh->MarkPendingKill(); - FoundStaticMesh = nullptr; - } - } - */ - - return FoundStaticMesh; -} - -UHoudiniStaticMesh* -FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - // See if we already have an input object for that output identifier - FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); - UHoudiniStaticMesh* FoundStaticMesh = nullptr; - if (FoundOutputObjectPtr) - { - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - FoundStaticMesh = nullptr; - } - - if (!FoundStaticMesh) - { - // No input object matching this identifier, see if we have created an output object that matches - FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); - if (!FoundOutputObjectPtr) - return nullptr; - - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - return nullptr; - } - - return FoundStaticMesh; -} - -EHoudiniSplitType -FHoudiniMeshTranslator::GetSplitTypeFromSplitName(const FString& InSplitName) -{ - const FString MainGroup = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - if (InSplitName.StartsWith(MainGroup, ESearchCase::IgnoreCase)) - return EHoudiniSplitType::Normal; - - const FString LODGroupPrefix = HAPI_UNREAL_GROUP_LOD_PREFIX; - if (InSplitName.StartsWith(LODGroupPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::LOD; - } - - const FString RenderedCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; - if (InSplitName.StartsWith(RenderedCollisionPrefix, ESearchCase::IgnoreCase)) - { - // Rendered colliders - // See if it is a simple/ucx/complex - const FString RenderedUCXCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX; - const FString RenderedSimpleCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(RenderedUCXCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::RenderedUCXCollider; - } - else if (InSplitName.StartsWith(RenderedSimpleCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::RenderedSimpleCollider; - } - else - { - return EHoudiniSplitType::RenderedComplexCollider; - } - } - - const FString InvisibleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(InvisibleCollisionPrefix, ESearchCase::IgnoreCase)) - { - // Invisible colliders - // See if it is a simple/ucx/complex - const FString InvisibleUCXCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX; - const FString InvisibleSimpleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(InvisibleUCXCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::InvisibleUCXCollider; - } - else if (InSplitName.StartsWith(InvisibleSimpleCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::InvisibleSimpleCollider; - } - else - { - return EHoudiniSplitType::InvisibleComplexCollider; - } - } - - // ? - return EHoudiniSplitType::Invalid; - //return EHoudiniSplitType::Normal; -} - -bool -FHoudiniMeshTranslator::AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) -{ - // Get the vertex indices for the split group - TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; - - // We're only interested in unique vertices - TArray UniqueVertexIndexes; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - UniqueVertexIndexes.AddUnique(Index); - } - - // Extract the collision geo's vertices - TArray< FVector > VertexArray; - VertexArray.SetNum(UniqueVertexIndexes.Num()); - for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) - { - int32 VertexIndex = UniqueVertexIndexes[Idx]; - if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) - continue; - - VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - -#if WITH_EDITOR - // Do we want to create multiple convex hulls? - bool bDoMultiHullDecomp = false; - if (SplitGroupName.Contains(TEXT("ucx_multi"), ESearchCase::IgnoreCase)) - bDoMultiHullDecomp = true; - - uint32 HullCount = 8; - int32 MaxHullVerts = 16; - if (bDoMultiHullDecomp) - { - // TODO: - // Look for extra attributes for the decomposition parameters? (HullCount/MaxHullVerts) - } - - if (bDoMultiHullDecomp && (VertexArray.Num() >= 3 || UniqueVertexIndexes.Num() >= 3)) - { - // creating multiple convex hull collision - // ... this might take a while - - // We're only interested in the valid indices! - TArray Indices; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - Indices.Add(Index); - } - - // But we need all the positions as vertex - TArray< FVector > Vertices; - Vertices.SetNum(PartPositions.Num() / 3); - - for (int32 Idx = 0; Idx < Vertices.Num(); Idx++) - { - Vertices[Idx].X = PartPositions[Idx * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[Idx].Y = PartPositions[Idx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[Idx].Z = PartPositions[Idx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - // We are using Unreal's DecomposeMeshToHulls() - // We need a BodySetup so create a fake/transient one - UBodySetup* BodySetup = NewObject(); - - // Run actual util to do the work (if we have some valid input) - DecomposeMeshToHulls(BodySetup, Vertices, Indices, HullCount, MaxHullVerts); - - // If we succeed, return here - // If not, keep going and we'll try to do a single hull decomposition - if (BodySetup->AggGeom.ConvexElems.Num() > 0) - { - // Copy the convex elem to our aggregate - for (int32 n = 0; n < BodySetup->AggGeom.ConvexElems.Num(); n++) - AggCollisions.ConvexElems.Add(BodySetup->AggGeom.ConvexElems[n]); - - return true; - } - } -#endif - - // Creating a single Convex collision - FKConvexElem ConvexCollision; - ConvexCollision.VertexData = VertexArray; - ConvexCollision.UpdateElemBox(); - - AggCollisions.ConvexElems.Add(ConvexCollision); - - return true; -} - -bool -FHoudiniMeshTranslator::AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) -{ - // Get the vertex indices for the split group - TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; - - // We're only interested in unique vertices - TArray UniqueVertexIndexes; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - UniqueVertexIndexes.AddUnique(Index); - } - - // Extract the collision geo's vertices - TArray< FVector > VertexArray; - VertexArray.SetNum(UniqueVertexIndexes.Num()); - for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) - { - int32 VertexIndex = UniqueVertexIndexes[Idx]; - if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) - continue; - - VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - int32 NewColliders = 0; - if (SplitGroupName.Contains("Box")) - { - NewColliders = FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(VertexArray, AggCollisions); - } - else if (SplitGroupName.Contains("Sphere")) - { - NewColliders = FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(VertexArray, AggCollisions); - } - else if (SplitGroupName.Contains("Capsule")) - { - NewColliders = FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(VertexArray, AggCollisions); - } - else - { - // We need to see what type of collision the user wants - // by default, a kdop26 will be created - uint32 NumDirections = 26; - const FVector* Directions = KDopDir26; - if (SplitGroupName.Contains("kdop10X")) - { - NumDirections = 10; - Directions = KDopDir10X; - } - else if (SplitGroupName.Contains("kdop10Y")) - { - NumDirections = 10; - Directions = KDopDir10Y; - } - else if (SplitGroupName.Contains("kdop10Z")) - { - NumDirections = 10; - Directions = KDopDir10Z; - } - else if (SplitGroupName.Contains("kdop18")) - { - NumDirections = 18; - Directions = KDopDir18; - } - - // Converting the directions to a TArray - TArray DirArray; - DirArray.SetNum(NumDirections); - for (uint32 DirectionIndex = 0; DirectionIndex < NumDirections; DirectionIndex++) - { - DirArray[DirectionIndex] = Directions[DirectionIndex]; - } - - NewColliders = FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(VertexArray, DirArray, AggCollisions); - } - - return (NewColliders > 0); -} - -int32 -FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData) -{ - return FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, OutVertexData); -} - -/* -int32 -FHoudiniMeshTranslator::GetSplitNormals( - const TArray& InSplitVertexList, TArray& OutNormals) -{ - // Extract the normals - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutNormals.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; - OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; - OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; - - OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; - OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; - OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; - - OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; - OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; - OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; - } - - return WedgeCount; -} - -int32 -FHoudiniMeshTranslator::GetSplitUVs( - const TArray& InSplitVertexList, TArray& OutUVs) -{ - // Extract the normals - UpdatePartUVSetsIfNeeded(); - - // Get the normals for this split - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutNormals.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; - OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; - OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; - - OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; - OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; - OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; - - OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; - OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; - OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; - } - - return WedgeCount; -} - - -int32 -FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutData, - const float& ScaleFactor) -{ - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutData.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; - OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; - - OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; - OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; - - OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; - OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; - } - - return WedgeCount; -} - -int32 -FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutData, - const float& ScaleFactor) -{ - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutData.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; - OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; - - OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; - OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; - - OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; - OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; - } - - return WedgeCount; -} -*/ - - -template -int32 FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::TransferPartAttributesToSplit")); - - if (!InAttribInfo.exists || InAttribInfo.tupleSize <= 0) - return 0; - - if (InData.Num() <= 0) - return 0; - - int32 ValidWedgeCount = 0; - - // Future optimization - see if we can do direct vertex transfer. - int32 WedgeCount = InVertexList.Num(); - int32 LastValidWedgeIdx = 0; - if (InAttribInfo.owner == HAPI_ATTROWNER_POINT) - { - // Point attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - int32 VertexIdx = InVertexList[WedgeIdx]; - if (VertexIdx < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[VertexIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_VERTEX) - { - // Vertex attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[WedgeIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_PRIM) - { - // Primitive attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 PrimIdx = WedgeIdx / 3; - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[PrimIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_DETAIL) - { - // Detail attribute transfer - // We have one value to copy for all output split vertices - // if the attribute is a single value (not a tuple) - // then we can simply use the array init function instead of looping - if (InAttribInfo.tupleSize == 1) - { - OutVertexData.Init(InData[0], WedgeCount); - } - else - { - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - } - else - { - // Invalid attribute owner, shouldn't happen - check(false); - } - - OutVertexData.SetNumZeroed(ValidWedgeCount * InAttribInfo.tupleSize); - - return ValidWedgeCount; -} - -float -FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) -{ - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = -1.0f; - - // Start by looking at the lod_screensize primitive attribute - bool bAttribValid = false; - UpdatePartLODScreensizeIfNeeded(); - - if (PartLODScreensize.Num() > 0) - { - // use the "lod_screensize" primitive attribute - int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - if (PartLODScreensize.IsValidIndex(FirstValidPrimIndex)) - screensize = PartLODScreensize[FirstValidPrimIndex]; - } - - if (screensize >= 0.0f) - { - // We couldn't find the primitive attribute, look for a "lodX_screensize" detail attribute - FString LODAttributeName = SplitGroupName + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX; - - TArray LODScreenSizes; - HAPI_AttributeInfo AttribInfoScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); - - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*LODAttributeName), - AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_DETAIL); - - if (AttribInfoScreenSize.exists && LODScreenSizes.Num() > 0) - { - screensize = LODScreenSizes[0]; - } - } - - if (screensize >= 0.0f) - { - // finally, look for a potential uproperty style attribute - // aka, "unreal_uproperty_screensize" - TArray LODScreenSizes; - HAPI_AttributeInfo AttribInfoScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); - - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, "unreal_uproperty_screensize", - AttribInfoScreenSize, LODScreenSizes); - - if (AttribInfoScreenSize.exists) - { - if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_DETAIL && LODScreenSizes.Num() > 0) - { - screensize = LODScreenSizes[0]; - } - else if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_PRIM) - { - int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - if (LODScreenSizes.IsValidIndex(FirstValidPrimIndex)) - screensize = LODScreenSizes[FirstValidPrimIndex]; - } - } - } - - // Make sure the screensize is in percent, so if its above 1, divide by 100 - if (screensize > 1.0f) - screensize /= 100.0f; - - return screensize; -} - -int32 -FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - // Calculate bounding Box. - FVector Center, Extents; - FVector unitVec = FVector::OneVector;// bs->BuildScale3D; - CalcBoundingBox(InPositionArray, Center, Extents, unitVec); - - FKBoxElem BoxElem; - BoxElem.Center = Center; - BoxElem.X = Extents.X * 2.0f; - BoxElem.Y = Extents.Y * 2.0f; - BoxElem.Z = Extents.Z * 2.0f; - OutAggregateCollisions.BoxElems.Add(BoxElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FBox Box(ForceInit); - for (const FVector& CurPos : PositionArray) - { - Box += CurPos; - } - Box.GetCenterAndExtents(Center, Extents); -} - -int32 -FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FSphere bSphere, bSphere2, bestSphere; - FVector unitVec = FVector::OneVector; - - // Calculate bounding sphere. - CalcBoundingSphere(InPositionArray, bSphere, unitVec); - CalcBoundingSphere2(InPositionArray, bSphere2, unitVec); - - if (bSphere.W < bSphere2.W) - bestSphere = bSphere; - else - bestSphere = bSphere2; - - // Don't use if radius is zero. - if (bestSphere.W <= 0.f) - { - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Sphere collider.")); - return 0; - } - - FKSphereElem SphereElem; - SphereElem.Center = bestSphere.Center; - SphereElem.Radius = bestSphere.W; - OutAggregateCollisions.SphereElems.Add(SphereElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - if (PositionArray.Num() == 0) - return; - - FBox Box; - FVector MinIx[3]; - FVector MaxIx[3]; - - bool bFirstVertex = true; - for (const FVector& CurPosition : PositionArray) - { - FVector p = CurPosition * LimitVec; - if (bFirstVertex) - { - // First, find AABB, remembering furthest points in each dir. - Box.Min = p; - Box.Max = Box.Min; - - MinIx[0] = CurPosition; - MinIx[1] = CurPosition; - MinIx[2] = CurPosition; - - MaxIx[0] = CurPosition; - MaxIx[1] = CurPosition; - MaxIx[2] = CurPosition; - bFirstVertex = false; - continue; - } - - // X // - if (p.X < Box.Min.X) - { - Box.Min.X = p.X; - MinIx[0] = CurPosition; - } - else if (p.X > Box.Max.X) - { - Box.Max.X = p.X; - MaxIx[0] = CurPosition; - } - - // Y // - if (p.Y < Box.Min.Y) - { - Box.Min.Y = p.Y; - MinIx[1] = CurPosition; - } - else if (p.Y > Box.Max.Y) - { - Box.Max.Y = p.Y; - MaxIx[1] = CurPosition; - } - - // Z // - if (p.Z < Box.Min.Z) - { - Box.Min.Z = p.Z; - MinIx[2] = CurPosition; - } - else if (p.Z > Box.Max.Z) - { - Box.Max.Z = p.Z; - MaxIx[2] = CurPosition; - } - } - - const FVector Extremes[3] = { (MaxIx[0] - MinIx[0]) * LimitVec, - (MaxIx[1] - MinIx[1]) * LimitVec, - (MaxIx[2] - MinIx[2]) * LimitVec }; - - // Now find extreme points furthest apart, and initial center and radius of sphere. - float d2 = 0.f; - for (int32 i = 0; i < 3; i++) - { - const float tmpd2 = Extremes[i].SizeSquared(); - if (tmpd2 > d2) - { - d2 = tmpd2; - sphere.Center = (MinIx[i] + (0.5f * Extremes[i])) * LimitVec; - sphere.W = 0.f; - } - } - - const FVector Extents = FVector(Extremes[0].X, Extremes[1].Y, Extremes[2].Z); - - // radius and radius squared - float r = 0.5f * Extents.GetMax(); - float r2 = FMath::Square(r); - - // Now check each point lies within this sphere. If not - expand it a bit. - for (const FVector& curPos : PositionArray) - { - const FVector cToP = (curPos * LimitVec) - sphere.Center; - - const float pr2 = cToP.SizeSquared(); - - // If this point is outside our current bounding sphere's radius - if (pr2 > r2) - { - // ..expand radius just enough to include this point. - const float pr = FMath::Sqrt(pr2); - r = 0.5f * (r + pr); - r2 = FMath::Square(r); - - sphere.Center += ((pr - r) / pr * cToP); - } - } - - sphere.W = r; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FVector Center, Extents; - CalcBoundingBox(PositionArray, Center, Extents, LimitVec); - - sphere.Center = Center; - sphere.W = 0.0f; - - for (const FVector& curPos : PositionArray) - { - float Dist = FVector::DistSquared(curPos * LimitVec, sphere.Center); - if (Dist > sphere.W) - sphere.W = Dist; - } - sphere.W = FMath::Sqrt(sphere.W); -} - -int32 -FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FSphere sphere; - float length; - FRotator rotation; - FVector unitVec = FVector::OneVector; - - // Calculate bounding sphyl. - CalcBoundingSphyl(InPositionArray, sphere, length, rotation, unitVec); - - // Dont use if radius is zero. - if (sphere.W <= 0.f) - { - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Capsule collider.")); - return 0; - } - - // If height is zero, then a sphere would be better (should we just create one instead?) - if (length <= 0.f) - { - length = SMALL_NUMBER; - } - - FKSphylElem SphylElem; - SphylElem.Center = sphere.Center; - SphylElem.Rotation = rotation; - SphylElem.Radius = sphere.W; - SphylElem.Length = length; - OutAggregateCollisions.SphylElems.Add(SphylElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - if (PositionArray.Num() == 0) - return; - - FVector Center, Extents; - CalcBoundingBox(PositionArray, Center, Extents, LimitVec); - - // @todo sphere.Center could perhaps be adjusted to best fit if model is non-symmetric on it's longest axis - sphere.Center = Center; - - // Work out best axis aligned orientation (longest side) - float Extent = Extents.GetMax(); - if (Extent == Extents.X) - { - rotation = FRotator(90.f, 0.f, 0.f); - Extents.X = 0.0f; - } - else if (Extent == Extents.Y) - { - rotation = FRotator(0.f, 0.f, 90.f); - Extents.Y = 0.0f; - } - else - { - rotation = FRotator(0.f, 0.f, 0.f); - Extents.Z = 0.0f; - } - - // Cleared the largest axis above, remaining determines the radius - float r = Extents.GetMax(); - float r2 = FMath::Square(r); - - // Now check each point lies within this the radius. If not - expand it a bit. - for (const FVector& CurPos : PositionArray) - { - FVector cToP = (CurPos * LimitVec) - sphere.Center; - cToP = rotation.UnrotateVector(cToP); - - const float pr2 = cToP.SizeSquared2D(); // Ignore Z here... - - // If this point is outside our current bounding sphere's radius - if (pr2 > r2) - { - // ..expand radius just enough to include this point. - const float pr = FMath::Sqrt(pr2); - r = 0.5f * (r + pr); - r2 = FMath::Square(r); - } - } - - // The length is the longest side minus the radius. - float hl = FMath::Max(0.0f, Extent - r); - - // Now check each point lies within the length. If not - expand it a bit. - for (const FVector& CurPos : PositionArray) - { - FVector cToP = (CurPos * LimitVec) - sphere.Center; - cToP = rotation.UnrotateVector(cToP); - - // If this point is outside our current bounding sphyl's length - if (FMath::Abs(cToP.Z) > hl) - { - const bool bFlip = (cToP.Z < 0.f ? true : false); - const FVector cOrigin(0.f, 0.f, (bFlip ? -hl : hl)); - - const float pr2 = (cOrigin - cToP).SizeSquared(); - - // If this point is outside our current bounding sphyl's radius - if (pr2 > r2) - { - FVector cPoint; - FMath::SphereDistToLine(cOrigin, r, cToP, (bFlip ? FVector(0.f, 0.f, 1.f) : FVector(0.f, 0.f, -1.f)), cPoint); - - // Don't accept zero as a valid diff when we know it's outside the sphere (saves needless retest on further iterations of like points) - hl += FMath::Max(FMath::Abs(cToP.Z - cPoint.Z), 1.e-6f); - } - } - } - - sphere.W = r; - length = hl * 2.0f; -} - -int32 -FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - const float my_flt_max = 3.402823466e+38F; - - // Do k- specific stuff. - int32 kCount = Dirs.Num(); - - TArray maxDist; - maxDist.Init(-my_flt_max, kCount); - /* - for (int32 i = 0; i < kCount; i++) - maxDist.Add(my_flt_max); - */ - - // Construct temporary UModel for kdop creation. We keep no refs to it, so it can be GC'd. - auto TempModel = NewObject(); - TempModel->Initialize(nullptr, 1); - - // For each vertex, project along each kdop direction, to find the max in that direction. - for (int32 i = 0; i < InPositionArray.Num(); i++) - { - for (int32 j = 0; j < kCount; j++) - { - float dist = InPositionArray[i] | Dirs[j]; - maxDist[j] = FMath::Max(dist, maxDist[j]); - } - } - - // Inflate kdop to ensure it is no degenerate - const float MinSize = 0.1f; - for (int32 i = 0; i < kCount; i++) - { - maxDist[i] += MinSize; - } - - // Now we have the planes of the kdop, we work out the face polygons. - TArray planes; - for (int32 i = 0; i < kCount; i++) - planes.Add(FPlane(Dirs[i], maxDist[i])); - - for (int32 i = 0; i < planes.Num(); i++) - { - FPoly* Polygon = new(TempModel->Polys->Element) FPoly(); - FVector Base, AxisX, AxisY; - - Polygon->Init(); - Polygon->Normal = planes[i]; - Polygon->Normal.FindBestAxisVectors(AxisX, AxisY); - - Base = planes[i] * planes[i].W; - - new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); - - for (int32 j = 0; j < planes.Num(); j++) - { - if (i != j) - { - if (!Polygon->Split(-FVector(planes[j]), planes[j] * planes[j].W)) - { - Polygon->Vertices.Empty(); - break; - } - } - } - - if (Polygon->Vertices.Num() < 3) - { - // If poly resulted in no verts, remove from array - TempModel->Polys->Element.RemoveAt(TempModel->Polys->Element.Num() - 1); - } - else - { - // Other stuff... - Polygon->iLink = i; - Polygon->CalcNormal(1); - } - } - - if (TempModel->Polys->Element.Num() < 4) - { - TempModel = NULL; - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple KDOP collider.")); - return 0; - } - - // Build bounding box. - TempModel->BuildBound(); - - // Build BSP for the brush. - FBSPOps::bspBuild(TempModel, FBSPOps::BSP_Good, 15, 70, 1, 0); - FBSPOps::bspRefresh(TempModel, 1); - FBSPOps::bspBuildBounds(TempModel); - - // Now, create a temporary BodySetup to build the colliders - UBodySetup* TempBS = NewObject(); - TempBS->CreateFromModel(TempModel, false); - - // Copy the convex elements back to our aggregate - int32 NewConvexElems = 0; - if (TempBS && TempBS->AggGeom.ConvexElems.Num() > 0) - { - for (const auto& CurConvexElem : TempBS->AggGeom.ConvexElems) - { - OutAggregateCollisions.ConvexElems.Add(CurConvexElem); - NewConvexElems++; - } - } - - return NewConvexElems; -} - - -bool -FHoudiniMeshTranslator::GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, - TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive property attributes for the given prim - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); - - // .. then finally, point uprop attributes for the given vert - // TODO: !! get the correct Index here? - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidVertexIndex); - - return FoundCount > 0; -} - -bool -FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (auto CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current Property Attribute - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); - } - - return (NumSuccess > 0); -} - - -void -FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO) -{ - PackageParams = InPackageParams; - - if (bUpdateHGPO) - { - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.ObjectId; - PackageParams.PartId = HGPO.ObjectId; - } -} - -bool -FHoudiniMeshTranslator::RemoveAndDestroyComponent(UObject* InComponent) -{ - if (!InComponent || InComponent->IsPendingKill()) - return false; - - USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - return true; - } - - return false; -} - -UMeshComponent* -FHoudiniMeshTranslator::CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf &InComponentType) -{ - // Create a new SMC as we couldn't find an existing one - USceneComponent* OuterSceneComponent = Cast(InOuterComponent); - UObject * Outer = nullptr; - if (OuterSceneComponent && !OuterSceneComponent->IsPendingKill()) - Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); - - UMeshComponent *MeshComponent = NewObject(Outer, InComponentType, NAME_None, RF_Transactional); - - // Initialize it - MeshComponent->SetVisibility(true); - //MeshComponent->SetMobility(Mobility); - - // TODO: - // Property propagation: set the new SMC's properties to the HAC's current settings - //CopyComponentPropertiesTo(MeshComponent); - - // Change the creation method so the component is listed in the details panels - MeshComponent->CreationMethod = EComponentCreationMethod::Instance; - - // Attach created static mesh component to our Houdini component. - MeshComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - MeshComponent->OnComponentCreated(); - MeshComponent->RegisterComponent(); - - return MeshComponent; -} - -bool -FHoudiniMeshTranslator::PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh) -{ - UStaticMesh *Mesh = Cast(InMesh); - if (Mesh) - { - InComponent->SetStaticMesh(Mesh); - - return true; - } - - return false; -} - -bool -FHoudiniMeshTranslator::PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh) -{ - UHoudiniStaticMesh *Mesh = Cast(InMesh); - if (Mesh) - { - InComponent->SetMesh(Mesh); - - return true; - } - - return false; -} - -UMeshComponent* -FHoudiniMeshTranslator::CreateOrUpdateMeshComponent( - const UHoudiniOutput *InOutput, - UObject *InOuterComponent, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TSubclassOf& InComponentType, - FHoudiniOutputObject& OutputObject, - FHoudiniGeoPartObject const *& OutFoundHGPO, - bool& bCreated) -{ - bCreated = false; - OutFoundHGPO = nullptr; - - // Find the HGPO that matches this mesh - for (auto& curHGPO : InOutput->HoudiniGeoPartObjects) - { - if (curHGPO.ObjectId != InOutputIdentifier.ObjectId - || curHGPO.GeoId != InOutputIdentifier.GeoId - || curHGPO.PartId != InOutputIdentifier.PartId) - { - continue; - } - - if (InOutputIdentifier.SplitIdentifier.Equals(HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION) - || curHGPO.SplitGroups.Contains(InOutputIdentifier.SplitIdentifier)) - { - OutFoundHGPO = &curHGPO; - } - } - - // No need to create a component for instanced meshes! - if (OutFoundHGPO && OutFoundHGPO->bIsInstanced) - return nullptr; - - bool bIsProxyComponent = InComponentType == UHoudiniStaticMeshComponent::StaticClass(); - - // See if we already have a component for that mesh - UMeshComponent* MeshComponent = nullptr; - if (bIsProxyComponent) - MeshComponent = Cast(OutputObject.ProxyComponent); - else - MeshComponent = Cast(OutputObject.OutputComponent); - - // If there is an existing component, but it is pending kill, then it was likely - // deleted by some other process, such as by the user in the editor, so don't use it - if (!MeshComponent || MeshComponent->IsPendingKill() || !MeshComponent->IsA(InComponentType)) - { - // If the component is not of type InComponentType, or the found component is pending kill, destroy - // the existing component (a new one is then created below) - RemoveAndDestroyComponent(MeshComponent); - MeshComponent = nullptr; - } - - if (!MeshComponent) - { - // Create a new SMC/HSMC as we couldn't find an existing one - MeshComponent = CreateMeshComponent(InOuterComponent, InComponentType); - - if (MeshComponent) - { - // Add to the output object - if (bIsProxyComponent) - OutputObject.ProxyComponent = MeshComponent; - else - OutputObject.OutputComponent = MeshComponent; - - bCreated = true; - } - } - - return MeshComponent; -} - -bool -FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, - TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors) -{ - if (!Socket || Socket->IsPendingKill() || !StaticMeshComponent || StaticMeshComponent->IsPendingKill()) - return false; - - // The actor to assign is stored is the socket's tag - FString ActorString = Socket->Tag; - if (ActorString.IsEmpty()) - return false; - - // The actor to assign are listed after a | - TArray ActorStringArray; - ActorString.ParseIntoArray(ActorStringArray, TEXT("|"), false); - - // The "real" Tag is the first - if (ActorStringArray.Num() > 0) - Socket->Tag = ActorStringArray[0]; - - // We just add a Tag, no Actor - if (ActorStringArray.Num() == 1) - return false; - - // Extract the parsed actor string to split it further - ActorString = ActorStringArray[1]; - - // Converting the string to a string array using delimiters - const TCHAR* Delims[] = { TEXT(","), TEXT(";") }; - ActorString.ParseIntoArray(ActorStringArray, Delims, 2); - - // And try to find the corresponding HoudiniAssetActor in the editor world - // to avoid finding "deleted" assets with the same name - //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); -#if WITH_EDITOR - UWorld* EditorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; - if (!EditorWorld || EditorWorld->IsPendingKill()) - return false; - - // Remove the previous created actors which were attached to this socket - { - for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - continue; - } - - if (CurActor->GetAttachParentSocketName() == Socket->SocketName) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - CurActor->Destroy(); - } - } - } - - // Detach the previous in level actors which was attached to this socket - { - for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniAttachedSocketActors.RemoveAt(Idx); - continue; - } - - if (CurActor->GetAttachParentSocketName() == Socket->SocketName) - { - CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - HoudiniAttachedSocketActors.RemoveAt(Idx); - } - } - } - - auto CreateDefaultActor = [EditorWorld, StaticMeshComponent, Socket, HoudiniCreatedSocketActors]() - { - AActor * CreatedDefaultActor = nullptr; - - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) - { - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( - EditorWorld->GetCurrentLevel(), DefaultReferenceSM, false, RF_Transactional, nullptr); - - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load default mesh.")); - } - else - { - - // Set the default mesh actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : NewActors[0]->GetComponents()) - { - UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) - CurSMC->SetMobility(OutputSMCMobility); - } - - // Set the default mesh actor hidden in game. - NewActors[0]->SetActorHiddenInGame(true); - - Socket->AttachActor(NewActors[0], StaticMeshComponent); - CreatedDefaultActor = NewActors[0]; - //HoudiniCreatedSocketActors.Add(NewActors[0]); - } - } - else - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load default mesh.")); - } - - return CreatedDefaultActor; - }; - - bool bUseDefaultActor = true; - // Get from the Houdini runtime setting if use default object when the reference is invalid - // true by default if fail to access HoudiniRuntimeSettings - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bUseDefaultActor = HoudiniRuntimeSettings->bShowDefaultMesh; - } - - if (ActorStringArray.Num() <= 0) - { - if (!bUseDefaultActor) - return true; - - HOUDINI_LOG_WARNING( - TEXT("Output static mesh: Socket '%s' actor is not specified. Spawn a default mesh (hidden in game)."), *(Socket->GetName())); - - AActor * DefaultActor = CreateDefaultActor(); - if (DefaultActor && !DefaultActor->IsPendingKill()) - HoudiniCreatedSocketActors.Add(DefaultActor); - - return true; - } - - // try to find the actor in level first - for (TActorIterator ActorItr(EditorWorld); ActorItr; ++ActorItr) - { - // Same as with the Object Iterator, access the subclass instance with the * or -> operators. - AActor *Actor = *ActorItr; - if (!Actor || Actor->IsPendingKillOrUnreachable()) - continue; - - for (int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++) - { - if (Actor->GetName() != ActorStringArray[StringIdx] - && Actor->GetActorLabel() != ActorStringArray[StringIdx]) - continue; - - // Set the actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : Actor->GetComponents()) - { - UStaticMeshComponent * SMC = Cast(CurComp); - if (SMC && !SMC->IsPendingKill()) - SMC->SetMobility(OutputSMCMobility); - } - - Socket->AttachActor(Actor, StaticMeshComponent); - HoudiniAttachedSocketActors.Add(Actor); - - // Remove the string if the actor is found in the editor level - ActorStringArray.RemoveAt(StringIdx); - break; - } - } - - bool bSuccess = true; - // If some of the actors are not found in the level, try to find them in the content browser. Spawn one if existed - for (int32 Idx = ActorStringArray.Num() - 1; Idx>= 0; --Idx) - { - UObject * Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *ActorStringArray[Idx]); - if (!Obj || Obj->IsPendingKill()) - { - bSuccess = false; - continue; - } - - // Spawn a new actor with the found object - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( - EditorWorld->GetCurrentLevel(), Obj, false, RF_Transactional, nullptr); - - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) - { - bSuccess = false; - continue; - } - - // Set the new actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : NewActors[0]->GetComponents()) - { - UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) - CurSMC->SetMobility(OutputSMCMobility); - } - - Socket->AttachActor(NewActors[0], StaticMeshComponent); - HoudiniCreatedSocketActors.Add(NewActors[0]); - - ActorStringArray.RemoveAt(Idx); - } - - // Failed to find actors in both level and content browser - // Spawn default actors if enabled - if (bUseDefaultActor) - { - for (int32 Idx = ActorStringArray.Num() - 1; Idx >= 0; --Idx) - { - HOUDINI_LOG_WARNING( - TEXT("Output static mesh: Failed to attach '%s' to socket '%s', spawn a default mesh (hidden in game)."), *(ActorStringArray[Idx]), *(Socket->GetName())); - - // If failed to load this object, spawn a default mesh - AActor * CurDefaultActor = CreateDefaultActor(); - if (CurDefaultActor && !CurDefaultActor->IsPendingKill()) - HoudiniCreatedSocketActors.Add(CurDefaultActor); - } - } - - if (ActorStringArray.Num() > 0) - return false; -#endif - - return bSuccess; -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniMeshTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniOutput.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniMaterialTranslator.h" +#include "HoudiniAssetActor.h" + +#include "HoudiniStaticMesh.h" +#include "HoudiniStaticMeshComponent.h" +#include "Engine/StaticMeshSocket.h" + +#include "PhysicsEngine/BodySetup.h" +#include "Engine/StaticMesh.h" +#include "PackageTools.h" +#include "RawMesh.h" +#include "Materials/MaterialInterface.h" +#include "Materials/Material.h" +#include "MeshDescription.h" +#include "StaticMeshAttributes.h" +#include "MeshDescriptionOperations.h" + +#include "BSPOps.h" +#include "Model.h" +#include "Engine/Polys.h" +#include "AssetRegistryModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "AI/Navigation/NavCollisionBase.h" +#include "ObjectTools.h" + +// #include "Async/ParallelFor.h" + +#include "ProfilingDebugging/CpuProfilerTrace.h" + +#include "EditorSupportDelegates.h" + +#if WITH_EDITOR + #include "UnrealEd/Private/ConvexDecompTool.h" + #include "Editor/UnrealEd/Private/GeomFitUtils.h" + #include "LevelEditorViewport.h" + #include "FileHelpers.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// +bool +FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + UHoudiniOutput* InOutput, + const FHoudiniPackageParams& InPackageParams, + EHoudiniStaticMeshMethod InStaticMeshMethod, + UObject* InOuterComponent, + bool bInTreatExistingMaterialsAsUpToDate, + bool bInDestroyProxies) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InPackageParams.OuterPackage || InPackageParams.OuterPackage->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + TMap NewOutputObjects; + TMap OldOutputObjects = InOutput->GetOutputObjects(); + TMap& AssignementMaterials = InOutput->GetAssignementMaterials(); + TMap& ReplacementMaterials = InOutput->GetReplacementMaterials(); + + bool InForceRebuild = false; + if (InOutput->HasAnyCurrentProxy() && InStaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh) + { + // Make sure we're not preventing refinement + InForceRebuild = true; + } + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) + { + // Not a mesh, skip + if (CurHGPO.Type != EHoudiniPartType::Mesh) + continue; + + CreateStaticMeshFromHoudiniGeoPartObject( + CurHGPO, + InPackageParams, + OldOutputObjects, + NewOutputObjects, + AssignementMaterials, + ReplacementMaterials, + InForceRebuild, + InStaticMeshMethod, + bInTreatExistingMaterialsAsUpToDate); + } + + return FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + InOutput, + InOuterComponent, + NewOutputObjects, + bInDestroyProxies); +} + +bool +FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + UHoudiniOutput* InOutput, + UObject* InOuterComponent, + TMap& InNewOutputObjects, + bool bInDestroyProxies, + bool bInApplyGenericProperties) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + TMap OldOutputObjects = InOutput->GetOutputObjects(); + + // Remove Static Meshes and their components from the old map + // to avoid their deletion if new proxies were created for them + for (auto& NewOutputObj : InNewOutputObjects) + { + FHoudiniOutputObjectIdentifier OutputIdentifier = NewOutputObj.Key; + + // See if we already had that pair in the old map of static mesh + FHoudiniOutputObject* FoundOldOutputObj = OldOutputObjects.Find(NewOutputObj.Key); + if (!FoundOldOutputObj) + continue; + + UObject* NewStaticMesh = NewOutputObj.Value.OutputObject; + UObject* NewProxyMesh = NewOutputObj.Value.ProxyObject; + + UObject* OldStaticMesh = FoundOldOutputObj->OutputObject; + if (OldStaticMesh && !OldStaticMesh->IsPendingKill()) + { + // If a proxy was created for an existing static mesh, keep the existing static + // mesh (will be hidden) + if (NewProxyMesh && NewOutputObj.Value.bProxyIsCurrent) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + else if (NewStaticMesh && NewStaticMesh == OldStaticMesh) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + + UObject* OldProxyMesh = FoundOldOutputObj->ProxyObject; + if (OldProxyMesh && !OldProxyMesh->IsPendingKill()) + { + // If a new static mesh was created for a proxy, keep the proxy (will be hidden) + // ... unless we want to explicitly destroy proxies + if (NewStaticMesh && !bInDestroyProxies) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + else if (NewProxyMesh && (NewProxyMesh == OldProxyMesh)) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + } + + // The old map now only contains unused/stale Meshes/Components, delete them + for (auto& OldPair : OldOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; + FHoudiniOutputObject& OldOutputObject = OldPair.Value; + + // Remove the old component from the map + RemoveAndDestroyComponent(OldOutputObject.OutputComponent); + OldOutputObject.OutputComponent = nullptr; + // Remove the old proxy component from the map + RemoveAndDestroyComponent(OldOutputObject.ProxyComponent); + OldOutputObject.ProxyComponent = nullptr; + + if (OldOutputObject.OutputObject && !OldOutputObject.OutputObject->IsPendingKill()) + { + OldOutputObject.OutputObject->MarkPendingKill(); + } + + if (OldOutputObject.ProxyObject && !OldOutputObject.ProxyObject->IsPendingKill()) + { + OldOutputObject.ProxyObject->MarkPendingKill(); + } + } + OldOutputObjects.Empty(); + + /* + // Remove any stale components, these are components with OutputIdentifiers that are not + // in NewOutputObjects. This seems to happen mostly with the first or second cook after a + // "Rebuild Asset" + if (OutputComponents.Num() > 0 || OutputProxyComponents.Num() > 0) + { + TArray> StaleComponents; + const uint32 MaxNumStale = FMath::Max(OutputComponents.Num(), OutputProxyComponents.Num()); + StaleComponents.Reserve(MaxNumStale); + for (auto& ComponentPair : OutputComponents) + { + if (!NewOutputObjects.Contains(ComponentPair.Key) && !OldOutputObjectsReplacedByProxy.Contains(ComponentPair.Key)) + { + StaleComponents.Add(ComponentPair); + } + } + for (auto& ComponentPair : StaleComponents) + { + RemoveAndDestroyComponent(ComponentPair.Key, OutputComponents); + } + StaleComponents.Empty(MaxNumStale); + + for (auto& ComponentPair : OutputProxyComponents) + { + if (!NewOutputProxyObjects.Contains(ComponentPair.Key) && !OldOutputProxyObjectsReplacedByStaticMesh.Contains(ComponentPair.Key)) + { + StaleComponents.Add(ComponentPair); + } + } + for (auto& ComponentPair : StaleComponents) + { + RemoveAndDestroyComponent(ComponentPair.Key, OutputProxyComponents); + } + StaleComponents.Empty(); + } + */ + + // Now create/update the new static mesh components + for (auto& NewPair : InNewOutputObjects) + { + // Get the old Identifier / StaticMesh + const FHoudiniOutputObjectIdentifier& OutputIdentifier = NewPair.Key; + FHoudiniOutputObject& OutputObject = NewPair.Value; + + // Check if we should create a Proxy/SMC + if (OutputObject.bProxyIsCurrent) + { + UObject *Mesh = OutputObject.ProxyObject; + if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + { + HOUDINI_LOG_ERROR(TEXT("Proxy Mesh is invalid (wrong type or pending kill)...")); + continue; + } + + // Create or update a new proxy component + TSubclassOf ComponentType = UHoudiniStaticMeshComponent::StaticClass(); + const FHoudiniGeoPartObject *FoundHGPO = nullptr; + bool bCreated = false; + UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); + if (MeshComponent) + { + UHoudiniStaticMeshComponent *HSMC = Cast(MeshComponent); + + if (bCreated) + { + PostCreateHoudiniStaticMeshComponent(HSMC, Mesh); + } + else if (HSMC && !HSMC->IsPendingKill() && HSMC->GetMesh() != Mesh) + { + // We need to reassign the HSM to the component + UHoudiniStaticMesh* HSM = Cast(Mesh); + HSMC->SetMesh(HSM); + } + + UpdateMeshComponent( + MeshComponent, + OutputIdentifier, + FoundHGPO, + InOutput->HoudiniCreatedSocketActors, + InOutput->HoudiniAttachedSocketActors, + bInApplyGenericProperties); + + if (!bCreated) + { + // For proxy meshes: notify that the mesh has been updated + HSMC->NotifyMeshUpdated(); + HSMC->SetHoudiniIconVisible(true); + } + } + + // Now, ensure that meshes replaced by proxies are still kept but hidden + USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); + if (SceneComponent) + { + SceneComponent->SetVisibility(false); + SceneComponent->SetHiddenInGame(true); + } + + // If the proxy mesh we just created is templated, hide it in game + if (FoundHGPO->bIsTemplated) + { + MeshComponent->SetHiddenInGame(true); + } + } + else + { + // Create a new SMC if needed + UObject* Mesh = OutputObject.OutputObject; + if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + { + HOUDINI_LOG_ERROR(TEXT("Mesh is invalid (wrong type or pending kill)...")); + continue; + } + + TSubclassOf ComponentType = UStaticMeshComponent::StaticClass(); + const FHoudiniGeoPartObject *FoundHGPO = nullptr; + bool bCreated = false; + UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); + if (MeshComponent) + { + if (bCreated) + { + PostCreateStaticMeshComponent(Cast(MeshComponent), Mesh); + } + UpdateMeshComponent( + MeshComponent, + OutputIdentifier, + FoundHGPO, + InOutput->HoudiniCreatedSocketActors, + InOutput->HoudiniAttachedSocketActors, + bInApplyGenericProperties); + } + + // Now, ensure that proxies replaced by meshes are still kept but hidden + UHoudiniStaticMeshComponent *HSMC = Cast(OutputObject.ProxyComponent); + if (HSMC) + { + HSMC->SetVisibility(false); + HSMC->SetHiddenInGame(true); + HSMC->SetHoudiniIconVisible(false); + } + + // If the mesh we just created is templated, hide it in game + if (FoundHGPO->bIsTemplated) + { + MeshComponent->SetHiddenInGame(true); + } + } + } + + // Assign the new output objects to the output + InOutput->SetOutputObjects(InNewOutputObjects); + + return true; +} + +void +FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, + const FHoudiniGeoPartObject *InHGPO, TArray &HoudiniCreatedSocketActors, TArray &HoudiniAttachedSocketActors, + bool bInApplyGenericProperties) +{ + // Update collision/visibility + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(InOutputIdentifier.SplitIdentifier); + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider) + { + // Invisible complex collider should not be seen + InMeshComponent->SetVisibility(false); + InMeshComponent->SetHiddenInGame(true); + InMeshComponent->SetCollisionProfileName(FName(TEXT("InvisibleWall"))); + InMeshComponent->SetCastShadow(false); + } + else + { + // Update visiblity + bool bVisible = InHGPO ? InHGPO->bIsVisible : true; + InMeshComponent->SetVisibility(bVisible); + InMeshComponent->SetHiddenInGame(!bVisible); + } + + // TODO: + // Update navmesh? + + // Transform the component by transformation provided by HAPI. + InMeshComponent->SetRelativeTransform(InHGPO->TransformMatrix); + + // If the static mesh had sockets, we can assign the desired actor to them now + UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); + UStaticMesh * StaticMesh = nullptr; + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + StaticMesh = StaticMeshComponent->GetStaticMesh(); + + if (StaticMesh && !StaticMesh->IsPendingKill()) + { + 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; + + AddActorsToMeshSocket(StaticMesh->Sockets[nSocket], StaticMeshComponent, HoudiniCreatedSocketActors, HoudiniAttachedSocketActors); + } + + // Iterate all remaining created socket actors, destroy the ones that are not assigned to socket after re-cook + { + for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniCreatedSocketActors[Idx]; + + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + continue; + } + + bool bFoundSocket = false; + for (auto & CurSocket : StaticMesh->Sockets) + { + if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) + { + bFoundSocket = true; + break; + } + } + // cur actor's attaching socket is found, skip + if (bFoundSocket) + continue; + + // Destroy the previous created socket actor if not found + HoudiniCreatedSocketActors.RemoveAt(Idx); + CurActor->Destroy(); + } + } + + // Detach the in level actors which is not attached to any socket now + { + for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor* CurActor = HoudiniAttachedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniAttachedSocketActors.RemoveAt(Idx); + continue; + } + + bool bFoundSocket = false; + for (auto & CurSocket : StaticMesh->Sockets) + { + if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) + { + bFoundSocket = true; + break; + } + } + + if (bFoundSocket) + continue; + + // If the attached socket name is not found in current socket, detach it and remove from the array + CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + HoudiniAttachedSocketActors.RemoveAt(Idx); + } + } + + } + + if (bInApplyGenericProperties) + { + // Clear the component tags as generic properties only add them + InMeshComponent->ComponentTags.Empty(); + // Update the property attributes on the component + TArray PropertyAttributes; + if (GetGenericPropertiesAttributes( + InOutputIdentifier.GeoId, InOutputIdentifier.PartId, + InOutputIdentifier.PointIndex, InOutputIdentifier.PrimitiveIndex, + PropertyAttributes)) + { + UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); + } + } +} + +bool +FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& InOutputObjects, + TMap& OutOutputObjects, + TMap& AssignmentMaterialMap, + TMap& ReplacementMaterialMap, + const bool& InForceRebuild, + EHoudiniStaticMeshMethod InStaticMeshMethod, + bool bInTreatExistingMaterialsAsUpToDate) +{ + // If we're not forcing the rebuild + // No need to recreate something that hasn't changed + if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged) && InOutputObjects.Num() > 0) + { + // Simply reuse the existing meshes + OutOutputObjects = InOutputObjects; + return true; + } + + FHoudiniMeshTranslator CurrentTranslator; + CurrentTranslator.ForceRebuild = InForceRebuild; + CurrentTranslator.SetHoudiniGeoPartObject(InHGPO); + CurrentTranslator.SetInputObjects(InOutputObjects); + CurrentTranslator.SetOutputObjects(OutOutputObjects); + CurrentTranslator.SetInputAssignmentMaterials(AssignmentMaterialMap); + CurrentTranslator.SetReplacementMaterials(ReplacementMaterialMap); + CurrentTranslator.SetPackageParams(InPackageParams, true); + CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); + + // TODO: Fetch from settings/HAC + CurrentTranslator.DefaultMeshSmoothing = 1; + if (false) + CurrentTranslator.DefaultMeshSmoothing = 0; + + // TODO: mechanism to determine when to use dynamic mesh for fast updates, and when to switch to + // baking the full static mesh + switch (InStaticMeshMethod) + { + case EHoudiniStaticMeshMethod::RawMesh: + CurrentTranslator.CreateStaticMesh_RawMesh(); + break; + case EHoudiniStaticMeshMethod::FMeshDescription: + CurrentTranslator.CreateStaticMesh_MeshDescription(); + break; + case EHoudiniStaticMeshMethod::UHoudiniStaticMesh: + CurrentTranslator.CreateHoudiniStaticMesh(); + break; + } + + // Copy the output objects/materials + OutOutputObjects = CurrentTranslator.OutputObjects; + AssignmentMaterialMap = CurrentTranslator.OutputAssignmentMaterials; + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartVertexList() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartVertexList")); + + if (HGPO.PartInfo.VertexCount <= 0) + return false; + + // Get the vertex List + PartVertexList.SetNumUninitialized(HGPO.PartInfo.VertexCount); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, &PartVertexList[0], 0, HGPO.PartInfo.VertexCount)) + { + // Error getting the vertex list. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + + return false; + } + + return true; +} + +void +FHoudiniMeshTranslator::SortSplitGroups() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::SortSplitGroups")); + + // Sort the splits in the order that we want to process them: + // Simple/Convex invisible colliders should be treated first as they will need to be attached to the visible meshes + TArray First; + + // The main geo and its LODs should be created after. + TArray Main; + TArray LODs; + + // Finally, visible colliders and invisible complex colliders as they need their own static mesh + TArray Last; + + for (auto& curSplit : HGPO.SplitGroups) + { + EHoudiniSplitType curSplitType = GetSplitTypeFromSplitName(curSplit); + switch (curSplitType) + { + case EHoudiniSplitType::InvisibleSimpleCollider: + case EHoudiniSplitType::InvisibleUCXCollider: + First.Add(curSplit); + break; + + case EHoudiniSplitType::Normal: + Main.Add(curSplit); + break; + + case EHoudiniSplitType::LOD: + LODs.Add(curSplit); + break; + + case EHoudiniSplitType::RenderedSimpleCollider: + case EHoudiniSplitType::RenderedUCXCollider: + case EHoudiniSplitType::RenderedComplexCollider: + case EHoudiniSplitType::InvisibleComplexCollider: + Last.Add(curSplit); + break; + } + } + + // Make sure LODs are order by name + LODs.Sort(); + + // Copy the split names in order + AllSplitGroups.Empty(); + for (auto& splitName : First) + AllSplitGroups.Add(splitName); + + for (auto& splitName : Main) + AllSplitGroups.Add(splitName); + + for (auto& splitName : LODs) + AllSplitGroups.Add(splitName); + + for (auto& splitName : Last) + AllSplitGroups.Add(splitName); +} + +bool +FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices")); + + // Reset the splits faces/indices arrays + AllSplitVertexLists.Empty(); + AllSplitVertexCounts.Empty(); + AllSplitFaceIndices.Empty(); + AllSplitFirstValidVertexIndex.Empty(); + AllSplitFirstValidPrimIndex.Empty(); + + bool bHasSplit = AllSplitGroups.Num() > 0; + if (bHasSplit) + { + HAPI_PartInfo PartInfo = FHoudiniEngineUtils::ToHAPIPartInfo(HGPO.PartInfo); + + // Buffer for all vertex indices used for split groups. + // We need this to figure out all vertex indices that are not part of them. + TArray AllVertexList; + AllVertexList.SetNumZeroed(PartVertexList.Num()); + + // Buffer for all face indices used for split groups. + // We need this to figure out all face indices that are not part of them. + TArray AllGroupFaceIndices; + AllGroupFaceIndices.SetNumZeroed(HGPO.PartInfo.FaceCount); + + // Some of the groups may contain invalid geometry + // Store them here so we can remove them afterwards + TArray InvalidGroupNameIndices; + + // Extract the vertices/faces for each of the split groups + for (int32 SplitIdx = 0; SplitIdx < AllSplitGroups.Num(); SplitIdx++) + { + const FString& GroupName = AllSplitGroups[SplitIdx]; + + // New vertex list just for this group. + TArray< int32 > GroupVertexList; + TArray< int32 > AllFaceList; + + int32 FirstValidPrimIndex = 0; + int32 FirstValidVertexIndex = 0; + // Extract vertex indices for this split. + int32 GroupVertexListCount = FHoudiniEngineUtils::HapiGetVertexListForGroup( + HGPO.GeoId, PartInfo, GroupName, + PartVertexList, GroupVertexList, + AllVertexList, AllFaceList, AllGroupFaceIndices, + FirstValidVertexIndex, FirstValidPrimIndex, + HGPO.PartInfo.bIsInstanced); + + if (GroupVertexListCount <= 0) + { + // This group doesn't have vertices/faces, mark it as invalid + InvalidGroupNameIndices.Add(SplitIdx); + + // Error getting the vertex list. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list for group %s - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, *GroupName); + + continue; + } + + // If list is not empty, we store it for this group - this will define new mesh. + AllSplitVertexLists.Add(GroupName, GroupVertexList); + AllSplitVertexCounts.Add(GroupName, GroupVertexListCount); + AllSplitFaceIndices.Add(GroupName, AllFaceList); + AllSplitFirstValidVertexIndex.Add(GroupName, FirstValidPrimIndex); + AllSplitFirstValidPrimIndex.Add(GroupName, FirstValidVertexIndex); + } + + if (InvalidGroupNameIndices.Num() > 0) + { + // Remove all invalid split groups + for (int32 InvalIdx = InvalidGroupNameIndices.Num() - 1; InvalIdx >= 0; InvalIdx--) + { + int32 Index = InvalidGroupNameIndices[InvalIdx]; + AllSplitGroups.RemoveAt(Index); + } + } + + // We also need to figure out / construct the vertex list for everything that's not in a split group + TArray GroupSplitFacesRemaining; + GroupSplitFacesRemaining.Init(-1, PartVertexList.Num()); + + int32 GroupVertexListCount = 0; + bool bHasMainSplitGroup = false; + TArray< int32 > GroupSplitFaceIndicesRemaining; + int32 FistUnusedVertexIndex = -1; + for (int32 SplitVertexIdx = 0; SplitVertexIdx < AllVertexList.Num(); SplitVertexIdx++) + { + if (AllVertexList[SplitVertexIdx] == 0) + { + // This is an unused index, we need to add it to unused vertex list. + FistUnusedVertexIndex = SplitVertexIdx; + GroupSplitFacesRemaining[SplitVertexIdx] = PartVertexList[SplitVertexIdx]; + bHasMainSplitGroup = true; + GroupVertexListCount++; + } + } + + int32 FistUnusedPrimIndex = -1; + for (int32 SplitFaceIdx = 0; SplitFaceIdx < AllGroupFaceIndices.Num(); SplitFaceIdx++) + { + if (AllGroupFaceIndices[SplitFaceIdx] == 0) + { + // This is unused face, we need to add it to unused faces list. + GroupSplitFaceIndicesRemaining.Add(SplitFaceIdx); + FistUnusedPrimIndex = SplitFaceIdx; + } + } + + // We store the remaining geo vertex list as a special split named "main geo" + // and make sure its treated before the collider meshes + if (bHasMainSplitGroup) + { + static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + AllSplitGroups.Add(RemainingGroupName); + AllSplitVertexLists.Add(RemainingGroupName, GroupSplitFacesRemaining); + AllSplitVertexCounts.Add(RemainingGroupName, GroupVertexListCount); + AllSplitFaceIndices.Add(RemainingGroupName, GroupSplitFaceIndicesRemaining); + AllSplitFirstValidPrimIndex.Add(RemainingGroupName, FistUnusedPrimIndex); + AllSplitFirstValidVertexIndex.Add(RemainingGroupName, FistUnusedVertexIndex); + } + } + else + { + // No splitting required + // Mark everything as the main geo group + static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + AllSplitGroups.Add(RemainingGroupName); + AllSplitVertexLists.Add(RemainingGroupName, PartVertexList); + AllSplitVertexCounts.Add(RemainingGroupName, PartVertexList.Num()); + AllSplitFirstValidPrimIndex.Add(RemainingGroupName, 0); + AllSplitFirstValidVertexIndex.Add(RemainingGroupName, 0); + + TArray AllFaces; + for (int32 FaceIdx = 0; FaceIdx < HGPO.PartInfo.FaceCount; ++FaceIdx) + AllFaces.Add(FaceIdx); + + AllSplitFaceIndices.Add(RemainingGroupName, AllFaces); + } + + return true; +} + +void +FHoudiniMeshTranslator::ResetPartCache() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::ResetPartCache")); + + // Vertex Positions + PartPositions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Vertex Normals + PartNormals.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Vertex TangentU + PartTangentU.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentU); + + // Vertex TangentV + PartTangentV.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentV); + + // Vertex Colors + PartColors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoColors); + + // Vertex Alpha values + PartAlphas.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoAlpha); + + // FaceSmoothing values + PartFaceSmoothingMasks.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceSmoothingMasks); + + // UVs + PartUVSets.Empty(); + AttribInfoUVSets.Empty(); + + // UVs + PartLightMapResolutions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoLightmapResolution); + + // Material IDs per face + PartFaceMaterialIds.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialIds); + // Unique material IDs + PartUniqueMaterialIds.Empty(); + // Material infos for each unique Material + PartUniqueMaterialInfos.Empty(); + // + bOnlyOneFaceMaterial = false; + + // Face Materials override + PartFaceMaterialOverrides.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialOverrides); + bMaterialOverrideNeedsCreateInstance = false; + + // LOD Screensize + PartLODScreensize.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoLODScreensize); +} + +bool +FHoudiniMeshTranslator::UpdatePartPositionIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartPositionIfNeeded")); + + // Only Retrieve the vertices positions if necessary + if (PartPositions.Num() > 0) + return true; + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve position data") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded")); + + // No need to read the normals if we want unreal to recompute them after + bool bReadNormals = true; + // TODO: Add runtime setting check! + //bool bReadNormals = HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; + if (!bReadNormals) + return true; + + // Only Retrieve the normals if we haven't already + if (PartNormals.Num() > 0) + return true; + + // Retrieve normal data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, PartNormals); + + // There is no normals to fetch + if (!AttribInfoNormals.exists) + return true; + + if (!Success && AttribInfoNormals.exists) + { + // Error retrieving normals. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve normal data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded")) + + bool bReturn = true; + if (PartTangentU.Num() <= 0) + { + // Retrieve TangentU data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_TANGENTU, AttribInfoTangentU, PartTangentU); + + if (!Success && AttribInfoTangentU.exists) + { + // Error retrieving tangent. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentU data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + bReturn = false; + } + } + + if (PartTangentV.Num() <= 0) + { + // Retrieve TangentV data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_TANGENTV, AttribInfoTangentV, PartTangentV); + + if (!Success && AttribInfoTangentV.exists) + { + // Error retrieving tangent. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentV data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + bReturn = false; + } + } + + return bReturn; +} + +bool +FHoudiniMeshTranslator::UpdatePartColorsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartColorsIfNeeded")); + + // Only Retrieve the vertices colors if necessary + if (PartColors.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_COLOR, AttribInfoColors, PartColors); + + if (!Success && AttribInfoColors.exists) + { + // Error retrieving colors. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve color data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded")); + + // Only Retrieve the vertices alphas if necessary + if (PartAlphas.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_ALPHA, AttribInfoAlpha, PartAlphas); + + if (!Success && AttribInfoAlpha.exists) + { + // Error retrieving alpha values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve alpha data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceSmoothingIfNeeded() +{ + // Only Retrieve the vertices FaceSmoothing if necessary + if (PartFaceSmoothingMasks.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, + AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks); + + if (!Success && AttribInfoFaceSmoothingMasks.exists) + { + // Error retrieving FaceSmoothing values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve FaceSmoothing data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded")); + + // Only Retrieve uvs if necessary + if (PartUVSets.Num() > 0) + return true; + + PartUVSets.SetNum(MAX_STATIC_TEXCOORDS); + AttribInfoUVSets.SetNum(MAX_STATIC_TEXCOORDS); + + // The second UV set should be called uv2, but we will still check if need to look for a uv1 set. + // If uv1 exists, we'll look for uv, uv1, uv2 etc.. if not we'll look for uv, uv2, uv3 etc.. + bool bUV1Exists = FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, "uv1"); + + // Retrieve UVs. + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (TexCoordIdx > 0) + UVAttributeName += FString::Printf(TEXT("%d"), bUV1Exists ? TexCoordIdx : TexCoordIdx + 1); + + FHoudiniApi::AttributeInfo_Init(&AttribInfoUVSets[TexCoordIdx]); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*UVAttributeName), + AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], 2); + } + + // Also look for 16.5 uvs (attributes with a Texture type) + // For that, we'll have to iterate through ALL the attributes and check their types + TArray< FString > FoundAttributeNames; + TArray< HAPI_AttributeInfo > FoundAttributeInfos; + + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + FHoudiniEngineUtils::HapiGetAttributeOfType( + HGPO.GeoId, HGPO.PartId, (HAPI_AttributeOwner)AttrIdx, + HAPI_ATTRIBUTE_TYPE_TEXTURE, FoundAttributeInfos, FoundAttributeNames); + } + + if (FoundAttributeInfos.Num() <= 0) + return true; + + // We found some additionnal uv attributes + int32 AvailableIdx = 0; + for (int32 attrIdx = 0; attrIdx < FoundAttributeInfos.Num(); attrIdx++) + { + // Ignore the old uvs + if (FoundAttributeNames[attrIdx] == TEXT("uv") + || FoundAttributeNames[attrIdx] == TEXT("uv1") + || FoundAttributeNames[attrIdx] == TEXT("uv2") + || FoundAttributeNames[attrIdx] == TEXT("uv3") + || FoundAttributeNames[attrIdx] == TEXT("uv4") + || FoundAttributeNames[attrIdx] == TEXT("uv5") + || FoundAttributeNames[attrIdx] == TEXT("uv6") + || FoundAttributeNames[attrIdx] == TEXT("uv7") + || FoundAttributeNames[attrIdx] == TEXT("uv8")) + continue; + + HAPI_AttributeInfo CurrentAttrInfo = FoundAttributeInfos[attrIdx]; + if (!CurrentAttrInfo.exists) + continue; + + // Look for the next available index in the return arrays + for (; AvailableIdx < AttribInfoUVSets.Num(); AvailableIdx++) + { + if (!AttribInfoUVSets[AvailableIdx].exists) + break; + } + + // We are limited to MAX_STATIC_TEXCOORDS uv sets! + // If we already have too many uv sets, skip the rest + if ((AvailableIdx >= MAX_STATIC_TEXCOORDS) || (AvailableIdx >= AttribInfoUVSets.Num())) + { + HOUDINI_LOG_WARNING(TEXT("Too many UV sets found. Unreal only supports %d , skipping the remaining uv sets."), (int32)MAX_STATIC_TEXCOORDS); + break; + } + + // Force the tuple size to 2 ? + CurrentAttrInfo.tupleSize = 2; + + // Add the attribute infos we found + AttribInfoUVSets[AvailableIdx] = CurrentAttrInfo; + + // Allocate sufficient buffer for the attribute's data. + PartUVSets[AvailableIdx].SetNumUninitialized(CurrentAttrInfo.count * CurrentAttrInfo.tupleSize); + + // Get the texture coordinates + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, TCHAR_TO_UTF8(*(FoundAttributeNames[attrIdx])), + &AttribInfoUVSets[AvailableIdx], -1, + &PartUVSets[AvailableIdx][0], 0, CurrentAttrInfo.count)) + { + // Something went wrong when trying to access the uv values, invalidate this set + AttribInfoUVSets[AvailableIdx].exists = false; + } + } + + // Remove unused UV sets + if (bRemoveUnused) + { + for (int32 Idx = PartUVSets.Num() - 1; Idx >= 0; Idx--) + { + if (PartUVSets[Idx].Num() > 0) + continue; + + PartUVSets.RemoveAt(Idx); + } + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartLightmapResolutionsIfNeeded() +{ + // Only Retrieve the vertices lightmap resolution if necessary + if (PartLightMapResolutions.Num() > 0) + return true; + + // Get lightmap resolution (if present). + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, + AttribInfoLightmapResolution, PartLightMapResolutions); + + if (!Success && AttribInfoLightmapResolution.exists) + { + // Error retrieving lightmap resolution values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve lightmap resolution data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded")); + + // Only Retrieve the material IDs if necessary + if (PartFaceMaterialIds.Num() > 0) + return true; + + int32 NumFaces = HGPO.PartInfo.FaceCount; + if (NumFaces <= 0) + return true; + + PartFaceMaterialIds.SetNum(NumFaces); + + // Get the materials IDs per face + HAPI_Bool bSingleFaceMaterial = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, &bSingleFaceMaterial, + &PartFaceMaterialIds[0], 0, NumFaces)) + { + // Error retrieving material face assignments. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material face assignments"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + bOnlyOneFaceMaterial = bSingleFaceMaterial; + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded")); + + // Only Retrieve the material overrides if necessary + if (PartFaceMaterialOverrides.Num() > 0) + return true; + + bMaterialOverrideNeedsCreateInstance = false; + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + + // If material attribute was not found, check fallback compatibility attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + } + + // If material attribute and fallbacks were not found, check the material instance attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + + // We will we need to create material instances from the override attributes + bMaterialOverrideNeedsCreateInstance = AttribInfoFaceMaterialOverrides.exists; + } + + if (AttribInfoFaceMaterialOverrides.exists + && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_PRIM + && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_DETAIL) + { + HOUDINI_LOG_WARNING(TEXT("Static Mesh [%d %s], Geo [%d], Part [%d %s]: unreal_material must be a primitive or detail attribute, ignoring attribute."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + AttribInfoFaceMaterialOverrides.exists = false; + bMaterialOverrideNeedsCreateInstance = false; + PartFaceMaterialOverrides.Empty(); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartNeededMaterials() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials")); + + // Update the per face material IDs + UpdatePartFaceMaterialIDsIfNeeded(); + + // See if we have some material overides + UpdatePartFaceMaterialOverridesIfNeeded(); + + // If we have houdini materials AND overrides: + // We want to only create the Houdini materials that are not "covered" by overrides + // If we have material instance attributes, create all the houdini material anyway + // as their textures could be referenced by the material instance parameters + if (PartFaceMaterialOverrides.Num() > 0 && !bMaterialOverrideNeedsCreateInstance) + { + // If the material override was set on the detail, no need to look for houdini material IDs, as only the override will be used + if (AttribInfoFaceMaterialOverrides.exists && AttribInfoFaceMaterialOverrides.owner == HAPI_ATTROWNER_PRIM) + { + for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) + { + // Add a material ID to the unique array only if that face is not using the override + if (PartFaceMaterialOverrides[MaterialIdx].IsEmpty()) + PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); + } + } + } + else + { + // No material overrides, simply update the unique material array + for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) + PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); + } + + // Remove the invalid material ID from the unique array + PartUniqueMaterialIds.RemoveSingle(-1); + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials - Get the unique material infos")); + // Get the unique material infos + PartUniqueMaterialInfos.SetNum(PartUniqueMaterialIds.Num()); + for (int32 MaterialIdx = 0; MaterialIdx < PartUniqueMaterialIds.Num(); MaterialIdx++) + { + + FHoudiniApi::MaterialInfo_Init(&PartUniqueMaterialInfos[MaterialIdx]); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), + PartUniqueMaterialIds[MaterialIdx], + &PartUniqueMaterialInfos[MaterialIdx])) + { + // Error retrieving material face assignments. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material info for material %d"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, PartUniqueMaterialIds[MaterialIdx]); + continue; + } + } + } + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartLODScreensizeIfNeeded() +{ + // Only retrieve LOD screensizes if necessary + if (PartLODScreensize.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE, + AttribInfoLODScreensize, PartLODScreensize); + + if (!Success && AttribInfoLODScreensize.exists) + { + // Error retrieving FaceSmoothing values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve LOD screensizes"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + + +UStaticMesh* +FHoudiniMeshTranslator::CreateNewStaticMesh(const FString& InSplitIdentifier) +{ + // Update the current Obj/Geo/Part/Split IDs + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.GeoId; + PackageParams.PartId = HGPO.PartId; + PackageParams.SplitStr = InSplitIdentifier; + + UStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); + if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + return nullptr; + + return NewStaticMesh; +} + +UHoudiniStaticMesh* +FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentifier) +{ + // Update the current Obj/Geo/Part/Split IDs + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.GeoId; + PackageParams.PartId = HGPO.PartId; + // Add _HSM suffix to the split str, to distinguish the temporary HoudiniStaticMesh + // from the UStaticMesh + PackageParams.SplitStr = InSplitIdentifier + "_HSM"; + + UHoudiniStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); + if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + return nullptr; + + return NewStaticMesh; +} + +bool +FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() +{ + double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Prepare the object that will store UCX and simple colliders + AllAggregateCollisions.Empty(); + + // We need to know the number of LODs that will be needed for this part + int32 NumberOfLODs = 0; + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) + NumberOfLODs++; + else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + bHasMainGeo = true; + } + + // Update the part's material's IDS and info now + CreateNeededMaterials(); + + // Check now if they were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Get the current target platform for default lod policies + ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + check(CurrentPlatform); + + // New mesh list + TMap StaticMeshToBuild; + + // Map of Houdini Material IDs to Unreal Material Indices + TMap MapHoudiniMatIdToUnrealIndex; + // Map of Houdini Material Attributes to Unreal Material Indices + TMap MapHoudiniMatAttributesToUnrealIndex; + + bool MeshMaterialsHaveBeenReset = false; + + // Mesh Socket array + TArray AllSockets; + FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + + // Iterate through all detected split groups we care about and split geometry. + // The split are ordered in the following way: + // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + + // Get/Create the Aggregate Collisions for this mesh identifier + FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); + + // Handle UCX / Convex Hull colliders + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) + { + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the convex hull colliders and add them to the Aggregate + if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) + continue; + } + else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) + { + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the simple colliders and add them to the aggregate + if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) + continue; + } + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing SM from a previous cook + UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + // Prepare LOD Group data for this static mesh + FStaticMeshLODGroup LODGroup; + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing static mesh, create a new one + FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + + // Use the platform's default LODGroup policy + // TODO? Add setting for default LOD Group? + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); + } + else + { + // Try to reuse the existing SM's LOD group instead of the default one + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); + } + + if (!FoundOutputObject) + { + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + InputObjects.Remove(OutputObjectIdentifier); + } + else + { + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + FoundOutputObject->CachedAttributes.Empty(); + FoundOutputObject->CachedTokens.Empty(); + } + FoundOutputObject->bProxyIsCurrent = false; + + // TODO: Needed? + // Free any RHI resources for existing mesh before we re-create in place. + FoundStaticMesh->PreEditChange(NULL); + + // Check that the Static Mesh we found has the appropriate number of Source models/LODs + int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); + + // LODs are only for the "main" mesh, not for complex colliders! + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); + + if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) + { + while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) + FoundStaticMesh->AddSourceModel(); + + // We may have to remove excessive LOD levels + if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) + FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); + + // Initialize their default reduction setting + for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) + { + FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); + } + FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); + } + + // By default, always work on the first source model, unless we're a LOD + int32 SrcModelIndex = 0; + int32 LODIndex = 0; + if (SplitType == EHoudiniSplitType::LOD) + { + for (auto& curSplit : AllSplitGroups) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); + if (CurrentSplitType == EHoudiniSplitType::LOD + || CurrentSplitType == EHoudiniSplitType::Normal) + { + LODIndex++; + } + + if (curSplit == SplitGroupName) + break; + } + + // Fix for the case where we don't have a main geo + if(!bHasMainGeo) + LODIndex--; + } + + // Grab the appropriate SourceModel + FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; + if (!SrcModel) + { + HOUDINI_LOG_ERROR( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); + continue; + } + + // Load existing raw model. This will be empty as we are constructing a new mesh. + FRawMesh RawMesh; + if (!bRebuildStaticMesh) + { + // We dont need to rebuild the mesh itself: + // the geometry hasn't changed, but the materials have. + // We can just load the old data into the Raw mesh and reuse it. + SrcModel->LoadRawMesh(RawMesh); + } + else + { + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's normal if needed + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + // Check that the number of normal we retrieved is correct + int32 WedgeNormalCount = SplitNormals.Num() / 3; + if (SplitNormals.Num() < 0 || !SplitNormals.IsValidIndex((WedgeNormalCount - 1) * 3 + 2)) + { + // Ignore normals + WedgeNormalCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); + } + + // Transfer the normals to the raw mesh + RawMesh.WedgeTangentZ.SetNumZeroed(WedgeNormalCount); + for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) + { + // Swap Y/Z for Coordinates conversion + RawMesh.WedgeTangentZ[WedgeTangentZIdx].X = SplitNormals[WedgeTangentZIdx * 3 + 0]; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].Y = SplitNormals[WedgeTangentZIdx * 3 + 2]; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].Z = SplitNormals[WedgeTangentZIdx * 3 + 1]; + } + + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENTS + //--------------------------------------------------------------------------------------------------------------------- + + // No need to read the tangents if we want unreal to recompute them after + bool bReadTangents = true; + // TODO: Add runtime setting check! + //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + TArray< float > SplitTangentU; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + TArray< float > SplitTangentV; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + bool bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + + // Check that the number of tangents read matches the number of normals + int32 WedgeTangentUCount = SplitTangentU.Num() / 3; + int32 WedgeTangentVCount = SplitTangentV.Num() / 3; + if (WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount) + bGenerateTangents = true; + + /* + // TODO: Add settings check! + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + */ + + // Generate the tangents if needed + if (bGenerateTangents) + { + RawMesh.WedgeTangentX.SetNumZeroed(WedgeNormalCount); + RawMesh.WedgeTangentY.SetNumZeroed(WedgeNormalCount); + for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) + { + FVector TangentX, TangentY; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].FindBestAxisVectors(TangentX, TangentY); + + RawMesh.WedgeTangentX[WedgeTangentZIdx] = TangentX; + RawMesh.WedgeTangentY[WedgeTangentZIdx] = TangentY; + } + } + else + { + // Transfer the tangents we have read them and they're valid + RawMesh.WedgeTangentX.SetNumZeroed(WedgeTangentUCount); + for (int32 WedgeTangentUIdx = 0; WedgeTangentUIdx < WedgeTangentUCount; ++WedgeTangentUIdx) + { + // We need to flip Z and Y + RawMesh.WedgeTangentX[WedgeTangentUIdx].X = SplitTangentU[WedgeTangentUIdx * 3 + 0]; + RawMesh.WedgeTangentX[WedgeTangentUIdx].Y = SplitTangentU[WedgeTangentUIdx * 3 + 2]; + RawMesh.WedgeTangentX[WedgeTangentUIdx].Z = SplitTangentU[WedgeTangentUIdx * 3 + 1]; + } + + RawMesh.WedgeTangentY.SetNumZeroed(WedgeTangentVCount); + for (int32 WedgeTangentVIdx = 0; WedgeTangentVIdx < WedgeTangentVCount; ++WedgeTangentVIdx) + { + // We need to flip Z and Y + RawMesh.WedgeTangentY[WedgeTangentVIdx].X = SplitTangentV[WedgeTangentVIdx * 3 + 0]; + RawMesh.WedgeTangentY[WedgeTangentVIdx].Y = SplitTangentV[WedgeTangentVIdx * 3 + 2]; + RawMesh.WedgeTangentY[WedgeTangentVIdx].Z = SplitTangentV[WedgeTangentVIdx * 3 + 1]; + } + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // VERTEX COLORS AND ALPHAS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's colors if needed + UpdatePartColorsIfNeeded(); + + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract this part's alpha values if needed + UpdatePartAlphasIfNeeded(); + + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + + // Transfer colors and alphas if possible + int32 WedgeColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; + bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && WedgeColorsCount > 0; + bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == WedgeColorsCount); + if (bSplitColorValid) + { + RawMesh.WedgeColors.SetNumZeroed(WedgeColorsCount); + for (int32 WedgeColorIdx = 0; WedgeColorIdx < WedgeColorsCount; WedgeColorIdx++) + { + FLinearColor WedgeColor; + WedgeColor.R = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); + WedgeColor.G = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); + WedgeColor.B = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); + + if (bSplitAlphaValid) + { + // Use the Alpha attribute value + WedgeColor.A = FMath::Clamp(SplitAlphas[WedgeColorIdx], 0.0f, 1.0f); + } + else if (AttribInfoColors.tupleSize >= 4) + { + // Use the alpha value from the color attribute + WedgeColor.A = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); + } + else + { + WedgeColor.A = 1.0f; + } + + // Convert linear color to fixed color. + RawMesh.WedgeColors[WedgeColorIdx] = WedgeColor.ToFColor(false); + } + } + else + { + // TODO? Needed? New meshes wont have WedgeIndices yet!? + // No Colors or Alphas, init colors to White + FColor DefaultWedgeColor = FLinearColor::White.ToFColor(false); + WedgeColorsCount = RawMesh.WedgeIndices.Num(); + if (WedgeColorsCount > 0) + RawMesh.WedgeColors.Init(DefaultWedgeColor, WedgeColorsCount); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's FaceSmoothing values if needed + UpdatePartFaceSmoothingIfNeeded(); + + // Get the FaceSmoothing values for this split + TArray SplitFaceSmoothingMasks; + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); + + // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! + RawMesh.FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); + + // Check that the number of face smoothing values we retrieved is correct + int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; + if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) + { + // Ignore our face smoothing values + WedgeFaceSmoothCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); + } + + // Transfer the face smoothing masks to the raw mesh if we have any + for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) + { + RawMesh.FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; + } + + //--------------------------------------------------------------------------------------------------------------------- + // UVS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's UV sets if needed + UpdatePartUVSetsIfNeeded(); + + // See if we need to transfer uv point attributes to vertex attributes. + TArray> SplitUVSets; + SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + } + + // Transfer UVs to the Raw Mesh + int32 UVChannelCount = 0; + int32 LightMapUVChannel = 0; + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; + + int32 WedgeUVCount = SplitUVs.Num() / 2; + if (SplitUVs.Num() > 0 && SplitUVs.IsValidIndex((WedgeUVCount - 1) * 2 + 1)) + { + RawMesh.WedgeTexCoords[TexCoordIdx].SetNumZeroed(WedgeUVCount); + for (int32 WedgeUVIdx = 0; WedgeUVIdx < WedgeUVCount; ++WedgeUVIdx) + { + // We need to flip V coordinate when it's coming from HAPI. + RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].X = SplitUVs[WedgeUVIdx * 2 + 0]; + RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].Y = 1.0f - SplitUVs[WedgeUVIdx * 2 + 1]; + } + + UVChannelCount++; + if (UVChannelCount <= 2) + LightMapUVChannel = TexCoordIdx; + } + else + { + RawMesh.WedgeTexCoords[TexCoordIdx].Empty(); + } + } + + // We must have at least one UV channel. If there's none, create one filled with zero data. + if (UVChannelCount == 0) + RawMesh.WedgeTexCoords[0].SetNumZeroed(SplitVertexCount); + + // Set the lightmap Coordinate Index + // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention + // If not, the first UV set will be used + FoundStaticMesh->LightMapCoordinateIndex = LightMapUVChannel; + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's LightmapResolution values if needed + UpdatePartLightmapResolutionsIfNeeded(); + + // make sure the mesh has a new lighting guid + FoundStaticMesh->LightingGuid = FGuid::NewGuid(); + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" + // So that IndicesMapper[ oldIndex ] => newIndex + TArray IndicesMapper; + IndicesMapper.Init(-1, SplitVertexList.Num()); + int32 CurrentMapperIndex = 0; + + // NeededVertices: + // Array containing the old index of the needed vertices for the current split + // NeededVertices[ newIndex ] => oldIndex + TArray< int32 > NeededVertices; + RawMesh.WedgeIndices.SetNumZeroed(SplitVertexCount); + + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) + || !IndicesMapper.IsValidIndex(WedgeIndices[1]) + || !IndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (IndicesMapper[WedgeIndices[i]] < 0) + { + // This old index has not yet been "converted" to a new index + NeededVertices.Add(WedgeIndices[i]); + IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; + CurrentMapperIndex++; + } + + // Replace the old index with the new one + WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; + } + + if (!RawMesh.WedgeIndices.IsValidIndex(ValidVertexId + 2)) + break; + + // Flip wedge indices to fix the winding order. + RawMesh.WedgeIndices[ValidVertexId + 0] = WedgeIndices[0]; + RawMesh.WedgeIndices[ValidVertexId + 1] = WedgeIndices[2]; + RawMesh.WedgeIndices[ValidVertexId + 2] = WedgeIndices[1]; + + // Check if we need to patch UVs. + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + if (RawMesh.WedgeTexCoords[TexCoordIdx].IsValidIndex(ValidVertexId + 2)) + { + Swap(RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 1], + RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 2]); + } + } + + // Check if we need to patch colors. + if (RawMesh.WedgeColors.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeColors[ValidVertexId + 1], RawMesh.WedgeColors[ValidVertexId + 2]); + + // Check if we need to patch Normals and tangents. + if (RawMesh.WedgeTangentZ.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentZ[ValidVertexId + 1], RawMesh.WedgeTangentZ[ValidVertexId + 2]); + + if (RawMesh.WedgeTangentX.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentX[ValidVertexId + 1], RawMesh.WedgeTangentX[ValidVertexId + 2]); + + if (RawMesh.WedgeTangentY.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentY[ValidVertexId + 1], RawMesh.WedgeTangentY[ValidVertexId + 2]); + + ValidVertexId += 3; + } + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + UpdatePartPositionIfNeeded(); + + // + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + int32 VertexPositionsCount = NeededVertices.Num(); + RawMesh.VertexPositions.SetNumZeroed(VertexPositionsCount); + + for (int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx) + { + int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; + if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // We need to swap Z and Y coordinate here, and convert from m to cm. + RawMesh.VertexPositions[VertexPositionIdx].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + RawMesh.VertexPositions[VertexPositionIdx].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + RawMesh.VertexPositions[VertexPositionIdx].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + /* + // TODO: + // Check if this mesh contains only degenerate triangles. + if (FHoudiniEngineUtils::CountDegenerateTriangles(RawMesh) == SplitGroupFaceCount) + { + // This mesh contains only degenerate triangles, there's nothing we can do. + if (bStaticMeshCreated) + StaticMesh->MarkPendingKill(); + + continue; + } + */ + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL ATTRIBUTE OVERRIDES + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: These are actually per faces, not per vertices... + // Need to update!! + UpdatePartFaceMaterialOverridesIfNeeded(); + + //--------------------------------------------------------------------------------------------------------------------- + // FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Handle Materials!!!! + + // Get face indices for this split. + TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; + + // We need to reset the Static Mesh's materials once per SM: + // so, for the first lod, or the main geo... + if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + { + FoundStaticMesh->StaticMaterials.Empty(); + MeshMaterialsHaveBeenReset = true; + } + + // .. or for each visible complex collider + if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + FoundStaticMesh->StaticMaterials.Empty(); + + // Process material overrides first + if (PartFaceMaterialOverrides.Num() > 0) + { + // If the part has material overrides + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + continue; + + const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); + int32 CurrentFaceMaterialIdx = 0; + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Try to locate the corresponding material interface + UMaterialInterface * MaterialInterface = nullptr; + + // Start by looking in our assignment map + auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface && !MaterialName.IsEmpty()) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add this material to the map + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + } + else + { + // The Attribute Material and its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + CurrentFaceMaterialIdx = *FoundUnrealMatIndex; + } + else + { + // If everything fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + } + } + } + + // Update the Face Material on the mesh + RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + } + } + else if (PartUniqueMaterialIds.Num() > 0) + { + // The part has houdini materials + if (bOnlyOneFaceMaterial) + { + // We have only one material. + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Empty(); + FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + else + { + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + // Reset Rawmesh material face assignments. + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } + + UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Add the material to the Static mesh + int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + + // Update the face index + RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } + } + } + else + { + // No materials were found, we need to use default Houdini material. + int32 SplitFaceCount = SplitFaceIndices.Num(); + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceCount); + + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Empty(); + FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + + // TODO: + // BUILD SETTINGS + // (Using default for now) + SrcModel->BuildSettings.bRemoveDegenerates = true; + SrcModel->BuildSettings.bUseMikkTSpace = true; + SrcModel->BuildSettings.bBuildAdjacencyBuffer = false; + SrcModel->BuildSettings.MinLightmapResolution = 64; + SrcModel->BuildSettings.bUseFullPrecisionUVs = false; + SrcModel->BuildSettings.SrcLightmapIndex = 0; + SrcModel->BuildSettings.DstLightmapIndex = 1; + SrcModel->BuildSettings.bRecomputeNormals = (0 == RawMesh.WedgeTangentZ.Num()); + SrcModel->BuildSettings.bRecomputeTangents = (0 == RawMesh.WedgeTangentX.Num() || 0 == RawMesh.WedgeTangentY.Num()); + SrcModel->BuildSettings.bGenerateLightmapUVs = RawMesh.WedgeTexCoords->Num() <= 0; + + // Check for a lightmap resolution override + int32 LightMapResolutionOverride = -1; + if (PartLightMapResolutions.Num() > 0) + LightMapResolutionOverride = PartLightMapResolutions[0]; + + if (LightMapResolutionOverride > 0) + FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; + else + FoundStaticMesh->LightMapResolution = 64; + + // TODO: + // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? + + // By default the distance field resolution should be set to 2.0 + // TODO should come from the HAC + //SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniCookParams.GeneratedDistanceFieldResolutionScale; + SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; + + // This is required due to the impeding deprecation of FRawMesh + // If we dont update this UE4 will crash upon deleting an asset. + SrcModel->StaticMeshOwner = FoundStaticMesh; + // Store the new raw mesh. + SrcModel->SaveRawMesh(RawMesh); + + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = GetLODSCreensizeForSplit(SplitGroupName); + if (screensize >= 0.0f) + { + // Only apply the LOD screensize if it's valid + SrcModel->ScreenSize = screensize; + //FoundStaticMesh->GetSourceModel(LODIndex).ScreenSize = screensize; + FoundStaticMesh->bAutoComputeLODScreenSize = false; + } + + // TODO: + // SET STATIC MESH GENERATION PARAM + // HANDLE COLLIDERS + // REMOVE OLD COLLIDERS + // CUSTOM BAKE NAME OVERRIDE + + // Update property attributes on the SM + TArray PropertyAttributes; + if (GetGenericPropertiesAttributes( + HGPO.GeoId, HGPO.PartId, + AllSplitFirstValidVertexIndex[SplitGroupName], + AllSplitFirstValidPrimIndex[SplitGroupName], + PropertyAttributes)) + { + UpdateGenericPropertiesAttributes( + FoundStaticMesh, PropertyAttributes); + } + + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray TileValues; + if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + // Notify that we created a new Static Mesh if needed + if (bNewStaticMeshCreated) + FAssetRegistryModule::AssetCreated(FoundStaticMesh); + + // Add the Static mesh to the output maps and the build map if we haven't already + if (FoundOutputObject) + { + FoundOutputObject->OutputObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = false; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + + StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); + } + + // Look if we only have colliders + // If we do, we'll allow attaching sockets to the collider meshes + bool bCollidersOnly = true; + for (auto& Current : StaticMeshToBuild) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) + { + bCollidersOnly = false; + break; + } + } + + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + for (auto& Current : StaticMeshToBuild) + { + UStaticMesh* SM = Current.Value; + if (!SM || SM->IsPendingKill()) + continue; + + UBodySetup * BodySetup = SM->BodySetup; + if (!BodySetup) + { + SM->CreateBodySetup(); + BodySetup = SM->BodySetup; + } + + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + + // Handle the Static Mesh's colliders + if (BodySetup && !BodySetup->IsPendingKill()) + { + // Make sure rendering is done - so we are not changing data being used by collision drawing. + FlushRenderingCommands(); + + // Clean up old colliders from a previous cook + BodySetup->Modify(); + BodySetup->RemoveSimpleCollision(); + // Create new GUID + BodySetup->InvalidatePhysicsData(); + + FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; + FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); + if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) + { + BodySetup->AddCollisionFrom(*CurrentAggColl); + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; + } + + RefreshCollisionChange(*SM); + SM->bCustomizedCollision = true; + + // See if we need to enable collisions on the whole mesh + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + // Complex collider, enable collisions for this static mesh. + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + else + { + // TODO + // if the LODForCollision uproperty attribute is set, we need to activate complex collision + // on the static mesh for that lod to be picked up properly as a collider + if (FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, + "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) + { + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + } + } + + // Add the Sockets to the StaticMesh + // We only add them to the main geo, or to the colliders if we only generate colliders + bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; + if (bAddSocket) + { + if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); + } + } + + // BUILD the Static Mesh + // bSilent doesnt add the Build Errors... + double build_start = FPlatformTime::Seconds(); + TArray SMBuildErrors; + SM->Build(true, &SMBuildErrors); + double build_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); + + SM->GetOnMeshChanged().Broadcast(); + + /* + // Try to find the outer package so we can dirty it up + if (SM->GetOuter()) + { + SM->GetOuter()->MarkPackageDirty(); + } + else + { + SM->MarkPackageDirty(); + } + */ + + + UPackage* MeshPackage = SM->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + + /* + // DPT: deactivated auto saving mesh/material package + // only dirty for now, as we'll save them when saving the world. + TArray PackageToSave; + PackageToSave.Add(MeshPackage); + + // Save the created package + FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); + */ + } + } + + // TODO: Still necessary ? SM->Build should actually update the navmesh... + // Now that all the meshes are built and their collisions meshes and primitives updated, + // we need to update their pre-built navigation collision used by the navmesh + for (auto& Iter : OutputObjects) + { + UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); + if (!StaticMesh || StaticMesh->IsPendingKill()) + continue; + + UBodySetup * BodySetup = StaticMesh->BodySetup; + if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) + { + // Unreal caches the Navigation Collision and never updates it for StaticMeshes, + // so we need to manually flush and recreate the data to have proper navigation collision + BodySetup->InvalidatePhysicsData(); + BodySetup->CreatePhysicsMeshes(); + StaticMesh->NavCollision->Setup(BodySetup); + } + } + + double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() executed in %f seconds."), time_end - time_start); + + return true; +} + +bool +FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() +{ + double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + // Simple colliders first, lods and finally, invisible colliders (that are separate Static Mesh) + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Prepare the object that will store UCX and simple colliders + AllAggregateCollisions.Empty(); + + // We need to know the number of LODs that will be needed for this part + int32 NumberOfLODs = 0; + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) + NumberOfLODs++; + else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + bHasMainGeo = true; + } + + // Update the part's material's IDS and info now + CreateNeededMaterials(); + + // Check if the materials were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Get the current target platform for default lod policies + ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + check(CurrentPlatform); + + // New mesh list + TMap StaticMeshToBuild; + + // Map of Houdini Material IDs to Unreal Material Indices + TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; + // Map of Houdini Material Attributes to Unreal Material Indices + TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; + + bool MeshMaterialsHaveBeenReset = false; + + // Mesh Socket array + TArray AllSockets; + FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + + double tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); + + // Iterate through all detected split groups we care about and split geometry. + // The split are ordered in the following way: + // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + + // Get/Create the Aggregate Collisions for this mesh identifier + FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); + + // Handle UCX / Convex Hull colliders + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) + { + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the convex hull colliders and add them to the Aggregate + if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) + continue; + } + else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) + { + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the simple colliders and add them to the aggregate + if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) + continue; + } + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing SM from a previous cook + UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + // Prepare LOD Group data for this static mesh + FStaticMeshLODGroup LODGroup; + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing static mesh, create a new one + FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + + // Use the platform's default LODGroup policy + // TODO? Add setting for default LOD Group? + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); + } + else + { + // Try to reuse the existing SM's LOD group instead of the default one + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); + } + + if (!FoundOutputObject) + { + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + } + else + { + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + FoundOutputObject->CachedAttributes.Empty(); + FoundOutputObject->CachedTokens.Empty(); + } + FoundOutputObject->bProxyIsCurrent = false; + + // TODO: Needed? + // Free any RHI resources for existing mesh before we re-create in place. + FoundStaticMesh->PreEditChange(NULL); + + // Check that the Static Mesh we found has the appropriate number of Source models/LODs + int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); + + // LODs are only for the "main" mesh, not for complex colliders! + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); + + if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) + { + while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) + FoundStaticMesh->AddSourceModel(); + + // We may have to remove excessive LOD levels + if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) + FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); + + // Initialize their default reduction setting + for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) + { + FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); + } + FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); + } + + // By default, always work on the first source model, unless we're a LOD + int32 SrcModelIndex = 0; + int32 LODIndex = 0; + if (SplitType == EHoudiniSplitType::LOD) + { + for (auto& curSplit : AllSplitGroups) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); + if (CurrentSplitType == EHoudiniSplitType::LOD + || CurrentSplitType == EHoudiniSplitType::Normal) + { + LODIndex++; + } + + if (curSplit == SplitGroupName) + break; + } + + // Fix for the case where we don't have a main geo + if(!bHasMainGeo) + LODIndex--; + } + + // Grab the appropriate SourceModel + FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; + if (!SrcModel) + { + HOUDINI_LOG_ERROR( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); + continue; + } + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + bool bRecomputeNormal = false; + bool bRecomputeTangent = false; + + // Load the existing mesh description if we don't need to rebuild the mesh + //FRawMesh RawMesh; + FMeshDescription* MeshDescription; + if (!bRebuildStaticMesh) + { + // We dont need to rebuild the mesh itself: + // the geometry hasn't changed, but the materials have. + // We can just reuse the old MeshDescription and reuse it. + MeshDescription = FoundStaticMesh->GetMeshDescription(LODIndex); + } + else + { + // Extract all the data needed for this split + // Start by initializing the MeshDescription for this LOD + MeshDescription = FoundStaticMesh->CreateMeshDescription(LODIndex); + FStaticMeshAttributes(*MeshDescription).Register(); + + // Mesh description uses material to create its PolygonGroups, + // so we first need to know how many different materials we have for this split + // and what vertices/indices belong to each material for remapping + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // SplitNeededVertices + // Array containing the (unique) part indices for the vertices that are needed for this split + // SplitNeededVertices[splitIndex] = PartIndex + TArray SplitNeededVertices; + //SplitNeededVertices.SetNumZeroed(SplitVertexCount); + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" so that IndicesMapper[ partIndex ] => splitIndex + TArray PartToSplitIndicesMapper; + PartToSplitIndicesMapper.Init(-1, SplitVertexList.Num()); + //TMap SplitToPartIndicesMapper; + + // SplitIndices + // Array of SplitIndices used to describe this split's polygons + TArray SplitIndices; + SplitIndices.SetNumZeroed(SplitVertexCount); + + int32 CurrentSplitIndex = 0; + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[0]) + || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[1]) + || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (PartToSplitIndicesMapper[WedgeIndices[i]] < 0) + { + // This part index has not yet been "converted" to a new split index + SplitNeededVertices.Add(WedgeIndices[i]); + PartToSplitIndicesMapper[WedgeIndices[i]] = CurrentSplitIndex; + //SplitToPartIndicesMapper.Add(CurrentSplitIndex, WedgeIndices[i]); + CurrentSplitIndex++; + } + + // Replace the old part index with the new split index + WedgeIndices[i] = PartToSplitIndicesMapper[WedgeIndices[i]]; + } + + if (!SplitIndices.IsValidIndex(ValidVertexId + 2)) + break; + + // Flip wedge indices to fix the winding order. + SplitIndices[ValidVertexId + 0] = WedgeIndices[0]; + SplitIndices[ValidVertexId + 1] = WedgeIndices[2]; + SplitIndices[ValidVertexId + 2] = WedgeIndices[1]; + + ValidVertexId += 3; + } + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract position for this part + UpdatePartPositionIfNeeded(); + + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + TVertexAttributesRef VertexPositions = + MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); + + MeshDescription->ReserveNewVertices(SplitNeededVertices.Num()); + for ( const int32& NeededVertexIndex : SplitNeededVertices) + { + // Create a new Vertex + FVertexID VertexID = MeshDescription->CreateVertex(); + if (PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexPositions[VertexID].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexPositions[VertexID].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexPositions[VertexID].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + else + { + // Error when retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + } + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: Check if still needed for MeshDescription + // We need to reset the Static Mesh's materials once per SM: + // so, for the first lod, or the main geo... + if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + { + FoundStaticMesh->StaticMaterials.Empty(); + MeshMaterialsHaveBeenReset = true; + } + + // .. or for each visible complex collider + if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + FoundStaticMesh->StaticMaterials.Empty(); + + // Get this split's faces + TArray& SplitGroupFaceIndices = AllSplitFaceIndices[SplitGroupName]; + // Array holding the materials needed for this split + //TArray SplitMaterials; + // Split Material indices per face, by default all faces are set to use the first Material + TArray SplitFaceMaterialIndices; + SplitFaceMaterialIndices.SetNumZeroed(SplitGroupFaceIndices.Num()); + + bool HasHoudiniMaterials = PartUniqueMaterialIds.Num() > 0; + bool HasMaterialOverrides = PartFaceMaterialOverrides.Num() > 0; + if (!HasHoudiniMaterials && !HasMaterialOverrides) + { + // We don't have any material override or houdini material + // we just need one polygon group using the default Houdini material. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + + // TODO: ? Add default mat to the assignement map? + } + else if (HasHoudiniMaterials && !HasMaterialOverrides) + { + // We have Houdini Material but no overrides + if (bOnlyOneFaceMaterial || PartUniqueMaterialIds.Num() == 1) + { + // We have only one Houdini material. + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + + // TODO: ? Add the mat to the assignement map? + } + else + { + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * MaterialDefault = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + // Reset Rawmesh material face assignments. + for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just use its material index + SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } + + UMaterialInterface * MaterialInterface = Cast(MaterialDefault); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Add the material to the Static mesh + //int32 UnrealMatIndex = SplitMaterials.Add(Material); + int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + + // Update the face index + SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } + } + } + else + { + // If we have material overrides + for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; + + int32 CurrentFaceMaterialIdx = -1; + if (PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + { + const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); + if (FoundFaceMaterialIdx) + { + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Try to locate the corresponding material interface + UMaterialInterface * MaterialInterface = nullptr; + if (!MaterialName.IsEmpty()) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast< UMaterialInterface >( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add this material to the map + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + } + } + + if (CurrentFaceMaterialIdx < 0) + { + // The attribute Material or its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + CurrentFaceMaterialIdx = *FoundUnrealMatIndex; + } + else + { + // If everything else fails, we'll use the default material + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + } + } + } + + // Update the Face Material on the mesh + SplitFaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + } + } + + // Create a Polygon Group for each material slot + TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = + MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); + + // We must use the number of assignment materials found to reserve the number of material slots + // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials + int32 NumberOfMaterials = OutputAssignmentMaterials.Num(); + if (NumberOfMaterials <= 0) + { + // No materials, create a polygon group for the default one + const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + } + else + { + MeshDescription->ReserveNewPolygonGroups(NumberOfMaterials); + //for (int32 MatIndex = 0; MatIndex < NumberOfMaterials; ++MatIndex) + for (auto& CurrentMatAssignement : OutputAssignmentMaterials) + { + const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = + FName(CurrentMatAssignement.Value ? *(CurrentMatAssignement.Value->GetName()) : *(CurrentMatAssignement.Key)); + } + } + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + // + // VERTEX INSTANCE ATTRIBUTES + // NORMALS, TANGENTS, COLORS, UVS, Alpha + // + + // Extract the normals + UpdatePartNormalsIfNeeded(); + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); + + // Extract the tangents + // No need to read the tangents if we want unreal to recompute them after + TArray SplitTangentU; + TArray SplitTangentV; + bool bReadTangents = true; + // TODO: Add runtime setting check! + //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + int32 NormalCount = SplitNormals.Num(); + bool bGenerateTangents = (NormalCount > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + // Check that the number of tangents read matches the number of normals + if (SplitTangentU.Num() != NormalCount || SplitTangentV.Num() != NormalCount) + bGenerateTangents = true; + + /* + // TODO: Add settings check! + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + */ + + // Generate the tangents if needed + if (bGenerateTangents) + { + SplitTangentU.SetNumZeroed(NormalCount); + SplitTangentV.SetNumZeroed(NormalCount); + for (int32 Idx = 0; Idx + 2 < NormalCount; Idx += 3) + { + FVector TangentZ; + TangentZ.X = SplitNormals[Idx + 0]; + TangentZ.Y = SplitNormals[Idx + 2]; + TangentZ.Z = SplitNormals[Idx + 1]; + + FVector TangentX, TangentY; + TangentZ.FindBestAxisVectors(TangentX, TangentY); + + SplitTangentU[Idx + 0] = TangentX.X; + SplitTangentU[Idx + 2] = TangentX.Y; + SplitTangentU[Idx + 1] = TangentX.Z; + + SplitTangentV[Idx + 0] = TangentY.X; + SplitTangentV[Idx + 2] = TangentY.Y; + SplitTangentV[Idx + 1] = TangentY.Z; + } + } + } + TVertexInstanceAttributesRef VertexInstanceTangents = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); + TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); + + // Extract the color values + UpdatePartColorsIfNeeded(); + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract the alpha values + UpdatePartAlphasIfNeeded(); + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + TVertexInstanceAttributesRef VertexInstanceColors = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); + + // Extract UVs + UpdatePartUVSetsIfNeeded(true); + // See if we need to transfer uv point attributes to vertex attributes. + int32 UVSetCount = PartUVSets.Num(); + TArray> SplitUVSets; + SplitUVSets.SetNum(UVSetCount); + for (int32 TexCoordIdx = 0; TexCoordIdx < UVSetCount; TexCoordIdx++) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + } + TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); + VertexInstanceUVs.SetNumIndices(UVSetCount); + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + // Allocate space for the vertex instances and polygons + MeshDescription->ReserveNewVertexInstances(SplitIndices.Num()); + MeshDescription->ReserveNewPolygons(SplitIndices.Num() / 3); + //Approximately 2.5 edges per polygons + MeshDescription->ReserveNewEdges(SplitIndices.Num() * 2.5f / 3); + + bool bHasNormal = SplitNormals.Num() > 0; + bool bHasTangents = SplitTangentU.Num() > 0 && SplitTangentV.Num() > 0; + bool bHasRGB = SplitColors.Num() > 0; + bool bHasRGBA = bHasRGB && AttribInfoColors.tupleSize == 4; + bool bHasAlpha = SplitAlphas.Num() > 0; + + bRecomputeNormal = !bHasNormal; + bRecomputeTangent = !bHasTangents; + + TArray HasUVSets; + HasUVSets.SetNumZeroed(PartUVSets.Num()); + for (int32 Idx = 0; Idx < PartUVSets.Num(); Idx++) + HasUVSets[Idx] = PartUVSets[Idx].Num() > 0; + + uint32 FaceCount = SplitIndices.Num() / 3; + for (uint32 FaceIndex = 0; FaceIndex < FaceCount; FaceIndex++) + { + TArray FaceVertexInstanceIDs; + FaceVertexInstanceIDs.SetNum(3); + + // Ignore degenerate triangles + FVertexID VertexIDs[3]; + for (int32 Corner = 0; Corner < 3; ++Corner) + { + VertexIDs[Corner] = FVertexID(SplitIndices[(FaceIndex * 3) + Corner]); + } + if (VertexIDs[0] == VertexIDs[1] || VertexIDs[0] == VertexIDs[2] || VertexIDs[1] == VertexIDs[2]) + continue; + + //FVertexID FaceVertexIDs[3]; + for (int32 Corner = 0; Corner < 3; Corner++) + { + uint32 SplitIndex = (FaceIndex * 3) + Corner; + uint32 SplitVertexIndex = SplitIndices[SplitIndex]; + const FVertexInstanceID& VertexInstanceID = MeshDescription->CreateVertexInstance(FVertexID(SplitVertexIndex)); + + // Fix the winding order by updating the SplitIndex (invert corner 1 and 2) + // instead of going 0 1 2 go 0 2 1 + // TODO; this slows down StaticMesh->Build() considerably! + Corner == 1 ? SplitIndex++ : Corner == 2 ? SplitIndex-- : SplitIndex; + + const uint32 SplitVertexIndex_X = SplitIndex * 3 + 0; + const uint32 SplitVertexIndex_Y = SplitIndex * 3 + 2; + const uint32 SplitVertexIndex_Z = SplitIndex * 3 + 1; + // Normals + if (bHasNormal) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexInstanceNormals[VertexInstanceID].X = SplitNormals[SplitVertexIndex_X]; + VertexInstanceNormals[VertexInstanceID].Y = SplitNormals[SplitVertexIndex_Y]; + VertexInstanceNormals[VertexInstanceID].Z = SplitNormals[SplitVertexIndex_Z]; + } + + // Tangents and binormals + if (bHasTangents) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexInstanceTangents[VertexInstanceID].X = SplitTangentU[SplitVertexIndex_X]; + VertexInstanceTangents[VertexInstanceID].Y = SplitTangentU[SplitVertexIndex_Y]; + VertexInstanceTangents[VertexInstanceID].Z = SplitTangentU[SplitVertexIndex_Z]; + + FVector TangentY; + TangentY.X = SplitTangentV[SplitVertexIndex_X]; + TangentY.Y = SplitTangentV[SplitVertexIndex_Y]; + TangentY.Z = SplitTangentV[SplitVertexIndex_Z]; + + VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign( + VertexInstanceTangents[VertexInstanceID].GetSafeNormal(), + TangentY.GetSafeNormal(), + VertexInstanceNormals[VertexInstanceID].GetSafeNormal()); + } + + // Color + FLinearColor Color = FLinearColor::White; + if (bHasRGB) + { + Color.R = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); + Color.G = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); + Color.B = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); + } + // Alpha + if (bHasAlpha) + { + Color.A = FMath::Clamp(SplitAlphas[SplitIndex], 0.0f, 1.0f); + } + else if (bHasRGBA) + { + Color.A = FMath::Clamp(SplitColors[SplitIndex * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); + } + VertexInstanceColors[VertexInstanceID] = FVector4(Color); + + // UVs + for (int32 UVIndex = 0; UVIndex < SplitUVSets.Num(); UVIndex++) + { + if (HasUVSets[UVIndex]) + { + // We need to flip V coordinate when it's coming from HAPI. + FVector2D CurrentUV; + CurrentUV.X = SplitUVSets[UVIndex][SplitIndex * 2 + 0]; + CurrentUV.Y = 1.0f - SplitUVSets[UVIndex][SplitIndex * 2 + 1]; + + VertexInstanceUVs.Set(VertexInstanceID, UVIndex, CurrentUV); + } + } + + FaceVertexInstanceIDs[Corner] = VertexInstanceID; + } + + const FPolygonGroupID PolygonGroupID(SplitFaceMaterialIndices[FaceIndex]); + + // Insert a triangle into the mesh + MeshDescription->CreateTriangle(PolygonGroupID, FaceVertexInstanceIDs); + } + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's FaceSmoothing values if needed + UpdatePartFaceSmoothingIfNeeded(); + + // Get the FaceSmoothing values for this split + TArray SplitFaceSmoothingMasks; + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); + + // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! + // TODO: Expose the default FaceSmoothing value + // 0 will make hard face + TArray FaceSmoothingMasks; + FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); + + // Check that the number of face smoothing values we retrieved is correct + int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; + if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) + { + // Ignore our face smoothing values + WedgeFaceSmoothCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); + } + + // Transfer the face smoothing masks to the raw mesh if we have any + for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) + { + FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; + } + + // TODO + // Check + FStaticMeshOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, *MeshDescription); + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + // Extract this part's LightmapResolution values if needed + UpdatePartLightmapResolutionsIfNeeded(); + + // make sure the mesh has a new lighting guid + FoundStaticMesh->LightingGuid = FGuid::NewGuid(); + } + + // TODO: + // BUILD SETTINGS + // (Using default for now) + SrcModel->BuildSettings.bRemoveDegenerates = true; + SrcModel->BuildSettings.bUseMikkTSpace = true; + SrcModel->BuildSettings.bBuildAdjacencyBuffer = false; + SrcModel->BuildSettings.MinLightmapResolution = 64; + SrcModel->BuildSettings.bUseFullPrecisionUVs = false; + SrcModel->BuildSettings.SrcLightmapIndex = 0; + SrcModel->BuildSettings.DstLightmapIndex = 1; + SrcModel->BuildSettings.bRecomputeNormals = bRecomputeNormal; + SrcModel->BuildSettings.bRecomputeTangents = bRecomputeNormal || bRecomputeTangent; + SrcModel->BuildSettings.bGenerateLightmapUVs = PartUVSets.Num() <= 0; + + // Set the lightmap Coordinate Index + // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention + FoundStaticMesh->LightMapCoordinateIndex = PartUVSets.Num() > 1 ? 1 : 0; + + // Check for a lightmapa resolution override + int32 LightMapResolutionOverride = -1; + if ( PartLightMapResolutions.Num() > 0) + LightMapResolutionOverride = PartLightMapResolutions[0]; + + if (LightMapResolutionOverride > 0) + FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; + else + FoundStaticMesh->LightMapResolution = 64; + + // TODO: + // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? + + // By default the distance field resolution should be set to 2.0 + // TODO should come from the HAC + //SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniCookParams.GeneratedDistanceFieldResolutionScale; + SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; + + // RAW MESH CHECKS + + // TODO: Check not needed w/ FMeshDesc + // This is required due to the impeding deprecation of FRawMesh + // If we dont update this UE4 will crash upon deleting an asset. + //SrcModel->StaticMeshOwner = FoundStaticMesh; + + // Store the new MeshDescription + FoundStaticMesh->CommitMeshDescription(LODIndex); + //Set the Imported version before calling the build + FoundStaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; + + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = GetLODSCreensizeForSplit(SplitGroupName); + if (screensize >= 0.0f) + { + // Only apply the LOD screensize if it's valid + SrcModel->ScreenSize = screensize; + //FoundStaticMesh->GetSourceModel(LodIndex).ScreenSize = screensize; + FoundStaticMesh->bAutoComputeLODScreenSize = false; + } + + // SET STATIC MESH GENERATION PARAM + // HANDLE COLLIDERS + // REMOVE OLD COLLIDERS + // CUSTOM BAKE NAME OVERRIDE + + // UPDATE UPROPERTY ATTRIBS + // Update property attributes on the SM + TArray PropertyAttributes; + if (GetGenericPropertiesAttributes( + HGPO.GeoId, HGPO.PartId, + AllSplitFirstValidVertexIndex[SplitGroupName], + AllSplitFirstValidPrimIndex[SplitGroupName], + PropertyAttributes)) + { + UpdateGenericPropertiesAttributes( + FoundStaticMesh, PropertyAttributes); + } + + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray TileValues; + if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + // Notify that we created a new Static Mesh if needed + if(bNewStaticMeshCreated) + FAssetRegistryModule::AssetCreated(FoundStaticMesh); + + // Add the Static mesh to the output maps and the build map if we haven't already + if (FoundOutputObject) + { + FoundOutputObject->OutputObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = false; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + + StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished MD in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Look if we only have colliders + // If we do, we'll allow attaching sockets to the collider meshes + bool bCollidersOnly = true; + for (auto& Current : StaticMeshToBuild) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) + { + bCollidersOnly = false; + break; + } + } + + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + for (auto& Current : StaticMeshToBuild) + { + UStaticMesh* SM = Current.Value; + if (!SM || SM->IsPendingKill()) + continue; + + UBodySetup * BodySetup = SM->BodySetup; + if (!BodySetup) + { + SM->CreateBodySetup(); + BodySetup = SM->BodySetup; + } + + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + + // Handle the Static Mesh's colliders + if (BodySetup && !BodySetup->IsPendingKill()) + { + // Make sure rendering is done - so we are not changing data being used by collision drawing. + FlushRenderingCommands(); + + // Clean up old colliders from a previous cook + BodySetup->Modify(); + BodySetup->RemoveSimpleCollision(); + // Create new GUID + BodySetup->InvalidatePhysicsData(); + + FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; + FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); + if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) + { + BodySetup->AddCollisionFrom(*CurrentAggColl); + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; + } + + // Moved RefreshCollisionChange to after the SM->Build call + // RefreshCollisionChange(*SM); + SM->bCustomizedCollision = true; + + // See if we need to enable collisions on the whole mesh + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + // Complex collider, enable collisions for this static mesh. + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + else + { + // TODO + // if the LODForCollision uproperty attribute is set, we need to activate complex collision + // on the static mesh for that lod to be picked up properly as a collider + if ( FHoudiniEngineUtils::HapiCheckAttributeExists( HGPO.GeoId, HGPO.PartId, + "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) + { + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + } + } + + // Add the Sockets to the StaticMesh + // We only add them to the main geo, or to the colliders if we only generate colliders + bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; + if (bAddSocket) + { + if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); + } + } + + // BUILD the Static Mesh + // bSilent doesnt add the Build Errors... + double build_start = FPlatformTime::Seconds(); + TArray SMBuildErrors; + SM->Build(true, &SMBuildErrors); + double build_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); + + // TODO: copied the content of RefreshCollision below and commented out CreateNavCollision + // it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, + // and can be expensive depending on the vert/poly count of the mesh + // TODO: also moved this to after the call to Build, since Build updates the mesh's + // physics state (calling this before Build when rebuilding an existing high poly mesh as + // low poly mesh, incurs quite a performance hit. This is likely due to processing of physics + // meshes with high vert/poly count before the Build + // RefreshCollisionChange(*SM); + { + // SM->CreateNavCollision(/*bIsUpdate=*/true); + + for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + { + UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); + if (StaticMeshComponent->GetStaticMesh() == SM) + { + // it needs to recreate IF it already has been created + if (StaticMeshComponent->IsPhysicsStateCreated()) + { + StaticMeshComponent->RecreatePhysicsState(); + } + } + } + + FEditorSupportDelegates::RedrawAllViewports.Broadcast(); + } + + SM->GetOnMeshChanged().Broadcast(); + /* + // Try to find the outer package so we can dirty it up + if (SM->GetOuter()) + { + SM->GetOuter()->MarkPackageDirty(); + } + else + { + SM->MarkPackageDirty(); + } + */ + + UPackage* MeshPackage = SM->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + /* + // DPT: deactivated auto saving mesh/material package + // only dirty for now, as we'll save them when saving the world. + TArray PackageToSave; + PackageToSave.Add(MeshPackage); + + // Save the created package + FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); + */ + } + } + + // TODO: Still necessary ? SM->Build should actually update the navmesh... + // TODO: Commented out for now, since it appears that the content of the loop is + // already called in UStaticMesh::BuildInternal and UStaticMesh::PostBuildInternal + //// Now that all the meshes are built and their collisions meshes and primitives updated, + //// we need to update their pre-built navigation collision used by the navmesh + //for (auto& Iter : OutputObjects) + //{ + // UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); + // if (!StaticMesh || StaticMesh->IsPendingKill()) + // continue; + + // UBodySetup * BodySetup = StaticMesh->BodySetup; + // if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) + // { + // // Unreal caches the Navigation Collision and never updates it for StaticMeshes, + // // so we need to manually flush and recreate the data to have proper navigation collision + // // TODO: Is this still required? These two functions are called by + // // UStaticMesh::BuildInternal, which is called by UStaticMesh::Build/BatchBuild + // // BodySetup->InvalidatePhysicsData(); + // // BodySetup->CreatePhysicsMeshes(); + + // // TODO: Is this still required? This function is called by UStaticMesh::CreateNavCollision + // // which is called by the UStaticMesh::PostBuildInternal function, which is called at the + // // end of the build. + // // StaticMesh->NavCollision->Setup(BodySetup); + // } + //} + + double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() executed in %f seconds."), time_end - time_start); + + return true; +} + +bool +FHoudiniMeshTranslator::CreateHoudiniStaticMesh() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh")); + + const double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Determine if there is "main" geo, if not we'll use the first LOD + // as main geo + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + { + bHasMainGeo = true; + break; + } + } + + // Update the part's material's IDS and info now + //UpdatePartFaceMaterialsIfNeeded(); + CreateNeededMaterials(); + + // Check if the materials were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Map of Houdini Material IDs to Unreal Material Indices + TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; + // Map of Houdini Material Attributes to Unreal Material Indices + TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; + + bool MeshMaterialsHaveBeenReset = false; + + double tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); + + // Iterate through all detected split groups we care about and split geometry. + bool bMainGeoOrFirstLODFound = false; + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Per Split")); + + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // We are only interested in the Normal/main geo and visible colliders + if (SplitType != EHoudiniSplitType::Normal && + SplitType != EHoudiniSplitType::LOD && + SplitType != EHoudiniSplitType::RenderedComplexCollider && + SplitType != EHoudiniSplitType::RenderedSimpleCollider && + SplitType != EHoudiniSplitType::RenderedUCXCollider) + { + continue; + } + + // We only use LOD if there is no Normal geo + if (SplitType == EHoudiniSplitType::Normal) + { + bMainGeoOrFirstLODFound = true; + HOUDINI_LOG_MESSAGE(TEXT("Found Normal geo for mesh.")); + } + else if (SplitType == EHoudiniSplitType::LOD) + { + if (bHasMainGeo) + { + HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since the mesh has Normal geo.")); + continue; + } + else if (bMainGeoOrFirstLODFound) + { + HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since we have already processed the first LOD.")); + continue; + } + else + { + bMainGeoOrFirstLODFound = true; + HOUDINI_LOG_MESSAGE(TEXT("Mesh does not have Normal geo, found first LOD.")); + } + } + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing DM from a previous cook + UHoudiniStaticMesh* FoundStaticMesh = FindExistingHoudiniStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing dynamic mesh, create a new one + FoundStaticMesh = CreateNewHoudiniStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + } + + if (!FoundOutputObject) + { + // If we couldnt find a previous output object, create a new one + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + } + FoundOutputObject->bProxyIsCurrent = true; + + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + if (bRebuildStaticMesh) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build/Rebuild UHoudiniStaticMesh")); + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" + // So that IndicesMapper[ oldIndex ] => newIndex + TArray IndicesMapper; + IndicesMapper.Init(-1, SplitVertexList.Num()); + int32 CurrentMapperIndex = 0; + + // NeededVertices: + // Array containing the old index of the needed vertices for the current split + // NeededVertices[ newIndex ] => oldIndex + TArray< int32 > NeededVertices; + NeededVertices.Reserve(SplitVertexList.Num() / 3); + TArray< int32 > TriangleIndices; + TriangleIndices.Reserve(SplitVertexList.Num()); + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build IndicesMapper and NeededVertices")); + + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) + || !IndicesMapper.IsValidIndex(WedgeIndices[1]) + || !IndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (IndicesMapper[WedgeIndices[i]] < 0) + { + // This old index has not yet been "converted" to a new index + NeededVertices.Add(WedgeIndices[i]); + IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; + CurrentMapperIndex++; + } + + // Replace the old index with the new one + WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; + } + + // Flip wedge indices to fix the winding order. + TriangleIndices.Add(WedgeIndices[0]); + TriangleIndices.Add(WedgeIndices[2]); + TriangleIndices.Add(WedgeIndices[1]); + + ValidVertexId += 3; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's normal if needed + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + // Check that the number of normal we retrieved is correct + int32 NormalCount = SplitNormals.Num() / 3; + if (NormalCount < 0 || NormalCount < NeededVertices.Num()) + { + // Ignore normals + NormalCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENTS + //--------------------------------------------------------------------------------------------------------------------- + + TArray< float > SplitTangentU; + TArray< float > SplitTangentV; + int32 TangentUCount = 0; + int32 TangentVCount = 0; + // No need to read the tangents if we want unreal to recompute them after + // TODO: Add runtime setting check! + //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; + bool bReadTangents = true; + bool bGenerateTangents = false; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + + // Check that the number of tangents read matches the number of normals + TangentUCount = SplitTangentU.Num() / 3; + TangentVCount = SplitTangentV.Num() / 3; + if (TangentUCount != NormalCount || TangentVCount != NormalCount) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh: Generate tangents due to count mismatch (# U Tangents = %d; # V Tangents = %d; # Normals = %d)"), TangentUCount, TangentVCount, NormalCount); + bGenerateTangents = true; + } + + /* + // TODO: Add settings check! + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + */ + } + + //--------------------------------------------------------------------------------------------------------------------- + // VERTEX COLORS AND ALPHAS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's colors if needed + UpdatePartColorsIfNeeded(); + + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract this part's alpha values if needed + UpdatePartAlphasIfNeeded(); + + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + + const int32 ColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; + const bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && ColorsCount > 0; + const bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == ColorsCount); + + //--------------------------------------------------------------------------------------------------------------------- + // UVS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's UV sets if needed + UpdatePartUVSetsIfNeeded(); + + // See if we need to transfer uv point attributes to vertex attributes. + int32 NumUVLayers = 0; + TArray> SplitUVSets; + SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + if (SplitUVSets[TexCoordIdx].Num() > 0) + { + NumUVLayers++; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL ATTRIBUTE OVERRIDES + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: These are actually per faces, not per vertices... + // Need to update!! + UpdatePartFaceMaterialOverridesIfNeeded(); + + // + // Initialize mesh + // + const int32 NumVertexPositions = NeededVertices.Num(); + const int32 NumTriangles = TriangleIndices.Num() / 3; + const bool bHasPerFaceMaterials = PartFaceMaterialOverrides.Num() > 0 || (PartUniqueMaterialIds.Num() > 0 && !bOnlyOneFaceMaterial); + + FoundStaticMesh->Initialize( + NumVertexPositions, + NumTriangles, + NumUVLayers, // NumUVLayers + 0, // InitialNumStaticMaterials + NormalCount > 0, // HasNormals + NormalCount > 0 && bReadTangents, // HasTangents + bSplitColorValid, // HasColors + bHasPerFaceMaterials // HasPerFaceMaterials + ); + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + UpdatePartPositionIfNeeded(); + + // + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Vertex Positions")); + + for (int32 VertexPositionIdx = 0; VertexPositionIdx < NumVertexPositions; ++VertexPositionIdx) + //ParallelFor(NumVertexPositions, [&](uint32 VertexPositionIdx) + { + int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; + if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // We need to swap Z and Y coordinate here, and convert from m to cm. + FoundStaticMesh->SetVertexPosition(VertexPositionIdx, FVector( + PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION, + PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION, + PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION + )); + }//); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACES / TRIS + // Now set Normals, UVs and Colors on mesh points and AttributeSet + //--------------------------------------------------------------------------------------------------------------------- + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Triangle Indices & Per Vertex Instance Attribute Values")); + + // Now add the triangles to the mesh + for (int32 TriangleIdx = 0; TriangleIdx < NumTriangles; ++TriangleIdx) + // ParallelFor(NumTriangles, [&](uint32 TriangleIdx) + { + + const int32 TriVertIdx0 = TriangleIdx * 3; + FoundStaticMesh->SetTriangleVertexIndices(TriangleIdx, FIntVector( + TriangleIndices[TriVertIdx0 + 0], + TriangleIndices[TriVertIdx0 + 1], + TriangleIndices[TriVertIdx0 + 2] + )); + + const int32 TriWindingIndex[3] = { 0, 2, 1 }; + if (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)) + { + // Flip Z and Y coordinate for normal, but don't scale + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + const FVector Normal( + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] + ); + + FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); + + if (bReadTangents) + { + FVector TangentU, TangentV; + if (bGenerateTangents) + { + // Generate the tangents if needed + Normal.FindBestAxisVectors(TangentU, TangentV); + } + else + { + // Transfer the tangents from Houdini + TangentU.X = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; + TangentU.Y = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; + TangentU.Z = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; + + TangentU.X = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; + TangentU.Y = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; + TangentU.Z = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; + } + + FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); + FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + } + } + } + + if (bSplitColorValid && SplitColors.IsValidIndex(TriVertIdx0 * AttribInfoColors.tupleSize + 3 * AttribInfoColors.tupleSize - 1)) + { + FLinearColor VertexLinearColor; + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + VertexLinearColor.R = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 0], 0.0f, 1.0f); + VertexLinearColor.G = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 1], 0.0f, 1.0f); + VertexLinearColor.B = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 2], 0.0f, 1.0f); + + if (bSplitAlphaValid) + { + VertexLinearColor.A = FMath::Clamp(SplitAlphas[TriVertIdx0 + ElementIdx], 0.0f, 1.0f); + } + else if (AttribInfoColors.tupleSize >= 4) + { + VertexLinearColor.A = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 3], 0.0f, 1.0f); + } + else + { + VertexLinearColor.A = 1.0f; + } + const FColor VertexColor = VertexLinearColor.ToFColor(false); + FoundStaticMesh->SetTriangleVertexColor(TriangleIdx, TriWindingIndex[ElementIdx], VertexColor); + } + } + + if (NumUVLayers > 0) + { + // Dynamic mesh supports only 1 UV layer on the mesh it self. So we set the first layer + // on the mesh itself only, and we set all layers on the AttributeSet + for (int32 TexCoordIdx = 0; TexCoordIdx < NumUVLayers; ++TexCoordIdx) + { + const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; + if (SplitUVs.IsValidIndex(TriVertIdx0 * 2 + 3 * 2 - 1)) + { + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + const int32 UVIdx = TriVertIdx0 * 2 + ElementIdx * 2; + // We need to flip V coordinate when it's coming from HAPI. + const FVector2D UV(SplitUVs[UVIdx + 0], 1.0f - SplitUVs[UVIdx + 1]); + // Set the UV on the vertex instance in the UVLayer + FoundStaticMesh->SetTriangleVertexUV(TriangleIdx, TriWindingIndex[ElementIdx], TexCoordIdx, UV); + } + } + } + } + }//); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIALS / FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // Get face indices for this split. + TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; + + // Process material overrides first + if (PartFaceMaterialOverrides.Num() > 0) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Material Overrides")); + + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + continue; + + const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); + int32 CurrentFaceMaterialIdx = 0; + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Try to locate the corresponding material interface + UMaterialInterface * MaterialInterface = nullptr; + + // Start by looking in our assignment map + auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface && !MaterialName.IsEmpty()) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add this material to the map + CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + } + else + { + // The Attribute Material and its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + CurrentFaceMaterialIdx = *FoundUnrealMatIndex; + } + else + { + // If everything fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Add the material to the mesh + CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + } + } + } + + // Update the Face Material on the mesh + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); + } + } + else if (PartUniqueMaterialIds.Num() > 0) + { + // The part has houdini materials + if (bOnlyOneFaceMaterial) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Single Material")); + + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + } + else + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Materials")); + + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); + continue; + } + + UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Add the material to the mesh + int32 UnrealMatIndex = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + + // Update the face index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); + } + } + } + else + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Default Material")); + + // No materials were found, we need to use default Houdini material. + int32 SplitFaceCount = SplitFaceIndices.Num(); + + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + } + + //// Update property attributes on the mesh + //TArray PropertyAttributes; + //if (GetGenericPropertiesAttributes( + // HGPO.GeoId, HGPO.PartId, + // AllSplitFirstValidVertexIndex[SplitGroupName], + // AllSplitFirstValidPrimIndex[SplitGroupName], + // PropertyAttributes)) + //{ + // UpdateGenericPropertiesAttributes( + // FoundStaticMesh, PropertyAttributes); + //} + + FoundStaticMesh->Optimize(); + + //// Try to find the outer package so we can dirty it up + //if (FoundStaticMesh->GetOuter()) + //{ + // FoundStaticMesh->GetOuter()->MarkPackageDirty(); + //} + //else + //{ + // FoundStaticMesh->MarkPackageDirty(); + //} + UPackage *MeshPackage = FoundStaticMesh->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + + /* + // DPT: deactivated auto saving mesh/material package + // only dirty for now, as we'll save them when saving the world. + // Save the created/updated package + FEditorFileUtils::PromptForCheckoutAndSave({ MeshPackage }, false, false); + */ + } + + // Add the Proxy mesh to the output maps + if (FoundOutputObject) + { + FoundOutputObject->ProxyObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = true; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + } + + const double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() executed in %f seconds."), time_end - time_start); + + return true; +} + +bool +FHoudiniMeshTranslator::CreateNeededMaterials() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateNeededMaterials")); + + UpdatePartNeededMaterials(); + + TArray MaterialAndTexturePackages; + FHoudiniMaterialTranslator::CreateHoudiniMaterials( + HGPO.AssetId, PackageParams, + PartUniqueMaterialIds, PartUniqueMaterialInfos, + InputAssignmentMaterials, OutputAssignmentMaterials, + MaterialAndTexturePackages, false, bTreatExistingMaterialsAsUpToDate); + + /* + // Save the created packages if needed + // DPT: deactivated, only dirty for now, as we'll save them when saving the world. + if (MaterialAndTexturePackages.Num() > 0) + FEditorFileUtils::PromptForCheckoutAndSave(MaterialAndTexturePackages, true, false); + */ + + if (bMaterialOverrideNeedsCreateInstance && PartFaceMaterialOverrides.Num() > 0) + { + // Map containing unique face materials override attribute + // and their first valid prim index + // We create only one material instance per attribute + TMap UniqueFaceMaterialOverrides; + for (int FaceIdx = 0; FaceIdx < PartFaceMaterialOverrides.Num(); FaceIdx++) + { + FString MatOverrideAttr = PartFaceMaterialOverrides[FaceIdx]; + if (UniqueFaceMaterialOverrides.Contains(MatOverrideAttr)) + continue; + + // Add the material override and face index to the map + UniqueFaceMaterialOverrides.Add(MatOverrideAttr, FaceIdx); + } + + FHoudiniMaterialTranslator::CreateMaterialInstances( + HGPO, PackageParams, + UniqueFaceMaterialOverrides, MaterialAndTexturePackages, + InputAssignmentMaterials, OutputAssignmentMaterials, + false); + } + + return true; +} + +FString +FHoudiniMeshTranslator::GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType) +{ + FString MeshIdentifier = TEXT(""); + switch (InSplitType) + { + case EHoudiniSplitType::Normal: + case EHoudiniSplitType::LOD: + case EHoudiniSplitType::InvisibleUCXCollider: + case EHoudiniSplitType::InvisibleSimpleCollider: + // LODs and Invisible simple colliders use the main mesh + MeshIdentifier = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + break; + + case EHoudiniSplitType::InvisibleComplexCollider: + case EHoudiniSplitType::RenderedComplexCollider: + case EHoudiniSplitType::RenderedUCXCollider: + case EHoudiniSplitType::RenderedSimpleCollider: + // Rendered colliders or invisible complex colliders have their own static mesh + MeshIdentifier = InSplitName; + break; + + default: + break; + } + + return MeshIdentifier; +} + +UStaticMesh* +FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + // See if we already have an input object for that output identifier + FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); + UStaticMesh* FoundStaticMesh = nullptr; + if (FoundOutputObjectPtr) + { + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + FoundStaticMesh = nullptr; + } + + if (!FoundStaticMesh) + { + // No input object matching this identifier, see if we have created an output object that matches + FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); + if (!FoundOutputObjectPtr) + return nullptr; + + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + return nullptr; + } + + if (FoundStaticMesh) + { + UObject* OuterMost = FoundStaticMesh->GetOutermostObject(); + if (OuterMost->IsA()) + { + // The Outermost for this static mesh is a level + // This is likely a SM created by V1, and we should not reuse it. + // This will force the plugin to recreate a "proper" SM in the temp folder. + FoundStaticMesh->MarkPendingKill(); + FoundStaticMesh = nullptr; + } + } + + return FoundStaticMesh; +} + +UHoudiniStaticMesh* +FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + // See if we already have an input object for that output identifier + FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); + UHoudiniStaticMesh* FoundStaticMesh = nullptr; + if (FoundOutputObjectPtr) + { + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + FoundStaticMesh = nullptr; + } + + if (!FoundStaticMesh) + { + // No input object matching this identifier, see if we have created an output object that matches + FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); + if (!FoundOutputObjectPtr) + return nullptr; + + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + return nullptr; + } + + return FoundStaticMesh; +} + +EHoudiniSplitType +FHoudiniMeshTranslator::GetSplitTypeFromSplitName(const FString& InSplitName) +{ + const FString MainGroup = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + if (InSplitName.StartsWith(MainGroup, ESearchCase::IgnoreCase)) + return EHoudiniSplitType::Normal; + + const FString LODGroupPrefix = HAPI_UNREAL_GROUP_LOD_PREFIX; + if (InSplitName.StartsWith(LODGroupPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::LOD; + } + + const FString RenderedCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; + if (InSplitName.StartsWith(RenderedCollisionPrefix, ESearchCase::IgnoreCase)) + { + // Rendered colliders + // See if it is a simple/ucx/complex + const FString RenderedUCXCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX; + const FString RenderedSimpleCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(RenderedUCXCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::RenderedUCXCollider; + } + else if (InSplitName.StartsWith(RenderedSimpleCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::RenderedSimpleCollider; + } + else + { + return EHoudiniSplitType::RenderedComplexCollider; + } + } + + const FString InvisibleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(InvisibleCollisionPrefix, ESearchCase::IgnoreCase)) + { + // Invisible colliders + // See if it is a simple/ucx/complex + const FString InvisibleUCXCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX; + const FString InvisibleSimpleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(InvisibleUCXCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::InvisibleUCXCollider; + } + else if (InSplitName.StartsWith(InvisibleSimpleCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::InvisibleSimpleCollider; + } + else + { + return EHoudiniSplitType::InvisibleComplexCollider; + } + } + + // ? + return EHoudiniSplitType::Invalid; + //return EHoudiniSplitType::Normal; +} + +bool +FHoudiniMeshTranslator::AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) +{ + // Get the vertex indices for the split group + TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; + + // We're only interested in unique vertices + TArray UniqueVertexIndexes; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + UniqueVertexIndexes.AddUnique(Index); + } + + // Extract the collision geo's vertices + TArray< FVector > VertexArray; + VertexArray.SetNum(UniqueVertexIndexes.Num()); + for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) + { + int32 VertexIndex = UniqueVertexIndexes[Idx]; + if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) + continue; + + VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + +#if WITH_EDITOR + // Do we want to create multiple convex hulls? + bool bDoMultiHullDecomp = false; + if (SplitGroupName.Contains(TEXT("ucx_multi"), ESearchCase::IgnoreCase)) + bDoMultiHullDecomp = true; + + uint32 HullCount = 8; + int32 MaxHullVerts = 16; + if (bDoMultiHullDecomp) + { + // TODO: + // Look for extra attributes for the decomposition parameters? (HullCount/MaxHullVerts) + } + + if (bDoMultiHullDecomp && (VertexArray.Num() >= 3 || UniqueVertexIndexes.Num() >= 3)) + { + // creating multiple convex hull collision + // ... this might take a while + + // We're only interested in the valid indices! + TArray Indices; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + Indices.Add(Index); + } + + // But we need all the positions as vertex + TArray< FVector > Vertices; + Vertices.SetNum(PartPositions.Num() / 3); + + for (int32 Idx = 0; Idx < Vertices.Num(); Idx++) + { + Vertices[Idx].X = PartPositions[Idx * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[Idx].Y = PartPositions[Idx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[Idx].Z = PartPositions[Idx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + // We are using Unreal's DecomposeMeshToHulls() + // We need a BodySetup so create a fake/transient one + UBodySetup* BodySetup = NewObject(); + + // Run actual util to do the work (if we have some valid input) + DecomposeMeshToHulls(BodySetup, Vertices, Indices, HullCount, MaxHullVerts); + + // If we succeed, return here + // If not, keep going and we'll try to do a single hull decomposition + if (BodySetup->AggGeom.ConvexElems.Num() > 0) + { + // Copy the convex elem to our aggregate + for (int32 n = 0; n < BodySetup->AggGeom.ConvexElems.Num(); n++) + AggCollisions.ConvexElems.Add(BodySetup->AggGeom.ConvexElems[n]); + + return true; + } + } +#endif + + // Creating a single Convex collision + FKConvexElem ConvexCollision; + ConvexCollision.VertexData = VertexArray; + ConvexCollision.UpdateElemBox(); + + AggCollisions.ConvexElems.Add(ConvexCollision); + + return true; +} + +bool +FHoudiniMeshTranslator::AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) +{ + // Get the vertex indices for the split group + TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; + + // We're only interested in unique vertices + TArray UniqueVertexIndexes; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + UniqueVertexIndexes.AddUnique(Index); + } + + // Extract the collision geo's vertices + TArray< FVector > VertexArray; + VertexArray.SetNum(UniqueVertexIndexes.Num()); + for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) + { + int32 VertexIndex = UniqueVertexIndexes[Idx]; + if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) + continue; + + VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + int32 NewColliders = 0; + if (SplitGroupName.Contains("Box")) + { + NewColliders = FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(VertexArray, AggCollisions); + } + else if (SplitGroupName.Contains("Sphere")) + { + NewColliders = FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(VertexArray, AggCollisions); + } + else if (SplitGroupName.Contains("Capsule")) + { + NewColliders = FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(VertexArray, AggCollisions); + } + else + { + // We need to see what type of collision the user wants + // by default, a kdop26 will be created + uint32 NumDirections = 26; + const FVector* Directions = KDopDir26; + if (SplitGroupName.Contains("kdop10X")) + { + NumDirections = 10; + Directions = KDopDir10X; + } + else if (SplitGroupName.Contains("kdop10Y")) + { + NumDirections = 10; + Directions = KDopDir10Y; + } + else if (SplitGroupName.Contains("kdop10Z")) + { + NumDirections = 10; + Directions = KDopDir10Z; + } + else if (SplitGroupName.Contains("kdop18")) + { + NumDirections = 18; + Directions = KDopDir18; + } + + // Converting the directions to a TArray + TArray DirArray; + DirArray.SetNum(NumDirections); + for (uint32 DirectionIndex = 0; DirectionIndex < NumDirections; DirectionIndex++) + { + DirArray[DirectionIndex] = Directions[DirectionIndex]; + } + + NewColliders = FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(VertexArray, DirArray, AggCollisions); + } + + return (NewColliders > 0); +} + +int32 +FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData) +{ + return FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, OutVertexData); +} + +/* +int32 +FHoudiniMeshTranslator::GetSplitNormals( + const TArray& InSplitVertexList, TArray& OutNormals) +{ + // Extract the normals + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutNormals.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; + OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; + OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; + + OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; + OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; + OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; + + OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; + OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; + OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; + } + + return WedgeCount; +} + +int32 +FHoudiniMeshTranslator::GetSplitUVs( + const TArray& InSplitVertexList, TArray& OutUVs) +{ + // Extract the normals + UpdatePartUVSetsIfNeeded(); + + // Get the normals for this split + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutNormals.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; + OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; + OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; + + OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; + OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; + OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; + + OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; + OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; + OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; + } + + return WedgeCount; +} + + +int32 +FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutData, + const float& ScaleFactor) +{ + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutData.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; + OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; + + OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; + OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; + + OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; + OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; + } + + return WedgeCount; +} + +int32 +FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutData, + const float& ScaleFactor) +{ + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutData.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; + OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; + + OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; + OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; + + OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; + OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; + } + + return WedgeCount; +} +*/ + + +template +int32 FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::TransferPartAttributesToSplit")); + + if (!InAttribInfo.exists || InAttribInfo.tupleSize <= 0) + return 0; + + if (InData.Num() <= 0) + return 0; + + int32 ValidWedgeCount = 0; + + // Future optimization - see if we can do direct vertex transfer. + int32 WedgeCount = InVertexList.Num(); + int32 LastValidWedgeIdx = 0; + if (InAttribInfo.owner == HAPI_ATTROWNER_POINT) + { + // Point attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + int32 VertexIdx = InVertexList[WedgeIdx]; + if (VertexIdx < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[VertexIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_VERTEX) + { + // Vertex attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[WedgeIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_PRIM) + { + // Primitive attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 PrimIdx = WedgeIdx / 3; + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[PrimIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_DETAIL) + { + // Detail attribute transfer + // We have one value to copy for all output split vertices + // if the attribute is a single value (not a tuple) + // then we can simply use the array init function instead of looping + if (InAttribInfo.tupleSize == 1) + { + OutVertexData.Init(InData[0], WedgeCount); + } + else + { + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + } + else + { + // Invalid attribute owner, shouldn't happen + check(false); + } + + OutVertexData.SetNumZeroed(ValidWedgeCount * InAttribInfo.tupleSize); + + return ValidWedgeCount; +} + +float +FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) +{ + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = -1.0f; + + // Start by looking at the lod_screensize primitive attribute + bool bAttribValid = false; + UpdatePartLODScreensizeIfNeeded(); + + if (PartLODScreensize.Num() > 0) + { + // use the "lod_screensize" primitive attribute + int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + if (PartLODScreensize.IsValidIndex(FirstValidPrimIndex)) + screensize = PartLODScreensize[FirstValidPrimIndex]; + } + + if (screensize >= 0.0f) + { + // We couldn't find the primitive attribute, look for a "lodX_screensize" detail attribute + FString LODAttributeName = SplitGroupName + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX; + + TArray LODScreenSizes; + HAPI_AttributeInfo AttribInfoScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); + + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*LODAttributeName), + AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_DETAIL); + + if (AttribInfoScreenSize.exists && LODScreenSizes.Num() > 0) + { + screensize = LODScreenSizes[0]; + } + } + + if (screensize >= 0.0f) + { + // finally, look for a potential uproperty style attribute + // aka, "unreal_uproperty_screensize" + TArray LODScreenSizes; + HAPI_AttributeInfo AttribInfoScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); + + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, "unreal_uproperty_screensize", + AttribInfoScreenSize, LODScreenSizes); + + if (AttribInfoScreenSize.exists) + { + if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_DETAIL && LODScreenSizes.Num() > 0) + { + screensize = LODScreenSizes[0]; + } + else if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_PRIM) + { + int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + if (LODScreenSizes.IsValidIndex(FirstValidPrimIndex)) + screensize = LODScreenSizes[FirstValidPrimIndex]; + } + } + } + + // Make sure the screensize is in percent, so if its above 1, divide by 100 + if (screensize > 1.0f) + screensize /= 100.0f; + + return screensize; +} + +int32 +FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + // Calculate bounding Box. + FVector Center, Extents; + FVector unitVec = FVector::OneVector;// bs->BuildScale3D; + CalcBoundingBox(InPositionArray, Center, Extents, unitVec); + + FKBoxElem BoxElem; + BoxElem.Center = Center; + BoxElem.X = Extents.X * 2.0f; + BoxElem.Y = Extents.Y * 2.0f; + BoxElem.Z = Extents.Z * 2.0f; + OutAggregateCollisions.BoxElems.Add(BoxElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FBox Box(ForceInit); + for (const FVector& CurPos : PositionArray) + { + Box += CurPos; + } + Box.GetCenterAndExtents(Center, Extents); +} + +int32 +FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FSphere bSphere, bSphere2, bestSphere; + FVector unitVec = FVector::OneVector; + + // Calculate bounding sphere. + CalcBoundingSphere(InPositionArray, bSphere, unitVec); + CalcBoundingSphere2(InPositionArray, bSphere2, unitVec); + + if (bSphere.W < bSphere2.W) + bestSphere = bSphere; + else + bestSphere = bSphere2; + + // Don't use if radius is zero. + if (bestSphere.W <= 0.f) + { + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Sphere collider.")); + return 0; + } + + FKSphereElem SphereElem; + SphereElem.Center = bestSphere.Center; + SphereElem.Radius = bestSphere.W; + OutAggregateCollisions.SphereElems.Add(SphereElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + if (PositionArray.Num() == 0) + return; + + FBox Box; + FVector MinIx[3]; + FVector MaxIx[3]; + + bool bFirstVertex = true; + for (const FVector& CurPosition : PositionArray) + { + FVector p = CurPosition * LimitVec; + if (bFirstVertex) + { + // First, find AABB, remembering furthest points in each dir. + Box.Min = p; + Box.Max = Box.Min; + + MinIx[0] = CurPosition; + MinIx[1] = CurPosition; + MinIx[2] = CurPosition; + + MaxIx[0] = CurPosition; + MaxIx[1] = CurPosition; + MaxIx[2] = CurPosition; + bFirstVertex = false; + continue; + } + + // X // + if (p.X < Box.Min.X) + { + Box.Min.X = p.X; + MinIx[0] = CurPosition; + } + else if (p.X > Box.Max.X) + { + Box.Max.X = p.X; + MaxIx[0] = CurPosition; + } + + // Y // + if (p.Y < Box.Min.Y) + { + Box.Min.Y = p.Y; + MinIx[1] = CurPosition; + } + else if (p.Y > Box.Max.Y) + { + Box.Max.Y = p.Y; + MaxIx[1] = CurPosition; + } + + // Z // + if (p.Z < Box.Min.Z) + { + Box.Min.Z = p.Z; + MinIx[2] = CurPosition; + } + else if (p.Z > Box.Max.Z) + { + Box.Max.Z = p.Z; + MaxIx[2] = CurPosition; + } + } + + const FVector Extremes[3] = { (MaxIx[0] - MinIx[0]) * LimitVec, + (MaxIx[1] - MinIx[1]) * LimitVec, + (MaxIx[2] - MinIx[2]) * LimitVec }; + + // Now find extreme points furthest apart, and initial center and radius of sphere. + float d2 = 0.f; + for (int32 i = 0; i < 3; i++) + { + const float tmpd2 = Extremes[i].SizeSquared(); + if (tmpd2 > d2) + { + d2 = tmpd2; + sphere.Center = (MinIx[i] + (0.5f * Extremes[i])) * LimitVec; + sphere.W = 0.f; + } + } + + const FVector Extents = FVector(Extremes[0].X, Extremes[1].Y, Extremes[2].Z); + + // radius and radius squared + float r = 0.5f * Extents.GetMax(); + float r2 = FMath::Square(r); + + // Now check each point lies within this sphere. If not - expand it a bit. + for (const FVector& curPos : PositionArray) + { + const FVector cToP = (curPos * LimitVec) - sphere.Center; + + const float pr2 = cToP.SizeSquared(); + + // If this point is outside our current bounding sphere's radius + if (pr2 > r2) + { + // ..expand radius just enough to include this point. + const float pr = FMath::Sqrt(pr2); + r = 0.5f * (r + pr); + r2 = FMath::Square(r); + + sphere.Center += ((pr - r) / pr * cToP); + } + } + + sphere.W = r; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FVector Center, Extents; + CalcBoundingBox(PositionArray, Center, Extents, LimitVec); + + sphere.Center = Center; + sphere.W = 0.0f; + + for (const FVector& curPos : PositionArray) + { + float Dist = FVector::DistSquared(curPos * LimitVec, sphere.Center); + if (Dist > sphere.W) + sphere.W = Dist; + } + sphere.W = FMath::Sqrt(sphere.W); +} + +int32 +FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FSphere sphere; + float length; + FRotator rotation; + FVector unitVec = FVector::OneVector; + + // Calculate bounding sphyl. + CalcBoundingSphyl(InPositionArray, sphere, length, rotation, unitVec); + + // Dont use if radius is zero. + if (sphere.W <= 0.f) + { + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Capsule collider.")); + return 0; + } + + // If height is zero, then a sphere would be better (should we just create one instead?) + if (length <= 0.f) + { + length = SMALL_NUMBER; + } + + FKSphylElem SphylElem; + SphylElem.Center = sphere.Center; + SphylElem.Rotation = rotation; + SphylElem.Radius = sphere.W; + SphylElem.Length = length; + OutAggregateCollisions.SphylElems.Add(SphylElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + if (PositionArray.Num() == 0) + return; + + FVector Center, Extents; + CalcBoundingBox(PositionArray, Center, Extents, LimitVec); + + // @todo sphere.Center could perhaps be adjusted to best fit if model is non-symmetric on it's longest axis + sphere.Center = Center; + + // Work out best axis aligned orientation (longest side) + float Extent = Extents.GetMax(); + if (Extent == Extents.X) + { + rotation = FRotator(90.f, 0.f, 0.f); + Extents.X = 0.0f; + } + else if (Extent == Extents.Y) + { + rotation = FRotator(0.f, 0.f, 90.f); + Extents.Y = 0.0f; + } + else + { + rotation = FRotator(0.f, 0.f, 0.f); + Extents.Z = 0.0f; + } + + // Cleared the largest axis above, remaining determines the radius + float r = Extents.GetMax(); + float r2 = FMath::Square(r); + + // Now check each point lies within this the radius. If not - expand it a bit. + for (const FVector& CurPos : PositionArray) + { + FVector cToP = (CurPos * LimitVec) - sphere.Center; + cToP = rotation.UnrotateVector(cToP); + + const float pr2 = cToP.SizeSquared2D(); // Ignore Z here... + + // If this point is outside our current bounding sphere's radius + if (pr2 > r2) + { + // ..expand radius just enough to include this point. + const float pr = FMath::Sqrt(pr2); + r = 0.5f * (r + pr); + r2 = FMath::Square(r); + } + } + + // The length is the longest side minus the radius. + float hl = FMath::Max(0.0f, Extent - r); + + // Now check each point lies within the length. If not - expand it a bit. + for (const FVector& CurPos : PositionArray) + { + FVector cToP = (CurPos * LimitVec) - sphere.Center; + cToP = rotation.UnrotateVector(cToP); + + // If this point is outside our current bounding sphyl's length + if (FMath::Abs(cToP.Z) > hl) + { + const bool bFlip = (cToP.Z < 0.f ? true : false); + const FVector cOrigin(0.f, 0.f, (bFlip ? -hl : hl)); + + const float pr2 = (cOrigin - cToP).SizeSquared(); + + // If this point is outside our current bounding sphyl's radius + if (pr2 > r2) + { + FVector cPoint; + FMath::SphereDistToLine(cOrigin, r, cToP, (bFlip ? FVector(0.f, 0.f, 1.f) : FVector(0.f, 0.f, -1.f)), cPoint); + + // Don't accept zero as a valid diff when we know it's outside the sphere (saves needless retest on further iterations of like points) + hl += FMath::Max(FMath::Abs(cToP.Z - cPoint.Z), 1.e-6f); + } + } + } + + sphere.W = r; + length = hl * 2.0f; +} + +int32 +FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + const float my_flt_max = 3.402823466e+38F; + + // Do k- specific stuff. + int32 kCount = Dirs.Num(); + + TArray maxDist; + maxDist.Init(-my_flt_max, kCount); + /* + for (int32 i = 0; i < kCount; i++) + maxDist.Add(my_flt_max); + */ + + // Construct temporary UModel for kdop creation. We keep no refs to it, so it can be GC'd. + auto TempModel = NewObject(); + TempModel->Initialize(nullptr, 1); + + // For each vertex, project along each kdop direction, to find the max in that direction. + for (int32 i = 0; i < InPositionArray.Num(); i++) + { + for (int32 j = 0; j < kCount; j++) + { + float dist = InPositionArray[i] | Dirs[j]; + maxDist[j] = FMath::Max(dist, maxDist[j]); + } + } + + // Inflate kdop to ensure it is no degenerate + const float MinSize = 0.1f; + for (int32 i = 0; i < kCount; i++) + { + maxDist[i] += MinSize; + } + + // Now we have the planes of the kdop, we work out the face polygons. + TArray planes; + for (int32 i = 0; i < kCount; i++) + planes.Add(FPlane(Dirs[i], maxDist[i])); + + for (int32 i = 0; i < planes.Num(); i++) + { + FPoly* Polygon = new(TempModel->Polys->Element) FPoly(); + FVector Base, AxisX, AxisY; + + Polygon->Init(); + Polygon->Normal = planes[i]; + Polygon->Normal.FindBestAxisVectors(AxisX, AxisY); + + Base = planes[i] * planes[i].W; + + new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); + + for (int32 j = 0; j < planes.Num(); j++) + { + if (i != j) + { + if (!Polygon->Split(-FVector(planes[j]), planes[j] * planes[j].W)) + { + Polygon->Vertices.Empty(); + break; + } + } + } + + if (Polygon->Vertices.Num() < 3) + { + // If poly resulted in no verts, remove from array + TempModel->Polys->Element.RemoveAt(TempModel->Polys->Element.Num() - 1); + } + else + { + // Other stuff... + Polygon->iLink = i; + Polygon->CalcNormal(1); + } + } + + if (TempModel->Polys->Element.Num() < 4) + { + TempModel = NULL; + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple KDOP collider.")); + return 0; + } + + // Build bounding box. + TempModel->BuildBound(); + + // Build BSP for the brush. + FBSPOps::bspBuild(TempModel, FBSPOps::BSP_Good, 15, 70, 1, 0); + FBSPOps::bspRefresh(TempModel, 1); + FBSPOps::bspBuildBounds(TempModel); + + // Now, create a temporary BodySetup to build the colliders + UBodySetup* TempBS = NewObject(); + TempBS->CreateFromModel(TempModel, false); + + // Copy the convex elements back to our aggregate + int32 NewConvexElems = 0; + if (TempBS && TempBS->AggGeom.ConvexElems.Num() > 0) + { + for (const auto& CurConvexElem : TempBS->AggGeom.ConvexElems) + { + OutAggregateCollisions.ConvexElems.Add(CurConvexElem); + NewConvexElems++; + } + } + + return NewConvexElems; +} + + +bool +FHoudiniMeshTranslator::GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, + const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, + TArray& OutPropertyAttributes) +{ + // List all the generic property detail attributes ... + int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + + // .. then the primitive property attributes for the given prim + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); + + // .. then finally, point uprop attributes for the given vert + // TODO: !! get the correct Index here? + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidVertexIndex); + + return FoundCount > 0; +} + +bool +FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (auto CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current Property Attribute + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) + continue; + + // Success! + NumSuccess++; + FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); + } + + return (NumSuccess > 0); +} + + +void +FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO) +{ + PackageParams = InPackageParams; + + if (bUpdateHGPO) + { + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.ObjectId; + PackageParams.PartId = HGPO.ObjectId; + } +} + +bool +FHoudiniMeshTranslator::RemoveAndDestroyComponent(UObject* InComponent) +{ + if (!InComponent || InComponent->IsPendingKill()) + return false; + + USceneComponent* SceneComponent = Cast(InComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + return true; + } + + return false; +} + +UMeshComponent* +FHoudiniMeshTranslator::CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf &InComponentType) +{ + // Create a new SMC as we couldn't find an existing one + USceneComponent* OuterSceneComponent = Cast(InOuterComponent); + UObject * Outer = nullptr; + if (OuterSceneComponent && !OuterSceneComponent->IsPendingKill()) + Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); + + UMeshComponent *MeshComponent = NewObject(Outer, InComponentType, NAME_None, RF_Transactional); + + // Initialize it + MeshComponent->SetVisibility(true); + //MeshComponent->SetMobility(Mobility); + + // TODO: + // Property propagation: set the new SMC's properties to the HAC's current settings + //CopyComponentPropertiesTo(MeshComponent); + + // Change the creation method so the component is listed in the details panels + MeshComponent->CreationMethod = EComponentCreationMethod::Instance; + + // Attach created static mesh component to our Houdini component. + MeshComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + MeshComponent->OnComponentCreated(); + MeshComponent->RegisterComponent(); + + return MeshComponent; +} + +bool +FHoudiniMeshTranslator::PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh) +{ + UStaticMesh *Mesh = Cast(InMesh); + if (Mesh) + { + InComponent->SetStaticMesh(Mesh); + + return true; + } + + return false; +} + +bool +FHoudiniMeshTranslator::PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh) +{ + UHoudiniStaticMesh *Mesh = Cast(InMesh); + if (Mesh) + { + InComponent->SetMesh(Mesh); + + return true; + } + + return false; +} + +UMeshComponent* +FHoudiniMeshTranslator::CreateOrUpdateMeshComponent( + const UHoudiniOutput *InOutput, + UObject *InOuterComponent, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TSubclassOf& InComponentType, + FHoudiniOutputObject& OutputObject, + FHoudiniGeoPartObject const *& OutFoundHGPO, + bool& bCreated) +{ + bCreated = false; + OutFoundHGPO = nullptr; + + // Find the HGPO that matches this mesh + for (auto& curHGPO : InOutput->HoudiniGeoPartObjects) + { + if (curHGPO.ObjectId != InOutputIdentifier.ObjectId + || curHGPO.GeoId != InOutputIdentifier.GeoId + || curHGPO.PartId != InOutputIdentifier.PartId) + { + continue; + } + + if (InOutputIdentifier.SplitIdentifier.Equals(HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION) + || curHGPO.SplitGroups.Contains(InOutputIdentifier.SplitIdentifier)) + { + OutFoundHGPO = &curHGPO; + } + } + + // No need to create a component for instanced meshes! + if (OutFoundHGPO && OutFoundHGPO->bIsInstanced) + return nullptr; + + bool bIsProxyComponent = InComponentType == UHoudiniStaticMeshComponent::StaticClass(); + + // See if we already have a component for that mesh + UMeshComponent* MeshComponent = nullptr; + if (bIsProxyComponent) + MeshComponent = Cast(OutputObject.ProxyComponent); + else + MeshComponent = Cast(OutputObject.OutputComponent); + + // If there is an existing component, but it is pending kill, then it was likely + // deleted by some other process, such as by the user in the editor, so don't use it + if (!MeshComponent || MeshComponent->IsPendingKill() || !MeshComponent->IsA(InComponentType)) + { + // If the component is not of type InComponentType, or the found component is pending kill, destroy + // the existing component (a new one is then created below) + RemoveAndDestroyComponent(MeshComponent); + MeshComponent = nullptr; + } + + if (!MeshComponent) + { + // Create a new SMC/HSMC as we couldn't find an existing one + MeshComponent = CreateMeshComponent(InOuterComponent, InComponentType); + + if (MeshComponent) + { + // Add to the output object + if (bIsProxyComponent) + OutputObject.ProxyComponent = MeshComponent; + else + OutputObject.OutputComponent = MeshComponent; + + bCreated = true; + } + } + + return MeshComponent; +} + +bool +FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, + TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors) +{ + if (!Socket || Socket->IsPendingKill() || !StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + return false; + + // The actor to assign is stored is the socket's tag + FString ActorString = Socket->Tag; + if (ActorString.IsEmpty()) + return false; + + // The actor to assign are listed after a | + TArray ActorStringArray; + ActorString.ParseIntoArray(ActorStringArray, TEXT("|"), false); + + // The "real" Tag is the first + if (ActorStringArray.Num() > 0) + Socket->Tag = ActorStringArray[0]; + + // We just add a Tag, no Actor + if (ActorStringArray.Num() == 1) + return false; + + // Extract the parsed actor string to split it further + ActorString = ActorStringArray[1]; + + // Converting the string to a string array using delimiters + const TCHAR* Delims[] = { TEXT(","), TEXT(";") }; + ActorString.ParseIntoArray(ActorStringArray, Delims, 2); + + // And try to find the corresponding HoudiniAssetActor in the editor world + // to avoid finding "deleted" assets with the same name + //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); +#if WITH_EDITOR + UWorld* EditorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; + if (!EditorWorld || EditorWorld->IsPendingKill()) + return false; + + // Remove the previous created actors which were attached to this socket + { + for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniCreatedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + continue; + } + + if (CurActor->GetAttachParentSocketName() == Socket->SocketName) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + CurActor->Destroy(); + } + } + } + + // Detach the previous in level actors which was attached to this socket + { + for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniAttachedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniAttachedSocketActors.RemoveAt(Idx); + continue; + } + + if (CurActor->GetAttachParentSocketName() == Socket->SocketName) + { + CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + HoudiniAttachedSocketActors.RemoveAt(Idx); + } + } + } + + auto CreateDefaultActor = [EditorWorld, StaticMeshComponent, Socket, HoudiniCreatedSocketActors]() + { + AActor * CreatedDefaultActor = nullptr; + + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + { + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( + EditorWorld->GetCurrentLevel(), DefaultReferenceSM, false, RF_Transactional, nullptr); + + if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load default mesh.")); + } + else + { + + // Set the default mesh actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : NewActors[0]->GetComponents()) + { + UStaticMeshComponent * CurSMC = Cast(CurComp); + if (CurSMC && !CurSMC->IsPendingKill()) + CurSMC->SetMobility(OutputSMCMobility); + } + + // Set the default mesh actor hidden in game. + NewActors[0]->SetActorHiddenInGame(true); + + Socket->AttachActor(NewActors[0], StaticMeshComponent); + CreatedDefaultActor = NewActors[0]; + //HoudiniCreatedSocketActors.Add(NewActors[0]); + } + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load default mesh.")); + } + + return CreatedDefaultActor; + }; + + bool bUseDefaultActor = true; + // Get from the Houdini runtime setting if use default object when the reference is invalid + // true by default if fail to access HoudiniRuntimeSettings + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bUseDefaultActor = HoudiniRuntimeSettings->bShowDefaultMesh; + } + + if (ActorStringArray.Num() <= 0) + { + if (!bUseDefaultActor) + return true; + + HOUDINI_LOG_WARNING( + TEXT("Output static mesh: Socket '%s' actor is not specified. Spawn a default mesh (hidden in game)."), *(Socket->GetName())); + + AActor * DefaultActor = CreateDefaultActor(); + if (DefaultActor && !DefaultActor->IsPendingKill()) + HoudiniCreatedSocketActors.Add(DefaultActor); + + return true; + } + + // try to find the actor in level first + for (TActorIterator ActorItr(EditorWorld); ActorItr; ++ActorItr) + { + // Same as with the Object Iterator, access the subclass instance with the * or -> operators. + AActor *Actor = *ActorItr; + if (!Actor || Actor->IsPendingKillOrUnreachable()) + continue; + + for (int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++) + { + if (Actor->GetName() != ActorStringArray[StringIdx] + && Actor->GetActorLabel() != ActorStringArray[StringIdx]) + continue; + + // Set the actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : Actor->GetComponents()) + { + UStaticMeshComponent * SMC = Cast(CurComp); + if (SMC && !SMC->IsPendingKill()) + SMC->SetMobility(OutputSMCMobility); + } + + Socket->AttachActor(Actor, StaticMeshComponent); + HoudiniAttachedSocketActors.Add(Actor); + + // Remove the string if the actor is found in the editor level + ActorStringArray.RemoveAt(StringIdx); + break; + } + } + + bool bSuccess = true; + // If some of the actors are not found in the level, try to find them in the content browser. Spawn one if existed + for (int32 Idx = ActorStringArray.Num() - 1; Idx>= 0; --Idx) + { + UObject * Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *ActorStringArray[Idx]); + if (!Obj || Obj->IsPendingKill()) + { + bSuccess = false; + continue; + } + + // Spawn a new actor with the found object + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( + EditorWorld->GetCurrentLevel(), Obj, false, RF_Transactional, nullptr); + + if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + { + bSuccess = false; + continue; + } + + // Set the new actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : NewActors[0]->GetComponents()) + { + UStaticMeshComponent * CurSMC = Cast(CurComp); + if (CurSMC && !CurSMC->IsPendingKill()) + CurSMC->SetMobility(OutputSMCMobility); + } + + Socket->AttachActor(NewActors[0], StaticMeshComponent); + HoudiniCreatedSocketActors.Add(NewActors[0]); + + ActorStringArray.RemoveAt(Idx); + } + + // Failed to find actors in both level and content browser + // Spawn default actors if enabled + if (bUseDefaultActor) + { + for (int32 Idx = ActorStringArray.Num() - 1; Idx >= 0; --Idx) + { + HOUDINI_LOG_WARNING( + TEXT("Output static mesh: Failed to attach '%s' to socket '%s', spawn a default mesh (hidden in game)."), *(ActorStringArray[Idx]), *(Socket->GetName())); + + // If failed to load this object, spawn a default mesh + AActor * CurDefaultActor = CreateDefaultActor(); + if (CurDefaultActor && !CurDefaultActor->IsPendingKill()) + HoudiniCreatedSocketActors.Add(CurDefaultActor); + } + } + + if (ActorStringArray.Num() > 0) + return false; +#endif + + return bSuccess; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h index b9a6da6aa..d6a2e2fc7 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h @@ -1,404 +1,404 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "HoudiniPackageParams.h" -#include "HoudiniAssetComponent.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "PhysicsEngine/AggregateGeom.h" - -//#include "HoudiniMeshTranslator.generated.h" - -class UStaticMesh; -class UStaticMeshSocket; -class UMaterialInterface; -class UMeshComponent; -class UStaticMeshComponent; -class UHoudiniStaticMesh; -class UHoudiniStaticMeshComponent; - -struct FKAggregateGeom; -struct FHoudiniGenericAttribute; - - -UENUM() -enum class EHoudiniSplitType : uint8 -{ - Invalid, - - Normal, - - LOD, - - RenderedComplexCollider, - InvisibleComplexCollider, - - RenderedUCXCollider, - InvisibleUCXCollider, - - RenderedSimpleCollider, - InvisibleSimpleCollider -}; - -struct HOUDINIENGINE_API FHoudiniMeshTranslator -{ - public: - - //----------------------------------------------------------------------------------------------------------------------------- - // HOUDINI TO UNREAL - //----------------------------------------------------------------------------------------------------------------------------- - - // - static bool CreateAllMeshesAndComponentsFromHoudiniOutput( - UHoudiniOutput* InOutput, - const FHoudiniPackageParams& InPackageParams, - EHoudiniStaticMeshMethod InStaticMeshMethod, - UObject* InOuterComponent, - bool bInTreatExistingMaterialsAsUpToDate=false, - bool bInDestroyProxies=false); - - static bool CreateStaticMeshFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& InOutputObjects, - TMap& OutOutputObjects, - TMap& InAssignmentMaterialMap, - TMap& InReplacementMaterialMap, - const bool& InForceRebuild, - EHoudiniStaticMeshMethod InStaticMeshMethod, - bool bInTreatExistingMaterialsAsUpToDate = false); - - static bool CreateOrUpdateAllComponents( - UHoudiniOutput* InOutput, - UObject* InOuterComponent, - TMap& InNewOutputObjects, - bool bInDestroyProxies=false, - bool bInApplyGenericProperties=true); - - - //----------------------------------------------------------------------------------------------------------------------------- - // HELPERS - //----------------------------------------------------------------------------------------------------------------------------- - static EHoudiniSplitType GetSplitTypeFromSplitName(const FString& InSplitName); - - static FString GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType); - - // TODO: Rename me! and template me! float/int/string ? - // TransferPartAttributesToSplitVertices - static int32 TransferRegularPointAttributesToVertices( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData); - - template - static int32 TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutSplitData); - - - //----------------------------------------------------------------------------------------------------------------------------- - // ACCESSORS - //----------------------------------------------------------------------------------------------------------------------------- - - //----------------------------------------------------------------------------------------------------------------------------- - // MUTATORS - //----------------------------------------------------------------------------------------------------------------------------- - void SetHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) { HGPO = InHGPO; }; - void SetOuterComponent(UObject* InOuter) { OuterComponent = InOuter; }; - void SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO = false); - - void SetInputObjects(const TMap& InInputObjects) { InputObjects = InInputObjects; }; - void SetOutputObjects(TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; - - void SetInputAssignmentMaterials(const TMap& InInputMaterials) { InputAssignmentMaterials = InInputMaterials; }; - void SetReplacementMaterials(const TMap& InReplacementMaterials) { ReplacementMaterials = InReplacementMaterials; }; - - //void SetInputObjectProperties(const TMap& InInputObjectProperties) { InputObjectProperties = InInputObjectProperties; }; - //void SetOutputObjectProperties(TMap& InOutputObjectProperties) { OutputObjectProperties = InOutputObjectProperties; }; - - void SetTreatExistingMaterialsAsUpToDate(bool bInTreatExistingMaterialsAsUpToDate) { bTreatExistingMaterialsAsUpToDate = bInTreatExistingMaterialsAsUpToDate; } - - //----------------------------------------------------------------------------------------------------------------------------- - // Helpers - //----------------------------------------------------------------------------------------------------------------------------- - - // Helper functions for generic property attributes - static bool GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes); - - protected: - - // Create a StaticMesh using the MeshDescription format - bool CreateStaticMesh_MeshDescription(); - - // Legacy function using RawMesh for static Mesh creation - bool CreateStaticMesh_RawMesh(); - - // Create a UHoudiniStaticMesh - bool CreateHoudiniStaticMesh(); - - void ResetPartCache(); - - bool UpdatePartVertexList(); - - void SortSplitGroups(); - - bool UpdateSplitsFacesAndIndices(); - - // Update this part's position cache if we haven't already - bool UpdatePartPositionIfNeeded(); - - // Update this part's normal cache if we haven't already - bool UpdatePartNormalsIfNeeded(); - - // Update this part's tangent and binormal caches if we haven't already - bool UpdatePartTangentsIfNeeded(); - - // Update this part's color cache if we haven't already - bool UpdatePartColorsIfNeeded(); - - // Update this part's alpha if we haven't already - bool UpdatePartAlphasIfNeeded(); - - // Update this part's face smoothing values if we haven't already - bool UpdatePartFaceSmoothingIfNeeded(); - - // Update this part's UV sets if we haven't already - bool UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused = false); - - // Update this part;s lightmap resolution cache if we haven't already - bool UpdatePartLightmapResolutionsIfNeeded(); - - // Update this part's lod screensize attribute cache if we haven't already - bool UpdatePartLODScreensizeIfNeeded(); - - // Update th unique materials ids and infos needed for this part using the face materials and overrides - bool UpdatePartNeededMaterials(); - - // Update this part's face material IDs, unique material IDs and material Infos caches if we haven't already - bool UpdatePartFaceMaterialIDsIfNeeded(); - - // Update this part's material overrides cache if we haven't already - bool UpdatePartFaceMaterialOverridesIfNeeded(); - - // Updates and create the material that are needed for this part - bool CreateNeededMaterials(); - - UStaticMesh* CreateNewStaticMesh(const FString& InMeshIdentifierString); - - UStaticMesh* FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); - - UHoudiniStaticMesh* CreateNewHoudiniStaticMesh(const FString& InMeshIdentifierString); - - UHoudiniStaticMesh* FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); - - float GetLODSCreensizeForSplit(const FString& SplitGroupName); - - // Create convex/UCX collider for a split and add to the aggregate - bool AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); - // Create simple colliders for a split and add to the aggregate - bool AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); - - // Helper functions to generate the simple colliders and add them to the aggregate - static int32 GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions); - - // Helper functions for the simple colliders generation - static void CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec); - static void CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); - static void CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); - static void CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec); - - // Helper functions to remove unused/stale components - static bool RemoveAndDestroyComponent(UObject* InComponent); - - // Helper to create a new mesh component - static UMeshComponent* CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf& InComponentType); - - // Helper to update an existing mesh component - static void UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, - const FHoudiniGeoPartObject *InHGPO, TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors, - bool bInApplyGenericProperties=true); - - // Helper to create or update a mesh component for a UStaticMesh or proxy mesh output - static UMeshComponent* CreateOrUpdateMeshComponent( - const UHoudiniOutput* InOutput, - UObject* InOuterComponent, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TSubclassOf& InComponentType, - FHoudiniOutputObject& OutOutputObject, - FHoudiniGeoPartObject const *& OutFoundHGPO, - bool &bCreated); - - // Helper to initialize a UStaticMeshComponent after it was created. - static bool PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh); - - // Helper to initialize a UHoudiniStaticMeshComponent after it was created. - static bool PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh); - - static bool AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, - TArray& HoudiniCreatedSocketActors, TArray& HoudiniAttachedSocketActors); - - protected: - - // Data cache for this translator - - // The HoudiniGeoPartObject we're working on - FHoudiniGeoPartObject HGPO; - - // Outer object for attaching components to - UObject* OuterComponent; - - // Structure that handles cooking/baking package creation parameters - FHoudiniPackageParams PackageParams; - - - // Previous output objects - TMap InputObjects; - - // New Output objects - TMap OutputObjects; - - - // Input Material Map - TMap InputAssignmentMaterials; - // Output Material Map - TMap OutputAssignmentMaterials; - // Input Replacement Materials maps - TMap ReplacementMaterials; - - // Input mesh properties - //TMap InputObjectProperties; - // Output mesh properties - //TMap OutputObjectProperties; - - // Indicates the update is forced - bool ForceRebuild; - - // The generated simple/UCX colliders - TMap AllAggregateCollisions; - - // Names of the groups used for splitting the geometry - TArray AllSplitGroups; - - // Per-split lists of faces - TMap> AllSplitVertexLists; - - // Per-split number of faces - TMap AllSplitVertexCounts; - - // Per-split indices arrays - TMap> AllSplitFaceIndices; - - // Per-split first valid vertex index - TMap AllSplitFirstValidVertexIndex; - - // Per-split first valid prim index - TMap AllSplitFirstValidPrimIndex; - - // Vertex Indices for the part - TArray PartVertexList; - - // Positions - TArray PartPositions; - HAPI_AttributeInfo AttribInfoPositions; - - // Vertex Normals - TArray PartNormals; - HAPI_AttributeInfo AttribInfoNormals; - - // Vertex TangentU - TArray PartTangentU; - HAPI_AttributeInfo AttribInfoTangentU; - - // Vertex TangentV - TArray PartTangentV; - HAPI_AttributeInfo AttribInfoTangentV; - - // Vertex Colors - TArray PartColors; - HAPI_AttributeInfo AttribInfoColors; - - // Vertex Alpha values - TArray PartAlphas; - HAPI_AttributeInfo AttribInfoAlpha; - - // Face Smoothing masks - TArray PartFaceSmoothingMasks; - HAPI_AttributeInfo AttribInfoFaceSmoothingMasks; - - // UVs - TArray> PartUVSets; - TArray AttribInfoUVSets; - - // Lightmap resolution - TArray PartLightMapResolutions; - HAPI_AttributeInfo AttribInfoLightmapResolution; - - // Material IDs per face - TArray PartFaceMaterialIds; - HAPI_AttributeInfo AttribInfoFaceMaterialIds; - // Unique material IDs - TArray PartUniqueMaterialIds; - //TSet PartUniqueMaterialIds; - // Material infos for each unique Material - TArray PartUniqueMaterialInfos; - //TSet PartUniqueMaterialInfos; - // Indicates we only have a single face material - bool bOnlyOneFaceMaterial; - - // Material Overrides per face - TArray PartFaceMaterialOverrides; - HAPI_AttributeInfo AttribInfoFaceMaterialOverrides; - // Indicates that material overides attributes need an instance to be created - bool bMaterialOverrideNeedsCreateInstance; - - // LOD Screensize - TArray PartLODScreensize; - HAPI_AttributeInfo AttribInfoLODScreensize; - - int32 DefaultMeshSmoothing; - - // When building a mesh, if an associated material already exists, treat - // it as up to date, regardless of the MaterialInfo.bHasChanged flag - bool bTreatExistingMaterialsAsUpToDate; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" +#include "HoudiniAssetComponent.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "PhysicsEngine/AggregateGeom.h" + +//#include "HoudiniMeshTranslator.generated.h" + +class UStaticMesh; +class UStaticMeshSocket; +class UMaterialInterface; +class UMeshComponent; +class UStaticMeshComponent; +class UHoudiniStaticMesh; +class UHoudiniStaticMeshComponent; + +struct FKAggregateGeom; +struct FHoudiniGenericAttribute; + + +UENUM() +enum class EHoudiniSplitType : uint8 +{ + Invalid, + + Normal, + + LOD, + + RenderedComplexCollider, + InvisibleComplexCollider, + + RenderedUCXCollider, + InvisibleUCXCollider, + + RenderedSimpleCollider, + InvisibleSimpleCollider +}; + +struct HOUDINIENGINE_API FHoudiniMeshTranslator +{ + public: + + //----------------------------------------------------------------------------------------------------------------------------- + // HOUDINI TO UNREAL + //----------------------------------------------------------------------------------------------------------------------------- + + // + static bool CreateAllMeshesAndComponentsFromHoudiniOutput( + UHoudiniOutput* InOutput, + const FHoudiniPackageParams& InPackageParams, + EHoudiniStaticMeshMethod InStaticMeshMethod, + UObject* InOuterComponent, + bool bInTreatExistingMaterialsAsUpToDate=false, + bool bInDestroyProxies=false); + + static bool CreateStaticMeshFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& InOutputObjects, + TMap& OutOutputObjects, + TMap& InAssignmentMaterialMap, + TMap& InReplacementMaterialMap, + const bool& InForceRebuild, + EHoudiniStaticMeshMethod InStaticMeshMethod, + bool bInTreatExistingMaterialsAsUpToDate = false); + + static bool CreateOrUpdateAllComponents( + UHoudiniOutput* InOutput, + UObject* InOuterComponent, + TMap& InNewOutputObjects, + bool bInDestroyProxies=false, + bool bInApplyGenericProperties=true); + + + //----------------------------------------------------------------------------------------------------------------------------- + // HELPERS + //----------------------------------------------------------------------------------------------------------------------------- + static EHoudiniSplitType GetSplitTypeFromSplitName(const FString& InSplitName); + + static FString GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType); + + // TODO: Rename me! and template me! float/int/string ? + // TransferPartAttributesToSplitVertices + static int32 TransferRegularPointAttributesToVertices( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData); + + template + static int32 TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutSplitData); + + + //----------------------------------------------------------------------------------------------------------------------------- + // ACCESSORS + //----------------------------------------------------------------------------------------------------------------------------- + + //----------------------------------------------------------------------------------------------------------------------------- + // MUTATORS + //----------------------------------------------------------------------------------------------------------------------------- + void SetHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) { HGPO = InHGPO; }; + void SetOuterComponent(UObject* InOuter) { OuterComponent = InOuter; }; + void SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO = false); + + void SetInputObjects(const TMap& InInputObjects) { InputObjects = InInputObjects; }; + void SetOutputObjects(TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; + + void SetInputAssignmentMaterials(const TMap& InInputMaterials) { InputAssignmentMaterials = InInputMaterials; }; + void SetReplacementMaterials(const TMap& InReplacementMaterials) { ReplacementMaterials = InReplacementMaterials; }; + + //void SetInputObjectProperties(const TMap& InInputObjectProperties) { InputObjectProperties = InInputObjectProperties; }; + //void SetOutputObjectProperties(TMap& InOutputObjectProperties) { OutputObjectProperties = InOutputObjectProperties; }; + + void SetTreatExistingMaterialsAsUpToDate(bool bInTreatExistingMaterialsAsUpToDate) { bTreatExistingMaterialsAsUpToDate = bInTreatExistingMaterialsAsUpToDate; } + + //----------------------------------------------------------------------------------------------------------------------------- + // Helpers + //----------------------------------------------------------------------------------------------------------------------------- + + // Helper functions for generic property attributes + static bool GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, + const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes); + + protected: + + // Create a StaticMesh using the MeshDescription format + bool CreateStaticMesh_MeshDescription(); + + // Legacy function using RawMesh for static Mesh creation + bool CreateStaticMesh_RawMesh(); + + // Create a UHoudiniStaticMesh + bool CreateHoudiniStaticMesh(); + + void ResetPartCache(); + + bool UpdatePartVertexList(); + + void SortSplitGroups(); + + bool UpdateSplitsFacesAndIndices(); + + // Update this part's position cache if we haven't already + bool UpdatePartPositionIfNeeded(); + + // Update this part's normal cache if we haven't already + bool UpdatePartNormalsIfNeeded(); + + // Update this part's tangent and binormal caches if we haven't already + bool UpdatePartTangentsIfNeeded(); + + // Update this part's color cache if we haven't already + bool UpdatePartColorsIfNeeded(); + + // Update this part's alpha if we haven't already + bool UpdatePartAlphasIfNeeded(); + + // Update this part's face smoothing values if we haven't already + bool UpdatePartFaceSmoothingIfNeeded(); + + // Update this part's UV sets if we haven't already + bool UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused = false); + + // Update this part;s lightmap resolution cache if we haven't already + bool UpdatePartLightmapResolutionsIfNeeded(); + + // Update this part's lod screensize attribute cache if we haven't already + bool UpdatePartLODScreensizeIfNeeded(); + + // Update th unique materials ids and infos needed for this part using the face materials and overrides + bool UpdatePartNeededMaterials(); + + // Update this part's face material IDs, unique material IDs and material Infos caches if we haven't already + bool UpdatePartFaceMaterialIDsIfNeeded(); + + // Update this part's material overrides cache if we haven't already + bool UpdatePartFaceMaterialOverridesIfNeeded(); + + // Updates and create the material that are needed for this part + bool CreateNeededMaterials(); + + UStaticMesh* CreateNewStaticMesh(const FString& InMeshIdentifierString); + + UStaticMesh* FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); + + UHoudiniStaticMesh* CreateNewHoudiniStaticMesh(const FString& InMeshIdentifierString); + + UHoudiniStaticMesh* FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); + + float GetLODSCreensizeForSplit(const FString& SplitGroupName); + + // Create convex/UCX collider for a split and add to the aggregate + bool AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); + // Create simple colliders for a split and add to the aggregate + bool AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); + + // Helper functions to generate the simple colliders and add them to the aggregate + static int32 GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions); + + // Helper functions for the simple colliders generation + static void CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec); + static void CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); + static void CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); + static void CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec); + + // Helper functions to remove unused/stale components + static bool RemoveAndDestroyComponent(UObject* InComponent); + + // Helper to create a new mesh component + static UMeshComponent* CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf& InComponentType); + + // Helper to update an existing mesh component + static void UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, + const FHoudiniGeoPartObject *InHGPO, TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors, + bool bInApplyGenericProperties=true); + + // Helper to create or update a mesh component for a UStaticMesh or proxy mesh output + static UMeshComponent* CreateOrUpdateMeshComponent( + const UHoudiniOutput* InOutput, + UObject* InOuterComponent, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TSubclassOf& InComponentType, + FHoudiniOutputObject& OutOutputObject, + FHoudiniGeoPartObject const *& OutFoundHGPO, + bool &bCreated); + + // Helper to initialize a UStaticMeshComponent after it was created. + static bool PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh); + + // Helper to initialize a UHoudiniStaticMeshComponent after it was created. + static bool PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh); + + static bool AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, + TArray& HoudiniCreatedSocketActors, TArray& HoudiniAttachedSocketActors); + + protected: + + // Data cache for this translator + + // The HoudiniGeoPartObject we're working on + FHoudiniGeoPartObject HGPO; + + // Outer object for attaching components to + UObject* OuterComponent; + + // Structure that handles cooking/baking package creation parameters + FHoudiniPackageParams PackageParams; + + + // Previous output objects + TMap InputObjects; + + // New Output objects + TMap OutputObjects; + + + // Input Material Map + TMap InputAssignmentMaterials; + // Output Material Map + TMap OutputAssignmentMaterials; + // Input Replacement Materials maps + TMap ReplacementMaterials; + + // Input mesh properties + //TMap InputObjectProperties; + // Output mesh properties + //TMap OutputObjectProperties; + + // Indicates the update is forced + bool ForceRebuild; + + // The generated simple/UCX colliders + TMap AllAggregateCollisions; + + // Names of the groups used for splitting the geometry + TArray AllSplitGroups; + + // Per-split lists of faces + TMap> AllSplitVertexLists; + + // Per-split number of faces + TMap AllSplitVertexCounts; + + // Per-split indices arrays + TMap> AllSplitFaceIndices; + + // Per-split first valid vertex index + TMap AllSplitFirstValidVertexIndex; + + // Per-split first valid prim index + TMap AllSplitFirstValidPrimIndex; + + // Vertex Indices for the part + TArray PartVertexList; + + // Positions + TArray PartPositions; + HAPI_AttributeInfo AttribInfoPositions; + + // Vertex Normals + TArray PartNormals; + HAPI_AttributeInfo AttribInfoNormals; + + // Vertex TangentU + TArray PartTangentU; + HAPI_AttributeInfo AttribInfoTangentU; + + // Vertex TangentV + TArray PartTangentV; + HAPI_AttributeInfo AttribInfoTangentV; + + // Vertex Colors + TArray PartColors; + HAPI_AttributeInfo AttribInfoColors; + + // Vertex Alpha values + TArray PartAlphas; + HAPI_AttributeInfo AttribInfoAlpha; + + // Face Smoothing masks + TArray PartFaceSmoothingMasks; + HAPI_AttributeInfo AttribInfoFaceSmoothingMasks; + + // UVs + TArray> PartUVSets; + TArray AttribInfoUVSets; + + // Lightmap resolution + TArray PartLightMapResolutions; + HAPI_AttributeInfo AttribInfoLightmapResolution; + + // Material IDs per face + TArray PartFaceMaterialIds; + HAPI_AttributeInfo AttribInfoFaceMaterialIds; + // Unique material IDs + TArray PartUniqueMaterialIds; + //TSet PartUniqueMaterialIds; + // Material infos for each unique Material + TArray PartUniqueMaterialInfos; + //TSet PartUniqueMaterialInfos; + // Indicates we only have a single face material + bool bOnlyOneFaceMaterial; + + // Material Overrides per face + TArray PartFaceMaterialOverrides; + HAPI_AttributeInfo AttribInfoFaceMaterialOverrides; + // Indicates that material overides attributes need an instance to be created + bool bMaterialOverrideNeedsCreateInstance; + + // LOD Screensize + TArray PartLODScreensize; + HAPI_AttributeInfo AttribInfoLODScreensize; + + int32 DefaultMeshSmoothing; + + // When building a mesh, if an associated material already exists, treat + // it as up to date, regardless of the MaterialInfo.bHasChanged flag + bool bTreatExistingMaterialsAsUpToDate; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp index 299a2af4d..825ad66f4 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp @@ -1,2045 +1,2045 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniOutputTranslator.h" - -#include "HoudiniOutput.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" - -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniInput.h" -#include "HoudiniStaticMesh.h" - -#include "HoudiniMeshTranslator.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" - -#include "Editor.h" -#include "EditorSupportDelegates.h" -#include "FileHelpers.h" -#include "LandscapeInfo.h" -#include "HAL/PlatformFilemanager.h" -#include "HAL/FileManager.h" -#include "Engine/WorldComposition.h" -#include "Modules/ModuleManager.h" -#include "WorldBrowserModule.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// -bool -FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& bInForceUpdate, bool& bOutHasHoudiniStaticMeshOutput) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the bake folder override - FHoudiniOutputTranslator::GetBakeFolderFromAttribute(HAC); - - // Get the temp folder override - FHoudiniOutputTranslator::GetTempFolderFromAttribute(HAC); - - // Check if the HDA has been marked as not producing outputs - if (!HAC->bOutputless) - { - // Check if we want to convert legacy v1 data - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility && HAC->Version1CompatibilityHAC) - { - // Do not reuse legacy outputs! - for (auto& OldOutput : HAC->Outputs) - { - ClearOutput(OldOutput); - } - } - - TArray NewOutputs; - if (FHoudiniOutputTranslator::BuildAllOutputs(HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos)) - { - ClearAndRemoveOutputs(HAC); - // Replace with the new parameters - HAC->Outputs = NewOutputs; - } - } - else - { - // This HDA is marked as not supposed to produce any output - ClearAndRemoveOutputs(HAC); - } - - // NOTE: PersistentWorld can be NULL when, for example, working with - // HoudiniAssetComponents in Blueprints. - UWorld* PersistentWorld = HAC->GetWorld(); - UWorldComposition* WorldComposition = nullptr; - if (PersistentWorld) - { - WorldComposition = PersistentWorld->WorldComposition; - } - - if (IsValid(WorldComposition)) - { - // We don't want the origin to shift as we're potentially updating levels. - WorldComposition->bTemporallyDisableOriginTracking = true; - } - - // "Process" the mesh. - // TODO: Move this to the actual processing stage, - // And see if some of this could be threaded - UObject* OuterComponent = HAC; - - FString HoudiniAssetPath = FPaths::GetPath(HAC->GetPathName()); - FString ComponentGUIDString = HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); - FString HoudiniAssetNameString = HAC->GetDisplayName(); - - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - PackageParams.ObjectName = FString(); - - TArray CreatedWorldCompositionPackages; - bool bCreatedNewMaps = false; - //... for heightfield outputs ...// - - // Collect all the landscape layers' global min/max values. - TMap LandscapeLayerGlobalMinimums; - TMap LandscapeLayerGlobalMaximums; - - // Store the instancer outputs separately so we can process them later, after all mesh output are processed. - // Determine the total number of instances, if we have more than 1 then mesh parts with instanced geo we will not create proxy meshes - // Also if we have object instancer (or oldschool attribute instancers), we won't be creating any proxy at all - TArray InstancerOutputs; - int32 NumInstances = 0; - bool bHasObjectInstancer = false; - - for (auto& CurOutput : HAC->Outputs) - { - if (CurOutput->GetType() == EHoudiniOutputType::Instancer) - { - // InstancerOutputs.Add(CurOutput); - for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type == EHoudiniPartType::Instancer) - { - if (HGPO.InstancerType == EHoudiniInstancerType::PackedPrimitive) - { - NumInstances += HGPO.PartInfo.InstanceCount; - } - else - { - NumInstances += HGPO.PartInfo.PointCount; - } - - if ((HGPO.InstancerType == EHoudiniInstancerType::ObjectInstancer) - || (HGPO.InstancerType == EHoudiniInstancerType::OldSchoolAttributeInstancer)) - { - bHasObjectInstancer = true; - } - } - } - } - else if (CurOutput->GetType() == EHoudiniOutputType::Landscape) - { - FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, false); - } - } - - bOutHasHoudiniStaticMeshOutput = false; - int32 NumVisibleOutputs = 0; - int32 NumOutputs = HAC->Outputs.Num(); - bool bHasLandscape = false; - - // Before processing all the outputs, - // See if we have any landscape input that have "Update Input Landscape" enabled - // And make an array of all our input landscapes - TArray AllInputLandscapes; - TArray InputLandscapesToUpdate; - - for (auto CurrentInput : HAC->Inputs) - { - if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) - continue; - - // Get the landscape input's landscape - ALandscapeProxy* InputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); - if (!InputLandscape) - continue; - - AllInputLandscapes.Add(InputLandscape); - - if (CurrentInput->GetUpdateInputLandscape()) - InputLandscapesToUpdate.Add(InputLandscape); - } - - // ---------------------------------------------------- - // Process outputs - // ---------------------------------------------------- - TArray CreatedPackages; - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* CurOutput = HAC->GetOutputAt(OutputIdx); - if (!CurOutput || CurOutput->IsPendingKill()) - continue; - - FString Notification = FString::Format(TEXT("Processing output {0} / {1}..."), {FString::FromInt(OutputIdx + 1), FString::FromInt(NumOutputs)}); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - if (!HAC->IsOutputTypeSupported(CurOutput->GetType())) - continue; - - switch (CurOutput->GetType()) - { - case EHoudiniOutputType::Mesh: - { - bool bIsProxyStaticMeshEnabled = ( - HAC->IsProxyStaticMeshEnabled() && - !HAC->HasNoProxyMeshNextCookBeenRequested() && - !HAC->IsBakeAfterNextCookEnabled()); - if (bIsProxyStaticMeshEnabled && NumInstances > 1) - { - if (bHasObjectInstancer) - { - // Completely disable proxies if we have object instancers/old school attribute instancers - // as they rely on having a static mesh created (and the instanced mesh HGPO is not marked as instanced...) - bIsProxyStaticMeshEnabled = false; - } - else - { - // If we dont have proxy instancer, enable proxy only for non-instanced mesh - for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.bIsInstanced && HGPO.Type == EHoudiniPartType::Mesh) - { - bIsProxyStaticMeshEnabled = false; - break; - } - } - } - } - - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - PackageParams, - bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, - OuterComponent); - - NumVisibleOutputs++; - - // Look for UHoudiniStaticMesh in the output, and set bOutHasHoudiniStaticMeshOutput accordingly - if (bIsProxyStaticMeshEnabled && !bOutHasHoudiniStaticMeshOutput) - { - bOutHasHoudiniStaticMeshOutput &= CurOutput->HasAnyCurrentProxy(); - } - - break; - } - - case EHoudiniOutputType::Curve: - { - const TArray &GeoPartObjects = CurOutput->GetHoudiniGeoPartObjects(); - - if (GeoPartObjects.Num() <= 0) - continue; - - const FHoudiniGeoPartObject & CurHGPO = GeoPartObjects[0]; - - if (CurOutput->IsEditableNode()) - { - if (!CurOutput->HasEditableNodeBuilt()) - { - // Editable curve, only need to be built once. - UHoudiniSplineComponent* HoudiniSplineComponent = FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode( - CurHGPO.GeoId, - CurHGPO.PartName, - HAC); - - HoudiniSplineComponent->SetIsEditableOutputCurve(true); - - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; - EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; - EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; - EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; - - TMap& OutputObjects = CurOutput->GetOutputObjects(); - FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - FoundOutputObject.OutputComponent = HoudiniSplineComponent; - - CurOutput->SetHasEditableNodeBuilt(true); - } - } - else - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); - NumVisibleOutputs += CurOutput->GetOutputObjects().Num(); - break; - } - } - break; - - case EHoudiniOutputType::Instancer: - InstancerOutputs.Add(CurOutput); - break; - - case EHoudiniOutputType::Landscape: - { - NumVisibleOutputs++; - - // This gets called for each heightfield primitive from Houdini, i.e., each "tile". - bool bNewMapCreated = false; - // Registering of untracked actors is not currently used in the HDA - // workflow. HDA cleanup will manually search for shared landscapes - // and remove them. That aforementioned behaviour should really be updated to - // make use of untracked actors on the HAC (similar to PDG Asset Link). - TArray> UntrackedActors; - - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - UntrackedActors, - InputLandscapesToUpdate, - AllInputLandscapes, - HAC, - TEXT("{hda_actor_name}_"), - PersistentWorld, - LandscapeLayerGlobalMinimums, - LandscapeLayerGlobalMaximums, - PackageParams, - CreatedPackages); - - bHasLandscape = true; - - // Attach the created landscape to the parent HAC. - ALandscapeProxy* OutputLandscape = nullptr; - for (auto& Pair : CurOutput->GetOutputObjects()) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(Pair.Value.OutputObject); - OutputLandscape = LandscapePtr->GetRawPtr(); - break; - } - - if (OutputLandscape) - { - // Attach the created landscapes to HAC - // Output Transforms are always relative to the HDA - HAC->SetMobility(EComponentMobility::Static); - OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); - // Note that the above attach will cause the collision components to crap out. This manifests - // itself via the Landscape editor tools not being able to trace Landscape collision components. - // By recreating collision components here, it appears to put things back into working order. - OutputLandscape->RecreateCollisionComponents(); - } - - bCreatedNewMaps |= bNewMapCreated; - - break; - } - default: - // Do Nothing for now - break; - } - } - - // Now that all meshes have been created, process the instancers - for (auto& CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); - NumVisibleOutputs++; - } - - if (NumVisibleOutputs > 0) - { - // If we have valid outputs, we don't need to display the houdini logo anymore... - FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(HAC); - } - else - { - // ... if we don't have any valid outputs however, we should - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HAC); - } - - if (bHasLandscape) - { - // ---------------------------------------------------- - // Cleanup untracked shared landscape actors - // ---------------------------------------------------- - // This is a nasty hack to clean up SharedLandscape actors generated by the - // Landscape translator but aren't tracked by an HoudiniOutputObject, since the - // translators can't dynamically create outputs. - - { - // First collect all the landscapes that is being tracked by the HAC. - TSet TrackedLandscapes; - for(UHoudiniOutput* Output : HAC->Outputs) - { - if (Output->GetType() == EHoudiniOutputType::Landscape) - { - for(auto& Elem : Output->GetOutputObjects()) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(Elem.Value.OutputObject); - if (!IsValid(LandscapePtr)) - continue; - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - if (IsValid(LandscapeProxy)) - { - TrackedLandscapes.Add(LandscapeProxy); - - // We need to recreate component states for landscapes if a tile was created, moved, or resized - // otherwise the landscape will exhibit render artifacts (such as only rendering every other - // component.) - LandscapeProxy->RecreateComponentsState(); - } - } - } - } - - // Iterate over Houdini asset child assets in order to find dangling Landscape actors - TArray AttachedComponents = HAC->GetAttachChildren(); - for(USceneComponent* Component : AttachedComponents) - { - if (!IsValid(Component)) - continue; - AActor* Actor = Component->GetOwner(); - ALandscape* Landscape = Cast(Actor); - if (!Landscape) - continue; - if (TrackedLandscapes.Contains(Landscape)) - continue; - - ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); - if (!Info || Info->Proxies.Num() == 0) - { - Landscape->Destroy(); - } - } - } - - // Recreate Landscape Info calls WorldChange, so no need to do it manually. - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - } - - if (IsValid(WorldComposition)) - { - // Disable the flag that we set before starting the import process. - WorldComposition->bTemporallyDisableOriginTracking = false; - } - - // If the owner component was marked as loaded, unmark all outputs - if (HAC->HasBeenLoaded()) - { - for (auto& CurrentOutput : HAC->Outputs) - { - CurrentOutput->MarkAsLoaded(false); - } - } - - if (bCreatedNewMaps) - { - // Force the asset registry to update its cache of packages paths - // recursively for this world, otherwise world composition won't - // pick them up during the WorldComposition::Rescan(). - FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); - - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - - FHoudiniEngineUtils::LogWorldInfo(PersistentWorld); - if (WorldComposition) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); - } - - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); - } - - if (CreatedPackages.Num() > 0) - { - // Save created packages. For example, we don't want landscape layers deleted - // along with the HDA. - FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); - } - - return true; -} - -bool -FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - UObject* OuterComponent = HAC; - - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - PackageParams.ObjectName = FString(); - - bool bFoundProxies = false; - TArray InstancerOutputs; - for (auto& CurOutput : HAC->Outputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType == EHoudiniOutputType::Mesh) - { - if (CurOutput->HasAnyCurrentProxy()) - { - bFoundProxies = true; - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - PackageParams, - HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, - OuterComponent, - true, // bInTreatExistingMaterialsAsUpToDate - bInDestroyProxies - ); - } - } - else if (OutputType == EHoudiniOutputType::Instancer) - { - InstancerOutputs.Add(CurOutput); - } - } - - // Rebuild instancers if we built any static meshes from proxies - if (bFoundProxies) - { - for (auto& CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); - } - } - - return true; -} - -// -bool -FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) -{ - HAPI_NodeId & AssetId = HAC->AssetId; - // Get the AssetInfo - 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; - - TArray EditableCurveObjIds; - TArray EditableCurveGeoIds; - TArray EditableCurvePartIds; - TArray EditableCurvePartNames; - - // Iterate through all objects to get all editable curve's object geo and part Ids. - - for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) - { - // Retrieve the object info - const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; - - // Cache/convert them - FHoudiniObjectInfo CurrentObjectInfo; - CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); - - // Start by getting the number of editable nodes - int32 EditableNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, - true, &EditableNodeCount)); - - if (EditableNodeCount > 0) - { - TArray< HAPI_NodeId > EditableNodeIds; - EditableNodeIds.SetNumUninitialized(EditableNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - - for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) - { - HAPI_GeoInfo CurrentEditableGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentEditableGeoInfo.isDisplayGeo) - continue; - - // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) - continue; - - // Only catch editable curves - if (CurrentEditableGeoInfo.type != HAPI_GeoType::HAPI_GEOTYPE_CURVE) - continue; - - // Cook the editable node to get its parts - if (CurrentEditableGeoInfo.partCount <= 0) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentEditableGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentEditableGeoInfo.nodeId, - &CurrentEditableGeoInfo)); - } - - // Iterate on this geo's parts - for (int32 PartId = 0; PartId < CurrentEditableGeoInfo.partCount; ++PartId) - { - // Get part information. - HAPI_PartInfo CurrentHapiPartInfo; - FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - continue; - - if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE) - continue; - - // Get the editable curve's part name - FHoudiniEngineString hapiSTR(CurrentHapiPartInfo.nameSH); - FString PartName; - hapiSTR.ToFString(PartName); - - EditableCurveObjIds.Add(CurrentHapiObjectInfo.nodeId); - EditableCurveGeoIds.Add(CurrentEditableGeoInfo.nodeId); - EditableCurvePartIds.Add(CurrentHapiPartInfo.id); - EditableCurvePartNames.Add(PartName); - } - } - } - - } - - int32 Idx = 0; - for (auto& CurrentOutput : HAC->Outputs) - { - if (CurrentOutput->IsEditableNode()) - { - // The HAC is Loaded, re-assign node id to its editable curves - if (CurrentOutput->HasEditableNodeBuilt()) - { - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - for (auto& Pair : OutputObjects) - { - if (Idx >= EditableCurvePartIds.Num()) - break; - - UHoudiniSplineComponent * HoudiniSplineComponent = Cast(Pair.Value.OutputComponent); - if (HoudiniSplineComponent && !HoudiniSplineComponent->IsPendingKill()) - { - HoudiniSplineComponent->SetNodeId(EditableCurveGeoIds[Idx]); - - Pair.Key.ObjectId = EditableCurveObjIds[Idx]; - Pair.Key.GeoId = EditableCurveGeoIds[Idx]; - Pair.Key.PartId = EditableCurvePartIds[Idx]; - Pair.Key.PartName = EditableCurvePartNames[Idx]; - - Idx += 1; - } - } - } - // The HAC is a Duplication, re-construct output objects with attached duplicated editable curves, matching by part name - else - { - const TArray &Children = HAC->GetAttachChildren(); - for (auto & CurAttachedComp : Children) - { - if (!CurAttachedComp || CurAttachedComp->IsPendingKill()) - continue; - - if (!CurAttachedComp->IsA()) - continue; - - UHoudiniSplineComponent * CurAttachedSplineComp = Cast(CurAttachedComp); - if (!CurAttachedSplineComp) - continue; - - if (!CurAttachedSplineComp->IsEditableOutputCurve()) - continue; - - if (Idx >= EditableCurvePartIds.Num()) - break; - - // Found a match - if (CurAttachedSplineComp->GetGeoPartName().Equals(EditableCurvePartNames[Idx])) - { - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = EditableCurveObjIds[Idx]; - EditableSplineComponentIdentifier.GeoId = EditableCurveGeoIds[Idx]; - EditableSplineComponentIdentifier.PartId = EditableCurvePartIds[Idx]; - EditableSplineComponentIdentifier.PartName = EditableCurvePartNames[Idx]; - - CurAttachedSplineComp->SetNodeId(EditableSplineComponentIdentifier.GeoId); - - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - FHoudiniOutputObject& NewOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - NewOutputObject.OutputComponent = CurAttachedSplineComp; - - CurrentOutput->SetHasEditableNodeBuilt(true); - FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(CurAttachedSplineComp); - - Idx += 1; - break; - } - } - } - } - else - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); - } - - // Mark our outputs as loaded so they can be matched for potential reuse - // This indicates that the HGPO's ids are invalid and that HGPO should be matched using partnames instead - CurrentOutput->MarkAsLoaded(true); - } - - return true; -} - -// -bool -FHoudiniOutputTranslator::UploadChangedEditableOutput( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray &Outputs = HAC->Outputs; - - // Iterate through the outputs array of HAC. - for (auto& CurrentOutput : HAC->Outputs) - { - if (!CurrentOutput) - continue; - - // Only update the editable nodes that have been built before. - if (!CurrentOutput->IsEditableNode() || !CurrentOutput->HasEditableNodeBuilt()) - continue; - - for (auto& CurrentOutputObj : CurrentOutput->GetOutputObjects()) - { - UHoudiniSplineComponent* HoudiniSplineComponent = Cast(CurrentOutputObj.Value.OutputComponent); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (!HoudiniSplineComponent->HasChanged()) - continue; - - if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(HoudiniSplineComponent)) - HoudiniSplineComponent->MarkChanged(false); - else - HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); - } - } - - return true; -} - - -bool -FHoudiniOutputTranslator::BuildAllOutputs( - const HAPI_NodeId& AssetId, - UObject* InOuterObject, - TArray& InOldOutputs, - TArray& OutNewOutputs, - const bool& InOutputTemplatedGeos) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Get the AssetInfo - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - FString CurrentAssetName; - { - FHoudiniEngineString hapiSTR(AssetInfo.nameSH); - hapiSTR.ToFString(CurrentAssetName); - } - - // Retrieve the asset's transform. - // TODO: Unused?! - //FTransform AssetUnrealTransform; - //if (!FHoudiniEngineUtils::HapiGetAssetTransform(AssetId, AssetUnrealTransform)) - // return false; - - // Retrieve information about each object contained within our asset. - TArray ObjectInfos; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos)) - return false; - - //const int32 ObjectCount = ObjectInfos.Num(); - - // Retrieve transforms for each object in this asset. - TArray ObjectTransforms; - if (!FHoudiniEngineUtils::HapiGetObjectTransforms(AssetId, ObjectTransforms)) - return false; - - // Mark all the previous HGPOs on the outputs as stale - // This indicates that they were from a previous cook and should then be deleted - for (auto& CurOutput : InOldOutputs) - { - if (CurOutput) - CurOutput->MarkAllHGPOsAsStale(true); - } - - // For HF / Volumes, we only create new Outputs for height volume - // Store all the other volumes (masks etc) on the side and we will - // match them with theit corresponding height volume after - TArray UnassignedVolumeParts; - - TArray AllSockets; - - // Iterate through all objects. - int32 OutputIdx = 1; - for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) - { - // Retrieve the object info - const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; - - // Cache/convert them - FHoudiniObjectInfo CurrentObjectInfo; - CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); - - // Retrieve object name. - FString CurrentObjectName = CurrentObjectInfo.Name; - - // Get transformation for this object. - const HAPI_Transform & ObjectTransform = ObjectTransforms[ObjectId]; - FTransform TransformMatrix; - FHoudiniEngineUtils::TranslateHapiTransform(ObjectTransform, TransformMatrix); - - // TODO: Check transforms?? - - // Build an array of the geos we'll need to process - // In most case, it will only be the display geo, - // but we may also want to process editable geos as well - TArray GeoInfos; - - // Get the Display Geo's info - HAPI_GeoInfo DisplayHapiGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName); - } - else - { - // Add the display geo info to the array - GeoInfos.Add(DisplayHapiGeoInfo); - } - - // Handle the editable nodes for this geo - // Start by getting the number of editable nodes - int32 EditableNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, - true, &EditableNodeCount)); - - if (EditableNodeCount > 0) - { - TArray< HAPI_NodeId > EditableNodeIds; - EditableNodeIds.SetNumUninitialized(EditableNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - - for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) - { - HAPI_GeoInfo CurrentEditableGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentEditableGeoInfo.isDisplayGeo) - continue; - - // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) - continue; - - // Add this geo to the geo info array - GeoInfos.Add(CurrentEditableGeoInfo); - } - } - - // Handle the templated nodes if desired - if (InOutputTemplatedGeos) - { - // Start by getting the number of templated nodes - int32 TemplatedNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, - HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, - true, &TemplatedNodeCount)); - - if (TemplatedNodeCount > 0) - { - TArray TemplatedNodeIds; - TemplatedNodeIds.SetNumUninitialized(TemplatedNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, TemplatedNodeIds.GetData(), TemplatedNodeCount)); - - for (int32 nTemplated = 0; nTemplated < TemplatedNodeCount; nTemplated++) - { - HAPI_GeoInfo CurrentTemplatedGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentTemplatedGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - TemplatedNodeIds[nTemplated], &CurrentTemplatedGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentTemplatedGeoInfo.isDisplayGeo) - continue; - - // We don't want all the nested template node IDs, - // as our HDA could potentially be using other HDAs with nested template flags - // Make sure the parent of the templated node is either the HDA, the current OBJ or the Display SOP - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurrentTemplatedGeoInfo.nodeId); - if (ParentId != CurrentHapiObjectInfo.nodeId - && ParentId != DisplayHapiGeoInfo.nodeId - && ParentId != AssetId) - { - continue; - } - - // Add this geo to the geo info array - GeoInfos.Add(CurrentTemplatedGeoInfo); - } - } - } - - // Iterates through the geos we want to process - for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) - { - // Cook editable/templated nodes to get their parts. - const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; - if ((CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) - || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0)) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, - &GeoInfos[GeoIdx])); - } - - // Cache/convert the display geo's info - FHoudiniGeoInfo CurrentGeoInfo; - CacheGeoInfo(CurrentHapiGeoInfo, CurrentGeoInfo); - - // Simply create an empty array for this geo's group names - // We might need it later for splitting - TArray GeoGroupNames; - bool HasSocketGroups = false; - - // Iterate on this geo's parts - for (int32 PartId = 0; PartId < CurrentGeoInfo.PartCount; ++PartId) - { - // Get part information. - HAPI_PartInfo CurrentHapiPartInfo; - FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); - - // If the geo is templated, cook it manually - if (CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - } - - bool bPartInfoFailed = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - { - bPartInfoFailed = true; - - // If the geo is templated, attempt to cook it manually - if(CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, nullptr); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, - &GeoInfos[GeoIdx])); - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - { - // We managed to get the templated part infos after cooking - bPartInfoFailed = false; - } - } - } - - if (bPartInfoFailed) - { - // Error retrieving part info. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d] unable to retrieve PartInfo - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId); - continue; - } - - // Convert/cache the part info - FHoudiniPartInfo CurrentPartInfo; - CachePartInfo(CurrentHapiPartInfo, CurrentPartInfo); - - // Retrieve part name. - FString CurrentPartName = CurrentPartInfo.Name; - - // Unsupported/Invalid part - if (CurrentPartInfo.Type == EHoudiniPartType::Invalid) - continue; - - // Update part/instancer type from the part infos - EHoudiniPartType CurrentPartType = EHoudiniPartType::Invalid; - EHoudiniInstancerType CurrentInstancerType = EHoudiniInstancerType::Invalid; - switch (CurrentHapiPartInfo.type) - { - case HAPI_PARTTYPE_BOX: - case HAPI_PARTTYPE_SPHERE: - case HAPI_PARTTYPE_MESH: - { - if (CurrentHapiGeoInfo.type == HAPI_GEOTYPE_CURVE) - { - // Closed curve will be seen as mesh - CurrentPartType = EHoudiniPartType::Curve; - } - else - { - CurrentPartType = EHoudiniPartType::Mesh; - - if (CurrentHapiObjectInfo.isInstancer) - { - if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // That part is actually an attribute instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - } - else - { - // That part is actually an instancer - CurrentPartType = EHoudiniPartType::Instancer; - CurrentInstancerType = EHoudiniInstancerType::ObjectInstancer; - } - - } - else if (CurrentHapiPartInfo.vertexCount <= 0 && CurrentHapiPartInfo.pointCount <= 0) - { - // No points, no vertices, we're likely invalid - CurrentPartType = EHoudiniPartType::Invalid; - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a mesh with no points or vertices - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - } - else if (CurrentHapiPartInfo.vertexCount <= 0) - { - // This is not an instancer, we do not have vertices, but we have points - // Maybe this is a point cloud with attribute override instancing - if(FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // Mark it as an instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - } - else - { - // No vertices, not an instancer, just a point cloud, consider ourself as invalid - CurrentPartType = EHoudiniPartType::Invalid; - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a point cloud mesh - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - } - } - } - } - break; - - case HAPI_PARTTYPE_CURVE: - { - // Make sure that this curve is not an an attribute instancer! - if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // Mark the part as an instancer it as an instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - } - else - { - // The curve is a curve! - CurrentPartType = EHoudiniPartType::Curve; - } - } - break; - - case HAPI_PARTTYPE_INSTANCER: - // This is a packed primitive instancer - CurrentPartType = EHoudiniPartType::Instancer; - CurrentInstancerType = EHoudiniInstancerType::PackedPrimitive; - break; - - case HAPI_PARTTYPE_VOLUME: - // Volume data, likely a Heightfield height / mask - CurrentPartType = EHoudiniPartType::Volume; - break; - - default: - // Unsupported Part Type - break; - } - - // There are no vertices AND no points and this part is not a packed prim instancer - if ((CurrentPartInfo.VertexCount <= 0 && CurrentPartInfo.PointCount <= 0) - && (CurrentPartType != EHoudiniPartType::Instancer || CurrentInstancerType != EHoudiniInstancerType::PackedPrimitive)) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] no points or vertices found - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - continue; - } - - // This is an instancer with no points. - if (CurrentHapiObjectInfo.isInstancer && CurrentHapiPartInfo.pointCount <= 0) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is instancer but has 0 points - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - continue; - } - - // Ignore invalid parts - if (CurrentPartType == EHoudiniPartType::Invalid) - continue; - - // Build the HGPO corresponding to this part - FHoudiniGeoPartObject currentHGPO; - currentHGPO.AssetId = AssetId; - currentHGPO.AssetName = CurrentAssetName; - - currentHGPO.ObjectId = CurrentHapiObjectInfo.nodeId; - currentHGPO.ObjectName = CurrentObjectName; - - currentHGPO.GeoId = CurrentHapiGeoInfo.nodeId; - - currentHGPO.PartId = CurrentHapiPartInfo.id; - - currentHGPO.Type = CurrentPartType; - currentHGPO.InstancerType = CurrentInstancerType; - - currentHGPO.TransformMatrix = TransformMatrix; - - currentHGPO.NodePath = TEXT(""); - - currentHGPO.bIsVisible = CurrentHapiObjectInfo.isVisible && !CurrentHapiPartInfo.isInstanced; - currentHGPO.bIsEditable = CurrentHapiGeoInfo.isEditable; - currentHGPO.bIsInstanced = CurrentHapiPartInfo.isInstanced; - // Never consider a display geo as templated! - currentHGPO.bIsTemplated = CurrentHapiGeoInfo.isDisplayGeo ? false : CurrentHapiGeoInfo.isTemplated; - - currentHGPO.bHasGeoChanged = CurrentHapiGeoInfo.hasGeoChanged; - currentHGPO.bHasPartChanged = CurrentHapiPartInfo.hasChanged; - currentHGPO.bHasMaterialsChanged = CurrentHapiGeoInfo.hasMaterialChanged; - currentHGPO.bHasTransformChanged = CurrentHapiObjectInfo.hasTransformChanged; - - // Copy the HAPI info caches - currentHGPO.ObjectInfo = CurrentObjectInfo; - currentHGPO.GeoInfo = CurrentGeoInfo; - currentHGPO.PartInfo = CurrentPartInfo; - - // We only support meshes for templated geos - if (currentHGPO.bIsTemplated && (CurrentPartType != EHoudiniPartType::Mesh)) - continue; - - // Update the HGPO's node path - FHoudiniEngineUtils::HapiGetNodePath(currentHGPO, currentHGPO.NodePath); - - // Try to get the custom part name from attribute - FString CustomPartName; - if (FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CustomPartName)) - currentHGPO.SetCustomPartName(CustomPartName); - else - currentHGPO.PartName = CurrentPartName; - - // - // Mesh Only - Extract split groups - // - // Extract the group names used by this part to see if it will require splitting - // Only meshes can be split, via their primitive groups - TArray< FString > SplitGroupNames; - if (CurrentPartType == EHoudiniPartType::Mesh) - { - if (!CurrentHapiPartInfo.isInstanced && GeoGroupNames.Num() > 0) - { - // We are not instanced and already have extracted the geo's group names - // We can simply reuse the Geo group names / socket groups - currentHGPO.SplitGroups = GeoGroupNames; - } - else - { - // We need to get the primitive group names from HAPI - int32 GroupCount = 0; - TArray GroupNames; - if (!FHoudiniEngineUtils::HapiGetGroupNames( - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - HAPI_GROUPTYPE_PRIM, CurrentHapiPartInfo.isInstanced, - GroupNames)) - { - GroupCount = 0; - GroupNames.Empty(); - } - - // Convert the string handles to FStrings - for (const FString& GroupName : GroupNames) - { - FString LodGroup = HAPI_UNREAL_GROUP_LOD_PREFIX; - FString CollisionGroup = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; - FString RenderedCollisionGroup = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; - if (GroupName.StartsWith(LodGroup, ESearchCase::IgnoreCase) - || GroupName.StartsWith(CollisionGroup, ESearchCase::IgnoreCase) - || GroupName.StartsWith(RenderedCollisionGroup, ESearchCase::IgnoreCase)) - //|| GroupName.StartsWith(HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX, ESearchCase::IgnoreCase)) - { - // Split by collisions / lods - currentHGPO.SplitGroups.Add(GroupName); - } - } - - // Sort the Group name array by name, - // this will order the LODs and other incremental group names - currentHGPO.SplitGroups.Sort(); - - // If this part is not instanced, we can copy the geo - // group names so we can reuse them for another part - if (!CurrentHapiPartInfo.isInstanced) - { - GeoGroupNames = currentHGPO.SplitGroups; - } - } - } - - // - // Volume Only - Extract volume name/tile index - // - // Extract the volume's name, and see if a tile attribute is present - FHoudiniVolumeInfo CurrentVolumeInfo; - if (CurrentPartType == EHoudiniPartType::Volume) - { - // Get this volume's info - HAPI_VolumeInfo CurrentHapiVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentHapiVolumeInfo); - - bool bVolumeValid = true; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - &CurrentHapiVolumeInfo)) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.tupleSize != 1) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.zLength != 1) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - { - bVolumeValid = false; - } - - // Only cache valid volumes - if (bVolumeValid) - { - // Convert/Cache the volume info - CacheVolumeInfo(CurrentHapiVolumeInfo, CurrentVolumeInfo); - - // Get the volume's name - currentHGPO.VolumeName = CurrentVolumeInfo.Name; - - // Now see if this volume has a tile attribute - TArray TileValues; - if (FHoudiniEngineUtils::GetTileAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, TileValues, HAPI_ATTROWNER_PRIM)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - currentHGPO.VolumeTileIndex = TileValues[0]; - else - currentHGPO.VolumeTileIndex = -1; - } - } - } - currentHGPO.VolumeInfo = CurrentVolumeInfo; - - // Cache the curve info as well - FHoudiniCurveInfo CurrentCurveInfo; - if (CurrentPartType == EHoudiniPartType::Curve) - { - HAPI_CurveInfo CurrentHapiCurveInfo; - FHoudiniApi::CurveInfo_Init(&CurrentHapiCurveInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - &CurrentHapiCurveInfo)) - { - // Cache/Convert this part's curve info - CacheCurveInfo(CurrentHapiCurveInfo, CurrentCurveInfo); - } - } - currentHGPO.CurveInfo = CurrentCurveInfo; - - - // TODO: - // DONE? bake folders are handled out of this loop? - // See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute - //TArray BakeFolderOverrides; - - // Extract socket points - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); - - // See if we have an existing output that matches this HGPO or if we need to create a new one - bool IsFoundOutputValid = false; - UHoudiniOutput ** FoundHoudiniOutput = nullptr; - // We handle volumes differently than other outputs types, as a single HF output has multiple HGPOs - if (currentHGPO.Type != EHoudiniPartType::Volume) - { - // Look in the previous output if we have a match - FoundHoudiniOutput = InOldOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(currentHGPO) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - - } - else - { - // Look in the previous outputs if we have a match - FoundHoudiniOutput = InOldOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, true) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - - // If we dont have a match in the old maps, also look in the newly created outputs - if (!IsFoundOutputValid) - { - FoundHoudiniOutput = OutNewOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, false) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - } - } - - UHoudiniOutput * HoudiniOutput = nullptr; - if (IsFoundOutputValid) - { - // We can reuse the existing output - HoudiniOutput = *FoundHoudiniOutput; - HoudiniOutput->SetIsUpdating(true); - // Transfer this output from the old array to the new one - InOldOutputs.Remove(HoudiniOutput); - } - else - { - // We couldn't find a valid output object, so create a new one - - // If the current part is a volume, only create a new output object - // if the volume's name is "height", if not store the HGPO aside - if (currentHGPO.Type == EHoudiniPartType::Volume - && !currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) - { - UnassignedVolumeParts.Add(currentHGPO); - continue; - } - - // Create a new output object - //FString OutputName = TEXT("Output") + FString::FromInt(OutputIdx++); - HoudiniOutput = NewObject( - InOuterObject, - UHoudiniOutput::StaticClass(), - NAME_None,//FName(*OutputName), - RF_NoFlags); - - // Make sure the created object is valid - if (!HoudiniOutput || HoudiniOutput->IsPendingKill()) - { - //HOUDINI_LOG_WARNING("Failed to create asset output"); - continue; - } - - // Mark if the HoudiniOutput is editable - HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); - } - - // Add the HGPO to the output - HoudiniOutput->AddNewHGPO(currentHGPO); - // Add this output object to the new ouput array - OutNewOutputs.AddUnique(HoudiniOutput); - } - } - } - - // Update the output/HGPO associations from the map - // Clear the old HGPO since we don't need them anymore - for (auto& CurrentOuput : OutNewOutputs) - { - if (!CurrentOuput || CurrentOuput->IsPendingKill()) - continue; - - CurrentOuput->DeleteAllStaleHGPOs(); - } - - // If we have unassigned volumes, - // try to find their corresponding output - if (UnassignedVolumeParts.Num() > 0) - { - for (auto& currentVolumeHGPO : UnassignedVolumeParts) - { - UHoudiniOutput ** FoundHoudiniOutput = OutNewOutputs.FindByPredicate( - [currentVolumeHGPO](UHoudiniOutput* Output) - { - return Output ? Output->HeightfieldMatch(currentVolumeHGPO, false) : false; - }); - - if (!FoundHoudiniOutput || !(*FoundHoudiniOutput) || (*FoundHoudiniOutput)->IsPendingKill()) - { - // Skip - consider this volume as invalid - continue; - } - - // Add this HGPO to the output - (*FoundHoudiniOutput)->AddNewHGPO(currentVolumeHGPO); - } - } - - // All our output objects now have their HGPO assigned - // We can now parse them to update the output type - for (auto& Output : OutNewOutputs) - { - Output->UpdateOutputType(); - } - - return true; -} - -bool -FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray& Outputs = HAC->Outputs; - - // Iterate through the outputs array of HAC. - for (int32 Index = 0; Index < HAC->GetNumOutputs(); ++Index) - { - UHoudiniOutput* CurrentOutput = HAC->GetOutputAt(Index); - if (!CurrentOutput) - continue; - - if (!HAC->IsOutputTypeSupported(CurrentOutput->GetType())) - continue; - - switch (CurrentOutput->GetType()) - { - case EHoudiniOutputType::Instancer: - { - bool bNeedToRecreateInstancers = false; - for (auto& Iter : CurrentOutput->GetInstancedOutputs()) - { - FHoudiniInstancedOutput& InstOutput = Iter.Value; - if (!InstOutput.bChanged) - continue; - - /* - FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( - InstOutput, Iter.Key, CurrentOutput, HAC); - */ - - // TODO: - // UpdateChangedInstancedOutput needs some improvements - // as it currently destroy too many components. - // For now, we'll update all the instancers - bNeedToRecreateInstancers = true; - - InstOutput.MarkChanged(false); - } - - if (bNeedToRecreateInstancers) - { - if (HAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation || HAC->HasBeenLoaded()) - { - // Instantiate the HDA if it's not been - // This is because CreateAllInstancersFromHoudiniOutput() actually reads the transform from HAPI - // Calling it on a HDA not yet instantiated causes a crash... - HAC->AssetState = EHoudiniAssetState::PreInstantiation; - } - else - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurrentOutput, Outputs, HAC); - } - } - } - break; - - case EHoudiniOutputType::Curve: - { - //FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); - } - break; - - default: - break; - } - } - - return true; -} - -void -FHoudiniOutputTranslator::CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache) -{ - FHoudiniEngineString hapiSTR(InObjInfo.nameSH); - hapiSTR.ToFString(OutObjInfoCache.Name); - //OutObjInfoCache.Name = InObjInfo.nameSH; - - OutObjInfoCache.NodeId = InObjInfo.nodeId; - OutObjInfoCache.ObjectToInstanceID = InObjInfo.objectToInstanceId; - - OutObjInfoCache.bHasTransformChanged = InObjInfo.hasTransformChanged; - OutObjInfoCache.bHaveGeosChanged = InObjInfo.haveGeosChanged; - - OutObjInfoCache.bIsVisible = InObjInfo.isVisible; - OutObjInfoCache.bIsInstancer = InObjInfo.isInstancer; - OutObjInfoCache.bIsInstanced = InObjInfo.isInstanced; - - OutObjInfoCache.GeoCount = InObjInfo.geoCount; -}; - -EHoudiniGeoType -FHoudiniOutputTranslator::ConvertHapiGeoType(const HAPI_GeoType& InType) -{ - EHoudiniGeoType OutType = EHoudiniGeoType::Invalid; - switch (InType) - { - case HAPI_GEOTYPE_DEFAULT: - OutType = EHoudiniGeoType::Default; - break; - - case HAPI_GEOTYPE_INTERMEDIATE: - OutType = EHoudiniGeoType::Intermediate; - break; - - case HAPI_GEOTYPE_INPUT: - OutType = EHoudiniGeoType::Input; - break; - - case HAPI_GEOTYPE_CURVE: - OutType = EHoudiniGeoType::Curve; - break; - - default: - OutType = EHoudiniGeoType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache) -{ - OutGeoInfoCache.Type = ConvertHapiGeoType(InGeoInfo.type); - - FHoudiniEngineString hapiSTR(InGeoInfo.nameSH); - hapiSTR.ToFString(OutGeoInfoCache.Name); - - OutGeoInfoCache.NodeId = InGeoInfo.nodeId; - - OutGeoInfoCache.bIsEditable = InGeoInfo.isEditable; - OutGeoInfoCache.bIsTemplated = InGeoInfo.isTemplated; - OutGeoInfoCache.bIsDisplayGeo = InGeoInfo.isDisplayGeo; - OutGeoInfoCache.bHasGeoChanged = InGeoInfo.hasGeoChanged; - OutGeoInfoCache.bHasMaterialChanged = InGeoInfo.hasMaterialChanged; - - OutGeoInfoCache.PartCount = InGeoInfo.partCount; - OutGeoInfoCache.PointGroupCount = InGeoInfo.pointGroupCount; - OutGeoInfoCache.PrimitiveGroupCount = InGeoInfo.primitiveGroupCount; -}; - - -EHoudiniPartType -FHoudiniOutputTranslator::ConvertHapiPartType(const HAPI_PartType& InType) -{ - EHoudiniPartType OutType = EHoudiniPartType::Invalid; - switch (InType) - { - case HAPI_PARTTYPE_BOX: - case HAPI_PARTTYPE_SPHERE: - case HAPI_PARTTYPE_MESH: - OutType = EHoudiniPartType::Mesh; - break; - - case HAPI_PARTTYPE_CURVE: - OutType = EHoudiniPartType::Curve; - break; - - case HAPI_PARTTYPE_INSTANCER: - OutType = EHoudiniPartType::Instancer; - break; - - case HAPI_PARTTYPE_VOLUME: - OutType = EHoudiniPartType::Volume; - break; - - default: - OutType = EHoudiniPartType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache) -{ - OutPartInfoCache.PartId = InPartInfo.id; - - FHoudiniEngineString hapiSTR(InPartInfo.nameSH); - hapiSTR.ToFString(OutPartInfoCache.Name); - - OutPartInfoCache.Type = ConvertHapiPartType(InPartInfo.type); - - OutPartInfoCache.FaceCount = InPartInfo.faceCount; - OutPartInfoCache.VertexCount = InPartInfo.vertexCount; - OutPartInfoCache.PointCount = InPartInfo.pointCount; - - OutPartInfoCache.PointAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_POINT]; - OutPartInfoCache.VertexAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX]; - OutPartInfoCache.PrimitiveAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_PRIM]; - OutPartInfoCache.DetailAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL]; - - OutPartInfoCache.bIsInstanced = InPartInfo.isInstanced; - - OutPartInfoCache.InstancedPartCount = InPartInfo.instancedPartCount; - OutPartInfoCache.InstanceCount = InPartInfo.instanceCount; - - OutPartInfoCache.bHasChanged = InPartInfo.hasChanged; -}; - -void -FHoudiniOutputTranslator::CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache) -{ - FHoudiniEngineString hapiSTR(InVolumeInfo.nameSH); - hapiSTR.ToFString(OutVolumeInfoCache.Name); - - OutVolumeInfoCache.bIsVDB = (InVolumeInfo.type == HAPI_VOLUMETYPE_VDB); // replaces VolumeType Type; - - OutVolumeInfoCache.TupleSize = InVolumeInfo.tupleSize; - OutVolumeInfoCache.bIsFloat = (InVolumeInfo.storage == HAPI_STORAGETYPE_FLOAT); // replaces StorageType StorageType; - OutVolumeInfoCache.TileSize = InVolumeInfo.tileSize; - - FHoudiniEngineUtils::TranslateHapiTransform(InVolumeInfo.transform, OutVolumeInfoCache.Transform); - OutVolumeInfoCache.bHasTaper = InVolumeInfo.hasTaper; - - OutVolumeInfoCache.XLength = InVolumeInfo.xLength; - OutVolumeInfoCache.YLength = InVolumeInfo.yLength; - OutVolumeInfoCache.ZLength = InVolumeInfo.zLength; - - OutVolumeInfoCache.MinX = InVolumeInfo.minX; - OutVolumeInfoCache.MinY = InVolumeInfo.minY; - OutVolumeInfoCache.MinZ = InVolumeInfo.minZ; - - OutVolumeInfoCache.XTaper = InVolumeInfo.xTaper; - OutVolumeInfoCache.YTaper = InVolumeInfo.yTaper; -}; - -EHoudiniCurveType -FHoudiniOutputTranslator::ConvertHapiCurveType(const HAPI_CurveType& InType) -{ - EHoudiniCurveType OutType = EHoudiniCurveType::Invalid; - switch (InType) - { - case HAPI_CURVETYPE_LINEAR: - OutType = EHoudiniCurveType::Polygon; - break; - - case HAPI_CURVETYPE_NURBS: - OutType = EHoudiniCurveType::Nurbs; - break; - - case HAPI_CURVETYPE_BEZIER: - OutType = EHoudiniCurveType::Bezier; - break; - - case HAPI_CURVETYPE_MAX: - OutType = EHoudiniCurveType::Points; - break; - - default: - OutType = EHoudiniCurveType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache) -{ - OutCurveInfoCache.Type = ConvertHapiCurveType(InCurveInfo.curveType); - - OutCurveInfoCache.CurveCount = InCurveInfo.curveCount; - OutCurveInfoCache.VertexCount = InCurveInfo.vertexCount; - OutCurveInfoCache.KnotCount = InCurveInfo.knotCount; - - OutCurveInfoCache.bIsPeriodic = InCurveInfo.isPeriodic; - OutCurveInfoCache.bIsRational = InCurveInfo.isRational; - - OutCurveInfoCache.Order = InCurveInfo.order; - - OutCurveInfoCache.bHasKnots = InCurveInfo.hasKnots; -}; - - -void -FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC) -{ - if (!IsValid(InHAC)) - return; - - // DO NOT MANUALLY DESTROY THE OLD/DANGLING OUTPUTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - // Simply clearing the array is enough - for (auto& OldOutput : InHAC->Outputs) - { - ClearOutput(OldOutput); - } - - InHAC->Outputs.Empty(); -} - -void -FHoudiniOutputTranslator::ClearOutput(UHoudiniOutput* Output) -{ - switch (Output->GetType()) - { - case EHoudiniOutputType::Landscape: - { - for (auto& OutputObject : Output->GetOutputObjects()) - { - // Currently, any Landscape managed by an HDA is always present in the current level. - // Only when it gets baked will Landscapes be serialized to other levels so for now - // assume that a landscape should be available, unless explicitly deleted the user. - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.Value.OutputObject); - if (!LandscapePtr) - continue; - - ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); - - if (!IsValid(Landscape) || !Landscape->IsValidLowLevel()) - continue; - - Landscape->UnregisterAllComponents(); - Landscape->Destroy(); - - // if (Output->IsLandscapeWorldComposition()) - // { - // TSoftObjectPtr LandscapeSoftPtr = LandscapePtr->GetSoftPtr(); - // - // FString SoftPtrPath = LandscapeSoftPtr.ToSoftObjectPath().ToString(); - // - // FString FileName = FPaths::GetBaseFilename(SoftPtrPath); - // FString FileDirectory = FPaths::GetPath(SoftPtrPath); - // - // FString ContentPath = FPaths::ProjectContentDir(); - // FString ContentFullPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*ContentPath); - // - // FString AbsoluteFilePath = ContentFullPath + FileDirectory.Mid(5, FileDirectory.Len() - 5) + "/" + FPaths::GetBaseFilename(FileName) + ".umap"; - // - // FPlatformFileManager::Get().GetPlatformFile().FileExists(*(AbsoluteFilePath)); - // - // FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*(AbsoluteFilePath)); - // } - // else - // { - - // } - } - } - break; - - case EHoudiniOutputType::Instancer: - { - for (auto& OutputObject : Output->GetOutputObjects()) - { - // Is this a foliage instancer? Check if the component is owned by an AInstancedFoliageActor - UActorComponent* const Component = Cast(OutputObject.Value.OutputComponent); - if (!IsValid(Component)) - continue; - AActor* const OwnerActor = Component->GetOwner(); - if (!IsValid(OwnerActor) || !OwnerActor->IsA()) - continue; - - UHierarchicalInstancedStaticMeshComponent* const FoliageHISMC = Cast(Component); - if (IsValid(FoliageHISMC)) - { - // Find the parent component: the foliage component outer, otherwise, if a houdini asset actor, the - // houdini asset component, otherwise for a normal actor its root component, finally try and see - // if the outer itself is a component. - USceneComponent* ParentComponent = Cast(FoliageHISMC->GetOuter()); - if (!IsValid(ParentComponent)) - { - UObject* const OutputOuter = Output->GetOuter(); - if (IsValid(OutputOuter)) - { - if (OutputOuter->IsA()) - { - ParentComponent = Cast(OutputOuter)->GetHoudiniAssetComponent(); - } - else if (OutputOuter->IsA()) - { - ParentComponent = Cast(OutputOuter)->GetRootComponent(); - } - else - { - ParentComponent = Cast(OutputOuter); - } - } - } - if (IsValid(ParentComponent)) - { - FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, ParentComponent); - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - } - } - } - } - break; - // ... Other output types ...// - - default: - break; - - } - - Output->Clear(); -} - - -bool -FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName) -{ - HAPI_AttributeInfo CustomPartNameInfo; - FHoudiniApi::AttributeInfo_Init(&CustomPartNameInfo); - - bool bHasCustomName = false; - TArray CustomNames; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, CustomPartNameInfo, CustomNames)) - { - // Look for the v2 attribute (unreal_output_name) - bHasCustomName = true; - } - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, CustomPartNameInfo, CustomNames)) - { - // If we couldnt find the new attribute, use the legacy v1 attribute (unreal_generated_mesh_name) - bHasCustomName = true; - } - - if (!bHasCustomName) - return false; - - if (CustomNames.Num() <= 0) - return false; - - OutCustomPartName = CustomNames[0]; - - if (OutCustomPartName.IsEmpty()) - return false; - - return true; -} - -void -FHoudiniOutputTranslator::GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) - return; - - FString BakeFolderOverride = FString(); - - FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); - - // If the TempCookFolder of the HAC is non-empty and is different from the override path. - // do not override it if the current temp cook path is valid. (it was user specified) - if (!HAC->BakeFolder.Path.IsEmpty() && !HAC->BakeFolder.Path.Equals(BakeFolderOverride)) - return; - - HAC->BakeFolder.Path = BakeFolderOverride; -} - -void -FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) - return; - - FString TempFolderOverride = FString(); - - HAPI_AttributeInfo TempFolderAttriInfo; - FHoudiniApi::AttributeInfo_Init(&TempFolderAttriInfo); - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_DETAIL)) - { - TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - else - { - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) - { - TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - } - - if (TempFolderOverride.StartsWith("Game/")) - { - TempFolderOverride = "/" + TempFolderOverride; - } - - FString AbsoluteOverridePath; - if (TempFolderOverride.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + TempFolderOverride.Mid(6, TempFolderOverride.Len() - 6); - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - if (!TempFolderOverride.IsEmpty()) - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*TempFolderOverride); - } - - // Check Validity of the path - if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) - { - // Only display a warning if the path is invalid, empty is fine - if(!AbsoluteOverridePath.IsEmpty()) - HOUDINI_LOG_WARNING(TEXT("Invalid override temporary cook path: %s"), *TempFolderOverride); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - TempFolderOverride = HoudiniRuntimeSettings->DefaultTemporaryCookFolder; - } - - // If the TempCookFolder of the HAC is non-empty and is different from the override path. - // do not override it if the current temp cook path is valid. (it was user specified) - if (!HAC->TemporaryCookFolder.Path.IsEmpty() && !HAC->TemporaryCookFolder.Path.Equals(TempFolderOverride)) - return; - - HAC->TemporaryCookFolder.Path = TempFolderOverride; -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniOutputTranslator.h" + +#include "HoudiniOutput.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" + +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniInput.h" +#include "HoudiniStaticMesh.h" + +#include "HoudiniMeshTranslator.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" + +#include "Editor.h" +#include "EditorSupportDelegates.h" +#include "FileHelpers.h" +#include "LandscapeInfo.h" +#include "HAL/PlatformFilemanager.h" +#include "HAL/FileManager.h" +#include "Engine/WorldComposition.h" +#include "Modules/ModuleManager.h" +#include "WorldBrowserModule.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// +bool +FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& bInForceUpdate, bool& bOutHasHoudiniStaticMeshOutput) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the bake folder override + FHoudiniOutputTranslator::GetBakeFolderFromAttribute(HAC); + + // Get the temp folder override + FHoudiniOutputTranslator::GetTempFolderFromAttribute(HAC); + + // Check if the HDA has been marked as not producing outputs + if (!HAC->bOutputless) + { + // Check if we want to convert legacy v1 data + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility && HAC->Version1CompatibilityHAC) + { + // Do not reuse legacy outputs! + for (auto& OldOutput : HAC->Outputs) + { + ClearOutput(OldOutput); + } + } + + TArray NewOutputs; + if (FHoudiniOutputTranslator::BuildAllOutputs(HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos)) + { + ClearAndRemoveOutputs(HAC); + // Replace with the new parameters + HAC->Outputs = NewOutputs; + } + } + else + { + // This HDA is marked as not supposed to produce any output + ClearAndRemoveOutputs(HAC); + } + + // NOTE: PersistentWorld can be NULL when, for example, working with + // HoudiniAssetComponents in Blueprints. + UWorld* PersistentWorld = HAC->GetWorld(); + UWorldComposition* WorldComposition = nullptr; + if (PersistentWorld) + { + WorldComposition = PersistentWorld->WorldComposition; + } + + if (IsValid(WorldComposition)) + { + // We don't want the origin to shift as we're potentially updating levels. + WorldComposition->bTemporarilyDisableOriginTracking = true; + } + + // "Process" the mesh. + // TODO: Move this to the actual processing stage, + // And see if some of this could be threaded + UObject* OuterComponent = HAC; + + FString HoudiniAssetPath = FPaths::GetPath(HAC->GetPathName()); + FString ComponentGUIDString = HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); + FString HoudiniAssetNameString = HAC->GetDisplayName(); + + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + PackageParams.ObjectName = FString(); + + TArray CreatedWorldCompositionPackages; + bool bCreatedNewMaps = false; + //... for heightfield outputs ...// + + // Collect all the landscape layers' global min/max values. + TMap LandscapeLayerGlobalMinimums; + TMap LandscapeLayerGlobalMaximums; + + // Store the instancer outputs separately so we can process them later, after all mesh output are processed. + // Determine the total number of instances, if we have more than 1 then mesh parts with instanced geo we will not create proxy meshes + // Also if we have object instancer (or oldschool attribute instancers), we won't be creating any proxy at all + TArray InstancerOutputs; + int32 NumInstances = 0; + bool bHasObjectInstancer = false; + + for (auto& CurOutput : HAC->Outputs) + { + if (CurOutput->GetType() == EHoudiniOutputType::Instancer) + { + // InstancerOutputs.Add(CurOutput); + for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type == EHoudiniPartType::Instancer) + { + if (HGPO.InstancerType == EHoudiniInstancerType::PackedPrimitive) + { + NumInstances += HGPO.PartInfo.InstanceCount; + } + else + { + NumInstances += HGPO.PartInfo.PointCount; + } + + if ((HGPO.InstancerType == EHoudiniInstancerType::ObjectInstancer) + || (HGPO.InstancerType == EHoudiniInstancerType::OldSchoolAttributeInstancer)) + { + bHasObjectInstancer = true; + } + } + } + } + else if (CurOutput->GetType() == EHoudiniOutputType::Landscape) + { + FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, false); + } + } + + bOutHasHoudiniStaticMeshOutput = false; + int32 NumVisibleOutputs = 0; + int32 NumOutputs = HAC->Outputs.Num(); + bool bHasLandscape = false; + + // Before processing all the outputs, + // See if we have any landscape input that have "Update Input Landscape" enabled + // And make an array of all our input landscapes + TArray AllInputLandscapes; + TArray InputLandscapesToUpdate; + + for (auto CurrentInput : HAC->Inputs) + { + if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) + continue; + + // Get the landscape input's landscape + ALandscapeProxy* InputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); + if (!InputLandscape) + continue; + + AllInputLandscapes.Add(InputLandscape); + + if (CurrentInput->GetUpdateInputLandscape()) + InputLandscapesToUpdate.Add(InputLandscape); + } + + // ---------------------------------------------------- + // Process outputs + // ---------------------------------------------------- + TArray CreatedPackages; + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) + { + UHoudiniOutput* CurOutput = HAC->GetOutputAt(OutputIdx); + if (!CurOutput || CurOutput->IsPendingKill()) + continue; + + FString Notification = FString::Format(TEXT("Processing output {0} / {1}..."), {FString::FromInt(OutputIdx + 1), FString::FromInt(NumOutputs)}); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + if (!HAC->IsOutputTypeSupported(CurOutput->GetType())) + continue; + + switch (CurOutput->GetType()) + { + case EHoudiniOutputType::Mesh: + { + bool bIsProxyStaticMeshEnabled = ( + HAC->IsProxyStaticMeshEnabled() && + !HAC->HasNoProxyMeshNextCookBeenRequested() && + !HAC->IsBakeAfterNextCookEnabled()); + if (bIsProxyStaticMeshEnabled && NumInstances > 1) + { + if (bHasObjectInstancer) + { + // Completely disable proxies if we have object instancers/old school attribute instancers + // as they rely on having a static mesh created (and the instanced mesh HGPO is not marked as instanced...) + bIsProxyStaticMeshEnabled = false; + } + else + { + // If we dont have proxy instancer, enable proxy only for non-instanced mesh + for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.bIsInstanced && HGPO.Type == EHoudiniPartType::Mesh) + { + bIsProxyStaticMeshEnabled = false; + break; + } + } + } + } + + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + PackageParams, + bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, + OuterComponent); + + NumVisibleOutputs++; + + // Look for UHoudiniStaticMesh in the output, and set bOutHasHoudiniStaticMeshOutput accordingly + if (bIsProxyStaticMeshEnabled && !bOutHasHoudiniStaticMeshOutput) + { + bOutHasHoudiniStaticMeshOutput &= CurOutput->HasAnyCurrentProxy(); + } + + break; + } + + case EHoudiniOutputType::Curve: + { + const TArray &GeoPartObjects = CurOutput->GetHoudiniGeoPartObjects(); + + if (GeoPartObjects.Num() <= 0) + continue; + + const FHoudiniGeoPartObject & CurHGPO = GeoPartObjects[0]; + + if (CurOutput->IsEditableNode()) + { + if (!CurOutput->HasEditableNodeBuilt()) + { + // Editable curve, only need to be built once. + UHoudiniSplineComponent* HoudiniSplineComponent = FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode( + CurHGPO.GeoId, + CurHGPO.PartName, + HAC); + + HoudiniSplineComponent->SetIsEditableOutputCurve(true); + + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; + EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; + EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; + EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; + + TMap& OutputObjects = CurOutput->GetOutputObjects(); + FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + FoundOutputObject.OutputComponent = HoudiniSplineComponent; + + CurOutput->SetHasEditableNodeBuilt(true); + } + } + else + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); + NumVisibleOutputs += CurOutput->GetOutputObjects().Num(); + break; + } + } + break; + + case EHoudiniOutputType::Instancer: + InstancerOutputs.Add(CurOutput); + break; + + case EHoudiniOutputType::Landscape: + { + NumVisibleOutputs++; + + // This gets called for each heightfield primitive from Houdini, i.e., each "tile". + bool bNewMapCreated = false; + // Registering of untracked actors is not currently used in the HDA + // workflow. HDA cleanup will manually search for shared landscapes + // and remove them. That aforementioned behaviour should really be updated to + // make use of untracked actors on the HAC (similar to PDG Asset Link). + TArray> UntrackedActors; + + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + UntrackedActors, + InputLandscapesToUpdate, + AllInputLandscapes, + HAC, + TEXT("{hda_actor_name}_"), + PersistentWorld, + LandscapeLayerGlobalMinimums, + LandscapeLayerGlobalMaximums, + PackageParams, + CreatedPackages); + + bHasLandscape = true; + + // Attach the created landscape to the parent HAC. + ALandscapeProxy* OutputLandscape = nullptr; + for (auto& Pair : CurOutput->GetOutputObjects()) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(Pair.Value.OutputObject); + OutputLandscape = LandscapePtr->GetRawPtr(); + break; + } + + if (OutputLandscape) + { + // Attach the created landscapes to HAC + // Output Transforms are always relative to the HDA + HAC->SetMobility(EComponentMobility::Static); + OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + // Note that the above attach will cause the collision components to crap out. This manifests + // itself via the Landscape editor tools not being able to trace Landscape collision components. + // By recreating collision components here, it appears to put things back into working order. + OutputLandscape->RecreateCollisionComponents(); + } + + bCreatedNewMaps |= bNewMapCreated; + + break; + } + default: + // Do Nothing for now + break; + } + } + + // Now that all meshes have been created, process the instancers + for (auto& CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); + NumVisibleOutputs++; + } + + if (NumVisibleOutputs > 0) + { + // If we have valid outputs, we don't need to display the houdini logo anymore... + FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(HAC); + } + else + { + // ... if we don't have any valid outputs however, we should + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HAC); + } + + if (bHasLandscape) + { + // ---------------------------------------------------- + // Cleanup untracked shared landscape actors + // ---------------------------------------------------- + // This is a nasty hack to clean up SharedLandscape actors generated by the + // Landscape translator but aren't tracked by an HoudiniOutputObject, since the + // translators can't dynamically create outputs. + + { + // First collect all the landscapes that is being tracked by the HAC. + TSet TrackedLandscapes; + for(UHoudiniOutput* Output : HAC->Outputs) + { + if (Output->GetType() == EHoudiniOutputType::Landscape) + { + for(auto& Elem : Output->GetOutputObjects()) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(Elem.Value.OutputObject); + if (!IsValid(LandscapePtr)) + continue; + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapeProxy)) + { + TrackedLandscapes.Add(LandscapeProxy); + + // We need to recreate component states for landscapes if a tile was created, moved, or resized + // otherwise the landscape will exhibit render artifacts (such as only rendering every other + // component.) + LandscapeProxy->RecreateComponentsState(); + } + } + } + } + + // Iterate over Houdini asset child assets in order to find dangling Landscape actors + TArray AttachedComponents = HAC->GetAttachChildren(); + for(USceneComponent* Component : AttachedComponents) + { + if (!IsValid(Component)) + continue; + AActor* Actor = Component->GetOwner(); + ALandscape* Landscape = Cast(Actor); + if (!Landscape) + continue; + if (TrackedLandscapes.Contains(Landscape)) + continue; + + ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); + if (!Info || Info->Proxies.Num() == 0) + { + Landscape->Destroy(); + } + } + } + + // Recreate Landscape Info calls WorldChange, so no need to do it manually. + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + } + + if (IsValid(WorldComposition)) + { + // Disable the flag that we set before starting the import process. + WorldComposition->bTemporarilyDisableOriginTracking = false; + } + + // If the owner component was marked as loaded, unmark all outputs + if (HAC->HasBeenLoaded()) + { + for (auto& CurrentOutput : HAC->Outputs) + { + CurrentOutput->MarkAsLoaded(false); + } + } + + if (bCreatedNewMaps) + { + // Force the asset registry to update its cache of packages paths + // recursively for this world, otherwise world composition won't + // pick them up during the WorldComposition::Rescan(). + FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); + + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + + FHoudiniEngineUtils::LogWorldInfo(PersistentWorld); + if (WorldComposition) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); + } + + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); + } + + if (CreatedPackages.Num() > 0) + { + // Save created packages. For example, we don't want landscape layers deleted + // along with the HDA. + FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); + } + + return true; +} + +bool +FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + UObject* OuterComponent = HAC; + + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + PackageParams.ObjectName = FString(); + + bool bFoundProxies = false; + TArray InstancerOutputs; + for (auto& CurOutput : HAC->Outputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType == EHoudiniOutputType::Mesh) + { + if (CurOutput->HasAnyCurrentProxy()) + { + bFoundProxies = true; + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + PackageParams, + HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, + OuterComponent, + true, // bInTreatExistingMaterialsAsUpToDate + bInDestroyProxies + ); + } + } + else if (OutputType == EHoudiniOutputType::Instancer) + { + InstancerOutputs.Add(CurOutput); + } + } + + // Rebuild instancers if we built any static meshes from proxies + if (bFoundProxies) + { + for (auto& CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); + } + } + + return true; +} + +// +bool +FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) +{ + HAPI_NodeId & AssetId = HAC->AssetId; + // Get the AssetInfo + 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; + + TArray EditableCurveObjIds; + TArray EditableCurveGeoIds; + TArray EditableCurvePartIds; + TArray EditableCurvePartNames; + + // Iterate through all objects to get all editable curve's object geo and part Ids. + + for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; + + // Cache/convert them + FHoudiniObjectInfo CurrentObjectInfo; + CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); + + // Start by getting the number of editable nodes + int32 EditableNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + + if (EditableNodeCount > 0) + { + TArray< HAPI_NodeId > EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) + continue; + + // Only catch editable curves + if (CurrentEditableGeoInfo.type != HAPI_GeoType::HAPI_GEOTYPE_CURVE) + continue; + + // Cook the editable node to get its parts + if (CurrentEditableGeoInfo.partCount <= 0) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentEditableGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentEditableGeoInfo.nodeId, + &CurrentEditableGeoInfo)); + } + + // Iterate on this geo's parts + for (int32 PartId = 0; PartId < CurrentEditableGeoInfo.partCount; ++PartId) + { + // Get part information. + HAPI_PartInfo CurrentHapiPartInfo; + FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + continue; + + if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE) + continue; + + // Get the editable curve's part name + FHoudiniEngineString hapiSTR(CurrentHapiPartInfo.nameSH); + FString PartName; + hapiSTR.ToFString(PartName); + + EditableCurveObjIds.Add(CurrentHapiObjectInfo.nodeId); + EditableCurveGeoIds.Add(CurrentEditableGeoInfo.nodeId); + EditableCurvePartIds.Add(CurrentHapiPartInfo.id); + EditableCurvePartNames.Add(PartName); + } + } + } + + } + + int32 Idx = 0; + for (auto& CurrentOutput : HAC->Outputs) + { + if (CurrentOutput->IsEditableNode()) + { + // The HAC is Loaded, re-assign node id to its editable curves + if (CurrentOutput->HasEditableNodeBuilt()) + { + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + for (auto& Pair : OutputObjects) + { + if (Idx >= EditableCurvePartIds.Num()) + break; + + UHoudiniSplineComponent * HoudiniSplineComponent = Cast(Pair.Value.OutputComponent); + if (HoudiniSplineComponent && !HoudiniSplineComponent->IsPendingKill()) + { + HoudiniSplineComponent->SetNodeId(EditableCurveGeoIds[Idx]); + + Pair.Key.ObjectId = EditableCurveObjIds[Idx]; + Pair.Key.GeoId = EditableCurveGeoIds[Idx]; + Pair.Key.PartId = EditableCurvePartIds[Idx]; + Pair.Key.PartName = EditableCurvePartNames[Idx]; + + Idx += 1; + } + } + } + // The HAC is a Duplication, re-construct output objects with attached duplicated editable curves, matching by part name + else + { + const TArray &Children = HAC->GetAttachChildren(); + for (auto & CurAttachedComp : Children) + { + if (!CurAttachedComp || CurAttachedComp->IsPendingKill()) + continue; + + if (!CurAttachedComp->IsA()) + continue; + + UHoudiniSplineComponent * CurAttachedSplineComp = Cast(CurAttachedComp); + if (!CurAttachedSplineComp) + continue; + + if (!CurAttachedSplineComp->IsEditableOutputCurve()) + continue; + + if (Idx >= EditableCurvePartIds.Num()) + break; + + // Found a match + if (CurAttachedSplineComp->GetGeoPartName().Equals(EditableCurvePartNames[Idx])) + { + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = EditableCurveObjIds[Idx]; + EditableSplineComponentIdentifier.GeoId = EditableCurveGeoIds[Idx]; + EditableSplineComponentIdentifier.PartId = EditableCurvePartIds[Idx]; + EditableSplineComponentIdentifier.PartName = EditableCurvePartNames[Idx]; + + CurAttachedSplineComp->SetNodeId(EditableSplineComponentIdentifier.GeoId); + + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + FHoudiniOutputObject& NewOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + NewOutputObject.OutputComponent = CurAttachedSplineComp; + + CurrentOutput->SetHasEditableNodeBuilt(true); + FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(CurAttachedSplineComp); + + Idx += 1; + break; + } + } + } + } + else + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); + } + + // Mark our outputs as loaded so they can be matched for potential reuse + // This indicates that the HGPO's ids are invalid and that HGPO should be matched using partnames instead + CurrentOutput->MarkAsLoaded(true); + } + + return true; +} + +// +bool +FHoudiniOutputTranslator::UploadChangedEditableOutput( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray &Outputs = HAC->Outputs; + + // Iterate through the outputs array of HAC. + for (auto& CurrentOutput : HAC->Outputs) + { + if (!CurrentOutput) + continue; + + // Only update the editable nodes that have been built before. + if (!CurrentOutput->IsEditableNode() || !CurrentOutput->HasEditableNodeBuilt()) + continue; + + for (auto& CurrentOutputObj : CurrentOutput->GetOutputObjects()) + { + UHoudiniSplineComponent* HoudiniSplineComponent = Cast(CurrentOutputObj.Value.OutputComponent); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (!HoudiniSplineComponent->HasChanged()) + continue; + + if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(HoudiniSplineComponent)) + HoudiniSplineComponent->MarkChanged(false); + else + HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); + } + } + + return true; +} + + +bool +FHoudiniOutputTranslator::BuildAllOutputs( + const HAPI_NodeId& AssetId, + UObject* InOuterObject, + TArray& InOldOutputs, + TArray& OutNewOutputs, + const bool& InOutputTemplatedGeos) +{ + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Get the AssetInfo + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + FString CurrentAssetName; + { + FHoudiniEngineString hapiSTR(AssetInfo.nameSH); + hapiSTR.ToFString(CurrentAssetName); + } + + // Retrieve the asset's transform. + // TODO: Unused?! + //FTransform AssetUnrealTransform; + //if (!FHoudiniEngineUtils::HapiGetAssetTransform(AssetId, AssetUnrealTransform)) + // return false; + + // Retrieve information about each object contained within our asset. + TArray ObjectInfos; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos)) + return false; + + //const int32 ObjectCount = ObjectInfos.Num(); + + // Retrieve transforms for each object in this asset. + TArray ObjectTransforms; + if (!FHoudiniEngineUtils::HapiGetObjectTransforms(AssetId, ObjectTransforms)) + return false; + + // Mark all the previous HGPOs on the outputs as stale + // This indicates that they were from a previous cook and should then be deleted + for (auto& CurOutput : InOldOutputs) + { + if (CurOutput) + CurOutput->MarkAllHGPOsAsStale(true); + } + + // For HF / Volumes, we only create new Outputs for height volume + // Store all the other volumes (masks etc) on the side and we will + // match them with theit corresponding height volume after + TArray UnassignedVolumeParts; + + TArray AllSockets; + + // Iterate through all objects. + int32 OutputIdx = 1; + for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; + + // Cache/convert them + FHoudiniObjectInfo CurrentObjectInfo; + CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); + + // Retrieve object name. + FString CurrentObjectName = CurrentObjectInfo.Name; + + // Get transformation for this object. + const HAPI_Transform & ObjectTransform = ObjectTransforms[ObjectId]; + FTransform TransformMatrix; + FHoudiniEngineUtils::TranslateHapiTransform(ObjectTransform, TransformMatrix); + + // TODO: Check transforms?? + + // Build an array of the geos we'll need to process + // In most case, it will only be the display geo, + // but we may also want to process editable geos as well + TArray GeoInfos; + + // Get the Display Geo's info + HAPI_GeoInfo DisplayHapiGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName); + } + else + { + // Add the display geo info to the array + GeoInfos.Add(DisplayHapiGeoInfo); + } + + // Handle the editable nodes for this geo + // Start by getting the number of editable nodes + int32 EditableNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + + if (EditableNodeCount > 0) + { + TArray< HAPI_NodeId > EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) + continue; + + // Add this geo to the geo info array + GeoInfos.Add(CurrentEditableGeoInfo); + } + } + + // Handle the templated nodes if desired + if (InOutputTemplatedGeos) + { + // Start by getting the number of templated nodes + int32 TemplatedNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, + HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, + true, &TemplatedNodeCount)); + + if (TemplatedNodeCount > 0) + { + TArray TemplatedNodeIds; + TemplatedNodeIds.SetNumUninitialized(TemplatedNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, TemplatedNodeIds.GetData(), TemplatedNodeCount)); + + for (int32 nTemplated = 0; nTemplated < TemplatedNodeCount; nTemplated++) + { + HAPI_GeoInfo CurrentTemplatedGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentTemplatedGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + TemplatedNodeIds[nTemplated], &CurrentTemplatedGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentTemplatedGeoInfo.isDisplayGeo) + continue; + + // We don't want all the nested template node IDs, + // as our HDA could potentially be using other HDAs with nested template flags + // Make sure the parent of the templated node is either the HDA, the current OBJ or the Display SOP + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurrentTemplatedGeoInfo.nodeId); + if (ParentId != CurrentHapiObjectInfo.nodeId + && ParentId != DisplayHapiGeoInfo.nodeId + && ParentId != AssetId) + { + continue; + } + + // Add this geo to the geo info array + GeoInfos.Add(CurrentTemplatedGeoInfo); + } + } + } + + // Iterates through the geos we want to process + for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) + { + // Cook editable/templated nodes to get their parts. + const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; + if ((CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) + || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0)) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, + &GeoInfos[GeoIdx])); + } + + // Cache/convert the display geo's info + FHoudiniGeoInfo CurrentGeoInfo; + CacheGeoInfo(CurrentHapiGeoInfo, CurrentGeoInfo); + + // Simply create an empty array for this geo's group names + // We might need it later for splitting + TArray GeoGroupNames; + bool HasSocketGroups = false; + + // Iterate on this geo's parts + for (int32 PartId = 0; PartId < CurrentGeoInfo.PartCount; ++PartId) + { + // Get part information. + HAPI_PartInfo CurrentHapiPartInfo; + FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); + + // If the geo is templated, cook it manually + if (CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + } + + bool bPartInfoFailed = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + { + bPartInfoFailed = true; + + // If the geo is templated, attempt to cook it manually + if(CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, nullptr); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, + &GeoInfos[GeoIdx])); + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + { + // We managed to get the templated part infos after cooking + bPartInfoFailed = false; + } + } + } + + if (bPartInfoFailed) + { + // Error retrieving part info. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d] unable to retrieve PartInfo - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId); + continue; + } + + // Convert/cache the part info + FHoudiniPartInfo CurrentPartInfo; + CachePartInfo(CurrentHapiPartInfo, CurrentPartInfo); + + // Retrieve part name. + FString CurrentPartName = CurrentPartInfo.Name; + + // Unsupported/Invalid part + if (CurrentPartInfo.Type == EHoudiniPartType::Invalid) + continue; + + // Update part/instancer type from the part infos + EHoudiniPartType CurrentPartType = EHoudiniPartType::Invalid; + EHoudiniInstancerType CurrentInstancerType = EHoudiniInstancerType::Invalid; + switch (CurrentHapiPartInfo.type) + { + case HAPI_PARTTYPE_BOX: + case HAPI_PARTTYPE_SPHERE: + case HAPI_PARTTYPE_MESH: + { + if (CurrentHapiGeoInfo.type == HAPI_GEOTYPE_CURVE) + { + // Closed curve will be seen as mesh + CurrentPartType = EHoudiniPartType::Curve; + } + else + { + CurrentPartType = EHoudiniPartType::Mesh; + + if (CurrentHapiObjectInfo.isInstancer) + { + if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // That part is actually an attribute instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + } + else + { + // That part is actually an instancer + CurrentPartType = EHoudiniPartType::Instancer; + CurrentInstancerType = EHoudiniInstancerType::ObjectInstancer; + } + + } + else if (CurrentHapiPartInfo.vertexCount <= 0 && CurrentHapiPartInfo.pointCount <= 0) + { + // No points, no vertices, we're likely invalid + CurrentPartType = EHoudiniPartType::Invalid; + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a mesh with no points or vertices - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + } + else if (CurrentHapiPartInfo.vertexCount <= 0) + { + // This is not an instancer, we do not have vertices, but we have points + // Maybe this is a point cloud with attribute override instancing + if(FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // Mark it as an instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + } + else + { + // No vertices, not an instancer, just a point cloud, consider ourself as invalid + CurrentPartType = EHoudiniPartType::Invalid; + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a point cloud mesh - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + } + } + } + } + break; + + case HAPI_PARTTYPE_CURVE: + { + // Make sure that this curve is not an an attribute instancer! + if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // Mark the part as an instancer it as an instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + } + else + { + // The curve is a curve! + CurrentPartType = EHoudiniPartType::Curve; + } + } + break; + + case HAPI_PARTTYPE_INSTANCER: + // This is a packed primitive instancer + CurrentPartType = EHoudiniPartType::Instancer; + CurrentInstancerType = EHoudiniInstancerType::PackedPrimitive; + break; + + case HAPI_PARTTYPE_VOLUME: + // Volume data, likely a Heightfield height / mask + CurrentPartType = EHoudiniPartType::Volume; + break; + + default: + // Unsupported Part Type + break; + } + + // There are no vertices AND no points and this part is not a packed prim instancer + if ((CurrentPartInfo.VertexCount <= 0 && CurrentPartInfo.PointCount <= 0) + && (CurrentPartType != EHoudiniPartType::Instancer || CurrentInstancerType != EHoudiniInstancerType::PackedPrimitive)) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] no points or vertices found - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + continue; + } + + // This is an instancer with no points. + if (CurrentHapiObjectInfo.isInstancer && CurrentHapiPartInfo.pointCount <= 0) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is instancer but has 0 points - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + continue; + } + + // Ignore invalid parts + if (CurrentPartType == EHoudiniPartType::Invalid) + continue; + + // Build the HGPO corresponding to this part + FHoudiniGeoPartObject currentHGPO; + currentHGPO.AssetId = AssetId; + currentHGPO.AssetName = CurrentAssetName; + + currentHGPO.ObjectId = CurrentHapiObjectInfo.nodeId; + currentHGPO.ObjectName = CurrentObjectName; + + currentHGPO.GeoId = CurrentHapiGeoInfo.nodeId; + + currentHGPO.PartId = CurrentHapiPartInfo.id; + + currentHGPO.Type = CurrentPartType; + currentHGPO.InstancerType = CurrentInstancerType; + + currentHGPO.TransformMatrix = TransformMatrix; + + currentHGPO.NodePath = TEXT(""); + + currentHGPO.bIsVisible = CurrentHapiObjectInfo.isVisible && !CurrentHapiPartInfo.isInstanced; + currentHGPO.bIsEditable = CurrentHapiGeoInfo.isEditable; + currentHGPO.bIsInstanced = CurrentHapiPartInfo.isInstanced; + // Never consider a display geo as templated! + currentHGPO.bIsTemplated = CurrentHapiGeoInfo.isDisplayGeo ? false : CurrentHapiGeoInfo.isTemplated; + + currentHGPO.bHasGeoChanged = CurrentHapiGeoInfo.hasGeoChanged; + currentHGPO.bHasPartChanged = CurrentHapiPartInfo.hasChanged; + currentHGPO.bHasMaterialsChanged = CurrentHapiGeoInfo.hasMaterialChanged; + currentHGPO.bHasTransformChanged = CurrentHapiObjectInfo.hasTransformChanged; + + // Copy the HAPI info caches + currentHGPO.ObjectInfo = CurrentObjectInfo; + currentHGPO.GeoInfo = CurrentGeoInfo; + currentHGPO.PartInfo = CurrentPartInfo; + + // We only support meshes for templated geos + if (currentHGPO.bIsTemplated && (CurrentPartType != EHoudiniPartType::Mesh)) + continue; + + // Update the HGPO's node path + FHoudiniEngineUtils::HapiGetNodePath(currentHGPO, currentHGPO.NodePath); + + // Try to get the custom part name from attribute + FString CustomPartName; + if (FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CustomPartName)) + currentHGPO.SetCustomPartName(CustomPartName); + else + currentHGPO.PartName = CurrentPartName; + + // + // Mesh Only - Extract split groups + // + // Extract the group names used by this part to see if it will require splitting + // Only meshes can be split, via their primitive groups + TArray< FString > SplitGroupNames; + if (CurrentPartType == EHoudiniPartType::Mesh) + { + if (!CurrentHapiPartInfo.isInstanced && GeoGroupNames.Num() > 0) + { + // We are not instanced and already have extracted the geo's group names + // We can simply reuse the Geo group names / socket groups + currentHGPO.SplitGroups = GeoGroupNames; + } + else + { + // We need to get the primitive group names from HAPI + int32 GroupCount = 0; + TArray GroupNames; + if (!FHoudiniEngineUtils::HapiGetGroupNames( + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + HAPI_GROUPTYPE_PRIM, CurrentHapiPartInfo.isInstanced, + GroupNames)) + { + GroupCount = 0; + GroupNames.Empty(); + } + + // Convert the string handles to FStrings + for (const FString& GroupName : GroupNames) + { + FString LodGroup = HAPI_UNREAL_GROUP_LOD_PREFIX; + FString CollisionGroup = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; + FString RenderedCollisionGroup = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; + if (GroupName.StartsWith(LodGroup, ESearchCase::IgnoreCase) + || GroupName.StartsWith(CollisionGroup, ESearchCase::IgnoreCase) + || GroupName.StartsWith(RenderedCollisionGroup, ESearchCase::IgnoreCase)) + //|| GroupName.StartsWith(HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX, ESearchCase::IgnoreCase)) + { + // Split by collisions / lods + currentHGPO.SplitGroups.Add(GroupName); + } + } + + // Sort the Group name array by name, + // this will order the LODs and other incremental group names + currentHGPO.SplitGroups.Sort(); + + // If this part is not instanced, we can copy the geo + // group names so we can reuse them for another part + if (!CurrentHapiPartInfo.isInstanced) + { + GeoGroupNames = currentHGPO.SplitGroups; + } + } + } + + // + // Volume Only - Extract volume name/tile index + // + // Extract the volume's name, and see if a tile attribute is present + FHoudiniVolumeInfo CurrentVolumeInfo; + if (CurrentPartType == EHoudiniPartType::Volume) + { + // Get this volume's info + HAPI_VolumeInfo CurrentHapiVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentHapiVolumeInfo); + + bool bVolumeValid = true; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + &CurrentHapiVolumeInfo)) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.tupleSize != 1) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.zLength != 1) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + { + bVolumeValid = false; + } + + // Only cache valid volumes + if (bVolumeValid) + { + // Convert/Cache the volume info + CacheVolumeInfo(CurrentHapiVolumeInfo, CurrentVolumeInfo); + + // Get the volume's name + currentHGPO.VolumeName = CurrentVolumeInfo.Name; + + // Now see if this volume has a tile attribute + TArray TileValues; + if (FHoudiniEngineUtils::GetTileAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, TileValues, HAPI_ATTROWNER_PRIM)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + currentHGPO.VolumeTileIndex = TileValues[0]; + else + currentHGPO.VolumeTileIndex = -1; + } + } + } + currentHGPO.VolumeInfo = CurrentVolumeInfo; + + // Cache the curve info as well + FHoudiniCurveInfo CurrentCurveInfo; + if (CurrentPartType == EHoudiniPartType::Curve) + { + HAPI_CurveInfo CurrentHapiCurveInfo; + FHoudiniApi::CurveInfo_Init(&CurrentHapiCurveInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + &CurrentHapiCurveInfo)) + { + // Cache/Convert this part's curve info + CacheCurveInfo(CurrentHapiCurveInfo, CurrentCurveInfo); + } + } + currentHGPO.CurveInfo = CurrentCurveInfo; + + + // TODO: + // DONE? bake folders are handled out of this loop? + // See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute + //TArray BakeFolderOverrides; + + // Extract socket points + FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); + FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); + + // See if we have an existing output that matches this HGPO or if we need to create a new one + bool IsFoundOutputValid = false; + UHoudiniOutput ** FoundHoudiniOutput = nullptr; + // We handle volumes differently than other outputs types, as a single HF output has multiple HGPOs + if (currentHGPO.Type != EHoudiniPartType::Volume) + { + // Look in the previous output if we have a match + FoundHoudiniOutput = InOldOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(currentHGPO) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + + } + else + { + // Look in the previous outputs if we have a match + FoundHoudiniOutput = InOldOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, true) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + + // If we dont have a match in the old maps, also look in the newly created outputs + if (!IsFoundOutputValid) + { + FoundHoudiniOutput = OutNewOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, false) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + } + } + + UHoudiniOutput * HoudiniOutput = nullptr; + if (IsFoundOutputValid) + { + // We can reuse the existing output + HoudiniOutput = *FoundHoudiniOutput; + HoudiniOutput->SetIsUpdating(true); + // Transfer this output from the old array to the new one + InOldOutputs.Remove(HoudiniOutput); + } + else + { + // We couldn't find a valid output object, so create a new one + + // If the current part is a volume, only create a new output object + // if the volume's name is "height", if not store the HGPO aside + if (currentHGPO.Type == EHoudiniPartType::Volume + && !currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) + { + UnassignedVolumeParts.Add(currentHGPO); + continue; + } + + // Create a new output object + //FString OutputName = TEXT("Output") + FString::FromInt(OutputIdx++); + HoudiniOutput = NewObject( + InOuterObject, + UHoudiniOutput::StaticClass(), + NAME_None,//FName(*OutputName), + RF_NoFlags); + + // Make sure the created object is valid + if (!HoudiniOutput || HoudiniOutput->IsPendingKill()) + { + //HOUDINI_LOG_WARNING("Failed to create asset output"); + continue; + } + + // Mark if the HoudiniOutput is editable + HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); + } + + // Add the HGPO to the output + HoudiniOutput->AddNewHGPO(currentHGPO); + // Add this output object to the new ouput array + OutNewOutputs.AddUnique(HoudiniOutput); + } + } + } + + // Update the output/HGPO associations from the map + // Clear the old HGPO since we don't need them anymore + for (auto& CurrentOuput : OutNewOutputs) + { + if (!CurrentOuput || CurrentOuput->IsPendingKill()) + continue; + + CurrentOuput->DeleteAllStaleHGPOs(); + } + + // If we have unassigned volumes, + // try to find their corresponding output + if (UnassignedVolumeParts.Num() > 0) + { + for (auto& currentVolumeHGPO : UnassignedVolumeParts) + { + UHoudiniOutput ** FoundHoudiniOutput = OutNewOutputs.FindByPredicate( + [currentVolumeHGPO](UHoudiniOutput* Output) + { + return Output ? Output->HeightfieldMatch(currentVolumeHGPO, false) : false; + }); + + if (!FoundHoudiniOutput || !(*FoundHoudiniOutput) || (*FoundHoudiniOutput)->IsPendingKill()) + { + // Skip - consider this volume as invalid + continue; + } + + // Add this HGPO to the output + (*FoundHoudiniOutput)->AddNewHGPO(currentVolumeHGPO); + } + } + + // All our output objects now have their HGPO assigned + // We can now parse them to update the output type + for (auto& Output : OutNewOutputs) + { + Output->UpdateOutputType(); + } + + return true; +} + +bool +FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray& Outputs = HAC->Outputs; + + // Iterate through the outputs array of HAC. + for (int32 Index = 0; Index < HAC->GetNumOutputs(); ++Index) + { + UHoudiniOutput* CurrentOutput = HAC->GetOutputAt(Index); + if (!CurrentOutput) + continue; + + if (!HAC->IsOutputTypeSupported(CurrentOutput->GetType())) + continue; + + switch (CurrentOutput->GetType()) + { + case EHoudiniOutputType::Instancer: + { + bool bNeedToRecreateInstancers = false; + for (auto& Iter : CurrentOutput->GetInstancedOutputs()) + { + FHoudiniInstancedOutput& InstOutput = Iter.Value; + if (!InstOutput.bChanged) + continue; + + /* + FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( + InstOutput, Iter.Key, CurrentOutput, HAC); + */ + + // TODO: + // UpdateChangedInstancedOutput needs some improvements + // as it currently destroy too many components. + // For now, we'll update all the instancers + bNeedToRecreateInstancers = true; + + InstOutput.MarkChanged(false); + } + + if (bNeedToRecreateInstancers) + { + if (HAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation || HAC->HasBeenLoaded()) + { + // Instantiate the HDA if it's not been + // This is because CreateAllInstancersFromHoudiniOutput() actually reads the transform from HAPI + // Calling it on a HDA not yet instantiated causes a crash... + HAC->AssetState = EHoudiniAssetState::PreInstantiation; + } + else + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurrentOutput, Outputs, HAC); + } + } + } + break; + + case EHoudiniOutputType::Curve: + { + //FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); + } + break; + + default: + break; + } + } + + return true; +} + +void +FHoudiniOutputTranslator::CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache) +{ + FHoudiniEngineString hapiSTR(InObjInfo.nameSH); + hapiSTR.ToFString(OutObjInfoCache.Name); + //OutObjInfoCache.Name = InObjInfo.nameSH; + + OutObjInfoCache.NodeId = InObjInfo.nodeId; + OutObjInfoCache.ObjectToInstanceID = InObjInfo.objectToInstanceId; + + OutObjInfoCache.bHasTransformChanged = InObjInfo.hasTransformChanged; + OutObjInfoCache.bHaveGeosChanged = InObjInfo.haveGeosChanged; + + OutObjInfoCache.bIsVisible = InObjInfo.isVisible; + OutObjInfoCache.bIsInstancer = InObjInfo.isInstancer; + OutObjInfoCache.bIsInstanced = InObjInfo.isInstanced; + + OutObjInfoCache.GeoCount = InObjInfo.geoCount; +}; + +EHoudiniGeoType +FHoudiniOutputTranslator::ConvertHapiGeoType(const HAPI_GeoType& InType) +{ + EHoudiniGeoType OutType = EHoudiniGeoType::Invalid; + switch (InType) + { + case HAPI_GEOTYPE_DEFAULT: + OutType = EHoudiniGeoType::Default; + break; + + case HAPI_GEOTYPE_INTERMEDIATE: + OutType = EHoudiniGeoType::Intermediate; + break; + + case HAPI_GEOTYPE_INPUT: + OutType = EHoudiniGeoType::Input; + break; + + case HAPI_GEOTYPE_CURVE: + OutType = EHoudiniGeoType::Curve; + break; + + default: + OutType = EHoudiniGeoType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache) +{ + OutGeoInfoCache.Type = ConvertHapiGeoType(InGeoInfo.type); + + FHoudiniEngineString hapiSTR(InGeoInfo.nameSH); + hapiSTR.ToFString(OutGeoInfoCache.Name); + + OutGeoInfoCache.NodeId = InGeoInfo.nodeId; + + OutGeoInfoCache.bIsEditable = InGeoInfo.isEditable; + OutGeoInfoCache.bIsTemplated = InGeoInfo.isTemplated; + OutGeoInfoCache.bIsDisplayGeo = InGeoInfo.isDisplayGeo; + OutGeoInfoCache.bHasGeoChanged = InGeoInfo.hasGeoChanged; + OutGeoInfoCache.bHasMaterialChanged = InGeoInfo.hasMaterialChanged; + + OutGeoInfoCache.PartCount = InGeoInfo.partCount; + OutGeoInfoCache.PointGroupCount = InGeoInfo.pointGroupCount; + OutGeoInfoCache.PrimitiveGroupCount = InGeoInfo.primitiveGroupCount; +}; + + +EHoudiniPartType +FHoudiniOutputTranslator::ConvertHapiPartType(const HAPI_PartType& InType) +{ + EHoudiniPartType OutType = EHoudiniPartType::Invalid; + switch (InType) + { + case HAPI_PARTTYPE_BOX: + case HAPI_PARTTYPE_SPHERE: + case HAPI_PARTTYPE_MESH: + OutType = EHoudiniPartType::Mesh; + break; + + case HAPI_PARTTYPE_CURVE: + OutType = EHoudiniPartType::Curve; + break; + + case HAPI_PARTTYPE_INSTANCER: + OutType = EHoudiniPartType::Instancer; + break; + + case HAPI_PARTTYPE_VOLUME: + OutType = EHoudiniPartType::Volume; + break; + + default: + OutType = EHoudiniPartType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache) +{ + OutPartInfoCache.PartId = InPartInfo.id; + + FHoudiniEngineString hapiSTR(InPartInfo.nameSH); + hapiSTR.ToFString(OutPartInfoCache.Name); + + OutPartInfoCache.Type = ConvertHapiPartType(InPartInfo.type); + + OutPartInfoCache.FaceCount = InPartInfo.faceCount; + OutPartInfoCache.VertexCount = InPartInfo.vertexCount; + OutPartInfoCache.PointCount = InPartInfo.pointCount; + + OutPartInfoCache.PointAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_POINT]; + OutPartInfoCache.VertexAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX]; + OutPartInfoCache.PrimitiveAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_PRIM]; + OutPartInfoCache.DetailAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL]; + + OutPartInfoCache.bIsInstanced = InPartInfo.isInstanced; + + OutPartInfoCache.InstancedPartCount = InPartInfo.instancedPartCount; + OutPartInfoCache.InstanceCount = InPartInfo.instanceCount; + + OutPartInfoCache.bHasChanged = InPartInfo.hasChanged; +}; + +void +FHoudiniOutputTranslator::CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache) +{ + FHoudiniEngineString hapiSTR(InVolumeInfo.nameSH); + hapiSTR.ToFString(OutVolumeInfoCache.Name); + + OutVolumeInfoCache.bIsVDB = (InVolumeInfo.type == HAPI_VOLUMETYPE_VDB); // replaces VolumeType Type; + + OutVolumeInfoCache.TupleSize = InVolumeInfo.tupleSize; + OutVolumeInfoCache.bIsFloat = (InVolumeInfo.storage == HAPI_STORAGETYPE_FLOAT); // replaces StorageType StorageType; + OutVolumeInfoCache.TileSize = InVolumeInfo.tileSize; + + FHoudiniEngineUtils::TranslateHapiTransform(InVolumeInfo.transform, OutVolumeInfoCache.Transform); + OutVolumeInfoCache.bHasTaper = InVolumeInfo.hasTaper; + + OutVolumeInfoCache.XLength = InVolumeInfo.xLength; + OutVolumeInfoCache.YLength = InVolumeInfo.yLength; + OutVolumeInfoCache.ZLength = InVolumeInfo.zLength; + + OutVolumeInfoCache.MinX = InVolumeInfo.minX; + OutVolumeInfoCache.MinY = InVolumeInfo.minY; + OutVolumeInfoCache.MinZ = InVolumeInfo.minZ; + + OutVolumeInfoCache.XTaper = InVolumeInfo.xTaper; + OutVolumeInfoCache.YTaper = InVolumeInfo.yTaper; +}; + +EHoudiniCurveType +FHoudiniOutputTranslator::ConvertHapiCurveType(const HAPI_CurveType& InType) +{ + EHoudiniCurveType OutType = EHoudiniCurveType::Invalid; + switch (InType) + { + case HAPI_CURVETYPE_LINEAR: + OutType = EHoudiniCurveType::Polygon; + break; + + case HAPI_CURVETYPE_NURBS: + OutType = EHoudiniCurveType::Nurbs; + break; + + case HAPI_CURVETYPE_BEZIER: + OutType = EHoudiniCurveType::Bezier; + break; + + case HAPI_CURVETYPE_MAX: + OutType = EHoudiniCurveType::Points; + break; + + default: + OutType = EHoudiniCurveType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache) +{ + OutCurveInfoCache.Type = ConvertHapiCurveType(InCurveInfo.curveType); + + OutCurveInfoCache.CurveCount = InCurveInfo.curveCount; + OutCurveInfoCache.VertexCount = InCurveInfo.vertexCount; + OutCurveInfoCache.KnotCount = InCurveInfo.knotCount; + + OutCurveInfoCache.bIsPeriodic = InCurveInfo.isPeriodic; + OutCurveInfoCache.bIsRational = InCurveInfo.isRational; + + OutCurveInfoCache.Order = InCurveInfo.order; + + OutCurveInfoCache.bHasKnots = InCurveInfo.hasKnots; +}; + + +void +FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC) +{ + if (!IsValid(InHAC)) + return; + + // DO NOT MANUALLY DESTROY THE OLD/DANGLING OUTPUTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + // Simply clearing the array is enough + for (auto& OldOutput : InHAC->Outputs) + { + ClearOutput(OldOutput); + } + + InHAC->Outputs.Empty(); +} + +void +FHoudiniOutputTranslator::ClearOutput(UHoudiniOutput* Output) +{ + switch (Output->GetType()) + { + case EHoudiniOutputType::Landscape: + { + for (auto& OutputObject : Output->GetOutputObjects()) + { + // Currently, any Landscape managed by an HDA is always present in the current level. + // Only when it gets baked will Landscapes be serialized to other levels so for now + // assume that a landscape should be available, unless explicitly deleted the user. + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.Value.OutputObject); + if (!LandscapePtr) + continue; + + ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); + + if (!IsValid(Landscape) || !Landscape->IsValidLowLevel()) + continue; + + Landscape->UnregisterAllComponents(); + Landscape->Destroy(); + + // if (Output->IsLandscapeWorldComposition()) + // { + // TSoftObjectPtr LandscapeSoftPtr = LandscapePtr->GetSoftPtr(); + // + // FString SoftPtrPath = LandscapeSoftPtr.ToSoftObjectPath().ToString(); + // + // FString FileName = FPaths::GetBaseFilename(SoftPtrPath); + // FString FileDirectory = FPaths::GetPath(SoftPtrPath); + // + // FString ContentPath = FPaths::ProjectContentDir(); + // FString ContentFullPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*ContentPath); + // + // FString AbsoluteFilePath = ContentFullPath + FileDirectory.Mid(5, FileDirectory.Len() - 5) + "/" + FPaths::GetBaseFilename(FileName) + ".umap"; + // + // FPlatformFileManager::Get().GetPlatformFile().FileExists(*(AbsoluteFilePath)); + // + // FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*(AbsoluteFilePath)); + // } + // else + // { + + // } + } + } + break; + + case EHoudiniOutputType::Instancer: + { + for (auto& OutputObject : Output->GetOutputObjects()) + { + // Is this a foliage instancer? Check if the component is owned by an AInstancedFoliageActor + UActorComponent* const Component = Cast(OutputObject.Value.OutputComponent); + if (!IsValid(Component)) + continue; + AActor* const OwnerActor = Component->GetOwner(); + if (!IsValid(OwnerActor) || !OwnerActor->IsA()) + continue; + + UHierarchicalInstancedStaticMeshComponent* const FoliageHISMC = Cast(Component); + if (IsValid(FoliageHISMC)) + { + // Find the parent component: the foliage component outer, otherwise, if a houdini asset actor, the + // houdini asset component, otherwise for a normal actor its root component, finally try and see + // if the outer itself is a component. + USceneComponent* ParentComponent = Cast(FoliageHISMC->GetOuter()); + if (!IsValid(ParentComponent)) + { + UObject* const OutputOuter = Output->GetOuter(); + if (IsValid(OutputOuter)) + { + if (OutputOuter->IsA()) + { + ParentComponent = Cast(OutputOuter)->GetHoudiniAssetComponent(); + } + else if (OutputOuter->IsA()) + { + ParentComponent = Cast(OutputOuter)->GetRootComponent(); + } + else + { + ParentComponent = Cast(OutputOuter); + } + } + } + if (IsValid(ParentComponent)) + { + FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, ParentComponent); + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + } + } + } + } + break; + // ... Other output types ...// + + default: + break; + + } + + Output->Clear(); +} + + +bool +FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName) +{ + HAPI_AttributeInfo CustomPartNameInfo; + FHoudiniApi::AttributeInfo_Init(&CustomPartNameInfo); + + bool bHasCustomName = false; + TArray CustomNames; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, CustomPartNameInfo, CustomNames)) + { + // Look for the v2 attribute (unreal_output_name) + bHasCustomName = true; + } + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, CustomPartNameInfo, CustomNames)) + { + // If we couldnt find the new attribute, use the legacy v1 attribute (unreal_generated_mesh_name) + bHasCustomName = true; + } + + if (!bHasCustomName) + return false; + + if (CustomNames.Num() <= 0) + return false; + + OutCustomPartName = CustomNames[0]; + + if (OutCustomPartName.IsEmpty()) + return false; + + return true; +} + +void +FHoudiniOutputTranslator::GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) + return; + + FString BakeFolderOverride = FString(); + + FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); + + // If the TempCookFolder of the HAC is non-empty and is different from the override path. + // do not override it if the current temp cook path is valid. (it was user specified) + if (!HAC->BakeFolder.Path.IsEmpty() && !HAC->BakeFolder.Path.Equals(BakeFolderOverride)) + return; + + HAC->BakeFolder.Path = BakeFolderOverride; +} + +void +FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) + return; + + FString TempFolderOverride = FString(); + + HAPI_AttributeInfo TempFolderAttriInfo; + FHoudiniApi::AttributeInfo_Init(&TempFolderAttriInfo); + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_DETAIL)) + { + TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); + } + else + { + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) + { + TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); + } + } + + if (TempFolderOverride.StartsWith("Game/")) + { + TempFolderOverride = "/" + TempFolderOverride; + } + + FString AbsoluteOverridePath; + if (TempFolderOverride.StartsWith("/Game/")) + { + FString RelativePath = FPaths::ProjectContentDir() + TempFolderOverride.Mid(6, TempFolderOverride.Len() - 6); + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + if (!TempFolderOverride.IsEmpty()) + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*TempFolderOverride); + } + + // Check Validity of the path + if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) + { + // Only display a warning if the path is invalid, empty is fine + if(!AbsoluteOverridePath.IsEmpty()) + HOUDINI_LOG_WARNING(TEXT("Invalid override temporary cook path: %s"), *TempFolderOverride); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + TempFolderOverride = HoudiniRuntimeSettings->DefaultTemporaryCookFolder; + } + + // If the TempCookFolder of the HAC is non-empty and is different from the override path. + // do not override it if the current temp cook path is valid. (it was user specified) + if (!HAC->TemporaryCookFolder.Path.IsEmpty() && !HAC->TemporaryCookFolder.Path.Equals(TempFolderOverride)) + return; + + HAC->TemporaryCookFolder.Path = TempFolderOverride; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h index 76b45c06e..371e0113f 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h @@ -1,94 +1,94 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -class UHoudiniOutput; -class UHoudiniAssetComponent; - -struct FHoudiniObjectInfo; -struct FHoudiniGeoInfo; -struct FHoudiniPartInfo; -struct FHoudiniVolumeInfo; -struct FHoudiniCurveInfo; - -enum class EHoudiniOutputType : uint8; -enum class EHoudiniGeoType : uint8; -enum class EHoudiniPartType : uint8; -enum class EHoudiniCurveType : int8; - -struct HOUDINIENGINE_API FHoudiniOutputTranslator -{ - // - static bool UpdateOutputs( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate, - bool& bOutHasHoudiniStaticMeshOutput); - - // - static bool BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies=false); - - // - static bool UpdateLoadedOutputs(UHoudiniAssetComponent* HAC); - - // - static bool UploadChangedEditableOutput( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate); - // - static bool BuildAllOutputs( - const HAPI_NodeId& AssetId, - UObject* InOuterObject, - TArray& InOldOutputs, - TArray& OutNewOutputs, - const bool& InOutputTemplatedGeos); - - static bool UpdateChangedOutputs( - UHoudiniAssetComponent* HAC); - - // Helpers functions used to convert HAPI types - static EHoudiniGeoType ConvertHapiGeoType(const HAPI_GeoType& InType); - static EHoudiniPartType ConvertHapiPartType(const HAPI_PartType& InType); - static EHoudiniCurveType ConvertHapiCurveType(const HAPI_CurveType& InType); - - // Helper functions used to cache HAPI infos - static void CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache); - static void CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache); - static void CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache); - static void CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache); - static void CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache); - - // Helper to clear the outputs of the houdini asset component - static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC); - // Helper to clear an individual UHoudiniOutput - static void ClearOutput(UHoudiniOutput* Output); - - static bool GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName); - static void GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC); - static void GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +class UHoudiniOutput; +class UHoudiniAssetComponent; + +struct FHoudiniObjectInfo; +struct FHoudiniGeoInfo; +struct FHoudiniPartInfo; +struct FHoudiniVolumeInfo; +struct FHoudiniCurveInfo; + +enum class EHoudiniOutputType : uint8; +enum class EHoudiniGeoType : uint8; +enum class EHoudiniPartType : uint8; +enum class EHoudiniCurveType : int8; + +struct HOUDINIENGINE_API FHoudiniOutputTranslator +{ + // + static bool UpdateOutputs( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate, + bool& bOutHasHoudiniStaticMeshOutput); + + // + static bool BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies=false); + + // + static bool UpdateLoadedOutputs(UHoudiniAssetComponent* HAC); + + // + static bool UploadChangedEditableOutput( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate); + // + static bool BuildAllOutputs( + const HAPI_NodeId& AssetId, + UObject* InOuterObject, + TArray& InOldOutputs, + TArray& OutNewOutputs, + const bool& InOutputTemplatedGeos); + + static bool UpdateChangedOutputs( + UHoudiniAssetComponent* HAC); + + // Helpers functions used to convert HAPI types + static EHoudiniGeoType ConvertHapiGeoType(const HAPI_GeoType& InType); + static EHoudiniPartType ConvertHapiPartType(const HAPI_PartType& InType); + static EHoudiniCurveType ConvertHapiCurveType(const HAPI_CurveType& InType); + + // Helper functions used to cache HAPI infos + static void CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache); + static void CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache); + static void CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache); + static void CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache); + static void CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache); + + // Helper to clear the outputs of the houdini asset component + static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC); + // Helper to clear an individual UHoudiniOutput + static void ClearOutput(UHoudiniOutput* Output); + + static bool GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName); + static void GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC); + static void GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp index 73fe067d3..4111d45ae 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp @@ -1,81 +1,81 @@ -#include "HoudiniPDGImporterMessages.h" - - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage() - : FilePath() - , Name() - , TOPNodeId(-1) - , WorkItemId(-1) -{ - -} - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams -) - : FilePath(InFilePath) - , Name(InName) - , TOPNodeId(-1) - , WorkItemId(-1) -{ - SetPackageParams(InPackageParams); -} - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - HAPI_NodeId InTOPNodeId, - HAPI_PDG_WorkitemId InWorkItemId -) - : FilePath(InFilePath) - , Name(InName) - , TOPNodeId(InTOPNodeId) - , WorkItemId(InWorkItemId) -{ - SetPackageParams(InPackageParams); -} - -void FHoudiniPDGImportBGEOMessage::SetPackageParams(const FHoudiniPackageParams& InPackageParams) -{ - PackageParams = InPackageParams; - PackageParams.OuterPackage = nullptr; -} - -void FHoudiniPDGImportBGEOMessage::PopulatePackageParams(FHoudiniPackageParams& OutPackageParams) const -{ - UObject* KeepOuter = OutPackageParams.OuterPackage; - OutPackageParams = PackageParams; - OutPackageParams.OuterPackage = KeepOuter; -} - -FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage() - : ImportResult(EHoudiniPDGImportBGEOResult::HPIBR_Failed) -{ - -} - -FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - const EHoudiniPDGImportBGEOResult& InImportResult -) - : FHoudiniPDGImportBGEOMessage(InFilePath, InName, InPackageParams) - , ImportResult(InImportResult) -{ -} - -FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage() - : CommandletGuid() -{ - -} - -FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid) - : CommandletGuid(InCommandletGuid) -{ - -} +#include "HoudiniPDGImporterMessages.h" + + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage() + : FilePath() + , Name() + , TOPNodeId(-1) + , WorkItemId(-1) +{ + +} + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams +) + : FilePath(InFilePath) + , Name(InName) + , TOPNodeId(-1) + , WorkItemId(-1) +{ + SetPackageParams(InPackageParams); +} + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + HAPI_NodeId InTOPNodeId, + HAPI_PDG_WorkitemId InWorkItemId +) + : FilePath(InFilePath) + , Name(InName) + , TOPNodeId(InTOPNodeId) + , WorkItemId(InWorkItemId) +{ + SetPackageParams(InPackageParams); +} + +void FHoudiniPDGImportBGEOMessage::SetPackageParams(const FHoudiniPackageParams& InPackageParams) +{ + PackageParams = InPackageParams; + PackageParams.OuterPackage = nullptr; +} + +void FHoudiniPDGImportBGEOMessage::PopulatePackageParams(FHoudiniPackageParams& OutPackageParams) const +{ + UObject* KeepOuter = OutPackageParams.OuterPackage; + OutPackageParams = PackageParams; + OutPackageParams.OuterPackage = KeepOuter; +} + +FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage() + : ImportResult(EHoudiniPDGImportBGEOResult::HPIBR_Failed) +{ + +} + +FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + const EHoudiniPDGImportBGEOResult& InImportResult +) + : FHoudiniPDGImportBGEOMessage(InFilePath, InName, InPackageParams) + , ImportResult(InImportResult) +{ +} + +FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage() + : CommandletGuid() +{ + +} + +FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid) + : CommandletGuid(InCommandletGuid) +{ + +} diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h index c09b330a4..0bcb0717f 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h @@ -1,161 +1,161 @@ -#pragma once - -#include "CoreMinimal.h" -#include "Misc/Guid.h" - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniPackageParams.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInstanceTranslator.h" - -#include "HoudiniPDGImporterMessages.generated.h" - -// Message used to find/discover running commandlets -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEODiscoverMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEODiscoverMessage(); - - FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid); - - // The GUID of the commandlet we are looking for - UPROPERTY() - FGuid CommandletGuid; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEOMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEOMessage(); - - FHoudiniPDGImportBGEOMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams); - - FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - HAPI_NodeId InTOPNodeId, - HAPI_PDG_WorkitemId InWorkItemId); - - void SetPackageParams(const FHoudiniPackageParams& InPackageParams); - - void PopulatePackageParams(FHoudiniPackageParams &OutPackageParams) const; - - // BGEO file path - UPROPERTY() - FString FilePath; - - // PDG work item name - UPROPERTY() - FString Name; - - // TOP/PDG info - // TOP node ID - UPROPERTY() - // HAPI_NodeId TOPNodeId; - int32 TOPNodeId; - - // Work item id - UPROPERTY() - // HAPI_PDG_WorkitemId WorkItemId; - int32 WorkItemId; - - // Package params for the asset - UPROPERTY() - FHoudiniPackageParams PackageParams; -}; - - -UENUM() -enum class EHoudiniPDGImportBGEOResult : uint8 -{ - // Create uassets from the bgeo completely failed. - HPIBR_Failed UMETA(DisplayName="Failed"), - - // Successfully created uassets for all content in the bgeo - HPIBR_Success UMETA(DisplayName = "Success"), - - // Some uassets were created, but there were unsupported objects in the bgeo as well - HPIBR_PartialSuccess UMETA(DisplayName = "Partial Success"), - - HIBPR_MAX -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniGenericAttributes -{ -public: - GENERATED_BODY() - - FHoudiniGenericAttributes() {}; - FHoudiniGenericAttributes(const TArray& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; - FHoudiniGenericAttributes(TArray&& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; - - UPROPERTY() - TArray PropertyAttributes; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutputObject -{ -public: - GENERATED_BODY(); - - UPROPERTY() - FHoudiniOutputObjectIdentifier Identifier; - - UPROPERTY() - FString PackagePath; - - UPROPERTY() - FHoudiniGenericAttributes GenericAttributes; - - UPROPERTY() - TMap CachedAttributes; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutput -{ -public: - GENERATED_BODY(); - - UPROPERTY() - TArray HoudiniGeoPartObjects; - - UPROPERTY() - TArray OutputObjects; - - UPROPERTY() - TArray InstancedOutputPartData; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEOResultMessage : public FHoudiniPDGImportBGEOMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEOResultMessage(); - - FHoudiniPDGImportBGEOResultMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams, const EHoudiniPDGImportBGEOResult& InImportResult); - - void operator=(const FHoudiniPDGImportBGEOMessage& InRHS) { (*static_cast(this)) = InRHS; } - - // Result of the bgeo import -> uassets - UPROPERTY() - EHoudiniPDGImportBGEOResult ImportResult; - - UPROPERTY() - TArray Outputs; - -}; +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Guid.h" + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniPackageParams.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInstanceTranslator.h" + +#include "HoudiniPDGImporterMessages.generated.h" + +// Message used to find/discover running commandlets +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEODiscoverMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEODiscoverMessage(); + + FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid); + + // The GUID of the commandlet we are looking for + UPROPERTY() + FGuid CommandletGuid; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEOMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEOMessage(); + + FHoudiniPDGImportBGEOMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams); + + FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + HAPI_NodeId InTOPNodeId, + HAPI_PDG_WorkitemId InWorkItemId); + + void SetPackageParams(const FHoudiniPackageParams& InPackageParams); + + void PopulatePackageParams(FHoudiniPackageParams &OutPackageParams) const; + + // BGEO file path + UPROPERTY() + FString FilePath; + + // PDG work item name + UPROPERTY() + FString Name; + + // TOP/PDG info + // TOP node ID + UPROPERTY() + // HAPI_NodeId TOPNodeId; + int32 TOPNodeId; + + // Work item id + UPROPERTY() + // HAPI_PDG_WorkitemId WorkItemId; + int32 WorkItemId; + + // Package params for the asset + UPROPERTY() + FHoudiniPackageParams PackageParams; +}; + + +UENUM() +enum class EHoudiniPDGImportBGEOResult : uint8 +{ + // Create uassets from the bgeo completely failed. + HPIBR_Failed UMETA(DisplayName="Failed"), + + // Successfully created uassets for all content in the bgeo + HPIBR_Success UMETA(DisplayName = "Success"), + + // Some uassets were created, but there were unsupported objects in the bgeo as well + HPIBR_PartialSuccess UMETA(DisplayName = "Partial Success"), + + HIBPR_MAX +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniGenericAttributes +{ +public: + GENERATED_BODY() + + FHoudiniGenericAttributes() {}; + FHoudiniGenericAttributes(const TArray& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; + FHoudiniGenericAttributes(TArray&& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; + + UPROPERTY() + TArray PropertyAttributes; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutputObject +{ +public: + GENERATED_BODY(); + + UPROPERTY() + FHoudiniOutputObjectIdentifier Identifier; + + UPROPERTY() + FString PackagePath; + + UPROPERTY() + FHoudiniGenericAttributes GenericAttributes; + + UPROPERTY() + TMap CachedAttributes; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutput +{ +public: + GENERATED_BODY(); + + UPROPERTY() + TArray HoudiniGeoPartObjects; + + UPROPERTY() + TArray OutputObjects; + + UPROPERTY() + TArray InstancedOutputPartData; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEOResultMessage : public FHoudiniPDGImportBGEOMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEOResultMessage(); + + FHoudiniPDGImportBGEOResultMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams, const EHoudiniPDGImportBGEOResult& InImportResult); + + void operator=(const FHoudiniPDGImportBGEOMessage& InRHS) { (*static_cast(this)) = InRHS; } + + // Result of the bgeo import -> uassets + UPROPERTY() + EHoudiniPDGImportBGEOResult ImportResult; + + UPROPERTY() + TArray Outputs; + +}; diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp index 3a2186537..89af09131 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp @@ -1,2057 +1,2057 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGManager.h" - -#include "Modules/ModuleManager.h" -#include "MessageEndpointBuilder.h" -#include "HAL/FileManager.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPackageParams.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniPDGTranslator.h" -#include "HoudiniPDGImporterMessages.h" - -#include "HAPI/HAPI_Common.h" - -HOUDINI_PDG_DEFINE_LOG_CATEGORY(); - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniPDGManager::FHoudiniPDGManager() -{ -} - -FHoudiniPDGManager::~FHoudiniPDGManager() -{ -} - -bool -FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) -{ - if (!InHAC || InHAC->IsPendingKill()) - return false; - - int32 AssetId = InHAC->GetAssetId(); - if (AssetId < 0) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) - return false; - - // Create a new PDG Asset Link Object - bool bRegisterPDGAssetLink = false; - UHoudiniPDGAssetLink* PDGAssetLink = InHAC->GetPDGAssetLink(); - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - { - PDGAssetLink = NewObject(InHAC, UHoudiniPDGAssetLink::StaticClass(), NAME_None, RF_Transactional); - bRegisterPDGAssetLink = true; - } - - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - PDGAssetLink->AssetID = AssetId; - - // Get the HDA's info - HAPI_NodeInfo AssetInfo; - FHoudiniApi::NodeInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, &AssetInfo), false); - - // Get the node path - FString AssetNodePath; - FHoudiniEngineString::ToFString(AssetInfo.internalNodePathSH, PDGAssetLink->AssetNodePath); - - // Get the node name - FString AssetName; - FHoudiniEngineString::ToFString(AssetInfo.nameSH, PDGAssetLink->AssetName); - - const bool bZeroWorkItemTallys = true; - if (!FHoudiniPDGManager::PopulateTOPNetworks(PDGAssetLink, bZeroWorkItemTallys)) - { - // We couldn't find any valid TOPNet/TOPNode, this is not a PDG Asset - // Make sure the HDA doesn't have a PDGAssetLink - InHAC->SetPDGAssetLink(nullptr); - return false; - } - - // If the PDG asset link comes from a loaded asset, we also need to register it - if (InHAC->HasBeenLoaded()) - { - bRegisterPDGAssetLink = true; - } - - // We have found valid TOPNetworks and TOPNodes, - // This is a PDG HDA, so add the AssetLink to it - PDGAssetLink->LinkState = EPDGLinkState::Linked; - - if (PDGAssetLink->SelectedTOPNetworkIndex < 0) - PDGAssetLink->SelectedTOPNetworkIndex = 0; - - InHAC->SetPDGAssetLink(PDGAssetLink); - - if (bRegisterPDGAssetLink) - { - // Register this PDG Asset Link to the PDG Manager - TWeakObjectPtr AssetLinkPtr(PDGAssetLink); - PDGAssetLinks.Add(AssetLinkPtr); - } - - // If the commandlet is enabled, check if we have started and established communication with the commandlet yet - // if not, try to start the commandlet - bool bCommandletIsEnabled = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (IsValid(HoudiniRuntimeSettings)) - { - bCommandletIsEnabled = HoudiniRuntimeSettings->bPDGAsyncCommandletImportEnabled; - } - - if (bCommandletIsEnabled) - { - const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); - if (CommandletStatus == EHoudiniBGEOCommandletStatus::NotStarted && bCommandletIsEnabled) - { - CreateBGEOCommandletAndEndpoint(); - } - } - - return true; -} - -bool -FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) -{ - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - // If the PDG Asset link is inactive, indicate that our HDA must be instantiated - if (PDGAssetLink->LinkState == EPDGLinkState::Inactive) - { - UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); - if(!ParentHAC) - { - // No valid parent HAC, error! - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("No valid Houdini Asset Component parent for PDG Asset Link!")); - } - else if (ParentHAC && ParentHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) - { - PDGAssetLink->LinkState = EPDGLinkState::Linking; - ParentHAC->AssetState = EHoudiniAssetState::PreInstantiation; - } - else - { - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("Unable to link the PDG Asset link! Try to rebuild the HDA.")); - } - } - - if (PDGAssetLink->LinkState == EPDGLinkState::Linking) - { - return true; - } - - if (PDGAssetLink->LinkState != EPDGLinkState::Linked) - { - UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); - int32 AssetId = ParentHAC->GetAssetId(); - if (AssetId < 0) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) - return false; - - PDGAssetLink->AssetID = AssetId; - } - - if(!PopulateTOPNetworks(PDGAssetLink)) - { - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("Failed to populte the PDG Asset Link.")); - return false; - } - - return true; -} - - -bool -FHoudiniPDGManager::PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys) -{ - // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - // Get all the network nodes within the asset, recursively. - // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type - int32 NetworkNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); - - if (NetworkNodeCount <= 0) - return false; - - TArray AllNetworkNodeIDs; - AllNetworkNodeIDs.SetNum(NetworkNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); - - // There is currently no way to only get non bypassed nodes via HAPI - // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... - TArray AllBypassedTOPNetNodeIDs; - { - int32 BypassedTOPNetNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); - - if (BypassedTOPNetNodeCount > 0) - { - // Get the list of all bypassed TOP Net... - AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); - - // ... and remove them from the network list - for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) - AllNetworkNodeIDs.RemoveAt(Idx); - } - } - } - - - // For each Network we found earlier, only add those with TOP child nodes - // Therefore guaranteeing that we only add TOP networks - TArray AllTOPNetworks; - for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) - { - if (CurrentNodeId < 0) - { - continue; - } - - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) - { - continue; - } - - // Skip non TOP or SOP networks - if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP - && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) - { - continue; - } - - // Check that this TOP Net is not nested in another TOP Net... - // This will happen with ROP Geometry TOPs for example... - bool bIsNestedInTOPNet = false; - HAPI_NodeId CurrentParentId = CurrentNodeInfo.parentId; - while (CurrentParentId > 0) - { - if (AllNetworkNodeIDs.Contains(CurrentParentId)) - { - bIsNestedInTOPNet = true; - break; - } - - if(AllBypassedTOPNetNodeIDs.Contains(CurrentParentId)) - { - bIsNestedInTOPNet = true; - break; - } - - HAPI_NodeInfo ParentNodeInfo; - FHoudiniApi::NodeInfo_Init(&ParentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentParentId, &ParentNodeInfo)) - { - break; - } - - // Get our parent's parent - CurrentParentId = ParentNodeInfo.parentId; - } - - // Skip nested TOP nets - if (bIsNestedInTOPNet) - continue; - - // Get the list of all TOP nodes within the current network (ignoring schedulers) - int32 TOPNodeCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) - { - continue; - } - - TArray AllTOPNodeIDs; - AllTOPNodeIDs.SetNum(TOPNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, AllTOPNodeIDs.GetData(), TOPNodeCount), false); - - // Skip networks without TOP nodes - if (AllTOPNodeIDs.Num() <= 0) - { - continue; - } - - // Since there is currently no way to get only non-bypassed nodes via HAPI - // we need to get a list of all the bypassed top nodes to remove them from the previous list - { - int32 BypassedTOPNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_TOP_NONSCHEDULER | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNodeCount), false); - - if (BypassedTOPNodeCount > 0) - { - // Get the list of all bypassed TOP Nodes... - TArray AllBypassedTOPNodes; - AllBypassedTOPNodes.SetNum(BypassedTOPNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - AllBypassedTOPNodes.GetData(), BypassedTOPNodeCount), false); - - // ... and remove them from the top node list - for (int32 Idx = AllTOPNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNodes.Contains(AllTOPNodeIDs[Idx])) - AllTOPNodeIDs.RemoveAt(Idx); - } - } - } - - // TODO: - // Apply the show and output filter on that node - bool bShow = true; - - // Get the node path - FString CurrentNodePath; - FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, PDGAssetLink->AssetID, CurrentNodePath); - - // Get the node name - FString CurrentNodeName; - FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, CurrentNodeName); - - UTOPNetwork* CurrentTOPNetwork = nullptr; - int32 FoundTOPNetIndex = INDEX_NONE; - UTOPNetwork* FoundTOPNet = PDGAssetLink->GetTOPNetworkByNodePath(CurrentNodeName, PDGAssetLink->AllTOPNetworks, FoundTOPNetIndex); - if (IsValid(FoundTOPNet)) - { - // Reuse the existing corresponding TOP NET - CurrentTOPNetwork = FoundTOPNet; - PDGAssetLink->AllTOPNetworks.RemoveAt(FoundTOPNetIndex); - } - else - { - // Create a new instance for the TOP NET - CurrentTOPNetwork = NewObject(PDGAssetLink, UTOPNetwork::StaticClass(), NAME_None, RF_Transactional); - } - - // Update the TOP NET - CurrentTOPNetwork->NodeId = CurrentNodeId; - CurrentTOPNetwork->NodeName = CurrentNodeName; - CurrentTOPNetwork->NodePath = CurrentNodePath; - CurrentTOPNetwork->ParentName = PDGAssetLink->AssetName; - CurrentTOPNetwork->bShowResults = bShow; - - // Only add network that have valid TOP Nodes - if (PopulateTOPNodes(AllTOPNodeIDs, CurrentTOPNetwork, PDGAssetLink, bInZeroWorkItemTallys)) - { - // See if we need to select a new TOP node - bool bReselectValidTOP = false; - if (CurrentTOPNetwork->SelectedTOPIndex < 0) - bReselectValidTOP = true; - else if (!CurrentTOPNetwork->AllTOPNodes.IsValidIndex(CurrentTOPNetwork->SelectedTOPIndex)) - bReselectValidTOP = true; - else if (!IsValid(CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]) || - CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]->bHidden) - bReselectValidTOP = true; - - if (bReselectValidTOP) - { - // Select the first valid TOP node (not hidden) - for (int Idx = 0; Idx < CurrentTOPNetwork->AllTOPNodes.Num(); Idx++) - { - UTOPNode *TOPNode = CurrentTOPNetwork->AllTOPNodes[Idx]; - if (!IsValid(TOPNode) || TOPNode->bHidden) - continue; - - CurrentTOPNetwork->SelectedTOPIndex = Idx; - break; - } - } - - AllTOPNetworks.Add(CurrentTOPNetwork); - } - } - - // Clear previous TOP networks, nodes and generated data - for (UTOPNetwork* CurrentTOPNet : PDGAssetLink->AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - PDGAssetLink->ClearTOPNetworkWorkItemResults(CurrentTOPNet); - } - //PDGAssetLink->ClearAllTOPData(); - PDGAssetLink->AllTOPNetworks = AllTOPNetworks; - - return (AllTOPNetworks.Num() > 0); -} - - -bool -FHoudiniPDGManager::PopulateTOPNodes( - const TArray& InTopNodeIDs, UTOPNetwork* InTOPNetwork, UHoudiniPDGAssetLink* InPDGAssetLink, bool bInZeroWorkItemTallys) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InTOPNetwork)) - return false; - - // - int32 TOPNodeCount = 0; - - // Holds list of found TOP nodes - TArray AllTOPNodes; - for(const HAPI_NodeId& CurrentTOPNodeID : InTopNodeIDs) - { - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentTOPNodeID, &CurrentNodeInfo)) - { - continue; - } - - // Increase the number of valid TOP Node - // (before applying the node filter) - TOPNodeCount++; - - // Get the node path - FString NodePath; - FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, InTOPNetwork->NodeId, NodePath); - - // Get the node name - FString NodeName; - FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, NodeName); - - // See if we can find an existing version of this TOPNOde - UTOPNode* CurrentTOPNode = nullptr; - int32 FoundNodeIndex = INDEX_NONE; - UTOPNode* FoundNode = InPDGAssetLink->GetTOPNodeByNodePath(NodePath, InTOPNetwork->AllTOPNodes, FoundNodeIndex); - if (IsValid(FoundNode)) - { - CurrentTOPNode = FoundNode; - InTOPNetwork->AllTOPNodes.RemoveAt(FoundNodeIndex); - - if (bInZeroWorkItemTallys) - { - CurrentTOPNode->WorkItemTally.ZeroAll(); - CurrentTOPNode->NodeState = EPDGNodeState::None; - } - } - else - { - // Didn't find an existing UTOPNode for this node, create a new UTOPNode instance - CurrentTOPNode = NewObject(InPDGAssetLink, UTOPNode::StaticClass(), NAME_None, RF_Transactional); - } - - CurrentTOPNode->NodeId = CurrentTOPNodeID; - CurrentTOPNode->NodeName = NodeName; - CurrentTOPNode->NodePath = NodePath; - CurrentTOPNode->ParentName = InTOPNetwork->ParentName + TEXT("_") + InTOPNetwork->NodeName; - CurrentTOPNode->bHasChildNodes = CurrentNodeInfo.childNodeCount > 0; - - // Filter display/autoload using name - CurrentTOPNode->bHidden = false; - if (InPDGAssetLink->bUseTOPNodeFilter && !InPDGAssetLink->TOPNodeFilter.IsEmpty()) - { - // Only display nodes that matches the filter - if (!NodeName.StartsWith(InPDGAssetLink->TOPNodeFilter)) - CurrentTOPNode->bHidden = true; - } - - // Automatically load results for nodes that match the filter - if (InPDGAssetLink->bUseTOPOutputFilter) - { - bool bAutoLoad = false; - if (InPDGAssetLink->TOPOutputFilter.IsEmpty()) - bAutoLoad = true; - else if (NodeName.StartsWith(InPDGAssetLink->TOPOutputFilter)) - bAutoLoad = true; - - CurrentTOPNode->bAutoLoad = bAutoLoad; - - // Show autoloaded results by default - CurrentTOPNode->SetVisibleInLevel(bAutoLoad); - } - - AllTOPNodes.Add(CurrentTOPNode); - } - - for (UTOPNode* CurTOPNode : InTOPNetwork->AllTOPNodes) - { - if (!IsValid(CurTOPNode)) - continue; - - InPDGAssetLink->ClearTOPNodeWorkItemResults(CurTOPNode); - } - - InTOPNetwork->AllTOPNodes = AllTOPNodes; - - return (TOPNodeCount > 0); -} - - -void -FHoudiniPDGManager::DirtyTOPNode(UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - // Dirty the specified TOP node... - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( - FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, true)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *(InTOPNode->NodeName)); - } - - // ... and clear its work item results. - UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(InTOPNode); -} - -// void -// FHoudiniPDGManager::DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode) -// { -// // Dirty the specified TOP node... -// if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( -// FHoudiniEngine::Get().GetSession(), InTOPNode.NodeId, true)) -// { -// HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *InTOPNode.NodeName); -// } -// } - -void -FHoudiniPDGManager::CookTOPNode(UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - if (InTOPNode->NodeState == EPDGNodeState::Cooking || InTOPNode->AnyWorkItemsPending()) - { - HOUDINI_LOG_WARNING(TEXT("PDG Cook TOP Node - %s is already/still cooking, ignoring 'Cook TOP Node' request."), *(InTOPNode->NodePath)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( - FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, 0, 0)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook TOP Node - Failed to cook %s!"), *(InTOPNode->NodeName)); - } -} - - -void -FHoudiniPDGManager::DirtyAll(UTOPNetwork* InTOPNet) -{ - if (!IsValid(InTOPNet)) - return; - - // Dirty the specified TOP network... - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, true)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Dirty All - Failed to dirty all of %s's TOP nodes!"), *(InTOPNet->NodeName)); - return; - } - - // ... and clear its work item results. - UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(InTOPNet); -} - - -void -FHoudiniPDGManager::CookOutput(UTOPNetwork* InTOPNet) -{ - // Cook the output TOP node of the currently selected TOP network. - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - bool bAlreadyCooking = InTOPNet->AnyWorkItemsPending(); - - if (!bAlreadyCooking) - { - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - int32 PDGState = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( - FHoudiniEngine::Get().GetSession(), GraphContextId, &PDGState)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's PDG state."), *(InTOPNet->NodeName)); - return; - } - bAlreadyCooking = ((HAPI_PDG_State) PDGState == HAPI_PDG_STATE_COOKING); - } - - if (bAlreadyCooking) - { - HOUDINI_LOG_WARNING(TEXT("PDG Cook Output - %s is already/still cooking, ignoring 'Cook Output' request."), *(InTOPNet->NodeName)); - return; - } - - // TODO: ??? - // Cancel all cooks. This is required as otherwise the graph gets into an infinite cook state (bug?) - if(HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, 0, 0)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to cook %s's output!"), *(InTOPNet->NodeName)); - } -} - - -void -FHoudiniPDGManager::PauseCook(UTOPNetwork* InTOPNet) -{ - // Pause the PDG cook of the currently selected TOP network - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::PausePDGCook( - FHoudiniEngine::Get().GetSession(), GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to pause %s!"), *(InTOPNet->NodeName)); - return; - } -} - - -void -FHoudiniPDGManager::CancelCook(UTOPNetwork* InTOPNet) -{ - // Cancel the PDG cook of the currently selected TOP network - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CancelPDGCook( - FHoudiniEngine::Get().GetSession(), GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to cancel cook for %s!"), *(InTOPNet->NodeName)); - return; - } -} - -void -FHoudiniPDGManager::Update() -{ - // Clean up registered PDG Asset Links - for(int32 Idx = PDGAssetLinks.Num() - 1; Idx >= 0; Idx--) - { - TWeakObjectPtr Ptr = PDGAssetLinks[Idx]; - if (!Ptr.IsValid() || Ptr.IsStale()) - { - PDGAssetLinks.RemoveAt(Idx); - continue; - } - - UHoudiniPDGAssetLink* CurPDGAssetLink = PDGAssetLinks[Idx].Get(); - if (!CurPDGAssetLink || CurPDGAssetLink->IsPendingKill()) - { - PDGAssetLinks.RemoveAt(Idx); - continue; - } - } - - // Do nothing if we dont have any valid PDG asset Link - if (PDGAssetLinks.Num() <= 0) - return; - - // Update the PDG contexts and handle all pdg events and work item status updates - UpdatePDGContexts(); - - // Prcoess any workitem result if we have any - ProcessWorkItemResults(); -} - -// Query all the PDG graph context in the current Houdini Engine session. -// Handle PDG events, work item status updates. -// Forward relevant events to PDGAssetLink objects. -void -FHoudiniPDGManager::UpdatePDGContexts() -{ - // Get current PDG graph contexts - ReinitializePDGContext(); - - // Process next set of events for each graph context - if (PDGContextIDs.Num() > 0) - { - // Only initialize event array if not valid, or user resized max size - if(PDGEventInfos.Num() != MaxNumberOfPDGEvents) - PDGEventInfos.SetNum(MaxNumberOfPDGEvents); - - // TODO: member? - //HAPI_PDG_State PDGState; - for(const HAPI_PDG_GraphContextId& CurrentContextID : PDGContextIDs) - { - /* - // TODO: No need to reset events at each tick - int32 PDGStateInt; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( - FHoudiniEngine::Get().GetSession(), CurrentContextID, &PDGStateInt)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get PDG state")); - continue; - } - - PDGState = (HAPI_PDG_State)PDGStateInt; - - for (int32 Idx = 0; Idx < PDGEventInfos.Num(); Idx++) - { - ResetPDGEventInfo(PDGEventInfos[Idx]); - } - */ - - int32 PDGEventCount = 0; - int32 RemainingPDGEventCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGEvents( - FHoudiniEngine::Get().GetSession(), CurrentContextID, PDGEventInfos.GetData(), - MaxNumberOfPDGEvents, &PDGEventCount, &RemainingPDGEventCount)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get PDG events")); - continue; - } - - if (PDGEventCount < 1) - continue; - - for (int32 EventIdx = 0; EventIdx < PDGEventCount; EventIdx++) - { - ProcessPDGEvent(CurrentContextID, PDGEventInfos[EventIdx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("PDG: Tick processed %d events, %d remaining."), PDGEventCount, RemainingPDGEventCount); - } - } - - // Refresh UI if necessary - for (auto CurAssetLink : PDGAssetLinks) - { - UHoudiniPDGAssetLink* AssetLink = CurAssetLink.Get(); - if (AssetLink) - { - if (AssetLink->bNeedsUIRefresh) - { - FHoudiniPDGManager::RefreshPDGAssetLinkUI(AssetLink); - AssetLink->bNeedsUIRefresh = false; - } - else - { - AssetLink->UpdateWorkItemTally(); - } - } - } -} - -// Query the currently active PDG graph contexts in the Houdini Engine session. -// Should be done each time to get latest set of graph contexts. -void -FHoudiniPDGManager::ReinitializePDGContext() -{ - int32 NumContexts = 0; - - PDGContextNames.SetNum(MaxNumberOPDGContexts); - PDGContextIDs.SetNum(MaxNumberOPDGContexts); - - if(HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContexts( - FHoudiniEngine::Get().GetSession(), - &NumContexts, PDGContextNames.GetData(), PDGContextIDs.GetData(), MaxNumberOPDGContexts) || NumContexts <= 0) - { - PDGContextNames.SetNum(0); - PDGContextIDs.SetNum(0); - return; - } - - if(PDGContextIDs.Num() != NumContexts) - PDGContextIDs.SetNum(NumContexts); - - if (PDGContextNames.Num() != NumContexts) - PDGContextNames.SetNum(NumContexts); -} - -// Process a PDG event. Notify the relevant PDGAssetLink object. -void -FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo) -{ - UHoudiniPDGAssetLink* PDGAssetLink = nullptr; - UTOPNode* TOPNode = nullptr; - - HAPI_PDG_EventType EventType = (HAPI_PDG_EventType)EventInfo.eventType; - HAPI_PDG_WorkitemState CurrentWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.currentState; - HAPI_PDG_WorkitemState LastWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.lastState; - - // Debug: get the HAPI_PDG_EventType as a string - const FString EventName = FHoudiniEngineUtils::HapiGetEventTypeAsString(EventType); - const FString CurrentWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(CurrentWorkItemState); - const FString LastWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(LastWorkItemState); - - if(!GetTOPAssetLinkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNode) - || PDGAssetLink == nullptr || PDGAssetLink->IsPendingKill() - || TOPNode == nullptr || TOPNode->IsPendingKill() - || TOPNode->NodeId != EventInfo.nodeId) - { - - HOUDINI_LOG_WARNING(TEXT("[ProcessPDGEvent]: Could not find matching TOPNode for event %s, workitem id %d, node id %d"), *EventName, EventInfo.workitemId, EventInfo.nodeId); - return; - } - - HOUDINI_PDG_MESSAGE( - TEXT("[ProcessPDGEvent]: TOPNode: %s, WorkItem ID: %d, Event Type: %s, Current State: %s, Last State %s"), - *(TOPNode->NodePath), EventInfo.workitemId, *EventName, *CurrentWorkitemStateName, *LastWorkitemStateName); - - FLinearColor MsgColor = FLinearColor::White; - - bool bUpdatePDGNodeState = false; - switch (EventType) - { - case HAPI_PDG_EVENT_NULL: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::None); - break; - - case HAPI_PDG_EVENT_NODE_CLEAR: - NotifyTOPNodePDGStateClear(PDGAssetLink, TOPNode); - break; - - case HAPI_PDG_EVENT_WORKITEM_ADD: - bUpdatePDGNodeState = true; - NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, 1); - break; - - case HAPI_PDG_EVENT_WORKITEM_REMOVE: - RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, TOPNode); - bUpdatePDGNodeState = true; - NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, -1); - break; - - case HAPI_PDG_EVENT_COOK_WARNING: - MsgColor = FLinearColor::Yellow; - break; - - case HAPI_PDG_EVENT_COOK_ERROR: - MsgColor = FLinearColor::Red; - break; - - case HAPI_PDG_EVENT_COOK_COMPLETE: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); - break; - - case HAPI_PDG_EVENT_DIRTY_START: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtying); - break; - - case HAPI_PDG_EVENT_DIRTY_STOP: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtied); - break; - - case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: - { - // Last states - bUpdatePDGNodeState = true; - if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) - { - NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, -1); - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) - { - NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, -1); - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) - { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); - } - else if ( (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE || LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) - && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) - { - // Handled previously cooked WI - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, -1); - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) - { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); - } - else - { - // TODO: - // unhandled state change - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); - } - - if (LastWorkItemState == CurrentWorkItemState) - { - // TODO: - // Not a change!! shouldnt happen! - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); - } - - // New states - if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) - { - NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, 1); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNCOOKED) - { - - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_DIRTY) - { - // ClearWorkItemResult(InContextID, EventInfo, *TOPNode); - ClearWorkItemResult(PDGAssetLink, EventInfo.workitemId, TOPNode); - // RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, *TOPNode); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) - { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, 1); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) - { - NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, 1); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS - || CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE) - { - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 1); - - // On cook success, handle results - CreateWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) - { - // TODO: on cook failure, get log path? - NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, 1); - MsgColor = FLinearColor::Red; - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CANCEL) - { - // Ignore it because in-progress cooks can be cancelled when automatically recooking graph - } - } - break; - - // Unhandled events - case HAPI_PDG_EVENT_DIRTY_ALL: - case HAPI_PDG_EVENT_COOK_START: - case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: - case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: - case HAPI_PDG_EVENT_UI_SELECT: - case HAPI_PDG_EVENT_NODE_CREATE: - case HAPI_PDG_EVENT_NODE_REMOVE: - case HAPI_PDG_EVENT_NODE_RENAME: - case HAPI_PDG_EVENT_NODE_CONNECT: - case HAPI_PDG_EVENT_NODE_DISCONNECT: - case HAPI_PDG_EVENT_WORKITEM_SET_INT: - case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: - case HAPI_PDG_EVENT_WORKITEM_SET_STRING: - case HAPI_PDG_EVENT_WORKITEM_SET_FILE: - case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: - case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: - case HAPI_PDG_EVENT_WORKITEM_RESULT: - case HAPI_PDG_EVENT_WORKITEM_PRIORITY: - case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: - case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: - case HAPI_PDG_EVENT_ALL: - case HAPI_PDG_EVENT_LOG: - case HAPI_PDG_CONTEXT_EVENTS: - break; - } - - if (bUpdatePDGNodeState) - { - // Work item events - EPDGNodeState CurrentTOPNodeState = TOPNode->NodeState; - if (CurrentTOPNodeState == EPDGNodeState::Cooking) - { - if (TOPNode->AreAllWorkItemsComplete()) - { - if (TOPNode->AnyWorkItemsFailed()) - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); - } - else - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); - } - } - } - else if (TOPNode->AnyWorkItemsPending()) - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cooking); - } - } - - if (EventInfo.msgSH >= 0) - { - FString EventMsg; - FHoudiniEngineString::ToFString(EventInfo.msgSH, EventMsg); - if (!EventMsg.IsEmpty()) - { - // TODO: Event MSG? - // Somehow update the PDG event msg UI ?? - // Simply log for now... - if (MsgColor == FLinearColor::Red) - { - HOUDINI_LOG_ERROR(TEXT("%s"), *EventMsg); - } - else if (MsgColor == FLinearColor::Yellow) - { - HOUDINI_LOG_WARNING(TEXT("%s"), *EventMsg); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT("%s"), *EventMsg); - } - } - } -} - -void -FHoudiniPDGManager::ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo) -{ - InEventInfo.nodeId = -1; - InEventInfo.workitemId = -1; - InEventInfo.dependencyId = -1; - InEventInfo.currentState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; - InEventInfo.lastState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; - InEventInfo.eventType = HAPI_PDG_EventType::HAPI_PDG_EVENT_NULL; -} - - -bool -FHoudiniPDGManager::GetTOPAssetLinkAndNode( - const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode) -{ - // Returns the PDGAssetLink and FTOPNode data associated with this TOP node ID - OutAssetLink = nullptr; - OutTOPNode = nullptr; - for (TWeakObjectPtr& CurAssetLinkPtr : PDGAssetLinks) - { - if (!CurAssetLinkPtr.IsValid() || CurAssetLinkPtr.IsStale()) - continue; - - UHoudiniPDGAssetLink* CurAssetLink = CurAssetLinkPtr.Get(); - if (!CurAssetLink || CurAssetLink->IsPendingKill()) - continue; - - OutTOPNode = CurAssetLink->GetTOPNode((int32)InNodeID); - - if (OutTOPNode != nullptr) - { - OutAssetLink = CurAssetLink; - return true; - } - } - - return false; -} - -void -FHoudiniPDGManager::SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->NodeState = InPDGState; - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - //Debug.LogFormat("NotifyTOPNodePDGStateClear:: {0}", topNode._nodeName); - InTOPNode->NodeState = EPDGNodeState::None; - InTOPNode->WorkItemTally.ZeroAll(); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ZeroAll"), *(InTOPNode->NodePath)); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); - -} - -void -FHoudiniPDGManager::NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.TotalWorkItems = FMath::Max(InTOPNode->WorkItemTally.TotalWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally TotalWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.CookedWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookedWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.ErroredWorkItems = FMath::Max(InTOPNode->WorkItemTally.ErroredWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.WaitingWorkItems = FMath::Max(InTOPNode->WorkItemTally.WaitingWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.ScheduledWorkItems = FMath::Max(InTOPNode->WorkItemTally.ScheduledWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.CookingWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookingWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // TODO!!! - // Clear all work items' results for the specified TOP node. - // This destroys any loaded results (geometry etc). - //session.LogErrorOverride = false; - InAssetLink->ClearWorkItemResultByID(InWorkItemID, InTOPNode); - // session.LogErrorOverride = true; -} - -void -FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // Clear all of the work item's results for the specified TOP node and also remove the work item itself from - // the TOP node. - InAssetLink->DestroyWorkItemByID(InWorkItemID, InTOPNode); -} - -void -FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // Only update the editor properties if the PDG asset link's Actor is selected - // else, just update the workitemtally - InAssetLink->UpdateWorkItemTally(); - - UHoudiniAssetComponent* HAC = Cast(InAssetLink->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor* ActorOwner = HAC->GetOwner(); - if (ActorOwner != nullptr && ActorOwner->IsSelected()) - { - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - } -} - -void -FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - if (bSuccess) - { - if (InAssetLink->LinkState == EPDGLinkState::Linked) - { - if (InAssetLink->bAutoCook) - { - FHoudiniPDGManager::CookOutput(InAssetLink->GetSelectedTOPNetwork()); - } - } - else - { - UpdatePDGAssetLink(InAssetLink); - } - } - else - { - InAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - } -} - -bool -FHoudiniPDGManager::CreateWorkItemResult( - UTOPNode* InTOPNode, - const HAPI_PDG_GraphContextId& InContextID, - HAPI_PDG_WorkitemId InWorkItemID, - bool bInLoadResultObjects) -{ - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); - return false; - } - - HAPI_PDG_WorkitemInfo WorkItemInfo; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( - FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); - // TODO? continue? - return false; - } - - if (WorkItemInfo.numResults > 0) - { - TArray ResultInfos; - ResultInfos.SetNum(WorkItemInfo.numResults); - const int32 resultCount = WorkItemInfo.numResults; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemResultInfo( - FHoudiniEngine::Get().GetSession(), - InTOPNode->NodeId, InWorkItemID, ResultInfos.GetData(), resultCount)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d result info for %s"), InWorkItemID, *(InTOPNode->NodeName)); - // TODO? continue? - return false; - } - - FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); - if (!WorkResult) - { - FTOPWorkResult LocalWorkResult; - LocalWorkResult.WorkItemID = InWorkItemID; - LocalWorkResult.WorkItemIndex = WorkItemInfo.index; - const int32 Idx = InTOPNode->WorkResult.Add(LocalWorkResult); - WorkResult = &(InTOPNode->WorkResult[Idx]); - } - - FString WorkItemName; - FHoudiniEngineString::ToFString(WorkItemInfo.nameSH, WorkItemName); - - // Load each result geometry - const int32 NumResults = ResultInfos.Num(); - for (int32 Idx = 0; Idx < NumResults; Idx++) - { - const HAPI_PDG_WorkitemResultInfo& ResultInfo = ResultInfos[Idx]; - if (ResultInfo.resultTagSH <= 0 || ResultInfo.resultSH <= 0) - continue; - - FString CurrentTag; - FHoudiniEngineString::ToFString(ResultInfo.resultTagSH, CurrentTag); - if(CurrentTag.IsEmpty() || !CurrentTag.StartsWith(TEXT("file"))) - continue; - - FString CurrentPath = FString(); - FHoudiniEngineString::ToFString(ResultInfo.resultSH, CurrentPath); - - // Construct the name and look for an existing work result, re-use it if found, otherwise create a new one - const FString WorkResultName = FString::Printf( - TEXT("%s_%s_%d"), - *(InTOPNode->ParentName), - *WorkItemName, - WorkItemInfo.index); - - FTOPWorkResultObject* ExistingResultObject = WorkResult->ResultObjects.FindByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) - { - return InResultObject.Name == WorkResultName; - }); - if (ExistingResultObject) - { - ExistingResultObject->FilePath = CurrentPath; - if (ExistingResultObject->State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) - { - ExistingResultObject->State = EPDGWorkResultState::ToDelete; - } - else if ((ExistingResultObject->State == EPDGWorkResultState::Loaded || - ExistingResultObject->State == EPDGWorkResultState::ToDelete || - ExistingResultObject->State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) - { - // When the commandlet is not being used, we could leave the outputs in place and have - // translators try to re-use objects/components. When the commandlet is being used, the packages - // are always saved and standalone, so if we want to automatically clean up old results then we - // need to destroy the existing outputs - if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - ExistingResultObject->DestroyResultOutputs(); - ExistingResultObject->State = EPDGWorkResultState::ToLoad; - } - else - { - ExistingResultObject->State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; - } - } - else - { - FTOPWorkResultObject ResultObj; - ResultObj.Name = WorkResultName; - ResultObj.FilePath = CurrentPath; - ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; - - WorkResult->ResultObjects.Add(ResultObj); - } - } - } - - return true; -} - -void -FHoudiniPDGManager::ProcessWorkItemResults() -{ - const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); - for (auto& CurrentPDGAssetLink : PDGAssetLinks) - { - // Iterate through all PDG Asset Link - UHoudiniPDGAssetLink* AssetLink = CurrentPDGAssetLink.Get(); - if (!AssetLink) - continue; - - // Set up package parameters to: - // Cook to temp houdini engine directory - // and if the PDG asset link is associated with a Houdini Asset Component (HAC): - // set the outer package to the HAC - // set the HoudiniAssetName according to the HAC - // set the ComponentGUID according to the HAC - // otherwise we set the outer to the asset link's parent and leave naming and GUID blank - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - // AActor* ParentActor = nullptr; - UObject* AssetLinkParent = AssetLink->GetOuter(); - UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; - if (HAC) - { - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - - // ParentActor = HAC->GetOwner(); - } - else - { - PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); - PackageParams.HoudiniAssetName = FString(); - PackageParams.HoudiniAssetActorName = FString(); - // PackageParams.ComponentGUID = HAC->GetComponentGUID(); - - // // Try to find a parent actor - // UObject* Parent = AssetLinkParent; - // while (Parent && !ParentActor) - // { - // ParentActor = Cast(Parent); - // if (!ParentActor) - // Parent = ParentActor->GetOuter(); - // } - } - PackageParams.ObjectName = FString(); - - // UWorld *World = ParentActor ? ParentActor->GetWorld() : AssetLink->GetWorld(); - UWorld *World = AssetLink->GetWorld(); - - // .. All TOP Nets - for (UTOPNetwork* CurrentTOPNet : AssetLink->AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - // .. All TOP Nodes - for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - // ... All WorkResult - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; - CurrentTOPNode->bCachedHaveLoadedWorkResults = false; - for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) - { - // ... All WorkResultObjects - for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) - { - if (CurrentWorkResultObj.State == EPDGWorkResultState::ToLoad) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Loading; - - // Load this WRObj - PackageParams.PDGTOPNetworkName = CurrentTOPNet->NodeName; - PackageParams.PDGTOPNodeName = CurrentTOPNode->NodeName; - PackageParams.PDGWorkItemIndex = CurrentWorkResult.WorkItemIndex; - - if (CommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - { - BGEOCommandletEndpoint->Send(new FHoudiniPDGImportBGEOMessage( - CurrentWorkResultObj.FilePath, - CurrentWorkResultObj.Name, - PackageParams, - CurrentTOPNode->NodeId, - CurrentWorkResult.WorkItemID - ), BGEOCommandletAddress); - } - else - { - if (FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - AssetLink, - CurrentTOPNode, - CurrentWorkResultObj, - PackageParams)) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Loaded; - CurrentTOPNode->bCachedHaveLoadedWorkResults = true; - - // Broadcast that we have loaded the work result object to those interested - AssetLink->OnWorkResultObjectLoaded.Broadcast( - AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemID, CurrentWorkResultObj.Name); - } - else - { - CurrentWorkResultObj.State = EPDGWorkResultState::None; - } - } - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::Loaded) - { - // If the work item result obj is in the "Loaded" state, confirm that the output actor - // is still valid (the user could have manually deleted the output - if (!IsValid(CurrentWorkResultObj.GetOutputActorOwner().GetOutputActor())) - { - // If the output actor is invalid, set the state to ToDelete to complete the - // unload/deletion process - CurrentWorkResultObj.State = EPDGWorkResultState::ToDelete; - } - else - { - CurrentTOPNode->bCachedHaveLoadedWorkResults = true; - } - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::ToDelete) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Deleting; - - // Delete and clean up that WRObj - CurrentWorkResultObj.DestroyResultOutputs(); - CurrentWorkResultObj.GetOutputActorOwner().DestroyOutputActor(); - CurrentWorkResultObj.State = EPDGWorkResultState::Deleted; - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::Deleted) - { - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::NotLoaded) - { - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - } - } - } - } - } -} - -void FHoudiniPDGManager::HandleImportBGEODiscoverMessage( - const FHoudiniPDGImportBGEODiscoverMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_DISPLAY(TEXT("Received Discover from %s"), *InContext->GetSender().ToString()); - // Ignore any discover acks received if we already have a valid local address - // for the commandlet - if (BGEOCommandletAddress.IsValid()) - return; - - if (BGEOCommandletProcHandle.IsValid() && InMessage.CommandletGuid.IsValid() && BGEOCommandletGuid == InMessage.CommandletGuid) - { - BGEOCommandletAddress = InContext->GetSender(); - } -} - -void FHoudiniPDGManager::HandleImportBGEOResultMessage( - const FHoudiniPDGImportBGEOResultMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_MESSAGE(TEXT("Received BGEO import result message")); - if (InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_Success || InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_PartialSuccess) - { - FHoudiniPackageParams PackageParams; - InMessage.PopulatePackageParams(PackageParams); - - // Find asset link and work result object - UHoudiniPDGAssetLink *AssetLink = nullptr; - UTOPNode *TOPNode = nullptr; - if (!GetTOPAssetLinkAndNode(InMessage.TOPNodeId, AssetLink, TOPNode) || - !IsValid(AssetLink) || !IsValid(TOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP node with id %d, aborting output object creation."), InMessage.TOPNodeId); - return; - } - - FTOPWorkResult* WorkResult = AssetLink->GetWorkResultByID(InMessage.WorkItemId, TOPNode); - if (WorkResult == nullptr) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result with id %d, aborting output object creation."), InMessage.WorkItemId); - return; - } - const FString& WorkResultObjectName = InMessage.Name; - FTOPWorkResultObject* WorkResultObject = WorkResult->ResultObjects.FindByPredicate( - [&WorkResultObjectName](const FTOPWorkResultObject& WorkResultObject) - { - return WorkResultObject.Name == WorkResultObjectName; - } - ); - if (WorkResultObject == nullptr) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result object with name %s, aborting output object creation."), *InMessage.Name); - return; - } - - if (WorkResultObject->State != EPDGWorkResultState::Loading) - { - HOUDINI_LOG_WARNING(TEXT("TOP work result object (%s) not in Loading state, aborting output object creation."), *InMessage.Name); - return; - } - - // Set package params outer - UObject* AssetLinkParent = AssetLink->GetOuter(); - UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; - if (HAC) - { - PackageParams.OuterPackage = HAC->GetComponentLevel(); - } - else - { - PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); - } - - // Construct UHoudiniOutputs - bool bHasUnsupportedOutputs = false; - TArray NewOutputs; - TMap InstancedOutputPartData; - NewOutputs.Reserve(InMessage.Outputs.Num()); - for (const FHoudiniPDGImportNodeOutput& Output : InMessage.Outputs) - { - UHoudiniOutput* NewOutput = NewObject( - AssetLink, - UHoudiniOutput::StaticClass(), - NAME_None,//FName(*OutputName), - RF_NoFlags); - NewOutputs.Add(NewOutput); - const int32 NumHGPO = Output.HoudiniGeoPartObjects.Num(); - for (int32 Index = 0; Index < NumHGPO; ++Index) - { - const FHoudiniGeoPartObject& HGPO = Output.HoudiniGeoPartObjects[Index]; - NewOutput->AddNewHGPO(HGPO); - - if (Output.InstancedOutputPartData.IsValidIndex(Index)) - { - FHoudiniOutputObjectIdentifier Identifier; - Identifier.ObjectId = HGPO.ObjectId; - Identifier.GeoId = HGPO.GeoId; - Identifier.PartId = HGPO.PartId; - Identifier.PartName = HGPO.PartName; - FHoudiniInstancedOutputPartData InstancedPartData = Output.InstancedOutputPartData[Index]; - InstancedPartData.BuildOriginalInstancedTransformsAndObjectArrays(); - InstancedOutputPartData.Add(Identifier, InstancedPartData); - } - } - const int32 NumObjects = Output.OutputObjects.Num(); - for (int32 Index = 0; Index < NumObjects; ++Index) - { - const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; - FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; - - const FString& FullPackagePath = ImportOutputObject.PackagePath; - FString PackagePath; - FString PackageName; - const bool bDidSplit = FullPackagePath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); - if (!bDidSplit) - PackagePath = FullPackagePath; - - FHoudiniOutputObject OutputObject; - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!IsValid(Package)) - { - // Editor might have picked up the package yet, try to load it - Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); - } - if (IsValid(Package)) - { - OutputObject.OutputObject = FindObject(Package, *PackageName); - } - Identifier.bLoaded = true; - NewOutput->GetOutputObjects().Add(Identifier, OutputObject); - } - NewOutput->UpdateOutputType(); - const EHoudiniOutputType OutputType = NewOutput->GetType(); - if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) - { - bHasUnsupportedOutputs = true; - } - } - - bool bSuccess = true; - if (bHasUnsupportedOutputs) - { - HOUDINI_LOG_MESSAGE(TEXT("Processing output types not supported by commandlet for %s"), *InMessage.Name); - bSuccess = FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - AssetLink, TOPNode, *WorkResultObject, PackageParams, - { - EHoudiniOutputType::Landscape, - EHoudiniOutputType::Curve, - EHoudiniOutputType::Skeletal - } - ); - - if (bSuccess) - { - // Clear/remove the outputs on WorkResultObject that are supported by the commandlet, since we - // are going to replace them with NewOutputs now - TArray& CurrentOutputs = WorkResultObject->GetResultOutputs(); - const int32 NumCurrentOutputs = CurrentOutputs.Num(); - for (int32 Index = 0; Index < NumCurrentOutputs; ++Index) - { - UHoudiniOutput* CurOutput = CurrentOutputs[Index]; - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) - { - // Was created in editor, override the dummy one in NewOutputs with CurOutput - if (NewOutputs.IsValidIndex(Index)) - { - if (OutputType == NewOutputs[Index]->GetType()) - { - UHoudiniOutput* TempOutput = NewOutputs[Index]; - FHoudiniOutputTranslator::ClearOutput(TempOutput); - NewOutputs[Index] = CurOutput; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Unexpected commandlet output type at index %d!"), Index); - } - } - else - { - HOUDINI_LOG_ERROR(TEXT("Expected output index %d from commandlet to be exist!"), Index); - } - } - } - } - } - - if (bSuccess && FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( - AssetLink, - TOPNode, - *WorkResultObject, - PackageParams, - NewOutputs, - {EHoudiniOutputType::Mesh, EHoudiniOutputType::Instancer}, - &InstancedOutputPartData)) - { - const int32 NumOutputs = NewOutputs.Num(); - for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex) - { - UHoudiniOutput *NewOutput = NewOutputs[OutputIndex]; - - if (NewOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - const FHoudiniPDGImportNodeOutput& Output = InMessage.Outputs[OutputIndex]; - int32 NumObjects = Output.OutputObjects.Num(); - for (int32 Index = 0; Index < NumObjects; ++Index) - { - const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; - FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; - FHoudiniOutputObject *OutputObject = NewOutput->GetOutputObjects().Find(Identifier); - if (OutputObject && IsValid(OutputObject->OutputComponent)) - { - // Update generic property attributes - FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( - OutputObject->OutputComponent, - ImportOutputObject.GenericAttributes.PropertyAttributes); - - // Copy cached attributes - OutputObject->CachedAttributes.Append(ImportOutputObject.CachedAttributes); - } - } - } - } - else - { - bSuccess = false; - } - - if (bSuccess) - { - WorkResultObject->State = EPDGWorkResultState::Loaded; - HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); - // Broadcast that we have loaded the work result object to those interested - AssetLink->OnWorkResultObjectLoaded.Broadcast(AssetLink, TOPNode, WorkResult->WorkItemID, WorkResultObject->Name); - } - else - { - WorkResultObject->State = EPDGWorkResultState::None; - HOUDINI_LOG_WARNING(TEXT("Failed to process loaded assets for %s"), *InMessage.Name); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Commandlet failed to import bgeo for %s"), *InMessage.Name); - } -} - -bool FHoudiniPDGManager::CreateBGEOCommandletAndEndpoint() -{ - if (!BGEOCommandletEndpoint.IsValid()) - { - BGEOCommandletAddress.Invalidate(); - BGEOCommandletEndpoint = FMessageEndpoint::Builder(TEXT("Houdini BGEO Commandlet")) - .Handling(this, &FHoudiniPDGManager::HandleImportBGEOResultMessage) - .Handling(this, &FHoudiniPDGManager::HandleImportBGEODiscoverMessage) - .ReceivingOnThread(ENamedThreads::GameThread); - - if (!BGEOCommandletEndpoint.IsValid()) - { - HOUDINI_LOG_WARNING(TEXT("Could not set up messaging end point for BGEO commandlet")); - return false; - } - - BGEOCommandletEndpoint->Subscribe(); - } - - if (!BGEOCommandletProcHandle.IsValid() || !FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - { - // Start the bgeo commandlet - static const FString BGEOCommandletName = TEXT("HoudiniGeoImport"); - BGEOCommandletGuid = FGuid::NewGuid(); - BGEOCommandletAddress.Invalidate(); - - // Get the absolute path to the project file, if known, otherwise get - // the project name. For the path: quote it for the command line. - IFileManager& FileManager = IFileManager::Get(); - FString ProjectPathOrName = FApp::GetProjectName(); - if (FPaths::IsProjectFilePathSet()) - { - const FString ProjectPath = FPaths::GetProjectFilePath(); - if (!ProjectPath.IsEmpty()) - { - ProjectPathOrName = FString::Printf( - TEXT("\"%s\""), - *FileManager.ConvertToAbsolutePathForExternalAppForRead(*ProjectPath) - ); - } - } - - if (ProjectPathOrName.IsEmpty()) - return false; - - // Get the executable path for the app/editor - FString ExePath = FPlatformProcess::GenerateApplicationPath(FApp::GetName(), FApp::GetBuildConfiguration()); - if (!ExePath.IsEmpty()) - ExePath = FileManager.ConvertToAbsolutePathForExternalAppForRead(*ExePath); - - if (ExePath.IsEmpty()) - return false; - - const FString CommandLineParameters = FString::Printf( - TEXT("%s -messaging -run=%s -guid=%s -listen=%s -managerpid=%d"), - *ProjectPathOrName, - *BGEOCommandletName, - *BGEOCommandletGuid.ToString(), - *BGEOCommandletEndpoint->GetAddress().ToString(), - FPlatformProcess::GetCurrentProcessId()); - - BGEOCommandletProcHandle = FPlatformProcess::CreateProc( - *ExePath, - *CommandLineParameters, - false, - true, - false, - &BGEOCommandletProcessId, - 0, - NULL, - NULL); - if (!BGEOCommandletProcHandle.IsValid()) - { - return false; - } - } - - return true; -} - -void FHoudiniPDGManager::StopBGEOCommandletAndEndpoint() -{ - BGEOCommandletEndpoint.Reset(); - BGEOCommandletAddress.Invalidate(); - BGEOCommandletGuid.Invalidate(); - - if (BGEOCommandletProcHandle.IsValid() && FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - { - FPlatformProcess::TerminateProc(BGEOCommandletProcHandle, true); - if (BGEOCommandletProcHandle.IsValid()) - { - FPlatformProcess::WaitForProc(BGEOCommandletProcHandle); - FPlatformProcess::CloseProc(BGEOCommandletProcHandle); - } - } -} - -EHoudiniBGEOCommandletStatus FHoudiniPDGManager::UpdateAndGetBGEOCommandletStatus() -{ - if (BGEOCommandletProcHandle.IsValid()) - { - if (!FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Crashed; - else if (BGEOCommandletAddress.IsValid()) - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Connected; - else - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Running; - } - else - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::NotStarted; - - return BGEOCommandletStatus; -} - - -bool -FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) -{ - if (InAssetId < 0) - return false; - - // Get all the network nodes within the asset, recursively. - // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type - int32 NetworkNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); - - if (NetworkNodeCount <= 0) - return false; - - TArray AllNetworkNodeIDs; - AllNetworkNodeIDs.SetNum(NetworkNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); - - // There is currently no way to only get non bypassed nodes via HAPI - // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... - TArray AllBypassedTOPNetNodeIDs; - { - int32 BypassedTOPNetNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); - - if (BypassedTOPNetNodeCount > 0) - { - // Get the list of all bypassed TOP Net... - AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); - - // ... and remove them from the network list - for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) - AllNetworkNodeIDs.RemoveAt(Idx); - } - } - } - - // For each Network we found earlier, only consider those with TOP child nodes - // If we find TOP nodes in a valid network, then consider this HDA a PDG HDA - for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) - { - if (CurrentNodeId < 0) - { - continue; - } - - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) - { - continue; - } - - // Skip non TOP or SOP networks - if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP - && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) - { - continue; - } - - // Get the list of all TOP nodes within the current network (ignoring schedulers) - int32 TOPNodeCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) - { - continue; - } - - // We found valid TOP Nodes, this is a PDG HDA - if (TOPNodeCount > 0) - return true; - } - - // No valid TOP node found in SOP/TOP Networks, this is not a PDG HDA - return false; -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGManager.h" + +#include "Modules/ModuleManager.h" +#include "MessageEndpointBuilder.h" +#include "HAL/FileManager.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPackageParams.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniPDGTranslator.h" +#include "HoudiniPDGImporterMessages.h" + +#include "HAPI/HAPI_Common.h" + +HOUDINI_PDG_DEFINE_LOG_CATEGORY(); + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniPDGManager::FHoudiniPDGManager() +{ +} + +FHoudiniPDGManager::~FHoudiniPDGManager() +{ +} + +bool +FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) +{ + if (!InHAC || InHAC->IsPendingKill()) + return false; + + int32 AssetId = InHAC->GetAssetId(); + if (AssetId < 0) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) + return false; + + // Create a new PDG Asset Link Object + bool bRegisterPDGAssetLink = false; + UHoudiniPDGAssetLink* PDGAssetLink = InHAC->GetPDGAssetLink(); + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + { + PDGAssetLink = NewObject(InHAC, UHoudiniPDGAssetLink::StaticClass(), NAME_None, RF_Transactional); + bRegisterPDGAssetLink = true; + } + + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + PDGAssetLink->AssetID = AssetId; + + // Get the HDA's info + HAPI_NodeInfo AssetInfo; + FHoudiniApi::NodeInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, &AssetInfo), false); + + // Get the node path + FString AssetNodePath; + FHoudiniEngineString::ToFString(AssetInfo.internalNodePathSH, PDGAssetLink->AssetNodePath); + + // Get the node name + FString AssetName; + FHoudiniEngineString::ToFString(AssetInfo.nameSH, PDGAssetLink->AssetName); + + const bool bZeroWorkItemTallys = true; + if (!FHoudiniPDGManager::PopulateTOPNetworks(PDGAssetLink, bZeroWorkItemTallys)) + { + // We couldn't find any valid TOPNet/TOPNode, this is not a PDG Asset + // Make sure the HDA doesn't have a PDGAssetLink + InHAC->SetPDGAssetLink(nullptr); + return false; + } + + // If the PDG asset link comes from a loaded asset, we also need to register it + if (InHAC->HasBeenLoaded()) + { + bRegisterPDGAssetLink = true; + } + + // We have found valid TOPNetworks and TOPNodes, + // This is a PDG HDA, so add the AssetLink to it + PDGAssetLink->LinkState = EPDGLinkState::Linked; + + if (PDGAssetLink->SelectedTOPNetworkIndex < 0) + PDGAssetLink->SelectedTOPNetworkIndex = 0; + + InHAC->SetPDGAssetLink(PDGAssetLink); + + if (bRegisterPDGAssetLink) + { + // Register this PDG Asset Link to the PDG Manager + TWeakObjectPtr AssetLinkPtr(PDGAssetLink); + PDGAssetLinks.Add(AssetLinkPtr); + } + + // If the commandlet is enabled, check if we have started and established communication with the commandlet yet + // if not, try to start the commandlet + bool bCommandletIsEnabled = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (IsValid(HoudiniRuntimeSettings)) + { + bCommandletIsEnabled = HoudiniRuntimeSettings->bPDGAsyncCommandletImportEnabled; + } + + if (bCommandletIsEnabled) + { + const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); + if (CommandletStatus == EHoudiniBGEOCommandletStatus::NotStarted && bCommandletIsEnabled) + { + CreateBGEOCommandletAndEndpoint(); + } + } + + return true; +} + +bool +FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) +{ + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + // If the PDG Asset link is inactive, indicate that our HDA must be instantiated + if (PDGAssetLink->LinkState == EPDGLinkState::Inactive) + { + UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); + if(!ParentHAC) + { + // No valid parent HAC, error! + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("No valid Houdini Asset Component parent for PDG Asset Link!")); + } + else if (ParentHAC && ParentHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) + { + PDGAssetLink->LinkState = EPDGLinkState::Linking; + ParentHAC->AssetState = EHoudiniAssetState::PreInstantiation; + } + else + { + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("Unable to link the PDG Asset link! Try to rebuild the HDA.")); + } + } + + if (PDGAssetLink->LinkState == EPDGLinkState::Linking) + { + return true; + } + + if (PDGAssetLink->LinkState != EPDGLinkState::Linked) + { + UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); + int32 AssetId = ParentHAC->GetAssetId(); + if (AssetId < 0) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) + return false; + + PDGAssetLink->AssetID = AssetId; + } + + if(!PopulateTOPNetworks(PDGAssetLink)) + { + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("Failed to populte the PDG Asset Link.")); + return false; + } + + return true; +} + + +bool +FHoudiniPDGManager::PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys) +{ + // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + // Get all the network nodes within the asset, recursively. + // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type + int32 NetworkNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); + + if (NetworkNodeCount <= 0) + return false; + + TArray AllNetworkNodeIDs; + AllNetworkNodeIDs.SetNum(NetworkNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); + + // There is currently no way to only get non bypassed nodes via HAPI + // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... + TArray AllBypassedTOPNetNodeIDs; + { + int32 BypassedTOPNetNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); + + if (BypassedTOPNetNodeCount > 0) + { + // Get the list of all bypassed TOP Net... + AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); + + // ... and remove them from the network list + for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) + AllNetworkNodeIDs.RemoveAt(Idx); + } + } + } + + + // For each Network we found earlier, only add those with TOP child nodes + // Therefore guaranteeing that we only add TOP networks + TArray AllTOPNetworks; + for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) + { + if (CurrentNodeId < 0) + { + continue; + } + + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) + { + continue; + } + + // Skip non TOP or SOP networks + if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP + && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) + { + continue; + } + + // Check that this TOP Net is not nested in another TOP Net... + // This will happen with ROP Geometry TOPs for example... + bool bIsNestedInTOPNet = false; + HAPI_NodeId CurrentParentId = CurrentNodeInfo.parentId; + while (CurrentParentId > 0) + { + if (AllNetworkNodeIDs.Contains(CurrentParentId)) + { + bIsNestedInTOPNet = true; + break; + } + + if(AllBypassedTOPNetNodeIDs.Contains(CurrentParentId)) + { + bIsNestedInTOPNet = true; + break; + } + + HAPI_NodeInfo ParentNodeInfo; + FHoudiniApi::NodeInfo_Init(&ParentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentParentId, &ParentNodeInfo)) + { + break; + } + + // Get our parent's parent + CurrentParentId = ParentNodeInfo.parentId; + } + + // Skip nested TOP nets + if (bIsNestedInTOPNet) + continue; + + // Get the list of all TOP nodes within the current network (ignoring schedulers) + int32 TOPNodeCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) + { + continue; + } + + TArray AllTOPNodeIDs; + AllTOPNodeIDs.SetNum(TOPNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, AllTOPNodeIDs.GetData(), TOPNodeCount), false); + + // Skip networks without TOP nodes + if (AllTOPNodeIDs.Num() <= 0) + { + continue; + } + + // Since there is currently no way to get only non-bypassed nodes via HAPI + // we need to get a list of all the bypassed top nodes to remove them from the previous list + { + int32 BypassedTOPNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_TOP_NONSCHEDULER | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNodeCount), false); + + if (BypassedTOPNodeCount > 0) + { + // Get the list of all bypassed TOP Nodes... + TArray AllBypassedTOPNodes; + AllBypassedTOPNodes.SetNum(BypassedTOPNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + AllBypassedTOPNodes.GetData(), BypassedTOPNodeCount), false); + + // ... and remove them from the top node list + for (int32 Idx = AllTOPNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNodes.Contains(AllTOPNodeIDs[Idx])) + AllTOPNodeIDs.RemoveAt(Idx); + } + } + } + + // TODO: + // Apply the show and output filter on that node + bool bShow = true; + + // Get the node path + FString CurrentNodePath; + FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, PDGAssetLink->AssetID, CurrentNodePath); + + // Get the node name + FString CurrentNodeName; + FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, CurrentNodeName); + + UTOPNetwork* CurrentTOPNetwork = nullptr; + int32 FoundTOPNetIndex = INDEX_NONE; + UTOPNetwork* FoundTOPNet = PDGAssetLink->GetTOPNetworkByNodePath(CurrentNodeName, PDGAssetLink->AllTOPNetworks, FoundTOPNetIndex); + if (IsValid(FoundTOPNet)) + { + // Reuse the existing corresponding TOP NET + CurrentTOPNetwork = FoundTOPNet; + PDGAssetLink->AllTOPNetworks.RemoveAt(FoundTOPNetIndex); + } + else + { + // Create a new instance for the TOP NET + CurrentTOPNetwork = NewObject(PDGAssetLink, UTOPNetwork::StaticClass(), NAME_None, RF_Transactional); + } + + // Update the TOP NET + CurrentTOPNetwork->NodeId = CurrentNodeId; + CurrentTOPNetwork->NodeName = CurrentNodeName; + CurrentTOPNetwork->NodePath = CurrentNodePath; + CurrentTOPNetwork->ParentName = PDGAssetLink->AssetName; + CurrentTOPNetwork->bShowResults = bShow; + + // Only add network that have valid TOP Nodes + if (PopulateTOPNodes(AllTOPNodeIDs, CurrentTOPNetwork, PDGAssetLink, bInZeroWorkItemTallys)) + { + // See if we need to select a new TOP node + bool bReselectValidTOP = false; + if (CurrentTOPNetwork->SelectedTOPIndex < 0) + bReselectValidTOP = true; + else if (!CurrentTOPNetwork->AllTOPNodes.IsValidIndex(CurrentTOPNetwork->SelectedTOPIndex)) + bReselectValidTOP = true; + else if (!IsValid(CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]) || + CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]->bHidden) + bReselectValidTOP = true; + + if (bReselectValidTOP) + { + // Select the first valid TOP node (not hidden) + for (int Idx = 0; Idx < CurrentTOPNetwork->AllTOPNodes.Num(); Idx++) + { + UTOPNode *TOPNode = CurrentTOPNetwork->AllTOPNodes[Idx]; + if (!IsValid(TOPNode) || TOPNode->bHidden) + continue; + + CurrentTOPNetwork->SelectedTOPIndex = Idx; + break; + } + } + + AllTOPNetworks.Add(CurrentTOPNetwork); + } + } + + // Clear previous TOP networks, nodes and generated data + for (UTOPNetwork* CurrentTOPNet : PDGAssetLink->AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + PDGAssetLink->ClearTOPNetworkWorkItemResults(CurrentTOPNet); + } + //PDGAssetLink->ClearAllTOPData(); + PDGAssetLink->AllTOPNetworks = AllTOPNetworks; + + return (AllTOPNetworks.Num() > 0); +} + + +bool +FHoudiniPDGManager::PopulateTOPNodes( + const TArray& InTopNodeIDs, UTOPNetwork* InTOPNetwork, UHoudiniPDGAssetLink* InPDGAssetLink, bool bInZeroWorkItemTallys) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InTOPNetwork)) + return false; + + // + int32 TOPNodeCount = 0; + + // Holds list of found TOP nodes + TArray AllTOPNodes; + for(const HAPI_NodeId& CurrentTOPNodeID : InTopNodeIDs) + { + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentTOPNodeID, &CurrentNodeInfo)) + { + continue; + } + + // Increase the number of valid TOP Node + // (before applying the node filter) + TOPNodeCount++; + + // Get the node path + FString NodePath; + FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, InTOPNetwork->NodeId, NodePath); + + // Get the node name + FString NodeName; + FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, NodeName); + + // See if we can find an existing version of this TOPNOde + UTOPNode* CurrentTOPNode = nullptr; + int32 FoundNodeIndex = INDEX_NONE; + UTOPNode* FoundNode = InPDGAssetLink->GetTOPNodeByNodePath(NodePath, InTOPNetwork->AllTOPNodes, FoundNodeIndex); + if (IsValid(FoundNode)) + { + CurrentTOPNode = FoundNode; + InTOPNetwork->AllTOPNodes.RemoveAt(FoundNodeIndex); + + if (bInZeroWorkItemTallys) + { + CurrentTOPNode->WorkItemTally.ZeroAll(); + CurrentTOPNode->NodeState = EPDGNodeState::None; + } + } + else + { + // Didn't find an existing UTOPNode for this node, create a new UTOPNode instance + CurrentTOPNode = NewObject(InPDGAssetLink, UTOPNode::StaticClass(), NAME_None, RF_Transactional); + } + + CurrentTOPNode->NodeId = CurrentTOPNodeID; + CurrentTOPNode->NodeName = NodeName; + CurrentTOPNode->NodePath = NodePath; + CurrentTOPNode->ParentName = InTOPNetwork->ParentName + TEXT("_") + InTOPNetwork->NodeName; + CurrentTOPNode->bHasChildNodes = CurrentNodeInfo.childNodeCount > 0; + + // Filter display/autoload using name + CurrentTOPNode->bHidden = false; + if (InPDGAssetLink->bUseTOPNodeFilter && !InPDGAssetLink->TOPNodeFilter.IsEmpty()) + { + // Only display nodes that matches the filter + if (!NodeName.StartsWith(InPDGAssetLink->TOPNodeFilter)) + CurrentTOPNode->bHidden = true; + } + + // Automatically load results for nodes that match the filter + if (InPDGAssetLink->bUseTOPOutputFilter) + { + bool bAutoLoad = false; + if (InPDGAssetLink->TOPOutputFilter.IsEmpty()) + bAutoLoad = true; + else if (NodeName.StartsWith(InPDGAssetLink->TOPOutputFilter)) + bAutoLoad = true; + + CurrentTOPNode->bAutoLoad = bAutoLoad; + + // Show autoloaded results by default + CurrentTOPNode->SetVisibleInLevel(bAutoLoad); + } + + AllTOPNodes.Add(CurrentTOPNode); + } + + for (UTOPNode* CurTOPNode : InTOPNetwork->AllTOPNodes) + { + if (!IsValid(CurTOPNode)) + continue; + + InPDGAssetLink->ClearTOPNodeWorkItemResults(CurTOPNode); + } + + InTOPNetwork->AllTOPNodes = AllTOPNodes; + + return (TOPNodeCount > 0); +} + + +void +FHoudiniPDGManager::DirtyTOPNode(UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + // Dirty the specified TOP node... + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( + FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, true)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *(InTOPNode->NodeName)); + } + + // ... and clear its work item results. + UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(InTOPNode); +} + +// void +// FHoudiniPDGManager::DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode) +// { +// // Dirty the specified TOP node... +// if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( +// FHoudiniEngine::Get().GetSession(), InTOPNode.NodeId, true)) +// { +// HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *InTOPNode.NodeName); +// } +// } + +void +FHoudiniPDGManager::CookTOPNode(UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + if (InTOPNode->NodeState == EPDGNodeState::Cooking || InTOPNode->AnyWorkItemsPending()) + { + HOUDINI_LOG_WARNING(TEXT("PDG Cook TOP Node - %s is already/still cooking, ignoring 'Cook TOP Node' request."), *(InTOPNode->NodePath)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( + FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, 0, 0)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook TOP Node - Failed to cook %s!"), *(InTOPNode->NodeName)); + } +} + + +void +FHoudiniPDGManager::DirtyAll(UTOPNetwork* InTOPNet) +{ + if (!IsValid(InTOPNet)) + return; + + // Dirty the specified TOP network... + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, true)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Dirty All - Failed to dirty all of %s's TOP nodes!"), *(InTOPNet->NodeName)); + return; + } + + // ... and clear its work item results. + UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(InTOPNet); +} + + +void +FHoudiniPDGManager::CookOutput(UTOPNetwork* InTOPNet) +{ + // Cook the output TOP node of the currently selected TOP network. + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + bool bAlreadyCooking = InTOPNet->AnyWorkItemsPending(); + + if (!bAlreadyCooking) + { + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + int32 PDGState = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( + FHoudiniEngine::Get().GetSession(), GraphContextId, &PDGState)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's PDG state."), *(InTOPNet->NodeName)); + return; + } + bAlreadyCooking = ((HAPI_PDG_State) PDGState == HAPI_PDG_STATE_COOKING); + } + + if (bAlreadyCooking) + { + HOUDINI_LOG_WARNING(TEXT("PDG Cook Output - %s is already/still cooking, ignoring 'Cook Output' request."), *(InTOPNet->NodeName)); + return; + } + + // TODO: ??? + // Cancel all cooks. This is required as otherwise the graph gets into an infinite cook state (bug?) + if(HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, 0, 0)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to cook %s's output!"), *(InTOPNet->NodeName)); + } +} + + +void +FHoudiniPDGManager::PauseCook(UTOPNetwork* InTOPNet) +{ + // Pause the PDG cook of the currently selected TOP network + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::PausePDGCook( + FHoudiniEngine::Get().GetSession(), GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to pause %s!"), *(InTOPNet->NodeName)); + return; + } +} + + +void +FHoudiniPDGManager::CancelCook(UTOPNetwork* InTOPNet) +{ + // Cancel the PDG cook of the currently selected TOP network + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CancelPDGCook( + FHoudiniEngine::Get().GetSession(), GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to cancel cook for %s!"), *(InTOPNet->NodeName)); + return; + } +} + +void +FHoudiniPDGManager::Update() +{ + // Clean up registered PDG Asset Links + for(int32 Idx = PDGAssetLinks.Num() - 1; Idx >= 0; Idx--) + { + TWeakObjectPtr Ptr = PDGAssetLinks[Idx]; + if (!Ptr.IsValid() || Ptr.IsStale()) + { + PDGAssetLinks.RemoveAt(Idx); + continue; + } + + UHoudiniPDGAssetLink* CurPDGAssetLink = PDGAssetLinks[Idx].Get(); + if (!CurPDGAssetLink || CurPDGAssetLink->IsPendingKill()) + { + PDGAssetLinks.RemoveAt(Idx); + continue; + } + } + + // Do nothing if we dont have any valid PDG asset Link + if (PDGAssetLinks.Num() <= 0) + return; + + // Update the PDG contexts and handle all pdg events and work item status updates + UpdatePDGContexts(); + + // Prcoess any workitem result if we have any + ProcessWorkItemResults(); +} + +// Query all the PDG graph context in the current Houdini Engine session. +// Handle PDG events, work item status updates. +// Forward relevant events to PDGAssetLink objects. +void +FHoudiniPDGManager::UpdatePDGContexts() +{ + // Get current PDG graph contexts + ReinitializePDGContext(); + + // Process next set of events for each graph context + if (PDGContextIDs.Num() > 0) + { + // Only initialize event array if not valid, or user resized max size + if(PDGEventInfos.Num() != MaxNumberOfPDGEvents) + PDGEventInfos.SetNum(MaxNumberOfPDGEvents); + + // TODO: member? + //HAPI_PDG_State PDGState; + for(const HAPI_PDG_GraphContextId& CurrentContextID : PDGContextIDs) + { + /* + // TODO: No need to reset events at each tick + int32 PDGStateInt; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( + FHoudiniEngine::Get().GetSession(), CurrentContextID, &PDGStateInt)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get PDG state")); + continue; + } + + PDGState = (HAPI_PDG_State)PDGStateInt; + + for (int32 Idx = 0; Idx < PDGEventInfos.Num(); Idx++) + { + ResetPDGEventInfo(PDGEventInfos[Idx]); + } + */ + + int32 PDGEventCount = 0; + int32 RemainingPDGEventCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGEvents( + FHoudiniEngine::Get().GetSession(), CurrentContextID, PDGEventInfos.GetData(), + MaxNumberOfPDGEvents, &PDGEventCount, &RemainingPDGEventCount)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get PDG events")); + continue; + } + + if (PDGEventCount < 1) + continue; + + for (int32 EventIdx = 0; EventIdx < PDGEventCount; EventIdx++) + { + ProcessPDGEvent(CurrentContextID, PDGEventInfos[EventIdx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("PDG: Tick processed %d events, %d remaining."), PDGEventCount, RemainingPDGEventCount); + } + } + + // Refresh UI if necessary + for (auto CurAssetLink : PDGAssetLinks) + { + UHoudiniPDGAssetLink* AssetLink = CurAssetLink.Get(); + if (AssetLink) + { + if (AssetLink->bNeedsUIRefresh) + { + FHoudiniPDGManager::RefreshPDGAssetLinkUI(AssetLink); + AssetLink->bNeedsUIRefresh = false; + } + else + { + AssetLink->UpdateWorkItemTally(); + } + } + } +} + +// Query the currently active PDG graph contexts in the Houdini Engine session. +// Should be done each time to get latest set of graph contexts. +void +FHoudiniPDGManager::ReinitializePDGContext() +{ + int32 NumContexts = 0; + + PDGContextNames.SetNum(MaxNumberOPDGContexts); + PDGContextIDs.SetNum(MaxNumberOPDGContexts); + + if(HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContexts( + FHoudiniEngine::Get().GetSession(), + &NumContexts, PDGContextNames.GetData(), PDGContextIDs.GetData(), MaxNumberOPDGContexts) || NumContexts <= 0) + { + PDGContextNames.SetNum(0); + PDGContextIDs.SetNum(0); + return; + } + + if(PDGContextIDs.Num() != NumContexts) + PDGContextIDs.SetNum(NumContexts); + + if (PDGContextNames.Num() != NumContexts) + PDGContextNames.SetNum(NumContexts); +} + +// Process a PDG event. Notify the relevant PDGAssetLink object. +void +FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo) +{ + UHoudiniPDGAssetLink* PDGAssetLink = nullptr; + UTOPNode* TOPNode = nullptr; + + HAPI_PDG_EventType EventType = (HAPI_PDG_EventType)EventInfo.eventType; + HAPI_PDG_WorkitemState CurrentWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.currentState; + HAPI_PDG_WorkitemState LastWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.lastState; + + // Debug: get the HAPI_PDG_EventType as a string + const FString EventName = FHoudiniEngineUtils::HapiGetEventTypeAsString(EventType); + const FString CurrentWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(CurrentWorkItemState); + const FString LastWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(LastWorkItemState); + + if(!GetTOPAssetLinkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNode) + || PDGAssetLink == nullptr || PDGAssetLink->IsPendingKill() + || TOPNode == nullptr || TOPNode->IsPendingKill() + || TOPNode->NodeId != EventInfo.nodeId) + { + + HOUDINI_LOG_WARNING(TEXT("[ProcessPDGEvent]: Could not find matching TOPNode for event %s, workitem id %d, node id %d"), *EventName, EventInfo.workitemId, EventInfo.nodeId); + return; + } + + HOUDINI_PDG_MESSAGE( + TEXT("[ProcessPDGEvent]: TOPNode: %s, WorkItem ID: %d, Event Type: %s, Current State: %s, Last State %s"), + *(TOPNode->NodePath), EventInfo.workitemId, *EventName, *CurrentWorkitemStateName, *LastWorkitemStateName); + + FLinearColor MsgColor = FLinearColor::White; + + bool bUpdatePDGNodeState = false; + switch (EventType) + { + case HAPI_PDG_EVENT_NULL: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::None); + break; + + case HAPI_PDG_EVENT_NODE_CLEAR: + NotifyTOPNodePDGStateClear(PDGAssetLink, TOPNode); + break; + + case HAPI_PDG_EVENT_WORKITEM_ADD: + bUpdatePDGNodeState = true; + NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, 1); + break; + + case HAPI_PDG_EVENT_WORKITEM_REMOVE: + RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, TOPNode); + bUpdatePDGNodeState = true; + NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, -1); + break; + + case HAPI_PDG_EVENT_COOK_WARNING: + MsgColor = FLinearColor::Yellow; + break; + + case HAPI_PDG_EVENT_COOK_ERROR: + MsgColor = FLinearColor::Red; + break; + + case HAPI_PDG_EVENT_COOK_COMPLETE: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + break; + + case HAPI_PDG_EVENT_DIRTY_START: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtying); + break; + + case HAPI_PDG_EVENT_DIRTY_STOP: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtied); + break; + + case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: + { + // Last states + bUpdatePDGNodeState = true; + if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) + { + NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, -1); + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) + { + NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, -1); + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) + { + NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); + } + else if ( (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE || LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) + && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) + { + // Handled previously cooked WI + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, -1); + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) + { + NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); + } + else + { + // TODO: + // unhandled state change + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); + } + + if (LastWorkItemState == CurrentWorkItemState) + { + // TODO: + // Not a change!! shouldnt happen! + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); + } + + // New states + if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) + { + NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, 1); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNCOOKED) + { + + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_DIRTY) + { + // ClearWorkItemResult(InContextID, EventInfo, *TOPNode); + ClearWorkItemResult(PDGAssetLink, EventInfo.workitemId, TOPNode); + // RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, *TOPNode); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) + { + NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, 1); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) + { + NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, 1); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS + || CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE) + { + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 1); + + // On cook success, handle results + CreateWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) + { + // TODO: on cook failure, get log path? + NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, 1); + MsgColor = FLinearColor::Red; + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CANCEL) + { + // Ignore it because in-progress cooks can be cancelled when automatically recooking graph + } + } + break; + + // Unhandled events + case HAPI_PDG_EVENT_DIRTY_ALL: + case HAPI_PDG_EVENT_COOK_START: + case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: + case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: + case HAPI_PDG_EVENT_UI_SELECT: + case HAPI_PDG_EVENT_NODE_CREATE: + case HAPI_PDG_EVENT_NODE_REMOVE: + case HAPI_PDG_EVENT_NODE_RENAME: + case HAPI_PDG_EVENT_NODE_CONNECT: + case HAPI_PDG_EVENT_NODE_DISCONNECT: + case HAPI_PDG_EVENT_WORKITEM_SET_INT: + case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: + case HAPI_PDG_EVENT_WORKITEM_SET_STRING: + case HAPI_PDG_EVENT_WORKITEM_SET_FILE: + case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: + case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: + case HAPI_PDG_EVENT_WORKITEM_RESULT: + case HAPI_PDG_EVENT_WORKITEM_PRIORITY: + case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: + case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: + case HAPI_PDG_EVENT_ALL: + case HAPI_PDG_EVENT_LOG: + case HAPI_PDG_CONTEXT_EVENTS: + break; + } + + if (bUpdatePDGNodeState) + { + // Work item events + EPDGNodeState CurrentTOPNodeState = TOPNode->NodeState; + if (CurrentTOPNodeState == EPDGNodeState::Cooking) + { + if (TOPNode->AreAllWorkItemsComplete()) + { + if (TOPNode->AnyWorkItemsFailed()) + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); + } + else + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + } + } + } + else if (TOPNode->AnyWorkItemsPending()) + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cooking); + } + } + + if (EventInfo.msgSH >= 0) + { + FString EventMsg; + FHoudiniEngineString::ToFString(EventInfo.msgSH, EventMsg); + if (!EventMsg.IsEmpty()) + { + // TODO: Event MSG? + // Somehow update the PDG event msg UI ?? + // Simply log for now... + if (MsgColor == FLinearColor::Red) + { + HOUDINI_LOG_ERROR(TEXT("%s"), *EventMsg); + } + else if (MsgColor == FLinearColor::Yellow) + { + HOUDINI_LOG_WARNING(TEXT("%s"), *EventMsg); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT("%s"), *EventMsg); + } + } + } +} + +void +FHoudiniPDGManager::ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo) +{ + InEventInfo.nodeId = -1; + InEventInfo.workitemId = -1; + InEventInfo.dependencyId = -1; + InEventInfo.currentState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; + InEventInfo.lastState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; + InEventInfo.eventType = HAPI_PDG_EventType::HAPI_PDG_EVENT_NULL; +} + + +bool +FHoudiniPDGManager::GetTOPAssetLinkAndNode( + const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode) +{ + // Returns the PDGAssetLink and FTOPNode data associated with this TOP node ID + OutAssetLink = nullptr; + OutTOPNode = nullptr; + for (TWeakObjectPtr& CurAssetLinkPtr : PDGAssetLinks) + { + if (!CurAssetLinkPtr.IsValid() || CurAssetLinkPtr.IsStale()) + continue; + + UHoudiniPDGAssetLink* CurAssetLink = CurAssetLinkPtr.Get(); + if (!CurAssetLink || CurAssetLink->IsPendingKill()) + continue; + + OutTOPNode = CurAssetLink->GetTOPNode((int32)InNodeID); + + if (OutTOPNode != nullptr) + { + OutAssetLink = CurAssetLink; + return true; + } + } + + return false; +} + +void +FHoudiniPDGManager::SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->NodeState = InPDGState; + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + //Debug.LogFormat("NotifyTOPNodePDGStateClear:: {0}", topNode._nodeName); + InTOPNode->NodeState = EPDGNodeState::None; + InTOPNode->WorkItemTally.ZeroAll(); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ZeroAll"), *(InTOPNode->NodePath)); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); + +} + +void +FHoudiniPDGManager::NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.TotalWorkItems = FMath::Max(InTOPNode->WorkItemTally.TotalWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally TotalWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.CookedWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookedWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.ErroredWorkItems = FMath::Max(InTOPNode->WorkItemTally.ErroredWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.WaitingWorkItems = FMath::Max(InTOPNode->WorkItemTally.WaitingWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.ScheduledWorkItems = FMath::Max(InTOPNode->WorkItemTally.ScheduledWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.CookingWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookingWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // TODO!!! + // Clear all work items' results for the specified TOP node. + // This destroys any loaded results (geometry etc). + //session.LogErrorOverride = false; + InAssetLink->ClearWorkItemResultByID(InWorkItemID, InTOPNode); + // session.LogErrorOverride = true; +} + +void +FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // Clear all of the work item's results for the specified TOP node and also remove the work item itself from + // the TOP node. + InAssetLink->DestroyWorkItemByID(InWorkItemID, InTOPNode); +} + +void +FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // Only update the editor properties if the PDG asset link's Actor is selected + // else, just update the workitemtally + InAssetLink->UpdateWorkItemTally(); + + UHoudiniAssetComponent* HAC = Cast(InAssetLink->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor* ActorOwner = HAC->GetOwner(); + if (ActorOwner != nullptr && ActorOwner->IsSelected()) + { + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + } +} + +void +FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + if (bSuccess) + { + if (InAssetLink->LinkState == EPDGLinkState::Linked) + { + if (InAssetLink->bAutoCook) + { + FHoudiniPDGManager::CookOutput(InAssetLink->GetSelectedTOPNetwork()); + } + } + else + { + UpdatePDGAssetLink(InAssetLink); + } + } + else + { + InAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + } +} + +bool +FHoudiniPDGManager::CreateWorkItemResult( + UTOPNode* InTOPNode, + const HAPI_PDG_GraphContextId& InContextID, + HAPI_PDG_WorkitemId InWorkItemID, + bool bInLoadResultObjects) +{ + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); + return false; + } + + HAPI_PDG_WorkitemInfo WorkItemInfo; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( + FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return false; + } + + if (WorkItemInfo.numResults > 0) + { + TArray ResultInfos; + ResultInfos.SetNum(WorkItemInfo.numResults); + const int32 resultCount = WorkItemInfo.numResults; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemResultInfo( + FHoudiniEngine::Get().GetSession(), + InTOPNode->NodeId, InWorkItemID, ResultInfos.GetData(), resultCount)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d result info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return false; + } + + FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); + if (!WorkResult) + { + FTOPWorkResult LocalWorkResult; + LocalWorkResult.WorkItemID = InWorkItemID; + LocalWorkResult.WorkItemIndex = WorkItemInfo.index; + const int32 Idx = InTOPNode->WorkResult.Add(LocalWorkResult); + WorkResult = &(InTOPNode->WorkResult[Idx]); + } + + FString WorkItemName; + FHoudiniEngineString::ToFString(WorkItemInfo.nameSH, WorkItemName); + + // Load each result geometry + const int32 NumResults = ResultInfos.Num(); + for (int32 Idx = 0; Idx < NumResults; Idx++) + { + const HAPI_PDG_WorkitemResultInfo& ResultInfo = ResultInfos[Idx]; + if (ResultInfo.resultTagSH <= 0 || ResultInfo.resultSH <= 0) + continue; + + FString CurrentTag; + FHoudiniEngineString::ToFString(ResultInfo.resultTagSH, CurrentTag); + if(CurrentTag.IsEmpty() || !CurrentTag.StartsWith(TEXT("file"))) + continue; + + FString CurrentPath = FString(); + FHoudiniEngineString::ToFString(ResultInfo.resultSH, CurrentPath); + + // Construct the name and look for an existing work result, re-use it if found, otherwise create a new one + const FString WorkResultName = FString::Printf( + TEXT("%s_%s_%d"), + *(InTOPNode->ParentName), + *WorkItemName, + WorkItemInfo.index); + + FTOPWorkResultObject* ExistingResultObject = WorkResult->ResultObjects.FindByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) + { + return InResultObject.Name == WorkResultName; + }); + if (ExistingResultObject) + { + ExistingResultObject->FilePath = CurrentPath; + if (ExistingResultObject->State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) + { + ExistingResultObject->State = EPDGWorkResultState::ToDelete; + } + else if ((ExistingResultObject->State == EPDGWorkResultState::Loaded || + ExistingResultObject->State == EPDGWorkResultState::ToDelete || + ExistingResultObject->State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) + { + // When the commandlet is not being used, we could leave the outputs in place and have + // translators try to re-use objects/components. When the commandlet is being used, the packages + // are always saved and standalone, so if we want to automatically clean up old results then we + // need to destroy the existing outputs + if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) + ExistingResultObject->DestroyResultOutputs(); + ExistingResultObject->State = EPDGWorkResultState::ToLoad; + } + else + { + ExistingResultObject->State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + } + } + else + { + FTOPWorkResultObject ResultObj; + ResultObj.Name = WorkResultName; + ResultObj.FilePath = CurrentPath; + ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + + WorkResult->ResultObjects.Add(ResultObj); + } + } + } + + return true; +} + +void +FHoudiniPDGManager::ProcessWorkItemResults() +{ + const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); + for (auto& CurrentPDGAssetLink : PDGAssetLinks) + { + // Iterate through all PDG Asset Link + UHoudiniPDGAssetLink* AssetLink = CurrentPDGAssetLink.Get(); + if (!AssetLink) + continue; + + // Set up package parameters to: + // Cook to temp houdini engine directory + // and if the PDG asset link is associated with a Houdini Asset Component (HAC): + // set the outer package to the HAC + // set the HoudiniAssetName according to the HAC + // set the ComponentGUID according to the HAC + // otherwise we set the outer to the asset link's parent and leave naming and GUID blank + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + // AActor* ParentActor = nullptr; + UObject* AssetLinkParent = AssetLink->GetOuter(); + UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; + if (HAC) + { + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + + // ParentActor = HAC->GetOwner(); + } + else + { + PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); + PackageParams.HoudiniAssetName = FString(); + PackageParams.HoudiniAssetActorName = FString(); + // PackageParams.ComponentGUID = HAC->GetComponentGUID(); + + // // Try to find a parent actor + // UObject* Parent = AssetLinkParent; + // while (Parent && !ParentActor) + // { + // ParentActor = Cast(Parent); + // if (!ParentActor) + // Parent = ParentActor->GetOuter(); + // } + } + PackageParams.ObjectName = FString(); + + // UWorld *World = ParentActor ? ParentActor->GetWorld() : AssetLink->GetWorld(); + UWorld *World = AssetLink->GetWorld(); + + // .. All TOP Nets + for (UTOPNetwork* CurrentTOPNet : AssetLink->AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + // .. All TOP Nodes + for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + // ... All WorkResult + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; + CurrentTOPNode->bCachedHaveLoadedWorkResults = false; + for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) + { + // ... All WorkResultObjects + for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) + { + if (CurrentWorkResultObj.State == EPDGWorkResultState::ToLoad) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Loading; + + // Load this WRObj + PackageParams.PDGTOPNetworkName = CurrentTOPNet->NodeName; + PackageParams.PDGTOPNodeName = CurrentTOPNode->NodeName; + PackageParams.PDGWorkItemIndex = CurrentWorkResult.WorkItemIndex; + + if (CommandletStatus == EHoudiniBGEOCommandletStatus::Connected) + { + BGEOCommandletEndpoint->Send(new FHoudiniPDGImportBGEOMessage( + CurrentWorkResultObj.FilePath, + CurrentWorkResultObj.Name, + PackageParams, + CurrentTOPNode->NodeId, + CurrentWorkResult.WorkItemID + ), BGEOCommandletAddress); + } + else + { + if (FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + AssetLink, + CurrentTOPNode, + CurrentWorkResultObj, + PackageParams)) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Loaded; + CurrentTOPNode->bCachedHaveLoadedWorkResults = true; + + // Broadcast that we have loaded the work result object to those interested + AssetLink->OnWorkResultObjectLoaded.Broadcast( + AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemID, CurrentWorkResultObj.Name); + } + else + { + CurrentWorkResultObj.State = EPDGWorkResultState::None; + } + } + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::Loaded) + { + // If the work item result obj is in the "Loaded" state, confirm that the output actor + // is still valid (the user could have manually deleted the output + if (!IsValid(CurrentWorkResultObj.GetOutputActorOwner().GetOutputActor())) + { + // If the output actor is invalid, set the state to ToDelete to complete the + // unload/deletion process + CurrentWorkResultObj.State = EPDGWorkResultState::ToDelete; + } + else + { + CurrentTOPNode->bCachedHaveLoadedWorkResults = true; + } + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::ToDelete) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Deleting; + + // Delete and clean up that WRObj + CurrentWorkResultObj.DestroyResultOutputs(); + CurrentWorkResultObj.GetOutputActorOwner().DestroyOutputActor(); + CurrentWorkResultObj.State = EPDGWorkResultState::Deleted; + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::Deleted) + { + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::NotLoaded) + { + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + } + } + } + } + } +} + +void FHoudiniPDGManager::HandleImportBGEODiscoverMessage( + const FHoudiniPDGImportBGEODiscoverMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_DISPLAY(TEXT("Received Discover from %s"), *InContext->GetSender().ToString()); + // Ignore any discover acks received if we already have a valid local address + // for the commandlet + if (BGEOCommandletAddress.IsValid()) + return; + + if (BGEOCommandletProcHandle.IsValid() && InMessage.CommandletGuid.IsValid() && BGEOCommandletGuid == InMessage.CommandletGuid) + { + BGEOCommandletAddress = InContext->GetSender(); + } +} + +void FHoudiniPDGManager::HandleImportBGEOResultMessage( + const FHoudiniPDGImportBGEOResultMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_MESSAGE(TEXT("Received BGEO import result message")); + if (InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_Success || InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_PartialSuccess) + { + FHoudiniPackageParams PackageParams; + InMessage.PopulatePackageParams(PackageParams); + + // Find asset link and work result object + UHoudiniPDGAssetLink *AssetLink = nullptr; + UTOPNode *TOPNode = nullptr; + if (!GetTOPAssetLinkAndNode(InMessage.TOPNodeId, AssetLink, TOPNode) || + !IsValid(AssetLink) || !IsValid(TOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP node with id %d, aborting output object creation."), InMessage.TOPNodeId); + return; + } + + FTOPWorkResult* WorkResult = AssetLink->GetWorkResultByID(InMessage.WorkItemId, TOPNode); + if (WorkResult == nullptr) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result with id %d, aborting output object creation."), InMessage.WorkItemId); + return; + } + const FString& WorkResultObjectName = InMessage.Name; + FTOPWorkResultObject* WorkResultObject = WorkResult->ResultObjects.FindByPredicate( + [&WorkResultObjectName](const FTOPWorkResultObject& WorkResultObject) + { + return WorkResultObject.Name == WorkResultObjectName; + } + ); + if (WorkResultObject == nullptr) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result object with name %s, aborting output object creation."), *InMessage.Name); + return; + } + + if (WorkResultObject->State != EPDGWorkResultState::Loading) + { + HOUDINI_LOG_WARNING(TEXT("TOP work result object (%s) not in Loading state, aborting output object creation."), *InMessage.Name); + return; + } + + // Set package params outer + UObject* AssetLinkParent = AssetLink->GetOuter(); + UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; + if (HAC) + { + PackageParams.OuterPackage = HAC->GetComponentLevel(); + } + else + { + PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); + } + + // Construct UHoudiniOutputs + bool bHasUnsupportedOutputs = false; + TArray NewOutputs; + TMap InstancedOutputPartData; + NewOutputs.Reserve(InMessage.Outputs.Num()); + for (const FHoudiniPDGImportNodeOutput& Output : InMessage.Outputs) + { + UHoudiniOutput* NewOutput = NewObject( + AssetLink, + UHoudiniOutput::StaticClass(), + NAME_None,//FName(*OutputName), + RF_NoFlags); + NewOutputs.Add(NewOutput); + const int32 NumHGPO = Output.HoudiniGeoPartObjects.Num(); + for (int32 Index = 0; Index < NumHGPO; ++Index) + { + const FHoudiniGeoPartObject& HGPO = Output.HoudiniGeoPartObjects[Index]; + NewOutput->AddNewHGPO(HGPO); + + if (Output.InstancedOutputPartData.IsValidIndex(Index)) + { + FHoudiniOutputObjectIdentifier Identifier; + Identifier.ObjectId = HGPO.ObjectId; + Identifier.GeoId = HGPO.GeoId; + Identifier.PartId = HGPO.PartId; + Identifier.PartName = HGPO.PartName; + FHoudiniInstancedOutputPartData InstancedPartData = Output.InstancedOutputPartData[Index]; + InstancedPartData.BuildOriginalInstancedTransformsAndObjectArrays(); + InstancedOutputPartData.Add(Identifier, InstancedPartData); + } + } + const int32 NumObjects = Output.OutputObjects.Num(); + for (int32 Index = 0; Index < NumObjects; ++Index) + { + const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; + FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; + + const FString& FullPackagePath = ImportOutputObject.PackagePath; + FString PackagePath; + FString PackageName; + const bool bDidSplit = FullPackagePath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bDidSplit) + PackagePath = FullPackagePath; + + FHoudiniOutputObject OutputObject; + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!IsValid(Package)) + { + // Editor might have picked up the package yet, try to load it + Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); + } + if (IsValid(Package)) + { + OutputObject.OutputObject = FindObject(Package, *PackageName); + } + Identifier.bLoaded = true; + NewOutput->GetOutputObjects().Add(Identifier, OutputObject); + } + NewOutput->UpdateOutputType(); + const EHoudiniOutputType OutputType = NewOutput->GetType(); + if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) + { + bHasUnsupportedOutputs = true; + } + } + + bool bSuccess = true; + if (bHasUnsupportedOutputs) + { + HOUDINI_LOG_MESSAGE(TEXT("Processing output types not supported by commandlet for %s"), *InMessage.Name); + bSuccess = FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + AssetLink, TOPNode, *WorkResultObject, PackageParams, + { + EHoudiniOutputType::Landscape, + EHoudiniOutputType::Curve, + EHoudiniOutputType::Skeletal + } + ); + + if (bSuccess) + { + // Clear/remove the outputs on WorkResultObject that are supported by the commandlet, since we + // are going to replace them with NewOutputs now + TArray& CurrentOutputs = WorkResultObject->GetResultOutputs(); + const int32 NumCurrentOutputs = CurrentOutputs.Num(); + for (int32 Index = 0; Index < NumCurrentOutputs; ++Index) + { + UHoudiniOutput* CurOutput = CurrentOutputs[Index]; + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) + { + // Was created in editor, override the dummy one in NewOutputs with CurOutput + if (NewOutputs.IsValidIndex(Index)) + { + if (OutputType == NewOutputs[Index]->GetType()) + { + UHoudiniOutput* TempOutput = NewOutputs[Index]; + FHoudiniOutputTranslator::ClearOutput(TempOutput); + NewOutputs[Index] = CurOutput; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Unexpected commandlet output type at index %d!"), Index); + } + } + else + { + HOUDINI_LOG_ERROR(TEXT("Expected output index %d from commandlet to be exist!"), Index); + } + } + } + } + } + + if (bSuccess && FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( + AssetLink, + TOPNode, + *WorkResultObject, + PackageParams, + NewOutputs, + {EHoudiniOutputType::Mesh, EHoudiniOutputType::Instancer}, + &InstancedOutputPartData)) + { + const int32 NumOutputs = NewOutputs.Num(); + for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex) + { + UHoudiniOutput *NewOutput = NewOutputs[OutputIndex]; + + if (NewOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + const FHoudiniPDGImportNodeOutput& Output = InMessage.Outputs[OutputIndex]; + int32 NumObjects = Output.OutputObjects.Num(); + for (int32 Index = 0; Index < NumObjects; ++Index) + { + const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; + FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; + FHoudiniOutputObject *OutputObject = NewOutput->GetOutputObjects().Find(Identifier); + if (OutputObject && IsValid(OutputObject->OutputComponent)) + { + // Update generic property attributes + FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( + OutputObject->OutputComponent, + ImportOutputObject.GenericAttributes.PropertyAttributes); + + // Copy cached attributes + OutputObject->CachedAttributes.Append(ImportOutputObject.CachedAttributes); + } + } + } + } + else + { + bSuccess = false; + } + + if (bSuccess) + { + WorkResultObject->State = EPDGWorkResultState::Loaded; + HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); + // Broadcast that we have loaded the work result object to those interested + AssetLink->OnWorkResultObjectLoaded.Broadcast(AssetLink, TOPNode, WorkResult->WorkItemID, WorkResultObject->Name); + } + else + { + WorkResultObject->State = EPDGWorkResultState::None; + HOUDINI_LOG_WARNING(TEXT("Failed to process loaded assets for %s"), *InMessage.Name); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Commandlet failed to import bgeo for %s"), *InMessage.Name); + } +} + +bool FHoudiniPDGManager::CreateBGEOCommandletAndEndpoint() +{ + if (!BGEOCommandletEndpoint.IsValid()) + { + BGEOCommandletAddress.Invalidate(); + BGEOCommandletEndpoint = FMessageEndpoint::Builder(TEXT("Houdini BGEO Commandlet")) + .Handling(this, &FHoudiniPDGManager::HandleImportBGEOResultMessage) + .Handling(this, &FHoudiniPDGManager::HandleImportBGEODiscoverMessage) + .ReceivingOnThread(ENamedThreads::GameThread); + + if (!BGEOCommandletEndpoint.IsValid()) + { + HOUDINI_LOG_WARNING(TEXT("Could not set up messaging end point for BGEO commandlet")); + return false; + } + + BGEOCommandletEndpoint->Subscribe(); + } + + if (!BGEOCommandletProcHandle.IsValid() || !FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + { + // Start the bgeo commandlet + static const FString BGEOCommandletName = TEXT("HoudiniGeoImport"); + BGEOCommandletGuid = FGuid::NewGuid(); + BGEOCommandletAddress.Invalidate(); + + // Get the absolute path to the project file, if known, otherwise get + // the project name. For the path: quote it for the command line. + IFileManager& FileManager = IFileManager::Get(); + FString ProjectPathOrName = FApp::GetProjectName(); + if (FPaths::IsProjectFilePathSet()) + { + const FString ProjectPath = FPaths::GetProjectFilePath(); + if (!ProjectPath.IsEmpty()) + { + ProjectPathOrName = FString::Printf( + TEXT("\"%s\""), + *FileManager.ConvertToAbsolutePathForExternalAppForRead(*ProjectPath) + ); + } + } + + if (ProjectPathOrName.IsEmpty()) + return false; + + // Get the executable path for the app/editor + FString ExePath = FPlatformProcess::GenerateApplicationPath(FApp::GetName(), FApp::GetBuildConfiguration()); + if (!ExePath.IsEmpty()) + ExePath = FileManager.ConvertToAbsolutePathForExternalAppForRead(*ExePath); + + if (ExePath.IsEmpty()) + return false; + + const FString CommandLineParameters = FString::Printf( + TEXT("%s -messaging -run=%s -guid=%s -listen=%s -managerpid=%d"), + *ProjectPathOrName, + *BGEOCommandletName, + *BGEOCommandletGuid.ToString(), + *BGEOCommandletEndpoint->GetAddress().ToString(), + FPlatformProcess::GetCurrentProcessId()); + + BGEOCommandletProcHandle = FPlatformProcess::CreateProc( + *ExePath, + *CommandLineParameters, + false, + true, + false, + &BGEOCommandletProcessId, + 0, + NULL, + NULL); + if (!BGEOCommandletProcHandle.IsValid()) + { + return false; + } + } + + return true; +} + +void FHoudiniPDGManager::StopBGEOCommandletAndEndpoint() +{ + BGEOCommandletEndpoint.Reset(); + BGEOCommandletAddress.Invalidate(); + BGEOCommandletGuid.Invalidate(); + + if (BGEOCommandletProcHandle.IsValid() && FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + { + FPlatformProcess::TerminateProc(BGEOCommandletProcHandle, true); + if (BGEOCommandletProcHandle.IsValid()) + { + FPlatformProcess::WaitForProc(BGEOCommandletProcHandle); + FPlatformProcess::CloseProc(BGEOCommandletProcHandle); + } + } +} + +EHoudiniBGEOCommandletStatus FHoudiniPDGManager::UpdateAndGetBGEOCommandletStatus() +{ + if (BGEOCommandletProcHandle.IsValid()) + { + if (!FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Crashed; + else if (BGEOCommandletAddress.IsValid()) + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Connected; + else + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Running; + } + else + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::NotStarted; + + return BGEOCommandletStatus; +} + + +bool +FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) +{ + if (InAssetId < 0) + return false; + + // Get all the network nodes within the asset, recursively. + // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type + int32 NetworkNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); + + if (NetworkNodeCount <= 0) + return false; + + TArray AllNetworkNodeIDs; + AllNetworkNodeIDs.SetNum(NetworkNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); + + // There is currently no way to only get non bypassed nodes via HAPI + // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... + TArray AllBypassedTOPNetNodeIDs; + { + int32 BypassedTOPNetNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); + + if (BypassedTOPNetNodeCount > 0) + { + // Get the list of all bypassed TOP Net... + AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); + + // ... and remove them from the network list + for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) + AllNetworkNodeIDs.RemoveAt(Idx); + } + } + } + + // For each Network we found earlier, only consider those with TOP child nodes + // If we find TOP nodes in a valid network, then consider this HDA a PDG HDA + for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) + { + if (CurrentNodeId < 0) + { + continue; + } + + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) + { + continue; + } + + // Skip non TOP or SOP networks + if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP + && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) + { + continue; + } + + // Get the list of all TOP nodes within the current network (ignoring schedulers) + int32 TOPNodeCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) + { + continue; + } + + // We found valid TOP Nodes, this is a PDG HDA + if (TOPNodeCount > 0) + return true; + } + + // No valid TOP node found in SOP/TOP Networks, this is not a PDG HDA + return false; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.h b/Source/HoudiniEngine/Private/HoudiniPDGManager.h index de2e6fe3a..234a35ee9 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.h @@ -1,200 +1,200 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HAL/PlatformProcess.h" - -#include "MessageEndpoint.h" - -class UHoudiniAssetComponent; -class UHoudiniPDGAssetLink; -class UTOPNetwork; -class UTOPNode; -class FSocket; - -enum class EPDGNodeState : uint8; - -// BGEO commandlet status -enum class HOUDINIENGINE_API EHoudiniBGEOCommandletStatus : uint8 -{ - // PDG manager has not tried to start the commandlet - NotStarted, - // PDG manager has PID for the commandlet and the host OS indicates it is running, but no message has been - // received from it yet - Running, - // PDG manager has PID for the commandlet, the host OS indicates it is running, and a discover message has been - // received - Connected, - // After being in the Connected state, the commandlet stopped running (host OS indicates PID is not valid) - Crashed -}; - -struct HOUDINIENGINE_API FHoudiniPDGManager -{ - -public: - - FHoudiniPDGManager(); - - virtual ~FHoudiniPDGManager(); - - // Initialize the PDG Asset Link for a HoudiniAssetComponent - // returns true if the HAC uses a PDG asset, and a PDGAssetLink was successfully created - bool InitializePDGAssetLink(UHoudiniAssetComponent* InHAC); - - // Updates an existing PDG AssetLink - static bool UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink); - - // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. - static bool PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys=false); - - static void RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink); - - // Indicates if the Asset is a PDG Asset - // This will look for TOP nodes in all SOP/TOP net in the HDA. - static bool IsPDGAsset(const HAPI_NodeId& InAssetId); - - // Given TOP nodes from a TOP network, populate internal state from each TOP node. - static bool PopulateTOPNodes( - const TArray& InTopNodeIDs, - UTOPNetwork* InTOPNetwork, - UHoudiniPDGAssetLink* InPDGAssetLink, - bool bInZeroWorkItemTallys=false); - - // Cook the specified TOP node. - static void CookTOPNode(UTOPNode* InTOPNode); - - // Dirty the specified TOP node and clear its work item results. - static void DirtyTOPNode(UTOPNode* InTOPNode); - - // // Dirty all the tasks/work items of the specified TOP node. Does not - // // clear its work item results. - // static void DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode); - - // Dirty the TOP network and clear all work item results. - static void DirtyAll(UTOPNetwork* InTOPNet); - - // Cook the output TOP node of the currently selected TOP network. - static void CookOutput(UTOPNetwork* InTOPNet); - - // Pause the PDG cook of the currently selected TOP network - static void PauseCook(UTOPNetwork* InTOPNet); - - // Cancel the PDG cook of the currently selected TOP network - static void CancelCook(UTOPNetwork* InTOPNet); - - static void NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess); - - // Update all registered PDG Asset links - void Update(); - - void ReinitializePDGContext(); - - // Clear all of the specified work item's results from the specified TOP node. This destroys any loaded results - // (geometry etc), but keeps the work item struct. - //void ClearWorkItemResult(const HAPI_PDG_GraphContextId& InContextID, const HAPI_PDG_EventInfo& InEventInfo, FTOPNode& TOPNode); - void ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); - - // Clear the specified work item's results from the specified TOP node and remove the work item struct from the TOP - // node. This destroys any loaded results (geometry etc), and the work item struct. - void RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); - - // Create FTOPWorkResult for a given TOP node, and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. - // Geometry is not directly loaded by this function, the FTOPWorkResultObjects' states will be set to ToLoad and - // the ProcessWorkItemResults function will take care of loading the geo. - // Results must be tagged with 'file', and must have a file path, otherwise will not included. - bool CreateWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); - - // Handles replies from commandlets in response to a FHoudiniPDGImportBGEODiscoverMessage - void HandleImportBGEODiscoverMessage( - const struct FHoudiniPDGImportBGEODiscoverMessage& InMessage, - const TSharedRef& InContext); - - // Handles messages sent by the commandlet once an import of a bgeo is complete, and uassets have been created. - void HandleImportBGEOResultMessage( - const struct FHoudiniPDGImportBGEOResultMessage& InMessage, - const TSharedRef& InContext); - - // Create the bgeo commandlet endpoint and start the commandlet (if not already running). - bool CreateBGEOCommandletAndEndpoint(); - - void StopBGEOCommandletAndEndpoint(); - - // Updates and returns the BGEO commandlet status - EHoudiniBGEOCommandletStatus UpdateAndGetBGEOCommandletStatus(); - -private: - - void UpdatePDGContexts(); - - void ProcessWorkItemResults(); - - void ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo); - - static void ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo); - - // Returns the PDGAssetLink and FTOPNode associated with this TOP node ID - bool GetTOPAssetLinkAndNode(const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode); - - void SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState); - - void NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode); - - void NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - - void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - - void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - - void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - - void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - - void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - -private: - - TArray PDGContextNames; - TArray PDGContextIDs; - TArray PDGEventInfos; - - TArray> PDGAssetLinks; - - int32 MaxNumberOfPDGEvents = 20; - int32 MaxNumberOPDGContexts = 20; - - TSharedPtr BGEOCommandletEndpoint; - FMessageAddress BGEOCommandletAddress; - FProcHandle BGEOCommandletProcHandle; - FGuid BGEOCommandletGuid; - uint32 BGEOCommandletProcessId; - // Keep track of the BGEO commandlet status - EHoudiniBGEOCommandletStatus BGEOCommandletStatus; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HAL/PlatformProcess.h" + +#include "MessageEndpoint.h" + +class UHoudiniAssetComponent; +class UHoudiniPDGAssetLink; +class UTOPNetwork; +class UTOPNode; +class FSocket; + +enum class EPDGNodeState : uint8; + +// BGEO commandlet status +enum class HOUDINIENGINE_API EHoudiniBGEOCommandletStatus : uint8 +{ + // PDG manager has not tried to start the commandlet + NotStarted, + // PDG manager has PID for the commandlet and the host OS indicates it is running, but no message has been + // received from it yet + Running, + // PDG manager has PID for the commandlet, the host OS indicates it is running, and a discover message has been + // received + Connected, + // After being in the Connected state, the commandlet stopped running (host OS indicates PID is not valid) + Crashed +}; + +struct HOUDINIENGINE_API FHoudiniPDGManager +{ + +public: + + FHoudiniPDGManager(); + + virtual ~FHoudiniPDGManager(); + + // Initialize the PDG Asset Link for a HoudiniAssetComponent + // returns true if the HAC uses a PDG asset, and a PDGAssetLink was successfully created + bool InitializePDGAssetLink(UHoudiniAssetComponent* InHAC); + + // Updates an existing PDG AssetLink + static bool UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink); + + // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. + static bool PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys=false); + + static void RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink); + + // Indicates if the Asset is a PDG Asset + // This will look for TOP nodes in all SOP/TOP net in the HDA. + static bool IsPDGAsset(const HAPI_NodeId& InAssetId); + + // Given TOP nodes from a TOP network, populate internal state from each TOP node. + static bool PopulateTOPNodes( + const TArray& InTopNodeIDs, + UTOPNetwork* InTOPNetwork, + UHoudiniPDGAssetLink* InPDGAssetLink, + bool bInZeroWorkItemTallys=false); + + // Cook the specified TOP node. + static void CookTOPNode(UTOPNode* InTOPNode); + + // Dirty the specified TOP node and clear its work item results. + static void DirtyTOPNode(UTOPNode* InTOPNode); + + // // Dirty all the tasks/work items of the specified TOP node. Does not + // // clear its work item results. + // static void DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode); + + // Dirty the TOP network and clear all work item results. + static void DirtyAll(UTOPNetwork* InTOPNet); + + // Cook the output TOP node of the currently selected TOP network. + static void CookOutput(UTOPNetwork* InTOPNet); + + // Pause the PDG cook of the currently selected TOP network + static void PauseCook(UTOPNetwork* InTOPNet); + + // Cancel the PDG cook of the currently selected TOP network + static void CancelCook(UTOPNetwork* InTOPNet); + + static void NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess); + + // Update all registered PDG Asset links + void Update(); + + void ReinitializePDGContext(); + + // Clear all of the specified work item's results from the specified TOP node. This destroys any loaded results + // (geometry etc), but keeps the work item struct. + //void ClearWorkItemResult(const HAPI_PDG_GraphContextId& InContextID, const HAPI_PDG_EventInfo& InEventInfo, FTOPNode& TOPNode); + void ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); + + // Clear the specified work item's results from the specified TOP node and remove the work item struct from the TOP + // node. This destroys any loaded results (geometry etc), and the work item struct. + void RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); + + // Create FTOPWorkResult for a given TOP node, and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. + // Geometry is not directly loaded by this function, the FTOPWorkResultObjects' states will be set to ToLoad and + // the ProcessWorkItemResults function will take care of loading the geo. + // Results must be tagged with 'file', and must have a file path, otherwise will not included. + bool CreateWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); + + // Handles replies from commandlets in response to a FHoudiniPDGImportBGEODiscoverMessage + void HandleImportBGEODiscoverMessage( + const struct FHoudiniPDGImportBGEODiscoverMessage& InMessage, + const TSharedRef& InContext); + + // Handles messages sent by the commandlet once an import of a bgeo is complete, and uassets have been created. + void HandleImportBGEOResultMessage( + const struct FHoudiniPDGImportBGEOResultMessage& InMessage, + const TSharedRef& InContext); + + // Create the bgeo commandlet endpoint and start the commandlet (if not already running). + bool CreateBGEOCommandletAndEndpoint(); + + void StopBGEOCommandletAndEndpoint(); + + // Updates and returns the BGEO commandlet status + EHoudiniBGEOCommandletStatus UpdateAndGetBGEOCommandletStatus(); + +private: + + void UpdatePDGContexts(); + + void ProcessWorkItemResults(); + + void ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo); + + static void ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo); + + // Returns the PDGAssetLink and FTOPNode associated with this TOP node ID + bool GetTOPAssetLinkAndNode(const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode); + + void SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState); + + void NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode); + + void NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + + void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + + void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + + void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + + void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + + void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + +private: + + TArray PDGContextNames; + TArray PDGContextIDs; + TArray PDGEventInfos; + + TArray> PDGAssetLinks; + + int32 MaxNumberOfPDGEvents = 20; + int32 MaxNumberOPDGContexts = 20; + + TSharedPtr BGEOCommandletEndpoint; + FMessageAddress BGEOCommandletAddress; + FProcHandle BGEOCommandletProcHandle; + FGuid BGEOCommandletGuid; + uint32 BGEOCommandletProcessId; + // Keep track of the BGEO commandlet status + EHoudiniBGEOCommandletStatus BGEOCommandletStatus; + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp index 53a9ed520..29733597c 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp @@ -255,7 +255,7 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( // { // // Save the current map // FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); - // UPackage* CurrentWorldPackage = CreatePackage(nullptr, *CurrentWorldPath); + // UPackage* CurrentWorldPackage = CreatePackage(*CurrentWorldPath); // if (CurrentWorldPackage) // { // CurrentWorldPackage->MarkPackageDirty(); diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h index dd52428ed..2419b3c81 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h @@ -1,75 +1,75 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -class UHoudiniPDGAssetLink; -class UHoudiniOutput; -class AActor; -class UTOPNode; - -enum class EHoudiniOutputType : uint8; - -struct FHoudiniPackageParams; -struct FTOPWorkResultObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniInstancedOutputPartData; - -struct HOUDINIENGINE_API FHoudiniPDGTranslator -{ - public: - // Create/update assets/geometry for all PDG outputs of InWorkResultObject. This will use - // InWorkResultObject.FilePath to load the BGEO file and update/build outputs. - static bool CreateAllResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray InOutputTypesToProcess={}, - bool bInTreatExistingMaterialsAsUpToDate=false); - - static bool LoadExistingAssetsAsResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray& InOutputs, - TArray InOutputTypesToProcess={}, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); - - // Use the relevant translators to create assets/geometry for all PDG outputs (InOutputs). - // InOuterComponent is the component to attach the created output objects/components to. - static bool CreateAllResultObjectsFromPDGOutputs( - TArray& InOutputs, - const FHoudiniPackageParams& InPackageParams, - UObject* InOuterComponent, - TArray InOutputTypesToProcess={}, - bool bInTreatExistingMaterialsAsUpToDate=false, - bool bInOnlyUseExistingAssets=false, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +class UHoudiniPDGAssetLink; +class UHoudiniOutput; +class AActor; +class UTOPNode; + +enum class EHoudiniOutputType : uint8; + +struct FHoudiniPackageParams; +struct FTOPWorkResultObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniInstancedOutputPartData; + +struct HOUDINIENGINE_API FHoudiniPDGTranslator +{ + public: + // Create/update assets/geometry for all PDG outputs of InWorkResultObject. This will use + // InWorkResultObject.FilePath to load the BGEO file and update/build outputs. + static bool CreateAllResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray InOutputTypesToProcess={}, + bool bInTreatExistingMaterialsAsUpToDate=false); + + static bool LoadExistingAssetsAsResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray& InOutputs, + TArray InOutputTypesToProcess={}, + const TMap* InPreBuiltInstancedOutputPartData=nullptr); + + // Use the relevant translators to create assets/geometry for all PDG outputs (InOutputs). + // InOuterComponent is the component to attach the created output objects/components to. + static bool CreateAllResultObjectsFromPDGOutputs( + TArray& InOutputs, + const FHoudiniPackageParams& InPackageParams, + UObject* InOuterComponent, + TArray InOutputTypesToProcess={}, + bool bInTreatExistingMaterialsAsUpToDate=false, + bool bInOnlyUseExistingAssets=false, + const TMap* InPreBuiltInstancedOutputPartData=nullptr); +}; diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp index ccb218754..c320de333 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp @@ -1,433 +1,435 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPackageParams.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniStringResolver.h" - -#include "PackageTools.h" -#include "ObjectTools.h" -#include "Engine/StaticMesh.h" -#include "UObject/MetaData.h" - -// -FHoudiniPackageParams::FHoudiniPackageParams() -{ - PackageMode = EPackageMode::CookToTemp; - ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - OuterPackage = nullptr; - ObjectName = FString(); - HoudiniAssetName = FString(); - HoudiniAssetActorName = FString(); - - ObjectId = 0; - GeoId = 0; - PartId = 0; - SplitStr = 0; - - ComponentGUID.Invalidate(); - - PDGTOPNetworkName.Empty(); - PDGTOPNodeName.Empty(); - PDGWorkItemIndex = INDEX_NONE; - - bAttemptToLoadMissingPackages = false; -} - - -// -FHoudiniPackageParams::~FHoudiniPackageParams() -{ - - -} - - -// Returns the object flags corresponding to the current package mode -EObjectFlags -FHoudiniPackageParams::GetObjectFlags() const -{ - if (PackageMode == EPackageMode::CookToTemp) - return RF_Public | RF_Standalone; - else if (PackageMode == EPackageMode::Bake) - return RF_Public | RF_Standalone; - else - return RF_NoFlags; -} - -FString -FHoudiniPackageParams::GetPackageName() const -{ - if (!ObjectName.IsEmpty()) - return ObjectName; - - // If we have PDG infos, generate a name including them - if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkItemIndex >= 0) - { - return FString::Printf( - TEXT("%s_%s_%s_%d_%d_%s"), - *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkItemIndex, PartId, *SplitStr); - } - else - { - // Generate an object name using the HGPO IDs and the HDA name - return FString::Printf(TEXT("%s_%d_%d_%d_%s"), *HoudiniAssetName, ObjectId, GeoId, PartId, *SplitStr); - } -} - -FString -FHoudiniPackageParams::GetPackagePath() const -{ - FString PackagePath = FString(); - switch (PackageMode) - { - case EPackageMode::CookToLevel: - { - // Path to the persistent level - //PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()); - - // In this mode, we'll use the persistent level as our package's outer - // simply use the hda + component guid for the path - // Add a subdir for the HDA - if (!HoudiniAssetName.IsEmpty()) - PackagePath += TEXT("/") + HoudiniAssetName; - // Add a subdir using the owner component GUID if possible - if(ComponentGUID.IsValid()) - PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); - - // TODO: FIX ME!!! - // Old version - // Build the package name - PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()) + - TEXT("/") + - HoudiniAssetName; - } - break; - - case EPackageMode::CookToTemp: - { - // Temporary Folder - PackagePath = TempCookFolder; - // Add a subdir for the HDA - if (!HoudiniAssetName.IsEmpty()) - PackagePath += TEXT("/") + HoudiniAssetName; - // Add a subdir using the owner component GUID if possible - if (ComponentGUID.IsValid()) - PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); - } - break; - - case EPackageMode::Bake: - { - PackagePath = BakeFolder; - } - break; - } - - return PackagePath; -} - -bool -FHoudiniPackageParams::GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter) -{ - OutBakeCounter = 0; - - if (!IsValid(InAsset)) - return false; - - UPackage* Package = InAsset->GetOutermost();// GetPackage(); - // const FString PackagePathName = Package->GetPathName(); - // FString PackagePathNamePrefix; - // FString BakeCountOrGUID; - // if (!GetPackageNameWithoutBakeCounterOrGUIDSuffix(PackagePathName, PackagePathNamePrefix, BakeCountOrGUID)) - // PackagePathNamePrefix = PackagePathName; - // - // const FString ThisPackageNameBase = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); - // if (!PackagePathNamePrefix.Equals(ThisPackageNameBase)) - // return false; - // - // // Not a valid counter suffix, could be a GUID suffix. Return true since the prefixes match. - // if (BakeCountOrGUID.IsNumeric()) - // OutBakeCounter = FCString::Atoi(*BakeCountOrGUID); - // - // return true; - - if (!IsValid(Package)) - return false; - - UMetaData* MetaData = Package->GetMetaData(); - if (!IsValid(MetaData)) - return false; - - if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER)) - { - FString BakeCounterStr = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER); - BakeCounterStr.TrimStartAndEndInline(); - if (BakeCounterStr.IsNumeric()) - { - OutBakeCounter = FCString::Atoi(*BakeCounterStr); - return true; - } - } - - return false; -} - -bool -FHoudiniPackageParams::GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID) -{ - if (!InAsset) - return false; - - UPackage* Package = InAsset->GetOutermost();//GetPackage(); - if (!IsValid(Package)) - return false; - - UMetaData* MetaData = Package->GetMetaData(); - if (!IsValid(MetaData)) - return false; - - if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) - { - OutGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); - OutGUID.TrimStartAndEndInline(); - if (!OutGUID.IsEmpty()) - return true; - } - - return false; -} - -FString -FHoudiniPackageParams::GetPackageNameExcludingBakeCounter(const UObject* InAsset) -{ - if (!IsValid(InAsset)) - return FString(); - - UPackage* Package = InAsset->GetOutermost(); - if (!IsValid(Package)) - return FString(); - - FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); - - int32 BakeCounter = 0; - if (GetBakeCounterFromBakedAsset(InAsset, BakeCounter)) - { - const FString BakeCounterSuffix = FString::Printf(TEXT("_%d"), BakeCounter); - if (PackageName.EndsWith(BakeCounterSuffix)) - PackageName = PackageName.Mid(0, PackageName.Len() - BakeCounterSuffix.Len()); - } - - return PackageName; -} - -bool -FHoudiniPackageParams::MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const -{ - if (!IsValid(InAsset)) - return false; - - UPackage* Package = InAsset->GetOutermost();//GetPackage(); - if (!IsValid(Package)) - return false; - - const FString InAssetPackagePathName = FPaths::GetPath(Package->GetPathName()) + TEXT("/") + GetPackageNameExcludingBakeCounter(InAsset); - const FString ThisPackagePathName = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); - return InAssetPackagePathName.Equals(ThisPackagePathName); -} - -FString -FHoudiniPackageParams::GetPackageNameExcludingGUID(const UObject* InAsset) -{ - if (!IsValid(InAsset)) - return FString(); - - UPackage* Package = InAsset->GetOutermost(); - if (!IsValid(Package)) - return FString(); - - FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); - - FString GUIDStr; - if (GetGUIDFromTempAsset(InAsset, GUIDStr)) - { - if (PackageName.EndsWith(TEXT("_") + GUIDStr)) - PackageName = PackageName.Mid(0, PackageName.Len() - GUIDStr.Len() - 1); - } - - return PackageName; -} - -UPackage* -FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart) const -{ - // GUID/counter used to differentiate with existing package - int32 BakeCounter = InBakeCounterStart; - FGuid CurrentGuid = FGuid::NewGuid(); - - // Get the appropriate package path/name for this object - FString PackageName = GetPackageName(); - FString PackagePath = GetPackagePath(); - - // Iterate until we find a suitable name for the package - UPackage * NewPackage = nullptr; - while (true) - { - OutPackageName = PackageName; - - // Append the Bake guid/counter to the object name if needed - if (BakeCounter > 0) - { - OutPackageName += (PackageMode == EPackageMode::Bake) - ? TEXT("_") + FString::FromInt(BakeCounter) - : TEXT("_") + CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); - } - - // Build the final package name - FString FinalPackageName = PackagePath + TEXT("/") + OutPackageName; - // Sanitize package name. - FinalPackageName = UPackageTools::SanitizePackageName(FinalPackageName); - - UObject * PackageOuter = nullptr; - if (PackageMode == EPackageMode::CookToLevel) - { - // If we are not baking, then use outermost package, since objects within our package - // need to be visible to external operations, such as copy paste. - PackageOuter = OuterPackage; - } - - // See if a package named similarly already exists - UPackage* FoundPackage = FindPackage(PackageOuter, *FinalPackageName); - if (FoundPackage == nullptr && bAttemptToLoadMissingPackages) - { - FoundPackage = LoadPackage(Cast(PackageOuter), *FinalPackageName, LOAD_NoWarn); - } - if (ReplaceMode == EPackageReplaceMode::CreateNewAssets - && FoundPackage && !FoundPackage->IsPendingKill()) - { - // we need to generate a new name for it - CurrentGuid = FGuid::NewGuid(); - BakeCounter++; - continue; - } - - // Create actual package. - NewPackage = CreatePackage(PackageOuter, *FinalPackageName); - if (IsValid(NewPackage)) - { - // Record bake counter / temp GUID in package metadata - UMetaData* MetaData = NewPackage->GetMetaData(); - if (IsValid(MetaData)) - { - if (PackageMode == EPackageMode::Bake) - { - // HOUDINI_LOG_MESSAGE(TEXT("Recording bake counter in package metadata: %d"), BakeCounter); - MetaData->RootMetaDataMap.Add( - HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER, FString::FromInt(BakeCounter)); - } - else if (CurrentGuid.IsValid()) - { - const FString GuidStr = CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); - // HOUDINI_LOG_MESSAGE(TEXT("Recording temp guid in package metadata: %s"), *GuidStr); - MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_TEMP_GUID, GuidStr); - } - } - } - - break; - } - - return NewPackage; -} - - -// Fixes link error with the template function under -void TemplateFixer() -{ - FHoudiniPackageParams PP; - UStaticMesh* SM = PP.CreateObjectAndPackage(); - UHoudiniStaticMesh* HSM = PP.CreateObjectAndPackage(); - //UMaterial* Mat = PP.CreateObjectAndPackage(); - //UTexture2D* Text = PP.CreateObjectAndPackage(); -} - -template -T* FHoudiniPackageParams::CreateObjectAndPackage() -{ - // Create the package for the object - FString NewObjectName; - UPackage* Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) - return nullptr; - - const FString SanitizedObjectName = ObjectTools::SanitizeObjectName(NewObjectName); - - T* ExistingTypedObject = FindObject(Package, *NewObjectName); - UObject* ExistingObject = FindObject(Package, *NewObjectName); - - if (ExistingTypedObject != nullptr && !ExistingTypedObject->IsPendingKill()) - { - // An object of the appropriate type already exists, update it! - ExistingTypedObject->PreEditChange(nullptr); - } - else if (ExistingObject != nullptr) - { - // Replacing an object of a different type, Delete it first. - const bool bDeleteSucceeded = ObjectTools::DeleteSingleObject(ExistingObject); - if (bDeleteSucceeded) - { - // Force GC so we can cleanly create a new asset (and not do an 'in place' replacement) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - // Create a package for each mesh - Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) - return nullptr; - } - else - { - // failed to delete - return nullptr; - } - } - - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); - - return NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPackageParams.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniStringResolver.h" + +#include "PackageTools.h" +#include "ObjectTools.h" +#include "Engine/StaticMesh.h" +#include "UObject/MetaData.h" + +// +FHoudiniPackageParams::FHoudiniPackageParams() +{ + PackageMode = EPackageMode::CookToTemp; + ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + OuterPackage = nullptr; + ObjectName = FString(); + HoudiniAssetName = FString(); + HoudiniAssetActorName = FString(); + + ObjectId = 0; + GeoId = 0; + PartId = 0; + SplitStr = 0; + + ComponentGUID.Invalidate(); + + PDGTOPNetworkName.Empty(); + PDGTOPNodeName.Empty(); + PDGWorkItemIndex = INDEX_NONE; + + bAttemptToLoadMissingPackages = false; +} + + +// +FHoudiniPackageParams::~FHoudiniPackageParams() +{ + + +} + + +// Returns the object flags corresponding to the current package mode +EObjectFlags +FHoudiniPackageParams::GetObjectFlags() const +{ + if (PackageMode == EPackageMode::CookToTemp) + return RF_Public | RF_Standalone; + else if (PackageMode == EPackageMode::Bake) + return RF_Public | RF_Standalone; + else + return RF_NoFlags; +} + +FString +FHoudiniPackageParams::GetPackageName() const +{ + if (!ObjectName.IsEmpty()) + return ObjectName; + + // If we have PDG infos, generate a name including them + if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkItemIndex >= 0) + { + return FString::Printf( + TEXT("%s_%s_%s_%d_%d_%s"), + *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkItemIndex, PartId, *SplitStr); + } + else + { + // Generate an object name using the HGPO IDs and the HDA name + return FString::Printf(TEXT("%s_%d_%d_%d_%s"), *HoudiniAssetName, ObjectId, GeoId, PartId, *SplitStr); + } +} + +FString +FHoudiniPackageParams::GetPackagePath() const +{ + FString PackagePath = FString(); + switch (PackageMode) + { + case EPackageMode::CookToLevel: + { + // Path to the persistent level + //PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()); + + // In this mode, we'll use the persistent level as our package's outer + // simply use the hda + component guid for the path + // Add a subdir for the HDA + if (!HoudiniAssetName.IsEmpty()) + PackagePath += TEXT("/") + HoudiniAssetName; + // Add a subdir using the owner component GUID if possible + if(ComponentGUID.IsValid()) + PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); + + // TODO: FIX ME!!! + // Old version + // Build the package name + PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()) + + TEXT("/") + + HoudiniAssetName; + } + break; + + case EPackageMode::CookToTemp: + { + // Temporary Folder + PackagePath = TempCookFolder; + // Add a subdir for the HDA + if (!HoudiniAssetName.IsEmpty()) + PackagePath += TEXT("/") + HoudiniAssetName; + // Add a subdir using the owner component GUID if possible + if (ComponentGUID.IsValid()) + PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); + } + break; + + case EPackageMode::Bake: + { + PackagePath = BakeFolder; + } + break; + } + + return PackagePath; +} + +bool +FHoudiniPackageParams::GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter) +{ + OutBakeCounter = 0; + + if (!IsValid(InAsset)) + return false; + + UPackage* Package = InAsset->GetPackage(); + // const FString PackagePathName = Package->GetPathName(); + // FString PackagePathNamePrefix; + // FString BakeCountOrGUID; + // if (!GetPackageNameWithoutBakeCounterOrGUIDSuffix(PackagePathName, PackagePathNamePrefix, BakeCountOrGUID)) + // PackagePathNamePrefix = PackagePathName; + // + // const FString ThisPackageNameBase = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); + // if (!PackagePathNamePrefix.Equals(ThisPackageNameBase)) + // return false; + // + // // Not a valid counter suffix, could be a GUID suffix. Return true since the prefixes match. + // if (BakeCountOrGUID.IsNumeric()) + // OutBakeCounter = FCString::Atoi(*BakeCountOrGUID); + // + // return true; + + if (!IsValid(Package)) + return false; + + UMetaData* MetaData = Package->GetMetaData(); + if (!IsValid(MetaData)) + return false; + + if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER)) + { + FString BakeCounterStr = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER); + BakeCounterStr.TrimStartAndEndInline(); + if (BakeCounterStr.IsNumeric()) + { + OutBakeCounter = FCString::Atoi(*BakeCounterStr); + return true; + } + } + + return false; +} + +bool +FHoudiniPackageParams::GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID) +{ + if (!InAsset) + return false; + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return false; + + UMetaData* MetaData = Package->GetMetaData(); + if (!IsValid(MetaData)) + return false; + + if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) + { + OutGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); + OutGUID.TrimStartAndEndInline(); + if (!OutGUID.IsEmpty()) + return true; + } + + return false; +} + +FString +FHoudiniPackageParams::GetPackageNameExcludingBakeCounter(const UObject* InAsset) +{ + if (!IsValid(InAsset)) + return FString(); + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return FString(); + + FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); + int32 BakeCounter = 0; + if (GetBakeCounterFromBakedAsset(InAsset, BakeCounter)) + { + const FString BakeCounterSuffix = FString::Printf(TEXT("_%d"), BakeCounter); + if (PackageName.EndsWith(BakeCounterSuffix)) + PackageName = PackageName.Mid(0, PackageName.Len() - BakeCounterSuffix.Len()); + } + + return PackageName; +} + +bool +FHoudiniPackageParams::MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const +{ + if (!IsValid(InAsset)) + return false; + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return false; + + const FString InAssetPackagePathName = FPaths::GetPath(Package->GetPathName()) + TEXT("/") + GetPackageNameExcludingBakeCounter(InAsset); + const FString ThisPackagePathName = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); + return InAssetPackagePathName.Equals(ThisPackagePathName); +} + +FString +FHoudiniPackageParams::GetPackageNameExcludingGUID(const UObject* InAsset) +{ + if (!IsValid(InAsset)) + return FString(); + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return FString(); + + FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); + FString GUIDStr; + if (GetGUIDFromTempAsset(InAsset, GUIDStr)) + { + if (PackageName.EndsWith(TEXT("_") + GUIDStr)) + PackageName = PackageName.Mid(0, PackageName.Len() - GUIDStr.Len() - 1); + } + + return PackageName; +} + +UPackage* +FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart) const +{ + // GUID/counter used to differentiate with existing package + int32 BakeCounter = InBakeCounterStart; + FGuid CurrentGuid = FGuid::NewGuid(); + + // Get the appropriate package path/name for this object + FString PackageName = GetPackageName(); + FString PackagePath = GetPackagePath(); + + // Iterate until we find a suitable name for the package + UPackage * NewPackage = nullptr; + while (true) + { + OutPackageName = PackageName; + + // Append the Bake guid/counter to the object name if needed + if (BakeCounter > 0) + { + OutPackageName += (PackageMode == EPackageMode::Bake) + ? TEXT("_") + FString::FromInt(BakeCounter) + : TEXT("_") + CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); + } + + // Build the final package name + FString FinalPackageName = PackagePath + TEXT("/") + OutPackageName; + // Sanitize package name. + FinalPackageName = UPackageTools::SanitizePackageName(FinalPackageName); + + UObject * PackageOuter = nullptr; + /* + // As of UE4.26, it is not possible anymore to create package with a non null outer + // CookToLevel is, anyway, no logner supported in v2. + if (PackageMode == EPackageMode::CookToLevel) + { + // If we are not baking, then use outermost package, since objects within our package + // need to be visible to external operations, such as copy paste. + PackageOuter = OuterPackage; + } + */ + + // See if a package named similarly already exists + UPackage* FoundPackage = FindPackage(PackageOuter, *FinalPackageName); + if (FoundPackage == nullptr && bAttemptToLoadMissingPackages) + { + FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_NoWarn); + } + if (ReplaceMode == EPackageReplaceMode::CreateNewAssets + && FoundPackage && !FoundPackage->IsPendingKill()) + { + // we need to generate a new name for it + CurrentGuid = FGuid::NewGuid(); + BakeCounter++; + continue; + } + + // Create actual package. + NewPackage = CreatePackage(*FinalPackageName); + if (IsValid(NewPackage)) + { + // Record bake counter / temp GUID in package metadata + UMetaData* MetaData = NewPackage->GetMetaData(); + if (IsValid(MetaData)) + { + if (PackageMode == EPackageMode::Bake) + { + // HOUDINI_LOG_MESSAGE(TEXT("Recording bake counter in package metadata: %d"), BakeCounter); + MetaData->RootMetaDataMap.Add( + HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER, FString::FromInt(BakeCounter)); + } + else if (CurrentGuid.IsValid()) + { + const FString GuidStr = CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); + // HOUDINI_LOG_MESSAGE(TEXT("Recording temp guid in package metadata: %s"), *GuidStr); + MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_TEMP_GUID, GuidStr); + } + } + } + + break; + } + + return NewPackage; +} + + +// Fixes link error with the template function under +void TemplateFixer() +{ + FHoudiniPackageParams PP; + UStaticMesh* SM = PP.CreateObjectAndPackage(); + UHoudiniStaticMesh* HSM = PP.CreateObjectAndPackage(); + //UMaterial* Mat = PP.CreateObjectAndPackage(); + //UTexture2D* Text = PP.CreateObjectAndPackage(); +} + +template +T* FHoudiniPackageParams::CreateObjectAndPackage() +{ + // Create the package for the object + FString NewObjectName; + UPackage* Package = CreatePackageForObject(NewObjectName); + if (!Package || Package->IsPendingKill()) + return nullptr; + + const FString SanitizedObjectName = ObjectTools::SanitizeObjectName(NewObjectName); + + T* ExistingTypedObject = FindObject(Package, *NewObjectName); + UObject* ExistingObject = FindObject(Package, *NewObjectName); + + if (ExistingTypedObject != nullptr && !ExistingTypedObject->IsPendingKill()) + { + // An object of the appropriate type already exists, update it! + ExistingTypedObject->PreEditChange(nullptr); + } + else if (ExistingObject != nullptr) + { + // Replacing an object of a different type, Delete it first. + const bool bDeleteSucceeded = ObjectTools::DeleteSingleObject(ExistingObject); + if (bDeleteSucceeded) + { + // Force GC so we can cleanly create a new asset (and not do an 'in place' replacement) + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Create a package for each mesh + Package = CreatePackageForObject(NewObjectName); + if (!Package || Package->IsPendingKill()) + return nullptr; + } + else + { + // failed to delete + return nullptr; + } + } + + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); + + return NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); +} + diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.h b/Source/HoudiniEngine/Private/HoudiniPackageParams.h index 8580b3376..ac963bfb7 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.h +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.h @@ -1,239 +1,239 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/ObjectMacros.h" -#include "Engine/World.h" -#include "Misc/Paths.h" - -#include "HoudiniStringResolver.h" - -#include "HoudiniPackageParams.generated.h" - -class UStaticMesh; - -UENUM() -enum class EPackageMode : int8 -{ - CookToLevel, - CookToTemp, - Bake -}; - -UENUM() -enum class EPackageReplaceMode : int8 -{ - CreateNewAssets, - ReplaceExistingAssets -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPackageParams -{ -public: - GENERATED_BODY(); - - // - FHoudiniPackageParams(); - // - ~FHoudiniPackageParams(); - - // Helper functions returning the default behavior expected when cooking mesh - static EPackageMode GetDefaultStaticMeshesCookMode() { return EPackageMode::CookToTemp; }; - // Helper functions returning the default behavior expected when cooking materials or textures - static EPackageMode GetDefaultMaterialAndTextureCookMode() { return EPackageMode::CookToTemp; }; - // Helper functions returning the default behavior for replacing existing package - static EPackageReplaceMode GetDefaultReplaceMode() { return EPackageReplaceMode::ReplaceExistingAssets; }; - - // Returns the name for the package depending on the mode - FString GetPackageName() const; - // Returns the package's path depending on the mode - FString GetPackagePath() const; - // Returns the object flags corresponding to the current package mode - EObjectFlags GetObjectFlags() const; - - // Get the bake counter for InAsset's package metadata. Return true if the counter was found, false otherwise. - static bool GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter); - - // Get the GUID for a temp asset. - static bool GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID); - - // Get package name without bake counter - static FString GetPackageNameExcludingBakeCounter(const UObject* InAsset); - - // Get package name without temp GUID suffix - static FString GetPackageNameExcludingGUID(const UObject* InAsset); - - // Returns true if these package params generate the same package path and name as InAsset's package path name (with - // any potential bake counters stripped during comparison) - bool MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const; - - // Helper function to create a Package for a given object - UPackage* CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart=0) const; - - // Helper function to create an object and its package - template T* CreateObjectAndPackage(); - - - // The current cook/baking mode - UPROPERTY() - EPackageMode PackageMode; - // How to handle existing assets? replace or rename? - UPROPERTY() - EPackageReplaceMode ReplaceMode; - - // When cooking in bake mode - folder to create assets in - UPROPERTY() - FString BakeFolder; - // When cooking in temp mode - folder to create assets in - UPROPERTY() - FString TempCookFolder; - - // Package to save to - UPROPERTY() - UObject* OuterPackage; - - // Name of the package we want to create - // If null, we'll generate one from: - // (without PDG) ASSET_OBJ_GEO_PART_SPLIT, - // (with PDG) ASSET_TOPNET_TOPNODE_WORKITEMINDEX_PART_SPLIT - UPROPERTY() - FString ObjectName; - - // Name of the HDA - UPROPERTY() - FString HoudiniAssetName; - - // Name of actor that is managing an instance of the HDA - UPROPERTY() - FString HoudiniAssetActorName; - - // - UPROPERTY() - int32 ObjectId; - // - UPROPERTY() - int32 GeoId; - // - UPROPERTY() - int32 PartId; - // - UPROPERTY() - FString SplitStr; - - // GUID used for the owner - UPROPERTY() - FGuid ComponentGUID; - - // For PDG temporary outputs: the TOP network name - UPROPERTY() - FString PDGTOPNetworkName; - // For PDG temporary outputs: the TOP node name - UPROPERTY() - FString PDGTOPNodeName; - // For PDG temporary outputs: the work item index of the TOP node - UPROPERTY() - int32 PDGWorkItemIndex; - - // If FindPackage returns null, if this flag is true then a LoadPackage will also be attempted - // This is for use cases, such as commandlets, that might unload packages once done with them, but that must - // reliably be able to determine if a package exists later - UPROPERTY() - bool bAttemptToLoadMissingPackages; - - ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. - //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; - //FString GetBakeFolderArgument(ERuntimePackageMode PackageMode) const; - - //// Return the output path as either the temp or bake path, depending on the package mode. - //FString GetOutputFolderForPackageMode(ERuntimePackageMode PackageMode) const; - - /* - * Build a "standard" set of string formatting arguments that - * is typically used across HoudiniEngine path naming outputs. - * Note that each output type may contain additional named arguments - * that are not listed here. - * {out} - The output directory (varies depending on the package mode). - * {pkg} - The path to the destination package (varies depending on the package mode). - * {world} - Path the directory that contains the world. - * {hda_name} - Name of the HDA - * {guid} - guid of the HDA component - * @param PackageParams The output path for the current build mode (Temp / Bake). - * @param HACWorld The world in which the HDA component lives (typically Editor world). - * @param OutArgs The generated named arguments to be used for string formatting. - */ - - // Populate a map of named arguments from this FHoudiniPackageParams. - template - void UpdateTokensFromParams( - const UWorld* WorldContext, - TMap& OutTokens) const - { - UpdateOutputPathTokens(PackageMode, OutTokens); - - OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); - OutTokens.Add("object_name", ValueT( ObjectName )); - OutTokens.Add("object_id", ValueT( FString::FromInt(ObjectId) )); - OutTokens.Add("geo_id", ValueT( FString::FromInt(GeoId) )); - OutTokens.Add("part_id", ValueT( FString::FromInt(PartId) )); - OutTokens.Add("split_str", ValueT( SplitStr)); - OutTokens.Add("hda_name", ValueT( HoudiniAssetName )); - OutTokens.Add("hda_actor_name", ValueT( HoudiniAssetActorName )); - OutTokens.Add("pdg_topnet_name", ValueT( PDGTOPNetworkName )); - OutTokens.Add("pdg_topnode_name", ValueT( PDGTOPNodeName )); - OutTokens.Add("pdg_workitem_index", ValueT( FString::FromInt(PDGWorkItemIndex) )); - OutTokens.Add("guid", ValueT( ComponentGUID.ToString() )); - } - - template - void UpdateOutputPathTokens(EPackageMode InPackageMode, TMap& OutTokens) const - { - const FString PackagePath = GetPackagePath(); - - OutTokens.Add("temp", ValueT(FPaths::GetPath(TempCookFolder))); - OutTokens.Add("bake", ValueT(FPaths::GetPath(BakeFolder))); - - // `out_basepath` is useful if users want to organize their cook/bake assets - // different to the convention defined by GetPackagePath(). This would typically - // be combined with `unreal_level_path` during level path resolves. - switch (InPackageMode) - { - case EPackageMode::CookToTemp: - case EPackageMode::CookToLevel: - OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(TempCookFolder))); - break; - case EPackageMode::Bake: - OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(BakeFolder))); - break; - } - - OutTokens.Add("out", ValueT( FPaths::GetPath(PackagePath) )); - } - -}; - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/ObjectMacros.h" +#include "Engine/World.h" +#include "Misc/Paths.h" + +#include "HoudiniStringResolver.h" + +#include "HoudiniPackageParams.generated.h" + +class UStaticMesh; + +UENUM() +enum class EPackageMode : int8 +{ + CookToLevel, + CookToTemp, + Bake +}; + +UENUM() +enum class EPackageReplaceMode : int8 +{ + CreateNewAssets, + ReplaceExistingAssets +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPackageParams +{ +public: + GENERATED_BODY(); + + // + FHoudiniPackageParams(); + // + ~FHoudiniPackageParams(); + + // Helper functions returning the default behavior expected when cooking mesh + static EPackageMode GetDefaultStaticMeshesCookMode() { return EPackageMode::CookToTemp; }; + // Helper functions returning the default behavior expected when cooking materials or textures + static EPackageMode GetDefaultMaterialAndTextureCookMode() { return EPackageMode::CookToTemp; }; + // Helper functions returning the default behavior for replacing existing package + static EPackageReplaceMode GetDefaultReplaceMode() { return EPackageReplaceMode::ReplaceExistingAssets; }; + + // Returns the name for the package depending on the mode + FString GetPackageName() const; + // Returns the package's path depending on the mode + FString GetPackagePath() const; + // Returns the object flags corresponding to the current package mode + EObjectFlags GetObjectFlags() const; + + // Get the bake counter for InAsset's package metadata. Return true if the counter was found, false otherwise. + static bool GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter); + + // Get the GUID for a temp asset. + static bool GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID); + + // Get package name without bake counter + static FString GetPackageNameExcludingBakeCounter(const UObject* InAsset); + + // Get package name without temp GUID suffix + static FString GetPackageNameExcludingGUID(const UObject* InAsset); + + // Returns true if these package params generate the same package path and name as InAsset's package path name (with + // any potential bake counters stripped during comparison) + bool MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const; + + // Helper function to create a Package for a given object + UPackage* CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart=0) const; + + // Helper function to create an object and its package + template T* CreateObjectAndPackage(); + + + // The current cook/baking mode + UPROPERTY() + EPackageMode PackageMode; + // How to handle existing assets? replace or rename? + UPROPERTY() + EPackageReplaceMode ReplaceMode; + + // When cooking in bake mode - folder to create assets in + UPROPERTY() + FString BakeFolder; + // When cooking in temp mode - folder to create assets in + UPROPERTY() + FString TempCookFolder; + + // Package to save to + UPROPERTY() + UObject* OuterPackage; + + // Name of the package we want to create + // If null, we'll generate one from: + // (without PDG) ASSET_OBJ_GEO_PART_SPLIT, + // (with PDG) ASSET_TOPNET_TOPNODE_WORKITEMINDEX_PART_SPLIT + UPROPERTY() + FString ObjectName; + + // Name of the HDA + UPROPERTY() + FString HoudiniAssetName; + + // Name of actor that is managing an instance of the HDA + UPROPERTY() + FString HoudiniAssetActorName; + + // + UPROPERTY() + int32 ObjectId; + // + UPROPERTY() + int32 GeoId; + // + UPROPERTY() + int32 PartId; + // + UPROPERTY() + FString SplitStr; + + // GUID used for the owner + UPROPERTY() + FGuid ComponentGUID; + + // For PDG temporary outputs: the TOP network name + UPROPERTY() + FString PDGTOPNetworkName; + // For PDG temporary outputs: the TOP node name + UPROPERTY() + FString PDGTOPNodeName; + // For PDG temporary outputs: the work item index of the TOP node + UPROPERTY() + int32 PDGWorkItemIndex; + + // If FindPackage returns null, if this flag is true then a LoadPackage will also be attempted + // This is for use cases, such as commandlets, that might unload packages once done with them, but that must + // reliably be able to determine if a package exists later + UPROPERTY() + bool bAttemptToLoadMissingPackages; + + ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. + //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; + //FString GetBakeFolderArgument(ERuntimePackageMode PackageMode) const; + + //// Return the output path as either the temp or bake path, depending on the package mode. + //FString GetOutputFolderForPackageMode(ERuntimePackageMode PackageMode) const; + + /* + * Build a "standard" set of string formatting arguments that + * is typically used across HoudiniEngine path naming outputs. + * Note that each output type may contain additional named arguments + * that are not listed here. + * {out} - The output directory (varies depending on the package mode). + * {pkg} - The path to the destination package (varies depending on the package mode). + * {world} - Path the directory that contains the world. + * {hda_name} - Name of the HDA + * {guid} - guid of the HDA component + * @param PackageParams The output path for the current build mode (Temp / Bake). + * @param HACWorld The world in which the HDA component lives (typically Editor world). + * @param OutArgs The generated named arguments to be used for string formatting. + */ + + // Populate a map of named arguments from this FHoudiniPackageParams. + template + void UpdateTokensFromParams( + const UWorld* WorldContext, + TMap& OutTokens) const + { + UpdateOutputPathTokens(PackageMode, OutTokens); + + OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); + OutTokens.Add("object_name", ValueT( ObjectName )); + OutTokens.Add("object_id", ValueT( FString::FromInt(ObjectId) )); + OutTokens.Add("geo_id", ValueT( FString::FromInt(GeoId) )); + OutTokens.Add("part_id", ValueT( FString::FromInt(PartId) )); + OutTokens.Add("split_str", ValueT( SplitStr)); + OutTokens.Add("hda_name", ValueT( HoudiniAssetName )); + OutTokens.Add("hda_actor_name", ValueT( HoudiniAssetActorName )); + OutTokens.Add("pdg_topnet_name", ValueT( PDGTOPNetworkName )); + OutTokens.Add("pdg_topnode_name", ValueT( PDGTOPNodeName )); + OutTokens.Add("pdg_workitem_index", ValueT( FString::FromInt(PDGWorkItemIndex) )); + OutTokens.Add("guid", ValueT( ComponentGUID.ToString() )); + } + + template + void UpdateOutputPathTokens(EPackageMode InPackageMode, TMap& OutTokens) const + { + const FString PackagePath = GetPackagePath(); + + OutTokens.Add("temp", ValueT(FPaths::GetPath(TempCookFolder))); + OutTokens.Add("bake", ValueT(FPaths::GetPath(BakeFolder))); + + // `out_basepath` is useful if users want to organize their cook/bake assets + // different to the convention defined by GetPackagePath(). This would typically + // be combined with `unreal_level_path` during level path resolves. + switch (InPackageMode) + { + case EPackageMode::CookToTemp: + case EPackageMode::CookToLevel: + OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(TempCookFolder))); + break; + case EPackageMode::Bake: + OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(BakeFolder))); + break; + } + + OutTokens.Add("out", ValueT( FPaths::GetPath(PackagePath) )); + } + +}; + + diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp index d01233694..60f636cc7 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp @@ -1,2939 +1,2933 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniInput.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniParameter.h" -#include "HoudiniAssetComponent.h" - - -// Used parameter tags -#define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" -#define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" -#define HAPI_PARAM_TAG_UNITS "units" -#define HAPI_PARAM_TAG_ASSET_REF "asset_ref" - -// Default values for certain UI min and max parameter values -#define HAPI_UNREAL_PARAM_INT_UI_MIN 0 -#define HAPI_UNREAL_PARAM_INT_UI_MAX 10 -#define HAPI_UNREAL_PARAM_FLOAT_UI_MIN 0.0f -#define HAPI_UNREAL_PARAM_FLOAT_UI_MAX 10.0f - -// Some default parameter name -#define HAPI_UNREAL_PARAM_TRANSLATE "t" -#define HAPI_UNREAL_PARAM_ROTATE "r" -#define HAPI_UNREAL_PARAM_SCALE "s" -#define HAPI_UNREAL_PARAM_PIVOT "p" -#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" - -// -bool -FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // When recooking/rebuilding the HDA, force a full update of all params - bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); - - TArray NewParameters; - if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, true, bForceFullUpdate)) - { - /* - // DO NOT MANUALLY DESTROY THE OLD/DANGLING PARAMETERS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - - // Destroy old/dangling parameters - for (auto& OldParm : HAC->Parameters) - { - if (!OldParm || OldParm->IsPendingKill()) - continue; - - OldParm->ConditionalBeginDestroy(); - OldParm = nullptr; - } - */ - - // Replace with the new parameters - HAC->Parameters = NewParameters; - } - - - return true; -} - -bool -FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) -{ - // Call OnPreCook for all parameters. - // Parameters can use this to ensure that any cached / non-cooking state is properly - // synced before the cook starts (Looking at you, ramp parameters!) - for (UHoudiniParameter* Param : HAC->Parameters) - { - if (!Param || Param->IsPendingKill()) - continue; - - Param->OnPreCook(); - } - - return true; -} - -// -bool -FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Update all the parameters using the loaded parameter object - // We set "UpdateValues" to false because we do not want to "read" the parameter value from Houdini - // but keep the loaded value - - // This is the first cook on loading after a save or duplication, - // We need to sync the Ramp parameters first, so that their child parameters can be kept - // TODO: Simplify this, should be handled in BuildAllParameters, - for (int32 Idx = 0; Idx < HAC->Parameters.Num(); ++Idx) - { - UHoudiniParameter* Param = HAC->Parameters[Idx]; - - if (!Param || Param->IsPendingKill()) - continue; - - switch(Param->GetParameterType()) - { - case EHoudiniParameterType::ColorRamp: - case EHoudiniParameterType::FloatRamp: - case EHoudiniParameterType::MultiParm: - { - SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, Idx); - } - break; - - default: - break; - } - } - - // When recooking/rebuilding the HDA, force a full update of all params - bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); - - // This call to BuildAllParameters will keep all the loaded parameters (in the HAC's Parameters array) - // that are still present in the HDA, and keep their loaded value. - TArray NewParameters; - if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, false, bForceFullUpdate)) - { - /* - // DO NOT DESTROY OLD PARAMS MANUALLY HERE - // This causes crashes upon duplication due to uncollected zombie objects... - // GC is supposed to handle this by itself - // Destroy old/dangling parameters - for (auto& OldParm : HAC->Parameters) - { - if (!OldParm || OldParm->IsPendingKill()) - continue; - - OldParm->ConditionalBeginDestroy(); - OldParm = nullptr; - } - */ - - // Simply replace with the new parameters - HAC->Parameters = NewParameters; - } - - return true; -} - -bool -FHoudiniParameterTranslator::BuildAllParameters( - const HAPI_NodeId& AssetId, - class UObject* Outer, - TArray& CurrentParameters, - TArray& NewParameters, - const bool& bUpdateValues, - const bool& InForceFullUpdate) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); - - NewParameters.Empty(); - if (NodeInfo.parmCount == 0) - { - // The asset doesnt have any parameter, we're done. - return true; - } - else if (NodeInfo.parmCount < 0) - { - // Invalid parm count - return false; - } - - TArray AllMultiParams; - - // Retrieve all the parameter infos. - TArray< HAPI_ParmInfo > ParmInfos; - ParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); - - // Create a name lookup cache for the current parameters - TMap CurrentParametersByName; - CurrentParametersByName.Reserve(CurrentParameters.Num()); - for (auto& Parm : CurrentParameters) - { - if (!Parm) - continue; - CurrentParametersByName.Add(Parm->GetParameterName(), Parm); - } - - // Create properties for parameters. - TArray NewParmIds; - for (int32 ParamIdx = 0; ParamIdx < NodeInfo.parmCount; ++ParamIdx) - { - - // Retrieve param info at this index. - const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; - - // If the parameter is corrupt, skip it - if (ParmInfo.id < 0 || ParmInfo.childIndex < 0) - { - HOUDINI_LOG_WARNING(TEXT("Corrupt parameter %d detected, skipping."), ParamIdx); - continue; - } - - // If the parameter is invisible, skip it. - //if (ParmInfo.invisible) - // continue; - - // Check if any parent folder of this parameter is invisible - bool SkipParm = false; - HAPI_ParmId ParentId = ParmInfo.parentId; - while (ParentId > 0 && !SkipParm) - { - if (const HAPI_ParmInfo* ParentInfoPtr = ParmInfos.FindByPredicate([=](const HAPI_ParmInfo& Info) { - return Info.id == ParentId; - })) - { - if (ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER) - SkipParm = true; - ParentId = ParentInfoPtr->parentId; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not find parent of parameter %d"), ParmInfo.id); - SkipParm = true; - } - } - - if (SkipParm) - continue; - - // See if this parameter has already been created. - // We can't use the HAPI_ParmId because it is not unique to parameter instances, - // so instead, try to find the existing parameter by name using the lookup table - FString NewParmName; - FHoudiniEngineString(ParmInfo.nameSH).ToFString(NewParmName); - - EHoudiniParameterType ParmType = EHoudiniParameterType::Invalid; - FHoudiniParameterTranslator::GetParmTypeFromParmInfo(ParmInfo, ParmType); - - UHoudiniParameter ** FoundHoudiniParameter = CurrentParametersByName.Find(NewParmName); - - // If that parameter exists, we might be able to simply reuse it. - bool IsFoundParameterValid = false; - if (FoundHoudiniParameter && *FoundHoudiniParameter && !(*FoundHoudiniParameter)->IsPendingKill()) - { - // First, we can simply check that the tuple size hasn't changed - if ((*FoundHoudiniParameter)->GetTupleSize() != ParmInfo.size) - { - IsFoundParameterValid = false; - } - else if (ParmType == EHoudiniParameterType::Invalid ) - { - IsFoundParameterValid = false; - } - else if (ParmType != (*FoundHoudiniParameter)->GetParameterType() ) - { - // Types do not match - IsFoundParameterValid = false; - } - else if ( !CheckParameterTypeAndClassMatch( *FoundHoudiniParameter, ParmType) ) - { - // Found parameter class does not match - IsFoundParameterValid = false; - } - else - { - // We can reuse the parameter - IsFoundParameterValid = true; - } - } - - UHoudiniParameter * HoudiniAssetParameter = nullptr; - - if (IsFoundParameterValid) - { - // We can reuse the parameter we found - HoudiniAssetParameter = *FoundHoudiniParameter; - - // Transfer param object from current map to new map - CurrentParameters.Remove(HoudiniAssetParameter); - CurrentParametersByName.Remove(NewParmName); - - // Do a fast update of this parameter - if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, InForceFullUpdate, bUpdateValues)) - continue; - - // Reset the states of ramp parameters. - switch (HoudiniAssetParameter->GetParameterType()) - { - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); - if (FloatRampParam) - { - UHoudiniAssetComponent* ParentHAC = Cast(FloatRampParam->GetOuter()); - if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) - FloatRampParam->bCaching = false; - } - - break; - } - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); - if (ColorRampParam) - { - UHoudiniAssetComponent* ParentHAC = Cast(ColorRampParam->GetOuter()); - if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) - ColorRampParam->bCaching = false; - } - - break; - } - } - - } - else - { - // Create a new parameter object of the appropriate type - HoudiniAssetParameter = CreateTypedParameter(Outer, ParmType, NewParmName); - // Fully update this parameter - if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, true, true)) - continue; - - } - - // Add the new parameters - NewParameters.Add(HoudiniAssetParameter); - NewParmIds.Add(ParmInfo.id); - - - // Check if the parameter is a direct child of a multiparam. - if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::MultiParm) - AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); - - if (AllMultiParams.Contains(HoudiniAssetParameter->GetParentParmId())) - { - HoudiniAssetParameter->SetIsDirectChildOfMultiParm(true); - - // Treat the folderlist whose direct parent is a multi param as a multi param too. - if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::FolderList) - AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); - } - - } - - // Assign folder type to all folderlists, - // if the first child of the folderlist is Tab or Radio button, set the bIsTabMenu of the folderlistParam to be true, otherwise false - for (int32 Idx = 0; Idx < NewParameters.Num(); ++Idx) - { - UHoudiniParameter * CurParam = NewParameters[Idx]; - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (CurParam->GetParameterType() == EHoudiniParameterType::FolderList) - { - UHoudiniParameterFolderList* CurFolderList = Cast(CurParam); - if (!CurFolderList || CurFolderList->IsPendingKill()) - continue; - - int32 FirstChildIdx = Idx + 1; - if (!NewParameters.IsValidIndex(FirstChildIdx)) - continue; - - UHoudiniParameterFolder* FirstChildFolder = Cast(NewParameters[FirstChildIdx]); - if (!FirstChildFolder || FirstChildFolder->IsPendingKill()) - continue; - - if (FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Radio || - FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Tabs) - { - // If this is the first time build - if (!CurFolderList->IsTabMenu()) - { - // Set the folderlist to be tabs - CurFolderList->SetIsTabMenu(true); - // Select the first child tab folder by default. - FirstChildFolder->SetChosen(true); - } - } - else - CurFolderList->SetIsTabMenu(false); - } - } - - FHoudiniEngineUtils::UpdateEditorProperties(Outer, true); - - return true; -} - - -void -FHoudiniParameterTranslator::GetParmTypeFromParmInfo( - const HAPI_ParmInfo& ParmInfo, - EHoudiniParameterType& ParmType) -{ - ParmType = EHoudiniParameterType::Invalid; - //ParmValueType = EHoudiniParameterValueType::Invalid; - - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_BUTTON: - ParmType = EHoudiniParameterType::Button; - //ParmValueType = EHoudiniParameterValueType::Int; - break; - - case HAPI_PARMTYPE_STRING: - { - if (ParmInfo.choiceCount > 0) - { - ParmType = EHoudiniParameterType::StringChoice; - //ParmValueType = EHoudiniParameterValueType::String; - } - else - { - ParmType = EHoudiniParameterType::String; - //ParmValueType = EHoudiniParameterValueType::String; - } - break; - } - - case HAPI_PARMTYPE_INT: - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_BUTTONSTRIP) - { - ParmType = EHoudiniParameterType::ButtonStrip; - break; - } - - if (ParmInfo.choiceCount > 0) - { - ParmType = EHoudiniParameterType::IntChoice; - //ParmValueType = EHoudiniParameterValueType::Int; - } - else - { - ParmType = EHoudiniParameterType::Int; - //ParmValueType = EHoudiniParameterValueType::Int; - } - break; - } - - case HAPI_PARMTYPE_FLOAT: - { - ParmType = EHoudiniParameterType::Float; - //ParmValueType = EHoudiniParameterValueType::Float; - break; - } - - case HAPI_PARMTYPE_TOGGLE: - { - ParmType = EHoudiniParameterType::Toggle; - //ParmValueType = EHoudiniParameterValueType::Int; - break; - } - - case HAPI_PARMTYPE_COLOR: - { - ParmType = EHoudiniParameterType::Color; - //ParmValueType = EHoudiniParameterValueType::Float; - break; - } - - case HAPI_PARMTYPE_LABEL: - { - ParmType = EHoudiniParameterType::Label; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_SEPARATOR: - { - ParmType = EHoudiniParameterType::Separator; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - case HAPI_PARMTYPE_FOLDERLIST: - { - ParmType = EHoudiniParameterType::FolderList; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - // Treat radio folders as tab folders for now - case HAPI_PARMTYPE_FOLDERLIST_RADIO: - { - ParmType = EHoudiniParameterType::FolderList; - break; - } - - case HAPI_PARMTYPE_FOLDER: - { - ParmType = EHoudiniParameterType::Folder; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - case HAPI_PARMTYPE_MULTIPARMLIST: - { - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType) - { - ParmType = EHoudiniParameterType::FloatRamp; - //ParmValueType = EHoudiniParameterValueType::Float; - } - else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - { - ParmType = EHoudiniParameterType::ColorRamp; - //ParmValueType = EHoudiniParameterValueType::Float; - } - else - { - ParmType = EHoudiniParameterType::MultiParm; - //ParmValueType = EHoudiniParameterValueType::Int; - } - break; - } - - case HAPI_PARMTYPE_PATH_FILE: - { - ParmType = EHoudiniParameterType::File; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_DIR: - { - ParmType = EHoudiniParameterType::FileDir; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_GEO: - { - ParmType = EHoudiniParameterType::FileGeo; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - { - ParmType = EHoudiniParameterType::FileImage; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_NODE: - { - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - ParmType = EHoudiniParameterType::Input; - } - else - { - ParmType = EHoudiniParameterType::String; - } - break; - } - - default: - { - // Just ignore unsupported types for now. - HOUDINI_LOG_WARNING(TEXT("Parameter Type (%d) is unsupported"), static_cast(ParmInfo.type)); - break; - } - } -} - -UClass* -FHoudiniParameterTranslator::GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo) -{ - UClass* FoundClass = nullptr; - - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_STRING: - if (!ParmInfo.choiceCount) - FoundClass = UHoudiniParameterString::StaticClass(); - else - FoundClass = UHoudiniParameterChoice ::StaticClass(); - break; - - case HAPI_PARMTYPE_INT: - if (!ParmInfo.choiceCount) - FoundClass = UHoudiniParameterInt::StaticClass(); - else - FoundClass = UHoudiniParameterChoice::StaticClass(); - break; - - case HAPI_PARMTYPE_FLOAT: - FoundClass = UHoudiniParameterFloat::StaticClass(); - break; - - case HAPI_PARMTYPE_TOGGLE: - FoundClass = UHoudiniParameterToggle::StaticClass(); - break; - - case HAPI_PARMTYPE_COLOR: - FoundClass = UHoudiniParameterColor::StaticClass(); - break; - - case HAPI_PARMTYPE_LABEL: - FoundClass = UHoudiniParameterLabel::StaticClass(); - break; - - case HAPI_PARMTYPE_BUTTON: - FoundClass = UHoudiniParameterButton::StaticClass(); - break; - - case HAPI_PARMTYPE_SEPARATOR: - FoundClass = UHoudiniParameterSeparator::StaticClass(); - break; - - case HAPI_PARMTYPE_FOLDERLIST: - FoundClass = UHoudiniParameterFolderList::StaticClass(); - break; - - case HAPI_PARMTYPE_FOLDER: - FoundClass = UHoudiniParameterFolder::StaticClass(); - break; - - case HAPI_PARMTYPE_MULTIPARMLIST: - { - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - FoundClass = UHoudiniParameterRampFloat::StaticClass(); - else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - FoundClass = UHoudiniParameterRampColor::StaticClass(); - } - break; - - case HAPI_PARMTYPE_PATH_FILE: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_DIR: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_GEO: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - - case HAPI_PARMTYPE_NODE: - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - FoundClass = UHoudiniParameter::StaticClass(); - } - else - { - FoundClass = UHoudiniParameterString::StaticClass(); - } - break; - } - - if (FoundClass == nullptr) - return UHoudiniParameter::StaticClass(); - - return FoundClass; -} - -bool -FHoudiniParameterTranslator::CheckParameterTypeAndClassMatch(UHoudiniParameter* Parameter, const EHoudiniParameterType& ParmType) -{ - UClass* FoundClass = Parameter->GetClass(); - bool FailedTypeCheck = true; - - switch (ParmType) - { - case EHoudiniParameterType::Invalid: - { - FailedTypeCheck = true; - break; - } - - case EHoudiniParameterType::Button: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterButton >(); - break; - } - - case EHoudiniParameterType::Color: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterColor >(); - break; - } - - case EHoudiniParameterType::ColorRamp: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampColor >(); - break; - } - case EHoudiniParameterType::FloatRamp: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampFloat >(); - break; - } - - case EHoudiniParameterType::File: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileDir: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileGeo: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileImage: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - - case EHoudiniParameterType::Float: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFloat >(); - break; - } - - case EHoudiniParameterType::Folder: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolder >(); - break; - } - - case EHoudiniParameterType::FolderList: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolderList >(); - break; - } - - case EHoudiniParameterType::Input: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterOperatorPath >(); - break; - } - - case EHoudiniParameterType::Int: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterInt >(); - break; - } - - case EHoudiniParameterType::IntChoice: - case EHoudiniParameterType::StringChoice: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); - break; - } - - case EHoudiniParameterType::Label: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterLabel >(); - break; - } - - case EHoudiniParameterType::MultiParm: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterMultiParm >(); - break; - } - - case EHoudiniParameterType::Separator: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterSeparator >(); - break; - } - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); - break; - } - - case EHoudiniParameterType::Toggle: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterToggle >(); - break; - } - }; - - return !FailedTypeCheck; -} -/* -bool -FHoudiniParameterTranslator::CheckParameterClassAndInfoMatch(UHoudiniParameter* Parameter, const HAPI_ParmInfo& ParmInfo) -{ - if (!Parameter || Parameter->IsPendingKill()) - return false; - - UClass* FoundClass = Parameter->GetClass(); - bool FailedTypeCheck = true; - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_STRING: - if (!ParmInfo.choiceCount) - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); - else - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); - break; - - case HAPI_PARMTYPE_INT: - if (!ParmInfo.choiceCount) - FailedTypeCheck &= !FoundClass->IsChildOf(); - else - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FLOAT: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_TOGGLE: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_COLOR: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_LABEL: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_BUTTON: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_SEPARATOR: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FOLDERLIST: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FOLDER: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_MULTIPARMLIST: - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - break; - - case HAPI_PARMTYPE_PATH_FILE: - case HAPI_PARMTYPE_PATH_FILE_DIR: - case HAPI_PARMTYPE_PATH_FILE_GEO: - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_NODE: - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - else - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - break; - } - - return FailedTypeCheck; -} -*/ - -UHoudiniParameter * -FHoudiniParameterTranslator::CreateTypedParameter(UObject * Outer, const EHoudiniParameterType& ParmType, const FString& ParmName) -{ - UHoudiniParameter* HoudiniParameter = nullptr; - // Create a parameter of the desired type - switch (ParmType) - { - case EHoudiniParameterType::Button: - HoudiniParameter = UHoudiniParameterButton::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::ButtonStrip: - HoudiniParameter = UHoudiniParameterButtonStrip::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Color: - HoudiniParameter = UHoudiniParameterColor::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::ColorRamp: - HoudiniParameter = UHoudiniParameterRampColor::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FloatRamp: - HoudiniParameter = UHoudiniParameterRampFloat::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::File: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FileDir: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileDir); - break; - - case EHoudiniParameterType::FileGeo: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileGeo); - break; - - case EHoudiniParameterType::FileImage: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileImage); - break; - - case EHoudiniParameterType::Float: - HoudiniParameter = UHoudiniParameterFloat::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Folder: - HoudiniParameter = UHoudiniParameterFolder::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FolderList: - HoudiniParameter = UHoudiniParameterFolderList::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Input: - // Input parameter simply use the base class as all the processingsince is handled by UHoudiniInput - HoudiniParameter = UHoudiniParameterOperatorPath::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(ParmType); - break; - - case EHoudiniParameterType::Int: - HoudiniParameter = UHoudiniParameterInt::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::IntChoice: - HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::IntChoice); - break; - - case EHoudiniParameterType::StringChoice: - HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::StringChoice); - break; - - case EHoudiniParameterType::Label: - HoudiniParameter = UHoudiniParameterLabel::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::MultiParm: - HoudiniParameter = UHoudiniParameterMultiParm::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Separator: - HoudiniParameter = UHoudiniParameterSeparator::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - HoudiniParameter = UHoudiniParameterString::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Toggle: - HoudiniParameter = UHoudiniParameterToggle::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Invalid: - // TODO handle invalid params - HoudiniParameter = UHoudiniParameter::Create(Outer, ParmName); - break; - } - - return HoudiniParameter; -} - -bool -FHoudiniParameterTranslator::UpdateParameterFromInfo( - UHoudiniParameter * HoudiniParameter, const HAPI_NodeId& InNodeId, const HAPI_ParmInfo& ParmInfo, - const bool& bFullUpdate, const bool& bUpdateValue) -{ - if (!HoudiniParameter || HoudiniParameter->IsPendingKill()) - return false; - - // Copy values from the ParmInfos - HoudiniParameter->SetNodeId(InNodeId); - HoudiniParameter->SetParmId(ParmInfo.id); - HoudiniParameter->SetParentParmId(ParmInfo.parentId); - - HoudiniParameter->SetChildIndex(ParmInfo.childIndex); - HoudiniParameter->SetTagCount(ParmInfo.tagCount); - HoudiniParameter->SetTupleSize(ParmInfo.size); - - HoudiniParameter->SetVisible(!ParmInfo.invisible); - HoudiniParameter->SetDisabled(ParmInfo.disabled); - HoudiniParameter->SetSpare(ParmInfo.spare); - HoudiniParameter->SetJoinNext(ParmInfo.joinNext); - - HoudiniParameter->SetTagCount(ParmInfo.tagCount); - HoudiniParameter->SetIsChildOfMultiParm(ParmInfo.isChildOfMultiParm); - - UHoudiniParameterMultiParm* MultiParm = Cast(HoudiniParameter); - if(MultiParm) - MultiParm->InstanceStartOffset = ParmInfo.instanceStartOffset; - - - - // Get the parameter type - EHoudiniParameterType ParmType = HoudiniParameter->GetParameterType(); - - // We need to set string values from the parmInfo - if (bFullUpdate) - { - FString Name; - { - // Name - FHoudiniEngineString HoudiniEngineStringName(ParmInfo.nameSH); - if (HoudiniEngineStringName.ToFString(Name)) - HoudiniParameter->SetParameterName(Name); - } - - { - // Label - FString Label; - FHoudiniEngineString HoudiniEngineStringLabel(ParmInfo.labelSH); - if (HoudiniEngineStringLabel.ToFString(Label)) - HoudiniParameter->SetParameterLabel(Label); - } - - { - // Help - FString Help; - FHoudiniEngineString HoudiniEngineStringHelp(ParmInfo.helpSH); - if (HoudiniEngineStringHelp.ToFString(Help)) - HoudiniParameter->SetParameterHelp(Help); - } - - if (ParmType == EHoudiniParameterType::String - || ParmType == EHoudiniParameterType::Int - || ParmType == EHoudiniParameterType::Float - || ParmType == EHoudiniParameterType::Toggle - || ParmType == EHoudiniParameterType::Color) - { - // See if the parm has an expression - int32 TupleIdx = ParmInfo.intValuesIndex; - bool bHasExpression = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ParmHasExpression( - FHoudiniEngine::Get().GetSession(), InNodeId, - TCHAR_TO_UTF8(*Name), TupleIdx, &bHasExpression)) - { - // ? - } - - FString ParmExprString = TEXT(""); - if (bHasExpression) - { - // Try to get the expression's value - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmExpression( - FHoudiniEngine::Get().GetSession(), InNodeId, - TCHAR_TO_UTF8(*Name), TupleIdx, &StringHandle)) - { - FHoudiniEngineString HoudiniEngineString(StringHandle); - HoudiniEngineString.ToFString(ParmExprString); - } - - // Check if we actually have an expression - // String parameters return true even if they do not have one - bHasExpression = ParmExprString.Len() > 0; - - } - - HoudiniParameter->SetHasExpression(bHasExpression); - HoudiniParameter->SetExpression(ParmExprString); - } - else - { - HoudiniParameter->SetHasExpression(false); - HoudiniParameter->SetExpression(FString()); - } - - // Get parameter tags. - int32 TagCount = HoudiniParameter->GetTagCount(); - for (int32 Idx = 0; Idx < TagCount; ++Idx) - { - HAPI_StringHandle TagNameSH; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagName( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmInfo.id, Idx, &TagNameSH)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); - continue; - } - - FString NameString = TEXT(""); - FHoudiniEngineString::ToFString(TagNameSH, NameString); - if (NameString.IsEmpty()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); - continue; - } - - HAPI_StringHandle TagValueSH; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagValue( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmInfo.id, TCHAR_TO_ANSI(*NameString), &TagValueSH)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag value: parmId: %d, tag: %s"), ParmInfo.id, *NameString); - } - - FString ValueString = TEXT(""); - FHoudiniEngineString::ToFString(TagValueSH, ValueString); - - HoudiniParameter->GetTags().Add(NameString, ValueString); - } - } - - // - // Update properties specific to parameter classes - switch (ParmType) - { - case EHoudiniParameterType::Button: - { - UHoudiniParameterButton* HoudiniParameterButton = Cast(HoudiniParameter); - if (HoudiniParameterButton && !HoudiniParameterButton->IsPendingKill()) - { - HoudiniParameterButton->SetValueIndex(ParmInfo.intValuesIndex); - } - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - UHoudiniParameterButtonStrip* HoudiniParameterButtonStrip = Cast(HoudiniParameter); - if (HoudiniParameterButtonStrip && !HoudiniParameterButtonStrip->IsPendingKill()) - { - HoudiniParameterButtonStrip->SetValueIndex(ParmInfo.intValuesIndex); - HoudiniParameterButtonStrip->Count = ParmInfo.choiceCount; - } - - if (bFullUpdate) - { - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNum(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - - HoudiniParameterButtonStrip->InitializeLabels(ParmInfo.choiceCount); - - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ButtonLabel = HoudiniParameterButtonStrip->GetStringLabelAt(ChoiceIdx); - if (ButtonLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ButtonLabel)) - return false; - } - } - - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterButtonStrip->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.choiceCount) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - } - break; - - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* HoudiniParameterColor = Cast(HoudiniParameter); - if (HoudiniParameterColor && !HoudiniParameterColor->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterColor->SetValueIndex(ParmInfo.floatValuesIndex); - - // Update the Parameter value if we want to - if (bUpdateValue) - { - // Get the actual value for this property. - FLinearColor Color = FLinearColor::White; - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - (float *)&Color.R, ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - - HoudiniParameterColor->SetColorValue(Color); - } - - if (bFullUpdate) - { - // Set the default value at parameter created. - HoudiniParameterColor->SetDefaultValue(); - } - } - } - break; - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* HoudiniParameterRampColor = Cast(HoudiniParameter); - if (HoudiniParameterRampColor && !HoudiniParameterRampColor->IsPendingKill()) - { - HoudiniParameterRampColor->SetInstanceCount(ParmInfo.instanceCount); - HoudiniParameterRampColor->MultiParmInstanceLength = ParmInfo.instanceLength; - } - } - break; - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* HoudiniParameterRampFloat = Cast(HoudiniParameter); - if (HoudiniParameterRampFloat && !HoudiniParameterRampFloat->IsPendingKill()) - { - HoudiniParameterRampFloat->SetInstanceCount(ParmInfo.instanceCount); - HoudiniParameterRampFloat->MultiParmInstanceLength = ParmInfo.instanceLength; - } - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - UHoudiniParameterFile* HoudiniParameterFile = Cast(HoudiniParameter); - if (HoudiniParameterFile && !HoudiniParameterFile->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFile->SetValueIndex(ParmInfo.stringValuesIndex); - - // Update the file filter and read only tag only for full updates - if (bFullUpdate) - { - // Check if we are read-only - bool bIsReadOnly = false; - FString FileChooserTag; - if (FHoudiniParameterTranslator::HapiGetParameterTagValue(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_FILE_READONLY, FileChooserTag)) - { - if (FileChooserTag.Equals(TEXT("read"), ESearchCase::IgnoreCase)) - bIsReadOnly = true; - } - HoudiniParameterFile->SetReadOnly(bIsReadOnly); - - // Update the file type using the typeInfo string. - if (ParmInfo.typeInfoSH > 0) - { - FString Filters; - FHoudiniEngineString HoudiniEngineString(ParmInfo.typeInfoSH); - if (HoudiniEngineString.ToFString(Filters)) - { - if (!Filters.IsEmpty()) - HoudiniParameterFile->SetFileFilters(Filters); - } - } - } - - if (bUpdateValue) - { - // Get the actual values for this property. - TArray< HAPI_StringHandle > StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), InNodeId, false, - &StringHandles[0], ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - - // Convert HAPI string handles to Unreal strings. - HoudiniParameterFile->SetNumberOfValues(ParmInfo.size); - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - - // Update the parameter value - HoudiniParameterFile->SetValueAt(ValueString, Idx); - } - } - - if (bFullUpdate) - { - HoudiniParameterFile->SetDefaultValues(); - } - } - } - break; - - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* HoudiniParameterFloat = Cast(HoudiniParameter); - if (HoudiniParameterFloat && !HoudiniParameterFloat->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFloat->SetValueIndex(ParmInfo.floatValuesIndex); - - if (bUpdateValue) - { - // Update the parameter's value - HoudiniParameterFloat->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterFloat->GetValuesPtr(), - ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - - if (bFullUpdate) - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_FLOAT_LOG) - { - HoudiniParameterFloat->SetIsLogarithmic(true); - } - - // set the default float values. - HoudiniParameterFloat->SetDefaultValues(); - - // Only update Unit, no swap, and Min/Max values when doing a full update - - // Get the parameter's unit from the "unit" tag - FString ParamUnit; - FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); - HoudiniParameterFloat->SetUnit(ParamUnit); - - // Get the parameter's no swap tag (hengine_noswap) - HoudiniParameterFloat->SetNoSwap(HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_NOSWAP)); - - // Set the min and max for this parameter - if (ParmInfo.hasMin) - { - HoudiniParameterFloat->SetHasMin(true); - HoudiniParameterFloat->SetMin(ParmInfo.min); - } - else - { - HoudiniParameterFloat->SetHasMin(false); - HoudiniParameterFloat->SetMin(TNumericLimits::Lowest()); - } - - if (ParmInfo.hasMax) - { - HoudiniParameterFloat->SetHasMax(true); - HoudiniParameterFloat->SetMax(ParmInfo.max); - } - else - { - HoudiniParameterFloat->SetHasMax(false); - HoudiniParameterFloat->SetMax(TNumericLimits::Max()); - } - - // Set min and max for UI for this property. - bool bUsesDefaultMin = false; - if (ParmInfo.hasUIMin) - { - HoudiniParameterFloat->SetHasUIMin(true); - HoudiniParameterFloat->SetUIMin(ParmInfo.UIMin); - } - else if (ParmInfo.hasMin) - { - // If it is not set, use supplied min. - HoudiniParameterFloat->SetUIMin(ParmInfo.min); - } - else - { - // Min value Houdini uses by default. - HoudiniParameterFloat->SetUIMin(HAPI_UNREAL_PARAM_FLOAT_UI_MIN); - bUsesDefaultMin = true; - } - - bool bUsesDefaultMax = false; - if (ParmInfo.hasUIMax) - { - HoudiniParameterFloat->SetHasUIMax(true); - HoudiniParameterFloat->SetUIMax(ParmInfo.UIMax); - } - else if (ParmInfo.hasMax) - { - // If it is not set, use supplied max. - HoudiniParameterFloat->SetUIMax(ParmInfo.max); - } - else - { - // Max value Houdini uses by default. - HoudiniParameterFloat->SetUIMax(HAPI_UNREAL_PARAM_FLOAT_UI_MAX); - bUsesDefaultMax = true; - } - - if (bUsesDefaultMin || bUsesDefaultMax) - { - // If we are using defaults, we can detect some most common parameter names and alter defaults. - FString LocalParameterName = HoudiniParameterFloat->GetParameterName(); - FHoudiniEngineString HoudiniEngineString(ParmInfo.nameSH); - HoudiniEngineString.ToFString(LocalParameterName); - - static const FString ParameterNameTranslate(TEXT(HAPI_UNREAL_PARAM_TRANSLATE)); - static const FString ParameterNameRotate(TEXT(HAPI_UNREAL_PARAM_ROTATE)); - static const FString ParameterNameScale(TEXT(HAPI_UNREAL_PARAM_SCALE)); - static const FString ParameterNamePivot(TEXT(HAPI_UNREAL_PARAM_PIVOT)); - - if (!LocalParameterName.IsEmpty()) - { - if (LocalParameterName.Equals(ParameterNameTranslate) - || LocalParameterName.Equals(ParameterNameScale) - || LocalParameterName.Equals(ParameterNamePivot)) - { - if (bUsesDefaultMin) - { - HoudiniParameterFloat->SetUIMin(-1.0f); - } - if (bUsesDefaultMax) - { - HoudiniParameterFloat->SetUIMax(1.0f); - } - } - else if (LocalParameterName.Equals(ParameterNameRotate)) - { - if (bUsesDefaultMin) - { - HoudiniParameterFloat->SetUIMin(0.0f); - } - if (bUsesDefaultMax) - { - HoudiniParameterFloat->SetUIMax(360.0f); - } - } - } - } - } - } - } - break; - - case EHoudiniParameterType::Folder: - { - UHoudiniParameterFolder* HoudiniParameterFolder = Cast(HoudiniParameter); - if (HoudiniParameterFolder && !HoudiniParameterFolder->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFolder->SetValueIndex(ParmInfo.intValuesIndex); - HoudiniParameterFolder->SetFolderType(GetFolderTypeFromParamInfo(&ParmInfo)); - } - } - break; - - case EHoudiniParameterType::FolderList: - { - UHoudiniParameterFolderList* HoudiniParameterFolderList = Cast(HoudiniParameter); - if (HoudiniParameterFolderList && !HoudiniParameterFolderList->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFolderList->SetValueIndex(ParmInfo.intValuesIndex); - } - } - break; - - case EHoudiniParameterType::Input: - { - // Inputs parameters are just stored, and handled separately by UHoudiniInputs - UHoudiniParameterOperatorPath* HoudiniParameterOperatorPath = Cast(HoudiniParameter); - if (HoudiniParameterOperatorPath && !HoudiniParameterOperatorPath->IsPendingKill()) - { - /* - // DO NOT CREATE A DUPLICATE INPUT HERE! - // Inputs are created by the input translator, and will be tied to this parameter there - UHoudiniInput * NewInput = NewObject< UHoudiniInput >( - HoudiniParameterOperatorPath, - UHoudiniInput::StaticClass()); - - UHoudiniAssetComponent *ParentHAC = Cast(HoudiniParameterOperatorPath->GetOuter()); - - if (!ParentHAC) - return false; - - if (!NewInput || NewInput->IsPendingKill()) - return false; - - // Set the nodeId - NewInput->SetAssetNodeId(ParentHAC->GetAssetId()); - NewInput->SetInputType(EHoudiniInputType::Geometry); - HoudiniParameterOperatorPath->HoudiniInputs.Add(NewInput); - */ - // Set the valueIndex - HoudiniParameterOperatorPath->SetValueIndex(ParmInfo.stringValuesIndex); - } - } - break; - - case EHoudiniParameterType::Int: - { - UHoudiniParameterInt* HoudiniParameterInt = Cast(HoudiniParameter); - if (HoudiniParameterInt && !HoudiniParameterInt->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterInt->SetValueIndex(ParmInfo.intValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - HoudiniParameterInt->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterInt->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - - if (bFullUpdate) - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_INT_LOG) - { - HoudiniParameterInt->SetIsLogarithmic(true); - } - - // Set the default int values at created - HoudiniParameterInt->SetDefaultValues(); - // Only update unit and Min/Max values for a full update - - // Get the parameter's unit from the "unit" tag - FString ParamUnit; - FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); - HoudiniParameterInt->SetUnit(ParamUnit); - - // Set the min and max for this parameter - if (ParmInfo.hasMin) - { - HoudiniParameterInt->SetHasMin(true); - HoudiniParameterInt->SetMin((int32)ParmInfo.min); - } - else - { - HoudiniParameterInt->SetHasMin(false); - HoudiniParameterInt->SetMin(TNumericLimits::Lowest()); - } - - if (ParmInfo.hasMax) - { - HoudiniParameterInt->SetHasMax(true); - HoudiniParameterInt->SetMax((int32)ParmInfo.max); - } - else - { - HoudiniParameterInt->SetHasMax(false); - HoudiniParameterInt->SetMax(TNumericLimits::Max()); - } - - // Set min and max for UI for this property. - if (ParmInfo.hasUIMin) - { - HoudiniParameterInt->SetHasUIMin(true); - HoudiniParameterInt->SetUIMin((int32)ParmInfo.UIMin); - } - else if (ParmInfo.hasMin) - { - // If it is not set, use supplied min. - HoudiniParameterInt->SetUIMin((int32)ParmInfo.min); - } - else - { - // Min value Houdini uses by default. - HoudiniParameterInt->SetUIMin(HAPI_UNREAL_PARAM_INT_UI_MIN); - } - - if (ParmInfo.hasUIMax) - { - HoudiniParameterInt->SetHasUIMax(true); - HoudiniParameterInt->SetUIMax((int32)ParmInfo.UIMax); - } - else if (ParmInfo.hasMax) - { - // If it is not set, use supplied max. - HoudiniParameterInt->SetUIMax((int32)ParmInfo.max); - } - else - { - // Max value Houdini uses by default. - HoudiniParameterInt->SetUIMax(HAPI_UNREAL_PARAM_INT_UI_MAX); - } - } - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* HoudiniParameterIntChoice = Cast(HoudiniParameter); - if (HoudiniParameterIntChoice && !HoudiniParameterIntChoice->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterIntChoice->SetValueIndex(ParmInfo.intValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - int32 CurrentIntValue = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &CurrentIntValue, - ParmInfo.intValuesIndex, ParmInfo.size), false); - - // Check the value is valid - if (CurrentIntValue >= ParmInfo.choiceCount) - { - HOUDINI_LOG_WARNING(TEXT("parm '%s' has an invalid value %d, menu tokens are not supported for choice menus"), - *HoudiniParameterIntChoice->GetParameterName(), CurrentIntValue); - CurrentIntValue = 0; - } - - HoudiniParameterIntChoice->SetIntValue(CurrentIntValue); - } - - // Get the choice descriptors - if (bFullUpdate) - { - // Set the default value at created - HoudiniParameterIntChoice->SetDefaultIntValue(); - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - - // Set the array sizes - HoudiniParameterIntChoice->SetNumChoices(ParmInfo.choiceCount); - - bool bMatchedSelectionLabel = false; - int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValue(); - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ChoiceLabel = HoudiniParameterIntChoice->GetStringChoiceLabelAt(ChoiceIdx); - if (ChoiceLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ChoiceLabel)) - return false; - //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); - } - - // Match our string value to the corresponding selection label. - if (ChoiceIdx == CurrentIntValue) - { - HoudiniParameterIntChoice->SetStringValue(*ChoiceLabel); - } - } - } - else if (bUpdateValue) - { - // We still need to match the string value to the label - HoudiniParameterIntChoice->UpdateStringValueFromInt(); - } - } - } - break; - - case EHoudiniParameterType::StringChoice: - { - UHoudiniParameterChoice* HoudiniParameterStringChoice = Cast(HoudiniParameter); - if (HoudiniParameterStringChoice && !HoudiniParameterStringChoice->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterStringChoice->SetValueIndex(ParmInfo.stringValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandle, - ParmInfo.stringValuesIndex, ParmInfo.size), false); - - // Get the string value - FString StringValue; - FHoudiniEngineString HoudiniEngineString(StringHandle); - HoudiniEngineString.ToFString(StringValue); - - HoudiniParameterStringChoice->SetStringValue(StringValue); - } - - // Get the choice descriptors - if (bFullUpdate) - { - // Set default value at created. - HoudiniParameterStringChoice->SetDefaultStringValue(); - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - - // Set the array sizes - HoudiniParameterStringChoice->SetNumChoices(ParmInfo.choiceCount); - - bool bMatchedSelectionLabel = false; - FString CurrentStringValue = HoudiniParameterStringChoice->GetStringValue(); - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ChoiceValue = HoudiniParameterStringChoice->GetStringChoiceValueAt(ChoiceIdx); - if (ChoiceValue) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].valueSH); - if (!HoudiniEngineString.ToFString(*ChoiceValue)) - return false; - //StringChoiceValues.Add(TSharedPtr< FString >(ChoiceValue)); - } - - FString * ChoiceLabel = HoudiniParameterStringChoice->GetStringChoiceLabelAt(ChoiceIdx); - if (ChoiceLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ChoiceLabel)) - return false; - //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); - } - - // If this is a string choice list, we need to match name with corresponding selection label. - if (!bMatchedSelectionLabel && ChoiceValue->Equals(CurrentStringValue)) - { - bMatchedSelectionLabel = true; - HoudiniParameterStringChoice->SetIntValue(ChoiceIdx); - } - } - } - else if (bUpdateValue) - { - // We still need to match the string value to the label - HoudiniParameterStringChoice->UpdateIntValueFromString(); - } - } - } - break; - - case EHoudiniParameterType::Label: - { - UHoudiniParameterLabel* HoudiniParameterLabel = Cast(HoudiniParameter); - if (HoudiniParameterLabel && !HoudiniParameterLabel->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_LABEL) - return false; - - // Set the valueIndex - HoudiniParameterLabel->SetValueIndex(ParmInfo.stringValuesIndex); - - // Get the actual value for this property. - TArray StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandles[0], - ParmInfo.stringValuesIndex, ParmInfo.size); - - HoudiniParameterLabel->EmptyLabelString(); - - // Convert HAPI string handles to Unreal strings. - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - HoudiniParameterLabel->AddLabelString(ValueString); - } - } - } - break; - - case EHoudiniParameterType::MultiParm: - { - UHoudiniParameterMultiParm* HoudiniParameterMulti = Cast(HoudiniParameter); - if (HoudiniParameterMulti && !HoudiniParameterMulti->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST) - return false; - - // Set the valueIndex - HoudiniParameterMulti->SetValueIndex(ParmInfo.intValuesIndex); - - // Set the multiparm value - int32 MultiParmValue = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &MultiParmValue, ParmInfo.intValuesIndex, 1), false); - - HoudiniParameterMulti->SetValue(MultiParmValue); - HoudiniParameterMulti->MultiParmInstanceCount = ParmInfo.instanceCount; - HoudiniParameterMulti->MultiParmInstanceLength = ParmInfo.instanceLength; - - } - - if (bFullUpdate) - { - HoudiniParameterMulti->SetDefaultInstanceCount(ParmInfo.instanceCount); - } - } - break; - - case EHoudiniParameterType::Separator: - { - UHoudiniParameterSeparator* HoudiniParameterSeparator = Cast(HoudiniParameter); - if (HoudiniParameterSeparator && !HoudiniParameterSeparator->IsPendingKill()) - { - // We can only handle separator type. - if (ParmInfo.type != HAPI_PARMTYPE_SEPARATOR) - return false; - - // Set the valueIndex - HoudiniParameterSeparator->SetValueIndex(ParmInfo.stringValuesIndex); - } - } - break; - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - { - UHoudiniParameterString* HoudiniParameterString = Cast(HoudiniParameter); - if (HoudiniParameterString && !HoudiniParameterString->IsPendingKill()) - { - // We can only handle string type. - if (ParmInfo.type != HAPI_PARMTYPE_STRING && ParmInfo.type != HAPI_PARMTYPE_NODE) - return false; - - // Set the valueIndex - HoudiniParameterString->SetValueIndex(ParmInfo.stringValuesIndex); - - // Stop if we don't want to update the value - if (bUpdateValue) - { - // Get the actual value for this property. - TArray< HAPI_StringHandle > StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandles[0], - ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - - // Convert HAPI string handles to Unreal strings. - HoudiniParameterString->SetNumberOfValues(ParmInfo.size); - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - HoudiniParameterString->SetValueAt(ValueString, Idx); - } - } - - if (bFullUpdate) - { - // Set default string values on created - HoudiniParameterString->SetDefaultValues(); - // Check if the parameter has the "asset_ref" tag - HoudiniParameterString->SetIsAssetRef( - FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_ASSET_REF)); - } - } - } - break; - - case EHoudiniParameterType::Toggle: - { - UHoudiniParameterToggle* HoudiniParameterToggle = Cast(HoudiniParameter); - if (HoudiniParameterToggle && !HoudiniParameterToggle->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_TOGGLE) - return false; - - // Set the valueIndex - HoudiniParameterToggle->SetValueIndex(ParmInfo.intValuesIndex); - - // Stop if we don't want to update the value - if (bUpdateValue) - { - // Get the actual values for this property. - HoudiniParameterToggle->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterToggle->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - } - - if (bFullUpdate) - { - HoudiniParameterToggle->SetDefaultValues(); - } - } - break; - - case EHoudiniParameterType::Invalid: - { - // TODO - } - break; - } - - return true; -} - -bool -FHoudiniParameterTranslator::HapiGetParameterTagValue(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag, FString& TagValue) -{ - // Default - TagValue = FString(); - - // Does the parameter has the tag? - bool HasTag = false; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ParmHasTag( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &HasTag), false); - - if (!HasTag) - return false; - - // Get the tag string value - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmTagValue( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &StringHandle), false); - - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(TagValue)) - { - return true; - } - - return false; -} - - -bool -FHoudiniParameterTranslator::HapiGetParameterUnit(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString) -{ - // - OutUnitString = TEXT(""); - - // We're looking for the parameter unit tag - //FString UnitTag = "units"; - - // Get the actual string value. - FString UnitString = TEXT(""); - if (!FHoudiniParameterTranslator::HapiGetParameterTagValue(NodeId, ParmId, "units", UnitString)) - return false; - - // We need to do some replacement in the string here in order to be able to get the - // proper unit type when calling FUnitConversion::UnitFromString(...) after. - - // Per second and per hour are the only "per" unit that unreal recognize - UnitString.ReplaceInline(TEXT("s-1"), TEXT("/s")); - UnitString.ReplaceInline(TEXT("h-1"), TEXT("/h")); - - // Houdini likes to add '1' on all the unit, so we'll remove all of them - // except the '-1' that still needs to remain. - UnitString.ReplaceInline(TEXT("-1"), TEXT("--")); - UnitString.ReplaceInline(TEXT("1"), TEXT("")); - UnitString.ReplaceInline(TEXT("--"), TEXT("-1")); - - OutUnitString = UnitString; - - return true; -} - -bool -FHoudiniParameterTranslator::HapiGetParameterHasTag(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag) -{ - // Does the parameter has the tag we're looking for? - bool HasTag = false; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &HasTag), false); - - return HasTag; -} - - -bool -FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * HAC ) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TMap RampsToRevert; - - for (int32 ParmIdx = 0; ParmIdx < HAC->GetNumParameters(); ParmIdx++) - { - UHoudiniParameter*& CurrentParm = HAC->Parameters[ParmIdx]; - if (!CurrentParm || CurrentParm->IsPendingKill() || !CurrentParm->HasChanged()) - continue; - - bool bSuccess = false; - - if (CurrentParm->IsPendingRevertToDefault()) - { - bSuccess = RevertParameterToDefault(CurrentParm); - - if (CurrentParm->GetParameterType() == EHoudiniParameterType::FloatRamp || - CurrentParm->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - RampsToRevert.Add(CurrentParm->GetParameterName(), CurrentParm); - } - } - else - { - bSuccess = UploadParameterValue(CurrentParm); - } - - - if (bSuccess) - { - CurrentParm->MarkChanged(false); - //CurrentParm->SetNeedsToTriggerUpdate(false); - } - else - { - // Keep this param marked as changed but prevent it from generating updates - CurrentParm->SetNeedsToTriggerUpdate(false); - } - } - - FHoudiniParameterTranslator::RevertRampParameters(RampsToRevert, HAC->GetAssetId()); - - return true; -} - -bool -FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return false; - - switch (InParam->GetParameterType()) - { - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParam = Cast(InParam); - if (!FloatParam || FloatParam->IsPendingKill()) - { - return false; - } - - float* DataPtr = FloatParam->GetValuesPtr(); - if (!DataPtr) - { - return false; - } - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - FloatParam->GetNodeId(), DataPtr, FloatParam->GetValueIndex(), FloatParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::Int: - { - UHoudiniParameterInt* IntParam = Cast(InParam); - if (!IntParam || IntParam->IsPendingKill()) - { - return false; - } - - int32* DataPtr = IntParam->GetValuesPtr(); - if (!DataPtr) - { - return false; - } - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - IntParam->GetNodeId(), DataPtr, IntParam->GetValueIndex(), IntParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::String: - { - UHoudiniParameterString* StringParam = Cast(InParam); - if (!StringParam || StringParam->IsPendingKill()) - { - return false; - } - - int32 NumValues = StringParam->GetNumberOfValues(); - if (NumValues <= 0) - { - return false; - } - - for (int32 Idx = 0; Idx < NumValues; Idx++) - { - std::string ConvertedString = TCHAR_TO_UTF8(*(StringParam->GetValueAt(Idx))); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - StringParam->GetNodeId(), ConvertedString.c_str(), StringParam->GetParmId(), Idx), false); - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) - return false; - - // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValue(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); - } - break; - case EHoudiniParameterType::StringChoice: - { - UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) - { - return false; - } - - if (ChoiceParam->IsStringChoice()) - { - // Set the parameter's string value. - std::string ConvertedString = TCHAR_TO_UTF8(*(ChoiceParam->GetStringValue())); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), ConvertedString.c_str(), ChoiceParam->GetParmId(), 0), false); - } - else - { - // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValue(); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); - } - } - break; - - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* ColorParam = Cast(InParam); - if (!ColorParam || ColorParam->IsPendingKill()) - return false; - - bool bHasAlpha = ColorParam->GetTupleSize() == 4 ? true : false; - FLinearColor Color = ColorParam->GetColorValue(); - - // Set the color value - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - ColorParam->GetNodeId(), - (float*)(&Color.R), ColorParam->GetValueIndex(), bHasAlpha ? 4 : 3), false); - - } - break; - - case EHoudiniParameterType::Button: - { - UHoudiniParameterButton* ButtonParam = Cast(InParam); - if (!ButtonParam) - return false; - - TArray DataArray; - DataArray.Add(1); - - // Set the button parameter value to 1, (setting button param to any value will call the callback function.) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ButtonParam->GetNodeId(), - DataArray.GetData(), - ButtonParam->GetValueIndex(), 1), false); - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - UHoudiniParameterButtonStrip* ButtonStripParam = Cast(InParam); - if (!ButtonStripParam) - return false; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ButtonStripParam->GetNodeId(), - ButtonStripParam->Values.GetData(), - ButtonStripParam->GetValueIndex(), ButtonStripParam->Count), false); - } - break; - - case EHoudiniParameterType::Toggle: - { - UHoudiniParameterToggle* ToggleParam = Cast(InParam); - if (!ToggleParam) - return false; - - // Set the toggle parameter values. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ToggleParam->GetNodeId(), - ToggleParam->GetValuesPtr(), - ToggleParam->GetValueIndex(), ToggleParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - UHoudiniParameterFile* FileParam = Cast(InParam); - - if (!UploadDirectoryPath(FileParam)) - return false; - } - break; - - case EHoudiniParameterType::MultiParm: - { - if (!UploadMultiParmValues(InParam)) - return false; - } - - break; - - case EHoudiniParameterType::FloatRamp: - { - if (!UploadRampParameter(InParam)) - return false; - } - break; - - case EHoudiniParameterType::ColorRamp: - { - if (!UploadRampParameter(InParam)) - return false; - } - break; - - default: - { - // TODO: implement other parameter types! - return false; - } - break; - } - - // The parameter is no longer considered as changed - InParam->MarkChanged(false); - - return true; -} - -bool -FHoudiniParameterTranslator::RevertParameterToDefault(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return false; - - if (!InParam->IsPendingRevertToDefault()) - return false; - - TArray TupleToRevert; - InParam->GetTuplePendingRevertToDefault(TupleToRevert); - if (TupleToRevert.Num() <= 0) - return false; - - FString ParameterName = InParam->GetParameterName(); - - bool bReverted = true; - for (auto CurrentIdx : TupleToRevert ) - { - if (!TupleToRevert.IsValidIndex(CurrentIdx)) - { - // revert the whole parameter to its default value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefaults( - FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName))) - { - HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s to its default value."), *ParameterName); - bReverted = false; - } - } - else - { - // revert a tuple to its default value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefault( - FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName), CurrentIdx)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s - %d to its default value."), *ParameterName, CurrentIdx); - bReverted = false; - } - } - } - - if (!bReverted) - return false; - - // The parameter no longer needs to be reverted - InParam->MarkDefault(true); - - return true; -} - -EHoudiniFolderParameterType -FHoudiniParameterTranslator::GetFolderTypeFromParamInfo(const HAPI_ParmInfo* ParamInfo) -{ - EHoudiniFolderParameterType Type = EHoudiniFolderParameterType::Invalid; - - switch (ParamInfo->scriptType) - { - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPSIMPLE: - Type = EHoudiniFolderParameterType::Simple; - break; - - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPCOLLAPSIBLE: - Type = EHoudiniFolderParameterType::Collapsible; - break; - - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUP: - Type = EHoudiniFolderParameterType::Tabs; - break; - - // Treat Radio folders as tabs for now - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPRADIO: - Type = EHoudiniFolderParameterType::Radio; - break; - - default: - Type = EHoudiniFolderParameterType::Other; - break; - - } - - return Type; - -} - -bool -FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InParam, TArray &OldParams, const int32& InAssetId, const int32 CurrentIndex) -{ - - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - - if (!MultiParam || MultiParam->IsPendingKill()) - return false; - - UHoudiniParameterRampFloat* FloatRampParameter = nullptr; - UHoudiniParameterRampColor* ColorRampParameter = nullptr; - - if (MultiParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - FloatRampParameter = Cast(MultiParam); - - else if (MultiParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - ColorRampParameter = Cast(MultiParam); - - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); - - HAPI_NodeId NodeId = AssetInfo.nodeId; - - int32 Idx = 0; - int32 InstanceCount = -1; - HAPI_ParmId ParmId = -1; - TArray ParmInfos; - if (!GetMultiParmInstanceStartIdx(AssetInfo, MultiParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - - for (int n = 0; n < InstanceCount - MultiParam->GetInstanceCount(); ++n) - { - FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset); - } - - for (int n = 0; n < MultiParam->GetInstanceCount() - InstanceCount; ++n) - { - FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset); - } - - - // Sync nested multi-params recursively - for (int32 ParamIdx = CurrentIndex; ParamIdx < OldParams.Num(); ++ParamIdx) - { - UHoudiniParameter* NextParm = OldParams[ParamIdx]; - if (NextParm->GetParentParmId() == ParmId) - { - if (NextParm->GetParameterType() == EHoudiniParameterType::MultiParm) - { - SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, ParamIdx); - } - } - } - - - // The multiparm is a ramp, Get the param infos again, since the number of param instances is changed - if (!GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - // Step 3: Set values of the inserted points - if (FloatRampParameter) - { - for (auto & Point : FloatRampParameter->Points) - { - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); - - // 2: update float value at param Idx + 1 - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Value), ParmInfos[Idx + 1].floatValuesIndex, 1); - - // 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Point->Interpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - else if (ColorRampParameter) - { - for (auto& Point : ColorRampParameter->Points) - { - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); - - // 2: update color value at param Idx + 1 - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, (float*)(&Point->Value.R), ParmInfos[Idx + 1].floatValuesIndex, 3); - - // 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Point->Interpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - - - return true; -} - - -bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam) -{ - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam || MultiParam->IsPendingKill()) - return false; - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InParam->GetOuter()); - if (!HoudiniAssetComponent) - return false; - - int32 InsertIndexStart = -1; - UHoudiniParameterRampFloat* RampFloatParam = Cast(InParam); - UHoudiniParameterRampColor* RampColorParam = Cast(InParam); - - TArray *Events = nullptr; - if (RampFloatParam) - { - Events = &(RampFloatParam->ModificationEvents); - InsertIndexStart = RampFloatParam->GetInstanceCount(); - } - else if (RampColorParam) - { - Events = &(RampColorParam->ModificationEvents); - InsertIndexStart = RampColorParam->GetInstanceCount(); - } - else - return false; - - // Handle All Events - Events->Sort([](const UHoudiniParameterRampModificationEvent& A, const UHoudiniParameterRampModificationEvent& B) - { - return A.DeleteInstanceIndex > B.DeleteInstanceIndex; - }); - - - // Step 1: Handle all delete events first - for (auto& Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsDeleteEvent()) - continue; - - FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Event->DeleteInstanceIndex + MultiParam->InstanceStartOffset); - - InsertIndexStart -= 1; - } - - int32 InsertIndex = InsertIndexStart; - - - // Step 2: Handle all insert events - for (auto& Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsInsertEvent()) - continue; - - FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), InsertIndex + MultiParam->InstanceStartOffset); - - InsertIndex += 1; - } - - // Step 3: Set inserted parameter values (only if there are instances inserted) - if (InsertIndex > InsertIndexStart) - { - if (HoudiniAssetComponent) - { - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), HoudiniAssetComponent->AssetId, &AssetInfo), false); - - int32 Idx = 0; - int32 InstanceCount = -1; - HAPI_ParmId ParmId = -1; - TArray< HAPI_ParmInfo > ParmInfos; - - if (!FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), - Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - if (InstanceCount < 0) - return false; - - // Instance count doesn't match, - if (InsertIndex != InstanceCount) - return false; - - - // Starting index of parameters which just inserted - Idx += 3 * InsertIndexStart; - - - for (auto & Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsInsertEvent()) - continue; - - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &(Event->InsertPosition), ParmInfos[Idx].floatValuesIndex, 1); - - // step 2: update value at param Idx + 1 - if (Event->IsFloatRampEvent()) - { - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &(Event->InsertFloat), ParmInfos[Idx + 1].floatValuesIndex, 1); - } - else - { - // color value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, (float*)(&Event->InsertColor.R), ParmInfos[Idx + 1].floatValuesIndex, 3); - } - - // step 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Event->InsertInterpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - } - - // Step 4: clear all events - Events->Empty(); - - return true; -} - -bool FHoudiniParameterTranslator::UploadMultiParmValues(UHoudiniParameter* InParam) -{ - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam) - return false; - - TArray &LastModificationArray = MultiParam->MultiParmInstanceLastModifyArray; - - int32 Size = MultiParam->MultiParmInstanceLastModifyArray.Num(); - - for (int32 Index = 0; Index < Size; ++Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Inserted) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) - return false; - - } - } - - for (int32 Index = Size - 1; Index >= 0; --Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) - return false; - } - } - - // Remove all removal events. - for (int32 Index = Size - 1; Index >= 0; --Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) - LastModificationArray.RemoveAt(Index); - } - - // The last modification array is resized. - Size = LastModificationArray.Num(); - - // Reset the last modification array - for (int32 Itr =Size - 1; Itr >= 0; --Itr) - { - LastModificationArray[Itr] = EHoudiniMultiParmModificationType::None; - } - - MultiParam->MultiParmInstanceCount = Size; - - return true; -} - -bool -FHoudiniParameterTranslator::UploadDirectoryPath(UHoudiniParameterFile* InParam) -{ - if(!InParam) - return false; - - for (int32 Index = 0; Index < InParam->GetNumValues(); ++Index) - { - std::string ConvertedString = TCHAR_TO_UTF8(*(InParam->GetValueAt(Index))); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue(FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), ConvertedString.c_str(), InParam->GetParmId(), Index), false); - } - - return true; -} - -bool -FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, - int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos) -{ - // Reset outputs - OutStartIdx = 0; - OutInstanceCount = -1; - OutParmId = -1; - OutParmInfos.Empty(); - - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &NodeInfo), false); - - OutParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &OutParmInfos[0], 0, NodeInfo.parmCount), false); - - - while (OutStartIdx < OutParmInfos.Num()) - { - FString ParmNameBuffer; - FHoudiniEngineString(OutParmInfos[OutStartIdx].nameSH).ToFString(ParmNameBuffer); - - if (ParmNameBuffer == InParmName) - { - OutParmId = OutParmInfos[OutStartIdx].id; - OutInstanceCount = OutParmInfos[OutStartIdx].instanceCount; - break; - } - - OutStartIdx += 1; - } - - // Start index of the ramp children parameters - OutStartIdx += 1; - - return true; -} - -bool -FHoudiniParameterTranslator::RevertRampParameters(TMap & InRampParams, const int32 & AssetId) -{ - if (InRampParams.Num() <= 0) - return true; - - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); - - // Retrieve all the parameter infos. - TArray< HAPI_ParmInfo > ParmInfos; - ParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); - - int32 ParamIdx = 0; - while (ParamIdx < ParmInfos.Num()) - { - const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; - FString ParmName; - FHoudiniEngineString(ParmInfo.nameSH).ToFString(ParmName); - - if (InRampParams.Contains(ParmName)) - { - if (!InRampParams[ParmName]) - { - ParamIdx += 1; - continue; - } - - if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat * FloatRamp = Cast(InRampParams[ParmName]); - if (!FloatRamp) - { - ParamIdx += 1; - continue; - } - - if (ParmInfo.instanceCount != FloatRamp->NumDefaultPoints) - { - ParamIdx += 1; - continue; - } - - for (int32 PtIdx = 0; PtIdx < FloatRamp->NumDefaultPoints; ++PtIdx) - { - const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; - const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; - const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultValues.GetData() + PtIdx, ValueParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); - - ParamIdx += 3; - } - } - - if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor * ColorRamp = Cast(InRampParams[ParmName]); - if (!ColorRamp) - { - ParamIdx += 1; - continue; - } - - if (ParmInfo.instanceCount != ColorRamp->NumDefaultPoints) - { - ParamIdx += 1; - continue; - } - - for (int32 PtIdx = 0; PtIdx < ColorRamp->NumDefaultPoints; ++PtIdx) - { - const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; - const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; - const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, ColorRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, (float*)(&ColorRamp->DefaultValues[PtIdx].R), ValueParmInfo.floatValuesIndex, 3); - - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, ColorRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); - - ParamIdx += 3; - } - } - } - - ParamIdx += 1; - } - - return true; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniInput.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniParameter.h" +#include "HoudiniAssetComponent.h" + + +// Default values for certain UI min and max parameter values +#define HAPI_UNREAL_PARAM_INT_UI_MIN 0 +#define HAPI_UNREAL_PARAM_INT_UI_MAX 10 +#define HAPI_UNREAL_PARAM_FLOAT_UI_MIN 0.0f +#define HAPI_UNREAL_PARAM_FLOAT_UI_MAX 10.0f + +// Some default parameter name +#define HAPI_UNREAL_PARAM_TRANSLATE "t" +#define HAPI_UNREAL_PARAM_ROTATE "r" +#define HAPI_UNREAL_PARAM_SCALE "s" +#define HAPI_UNREAL_PARAM_PIVOT "p" +#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" + +// +bool +FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // When recooking/rebuilding the HDA, force a full update of all params + bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); + + TArray NewParameters; + if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, true, bForceFullUpdate)) + { + /* + // DO NOT MANUALLY DESTROY THE OLD/DANGLING PARAMETERS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + + // Destroy old/dangling parameters + for (auto& OldParm : HAC->Parameters) + { + if (!OldParm || OldParm->IsPendingKill()) + continue; + + OldParm->ConditionalBeginDestroy(); + OldParm = nullptr; + } + */ + + // Replace with the new parameters + HAC->Parameters = NewParameters; + } + + + return true; +} + +bool +FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) +{ + // Call OnPreCook for all parameters. + // Parameters can use this to ensure that any cached / non-cooking state is properly + // synced before the cook starts (Looking at you, ramp parameters!) + for (UHoudiniParameter* Param : HAC->Parameters) + { + if (!Param || Param->IsPendingKill()) + continue; + + Param->OnPreCook(); + } + + return true; +} + +// +bool +FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Update all the parameters using the loaded parameter object + // We set "UpdateValues" to false because we do not want to "read" the parameter value from Houdini + // but keep the loaded value + + // This is the first cook on loading after a save or duplication, + // We need to sync the Ramp parameters first, so that their child parameters can be kept + // TODO: Simplify this, should be handled in BuildAllParameters, + for (int32 Idx = 0; Idx < HAC->Parameters.Num(); ++Idx) + { + UHoudiniParameter* Param = HAC->Parameters[Idx]; + + if (!Param || Param->IsPendingKill()) + continue; + + switch(Param->GetParameterType()) + { + case EHoudiniParameterType::ColorRamp: + case EHoudiniParameterType::FloatRamp: + case EHoudiniParameterType::MultiParm: + { + SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, Idx); + } + break; + + default: + break; + } + } + + // When recooking/rebuilding the HDA, force a full update of all params + bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); + + // This call to BuildAllParameters will keep all the loaded parameters (in the HAC's Parameters array) + // that are still present in the HDA, and keep their loaded value. + TArray NewParameters; + if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, false, bForceFullUpdate)) + { + /* + // DO NOT DESTROY OLD PARAMS MANUALLY HERE + // This causes crashes upon duplication due to uncollected zombie objects... + // GC is supposed to handle this by itself + // Destroy old/dangling parameters + for (auto& OldParm : HAC->Parameters) + { + if (!OldParm || OldParm->IsPendingKill()) + continue; + + OldParm->ConditionalBeginDestroy(); + OldParm = nullptr; + } + */ + + // Simply replace with the new parameters + HAC->Parameters = NewParameters; + } + + return true; +} + +bool +FHoudiniParameterTranslator::BuildAllParameters( + const HAPI_NodeId& AssetId, + class UObject* Outer, + TArray& CurrentParameters, + TArray& NewParameters, + const bool& bUpdateValues, + const bool& InForceFullUpdate) +{ + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); + + NewParameters.Empty(); + if (NodeInfo.parmCount == 0) + { + // The asset doesnt have any parameter, we're done. + return true; + } + else if (NodeInfo.parmCount < 0) + { + // Invalid parm count + return false; + } + + TArray AllMultiParams; + + // Retrieve all the parameter infos. + TArray< HAPI_ParmInfo > ParmInfos; + ParmInfos.SetNumUninitialized(NodeInfo.parmCount); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); + + // Create a name lookup cache for the current parameters + TMap CurrentParametersByName; + CurrentParametersByName.Reserve(CurrentParameters.Num()); + for (auto& Parm : CurrentParameters) + { + if (!Parm) + continue; + CurrentParametersByName.Add(Parm->GetParameterName(), Parm); + } + + // Create properties for parameters. + TArray NewParmIds; + for (int32 ParamIdx = 0; ParamIdx < NodeInfo.parmCount; ++ParamIdx) + { + + // Retrieve param info at this index. + const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; + + // If the parameter is corrupt, skip it + if (ParmInfo.id < 0 || ParmInfo.childIndex < 0) + { + HOUDINI_LOG_WARNING(TEXT("Corrupt parameter %d detected, skipping."), ParamIdx); + continue; + } + + // If the parameter is invisible, skip it. + //if (ParmInfo.invisible) + // continue; + + // Check if any parent folder of this parameter is invisible + bool SkipParm = false; + HAPI_ParmId ParentId = ParmInfo.parentId; + while (ParentId > 0 && !SkipParm) + { + if (const HAPI_ParmInfo* ParentInfoPtr = ParmInfos.FindByPredicate([=](const HAPI_ParmInfo& Info) { + return Info.id == ParentId; + })) + { + if (ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER) + SkipParm = true; + ParentId = ParentInfoPtr->parentId; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not find parent of parameter %d"), ParmInfo.id); + SkipParm = true; + } + } + + if (SkipParm) + continue; + + // See if this parameter has already been created. + // We can't use the HAPI_ParmId because it is not unique to parameter instances, + // so instead, try to find the existing parameter by name using the lookup table + FString NewParmName; + FHoudiniEngineString(ParmInfo.nameSH).ToFString(NewParmName); + + EHoudiniParameterType ParmType = EHoudiniParameterType::Invalid; + FHoudiniParameterTranslator::GetParmTypeFromParmInfo(ParmInfo, ParmType); + + UHoudiniParameter ** FoundHoudiniParameter = CurrentParametersByName.Find(NewParmName); + + // If that parameter exists, we might be able to simply reuse it. + bool IsFoundParameterValid = false; + if (FoundHoudiniParameter && *FoundHoudiniParameter && !(*FoundHoudiniParameter)->IsPendingKill()) + { + // First, we can simply check that the tuple size hasn't changed + if ((*FoundHoudiniParameter)->GetTupleSize() != ParmInfo.size) + { + IsFoundParameterValid = false; + } + else if (ParmType == EHoudiniParameterType::Invalid ) + { + IsFoundParameterValid = false; + } + else if (ParmType != (*FoundHoudiniParameter)->GetParameterType() ) + { + // Types do not match + IsFoundParameterValid = false; + } + else if ( !CheckParameterTypeAndClassMatch( *FoundHoudiniParameter, ParmType) ) + { + // Found parameter class does not match + IsFoundParameterValid = false; + } + else + { + // We can reuse the parameter + IsFoundParameterValid = true; + } + } + + UHoudiniParameter * HoudiniAssetParameter = nullptr; + + if (IsFoundParameterValid) + { + // We can reuse the parameter we found + HoudiniAssetParameter = *FoundHoudiniParameter; + + // Transfer param object from current map to new map + CurrentParameters.Remove(HoudiniAssetParameter); + CurrentParametersByName.Remove(NewParmName); + + // Do a fast update of this parameter + if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, InForceFullUpdate, bUpdateValues)) + continue; + + // Reset the states of ramp parameters. + switch (HoudiniAssetParameter->GetParameterType()) + { + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); + if (FloatRampParam) + { + UHoudiniAssetComponent* ParentHAC = Cast(FloatRampParam->GetOuter()); + if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) + FloatRampParam->bCaching = false; + } + + break; + } + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); + if (ColorRampParam) + { + UHoudiniAssetComponent* ParentHAC = Cast(ColorRampParam->GetOuter()); + if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) + ColorRampParam->bCaching = false; + } + + break; + } + } + + } + else + { + // Create a new parameter object of the appropriate type + HoudiniAssetParameter = CreateTypedParameter(Outer, ParmType, NewParmName); + // Fully update this parameter + if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, true, true)) + continue; + + } + + // Add the new parameters + NewParameters.Add(HoudiniAssetParameter); + NewParmIds.Add(ParmInfo.id); + + + // Check if the parameter is a direct child of a multiparam. + if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::MultiParm) + AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); + + if (AllMultiParams.Contains(HoudiniAssetParameter->GetParentParmId())) + { + HoudiniAssetParameter->SetIsDirectChildOfMultiParm(true); + + // Treat the folderlist whose direct parent is a multi param as a multi param too. + if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::FolderList) + AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); + } + + } + + // Assign folder type to all folderlists, + // if the first child of the folderlist is Tab or Radio button, set the bIsTabMenu of the folderlistParam to be true, otherwise false + for (int32 Idx = 0; Idx < NewParameters.Num(); ++Idx) + { + UHoudiniParameter * CurParam = NewParameters[Idx]; + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (CurParam->GetParameterType() == EHoudiniParameterType::FolderList) + { + UHoudiniParameterFolderList* CurFolderList = Cast(CurParam); + if (!CurFolderList || CurFolderList->IsPendingKill()) + continue; + + int32 FirstChildIdx = Idx + 1; + if (!NewParameters.IsValidIndex(FirstChildIdx)) + continue; + + UHoudiniParameterFolder* FirstChildFolder = Cast(NewParameters[FirstChildIdx]); + if (!FirstChildFolder || FirstChildFolder->IsPendingKill()) + continue; + + if (FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Radio || + FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Tabs) + { + // If this is the first time build + if (!CurFolderList->IsTabMenu()) + { + // Set the folderlist to be tabs + CurFolderList->SetIsTabMenu(true); + // Select the first child tab folder by default. + FirstChildFolder->SetChosen(true); + } + } + else + CurFolderList->SetIsTabMenu(false); + } + } + + FHoudiniEngineUtils::UpdateEditorProperties(Outer, true); + + return true; +} + + +void +FHoudiniParameterTranslator::GetParmTypeFromParmInfo( + const HAPI_ParmInfo& ParmInfo, + EHoudiniParameterType& ParmType) +{ + ParmType = EHoudiniParameterType::Invalid; + //ParmValueType = EHoudiniParameterValueType::Invalid; + + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_BUTTON: + ParmType = EHoudiniParameterType::Button; + //ParmValueType = EHoudiniParameterValueType::Int; + break; + + case HAPI_PARMTYPE_STRING: + { + if (ParmInfo.choiceCount > 0) + { + ParmType = EHoudiniParameterType::StringChoice; + //ParmValueType = EHoudiniParameterValueType::String; + } + else + { + ParmType = EHoudiniParameterType::String; + //ParmValueType = EHoudiniParameterValueType::String; + } + break; + } + + case HAPI_PARMTYPE_INT: + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_BUTTONSTRIP) + { + ParmType = EHoudiniParameterType::ButtonStrip; + break; + } + + if (ParmInfo.choiceCount > 0) + { + ParmType = EHoudiniParameterType::IntChoice; + //ParmValueType = EHoudiniParameterValueType::Int; + } + else + { + ParmType = EHoudiniParameterType::Int; + //ParmValueType = EHoudiniParameterValueType::Int; + } + break; + } + + case HAPI_PARMTYPE_FLOAT: + { + ParmType = EHoudiniParameterType::Float; + //ParmValueType = EHoudiniParameterValueType::Float; + break; + } + + case HAPI_PARMTYPE_TOGGLE: + { + ParmType = EHoudiniParameterType::Toggle; + //ParmValueType = EHoudiniParameterValueType::Int; + break; + } + + case HAPI_PARMTYPE_COLOR: + { + ParmType = EHoudiniParameterType::Color; + //ParmValueType = EHoudiniParameterValueType::Float; + break; + } + + case HAPI_PARMTYPE_LABEL: + { + ParmType = EHoudiniParameterType::Label; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_SEPARATOR: + { + ParmType = EHoudiniParameterType::Separator; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + case HAPI_PARMTYPE_FOLDERLIST: + { + ParmType = EHoudiniParameterType::FolderList; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + // Treat radio folders as tab folders for now + case HAPI_PARMTYPE_FOLDERLIST_RADIO: + { + ParmType = EHoudiniParameterType::FolderList; + break; + } + + case HAPI_PARMTYPE_FOLDER: + { + ParmType = EHoudiniParameterType::Folder; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + case HAPI_PARMTYPE_MULTIPARMLIST: + { + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType) + { + ParmType = EHoudiniParameterType::FloatRamp; + //ParmValueType = EHoudiniParameterValueType::Float; + } + else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + { + ParmType = EHoudiniParameterType::ColorRamp; + //ParmValueType = EHoudiniParameterValueType::Float; + } + else + { + ParmType = EHoudiniParameterType::MultiParm; + //ParmValueType = EHoudiniParameterValueType::Int; + } + break; + } + + case HAPI_PARMTYPE_PATH_FILE: + { + ParmType = EHoudiniParameterType::File; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_DIR: + { + ParmType = EHoudiniParameterType::FileDir; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_GEO: + { + ParmType = EHoudiniParameterType::FileGeo; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + { + ParmType = EHoudiniParameterType::FileImage; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_NODE: + { + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + ParmType = EHoudiniParameterType::Input; + } + else + { + ParmType = EHoudiniParameterType::String; + } + break; + } + + default: + { + // Just ignore unsupported types for now. + HOUDINI_LOG_WARNING(TEXT("Parameter Type (%d) is unsupported"), static_cast(ParmInfo.type)); + break; + } + } +} + +UClass* +FHoudiniParameterTranslator::GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo) +{ + UClass* FoundClass = nullptr; + + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_STRING: + if (!ParmInfo.choiceCount) + FoundClass = UHoudiniParameterString::StaticClass(); + else + FoundClass = UHoudiniParameterChoice ::StaticClass(); + break; + + case HAPI_PARMTYPE_INT: + if (!ParmInfo.choiceCount) + FoundClass = UHoudiniParameterInt::StaticClass(); + else + FoundClass = UHoudiniParameterChoice::StaticClass(); + break; + + case HAPI_PARMTYPE_FLOAT: + FoundClass = UHoudiniParameterFloat::StaticClass(); + break; + + case HAPI_PARMTYPE_TOGGLE: + FoundClass = UHoudiniParameterToggle::StaticClass(); + break; + + case HAPI_PARMTYPE_COLOR: + FoundClass = UHoudiniParameterColor::StaticClass(); + break; + + case HAPI_PARMTYPE_LABEL: + FoundClass = UHoudiniParameterLabel::StaticClass(); + break; + + case HAPI_PARMTYPE_BUTTON: + FoundClass = UHoudiniParameterButton::StaticClass(); + break; + + case HAPI_PARMTYPE_SEPARATOR: + FoundClass = UHoudiniParameterSeparator::StaticClass(); + break; + + case HAPI_PARMTYPE_FOLDERLIST: + FoundClass = UHoudiniParameterFolderList::StaticClass(); + break; + + case HAPI_PARMTYPE_FOLDER: + FoundClass = UHoudiniParameterFolder::StaticClass(); + break; + + case HAPI_PARMTYPE_MULTIPARMLIST: + { + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + FoundClass = UHoudiniParameterRampFloat::StaticClass(); + else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + FoundClass = UHoudiniParameterRampColor::StaticClass(); + } + break; + + case HAPI_PARMTYPE_PATH_FILE: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_DIR: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_GEO: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + + case HAPI_PARMTYPE_NODE: + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + FoundClass = UHoudiniParameter::StaticClass(); + } + else + { + FoundClass = UHoudiniParameterString::StaticClass(); + } + break; + } + + if (FoundClass == nullptr) + return UHoudiniParameter::StaticClass(); + + return FoundClass; +} + +bool +FHoudiniParameterTranslator::CheckParameterTypeAndClassMatch(UHoudiniParameter* Parameter, const EHoudiniParameterType& ParmType) +{ + UClass* FoundClass = Parameter->GetClass(); + bool FailedTypeCheck = true; + + switch (ParmType) + { + case EHoudiniParameterType::Invalid: + { + FailedTypeCheck = true; + break; + } + + case EHoudiniParameterType::Button: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterButton >(); + break; + } + + case EHoudiniParameterType::Color: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterColor >(); + break; + } + + case EHoudiniParameterType::ColorRamp: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampColor >(); + break; + } + case EHoudiniParameterType::FloatRamp: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampFloat >(); + break; + } + + case EHoudiniParameterType::File: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileDir: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileGeo: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileImage: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + + case EHoudiniParameterType::Float: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFloat >(); + break; + } + + case EHoudiniParameterType::Folder: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolder >(); + break; + } + + case EHoudiniParameterType::FolderList: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolderList >(); + break; + } + + case EHoudiniParameterType::Input: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterOperatorPath >(); + break; + } + + case EHoudiniParameterType::Int: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterInt >(); + break; + } + + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::StringChoice: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); + break; + } + + case EHoudiniParameterType::Label: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterLabel >(); + break; + } + + case EHoudiniParameterType::MultiParm: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterMultiParm >(); + break; + } + + case EHoudiniParameterType::Separator: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterSeparator >(); + break; + } + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); + break; + } + + case EHoudiniParameterType::Toggle: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterToggle >(); + break; + } + }; + + return !FailedTypeCheck; +} +/* +bool +FHoudiniParameterTranslator::CheckParameterClassAndInfoMatch(UHoudiniParameter* Parameter, const HAPI_ParmInfo& ParmInfo) +{ + if (!Parameter || Parameter->IsPendingKill()) + return false; + + UClass* FoundClass = Parameter->GetClass(); + bool FailedTypeCheck = true; + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_STRING: + if (!ParmInfo.choiceCount) + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); + else + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); + break; + + case HAPI_PARMTYPE_INT: + if (!ParmInfo.choiceCount) + FailedTypeCheck &= !FoundClass->IsChildOf(); + else + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FLOAT: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_TOGGLE: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_COLOR: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_LABEL: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_BUTTON: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_SEPARATOR: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FOLDERLIST: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FOLDER: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_MULTIPARMLIST: + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + break; + + case HAPI_PARMTYPE_PATH_FILE: + case HAPI_PARMTYPE_PATH_FILE_DIR: + case HAPI_PARMTYPE_PATH_FILE_GEO: + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_NODE: + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + else + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + break; + } + + return FailedTypeCheck; +} +*/ + +UHoudiniParameter * +FHoudiniParameterTranslator::CreateTypedParameter(UObject * Outer, const EHoudiniParameterType& ParmType, const FString& ParmName) +{ + UHoudiniParameter* HoudiniParameter = nullptr; + // Create a parameter of the desired type + switch (ParmType) + { + case EHoudiniParameterType::Button: + HoudiniParameter = UHoudiniParameterButton::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::ButtonStrip: + HoudiniParameter = UHoudiniParameterButtonStrip::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Color: + HoudiniParameter = UHoudiniParameterColor::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::ColorRamp: + HoudiniParameter = UHoudiniParameterRampColor::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FloatRamp: + HoudiniParameter = UHoudiniParameterRampFloat::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::File: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FileDir: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileDir); + break; + + case EHoudiniParameterType::FileGeo: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileGeo); + break; + + case EHoudiniParameterType::FileImage: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileImage); + break; + + case EHoudiniParameterType::Float: + HoudiniParameter = UHoudiniParameterFloat::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Folder: + HoudiniParameter = UHoudiniParameterFolder::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FolderList: + HoudiniParameter = UHoudiniParameterFolderList::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Input: + // Input parameter simply use the base class as all the processingsince is handled by UHoudiniInput + HoudiniParameter = UHoudiniParameterOperatorPath::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(ParmType); + break; + + case EHoudiniParameterType::Int: + HoudiniParameter = UHoudiniParameterInt::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::IntChoice: + HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::IntChoice); + break; + + case EHoudiniParameterType::StringChoice: + HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::StringChoice); + break; + + case EHoudiniParameterType::Label: + HoudiniParameter = UHoudiniParameterLabel::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::MultiParm: + HoudiniParameter = UHoudiniParameterMultiParm::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Separator: + HoudiniParameter = UHoudiniParameterSeparator::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + HoudiniParameter = UHoudiniParameterString::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Toggle: + HoudiniParameter = UHoudiniParameterToggle::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Invalid: + // TODO handle invalid params + HoudiniParameter = UHoudiniParameter::Create(Outer, ParmName); + break; + } + + return HoudiniParameter; +} + +bool +FHoudiniParameterTranslator::UpdateParameterFromInfo( + UHoudiniParameter * HoudiniParameter, const HAPI_NodeId& InNodeId, const HAPI_ParmInfo& ParmInfo, + const bool& bFullUpdate, const bool& bUpdateValue) +{ + if (!HoudiniParameter || HoudiniParameter->IsPendingKill()) + return false; + + // Copy values from the ParmInfos + HoudiniParameter->SetNodeId(InNodeId); + HoudiniParameter->SetParmId(ParmInfo.id); + HoudiniParameter->SetParentParmId(ParmInfo.parentId); + + HoudiniParameter->SetChildIndex(ParmInfo.childIndex); + HoudiniParameter->SetTagCount(ParmInfo.tagCount); + HoudiniParameter->SetTupleSize(ParmInfo.size); + + HoudiniParameter->SetVisible(!ParmInfo.invisible); + HoudiniParameter->SetDisabled(ParmInfo.disabled); + HoudiniParameter->SetSpare(ParmInfo.spare); + HoudiniParameter->SetJoinNext(ParmInfo.joinNext); + + HoudiniParameter->SetTagCount(ParmInfo.tagCount); + HoudiniParameter->SetIsChildOfMultiParm(ParmInfo.isChildOfMultiParm); + + UHoudiniParameterMultiParm* MultiParm = Cast(HoudiniParameter); + if(MultiParm) + MultiParm->InstanceStartOffset = ParmInfo.instanceStartOffset; + + + + // Get the parameter type + EHoudiniParameterType ParmType = HoudiniParameter->GetParameterType(); + + // We need to set string values from the parmInfo + if (bFullUpdate) + { + FString Name; + { + // Name + FHoudiniEngineString HoudiniEngineStringName(ParmInfo.nameSH); + if (HoudiniEngineStringName.ToFString(Name)) + HoudiniParameter->SetParameterName(Name); + } + + { + // Label + FString Label; + FHoudiniEngineString HoudiniEngineStringLabel(ParmInfo.labelSH); + if (HoudiniEngineStringLabel.ToFString(Label)) + HoudiniParameter->SetParameterLabel(Label); + } + + { + // Help + FString Help; + FHoudiniEngineString HoudiniEngineStringHelp(ParmInfo.helpSH); + if (HoudiniEngineStringHelp.ToFString(Help)) + HoudiniParameter->SetParameterHelp(Help); + } + + if (ParmType == EHoudiniParameterType::String + || ParmType == EHoudiniParameterType::Int + || ParmType == EHoudiniParameterType::Float + || ParmType == EHoudiniParameterType::Toggle + || ParmType == EHoudiniParameterType::Color) + { + // See if the parm has an expression + int32 TupleIdx = ParmInfo.intValuesIndex; + bool bHasExpression = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ParmHasExpression( + FHoudiniEngine::Get().GetSession(), InNodeId, + TCHAR_TO_UTF8(*Name), TupleIdx, &bHasExpression)) + { + // ? + } + + FString ParmExprString = TEXT(""); + if (bHasExpression) + { + // Try to get the expression's value + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmExpression( + FHoudiniEngine::Get().GetSession(), InNodeId, + TCHAR_TO_UTF8(*Name), TupleIdx, &StringHandle)) + { + FHoudiniEngineString HoudiniEngineString(StringHandle); + HoudiniEngineString.ToFString(ParmExprString); + } + + // Check if we actually have an expression + // String parameters return true even if they do not have one + bHasExpression = ParmExprString.Len() > 0; + + } + + HoudiniParameter->SetHasExpression(bHasExpression); + HoudiniParameter->SetExpression(ParmExprString); + } + else + { + HoudiniParameter->SetHasExpression(false); + HoudiniParameter->SetExpression(FString()); + } + + // Get parameter tags. + int32 TagCount = HoudiniParameter->GetTagCount(); + for (int32 Idx = 0; Idx < TagCount; ++Idx) + { + HAPI_StringHandle TagNameSH; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagName( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmInfo.id, Idx, &TagNameSH)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); + continue; + } + + FString NameString = TEXT(""); + FHoudiniEngineString::ToFString(TagNameSH, NameString); + if (NameString.IsEmpty()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); + continue; + } + + HAPI_StringHandle TagValueSH; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmInfo.id, TCHAR_TO_ANSI(*NameString), &TagValueSH)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag value: parmId: %d, tag: %s"), ParmInfo.id, *NameString); + } + + FString ValueString = TEXT(""); + FHoudiniEngineString::ToFString(TagValueSH, ValueString); + + HoudiniParameter->GetTags().Add(NameString, ValueString); + } + } + + // + // Update properties specific to parameter classes + switch (ParmType) + { + case EHoudiniParameterType::Button: + { + UHoudiniParameterButton* HoudiniParameterButton = Cast(HoudiniParameter); + if (HoudiniParameterButton && !HoudiniParameterButton->IsPendingKill()) + { + HoudiniParameterButton->SetValueIndex(ParmInfo.intValuesIndex); + } + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + UHoudiniParameterButtonStrip* HoudiniParameterButtonStrip = Cast(HoudiniParameter); + if (HoudiniParameterButtonStrip && !HoudiniParameterButtonStrip->IsPendingKill()) + { + HoudiniParameterButtonStrip->SetValueIndex(ParmInfo.intValuesIndex); + HoudiniParameterButtonStrip->Count = ParmInfo.choiceCount; + } + + if (bFullUpdate) + { + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + ParmChoices.SetNum(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + + HoudiniParameterButtonStrip->InitializeLabels(ParmInfo.choiceCount); + + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ButtonLabel = HoudiniParameterButtonStrip->GetStringLabelAt(ChoiceIdx); + if (ButtonLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ButtonLabel)) + return false; + } + } + + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterButtonStrip->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.choiceCount) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + } + break; + + case EHoudiniParameterType::Color: + { + UHoudiniParameterColor* HoudiniParameterColor = Cast(HoudiniParameter); + if (HoudiniParameterColor && !HoudiniParameterColor->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterColor->SetValueIndex(ParmInfo.floatValuesIndex); + + // Update the Parameter value if we want to + if (bUpdateValue) + { + // Get the actual value for this property. + FLinearColor Color = FLinearColor::White; + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + (float *)&Color.R, ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + + HoudiniParameterColor->SetColorValue(Color); + } + + if (bFullUpdate) + { + // Set the default value at parameter created. + HoudiniParameterColor->SetDefaultValue(); + } + } + } + break; + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* HoudiniParameterRampColor = Cast(HoudiniParameter); + if (HoudiniParameterRampColor && !HoudiniParameterRampColor->IsPendingKill()) + { + HoudiniParameterRampColor->SetInstanceCount(ParmInfo.instanceCount); + HoudiniParameterRampColor->MultiParmInstanceLength = ParmInfo.instanceLength; + } + } + break; + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* HoudiniParameterRampFloat = Cast(HoudiniParameter); + if (HoudiniParameterRampFloat && !HoudiniParameterRampFloat->IsPendingKill()) + { + HoudiniParameterRampFloat->SetInstanceCount(ParmInfo.instanceCount); + HoudiniParameterRampFloat->MultiParmInstanceLength = ParmInfo.instanceLength; + } + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + UHoudiniParameterFile* HoudiniParameterFile = Cast(HoudiniParameter); + if (HoudiniParameterFile && !HoudiniParameterFile->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFile->SetValueIndex(ParmInfo.stringValuesIndex); + + // Update the file filter and read only tag only for full updates + if (bFullUpdate) + { + // Check if we are read-only + bool bIsReadOnly = false; + FString FileChooserTag; + if (FHoudiniParameterTranslator::HapiGetParameterTagValue(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_FILE_READONLY, FileChooserTag)) + { + if (FileChooserTag.Equals(TEXT("read"), ESearchCase::IgnoreCase)) + bIsReadOnly = true; + } + HoudiniParameterFile->SetReadOnly(bIsReadOnly); + + // Update the file type using the typeInfo string. + if (ParmInfo.typeInfoSH > 0) + { + FString Filters; + FHoudiniEngineString HoudiniEngineString(ParmInfo.typeInfoSH); + if (HoudiniEngineString.ToFString(Filters)) + { + if (!Filters.IsEmpty()) + HoudiniParameterFile->SetFileFilters(Filters); + } + } + } + + if (bUpdateValue) + { + // Get the actual values for this property. + TArray< HAPI_StringHandle > StringHandles; + StringHandles.SetNumZeroed(ParmInfo.size); + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), InNodeId, false, + &StringHandles[0], ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + + // Convert HAPI string handles to Unreal strings. + HoudiniParameterFile->SetNumberOfValues(ParmInfo.size); + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + + // Update the parameter value + HoudiniParameterFile->SetValueAt(ValueString, Idx); + } + } + + if (bFullUpdate) + { + HoudiniParameterFile->SetDefaultValues(); + } + } + } + break; + + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* HoudiniParameterFloat = Cast(HoudiniParameter); + if (HoudiniParameterFloat && !HoudiniParameterFloat->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFloat->SetValueIndex(ParmInfo.floatValuesIndex); + + if (bUpdateValue) + { + // Update the parameter's value + HoudiniParameterFloat->SetNumberOfValues(ParmInfo.size); + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterFloat->GetValuesPtr(), + ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + + if (bFullUpdate) + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_FLOAT_LOG) + { + HoudiniParameterFloat->SetIsLogarithmic(true); + } + + // set the default float values. + HoudiniParameterFloat->SetDefaultValues(); + + // Only update Unit, no swap, and Min/Max values when doing a full update + + // Get the parameter's unit from the "unit" tag + FString ParamUnit; + FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); + HoudiniParameterFloat->SetUnit(ParamUnit); + + // Get the parameter's no swap tag (hengine_noswap) + HoudiniParameterFloat->SetNoSwap(HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_NOSWAP)); + + // Set the min and max for this parameter + if (ParmInfo.hasMin) + { + HoudiniParameterFloat->SetHasMin(true); + HoudiniParameterFloat->SetMin(ParmInfo.min); + } + else + { + HoudiniParameterFloat->SetHasMin(false); + HoudiniParameterFloat->SetMin(TNumericLimits::Lowest()); + } + + if (ParmInfo.hasMax) + { + HoudiniParameterFloat->SetHasMax(true); + HoudiniParameterFloat->SetMax(ParmInfo.max); + } + else + { + HoudiniParameterFloat->SetHasMax(false); + HoudiniParameterFloat->SetMax(TNumericLimits::Max()); + } + + // Set min and max for UI for this property. + bool bUsesDefaultMin = false; + if (ParmInfo.hasUIMin) + { + HoudiniParameterFloat->SetHasUIMin(true); + HoudiniParameterFloat->SetUIMin(ParmInfo.UIMin); + } + else if (ParmInfo.hasMin) + { + // If it is not set, use supplied min. + HoudiniParameterFloat->SetUIMin(ParmInfo.min); + } + else + { + // Min value Houdini uses by default. + HoudiniParameterFloat->SetUIMin(HAPI_UNREAL_PARAM_FLOAT_UI_MIN); + bUsesDefaultMin = true; + } + + bool bUsesDefaultMax = false; + if (ParmInfo.hasUIMax) + { + HoudiniParameterFloat->SetHasUIMax(true); + HoudiniParameterFloat->SetUIMax(ParmInfo.UIMax); + } + else if (ParmInfo.hasMax) + { + // If it is not set, use supplied max. + HoudiniParameterFloat->SetUIMax(ParmInfo.max); + } + else + { + // Max value Houdini uses by default. + HoudiniParameterFloat->SetUIMax(HAPI_UNREAL_PARAM_FLOAT_UI_MAX); + bUsesDefaultMax = true; + } + + if (bUsesDefaultMin || bUsesDefaultMax) + { + // If we are using defaults, we can detect some most common parameter names and alter defaults. + FString LocalParameterName = HoudiniParameterFloat->GetParameterName(); + FHoudiniEngineString HoudiniEngineString(ParmInfo.nameSH); + HoudiniEngineString.ToFString(LocalParameterName); + + static const FString ParameterNameTranslate(TEXT(HAPI_UNREAL_PARAM_TRANSLATE)); + static const FString ParameterNameRotate(TEXT(HAPI_UNREAL_PARAM_ROTATE)); + static const FString ParameterNameScale(TEXT(HAPI_UNREAL_PARAM_SCALE)); + static const FString ParameterNamePivot(TEXT(HAPI_UNREAL_PARAM_PIVOT)); + + if (!LocalParameterName.IsEmpty()) + { + if (LocalParameterName.Equals(ParameterNameTranslate) + || LocalParameterName.Equals(ParameterNameScale) + || LocalParameterName.Equals(ParameterNamePivot)) + { + if (bUsesDefaultMin) + { + HoudiniParameterFloat->SetUIMin(-1.0f); + } + if (bUsesDefaultMax) + { + HoudiniParameterFloat->SetUIMax(1.0f); + } + } + else if (LocalParameterName.Equals(ParameterNameRotate)) + { + if (bUsesDefaultMin) + { + HoudiniParameterFloat->SetUIMin(0.0f); + } + if (bUsesDefaultMax) + { + HoudiniParameterFloat->SetUIMax(360.0f); + } + } + } + } + } + } + } + break; + + case EHoudiniParameterType::Folder: + { + UHoudiniParameterFolder* HoudiniParameterFolder = Cast(HoudiniParameter); + if (HoudiniParameterFolder && !HoudiniParameterFolder->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFolder->SetValueIndex(ParmInfo.intValuesIndex); + HoudiniParameterFolder->SetFolderType(GetFolderTypeFromParamInfo(&ParmInfo)); + } + } + break; + + case EHoudiniParameterType::FolderList: + { + UHoudiniParameterFolderList* HoudiniParameterFolderList = Cast(HoudiniParameter); + if (HoudiniParameterFolderList && !HoudiniParameterFolderList->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFolderList->SetValueIndex(ParmInfo.intValuesIndex); + } + } + break; + + case EHoudiniParameterType::Input: + { + // Inputs parameters are just stored, and handled separately by UHoudiniInputs + UHoudiniParameterOperatorPath* HoudiniParameterOperatorPath = Cast(HoudiniParameter); + if (HoudiniParameterOperatorPath && !HoudiniParameterOperatorPath->IsPendingKill()) + { + /* + // DO NOT CREATE A DUPLICATE INPUT HERE! + // Inputs are created by the input translator, and will be tied to this parameter there + UHoudiniInput * NewInput = NewObject< UHoudiniInput >( + HoudiniParameterOperatorPath, + UHoudiniInput::StaticClass()); + + UHoudiniAssetComponent *ParentHAC = Cast(HoudiniParameterOperatorPath->GetOuter()); + + if (!ParentHAC) + return false; + + if (!NewInput || NewInput->IsPendingKill()) + return false; + + // Set the nodeId + NewInput->SetAssetNodeId(ParentHAC->GetAssetId()); + NewInput->SetInputType(EHoudiniInputType::Geometry); + HoudiniParameterOperatorPath->HoudiniInputs.Add(NewInput); + */ + // Set the valueIndex + HoudiniParameterOperatorPath->SetValueIndex(ParmInfo.stringValuesIndex); + } + } + break; + + case EHoudiniParameterType::Int: + { + UHoudiniParameterInt* HoudiniParameterInt = Cast(HoudiniParameter); + if (HoudiniParameterInt && !HoudiniParameterInt->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterInt->SetValueIndex(ParmInfo.intValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + HoudiniParameterInt->SetNumberOfValues(ParmInfo.size); + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterInt->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + + if (bFullUpdate) + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_INT_LOG) + { + HoudiniParameterInt->SetIsLogarithmic(true); + } + + // Set the default int values at created + HoudiniParameterInt->SetDefaultValues(); + // Only update unit and Min/Max values for a full update + + // Get the parameter's unit from the "unit" tag + FString ParamUnit; + FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); + HoudiniParameterInt->SetUnit(ParamUnit); + + // Set the min and max for this parameter + if (ParmInfo.hasMin) + { + HoudiniParameterInt->SetHasMin(true); + HoudiniParameterInt->SetMin((int32)ParmInfo.min); + } + else + { + HoudiniParameterInt->SetHasMin(false); + HoudiniParameterInt->SetMin(TNumericLimits::Lowest()); + } + + if (ParmInfo.hasMax) + { + HoudiniParameterInt->SetHasMax(true); + HoudiniParameterInt->SetMax((int32)ParmInfo.max); + } + else + { + HoudiniParameterInt->SetHasMax(false); + HoudiniParameterInt->SetMax(TNumericLimits::Max()); + } + + // Set min and max for UI for this property. + if (ParmInfo.hasUIMin) + { + HoudiniParameterInt->SetHasUIMin(true); + HoudiniParameterInt->SetUIMin((int32)ParmInfo.UIMin); + } + else if (ParmInfo.hasMin) + { + // If it is not set, use supplied min. + HoudiniParameterInt->SetUIMin((int32)ParmInfo.min); + } + else + { + // Min value Houdini uses by default. + HoudiniParameterInt->SetUIMin(HAPI_UNREAL_PARAM_INT_UI_MIN); + } + + if (ParmInfo.hasUIMax) + { + HoudiniParameterInt->SetHasUIMax(true); + HoudiniParameterInt->SetUIMax((int32)ParmInfo.UIMax); + } + else if (ParmInfo.hasMax) + { + // If it is not set, use supplied max. + HoudiniParameterInt->SetUIMax((int32)ParmInfo.max); + } + else + { + // Max value Houdini uses by default. + HoudiniParameterInt->SetUIMax(HAPI_UNREAL_PARAM_INT_UI_MAX); + } + } + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* HoudiniParameterIntChoice = Cast(HoudiniParameter); + if (HoudiniParameterIntChoice && !HoudiniParameterIntChoice->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterIntChoice->SetValueIndex(ParmInfo.intValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + int32 CurrentIntValue = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &CurrentIntValue, + ParmInfo.intValuesIndex, ParmInfo.size), false); + + // Check the value is valid + if (CurrentIntValue >= ParmInfo.choiceCount) + { + HOUDINI_LOG_WARNING(TEXT("parm '%s' has an invalid value %d, menu tokens are not supported for choice menus"), + *HoudiniParameterIntChoice->GetParameterName(), CurrentIntValue); + CurrentIntValue = 0; + } + + HoudiniParameterIntChoice->SetIntValue(CurrentIntValue); + } + + // Get the choice descriptors + if (bFullUpdate) + { + // Set the default value at created + HoudiniParameterIntChoice->SetDefaultIntValue(); + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + + // Set the array sizes + HoudiniParameterIntChoice->SetNumChoices(ParmInfo.choiceCount); + + bool bMatchedSelectionLabel = false; + int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValue(); + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ChoiceLabel = HoudiniParameterIntChoice->GetStringChoiceLabelAt(ChoiceIdx); + if (ChoiceLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ChoiceLabel)) + return false; + //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); + } + + // Match our string value to the corresponding selection label. + if (ChoiceIdx == CurrentIntValue) + { + HoudiniParameterIntChoice->SetStringValue(*ChoiceLabel); + } + } + } + else if (bUpdateValue) + { + // We still need to match the string value to the label + HoudiniParameterIntChoice->UpdateStringValueFromInt(); + } + } + } + break; + + case EHoudiniParameterType::StringChoice: + { + UHoudiniParameterChoice* HoudiniParameterStringChoice = Cast(HoudiniParameter); + if (HoudiniParameterStringChoice && !HoudiniParameterStringChoice->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterStringChoice->SetValueIndex(ParmInfo.stringValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandle, + ParmInfo.stringValuesIndex, ParmInfo.size), false); + + // Get the string value + FString StringValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + HoudiniEngineString.ToFString(StringValue); + + HoudiniParameterStringChoice->SetStringValue(StringValue); + } + + // Get the choice descriptors + if (bFullUpdate) + { + // Set default value at created. + HoudiniParameterStringChoice->SetDefaultStringValue(); + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + + // Set the array sizes + HoudiniParameterStringChoice->SetNumChoices(ParmInfo.choiceCount); + + bool bMatchedSelectionLabel = false; + FString CurrentStringValue = HoudiniParameterStringChoice->GetStringValue(); + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ChoiceValue = HoudiniParameterStringChoice->GetStringChoiceValueAt(ChoiceIdx); + if (ChoiceValue) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].valueSH); + if (!HoudiniEngineString.ToFString(*ChoiceValue)) + return false; + //StringChoiceValues.Add(TSharedPtr< FString >(ChoiceValue)); + } + + FString * ChoiceLabel = HoudiniParameterStringChoice->GetStringChoiceLabelAt(ChoiceIdx); + if (ChoiceLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ChoiceLabel)) + return false; + //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); + } + + // If this is a string choice list, we need to match name with corresponding selection label. + if (!bMatchedSelectionLabel && ChoiceValue->Equals(CurrentStringValue)) + { + bMatchedSelectionLabel = true; + HoudiniParameterStringChoice->SetIntValue(ChoiceIdx); + } + } + } + else if (bUpdateValue) + { + // We still need to match the string value to the label + HoudiniParameterStringChoice->UpdateIntValueFromString(); + } + } + } + break; + + case EHoudiniParameterType::Label: + { + UHoudiniParameterLabel* HoudiniParameterLabel = Cast(HoudiniParameter); + if (HoudiniParameterLabel && !HoudiniParameterLabel->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_LABEL) + return false; + + // Set the valueIndex + HoudiniParameterLabel->SetValueIndex(ParmInfo.stringValuesIndex); + + // Get the actual value for this property. + TArray StringHandles; + StringHandles.SetNumZeroed(ParmInfo.size); + FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandles[0], + ParmInfo.stringValuesIndex, ParmInfo.size); + + HoudiniParameterLabel->EmptyLabelString(); + + // Convert HAPI string handles to Unreal strings. + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + HoudiniParameterLabel->AddLabelString(ValueString); + } + } + } + break; + + case EHoudiniParameterType::MultiParm: + { + UHoudiniParameterMultiParm* HoudiniParameterMulti = Cast(HoudiniParameter); + if (HoudiniParameterMulti && !HoudiniParameterMulti->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST) + return false; + + // Set the valueIndex + HoudiniParameterMulti->SetValueIndex(ParmInfo.intValuesIndex); + + // Set the multiparm value + int32 MultiParmValue = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &MultiParmValue, ParmInfo.intValuesIndex, 1), false); + + HoudiniParameterMulti->SetValue(MultiParmValue); + HoudiniParameterMulti->MultiParmInstanceCount = ParmInfo.instanceCount; + HoudiniParameterMulti->MultiParmInstanceLength = ParmInfo.instanceLength; + + } + + if (bFullUpdate) + { + HoudiniParameterMulti->SetDefaultInstanceCount(ParmInfo.instanceCount); + } + } + break; + + case EHoudiniParameterType::Separator: + { + UHoudiniParameterSeparator* HoudiniParameterSeparator = Cast(HoudiniParameter); + if (HoudiniParameterSeparator && !HoudiniParameterSeparator->IsPendingKill()) + { + // We can only handle separator type. + if (ParmInfo.type != HAPI_PARMTYPE_SEPARATOR) + return false; + + // Set the valueIndex + HoudiniParameterSeparator->SetValueIndex(ParmInfo.stringValuesIndex); + } + } + break; + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + { + UHoudiniParameterString* HoudiniParameterString = Cast(HoudiniParameter); + if (HoudiniParameterString && !HoudiniParameterString->IsPendingKill()) + { + // We can only handle string type. + if (ParmInfo.type != HAPI_PARMTYPE_STRING && ParmInfo.type != HAPI_PARMTYPE_NODE) + return false; + + // Set the valueIndex + HoudiniParameterString->SetValueIndex(ParmInfo.stringValuesIndex); + + // Stop if we don't want to update the value + if (bUpdateValue) + { + // Get the actual value for this property. + TArray< HAPI_StringHandle > StringHandles; + StringHandles.SetNumZeroed(ParmInfo.size); + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandles[0], + ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + + // Convert HAPI string handles to Unreal strings. + HoudiniParameterString->SetNumberOfValues(ParmInfo.size); + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + HoudiniParameterString->SetValueAt(ValueString, Idx); + } + } + + if (bFullUpdate) + { + // Set default string values on created + HoudiniParameterString->SetDefaultValues(); + // Check if the parameter has the "asset_ref" tag + HoudiniParameterString->SetIsAssetRef( + FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_ASSET_REF)); + } + } + } + break; + + case EHoudiniParameterType::Toggle: + { + UHoudiniParameterToggle* HoudiniParameterToggle = Cast(HoudiniParameter); + if (HoudiniParameterToggle && !HoudiniParameterToggle->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_TOGGLE) + return false; + + // Set the valueIndex + HoudiniParameterToggle->SetValueIndex(ParmInfo.intValuesIndex); + + // Stop if we don't want to update the value + if (bUpdateValue) + { + // Get the actual values for this property. + HoudiniParameterToggle->SetNumberOfValues(ParmInfo.size); + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterToggle->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + } + + if (bFullUpdate) + { + HoudiniParameterToggle->SetDefaultValues(); + } + } + break; + + case EHoudiniParameterType::Invalid: + { + // TODO + } + break; + } + + return true; +} + +bool +FHoudiniParameterTranslator::HapiGetParameterTagValue(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag, FString& TagValue) +{ + // Default + TagValue = FString(); + + // Does the parameter has the tag? + bool HasTag = false; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &HasTag), false); + + if (!HasTag) + return false; + + // Get the tag string value + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &StringHandle), false); + + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(TagValue)) + { + return true; + } + + return false; +} + + +bool +FHoudiniParameterTranslator::HapiGetParameterUnit(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString) +{ + // + OutUnitString = TEXT(""); + + // We're looking for the parameter unit tag + //FString UnitTag = "units"; + + // Get the actual string value. + FString UnitString = TEXT(""); + if (!FHoudiniParameterTranslator::HapiGetParameterTagValue(NodeId, ParmId, "units", UnitString)) + return false; + + // We need to do some replacement in the string here in order to be able to get the + // proper unit type when calling FUnitConversion::UnitFromString(...) after. + + // Per second and per hour are the only "per" unit that unreal recognize + UnitString.ReplaceInline(TEXT("s-1"), TEXT("/s")); + UnitString.ReplaceInline(TEXT("h-1"), TEXT("/h")); + + // Houdini likes to add '1' on all the unit, so we'll remove all of them + // except the '-1' that still needs to remain. + UnitString.ReplaceInline(TEXT("-1"), TEXT("--")); + UnitString.ReplaceInline(TEXT("1"), TEXT("")); + UnitString.ReplaceInline(TEXT("--"), TEXT("-1")); + + OutUnitString = UnitString; + + return true; +} + +bool +FHoudiniParameterTranslator::HapiGetParameterHasTag(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag) +{ + // Does the parameter has the tag we're looking for? + bool HasTag = false; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &HasTag), false); + + return HasTag; +} + + +bool +FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * HAC ) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TMap RampsToRevert; + + for (int32 ParmIdx = 0; ParmIdx < HAC->GetNumParameters(); ParmIdx++) + { + UHoudiniParameter*& CurrentParm = HAC->Parameters[ParmIdx]; + if (!CurrentParm || CurrentParm->IsPendingKill() || !CurrentParm->HasChanged()) + continue; + + bool bSuccess = false; + + if (CurrentParm->IsPendingRevertToDefault()) + { + bSuccess = RevertParameterToDefault(CurrentParm); + + if (CurrentParm->GetParameterType() == EHoudiniParameterType::FloatRamp || + CurrentParm->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + RampsToRevert.Add(CurrentParm->GetParameterName(), CurrentParm); + } + } + else + { + bSuccess = UploadParameterValue(CurrentParm); + } + + + if (bSuccess) + { + CurrentParm->MarkChanged(false); + //CurrentParm->SetNeedsToTriggerUpdate(false); + } + else + { + // Keep this param marked as changed but prevent it from generating updates + CurrentParm->SetNeedsToTriggerUpdate(false); + } + } + + FHoudiniParameterTranslator::RevertRampParameters(RampsToRevert, HAC->GetAssetId()); + + return true; +} + +bool +FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return false; + + switch (InParam->GetParameterType()) + { + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* FloatParam = Cast(InParam); + if (!FloatParam || FloatParam->IsPendingKill()) + { + return false; + } + + float* DataPtr = FloatParam->GetValuesPtr(); + if (!DataPtr) + { + return false; + } + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + FloatParam->GetNodeId(), DataPtr, FloatParam->GetValueIndex(), FloatParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::Int: + { + UHoudiniParameterInt* IntParam = Cast(InParam); + if (!IntParam || IntParam->IsPendingKill()) + { + return false; + } + + int32* DataPtr = IntParam->GetValuesPtr(); + if (!DataPtr) + { + return false; + } + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + IntParam->GetNodeId(), DataPtr, IntParam->GetValueIndex(), IntParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::String: + { + UHoudiniParameterString* StringParam = Cast(InParam); + if (!StringParam || StringParam->IsPendingKill()) + { + return false; + } + + int32 NumValues = StringParam->GetNumberOfValues(); + if (NumValues <= 0) + { + return false; + } + + for (int32 Idx = 0; Idx < NumValues; Idx++) + { + std::string ConvertedString = TCHAR_TO_UTF8(*(StringParam->GetValueAt(Idx))); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + StringParam->GetNodeId(), ConvertedString.c_str(), StringParam->GetParmId(), Idx), false); + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* ChoiceParam = Cast(InParam); + if (!ChoiceParam || ChoiceParam->IsPendingKill()) + return false; + + // Set the parameter's int value. + int32 IntValue = ChoiceParam->GetIntValue(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); + } + break; + case EHoudiniParameterType::StringChoice: + { + UHoudiniParameterChoice* ChoiceParam = Cast(InParam); + if (!ChoiceParam || ChoiceParam->IsPendingKill()) + { + return false; + } + + if (ChoiceParam->IsStringChoice()) + { + // Set the parameter's string value. + std::string ConvertedString = TCHAR_TO_UTF8(*(ChoiceParam->GetStringValue())); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), ConvertedString.c_str(), ChoiceParam->GetParmId(), 0), false); + } + else + { + // Set the parameter's int value. + int32 IntValue = ChoiceParam->GetIntValue(); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); + } + } + break; + + case EHoudiniParameterType::Color: + { + UHoudiniParameterColor* ColorParam = Cast(InParam); + if (!ColorParam || ColorParam->IsPendingKill()) + return false; + + bool bHasAlpha = ColorParam->GetTupleSize() == 4 ? true : false; + FLinearColor Color = ColorParam->GetColorValue(); + + // Set the color value + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + ColorParam->GetNodeId(), + (float*)(&Color.R), ColorParam->GetValueIndex(), bHasAlpha ? 4 : 3), false); + + } + break; + + case EHoudiniParameterType::Button: + { + UHoudiniParameterButton* ButtonParam = Cast(InParam); + if (!ButtonParam) + return false; + + TArray DataArray; + DataArray.Add(1); + + // Set the button parameter value to 1, (setting button param to any value will call the callback function.) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ButtonParam->GetNodeId(), + DataArray.GetData(), + ButtonParam->GetValueIndex(), 1), false); + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + UHoudiniParameterButtonStrip* ButtonStripParam = Cast(InParam); + if (!ButtonStripParam) + return false; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ButtonStripParam->GetNodeId(), + ButtonStripParam->Values.GetData(), + ButtonStripParam->GetValueIndex(), ButtonStripParam->Count), false); + } + break; + + case EHoudiniParameterType::Toggle: + { + UHoudiniParameterToggle* ToggleParam = Cast(InParam); + if (!ToggleParam) + return false; + + // Set the toggle parameter values. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ToggleParam->GetNodeId(), + ToggleParam->GetValuesPtr(), + ToggleParam->GetValueIndex(), ToggleParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + UHoudiniParameterFile* FileParam = Cast(InParam); + + if (!UploadDirectoryPath(FileParam)) + return false; + } + break; + + case EHoudiniParameterType::MultiParm: + { + if (!UploadMultiParmValues(InParam)) + return false; + } + + break; + + case EHoudiniParameterType::FloatRamp: + { + if (!UploadRampParameter(InParam)) + return false; + } + break; + + case EHoudiniParameterType::ColorRamp: + { + if (!UploadRampParameter(InParam)) + return false; + } + break; + + default: + { + // TODO: implement other parameter types! + return false; + } + break; + } + + // The parameter is no longer considered as changed + InParam->MarkChanged(false); + + return true; +} + +bool +FHoudiniParameterTranslator::RevertParameterToDefault(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return false; + + if (!InParam->IsPendingRevertToDefault()) + return false; + + TArray TupleToRevert; + InParam->GetTuplePendingRevertToDefault(TupleToRevert); + if (TupleToRevert.Num() <= 0) + return false; + + FString ParameterName = InParam->GetParameterName(); + + bool bReverted = true; + for (auto CurrentIdx : TupleToRevert ) + { + if (!TupleToRevert.IsValidIndex(CurrentIdx)) + { + // revert the whole parameter to its default value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefaults( + FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName))) + { + HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s to its default value."), *ParameterName); + bReverted = false; + } + } + else + { + // revert a tuple to its default value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefault( + FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName), CurrentIdx)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s - %d to its default value."), *ParameterName, CurrentIdx); + bReverted = false; + } + } + } + + if (!bReverted) + return false; + + // The parameter no longer needs to be reverted + InParam->MarkDefault(true); + + return true; +} + +EHoudiniFolderParameterType +FHoudiniParameterTranslator::GetFolderTypeFromParamInfo(const HAPI_ParmInfo* ParamInfo) +{ + EHoudiniFolderParameterType Type = EHoudiniFolderParameterType::Invalid; + + switch (ParamInfo->scriptType) + { + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPSIMPLE: + Type = EHoudiniFolderParameterType::Simple; + break; + + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPCOLLAPSIBLE: + Type = EHoudiniFolderParameterType::Collapsible; + break; + + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUP: + Type = EHoudiniFolderParameterType::Tabs; + break; + + // Treat Radio folders as tabs for now + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPRADIO: + Type = EHoudiniFolderParameterType::Radio; + break; + + default: + Type = EHoudiniFolderParameterType::Other; + break; + + } + + return Type; + +} + +bool +FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InParam, TArray &OldParams, const int32& InAssetId, const int32 CurrentIndex) +{ + + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + + if (!MultiParam || MultiParam->IsPendingKill()) + return false; + + UHoudiniParameterRampFloat* FloatRampParameter = nullptr; + UHoudiniParameterRampColor* ColorRampParameter = nullptr; + + if (MultiParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + FloatRampParameter = Cast(MultiParam); + + else if (MultiParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + ColorRampParameter = Cast(MultiParam); + + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); + + HAPI_NodeId NodeId = AssetInfo.nodeId; + + int32 Idx = 0; + int32 InstanceCount = -1; + HAPI_ParmId ParmId = -1; + TArray ParmInfos; + if (!GetMultiParmInstanceStartIdx(AssetInfo, MultiParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + + for (int n = 0; n < InstanceCount - MultiParam->GetInstanceCount(); ++n) + { + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset); + } + + for (int n = 0; n < MultiParam->GetInstanceCount() - InstanceCount; ++n) + { + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset); + } + + + // Sync nested multi-params recursively + for (int32 ParamIdx = CurrentIndex; ParamIdx < OldParams.Num(); ++ParamIdx) + { + UHoudiniParameter* NextParm = OldParams[ParamIdx]; + if (NextParm->GetParentParmId() == ParmId) + { + if (NextParm->GetParameterType() == EHoudiniParameterType::MultiParm) + { + SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, ParamIdx); + } + } + } + + + // The multiparm is a ramp, Get the param infos again, since the number of param instances is changed + if (!GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + // Step 3: Set values of the inserted points + if (FloatRampParameter) + { + for (auto & Point : FloatRampParameter->Points) + { + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); + + // 2: update float value at param Idx + 1 + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Value), ParmInfos[Idx + 1].floatValuesIndex, 1); + + // 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Point->Interpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + else if (ColorRampParameter) + { + for (auto& Point : ColorRampParameter->Points) + { + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); + + // 2: update color value at param Idx + 1 + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, (float*)(&Point->Value.R), ParmInfos[Idx + 1].floatValuesIndex, 3); + + // 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Point->Interpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + + + return true; +} + + +bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam) +{ + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + if (!MultiParam || MultiParam->IsPendingKill()) + return false; + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InParam->GetOuter()); + if (!HoudiniAssetComponent) + return false; + + int32 InsertIndexStart = -1; + UHoudiniParameterRampFloat* RampFloatParam = Cast(InParam); + UHoudiniParameterRampColor* RampColorParam = Cast(InParam); + + TArray *Events = nullptr; + if (RampFloatParam) + { + Events = &(RampFloatParam->ModificationEvents); + InsertIndexStart = RampFloatParam->GetInstanceCount(); + } + else if (RampColorParam) + { + Events = &(RampColorParam->ModificationEvents); + InsertIndexStart = RampColorParam->GetInstanceCount(); + } + else + return false; + + // Handle All Events + Events->Sort([](const UHoudiniParameterRampModificationEvent& A, const UHoudiniParameterRampModificationEvent& B) + { + return A.DeleteInstanceIndex > B.DeleteInstanceIndex; + }); + + + // Step 1: Handle all delete events first + for (auto& Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsDeleteEvent()) + continue; + + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Event->DeleteInstanceIndex + MultiParam->InstanceStartOffset); + + InsertIndexStart -= 1; + } + + int32 InsertIndex = InsertIndexStart; + + + // Step 2: Handle all insert events + for (auto& Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsInsertEvent()) + continue; + + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), InsertIndex + MultiParam->InstanceStartOffset); + + InsertIndex += 1; + } + + // Step 3: Set inserted parameter values (only if there are instances inserted) + if (InsertIndex > InsertIndexStart) + { + if (HoudiniAssetComponent) + { + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), HoudiniAssetComponent->AssetId, &AssetInfo), false); + + int32 Idx = 0; + int32 InstanceCount = -1; + HAPI_ParmId ParmId = -1; + TArray< HAPI_ParmInfo > ParmInfos; + + if (!FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), + Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + if (InstanceCount < 0) + return false; + + // Instance count doesn't match, + if (InsertIndex != InstanceCount) + return false; + + + // Starting index of parameters which just inserted + Idx += 3 * InsertIndexStart; + + + for (auto & Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsInsertEvent()) + continue; + + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &(Event->InsertPosition), ParmInfos[Idx].floatValuesIndex, 1); + + // step 2: update value at param Idx + 1 + if (Event->IsFloatRampEvent()) + { + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &(Event->InsertFloat), ParmInfos[Idx + 1].floatValuesIndex, 1); + } + else + { + // color value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, (float*)(&Event->InsertColor.R), ParmInfos[Idx + 1].floatValuesIndex, 3); + } + + // step 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Event->InsertInterpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + } + + // Step 4: clear all events + Events->Empty(); + + return true; +} + +bool FHoudiniParameterTranslator::UploadMultiParmValues(UHoudiniParameter* InParam) +{ + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + if (!MultiParam) + return false; + + TArray &LastModificationArray = MultiParam->MultiParmInstanceLastModifyArray; + + int32 Size = MultiParam->MultiParmInstanceLastModifyArray.Num(); + + for (int32 Index = 0; Index < Size; ++Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Inserted) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) + return false; + + } + } + + for (int32 Index = Size - 1; Index >= 0; --Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) + return false; + } + } + + // Remove all removal events. + for (int32 Index = Size - 1; Index >= 0; --Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) + LastModificationArray.RemoveAt(Index); + } + + // The last modification array is resized. + Size = LastModificationArray.Num(); + + // Reset the last modification array + for (int32 Itr =Size - 1; Itr >= 0; --Itr) + { + LastModificationArray[Itr] = EHoudiniMultiParmModificationType::None; + } + + MultiParam->MultiParmInstanceCount = Size; + + return true; +} + +bool +FHoudiniParameterTranslator::UploadDirectoryPath(UHoudiniParameterFile* InParam) +{ + if(!InParam) + return false; + + for (int32 Index = 0; Index < InParam->GetNumValues(); ++Index) + { + std::string ConvertedString = TCHAR_TO_UTF8(*(InParam->GetValueAt(Index))); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue(FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), ConvertedString.c_str(), InParam->GetParmId(), Index), false); + } + + return true; +} + +bool +FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, + int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos) +{ + // Reset outputs + OutStartIdx = 0; + OutInstanceCount = -1; + OutParmId = -1; + OutParmInfos.Empty(); + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &NodeInfo), false); + + OutParmInfos.SetNumUninitialized(NodeInfo.parmCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &OutParmInfos[0], 0, NodeInfo.parmCount), false); + + + while (OutStartIdx < OutParmInfos.Num()) + { + FString ParmNameBuffer; + FHoudiniEngineString(OutParmInfos[OutStartIdx].nameSH).ToFString(ParmNameBuffer); + + if (ParmNameBuffer == InParmName) + { + OutParmId = OutParmInfos[OutStartIdx].id; + OutInstanceCount = OutParmInfos[OutStartIdx].instanceCount; + break; + } + + OutStartIdx += 1; + } + + // Start index of the ramp children parameters + OutStartIdx += 1; + + return true; +} + +bool +FHoudiniParameterTranslator::RevertRampParameters(TMap & InRampParams, const int32 & AssetId) +{ + if (InRampParams.Num() <= 0) + return true; + + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); + + // Retrieve all the parameter infos. + TArray< HAPI_ParmInfo > ParmInfos; + ParmInfos.SetNumUninitialized(NodeInfo.parmCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); + + int32 ParamIdx = 0; + while (ParamIdx < ParmInfos.Num()) + { + const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; + FString ParmName; + FHoudiniEngineString(ParmInfo.nameSH).ToFString(ParmName); + + if (InRampParams.Contains(ParmName)) + { + if (!InRampParams[ParmName]) + { + ParamIdx += 1; + continue; + } + + if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat * FloatRamp = Cast(InRampParams[ParmName]); + if (!FloatRamp) + { + ParamIdx += 1; + continue; + } + + if (ParmInfo.instanceCount != FloatRamp->NumDefaultPoints) + { + ParamIdx += 1; + continue; + } + + for (int32 PtIdx = 0; PtIdx < FloatRamp->NumDefaultPoints; ++PtIdx) + { + const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; + const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; + const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultValues.GetData() + PtIdx, ValueParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); + + ParamIdx += 3; + } + } + + if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor * ColorRamp = Cast(InRampParams[ParmName]); + if (!ColorRamp) + { + ParamIdx += 1; + continue; + } + + if (ParmInfo.instanceCount != ColorRamp->NumDefaultPoints) + { + ParamIdx += 1; + continue; + } + + for (int32 PtIdx = 0; PtIdx < ColorRamp->NumDefaultPoints; ++PtIdx) + { + const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; + const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; + const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, ColorRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, (float*)(&ColorRamp->DefaultValues[PtIdx].R), ValueParmInfo.floatValuesIndex, 3); + + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, ColorRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); + + ParamIdx += 3; + } + } + } + + ParamIdx += 1; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h index 203372a51..534421fc6 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h @@ -1,148 +1,148 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -class UHoudiniAssetComponent; -class UHoudiniParameter; -class UHoudiniParameterFile; - -enum class EHoudiniFolderParameterType : uint8; -enum class EHoudiniParameterType : uint8; - -struct HOUDINIENGINE_API FHoudiniParameterTranslator -{ - // - static bool UpdateParameters(UHoudiniAssetComponent* HAC); - - static bool OnPreCookParameters(UHoudiniAssetComponent* HAC); - - // - static bool UpdateLoadedParameters(UHoudiniAssetComponent* HAC); - - // - static bool UploadChangedParameters(UHoudiniAssetComponent* HAC); - - // - static bool UploadParameterValue(UHoudiniParameter* InParam); - - // - static bool UploadMultiParmValues(UHoudiniParameter* InParam); - - // - static bool UploadRampParameter(UHoudiniParameter* InParam); - - // - static bool UploadDirectoryPath(UHoudiniParameterFile* InParam); - - // - static bool RevertParameterToDefault(UHoudiniParameter* InParam); - - // - static bool SyncMultiParmValuesAtLoad(UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const int32 Idx); - - // - static bool GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, - int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos); - - /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. - @AssetId: Id of the digital asset - @PrimaryObject: Object to use for transactions and as Outer for new top-level parameters - @CurrentParameters: pre: current & post: invalid parameters - @NewParameters: new params added to this - - On Return: CurrentParameters are the old parameters that are no longer valid, - NewParameters are new and re-used parameters. - */ - static bool BuildAllParameters( - const HAPI_NodeId& AssetId, - class UObject* OuterObject, - TArray& CurrentParameters, - TArray& NewParameters, - const bool& bUpdateValues, - const bool& InForceFullUpdate); - - // Parameter creation - static UHoudiniParameter * CreateTypedParameter( - class UObject * Outer, - const EHoudiniParameterType& ParmType, - const FString& ParmName ); - - // Parameter update - // bFullUpdate should be set to false after a minor update (change/recook) of the parameter - // and set to true when creating a new parameter - // bUpdateValue should be set to false when updating loaded parameters - // as the internal parameter's value from HAPI - static bool UpdateParameterFromInfo( - UHoudiniParameter * HoudiniParameter, - const HAPI_NodeId& InNodeId, - const HAPI_ParmInfo& ParmInfo, - const bool& bFullUpdate = true, - const bool& bUpdateValue = true); - - static UClass* GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo); - - static void GetParmTypeFromParmInfo( - const HAPI_ParmInfo& ParmInfo, - EHoudiniParameterType& ParmType); - - static bool CheckParameterTypeAndClassMatch( - UHoudiniParameter* Parameter, - const EHoudiniParameterType& ParmType); - - /* - static bool CheckParameterClassAndInfoMatch( - UHoudiniParameter* Parameter, - const HAPI_ParmInfo& ParmInfo ); - */ - - // HAPI: Get a parameter's tag value. - static bool HapiGetParameterTagValue( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - const FString& Tag, - FString& TagValue); - - // HAPI: Get a parameter's unit. - static bool HapiGetParameterUnit( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - FString& OutUnitString ); - - // HAPI: Indicates if a parameter has a given tag - static bool HapiGetParameterHasTag( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - const FString& Tag); - - // Get folder parameter type from HAPI_ParmInfo struct - static EHoudiniFolderParameterType GetFolderTypeFromParamInfo( - const HAPI_ParmInfo* ParamInfo); - - static bool RevertRampParameters(TMap & InRampParams, const int32 & AssetId); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +class UHoudiniAssetComponent; +class UHoudiniParameter; +class UHoudiniParameterFile; + +enum class EHoudiniFolderParameterType : uint8; +enum class EHoudiniParameterType : uint8; + +struct HOUDINIENGINE_API FHoudiniParameterTranslator +{ + // + static bool UpdateParameters(UHoudiniAssetComponent* HAC); + + static bool OnPreCookParameters(UHoudiniAssetComponent* HAC); + + // + static bool UpdateLoadedParameters(UHoudiniAssetComponent* HAC); + + // + static bool UploadChangedParameters(UHoudiniAssetComponent* HAC); + + // + static bool UploadParameterValue(UHoudiniParameter* InParam); + + // + static bool UploadMultiParmValues(UHoudiniParameter* InParam); + + // + static bool UploadRampParameter(UHoudiniParameter* InParam); + + // + static bool UploadDirectoryPath(UHoudiniParameterFile* InParam); + + // + static bool RevertParameterToDefault(UHoudiniParameter* InParam); + + // + static bool SyncMultiParmValuesAtLoad(UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const int32 Idx); + + // + static bool GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, + int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos); + + /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. + @AssetId: Id of the digital asset + @PrimaryObject: Object to use for transactions and as Outer for new top-level parameters + @CurrentParameters: pre: current & post: invalid parameters + @NewParameters: new params added to this + + On Return: CurrentParameters are the old parameters that are no longer valid, + NewParameters are new and re-used parameters. + */ + static bool BuildAllParameters( + const HAPI_NodeId& AssetId, + class UObject* OuterObject, + TArray& CurrentParameters, + TArray& NewParameters, + const bool& bUpdateValues, + const bool& InForceFullUpdate); + + // Parameter creation + static UHoudiniParameter * CreateTypedParameter( + class UObject * Outer, + const EHoudiniParameterType& ParmType, + const FString& ParmName ); + + // Parameter update + // bFullUpdate should be set to false after a minor update (change/recook) of the parameter + // and set to true when creating a new parameter + // bUpdateValue should be set to false when updating loaded parameters + // as the internal parameter's value from HAPI + static bool UpdateParameterFromInfo( + UHoudiniParameter * HoudiniParameter, + const HAPI_NodeId& InNodeId, + const HAPI_ParmInfo& ParmInfo, + const bool& bFullUpdate = true, + const bool& bUpdateValue = true); + + static UClass* GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo); + + static void GetParmTypeFromParmInfo( + const HAPI_ParmInfo& ParmInfo, + EHoudiniParameterType& ParmType); + + static bool CheckParameterTypeAndClassMatch( + UHoudiniParameter* Parameter, + const EHoudiniParameterType& ParmType); + + /* + static bool CheckParameterClassAndInfoMatch( + UHoudiniParameter* Parameter, + const HAPI_ParmInfo& ParmInfo ); + */ + + // HAPI: Get a parameter's tag value. + static bool HapiGetParameterTagValue( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + const FString& Tag, + FString& TagValue); + + // HAPI: Get a parameter's unit. + static bool HapiGetParameterUnit( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + FString& OutUnitString ); + + // HAPI: Indicates if a parameter has a given tag + static bool HapiGetParameterHasTag( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + const FString& Tag); + + // Get folder parameter type from HAPI_ParmInfo struct + static EHoudiniFolderParameterType GetFolderTypeFromParamInfo( + const HAPI_ParmInfo* ParamInfo); + + static bool RevertRampParameters(TMap & InRampParams, const int32 & AssetId); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp index 11c1b8b61..9a70ac1a2 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp @@ -1,1610 +1,1610 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" - -#include "HoudiniGeoPartObject.h" -#include "Components/SplineComponent.h" - -#include "EditorViewportClient.h" -#include "Engine/Selection.h" - -#include "HoudiniEnginePrivatePCH.h" - -void -FHoudiniSplineTranslator::ExtractStringPositions(const FString& Positions, TArray& OutPositions) -{ - TArray< FString > PointStrings; - static const TCHAR * PositionSeparators[] = - { - TEXT(" "), - TEXT(","), - }; - - int32 NumCoords = Positions.ParseIntoArray(PointStrings, PositionSeparators, 2); - OutPositions.SetNum(NumCoords / 3); - for (int32 OutIndex = 0; OutIndex < OutPositions.Num(); OutIndex++) - { - const int32& CoordIndex = OutIndex * 3; - OutPositions[OutIndex].X = FCString::Atof(*(PointStrings[CoordIndex + 0])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutPositions[OutIndex].Y = FCString::Atof(*(PointStrings[CoordIndex + 2])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutPositions[OutIndex].Z = FCString::Atof(*(PointStrings[CoordIndex + 1])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } -} - -void -FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData) -{ - OutVectorData.SetNum(InRawData.Num() / 3); - - for (int32 OutIndex = 0; OutIndex < OutVectorData.Num(); OutIndex++) - { - const int32& InIndex = OutIndex * 3; - OutVectorData[OutIndex].X = InRawData[InIndex + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutVectorData[OutIndex].Y = InRawData[InIndex + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutVectorData[OutIndex].Z = InRawData[InIndex + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } -} - -void -FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts) -{ - OutVectorData.SetNum(CurveCounts.Num()); - - int32 TotalNumPoints = 0; - for (const int32 & NextCount : CurveCounts) - TotalNumPoints += NextCount; - - // Do not fill the output array, if the total number of points does not match - if (InRawData.Num() < TotalNumPoints * 3) - return; - - - int32 Itr = 0; - - for (int32 n = 0; n < CurveCounts.Num(); ++n) - { - TArray & NextVectorDataArray = OutVectorData[n]; - NextVectorDataArray.SetNumZeroed(CurveCounts[n]); - - for (int32 PtIdx = 0; PtIdx < CurveCounts[n]; ++PtIdx) - { - if (Itr + 2 >= InRawData.Num()) - return; - - NextVectorDataArray[PtIdx].X = InRawData[Itr] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - NextVectorDataArray[PtIdx].Y = InRawData[Itr + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - NextVectorDataArray[PtIdx].Z = InRawData[Itr + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - - Itr += 3; - } - } -} -void -FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC) -{ - for (UHoudiniInput * NextInput : HAC->Inputs) - UpdateHoudiniInputCurves(NextInput); -} - -void -FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniInput* Input) -{ - if (!Input || Input->GetInputType() != EHoudiniInputType::Curve) - return; - - TArray *InputObjectArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!InputObjectArray) - return; - - for (UHoudiniInputObject * NextInputObject : *InputObjectArray) - { - UHoudiniInputHoudiniSplineComponent * HoudiniSplineInput = Cast(NextInputObject); - if (!HoudiniSplineInput) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInput->GetCurveComponent(); - FHoudiniSplineTranslator::UpdateHoudiniCurve(HoudiniSplineComponent); - } -} - -bool -FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent) -{ - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return false; - - int32 CurveNode_id = HoudiniSplineComponent->GetNodeId(); - if (CurveNode_id < 0) - return false; - - bool Success = true; - FString CurvePointsString = FString(); - int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; - int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; - int32 CurveClosed = 0; - int32 CurveReversed = 0; - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsString( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); - - - HoudiniSplineComponent->SetCurveType((EHoudiniCurveType)CurveTypeValue); - HoudiniSplineComponent->SetCurveMethod((EHoudiniCurveMethod)CurveMethodValue); - HoudiniSplineComponent->SetClosedCurve(CurveClosed == 1); - HoudiniSplineComponent->SetReversed(CurveReversed == 1); - - // We need to get the NodeInfo to get the parent id - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurveNode_id, &NodeInfo), false); - - TArray< float > RefinedCurvePositions; - HAPI_AttributeInfo AttributeRefinedCurvePositions; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); - Success &= FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); - - // Process coords string and extract positions. - TArray CurvePoints; - FHoudiniSplineTranslator::ExtractStringPositions(CurvePointsString, CurvePoints); - - TArray CurveDisplayPoints; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurveDisplayPoints); - - // build curve points for editable curves. - if (HoudiniSplineComponent->CurvePoints.Num() < CurvePoints.Num()) - { - HoudiniSplineComponent->CurvePoints.Empty(); - for (FVector NextPos : CurvePoints) - { - FTransform NextTrans = FTransform::Identity; - NextTrans.SetLocation(NextPos); - HoudiniSplineComponent->CurvePoints.Add(NextTrans); - } - } - - // Update the display point on the curve - HoudiniSplineComponent->Construct(CurveDisplayPoints); - - HoudiniSplineComponent->MarkChanged(false); - - return Success; -} - - -bool -FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent) -{ - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return true; - - TArray PositionArray; - TArray RotationArray; - TArray Scales3dArray; - TArray UniformScaleArray; - for (FTransform & NextTransform : HoudiniSplineComponent->CurvePoints) - { - PositionArray.Add(NextTransform.GetLocation()); - RotationArray.Add(NextTransform.GetRotation()); - Scales3dArray.Add(NextTransform.GetScale3D()); - UniformScaleArray.Add(1.f); - } - - HAPI_NodeId CurveNode_id = HoudiniSplineComponent->GetNodeId(); - FTransform ParentTransform = HoudiniSplineComponent->GetComponentTransform(); - - FString InputNodeNameString = HoudiniSplineComponent->GetName(); - UHoudiniInputHoudiniSplineComponent* InputObject = Cast(HoudiniSplineComponent->GetOuter()); - if (InputObject) - { - UHoudiniInput* Input = Cast(InputObject->GetOuter()); - if (Input) - { - InputNodeNameString = Input->GetNodeBaseName(); - } - } - InputNodeNameString += TEXT("_curve"); - - bool Success = FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( - CurveNode_id, - InputNodeNameString, - &PositionArray, - &RotationArray, - &Scales3dArray, - HoudiniSplineComponent->GetCurveType(), - HoudiniSplineComponent->GetCurveMethod(), - HoudiniSplineComponent->IsClosedCurve(), - HoudiniSplineComponent->IsReversed(), - false, - ParentTransform); - - HoudiniSplineComponent->SetNodeId(CurveNode_id); - Success &= UpdateHoudiniCurve(HoudiniSplineComponent); - - return Success; -} - -bool -FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent) -{ - if (!SplineComponent || SplineComponent->IsPendingKill()) - return true; - - bool Success = HapiUpdateNodeForHoudiniSplineComponent(SplineComponent); - - return Success; -} - -bool -FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( - HAPI_NodeId& CurveNodeId, - const FString& InputNodeName, - TArray* Positions, - TArray* Rotations, - TArray* Scales3d, - EHoudiniCurveType InCurveType, - EHoudiniCurveMethod InCurveMethod, - const bool& InClosed, - const bool& InReversed, - const bool& InForceClose, - const FTransform& ParentTransform ) -{ -#if WITH_EDITOR - // Positions are required - if (!Positions) - return false; - - // We also need a valid host asset and 2 points to make a curve - int32 NumberOfCVs = Positions->Num(); - if (NumberOfCVs < 2) - return false; - - // Check if connected asset id is valid, if it is not, we need to create an input asset. - if (CurveNodeId < 0) - { - HAPI_NodeId NodeId = -1; - // Create the curve SOP Node - if (!FHoudiniSplineTranslator::HapiCreateCurveInputNode(NodeId, InputNodeName)) - return false; - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NodeId)) - return false; - - // We now have a valid id. - CurveNodeId = NodeId; - } - else - { - // We have to revert the Geo to its original state so we can use the Curve SOP: - // adding parameters to the Curve SOP locked it, preventing its parameters (type, method, isClosed) from working - FHoudiniApi::RevertGeo(FHoudiniEngine::Get().GetSession(), CurveNodeId); - } - - // - // In order to be able to add rotations and scale attributes to the curve SOP, we need to cook it twice: - // - // - First, we send the positions string to it, and cook it without refinement. - // this will allow us to get the proper curve CVs, part attributes and curve info to create the desired curve. - // - // - We then need to send back all the info extracted from the curve SOP to it, and add the rotation - // and scale attributes to it. This will lock the curve SOP, and prevent the curve type and method - // parameters from functioning properly (hence why we needed the first cook to set that up) - // - - // Set the curve type and curve method parameters for the curve node - int32 CurveTypeValue = (int32)InCurveType; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - - int32 CurveMethodValue = (int32)InCurveMethod; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - - int32 CurveClosed = InClosed ? 1 : 0; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - - int32 CurveReversed = InReversed ? 1 : 0; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); - - // Reading the curve parameters - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); - - if (InForceClose) - { - // We need to update the closed parameter - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); - - CurveClosed = 1; - } - - // For closed NURBS (CVs and Breakpoints), we have to close the curve manually, by duplicating its last point - // in order to be able to set the rotations and scales attributes properly. - bool bCloseCurveManually = false; - if (CurveClosed && (CurveTypeValue == HAPI_CURVETYPE_NURBS) && (CurveMethodValue != 2)) - { - // The curve is not closed anymore - if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 0)) - { - bCloseCurveManually = true; - - // Duplicating the first point to the end of the curve - // This needs to be done before sending the position string - FVector pos = (*Positions)[0]; - Positions->Add(pos); - - CurveClosed = false; - } - } - - // Creating the position string - FString PositionString = TEXT(""); - FHoudiniSplineTranslator::CreatePositionsString(*Positions, PositionString); - - // Get param id for the PositionString and modify it - HAPI_ParmId ParmId = -1; - if (FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId) != HAPI_RESULT_SUCCESS) - { - return false; - } - - std::string ConvertedString = TCHAR_TO_UTF8(*PositionString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - ConvertedString.c_str(), ParmId, 0), false); - - // If we don't want to add rotations or scale attributes to the curve, - // we can just cook the node normally and stop here. - bool bAddRotations = (Rotations != nullptr); - bool bAddScales3d = (Scales3d != nullptr); - if (!bAddRotations && !bAddScales3d) - { - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); - */ - - // Cook the node, no need to wait for completion - return FHoudiniEngineUtils::HapiCookNode(CurveNodeId, nullptr, false); - } - - // Setting up the first cook, without the curve refinement - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - CookOptions.maxVerticesPerPrimitive = -1; - CookOptions.refineCurveToLinear = false; - if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, true)) - return false; - - // We can now read back the Part infos from the cooked curve. - HAPI_PartInfo PartInfos; - FHoudiniApi::PartInfo_Init(&PartInfos); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurveNodeId, 0, &PartInfos), false); - - // - // Depending on the curve type and method, additionnal control points might have been created. - // We now have to interpolate the rotations and scale attributes for these. - // - - // Lambda function that interpolates rotation, scale and uniform scales values - // between two points using fCoeff as a weight, and insert the interpolated value at nInsertIndex - auto InterpolateRotScaleUScale = [&](const int32& nIndex1, const int32& nIndex2, const float& fCoeff, const int32& nInsertIndex) - { - if (Rotations && Rotations->IsValidIndex(nIndex1) && Rotations->IsValidIndex(nIndex2)) - { - FQuat interpolation = FQuat::Slerp((*Rotations)[nIndex1], (*Rotations)[nIndex2], fCoeff); - if (Rotations->IsValidIndex(nInsertIndex)) - Rotations->Insert(interpolation, nInsertIndex); - else - Rotations->Add(interpolation); - } - - if (Scales3d && Scales3d->IsValidIndex(nIndex1) && Scales3d->IsValidIndex(nIndex2)) - { - FVector interpolation = fCoeff * (*Scales3d)[nIndex1] + (1.0f - fCoeff) * (*Scales3d)[nIndex2]; - if (Scales3d->IsValidIndex(nInsertIndex)) - Scales3d->Insert(interpolation, nInsertIndex); - else - Scales3d->Add(interpolation); - } - }; - - // Lambda function that duplicates rotation and scale values - // at nIndex and insert/adds it at nInsertIndex - auto DuplicateRotScale = [&](const int32& nIndex, const int32& nInsertIndex) - { - if (Rotations && Rotations->IsValidIndex(nIndex)) - { - FQuat value = (*Rotations)[nIndex]; - if (Rotations->IsValidIndex(nInsertIndex)) - Rotations->Insert(value, nInsertIndex); - else - Rotations->Add(value); - } - - if (Scales3d && Scales3d->IsValidIndex(nIndex)) - { - FVector value = (*Scales3d)[nIndex]; - if (Scales3d->IsValidIndex(nInsertIndex)) - Scales3d->Insert(value, nInsertIndex); - else - Scales3d->Add(value); - } - }; - - // Do we want to close the curve by ourselves? - if (bCloseCurveManually) - { - // We need to duplicate the info of the first point to the last - DuplicateRotScale(0, NumberOfCVs++); - - // We need to update the closed parameter - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); - } - - // INTERPOLATION - if (CurveTypeValue == HAPI_CURVETYPE_NURBS) - { - // Closed NURBS have additional points reproducing the first ones - if (InClosed) - { - // Only the first one if the method is freehand ... - DuplicateRotScale(0, NumberOfCVs++); - - if (CurveMethodValue != 2) - { - // ... but also the 2nd and 3rd if the method is CVs or Breakpoints. - DuplicateRotScale(1, NumberOfCVs++); - DuplicateRotScale(2, NumberOfCVs++); - } - } - else if (CurveMethodValue == 1) - { - // Open NURBS have 2 new points if the method is breakpoint: - // One between the 1st and 2nd ... - InterpolateRotScaleUScale(0, 1, 0.5f, 1); - - // ... and one before the last one. - InterpolateRotScaleUScale(NumberOfCVs, NumberOfCVs - 1, 0.5f, NumberOfCVs); - NumberOfCVs += 2; - } - } - else if (CurveTypeValue == HAPI_CURVETYPE_BEZIER) - { - // Bezier curves requires additional point if the method is Breakpoints - if (CurveMethodValue == 1) - { - // 2 interpolated control points are added per points (except the last one) - int32 nOffset = 0; - for (int32 n = 0; n < NumberOfCVs - 1; n++) - { - int nIndex1 = n + nOffset; - int nIndex2 = n + nOffset + 1; - - InterpolateRotScaleUScale(nIndex1, nIndex2, 0.33f, nIndex2); - nIndex2++; - InterpolateRotScaleUScale(nIndex1, nIndex2, 0.66f, nIndex2); - - nOffset += 2; - } - NumberOfCVs += nOffset; - - if (CurveClosed) - { - // If the curve is closed, we need to add 2 points after the last, - // interpolated between the last and the first one - int nIndex = NumberOfCVs - 1; - InterpolateRotScaleUScale(nIndex, 0, 0.33f, NumberOfCVs++); - InterpolateRotScaleUScale(nIndex, 0, 0.66f, NumberOfCVs++); - - // and finally, the last point is the first.. - DuplicateRotScale(0, NumberOfCVs++); - } - } - else if (CurveClosed) - { - // For the other methods, if the bezier curve is closed, the last point is the 1st - DuplicateRotScale(0, NumberOfCVs++); - } - } - - // Even after interpolation, additional points might still be missing: - // Bezier curves require a certain number of points regarding their order, - // if points are lacking then HAPI duplicates the last one. - if (NumberOfCVs < PartInfos.pointCount) - { - int nToAdd = PartInfos.pointCount - NumberOfCVs; - for (int n = 0; n < nToAdd; n++) - { - DuplicateRotScale(NumberOfCVs - 1, NumberOfCVs); - NumberOfCVs++; - } - } - - // To avoid crashes, attributes will only be added if we now have the correct number of them - bAddRotations = bAddRotations && (Rotations->Num() == PartInfos.pointCount); - bAddScales3d = bAddScales3d && (Scales3d->Num() == PartInfos.pointCount); - - // We need to increase the point attributes count for points in the Part Infos - HAPI_AttributeOwner NewAttributesOwner = HAPI_ATTROWNER_POINT; - HAPI_AttributeOwner OriginalAttributesOwner = HAPI_ATTROWNER_POINT; - - int OriginalPointParametersCount = PartInfos.attributeCounts[NewAttributesOwner]; - if (bAddRotations) - PartInfos.attributeCounts[NewAttributesOwner] += 1; - if (bAddScales3d) - PartInfos.attributeCounts[NewAttributesOwner] += 1; - - // Sending the updated PartInfos - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, &PartInfos), false); - - // We need now to reproduce ALL the curves attributes for ALL the Owners.. - for (int nOwner = 0; nOwner < HAPI_ATTROWNER_MAX; nOwner++) - { - int nOwnerAttributeCount = nOwner == NewAttributesOwner ? OriginalPointParametersCount : PartInfos.attributeCounts[nOwner]; - if (nOwnerAttributeCount == 0) - continue; - - TArray AttributeNamesSH; - AttributeNamesSH.SetNum(nOwnerAttributeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, (HAPI_AttributeOwner)nOwner, - AttributeNamesSH.GetData(), AttributeNamesSH.Num()), false); - - for (int nAttribute = 0; nAttribute < AttributeNamesSH.Num(); nAttribute++) - { - const HAPI_StringHandle sh = AttributeNamesSH[nAttribute]; - if (sh == 0) - continue; - - // Get the attribute name - std::string attr_name; - FHoudiniEngineString::ToStdString(sh, attr_name); - if (strcmp(attr_name.c_str(), "__topology") == 0) - continue; - - // and the attribute infos - HAPI_AttributeInfo attr_info; - FHoudiniApi::AttributeInfo_Init(&attr_info); - //FMemory::Memzero< HAPI_AttributeInfo >( attr_info ); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, attr_name.c_str(), - (HAPI_AttributeOwner)nOwner, &attr_info), false); - - switch (attr_info.storage) - { - case HAPI_STORAGETYPE_INT: - { - // Storing IntData - TArray< int > IntData; - IntData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, -1, - IntData.GetData(), 0, attr_info.count), false); - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info, IntData.GetData(), - 0, attr_info.count), false); - } - break; - - case HAPI_STORAGETYPE_FLOAT: - { - // Storing Float Data - TArray< float > FloatData; - FloatData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, -1, - FloatData.GetData(), - 0, attr_info.count), false); - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - FloatData.GetData(), - 0, attr_info.count), false); - } - break; - - case HAPI_STORAGETYPE_STRING: - { - // Storing String Data - TArray StringHandleData; - StringHandleData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - StringHandleData.GetData(), - 0, attr_info.count), false); - - // Convert the SH to const char * - TArray StringData; - StringData.SetNumUninitialized(attr_info.count); - for (int n = 0; n < StringHandleData.Num(); n++) - { - // Converting the string - std::string strSTD; - FHoudiniEngineString::ToStdString(sh, strSTD); - - StringData[n] = strSTD.c_str(); - } - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - StringData.GetData(), - 0, attr_info.count), false); - } - break; - - default: - continue; - } - } - } - - // Only GET/SET curve infos if the part is a curve... - // (Closed linear curves are actually not considered as curves...) - if (PartInfos.type == HAPI_PARTTYPE_CURVE) - { - // We need to read the curve infos ... - HAPI_CurveInfo CurveInfo; - FHoudiniApi::CurveInfo_Init(&CurveInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - &CurveInfo), false); - - // ... the curve counts - TArray< int > CurveCounts; - CurveCounts.SetNumUninitialized(CurveInfo.curveCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveCounts.GetData(), - 0, CurveInfo.curveCount), false); - - // .. the curve orders - TArray< int > CurveOrders; - CurveOrders.SetNumUninitialized(CurveInfo.curveCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveOrders( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveOrders.GetData(), - 0, CurveInfo.curveCount), false); - - // .. And the Knots if they exist. - TArray< float > KnotsArray; - if (CurveInfo.hasKnots) - { - KnotsArray.SetNumUninitialized(CurveInfo.knotCount); - HOUDINI_CHECK_ERROR_RETURN( - FHoudiniApi::GetCurveKnots( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - KnotsArray.GetData(), - 0, CurveInfo.knotCount), false); - } - - // To set them back in HAPI - // CurveInfo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - &CurveInfo), false); - - // CurveCounts - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveCounts.GetData(), - 0, CurveInfo.curveCount), false); - - // CurveOrders - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveOrders( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveOrders.GetData(), - 0, CurveInfo.curveCount), false); - - // And Knots if they exist - if (CurveInfo.hasKnots) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveKnots( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - KnotsArray.GetData(), - 0, CurveInfo.knotCount), false); - } - } - - if (PartInfos.faceCount > 0) - { - // getting the face counts - TArray< int > FaceCounts; - FaceCounts.SetNumUninitialized(PartInfos.faceCount); - - if (FHoudiniApi::GetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - FaceCounts.GetData(), 0, - PartInfos.faceCount) == HAPI_RESULT_SUCCESS) - { - // Set the face count - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - FaceCounts.GetData(), - 0, PartInfos.faceCount), false); - } - } - - if (PartInfos.vertexCount > 0) - { - // the vertex list - TArray< int > VertexList; - VertexList.SetNumUninitialized(PartInfos.vertexCount); - - if (FHoudiniApi::GetVertexList( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - VertexList.GetData(), - 0, PartInfos.vertexCount) == HAPI_RESULT_SUCCESS) - { - // setting the vertex list - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - VertexList.GetData(), - 0, PartInfos.vertexCount), false); - } - } - - // We can add attributes to the curve now that all the curves attributes - // and properties have been reset. - if (bAddRotations) - { - // Create ROTATION attribute info - HAPI_AttributeInfo AttributeInfoRotation; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); - AttributeInfoRotation.count = NumberOfCVs; - AttributeInfoRotation.tupleSize = 4; - AttributeInfoRotation.exists = true; - AttributeInfoRotation.owner = NewAttributesOwner; - AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRotation.originalOwner = OriginalAttributesOwner; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_ROTATION, - &AttributeInfoRotation), false); - - // Convert the rotation infos - TArray< float > CurveRotations; - CurveRotations.SetNumZeroed(NumberOfCVs * 4); - for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) - { - // Get current quaternion - const FQuat& RotationQuaternion = (*Rotations)[Idx]; - - CurveRotations[Idx * 4 + 0] = RotationQuaternion.X; - CurveRotations[Idx * 4 + 1] = RotationQuaternion.Z; - CurveRotations[Idx * 4 + 2] = RotationQuaternion.Y; - CurveRotations[Idx * 4 + 3] = -RotationQuaternion.W; - } - - //we can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_ROTATION, - &AttributeInfoRotation, - CurveRotations.GetData(), - 0, AttributeInfoRotation.count), false); - } - - // Create SCALE attribute info. - if (bAddScales3d) - { - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = NumberOfCVs; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = NewAttributesOwner; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = OriginalAttributesOwner; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_SCALE, - &AttributeInfoScale), false); - - // Convert the scale - TArray< float > CurveScales; - CurveScales.SetNumZeroed(NumberOfCVs * 3); - for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) - { - // Get current scale - FVector ScaleVector = (*Scales3d)[Idx]; - CurveScales[Idx * 3 + 0] = ScaleVector.X; - CurveScales[Idx * 3 + 1] = ScaleVector.Z; - CurveScales[Idx * 3 + 2] = ScaleVector.Y; - } - - // We can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_SCALE, - &AttributeInfoScale, - CurveScales.GetData(), - 0, AttributeInfoScale.count), false); - } - - // Finally, commit the geo ... - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), CurveNodeId), false); - - // And cook it with refinement enabled - CookOptions.refineCurveToLinear = true; - //HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - // FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); - if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, false)) - return false; -#endif - - return true; -} - -void -FHoudiniSplineTranslator::CreatePositionsString(const TArray& InPositions, FString& OutPositionString) -{ - OutPositionString = TEXT(""); - for (int32 Idx = 0; Idx < InPositions.Num(); ++Idx) - { - FVector Position = InPositions[Idx]; - // Convert to meters - Position /= HAPI_UNREAL_SCALE_FACTOR_POSITION; - // Swap Y/Z - OutPositionString += FString::Printf(TEXT("%f, %f, %f "), Position.X, Position.Z, Position.Y); - } -} - -bool -FHoudiniSplineTranslator::HapiCreateCurveInputNode(HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName) -{ - // Create the curve SOP Node - HAPI_NodeId NewNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/curve"), InputNodeName, false, &NewNodeId), false); - - OutCurveNodeId = NewNodeId; - - // Submit default points to curve. - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), NewNodeId, - HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), NewNodeId, - HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT, ParmId, 0), false); - - // Cook the newly created node - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); - */ - - return FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true); -} - -UHoudiniSplineComponent* -FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent) -{ - if (GeoId < 0) - return nullptr; - - if (!OuterComponent || OuterComponent->IsPendingKill()) - return nullptr; - - USceneComponent* const SceneComponent = Cast(OuterComponent); - if (!IsValid(SceneComponent)) - return nullptr; - - // Create a HoudiniSplineComponent for the editable curve. - UHoudiniSplineComponent* HoudiniSplineComponent = NewObject( - OuterComponent, - UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Public | RF_Transactional); - - HoudiniSplineComponent->SetNodeId(GeoId); - HoudiniSplineComponent->SetGeoPartName(PartName); - - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(SceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - - UpdateHoudiniCurve(HoudiniSplineComponent); - - ReselectSelectedActors(); - - return HoudiniSplineComponent; - -} - -UHoudiniSplineComponent* -FHoudiniSplineTranslator::CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC) -{ - if (!OuterHAC || OuterHAC->IsPendingKill()) - return nullptr; - - UObject* Outer = nullptr; - if (OuterHAC && !OuterHAC->IsPendingKill()) - Outer = OuterHAC->GetOwner() ? OuterHAC->GetOwner() : OuterHAC->GetOuter(); - - UHoudiniSplineComponent *NewHoudiniSplineComponent = NewObject(Outer, UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!NewHoudiniSplineComponent) - return nullptr; - - NewHoudiniSplineComponent->Construct(CurvePoints); - - bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); - bool bHasScales = CurveScales.Num() == CurvePoints.Num(); - - TArray Transforms; - for (int32 n = 0; n < CurvePoints.Num(); ++n) - { - FTransform NextTransform = FTransform::Identity; - NextTransform.SetLocation(CurvePoints[n]); - - if (bHasRotations) - NextTransform.SetRotation(CurveRotations[n].Rotation().Quaternion()); - - if (bHasScales) - NextTransform.SetScale3D(CurveScales[n]); - - Transforms.Add(NextTransform); - } - - NewHoudiniSplineComponent->CurveType = EHoudiniCurveType::Polygon; - NewHoudiniSplineComponent->bIsOutputCurve = true; - - NewHoudiniSplineComponent->AttachToComponent(OuterHAC, FAttachmentTransformRules::KeepRelativeTransform); - NewHoudiniSplineComponent->RegisterComponent(); - - ReselectSelectedActors(); - - return NewHoudiniSplineComponent; -} - -USplineComponent* -FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, - UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed) -{ - if (!OuterComponent || OuterComponent->IsPendingKill()) - return nullptr; - - USceneComponent* OuterSceneComponent = Cast(OuterComponent); - if (!IsValid(OuterSceneComponent)) - return nullptr; - - UObject* Outer = nullptr; - Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); - - USplineComponent* NewSplineComponent = NewObject(Outer, USplineComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!NewSplineComponent) - return nullptr; - - // Clear default USplineComponent's points - NewSplineComponent->ClearSplinePoints(); - NewSplineComponent->bEditableWhenInherited = false; - - //bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); - //bool bHasScales = CurveScales.Num() == CurvePoints.Num(); - - for (int32 n = 0; n < CurvePoints.Num(); ++n) - { - NewSplineComponent->AddSplinePoint(CurvePoints[n], ESplineCoordinateSpace::Local); - - //FSplinePoint NewSplinePoint; - //NewSplinePoint.Position = CurvePoints[n]; - //if (bHasRotations) - // NewSplinePoint.Rotation = CurveRotations[n].Rotation(); - - //if (bHasScales) - // NewSplinePoint.Scale = CurveScales[n]; - //NewSplineComponent->AddPoint(NewSplinePoint, false); - } - - if (bIsLinear) - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - else - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); - } - - - NewSplineComponent->SetClosedLoop(bIsClosed); - - /* - NewSplineComponent->SetClosedLoop(bClosed); - - if (Type == int32(EHoudiniCurveType::Linear)) - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - else - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); - } - */ - - NewSplineComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - NewSplineComponent->RegisterComponent(); - AActor *OwnerActor = Cast(Outer); - if (IsValid(OwnerActor)) - OwnerActor->AddInstanceComponent(NewSplineComponent); - - ReselectSelectedActors(); - - return NewSplineComponent; -} - -bool -FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed) -{ - if (!EditedSplineComponent || EditedSplineComponent->IsPendingKill()) - return false; - - if (CurvePoints.Num() < 2) - return false; - - int MinCount = FMath::Min(CurvePoints.Num(), EditedSplineComponent->GetNumberOfSplinePoints()); - - for (int32 Idx = EditedSplineComponent->GetNumberOfSplinePoints() - 1; Idx >= 0; --Idx) - { - EditedSplineComponent->RemoveSplinePoint(Idx, false); - } - - for (int32 Idx = 0; Idx < CurvePoints.Num(); ++Idx) - { - EditedSplineComponent->AddSplinePoint(CurvePoints[Idx], ESplineCoordinateSpace::Local, false); - - if (CurveType == EHoudiniCurveType::Polygon) - EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Linear, false); - else - EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Curve, false); - } - - EditedSplineComponent->SetClosedLoop(bClosed, true); - - return true; -} - -bool -FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent) -{ - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - if (CurvePoints.Num() < 2) - return false; - - int MinCount = FMath::Min(CurvePoints.Num(), EditedHoudiniSplineComponent->CurvePoints.Num()); - - int Idx = 0; - // modify existing points - for (; Idx < MinCount; ++Idx) - { - FTransform CurTrans = EditedHoudiniSplineComponent->CurvePoints[Idx]; - if (CurTrans.GetLocation() == CurvePoints[Idx]) - continue; - - CurTrans.SetLocation(CurvePoints[Idx]); - } - - // remove extra points - if (Idx < EditedHoudiniSplineComponent->CurvePoints.Num()-1) - { - for (int32 n = EditedHoudiniSplineComponent->CurvePoints.Num() - 1; n >= Idx; --n) - { - EditedHoudiniSplineComponent->RemovePointAtIndex(n); - } - } - - - // append extra points - for (; Idx < CurvePoints.Num(); ++Idx) - { - FTransform NewPoint = FTransform::Identity; - NewPoint.SetLocation(CurvePoints[Idx]); - EditedHoudiniSplineComponent->CurvePoints.Add(NewPoint); - } - - return true; -} - - -bool -FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - UObject* InOuterComponent, - TMap& InSplines, - TMap& OutSplines, - const bool& InForceRebuild, - const EHoudiniCurveOutputType& OutputCurveType, - const bool& bIsLinear, - const bool& bIsClosed) -{ - // If we're not forcing the rebuild - // No need to recreate something that hasn't changed - if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged)) - { - // Simply reuse the existing meshes - OutSplines = InSplines; - return true; - } - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - int32 CurveNodeId = InHGPO.GeoId; - int32 CurvePartId = InHGPO.PartId; - if (CurveNodeId < 0 || CurvePartId < 0) - return false; - - // Extract all curve points from this HGPO - TArray RefinedCurvePositions; - HAPI_AttributeInfo AttributeRefinedCurvePositions; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); - - TArray RefinedCurveRotations; - HAPI_AttributeInfo AttributeRefinedCurveRotations; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveRotations); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_ROTATION, AttributeRefinedCurveRotations, RefinedCurveRotations); - - TArray RefinedCurveScales; - HAPI_AttributeInfo AttributeRefinedCurveScales; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveScales); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_SCALE, AttributeRefinedCurveScales, RefinedCurveScales); - - HAPI_CurveInfo CurveInfo; - FHoudiniApi::CurveInfo_Init(&CurveInfo); - FHoudiniApi::GetCurveInfo(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, &CurveInfo); - - int32 NumOfCurves = CurveInfo.curveCount; - TArray CurvePointsCounts; - CurvePointsCounts.SetNumZeroed(NumOfCurves); - FHoudiniApi::GetCurveCounts(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, CurvePointsCounts.GetData(), 0, NumOfCurves); - - TArray> CurvesDisplayPoints; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurvesDisplayPoints, CurvePointsCounts); - - TArray> CurvesRotations; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveRotations, CurvesRotations, CurvePointsCounts); - - TArray> CurvesScales; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveScales, CurvesScales, CurvePointsCounts); - - // Extract all curve points from this HGPO - FString GeoName = InHGPO.PartName; - int32 CurveIdx = 1; - - // Iterate through all curves found in this HGPO - for (int32 n = 0; n < CurvesDisplayPoints.Num(); ++n) - { - FString CurveName = FString::Printf(TEXT("%s curve %d"), *GeoName, CurveIdx); - CurveIdx += 1; - - if (CurvePointsCounts[n] < 2) - { - // Invalid vertex count, skip this curve. - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d] invalid vertex count.") - TEXT("- skipping."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx); - continue; - } - - FHoudiniOutputObjectIdentifier CurveIdentifier(InHGPO.ObjectId, InHGPO.GeoId, InHGPO.PartId, CurveName); - FHoudiniOutputObject* FoundOutputObject = InSplines.Find(CurveIdentifier); - - bool bNeedToRebuildSpline = false; - if (!FoundOutputObject) - bNeedToRebuildSpline = true; - - USceneComponent* FoundComponent = Cast(FoundOutputObject ? FoundOutputObject->OutputComponent : nullptr); - if (FoundComponent && !FoundComponent->IsPendingKill()) - { - // Only support output to Unreal Spline for now... - //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::UnrealSpline) - // bNeedToRebuildSpline = true; - - //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) - // bNeedToRebuildSpline = true; - - if (InHGPO.bHasGeoChanged || InHGPO.PartInfo.bHasChanged || InForceRebuild) - bNeedToRebuildSpline = true; - } - else - { - bNeedToRebuildSpline = true; - } - - // The curve has not changed, no need to go through the rest - if (!bNeedToRebuildSpline) - { - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - continue; - } - - bool bReusedPreviousOutput = false; - if (!FoundOutputObject) - { - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline (default): Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - // If not found (at initialize), create an Unreal spline - // We only support unreal spline for now.. - // May support Houdini spline too later - USplineComponent* CreatedSplineComponent = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); - if (!CreatedSplineComponent) - continue; - - // Create a new output object - FHoudiniOutputObject NewOutputObject; - NewOutputObject.OutputComponent = CreatedSplineComponent; - - NewOutputObject.CurveOutputProperty.CurveOutputType = OutputCurveType; - NewOutputObject.CurveOutputProperty.NumPoints = CurvePointsCounts[n]; - - // TODO: Need a way to access info of the output curve - NewOutputObject.CurveOutputProperty.CurveMethod = EHoudiniCurveMethod::Breakpoints; - NewOutputObject.CurveOutputProperty.CurveType = bIsLinear ? EHoudiniCurveType::Polygon : EHoudiniCurveType::Bezier; - NewOutputObject.CurveOutputProperty.bClosed = false; - // Fill in the rest of output curve properties - - OutSplines.Add(CurveIdentifier, NewOutputObject); - } - else - { - // - if (FoundOutputObject->CurveOutputProperty.CurveOutputType == EHoudiniCurveOutputType::UnrealSpline) - { - // See if we can simply update the previous Spline Component - bool bCanUpdateUnrealSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); - if (bCanUpdateUnrealSpline) - { - // Update the existing unreal spline component - bReusedPreviousOutput = true; - HOUDINI_LOG_WARNING( - TEXT("Updating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - USplineComponent* FoundUnrealSpline = Cast(FoundOutputObject->OutputComponent); - if (!FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(CurvesDisplayPoints[n], FoundUnrealSpline, FoundOutputObject->CurveOutputProperty.CurveType, FoundOutputObject->CurveOutputProperty.bClosed)) - continue; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - else - { - // Create a new Unreal spline component - // We support unreal spline only for now... - bReusedPreviousOutput = false; - FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::UnrealSpline; - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - USplineComponent* NewUnrealSpline = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); - if (!NewUnrealSpline) - continue; - - FoundOutputObject->OutputComponent = NewUnrealSpline; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - } - // We current support Unreal Spline output only... - /* - else - { - // We want to output a Houdini Spline Component - // See if we can simply update the previous Houdini Spline Component - bool bCanUpdateHoudiniSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); - if (bCanUpdateHoudiniSpline) - { - // Update the existing houdini spline component - bReusedPreviousOutput = true; - HOUDINI_LOG_WARNING( - TEXT("Changing Houdini Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - UHoudiniSplineComponent* FoundHoudiniSpline = Cast(FoundOutputObject->OutputComponent); - if (!FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], FoundHoudiniSpline)) - continue; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - else - { - // Create a new Houdini spline component - bReusedPreviousOutput = false; - FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - UHoudiniSplineComponent* NewHoudiniSpline = CreateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuter); - if (!NewHoudiniSpline) - continue; - - FoundOutputObject->OutputComponent = NewHoudiniSpline; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - } - */ - } - - if (bReusedPreviousOutput) - { - // Remove the reused output unreal spline from the old map to avoid its deletion - InSplines.Remove(CurveIdentifier); - } - - HOUDINI_LOG_WARNING( - TEXT("Finished Generating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - } - - return true; -} - - -bool -FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - // ONLY DO THIS ON CURVES!!!! - if (InOutput->GetType() != EHoudiniOutputType::Curve) - return false; - - // UHoudiniAssetComponent* OuterHAC = Cast(InOuterComponent); - // - // if (!OuterHAC || OuterHAC->IsPendingKill()) - // return false; - - TMap NewOutputObjects; - TMap& OldOutputObjects = InOutput->GetOutputObjects(); - - // Iterate on all the output's HGPO - for (const FHoudiniGeoPartObject & CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - // not a curve, skip - if (CurHGPO.Type != EHoudiniPartType::Curve) - continue; - - // Check if we want to create a houdini output curve from corresponding attribute - HAPI_AttributeInfo CurveOutputAttriInfo; - FHoudiniApi::AttributeInfo_Init(&CurveOutputAttriInfo); - TArray IntData; - IntData.Empty(); - - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE, CurveOutputAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - continue; - - if (IntData.Num() <= 0) - continue; - else - { - if (IntData[0] == 0) - continue; - } - - HAPI_AttributeInfo LinearAttriInfo; - FHoudiniApi::AttributeInfo_Init(&LinearAttriInfo); - IntData.Empty(); - - bool bIsLinear = false; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR, LinearAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - { - if (IntData.Num() > 0) - bIsLinear = IntData[0] == 1; - } - - HAPI_AttributeInfo ClosedAttriInfo; - FHoudiniApi::AttributeInfo_Init(&ClosedAttriInfo); - IntData.Empty(); - - bool bIsClosed = false; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED, ClosedAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - { - if (IntData.Num() > 0) - bIsClosed = IntData[0] == 1; - } - - // We output curve to Unreal Spline only for now - // May support output to Houdini Spline later - CreateOutputSplinesFromHoudiniGeoPartObject(CurHGPO, InOuterComponent, OldOutputObjects, NewOutputObjects, false, EHoudiniCurveOutputType::UnrealSpline, bIsLinear, bIsClosed); - } - - // TODO: FIX ME!!! This literally nukes all the output objects, even if they are not curves! - - // The old map now only contains unused/stale output curves destroy them - for (auto& OldPair : OldOutputObjects) - { - USceneComponent* OldSplineSceneComponent = Cast(OldPair.Value.OutputComponent); - - if (!OldSplineSceneComponent || OldSplineSceneComponent->IsPendingKill()) - continue; - - // The output object is supposed to be a spline - if (!OldSplineSceneComponent->IsA() && !OldSplineSceneComponent->IsA()) - continue; - - OldSplineSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - OldSplineSceneComponent->UnregisterComponent(); - OldSplineSceneComponent->DestroyComponent(); - } - OldOutputObjects.Empty(); - - InOutput->SetOutputObjects(NewOutputObjects); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - - return true; -} - -void -FHoudiniSplineTranslator::ReselectSelectedActors() -{ - // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? - USelection* Selection = GEditor->GetSelectedActors(); - TArray SelectedActors; - SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); - Selection->GetSelectedObjects(SelectedActors); - - GEditor->SelectNone(false, false, false); - - for (AActor* NextSelected : SelectedActors) - { - GEditor->SelectActor(NextSelected, true, true, true, true); - } +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "HoudiniGeoPartObject.h" +#include "Components/SplineComponent.h" + +#include "EditorViewportClient.h" +#include "Engine/Selection.h" + +#include "HoudiniEnginePrivatePCH.h" + +void +FHoudiniSplineTranslator::ExtractStringPositions(const FString& Positions, TArray& OutPositions) +{ + TArray< FString > PointStrings; + static const TCHAR * PositionSeparators[] = + { + TEXT(" "), + TEXT(","), + }; + + int32 NumCoords = Positions.ParseIntoArray(PointStrings, PositionSeparators, 2); + OutPositions.SetNum(NumCoords / 3); + for (int32 OutIndex = 0; OutIndex < OutPositions.Num(); OutIndex++) + { + const int32& CoordIndex = OutIndex * 3; + OutPositions[OutIndex].X = FCString::Atof(*(PointStrings[CoordIndex + 0])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutPositions[OutIndex].Y = FCString::Atof(*(PointStrings[CoordIndex + 2])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutPositions[OutIndex].Z = FCString::Atof(*(PointStrings[CoordIndex + 1])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } +} + +void +FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData) +{ + OutVectorData.SetNum(InRawData.Num() / 3); + + for (int32 OutIndex = 0; OutIndex < OutVectorData.Num(); OutIndex++) + { + const int32& InIndex = OutIndex * 3; + OutVectorData[OutIndex].X = InRawData[InIndex + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutVectorData[OutIndex].Y = InRawData[InIndex + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutVectorData[OutIndex].Z = InRawData[InIndex + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } +} + +void +FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts) +{ + OutVectorData.SetNum(CurveCounts.Num()); + + int32 TotalNumPoints = 0; + for (const int32 & NextCount : CurveCounts) + TotalNumPoints += NextCount; + + // Do not fill the output array, if the total number of points does not match + if (InRawData.Num() < TotalNumPoints * 3) + return; + + + int32 Itr = 0; + + for (int32 n = 0; n < CurveCounts.Num(); ++n) + { + TArray & NextVectorDataArray = OutVectorData[n]; + NextVectorDataArray.SetNumZeroed(CurveCounts[n]); + + for (int32 PtIdx = 0; PtIdx < CurveCounts[n]; ++PtIdx) + { + if (Itr + 2 >= InRawData.Num()) + return; + + NextVectorDataArray[PtIdx].X = InRawData[Itr] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + NextVectorDataArray[PtIdx].Y = InRawData[Itr + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + NextVectorDataArray[PtIdx].Z = InRawData[Itr + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + + Itr += 3; + } + } +} +void +FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC) +{ + for (UHoudiniInput * NextInput : HAC->Inputs) + UpdateHoudiniInputCurves(NextInput); +} + +void +FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniInput* Input) +{ + if (!Input || Input->GetInputType() != EHoudiniInputType::Curve) + return; + + TArray *InputObjectArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!InputObjectArray) + return; + + for (UHoudiniInputObject * NextInputObject : *InputObjectArray) + { + UHoudiniInputHoudiniSplineComponent * HoudiniSplineInput = Cast(NextInputObject); + if (!HoudiniSplineInput) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInput->GetCurveComponent(); + FHoudiniSplineTranslator::UpdateHoudiniCurve(HoudiniSplineComponent); + } +} + +bool +FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent) +{ + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return false; + + int32 CurveNode_id = HoudiniSplineComponent->GetNodeId(); + if (CurveNode_id < 0) + return false; + + bool Success = true; + FString CurvePointsString = FString(); + int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; + int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; + int32 CurveClosed = 0; + int32 CurveReversed = 0; + + Success &= FHoudiniEngineUtils::HapiGetParameterDataAsString( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString); + + Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); + + Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); + + Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); + + Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + + + HoudiniSplineComponent->SetCurveType((EHoudiniCurveType)CurveTypeValue); + HoudiniSplineComponent->SetCurveMethod((EHoudiniCurveMethod)CurveMethodValue); + HoudiniSplineComponent->SetClosedCurve(CurveClosed == 1); + HoudiniSplineComponent->SetReversed(CurveReversed == 1); + + // We need to get the NodeInfo to get the parent id + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurveNode_id, &NodeInfo), false); + + TArray< float > RefinedCurvePositions; + HAPI_AttributeInfo AttributeRefinedCurvePositions; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); + Success &= FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); + + // Process coords string and extract positions. + TArray CurvePoints; + FHoudiniSplineTranslator::ExtractStringPositions(CurvePointsString, CurvePoints); + + TArray CurveDisplayPoints; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurveDisplayPoints); + + // build curve points for editable curves. + if (HoudiniSplineComponent->CurvePoints.Num() < CurvePoints.Num()) + { + HoudiniSplineComponent->CurvePoints.Empty(); + for (FVector NextPos : CurvePoints) + { + FTransform NextTrans = FTransform::Identity; + NextTrans.SetLocation(NextPos); + HoudiniSplineComponent->CurvePoints.Add(NextTrans); + } + } + + // Update the display point on the curve + HoudiniSplineComponent->Construct(CurveDisplayPoints); + + HoudiniSplineComponent->MarkChanged(false); + + return Success; +} + + +bool +FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent) +{ + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return true; + + TArray PositionArray; + TArray RotationArray; + TArray Scales3dArray; + TArray UniformScaleArray; + for (FTransform & NextTransform : HoudiniSplineComponent->CurvePoints) + { + PositionArray.Add(NextTransform.GetLocation()); + RotationArray.Add(NextTransform.GetRotation()); + Scales3dArray.Add(NextTransform.GetScale3D()); + UniformScaleArray.Add(1.f); + } + + HAPI_NodeId CurveNode_id = HoudiniSplineComponent->GetNodeId(); + FTransform ParentTransform = HoudiniSplineComponent->GetComponentTransform(); + + FString InputNodeNameString = HoudiniSplineComponent->GetName(); + UHoudiniInputHoudiniSplineComponent* InputObject = Cast(HoudiniSplineComponent->GetOuter()); + if (InputObject) + { + UHoudiniInput* Input = Cast(InputObject->GetOuter()); + if (Input) + { + InputNodeNameString = Input->GetNodeBaseName(); + } + } + InputNodeNameString += TEXT("_curve"); + + bool Success = FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( + CurveNode_id, + InputNodeNameString, + &PositionArray, + &RotationArray, + &Scales3dArray, + HoudiniSplineComponent->GetCurveType(), + HoudiniSplineComponent->GetCurveMethod(), + HoudiniSplineComponent->IsClosedCurve(), + HoudiniSplineComponent->IsReversed(), + false, + ParentTransform); + + HoudiniSplineComponent->SetNodeId(CurveNode_id); + Success &= UpdateHoudiniCurve(HoudiniSplineComponent); + + return Success; +} + +bool +FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent) +{ + if (!SplineComponent || SplineComponent->IsPendingKill()) + return true; + + bool Success = HapiUpdateNodeForHoudiniSplineComponent(SplineComponent); + + return Success; +} + +bool +FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( + HAPI_NodeId& CurveNodeId, + const FString& InputNodeName, + TArray* Positions, + TArray* Rotations, + TArray* Scales3d, + EHoudiniCurveType InCurveType, + EHoudiniCurveMethod InCurveMethod, + const bool& InClosed, + const bool& InReversed, + const bool& InForceClose, + const FTransform& ParentTransform ) +{ +#if WITH_EDITOR + // Positions are required + if (!Positions) + return false; + + // We also need a valid host asset and 2 points to make a curve + int32 NumberOfCVs = Positions->Num(); + if (NumberOfCVs < 2) + return false; + + // Check if connected asset id is valid, if it is not, we need to create an input asset. + if (CurveNodeId < 0) + { + HAPI_NodeId NodeId = -1; + // Create the curve SOP Node + if (!FHoudiniSplineTranslator::HapiCreateCurveInputNode(NodeId, InputNodeName)) + return false; + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NodeId)) + return false; + + // We now have a valid id. + CurveNodeId = NodeId; + } + else + { + // We have to revert the Geo to its original state so we can use the Curve SOP: + // adding parameters to the Curve SOP locked it, preventing its parameters (type, method, isClosed) from working + FHoudiniApi::RevertGeo(FHoudiniEngine::Get().GetSession(), CurveNodeId); + } + + // + // In order to be able to add rotations and scale attributes to the curve SOP, we need to cook it twice: + // + // - First, we send the positions string to it, and cook it without refinement. + // this will allow us to get the proper curve CVs, part attributes and curve info to create the desired curve. + // + // - We then need to send back all the info extracted from the curve SOP to it, and add the rotation + // and scale attributes to it. This will lock the curve SOP, and prevent the curve type and method + // parameters from functioning properly (hence why we needed the first cook to set that up) + // + + // Set the curve type and curve method parameters for the curve node + int32 CurveTypeValue = (int32)InCurveType; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); + + int32 CurveMethodValue = (int32)InCurveMethod; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); + + int32 CurveClosed = InClosed ? 1 : 0; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); + + int32 CurveReversed = InReversed ? 1 : 0; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + + // Reading the curve parameters + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + + if (InForceClose) + { + // We need to update the closed parameter + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); + + CurveClosed = 1; + } + + // For closed NURBS (CVs and Breakpoints), we have to close the curve manually, by duplicating its last point + // in order to be able to set the rotations and scales attributes properly. + bool bCloseCurveManually = false; + if (CurveClosed && (CurveTypeValue == HAPI_CURVETYPE_NURBS) && (CurveMethodValue != 2)) + { + // The curve is not closed anymore + if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 0)) + { + bCloseCurveManually = true; + + // Duplicating the first point to the end of the curve + // This needs to be done before sending the position string + FVector pos = (*Positions)[0]; + Positions->Add(pos); + + CurveClosed = false; + } + } + + // Creating the position string + FString PositionString = TEXT(""); + FHoudiniSplineTranslator::CreatePositionsString(*Positions, PositionString); + + // Get param id for the PositionString and modify it + HAPI_ParmId ParmId = -1; + if (FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId) != HAPI_RESULT_SUCCESS) + { + return false; + } + + std::string ConvertedString = TCHAR_TO_UTF8(*PositionString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + ConvertedString.c_str(), ParmId, 0), false); + + // If we don't want to add rotations or scale attributes to the curve, + // we can just cook the node normally and stop here. + bool bAddRotations = (Rotations != nullptr); + bool bAddScales3d = (Scales3d != nullptr); + if (!bAddRotations && !bAddScales3d) + { + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); + */ + + // Cook the node, no need to wait for completion + return FHoudiniEngineUtils::HapiCookNode(CurveNodeId, nullptr, false); + } + + // Setting up the first cook, without the curve refinement + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + CookOptions.maxVerticesPerPrimitive = -1; + CookOptions.refineCurveToLinear = false; + if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, true)) + return false; + + // We can now read back the Part infos from the cooked curve. + HAPI_PartInfo PartInfos; + FHoudiniApi::PartInfo_Init(&PartInfos); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurveNodeId, 0, &PartInfos), false); + + // + // Depending on the curve type and method, additionnal control points might have been created. + // We now have to interpolate the rotations and scale attributes for these. + // + + // Lambda function that interpolates rotation, scale and uniform scales values + // between two points using fCoeff as a weight, and insert the interpolated value at nInsertIndex + auto InterpolateRotScaleUScale = [&](const int32& nIndex1, const int32& nIndex2, const float& fCoeff, const int32& nInsertIndex) + { + if (Rotations && Rotations->IsValidIndex(nIndex1) && Rotations->IsValidIndex(nIndex2)) + { + FQuat interpolation = FQuat::Slerp((*Rotations)[nIndex1], (*Rotations)[nIndex2], fCoeff); + if (Rotations->IsValidIndex(nInsertIndex)) + Rotations->Insert(interpolation, nInsertIndex); + else + Rotations->Add(interpolation); + } + + if (Scales3d && Scales3d->IsValidIndex(nIndex1) && Scales3d->IsValidIndex(nIndex2)) + { + FVector interpolation = fCoeff * (*Scales3d)[nIndex1] + (1.0f - fCoeff) * (*Scales3d)[nIndex2]; + if (Scales3d->IsValidIndex(nInsertIndex)) + Scales3d->Insert(interpolation, nInsertIndex); + else + Scales3d->Add(interpolation); + } + }; + + // Lambda function that duplicates rotation and scale values + // at nIndex and insert/adds it at nInsertIndex + auto DuplicateRotScale = [&](const int32& nIndex, const int32& nInsertIndex) + { + if (Rotations && Rotations->IsValidIndex(nIndex)) + { + FQuat value = (*Rotations)[nIndex]; + if (Rotations->IsValidIndex(nInsertIndex)) + Rotations->Insert(value, nInsertIndex); + else + Rotations->Add(value); + } + + if (Scales3d && Scales3d->IsValidIndex(nIndex)) + { + FVector value = (*Scales3d)[nIndex]; + if (Scales3d->IsValidIndex(nInsertIndex)) + Scales3d->Insert(value, nInsertIndex); + else + Scales3d->Add(value); + } + }; + + // Do we want to close the curve by ourselves? + if (bCloseCurveManually) + { + // We need to duplicate the info of the first point to the last + DuplicateRotScale(0, NumberOfCVs++); + + // We need to update the closed parameter + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); + } + + // INTERPOLATION + if (CurveTypeValue == HAPI_CURVETYPE_NURBS) + { + // Closed NURBS have additional points reproducing the first ones + if (InClosed) + { + // Only the first one if the method is freehand ... + DuplicateRotScale(0, NumberOfCVs++); + + if (CurveMethodValue != 2) + { + // ... but also the 2nd and 3rd if the method is CVs or Breakpoints. + DuplicateRotScale(1, NumberOfCVs++); + DuplicateRotScale(2, NumberOfCVs++); + } + } + else if (CurveMethodValue == 1) + { + // Open NURBS have 2 new points if the method is breakpoint: + // One between the 1st and 2nd ... + InterpolateRotScaleUScale(0, 1, 0.5f, 1); + + // ... and one before the last one. + InterpolateRotScaleUScale(NumberOfCVs, NumberOfCVs - 1, 0.5f, NumberOfCVs); + NumberOfCVs += 2; + } + } + else if (CurveTypeValue == HAPI_CURVETYPE_BEZIER) + { + // Bezier curves requires additional point if the method is Breakpoints + if (CurveMethodValue == 1) + { + // 2 interpolated control points are added per points (except the last one) + int32 nOffset = 0; + for (int32 n = 0; n < NumberOfCVs - 1; n++) + { + int nIndex1 = n + nOffset; + int nIndex2 = n + nOffset + 1; + + InterpolateRotScaleUScale(nIndex1, nIndex2, 0.33f, nIndex2); + nIndex2++; + InterpolateRotScaleUScale(nIndex1, nIndex2, 0.66f, nIndex2); + + nOffset += 2; + } + NumberOfCVs += nOffset; + + if (CurveClosed) + { + // If the curve is closed, we need to add 2 points after the last, + // interpolated between the last and the first one + int nIndex = NumberOfCVs - 1; + InterpolateRotScaleUScale(nIndex, 0, 0.33f, NumberOfCVs++); + InterpolateRotScaleUScale(nIndex, 0, 0.66f, NumberOfCVs++); + + // and finally, the last point is the first.. + DuplicateRotScale(0, NumberOfCVs++); + } + } + else if (CurveClosed) + { + // For the other methods, if the bezier curve is closed, the last point is the 1st + DuplicateRotScale(0, NumberOfCVs++); + } + } + + // Even after interpolation, additional points might still be missing: + // Bezier curves require a certain number of points regarding their order, + // if points are lacking then HAPI duplicates the last one. + if (NumberOfCVs < PartInfos.pointCount) + { + int nToAdd = PartInfos.pointCount - NumberOfCVs; + for (int n = 0; n < nToAdd; n++) + { + DuplicateRotScale(NumberOfCVs - 1, NumberOfCVs); + NumberOfCVs++; + } + } + + // To avoid crashes, attributes will only be added if we now have the correct number of them + bAddRotations = bAddRotations && (Rotations->Num() == PartInfos.pointCount); + bAddScales3d = bAddScales3d && (Scales3d->Num() == PartInfos.pointCount); + + // We need to increase the point attributes count for points in the Part Infos + HAPI_AttributeOwner NewAttributesOwner = HAPI_ATTROWNER_POINT; + HAPI_AttributeOwner OriginalAttributesOwner = HAPI_ATTROWNER_POINT; + + int OriginalPointParametersCount = PartInfos.attributeCounts[NewAttributesOwner]; + if (bAddRotations) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + if (bAddScales3d) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + + // Sending the updated PartInfos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, &PartInfos), false); + + // We need now to reproduce ALL the curves attributes for ALL the Owners.. + for (int nOwner = 0; nOwner < HAPI_ATTROWNER_MAX; nOwner++) + { + int nOwnerAttributeCount = nOwner == NewAttributesOwner ? OriginalPointParametersCount : PartInfos.attributeCounts[nOwner]; + if (nOwnerAttributeCount == 0) + continue; + + TArray AttributeNamesSH; + AttributeNamesSH.SetNum(nOwnerAttributeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, (HAPI_AttributeOwner)nOwner, + AttributeNamesSH.GetData(), AttributeNamesSH.Num()), false); + + for (int nAttribute = 0; nAttribute < AttributeNamesSH.Num(); nAttribute++) + { + const HAPI_StringHandle sh = AttributeNamesSH[nAttribute]; + if (sh == 0) + continue; + + // Get the attribute name + std::string attr_name; + FHoudiniEngineString::ToStdString(sh, attr_name); + if (strcmp(attr_name.c_str(), "__topology") == 0) + continue; + + // and the attribute infos + HAPI_AttributeInfo attr_info; + FHoudiniApi::AttributeInfo_Init(&attr_info); + //FMemory::Memzero< HAPI_AttributeInfo >( attr_info ); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, attr_name.c_str(), + (HAPI_AttributeOwner)nOwner, &attr_info), false); + + switch (attr_info.storage) + { + case HAPI_STORAGETYPE_INT: + { + // Storing IntData + TArray< int > IntData; + IntData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + IntData.GetData(), 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info, IntData.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_FLOAT: + { + // Storing Float Data + TArray< float > FloatData; + FloatData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + FloatData.GetData(), + 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + FloatData.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_STRING: + { + // Storing String Data + TArray StringHandleData; + StringHandleData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + StringHandleData.GetData(), + 0, attr_info.count), false); + + // Convert the SH to const char * + TArray StringData; + StringData.SetNumUninitialized(attr_info.count); + for (int n = 0; n < StringHandleData.Num(); n++) + { + // Converting the string + std::string strSTD; + FHoudiniEngineString::ToStdString(sh, strSTD); + + StringData[n] = strSTD.c_str(); + } + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + StringData.GetData(), + 0, attr_info.count), false); + } + break; + + default: + continue; + } + } + } + + // Only GET/SET curve infos if the part is a curve... + // (Closed linear curves are actually not considered as curves...) + if (PartInfos.type == HAPI_PARTTYPE_CURVE) + { + // We need to read the curve infos ... + HAPI_CurveInfo CurveInfo; + FHoudiniApi::CurveInfo_Init(&CurveInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + &CurveInfo), false); + + // ... the curve counts + TArray< int > CurveCounts; + CurveCounts.SetNumUninitialized(CurveInfo.curveCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveCounts.GetData(), + 0, CurveInfo.curveCount), false); + + // .. the curve orders + TArray< int > CurveOrders; + CurveOrders.SetNumUninitialized(CurveInfo.curveCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveOrders( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveOrders.GetData(), + 0, CurveInfo.curveCount), false); + + // .. And the Knots if they exist. + TArray< float > KnotsArray; + if (CurveInfo.hasKnots) + { + KnotsArray.SetNumUninitialized(CurveInfo.knotCount); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetCurveKnots( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + KnotsArray.GetData(), + 0, CurveInfo.knotCount), false); + } + + // To set them back in HAPI + // CurveInfo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + &CurveInfo), false); + + // CurveCounts + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveCounts.GetData(), + 0, CurveInfo.curveCount), false); + + // CurveOrders + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveOrders( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveOrders.GetData(), + 0, CurveInfo.curveCount), false); + + // And Knots if they exist + if (CurveInfo.hasKnots) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveKnots( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + KnotsArray.GetData(), + 0, CurveInfo.knotCount), false); + } + } + + if (PartInfos.faceCount > 0) + { + // getting the face counts + TArray< int > FaceCounts; + FaceCounts.SetNumUninitialized(PartInfos.faceCount); + + if (FHoudiniApi::GetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + FaceCounts.GetData(), 0, + PartInfos.faceCount) == HAPI_RESULT_SUCCESS) + { + // Set the face count + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + FaceCounts.GetData(), + 0, PartInfos.faceCount), false); + } + } + + if (PartInfos.vertexCount > 0) + { + // the vertex list + TArray< int > VertexList; + VertexList.SetNumUninitialized(PartInfos.vertexCount); + + if (FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + VertexList.GetData(), + 0, PartInfos.vertexCount) == HAPI_RESULT_SUCCESS) + { + // setting the vertex list + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + VertexList.GetData(), + 0, PartInfos.vertexCount), false); + } + } + + // We can add attributes to the curve now that all the curves attributes + // and properties have been reset. + if (bAddRotations) + { + // Create ROTATION attribute info + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + AttributeInfoRotation.count = NumberOfCVs; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = NewAttributesOwner; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation), false); + + // Convert the rotation infos + TArray< float > CurveRotations; + CurveRotations.SetNumZeroed(NumberOfCVs * 4); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + // Get current quaternion + const FQuat& RotationQuaternion = (*Rotations)[Idx]; + + CurveRotations[Idx * 4 + 0] = RotationQuaternion.X; + CurveRotations[Idx * 4 + 1] = RotationQuaternion.Z; + CurveRotations[Idx * 4 + 2] = RotationQuaternion.Y; + CurveRotations[Idx * 4 + 3] = -RotationQuaternion.W; + } + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation, + CurveRotations.GetData(), + 0, AttributeInfoRotation.count), false); + } + + // Create SCALE attribute info. + if (bAddScales3d) + { + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = NumberOfCVs; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = NewAttributesOwner; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale), false); + + // Convert the scale + TArray< float > CurveScales; + CurveScales.SetNumZeroed(NumberOfCVs * 3); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + // Get current scale + FVector ScaleVector = (*Scales3d)[Idx]; + CurveScales[Idx * 3 + 0] = ScaleVector.X; + CurveScales[Idx * 3 + 1] = ScaleVector.Z; + CurveScales[Idx * 3 + 2] = ScaleVector.Y; + } + + // We can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale, + CurveScales.GetData(), + 0, AttributeInfoScale.count), false); + } + + // Finally, commit the geo ... + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), CurveNodeId), false); + + // And cook it with refinement enabled + CookOptions.refineCurveToLinear = true; + //HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + // FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); + if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, false)) + return false; +#endif + + return true; +} + +void +FHoudiniSplineTranslator::CreatePositionsString(const TArray& InPositions, FString& OutPositionString) +{ + OutPositionString = TEXT(""); + for (int32 Idx = 0; Idx < InPositions.Num(); ++Idx) + { + FVector Position = InPositions[Idx]; + // Convert to meters + Position /= HAPI_UNREAL_SCALE_FACTOR_POSITION; + // Swap Y/Z + OutPositionString += FString::Printf(TEXT("%f, %f, %f "), Position.X, Position.Z, Position.Y); + } +} + +bool +FHoudiniSplineTranslator::HapiCreateCurveInputNode(HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName) +{ + // Create the curve SOP Node + HAPI_NodeId NewNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/curve"), InputNodeName, false, &NewNodeId), false); + + OutCurveNodeId = NewNodeId; + + // Submit default points to curve. + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), NewNodeId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NewNodeId, + HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT, ParmId, 0), false); + + // Cook the newly created node + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); + */ + + return FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true); +} + +UHoudiniSplineComponent* +FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent) +{ + if (GeoId < 0) + return nullptr; + + if (!OuterComponent || OuterComponent->IsPendingKill()) + return nullptr; + + USceneComponent* const SceneComponent = Cast(OuterComponent); + if (!IsValid(SceneComponent)) + return nullptr; + + // Create a HoudiniSplineComponent for the editable curve. + UHoudiniSplineComponent* HoudiniSplineComponent = NewObject( + OuterComponent, + UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Public | RF_Transactional); + + HoudiniSplineComponent->SetNodeId(GeoId); + HoudiniSplineComponent->SetGeoPartName(PartName); + + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(SceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + + UpdateHoudiniCurve(HoudiniSplineComponent); + + ReselectSelectedActors(); + + return HoudiniSplineComponent; + +} + +UHoudiniSplineComponent* +FHoudiniSplineTranslator::CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC) +{ + if (!OuterHAC || OuterHAC->IsPendingKill()) + return nullptr; + + UObject* Outer = nullptr; + if (OuterHAC && !OuterHAC->IsPendingKill()) + Outer = OuterHAC->GetOwner() ? OuterHAC->GetOwner() : OuterHAC->GetOuter(); + + UHoudiniSplineComponent *NewHoudiniSplineComponent = NewObject(Outer, UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!NewHoudiniSplineComponent) + return nullptr; + + NewHoudiniSplineComponent->Construct(CurvePoints); + + bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); + bool bHasScales = CurveScales.Num() == CurvePoints.Num(); + + TArray Transforms; + for (int32 n = 0; n < CurvePoints.Num(); ++n) + { + FTransform NextTransform = FTransform::Identity; + NextTransform.SetLocation(CurvePoints[n]); + + if (bHasRotations) + NextTransform.SetRotation(CurveRotations[n].Rotation().Quaternion()); + + if (bHasScales) + NextTransform.SetScale3D(CurveScales[n]); + + Transforms.Add(NextTransform); + } + + NewHoudiniSplineComponent->CurveType = EHoudiniCurveType::Polygon; + NewHoudiniSplineComponent->bIsOutputCurve = true; + + NewHoudiniSplineComponent->AttachToComponent(OuterHAC, FAttachmentTransformRules::KeepRelativeTransform); + NewHoudiniSplineComponent->RegisterComponent(); + + ReselectSelectedActors(); + + return NewHoudiniSplineComponent; +} + +USplineComponent* +FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, + UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed) +{ + if (!OuterComponent || OuterComponent->IsPendingKill()) + return nullptr; + + USceneComponent* OuterSceneComponent = Cast(OuterComponent); + if (!IsValid(OuterSceneComponent)) + return nullptr; + + UObject* Outer = nullptr; + Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); + + USplineComponent* NewSplineComponent = NewObject(Outer, USplineComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!NewSplineComponent) + return nullptr; + + // Clear default USplineComponent's points + NewSplineComponent->ClearSplinePoints(); + NewSplineComponent->bEditableWhenInherited = false; + + //bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); + //bool bHasScales = CurveScales.Num() == CurvePoints.Num(); + + for (int32 n = 0; n < CurvePoints.Num(); ++n) + { + NewSplineComponent->AddSplinePoint(CurvePoints[n], ESplineCoordinateSpace::Local); + + //FSplinePoint NewSplinePoint; + //NewSplinePoint.Position = CurvePoints[n]; + //if (bHasRotations) + // NewSplinePoint.Rotation = CurveRotations[n].Rotation(); + + //if (bHasScales) + // NewSplinePoint.Scale = CurveScales[n]; + //NewSplineComponent->AddPoint(NewSplinePoint, false); + } + + if (bIsLinear) + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + else + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); + } + + + NewSplineComponent->SetClosedLoop(bIsClosed); + + /* + NewSplineComponent->SetClosedLoop(bClosed); + + if (Type == int32(EHoudiniCurveType::Linear)) + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + else + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); + } + */ + + NewSplineComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + NewSplineComponent->RegisterComponent(); + AActor *OwnerActor = Cast(Outer); + if (IsValid(OwnerActor)) + OwnerActor->AddInstanceComponent(NewSplineComponent); + + ReselectSelectedActors(); + + return NewSplineComponent; +} + +bool +FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed) +{ + if (!EditedSplineComponent || EditedSplineComponent->IsPendingKill()) + return false; + + if (CurvePoints.Num() < 2) + return false; + + int MinCount = FMath::Min(CurvePoints.Num(), EditedSplineComponent->GetNumberOfSplinePoints()); + + for (int32 Idx = EditedSplineComponent->GetNumberOfSplinePoints() - 1; Idx >= 0; --Idx) + { + EditedSplineComponent->RemoveSplinePoint(Idx, false); + } + + for (int32 Idx = 0; Idx < CurvePoints.Num(); ++Idx) + { + EditedSplineComponent->AddSplinePoint(CurvePoints[Idx], ESplineCoordinateSpace::Local, false); + + if (CurveType == EHoudiniCurveType::Polygon) + EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Linear, false); + else + EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Curve, false); + } + + EditedSplineComponent->SetClosedLoop(bClosed, true); + + return true; +} + +bool +FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent) +{ + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + if (CurvePoints.Num() < 2) + return false; + + int MinCount = FMath::Min(CurvePoints.Num(), EditedHoudiniSplineComponent->CurvePoints.Num()); + + int Idx = 0; + // modify existing points + for (; Idx < MinCount; ++Idx) + { + FTransform CurTrans = EditedHoudiniSplineComponent->CurvePoints[Idx]; + if (CurTrans.GetLocation() == CurvePoints[Idx]) + continue; + + CurTrans.SetLocation(CurvePoints[Idx]); + } + + // remove extra points + if (Idx < EditedHoudiniSplineComponent->CurvePoints.Num()-1) + { + for (int32 n = EditedHoudiniSplineComponent->CurvePoints.Num() - 1; n >= Idx; --n) + { + EditedHoudiniSplineComponent->RemovePointAtIndex(n); + } + } + + + // append extra points + for (; Idx < CurvePoints.Num(); ++Idx) + { + FTransform NewPoint = FTransform::Identity; + NewPoint.SetLocation(CurvePoints[Idx]); + EditedHoudiniSplineComponent->CurvePoints.Add(NewPoint); + } + + return true; +} + + +bool +FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + UObject* InOuterComponent, + TMap& InSplines, + TMap& OutSplines, + const bool& InForceRebuild, + const EHoudiniCurveOutputType& OutputCurveType, + const bool& bIsLinear, + const bool& bIsClosed) +{ + // If we're not forcing the rebuild + // No need to recreate something that hasn't changed + if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged)) + { + // Simply reuse the existing meshes + OutSplines = InSplines; + return true; + } + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + int32 CurveNodeId = InHGPO.GeoId; + int32 CurvePartId = InHGPO.PartId; + if (CurveNodeId < 0 || CurvePartId < 0) + return false; + + // Extract all curve points from this HGPO + TArray RefinedCurvePositions; + HAPI_AttributeInfo AttributeRefinedCurvePositions; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); + + TArray RefinedCurveRotations; + HAPI_AttributeInfo AttributeRefinedCurveRotations; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveRotations); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_ROTATION, AttributeRefinedCurveRotations, RefinedCurveRotations); + + TArray RefinedCurveScales; + HAPI_AttributeInfo AttributeRefinedCurveScales; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveScales); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_SCALE, AttributeRefinedCurveScales, RefinedCurveScales); + + HAPI_CurveInfo CurveInfo; + FHoudiniApi::CurveInfo_Init(&CurveInfo); + FHoudiniApi::GetCurveInfo(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, &CurveInfo); + + int32 NumOfCurves = CurveInfo.curveCount; + TArray CurvePointsCounts; + CurvePointsCounts.SetNumZeroed(NumOfCurves); + FHoudiniApi::GetCurveCounts(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, CurvePointsCounts.GetData(), 0, NumOfCurves); + + TArray> CurvesDisplayPoints; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurvesDisplayPoints, CurvePointsCounts); + + TArray> CurvesRotations; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveRotations, CurvesRotations, CurvePointsCounts); + + TArray> CurvesScales; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveScales, CurvesScales, CurvePointsCounts); + + // Extract all curve points from this HGPO + FString GeoName = InHGPO.PartName; + int32 CurveIdx = 1; + + // Iterate through all curves found in this HGPO + for (int32 n = 0; n < CurvesDisplayPoints.Num(); ++n) + { + FString CurveName = FString::Printf(TEXT("%s curve %d"), *GeoName, CurveIdx); + CurveIdx += 1; + + if (CurvePointsCounts[n] < 2) + { + // Invalid vertex count, skip this curve. + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d] invalid vertex count.") + TEXT("- skipping."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx); + continue; + } + + FHoudiniOutputObjectIdentifier CurveIdentifier(InHGPO.ObjectId, InHGPO.GeoId, InHGPO.PartId, CurveName); + FHoudiniOutputObject* FoundOutputObject = InSplines.Find(CurveIdentifier); + + bool bNeedToRebuildSpline = false; + if (!FoundOutputObject) + bNeedToRebuildSpline = true; + + USceneComponent* FoundComponent = Cast(FoundOutputObject ? FoundOutputObject->OutputComponent : nullptr); + if (FoundComponent && !FoundComponent->IsPendingKill()) + { + // Only support output to Unreal Spline for now... + //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::UnrealSpline) + // bNeedToRebuildSpline = true; + + //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + // bNeedToRebuildSpline = true; + + if (InHGPO.bHasGeoChanged || InHGPO.PartInfo.bHasChanged || InForceRebuild) + bNeedToRebuildSpline = true; + } + else + { + bNeedToRebuildSpline = true; + } + + // The curve has not changed, no need to go through the rest + if (!bNeedToRebuildSpline) + { + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + continue; + } + + bool bReusedPreviousOutput = false; + if (!FoundOutputObject) + { + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline (default): Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + // If not found (at initialize), create an Unreal spline + // We only support unreal spline for now.. + // May support Houdini spline too later + USplineComponent* CreatedSplineComponent = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); + if (!CreatedSplineComponent) + continue; + + // Create a new output object + FHoudiniOutputObject NewOutputObject; + NewOutputObject.OutputComponent = CreatedSplineComponent; + + NewOutputObject.CurveOutputProperty.CurveOutputType = OutputCurveType; + NewOutputObject.CurveOutputProperty.NumPoints = CurvePointsCounts[n]; + + // TODO: Need a way to access info of the output curve + NewOutputObject.CurveOutputProperty.CurveMethod = EHoudiniCurveMethod::Breakpoints; + NewOutputObject.CurveOutputProperty.CurveType = bIsLinear ? EHoudiniCurveType::Polygon : EHoudiniCurveType::Bezier; + NewOutputObject.CurveOutputProperty.bClosed = false; + // Fill in the rest of output curve properties + + OutSplines.Add(CurveIdentifier, NewOutputObject); + } + else + { + // + if (FoundOutputObject->CurveOutputProperty.CurveOutputType == EHoudiniCurveOutputType::UnrealSpline) + { + // See if we can simply update the previous Spline Component + bool bCanUpdateUnrealSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); + if (bCanUpdateUnrealSpline) + { + // Update the existing unreal spline component + bReusedPreviousOutput = true; + HOUDINI_LOG_WARNING( + TEXT("Updating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + USplineComponent* FoundUnrealSpline = Cast(FoundOutputObject->OutputComponent); + if (!FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(CurvesDisplayPoints[n], FoundUnrealSpline, FoundOutputObject->CurveOutputProperty.CurveType, FoundOutputObject->CurveOutputProperty.bClosed)) + continue; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + else + { + // Create a new Unreal spline component + // We support unreal spline only for now... + bReusedPreviousOutput = false; + FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::UnrealSpline; + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + USplineComponent* NewUnrealSpline = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); + if (!NewUnrealSpline) + continue; + + FoundOutputObject->OutputComponent = NewUnrealSpline; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + } + // We current support Unreal Spline output only... + /* + else + { + // We want to output a Houdini Spline Component + // See if we can simply update the previous Houdini Spline Component + bool bCanUpdateHoudiniSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); + if (bCanUpdateHoudiniSpline) + { + // Update the existing houdini spline component + bReusedPreviousOutput = true; + HOUDINI_LOG_WARNING( + TEXT("Changing Houdini Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + UHoudiniSplineComponent* FoundHoudiniSpline = Cast(FoundOutputObject->OutputComponent); + if (!FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], FoundHoudiniSpline)) + continue; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + else + { + // Create a new Houdini spline component + bReusedPreviousOutput = false; + FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + UHoudiniSplineComponent* NewHoudiniSpline = CreateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuter); + if (!NewHoudiniSpline) + continue; + + FoundOutputObject->OutputComponent = NewHoudiniSpline; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + } + */ + } + + if (bReusedPreviousOutput) + { + // Remove the reused output unreal spline from the old map to avoid its deletion + InSplines.Remove(CurveIdentifier); + } + + HOUDINI_LOG_WARNING( + TEXT("Finished Generating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + } + + return true; +} + + +bool +FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + // ONLY DO THIS ON CURVES!!!! + if (InOutput->GetType() != EHoudiniOutputType::Curve) + return false; + + // UHoudiniAssetComponent* OuterHAC = Cast(InOuterComponent); + // + // if (!OuterHAC || OuterHAC->IsPendingKill()) + // return false; + + TMap NewOutputObjects; + TMap& OldOutputObjects = InOutput->GetOutputObjects(); + + // Iterate on all the output's HGPO + for (const FHoudiniGeoPartObject & CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + // not a curve, skip + if (CurHGPO.Type != EHoudiniPartType::Curve) + continue; + + // Check if we want to create a houdini output curve from corresponding attribute + HAPI_AttributeInfo CurveOutputAttriInfo; + FHoudiniApi::AttributeInfo_Init(&CurveOutputAttriInfo); + TArray IntData; + IntData.Empty(); + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE, CurveOutputAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + continue; + + if (IntData.Num() <= 0) + continue; + else + { + if (IntData[0] == 0) + continue; + } + + HAPI_AttributeInfo LinearAttriInfo; + FHoudiniApi::AttributeInfo_Init(&LinearAttriInfo); + IntData.Empty(); + + bool bIsLinear = false; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR, LinearAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + { + if (IntData.Num() > 0) + bIsLinear = IntData[0] == 1; + } + + HAPI_AttributeInfo ClosedAttriInfo; + FHoudiniApi::AttributeInfo_Init(&ClosedAttriInfo); + IntData.Empty(); + + bool bIsClosed = false; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED, ClosedAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + { + if (IntData.Num() > 0) + bIsClosed = IntData[0] == 1; + } + + // We output curve to Unreal Spline only for now + // May support output to Houdini Spline later + CreateOutputSplinesFromHoudiniGeoPartObject(CurHGPO, InOuterComponent, OldOutputObjects, NewOutputObjects, false, EHoudiniCurveOutputType::UnrealSpline, bIsLinear, bIsClosed); + } + + // TODO: FIX ME!!! This literally nukes all the output objects, even if they are not curves! + + // The old map now only contains unused/stale output curves destroy them + for (auto& OldPair : OldOutputObjects) + { + USceneComponent* OldSplineSceneComponent = Cast(OldPair.Value.OutputComponent); + + if (!OldSplineSceneComponent || OldSplineSceneComponent->IsPendingKill()) + continue; + + // The output object is supposed to be a spline + if (!OldSplineSceneComponent->IsA() && !OldSplineSceneComponent->IsA()) + continue; + + OldSplineSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + OldSplineSceneComponent->UnregisterComponent(); + OldSplineSceneComponent->DestroyComponent(); + } + OldOutputObjects.Empty(); + + InOutput->SetOutputObjects(NewOutputObjects); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + + return true; +} + +void +FHoudiniSplineTranslator::ReselectSelectedActors() +{ + // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? + USelection* Selection = GEditor->GetSelectedActors(); + TArray SelectedActors; + SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); + Selection->GetSelectedObjects(SelectedActors); + + GEditor->SelectNone(false, false, false); + + for (AActor* NextSelected : SelectedActors) + { + GEditor->SelectActor(NextSelected, true, true, true, true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h index 7a32561a7..2d6df4eb6 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h @@ -1,116 +1,116 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" - -class UHoudiniInput; -class UHoudiniOutput; -class UHoudiniAssetComponent; -class UHoudiniSplineComponent; -class USceneComponent; -class USplineComponent; - -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniOutputObject; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniCurveOutputType : uint8; - -struct HOUDINIENGINE_API FHoudiniSplineTranslator -{ - // Get the cooked Houdini curve. - static bool UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent); - - // Get all cooked Houdini curves of an input. - static void UpdateHoudiniInputCurves(UHoudiniInput* Input); - - // Get all cooked Houdini curves of inputs in an HAC. - static void UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC); - - // Upload Houdini spline component data to the curve node, and then sync the Houdini Spline Component with the curve node. - static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent); - - // Create a new curve node. - static bool HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent); - - // Update the curve node data, or create a new curve node if the CurveNodeId is valid. - static bool HapiCreateCurveInputNodeForData( - HAPI_NodeId& CurveNodeId, - const FString& InputNodeName, - TArray* Positions, - TArray* Rotations, - TArray* Scales3d, - EHoudiniCurveType InCurveType, - EHoudiniCurveMethod InCurveMethod, - const bool& InClosed, - const bool& InReversed, - const bool& InForceClose = false, - const FTransform& ParentTransform = FTransform::Identity); - - // Create a default curve node. - static bool HapiCreateCurveInputNode( - HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName); - - // Create a Houdini spline component from a given editable node. (Only called once when first build the editable node.) - static UHoudiniSplineComponent* CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent); - - // Helper functions. - static void ExtractStringPositions(const FString& Positions, TArray& OutPositions); - - static void ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData); - - static void ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts); - - static void CreatePositionsString(const TArray& InPositions, FString& OutPositionString); - - static bool CreateOutputSplinesFromHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO, UObject* InOuterComponent, - TMap& InSplines, - TMap& OutSplines, - const bool& InForceRebuild, - const EHoudiniCurveOutputType& OutputCurveType, - const bool& bIsLinear, - const bool& bIsclosed); - - static bool CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent); - - static USplineComponent* CreateOutputUnrealSplineComponent(const TArray& CurvePoints, - const TArray& CurveRotations, const TArray& CurveScales, UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed); - - static UHoudiniSplineComponent* CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC); - - static bool UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed); - - static bool UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent); - - static void ReselectSelectedActors(); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" + +class UHoudiniInput; +class UHoudiniOutput; +class UHoudiniAssetComponent; +class UHoudiniSplineComponent; +class USceneComponent; +class USplineComponent; + +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniOutputObject; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniCurveOutputType : uint8; + +struct HOUDINIENGINE_API FHoudiniSplineTranslator +{ + // Get the cooked Houdini curve. + static bool UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent); + + // Get all cooked Houdini curves of an input. + static void UpdateHoudiniInputCurves(UHoudiniInput* Input); + + // Get all cooked Houdini curves of inputs in an HAC. + static void UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC); + + // Upload Houdini spline component data to the curve node, and then sync the Houdini Spline Component with the curve node. + static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent); + + // Create a new curve node. + static bool HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent); + + // Update the curve node data, or create a new curve node if the CurveNodeId is valid. + static bool HapiCreateCurveInputNodeForData( + HAPI_NodeId& CurveNodeId, + const FString& InputNodeName, + TArray* Positions, + TArray* Rotations, + TArray* Scales3d, + EHoudiniCurveType InCurveType, + EHoudiniCurveMethod InCurveMethod, + const bool& InClosed, + const bool& InReversed, + const bool& InForceClose = false, + const FTransform& ParentTransform = FTransform::Identity); + + // Create a default curve node. + static bool HapiCreateCurveInputNode( + HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName); + + // Create a Houdini spline component from a given editable node. (Only called once when first build the editable node.) + static UHoudiniSplineComponent* CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent); + + // Helper functions. + static void ExtractStringPositions(const FString& Positions, TArray& OutPositions); + + static void ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData); + + static void ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts); + + static void CreatePositionsString(const TArray& InPositions, FString& OutPositionString); + + static bool CreateOutputSplinesFromHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO, UObject* InOuterComponent, + TMap& InSplines, + TMap& OutSplines, + const bool& InForceRebuild, + const EHoudiniCurveOutputType& OutputCurveType, + const bool& bIsLinear, + const bool& bIsclosed); + + static bool CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent); + + static USplineComponent* CreateOutputUnrealSplineComponent(const TArray& CurvePoints, + const TArray& CurveRotations, const TArray& CurveScales, UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed); + + static UHoudiniSplineComponent* CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC); + + static bool UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed); + + static bool UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent); + + static void ReselectSelectedActors(); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp index bc6c64da3..3a490d3e8 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp @@ -1,125 +1,125 @@ - -#include "HoudiniStringResolver.h" -#include "HoudiniEngineRuntimeUtils.h" - -void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutTokens) const -{ - for (auto& Elem : CachedTokens) - { - OutTokens.Add(Elem.Key, Elem.Value.StringValue); - } -} - -void FHoudiniStringResolver::SetToken(const FString& InName, const FString& InValue) -{ - CachedTokens.Add(InName, InValue); -} - -void FHoudiniStringResolver::SetTokensFromStringMap(const TMap& InTokens, bool bClearTokens) -{ - if (bClearTokens) - { - CachedTokens.Empty(); - } - - for (auto& Elem : InTokens) - { - CachedTokens.Add(Elem.Key, Elem.Value); - } -} - - - -FString FHoudiniStringResolver::ResolveString( - const FString& InString) const -{ - const FString Result = FString::Format(*InString, CachedTokens); - return Result; -} - -//void FHoudiniStringResolver::SetCurrentWorld(UWorld* InWorld) -//{ -// SetAttribute("world", InWorld->GetPathName()); -//} - -FString FHoudiniAttributeResolver::ResolveAttribute( - const FString& InAttrName, - const FString& InDefaultValue) const -{ - if (!CachedAttributes.Contains(InAttrName)) - { - return ResolveString(InDefaultValue); - } - FString AttrStr = CachedAttributes.FindChecked(InAttrName); - return ResolveString(AttrStr); -} - -//FString FHoudiniStringResolver::GetTempFolderArgument() const -//{ -// // The actual temp directory should have been supplied externally -// if (Tokens.Contains(TEXT("temp"))) -// return Tokens.FindChecked(TEXT("temp")); -// -// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'temp' argument. Using fallback value.")); -// return TEXT("/Game/Content/HoudiniEngine/Temp"); // Fallback value -//} -// -//FString FHoudiniStringResolver::GetBakeFolderArgument() const -//{ -// // The actual bake directory should have been supplied externally -// if (Tokens.Contains(TEXT("bake"))) -// return Tokens.FindChecked(TEXT("bake")); -// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'bake' argument. Using fallback value.")); -// return TEXT("/Game/Content/HoudiniEngine/Bake"); // Fallback value -//} -// -//FString FHoudiniStringResolver::GetOutputFolderForPackageMode(EPackageMode PackageMode) const -//{ -// switch (PackageMode) -// { -// case EPackageMode::Bake: -// return GetBakeFolderArgument(); -// case EPackageMode::CookToLevel: -// case EPackageMode::CookToTemp: -// return GetTempFolderArgument(); -// } -// return ""; -//} - - void FHoudiniAttributeResolver::SetCachedAttributes(const TMap& Attributes) - { - CachedAttributes = Attributes; - } - -void FHoudiniAttributeResolver::SetAttribute(const FString& InName, const FString& InValue) -{ - CachedAttributes.Add(InName, InValue); -} - -FString FHoudiniAttributeResolver::ResolveFullLevelPath() const -{ - FString OutputFolder = TEXT("/Game/Content/HoudiniEngine/Temp"); - - const FStringFormatArg* BaseDir = CachedTokens.Find(TEXT("out_basedir")); - if (BaseDir) - OutputFolder = BaseDir->StringValue; - - FString LevelPathAttr = ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, TEXT("{out}")); - if (LevelPathAttr.IsEmpty()) - return OutputFolder; - - return FHoudiniEngineRuntimeUtils::JoinPaths(OutputFolder, LevelPathAttr); -} - -FString FHoudiniAttributeResolver::ResolveOutputName() const -{ - FString OutputAttribName; - - if (CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2)) - OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2; - else - OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1; - - return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); -} - + +#include "HoudiniStringResolver.h" +#include "HoudiniEngineRuntimeUtils.h" + +void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutTokens) const +{ + for (auto& Elem : CachedTokens) + { + OutTokens.Add(Elem.Key, Elem.Value.StringValue); + } +} + +void FHoudiniStringResolver::SetToken(const FString& InName, const FString& InValue) +{ + CachedTokens.Add(InName, InValue); +} + +void FHoudiniStringResolver::SetTokensFromStringMap(const TMap& InTokens, bool bClearTokens) +{ + if (bClearTokens) + { + CachedTokens.Empty(); + } + + for (auto& Elem : InTokens) + { + CachedTokens.Add(Elem.Key, Elem.Value); + } +} + + + +FString FHoudiniStringResolver::ResolveString( + const FString& InString) const +{ + const FString Result = FString::Format(*InString, CachedTokens); + return Result; +} + +//void FHoudiniStringResolver::SetCurrentWorld(UWorld* InWorld) +//{ +// SetAttribute("world", InWorld->GetPathName()); +//} + +FString FHoudiniAttributeResolver::ResolveAttribute( + const FString& InAttrName, + const FString& InDefaultValue) const +{ + if (!CachedAttributes.Contains(InAttrName)) + { + return ResolveString(InDefaultValue); + } + FString AttrStr = CachedAttributes.FindChecked(InAttrName); + return ResolveString(AttrStr); +} + +//FString FHoudiniStringResolver::GetTempFolderArgument() const +//{ +// // The actual temp directory should have been supplied externally +// if (Tokens.Contains(TEXT("temp"))) +// return Tokens.FindChecked(TEXT("temp")); +// +// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'temp' argument. Using fallback value.")); +// return TEXT("/Game/Content/HoudiniEngine/Temp"); // Fallback value +//} +// +//FString FHoudiniStringResolver::GetBakeFolderArgument() const +//{ +// // The actual bake directory should have been supplied externally +// if (Tokens.Contains(TEXT("bake"))) +// return Tokens.FindChecked(TEXT("bake")); +// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'bake' argument. Using fallback value.")); +// return TEXT("/Game/Content/HoudiniEngine/Bake"); // Fallback value +//} +// +//FString FHoudiniStringResolver::GetOutputFolderForPackageMode(EPackageMode PackageMode) const +//{ +// switch (PackageMode) +// { +// case EPackageMode::Bake: +// return GetBakeFolderArgument(); +// case EPackageMode::CookToLevel: +// case EPackageMode::CookToTemp: +// return GetTempFolderArgument(); +// } +// return ""; +//} + + void FHoudiniAttributeResolver::SetCachedAttributes(const TMap& Attributes) + { + CachedAttributes = Attributes; + } + +void FHoudiniAttributeResolver::SetAttribute(const FString& InName, const FString& InValue) +{ + CachedAttributes.Add(InName, InValue); +} + +FString FHoudiniAttributeResolver::ResolveFullLevelPath() const +{ + FString OutputFolder = TEXT("/Game/Content/HoudiniEngine/Temp"); + + const FStringFormatArg* BaseDir = CachedTokens.Find(TEXT("out_basedir")); + if (BaseDir) + OutputFolder = BaseDir->StringValue; + + FString LevelPathAttr = ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, TEXT("{out}")); + if (LevelPathAttr.IsEmpty()) + return OutputFolder; + + return FHoudiniEngineRuntimeUtils::JoinPaths(OutputFolder, LevelPathAttr); +} + +FString FHoudiniAttributeResolver::ResolveOutputName() const +{ + FString OutputAttribName; + + if (CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2)) + OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2; + else + OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1; + + return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); +} + diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.h b/Source/HoudiniEngine/Private/HoudiniStringResolver.h index 152b1ad0f..dab936184 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.h +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.h @@ -1,76 +1,76 @@ - -#pragma once - -#include "CoreMinimal.h" - -#include "HoudiniStringResolver.generated.h" - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniStringResolver -{ - - GENERATED_USTRUCT_BODY(); - -protected: - - // Named arguments that will be substituted into attribute values upon retrieval. - TMap CachedTokens; - - -public: - // ---------------------------------- - // Named argument accessors - // ---------------------------------- - - TMap& GetCachedTokens() { return CachedTokens; } - - - // Set a named argument that will be used for argument replacement during GetAttribute calls. - void SetToken(const FString& InName, const FString& InValue); - - void GetTokensAsStringMap(TMap& OutTokens) const; - - void SetTokensFromStringMap(const TMap& InValue, bool bClearTokens=true); - - // Resolve a string by substituting `Tokens` as named arguments during string formatting. - FString ResolveString(const FString& InStr) const; - -}; - - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolver -{ - GENERATED_USTRUCT_BODY(); - -protected: - TMap CachedAttributes; - -public: - - void SetCachedAttributes(const TMap& Attributes); - - // Return a mutable reference to the cached attributes. - TMap& GetCachedAttributes() { return CachedAttributes; } - - // Return immutable reference to cached attributes. - const TMap& GetCachedAttributes() const { return CachedAttributes; } - - // Set an attribute with the given name and value in the attribute cache. - void SetAttribute(const FString& InName, const FString& InValue); - - // Try to resolve an attribute with the given name. If the attribute could not be - // found, use DefaultValue as a fallback. - FString ResolveAttribute(const FString& InAttrName, const FString& InDefaultValue) const; - - // ---------------------------------- - // Helpers - // ---------------------------------- - - // Helper for resolving the `unreal_level_path` attribute. - FString ResolveFullLevelPath() const; - - // Helper for resolver custom output name attributes. - FString ResolveOutputName() const; - + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniStringResolver.generated.h" + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniStringResolver +{ + + GENERATED_USTRUCT_BODY(); + +protected: + + // Named arguments that will be substituted into attribute values upon retrieval. + TMap CachedTokens; + + +public: + // ---------------------------------- + // Named argument accessors + // ---------------------------------- + + TMap& GetCachedTokens() { return CachedTokens; } + + + // Set a named argument that will be used for argument replacement during GetAttribute calls. + void SetToken(const FString& InName, const FString& InValue); + + void GetTokensAsStringMap(TMap& OutTokens) const; + + void SetTokensFromStringMap(const TMap& InValue, bool bClearTokens=true); + + // Resolve a string by substituting `Tokens` as named arguments during string formatting. + FString ResolveString(const FString& InStr) const; + +}; + + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolver +{ + GENERATED_USTRUCT_BODY(); + +protected: + TMap CachedAttributes; + +public: + + void SetCachedAttributes(const TMap& Attributes); + + // Return a mutable reference to the cached attributes. + TMap& GetCachedAttributes() { return CachedAttributes; } + + // Return immutable reference to cached attributes. + const TMap& GetCachedAttributes() const { return CachedAttributes; } + + // Set an attribute with the given name and value in the attribute cache. + void SetAttribute(const FString& InName, const FString& InValue); + + // Try to resolve an attribute with the given name. If the attribute could not be + // found, use DefaultValue as a fallback. + FString ResolveAttribute(const FString& InAttrName, const FString& InDefaultValue) const; + + // ---------------------------------- + // Helpers + // ---------------------------------- + + // Helper for resolving the `unreal_level_path` attribute. + FString ResolveFullLevelPath() const; + + // Helper for resolver custom output name attributes. + FString ResolveOutputName() const; + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp b/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp index 63ad13f59..214f00254 100644 --- a/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp +++ b/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp @@ -1,159 +1,159 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "SAssetSelectionWidget.h" - -#include "HoudiniEngineString.h" - -#if WITH_EDITOR - -#include "EditorStyleSet.h" -#include "Widgets/Layout/SBorder.h" -#include "Widgets/Input/SButton.h" - - -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 WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - -FReply -SAssetSelectionWidget::OnButtonOk() -{ - if (TSharedPtr WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - -FReply -SAssetSelectionWidget::OnButtonCancel() -{ - bIsCancelled = true; - - if (TSharedPtr WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "SAssetSelectionWidget.h" + +#include "HoudiniEngineString.h" + +#if WITH_EDITOR + +#include "EditorStyleSet.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Input/SButton.h" + + +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 WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply +SAssetSelectionWidget::OnButtonOk() +{ + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply +SAssetSelectionWidget::OnButtonCancel() +{ + bIsCancelled = true; + + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + #endif \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/SAssetSelectionWidget.h b/Source/HoudiniEngine/Private/SAssetSelectionWidget.h index 7fb29c056..520f1ccd3 100644 --- a/Source/HoudiniEngine/Private/SAssetSelectionWidget.h +++ b/Source/HoudiniEngine/Private/SAssetSelectionWidget.h @@ -1,100 +1,100 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#if WITH_EDITOR - -#include "CoreMinimal.h" -//#include "Misc/Attribute.h" - -#include "Widgets/SWidget.h" -#include "Widgets/SCompoundWidget.h" -#include "Widgets/SWindow.h" -#include "Widgets/DeclarativeSyntaxSupport.h" - -#include "HAPI/HAPI_Common.h" - -/** 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, 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; -}; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +//#include "Misc/Attribute.h" + +#include "Widgets/SWidget.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWindow.h" +#include "Widgets/DeclarativeSyntaxSupport.h" + +#include "HAPI/HAPI_Common.h" + +/** 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, 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; +}; + #endif \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp index d2214b9b6..1485c5126 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp @@ -1,422 +1,422 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#include "UnrealBrushTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniInputObject.h" - -#include "HoudiniGeoPartObject.h" -#include "Model.h" -#include "Engine/Polys.h" - -#include "HoudiniEngineRuntimeUtils.h" - -// Includes for Brush building code. Remove when the code is in the correct place. -#include "HCsgUtils.h" -#include "ActorEditorUtils.h" -#include "Misc/ScopedSlowTask.h" - -#include "Engine/Level.h" - -// TODO: Fix this -// This is currently being included to get access to the CreateFaceMaterialArray / DeleteFaceMaterialArray methods. -#include "UnrealMeshTranslator.h" - -DEFINE_LOG_CATEGORY_STATIC(LogBrushTranslator, Log, All); - -bool FUnrealBrushTranslator::CreateInputNodeForBrush( - UHoudiniInputBrush* InputBrushObject, - ABrush* BrushActor, - const TArray* ExcludeActors, - HAPI_NodeId &CreatedNodeId, - const FString& NodeName -) -{ - if (!IsValid(BrushActor)) - return false; - - if (!IsValid(BrushActor->Brush)) - return false; - - if (InputBrushObject->ShouldIgnoreThisInput()) - return true; - - //-------------------------------------------------------------------------------------------------- - // Create an input node - //-------------------------------------------------------------------------------------------------- - - HAPI_NodeId ParentNodeId = -1; - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CreatedNodeId)) - { - HAPI_NodeId InputNodeId = -1; - //FString BrushNodeName = NodeName + TEXT("_") + BrushActor->GetName(); - FString BrushNodeName = BrushActor->GetName(); - // Create Brush SOP node - std::string NodeNameRawString; - FHoudiniEngineUtils::ConvertUnrealString(BrushNodeName, NodeNameRawString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - return false; - - // We now have a valid id. - CreatedNodeId = InputNodeId; - ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); - - // Add a clean node - HAPI_NodeId CleanNodeId; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - ParentNodeId, TEXT("clean"), TEXT("clean"), true, &CleanNodeId), false); - - // Connect input node to the clean node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CleanNodeId, 0, CreatedNodeId, 0), false); - - // Set display flag on the clean node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetNodeDisplay( - FHoudiniEngine::Get().GetSession(), CleanNodeId, 1), false); - } - else - { - ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); - } - - - // Transform for positions - const FTransform ActorTransform = BrushActor->GetActorTransform(); - const FTransform ActorTransformInverse = ActorTransform.Inverse(); - // Precompute matrices for Normal transformations (see FPlane::TransformBy) - const FMatrix NmlInvXform = ActorTransformInverse.ToMatrixWithScale().TransposeAdjoint(); - float NScale = ActorTransformInverse.GetDeterminant() < 0 ? -1.f : 1.f; - - //-------------------------------------------------------------------------------------------------- - // Find actors that intersect with the given brush. - //-------------------------------------------------------------------------------------------------- - TArray BrushActors; - UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(InputBrushObject, BrushActors); - - UModel* BrushModel = UHCsgUtils::BuildModelFromBrushes(BrushActors); - InputBrushObject->UpdateCachedData(BrushModel, BrushActors); - - // DEBUG: Upload the level model (baked by UE) to Houdini - // ULevel* Level = BrushActor->GetTypedOuter(); - // BrushModel = Level->Model; - - - int NumPoints = BrushModel->Points.Num(); - if (NumPoints == 0) - { - // The content has changed and now we don't have geo to output. - // Be sure to clean up existing nodes in Houdini. - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), ParentNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *(BrushActor->GetName())); - } - CreatedNodeId = -1; - return true; - } - - //-------------------------------------------------------------------------------------------------- - // Construct the face count buffer. Also count the vertex indices in required to define the Part. - //-------------------------------------------------------------------------------------------------- - - int NumIndices = 0; - TArray FaceCountBuffer; - - { - // Calculate the size of the vertex buffer and the base vertex index of each node. - TArray& Nodes = BrushModel->Nodes; - //TArray& Surfs = BrushModel->Surfs; - TArray& Verts = BrushModel->Verts; - - int32 NumNodes = Nodes.Num(); - - FaceCountBuffer.SetNumUninitialized(NumNodes); - // Build the face counts buffer by iterating over the BSP nodes. - for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) - { - FBspNode& Node = Nodes[NodeIndex]; - FaceCountBuffer[NodeIndex] = Node.NumVertices; - NumIndices += Node.NumVertices; - } - } - - //-------------------------------------------------------------------------------------------------- - // Apply actor transform - //-------------------------------------------------------------------------------------------------- - - if (!ActorTransform.Equals(FTransform::Identity)) - { - - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ActorTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), ParentNodeId, &HapiTransform), false); - } - - //-------------------------------------------------------------------------------------------------- - // Start processing the geo and add it to the input node - //-------------------------------------------------------------------------------------------------- - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumIndices; - Part.faceCount = FaceCountBuffer.Num(); - Part.pointCount = NumPoints; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, &Part), false); - - // ----------------------------- - // Vector - Point Attribute Info - // ----------------------------- - HAPI_AttributeInfo AttributeInfoPointVector; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointVector); - AttributeInfoPointVector.count = NumPoints; - AttributeInfoPointVector.tupleSize = 3; - AttributeInfoPointVector.exists = true; - AttributeInfoPointVector.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointVector.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointVector.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector), false); - - // ----------------------------- - // Vector - Vertex Attribute Info - // ----------------------------- - HAPI_AttributeInfo AttributeInfoVertexVector; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertexVector); - AttributeInfoVertexVector.count = NumIndices; - AttributeInfoVertexVector.tupleSize = 3; - AttributeInfoVertexVector.exists = true; - AttributeInfoVertexVector.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertexVector.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertexVector.originalOwner = HAPI_ATTROWNER_INVALID; - - // ----------------------------- - // POSITION (P) - // ----------------------------- - - { - TArray< FVector > OutPosition; - FVector Scale = FVector(1.f, 1.f, 1.f); // TODO: Extract from actor transform. - OutPosition.SetNumUninitialized(NumPoints); - - for (int32 PosIndex = 0; PosIndex < NumPoints; ++PosIndex) - { - FVector Point = BrushModel->Points[PosIndex]; - Point = ActorTransformInverse.TransformPosition(Point); - FVector Pos(Point.X, Point.Z, Point.Y); - OutPosition[PosIndex] = Pos/HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - // Upload point positions. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector, - (const float *)OutPosition.GetData(), 0, AttributeInfoPointVector.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES (VertexList), NORMALS, UVS - //--------------------------------------------------------------------------------------------------------------------- - // Vertex buffer processing logic based on UModel::BuildVertexBuffers(). - { - // Calculate the size of the vertex buffer and the base vertex index of each node. - TArray& Positions = BrushModel->Points; - TArray& Nodes = BrushModel->Nodes; - TArray& Surfs = BrushModel->Surfs; - TArray& Verts = BrushModel->Verts; - TArray& Vectors = BrushModel->Vectors; - - int32 NumNodes = Nodes.Num(); - TArray Indices; - TArray OutNormals; - TArray OutUV; - TArray MaterialIndices; - TMap MaterialMap; - - Indices.SetNumUninitialized(NumIndices); - OutNormals.SetNumUninitialized(NumIndices); - OutUV.SetNumUninitialized(NumIndices); - - MaterialIndices.SetNumUninitialized(NumNodes); - - // Populate the vertex index buffer. - int32 iVertex = 0; - for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) - { - FBspNode& Node = Nodes[NodeIndex]; - FBspSurf& Surf = Surfs[Node.iSurf]; - for (int32 NodeVertexIndex = 0; NodeVertexIndex < Node.NumVertices; ++NodeVertexIndex) - { - // Vertex Index - Indices[iVertex] = Verts[Node.iVertPool + NodeVertexIndex].pVertex; - // Normal - FVector N = Vectors[Surf.vNormal]; - N = NmlInvXform.TransformVector(N).GetSafeNormal(); - - OutNormals[iVertex] = FVector(N.X, N.Z, N.Y); - // UVs - FVector& vU = Vectors[Surf.vTextureU]; - FVector& vV = Vectors[Surf.vTextureV]; - FVector deltaVtx = (Positions[Indices[iVertex]] - Positions[Surf.pBase]); - float U = FVector::DotProduct(deltaVtx, vU) / UModel::GetGlobalBSPTexelScale(); - float V = -FVector::DotProduct(deltaVtx, vV) / UModel::GetGlobalBSPTexelScale(); - OutUV[iVertex] = FVector(U, V, 0.f); - ++iVertex; - } - // Face Material - // Construct a material index array for the faces - int32 MaterialIndex = MaterialMap.FindOrAdd(Surf.Material, MaterialMap.Num()); - MaterialIndices[NodeIndex] = MaterialIndex; - } - - // Set the vertex index buffer - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, Indices.GetData(), 0, NumIndices), false); - - // Set the face counts as per the BSP nodes. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, FaceCountBuffer.GetData(), 0, FaceCountBuffer.Num()), false); - - // ----------------------------- - // Normal attribute - // ----------------------------- - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertexVector), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertexVector, (const float *)OutNormals.GetData(), - 0, AttributeInfoVertexVector.count), false); - - // ----------------------------- - // UV attribute - // ----------------------------- - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_UV, &AttributeInfoVertexVector), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_UV, - &AttributeInfoVertexVector, (const float *)OutUV.GetData(), - 0, AttributeInfoVertexVector.count), false); - - // ----------------------------- - // Material attribute - // ----------------------------- - - TArray Materials; - - if (MaterialMap.Num() > 0) - { - // Sort the output material in the correct order (by material index) - MaterialMap.ValueSort([](int32 A, int32 B) { return A < B; }); - - // Set the value in the correct order - // Do not reduce the array of materials, this could cause crahses in some weird cases.. - /*if (MaterialMap.Num() > MaterialInterfaces.Num()) - MaterialInterfaces.SetNumZeroed(MaterialMap.Num());*/ - Materials.SetNumUninitialized(MaterialMap.Num()); - - int32 MaterialIndex = 0; - for (auto Kvp : MaterialMap) - { - Materials[MaterialIndex++] = Kvp.Key; - } - } - - // Create list of materials, one for each face. - TArray< char * > OutMaterials; - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - Materials, MaterialIndices, OutMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - CreatedNodeId, - 0, - NumNodes, - OutMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(OutMaterials); - - // Delete texture material parameter names. - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedNodeId), false ); - - - return true; -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "UnrealBrushTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniInputObject.h" + +#include "HoudiniGeoPartObject.h" +#include "Model.h" +#include "Engine/Polys.h" + +#include "HoudiniEngineRuntimeUtils.h" + +// Includes for Brush building code. Remove when the code is in the correct place. +#include "HCsgUtils.h" +#include "ActorEditorUtils.h" +#include "Misc/ScopedSlowTask.h" + +#include "Engine/Level.h" + +// TODO: Fix this +// This is currently being included to get access to the CreateFaceMaterialArray / DeleteFaceMaterialArray methods. +#include "UnrealMeshTranslator.h" + +DEFINE_LOG_CATEGORY_STATIC(LogBrushTranslator, Log, All); + +bool FUnrealBrushTranslator::CreateInputNodeForBrush( + UHoudiniInputBrush* InputBrushObject, + ABrush* BrushActor, + const TArray* ExcludeActors, + HAPI_NodeId &CreatedNodeId, + const FString& NodeName +) +{ + if (!IsValid(BrushActor)) + return false; + + if (!IsValid(BrushActor->Brush)) + return false; + + if (InputBrushObject->ShouldIgnoreThisInput()) + return true; + + //-------------------------------------------------------------------------------------------------- + // Create an input node + //-------------------------------------------------------------------------------------------------- + + HAPI_NodeId ParentNodeId = -1; + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CreatedNodeId)) + { + HAPI_NodeId InputNodeId = -1; + //FString BrushNodeName = NodeName + TEXT("_") + BrushActor->GetName(); + FString BrushNodeName = BrushActor->GetName(); + // Create Brush SOP node + std::string NodeNameRawString; + FHoudiniEngineUtils::ConvertUnrealString(BrushNodeName, NodeNameRawString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + return false; + + // We now have a valid id. + CreatedNodeId = InputNodeId; + ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); + + // Add a clean node + HAPI_NodeId CleanNodeId; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + ParentNodeId, TEXT("clean"), TEXT("clean"), true, &CleanNodeId), false); + + // Connect input node to the clean node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CleanNodeId, 0, CreatedNodeId, 0), false); + + // Set display flag on the clean node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetNodeDisplay( + FHoudiniEngine::Get().GetSession(), CleanNodeId, 1), false); + } + else + { + ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); + } + + + // Transform for positions + const FTransform ActorTransform = BrushActor->GetActorTransform(); + const FTransform ActorTransformInverse = ActorTransform.Inverse(); + // Precompute matrices for Normal transformations (see FPlane::TransformBy) + const FMatrix NmlInvXform = ActorTransformInverse.ToMatrixWithScale().TransposeAdjoint(); + float NScale = ActorTransformInverse.GetDeterminant() < 0 ? -1.f : 1.f; + + //-------------------------------------------------------------------------------------------------- + // Find actors that intersect with the given brush. + //-------------------------------------------------------------------------------------------------- + TArray BrushActors; + UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(InputBrushObject, BrushActors); + + UModel* BrushModel = UHCsgUtils::BuildModelFromBrushes(BrushActors); + InputBrushObject->UpdateCachedData(BrushModel, BrushActors); + + // DEBUG: Upload the level model (baked by UE) to Houdini + // ULevel* Level = BrushActor->GetTypedOuter(); + // BrushModel = Level->Model; + + + int NumPoints = BrushModel->Points.Num(); + if (NumPoints == 0) + { + // The content has changed and now we don't have geo to output. + // Be sure to clean up existing nodes in Houdini. + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), ParentNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *(BrushActor->GetName())); + } + CreatedNodeId = -1; + return true; + } + + //-------------------------------------------------------------------------------------------------- + // Construct the face count buffer. Also count the vertex indices in required to define the Part. + //-------------------------------------------------------------------------------------------------- + + int NumIndices = 0; + TArray FaceCountBuffer; + + { + // Calculate the size of the vertex buffer and the base vertex index of each node. + TArray& Nodes = BrushModel->Nodes; + //TArray& Surfs = BrushModel->Surfs; + TArray& Verts = BrushModel->Verts; + + int32 NumNodes = Nodes.Num(); + + FaceCountBuffer.SetNumUninitialized(NumNodes); + // Build the face counts buffer by iterating over the BSP nodes. + for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) + { + FBspNode& Node = Nodes[NodeIndex]; + FaceCountBuffer[NodeIndex] = Node.NumVertices; + NumIndices += Node.NumVertices; + } + } + + //-------------------------------------------------------------------------------------------------- + // Apply actor transform + //-------------------------------------------------------------------------------------------------- + + if (!ActorTransform.Equals(FTransform::Identity)) + { + + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ActorTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), ParentNodeId, &HapiTransform), false); + } + + //-------------------------------------------------------------------------------------------------- + // Start processing the geo and add it to the input node + //-------------------------------------------------------------------------------------------------- + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumIndices; + Part.faceCount = FaceCountBuffer.Num(); + Part.pointCount = NumPoints; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, &Part), false); + + // ----------------------------- + // Vector - Point Attribute Info + // ----------------------------- + HAPI_AttributeInfo AttributeInfoPointVector; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointVector); + AttributeInfoPointVector.count = NumPoints; + AttributeInfoPointVector.tupleSize = 3; + AttributeInfoPointVector.exists = true; + AttributeInfoPointVector.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointVector.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointVector.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector), false); + + // ----------------------------- + // Vector - Vertex Attribute Info + // ----------------------------- + HAPI_AttributeInfo AttributeInfoVertexVector; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertexVector); + AttributeInfoVertexVector.count = NumIndices; + AttributeInfoVertexVector.tupleSize = 3; + AttributeInfoVertexVector.exists = true; + AttributeInfoVertexVector.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertexVector.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertexVector.originalOwner = HAPI_ATTROWNER_INVALID; + + // ----------------------------- + // POSITION (P) + // ----------------------------- + + { + TArray< FVector > OutPosition; + FVector Scale = FVector(1.f, 1.f, 1.f); // TODO: Extract from actor transform. + OutPosition.SetNumUninitialized(NumPoints); + + for (int32 PosIndex = 0; PosIndex < NumPoints; ++PosIndex) + { + FVector Point = BrushModel->Points[PosIndex]; + Point = ActorTransformInverse.TransformPosition(Point); + FVector Pos(Point.X, Point.Z, Point.Y); + OutPosition[PosIndex] = Pos/HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + // Upload point positions. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector, + (const float *)OutPosition.GetData(), 0, AttributeInfoPointVector.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES (VertexList), NORMALS, UVS + //--------------------------------------------------------------------------------------------------------------------- + // Vertex buffer processing logic based on UModel::BuildVertexBuffers(). + { + // Calculate the size of the vertex buffer and the base vertex index of each node. + TArray& Positions = BrushModel->Points; + TArray& Nodes = BrushModel->Nodes; + TArray& Surfs = BrushModel->Surfs; + TArray& Verts = BrushModel->Verts; + TArray& Vectors = BrushModel->Vectors; + + int32 NumNodes = Nodes.Num(); + TArray Indices; + TArray OutNormals; + TArray OutUV; + TArray MaterialIndices; + TMap MaterialMap; + + Indices.SetNumUninitialized(NumIndices); + OutNormals.SetNumUninitialized(NumIndices); + OutUV.SetNumUninitialized(NumIndices); + + MaterialIndices.SetNumUninitialized(NumNodes); + + // Populate the vertex index buffer. + int32 iVertex = 0; + for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) + { + FBspNode& Node = Nodes[NodeIndex]; + FBspSurf& Surf = Surfs[Node.iSurf]; + for (int32 NodeVertexIndex = 0; NodeVertexIndex < Node.NumVertices; ++NodeVertexIndex) + { + // Vertex Index + Indices[iVertex] = Verts[Node.iVertPool + NodeVertexIndex].pVertex; + // Normal + FVector N = Vectors[Surf.vNormal]; + N = NmlInvXform.TransformVector(N).GetSafeNormal(); + + OutNormals[iVertex] = FVector(N.X, N.Z, N.Y); + // UVs + FVector& vU = Vectors[Surf.vTextureU]; + FVector& vV = Vectors[Surf.vTextureV]; + FVector deltaVtx = (Positions[Indices[iVertex]] - Positions[Surf.pBase]); + float U = FVector::DotProduct(deltaVtx, vU) / UModel::GetGlobalBSPTexelScale(); + float V = -FVector::DotProduct(deltaVtx, vV) / UModel::GetGlobalBSPTexelScale(); + OutUV[iVertex] = FVector(U, V, 0.f); + ++iVertex; + } + // Face Material + // Construct a material index array for the faces + int32 MaterialIndex = MaterialMap.FindOrAdd(Surf.Material, MaterialMap.Num()); + MaterialIndices[NodeIndex] = MaterialIndex; + } + + // Set the vertex index buffer + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, Indices.GetData(), 0, NumIndices), false); + + // Set the face counts as per the BSP nodes. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, FaceCountBuffer.GetData(), 0, FaceCountBuffer.Num()), false); + + // ----------------------------- + // Normal attribute + // ----------------------------- + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertexVector), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertexVector, (const float *)OutNormals.GetData(), + 0, AttributeInfoVertexVector.count), false); + + // ----------------------------- + // UV attribute + // ----------------------------- + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_UV, &AttributeInfoVertexVector), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_UV, + &AttributeInfoVertexVector, (const float *)OutUV.GetData(), + 0, AttributeInfoVertexVector.count), false); + + // ----------------------------- + // Material attribute + // ----------------------------- + + TArray Materials; + + if (MaterialMap.Num() > 0) + { + // Sort the output material in the correct order (by material index) + MaterialMap.ValueSort([](int32 A, int32 B) { return A < B; }); + + // Set the value in the correct order + // Do not reduce the array of materials, this could cause crahses in some weird cases.. + /*if (MaterialMap.Num() > MaterialInterfaces.Num()) + MaterialInterfaces.SetNumZeroed(MaterialMap.Num());*/ + Materials.SetNumUninitialized(MaterialMap.Num()); + + int32 MaterialIndex = 0; + for (auto Kvp : MaterialMap) + { + Materials[MaterialIndex++] = Kvp.Key; + } + } + + // Create list of materials, one for each face. + TArray< char * > OutMaterials; + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + Materials, MaterialIndices, OutMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + CreatedNodeId, + 0, + NumNodes, + OutMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(OutMaterials); + + // Delete texture material parameter names. + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedNodeId), false ); + + + return true; +} + diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.h b/Source/HoudiniEngine/Private/UnrealBrushTranslator.h index f03075a2c..b47f27d28 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.h @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "UObject/NameTypes.h" - -class ABrush; -class AActor; -class UModel; -class UHoudiniInputBrush; -class ABrush; -class AActor; - -struct HOUDINIENGINE_API FUnrealBrushTranslator -{ -public: - static bool CreateInputNodeForBrush( - UHoudiniInputBrush* InputBrushObject, - ABrush* BrushActor, - const TArray* ExcludeActors, - HAPI_NodeId &CreatedNodeId, - const FString& NodeName - ); - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "UObject/NameTypes.h" + +class ABrush; +class AActor; +class UModel; +class UHoudiniInputBrush; +class ABrush; +class AActor; + +struct HOUDINIENGINE_API FUnrealBrushTranslator +{ +public: + static bool CreateInputNodeForBrush( + UHoudiniInputBrush* InputBrushObject, + ABrush* BrushActor, + const TArray* ExcludeActors, + HAPI_NodeId &CreatedNodeId, + const FString& NodeName + ); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp index b7d13e751..8a06f1276 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp @@ -1,212 +1,212 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealInstanceTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "UnrealMeshTranslator.h" - -#include "Engine/StaticMesh.h" -#include "Components/InstancedStaticMeshComponent.h" - -bool -FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( - UInstancedStaticMeshComponent* ISMC, - const FString& InNodeName, - HAPI_NodeId& OutCreatedNodeId, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bExportAsAttributeInstancer) -{ - int32 InstanceCount = ISMC->GetInstanceCount(); - if (InstanceCount < 1) - return true; - - // Get the Static Mesh instanced by the component - UStaticMesh* SM = ISMC->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - // Marshall the Static Mesh to Houdini - int32 SMNodeId = -1; - FString ISMCName = InNodeName + TEXT("_") + ISMC->GetName(); - bool bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, SMNodeId, InNodeName, ISMC, bExportLODs, bExportSockets, bExportColliders); - - if (!bSuccess) - return false; - - // To create the instance properly (via packed prim), we need to: - // - create a copytopoints (with pack and instance enable - // - an inputnode containing all of the instances transform as points - // - plug the input node and the static mesh node in the copytopoints - - // Create the copytopoints SOP. - int32 CopyNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/copytopoints"), InNodeName, true, &CopyNodeId), false); - - // set "Pack And Instance" (pack) to true - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CopyNodeId, "pack", 0, 1), false); - - // Get the copytopoints parent OBJ NodeID - HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CopyNodeId); - - // Now create an input node for the instance transforms - int32 InstancesNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - ParentNodeId, TEXT("null"), "instances", false, &InstancesNodeId), false); - - // MARSHALL THE INSTANCE TRANSFORM - { - // Get the instance transform and convert them to Position/Rotation/Scale array - TArray Positions; - Positions.SetNumZeroed(InstanceCount * 3); - TArray Rotations; - Rotations.SetNumZeroed(InstanceCount * 4); - TArray Scales; - Scales.SetNumZeroed(InstanceCount * 3); - for (int32 InstanceIdx = 0; InstanceIdx < InstanceCount; InstanceIdx++) - { - FTransform CurTransform; - ISMC->GetInstanceTransform(InstanceIdx, CurTransform); - - // Convert Unreal Position to Houdini - FVector PositionVector = CurTransform.GetLocation(); - Positions[InstanceIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Positions[InstanceIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Positions[InstanceIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - - // Convert Unreal Rotation to Houdini - FQuat RotationQuaternion = CurTransform.GetRotation(); - Rotations[InstanceIdx * 4 + 0] = RotationQuaternion.X; - Rotations[InstanceIdx * 4 + 1] = RotationQuaternion.Z; - Rotations[InstanceIdx * 4 + 2] = RotationQuaternion.Y; - Rotations[InstanceIdx * 4 + 3] = -RotationQuaternion.W; - - // Convert Unreal Scale to Houdini - FVector ScaleVector = CurTransform.GetScale3D(); - Scales[InstanceIdx * 3 + 0] = ScaleVector.X; - Scales[InstanceIdx * 3 + 1] = ScaleVector.Z; - Scales[InstanceIdx * 3 + 2] = ScaleVector.Y; - } - - // Create a part for the instance points. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = InstanceCount; - Part.type = HAPI_PARTTYPE_MESH; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InstancesNodeId, 0, &Part), false); - - // Create position (P) attribute - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = InstanceCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Positions.GetData(), 0, AttributeInfoPoint.count), false); - - // Create Rotation (rot) attribute - HAPI_AttributeInfo AttributeInfoRotation; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); - AttributeInfoRotation.count = InstanceCount; - AttributeInfoRotation.tupleSize = 4; - AttributeInfoRotation.exists = true; - AttributeInfoRotation.owner = HAPI_ATTROWNER_POINT; - AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRotation.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation, - Rotations.GetData(), 0, AttributeInfoRotation.count), false); - - // Create scale attribute - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = InstanceCount; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, - Scales.GetData(), 0, AttributeInfoScale.count), false); - - // Commit the instance point geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InstancesNodeId), false); - } - - // Connect the mesh to the copytopoints node's second input - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CopyNodeId, 0, SMNodeId, 0), false); - - // Connect the instances to the copytopoints node's second input - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CopyNodeId, 1, InstancesNodeId, 0), false); - - // Update this input object's node IDs - OutCreatedNodeId = CopyNodeId; - - return true; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealInstanceTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "UnrealMeshTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Components/InstancedStaticMeshComponent.h" + +bool +FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( + UInstancedStaticMeshComponent* ISMC, + const FString& InNodeName, + HAPI_NodeId& OutCreatedNodeId, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bExportAsAttributeInstancer) +{ + int32 InstanceCount = ISMC->GetInstanceCount(); + if (InstanceCount < 1) + return true; + + // Get the Static Mesh instanced by the component + UStaticMesh* SM = ISMC->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + // Marshall the Static Mesh to Houdini + int32 SMNodeId = -1; + FString ISMCName = InNodeName + TEXT("_") + ISMC->GetName(); + bool bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, SMNodeId, InNodeName, ISMC, bExportLODs, bExportSockets, bExportColliders); + + if (!bSuccess) + return false; + + // To create the instance properly (via packed prim), we need to: + // - create a copytopoints (with pack and instance enable + // - an inputnode containing all of the instances transform as points + // - plug the input node and the static mesh node in the copytopoints + + // Create the copytopoints SOP. + int32 CopyNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/copytopoints"), InNodeName, true, &CopyNodeId), false); + + // set "Pack And Instance" (pack) to true + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CopyNodeId, "pack", 0, 1), false); + + // Get the copytopoints parent OBJ NodeID + HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CopyNodeId); + + // Now create an input node for the instance transforms + int32 InstancesNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + ParentNodeId, TEXT("null"), "instances", false, &InstancesNodeId), false); + + // MARSHALL THE INSTANCE TRANSFORM + { + // Get the instance transform and convert them to Position/Rotation/Scale array + TArray Positions; + Positions.SetNumZeroed(InstanceCount * 3); + TArray Rotations; + Rotations.SetNumZeroed(InstanceCount * 4); + TArray Scales; + Scales.SetNumZeroed(InstanceCount * 3); + for (int32 InstanceIdx = 0; InstanceIdx < InstanceCount; InstanceIdx++) + { + FTransform CurTransform; + ISMC->GetInstanceTransform(InstanceIdx, CurTransform); + + // Convert Unreal Position to Houdini + FVector PositionVector = CurTransform.GetLocation(); + Positions[InstanceIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Positions[InstanceIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Positions[InstanceIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + + // Convert Unreal Rotation to Houdini + FQuat RotationQuaternion = CurTransform.GetRotation(); + Rotations[InstanceIdx * 4 + 0] = RotationQuaternion.X; + Rotations[InstanceIdx * 4 + 1] = RotationQuaternion.Z; + Rotations[InstanceIdx * 4 + 2] = RotationQuaternion.Y; + Rotations[InstanceIdx * 4 + 3] = -RotationQuaternion.W; + + // Convert Unreal Scale to Houdini + FVector ScaleVector = CurTransform.GetScale3D(); + Scales[InstanceIdx * 3 + 0] = ScaleVector.X; + Scales[InstanceIdx * 3 + 1] = ScaleVector.Z; + Scales[InstanceIdx * 3 + 2] = ScaleVector.Y; + } + + // Create a part for the instance points. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = InstanceCount; + Part.type = HAPI_PARTTYPE_MESH; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InstancesNodeId, 0, &Part), false); + + // Create position (P) attribute + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = InstanceCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Positions.GetData(), 0, AttributeInfoPoint.count), false); + + // Create Rotation (rot) attribute + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + AttributeInfoRotation.count = InstanceCount; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation, + Rotations.GetData(), 0, AttributeInfoRotation.count), false); + + // Create scale attribute + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = InstanceCount; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, + Scales.GetData(), 0, AttributeInfoScale.count), false); + + // Commit the instance point geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InstancesNodeId), false); + } + + // Connect the mesh to the copytopoints node's second input + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CopyNodeId, 0, SMNodeId, 0), false); + + // Connect the instances to the copytopoints node's second input + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CopyNodeId, 1, InstancesNodeId, 0), false); + + // Update this input object's node IDs + OutCreatedNodeId = CopyNodeId; + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h index 904cf8ec6..9ef5cbb1a 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h @@ -1,49 +1,49 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -class UInstancedStaticMeshComponent; - -struct HOUDINIENGINE_API FUnrealInstanceTranslator -{ - public: - - // HAPI : Marshaling, extract geometry and create input asset for it - return true on success - static bool HapiCreateInputNodeForInstancer( - UInstancedStaticMeshComponent* ISMC, - const FString& InNodeName, - HAPI_NodeId& OutCreatedNodeId, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bExportAsAttributeInstancer); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +class UInstancedStaticMeshComponent; + +struct HOUDINIENGINE_API FUnrealInstanceTranslator +{ + public: + + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForInstancer( + UInstancedStaticMeshComponent* ISMC, + const FString& InNodeName, + HAPI_NodeId& OutCreatedNodeId, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bExportAsAttributeInstancer); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp index b36ba936e..f14c42665 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp @@ -1,1990 +1,1990 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#include "HoudiniApi.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" - -#include "UnrealLandscapeTranslator.h" -#include "HoudiniGeoPartObject.h" - -#include "Landscape.h" -#include "LandscapeDataAccess.h" -#include "LandscapeEdit.h" -#include "LightMap.h" -#include "Engine/MapBuildDataRegistry.h" -#include "PhysicalMaterials/PhysicalMaterial.h" - - -bool -FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( - ALandscapeProxy* LandscapeProxy, - HAPI_NodeId& CreatedNodeId, - const FString& InputNodeNameString, - const bool& bExportGeometryAsMesh, - const bool& bExportTileUVs, - const bool bExportNormalizedUVs, - const bool bExportLighting, - const bool bExportMaterials ) -{ - //-------------------------------------------------------------------------------------------------- - // 1. Create an input node - //-------------------------------------------------------------------------------------------------- - HAPI_NodeId InputNodeId = -1; - // Create the curve SOP Node - std::string NodeNameRawString; - FHoudiniEngineUtils::ConvertUnrealString(InputNodeNameString, NodeNameRawString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - return false; - - // We now have a valid id. - CreatedNodeId = InputNodeId; - - if(!FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true)) - return false; - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); - */ - //-------------------------------------------------------------------------------------------------- - // 2. Set the part info - //-------------------------------------------------------------------------------------------------- - int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; - float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; - - //int32 NumComponents = bExportOnlySelected ? SelectedComponents.Num() : LandscapeProxy->LandscapeComponents.Num(); - int32 NumComponents = LandscapeProxy->LandscapeComponents.Num(); - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - int32 VertexCount = NumComponents * VertexCountPerComponent; - if (!VertexCount) - return false; - - int32 TriangleCount = NumComponents * FMath::Square(ComponentSizeQuads) * 2; - int32 QuadCount = NumComponents * FMath::Square(ComponentSizeQuads); - int32 IndexCount = QuadCount * 4; - - // Create part info - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - //FMemory::Memzero< HAPI_PartInfo >(Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = VertexCount; - Part.type = HAPI_PARTTYPE_MESH; - - // If we are exporting to a mesh, we need vertices and faces - if (bExportGeometryAsMesh) - { - Part.vertexCount = IndexCount; - Part.faceCount = QuadCount; - } - - // Set the part infos - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, &DisplayGeoInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part), false); - - //-------------------------------------------------------------------------------------------------- - // 3. Extract the landscape data - //-------------------------------------------------------------------------------------------------- - // Array for the position data - TArray LandscapePositionArray; - // Array for the normals - TArray LandscapeNormalArray; - // Array for the UVs - TArray LandscapeUVArray; - // Array for the vertex index of each point in its component - TArray LandscapeComponentVertexIndicesArray; - // Array for the tile names per point - TArray LandscapeComponentNameArray; - // Array for the lightmap values - TArray LandscapeLightmapValues; - // Selected components set to all components in current landscape proxy - TSet SelectedComponents; - SelectedComponents.Append(LandscapeProxy->LandscapeComponents); - - // Extract all the data from the landscape to the arrays - if (!ExtractLandscapeData( - LandscapeProxy, SelectedComponents, - bExportLighting, bExportTileUVs, bExportNormalizedUVs, - LandscapePositionArray, LandscapeNormalArray, - LandscapeUVArray, LandscapeComponentVertexIndicesArray, - LandscapeComponentNameArray, LandscapeLightmapValues)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 3. Set the corresponding attributes in Houdini - //-------------------------------------------------------------------------------------------------- - - // Create point attribute info containing positions. - if (!AddLandscapePositionAttribute(DisplayGeoInfo.nodeId, LandscapePositionArray)) - return false; - - // Create point attribute info containing normals. - if (!AddLandscapeNormalAttribute(DisplayGeoInfo.nodeId, LandscapeNormalArray)) - return false; - - // Create point attribute info containing UVs. - if (!AddLandscapeUVAttribute(DisplayGeoInfo.nodeId, LandscapeUVArray)) - return false; - - // Create point attribute containing landscape component vertex indices (indices of vertices within the grid - x,y). - if (!AddLandscapeComponentVertexIndicesAttribute(DisplayGeoInfo.nodeId, LandscapeComponentVertexIndicesArray)) - return false; - - // Create point attribute containing landscape component name. - if (!AddLandscapeComponentNameAttribute(DisplayGeoInfo.nodeId, LandscapeComponentNameArray)) - return false; - - // Create point attribute info containing lightmap information. - if (bExportLighting) - { - if (!AddLandscapeLightmapColorAttribute(DisplayGeoInfo.nodeId, LandscapeLightmapValues)) - return false; - } - - // Set indices if we are exporting full geometry. - if (bExportGeometryAsMesh) - { - if (!AddLandscapeMeshIndicesAndMaterialsAttribute( - DisplayGeoInfo.nodeId, - bExportMaterials, - ComponentSizeQuads, - QuadCount, - LandscapeProxy, - SelectedComponents)) - return false; - } - - // If we are marshalling material information. - if (bExportMaterials) - { - if (!AddLandscapeGlobalMaterialAttribute(DisplayGeoInfo.nodeId, LandscapeProxy)) - return false; - } - - /* - // TODO: Move this to ExtractLandscapeData() - //-------------------------------------------------------------------------------------------------- - // 4. Extract and convert all the layers - //-------------------------------------------------------------------------------------------------- - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Get the landscape X/Y Size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - return false; - - // Calc the X/Y size in points - int32 XSize = (MaxX - MinX + 1); - int32 YSize = (MaxY - MinY + 1); - if ((XSize < 2) || (YSize < 2)) - return false; - - bool MaskInitialized = false; - int32 NumLayers = LandscapeInfo->Layers.Num(); - for (int32 n = 0; n < NumLayers; n++) - { - // 1. Extract the uint8 values from the layer - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!GetLandscapeLayerData( - LandscapeInfo, n, - MinX, MinY, MaxX, MaxY, - CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - // 2. Convert unreal uint8 values to floats - // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float - HAPI_VolumeInfo CurrentLayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); - TArray CurrentLayerFloatData; - if (!ConvertLandscapeLayerDataToHeightfieldData( - CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, - CurrentLayerFloatData, CurrentLayerVolumeInfo)) - continue; - - if (!AddLandscapeLayerAttribute( - DisplayGeoInfo.nodeId, CurrentLayerFloatData, LayerName)) - continue; - } - */ - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId), false); - - // TODO: Remove me! - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); - */ - - return FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true); -} - -bool -FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( - ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId, const FString& InputNodeNameStr) -{ - if (!LandscapeProxy) - return false; - - // Export the whole landscape and its layer as a single heightfield. - - //-------------------------------------------------------------------------------------------------- - // 1. Extracting the height data - //-------------------------------------------------------------------------------------------------- - TArray HeightData; - int32 XSize, YSize; - FVector Min, Max; - if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 2. Convert the height uint16 data to float - //-------------------------------------------------------------------------------------------------- - TArray HeightfieldFloatValues; - HAPI_VolumeInfo HeightfieldVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); - FTransform LandscapeTransform = LandscapeProxy->ActorToWorld(); - FVector CenterOffset = FVector::ZeroVector; - if (!ConvertLandscapeDataToHeightfieldData( - HeightData, XSize, YSize, Min, Max, LandscapeTransform, - HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 3. Create the Heightfield Input Node - //-------------------------------------------------------------------------------------------------- - HAPI_NodeId HeightFieldId = -1; - HAPI_NodeId HeightId = -1; - HAPI_NodeId MaskId = -1; - HAPI_NodeId MergeId = -1; - if (!CreateHeightfieldInputNode(InputNodeNameStr, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 4. Set the HeightfieldData in Houdini - //-------------------------------------------------------------------------------------------------- - // Set the Height volume's data - HAPI_PartId PartId = 0; - if (!SetHeighfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) - return false; - - // Add the materials used - UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); - UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); - UPhysicalMaterial* LandscapePhysMat = LandscapeProxy->DefaultPhysMaterial; - AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(HeightId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(HeightId, PartId, LandscapeProxy, 1); - - // Add the unreal_level_path attribute - ULevel* Level = LandscapeProxy->GetLevel(); - if (Level) - { - FHoudiniEngineUtils::AddLevelPathAttribute(HeightId, PartId, Level, 1); - /* - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToVolume(HeightId, PartId, LevelPath); - */ - } - - // Commit the height volume - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), HeightId), false); - - //-------------------------------------------------------------------------------------------------- - // 5. Extract and convert all the layers - //-------------------------------------------------------------------------------------------------- - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - bool MaskInitialized = false; - int32 MergeInputIndex = 2; - int32 NumLayers = LandscapeInfo->Layers.Num(); - for (int32 n = 0; n < NumLayers; n++) - { - // 1. Extract the uint8 values from the layer - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!GetLandscapeLayerData(LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - // 2. Convert unreal uint8 values to floats - // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float - HAPI_VolumeInfo CurrentLayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); - TArray CurrentLayerFloatData; - if (!ConvertLandscapeLayerDataToHeightfieldData( - CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, - CurrentLayerFloatData, CurrentLayerVolumeInfo)) - continue; - - // We reuse the height layer's transform - CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; - - // 3. See if we need to create an input volume, or can reuse the HF's default mask volume - bool IsMask = false; - if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) - IsMask = true; - - HAPI_NodeId LayerVolumeNodeId = -1; - if (!IsMask) - { - // Current layer is not mask, so we need to create a new input volume - std::string LayerNameStr; - FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); - - FHoudiniApi::CreateHeightfieldInputVolumeNode( - FHoudiniEngine::Get().GetSession(), - HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); - } - else - { - // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node - LayerVolumeNodeId = MaskId; - } - - // Check if we have a valid id for the input volume. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) - continue; - - // 4. Set the layer/mask heighfield data in Houdini - HAPI_PartId CurrentPartId = 0; - if (!SetHeighfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) - continue; - - // Get the physical material used by that layer - UPhysicalMaterial* LayerPhysicalMat = LandscapePhysMat; - { - FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; - ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; - if (LayerInfo) - LayerPhysicalMat = LayerInfo->PhysMaterial; - } - - // Also add the material attributes to the layer volumes - AddLandscapeMaterialAttributesToVolume(LayerVolumeNodeId, PartId, LandscapeMat, LandscapeHoleMat, LayerPhysicalMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(LayerVolumeNodeId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(LayerVolumeNodeId, PartId, LandscapeProxy, 1); - - // Also add the level path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(LayerVolumeNodeId, PartId, Level, 1); - //AddLevelPathAttributeToVolume(LayerVolumeNodeId, PartId, LevelPath); - - // Commit the volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); - - if (!IsMask) - { - // We had to create a new volume for this layer, so we need to connect it to the HF's merge node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - MergeId, MergeInputIndex, LayerVolumeNodeId, 0), false); - - MergeInputIndex++; - } - else - { - MaskInitialized = true; - } - } - - // We need to have a mask layer as it is required for proper heightfield functionalities - // Setting the volume info on the mask is needed for the HF to have proper transform in H! - // If we didn't create a mask volume before, send a default one now - if (!MaskInitialized) - { - MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); - - // Add the materials used - AddLandscapeMaterialAttributesToVolume(MaskId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(MaskId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(MaskId, PartId, LandscapeProxy, 1); - - // Also add the level path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(MaskId, PartId, Level, 1); - //AddLevelPathAttributeToVolume(MaskId, PartId, LevelPath); - - // Commit the mask volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), MaskId), false); - } - - HAPI_TransformEuler HAPIObjectTransform; - FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); - //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); - LandscapeTransform.SetScale3D(FVector::OneVector); - FHoudiniEngineUtils::TranslateUnrealTransform(LandscapeTransform, HAPIObjectTransform); - HAPIObjectTransform.position[1] = 0.0f; - - HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(HeightFieldId); - FHoudiniApi::SetObjectTransform(FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform); - - // Since HF are centered but landscape aren't, we need to set the HF's center parameter - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); - - // Finally, cook the Heightfield node - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), HeightFieldId, &CookOptions), false); - */ - if(!FHoudiniEngineUtils::HapiCookNode(HeightFieldId, nullptr, true)) - return false; - - CreatedHeightfieldNodeId = HeightFieldId; - - return true; -} - -// Converts Unreal uint16 values to Houdini Float -bool -FUnrealLandscapeTranslator::ConvertLandscapeLayerDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - const FLinearColor& LayerUsageDebugColor, - TArray& LayerFloatValues, - HAPI_VolumeInfo& LayerVolumeInfo) -{ - LayerFloatValues.Empty(); - - int32 HoudiniXSize = YSize; - int32 HoudiniYSize = XSize; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - if (IntHeightData.Num() != SizeInPoints) - return false; - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to float - //-------------------------------------------------------------------------------------------------- - - // By default, values are converted from unreal [0 255] uint8 to Houdini [0 1] float - // uint8 min/max - uint8 IntMin = 0; - uint8 IntMax = UINT8_MAX; - // The range in Digits - double DigitRange = (double)UINT8_MAX; - - // By default, the values will be converted to [0, 1] - float LayerMin = 0.0f; - float LayerMax = 1.0f; - float LayerSpacing = 1.0f / DigitRange; - - // If this layer came from Houdini, its alpha value should be PI - // This indicates that we can extract additional infos stored its debug usage color - // so we can reconstruct the original source values (float) more accurately - if (LayerUsageDebugColor.A == PI) - { - // We need the ZMin / ZMax uint8 values - IntMin = IntHeightData[0]; - IntMax = IntMin; - for (int n = 0; n < IntHeightData.Num(); n++) - { - if (IntHeightData[n] < IntMin) - IntMin = IntHeightData[n]; - if (IntHeightData[n] > IntMax) - IntMax = IntHeightData[n]; - } - - DigitRange = (double)IntMax - (double)IntMin; - - // Read the original min/max and spacing stored in the debug color - LayerMin = LayerUsageDebugColor.R; - LayerMax = LayerUsageDebugColor.G; - LayerSpacing = LayerUsageDebugColor.B; - } - - // Convert the Int data to Float - LayerFloatValues.SetNumUninitialized(SizeInPoints); - - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // We need to invert X/Y when reading the value from Unreal - int32 nHoudini = nX + nY * HoudiniXSize; - int32 nUnreal = nY + nX * XSize; - - // Convert the int values to meter - // Unreal's digit value have a zero value of 32768 - double DoubleValue = ((double)IntHeightData[nUnreal] - (double)IntMin) * LayerSpacing + LayerMin; - LayerFloatValues[nHoudini] = (float)DoubleValue; - } - } - - /* - // Verifying the converted ZMin / ZMax - float FloatMin = LayerFloatValues[0]; - float FloatMax = FloatMin; - for (int32 n = 0; n < LayerFloatValues.Num(); n++) - { - if (LayerFloatValues[n] < FloatMin) - FloatMin = LayerFloatValues[n]; - if (LayerFloatValues[n] > FloatMax) - FloatMax = LayerFloatValues[n]; - } - */ - - //-------------------------------------------------------------------------------------------------- - // 2. Fill the volume info - //-------------------------------------------------------------------------------------------------- - LayerVolumeInfo.xLength = HoudiniXSize; - LayerVolumeInfo.yLength = HoudiniYSize; - LayerVolumeInfo.zLength = 1; - - LayerVolumeInfo.minX = 0; - LayerVolumeInfo.minY = 0; - LayerVolumeInfo.minZ = 0; - - LayerVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; - LayerVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; - LayerVolumeInfo.tupleSize = 1; - LayerVolumeInfo.tileSize = 1; - - LayerVolumeInfo.hasTaper = false; - LayerVolumeInfo.xTaper = 0.0; - LayerVolumeInfo.yTaper = 0.0; - - // The layer transform will have to be copied from the main heightfield's transform - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeData( - ALandscapeProxy* LandscapeProxy, - TArray& HeightData, - int32& XSize, int32& YSize, - FVector& Min, FVector& Max) -{ - if (!LandscapeProxy) - return false; - - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Get the landscape extents to get its size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - - // To handle streaming proxies correctly, get the extents via all the components, - // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. - for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) - { - Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); - } - - if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize)) - return false; - - // Get the landscape Min/Max values - // Do not use Landscape->GetActorBounds() here as instanced geo - // (due to grass layers for example) can cause it to return incorrect bounds! - FVector Origin, Extent; - GetLandscapeProxyBounds(LandscapeProxy, Origin, Extent); - - // Get the landscape Min/Max values - Min = Origin - Extent; - Max = Origin + Extent; - - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeData( - ULandscapeInfo* LandscapeInfo, - const int32& MinX, const int32& MinY, - const int32& MaxX, const int32& MaxY, - TArray& HeightData, - int32& XSize, int32& YSize) -{ - if (!LandscapeInfo) - return false; - - // Get the X/Y size in points - XSize = (MaxX - MinX + 1); - YSize = (MaxY - MinY + 1); - - if ((XSize < 2) || (YSize < 2)) - return false; - - // Extracting the uint16 values from the landscape - FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); - HeightData.AddZeroed(XSize * YSize); - LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0); - - return true; -} - - -void -FUnrealLandscapeTranslator::GetLandscapeProxyBounds( - ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents) -{ - // Iterate only on the landscape components - FBox Bounds(ForceInit); - for (const UActorComponent* ActorComponent : LandscapeProxy->GetComponents()) - { - const ULandscapeComponent* LandscapeComp = Cast(ActorComponent); - if (LandscapeComp && LandscapeComp->IsRegistered()) - Bounds += LandscapeComp->Bounds.GetBox(); - } - - // Convert the bounds to origin/offset vectors - Bounds.GetCenterAndExtents(Origin, Extents); -} - -bool -FUnrealLandscapeTranslator::ConvertLandscapeDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - FVector Min, FVector Max, - const FTransform& LandscapeTransform, - TArray& HeightfieldFloatValues, - HAPI_VolumeInfo& HeightfieldVolumeInfo, - FVector& CenterOffset) -{ - HeightfieldFloatValues.Empty(); - - int32 HoudiniXSize = YSize; - int32 HoudiniYSize = XSize; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - if (IntHeightData.Num() != SizeInPoints) - return false; - - // Use default unreal scaling for marshalling landscapes - // A lot of precision will be lost in order to keep the same transform as the landscape input - bool bUseDefaultUE4Scaling = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) - bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to float - //-------------------------------------------------------------------------------------------------- - - - // Convert the min/max values from cm to meters - Min /= 100.0; - Max /= 100.0; - - // Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0 - // To convert the uint16 values to float "metric" values, offset the int by 32768 to center it, - // then scale it - - // Spacing used to convert from uint16 to meters - double ZSpacing = 512.0 / ((double)UINT16_MAX); - ZSpacing *= ((double)LandscapeTransform.GetScale3D().Z / 100.0); - - // Center value in meters (Landscape ranges from [-255:257] meters at default scale - double ZCenterOffset = 32767; - double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f; - // Convert the Int data to Float - HeightfieldFloatValues.SetNumUninitialized(SizeInPoints); - - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // We need to invert X/Y when reading the value from Unreal - int32 nHoudini = nX + nY * HoudiniXSize; - int32 nUnreal = nY + nX * XSize; - - // Convert the int values to meter - // Unreal's digit value have a zero value of 32768 - double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset; - HeightfieldFloatValues[nHoudini] = (float)DoubleValue; - } - } - - //-------------------------------------------------------------------------------------------------- - // 2. Convert the Unreal Transform to a HAPI_transform - //-------------------------------------------------------------------------------------------------- - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - //FMemory::Memzero< HAPI_Transform >( HapiTransform ); - { - FQuat Rotation = LandscapeTransform.GetRotation(); - if (Rotation != FQuat::Identity) - { - //Swap(ObjectRotation.Y, ObjectRotation.Z); - HapiTransform.rotationQuaternion[0] = Rotation.X; - HapiTransform.rotationQuaternion[1] = Rotation.Z; - HapiTransform.rotationQuaternion[2] = Rotation.Y; - HapiTransform.rotationQuaternion[3] = -Rotation.W; - } - else - { - HapiTransform.rotationQuaternion[0] = 0; - HapiTransform.rotationQuaternion[1] = 0; - HapiTransform.rotationQuaternion[2] = 0; - HapiTransform.rotationQuaternion[3] = 1; - } - - // Heightfield are centered, landscapes are not - CenterOffset = (Max - Min) * 0.5f; - - // Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform) - //FVector Position = LandscapeTransform.GetLocation() / 100.0f; - HapiTransform.position[1] = 0.0f;//Position.X + CenterOffset.X; - HapiTransform.position[0] = 0.0f;//Position.Y + CenterOffset.Y; - HapiTransform.position[2] = 0.0f; - - FVector Scale = LandscapeTransform.GetScale3D() / 100.0f; - HapiTransform.scale[0] = Scale.X * 0.5f * HoudiniXSize; - HapiTransform.scale[1] = Scale.Y * 0.5f * HoudiniYSize; - HapiTransform.scale[2] = 0.5f; - if (bUseDefaultUE4Scaling) - HapiTransform.scale[2] *= Scale.Z; - - HapiTransform.shear[0] = 0.0f; - HapiTransform.shear[1] = 0.0f; - HapiTransform.shear[2] = 0.0f; - } - - //-------------------------------------------------------------------------------------------------- - // 3. Fill the volume info - //-------------------------------------------------------------------------------------------------- - HeightfieldVolumeInfo.xLength = HoudiniXSize; - HeightfieldVolumeInfo.yLength = HoudiniYSize; - HeightfieldVolumeInfo.zLength = 1; - - HeightfieldVolumeInfo.minX = 0; - HeightfieldVolumeInfo.minY = 0; - HeightfieldVolumeInfo.minZ = 0; - - HeightfieldVolumeInfo.transform = HapiTransform; - - HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; - HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; - HeightfieldVolumeInfo.tupleSize = 1; - HeightfieldVolumeInfo.tileSize = 1; - - HeightfieldVolumeInfo.hasTaper = false; - HeightfieldVolumeInfo.xTaper = 0.0; - HeightfieldVolumeInfo.yTaper = 0.0; - - return true; -} - -bool -FUnrealLandscapeTranslator::CreateHeightfieldInputNode( - const FString& NodeName, - const int32& XSize, - const int32& YSize, - HAPI_NodeId& HeightfieldNodeId, - HAPI_NodeId& HeightNodeId, - HAPI_NodeId& MaskNodeId, - HAPI_NodeId& MergeNodeId) -{ - // Make sure the Heightfield node doesnt already exists - if (HeightfieldNodeId != -1) - return false; - - // Convert the node's name - std::string NameStr; - FHoudiniEngineUtils::ConvertUnrealString(NodeName, NameStr); - - // Create the heigthfield node via HAPI - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightFieldInput( - FHoudiniEngine::Get().GetSession(), - -1, NameStr.c_str(), XSize, YSize, 1.0f, HAPI_HeightFieldSampling::HAPI_HEIGHTFIELD_SAMPLING_CORNER, - &HeightfieldNodeId, &HeightNodeId, &MaskNodeId, &MergeNodeId), false); - - // Cook it - return FHoudiniEngineUtils::HapiCookNode(HeightfieldNodeId, nullptr, true); - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), HeightfieldNodeId, &CookOptions), false); - - return true; - */ -} - -bool -FUnrealLandscapeTranslator::SetHeighfieldData( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - TArray& FloatValues, - const HAPI_VolumeInfo& VolumeInfo, - const FString& HeightfieldName) -{ - // Cook the node to get proper infos on it - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, &CookOptions), false); - */ - if(!FHoudiniEngineUtils::HapiCookNode(VolumeNodeId, nullptr, true)) - return false; - - // Read the geo/part/volume info from the volume node - HAPI_GeoInfo GeoInfo; - FHoudiniApi::GeoInfo_Init(&GeoInfo); - //FMemory::Memset< HAPI_GeoInfo >(GeoInfo, 0); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, &GeoInfo), false); - - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - //FMemory::Memset< HAPI_PartInfo >(PartInfo, 0); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), - GeoInfo.nodeId, PartId, &PartInfo), false); - - // Update the volume infos - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, PartInfo.id, &VolumeInfo), false); - - // Volume name - std::string NameStr; - FHoudiniEngineUtils::ConvertUnrealString(HeightfieldName, NameStr); - - // Set the Heighfield data on the volume - float * HeightData = FloatValues.GetData(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetHeightFieldData( - FHoudiniEngine::Get().GetSession(), - GeoInfo.nodeId, PartInfo.id, NameStr.c_str(), HeightData, 0, FloatValues.Num()), false); - - return true; -} - -bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - UMaterialInterface* InLandscapeMaterial, - UMaterialInterface* InLandscapeHoleMaterial, - UPhysicalMaterial* InPhysicalMaterial) -{ - if (VolumeNodeId == -1) - return false; - - // LANDSCAPE MATERIAL - if (InLandscapeMaterial && !InLandscapeMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InLandscapeMaterialString = InLandscapeMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - // HOLE MATERIAL - if (InLandscapeHoleMaterial && !InLandscapeHoleMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InLandscapeMaterialString = InLandscapeHoleMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (Result == HAPI_RESULT_SUCCESS) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_hole_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - // PHYSICAL MATERIAL - if (InPhysicalMaterial && !InPhysicalMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InPhysMatlString = InPhysicalMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (Result == HAPI_RESULT_SUCCESS) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InPhysMatlString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_physical_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - return true; -} - -/* -bool -FUnrealLandscapeTranslator::AddLevelPathAttributeToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - const FString& LevelPath) -{ - if (VolumeNodeId == -1) - return false; - - // LANDSCAPE MATERIAL - if (LevelPath.IsEmpty()) - return false; - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = 1; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to cont char * - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray LevelPathArr; - LevelPathArr.Add(LevelPathCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - LevelPathArr.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} -*/ - -bool -FUnrealLandscapeTranslator::GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, const int32& LayerIndex, - TArray& LayerData, FLinearColor& LayerUsageDebugColor, - FString& LayerName) -{ - if (!LandscapeInfo) - return false; - - // Get the landscape X/Y Size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - return false; - - if (!GetLandscapeLayerData( - LandscapeInfo, LayerIndex, - MinX, MinY, MaxX, MaxY, - LayerData, LayerUsageDebugColor, LayerName)) - return false; - - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - const int32& MinX, const int32& MinY, - const int32& MaxX, const int32& MaxY, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName) -{ - if (!LandscapeInfo) - return false; - - if (!LandscapeInfo->Layers.IsValidIndex(LayerIndex)) - return false; - - FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[LayerIndex]; - ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; - if (!LayerInfo) - return false; - - // Calc the X/Y size in points - int32 XSize = (MaxX - MinX + 1); - int32 YSize = (MaxY - MinY + 1); - if ((XSize < 2) || (YSize < 2)) - return false; - - // extracting the uint8 values from the layer - FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); - LayerData.AddZeroed(XSize * YSize); - LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, LayerData.GetData(), 0); - - LayerUsageDebugColor = LayerInfo->LayerUsageDebugColor; - - LayerName = LayersSetting.GetLayerName().ToString(); - - return true; -} - -bool -FUnrealLandscapeTranslator::InitDefaultHeightfieldMask( - const HAPI_VolumeInfo& HeightVolumeInfo, - const HAPI_NodeId& MaskVolumeNodeId) -{ - // We need to have a mask layer as it is required for proper heightfield functionalities - - // Creating an array filled with 0.0 - TArray< float > MaskFloatData; - MaskFloatData.Init(0.0f, HeightVolumeInfo.xLength * HeightVolumeInfo.yLength); - - // Creating the volume infos - HAPI_VolumeInfo MaskVolumeInfo = HeightVolumeInfo; - - // Set the heighfield data in Houdini - FString MaskName = TEXT("mask"); - HAPI_PartId PartId = 0; - if (!SetHeighfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) - return false; - - return true; -} - -bool -FUnrealLandscapeTranslator::DestroyLandscapeAssetNode(HAPI_NodeId& ConnectedAssetId, TArray& CreatedInputAssetIds) -{ - HAPI_AssetInfo NodeAssetInfo; - FHoudiniApi::AssetInfo_Init(&NodeAssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeAssetInfo), false); - - FHoudiniEngineString AssetOpName(NodeAssetInfo.fullOpNameSH); - FString OpName; - if (!AssetOpName.ToFString(OpName)) - return false; - - if (!OpName.Contains(TEXT("xform"))) - { - // Not a transform node, so not a Heightfield - // We just need to destroy the landscape asset node - return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); - } - - // The landscape was marshalled as a heightfield, so we need to destroy and disconnect - // the volvis nodes, all the merge node's input (each merge input is a volume for one - // of the layer/mask of the landscape ) - - // Query the volvis node id - // The volvis node is the fist input of the xform node - HAPI_NodeId VolvisNodeId = -1; - FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ConnectedAssetId, 0, &VolvisNodeId); - - // First, destroy the merge node and its inputs - // The merge node is in the first input of the volvis node - HAPI_NodeId MergeNodeId = -1; - FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - VolvisNodeId, 0, &MergeNodeId); - - if (MergeNodeId != -1) - { - // Get the merge node info - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), MergeNodeId, &NodeInfo), false); - - for (int32 n = 0; n < NodeInfo.inputCount; n++) - { - // Get the Input node ID from the host ID - HAPI_NodeId InputNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - MergeNodeId, n, &InputNodeId)) - break; - - if (InputNodeId == -1) - break; - - // Disconnect and Destroy that input - FHoudiniEngineUtils::HapiDisconnectAsset(MergeNodeId, n); - FHoudiniEngineUtils::DestroyHoudiniAsset(InputNodeId); - } - } - - // Second step, destroy all the volumes GEO assets - for (HAPI_NodeId AssetNodeId : CreatedInputAssetIds) - { - FHoudiniEngineUtils::DestroyHoudiniAsset(AssetNodeId); - } - CreatedInputAssetIds.Empty(); - - // Finally disconnect and destroy the xform, volvis and merge nodes, then destroy them - FHoudiniEngineUtils::HapiDisconnectAsset(ConnectedAssetId, 0); - FHoudiniEngineUtils::HapiDisconnectAsset(VolvisNodeId, 0); - FHoudiniEngineUtils::DestroyHoudiniAsset(MergeNodeId); - FHoudiniEngineUtils::DestroyHoudiniAsset(VolvisNodeId); - - return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); -} - - -bool -FUnrealLandscapeTranslator::ExtractLandscapeData( - ALandscapeProxy * LandscapeProxy, TSet& SelectedComponents, - const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, - TArray& LandscapePositionArray, - TArray& LandscapeNormalArray, - TArray& LandscapeUVArray, - TArray& LandscapeComponentVertexIndicesArray, - TArray& LandscapeComponentNameArray, - TArray& LandscapeLightmapValues) -{ - if (!LandscapeProxy) - return false; - - if (SelectedComponents.Num() < 1) - return false; - - // Get runtime settings. - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Calc all the needed sizes - int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; - float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; - - int32 NumComponents = SelectedComponents.Num(); - bool bExportOnlySelected = NumComponents != LandscapeProxy->LandscapeComponents.Num(); - - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - int32 VertexCount = NumComponents * VertexCountPerComponent; - if (!VertexCount) - return false; - - // Initialize the data arrays - LandscapePositionArray.SetNumUninitialized(VertexCount); - LandscapeNormalArray.SetNumUninitialized(VertexCount); - LandscapeUVArray.SetNumUninitialized(VertexCount); - LandscapeComponentNameArray.SetNumUninitialized(VertexCount); - LandscapeComponentVertexIndicesArray.SetNumUninitialized(VertexCount); - if (bExportLighting) - LandscapeLightmapValues.SetNumUninitialized(VertexCount); - - //----------------------------------------------------------------------------------------------------------------- - // EXTRACT THE LANDSCAPE DATA - //----------------------------------------------------------------------------------------------------------------- - FIntPoint IntPointMax = FIntPoint::ZeroValue; - - int32 AllPositionsIdx = 0; - for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) - { - ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; - if (bExportOnlySelected && !SelectedComponents.Contains(LandscapeComponent)) - continue; - - TArray64< uint8 > LightmapMipData; - int32 LightmapMipSizeX = 0; - int32 LightmapMipSizeY = 0; - - // See if we need to export lighting information. - if (bExportLighting) - { - const FMeshMapBuildData* MapBuildData = LandscapeComponent->GetMeshMapBuildData(); - FLightMap2D* LightMap2D = MapBuildData && MapBuildData->LightMap ? MapBuildData->LightMap->GetLightMap2D() : nullptr; - if (LightMap2D && LightMap2D->IsValid(0)) - { - UTexture2D * TextureLightmap = LightMap2D->GetTexture(0); - if (TextureLightmap) - { - if (TextureLightmap->Source.GetMipData(LightmapMipData, 0, 0, 0, nullptr)) - { - LightmapMipSizeX = TextureLightmap->Source.GetSizeX(); - LightmapMipSizeY = TextureLightmap->Source.GetSizeY(); - } - else - { - LightmapMipData.Empty(); - } - } - } - } - - // Construct landscape component data interface to access raw data. - FLandscapeComponentDataInterface CDI(LandscapeComponent, LandscapeProxy->ExportLOD); - - // Get name of this landscape component. - const char * LandscapeComponentNameStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->GetName()); - for (int32 VertexIdx = 0; VertexIdx < VertexCountPerComponent; VertexIdx++) - { - int32 VertX = 0; - int32 VertY = 0; - CDI.VertexIndexToXY(VertexIdx, VertX, VertY); - - // Get position. - FVector PositionVector = CDI.GetWorldVertex(VertX, VertY); - - // Get normal / tangent / binormal. - FVector Normal = FVector::ZeroVector; - FVector TangentX = FVector::ZeroVector; - FVector TangentY = FVector::ZeroVector; - CDI.GetLocalTangentVectors(VertX, VertY, TangentX, TangentY, Normal); - - // Export UVs. - FVector TextureUV = FVector::ZeroVector; - if (bExportTileUVs) - { - // We want to export uvs per tile. - TextureUV = FVector(VertX, VertY, 0.0f); - - // If we need to normalize UV space. - if (bExportNormalizedUVs) - TextureUV /= ComponentSizeQuads; - } - else - { - // We want to export global uvs (default). - FIntPoint IntPoint = LandscapeComponent->GetSectionBase(); - TextureUV = FVector(VertX * ScaleFactor + IntPoint.X, VertY * ScaleFactor + IntPoint.Y, 0.0f); - - // Keep track of max offset. - IntPointMax = IntPointMax.ComponentMax(IntPoint); - } - - if (bExportLighting) - { - FLinearColor VertexLightmapColor(0.0f, 0.0f, 0.0f, 1.0f); - if (LightmapMipData.Num() > 0) - { - FVector2D UVCoord(VertX, VertY); - UVCoord /= (ComponentSizeQuads + 1); - - FColor LightmapColorRaw = PickVertexColorFromTextureMip( - LightmapMipData.GetData(), UVCoord, LightmapMipSizeX, LightmapMipSizeY); - - VertexLightmapColor = LightmapColorRaw.ReinterpretAsLinear(); - } - - LandscapeLightmapValues[AllPositionsIdx] = VertexLightmapColor; - } - - // Retrieve component transform. - const FTransform & ComponentTransform = LandscapeComponent->GetComponentTransform(); - - // Retrieve component scale. - const FVector & ScaleVector = ComponentTransform.GetScale3D(); - - // Perform normalization. - Normal /= ScaleVector; - Normal.Normalize(); - - TangentX /= ScaleVector; - TangentX.Normalize(); - - TangentY /= ScaleVector; - TangentY.Normalize(); - - // Perform position scaling. - FVector PositionTransformed = PositionVector / HAPI_UNREAL_SCALE_FACTOR_POSITION; - LandscapePositionArray[AllPositionsIdx].X = PositionTransformed.X; - LandscapePositionArray[AllPositionsIdx].Y = PositionTransformed.Z; - LandscapePositionArray[AllPositionsIdx].Z = PositionTransformed.Y; - - Swap(Normal.Y, Normal.Z); - - // Store landscape component name for this point. - LandscapeComponentNameArray[AllPositionsIdx] = LandscapeComponentNameStr; - - // Store vertex index (x,y) for this point. - LandscapeComponentVertexIndicesArray[AllPositionsIdx].X = VertX; - LandscapeComponentVertexIndicesArray[AllPositionsIdx].Y = VertY; - - // Store point normal. - LandscapeNormalArray[AllPositionsIdx] = Normal; - - // Store uv. - LandscapeUVArray[AllPositionsIdx] = TextureUV; - - AllPositionsIdx++; - } - - // Free the memory allocated for LandscapeComponentNameStr - FHoudiniEngineUtils::FreeRawStringMemory(LandscapeComponentNameStr); - } - - // If we need to normalize UV space and we are doing global UVs. - if (!bExportTileUVs && bExportNormalizedUVs) - { - IntPointMax += FIntPoint(ComponentSizeQuads, ComponentSizeQuads); - IntPointMax = IntPointMax.ComponentMax(FIntPoint(1, 1)); - - for (int32 UVIdx = 0; UVIdx < VertexCount; ++UVIdx) - { - FVector & PositionUV = LandscapeUVArray[UVIdx]; - PositionUV.X /= IntPointMax.X; - PositionUV.Y /= IntPointMax.Y; - } - } - - return true; -} - -FColor -FUnrealLandscapeTranslator::PickVertexColorFromTextureMip( - const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight) -{ - check(MipBytes); - - FColor ResultColor(0, 0, 0, 255); - - if (UVCoord.X >= 0.0f && UVCoord.X < 1.0f && UVCoord.Y >= 0.0f && UVCoord.Y < 1.0f) - { - const int32 X = MipWidth * UVCoord.X; - const int32 Y = MipHeight * UVCoord.Y; - - const int32 Index = ((Y * MipWidth) + X) * 4; - - ResultColor.B = MipBytes[Index + 0]; - ResultColor.G = MipBytes[Index + 1]; - ResultColor.R = MipBytes[Index + 2]; - ResultColor.A = MipBytes[Index + 3]; - } - - return ResultColor; -} - -bool -FUnrealLandscapeTranslator::AddLandscapePositionAttribute(const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray) -{ - int32 VertexCount = LandscapePositionArray.Num(); - if (VertexCount < 3) - return false; - - // Create point attribute info containing positions. - HAPI_AttributeInfo AttributeInfoPointPosition; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointPosition); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointPosition ); - AttributeInfoPointPosition.count = VertexCount; - AttributeInfoPointPosition.tupleSize = 3; - AttributeInfoPointPosition.exists = true; - AttributeInfoPointPosition.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointPosition.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointPosition.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition), false); - - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition, - (const float *)LandscapePositionArray.GetData(), - 0, AttributeInfoPointPosition.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeNormalAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeNormalArray) -{ - int32 VertexCount = LandscapeNormalArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointNormal; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointNormal); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointNormal ); - AttributeInfoPointNormal.count = VertexCount; - AttributeInfoPointNormal.tupleSize = 3; - AttributeInfoPointNormal.exists = true; - AttributeInfoPointNormal.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointNormal.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointNormal.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal, - (const float *)LandscapeNormalArray.GetData(), 0, VertexCount), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeUVAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeUVArray) -{ - int32 VertexCount = LandscapeUVArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointUV; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointUV); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointUV ); - AttributeInfoPointUV.count = VertexCount; - AttributeInfoPointUV.tupleSize = 3; - AttributeInfoPointUV.exists = true; - AttributeInfoPointUV.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointUV.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointUV.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV, - (const float *)LandscapeUVArray.GetData(), 0, AttributeInfoPointUV.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeComponentVertexIndicesAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentVertexIndicesArray) -{ - int32 VertexCount = LandscapeComponentVertexIndicesArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointLandscapeComponentVertexIndices; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentVertexIndices); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentVertexIndices ); - AttributeInfoPointLandscapeComponentVertexIndices.count = VertexCount; - AttributeInfoPointLandscapeComponentVertexIndices.tupleSize = 2; - AttributeInfoPointLandscapeComponentVertexIndices.exists = true; - AttributeInfoPointLandscapeComponentVertexIndices.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLandscapeComponentVertexIndices.storage = HAPI_STORAGETYPE_INT; - AttributeInfoPointLandscapeComponentVertexIndices.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, - &AttributeInfoPointLandscapeComponentVertexIndices), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, - &AttributeInfoPointLandscapeComponentVertexIndices, - (const int *)LandscapeComponentVertexIndicesArray.GetData(), 0, - AttributeInfoPointLandscapeComponentVertexIndices.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeComponentNameAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray) -{ - int32 VertexCount = LandscapeComponentNameArray.Num(); - if (VertexCount < 3) - return false; - - // Create point attribute containing landscape component name. - HAPI_AttributeInfo AttributeInfoPointLandscapeComponentNames; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentNames); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentNames ); - AttributeInfoPointLandscapeComponentNames.count = VertexCount; - AttributeInfoPointLandscapeComponentNames.tupleSize = 1; - AttributeInfoPointLandscapeComponentNames.exists = true; - AttributeInfoPointLandscapeComponentNames.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLandscapeComponentNames.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPointLandscapeComponentNames.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, - &AttributeInfoPointLandscapeComponentNames), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, - &AttributeInfoPointLandscapeComponentNames, - (const char **)LandscapeComponentNameArray.GetData(), - 0, AttributeInfoPointLandscapeComponentNames.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeLightmapColorAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues) -{ - int32 VertexCount = LandscapeLightmapValues.Num(); - - HAPI_AttributeInfo AttributeInfoPointLightmapColor; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLightmapColor); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLightmapColor ); - AttributeInfoPointLightmapColor.count = VertexCount; - AttributeInfoPointLightmapColor.tupleSize = 4; - AttributeInfoPointLightmapColor.exists = true; - AttributeInfoPointLightmapColor.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLightmapColor.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointLightmapColor.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor, - (const float *)LandscapeLightmapValues.GetData(), 0, - AttributeInfoPointLightmapColor.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeMeshIndicesAndMaterialsAttribute( - const HAPI_NodeId& NodeId, const bool& bExportMaterials, - const int32& ComponentSizeQuads, const int32& QuadCount, - ALandscapeProxy * LandscapeProxy, - const TSet< ULandscapeComponent * >& SelectedComponents) -{ - if (!LandscapeProxy) - return false; - - // Compute number of necessary indices. - int32 IndexCount = QuadCount * 4; - if (IndexCount < 0) - return false; - - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - - // Array holding indices data. - TArray LandscapeIndices; - LandscapeIndices.SetNumUninitialized(IndexCount); - - // Allocate space for face names. - // The LandscapeMaterial and HoleMaterial per point - TArray FaceMaterials; - TArray FaceHoleMaterials; - FaceMaterials.SetNumUninitialized(QuadCount); - FaceHoleMaterials.SetNumUninitialized(QuadCount); - - int32 VertIdx = 0; - int32 QuadIdx = 0; - - const char * MaterialRawStr = nullptr; - const char * MaterialHoleRawStr = nullptr; - - // Lambda for freeing the memory allocated by ExtractRawString and returning - auto FreeMemoryReturn = [&MaterialRawStr, &MaterialHoleRawStr](const bool& bReturn) - { - FHoudiniEngineUtils::FreeRawStringMemory(MaterialRawStr); - FHoudiniEngineUtils::FreeRawStringMemory(MaterialHoleRawStr); - - return bReturn; - }; - - const int32 QuadComponentCount = ComponentSizeQuads + 1; - for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) - { - ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; - if (!SelectedComponents.Contains(LandscapeComponent)) - continue; - - if (bExportMaterials) - { - // If component has an override material, we need to get the raw name (if exporting materials). - if (LandscapeComponent->OverrideMaterial) - { - MaterialRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideMaterial->GetName()); - } - - // If component has an override hole material, we need to get the raw name (if exporting materials). - if (LandscapeComponent->OverrideHoleMaterial) - { - MaterialHoleRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideHoleMaterial->GetName()); - } - } - - int32 BaseVertIndex = ComponentIdx * VertexCountPerComponent; - for (int32 YIdx = 0; YIdx < ComponentSizeQuads; YIdx++) - { - for (int32 XIdx = 0; XIdx < ComponentSizeQuads; XIdx++) - { - LandscapeIndices[VertIdx + 0] = BaseVertIndex + (XIdx + 0) + (YIdx + 0) * QuadComponentCount; - LandscapeIndices[VertIdx + 1] = BaseVertIndex + (XIdx + 1) + (YIdx + 0) * QuadComponentCount; - LandscapeIndices[VertIdx + 2] = BaseVertIndex + (XIdx + 1) + (YIdx + 1) * QuadComponentCount; - LandscapeIndices[VertIdx + 3] = BaseVertIndex + (XIdx + 0) + (YIdx + 1) * QuadComponentCount; - - // Store override materials (if exporting materials). - if (bExportMaterials) - { - FaceMaterials[QuadIdx] = MaterialRawStr; - FaceHoleMaterials[QuadIdx] = MaterialHoleRawStr; - } - - VertIdx += 4; - QuadIdx++; - } - } - } - - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, LandscapeIndices.GetData(), 0, LandscapeIndices.Num()), - FreeMemoryReturn(false)); - - // We need to generate array of face counts. - TArray LandscapeFaces; - LandscapeFaces.Init(4, QuadCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, LandscapeFaces.GetData(), 0, LandscapeFaces.Num()), - FreeMemoryReturn(false)); - - if (bExportMaterials) - { - if (!FaceMaterials.Contains(nullptr)) - { - // Marshall in override primitive material names. - HAPI_AttributeInfo AttributeInfoPrimitiveMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterial ); - AttributeInfoPrimitiveMaterial.count = FaceMaterials.Num(); - AttributeInfoPrimitiveMaterial.tupleSize = 1; - AttributeInfoPrimitiveMaterial.exists = true; - AttributeInfoPrimitiveMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoPrimitiveMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPrimitiveMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial, - (const char **)FaceMaterials.GetData(), 0, AttributeInfoPrimitiveMaterial.count), - FreeMemoryReturn(false)); - } - - if (!FaceHoleMaterials.Contains(nullptr)) - { - // Marshall in override primitive material hole names. - HAPI_AttributeInfo AttributeInfoPrimitiveMaterialHole; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterialHole); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterialHole ); - AttributeInfoPrimitiveMaterialHole.count = FaceHoleMaterials.Num(); - AttributeInfoPrimitiveMaterialHole.tupleSize = 1; - AttributeInfoPrimitiveMaterialHole.exists = true; - AttributeInfoPrimitiveMaterialHole.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoPrimitiveMaterialHole.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPrimitiveMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoPrimitiveMaterialHole), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoPrimitiveMaterialHole, (const char **)FaceHoleMaterials.GetData(), 0, - AttributeInfoPrimitiveMaterialHole.count), - FreeMemoryReturn(false)); - } - } - - // Free the memory and return true - return FreeMemoryReturn(true); -} - -bool -FUnrealLandscapeTranslator::AddLandscapeGlobalMaterialAttribute( - const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy) -{ - if (!LandscapeProxy) - return false; - - // If there's a global landscape material, we marshall it as detail. - UMaterialInterface * MaterialInterface = LandscapeProxy->GetLandscapeMaterial(); - const char * MaterialNameStr = ""; - if (MaterialInterface) - { - FString FullMaterialName = MaterialInterface->GetPathName(); - MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); - } - - HAPI_AttributeInfo AttributeInfoDetailMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterial ); - AttributeInfoDetailMaterial.count = 1; - AttributeInfoDetailMaterial.tupleSize = 1; - AttributeInfoDetailMaterial.exists = true; - AttributeInfoDetailMaterial.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoDetailMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoDetailMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial, - (const char**)&MaterialNameStr, 0, AttributeInfoDetailMaterial.count), false); - - // If there's a global landscape hole material, we marshall it as detail. - UMaterialInterface * HoleMaterialInterface = LandscapeProxy->GetLandscapeHoleMaterial(); - const char * HoleMaterialNameStr = ""; - if (HoleMaterialInterface) - { - FString FullMaterialName = HoleMaterialInterface->GetPathName(); - MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); - } - - HAPI_AttributeInfo AttributeInfoDetailMaterialHole; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterialHole); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterialHole ); - AttributeInfoDetailMaterialHole.count = 1; - AttributeInfoDetailMaterialHole.tupleSize = 1; - AttributeInfoDetailMaterialHole.exists = true; - AttributeInfoDetailMaterialHole.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoDetailMaterialHole.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoDetailMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoDetailMaterialHole), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoDetailMaterialHole, (const char **)&HoleMaterialNameStr, 0, - AttributeInfoDetailMaterialHole.count), false); - - return true; -} - - -bool -FUnrealLandscapeTranslator::AddLandscapeLayerAttribute( - const HAPI_NodeId& NodeId, const TArray& LandscapeLayerArray, const FString& LayerName) -{ - int32 VertexCount = LandscapeLayerArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoLayer; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLayer); - AttributeInfoLayer.count = VertexCount; - AttributeInfoLayer.tupleSize = 1; - AttributeInfoLayer.exists = true; - AttributeInfoLayer.owner = HAPI_ATTROWNER_POINT; - AttributeInfoLayer.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLayer.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, - TCHAR_TO_ANSI(*LayerName), - &AttributeInfoLayer), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, - TCHAR_TO_ANSI(*LayerName), - &AttributeInfoLayer, - (const float *)LandscapeLayerArray.GetData(), - 0, AttributeInfoLayer.count), false); - - return true; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "UnrealLandscapeTranslator.h" +#include "HoudiniGeoPartObject.h" + +#include "Landscape.h" +#include "LandscapeDataAccess.h" +#include "LandscapeEdit.h" +#include "LightMap.h" +#include "Engine/MapBuildDataRegistry.h" +#include "PhysicalMaterials/PhysicalMaterial.h" + + +bool +FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( + ALandscapeProxy* LandscapeProxy, + HAPI_NodeId& CreatedNodeId, + const FString& InputNodeNameString, + const bool& bExportGeometryAsMesh, + const bool& bExportTileUVs, + const bool bExportNormalizedUVs, + const bool bExportLighting, + const bool bExportMaterials ) +{ + //-------------------------------------------------------------------------------------------------- + // 1. Create an input node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId InputNodeId = -1; + // Create the curve SOP Node + std::string NodeNameRawString; + FHoudiniEngineUtils::ConvertUnrealString(InputNodeNameString, NodeNameRawString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + return false; + + // We now have a valid id. + CreatedNodeId = InputNodeId; + + if(!FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true)) + return false; + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); + */ + //-------------------------------------------------------------------------------------------------- + // 2. Set the part info + //-------------------------------------------------------------------------------------------------- + int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; + float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; + + //int32 NumComponents = bExportOnlySelected ? SelectedComponents.Num() : LandscapeProxy->LandscapeComponents.Num(); + int32 NumComponents = LandscapeProxy->LandscapeComponents.Num(); + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + int32 VertexCount = NumComponents * VertexCountPerComponent; + if (!VertexCount) + return false; + + int32 TriangleCount = NumComponents * FMath::Square(ComponentSizeQuads) * 2; + int32 QuadCount = NumComponents * FMath::Square(ComponentSizeQuads); + int32 IndexCount = QuadCount * 4; + + // Create part info + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + //FMemory::Memzero< HAPI_PartInfo >(Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = VertexCount; + Part.type = HAPI_PARTTYPE_MESH; + + // If we are exporting to a mesh, we need vertices and faces + if (bExportGeometryAsMesh) + { + Part.vertexCount = IndexCount; + Part.faceCount = QuadCount; + } + + // Set the part infos + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, &DisplayGeoInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part), false); + + //-------------------------------------------------------------------------------------------------- + // 3. Extract the landscape data + //-------------------------------------------------------------------------------------------------- + // Array for the position data + TArray LandscapePositionArray; + // Array for the normals + TArray LandscapeNormalArray; + // Array for the UVs + TArray LandscapeUVArray; + // Array for the vertex index of each point in its component + TArray LandscapeComponentVertexIndicesArray; + // Array for the tile names per point + TArray LandscapeComponentNameArray; + // Array for the lightmap values + TArray LandscapeLightmapValues; + // Selected components set to all components in current landscape proxy + TSet SelectedComponents; + SelectedComponents.Append(LandscapeProxy->LandscapeComponents); + + // Extract all the data from the landscape to the arrays + if (!ExtractLandscapeData( + LandscapeProxy, SelectedComponents, + bExportLighting, bExportTileUVs, bExportNormalizedUVs, + LandscapePositionArray, LandscapeNormalArray, + LandscapeUVArray, LandscapeComponentVertexIndicesArray, + LandscapeComponentNameArray, LandscapeLightmapValues)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Set the corresponding attributes in Houdini + //-------------------------------------------------------------------------------------------------- + + // Create point attribute info containing positions. + if (!AddLandscapePositionAttribute(DisplayGeoInfo.nodeId, LandscapePositionArray)) + return false; + + // Create point attribute info containing normals. + if (!AddLandscapeNormalAttribute(DisplayGeoInfo.nodeId, LandscapeNormalArray)) + return false; + + // Create point attribute info containing UVs. + if (!AddLandscapeUVAttribute(DisplayGeoInfo.nodeId, LandscapeUVArray)) + return false; + + // Create point attribute containing landscape component vertex indices (indices of vertices within the grid - x,y). + if (!AddLandscapeComponentVertexIndicesAttribute(DisplayGeoInfo.nodeId, LandscapeComponentVertexIndicesArray)) + return false; + + // Create point attribute containing landscape component name. + if (!AddLandscapeComponentNameAttribute(DisplayGeoInfo.nodeId, LandscapeComponentNameArray)) + return false; + + // Create point attribute info containing lightmap information. + if (bExportLighting) + { + if (!AddLandscapeLightmapColorAttribute(DisplayGeoInfo.nodeId, LandscapeLightmapValues)) + return false; + } + + // Set indices if we are exporting full geometry. + if (bExportGeometryAsMesh) + { + if (!AddLandscapeMeshIndicesAndMaterialsAttribute( + DisplayGeoInfo.nodeId, + bExportMaterials, + ComponentSizeQuads, + QuadCount, + LandscapeProxy, + SelectedComponents)) + return false; + } + + // If we are marshalling material information. + if (bExportMaterials) + { + if (!AddLandscapeGlobalMaterialAttribute(DisplayGeoInfo.nodeId, LandscapeProxy)) + return false; + } + + /* + // TODO: Move this to ExtractLandscapeData() + //-------------------------------------------------------------------------------------------------- + // 4. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Get the landscape X/Y Size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + return false; + + // Calc the X/Y size in points + int32 XSize = (MaxX - MinX + 1); + int32 YSize = (MaxY - MinY + 1); + if ((XSize < 2) || (YSize < 2)) + return false; + + bool MaskInitialized = false; + int32 NumLayers = LandscapeInfo->Layers.Num(); + for (int32 n = 0; n < NumLayers; n++) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!GetLandscapeLayerData( + LandscapeInfo, n, + MinX, MinY, MaxX, MaxY, + CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray CurrentLayerFloatData; + if (!ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo)) + continue; + + if (!AddLandscapeLayerAttribute( + DisplayGeoInfo.nodeId, CurrentLayerFloatData, LayerName)) + continue; + } + */ + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId), false); + + // TODO: Remove me! + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); + */ + + return FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true); +} + +bool +FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( + ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId, const FString& InputNodeNameStr) +{ + if (!LandscapeProxy) + return false; + + // Export the whole landscape and its layer as a single heightfield. + + //-------------------------------------------------------------------------------------------------- + // 1. Extracting the height data + //-------------------------------------------------------------------------------------------------- + TArray HeightData; + int32 XSize, YSize; + FVector Min, Max; + if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + HAPI_VolumeInfo HeightfieldVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + FTransform LandscapeTransform = LandscapeProxy->ActorToWorld(); + FVector CenterOffset = FVector::ZeroVector; + if (!ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId HeightFieldId = -1; + HAPI_NodeId HeightId = -1; + HAPI_NodeId MaskId = -1; + HAPI_NodeId MergeId = -1; + if (!CreateHeightfieldInputNode(InputNodeNameStr, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + HAPI_PartId PartId = 0; + if (!SetHeighfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) + return false; + + // Add the materials used + UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); + UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); + UPhysicalMaterial* LandscapePhysMat = LandscapeProxy->DefaultPhysMaterial; + AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateAttributesFromTags(HeightId, PartId, LandscapeProxy->Tags); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(HeightId, PartId, LandscapeProxy, 1); + + // Add the unreal_level_path attribute + ULevel* Level = LandscapeProxy->GetLevel(); + if (Level) + { + FHoudiniEngineUtils::AddLevelPathAttribute(HeightId, PartId, Level, 1); + /* + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToVolume(HeightId, PartId, LevelPath); + */ + } + + // Commit the height volume + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), HeightId), false); + + //-------------------------------------------------------------------------------------------------- + // 5. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + bool MaskInitialized = false; + int32 MergeInputIndex = 2; + int32 NumLayers = LandscapeInfo->Layers.Num(); + for (int32 n = 0; n < NumLayers; n++) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!GetLandscapeLayerData(LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray CurrentLayerFloatData; + if (!ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo)) + continue; + + // We reuse the height layer's transform + CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; + + // 3. See if we need to create an input volume, or can reuse the HF's default mask volume + bool IsMask = false; + if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) + IsMask = true; + + HAPI_NodeId LayerVolumeNodeId = -1; + if (!IsMask) + { + // Current layer is not mask, so we need to create a new input volume + std::string LayerNameStr; + FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); + } + else + { + // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node + LayerVolumeNodeId = MaskId; + } + + // Check if we have a valid id for the input volume. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) + continue; + + // 4. Set the layer/mask heighfield data in Houdini + HAPI_PartId CurrentPartId = 0; + if (!SetHeighfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) + continue; + + // Get the physical material used by that layer + UPhysicalMaterial* LayerPhysicalMat = LandscapePhysMat; + { + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (LayerInfo) + LayerPhysicalMat = LayerInfo->PhysMaterial; + } + + // Also add the material attributes to the layer volumes + AddLandscapeMaterialAttributesToVolume(LayerVolumeNodeId, PartId, LandscapeMat, LandscapeHoleMat, LayerPhysicalMat); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateAttributesFromTags(LayerVolumeNodeId, PartId, LandscapeProxy->Tags); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(LayerVolumeNodeId, PartId, LandscapeProxy, 1); + + // Also add the level path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(LayerVolumeNodeId, PartId, Level, 1); + //AddLevelPathAttributeToVolume(LayerVolumeNodeId, PartId, LevelPath); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); + + if (!IsMask) + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex, LayerVolumeNodeId, 0), false); + + MergeInputIndex++; + } + else + { + MaskInitialized = true; + } + } + + // We need to have a mask layer as it is required for proper heightfield functionalities + // Setting the volume info on the mask is needed for the HF to have proper transform in H! + // If we didn't create a mask volume before, send a default one now + if (!MaskInitialized) + { + MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); + + // Add the materials used + AddLandscapeMaterialAttributesToVolume(MaskId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateAttributesFromTags(MaskId, PartId, LandscapeProxy->Tags); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(MaskId, PartId, LandscapeProxy, 1); + + // Also add the level path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(MaskId, PartId, Level, 1); + //AddLevelPathAttributeToVolume(MaskId, PartId, LevelPath); + + // Commit the mask volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), MaskId), false); + } + + HAPI_TransformEuler HAPIObjectTransform; + FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); + LandscapeTransform.SetScale3D(FVector::OneVector); + FHoudiniEngineUtils::TranslateUnrealTransform(LandscapeTransform, HAPIObjectTransform); + HAPIObjectTransform.position[1] = 0.0f; + + HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(HeightFieldId); + FHoudiniApi::SetObjectTransform(FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform); + + // Since HF are centered but landscape aren't, we need to set the HF's center parameter + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); + + // Finally, cook the Heightfield node + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightFieldId, &CookOptions), false); + */ + if(!FHoudiniEngineUtils::HapiCookNode(HeightFieldId, nullptr, true)) + return false; + + CreatedHeightfieldNodeId = HeightFieldId; + + return true; +} + +// Converts Unreal uint16 values to Houdini Float +bool +FUnrealLandscapeTranslator::ConvertLandscapeLayerDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + const FLinearColor& LayerUsageDebugColor, + TArray& LayerFloatValues, + HAPI_VolumeInfo& LayerVolumeInfo) +{ + LayerFloatValues.Empty(); + + int32 HoudiniXSize = YSize; + int32 HoudiniYSize = XSize; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + if (IntHeightData.Num() != SizeInPoints) + return false; + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to float + //-------------------------------------------------------------------------------------------------- + + // By default, values are converted from unreal [0 255] uint8 to Houdini [0 1] float + // uint8 min/max + uint8 IntMin = 0; + uint8 IntMax = UINT8_MAX; + // The range in Digits + double DigitRange = (double)UINT8_MAX; + + // By default, the values will be converted to [0, 1] + float LayerMin = 0.0f; + float LayerMax = 1.0f; + float LayerSpacing = 1.0f / DigitRange; + + // If this layer came from Houdini, its alpha value should be PI + // This indicates that we can extract additional infos stored its debug usage color + // so we can reconstruct the original source values (float) more accurately + if (LayerUsageDebugColor.A == PI) + { + // We need the ZMin / ZMax uint8 values + IntMin = IntHeightData[0]; + IntMax = IntMin; + for (int n = 0; n < IntHeightData.Num(); n++) + { + if (IntHeightData[n] < IntMin) + IntMin = IntHeightData[n]; + if (IntHeightData[n] > IntMax) + IntMax = IntHeightData[n]; + } + + DigitRange = (double)IntMax - (double)IntMin; + + // Read the original min/max and spacing stored in the debug color + LayerMin = LayerUsageDebugColor.R; + LayerMax = LayerUsageDebugColor.G; + LayerSpacing = LayerUsageDebugColor.B; + } + + // Convert the Int data to Float + LayerFloatValues.SetNumUninitialized(SizeInPoints); + + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // We need to invert X/Y when reading the value from Unreal + int32 nHoudini = nX + nY * HoudiniXSize; + int32 nUnreal = nY + nX * XSize; + + // Convert the int values to meter + // Unreal's digit value have a zero value of 32768 + double DoubleValue = ((double)IntHeightData[nUnreal] - (double)IntMin) * LayerSpacing + LayerMin; + LayerFloatValues[nHoudini] = (float)DoubleValue; + } + } + + /* + // Verifying the converted ZMin / ZMax + float FloatMin = LayerFloatValues[0]; + float FloatMax = FloatMin; + for (int32 n = 0; n < LayerFloatValues.Num(); n++) + { + if (LayerFloatValues[n] < FloatMin) + FloatMin = LayerFloatValues[n]; + if (LayerFloatValues[n] > FloatMax) + FloatMax = LayerFloatValues[n]; + } + */ + + //-------------------------------------------------------------------------------------------------- + // 2. Fill the volume info + //-------------------------------------------------------------------------------------------------- + LayerVolumeInfo.xLength = HoudiniXSize; + LayerVolumeInfo.yLength = HoudiniYSize; + LayerVolumeInfo.zLength = 1; + + LayerVolumeInfo.minX = 0; + LayerVolumeInfo.minY = 0; + LayerVolumeInfo.minZ = 0; + + LayerVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; + LayerVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; + LayerVolumeInfo.tupleSize = 1; + LayerVolumeInfo.tileSize = 1; + + LayerVolumeInfo.hasTaper = false; + LayerVolumeInfo.xTaper = 0.0; + LayerVolumeInfo.yTaper = 0.0; + + // The layer transform will have to be copied from the main heightfield's transform + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeData( + ALandscapeProxy* LandscapeProxy, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max) +{ + if (!LandscapeProxy) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Get the landscape extents to get its size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + + // To handle streaming proxies correctly, get the extents via all the components, + // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. + for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) + { + Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); + } + + if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize)) + return false; + + // Get the landscape Min/Max values + // Do not use Landscape->GetActorBounds() here as instanced geo + // (due to grass layers for example) can cause it to return incorrect bounds! + FVector Origin, Extent; + GetLandscapeProxyBounds(LandscapeProxy, Origin, Extent); + + // Get the landscape Min/Max values + Min = Origin - Extent; + Max = Origin + Extent; + + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeData( + ULandscapeInfo* LandscapeInfo, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& HeightData, + int32& XSize, int32& YSize) +{ + if (!LandscapeInfo) + return false; + + // Get the X/Y size in points + XSize = (MaxX - MinX + 1); + YSize = (MaxY - MinY + 1); + + if ((XSize < 2) || (YSize < 2)) + return false; + + // Extracting the uint16 values from the landscape + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + HeightData.AddZeroed(XSize * YSize); + LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0); + + return true; +} + + +void +FUnrealLandscapeTranslator::GetLandscapeProxyBounds( + ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents) +{ + // Iterate only on the landscape components + FBox Bounds(ForceInit); + for (const UActorComponent* ActorComponent : LandscapeProxy->GetComponents()) + { + const ULandscapeComponent* LandscapeComp = Cast(ActorComponent); + if (LandscapeComp && LandscapeComp->IsRegistered()) + Bounds += LandscapeComp->Bounds.GetBox(); + } + + // Convert the bounds to origin/offset vectors + Bounds.GetCenterAndExtents(Origin, Extents); +} + +bool +FUnrealLandscapeTranslator::ConvertLandscapeDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + FVector Min, FVector Max, + const FTransform& LandscapeTransform, + TArray& HeightfieldFloatValues, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + FVector& CenterOffset) +{ + HeightfieldFloatValues.Empty(); + + int32 HoudiniXSize = YSize; + int32 HoudiniYSize = XSize; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + if (IntHeightData.Num() != SizeInPoints) + return false; + + // Use default unreal scaling for marshalling landscapes + // A lot of precision will be lost in order to keep the same transform as the landscape input + bool bUseDefaultUE4Scaling = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) + bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to float + //-------------------------------------------------------------------------------------------------- + + + // Convert the min/max values from cm to meters + Min /= 100.0; + Max /= 100.0; + + // Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0 + // To convert the uint16 values to float "metric" values, offset the int by 32768 to center it, + // then scale it + + // Spacing used to convert from uint16 to meters + double ZSpacing = 512.0 / ((double)UINT16_MAX); + ZSpacing *= ((double)LandscapeTransform.GetScale3D().Z / 100.0); + + // Center value in meters (Landscape ranges from [-255:257] meters at default scale + double ZCenterOffset = 32767; + double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f; + // Convert the Int data to Float + HeightfieldFloatValues.SetNumUninitialized(SizeInPoints); + + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // We need to invert X/Y when reading the value from Unreal + int32 nHoudini = nX + nY * HoudiniXSize; + int32 nUnreal = nY + nX * XSize; + + // Convert the int values to meter + // Unreal's digit value have a zero value of 32768 + double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset; + HeightfieldFloatValues[nHoudini] = (float)DoubleValue; + } + } + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the Unreal Transform to a HAPI_transform + //-------------------------------------------------------------------------------------------------- + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + //FMemory::Memzero< HAPI_Transform >( HapiTransform ); + { + FQuat Rotation = LandscapeTransform.GetRotation(); + if (Rotation != FQuat::Identity) + { + //Swap(ObjectRotation.Y, ObjectRotation.Z); + HapiTransform.rotationQuaternion[0] = Rotation.X; + HapiTransform.rotationQuaternion[1] = Rotation.Z; + HapiTransform.rotationQuaternion[2] = Rotation.Y; + HapiTransform.rotationQuaternion[3] = -Rotation.W; + } + else + { + HapiTransform.rotationQuaternion[0] = 0; + HapiTransform.rotationQuaternion[1] = 0; + HapiTransform.rotationQuaternion[2] = 0; + HapiTransform.rotationQuaternion[3] = 1; + } + + // Heightfield are centered, landscapes are not + CenterOffset = (Max - Min) * 0.5f; + + // Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform) + //FVector Position = LandscapeTransform.GetLocation() / 100.0f; + HapiTransform.position[1] = 0.0f;//Position.X + CenterOffset.X; + HapiTransform.position[0] = 0.0f;//Position.Y + CenterOffset.Y; + HapiTransform.position[2] = 0.0f; + + FVector Scale = LandscapeTransform.GetScale3D() / 100.0f; + HapiTransform.scale[0] = Scale.X * 0.5f * HoudiniXSize; + HapiTransform.scale[1] = Scale.Y * 0.5f * HoudiniYSize; + HapiTransform.scale[2] = 0.5f; + if (bUseDefaultUE4Scaling) + HapiTransform.scale[2] *= Scale.Z; + + HapiTransform.shear[0] = 0.0f; + HapiTransform.shear[1] = 0.0f; + HapiTransform.shear[2] = 0.0f; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Fill the volume info + //-------------------------------------------------------------------------------------------------- + HeightfieldVolumeInfo.xLength = HoudiniXSize; + HeightfieldVolumeInfo.yLength = HoudiniYSize; + HeightfieldVolumeInfo.zLength = 1; + + HeightfieldVolumeInfo.minX = 0; + HeightfieldVolumeInfo.minY = 0; + HeightfieldVolumeInfo.minZ = 0; + + HeightfieldVolumeInfo.transform = HapiTransform; + + HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; + HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; + HeightfieldVolumeInfo.tupleSize = 1; + HeightfieldVolumeInfo.tileSize = 1; + + HeightfieldVolumeInfo.hasTaper = false; + HeightfieldVolumeInfo.xTaper = 0.0; + HeightfieldVolumeInfo.yTaper = 0.0; + + return true; +} + +bool +FUnrealLandscapeTranslator::CreateHeightfieldInputNode( + const FString& NodeName, + const int32& XSize, + const int32& YSize, + HAPI_NodeId& HeightfieldNodeId, + HAPI_NodeId& HeightNodeId, + HAPI_NodeId& MaskNodeId, + HAPI_NodeId& MergeNodeId) +{ + // Make sure the Heightfield node doesnt already exists + if (HeightfieldNodeId != -1) + return false; + + // Convert the node's name + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString(NodeName, NameStr); + + // Create the heigthfield node via HAPI + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightFieldInput( + FHoudiniEngine::Get().GetSession(), + -1, NameStr.c_str(), XSize, YSize, 1.0f, HAPI_HeightFieldSampling::HAPI_HEIGHTFIELD_SAMPLING_CORNER, + &HeightfieldNodeId, &HeightNodeId, &MaskNodeId, &MergeNodeId), false); + + // Cook it + return FHoudiniEngineUtils::HapiCookNode(HeightfieldNodeId, nullptr, true); + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightfieldNodeId, &CookOptions), false); + + return true; + */ +} + +bool +FUnrealLandscapeTranslator::SetHeighfieldData( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + TArray& FloatValues, + const HAPI_VolumeInfo& VolumeInfo, + const FString& HeightfieldName) +{ + // Cook the node to get proper infos on it + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, &CookOptions), false); + */ + if(!FHoudiniEngineUtils::HapiCookNode(VolumeNodeId, nullptr, true)) + return false; + + // Read the geo/part/volume info from the volume node + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + //FMemory::Memset< HAPI_GeoInfo >(GeoInfo, 0); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, &GeoInfo), false); + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + //FMemory::Memset< HAPI_PartInfo >(PartInfo, 0); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoInfo.nodeId, PartId, &PartInfo), false); + + // Update the volume infos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, PartInfo.id, &VolumeInfo), false); + + // Volume name + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString(HeightfieldName, NameStr); + + // Set the Heighfield data on the volume + float * HeightData = FloatValues.GetData(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetHeightFieldData( + FHoudiniEngine::Get().GetSession(), + GeoInfo.nodeId, PartInfo.id, NameStr.c_str(), HeightData, 0, FloatValues.Num()), false); + + return true; +} + +bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + UMaterialInterface* InLandscapeMaterial, + UMaterialInterface* InLandscapeHoleMaterial, + UPhysicalMaterial* InPhysicalMaterial) +{ + if (VolumeNodeId == -1) + return false; + + // LANDSCAPE MATERIAL + if (InLandscapeMaterial && !InLandscapeMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InLandscapeMaterialString = InLandscapeMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + // HOLE MATERIAL + if (InLandscapeHoleMaterial && !InLandscapeHoleMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InLandscapeMaterialString = InLandscapeHoleMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (Result == HAPI_RESULT_SUCCESS) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_hole_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + // PHYSICAL MATERIAL + if (InPhysicalMaterial && !InPhysicalMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InPhysMatlString = InPhysicalMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (Result == HAPI_RESULT_SUCCESS) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InPhysMatlString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_physical_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + return true; +} + +/* +bool +FUnrealLandscapeTranslator::AddLevelPathAttributeToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + const FString& LevelPath) +{ + if (VolumeNodeId == -1) + return false; + + // LANDSCAPE MATERIAL + if (LevelPath.IsEmpty()) + return false; + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = 1; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to cont char * + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray LevelPathArr; + LevelPathArr.Add(LevelPathCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + LevelPathArr.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} +*/ + +bool +FUnrealLandscapeTranslator::GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, const int32& LayerIndex, + TArray& LayerData, FLinearColor& LayerUsageDebugColor, + FString& LayerName) +{ + if (!LandscapeInfo) + return false; + + // Get the landscape X/Y Size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + return false; + + if (!GetLandscapeLayerData( + LandscapeInfo, LayerIndex, + MinX, MinY, MaxX, MaxY, + LayerData, LayerUsageDebugColor, LayerName)) + return false; + + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName) +{ + if (!LandscapeInfo) + return false; + + if (!LandscapeInfo->Layers.IsValidIndex(LayerIndex)) + return false; + + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[LayerIndex]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (!LayerInfo) + return false; + + // Calc the X/Y size in points + int32 XSize = (MaxX - MinX + 1); + int32 YSize = (MaxY - MinY + 1); + if ((XSize < 2) || (YSize < 2)) + return false; + + // extracting the uint8 values from the layer + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + LayerData.AddZeroed(XSize * YSize); + LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, LayerData.GetData(), 0); + + LayerUsageDebugColor = LayerInfo->LayerUsageDebugColor; + + LayerName = LayersSetting.GetLayerName().ToString(); + + return true; +} + +bool +FUnrealLandscapeTranslator::InitDefaultHeightfieldMask( + const HAPI_VolumeInfo& HeightVolumeInfo, + const HAPI_NodeId& MaskVolumeNodeId) +{ + // We need to have a mask layer as it is required for proper heightfield functionalities + + // Creating an array filled with 0.0 + TArray< float > MaskFloatData; + MaskFloatData.Init(0.0f, HeightVolumeInfo.xLength * HeightVolumeInfo.yLength); + + // Creating the volume infos + HAPI_VolumeInfo MaskVolumeInfo = HeightVolumeInfo; + + // Set the heighfield data in Houdini + FString MaskName = TEXT("mask"); + HAPI_PartId PartId = 0; + if (!SetHeighfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) + return false; + + return true; +} + +bool +FUnrealLandscapeTranslator::DestroyLandscapeAssetNode(HAPI_NodeId& ConnectedAssetId, TArray& CreatedInputAssetIds) +{ + HAPI_AssetInfo NodeAssetInfo; + FHoudiniApi::AssetInfo_Init(&NodeAssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeAssetInfo), false); + + FHoudiniEngineString AssetOpName(NodeAssetInfo.fullOpNameSH); + FString OpName; + if (!AssetOpName.ToFString(OpName)) + return false; + + if (!OpName.Contains(TEXT("xform"))) + { + // Not a transform node, so not a Heightfield + // We just need to destroy the landscape asset node + return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); + } + + // The landscape was marshalled as a heightfield, so we need to destroy and disconnect + // the volvis nodes, all the merge node's input (each merge input is a volume for one + // of the layer/mask of the landscape ) + + // Query the volvis node id + // The volvis node is the fist input of the xform node + HAPI_NodeId VolvisNodeId = -1; + FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, &VolvisNodeId); + + // First, destroy the merge node and its inputs + // The merge node is in the first input of the volvis node + HAPI_NodeId MergeNodeId = -1; + FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + VolvisNodeId, 0, &MergeNodeId); + + if (MergeNodeId != -1) + { + // Get the merge node info + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), MergeNodeId, &NodeInfo), false); + + for (int32 n = 0; n < NodeInfo.inputCount; n++) + { + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeNodeId, n, &InputNodeId)) + break; + + if (InputNodeId == -1) + break; + + // Disconnect and Destroy that input + FHoudiniEngineUtils::HapiDisconnectAsset(MergeNodeId, n); + FHoudiniEngineUtils::DestroyHoudiniAsset(InputNodeId); + } + } + + // Second step, destroy all the volumes GEO assets + for (HAPI_NodeId AssetNodeId : CreatedInputAssetIds) + { + FHoudiniEngineUtils::DestroyHoudiniAsset(AssetNodeId); + } + CreatedInputAssetIds.Empty(); + + // Finally disconnect and destroy the xform, volvis and merge nodes, then destroy them + FHoudiniEngineUtils::HapiDisconnectAsset(ConnectedAssetId, 0); + FHoudiniEngineUtils::HapiDisconnectAsset(VolvisNodeId, 0); + FHoudiniEngineUtils::DestroyHoudiniAsset(MergeNodeId); + FHoudiniEngineUtils::DestroyHoudiniAsset(VolvisNodeId); + + return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); +} + + +bool +FUnrealLandscapeTranslator::ExtractLandscapeData( + ALandscapeProxy * LandscapeProxy, TSet& SelectedComponents, + const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, + TArray& LandscapePositionArray, + TArray& LandscapeNormalArray, + TArray& LandscapeUVArray, + TArray& LandscapeComponentVertexIndicesArray, + TArray& LandscapeComponentNameArray, + TArray& LandscapeLightmapValues) +{ + if (!LandscapeProxy) + return false; + + if (SelectedComponents.Num() < 1) + return false; + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Calc all the needed sizes + int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; + float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; + + int32 NumComponents = SelectedComponents.Num(); + bool bExportOnlySelected = NumComponents != LandscapeProxy->LandscapeComponents.Num(); + + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + int32 VertexCount = NumComponents * VertexCountPerComponent; + if (!VertexCount) + return false; + + // Initialize the data arrays + LandscapePositionArray.SetNumUninitialized(VertexCount); + LandscapeNormalArray.SetNumUninitialized(VertexCount); + LandscapeUVArray.SetNumUninitialized(VertexCount); + LandscapeComponentNameArray.SetNumUninitialized(VertexCount); + LandscapeComponentVertexIndicesArray.SetNumUninitialized(VertexCount); + if (bExportLighting) + LandscapeLightmapValues.SetNumUninitialized(VertexCount); + + //----------------------------------------------------------------------------------------------------------------- + // EXTRACT THE LANDSCAPE DATA + //----------------------------------------------------------------------------------------------------------------- + FIntPoint IntPointMax = FIntPoint::ZeroValue; + + int32 AllPositionsIdx = 0; + for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; + if (bExportOnlySelected && !SelectedComponents.Contains(LandscapeComponent)) + continue; + + TArray64< uint8 > LightmapMipData; + int32 LightmapMipSizeX = 0; + int32 LightmapMipSizeY = 0; + + // See if we need to export lighting information. + if (bExportLighting) + { + const FMeshMapBuildData* MapBuildData = LandscapeComponent->GetMeshMapBuildData(); + FLightMap2D* LightMap2D = MapBuildData && MapBuildData->LightMap ? MapBuildData->LightMap->GetLightMap2D() : nullptr; + if (LightMap2D && LightMap2D->IsValid(0)) + { + UTexture2D * TextureLightmap = LightMap2D->GetTexture(0); + if (TextureLightmap) + { + if (TextureLightmap->Source.GetMipData(LightmapMipData, 0, 0, 0, nullptr)) + { + LightmapMipSizeX = TextureLightmap->Source.GetSizeX(); + LightmapMipSizeY = TextureLightmap->Source.GetSizeY(); + } + else + { + LightmapMipData.Empty(); + } + } + } + } + + // Construct landscape component data interface to access raw data. + FLandscapeComponentDataInterface CDI(LandscapeComponent, LandscapeProxy->ExportLOD); + + // Get name of this landscape component. + const char * LandscapeComponentNameStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->GetName()); + for (int32 VertexIdx = 0; VertexIdx < VertexCountPerComponent; VertexIdx++) + { + int32 VertX = 0; + int32 VertY = 0; + CDI.VertexIndexToXY(VertexIdx, VertX, VertY); + + // Get position. + FVector PositionVector = CDI.GetWorldVertex(VertX, VertY); + + // Get normal / tangent / binormal. + FVector Normal = FVector::ZeroVector; + FVector TangentX = FVector::ZeroVector; + FVector TangentY = FVector::ZeroVector; + CDI.GetLocalTangentVectors(VertX, VertY, TangentX, TangentY, Normal); + + // Export UVs. + FVector TextureUV = FVector::ZeroVector; + if (bExportTileUVs) + { + // We want to export uvs per tile. + TextureUV = FVector(VertX, VertY, 0.0f); + + // If we need to normalize UV space. + if (bExportNormalizedUVs) + TextureUV /= ComponentSizeQuads; + } + else + { + // We want to export global uvs (default). + FIntPoint IntPoint = LandscapeComponent->GetSectionBase(); + TextureUV = FVector(VertX * ScaleFactor + IntPoint.X, VertY * ScaleFactor + IntPoint.Y, 0.0f); + + // Keep track of max offset. + IntPointMax = IntPointMax.ComponentMax(IntPoint); + } + + if (bExportLighting) + { + FLinearColor VertexLightmapColor(0.0f, 0.0f, 0.0f, 1.0f); + if (LightmapMipData.Num() > 0) + { + FVector2D UVCoord(VertX, VertY); + UVCoord /= (ComponentSizeQuads + 1); + + FColor LightmapColorRaw = PickVertexColorFromTextureMip( + LightmapMipData.GetData(), UVCoord, LightmapMipSizeX, LightmapMipSizeY); + + VertexLightmapColor = LightmapColorRaw.ReinterpretAsLinear(); + } + + LandscapeLightmapValues[AllPositionsIdx] = VertexLightmapColor; + } + + // Retrieve component transform. + const FTransform & ComponentTransform = LandscapeComponent->GetComponentTransform(); + + // Retrieve component scale. + const FVector & ScaleVector = ComponentTransform.GetScale3D(); + + // Perform normalization. + Normal /= ScaleVector; + Normal.Normalize(); + + TangentX /= ScaleVector; + TangentX.Normalize(); + + TangentY /= ScaleVector; + TangentY.Normalize(); + + // Perform position scaling. + FVector PositionTransformed = PositionVector / HAPI_UNREAL_SCALE_FACTOR_POSITION; + LandscapePositionArray[AllPositionsIdx].X = PositionTransformed.X; + LandscapePositionArray[AllPositionsIdx].Y = PositionTransformed.Z; + LandscapePositionArray[AllPositionsIdx].Z = PositionTransformed.Y; + + Swap(Normal.Y, Normal.Z); + + // Store landscape component name for this point. + LandscapeComponentNameArray[AllPositionsIdx] = LandscapeComponentNameStr; + + // Store vertex index (x,y) for this point. + LandscapeComponentVertexIndicesArray[AllPositionsIdx].X = VertX; + LandscapeComponentVertexIndicesArray[AllPositionsIdx].Y = VertY; + + // Store point normal. + LandscapeNormalArray[AllPositionsIdx] = Normal; + + // Store uv. + LandscapeUVArray[AllPositionsIdx] = TextureUV; + + AllPositionsIdx++; + } + + // Free the memory allocated for LandscapeComponentNameStr + FHoudiniEngineUtils::FreeRawStringMemory(LandscapeComponentNameStr); + } + + // If we need to normalize UV space and we are doing global UVs. + if (!bExportTileUVs && bExportNormalizedUVs) + { + IntPointMax += FIntPoint(ComponentSizeQuads, ComponentSizeQuads); + IntPointMax = IntPointMax.ComponentMax(FIntPoint(1, 1)); + + for (int32 UVIdx = 0; UVIdx < VertexCount; ++UVIdx) + { + FVector & PositionUV = LandscapeUVArray[UVIdx]; + PositionUV.X /= IntPointMax.X; + PositionUV.Y /= IntPointMax.Y; + } + } + + return true; +} + +FColor +FUnrealLandscapeTranslator::PickVertexColorFromTextureMip( + const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight) +{ + check(MipBytes); + + FColor ResultColor(0, 0, 0, 255); + + if (UVCoord.X >= 0.0f && UVCoord.X < 1.0f && UVCoord.Y >= 0.0f && UVCoord.Y < 1.0f) + { + const int32 X = MipWidth * UVCoord.X; + const int32 Y = MipHeight * UVCoord.Y; + + const int32 Index = ((Y * MipWidth) + X) * 4; + + ResultColor.B = MipBytes[Index + 0]; + ResultColor.G = MipBytes[Index + 1]; + ResultColor.R = MipBytes[Index + 2]; + ResultColor.A = MipBytes[Index + 3]; + } + + return ResultColor; +} + +bool +FUnrealLandscapeTranslator::AddLandscapePositionAttribute(const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray) +{ + int32 VertexCount = LandscapePositionArray.Num(); + if (VertexCount < 3) + return false; + + // Create point attribute info containing positions. + HAPI_AttributeInfo AttributeInfoPointPosition; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointPosition); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointPosition ); + AttributeInfoPointPosition.count = VertexCount; + AttributeInfoPointPosition.tupleSize = 3; + AttributeInfoPointPosition.exists = true; + AttributeInfoPointPosition.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointPosition.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointPosition.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition), false); + + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition, + (const float *)LandscapePositionArray.GetData(), + 0, AttributeInfoPointPosition.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeNormalAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeNormalArray) +{ + int32 VertexCount = LandscapeNormalArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointNormal; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointNormal); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointNormal ); + AttributeInfoPointNormal.count = VertexCount; + AttributeInfoPointNormal.tupleSize = 3; + AttributeInfoPointNormal.exists = true; + AttributeInfoPointNormal.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointNormal.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointNormal.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal, + (const float *)LandscapeNormalArray.GetData(), 0, VertexCount), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeUVAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeUVArray) +{ + int32 VertexCount = LandscapeUVArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointUV; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointUV); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointUV ); + AttributeInfoPointUV.count = VertexCount; + AttributeInfoPointUV.tupleSize = 3; + AttributeInfoPointUV.exists = true; + AttributeInfoPointUV.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointUV.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointUV.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV, + (const float *)LandscapeUVArray.GetData(), 0, AttributeInfoPointUV.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeComponentVertexIndicesAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentVertexIndicesArray) +{ + int32 VertexCount = LandscapeComponentVertexIndicesArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointLandscapeComponentVertexIndices; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentVertexIndices); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentVertexIndices ); + AttributeInfoPointLandscapeComponentVertexIndices.count = VertexCount; + AttributeInfoPointLandscapeComponentVertexIndices.tupleSize = 2; + AttributeInfoPointLandscapeComponentVertexIndices.exists = true; + AttributeInfoPointLandscapeComponentVertexIndices.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLandscapeComponentVertexIndices.storage = HAPI_STORAGETYPE_INT; + AttributeInfoPointLandscapeComponentVertexIndices.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, + &AttributeInfoPointLandscapeComponentVertexIndices), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, + &AttributeInfoPointLandscapeComponentVertexIndices, + (const int *)LandscapeComponentVertexIndicesArray.GetData(), 0, + AttributeInfoPointLandscapeComponentVertexIndices.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeComponentNameAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray) +{ + int32 VertexCount = LandscapeComponentNameArray.Num(); + if (VertexCount < 3) + return false; + + // Create point attribute containing landscape component name. + HAPI_AttributeInfo AttributeInfoPointLandscapeComponentNames; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentNames); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentNames ); + AttributeInfoPointLandscapeComponentNames.count = VertexCount; + AttributeInfoPointLandscapeComponentNames.tupleSize = 1; + AttributeInfoPointLandscapeComponentNames.exists = true; + AttributeInfoPointLandscapeComponentNames.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLandscapeComponentNames.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPointLandscapeComponentNames.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, + &AttributeInfoPointLandscapeComponentNames), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, + &AttributeInfoPointLandscapeComponentNames, + (const char **)LandscapeComponentNameArray.GetData(), + 0, AttributeInfoPointLandscapeComponentNames.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeLightmapColorAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues) +{ + int32 VertexCount = LandscapeLightmapValues.Num(); + + HAPI_AttributeInfo AttributeInfoPointLightmapColor; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLightmapColor); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLightmapColor ); + AttributeInfoPointLightmapColor.count = VertexCount; + AttributeInfoPointLightmapColor.tupleSize = 4; + AttributeInfoPointLightmapColor.exists = true; + AttributeInfoPointLightmapColor.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLightmapColor.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointLightmapColor.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor, + (const float *)LandscapeLightmapValues.GetData(), 0, + AttributeInfoPointLightmapColor.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeMeshIndicesAndMaterialsAttribute( + const HAPI_NodeId& NodeId, const bool& bExportMaterials, + const int32& ComponentSizeQuads, const int32& QuadCount, + ALandscapeProxy * LandscapeProxy, + const TSet< ULandscapeComponent * >& SelectedComponents) +{ + if (!LandscapeProxy) + return false; + + // Compute number of necessary indices. + int32 IndexCount = QuadCount * 4; + if (IndexCount < 0) + return false; + + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + + // Array holding indices data. + TArray LandscapeIndices; + LandscapeIndices.SetNumUninitialized(IndexCount); + + // Allocate space for face names. + // The LandscapeMaterial and HoleMaterial per point + TArray FaceMaterials; + TArray FaceHoleMaterials; + FaceMaterials.SetNumUninitialized(QuadCount); + FaceHoleMaterials.SetNumUninitialized(QuadCount); + + int32 VertIdx = 0; + int32 QuadIdx = 0; + + const char * MaterialRawStr = nullptr; + const char * MaterialHoleRawStr = nullptr; + + // Lambda for freeing the memory allocated by ExtractRawString and returning + auto FreeMemoryReturn = [&MaterialRawStr, &MaterialHoleRawStr](const bool& bReturn) + { + FHoudiniEngineUtils::FreeRawStringMemory(MaterialRawStr); + FHoudiniEngineUtils::FreeRawStringMemory(MaterialHoleRawStr); + + return bReturn; + }; + + const int32 QuadComponentCount = ComponentSizeQuads + 1; + for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; + if (!SelectedComponents.Contains(LandscapeComponent)) + continue; + + if (bExportMaterials) + { + // If component has an override material, we need to get the raw name (if exporting materials). + if (LandscapeComponent->OverrideMaterial) + { + MaterialRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideMaterial->GetName()); + } + + // If component has an override hole material, we need to get the raw name (if exporting materials). + if (LandscapeComponent->OverrideHoleMaterial) + { + MaterialHoleRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideHoleMaterial->GetName()); + } + } + + int32 BaseVertIndex = ComponentIdx * VertexCountPerComponent; + for (int32 YIdx = 0; YIdx < ComponentSizeQuads; YIdx++) + { + for (int32 XIdx = 0; XIdx < ComponentSizeQuads; XIdx++) + { + LandscapeIndices[VertIdx + 0] = BaseVertIndex + (XIdx + 0) + (YIdx + 0) * QuadComponentCount; + LandscapeIndices[VertIdx + 1] = BaseVertIndex + (XIdx + 1) + (YIdx + 0) * QuadComponentCount; + LandscapeIndices[VertIdx + 2] = BaseVertIndex + (XIdx + 1) + (YIdx + 1) * QuadComponentCount; + LandscapeIndices[VertIdx + 3] = BaseVertIndex + (XIdx + 0) + (YIdx + 1) * QuadComponentCount; + + // Store override materials (if exporting materials). + if (bExportMaterials) + { + FaceMaterials[QuadIdx] = MaterialRawStr; + FaceHoleMaterials[QuadIdx] = MaterialHoleRawStr; + } + + VertIdx += 4; + QuadIdx++; + } + } + } + + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, LandscapeIndices.GetData(), 0, LandscapeIndices.Num()), + FreeMemoryReturn(false)); + + // We need to generate array of face counts. + TArray LandscapeFaces; + LandscapeFaces.Init(4, QuadCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, LandscapeFaces.GetData(), 0, LandscapeFaces.Num()), + FreeMemoryReturn(false)); + + if (bExportMaterials) + { + if (!FaceMaterials.Contains(nullptr)) + { + // Marshall in override primitive material names. + HAPI_AttributeInfo AttributeInfoPrimitiveMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterial ); + AttributeInfoPrimitiveMaterial.count = FaceMaterials.Num(); + AttributeInfoPrimitiveMaterial.tupleSize = 1; + AttributeInfoPrimitiveMaterial.exists = true; + AttributeInfoPrimitiveMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoPrimitiveMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPrimitiveMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial, + (const char **)FaceMaterials.GetData(), 0, AttributeInfoPrimitiveMaterial.count), + FreeMemoryReturn(false)); + } + + if (!FaceHoleMaterials.Contains(nullptr)) + { + // Marshall in override primitive material hole names. + HAPI_AttributeInfo AttributeInfoPrimitiveMaterialHole; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterialHole); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterialHole ); + AttributeInfoPrimitiveMaterialHole.count = FaceHoleMaterials.Num(); + AttributeInfoPrimitiveMaterialHole.tupleSize = 1; + AttributeInfoPrimitiveMaterialHole.exists = true; + AttributeInfoPrimitiveMaterialHole.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoPrimitiveMaterialHole.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPrimitiveMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoPrimitiveMaterialHole), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoPrimitiveMaterialHole, (const char **)FaceHoleMaterials.GetData(), 0, + AttributeInfoPrimitiveMaterialHole.count), + FreeMemoryReturn(false)); + } + } + + // Free the memory and return true + return FreeMemoryReturn(true); +} + +bool +FUnrealLandscapeTranslator::AddLandscapeGlobalMaterialAttribute( + const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy) +{ + if (!LandscapeProxy) + return false; + + // If there's a global landscape material, we marshall it as detail. + UMaterialInterface * MaterialInterface = LandscapeProxy->GetLandscapeMaterial(); + const char * MaterialNameStr = ""; + if (MaterialInterface) + { + FString FullMaterialName = MaterialInterface->GetPathName(); + MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); + } + + HAPI_AttributeInfo AttributeInfoDetailMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterial ); + AttributeInfoDetailMaterial.count = 1; + AttributeInfoDetailMaterial.tupleSize = 1; + AttributeInfoDetailMaterial.exists = true; + AttributeInfoDetailMaterial.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoDetailMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoDetailMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial, + (const char**)&MaterialNameStr, 0, AttributeInfoDetailMaterial.count), false); + + // If there's a global landscape hole material, we marshall it as detail. + UMaterialInterface * HoleMaterialInterface = LandscapeProxy->GetLandscapeHoleMaterial(); + const char * HoleMaterialNameStr = ""; + if (HoleMaterialInterface) + { + FString FullMaterialName = HoleMaterialInterface->GetPathName(); + MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); + } + + HAPI_AttributeInfo AttributeInfoDetailMaterialHole; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterialHole); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterialHole ); + AttributeInfoDetailMaterialHole.count = 1; + AttributeInfoDetailMaterialHole.tupleSize = 1; + AttributeInfoDetailMaterialHole.exists = true; + AttributeInfoDetailMaterialHole.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoDetailMaterialHole.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoDetailMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoDetailMaterialHole), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoDetailMaterialHole, (const char **)&HoleMaterialNameStr, 0, + AttributeInfoDetailMaterialHole.count), false); + + return true; +} + + +bool +FUnrealLandscapeTranslator::AddLandscapeLayerAttribute( + const HAPI_NodeId& NodeId, const TArray& LandscapeLayerArray, const FString& LayerName) +{ + int32 VertexCount = LandscapeLayerArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoLayer; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLayer); + AttributeInfoLayer.count = VertexCount; + AttributeInfoLayer.tupleSize = 1; + AttributeInfoLayer.exists = true; + AttributeInfoLayer.owner = HAPI_ATTROWNER_POINT; + AttributeInfoLayer.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLayer.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, + TCHAR_TO_ANSI(*LayerName), + &AttributeInfoLayer), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, + TCHAR_TO_ANSI(*LayerName), + &AttributeInfoLayer, + (const float *)LandscapeLayerArray.GetData(), + 0, AttributeInfoLayer.count), false); + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h index ecc94a963..b6dafe507 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h @@ -1,234 +1,234 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Landscape.h" -#include "HAPI/HAPI_Common.h" - -class ALandscapeProxy; -class UHoudiniInputLandscape; - -struct HOUDINIENGINE_API FUnrealLandscapeTranslator -{ - public: - - // ------------------------------------------------------------------------------------------ - // Unreal Landscape to Houdini Heightfield - // ------------------------------------------------------------------------------------------ - static bool CreateHeightfieldFromLandscape( - ALandscapeProxy* LandcapeProxy, - HAPI_NodeId& CreatedHeightfieldNodeId, - const FString &InputNodeNameStr); - - // Extracts the uint16 values of a given landscape - static bool GetLandscapeData( - ALandscapeProxy* LandscapeProxy, - TArray& HeightData, - int32& XSize, int32& YSize, - FVector& Min, FVector& Max); - - static bool GetLandscapeData( - ULandscapeInfo* LandscapeInfo, - const int32& MinX, - const int32& MinY, - const int32& MaxX, - const int32& MaxY, - TArray& HeightData, - int32& XSize, int32& YSize); - - static void GetLandscapeProxyBounds( - ALandscapeProxy* LandscapeProxy, - FVector& Origin, FVector& Extents); - - // Converts Unreal uint16 values to Houdini Float - static bool ConvertLandscapeDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, - const int32& YSize, - FVector Min, - FVector Max, - const FTransform& LandscapeTransform, - TArray& HeightfieldFloatValues, - HAPI_VolumeInfo& HeightfieldVolumeInfo, - FVector& CenterOffset); - - // Converts Unreal uint8 values to Houdini Float - static bool ConvertLandscapeLayerDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - const FLinearColor& LayerUsageDebugColor, - TArray& LayerFloatValues, - HAPI_VolumeInfo& LayerVolumeInfo); - - // Creates an unlocked heightfield input node - static bool CreateHeightfieldInputNode( - const FString& NodeName, - const int32& XSize, - const int32& YSize, - HAPI_NodeId& HeightfieldNodeId, - HAPI_NodeId& HeightNodeId, - HAPI_NodeId& MaskNodeId, - HAPI_NodeId& MergeNodeId ); - - // Set the volume float value for a heightfield - static bool SetHeighfieldData( - const HAPI_NodeId& AssetId, - const HAPI_PartId& PartId, - TArray< float >& FloatValues, - const HAPI_VolumeInfo& VolumeInfo, - const FString& HeightfieldName); - - static bool AddLandscapeMaterialAttributesToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* InPhysicalMaterial); - - /* - static bool AddLevelPathAttributeToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - const FString& LevelPath); - */ - - // Extracts the uint8 values of a given landscape - static bool GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName); - - static bool GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - const int32& MinX, - const int32& MinY, - const int32& MaxX, - const int32& MaxY, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName); - - // Initialise the Heightfield Mask with default values - static bool InitDefaultHeightfieldMask( - const HAPI_VolumeInfo& HeightVolumeInfo, - const HAPI_NodeId& MaskVolumeNodeId); - - // Landscape nodes clean up - static bool DestroyLandscapeAssetNode( - HAPI_NodeId& ConnectedAssetId, - TArray& CreatedInputAssetIds); - - - //-------------------------------------------------------------------------------------------------- - // Unreal to Houdini - MESH / POINTS - //-------------------------------------------------------------------------------------------------- - - static bool CreateMeshOrPointsFromLandscape( - ALandscapeProxy* InLandscape, - HAPI_NodeId& InputNodeId, - const FString& InInputNodeNameString, - const bool& bExportGeometryAsMesh, - const bool& bExportTileUVs, - const bool bExportNormalizedUVs, - const bool bExportLighting, - const bool bExportMaterials); - - // Extract data from the landscape - static bool ExtractLandscapeData( - ALandscapeProxy * LandscapeProxy, - TSet& SelectedComponents, - const bool& bExportLighting, - const bool& bExportTileUVs, - const bool& bExportNormalizedUVs, - TArray& LandscapePositionArray, - TArray& LandscapeNormalArray, - TArray& LandscapeUVArray, - TArray& LandscapeComponentVertexIndicesArray, - TArray& LandscapeComponentNameArray, - TArray& LandscapeLightmapValues); - - // Helper functions to extract color from a texture - static FColor PickVertexColorFromTextureMip( - const uint8 * MipBytes, - FVector2D & UVCoord, - int32 MipWidth, - int32 MipHeight); - - // Add the Position attribute extracted from a landscape - static bool AddLandscapePositionAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapePositionArray); - - // Add the Normal attribute extracted from a landscape - static bool AddLandscapeNormalAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapeNormalArray); - - // Add the UV attribute extracted from a landscape - static bool AddLandscapeUVAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapeUVArray); - - // Add the Component Vertex Index attribute extracted from a landscape - static bool AddLandscapeComponentVertexIndicesAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeComponentVertexIndicesArray); - - // Add the Component Name attribute extracted from a landscape - static bool AddLandscapeComponentNameAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeComponentNameArray); - - // Add the lightmap color attribute extracted from a landscape - static bool AddLandscapeLightmapColorAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeLightmapValues); - - // Creates and add the vertex indices and face materials attribute from a landscape - static bool AddLandscapeMeshIndicesAndMaterialsAttribute( - const HAPI_NodeId& NodeId, - const bool& bExportMaterials, - const int32& ComponentSizeQuads, - const int32& QuadCount, - ALandscapeProxy * LandscapeProxy, - const TSet& SelectedComponents); - - // Add the global (detail) material and hole material attribute from a landscape - static bool AddLandscapeGlobalMaterialAttribute( - const HAPI_NodeId& NodeId, - ALandscapeProxy * LandscapeProxy); - - // Add landscape layer values as point attributes - static bool AddLandscapeLayerAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeLayerArray, - const FString& LayerName); - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Landscape.h" +#include "HAPI/HAPI_Common.h" + +class ALandscapeProxy; +class UHoudiniInputLandscape; + +struct HOUDINIENGINE_API FUnrealLandscapeTranslator +{ + public: + + // ------------------------------------------------------------------------------------------ + // Unreal Landscape to Houdini Heightfield + // ------------------------------------------------------------------------------------------ + static bool CreateHeightfieldFromLandscape( + ALandscapeProxy* LandcapeProxy, + HAPI_NodeId& CreatedHeightfieldNodeId, + const FString &InputNodeNameStr); + + // Extracts the uint16 values of a given landscape + static bool GetLandscapeData( + ALandscapeProxy* LandscapeProxy, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max); + + static bool GetLandscapeData( + ULandscapeInfo* LandscapeInfo, + const int32& MinX, + const int32& MinY, + const int32& MaxX, + const int32& MaxY, + TArray& HeightData, + int32& XSize, int32& YSize); + + static void GetLandscapeProxyBounds( + ALandscapeProxy* LandscapeProxy, + FVector& Origin, FVector& Extents); + + // Converts Unreal uint16 values to Houdini Float + static bool ConvertLandscapeDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, + const int32& YSize, + FVector Min, + FVector Max, + const FTransform& LandscapeTransform, + TArray& HeightfieldFloatValues, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + FVector& CenterOffset); + + // Converts Unreal uint8 values to Houdini Float + static bool ConvertLandscapeLayerDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + const FLinearColor& LayerUsageDebugColor, + TArray& LayerFloatValues, + HAPI_VolumeInfo& LayerVolumeInfo); + + // Creates an unlocked heightfield input node + static bool CreateHeightfieldInputNode( + const FString& NodeName, + const int32& XSize, + const int32& YSize, + HAPI_NodeId& HeightfieldNodeId, + HAPI_NodeId& HeightNodeId, + HAPI_NodeId& MaskNodeId, + HAPI_NodeId& MergeNodeId ); + + // Set the volume float value for a heightfield + static bool SetHeighfieldData( + const HAPI_NodeId& AssetId, + const HAPI_PartId& PartId, + TArray< float >& FloatValues, + const HAPI_VolumeInfo& VolumeInfo, + const FString& HeightfieldName); + + static bool AddLandscapeMaterialAttributesToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* InPhysicalMaterial); + + /* + static bool AddLevelPathAttributeToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + const FString& LevelPath); + */ + + // Extracts the uint8 values of a given landscape + static bool GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName); + + static bool GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + const int32& MinX, + const int32& MinY, + const int32& MaxX, + const int32& MaxY, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName); + + // Initialise the Heightfield Mask with default values + static bool InitDefaultHeightfieldMask( + const HAPI_VolumeInfo& HeightVolumeInfo, + const HAPI_NodeId& MaskVolumeNodeId); + + // Landscape nodes clean up + static bool DestroyLandscapeAssetNode( + HAPI_NodeId& ConnectedAssetId, + TArray& CreatedInputAssetIds); + + + //-------------------------------------------------------------------------------------------------- + // Unreal to Houdini - MESH / POINTS + //-------------------------------------------------------------------------------------------------- + + static bool CreateMeshOrPointsFromLandscape( + ALandscapeProxy* InLandscape, + HAPI_NodeId& InputNodeId, + const FString& InInputNodeNameString, + const bool& bExportGeometryAsMesh, + const bool& bExportTileUVs, + const bool bExportNormalizedUVs, + const bool bExportLighting, + const bool bExportMaterials); + + // Extract data from the landscape + static bool ExtractLandscapeData( + ALandscapeProxy * LandscapeProxy, + TSet& SelectedComponents, + const bool& bExportLighting, + const bool& bExportTileUVs, + const bool& bExportNormalizedUVs, + TArray& LandscapePositionArray, + TArray& LandscapeNormalArray, + TArray& LandscapeUVArray, + TArray& LandscapeComponentVertexIndicesArray, + TArray& LandscapeComponentNameArray, + TArray& LandscapeLightmapValues); + + // Helper functions to extract color from a texture + static FColor PickVertexColorFromTextureMip( + const uint8 * MipBytes, + FVector2D & UVCoord, + int32 MipWidth, + int32 MipHeight); + + // Add the Position attribute extracted from a landscape + static bool AddLandscapePositionAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapePositionArray); + + // Add the Normal attribute extracted from a landscape + static bool AddLandscapeNormalAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapeNormalArray); + + // Add the UV attribute extracted from a landscape + static bool AddLandscapeUVAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapeUVArray); + + // Add the Component Vertex Index attribute extracted from a landscape + static bool AddLandscapeComponentVertexIndicesAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeComponentVertexIndicesArray); + + // Add the Component Name attribute extracted from a landscape + static bool AddLandscapeComponentNameAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeComponentNameArray); + + // Add the lightmap color attribute extracted from a landscape + static bool AddLandscapeLightmapColorAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeLightmapValues); + + // Creates and add the vertex indices and face materials attribute from a landscape + static bool AddLandscapeMeshIndicesAndMaterialsAttribute( + const HAPI_NodeId& NodeId, + const bool& bExportMaterials, + const int32& ComponentSizeQuads, + const int32& QuadCount, + ALandscapeProxy * LandscapeProxy, + const TSet& SelectedComponents); + + // Add the global (detail) material and hole material attribute from a landscape + static bool AddLandscapeGlobalMaterialAttribute( + const HAPI_NodeId& NodeId, + ALandscapeProxy * LandscapeProxy); + + // Add landscape layer values as point attributes + static bool AddLandscapeLayerAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeLayerArray, + const FString& LayerName); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp index 49d233faf..63593286f 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp @@ -1,4123 +1,4120 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealMeshTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "RawMesh.h" -#include "MeshDescription.h" -#include "MeshDescriptionOperations.h" -#include "Engine/StaticMesh.h" -#include "PhysicsEngine/BodySetup.h" -#include "Engine/StaticMeshSocket.h" -#include "Components/StaticMeshComponent.h" -#include "Materials/Material.h" -#include "Materials/MaterialInterface.h" -#include "MeshAttributes.h" -#include "StaticMeshAttributes.h" - -#if WITH_EDITOR - #include "EditorFramework/AssetImportData.h" -#endif - -bool -FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - UStaticMesh* StaticMesh, - HAPI_NodeId& InputNodeId, - const FString& InputNodeName, - UStaticMeshComponent* StaticMeshComponent /* = nullptr */, - const bool& ExportAllLODs /* = false */, - const bool& ExportSockets /* = false */, - const bool& ExportColliders /* = false */) -{ - // If we don't have a static mesh there's nothing to do. - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Node ID for the newly created node - HAPI_NodeId NewNodeId = -1; - - // Export sockets if there are some - bool DoExportSockets = ExportSockets && (StaticMesh->Sockets.Num() > 0); - - // Export LODs if there are some - bool DoExportLODs = ExportAllLODs && (StaticMesh->GetNumLODs() > 1); - - // Export colliders if there are some - bool DoExportColliders = ExportColliders && StaticMesh->BodySetup != nullptr; - if (DoExportColliders) - { - if (!StaticMesh->BodySetup) - { - DoExportColliders = false; - } - else - { - if (StaticMesh->BodySetup->AggGeom.GetElementCount() <= 0) - DoExportColliders = false; - } - } - - // We need to use a merge node if we export lods OR sockets - bool UseMergeNode = DoExportLODs || DoExportSockets || DoExportColliders; - if (UseMergeNode) - { - // TODO: - // What if OutInputNodeId already exists? - // Delete previous merge?/input? - - // Create a merge SOP asset. This will be our "InputNodeId" - // as all the different LOD meshes and sockets will be plugged into it - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/merge"), InputNodeName, true, &NewNodeId), false); - } - else - { - // No LODs/Sockets, we just need a single input node - // If InputNodeId is invalid, we need to create an input node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_ANSI(*InputNodeName)), false); - - if (!FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true)) - return false; - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); - */ - } - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) - return false; - - HAPI_NodeId PreviousInputNodeId = InputNodeId; - - // Update our input NodeId - InputNodeId = NewNodeId; - // Get our parent OBJ NodeID - HAPI_NodeId InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); - - // We have now created a valid new input node, delete the previous one - if (PreviousInputNodeId >= 0) - { - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); - } - } - - // TODO: - // Setting for lightmap resolution? - //const uint8 ExportMethod = 0; // Raw mesh - //const uint8 ExportMethod = 1; // Mesh description - const uint8 ExportMethod = 2; // LODResources (render mesh) - //bool bExportViaRawMesh = false; - - int32 NumLODsToExport = DoExportLODs ? StaticMesh->GetNumLODs() : 1; - for (int32 LODIndex = 0; LODIndex < NumLODsToExport; LODIndex++) - { - // Grab the LOD level. - FStaticMeshSourceModel & SrcModel = StaticMesh->GetSourceModel(LODIndex); - - // If we're using a merge node, we need to create a new input null - HAPI_NodeId CurrentLODNodeId = -1; - if (UseMergeNode) - { - // Create a new input node for the current LOD - const char * LODName = ""; - { - FString LOD = TEXT("lod") + FString::FromInt(LODIndex); - LODName = TCHAR_TO_UTF8(*LOD); - } - - // Create the node in this input object's OBJ node - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InputObjectNodeId, TEXT("null"), LODName, false, &CurrentLODNodeId), false); - } - else - { - // No merge node, just use the input node we created before - CurrentLODNodeId = NewNodeId; - } - - // Either export the current LOD Mesh by using RawMEsh or MeshDescription (legacy) - FMeshDescription* MeshDesc = nullptr; - // if (!bExportViaRawMesh) - if (ExportMethod == 1) - { - // This will either fetch the mesh description that is cached on the SrcModel - // or load it from bulk data / DDC once - if (SrcModel.MeshDescription.IsValid()) - { - MeshDesc = SrcModel.MeshDescription.Get(); - } - else - { - const double StartTime = FPlatformTime::Seconds(); - MeshDesc = StaticMesh->GetMeshDescription(LODIndex); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->GetMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - } - - bool bMeshSuccess = false; - if (ExportMethod == 1 && MeshDesc) - { - // Convert the Mesh using FMeshDescription - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForMeshDescription( - CurrentLODNodeId, - *MeshDesc, - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - else if (ExportMethod == 2) - { - // Convert the LOD Mesh using FStaticMeshLODResources - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( - CurrentLODNodeId, - StaticMesh->GetLODForExport(LODIndex), - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - else - { - // Convert the LOD Mesh using FRawMesh - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForRawMesh( - CurrentLODNodeId, - SrcModel, - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForRawMesh completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - - if (!bMeshSuccess) - continue; - - if (UseMergeNode) - { - // Connect the LOD node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, LODIndex, CurrentLODNodeId, 0), false); - } - } - - // next Index for adding nodes to the merge - int32 NextMergeIndex = NumLODsToExport; - if (DoExportColliders) - { - FKAggregateGeom SimpleColliders = StaticMesh->BodySetup->AggGeom; - - // Export BOX colliders - for (auto& CurBox : SimpleColliders.BoxElems) - { - FVector BoxCenter = CurBox.Center; - FVector BoxExtent = FVector(CurBox.X, CurBox.Y, CurBox.Z); - FRotator BoxRotation = CurBox.Rotation; - - HAPI_NodeId BoxNodeId = -1; - if (!CreateInputNodeForBox( - BoxNodeId, InputObjectNodeId, NextMergeIndex, - BoxCenter, BoxExtent, BoxRotation)) - continue; - - if (BoxNodeId < 0) - continue; - - // Connect the Box node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, BoxNodeId, 0), false); - - NextMergeIndex++; - } - - // Export SPHERE colliders - for (auto& CurSphere : SimpleColliders.SphereElems) - { - HAPI_NodeId SphereNodeId = -1; - if (!CreateInputNodeForSphere( - SphereNodeId, InputObjectNodeId, NextMergeIndex, - CurSphere.Center, CurSphere.Radius)) - continue; - - if (SphereNodeId < 0) - continue; - - // Connect the Sphere node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, SphereNodeId, 0), false); - - NextMergeIndex++; - } - - // Export CAPSULE colliders - for (auto& CurSphyl : SimpleColliders.SphylElems) - { - HAPI_NodeId SphylNodeId = -1; - if (!CreateInputNodeForSphyl( - SphylNodeId, InputObjectNodeId, NextMergeIndex, - CurSphyl.Center, CurSphyl.Rotation, CurSphyl.Radius, CurSphyl.Length)) - continue; - - if (SphylNodeId < 0) - continue; - - // Connect the capsule node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, SphylNodeId, 0), false); - - NextMergeIndex++; - } - - // Export CONVEX colliders - for (auto& CurConvex : SimpleColliders.ConvexElems) - { - HAPI_NodeId ConvexNodeId = -1; - if (!CreateInputNodeForConvex( - ConvexNodeId, InputObjectNodeId, NextMergeIndex, CurConvex)) - continue; - - if (ConvexNodeId < 0) - continue; - - // Connect the capsule node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, ConvexNodeId, 0), false); - - NextMergeIndex++; - } - } - - if (DoExportSockets && StaticMesh->Sockets.Num() > 0) - { - // Create an input node for the mesh sockets - HAPI_NodeId SocketsNodeId = -1; - if (CreateInputNodeForMeshSockets(StaticMesh->Sockets, InputObjectNodeId, SocketsNodeId)) - { - // We can connect the socket node to the merge node's last input. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), NewNodeId, NextMergeIndex, SocketsNodeId, 0), false); - - NextMergeIndex++; - } - else if (SocketsNodeId != -1) - { - // If we failed to properly export the sockets, clean up the created node - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), SocketsNodeId); - } - } - - // - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForMeshSockets( - const TArray& InMeshSocket, const HAPI_NodeId& InParentNodeId, HAPI_NodeId& OutSocketsNodeId) -{ - int32 NumSockets = InMeshSocket.Num(); - if (NumSockets <= 0) - return false; - - // Create a new input node for the sockets - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeId, TEXT("null"), "sockets", false, &OutSocketsNodeId), false); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.pointCount = NumSockets; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, &Part), false); - - // Create POS point attribute info. - HAPI_AttributeInfo AttributeInfoPos; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); - AttributeInfoPos.count = NumSockets; - AttributeInfoPos.tupleSize = 3; - AttributeInfoPos.exists = true; - AttributeInfoPos.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos), false); - - // Create Rot point attribute Info - HAPI_AttributeInfo AttributeInfoRot; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); - AttributeInfoRot.count = NumSockets; - AttributeInfoRot.tupleSize = 4; - AttributeInfoRot.exists = true; - AttributeInfoRot.owner = HAPI_ATTROWNER_POINT; - AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot), false); - - // Create scale point attribute Info - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = NumSockets; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); - - // Create the name attrib info - HAPI_AttributeInfo AttributeInfoName; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); - AttributeInfoName.count = NumSockets; - AttributeInfoName.tupleSize = 1; - AttributeInfoName.exists = true; - AttributeInfoName.owner = HAPI_ATTROWNER_POINT; - AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName), false); - - // Create the tag attrib info - HAPI_AttributeInfo AttributeInfoTag; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); - AttributeInfoTag.count = NumSockets; - AttributeInfoTag.tupleSize = 1; - AttributeInfoTag.exists = true; - AttributeInfoTag.owner = HAPI_ATTROWNER_POINT; - AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag), false); - - // Extract the sockets transform values - TArray SocketPos; - SocketPos.SetNumZeroed(NumSockets * 3); - TArray SocketRot; - SocketRot.SetNumZeroed(NumSockets * 4); - TArray SocketScale; - SocketScale.SetNumZeroed(NumSockets * 3); - - // raw string array for names and tag, will need to be free before returning - TArray SocketNames; - TArray SocketTags; - - // Lambda for freeing the const char array's memory and returning - auto FreeMemoryReturn = [&SocketNames, &SocketTags](const bool& bReturn) - { - // Frees the memory allocated by ExtractRawString for the names and tags - FHoudiniEngineUtils::FreeRawStringMemory(SocketNames); - FHoudiniEngineUtils::FreeRawStringMemory(SocketTags); - - return bReturn; - }; - - for (int32 Idx = 0; Idx < NumSockets; ++Idx) - { - UStaticMeshSocket* CurrentSocket = InMeshSocket[Idx]; - if (!CurrentSocket || CurrentSocket->IsPendingKill()) - continue; - - // Get the socket's transform and convert it to HapiTransform - FTransform SocketTransform(CurrentSocket->RelativeRotation, CurrentSocket->RelativeLocation, CurrentSocket->RelativeScale); - HAPI_Transform HapiSocketTransform; - FHoudiniApi::Transform_Init(&HapiSocketTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(SocketTransform, HapiSocketTransform); - - // Fill the attribute values - SocketPos[3 * Idx + 0] = HapiSocketTransform.position[0]; - SocketPos[3 * Idx + 1] = HapiSocketTransform.position[1]; - SocketPos[3 * Idx + 2] = HapiSocketTransform.position[2]; - - SocketRot[4 * Idx + 0] = HapiSocketTransform.rotationQuaternion[0]; - SocketRot[4 * Idx + 1] = HapiSocketTransform.rotationQuaternion[1]; - SocketRot[4 * Idx + 2] = HapiSocketTransform.rotationQuaternion[2]; - SocketRot[4 * Idx + 3] = HapiSocketTransform.rotationQuaternion[3]; - - SocketScale[3 * Idx + 0] = HapiSocketTransform.scale[0]; - SocketScale[3 * Idx + 1] = HapiSocketTransform.scale[1]; - SocketScale[3 * Idx + 2] = HapiSocketTransform.scale[2]; - - FString CurrentSocketName; - if (!CurrentSocket->SocketName.IsNone()) - CurrentSocketName = CurrentSocket->SocketName.ToString(); - else - CurrentSocketName = TEXT("Socket") + FString::FromInt(Idx); - SocketNames.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocketName)); - - if (!CurrentSocket->Tag.IsEmpty()) - SocketTags.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocket->Tag)); - else - SocketTags.Add(""); - } - - //we can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos, - SocketPos.GetData(), 0, AttributeInfoPos.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot, - SocketRot.GetData(), 0, AttributeInfoRot.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, - SocketScale.GetData(), 0, AttributeInfoScale.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName, - SocketNames.GetData(), 0, AttributeInfoName.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag, - SocketTags.GetData(), 0, AttributeInfoTag.count), - FreeMemoryReturn(false)); - - // We will also create the socket_details attributes - for (int32 Idx = 0; Idx < NumSockets; ++Idx) - { - // Build the current socket's prefix - FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(Idx); - - // Create mesh_socketX_pos attribute info. - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); - AttributeInfoPos.count = 1; - AttributeInfoPos.tupleSize = 3; - AttributeInfoPos.exists = true; - AttributeInfoPos.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; - - FString PosAttr = SocketAttrPrefix + TEXT("_pos"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos, - &(SocketPos[3 * Idx]), 0, AttributeInfoPos.count), - FreeMemoryReturn(false)); - - // Create mesh_socketX_rot point attribute Info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); - AttributeInfoRot.count = 1; - AttributeInfoRot.tupleSize = 4; - AttributeInfoRot.exists = true; - AttributeInfoRot.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; - - FString RotAttr = SocketAttrPrefix + TEXT("_rot"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot, - &(SocketRot[4 * Idx]), 0, AttributeInfoRot.count), - FreeMemoryReturn(false)); - - // Create mesh_socketX_scale point attribute Info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = 1; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - FString ScaleAttr = SocketAttrPrefix + TEXT("_scale"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale, - &(SocketScale[3 * Idx]), 0, AttributeInfoScale.count), - FreeMemoryReturn(false)); - - // Create the mesh_socketX_name attrib info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); - AttributeInfoName.count = 1; - AttributeInfoName.tupleSize = 1; - AttributeInfoName.exists = true; - AttributeInfoName.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; - - FString NameAttr = SocketAttrPrefix + TEXT("_name"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName, - &(SocketNames[Idx]), 0, AttributeInfoName.count), - FreeMemoryReturn(false)); - - // Create the mesh_socketX_tag attrib info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); - AttributeInfoTag.count = 1; - AttributeInfoTag.tupleSize = 1; - AttributeInfoTag.exists = true; - AttributeInfoTag.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; - - FString TagAttr = SocketAttrPrefix + TEXT("_tag"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag, - &(SocketTags[Idx]), 0, AttributeInfoTag.count), - FreeMemoryReturn(false)); - } - - // Now add the sockets group - const char * SocketGroupStr = "socket_imported"; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr), - FreeMemoryReturn(false)); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, NumSockets); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr, GroupArray.GetData(), 0, NumSockets), - FreeMemoryReturn(false)); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), OutSocketsNodeId), - FreeMemoryReturn(false)); - - return FreeMemoryReturn(true); -} - -bool -FUnrealMeshTranslator::CreateInputNodeForRawMesh( - const HAPI_NodeId& NodeId, - const FStaticMeshSourceModel& SourceModel, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent ) -{ - // Convert the Mesh using FRawMesh - FRawMesh RawMesh; - SourceModel.LoadRawMesh(RawMesh); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = RawMesh.WedgeIndices.Num(); - Part.faceCount = RawMesh.WedgeIndices.Num() / 3; - Part.pointCount = RawMesh.VertexPositions.Num(); - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = RawMesh.VertexPositions.Num(); - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Grab the build scale - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.VertexPositions.Num() > 3) - { - TArray StaticMeshVertices; - StaticMeshVertices.SetNumZeroed(RawMesh.VertexPositions.Num() * 3); - for (int32 VertexIdx = 0; VertexIdx < RawMesh.VertexPositions.Num(); ++VertexIdx) - { - // Convert Unreal to Houdini - const FVector & PositionVector = RawMesh.VertexPositions[VertexIdx]; - StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; - StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; - StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - for (int32 MeshTexCoordIdx = 0; MeshTexCoordIdx < MAX_STATIC_TEXCOORDS; MeshTexCoordIdx++) - { - int32 StaticMeshUVCount = RawMesh.WedgeTexCoords[MeshTexCoordIdx].Num(); - if (StaticMeshUVCount > 0) - { - const TArray & RawMeshUVs = RawMesh.WedgeTexCoords[MeshTexCoordIdx]; - TArray StaticMeshUVs; - StaticMeshUVs.Reserve(StaticMeshUVCount); - - // Transfer UV data. - for (int32 UVIdx = 0; UVIdx < StaticMeshUVCount; UVIdx++) - StaticMeshUVs.Emplace(RawMeshUVs[UVIdx].X, 1.0 - RawMeshUVs[UVIdx].Y, 0); - - // Convert Unreal to Houdini - // We need to re-index UVs for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - // We do not touch wedge 0 of this triangle, Swap 2 and 3 to reverse the winding order. - StaticMeshUVs.SwapMemory(WedgeIdx + 1, WedgeIdx + 2); - } - - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (MeshTexCoordIdx > 0) - UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = StaticMeshUVCount; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, (const float *)StaticMeshUVs.GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentZ.Num() > 0) - { - TArray ChangedNormals(RawMesh.WedgeTangentZ); - - // We need to re-index normals for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentZ1 = ChangedNormals[WedgeIdx + 1]; - FVector TangentZ2 = ChangedNormals[WedgeIdx + 2]; - - ChangedNormals[WedgeIdx + 1] = TangentZ2; - ChangedNormals[WedgeIdx + 2] = TangentZ1; - } - - // We also need to swap the vector's Y and Z components - for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedNormals[WedgeIdx].Y, ChangedNormals[WedgeIdx].Z); - - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedNormals.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, (const float *)ChangedNormals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentX.Num() > 0) - { - TArray ChangedTangentU(RawMesh.WedgeTangentX); - - // We need to re-index tangents for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentU1 = ChangedTangentU[WedgeIdx + 1]; - FVector TangentU2 = ChangedTangentU[WedgeIdx + 2]; - - ChangedTangentU[WedgeIdx + 1] = TangentU2; - ChangedTangentU[WedgeIdx + 2] = TangentU1; - } - - // We also need to swap the vector's Y and Z components - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedTangentU[WedgeIdx].Y, ChangedTangentU[WedgeIdx].Z); - - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedTangentU.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - (const float *)ChangedTangentU.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentY.Num() > 0) - { - TArray ChangedTangentV(RawMesh.WedgeTangentY); - // We need to re-index normals for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentV1 = ChangedTangentV[WedgeIdx + 1]; - FVector TangentV2 = ChangedTangentV[WedgeIdx + 2]; - - ChangedTangentV[WedgeIdx + 1] = TangentV2; - ChangedTangentV[WedgeIdx + 2] = TangentV1; - } - - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedTangentV[WedgeIdx].Y, ChangedTangentV[WedgeIdx].Z); - - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedTangentV.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - (const float *)ChangedTangentV.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - { - // If we have instance override vertex colors on the StaticMeshComponent, - // we first need to propagate them to our copy of the RawMesh Vert Colors - TArray ChangedColors; - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && - StaticMesh->RenderData && - StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; - FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) - { - // Use the wedge map if it is available as it is lossless. - int32 NumWedges = RawMesh.WedgeIndices.Num(); - if (RenderModel.WedgeMap.Num() == NumWedges) - { - int32 NumExistingColors = RawMesh.WedgeColors.Num(); - if (NumExistingColors < NumWedges) - { - RawMesh.WedgeColors.AddUninitialized(NumWedges - NumExistingColors); - } - - // Replace mesh colors with override colors - for (int32 i = 0; i < NumWedges; i++) - { - FColor WedgeColor = FColor::White; - int32 Index = RenderModel.WedgeMap[i]; - if (Index != INDEX_NONE) - { - WedgeColor = ColorVertexBuffer.VertexColor(Index); - } - RawMesh.WedgeColors[i] = WedgeColor; - } - } - } - } - - // See if we have colors to upload. - if (RawMesh.WedgeColors.Num() > 0) - { - ChangedColors.SetNumUninitialized(RawMesh.WedgeColors.Num()); - - // Convert Unreal to Houdini - // We need to re-index colors for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - ChangedColors[WedgeIdx + 0] = RawMesh.WedgeColors[WedgeIdx + 0].ReinterpretAsLinear(); - ChangedColors[WedgeIdx + 1] = RawMesh.WedgeColors[WedgeIdx + 2].ReinterpretAsLinear(); - ChangedColors[WedgeIdx + 2] = RawMesh.WedgeColors[WedgeIdx + 1].ReinterpretAsLinear(); - } - } - - if (ChangedColors.Num() > 0) - { - // Extract the RGB colors - TArray ColorValues; - ColorValues.SetNum(ChangedColors.Num() * 3); - for (int32 colorIndex = 0; colorIndex < ChangedColors.Num(); colorIndex++) - { - ColorValues[colorIndex * 3] = ChangedColors[colorIndex].R; - ColorValues[colorIndex * 3 + 1] = ChangedColors[colorIndex].G; - ColorValues[colorIndex * 3 + 2] = ChangedColors[colorIndex].B; - } - - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedColors.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - ColorValues.GetData(), 0, AttributeInfoVertex.count), false); - - // Create the attribute for Alpha - TArray AlphaValues; - AlphaValues.SetNum(ChangedColors.Num()); - for (int32 alphaIndex = 0; alphaIndex < ChangedColors.Num(); alphaIndex++) - AlphaValues[alphaIndex] = ChangedColors[alphaIndex].A; - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.count = AlphaValues.Num(); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - AlphaValues.GetData(), 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES (VertexList) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeIndices.Num() > 0) - { - TArray StaticMeshIndices; - StaticMeshIndices.SetNumUninitialized(RawMesh.WedgeIndices.Num()); - - // Convert Unreal to Houdini - for (int32 IndexIdx = 0; IndexIdx < RawMesh.WedgeIndices.Num(); IndexIdx += 3) - { - // Swap indices to fix winding order. - StaticMeshIndices[IndexIdx + 0] = RawMesh.WedgeIndices[IndexIdx + 0]; - StaticMeshIndices[IndexIdx + 1] = RawMesh.WedgeIndices[IndexIdx + 2]; - StaticMeshIndices[IndexIdx + 2] = RawMesh.WedgeIndices[IndexIdx + 1]; - } - - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, StaticMeshIndices.GetData(), 0, StaticMeshIndices.Num()), false); - - // We need to generate array of face counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, StaticMeshFaceCounts.GetData(), 0, StaticMeshFaceCounts.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - // Marshall face material indices. - if (RawMesh.FaceMaterialIndices.Num() > 0) - { - // Create an array of Material Interfaces - TArray MaterialInterfaces; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()) - { - // StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); - - int32 NumMeshBasedMaterials = StaticMeshComponent->GetNumMaterials(); - TArray MeshBasedMaterialInterfaces; - MeshBasedMaterialInterfaces.SetNumUninitialized(NumMeshBasedMaterials); - for (int32 i = 0; i < NumMeshBasedMaterials; i++) - MeshBasedMaterialInterfaces[i] = StaticMeshComponent->GetMaterial(i); - - int32 NumSections = StaticMesh->GetNumSections(InLODIndex); - MaterialInterfaces.SetNumUninitialized(NumSections); - for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) - { - FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex); - check(Info.MaterialIndex < NumMeshBasedMaterials); - MaterialInterfaces[SectionIndex] = MeshBasedMaterialInterfaces[Info.MaterialIndex]; - } - } - else - { - // Query the Static mesh's materials - for (int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++) - { - MaterialInterfaces.Add(StaticMesh->GetMaterial(MatIdx)); - } - - // Try to fix up inconsistencies between the RawMesh / StaticMesh material indexes - // by using the meshes sections... - // TODO: Fix me properly! - // Proper fix would be to export the meshes via the FStaticMeshLODResources obtained - // by GetLODForExport(), and then export the mesh by sections. - if (StaticMesh->RenderData && StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - TMap MapOfMaterials; - FStaticMeshLODResources& LODResources = StaticMesh->RenderData->LODResources[InLODIndex]; - for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) - { - // Get the material for each element at the current lod index - int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; - if (!MapOfMaterials.Contains(MaterialIndex)) - { - MapOfMaterials.Add(MaterialIndex, StaticMesh->GetMaterial(MaterialIndex)); - } - } - - if (MapOfMaterials.Num() > 0) - { - // Sort the output material in the correct order (by material index) - MapOfMaterials.KeySort([](int32 A, int32 B) { return A < B; }); - - // Set the value in the correct order - // Do not reduce the array of materials, this could cause crahses in some weird cases.. - if (MapOfMaterials.Num() > MaterialInterfaces.Num()) - MaterialInterfaces.SetNumZeroed(MapOfMaterials.Num()); - - int32 MaterialIndex = 0; - for (auto Kvp : MapOfMaterials) - { - MaterialInterfaces[MaterialIndex++] = Kvp.Value; - } - } - } - } - - // Create list of materials, one for each face. - TArray StaticMeshFaceMaterials; - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - RawMesh.FaceMaterialIndices.Num(), - StaticMeshFaceMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(StaticMeshFaceMaterials); - - // Delete texture material parameter names - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING MASKS - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.FaceSmoothingMasks.Num() > 0) - { - HAPI_AttributeInfo AttributeInfoSmoothingMasks; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - AttributeInfoSmoothingMasks.count = RawMesh.FaceSmoothingMasks.Num(); - AttributeInfoSmoothingMasks.tupleSize = 1; - AttributeInfoSmoothingMasks.exists = true; - AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - (const int32 *)RawMesh.FaceSmoothingMasks.GetData(), 0, RawMesh.FaceSmoothingMasks.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - TArray AllTags; - for (auto& ComponentTag : StaticMeshComponent->ComponentTags) - AllTags.AddUnique(ComponentTag); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - for (auto& ActorTag : ParentActor->Tags) - AllTags.AddUnique(ActorTag); - } - - // Try to create groups for the tags - if (!FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, AllTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups for the Static Mesh Component and Actor tags!")); - - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - /* - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - -bool -FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( - const HAPI_NodeId& NodeId, - const FStaticMeshLODResources& LODResources, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent) -{ - // Convert the Mesh using FStaticMeshLODResources - - // Check that the mesh is not empty - if (LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0) - { - HOUDINI_LOG_ERROR(TEXT("No vertices in mesh!")); - return false; - } - - if (LODResources.Sections.Num() == 0) - { - HOUDINI_LOG_ERROR(TEXT("No triangles in mesh!")); - return false; - } - - // Vertex instance and triangle counts - const uint32 OrigNumVertexInstances = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); - const uint32 NumTriangles = LODResources.GetNumTriangles(); - const uint32 NumVertexInstances = NumTriangles * 3; - const uint32 NumSections = LODResources.Sections.Num(); - - // Grab the build scale - const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - // In FStaticMeshLODResources each vertex instances stores its position, even if the positions are not unique (in other - // words, in Houdini terminology, the number of points and vertices are the same. We'll do the same thing that Epic - // does in FBX export: we'll run through all vertex instances and use a hash to determine which instances share a - // position, so that we can a smaller number of points than vertices, and vertices share point positions - TArray UEVertexInstanceIdxToPointIdx; - UEVertexInstanceIdxToPointIdx.Reserve(OrigNumVertexInstances); - - TMap PositionToPointIndexMap; - PositionToPointIndexMap.Reserve(OrigNumVertexInstances); - - TArray StaticMeshVertices; - StaticMeshVertices.Reserve(OrigNumVertexInstances * 3); - for (uint32 VertexInstanceIndex = 0; VertexInstanceIndex < OrigNumVertexInstances; ++VertexInstanceIndex) - { - // Convert Unreal to Houdini - const FVector &PositionVector = LODResources.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexInstanceIndex); - const int32 *FoundPointIndexPtr = PositionToPointIndexMap.Find(PositionVector); - if (!FoundPointIndexPtr) - { - const int32 NewPointIndex = StaticMeshVertices.Add(PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X) / 3; - StaticMeshVertices.Add(PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z); - StaticMeshVertices.Add(PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y); - - PositionToPointIndexMap.Add(PositionVector, NewPointIndex); - UEVertexInstanceIdxToPointIdx.Add(NewPointIndex); - } - else - { - UEVertexInstanceIdxToPointIdx.Add(*FoundPointIndexPtr); - } - } - - StaticMeshVertices.Shrink(); - const uint32 NumVertices = StaticMeshVertices.Num() / 3; - - // Now that we know how many vertices (points), vertex instances (vertices) and triagnles we have, - // we can create the part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumVertexInstances; - Part.faceCount = NumTriangles; - Part.pointCount = NumVertices; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = Part.pointCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - - // Determine which attributes we have - const bool bIsVertexInstanceNormalsValid = true; - const bool bIsVertexInstanceTangentsValid = true; - const bool bIsVertexInstanceBinormalsValid = true; - const bool bIsVertexInstanceColorsValid = LODResources.bHasColorVertexData; - const uint32 NumUVLayers = FMath::Min(LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(), MAX_STATIC_TEXCOORDS); - const bool bIsVertexInstanceUVsValid = NumUVLayers > 0; - - bool bUseComponentOverrideColors = false; - // Determine if have override colors on the static mesh component, if so prefer to use those - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (ColorVertexBuffer.GetNumVertices() == LODResources.GetNumVertices()) - { - bUseComponentOverrideColors = true; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL INDEX -> MATERIAL INTERFACE - //--------------------------------------------------------------------------------------------------------------------- - TArray MaterialInterfaces; - TArray TriangleMaterialIndices; - - // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); - const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); - // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, - // then we will set UEDefaultMaterial at the invalid index - int32 UEDefaultMaterialIndex = INDEX_NONE; - UMaterialInterface *UEDefaultMaterial = nullptr; - if (NumStaticMaterials > 0) - { - MaterialInterfaces.Reserve(NumStaticMaterials); - for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) - { - const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; - UMaterialInterface *Material = nullptr; - if (bIsStaticMeshComponentValid) - { - Material = StaticMeshComponent->GetMaterial(MaterialIndex); - } - else - { - Material = MaterialInfo.MaterialInterface; - } - // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - UEDefaultMaterialIndex = MaterialIndex; - } - Material = UEDefaultMaterial; - HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); - } - // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); - MaterialInterfaces.Add(Material); - } - - TriangleMaterialIndices.Reserve(NumTriangles); - } - - // If we haven't created UEDefaultMaterial yet, check that all the sections' MaterialIndex - // is valid, if not, create UEDefaultMaterial and add to MaterialInterfaces to get UEDefaultMaterialIndex - if (!UEDefaultMaterial || UEDefaultMaterialIndex == INDEX_NONE) - { - for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) - { - // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial - // up to and including MaterialIndex and log a warning - const int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; - if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - // Add the UEDefaultMaterial to MaterialInterfaces - UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); - } - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); - } - } - } - - // Determine the final number of materials we have, with default for missing/invalid indices - const int32 NumMaterials = MaterialInterfaces.Num(); - - // Now we deal with vertex instance attributes. - if (NumTriangles > 0) - { - // UV layer array. Each layer has an array of floats, 3 floats per vertex instance - TArray> UVs; - // Normals: 3 floats per vertex instance - TArray Normals; - // Tangents: 3 floats per vertex instance - TArray Tangents; - // Binormals: 3 floats per vertex instance - TArray Binormals; - // RGBColors: 3 floats per vertex instance - TArray RGBColors; - // Alphas: 1 float per vertex instance - TArray Alphas; - - // Initialize the arrays for the attributes that are valid - if (bIsVertexInstanceUVsValid) - { - UVs.SetNum(NumUVLayers); - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); - } - } - - if (bIsVertexInstanceNormalsValid) - { - Normals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceTangentsValid) - { - Tangents.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceBinormalsValid) - { - Binormals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - RGBColors.SetNumUninitialized(NumVertexInstances * 3); - Alphas.SetNumUninitialized(NumVertexInstances); - } - - // Array of vertex (point position) indices per triangle - TArray MeshTriangleVertexIndices; - MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); - // Array of vertex counts per triangle/face - TArray MeshTriangleVertexCounts; - MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); - - int32 TriangleIdx = 0; - int32 HoudiniVertexIdx = 0; - FIndexArrayView TriangleVertexIndices = LODResources.IndexBuffer.GetArrayView(); - for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) - { - const FStaticMeshSection& Section = LODResources.Sections[SectionIndex]; - for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < Section.NumTriangles; ++SectionTriangleIndex) - { - MeshTriangleVertexCounts[TriangleIdx] = 3; - for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) - { - // Reverse the winding order for Houdini (but still start at 0) - const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; - const uint32 UEVertexIndex = TriangleVertexIndices[Section.FirstIndex + SectionTriangleIndex * 3 + WindingIdx]; - - // Calculate the index of the first component of a vertex instance's value in an inline float array - // representing vectors (3 float) per vertex instance - const int32 Float3Index = HoudiniVertexIdx * 3; - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - const FVector2D &UV = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(UEVertexIndex, UVLayerIndex); - UVs[UVLayerIndex][Float3Index + 0] = UV.X; - UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; - UVs[UVLayerIndex][Float3Index + 2] = 0; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - const FVector &Normal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(UEVertexIndex); - Normals[Float3Index + 0] = Normal.X; - Normals[Float3Index + 1] = Normal.Z; - Normals[Float3Index + 2] = Normal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - const FVector &Tangent = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(UEVertexIndex); - Tangents[Float3Index + 0] = Tangent.X; - Tangents[Float3Index + 1] = Tangent.Z; - Tangents[Float3Index + 2] = Tangent.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - // In order to calculate the binormal we also need the tangent and normal - if (bIsVertexInstanceBinormalsValid) - { - FVector Binormal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(UEVertexIndex); - Binormals[Float3Index + 0] = Binormal.X; - Binormals[Float3Index + 1] = Binormal.Z; - Binormals[Float3Index + 2] = Binormal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - FVector4 Color = FLinearColor::White; - if (bUseComponentOverrideColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - Color = ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); - } - else - { - Color = LODResources.VertexBuffers.ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); - } - RGBColors[Float3Index + 0] = Color[0]; - RGBColors[Float3Index + 1] = Color[1]; - RGBColors[Float3Index + 2] = Color[2]; - Alphas[HoudiniVertexIdx] = Color[3]; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - if (UEVertexInstanceIdxToPointIdx.IsValidIndex(UEVertexIndex)) - { - MeshTriangleVertexIndices[HoudiniVertexIdx] = UEVertexInstanceIdxToPointIdx[UEVertexIndex]; - } - - HoudiniVertexIdx++; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - if (MaterialInterfaces.IsValidIndex(Section.MaterialIndex)) - { - TriangleMaterialIndices.Add(Section.MaterialIndex); - } - else - { - TriangleMaterialIndices.Add(UEDefaultMaterialIndex); - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, Section.MaterialIndex, *(UEDefaultMaterial->GetPathName())); - } - - TriangleIdx++; - } - } - - // Now transfer valid vertex instance attributes to Houdini vertex attributes - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) - { - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (UVLayerIndex > 0) - UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = NumVertexInstances; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, Normals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - Tangents.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceBinormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - Binormals.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - RGBColors.GetData(), 0, AttributeInfoVertex.count), false); - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.count = Alphas.Num(); - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - Alphas.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); - - // Send the array of face vertex counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); - - // Send material assignments to Houdini - if (NumMaterials > 0) - { - // Create list of materials, one for each face. - TArray TriangleMaterials; - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); - - // Delete texture parameter attribute names. - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - - } - - // TODO: The render mesh (LODResources) does not have face smoothing information, and the raw mesh triangle order is - // potentially different (see also line 4152 TODO_FBX in Engine\Source\Editor\UnrealEd\Private\Fbx\FbxMainExport.cpp - ////--------------------------------------------------------------------------------------------------------------------- - //// TRIANGLE SMOOTHING MASKS - ////--------------------------------------------------------------------------------------------------------------------- - //TArray TriangleSmoothingMasks; - //TriangleSmoothingMasks.SetNumZeroed(NumTriangles); - //FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); - //if (TriangleSmoothingMasks.Num() > 0) - //{ - // HAPI_AttributeInfo AttributeInfoSmoothingMasks; - // FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - // AttributeInfoSmoothingMasks.tupleSize = 1; - // AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); - // AttributeInfoSmoothingMasks.exists = true; - // AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - // AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - // AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - // FHoudiniEngine::Get().GetSession(), - // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - // FHoudiniEngine::Get().GetSession(), - // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - // (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); - //} - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - // Try to create groups for the static mesh component's tags - if (StaticMeshComponent->ComponentTags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Try to create groups for the parent Actor's tags - if (ParentActor->Tags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - - /* - // Add the unreal_level_path attribute - FString LevelPath = ActorPath;// FString(); - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - -bool -FUnrealMeshTranslator::CreateInputNodeForMeshDescription( - const HAPI_NodeId& NodeId, - const FMeshDescription& MeshDescription, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent) -{ - // Convert the Mesh using FMeshDescription - // Get references to the attributes we are interested in - // before sending to Houdini we'll check if each attribute is valid - FStaticMeshConstAttributes MeshDescriptionAttributes(MeshDescription); - - TVertexAttributesConstRef VertexPositions = MeshDescriptionAttributes.GetVertexPositions(); - TVertexInstanceAttributesConstRef VertexInstanceNormals = MeshDescriptionAttributes.GetVertexInstanceNormals(); - TVertexInstanceAttributesConstRef VertexInstanceTangents = MeshDescriptionAttributes.GetVertexInstanceTangents(); - TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = MeshDescriptionAttributes.GetVertexInstanceBinormalSigns(); - TVertexInstanceAttributesConstRef VertexInstanceColors = MeshDescriptionAttributes.GetVertexInstanceColors(); - TVertexInstanceAttributesConstRef VertexInstanceUVs = MeshDescriptionAttributes.GetVertexInstanceUVs(); - //TPolygonGroupAttributesConstRef PolygonGroupMaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames(); - - // Get the vertex and triangle arrays - const FVertexArray &MDVertices = MeshDescription.Vertices(); - const FPolygonGroupArray &MDPolygonGroups = MeshDescription.PolygonGroups(); - const FPolygonArray &MDPolygons = MeshDescription.Polygons(); - const FTriangleArray &MDTriangles = MeshDescription.Triangles(); - - // Determine point, vertex and polygon counts - const uint32 NumVertices = MDVertices.Num(); - const uint32 NumTriangles = MDTriangles.Num(); - const uint32 NumVertexInstances = NumTriangles * 3; - - // Some checks: we expect triangulated meshes - if (MeshDescription.VertexInstances().Num() != NumTriangles * 3) - { - HOUDINI_LOG_ERROR(TEXT("Expected a triangulated mesh, but # VertexInstances (%d) != # Triangles * 3 (%d)"), MeshDescription.VertexInstances().Num(), NumTriangles * 3); - return false; - } - - // Determine which attributes we have - const bool bIsVertexPositionsValid = VertexPositions.IsValid(); - const bool bIsVertexInstanceNormalsValid = VertexInstanceNormals.IsValid(); - const bool bIsVertexInstanceTangentsValid = VertexInstanceTangents.IsValid(); - const bool bIsVertexInstanceBinormalSignsValid = VertexInstanceBinormalSigns.IsValid(); - const bool bIsVertexInstanceColorsValid = VertexInstanceColors.IsValid(); - const bool bIsVertexInstanceUVsValid = VertexInstanceUVs.IsValid(); - //const bool bIsPolygonGroupImportedMaterialSlotNamesValid = PolygonGroupMaterialSlotNames.IsValid(); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumVertexInstances; - Part.faceCount = NumTriangles; - Part.pointCount = NumVertices; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = Part.pointCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Grab the build scale - const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - // The mesh element arrays are sparse: the max index/ID value can be larger than the number of elements - 1 - // so we have to maintain a lookup of VertexID (UE) to PointIndex (Houdini) - TArray VertexIDToHIndex; - if (bIsVertexPositionsValid && VertexPositions.GetNumElements() >= 3) - { - TArray StaticMeshVertices; - StaticMeshVertices.SetNumUninitialized(NumVertices * 3); - - int32 VertexIdx = 0; - VertexIDToHIndex.Init(INDEX_NONE, MDVertices.GetArraySize()); - - for (const FVertexID& VertexID : MDVertices.GetElementIDs()) - { - // Convert Unreal to Houdini - const FVector &PositionVector = VertexPositions.Get(VertexID); - StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; - StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; - StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; - - // Record the UE Vertex ID to Houdini Point Index lookup - VertexIDToHIndex[VertexID.GetValue()] = VertexIdx; - VertexIdx++; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - } - - bool bUseComponentOverrideColors = false; - // Determine if have override colors on the static mesh component, if so prefer to use those - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && - StaticMesh->RenderData && - StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; - FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) - { - // Use the wedge map if it is available as it is lossless. - if (RenderModel.WedgeMap.Num() == NumVertexInstances) - { - bUseComponentOverrideColors = true; - } - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL SLOT -> MATERIAL INTERFACE - //--------------------------------------------------------------------------------------------------------------------- - // In theory the ImportedMaterialSlotName attribute on PolygonGroups should tell us which material slot is used by - // that group, and thus which MaterialIndex we should assign to triangles in that group. Unfortunately we have - // encountered cases where the ImportedMaterialSlotName name attribute does not match any of the MaterialSlotName or - // ImportedMaterialSlotNames in the StaticMesh->StaticMaterials array. Therefore we have no choice but to rely - // on the PolygonGroup order vs Section order to determine the MaterialIndex for a group. We do what Epic does - // when building a static mesh: Sections are created in the same order as iterating over PolygonGroups, but importantly, - // empty PolygonGroups are skipped - - // // Get material slot name to material index - // and the UMaterialInterface array - // TMap MaterialSlotToInterface; - TArray MaterialInterfaces; - TArray TriangleMaterialIndices; - - // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); - const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); - // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, - // then we will set UEDefaultMaterial at the invalid index - int32 UEDefaultMaterialIndex = INDEX_NONE; - UMaterialInterface *UEDefaultMaterial = nullptr; - if (NumStaticMaterials > 0) - { - MaterialInterfaces.Reserve(NumStaticMaterials); - for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) - { - const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; - UMaterialInterface *Material = nullptr; - if (bIsStaticMeshComponentValid) - { - Material = StaticMeshComponent->GetMaterial(MaterialIndex); - } - else - { - Material = MaterialInfo.MaterialInterface; - } - // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - UEDefaultMaterialIndex = MaterialIndex; - } - Material = UEDefaultMaterial; - HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); - } - // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); - MaterialInterfaces.Add(Material); - } - - TriangleMaterialIndices.Reserve(NumTriangles); - } - // SectionIndex: Looking at Epic's StaticMesh build code, Sections are created in the same - // order as iterating over PolygonGroups, but skipping empty PolygonGroups - TMap PolygonGroupToMaterialIndex; - PolygonGroupToMaterialIndex.Reserve(MeshDescription.PolygonGroups().Num()); - int32 SectionIndex = 0; - for (const FPolygonGroupID &PolygonGroupID : MDPolygonGroups.GetElementIDs()) - { - // Skip empty polygon groups - if (MeshDescription.GetNumPolygonGroupPolygons(PolygonGroupID) == 0) - { - continue; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - // // Get the material index for the material slot for this polygon group - //int32 MaterialIndex = INDEX_NONE; - //if (bIsPolygonGroupImportedMaterialSlotNamesValid) - //{ - // const FName &MaterialSlotName = PolygonGroupMaterialSlotNames.Get(PolygonGroupID); - // const int32 *MaterialIndexPtr = MaterialSlotToInterface.Find(MaterialSlotName); - // if (MaterialIndexPtr != nullptr) - // { - // MaterialIndex = *MaterialIndexPtr; - // } - //} - - // Get the material for the LOD and section via the section info map - if (StaticMesh->GetNumSections(InLODIndex) <= SectionIndex) - { - HOUDINI_LOG_ERROR(TEXT("Found more non-empty polygon groups in the mesh description for LOD %d than sections in the static mesh..."), InLODIndex); - return false; - } - - // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial - // up to and including MaterialIndex and log a warning - int32 MaterialIndex = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex).MaterialIndex; - if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - // Add the UEDefaultMaterial to MaterialInterfaces - UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); - } - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); - MaterialIndex = UEDefaultMaterialIndex; - } - - PolygonGroupToMaterialIndex.Add(PolygonGroupID, MaterialIndex); - SectionIndex++; - } - - // Determine the final number of materials we have, with defaults for missing/invalid indices - const int32 NumMaterials = MaterialInterfaces.Num(); - - // Now we deal with vertex instance attributes. - // // First we must also record a UE VertexInstanceID to Houdini Vertex Index lookup, - // // and then get and convert all valid and supported vertex instance attributes from UE - // TArray VertexInstanceIDToHIndex; - - if (NumTriangles > 0) - { - // UV layer array. Each layer has an array of floats, 3 floats per vertex instance - TArray> UVs; - const int32 NumUVLayers = bIsVertexInstanceUVsValid ? FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_STATIC_TEXCOORDS) : 0; - // Normals: 3 floats per vertex instance - TArray Normals; - // Tangents: 3 floats per vertex instance - TArray Tangents; - // Binormals: 3 floats per vertex instance - TArray Binormals; - // RGBColors: 3 floats per vertex instance - TArray RGBColors; - // Alphas: 1 float per vertex instance - TArray Alphas; - - // Initialize the arrays for the attributes that are valid - if (bIsVertexInstanceUVsValid) - { - UVs.SetNum(NumUVLayers); - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); - } - } - - if (bIsVertexInstanceNormalsValid) - { - Normals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceTangentsValid) - { - Tangents.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceBinormalSignsValid) - { - Binormals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - RGBColors.SetNumUninitialized(NumVertexInstances * 3); - Alphas.SetNumUninitialized(NumVertexInstances); - } - - // Array of material index per triangle/face - TArray MeshTriangleVertexIndices; - MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); - // Array of vertex counts per triangle/face - TArray MeshTriangleVertexCounts; - MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); - - int32 TriangleIdx = 0; - int32 VertexInstanceIdx = 0; - // VertexInstanceIDToHIndex.Init(-1, MDVertexInstances.GetArraySize()); - - for (const FPolygonID &PolygonID : MDPolygons.GetElementIDs()) - { - for (const FTriangleID &TriangleID : MeshDescription.GetPolygonTriangleIDs(PolygonID)) - { - MeshTriangleVertexCounts[TriangleIdx] = 3; - for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) - { - // Reverse the winding order for Houdini (but still start at 0) - const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; - const FVertexInstanceID &VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, WindingIdx); - - // // UE Vertex Instance ID to Houdini Vertex Index look up - // VertexInstanceIDToHIndex[VertexInstanceID.GetValue()] = VertexInstanceIdx; - - // Calculate the index of the first component of a vertex instance's value in an inline float array - // representing vectors (3 float) per vertex instance - const int32 Float3Index = VertexInstanceIdx * 3; - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - const FVector2D &UV = VertexInstanceUVs.Get(VertexInstanceID, UVLayerIndex); - UVs[UVLayerIndex][Float3Index + 0] = UV.X; - UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; - UVs[UVLayerIndex][Float3Index + 2] = 0; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - const FVector &Normal = VertexInstanceNormals.Get(VertexInstanceID); - Normals[Float3Index + 0] = Normal.X; - Normals[Float3Index + 1] = Normal.Z; - Normals[Float3Index + 2] = Normal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - const FVector &Tangent = VertexInstanceTangents.Get(VertexInstanceID); - Tangents[Float3Index + 0] = Tangent.X; - Tangents[Float3Index + 1] = Tangent.Z; - Tangents[Float3Index + 2] = Tangent.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - // In order to calculate the binormal we also need the tangent and normal - if (bIsVertexInstanceBinormalSignsValid && bIsVertexInstanceTangentsValid && bIsVertexInstanceNormalsValid) - { - const float &BinormalSign = VertexInstanceBinormalSigns.Get(VertexInstanceID); - FVector Binormal = FVector::CrossProduct( - FVector(Tangents[Float3Index + 0], Tangents[Float3Index + 1], Tangents[Float3Index + 2]), - FVector(Normals[Float3Index + 0], Normals[Float3Index + 1], Normals[Float3Index + 2]) - ) * BinormalSign; - Binormals[Float3Index + 0] = Binormal.X; - Binormals[Float3Index + 1] = Binormal.Y; - Binormals[Float3Index + 2] = Binormal.Z; - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - FVector4 Color = FLinearColor::White; - if (bUseComponentOverrideColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshLODResources& RenderModel = StaticMesh->RenderData->LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - int32 Index = RenderModel.WedgeMap[VertexInstanceIdx]; - if (Index != INDEX_NONE) - { - Color = ColorVertexBuffer.VertexColor(Index).ReinterpretAsLinear(); - } - } - else - { - Color = VertexInstanceColors.Get(VertexInstanceID); - } - RGBColors[Float3Index + 0] = Color[0]; - RGBColors[Float3Index + 1] = Color[1]; - RGBColors[Float3Index + 2] = Color[2]; - Alphas[VertexInstanceIdx] = Color[3]; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - const FVertexID& VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID); - const int32 UEVertexIdx = VertexID.GetValue(); - if (VertexIDToHIndex.IsValidIndex(UEVertexIdx)) - { - MeshTriangleVertexIndices[VertexInstanceIdx] = VertexIDToHIndex[UEVertexIdx]; - } - - VertexInstanceIdx++; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - const FPolygonGroupID &PolygonGroupID = MeshDescription.GetPolygonPolygonGroup(PolygonID); - const int32 MaterialIndex = PolygonGroupToMaterialIndex.FindChecked(PolygonGroupID); - TriangleMaterialIndices.Add(MaterialIndex); - - TriangleIdx++; - } - } - - // Now transfer valid vertex instance attributes to Houdini vertex attributes - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) - { - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (UVLayerIndex > 0) - UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = NumVertexInstances; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, Normals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - Tangents.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceBinormalSignsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - Binormals.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - RGBColors.GetData(), 0, AttributeInfoVertex.count), false); - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.count = Alphas.Num(); - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - Alphas.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); - - // Send the array of face vertex counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); - - // Send material assignments to Houdini - if (NumMaterials > 0) - { - // Create list of materials, one for each face. - TArray TriangleMaterials; - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterialIndices.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); - - // Delete texture material parameter names. - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (bAttributeSuccess) - { - check(0); - return false; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE SMOOTHING MASKS - //--------------------------------------------------------------------------------------------------------------------- - TArray TriangleSmoothingMasks; - TriangleSmoothingMasks.SetNumZeroed(NumTriangles); - FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); - if (TriangleSmoothingMasks.Num() > 0) - { - HAPI_AttributeInfo AttributeInfoSmoothingMasks; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - AttributeInfoSmoothingMasks.tupleSize = 1; - AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); - AttributeInfoSmoothingMasks.exists = true; - AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - // Try to create groups for the static mesh component's tags - if (StaticMeshComponent->ComponentTags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Try to create groups for the parent Actor's tags - if (ParentActor->Tags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - - /* - FString LevelPath = FString(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - - -void -FUnrealMeshTranslator::CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray& OutStaticMeshFaceMaterials, - TMap> & OutScalarMaterialParameters, - TMap> & OutVectorMaterialParameters, - TMap> & OutTextureMaterialParameters) -{ - // We need to create list of unique materials. - TArray< char * > UniqueMaterialList; - - UMaterialInterface * MaterialInterface = nullptr; - char* UniqueName = nullptr; - - UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); - - // Initialize material parameter arrays - TMap> ScalarParams; - TMap> VectorParams; - TMap> TextureParams; - - if (Materials.Num()) - { - // We have materials. - for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) - { - UniqueName = nullptr; - MaterialInterface = Materials[MaterialIdx]; - if (!MaterialInterface) - { - // Null material interface found, add default instead. - UniqueMaterialList.Add(DefaultMaterialName); - - // No need to collect material parameters on the default material - continue; - } - - // We found a material, get its name and material parameters - FString FullMaterialName = MaterialInterface->GetPathName(); - UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); - UniqueMaterialList.Add(UniqueName); - - // Collect all scalar parameters in all materials - { - TArray MaterialScalarParamInfos; - TArray MaterialScalarParamGuids; - MaterialInterface->GetAllScalarParameterInfo(MaterialScalarParamInfos, MaterialScalarParamGuids); - - for (auto & CurScalarParam : MaterialScalarParamInfos) - { - FString CurScalarParamName = CurScalarParam.Name.ToString(); - float CurScalarVal; - MaterialInterface->GetScalarParameterValue(CurScalarParam, CurScalarVal); - if (!ScalarParams.Contains(CurScalarParamName)) - { - TArray CurArray; - CurArray.SetNumUninitialized(Materials.Num()); - // Initialize the array with the Min float value - for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) - CurArray[ArrIdx] = FLT_MIN; - - ScalarParams.Add(CurScalarParamName, CurArray); - OutScalarMaterialParameters.Add(CurScalarParamName); - } - - ScalarParams[CurScalarParamName][MaterialIdx] = CurScalarVal; - } - } - - // Collect all vector parameters in all materials - { - TArray MaterialVectorParamInfos; - TArray MaterialVectorParamGuids; - MaterialInterface->GetAllVectorParameterInfo(MaterialVectorParamInfos, MaterialVectorParamGuids); - - for (auto & CurVectorParam : MaterialVectorParamInfos) - { - FString CurVectorParamName = CurVectorParam.Name.ToString(); - FLinearColor CurVectorValue; - MaterialInterface->GetVectorParameterValue(CurVectorParam, CurVectorValue); - if (!VectorParams.Contains(CurVectorParamName)) - { - TArray CurArray; - CurArray.SetNumUninitialized(Materials.Num()); - FLinearColor MinColor(FLT_MIN, FLT_MIN, FLT_MIN, FLT_MIN); - for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) - CurArray[ArrIdx] = MinColor; - - VectorParams.Add(CurVectorParamName, CurArray); - OutVectorMaterialParameters.Add(CurVectorParamName); - } - - VectorParams[CurVectorParamName][MaterialIdx] = CurVectorValue; - } - } - - // Collect all texture parameters in all materials - { - TArray MaterialTextureParamInfos; - TArray MaterialTextureParamGuids; - MaterialInterface->GetAllTextureParameterInfo(MaterialTextureParamInfos, MaterialTextureParamGuids); - - for (auto & CurTextureParam : MaterialTextureParamInfos) - { - FString CurTextureParamName = CurTextureParam.Name.ToString(); - UTexture * CurTexture = nullptr; - MaterialInterface->GetTextureParameterValue(CurTextureParam, CurTexture); - - if (!CurTexture || CurTexture->IsPendingKill()) - continue; - - FString TexturePath = CurTexture->GetPathName(); - if (!TextureParams.Contains(CurTextureParamName)) - { - TArray CurArray; - CurArray.SetNumZeroed(Materials.Num()); - - TextureParams.Add(CurTextureParamName, CurArray); - OutTextureMaterialParameters.Add(CurTextureParamName); - } - - char * TexturePathRawStr = UniqueName = FHoudiniEngineUtils::ExtractRawString(TexturePath); - TextureParams[CurTextureParamName][MaterialIdx] = TexturePathRawStr; - } - } - - } - } - else - { - // We do not have any materials, add default. - UniqueMaterialList.Add(DefaultMaterialName); - } - - // TODO: Needs to be improved! - // We shouldnt be testing for each face, but only for each unique facematerial value... - for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) - { - int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; - if(UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) - { - OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); - - for (auto & Pair : ScalarParams) - { - OutScalarMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); - } - - for (auto & Pair : VectorParams) - { - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].R); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].G); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].B); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].A); - } - - for (auto & Pair : TextureParams) - { - OutTextureMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); - } - } - else - { - OutStaticMeshFaceMaterials.Add(DefaultMaterialName); - } - } -} - - -void -FUnrealMeshTranslator::DeleteFaceMaterialArray(TArray& OutStaticMeshFaceMaterials) -{ - // Clean up the memory allocated by CreateFaceMaterialArray() - TSet UniqueMaterials(OutStaticMeshFaceMaterials); - for (TSet::TIterator Iter = UniqueMaterials.CreateIterator(); Iter; ++Iter) - { - char* MaterialName = *Iter; - FMemory::Free(MaterialName); - } - - OutStaticMeshFaceMaterials.Empty(); -} - -bool -FUnrealMeshTranslator::CreateInputNodeForBox( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& BoxCenter, - const FVector& BoxExtent, - const FRotator& BoxRotation) -{ - // Create a new input node for the box collider - FString BoxName = TEXT("box") + FString::FromInt(ColliderIndex); - - // Create the node in this input object's OBJ node - HAPI_NodeId BoxNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("box"), BoxName, false, &BoxNodeId), false); - - // Set the box parameters - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 0, BoxExtent.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 1, BoxExtent.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 2, BoxExtent.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 0, BoxCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 1, BoxCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 2, BoxCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 0, BoxRotation.Roll); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 2, BoxRotation.Pitch); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 1, BoxRotation.Yaw); - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), BoxNodeId, &CookOptions); - */ - if (!FHoudiniEngineUtils::HapiCookNode(BoxNodeId, nullptr, true)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_box") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, BoxNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForSphere( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphereCenter, - const float& SphereRadius) -{ - // Create a new input node for the sphere collider - const char * SphereName = ""; - { - FString SPH = TEXT("Sphere") + FString::FromInt(ColliderIndex); - SphereName = TCHAR_TO_UTF8(*SPH); - } - - // Create the node in this input object's OBJ node - HAPI_NodeId SphereNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "sphere", SphereName, false, &SphereNodeId), false); - - // Set the box parameters - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 1, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 2, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 0, SphereCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 1, SphereCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 2, SphereCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "type", 0, 1); - /* - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "scale", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - */ - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), SphereNodeId, &CookOptions); - */ - if (!FHoudiniEngineUtils::HapiCookNode(SphereNodeId, nullptr, true)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_sphere") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphereNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForSphyl( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphylCenter, - const FRotator& SphylRotation, - const float& SphylRadius, - const float& SphereLength) -{ - // - // Get the Sphyl's vertices and indices - // (code drived from FKSphylElem::GetElemSolid) - // - - // TODO: - // Rotation? - - const int32 NumSides = 6; - const int32 NumRings = (NumSides / 2) + 1; - - // The first/last arc are on top of each other. - const int32 NumVerts = (NumSides + 1) * (NumRings + 1); - - // Calculate the vertices for one arc - TArray ArcVertices; - ArcVertices.SetNum(NumRings + 1); - for (int32 RingIdx = 0; RingIdx < NumRings + 1; RingIdx++) - { - float Angle; - float ZOffset; - if (RingIdx <= NumSides / 4) - { - Angle = ((float)RingIdx / (NumRings - 1)) * PI; - ZOffset = 0.5 * SphereLength; - } - else - { - Angle = ((float)(RingIdx - 1) / (NumRings - 1)) * PI; - ZOffset = -0.5 * SphereLength; - } - - // Note- unit sphere, so position always has mag of one. We can just use it for normal! - FVector SpherePos; - SpherePos.X = 0.0f; - SpherePos.Y = SphylRadius * FMath::Sin(Angle); - SpherePos.Z = SphylRadius * FMath::Cos(Angle); - - ArcVertices[RingIdx] = SpherePos + FVector(0, 0, ZOffset); - } - - // Get the transform matrix for the rotation - FRotationMatrix SphylRotMatrix(SphylRotation); - - // Get the Sphyl's vertices by rotating the arc NumSides+1 times. - TArray Vertices; - Vertices.SetNum(NumVerts * 3); - for (int32 SideIdx = 0; SideIdx < NumSides + 1; SideIdx++) - { - const FRotator ArcRotator(0, 360.f * ((float)SideIdx / NumSides), 0); - const FRotationMatrix ArcRot(ArcRotator); - const float XTexCoord = ((float)SideIdx / NumSides); - - for (int32 VertIdx = 0; VertIdx < NumRings + 1; VertIdx++) - { - int32 VIx = (NumRings + 1)*SideIdx + VertIdx; - - FVector CurPosition = SphylCenter + ArcRot.TransformPosition(ArcVertices[VertIdx]); - CurPosition = SphylRotMatrix.TransformPosition(CurPosition); - - // Convert the UE4 position to Houdini - Vertices[VIx * 3 + 0] = CurPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[VIx * 3 + 1] = CurPosition.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[VIx * 3 + 2] = CurPosition.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - } - - // Add all of the indices to the mesh. - int32 NumIndices = NumSides * NumRings * 6; - TArray Indices; - Indices.SetNum(NumIndices); - - int32 CurIndex = 0; - for (int32 SideIdx = 0; SideIdx < NumSides; SideIdx++) - { - const int32 a0start = (SideIdx + 0) * (NumRings + 1); - const int32 a1start = (SideIdx + 1) * (NumRings + 1); - for (int32 RingIdx = 0; RingIdx < NumRings; RingIdx++) - { - // First Tri (reverse winding) - Indices[CurIndex+0] = a0start + RingIdx + 0; - Indices[CurIndex+2] = a1start + RingIdx + 0; - Indices[CurIndex+1] = a0start + RingIdx + 1; - CurIndex += 3; - // Second Tri (reverse winding) - Indices[CurIndex+0] = a1start + RingIdx + 0; - Indices[CurIndex+2] = a1start + RingIdx + 1; - Indices[CurIndex+1] = a0start + RingIdx + 1; - CurIndex += 3; - } - } - - // - // Create the Sphyl Mesh in houdini - // - HAPI_NodeId SphylNodeId = -1; - FString SphylName = TEXT("Sphyl") + FString::FromInt(ColliderIndex); - if(!CreateInputNodeForCollider(SphylNodeId, InParentNodeID, ColliderIndex, SphylName, Vertices, Indices)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_capsule") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphylNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForConvex( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FKConvexElem& ConvexCollider) -{ - TArray Vertices; - TArray Indices; - -/* -#if PHYSICS_INTERFACE_PHYSX - if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) -#elif WITH_CHAOS - //if (ConvexCollider.GetChaosConvexMesh().IsValid()) - if (ConvexCollider.IndexData.Num() > 0 && ConvexCollider.IndexData.Num() % 3 == 0) -#else - if(false) -#endif -*/ - if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) - { - // Get the convex colliders vertices and indices from the mesh - TArray VertexBuffer; - TArray IndexBuffer; - ConvexCollider.AddCachedSolidConvexGeom(VertexBuffer, IndexBuffer, FColor::White); - - Vertices.SetNum(VertexBuffer.Num() * 3); - int32 CurIndex = 0; - for (auto& CurVert : VertexBuffer) - { - Vertices[CurIndex * 3 + 0] = CurVert.Position.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 1] = CurVert.Position.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 2] = CurVert.Position.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - CurIndex++; - } - - Indices.SetNum(IndexBuffer.Num()); - for (int Idx = 0; (Idx + 2) < IndexBuffer.Num(); Idx += 3) - { - // Reverse winding - Indices[Idx + 0] = Indices[Idx + 0]; - Indices[Idx + 2] = Indices[Idx + 1]; - Indices[Idx + 1] = Indices[Idx + 2]; - } - } - else - { - int32 NumVert = ConvexCollider.VertexData.Num(); - Vertices.SetNum(NumVert * 3); - //Indices.SetNum(NumVert); - int32 CurIndex = 0; - for (auto& CurVert : ConvexCollider.VertexData) - { - Vertices[CurIndex * 3 + 0] = CurVert.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 1] = CurVert.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 2] = CurVert.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - - /* - // TODO: Get proper polygons... - Indices[CurIndex] = CurIndex; - */ - CurIndex++; - } - - // TODO: Get Proper polygons - for (int32 Idx = 0; Idx + 2 < NumVert; Idx++) - { - Indices.Add(Idx + 0); - Indices.Add(Idx + 1); - Indices.Add(Idx + 2); - } - - /* - for (int32 Idx = 0; Idx + 3 < NumVert; Idx+= 4) - { - Indices.Add(Idx + 0); - Indices.Add(Idx + 1); - Indices.Add(Idx + 2); - - Indices.Add(Idx + 2); - Indices.Add(Idx + 1); - Indices.Add(Idx + 3); - } - */ - } - - // - // Create the Convex Mesh in houdini - // - HAPI_NodeId ConvexNodeId = -1; - FString ConvexName = TEXT("Convex") + FString::FromInt(ColliderIndex); - if (!CreateInputNodeForCollider(ConvexNodeId, InParentNodeID, ColliderIndex, ConvexName, Vertices, Indices)) - return false; - - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), ColliderNodeId, &CookOptions); - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "groupcreate", GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_ucx - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_ucx") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Create a convex hull (shrinkwrap::2.0) node to fix the lack of proper indices - HAPI_NodeId ConvexHullNodeId = -1; - FString ConvexHullName = TEXT("ConvexHull") + FString::FromInt(ColliderIndex); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "shrinkwrap::2.0", ConvexHullName, false, &ConvexHullNodeId), false); - - if (ConvexHullNodeId > 0) - { - // Connect the collider to the convex hull - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), ConvexHullNodeId, 0, ConvexNodeId, 0), false); - - // Connect the convex hull to the group - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexHullNodeId, 0), false); - } - else - { - // Connect the collider to the group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexNodeId, 0), false); - - } - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForCollider( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FString& ColliderName, - const TArray& ColliderVertices, - const TArray& ColliderIndices) -{ - // Create a new input node for the collider - const char * ColliderNameStr = TCHAR_TO_UTF8(*ColliderName); - - // Create the node in this input object's OBJ node - HAPI_NodeId ColliderNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "null", ColliderNameStr, false, &ColliderNodeId), false); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = ColliderIndices.Num(); - Part.faceCount = ColliderIndices.Num() / 3; - Part.pointCount = ColliderVertices.Num() / 3; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), ColliderNodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = ColliderVertices.Num() / 3; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Upload the positions - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - ColliderVertices.GetData(), 0, AttributeInfoPoint.count), false); - - // Upload the indices - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, ColliderIndices.GetData(), 0, ColliderIndices.Num()), false); - - // Generate the array of face counts. - TArray ColldierFaceCounts; - ColldierFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, ColldierFaceCounts.GetData(), 0, ColldierFaceCounts.Num()), false); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), ColliderNodeId), false); - - OutNodeId = ColliderNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - const int32 & NodeId, - const int32 & PartId, - const int32 & Count, - const TArray & TriangleMaterials, - const TMap> & ScalarMaterialParameters, - const TMap> & VectorMaterialParameters, - const TMap> & TextureMaterialParameters) -{ - if (NodeId < 0) - return false; - - bool bSuccess = true; - - // Create attribute for materials. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.count = Count; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - // Create the new attribute - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial, - (const char **)TriangleMaterials.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - - // Add scalar material parameter attributes - for (auto & Pair : ScalarMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 1; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - // Create the new attribute - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - Pair.Value.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - // Add vector material parameters - for (auto & Pair : VectorMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 4; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - Pair.Value.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - // Add texture material parameter attributes - for (auto & Pair : TextureMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 1; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // Replace null strings by empty strings to prevent crashes when setting the attribute. - char* EmptyString = nullptr; - TArray StringData = Pair.Value; - for (auto& CurValue : StringData) - { - if (CurValue != nullptr) - continue; - - if (!EmptyString) - { - // Allocate the empty string the first time it is needed. This is free'd along with - // the other strings in the arrays in TextureMaterialParameters by calls to DeleteFaceMaterialArray - EmptyString = FHoudiniEngineUtils::ExtractRawString(FString(TEXT(""))); - } - CurValue = EmptyString; - } - - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - (const char **)StringData.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - return bSuccess; -} - -/* -bool -FUnrealMeshTranslator::AddLevelPathAttributeToMesh( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const FString& LevelPath, - const int32& Count) -{ - if (NodeId == -1 || LevelPath.IsEmpty() || Count <= 0) - return false; - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = Count; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Count); - for (int32 Idx = 0; Idx < Count; ++Idx) - { - PrimitiveAttrs[Idx] = LevelPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealMeshTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "RawMesh.h" +#include "MeshDescription.h" +#include "MeshDescriptionOperations.h" +#include "Engine/StaticMesh.h" +#include "PhysicsEngine/BodySetup.h" +#include "Engine/StaticMeshSocket.h" +#include "Components/StaticMeshComponent.h" +#include "Materials/Material.h" +#include "Materials/MaterialInterface.h" +#include "MeshAttributes.h" +#include "StaticMeshAttributes.h" + +#if WITH_EDITOR + #include "EditorFramework/AssetImportData.h" +#endif + +bool +FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + UStaticMesh* StaticMesh, + HAPI_NodeId& InputNodeId, + const FString& InputNodeName, + UStaticMeshComponent* StaticMeshComponent /* = nullptr */, + const bool& ExportAllLODs /* = false */, + const bool& ExportSockets /* = false */, + const bool& ExportColliders /* = false */) +{ + // If we don't have a static mesh there's nothing to do. + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Node ID for the newly created node + HAPI_NodeId NewNodeId = -1; + + // Export sockets if there are some + bool DoExportSockets = ExportSockets && (StaticMesh->Sockets.Num() > 0); + + // Export LODs if there are some + bool DoExportLODs = ExportAllLODs && (StaticMesh->GetNumLODs() > 1); + + // Export colliders if there are some + bool DoExportColliders = ExportColliders && StaticMesh->BodySetup != nullptr; + if (DoExportColliders) + { + if (!StaticMesh->BodySetup) + { + DoExportColliders = false; + } + else + { + if (StaticMesh->BodySetup->AggGeom.GetElementCount() <= 0) + DoExportColliders = false; + } + } + + // We need to use a merge node if we export lods OR sockets + bool UseMergeNode = DoExportLODs || DoExportSockets || DoExportColliders; + if (UseMergeNode) + { + // TODO: + // What if OutInputNodeId already exists? + // Delete previous merge?/input? + + // Create a merge SOP asset. This will be our "InputNodeId" + // as all the different LOD meshes and sockets will be plugged into it + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/merge"), InputNodeName, true, &NewNodeId), false); + } + else + { + // No LODs/Sockets, we just need a single input node + // If InputNodeId is invalid, we need to create an input node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_ANSI(*InputNodeName)), false); + + if (!FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true)) + return false; + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); + */ + } + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) + return false; + + HAPI_NodeId PreviousInputNodeId = InputNodeId; + + // Update our input NodeId + InputNodeId = NewNodeId; + // Get our parent OBJ NodeID + HAPI_NodeId InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); + + // We have now created a valid new input node, delete the previous one + if (PreviousInputNodeId >= 0) + { + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); + } + } + + // TODO: + // Setting for lightmap resolution? + //const uint8 ExportMethod = 0; // Raw mesh + //const uint8 ExportMethod = 1; // Mesh description + const uint8 ExportMethod = 2; // LODResources (render mesh) + //bool bExportViaRawMesh = false; + + int32 NumLODsToExport = DoExportLODs ? StaticMesh->GetNumLODs() : 1; + for (int32 LODIndex = 0; LODIndex < NumLODsToExport; LODIndex++) + { + // Grab the LOD level. + FStaticMeshSourceModel & SrcModel = StaticMesh->GetSourceModel(LODIndex); + + // If we're using a merge node, we need to create a new input null + HAPI_NodeId CurrentLODNodeId = -1; + if (UseMergeNode) + { + // Create a new input node for the current LOD + const char * LODName = ""; + { + FString LOD = TEXT("lod") + FString::FromInt(LODIndex); + LODName = TCHAR_TO_UTF8(*LOD); + } + + // Create the node in this input object's OBJ node + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InputObjectNodeId, TEXT("null"), LODName, false, &CurrentLODNodeId), false); + } + else + { + // No merge node, just use the input node we created before + CurrentLODNodeId = NewNodeId; + } + + // Either export the current LOD Mesh by using RawMEsh or MeshDescription (legacy) + FMeshDescription* MeshDesc = nullptr; + // if (!bExportViaRawMesh) + if (ExportMethod == 1) + { + // This will either fetch the mesh description that is cached on the SrcModel + // or load it from bulk data / DDC once + if (SrcModel.MeshDescription.IsValid()) + { + MeshDesc = SrcModel.MeshDescription.Get(); + } + else + { + const double StartTime = FPlatformTime::Seconds(); + MeshDesc = StaticMesh->GetMeshDescription(LODIndex); + HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->GetMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + } + + bool bMeshSuccess = false; + if (ExportMethod == 1 && MeshDesc) + { + // Convert the Mesh using FMeshDescription + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForMeshDescription( + CurrentLODNodeId, + *MeshDesc, + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + else if (ExportMethod == 2) + { + // Convert the LOD Mesh using FStaticMeshLODResources + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( + CurrentLODNodeId, + StaticMesh->GetLODForExport(LODIndex), + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + else + { + // Convert the LOD Mesh using FRawMesh + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForRawMesh( + CurrentLODNodeId, + SrcModel, + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForRawMesh completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + + if (!bMeshSuccess) + continue; + + if (UseMergeNode) + { + // Connect the LOD node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, LODIndex, CurrentLODNodeId, 0), false); + } + } + + // next Index for adding nodes to the merge + int32 NextMergeIndex = NumLODsToExport; + if (DoExportColliders) + { + FKAggregateGeom SimpleColliders = StaticMesh->BodySetup->AggGeom; + + // Export BOX colliders + for (auto& CurBox : SimpleColliders.BoxElems) + { + FVector BoxCenter = CurBox.Center; + FVector BoxExtent = FVector(CurBox.X, CurBox.Y, CurBox.Z); + FRotator BoxRotation = CurBox.Rotation; + + HAPI_NodeId BoxNodeId = -1; + if (!CreateInputNodeForBox( + BoxNodeId, InputObjectNodeId, NextMergeIndex, + BoxCenter, BoxExtent, BoxRotation)) + continue; + + if (BoxNodeId < 0) + continue; + + // Connect the Box node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, BoxNodeId, 0), false); + + NextMergeIndex++; + } + + // Export SPHERE colliders + for (auto& CurSphere : SimpleColliders.SphereElems) + { + HAPI_NodeId SphereNodeId = -1; + if (!CreateInputNodeForSphere( + SphereNodeId, InputObjectNodeId, NextMergeIndex, + CurSphere.Center, CurSphere.Radius)) + continue; + + if (SphereNodeId < 0) + continue; + + // Connect the Sphere node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, SphereNodeId, 0), false); + + NextMergeIndex++; + } + + // Export CAPSULE colliders + for (auto& CurSphyl : SimpleColliders.SphylElems) + { + HAPI_NodeId SphylNodeId = -1; + if (!CreateInputNodeForSphyl( + SphylNodeId, InputObjectNodeId, NextMergeIndex, + CurSphyl.Center, CurSphyl.Rotation, CurSphyl.Radius, CurSphyl.Length)) + continue; + + if (SphylNodeId < 0) + continue; + + // Connect the capsule node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, SphylNodeId, 0), false); + + NextMergeIndex++; + } + + // Export CONVEX colliders + for (auto& CurConvex : SimpleColliders.ConvexElems) + { + HAPI_NodeId ConvexNodeId = -1; + if (!CreateInputNodeForConvex( + ConvexNodeId, InputObjectNodeId, NextMergeIndex, CurConvex)) + continue; + + if (ConvexNodeId < 0) + continue; + + // Connect the capsule node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, ConvexNodeId, 0), false); + + NextMergeIndex++; + } + } + + if (DoExportSockets && StaticMesh->Sockets.Num() > 0) + { + // Create an input node for the mesh sockets + HAPI_NodeId SocketsNodeId = -1; + if (CreateInputNodeForMeshSockets(StaticMesh->Sockets, InputObjectNodeId, SocketsNodeId)) + { + // We can connect the socket node to the merge node's last input. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), NewNodeId, NextMergeIndex, SocketsNodeId, 0), false); + + NextMergeIndex++; + } + else if (SocketsNodeId != -1) + { + // If we failed to properly export the sockets, clean up the created node + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), SocketsNodeId); + } + } + + // + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForMeshSockets( + const TArray& InMeshSocket, const HAPI_NodeId& InParentNodeId, HAPI_NodeId& OutSocketsNodeId) +{ + int32 NumSockets = InMeshSocket.Num(); + if (NumSockets <= 0) + return false; + + // Create a new input node for the sockets + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeId, TEXT("null"), "sockets", false, &OutSocketsNodeId), false); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.pointCount = NumSockets; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, &Part), false); + + // Create POS point attribute info. + HAPI_AttributeInfo AttributeInfoPos; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); + AttributeInfoPos.count = NumSockets; + AttributeInfoPos.tupleSize = 3; + AttributeInfoPos.exists = true; + AttributeInfoPos.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos), false); + + // Create Rot point attribute Info + HAPI_AttributeInfo AttributeInfoRot; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); + AttributeInfoRot.count = NumSockets; + AttributeInfoRot.tupleSize = 4; + AttributeInfoRot.exists = true; + AttributeInfoRot.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot), false); + + // Create scale point attribute Info + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = NumSockets; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); + + // Create the name attrib info + HAPI_AttributeInfo AttributeInfoName; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); + AttributeInfoName.count = NumSockets; + AttributeInfoName.tupleSize = 1; + AttributeInfoName.exists = true; + AttributeInfoName.owner = HAPI_ATTROWNER_POINT; + AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName), false); + + // Create the tag attrib info + HAPI_AttributeInfo AttributeInfoTag; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); + AttributeInfoTag.count = NumSockets; + AttributeInfoTag.tupleSize = 1; + AttributeInfoTag.exists = true; + AttributeInfoTag.owner = HAPI_ATTROWNER_POINT; + AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag), false); + + // Extract the sockets transform values + TArray SocketPos; + SocketPos.SetNumZeroed(NumSockets * 3); + TArray SocketRot; + SocketRot.SetNumZeroed(NumSockets * 4); + TArray SocketScale; + SocketScale.SetNumZeroed(NumSockets * 3); + + // raw string array for names and tag, will need to be free before returning + TArray SocketNames; + TArray SocketTags; + + // Lambda for freeing the const char array's memory and returning + auto FreeMemoryReturn = [&SocketNames, &SocketTags](const bool& bReturn) + { + // Frees the memory allocated by ExtractRawString for the names and tags + FHoudiniEngineUtils::FreeRawStringMemory(SocketNames); + FHoudiniEngineUtils::FreeRawStringMemory(SocketTags); + + return bReturn; + }; + + for (int32 Idx = 0; Idx < NumSockets; ++Idx) + { + UStaticMeshSocket* CurrentSocket = InMeshSocket[Idx]; + if (!CurrentSocket || CurrentSocket->IsPendingKill()) + continue; + + // Get the socket's transform and convert it to HapiTransform + FTransform SocketTransform(CurrentSocket->RelativeRotation, CurrentSocket->RelativeLocation, CurrentSocket->RelativeScale); + HAPI_Transform HapiSocketTransform; + FHoudiniApi::Transform_Init(&HapiSocketTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(SocketTransform, HapiSocketTransform); + + // Fill the attribute values + SocketPos[3 * Idx + 0] = HapiSocketTransform.position[0]; + SocketPos[3 * Idx + 1] = HapiSocketTransform.position[1]; + SocketPos[3 * Idx + 2] = HapiSocketTransform.position[2]; + + SocketRot[4 * Idx + 0] = HapiSocketTransform.rotationQuaternion[0]; + SocketRot[4 * Idx + 1] = HapiSocketTransform.rotationQuaternion[1]; + SocketRot[4 * Idx + 2] = HapiSocketTransform.rotationQuaternion[2]; + SocketRot[4 * Idx + 3] = HapiSocketTransform.rotationQuaternion[3]; + + SocketScale[3 * Idx + 0] = HapiSocketTransform.scale[0]; + SocketScale[3 * Idx + 1] = HapiSocketTransform.scale[1]; + SocketScale[3 * Idx + 2] = HapiSocketTransform.scale[2]; + + FString CurrentSocketName; + if (!CurrentSocket->SocketName.IsNone()) + CurrentSocketName = CurrentSocket->SocketName.ToString(); + else + CurrentSocketName = TEXT("Socket") + FString::FromInt(Idx); + SocketNames.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocketName)); + + if (!CurrentSocket->Tag.IsEmpty()) + SocketTags.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocket->Tag)); + else + SocketTags.Add(""); + } + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos, + SocketPos.GetData(), 0, AttributeInfoPos.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot, + SocketRot.GetData(), 0, AttributeInfoRot.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, + SocketScale.GetData(), 0, AttributeInfoScale.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName, + SocketNames.GetData(), 0, AttributeInfoName.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag, + SocketTags.GetData(), 0, AttributeInfoTag.count), + FreeMemoryReturn(false)); + + // We will also create the socket_details attributes + for (int32 Idx = 0; Idx < NumSockets; ++Idx) + { + // Build the current socket's prefix + FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(Idx); + + // Create mesh_socketX_pos attribute info. + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); + AttributeInfoPos.count = 1; + AttributeInfoPos.tupleSize = 3; + AttributeInfoPos.exists = true; + AttributeInfoPos.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; + + FString PosAttr = SocketAttrPrefix + TEXT("_pos"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos, + &(SocketPos[3 * Idx]), 0, AttributeInfoPos.count), + FreeMemoryReturn(false)); + + // Create mesh_socketX_rot point attribute Info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); + AttributeInfoRot.count = 1; + AttributeInfoRot.tupleSize = 4; + AttributeInfoRot.exists = true; + AttributeInfoRot.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; + + FString RotAttr = SocketAttrPrefix + TEXT("_rot"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot, + &(SocketRot[4 * Idx]), 0, AttributeInfoRot.count), + FreeMemoryReturn(false)); + + // Create mesh_socketX_scale point attribute Info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = 1; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + FString ScaleAttr = SocketAttrPrefix + TEXT("_scale"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale, + &(SocketScale[3 * Idx]), 0, AttributeInfoScale.count), + FreeMemoryReturn(false)); + + // Create the mesh_socketX_name attrib info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); + AttributeInfoName.count = 1; + AttributeInfoName.tupleSize = 1; + AttributeInfoName.exists = true; + AttributeInfoName.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; + + FString NameAttr = SocketAttrPrefix + TEXT("_name"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName, + &(SocketNames[Idx]), 0, AttributeInfoName.count), + FreeMemoryReturn(false)); + + // Create the mesh_socketX_tag attrib info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); + AttributeInfoTag.count = 1; + AttributeInfoTag.tupleSize = 1; + AttributeInfoTag.exists = true; + AttributeInfoTag.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; + + FString TagAttr = SocketAttrPrefix + TEXT("_tag"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag, + &(SocketTags[Idx]), 0, AttributeInfoTag.count), + FreeMemoryReturn(false)); + } + + // Now add the sockets group + const char * SocketGroupStr = "socket_imported"; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr), + FreeMemoryReturn(false)); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, NumSockets); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr, GroupArray.GetData(), 0, NumSockets), + FreeMemoryReturn(false)); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), OutSocketsNodeId), + FreeMemoryReturn(false)); + + return FreeMemoryReturn(true); +} + +bool +FUnrealMeshTranslator::CreateInputNodeForRawMesh( + const HAPI_NodeId& NodeId, + const FStaticMeshSourceModel& SourceModel, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent ) +{ + // Convert the Mesh using FRawMesh + FRawMesh RawMesh; + SourceModel.LoadRawMesh(RawMesh); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = RawMesh.WedgeIndices.Num(); + Part.faceCount = RawMesh.WedgeIndices.Num() / 3; + Part.pointCount = RawMesh.VertexPositions.Num(); + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = RawMesh.VertexPositions.Num(); + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Grab the build scale + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.VertexPositions.Num() > 3) + { + TArray StaticMeshVertices; + StaticMeshVertices.SetNumZeroed(RawMesh.VertexPositions.Num() * 3); + for (int32 VertexIdx = 0; VertexIdx < RawMesh.VertexPositions.Num(); ++VertexIdx) + { + // Convert Unreal to Houdini + const FVector & PositionVector = RawMesh.VertexPositions[VertexIdx]; + StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; + StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; + StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + for (int32 MeshTexCoordIdx = 0; MeshTexCoordIdx < MAX_STATIC_TEXCOORDS; MeshTexCoordIdx++) + { + int32 StaticMeshUVCount = RawMesh.WedgeTexCoords[MeshTexCoordIdx].Num(); + if (StaticMeshUVCount > 0) + { + const TArray & RawMeshUVs = RawMesh.WedgeTexCoords[MeshTexCoordIdx]; + TArray StaticMeshUVs; + StaticMeshUVs.Reserve(StaticMeshUVCount); + + // Transfer UV data. + for (int32 UVIdx = 0; UVIdx < StaticMeshUVCount; UVIdx++) + StaticMeshUVs.Emplace(RawMeshUVs[UVIdx].X, 1.0 - RawMeshUVs[UVIdx].Y, 0); + + // Convert Unreal to Houdini + // We need to re-index UVs for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + // We do not touch wedge 0 of this triangle, Swap 2 and 3 to reverse the winding order. + StaticMeshUVs.SwapMemory(WedgeIdx + 1, WedgeIdx + 2); + } + + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (MeshTexCoordIdx > 0) + UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = StaticMeshUVCount; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, (const float *)StaticMeshUVs.GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentZ.Num() > 0) + { + TArray ChangedNormals(RawMesh.WedgeTangentZ); + + // We need to re-index normals for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentZ1 = ChangedNormals[WedgeIdx + 1]; + FVector TangentZ2 = ChangedNormals[WedgeIdx + 2]; + + ChangedNormals[WedgeIdx + 1] = TangentZ2; + ChangedNormals[WedgeIdx + 2] = TangentZ1; + } + + // We also need to swap the vector's Y and Z components + for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedNormals[WedgeIdx].Y, ChangedNormals[WedgeIdx].Z); + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedNormals.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, (const float *)ChangedNormals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentX.Num() > 0) + { + TArray ChangedTangentU(RawMesh.WedgeTangentX); + + // We need to re-index tangents for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentU1 = ChangedTangentU[WedgeIdx + 1]; + FVector TangentU2 = ChangedTangentU[WedgeIdx + 2]; + + ChangedTangentU[WedgeIdx + 1] = TangentU2; + ChangedTangentU[WedgeIdx + 2] = TangentU1; + } + + // We also need to swap the vector's Y and Z components + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedTangentU[WedgeIdx].Y, ChangedTangentU[WedgeIdx].Z); + + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedTangentU.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + (const float *)ChangedTangentU.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentY.Num() > 0) + { + TArray ChangedTangentV(RawMesh.WedgeTangentY); + // We need to re-index normals for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentV1 = ChangedTangentV[WedgeIdx + 1]; + FVector TangentV2 = ChangedTangentV[WedgeIdx + 2]; + + ChangedTangentV[WedgeIdx + 1] = TangentV2; + ChangedTangentV[WedgeIdx + 2] = TangentV1; + } + + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedTangentV[WedgeIdx].Y, ChangedTangentV[WedgeIdx].Z); + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedTangentV.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + (const float *)ChangedTangentV.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + { + // If we have instance override vertex colors on the StaticMeshComponent, + // we first need to propagate them to our copy of the RawMesh Vert Colors + TArray ChangedColors; + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && + StaticMesh->RenderData && + StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; + FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) + { + // Use the wedge map if it is available as it is lossless. + int32 NumWedges = RawMesh.WedgeIndices.Num(); + if (RenderModel.WedgeMap.Num() == NumWedges) + { + int32 NumExistingColors = RawMesh.WedgeColors.Num(); + if (NumExistingColors < NumWedges) + { + RawMesh.WedgeColors.AddUninitialized(NumWedges - NumExistingColors); + } + + // Replace mesh colors with override colors + for (int32 i = 0; i < NumWedges; i++) + { + FColor WedgeColor = FColor::White; + int32 Index = RenderModel.WedgeMap[i]; + if (Index != INDEX_NONE) + { + WedgeColor = ColorVertexBuffer.VertexColor(Index); + } + RawMesh.WedgeColors[i] = WedgeColor; + } + } + } + } + + // See if we have colors to upload. + if (RawMesh.WedgeColors.Num() > 0) + { + ChangedColors.SetNumUninitialized(RawMesh.WedgeColors.Num()); + + // Convert Unreal to Houdini + // We need to re-index colors for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + ChangedColors[WedgeIdx + 0] = RawMesh.WedgeColors[WedgeIdx + 0].ReinterpretAsLinear(); + ChangedColors[WedgeIdx + 1] = RawMesh.WedgeColors[WedgeIdx + 2].ReinterpretAsLinear(); + ChangedColors[WedgeIdx + 2] = RawMesh.WedgeColors[WedgeIdx + 1].ReinterpretAsLinear(); + } + } + + if (ChangedColors.Num() > 0) + { + // Extract the RGB colors + TArray ColorValues; + ColorValues.SetNum(ChangedColors.Num() * 3); + for (int32 colorIndex = 0; colorIndex < ChangedColors.Num(); colorIndex++) + { + ColorValues[colorIndex * 3] = ChangedColors[colorIndex].R; + ColorValues[colorIndex * 3 + 1] = ChangedColors[colorIndex].G; + ColorValues[colorIndex * 3 + 2] = ChangedColors[colorIndex].B; + } + + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedColors.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + ColorValues.GetData(), 0, AttributeInfoVertex.count), false); + + // Create the attribute for Alpha + TArray AlphaValues; + AlphaValues.SetNum(ChangedColors.Num()); + for (int32 alphaIndex = 0; alphaIndex < ChangedColors.Num(); alphaIndex++) + AlphaValues[alphaIndex] = ChangedColors[alphaIndex].A; + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.count = AlphaValues.Num(); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + AlphaValues.GetData(), 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES (VertexList) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeIndices.Num() > 0) + { + TArray StaticMeshIndices; + StaticMeshIndices.SetNumUninitialized(RawMesh.WedgeIndices.Num()); + + // Convert Unreal to Houdini + for (int32 IndexIdx = 0; IndexIdx < RawMesh.WedgeIndices.Num(); IndexIdx += 3) + { + // Swap indices to fix winding order. + StaticMeshIndices[IndexIdx + 0] = RawMesh.WedgeIndices[IndexIdx + 0]; + StaticMeshIndices[IndexIdx + 1] = RawMesh.WedgeIndices[IndexIdx + 2]; + StaticMeshIndices[IndexIdx + 2] = RawMesh.WedgeIndices[IndexIdx + 1]; + } + + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, StaticMeshIndices.GetData(), 0, StaticMeshIndices.Num()), false); + + // We need to generate array of face counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, StaticMeshFaceCounts.GetData(), 0, StaticMeshFaceCounts.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + // Marshall face material indices. + if (RawMesh.FaceMaterialIndices.Num() > 0) + { + // Create an array of Material Interfaces + TArray MaterialInterfaces; + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()) + { + // StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); + + int32 NumMeshBasedMaterials = StaticMeshComponent->GetNumMaterials(); + TArray MeshBasedMaterialInterfaces; + MeshBasedMaterialInterfaces.SetNumUninitialized(NumMeshBasedMaterials); + for (int32 i = 0; i < NumMeshBasedMaterials; i++) + MeshBasedMaterialInterfaces[i] = StaticMeshComponent->GetMaterial(i); + + int32 NumSections = StaticMesh->GetNumSections(InLODIndex); + MaterialInterfaces.SetNumUninitialized(NumSections); + for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) + { + FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex); + check(Info.MaterialIndex < NumMeshBasedMaterials); + MaterialInterfaces[SectionIndex] = MeshBasedMaterialInterfaces[Info.MaterialIndex]; + } + } + else + { + // Query the Static mesh's materials + for (int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++) + { + MaterialInterfaces.Add(StaticMesh->GetMaterial(MatIdx)); + } + + // Try to fix up inconsistencies between the RawMesh / StaticMesh material indexes + // by using the meshes sections... + // TODO: Fix me properly! + // Proper fix would be to export the meshes via the FStaticMeshLODResources obtained + // by GetLODForExport(), and then export the mesh by sections. + if (StaticMesh->RenderData && StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + { + TMap MapOfMaterials; + FStaticMeshLODResources& LODResources = StaticMesh->RenderData->LODResources[InLODIndex]; + for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) + { + // Get the material for each element at the current lod index + int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; + if (!MapOfMaterials.Contains(MaterialIndex)) + { + MapOfMaterials.Add(MaterialIndex, StaticMesh->GetMaterial(MaterialIndex)); + } + } + + if (MapOfMaterials.Num() > 0) + { + // Sort the output material in the correct order (by material index) + MapOfMaterials.KeySort([](int32 A, int32 B) { return A < B; }); + + // Set the value in the correct order + // Do not reduce the array of materials, this could cause crahses in some weird cases.. + if (MapOfMaterials.Num() > MaterialInterfaces.Num()) + MaterialInterfaces.SetNumZeroed(MapOfMaterials.Num()); + + int32 MaterialIndex = 0; + for (auto Kvp : MapOfMaterials) + { + MaterialInterfaces[MaterialIndex++] = Kvp.Value; + } + } + } + } + + // Create list of materials, one for each face. + TArray StaticMeshFaceMaterials; + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + RawMesh.FaceMaterialIndices.Num(), + StaticMeshFaceMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(StaticMeshFaceMaterials); + + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING MASKS + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.FaceSmoothingMasks.Num() > 0) + { + HAPI_AttributeInfo AttributeInfoSmoothingMasks; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + AttributeInfoSmoothingMasks.count = RawMesh.FaceSmoothingMasks.Num(); + AttributeInfoSmoothingMasks.tupleSize = 1; + AttributeInfoSmoothingMasks.exists = true; + AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + (const int32 *)RawMesh.FaceSmoothingMasks.GetData(), 0, RawMesh.FaceSmoothingMasks.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->LightMapResolution); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + TArray AllTags; + for (auto& ComponentTag : StaticMeshComponent->ComponentTags) + AllTags.AddUnique(ComponentTag); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + for (auto& ActorTag : ParentActor->Tags) + AllTags.AddUnique(ActorTag); + } + + // Try to create groups for the tags + if (!FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, AllTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups for the Static Mesh Component and Actor tags!")); + + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + /* + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +bool +FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( + const HAPI_NodeId& NodeId, + const FStaticMeshLODResources& LODResources, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent) +{ + // Convert the Mesh using FStaticMeshLODResources + + // Check that the mesh is not empty + if (LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0) + { + HOUDINI_LOG_ERROR(TEXT("No vertices in mesh!")); + return false; + } + + if (LODResources.Sections.Num() == 0) + { + HOUDINI_LOG_ERROR(TEXT("No triangles in mesh!")); + return false; + } + + // Vertex instance and triangle counts + const uint32 OrigNumVertexInstances = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); + const uint32 NumTriangles = LODResources.GetNumTriangles(); + const uint32 NumVertexInstances = NumTriangles * 3; + const uint32 NumSections = LODResources.Sections.Num(); + + // Grab the build scale + const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + // In FStaticMeshLODResources each vertex instances stores its position, even if the positions are not unique (in other + // words, in Houdini terminology, the number of points and vertices are the same. We'll do the same thing that Epic + // does in FBX export: we'll run through all vertex instances and use a hash to determine which instances share a + // position, so that we can a smaller number of points than vertices, and vertices share point positions + TArray UEVertexInstanceIdxToPointIdx; + UEVertexInstanceIdxToPointIdx.Reserve(OrigNumVertexInstances); + + TMap PositionToPointIndexMap; + PositionToPointIndexMap.Reserve(OrigNumVertexInstances); + + TArray StaticMeshVertices; + StaticMeshVertices.Reserve(OrigNumVertexInstances * 3); + for (uint32 VertexInstanceIndex = 0; VertexInstanceIndex < OrigNumVertexInstances; ++VertexInstanceIndex) + { + // Convert Unreal to Houdini + const FVector &PositionVector = LODResources.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexInstanceIndex); + const int32 *FoundPointIndexPtr = PositionToPointIndexMap.Find(PositionVector); + if (!FoundPointIndexPtr) + { + const int32 NewPointIndex = StaticMeshVertices.Add(PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X) / 3; + StaticMeshVertices.Add(PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z); + StaticMeshVertices.Add(PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y); + + PositionToPointIndexMap.Add(PositionVector, NewPointIndex); + UEVertexInstanceIdxToPointIdx.Add(NewPointIndex); + } + else + { + UEVertexInstanceIdxToPointIdx.Add(*FoundPointIndexPtr); + } + } + + StaticMeshVertices.Shrink(); + const uint32 NumVertices = StaticMeshVertices.Num() / 3; + + // Now that we know how many vertices (points), vertex instances (vertices) and triagnles we have, + // we can create the part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumVertexInstances; + Part.faceCount = NumTriangles; + Part.pointCount = NumVertices; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = Part.pointCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + + // Determine which attributes we have + const bool bIsVertexInstanceNormalsValid = true; + const bool bIsVertexInstanceTangentsValid = true; + const bool bIsVertexInstanceBinormalsValid = true; + const bool bIsVertexInstanceColorsValid = LODResources.bHasColorVertexData; + const uint32 NumUVLayers = FMath::Min(LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(), MAX_STATIC_TEXCOORDS); + const bool bIsVertexInstanceUVsValid = NumUVLayers > 0; + + bool bUseComponentOverrideColors = false; + // Determine if have override colors on the static mesh component, if so prefer to use those + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (ColorVertexBuffer.GetNumVertices() == LODResources.GetNumVertices()) + { + bUseComponentOverrideColors = true; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL INDEX -> MATERIAL INTERFACE + //--------------------------------------------------------------------------------------------------------------------- + TArray MaterialInterfaces; + TArray TriangleMaterialIndices; + + // If the static mesh component is valid, get the materials via the component to account for overrides + const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); + const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); + // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, + // then we will set UEDefaultMaterial at the invalid index + int32 UEDefaultMaterialIndex = INDEX_NONE; + UMaterialInterface *UEDefaultMaterial = nullptr; + if (NumStaticMaterials > 0) + { + MaterialInterfaces.Reserve(NumStaticMaterials); + for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) + { + const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; + UMaterialInterface *Material = nullptr; + if (bIsStaticMeshComponentValid) + { + Material = StaticMeshComponent->GetMaterial(MaterialIndex); + } + else + { + Material = MaterialInfo.MaterialInterface; + } + // If the Material is NULL or invalid, fallback to the default material + if (!Material || Material->IsPendingKill()) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + UEDefaultMaterialIndex = MaterialIndex; + } + Material = UEDefaultMaterial; + HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); + } + // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); + MaterialInterfaces.Add(Material); + } + + TriangleMaterialIndices.Reserve(NumTriangles); + } + + // If we haven't created UEDefaultMaterial yet, check that all the sections' MaterialIndex + // is valid, if not, create UEDefaultMaterial and add to MaterialInterfaces to get UEDefaultMaterialIndex + if (!UEDefaultMaterial || UEDefaultMaterialIndex == INDEX_NONE) + { + for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + { + // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial + // up to and including MaterialIndex and log a warning + const int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; + if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + // Add the UEDefaultMaterial to MaterialInterfaces + UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); + } + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); + } + } + } + + // Determine the final number of materials we have, with default for missing/invalid indices + const int32 NumMaterials = MaterialInterfaces.Num(); + + // Now we deal with vertex instance attributes. + if (NumTriangles > 0) + { + // UV layer array. Each layer has an array of floats, 3 floats per vertex instance + TArray> UVs; + // Normals: 3 floats per vertex instance + TArray Normals; + // Tangents: 3 floats per vertex instance + TArray Tangents; + // Binormals: 3 floats per vertex instance + TArray Binormals; + // RGBColors: 3 floats per vertex instance + TArray RGBColors; + // Alphas: 1 float per vertex instance + TArray Alphas; + + // Initialize the arrays for the attributes that are valid + if (bIsVertexInstanceUVsValid) + { + UVs.SetNum(NumUVLayers); + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); + } + } + + if (bIsVertexInstanceNormalsValid) + { + Normals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceTangentsValid) + { + Tangents.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceBinormalsValid) + { + Binormals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + RGBColors.SetNumUninitialized(NumVertexInstances * 3); + Alphas.SetNumUninitialized(NumVertexInstances); + } + + // Array of vertex (point position) indices per triangle + TArray MeshTriangleVertexIndices; + MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); + // Array of vertex counts per triangle/face + TArray MeshTriangleVertexCounts; + MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); + + int32 TriangleIdx = 0; + int32 HoudiniVertexIdx = 0; + FIndexArrayView TriangleVertexIndices = LODResources.IndexBuffer.GetArrayView(); + for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + { + const FStaticMeshSection& Section = LODResources.Sections[SectionIndex]; + for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < Section.NumTriangles; ++SectionTriangleIndex) + { + MeshTriangleVertexCounts[TriangleIdx] = 3; + for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) + { + // Reverse the winding order for Houdini (but still start at 0) + const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; + const uint32 UEVertexIndex = TriangleVertexIndices[Section.FirstIndex + SectionTriangleIndex * 3 + WindingIdx]; + + // Calculate the index of the first component of a vertex instance's value in an inline float array + // representing vectors (3 float) per vertex instance + const int32 Float3Index = HoudiniVertexIdx * 3; + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + const FVector2D &UV = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(UEVertexIndex, UVLayerIndex); + UVs[UVLayerIndex][Float3Index + 0] = UV.X; + UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; + UVs[UVLayerIndex][Float3Index + 2] = 0; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + const FVector &Normal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(UEVertexIndex); + Normals[Float3Index + 0] = Normal.X; + Normals[Float3Index + 1] = Normal.Z; + Normals[Float3Index + 2] = Normal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + const FVector &Tangent = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(UEVertexIndex); + Tangents[Float3Index + 0] = Tangent.X; + Tangents[Float3Index + 1] = Tangent.Z; + Tangents[Float3Index + 2] = Tangent.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + // In order to calculate the binormal we also need the tangent and normal + if (bIsVertexInstanceBinormalsValid) + { + FVector Binormal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(UEVertexIndex); + Binormals[Float3Index + 0] = Binormal.X; + Binormals[Float3Index + 1] = Binormal.Z; + Binormals[Float3Index + 2] = Binormal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + FVector4 Color = FLinearColor::White; + if (bUseComponentOverrideColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + Color = ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); + } + else + { + Color = LODResources.VertexBuffers.ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); + } + RGBColors[Float3Index + 0] = Color[0]; + RGBColors[Float3Index + 1] = Color[1]; + RGBColors[Float3Index + 2] = Color[2]; + Alphas[HoudiniVertexIdx] = Color[3]; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + if (UEVertexInstanceIdxToPointIdx.IsValidIndex(UEVertexIndex)) + { + MeshTriangleVertexIndices[HoudiniVertexIdx] = UEVertexInstanceIdxToPointIdx[UEVertexIndex]; + } + + HoudiniVertexIdx++; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + if (MaterialInterfaces.IsValidIndex(Section.MaterialIndex)) + { + TriangleMaterialIndices.Add(Section.MaterialIndex); + } + else + { + TriangleMaterialIndices.Add(UEDefaultMaterialIndex); + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, Section.MaterialIndex, *(UEDefaultMaterial->GetPathName())); + } + + TriangleIdx++; + } + } + + // Now transfer valid vertex instance attributes to Houdini vertex attributes + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) + { + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (UVLayerIndex > 0) + UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = NumVertexInstances; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, Normals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + Tangents.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceBinormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + Binormals.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + RGBColors.GetData(), 0, AttributeInfoVertex.count), false); + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.count = Alphas.Num(); + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + Alphas.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); + + // Send the array of face vertex counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); + + // Send material assignments to Houdini + if (NumMaterials > 0) + { + // Create list of materials, one for each face. + TArray TriangleMaterials; + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); + + // Delete texture parameter attribute names. + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + + } + + // TODO: The render mesh (LODResources) does not have face smoothing information, and the raw mesh triangle order is + // potentially different (see also line 4152 TODO_FBX in Engine\Source\Editor\UnrealEd\Private\Fbx\FbxMainExport.cpp + ////--------------------------------------------------------------------------------------------------------------------- + //// TRIANGLE SMOOTHING MASKS + ////--------------------------------------------------------------------------------------------------------------------- + //TArray TriangleSmoothingMasks; + //TriangleSmoothingMasks.SetNumZeroed(NumTriangles); + //FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); + //if (TriangleSmoothingMasks.Num() > 0) + //{ + // HAPI_AttributeInfo AttributeInfoSmoothingMasks; + // FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + // AttributeInfoSmoothingMasks.tupleSize = 1; + // AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); + // AttributeInfoSmoothingMasks.exists = true; + // AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + // AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + // AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + // FHoudiniEngine::Get().GetSession(), + // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + // FHoudiniEngine::Get().GetSession(), + // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + // (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); + //} + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->LightMapResolution); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + // Try to create groups for the static mesh component's tags + if (StaticMeshComponent->ComponentTags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Try to create groups for the parent Actor's tags + if (ParentActor->Tags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + + /* + // Add the unreal_level_path attribute + FString LevelPath = ActorPath;// FString(); + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +bool +FUnrealMeshTranslator::CreateInputNodeForMeshDescription( + const HAPI_NodeId& NodeId, + const FMeshDescription& MeshDescription, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent) +{ + // Convert the Mesh using FMeshDescription + // Get references to the attributes we are interested in + // before sending to Houdini we'll check if each attribute is valid + FStaticMeshConstAttributes MeshDescriptionAttributes(MeshDescription); + + TVertexAttributesConstRef VertexPositions = MeshDescriptionAttributes.GetVertexPositions(); + TVertexInstanceAttributesConstRef VertexInstanceNormals = MeshDescriptionAttributes.GetVertexInstanceNormals(); + TVertexInstanceAttributesConstRef VertexInstanceTangents = MeshDescriptionAttributes.GetVertexInstanceTangents(); + TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = MeshDescriptionAttributes.GetVertexInstanceBinormalSigns(); + TVertexInstanceAttributesConstRef VertexInstanceColors = MeshDescriptionAttributes.GetVertexInstanceColors(); + TVertexInstanceAttributesConstRef VertexInstanceUVs = MeshDescriptionAttributes.GetVertexInstanceUVs(); + //TPolygonGroupAttributesConstRef PolygonGroupMaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames(); + + // Get the vertex and triangle arrays + const FVertexArray &MDVertices = MeshDescription.Vertices(); + const FPolygonGroupArray &MDPolygonGroups = MeshDescription.PolygonGroups(); + const FPolygonArray &MDPolygons = MeshDescription.Polygons(); + const FTriangleArray &MDTriangles = MeshDescription.Triangles(); + + // Determine point, vertex and polygon counts + const uint32 NumVertices = MDVertices.Num(); + const uint32 NumTriangles = MDTriangles.Num(); + const uint32 NumVertexInstances = NumTriangles * 3; + + // Some checks: we expect triangulated meshes + if (MeshDescription.VertexInstances().Num() != NumTriangles * 3) + { + HOUDINI_LOG_ERROR(TEXT("Expected a triangulated mesh, but # VertexInstances (%d) != # Triangles * 3 (%d)"), MeshDescription.VertexInstances().Num(), NumTriangles * 3); + return false; + } + + // Determine which attributes we have + const bool bIsVertexPositionsValid = VertexPositions.IsValid(); + const bool bIsVertexInstanceNormalsValid = VertexInstanceNormals.IsValid(); + const bool bIsVertexInstanceTangentsValid = VertexInstanceTangents.IsValid(); + const bool bIsVertexInstanceBinormalSignsValid = VertexInstanceBinormalSigns.IsValid(); + const bool bIsVertexInstanceColorsValid = VertexInstanceColors.IsValid(); + const bool bIsVertexInstanceUVsValid = VertexInstanceUVs.IsValid(); + //const bool bIsPolygonGroupImportedMaterialSlotNamesValid = PolygonGroupMaterialSlotNames.IsValid(); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumVertexInstances; + Part.faceCount = NumTriangles; + Part.pointCount = NumVertices; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = Part.pointCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Grab the build scale + const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + // The mesh element arrays are sparse: the max index/ID value can be larger than the number of elements - 1 + // so we have to maintain a lookup of VertexID (UE) to PointIndex (Houdini) + TArray VertexIDToHIndex; + if (bIsVertexPositionsValid && VertexPositions.GetNumElements() >= 3) + { + TArray StaticMeshVertices; + StaticMeshVertices.SetNumUninitialized(NumVertices * 3); + + int32 VertexIdx = 0; + VertexIDToHIndex.Init(INDEX_NONE, MDVertices.GetArraySize()); + + for (const FVertexID& VertexID : MDVertices.GetElementIDs()) + { + // Convert Unreal to Houdini + const FVector &PositionVector = VertexPositions.Get(VertexID); + StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; + StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; + StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; + + // Record the UE Vertex ID to Houdini Point Index lookup + VertexIDToHIndex[VertexID.GetValue()] = VertexIdx; + VertexIdx++; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + } + + bool bUseComponentOverrideColors = false; + // Determine if have override colors on the static mesh component, if so prefer to use those + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && + StaticMesh->RenderData && + StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; + FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) + { + // Use the wedge map if it is available as it is lossless. + if (RenderModel.WedgeMap.Num() == NumVertexInstances) + { + bUseComponentOverrideColors = true; + } + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL SLOT -> MATERIAL INTERFACE + //--------------------------------------------------------------------------------------------------------------------- + // In theory the ImportedMaterialSlotName attribute on PolygonGroups should tell us which material slot is used by + // that group, and thus which MaterialIndex we should assign to triangles in that group. Unfortunately we have + // encountered cases where the ImportedMaterialSlotName name attribute does not match any of the MaterialSlotName or + // ImportedMaterialSlotNames in the StaticMesh->StaticMaterials array. Therefore we have no choice but to rely + // on the PolygonGroup order vs Section order to determine the MaterialIndex for a group. We do what Epic does + // when building a static mesh: Sections are created in the same order as iterating over PolygonGroups, but importantly, + // empty PolygonGroups are skipped + + // // Get material slot name to material index + // and the UMaterialInterface array + // TMap MaterialSlotToInterface; + TArray MaterialInterfaces; + TArray TriangleMaterialIndices; + + // If the static mesh component is valid, get the materials via the component to account for overrides + const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); + const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); + // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, + // then we will set UEDefaultMaterial at the invalid index + int32 UEDefaultMaterialIndex = INDEX_NONE; + UMaterialInterface *UEDefaultMaterial = nullptr; + if (NumStaticMaterials > 0) + { + MaterialInterfaces.Reserve(NumStaticMaterials); + for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) + { + const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; + UMaterialInterface *Material = nullptr; + if (bIsStaticMeshComponentValid) + { + Material = StaticMeshComponent->GetMaterial(MaterialIndex); + } + else + { + Material = MaterialInfo.MaterialInterface; + } + // If the Material is NULL or invalid, fallback to the default material + if (!Material || Material->IsPendingKill()) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + UEDefaultMaterialIndex = MaterialIndex; + } + Material = UEDefaultMaterial; + HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); + } + // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); + MaterialInterfaces.Add(Material); + } + + TriangleMaterialIndices.Reserve(NumTriangles); + } + // SectionIndex: Looking at Epic's StaticMesh build code, Sections are created in the same + // order as iterating over PolygonGroups, but skipping empty PolygonGroups + TMap PolygonGroupToMaterialIndex; + PolygonGroupToMaterialIndex.Reserve(MeshDescription.PolygonGroups().Num()); + int32 SectionIndex = 0; + for (const FPolygonGroupID &PolygonGroupID : MDPolygonGroups.GetElementIDs()) + { + // Skip empty polygon groups + if (MeshDescription.GetNumPolygonGroupPolygons(PolygonGroupID) == 0) + { + continue; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + // // Get the material index for the material slot for this polygon group + //int32 MaterialIndex = INDEX_NONE; + //if (bIsPolygonGroupImportedMaterialSlotNamesValid) + //{ + // const FName &MaterialSlotName = PolygonGroupMaterialSlotNames.Get(PolygonGroupID); + // const int32 *MaterialIndexPtr = MaterialSlotToInterface.Find(MaterialSlotName); + // if (MaterialIndexPtr != nullptr) + // { + // MaterialIndex = *MaterialIndexPtr; + // } + //} + + // Get the material for the LOD and section via the section info map + if (StaticMesh->GetNumSections(InLODIndex) <= SectionIndex) + { + HOUDINI_LOG_ERROR(TEXT("Found more non-empty polygon groups in the mesh description for LOD %d than sections in the static mesh..."), InLODIndex); + return false; + } + + // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial + // up to and including MaterialIndex and log a warning + int32 MaterialIndex = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex).MaterialIndex; + if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + // Add the UEDefaultMaterial to MaterialInterfaces + UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); + } + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); + MaterialIndex = UEDefaultMaterialIndex; + } + + PolygonGroupToMaterialIndex.Add(PolygonGroupID, MaterialIndex); + SectionIndex++; + } + + // Determine the final number of materials we have, with defaults for missing/invalid indices + const int32 NumMaterials = MaterialInterfaces.Num(); + + // Now we deal with vertex instance attributes. + // // First we must also record a UE VertexInstanceID to Houdini Vertex Index lookup, + // // and then get and convert all valid and supported vertex instance attributes from UE + // TArray VertexInstanceIDToHIndex; + + if (NumTriangles > 0) + { + // UV layer array. Each layer has an array of floats, 3 floats per vertex instance + TArray> UVs; + const int32 NumUVLayers = bIsVertexInstanceUVsValid ? FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_STATIC_TEXCOORDS) : 0; + // Normals: 3 floats per vertex instance + TArray Normals; + // Tangents: 3 floats per vertex instance + TArray Tangents; + // Binormals: 3 floats per vertex instance + TArray Binormals; + // RGBColors: 3 floats per vertex instance + TArray RGBColors; + // Alphas: 1 float per vertex instance + TArray Alphas; + + // Initialize the arrays for the attributes that are valid + if (bIsVertexInstanceUVsValid) + { + UVs.SetNum(NumUVLayers); + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); + } + } + + if (bIsVertexInstanceNormalsValid) + { + Normals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceTangentsValid) + { + Tangents.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceBinormalSignsValid) + { + Binormals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + RGBColors.SetNumUninitialized(NumVertexInstances * 3); + Alphas.SetNumUninitialized(NumVertexInstances); + } + + // Array of material index per triangle/face + TArray MeshTriangleVertexIndices; + MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); + // Array of vertex counts per triangle/face + TArray MeshTriangleVertexCounts; + MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); + + int32 TriangleIdx = 0; + int32 VertexInstanceIdx = 0; + // VertexInstanceIDToHIndex.Init(-1, MDVertexInstances.GetArraySize()); + + for (const FPolygonID &PolygonID : MDPolygons.GetElementIDs()) + { + for (const FTriangleID &TriangleID : MeshDescription.GetPolygonTriangleIDs(PolygonID)) + { + MeshTriangleVertexCounts[TriangleIdx] = 3; + for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) + { + // Reverse the winding order for Houdini (but still start at 0) + const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; + const FVertexInstanceID &VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, WindingIdx); + + // // UE Vertex Instance ID to Houdini Vertex Index look up + // VertexInstanceIDToHIndex[VertexInstanceID.GetValue()] = VertexInstanceIdx; + + // Calculate the index of the first component of a vertex instance's value in an inline float array + // representing vectors (3 float) per vertex instance + const int32 Float3Index = VertexInstanceIdx * 3; + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + const FVector2D &UV = VertexInstanceUVs.Get(VertexInstanceID, UVLayerIndex); + UVs[UVLayerIndex][Float3Index + 0] = UV.X; + UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; + UVs[UVLayerIndex][Float3Index + 2] = 0; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + const FVector &Normal = VertexInstanceNormals.Get(VertexInstanceID); + Normals[Float3Index + 0] = Normal.X; + Normals[Float3Index + 1] = Normal.Z; + Normals[Float3Index + 2] = Normal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + const FVector &Tangent = VertexInstanceTangents.Get(VertexInstanceID); + Tangents[Float3Index + 0] = Tangent.X; + Tangents[Float3Index + 1] = Tangent.Z; + Tangents[Float3Index + 2] = Tangent.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + // In order to calculate the binormal we also need the tangent and normal + if (bIsVertexInstanceBinormalSignsValid && bIsVertexInstanceTangentsValid && bIsVertexInstanceNormalsValid) + { + const float &BinormalSign = VertexInstanceBinormalSigns.Get(VertexInstanceID); + FVector Binormal = FVector::CrossProduct( + FVector(Tangents[Float3Index + 0], Tangents[Float3Index + 1], Tangents[Float3Index + 2]), + FVector(Normals[Float3Index + 0], Normals[Float3Index + 1], Normals[Float3Index + 2]) + ) * BinormalSign; + Binormals[Float3Index + 0] = Binormal.X; + Binormals[Float3Index + 1] = Binormal.Y; + Binormals[Float3Index + 2] = Binormal.Z; + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + FVector4 Color = FLinearColor::White; + if (bUseComponentOverrideColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshLODResources& RenderModel = StaticMesh->RenderData->LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + int32 Index = RenderModel.WedgeMap[VertexInstanceIdx]; + if (Index != INDEX_NONE) + { + Color = ColorVertexBuffer.VertexColor(Index).ReinterpretAsLinear(); + } + } + else + { + Color = VertexInstanceColors.Get(VertexInstanceID); + } + RGBColors[Float3Index + 0] = Color[0]; + RGBColors[Float3Index + 1] = Color[1]; + RGBColors[Float3Index + 2] = Color[2]; + Alphas[VertexInstanceIdx] = Color[3]; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + const FVertexID& VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID); + const int32 UEVertexIdx = VertexID.GetValue(); + if (VertexIDToHIndex.IsValidIndex(UEVertexIdx)) + { + MeshTriangleVertexIndices[VertexInstanceIdx] = VertexIDToHIndex[UEVertexIdx]; + } + + VertexInstanceIdx++; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + const FPolygonGroupID &PolygonGroupID = MeshDescription.GetPolygonPolygonGroup(PolygonID); + const int32 MaterialIndex = PolygonGroupToMaterialIndex.FindChecked(PolygonGroupID); + TriangleMaterialIndices.Add(MaterialIndex); + + TriangleIdx++; + } + } + + // Now transfer valid vertex instance attributes to Houdini vertex attributes + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) + { + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (UVLayerIndex > 0) + UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = NumVertexInstances; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, Normals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + Tangents.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceBinormalSignsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + Binormals.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + RGBColors.GetData(), 0, AttributeInfoVertex.count), false); + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.count = Alphas.Num(); + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + Alphas.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); + + // Send the array of face vertex counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); + + // Send material assignments to Houdini + if (NumMaterials > 0) + { + // Create list of materials, one for each face. + TArray TriangleMaterials; + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterialIndices.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); + + // Delete texture material parameter names. + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (bAttributeSuccess) + { + check(0); + return false; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE SMOOTHING MASKS + //--------------------------------------------------------------------------------------------------------------------- + TArray TriangleSmoothingMasks; + TriangleSmoothingMasks.SetNumZeroed(NumTriangles); + FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); + if (TriangleSmoothingMasks.Num() > 0) + { + HAPI_AttributeInfo AttributeInfoSmoothingMasks; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + AttributeInfoSmoothingMasks.tupleSize = 1; + AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); + AttributeInfoSmoothingMasks.exists = true; + AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->LightMapResolution); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + // Try to create groups for the static mesh component's tags + if (StaticMeshComponent->ComponentTags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Try to create groups for the parent Actor's tags + if (ParentActor->Tags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + + /* + FString LevelPath = FString(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + + +void +FUnrealMeshTranslator::CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials, + TMap> & OutScalarMaterialParameters, + TMap> & OutVectorMaterialParameters, + TMap> & OutTextureMaterialParameters) +{ + // We need to create list of unique materials. + TArray< char * > UniqueMaterialList; + + UMaterialInterface * MaterialInterface = nullptr; + char* UniqueName = nullptr; + + UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); + + // Initialize material parameter arrays + TMap> ScalarParams; + TMap> VectorParams; + TMap> TextureParams; + + if (Materials.Num()) + { + // We have materials. + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) + { + UniqueName = nullptr; + MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface) + { + // Null material interface found, add default instead. + UniqueMaterialList.Add(DefaultMaterialName); + + // No need to collect material parameters on the default material + continue; + } + + // We found a material, get its name and material parameters + FString FullMaterialName = MaterialInterface->GetPathName(); + UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); + UniqueMaterialList.Add(UniqueName); + + // Collect all scalar parameters in all materials + { + TArray MaterialScalarParamInfos; + TArray MaterialScalarParamGuids; + MaterialInterface->GetAllScalarParameterInfo(MaterialScalarParamInfos, MaterialScalarParamGuids); + + for (auto & CurScalarParam : MaterialScalarParamInfos) + { + FString CurScalarParamName = CurScalarParam.Name.ToString(); + float CurScalarVal; + MaterialInterface->GetScalarParameterValue(CurScalarParam, CurScalarVal); + if (!ScalarParams.Contains(CurScalarParamName)) + { + TArray CurArray; + CurArray.SetNumUninitialized(Materials.Num()); + // Initialize the array with the Min float value + for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) + CurArray[ArrIdx] = FLT_MIN; + + ScalarParams.Add(CurScalarParamName, CurArray); + OutScalarMaterialParameters.Add(CurScalarParamName); + } + + ScalarParams[CurScalarParamName][MaterialIdx] = CurScalarVal; + } + } + + // Collect all vector parameters in all materials + { + TArray MaterialVectorParamInfos; + TArray MaterialVectorParamGuids; + MaterialInterface->GetAllVectorParameterInfo(MaterialVectorParamInfos, MaterialVectorParamGuids); + + for (auto & CurVectorParam : MaterialVectorParamInfos) + { + FString CurVectorParamName = CurVectorParam.Name.ToString(); + FLinearColor CurVectorValue; + MaterialInterface->GetVectorParameterValue(CurVectorParam, CurVectorValue); + if (!VectorParams.Contains(CurVectorParamName)) + { + TArray CurArray; + CurArray.SetNumUninitialized(Materials.Num()); + FLinearColor MinColor(FLT_MIN, FLT_MIN, FLT_MIN, FLT_MIN); + for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) + CurArray[ArrIdx] = MinColor; + + VectorParams.Add(CurVectorParamName, CurArray); + OutVectorMaterialParameters.Add(CurVectorParamName); + } + + VectorParams[CurVectorParamName][MaterialIdx] = CurVectorValue; + } + } + + // Collect all texture parameters in all materials + { + TArray MaterialTextureParamInfos; + TArray MaterialTextureParamGuids; + MaterialInterface->GetAllTextureParameterInfo(MaterialTextureParamInfos, MaterialTextureParamGuids); + + for (auto & CurTextureParam : MaterialTextureParamInfos) + { + FString CurTextureParamName = CurTextureParam.Name.ToString(); + UTexture * CurTexture = nullptr; + MaterialInterface->GetTextureParameterValue(CurTextureParam, CurTexture); + + if (!CurTexture || CurTexture->IsPendingKill()) + continue; + + FString TexturePath = CurTexture->GetPathName(); + if (!TextureParams.Contains(CurTextureParamName)) + { + TArray CurArray; + CurArray.SetNumZeroed(Materials.Num()); + + TextureParams.Add(CurTextureParamName, CurArray); + OutTextureMaterialParameters.Add(CurTextureParamName); + } + + char * TexturePathRawStr = UniqueName = FHoudiniEngineUtils::ExtractRawString(TexturePath); + TextureParams[CurTextureParamName][MaterialIdx] = TexturePathRawStr; + } + } + + } + } + else + { + // We do not have any materials, add default. + UniqueMaterialList.Add(DefaultMaterialName); + } + + // TODO: Needs to be improved! + // We shouldnt be testing for each face, but only for each unique facematerial value... + for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) + { + int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; + if(UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) + { + OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); + + for (auto & Pair : ScalarParams) + { + OutScalarMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); + } + + for (auto & Pair : VectorParams) + { + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].R); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].G); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].B); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].A); + } + + for (auto & Pair : TextureParams) + { + OutTextureMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); + } + } + else + { + OutStaticMeshFaceMaterials.Add(DefaultMaterialName); + } + } +} + + +void +FUnrealMeshTranslator::DeleteFaceMaterialArray(TArray& OutStaticMeshFaceMaterials) +{ + // Clean up the memory allocated by CreateFaceMaterialArray() + TSet UniqueMaterials(OutStaticMeshFaceMaterials); + for (TSet::TIterator Iter = UniqueMaterials.CreateIterator(); Iter; ++Iter) + { + char* MaterialName = *Iter; + FMemory::Free(MaterialName); + } + + OutStaticMeshFaceMaterials.Empty(); +} + +bool +FUnrealMeshTranslator::CreateInputNodeForBox( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& BoxCenter, + const FVector& BoxExtent, + const FRotator& BoxRotation) +{ + // Create a new input node for the box collider + FString BoxName = TEXT("box") + FString::FromInt(ColliderIndex); + + // Create the node in this input object's OBJ node + HAPI_NodeId BoxNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("box"), BoxName, false, &BoxNodeId), false); + + // Set the box parameters + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 0, BoxExtent.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 1, BoxExtent.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 2, BoxExtent.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 0, BoxCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 1, BoxCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 2, BoxCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 0, BoxRotation.Roll); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 2, BoxRotation.Pitch); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 1, BoxRotation.Yaw); + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), BoxNodeId, &CookOptions); + */ + if (!FHoudiniEngineUtils::HapiCookNode(BoxNodeId, nullptr, true)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_box") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, BoxNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForSphere( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphereCenter, + const float& SphereRadius) +{ + // Create a new input node for the sphere collider + const char * SphereName = ""; + { + FString SPH = TEXT("Sphere") + FString::FromInt(ColliderIndex); + SphereName = TCHAR_TO_UTF8(*SPH); + } + + // Create the node in this input object's OBJ node + HAPI_NodeId SphereNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "sphere", SphereName, false, &SphereNodeId), false); + + // Set the box parameters + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 1, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 2, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 0, SphereCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 1, SphereCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 2, SphereCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "type", 0, 1); + /* + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "scale", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + */ + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), SphereNodeId, &CookOptions); + */ + if (!FHoudiniEngineUtils::HapiCookNode(SphereNodeId, nullptr, true)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_sphere") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphereNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForSphyl( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphylCenter, + const FRotator& SphylRotation, + const float& SphylRadius, + const float& SphereLength) +{ + // + // Get the Sphyl's vertices and indices + // (code drived from FKSphylElem::GetElemSolid) + // + + // TODO: + // Rotation? + + const int32 NumSides = 6; + const int32 NumRings = (NumSides / 2) + 1; + + // The first/last arc are on top of each other. + const int32 NumVerts = (NumSides + 1) * (NumRings + 1); + + // Calculate the vertices for one arc + TArray ArcVertices; + ArcVertices.SetNum(NumRings + 1); + for (int32 RingIdx = 0; RingIdx < NumRings + 1; RingIdx++) + { + float Angle; + float ZOffset; + if (RingIdx <= NumSides / 4) + { + Angle = ((float)RingIdx / (NumRings - 1)) * PI; + ZOffset = 0.5 * SphereLength; + } + else + { + Angle = ((float)(RingIdx - 1) / (NumRings - 1)) * PI; + ZOffset = -0.5 * SphereLength; + } + + // Note- unit sphere, so position always has mag of one. We can just use it for normal! + FVector SpherePos; + SpherePos.X = 0.0f; + SpherePos.Y = SphylRadius * FMath::Sin(Angle); + SpherePos.Z = SphylRadius * FMath::Cos(Angle); + + ArcVertices[RingIdx] = SpherePos + FVector(0, 0, ZOffset); + } + + // Get the transform matrix for the rotation + FRotationMatrix SphylRotMatrix(SphylRotation); + + // Get the Sphyl's vertices by rotating the arc NumSides+1 times. + TArray Vertices; + Vertices.SetNum(NumVerts * 3); + for (int32 SideIdx = 0; SideIdx < NumSides + 1; SideIdx++) + { + const FRotator ArcRotator(0, 360.f * ((float)SideIdx / NumSides), 0); + const FRotationMatrix ArcRot(ArcRotator); + const float XTexCoord = ((float)SideIdx / NumSides); + + for (int32 VertIdx = 0; VertIdx < NumRings + 1; VertIdx++) + { + int32 VIx = (NumRings + 1)*SideIdx + VertIdx; + + FVector CurPosition = SphylCenter + ArcRot.TransformPosition(ArcVertices[VertIdx]); + CurPosition = SphylRotMatrix.TransformPosition(CurPosition); + + // Convert the UE4 position to Houdini + Vertices[VIx * 3 + 0] = CurPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[VIx * 3 + 1] = CurPosition.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[VIx * 3 + 2] = CurPosition.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + } + + // Add all of the indices to the mesh. + int32 NumIndices = NumSides * NumRings * 6; + TArray Indices; + Indices.SetNum(NumIndices); + + int32 CurIndex = 0; + for (int32 SideIdx = 0; SideIdx < NumSides; SideIdx++) + { + const int32 a0start = (SideIdx + 0) * (NumRings + 1); + const int32 a1start = (SideIdx + 1) * (NumRings + 1); + for (int32 RingIdx = 0; RingIdx < NumRings; RingIdx++) + { + // First Tri (reverse winding) + Indices[CurIndex+0] = a0start + RingIdx + 0; + Indices[CurIndex+2] = a1start + RingIdx + 0; + Indices[CurIndex+1] = a0start + RingIdx + 1; + CurIndex += 3; + // Second Tri (reverse winding) + Indices[CurIndex+0] = a1start + RingIdx + 0; + Indices[CurIndex+2] = a1start + RingIdx + 1; + Indices[CurIndex+1] = a0start + RingIdx + 1; + CurIndex += 3; + } + } + + // + // Create the Sphyl Mesh in houdini + // + HAPI_NodeId SphylNodeId = -1; + FString SphylName = TEXT("Sphyl") + FString::FromInt(ColliderIndex); + if(!CreateInputNodeForCollider(SphylNodeId, InParentNodeID, ColliderIndex, SphylName, Vertices, Indices)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_capsule") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphylNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForConvex( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FKConvexElem& ConvexCollider) +{ + TArray Vertices; + TArray Indices; + +#if PHYSICS_INTERFACE_PHYSX + if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) +#elif WITH_CHAOS + //if (ConvexCollider.GetChaosConvexMesh().IsValid()) + if (ConvexCollider.IndexData.Num() > 0 && ConvexCollider.IndexData.Num() % 3 == 0) +#else + if(false) +#endif + { + // Get the convex colliders vertices and indices from the mesh + TArray VertexBuffer; + TArray IndexBuffer; + ConvexCollider.AddCachedSolidConvexGeom(VertexBuffer, IndexBuffer, FColor::White); + + Vertices.SetNum(VertexBuffer.Num() * 3); + int32 CurIndex = 0; + for (auto& CurVert : VertexBuffer) + { + Vertices[CurIndex * 3 + 0] = CurVert.Position.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 1] = CurVert.Position.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 2] = CurVert.Position.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + CurIndex++; + } + + Indices.SetNum(IndexBuffer.Num()); + for (int Idx = 0; (Idx + 2) < IndexBuffer.Num(); Idx += 3) + { + // Reverse winding + Indices[Idx + 0] = Indices[Idx + 0]; + Indices[Idx + 2] = Indices[Idx + 1]; + Indices[Idx + 1] = Indices[Idx + 2]; + } + } + else + { + int32 NumVert = ConvexCollider.VertexData.Num(); + Vertices.SetNum(NumVert * 3); + //Indices.SetNum(NumVert); + int32 CurIndex = 0; + for (auto& CurVert : ConvexCollider.VertexData) + { + Vertices[CurIndex * 3 + 0] = CurVert.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 1] = CurVert.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 2] = CurVert.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + + /* + // TODO: Get proper polygons... + Indices[CurIndex] = CurIndex; + */ + CurIndex++; + } + + // TODO: Get Proper polygons + for (int32 Idx = 0; Idx + 2 < NumVert; Idx++) + { + Indices.Add(Idx + 0); + Indices.Add(Idx + 1); + Indices.Add(Idx + 2); + } + + /* + for (int32 Idx = 0; Idx + 3 < NumVert; Idx+= 4) + { + Indices.Add(Idx + 0); + Indices.Add(Idx + 1); + Indices.Add(Idx + 2); + + Indices.Add(Idx + 2); + Indices.Add(Idx + 1); + Indices.Add(Idx + 3); + } + */ + } + + // + // Create the Convex Mesh in houdini + // + HAPI_NodeId ConvexNodeId = -1; + FString ConvexName = TEXT("Convex") + FString::FromInt(ColliderIndex); + if (!CreateInputNodeForCollider(ConvexNodeId, InParentNodeID, ColliderIndex, ConvexName, Vertices, Indices)) + return false; + + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), ColliderNodeId, &CookOptions); + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "groupcreate", GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_ucx + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_ucx") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Create a convex hull (shrinkwrap::2.0) node to fix the lack of proper indices + HAPI_NodeId ConvexHullNodeId = -1; + FString ConvexHullName = TEXT("ConvexHull") + FString::FromInt(ColliderIndex); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "shrinkwrap::2.0", ConvexHullName, false, &ConvexHullNodeId), false); + + if (ConvexHullNodeId > 0) + { + // Connect the collider to the convex hull + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), ConvexHullNodeId, 0, ConvexNodeId, 0), false); + + // Connect the convex hull to the group + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexHullNodeId, 0), false); + } + else + { + // Connect the collider to the group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexNodeId, 0), false); + + } + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForCollider( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FString& ColliderName, + const TArray& ColliderVertices, + const TArray& ColliderIndices) +{ + // Create a new input node for the collider + const char * ColliderNameStr = TCHAR_TO_UTF8(*ColliderName); + + // Create the node in this input object's OBJ node + HAPI_NodeId ColliderNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "null", ColliderNameStr, false, &ColliderNodeId), false); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = ColliderIndices.Num(); + Part.faceCount = ColliderIndices.Num() / 3; + Part.pointCount = ColliderVertices.Num() / 3; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), ColliderNodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = ColliderVertices.Num() / 3; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Upload the positions + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + ColliderVertices.GetData(), 0, AttributeInfoPoint.count), false); + + // Upload the indices + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, ColliderIndices.GetData(), 0, ColliderIndices.Num()), false); + + // Generate the array of face counts. + TArray ColldierFaceCounts; + ColldierFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, ColldierFaceCounts.GetData(), 0, ColldierFaceCounts.Num()), false); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), ColliderNodeId), false); + + OutNodeId = ColliderNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + const int32 & NodeId, + const int32 & PartId, + const int32 & Count, + const TArray & TriangleMaterials, + const TMap> & ScalarMaterialParameters, + const TMap> & VectorMaterialParameters, + const TMap> & TextureMaterialParameters) +{ + if (NodeId < 0) + return false; + + bool bSuccess = true; + + // Create attribute for materials. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.count = Count; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial, + (const char **)TriangleMaterials.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + + // Add scalar material parameter attributes + for (auto & Pair : ScalarMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 1; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + Pair.Value.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + // Add vector material parameters + for (auto & Pair : VectorMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 4; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + Pair.Value.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + // Add texture material parameter attributes + for (auto & Pair : TextureMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 1; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // Replace null strings by empty strings to prevent crashes when setting the attribute. + char* EmptyString = nullptr; + TArray StringData = Pair.Value; + for (auto& CurValue : StringData) + { + if (CurValue != nullptr) + continue; + + if (!EmptyString) + { + // Allocate the empty string the first time it is needed. This is free'd along with + // the other strings in the arrays in TextureMaterialParameters by calls to DeleteFaceMaterialArray + EmptyString = FHoudiniEngineUtils::ExtractRawString(FString(TEXT(""))); + } + CurValue = EmptyString; + } + + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + (const char **)StringData.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + return bSuccess; +} + +/* +bool +FUnrealMeshTranslator::AddLevelPathAttributeToMesh( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const FString& LevelPath, + const int32& Count) +{ + if (NodeId == -1 || LevelPath.IsEmpty() || Count <= 0) + return false; + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = Count; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Count); + for (int32 Idx = 0; Idx < Count; ++Idx) + { + PrimitiveAttrs[Idx] = LevelPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} */ \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.h b/Source/HoudiniEngine/Private/UnrealMeshTranslator.h index e39998d1e..42a4c7243 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.h @@ -1,164 +1,164 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -class UStaticMesh; -class UStaticMeshComponent; -class UMaterialInterface; -class UStaticMeshSocket; - -struct FStaticMeshSourceModel; -struct FStaticMeshLODResources; -struct FMeshDescription; -struct FKConvexElem; - -struct HOUDINIENGINE_API FUnrealMeshTranslator -{ - public: - - // HAPI : Marshaling, extract geometry and create input asset for it - return true on success - static bool HapiCreateInputNodeForStaticMesh( - UStaticMesh * Mesh, - HAPI_NodeId& InputObjectNodeId, - const FString& InputNodeName, - class UStaticMeshComponent* StaticMeshComponent = nullptr, - const bool& ExportAllLODs = false, - const bool& ExportSockets = false, - const bool& ExportColliders = false); - - // Convert the Mesh using FStaticMeshLODResources - static bool CreateInputNodeForStaticMeshLODResources( - const HAPI_NodeId& NodeId, - const FStaticMeshLODResources& LODResources, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - // Convert the Mesh using FMeshDescription - static bool CreateInputNodeForMeshDescription( - const HAPI_NodeId& NodeId, - const FMeshDescription& MeshDescription, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - // Convert the Mesh using FRawMesh - - static bool CreateInputNodeForRawMesh( - const HAPI_NodeId& NodeId, - const FStaticMeshSourceModel& SourceModel, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - static bool CreateInputNodeForBox( - HAPI_NodeId& OutBoxNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& BoxCenter, - const FVector& BoxExtent, - const FRotator& BoxRotation); - - static bool CreateInputNodeForSphere( - HAPI_NodeId& OutSphereNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphereCenter, - const float& SphereRadius); - - static bool CreateInputNodeForSphyl( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphylCenter, - const FRotator& SphylRotation, - const float& SphylRadius, - const float& SphereLength); - - static bool CreateInputNodeForConvex( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FKConvexElem& ConvexCollider); - - static bool CreateInputNodeForCollider( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FString& ColliderName, - const TArray& ColliderVertices, - const TArray& ColliderIndices); - - static bool CreateInputNodeForMeshSockets( - const TArray& InMeshSocket, - const HAPI_NodeId& InParentNodeId, - HAPI_NodeId& OutSocketsNodeId); - - // Create helper array of material names, used for marshalling static mesh's materials. - // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() - static void CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray & OutStaticMeshFaceMaterials, - TMap> & OutScalarMaterialParameters, - TMap> & OutVectorMaterialParameters, - TMap> & OutTextureMaterialParameters); - - // Delete helper array of material names. - // Clean up the memory allocated by CreateFaceMaterialArray() - static void DeleteFaceMaterialArray(TArray & OutStaticMeshFaceMaterials); - - // Create and set mesh material attribute and material (scalar, vector and texture) parameters attributes - static bool CreateHoudiniMeshAttributes( - const int32 & NodeId, - const int32 & PartId, - const int32 & Count, - const TArray & TriangleMaterials, - const TMap> & ScalarMaterialParameters, - const TMap> & VectorMaterialParameters, - const TMap> & TextureMaterialParameters); - - /* - // Creates the unreal_level_path attribute on the input mesh - static bool AddLevelPathAttributeToMesh( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const FString& LevelPath, - const int32& Count); - */ - - private: - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +class UStaticMesh; +class UStaticMeshComponent; +class UMaterialInterface; +class UStaticMeshSocket; + +struct FStaticMeshSourceModel; +struct FStaticMeshLODResources; +struct FMeshDescription; +struct FKConvexElem; + +struct HOUDINIENGINE_API FUnrealMeshTranslator +{ + public: + + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForStaticMesh( + UStaticMesh * Mesh, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + class UStaticMeshComponent* StaticMeshComponent = nullptr, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false, + const bool& ExportColliders = false); + + // Convert the Mesh using FStaticMeshLODResources + static bool CreateInputNodeForStaticMeshLODResources( + const HAPI_NodeId& NodeId, + const FStaticMeshLODResources& LODResources, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + // Convert the Mesh using FMeshDescription + static bool CreateInputNodeForMeshDescription( + const HAPI_NodeId& NodeId, + const FMeshDescription& MeshDescription, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + // Convert the Mesh using FRawMesh + + static bool CreateInputNodeForRawMesh( + const HAPI_NodeId& NodeId, + const FStaticMeshSourceModel& SourceModel, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + static bool CreateInputNodeForBox( + HAPI_NodeId& OutBoxNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& BoxCenter, + const FVector& BoxExtent, + const FRotator& BoxRotation); + + static bool CreateInputNodeForSphere( + HAPI_NodeId& OutSphereNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphereCenter, + const float& SphereRadius); + + static bool CreateInputNodeForSphyl( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphylCenter, + const FRotator& SphylRotation, + const float& SphylRadius, + const float& SphereLength); + + static bool CreateInputNodeForConvex( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FKConvexElem& ConvexCollider); + + static bool CreateInputNodeForCollider( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FString& ColliderName, + const TArray& ColliderVertices, + const TArray& ColliderIndices); + + static bool CreateInputNodeForMeshSockets( + const TArray& InMeshSocket, + const HAPI_NodeId& InParentNodeId, + HAPI_NodeId& OutSocketsNodeId); + + // Create helper array of material names, used for marshalling static mesh's materials. + // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() + static void CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray & OutStaticMeshFaceMaterials, + TMap> & OutScalarMaterialParameters, + TMap> & OutVectorMaterialParameters, + TMap> & OutTextureMaterialParameters); + + // Delete helper array of material names. + // Clean up the memory allocated by CreateFaceMaterialArray() + static void DeleteFaceMaterialArray(TArray & OutStaticMeshFaceMaterials); + + // Create and set mesh material attribute and material (scalar, vector and texture) parameters attributes + static bool CreateHoudiniMeshAttributes( + const int32 & NodeId, + const int32 & PartId, + const int32 & Count, + const TArray & TriangleMaterials, + const TMap> & ScalarMaterialParameters, + const TMap> & VectorMaterialParameters, + const TMap> & TextureMaterialParameters); + + /* + // Creates the unreal_level_path attribute on the input mesh + static bool AddLevelPathAttributeToMesh( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const FString& LevelPath, + const int32& Count); + */ + + private: + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp index cc6f857bb..0cc61dd0d 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp @@ -1,123 +1,123 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#include "UnrealSplineTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "Components/SplineComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniSplineTranslator.h" - -bool -FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId& CreatedInputNodeId, const FString& NodeName) -{ - if (!SplineComponent || SplineComponent->IsPendingKill()) - return false; - - int32 NumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); - float SplineLength = SplineComponent->GetSplineLength(); - - // Calculate the number of refined point we want - int32 NumberOfRefinedSplinePoints = SplineResolution > 0.0f ? ceil(SplineLength / SplineResolution) + 1 : NumberOfControlPoints; - - TArray RefinedSplinePositions; - TArray RefinedSplineRotations; - TArray RefinedSplineScales; - - if (NumberOfRefinedSplinePoints <= NumberOfControlPoints) - { - // There's not enough refined points, so we'll use the control points instead - RefinedSplinePositions.SetNumZeroed(NumberOfControlPoints); - RefinedSplineRotations.SetNumZeroed(NumberOfControlPoints); - RefinedSplineScales.SetNumZeroed(NumberOfControlPoints); - - for (int32 n = 0; n < NumberOfControlPoints; ++n) - { - RefinedSplinePositions[n] = SplineComponent->GetLocationAtSplinePoint(n, ESplineCoordinateSpace::Local); - RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World); - RefinedSplineScales[n] = SplineComponent->GetScaleAtSplinePoint(n); - } - } - else - { - // Calculate the refined spline component - RefinedSplinePositions.SetNumZeroed(NumberOfRefinedSplinePoints); - RefinedSplineRotations.SetNumZeroed(NumberOfRefinedSplinePoints); - RefinedSplineScales.SetNumZeroed(NumberOfRefinedSplinePoints); - - float CurrentDistance = 0.0f; - for (int32 n = 0; n < NumberOfRefinedSplinePoints; ++n) - { - RefinedSplinePositions[n] = SplineComponent->GetLocationAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::Local); - RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::World); - RefinedSplineScales[n] = SplineComponent->GetScaleAtDistanceAlongSpline(CurrentDistance); - - CurrentDistance += SplineResolution; - } - } - - - if (!FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData(CreatedInputNodeId, NodeName, - &RefinedSplinePositions, &RefinedSplineRotations, &RefinedSplineScales, - EHoudiniCurveType::Polygon, EHoudiniCurveMethod::Breakpoints, false, SplineComponent->IsClosedLoop())) - return false; - - // Add spline component tags if it has any - bool NeedToCommit = FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, SplineComponent->ComponentTags); - - // Add the parent actor's tag if it has any - AActor* ParentActor = SplineComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - if (FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, ParentActor->Tags)) - NeedToCommit = true; - - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - FHoudiniApi::GetPartInfo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId, 0, &PartInfo); - - // Add the unreal_actor_path attribute - if(FHoudiniEngineUtils::AddActorPathAttribute(CreatedInputNodeId, 0, ParentActor, PartInfo.faceCount)) - NeedToCommit = true; - - // Add the unreal_level_path attribute - if(FHoudiniEngineUtils::AddLevelPathAttribute(CreatedInputNodeId, 0, ParentActor->GetLevel(), PartInfo.faceCount)) - NeedToCommit = true; - } - - if (NeedToCommit) - { - // We successfully added tags to the geo, so we need to commit the changes - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups for the spline input's tags!")); - } - - - return true; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "UnrealSplineTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "Components/SplineComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniSplineTranslator.h" + +bool +FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId& CreatedInputNodeId, const FString& NodeName) +{ + if (!SplineComponent || SplineComponent->IsPendingKill()) + return false; + + int32 NumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); + float SplineLength = SplineComponent->GetSplineLength(); + + // Calculate the number of refined point we want + int32 NumberOfRefinedSplinePoints = SplineResolution > 0.0f ? ceil(SplineLength / SplineResolution) + 1 : NumberOfControlPoints; + + TArray RefinedSplinePositions; + TArray RefinedSplineRotations; + TArray RefinedSplineScales; + + if (NumberOfRefinedSplinePoints <= NumberOfControlPoints) + { + // There's not enough refined points, so we'll use the control points instead + RefinedSplinePositions.SetNumZeroed(NumberOfControlPoints); + RefinedSplineRotations.SetNumZeroed(NumberOfControlPoints); + RefinedSplineScales.SetNumZeroed(NumberOfControlPoints); + + for (int32 n = 0; n < NumberOfControlPoints; ++n) + { + RefinedSplinePositions[n] = SplineComponent->GetLocationAtSplinePoint(n, ESplineCoordinateSpace::Local); + RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World); + RefinedSplineScales[n] = SplineComponent->GetScaleAtSplinePoint(n); + } + } + else + { + // Calculate the refined spline component + RefinedSplinePositions.SetNumZeroed(NumberOfRefinedSplinePoints); + RefinedSplineRotations.SetNumZeroed(NumberOfRefinedSplinePoints); + RefinedSplineScales.SetNumZeroed(NumberOfRefinedSplinePoints); + + float CurrentDistance = 0.0f; + for (int32 n = 0; n < NumberOfRefinedSplinePoints; ++n) + { + RefinedSplinePositions[n] = SplineComponent->GetLocationAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::Local); + RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::World); + RefinedSplineScales[n] = SplineComponent->GetScaleAtDistanceAlongSpline(CurrentDistance); + + CurrentDistance += SplineResolution; + } + } + + + if (!FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData(CreatedInputNodeId, NodeName, + &RefinedSplinePositions, &RefinedSplineRotations, &RefinedSplineScales, + EHoudiniCurveType::Polygon, EHoudiniCurveMethod::Breakpoints, false, SplineComponent->IsClosedLoop())) + return false; + + // Add spline component tags if it has any + bool NeedToCommit = FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, SplineComponent->ComponentTags); + + // Add the parent actor's tag if it has any + AActor* ParentActor = SplineComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + if (FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, ParentActor->Tags)) + NeedToCommit = true; + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + FHoudiniApi::GetPartInfo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId, 0, &PartInfo); + + // Add the unreal_actor_path attribute + if(FHoudiniEngineUtils::AddActorPathAttribute(CreatedInputNodeId, 0, ParentActor, PartInfo.faceCount)) + NeedToCommit = true; + + // Add the unreal_level_path attribute + if(FHoudiniEngineUtils::AddLevelPathAttribute(CreatedInputNodeId, 0, ParentActor->GetLevel(), PartInfo.faceCount)) + NeedToCommit = true; + } + + if (NeedToCommit) + { + // We successfully added tags to the geo, so we need to commit the changes + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups for the spline input's tags!")); + } + + + return true; +} diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.h b/Source/HoudiniEngine/Private/UnrealSplineTranslator.h index 826bc9ba2..aef0858d9 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.h @@ -1,39 +1,39 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "UObject/NameTypes.h" - -class USplineComponent; - -struct HOUDINIENGINE_API FUnrealSplineTranslator -{ -public: - static bool CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId &CreatedInputNodeId, const FString& NodeName); - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "UObject/NameTypes.h" + +class USplineComponent; + +struct HOUDINIENGINE_API FUnrealSplineTranslator +{ +public: + static bool CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId &CreatedInputNodeId, const FString& NodeName); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Public/HoudiniApi.h b/Source/HoudiniEngine/Public/HoudiniApi.h index c8e782c63..46383cfa7 100644 --- a/Source/HoudiniEngine/Public/HoudiniApi.h +++ b/Source/HoudiniEngine/Public/HoudiniApi.h @@ -1,977 +1,977 @@ -/* - * Copyright (c) <2020> 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. - * - * - * COMMENTS: - * This file is generated. Do not modify directly. - */ - -#pragma once -#include "HAPI/HAPI.h" -#include "HAL/PlatformProcess.h" - - -struct HOUDINIENGINE_API FHoudiniApi -{ -public: - - static void InitializeHAPI(void* LibraryHandle); - static void FinalizeHAPI(); - static bool IsHAPIInitialized(); - -public: - - typedef HAPI_Result (*AddAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*AddGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - typedef HAPI_AssetInfo (*AssetInfo_CreateFuncPtr)(); - typedef void (*AssetInfo_InitFuncPtr)(HAPI_AssetInfo * in); - typedef HAPI_AttributeInfo (*AttributeInfo_CreateFuncPtr)(); - typedef void (*AttributeInfo_InitFuncPtr)(HAPI_AttributeInfo * in); - typedef HAPI_Result (*BindCustomImplementationFuncPtr)(HAPI_SessionType session_type, const char * dll_path); - typedef HAPI_Result (*CancelPDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - typedef HAPI_Result (*CheckForSpecificErrorsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); - typedef HAPI_Result (*CleanupFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*ClearConnectionErrorFuncPtr)(); - typedef HAPI_Result (*CloseSessionFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*CommitGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*CommitWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*ComposeChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - typedef HAPI_Result (*ComposeNodeCookResultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); - typedef HAPI_Result (*ComposeObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); - typedef HAPI_Result (*ConnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); - typedef HAPI_Result (*ConvertMatrixToEulerFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - typedef HAPI_Result (*ConvertMatrixToQuatFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); - typedef HAPI_Result (*ConvertTransformFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - typedef HAPI_Result (*ConvertTransformEulerToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); - typedef HAPI_Result (*ConvertTransformQuatToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); - typedef HAPI_Result (*CookNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); - typedef HAPI_Bool (*CookOptions_AreEqualFuncPtr)(const HAPI_CookOptions * left, const HAPI_CookOptions * right); - typedef HAPI_CookOptions (*CookOptions_CreateFuncPtr)(); - typedef void (*CookOptions_InitFuncPtr)(HAPI_CookOptions * in); - typedef HAPI_Result (*CookPDGFuncPtr)(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); - typedef HAPI_Result (*CreateCustomSessionFuncPtr)(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); - typedef HAPI_Result (*CreateHeightFieldInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); - typedef HAPI_Result (*CreateHeightfieldInputVolumeNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); - typedef HAPI_Result (*CreateInProcessSessionFuncPtr)(HAPI_Session * session); - typedef HAPI_Result (*CreateInputNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); - typedef HAPI_Result (*CreateNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); - typedef HAPI_Result (*CreateThriftNamedPipeSessionFuncPtr)(HAPI_Session * session, const char * pipe_name); - typedef HAPI_Result (*CreateThriftSocketSessionFuncPtr)(HAPI_Session * session, const char * host_name, int port); - typedef HAPI_Result (*CreateWorkitemFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); - typedef HAPI_CurveInfo (*CurveInfo_CreateFuncPtr)(); - typedef void (*CurveInfo_InitFuncPtr)(HAPI_CurveInfo * in); - typedef HAPI_Result (*DeleteAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*DeleteGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - typedef HAPI_Result (*DeleteNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*DirtyPDGNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); - typedef HAPI_Result (*DisconnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); - typedef HAPI_Result (*DisconnectNodeOutputsAtFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); - typedef HAPI_Result (*ExtractImageToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); - typedef HAPI_Result (*ExtractImageToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); - typedef HAPI_GeoInfo (*GeoInfo_CreateFuncPtr)(); - typedef int (*GeoInfo_GetGroupCountByTypeFuncPtr)(HAPI_GeoInfo * in, HAPI_GroupType type); - typedef void (*GeoInfo_InitFuncPtr)(HAPI_GeoInfo * in); - typedef HAPI_Result (*GetActiveCacheCountFuncPtr)(const HAPI_Session * session, int * active_cache_count); - typedef HAPI_Result (*GetActiveCacheNamesFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); - typedef HAPI_Result (*GetAssetDefinitionParmCountsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); - typedef HAPI_Result (*GetAssetDefinitionParmInfosFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); - typedef HAPI_Result (*GetAssetDefinitionParmValuesFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); - typedef HAPI_Result (*GetAssetInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); - typedef HAPI_Result (*GetAttributeFloat64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeFloatArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*GetAttributeInt64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeIntArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); - typedef HAPI_Result (*GetAttributeStringArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); - typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); - typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); - typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); - typedef HAPI_Result (*GetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); - typedef HAPI_Result (*GetComposedChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); - typedef HAPI_Result (*GetComposedNodeCookResultFuncPtr)(const HAPI_Session * session, char * string_value, int length); - typedef HAPI_Result (*GetComposedObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); - typedef HAPI_Result (*GetComposedObjectTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); - typedef HAPI_Result (*GetConnectionErrorFuncPtr)(char * string_value, int length, HAPI_Bool clear); - typedef HAPI_Result (*GetConnectionErrorLengthFuncPtr)(int * buffer_length); - typedef HAPI_Result (*GetCookingCurrentCountFuncPtr)(const HAPI_Session * session, int * count); - typedef HAPI_Result (*GetCookingTotalCountFuncPtr)(const HAPI_Session * session, int * count); - typedef HAPI_Result (*GetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); - typedef HAPI_Result (*GetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); - typedef HAPI_Result (*GetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); - typedef HAPI_Result (*GetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); - typedef HAPI_Result (*GetDisplayGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); - typedef HAPI_Result (*GetEnvIntFuncPtr)(HAPI_EnvIntType int_type, int * value); - typedef HAPI_Result (*GetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); - typedef HAPI_Result (*GetFirstVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - typedef HAPI_Result (*GetGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); - typedef HAPI_Result (*GetGeoSizeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); - typedef HAPI_Result (*GetGroupCountOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); - typedef HAPI_Result (*GetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - typedef HAPI_Result (*GetGroupMembershipOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - typedef HAPI_Result (*GetGroupNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - typedef HAPI_Result (*GetGroupNamesOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - typedef HAPI_Result (*GetHIPFileNodeCountFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, int * count); - typedef HAPI_Result (*GetHIPFileNodeIdsFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); - typedef HAPI_Result (*GetHandleBindingInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); - typedef HAPI_Result (*GetHandleInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); - typedef HAPI_Result (*GetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); - typedef HAPI_Result (*GetImageFilePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); - typedef HAPI_Result (*GetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); - typedef HAPI_Result (*GetImageMemoryBufferFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); - typedef HAPI_Result (*GetImagePlaneCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); - typedef HAPI_Result (*GetImagePlanesFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); - typedef HAPI_Result (*GetInstanceTransformsOnPartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - typedef HAPI_Result (*GetInstancedObjectIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); - typedef HAPI_Result (*GetInstancedPartIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); - typedef HAPI_Result (*GetInstancerPartTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - typedef HAPI_Result (*GetManagerNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); - typedef HAPI_Result (*GetMaterialInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); - typedef HAPI_Result (*GetMaterialNodeIdsOnFacesFuncPtr)(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); - typedef HAPI_Result (*GetNextVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - typedef HAPI_Result (*GetNodeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); - typedef HAPI_Result (*GetNodeInputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); - typedef HAPI_Result (*GetNodeOutputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); - typedef HAPI_Result (*GetNodePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); - typedef HAPI_Result (*GetNumWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * num); - typedef HAPI_Result (*GetObjectInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); - typedef HAPI_Result (*GetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); - typedef HAPI_Result (*GetOutputNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); - typedef HAPI_Result (*GetPDGEventsFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); - typedef HAPI_Result (*GetPDGGraphContextIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); - typedef HAPI_Result (*GetPDGGraphContextsFuncPtr)(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); - typedef HAPI_Result (*GetPDGStateFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); - typedef HAPI_Result (*GetParametersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); - typedef HAPI_Result (*GetParmChoiceListsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); - typedef HAPI_Result (*GetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); - typedef HAPI_Result (*GetParmFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); - typedef HAPI_Result (*GetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); - typedef HAPI_Result (*GetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); - typedef HAPI_Result (*GetParmIdFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); - typedef HAPI_Result (*GetParmInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); - typedef HAPI_Result (*GetParmInfoFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); - typedef HAPI_Result (*GetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); - typedef HAPI_Result (*GetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); - typedef HAPI_Result (*GetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); - typedef HAPI_Result (*GetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); - typedef HAPI_Result (*GetParmStringValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); - typedef HAPI_Result (*GetParmTagNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); - typedef HAPI_Result (*GetParmTagValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); - typedef HAPI_Result (*GetParmWithTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); - typedef HAPI_Result (*GetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); - typedef HAPI_Result (*GetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); - typedef HAPI_Result (*GetPresetBufLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); - typedef HAPI_Result (*GetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int * value); - typedef HAPI_Result (*GetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); - typedef HAPI_Result (*GetServerEnvVarCountFuncPtr)(const HAPI_Session * session, int * env_count); - typedef HAPI_Result (*GetServerEnvVarListFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); - typedef HAPI_Result (*GetSessionEnvIntFuncPtr)(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); - typedef HAPI_Result (*GetSessionSyncInfoFuncPtr)(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); - typedef HAPI_Result (*GetSphereInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); - typedef HAPI_Result (*GetStatusFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, int * status); - typedef HAPI_Result (*GetStatusStringFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); - typedef HAPI_Result (*GetStatusStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); - typedef HAPI_Result (*GetStringFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); - typedef HAPI_Result (*GetStringBatchFuncPtr)(const HAPI_Session * session, char * char_buffer, int char_array_length); - typedef HAPI_Result (*GetStringBatchSizeFuncPtr)(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); - typedef HAPI_Result (*GetStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); - typedef HAPI_Result (*GetSupportedImageFileFormatCountFuncPtr)(const HAPI_Session * session, int * file_format_count); - typedef HAPI_Result (*GetSupportedImageFileFormatsFuncPtr)(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); - typedef HAPI_Result (*GetTimeFuncPtr)(const HAPI_Session * session, float * time); - typedef HAPI_Result (*GetTimelineOptionsFuncPtr)(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); - typedef HAPI_Result (*GetTotalCookCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - typedef HAPI_Result (*GetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool * enabled); - typedef HAPI_Result (*GetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); - typedef HAPI_Result (*GetViewportFuncPtr)(const HAPI_Session * session, HAPI_Viewport * viewport); - typedef HAPI_Result (*GetVolumeBoundsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); - typedef HAPI_Result (*GetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); - typedef HAPI_Result (*GetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); - typedef HAPI_Result (*GetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); - typedef HAPI_Result (*GetVolumeVisualInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); - typedef HAPI_Result (*GetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); - typedef HAPI_Result (*GetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); - typedef HAPI_Result (*GetWorkitemDataLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); - typedef HAPI_Result (*GetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); - typedef HAPI_Result (*GetWorkitemInfoFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); - typedef HAPI_Result (*GetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); - typedef HAPI_Result (*GetWorkitemResultInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); - typedef HAPI_Result (*GetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); - typedef HAPI_Result (*GetWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); - typedef HAPI_HandleBindingInfo (*HandleBindingInfo_CreateFuncPtr)(); - typedef void (*HandleBindingInfo_InitFuncPtr)(HAPI_HandleBindingInfo * in); - typedef HAPI_HandleInfo (*HandleInfo_CreateFuncPtr)(); - typedef void (*HandleInfo_InitFuncPtr)(HAPI_HandleInfo * in); - typedef HAPI_ImageFileFormat (*ImageFileFormat_CreateFuncPtr)(); - typedef void (*ImageFileFormat_InitFuncPtr)(HAPI_ImageFileFormat *in); - typedef HAPI_ImageInfo (*ImageInfo_CreateFuncPtr)(); - typedef void (*ImageInfo_InitFuncPtr)(HAPI_ImageInfo * in); - typedef HAPI_Result (*InitializeFuncPtr)(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); - typedef HAPI_Result (*InsertMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - typedef HAPI_Result (*InterruptFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*IsInitializedFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*IsNodeValidFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); - typedef HAPI_Result (*IsSessionValidFuncPtr)(const HAPI_Session * session); - typedef HAPI_Keyframe (*Keyframe_CreateFuncPtr)(); - typedef void (*Keyframe_InitFuncPtr)(HAPI_Keyframe * in); - typedef HAPI_Result (*LoadAssetLibraryFromFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - typedef HAPI_Result (*LoadAssetLibraryFromMemoryFuncPtr)(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - typedef HAPI_Result (*LoadGeoFromFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_Result (*LoadGeoFromMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); - typedef HAPI_Result (*LoadHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); - typedef HAPI_Result (*LoadNodeFromFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); - typedef HAPI_MaterialInfo (*MaterialInfo_CreateFuncPtr)(); - typedef void (*MaterialInfo_InitFuncPtr)(HAPI_MaterialInfo * in); - typedef HAPI_Result (*MergeHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); - typedef HAPI_NodeInfo (*NodeInfo_CreateFuncPtr)(); - typedef void (*NodeInfo_InitFuncPtr)(HAPI_NodeInfo * in); - typedef HAPI_ObjectInfo (*ObjectInfo_CreateFuncPtr)(); - typedef void (*ObjectInfo_InitFuncPtr)(HAPI_ObjectInfo * in); - typedef HAPI_ParmChoiceInfo (*ParmChoiceInfo_CreateFuncPtr)(); - typedef void (*ParmChoiceInfo_InitFuncPtr)(HAPI_ParmChoiceInfo * in); - typedef HAPI_Result (*ParmHasExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); - typedef HAPI_Result (*ParmHasTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); - typedef HAPI_ParmInfo (*ParmInfo_CreateFuncPtr)(); - typedef int (*ParmInfo_GetFloatValueCountFuncPtr)(const HAPI_ParmInfo * in); - typedef int (*ParmInfo_GetIntValueCountFuncPtr)(const HAPI_ParmInfo * in); - typedef int (*ParmInfo_GetStringValueCountFuncPtr)(const HAPI_ParmInfo* in); - typedef void (*ParmInfo_InitFuncPtr)(HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsFloatFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsIntFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsNodeFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsNonValueFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsPathFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsStringFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_PartInfo (*PartInfo_CreateFuncPtr)(); - typedef int (*PartInfo_GetAttributeCountByOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - typedef int (*PartInfo_GetElementCountByAttributeOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - typedef int (*PartInfo_GetElementCountByGroupTypeFuncPtr)(HAPI_PartInfo * in, HAPI_GroupType type); - typedef void (*PartInfo_InitFuncPtr)(HAPI_PartInfo * in); - typedef HAPI_Result (*PausePDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - typedef HAPI_Result (*PythonThreadInterpreterLockFuncPtr)(const HAPI_Session * session, HAPI_Bool locked); - typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); - typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); - typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const int string_handle); - typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); - typedef HAPI_Result (*RenderCOPToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId cop_node_id); - typedef HAPI_Result (*RenderTextureToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); - typedef HAPI_Result (*ResetSimulationFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*RevertGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*RevertParmToDefaultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); - typedef HAPI_Result (*RevertParmToDefaultsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); - typedef HAPI_Result (*SaveGeoToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_Result (*SaveGeoToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); - typedef HAPI_Result (*SaveHIPFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); - typedef HAPI_Result (*SaveNodeToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_SessionSyncInfo (*SessionSyncInfo_CreateFuncPtr)(); - typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); - typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); - typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); - typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); - typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); - typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, int * handle_value); - typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); - typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); - typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); - typedef HAPI_Result (*SetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); - typedef HAPI_Result (*SetNodeDisplayFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); - typedef HAPI_Result (*SetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); - typedef HAPI_Result (*SetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*SetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); - typedef HAPI_Result (*SetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); - typedef HAPI_Result (*SetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); - typedef HAPI_Result (*SetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); - typedef HAPI_Result (*SetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); - typedef HAPI_Result (*SetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*SetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); - typedef HAPI_Result (*SetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); - typedef HAPI_Result (*SetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int value); - typedef HAPI_Result (*SetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, const char * value); - typedef HAPI_Result (*SetSessionSyncFuncPtr)(const HAPI_Session * session, HAPI_Bool enable); - typedef HAPI_Result (*SetSessionSyncInfoFuncPtr)(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); - typedef HAPI_Result (*SetTimeFuncPtr)(const HAPI_Session * session, float time); - typedef HAPI_Result (*SetTimelineOptionsFuncPtr)(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); - typedef HAPI_Result (*SetTransformAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - typedef HAPI_Result (*SetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool enabled); - typedef HAPI_Result (*SetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); - typedef HAPI_Result (*SetViewportFuncPtr)(const HAPI_Session * session, const HAPI_Viewport * viewport); - typedef HAPI_Result (*SetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); - typedef HAPI_Result (*SetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); - typedef HAPI_Result (*SetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); - typedef HAPI_Result (*SetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); - typedef HAPI_Result (*SetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); - typedef HAPI_Result (*SetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); - typedef HAPI_Result (*SetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); - typedef HAPI_Result (*SetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); - typedef HAPI_Result (*StartThriftNamedPipeServerFuncPtr)(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); - typedef HAPI_Result (*StartThriftSocketServerFuncPtr)(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); - typedef HAPI_ThriftServerOptions (*ThriftServerOptions_CreateFuncPtr)(); - typedef void (*ThriftServerOptions_InitFuncPtr)(HAPI_ThriftServerOptions * in); - typedef HAPI_TimelineOptions (*TimelineOptions_CreateFuncPtr)(); - typedef void (*TimelineOptions_InitFuncPtr)(HAPI_TimelineOptions * in); - typedef HAPI_TransformEuler (*TransformEuler_CreateFuncPtr)(); - typedef void (*TransformEuler_InitFuncPtr)(HAPI_TransformEuler * in); - typedef HAPI_Transform (*Transform_CreateFuncPtr)(); - typedef void (*Transform_InitFuncPtr)(HAPI_Transform * in); - typedef HAPI_Viewport (*Viewport_CreateFuncPtr)(); - typedef HAPI_VolumeInfo (*VolumeInfo_CreateFuncPtr)(); - typedef void (*VolumeInfo_InitFuncPtr)(HAPI_VolumeInfo * in); - typedef HAPI_VolumeTileInfo (*VolumeTileInfo_CreateFuncPtr)(); - typedef void (*VolumeTileInfo_InitFuncPtr)(HAPI_VolumeTileInfo * in); - -public: - - static AddAttributeFuncPtr AddAttribute; - static AddGroupFuncPtr AddGroup; - static AssetInfo_CreateFuncPtr AssetInfo_Create; - static AssetInfo_InitFuncPtr AssetInfo_Init; - static AttributeInfo_CreateFuncPtr AttributeInfo_Create; - static AttributeInfo_InitFuncPtr AttributeInfo_Init; - static BindCustomImplementationFuncPtr BindCustomImplementation; - static CancelPDGCookFuncPtr CancelPDGCook; - static CheckForSpecificErrorsFuncPtr CheckForSpecificErrors; - static CleanupFuncPtr Cleanup; - static ClearConnectionErrorFuncPtr ClearConnectionError; - static CloseSessionFuncPtr CloseSession; - static CommitGeoFuncPtr CommitGeo; - static CommitWorkitemsFuncPtr CommitWorkitems; - static ComposeChildNodeListFuncPtr ComposeChildNodeList; - static ComposeNodeCookResultFuncPtr ComposeNodeCookResult; - static ComposeObjectListFuncPtr ComposeObjectList; - static ConnectNodeInputFuncPtr ConnectNodeInput; - static ConvertMatrixToEulerFuncPtr ConvertMatrixToEuler; - static ConvertMatrixToQuatFuncPtr ConvertMatrixToQuat; - static ConvertTransformFuncPtr ConvertTransform; - static ConvertTransformEulerToMatrixFuncPtr ConvertTransformEulerToMatrix; - static ConvertTransformQuatToMatrixFuncPtr ConvertTransformQuatToMatrix; - static CookNodeFuncPtr CookNode; - static CookOptions_AreEqualFuncPtr CookOptions_AreEqual; - static CookOptions_CreateFuncPtr CookOptions_Create; - static CookOptions_InitFuncPtr CookOptions_Init; - static CookPDGFuncPtr CookPDG; - static CreateCustomSessionFuncPtr CreateCustomSession; - static CreateHeightFieldInputFuncPtr CreateHeightFieldInput; - static CreateHeightfieldInputVolumeNodeFuncPtr CreateHeightfieldInputVolumeNode; - static CreateInProcessSessionFuncPtr CreateInProcessSession; - static CreateInputNodeFuncPtr CreateInputNode; - static CreateNodeFuncPtr CreateNode; - static CreateThriftNamedPipeSessionFuncPtr CreateThriftNamedPipeSession; - static CreateThriftSocketSessionFuncPtr CreateThriftSocketSession; - static CreateWorkitemFuncPtr CreateWorkitem; - static CurveInfo_CreateFuncPtr CurveInfo_Create; - static CurveInfo_InitFuncPtr CurveInfo_Init; - static DeleteAttributeFuncPtr DeleteAttribute; - static DeleteGroupFuncPtr DeleteGroup; - static DeleteNodeFuncPtr DeleteNode; - static DirtyPDGNodeFuncPtr DirtyPDGNode; - static DisconnectNodeInputFuncPtr DisconnectNodeInput; - static DisconnectNodeOutputsAtFuncPtr DisconnectNodeOutputsAt; - static ExtractImageToFileFuncPtr ExtractImageToFile; - static ExtractImageToMemoryFuncPtr ExtractImageToMemory; - static GeoInfo_CreateFuncPtr GeoInfo_Create; - static GeoInfo_GetGroupCountByTypeFuncPtr GeoInfo_GetGroupCountByType; - static GeoInfo_InitFuncPtr GeoInfo_Init; - static GetActiveCacheCountFuncPtr GetActiveCacheCount; - static GetActiveCacheNamesFuncPtr GetActiveCacheNames; - static GetAssetDefinitionParmCountsFuncPtr GetAssetDefinitionParmCounts; - static GetAssetDefinitionParmInfosFuncPtr GetAssetDefinitionParmInfos; - static GetAssetDefinitionParmValuesFuncPtr GetAssetDefinitionParmValues; - static GetAssetInfoFuncPtr GetAssetInfo; - static GetAttributeFloat64ArrayDataFuncPtr GetAttributeFloat64ArrayData; - static GetAttributeFloat64DataFuncPtr GetAttributeFloat64Data; - static GetAttributeFloatArrayDataFuncPtr GetAttributeFloatArrayData; - static GetAttributeFloatDataFuncPtr GetAttributeFloatData; - static GetAttributeInfoFuncPtr GetAttributeInfo; - static GetAttributeInt64ArrayDataFuncPtr GetAttributeInt64ArrayData; - static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; - static GetAttributeIntArrayDataFuncPtr GetAttributeIntArrayData; - static GetAttributeIntDataFuncPtr GetAttributeIntData; - static GetAttributeNamesFuncPtr GetAttributeNames; - static GetAttributeStringArrayDataFuncPtr GetAttributeStringArrayData; - static GetAttributeStringDataFuncPtr GetAttributeStringData; - static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; - static GetAvailableAssetsFuncPtr GetAvailableAssets; - static GetBoxInfoFuncPtr GetBoxInfo; - static GetCachePropertyFuncPtr GetCacheProperty; - static GetComposedChildNodeListFuncPtr GetComposedChildNodeList; - static GetComposedNodeCookResultFuncPtr GetComposedNodeCookResult; - static GetComposedObjectListFuncPtr GetComposedObjectList; - static GetComposedObjectTransformsFuncPtr GetComposedObjectTransforms; - static GetConnectionErrorFuncPtr GetConnectionError; - static GetConnectionErrorLengthFuncPtr GetConnectionErrorLength; - static GetCookingCurrentCountFuncPtr GetCookingCurrentCount; - static GetCookingTotalCountFuncPtr GetCookingTotalCount; - static GetCurveCountsFuncPtr GetCurveCounts; - static GetCurveInfoFuncPtr GetCurveInfo; - static GetCurveKnotsFuncPtr GetCurveKnots; - static GetCurveOrdersFuncPtr GetCurveOrders; - static GetDisplayGeoInfoFuncPtr GetDisplayGeoInfo; - static GetEnvIntFuncPtr GetEnvInt; - static GetFaceCountsFuncPtr GetFaceCounts; - static GetFirstVolumeTileFuncPtr GetFirstVolumeTile; - static GetGeoInfoFuncPtr GetGeoInfo; - static GetGeoSizeFuncPtr GetGeoSize; - static GetGroupCountOnPackedInstancePartFuncPtr GetGroupCountOnPackedInstancePart; - static GetGroupMembershipFuncPtr GetGroupMembership; - static GetGroupMembershipOnPackedInstancePartFuncPtr GetGroupMembershipOnPackedInstancePart; - static GetGroupNamesFuncPtr GetGroupNames; - static GetGroupNamesOnPackedInstancePartFuncPtr GetGroupNamesOnPackedInstancePart; - static GetHIPFileNodeCountFuncPtr GetHIPFileNodeCount; - static GetHIPFileNodeIdsFuncPtr GetHIPFileNodeIds; - static GetHandleBindingInfoFuncPtr GetHandleBindingInfo; - static GetHandleInfoFuncPtr GetHandleInfo; - static GetHeightFieldDataFuncPtr GetHeightFieldData; - static GetImageFilePathFuncPtr GetImageFilePath; - static GetImageInfoFuncPtr GetImageInfo; - static GetImageMemoryBufferFuncPtr GetImageMemoryBuffer; - static GetImagePlaneCountFuncPtr GetImagePlaneCount; - static GetImagePlanesFuncPtr GetImagePlanes; - static GetInstanceTransformsOnPartFuncPtr GetInstanceTransformsOnPart; - static GetInstancedObjectIdsFuncPtr GetInstancedObjectIds; - static GetInstancedPartIdsFuncPtr GetInstancedPartIds; - static GetInstancerPartTransformsFuncPtr GetInstancerPartTransforms; - static GetManagerNodeIdFuncPtr GetManagerNodeId; - static GetMaterialInfoFuncPtr GetMaterialInfo; - static GetMaterialNodeIdsOnFacesFuncPtr GetMaterialNodeIdsOnFaces; - static GetNextVolumeTileFuncPtr GetNextVolumeTile; - static GetNodeInfoFuncPtr GetNodeInfo; - static GetNodeInputNameFuncPtr GetNodeInputName; - static GetNodeOutputNameFuncPtr GetNodeOutputName; - static GetNodePathFuncPtr GetNodePath; - static GetNumWorkitemsFuncPtr GetNumWorkitems; - static GetObjectInfoFuncPtr GetObjectInfo; - static GetObjectTransformFuncPtr GetObjectTransform; - static GetOutputNodeIdFuncPtr GetOutputNodeId; - static GetPDGEventsFuncPtr GetPDGEvents; - static GetPDGGraphContextIdFuncPtr GetPDGGraphContextId; - static GetPDGGraphContextsFuncPtr GetPDGGraphContexts; - static GetPDGStateFuncPtr GetPDGState; - static GetParametersFuncPtr GetParameters; - static GetParmChoiceListsFuncPtr GetParmChoiceLists; - static GetParmExpressionFuncPtr GetParmExpression; - static GetParmFileFuncPtr GetParmFile; - static GetParmFloatValueFuncPtr GetParmFloatValue; - static GetParmFloatValuesFuncPtr GetParmFloatValues; - static GetParmIdFromNameFuncPtr GetParmIdFromName; - static GetParmInfoFuncPtr GetParmInfo; - static GetParmInfoFromNameFuncPtr GetParmInfoFromName; - static GetParmIntValueFuncPtr GetParmIntValue; - static GetParmIntValuesFuncPtr GetParmIntValues; - static GetParmNodeValueFuncPtr GetParmNodeValue; - static GetParmStringValueFuncPtr GetParmStringValue; - static GetParmStringValuesFuncPtr GetParmStringValues; - static GetParmTagNameFuncPtr GetParmTagName; - static GetParmTagValueFuncPtr GetParmTagValue; - static GetParmWithTagFuncPtr GetParmWithTag; - static GetPartInfoFuncPtr GetPartInfo; - static GetPresetFuncPtr GetPreset; - static GetPresetBufLengthFuncPtr GetPresetBufLength; - static GetServerEnvIntFuncPtr GetServerEnvInt; - static GetServerEnvStringFuncPtr GetServerEnvString; - static GetServerEnvVarCountFuncPtr GetServerEnvVarCount; - static GetServerEnvVarListFuncPtr GetServerEnvVarList; - static GetSessionEnvIntFuncPtr GetSessionEnvInt; - static GetSessionSyncInfoFuncPtr GetSessionSyncInfo; - static GetSphereInfoFuncPtr GetSphereInfo; - static GetStatusFuncPtr GetStatus; - static GetStatusStringFuncPtr GetStatusString; - static GetStatusStringBufLengthFuncPtr GetStatusStringBufLength; - static GetStringFuncPtr GetString; - static GetStringBatchFuncPtr GetStringBatch; - static GetStringBatchSizeFuncPtr GetStringBatchSize; - static GetStringBufLengthFuncPtr GetStringBufLength; - static GetSupportedImageFileFormatCountFuncPtr GetSupportedImageFileFormatCount; - static GetSupportedImageFileFormatsFuncPtr GetSupportedImageFileFormats; - static GetTimeFuncPtr GetTime; - static GetTimelineOptionsFuncPtr GetTimelineOptions; - static GetTotalCookCountFuncPtr GetTotalCookCount; - static GetUseHoudiniTimeFuncPtr GetUseHoudiniTime; - static GetVertexListFuncPtr GetVertexList; - static GetViewportFuncPtr GetViewport; - static GetVolumeBoundsFuncPtr GetVolumeBounds; - static GetVolumeInfoFuncPtr GetVolumeInfo; - static GetVolumeTileFloatDataFuncPtr GetVolumeTileFloatData; - static GetVolumeTileIntDataFuncPtr GetVolumeTileIntData; - static GetVolumeVisualInfoFuncPtr GetVolumeVisualInfo; - static GetVolumeVoxelFloatDataFuncPtr GetVolumeVoxelFloatData; - static GetVolumeVoxelIntDataFuncPtr GetVolumeVoxelIntData; - static GetWorkitemDataLengthFuncPtr GetWorkitemDataLength; - static GetWorkitemFloatDataFuncPtr GetWorkitemFloatData; - static GetWorkitemInfoFuncPtr GetWorkitemInfo; - static GetWorkitemIntDataFuncPtr GetWorkitemIntData; - static GetWorkitemResultInfoFuncPtr GetWorkitemResultInfo; - static GetWorkitemStringDataFuncPtr GetWorkitemStringData; - static GetWorkitemsFuncPtr GetWorkitems; - static HandleBindingInfo_CreateFuncPtr HandleBindingInfo_Create; - static HandleBindingInfo_InitFuncPtr HandleBindingInfo_Init; - static HandleInfo_CreateFuncPtr HandleInfo_Create; - static HandleInfo_InitFuncPtr HandleInfo_Init; - static ImageFileFormat_CreateFuncPtr ImageFileFormat_Create; - static ImageFileFormat_InitFuncPtr ImageFileFormat_Init; - static ImageInfo_CreateFuncPtr ImageInfo_Create; - static ImageInfo_InitFuncPtr ImageInfo_Init; - static InitializeFuncPtr Initialize; - static InsertMultiparmInstanceFuncPtr InsertMultiparmInstance; - static InterruptFuncPtr Interrupt; - static IsInitializedFuncPtr IsInitialized; - static IsNodeValidFuncPtr IsNodeValid; - static IsSessionValidFuncPtr IsSessionValid; - static Keyframe_CreateFuncPtr Keyframe_Create; - static Keyframe_InitFuncPtr Keyframe_Init; - static LoadAssetLibraryFromFileFuncPtr LoadAssetLibraryFromFile; - static LoadAssetLibraryFromMemoryFuncPtr LoadAssetLibraryFromMemory; - static LoadGeoFromFileFuncPtr LoadGeoFromFile; - static LoadGeoFromMemoryFuncPtr LoadGeoFromMemory; - static LoadHIPFileFuncPtr LoadHIPFile; - static LoadNodeFromFileFuncPtr LoadNodeFromFile; - static MaterialInfo_CreateFuncPtr MaterialInfo_Create; - static MaterialInfo_InitFuncPtr MaterialInfo_Init; - static MergeHIPFileFuncPtr MergeHIPFile; - static NodeInfo_CreateFuncPtr NodeInfo_Create; - static NodeInfo_InitFuncPtr NodeInfo_Init; - static ObjectInfo_CreateFuncPtr ObjectInfo_Create; - static ObjectInfo_InitFuncPtr ObjectInfo_Init; - static ParmChoiceInfo_CreateFuncPtr ParmChoiceInfo_Create; - static ParmChoiceInfo_InitFuncPtr ParmChoiceInfo_Init; - static ParmHasExpressionFuncPtr ParmHasExpression; - static ParmHasTagFuncPtr ParmHasTag; - static ParmInfo_CreateFuncPtr ParmInfo_Create; - static ParmInfo_GetFloatValueCountFuncPtr ParmInfo_GetFloatValueCount; - static ParmInfo_GetIntValueCountFuncPtr ParmInfo_GetIntValueCount; - static ParmInfo_GetStringValueCountFuncPtr ParmInfo_GetStringValueCount; - static ParmInfo_InitFuncPtr ParmInfo_Init; - static ParmInfo_IsFloatFuncPtr ParmInfo_IsFloat; - static ParmInfo_IsIntFuncPtr ParmInfo_IsInt; - static ParmInfo_IsNodeFuncPtr ParmInfo_IsNode; - static ParmInfo_IsNonValueFuncPtr ParmInfo_IsNonValue; - static ParmInfo_IsPathFuncPtr ParmInfo_IsPath; - static ParmInfo_IsStringFuncPtr ParmInfo_IsString; - static PartInfo_CreateFuncPtr PartInfo_Create; - static PartInfo_GetAttributeCountByOwnerFuncPtr PartInfo_GetAttributeCountByOwner; - static PartInfo_GetElementCountByAttributeOwnerFuncPtr PartInfo_GetElementCountByAttributeOwner; - static PartInfo_GetElementCountByGroupTypeFuncPtr PartInfo_GetElementCountByGroupType; - static PartInfo_InitFuncPtr PartInfo_Init; - static PausePDGCookFuncPtr PausePDGCook; - static PythonThreadInterpreterLockFuncPtr PythonThreadInterpreterLock; - static QueryNodeInputFuncPtr QueryNodeInput; - static QueryNodeOutputConnectedCountFuncPtr QueryNodeOutputConnectedCount; - static QueryNodeOutputConnectedNodesFuncPtr QueryNodeOutputConnectedNodes; - static RemoveCustomStringFuncPtr RemoveCustomString; - static RemoveMultiparmInstanceFuncPtr RemoveMultiparmInstance; - static RemoveParmExpressionFuncPtr RemoveParmExpression; - static RenameNodeFuncPtr RenameNode; - static RenderCOPToImageFuncPtr RenderCOPToImage; - static RenderTextureToImageFuncPtr RenderTextureToImage; - static ResetSimulationFuncPtr ResetSimulation; - static RevertGeoFuncPtr RevertGeo; - static RevertParmToDefaultFuncPtr RevertParmToDefault; - static RevertParmToDefaultsFuncPtr RevertParmToDefaults; - static SaveGeoToFileFuncPtr SaveGeoToFile; - static SaveGeoToMemoryFuncPtr SaveGeoToMemory; - static SaveHIPFileFuncPtr SaveHIPFile; - static SaveNodeToFileFuncPtr SaveNodeToFile; - static SessionSyncInfo_CreateFuncPtr SessionSyncInfo_Create; - static SetAnimCurveFuncPtr SetAnimCurve; - static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; - static SetAttributeFloatDataFuncPtr SetAttributeFloatData; - static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; - static SetAttributeIntDataFuncPtr SetAttributeIntData; - static SetAttributeStringDataFuncPtr SetAttributeStringData; - static SetCachePropertyFuncPtr SetCacheProperty; - static SetCurveCountsFuncPtr SetCurveCounts; - static SetCurveInfoFuncPtr SetCurveInfo; - static SetCurveKnotsFuncPtr SetCurveKnots; - static SetCurveOrdersFuncPtr SetCurveOrders; - static SetCustomStringFuncPtr SetCustomString; - static SetFaceCountsFuncPtr SetFaceCounts; - static SetGroupMembershipFuncPtr SetGroupMembership; - static SetHeightFieldDataFuncPtr SetHeightFieldData; - static SetImageInfoFuncPtr SetImageInfo; - static SetNodeDisplayFuncPtr SetNodeDisplay; - static SetObjectTransformFuncPtr SetObjectTransform; - static SetParmExpressionFuncPtr SetParmExpression; - static SetParmFloatValueFuncPtr SetParmFloatValue; - static SetParmFloatValuesFuncPtr SetParmFloatValues; - static SetParmIntValueFuncPtr SetParmIntValue; - static SetParmIntValuesFuncPtr SetParmIntValues; - static SetParmNodeValueFuncPtr SetParmNodeValue; - static SetParmStringValueFuncPtr SetParmStringValue; - static SetPartInfoFuncPtr SetPartInfo; - static SetPresetFuncPtr SetPreset; - static SetServerEnvIntFuncPtr SetServerEnvInt; - static SetServerEnvStringFuncPtr SetServerEnvString; - static SetSessionSyncFuncPtr SetSessionSync; - static SetSessionSyncInfoFuncPtr SetSessionSyncInfo; - static SetTimeFuncPtr SetTime; - static SetTimelineOptionsFuncPtr SetTimelineOptions; - static SetTransformAnimCurveFuncPtr SetTransformAnimCurve; - static SetUseHoudiniTimeFuncPtr SetUseHoudiniTime; - static SetVertexListFuncPtr SetVertexList; - static SetViewportFuncPtr SetViewport; - static SetVolumeInfoFuncPtr SetVolumeInfo; - static SetVolumeTileFloatDataFuncPtr SetVolumeTileFloatData; - static SetVolumeTileIntDataFuncPtr SetVolumeTileIntData; - static SetVolumeVoxelFloatDataFuncPtr SetVolumeVoxelFloatData; - static SetVolumeVoxelIntDataFuncPtr SetVolumeVoxelIntData; - static SetWorkitemFloatDataFuncPtr SetWorkitemFloatData; - static SetWorkitemIntDataFuncPtr SetWorkitemIntData; - static SetWorkitemStringDataFuncPtr SetWorkitemStringData; - static StartThriftNamedPipeServerFuncPtr StartThriftNamedPipeServer; - static StartThriftSocketServerFuncPtr StartThriftSocketServer; - static ThriftServerOptions_CreateFuncPtr ThriftServerOptions_Create; - static ThriftServerOptions_InitFuncPtr ThriftServerOptions_Init; - static TimelineOptions_CreateFuncPtr TimelineOptions_Create; - static TimelineOptions_InitFuncPtr TimelineOptions_Init; - static TransformEuler_CreateFuncPtr TransformEuler_Create; - static TransformEuler_InitFuncPtr TransformEuler_Init; - static Transform_CreateFuncPtr Transform_Create; - static Transform_InitFuncPtr Transform_Init; - static Viewport_CreateFuncPtr Viewport_Create; - static VolumeInfo_CreateFuncPtr VolumeInfo_Create; - static VolumeInfo_InitFuncPtr VolumeInfo_Init; - static VolumeTileInfo_CreateFuncPtr VolumeTileInfo_Create; - static VolumeTileInfo_InitFuncPtr VolumeTileInfo_Init; - -public: - - static HAPI_Result AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - static HAPI_Result AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - static HAPI_AssetInfo AssetInfo_CreateEmptyStub(); - static void AssetInfo_InitEmptyStub(HAPI_AssetInfo * in); - static HAPI_AttributeInfo AttributeInfo_CreateEmptyStub(); - static void AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in); - static HAPI_Result BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path); - static HAPI_Result CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - static HAPI_Result CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); - static HAPI_Result CleanupEmptyStub(const HAPI_Session * session); - static HAPI_Result ClearConnectionErrorEmptyStub(); - static HAPI_Result CloseSessionEmptyStub(const HAPI_Session * session); - static HAPI_Result CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - static HAPI_Result ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); - static HAPI_Result ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); - static HAPI_Result ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); - static HAPI_Result ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - static HAPI_Result ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); - static HAPI_Result ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - static HAPI_Result ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); - static HAPI_Result ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); - static HAPI_Result CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); - static HAPI_Bool CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right); - static HAPI_CookOptions CookOptions_CreateEmptyStub(); - static void CookOptions_InitEmptyStub(HAPI_CookOptions * in); - static HAPI_Result CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); - static HAPI_Result CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); - static HAPI_Result CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); - static HAPI_Result CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); - static HAPI_Result CreateInProcessSessionEmptyStub(HAPI_Session * session); - static HAPI_Result CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); - static HAPI_Result CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); - static HAPI_Result CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name); - static HAPI_Result CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port); - static HAPI_Result CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); - static HAPI_CurveInfo CurveInfo_CreateEmptyStub(); - static void CurveInfo_InitEmptyStub(HAPI_CurveInfo * in); - static HAPI_Result DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - static HAPI_Result DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - static HAPI_Result DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); - static HAPI_Result DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); - static HAPI_Result DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); - static HAPI_Result ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); - static HAPI_Result ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); - static HAPI_GeoInfo GeoInfo_CreateEmptyStub(); - static int GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type); - static void GeoInfo_InitEmptyStub(HAPI_GeoInfo * in); - static HAPI_Result GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count); - static HAPI_Result GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); - static HAPI_Result GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); - static HAPI_Result GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); - static HAPI_Result GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); - static HAPI_Result GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); - static HAPI_Result GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); - static HAPI_Result GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); - static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); - static HAPI_Result GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); - static HAPI_Result GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); - static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); - static HAPI_Result GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); - static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); - static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); - static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); - static HAPI_Result GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); - static HAPI_Result GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); - static HAPI_Result GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length); - static HAPI_Result GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); - static HAPI_Result GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); - static HAPI_Result GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear); - static HAPI_Result GetConnectionErrorLengthEmptyStub(int * buffer_length); - static HAPI_Result GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count); - static HAPI_Result GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count); - static HAPI_Result GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); - static HAPI_Result GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); - static HAPI_Result GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); - static HAPI_Result GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); - static HAPI_Result GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); - static HAPI_Result GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value); - static HAPI_Result GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); - static HAPI_Result GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - static HAPI_Result GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); - static HAPI_Result GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); - static HAPI_Result GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); - static HAPI_Result GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - static HAPI_Result GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - static HAPI_Result GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - static HAPI_Result GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - static HAPI_Result GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count); - static HAPI_Result GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); - static HAPI_Result GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); - static HAPI_Result GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); - static HAPI_Result GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); - static HAPI_Result GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); - static HAPI_Result GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); - static HAPI_Result GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); - static HAPI_Result GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); - static HAPI_Result GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); - static HAPI_Result GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - static HAPI_Result GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); - static HAPI_Result GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); - static HAPI_Result GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - static HAPI_Result GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); - static HAPI_Result GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); - static HAPI_Result GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); - static HAPI_Result GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - static HAPI_Result GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); - static HAPI_Result GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); - static HAPI_Result GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); - static HAPI_Result GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); - static HAPI_Result GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num); - static HAPI_Result GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); - static HAPI_Result GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); - static HAPI_Result GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); - static HAPI_Result GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); - static HAPI_Result GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); - static HAPI_Result GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); - static HAPI_Result GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); - static HAPI_Result GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); - static HAPI_Result GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); - static HAPI_Result GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); - static HAPI_Result GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); - static HAPI_Result GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); - static HAPI_Result GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); - static HAPI_Result GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); - static HAPI_Result GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); - static HAPI_Result GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); - static HAPI_Result GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); - static HAPI_Result GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); - static HAPI_Result GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); - static HAPI_Result GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); - static HAPI_Result GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); - static HAPI_Result GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); - static HAPI_Result GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); - static HAPI_Result GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); - static HAPI_Result GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); - static HAPI_Result GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); - static HAPI_Result GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); - static HAPI_Result GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value); - static HAPI_Result GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); - static HAPI_Result GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count); - static HAPI_Result GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); - static HAPI_Result GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); - static HAPI_Result GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); - static HAPI_Result GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); - static HAPI_Result GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status); - static HAPI_Result GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); - static HAPI_Result GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); - static HAPI_Result GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); - static HAPI_Result GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length); - static HAPI_Result GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); - static HAPI_Result GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); - static HAPI_Result GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count); - static HAPI_Result GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); - static HAPI_Result GetTimeEmptyStub(const HAPI_Session * session, float * time); - static HAPI_Result GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); - static HAPI_Result GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - static HAPI_Result GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled); - static HAPI_Result GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); - static HAPI_Result GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport); - static HAPI_Result GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); - static HAPI_Result GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); - static HAPI_Result GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); - static HAPI_Result GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); - static HAPI_Result GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); - static HAPI_Result GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); - static HAPI_Result GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); - static HAPI_Result GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); - static HAPI_Result GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); - static HAPI_Result GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); - static HAPI_Result GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); - static HAPI_Result GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); - static HAPI_Result GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); - static HAPI_Result GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); - static HAPI_HandleBindingInfo HandleBindingInfo_CreateEmptyStub(); - static void HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in); - static HAPI_HandleInfo HandleInfo_CreateEmptyStub(); - static void HandleInfo_InitEmptyStub(HAPI_HandleInfo * in); - static HAPI_ImageFileFormat ImageFileFormat_CreateEmptyStub(); - static void ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in); - static HAPI_ImageInfo ImageInfo_CreateEmptyStub(); - static void ImageInfo_InitEmptyStub(HAPI_ImageInfo * in); - static HAPI_Result InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); - static HAPI_Result InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - static HAPI_Result InterruptEmptyStub(const HAPI_Session * session); - static HAPI_Result IsInitializedEmptyStub(const HAPI_Session * session); - static HAPI_Result IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); - static HAPI_Result IsSessionValidEmptyStub(const HAPI_Session * session); - static HAPI_Keyframe Keyframe_CreateEmptyStub(); - static void Keyframe_InitEmptyStub(HAPI_Keyframe * in); - static HAPI_Result LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - static HAPI_Result LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - static HAPI_Result LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_Result LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); - static HAPI_Result LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); - static HAPI_Result LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); - static HAPI_MaterialInfo MaterialInfo_CreateEmptyStub(); - static void MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in); - static HAPI_Result MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); - static HAPI_NodeInfo NodeInfo_CreateEmptyStub(); - static void NodeInfo_InitEmptyStub(HAPI_NodeInfo * in); - static HAPI_ObjectInfo ObjectInfo_CreateEmptyStub(); - static void ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in); - static HAPI_ParmChoiceInfo ParmChoiceInfo_CreateEmptyStub(); - static void ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in); - static HAPI_Result ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); - static HAPI_Result ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); - static HAPI_ParmInfo ParmInfo_CreateEmptyStub(); - static int ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in); - static int ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in); - static int ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in); - static void ParmInfo_InitEmptyStub(HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in); - static HAPI_PartInfo PartInfo_CreateEmptyStub(); - static int PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - static int PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - static int PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type); - static void PartInfo_InitEmptyStub(HAPI_PartInfo * in); - static HAPI_Result PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - static HAPI_Result PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked); - static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); - static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); - static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle); - static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); - static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); - static HAPI_Result RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id); - static HAPI_Result RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); - static HAPI_Result ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); - static HAPI_Result RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); - static HAPI_Result SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_Result SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); - static HAPI_Result SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); - static HAPI_Result SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_SessionSyncInfo SessionSyncInfo_CreateEmptyStub(); - static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); - static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); - static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); - static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); - static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); - static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); - static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); - static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); - static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); - static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value); - static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); - static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); - static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); - static HAPI_Result SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); - static HAPI_Result SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); - static HAPI_Result SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); - static HAPI_Result SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - static HAPI_Result SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); - static HAPI_Result SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); - static HAPI_Result SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); - static HAPI_Result SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); - static HAPI_Result SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); - static HAPI_Result SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - static HAPI_Result SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); - static HAPI_Result SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); - static HAPI_Result SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value); - static HAPI_Result SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value); - static HAPI_Result SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable); - static HAPI_Result SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); - static HAPI_Result SetTimeEmptyStub(const HAPI_Session * session, float time); - static HAPI_Result SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); - static HAPI_Result SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - static HAPI_Result SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled); - static HAPI_Result SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); - static HAPI_Result SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport); - static HAPI_Result SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); - static HAPI_Result SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); - static HAPI_Result SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); - static HAPI_Result SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); - static HAPI_Result SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); - static HAPI_Result SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); - static HAPI_Result SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); - static HAPI_Result SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); - static HAPI_Result StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); - static HAPI_Result StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); - static HAPI_ThriftServerOptions ThriftServerOptions_CreateEmptyStub(); - static void ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in); - static HAPI_TimelineOptions TimelineOptions_CreateEmptyStub(); - static void TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in); - static HAPI_TransformEuler TransformEuler_CreateEmptyStub(); - static void TransformEuler_InitEmptyStub(HAPI_TransformEuler * in); - static HAPI_Transform Transform_CreateEmptyStub(); - static void Transform_InitEmptyStub(HAPI_Transform * in); - static HAPI_Viewport Viewport_CreateEmptyStub(); - static HAPI_VolumeInfo VolumeInfo_CreateEmptyStub(); - static void VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in); - static HAPI_VolumeTileInfo VolumeTileInfo_CreateEmptyStub(); - static void VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in); -}; +/* + * Copyright (c) <2020> 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. + * + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +#pragma once +#include "HAPI/HAPI.h" +#include "HAL/PlatformProcess.h" + + +struct HOUDINIENGINE_API FHoudiniApi +{ +public: + + static void InitializeHAPI(void* LibraryHandle); + static void FinalizeHAPI(); + static bool IsHAPIInitialized(); + +public: + + typedef HAPI_Result (*AddAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*AddGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + typedef HAPI_AssetInfo (*AssetInfo_CreateFuncPtr)(); + typedef void (*AssetInfo_InitFuncPtr)(HAPI_AssetInfo * in); + typedef HAPI_AttributeInfo (*AttributeInfo_CreateFuncPtr)(); + typedef void (*AttributeInfo_InitFuncPtr)(HAPI_AttributeInfo * in); + typedef HAPI_Result (*BindCustomImplementationFuncPtr)(HAPI_SessionType session_type, const char * dll_path); + typedef HAPI_Result (*CancelPDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + typedef HAPI_Result (*CheckForSpecificErrorsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); + typedef HAPI_Result (*CleanupFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*ClearConnectionErrorFuncPtr)(); + typedef HAPI_Result (*CloseSessionFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*CommitGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*CommitWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*ComposeChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + typedef HAPI_Result (*ComposeNodeCookResultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); + typedef HAPI_Result (*ComposeObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + typedef HAPI_Result (*ConnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); + typedef HAPI_Result (*ConvertMatrixToEulerFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + typedef HAPI_Result (*ConvertMatrixToQuatFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); + typedef HAPI_Result (*ConvertTransformFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + typedef HAPI_Result (*ConvertTransformEulerToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); + typedef HAPI_Result (*ConvertTransformQuatToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); + typedef HAPI_Result (*CookNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); + typedef HAPI_Bool (*CookOptions_AreEqualFuncPtr)(const HAPI_CookOptions * left, const HAPI_CookOptions * right); + typedef HAPI_CookOptions (*CookOptions_CreateFuncPtr)(); + typedef void (*CookOptions_InitFuncPtr)(HAPI_CookOptions * in); + typedef HAPI_Result (*CookPDGFuncPtr)(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); + typedef HAPI_Result (*CreateCustomSessionFuncPtr)(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); + typedef HAPI_Result (*CreateHeightFieldInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); + typedef HAPI_Result (*CreateHeightfieldInputVolumeNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); + typedef HAPI_Result (*CreateInProcessSessionFuncPtr)(HAPI_Session * session); + typedef HAPI_Result (*CreateInputNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); + typedef HAPI_Result (*CreateNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); + typedef HAPI_Result (*CreateThriftNamedPipeSessionFuncPtr)(HAPI_Session * session, const char * pipe_name); + typedef HAPI_Result (*CreateThriftSocketSessionFuncPtr)(HAPI_Session * session, const char * host_name, int port); + typedef HAPI_Result (*CreateWorkitemFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); + typedef HAPI_CurveInfo (*CurveInfo_CreateFuncPtr)(); + typedef void (*CurveInfo_InitFuncPtr)(HAPI_CurveInfo * in); + typedef HAPI_Result (*DeleteAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*DeleteGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + typedef HAPI_Result (*DeleteNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*DirtyPDGNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); + typedef HAPI_Result (*DisconnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); + typedef HAPI_Result (*DisconnectNodeOutputsAtFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); + typedef HAPI_Result (*ExtractImageToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); + typedef HAPI_Result (*ExtractImageToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); + typedef HAPI_GeoInfo (*GeoInfo_CreateFuncPtr)(); + typedef int (*GeoInfo_GetGroupCountByTypeFuncPtr)(HAPI_GeoInfo * in, HAPI_GroupType type); + typedef void (*GeoInfo_InitFuncPtr)(HAPI_GeoInfo * in); + typedef HAPI_Result (*GetActiveCacheCountFuncPtr)(const HAPI_Session * session, int * active_cache_count); + typedef HAPI_Result (*GetActiveCacheNamesFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); + typedef HAPI_Result (*GetAssetDefinitionParmCountsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); + typedef HAPI_Result (*GetAssetDefinitionParmInfosFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); + typedef HAPI_Result (*GetAssetDefinitionParmValuesFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); + typedef HAPI_Result (*GetAssetInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); + typedef HAPI_Result (*GetAttributeFloat64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeFloatArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*GetAttributeInt64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeIntArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); + typedef HAPI_Result (*GetAttributeStringArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); + typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); + typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); + typedef HAPI_Result (*GetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); + typedef HAPI_Result (*GetComposedChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); + typedef HAPI_Result (*GetComposedNodeCookResultFuncPtr)(const HAPI_Session * session, char * string_value, int length); + typedef HAPI_Result (*GetComposedObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); + typedef HAPI_Result (*GetComposedObjectTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + typedef HAPI_Result (*GetConnectionErrorFuncPtr)(char * string_value, int length, HAPI_Bool clear); + typedef HAPI_Result (*GetConnectionErrorLengthFuncPtr)(int * buffer_length); + typedef HAPI_Result (*GetCookingCurrentCountFuncPtr)(const HAPI_Session * session, int * count); + typedef HAPI_Result (*GetCookingTotalCountFuncPtr)(const HAPI_Session * session, int * count); + typedef HAPI_Result (*GetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); + typedef HAPI_Result (*GetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); + typedef HAPI_Result (*GetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); + typedef HAPI_Result (*GetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); + typedef HAPI_Result (*GetDisplayGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetEnvIntFuncPtr)(HAPI_EnvIntType int_type, int * value); + typedef HAPI_Result (*GetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); + typedef HAPI_Result (*GetFirstVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + typedef HAPI_Result (*GetGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetGeoSizeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); + typedef HAPI_Result (*GetGroupCountOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); + typedef HAPI_Result (*GetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + typedef HAPI_Result (*GetGroupMembershipOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + typedef HAPI_Result (*GetGroupNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + typedef HAPI_Result (*GetGroupNamesOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + typedef HAPI_Result (*GetHIPFileNodeCountFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, int * count); + typedef HAPI_Result (*GetHIPFileNodeIdsFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); + typedef HAPI_Result (*GetHandleBindingInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); + typedef HAPI_Result (*GetHandleInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); + typedef HAPI_Result (*GetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); + typedef HAPI_Result (*GetImageFilePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); + typedef HAPI_Result (*GetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); + typedef HAPI_Result (*GetImageMemoryBufferFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); + typedef HAPI_Result (*GetImagePlaneCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); + typedef HAPI_Result (*GetImagePlanesFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); + typedef HAPI_Result (*GetInstanceTransformsOnPartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + typedef HAPI_Result (*GetInstancedObjectIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); + typedef HAPI_Result (*GetInstancedPartIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); + typedef HAPI_Result (*GetInstancerPartTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + typedef HAPI_Result (*GetManagerNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); + typedef HAPI_Result (*GetMaterialInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); + typedef HAPI_Result (*GetMaterialNodeIdsOnFacesFuncPtr)(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); + typedef HAPI_Result (*GetNextVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + typedef HAPI_Result (*GetNodeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); + typedef HAPI_Result (*GetNodeInputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); + typedef HAPI_Result (*GetNodeOutputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); + typedef HAPI_Result (*GetNodePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); + typedef HAPI_Result (*GetNumWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * num); + typedef HAPI_Result (*GetObjectInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); + typedef HAPI_Result (*GetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + typedef HAPI_Result (*GetOutputNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); + typedef HAPI_Result (*GetPDGEventsFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); + typedef HAPI_Result (*GetPDGGraphContextIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); + typedef HAPI_Result (*GetPDGGraphContextsFuncPtr)(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); + typedef HAPI_Result (*GetPDGStateFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); + typedef HAPI_Result (*GetParametersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); + typedef HAPI_Result (*GetParmChoiceListsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); + typedef HAPI_Result (*GetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); + typedef HAPI_Result (*GetParmFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); + typedef HAPI_Result (*GetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); + typedef HAPI_Result (*GetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); + typedef HAPI_Result (*GetParmIdFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); + typedef HAPI_Result (*GetParmInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); + typedef HAPI_Result (*GetParmInfoFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); + typedef HAPI_Result (*GetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); + typedef HAPI_Result (*GetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); + typedef HAPI_Result (*GetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); + typedef HAPI_Result (*GetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); + typedef HAPI_Result (*GetParmStringValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); + typedef HAPI_Result (*GetParmTagNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); + typedef HAPI_Result (*GetParmTagValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); + typedef HAPI_Result (*GetParmWithTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); + typedef HAPI_Result (*GetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); + typedef HAPI_Result (*GetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); + typedef HAPI_Result (*GetPresetBufLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); + typedef HAPI_Result (*GetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int * value); + typedef HAPI_Result (*GetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); + typedef HAPI_Result (*GetServerEnvVarCountFuncPtr)(const HAPI_Session * session, int * env_count); + typedef HAPI_Result (*GetServerEnvVarListFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); + typedef HAPI_Result (*GetSessionEnvIntFuncPtr)(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); + typedef HAPI_Result (*GetSessionSyncInfoFuncPtr)(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); + typedef HAPI_Result (*GetSphereInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); + typedef HAPI_Result (*GetStatusFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, int * status); + typedef HAPI_Result (*GetStatusStringFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); + typedef HAPI_Result (*GetStatusStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); + typedef HAPI_Result (*GetStringFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); + typedef HAPI_Result (*GetStringBatchFuncPtr)(const HAPI_Session * session, char * char_buffer, int char_array_length); + typedef HAPI_Result (*GetStringBatchSizeFuncPtr)(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); + typedef HAPI_Result (*GetStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); + typedef HAPI_Result (*GetSupportedImageFileFormatCountFuncPtr)(const HAPI_Session * session, int * file_format_count); + typedef HAPI_Result (*GetSupportedImageFileFormatsFuncPtr)(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); + typedef HAPI_Result (*GetTimeFuncPtr)(const HAPI_Session * session, float * time); + typedef HAPI_Result (*GetTimelineOptionsFuncPtr)(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); + typedef HAPI_Result (*GetTotalCookCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + typedef HAPI_Result (*GetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool * enabled); + typedef HAPI_Result (*GetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); + typedef HAPI_Result (*GetViewportFuncPtr)(const HAPI_Session * session, HAPI_Viewport * viewport); + typedef HAPI_Result (*GetVolumeBoundsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); + typedef HAPI_Result (*GetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); + typedef HAPI_Result (*GetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); + typedef HAPI_Result (*GetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); + typedef HAPI_Result (*GetVolumeVisualInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); + typedef HAPI_Result (*GetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); + typedef HAPI_Result (*GetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); + typedef HAPI_Result (*GetWorkitemDataLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); + typedef HAPI_Result (*GetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); + typedef HAPI_Result (*GetWorkitemInfoFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); + typedef HAPI_Result (*GetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); + typedef HAPI_Result (*GetWorkitemResultInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); + typedef HAPI_Result (*GetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); + typedef HAPI_Result (*GetWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); + typedef HAPI_HandleBindingInfo (*HandleBindingInfo_CreateFuncPtr)(); + typedef void (*HandleBindingInfo_InitFuncPtr)(HAPI_HandleBindingInfo * in); + typedef HAPI_HandleInfo (*HandleInfo_CreateFuncPtr)(); + typedef void (*HandleInfo_InitFuncPtr)(HAPI_HandleInfo * in); + typedef HAPI_ImageFileFormat (*ImageFileFormat_CreateFuncPtr)(); + typedef void (*ImageFileFormat_InitFuncPtr)(HAPI_ImageFileFormat *in); + typedef HAPI_ImageInfo (*ImageInfo_CreateFuncPtr)(); + typedef void (*ImageInfo_InitFuncPtr)(HAPI_ImageInfo * in); + typedef HAPI_Result (*InitializeFuncPtr)(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); + typedef HAPI_Result (*InsertMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + typedef HAPI_Result (*InterruptFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*IsInitializedFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*IsNodeValidFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); + typedef HAPI_Result (*IsSessionValidFuncPtr)(const HAPI_Session * session); + typedef HAPI_Keyframe (*Keyframe_CreateFuncPtr)(); + typedef void (*Keyframe_InitFuncPtr)(HAPI_Keyframe * in); + typedef HAPI_Result (*LoadAssetLibraryFromFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + typedef HAPI_Result (*LoadAssetLibraryFromMemoryFuncPtr)(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + typedef HAPI_Result (*LoadGeoFromFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_Result (*LoadGeoFromMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); + typedef HAPI_Result (*LoadHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); + typedef HAPI_Result (*LoadNodeFromFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); + typedef HAPI_MaterialInfo (*MaterialInfo_CreateFuncPtr)(); + typedef void (*MaterialInfo_InitFuncPtr)(HAPI_MaterialInfo * in); + typedef HAPI_Result (*MergeHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); + typedef HAPI_NodeInfo (*NodeInfo_CreateFuncPtr)(); + typedef void (*NodeInfo_InitFuncPtr)(HAPI_NodeInfo * in); + typedef HAPI_ObjectInfo (*ObjectInfo_CreateFuncPtr)(); + typedef void (*ObjectInfo_InitFuncPtr)(HAPI_ObjectInfo * in); + typedef HAPI_ParmChoiceInfo (*ParmChoiceInfo_CreateFuncPtr)(); + typedef void (*ParmChoiceInfo_InitFuncPtr)(HAPI_ParmChoiceInfo * in); + typedef HAPI_Result (*ParmHasExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); + typedef HAPI_Result (*ParmHasTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); + typedef HAPI_ParmInfo (*ParmInfo_CreateFuncPtr)(); + typedef int (*ParmInfo_GetFloatValueCountFuncPtr)(const HAPI_ParmInfo * in); + typedef int (*ParmInfo_GetIntValueCountFuncPtr)(const HAPI_ParmInfo * in); + typedef int (*ParmInfo_GetStringValueCountFuncPtr)(const HAPI_ParmInfo* in); + typedef void (*ParmInfo_InitFuncPtr)(HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsFloatFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsIntFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsNodeFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsNonValueFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsPathFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsStringFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_PartInfo (*PartInfo_CreateFuncPtr)(); + typedef int (*PartInfo_GetAttributeCountByOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + typedef int (*PartInfo_GetElementCountByAttributeOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + typedef int (*PartInfo_GetElementCountByGroupTypeFuncPtr)(HAPI_PartInfo * in, HAPI_GroupType type); + typedef void (*PartInfo_InitFuncPtr)(HAPI_PartInfo * in); + typedef HAPI_Result (*PausePDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + typedef HAPI_Result (*PythonThreadInterpreterLockFuncPtr)(const HAPI_Session * session, HAPI_Bool locked); + typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); + typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); + typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); + typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const int string_handle); + typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); + typedef HAPI_Result (*RenderCOPToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId cop_node_id); + typedef HAPI_Result (*RenderTextureToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); + typedef HAPI_Result (*ResetSimulationFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*RevertGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*RevertParmToDefaultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); + typedef HAPI_Result (*RevertParmToDefaultsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); + typedef HAPI_Result (*SaveGeoToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_Result (*SaveGeoToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); + typedef HAPI_Result (*SaveHIPFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); + typedef HAPI_Result (*SaveNodeToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_SessionSyncInfo (*SessionSyncInfo_CreateFuncPtr)(); + typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); + typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); + typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); + typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); + typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, int * handle_value); + typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); + typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); + typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); + typedef HAPI_Result (*SetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); + typedef HAPI_Result (*SetNodeDisplayFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); + typedef HAPI_Result (*SetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); + typedef HAPI_Result (*SetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*SetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); + typedef HAPI_Result (*SetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); + typedef HAPI_Result (*SetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); + typedef HAPI_Result (*SetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); + typedef HAPI_Result (*SetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); + typedef HAPI_Result (*SetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*SetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); + typedef HAPI_Result (*SetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); + typedef HAPI_Result (*SetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int value); + typedef HAPI_Result (*SetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, const char * value); + typedef HAPI_Result (*SetSessionSyncFuncPtr)(const HAPI_Session * session, HAPI_Bool enable); + typedef HAPI_Result (*SetSessionSyncInfoFuncPtr)(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); + typedef HAPI_Result (*SetTimeFuncPtr)(const HAPI_Session * session, float time); + typedef HAPI_Result (*SetTimelineOptionsFuncPtr)(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); + typedef HAPI_Result (*SetTransformAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + typedef HAPI_Result (*SetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool enabled); + typedef HAPI_Result (*SetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); + typedef HAPI_Result (*SetViewportFuncPtr)(const HAPI_Session * session, const HAPI_Viewport * viewport); + typedef HAPI_Result (*SetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); + typedef HAPI_Result (*SetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); + typedef HAPI_Result (*SetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); + typedef HAPI_Result (*SetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); + typedef HAPI_Result (*SetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); + typedef HAPI_Result (*SetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); + typedef HAPI_Result (*SetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); + typedef HAPI_Result (*SetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); + typedef HAPI_Result (*StartThriftNamedPipeServerFuncPtr)(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); + typedef HAPI_Result (*StartThriftSocketServerFuncPtr)(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); + typedef HAPI_ThriftServerOptions (*ThriftServerOptions_CreateFuncPtr)(); + typedef void (*ThriftServerOptions_InitFuncPtr)(HAPI_ThriftServerOptions * in); + typedef HAPI_TimelineOptions (*TimelineOptions_CreateFuncPtr)(); + typedef void (*TimelineOptions_InitFuncPtr)(HAPI_TimelineOptions * in); + typedef HAPI_TransformEuler (*TransformEuler_CreateFuncPtr)(); + typedef void (*TransformEuler_InitFuncPtr)(HAPI_TransformEuler * in); + typedef HAPI_Transform (*Transform_CreateFuncPtr)(); + typedef void (*Transform_InitFuncPtr)(HAPI_Transform * in); + typedef HAPI_Viewport (*Viewport_CreateFuncPtr)(); + typedef HAPI_VolumeInfo (*VolumeInfo_CreateFuncPtr)(); + typedef void (*VolumeInfo_InitFuncPtr)(HAPI_VolumeInfo * in); + typedef HAPI_VolumeTileInfo (*VolumeTileInfo_CreateFuncPtr)(); + typedef void (*VolumeTileInfo_InitFuncPtr)(HAPI_VolumeTileInfo * in); + +public: + + static AddAttributeFuncPtr AddAttribute; + static AddGroupFuncPtr AddGroup; + static AssetInfo_CreateFuncPtr AssetInfo_Create; + static AssetInfo_InitFuncPtr AssetInfo_Init; + static AttributeInfo_CreateFuncPtr AttributeInfo_Create; + static AttributeInfo_InitFuncPtr AttributeInfo_Init; + static BindCustomImplementationFuncPtr BindCustomImplementation; + static CancelPDGCookFuncPtr CancelPDGCook; + static CheckForSpecificErrorsFuncPtr CheckForSpecificErrors; + static CleanupFuncPtr Cleanup; + static ClearConnectionErrorFuncPtr ClearConnectionError; + static CloseSessionFuncPtr CloseSession; + static CommitGeoFuncPtr CommitGeo; + static CommitWorkitemsFuncPtr CommitWorkitems; + static ComposeChildNodeListFuncPtr ComposeChildNodeList; + static ComposeNodeCookResultFuncPtr ComposeNodeCookResult; + static ComposeObjectListFuncPtr ComposeObjectList; + static ConnectNodeInputFuncPtr ConnectNodeInput; + static ConvertMatrixToEulerFuncPtr ConvertMatrixToEuler; + static ConvertMatrixToQuatFuncPtr ConvertMatrixToQuat; + static ConvertTransformFuncPtr ConvertTransform; + static ConvertTransformEulerToMatrixFuncPtr ConvertTransformEulerToMatrix; + static ConvertTransformQuatToMatrixFuncPtr ConvertTransformQuatToMatrix; + static CookNodeFuncPtr CookNode; + static CookOptions_AreEqualFuncPtr CookOptions_AreEqual; + static CookOptions_CreateFuncPtr CookOptions_Create; + static CookOptions_InitFuncPtr CookOptions_Init; + static CookPDGFuncPtr CookPDG; + static CreateCustomSessionFuncPtr CreateCustomSession; + static CreateHeightFieldInputFuncPtr CreateHeightFieldInput; + static CreateHeightfieldInputVolumeNodeFuncPtr CreateHeightfieldInputVolumeNode; + static CreateInProcessSessionFuncPtr CreateInProcessSession; + static CreateInputNodeFuncPtr CreateInputNode; + static CreateNodeFuncPtr CreateNode; + static CreateThriftNamedPipeSessionFuncPtr CreateThriftNamedPipeSession; + static CreateThriftSocketSessionFuncPtr CreateThriftSocketSession; + static CreateWorkitemFuncPtr CreateWorkitem; + static CurveInfo_CreateFuncPtr CurveInfo_Create; + static CurveInfo_InitFuncPtr CurveInfo_Init; + static DeleteAttributeFuncPtr DeleteAttribute; + static DeleteGroupFuncPtr DeleteGroup; + static DeleteNodeFuncPtr DeleteNode; + static DirtyPDGNodeFuncPtr DirtyPDGNode; + static DisconnectNodeInputFuncPtr DisconnectNodeInput; + static DisconnectNodeOutputsAtFuncPtr DisconnectNodeOutputsAt; + static ExtractImageToFileFuncPtr ExtractImageToFile; + static ExtractImageToMemoryFuncPtr ExtractImageToMemory; + static GeoInfo_CreateFuncPtr GeoInfo_Create; + static GeoInfo_GetGroupCountByTypeFuncPtr GeoInfo_GetGroupCountByType; + static GeoInfo_InitFuncPtr GeoInfo_Init; + static GetActiveCacheCountFuncPtr GetActiveCacheCount; + static GetActiveCacheNamesFuncPtr GetActiveCacheNames; + static GetAssetDefinitionParmCountsFuncPtr GetAssetDefinitionParmCounts; + static GetAssetDefinitionParmInfosFuncPtr GetAssetDefinitionParmInfos; + static GetAssetDefinitionParmValuesFuncPtr GetAssetDefinitionParmValues; + static GetAssetInfoFuncPtr GetAssetInfo; + static GetAttributeFloat64ArrayDataFuncPtr GetAttributeFloat64ArrayData; + static GetAttributeFloat64DataFuncPtr GetAttributeFloat64Data; + static GetAttributeFloatArrayDataFuncPtr GetAttributeFloatArrayData; + static GetAttributeFloatDataFuncPtr GetAttributeFloatData; + static GetAttributeInfoFuncPtr GetAttributeInfo; + static GetAttributeInt64ArrayDataFuncPtr GetAttributeInt64ArrayData; + static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; + static GetAttributeIntArrayDataFuncPtr GetAttributeIntArrayData; + static GetAttributeIntDataFuncPtr GetAttributeIntData; + static GetAttributeNamesFuncPtr GetAttributeNames; + static GetAttributeStringArrayDataFuncPtr GetAttributeStringArrayData; + static GetAttributeStringDataFuncPtr GetAttributeStringData; + static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; + static GetAvailableAssetsFuncPtr GetAvailableAssets; + static GetBoxInfoFuncPtr GetBoxInfo; + static GetCachePropertyFuncPtr GetCacheProperty; + static GetComposedChildNodeListFuncPtr GetComposedChildNodeList; + static GetComposedNodeCookResultFuncPtr GetComposedNodeCookResult; + static GetComposedObjectListFuncPtr GetComposedObjectList; + static GetComposedObjectTransformsFuncPtr GetComposedObjectTransforms; + static GetConnectionErrorFuncPtr GetConnectionError; + static GetConnectionErrorLengthFuncPtr GetConnectionErrorLength; + static GetCookingCurrentCountFuncPtr GetCookingCurrentCount; + static GetCookingTotalCountFuncPtr GetCookingTotalCount; + static GetCurveCountsFuncPtr GetCurveCounts; + static GetCurveInfoFuncPtr GetCurveInfo; + static GetCurveKnotsFuncPtr GetCurveKnots; + static GetCurveOrdersFuncPtr GetCurveOrders; + static GetDisplayGeoInfoFuncPtr GetDisplayGeoInfo; + static GetEnvIntFuncPtr GetEnvInt; + static GetFaceCountsFuncPtr GetFaceCounts; + static GetFirstVolumeTileFuncPtr GetFirstVolumeTile; + static GetGeoInfoFuncPtr GetGeoInfo; + static GetGeoSizeFuncPtr GetGeoSize; + static GetGroupCountOnPackedInstancePartFuncPtr GetGroupCountOnPackedInstancePart; + static GetGroupMembershipFuncPtr GetGroupMembership; + static GetGroupMembershipOnPackedInstancePartFuncPtr GetGroupMembershipOnPackedInstancePart; + static GetGroupNamesFuncPtr GetGroupNames; + static GetGroupNamesOnPackedInstancePartFuncPtr GetGroupNamesOnPackedInstancePart; + static GetHIPFileNodeCountFuncPtr GetHIPFileNodeCount; + static GetHIPFileNodeIdsFuncPtr GetHIPFileNodeIds; + static GetHandleBindingInfoFuncPtr GetHandleBindingInfo; + static GetHandleInfoFuncPtr GetHandleInfo; + static GetHeightFieldDataFuncPtr GetHeightFieldData; + static GetImageFilePathFuncPtr GetImageFilePath; + static GetImageInfoFuncPtr GetImageInfo; + static GetImageMemoryBufferFuncPtr GetImageMemoryBuffer; + static GetImagePlaneCountFuncPtr GetImagePlaneCount; + static GetImagePlanesFuncPtr GetImagePlanes; + static GetInstanceTransformsOnPartFuncPtr GetInstanceTransformsOnPart; + static GetInstancedObjectIdsFuncPtr GetInstancedObjectIds; + static GetInstancedPartIdsFuncPtr GetInstancedPartIds; + static GetInstancerPartTransformsFuncPtr GetInstancerPartTransforms; + static GetManagerNodeIdFuncPtr GetManagerNodeId; + static GetMaterialInfoFuncPtr GetMaterialInfo; + static GetMaterialNodeIdsOnFacesFuncPtr GetMaterialNodeIdsOnFaces; + static GetNextVolumeTileFuncPtr GetNextVolumeTile; + static GetNodeInfoFuncPtr GetNodeInfo; + static GetNodeInputNameFuncPtr GetNodeInputName; + static GetNodeOutputNameFuncPtr GetNodeOutputName; + static GetNodePathFuncPtr GetNodePath; + static GetNumWorkitemsFuncPtr GetNumWorkitems; + static GetObjectInfoFuncPtr GetObjectInfo; + static GetObjectTransformFuncPtr GetObjectTransform; + static GetOutputNodeIdFuncPtr GetOutputNodeId; + static GetPDGEventsFuncPtr GetPDGEvents; + static GetPDGGraphContextIdFuncPtr GetPDGGraphContextId; + static GetPDGGraphContextsFuncPtr GetPDGGraphContexts; + static GetPDGStateFuncPtr GetPDGState; + static GetParametersFuncPtr GetParameters; + static GetParmChoiceListsFuncPtr GetParmChoiceLists; + static GetParmExpressionFuncPtr GetParmExpression; + static GetParmFileFuncPtr GetParmFile; + static GetParmFloatValueFuncPtr GetParmFloatValue; + static GetParmFloatValuesFuncPtr GetParmFloatValues; + static GetParmIdFromNameFuncPtr GetParmIdFromName; + static GetParmInfoFuncPtr GetParmInfo; + static GetParmInfoFromNameFuncPtr GetParmInfoFromName; + static GetParmIntValueFuncPtr GetParmIntValue; + static GetParmIntValuesFuncPtr GetParmIntValues; + static GetParmNodeValueFuncPtr GetParmNodeValue; + static GetParmStringValueFuncPtr GetParmStringValue; + static GetParmStringValuesFuncPtr GetParmStringValues; + static GetParmTagNameFuncPtr GetParmTagName; + static GetParmTagValueFuncPtr GetParmTagValue; + static GetParmWithTagFuncPtr GetParmWithTag; + static GetPartInfoFuncPtr GetPartInfo; + static GetPresetFuncPtr GetPreset; + static GetPresetBufLengthFuncPtr GetPresetBufLength; + static GetServerEnvIntFuncPtr GetServerEnvInt; + static GetServerEnvStringFuncPtr GetServerEnvString; + static GetServerEnvVarCountFuncPtr GetServerEnvVarCount; + static GetServerEnvVarListFuncPtr GetServerEnvVarList; + static GetSessionEnvIntFuncPtr GetSessionEnvInt; + static GetSessionSyncInfoFuncPtr GetSessionSyncInfo; + static GetSphereInfoFuncPtr GetSphereInfo; + static GetStatusFuncPtr GetStatus; + static GetStatusStringFuncPtr GetStatusString; + static GetStatusStringBufLengthFuncPtr GetStatusStringBufLength; + static GetStringFuncPtr GetString; + static GetStringBatchFuncPtr GetStringBatch; + static GetStringBatchSizeFuncPtr GetStringBatchSize; + static GetStringBufLengthFuncPtr GetStringBufLength; + static GetSupportedImageFileFormatCountFuncPtr GetSupportedImageFileFormatCount; + static GetSupportedImageFileFormatsFuncPtr GetSupportedImageFileFormats; + static GetTimeFuncPtr GetTime; + static GetTimelineOptionsFuncPtr GetTimelineOptions; + static GetTotalCookCountFuncPtr GetTotalCookCount; + static GetUseHoudiniTimeFuncPtr GetUseHoudiniTime; + static GetVertexListFuncPtr GetVertexList; + static GetViewportFuncPtr GetViewport; + static GetVolumeBoundsFuncPtr GetVolumeBounds; + static GetVolumeInfoFuncPtr GetVolumeInfo; + static GetVolumeTileFloatDataFuncPtr GetVolumeTileFloatData; + static GetVolumeTileIntDataFuncPtr GetVolumeTileIntData; + static GetVolumeVisualInfoFuncPtr GetVolumeVisualInfo; + static GetVolumeVoxelFloatDataFuncPtr GetVolumeVoxelFloatData; + static GetVolumeVoxelIntDataFuncPtr GetVolumeVoxelIntData; + static GetWorkitemDataLengthFuncPtr GetWorkitemDataLength; + static GetWorkitemFloatDataFuncPtr GetWorkitemFloatData; + static GetWorkitemInfoFuncPtr GetWorkitemInfo; + static GetWorkitemIntDataFuncPtr GetWorkitemIntData; + static GetWorkitemResultInfoFuncPtr GetWorkitemResultInfo; + static GetWorkitemStringDataFuncPtr GetWorkitemStringData; + static GetWorkitemsFuncPtr GetWorkitems; + static HandleBindingInfo_CreateFuncPtr HandleBindingInfo_Create; + static HandleBindingInfo_InitFuncPtr HandleBindingInfo_Init; + static HandleInfo_CreateFuncPtr HandleInfo_Create; + static HandleInfo_InitFuncPtr HandleInfo_Init; + static ImageFileFormat_CreateFuncPtr ImageFileFormat_Create; + static ImageFileFormat_InitFuncPtr ImageFileFormat_Init; + static ImageInfo_CreateFuncPtr ImageInfo_Create; + static ImageInfo_InitFuncPtr ImageInfo_Init; + static InitializeFuncPtr Initialize; + static InsertMultiparmInstanceFuncPtr InsertMultiparmInstance; + static InterruptFuncPtr Interrupt; + static IsInitializedFuncPtr IsInitialized; + static IsNodeValidFuncPtr IsNodeValid; + static IsSessionValidFuncPtr IsSessionValid; + static Keyframe_CreateFuncPtr Keyframe_Create; + static Keyframe_InitFuncPtr Keyframe_Init; + static LoadAssetLibraryFromFileFuncPtr LoadAssetLibraryFromFile; + static LoadAssetLibraryFromMemoryFuncPtr LoadAssetLibraryFromMemory; + static LoadGeoFromFileFuncPtr LoadGeoFromFile; + static LoadGeoFromMemoryFuncPtr LoadGeoFromMemory; + static LoadHIPFileFuncPtr LoadHIPFile; + static LoadNodeFromFileFuncPtr LoadNodeFromFile; + static MaterialInfo_CreateFuncPtr MaterialInfo_Create; + static MaterialInfo_InitFuncPtr MaterialInfo_Init; + static MergeHIPFileFuncPtr MergeHIPFile; + static NodeInfo_CreateFuncPtr NodeInfo_Create; + static NodeInfo_InitFuncPtr NodeInfo_Init; + static ObjectInfo_CreateFuncPtr ObjectInfo_Create; + static ObjectInfo_InitFuncPtr ObjectInfo_Init; + static ParmChoiceInfo_CreateFuncPtr ParmChoiceInfo_Create; + static ParmChoiceInfo_InitFuncPtr ParmChoiceInfo_Init; + static ParmHasExpressionFuncPtr ParmHasExpression; + static ParmHasTagFuncPtr ParmHasTag; + static ParmInfo_CreateFuncPtr ParmInfo_Create; + static ParmInfo_GetFloatValueCountFuncPtr ParmInfo_GetFloatValueCount; + static ParmInfo_GetIntValueCountFuncPtr ParmInfo_GetIntValueCount; + static ParmInfo_GetStringValueCountFuncPtr ParmInfo_GetStringValueCount; + static ParmInfo_InitFuncPtr ParmInfo_Init; + static ParmInfo_IsFloatFuncPtr ParmInfo_IsFloat; + static ParmInfo_IsIntFuncPtr ParmInfo_IsInt; + static ParmInfo_IsNodeFuncPtr ParmInfo_IsNode; + static ParmInfo_IsNonValueFuncPtr ParmInfo_IsNonValue; + static ParmInfo_IsPathFuncPtr ParmInfo_IsPath; + static ParmInfo_IsStringFuncPtr ParmInfo_IsString; + static PartInfo_CreateFuncPtr PartInfo_Create; + static PartInfo_GetAttributeCountByOwnerFuncPtr PartInfo_GetAttributeCountByOwner; + static PartInfo_GetElementCountByAttributeOwnerFuncPtr PartInfo_GetElementCountByAttributeOwner; + static PartInfo_GetElementCountByGroupTypeFuncPtr PartInfo_GetElementCountByGroupType; + static PartInfo_InitFuncPtr PartInfo_Init; + static PausePDGCookFuncPtr PausePDGCook; + static PythonThreadInterpreterLockFuncPtr PythonThreadInterpreterLock; + static QueryNodeInputFuncPtr QueryNodeInput; + static QueryNodeOutputConnectedCountFuncPtr QueryNodeOutputConnectedCount; + static QueryNodeOutputConnectedNodesFuncPtr QueryNodeOutputConnectedNodes; + static RemoveCustomStringFuncPtr RemoveCustomString; + static RemoveMultiparmInstanceFuncPtr RemoveMultiparmInstance; + static RemoveParmExpressionFuncPtr RemoveParmExpression; + static RenameNodeFuncPtr RenameNode; + static RenderCOPToImageFuncPtr RenderCOPToImage; + static RenderTextureToImageFuncPtr RenderTextureToImage; + static ResetSimulationFuncPtr ResetSimulation; + static RevertGeoFuncPtr RevertGeo; + static RevertParmToDefaultFuncPtr RevertParmToDefault; + static RevertParmToDefaultsFuncPtr RevertParmToDefaults; + static SaveGeoToFileFuncPtr SaveGeoToFile; + static SaveGeoToMemoryFuncPtr SaveGeoToMemory; + static SaveHIPFileFuncPtr SaveHIPFile; + static SaveNodeToFileFuncPtr SaveNodeToFile; + static SessionSyncInfo_CreateFuncPtr SessionSyncInfo_Create; + static SetAnimCurveFuncPtr SetAnimCurve; + static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; + static SetAttributeFloatDataFuncPtr SetAttributeFloatData; + static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; + static SetAttributeIntDataFuncPtr SetAttributeIntData; + static SetAttributeStringDataFuncPtr SetAttributeStringData; + static SetCachePropertyFuncPtr SetCacheProperty; + static SetCurveCountsFuncPtr SetCurveCounts; + static SetCurveInfoFuncPtr SetCurveInfo; + static SetCurveKnotsFuncPtr SetCurveKnots; + static SetCurveOrdersFuncPtr SetCurveOrders; + static SetCustomStringFuncPtr SetCustomString; + static SetFaceCountsFuncPtr SetFaceCounts; + static SetGroupMembershipFuncPtr SetGroupMembership; + static SetHeightFieldDataFuncPtr SetHeightFieldData; + static SetImageInfoFuncPtr SetImageInfo; + static SetNodeDisplayFuncPtr SetNodeDisplay; + static SetObjectTransformFuncPtr SetObjectTransform; + static SetParmExpressionFuncPtr SetParmExpression; + static SetParmFloatValueFuncPtr SetParmFloatValue; + static SetParmFloatValuesFuncPtr SetParmFloatValues; + static SetParmIntValueFuncPtr SetParmIntValue; + static SetParmIntValuesFuncPtr SetParmIntValues; + static SetParmNodeValueFuncPtr SetParmNodeValue; + static SetParmStringValueFuncPtr SetParmStringValue; + static SetPartInfoFuncPtr SetPartInfo; + static SetPresetFuncPtr SetPreset; + static SetServerEnvIntFuncPtr SetServerEnvInt; + static SetServerEnvStringFuncPtr SetServerEnvString; + static SetSessionSyncFuncPtr SetSessionSync; + static SetSessionSyncInfoFuncPtr SetSessionSyncInfo; + static SetTimeFuncPtr SetTime; + static SetTimelineOptionsFuncPtr SetTimelineOptions; + static SetTransformAnimCurveFuncPtr SetTransformAnimCurve; + static SetUseHoudiniTimeFuncPtr SetUseHoudiniTime; + static SetVertexListFuncPtr SetVertexList; + static SetViewportFuncPtr SetViewport; + static SetVolumeInfoFuncPtr SetVolumeInfo; + static SetVolumeTileFloatDataFuncPtr SetVolumeTileFloatData; + static SetVolumeTileIntDataFuncPtr SetVolumeTileIntData; + static SetVolumeVoxelFloatDataFuncPtr SetVolumeVoxelFloatData; + static SetVolumeVoxelIntDataFuncPtr SetVolumeVoxelIntData; + static SetWorkitemFloatDataFuncPtr SetWorkitemFloatData; + static SetWorkitemIntDataFuncPtr SetWorkitemIntData; + static SetWorkitemStringDataFuncPtr SetWorkitemStringData; + static StartThriftNamedPipeServerFuncPtr StartThriftNamedPipeServer; + static StartThriftSocketServerFuncPtr StartThriftSocketServer; + static ThriftServerOptions_CreateFuncPtr ThriftServerOptions_Create; + static ThriftServerOptions_InitFuncPtr ThriftServerOptions_Init; + static TimelineOptions_CreateFuncPtr TimelineOptions_Create; + static TimelineOptions_InitFuncPtr TimelineOptions_Init; + static TransformEuler_CreateFuncPtr TransformEuler_Create; + static TransformEuler_InitFuncPtr TransformEuler_Init; + static Transform_CreateFuncPtr Transform_Create; + static Transform_InitFuncPtr Transform_Init; + static Viewport_CreateFuncPtr Viewport_Create; + static VolumeInfo_CreateFuncPtr VolumeInfo_Create; + static VolumeInfo_InitFuncPtr VolumeInfo_Init; + static VolumeTileInfo_CreateFuncPtr VolumeTileInfo_Create; + static VolumeTileInfo_InitFuncPtr VolumeTileInfo_Init; + +public: + + static HAPI_Result AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + static HAPI_Result AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + static HAPI_AssetInfo AssetInfo_CreateEmptyStub(); + static void AssetInfo_InitEmptyStub(HAPI_AssetInfo * in); + static HAPI_AttributeInfo AttributeInfo_CreateEmptyStub(); + static void AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in); + static HAPI_Result BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path); + static HAPI_Result CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + static HAPI_Result CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); + static HAPI_Result CleanupEmptyStub(const HAPI_Session * session); + static HAPI_Result ClearConnectionErrorEmptyStub(); + static HAPI_Result CloseSessionEmptyStub(const HAPI_Session * session); + static HAPI_Result CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + static HAPI_Result ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); + static HAPI_Result ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + static HAPI_Result ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); + static HAPI_Result ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + static HAPI_Result ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); + static HAPI_Result ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + static HAPI_Result ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); + static HAPI_Result ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); + static HAPI_Result CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); + static HAPI_Bool CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right); + static HAPI_CookOptions CookOptions_CreateEmptyStub(); + static void CookOptions_InitEmptyStub(HAPI_CookOptions * in); + static HAPI_Result CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); + static HAPI_Result CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); + static HAPI_Result CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); + static HAPI_Result CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); + static HAPI_Result CreateInProcessSessionEmptyStub(HAPI_Session * session); + static HAPI_Result CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); + static HAPI_Result CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); + static HAPI_Result CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name); + static HAPI_Result CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port); + static HAPI_Result CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); + static HAPI_CurveInfo CurveInfo_CreateEmptyStub(); + static void CurveInfo_InitEmptyStub(HAPI_CurveInfo * in); + static HAPI_Result DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + static HAPI_Result DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + static HAPI_Result DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); + static HAPI_Result DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); + static HAPI_Result DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); + static HAPI_Result ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); + static HAPI_Result ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); + static HAPI_GeoInfo GeoInfo_CreateEmptyStub(); + static int GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type); + static void GeoInfo_InitEmptyStub(HAPI_GeoInfo * in); + static HAPI_Result GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count); + static HAPI_Result GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); + static HAPI_Result GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); + static HAPI_Result GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); + static HAPI_Result GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); + static HAPI_Result GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); + static HAPI_Result GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); + static HAPI_Result GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); + static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + static HAPI_Result GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + static HAPI_Result GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); + static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); + static HAPI_Result GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); + static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); + static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); + static HAPI_Result GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); + static HAPI_Result GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); + static HAPI_Result GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length); + static HAPI_Result GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); + static HAPI_Result GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + static HAPI_Result GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear); + static HAPI_Result GetConnectionErrorLengthEmptyStub(int * buffer_length); + static HAPI_Result GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count); + static HAPI_Result GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count); + static HAPI_Result GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); + static HAPI_Result GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); + static HAPI_Result GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); + static HAPI_Result GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); + static HAPI_Result GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value); + static HAPI_Result GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); + static HAPI_Result GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + static HAPI_Result GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); + static HAPI_Result GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); + static HAPI_Result GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + static HAPI_Result GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + static HAPI_Result GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + static HAPI_Result GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + static HAPI_Result GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count); + static HAPI_Result GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); + static HAPI_Result GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); + static HAPI_Result GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); + static HAPI_Result GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); + static HAPI_Result GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); + static HAPI_Result GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); + static HAPI_Result GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); + static HAPI_Result GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); + static HAPI_Result GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); + static HAPI_Result GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + static HAPI_Result GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); + static HAPI_Result GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); + static HAPI_Result GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + static HAPI_Result GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); + static HAPI_Result GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); + static HAPI_Result GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); + static HAPI_Result GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + static HAPI_Result GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); + static HAPI_Result GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); + static HAPI_Result GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); + static HAPI_Result GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); + static HAPI_Result GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num); + static HAPI_Result GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); + static HAPI_Result GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + static HAPI_Result GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); + static HAPI_Result GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); + static HAPI_Result GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); + static HAPI_Result GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); + static HAPI_Result GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); + static HAPI_Result GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); + static HAPI_Result GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); + static HAPI_Result GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); + static HAPI_Result GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); + static HAPI_Result GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); + static HAPI_Result GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); + static HAPI_Result GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); + static HAPI_Result GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); + static HAPI_Result GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); + static HAPI_Result GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); + static HAPI_Result GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); + static HAPI_Result GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); + static HAPI_Result GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); + static HAPI_Result GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); + static HAPI_Result GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); + static HAPI_Result GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); + static HAPI_Result GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); + static HAPI_Result GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); + static HAPI_Result GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); + static HAPI_Result GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); + static HAPI_Result GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value); + static HAPI_Result GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); + static HAPI_Result GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count); + static HAPI_Result GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); + static HAPI_Result GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); + static HAPI_Result GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); + static HAPI_Result GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); + static HAPI_Result GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status); + static HAPI_Result GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); + static HAPI_Result GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); + static HAPI_Result GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); + static HAPI_Result GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length); + static HAPI_Result GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); + static HAPI_Result GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); + static HAPI_Result GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count); + static HAPI_Result GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); + static HAPI_Result GetTimeEmptyStub(const HAPI_Session * session, float * time); + static HAPI_Result GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); + static HAPI_Result GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + static HAPI_Result GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled); + static HAPI_Result GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); + static HAPI_Result GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport); + static HAPI_Result GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); + static HAPI_Result GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); + static HAPI_Result GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); + static HAPI_Result GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); + static HAPI_Result GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); + static HAPI_Result GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); + static HAPI_Result GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); + static HAPI_Result GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); + static HAPI_Result GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); + static HAPI_Result GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); + static HAPI_Result GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); + static HAPI_Result GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); + static HAPI_Result GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); + static HAPI_Result GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); + static HAPI_HandleBindingInfo HandleBindingInfo_CreateEmptyStub(); + static void HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in); + static HAPI_HandleInfo HandleInfo_CreateEmptyStub(); + static void HandleInfo_InitEmptyStub(HAPI_HandleInfo * in); + static HAPI_ImageFileFormat ImageFileFormat_CreateEmptyStub(); + static void ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in); + static HAPI_ImageInfo ImageInfo_CreateEmptyStub(); + static void ImageInfo_InitEmptyStub(HAPI_ImageInfo * in); + static HAPI_Result InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); + static HAPI_Result InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + static HAPI_Result InterruptEmptyStub(const HAPI_Session * session); + static HAPI_Result IsInitializedEmptyStub(const HAPI_Session * session); + static HAPI_Result IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); + static HAPI_Result IsSessionValidEmptyStub(const HAPI_Session * session); + static HAPI_Keyframe Keyframe_CreateEmptyStub(); + static void Keyframe_InitEmptyStub(HAPI_Keyframe * in); + static HAPI_Result LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + static HAPI_Result LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + static HAPI_Result LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_Result LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); + static HAPI_Result LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); + static HAPI_Result LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); + static HAPI_MaterialInfo MaterialInfo_CreateEmptyStub(); + static void MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in); + static HAPI_Result MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); + static HAPI_NodeInfo NodeInfo_CreateEmptyStub(); + static void NodeInfo_InitEmptyStub(HAPI_NodeInfo * in); + static HAPI_ObjectInfo ObjectInfo_CreateEmptyStub(); + static void ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in); + static HAPI_ParmChoiceInfo ParmChoiceInfo_CreateEmptyStub(); + static void ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in); + static HAPI_Result ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); + static HAPI_Result ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); + static HAPI_ParmInfo ParmInfo_CreateEmptyStub(); + static int ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in); + static int ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in); + static int ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in); + static void ParmInfo_InitEmptyStub(HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in); + static HAPI_PartInfo PartInfo_CreateEmptyStub(); + static int PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + static int PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + static int PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type); + static void PartInfo_InitEmptyStub(HAPI_PartInfo * in); + static HAPI_Result PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + static HAPI_Result PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked); + static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); + static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); + static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); + static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle); + static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); + static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); + static HAPI_Result RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id); + static HAPI_Result RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); + static HAPI_Result ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); + static HAPI_Result RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); + static HAPI_Result SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_Result SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); + static HAPI_Result SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); + static HAPI_Result SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_SessionSyncInfo SessionSyncInfo_CreateEmptyStub(); + static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); + static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); + static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); + static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); + static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); + static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); + static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value); + static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); + static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); + static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); + static HAPI_Result SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); + static HAPI_Result SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); + static HAPI_Result SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); + static HAPI_Result SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + static HAPI_Result SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); + static HAPI_Result SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); + static HAPI_Result SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); + static HAPI_Result SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); + static HAPI_Result SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); + static HAPI_Result SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + static HAPI_Result SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); + static HAPI_Result SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); + static HAPI_Result SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value); + static HAPI_Result SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value); + static HAPI_Result SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable); + static HAPI_Result SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); + static HAPI_Result SetTimeEmptyStub(const HAPI_Session * session, float time); + static HAPI_Result SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); + static HAPI_Result SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + static HAPI_Result SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled); + static HAPI_Result SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); + static HAPI_Result SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport); + static HAPI_Result SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); + static HAPI_Result SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); + static HAPI_Result SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); + static HAPI_Result SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); + static HAPI_Result SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); + static HAPI_Result SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); + static HAPI_Result SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); + static HAPI_Result SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); + static HAPI_Result StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); + static HAPI_Result StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); + static HAPI_ThriftServerOptions ThriftServerOptions_CreateEmptyStub(); + static void ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in); + static HAPI_TimelineOptions TimelineOptions_CreateEmptyStub(); + static void TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in); + static HAPI_TransformEuler TransformEuler_CreateEmptyStub(); + static void TransformEuler_InitEmptyStub(HAPI_TransformEuler * in); + static HAPI_Transform Transform_CreateEmptyStub(); + static void Transform_InitEmptyStub(HAPI_Transform * in); + static HAPI_Viewport Viewport_CreateEmptyStub(); + static HAPI_VolumeInfo VolumeInfo_CreateEmptyStub(); + static void VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in); + static HAPI_VolumeTileInfo VolumeTileInfo_CreateEmptyStub(); + static void VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in); +}; diff --git a/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs b/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs index 9951bbe2a..d51f6ac40 100644 --- a/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs +++ b/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs @@ -1,123 +1,123 @@ -/* - * Copyright (c) <2020> 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. - * - * Produced by: - * Side Effects Software Inc - * 123 Front Street West, Suite 1401 - * Toronto, Ontario - * Canada M5J 2M2 - * 416-504-9876 - * - */ - -using UnrealBuildTool; -using System; -using System.IO; - -public class HoudiniEngineEditor : ModuleRules -{ - public HoudiniEngineEditor( ReadOnlyTargetRules Target ) : base( Target ) - { - bPrecompile = true; - PCHUsage = PCHUsageMode.NoSharedPCHs; - PrivatePCHHeaderFile = "Private/HoudiniEngineEditorPrivatePCH.h"; - - // Check if we are compiling on unsupported platforms. - if ( Target.Platform != UnrealTargetPlatform.Win64 && - Target.Platform != UnrealTargetPlatform.Mac && - Target.Platform != UnrealTargetPlatform.Linux ) - { - string Err = string.Format( "Houdini Engine Editor: Compiling for unsupported platform." ); - System.Console.WriteLine( Err ); - throw new BuildException( Err ); - } - - PublicIncludePaths.AddRange( - new string[] { - Path.Combine(ModuleDirectory, "Public") - } - ); - - PrivateIncludePaths.AddRange( - new string[] { - "HoudiniEngine/Private", - "HoudiniEngineRuntime/Private" - } - ); - - PrivateIncludePathModuleNames.AddRange( - new string[] { - "PlacementMode" - } - ); - - // Add common dependencies. - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "HoudiniEngine", - "HoudiniEngineRuntime", - "Slate", - "SlateCore", - "Landscape", - "Foliage" - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "AppFramework", - "AssetTools", - "ContentBrowser", - "DesktopWidgets", - "EditorStyle", - "EditorWidgets", - "Engine", - "InputCore", - "LevelEditor", - "MainFrame", - "Projects", - "PropertyEditor", - "RHI", - "RawMesh", - "RenderCore", - "TargetPlatform", - "UnrealEd", - "ApplicationCore", - "CurveEditor", - "Json", - "SceneOutliner", - "PropertyPath", - "MaterialEditor" - } - ); - - DynamicallyLoadedModuleNames.AddRange( - new string[] - { - "PlacementMode", - } - ); - } -} +/* + * Copyright (c) <2020> 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. + * + * Produced by: + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +using UnrealBuildTool; +using System; +using System.IO; + +public class HoudiniEngineEditor : ModuleRules +{ + public HoudiniEngineEditor( ReadOnlyTargetRules Target ) : base( Target ) + { + bPrecompile = true; + PCHUsage = PCHUsageMode.NoSharedPCHs; + PrivatePCHHeaderFile = "Private/HoudiniEngineEditorPrivatePCH.h"; + + // Check if we are compiling on unsupported platforms. + if ( Target.Platform != UnrealTargetPlatform.Win64 && + Target.Platform != UnrealTargetPlatform.Mac && + Target.Platform != UnrealTargetPlatform.Linux ) + { + string Err = string.Format( "Houdini Engine Editor: Compiling for unsupported platform." ); + System.Console.WriteLine( Err ); + throw new BuildException( Err ); + } + + PublicIncludePaths.AddRange( + new string[] { + Path.Combine(ModuleDirectory, "Public") + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "HoudiniEngine/Private", + "HoudiniEngineRuntime/Private" + } + ); + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "PlacementMode" + } + ); + + // Add common dependencies. + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "HoudiniEngine", + "HoudiniEngineRuntime", + "Slate", + "SlateCore", + "Landscape", + "Foliage" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "AppFramework", + "AssetTools", + "ContentBrowser", + "DesktopWidgets", + "EditorStyle", + "EditorWidgets", + "Engine", + "InputCore", + "LevelEditor", + "MainFrame", + "Projects", + "PropertyEditor", + "RHI", + "RawMesh", + "RenderCore", + "TargetPlatform", + "UnrealEd", + "ApplicationCore", + "CurveEditor", + "Json", + "SceneOutliner", + "PropertyPath", + "MaterialEditor" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + "PlacementMode", + } + ); + } +} diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp index 68599e511..5ac0ad6f4 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp @@ -1,459 +1,459 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "AssetTypeActions_HoudiniAsset.h" -#include "HoudiniAsset.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniTool.h" -#include "HoudiniEngineEditorUtils.h" - -#include "EditorReimportHandler.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "HAL/FileManager.h" -#include "EditorFramework/AssetImportData.h" -#include "LevelEditor.h" -#include "Modules/ModuleManager.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FText -FAssetTypeActions_HoudiniAsset::GetName() const -{ - return LOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset"); -} - -FColor -FAssetTypeActions_HoudiniAsset::GetTypeColor() const -{ - return FColor(255, 165, 0); -} - -UClass * -FAssetTypeActions_HoudiniAsset::GetSupportedClass() const -{ - return UHoudiniAsset::StaticClass(); -} - -uint32 -FAssetTypeActions_HoudiniAsset::GetCategories() -{ - return EAssetTypeCategories::Misc; -} - -/* -UThumbnailInfo * -FAssetTypeActions_HoudiniAsset::GetThumbnailInfo(UObject * Asset) const -{ - if (!Asset || Asset->IsPendingKill()) - return nullptr; - - UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >(Asset); - UThumbnailInfo * ThumbnailInfo = HoudiniAsset->ThumbnailInfo; - if (!ThumbnailInfo) - { - // If we have no thumbnail information, construct it. - ThumbnailInfo = NewObject< USceneThumbnailInfo >(HoudiniAsset, USceneThumbnailInfo::StaticClass()); - HoudiniAsset->ThumbnailInfo = ThumbnailInfo; - } - - return ThumbnailInfo; -} -*/ - -bool -FAssetTypeActions_HoudiniAsset::HasActions(const TArray< UObject * > & InObjects) const -{ - return true; -} - -void -FAssetTypeActions_HoudiniAsset::GetActions(const TArray & InObjects, class FMenuBuilder & MenuBuilder) -{ - bool ValidObjects = false; - TArray> HoudiniAssets; - if (InObjects.Num() > 0) - { - HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); - ValidObjects = true; - } - - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Reimport", "Reimport"), - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ReimportTooltip", "Reimport selected Houdini Assets."), - FSlateIcon(StyleSetName, "HoudiniEngine._Reset"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteReimport, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAll", "Rebuild All Instances"), - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAllTooltip", "Reimports and rebuild all instances of the selected Houdini Assets."), - FSlateIcon(StyleSetName, "HoudiniEngine._RebuildAll"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorer", "Find Source"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorerTooltip", - "Opens explorer at the location of this asset."), - FSlateIcon(StyleSetName, "HoudiniEngine.Hou_OpenInHoudinidiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudini", "Open in Houdini"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudiniTooltip", - "Opens the selected asset in Houdini."), - FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuSeparator(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Instantiate", "Instantiate"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateTooltip", - "Instantiate the selected asset in the current world."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiate, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOrigin", "Instantiate at the origin"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOriginTooltip", - "Instantiate the selected asset in the current world. The Houdini Asset Actor will be created at the origin of the level (0,0,0)."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuSeparator(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingle", "Apply to the current selection (single input)"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingleTooltip", - "Applies the selected asset to the current world selection. All the selected object will be assigned to the first input."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMulti", "Apply to the current selection (multiple inputs )"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMultiTooltip", - "Applies the selected asset to the current world selection. Each selected object will be assigned to its own input (one object per input)."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatch", "Batch Apply to the current selection"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatchTooltip", - "Batch apply the selected asset to the current world selection. An instance of the selected Houdini asset will be created for each selected object."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); -} - - -bool -FAssetTypeActions_HoudiniAsset::AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) -{ - if (ActivationType == EAssetTypeActivationMethod::DoubleClicked) - { - bool ValidObjects = false; - TArray> HoudiniAssets; - if (InObjects.Num() > 0) - { - HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); - ValidObjects = true; - } - - if (ValidObjects) - { - ExecuteInstantiate(HoudiniAssets); - return true; - } - } - - return false; -} - - -TSharedRef -FAssetTypeActions_HoudiniAsset::AddLevelEditorMenuExtenders(TArray> HoudiniAssets) -{ - FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); - TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); - - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - TSharedRef Extender = MakeShareable(new FExtender); - Extender->AddMenuExtension( - "ActorAsset", - EExtensionHook::After, - LevelEditorCommandBindings, - FMenuExtensionDelegate::CreateLambda([this, HoudiniAssets, StyleSetName](FMenuBuilder& MenuBuilder) - { - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorer", "Find Source HDA"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorerTooltip", "Opens an explorer at the location of this actor's source HDA file."), - FSlateIcon(StyleSetName, "HoudiniEngine.DigitalAsset"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudini", "Open HDA in Houdini"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudiniTooltip", "Opens the selected asset's source HDA file in Houdini."), - FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - }) - ); - - return Extender; -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteReimport(TArray> InHoudiniAssetPtrs) -{ - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (HoudiniAsset) - FReimportManager::Instance()->Reimport(HoudiniAsset, true); - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs) -{ - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (HoudiniAsset && HoudiniAsset->AssetImportData) - { - const FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); - if (SourceFilePath.Len() && IFileManager::Get().FileSize(*SourceFilePath) != INDEX_NONE) - return FPlatformProcess::ExploreFolder(*SourceFilePath); - } - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs) -{ - if (!FHoudiniEngine::IsInitialized()) - return; - - if (InHoudiniAssetPtrs.Num() != 1) - return; - - UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - return; - - FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); - if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) - return; - - if (!FPaths::FileExists(SourceFilePath)) - return; - - // We'll need to modify the file name for expanded .hda - FString FileExtension = FPaths::GetExtension(SourceFilePath); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) - { - // the .hda directory is what we're actually interested in loading - SourceFilePath = FPaths::GetPath(SourceFilePath); - } - - if (FPaths::IsRelative(SourceFilePath)) - FPaths::ConvertRelativePathToFull(SourceFilePath); - - // Then open the HDA file in Houdini - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + "/hview"; - - FPlatformProcess::CreateProc( - HoudiniLocation.GetCharArray().GetData(), - SourceFilePath.GetCharArray().GetData(), - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); -} - - -void -FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs) -{ - // Reimports and then rebuild all instances of the asset - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (!HoudiniAsset) - continue; - - // Reimports the asset - FReimportManager::Instance()->Reimport(HoudiniAsset, true); - - // Rebuilds all instances of that asset in the scene - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * Component = *Itr; - if (Component && (Component->GetHoudiniAsset() == HoudiniAsset)) - { - Component->MarkAsNeedRebuild(); - } - } - } -} - - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE); -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI); -} -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH); -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType) -{ - if (InHoudiniAssetPtrs.Num() != 1) - return; - - UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - return; - - - FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(HoudiniAsset, InType, EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY); - /* - // Creating a temporary tool for the selected asset - TSoftObjectPtr HoudiniAssetPtr(HoudiniAsset); - FHoudiniTool HoudiniTool( - HoudiniAssetPtr, - FText::FromString(HoudiniAsset->GetName()), - Type, - EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY, - FText(), - NULL, - FString(), - false, - FFilePath(), - FHoudiniToolDirectory(), - FString()); - - SHoudiniToolPalette::InstantiateHoudiniTool(&HoudiniTool); - */ -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs) -{ - for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) - { - UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - continue; - - FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, FTransform::Identity); - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteInstantiate(TArray> InHoudiniAssetPtrs) -{ - FTransform DefaultTransform = FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform(); - for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) - { - UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - continue; - - FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, DefaultTransform); - } -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "AssetTypeActions_HoudiniAsset.h" +#include "HoudiniAsset.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniTool.h" +#include "HoudiniEngineEditorUtils.h" + +#include "EditorReimportHandler.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "HAL/FileManager.h" +#include "EditorFramework/AssetImportData.h" +#include "LevelEditor.h" +#include "Modules/ModuleManager.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FText +FAssetTypeActions_HoudiniAsset::GetName() const +{ + return LOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset"); +} + +FColor +FAssetTypeActions_HoudiniAsset::GetTypeColor() const +{ + return FColor(255, 165, 0); +} + +UClass * +FAssetTypeActions_HoudiniAsset::GetSupportedClass() const +{ + return UHoudiniAsset::StaticClass(); +} + +uint32 +FAssetTypeActions_HoudiniAsset::GetCategories() +{ + return EAssetTypeCategories::Misc; +} + +/* +UThumbnailInfo * +FAssetTypeActions_HoudiniAsset::GetThumbnailInfo(UObject * Asset) const +{ + if (!Asset || Asset->IsPendingKill()) + return nullptr; + + UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >(Asset); + UThumbnailInfo * ThumbnailInfo = HoudiniAsset->ThumbnailInfo; + if (!ThumbnailInfo) + { + // If we have no thumbnail information, construct it. + ThumbnailInfo = NewObject< USceneThumbnailInfo >(HoudiniAsset, USceneThumbnailInfo::StaticClass()); + HoudiniAsset->ThumbnailInfo = ThumbnailInfo; + } + + return ThumbnailInfo; +} +*/ + +bool +FAssetTypeActions_HoudiniAsset::HasActions(const TArray< UObject * > & InObjects) const +{ + return true; +} + +void +FAssetTypeActions_HoudiniAsset::GetActions(const TArray & InObjects, class FMenuBuilder & MenuBuilder) +{ + bool ValidObjects = false; + TArray> HoudiniAssets; + if (InObjects.Num() > 0) + { + HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); + ValidObjects = true; + } + + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Reimport", "Reimport"), + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ReimportTooltip", "Reimport selected Houdini Assets."), + FSlateIcon(StyleSetName, "HoudiniEngine._Reset"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteReimport, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAll", "Rebuild All Instances"), + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAllTooltip", "Reimports and rebuild all instances of the selected Houdini Assets."), + FSlateIcon(StyleSetName, "HoudiniEngine._RebuildAll"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorer", "Find Source"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorerTooltip", + "Opens explorer at the location of this asset."), + FSlateIcon(StyleSetName, "HoudiniEngine.Hou_OpenInHoudinidiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudini", "Open in Houdini"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudiniTooltip", + "Opens the selected asset in Houdini."), + FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuSeparator(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Instantiate", "Instantiate"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateTooltip", + "Instantiate the selected asset in the current world."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiate, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOrigin", "Instantiate at the origin"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOriginTooltip", + "Instantiate the selected asset in the current world. The Houdini Asset Actor will be created at the origin of the level (0,0,0)."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuSeparator(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingle", "Apply to the current selection (single input)"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingleTooltip", + "Applies the selected asset to the current world selection. All the selected object will be assigned to the first input."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMulti", "Apply to the current selection (multiple inputs )"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMultiTooltip", + "Applies the selected asset to the current world selection. Each selected object will be assigned to its own input (one object per input)."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatch", "Batch Apply to the current selection"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatchTooltip", + "Batch apply the selected asset to the current world selection. An instance of the selected Houdini asset will be created for each selected object."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); +} + + +bool +FAssetTypeActions_HoudiniAsset::AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) +{ + if (ActivationType == EAssetTypeActivationMethod::DoubleClicked) + { + bool ValidObjects = false; + TArray> HoudiniAssets; + if (InObjects.Num() > 0) + { + HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); + ValidObjects = true; + } + + if (ValidObjects) + { + ExecuteInstantiate(HoudiniAssets); + return true; + } + } + + return false; +} + + +TSharedRef +FAssetTypeActions_HoudiniAsset::AddLevelEditorMenuExtenders(TArray> HoudiniAssets) +{ + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); + + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + TSharedRef Extender = MakeShareable(new FExtender); + Extender->AddMenuExtension( + "ActorAsset", + EExtensionHook::After, + LevelEditorCommandBindings, + FMenuExtensionDelegate::CreateLambda([this, HoudiniAssets, StyleSetName](FMenuBuilder& MenuBuilder) + { + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorer", "Find Source HDA"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorerTooltip", "Opens an explorer at the location of this actor's source HDA file."), + FSlateIcon(StyleSetName, "HoudiniEngine.DigitalAsset"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudini", "Open HDA in Houdini"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudiniTooltip", "Opens the selected asset's source HDA file in Houdini."), + FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + }) + ); + + return Extender; +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteReimport(TArray> InHoudiniAssetPtrs) +{ + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (HoudiniAsset) + FReimportManager::Instance()->Reimport(HoudiniAsset, true); + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs) +{ + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (HoudiniAsset && HoudiniAsset->AssetImportData) + { + const FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); + if (SourceFilePath.Len() && IFileManager::Get().FileSize(*SourceFilePath) != INDEX_NONE) + return FPlatformProcess::ExploreFolder(*SourceFilePath); + } + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs) +{ + if (!FHoudiniEngine::IsInitialized()) + return; + + if (InHoudiniAssetPtrs.Num() != 1) + return; + + UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + return; + + FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); + if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) + return; + + if (!FPaths::FileExists(SourceFilePath)) + return; + + // We'll need to modify the file name for expanded .hda + FString FileExtension = FPaths::GetExtension(SourceFilePath); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) + { + // the .hda directory is what we're actually interested in loading + SourceFilePath = FPaths::GetPath(SourceFilePath); + } + + if (FPaths::IsRelative(SourceFilePath)) + FPaths::ConvertRelativePathToFull(SourceFilePath); + + // Then open the HDA file in Houdini + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + "/hview"; + + FPlatformProcess::CreateProc( + HoudiniLocation.GetCharArray().GetData(), + SourceFilePath.GetCharArray().GetData(), + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); +} + + +void +FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs) +{ + // Reimports and then rebuild all instances of the asset + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (!HoudiniAsset) + continue; + + // Reimports the asset + FReimportManager::Instance()->Reimport(HoudiniAsset, true); + + // Rebuilds all instances of that asset in the scene + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * Component = *Itr; + if (Component && (Component->GetHoudiniAsset() == HoudiniAsset)) + { + Component->MarkAsNeedRebuild(); + } + } + } +} + + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE); +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI); +} +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH); +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType) +{ + if (InHoudiniAssetPtrs.Num() != 1) + return; + + UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + return; + + + FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(HoudiniAsset, InType, EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY); + /* + // Creating a temporary tool for the selected asset + TSoftObjectPtr HoudiniAssetPtr(HoudiniAsset); + FHoudiniTool HoudiniTool( + HoudiniAssetPtr, + FText::FromString(HoudiniAsset->GetName()), + Type, + EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY, + FText(), + NULL, + FString(), + false, + FFilePath(), + FHoudiniToolDirectory(), + FString()); + + SHoudiniToolPalette::InstantiateHoudiniTool(&HoudiniTool); + */ +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs) +{ + for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) + { + UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + continue; + + FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, FTransform::Identity); + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteInstantiate(TArray> InHoudiniAssetPtrs) +{ + FTransform DefaultTransform = FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform(); + for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) + { + UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + continue; + + FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, DefaultTransform); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h index ac8806ebf..f29ee8874 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h @@ -1,84 +1,84 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "AssetTypeActions_Base.h" - -class UClass; -class UObject; -class UHoudiniAsset; - -enum class EHoudiniToolType : uint8; - -class FAssetTypeActions_HoudiniAsset : public FAssetTypeActions_Base -{ - public: - - // FAssetTypeActions_Base methods. - virtual FText GetName() const override; - virtual FColor GetTypeColor() const override; - virtual UClass* GetSupportedClass() const override; - virtual uint32 GetCategories() override; - //virtual UThumbnailInfo * GetThumbnailInfo(UObject * Asset) const override; - virtual bool HasActions(const TArray< UObject * > & InObjects) const override; - virtual void GetActions(const TArray< UObject * > & InObjects, class FMenuBuilder & MenuBuilder) override; - - virtual bool AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) override; - - TSharedRef AddLevelEditorMenuExtenders(TArray> InHoudiniAssetPtrs); - - protected: - - // Handler for reimport option. - void ExecuteReimport(TArray> InHoudiniAssetPtrs); - - // Handler for rebuild all option - void ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs); - - // Handler for find in explorer option - void ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs); - - // Handler for the open in Houdini option - void ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs); - - // Handler to apply the current hda to the current world selection (single input) - void ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs); - - // Handler to apply the current hda to the current world selection (multi input) - void ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs); - - // Handler to batch apply the current hda to the current world selection - void ExecuteApplyBatch(TArray> InHoudiniAssetPtrs ); - - // Handler to instantiate the HDA in the world - void ExecuteInstantiate(TArray> InHoudiniAssetPtrs); - - // Handler to instantiate the HDA in the world, actor is placed at the origin - void ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs); - - void ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType); -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "AssetTypeActions_Base.h" + +class UClass; +class UObject; +class UHoudiniAsset; + +enum class EHoudiniToolType : uint8; + +class FAssetTypeActions_HoudiniAsset : public FAssetTypeActions_Base +{ + public: + + // FAssetTypeActions_Base methods. + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + //virtual UThumbnailInfo * GetThumbnailInfo(UObject * Asset) const override; + virtual bool HasActions(const TArray< UObject * > & InObjects) const override; + virtual void GetActions(const TArray< UObject * > & InObjects, class FMenuBuilder & MenuBuilder) override; + + virtual bool AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) override; + + TSharedRef AddLevelEditorMenuExtenders(TArray> InHoudiniAssetPtrs); + + protected: + + // Handler for reimport option. + void ExecuteReimport(TArray> InHoudiniAssetPtrs); + + // Handler for rebuild all option + void ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs); + + // Handler for find in explorer option + void ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs); + + // Handler for the open in Houdini option + void ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs); + + // Handler to apply the current hda to the current world selection (single input) + void ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs); + + // Handler to apply the current hda to the current world selection (multi input) + void ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs); + + // Handler to batch apply the current hda to the current world selection + void ExecuteApplyBatch(TArray> InHoudiniAssetPtrs ); + + // Handler to instantiate the HDA in the world + void ExecuteInstantiate(TArray> InHoudiniAssetPtrs); + + // Handler to instantiate the HDA in the world, actor is placed at the origin + void ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs); + + void ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp index 8991f9796..58797cb32 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp @@ -1,117 +1,117 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetActorFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineUtils.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniAssetActorFactory::UHoudiniAssetActorFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - DisplayName = LOCTEXT("HoudiniAssetDisplayName", "Houdini Engine Asset"); - NewActorClass = AHoudiniAssetActor::StaticClass(); -} - -bool -UHoudiniAssetActorFactory::CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) -{ - if (!AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UHoudiniAsset::StaticClass())) - { - OutErrorMsg = NSLOCTEXT("CanCreateActor", "NoHoudiniAsset", "A valid Houdini Engine asset must be specified."); - return false; - } - - return true; -} - -UObject * -UHoudiniAssetActorFactory::GetAssetFromActorInstance(AActor * Instance) -{ - check(Instance->IsA(NewActorClass)); - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(Instance); - - check(HoudiniAssetActor->GetHoudiniAssetComponent()); - return HoudiniAssetActor->GetHoudiniAssetComponent()->HoudiniAsset; -} - -void -UHoudiniAssetActorFactory::PostSpawnActor(UObject * Asset, AActor * NewActor) -{ - HOUDINI_LOG_MESSAGE(TEXT("PostSpawnActor %s, supplied Asset = 0x%0.8p"), *NewActor->GetName(), Asset); - - UHoudiniAsset * HoudiniAsset = CastChecked(Asset); - if (HoudiniAsset) - { - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(NewActor); - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - check(HoudiniAssetComponent); - - //HoudiniAssetComponent->UnregisterComponent(); - //HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - //HoudiniAssetComponent->RegisterComponent(); - - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); - - if (!HoudiniAssetActor->IsUsedForPreview()) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); - } - } -} - -void -UHoudiniAssetActorFactory::PostCreateBlueprint(UObject * Asset, AActor * CDO) -{ - HOUDINI_LOG_MESSAGE(TEXT("PostCreateBlueprint, supplied Asset = 0x%0.8p"), Asset); - - UHoudiniAsset * HoudiniAsset = CastChecked(Asset); - if (HoudiniAsset) - { - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(CDO); - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - check(HoudiniAssetComponent); - - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); - - if (!HoudiniAssetActor->IsUsedForPreview()) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); - } - } -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetActorFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineUtils.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetActorFactory::UHoudiniAssetActorFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + DisplayName = LOCTEXT("HoudiniAssetDisplayName", "Houdini Engine Asset"); + NewActorClass = AHoudiniAssetActor::StaticClass(); +} + +bool +UHoudiniAssetActorFactory::CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) +{ + if (!AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UHoudiniAsset::StaticClass())) + { + OutErrorMsg = NSLOCTEXT("CanCreateActor", "NoHoudiniAsset", "A valid Houdini Engine asset must be specified."); + return false; + } + + return true; +} + +UObject * +UHoudiniAssetActorFactory::GetAssetFromActorInstance(AActor * Instance) +{ + check(Instance->IsA(NewActorClass)); + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(Instance); + + check(HoudiniAssetActor->GetHoudiniAssetComponent()); + return HoudiniAssetActor->GetHoudiniAssetComponent()->HoudiniAsset; +} + +void +UHoudiniAssetActorFactory::PostSpawnActor(UObject * Asset, AActor * NewActor) +{ + HOUDINI_LOG_MESSAGE(TEXT("PostSpawnActor %s, supplied Asset = 0x%0.8p"), *NewActor->GetName(), Asset); + + UHoudiniAsset * HoudiniAsset = CastChecked(Asset); + if (HoudiniAsset) + { + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(NewActor); + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + check(HoudiniAssetComponent); + + //HoudiniAssetComponent->UnregisterComponent(); + //HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + //HoudiniAssetComponent->RegisterComponent(); + + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); + + if (!HoudiniAssetActor->IsUsedForPreview()) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); + } + } +} + +void +UHoudiniAssetActorFactory::PostCreateBlueprint(UObject * Asset, AActor * CDO) +{ + HOUDINI_LOG_MESSAGE(TEXT("PostCreateBlueprint, supplied Asset = 0x%0.8p"), Asset); + + UHoudiniAsset * HoudiniAsset = CastChecked(Asset); + if (HoudiniAsset) + { + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(CDO); + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + check(HoudiniAssetComponent); + + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); + + if (!HoudiniAssetActor->IsUsedForPreview()) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h index 6b22120f9..b3a99dc97 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h @@ -1,58 +1,58 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ActorFactories/ActorFactory.h" -#include "HoudiniAssetActorFactory.generated.h" - -class FText; -class AActor; -class UObject; -class UHoudiniAssetComponent; - -struct FAssetData; - -UCLASS(config = Editor) -class UHoudiniAssetActorFactory : public UActorFactory -{ - GENERATED_UCLASS_BODY() - -public: - // UActorFactory methods: - // Return true if Actor can be created from a given asset. - virtual bool CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) override; - // Given an instance of an actor pertaining to this factory, find the asset that should be used to create a new actor. - virtual UObject * GetAssetFromActorInstance(AActor * Instance) override; - // Modify the actor after it has been spawned. - virtual void PostSpawnActor(UObject * Asset, AActor * NewActor) override; - // Called after a blueprint is created by this factory to update the blueprint's CDO properties - // with state from the asset for this factory. - virtual void PostCreateBlueprint(UObject * Asset, AActor * CDO) override; - -protected: - bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ActorFactories/ActorFactory.h" +#include "HoudiniAssetActorFactory.generated.h" + +class FText; +class AActor; +class UObject; +class UHoudiniAssetComponent; + +struct FAssetData; + +UCLASS(config = Editor) +class UHoudiniAssetActorFactory : public UActorFactory +{ + GENERATED_UCLASS_BODY() + +public: + // UActorFactory methods: + // Return true if Actor can be created from a given asset. + virtual bool CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) override; + // Given an instance of an actor pertaining to this factory, find the asset that should be used to create a new actor. + virtual UObject * GetAssetFromActorInstance(AActor * Instance) override; + // Modify the actor after it has been spawned. + virtual void PostSpawnActor(UObject * Asset, AActor * NewActor) override; + // Called after a blueprint is created by this factory to update the blueprint's CDO properties + // with state from the asset for this factory. + virtual void PostCreateBlueprint(UObject * Asset, AActor * CDO) override; + +protected: + bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp index 165addd61..83b5a7e59 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp @@ -1,72 +1,72 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetBroker.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAsset.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniAssetBroker::~FHoudiniAssetBroker() -{ - -} - -UClass * -FHoudiniAssetBroker::GetSupportedAssetClass() -{ - return UHoudiniAsset::StaticClass(); -} - -bool -FHoudiniAssetBroker::AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) -{ - if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) - { - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(InAsset); - if (HoudiniAsset || !InAsset) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - return true; - } - } - - return false; -} - -UObject * -FHoudiniAssetBroker::GetAssetFromComponent(UActorComponent * InComponent) -{ - if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) - { - return HoudiniAssetComponent->GetHoudiniAsset(); - } - - return nullptr; -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetBroker.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAsset.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniAssetBroker::~FHoudiniAssetBroker() +{ + +} + +UClass * +FHoudiniAssetBroker::GetSupportedAssetClass() +{ + return UHoudiniAsset::StaticClass(); +} + +bool +FHoudiniAssetBroker::AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) +{ + if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) + { + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(InAsset); + if (HoudiniAsset || !InAsset) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + return true; + } + } + + return false; +} + +UObject * +FHoudiniAssetBroker::GetAssetFromComponent(UActorComponent * InComponent) +{ + if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) + { + return HoudiniAssetComponent->GetHoudiniAsset(); + } + + return nullptr; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h index 982113c99..01d6f3da8 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h @@ -1,49 +1,49 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ComponentAssetBroker.h" - -class UObject; -class UActorComponent; - -class FHoudiniAssetBroker : public IComponentAssetBroker -{ -public: - - virtual ~FHoudiniAssetBroker(); - - // IComponentAssetBroker methods. - // Reports the asset class this broker knows how to handle. - UClass * GetSupportedAssetClass() override; - - // Assign the assigned asset to the supplied component. - bool AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) override; - - // Get the currently assigned asset from the component. - UObject * GetAssetFromComponent(UActorComponent * InComponent) override; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ComponentAssetBroker.h" + +class UObject; +class UActorComponent; + +class FHoudiniAssetBroker : public IComponentAssetBroker +{ +public: + + virtual ~FHoudiniAssetBroker(); + + // IComponentAssetBroker methods. + // Reports the asset class this broker knows how to handle. + UClass * GetSupportedAssetClass() override; + + // Assign the assigned asset to the supplied component. + bool AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) override; + + // Get the currently assigned asset from the component. + UObject * GetAssetFromComponent(UActorComponent * InComponent) override; +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp index 50f4f5cc9..b1362ed0e 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp @@ -1,466 +1,466 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetComponentDetails.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAsset.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniParameter.h" -#include "HoudiniHandleComponent.h" -#include "HoudiniParameterDetails.h" -#include "HoudiniInput.h" -#include "HoudiniInputDetails.h" -#include "HoudiniHandleDetails.h" -#include "HoudiniOutput.h" -#include "HoudiniOutputDetails.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Layout/SSeparator.h" -#include "Widgets/Layout/SBox.h" -#include "Widgets/Images/SImage.h" - -#include "PropertyCustomizationHelpers.h" -#include "DetailLayoutBuilder.h" -#include "DetailCategoryBuilder.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -TSharedRef< IDetailCustomization > -FHoudiniAssetComponentDetails::MakeInstance() -{ - return MakeShareable(new FHoudiniAssetComponentDetails); -} - -FHoudiniAssetComponentDetails::FHoudiniAssetComponentDetails() -{ - OutputDetails = MakeShared(); - ParameterDetails = MakeShared(); - PDGDetails = MakeShared(); - HoudiniEngineDetails = MakeShared(); -} - - -FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() -{ - // The ramp param's curves are added to root to avoid garbage collection - // We need to remove those curves from the root when the details classes are destroyed. - if (ParameterDetails.IsValid()) - { - FHoudiniParameterDetails* ParamDetailsPtr = ParameterDetails.Get(); - - for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) - { - if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) - continue; - - CurFloatRampCurve->RemoveFromRoot(); - } - - for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) - { - if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) - continue; - - CurColorRampCurve->RemoveFromRoot(); - } - - ParamDetailsPtr->CreatedFloatRampCurves.Empty(); - ParamDetailsPtr->CreatedColorRampCurves.Empty(); - } -} - -void -FHoudiniAssetComponentDetails::AddIndieLicenseRow(IDetailCategoryBuilder& InCategory) -{ - FText IndieText = - FText::FromString(TEXT("Houdini Engine Indie - For Limited Commercial Use Only")); - - FSlateFontInfo LargeDetailsFont = IDetailLayoutBuilder::GetDetailFontBold(); - LargeDetailsFont.Size += 2; - - FSlateColor LabelColor = FLinearColor(1.0f, 1.0f, 0.0f, 1.0f); - - InCategory.AddCustomRow(FText::GetEmpty()) - [ - SNew(STextBlock) - .Text(IndieText) - .ToolTipText(IndieText) - .Font(LargeDetailsFont) - .Justification(ETextJustify::Center) - .ColorAndOpacity(LabelColor) - ]; - - InCategory.AddCustomRow(FText::GetEmpty()) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .Padding(0, 0, 5, 0) - [ - SNew(SSeparator) - .Thickness(2.0f) - ] - ]; -} - -void -FHoudiniAssetComponentDetails::AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC) -{ - FString CategoryName = "Bake"; - InCategory.AddGroup(FName(*CategoryName), FText::FromString(CategoryName), false, false); - -} - -void -FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) -{ - // Get all components which are being customized. - TArray< TWeakObjectPtr< UObject > > ObjectsCustomized; - DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); - - // Extract the Houdini Asset Component to detail - for (int32 i = 0; i < ObjectsCustomized.Num(); ++i) - { - if (ObjectsCustomized[i].IsValid()) - { - UObject * Object = ObjectsCustomized[i].Get(); - if (Object) - { - UHoudiniAssetComponent * HAC = Cast< UHoudiniAssetComponent >(Object); - if (HAC && !HAC->IsPendingKill()) - HoudiniAssetComponents.Add(HAC); - } - } - } - - // Check if we'll need to add indie license labels - bool bIsIndieLicense = FHoudiniEngine::Get().IsLicenseIndie(); - - // To handle multiselection parameter edit, we try to group the selected components by their houdini assets - // TODO? ignore multiselection if all are not the same HDA? - // TODO do the same for inputs - TMap, TArray>> HoudiniAssetToHACs; - for (auto HAC : HoudiniAssetComponents) - { - TWeakObjectPtr HoudiniAsset = HAC->GetHoudiniAsset(); - if (!HoudiniAsset.IsValid()) - continue; - - TArray>& ValueRef = HoudiniAssetToHACs.FindOrAdd(HoudiniAsset); - ValueRef.Add(HAC); - } - - for (auto Iter : HoudiniAssetToHACs) - { - TArray> HACs = Iter.Value; - if (HACs.Num() < 1) - continue; - - TWeakObjectPtr MainComponent = HACs[0]; - if (!MainComponent.IsValid()) - continue; - - // If we have selected more than one component that have different HDAs, - // we'll want to separate the param/input/output category for each HDA - FString MultiSelectionIdentifier = FString(); - if (HoudiniAssetToHACs.Num() > 1) - { - MultiSelectionIdentifier = TEXT("("); - if (MainComponent->GetHoudiniAsset()) - MultiSelectionIdentifier += MainComponent->GetHoudiniAsset()->GetName(); - MultiSelectionIdentifier += TEXT(")"); - } - - /* - // Handled by the UPROPERTIES on the component in v2! - // Edit the Houdini details category - IDetailCategoryBuilder & HoudiniAssetCategory = - DetailBuilder.EditCategory("HoudiniAsset", FText::GetEmpty(), ECategoryPriority::Important); - */ - - // - // 0. HOUDINI ASSET DETAILS - // - - { - FString HoudiniEngineCategoryName = "Houdini Engine"; - HoudiniEngineCategoryName += MultiSelectionIdentifier; - - // Create Houdini Engine details category - IDetailCategoryBuilder & HouEngineCategory = - DetailBuilder.EditCategory(*HoudiniEngineCategoryName, FText::FromString("Houdini Engine"), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouEngineCategory); - - TArray MultiSelectedHACs; - for (auto& NextHACWeakPtr : HACs) - { - if (NextHACWeakPtr.IsValid()) - MultiSelectedHACs.Add(NextHACWeakPtr.Get()); - } - - HoudiniEngineDetails->CreateWidget(HouEngineCategory, MultiSelectedHACs); - } - - // - // 1. PDG ASSET LINK (if available) - // - if (MainComponent->GetPDGAssetLink()) - { - FString PDGCatName = "HoudiniPDGAssetLink"; - PDGCatName += MultiSelectionIdentifier; - - // Create the PDG Asset Link details category - IDetailCategoryBuilder & HouPDGCategory = - DetailBuilder.EditCategory(*PDGCatName, FText::FromString("Houdini - PDG Asset Link"), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouPDGCategory); - - // TODO: Handle multi selection of outputs like params/inputs? - - - PDGDetails->CreateWidget(HouPDGCategory, MainComponent->GetPDGAssetLink()/*, MainComponent*/); - } - - - // - // 2. PARAMETER DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString ParamCatName = "HoudiniParameters"; - ParamCatName += MultiSelectionIdentifier; - - // Create the Parameters details category - IDetailCategoryBuilder & HouParameterCategory = - DetailBuilder.EditCategory(*ParamCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if(bIsIndieLicense) - AddIndieLicenseRow(HouParameterCategory); - - // Iterate through the component's parameters - for (int32 ParamIdx = 0; ParamIdx < MainComponent->GetNumParameters(); ParamIdx++) - { - // We only want to create root parameters here, they will recursively create child parameters. - UHoudiniParameter* CurrentParam = MainComponent->GetParameterAt(ParamIdx); - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - // TODO: remove ? unneeded? - // ensure the parameter is actually owned by a HAC - /*const TWeakObjectPtr Owner = Cast(CurrentParam->GetOuter()); - if (!Owner.IsValid()) - continue;*/ - - // Build an array of edited parameter for multi edit - TArray EditedParams; - EditedParams.Add(CurrentParam); - - // Add the corresponding params in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniParameter* LinkedParam = HACs[LinkedIdx]->GetParameterAt(ParamIdx); - if (!LinkedParam || LinkedParam->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if ( !LinkedParam->Matches(*CurrentParam) ) - { - LinkedParam = MainComponent->FindMatchingParameter(CurrentParam); - if (!LinkedParam || LinkedParam->IsPendingKill() || LinkedParam->IsChildParameter()) - continue; - } - - EditedParams.Add(LinkedParam); - } - - ParameterDetails->CreateWidget(HouParameterCategory, EditedParams); - } - - /*** HOUDINI HANDLE DETAILS ***/ - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString HandleCatName = "HoudiniHandles"; - HandleCatName += MultiSelectionIdentifier; - - // Create the Parameters details category - IDetailCategoryBuilder & HouHandleCategory = - DetailBuilder.EditCategory(*HandleCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouHandleCategory); - - // Iterate through the component's Houdini handles - for (int32 HandleIdx = 0; HandleIdx < MainComponent->GetNumHandles(); ++HandleIdx) - { - UHoudiniHandleComponent* CurrentHandleComponent = MainComponent->GetHandleComponentAt(HandleIdx); - - if (!CurrentHandleComponent || CurrentHandleComponent->IsPendingKill()) - continue; - - TArray EditedHandles; - EditedHandles.Add(CurrentHandleComponent); - - // Add the corresponding params in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); ++LinkedIdx) - { - UHoudiniHandleComponent* LinkedHandle = HACs[LinkedIdx]->GetHandleComponentAt(HandleIdx); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) - continue; - - // Linked handles should match the main param, if not try to find one that matches - if (!LinkedHandle->Matches(*CurrentHandleComponent)) - { - LinkedHandle = MainComponent->FindMatchingHandle(CurrentHandleComponent); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) - continue; - } - - EditedHandles.Add(LinkedHandle); - } - - FHoudiniHandleDetails::CreateWidget(HouHandleCategory, EditedHandles); - } - - - // - // 3. INPUT DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString InputCatName = "HoudiniInputs"; - InputCatName += MultiSelectionIdentifier; - - // Create the input details category - IDetailCategoryBuilder & HouInputCategory = - DetailBuilder.EditCategory(*InputCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouInputCategory); - - // Iterate through the component's inputs - for (int32 InputIdx = 0; InputIdx < MainComponent->GetNumInputs(); InputIdx++) - { - UHoudiniInput* CurrentInput = MainComponent->GetInputAt(InputIdx); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (!MainComponent->IsInputTypeSupported(CurrentInput->GetInputType())) - continue; - - // Object path parameter inputs are displayed by the ParameterDetails - skip them - if (CurrentInput->IsObjectPathParameter()) - continue; - - // Build an array of edited inputs for multi edit - TArray EditedInputs; - EditedInputs.Add(CurrentInput); - - // Add the corresponding inputs in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniInput* LinkedInput = HACs[LinkedIdx]->GetInputAt(InputIdx); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if (!LinkedInput->Matches(*CurrentInput)) - { - LinkedInput = MainComponent->FindMatchingInput(CurrentInput); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - } - - EditedInputs.Add(LinkedInput); - } - - FHoudiniInputDetails::CreateWidget(HouInputCategory, EditedInputs); - } - - // - // 4. OUTPUT DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString OutputCatName = "HoudiniOutputs"; - OutputCatName += MultiSelectionIdentifier; - - // Create the output details category - IDetailCategoryBuilder & HouOutputCategory = - DetailBuilder.EditCategory(*OutputCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // Iterate through the component's outputs - for (int32 OutputIdx = 0; OutputIdx < MainComponent->GetNumOutputs(); OutputIdx++) - { - UHoudiniOutput* CurrentOutput = MainComponent->GetOutputAt(OutputIdx); - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - // Build an array of edited inpoutputs for multi edit - TArray EditedOutputs; - EditedOutputs.Add(CurrentOutput); - - // Add the corresponding outputs in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniOutput* LinkedOutput = HACs[LinkedIdx]->GetOutputAt(OutputIdx); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) - continue; - - /* - // Linked output should match the main output! If not try to find one that matches - if (!LinkedOutput->Matches(*CurrentOutput)) - { - LinkedOutput = MainComponent->FindMatchingInput(CurrentOutput); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) - continue; - } - */ - - EditedOutputs.Add(LinkedOutput); - } - - // TODO: Handle multi selection of outputs like params/inputs? - OutputDetails->CreateWidget(HouOutputCategory, EditedOutputs); - } - } -} - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetComponentDetails.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAsset.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniParameter.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniParameterDetails.h" +#include "HoudiniInput.h" +#include "HoudiniInputDetails.h" +#include "HoudiniHandleDetails.h" +#include "HoudiniOutput.h" +#include "HoudiniOutputDetails.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Images/SImage.h" + +#include "PropertyCustomizationHelpers.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +TSharedRef< IDetailCustomization > +FHoudiniAssetComponentDetails::MakeInstance() +{ + return MakeShareable(new FHoudiniAssetComponentDetails); +} + +FHoudiniAssetComponentDetails::FHoudiniAssetComponentDetails() +{ + OutputDetails = MakeShared(); + ParameterDetails = MakeShared(); + PDGDetails = MakeShared(); + HoudiniEngineDetails = MakeShared(); +} + + +FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() +{ + // The ramp param's curves are added to root to avoid garbage collection + // We need to remove those curves from the root when the details classes are destroyed. + if (ParameterDetails.IsValid()) + { + FHoudiniParameterDetails* ParamDetailsPtr = ParameterDetails.Get(); + + for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) + { + if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) + continue; + + CurFloatRampCurve->RemoveFromRoot(); + } + + for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) + { + if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) + continue; + + CurColorRampCurve->RemoveFromRoot(); + } + + ParamDetailsPtr->CreatedFloatRampCurves.Empty(); + ParamDetailsPtr->CreatedColorRampCurves.Empty(); + } +} + +void +FHoudiniAssetComponentDetails::AddIndieLicenseRow(IDetailCategoryBuilder& InCategory) +{ + FText IndieText = + FText::FromString(TEXT("Houdini Engine Indie - For Limited Commercial Use Only")); + + FSlateFontInfo LargeDetailsFont = IDetailLayoutBuilder::GetDetailFontBold(); + LargeDetailsFont.Size += 2; + + FSlateColor LabelColor = FLinearColor(1.0f, 1.0f, 0.0f, 1.0f); + + InCategory.AddCustomRow(FText::GetEmpty()) + [ + SNew(STextBlock) + .Text(IndieText) + .ToolTipText(IndieText) + .Font(LargeDetailsFont) + .Justification(ETextJustify::Center) + .ColorAndOpacity(LabelColor) + ]; + + InCategory.AddCustomRow(FText::GetEmpty()) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(0, 0, 5, 0) + [ + SNew(SSeparator) + .Thickness(2.0f) + ] + ]; +} + +void +FHoudiniAssetComponentDetails::AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC) +{ + FString CategoryName = "Bake"; + InCategory.AddGroup(FName(*CategoryName), FText::FromString(CategoryName), false, false); + +} + +void +FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + // Get all components which are being customized. + TArray< TWeakObjectPtr< UObject > > ObjectsCustomized; + DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); + + // Extract the Houdini Asset Component to detail + for (int32 i = 0; i < ObjectsCustomized.Num(); ++i) + { + if (ObjectsCustomized[i].IsValid()) + { + UObject * Object = ObjectsCustomized[i].Get(); + if (Object) + { + UHoudiniAssetComponent * HAC = Cast< UHoudiniAssetComponent >(Object); + if (HAC && !HAC->IsPendingKill()) + HoudiniAssetComponents.Add(HAC); + } + } + } + + // Check if we'll need to add indie license labels + bool bIsIndieLicense = FHoudiniEngine::Get().IsLicenseIndie(); + + // To handle multiselection parameter edit, we try to group the selected components by their houdini assets + // TODO? ignore multiselection if all are not the same HDA? + // TODO do the same for inputs + TMap, TArray>> HoudiniAssetToHACs; + for (auto HAC : HoudiniAssetComponents) + { + TWeakObjectPtr HoudiniAsset = HAC->GetHoudiniAsset(); + if (!HoudiniAsset.IsValid()) + continue; + + TArray>& ValueRef = HoudiniAssetToHACs.FindOrAdd(HoudiniAsset); + ValueRef.Add(HAC); + } + + for (auto Iter : HoudiniAssetToHACs) + { + TArray> HACs = Iter.Value; + if (HACs.Num() < 1) + continue; + + TWeakObjectPtr MainComponent = HACs[0]; + if (!MainComponent.IsValid()) + continue; + + // If we have selected more than one component that have different HDAs, + // we'll want to separate the param/input/output category for each HDA + FString MultiSelectionIdentifier = FString(); + if (HoudiniAssetToHACs.Num() > 1) + { + MultiSelectionIdentifier = TEXT("("); + if (MainComponent->GetHoudiniAsset()) + MultiSelectionIdentifier += MainComponent->GetHoudiniAsset()->GetName(); + MultiSelectionIdentifier += TEXT(")"); + } + + /* + // Handled by the UPROPERTIES on the component in v2! + // Edit the Houdini details category + IDetailCategoryBuilder & HoudiniAssetCategory = + DetailBuilder.EditCategory("HoudiniAsset", FText::GetEmpty(), ECategoryPriority::Important); + */ + + // + // 0. HOUDINI ASSET DETAILS + // + + { + FString HoudiniEngineCategoryName = "Houdini Engine"; + HoudiniEngineCategoryName += MultiSelectionIdentifier; + + // Create Houdini Engine details category + IDetailCategoryBuilder & HouEngineCategory = + DetailBuilder.EditCategory(*HoudiniEngineCategoryName, FText::FromString("Houdini Engine"), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouEngineCategory); + + TArray MultiSelectedHACs; + for (auto& NextHACWeakPtr : HACs) + { + if (NextHACWeakPtr.IsValid()) + MultiSelectedHACs.Add(NextHACWeakPtr.Get()); + } + + HoudiniEngineDetails->CreateWidget(HouEngineCategory, MultiSelectedHACs); + } + + // + // 1. PDG ASSET LINK (if available) + // + if (MainComponent->GetPDGAssetLink()) + { + FString PDGCatName = "HoudiniPDGAssetLink"; + PDGCatName += MultiSelectionIdentifier; + + // Create the PDG Asset Link details category + IDetailCategoryBuilder & HouPDGCategory = + DetailBuilder.EditCategory(*PDGCatName, FText::FromString("Houdini - PDG Asset Link"), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouPDGCategory); + + // TODO: Handle multi selection of outputs like params/inputs? + + + PDGDetails->CreateWidget(HouPDGCategory, MainComponent->GetPDGAssetLink()/*, MainComponent*/); + } + + + // + // 2. PARAMETER DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString ParamCatName = "HoudiniParameters"; + ParamCatName += MultiSelectionIdentifier; + + // Create the Parameters details category + IDetailCategoryBuilder & HouParameterCategory = + DetailBuilder.EditCategory(*ParamCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if(bIsIndieLicense) + AddIndieLicenseRow(HouParameterCategory); + + // Iterate through the component's parameters + for (int32 ParamIdx = 0; ParamIdx < MainComponent->GetNumParameters(); ParamIdx++) + { + // We only want to create root parameters here, they will recursively create child parameters. + UHoudiniParameter* CurrentParam = MainComponent->GetParameterAt(ParamIdx); + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + // TODO: remove ? unneeded? + // ensure the parameter is actually owned by a HAC + /*const TWeakObjectPtr Owner = Cast(CurrentParam->GetOuter()); + if (!Owner.IsValid()) + continue;*/ + + // Build an array of edited parameter for multi edit + TArray EditedParams; + EditedParams.Add(CurrentParam); + + // Add the corresponding params in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniParameter* LinkedParam = HACs[LinkedIdx]->GetParameterAt(ParamIdx); + if (!LinkedParam || LinkedParam->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if ( !LinkedParam->Matches(*CurrentParam) ) + { + LinkedParam = MainComponent->FindMatchingParameter(CurrentParam); + if (!LinkedParam || LinkedParam->IsPendingKill() || LinkedParam->IsChildParameter()) + continue; + } + + EditedParams.Add(LinkedParam); + } + + ParameterDetails->CreateWidget(HouParameterCategory, EditedParams); + } + + /*** HOUDINI HANDLE DETAILS ***/ + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString HandleCatName = "HoudiniHandles"; + HandleCatName += MultiSelectionIdentifier; + + // Create the Parameters details category + IDetailCategoryBuilder & HouHandleCategory = + DetailBuilder.EditCategory(*HandleCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouHandleCategory); + + // Iterate through the component's Houdini handles + for (int32 HandleIdx = 0; HandleIdx < MainComponent->GetNumHandles(); ++HandleIdx) + { + UHoudiniHandleComponent* CurrentHandleComponent = MainComponent->GetHandleComponentAt(HandleIdx); + + if (!CurrentHandleComponent || CurrentHandleComponent->IsPendingKill()) + continue; + + TArray EditedHandles; + EditedHandles.Add(CurrentHandleComponent); + + // Add the corresponding params in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); ++LinkedIdx) + { + UHoudiniHandleComponent* LinkedHandle = HACs[LinkedIdx]->GetHandleComponentAt(HandleIdx); + if (!LinkedHandle || LinkedHandle->IsPendingKill()) + continue; + + // Linked handles should match the main param, if not try to find one that matches + if (!LinkedHandle->Matches(*CurrentHandleComponent)) + { + LinkedHandle = MainComponent->FindMatchingHandle(CurrentHandleComponent); + if (!LinkedHandle || LinkedHandle->IsPendingKill()) + continue; + } + + EditedHandles.Add(LinkedHandle); + } + + FHoudiniHandleDetails::CreateWidget(HouHandleCategory, EditedHandles); + } + + + // + // 3. INPUT DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString InputCatName = "HoudiniInputs"; + InputCatName += MultiSelectionIdentifier; + + // Create the input details category + IDetailCategoryBuilder & HouInputCategory = + DetailBuilder.EditCategory(*InputCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouInputCategory); + + // Iterate through the component's inputs + for (int32 InputIdx = 0; InputIdx < MainComponent->GetNumInputs(); InputIdx++) + { + UHoudiniInput* CurrentInput = MainComponent->GetInputAt(InputIdx); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (!MainComponent->IsInputTypeSupported(CurrentInput->GetInputType())) + continue; + + // Object path parameter inputs are displayed by the ParameterDetails - skip them + if (CurrentInput->IsObjectPathParameter()) + continue; + + // Build an array of edited inputs for multi edit + TArray EditedInputs; + EditedInputs.Add(CurrentInput); + + // Add the corresponding inputs in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniInput* LinkedInput = HACs[LinkedIdx]->GetInputAt(InputIdx); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if (!LinkedInput->Matches(*CurrentInput)) + { + LinkedInput = MainComponent->FindMatchingInput(CurrentInput); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + } + + EditedInputs.Add(LinkedInput); + } + + FHoudiniInputDetails::CreateWidget(HouInputCategory, EditedInputs); + } + + // + // 4. OUTPUT DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString OutputCatName = "HoudiniOutputs"; + OutputCatName += MultiSelectionIdentifier; + + // Create the output details category + IDetailCategoryBuilder & HouOutputCategory = + DetailBuilder.EditCategory(*OutputCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // Iterate through the component's outputs + for (int32 OutputIdx = 0; OutputIdx < MainComponent->GetNumOutputs(); OutputIdx++) + { + UHoudiniOutput* CurrentOutput = MainComponent->GetOutputAt(OutputIdx); + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + // Build an array of edited inpoutputs for multi edit + TArray EditedOutputs; + EditedOutputs.Add(CurrentOutput); + + // Add the corresponding outputs in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniOutput* LinkedOutput = HACs[LinkedIdx]->GetOutputAt(OutputIdx); + if (!LinkedOutput || LinkedOutput->IsPendingKill()) + continue; + + /* + // Linked output should match the main output! If not try to find one that matches + if (!LinkedOutput->Matches(*CurrentOutput)) + { + LinkedOutput = MainComponent->FindMatchingInput(CurrentOutput); + if (!LinkedOutput || LinkedOutput->IsPendingKill()) + continue; + } + */ + + EditedOutputs.Add(LinkedOutput); + } + + // TODO: Handle multi selection of outputs like params/inputs? + OutputDetails->CreateWidget(HouOutputCategory, EditedOutputs); + } + } +} + + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h index 702bc8f80..47a51e56a 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h @@ -1,86 +1,86 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "IDetailCustomization.h" -#include "HoudiniPDGDetails.h" -#include "HoudiniOutputDetails.h" -#include "HoudiniParameterDetails.h" -#include "HoudiniEngineDetails.h" - -class UHoudiniAssetComponent; -class UStaticMesh; - -class FHoudiniAssetComponentDetails : public IDetailCustomization -{ -public: - - // Constructor. - FHoudiniAssetComponentDetails(); - - // Destructor. - virtual ~FHoudiniAssetComponentDetails(); - - // IDetailCustomization methods. - virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; - - // Create an instance of this detail layout class. - static TSharedRef MakeInstance(); - -private: - - // Adds a text row indicate we're using a Houdini indie license - void AddIndieLicenseRow(IDetailCategoryBuilder& InCategory); - - // Adds a category for baking options - void AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC); - - // Handler for double clicking the static mesh thumbnail, opens the editor. - FReply OnThumbnailDoubleClick( - const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent, UObject* Object); - - -private: - - // Components which are being customized. - TArray> HoudiniAssetComponents; - - // Structure holding the output's details - TSharedPtr OutputDetails; - - // Structure holding the parameter's details - TSharedPtr ParameterDetails; - - // Structure holding the PDG Asset Link's details - TSharedPtr PDGDetails; - - // Structure holding the HoudiniAsset details - TSharedPtr HoudiniEngineDetails; - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" +#include "HoudiniPDGDetails.h" +#include "HoudiniOutputDetails.h" +#include "HoudiniParameterDetails.h" +#include "HoudiniEngineDetails.h" + +class UHoudiniAssetComponent; +class UStaticMesh; + +class FHoudiniAssetComponentDetails : public IDetailCustomization +{ +public: + + // Constructor. + FHoudiniAssetComponentDetails(); + + // Destructor. + virtual ~FHoudiniAssetComponentDetails(); + + // IDetailCustomization methods. + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + + // Create an instance of this detail layout class. + static TSharedRef MakeInstance(); + +private: + + // Adds a text row indicate we're using a Houdini indie license + void AddIndieLicenseRow(IDetailCategoryBuilder& InCategory); + + // Adds a category for baking options + void AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC); + + // Handler for double clicking the static mesh thumbnail, opens the editor. + FReply OnThumbnailDoubleClick( + const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent, UObject* Object); + + +private: + + // Components which are being customized. + TArray> HoudiniAssetComponents; + + // Structure holding the output's details + TSharedPtr OutputDetails; + + // Structure holding the parameter's details + TSharedPtr ParameterDetails; + + // Structure holding the PDG Asset Link's details + TSharedPtr PDGDetails; + + // Structure holding the HoudiniAsset details + TSharedPtr HoudiniEngineDetails; + + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp index 63378d038..56db7a3db 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp @@ -1,209 +1,209 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniAsset.h" - -#include "EditorFramework/AssetImportData.h" -#include "Misc/FileHelper.h" -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniAssetFactory::UHoudiniAssetFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - // This factory is responsible for manufacturing HoudiniEngine assets. - SupportedClass = UHoudiniAsset::StaticClass(); - - // This factory does not manufacture new objects from scratch. - bCreateNew = false; - - // This factory will not open the editor for each new object. - bEditAfterNew = false; - - // This factory will import objects from files. - bEditorImport = true; - - // Factory does not import objects from text. - bText = false; - - // Add supported formats. - Formats.Add(TEXT("otl;Houdini Engine Asset")); - Formats.Add(TEXT("otllc;Houdini Engine Limited Commercial Asset")); - Formats.Add(TEXT("otlnc;Houdini Engine Non-Commercial Asset")); - Formats.Add(TEXT("hda;Houdini Engine Asset")); - Formats.Add(TEXT("hdalc;Houdini Engine Limited Commercial Asset")); - Formats.Add(TEXT("hdanc;Houdini Engine Non-Commercial Asset")); - Formats.Add(TEXT("hdalibrary;Houdini Engine Expanded Asset")); -} - -bool -UHoudiniAssetFactory::DoesSupportClass(UClass * Class) -{ - return Class == SupportedClass; -} - -FText -UHoudiniAssetFactory::GetDisplayName() const -{ - return LOCTEXT("HoudiniAssetFactoryDescription", "Houdini Engine Asset"); -} - -UObject * -UHoudiniAssetFactory::FactoryCreateBinary( - UClass * InClass, UObject* InParent, FName InName, EObjectFlags Flags, - UObject * Context, const TCHAR * Type, const uint8 *& Buffer, - const uint8 * BufferEnd, FFeedbackContext * Warn ) -{ - // Broadcast notification that a new asset is being imported. - GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type); - - // Create a new asset. - UHoudiniAsset * HoudiniAsset = NewObject< UHoudiniAsset >(InParent, InName, Flags); - HoudiniAsset->CreateAsset(Buffer, BufferEnd, UFactory::GetCurrentFilename()); - - // Create reimport information. - UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(HoudiniAsset, UAssetImportData::StaticClass()); - HoudiniAsset->AssetImportData = AssetImportData; - } - - AssetImportData->Update(UFactory::GetCurrentFilename()); - - // Broadcast notification that the new asset has been imported. - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, HoudiniAsset); - - return HoudiniAsset; -} - -UObject* -UHoudiniAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) -{ - // "houdini.hdalibrary" files (expanded hda / hda folder) need a special treatment, - // but ".hda" files can be loaded normally - FString FileExtension = FPaths::GetExtension(Filename); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) != 0) - return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); - - // Make sure the file name is sections.list - FString NameOfFile = FPaths::GetBaseFilename(Filename); - if (NameOfFile.Compare(TEXT("houdini"), ESearchCase::IgnoreCase) != 0) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); - return nullptr; - } - - // Make sure that the proper .list file is loaded - FString PathToFile = FPaths::GetPath(Filename); - if (PathToFile.Find(TEXT(".hda")) != (PathToFile.Len() - 4)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); - return nullptr; - } - - FString NewFilename = PathToFile; - FString NewFileNameNoHDA = FPaths::GetBaseFilename(PathToFile); - FName NewIname = FName(*NewFileNameNoHDA); - FString NewFileExtension = FPaths::GetExtension(NewFilename); - - // load as binary - TArray Data; - if (!FFileHelper::LoadFileToArray(Data, *Filename)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s' to array"), *Filename); - return nullptr; - } - - Data.Add(0); - ParseParms(Parms); - const uint8* Ptr = &Data[0]; - - return FactoryCreateBinary(InClass, InParent, NewIname, Flags, nullptr, *NewFileExtension, Ptr, Ptr + Data.Num() - 1, Warn); -} - -bool -UHoudiniAssetFactory::CanReimport(UObject * Obj, TArray< FString > & OutFilenames) -{ - UHoudiniAsset * HoudiniAsset = Cast(Obj); - if (HoudiniAsset) - { - UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; - if (AssetImportData) - OutFilenames.Add(AssetImportData->GetFirstFilename()); - else - OutFilenames.Add(TEXT("")); - - return true; - } - - return false; -} - -void -UHoudiniAssetFactory::SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) -{ - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); - if (HoudiniAsset && (1 == NewReimportPaths.Num())) - HoudiniAsset->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); -} - -EReimportResult::Type -UHoudiniAssetFactory::Reimport(UObject * Obj) -{ - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); - if (HoudiniAsset && HoudiniAsset->AssetImportData) - { - // Make sure file is valid and exists. - const FString & Filename = HoudiniAsset->AssetImportData->GetFirstFilename(); - - if (!Filename.Len() || IFileManager::Get().FileSize(*Filename) == INDEX_NONE) - return EReimportResult::Failed; - - if (UFactory::StaticImportObject( - HoudiniAsset->GetClass(), HoudiniAsset->GetOuter(), *HoudiniAsset->GetName(), - RF_Public | RF_Standalone, *Filename, NULL, this)) - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimported successfully.")); - - if (HoudiniAsset->GetOuter()) - HoudiniAsset->GetOuter()->MarkPackageDirty(); - else - HoudiniAsset->MarkPackageDirty(); - - return EReimportResult::Succeeded; - } - } - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); - return EReimportResult::Failed; -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniAsset.h" + +#include "EditorFramework/AssetImportData.h" +#include "Misc/FileHelper.h" +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetFactory::UHoudiniAssetFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + // This factory is responsible for manufacturing HoudiniEngine assets. + SupportedClass = UHoudiniAsset::StaticClass(); + + // This factory does not manufacture new objects from scratch. + bCreateNew = false; + + // This factory will not open the editor for each new object. + bEditAfterNew = false; + + // This factory will import objects from files. + bEditorImport = true; + + // Factory does not import objects from text. + bText = false; + + // Add supported formats. + Formats.Add(TEXT("otl;Houdini Engine Asset")); + Formats.Add(TEXT("otllc;Houdini Engine Limited Commercial Asset")); + Formats.Add(TEXT("otlnc;Houdini Engine Non-Commercial Asset")); + Formats.Add(TEXT("hda;Houdini Engine Asset")); + Formats.Add(TEXT("hdalc;Houdini Engine Limited Commercial Asset")); + Formats.Add(TEXT("hdanc;Houdini Engine Non-Commercial Asset")); + Formats.Add(TEXT("hdalibrary;Houdini Engine Expanded Asset")); +} + +bool +UHoudiniAssetFactory::DoesSupportClass(UClass * Class) +{ + return Class == SupportedClass; +} + +FText +UHoudiniAssetFactory::GetDisplayName() const +{ + return LOCTEXT("HoudiniAssetFactoryDescription", "Houdini Engine Asset"); +} + +UObject * +UHoudiniAssetFactory::FactoryCreateBinary( + UClass * InClass, UObject* InParent, FName InName, EObjectFlags Flags, + UObject * Context, const TCHAR * Type, const uint8 *& Buffer, + const uint8 * BufferEnd, FFeedbackContext * Warn ) +{ + // Broadcast notification that a new asset is being imported. + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type); + + // Create a new asset. + UHoudiniAsset * HoudiniAsset = NewObject< UHoudiniAsset >(InParent, InName, Flags); + HoudiniAsset->CreateAsset(Buffer, BufferEnd, UFactory::GetCurrentFilename()); + + // Create reimport information. + UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(HoudiniAsset, UAssetImportData::StaticClass()); + HoudiniAsset->AssetImportData = AssetImportData; + } + + AssetImportData->Update(UFactory::GetCurrentFilename()); + + // Broadcast notification that the new asset has been imported. + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, HoudiniAsset); + + return HoudiniAsset; +} + +UObject* +UHoudiniAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) +{ + // "houdini.hdalibrary" files (expanded hda / hda folder) need a special treatment, + // but ".hda" files can be loaded normally + FString FileExtension = FPaths::GetExtension(Filename); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) != 0) + return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); + + // Make sure the file name is sections.list + FString NameOfFile = FPaths::GetBaseFilename(Filename); + if (NameOfFile.Compare(TEXT("houdini"), ESearchCase::IgnoreCase) != 0) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); + return nullptr; + } + + // Make sure that the proper .list file is loaded + FString PathToFile = FPaths::GetPath(Filename); + if (PathToFile.Find(TEXT(".hda")) != (PathToFile.Len() - 4)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); + return nullptr; + } + + FString NewFilename = PathToFile; + FString NewFileNameNoHDA = FPaths::GetBaseFilename(PathToFile); + FName NewIname = FName(*NewFileNameNoHDA); + FString NewFileExtension = FPaths::GetExtension(NewFilename); + + // load as binary + TArray Data; + if (!FFileHelper::LoadFileToArray(Data, *Filename)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s' to array"), *Filename); + return nullptr; + } + + Data.Add(0); + ParseParms(Parms); + const uint8* Ptr = &Data[0]; + + return FactoryCreateBinary(InClass, InParent, NewIname, Flags, nullptr, *NewFileExtension, Ptr, Ptr + Data.Num() - 1, Warn); +} + +bool +UHoudiniAssetFactory::CanReimport(UObject * Obj, TArray< FString > & OutFilenames) +{ + UHoudiniAsset * HoudiniAsset = Cast(Obj); + if (HoudiniAsset) + { + UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; + if (AssetImportData) + OutFilenames.Add(AssetImportData->GetFirstFilename()); + else + OutFilenames.Add(TEXT("")); + + return true; + } + + return false; +} + +void +UHoudiniAssetFactory::SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); + if (HoudiniAsset && (1 == NewReimportPaths.Num())) + HoudiniAsset->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); +} + +EReimportResult::Type +UHoudiniAssetFactory::Reimport(UObject * Obj) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); + if (HoudiniAsset && HoudiniAsset->AssetImportData) + { + // Make sure file is valid and exists. + const FString & Filename = HoudiniAsset->AssetImportData->GetFirstFilename(); + + if (!Filename.Len() || IFileManager::Get().FileSize(*Filename) == INDEX_NONE) + return EReimportResult::Failed; + + if (UFactory::StaticImportObject( + HoudiniAsset->GetClass(), HoudiniAsset->GetOuter(), *HoudiniAsset->GetName(), + RF_Public | RF_Standalone, *Filename, NULL, this)) + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimported successfully.")); + + if (HoudiniAsset->GetOuter()) + HoudiniAsset->GetOuter()->MarkPackageDirty(); + else + HoudiniAsset->MarkPackageDirty(); + + return EReimportResult::Succeeded; + } + } + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); + return EReimportResult::Failed; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h index ca8ba14cf..aa64b17af 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "EditorReimportHandler.h" -#include "Factories/Factory.h" -#include "HoudiniAssetFactory.generated.h" - -class UClass; -class UObject; -class FFeedbackContext; - -UCLASS(config = Editor) -class UHoudiniAssetFactory : public UFactory, public FReimportHandler -{ - GENERATED_UCLASS_BODY() - - public: - - // UFactory methods. - // return true if it supports this class - virtual bool DoesSupportClass(UClass * Class) override; - - // Returns the name of the factory for menus - virtual FText GetDisplayName() const override; - - // Create a new object by importing it from a binary buffer. - virtual UObject * FactoryCreateBinary( - UClass * InClass, UObject * InParent, FName InName, EObjectFlags Flags, - UObject * Context, const TCHAR * Type, const uint8 *& Buffer, const uint8 * BufferEnd, - FFeedbackContext * Warn) override; - - // Create a new object by importing it from a file name. - virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, - EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, - FFeedbackContext* Warn, bool& bOutOperationCanceled) override; - - // FReimportHandler methods. - // Check to see if we have a handler to manage the reimporting of the object - virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; - - // Sets the reimport path(s) for the specified object - virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; - - // Attempt to reimport the specified object from its source - virtual EReimportResult::Type Reimport(UObject * Obj) override; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "EditorReimportHandler.h" +#include "Factories/Factory.h" +#include "HoudiniAssetFactory.generated.h" + +class UClass; +class UObject; +class FFeedbackContext; + +UCLASS(config = Editor) +class UHoudiniAssetFactory : public UFactory, public FReimportHandler +{ + GENERATED_UCLASS_BODY() + + public: + + // UFactory methods. + // return true if it supports this class + virtual bool DoesSupportClass(UClass * Class) override; + + // Returns the name of the factory for menus + virtual FText GetDisplayName() const override; + + // Create a new object by importing it from a binary buffer. + virtual UObject * FactoryCreateBinary( + UClass * InClass, UObject * InParent, FName InName, EObjectFlags Flags, + UObject * Context, const TCHAR * Type, const uint8 *& Buffer, const uint8 * BufferEnd, + FFeedbackContext * Warn) override; + + // Create a new object by importing it from a file name. + virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, + EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, + FFeedbackContext* Warn, bool& bOutOperationCanceled) override; + + // FReimportHandler methods. + // Check to see if we have a handler to manage the reimporting of the object + virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; + + // Sets the reimport path(s) for the specified object + virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; + + // Attempt to reimport the specified object from its source + virtual EReimportResult::Type Reimport(UObject * Obj) override; +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp index f3910a62d..d05322a6f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp @@ -1,5209 +1,5209 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineBakeUtils.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngineUtils.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutput.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineUtils.h" -#include "UnrealLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniStringResolver.h" -#include "HoudiniEngineCommands.h" - -#include "Engine/StaticMesh.h" -#include "Engine/World.h" -#include "RawMesh.h" -#include "UObject/Package.h" -#include "PackageTools.h" -#include "UObject/MetaData.h" -#include "AssetRegistryModule.h" -#include "Materials/Material.h" -#include "LandscapeProxy.h" -#include "LandscapeStreamingProxy.h" -#include "LandscapeInfo.h" -#include "Factories/WorldFactory.h" -#include "AssetToolsModule.h" -#include "InstancedFoliageActor.h" -#include "Components/SplineComponent.h" -#include "GameFramework/Actor.h" -#include "Engine/StaticMeshActor.h" -#include "Components/StaticMeshComponent.h" -#include "PhysicsEngine/BodySetup.h" -#include "ActorFactories/ActorFactoryStaticMesh.h" -#include "ActorFactories/ActorFactoryEmptyActor.h" -#include "BusyCursor.h" -#include "Editor.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "FileHelpers.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngine.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "Editor/EditorEngine.h" -#include "Factories/BlueprintFactory.h" -#include "Engine/SimpleConstructionScript.h" -#include "Misc/Paths.h" -#include "HAL/FileManager.h" -#include "LandscapeEdit.h" -#include "Containers/UnrealString.h" -#include "Components/AudioComponent.h" -#include "Engine/WorldComposition.h" -#include "Kismet2/BlueprintEditorUtils.h" -#include "MaterialEditor/Public/MaterialEditingLibrary.h" -#include "MaterialGraph/MaterialGraph.h" -#include "Particles/ParticleSystemComponent.h" -#include "Sound/SoundBase.h" -#include "UObject/UnrealType.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() - : Actor(nullptr) - , OutputIndex(INDEX_NONE) - , OutputObjectIdentifier() - , ActorBakeName(NAME_None) - , BakedObject(nullptr) - , SourceObject(nullptr) -{ -} - -FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( - AActor* InActor, - FName InActorBakeName, - FName InWorldOutlinerFolder, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - UObject* InBakedObject, - UObject* InSourceObject) - : Actor(InActor) - , OutputIndex(InOutputIndex) - , OutputObjectIdentifier(InOutputObjectIdentifier) - , ActorBakeName(InActorBakeName) - , WorldOutlinerFolder(InWorldOutlinerFolder) - , BakedObject(InBakedObject) - , SourceObject(InSourceObject) -{ -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - UHoudiniAssetComponent* InHACToBake, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess) -{ - if (!IsValid(InHACToBake)) - return false; - - // Handle proxies: if the output has any current proxies, first refine them - bool bHACNeedsToReCook; - if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bHACNeedsToReCook)) - { - // Either the component is invalid, or needs a recook to refine a proxy mesh - return false; - } - - bool bSuccess = false; - switch (InBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake); - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake); - } - break; - - case EHoudiniEngineBakeOption::ToFoliage: - { - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake); - } - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - { - //Todo - bSuccess = false; - } - break; - - } - - if (bSuccess && bInRemoveHACOutputOnSuccess) - FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - TArray NewActors; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - - if (!FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats)) - { - // TODO ? - HOUDINI_LOG_WARNING(TEXT("Errors when baking")); - } - - // Save the created packages - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Recenter and select the baked actors - if (GEditor && NewActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : NewActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (HoudiniAssetComponent->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && NewActors.Num() > 0) - GEditor->NoteSelectionChange(); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!IsValid(OwnerActor)) - return false; - - const FString HoudiniAssetName = OwnerActor->GetName(); - - // Get an array of the outputs - const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); - TArray Outputs; - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - Outputs.Add(HoudiniAssetComponent->GetOutputAt(OutputIdx)); - } - - // Get the previous bake objects and grow/shrink to match asset outputs - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - // Ensure we have an entry for each output - if (BakedOutputs.Num() != NumOutputs) - BakedOutputs.SetNum(NumOutputs); - - return BakeHoudiniOutputsToActors( - Outputs, - BakedOutputs, - HoudiniAssetName, - HoudiniAssetComponent->GetComponentTransform(), - HoudiniAssetComponent->BakeFolder, - HoudiniAssetComponent->TemporaryCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutNewActors, - OutPackagesToSave, - OutBakeStats, - InOutputTypesToBake, - InInstancerComponentTypesToBake, - InFallbackActor, - InFallbackWorldOutlinerFolder); -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( - const TArray& InOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FTransform& InParentTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - const int32 NumOutputs = InOutputs.Num(); - - const FString MsgTemplate = TEXT("Baking output: {0}/{1}."); - FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs }); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg)); - - TArray BakedActors; - - // First bake everything except instancers, then bake instancers. Since instancers might use meshes in - // from the other outputs. - bool bHasAnyInstancers = false; - int32 NumProcessedOutputs = 0; - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) - { - NumProcessedOutputs++; - continue; - } - - Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); - - const EHoudiniOutputType OutputType = Output->GetType(); - // Check if we should skip this output type - if (InOutputTypesToBake && InOutputTypesToBake->Find(OutputType) == INDEX_NONE) - { - NumProcessedOutputs++; - continue; - } - - switch (OutputType) - { - case EHoudiniOutputType::Mesh: - { - FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( - OutputIdx, - InOutputs, - InBakedOutputs, - InHoudiniAssetName, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - break; - - case EHoudiniOutputType::Instancer: - { - if (!bHasAnyInstancers) - bHasAnyInstancers = true; - NumProcessedOutputs--; - } - break; - - case EHoudiniOutputType::Landscape: - { - UHoudiniAssetComponent* HAC = Cast(Output->GetOuter()); - if (IsValid(HAC)) - { - // UWorld* WorldContext = Output->GetWorld(); - const bool bResult = BakeLandscape( - OutputIdx, - Output, - InBakedOutputs[OutputIdx].BakedOutputObjects, - bInReplaceActors, - bInReplaceAssets, - InBakeFolder.Path, - InHoudiniAssetName, - OutBakeStats); - } - } - break; - - case EHoudiniOutputType::Skeletal: - break; - - case EHoudiniOutputType::Curve: - { - FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - Output, - InBakedOutputs[OutputIdx].BakedOutputObjects, - InBakedOutputs, - InHoudiniAssetName, - InBakeFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - break; - - case EHoudiniOutputType::Invalid: - break; - } - - NumProcessedOutputs++; - } - - if (bHasAnyInstancers) - { - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) - { - NumProcessedOutputs++; - continue; - } - - Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); - - if (Output->GetType() == EHoudiniOutputType::Instancer) - { - FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( - OutputIdx, - InOutputs, - InBakedOutputs, - InParentTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - OutPackagesToSave, - InInstancerComponentTypesToBake, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - - NumProcessedOutputs++; - } - } - - OutNewActors.Append(BakedActors); - - return true; -} - - -bool -FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n) - { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n); - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Instancer) - continue; - - if (Output->GetInstancedOutputs().Num() > 0) - return true; - /* - // TODO: Is this needed? check we have components to bake? - for (auto& OutputObjectPair : Output->GetOutputObjects()) - { - if (OutputObjectPair.Value.OutputCompoent!= nullpt) - return true; - } - */ - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor * OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return false; - - int32 BakedCount = 0; - TArray PackagesToSave; - - FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); - - // Build an array of the outputs so that we can search for meshes/previous baked meshes - const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); - TArray Outputs; - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); - if (!Output || Output->IsPendingKill()) - continue; - - Outputs.Add(Output); - } - - // Get the previous bake outputs and match the output array size - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - if (BakedOutputs.Num() != NumOutputs) - BakedOutputs.SetNum(NumOutputs); - - // Map storing original and baked Static Meshes - TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Instancer) - continue; - - // TODO: No need to use the instanced outputs for this - // We should simply iterate on the Output Objects instead! - TMap& OutputObjects = Output->GetOutputObjects(); - TMap& InstancedOutputs = Output->GetInstancedOutputs(); - for (auto & Pair : InstancedOutputs) - { - FString InstanceName = OwnerActor->GetName(); - - // // See if we have a bake name for that output - // FHoudiniOutputObject* OutputObj = OutputObjects.Find(Pair.Key); - // if (OutputObj && OutputObj->BakeName.IsEmpty()) - // InstanceName = OutputObj->BakeName; - - FHoudiniInstancedOutput& InstancedOutput = Pair.Value; - for (int32 VariarionIdx = 0; VariarionIdx < InstancedOutput.VariationObjects.Num(); ++VariarionIdx) - { - // TODO: !!! what if the instanced object/var is not a static mesh!!!!!! - UObject* CurrentVariationObject = InstancedOutput.VariationObjects[VariarionIdx].Get(); - UStaticMesh* InstancedStaticMesh = Cast(CurrentVariationObject); - if (!InstancedStaticMesh) - { - if (CurrentVariationObject) - { - HOUDINI_LOG_ERROR(TEXT("Failed to bake the instances of %s to Foliage"), *CurrentVariationObject->GetName()); - } - continue; - } - - // Check if we have already handled this mesh (already baked it from a previous variation), if so, - // use that - UStaticMesh* OutStaticMesh = nullptr; - bool bCreateNewType = true; - if (OriginalToBakedMesh.Contains(InstancedStaticMesh)) - { - OutStaticMesh = OriginalToBakedMesh.FindChecked(InstancedStaticMesh); - bCreateNewType = false; - } - - if (!IsValid(OutStaticMesh)) - { - // Find the output object and identifier for the mesh and previous bake of the mesh (if it exists) - FString ObjectName; - int32 MeshOutputIdx = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshOutputIdentifier; - UStaticMesh* PreviousBakeMesh = nullptr; - FHoudiniBakedOutputObject* BakedOutputObject = nullptr; - if (FindOutputObject(InstancedStaticMesh, Outputs, MeshOutputIdx, MeshOutputIdentifier)) - { - GetTemporaryOutputObjectBakeName(InstancedStaticMesh, Outputs, ObjectName); - - BakedOutputObject = &BakedOutputs[MeshOutputIdx].BakedOutputObjects.FindOrAdd(MeshOutputIdentifier); - if (BakedOutputObject) - { - PreviousBakeMesh = Cast(BakedOutputObject->GetBakedObjectIfValid()); - } - } - else - { - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); - } - - // If the instanced static mesh is still a temporary Houdini created Static Mesh - // we will duplicate/bake it first before baking to foliage - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - MeshOutputIdentifier, - HoudiniAssetComponent->BakeFolder.Path, - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // DuplicateStaticMeshAndCreatePackageIfNeeded uses baked results to find a baked version of - // InstancedStaticMesh in the current bake results, but since we are already using - // OriginalToBakedMesh we don't have to populate BakedResults - const TArray BakedResults; - OutStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - InstancedStaticMesh, - PreviousBakeMesh, - PackageParams, - Outputs, - BakedResults, - HoudiniAssetComponent->TemporaryCookFolder.Path, - PackagesToSave); - OriginalToBakedMesh.Add(InstancedStaticMesh, OutStaticMesh); - - // Update our tracked baked output - if (BakedOutputObject) - BakedOutputObject->BakedObject = FSoftObjectPath(OutStaticMesh).ToString(); - - bCreateNewType = true; - } - - // See if we already have a FoliageType for that static mesh - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(OutStaticMesh); - if (!FoliageType || FoliageType->IsPendingKill()) - { - // We need to create a new FoliageType for this Static Mesh - // TODO: Add foliage default settings - InstancedFoliageActor->AddMesh(OutStaticMesh, &FoliageType); - bCreateNewType = true; - } - - // If we are baking in replace mode, remove the foliage type if it already exists - // and a create a new one - if (bInReplaceAssets && bCreateNewType && IsValid(FoliageType)) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it - FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); - if (!FoliageInfo) - continue; - - // Apply the transform offset on the transforms for this variation - TArray ProcessedTransforms; - FHoudiniInstanceTranslator::ProcessInstanceTransforms(InstancedOutput, VariarionIdx, ProcessedTransforms); - - FFoliageInstance FoliageInstance; - int32 CurrentInstanceCount = 0; - for (auto CurrentTransform : ProcessedTransforms) - { - FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); - FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - CurrentInstanceCount++; - } - - // TODO: This was due to a bug in UE4.22-20, check if still needed! - if (FoliageInfo->GetComponent()) - FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); - - // Notify the user that we succesfully bake the instances to foliage - FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + OutStaticMesh->GetName() + TEXT(" to Foliage"); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - BakedCount += ProcessedTransforms.Num(); - } - } - } - - InstancedFoliageActor->RegisterAllComponents(); - - // Update / repopulate the foliage editor mode's mesh list - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - - if (BakedCount > 0) - { - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - return true; - } - - return false; -} - - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) - return false; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - - // Ensure we have the same number of baked outputs and asset outputs - if (InBakedOutputs.Num() != InAllOutputs.Num()) - InBakedOutputs.SetNum(InAllOutputs.Num()); - - // Iterate on the output objects, baking their object/component as we go - for (auto& Pair : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = Pair.Value; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputs[InOutputIndex].BakedOutputObjects.FindOrAdd(Pair.Key); - - if (CurrentOutputObject.bProxyIsCurrent) - { - // TODO: we need to refine the SM first! - // ?? - } - - if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill()) - continue; - - if (CurrentOutputObject.OutputComponent->IsA()) - { - // TODO: Baking foliage instancer to actors it not supported currently - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent))) - { - BakeInstancerOutputToActors_ISMC( - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent))) - { - BakeInstancerOutputToActors_IAC( - InOutputIndex, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InBakeFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) - { - BakeInstancerOutputToActors_MSIC( - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) - { - BakeInstancerOutputToActors_SMC( - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else - { - // Unsupported component! - } - - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UInstancedStaticMeshComponent * InISMC = Cast(InOutputObject.OutputComponent); - if (!InISMC || InISMC->IsPendingKill()) - return false; - - AActor * OwnerActor = InISMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh * StaticMesh = InISMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) - { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - } - - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // ObjectName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, PackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); - - // Update the baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a new level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if(!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - /* - // TODO: Get the bake name! - // Bake override, the output name - // The bake name override has priority - FString InstancerName = InOutputObject.BakeName; - if (InstancerName.IsEmpty()) - { - // .. then use the output name - InstancerName = Resolver.ResolveOutputName(); - } - */ - - // Should we create one actor with an ISMC or multiple actors with one SMC? - bool bSpawnMultipleSMC = false; - if (bSpawnMultipleSMC) - { - // TODO: Double check, Has a crash here! - - // Get the StaticMesh ActorFactory - UActorFactory* SMFactory = nullptr; - - if (!FoundActor) - { - SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!SMFactory) - return false; - } - - // Split the instances to multiple StaticMeshActors - for (int32 InstanceIdx = 0; InstanceIdx < InISMC->GetInstanceCount(); InstanceIdx++) - { - FTransform InstanceTransform; - InISMC->GetInstanceTransform(InstanceIdx, InstanceTransform, true); - - if (!FoundActor) - { - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform, RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) - continue; - } - - FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName, FoundActor); - // FoundActor->Rename(*NewName.ToString()); - // FoundActor->SetActorLabel(NewName.ToString()); - RenameAndRelabelActor(FoundActor, NewName.ToString(), false); - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - continue; - - // Copy properties from the existing component - CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC); - - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh)); - } - } - else - { - bool bSpawnedActor = false; - if (!FoundActor) - { - // Only create one actor - FActorSpawnParameters SpawnInfo; - SpawnInfo.OverrideLevel = DesiredLevel; - SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); - SpawnInfo.bDeferConstruction = true; - - // Spawn the new Actor - FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - bSpawnedActor = true; - - FoundActor->SetActorLabel(FoundActor->GetName()); - FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); - } - else - { - // If there is a previously baked component, and we are in replace mode, remove it - if (bInReplaceAssets) - { - USceneComponent* InPrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(InPrevComponent) && InPrevComponent->GetOwner() == FoundActor) - RemovePreviouslyBakedComponent(InPrevComponent); - } - - const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); - } - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Get/create the actor's root component - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - if (bSpawnedActor && IsValid(RootComponent)) - RootComponent->SetWorldTransform(InTransform); - - // Duplicate the instancer component, create a Hierarchical ISMC if needed - UInstancedStaticMeshComponent* NewISMC = nullptr; - UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast(InISMC); - if (InHISMC) - { - NewISMC = DuplicateObject( - InHISMC, - FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetFName())); - } - else - { - NewISMC = DuplicateObject( - InISMC, - FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetFName())); - } - - if (!NewISMC) - { - //DesiredLevel->OwningWorld-> - return false; - } - - InBakedOutputObject.BakedComponent = FSoftObjectPath(NewISMC).ToString(); - - NewISMC->RegisterComponent(); - // NewISMC->SetupAttachment(nullptr); - NewISMC->SetStaticMesh(BakedStaticMesh); - FoundActor->AddInstanceComponent(NewISMC); - // NewActor->SetRootComponent(NewISMC); - if (IsValid(RootComponent)) - NewISMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - NewISMC->SetWorldTransform(InISMC->GetComponentTransform()); - - // TODO: do we need to copy properties here, we duplicated the component - // // Copy properties from the existing component - // CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); - - if (bSpawnedActor) - FoundActor->FinishSpawning(InTransform); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh)); - - FoundActor->InvalidateLightingCache(); - FoundActor->PostEditMove(true); - FoundActor->MarkPackageDirty(); - } - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UStaticMeshComponent* InSMC = Cast(InOutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) - return false; - - AActor* OwnerActor = InSMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh* StaticMesh = InSMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) - { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - } - - // BaseName holds the Actor / HDA name - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, - OutActors, InTempCookFolder.Path, OutPackagesToSave); - - // Update the previous baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - UStaticMeshComponent* StaticMeshComponent = nullptr; - // Create an actor if we didn't find one - if (!FoundActor) - { - // Get the StaticMesh ActorFactory - UActorFactory* SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!SMFactory) - return false; - - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - return false; - - StaticMeshComponent = SMActor->GetStaticMeshComponent(); - } - else - { - USceneComponent* RootComponent = GetActorRootComponent(FoundActor); - if (!IsValid(RootComponent)) - return false; - - if (bInReplaceAssets) - { - // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it - UStaticMeshComponent* PrevSMC = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) - { - StaticMeshComponent = PrevSMC; - } - } - - if (!IsValid(StaticMeshComponent)) - { - // Create a new static mesh component - StaticMeshComponent = NewObject(FoundActor, NAME_None, RF_Transactional); - - FoundActor->AddInstanceComponent(StaticMeshComponent); - StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - StaticMeshComponent->RegisterComponent(); - } - } - - FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName, FoundActor); - // FoundActor->Rename(*NewName.ToString()); - // FoundActor->SetActorLabel(NewName.ToString()); - RenameAndRelabelActor(FoundActor, NewName.ToString(), false); - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Update the previous baked component - InBakedOutputObject.BakedComponent = FSoftObjectPath(StaticMeshComponent).ToString(); - - if (!IsValid(StaticMeshComponent)) - return false; - - // Copy properties from the existing component - CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC); - StaticMeshComponent->SetStaticMesh(BakedStaticMesh); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh)); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave) -{ - UHoudiniInstancedActorComponent* InIAC = Cast(InOutputObject.OutputComponent); - if (!InIAC || InIAC->IsPendingKill()) - return false; - - AActor * OwnerActor = InIAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - // BaseName holds the Actor / HDA name - const FName BaseName = FName(OwnerActor->GetName()); - - // Get the object instanced by this IAC - UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - return false; - - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - BaseName.ToString(), - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // If we are baking in actor replacement mode, remove any previously baked instanced actors for this output - if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0) - { - UWorld* LevelWorld = DesiredLevel->GetWorld(); - if (IsValid(LevelWorld)) - { - for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) - { - const FSoftObjectPath ActorPath(ActorPathStr); - - if (!ActorPath.IsValid()) - continue; - - AActor* Actor = Cast(ActorPath.TryLoad()); - // Destroy Actor if it is valid and part of DesiredLevel - if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel) - { -#if WITH_EDITOR - LevelWorld->EditorDestroyActor(Actor, true); -#else - LevelWorld->DestroyActor(Actor); -#endif - } - } - } - } - - // Empty and reserve enough space for new instanced actors - InBakedOutputObject.InstancedActors.Empty(InIAC->GetInstancedActors().Num()); - - // Iterates on all the instances of the IAC - for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors()) - { - if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) - continue; - - FName NewInstanceName = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName); - FString NewNameStr = NewInstanceName.ToString(); - - FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); - AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); - if (!NewActor || NewActor->IsPendingKill()) - continue; - - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); - - NewActor->SetActorLabel(NewNameStr); - SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); - NewActor->SetActorTransform(CurrentTransform); - - InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); - - OutActors.Add(FHoudiniEngineBakedActor( - NewActor, - BaseName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - nullptr, - InstancedObject)); - } - - // TODO: - // Move Actors to DesiredLevel if needed?? - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = true; - const bool bInDestroyBakedInstancedActors = false; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UHoudiniMeshSplitInstancerComponent * InMSIC = Cast(InOutputObject.OutputComponent); - if (!InMSIC || InMSIC->IsPendingKill()) - return false; - - AActor * OwnerActor = InMSIC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh * StaticMesh = InMSIC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) - { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - } - - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, - OutActors, InTempCookFolder.Path, OutPackagesToSave); - - // Update the baked output - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Get the level specified by attribute - // Access some of the attributes that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - bool bSpawnedActor = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - if (!FoundActor) - { - // This is a split mesh instancer component - we will create a generic AActor with a bunch of SMC - FActorSpawnParameters SpawnInfo; - SpawnInfo.OverrideLevel = DesiredLevel; - SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); - SpawnInfo.bDeferConstruction = true; - - // Spawn the new Actor - FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - bSpawnedActor = true; - - FoundActor->SetActorLabel(FoundActor->GetName()); - FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); - } - else - { - // If we are baking in replacement mode, remove the previous components (if they belong to FoundActor) - for (const FString& PrevComponentPathStr : InBakedOutputObject.InstancedComponents) - { - const FSoftObjectPath PrevComponentPath(PrevComponentPathStr); - - if (!PrevComponentPath.IsValid()) - continue; - - UActorComponent* PrevComponent = Cast(PrevComponentPath.TryLoad()); - if (!IsValid(PrevComponent) || PrevComponent->GetOwner() != FoundActor) - continue; - - RemovePreviouslyBakedComponent(PrevComponent); - } - - const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); - } - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Get/create the actor's root component - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - if (bSpawnedActor && IsValid(RootComponent)) - RootComponent->SetWorldTransform(InTransform); - - // Empty and reserve enough space in the baked components array for the new components - InBakedOutputObject.InstancedComponents.Empty(InMSIC->GetInstances().Num()); - - // Now add s SMC component for each of the SMC's instance - for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances()) - { - if (!CurrentSMC || CurrentSMC->IsPendingKill()) - continue; - - UStaticMeshComponent* NewSMC = DuplicateObject( - CurrentSMC, - FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetFName())); - if (!NewSMC || NewSMC->IsPendingKill()) - continue; - - InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString()); - - NewSMC->RegisterComponent(); - // NewSMC->SetupAttachment(nullptr); - NewSMC->SetStaticMesh(BakedStaticMesh); - FoundActor->AddInstanceComponent(NewSMC); - NewSMC->SetWorldTransform(CurrentSMC->GetComponentTransform()); - if (IsValid(RootComponent)) - NewSMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); - - // TODO: Do we need to copy properties here, we duplicated the component - // // Copy properties from the existing component - // CopyPropertyToNewActorAndComponent(FoundActor, NewSMC, CurrentSMC); - } - - if (bSpawnedActor) - FoundActor->FinishSpawning(InTransform); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh)); - - FoundActor->InvalidateLightingCache(); - FoundActor->PostEditMove(true); - FoundActor->MarkPackageDirty(); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = true; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = false; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::FindHGPO( - const FHoudiniOutputObjectIdentifier& InIdentifier, - const TArray& InHGPOs, - FHoudiniGeoPartObject const*& OutHGPO) -{ - // Find the HGPO that matches this output identifier - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - for (auto & NextHGPO : InHGPOs) - { - // We use Matches() here as it handles the case where the HDA was loaded, - // which likely means that the the obj/geo/part ids dont match the output identifier - if(InIdentifier.Matches(NextHGPO)) - { - FoundHGPO = &NextHGPO; - break; - } - } - - OutHGPO = FoundHGPO; - return !OutHGPO; -} - -void -FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const FHoudiniOutputObject& InMeshOutputObject, - FString& OutBakeName) -{ - // The bake name override has priority - OutBakeName = InMeshOutputObject.BakeName; - if (OutBakeName.IsEmpty()) - { - FHoudiniAttributeResolver Resolver; - Resolver.SetCachedAttributes(InMeshOutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(InMeshOutputObject.CachedTokens); - const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InObject); - // The default output name (if not set via attributes) is {object_name}, which look for an object_name - // key-value token - if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) - Resolver.SetToken(TEXT("object_name"), DefaultObjectName); - OutBakeName = Resolver.ResolveOutputName(); - // const TArray& HGPOs = InAllOutputs[MeshOutputIdx]->GetHoudiniGeoPartObjects(); - // const FHoudiniGeoPartObject* FoundHGPO = nullptr; - // FindHGPO(MeshIdentifier, HGPOs, FoundHGPO); - // // ... finally the part name - // if (FoundHGPO && FoundHGPO->bHasCustomPartName) - // OutBakeName = FoundHGPO->PartName; - if (OutBakeName.IsEmpty()) - OutBakeName = DefaultObjectName; - } -} - -bool -FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const TArray& InAllOutputs, - FString& OutBakeName) -{ - if (!IsValid(InObject)) - return false; - - OutBakeName.Empty(); - - int32 MeshOutputIdx = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - if (FindOutputObject(InObject, InAllOutputs, MeshOutputIdx, MeshIdentifier)) - { - // Found the mesh, get its name - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier); - GetTemporaryOutputObjectBakeName(InObject, MeshOutputObject, OutBakeName); - - return true; - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) - return false; - - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!Factory) - return false; - - TMap& OutputObjects = InOutput->GetOutputObjects(); -const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); - - // Get the previous bake objects - if (InOutputIndex >= 0 && !InBakedOutputs.IsValidIndex(InOutputIndex)) - InBakedOutputs.SetNum(InOutputIndex + 1); - TMap& BakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; - - for (auto& Pair : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - const FHoudiniOutputObject& OutputObject = Pair.Value; - - // Fetch previous bake output - FHoudiniBakedOutputObject& BakedOutputObject = BakedOutputObjects.FindOrAdd(Identifier); - - UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) - continue; - - UStaticMeshComponent* InSMC = Cast(OutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) - continue; - - // Find the HGPO that matches this output identifier - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - FindHGPO(Identifier, HGPOs, FoundHGPO); - - // We do not bake templated geos - if (FoundHGPO && FoundHGPO->bIsTemplated) - continue; - - FHoudiniAttributeResolver Resolver; - Resolver.SetCachedAttributes(OutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(OutputObject.CachedTokens); - const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - // The default output name (if not set via attributes) is {object_name}, which look for an object_name - // key-value token - if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) - Resolver.SetToken(TEXT("object_name"), DefaultObjectName); - - // The bake name override has priority - FString SMName = OutputObject.BakeName; - if (SMName.IsEmpty()) - { - // // ... finally the part name - // if (FoundHGPO && FoundHGPO->bHasCustomPartName) - // SMName = FoundHGPO->PartName; - // else - SMName = Resolver.ResolveOutputName(); - if (SMName.IsEmpty()) - SMName = DefaultObjectName; - } - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, Identifier, InBakeFolder.Path, SMName, - InHoudiniAssetName, AssetPackageReplaceMode); - - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); - - UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - // See if this output object has an unreal_level_path attribute specified - // In which case, we need to create/find the desired level for baking instead of using the current one - bool bHasLevelPathAttribute = OutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Access some of the attribute that were cached on the output object - // FHoudiniAttributeResolver Resolver; - // const TMap& CachedAttributes = OutputObject.CachedAttributes; - TMap Tokens = OutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - // Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - continue; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add the level to the packages to save? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - // Bake the static mesh if it is still temporary - UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, - Cast(BakedOutputObject.GetBakedObjectIfValid()), - PackageParams, - InAllOutputs, - OutActors, - InTempCookFolder.Path, - OutPackagesToSave); - - if (!BakedSM || BakedSM->IsPendingKill()) - continue; - - // Record the baked object - BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString(); - - // Make sure we have a level to spawn to - if (!DesiredLevel || DesiredLevel->IsPendingKill()) - continue; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - UStaticMeshComponent* SMC = nullptr; - if (!FoundActor) - { - // Spawn the new actor - FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) - continue; - - // Copy properties to new actor - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - continue; - - SMC = SMActor->GetStaticMeshComponent(); - } - else - { - if (bInReplaceAssets) - { - // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it - UStaticMeshComponent* PrevSMC = Cast(BakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) - { - SMC = PrevSMC; - } - } - - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - - if (!IsValid(SMC)) - { - // Create a new static mesh component on the existing actor - SMC = NewObject(FoundActor, NAME_None, RF_Transactional); - - FoundActor->AddInstanceComponent(SMC); - if (IsValid(RootComponent)) - SMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - else - FoundActor->SetRootComponent(SMC); - SMC->RegisterComponent(); - } - } - - // We need to make a unique name for the actor, renaming an object on top of another is a fatal error - const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName, FoundActor); - const FString NewNameStr = NewName.ToString(); - // FoundActor->Rename(*NewNameStr); - // FoundActor->SetActorLabel(NewNameStr); - RenameAndRelabelActor(FoundActor, NewNameStr, false); - SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath); - - if (IsValid(SMC)) - { - CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC); - SMC->SetStaticMesh(BakedSM); - BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString(); - } - - BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh)); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - BakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - UHoudiniOutput* Output, - TMap& InBakedOutputObjects, - const TArray& InAllBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!Output || Output->IsPendingKill()) - return false; - - TArray PackagesToSave; - - TMap& OutputObjects = Output->GetOutputObjects(); - const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); - - for (auto & Pair : OutputObjects) - { - FHoudiniOutputObject& OutputObject = Pair.Value; - USplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - FHoudiniOutputObjectIdentifier & Identifier = Pair.Key; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(Identifier); - - // TODO: FIX ME!! May not work 100% - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - for (auto & NextHGPO : HGPOs) - { - if (Identifier.GeoId == NextHGPO.GeoId && - Identifier.ObjectId == NextHGPO.ObjectId && - Identifier.PartId == NextHGPO.PartId) - { - FoundHGPO = &NextHGPO; - break; - } - } - - if (!FoundHGPO) - continue; - - FString CurveName = Pair.Value.BakeName; - if (CurveName.IsEmpty()) - { - if (FoundHGPO->bHasCustomPartName) - CurveName = FoundHGPO->PartName; - else - CurveName = InHoudiniAssetName + "_" + SplineComponent->GetName(); - } - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, Identifier, InBakeFolder.Path, CurveName, - InHoudiniAssetName, AssetPackageReplaceMode); - - BakeCurve(OutputObject, BakedOutputObject, PackageParams, bInReplaceActors, bInReplaceAssets, OutActors, - PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); - } - - SaveBakedPackages(PackagesToSave); - - return true; -} - -bool -FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint) -{ - if (!InActor || InActor->IsPendingKill()) - return false; - - if (!OutBlueprint || OutBlueprint->IsPendingKill()) - return false; - - if (InActor->GetInstanceComponents().Num() > 0) - FKismetEditorUtilities::AddComponentsToBlueprint( - OutBlueprint, - InActor->GetInstanceComponents()); - - if (OutBlueprint->GeneratedClass) - { - AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject()); - if (!CDO || CDO->IsPendingKill()) - return false; - - const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) - (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); - - EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions); - - USceneComponent * Scene = CDO->GetRootComponent(); - if (Scene && !Scene->IsPendingKill()) - { - Scene->SetRelativeLocation(FVector::ZeroVector); - Scene->SetRelativeRotation(FRotator::ZeroRotator); - - // Clear out the attachment info after having copied the properties from the source actor - Scene->SetupAttachment(nullptr); - while (true) - { - const int32 ChildCount = Scene->GetAttachChildren().Num(); - if (ChildCount < 1) - break; - - USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; - if (Component && !Component->IsPendingKill()) - Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - } - check(Scene->GetAttachChildren().Num() == 0); - - // Ensure the light mass information is cleaned up - Scene->InvalidateLightingCache(); - - // Copy relative scale from source to target. - if (USceneComponent* SrcSceneRoot = InActor->GetRootComponent()) - { - Scene->SetRelativeScale3D_Direct(SrcSceneRoot->GetRelativeScale3D()); - } - } - } - - // Compile our blueprint and notify asset system about blueprint. - //FKismetEditorUtilities::CompileBlueprint(OutBlueprint); - //FAssetRegistryModule::AssetCreated(OutBlueprint); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) -{ - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, BakeStats, Blueprints, PackagesToSave); - if (!bSuccess) - { - // TODO: ? - HOUDINI_LOG_WARNING(TEXT("Errors while baking to blueprints.")); - } - - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprints( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceAssets, - FHoudiniEngineOutputStats& InBakeStats, - TArray& OutBlueprints, - TArray& OutPackagesToSave) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); - const bool bIsOwnerActorValid = IsValid(OwnerActor); - - TArray Actors; - - // Don't process outputs that are not supported in blueprints - TArray OutputsToBake = { - EHoudiniOutputType::Mesh, - EHoudiniOutputType::Instancer, - EHoudiniOutputType::Curve - }; - TArray InstancerComponentTypesToBake = { - EHoudiniInstancerComponentType::StaticMeshComponent, - EHoudiniInstancerComponentType::InstancedStaticMeshComponent, - EHoudiniInstancerComponentType::MeshSplitInstancerComponent, - }; - // When baking blueprints we always create new actors since they are deleted from the world once copied into the - // blueprint - const bool bReplaceActors = false; - bool bBakeSuccess = BakeHoudiniActorToActors( - HoudiniAssetComponent, - bReplaceActors, - bInReplaceAssets, - Actors, - OutPackagesToSave, - InBakeStats, - &OutputsToBake, - &InstancerComponentTypesToBake); - if (!bBakeSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Could not create output actors for baking to blueprint.")); - return false; - } - - // Get the previous baked outputs - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - - bBakeSuccess = BakeBlueprintsFromBakedActors( - Actors, - HoudiniAssetComponent->bRecenterBakedActors, - bInReplaceAssets, - bIsOwnerActorValid ? OwnerActor->GetName() : FString(), - HoudiniAssetComponent->BakeFolder, - &BakedOutputs, - nullptr, - OutBlueprints, - OutPackagesToSave); - - return bBakeSuccess; -} - -UStaticMesh* -FHoudiniEngineBakeUtils::BakeStaticMesh( - UStaticMesh * StaticMesh, - const FHoudiniPackageParams& PackageParams, - const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return nullptr; - - TArray PackagesToSave; - TArray Outputs; - const TArray BakedResults; - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave); - - if (BakedStaticMesh) - { - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor) - { - TArray Objects; - Objects.Add(BakedStaticMesh); - GEditor->SyncBrowserToObjects(Objects); - } - } - - return BakedStaticMesh; -} - -bool -FHoudiniEngineBakeUtils::BakeLandscape( - int32 InOutputIndex, - UHoudiniOutput* InOutput, - TMap& InBakedOutputObjects, - bool bInReplaceActors, - bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, - FHoudiniEngineOutputStats& BakeStats - ) -{ - if (!IsValid(InOutput)) - return false; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - TArray PackagesToSave; - TArray LandscapeWorldsToUpdate; - - FHoudiniPackageParams PackageParams; - - for (auto& Elem : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key; - FHoudiniOutputObject& OutputObject = Elem.Value; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(ObjectIdentifier); - - // Populate the package params for baking this output object. - if (!IsValid(OutputObject.OutputObject)) - continue; - - if (!OutputObject.OutputObject->IsA()) - continue; - - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); - if (!IsValid(Landscape)) - continue; - - FString ObjectName = Landscape->GetName(); - - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - ObjectIdentifier, - BakePath, - ObjectName, - HoudiniAssetName, - AssetPackageReplaceMode - ); - - BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, - PackageParams, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); - } - - if (PackagesToSave.Num() > 0) - { - FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); - } - - for(UWorld* LandscapeWorld : LandscapeWorldsToUpdate) - { - if (!LandscapeWorld) - continue; - FHoudiniEngineUtils::RescanWorldPath(LandscapeWorld); - ULandscapeInfo::RecreateLandscapeInfo(LandscapeWorld, true); - if (LandscapeWorld->WorldComposition) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(LandscapeWorld); - } - } - - if (PackagesToSave.Num() > 0) - { - // These packages were either created during the Bake process or they weren't - // loaded in the first place so be sure to unload them again to preserve their "state". - - TArray PackagesToUnload; - for (UPackage* Package : PackagesToSave) - { - if (!Package->IsDirty()) - PackagesToUnload.Add(Package); - } - UPackageTools::UnloadPackages(PackagesToUnload); - } - -#if WITH_EDITOR - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); -#endif - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeLandscapeObject( - FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInReplaceActors, - bool bInReplaceAssets, - FHoudiniPackageParams& PackageParams, - TArray& WorldsToUpdate, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& BakeStats) -{ - UHoudiniLandscapePtr* LandscapePointer = Cast(InOutputObject.OutputObject); - if (!LandscapePointer) - return false; - - ALandscapeProxy* TileActor = LandscapePointer->GetRawPtr(); - if (!TileActor) - return false; - - // Fetch the previous bake's pointer and proxy (if available) - ALandscapeProxy* PreviousTileActor = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - - UWorld* TileWorld = TileActor->GetWorld(); - ULevel* TileLevel = TileActor->GetLevel(); - - ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true); - - // At this point we reconstruct the resolver using cached attributes and tokens - // and just update certain tokens (output paths) for bake mode. - FHoudiniAttributeResolver Resolver; - { - TMap Tokens = InOutputObject.CachedTokens; - // PackageParams.UpdateOutputPathTokens(EPackageMode::Bake, Tokens); - PackageParams.UpdateTokensFromParams(TileWorld, Tokens); - Resolver.SetCachedAttributes(InOutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC - // and has the appropriate name. - ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor(); - check(SharedLandscapeActor); - - // Fetch the previous bake's shared landscape actor (if available) - ALandscape* PreviousSharedLandscapeActor = nullptr; - if (IsValid(PreviousTileActor)) - PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor(); - - const bool bHasSharedLandscape = SharedLandscapeActor != TileActor; - const bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; - bool bLandscapeReplaced = false; - if (bHasSharedLandscape) - { - // If we are baking in replace mode and we have a previous shared landscape actor, use the name of that - // actor - const FString DesiredSharedLandscapeName = bHasPreviousSharedLandscape && bInReplaceActors - ? PreviousSharedLandscapeActor->GetName() - : Resolver.ResolveAttribute( - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, - SharedLandscapeActor->GetName()); - - // If we are not baking in replacement mode, create a unique name if the name is already in use - const FString SharedLandscapeName = !bInReplaceActors - ? MakeUniqueObjectNameIfNeeded(SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *DesiredSharedLandscapeName).ToString() - : DesiredSharedLandscapeName; - - if (SharedLandscapeActor->GetName() != SharedLandscapeName) - { - AActor* FoundActor = nullptr; - ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); - if (ExistingLandscape && bInReplaceActors) - { - // Even though we found an existing landscape with the desired type, we're just going to destroy/replace - // it for now. - FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); - ExistingLandscape->Destroy(); - bLandscapeReplaced = true; - } - - // Fix name of shared landscape - FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); - } - - SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); - } - - // Find the world where the landscape tile should be placed. - - TArray ValidLandscapes; - - FString ActorName = Resolver.ResolveOutputName(); - - // If the unreal_level_path was not specified, then fallback to the tile world's package - FString PackagePath = TileWorld->GetOutermost() ? TileWorld->GetOutermost()->GetPathName() : FString(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - PackagePath = Resolver.ResolveFullLevelPath(); - - if (bInReplaceActors) - { - // If we are baking in replace mode: get the previous baked actor (if available) name, but only if it is in the - // same target level - if (IsValid(PreviousTileActor)) - { - UPackage* PreviousPackage = PreviousTileActor->GetOutermost();//GetPackage(); - if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath) - { - ActorName = PreviousTileActor->GetName(); - } - } - } - - bool bCreatedPackage = false; - UWorld* TargetWorld = nullptr; - ULevel* TargetLevel = nullptr; - ALandscapeProxy* TargetActor = FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( - TileActor->GetWorld(), - nullptr, //unused in bake mode - ValidLandscapes,//unused in bake mode - -1, //unused in bake mode - -1, //unused in bake mode - ActorName, - PackagePath, - TargetWorld, - TargetLevel, - bCreatedPackage - ); - - check(TargetLevel) - check(TargetWorld) - - if (TargetActor && TargetActor != TileActor) - { - if (bInReplaceActors && (!PreviousTileActor || PreviousTileActor == TargetActor)) - { - // We found an target matching the name that we want. For now, rename it and then nuke it, so that - // at the very least we can spawn a new actor with the desired name. At a later stage we'll implement - // a content update, if possible. - FHoudiniEngineUtils::RenameToUniqueActor(TargetActor, ActorName + TEXT("_0")); - TargetActor->Destroy(); - } - else - { - // incremental, keep existing actor and create a unique name for the new one - ActorName = MakeUniqueObjectName(TargetActor->GetOuter(), TargetActor->GetClass(), *ActorName).ToString(); - } - TargetActor = nullptr; - } - - if (TargetLevel != TileActor->GetLevel()) - { - bool bLevelInWorld = TileWorld->ContainsLevel(TargetLevel); - ALandscape* SharedLandscape = TileActor->GetLandscapeActor(); - ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); - - check(LandscapeInfo); - - // We can now move the current landscape to the new world / level - // if (TileActor->GetClass()->IsChildOf()) - { - // We can only move streaming proxies to sublevels for now. - TArray ActorsToMove = {TileActor}; - - ALandscapeProxy* NewLandscapeProxy = LandscapeInfo->MoveComponentsToLevel(TileActor->LandscapeComponents, TargetLevel); - // We have now moved the landscape components into the new level. We can (hopefully) safely delete the - // old tile actor. - TileActor->Destroy(); - - TargetLevel->MarkPackageDirty(); - - TileActor = NewLandscapeProxy; - } - } - else - { - // Ensure the landscape actor is detached. - TileActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - } - - // Ensure the tile actor has the desired name. - FHoudiniEngineUtils::SafeRenameActor(TileActor, ActorName); - - if (TileActor->GetClass()->IsChildOf(ALandscape::StaticClass())) - { - // This is not a shared landscape. Be sure to update this landscape's world when - // baking is done. - WorldsToUpdate.AddUnique(TileActor->GetWorld()); - } - - if (bCreatedPackage) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(TargetLevel->GetOutermost()); - } - - // Record the landscape in the baked output object via a new UHoudiniLandscapePtr - // UHoudiniLandscapePtr* BakedLandscapePtr = NewObject(LandscapePointer->GetOuter()); - // if (IsValid(BakedLandscapePtr)) - // { - // BakedLandscapePtr->SetSoftPtr(TileActor); - InBakedOutputObject.BakedObject = FSoftObjectPath(TileActor).ToString(); - // } - // else - // { - // InBakedOutputObject.BakedObject = nullptr; - // } - - // Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks - InOutputObject.OutputObject = nullptr; - - DestroyPreviousBakeOutput(InBakedOutputObject, true, true, true); - - // ---------------------------------------------------- - // Collect baking stats - // ---------------------------------------------------- - if (bLandscapeReplaced) - BakeStats.NotifyObjectsReplaced(EHoudiniOutputType::Landscape, 1); - else - BakeStats.NotifyObjectsCreated(EHoudiniOutputType::Landscape, 1); - - if (bCreatedPackage) - BakeStats.NotifyPackageCreated(1); - else - if (TileLevel != TargetLevel) - BakeStats.NotifyPackageUpdated(1); - - return true; -} - -UStaticMesh * -FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - UStaticMesh * InStaticMesh, - UStaticMesh * InPreviousBakeStaticMesh, - const FHoudiniPackageParams &PackageParams, - const TArray& InParentOutputs, - const TArray& InCurrentBakedActors, - const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages) -{ - if (!InStaticMesh || InStaticMesh->IsPendingKill()) - return nullptr; - - bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, InParentOutputs, InTemporaryCookFolder); - if (!bIsTemporaryStaticMesh) - { - // The Static Mesh is not a temporary one/already baked, we can simply reuse it - // instead of duplicating it - return InStaticMesh; - } - - // Look for InStaticMesh as the SourceObject in InCurrentBakedActors (it could have already been baked along with - // a previous output: instancers etc) - for (const FHoudiniEngineBakedActor& BakedActor : InCurrentBakedActors) - { - if (BakedActor.SourceObject == InStaticMesh && IsValid(BakedActor.BakedObject) - && BakedActor.BakedObject->IsA(InStaticMesh->GetClass())) - { - // We have found a bake result where InStaticMesh was the source object and we have a valid BakedObject - // of a compatible class - return Cast(BakedActor.BakedObject); - } - } - - // InStaticMesh is temporary and we didn't find a baked version of it in our current bake output, we need to bake it - - // If we have a previously baked static mesh, get the bake counter from it so that both replace and increment - // is consistent with the bake counter - int32 BakeCounter = 0; - bool bPreviousBakeStaticMeshValid = IsValid(InPreviousBakeStaticMesh); - TArray PreviousBakeMaterials; - if (bPreviousBakeStaticMeshValid) - { - bPreviousBakeStaticMeshValid = PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBakeStaticMesh); - if (bPreviousBakeStaticMeshValid) - { - PackageParams.GetBakeCounterFromBakedAsset(InPreviousBakeStaticMesh, BakeCounter); - PreviousBakeMaterials = InPreviousBakeStaticMesh->StaticMaterials;//GetStaticMaterials(); - } - } - FString CreatedPackageName; - UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter); - if (!MeshPackage || MeshPackage->IsPendingKill()) - return nullptr; - - OutCreatedPackages.Add(MeshPackage); - - // We need to be sure the package has been fully loaded before calling DuplicateObject - if (!MeshPackage->IsFullyLoaded()) - { - FlushAsyncLoading(); - if (!MeshPackage->GetOuter()) - { - MeshPackage->FullyLoad(); - } - else - { - MeshPackage->GetOutermost()->FullyLoad(); - } - } - - // If the a UStaticMesh with that name already exists then detach it from all of its components before replacing - // it so that its render resources can be safely replaced/updated, and then reattach it - UStaticMesh * DuplicatedStaticMesh = nullptr; - UStaticMesh* ExistingMesh = FindObject(MeshPackage, *CreatedPackageName); - if (IsValid(ExistingMesh)) - { - FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); - DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); - } - else - { - DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); - } - - if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MeshPackage, DuplicatedStaticMesh, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MeshPackage, DuplicatedStaticMesh, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedPackageName); - - // See if we need to duplicate materials and textures. - TArrayDuplicatedMaterials; - TArray& Materials = DuplicatedStaticMesh->StaticMaterials; - for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) - { - UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - continue; - - // Only duplicate the material if it is temporary - if (IsObjectTemporary(MaterialInterface, InParentOutputs, InTemporaryCookFolder)) - { - UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); - if (MaterialPackage && !MaterialPackage->IsPendingKill()) - { - FString MaterialName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( - MeshPackage, DuplicatedStaticMesh, MaterialName)) - { - MaterialName = MaterialName + "_Material" + FString::FromInt(MaterialIdx + 1); - - // We only deal with materials. - UMaterial * Material = Cast< UMaterial >(MaterialInterface); - if (Material && !Material->IsPendingKill()) - { - // Look for a previous bake material at this index - UMaterial* PreviousBakeMaterial = nullptr; - if (bPreviousBakeStaticMeshValid && PreviousBakeMaterials.IsValidIndex(MaterialIdx)) - { - PreviousBakeMaterial = Cast(PreviousBakeMaterials[MaterialIdx].MaterialInterface); - } - // Duplicate material resource. - UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages); - - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) - continue; - - // Store duplicated material. - FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx]; - DupeStaticMaterial.MaterialInterface = DuplicatedMaterial; - DuplicatedMaterials.Add(DupeStaticMaterial); - continue; - } - } - } - } - - // We can simply reuse the source material - DuplicatedMaterials.Add(Materials[MaterialIdx]); - } - - // Assign duplicated materials. - DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; - - // Notify registry that we have created a new duplicate mesh. - FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); - - // Dirty the static mesh package. - DuplicatedStaticMesh->MarkPackageDirty(); - - return DuplicatedStaticMesh; -} - -ALandscapeProxy* -FHoudiniEngineBakeUtils::BakeHeightfield( - ALandscapeProxy * InLandscapeProxy, - const FHoudiniPackageParams & PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType) -{ - if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill()) - return nullptr; - - const FString & BakeFolder = PackageParams.BakeFolder; - const FString & AssetName = PackageParams.HoudiniAssetName; - - switch (LandscapeOutputBakeType) - { - case EHoudiniLandscapeOutputBakeType::Detachment: - { - // Detach the landscape from the Houdini Asset Actor - InLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - } - break; - case EHoudiniLandscapeOutputBakeType::BakeToImage: - { - // Create heightmap image to the bake folder - ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) - return nullptr; - - // bake to image must use absoluate path, - // and the file name has a file extension (.png) - FString BakeFolderInFullPath = BakeFolder; - - // Figure absolute path, - if (!BakeFolderInFullPath.EndsWith("/")) - BakeFolderInFullPath += "/"; - - if (BakeFolderInFullPath.StartsWith("/Game")) - BakeFolderInFullPath = BakeFolderInFullPath.Mid(5, BakeFolderInFullPath.Len() - 5); - - if (BakeFolderInFullPath.StartsWith("/")) - BakeFolderInFullPath = BakeFolderInFullPath.Mid(1, BakeFolderInFullPath.Len() - 1); - - FString FullPath = FPaths::ProjectContentDir() + BakeFolderInFullPath + AssetName + "_" + InLandscapeProxy->GetName() + ".png"; - - InLandscapeInfo->ExportHeightmap(FullPath); - - // TODO: - // We should update this to have an asset/package.. - } - break; - case EHoudiniLandscapeOutputBakeType::BakeToWorld: - { - ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) - return nullptr; - - // 0. Get Landscape Data // - - // Extract landscape height data - TArray InLandscapeHeightData; - int32 XSize, YSize; - FVector Min, Max; - if (!FUnrealLandscapeTranslator::GetLandscapeData(InLandscapeProxy, InLandscapeHeightData, XSize, YSize, Min, Max)) - return nullptr; - - // Extract landscape Layers data - TArray InLandscapeImportLayerInfos; - for (int32 n = 0; n < InLandscapeInfo->Layers.Num(); ++n) - { - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - FLandscapeImportLayerInfo CurrentLayerInfo; - CurrentLayerInfo.LayerName = FName(LayerName); - CurrentLayerInfo.LayerInfo = InLandscapeInfo->Layers[n].LayerInfoObj; - CurrentLayerInfo.LayerData = CurrentLayerIntData; - - CurrentLayerInfo.LayerInfo->LayerUsageDebugColor = LayerUsageDebugColor; - - InLandscapeImportLayerInfos.Add(CurrentLayerInfo); - } - - // 1. Create package // - - FString PackagePath = PackageParams.GetPackagePath(); - FString PackageName = PackageParams.GetPackageName(); - - UPackage *CreatedPackage = nullptr; - FString CreatedPackageName; - - CreatedPackage = PackageParams.CreatePackageForObject(CreatedPackageName); - - if (!CreatedPackage) - return nullptr; - - // 2. Create a new world asset with dialog // - UWorldFactory* Factory = NewObject(); - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - UObject* Asset = AssetToolsModule.Get().CreateAssetWithDialog( - PackageName, PackagePath, - UWorld::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - - - UWorld* NewWorld = Cast(Asset); - if (!NewWorld) - return nullptr; - - NewWorld->SetCurrentLevel(NewWorld->PersistentLevel); - - // 4. Spawn a landscape proxy actor in the created world - ALandscapeStreamingProxy * BakedLandscapeProxy = NewWorld->SpawnActor(); - if (!BakedLandscapeProxy) - return nullptr; - - // Create a new GUID - FGuid currentGUID = FGuid::NewGuid(); - BakedLandscapeProxy->SetLandscapeGuid(currentGUID); - - // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue - BakedLandscapeProxy->bCastStaticShadow = false; - - - // 5. Import data to the created landscape proxy - TMap> HeightmapDataPerLayers; - TMap> MaterialLayerDataPerLayer; - - HeightmapDataPerLayers.Add(FGuid(), InLandscapeHeightData); - MaterialLayerDataPerLayer.Add(FGuid(), InLandscapeImportLayerInfos); - - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - - BakedLandscapeProxy->Import( - currentGUID, - 0, 0, XSize-1, YSize-1, - InLandscapeInfo->ComponentNumSubsections, InLandscapeInfo->SubsectionSizeQuads, - HeightmapDataPerLayers, NULL, - MaterialLayerDataPerLayer, ImportLayerType); - - BakedLandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); - - - if (BakedLandscapeProxy->LandscapeMaterial) - BakedLandscapeProxy->LandscapeMaterial = InLandscapeProxy->LandscapeMaterial; - - if (BakedLandscapeProxy->LandscapeHoleMaterial) - BakedLandscapeProxy->LandscapeHoleMaterial = InLandscapeProxy->LandscapeHoleMaterial; - - // 6. Register all the landscape components, and set landscape actor transform - BakedLandscapeProxy->RegisterAllComponents(); - BakedLandscapeProxy->SetActorTransform(InLandscapeProxy->GetTransform()); - - // 7. Save Package - TArray PackagesToSave; - PackagesToSave.Add(CreatedPackage); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor) - { - TArray Objects; - Objects.Add(NewWorld); - GEditor->SyncBrowserToObjects(Objects); - } - } - break; - } - - return InLandscapeProxy; -} - -bool -FHoudiniEngineBakeUtils::BakeCurve( - USplineComponent* InSplineComponent, - ULevel* InLevel, - const FHoudiniPackageParams &PackageParams, - AActor*& OutActor, - USplineComponent*& OutSplineComponent, - FName InOverrideFolderPath, - AActor* InActor) -{ - if (!IsValid(InActor)) - { - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; - if (!Factory) - return false; - - OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform(), RF_Transactional); - } - else - { - OutActor = InActor; - } - - // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FName BaseActorName(PackageParams.ObjectName); - const FName NewName = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName, OutActor); - const FString NewNameStr = NewName.ToString(); - // OutActor->Rename(*NewNameStr); - // OutActor->SetActorLabel(NewNameStr); - RenameAndRelabelActor(OutActor, NewNameStr, false); - OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); - - USplineComponent* DuplicatedSplineComponent = DuplicateObject( - InSplineComponent, - OutActor, - MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), FName(PackageParams.ObjectName))); - OutActor->AddInstanceComponent(DuplicatedSplineComponent); - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); - DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - - FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent); - DuplicatedSplineComponent->RegisterComponent(); - - OutSplineComponent = DuplicatedSplineComponent; - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeCurve( - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - // const TArray& InAllBakedOutputs, - const FHoudiniPackageParams &PackageParams, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - USplineComponent* SplineComponent = Cast(InOutputObject.OutputComponent); - if (!IsValid(SplineComponent)) - return false; - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; - - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a new level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if(!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - // If we are baking in replace mode, remove the previous bake component - if (bInReplaceAssets && !InBakedOutputObject.BakedComponent.IsEmpty()) - { - UActorComponent* PrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (PrevComponent && PrevComponent->GetOwner() == FoundActor) - { - RemovePreviouslyBakedComponent(PrevComponent); - } - } - - FHoudiniPackageParams CurvePackageParams = PackageParams; - CurvePackageParams.ObjectName = BakeActorName.ToString(); - USplineComponent* NewSplineComponent = nullptr; - const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName)); - if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor)) - return false; - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - InBakedOutputObject.BakedComponent = FSoftObjectPath(NewSplineComponent).ToString(); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - FHoudiniEngineBakedActor Result; - Result.Actor = FoundActor; - Result.ActorBakeName = BakeActorName; - OutActors.Add(Result); - - return true; -} - -AActor* -FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PackageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform) -{ - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) - return nullptr; - - TArray & DisplayPoints = InHoudiniSplineComponent->DisplayPoints; - if (DisplayPoints.Num() < 2) - return nullptr; - - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; - if (!Factory) - return nullptr; - - // Remove the actor if it exists - for (auto & Actor : DesiredLevel->Actors) - { - if (!Actor) - continue; - - if (Actor->GetName() == PackageParams.ObjectName) - { - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - Actor->RemoveFromRoot(); - Actor->ConditionalBeginDestroy(); - World->EditorDestroyActor(Actor, true); - - break; - } - } - - AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform(), RF_Transactional); - - USplineComponent* BakedUnrealSplineComponent = NewObject(NewActor); - if (!BakedUnrealSplineComponent) - return nullptr; - - // add display points to created unreal spline component - for (int32 n = 0; n < DisplayPoints.Num(); ++n) - { - FVector & NextPoint = DisplayPoints[n]; - BakedUnrealSplineComponent->AddSplinePoint(NextPoint, ESplineCoordinateSpace::Local); - // Set the curve point type to be linear, since we are using display points - BakedUnrealSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - NewActor->AddInstanceComponent(BakedUnrealSplineComponent); - - BakedUnrealSplineComponent->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); - - FAssetRegistryModule::AssetCreated(NewActor); - FAssetRegistryModule::AssetCreated(BakedUnrealSplineComponent); - BakedUnrealSplineComponent->RegisterComponent(); - - // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); - const FString NewNameStr = NewName.ToString(); - // NewActor->Rename(*NewNameStr); - // NewActor->SetActorLabel(NewNameStr); - RenameAndRelabelActor(NewActor, NewNameStr, false); - NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName)); - - return NewActor; -} - -UBlueprint* -FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PackageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform) -{ - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) - return nullptr; - - FGuid BakeGUID = FGuid::NewGuid(); - - if (!BakeGUID.IsValid()) - BakeGUID = FGuid::NewGuid(); - - // We only want half of generated guid string. - FString BakeGUIDString = BakeGUID.ToString().Left(FHoudiniEngineUtils::PackageGUIDItemNameLength); - - // Generate Blueprint name. - FString BlueprintName = PackageParams.ObjectName + "_BP"; - - // Generate unique package name. - FString PackageName = PackageParams.BakeFolder + "/" + BlueprintName; - PackageName = UPackageTools::SanitizePackageName(PackageName); - - // See if package exists, if it does, we need to regenerate the name. - UPackage * Package = FindPackage(nullptr, *PackageName); - - if (Package && !Package->IsPendingKill()) - { - // Package does exist, there's a collision, we need to generate a new name. - BakeGUID.Invalidate(); - } - else - { - // Create actual package. - Package = CreatePackage(nullptr, *PackageName); - } - - AActor * CreatedHoudiniSplineActor = FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - InHoudiniSplineComponent, PackageParams, WorldToSpawn, SpawnTransform); - - TArray PackagesToSave; - - UBlueprint * Blueprint = nullptr; - if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill()) - { - - UObject* Asset = nullptr; - - Asset = StaticFindObjectFast(UObject::StaticClass(), Package, FName(*BlueprintName)); - if (!Asset) - { - UBlueprintFactory* Factory = NewObject(); - - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - Asset = AssetToolsModule.Get().CreateAsset( - BlueprintName, PackageParams.BakeFolder, - UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - } - - TArray Components; - for (auto & Next : CreatedHoudiniSplineActor->GetComponents()) - { - Components.Add(Next); - } - - Blueprint = Cast(Asset); - - // Clear old Blueprint Node tree - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - - int32 NodeSize = SCS->GetAllNodes().Num(); - for (int32 n = NodeSize - 1; n >= 0; --n) - SCS->RemoveNode(SCS->GetAllNodes()[n]); - - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components); - - CreatedHoudiniSplineActor->RemoveFromRoot(); - CreatedHoudiniSplineActor->ConditionalBeginDestroy(); - - GWorld->EditorDestroyActor(CreatedHoudiniSplineActor, true); - - Package->MarkPackageDirty(); - PackagesToSave.Add(Package); - } - - // Save the created BP package. - FHoudiniEngineBakeUtils::SaveBakedPackages - (PackagesToSave); - - return Blueprint; -} - - -void -FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const TCHAR * Key, - const TCHAR * Value) -{ - if (!Package || Package->IsPendingKill()) - return; - - UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) - MetaData->SetValue(Object, Key, Value); -} - - -bool -FHoudiniEngineBakeUtils:: -GetHoudiniGeneratedNameFromMetaInformation( - UPackage * Package, UObject * Object, FString & HoudiniName) -{ - if (!Package || Package->IsPendingKill()) - return false; - - UMetaData * MetaData = Package->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return false; - - if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - { - // Retrieve name used for package generation. - const FString NameFull = MetaData->GetValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME); - - //HoudiniName = NameFull.Left(FMath::Min(NameFull.Len(), FHoudiniEngineUtils::PackageGUIDItemNameLength)); - HoudiniName = NameFull; - return true; - } - - return false; -} - -UMaterial * -FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - UMaterial * Material, UMaterial* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutGeneratedPackages) -{ - UMaterial * DuplicatedMaterial = nullptr; - - FString CreatedMaterialName; - // Create material package. Use the same package params as static mesh, but with the material's name - FHoudiniPackageParams MaterialPackageParams = ObjectPackageParams; - MaterialPackageParams.ObjectName = MaterialName; - - // Check if there is a valid previous material. If so, get the bake counter for consistency in - // replace or iterative package naming - bool bIsPreviousBakeMaterialValid = IsValid(PreviousBakeMaterial); - int32 BakeCounter = 0; - TArray PreviousBakeMaterialExpressions; - if (bIsPreviousBakeMaterialValid) - { - bIsPreviousBakeMaterialValid = MaterialPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeMaterial); - if (bIsPreviousBakeMaterialValid) - { - MaterialPackageParams.GetBakeCounterFromBakedAsset(PreviousBakeMaterial, BakeCounter); - PreviousBakeMaterialExpressions = PreviousBakeMaterial->Expressions; - } - } - - UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter); - - if (!MaterialPackage || MaterialPackage->IsPendingKill()) - return nullptr; - - // Clone material. - DuplicatedMaterial = DuplicateObject< UMaterial >(Material, MaterialPackage, *CreatedMaterialName); - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, DuplicatedMaterial, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, DuplicatedMaterial, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedMaterialName); - - // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. - const int32 NumExpressions = DuplicatedMaterial->Expressions.Num(); - for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx) - { - UMaterialExpression* Expression = DuplicatedMaterial->Expressions[ExpressionIdx]; - UMaterialExpression* PreviousBakeExpression = nullptr; - if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx)) - { - PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; - } - FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( - Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages); - } - - // Notify registry that we have created a new duplicate material. - FAssetRegistryModule::AssetCreated(DuplicatedMaterial); - - // Dirty the material package. - DuplicatedMaterial->MarkPackageDirty(); - - // Recompile the baked material - // DuplicatedMaterial->ForceRecompileForRendering(); - // Use UMaterialEditingLibrary::RecompileMaterial since it correctly updates texture references in the material - // which ForceRecompileForRendering does not do - UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterial); - - OutGeneratedPackages.Add(MaterialPackage); - - return DuplicatedMaterial; -} - -void -FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( - UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, - const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages) -{ - UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression); - if (!TextureSample || TextureSample->IsPendingKill()) - return; - - UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture); - if (!Texture || Texture->IsPendingKill()) - return; - - UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) - return; - - // Try to get the previous bake's texture - UTexture2D* PreviousBakeTexture = nullptr; - if (IsValid(PreviousBakeMaterialExpression)) - { - UMaterialExpressionTextureSample* PreviousBakeTextureSample = Cast< UMaterialExpressionTextureSample >(PreviousBakeMaterialExpression); - if (IsValid(PreviousBakeTextureSample)) - PreviousBakeTexture = Cast< UTexture2D >(PreviousBakeTextureSample->Texture); - } - - FString GeneratedTextureName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( - TexturePackage, Texture, GeneratedTextureName)) - { - // Duplicate texture. - UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( - Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages); - - // Re-assign generated texture. - TextureSample->Texture = DuplicatedTexture; - } -} - -UTexture2D * -FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( - UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages) -{ - UTexture2D* DuplicatedTexture = nullptr; -#if WITH_EDITOR - // Retrieve original package of this texture. - UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) - return nullptr; - - FString GeneratedTextureName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName)) - { - UMetaData * MetaData = TexturePackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return nullptr; - - // Retrieve texture type. - const FString & TextureType = - MetaData->GetValue(Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); - - FString CreatedTextureName; - - // Create texture package. Use the same package params as material's, but with object name appended by generated texture's name - FHoudiniPackageParams TexturePackageParams = PackageParams; - TexturePackageParams.ObjectName = TexturePackageParams.ObjectName + "_" + GeneratedTextureName; - - // Determine the bake counter of the previous bake's texture (if exists/valid) for naming consistency when - // replacing/iterating - bool bIsPreviousBakeTextureValid = IsValid(PreviousBakeTexture); - int32 BakeCounter = 0; - if (bIsPreviousBakeTextureValid) - { - bIsPreviousBakeTextureValid = TexturePackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeTexture); - if (bIsPreviousBakeTextureValid) - { - TexturePackageParams.GetBakeCounterFromBakedAsset(PreviousBakeTexture, BakeCounter); - } - } - - UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter); - - if (!NewTexturePackage || NewTexturePackage->IsPendingKill()) - return nullptr; - - // Clone texture. - DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName); - if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedTextureName); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); - - // Notify registry that we have created a new duplicate texture. - FAssetRegistryModule::AssetCreated(DuplicatedTexture); - - // Dirty the texture package. - DuplicatedTexture->MarkPackageDirty(); - - OutCreatedPackages.Add(NewTexturePackage); - } -#endif - return DuplicatedTexture; -} - - -bool -FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); - - if (!ActorOwner || ActorOwner->IsPendingKill()) - return false; - - UWorld* World = ActorOwner->GetWorld(); - if (!World) - World = GWorld; - - World->EditorDestroyActor(ActorOwner, false); - - return true; -} - - -void -FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld) -{ - UWorld * CurrentWorld = nullptr; - if (bSaveCurrentWorld && GEditor) - CurrentWorld = GEditor->GetEditorWorldContext().World(); - - if (CurrentWorld) - { - // Save the current map - FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); - UPackage* CurrentWorldPackage = CreatePackage(nullptr, *CurrentWorldPath); - - if (CurrentWorldPackage) - { - CurrentWorldPackage->MarkPackageDirty(); - PackagesToSave.Add(CurrentWorldPackage); - } - } - - FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); -} - -bool -FHoudiniEngineBakeUtils::FindOutputObject( - const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) -{ - if (!InObjectToFind || InObjectToFind->IsPendingKill()) - return false; - - const int32 NumOutputs = InOutputs.Num(); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - const UHoudiniOutput* CurOutput = InOutputs[OutputIdx]; - if (!IsValid(CurOutput)) - continue; - - for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) - { - if (CurOutputObject.Value.OutputObject == InObjectToFind - || CurOutputObject.Value.OutputComponent == InObjectToFind - || CurOutputObject.Value.ProxyObject == InObjectToFind - || CurOutputObject.Value.ProxyComponent == InObjectToFind) - { - OutOutputIndex = OutputIdx; - OutIdentifier = CurOutputObject.Key; - return true; - } - } - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - FString TempPath = FString(); - - // TODO: Get the HAC outputs in a better way? - TArray Outputs; - if (InHAC && !InHAC->IsPendingKill()) - { - const int32 NumOutputs = InHAC->GetNumOutputs(); - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - Outputs.Add(InHAC->GetOutputAt(OutputIdx)); - } - - TempPath = InHAC->TemporaryCookFolder.Path; - } - - return IsObjectTemporary(InObject, Outputs, TempPath); -} - -bool FHoudiniEngineBakeUtils::IsObjectTemporary( - UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - int32 ParentOutputIndex = -1; - FHoudiniOutputObjectIdentifier Identifier; - if (FindOutputObject(InObject, InParentOutputs, ParentOutputIndex, Identifier)) - return true; - - // Check the package path for this object - // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated - UPackage* ObjectPackage = InObject->GetOutermost(); - if (ObjectPackage && !ObjectPackage->IsPendingKill()) - { - const FString PathName = ObjectPackage->GetPathName(); - if (PathName.StartsWith(InTemporaryCookFolder)) - return true; - - // Also check the default temp folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder)) - return true; - - /* - // TODO: this just indicates that the object was generated by H - // it could as well have been baked before... - // we should probably add a "temp" metadata - // Look in the meta info as well?? - UMetaData * MetaData = ObjectPackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return false; - - if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - return true; - */ - } - - return false; -} - -void -FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC) -{ - if (!NewSMC || NewSMC->IsPendingKill()) - return; - - if (!InSMC || InSMC->IsPendingKill()) - return; - - // Copy properties to new actor - //UStaticMeshComponent* OtherSMC_NonConst = const_cast(StaticMeshComponent); - NewSMC->SetCollisionProfileName(InSMC->GetCollisionProfileName()); - NewSMC->SetCollisionEnabled(InSMC->GetCollisionEnabled()); - NewSMC->LightmassSettings = InSMC->LightmassSettings; - NewSMC->CastShadow = InSMC->CastShadow; - NewSMC->SetMobility(InSMC->Mobility); - - UBodySetup* InBodySetup = InSMC->GetBodySetup(); - UBodySetup* NewBodySetup = NewSMC->GetBodySetup(); - if (InBodySetup && NewBodySetup) - { - // Copy the BodySetup - NewBodySetup->CopyBodyPropertiesFrom(InBodySetup); - - // We need to recreate the physics mesh for the new body setup - NewBodySetup->InvalidatePhysicsData(); - NewBodySetup->CreatePhysicsMeshes(); - - // Only copy the physical material if it's different from the default one, - // As this was causing crashes on BakeToActors in some cases - if (GEngine != NULL && NewBodySetup->GetPhysMaterial() != GEngine->DefaultPhysMaterial) - NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial()); - } - - if (NewActor && !NewActor->IsPendingKill()) - NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame); - - NewSMC->SetVisibility(InSMC->IsVisible()); - - // TODO: - // // Reapply the uproperties modified by attributes on the new component - // FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO); - - // The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another - UClass* ComponentClass = InSMC->GetClass(); - if (ComponentClass != NewSMC->GetClass()) - { - HOUDINI_LOG_WARNING( - TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), - *(ComponentClass->GetName()), - *(NewSMC->GetClass()->GetName())); - - NewSMC->PostEditChange(); - return; - } - - TSet SourceUCSModifiedProperties; - InSMC->GetUCSModifiedProperties(SourceUCSModifiedProperties); - - AActor* SourceActor = InSMC->GetOwner(); - if (!IsValid(SourceActor)) - { - NewSMC->PostEditChange(); - return; - } - - TArray ModifiedObjects; - const EditorUtilities::FCopyOptions Options(EditorUtilities::ECopyOptions::CallPostEditChangeProperty); - // Copy component properties - for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) - { - const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); - const bool bIsIdentical = Property->Identical_InContainer( InSMC, NewSMC ); - const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); - const bool bIsTransform = - Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); - - // auto SourceComponentIsRoot = [&]() - // { - // USceneComponent* RootComponent = SourceActor->GetRootComponent(); - // if (InSMC == RootComponent) - // { - // return true; - // } - // return false; - // }; - - // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - // && ( !bIsTransform || !SourceComponentIsRoot() ) ) - if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - && !bIsTransform ) - { - // const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) - // && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); - const bool bIsSafeToCopy = true; - if( bIsSafeToCopy ) - { - if (!Options.CanCopyProperty(*Property, *SourceActor)) - { - continue; - } - - if( !ModifiedObjects.Contains(NewSMC) ) - { - NewSMC->SetFlags(RF_Transactional); - NewSMC->Modify(); - ModifiedObjects.Add(NewSMC); - } - - if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) - { - // @todo simulate: Should we be calling this on the component instead? - NewActor->PreEditChange( Property ); - } - - EditorUtilities::CopySingleProperty(InSMC, NewSMC, Property); - - if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) - { - FPropertyChangedEvent PropertyChangedEvent( Property ); - NewActor->PostEditChangeProperty( PropertyChangedEvent ); - } - } - } - } - - NewSMC->PostEditChange(); -}; - -bool -FHoudiniEngineBakeUtils::RemovePreviouslyBakedActor( - AActor* InNewBakedActor, - ULevel* InLevel, - const FHoudiniPackageParams& InPackageParams) -{ - // Remove a previous bake actor if it exists - for (auto & Actor : InLevel->Actors) - { - if (!Actor) - continue; - - if (Actor != InNewBakedActor && Actor->GetName() == InPackageParams.ObjectName) - { - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - Actor->RemoveFromRoot(); - Actor->ConditionalBeginDestroy(); - World->EditorDestroyActor(Actor, true); - - return true; - } - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::RemovePreviouslyBakedComponent(UActorComponent* InComponent) -{ - if (!IsValid(InComponent)) - return false; - - // Remove from its actor first - if (InComponent->GetOwner()) - InComponent->GetOwner()->RemoveOwnedComponent(InComponent); - - // Detach from its parent component if attached - USceneComponent* SceneComponent = Cast(InComponent); - if (IsValid(SceneComponent)) - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - InComponent->UnregisterComponent(); - InComponent->DestroyComponent(); - - return true; -} - -FName -FHoudiniEngineBakeUtils::GetOutputFolderPath(UObject* InOutputOwner) -{ - // Get an output folder path for PDG outputs generated by InOutputOwner. - // The folder path is: / - FString FolderName; - FName FolderDirName; - AActor* OuterActor = Cast(InOutputOwner); - if (OuterActor) - { - FolderName = OuterActor->GetActorLabel(); - FolderDirName = OuterActor->GetFolderPath(); - } - else - { - FolderName = InOutputOwner->GetName(); - } - if (!FolderDirName.IsNone()) - return FName(FString::Printf(TEXT("%s/%s"), *FolderDirName.ToString(), *FolderName)); - else - return FName(FolderName); -} - -void -FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique) -{ - FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); - - const FSoftObjectPath OldPath = FSoftObjectPath(InAsset); - - FString NewName; - if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetOutermost()/*GetPackage()*/, InAsset->GetClass(), FName(InNewName), InAsset).ToString(); - else - NewName = InNewName; - - InAsset->Rename(*NewName); - - const FSoftObjectPath NewPath = FSoftObjectPath(InAsset); - if (OldPath != NewPath) - { - TArray RenameData; - RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); - AssetToolsModule.Get().RenameAssets(RenameData); - } -} - -void -FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique) -{ - if (!IsValid(InActor)) - return; - - FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); - - const FSoftObjectPath OldPath = FSoftObjectPath(InActor); - - FString NewName; - if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName), InActor).ToString(); - else - NewName = InNewName; - - InActor->Rename(*NewName); - InActor->SetActorLabel(NewName); - - const FSoftObjectPath NewPath = FSoftObjectPath(InActor); - if (OldPath != NewPath) - { - TArray RenameData; - RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); - AssetToolsModule.Get().RenameAssets(RenameData); - } -} - -bool -FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor( - AActor* InActor, - const FString& InNewName, - const FName& InFolderPath) -{ - if (!IsValid(InActor)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: InActor is null.")); - return false; - } - - if (InNewName.TrimStartAndEnd().IsEmpty()) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: A valid actor name must be specified.")); - return false; - } - - // Detach from parent - InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - // Rename - // InActor->Rename(*MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName)).ToString()); - // InActor->SetActorLabel(InNewName); - const bool bMakeUniqueIfNotUnique = true; - RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique); - - InActor->SetFolderPath(InFolderPath); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultIndex, - int32 InWorkResultObjectIndex, - bool bInReplaceActors, - bool bInReplaceAssets, - bool bInBakeToWorkResultActor, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!IsValid(InPDGAssetLink)) - return false; - - if (!IsValid(InNode)) - return false; - - if (!InNode->WorkResult.IsValidIndex(InWorkResultIndex)) - return false; - - FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultIndex]; - if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectIndex)) - return false; - - FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectIndex]; - TArray& Outputs = WorkResultObject.GetResultOutputs(); - if (Outputs.Num() == 0) - return true; - - AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor(); - if (!IsValid(WorkResultObjectActor)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObjectActor (%s) is null (unexpected since # Outputs > 0)"), *WorkResultObject.Name); - return false; - } - - // BakedActorsForWorkResultObject contains each actor that contains baked PDG results. Actors may - // appear in the array more than once if they have more than one baked result/component associated with - // them - TArray BakedActorsForWorkResultObject; - const FString HoudiniAssetName(WorkResultObject.Name); - - // Find the previous bake output for this work result object - FString Key; - InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key); - FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key); - - BakeHoudiniOutputsToActors( - Outputs, - BakedOutputContainer.BakedOutputs, - HoudiniAssetName, - WorkResultObjectActor->GetActorTransform(), - InPDGAssetLink->BakeFolder, - InPDGAssetLink->GetTemporaryCookFolder(), - bInReplaceActors, - bInReplaceAssets, - BakedActorsForWorkResultObject, - OutPackagesToSave, - OutBakeStats, - InOutputTypesToBake, - InInstancerComponentTypesToBake, - bInBakeToWorkResultActor ? WorkResultObjectActor : nullptr, - InFallbackWorldOutlinerFolder); - - // Set the PDG indices on the output baked actor entries - if (BakedActorsForWorkResultObject.Num() > 0) - { - for (FHoudiniEngineBakedActor& BakedActorEntry : BakedActorsForWorkResultObject) - { - BakedActorEntry.PDGWorkResultIndex = InWorkResultIndex; - BakedActorEntry.PDGWorkResultObjectIndex = InWorkResultObjectIndex; - } - } - - // If anything was baked to WorkResultObjectActor, detach it from its parent - if (bInBakeToWorkResultActor) - { - FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); - // if we re-used the temp actor as a bake actor, then remove its temp outputs - WorkResultObject.DestroyResultOutputs(); - AActor* WROActor = OutputActorOwner.GetOutputActor(); - if (WROActor) - { - const FHoudiniEngineBakedActor* BakedActorEntry = BakedActorsForWorkResultObject.FindByPredicate([WROActor](const FHoudiniEngineBakedActor& Entry) - { - return Entry.Actor == WROActor; - }); - if (BakedActorEntry) - { - OutputActorOwner.SetOutputActor(nullptr); - const FString OldActorPath = FSoftObjectPath(WROActor).ToString(); - DetachAndRenameBakedPDGOutputActor( - WROActor, BakedActorEntry->ActorBakeName.ToString(), BakedActorEntry->WorldOutlinerFolder); - const FString NewActorPath = FSoftObjectPath(WROActor).ToString(); - if (OldActorPath != NewActorPath) - { - // Fix cached string reference in baked outputs to WROActor - for (FHoudiniBakedOutput& BakedOutput : BakedOutputContainer.BakedOutputs) - { - for (auto& Entry : BakedOutput.BakedOutputObjects) - { - if (Entry.Value.Actor == OldActorPath) - Entry.Value.Actor = NewActorPath; - } - } - } - } - else - { - OutputActorOwner.DestroyOutputActor(); - } - } - } - OutBakedActors.Append(BakedActorsForWorkResultObject); - return true; -} - - -bool -FHoudiniEngineBakeUtils::BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName) -{ - if (!IsValid(InPDGAssetLink)) - return false; - - if (!IsValid(InNode)) - return false; - - // Find the work result index and work result object index - const int32 WorkResultIndex = InNode->WorkResult.IndexOfByPredicate([InWorkResultId](const FTOPWorkResult& Entry) - { - return Entry.WorkItemID == InWorkResultId; - }); - if (!InNode->WorkResult.IsValidIndex(WorkResultIndex)) - return false; - FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIndex]; - const int32 WorkResultObjectIndex = WorkResult.ResultObjects.IndexOfByPredicate([InWorkResultObjectName](const FTOPWorkResultObject& Entry) - { - return Entry.Name.Equals(InWorkResultObjectName); - }); - if (!WorkResult.ResultObjects.IsValidIndex(WorkResultObjectIndex)) - return false; - - // Determine the output world outliner folder path via the PDG asset link's - // owner's folder path and name - UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); - if (!PDGOwner) - PDGOwner = InPDGAssetLink->GetOuter(); - const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); - - // Determine the actor/package replacement settings - const bool bBakeBlueprints = InPDGAssetLink->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToBlueprint; - const bool bReplaceActors = !bBakeBlueprints && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Determine the output types to bake: don't bake landscapes in blueprint baking mode - TArray OutputTypesToBake; - TArray InstancerComponentTypesToBake; - if (bBakeBlueprints) - { - OutputTypesToBake.Add(EHoudiniOutputType::Mesh); - OutputTypesToBake.Add(EHoudiniOutputType::Instancer); - OutputTypesToBake.Add(EHoudiniOutputType::Curve); - - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); - } - - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - TArray BakedActors; - - bool bSuccess = BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - WorkResultIndex, - WorkResultObjectIndex, - bReplaceActors, - bReplaceAssets, - !bBakeBlueprints, - BakedActors, - PackagesToSave, - BakeStats, - OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, - InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, - FallbackWorldOutlinerFolderPath.ToString() - ); - - // Recenter and select the baked actors - if (GEditor && BakedActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : BakedActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (InPDGAssetLink->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && BakedActors.Num() > 0) - GEditor->NoteSelectionChange(); - - if (bBakeBlueprints && bSuccess) - { - TArray Blueprints; - bSuccess = BakeBlueprintsFromBakedActors( - BakedActors, - InPDGAssetLink->bRecenterBakedActors, - bReplaceAssets, - InPDGAssetLink->AssetName, - InPDGAssetLink->BakeFolder, - nullptr, - &InNode->GetBakedWorkResultObjectsOutputs(), - Blueprints, - PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - } - - SaveBakedPackages(PackagesToSave); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return bSuccess; -} - -void -FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName) -{ - if (!IsValid(InPDGAssetLink)) - return; - - if (!InPDGAssetLink->bBakeAfterWorkResultObjectLoaded) - return; - - BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - InWorkResultId, - InWorkResultObjectName); -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInBakeForBlueprint, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNode)) - return false; - - // Determine the output world outliner folder path via the PDG asset link's - // owner's folder path and name - UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); - if (!PDGOwner) - PDGOwner = InPDGAssetLink->GetOuter(); - const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); - - // Determine the actor/package replacement settings - const bool bReplaceActors = !bInBakeForBlueprint && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Determine the output types to bake: don't bake landscapes in blueprint baking mode - TArray OutputTypesToBake; - TArray InstancerComponentTypesToBake; - if (bInBakeForBlueprint) - { - OutputTypesToBake.Add(EHoudiniOutputType::Mesh); - OutputTypesToBake.Add(EHoudiniOutputType::Instancer); - OutputTypesToBake.Add(EHoudiniOutputType::Curve); - - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); - } - - const int32 NumWorkResults = InNode->WorkResult.Num(); - for (int32 WorkResultIdx = 0; WorkResultIdx < NumWorkResults; ++WorkResultIdx) - { - FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIdx]; - const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); - for (int32 WorkResultObjectIdx = 0; WorkResultObjectIdx < NumWorkResultObjects; ++WorkResultObjectIdx) - { - BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - WorkResultIdx, - WorkResultObjectIdx, - bReplaceActors, - bReplaceAssets, - !bInBakeForBlueprint, - OutBakedActors, - OutPackagesToSave, - OutBakeStats, - OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, - InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, - FallbackWorldOutlinerFolderPath.ToString() - ); - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - bool bInBakeForBlueprint, - TArray& BakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNetwork)) - return false; - - bool bSuccess = true; - for (UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, BakedActors, OutPackagesToSave, OutBakeStats); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - TArray BakedActors; - - const bool bBakeBlueprints = false; - - bool bSuccess = true; - switch(InPDGAssetLink->PDGBakeSelectionOption) - { - case EPDGBakeSelectionOption::All: - for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) - { - if (!IsValid(Network)) - continue; - - for (UTOPNode* Node : Network->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); - } - } - break; - case EPDGBakeSelectionOption::SelectedNetwork: - bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); - case EPDGBakeSelectionOption::SelectedNode: - bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); - } - - SaveBakedPackages(PackagesToSave); - - // Recenter and select the baked actors - if (GEditor && BakedActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : BakedActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (InPDGAssetLink->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && BakedActors.Num() > 0) - GEditor->NoteSelectionChange(); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( - const TArray& InBakedActors, - bool bInRecenterBakedActors, - bool bInReplaceAssets, - const FString& InAssetName, - const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOuputs, - TMap* const InPDGBakedOutputs, - TArray& OutBlueprints, - TArray& OutPackagesToSave) -{ - // // Clear selection - // if (GEditor) - // { - // GEditor->SelectNone(false, true); - // GEditor->NoteSelectionChange(); - // } - - // Iterate over the baked actors. An actor might appear multiple times if multiple OutputComponents were - // baked to the same actor, so keep track of actors we have already processed in BakedActorSet - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem); - TArray AssetsToReOpenEditors; - TSet BakedActorSet; - - for (const FHoudiniEngineBakedActor& Entry : InBakedActors) - { - AActor *Actor = Entry.Actor; - - if (!Actor || Actor->IsPendingKill()) - continue; - - if (BakedActorSet.Contains(Actor)) - continue; - - BakedActorSet.Add(Actor); - - UObject* Asset = nullptr; - - // Recenter the actor to its bounding box center - if (bInRecenterBakedActors) - CenterActorToBoundingBoxCenter(Actor); - - // Create package for out Blueprint - FString BlueprintName; - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - FHoudiniOutputObjectIdentifier(), - InBakeFolder.Path, - Entry.ActorBakeName.ToString() + "_BP", - InAssetName, - AssetPackageReplaceMode); - - // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment - // is consistent with the bake counter - int32 BakeCounter = 0; - UBlueprint* InPreviousBlueprint = nullptr; - FHoudiniBakedOutputObject* BakedOutputObject = nullptr; - FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; - // Get the baked output object - if (Entry.PDGWorkResultIndex >= 0 && Entry.PDGWorkResultObjectIndex >= 0 && InPDGBakedOutputs) - { - const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkResultIndex, Entry.PDGWorkResultObjectIndex); - WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); - if (WorkResultObjectBakedOutput) - { - if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } - } - else if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs) - { - if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs->IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = (*InNonPDGBakedOuputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } - if (BakedOutputObject) - { - InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); - if (IsValid(InPreviousBlueprint)) - { - if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint)) - { - PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter); - } - } - } - - UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter); - - if (!Package || Package->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName())); - continue; - } - - if (!Package->IsFullyLoaded()) - Package->FullyLoad(); - - //Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false); - // Find existing asset first first (only relevant if we are in replacement mode). If the existing asset has a - // different base class than the incoming actor, we reparent the blueprint to the new base class before - // clearing the SCS graph and repopulating it from the temp actor. - Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName)); - if (IsValid(Asset)) - { - UBlueprint* Blueprint = Cast(Asset); - if (IsValid(Blueprint)) - { - if (Blueprint->GeneratedClass && Blueprint->GeneratedClass != Actor->GetClass()) - { - // Close editors opened on existing asset if applicable - if (Asset && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Asset, false) != nullptr) - { - AssetEditorSubsystem->CloseAllEditorsForAsset(Asset); - AssetsToReOpenEditors.Add(Asset); - } - - Blueprint->ParentClass = Actor->GetClass(); - - FBlueprintEditorUtils::RefreshAllNodes(Blueprint); - FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); - FKismetEditorUtilities::CompileBlueprint(Blueprint); - } - } - } - else if (Asset && Asset->IsPendingKill()) - { - // Rename to pending kill so that we can use the desired name - const FString AssetPendingKillName(BlueprintName + "_PENDING_KILL"); - // Asset->Rename(*MakeUniqueObjectNameIfNeeded(Package, UBlueprint::StaticClass(), AssetPendingKillName).ToString()); - RenameAsset(Asset, AssetPendingKillName, true); - Asset = nullptr; - } - - if (!Asset) - { - UBlueprintFactory* Factory = NewObject(); - Factory->ParentClass = Actor->GetClass(); - - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - Asset = AssetToolsModule.Get().CreateAsset( - BlueprintName, InBakeFolder.Path, - UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - } - - UBlueprint* Blueprint = Cast(Asset); - - if (!Blueprint || Blueprint->IsPendingKill()) - { - HOUDINI_LOG_WARNING( - TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."), - *(InBakeFolder.Path), *BlueprintName); - - continue; - } - - // Close editors opened on existing asset if applicable - if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr) - { - AssetEditorSubsystem->CloseAllEditorsForAsset(Blueprint); - AssetsToReOpenEditors.Add(Blueprint); - } - - // Record the blueprint as the previous bake blueprint - if (BakedOutputObject) - BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString(); - - OutBlueprints.Add(Blueprint); - - // Clear old Blueprint Node tree - { - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - - int32 NodeSize = SCS->GetAllNodes().Num(); - for (int32 n = NodeSize - 1; n >= 0; --n) - SCS->RemoveNode(SCS->GetAllNodes()[n]); - } - - FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint); - - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - World->EditorDestroyActor(Actor, true); - - // Save the created BP package. - Package->MarkPackageDirty(); - OutPackagesToSave.Add(Package); - } - - // Re-open asset editors for updated blueprints that were open in editors - if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0) - { - for (UObject* Asset : AssetsToReOpenEditors) - { - if (IsValid(Asset)) - { - AssetEditorSubsystem->OpenEditorForAsset(Asset); - } - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - TArray BPActors; - - if (!IsValid(InPDGAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InPDGAssetLink is null")); - return false; - } - - if (!IsValid(InNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InNode is null")); - return false; - } - - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Bake PDG output to new actors - // bInBakeForBlueprint == true will skip landscapes and instanced actor components - const bool bInBakeForBlueprint = true; - TArray BakedActors; - bool bSuccess = BakePDGTOPNodeOutputsKeepActors( - InPDGAssetLink, - InNode, - bInBakeForBlueprint, - BakedActors, - OutPackagesToSave, - OutBakeStats - ); - - if (bSuccess) - { - bSuccess = BakeBlueprintsFromBakedActors( - BakedActors, - InPDGAssetLink->bRecenterBakedActors, - bReplaceAssets, - InPDGAssetLink->AssetName, - InPDGAssetLink->BakeFolder, - nullptr, - &InNode->GetBakedWorkResultObjectsOutputs(), - OutBlueprints, - OutPackagesToSave); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNetwork)) - return false; - - bool bSuccess = true; - for (UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, OutBlueprints, OutPackagesToSave, OutBakeStats); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - TArray Blueprints; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - bool bSuccess = true; - switch(InPDGAssetLink->PDGBakeSelectionOption) - { - case EPDGBakeSelectionOption::All: - for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) - { - if (!IsValid(Network)) - continue; - - for (UTOPNode* Node : Network->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, Blueprints, PackagesToSave, BakeStats); - } - } - break; - case EPDGBakeSelectionOption::SelectedNetwork: - bSuccess &= BakePDGTOPNetworkBlueprints( - InPDGAssetLink, - InPDGAssetLink->GetSelectedTOPNetwork(), - Blueprints, - PackagesToSave, - BakeStats); - case EPDGBakeSelectionOption::SelectedNode: - bSuccess &= BakePDGTOPNodeBlueprints( - InPDGAssetLink, - InPDGAssetLink->GetSelectedTOPNode(), - Blueprints, - PackagesToSave, - BakeStats); - } - - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - const FString& InLevelPath, - ULevel*& OutDesiredLevel, - UWorld*& OutDesiredWorld, - bool& OutCreatedPackage) -{ - OutDesiredLevel = nullptr; - OutDesiredWorld = nullptr; - if (InLevelPath.IsEmpty()) - { - OutDesiredWorld = GWorld; - OutDesiredLevel = GWorld->GetCurrentLevel(); - } - else - { - OutCreatedPackage = false; - - UWorld* FoundWorld = nullptr; - ULevel* FoundLevel = nullptr; - bool bActorInWorld = false; - if (FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - GWorld, - InLevelPath, - true, - FoundWorld, - FoundLevel, - OutCreatedPackage, - bActorInWorld)) - { - OutDesiredLevel = FoundLevel; - OutDesiredWorld = FoundWorld; - } - } - - return ((OutDesiredWorld != nullptr) && (OutDesiredLevel != nullptr)); -} - - -bool -FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( - const FString& InBakeActorName, - ULevel* InLevel, - AActor*& OutActor, - bool bInNoPendingKillActors, - bool bRenamePendingKillActor) -{ - OutActor = nullptr; - - if (!IsValid(InLevel)) - return false; - - UWorld* const World = InLevel->GetWorld(); - if (!IsValid(World)) - return false; - - // Look for an actor with the given name in the world - const FName BakeActorFName(InBakeActorName); - AActor* FoundActor = Cast(StaticFindObjectFast(AActor::StaticClass(), InLevel, BakeActorFName)); - // for (TActorIterator Iter(World, AActor::StaticClass(), EActorIteratorFlags::AllActors); Iter; ++Iter) - // { - // AActor* const Actor = *Iter; - // if (Actor->GetFName() == BakeActorFName && Actor->GetLevel() == InLevel) - // { - // // Found the actor - // FoundActor = Actor; - // break; - // } - // } - - // If we found an actor and it is pending kill, rename it and don't use it - if (FoundActor) - { - if (FoundActor->IsPendingKill()) - { - if (bRenamePendingKillActor) - { - // FoundActor->Rename( - // *MakeUniqueObjectNameIfNeeded( - // FoundActor->GetOuter(), - // FoundActor->GetClass(), - // FName(FoundActor->GetName() + "_Pending_Kill")).ToString()); - RenameAndRelabelActor( - FoundActor, - *MakeUniqueObjectNameIfNeeded( - FoundActor->GetOuter(), - FoundActor->GetClass(), - FName(FoundActor->GetName() + "_Pending_Kill"), - FoundActor).ToString(), - false); - } - if (bInNoPendingKillActors) - FoundActor = nullptr; - else - OutActor = FoundActor; - } - else - { - OutActor = FoundActor; - } - } - - return true; -} - -bool FHoudiniEngineBakeUtils::FindUnrealBakeActor( - const FHoudiniOutputObject& InOutputObject, - const FHoudiniBakedOutputObject& InBakedOutputObject, - const TArray& InAllBakedActors, - ULevel* InLevel, - FName InDefaultActorName, - bool bInReplaceActorBakeMode, - AActor* InFallbackActor, - AActor*& OutFoundActor, - bool& bOutHasBakeActorName, - FName& OutBakeActorName) -{ - // Determine desired actor name via unreal_output_actor, fallback to InDefaultActorName - OutBakeActorName = NAME_None; - OutFoundActor = nullptr; - bOutHasBakeActorName = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_ACTOR); - if (bOutHasBakeActorName) - { - const FString& BakeActorNameStr = InOutputObject.CachedAttributes[HAPI_UNREAL_ATTRIB_BAKE_ACTOR]; - if (BakeActorNameStr.IsEmpty()) - { - OutBakeActorName = NAME_None; - bOutHasBakeActorName = false; - } - else - { - OutBakeActorName = *BakeActorNameStr; - // We have a bake actor name, look for the actor - AActor* BakeNameActor = nullptr; - if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor)) - { - // Found an actor with that name, check that we "own" it (we created in during baking previously) - AActor* IncrementedBakedActor = nullptr; - for (const FHoudiniEngineBakedActor& BakedActor : InAllBakedActors) - { - if (!IsValid(BakedActor.Actor)) - continue; - if (BakedActor.Actor == BakeNameActor) - { - OutFoundActor = BakeNameActor; - break; - } - else if (!IncrementedBakedActor && BakedActor.ActorBakeName == OutBakeActorName) - { - // Found an actor we have baked named OutBakeActorName_# (incremental version of our desired name) - IncrementedBakedActor = BakedActor.Actor; - } - } - if (!OutFoundActor && IncrementedBakedActor) - OutFoundActor = IncrementedBakedActor; - } - } - } - - // If unreal_actor_name is not set, or is blank, fallback to InDefaultActorName - if (!bOutHasBakeActorName || (OutBakeActorName.IsNone() || OutBakeActorName.ToString().TrimStartAndEnd().IsEmpty())) - OutBakeActorName = InDefaultActorName; - - if (!OutFoundActor) - { - // If in replace mode, use previous bake actor if valid and in InLevel - if (bInReplaceActorBakeMode) - { - const FSoftObjectPath PrevActorPath(InBakedOutputObject.Actor); - const FString ActorPath = PrevActorPath.IsSubobject() - ? PrevActorPath.GetAssetPathString() + ":" + PrevActorPath.GetSubPathString() - : PrevActorPath.GetAssetPathString(); - const FString LevelPath = IsValid(InLevel) ? InLevel->GetPathName() : ""; - if (PrevActorPath.IsValid() && (LevelPath.IsEmpty() || ActorPath.StartsWith(LevelPath))) - OutFoundActor = InBakedOutputObject.GetActorIfValid(); - } - - // Fallback to InFallbackActor if valid and in InLevel - if (!OutFoundActor && IsValid(InFallbackActor) && (!InLevel || InFallbackActor->GetLevel() == InLevel)) - OutFoundActor = InFallbackActor; - } - - return true; -} - -AActor* -FHoudiniEngineBakeUtils::FindExistingActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - bCreatedPackage = false; - - // Try to Locate a previous actor - AActor* FoundActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); - if (FoundActor) - FoundActor->Destroy(); // nuke it! - - if (FoundActor) - { - // TODO: make sure that the found is actor is actually assigned to the level defined by package path. - // If the found actor is not from that level, it should be moved there. - - OutWorld = FoundActor->GetWorld(); - OutLevel = FoundActor->GetLevel(); - } - else - { - // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. - bool bActorInWorld = false; - const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - InWorld, - InPackagePath, - true, - OutWorld, - OutLevel, - bCreatedPackage, - bActorInWorld); - - if (!bResult) - { - return nullptr; - } - - if (!bActorInWorld) - { - // The OutLevel is not present in the current world which means we might - // still find the tile actor in OutWorld. - FoundActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); - } - } - - return FoundActor; -} - -bool -FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh( - UHoudiniAssetComponent* InHoudiniAssetComponent, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess, - bool& bOutNeedsReCook) -{ - if (!IsValid(InHoudiniAssetComponent)) - { - return false; - } - - // Handle proxies: if the output has any current proxies, first refine them - bOutNeedsReCook = false; - if (InHoudiniAssetComponent->HasAnyCurrentProxyOutput()) - { - bool bNeedsRebuildOrDelete; - bool bInvalidState; - const bool bCookedDataAvailable = InHoudiniAssetComponent->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bInvalidState); - - if (bCookedDataAvailable) - { - // Cook data is available, refine the mesh - AHoudiniAssetActor* HoudiniActor = Cast(InHoudiniAssetComponent->GetOwner()); - if (IsValid(HoudiniActor)) - { - FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ HoudiniActor }); - } - } - else if (!bNeedsRebuildOrDelete && !bInvalidState) - { - // A cook is needed: request the cook, but with no proxy and with a bake after cook - InHoudiniAssetComponent->SetNoProxyMeshNextCookRequested(true); - // Only - if (!InHoudiniAssetComponent->IsBakeAfterNextCookEnabled() || !InHoudiniAssetComponent->GetOnPostCookBakeDelegate().IsBound()) - { - InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess](UHoudiniAssetComponent* InHAC) { - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess); - }); - } - InHoudiniAssetComponent->MarkAsNeedCook(); - - bOutNeedsReCook = true; - - // The cook has to complete first (asynchronously) before the bake can happen - // The SetBakeAfterNextCookEnabled flag will result in a bake after cook - return false; - } - else - { - // The HAC is in an unsupported state - const EHoudiniAssetState AssetState = InHoudiniAssetComponent->GetAssetState(); - HOUDINI_LOG_ERROR(TEXT("Could not refine (in order to bake) %s, the asset is in an unsupported state: %s"), *(InHoudiniAssetComponent->GetPathName()), *(UEnum::GetValueAsString(AssetState))); - return false; - } - } - - return true; -} - -void -FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor) -{ - if (!IsValid(InActor)) - return; - - USceneComponent * const RootComponent = InActor->GetRootComponent(); - if (!IsValid(RootComponent)) - return; - - // If the root component does not have any child components, then there is nothing to recenter - if (RootComponent->GetNumChildrenComponents() <= 0) - return; - - const bool bOnlyCollidingComponents = false; - const bool bIncludeFromChildActors = true; - FVector Origin; - FVector BoxExtent; - InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); - - const FVector Delta = Origin - RootComponent->GetComponentLocation(); - // Actor->SetActorLocation(Origin); - RootComponent->SetWorldLocation(Origin); - - for (USceneComponent* SceneComponent : RootComponent->GetAttachChildren()) - { - if (!IsValid(SceneComponent)) - continue; - - SceneComponent->SetWorldLocation(SceneComponent->GetComponentLocation() - Delta); - } -} - -void -FHoudiniEngineBakeUtils::CenterActorsToBoundingBoxCenter(const TArray& InActors) -{ - for (AActor* Actor : InActors) - { - if (!IsValid(Actor)) - continue; - - CenterActorToBoundingBoxCenter(Actor); - } -} - -USceneComponent* -FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMissing, EComponentMobility::Type InMobilityIfCreated) -{ - USceneComponent* RootComponent = InActor->GetRootComponent(); - if (!IsValid(RootComponent)) - { - RootComponent = NewObject(InActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); - - // Change the creation method so the component is listed in the details panels - InActor->SetRootComponent(RootComponent); - InActor->AddInstanceComponent(RootComponent); - RootComponent->RegisterComponent(); - RootComponent->SetMobility(InMobilityIfCreated); - } - - return RootComponent; -} - -FName -FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed) -{ - if (IsValid(InObjectThatWouldBeRenamed)) - { - const FName CurrentName = InObjectThatWouldBeRenamed->GetFName(); - if (CurrentName == InName) - return InName; - - // Check if the prefix matches (without counter suffix) the new name - const FString CurrentNamePlainStr = CurrentName.GetPlainNameString(); - if (CurrentNamePlainStr == InName.ToString()) - return CurrentName; - } - - UObject* ExistingObject = nullptr; - if (InOuter == ANY_PACKAGE) - { - ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *InName.ToString()); - } - else - { - ExistingObject = StaticFindObjectFast(nullptr, InOuter, InName); - } - - if (ExistingObject) - return MakeUniqueObjectName(InOuter, InClass, InName); - return InName; -} - -FName -FHoudiniEngineBakeUtils::GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) -{ - const FString* FolderPathPtr = InOutputObject.CachedAttributes.Find(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER); - if (FolderPathPtr && !FolderPathPtr->IsEmpty()) - return FName(*FolderPathPtr); - else - return InDefaultFolder; -} - -bool -FHoudiniEngineBakeUtils::SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) -{ - if (!IsValid(InActor)) - return false; - - InActor->SetFolderPath(GetOutlinerFolderPath(InOutputObject, InDefaultFolder)); - return true; -} - -uint32 -FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInDestroyBakedComponent, - bool bInDestroyBakedInstancedActors, - bool bInDestroyBakedInstancedComponents) -{ - uint32 NumDeleted = 0; - - if (bInDestroyBakedComponent) - { - UActorComponent* Component = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (Component) - { - if (RemovePreviouslyBakedComponent(Component)) - { - InBakedOutputObject.BakedComponent = nullptr; - NumDeleted++; - } - } - } - - if (bInDestroyBakedInstancedActors) - { - for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) - { - const FSoftObjectPath ActorPath(ActorPathStr); - - if (!ActorPath.IsValid()) - continue; - - AActor* Actor = Cast(ActorPath.TryLoad()); - if (IsValid(Actor)) - { - UWorld* World = Actor->GetWorld(); - if (IsValid(World)) - { -#if WITH_EDITOR - World->EditorDestroyActor(Actor, true); -#else - World->DestroyActor(Actor); -#endif - NumDeleted++; - } - } - } - InBakedOutputObject.InstancedActors.Empty(); - } - - if (bInDestroyBakedInstancedComponents) - { - for (const FString& ComponentPathStr : InBakedOutputObject.InstancedComponents) - { - const FSoftObjectPath ComponentPath(ComponentPathStr); - - if (!ComponentPath.IsValid()) - continue; - - UActorComponent* Component = Cast(ComponentPath.TryLoad()); - if (IsValid(Component)) - { - if (RemovePreviouslyBakedComponent(Component)) - NumDeleted++; - } - } - InBakedOutputObject.InstancedComponents.Empty(); - } - - return NumDeleted; -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineBakeUtils.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngineUtils.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutput.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineUtils.h" +#include "UnrealLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniStringResolver.h" +#include "HoudiniEngineCommands.h" + +#include "Engine/StaticMesh.h" +#include "Engine/World.h" +#include "RawMesh.h" +#include "UObject/Package.h" +#include "PackageTools.h" +#include "UObject/MetaData.h" +#include "AssetRegistryModule.h" +#include "Materials/Material.h" +#include "LandscapeProxy.h" +#include "LandscapeStreamingProxy.h" +#include "LandscapeInfo.h" +#include "Factories/WorldFactory.h" +#include "AssetToolsModule.h" +#include "InstancedFoliageActor.h" +#include "Components/SplineComponent.h" +#include "GameFramework/Actor.h" +#include "Engine/StaticMeshActor.h" +#include "Components/StaticMeshComponent.h" +#include "PhysicsEngine/BodySetup.h" +#include "ActorFactories/ActorFactoryStaticMesh.h" +#include "ActorFactories/ActorFactoryEmptyActor.h" +#include "BusyCursor.h" +#include "Editor.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "FileHelpers.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngine.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "Editor/EditorEngine.h" +#include "Factories/BlueprintFactory.h" +#include "Engine/SimpleConstructionScript.h" +#include "Misc/Paths.h" +#include "HAL/FileManager.h" +#include "LandscapeEdit.h" +#include "Containers/UnrealString.h" +#include "Components/AudioComponent.h" +#include "Engine/WorldComposition.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "MaterialEditor/Public/MaterialEditingLibrary.h" +#include "MaterialGraph/MaterialGraph.h" +#include "Particles/ParticleSystemComponent.h" +#include "Sound/SoundBase.h" +#include "UObject/UnrealType.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() + : Actor(nullptr) + , OutputIndex(INDEX_NONE) + , OutputObjectIdentifier() + , ActorBakeName(NAME_None) + , BakedObject(nullptr) + , SourceObject(nullptr) +{ +} + +FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( + AActor* InActor, + FName InActorBakeName, + FName InWorldOutlinerFolder, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + UObject* InBakedObject, + UObject* InSourceObject) + : Actor(InActor) + , OutputIndex(InOutputIndex) + , OutputObjectIdentifier(InOutputObjectIdentifier) + , ActorBakeName(InActorBakeName) + , WorldOutlinerFolder(InWorldOutlinerFolder) + , BakedObject(InBakedObject) + , SourceObject(InSourceObject) +{ +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + UHoudiniAssetComponent* InHACToBake, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess) +{ + if (!IsValid(InHACToBake)) + return false; + + // Handle proxies: if the output has any current proxies, first refine them + bool bHACNeedsToReCook; + if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bHACNeedsToReCook)) + { + // Either the component is invalid, or needs a recook to refine a proxy mesh + return false; + } + + bool bSuccess = false; + switch (InBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake); + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake); + } + break; + + case EHoudiniEngineBakeOption::ToFoliage: + { + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake); + } + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + { + //Todo + bSuccess = false; + } + break; + + } + + if (bSuccess && bInRemoveHACOutputOnSuccess) + FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + TArray NewActors; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + if (!FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats)) + { + // TODO ? + HOUDINI_LOG_WARNING(TEXT("Errors when baking")); + } + + // Save the created packages + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && NewActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : NewActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (HoudiniAssetComponent->bRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && NewActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); + if (!IsValid(OwnerActor)) + return false; + + const FString HoudiniAssetName = OwnerActor->GetName(); + + // Get an array of the outputs + const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); + TArray Outputs; + Outputs.Reserve(NumOutputs); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + Outputs.Add(HoudiniAssetComponent->GetOutputAt(OutputIdx)); + } + + // Get the previous bake objects and grow/shrink to match asset outputs + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + // Ensure we have an entry for each output + if (BakedOutputs.Num() != NumOutputs) + BakedOutputs.SetNum(NumOutputs); + + return BakeHoudiniOutputsToActors( + Outputs, + BakedOutputs, + HoudiniAssetName, + HoudiniAssetComponent->GetComponentTransform(), + HoudiniAssetComponent->BakeFolder, + HoudiniAssetComponent->TemporaryCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutNewActors, + OutPackagesToSave, + OutBakeStats, + InOutputTypesToBake, + InInstancerComponentTypesToBake, + InFallbackActor, + InFallbackWorldOutlinerFolder); +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( + const TArray& InOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FTransform& InParentTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + const int32 NumOutputs = InOutputs.Num(); + + const FString MsgTemplate = TEXT("Baking output: {0}/{1}."); + FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs }); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg)); + + TArray BakedActors; + + // First bake everything except instancers, then bake instancers. Since instancers might use meshes in + // from the other outputs. + bool bHasAnyInstancers = false; + int32 NumProcessedOutputs = 0; + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + UHoudiniOutput* Output = InOutputs[OutputIdx]; + if (!Output || Output->IsPendingKill()) + { + NumProcessedOutputs++; + continue; + } + + Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); + + const EHoudiniOutputType OutputType = Output->GetType(); + // Check if we should skip this output type + if (InOutputTypesToBake && InOutputTypesToBake->Find(OutputType) == INDEX_NONE) + { + NumProcessedOutputs++; + continue; + } + + switch (OutputType) + { + case EHoudiniOutputType::Mesh: + { + FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + OutputIdx, + InOutputs, + InBakedOutputs, + InHoudiniAssetName, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + break; + + case EHoudiniOutputType::Instancer: + { + if (!bHasAnyInstancers) + bHasAnyInstancers = true; + NumProcessedOutputs--; + } + break; + + case EHoudiniOutputType::Landscape: + { + UHoudiniAssetComponent* HAC = Cast(Output->GetOuter()); + if (IsValid(HAC)) + { + // UWorld* WorldContext = Output->GetWorld(); + const bool bResult = BakeLandscape( + OutputIdx, + Output, + InBakedOutputs[OutputIdx].BakedOutputObjects, + bInReplaceActors, + bInReplaceAssets, + InBakeFolder.Path, + InHoudiniAssetName, + OutBakeStats); + } + } + break; + + case EHoudiniOutputType::Skeletal: + break; + + case EHoudiniOutputType::Curve: + { + FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( + Output, + InBakedOutputs[OutputIdx].BakedOutputObjects, + InBakedOutputs, + InHoudiniAssetName, + InBakeFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + break; + + case EHoudiniOutputType::Invalid: + break; + } + + NumProcessedOutputs++; + } + + if (bHasAnyInstancers) + { + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + UHoudiniOutput* Output = InOutputs[OutputIdx]; + if (!Output || Output->IsPendingKill()) + { + NumProcessedOutputs++; + continue; + } + + Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); + + if (Output->GetType() == EHoudiniOutputType::Instancer) + { + FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + OutputIdx, + InOutputs, + InBakedOutputs, + InParentTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + OutPackagesToSave, + InInstancerComponentTypesToBake, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + + NumProcessedOutputs++; + } + } + + OutNewActors.Append(BakedActors); + + return true; +} + + +bool +FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n) + { + UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n); + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + continue; + + if (Output->GetInstancedOutputs().Num() > 0) + return true; + /* + // TODO: Is this needed? check we have components to bake? + for (auto& OutputObjectPair : Output->GetOutputObjects()) + { + if (OutputObjectPair.Value.OutputCompoent!= nullpt) + return true; + } + */ + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor * OwnerActor = HoudiniAssetComponent->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return false; + + int32 BakedCount = 0; + TArray PackagesToSave; + + FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); + + // Build an array of the outputs so that we can search for meshes/previous baked meshes + const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); + TArray Outputs; + Outputs.Reserve(NumOutputs); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) + { + UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); + if (!Output || Output->IsPendingKill()) + continue; + + Outputs.Add(Output); + } + + // Get the previous bake outputs and match the output array size + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + if (BakedOutputs.Num() != NumOutputs) + BakedOutputs.SetNum(NumOutputs); + + // Map storing original and baked Static Meshes + TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) + { + UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + continue; + + // TODO: No need to use the instanced outputs for this + // We should simply iterate on the Output Objects instead! + TMap& OutputObjects = Output->GetOutputObjects(); + TMap& InstancedOutputs = Output->GetInstancedOutputs(); + for (auto & Pair : InstancedOutputs) + { + FString InstanceName = OwnerActor->GetName(); + + // // See if we have a bake name for that output + // FHoudiniOutputObject* OutputObj = OutputObjects.Find(Pair.Key); + // if (OutputObj && OutputObj->BakeName.IsEmpty()) + // InstanceName = OutputObj->BakeName; + + FHoudiniInstancedOutput& InstancedOutput = Pair.Value; + for (int32 VariarionIdx = 0; VariarionIdx < InstancedOutput.VariationObjects.Num(); ++VariarionIdx) + { + // TODO: !!! what if the instanced object/var is not a static mesh!!!!!! + UObject* CurrentVariationObject = InstancedOutput.VariationObjects[VariarionIdx].Get(); + UStaticMesh* InstancedStaticMesh = Cast(CurrentVariationObject); + if (!InstancedStaticMesh) + { + if (CurrentVariationObject) + { + HOUDINI_LOG_ERROR(TEXT("Failed to bake the instances of %s to Foliage"), *CurrentVariationObject->GetName()); + } + continue; + } + + // Check if we have already handled this mesh (already baked it from a previous variation), if so, + // use that + UStaticMesh* OutStaticMesh = nullptr; + bool bCreateNewType = true; + if (OriginalToBakedMesh.Contains(InstancedStaticMesh)) + { + OutStaticMesh = OriginalToBakedMesh.FindChecked(InstancedStaticMesh); + bCreateNewType = false; + } + + if (!IsValid(OutStaticMesh)) + { + // Find the output object and identifier for the mesh and previous bake of the mesh (if it exists) + FString ObjectName; + int32 MeshOutputIdx = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshOutputIdentifier; + UStaticMesh* PreviousBakeMesh = nullptr; + FHoudiniBakedOutputObject* BakedOutputObject = nullptr; + if (FindOutputObject(InstancedStaticMesh, Outputs, MeshOutputIdx, MeshOutputIdentifier)) + { + GetTemporaryOutputObjectBakeName(InstancedStaticMesh, Outputs, ObjectName); + + BakedOutputObject = &BakedOutputs[MeshOutputIdx].BakedOutputObjects.FindOrAdd(MeshOutputIdentifier); + if (BakedOutputObject) + { + PreviousBakeMesh = Cast(BakedOutputObject->GetBakedObjectIfValid()); + } + } + else + { + ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); + } + + // If the instanced static mesh is still a temporary Houdini created Static Mesh + // we will duplicate/bake it first before baking to foliage + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + MeshOutputIdentifier, + HoudiniAssetComponent->BakeFolder.Path, + ObjectName, + OwnerActor->GetName(), + AssetPackageReplaceMode); + + // DuplicateStaticMeshAndCreatePackageIfNeeded uses baked results to find a baked version of + // InstancedStaticMesh in the current bake results, but since we are already using + // OriginalToBakedMesh we don't have to populate BakedResults + const TArray BakedResults; + OutStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + InstancedStaticMesh, + PreviousBakeMesh, + PackageParams, + Outputs, + BakedResults, + HoudiniAssetComponent->TemporaryCookFolder.Path, + PackagesToSave); + OriginalToBakedMesh.Add(InstancedStaticMesh, OutStaticMesh); + + // Update our tracked baked output + if (BakedOutputObject) + BakedOutputObject->BakedObject = FSoftObjectPath(OutStaticMesh).ToString(); + + bCreateNewType = true; + } + + // See if we already have a FoliageType for that static mesh + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(OutStaticMesh); + if (!FoliageType || FoliageType->IsPendingKill()) + { + // We need to create a new FoliageType for this Static Mesh + // TODO: Add foliage default settings + InstancedFoliageActor->AddMesh(OutStaticMesh, &FoliageType); + bCreateNewType = true; + } + + // If we are baking in replace mode, remove the foliage type if it already exists + // and a create a new one + if (bInReplaceAssets && bCreateNewType && IsValid(FoliageType)) + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if (!FoliageInfo) + continue; + + // Apply the transform offset on the transforms for this variation + TArray ProcessedTransforms; + FHoudiniInstanceTranslator::ProcessInstanceTransforms(InstancedOutput, VariarionIdx, ProcessedTransforms); + + FFoliageInstance FoliageInstance; + int32 CurrentInstanceCount = 0; + for (auto CurrentTransform : ProcessedTransforms) + { + FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); + FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + CurrentInstanceCount++; + } + + // TODO: This was due to a bug in UE4.22-20, check if still needed! + if (FoliageInfo->GetComponent()) + FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); + + // Notify the user that we succesfully bake the instances to foliage + FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + OutStaticMesh->GetName() + TEXT(" to Foliage"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + BakedCount += ProcessedTransforms.Num(); + } + } + } + + InstancedFoliageActor->RegisterAllComponents(); + + // Update / repopulate the foliage editor mode's mesh list + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + + if (BakedCount > 0) + { + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + return true; + } + + return false; +} + + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; + if (!InOutput || InOutput->IsPendingKill()) + return false; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + + // Ensure we have the same number of baked outputs and asset outputs + if (InBakedOutputs.Num() != InAllOutputs.Num()) + InBakedOutputs.SetNum(InAllOutputs.Num()); + + // Iterate on the output objects, baking their object/component as we go + for (auto& Pair : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = Pair.Value; + FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputs[InOutputIndex].BakedOutputObjects.FindOrAdd(Pair.Key); + + if (CurrentOutputObject.bProxyIsCurrent) + { + // TODO: we need to refine the SM first! + // ?? + } + + if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill()) + continue; + + if (CurrentOutputObject.OutputComponent->IsA()) + { + // TODO: Baking foliage instancer to actors it not supported currently + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent))) + { + BakeInstancerOutputToActors_ISMC( + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent))) + { + BakeInstancerOutputToActors_IAC( + InOutputIndex, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) + { + BakeInstancerOutputToActors_MSIC( + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) + { + BakeInstancerOutputToActors_SMC( + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else + { + // Unsupported component! + } + + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UInstancedStaticMeshComponent * InISMC = Cast(InOutputObject.OutputComponent); + if (!InISMC || InISMC->IsPendingKill()) + return false; + + AActor * OwnerActor = InISMC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh * StaticMesh = InISMC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its + // name from its package. + FString ObjectName; + if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) + { + // Not found in HDA/temp outputs, use its package name + ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + } + + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + // See if the instanced static mesh is still a temporary Houdini created Static Mesh + // If it is, we need to bake the StaticMesh first + FHoudiniPackageParams PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + InOutputObjectIdentifier, + InBakeFolder.Path, + // ObjectName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), + ObjectName, + OwnerActor->GetName(), + AssetPackageReplaceMode); + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, PackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + + // Update the baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + + // Access some of the attribute that were cached on the output object + FHoudiniAttributeResolver Resolver; + { + TMap CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if(!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + /* + // TODO: Get the bake name! + // Bake override, the output name + // The bake name override has priority + FString InstancerName = InOutputObject.BakeName; + if (InstancerName.IsEmpty()) + { + // .. then use the output name + InstancerName = Resolver.ResolveOutputName(); + } + */ + + // Should we create one actor with an ISMC or multiple actors with one SMC? + bool bSpawnMultipleSMC = false; + if (bSpawnMultipleSMC) + { + // TODO: Double check, Has a crash here! + + // Get the StaticMesh ActorFactory + UActorFactory* SMFactory = nullptr; + + if (!FoundActor) + { + SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!SMFactory) + return false; + } + + // Split the instances to multiple StaticMeshActors + for (int32 InstanceIdx = 0; InstanceIdx < InISMC->GetInstanceCount(); InstanceIdx++) + { + FTransform InstanceTransform; + InISMC->GetInstanceTransform(InstanceIdx, InstanceTransform, true); + + if (!FoundActor) + { + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform, RF_Transactional); + if (!FoundActor || FoundActor->IsPendingKill()) + continue; + } + + FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName, FoundActor); + // FoundActor->Rename(*NewName.ToString()); + // FoundActor->SetActorLabel(NewName.ToString()); + RenameAndRelabelActor(FoundActor, NewName.ToString(), false); + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + continue; + + // Copy properties from the existing component + CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC); + + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh)); + } + } + else + { + bool bSpawnedActor = false; + if (!FoundActor) + { + // Only create one actor + FActorSpawnParameters SpawnInfo; + SpawnInfo.OverrideLevel = DesiredLevel; + SpawnInfo.ObjectFlags = RF_Transactional; + SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); + SpawnInfo.bDeferConstruction = true; + + // Spawn the new Actor + FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + bSpawnedActor = true; + + FoundActor->SetActorLabel(FoundActor->GetName()); + FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); + } + else + { + // If there is a previously baked component, and we are in replace mode, remove it + if (bInReplaceAssets) + { + USceneComponent* InPrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(InPrevComponent) && InPrevComponent->GetOwner() == FoundActor) + RemovePreviouslyBakedComponent(InPrevComponent); + } + + const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); + } + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Get/create the actor's root component + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + if (bSpawnedActor && IsValid(RootComponent)) + RootComponent->SetWorldTransform(InTransform); + + // Duplicate the instancer component, create a Hierarchical ISMC if needed + UInstancedStaticMeshComponent* NewISMC = nullptr; + UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast(InISMC); + if (InHISMC) + { + NewISMC = DuplicateObject( + InHISMC, + FoundActor, + MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetFName())); + } + else + { + NewISMC = DuplicateObject( + InISMC, + FoundActor, + MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetFName())); + } + + if (!NewISMC) + { + //DesiredLevel->OwningWorld-> + return false; + } + + InBakedOutputObject.BakedComponent = FSoftObjectPath(NewISMC).ToString(); + + NewISMC->RegisterComponent(); + // NewISMC->SetupAttachment(nullptr); + NewISMC->SetStaticMesh(BakedStaticMesh); + FoundActor->AddInstanceComponent(NewISMC); + // NewActor->SetRootComponent(NewISMC); + if (IsValid(RootComponent)) + NewISMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + NewISMC->SetWorldTransform(InISMC->GetComponentTransform()); + + // TODO: do we need to copy properties here, we duplicated the component + // // Copy properties from the existing component + // CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); + + if (bSpawnedActor) + FoundActor->FinishSpawning(InTransform); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh)); + + FoundActor->InvalidateLightingCache(); + FoundActor->PostEditMove(true); + FoundActor->MarkPackageDirty(); + } + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UStaticMeshComponent* InSMC = Cast(InOutputObject.OutputComponent); + if (!InSMC || InSMC->IsPendingKill()) + return false; + + AActor* OwnerActor = InSMC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh* StaticMesh = InSMC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its + // name from its package. + FString ObjectName; + if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) + { + // Not found in HDA/temp outputs, use its package name + ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + } + + // BaseName holds the Actor / HDA name + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + // See if the instanced static mesh is still a temporary Houdini created Static Mesh + // If it is, we need to bake the StaticMesh first + FHoudiniPackageParams PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + InOutputObjectIdentifier, + InBakeFolder.Path, + // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), + ObjectName, + OwnerActor->GetName(), + AssetPackageReplaceMode); + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, + OutActors, InTempCookFolder.Path, OutPackagesToSave); + + // Update the previous baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + + // Access some of the attribute that were cached on the output object + FHoudiniAttributeResolver Resolver; + { + TMap CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + UStaticMeshComponent* StaticMeshComponent = nullptr; + // Create an actor if we didn't find one + if (!FoundActor) + { + // Get the StaticMesh ActorFactory + UActorFactory* SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!SMFactory) + return false; + + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + return false; + + StaticMeshComponent = SMActor->GetStaticMeshComponent(); + } + else + { + USceneComponent* RootComponent = GetActorRootComponent(FoundActor); + if (!IsValid(RootComponent)) + return false; + + if (bInReplaceAssets) + { + // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it + UStaticMeshComponent* PrevSMC = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) + { + StaticMeshComponent = PrevSMC; + } + } + + if (!IsValid(StaticMeshComponent)) + { + // Create a new static mesh component + StaticMeshComponent = NewObject(FoundActor, NAME_None, RF_Transactional); + + FoundActor->AddInstanceComponent(StaticMeshComponent); + StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + StaticMeshComponent->RegisterComponent(); + } + } + + FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName, FoundActor); + // FoundActor->Rename(*NewName.ToString()); + // FoundActor->SetActorLabel(NewName.ToString()); + RenameAndRelabelActor(FoundActor, NewName.ToString(), false); + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Update the previous baked component + InBakedOutputObject.BakedComponent = FSoftObjectPath(StaticMeshComponent).ToString(); + + if (!IsValid(StaticMeshComponent)) + return false; + + // Copy properties from the existing component + CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC); + StaticMeshComponent->SetStaticMesh(BakedStaticMesh); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh)); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave) +{ + UHoudiniInstancedActorComponent* InIAC = Cast(InOutputObject.OutputComponent); + if (!InIAC || InIAC->IsPendingKill()) + return false; + + AActor * OwnerActor = InIAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + // BaseName holds the Actor / HDA name + const FName BaseName = FName(OwnerActor->GetName()); + + // Get the object instanced by this IAC + UObject* InstancedObject = InIAC->GetInstancedObject(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + return false; + + FHoudiniPackageParams PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + InOutputObjectIdentifier, + InBakeFolder.Path, + BaseName.ToString(), + OwnerActor->GetName(), + AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Access some of the attribute that were cached on the output object + FHoudiniAttributeResolver Resolver; + { + TMap CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // If we are baking in actor replacement mode, remove any previously baked instanced actors for this output + if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0) + { + UWorld* LevelWorld = DesiredLevel->GetWorld(); + if (IsValid(LevelWorld)) + { + for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) + { + const FSoftObjectPath ActorPath(ActorPathStr); + + if (!ActorPath.IsValid()) + continue; + + AActor* Actor = Cast(ActorPath.TryLoad()); + // Destroy Actor if it is valid and part of DesiredLevel + if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel) + { +#if WITH_EDITOR + LevelWorld->EditorDestroyActor(Actor, true); +#else + LevelWorld->DestroyActor(Actor); +#endif + } + } + } + } + + // Empty and reserve enough space for new instanced actors + InBakedOutputObject.InstancedActors.Empty(InIAC->GetInstancedActors().Num()); + + // Iterates on all the instances of the IAC + for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors()) + { + if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) + continue; + + FName NewInstanceName = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName); + FString NewNameStr = NewInstanceName.ToString(); + + FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); + AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); + if (!NewActor || NewActor->IsPendingKill()) + continue; + + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); + + NewActor->SetActorLabel(NewNameStr); + SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); + NewActor->SetActorTransform(CurrentTransform); + + InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); + + OutActors.Add(FHoudiniEngineBakedActor( + NewActor, + BaseName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + nullptr, + InstancedObject)); + } + + // TODO: + // Move Actors to DesiredLevel if needed?? + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = true; + const bool bInDestroyBakedInstancedActors = false; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UHoudiniMeshSplitInstancerComponent * InMSIC = Cast(InOutputObject.OutputComponent); + if (!InMSIC || InMSIC->IsPendingKill()) + return false; + + AActor * OwnerActor = InMSIC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh * StaticMesh = InMSIC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its + // name from its package. + FString ObjectName; + if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) + { + // Not found in HDA/temp outputs, use its package name + ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + } + + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + // See if the instanced static mesh is still a temporary Houdini created Static Mesh + // If it is, we need to bake the StaticMesh first + FHoudiniPackageParams PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + InOutputObjectIdentifier, + InBakeFolder.Path, + // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), + ObjectName, + OwnerActor->GetName(), + AssetPackageReplaceMode); + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, + OutActors, InTempCookFolder.Path, OutPackagesToSave); + + // Update the baked output + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + + // Get the level specified by attribute + // Access some of the attributes that were cached on the output object + FHoudiniAttributeResolver Resolver; + { + TMap CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + bool bSpawnedActor = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + if (!FoundActor) + { + // This is a split mesh instancer component - we will create a generic AActor with a bunch of SMC + FActorSpawnParameters SpawnInfo; + SpawnInfo.OverrideLevel = DesiredLevel; + SpawnInfo.ObjectFlags = RF_Transactional; + SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); + SpawnInfo.bDeferConstruction = true; + + // Spawn the new Actor + FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + bSpawnedActor = true; + + FoundActor->SetActorLabel(FoundActor->GetName()); + FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); + } + else + { + // If we are baking in replacement mode, remove the previous components (if they belong to FoundActor) + for (const FString& PrevComponentPathStr : InBakedOutputObject.InstancedComponents) + { + const FSoftObjectPath PrevComponentPath(PrevComponentPathStr); + + if (!PrevComponentPath.IsValid()) + continue; + + UActorComponent* PrevComponent = Cast(PrevComponentPath.TryLoad()); + if (!IsValid(PrevComponent) || PrevComponent->GetOwner() != FoundActor) + continue; + + RemovePreviouslyBakedComponent(PrevComponent); + } + + const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); + } + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Get/create the actor's root component + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + if (bSpawnedActor && IsValid(RootComponent)) + RootComponent->SetWorldTransform(InTransform); + + // Empty and reserve enough space in the baked components array for the new components + InBakedOutputObject.InstancedComponents.Empty(InMSIC->GetInstances().Num()); + + // Now add s SMC component for each of the SMC's instance + for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances()) + { + if (!CurrentSMC || CurrentSMC->IsPendingKill()) + continue; + + UStaticMeshComponent* NewSMC = DuplicateObject( + CurrentSMC, + FoundActor, + MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetFName())); + if (!NewSMC || NewSMC->IsPendingKill()) + continue; + + InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString()); + + NewSMC->RegisterComponent(); + // NewSMC->SetupAttachment(nullptr); + NewSMC->SetStaticMesh(BakedStaticMesh); + FoundActor->AddInstanceComponent(NewSMC); + NewSMC->SetWorldTransform(CurrentSMC->GetComponentTransform()); + if (IsValid(RootComponent)) + NewSMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); + + // TODO: Do we need to copy properties here, we duplicated the component + // // Copy properties from the existing component + // CopyPropertyToNewActorAndComponent(FoundActor, NewSMC, CurrentSMC); + } + + if (bSpawnedActor) + FoundActor->FinishSpawning(InTransform); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh)); + + FoundActor->InvalidateLightingCache(); + FoundActor->PostEditMove(true); + FoundActor->MarkPackageDirty(); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = true; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = false; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::FindHGPO( + const FHoudiniOutputObjectIdentifier& InIdentifier, + const TArray& InHGPOs, + FHoudiniGeoPartObject const*& OutHGPO) +{ + // Find the HGPO that matches this output identifier + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + for (auto & NextHGPO : InHGPOs) + { + // We use Matches() here as it handles the case where the HDA was loaded, + // which likely means that the the obj/geo/part ids dont match the output identifier + if(InIdentifier.Matches(NextHGPO)) + { + FoundHGPO = &NextHGPO; + break; + } + } + + OutHGPO = FoundHGPO; + return !OutHGPO; +} + +void +FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const FHoudiniOutputObject& InMeshOutputObject, + FString& OutBakeName) +{ + // The bake name override has priority + OutBakeName = InMeshOutputObject.BakeName; + if (OutBakeName.IsEmpty()) + { + FHoudiniAttributeResolver Resolver; + Resolver.SetCachedAttributes(InMeshOutputObject.CachedAttributes); + Resolver.SetTokensFromStringMap(InMeshOutputObject.CachedTokens); + const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InObject); + // The default output name (if not set via attributes) is {object_name}, which look for an object_name + // key-value token + if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) + Resolver.SetToken(TEXT("object_name"), DefaultObjectName); + OutBakeName = Resolver.ResolveOutputName(); + // const TArray& HGPOs = InAllOutputs[MeshOutputIdx]->GetHoudiniGeoPartObjects(); + // const FHoudiniGeoPartObject* FoundHGPO = nullptr; + // FindHGPO(MeshIdentifier, HGPOs, FoundHGPO); + // // ... finally the part name + // if (FoundHGPO && FoundHGPO->bHasCustomPartName) + // OutBakeName = FoundHGPO->PartName; + if (OutBakeName.IsEmpty()) + OutBakeName = DefaultObjectName; + } +} + +bool +FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const TArray& InAllOutputs, + FString& OutBakeName) +{ + if (!IsValid(InObject)) + return false; + + OutBakeName.Empty(); + + int32 MeshOutputIdx = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + if (FindOutputObject(InObject, InAllOutputs, MeshOutputIdx, MeshIdentifier)) + { + // Found the mesh, get its name + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier); + GetTemporaryOutputObjectBakeName(InObject, MeshOutputObject, OutBakeName); + + return true; + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; + if (!InOutput || InOutput->IsPendingKill()) + return false; + + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!Factory) + return false; + + TMap& OutputObjects = InOutput->GetOutputObjects(); +const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); + + // Get the previous bake objects + if (InOutputIndex >= 0 && !InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + TMap& BakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + + for (auto& Pair : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + const FHoudiniOutputObject& OutputObject = Pair.Value; + + // Fetch previous bake output + FHoudiniBakedOutputObject& BakedOutputObject = BakedOutputObjects.FindOrAdd(Identifier); + + UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); + if (!StaticMesh || StaticMesh->IsPendingKill()) + continue; + + UStaticMeshComponent* InSMC = Cast(OutputObject.OutputComponent); + if (!InSMC || InSMC->IsPendingKill()) + continue; + + // Find the HGPO that matches this output identifier + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + FindHGPO(Identifier, HGPOs, FoundHGPO); + + // We do not bake templated geos + if (FoundHGPO && FoundHGPO->bIsTemplated) + continue; + + FHoudiniAttributeResolver Resolver; + Resolver.SetCachedAttributes(OutputObject.CachedAttributes); + Resolver.SetTokensFromStringMap(OutputObject.CachedTokens); + const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + // The default output name (if not set via attributes) is {object_name}, which look for an object_name + // key-value token + if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) + Resolver.SetToken(TEXT("object_name"), DefaultObjectName); + + // The bake name override has priority + FString SMName = OutputObject.BakeName; + if (SMName.IsEmpty()) + { + // // ... finally the part name + // if (FoundHGPO && FoundHGPO->bHasCustomPartName) + // SMName = FoundHGPO->PartName; + // else + SMName = Resolver.ResolveOutputName(); + if (SMName.IsEmpty()) + SMName = DefaultObjectName; + } + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, Identifier, InBakeFolder.Path, SMName, + InHoudiniAssetName, AssetPackageReplaceMode); + + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); + + UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + // See if this output object has an unreal_level_path attribute specified + // In which case, we need to create/find the desired level for baking instead of using the current one + bool bHasLevelPathAttribute = OutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Access some of the attribute that were cached on the output object + // FHoudiniAttributeResolver Resolver; + // const TMap& CachedAttributes = OutputObject.CachedAttributes; + TMap Tokens = OutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + // Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + continue; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add the level to the packages to save? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + // Bake the static mesh if it is still temporary + UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, + Cast(BakedOutputObject.GetBakedObjectIfValid()), + PackageParams, + InAllOutputs, + OutActors, + InTempCookFolder.Path, + OutPackagesToSave); + + if (!BakedSM || BakedSM->IsPendingKill()) + continue; + + // Record the baked object + BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString(); + + // Make sure we have a level to spawn to + if (!DesiredLevel || DesiredLevel->IsPendingKill()) + continue; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + UStaticMeshComponent* SMC = nullptr; + if (!FoundActor) + { + // Spawn the new actor + FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); + if (!FoundActor || FoundActor->IsPendingKill()) + continue; + + // Copy properties to new actor + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + continue; + + SMC = SMActor->GetStaticMeshComponent(); + } + else + { + if (bInReplaceAssets) + { + // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it + UStaticMeshComponent* PrevSMC = Cast(BakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) + { + SMC = PrevSMC; + } + } + + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + + if (!IsValid(SMC)) + { + // Create a new static mesh component on the existing actor + SMC = NewObject(FoundActor, NAME_None, RF_Transactional); + + FoundActor->AddInstanceComponent(SMC); + if (IsValid(RootComponent)) + SMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + else + FoundActor->SetRootComponent(SMC); + SMC->RegisterComponent(); + } + } + + // We need to make a unique name for the actor, renaming an object on top of another is a fatal error + const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName, FoundActor); + const FString NewNameStr = NewName.ToString(); + // FoundActor->Rename(*NewNameStr); + // FoundActor->SetActorLabel(NewNameStr); + RenameAndRelabelActor(FoundActor, NewNameStr, false); + SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath); + + if (IsValid(SMC)) + { + CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC); + SMC->SetStaticMesh(BakedSM); + BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString(); + } + + BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh)); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + BakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( + UHoudiniOutput* Output, + TMap& InBakedOutputObjects, + const TArray& InAllBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!Output || Output->IsPendingKill()) + return false; + + TArray PackagesToSave; + + TMap& OutputObjects = Output->GetOutputObjects(); + const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); + + for (auto & Pair : OutputObjects) + { + FHoudiniOutputObject& OutputObject = Pair.Value; + USplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + FHoudiniOutputObjectIdentifier & Identifier = Pair.Key; + FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(Identifier); + + // TODO: FIX ME!! May not work 100% + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + for (auto & NextHGPO : HGPOs) + { + if (Identifier.GeoId == NextHGPO.GeoId && + Identifier.ObjectId == NextHGPO.ObjectId && + Identifier.PartId == NextHGPO.PartId) + { + FoundHGPO = &NextHGPO; + break; + } + } + + if (!FoundHGPO) + continue; + + FString CurveName = Pair.Value.BakeName; + if (CurveName.IsEmpty()) + { + if (FoundHGPO->bHasCustomPartName) + CurveName = FoundHGPO->PartName; + else + CurveName = InHoudiniAssetName + "_" + SplineComponent->GetName(); + } + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, Identifier, InBakeFolder.Path, CurveName, + InHoudiniAssetName, AssetPackageReplaceMode); + + BakeCurve(OutputObject, BakedOutputObject, PackageParams, bInReplaceActors, bInReplaceAssets, OutActors, + PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); + } + + SaveBakedPackages(PackagesToSave); + + return true; +} + +bool +FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint) +{ + if (!InActor || InActor->IsPendingKill()) + return false; + + if (!OutBlueprint || OutBlueprint->IsPendingKill()) + return false; + + if (InActor->GetInstanceComponents().Num() > 0) + FKismetEditorUtilities::AddComponentsToBlueprint( + OutBlueprint, + InActor->GetInstanceComponents()); + + if (OutBlueprint->GeneratedClass) + { + AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject()); + if (!CDO || CDO->IsPendingKill()) + return false; + + const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) + (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); + + EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions); + + USceneComponent * Scene = CDO->GetRootComponent(); + if (Scene && !Scene->IsPendingKill()) + { + Scene->SetRelativeLocation(FVector::ZeroVector); + Scene->SetRelativeRotation(FRotator::ZeroRotator); + + // Clear out the attachment info after having copied the properties from the source actor + Scene->SetupAttachment(nullptr); + while (true) + { + const int32 ChildCount = Scene->GetAttachChildren().Num(); + if (ChildCount < 1) + break; + + USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; + if (Component && !Component->IsPendingKill()) + Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + } + check(Scene->GetAttachChildren().Num() == 0); + + // Ensure the light mass information is cleaned up + Scene->InvalidateLightingCache(); + + // Copy relative scale from source to target. + if (USceneComponent* SrcSceneRoot = InActor->GetRootComponent()) + { + Scene->SetRelativeScale3D_Direct(SrcSceneRoot->GetRelativeScale3D()); + } + } + } + + // Compile our blueprint and notify asset system about blueprint. + //FKismetEditorUtilities::CompileBlueprint(OutBlueprint); + //FAssetRegistryModule::AssetCreated(OutBlueprint); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) +{ + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, BakeStats, Blueprints, PackagesToSave); + if (!bSuccess) + { + // TODO: ? + HOUDINI_LOG_WARNING(TEXT("Errors while baking to blueprints.")); + } + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprints( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceAssets, + FHoudiniEngineOutputStats& InBakeStats, + TArray& OutBlueprints, + TArray& OutPackagesToSave) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); + const bool bIsOwnerActorValid = IsValid(OwnerActor); + + TArray Actors; + + // Don't process outputs that are not supported in blueprints + TArray OutputsToBake = { + EHoudiniOutputType::Mesh, + EHoudiniOutputType::Instancer, + EHoudiniOutputType::Curve + }; + TArray InstancerComponentTypesToBake = { + EHoudiniInstancerComponentType::StaticMeshComponent, + EHoudiniInstancerComponentType::InstancedStaticMeshComponent, + EHoudiniInstancerComponentType::MeshSplitInstancerComponent, + }; + // When baking blueprints we always create new actors since they are deleted from the world once copied into the + // blueprint + const bool bReplaceActors = false; + bool bBakeSuccess = BakeHoudiniActorToActors( + HoudiniAssetComponent, + bReplaceActors, + bInReplaceAssets, + Actors, + OutPackagesToSave, + InBakeStats, + &OutputsToBake, + &InstancerComponentTypesToBake); + if (!bBakeSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Could not create output actors for baking to blueprint.")); + return false; + } + + // Get the previous baked outputs + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + + bBakeSuccess = BakeBlueprintsFromBakedActors( + Actors, + HoudiniAssetComponent->bRecenterBakedActors, + bInReplaceAssets, + bIsOwnerActorValid ? OwnerActor->GetName() : FString(), + HoudiniAssetComponent->BakeFolder, + &BakedOutputs, + nullptr, + OutBlueprints, + OutPackagesToSave); + + return bBakeSuccess; +} + +UStaticMesh* +FHoudiniEngineBakeUtils::BakeStaticMesh( + UStaticMesh * StaticMesh, + const FHoudiniPackageParams& PackageParams, + const TArray& InAllOutputs, + const FDirectoryPath& InTempCookFolder) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return nullptr; + + TArray PackagesToSave; + TArray Outputs; + const TArray BakedResults; + UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave); + + if (BakedStaticMesh) + { + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor) + { + TArray Objects; + Objects.Add(BakedStaticMesh); + GEditor->SyncBrowserToObjects(Objects); + } + } + + return BakedStaticMesh; +} + +bool +FHoudiniEngineBakeUtils::BakeLandscape( + int32 InOutputIndex, + UHoudiniOutput* InOutput, + TMap& InBakedOutputObjects, + bool bInReplaceActors, + bool bInReplaceAssets, + FString BakePath, + FString HoudiniAssetName, + FHoudiniEngineOutputStats& BakeStats + ) +{ + if (!IsValid(InOutput)) + return false; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + TArray PackagesToSave; + TArray LandscapeWorldsToUpdate; + + FHoudiniPackageParams PackageParams; + + for (auto& Elem : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key; + FHoudiniOutputObject& OutputObject = Elem.Value; + FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(ObjectIdentifier); + + // Populate the package params for baking this output object. + if (!IsValid(OutputObject.OutputObject)) + continue; + + if (!OutputObject.OutputObject->IsA()) + continue; + + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); + if (!IsValid(Landscape)) + continue; + + FString ObjectName = Landscape->GetName(); + + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + ObjectIdentifier, + BakePath, + ObjectName, + HoudiniAssetName, + AssetPackageReplaceMode + ); + + BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, + PackageParams, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); + } + + if (PackagesToSave.Num() > 0) + { + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); + } + + for(UWorld* LandscapeWorld : LandscapeWorldsToUpdate) + { + if (!LandscapeWorld) + continue; + FHoudiniEngineUtils::RescanWorldPath(LandscapeWorld); + ULandscapeInfo::RecreateLandscapeInfo(LandscapeWorld, true); + if (LandscapeWorld->WorldComposition) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(LandscapeWorld); + } + } + + if (PackagesToSave.Num() > 0) + { + // These packages were either created during the Bake process or they weren't + // loaded in the first place so be sure to unload them again to preserve their "state". + + TArray PackagesToUnload; + for (UPackage* Package : PackagesToSave) + { + if (!Package->IsDirty()) + PackagesToUnload.Add(Package); + } + UPackageTools::UnloadPackages(PackagesToUnload); + } + +#if WITH_EDITOR + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); +#endif + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeLandscapeObject( + FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInReplaceActors, + bool bInReplaceAssets, + FHoudiniPackageParams& PackageParams, + TArray& WorldsToUpdate, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& BakeStats) +{ + UHoudiniLandscapePtr* LandscapePointer = Cast(InOutputObject.OutputObject); + if (!LandscapePointer) + return false; + + ALandscapeProxy* TileActor = LandscapePointer->GetRawPtr(); + if (!TileActor) + return false; + + // Fetch the previous bake's pointer and proxy (if available) + ALandscapeProxy* PreviousTileActor = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + + UWorld* TileWorld = TileActor->GetWorld(); + ULevel* TileLevel = TileActor->GetLevel(); + + ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true); + + // At this point we reconstruct the resolver using cached attributes and tokens + // and just update certain tokens (output paths) for bake mode. + FHoudiniAttributeResolver Resolver; + { + TMap Tokens = InOutputObject.CachedTokens; + // PackageParams.UpdateOutputPathTokens(EPackageMode::Bake, Tokens); + PackageParams.UpdateTokensFromParams(TileWorld, Tokens); + Resolver.SetCachedAttributes(InOutputObject.CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC + // and has the appropriate name. + ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor(); + check(SharedLandscapeActor); + + // Fetch the previous bake's shared landscape actor (if available) + ALandscape* PreviousSharedLandscapeActor = nullptr; + if (IsValid(PreviousTileActor)) + PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor(); + + const bool bHasSharedLandscape = SharedLandscapeActor != TileActor; + const bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; + bool bLandscapeReplaced = false; + if (bHasSharedLandscape) + { + // If we are baking in replace mode and we have a previous shared landscape actor, use the name of that + // actor + const FString DesiredSharedLandscapeName = bHasPreviousSharedLandscape && bInReplaceActors + ? PreviousSharedLandscapeActor->GetName() + : Resolver.ResolveAttribute( + HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + SharedLandscapeActor->GetName()); + + // If we are not baking in replacement mode, create a unique name if the name is already in use + const FString SharedLandscapeName = !bInReplaceActors + ? MakeUniqueObjectNameIfNeeded(SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *DesiredSharedLandscapeName).ToString() + : DesiredSharedLandscapeName; + + if (SharedLandscapeActor->GetName() != SharedLandscapeName) + { + AActor* FoundActor = nullptr; + ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); + if (ExistingLandscape && bInReplaceActors) + { + // Even though we found an existing landscape with the desired type, we're just going to destroy/replace + // it for now. + FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); + ExistingLandscape->Destroy(); + bLandscapeReplaced = true; + } + + // Fix name of shared landscape + FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); + } + + SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); + } + + // Find the world where the landscape tile should be placed. + + TArray ValidLandscapes; + + FString ActorName = Resolver.ResolveOutputName(); + + // If the unreal_level_path was not specified, then fallback to the tile world's package + FString PackagePath = TileWorld->GetPackage() ? TileWorld->GetPackage()->GetPathName() : FString(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + PackagePath = Resolver.ResolveFullLevelPath(); + + if (bInReplaceActors) + { + // If we are baking in replace mode: get the previous baked actor (if available) name, but only if it is in the + // same target level + if (IsValid(PreviousTileActor)) + { + UPackage* PreviousPackage = PreviousTileActor->GetPackage(); + if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath) + { + ActorName = PreviousTileActor->GetName(); + } + } + } + + bool bCreatedPackage = false; + UWorld* TargetWorld = nullptr; + ULevel* TargetLevel = nullptr; + ALandscapeProxy* TargetActor = FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( + TileActor->GetWorld(), + nullptr, //unused in bake mode + ValidLandscapes,//unused in bake mode + -1, //unused in bake mode + -1, //unused in bake mode + ActorName, + PackagePath, + TargetWorld, + TargetLevel, + bCreatedPackage + ); + + check(TargetLevel) + check(TargetWorld) + + if (TargetActor && TargetActor != TileActor) + { + if (bInReplaceActors && (!PreviousTileActor || PreviousTileActor == TargetActor)) + { + // We found an target matching the name that we want. For now, rename it and then nuke it, so that + // at the very least we can spawn a new actor with the desired name. At a later stage we'll implement + // a content update, if possible. + FHoudiniEngineUtils::RenameToUniqueActor(TargetActor, ActorName + TEXT("_0")); + TargetActor->Destroy(); + } + else + { + // incremental, keep existing actor and create a unique name for the new one + ActorName = MakeUniqueObjectName(TargetActor->GetOuter(), TargetActor->GetClass(), *ActorName).ToString(); + } + TargetActor = nullptr; + } + + if (TargetLevel != TileActor->GetLevel()) + { + bool bLevelInWorld = TileWorld->ContainsLevel(TargetLevel); + ALandscape* SharedLandscape = TileActor->GetLandscapeActor(); + ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); + + check(LandscapeInfo); + + // We can now move the current landscape to the new world / level + // if (TileActor->GetClass()->IsChildOf()) + { + // We can only move streaming proxies to sublevels for now. + TArray ActorsToMove = {TileActor}; + + ALandscapeProxy* NewLandscapeProxy = LandscapeInfo->MoveComponentsToLevel(TileActor->LandscapeComponents, TargetLevel); + // We have now moved the landscape components into the new level. We can (hopefully) safely delete the + // old tile actor. + TileActor->Destroy(); + + TargetLevel->MarkPackageDirty(); + + TileActor = NewLandscapeProxy; + } + } + else + { + // Ensure the landscape actor is detached. + TileActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + } + + // Ensure the tile actor has the desired name. + FHoudiniEngineUtils::SafeRenameActor(TileActor, ActorName); + + if (TileActor->GetClass()->IsChildOf(ALandscape::StaticClass())) + { + // This is not a shared landscape. Be sure to update this landscape's world when + // baking is done. + WorldsToUpdate.AddUnique(TileActor->GetWorld()); + } + + if (bCreatedPackage) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(TargetLevel->GetOutermost()); + } + + // Record the landscape in the baked output object via a new UHoudiniLandscapePtr + // UHoudiniLandscapePtr* BakedLandscapePtr = NewObject(LandscapePointer->GetOuter()); + // if (IsValid(BakedLandscapePtr)) + // { + // BakedLandscapePtr->SetSoftPtr(TileActor); + InBakedOutputObject.BakedObject = FSoftObjectPath(TileActor).ToString(); + // } + // else + // { + // InBakedOutputObject.BakedObject = nullptr; + // } + + // Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks + InOutputObject.OutputObject = nullptr; + + DestroyPreviousBakeOutput(InBakedOutputObject, true, true, true); + + // ---------------------------------------------------- + // Collect baking stats + // ---------------------------------------------------- + if (bLandscapeReplaced) + BakeStats.NotifyObjectsReplaced(EHoudiniOutputType::Landscape, 1); + else + BakeStats.NotifyObjectsCreated(EHoudiniOutputType::Landscape, 1); + + if (bCreatedPackage) + BakeStats.NotifyPackageCreated(1); + else + if (TileLevel != TargetLevel) + BakeStats.NotifyPackageUpdated(1); + + return true; +} + +UStaticMesh * +FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + UStaticMesh * InStaticMesh, + UStaticMesh * InPreviousBakeStaticMesh, + const FHoudiniPackageParams &PackageParams, + const TArray& InParentOutputs, + const TArray& InCurrentBakedActors, + const FString& InTemporaryCookFolder, + TArray & OutCreatedPackages) +{ + if (!InStaticMesh || InStaticMesh->IsPendingKill()) + return nullptr; + + bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, InParentOutputs, InTemporaryCookFolder); + if (!bIsTemporaryStaticMesh) + { + // The Static Mesh is not a temporary one/already baked, we can simply reuse it + // instead of duplicating it + return InStaticMesh; + } + + // Look for InStaticMesh as the SourceObject in InCurrentBakedActors (it could have already been baked along with + // a previous output: instancers etc) + for (const FHoudiniEngineBakedActor& BakedActor : InCurrentBakedActors) + { + if (BakedActor.SourceObject == InStaticMesh && IsValid(BakedActor.BakedObject) + && BakedActor.BakedObject->IsA(InStaticMesh->GetClass())) + { + // We have found a bake result where InStaticMesh was the source object and we have a valid BakedObject + // of a compatible class + return Cast(BakedActor.BakedObject); + } + } + + // InStaticMesh is temporary and we didn't find a baked version of it in our current bake output, we need to bake it + + // If we have a previously baked static mesh, get the bake counter from it so that both replace and increment + // is consistent with the bake counter + int32 BakeCounter = 0; + bool bPreviousBakeStaticMeshValid = IsValid(InPreviousBakeStaticMesh); + TArray PreviousBakeMaterials; + if (bPreviousBakeStaticMeshValid) + { + bPreviousBakeStaticMeshValid = PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBakeStaticMesh); + if (bPreviousBakeStaticMeshValid) + { + PackageParams.GetBakeCounterFromBakedAsset(InPreviousBakeStaticMesh, BakeCounter); + PreviousBakeMaterials = InPreviousBakeStaticMesh->StaticMaterials;//GetStaticMaterials(); + } + } + FString CreatedPackageName; + UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter); + if (!MeshPackage || MeshPackage->IsPendingKill()) + return nullptr; + + OutCreatedPackages.Add(MeshPackage); + + // We need to be sure the package has been fully loaded before calling DuplicateObject + if (!MeshPackage->IsFullyLoaded()) + { + FlushAsyncLoading(); + if (!MeshPackage->GetOuter()) + { + MeshPackage->FullyLoad(); + } + else + { + MeshPackage->GetOutermost()->FullyLoad(); + } + } + + // If the a UStaticMesh with that name already exists then detach it from all of its components before replacing + // it so that its render resources can be safely replaced/updated, and then reattach it + UStaticMesh * DuplicatedStaticMesh = nullptr; + UStaticMesh* ExistingMesh = FindObject(MeshPackage, *CreatedPackageName); + if (IsValid(ExistingMesh)) + { + FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); + DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + } + else + { + DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + } + + if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, DuplicatedStaticMesh, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, DuplicatedStaticMesh, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedPackageName); + + // See if we need to duplicate materials and textures. + TArrayDuplicatedMaterials; + TArray& Materials = DuplicatedStaticMesh->StaticMaterials; + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + continue; + + // Only duplicate the material if it is temporary + if (IsObjectTemporary(MaterialInterface, InParentOutputs, InTemporaryCookFolder)) + { + UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); + if (MaterialPackage && !MaterialPackage->IsPendingKill()) + { + FString MaterialName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + MeshPackage, DuplicatedStaticMesh, MaterialName)) + { + MaterialName = MaterialName + "_Material" + FString::FromInt(MaterialIdx + 1); + + // We only deal with materials. + UMaterial * Material = Cast< UMaterial >(MaterialInterface); + if (Material && !Material->IsPendingKill()) + { + // Look for a previous bake material at this index + UMaterial* PreviousBakeMaterial = nullptr; + if (bPreviousBakeStaticMeshValid && PreviousBakeMaterials.IsValidIndex(MaterialIdx)) + { + PreviousBakeMaterial = Cast(PreviousBakeMaterials[MaterialIdx].MaterialInterface); + } + // Duplicate material resource. + UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages); + + if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + continue; + + // Store duplicated material. + FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx]; + DupeStaticMaterial.MaterialInterface = DuplicatedMaterial; + DuplicatedMaterials.Add(DupeStaticMaterial); + continue; + } + } + } + } + + // We can simply reuse the source material + DuplicatedMaterials.Add(Materials[MaterialIdx]); + } + + // Assign duplicated materials. + DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; + + // Notify registry that we have created a new duplicate mesh. + FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); + + // Dirty the static mesh package. + DuplicatedStaticMesh->MarkPackageDirty(); + + return DuplicatedStaticMesh; +} + +ALandscapeProxy* +FHoudiniEngineBakeUtils::BakeHeightfield( + ALandscapeProxy * InLandscapeProxy, + const FHoudiniPackageParams & PackageParams, + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType) +{ + if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill()) + return nullptr; + + const FString & BakeFolder = PackageParams.BakeFolder; + const FString & AssetName = PackageParams.HoudiniAssetName; + + switch (LandscapeOutputBakeType) + { + case EHoudiniLandscapeOutputBakeType::Detachment: + { + // Detach the landscape from the Houdini Asset Actor + InLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + } + break; + case EHoudiniLandscapeOutputBakeType::BakeToImage: + { + // Create heightmap image to the bake folder + ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); + if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + return nullptr; + + // bake to image must use absoluate path, + // and the file name has a file extension (.png) + FString BakeFolderInFullPath = BakeFolder; + + // Figure absolute path, + if (!BakeFolderInFullPath.EndsWith("/")) + BakeFolderInFullPath += "/"; + + if (BakeFolderInFullPath.StartsWith("/Game")) + BakeFolderInFullPath = BakeFolderInFullPath.Mid(5, BakeFolderInFullPath.Len() - 5); + + if (BakeFolderInFullPath.StartsWith("/")) + BakeFolderInFullPath = BakeFolderInFullPath.Mid(1, BakeFolderInFullPath.Len() - 1); + + FString FullPath = FPaths::ProjectContentDir() + BakeFolderInFullPath + AssetName + "_" + InLandscapeProxy->GetName() + ".png"; + + InLandscapeInfo->ExportHeightmap(FullPath); + + // TODO: + // We should update this to have an asset/package.. + } + break; + case EHoudiniLandscapeOutputBakeType::BakeToWorld: + { + ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); + if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + return nullptr; + + // 0. Get Landscape Data // + + // Extract landscape height data + TArray InLandscapeHeightData; + int32 XSize, YSize; + FVector Min, Max; + if (!FUnrealLandscapeTranslator::GetLandscapeData(InLandscapeProxy, InLandscapeHeightData, XSize, YSize, Min, Max)) + return nullptr; + + // Extract landscape Layers data + TArray InLandscapeImportLayerInfos; + for (int32 n = 0; n < InLandscapeInfo->Layers.Num(); ++n) + { + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + FLandscapeImportLayerInfo CurrentLayerInfo; + CurrentLayerInfo.LayerName = FName(LayerName); + CurrentLayerInfo.LayerInfo = InLandscapeInfo->Layers[n].LayerInfoObj; + CurrentLayerInfo.LayerData = CurrentLayerIntData; + + CurrentLayerInfo.LayerInfo->LayerUsageDebugColor = LayerUsageDebugColor; + + InLandscapeImportLayerInfos.Add(CurrentLayerInfo); + } + + // 1. Create package // + + FString PackagePath = PackageParams.GetPackagePath(); + FString PackageName = PackageParams.GetPackageName(); + + UPackage *CreatedPackage = nullptr; + FString CreatedPackageName; + + CreatedPackage = PackageParams.CreatePackageForObject(CreatedPackageName); + + if (!CreatedPackage) + return nullptr; + + // 2. Create a new world asset with dialog // + UWorldFactory* Factory = NewObject(); + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + UObject* Asset = AssetToolsModule.Get().CreateAssetWithDialog( + PackageName, PackagePath, + UWorld::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + + + UWorld* NewWorld = Cast(Asset); + if (!NewWorld) + return nullptr; + + NewWorld->SetCurrentLevel(NewWorld->PersistentLevel); + + // 4. Spawn a landscape proxy actor in the created world + ALandscapeStreamingProxy * BakedLandscapeProxy = NewWorld->SpawnActor(); + if (!BakedLandscapeProxy) + return nullptr; + + // Create a new GUID + FGuid currentGUID = FGuid::NewGuid(); + BakedLandscapeProxy->SetLandscapeGuid(currentGUID); + + // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue + BakedLandscapeProxy->bCastStaticShadow = false; + + + // 5. Import data to the created landscape proxy + TMap> HeightmapDataPerLayers; + TMap> MaterialLayerDataPerLayer; + + HeightmapDataPerLayers.Add(FGuid(), InLandscapeHeightData); + MaterialLayerDataPerLayer.Add(FGuid(), InLandscapeImportLayerInfos); + + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + + BakedLandscapeProxy->Import( + currentGUID, + 0, 0, XSize-1, YSize-1, + InLandscapeInfo->ComponentNumSubsections, InLandscapeInfo->SubsectionSizeQuads, + HeightmapDataPerLayers, NULL, + MaterialLayerDataPerLayer, ImportLayerType); + + BakedLandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + + + if (BakedLandscapeProxy->LandscapeMaterial) + BakedLandscapeProxy->LandscapeMaterial = InLandscapeProxy->LandscapeMaterial; + + if (BakedLandscapeProxy->LandscapeHoleMaterial) + BakedLandscapeProxy->LandscapeHoleMaterial = InLandscapeProxy->LandscapeHoleMaterial; + + // 6. Register all the landscape components, and set landscape actor transform + BakedLandscapeProxy->RegisterAllComponents(); + BakedLandscapeProxy->SetActorTransform(InLandscapeProxy->GetTransform()); + + // 7. Save Package + TArray PackagesToSave; + PackagesToSave.Add(CreatedPackage); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor) + { + TArray Objects; + Objects.Add(NewWorld); + GEditor->SyncBrowserToObjects(Objects); + } + } + break; + } + + return InLandscapeProxy; +} + +bool +FHoudiniEngineBakeUtils::BakeCurve( + USplineComponent* InSplineComponent, + ULevel* InLevel, + const FHoudiniPackageParams &PackageParams, + AActor*& OutActor, + USplineComponent*& OutSplineComponent, + FName InOverrideFolderPath, + AActor* InActor) +{ + if (!IsValid(InActor)) + { + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; + if (!Factory) + return false; + + OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform(), RF_Transactional); + } + else + { + OutActor = InActor; + } + + // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset + const FName BaseActorName(PackageParams.ObjectName); + const FName NewName = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName, OutActor); + const FString NewNameStr = NewName.ToString(); + // OutActor->Rename(*NewNameStr); + // OutActor->SetActorLabel(NewNameStr); + RenameAndRelabelActor(OutActor, NewNameStr, false); + OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); + + USplineComponent* DuplicatedSplineComponent = DuplicateObject( + InSplineComponent, + OutActor, + MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), FName(PackageParams.ObjectName))); + OutActor->AddInstanceComponent(DuplicatedSplineComponent); + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); + DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + + FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent); + DuplicatedSplineComponent->RegisterComponent(); + + OutSplineComponent = DuplicatedSplineComponent; + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeCurve( + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + // const TArray& InAllBakedOutputs, + const FHoudiniPackageParams &PackageParams, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + USplineComponent* SplineComponent = Cast(InOutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + return false; + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; + + // Access some of the attribute that were cached on the output object + FHoudiniAttributeResolver Resolver; + { + TMap CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if(!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + // If we are baking in replace mode, remove the previous bake component + if (bInReplaceAssets && !InBakedOutputObject.BakedComponent.IsEmpty()) + { + UActorComponent* PrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (PrevComponent && PrevComponent->GetOwner() == FoundActor) + { + RemovePreviouslyBakedComponent(PrevComponent); + } + } + + FHoudiniPackageParams CurvePackageParams = PackageParams; + CurvePackageParams.ObjectName = BakeActorName.ToString(); + USplineComponent* NewSplineComponent = nullptr; + const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName)); + if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor)) + return false; + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + InBakedOutputObject.BakedComponent = FSoftObjectPath(NewSplineComponent).ToString(); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + FHoudiniEngineBakedActor Result; + Result.Actor = FoundActor; + Result.ActorBakeName = BakeActorName; + OutActors.Add(Result); + + return true; +} + +AActor* +FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PackageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform) +{ + if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + return nullptr; + + TArray & DisplayPoints = InHoudiniSplineComponent->DisplayPoints; + if (DisplayPoints.Num() < 2) + return nullptr; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; + if (!Factory) + return nullptr; + + // Remove the actor if it exists + for (auto & Actor : DesiredLevel->Actors) + { + if (!Actor) + continue; + + if (Actor->GetName() == PackageParams.ObjectName) + { + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + Actor->RemoveFromRoot(); + Actor->ConditionalBeginDestroy(); + World->EditorDestroyActor(Actor, true); + + break; + } + } + + AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform(), RF_Transactional); + + USplineComponent* BakedUnrealSplineComponent = NewObject(NewActor); + if (!BakedUnrealSplineComponent) + return nullptr; + + // add display points to created unreal spline component + for (int32 n = 0; n < DisplayPoints.Num(); ++n) + { + FVector & NextPoint = DisplayPoints[n]; + BakedUnrealSplineComponent->AddSplinePoint(NextPoint, ESplineCoordinateSpace::Local); + // Set the curve point type to be linear, since we are using display points + BakedUnrealSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + NewActor->AddInstanceComponent(BakedUnrealSplineComponent); + + BakedUnrealSplineComponent->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); + + FAssetRegistryModule::AssetCreated(NewActor); + FAssetRegistryModule::AssetCreated(BakedUnrealSplineComponent); + BakedUnrealSplineComponent->RegisterComponent(); + + // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset + const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); + const FString NewNameStr = NewName.ToString(); + // NewActor->Rename(*NewNameStr); + // NewActor->SetActorLabel(NewNameStr); + RenameAndRelabelActor(NewActor, NewNameStr, false); + NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName)); + + return NewActor; +} + +UBlueprint* +FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PackageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform) +{ + if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + return nullptr; + + FGuid BakeGUID = FGuid::NewGuid(); + + if (!BakeGUID.IsValid()) + BakeGUID = FGuid::NewGuid(); + + // We only want half of generated guid string. + FString BakeGUIDString = BakeGUID.ToString().Left(FHoudiniEngineUtils::PackageGUIDItemNameLength); + + // Generate Blueprint name. + FString BlueprintName = PackageParams.ObjectName + "_BP"; + + // Generate unique package name. + FString PackageName = PackageParams.BakeFolder + "/" + BlueprintName; + PackageName = UPackageTools::SanitizePackageName(PackageName); + + // See if package exists, if it does, we need to regenerate the name. + UPackage * Package = FindPackage(nullptr, *PackageName); + + if (Package && !Package->IsPendingKill()) + { + // Package does exist, there's a collision, we need to generate a new name. + BakeGUID.Invalidate(); + } + else + { + // Create actual package. + Package = CreatePackage(*PackageName); + } + + AActor * CreatedHoudiniSplineActor = FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + InHoudiniSplineComponent, PackageParams, WorldToSpawn, SpawnTransform); + + TArray PackagesToSave; + + UBlueprint * Blueprint = nullptr; + if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill()) + { + + UObject* Asset = nullptr; + + Asset = StaticFindObjectFast(UObject::StaticClass(), Package, FName(*BlueprintName)); + if (!Asset) + { + UBlueprintFactory* Factory = NewObject(); + + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + Asset = AssetToolsModule.Get().CreateAsset( + BlueprintName, PackageParams.BakeFolder, + UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + } + + TArray Components; + for (auto & Next : CreatedHoudiniSplineActor->GetComponents()) + { + Components.Add(Next); + } + + Blueprint = Cast(Asset); + + // Clear old Blueprint Node tree + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + + int32 NodeSize = SCS->GetAllNodes().Num(); + for (int32 n = NodeSize - 1; n >= 0; --n) + SCS->RemoveNode(SCS->GetAllNodes()[n]); + + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components); + + CreatedHoudiniSplineActor->RemoveFromRoot(); + CreatedHoudiniSplineActor->ConditionalBeginDestroy(); + + GWorld->EditorDestroyActor(CreatedHoudiniSplineActor, true); + + Package->MarkPackageDirty(); + PackagesToSave.Add(Package); + } + + // Save the created BP package. + FHoudiniEngineBakeUtils::SaveBakedPackages + (PackagesToSave); + + return Blueprint; +} + + +void +FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const TCHAR * Key, + const TCHAR * Value) +{ + if (!Package || Package->IsPendingKill()) + return; + + UMetaData * MetaData = Package->GetMetaData(); + if (MetaData && !MetaData->IsPendingKill()) + MetaData->SetValue(Object, Key, Value); +} + + +bool +FHoudiniEngineBakeUtils:: +GetHoudiniGeneratedNameFromMetaInformation( + UPackage * Package, UObject * Object, FString & HoudiniName) +{ + if (!Package || Package->IsPendingKill()) + return false; + + UMetaData * MetaData = Package->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return false; + + if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + { + // Retrieve name used for package generation. + const FString NameFull = MetaData->GetValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME); + + //HoudiniName = NameFull.Left(FMath::Min(NameFull.Len(), FHoudiniEngineUtils::PackageGUIDItemNameLength)); + HoudiniName = NameFull; + return true; + } + + return false; +} + +UMaterial * +FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + UMaterial * Material, UMaterial* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, + TArray & OutGeneratedPackages) +{ + UMaterial * DuplicatedMaterial = nullptr; + + FString CreatedMaterialName; + // Create material package. Use the same package params as static mesh, but with the material's name + FHoudiniPackageParams MaterialPackageParams = ObjectPackageParams; + MaterialPackageParams.ObjectName = MaterialName; + + // Check if there is a valid previous material. If so, get the bake counter for consistency in + // replace or iterative package naming + bool bIsPreviousBakeMaterialValid = IsValid(PreviousBakeMaterial); + int32 BakeCounter = 0; + TArray PreviousBakeMaterialExpressions; + if (bIsPreviousBakeMaterialValid) + { + bIsPreviousBakeMaterialValid = MaterialPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeMaterial); + if (bIsPreviousBakeMaterialValid) + { + MaterialPackageParams.GetBakeCounterFromBakedAsset(PreviousBakeMaterial, BakeCounter); + PreviousBakeMaterialExpressions = PreviousBakeMaterial->Expressions; + } + } + + UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter); + + if (!MaterialPackage || MaterialPackage->IsPendingKill()) + return nullptr; + + // Clone material. + DuplicatedMaterial = DuplicateObject< UMaterial >(Material, MaterialPackage, *CreatedMaterialName); + if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, DuplicatedMaterial, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, DuplicatedMaterial, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedMaterialName); + + // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. + const int32 NumExpressions = DuplicatedMaterial->Expressions.Num(); + for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx) + { + UMaterialExpression* Expression = DuplicatedMaterial->Expressions[ExpressionIdx]; + UMaterialExpression* PreviousBakeExpression = nullptr; + if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx)) + { + PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; + } + FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages); + } + + // Notify registry that we have created a new duplicate material. + FAssetRegistryModule::AssetCreated(DuplicatedMaterial); + + // Dirty the material package. + DuplicatedMaterial->MarkPackageDirty(); + + // Recompile the baked material + // DuplicatedMaterial->ForceRecompileForRendering(); + // Use UMaterialEditingLibrary::RecompileMaterial since it correctly updates texture references in the material + // which ForceRecompileForRendering does not do + UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterial); + + OutGeneratedPackages.Add(MaterialPackage); + + return DuplicatedMaterial; +} + +void +FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, + const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages) +{ + UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression); + if (!TextureSample || TextureSample->IsPendingKill()) + return; + + UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture); + if (!Texture || Texture->IsPendingKill()) + return; + + UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); + if (!TexturePackage || TexturePackage->IsPendingKill()) + return; + + // Try to get the previous bake's texture + UTexture2D* PreviousBakeTexture = nullptr; + if (IsValid(PreviousBakeMaterialExpression)) + { + UMaterialExpressionTextureSample* PreviousBakeTextureSample = Cast< UMaterialExpressionTextureSample >(PreviousBakeMaterialExpression); + if (IsValid(PreviousBakeTextureSample)) + PreviousBakeTexture = Cast< UTexture2D >(PreviousBakeTextureSample->Texture); + } + + FString GeneratedTextureName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + TexturePackage, Texture, GeneratedTextureName)) + { + // Duplicate texture. + UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( + Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages); + + // Re-assign generated texture. + TextureSample->Texture = DuplicatedTexture; + } +} + +UTexture2D * +FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( + UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages) +{ + UTexture2D* DuplicatedTexture = nullptr; +#if WITH_EDITOR + // Retrieve original package of this texture. + UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); + if (!TexturePackage || TexturePackage->IsPendingKill()) + return nullptr; + + FString GeneratedTextureName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName)) + { + UMetaData * MetaData = TexturePackage->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return nullptr; + + // Retrieve texture type. + const FString & TextureType = + MetaData->GetValue(Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); + + FString CreatedTextureName; + + // Create texture package. Use the same package params as material's, but with object name appended by generated texture's name + FHoudiniPackageParams TexturePackageParams = PackageParams; + TexturePackageParams.ObjectName = TexturePackageParams.ObjectName + "_" + GeneratedTextureName; + + // Determine the bake counter of the previous bake's texture (if exists/valid) for naming consistency when + // replacing/iterating + bool bIsPreviousBakeTextureValid = IsValid(PreviousBakeTexture); + int32 BakeCounter = 0; + if (bIsPreviousBakeTextureValid) + { + bIsPreviousBakeTextureValid = TexturePackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeTexture); + if (bIsPreviousBakeTextureValid) + { + TexturePackageParams.GetBakeCounterFromBakedAsset(PreviousBakeTexture, BakeCounter); + } + } + + UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter); + + if (!NewTexturePackage || NewTexturePackage->IsPendingKill()) + return nullptr; + + // Clone texture. + DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName); + if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedTextureName); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); + + // Notify registry that we have created a new duplicate texture. + FAssetRegistryModule::AssetCreated(DuplicatedTexture); + + // Dirty the texture package. + DuplicatedTexture->MarkPackageDirty(); + + OutCreatedPackages.Add(NewTexturePackage); + } +#endif + return DuplicatedTexture; +} + + +bool +FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); + + if (!ActorOwner || ActorOwner->IsPendingKill()) + return false; + + UWorld* World = ActorOwner->GetWorld(); + if (!World) + World = GWorld; + + World->EditorDestroyActor(ActorOwner, false); + + return true; +} + + +void +FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld) +{ + UWorld * CurrentWorld = nullptr; + if (bSaveCurrentWorld && GEditor) + CurrentWorld = GEditor->GetEditorWorldContext().World(); + + if (CurrentWorld) + { + // Save the current map + FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); + UPackage* CurrentWorldPackage = CreatePackage(*CurrentWorldPath); + + if (CurrentWorldPackage) + { + CurrentWorldPackage->MarkPackageDirty(); + PackagesToSave.Add(CurrentWorldPackage); + } + } + + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); +} + +bool +FHoudiniEngineBakeUtils::FindOutputObject( + const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) +{ + if (!InObjectToFind || InObjectToFind->IsPendingKill()) + return false; + + const int32 NumOutputs = InOutputs.Num(); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + const UHoudiniOutput* CurOutput = InOutputs[OutputIdx]; + if (!IsValid(CurOutput)) + continue; + + for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) + { + if (CurOutputObject.Value.OutputObject == InObjectToFind + || CurOutputObject.Value.OutputComponent == InObjectToFind + || CurOutputObject.Value.ProxyObject == InObjectToFind + || CurOutputObject.Value.ProxyComponent == InObjectToFind) + { + OutOutputIndex = OutputIdx; + OutIdentifier = CurOutputObject.Key; + return true; + } + } + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + FString TempPath = FString(); + + // TODO: Get the HAC outputs in a better way? + TArray Outputs; + if (InHAC && !InHAC->IsPendingKill()) + { + const int32 NumOutputs = InHAC->GetNumOutputs(); + Outputs.Reserve(NumOutputs); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + Outputs.Add(InHAC->GetOutputAt(OutputIdx)); + } + + TempPath = InHAC->TemporaryCookFolder.Path; + } + + return IsObjectTemporary(InObject, Outputs, TempPath); +} + +bool FHoudiniEngineBakeUtils::IsObjectTemporary( + UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + int32 ParentOutputIndex = -1; + FHoudiniOutputObjectIdentifier Identifier; + if (FindOutputObject(InObject, InParentOutputs, ParentOutputIndex, Identifier)) + return true; + + // Check the package path for this object + // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated + UPackage* ObjectPackage = InObject->GetOutermost(); + if (ObjectPackage && !ObjectPackage->IsPendingKill()) + { + const FString PathName = ObjectPackage->GetPathName(); + if (PathName.StartsWith(InTemporaryCookFolder)) + return true; + + // Also check the default temp folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder)) + return true; + + /* + // TODO: this just indicates that the object was generated by H + // it could as well have been baked before... + // we should probably add a "temp" metadata + // Look in the meta info as well?? + UMetaData * MetaData = ObjectPackage->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return false; + + if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + return true; + */ + } + + return false; +} + +void +FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC) +{ + if (!NewSMC || NewSMC->IsPendingKill()) + return; + + if (!InSMC || InSMC->IsPendingKill()) + return; + + // Copy properties to new actor + //UStaticMeshComponent* OtherSMC_NonConst = const_cast(StaticMeshComponent); + NewSMC->SetCollisionProfileName(InSMC->GetCollisionProfileName()); + NewSMC->SetCollisionEnabled(InSMC->GetCollisionEnabled()); + NewSMC->LightmassSettings = InSMC->LightmassSettings; + NewSMC->CastShadow = InSMC->CastShadow; + NewSMC->SetMobility(InSMC->Mobility); + + UBodySetup* InBodySetup = InSMC->GetBodySetup(); + UBodySetup* NewBodySetup = NewSMC->GetBodySetup(); + if (InBodySetup && NewBodySetup) + { + // Copy the BodySetup + NewBodySetup->CopyBodyPropertiesFrom(InBodySetup); + + // We need to recreate the physics mesh for the new body setup + NewBodySetup->InvalidatePhysicsData(); + NewBodySetup->CreatePhysicsMeshes(); + + // Only copy the physical material if it's different from the default one, + // As this was causing crashes on BakeToActors in some cases + if (GEngine != NULL && NewBodySetup->GetPhysMaterial() != GEngine->DefaultPhysMaterial) + NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial()); + } + + if (NewActor && !NewActor->IsPendingKill()) + NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame); + + NewSMC->SetVisibility(InSMC->IsVisible()); + + // TODO: + // // Reapply the uproperties modified by attributes on the new component + // FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO); + + // The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another + UClass* ComponentClass = InSMC->GetClass(); + if (ComponentClass != NewSMC->GetClass()) + { + HOUDINI_LOG_WARNING( + TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), + *(ComponentClass->GetName()), + *(NewSMC->GetClass()->GetName())); + + NewSMC->PostEditChange(); + return; + } + + TSet SourceUCSModifiedProperties; + InSMC->GetUCSModifiedProperties(SourceUCSModifiedProperties); + + AActor* SourceActor = InSMC->GetOwner(); + if (!IsValid(SourceActor)) + { + NewSMC->PostEditChange(); + return; + } + + TArray ModifiedObjects; + const EditorUtilities::FCopyOptions Options(EditorUtilities::ECopyOptions::CallPostEditChangeProperty); + // Copy component properties + for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) + { + const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); + const bool bIsIdentical = Property->Identical_InContainer( InSMC, NewSMC ); + const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); + const bool bIsTransform = + Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); + + // auto SourceComponentIsRoot = [&]() + // { + // USceneComponent* RootComponent = SourceActor->GetRootComponent(); + // if (InSMC == RootComponent) + // { + // return true; + // } + // return false; + // }; + + // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + // && ( !bIsTransform || !SourceComponentIsRoot() ) ) + if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + && !bIsTransform ) + { + // const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) + // && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); + const bool bIsSafeToCopy = true; + if( bIsSafeToCopy ) + { + if (!Options.CanCopyProperty(*Property, *SourceActor)) + { + continue; + } + + if( !ModifiedObjects.Contains(NewSMC) ) + { + NewSMC->SetFlags(RF_Transactional); + NewSMC->Modify(); + ModifiedObjects.Add(NewSMC); + } + + if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) + { + // @todo simulate: Should we be calling this on the component instead? + NewActor->PreEditChange( Property ); + } + + EditorUtilities::CopySingleProperty(InSMC, NewSMC, Property); + + if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) + { + FPropertyChangedEvent PropertyChangedEvent( Property ); + NewActor->PostEditChangeProperty( PropertyChangedEvent ); + } + } + } + } + + NewSMC->PostEditChange(); +}; + +bool +FHoudiniEngineBakeUtils::RemovePreviouslyBakedActor( + AActor* InNewBakedActor, + ULevel* InLevel, + const FHoudiniPackageParams& InPackageParams) +{ + // Remove a previous bake actor if it exists + for (auto & Actor : InLevel->Actors) + { + if (!Actor) + continue; + + if (Actor != InNewBakedActor && Actor->GetName() == InPackageParams.ObjectName) + { + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + Actor->RemoveFromRoot(); + Actor->ConditionalBeginDestroy(); + World->EditorDestroyActor(Actor, true); + + return true; + } + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::RemovePreviouslyBakedComponent(UActorComponent* InComponent) +{ + if (!IsValid(InComponent)) + return false; + + // Remove from its actor first + if (InComponent->GetOwner()) + InComponent->GetOwner()->RemoveOwnedComponent(InComponent); + + // Detach from its parent component if attached + USceneComponent* SceneComponent = Cast(InComponent); + if (IsValid(SceneComponent)) + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + InComponent->UnregisterComponent(); + InComponent->DestroyComponent(); + + return true; +} + +FName +FHoudiniEngineBakeUtils::GetOutputFolderPath(UObject* InOutputOwner) +{ + // Get an output folder path for PDG outputs generated by InOutputOwner. + // The folder path is: / + FString FolderName; + FName FolderDirName; + AActor* OuterActor = Cast(InOutputOwner); + if (OuterActor) + { + FolderName = OuterActor->GetActorLabel(); + FolderDirName = OuterActor->GetFolderPath(); + } + else + { + FolderName = InOutputOwner->GetName(); + } + if (!FolderDirName.IsNone()) + return FName(FString::Printf(TEXT("%s/%s"), *FolderDirName.ToString(), *FolderName)); + else + return FName(FolderName); +} + +void +FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique) +{ + FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); + + const FSoftObjectPath OldPath = FSoftObjectPath(InAsset); + + FString NewName; + if (bMakeUniqueIfNotUnique) + NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetPackage(), InAsset->GetClass(), FName(InNewName), InAsset).ToString(); + else + NewName = InNewName; + + InAsset->Rename(*NewName); + + const FSoftObjectPath NewPath = FSoftObjectPath(InAsset); + if (OldPath != NewPath) + { + TArray RenameData; + RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); + AssetToolsModule.Get().RenameAssets(RenameData); + } +} + +void +FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique) +{ + if (!IsValid(InActor)) + return; + + FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); + + const FSoftObjectPath OldPath = FSoftObjectPath(InActor); + + FString NewName; + if (bMakeUniqueIfNotUnique) + NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName), InActor).ToString(); + else + NewName = InNewName; + + InActor->Rename(*NewName); + InActor->SetActorLabel(NewName); + + const FSoftObjectPath NewPath = FSoftObjectPath(InActor); + if (OldPath != NewPath) + { + TArray RenameData; + RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); + AssetToolsModule.Get().RenameAssets(RenameData); + } +} + +bool +FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor( + AActor* InActor, + const FString& InNewName, + const FName& InFolderPath) +{ + if (!IsValid(InActor)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: InActor is null.")); + return false; + } + + if (InNewName.TrimStartAndEnd().IsEmpty()) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: A valid actor name must be specified.")); + return false; + } + + // Detach from parent + InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + // Rename + // InActor->Rename(*MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName)).ToString()); + // InActor->SetActorLabel(InNewName); + const bool bMakeUniqueIfNotUnique = true; + RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique); + + InActor->SetFolderPath(InFolderPath); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultIndex, + int32 InWorkResultObjectIndex, + bool bInReplaceActors, + bool bInReplaceAssets, + bool bInBakeToWorkResultActor, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!IsValid(InPDGAssetLink)) + return false; + + if (!IsValid(InNode)) + return false; + + if (!InNode->WorkResult.IsValidIndex(InWorkResultIndex)) + return false; + + FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultIndex]; + if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectIndex)) + return false; + + FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectIndex]; + TArray& Outputs = WorkResultObject.GetResultOutputs(); + if (Outputs.Num() == 0) + return true; + + AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor(); + if (!IsValid(WorkResultObjectActor)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObjectActor (%s) is null (unexpected since # Outputs > 0)"), *WorkResultObject.Name); + return false; + } + + // BakedActorsForWorkResultObject contains each actor that contains baked PDG results. Actors may + // appear in the array more than once if they have more than one baked result/component associated with + // them + TArray BakedActorsForWorkResultObject; + const FString HoudiniAssetName(WorkResultObject.Name); + + // Find the previous bake output for this work result object + FString Key; + InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key); + FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key); + + BakeHoudiniOutputsToActors( + Outputs, + BakedOutputContainer.BakedOutputs, + HoudiniAssetName, + WorkResultObjectActor->GetActorTransform(), + InPDGAssetLink->BakeFolder, + InPDGAssetLink->GetTemporaryCookFolder(), + bInReplaceActors, + bInReplaceAssets, + BakedActorsForWorkResultObject, + OutPackagesToSave, + OutBakeStats, + InOutputTypesToBake, + InInstancerComponentTypesToBake, + bInBakeToWorkResultActor ? WorkResultObjectActor : nullptr, + InFallbackWorldOutlinerFolder); + + // Set the PDG indices on the output baked actor entries + if (BakedActorsForWorkResultObject.Num() > 0) + { + for (FHoudiniEngineBakedActor& BakedActorEntry : BakedActorsForWorkResultObject) + { + BakedActorEntry.PDGWorkResultIndex = InWorkResultIndex; + BakedActorEntry.PDGWorkResultObjectIndex = InWorkResultObjectIndex; + } + } + + // If anything was baked to WorkResultObjectActor, detach it from its parent + if (bInBakeToWorkResultActor) + { + FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); + // if we re-used the temp actor as a bake actor, then remove its temp outputs + WorkResultObject.DestroyResultOutputs(); + AActor* WROActor = OutputActorOwner.GetOutputActor(); + if (WROActor) + { + const FHoudiniEngineBakedActor* BakedActorEntry = BakedActorsForWorkResultObject.FindByPredicate([WROActor](const FHoudiniEngineBakedActor& Entry) + { + return Entry.Actor == WROActor; + }); + if (BakedActorEntry) + { + OutputActorOwner.SetOutputActor(nullptr); + const FString OldActorPath = FSoftObjectPath(WROActor).ToString(); + DetachAndRenameBakedPDGOutputActor( + WROActor, BakedActorEntry->ActorBakeName.ToString(), BakedActorEntry->WorldOutlinerFolder); + const FString NewActorPath = FSoftObjectPath(WROActor).ToString(); + if (OldActorPath != NewActorPath) + { + // Fix cached string reference in baked outputs to WROActor + for (FHoudiniBakedOutput& BakedOutput : BakedOutputContainer.BakedOutputs) + { + for (auto& Entry : BakedOutput.BakedOutputObjects) + { + if (Entry.Value.Actor == OldActorPath) + Entry.Value.Actor = NewActorPath; + } + } + } + } + else + { + OutputActorOwner.DestroyOutputActor(); + } + } + } + OutBakedActors.Append(BakedActorsForWorkResultObject); + return true; +} + + +bool +FHoudiniEngineBakeUtils::BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultId, + const FString& InWorkResultObjectName) +{ + if (!IsValid(InPDGAssetLink)) + return false; + + if (!IsValid(InNode)) + return false; + + // Find the work result index and work result object index + const int32 WorkResultIndex = InNode->WorkResult.IndexOfByPredicate([InWorkResultId](const FTOPWorkResult& Entry) + { + return Entry.WorkItemID == InWorkResultId; + }); + if (!InNode->WorkResult.IsValidIndex(WorkResultIndex)) + return false; + FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIndex]; + const int32 WorkResultObjectIndex = WorkResult.ResultObjects.IndexOfByPredicate([InWorkResultObjectName](const FTOPWorkResultObject& Entry) + { + return Entry.Name.Equals(InWorkResultObjectName); + }); + if (!WorkResult.ResultObjects.IsValidIndex(WorkResultObjectIndex)) + return false; + + // Determine the output world outliner folder path via the PDG asset link's + // owner's folder path and name + UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); + if (!PDGOwner) + PDGOwner = InPDGAssetLink->GetOuter(); + const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); + + // Determine the actor/package replacement settings + const bool bBakeBlueprints = InPDGAssetLink->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToBlueprint; + const bool bReplaceActors = !bBakeBlueprints && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + // Determine the output types to bake: don't bake landscapes in blueprint baking mode + TArray OutputTypesToBake; + TArray InstancerComponentTypesToBake; + if (bBakeBlueprints) + { + OutputTypesToBake.Add(EHoudiniOutputType::Mesh); + OutputTypesToBake.Add(EHoudiniOutputType::Instancer); + OutputTypesToBake.Add(EHoudiniOutputType::Curve); + + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); + } + + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + TArray BakedActors; + + bool bSuccess = BakePDGWorkResultObject( + InPDGAssetLink, + InNode, + WorkResultIndex, + WorkResultObjectIndex, + bReplaceActors, + bReplaceAssets, + !bBakeBlueprints, + BakedActors, + PackagesToSave, + BakeStats, + OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, + InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, + FallbackWorldOutlinerFolderPath.ToString() + ); + + // Recenter and select the baked actors + if (GEditor && BakedActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : BakedActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (InPDGAssetLink->bRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && BakedActors.Num() > 0) + GEditor->NoteSelectionChange(); + + if (bBakeBlueprints && bSuccess) + { + TArray Blueprints; + bSuccess = BakeBlueprintsFromBakedActors( + BakedActors, + InPDGAssetLink->bRecenterBakedActors, + bReplaceAssets, + InPDGAssetLink->AssetName, + InPDGAssetLink->BakeFolder, + nullptr, + &InNode->GetBakedWorkResultObjectsOutputs(), + Blueprints, + PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + } + + SaveBakedPackages(PackagesToSave); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + return bSuccess; +} + +void +FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultId, + const FString& InWorkResultObjectName) +{ + if (!IsValid(InPDGAssetLink)) + return; + + if (!InPDGAssetLink->bBakeAfterWorkResultObjectLoaded) + return; + + BakePDGWorkResultObject( + InPDGAssetLink, + InNode, + InWorkResultId, + InWorkResultObjectName); +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInBakeForBlueprint, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNode)) + return false; + + // Determine the output world outliner folder path via the PDG asset link's + // owner's folder path and name + UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); + if (!PDGOwner) + PDGOwner = InPDGAssetLink->GetOuter(); + const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); + + // Determine the actor/package replacement settings + const bool bReplaceActors = !bInBakeForBlueprint && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + // Determine the output types to bake: don't bake landscapes in blueprint baking mode + TArray OutputTypesToBake; + TArray InstancerComponentTypesToBake; + if (bInBakeForBlueprint) + { + OutputTypesToBake.Add(EHoudiniOutputType::Mesh); + OutputTypesToBake.Add(EHoudiniOutputType::Instancer); + OutputTypesToBake.Add(EHoudiniOutputType::Curve); + + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); + } + + const int32 NumWorkResults = InNode->WorkResult.Num(); + for (int32 WorkResultIdx = 0; WorkResultIdx < NumWorkResults; ++WorkResultIdx) + { + FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIdx]; + const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); + for (int32 WorkResultObjectIdx = 0; WorkResultObjectIdx < NumWorkResultObjects; ++WorkResultObjectIdx) + { + BakePDGWorkResultObject( + InPDGAssetLink, + InNode, + WorkResultIdx, + WorkResultObjectIdx, + bReplaceActors, + bReplaceAssets, + !bInBakeForBlueprint, + OutBakedActors, + OutPackagesToSave, + OutBakeStats, + OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, + InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, + FallbackWorldOutlinerFolderPath.ToString() + ); + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + bool bInBakeForBlueprint, + TArray& BakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNetwork)) + return false; + + bool bSuccess = true; + for (UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, BakedActors, OutPackagesToSave, OutBakeStats); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + TArray BakedActors; + + const bool bBakeBlueprints = false; + + bool bSuccess = true; + switch(InPDGAssetLink->PDGBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) + { + if (!IsValid(Network)) + continue; + + for (UTOPNode* Node : Network->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + } + } + break; + case EPDGBakeSelectionOption::SelectedNetwork: + bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + case EPDGBakeSelectionOption::SelectedNode: + bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + } + + SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && BakedActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : BakedActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (InPDGAssetLink->bRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && BakedActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( + const TArray& InBakedActors, + bool bInRecenterBakedActors, + bool bInReplaceAssets, + const FString& InAssetName, + const FDirectoryPath& InBakeFolder, + TArray* const InNonPDGBakedOuputs, + TMap* const InPDGBakedOutputs, + TArray& OutBlueprints, + TArray& OutPackagesToSave) +{ + // // Clear selection + // if (GEditor) + // { + // GEditor->SelectNone(false, true); + // GEditor->NoteSelectionChange(); + // } + + // Iterate over the baked actors. An actor might appear multiple times if multiple OutputComponents were + // baked to the same actor, so keep track of actors we have already processed in BakedActorSet + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem); + TArray AssetsToReOpenEditors; + TSet BakedActorSet; + + for (const FHoudiniEngineBakedActor& Entry : InBakedActors) + { + AActor *Actor = Entry.Actor; + + if (!Actor || Actor->IsPendingKill()) + continue; + + if (BakedActorSet.Contains(Actor)) + continue; + + BakedActorSet.Add(Actor); + + UObject* Asset = nullptr; + + // Recenter the actor to its bounding box center + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Actor); + + // Create package for out Blueprint + FString BlueprintName; + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + FHoudiniOutputObjectIdentifier(), + InBakeFolder.Path, + Entry.ActorBakeName.ToString() + "_BP", + InAssetName, + AssetPackageReplaceMode); + + // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment + // is consistent with the bake counter + int32 BakeCounter = 0; + UBlueprint* InPreviousBlueprint = nullptr; + FHoudiniBakedOutputObject* BakedOutputObject = nullptr; + FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; + // Get the baked output object + if (Entry.PDGWorkResultIndex >= 0 && Entry.PDGWorkResultObjectIndex >= 0 && InPDGBakedOutputs) + { + const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkResultIndex, Entry.PDGWorkResultObjectIndex); + WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); + if (WorkResultObjectBakedOutput) + { + if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + } + else if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs) + { + if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs->IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = (*InNonPDGBakedOuputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + if (BakedOutputObject) + { + InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); + if (IsValid(InPreviousBlueprint)) + { + if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint)) + { + PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter); + } + } + } + + UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter); + + if (!Package || Package->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName())); + continue; + } + + if (!Package->IsFullyLoaded()) + Package->FullyLoad(); + + //Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false); + // Find existing asset first first (only relevant if we are in replacement mode). If the existing asset has a + // different base class than the incoming actor, we reparent the blueprint to the new base class before + // clearing the SCS graph and repopulating it from the temp actor. + Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName)); + if (IsValid(Asset)) + { + UBlueprint* Blueprint = Cast(Asset); + if (IsValid(Blueprint)) + { + if (Blueprint->GeneratedClass && Blueprint->GeneratedClass != Actor->GetClass()) + { + // Close editors opened on existing asset if applicable + if (Asset && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Asset, false) != nullptr) + { + AssetEditorSubsystem->CloseAllEditorsForAsset(Asset); + AssetsToReOpenEditors.Add(Asset); + } + + Blueprint->ParentClass = Actor->GetClass(); + + FBlueprintEditorUtils::RefreshAllNodes(Blueprint); + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + FKismetEditorUtilities::CompileBlueprint(Blueprint); + } + } + } + else if (Asset && Asset->IsPendingKill()) + { + // Rename to pending kill so that we can use the desired name + const FString AssetPendingKillName(BlueprintName + "_PENDING_KILL"); + // Asset->Rename(*MakeUniqueObjectNameIfNeeded(Package, UBlueprint::StaticClass(), AssetPendingKillName).ToString()); + RenameAsset(Asset, AssetPendingKillName, true); + Asset = nullptr; + } + + if (!Asset) + { + UBlueprintFactory* Factory = NewObject(); + Factory->ParentClass = Actor->GetClass(); + + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + Asset = AssetToolsModule.Get().CreateAsset( + BlueprintName, InBakeFolder.Path, + UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + } + + UBlueprint* Blueprint = Cast(Asset); + + if (!Blueprint || Blueprint->IsPendingKill()) + { + HOUDINI_LOG_WARNING( + TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."), + *(InBakeFolder.Path), *BlueprintName); + + continue; + } + + // Close editors opened on existing asset if applicable + if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr) + { + AssetEditorSubsystem->CloseAllEditorsForAsset(Blueprint); + AssetsToReOpenEditors.Add(Blueprint); + } + + // Record the blueprint as the previous bake blueprint + if (BakedOutputObject) + BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString(); + + OutBlueprints.Add(Blueprint); + + // Clear old Blueprint Node tree + { + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + + int32 NodeSize = SCS->GetAllNodes().Num(); + for (int32 n = NodeSize - 1; n >= 0; --n) + SCS->RemoveNode(SCS->GetAllNodes()[n]); + } + + FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint); + + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + World->EditorDestroyActor(Actor, true); + + // Save the created BP package. + Package->MarkPackageDirty(); + OutPackagesToSave.Add(Package); + } + + // Re-open asset editors for updated blueprints that were open in editors + if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0) + { + for (UObject* Asset : AssetsToReOpenEditors) + { + if (IsValid(Asset)) + { + AssetEditorSubsystem->OpenEditorForAsset(Asset); + } + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + TArray BPActors; + + if (!IsValid(InPDGAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InPDGAssetLink is null")); + return false; + } + + if (!IsValid(InNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InNode is null")); + return false; + } + + const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + // Bake PDG output to new actors + // bInBakeForBlueprint == true will skip landscapes and instanced actor components + const bool bInBakeForBlueprint = true; + TArray BakedActors; + bool bSuccess = BakePDGTOPNodeOutputsKeepActors( + InPDGAssetLink, + InNode, + bInBakeForBlueprint, + BakedActors, + OutPackagesToSave, + OutBakeStats + ); + + if (bSuccess) + { + bSuccess = BakeBlueprintsFromBakedActors( + BakedActors, + InPDGAssetLink->bRecenterBakedActors, + bReplaceAssets, + InPDGAssetLink->AssetName, + InPDGAssetLink->BakeFolder, + nullptr, + &InNode->GetBakedWorkResultObjectsOutputs(), + OutBlueprints, + OutPackagesToSave); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNetwork)) + return false; + + bool bSuccess = true; + for (UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, OutBlueprints, OutPackagesToSave, OutBakeStats); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + TArray Blueprints; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + bool bSuccess = true; + switch(InPDGAssetLink->PDGBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) + { + if (!IsValid(Network)) + continue; + + for (UTOPNode* Node : Network->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, Blueprints, PackagesToSave, BakeStats); + } + } + break; + case EPDGBakeSelectionOption::SelectedNetwork: + bSuccess &= BakePDGTOPNetworkBlueprints( + InPDGAssetLink, + InPDGAssetLink->GetSelectedTOPNetwork(), + Blueprints, + PackagesToSave, + BakeStats); + case EPDGBakeSelectionOption::SelectedNode: + bSuccess &= BakePDGTOPNodeBlueprints( + InPDGAssetLink, + InPDGAssetLink->GetSelectedTOPNode(), + Blueprints, + PackagesToSave, + BakeStats); + } + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + const FString& InLevelPath, + ULevel*& OutDesiredLevel, + UWorld*& OutDesiredWorld, + bool& OutCreatedPackage) +{ + OutDesiredLevel = nullptr; + OutDesiredWorld = nullptr; + if (InLevelPath.IsEmpty()) + { + OutDesiredWorld = GWorld; + OutDesiredLevel = GWorld->GetCurrentLevel(); + } + else + { + OutCreatedPackage = false; + + UWorld* FoundWorld = nullptr; + ULevel* FoundLevel = nullptr; + bool bActorInWorld = false; + if (FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + GWorld, + InLevelPath, + true, + FoundWorld, + FoundLevel, + OutCreatedPackage, + bActorInWorld)) + { + OutDesiredLevel = FoundLevel; + OutDesiredWorld = FoundWorld; + } + } + + return ((OutDesiredWorld != nullptr) && (OutDesiredLevel != nullptr)); +} + + +bool +FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( + const FString& InBakeActorName, + ULevel* InLevel, + AActor*& OutActor, + bool bInNoPendingKillActors, + bool bRenamePendingKillActor) +{ + OutActor = nullptr; + + if (!IsValid(InLevel)) + return false; + + UWorld* const World = InLevel->GetWorld(); + if (!IsValid(World)) + return false; + + // Look for an actor with the given name in the world + const FName BakeActorFName(InBakeActorName); + AActor* FoundActor = Cast(StaticFindObjectFast(AActor::StaticClass(), InLevel, BakeActorFName)); + // for (TActorIterator Iter(World, AActor::StaticClass(), EActorIteratorFlags::AllActors); Iter; ++Iter) + // { + // AActor* const Actor = *Iter; + // if (Actor->GetFName() == BakeActorFName && Actor->GetLevel() == InLevel) + // { + // // Found the actor + // FoundActor = Actor; + // break; + // } + // } + + // If we found an actor and it is pending kill, rename it and don't use it + if (FoundActor) + { + if (FoundActor->IsPendingKill()) + { + if (bRenamePendingKillActor) + { + // FoundActor->Rename( + // *MakeUniqueObjectNameIfNeeded( + // FoundActor->GetOuter(), + // FoundActor->GetClass(), + // FName(FoundActor->GetName() + "_Pending_Kill")).ToString()); + RenameAndRelabelActor( + FoundActor, + *MakeUniqueObjectNameIfNeeded( + FoundActor->GetOuter(), + FoundActor->GetClass(), + FName(FoundActor->GetName() + "_Pending_Kill"), + FoundActor).ToString(), + false); + } + if (bInNoPendingKillActors) + FoundActor = nullptr; + else + OutActor = FoundActor; + } + else + { + OutActor = FoundActor; + } + } + + return true; +} + +bool FHoudiniEngineBakeUtils::FindUnrealBakeActor( + const FHoudiniOutputObject& InOutputObject, + const FHoudiniBakedOutputObject& InBakedOutputObject, + const TArray& InAllBakedActors, + ULevel* InLevel, + FName InDefaultActorName, + bool bInReplaceActorBakeMode, + AActor* InFallbackActor, + AActor*& OutFoundActor, + bool& bOutHasBakeActorName, + FName& OutBakeActorName) +{ + // Determine desired actor name via unreal_output_actor, fallback to InDefaultActorName + OutBakeActorName = NAME_None; + OutFoundActor = nullptr; + bOutHasBakeActorName = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_ACTOR); + if (bOutHasBakeActorName) + { + const FString& BakeActorNameStr = InOutputObject.CachedAttributes[HAPI_UNREAL_ATTRIB_BAKE_ACTOR]; + if (BakeActorNameStr.IsEmpty()) + { + OutBakeActorName = NAME_None; + bOutHasBakeActorName = false; + } + else + { + OutBakeActorName = *BakeActorNameStr; + // We have a bake actor name, look for the actor + AActor* BakeNameActor = nullptr; + if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor)) + { + // Found an actor with that name, check that we "own" it (we created in during baking previously) + AActor* IncrementedBakedActor = nullptr; + for (const FHoudiniEngineBakedActor& BakedActor : InAllBakedActors) + { + if (!IsValid(BakedActor.Actor)) + continue; + if (BakedActor.Actor == BakeNameActor) + { + OutFoundActor = BakeNameActor; + break; + } + else if (!IncrementedBakedActor && BakedActor.ActorBakeName == OutBakeActorName) + { + // Found an actor we have baked named OutBakeActorName_# (incremental version of our desired name) + IncrementedBakedActor = BakedActor.Actor; + } + } + if (!OutFoundActor && IncrementedBakedActor) + OutFoundActor = IncrementedBakedActor; + } + } + } + + // If unreal_actor_name is not set, or is blank, fallback to InDefaultActorName + if (!bOutHasBakeActorName || (OutBakeActorName.IsNone() || OutBakeActorName.ToString().TrimStartAndEnd().IsEmpty())) + OutBakeActorName = InDefaultActorName; + + if (!OutFoundActor) + { + // If in replace mode, use previous bake actor if valid and in InLevel + if (bInReplaceActorBakeMode) + { + const FSoftObjectPath PrevActorPath(InBakedOutputObject.Actor); + const FString ActorPath = PrevActorPath.IsSubobject() + ? PrevActorPath.GetAssetPathString() + ":" + PrevActorPath.GetSubPathString() + : PrevActorPath.GetAssetPathString(); + const FString LevelPath = IsValid(InLevel) ? InLevel->GetPathName() : ""; + if (PrevActorPath.IsValid() && (LevelPath.IsEmpty() || ActorPath.StartsWith(LevelPath))) + OutFoundActor = InBakedOutputObject.GetActorIfValid(); + } + + // Fallback to InFallbackActor if valid and in InLevel + if (!OutFoundActor && IsValid(InFallbackActor) && (!InLevel || InFallbackActor->GetLevel() == InLevel)) + OutFoundActor = InFallbackActor; + } + + return true; +} + +AActor* +FHoudiniEngineBakeUtils::FindExistingActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + bCreatedPackage = false; + + // Try to Locate a previous actor + AActor* FoundActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); + if (FoundActor) + FoundActor->Destroy(); // nuke it! + + if (FoundActor) + { + // TODO: make sure that the found is actor is actually assigned to the level defined by package path. + // If the found actor is not from that level, it should be moved there. + + OutWorld = FoundActor->GetWorld(); + OutLevel = FoundActor->GetLevel(); + } + else + { + // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. + bool bActorInWorld = false; + const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + InWorld, + InPackagePath, + true, + OutWorld, + OutLevel, + bCreatedPackage, + bActorInWorld); + + if (!bResult) + { + return nullptr; + } + + if (!bActorInWorld) + { + // The OutLevel is not present in the current world which means we might + // still find the tile actor in OutWorld. + FoundActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); + } + } + + return FoundActor; +} + +bool +FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh( + UHoudiniAssetComponent* InHoudiniAssetComponent, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess, + bool& bOutNeedsReCook) +{ + if (!IsValid(InHoudiniAssetComponent)) + { + return false; + } + + // Handle proxies: if the output has any current proxies, first refine them + bOutNeedsReCook = false; + if (InHoudiniAssetComponent->HasAnyCurrentProxyOutput()) + { + bool bNeedsRebuildOrDelete; + bool bInvalidState; + const bool bCookedDataAvailable = InHoudiniAssetComponent->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bInvalidState); + + if (bCookedDataAvailable) + { + // Cook data is available, refine the mesh + AHoudiniAssetActor* HoudiniActor = Cast(InHoudiniAssetComponent->GetOwner()); + if (IsValid(HoudiniActor)) + { + FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ HoudiniActor }); + } + } + else if (!bNeedsRebuildOrDelete && !bInvalidState) + { + // A cook is needed: request the cook, but with no proxy and with a bake after cook + InHoudiniAssetComponent->SetNoProxyMeshNextCookRequested(true); + // Only + if (!InHoudiniAssetComponent->IsBakeAfterNextCookEnabled() || !InHoudiniAssetComponent->GetOnPostCookBakeDelegate().IsBound()) + { + InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess](UHoudiniAssetComponent* InHAC) { + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess); + }); + } + InHoudiniAssetComponent->MarkAsNeedCook(); + + bOutNeedsReCook = true; + + // The cook has to complete first (asynchronously) before the bake can happen + // The SetBakeAfterNextCookEnabled flag will result in a bake after cook + return false; + } + else + { + // The HAC is in an unsupported state + const EHoudiniAssetState AssetState = InHoudiniAssetComponent->GetAssetState(); + HOUDINI_LOG_ERROR(TEXT("Could not refine (in order to bake) %s, the asset is in an unsupported state: %s"), *(InHoudiniAssetComponent->GetPathName()), *(UEnum::GetValueAsString(AssetState))); + return false; + } + } + + return true; +} + +void +FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor) +{ + if (!IsValid(InActor)) + return; + + USceneComponent * const RootComponent = InActor->GetRootComponent(); + if (!IsValid(RootComponent)) + return; + + // If the root component does not have any child components, then there is nothing to recenter + if (RootComponent->GetNumChildrenComponents() <= 0) + return; + + const bool bOnlyCollidingComponents = false; + const bool bIncludeFromChildActors = true; + FVector Origin; + FVector BoxExtent; + InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); + + const FVector Delta = Origin - RootComponent->GetComponentLocation(); + // Actor->SetActorLocation(Origin); + RootComponent->SetWorldLocation(Origin); + + for (USceneComponent* SceneComponent : RootComponent->GetAttachChildren()) + { + if (!IsValid(SceneComponent)) + continue; + + SceneComponent->SetWorldLocation(SceneComponent->GetComponentLocation() - Delta); + } +} + +void +FHoudiniEngineBakeUtils::CenterActorsToBoundingBoxCenter(const TArray& InActors) +{ + for (AActor* Actor : InActors) + { + if (!IsValid(Actor)) + continue; + + CenterActorToBoundingBoxCenter(Actor); + } +} + +USceneComponent* +FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMissing, EComponentMobility::Type InMobilityIfCreated) +{ + USceneComponent* RootComponent = InActor->GetRootComponent(); + if (!IsValid(RootComponent)) + { + RootComponent = NewObject(InActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); + + // Change the creation method so the component is listed in the details panels + InActor->SetRootComponent(RootComponent); + InActor->AddInstanceComponent(RootComponent); + RootComponent->RegisterComponent(); + RootComponent->SetMobility(InMobilityIfCreated); + } + + return RootComponent; +} + +FName +FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed) +{ + if (IsValid(InObjectThatWouldBeRenamed)) + { + const FName CurrentName = InObjectThatWouldBeRenamed->GetFName(); + if (CurrentName == InName) + return InName; + + // Check if the prefix matches (without counter suffix) the new name + const FString CurrentNamePlainStr = CurrentName.GetPlainNameString(); + if (CurrentNamePlainStr == InName.ToString()) + return CurrentName; + } + + UObject* ExistingObject = nullptr; + if (InOuter == ANY_PACKAGE) + { + ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *InName.ToString()); + } + else + { + ExistingObject = StaticFindObjectFast(nullptr, InOuter, InName); + } + + if (ExistingObject) + return MakeUniqueObjectName(InOuter, InClass, InName); + return InName; +} + +FName +FHoudiniEngineBakeUtils::GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) +{ + const FString* FolderPathPtr = InOutputObject.CachedAttributes.Find(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER); + if (FolderPathPtr && !FolderPathPtr->IsEmpty()) + return FName(*FolderPathPtr); + else + return InDefaultFolder; +} + +bool +FHoudiniEngineBakeUtils::SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) +{ + if (!IsValid(InActor)) + return false; + + InActor->SetFolderPath(GetOutlinerFolderPath(InOutputObject, InDefaultFolder)); + return true; +} + +uint32 +FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInDestroyBakedComponent, + bool bInDestroyBakedInstancedActors, + bool bInDestroyBakedInstancedComponents) +{ + uint32 NumDeleted = 0; + + if (bInDestroyBakedComponent) + { + UActorComponent* Component = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (Component) + { + if (RemovePreviouslyBakedComponent(Component)) + { + InBakedOutputObject.BakedComponent = nullptr; + NumDeleted++; + } + } + } + + if (bInDestroyBakedInstancedActors) + { + for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) + { + const FSoftObjectPath ActorPath(ActorPathStr); + + if (!ActorPath.IsValid()) + continue; + + AActor* Actor = Cast(ActorPath.TryLoad()); + if (IsValid(Actor)) + { + UWorld* World = Actor->GetWorld(); + if (IsValid(World)) + { +#if WITH_EDITOR + World->EditorDestroyActor(Actor, true); +#else + World->DestroyActor(Actor); +#endif + NumDeleted++; + } + } + } + InBakedOutputObject.InstancedActors.Empty(); + } + + if (bInDestroyBakedInstancedComponents) + { + for (const FString& ComponentPathStr : InBakedOutputObject.InstancedComponents) + { + const FSoftObjectPath ComponentPath(ComponentPathStr); + + if (!ComponentPath.IsValid()) + continue; + + UActorComponent* Component = Cast(ComponentPath.TryLoad()); + if (IsValid(Component)) + { + if (RemovePreviouslyBakedComponent(Component)) + NumDeleted++; + } + } + InBakedOutputObject.InstancedComponents.Empty(); + } + + return NumDeleted; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h index 421ab59c7..bcfd41a5f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h @@ -1,621 +1,621 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#pragma once - -#include "HoudiniPDGAssetLink.h" -#include "HoudiniOutput.h" - -class UHoudiniAssetComponent; -class UHoudiniOutput; -class ALandscapeProxy; -class UStaticMesh; -class USplineComponent; -class UPackage; -class UWorld; -class AActor; -class UHoudiniSplineComponent; -class UStaticMeshComponent; -class UHoudiniPDGAssetLink; -class UTOPNetwork; -class UTOPNode; - -struct FHoudiniPackageParams; -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniEngineOutputStats; -struct FHoudiniBakedOutputObject; - -enum class EHoudiniLandscapeOutputBakeType : uint8; - -// An enum of the different types for instancer component/bake types -UENUM() -enum class EHoudiniInstancerComponentType : uint8 -{ - StaticMeshComponent, - InstancedStaticMeshComponent, - MeshSplitInstancerComponent, - InstancedActorComponent -}; - -// Helper struct to track actors created/used when baking, with -// the intended bake name (before making it unique), and their -// output index and output object identifier. -struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor -{ - FHoudiniEngineBakedActor(); - - FHoudiniEngineBakedActor( - AActor* InActor, - FName InActorBakeName, - FName InWorldOutlinerFolder, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - UObject* InBakedObject, - UObject* InSourceObject); - - // The actor that the baked output was associated with - AActor* Actor = nullptr; - - // The output index on the HAC for the baked object - int32 OutputIndex = INDEX_NONE; - - // The output object identifier for the baked object - FHoudiniOutputObjectIdentifier OutputObjectIdentifier; - - // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. - FName ActorBakeName = NAME_None; - - // The world outliner folder the actor is placed in - FName WorldOutlinerFolder = NAME_None; - - // The index of the work item when baking PDG - int32 PDGWorkResultIndex = INDEX_NONE; - - // The index of the work result object of the work item when baking PDG - int32 PDGWorkResultObjectIndex = INDEX_NONE; - - // The baked primary asset (such as static mesh) - UObject* BakedObject = nullptr; - - // The temp asset that was baked to BakedObject - UObject* SourceObject = nullptr; -}; - -struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils -{ -public: - - /** Bake static mesh. **/ - - /*static UStaticMesh * BakeStaticMesh( - UHoudiniAssetComponent * HoudiniAssetComponent, - UStaticMesh * InStaticMesh, - const FHoudiniPackageParams &PackageParams);*/ - - static ALandscapeProxy* BakeHeightfield( - ALandscapeProxy * InLandscapeProxy, - const FHoudiniPackageParams &PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType); - - static bool BakeCurve( - USplineComponent* InSplineComponent, - ULevel* InLevel, - const FHoudiniPackageParams &PackageParams, - AActor*& OutActor, - USplineComponent*& OutSplineComponent, - FName InOverrideFolderPath=NAME_None, - AActor* InActor=nullptr); - - static bool BakeCurve( - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - // const TArray& InAllBakedOutputs, - const FHoudiniPackageParams &PackageParams, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static AActor* BakeInputHoudiniCurveToActor( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PakcageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform); - - static UBlueprint* BakeInputHoudiniCurveToBlueprint( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PakcageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform); - - static UStaticMesh* BakeStaticMesh( - UStaticMesh * StaticMesh, - const FHoudiniPackageParams & PackageParams, - const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder); - - static bool BakeLandscape( - int32 InOutputIndex, - UHoudiniOutput* InOutput, - TMap& InBakedOutputObjects, - bool bInReplaceActors, - bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, - FHoudiniEngineOutputStats& BakeStats); - - static bool BakeLandscapeObject( - FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInReplaceActors, - bool bInReplaceAssets, - FHoudiniPackageParams& PackageParams, - TArray& WorldsToUpdate, - TArray& OutPackagesToUnload, - FHoudiniEngineOutputStats& BakeStats); - - static bool BakeInstancerOutputToActors( - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_ISMC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_IAC( - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave); - - static bool BakeInstancerOutputToActors_MSIC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_SMC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static UStaticMesh * DuplicateStaticMeshAndCreatePackageIfNeeded( - UStaticMesh * InStaticMesh, - UStaticMesh * InPreviousBakeStaticMesh, - const FHoudiniPackageParams &PackageParams, - const TArray& InParentOutputs, - const TArray& InCurrentBakedActors, - const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages); - - static UMaterial * DuplicateMaterialAndCreatePackage( - UMaterial * Material, - UMaterial* PreviousBakeMaterial, - const FString & SubMaterialName, - const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutCreatedPackages); - - static void ReplaceDuplicatedMaterialTextureSample( - UMaterialExpression * MaterialExpression, - UMaterialExpression* PreviousBakeMaterialExpression, - const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); - - static UTexture2D * DuplicateTextureAndCreatePackage( - UTexture2D * Texture, - UTexture2D* PreviousBakeTexture, - const FString & SubTextureName, - const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); - - // Bake a Houdini asset component (InHACToBake) based on the bInReplace and BakeOption arguments. - // Returns true if the underlying bake function (for example, BakeHoudiniActorToActors, returns true (or a valid UObject*)) - static bool BakeHoudiniAssetComponent( - UHoudiniAssetComponent* InHACToBake, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess); - - static bool BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets); - - static bool BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeHoudiniOutputsToActors( - const TArray& InOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FTransform& InParentTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); - - static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); - - static bool BakeStaticMeshOutputToActors( - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeHoudiniCurveOutputToActors( - UHoudiniOutput* Output, - TMap& InBakedOutputObjects, - const TArray& InAllBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeBlueprintsFromBakedActors( - const TArray& InBakedActors, - bool bInRecenterBakedActors, - bool bInReplaceAssets, - const FString& InAssetName, - const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOuputs, - TMap* const InPDGBakedOutputs, - TArray& OutBlueprints, - TArray& OutPackagesToSave); - - static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); - - static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, FHoudiniEngineOutputStats& InBakeStats, TArray& OutBlueprints, TArray& OutPackagesToSave); - - static bool CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint); - - static void AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const TCHAR * Key, - const TCHAR * Value); - - static bool GetHoudiniGeneratedNameFromMetaInformation( - UPackage * Package, UObject * Object, FString & HoudiniName); - - static bool DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent); - - static void SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld = false); - - // Look for InObjectToFind among InOutputs. Return true if found and set OutOutputIndex and OutIdentifier. - static bool FindOutputObject( - const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); - - static bool IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC); - - static bool IsObjectTemporary( - UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); - - // Function used to copy properties from the source Static Mesh Component to the new (baked) one - static void CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC); - - // Finds the world/level indicated by the package path. - // If the level doesn't exists, it will be created. - // If InLevelPath is empty, outputs the editor world and current level - // Returns true if the world/level were found, false otherwise - static bool FindOrCreateDesiredLevelFromLevelPath( - const FString& InLevelPath, - ULevel*& OutDesiredLevel, - UWorld*& OutDesiredWorld, - bool& OutCreatedPackage); - - // Finds the actor indicated by InBakeActorName in InLevel. - // Returns false if any input was invalid (InLevel is invalid for example), true otherwise - // If an actor was found OutActor is set - // If bInNoPendingKillActors is true, then if an actor called InBakeActorName is found but is pending kill, then - // it is not set in OutActor - // If bRenamePendingKillActor is true, then if a pending kill actor call InBakeActorName is found it is renamed - // (uniquely) with a _Pending_Kill suffix (regardless of bInNoPendingKillActors). - static bool FindDesiredBakeActorFromBakeActorName( - const FString& InBakeActorName, - ULevel* InLevel, - AActor*& OutActor, - bool bInNoPendingKillActors=true, - bool bRenamePendingKillActor=true); - - // Helper that determines the desired bake actor name with unreal_bake_actor attribute, falling - // back to InDefaultActorName if the attribute is not set. - // If unreal_bake_actor is set, we look for such in InLevel, and use it *if* it is present in InAlLBakedOutputs. - // Otherwise if we are baking in replace mode, and the previous bake actor is available and in InLevel, return it - // as OutFoundActor. Otherwise return InFallbackActor as OutFoundActor. - // bOutHasBakeActorName indicates if the output has the unreal_bake_actor attribute set. - // OutFoundActor is the actor that was found (if one was found) - static bool FindUnrealBakeActor( - const FHoudiniOutputObject& InOutputObject, - const FHoudiniBakedOutputObject& InBakedOutputObject, - const TArray& InAllBakedActors, - ULevel* InLevel, - FName InDefaultActorName, - bool bInReplaceActorBakeMode, - AActor* InFallbackActor, - AActor*& OutFoundActor, - bool& bOutHasBakeActorName, - FName& OutBakeActorName); - - // Try to find an actor that we can use for baking. - // If the requested actor could not be found, then `OutWorld` and `OutLevel` - // should be used to spawn the new bake actor. - // @returns AActor* if found. Otherwise, returns nullptr. - static AActor* FindExistingActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - // Remove a previously baked actor - static bool RemovePreviouslyBakedActor( - AActor* InNewBakedActor, - ULevel* InLevel, - const FHoudiniPackageParams& InPackageParams); - - static bool RemovePreviouslyBakedComponent(UActorComponent* InComponent); - - // Get the world outliner folder path for output generated by InOutputOwner - static FName GetOutputFolderPath(UObject* InOutputOwner); - - static void RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); - - // Helper function for renaming and relabelling an actor - static void RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); - - // Start: PDG Baking - - // Detach InActor from its parent, and rename to InNewName (attaches a numeric suffix to make it unique via - // MakeUniqueObjectName). Place it in the world outliner folder InFolderPath. - static bool DetachAndRenameBakedPDGOutputActor(AActor* InActor, const FString& InNewName, const FName& InFolderPath); - - static bool BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultIndex, - int32 InWorkResultObjectIndex, - bool bInReplaceActors, - bool bInReplaceAssets, - bool bInBakeToWorkResultActor, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName); - - // Checks if auto-bake is enabled on InPDGAssetLink, and if it is, calls BakePDGWorkResultObject. - static void AutoBakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName); - - // Bake PDG output. This bakes all assets from all work items in the specified InNode (FTOPNode). - // It uses the existing output actors in the level, but breaks any links from these actors to the PDG link and - // moves the actors out of the parent Folder/ detaches from the parent PDG output actor. - static bool BakePDGTOPNodeOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInBakeForBlueprint, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes all assets from all work items in the specified TOP network. - // It uses the existing output actors in the level, but breaks any links - // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent - // PDG output actor. - static bool BakePDGTOPNetworkOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - bool bInBakeForBlueprint, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes assets from TOP networks and nodes according to - // InPDGAssetLink->PDGBakeSelectionOption. It uses the existing output actors in the level, but breaks any links - // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent - // PDG output actor. - static bool BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink); - - // Bake PDG output. This bakes all supported assets from all work items in the specified InNode (FTOPNode). - // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from - // PDG output actors. - static bool BakePDGTOPNodeBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes all supported assets from all work items in the specified TOP network. - // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from - // PDG output actors. - static bool BakePDGTOPNetworkBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes assets from TOP networks and nodes according to - // InPDGAssetLink->PDGBakeSelectionOption. It duplicates the output actors and bakes them to blueprints. Assets - // that were baked are removed from PDG output actors. - static bool BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink); - - // End: PDG Baking - -protected: - - // Find the HGPO with matching identifier. Returns true if the HGPO was found. - static bool FindHGPO( - const FHoudiniOutputObjectIdentifier& InIdentifier, - const TArray& InHGPOs, - FHoudiniGeoPartObject const*& OutHGPO); - - // Set OutBakeName to the resolved output name of InMeshOutputObject / InObject. OutBakeName is set to the object's - // BakeName (the BakeName on the InMeshOutputObject, or if that is not set, the custom part name or finally the - // package name. - static void GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const FHoudiniOutputObject& InMeshOutputObject, - FString& OutBakeName); - - // Look for InObject in InAllOutputs. If found the function returns true and OutBakeName is set to the object's - // BakeName (the BakeName on the OutputObject, or if that is not set, the custom part name or finally the package - // name. - static bool GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const TArray& InAllOutputs, - FString& OutBakeName); - - // Checks if InHoudiniAssetComponent has any current proxy mesh. Refines if it possible. Returns true - // if baking can continue, false otherwise. If the component has a proxy, but no cook data, then false is - // returned, the component is set to recook without a proxy and with bake after cook, and bOutNeedsReCook is set - // to true. - // bInReplace and BakeOption represents the baking settings to use if a delayed bake (post-cook) needs to be triggered. - static bool CheckForAndRefineHoudiniProxyMesh( - UHoudiniAssetComponent* InHoudiniAssetComponent, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption BakeOption, - bool bInRemoveHACOutputOnSuccess, - bool& bOutNeedsReCook); - - // Position InActor at its bounding box center (keep components' world location) - static void CenterActorToBoundingBoxCenter(AActor* InActor); - - // Position each of the actors in InActors at its bounding box center (keep components' world location) - static void CenterActorsToBoundingBoxCenter(const TArray& InActors); - - // Helper to get or optionally create a RootComponent for an actor - static USceneComponent* GetActorRootComponent( - AActor* InActor, bool bCreateIfMissing=true, EComponentMobility::Type InMobilityIfCreated=EComponentMobility::Static); - - // Helper function to return a unique object name if the given is already in use - static FName MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed=nullptr); - - // Helper for getting the actor folder path for the world outliner, based unreal_bake_outliner_folder - static FName GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); - - // Helper for setting the actor folder path in the world outliner - static bool SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); - - // Helper for destroying previous bake components/actors - static uint32 DestroyPreviousBakeOutput( - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInDestroyBakedComponent, - bool bInDestroyBakedInstancedActors, - bool bInDestroyBakedInstancedComponents); -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once + +#include "HoudiniPDGAssetLink.h" +#include "HoudiniOutput.h" + +class UHoudiniAssetComponent; +class UHoudiniOutput; +class ALandscapeProxy; +class UStaticMesh; +class USplineComponent; +class UPackage; +class UWorld; +class AActor; +class UHoudiniSplineComponent; +class UStaticMeshComponent; +class UHoudiniPDGAssetLink; +class UTOPNetwork; +class UTOPNode; + +struct FHoudiniPackageParams; +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniEngineOutputStats; +struct FHoudiniBakedOutputObject; + +enum class EHoudiniLandscapeOutputBakeType : uint8; + +// An enum of the different types for instancer component/bake types +UENUM() +enum class EHoudiniInstancerComponentType : uint8 +{ + StaticMeshComponent, + InstancedStaticMeshComponent, + MeshSplitInstancerComponent, + InstancedActorComponent +}; + +// Helper struct to track actors created/used when baking, with +// the intended bake name (before making it unique), and their +// output index and output object identifier. +struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor +{ + FHoudiniEngineBakedActor(); + + FHoudiniEngineBakedActor( + AActor* InActor, + FName InActorBakeName, + FName InWorldOutlinerFolder, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + UObject* InBakedObject, + UObject* InSourceObject); + + // The actor that the baked output was associated with + AActor* Actor = nullptr; + + // The output index on the HAC for the baked object + int32 OutputIndex = INDEX_NONE; + + // The output object identifier for the baked object + FHoudiniOutputObjectIdentifier OutputObjectIdentifier; + + // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. + FName ActorBakeName = NAME_None; + + // The world outliner folder the actor is placed in + FName WorldOutlinerFolder = NAME_None; + + // The index of the work item when baking PDG + int32 PDGWorkResultIndex = INDEX_NONE; + + // The index of the work result object of the work item when baking PDG + int32 PDGWorkResultObjectIndex = INDEX_NONE; + + // The baked primary asset (such as static mesh) + UObject* BakedObject = nullptr; + + // The temp asset that was baked to BakedObject + UObject* SourceObject = nullptr; +}; + +struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils +{ +public: + + /** Bake static mesh. **/ + + /*static UStaticMesh * BakeStaticMesh( + UHoudiniAssetComponent * HoudiniAssetComponent, + UStaticMesh * InStaticMesh, + const FHoudiniPackageParams &PackageParams);*/ + + static ALandscapeProxy* BakeHeightfield( + ALandscapeProxy * InLandscapeProxy, + const FHoudiniPackageParams &PackageParams, + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType); + + static bool BakeCurve( + USplineComponent* InSplineComponent, + ULevel* InLevel, + const FHoudiniPackageParams &PackageParams, + AActor*& OutActor, + USplineComponent*& OutSplineComponent, + FName InOverrideFolderPath=NAME_None, + AActor* InActor=nullptr); + + static bool BakeCurve( + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + // const TArray& InAllBakedOutputs, + const FHoudiniPackageParams &PackageParams, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static AActor* BakeInputHoudiniCurveToActor( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PakcageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform); + + static UBlueprint* BakeInputHoudiniCurveToBlueprint( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PakcageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform); + + static UStaticMesh* BakeStaticMesh( + UStaticMesh * StaticMesh, + const FHoudiniPackageParams & PackageParams, + const TArray& InAllOutputs, + const FDirectoryPath& InTempCookFolder); + + static bool BakeLandscape( + int32 InOutputIndex, + UHoudiniOutput* InOutput, + TMap& InBakedOutputObjects, + bool bInReplaceActors, + bool bInReplaceAssets, + FString BakePath, + FString HoudiniAssetName, + FHoudiniEngineOutputStats& BakeStats); + + static bool BakeLandscapeObject( + FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInReplaceActors, + bool bInReplaceAssets, + FHoudiniPackageParams& PackageParams, + TArray& WorldsToUpdate, + TArray& OutPackagesToUnload, + FHoudiniEngineOutputStats& BakeStats); + + static bool BakeInstancerOutputToActors( + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_ISMC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_IAC( + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave); + + static bool BakeInstancerOutputToActors_MSIC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_SMC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static UStaticMesh * DuplicateStaticMeshAndCreatePackageIfNeeded( + UStaticMesh * InStaticMesh, + UStaticMesh * InPreviousBakeStaticMesh, + const FHoudiniPackageParams &PackageParams, + const TArray& InParentOutputs, + const TArray& InCurrentBakedActors, + const FString& InTemporaryCookFolder, + TArray & OutCreatedPackages); + + static UMaterial * DuplicateMaterialAndCreatePackage( + UMaterial * Material, + UMaterial* PreviousBakeMaterial, + const FString & SubMaterialName, + const FHoudiniPackageParams& ObjectPackageParams, + TArray & OutCreatedPackages); + + static void ReplaceDuplicatedMaterialTextureSample( + UMaterialExpression * MaterialExpression, + UMaterialExpression* PreviousBakeMaterialExpression, + const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages); + + static UTexture2D * DuplicateTextureAndCreatePackage( + UTexture2D * Texture, + UTexture2D* PreviousBakeTexture, + const FString & SubTextureName, + const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages); + + // Bake a Houdini asset component (InHACToBake) based on the bInReplace and BakeOption arguments. + // Returns true if the underlying bake function (for example, BakeHoudiniActorToActors, returns true (or a valid UObject*)) + static bool BakeHoudiniAssetComponent( + UHoudiniAssetComponent* InHACToBake, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess); + + static bool BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets); + + static bool BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeHoudiniOutputsToActors( + const TArray& InOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FTransform& InParentTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); + + static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); + + static bool BakeStaticMeshOutputToActors( + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeHoudiniCurveOutputToActors( + UHoudiniOutput* Output, + TMap& InBakedOutputObjects, + const TArray& InAllBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeBlueprintsFromBakedActors( + const TArray& InBakedActors, + bool bInRecenterBakedActors, + bool bInReplaceAssets, + const FString& InAssetName, + const FDirectoryPath& InBakeFolder, + TArray* const InNonPDGBakedOuputs, + TMap* const InPDGBakedOutputs, + TArray& OutBlueprints, + TArray& OutPackagesToSave); + + static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); + + static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, FHoudiniEngineOutputStats& InBakeStats, TArray& OutBlueprints, TArray& OutPackagesToSave); + + static bool CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint); + + static void AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const TCHAR * Key, + const TCHAR * Value); + + static bool GetHoudiniGeneratedNameFromMetaInformation( + UPackage * Package, UObject * Object, FString & HoudiniName); + + static bool DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent); + + static void SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld = false); + + // Look for InObjectToFind among InOutputs. Return true if found and set OutOutputIndex and OutIdentifier. + static bool FindOutputObject( + const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); + + static bool IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC); + + static bool IsObjectTemporary( + UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); + + // Function used to copy properties from the source Static Mesh Component to the new (baked) one + static void CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC); + + // Finds the world/level indicated by the package path. + // If the level doesn't exists, it will be created. + // If InLevelPath is empty, outputs the editor world and current level + // Returns true if the world/level were found, false otherwise + static bool FindOrCreateDesiredLevelFromLevelPath( + const FString& InLevelPath, + ULevel*& OutDesiredLevel, + UWorld*& OutDesiredWorld, + bool& OutCreatedPackage); + + // Finds the actor indicated by InBakeActorName in InLevel. + // Returns false if any input was invalid (InLevel is invalid for example), true otherwise + // If an actor was found OutActor is set + // If bInNoPendingKillActors is true, then if an actor called InBakeActorName is found but is pending kill, then + // it is not set in OutActor + // If bRenamePendingKillActor is true, then if a pending kill actor call InBakeActorName is found it is renamed + // (uniquely) with a _Pending_Kill suffix (regardless of bInNoPendingKillActors). + static bool FindDesiredBakeActorFromBakeActorName( + const FString& InBakeActorName, + ULevel* InLevel, + AActor*& OutActor, + bool bInNoPendingKillActors=true, + bool bRenamePendingKillActor=true); + + // Helper that determines the desired bake actor name with unreal_bake_actor attribute, falling + // back to InDefaultActorName if the attribute is not set. + // If unreal_bake_actor is set, we look for such in InLevel, and use it *if* it is present in InAlLBakedOutputs. + // Otherwise if we are baking in replace mode, and the previous bake actor is available and in InLevel, return it + // as OutFoundActor. Otherwise return InFallbackActor as OutFoundActor. + // bOutHasBakeActorName indicates if the output has the unreal_bake_actor attribute set. + // OutFoundActor is the actor that was found (if one was found) + static bool FindUnrealBakeActor( + const FHoudiniOutputObject& InOutputObject, + const FHoudiniBakedOutputObject& InBakedOutputObject, + const TArray& InAllBakedActors, + ULevel* InLevel, + FName InDefaultActorName, + bool bInReplaceActorBakeMode, + AActor* InFallbackActor, + AActor*& OutFoundActor, + bool& bOutHasBakeActorName, + FName& OutBakeActorName); + + // Try to find an actor that we can use for baking. + // If the requested actor could not be found, then `OutWorld` and `OutLevel` + // should be used to spawn the new bake actor. + // @returns AActor* if found. Otherwise, returns nullptr. + static AActor* FindExistingActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + // Remove a previously baked actor + static bool RemovePreviouslyBakedActor( + AActor* InNewBakedActor, + ULevel* InLevel, + const FHoudiniPackageParams& InPackageParams); + + static bool RemovePreviouslyBakedComponent(UActorComponent* InComponent); + + // Get the world outliner folder path for output generated by InOutputOwner + static FName GetOutputFolderPath(UObject* InOutputOwner); + + static void RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); + + // Helper function for renaming and relabelling an actor + static void RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); + + // Start: PDG Baking + + // Detach InActor from its parent, and rename to InNewName (attaches a numeric suffix to make it unique via + // MakeUniqueObjectName). Place it in the world outliner folder InFolderPath. + static bool DetachAndRenameBakedPDGOutputActor(AActor* InActor, const FString& InNewName, const FName& InFolderPath); + + static bool BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultIndex, + int32 InWorkResultObjectIndex, + bool bInReplaceActors, + bool bInReplaceAssets, + bool bInBakeToWorkResultActor, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultId, + const FString& InWorkResultObjectName); + + // Checks if auto-bake is enabled on InPDGAssetLink, and if it is, calls BakePDGWorkResultObject. + static void AutoBakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultId, + const FString& InWorkResultObjectName); + + // Bake PDG output. This bakes all assets from all work items in the specified InNode (FTOPNode). + // It uses the existing output actors in the level, but breaks any links from these actors to the PDG link and + // moves the actors out of the parent Folder/ detaches from the parent PDG output actor. + static bool BakePDGTOPNodeOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInBakeForBlueprint, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes all assets from all work items in the specified TOP network. + // It uses the existing output actors in the level, but breaks any links + // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent + // PDG output actor. + static bool BakePDGTOPNetworkOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + bool bInBakeForBlueprint, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes assets from TOP networks and nodes according to + // InPDGAssetLink->PDGBakeSelectionOption. It uses the existing output actors in the level, but breaks any links + // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent + // PDG output actor. + static bool BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink); + + // Bake PDG output. This bakes all supported assets from all work items in the specified InNode (FTOPNode). + // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from + // PDG output actors. + static bool BakePDGTOPNodeBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes all supported assets from all work items in the specified TOP network. + // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from + // PDG output actors. + static bool BakePDGTOPNetworkBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes assets from TOP networks and nodes according to + // InPDGAssetLink->PDGBakeSelectionOption. It duplicates the output actors and bakes them to blueprints. Assets + // that were baked are removed from PDG output actors. + static bool BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink); + + // End: PDG Baking + +protected: + + // Find the HGPO with matching identifier. Returns true if the HGPO was found. + static bool FindHGPO( + const FHoudiniOutputObjectIdentifier& InIdentifier, + const TArray& InHGPOs, + FHoudiniGeoPartObject const*& OutHGPO); + + // Set OutBakeName to the resolved output name of InMeshOutputObject / InObject. OutBakeName is set to the object's + // BakeName (the BakeName on the InMeshOutputObject, or if that is not set, the custom part name or finally the + // package name. + static void GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const FHoudiniOutputObject& InMeshOutputObject, + FString& OutBakeName); + + // Look for InObject in InAllOutputs. If found the function returns true and OutBakeName is set to the object's + // BakeName (the BakeName on the OutputObject, or if that is not set, the custom part name or finally the package + // name. + static bool GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const TArray& InAllOutputs, + FString& OutBakeName); + + // Checks if InHoudiniAssetComponent has any current proxy mesh. Refines if it possible. Returns true + // if baking can continue, false otherwise. If the component has a proxy, but no cook data, then false is + // returned, the component is set to recook without a proxy and with bake after cook, and bOutNeedsReCook is set + // to true. + // bInReplace and BakeOption represents the baking settings to use if a delayed bake (post-cook) needs to be triggered. + static bool CheckForAndRefineHoudiniProxyMesh( + UHoudiniAssetComponent* InHoudiniAssetComponent, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption BakeOption, + bool bInRemoveHACOutputOnSuccess, + bool& bOutNeedsReCook); + + // Position InActor at its bounding box center (keep components' world location) + static void CenterActorToBoundingBoxCenter(AActor* InActor); + + // Position each of the actors in InActors at its bounding box center (keep components' world location) + static void CenterActorsToBoundingBoxCenter(const TArray& InActors); + + // Helper to get or optionally create a RootComponent for an actor + static USceneComponent* GetActorRootComponent( + AActor* InActor, bool bCreateIfMissing=true, EComponentMobility::Type InMobilityIfCreated=EComponentMobility::Static); + + // Helper function to return a unique object name if the given is already in use + static FName MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed=nullptr); + + // Helper for getting the actor folder path for the world outliner, based unreal_bake_outliner_folder + static FName GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); + + // Helper for setting the actor folder path in the world outliner + static bool SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); + + // Helper for destroying previous bake components/actors + static uint32 DestroyPreviousBakeOutput( + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInDestroyBakedComponent, + bool bInDestroyBakedInstancedActors, + bool bInDestroyBakedInstancedComponents); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp index 4414ba03e..49b31f0bb 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp @@ -1,1695 +1,1695 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineCommands.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniOutput.h" - -#include "DesktopPlatformModule.h" -#include "Interfaces/IMainFrameModule.h" -#include "EditorDirectories.h" -#include "Misc/ScopedSlowTask.h" -#include "Async/Async.h" -#include "FileHelpers.h" -#include "AssetRegistryModule.h" -#include "Engine/ObjectLibrary.h" -#include "ObjectTools.h" -#include "CoreGlobals.h" -#include "HoudiniEngineOutputStats.h" -#include "Misc/FeedbackContext.h" -#include "HAL/FileManager.h" -#include "Modules/ModuleManager.h" -#include "ISettingsModule.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FDelegateHandle FHoudiniEngineCommands::OnPostSaveWorldRefineProxyMeshesHandle = FDelegateHandle(); - -void -FHoudiniEngineCommands::RegisterCommands() -{ - UI_COMMAND(_CreateSession, "Create Session", "Creates a new Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_ConnectSession, "Connect Session", "Connects to an existing Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_StopSession, "Stop Session", "Stops the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RestartSession, "Restart Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_OpenSessionSync, "Open Houdini Session Sync", "Opens Houdini with Session Sync and connect to it.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_CloseSessionSync, "Close Houdini Session Sync", "Close the Session Sync Houdini.", EUserInterfaceActionType::Button, FInputChord()); - - // Viewport Sync - UI_COMMAND(_ViewportSyncNone, "Disabled", "Do not sync viewports.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncUnreal, "Sync Unreal to Houdini.", "Sync the Unreal viewport to Houdini's.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncHoudini, "Sync Houdini to Unreal", "Sync the Houdini viewport to Unreal's.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncBoth, "Both", "Sync both Unreal and Houdini's viewport.", EUserInterfaceActionType::Check, FInputChord()); - - // PDG Import Commandlet - UI_COMMAND(_StartPDGCommandlet, "Start Async Importer", "Start the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_StopPDGCommandlet, "Stop Async Importer", "Stops the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_IsPDGCommandletEnabled, "Enable Async Importer", "Enables the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Check, FInputChord()); - - UI_COMMAND(_InstallInfo, "Installation Info", "Display information on the current Houdini Engine installation", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_PluginSettings, "PluginSettings", "Displays the Houdini Engine plugin settings", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_OpenInHoudini, "Open scene in Houdini", "Opens the current Houdini scene in Houdini.", EUserInterfaceActionType::Button, FInputChord(EKeys::O, EModifierKey::Control | EModifierKey::Alt)); - UI_COMMAND(_SaveHIPFile, "Save Houdini scene (HIP)", "Saves a .hip file of the current Houdini scene.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_OnlineDoc, "Online Documentation", "Go to the plugin's online documentation.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_OnlineForum, "Online Forum", "Go to the plugin's online forum.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_ReportBug, "Report a bug", "Report a bug for Houdini Engine for Unreal plugin.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_CookAll, "Recook All", "Recooks all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_CookSelected, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::C, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_RebuildAll, "Rebuild All", "Rebuilds all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RebuildSelected, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::R, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_BakeAll, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_BakeSelected, "Bake And Replace Selection", "Bakes and replaces with blueprints selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::B, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_RefineAll, "Refine all Houdini Proxy Meshes To Static Meshes", "Builds and replaces all Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RefineSelected, "Refine selected Houdini Proxy Meshes To Static Meshes", "Builds and replaces selected Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_CleanUpTempFolder, "Clean Houdini Engine Temp Folder", "Deletes the unused temporary files in the Temporary Cook Folder.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_PauseAssetCooking, "Pause Houdini Engine Cooking", "When activated, prevents Houdini Engine from cooking assets until unpaused.", EUserInterfaceActionType::Check, FInputChord(EKeys::P, EModifierKey::Control | EModifierKey::Alt)); -} - -void -FHoudiniEngineCommands::SaveHIPFile() -{ - IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get(); - if (!DesktopPlatform || !FHoudiniEngineUtils::IsInitialized()) - return; - - TArray< FString > SaveFilenames; - bool bSaved = false; - void * ParentWindowWindowHandle = NULL; - - IMainFrameModule & MainFrameModule = FModuleManager::LoadModuleChecked< IMainFrameModule >(TEXT("MainFrame")); - const TSharedPtr< SWindow > & MainFrameParentWindow = MainFrameModule.GetParentWindow(); - if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) - ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); - - bSaved = DesktopPlatform->SaveFileDialog( - ParentWindowWindowHandle, - NSLOCTEXT("SaveHIPFile", "SaveHIPFile", "Saves a .hip file of the current Houdini scene.").ToString(), - *(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT)), - TEXT(""), - TEXT("Houdini HIP file|*.hip"), - EFileDialogFlags::None, - SaveFilenames); - - if (bSaved && SaveFilenames.Num()) - { - // Add a slate notification - FString Notification = TEXT("Saving internal Houdini scene..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Saved Houdini scene to %s"), *SaveFilenames[0]); - - // Get first path. - std::string HIPPathConverted(TCHAR_TO_UTF8(*SaveFilenames[0])); - - // Save HIP file through Engine. - FHoudiniApi::SaveHIPFile(FHoudiniEngine::Get().GetSession(), HIPPathConverted.c_str(), false); - } -} - -void -FHoudiniEngineCommands::OpenInHoudini() -{ - if (!FHoudiniEngine::IsInitialized()) - return; - - // First, saves the current scene as a hip file - // Creates a proper temporary file name - FString UserTempPath = FPaths::CreateTempFilename( - FPlatformProcess::UserTempDir(), - TEXT("HoudiniEngine"), TEXT(".hip")); - - // Save HIP file through Engine. - std::string TempPathConverted(TCHAR_TO_UTF8(*UserTempPath)); - FHoudiniApi::SaveHIPFile( - FHoudiniEngine::Get().GetSession(), - TempPathConverted.c_str(), false); - - if (!FPaths::FileExists(UserTempPath)) - return; - - // Add a slate notification - FString Notification = TEXT("Opening scene in Houdini..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini.")); - - // Add quotes to the path to avoid issues with spaces - UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); - // Then open the hip file in Houdini - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); - FPlatformProcess::CreateProc( - *HoudiniLocation, - *UserTempPath, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - // Unfortunately, LaunchFileInDefaultExternalApplication doesn't seem to be working properly - //FPlatformProcess::LaunchFileInDefaultExternalApplication( UserTempPath.GetCharArray().GetData(), nullptr, ELaunchVerb::Open ); -} - -void -FHoudiniEngineCommands::ReportBug() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::ShowInstallInfo() -{ - // TODO -} - -void -FHoudiniEngineCommands::ShowPluginSettings() -{ - FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Plugins"), FName("HoudiniEngine")); -} - -void -FHoudiniEngineCommands::OnlineDocumentation() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_DOC_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::OnlineForum() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_FORUM_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::CleanUpTempFolder() -{ - // TODO: Improve me! slow now that we also have SM saved in the temp directory - // Due to the ref, we probably iterate a little too much, and should maybe do passes following the order of refs: - // mesh first, then materials, then textures. - // have a look at UWrangleContentCommandlet as well - - // Add a slate notification - FString Notification = TEXT("Cleaning up Houdini Engine temporary folder..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - GWarn->BeginSlowTask(LOCTEXT("CleanUpTemp", "Cleaning up the Houdini Engine Temp Folder"), false, false); - - // Get the default temp cook folder - FString TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - TArray TempCookFolders; - TempCookFolders.Add(FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder()); - for (TObjectIterator It; It; ++It) - { - FString CookFolder = It->TemporaryCookFolder.Path; - if (CookFolder.IsEmpty()) - continue; - - TempCookFolders.AddUnique(CookFolder); - } - - // The Asset registry will help us finding if the content of the asset is referenced - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - int32 DeletedCount = 0; - bool bDidDeleteAsset = true; - while (bDidDeleteAsset) - { - // To correctly clean the temp folder, we need to iterate multiple times, because some of the temp assets - // might be referenced by other temp assets.. (ie Textures are referenced by Materials) - // We'll stop looking for assets to delete when no deletion occured. - bDidDeleteAsset = false; - - TArray AssetDataList; - for (auto& TempFolder : TempCookFolders) - { - // The Object library will list all UObjects found in the TempFolder - auto ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, true); - ObjectLibrary->LoadAssetDataFromPath(TempFolder); - - // Get all the assets found in the TEMP folder - TArray CurrentAssetDataList; - ObjectLibrary->GetAssetDataList(CurrentAssetDataList); - - AssetDataList.Append(CurrentAssetDataList); - } - - // All the assets we're going to delete - TArray AssetDataToDelete; - for (FAssetData Data : AssetDataList) - { - UPackage* CurrentPackage = Data.GetPackage(); - if (!CurrentPackage || CurrentPackage->IsPendingKill()) - continue; - - // Do not try to delete the package if it's referenced anywhere - TArray ReferenceNames; - AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, EAssetRegistryDependencyType::All); - if (ReferenceNames.Num() > 0) - continue; - - bool bAssetDataSafeToDelete = true; - TArray AssetsInPackage; - AssetRegistryModule.Get().GetAssetsByPackageName(CurrentPackage->GetFName(), AssetsInPackage); - for (const auto& AssetInfo : AssetsInPackage) - { - // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) - UObject* AssetInPackage = AssetInfo.GetAsset(); - if (!AssetInPackage || AssetInPackage->IsPendingKill()) - continue; - - FReferencerInformationList ReferencesIncludingUndo; - bool bReferencedInMemoryOrUndoStack = IsReferenced(AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo); - if (!bReferencedInMemoryOrUndoStack) - continue; - - // We do have external references, check if the external references are in our ObjectToDelete list - // If they are, we can delete the asset because its references are going to be deleted as well. - for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences) - { - UObject* Outer = ExtRef.Referencer->GetOuter(); - if (!Outer || Outer->IsPendingKill()) - continue; - - bool bOuterFound = false; - for (auto DataToDelete : AssetDataToDelete) - { - if (DataToDelete.GetPackage() == Outer) - { - bOuterFound = true; - break; - } - else if (DataToDelete.GetAsset() == Outer) - { - bOuterFound = true; - break; - } - } - - // We have at least one reference that's not going to be deleted, we have to keep the asset - if (!bOuterFound) - { - bAssetDataSafeToDelete = false; - break; - } - } - } - - if (bAssetDataSafeToDelete) - AssetDataToDelete.Add(Data); - } - - // Nothing to delete - if (AssetDataToDelete.Num() <= 0) - break; - - int32 CurrentDeleted = ObjectTools::DeleteAssets(AssetDataToDelete, false); - - if (CurrentDeleted > 0) - { - DeletedCount += CurrentDeleted; - bDidDeleteAsset = true; - } - } - - - // Now, go through all the directories in the temp directories and delete all the empty ones - IFileManager& FM = IFileManager::Get(); - // Lambda that parses a directory recursively and returns true if it is empty - auto IsEmptyFolder = [&FM](FString PathToDeleteOnDisk) - { - struct FEmptyFolderVisitor : public IPlatformFile::FDirectoryVisitor - { - bool bIsEmpty; - FEmptyFolderVisitor() - : bIsEmpty(true) - { - } - - virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override - { - if (!bIsDirectory) - { - bIsEmpty = false; - return false; // abort searching - } - - return true; // continue searching - } - }; - - // Look for files on disk in case the folder contains things not tracked by the asset registry - FEmptyFolderVisitor EmptyFolderVisitor; - IFileManager::Get().IterateDirectoryRecursively(*PathToDeleteOnDisk, EmptyFolderVisitor); - return EmptyFolderVisitor.bIsEmpty; - }; - - // Iterates on all the temporary cook directories recursively, - // And keep not of all the empty directories - FString TempCookPathOnDisk; - TArray FoldersToDelete; - if (FPackageName::TryConvertLongPackageNameToFilename(TempCookFolder, TempCookPathOnDisk)) - { - FM.IterateDirectoryRecursively(*TempCookPathOnDisk, [&FM, &FoldersToDelete, &IsEmptyFolder](const TCHAR* InFilenameOrDirectory, const bool InIsDirectory) -> bool - { - // Skip Files - if (!InIsDirectory) - return true; - - FString CurrentDirectoryPath = FString(InFilenameOrDirectory); - if (IsEmptyFolder(CurrentDirectoryPath)) - FoldersToDelete.Add(CurrentDirectoryPath); - - // keep iterating - return true; - }); - } - - int32 DeletedDirectories = 0; - for (auto& FolderPath : FoldersToDelete) - { - FString PathToDelete; - if (!FPackageName::TryConvertFilenameToLongPackageName(FolderPath, PathToDelete)) - continue; - - if (IFileManager::Get().DeleteDirectory(*FolderPath, false, true)) - { - AssetRegistryModule.Get().RemovePath(PathToDelete); - DeletedDirectories++; - } - } - - GWarn->EndSlowTask(); - - // Add a slate notification - Notification = TEXT("Deleted ") + FString::FromInt(DeletedCount) + TEXT(" temporary files and ") + FString::FromInt(DeletedDirectories) + TEXT(" directories."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Deleted %d temporary files and %d directories."), DeletedCount, DeletedDirectories); -} - -void -FHoudiniEngineCommands::BakeAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Baking all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 BakedCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component")); - continue; - } - - if (!HoudiniAssetComponent->IsComponentValid()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - if (AssetName != "Default__HoudiniAssetActor") - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is invalid"), *AssetName); - continue; - } - - // If component is not cooking or instancing, we can bake blueprint. - if (HoudiniAssetComponent->IsInstantiatingOrCooking()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is actively instantiating or cooking"), *AssetName); - continue; - } - - bool bSuccess = false; - bool BakeToBlueprints = true; - if (BakeToBlueprints) - { - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // bSuccess = true; - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - if (bSuccess) - { - // Instantiate blueprints in component's level, then remove houdini asset actor - bSuccess = false; - ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); - if (IsValid(Level)) - { - UWorld* World = Level->GetWorld(); - if (IsValid(World)) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = Level; - FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); - for (UBlueprint* Blueprint : Blueprints) - { - if (!IsValid(Blueprint)) - continue; - World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); - } - - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - bSuccess = true; - } - } - } - } - else - { - // TODO: this used to have a way to not select in v1 - // if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent)) - // bSuccess = true; - if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, true, true)) - { - bSuccess = true; - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - } - } - - if (bSuccess) - BakedCount++; - } - - // Add a slate notification - Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); -} - -void -FHoudiniEngineCommands::PauseAssetCooking() -{ - // Revert the global flag - bool bCurrentCookingEnabled = !FHoudiniEngine::Get().IsCookingEnabled(); - FHoudiniEngine::Get().SetCookingEnabled(bCurrentCookingEnabled); - - // We need to refresh UI when pause cooking. Set refresh UI counter to be the number of current registered HACs. - if (!bCurrentCookingEnabled) - FHoudiniEngine::Get().SetUIRefreshCountWhenPauseCooking( FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount() ); - - // Add a slate notification - FString Notification = TEXT("Houdini Engine cooking paused"); - if (bCurrentCookingEnabled) - Notification = TEXT("Houdini Engine cooking resumed"); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - if (bCurrentCookingEnabled) - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking resumed.")); - else - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking paused.")); - - if (!bCurrentCookingEnabled) - return; - - /* - // If we are unpausing, tick each asset component to "update" them - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!")); - continue; - } - - HoudiniAssetComponent->StartHoudiniTicking(); - } - */ -} - -bool -FHoudiniEngineCommands::IsAssetCookingPaused() -{ - return !FHoudiniEngine::Get().IsCookingEnabled(); -} - -void -FHoudiniEngineCommands::RecookSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Cooking selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and cook the assets if they're in a valid state - int32 CookedCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedCook(); - CookedCount++; - } - - // Add a slate notification - Notification = TEXT("Re-cooking ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-cooking %d selected Houdini assets."), CookedCount); -} - -void -FHoudiniEngineCommands::RecookAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Cooking all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 CookedCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedCook(); - CookedCount++; - } - - // Add a slate notification - Notification = TEXT("Re-cooked ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-cooked %d Houdini assets in the current level."), CookedCount); -} - -void -FHoudiniEngineCommands::RebuildAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Re-building all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 RebuiltCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedRebuild(); - RebuiltCount++; - } - - // Add a slate notification - Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d Houdini assets in the current level."), RebuiltCount); -} - -void -FHoudiniEngineCommands::RebuildSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Rebuilding selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and rebuilds the assets if they're in a valid state - int32 RebuiltCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid()) - continue; - - HoudiniAssetComponent->MarkAsNeedRebuild(); - RebuiltCount++; - } - - // Add a slate notification - Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d selected Houdini assets."), RebuiltCount); -} - -void -FHoudiniEngineCommands::BakeSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Baking selected Houdini Asset Actors in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and rebuilds the assets if they're in a valid state - int32 BakedCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); - continue; - } - - if (!HoudiniAssetComponent->IsComponentValid()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - HOUDINI_LOG_ERROR(TEXT("Failed to export Houdini Asset: %s in the scene!"), *AssetName); - continue; - } - - // If component is not cooking or instancing, we can bake blueprint. - if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) - { - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // BakedCount++; - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // bSuccess = true; - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - if (bSuccess) - { - // Instantiate blueprints in component's level, then remove houdini asset actor - ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); - if (IsValid(Level)) - { - UWorld* World = Level->GetWorld(); - if (IsValid(World)) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = Level; - FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); - for (UBlueprint* Blueprint : Blueprints) - { - if (!IsValid(Blueprint)) - continue; - World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); - } - - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - BakedCount++; - } - } - } - } - } - - // Add a slate notification - Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); -} - -// Recentre HoudiniAsset actors' pivots to their input / cooked static-mesh average centre. -void FHoudiniEngineCommands::RecentreSelection() -{ - /* -#if WITH_EDITOR - //Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Recentering selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and cook the assets if they're in a valid state - int32 RecentreCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || !HoudiniAssetComponent->IsComponentValid()) - continue; - - // Get the average centre of all the created Static Meshes - FVector AverageBoundsCentre = FVector::ZeroVector; - int32 NumBounds = 0; - const FVector CurrentLocation = HoudiniAssetComponent->GetComponentLocation(); - { - //Check Static Meshes - TArray StaticMeshes; - StaticMeshes.Reserve(16); - HoudiniAssetComponent->GetAllUsedStaticMeshes(StaticMeshes); - - //Get average centre of all the static meshes. - for (const UStaticMesh* pMesh : StaticMeshes) - { - if (!pMesh) - continue; - - //to world space - AverageBoundsCentre += (pMesh->GetBounds().Origin + CurrentLocation); - NumBounds++; - } - } - - //Check Inputs - if (0 == NumBounds) - { - const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs; - for (const UHoudiniInput* pInput : AssetInputs) - { - if (!pInput || pInput->IsPendingKill()) - continue; - - // to world space - FBox Bounds = pInput->GetInputBounds(CurrentLocation); - if (Bounds.IsValid) - { - AverageBoundsCentre += Bounds.GetCenter(); - NumBounds++; - } - } - } - - //if we have more than one, get the average centre - if (NumBounds > 1) - { - AverageBoundsCentre /= (float)NumBounds; - } - - //if we need to move... - float fDist = FVector::DistSquared(CurrentLocation, AverageBoundsCentre); - if (NumBounds && fDist > 1.0f) - { - // Move actor to average centre and recook - // This will refresh the static mesh under the HoudiniAssestComponent ( undoing the translation ). - HoudiniAssetActor->SetActorLocation(AverageBoundsCentre, false, nullptr, ETeleportType::TeleportPhysics); - - // Recook now the houdini-static-mesh has a new origin - HoudiniAssetComponent->StartTaskAssetCookingManual(); - RecentreCount++; - } - } - - if (RecentreCount) - { - // UE4 Editor doesn't refresh the translation-handles until they are re-selected, confusing the user, deselect the objects. - GEditor->SelectNone(true, false); - } - - // Add a slate notification - Notification = TEXT("Re-centred ") + FString::FromInt(RecentreCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-centred %d selected Houdini assets."), RecentreCount); - -#endif //WITH_EDITOR - */ -} - -void -FHoudiniEngineCommands::OpenSessionSync() -{ - //if (!FHoudiniEngine::IsInitialized()) - // return; - - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - HAPI Not initialized")); - return; - } - - // Get the runtime settings to get the session/type and settings - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - - EHoudiniRuntimeSettingsSessionType SessionType = HoudiniRuntimeSettings->SessionType; - FString ServerPipeName = HoudiniRuntimeSettings->ServerPipeName; - int32 ServerPort = HoudiniRuntimeSettings->ServerPort; - - FString SessionSyncArgs = TEXT("-hess="); - if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe) - { - // Add the -hess=pipe:hapi argument - SessionSyncArgs += TEXT("pipe:") + ServerPipeName; - } - else if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_Socket) - { - // Add the -hess=port:9090 argument - SessionSyncArgs += TEXT("port:") + FString::FromInt(ServerPort); - } - else - { - // Invalid session type - HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - Invalid session type")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Opening Houdini Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Opening Houdini Session Sync.")); - - // Only launch Houdini in Session sync if we havent started it already! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - if (!FPlatformProcess::IsProcRunning(PreviousHESS)) - { - // Start houdini with the -hess commandline args - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); - FProcHandle HESSHandle = FPlatformProcess::CreateProc( - *HoudiniLocation, - *SessionSyncArgs, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - // Keep track of the SessionSync ProcHandle - FHoudiniEngine::Get().SetHESSProcHandle(HESSHandle); - } - - // Start an Async task to connect to Session Sync - Async(EAsyncExecution::TaskGraphMainThread, [SessionType, ServerPipeName, ServerPort]() - { - // Use a timeout to avoid waiting indefinitely for H to start in session sync mode - const double Timeout = 180.0; // 3min - const double StartTimestamp = FPlatformTime::Seconds(); - - FString ServerHost = TEXT("localhost"); - while (!FHoudiniEngine::Get().SessionSyncConnect(SessionType, ServerPipeName, ServerHost, ServerPort)) - { - // Houdini might not be done loading, sleep for one second - FPlatformProcess::Sleep(1); - - // Check for the timeout - if (FPlatformTime::Seconds() - StartTimestamp > Timeout) - { - // ... and a log message - HOUDINI_LOG_ERROR(TEXT("Failed to start SessionSync - Timeout...")); - return false; - } - } - - // Initialize HAPI with this session - if (!FHoudiniEngine::Get().InitializeHAPISession()) - { - FHoudiniEngine::Get().StopTicking(); - return false; - } - - // Notify all HACs that they need to instantiate in the new session - MarkAllHACsAsNeedInstantiation(); - - // Start ticking - FHoudiniEngine::Get().StartTicking(); - - // Add a slate notification - FString Notification = TEXT("Succesfully connected to Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Succesfully connected to Session Sync...")); - - return true; - }); -} - -void -FHoudiniEngineCommands::CloseSessionSync() -{ - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to stop Session Sync - HAPI Not initialized")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Stopping Houdini Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Stopping Houdini Session Sync.")); - - // Stop Houdini Session sync if it is still running! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - if (FPlatformProcess::IsProcRunning(PreviousHESS)) - { - FPlatformProcess::TerminateProc(PreviousHESS, true); - } -} - -void -FHoudiniEngineCommands::SetViewportSync(const int32& ViewportSync) -{ - if (ViewportSync == 1) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); - } - else if (ViewportSync == 2) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); - } - else if (ViewportSync == 3) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); - } - else - { - FHoudiniEngine::Get().SetSyncViewportEnabled(false); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); - } -} - -int32 -FHoudiniEngineCommands::GetViewportSync() -{ - if(!FHoudiniEngine::Get().IsSyncViewportEnabled()) - return 0; - - bool bSyncH = FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled(); - bool bSyncU = FHoudiniEngine::Get().IsSyncUnrealViewportEnabled(); - if (bSyncH && !bSyncU) - return 1; - else if (!bSyncH && bSyncU) - return 2; - else if (bSyncH && bSyncU) - return 3; - else - return 0; -} - -void -FHoudiniEngineCommands::RestartSession() -{ - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().RestartSession()) - return; - - // We've successfully restarted the Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::CreateSession() -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().CreateSession(HoudiniRuntimeSettings->SessionType)) - return; - - // We've successfully created the Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::ConnectSession() -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().ConnectSession(HoudiniRuntimeSettings->SessionType)) - return; - - // We've successfully connected to a Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation() -{ - // Notify all the HoudiniAssetComponents that they need to re instantiate themselves in the new Houdini engine session. - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedInstantiation(); - } -} - -bool -FHoudiniEngineCommands::IsSessionValid() -{ - return FHoudiniEngine::IsInitialized(); -} - -bool -FHoudiniEngineCommands::IsSessionSyncProcessValid() -{ - // Only launch Houdini in Session sync if we havent started it already! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - return FPlatformProcess::IsProcRunning(PreviousHESS); -} - -void -FHoudiniEngineCommands::StopSession() -{ - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine session stopped.")); - } -} - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE) -{ - // Get current world selection - TArray WorldSelection; - int32 NumSelectedHoudiniAssets = 0; - if (bOnlySelectedActors) - { - NumSelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (NumSelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - } - - // Add a slate notification - FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // First find the components that have meshes that we must refine - TArray ComponentsToRefine; - TArray ComponentsToCook; - // Components that would be candidates for refinement/cooking, but have errors - TArray SkippedComponents; - if (bOnlySelectedActors) - { - for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Index]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - } - else - { - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - } - - RefineTriagedHoudiniProxyMesehesToStaticMeshes( - ComponentsToRefine, - ComponentsToCook, - SkippedComponents, - bSilent, - bRefineAll, - bOnPreSaveWorld, - OnPreSaveWorld, - bOnPreBeginPIE - ); -} - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent) -{ - const bool bRefineAll = true; - const bool bOnPreSaveWorld = false; - UWorld* OnPreSaveWorld = nullptr; - const bool bOnPreBeginPIE = false; - - // First find the components that have meshes that we must refine - TArray ComponentsToRefine; - TArray ComponentsToCook; - // Components that would be candidates for refinement/cooking, but have errors - TArray SkippedComponents; - for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine) - { - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - - RefineTriagedHoudiniProxyMesehesToStaticMeshes( - ComponentsToRefine, - ComponentsToCook, - SkippedComponents, - bSilent, - bRefineAll, - bOnPreSaveWorld, - OnPreSaveWorld, - bOnPreBeginPIE - ); -} - -void -FHoudiniEngineCommands::StartPDGCommandlet() -{ - FHoudiniEngine::Get().StartPDGCommandlet(); -} - -void -FHoudiniEngineCommands::StopPDGCommandlet() -{ - FHoudiniEngine::Get().StopPDGCommandlet(); -} - -bool -FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected() -{ - return FHoudiniEngine::Get().IsPDGCommandletRunningOrConnected(); -} - -bool -FHoudiniEngineCommands::IsPDGCommandletEnabled() -{ - const UHoudiniRuntimeSettings* const Settings = GetDefault(); - if (IsValid(Settings)) - { - return Settings->bPDGAsyncCommandletImportEnabled; - } - - return false; -} - -bool -FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled) -{ - UHoudiniRuntimeSettings* const Settings = GetMutableDefault(); - if (IsValid(Settings)) - { - Settings->bPDGAsyncCommandletImportEnabled = InEnabled; - return true; - } - - return false; -} - -void -FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped) -{ - if (!InHAC || InHAC->IsPendingKill()) - return; - - // Make sure that the component's World and Owner are valid - AActor *Owner = InHAC->GetOwner(); - if (!Owner || Owner->IsPendingKill()) - return; - - UWorld *World = InHAC->GetWorld(); - if (!World || World->IsPendingKill()) - return; - - if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World) - return; - - // Check if we should consider this component for proxy mesh refinement based on its settings and - // flags passed to the function - if (bRefineAll || - (bOnPreSaveWorld && InHAC->IsProxyStaticMeshRefinementOnPreSaveWorldEnabled()) || - (bOnPreBeginPIE && InHAC->IsProxyStaticMeshRefinementOnPreBeginPIEEnabled())) - { - TArray ProxyMeshPackagesToSave; - TArray ComponentsWithProxiesToSave; - - if (InHAC->HasAnyCurrentProxyOutput()) - { - // Get the state of the asset and check if it is cooked - // If it is not cook, request a cook. We can only build the UStaticMesh - // if the data from the cook is available - // If the state is not pre-cook, or None (cooked), then the state is invalid, - // log an error and skip the component - bool bNeedsRebuildOrDelete = false; - bool bUnsupportedState = false; - const bool bCookedDataAvailable = InHAC->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bUnsupportedState); - if (bCookedDataAvailable) - { - OutToRefine.Add(InHAC); - ComponentsWithProxiesToSave.Add(InHAC); - } - else if (!bUnsupportedState && !bNeedsRebuildOrDelete) - { - InHAC->MarkAsNeedCook(); - // Force the output of the cook to be directly created as a UStaticMesh and not a proxy - InHAC->SetNoProxyMeshNextCookRequested(true); - OutToCook.Add(InHAC); - ComponentsWithProxiesToSave.Add(InHAC); - } - else - { - OutSkipped.Add(InHAC); - const EHoudiniAssetState AssetState = InHAC->GetAssetState(); - HOUDINI_LOG_ERROR(TEXT("Could not refine %s, the asset is in an unsupported state: %s"), *(InHAC->GetPathName()), *(UEnum::GetValueAsString(AssetState))); - } - } - else if (InHAC->HasAnyProxyOutput()) - { - // If the HAC has non-current proxies, destroy them - // TODO: Make this its own command? - const uint32 NumOutputs = InHAC->GetNumOutputs(); - for (uint32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = InHAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - TMap& OutputObjects = Output->GetOutputObjects(); - for (auto& CurrentPair : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; - if (!CurrentOutputObject.bProxyIsCurrent) - { - // The proxy is not current, delete it and its component - USceneComponent* FoundProxyComponent = Cast(CurrentOutputObject.ProxyComponent); - if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (FoundProxyComponent->GetOwner()) - FoundProxyComponent->GetOwner()->RemoveOwnedComponent(FoundProxyComponent); - - FoundProxyComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - FoundProxyComponent->UnregisterComponent(); - FoundProxyComponent->DestroyComponent(); - } - - UObject* ProxyObject = CurrentOutputObject.ProxyObject; - if (!ProxyObject || ProxyObject->IsPendingKill()) - continue; - - ProxyObject->MarkPendingKill(); - ProxyObject->MarkPackageDirty(); - UPackage* const Package = ProxyObject->GetOutermost();//GetPackage(); - if (IsValid(Package)) - ProxyMeshPackagesToSave.Add(Package); - } - } - } - } - - for (UHoudiniAssetComponent* const HAC : ComponentsWithProxiesToSave) - { - const uint32 NumOutputs = HAC->GetNumOutputs(); - for (uint32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - TMap& OutputObjects = Output->GetOutputObjects(); - for (auto& CurrentPair : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; - if (CurrentOutputObject.bProxyIsCurrent && CurrentOutputObject.ProxyObject) - { - UPackage* const Package = CurrentOutputObject.ProxyObject->GetOutermost();//GetPackage(); - if (IsValid(Package) && Package->IsDirty()) - ProxyMeshPackagesToSave.Add(Package); - } - } - } - } - - if (ProxyMeshPackagesToSave.Num() > 0) - { - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - FEditorFileUtils::PromptForCheckoutAndSave(ProxyMeshPackagesToSave, true, false); - } - } -} - -void -FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( - const TArray& InComponentsToRefine, - const TArray& InComponentsToCook, - const TArray& InSkippedComponents, - bool bInSilent, - bool bInRefineAll, - bool bInOnPreSaveWorld, - UWorld* InOnPreSaveWorld, - bool bInOnPrePIEBeginPlay) -{ - // Slate notification text - FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); - - const uint32 NumComponentsToCook = InComponentsToCook.Num(); - const uint32 NumComponentsToRefine = InComponentsToRefine.Num(); - const uint32 NumComponentsToProcess = NumComponentsToCook + NumComponentsToRefine; - TArray SuccessfulComponents; - uint32 NumSkippedComponents = InSkippedComponents.Num(); - if (NumComponentsToProcess > 0) - { - // The task progress pointer is potentially going to be shared with a background thread and tasks - // on the main thread, so make it thread safe - TSharedPtr TaskProgress = MakeShareable(new FSlowTask((float)NumComponentsToProcess, FText::FromString(Notification))); - TaskProgress->Initialize(); - if (!bInSilent) - TaskProgress->MakeDialog(/*bShowCancelButton=*/true); - - // Iterate over the components for which we can build UStaticMesh, and build the meshes - bool bCancelled = false; - for (uint32 ComponentIndex = 0; ComponentIndex < NumComponentsToRefine; ++ComponentIndex) - { - UHoudiniAssetComponent* HoudiniAssetComponent = InComponentsToRefine[ComponentIndex]; - TaskProgress->EnterProgressFrame(1.0f); - const bool bDestroyProxies = true; - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HoudiniAssetComponent, bDestroyProxies); - - SuccessfulComponents.Add(HoudiniAssetComponent); - - bCancelled = TaskProgress->ShouldCancel(); - if (bCancelled) - { - NumSkippedComponents += NumComponentsToRefine - ComponentIndex - 1; - break; - } - } - - if (NumComponentsToCook > 0 && !bCancelled) - { - // Now use an async task to check on the progress of the cooking components - Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { - RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); - }); - } - else - { - RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(NumComponentsToProcess, NumSkippedComponents, 0, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); - } - } -} - - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray& InComponentsToCook, TSharedPtr InTaskProgress, uint32 InNumComponentsToProcess, uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) -{ - // Copy to a double linked list so that we can loop through - // to check progress of each component and remove it easily - // if it has completed/failed - TDoubleLinkedList CookList; - for (UHoudiniAssetComponent *HAC : InComponentsToCook) - { - CookList.AddTail(HAC); - } - - // Add the successfully cooked compoments to the incoming successful components (previously refined) - TArray SuccessfulComponents(InSuccessfulComponents); - - bool bCancelled = false; - uint32 NumFailedToCook = 0; - while (CookList.Num() > 0 && !bCancelled) - { - TDoubleLinkedList::TDoubleLinkedListNode *Node = CookList.GetHead(); - while (Node && !bCancelled) - { - TDoubleLinkedList::TDoubleLinkedListNode *Next = Node->GetNextNode(); - UHoudiniAssetComponent* HAC = Node->GetValue(); - - if (HAC && !HAC->IsPendingKill()) - { - const EHoudiniAssetState State = HAC->GetAssetState(); - const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult(); - bool bUpdateProgress = false; - if (State == EHoudiniAssetState::None) - { - // Cooked, count as success, remove node - CookList.RemoveNode(Node); - SuccessfulComponents.Add(Node->GetValue()); - bUpdateProgress = true; - } - else if (ResultState != EHoudiniAssetStateResult::None && ResultState != EHoudiniAssetStateResult::Working) - { - // Failed, remove node - HOUDINI_LOG_ERROR(TEXT("Failed to cook %s to obtain static mesh."), *(HAC->GetPathName())); - CookList.RemoveNode(Node); - bUpdateProgress = true; - NumFailedToCook++; - } - - if (bUpdateProgress && InTaskProgress.IsValid()) - { - // Update progress only on the main thread, and check for cancellation request - bCancelled = Async(EAsyncExecution::TaskGraphMainThread, [InTaskProgress]() { - InTaskProgress->EnterProgressFrame(1.0f); - return InTaskProgress->ShouldCancel(); - }).Get(); - } - } - - Node = Next; - } - FPlatformProcess::Sleep(0.01f); - } - - if (bCancelled) - { - HOUDINI_LOG_WARNING(TEXT("Mesh refinement cancelled while waiting for %d components to cook."), CookList.Num()); - } - - // Cooking is done, or failed, display the notifications on the main thread - const uint32 NumRemaining = CookList.Num(); - Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InNumSkippedComponents, NumFailedToCook, NumRemaining, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { - RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InNumSkippedComponents + NumRemaining, NumFailedToCook, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); - }); -} - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) -{ - FString Notification; - if (InNumSkippedComponents + InNumFailedToCook > 0) - { - if (bCancelled) - { - Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); - } - else - { - Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); - } - FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_ERROR(TEXT("%s"), *Notification); - } - else if (InNumTotalComponents > 0) - { - Notification = TEXT("Done: Refining Houdini proxy meshes to static meshes."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_MESSAGE(TEXT("%s"), *Notification); - } - if (InTaskProgress) - { - InTaskProgress->Destroy(); - } - if (bOnPreSaveWorld && InSuccessfulComponents.Num() > 0) - { - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - - // Save the dirty static meshes in InSuccessfulComponents OnPostSaveWorld - // TODO: Remove? This may not be necessary now as we save all dirty temporary cook data in PostSaveWorld() already (Static Meshes, Materials...) - OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([InSuccessfulComponents, bOnPreSaveWorld, InOnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) { - if (bOnPreSaveWorld && InOnPreSaveWorld && InOnPreSaveWorld != InWorld) - return; - - RefineProxyMeshesHandleOnPostSaveWorld(InSuccessfulComponents, InSaveFlags, InWorld, bInSuccess); - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - }); - } -} - -void -FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) -{ - TArray PackagesToSave; - - for (UHoudiniAssetComponent* HAC : InSuccessfulComponents) - { - if (!HAC || HAC->IsPendingKill()) - continue; - - const int32 NumOutputs = HAC->GetNumOutputs(); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (auto &OutputObjectPair : Output->GetOutputObjects()) - { - UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) - continue; - - UStaticMesh *SM = Cast(Obj); - if (!SM) - continue; - - UPackage *Package = SM->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - } - } - - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineCommands.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniOutput.h" + +#include "DesktopPlatformModule.h" +#include "Interfaces/IMainFrameModule.h" +#include "EditorDirectories.h" +#include "Misc/ScopedSlowTask.h" +#include "Async/Async.h" +#include "FileHelpers.h" +#include "AssetRegistryModule.h" +#include "Engine/ObjectLibrary.h" +#include "ObjectTools.h" +#include "CoreGlobals.h" +#include "HoudiniEngineOutputStats.h" +#include "Misc/FeedbackContext.h" +#include "HAL/FileManager.h" +#include "Modules/ModuleManager.h" +#include "ISettingsModule.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FDelegateHandle FHoudiniEngineCommands::OnPostSaveWorldRefineProxyMeshesHandle = FDelegateHandle(); + +void +FHoudiniEngineCommands::RegisterCommands() +{ + UI_COMMAND(_CreateSession, "Create Session", "Creates a new Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_ConnectSession, "Connect Session", "Connects to an existing Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_StopSession, "Stop Session", "Stops the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RestartSession, "Restart Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_OpenSessionSync, "Open Houdini Session Sync", "Opens Houdini with Session Sync and connect to it.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_CloseSessionSync, "Close Houdini Session Sync", "Close the Session Sync Houdini.", EUserInterfaceActionType::Button, FInputChord()); + + // Viewport Sync + UI_COMMAND(_ViewportSyncNone, "Disabled", "Do not sync viewports.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncUnreal, "Sync Unreal to Houdini.", "Sync the Unreal viewport to Houdini's.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncHoudini, "Sync Houdini to Unreal", "Sync the Houdini viewport to Unreal's.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncBoth, "Both", "Sync both Unreal and Houdini's viewport.", EUserInterfaceActionType::Check, FInputChord()); + + // PDG Import Commandlet + UI_COMMAND(_StartPDGCommandlet, "Start Async Importer", "Start the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_StopPDGCommandlet, "Stop Async Importer", "Stops the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_IsPDGCommandletEnabled, "Enable Async Importer", "Enables the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Check, FInputChord()); + + UI_COMMAND(_InstallInfo, "Installation Info", "Display information on the current Houdini Engine installation", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_PluginSettings, "PluginSettings", "Displays the Houdini Engine plugin settings", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_OpenInHoudini, "Open scene in Houdini", "Opens the current Houdini scene in Houdini.", EUserInterfaceActionType::Button, FInputChord(EKeys::O, EModifierKey::Control | EModifierKey::Alt)); + UI_COMMAND(_SaveHIPFile, "Save Houdini scene (HIP)", "Saves a .hip file of the current Houdini scene.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_OnlineDoc, "Online Documentation", "Go to the plugin's online documentation.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_OnlineForum, "Online Forum", "Go to the plugin's online forum.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_ReportBug, "Report a bug", "Report a bug for Houdini Engine for Unreal plugin.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_CookAll, "Recook All", "Recooks all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_CookSelected, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::C, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_RebuildAll, "Rebuild All", "Rebuilds all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RebuildSelected, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::R, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_BakeAll, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_BakeSelected, "Bake And Replace Selection", "Bakes and replaces with blueprints selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::B, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_RefineAll, "Refine all Houdini Proxy Meshes To Static Meshes", "Builds and replaces all Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RefineSelected, "Refine selected Houdini Proxy Meshes To Static Meshes", "Builds and replaces selected Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_CleanUpTempFolder, "Clean Houdini Engine Temp Folder", "Deletes the unused temporary files in the Temporary Cook Folder.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_PauseAssetCooking, "Pause Houdini Engine Cooking", "When activated, prevents Houdini Engine from cooking assets until unpaused.", EUserInterfaceActionType::Check, FInputChord(EKeys::P, EModifierKey::Control | EModifierKey::Alt)); +} + +void +FHoudiniEngineCommands::SaveHIPFile() +{ + IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get(); + if (!DesktopPlatform || !FHoudiniEngineUtils::IsInitialized()) + return; + + TArray< FString > SaveFilenames; + bool bSaved = false; + void * ParentWindowWindowHandle = NULL; + + IMainFrameModule & MainFrameModule = FModuleManager::LoadModuleChecked< IMainFrameModule >(TEXT("MainFrame")); + const TSharedPtr< SWindow > & MainFrameParentWindow = MainFrameModule.GetParentWindow(); + if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) + ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); + + bSaved = DesktopPlatform->SaveFileDialog( + ParentWindowWindowHandle, + NSLOCTEXT("SaveHIPFile", "SaveHIPFile", "Saves a .hip file of the current Houdini scene.").ToString(), + *(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT)), + TEXT(""), + TEXT("Houdini HIP file|*.hip"), + EFileDialogFlags::None, + SaveFilenames); + + if (bSaved && SaveFilenames.Num()) + { + // Add a slate notification + FString Notification = TEXT("Saving internal Houdini scene..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Saved Houdini scene to %s"), *SaveFilenames[0]); + + // Get first path. + std::string HIPPathConverted(TCHAR_TO_UTF8(*SaveFilenames[0])); + + // Save HIP file through Engine. + FHoudiniApi::SaveHIPFile(FHoudiniEngine::Get().GetSession(), HIPPathConverted.c_str(), false); + } +} + +void +FHoudiniEngineCommands::OpenInHoudini() +{ + if (!FHoudiniEngine::IsInitialized()) + return; + + // First, saves the current scene as a hip file + // Creates a proper temporary file name + FString UserTempPath = FPaths::CreateTempFilename( + FPlatformProcess::UserTempDir(), + TEXT("HoudiniEngine"), TEXT(".hip")); + + // Save HIP file through Engine. + std::string TempPathConverted(TCHAR_TO_UTF8(*UserTempPath)); + FHoudiniApi::SaveHIPFile( + FHoudiniEngine::Get().GetSession(), + TempPathConverted.c_str(), false); + + if (!FPaths::FileExists(UserTempPath)) + return; + + // Add a slate notification + FString Notification = TEXT("Opening scene in Houdini..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini.")); + + // Add quotes to the path to avoid issues with spaces + UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); + // Then open the hip file in Houdini + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); + FPlatformProcess::CreateProc( + *HoudiniLocation, + *UserTempPath, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + // Unfortunately, LaunchFileInDefaultExternalApplication doesn't seem to be working properly + //FPlatformProcess::LaunchFileInDefaultExternalApplication( UserTempPath.GetCharArray().GetData(), nullptr, ELaunchVerb::Open ); +} + +void +FHoudiniEngineCommands::ReportBug() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::ShowInstallInfo() +{ + // TODO +} + +void +FHoudiniEngineCommands::ShowPluginSettings() +{ + FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Plugins"), FName("HoudiniEngine")); +} + +void +FHoudiniEngineCommands::OnlineDocumentation() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_DOC_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::OnlineForum() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_FORUM_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::CleanUpTempFolder() +{ + // TODO: Improve me! slow now that we also have SM saved in the temp directory + // Due to the ref, we probably iterate a little too much, and should maybe do passes following the order of refs: + // mesh first, then materials, then textures. + // have a look at UWrangleContentCommandlet as well + + // Add a slate notification + FString Notification = TEXT("Cleaning up Houdini Engine temporary folder..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + GWarn->BeginSlowTask(LOCTEXT("CleanUpTemp", "Cleaning up the Houdini Engine Temp Folder"), false, false); + + // Get the default temp cook folder + FString TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + TArray TempCookFolders; + TempCookFolders.Add(FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder()); + for (TObjectIterator It; It; ++It) + { + FString CookFolder = It->TemporaryCookFolder.Path; + if (CookFolder.IsEmpty()) + continue; + + TempCookFolders.AddUnique(CookFolder); + } + + // The Asset registry will help us finding if the content of the asset is referenced + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + int32 DeletedCount = 0; + bool bDidDeleteAsset = true; + while (bDidDeleteAsset) + { + // To correctly clean the temp folder, we need to iterate multiple times, because some of the temp assets + // might be referenced by other temp assets.. (ie Textures are referenced by Materials) + // We'll stop looking for assets to delete when no deletion occured. + bDidDeleteAsset = false; + + TArray AssetDataList; + for (auto& TempFolder : TempCookFolders) + { + // The Object library will list all UObjects found in the TempFolder + auto ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, true); + ObjectLibrary->LoadAssetDataFromPath(TempFolder); + + // Get all the assets found in the TEMP folder + TArray CurrentAssetDataList; + ObjectLibrary->GetAssetDataList(CurrentAssetDataList); + + AssetDataList.Append(CurrentAssetDataList); + } + + // All the assets we're going to delete + TArray AssetDataToDelete; + for (FAssetData Data : AssetDataList) + { + UPackage* CurrentPackage = Data.GetPackage(); + if (!CurrentPackage || CurrentPackage->IsPendingKill()) + continue; + + // Do not try to delete the package if it's referenced anywhere + TArray ReferenceNames; + AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, UE::AssetRegistry::EDependencyCategory::All); + if (ReferenceNames.Num() > 0) + continue; + + bool bAssetDataSafeToDelete = true; + TArray AssetsInPackage; + AssetRegistryModule.Get().GetAssetsByPackageName(CurrentPackage->GetFName(), AssetsInPackage); + for (const auto& AssetInfo : AssetsInPackage) + { + // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) + UObject* AssetInPackage = AssetInfo.GetAsset(); + if (!AssetInPackage || AssetInPackage->IsPendingKill()) + continue; + + FReferencerInformationList ReferencesIncludingUndo; + bool bReferencedInMemoryOrUndoStack = IsReferenced(AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo); + if (!bReferencedInMemoryOrUndoStack) + continue; + + // We do have external references, check if the external references are in our ObjectToDelete list + // If they are, we can delete the asset because its references are going to be deleted as well. + for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences) + { + UObject* Outer = ExtRef.Referencer->GetOuter(); + if (!Outer || Outer->IsPendingKill()) + continue; + + bool bOuterFound = false; + for (auto DataToDelete : AssetDataToDelete) + { + if (DataToDelete.GetPackage() == Outer) + { + bOuterFound = true; + break; + } + else if (DataToDelete.GetAsset() == Outer) + { + bOuterFound = true; + break; + } + } + + // We have at least one reference that's not going to be deleted, we have to keep the asset + if (!bOuterFound) + { + bAssetDataSafeToDelete = false; + break; + } + } + } + + if (bAssetDataSafeToDelete) + AssetDataToDelete.Add(Data); + } + + // Nothing to delete + if (AssetDataToDelete.Num() <= 0) + break; + + int32 CurrentDeleted = ObjectTools::DeleteAssets(AssetDataToDelete, false); + + if (CurrentDeleted > 0) + { + DeletedCount += CurrentDeleted; + bDidDeleteAsset = true; + } + } + + + // Now, go through all the directories in the temp directories and delete all the empty ones + IFileManager& FM = IFileManager::Get(); + // Lambda that parses a directory recursively and returns true if it is empty + auto IsEmptyFolder = [&FM](FString PathToDeleteOnDisk) + { + struct FEmptyFolderVisitor : public IPlatformFile::FDirectoryVisitor + { + bool bIsEmpty; + FEmptyFolderVisitor() + : bIsEmpty(true) + { + } + + virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override + { + if (!bIsDirectory) + { + bIsEmpty = false; + return false; // abort searching + } + + return true; // continue searching + } + }; + + // Look for files on disk in case the folder contains things not tracked by the asset registry + FEmptyFolderVisitor EmptyFolderVisitor; + IFileManager::Get().IterateDirectoryRecursively(*PathToDeleteOnDisk, EmptyFolderVisitor); + return EmptyFolderVisitor.bIsEmpty; + }; + + // Iterates on all the temporary cook directories recursively, + // And keep not of all the empty directories + FString TempCookPathOnDisk; + TArray FoldersToDelete; + if (FPackageName::TryConvertLongPackageNameToFilename(TempCookFolder, TempCookPathOnDisk)) + { + FM.IterateDirectoryRecursively(*TempCookPathOnDisk, [&FM, &FoldersToDelete, &IsEmptyFolder](const TCHAR* InFilenameOrDirectory, const bool InIsDirectory) -> bool + { + // Skip Files + if (!InIsDirectory) + return true; + + FString CurrentDirectoryPath = FString(InFilenameOrDirectory); + if (IsEmptyFolder(CurrentDirectoryPath)) + FoldersToDelete.Add(CurrentDirectoryPath); + + // keep iterating + return true; + }); + } + + int32 DeletedDirectories = 0; + for (auto& FolderPath : FoldersToDelete) + { + FString PathToDelete; + if (!FPackageName::TryConvertFilenameToLongPackageName(FolderPath, PathToDelete)) + continue; + + if (IFileManager::Get().DeleteDirectory(*FolderPath, false, true)) + { + AssetRegistryModule.Get().RemovePath(PathToDelete); + DeletedDirectories++; + } + } + + GWarn->EndSlowTask(); + + // Add a slate notification + Notification = TEXT("Deleted ") + FString::FromInt(DeletedCount) + TEXT(" temporary files and ") + FString::FromInt(DeletedDirectories) + TEXT(" directories."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Deleted %d temporary files and %d directories."), DeletedCount, DeletedDirectories); +} + +void +FHoudiniEngineCommands::BakeAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Baking all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 BakedCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component")); + continue; + } + + if (!HoudiniAssetComponent->IsComponentValid()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + if (AssetName != "Default__HoudiniAssetActor") + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is invalid"), *AssetName); + continue; + } + + // If component is not cooking or instancing, we can bake blueprint. + if (HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is actively instantiating or cooking"), *AssetName); + continue; + } + + bool bSuccess = false; + bool BakeToBlueprints = true; + if (BakeToBlueprints) + { + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // bSuccess = true; + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + if (bSuccess) + { + // Instantiate blueprints in component's level, then remove houdini asset actor + bSuccess = false; + ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); + if (IsValid(Level)) + { + UWorld* World = Level->GetWorld(); + if (IsValid(World)) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = Level; + FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); + for (UBlueprint* Blueprint : Blueprints) + { + if (!IsValid(Blueprint)) + continue; + World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); + } + + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + bSuccess = true; + } + } + } + } + else + { + // TODO: this used to have a way to not select in v1 + // if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent)) + // bSuccess = true; + if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, true, true)) + { + bSuccess = true; + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + } + } + + if (bSuccess) + BakedCount++; + } + + // Add a slate notification + Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); +} + +void +FHoudiniEngineCommands::PauseAssetCooking() +{ + // Revert the global flag + bool bCurrentCookingEnabled = !FHoudiniEngine::Get().IsCookingEnabled(); + FHoudiniEngine::Get().SetCookingEnabled(bCurrentCookingEnabled); + + // We need to refresh UI when pause cooking. Set refresh UI counter to be the number of current registered HACs. + if (!bCurrentCookingEnabled) + FHoudiniEngine::Get().SetUIRefreshCountWhenPauseCooking( FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount() ); + + // Add a slate notification + FString Notification = TEXT("Houdini Engine cooking paused"); + if (bCurrentCookingEnabled) + Notification = TEXT("Houdini Engine cooking resumed"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + if (bCurrentCookingEnabled) + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking resumed.")); + else + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking paused.")); + + if (!bCurrentCookingEnabled) + return; + + /* + // If we are unpausing, tick each asset component to "update" them + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!")); + continue; + } + + HoudiniAssetComponent->StartHoudiniTicking(); + } + */ +} + +bool +FHoudiniEngineCommands::IsAssetCookingPaused() +{ + return !FHoudiniEngine::Get().IsCookingEnabled(); +} + +void +FHoudiniEngineCommands::RecookSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Cooking selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and cook the assets if they're in a valid state + int32 CookedCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedCook(); + CookedCount++; + } + + // Add a slate notification + Notification = TEXT("Re-cooking ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-cooking %d selected Houdini assets."), CookedCount); +} + +void +FHoudiniEngineCommands::RecookAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Cooking all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 CookedCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedCook(); + CookedCount++; + } + + // Add a slate notification + Notification = TEXT("Re-cooked ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-cooked %d Houdini assets in the current level."), CookedCount); +} + +void +FHoudiniEngineCommands::RebuildAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Re-building all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 RebuiltCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedRebuild(); + RebuiltCount++; + } + + // Add a slate notification + Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d Houdini assets in the current level."), RebuiltCount); +} + +void +FHoudiniEngineCommands::RebuildSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Rebuilding selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and rebuilds the assets if they're in a valid state + int32 RebuiltCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid()) + continue; + + HoudiniAssetComponent->MarkAsNeedRebuild(); + RebuiltCount++; + } + + // Add a slate notification + Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d selected Houdini assets."), RebuiltCount); +} + +void +FHoudiniEngineCommands::BakeSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Baking selected Houdini Asset Actors in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and rebuilds the assets if they're in a valid state + int32 BakedCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); + continue; + } + + if (!HoudiniAssetComponent->IsComponentValid()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + HOUDINI_LOG_ERROR(TEXT("Failed to export Houdini Asset: %s in the scene!"), *AssetName); + continue; + } + + // If component is not cooking or instancing, we can bake blueprint. + if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // BakedCount++; + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // bSuccess = true; + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + if (bSuccess) + { + // Instantiate blueprints in component's level, then remove houdini asset actor + ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); + if (IsValid(Level)) + { + UWorld* World = Level->GetWorld(); + if (IsValid(World)) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = Level; + FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); + for (UBlueprint* Blueprint : Blueprints) + { + if (!IsValid(Blueprint)) + continue; + World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); + } + + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + BakedCount++; + } + } + } + } + } + + // Add a slate notification + Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); +} + +// Recentre HoudiniAsset actors' pivots to their input / cooked static-mesh average centre. +void FHoudiniEngineCommands::RecentreSelection() +{ + /* +#if WITH_EDITOR + //Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Recentering selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and cook the assets if they're in a valid state + int32 RecentreCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || !HoudiniAssetComponent->IsComponentValid()) + continue; + + // Get the average centre of all the created Static Meshes + FVector AverageBoundsCentre = FVector::ZeroVector; + int32 NumBounds = 0; + const FVector CurrentLocation = HoudiniAssetComponent->GetComponentLocation(); + { + //Check Static Meshes + TArray StaticMeshes; + StaticMeshes.Reserve(16); + HoudiniAssetComponent->GetAllUsedStaticMeshes(StaticMeshes); + + //Get average centre of all the static meshes. + for (const UStaticMesh* pMesh : StaticMeshes) + { + if (!pMesh) + continue; + + //to world space + AverageBoundsCentre += (pMesh->GetBounds().Origin + CurrentLocation); + NumBounds++; + } + } + + //Check Inputs + if (0 == NumBounds) + { + const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs; + for (const UHoudiniInput* pInput : AssetInputs) + { + if (!pInput || pInput->IsPendingKill()) + continue; + + // to world space + FBox Bounds = pInput->GetInputBounds(CurrentLocation); + if (Bounds.IsValid) + { + AverageBoundsCentre += Bounds.GetCenter(); + NumBounds++; + } + } + } + + //if we have more than one, get the average centre + if (NumBounds > 1) + { + AverageBoundsCentre /= (float)NumBounds; + } + + //if we need to move... + float fDist = FVector::DistSquared(CurrentLocation, AverageBoundsCentre); + if (NumBounds && fDist > 1.0f) + { + // Move actor to average centre and recook + // This will refresh the static mesh under the HoudiniAssestComponent ( undoing the translation ). + HoudiniAssetActor->SetActorLocation(AverageBoundsCentre, false, nullptr, ETeleportType::TeleportPhysics); + + // Recook now the houdini-static-mesh has a new origin + HoudiniAssetComponent->StartTaskAssetCookingManual(); + RecentreCount++; + } + } + + if (RecentreCount) + { + // UE4 Editor doesn't refresh the translation-handles until they are re-selected, confusing the user, deselect the objects. + GEditor->SelectNone(true, false); + } + + // Add a slate notification + Notification = TEXT("Re-centred ") + FString::FromInt(RecentreCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-centred %d selected Houdini assets."), RecentreCount); + +#endif //WITH_EDITOR + */ +} + +void +FHoudiniEngineCommands::OpenSessionSync() +{ + //if (!FHoudiniEngine::IsInitialized()) + // return; + + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - HAPI Not initialized")); + return; + } + + // Get the runtime settings to get the session/type and settings + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + + EHoudiniRuntimeSettingsSessionType SessionType = HoudiniRuntimeSettings->SessionType; + FString ServerPipeName = HoudiniRuntimeSettings->ServerPipeName; + int32 ServerPort = HoudiniRuntimeSettings->ServerPort; + + FString SessionSyncArgs = TEXT("-hess="); + if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe) + { + // Add the -hess=pipe:hapi argument + SessionSyncArgs += TEXT("pipe:") + ServerPipeName; + } + else if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_Socket) + { + // Add the -hess=port:9090 argument + SessionSyncArgs += TEXT("port:") + FString::FromInt(ServerPort); + } + else + { + // Invalid session type + HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - Invalid session type")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Opening Houdini Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Opening Houdini Session Sync.")); + + // Only launch Houdini in Session sync if we havent started it already! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + if (!FPlatformProcess::IsProcRunning(PreviousHESS)) + { + // Start houdini with the -hess commandline args + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); + FProcHandle HESSHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *SessionSyncArgs, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + // Keep track of the SessionSync ProcHandle + FHoudiniEngine::Get().SetHESSProcHandle(HESSHandle); + } + + // Start an Async task to connect to Session Sync + Async(EAsyncExecution::TaskGraphMainThread, [SessionType, ServerPipeName, ServerPort]() + { + // Use a timeout to avoid waiting indefinitely for H to start in session sync mode + const double Timeout = 180.0; // 3min + const double StartTimestamp = FPlatformTime::Seconds(); + + FString ServerHost = TEXT("localhost"); + while (!FHoudiniEngine::Get().SessionSyncConnect(SessionType, ServerPipeName, ServerHost, ServerPort)) + { + // Houdini might not be done loading, sleep for one second + FPlatformProcess::Sleep(1); + + // Check for the timeout + if (FPlatformTime::Seconds() - StartTimestamp > Timeout) + { + // ... and a log message + HOUDINI_LOG_ERROR(TEXT("Failed to start SessionSync - Timeout...")); + return false; + } + } + + // Initialize HAPI with this session + if (!FHoudiniEngine::Get().InitializeHAPISession()) + { + FHoudiniEngine::Get().StopTicking(); + return false; + } + + // Notify all HACs that they need to instantiate in the new session + MarkAllHACsAsNeedInstantiation(); + + // Start ticking + FHoudiniEngine::Get().StartTicking(); + + // Add a slate notification + FString Notification = TEXT("Succesfully connected to Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Succesfully connected to Session Sync...")); + + return true; + }); +} + +void +FHoudiniEngineCommands::CloseSessionSync() +{ + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to stop Session Sync - HAPI Not initialized")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Stopping Houdini Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Stopping Houdini Session Sync.")); + + // Stop Houdini Session sync if it is still running! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + if (FPlatformProcess::IsProcRunning(PreviousHESS)) + { + FPlatformProcess::TerminateProc(PreviousHESS, true); + } +} + +void +FHoudiniEngineCommands::SetViewportSync(const int32& ViewportSync) +{ + if (ViewportSync == 1) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); + } + else if (ViewportSync == 2) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); + } + else if (ViewportSync == 3) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); + } + else + { + FHoudiniEngine::Get().SetSyncViewportEnabled(false); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); + } +} + +int32 +FHoudiniEngineCommands::GetViewportSync() +{ + if(!FHoudiniEngine::Get().IsSyncViewportEnabled()) + return 0; + + bool bSyncH = FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled(); + bool bSyncU = FHoudiniEngine::Get().IsSyncUnrealViewportEnabled(); + if (bSyncH && !bSyncU) + return 1; + else if (!bSyncH && bSyncU) + return 2; + else if (bSyncH && bSyncU) + return 3; + else + return 0; +} + +void +FHoudiniEngineCommands::RestartSession() +{ + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().RestartSession()) + return; + + // We've successfully restarted the Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::CreateSession() +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().CreateSession(HoudiniRuntimeSettings->SessionType)) + return; + + // We've successfully created the Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::ConnectSession() +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().ConnectSession(HoudiniRuntimeSettings->SessionType)) + return; + + // We've successfully connected to a Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation() +{ + // Notify all the HoudiniAssetComponents that they need to re instantiate themselves in the new Houdini engine session. + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedInstantiation(); + } +} + +bool +FHoudiniEngineCommands::IsSessionValid() +{ + return FHoudiniEngine::IsInitialized(); +} + +bool +FHoudiniEngineCommands::IsSessionSyncProcessValid() +{ + // Only launch Houdini in Session sync if we havent started it already! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + return FPlatformProcess::IsProcRunning(PreviousHESS); +} + +void +FHoudiniEngineCommands::StopSession() +{ + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine session stopped.")); + } +} + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE) +{ + // Get current world selection + TArray WorldSelection; + int32 NumSelectedHoudiniAssets = 0; + if (bOnlySelectedActors) + { + NumSelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (NumSelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + } + + // Add a slate notification + FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // First find the components that have meshes that we must refine + TArray ComponentsToRefine; + TArray ComponentsToCook; + // Components that would be candidates for refinement/cooking, but have errors + TArray SkippedComponents; + if (bOnlySelectedActors) + { + for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Index]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + } + else + { + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + } + + RefineTriagedHoudiniProxyMesehesToStaticMeshes( + ComponentsToRefine, + ComponentsToCook, + SkippedComponents, + bSilent, + bRefineAll, + bOnPreSaveWorld, + OnPreSaveWorld, + bOnPreBeginPIE + ); +} + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent) +{ + const bool bRefineAll = true; + const bool bOnPreSaveWorld = false; + UWorld* OnPreSaveWorld = nullptr; + const bool bOnPreBeginPIE = false; + + // First find the components that have meshes that we must refine + TArray ComponentsToRefine; + TArray ComponentsToCook; + // Components that would be candidates for refinement/cooking, but have errors + TArray SkippedComponents; + for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine) + { + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + + RefineTriagedHoudiniProxyMesehesToStaticMeshes( + ComponentsToRefine, + ComponentsToCook, + SkippedComponents, + bSilent, + bRefineAll, + bOnPreSaveWorld, + OnPreSaveWorld, + bOnPreBeginPIE + ); +} + +void +FHoudiniEngineCommands::StartPDGCommandlet() +{ + FHoudiniEngine::Get().StartPDGCommandlet(); +} + +void +FHoudiniEngineCommands::StopPDGCommandlet() +{ + FHoudiniEngine::Get().StopPDGCommandlet(); +} + +bool +FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected() +{ + return FHoudiniEngine::Get().IsPDGCommandletRunningOrConnected(); +} + +bool +FHoudiniEngineCommands::IsPDGCommandletEnabled() +{ + const UHoudiniRuntimeSettings* const Settings = GetDefault(); + if (IsValid(Settings)) + { + return Settings->bPDGAsyncCommandletImportEnabled; + } + + return false; +} + +bool +FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled) +{ + UHoudiniRuntimeSettings* const Settings = GetMutableDefault(); + if (IsValid(Settings)) + { + Settings->bPDGAsyncCommandletImportEnabled = InEnabled; + return true; + } + + return false; +} + +void +FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped) +{ + if (!InHAC || InHAC->IsPendingKill()) + return; + + // Make sure that the component's World and Owner are valid + AActor *Owner = InHAC->GetOwner(); + if (!Owner || Owner->IsPendingKill()) + return; + + UWorld *World = InHAC->GetWorld(); + if (!World || World->IsPendingKill()) + return; + + if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World) + return; + + // Check if we should consider this component for proxy mesh refinement based on its settings and + // flags passed to the function + if (bRefineAll || + (bOnPreSaveWorld && InHAC->IsProxyStaticMeshRefinementOnPreSaveWorldEnabled()) || + (bOnPreBeginPIE && InHAC->IsProxyStaticMeshRefinementOnPreBeginPIEEnabled())) + { + TArray ProxyMeshPackagesToSave; + TArray ComponentsWithProxiesToSave; + + if (InHAC->HasAnyCurrentProxyOutput()) + { + // Get the state of the asset and check if it is cooked + // If it is not cook, request a cook. We can only build the UStaticMesh + // if the data from the cook is available + // If the state is not pre-cook, or None (cooked), then the state is invalid, + // log an error and skip the component + bool bNeedsRebuildOrDelete = false; + bool bUnsupportedState = false; + const bool bCookedDataAvailable = InHAC->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bUnsupportedState); + if (bCookedDataAvailable) + { + OutToRefine.Add(InHAC); + ComponentsWithProxiesToSave.Add(InHAC); + } + else if (!bUnsupportedState && !bNeedsRebuildOrDelete) + { + InHAC->MarkAsNeedCook(); + // Force the output of the cook to be directly created as a UStaticMesh and not a proxy + InHAC->SetNoProxyMeshNextCookRequested(true); + OutToCook.Add(InHAC); + ComponentsWithProxiesToSave.Add(InHAC); + } + else + { + OutSkipped.Add(InHAC); + const EHoudiniAssetState AssetState = InHAC->GetAssetState(); + HOUDINI_LOG_ERROR(TEXT("Could not refine %s, the asset is in an unsupported state: %s"), *(InHAC->GetPathName()), *(UEnum::GetValueAsString(AssetState))); + } + } + else if (InHAC->HasAnyProxyOutput()) + { + // If the HAC has non-current proxies, destroy them + // TODO: Make this its own command? + const uint32 NumOutputs = InHAC->GetNumOutputs(); + for (uint32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = InHAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + TMap& OutputObjects = Output->GetOutputObjects(); + for (auto& CurrentPair : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; + if (!CurrentOutputObject.bProxyIsCurrent) + { + // The proxy is not current, delete it and its component + USceneComponent* FoundProxyComponent = Cast(CurrentOutputObject.ProxyComponent); + if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (FoundProxyComponent->GetOwner()) + FoundProxyComponent->GetOwner()->RemoveOwnedComponent(FoundProxyComponent); + + FoundProxyComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + FoundProxyComponent->UnregisterComponent(); + FoundProxyComponent->DestroyComponent(); + } + + UObject* ProxyObject = CurrentOutputObject.ProxyObject; + if (!ProxyObject || ProxyObject->IsPendingKill()) + continue; + + ProxyObject->MarkPendingKill(); + ProxyObject->MarkPackageDirty(); + UPackage* const Package = ProxyObject->GetPackage(); + if (IsValid(Package)) + ProxyMeshPackagesToSave.Add(Package); + } + } + } + } + + for (UHoudiniAssetComponent* const HAC : ComponentsWithProxiesToSave) + { + const uint32 NumOutputs = HAC->GetNumOutputs(); + for (uint32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + TMap& OutputObjects = Output->GetOutputObjects(); + for (auto& CurrentPair : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; + if (CurrentOutputObject.bProxyIsCurrent && CurrentOutputObject.ProxyObject) + { + UPackage* const Package = CurrentOutputObject.ProxyObject->GetPackage(); + if (IsValid(Package) && Package->IsDirty()) + ProxyMeshPackagesToSave.Add(Package); + } + } + } + } + + if (ProxyMeshPackagesToSave.Num() > 0) + { + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + FEditorFileUtils::PromptForCheckoutAndSave(ProxyMeshPackagesToSave, true, false); + } + } +} + +void +FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( + const TArray& InComponentsToRefine, + const TArray& InComponentsToCook, + const TArray& InSkippedComponents, + bool bInSilent, + bool bInRefineAll, + bool bInOnPreSaveWorld, + UWorld* InOnPreSaveWorld, + bool bInOnPrePIEBeginPlay) +{ + // Slate notification text + FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); + + const uint32 NumComponentsToCook = InComponentsToCook.Num(); + const uint32 NumComponentsToRefine = InComponentsToRefine.Num(); + const uint32 NumComponentsToProcess = NumComponentsToCook + NumComponentsToRefine; + TArray SuccessfulComponents; + uint32 NumSkippedComponents = InSkippedComponents.Num(); + if (NumComponentsToProcess > 0) + { + // The task progress pointer is potentially going to be shared with a background thread and tasks + // on the main thread, so make it thread safe + TSharedPtr TaskProgress = MakeShareable(new FSlowTask((float)NumComponentsToProcess, FText::FromString(Notification))); + TaskProgress->Initialize(); + if (!bInSilent) + TaskProgress->MakeDialog(/*bShowCancelButton=*/true); + + // Iterate over the components for which we can build UStaticMesh, and build the meshes + bool bCancelled = false; + for (uint32 ComponentIndex = 0; ComponentIndex < NumComponentsToRefine; ++ComponentIndex) + { + UHoudiniAssetComponent* HoudiniAssetComponent = InComponentsToRefine[ComponentIndex]; + TaskProgress->EnterProgressFrame(1.0f); + const bool bDestroyProxies = true; + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HoudiniAssetComponent, bDestroyProxies); + + SuccessfulComponents.Add(HoudiniAssetComponent); + + bCancelled = TaskProgress->ShouldCancel(); + if (bCancelled) + { + NumSkippedComponents += NumComponentsToRefine - ComponentIndex - 1; + break; + } + } + + if (NumComponentsToCook > 0 && !bCancelled) + { + // Now use an async task to check on the progress of the cooking components + Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { + RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); + }); + } + else + { + RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(NumComponentsToProcess, NumSkippedComponents, 0, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); + } + } +} + + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray& InComponentsToCook, TSharedPtr InTaskProgress, uint32 InNumComponentsToProcess, uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) +{ + // Copy to a double linked list so that we can loop through + // to check progress of each component and remove it easily + // if it has completed/failed + TDoubleLinkedList CookList; + for (UHoudiniAssetComponent *HAC : InComponentsToCook) + { + CookList.AddTail(HAC); + } + + // Add the successfully cooked compoments to the incoming successful components (previously refined) + TArray SuccessfulComponents(InSuccessfulComponents); + + bool bCancelled = false; + uint32 NumFailedToCook = 0; + while (CookList.Num() > 0 && !bCancelled) + { + TDoubleLinkedList::TDoubleLinkedListNode *Node = CookList.GetHead(); + while (Node && !bCancelled) + { + TDoubleLinkedList::TDoubleLinkedListNode *Next = Node->GetNextNode(); + UHoudiniAssetComponent* HAC = Node->GetValue(); + + if (HAC && !HAC->IsPendingKill()) + { + const EHoudiniAssetState State = HAC->GetAssetState(); + const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult(); + bool bUpdateProgress = false; + if (State == EHoudiniAssetState::None) + { + // Cooked, count as success, remove node + CookList.RemoveNode(Node); + SuccessfulComponents.Add(Node->GetValue()); + bUpdateProgress = true; + } + else if (ResultState != EHoudiniAssetStateResult::None && ResultState != EHoudiniAssetStateResult::Working) + { + // Failed, remove node + HOUDINI_LOG_ERROR(TEXT("Failed to cook %s to obtain static mesh."), *(HAC->GetPathName())); + CookList.RemoveNode(Node); + bUpdateProgress = true; + NumFailedToCook++; + } + + if (bUpdateProgress && InTaskProgress.IsValid()) + { + // Update progress only on the main thread, and check for cancellation request + bCancelled = Async(EAsyncExecution::TaskGraphMainThread, [InTaskProgress]() { + InTaskProgress->EnterProgressFrame(1.0f); + return InTaskProgress->ShouldCancel(); + }).Get(); + } + } + + Node = Next; + } + FPlatformProcess::Sleep(0.01f); + } + + if (bCancelled) + { + HOUDINI_LOG_WARNING(TEXT("Mesh refinement cancelled while waiting for %d components to cook."), CookList.Num()); + } + + // Cooking is done, or failed, display the notifications on the main thread + const uint32 NumRemaining = CookList.Num(); + Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InNumSkippedComponents, NumFailedToCook, NumRemaining, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { + RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InNumSkippedComponents + NumRemaining, NumFailedToCook, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); + }); +} + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) +{ + FString Notification; + if (InNumSkippedComponents + InNumFailedToCook > 0) + { + if (bCancelled) + { + Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); + } + else + { + Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); + } + FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_ERROR(TEXT("%s"), *Notification); + } + else if (InNumTotalComponents > 0) + { + Notification = TEXT("Done: Refining Houdini proxy meshes to static meshes."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_MESSAGE(TEXT("%s"), *Notification); + } + if (InTaskProgress) + { + InTaskProgress->Destroy(); + } + if (bOnPreSaveWorld && InSuccessfulComponents.Num() > 0) + { + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + + // Save the dirty static meshes in InSuccessfulComponents OnPostSaveWorld + // TODO: Remove? This may not be necessary now as we save all dirty temporary cook data in PostSaveWorld() already (Static Meshes, Materials...) + OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([InSuccessfulComponents, bOnPreSaveWorld, InOnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) { + if (bOnPreSaveWorld && InOnPreSaveWorld && InOnPreSaveWorld != InWorld) + return; + + RefineProxyMeshesHandleOnPostSaveWorld(InSuccessfulComponents, InSaveFlags, InWorld, bInSuccess); + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + }); + } +} + +void +FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) +{ + TArray PackagesToSave; + + for (UHoudiniAssetComponent* HAC : InSuccessfulComponents) + { + if (!HAC || HAC->IsPendingKill()) + continue; + + const int32 NumOutputs = HAC->GetNumOutputs(); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (auto &OutputObjectPair : Output->GetOutputObjects()) + { + UObject *Obj = OutputObjectPair.Value.OutputObject; + if (!Obj || Obj->IsPendingKill()) + continue; + + UStaticMesh *SM = Cast(Obj); + if (!SM) + continue; + + UPackage *Package = SM->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + } + } + + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h index 1c33eb13d..b939cf376 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h @@ -1,262 +1,262 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineStyle.h" - -#include "Framework/Commands/Commands.h" -#include "Delegates/IDelegateInstance.h" - -class UHoudiniAssetComponent; -class AHoudiniAssetActor; -struct FSlowTask; - -// Class containing commands for Houdini Engine actions -class FHoudiniEngineCommands : public TCommands -{ -public: - FHoudiniEngineCommands() - : TCommands - ( - TEXT("HoudiniEngine"), // Context name for fast lookup - NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), // Localized context name for displaying - NAME_None, // Parent context name. - FHoudiniEngineStyle::GetStyleSetName() // Icon Style Set - ) - { - } - - // TCommand<> interface - virtual void RegisterCommands() override; - -public: - - // Menu action called to save a HIP file. - static void SaveHIPFile(); - - // Menu action called to report a bug. - static void ReportBug(); - - // Menu action called to open the current scene in Houdini. - static void OpenInHoudini(); - - // Menu action called to clean up all unused files in the cook temp folder - static void CleanUpTempFolder(); - - // Menu action to bake/replace all current Houdini Assets with blueprints - static void BakeAllAssets(); - - // Helper function for baking/replacing the current select Houdini Assets with blueprints - static void BakeSelection(); - - // Helper function for restarting the current Houdini Engine session. - static void RestartSession(); - - // Menu action to pause cooking for all Houdini Assets - static void PauseAssetCooking(); - - // Helper delegate used to get the current state of PauseAssetCooking. - static bool IsAssetCookingPaused(); - - // Helper function for recooking all assets in the current level - static void RecookAllAssets(); - - // Helper function for rebuilding all assets in the current level - static void RebuildAllAssets(); - - // Helper function for recooking selected assets - static void RecookSelection(); - - // Helper function for rebuilding selected assets - static void RebuildSelection(); - - // Helper function for rebuilding selected assets - static void RecentreSelection(); - - // Helper function for starting Houdini in Sesion Sync mode - static void OpenSessionSync(); - - // Helper function for closing the current Houdini Sesion Sync - static void CloseSessionSync(); - - // returns true if the current HE session is valid - static bool IsSessionValid(); - - // Returns true if the current Session Sync process is still running - static bool IsSessionSyncProcessValid(); - - static int32 GetViewportSync(); - - static void SetViewportSync(const int32& ViewportSync); - - static void CreateSession(); - - static void ConnectSession(); - - static void StopSession(); - - static void ShowInstallInfo(); - - static void ShowPluginSettings(); - - static void OnlineDocumentation(); - - static void OnlineForum(); - - // Helper function for building static meshes for all assets using HoudiniStaticMesh - // If bSilent is false, show a progress dialog. - // If bRefineAll is true, then all components with HoudiniStaticMesh meshes will be - // refined to UStaticMesh. Otherwise, bOnPreSaveWorld and bOnPrePIEBeginPlay is checked - // against the settings of the component to determine if refinement should take place. - // If bOnPreSaveWorld is true, then OnPreSaveWorld should be the World that is being saved. In - // that case, only proxy meshes attached to components from that world will be refined. - static void RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent=false, bool bRefineAll=true, bool bOnPreSaveWorld=false, UWorld *PreSaveWorld=nullptr, bool bOnPrePIEBeginPlay=false); - - // Refine all proxy meshes on UHoudiniAssetCompoments of InActorsToRefine. - static void RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent=false); - - static void StartPDGCommandlet(); - - static void StopPDGCommandlet(); - - static bool IsPDGCommandletRunningOrConnected(); - - // Returns true if the commandlet is enabled in the settings - static bool IsPDGCommandletEnabled(); - - // Set the bPDGAsyncCommandletImportEnabled in the settings - static bool SetPDGCommandletEnabled(bool InEnabled); - - static FDelegateHandle& GetOnPostSaveWorldRefineProxyMeshesHandle() { return OnPostSaveWorldRefineProxyMeshesHandle; } - -public: - - // UI Action to create a Houdini Engine Session - TSharedPtr _CreateSession; - // UI Action to connect to a Houdini Engine Session - TSharedPtr _ConnectSession; - // UI Action to stop the current Houdini Engine Session - TSharedPtr _StopSession; - // UI Action to restart the current Houdini Engine Session - TSharedPtr _RestartSession; - // UI Action to open Houdini Session Sync - TSharedPtr _OpenSessionSync; - // UI Action to open Houdini Session Sync - TSharedPtr _CloseSessionSync; - - // UI Action to disable viewport sync - TSharedPtr _ViewportSyncNone; - // UI Action to enable unreal viewport sync - TSharedPtr _ViewportSyncUnreal; - // UI Action to enable houdini viewport sync - TSharedPtr _ViewportSyncHoudini; - // UI Action to enable bidirectionnal viewport sync - TSharedPtr _ViewportSyncBoth; - - // - TSharedPtr _InstallInfo; - // - TSharedPtr _PluginSettings; - - // Menu action called to open the current scene in Houdini. - TSharedPtr _OpenInHoudini; - // Menu action called to save a HIP file. - TSharedPtr _SaveHIPFile; - // Menu action called to clean up all unused files in the cook temp folder - TSharedPtr _CleanUpTempFolder; - - // - TSharedPtr _OnlineDoc; - // - TSharedPtr _OnlineForum; - // Menu action called to report a bug. - TSharedPtr _ReportBug; - - // UI Action to recook all HDA - TSharedPtr _CookAll; - // UI Action to recook the current world selection - TSharedPtr _CookSelected; - // Menu action to bake/replace all current Houdini Assets with blueprints - TSharedPtr _BakeAll; - // UI Action to bake and replace the current world selection - TSharedPtr _BakeSelected; - // UI Action to rebuild all HDA - TSharedPtr _RebuildAll; - // UI Action to rebuild the current world selection - TSharedPtr _RebuildSelected; - // UI Action for building static meshes for all assets using HoudiniStaticMesh - TSharedPtr _RefineAll; - // UI Action for building static meshes for selected assets using HoudiniStaticMesh - TSharedPtr _RefineSelected; - // Menu action to pause cooking for all Houdini Assets - TSharedPtr _PauseAssetCooking; - - // UI Action to recentre the current selection - TSharedPtr _RecentreSelected; - - // Start PDG/BGEO commandlet - TSharedPtr _StartPDGCommandlet; - // Stop PDG/BGEO commandlet - TSharedPtr _StopPDGCommandlet; - // Is PDG/BGEO commandlet enabled - TSharedPtr _IsPDGCommandletEnabled; - -protected: - - // Triage a HoudiniAssetComponent with UHoudiniStaticMesh as needing cooking or if a UStaticMesh can be immediately built - static void TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped); - - static void RefineTriagedHoudiniProxyMesehesToStaticMeshes( - const TArray& InComponentsToRefine, - const TArray& InComponentsToCook, - const TArray& InSkippedComponents, - bool bInSilent=false, - bool bInRefineAll=true, - bool bInOnPreSaveWorld=false, - UWorld* InOnPreSaveWorld=nullptr, - bool bInOnPrePIEBeginPlay=false); - - // Called in a background thread by RefineHoudiniProxyMeshesToStaticMeshes when some components need to be cooked to generate UStaticMeshes. Checks and waits for - // cooking of each component to complete, and then calls RefineHoudiniProxyMeshesToStaticMeshesNotifyDone on the main thread. - static void RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray &InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumComponentsToProcess, const uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); - - // Display a notification / end/close progress dialog, when refining mesh proxies to static meshes is complete - static void RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); - - // Handle OnPostSaveWorld for refining proxy meshes: this saves all the dirty UPackages of the UStaticMeshes that were created during RefineHoudiniProxyMeshesToStaticMeshes - // if it was called as a result of a PreSaveWorld. - static void RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess); - - // Helper function used to indicate to all HAC that they need to be instantiated in the new HE session - // Needs to be call after starting/restarting/connecting/session syncing a HE session.. - static void MarkAllHACsAsNeedInstantiation(); - - // Delegate that is set up to refined proxy meshes post save world (it removes itself afterwards) - static FDelegateHandle OnPostSaveWorldRefineProxyMeshesHandle; - -}; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineStyle.h" + +#include "Framework/Commands/Commands.h" +#include "Delegates/IDelegateInstance.h" + +class UHoudiniAssetComponent; +class AHoudiniAssetActor; +struct FSlowTask; + +// Class containing commands for Houdini Engine actions +class FHoudiniEngineCommands : public TCommands +{ +public: + FHoudiniEngineCommands() + : TCommands + ( + TEXT("HoudiniEngine"), // Context name for fast lookup + NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), // Localized context name for displaying + NAME_None, // Parent context name. + FHoudiniEngineStyle::GetStyleSetName() // Icon Style Set + ) + { + } + + // TCommand<> interface + virtual void RegisterCommands() override; + +public: + + // Menu action called to save a HIP file. + static void SaveHIPFile(); + + // Menu action called to report a bug. + static void ReportBug(); + + // Menu action called to open the current scene in Houdini. + static void OpenInHoudini(); + + // Menu action called to clean up all unused files in the cook temp folder + static void CleanUpTempFolder(); + + // Menu action to bake/replace all current Houdini Assets with blueprints + static void BakeAllAssets(); + + // Helper function for baking/replacing the current select Houdini Assets with blueprints + static void BakeSelection(); + + // Helper function for restarting the current Houdini Engine session. + static void RestartSession(); + + // Menu action to pause cooking for all Houdini Assets + static void PauseAssetCooking(); + + // Helper delegate used to get the current state of PauseAssetCooking. + static bool IsAssetCookingPaused(); + + // Helper function for recooking all assets in the current level + static void RecookAllAssets(); + + // Helper function for rebuilding all assets in the current level + static void RebuildAllAssets(); + + // Helper function for recooking selected assets + static void RecookSelection(); + + // Helper function for rebuilding selected assets + static void RebuildSelection(); + + // Helper function for rebuilding selected assets + static void RecentreSelection(); + + // Helper function for starting Houdini in Sesion Sync mode + static void OpenSessionSync(); + + // Helper function for closing the current Houdini Sesion Sync + static void CloseSessionSync(); + + // returns true if the current HE session is valid + static bool IsSessionValid(); + + // Returns true if the current Session Sync process is still running + static bool IsSessionSyncProcessValid(); + + static int32 GetViewportSync(); + + static void SetViewportSync(const int32& ViewportSync); + + static void CreateSession(); + + static void ConnectSession(); + + static void StopSession(); + + static void ShowInstallInfo(); + + static void ShowPluginSettings(); + + static void OnlineDocumentation(); + + static void OnlineForum(); + + // Helper function for building static meshes for all assets using HoudiniStaticMesh + // If bSilent is false, show a progress dialog. + // If bRefineAll is true, then all components with HoudiniStaticMesh meshes will be + // refined to UStaticMesh. Otherwise, bOnPreSaveWorld and bOnPrePIEBeginPlay is checked + // against the settings of the component to determine if refinement should take place. + // If bOnPreSaveWorld is true, then OnPreSaveWorld should be the World that is being saved. In + // that case, only proxy meshes attached to components from that world will be refined. + static void RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent=false, bool bRefineAll=true, bool bOnPreSaveWorld=false, UWorld *PreSaveWorld=nullptr, bool bOnPrePIEBeginPlay=false); + + // Refine all proxy meshes on UHoudiniAssetCompoments of InActorsToRefine. + static void RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent=false); + + static void StartPDGCommandlet(); + + static void StopPDGCommandlet(); + + static bool IsPDGCommandletRunningOrConnected(); + + // Returns true if the commandlet is enabled in the settings + static bool IsPDGCommandletEnabled(); + + // Set the bPDGAsyncCommandletImportEnabled in the settings + static bool SetPDGCommandletEnabled(bool InEnabled); + + static FDelegateHandle& GetOnPostSaveWorldRefineProxyMeshesHandle() { return OnPostSaveWorldRefineProxyMeshesHandle; } + +public: + + // UI Action to create a Houdini Engine Session + TSharedPtr _CreateSession; + // UI Action to connect to a Houdini Engine Session + TSharedPtr _ConnectSession; + // UI Action to stop the current Houdini Engine Session + TSharedPtr _StopSession; + // UI Action to restart the current Houdini Engine Session + TSharedPtr _RestartSession; + // UI Action to open Houdini Session Sync + TSharedPtr _OpenSessionSync; + // UI Action to open Houdini Session Sync + TSharedPtr _CloseSessionSync; + + // UI Action to disable viewport sync + TSharedPtr _ViewportSyncNone; + // UI Action to enable unreal viewport sync + TSharedPtr _ViewportSyncUnreal; + // UI Action to enable houdini viewport sync + TSharedPtr _ViewportSyncHoudini; + // UI Action to enable bidirectionnal viewport sync + TSharedPtr _ViewportSyncBoth; + + // + TSharedPtr _InstallInfo; + // + TSharedPtr _PluginSettings; + + // Menu action called to open the current scene in Houdini. + TSharedPtr _OpenInHoudini; + // Menu action called to save a HIP file. + TSharedPtr _SaveHIPFile; + // Menu action called to clean up all unused files in the cook temp folder + TSharedPtr _CleanUpTempFolder; + + // + TSharedPtr _OnlineDoc; + // + TSharedPtr _OnlineForum; + // Menu action called to report a bug. + TSharedPtr _ReportBug; + + // UI Action to recook all HDA + TSharedPtr _CookAll; + // UI Action to recook the current world selection + TSharedPtr _CookSelected; + // Menu action to bake/replace all current Houdini Assets with blueprints + TSharedPtr _BakeAll; + // UI Action to bake and replace the current world selection + TSharedPtr _BakeSelected; + // UI Action to rebuild all HDA + TSharedPtr _RebuildAll; + // UI Action to rebuild the current world selection + TSharedPtr _RebuildSelected; + // UI Action for building static meshes for all assets using HoudiniStaticMesh + TSharedPtr _RefineAll; + // UI Action for building static meshes for selected assets using HoudiniStaticMesh + TSharedPtr _RefineSelected; + // Menu action to pause cooking for all Houdini Assets + TSharedPtr _PauseAssetCooking; + + // UI Action to recentre the current selection + TSharedPtr _RecentreSelected; + + // Start PDG/BGEO commandlet + TSharedPtr _StartPDGCommandlet; + // Stop PDG/BGEO commandlet + TSharedPtr _StopPDGCommandlet; + // Is PDG/BGEO commandlet enabled + TSharedPtr _IsPDGCommandletEnabled; + +protected: + + // Triage a HoudiniAssetComponent with UHoudiniStaticMesh as needing cooking or if a UStaticMesh can be immediately built + static void TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped); + + static void RefineTriagedHoudiniProxyMesehesToStaticMeshes( + const TArray& InComponentsToRefine, + const TArray& InComponentsToCook, + const TArray& InSkippedComponents, + bool bInSilent=false, + bool bInRefineAll=true, + bool bInOnPreSaveWorld=false, + UWorld* InOnPreSaveWorld=nullptr, + bool bInOnPrePIEBeginPlay=false); + + // Called in a background thread by RefineHoudiniProxyMeshesToStaticMeshes when some components need to be cooked to generate UStaticMeshes. Checks and waits for + // cooking of each component to complete, and then calls RefineHoudiniProxyMeshesToStaticMeshesNotifyDone on the main thread. + static void RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray &InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumComponentsToProcess, const uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); + + // Display a notification / end/close progress dialog, when refining mesh proxies to static meshes is complete + static void RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); + + // Handle OnPostSaveWorld for refining proxy meshes: this saves all the dirty UPackages of the UStaticMeshes that were created during RefineHoudiniProxyMeshesToStaticMeshes + // if it was called as a result of a PreSaveWorld. + static void RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess); + + // Helper function used to indicate to all HAC that they need to be instantiated in the new HE session + // Needs to be call after starting/restarting/connecting/session syncing a HE session.. + static void MarkAllHACsAsNeedInstantiation(); + + // Delegate that is set up to refined proxy meshes post save world (it removes itself afterwards) + static FDelegateHandle OnPostSaveWorldRefineProxyMeshesHandle; + +}; + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp index cf76fa2bc..bbd8e24c4 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp @@ -1,1950 +1,1950 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" - -#include "DetailCategoryBuilder.h" -#include "IDetailGroup.h" -#include "DetailWidgetRow.h" -#include "Widgets/SBoxPanel.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Layout/SScrollBox.h" -#include "Widgets/Input/SMultiLineEditableTextBox.h" -#include "Widgets/Layout/SUniformGridPanel.h" -#include "Brushes/SlateImageBrush.h" -#include "Widgets/Input/SComboBox.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "ActorPickerMode.h" -#include "SceneOutlinerModule.h" -#include "Modules/ModuleManager.h" -#include "Interfaces/IMainFrameModule.h" -#include "AssetThumbnail.h" -#include "DetailLayoutBuilder.h" -#include "SAssetDropTarget.h" -#include "PropertyCustomizationHelpers.h" -#include "ScopedTransaction.h" -#include "SEnumCombobox.h" -#include "HAL/FileManager.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define HOUDINI_ENGINE_UI_SECTION_GENERATE 1 -#define HOUDINI_ENGINE_UI_SECTION_BAKE 2 -#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS 3 -#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG 4 - -#define HOUDINI_ENGINE_UI_BUTTON_WIDTH 135.0f - -#define HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT "Generate" -#define HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT "Bake" -#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT "Asset Options" -#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT "Help and Debug" - - -void -SHoudiniAssetLogWidget::Construct(const FArguments & InArgs) -{ - this->ChildSlot - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background"))) - .Content() - [ - SNew(SScrollBox) - + SScrollBox::Slot() - [ - SNew(SMultiLineEditableTextBox) - .Text(FText::FromString(InArgs._LogText)) - .AutoWrapText(true) - .IsReadOnly(true) - ] - ] - ]; -} - - -void -FHoudiniEngineDetails::CreateWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // 0. Houdini Engine Icon - FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs); - - // 1. Create Generate Category - FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 2. Create Bake Category - FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 3. Create Asset Options Category - FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 4. Create Help and Debug Category - FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs); - -} - -void -FHoudiniEngineDetails::CreateHoudiniEngineIconWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Skip drawing the icon if the icon image is not loaded correctly. - TSharedPtr HoudiniEngineUIIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIIconBrush(); - if (!HoudiniEngineUIIconBrush.IsValid()) - return; - - FDetailWidgetRow & Row = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef Box = SNew(SHorizontalBox); - TSharedPtr Image; - - Box->AddSlot() - .AutoWidth() - .Padding(0.0f, 5.0f, 5.0f, 10.0f) - .HAlign(HAlign_Left) - [ - SNew(SBox) - .HeightOverride(30) - .WidthOverride(208) - [ - SAssignNew(Image, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - Image->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIIconBrush]() { - return HoudiniEngineUIIconBrush.Get(); - }))); - - Row.WholeRowWidget.Widget = Box; - Row.IsEnabled(false); -} - -void -FHoudiniEngineDetails::CreateGenerateWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - auto OnReBuildClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->MarkAsNeedRebuild(); - } - - return FReply::Handled(); - }; - - auto OnRecookClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->MarkAsNeedCook(); - } - - return FReply::Handled(); - }; - - auto ShouldEnableResetParametersButtonLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - // Reset parameters to default values? - for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) - { - UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); - - if (NextParm && !NextParm->IsDefault()) - return true; - } - } - - return false; - }; - - auto OnResetParametersClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - // Reset parameters to default values? - for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) - { - UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); - - if (NextParm && !NextParm->IsDefault()) - { - NextParm->RevertToDefault(); - } - } - } - - return FReply::Handled(); - }; - - auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) - { - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FString NewPathStr = Val.ToString(); - - if (NewPathStr.IsEmpty()) - return; - - if (NewPathStr.StartsWith("Game/")) - { - NewPathStr = "/" + NewPathStr; - } - - FString AbsolutePath; - if (NewPathStr.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); - } - - if (!FPaths::DirectoryExists(AbsolutePath)) - { - HOUDINI_LOG_WARNING(TEXT("Invalid path")); - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - return; - } - - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr)) - continue; - - NextHAC->TemporaryCookFolder.Path = NewPathStr; - } - }; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_GENERATE); - - // Button Row (draw only if expanded) - if (!MainHAC->bGenerateMenuExpanded) - return; - - TSharedPtr HoudiniEngineUIRebuildIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRebuildIconBrush(); - TSharedPtr HoudiniEngineUIRecookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - TSharedPtr HoudiniEngineUIResetParametersIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIResetParametersIconBrush(); - - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef ButtonHorizontalBox = SNew(SHorizontalBox); - - // Rebuild button - TSharedPtr RebuildButton; - TSharedPtr RebuildButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(15.0f, 0.0f, 0.0f, 2.0f) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(RebuildButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) - //.Text(FText::FromString("Rebuild")) - .Visibility(EVisibility::Visible) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) - ] - ] - .OnClicked_Lambda(OnReBuildClickedLambda) - ] - ]; - - if (HoudiniEngineUIRebuildIconBrush.IsValid()) - { - TSharedPtr RebuildImage; - RebuildButtonHorizontalBox->AddSlot() - //.Padding(25.0f, 0.0f, 3.0f, 0.0f) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RebuildImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - RebuildImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { - return HoudiniEngineUIRebuildIconBrush.Get(); - }))); - } - - RebuildButtonHorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .AutoWidth() - .Padding(5.0, 0.0, 0.0, 0.0) - [ - SNew(STextBlock) - .Text(FText::FromString("Rebuild")) - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; - ButtonRow.IsEnabled(false); - - // Recook button - TSharedPtr RecookButton; - TSharedPtr RecookButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(RecookButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) - //.Text(FText::FromString("Recook")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnRecookClickedLambda) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) - ] - ] - ] - ]; - - if (HoudiniEngineUIRecookIconBrush.IsValid()) - { - TSharedPtr RecookImage; - RecookButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RecookImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - RecookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { - return HoudiniEngineUIRecookIconBrush.Get(); - }))); - } - - RecookButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Recook")) - ]; - - // Reset Parameters button - TSharedPtr ResetParametersButton; - TSharedPtr ResetParametersButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(ResetParametersButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsResetParametersAssetButton", "Reset the selected Houdini Asset's parameters to their default values.")) - //.Text(FText::FromString("Reset Parameters")) - .IsEnabled_Lambda(ShouldEnableResetParametersButtonLambda) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnResetParametersClickedLambda) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(ResetParametersButtonHorizontalBox, SHorizontalBox) - ] - ] - ] - ]; - - if (HoudiniEngineUIResetParametersIconBrush.IsValid()) - { - TSharedPtr ResetParametersImage; - ResetParametersButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(0.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(ResetParametersImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - ResetParametersImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIResetParametersIconBrush]() { - return HoudiniEngineUIResetParametersIconBrush.Get(); - }))); - } - - ResetParametersButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - //.FillWidth(4.2f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - //.MinDesiredWidth(160.f) - .Text(FText::FromString("Reset Parameters")) - ]; - - - // Temp Cook Folder Row - FDetailWidgetRow & TempCookFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef TempCookFolderRowHorizontalBox = SNew(SHorizontalBox); - - TempCookFolderRowHorizontalBox->AddSlot() - //.Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineTemporaryCookFolderLabel", "Temporary Cook Folder")) - .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, MAterials, Textures..) that are generated by Houdini Assets when they cook.")) - ] - ]; - - TempCookFolderRowHorizontalBox->AddSlot() - .MaxWidth(235.0f) - [ - SNew(SBox) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, Materials, Textures..) that are generated by Houdini Assets when they cook.")) - .HintText(LOCTEXT("HoudiniEngineTempCookFolderHintText", "Input to set temporary cook folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainHAC->TemporaryCookFolder.Path)) - .OnTextCommitted_Lambda(OnCookFolderTextCommittedLambda) - ] - ]; - - TempCookFolderRow.WholeRowWidget.Widget = TempCookFolderRowHorizontalBox; -} - -void -FHoudiniEngineDetails::OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC) -{ - if (!IsValid(InHAC)) - return; - - if (!bInState) - { - if (InHAC->GetOnPostCookBakeDelegate().IsBound()) - InHAC->GetOnPostCookBakeDelegate().Unbind(); - } - else - { - InHAC->GetOnPostCookBakeDelegate().BindLambda([](UHoudiniAssetComponent* HAC) - { - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - HAC, - HAC->bReplacePreviousBake, - HAC->HoudiniEngineBakeOption, - HAC->bRemoveOutputAfterBake); - }); - } -} - -void -FHoudiniEngineDetails::CreateBakeWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE); - - if (!MainHAC->bBakeMenuExpanded) - return; - - auto OnBakeButtonClickedLambda = [InHACs, MainHAC]() - { - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - NextHAC, - MainHAC->bReplacePreviousBake, - MainHAC->HoudiniEngineBakeOption, - MainHAC->bRemoveOutputAfterBake); - } - - return FReply::Handled(); - }; - - auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) - { - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FString NewPathStr = Val.ToString(); - - if (NewPathStr.IsEmpty()) - return; - - if (NewPathStr.StartsWith("Game/")) - { - NewPathStr = "/" + NewPathStr; - } - - FString AbsolutePath; - if (NewPathStr.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); - } - - if (!FPaths::DirectoryExists(AbsolutePath)) - { - HOUDINI_LOG_WARNING(TEXT("Invalid path")); - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - return; - } - - - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - if (NextHAC->BakeFolder.Path.Equals(NewPathStr)) - continue; - - NextHAC->BakeFolder.Path = NewPathStr; - } - }; - - // Button Row - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - - // Bake Button - TSharedPtr BakeButton; - TSharedPtr BakeButtonHorizontalBox; - - ButtonRowHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(15.f, 0.0f, 0.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(BakeButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsBakeButton", "Bake the Houdini Asset Component(s).")) - //.Text(FText::FromString("Recook")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnBakeButtonClickedLambda) - .Content() - [ - SAssignNew(BakeButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); - if (BakeIconBrush.IsValid()) - { - TSharedPtr BakeImage; - BakeButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(BakeImage, SImage) - ] - ]; - - BakeImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([BakeIconBrush]() { - return BakeIconBrush.Get(); - }))); - } - - BakeButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Bake")) - ]; - - // Bake Type ComboBox - TSharedPtr>> TypeComboBox; - - TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEngineBakeTypeOptionsLabels(); - TSharedPtr IntialSelec; - if (OptionSource) - { - IntialSelec = (*OptionSource)[(int)MainHAC->HoudiniEngineBakeOption]; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - //.MaxWidth(103.f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - //.WidthOverride(103.f) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [MainHAC, InHACs](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EHoudiniEngineBakeOption NewOption = - FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - MainHAC->HoudiniEngineBakeOption = NewOption; - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - [ - SNew(STextBlock) - .Text_Lambda([MainHAC]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(MainHAC->HoudiniEngineBakeOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; - - // Clear Output After Baking Row - FDetailWidgetRow & ClearOutputAfterBakingRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); - - // Remove Output Checkbox - TSharedPtr CheckBoxRemoveOutput; - TSharedPtr CheckBoxAutoBake; - TSharedPtr CheckBoxRecenterBakedActors; - TSharedPtr CheckBoxReplacePreviousBake; - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRemoveOutput, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBox", "Remove HDA Output After Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBoxToolTip", "After baking the existing output of this Houdini Asset Actor will be removed.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bRemoveOutputAfterBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bRemoveOutputAfterBake = bNewState; - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bRecenterBakedActors = bNewState; - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - // TODO: find a better way to manage the initial binding/unbinding of the post cook bake delegate - // We do this here to ensure the delegate is bound/unbound correctly when the UI is initially drawn - // Currently we have the problem that the HoudiniEngineRuntime and HoudiniEngine modules cannot access - // the FHoudiniEngineBakeUtils code that is in HoudiniEngineEditor (this is the primary reason for the delegate and - // managing the delegate in this way). - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - const bool bState = NextHAC->IsBakeAfterNextCookEnabled(); - NextHAC->SetBakeAfterNextCookEnabled(bState); - OnBakeAfterCookChangedHelper(bState, NextHAC); - } - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxAutoBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake the next cook.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->IsBakeAfterNextCookEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->SetBakeAfterNextCookEnabled(bNewState); - OnBakeAfterCookChangedHelper(bNewState, NextHAC); - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - // Replace Checkbox - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxReplacePreviousBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBox", "Replace Previous Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBoxToolTip", "When baking replace the previous bake's output instead of creating additional output actors/components/objects.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bReplacePreviousBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - MainHAC->bReplacePreviousBake = bNewState; - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - ClearOutputAfterBakingRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; - - // Bake Folder Row - FDetailWidgetRow & BakeFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) - ] - ]; - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .MaxWidth(235.0) - [ - SNew(SBox) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) - .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainHAC->BakeFolder.Path)) - .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) - ] - ]; - - BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; - - switch (MainHAC->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToActorToolTip", - "Bake this Houdini Asset Actor and its components to native unreal actors and components, replacing the previous baked result.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToActorToolTip", - "Bake this Houdini Asset Actor and its components to native unreal actors and components.")); - } - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToBlueprintToolTip", - "Bake this Houdini Asset Actor to a blueprint.")); - } - break; - - case EHoudiniEngineBakeOption::ToFoliage: - { - if (!FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(MainHAC)) - { - // If the HAC does not have instanced output, disable Bake to Foliage - BakeButton->SetEnabled(false); - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonNoInstancedOutputToolTip", - "The Houdini Asset must be outputing at least one instancer in order to be able to bake to Foliage.")); - } - else - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToFoliageToolTip", - "Add this Houdini Asset Actor's instancers to the current level's Foliage, replacing the previously baked foliage instancers from this actor.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToFoliageToolTip", - "Add this Houdini Asset Actor's instancers to the current level's Foliage.")); - } - } - } - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToWorldOutlinerToolTip", - "Not implemented.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToWorldOutlinerToolTip", - "Not implemented.")); - } - } - break; - } - - // Todo: remove me! - if (MainHAC->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToWorldOutliner) - BakeButton->SetEnabled(false); - -} - -void -FHoudiniEngineDetails::CreateAssetOptionsWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Header Row - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS); - - if (!MainHAC->bAssetOptionMenuExpanded) - return; - - auto IsCheckedParameterChangedLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateParameterChangedLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnParameterChange = bChecked; - } - }; - - auto IsCheckedTransformChangeLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedTransformChangeLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnTransformChange = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedAssetInputCookLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedAssetInputCookLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnAssetInputCook = bChecked; - } - }; - - auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedPushTransformToHoudiniLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bUploadTransformsToHoudiniEngine = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedDoNotGenerateOutputsLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bOutputless = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedOutputTemplatedGeosLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bOutputTemplateGeos = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - // Checkboxes row - FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef CheckBoxesHorizontalBox = SNew(SHorizontalBox); - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - CheckBoxesHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - CheckBoxesHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineCookTriggersLabel", "Cook Triggers")) - ] - ]; - - // Parameter change check box - FText TooltipText = LOCTEXT("HoudiniEngineParameterChangeTooltip", "If enabled, modifying a parameter or input on this Houdini Asset will automatically trigger a cook of the HDA in Houdini."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineParameterChangeCheckBoxLabel", "On Parameter/Input Change")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateParameterChangedLambda) - .IsChecked_Lambda(IsCheckedParameterChangedLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Transform change check box - TooltipText = LOCTEXT("HoudiniEngineTransformChangeTooltip", "If enabled, changing the Houdini Asset Actor's transform in Unreal will also update its HDA's node transform in Houdini, and trigger a recook of the HDA with the updated transform."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineTransformChangeCheckBoxLabel", "On Transform Change")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedTransformChangeLambda) - .IsChecked_Lambda(IsCheckedTransformChangeLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Triggers Downstream cook checkbox - TooltipText = LOCTEXT("HoudiniEngineAssetInputCookTooltip", "When enabled, this asset will automatically re-cook after one its asset input has finished cooking."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineAssetInputCheckBoxLabel", "On Asset Input Cook")) - .ToolTipText(TooltipText) - ] - - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedAssetInputCookLambda) - .IsChecked_Lambda(IsCheckedAssetInputCookLambda) - .ToolTipText(TooltipText) - ] - ]; - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous")) - ]; - - // Push Transform to Houdini check box - TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini")) - .ToolTipText(TooltipText) - ] - - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda) - .IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Do not generate output check box - TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda) - .IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Output templated geos check box - TooltipText = LOCTEXT("HoudiniEnginOutputTemplatesTooltip", "If enabled, Geometry nodes in the asset that have the template flag will be outputed."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Output Templated Geos")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedOutputTemplatedGeosLambda) - .IsChecked_Lambda(IsCheckedOutputTemplatedGeosLambda) - .ToolTipText(TooltipText) - ] - ]; - - CheckBoxesRow.WholeRowWidget.Widget = CheckBoxesHorizontalBox; -} - -void -FHoudiniEngineDetails::CreateHelpAndDebugWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Header Row - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG); - - if (!MainHAC->bHelpAndDebugMenuExpanded) - return; - - auto OnFetchCookLogButtonClickedLambda = [InHACs]() - { - return ShowCookLog(InHACs); - }; - - auto OnHelpButtonClickedLambda = [MainHAC]() - { - return ShowAssetHelp(MainHAC); - }; - - // Button Row - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - TSharedPtr CookLogButtonHorizontalBox = SNew(SHorizontalBox); - - // Fetch Cook Log button - ButtonRowHorizontalBox->AddSlot() - //.Padding(15.0f, 0.0f, 0.0f, 0.0f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(FText::FromString("Fetch and display all cook logs available for this Houdini Asset Actor.")) - //.Text(FText::FromString("Fetch Cook Log")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnFetchCookLogButtonClickedLambda) - .Content() - [ - SAssignNew(CookLogButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr CookLogIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUICookLogIconBrush(); - if (CookLogIconBrush.IsValid()) - { - TSharedPtr CookImage; - CookLogButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookImage, SImage) - ] - ]; - - CookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookLogIconBrush]() { - return CookLogIconBrush.Get(); - }))); - } - - CookLogButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Show Cook Logs")) - ]; - - // Asset Help Button - TSharedPtr AssetHelpButtonHorizontalBox; - ButtonRowHorizontalBox->AddSlot() - //.Padding(4.0, 0.0f, 0.0f, 0.0f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(FText::FromString("Display this Houdini Asset Actor's HDA help.")) - //.Text(FText::FromString("Asset Help")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnHelpButtonClickedLambda) - .Content() - [ - SAssignNew(AssetHelpButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr AssetHelpIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIAssetHelpIconBrush(); - if (AssetHelpIconBrush.IsValid()) - { - TSharedPtr AssetHelpImage; - AssetHelpButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(AssetHelpImage, SImage) - ] - ]; - - AssetHelpImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([AssetHelpIconBrush]() { - return AssetHelpIconBrush.Get(); - }))); - } - - AssetHelpButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Asset Help")) - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; -} - -FMenuBuilder -FHoudiniEngineDetails::Helper_CreateHoudiniAssetPicker() -{ - auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - return true; - }; - - auto OnActorSelected = [](AActor* Actor) - { - UE_LOG(LogTemp, Warning, TEXT("Actor Selected")); - - return; - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterHoudiniAssetLambda); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("HoudiniEngineDetailsAssetPicker", "Asset")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -const FSlateBrush * -FHoudiniEngineDetails::GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const -{ - if (HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - -/* -TSharedRef< SWidget > -FHoudiniEngineDetails::OnGetHoudiniAssetMenuContent(TArray InHACs) -{ - TArray< const UClass * > AllowedClasses; - AllowedClasses.Add(UHoudiniAsset::StaticClass()); - - TArray< UFactory * > NewAssetFactories; - - UHoudiniAsset * HoudiniAsset = nullptr; - if (InHACs.Num() > 0) - { - UHoudiniAssetComponent * HoudiniAssetComponent = InHACs[0]; - HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; - } - - auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - return true; - }; - - // Delegate for filtering Houdini assets. - FOnShouldFilterAsset OnShouldFilterHoudiniAsset = FOnShouldFilterAsset::CreateLambda(OnShouldFilterHoudiniAssetLambda); - - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(HoudiniAsset), true, - AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset, - FOnAssetSelected::CreateLambda([](const FAssetData & AssetData) {}), - FSimpleDelegate::CreateLambda([]() { }) - ); -} -*/ - -FReply -FHoudiniEngineDetails::ShowCookLog(TArray InHACS) -{ - TSharedPtr< SWindow > ParentWindow; - FString CookLog = FHoudiniEngineUtils::GetCookLog(InHACS); - - // 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("MainFrame"); - ParentWindow = MainFrame.GetParentWindow(); - } - - if (ParentWindow.IsValid()) - { - TSharedPtr HoudiniAssetCookLog; - - TSharedRef Window = - SNew(SWindow) - .Title(LOCTEXT("WindowTitle", "Houdini Cook Log")) - .ClientSize(FVector2D(640, 480)); - - Window->SetContent( - SAssignNew(HoudiniAssetCookLog, SHoudiniAssetLogWidget) - .LogText(CookLog)); - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - } - - return FReply::Handled(); -} - -FReply -FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC) -{ - if (!InHAC) - return FReply::Handled(); - - FString AssetHelp = FHoudiniEngineUtils::GetAssetHelp(InHAC); - - 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 HoudiniAssetHelpLog; - - TSharedRef Window = - SNew(SWindow) - .Title(LOCTEXT("WindowTitle", "Houdini Asset Help")) - .ClientSize(FVector2D(640, 480)); - - Window->SetContent( - SAssignNew(HoudiniAssetHelpLog, SHoudiniAssetLogWidget) - .LogText(AssetHelp)); - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - } - return FReply::Handled(); -} - -void -FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return; - - FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - HoudiniAssetComponent->bGenerateMenuExpanded = !HoudiniAssetComponent->bGenerateMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - HoudiniAssetComponent->bBakeMenuExpanded = !HoudiniAssetComponent->bBakeMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - HoudiniAssetComponent->bAssetOptionMenuExpanded = !HoudiniAssetComponent->bAssetOptionMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - HoudiniAssetComponent->bHelpAndDebugMenuExpanded = !HoudiniAssetComponent->bHelpAndDebugMenuExpanded; - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniAssetComponent, true); - - return FReply::Handled(); - }); - - TFunction GetText = [MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT); - break; - } - return FText::FromString(""); - }; - - TFunction GetExpanderBrush = [HoudiniAssetComponent, MenuSection](SButton* InExpanderArrow) - { - FName ResourceName; - bool bMenuExpanded = false; - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - bMenuExpanded = HoudiniAssetComponent->bGenerateMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - bMenuExpanded = HoudiniAssetComponent->bBakeMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - bMenuExpanded = HoudiniAssetComponent->bAssetOptionMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - bMenuExpanded = HoudiniAssetComponent->bHelpAndDebugMenuExpanded; - } - - if (bMenuExpanded) - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - }; - - return AddHeaderRow(HoudiniEngineCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); -} - -void -FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]() - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - InPDGAssetLink->Modify(); - InPDGAssetLink->bBakeMenuExpanded = !InPDGAssetLink->bBakeMenuExpanded; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded), InPDGAssetLink); - break; - } - - //FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, true); - - return FReply::Handled(); - }); - - TFunction GetText = [MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); - break; - } - return FText::FromString(""); - }; - - TFunction GetExpanderBrush = [InPDGAssetLink, MenuSection](SButton* InExpanderArrow) - { - FName ResourceName; - bool bMenuExpanded = false; - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - bMenuExpanded = InPDGAssetLink->bBakeMenuExpanded; - break; - } - - if (bMenuExpanded) - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - }; - - return AddHeaderRow(PDGCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); -} - -void -FHoudiniEngineDetails::AddHeaderRow( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - FOnClicked& InOnExpanderClick, - TFunction& InGetText, - TFunction& InGetExpanderBrush) -{ - // Header Row - FDetailWidgetRow & HeaderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedPtr HeaderHorizontalBox; - HeaderRow.WholeRowWidget.Widget = SAssignNew(HeaderHorizontalBox, SHorizontalBox); - - TSharedPtr ExpanderImage; - TSharedPtr ExpanderArrow; - HeaderHorizontalBox->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth() - [ - SAssignNew(ExpanderArrow, SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - .OnClicked(InOnExpanderClick) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - HeaderHorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SNew(STextBlock) - .Text_Lambda([InGetText](){return InGetText(); }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - ExpanderImage->SetImage( - TAttribute::Create( - [ExpanderArrow, InGetExpanderBrush]() - { - return InGetExpanderBrush(ExpanderArrow.Get()); - })); -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" + +#include "DetailCategoryBuilder.h" +#include "IDetailGroup.h" +#include "DetailWidgetRow.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Brushes/SlateImageBrush.h" +#include "Widgets/Input/SComboBox.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "ActorPickerMode.h" +#include "SceneOutlinerModule.h" +#include "Modules/ModuleManager.h" +#include "Interfaces/IMainFrameModule.h" +#include "AssetThumbnail.h" +#include "DetailLayoutBuilder.h" +#include "SAssetDropTarget.h" +#include "PropertyCustomizationHelpers.h" +#include "ScopedTransaction.h" +#include "SEnumCombobox.h" +#include "HAL/FileManager.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define HOUDINI_ENGINE_UI_SECTION_GENERATE 1 +#define HOUDINI_ENGINE_UI_SECTION_BAKE 2 +#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS 3 +#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG 4 + +#define HOUDINI_ENGINE_UI_BUTTON_WIDTH 135.0f + +#define HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT "Generate" +#define HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT "Bake" +#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT "Asset Options" +#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT "Help and Debug" + + +void +SHoudiniAssetLogWidget::Construct(const FArguments & InArgs) +{ + this->ChildSlot + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background"))) + .Content() + [ + SNew(SScrollBox) + + SScrollBox::Slot() + [ + SNew(SMultiLineEditableTextBox) + .Text(FText::FromString(InArgs._LogText)) + .AutoWrapText(true) + .IsReadOnly(true) + ] + ] + ]; +} + + +void +FHoudiniEngineDetails::CreateWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // 0. Houdini Engine Icon + FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs); + + // 1. Create Generate Category + FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 2. Create Bake Category + FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 3. Create Asset Options Category + FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 4. Create Help and Debug Category + FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs); + +} + +void +FHoudiniEngineDetails::CreateHoudiniEngineIconWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Skip drawing the icon if the icon image is not loaded correctly. + TSharedPtr HoudiniEngineUIIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIIconBrush(); + if (!HoudiniEngineUIIconBrush.IsValid()) + return; + + FDetailWidgetRow & Row = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef Box = SNew(SHorizontalBox); + TSharedPtr Image; + + Box->AddSlot() + .AutoWidth() + .Padding(0.0f, 5.0f, 5.0f, 10.0f) + .HAlign(HAlign_Left) + [ + SNew(SBox) + .HeightOverride(30) + .WidthOverride(208) + [ + SAssignNew(Image, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + Image->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIIconBrush]() { + return HoudiniEngineUIIconBrush.Get(); + }))); + + Row.WholeRowWidget.Widget = Box; + Row.IsEnabled(false); +} + +void +FHoudiniEngineDetails::CreateGenerateWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + auto OnReBuildClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->MarkAsNeedRebuild(); + } + + return FReply::Handled(); + }; + + auto OnRecookClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->MarkAsNeedCook(); + } + + return FReply::Handled(); + }; + + auto ShouldEnableResetParametersButtonLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + // Reset parameters to default values? + for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) + { + UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); + + if (NextParm && !NextParm->IsDefault()) + return true; + } + } + + return false; + }; + + auto OnResetParametersClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + // Reset parameters to default values? + for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) + { + UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); + + if (NextParm && !NextParm->IsDefault()) + { + NextParm->RevertToDefault(); + } + } + } + + return FReply::Handled(); + }; + + auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) + { + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FString NewPathStr = Val.ToString(); + + if (NewPathStr.IsEmpty()) + return; + + if (NewPathStr.StartsWith("Game/")) + { + NewPathStr = "/" + NewPathStr; + } + + FString AbsolutePath; + if (NewPathStr.StartsWith("/Game/")) + { + FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); + } + + if (!FPaths::DirectoryExists(AbsolutePath)) + { + HOUDINI_LOG_WARNING(TEXT("Invalid path")); + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + return; + } + + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr)) + continue; + + NextHAC->TemporaryCookFolder.Path = NewPathStr; + } + }; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_GENERATE); + + // Button Row (draw only if expanded) + if (!MainHAC->bGenerateMenuExpanded) + return; + + TSharedPtr HoudiniEngineUIRebuildIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRebuildIconBrush(); + TSharedPtr HoudiniEngineUIRecookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + TSharedPtr HoudiniEngineUIResetParametersIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIResetParametersIconBrush(); + + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef ButtonHorizontalBox = SNew(SHorizontalBox); + + // Rebuild button + TSharedPtr RebuildButton; + TSharedPtr RebuildButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(15.0f, 0.0f, 0.0f, 2.0f) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(RebuildButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) + //.Text(FText::FromString("Rebuild")) + .Visibility(EVisibility::Visible) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) + ] + ] + .OnClicked_Lambda(OnReBuildClickedLambda) + ] + ]; + + if (HoudiniEngineUIRebuildIconBrush.IsValid()) + { + TSharedPtr RebuildImage; + RebuildButtonHorizontalBox->AddSlot() + //.Padding(25.0f, 0.0f, 3.0f, 0.0f) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RebuildImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + RebuildImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { + return HoudiniEngineUIRebuildIconBrush.Get(); + }))); + } + + RebuildButtonHorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) + [ + SNew(STextBlock) + .Text(FText::FromString("Rebuild")) + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; + ButtonRow.IsEnabled(false); + + // Recook button + TSharedPtr RecookButton; + TSharedPtr RecookButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(RecookButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) + //.Text(FText::FromString("Recook")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnRecookClickedLambda) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) + ] + ] + ] + ]; + + if (HoudiniEngineUIRecookIconBrush.IsValid()) + { + TSharedPtr RecookImage; + RecookButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RecookImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + RecookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { + return HoudiniEngineUIRecookIconBrush.Get(); + }))); + } + + RecookButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Recook")) + ]; + + // Reset Parameters button + TSharedPtr ResetParametersButton; + TSharedPtr ResetParametersButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(ResetParametersButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsResetParametersAssetButton", "Reset the selected Houdini Asset's parameters to their default values.")) + //.Text(FText::FromString("Reset Parameters")) + .IsEnabled_Lambda(ShouldEnableResetParametersButtonLambda) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnResetParametersClickedLambda) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(ResetParametersButtonHorizontalBox, SHorizontalBox) + ] + ] + ] + ]; + + if (HoudiniEngineUIResetParametersIconBrush.IsValid()) + { + TSharedPtr ResetParametersImage; + ResetParametersButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(0.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(ResetParametersImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + ResetParametersImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIResetParametersIconBrush]() { + return HoudiniEngineUIResetParametersIconBrush.Get(); + }))); + } + + ResetParametersButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + //.FillWidth(4.2f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + //.MinDesiredWidth(160.f) + .Text(FText::FromString("Reset Parameters")) + ]; + + + // Temp Cook Folder Row + FDetailWidgetRow & TempCookFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef TempCookFolderRowHorizontalBox = SNew(SHorizontalBox); + + TempCookFolderRowHorizontalBox->AddSlot() + //.Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineTemporaryCookFolderLabel", "Temporary Cook Folder")) + .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, MAterials, Textures..) that are generated by Houdini Assets when they cook.")) + ] + ]; + + TempCookFolderRowHorizontalBox->AddSlot() + .MaxWidth(235.0f) + [ + SNew(SBox) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, Materials, Textures..) that are generated by Houdini Assets when they cook.")) + .HintText(LOCTEXT("HoudiniEngineTempCookFolderHintText", "Input to set temporary cook folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainHAC->TemporaryCookFolder.Path)) + .OnTextCommitted_Lambda(OnCookFolderTextCommittedLambda) + ] + ]; + + TempCookFolderRow.WholeRowWidget.Widget = TempCookFolderRowHorizontalBox; +} + +void +FHoudiniEngineDetails::OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC) +{ + if (!IsValid(InHAC)) + return; + + if (!bInState) + { + if (InHAC->GetOnPostCookBakeDelegate().IsBound()) + InHAC->GetOnPostCookBakeDelegate().Unbind(); + } + else + { + InHAC->GetOnPostCookBakeDelegate().BindLambda([](UHoudiniAssetComponent* HAC) + { + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + HAC, + HAC->bReplacePreviousBake, + HAC->HoudiniEngineBakeOption, + HAC->bRemoveOutputAfterBake); + }); + } +} + +void +FHoudiniEngineDetails::CreateBakeWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE); + + if (!MainHAC->bBakeMenuExpanded) + return; + + auto OnBakeButtonClickedLambda = [InHACs, MainHAC]() + { + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + NextHAC, + MainHAC->bReplacePreviousBake, + MainHAC->HoudiniEngineBakeOption, + MainHAC->bRemoveOutputAfterBake); + } + + return FReply::Handled(); + }; + + auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) + { + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FString NewPathStr = Val.ToString(); + + if (NewPathStr.IsEmpty()) + return; + + if (NewPathStr.StartsWith("Game/")) + { + NewPathStr = "/" + NewPathStr; + } + + FString AbsolutePath; + if (NewPathStr.StartsWith("/Game/")) + { + FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); + } + + if (!FPaths::DirectoryExists(AbsolutePath)) + { + HOUDINI_LOG_WARNING(TEXT("Invalid path")); + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + return; + } + + + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + if (NextHAC->BakeFolder.Path.Equals(NewPathStr)) + continue; + + NextHAC->BakeFolder.Path = NewPathStr; + } + }; + + // Button Row + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + + // Bake Button + TSharedPtr BakeButton; + TSharedPtr BakeButtonHorizontalBox; + + ButtonRowHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(15.f, 0.0f, 0.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(BakeButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsBakeButton", "Bake the Houdini Asset Component(s).")) + //.Text(FText::FromString("Recook")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnBakeButtonClickedLambda) + .Content() + [ + SAssignNew(BakeButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); + if (BakeIconBrush.IsValid()) + { + TSharedPtr BakeImage; + BakeButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(BakeImage, SImage) + ] + ]; + + BakeImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([BakeIconBrush]() { + return BakeIconBrush.Get(); + }))); + } + + BakeButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Bake")) + ]; + + // Bake Type ComboBox + TSharedPtr>> TypeComboBox; + + TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEngineBakeTypeOptionsLabels(); + TSharedPtr IntialSelec; + if (OptionSource) + { + IntialSelec = (*OptionSource)[(int)MainHAC->HoudiniEngineBakeOption]; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + //.MaxWidth(103.f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + //.WidthOverride(103.f) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [MainHAC, InHACs](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EHoudiniEngineBakeOption NewOption = + FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + MainHAC->HoudiniEngineBakeOption = NewOption; + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + [ + SNew(STextBlock) + .Text_Lambda([MainHAC]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(MainHAC->HoudiniEngineBakeOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; + + // Clear Output After Baking Row + FDetailWidgetRow & ClearOutputAfterBakingRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); + + // Remove Output Checkbox + TSharedPtr CheckBoxRemoveOutput; + TSharedPtr CheckBoxAutoBake; + TSharedPtr CheckBoxRecenterBakedActors; + TSharedPtr CheckBoxReplacePreviousBake; + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRemoveOutput, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBox", "Remove HDA Output After Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBoxToolTip", "After baking the existing output of this Houdini Asset Actor will be removed.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bRemoveOutputAfterBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bRemoveOutputAfterBake = bNewState; + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bRecenterBakedActors = bNewState; + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + // TODO: find a better way to manage the initial binding/unbinding of the post cook bake delegate + // We do this here to ensure the delegate is bound/unbound correctly when the UI is initially drawn + // Currently we have the problem that the HoudiniEngineRuntime and HoudiniEngine modules cannot access + // the FHoudiniEngineBakeUtils code that is in HoudiniEngineEditor (this is the primary reason for the delegate and + // managing the delegate in this way). + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + const bool bState = NextHAC->IsBakeAfterNextCookEnabled(); + NextHAC->SetBakeAfterNextCookEnabled(bState); + OnBakeAfterCookChangedHelper(bState, NextHAC); + } + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxAutoBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake the next cook.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->IsBakeAfterNextCookEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->SetBakeAfterNextCookEnabled(bNewState); + OnBakeAfterCookChangedHelper(bNewState, NextHAC); + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + // Replace Checkbox + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxReplacePreviousBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBox", "Replace Previous Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBoxToolTip", "When baking replace the previous bake's output instead of creating additional output actors/components/objects.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bReplacePreviousBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + MainHAC->bReplacePreviousBake = bNewState; + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + ClearOutputAfterBakingRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; + + // Bake Folder Row + FDetailWidgetRow & BakeFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) + .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) + ] + ]; + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .MaxWidth(235.0) + [ + SNew(SBox) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) + .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainHAC->BakeFolder.Path)) + .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) + ] + ]; + + BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; + + switch (MainHAC->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToActorToolTip", + "Bake this Houdini Asset Actor and its components to native unreal actors and components, replacing the previous baked result.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToActorToolTip", + "Bake this Houdini Asset Actor and its components to native unreal actors and components.")); + } + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToBlueprintToolTip", + "Bake this Houdini Asset Actor to a blueprint.")); + } + break; + + case EHoudiniEngineBakeOption::ToFoliage: + { + if (!FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(MainHAC)) + { + // If the HAC does not have instanced output, disable Bake to Foliage + BakeButton->SetEnabled(false); + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonNoInstancedOutputToolTip", + "The Houdini Asset must be outputing at least one instancer in order to be able to bake to Foliage.")); + } + else + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToFoliageToolTip", + "Add this Houdini Asset Actor's instancers to the current level's Foliage, replacing the previously baked foliage instancers from this actor.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToFoliageToolTip", + "Add this Houdini Asset Actor's instancers to the current level's Foliage.")); + } + } + } + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToWorldOutlinerToolTip", + "Not implemented.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToWorldOutlinerToolTip", + "Not implemented.")); + } + } + break; + } + + // Todo: remove me! + if (MainHAC->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToWorldOutliner) + BakeButton->SetEnabled(false); + +} + +void +FHoudiniEngineDetails::CreateAssetOptionsWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Header Row + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS); + + if (!MainHAC->bAssetOptionMenuExpanded) + return; + + auto IsCheckedParameterChangedLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateParameterChangedLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnParameterChange = bChecked; + } + }; + + auto IsCheckedTransformChangeLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedTransformChangeLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnTransformChange = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedAssetInputCookLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedAssetInputCookLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnAssetInputCook = bChecked; + } + }; + + auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedPushTransformToHoudiniLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bUploadTransformsToHoudiniEngine = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedDoNotGenerateOutputsLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bOutputless = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedOutputTemplatedGeosLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bOutputTemplateGeos = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + // Checkboxes row + FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef CheckBoxesHorizontalBox = SNew(SHorizontalBox); + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + CheckBoxesHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + CheckBoxesHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineCookTriggersLabel", "Cook Triggers")) + ] + ]; + + // Parameter change check box + FText TooltipText = LOCTEXT("HoudiniEngineParameterChangeTooltip", "If enabled, modifying a parameter or input on this Houdini Asset will automatically trigger a cook of the HDA in Houdini."); + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineParameterChangeCheckBoxLabel", "On Parameter/Input Change")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateParameterChangedLambda) + .IsChecked_Lambda(IsCheckedParameterChangedLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Transform change check box + TooltipText = LOCTEXT("HoudiniEngineTransformChangeTooltip", "If enabled, changing the Houdini Asset Actor's transform in Unreal will also update its HDA's node transform in Houdini, and trigger a recook of the HDA with the updated transform."); + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineTransformChangeCheckBoxLabel", "On Transform Change")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedTransformChangeLambda) + .IsChecked_Lambda(IsCheckedTransformChangeLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Triggers Downstream cook checkbox + TooltipText = LOCTEXT("HoudiniEngineAssetInputCookTooltip", "When enabled, this asset will automatically re-cook after one its asset input has finished cooking."); + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineAssetInputCheckBoxLabel", "On Asset Input Cook")) + .ToolTipText(TooltipText) + ] + + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedAssetInputCookLambda) + .IsChecked_Lambda(IsCheckedAssetInputCookLambda) + .ToolTipText(TooltipText) + ] + ]; + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous")) + ]; + + // Push Transform to Houdini check box + TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini."); + RightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini")) + .ToolTipText(TooltipText) + ] + + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda) + .IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Do not generate output check box + TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs."); + RightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda) + .IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Output templated geos check box + TooltipText = LOCTEXT("HoudiniEnginOutputTemplatesTooltip", "If enabled, Geometry nodes in the asset that have the template flag will be outputed."); + RightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Output Templated Geos")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedOutputTemplatedGeosLambda) + .IsChecked_Lambda(IsCheckedOutputTemplatedGeosLambda) + .ToolTipText(TooltipText) + ] + ]; + + CheckBoxesRow.WholeRowWidget.Widget = CheckBoxesHorizontalBox; +} + +void +FHoudiniEngineDetails::CreateHelpAndDebugWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Header Row + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG); + + if (!MainHAC->bHelpAndDebugMenuExpanded) + return; + + auto OnFetchCookLogButtonClickedLambda = [InHACs]() + { + return ShowCookLog(InHACs); + }; + + auto OnHelpButtonClickedLambda = [MainHAC]() + { + return ShowAssetHelp(MainHAC); + }; + + // Button Row + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + TSharedPtr CookLogButtonHorizontalBox = SNew(SHorizontalBox); + + // Fetch Cook Log button + ButtonRowHorizontalBox->AddSlot() + //.Padding(15.0f, 0.0f, 0.0f, 0.0f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(FText::FromString("Fetch and display all cook logs available for this Houdini Asset Actor.")) + //.Text(FText::FromString("Fetch Cook Log")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnFetchCookLogButtonClickedLambda) + .Content() + [ + SAssignNew(CookLogButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr CookLogIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUICookLogIconBrush(); + if (CookLogIconBrush.IsValid()) + { + TSharedPtr CookImage; + CookLogButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookImage, SImage) + ] + ]; + + CookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookLogIconBrush]() { + return CookLogIconBrush.Get(); + }))); + } + + CookLogButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Show Cook Logs")) + ]; + + // Asset Help Button + TSharedPtr AssetHelpButtonHorizontalBox; + ButtonRowHorizontalBox->AddSlot() + //.Padding(4.0, 0.0f, 0.0f, 0.0f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(FText::FromString("Display this Houdini Asset Actor's HDA help.")) + //.Text(FText::FromString("Asset Help")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnHelpButtonClickedLambda) + .Content() + [ + SAssignNew(AssetHelpButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr AssetHelpIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIAssetHelpIconBrush(); + if (AssetHelpIconBrush.IsValid()) + { + TSharedPtr AssetHelpImage; + AssetHelpButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(AssetHelpImage, SImage) + ] + ]; + + AssetHelpImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([AssetHelpIconBrush]() { + return AssetHelpIconBrush.Get(); + }))); + } + + AssetHelpButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Asset Help")) + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; +} + +FMenuBuilder +FHoudiniEngineDetails::Helper_CreateHoudiniAssetPicker() +{ + auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + return true; + }; + + auto OnActorSelected = [](AActor* Actor) + { + UE_LOG(LogTemp, Warning, TEXT("Actor Selected")); + + return; + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterHoudiniAssetLambda); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("HoudiniEngineDetailsAssetPicker", "Asset")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +const FSlateBrush * +FHoudiniEngineDetails::GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const +{ + if (HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + +/* +TSharedRef< SWidget > +FHoudiniEngineDetails::OnGetHoudiniAssetMenuContent(TArray InHACs) +{ + TArray< const UClass * > AllowedClasses; + AllowedClasses.Add(UHoudiniAsset::StaticClass()); + + TArray< UFactory * > NewAssetFactories; + + UHoudiniAsset * HoudiniAsset = nullptr; + if (InHACs.Num() > 0) + { + UHoudiniAssetComponent * HoudiniAssetComponent = InHACs[0]; + HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; + } + + auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + return true; + }; + + // Delegate for filtering Houdini assets. + FOnShouldFilterAsset OnShouldFilterHoudiniAsset = FOnShouldFilterAsset::CreateLambda(OnShouldFilterHoudiniAssetLambda); + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(HoudiniAsset), true, + AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset, + FOnAssetSelected::CreateLambda([](const FAssetData & AssetData) {}), + FSimpleDelegate::CreateLambda([]() { }) + ); +} +*/ + +FReply +FHoudiniEngineDetails::ShowCookLog(TArray InHACS) +{ + TSharedPtr< SWindow > ParentWindow; + FString CookLog = FHoudiniEngineUtils::GetCookLog(InHACS); + + // 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("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (ParentWindow.IsValid()) + { + TSharedPtr HoudiniAssetCookLog; + + TSharedRef Window = + SNew(SWindow) + .Title(LOCTEXT("WindowTitle", "Houdini Cook Log")) + .ClientSize(FVector2D(640, 480)); + + Window->SetContent( + SAssignNew(HoudiniAssetCookLog, SHoudiniAssetLogWidget) + .LogText(CookLog)); + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + } + + return FReply::Handled(); +} + +FReply +FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC) +{ + if (!InHAC) + return FReply::Handled(); + + FString AssetHelp = FHoudiniEngineUtils::GetAssetHelp(InHAC); + + 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 HoudiniAssetHelpLog; + + TSharedRef Window = + SNew(SWindow) + .Title(LOCTEXT("WindowTitle", "Houdini Asset Help")) + .ClientSize(FVector2D(640, 480)); + + Window->SetContent( + SAssignNew(HoudiniAssetHelpLog, SHoudiniAssetLogWidget) + .LogText(AssetHelp)); + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + } + return FReply::Handled(); +} + +void +FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return; + + FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + HoudiniAssetComponent->bGenerateMenuExpanded = !HoudiniAssetComponent->bGenerateMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + HoudiniAssetComponent->bBakeMenuExpanded = !HoudiniAssetComponent->bBakeMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + HoudiniAssetComponent->bAssetOptionMenuExpanded = !HoudiniAssetComponent->bAssetOptionMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + HoudiniAssetComponent->bHelpAndDebugMenuExpanded = !HoudiniAssetComponent->bHelpAndDebugMenuExpanded; + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniAssetComponent, true); + + return FReply::Handled(); + }); + + TFunction GetText = [MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT); + break; + } + return FText::FromString(""); + }; + + TFunction GetExpanderBrush = [HoudiniAssetComponent, MenuSection](SButton* InExpanderArrow) + { + FName ResourceName; + bool bMenuExpanded = false; + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + bMenuExpanded = HoudiniAssetComponent->bGenerateMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + bMenuExpanded = HoudiniAssetComponent->bBakeMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + bMenuExpanded = HoudiniAssetComponent->bAssetOptionMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + bMenuExpanded = HoudiniAssetComponent->bHelpAndDebugMenuExpanded; + } + + if (bMenuExpanded) + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + }; + + return AddHeaderRow(HoudiniEngineCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); +} + +void +FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]() + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + InPDGAssetLink->Modify(); + InPDGAssetLink->bBakeMenuExpanded = !InPDGAssetLink->bBakeMenuExpanded; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded), InPDGAssetLink); + break; + } + + //FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, true); + + return FReply::Handled(); + }); + + TFunction GetText = [MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); + break; + } + return FText::FromString(""); + }; + + TFunction GetExpanderBrush = [InPDGAssetLink, MenuSection](SButton* InExpanderArrow) + { + FName ResourceName; + bool bMenuExpanded = false; + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + bMenuExpanded = InPDGAssetLink->bBakeMenuExpanded; + break; + } + + if (bMenuExpanded) + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + }; + + return AddHeaderRow(PDGCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); +} + +void +FHoudiniEngineDetails::AddHeaderRow( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + FOnClicked& InOnExpanderClick, + TFunction& InGetText, + TFunction& InGetExpanderBrush) +{ + // Header Row + FDetailWidgetRow & HeaderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedPtr HeaderHorizontalBox; + HeaderRow.WholeRowWidget.Widget = SAssignNew(HeaderHorizontalBox, SHorizontalBox); + + TSharedPtr ExpanderImage; + TSharedPtr ExpanderArrow; + HeaderHorizontalBox->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth() + [ + SAssignNew(ExpanderArrow, SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + .OnClicked(InOnExpanderClick) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + HeaderHorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SNew(STextBlock) + .Text_Lambda([InGetText](){return InGetText(); }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + ExpanderImage->SetImage( + TAttribute::Create( + [ExpanderArrow, InGetExpanderBrush]() + { + return InGetExpanderBrush(ExpanderArrow.Get()); + })); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h index 998725c16..cc52add15 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h @@ -1,117 +1,117 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#pragma once - -#include "CoreMinimal.h" - -class IDetailCategoryBuilder; -class UHoudiniAssetComponent; -class UHoudiniPDGAssetLink; -class FMenuBuilder; -class SBorder; -class SButton; - -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SCompoundWidget.h" -#include "Framework/SlateDelegates.h" - -class SHoudiniAssetLogWidget : public SCompoundWidget -{ -public: - - SLATE_BEGIN_ARGS(SHoudiniAssetLogWidget) - : _LogText(TEXT("")) - {} - - SLATE_ARGUMENT(FString, LogText) - SLATE_END_ARGS() - - /** Widget construct. **/ - void Construct(const FArguments & InArgs); -}; - -class FHoudiniEngineDetails : public TSharedFromThis -{ -public: - static void CreateWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateHoudiniEngineIconWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateGenerateWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateBakeWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreatePDGBakeWidgets( - IDetailCategoryBuilder& InPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink); - - static void CreateAssetOptionsWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateHelpAndDebugWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static FReply ShowCookLog(TArray InHACS); - - static FReply ShowAssetHelp(UHoudiniAssetComponent * InHAC); - - static FMenuBuilder Helper_CreateHoudiniAssetPicker(); - - const FSlateBrush * GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const; - - /** Construct drop down menu content for Houdini asset. **/ - //static TSharedRef< SWidget > OnGetHoudiniAssetMenuContent(TArray InHACs); - - static void AddHeaderRowForHoudiniAssetComponent( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - UHoudiniAssetComponent* HoudiniAssetComponent, - int32 MenuSection); - - static void AddHeaderRowForHoudiniPDGAssetLink( - IDetailCategoryBuilder& PDGCategoryBuilder, - UHoudiniPDGAssetLink* InPDGAssetLink, - int32 MenuSection); - - static void AddHeaderRow( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - FOnClicked& InOnExpanderClick, - TFunction& InGetText, - TFunction& InGetExpanderBrush); - - // Helper for binding/unbinding the post cook bake delegate - static void OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC); -}; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once + +#include "CoreMinimal.h" + +class IDetailCategoryBuilder; +class UHoudiniAssetComponent; +class UHoudiniPDGAssetLink; +class FMenuBuilder; +class SBorder; +class SButton; + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Framework/SlateDelegates.h" + +class SHoudiniAssetLogWidget : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SHoudiniAssetLogWidget) + : _LogText(TEXT("")) + {} + + SLATE_ARGUMENT(FString, LogText) + SLATE_END_ARGS() + + /** Widget construct. **/ + void Construct(const FArguments & InArgs); +}; + +class FHoudiniEngineDetails : public TSharedFromThis +{ +public: + static void CreateWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateHoudiniEngineIconWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateGenerateWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateBakeWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreatePDGBakeWidgets( + IDetailCategoryBuilder& InPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink); + + static void CreateAssetOptionsWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateHelpAndDebugWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static FReply ShowCookLog(TArray InHACS); + + static FReply ShowAssetHelp(UHoudiniAssetComponent * InHAC); + + static FMenuBuilder Helper_CreateHoudiniAssetPicker(); + + const FSlateBrush * GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const; + + /** Construct drop down menu content for Houdini asset. **/ + //static TSharedRef< SWidget > OnGetHoudiniAssetMenuContent(TArray InHACs); + + static void AddHeaderRowForHoudiniAssetComponent( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + UHoudiniAssetComponent* HoudiniAssetComponent, + int32 MenuSection); + + static void AddHeaderRowForHoudiniPDGAssetLink( + IDetailCategoryBuilder& PDGCategoryBuilder, + UHoudiniPDGAssetLink* InPDGAssetLink, + int32 MenuSection); + + static void AddHeaderRow( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + FOnClicked& InOnExpanderClick, + TFunction& InGetText, + TFunction& InGetExpanderBrush); + + // Helper for binding/unbinding the post cook bake delegate + static void OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC); +}; + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp index f729653c6..64137804f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp @@ -1,1474 +1,1474 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineEditor.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetBroker.h" -#include "HoudiniAssetActorFactory.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetComponentDetails.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniRuntimeSettingsDetails.h" -#include "HoudiniSplineComponentVisualizer.h" -#include "HoudiniHandleComponentVisualizer.h" -#include "AssetTypeActions_HoudiniAsset.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPackageParams.h" - -#include "Modules/ModuleManager.h" -#include "Interfaces/IPluginManager.h" -#include "HAL/PlatformFilemanager.h" -#include "Misc/MessageDialog.h" -#include "Misc/Paths.h" -#include "AssetRegistryModule.h" -#include "PropertyEditorModule.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "LevelEditor.h" -#include "Templates/SharedPointer.h" -#include "Framework/Application/SlateApplication.h" -#include "HAL/ConsoleManager.h" -#include "Editor/UnrealEdEngine.h" -#include "Editor.h" -#include "UnrealEdGlobals.h" -#include "Engine/Selection.h" -#include "Widgets/Input/SCheckBox.h" -#include "Logging/LogMacros.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FHoudiniEngineEditor, HoudiniEngineEditor); -DEFINE_LOG_CATEGORY(LogHoudiniEngineEditor); - -FHoudiniEngineEditor * -FHoudiniEngineEditor::HoudiniEngineEditorInstance = nullptr; - -FHoudiniEngineEditor & -FHoudiniEngineEditor::Get() -{ - return *HoudiniEngineEditorInstance; -} - -bool -FHoudiniEngineEditor::IsInitialized() -{ - return FHoudiniEngineEditor::HoudiniEngineEditorInstance != nullptr; -} - -FHoudiniEngineEditor::FHoudiniEngineEditor() -{ -} - -void FHoudiniEngineEditor::StartupModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine Editor module.")); - - // Create style set. - FHoudiniEngineStyle::Initialize(); - - // Initilizes various resources used by our editor UI widgets - InitializeWidgetResource(); - - // Register asset type actions. - RegisterAssetTypeActions(); - - // Register asset brokers. - RegisterAssetBrokers(); - - // Register component visualizers. - RegisterComponentVisualizers(); - - // Register detail presenters. - RegisterDetails(); - - // Register actor factories. - RegisterActorFactories(); - - // Extends the file menu. - ExtendMenu(); - - // Extend the World Outliner Menu - AddLevelViewportMenuExtender(); - - // Adds the custom console commands - RegisterConsoleCommands(); - - // Register global undo / redo callbacks. - //RegisterForUndo(); - - //RegisterPlacementModeExtensions(); - - // Register for any FEditorDelegates that we are interested in, such as - // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds - RegisterEditorDelegates(); - - // Store the instance. - FHoudiniEngineEditor::HoudiniEngineEditorInstance = this; - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module startup complete.")); -} - -void FHoudiniEngineEditor::ShutdownModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine Editor module.")); - - // Deregister editor delegates - UnregisterEditorDelegates(); - - // Deregister console commands - UnregisterConsoleCommands(); - - // Remove the level viewport Menu extender - RemoveLevelViewportMenuExtender(); - - // Unregister asset type actions. - UnregisterAssetTypeActions(); - - // Unregister asset brokers. - //UnregisterAssetBrokers(); - - // Unregister detail presenters. - UnregisterDetails(); - - // Unregister our component visualizers. - //UnregisterComponentVisualizers(); - - // Unregister global undo / redo callbacks. - //UnregisterForUndo(); - - //UnregisterPlacementModeExtensions(); - - // Unregister the styleset - FHoudiniEngineStyle::Shutdown(); - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module shutdown complete.")); -} - -FString -FHoudiniEngineEditor::GetHoudiniEnginePluginDir() -{ - FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(EnginePluginDir)) - return EnginePluginDir; - - FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(ProjectPluginDir)) - return ProjectPluginDir; - - TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); - FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; - if (FPaths::DirectoryExists(PluginBaseDir)) - return PluginBaseDir; - - HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); - - return EnginePluginDir; -} - -void -FHoudiniEngineEditor::RegisterDetails() -{ - FPropertyEditorModule & PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Register details presenter for our component type and runtime settings. - PropertyModule.RegisterCustomClassLayout( - TEXT("HoudiniAssetComponent"), - FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniAssetComponentDetails::MakeInstance)); - - PropertyModule.RegisterCustomClassLayout( - TEXT("HoudiniRuntimeSettings"), - FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniRuntimeSettingsDetails::MakeInstance)); -} - -void -FHoudiniEngineEditor::UnregisterDetails() -{ - if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) - { - FPropertyEditorModule & PropertyModule = - FModuleManager::LoadModuleChecked("PropertyEditor"); - - PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniAssetComponent")); - PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniRuntimeSettings")); - } -} - -void -FHoudiniEngineEditor::RegisterComponentVisualizers() -{ - if (GUnrealEd) - { - // Register Houdini spline visualizer - SplineComponentVisualizer = MakeShareable(new FHoudiniSplineComponentVisualizer); - if (SplineComponentVisualizer.IsValid()) - { - GUnrealEd->RegisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName(), SplineComponentVisualizer); - SplineComponentVisualizer->OnRegister(); - } - - // Register Houdini handle visualizer - HandleComponentVisualizer = MakeShareable(new FHoudiniHandleComponentVisualizer); - if (HandleComponentVisualizer.IsValid()) - { - GUnrealEd->RegisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName(), HandleComponentVisualizer); - HandleComponentVisualizer->OnRegister(); - } - } -} - -void -FHoudiniEngineEditor::UnregisterComponentVisualizers() -{ - if (GUnrealEd) - { - // Unregister Houdini spline visualizer - if(SplineComponentVisualizer.IsValid()) - GUnrealEd->UnregisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); - - // Unregister Houdini handle visualizer - if (HandleComponentVisualizer.IsValid()) - GUnrealEd->UnregisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - } -} - -void -FHoudiniEngineEditor::RegisterAssetTypeAction(IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action) -{ - AssetTools.RegisterAssetTypeActions(Action); - AssetTypeActions.Add(Action); -} - -void -FHoudiniEngineEditor::RegisterAssetTypeActions() -{ - // Create and register asset type actions for Houdini asset. - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools").Get(); - RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_HoudiniAsset())); -} - -void -FHoudiniEngineEditor::UnregisterAssetTypeActions() -{ - // Unregister asset type actions we have previously registered. - if (FModuleManager::Get().IsModuleLoaded("AssetTools")) - { - IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >("AssetTools").Get(); - - for (int32 Index = 0; Index < AssetTypeActions.Num(); ++Index) - AssetTools.UnregisterAssetTypeActions(AssetTypeActions[Index].ToSharedRef()); - - AssetTypeActions.Empty(); - } -} - -void -FHoudiniEngineEditor::RegisterAssetBrokers() -{ - // Create and register broker for Houdini asset. - HoudiniAssetBroker = MakeShareable(new FHoudiniAssetBroker()); - FComponentAssetBrokerage::RegisterBroker( HoudiniAssetBroker, UHoudiniAssetComponent::StaticClass(), true, true ); -} - -void -FHoudiniEngineEditor::UnregisterAssetBrokers() -{ - if (UObjectInitialized()) - { - // Unregister broker. - FComponentAssetBrokerage::UnregisterBroker( HoudiniAssetBroker ); - } -} - -void -FHoudiniEngineEditor::RegisterActorFactories() -{ - if (GEditor) - { - UHoudiniAssetActorFactory * HoudiniAssetActorFactory = - NewObject< UHoudiniAssetActorFactory >(GetTransientPackage(), UHoudiniAssetActorFactory::StaticClass()); - - GEditor->ActorFactories.Add(HoudiniAssetActorFactory); - } -} - -void -FHoudiniEngineEditor::BindMenuCommands() -{ - HEngineCommands = MakeShareable(new FUICommandList); - - FHoudiniEngineCommands::Register(); - const FHoudiniEngineCommands& Commands = FHoudiniEngineCommands::Get(); - - // Session - - HEngineCommands->MapAction( - Commands._CreateSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CreateSession(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._ConnectSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ConnectSession(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._StopSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopSession(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RestartSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RestartSession(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OpenSessionSync, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OpenSessionSync(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); - - HEngineCommands->MapAction( - Commands._CloseSessionSync, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CloseSessionSync(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); - - HEngineCommands->MapAction( - Commands._ViewportSyncNone, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(0); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 0); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncHoudini, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(1); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 1); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncUnreal, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(2); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 2); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncBoth, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(3); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 3); }) - ); - - // PDG commandlet - HEngineCommands->MapAction( - Commands._IsPDGCommandletEnabled, - FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::SetPDGCommandletEnabled(!FHoudiniEngineCommands::IsPDGCommandletEnabled()); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletEnabled(); }) - ); - - HEngineCommands->MapAction( - Commands._StartPDGCommandlet, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StartPDGCommandlet(); }), - FCanExecuteAction::CreateLambda([]() - { - return FHoudiniEngineCommands::IsPDGCommandletEnabled() && !FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); - }) - ); - - HEngineCommands->MapAction( - Commands._StopPDGCommandlet, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopPDGCommandlet(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); })); - - // Plugin - - HEngineCommands->MapAction( - Commands._InstallInfo, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowInstallInfo(); }), - FCanExecuteAction::CreateLambda([]() { return false; })); - - HEngineCommands->MapAction( - Commands._PluginSettings, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowPluginSettings(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Files - - HEngineCommands->MapAction( - Commands._OpenInHoudini, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::OpenInHoudini(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._SaveHIPFile, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::SaveHIPFile(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._CleanUpTempFolder, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CleanUpTempFolder(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Help and support - - HEngineCommands->MapAction( - Commands._ReportBug, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::ReportBug(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OnlineDoc, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineDocumentation(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OnlineForum, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineForum(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Actions - - HEngineCommands->MapAction( - Commands._CookAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookAllAssets(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._CookSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RebuildAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildAllAssets(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RebuildSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._BakeAll, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::BakeAllAssets(); }), - FCanExecuteAction::CreateLambda([](){ return true; })); - - HEngineCommands->MapAction( - Commands._BakeSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::BakeSelection(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._RefineAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._RefineSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._PauseAssetCooking, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::PauseAssetCooking(); }), - FCanExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::IsSessionValid(); }), - FIsActionChecked::CreateLambda([](){ return FHoudiniEngineCommands::IsAssetCookingPaused(); })); - - // Non menu command (used for shortcuts only) - - // Append the command to the editor module - FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >("LevelEditor"); - LevelEditorModule.GetGlobalLevelEditorActions()->Append(HEngineCommands.ToSharedRef()); -} - -void -FHoudiniEngineEditor::ExtendMenu() -{ - if (IsRunningCommandlet()) - return; - - // We need to add/bind the UI Commands to their functions first - BindMenuCommands(); - - MainMenuExtender = MakeShareable(new FExtender); - - // Extend File menu, we will add Houdini section. - MainMenuExtender->AddMenuExtension( - "FileLoadAndSave", - EExtensionHook::After, - HEngineCommands, - FMenuExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniFileMenuExtension)); - - MainMenuExtender->AddMenuBarExtension( - "Edit", - EExtensionHook::After, - HEngineCommands, - FMenuBarExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniEditorMenu)); - - // Add our menu extender - FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); - LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); -} - -void -FHoudiniEngineEditor::AddHoudiniFileMenuExtension(FMenuBuilder & MenuBuilder) -{ - MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); - - // Icons used by the commands are defined in the HoudiniEngineStyle - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - - MenuBuilder.EndSection(); -} - -void -FHoudiniEngineEditor::AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder) -{ - // View - MenuBarBuilder.AddPullDownMenu( - LOCTEXT("HoudiniLabel", "Houdini Engine"), - LOCTEXT("HoudiniMenu_ToolTip", "Open the Houdini Engine menu"), - FNewMenuDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniMainMenuExtension), - "View"); -} - -void -FHoudiniEngineEditor::AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder) -{ - /* - MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); - // Icons used by the commands are defined in the HoudiniEngineStyle - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - MenuBuilder.EndSection(); - */ - - MenuBuilder.BeginSection("Session", LOCTEXT("SessionLabel", "Session")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CreateSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ConnectSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenSessionSync); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CloseSessionSync); - - // Viewport sync menu - struct FLocalMenuBuilder - { - static void FillViewportSyncMenu(FMenuBuilder& InSubMenuBuilder) - { - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncNone); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncHoudini); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncUnreal); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncBoth); - } - }; - - MenuBuilder.AddSubMenu( - LOCTEXT("SyncViewport", "Sync Viewport"), - LOCTEXT("SyncViewport_ToolTip", "Sync Viewport"), - FNewMenuDelegate::CreateStatic(&FLocalMenuBuilder::FillViewportSyncMenu), - false, - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._SyncViewport")); - - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("PDG", LOCTEXT("PDGLabel", "PDG")); - struct FLocalPDGMenuBuilder - { - static void FillPDGMenu(FMenuBuilder& InSubMenuBuilder) - { - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._IsPDGCommandletEnabled); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StartPDGCommandlet); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopPDGCommandlet); - } - }; - MenuBuilder.AddSubMenu( - LOCTEXT("PDGSubMenu", "Work Item Import Settings"), - LOCTEXT("PDGSubmenu_ToolTip", "PDG Work Item Import Settings"), - FNewMenuDelegate::CreateStatic(&FLocalPDGMenuBuilder::FillPDGMenu), - false, - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.PDGLink")); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Plugin", LOCTEXT("PluginLabel", "Plugin")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._InstallInfo); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PluginSettings); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("File", LOCTEXT("FileLabel", "File")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Help", LOCTEXT("HelpLabel", "Help And Support")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineDoc); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineForum); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Actions", LOCTEXT("ActionsLabel", "Actions")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - - - MenuBuilder.EndSection(); -} - -void -FHoudiniEngineEditor::RegisterForUndo() -{ - /* - if (GUnrealEd) - GUnrealEd->RegisterForUndo(this); - */ -} - -void -FHoudiniEngineEditor::UnregisterForUndo() -{ - /* - if (GUnrealEd) - GUnrealEd->UnregisterForUndo(this); - */ -} - -void -FHoudiniEngineEditor::RegisterPlacementModeExtensions() -{ - // Load custom houdini tools - /* - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - check(HoudiniRuntimeSettings); - - if (HoudiniRuntimeSettings->bHidePlacementModeHoudiniTools) - return; - - FPlacementCategoryInfo Info( - LOCTEXT("HoudiniCategoryName", "Houdini Engine"), - "HoudiniEngine", - TEXT("PMHoudiniEngine"), - 25 - ); - Info.CustomGenerator = []() -> TSharedRef { return SNew(SHoudiniToolPalette); }; - - IPlacementModeModule::Get().RegisterPlacementCategory(Info); - */ -} - -void -FHoudiniEngineEditor::UnregisterPlacementModeExtensions() -{ - /* - if (IPlacementModeModule::IsAvailable()) - { - IPlacementModeModule::Get().UnregisterPlacementCategory("HoudiniEngine"); - } - - HoudiniTools.Empty(); - */ -} - -void -FHoudiniEngineEditor::InitializeWidgetResource() -{ - // Choice labels for all the input types - //TArray> InputTypeChoiceLabels; - InputTypeChoiceLabels.Reset(); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Asset)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Landscape)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::World)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Skeletal)))); - - BlueprintInputTypeChoiceLabels.Reset(); - BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); - BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); - - // Choice labels for all Houdini curve types - HoudiniCurveTypeChoiceLabels.Reset(); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Polygon)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Nurbs)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Bezier)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Points)))); - - // Choice labels for all Houdini curve methods - HoudiniCurveMethodChoiceLabels.Reset(); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::CVs)))); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Breakpoints)))); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Freehand)))); - - // Choice labels for all Houdini ramp parameter interpolation methods - HoudiniParameterRampInterpolationLabels.Reset(); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CONSTANT)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::LINEAR)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CATMULL_ROM)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::MONOTONE_CUBIC)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BEZIER)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BSPLINE)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::HERMITE)))); - - // Choice labels for all Houdini curve output export types - HoudiniCurveOutputExportTypeLabels.Reset(); - HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Unreal Spline")))); - HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Houdini Spline")))); - - // Choice labels for all Unreal curve output curve types - //(for temporary, we need to figure out a way to access the output curve's info later) - UnrealCurveOutputCurveTypeLabels.Reset(); - UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Linear")))); - UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Curve")))); - - // Option labels for all landscape outputs bake options - HoudiniLandscapeOutputBakeOptionLabels.Reset(); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Current Level")))); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Image")))); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To New World")))); - - // Option labels for Houdini Engine PDG bake options - HoudiniEnginePDGBakeTypeOptionLabels.Reset(); - HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); - HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); - - // Option labels for Houdini Engine bake options - HoudiniEngineBakeTypeOptionLabels.Reset(); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToFoliage)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToWorldOutliner)))); - - // Option labels for Houdini Engine PDG bake options - HoudiniEnginePDGBakeSelectionOptionLabels.Reset(); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::All)))); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNetwork)))); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNode)))); - - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Reset(); - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::ReplaceExistingAssets)))); - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::CreateNewAssets)))); - - - static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); - - // Houdini Logo Brush - FString Icon128FilePath = IconsDir + TEXT("icon_houdini_logo_128"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) - { - const FName BrushName(*Icon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Logo Brush - FString HEIcon128FilePath = IconsDir + TEXT("icon_hengine_logo_128"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) - { - const FName BrushName(*HEIcon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Banner - FString HoudiniEngineUIIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_banner_d.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Rebuild Icon Brush - FString HoudiniEngineUIRebuildIconFilePath = IconsDir + TEXT("rebuild_all16x16.png"); - //FString HoudiniEngineUIRebuildIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_reload_icon.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRebuildIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIRebuildIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIRebuildIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Recook Icon Brush - //FString HoudiniEngineUIRecookIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_recook_icon.png"); - FString HoudiniEngineUIRecookIconFilePath = IconsDir + TEXT("cook_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRecookIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIRecookIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIRecookIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Reset Parameters Icon Brush - //FString HoudiniEngineUIResetParametersIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_resetparameters_icon.png"); - FString HoudiniEngineUIResetParametersIconFilePath = IconsDir + TEXT("reset_parameters16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIResetParametersIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIResetParametersIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIResetParametersIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Bake - FString BakeIconFilePath = IconsDir + TEXT("bake_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*BakeIconFilePath)) - { - const FName BrushName(*BakeIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIBakeIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // CookLog - FString CookLogIconFilePath = IconsDir + TEXT("cook_log16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*CookLogIconFilePath)) - { - const FName BrushName(*CookLogIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUICookLogIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // AssetHelp - FString AssetHelpIconFilePath = IconsDir + TEXT("asset_help16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*AssetHelpIconFilePath)) - { - const FName BrushName(*AssetHelpIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIAssetHelpIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - - // PDG Asset Link - // PDG - FString PDGIconFilePath = IconsDir + TEXT("pdg_link16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGIconFilePath)) - { - const FName BrushName(*PDGIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Cancel - // PDGCancel - FString PDGCancelIconFilePath = IconsDir + TEXT("pdg_cancel16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGCancelIconFilePath)) - { - const FName BrushName(*PDGCancelIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGCancelIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Dirty All - // PDGDirtyAll - FString PDGDirtyAllIconFilePath = IconsDir + TEXT("pdg_dirty_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyAllIconFilePath)) - { - const FName BrushName(*PDGDirtyAllIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGDirtyAllIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Dirty Node - // PDGDirtyNode - FString PDGDirtyNodeIconFilePath = IconsDir + TEXT("pdg_dirty_node16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyNodeIconFilePath)) - { - const FName BrushName(*PDGDirtyNodeIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGDirtyNodeIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Pause - // PDGReset - FString PDGPauseIconFilePath = IconsDir + TEXT("pdg_pause16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGPauseIconFilePath)) - { - const FName BrushName(*PDGPauseIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGPauseIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Reset - // PDGReset - FString PDGResetIconFilePath = IconsDir + TEXT("pdg_reset16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGResetIconFilePath)) - { - const FName BrushName(*PDGResetIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGResetIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Refresh - // PDGRefresh - FString PDGRefreshIconFilePath = IconsDir + TEXT("pdg_refresh16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGRefreshIconFilePath)) - { - const FName BrushName(*PDGRefreshIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGRefreshIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } -} - -void -FHoudiniEngineEditor::AddLevelViewportMenuExtender() -{ - FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked("LevelEditor"); - auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); - - MenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender)); - LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle(); -} - -void -FHoudiniEngineEditor::RemoveLevelViewportMenuExtender() -{ - if (LevelViewportExtenderHandle.IsValid()) - { - FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr("LevelEditor"); - if (LevelEditorModule) - { - typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType; - LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll( - [=](const DelegateType& In) { return In.GetHandle() == LevelViewportExtenderHandle; }); - } - } -} - -TSharedRef -FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef CommandList, const TArray InActors) -{ - TSharedRef Extender = MakeShareable(new FExtender); - - // Build an array of the HoudiniAssets corresponding to the selected actors - TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets; - TArray< TWeakObjectPtr< AHoudiniAssetActor > > HoudiniAssetActors; - for (auto CurrentActor : InActors) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(CurrentActor); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - HoudiniAssetActors.Add(HoudiniAssetActor); - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssets.AddUnique(HoudiniAssetComponent->GetHoudiniAsset()); - } - - if (HoudiniAssets.Num() > 0) - { - // Add the Asset menu extension - if (AssetTypeActions.Num() > 0) - { - // Add the menu extensions via our HoudiniAssetTypeActions - FAssetTypeActions_HoudiniAsset * HATA = static_cast(AssetTypeActions[0].Get()); - if (HATA) - Extender = HATA->AddLevelEditorMenuExtenders(HoudiniAssets); - } - } - - if (HoudiniAssetActors.Num() > 0) - { - // Add some actor menu extensions - FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); - TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); - Extender->AddMenuExtension( - "ActorControl", - EExtensionHook::After, - LevelEditorCommandBindings, - FMenuExtensionDelegate::CreateLambda([this, HoudiniAssetActors](FMenuBuilder& MenuBuilder) - { - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recentre", "Recentre selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecentreTooltip", "Recentres the selected Houdini Asset Actors pivots to their input/cooked static mesh average centre."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecentreSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recook", "Recook selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecookTooltip", "Forces a recook on the selected Houdini Asset Actors."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._CookSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Rebuild", "Rebuild selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RebuildTooltip", "Rebuilds selected Houdini Asset Actors in the current level."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RebuildSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshes", "Refine Houdini Proxy Meshes"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshesTooltip", "Build and replace Houdini Proxy Meshes with Static Meshes."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RefineSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - }) - ); - } - - return Extender; -} - -void -FHoudiniEngineEditor::RegisterConsoleCommands() -{ - IConsoleManager &ConsoleManager = IConsoleManager::Get(); - const TCHAR *CommandName = TEXT("HoudiniEngine.RefineHoudiniProxyMeshesToStaticMeshes"); - IConsoleCommand *Command = ConsoleManager.RegisterConsoleCommand( - CommandName, - TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), - FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); - if (Command) - { - ConsoleCommands.Add(Command); - } - else - { - HOUDINI_LOG_ERROR(TEXT("Failed to register the '%s' console command."), CommandName); - } -} - -void -FHoudiniEngineEditor::UnregisterConsoleCommands() -{ - IConsoleManager &ConsoleManager = IConsoleManager::Get(); - for (IConsoleCommand *Command : ConsoleCommands) - { - if (Command) - { - ConsoleManager.UnregisterConsoleObject(Command); - } - } - ConsoleCommands.Empty(); -} - -void -FHoudiniEngineEditor::RegisterEditorDelegates() -{ - PreSaveWorldEditorDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([](uint32 SaveFlags, UWorld* World) - { - // Skip if this is a game world or an autosave, only refine meshes when the user manually saves - if (!World->IsGameWorld() && (SaveFlags & ESaveFlags::SAVE_FromAutosave) == 0) - { - const bool bSelectedOnly = false; - const bool bSilent = false; - const bool bRefineAll = false; - const bool bOnPreSaveWorld = true; - UWorld * const OnPreSaveWorld = World; - const bool bOnPreBeginPIE = false; - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); - } - - if (!World->IsGameWorld()) - { - UWorld * const OnPreSaveWorld = World; - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - - // Save all dirty temporary cook package OnPostSaveWorld - OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([OnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) - { - if (OnPreSaveWorld && OnPreSaveWorld != InWorld) - return; - - FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(InWorld); - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - }); - } - }); - - PreBeginPIEEditorDelegateHandle = FEditorDelegates::PreBeginPIE.AddLambda([](const bool bIsSimulating) - { - const bool bSelectedOnly = false; - const bool bSilent = false; - const bool bRefineAll = false; - const bool bOnPreSaveWorld = false; - UWorld * const OnPreSaveWorld = nullptr; - const bool bOnPreBeginPIE = true; - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); - }); - - OnDeleteActorsBegin = FEditorDelegates::OnDeleteActorsBegin.AddLambda([this](){ this->HandleOnDeleteActorsBegin(); }); - OnDeleteActorsEnd = FEditorDelegates::OnDeleteActorsEnd.AddLambda([this](){ this-> HandleOnDeleteActorsEnd(); }); -} - -void -FHoudiniEngineEditor::UnregisterEditorDelegates() -{ - if (PreSaveWorldEditorDelegateHandle.IsValid()) - FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldEditorDelegateHandle); - - if (PreBeginPIEEditorDelegateHandle.IsValid()) - FEditorDelegates::PreSaveWorld.Remove(PreBeginPIEEditorDelegateHandle); - - if (OnDeleteActorsBegin.IsValid()) - FEditorDelegates::OnDeleteActorsBegin.Remove(OnDeleteActorsBegin); - - if (OnDeleteActorsEnd.IsValid()) - FEditorDelegates::OnDeleteActorsEnd.Remove(OnDeleteActorsEnd); -} - -FString -FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption) -{ - FString Str; - switch (BakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - Str = "Actor"; - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - Str = "Blueprint"; - break; - - case EHoudiniEngineBakeOption::ToFoliage: - Str = "Foliage"; - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - Str = "World Outliner"; - break; - } - - return Str; -} - -FString -FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption) -{ - FString Str; - switch (BakeOption) - { - case EPDGBakeSelectionOption::All: - Str = "All Outputs"; - break; - - case EPDGBakeSelectionOption::SelectedNetwork: - Str = "Selected Network (All Outputs)"; - break; - - case EPDGBakeSelectionOption::SelectedNode: - Str = "Selected Node (All Outputs)"; - break; - } - - return Str; -} - -FString -FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption) -{ - FString Str; - switch (InOption) - { - case EPDGBakePackageReplaceModeOption::CreateNewAssets: - Str = "Create New Assets"; - break; - case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: - Str = "Replace Existing Assets"; - break; - } - - return Str; -} - -const EHoudiniEngineBakeOption -FHoudiniEngineEditor::StringToHoudiniEngineBakeOption(const FString & InString) -{ - if (InString == "Actor") - return EHoudiniEngineBakeOption::ToActor; - - if (InString == "Blueprint") - return EHoudiniEngineBakeOption::ToBlueprint; - - if (InString == "Foliage") - return EHoudiniEngineBakeOption::ToFoliage; - - if (InString == "World Outliner") - return EHoudiniEngineBakeOption::ToWorldOutliner; - - return EHoudiniEngineBakeOption::ToActor; -} - -const EPDGBakeSelectionOption -FHoudiniEngineEditor::StringToPDGBakeSelectionOption(const FString& InString) -{ - if (InString == "All Outputs") - return EPDGBakeSelectionOption::All; - - if (InString == "Selected Network (All Outputs)") - return EPDGBakeSelectionOption::SelectedNetwork; - - if (InString == "Selected Node (All Outputs)") - return EPDGBakeSelectionOption::SelectedNode; - - return EPDGBakeSelectionOption::All; -} - -const EPDGBakePackageReplaceModeOption -FHoudiniEngineEditor::StringToPDGBakePackageReplaceModeOption(const FString & InString) -{ - if (InString == "Create New Assets") - return EPDGBakePackageReplaceModeOption::CreateNewAssets; - - if (InString == "Replace Existing Assets") - return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; -} - -const EPackageReplaceMode -FHoudiniEngineEditor::PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode) -{ - EPackageReplaceMode Mode; - switch (InReplaceMode) - { - case EPDGBakePackageReplaceModeOption::CreateNewAssets: - Mode = EPackageReplaceMode::CreateNewAssets; - break; - case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: - Mode = EPackageReplaceMode::ReplaceExistingAssets; - break; - default: - { - Mode = FHoudiniPackageParams::GetDefaultReplaceMode(); - HOUDINI_LOG_WARNING(TEXT("Unsupported value for EPDGBakePackageReplaceModeOption %d, using " - "FHoudiniPackageParams::GetDefaultReplaceMode() for resulting EPackageReplaceMode %d"), - InReplaceMode, Mode); - } - } - - return Mode; -} - -void -FHoudiniEngineEditor::HandleOnDeleteActorsBegin() -{ - if (!GEditor) - return; - - TArray AssetActorsWithTempPDGOutput; - // Iterate over all selected actors - for(FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) - { - AActor* SelectedActor = Cast(*It); - if (IsValid(SelectedActor)) - { - // If the class is a AHoudiniAssetActor check if it has temporary PDG outputs - AHoudiniAssetActor* AssetActor = Cast(SelectedActor); - if (IsValid(AssetActor)) - { - UHoudiniPDGAssetLink* AssetLink = AssetActor->GetPDGAssetLink(); - if (IsValid(AssetLink) && AssetLink->HasTemporaryOutputs()) - { - AssetActorsWithTempPDGOutput.Add(AssetActor); - } - } - } - } - - if (AssetActorsWithTempPDGOutput.Num() > 0) - { - const FText DialogTitle = LOCTEXT( - "PDGAssetLink_DeleteWithTemporaryOutputs_Title", - "Warning: PDG Asset Link(s) With Temporary Outputs"); - const EAppReturnType::Type Choice = FMessageDialog::Open( - EAppMsgType::YesNo, - EAppReturnType::No, - LOCTEXT( - "PDGAssetLink_DeleteWithTemporaryOutputs", - "One or more PDG Asset Links in the selection still have temporary outputs. Are you sure you want to " - "delete these PDG Asset Links and their actors?"), - &DialogTitle); - - const bool bKeepAssetLinkActors = (Choice == EAppReturnType::No); - for (AHoudiniAssetActor* AssetActor : AssetActorsWithTempPDGOutput) - { - if (bKeepAssetLinkActors) - { - GEditor->SelectActor(AssetActor, false, false); - ActorsToReselectOnDeleteActorsEnd.Add(AssetActor); - } - } - } -} - -void -FHoudiniEngineEditor::HandleOnDeleteActorsEnd() -{ - if (!GEditor) - return; - - for (AActor* Actor : ActorsToReselectOnDeleteActorsEnd) - { - if (IsValid(Actor)) - GEditor->SelectActor(Actor, true, false); - } - GEditor->NoteSelectionChange(); - ActorsToReselectOnDeleteActorsEnd.Empty(); -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineEditor.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetBroker.h" +#include "HoudiniAssetActorFactory.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniRuntimeSettingsDetails.h" +#include "HoudiniSplineComponentVisualizer.h" +#include "HoudiniHandleComponentVisualizer.h" +#include "AssetTypeActions_HoudiniAsset.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPackageParams.h" + +#include "Modules/ModuleManager.h" +#include "Interfaces/IPluginManager.h" +#include "HAL/PlatformFilemanager.h" +#include "Misc/MessageDialog.h" +#include "Misc/Paths.h" +#include "AssetRegistryModule.h" +#include "PropertyEditorModule.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "LevelEditor.h" +#include "Templates/SharedPointer.h" +#include "Framework/Application/SlateApplication.h" +#include "HAL/ConsoleManager.h" +#include "Editor/UnrealEdEngine.h" +#include "Editor.h" +#include "UnrealEdGlobals.h" +#include "Engine/Selection.h" +#include "Widgets/Input/SCheckBox.h" +#include "Logging/LogMacros.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHoudiniEngineEditor, HoudiniEngineEditor); +DEFINE_LOG_CATEGORY(LogHoudiniEngineEditor); + +FHoudiniEngineEditor * +FHoudiniEngineEditor::HoudiniEngineEditorInstance = nullptr; + +FHoudiniEngineEditor & +FHoudiniEngineEditor::Get() +{ + return *HoudiniEngineEditorInstance; +} + +bool +FHoudiniEngineEditor::IsInitialized() +{ + return FHoudiniEngineEditor::HoudiniEngineEditorInstance != nullptr; +} + +FHoudiniEngineEditor::FHoudiniEngineEditor() +{ +} + +void FHoudiniEngineEditor::StartupModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine Editor module.")); + + // Create style set. + FHoudiniEngineStyle::Initialize(); + + // Initilizes various resources used by our editor UI widgets + InitializeWidgetResource(); + + // Register asset type actions. + RegisterAssetTypeActions(); + + // Register asset brokers. + RegisterAssetBrokers(); + + // Register component visualizers. + RegisterComponentVisualizers(); + + // Register detail presenters. + RegisterDetails(); + + // Register actor factories. + RegisterActorFactories(); + + // Extends the file menu. + ExtendMenu(); + + // Extend the World Outliner Menu + AddLevelViewportMenuExtender(); + + // Adds the custom console commands + RegisterConsoleCommands(); + + // Register global undo / redo callbacks. + //RegisterForUndo(); + + //RegisterPlacementModeExtensions(); + + // Register for any FEditorDelegates that we are interested in, such as + // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds + RegisterEditorDelegates(); + + // Store the instance. + FHoudiniEngineEditor::HoudiniEngineEditorInstance = this; + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module startup complete.")); +} + +void FHoudiniEngineEditor::ShutdownModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine Editor module.")); + + // Deregister editor delegates + UnregisterEditorDelegates(); + + // Deregister console commands + UnregisterConsoleCommands(); + + // Remove the level viewport Menu extender + RemoveLevelViewportMenuExtender(); + + // Unregister asset type actions. + UnregisterAssetTypeActions(); + + // Unregister asset brokers. + //UnregisterAssetBrokers(); + + // Unregister detail presenters. + UnregisterDetails(); + + // Unregister our component visualizers. + //UnregisterComponentVisualizers(); + + // Unregister global undo / redo callbacks. + //UnregisterForUndo(); + + //UnregisterPlacementModeExtensions(); + + // Unregister the styleset + FHoudiniEngineStyle::Shutdown(); + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module shutdown complete.")); +} + +FString +FHoudiniEngineEditor::GetHoudiniEnginePluginDir() +{ + FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(EnginePluginDir)) + return EnginePluginDir; + + FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(ProjectPluginDir)) + return ProjectPluginDir; + + TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); + FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; + if (FPaths::DirectoryExists(PluginBaseDir)) + return PluginBaseDir; + + HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); + + return EnginePluginDir; +} + +void +FHoudiniEngineEditor::RegisterDetails() +{ + FPropertyEditorModule & PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Register details presenter for our component type and runtime settings. + PropertyModule.RegisterCustomClassLayout( + TEXT("HoudiniAssetComponent"), + FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniAssetComponentDetails::MakeInstance)); + + PropertyModule.RegisterCustomClassLayout( + TEXT("HoudiniRuntimeSettings"), + FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniRuntimeSettingsDetails::MakeInstance)); +} + +void +FHoudiniEngineEditor::UnregisterDetails() +{ + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + FPropertyEditorModule & PropertyModule = + FModuleManager::LoadModuleChecked("PropertyEditor"); + + PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniAssetComponent")); + PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniRuntimeSettings")); + } +} + +void +FHoudiniEngineEditor::RegisterComponentVisualizers() +{ + if (GUnrealEd) + { + // Register Houdini spline visualizer + SplineComponentVisualizer = MakeShareable(new FHoudiniSplineComponentVisualizer); + if (SplineComponentVisualizer.IsValid()) + { + GUnrealEd->RegisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName(), SplineComponentVisualizer); + SplineComponentVisualizer->OnRegister(); + } + + // Register Houdini handle visualizer + HandleComponentVisualizer = MakeShareable(new FHoudiniHandleComponentVisualizer); + if (HandleComponentVisualizer.IsValid()) + { + GUnrealEd->RegisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName(), HandleComponentVisualizer); + HandleComponentVisualizer->OnRegister(); + } + } +} + +void +FHoudiniEngineEditor::UnregisterComponentVisualizers() +{ + if (GUnrealEd) + { + // Unregister Houdini spline visualizer + if(SplineComponentVisualizer.IsValid()) + GUnrealEd->UnregisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); + + // Unregister Houdini handle visualizer + if (HandleComponentVisualizer.IsValid()) + GUnrealEd->UnregisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + } +} + +void +FHoudiniEngineEditor::RegisterAssetTypeAction(IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action) +{ + AssetTools.RegisterAssetTypeActions(Action); + AssetTypeActions.Add(Action); +} + +void +FHoudiniEngineEditor::RegisterAssetTypeActions() +{ + // Create and register asset type actions for Houdini asset. + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools").Get(); + RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_HoudiniAsset())); +} + +void +FHoudiniEngineEditor::UnregisterAssetTypeActions() +{ + // Unregister asset type actions we have previously registered. + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >("AssetTools").Get(); + + for (int32 Index = 0; Index < AssetTypeActions.Num(); ++Index) + AssetTools.UnregisterAssetTypeActions(AssetTypeActions[Index].ToSharedRef()); + + AssetTypeActions.Empty(); + } +} + +void +FHoudiniEngineEditor::RegisterAssetBrokers() +{ + // Create and register broker for Houdini asset. + HoudiniAssetBroker = MakeShareable(new FHoudiniAssetBroker()); + FComponentAssetBrokerage::RegisterBroker( HoudiniAssetBroker, UHoudiniAssetComponent::StaticClass(), true, true ); +} + +void +FHoudiniEngineEditor::UnregisterAssetBrokers() +{ + if (UObjectInitialized()) + { + // Unregister broker. + FComponentAssetBrokerage::UnregisterBroker( HoudiniAssetBroker ); + } +} + +void +FHoudiniEngineEditor::RegisterActorFactories() +{ + if (GEditor) + { + UHoudiniAssetActorFactory * HoudiniAssetActorFactory = + NewObject< UHoudiniAssetActorFactory >(GetTransientPackage(), UHoudiniAssetActorFactory::StaticClass()); + + GEditor->ActorFactories.Add(HoudiniAssetActorFactory); + } +} + +void +FHoudiniEngineEditor::BindMenuCommands() +{ + HEngineCommands = MakeShareable(new FUICommandList); + + FHoudiniEngineCommands::Register(); + const FHoudiniEngineCommands& Commands = FHoudiniEngineCommands::Get(); + + // Session + + HEngineCommands->MapAction( + Commands._CreateSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CreateSession(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._ConnectSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ConnectSession(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._StopSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopSession(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RestartSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RestartSession(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OpenSessionSync, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OpenSessionSync(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); + + HEngineCommands->MapAction( + Commands._CloseSessionSync, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CloseSessionSync(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); + + HEngineCommands->MapAction( + Commands._ViewportSyncNone, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(0); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 0); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncHoudini, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(1); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 1); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncUnreal, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(2); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 2); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncBoth, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(3); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 3); }) + ); + + // PDG commandlet + HEngineCommands->MapAction( + Commands._IsPDGCommandletEnabled, + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::SetPDGCommandletEnabled(!FHoudiniEngineCommands::IsPDGCommandletEnabled()); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletEnabled(); }) + ); + + HEngineCommands->MapAction( + Commands._StartPDGCommandlet, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StartPDGCommandlet(); }), + FCanExecuteAction::CreateLambda([]() + { + return FHoudiniEngineCommands::IsPDGCommandletEnabled() && !FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); + }) + ); + + HEngineCommands->MapAction( + Commands._StopPDGCommandlet, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopPDGCommandlet(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); })); + + // Plugin + + HEngineCommands->MapAction( + Commands._InstallInfo, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowInstallInfo(); }), + FCanExecuteAction::CreateLambda([]() { return false; })); + + HEngineCommands->MapAction( + Commands._PluginSettings, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowPluginSettings(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Files + + HEngineCommands->MapAction( + Commands._OpenInHoudini, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::OpenInHoudini(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._SaveHIPFile, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::SaveHIPFile(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._CleanUpTempFolder, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CleanUpTempFolder(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Help and support + + HEngineCommands->MapAction( + Commands._ReportBug, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::ReportBug(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OnlineDoc, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineDocumentation(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OnlineForum, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineForum(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Actions + + HEngineCommands->MapAction( + Commands._CookAll, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookAllAssets(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._CookSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RebuildAll, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildAllAssets(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RebuildSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._BakeAll, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::BakeAllAssets(); }), + FCanExecuteAction::CreateLambda([](){ return true; })); + + HEngineCommands->MapAction( + Commands._BakeSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::BakeSelection(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._RefineAll, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._RefineSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._PauseAssetCooking, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::PauseAssetCooking(); }), + FCanExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::IsSessionValid(); }), + FIsActionChecked::CreateLambda([](){ return FHoudiniEngineCommands::IsAssetCookingPaused(); })); + + // Non menu command (used for shortcuts only) + + // Append the command to the editor module + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >("LevelEditor"); + LevelEditorModule.GetGlobalLevelEditorActions()->Append(HEngineCommands.ToSharedRef()); +} + +void +FHoudiniEngineEditor::ExtendMenu() +{ + if (IsRunningCommandlet()) + return; + + // We need to add/bind the UI Commands to their functions first + BindMenuCommands(); + + MainMenuExtender = MakeShareable(new FExtender); + + // Extend File menu, we will add Houdini section. + MainMenuExtender->AddMenuExtension( + "FileLoadAndSave", + EExtensionHook::After, + HEngineCommands, + FMenuExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniFileMenuExtension)); + + MainMenuExtender->AddMenuBarExtension( + "Edit", + EExtensionHook::After, + HEngineCommands, + FMenuBarExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniEditorMenu)); + + // Add our menu extender + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); +} + +void +FHoudiniEngineEditor::AddHoudiniFileMenuExtension(FMenuBuilder & MenuBuilder) +{ + MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); + + // Icons used by the commands are defined in the HoudiniEngineStyle + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + + MenuBuilder.EndSection(); +} + +void +FHoudiniEngineEditor::AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder) +{ + // View + MenuBarBuilder.AddPullDownMenu( + LOCTEXT("HoudiniLabel", "Houdini Engine"), + LOCTEXT("HoudiniMenu_ToolTip", "Open the Houdini Engine menu"), + FNewMenuDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniMainMenuExtension), + "View"); +} + +void +FHoudiniEngineEditor::AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder) +{ + /* + MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); + // Icons used by the commands are defined in the HoudiniEngineStyle + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + MenuBuilder.EndSection(); + */ + + MenuBuilder.BeginSection("Session", LOCTEXT("SessionLabel", "Session")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CreateSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ConnectSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenSessionSync); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CloseSessionSync); + + // Viewport sync menu + struct FLocalMenuBuilder + { + static void FillViewportSyncMenu(FMenuBuilder& InSubMenuBuilder) + { + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncNone); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncHoudini); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncUnreal); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncBoth); + } + }; + + MenuBuilder.AddSubMenu( + LOCTEXT("SyncViewport", "Sync Viewport"), + LOCTEXT("SyncViewport_ToolTip", "Sync Viewport"), + FNewMenuDelegate::CreateStatic(&FLocalMenuBuilder::FillViewportSyncMenu), + false, + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._SyncViewport")); + + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("PDG", LOCTEXT("PDGLabel", "PDG")); + struct FLocalPDGMenuBuilder + { + static void FillPDGMenu(FMenuBuilder& InSubMenuBuilder) + { + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._IsPDGCommandletEnabled); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StartPDGCommandlet); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopPDGCommandlet); + } + }; + MenuBuilder.AddSubMenu( + LOCTEXT("PDGSubMenu", "Work Item Import Settings"), + LOCTEXT("PDGSubmenu_ToolTip", "PDG Work Item Import Settings"), + FNewMenuDelegate::CreateStatic(&FLocalPDGMenuBuilder::FillPDGMenu), + false, + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.PDGLink")); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Plugin", LOCTEXT("PluginLabel", "Plugin")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._InstallInfo); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PluginSettings); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("File", LOCTEXT("FileLabel", "File")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Help", LOCTEXT("HelpLabel", "Help And Support")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineDoc); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineForum); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Actions", LOCTEXT("ActionsLabel", "Actions")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + + + MenuBuilder.EndSection(); +} + +void +FHoudiniEngineEditor::RegisterForUndo() +{ + /* + if (GUnrealEd) + GUnrealEd->RegisterForUndo(this); + */ +} + +void +FHoudiniEngineEditor::UnregisterForUndo() +{ + /* + if (GUnrealEd) + GUnrealEd->UnregisterForUndo(this); + */ +} + +void +FHoudiniEngineEditor::RegisterPlacementModeExtensions() +{ + // Load custom houdini tools + /* + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check(HoudiniRuntimeSettings); + + if (HoudiniRuntimeSettings->bHidePlacementModeHoudiniTools) + return; + + FPlacementCategoryInfo Info( + LOCTEXT("HoudiniCategoryName", "Houdini Engine"), + "HoudiniEngine", + TEXT("PMHoudiniEngine"), + 25 + ); + Info.CustomGenerator = []() -> TSharedRef { return SNew(SHoudiniToolPalette); }; + + IPlacementModeModule::Get().RegisterPlacementCategory(Info); + */ +} + +void +FHoudiniEngineEditor::UnregisterPlacementModeExtensions() +{ + /* + if (IPlacementModeModule::IsAvailable()) + { + IPlacementModeModule::Get().UnregisterPlacementCategory("HoudiniEngine"); + } + + HoudiniTools.Empty(); + */ +} + +void +FHoudiniEngineEditor::InitializeWidgetResource() +{ + // Choice labels for all the input types + //TArray> InputTypeChoiceLabels; + InputTypeChoiceLabels.Reset(); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Asset)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Landscape)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::World)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Skeletal)))); + + BlueprintInputTypeChoiceLabels.Reset(); + BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); + BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); + + // Choice labels for all Houdini curve types + HoudiniCurveTypeChoiceLabels.Reset(); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Polygon)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Nurbs)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Bezier)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Points)))); + + // Choice labels for all Houdini curve methods + HoudiniCurveMethodChoiceLabels.Reset(); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::CVs)))); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Breakpoints)))); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Freehand)))); + + // Choice labels for all Houdini ramp parameter interpolation methods + HoudiniParameterRampInterpolationLabels.Reset(); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CONSTANT)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::LINEAR)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CATMULL_ROM)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::MONOTONE_CUBIC)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BEZIER)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BSPLINE)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::HERMITE)))); + + // Choice labels for all Houdini curve output export types + HoudiniCurveOutputExportTypeLabels.Reset(); + HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Unreal Spline")))); + HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Houdini Spline")))); + + // Choice labels for all Unreal curve output curve types + //(for temporary, we need to figure out a way to access the output curve's info later) + UnrealCurveOutputCurveTypeLabels.Reset(); + UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Linear")))); + UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Curve")))); + + // Option labels for all landscape outputs bake options + HoudiniLandscapeOutputBakeOptionLabels.Reset(); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Current Level")))); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Image")))); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To New World")))); + + // Option labels for Houdini Engine PDG bake options + HoudiniEnginePDGBakeTypeOptionLabels.Reset(); + HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); + HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); + + // Option labels for Houdini Engine bake options + HoudiniEngineBakeTypeOptionLabels.Reset(); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToFoliage)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToWorldOutliner)))); + + // Option labels for Houdini Engine PDG bake options + HoudiniEnginePDGBakeSelectionOptionLabels.Reset(); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::All)))); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNetwork)))); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNode)))); + + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Reset(); + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::ReplaceExistingAssets)))); + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::CreateNewAssets)))); + + + static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); + + // Houdini Logo Brush + FString Icon128FilePath = IconsDir + TEXT("icon_houdini_logo_128"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) + { + const FName BrushName(*Icon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Logo Brush + FString HEIcon128FilePath = IconsDir + TEXT("icon_hengine_logo_128"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) + { + const FName BrushName(*HEIcon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Banner + FString HoudiniEngineUIIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_banner_d.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Rebuild Icon Brush + FString HoudiniEngineUIRebuildIconFilePath = IconsDir + TEXT("rebuild_all16x16.png"); + //FString HoudiniEngineUIRebuildIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_reload_icon.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRebuildIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIRebuildIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIRebuildIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Recook Icon Brush + //FString HoudiniEngineUIRecookIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_recook_icon.png"); + FString HoudiniEngineUIRecookIconFilePath = IconsDir + TEXT("cook_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRecookIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIRecookIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIRecookIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Reset Parameters Icon Brush + //FString HoudiniEngineUIResetParametersIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_resetparameters_icon.png"); + FString HoudiniEngineUIResetParametersIconFilePath = IconsDir + TEXT("reset_parameters16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIResetParametersIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIResetParametersIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIResetParametersIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Bake + FString BakeIconFilePath = IconsDir + TEXT("bake_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*BakeIconFilePath)) + { + const FName BrushName(*BakeIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIBakeIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // CookLog + FString CookLogIconFilePath = IconsDir + TEXT("cook_log16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*CookLogIconFilePath)) + { + const FName BrushName(*CookLogIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUICookLogIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // AssetHelp + FString AssetHelpIconFilePath = IconsDir + TEXT("asset_help16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*AssetHelpIconFilePath)) + { + const FName BrushName(*AssetHelpIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIAssetHelpIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + + // PDG Asset Link + // PDG + FString PDGIconFilePath = IconsDir + TEXT("pdg_link16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGIconFilePath)) + { + const FName BrushName(*PDGIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Cancel + // PDGCancel + FString PDGCancelIconFilePath = IconsDir + TEXT("pdg_cancel16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGCancelIconFilePath)) + { + const FName BrushName(*PDGCancelIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGCancelIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Dirty All + // PDGDirtyAll + FString PDGDirtyAllIconFilePath = IconsDir + TEXT("pdg_dirty_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyAllIconFilePath)) + { + const FName BrushName(*PDGDirtyAllIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGDirtyAllIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Dirty Node + // PDGDirtyNode + FString PDGDirtyNodeIconFilePath = IconsDir + TEXT("pdg_dirty_node16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyNodeIconFilePath)) + { + const FName BrushName(*PDGDirtyNodeIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGDirtyNodeIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Pause + // PDGReset + FString PDGPauseIconFilePath = IconsDir + TEXT("pdg_pause16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGPauseIconFilePath)) + { + const FName BrushName(*PDGPauseIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGPauseIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Reset + // PDGReset + FString PDGResetIconFilePath = IconsDir + TEXT("pdg_reset16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGResetIconFilePath)) + { + const FName BrushName(*PDGResetIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGResetIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Refresh + // PDGRefresh + FString PDGRefreshIconFilePath = IconsDir + TEXT("pdg_refresh16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGRefreshIconFilePath)) + { + const FName BrushName(*PDGRefreshIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGRefreshIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } +} + +void +FHoudiniEngineEditor::AddLevelViewportMenuExtender() +{ + FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked("LevelEditor"); + auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); + + MenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender)); + LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle(); +} + +void +FHoudiniEngineEditor::RemoveLevelViewportMenuExtender() +{ + if (LevelViewportExtenderHandle.IsValid()) + { + FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr("LevelEditor"); + if (LevelEditorModule) + { + typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType; + LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll( + [=](const DelegateType& In) { return In.GetHandle() == LevelViewportExtenderHandle; }); + } + } +} + +TSharedRef +FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef CommandList, const TArray InActors) +{ + TSharedRef Extender = MakeShareable(new FExtender); + + // Build an array of the HoudiniAssets corresponding to the selected actors + TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets; + TArray< TWeakObjectPtr< AHoudiniAssetActor > > HoudiniAssetActors; + for (auto CurrentActor : InActors) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(CurrentActor); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + HoudiniAssetActors.Add(HoudiniAssetActor); + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssets.AddUnique(HoudiniAssetComponent->GetHoudiniAsset()); + } + + if (HoudiniAssets.Num() > 0) + { + // Add the Asset menu extension + if (AssetTypeActions.Num() > 0) + { + // Add the menu extensions via our HoudiniAssetTypeActions + FAssetTypeActions_HoudiniAsset * HATA = static_cast(AssetTypeActions[0].Get()); + if (HATA) + Extender = HATA->AddLevelEditorMenuExtenders(HoudiniAssets); + } + } + + if (HoudiniAssetActors.Num() > 0) + { + // Add some actor menu extensions + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); + Extender->AddMenuExtension( + "ActorControl", + EExtensionHook::After, + LevelEditorCommandBindings, + FMenuExtensionDelegate::CreateLambda([this, HoudiniAssetActors](FMenuBuilder& MenuBuilder) + { + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recentre", "Recentre selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecentreTooltip", "Recentres the selected Houdini Asset Actors pivots to their input/cooked static mesh average centre."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecentreSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recook", "Recook selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecookTooltip", "Forces a recook on the selected Houdini Asset Actors."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._CookSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Rebuild", "Rebuild selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RebuildTooltip", "Rebuilds selected Houdini Asset Actors in the current level."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RebuildSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshes", "Refine Houdini Proxy Meshes"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshesTooltip", "Build and replace Houdini Proxy Meshes with Static Meshes."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RefineSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + }) + ); + } + + return Extender; +} + +void +FHoudiniEngineEditor::RegisterConsoleCommands() +{ + IConsoleManager &ConsoleManager = IConsoleManager::Get(); + const TCHAR *CommandName = TEXT("HoudiniEngine.RefineHoudiniProxyMeshesToStaticMeshes"); + IConsoleCommand *Command = ConsoleManager.RegisterConsoleCommand( + CommandName, + TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), + FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); + if (Command) + { + ConsoleCommands.Add(Command); + } + else + { + HOUDINI_LOG_ERROR(TEXT("Failed to register the '%s' console command."), CommandName); + } +} + +void +FHoudiniEngineEditor::UnregisterConsoleCommands() +{ + IConsoleManager &ConsoleManager = IConsoleManager::Get(); + for (IConsoleCommand *Command : ConsoleCommands) + { + if (Command) + { + ConsoleManager.UnregisterConsoleObject(Command); + } + } + ConsoleCommands.Empty(); +} + +void +FHoudiniEngineEditor::RegisterEditorDelegates() +{ + PreSaveWorldEditorDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([](uint32 SaveFlags, UWorld* World) + { + // Skip if this is a game world or an autosave, only refine meshes when the user manually saves + if (!World->IsGameWorld() && (SaveFlags & ESaveFlags::SAVE_FromAutosave) == 0) + { + const bool bSelectedOnly = false; + const bool bSilent = false; + const bool bRefineAll = false; + const bool bOnPreSaveWorld = true; + UWorld * const OnPreSaveWorld = World; + const bool bOnPreBeginPIE = false; + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); + } + + if (!World->IsGameWorld()) + { + UWorld * const OnPreSaveWorld = World; + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + + // Save all dirty temporary cook package OnPostSaveWorld + OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([OnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) + { + if (OnPreSaveWorld && OnPreSaveWorld != InWorld) + return; + + FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(InWorld); + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + }); + } + }); + + PreBeginPIEEditorDelegateHandle = FEditorDelegates::PreBeginPIE.AddLambda([](const bool bIsSimulating) + { + const bool bSelectedOnly = false; + const bool bSilent = false; + const bool bRefineAll = false; + const bool bOnPreSaveWorld = false; + UWorld * const OnPreSaveWorld = nullptr; + const bool bOnPreBeginPIE = true; + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); + }); + + OnDeleteActorsBegin = FEditorDelegates::OnDeleteActorsBegin.AddLambda([this](){ this->HandleOnDeleteActorsBegin(); }); + OnDeleteActorsEnd = FEditorDelegates::OnDeleteActorsEnd.AddLambda([this](){ this-> HandleOnDeleteActorsEnd(); }); +} + +void +FHoudiniEngineEditor::UnregisterEditorDelegates() +{ + if (PreSaveWorldEditorDelegateHandle.IsValid()) + FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldEditorDelegateHandle); + + if (PreBeginPIEEditorDelegateHandle.IsValid()) + FEditorDelegates::PreSaveWorld.Remove(PreBeginPIEEditorDelegateHandle); + + if (OnDeleteActorsBegin.IsValid()) + FEditorDelegates::OnDeleteActorsBegin.Remove(OnDeleteActorsBegin); + + if (OnDeleteActorsEnd.IsValid()) + FEditorDelegates::OnDeleteActorsEnd.Remove(OnDeleteActorsEnd); +} + +FString +FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption) +{ + FString Str; + switch (BakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + Str = "Actor"; + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + Str = "Blueprint"; + break; + + case EHoudiniEngineBakeOption::ToFoliage: + Str = "Foliage"; + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + Str = "World Outliner"; + break; + } + + return Str; +} + +FString +FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption) +{ + FString Str; + switch (BakeOption) + { + case EPDGBakeSelectionOption::All: + Str = "All Outputs"; + break; + + case EPDGBakeSelectionOption::SelectedNetwork: + Str = "Selected Network (All Outputs)"; + break; + + case EPDGBakeSelectionOption::SelectedNode: + Str = "Selected Node (All Outputs)"; + break; + } + + return Str; +} + +FString +FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption) +{ + FString Str; + switch (InOption) + { + case EPDGBakePackageReplaceModeOption::CreateNewAssets: + Str = "Create New Assets"; + break; + case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: + Str = "Replace Existing Assets"; + break; + } + + return Str; +} + +const EHoudiniEngineBakeOption +FHoudiniEngineEditor::StringToHoudiniEngineBakeOption(const FString & InString) +{ + if (InString == "Actor") + return EHoudiniEngineBakeOption::ToActor; + + if (InString == "Blueprint") + return EHoudiniEngineBakeOption::ToBlueprint; + + if (InString == "Foliage") + return EHoudiniEngineBakeOption::ToFoliage; + + if (InString == "World Outliner") + return EHoudiniEngineBakeOption::ToWorldOutliner; + + return EHoudiniEngineBakeOption::ToActor; +} + +const EPDGBakeSelectionOption +FHoudiniEngineEditor::StringToPDGBakeSelectionOption(const FString& InString) +{ + if (InString == "All Outputs") + return EPDGBakeSelectionOption::All; + + if (InString == "Selected Network (All Outputs)") + return EPDGBakeSelectionOption::SelectedNetwork; + + if (InString == "Selected Node (All Outputs)") + return EPDGBakeSelectionOption::SelectedNode; + + return EPDGBakeSelectionOption::All; +} + +const EPDGBakePackageReplaceModeOption +FHoudiniEngineEditor::StringToPDGBakePackageReplaceModeOption(const FString & InString) +{ + if (InString == "Create New Assets") + return EPDGBakePackageReplaceModeOption::CreateNewAssets; + + if (InString == "Replace Existing Assets") + return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; +} + +const EPackageReplaceMode +FHoudiniEngineEditor::PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode) +{ + EPackageReplaceMode Mode; + switch (InReplaceMode) + { + case EPDGBakePackageReplaceModeOption::CreateNewAssets: + Mode = EPackageReplaceMode::CreateNewAssets; + break; + case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: + Mode = EPackageReplaceMode::ReplaceExistingAssets; + break; + default: + { + Mode = FHoudiniPackageParams::GetDefaultReplaceMode(); + HOUDINI_LOG_WARNING(TEXT("Unsupported value for EPDGBakePackageReplaceModeOption %d, using " + "FHoudiniPackageParams::GetDefaultReplaceMode() for resulting EPackageReplaceMode %d"), + InReplaceMode, Mode); + } + } + + return Mode; +} + +void +FHoudiniEngineEditor::HandleOnDeleteActorsBegin() +{ + if (!GEditor) + return; + + TArray AssetActorsWithTempPDGOutput; + // Iterate over all selected actors + for(FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) + { + AActor* SelectedActor = Cast(*It); + if (IsValid(SelectedActor)) + { + // If the class is a AHoudiniAssetActor check if it has temporary PDG outputs + AHoudiniAssetActor* AssetActor = Cast(SelectedActor); + if (IsValid(AssetActor)) + { + UHoudiniPDGAssetLink* AssetLink = AssetActor->GetPDGAssetLink(); + if (IsValid(AssetLink) && AssetLink->HasTemporaryOutputs()) + { + AssetActorsWithTempPDGOutput.Add(AssetActor); + } + } + } + } + + if (AssetActorsWithTempPDGOutput.Num() > 0) + { + const FText DialogTitle = LOCTEXT( + "PDGAssetLink_DeleteWithTemporaryOutputs_Title", + "Warning: PDG Asset Link(s) With Temporary Outputs"); + const EAppReturnType::Type Choice = FMessageDialog::Open( + EAppMsgType::YesNo, + EAppReturnType::No, + LOCTEXT( + "PDGAssetLink_DeleteWithTemporaryOutputs", + "One or more PDG Asset Links in the selection still have temporary outputs. Are you sure you want to " + "delete these PDG Asset Links and their actors?"), + &DialogTitle); + + const bool bKeepAssetLinkActors = (Choice == EAppReturnType::No); + for (AHoudiniAssetActor* AssetActor : AssetActorsWithTempPDGOutput) + { + if (bKeepAssetLinkActors) + { + GEditor->SelectActor(AssetActor, false, false); + ActorsToReselectOnDeleteActorsEnd.Add(AssetActor); + } + } + } +} + +void +FHoudiniEngineEditor::HandleOnDeleteActorsEnd() +{ + if (!GEditor) + return; + + for (AActor* Actor : ActorsToReselectOnDeleteActorsEnd) + { + if (IsValid(Actor)) + GEditor->SelectActor(Actor, true, false); + } + GEditor->NoteSelectionChange(); + ActorsToReselectOnDeleteActorsEnd.Empty(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h index 559c3cb4e..ad08dc277 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h @@ -1,347 +1,347 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "IHoudiniEngineEditor.h" -#include "HoudiniInputTypes.h" - -#include "CoreTypes.h" -#include "Templates/SharedPointer.h" - -class FExtender; -class IAssetTools; -class IAssetTypeActions; -class IComponentAssetBroker; -class FComponentVisualizer; -class FMenuBuilder; -class FMenuBarBuilder; -class FUICommandList; -class AActor; - -struct IConsoleCommand; -struct FSlateDynamicImageBrush; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod: int8; -enum class EHoudiniLandscapeOutputBakeType: uint8; -enum class EHoudiniEngineBakeOption : uint8; -enum class EPDGBakeSelectionOption : uint8; -enum class EPDGBakePackageReplaceModeOption : uint8; -enum class EPackageReplaceMode : int8; - -class HOUDINIENGINEEDITOR_API FHoudiniEngineEditor : public IHoudiniEngineEditor -{ - public: - FHoudiniEngineEditor(); - - // IModuleInterface methods. - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // IHoudiniEngineEditor methods - virtual void RegisterComponentVisualizers() override; - virtual void UnregisterComponentVisualizers() override; - virtual void RegisterDetails() override; - virtual void UnregisterDetails() override; - virtual void RegisterAssetTypeActions() override; - virtual void UnregisterAssetTypeActions() override; - virtual void RegisterAssetBrokers() override; - virtual void UnregisterAssetBrokers() override; - virtual void RegisterActorFactories() override; - virtual void ExtendMenu() override; - virtual void RegisterForUndo() override; - virtual void UnregisterForUndo() override; - virtual void RegisterPlacementModeExtensions() override; - virtual void UnregisterPlacementModeExtensions() override; - - // Return singleton instance of Houdini Engine Editor, used internally. - static FHoudiniEngineEditor & Get(); - - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // Returns the plugin's directory - static FString GetHoudiniEnginePluginDir(); - - // Initializes Widget resources - void InitializeWidgetResource(); - - // Menu action to pause cooking for all Houdini Assets - void PauseAssetCooking(); - - // Helper delegate used to determine if PauseAssetCooking can be executed. - bool CanPauseAssetCooking(); - - // Helper delegate used to get the current state of PauseAssetCooking. - bool IsAssetCookingPaused(); - - // Returns a pointer to the input choice types - TArray>* GetInputTypeChoiceLabels() { return &InputTypeChoiceLabels; }; - TArray>* GetBlueprintInputTypeChoiceLabels() { return &BlueprintInputTypeChoiceLabels; }; - - // Returns a pointer to the Houdini curve types - TArray>* GetHoudiniCurveTypeChoiceLabels() { return &HoudiniCurveTypeChoiceLabels; }; - - // Returns a pointer to the Houdini curve methods - TArray>* GetHoudiniCurveMethodChoiceLabels() { return &HoudiniCurveMethodChoiceLabels; }; - - // Returns a pointer to the Houdini ramp parameter interpolation methods - TArray>* GetHoudiniParameterRampInterpolationMethodLabels() {return &HoudiniParameterRampInterpolationLabels;} - - // Returns a pointer to the Houdini curve output export types - TArray>* GetHoudiniCurveOutputExportTypeLabels() { return &HoudiniCurveOutputExportTypeLabels; }; - - TArray>* GetHoudiniLandscapeOutputBakeOptionsLabels() { return &HoudiniLandscapeOutputBakeOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Type labels - TArray>* GetHoudiniEnginePDGBakeTypeOptionsLabels() { return &HoudiniEnginePDGBakeTypeOptionLabels; }; - - // Returns a pointer to the Houdini Engine Bake Type labels - TArray>* GetHoudiniEngineBakeTypeOptionsLabels() { return &HoudiniEngineBakeTypeOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Target labels - TArray>* GetHoudiniEnginePDGBakeSelectionOptionsLabels() { return &HoudiniEnginePDGBakeSelectionOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Package Replace Mode labels - TArray>* GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels() { return &HoudiniEnginePDGBakePackageReplaceModeOptionLabels; }; - - // Returns a shared Ptr to the Houdini logo - TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; - TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; - - // Functions Return a shared Ptr to the Houdini Engine UI Icon - TSharedPtr GetHoudiniEngineUIIconBrush() const { return HoudiniEngineUIIconBrush; } - TSharedPtr GetHoudiniEngineUIRebuildIconBrush() const { return HoudiniEngineUIRebuildIconBrush; } - TSharedPtr GetHoudiniEngineUIRecookIconBrush() const { return HoudiniEngineUIRecookIconBrush; } - TSharedPtr GetHoudiniEngineUIResetParametersIconBrush() const { return HoudiniEngineUIResetParametersIconBrush; } - - TSharedPtr GetHoudiniEngineUIBakeIconBrush() const { return HoudiniEngineUIBakeIconBrush; } - TSharedPtr GetHoudiniEngineUICookLogIconBrush() const { return HoudiniEngineUICookLogIconBrush; } - TSharedPtr GetHoudiniEngineUIAssetHelpIconBrush() const { return HoudiniEngineUIAssetHelpIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGIconBrush() const { return HoudiniEngineUIPDGIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGCancelIconBrush() const { return HoudiniEngineUIPDGCancelIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGDirtyAllIconBrush() const { return HoudiniEngineUIPDGDirtyAllIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGDirtyNodeIconBrush() const { return HoudiniEngineUIPDGDirtyNodeIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGPauseIconBrush() const { return HoudiniEngineUIPDGPauseIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGResetIconBrush() const { return HoudiniEngineUIPDGResetIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGRefreshIconBrush() const { return HoudiniEngineUIPDGRefreshIconBrush; } - - // Returns a pointer to Unreal output curve types (for temporary) - TArray>* GetUnrealOutputCurveTypeLabels() { return &UnrealCurveOutputCurveTypeLabels; }; - - // returns string from Houdini Engine Bake Option - FString GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption); - - // returns string from Houdini Engine PDG Bake Target Option - FString GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption); - - // returns string from PDG package replace mode option - FString GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption); - - // Return HoudiniEngineBakeOption from FString - const EHoudiniEngineBakeOption StringToHoudiniEngineBakeOption(const FString & InString); - - // Return EPDGBakeSelectionOption from FString - const EPDGBakeSelectionOption StringToPDGBakeSelectionOption(const FString& InString); - - // Return EPDGBakePackageReplaceModeOption from FString - const EPDGBakePackageReplaceModeOption StringToPDGBakePackageReplaceModeOption(const FString & InString); - - // Convert EPDGBakePackageReplaceModeOption to EPackageReplaceMode - // TODO: perhaps EPackageReplaceMode can be moved to HoudiniEngineRuntime to avoid having both - // TODO: EPDGBakePackageReplaceModeOption and EPackageReplaceMode? - const EPackageReplaceMode PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode); - - // Get the reference of the radio button folder circle point arrays reference - TArray & GetHoudiniParameterRadioButtonPointsOuter() { return HoudiniParameterRadioButtonPointsOuter; }; - TArray & GetHoudiniParameterRadioButtonPointsInner() { return HoudiniParameterRadioButtonPointsInner; }; - - // Gets the PostSaveWorldOnceHandle - FDelegateHandle& GetOnPostSaveWorldOnceHandle() { return PostSaveWorldOnceHandle; } - - protected: - - // Binds the commands used by the menus - void BindMenuCommands(); - - // Register AssetType action. - void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef< IAssetTypeActions > Action); - - // Add menu extension for our module. - void AddHoudiniFileMenuExtension(FMenuBuilder& MenuBuilder); - - // Add the Houdini Engine editor menu - void AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder); - - // Add menu extension for our module. - void AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder); - - // Adds the custom Houdini Engine commands to the world outliner context menu - void AddLevelViewportMenuExtender(); - - // Removes the custom Houdini Engine commands to the world outliner context menu - void RemoveLevelViewportMenuExtender(); - - // Returns all the custom Houdini Engine commands for the world outliner context menu - TSharedRef GetLevelViewportContextMenuExtender( - const TSharedRef CommandList, const TArray InActors); - - // Register all console commands provided by this module - void RegisterConsoleCommands(); - - // Unregister all registered console commands provided by this module - void UnregisterConsoleCommands(); - - // Register for any FEditorDelegates that we are interested in, such as - // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds - void RegisterEditorDelegates(); - - // Deregister editor delegates - void UnregisterEditorDelegates(); - - // Process the OnDeleteActorsBegin call received from FEditorDelegates. - // Check if any AHoudiniAssetActors with PDG links are selected for deletion. If so, - // check if these still have temporary outputs and give the user to option to skip - // deleting the ones with temporary output. - void HandleOnDeleteActorsBegin(); - - // Re-select AHoudiniAssetActors that were deselected (to avoid deletion) by HandleOnDeleteActorsBegin - void HandleOnDeleteActorsEnd(); - - private: - - // Singleton instance of Houdini Engine Editor. - static FHoudiniEngineEditor * HoudiniEngineEditorInstance; - - // AssetType actions associated with Houdini asset. - TArray> AssetTypeActions; - - // Broker associated with Houdini asset. - TSharedPtr HoudiniAssetBroker; - - // Widget resources: Input Type combo box labels - TArray> InputTypeChoiceLabels; - TArray> BlueprintInputTypeChoiceLabels; - - // Widget resources: Houdini Curve Type combo box labels - TArray> HoudiniCurveTypeChoiceLabels; - - // Widget resources: Houdini Curve Method combo box labels - TArray> HoudiniCurveMethodChoiceLabels; - - // Widget resources: Houdini Ramp Interpolation method combo box labels - TArray> HoudiniParameterRampInterpolationLabels; - - // Widget resources: Houdini Curve Output type labels - TArray> HoudiniCurveOutputExportTypeLabels; - - // Widget resources: Unreal Curve type labels (for temporary, we need to figure out a way to access the output curve's info later) - TArray> UnrealCurveOutputCurveTypeLabels; - - // Widget resources: Landscape output Bake type labels - TArray> HoudiniLandscapeOutputBakeOptionLabels; - - // Widget resources: PDG Bake type labels - TArray> HoudiniEnginePDGBakeTypeOptionLabels; - - // Widget resources: Bake type labels - TArray> HoudiniEngineBakeTypeOptionLabels; - - // Widget resources: PDG Bake target labels - TArray> HoudiniEnginePDGBakeSelectionOptionLabels; - - // Widget resources: PDG Bake package replace mode labels - TArray> HoudiniEnginePDGBakePackageReplaceModeOptionLabels; - - // List of UI commands used by the various menus - TSharedPtr HEngineCommands; - - // Houdini logo brush. - TSharedPtr HoudiniLogoBrush; - // Houdini Engine logo brush - TSharedPtr HoudiniEngineLogoBrush; - - // houdini Engine UI Brushes - TSharedPtr HoudiniEngineUIIconBrush; - TSharedPtr HoudiniEngineUIRebuildIconBrush; - TSharedPtr HoudiniEngineUIRecookIconBrush; - TSharedPtr HoudiniEngineUIResetParametersIconBrush; - - TSharedPtr HoudiniEngineUIBakeIconBrush; - TSharedPtr HoudiniEngineUICookLogIconBrush; - TSharedPtr HoudiniEngineUIAssetHelpIconBrush; - TSharedPtr HoudiniEngineUIPDGIconBrush; - TSharedPtr HoudiniEngineUIPDGCancelIconBrush; - TSharedPtr HoudiniEngineUIPDGDirtyAllIconBrush; - TSharedPtr HoudiniEngineUIPDGDirtyNodeIconBrush; - TSharedPtr HoudiniEngineUIPDGPauseIconBrush; - TSharedPtr HoudiniEngineUIPDGResetIconBrush; - TSharedPtr HoudiniEngineUIPDGRefreshIconBrush; - - // The extender to pass to the level editor to extend it's File menu. - TSharedPtr MainMenuExtender; - - // The extender to pass to the level editor to extend it's Main menu. - //TSharedPtr FileMenuExtender; - - // DelegateHandle for the viewport's context menu extender - FDelegateHandle LevelViewportExtenderHandle; - - // SplineComponentVisualizer - TSharedPtr SplineComponentVisualizer; - - TSharedPtr HandleComponentVisualizer; - - // Array of HoudiniEngine console commands - TArray ConsoleCommands; - - // Delegate handle for the PreSaveWorld editor delegate - FDelegateHandle PreSaveWorldEditorDelegateHandle; - - // Delegate handle for the PostSaveWorld editor delegate: this - // is bound on PreSaveWorld with specific captures and then unbound - // by itself - FDelegateHandle PostSaveWorldOnceHandle; - - // Delegate handle for the PreBeginPIE editor delegate - FDelegateHandle PreBeginPIEEditorDelegateHandle; - - // Delegate handle for OnDeleteActorsBegin - FDelegateHandle OnDeleteActorsBegin; - - // Delegate handle for OnDeleteActorsEnd - FDelegateHandle OnDeleteActorsEnd; - - // List of actors that HandleOnDeleteActorsBegin marked to _not_ be deleted. This - // is used to re-select these actors in HandleOnDeleteActorsEnd. - TArray ActorsToReselectOnDeleteActorsEnd; - - // Cache the points of radio button folder circle points to avoid huge amount of repeat computation. - // (Computing points are time consuming since it uses trigonometric functions) - TArray HoudiniParameterRadioButtonPointsOuter; - TArray HoudiniParameterRadioButtonPointsInner; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "IHoudiniEngineEditor.h" +#include "HoudiniInputTypes.h" + +#include "CoreTypes.h" +#include "Templates/SharedPointer.h" + +class FExtender; +class IAssetTools; +class IAssetTypeActions; +class IComponentAssetBroker; +class FComponentVisualizer; +class FMenuBuilder; +class FMenuBarBuilder; +class FUICommandList; +class AActor; + +struct IConsoleCommand; +struct FSlateDynamicImageBrush; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod: int8; +enum class EHoudiniLandscapeOutputBakeType: uint8; +enum class EHoudiniEngineBakeOption : uint8; +enum class EPDGBakeSelectionOption : uint8; +enum class EPDGBakePackageReplaceModeOption : uint8; +enum class EPackageReplaceMode : int8; + +class HOUDINIENGINEEDITOR_API FHoudiniEngineEditor : public IHoudiniEngineEditor +{ + public: + FHoudiniEngineEditor(); + + // IModuleInterface methods. + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // IHoudiniEngineEditor methods + virtual void RegisterComponentVisualizers() override; + virtual void UnregisterComponentVisualizers() override; + virtual void RegisterDetails() override; + virtual void UnregisterDetails() override; + virtual void RegisterAssetTypeActions() override; + virtual void UnregisterAssetTypeActions() override; + virtual void RegisterAssetBrokers() override; + virtual void UnregisterAssetBrokers() override; + virtual void RegisterActorFactories() override; + virtual void ExtendMenu() override; + virtual void RegisterForUndo() override; + virtual void UnregisterForUndo() override; + virtual void RegisterPlacementModeExtensions() override; + virtual void UnregisterPlacementModeExtensions() override; + + // Return singleton instance of Houdini Engine Editor, used internally. + static FHoudiniEngineEditor & Get(); + + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // Returns the plugin's directory + static FString GetHoudiniEnginePluginDir(); + + // Initializes Widget resources + void InitializeWidgetResource(); + + // Menu action to pause cooking for all Houdini Assets + void PauseAssetCooking(); + + // Helper delegate used to determine if PauseAssetCooking can be executed. + bool CanPauseAssetCooking(); + + // Helper delegate used to get the current state of PauseAssetCooking. + bool IsAssetCookingPaused(); + + // Returns a pointer to the input choice types + TArray>* GetInputTypeChoiceLabels() { return &InputTypeChoiceLabels; }; + TArray>* GetBlueprintInputTypeChoiceLabels() { return &BlueprintInputTypeChoiceLabels; }; + + // Returns a pointer to the Houdini curve types + TArray>* GetHoudiniCurveTypeChoiceLabels() { return &HoudiniCurveTypeChoiceLabels; }; + + // Returns a pointer to the Houdini curve methods + TArray>* GetHoudiniCurveMethodChoiceLabels() { return &HoudiniCurveMethodChoiceLabels; }; + + // Returns a pointer to the Houdini ramp parameter interpolation methods + TArray>* GetHoudiniParameterRampInterpolationMethodLabels() {return &HoudiniParameterRampInterpolationLabels;} + + // Returns a pointer to the Houdini curve output export types + TArray>* GetHoudiniCurveOutputExportTypeLabels() { return &HoudiniCurveOutputExportTypeLabels; }; + + TArray>* GetHoudiniLandscapeOutputBakeOptionsLabels() { return &HoudiniLandscapeOutputBakeOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Type labels + TArray>* GetHoudiniEnginePDGBakeTypeOptionsLabels() { return &HoudiniEnginePDGBakeTypeOptionLabels; }; + + // Returns a pointer to the Houdini Engine Bake Type labels + TArray>* GetHoudiniEngineBakeTypeOptionsLabels() { return &HoudiniEngineBakeTypeOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Target labels + TArray>* GetHoudiniEnginePDGBakeSelectionOptionsLabels() { return &HoudiniEnginePDGBakeSelectionOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Package Replace Mode labels + TArray>* GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels() { return &HoudiniEnginePDGBakePackageReplaceModeOptionLabels; }; + + // Returns a shared Ptr to the Houdini logo + TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; + TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; + + // Functions Return a shared Ptr to the Houdini Engine UI Icon + TSharedPtr GetHoudiniEngineUIIconBrush() const { return HoudiniEngineUIIconBrush; } + TSharedPtr GetHoudiniEngineUIRebuildIconBrush() const { return HoudiniEngineUIRebuildIconBrush; } + TSharedPtr GetHoudiniEngineUIRecookIconBrush() const { return HoudiniEngineUIRecookIconBrush; } + TSharedPtr GetHoudiniEngineUIResetParametersIconBrush() const { return HoudiniEngineUIResetParametersIconBrush; } + + TSharedPtr GetHoudiniEngineUIBakeIconBrush() const { return HoudiniEngineUIBakeIconBrush; } + TSharedPtr GetHoudiniEngineUICookLogIconBrush() const { return HoudiniEngineUICookLogIconBrush; } + TSharedPtr GetHoudiniEngineUIAssetHelpIconBrush() const { return HoudiniEngineUIAssetHelpIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGIconBrush() const { return HoudiniEngineUIPDGIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGCancelIconBrush() const { return HoudiniEngineUIPDGCancelIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGDirtyAllIconBrush() const { return HoudiniEngineUIPDGDirtyAllIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGDirtyNodeIconBrush() const { return HoudiniEngineUIPDGDirtyNodeIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGPauseIconBrush() const { return HoudiniEngineUIPDGPauseIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGResetIconBrush() const { return HoudiniEngineUIPDGResetIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGRefreshIconBrush() const { return HoudiniEngineUIPDGRefreshIconBrush; } + + // Returns a pointer to Unreal output curve types (for temporary) + TArray>* GetUnrealOutputCurveTypeLabels() { return &UnrealCurveOutputCurveTypeLabels; }; + + // returns string from Houdini Engine Bake Option + FString GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption); + + // returns string from Houdini Engine PDG Bake Target Option + FString GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption); + + // returns string from PDG package replace mode option + FString GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption); + + // Return HoudiniEngineBakeOption from FString + const EHoudiniEngineBakeOption StringToHoudiniEngineBakeOption(const FString & InString); + + // Return EPDGBakeSelectionOption from FString + const EPDGBakeSelectionOption StringToPDGBakeSelectionOption(const FString& InString); + + // Return EPDGBakePackageReplaceModeOption from FString + const EPDGBakePackageReplaceModeOption StringToPDGBakePackageReplaceModeOption(const FString & InString); + + // Convert EPDGBakePackageReplaceModeOption to EPackageReplaceMode + // TODO: perhaps EPackageReplaceMode can be moved to HoudiniEngineRuntime to avoid having both + // TODO: EPDGBakePackageReplaceModeOption and EPackageReplaceMode? + const EPackageReplaceMode PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode); + + // Get the reference of the radio button folder circle point arrays reference + TArray & GetHoudiniParameterRadioButtonPointsOuter() { return HoudiniParameterRadioButtonPointsOuter; }; + TArray & GetHoudiniParameterRadioButtonPointsInner() { return HoudiniParameterRadioButtonPointsInner; }; + + // Gets the PostSaveWorldOnceHandle + FDelegateHandle& GetOnPostSaveWorldOnceHandle() { return PostSaveWorldOnceHandle; } + + protected: + + // Binds the commands used by the menus + void BindMenuCommands(); + + // Register AssetType action. + void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef< IAssetTypeActions > Action); + + // Add menu extension for our module. + void AddHoudiniFileMenuExtension(FMenuBuilder& MenuBuilder); + + // Add the Houdini Engine editor menu + void AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder); + + // Add menu extension for our module. + void AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder); + + // Adds the custom Houdini Engine commands to the world outliner context menu + void AddLevelViewportMenuExtender(); + + // Removes the custom Houdini Engine commands to the world outliner context menu + void RemoveLevelViewportMenuExtender(); + + // Returns all the custom Houdini Engine commands for the world outliner context menu + TSharedRef GetLevelViewportContextMenuExtender( + const TSharedRef CommandList, const TArray InActors); + + // Register all console commands provided by this module + void RegisterConsoleCommands(); + + // Unregister all registered console commands provided by this module + void UnregisterConsoleCommands(); + + // Register for any FEditorDelegates that we are interested in, such as + // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds + void RegisterEditorDelegates(); + + // Deregister editor delegates + void UnregisterEditorDelegates(); + + // Process the OnDeleteActorsBegin call received from FEditorDelegates. + // Check if any AHoudiniAssetActors with PDG links are selected for deletion. If so, + // check if these still have temporary outputs and give the user to option to skip + // deleting the ones with temporary output. + void HandleOnDeleteActorsBegin(); + + // Re-select AHoudiniAssetActors that were deselected (to avoid deletion) by HandleOnDeleteActorsBegin + void HandleOnDeleteActorsEnd(); + + private: + + // Singleton instance of Houdini Engine Editor. + static FHoudiniEngineEditor * HoudiniEngineEditorInstance; + + // AssetType actions associated with Houdini asset. + TArray> AssetTypeActions; + + // Broker associated with Houdini asset. + TSharedPtr HoudiniAssetBroker; + + // Widget resources: Input Type combo box labels + TArray> InputTypeChoiceLabels; + TArray> BlueprintInputTypeChoiceLabels; + + // Widget resources: Houdini Curve Type combo box labels + TArray> HoudiniCurveTypeChoiceLabels; + + // Widget resources: Houdini Curve Method combo box labels + TArray> HoudiniCurveMethodChoiceLabels; + + // Widget resources: Houdini Ramp Interpolation method combo box labels + TArray> HoudiniParameterRampInterpolationLabels; + + // Widget resources: Houdini Curve Output type labels + TArray> HoudiniCurveOutputExportTypeLabels; + + // Widget resources: Unreal Curve type labels (for temporary, we need to figure out a way to access the output curve's info later) + TArray> UnrealCurveOutputCurveTypeLabels; + + // Widget resources: Landscape output Bake type labels + TArray> HoudiniLandscapeOutputBakeOptionLabels; + + // Widget resources: PDG Bake type labels + TArray> HoudiniEnginePDGBakeTypeOptionLabels; + + // Widget resources: Bake type labels + TArray> HoudiniEngineBakeTypeOptionLabels; + + // Widget resources: PDG Bake target labels + TArray> HoudiniEnginePDGBakeSelectionOptionLabels; + + // Widget resources: PDG Bake package replace mode labels + TArray> HoudiniEnginePDGBakePackageReplaceModeOptionLabels; + + // List of UI commands used by the various menus + TSharedPtr HEngineCommands; + + // Houdini logo brush. + TSharedPtr HoudiniLogoBrush; + // Houdini Engine logo brush + TSharedPtr HoudiniEngineLogoBrush; + + // houdini Engine UI Brushes + TSharedPtr HoudiniEngineUIIconBrush; + TSharedPtr HoudiniEngineUIRebuildIconBrush; + TSharedPtr HoudiniEngineUIRecookIconBrush; + TSharedPtr HoudiniEngineUIResetParametersIconBrush; + + TSharedPtr HoudiniEngineUIBakeIconBrush; + TSharedPtr HoudiniEngineUICookLogIconBrush; + TSharedPtr HoudiniEngineUIAssetHelpIconBrush; + TSharedPtr HoudiniEngineUIPDGIconBrush; + TSharedPtr HoudiniEngineUIPDGCancelIconBrush; + TSharedPtr HoudiniEngineUIPDGDirtyAllIconBrush; + TSharedPtr HoudiniEngineUIPDGDirtyNodeIconBrush; + TSharedPtr HoudiniEngineUIPDGPauseIconBrush; + TSharedPtr HoudiniEngineUIPDGResetIconBrush; + TSharedPtr HoudiniEngineUIPDGRefreshIconBrush; + + // The extender to pass to the level editor to extend it's File menu. + TSharedPtr MainMenuExtender; + + // The extender to pass to the level editor to extend it's Main menu. + //TSharedPtr FileMenuExtender; + + // DelegateHandle for the viewport's context menu extender + FDelegateHandle LevelViewportExtenderHandle; + + // SplineComponentVisualizer + TSharedPtr SplineComponentVisualizer; + + TSharedPtr HandleComponentVisualizer; + + // Array of HoudiniEngine console commands + TArray ConsoleCommands; + + // Delegate handle for the PreSaveWorld editor delegate + FDelegateHandle PreSaveWorldEditorDelegateHandle; + + // Delegate handle for the PostSaveWorld editor delegate: this + // is bound on PreSaveWorld with specific captures and then unbound + // by itself + FDelegateHandle PostSaveWorldOnceHandle; + + // Delegate handle for the PreBeginPIE editor delegate + FDelegateHandle PreBeginPIEEditorDelegateHandle; + + // Delegate handle for OnDeleteActorsBegin + FDelegateHandle OnDeleteActorsBegin; + + // Delegate handle for OnDeleteActorsEnd + FDelegateHandle OnDeleteActorsEnd; + + // List of actors that HandleOnDeleteActorsBegin marked to _not_ be deleted. This + // is used to re-select these actors in HandleOnDeleteActorsEnd. + TArray ActorsToReselectOnDeleteActorsEnd; + + // Cache the points of radio button folder circle points to avoid huge amount of repeat computation. + // (Computing points are time consuming since it uses trigonometric functions) + TArray HoudiniParameterRadioButtonPointsOuter; + TArray HoudiniParameterRadioButtonPointsInner; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp index c5fdea932..8a63839cc 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp @@ -1,25 +1,25 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h index 1bfdf8b0c..293eeb8ed 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h @@ -1,27 +1,27 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h index 35a709559..d9ebe1684 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h @@ -1,149 +1,149 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#define HOUDINI_ENGINE_EDITOR - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "Editor.h" - -// Details panel desired sizes. -#define HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH 270 -#define HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH 310 -#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH 350 -#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_FULL_WIDGET_WIDTH 400 - - // URL used for bug reporting. -#define HAPI_UNREAL_BUG_REPORT_URL TEXT("https://www.sidefx.com/bugs/submit/") -#define HAPI_UNREAL_ONLINE_DOC_URL TEXT("https://www.sidefx.com/docs/unreal/") -#define HAPI_UNREAL_ONLINE_FORUM_URL TEXT("https://www.sidefx.com/forum/51/") - - -// -// Parameter UI constants -// - -// Constants for parameter UI indentation - -// Change this constant to change the overall indentation width -#define INDENTATION_UNIT_WIDTH 20.0f -// Do not change this width unless the folder triangle arrow is customized. -#define NON_FOLDER_OFFSET_WIDTH 22.0f - - -// Houdini parameter UI row margin heights -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON 8.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP 2.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR 5.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP 57.20f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3 7.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP 51.70f -#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER 2.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST 2.05f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY 62.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE 49.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM 36.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE 41.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE 68.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET 177.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE 235.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH 275.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD 219.35f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL 18.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3 7.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE 7.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL 4.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR 1.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING 6.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF 6.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE 7.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE 10.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE 5.60f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID 0.0f - - - -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER 4.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER 2.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER 2.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_MULTIPARMHEADER 57.20f -#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE_MULTIPARMHEADER 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER 2.60f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER 3.75f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_MULTIPARMHEADER 51.70f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE_MULTIPARMHEADER 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER 1.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER 58.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER 49.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER 40.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER 37.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER 68.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER 173.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER 231.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER 266.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER 215.05f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER 14.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER 2.57f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER 4.12f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER 4.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER 0.75f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER 1.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER 2.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER 2.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER 4.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER 10.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER 2.30f - -// Radio button UI constants -#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER 18 -#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER 8 -#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER 4.5f -#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER 1.0f -#define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X 7.0f +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#define HOUDINI_ENGINE_EDITOR + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Editor.h" + +// Details panel desired sizes. +#define HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH 270 +#define HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH 310 +#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH 350 +#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_FULL_WIDGET_WIDTH 400 + + // URL used for bug reporting. +#define HAPI_UNREAL_BUG_REPORT_URL TEXT("https://www.sidefx.com/bugs/submit/") +#define HAPI_UNREAL_ONLINE_DOC_URL TEXT("https://www.sidefx.com/docs/unreal/") +#define HAPI_UNREAL_ONLINE_FORUM_URL TEXT("https://www.sidefx.com/forum/51/") + + +// +// Parameter UI constants +// + +// Constants for parameter UI indentation + +// Change this constant to change the overall indentation width +#define INDENTATION_UNIT_WIDTH 20.0f +// Do not change this width unless the folder triangle arrow is customized. +#define NON_FOLDER_OFFSET_WIDTH 22.0f + + +// Houdini parameter UI row margin heights +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON 8.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP 2.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR 5.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP 57.20f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3 7.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP 51.70f +#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER 2.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST 2.05f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY 62.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE 49.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM 36.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE 41.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE 68.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET 177.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE 235.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH 275.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD 219.35f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL 18.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3 7.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE 7.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL 4.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR 1.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING 6.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF 6.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE 7.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE 10.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE 5.60f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID 0.0f + + + +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER 4.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER 2.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER 2.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_MULTIPARMHEADER 57.20f +#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE_MULTIPARMHEADER 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER 2.60f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER 3.75f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_MULTIPARMHEADER 51.70f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE_MULTIPARMHEADER 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER 1.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER 58.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER 49.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER 40.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER 37.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER 68.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER 173.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER 231.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER 266.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER 215.05f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER 14.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER 2.57f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER 4.12f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER 4.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER 0.75f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER 1.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER 2.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER 2.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER 4.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER 10.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER 2.30f + +// Radio button UI constants +#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER 18 +#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER 8 +#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER 4.5f +#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER 1.0f +#define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X 7.0f #define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_Y 13.2f \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp index 8ce8a5596..199a361e8 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp @@ -1,653 +1,653 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineEditorUtils.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniAssetActor.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniAsset.h" -#include "HoudiniOutput.h" -#include "HoudiniTool.h" - -#include "ContentBrowserModule.h" -#include "IContentBrowserSingleton.h" -#include "Engine/Selection.h" -#include "AssetRegistryModule.h" -#include "EditorViewportClient.h" -#include "ActorFactories/ActorFactory.h" -#include "FileHelpers.h" -#include "PropertyPathHelpers.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -int32 -FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection) -{ - ContentBrowserSelection.Empty(); - - // Get the current Content browser selection - FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >("ContentBrowser"); - TArray SelectedAssets; - ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets); - - for (int32 n = 0; n < SelectedAssets.Num(); n++) - { - // Get the current object - UObject * Object = SelectedAssets[n].GetAsset(); - if (!Object || Object->IsPendingKill()) - continue; - - // Only static meshes are supported - if (Object->GetClass() != UStaticMesh::StaticClass()) - continue; - - ContentBrowserSelection.Add(Object); - } - - return ContentBrowserSelection.Num(); -} - -int32 -FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly) -{ - WorldSelection.Empty(); - - // Get the current editor selection - if (GEditor) - { - USelection* SelectedActors = GEditor->GetSelectedActors(); - if (SelectedActors && !SelectedActors->IsPendingKill()) - { - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor * Actor = Cast< AActor >(*It); - if (!Actor && Actor->IsPendingKill()) - continue; - - // Ignore the SkySphere? - FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); - if (ClassName == TEXT("BP_Sky_Sphere_C")) - continue; - - // We're normally only selecting actors with StaticMeshComponents and SplineComponents - // Heightfields? Filter here or later? also allow HoudiniAssets? - WorldSelection.Add(Actor); - } - } - - } - - // If we only want Houdini Actors... - if (bHoudiniAssetActorsOnly) - { - // ... remove all but them - for (int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx--) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - WorldSelection.RemoveAt(Idx); - } - } - - return WorldSelection.Num(); -} - - -FString -FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType) -{ - FString HoudiniCurveTypeStr; - switch (HoudiniCurveType) - { - case EHoudiniCurveType::Invalid: - { - HoudiniCurveTypeStr = TEXT("Invalid"); - } - break; - - case EHoudiniCurveType::Polygon: - { - HoudiniCurveTypeStr = TEXT("Polygon"); - } - break; - - case EHoudiniCurveType::Nurbs: - { - HoudiniCurveTypeStr = TEXT("Nurbs"); - } - break; - - case EHoudiniCurveType::Bezier: - { - HoudiniCurveTypeStr = TEXT("Bezier"); - } - break; - - case EHoudiniCurveType::Points: - { - HoudiniCurveTypeStr = TEXT("Points"); - } - break; - } - - return HoudiniCurveTypeStr; -} - -FString -FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(const EHoudiniCurveMethod& CurveMethod) -{ - FString HoudiniCurveMethodStr; - switch (CurveMethod) - { - case EHoudiniCurveMethod::Invalid: - { - HoudiniCurveMethodStr = TEXT("Invalid"); - } - break; - case EHoudiniCurveMethod::CVs: - { - HoudiniCurveMethodStr = TEXT("CVs"); - } - break; - case EHoudiniCurveMethod::Breakpoints: - { - HoudiniCurveMethodStr = TEXT("Breakpoints"); - } - break; - case EHoudiniCurveMethod::Freehand: - { - HoudiniCurveMethodStr = TEXT("Freehand"); - } - break; - } - - return HoudiniCurveMethodStr; -} - - -FString -FHoudiniEngineEditorUtils::HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType) -{ - // Temporary, we need to figure out a way to access the output curve's info later - FString UnrealCurveType; - switch (HoudiniCurveType) - { - case EHoudiniCurveType::Polygon: - case EHoudiniCurveType::Points: - { - UnrealCurveType = TEXT("Linear"); - } - break; - - case EHoudiniCurveType::Nurbs: - case EHoudiniCurveType::Bezier: - { - UnrealCurveType = TEXT("Curve"); - } - break; - } - - return UnrealCurveType; -} - -FString -FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType) -{ - FString LandscapeBakeTypeString; - switch (LandscapeBakeType) - { - case EHoudiniLandscapeOutputBakeType::Detachment: - LandscapeBakeTypeString = "To Current Level"; - break; - - case EHoudiniLandscapeOutputBakeType::BakeToImage: - LandscapeBakeTypeString = "To Image"; - break; - - case EHoudiniLandscapeOutputBakeType::BakeToWorld: - LandscapeBakeTypeString = "To New World"; - break; - - } - - return LandscapeBakeTypeString; -} - - -FTransform -FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform() -{ - FTransform SpawnTransform = FTransform::Identity; - - // Get the editor viewport LookAt position to spawn the new objects - if (GEditor && GEditor->GetActiveViewport()) - { - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (ViewportClient) - { - // We need to toggle the orbit camera on to get the proper LookAtLocation to spawn our asset - ViewportClient->ToggleOrbitCamera(true); - SpawnTransform.SetLocation(ViewportClient->GetLookAtLocation()); - ViewportClient->ToggleOrbitCamera(false); - } - } - - return SpawnTransform; -} - -FTransform -FHoudiniEngineEditorUtils::GetMeanWorldSelectionTransform() -{ - FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); - - if (GEditor && (GEditor->GetSelectedActorCount() > 0)) - { - // Get the current Level Editor Selection - USelection* SelectedActors = GEditor->GetSelectedActors(); - - int NumAppliedTransform = 0; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor * Actor = Cast< AActor >(*It); - if (!Actor) - continue; - - // Just Ignore the SkySphere... - FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); - if (ClassName == TEXT("BP_Sky_Sphere_C")) - continue; - - FTransform CurrentTransform = Actor->GetTransform(); - - ALandscapeProxy* Landscape = Cast< ALandscapeProxy >(Actor); - if (Landscape) - { - // We need to offset Landscape's transform in X/Y to center them properly - FVector Origin, Extent; - Actor->GetActorBounds(false, Origin, Extent); - - // Use the origin's XY Position - FVector Location = CurrentTransform.GetLocation(); - Location.X = Origin.X; - Location.Y = Origin.Y; - CurrentTransform.SetLocation(Location); - } - - // Accumulate all the actor transforms... - if (NumAppliedTransform == 0) - SpawnTransform = CurrentTransform; - else - SpawnTransform.Accumulate(CurrentTransform); - - NumAppliedTransform++; - } - - if (NumAppliedTransform > 0) - { - // "Mean" all the accumulated Transform - SpawnTransform.SetScale3D(FVector::OneVector); - SpawnTransform.NormalizeRotation(); - - if (NumAppliedTransform > 1) - SpawnTransform.SetLocation(SpawnTransform.GetLocation() / (float)NumAppliedTransform); - } - } - - return SpawnTransform; -} - -void -FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType) -{ - if (!InHoudiniAsset) - return; - - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - // Load the asset - UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); - if (!AssetObj) - return; - - // Get the asset Factory - UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); - if (!Factory) - return; - - // Get the current Level Editor Selection - TArray WorldSelection; - int32 WorldSelectionCount = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection); - - // Get the current Content browser selection - TArray ContentBrowserSelection; - int32 ContentBrowserSelectionCount = FHoudiniEngineEditorUtils::GetContentBrowserSelection(ContentBrowserSelection); - - // By default, Content browser selection has a priority over the world selection - bool UseCBSelection = ContentBrowserSelectionCount > 0; - if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY) - UseCBSelection = true; - else if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY) - UseCBSelection = false; - - // Modify the created actor's position from the current editor world selection - FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); - if (WorldSelectionCount > 0) - { - // Get the "mean" transform of all the selected actors - SpawnTransform = GetMeanWorldSelectionTransform(); - } - - // If the current tool is a batch one, we'll need to create multiple instances of the HDA - if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH) - { - // Unselect the current selection to select the created actor after - if (GEditor) - GEditor->SelectNone(true, true, false); - - // An instance of the asset will be created for each selected object - for (int32 SelecIndex = 0; SelecIndex < (UseCBSelection ? ContentBrowserSelectionCount : WorldSelectionCount); SelecIndex++) - { - // Get the current object - UObject* CurrentSelectedObject = nullptr; - if (UseCBSelection && ContentBrowserSelection.IsValidIndex(SelecIndex)) - CurrentSelectedObject = ContentBrowserSelection[SelecIndex]; - - if (!UseCBSelection && WorldSelection.IsValidIndex(SelecIndex)) - CurrentSelectedObject = WorldSelection[SelecIndex]; - - if (!CurrentSelectedObject) - continue; - - // If it's an actor, use its Transform to spawn the HDA - AActor* CurrentSelectedActor = Cast(CurrentSelectedObject); - if (CurrentSelectedActor) - SpawnTransform = CurrentSelectedActor->GetTransform(); - else - SpawnTransform = GetDefaulAssetSpawnTransform(); - - // Create the actor for the HDA - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); - if (!CreatedActor) - continue; - - // Get the HoudiniAssetActor / HoudiniAssetComponent we just created - AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; - if (!HoudiniAssetActor) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent) - continue; - - // Create and set the input preset for this HDA and selected Object - TMap InputPreset; - InputPreset.Add(CurrentSelectedObject, 0); - HoudiniAssetComponent->SetInputPresets(InputPreset); - - // Select the Actor we just created - if (GEditor && GEditor->CanSelectActor(CreatedActor, true, false)) - GEditor->SelectActor(CreatedActor, true, true, true); - } - } - else - { - // We only need to create a single instance of the asset, regarding of the selection - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); - if (!CreatedActor) - return; - - // Generator tools don't need to preset their input - if (InType != EHoudiniToolType::HTOOLTYPE_GENERATOR) - { - TMap InputPresets; - AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor ? HoudiniAssetActor->GetHoudiniAssetComponent() : nullptr; - if (HoudiniAssetComponent) - { - // Build the preset map - int InputIndex = 0; - for (auto CurrentObject : (UseCBSelection ? ContentBrowserSelection : WorldSelection)) - { - if (!CurrentObject) - continue; - - if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI) - { - // The selection will be applied individually to multiple inputs - // (first object to first input, second object to second input etc...) - InputPresets.Add(CurrentObject, InputIndex++); - } - else - { - // All the selection will be applied to the asset's first input - InputPresets.Add(CurrentObject, 0); - } - } - - // Set the input preset on the HoudiniAssetComponent - if (InputPresets.Num() > 0) - HoudiniAssetComponent->SetInputPresets(InputPresets); - } - } - - // Select the Actor we just created - if (GEditor->CanSelectActor(CreatedActor, true, true)) - { - GEditor->SelectNone(true, true, false); - GEditor->SelectActor(CreatedActor, true, true, true); - } - } -} - -void -FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform) -{ - if (!InHoudiniAsset) - return; - - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - // Load the asset - UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); - if (!AssetObj) - return; - - // Get the asset Factory - UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); - if (!Factory) - return; - - // We only need to create a single instance of the asset, regarding of the selection - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), InTransform); - if (!CreatedActor) - return; - - // Select the Actor we just created - if (GEditor->CanSelectActor(CreatedActor, true, true)) - { - GEditor->SelectNone(true, true, false); - GEditor->SelectActor(CreatedActor, true, true, true); - } -} - - -void -FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) -{ - // Add a slate notification - FString Notification = TEXT("Saving all Houdini temporary cook data..."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - - TArray PackagesToSave; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HAC = *Itr; - if (!HAC || HAC->IsPendingKill()) - continue; - - if (InSaveWorld && InSaveWorld != HAC->GetWorld()) - continue; - - const int32 NumOutputs = HAC->GetNumOutputs(); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - // TODO: Also save landscape layer info objects? - if (Output->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (auto &OutputObjectPair : Output->GetOutputObjects()) - { - UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) - continue; - - UStaticMesh *SM = Cast(Obj); - if (!SM) - continue; - - UPackage *Package = SM->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - - for (auto& MaterialAssignementPair : Output->GetAssignementMaterials()) - { - UMaterialInterface* MatInterface = MaterialAssignementPair.Value; - if (!MatInterface || MatInterface->IsPendingKill()) - continue; - - UPackage *Package = MatInterface->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - } - } - - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); -} - -void -FHoudiniEngineEditorUtils::ReselectSelectedActors() -{ - // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? - USelection* Selection = GEditor->GetSelectedActors(); - TArray SelectedActors; - SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); - Selection->GetSelectedObjects(SelectedActors); - - GEditor->SelectNone(false, false, false); - - for (AActor* NextSelected : SelectedActors) - { - GEditor->SelectActor(NextSelected, true, true, true, true); - } -} - -FString -FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding, const TCHAR PathSep) -{ - int32 Depth = 0; - for (const TCHAR Char : InNodePath) - { - if (Char == PathSep) - Depth++; - } - FString Trimmed = InNodeName; - Trimmed.TrimStartInline(); - return Trimmed.LeftPad(Trimmed.Len() + (Depth * Padding)); -} - -void -FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject) -{ - if (!IsValid(InRootObject)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty]: InRootObject is null.")); - return; - } - - const FCachedPropertyPath CachedPath(InPropertyPath.ToString()); - if (CachedPath.Resolve(InRootObject)) - { - // Notify that we have changed the property - // FPropertyChangedEvent Evt = CachedPath.ToPropertyChangedEvent(EPropertyChangeType::Unspecified); - // Construct FPropertyChangedEvent from the cached property path - const int32 NumSegments = CachedPath.GetNumSegments(); - FPropertyChangedEvent Evt( - CastFieldChecked(CachedPath.GetLastSegment().GetField().ToField()), - EPropertyChangeType::Unspecified, - { InRootObject }); - - if(NumSegments > 1) - { - Evt.SetActiveMemberProperty(CastFieldChecked(CachedPath.GetSegment(NumSegments - 2).GetField().ToField())); - } - - // Set the array of indices to the changed property - TArray> ArrayIndicesPerObject; - ArrayIndicesPerObject.AddDefaulted(1); - for (int32 SegmentIdx = 0; SegmentIdx < NumSegments; ++SegmentIdx) - { - const FPropertyPathSegment& Segment = CachedPath.GetSegment(SegmentIdx); - const int32 ArrayIndex = Segment.GetArrayIndex(); - if (ArrayIndex != INDEX_NONE) - { - ArrayIndicesPerObject[0].Add(Segment.GetName().ToString(), ArrayIndex); - } - } - Evt.SetArrayIndexPerObject(ArrayIndicesPerObject); - - FEditPropertyChain Chain; - CachedPath.ToEditPropertyChain(Chain); - FPropertyChangedChainEvent ChainEvent(Chain, Evt); - ChainEvent.ObjectIteratorIndex = 0; - InRootObject->PostEditChangeChainProperty(ChainEvent); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could not resolve property path '%s' on %s."), *InPropertyPath.ToString(), *(InRootObject->GetFullName())); - } -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineEditorUtils.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniAssetActor.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniAsset.h" +#include "HoudiniOutput.h" +#include "HoudiniTool.h" + +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" +#include "Engine/Selection.h" +#include "AssetRegistryModule.h" +#include "EditorViewportClient.h" +#include "ActorFactories/ActorFactory.h" +#include "FileHelpers.h" +#include "PropertyPathHelpers.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +int32 +FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection) +{ + ContentBrowserSelection.Empty(); + + // Get the current Content browser selection + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >("ContentBrowser"); + TArray SelectedAssets; + ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets); + + for (int32 n = 0; n < SelectedAssets.Num(); n++) + { + // Get the current object + UObject * Object = SelectedAssets[n].GetAsset(); + if (!Object || Object->IsPendingKill()) + continue; + + // Only static meshes are supported + if (Object->GetClass() != UStaticMesh::StaticClass()) + continue; + + ContentBrowserSelection.Add(Object); + } + + return ContentBrowserSelection.Num(); +} + +int32 +FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly) +{ + WorldSelection.Empty(); + + // Get the current editor selection + if (GEditor) + { + USelection* SelectedActors = GEditor->GetSelectedActors(); + if (SelectedActors && !SelectedActors->IsPendingKill()) + { + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor * Actor = Cast< AActor >(*It); + if (!Actor && Actor->IsPendingKill()) + continue; + + // Ignore the SkySphere? + FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); + if (ClassName == TEXT("BP_Sky_Sphere_C")) + continue; + + // We're normally only selecting actors with StaticMeshComponents and SplineComponents + // Heightfields? Filter here or later? also allow HoudiniAssets? + WorldSelection.Add(Actor); + } + } + + } + + // If we only want Houdini Actors... + if (bHoudiniAssetActorsOnly) + { + // ... remove all but them + for (int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx--) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + WorldSelection.RemoveAt(Idx); + } + } + + return WorldSelection.Num(); +} + + +FString +FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType) +{ + FString HoudiniCurveTypeStr; + switch (HoudiniCurveType) + { + case EHoudiniCurveType::Invalid: + { + HoudiniCurveTypeStr = TEXT("Invalid"); + } + break; + + case EHoudiniCurveType::Polygon: + { + HoudiniCurveTypeStr = TEXT("Polygon"); + } + break; + + case EHoudiniCurveType::Nurbs: + { + HoudiniCurveTypeStr = TEXT("Nurbs"); + } + break; + + case EHoudiniCurveType::Bezier: + { + HoudiniCurveTypeStr = TEXT("Bezier"); + } + break; + + case EHoudiniCurveType::Points: + { + HoudiniCurveTypeStr = TEXT("Points"); + } + break; + } + + return HoudiniCurveTypeStr; +} + +FString +FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(const EHoudiniCurveMethod& CurveMethod) +{ + FString HoudiniCurveMethodStr; + switch (CurveMethod) + { + case EHoudiniCurveMethod::Invalid: + { + HoudiniCurveMethodStr = TEXT("Invalid"); + } + break; + case EHoudiniCurveMethod::CVs: + { + HoudiniCurveMethodStr = TEXT("CVs"); + } + break; + case EHoudiniCurveMethod::Breakpoints: + { + HoudiniCurveMethodStr = TEXT("Breakpoints"); + } + break; + case EHoudiniCurveMethod::Freehand: + { + HoudiniCurveMethodStr = TEXT("Freehand"); + } + break; + } + + return HoudiniCurveMethodStr; +} + + +FString +FHoudiniEngineEditorUtils::HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType) +{ + // Temporary, we need to figure out a way to access the output curve's info later + FString UnrealCurveType; + switch (HoudiniCurveType) + { + case EHoudiniCurveType::Polygon: + case EHoudiniCurveType::Points: + { + UnrealCurveType = TEXT("Linear"); + } + break; + + case EHoudiniCurveType::Nurbs: + case EHoudiniCurveType::Bezier: + { + UnrealCurveType = TEXT("Curve"); + } + break; + } + + return UnrealCurveType; +} + +FString +FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType) +{ + FString LandscapeBakeTypeString; + switch (LandscapeBakeType) + { + case EHoudiniLandscapeOutputBakeType::Detachment: + LandscapeBakeTypeString = "To Current Level"; + break; + + case EHoudiniLandscapeOutputBakeType::BakeToImage: + LandscapeBakeTypeString = "To Image"; + break; + + case EHoudiniLandscapeOutputBakeType::BakeToWorld: + LandscapeBakeTypeString = "To New World"; + break; + + } + + return LandscapeBakeTypeString; +} + + +FTransform +FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform() +{ + FTransform SpawnTransform = FTransform::Identity; + + // Get the editor viewport LookAt position to spawn the new objects + if (GEditor && GEditor->GetActiveViewport()) + { + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (ViewportClient) + { + // We need to toggle the orbit camera on to get the proper LookAtLocation to spawn our asset + ViewportClient->ToggleOrbitCamera(true); + SpawnTransform.SetLocation(ViewportClient->GetLookAtLocation()); + ViewportClient->ToggleOrbitCamera(false); + } + } + + return SpawnTransform; +} + +FTransform +FHoudiniEngineEditorUtils::GetMeanWorldSelectionTransform() +{ + FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); + + if (GEditor && (GEditor->GetSelectedActorCount() > 0)) + { + // Get the current Level Editor Selection + USelection* SelectedActors = GEditor->GetSelectedActors(); + + int NumAppliedTransform = 0; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor * Actor = Cast< AActor >(*It); + if (!Actor) + continue; + + // Just Ignore the SkySphere... + FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); + if (ClassName == TEXT("BP_Sky_Sphere_C")) + continue; + + FTransform CurrentTransform = Actor->GetTransform(); + + ALandscapeProxy* Landscape = Cast< ALandscapeProxy >(Actor); + if (Landscape) + { + // We need to offset Landscape's transform in X/Y to center them properly + FVector Origin, Extent; + Actor->GetActorBounds(false, Origin, Extent); + + // Use the origin's XY Position + FVector Location = CurrentTransform.GetLocation(); + Location.X = Origin.X; + Location.Y = Origin.Y; + CurrentTransform.SetLocation(Location); + } + + // Accumulate all the actor transforms... + if (NumAppliedTransform == 0) + SpawnTransform = CurrentTransform; + else + SpawnTransform.Accumulate(CurrentTransform); + + NumAppliedTransform++; + } + + if (NumAppliedTransform > 0) + { + // "Mean" all the accumulated Transform + SpawnTransform.SetScale3D(FVector::OneVector); + SpawnTransform.NormalizeRotation(); + + if (NumAppliedTransform > 1) + SpawnTransform.SetLocation(SpawnTransform.GetLocation() / (float)NumAppliedTransform); + } + } + + return SpawnTransform; +} + +void +FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType) +{ + if (!InHoudiniAsset) + return; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + // Load the asset + UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); + if (!AssetObj) + return; + + // Get the asset Factory + UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); + if (!Factory) + return; + + // Get the current Level Editor Selection + TArray WorldSelection; + int32 WorldSelectionCount = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection); + + // Get the current Content browser selection + TArray ContentBrowserSelection; + int32 ContentBrowserSelectionCount = FHoudiniEngineEditorUtils::GetContentBrowserSelection(ContentBrowserSelection); + + // By default, Content browser selection has a priority over the world selection + bool UseCBSelection = ContentBrowserSelectionCount > 0; + if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY) + UseCBSelection = true; + else if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY) + UseCBSelection = false; + + // Modify the created actor's position from the current editor world selection + FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); + if (WorldSelectionCount > 0) + { + // Get the "mean" transform of all the selected actors + SpawnTransform = GetMeanWorldSelectionTransform(); + } + + // If the current tool is a batch one, we'll need to create multiple instances of the HDA + if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH) + { + // Unselect the current selection to select the created actor after + if (GEditor) + GEditor->SelectNone(true, true, false); + + // An instance of the asset will be created for each selected object + for (int32 SelecIndex = 0; SelecIndex < (UseCBSelection ? ContentBrowserSelectionCount : WorldSelectionCount); SelecIndex++) + { + // Get the current object + UObject* CurrentSelectedObject = nullptr; + if (UseCBSelection && ContentBrowserSelection.IsValidIndex(SelecIndex)) + CurrentSelectedObject = ContentBrowserSelection[SelecIndex]; + + if (!UseCBSelection && WorldSelection.IsValidIndex(SelecIndex)) + CurrentSelectedObject = WorldSelection[SelecIndex]; + + if (!CurrentSelectedObject) + continue; + + // If it's an actor, use its Transform to spawn the HDA + AActor* CurrentSelectedActor = Cast(CurrentSelectedObject); + if (CurrentSelectedActor) + SpawnTransform = CurrentSelectedActor->GetTransform(); + else + SpawnTransform = GetDefaulAssetSpawnTransform(); + + // Create the actor for the HDA + AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); + if (!CreatedActor) + continue; + + // Get the HoudiniAssetActor / HoudiniAssetComponent we just created + AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; + if (!HoudiniAssetActor) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent) + continue; + + // Create and set the input preset for this HDA and selected Object + TMap InputPreset; + InputPreset.Add(CurrentSelectedObject, 0); + HoudiniAssetComponent->SetInputPresets(InputPreset); + + // Select the Actor we just created + if (GEditor && GEditor->CanSelectActor(CreatedActor, true, false)) + GEditor->SelectActor(CreatedActor, true, true, true); + } + } + else + { + // We only need to create a single instance of the asset, regarding of the selection + AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); + if (!CreatedActor) + return; + + // Generator tools don't need to preset their input + if (InType != EHoudiniToolType::HTOOLTYPE_GENERATOR) + { + TMap InputPresets; + AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor ? HoudiniAssetActor->GetHoudiniAssetComponent() : nullptr; + if (HoudiniAssetComponent) + { + // Build the preset map + int InputIndex = 0; + for (auto CurrentObject : (UseCBSelection ? ContentBrowserSelection : WorldSelection)) + { + if (!CurrentObject) + continue; + + if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI) + { + // The selection will be applied individually to multiple inputs + // (first object to first input, second object to second input etc...) + InputPresets.Add(CurrentObject, InputIndex++); + } + else + { + // All the selection will be applied to the asset's first input + InputPresets.Add(CurrentObject, 0); + } + } + + // Set the input preset on the HoudiniAssetComponent + if (InputPresets.Num() > 0) + HoudiniAssetComponent->SetInputPresets(InputPresets); + } + } + + // Select the Actor we just created + if (GEditor->CanSelectActor(CreatedActor, true, true)) + { + GEditor->SelectNone(true, true, false); + GEditor->SelectActor(CreatedActor, true, true, true); + } + } +} + +void +FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform) +{ + if (!InHoudiniAsset) + return; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + // Load the asset + UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); + if (!AssetObj) + return; + + // Get the asset Factory + UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); + if (!Factory) + return; + + // We only need to create a single instance of the asset, regarding of the selection + AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), InTransform); + if (!CreatedActor) + return; + + // Select the Actor we just created + if (GEditor->CanSelectActor(CreatedActor, true, true)) + { + GEditor->SelectNone(true, true, false); + GEditor->SelectActor(CreatedActor, true, true, true); + } +} + + +void +FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) +{ + // Add a slate notification + FString Notification = TEXT("Saving all Houdini temporary cook data..."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + + TArray PackagesToSave; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HAC = *Itr; + if (!HAC || HAC->IsPendingKill()) + continue; + + if (InSaveWorld && InSaveWorld != HAC->GetWorld()) + continue; + + const int32 NumOutputs = HAC->GetNumOutputs(); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + // TODO: Also save landscape layer info objects? + if (Output->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (auto &OutputObjectPair : Output->GetOutputObjects()) + { + UObject *Obj = OutputObjectPair.Value.OutputObject; + if (!Obj || Obj->IsPendingKill()) + continue; + + UStaticMesh *SM = Cast(Obj); + if (!SM) + continue; + + UPackage *Package = SM->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + + for (auto& MaterialAssignementPair : Output->GetAssignementMaterials()) + { + UMaterialInterface* MatInterface = MaterialAssignementPair.Value; + if (!MatInterface || MatInterface->IsPendingKill()) + continue; + + UPackage *Package = MatInterface->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + } + } + + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); +} + +void +FHoudiniEngineEditorUtils::ReselectSelectedActors() +{ + // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? + USelection* Selection = GEditor->GetSelectedActors(); + TArray SelectedActors; + SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); + Selection->GetSelectedObjects(SelectedActors); + + GEditor->SelectNone(false, false, false); + + for (AActor* NextSelected : SelectedActors) + { + GEditor->SelectActor(NextSelected, true, true, true, true); + } +} + +FString +FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding, const TCHAR PathSep) +{ + int32 Depth = 0; + for (const TCHAR Char : InNodePath) + { + if (Char == PathSep) + Depth++; + } + FString Trimmed = InNodeName; + Trimmed.TrimStartInline(); + return Trimmed.LeftPad(Trimmed.Len() + (Depth * Padding)); +} + +void +FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject) +{ + if (!IsValid(InRootObject)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty]: InRootObject is null.")); + return; + } + + const FCachedPropertyPath CachedPath(InPropertyPath.ToString()); + if (CachedPath.Resolve(InRootObject)) + { + // Notify that we have changed the property + // FPropertyChangedEvent Evt = CachedPath.ToPropertyChangedEvent(EPropertyChangeType::Unspecified); + // Construct FPropertyChangedEvent from the cached property path + const int32 NumSegments = CachedPath.GetNumSegments(); + FPropertyChangedEvent Evt( + CastFieldChecked(CachedPath.GetLastSegment().GetField().ToField()), + EPropertyChangeType::Unspecified, + { InRootObject }); + + if(NumSegments > 1) + { + Evt.SetActiveMemberProperty(CastFieldChecked(CachedPath.GetSegment(NumSegments - 2).GetField().ToField())); + } + + // Set the array of indices to the changed property + TArray> ArrayIndicesPerObject; + ArrayIndicesPerObject.AddDefaulted(1); + for (int32 SegmentIdx = 0; SegmentIdx < NumSegments; ++SegmentIdx) + { + const FPropertyPathSegment& Segment = CachedPath.GetSegment(SegmentIdx); + const int32 ArrayIndex = Segment.GetArrayIndex(); + if (ArrayIndex != INDEX_NONE) + { + ArrayIndicesPerObject[0].Add(Segment.GetName().ToString(), ArrayIndex); + } + } + Evt.SetArrayIndexPerObject(ArrayIndicesPerObject); + + FEditPropertyChain Chain; + CachedPath.ToEditPropertyChain(Chain); + FPropertyChangedChainEvent ChainEvent(Chain, Evt); + ChainEvent.ObjectIteratorIndex = 0; + InRootObject->PostEditChangeChainProperty(ChainEvent); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could not resolve property path '%s' on %s."), *InPropertyPath.ToString(), *(InRootObject->GetFullName())); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h index 4723fb478..dbe4f7bb4 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h @@ -1,86 +1,86 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -class FString; -class UObject; -class UHoudiniAsset; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniLandscapeOutputBakeType : uint8; -enum class EHoudiniToolType : uint8; -enum class EHoudiniToolSelectionType : uint8; - -struct FHoudiniEngineEditorUtils -{ -public: - - // Triggers an update the details panel - //static void UpdateEditorProperties(UObject* ObjectToUpdate); - - // Helper function for accessing the current CB selection - static int32 GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection); - - // Helper function for accessing the current world selection - static int32 GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly = false); - - static FString HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType); - - static FString HoudiniCurveMethodToString(const EHoudiniCurveMethod& HoudinCurveMethod); - - static FString HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType); - - // (for temporary, we need to figure out a way to access the output curve's info later) - static FString HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType); - - static FTransform GetDefaulAssetSpawnTransform(); - - static FTransform GetMeanWorldSelectionTransform(); - - // Instantiate a HoudiniAsset at a given position - static void InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform); - - // Instantiate the given HDA, and handles the current CB/World selection - static void InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType); - - // Helper function used to save all temporary packages when the level is saved - static void SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld); - - // Deselect and reselect all selected actors to get rid of component not showing bug after create. - static void ReselectSelectedActors(); - - // Gets the node name indent from the left with the specified number of spaces based on the path depth. - static FString GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding=4, const TCHAR PathSep='/'); - - // Property change notifications - // Call PostEditChangeChainProperty on InRootObject for the property at InPropertyPath relative to - // InRootObject. - static void NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +class FString; +class UObject; +class UHoudiniAsset; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniLandscapeOutputBakeType : uint8; +enum class EHoudiniToolType : uint8; +enum class EHoudiniToolSelectionType : uint8; + +struct FHoudiniEngineEditorUtils +{ +public: + + // Triggers an update the details panel + //static void UpdateEditorProperties(UObject* ObjectToUpdate); + + // Helper function for accessing the current CB selection + static int32 GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection); + + // Helper function for accessing the current world selection + static int32 GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly = false); + + static FString HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType); + + static FString HoudiniCurveMethodToString(const EHoudiniCurveMethod& HoudinCurveMethod); + + static FString HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType); + + // (for temporary, we need to figure out a way to access the output curve's info later) + static FString HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType); + + static FTransform GetDefaulAssetSpawnTransform(); + + static FTransform GetMeanWorldSelectionTransform(); + + // Instantiate a HoudiniAsset at a given position + static void InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform); + + // Instantiate the given HDA, and handles the current CB/World selection + static void InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType); + + // Helper function used to save all temporary packages when the level is saved + static void SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld); + + // Deselect and reselect all selected actors to get rid of component not showing bug after create. + static void ReselectSelectedActors(); + + // Gets the node name indent from the left with the specified number of spaces based on the path depth. + static FString GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding=4, const TCHAR PathSep='/'); + + // Property change notifications + // Call PostEditChangeChainProperty on InRootObject for the property at InPropertyPath relative to + // InRootObject. + static void NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp index 3b06bc10a..cf67c3152 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp @@ -1,311 +1,311 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineStyle.h" - -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineUtils.h" - -#include "EditorStyleSet.h" -#include "Styling/SlateStyleRegistry.h" -#include "Styling/SlateTypes.h" -#include "SlateOptMacros.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) -#define TTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) -#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) -#define OTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) - -TSharedPtr FHoudiniEngineStyle::StyleSet = nullptr; - -TSharedPtr -FHoudiniEngineStyle::Get() -{ - return StyleSet; -} - -FName -FHoudiniEngineStyle::GetStyleSetName() -{ - static FName HoudiniStyleName(TEXT("HoudiniEngineStyle")); - return HoudiniStyleName; -} - -BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION - -void -FHoudiniEngineStyle::Initialize() -{ - // Only register the StyleSet once - if (StyleSet.IsValid()) - return; - - StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName())); - StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); - StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); - - // Note, these sizes are in Slate Units. - // Slate Units do NOT have to map to pixels. - const FVector2D Icon5x16(5.0f, 16.0f); - const FVector2D Icon8x4(8.0f, 4.0f); - const FVector2D Icon8x8(8.0f, 8.0f); - const FVector2D Icon10x10(10.0f, 10.0f); - const FVector2D Icon12x12(12.0f, 12.0f); - const FVector2D Icon12x16(12.0f, 16.0f); - const FVector2D Icon14x14(14.0f, 14.0f); - const FVector2D Icon16x16(16.0f, 16.0f); - const FVector2D Icon20x20(20.0f, 20.0f); - const FVector2D Icon22x22(22.0f, 22.0f); - const FVector2D Icon24x24(24.0f, 24.0f); - const FVector2D Icon25x25(25.0f, 25.0f); - const FVector2D Icon32x32(32.0f, 32.0f); - const FVector2D Icon40x40(40.0f, 40.0f); - const FVector2D Icon64x64(64.0f, 64.0f); - const FVector2D Icon36x24(36.0f, 24.0f); - const FVector2D Icon128x128(128.0f, 128.0f); - - static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); - StyleSet->Set( - "HoudiniEngine.HoudiniEngineLogo", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set( - "ClassIcon.HoudiniAssetActor", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set( - "HoudiniEngine.HoudiniEngineLogo40", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); - - StyleSet->Set( - "ClassIcon.HoudiniAsset", - new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset.png"), Icon16x16)); - - StyleSet->Set( - "ClassThumbnail.HoudiniAsset", - new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset_128.png"), Icon64x64)); - - static FString ResourcesDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/"); - - FString AssetHelpIcon = IconsDir + TEXT("asset_help16x16.png"); - FString BakeAllIcon = IconsDir + TEXT("bake_all16x16.png"); - FString BakeSelIcon = IconsDir + TEXT("bake_selected16x16.png"); - FString CleanTempIcon = IconsDir + TEXT("clean_temp16x16.png"); - FString CookAllIcon = IconsDir + TEXT("cook_all16x16.png"); - FString CookLogIcon = IconsDir + TEXT("cook_log16x16.png"); - FString CookSelIcon = IconsDir + TEXT("cook_selected16x16.png"); - FString DigitalAssetIcon = IconsDir + TEXT("digital_asset16x16.png"); - FString OnlineForumIcon = IconsDir + TEXT("online_forum16x16.png"); - FString OnlineHelpIcon = IconsDir + TEXT("online_help16x16.png"); - FString OpenInHIcon = IconsDir + TEXT("open_in_houdini16x16.png"); - FString PauseIcon = IconsDir + TEXT("pause16x16.png"); - FString PDGCancelIcon = IconsDir + TEXT("pdg_cancel16x16.png"); - FString PDGDirtyAllIcon = IconsDir + TEXT("pdg_dirty_all16x16.png"); - FString PDGDirtyNodeIcon = IconsDir + TEXT("pdg_dirty_node16x16.png"); - FString PDGLinkIcon = IconsDir + TEXT("pdg_link16x16.png"); - FString PDGPauseIcon = IconsDir + TEXT("pdg_pause16x16.png"); - FString PDGRefreshIcon = IconsDir + TEXT("pdg_refresh16x16.png"); - FString PDGResetIcon = IconsDir + TEXT("pdg_reset16x16.png"); - FString RebuildAllIcon = IconsDir + TEXT("rebuild_all16x16.png"); - FString RebuildSelIcon = IconsDir + TEXT("rebuild_selected16x16.png"); - FString RefineAllIcon = IconsDir + TEXT("refine_all16x16.png"); - FString RefineSelIcon = IconsDir + TEXT("refine_selected16x16.png"); - FString ReportBugIcon = IconsDir + TEXT("report_bug16x16.png"); - FString ResetIcon = IconsDir + TEXT("reset16x16.png"); - FString ResetParamIcon = IconsDir + TEXT("reset_parameters16x16.png"); - FString SaveToHipIcon = IconsDir + TEXT("save_to_hip16x16.png"); - FString SessionConnectIcon = IconsDir + TEXT("session_connect16x16.png"); - FString SessionCreateIcon = IconsDir + TEXT("session_create16x16.png"); - FString SessionRestartIcon = IconsDir + TEXT("session_restart16x16.png"); - FString SessionStopIcon = IconsDir + TEXT("session_stop16x16.png"); - FString SessionSyncIcon = IconsDir + TEXT("session_sync16x16.png"); - FString SessionSyncStartIcon = IconsDir + TEXT("session_sync_start16x16.png"); - FString SessionSyncStopIcon = IconsDir + TEXT("session_sync_stop16x16.png"); - FString ViewportSyncIcon = IconsDir + TEXT("viewport_sync16x16.png"); - FString ViewportSyncBothIcon = IconsDir + TEXT("viewport_sync_both16x16.png"); - FString ViewportSyncHoudiniIcon = IconsDir + TEXT("viewport_sync_houdini16x16.png"); - FString ViewportSyncOffIcon = IconsDir + TEXT("viewport_sync_off16x16.png"); - FString ViewportSyncUnrealIcon = IconsDir + TEXT("viewport_sync_unreal16x16.png"); - - FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); - FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); - - StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(SessionCreateIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(SessionConnectIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(SessionStopIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(SessionRestartIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._SessionSync", new FSlateImageBrush(SessionSyncIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(SessionSyncStartIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(SessionSyncStopIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._SyncViewport", new FSlateImageBrush(ViewportSyncIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncNone", new FSlateImageBrush(ViewportSyncOffIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncBoth", new FSlateImageBrush(ViewportSyncBothIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncUnreal", new FSlateImageBrush(ViewportSyncUnrealIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncHoudini", new FSlateImageBrush(ViewportSyncHoudiniIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(OpenInHIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(SaveToHipIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(CleanTempIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(OnlineHelpIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(OnlineForumIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(ReportBugIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(CookAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(CookSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(BakeSelIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(BakeAllIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(RebuildAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(RebuildSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(RefineAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(RefineSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(PauseIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._Reset", new FSlateImageBrush(ResetIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine.DigitalAsset", new FSlateImageBrush(DigitalAssetIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine.PDGLink", new FSlateImageBrush(PDGLinkIcon, Icon16x16)); - - /* - FString StopIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Clear")->GetResourceName().ToString(); - FString RestartIcon = FEditorStyle::GetBrush("Tutorials.Browser.RestartButton")->GetResourceName().ToString(); - FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); - FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); - FString ClearIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Delete")->GetResourceName().ToString(); - FString HelpIcon = FEditorStyle::GetBrush("Icons.Help")->GetResourceName().ToString(); - FString WarningIcon = FEditorStyle::GetBrush("Icons.Warning")->GetResourceName().ToString(); - FString BPIcon = FEditorStyle::GetBrush("PropertyWindow.Button_CreateNewBlueprint")->GetResourceName().ToString(); - FString PauseIcon = FEditorStyle::GetBrush("Profiler.Pause")->GetResourceName().ToString(); - - StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(StopIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(RestartIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(StopIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); - - - StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(HelpIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(WarningIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - */ - - // We need some colors from Editor Style & this is the only way to do this at the moment - const FSlateColor DefaultForeground = FEditorStyle::GetSlateColor("DefaultForeground"); - const FSlateColor InvertedForeground = FEditorStyle::GetSlateColor("InvertedForeground"); - const FSlateColor SelectorColor = FEditorStyle::GetSlateColor("SelectorColor"); - const FSlateColor SelectionColor = FEditorStyle::GetSlateColor("SelectionColor"); - const FSlateColor SelectionColor_Inactive = FEditorStyle::GetSlateColor("SelectionColor_Inactive"); - - const FTableRowStyle &NormalTableRowStyle = FEditorStyle::Get().GetWidgetStyle("TableView.Row"); - StyleSet->Set( - "HoudiniEngine.TableRow", FTableRowStyle(NormalTableRowStyle) - .SetEvenRowBackgroundBrush(FSlateNoResource()) - .SetEvenRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) - .SetOddRowBackgroundBrush(FSlateNoResource()) - .SetOddRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) - .SetSelectorFocusedBrush(BORDER_BRUSH("Common/Selector", FMargin(4.f / 16.f), SelectorColor)) - .SetActiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) - .SetActiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) - .SetInactiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) - .SetInactiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) - .SetTextColor(DefaultForeground) - .SetSelectedTextColor(InvertedForeground) - ); - - // Normal Text - const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle("NormalText"); - StyleSet->Set( - "HoudiniEngine.ThumbnailText", FTextBlockStyle(NormalText) - .SetFont(TTF_CORE_FONT("Fonts/Roboto-Regular", 9)) - .SetColorAndOpacity(FSlateColor::UseForeground()) - .SetShadowOffset(FVector2D::ZeroVector) - .SetShadowColorAndOpacity(FLinearColor::Black) - .SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f)) - .SetHighlightShape(BOX_BRUSH("Common/TextBlockHighlightShape", FMargin(3.f / 8.f))) - ); - - StyleSet->Set("HoudiniEngine.ThumbnailShadow", new BOX_BRUSH("ContentBrowser/ThumbnailShadow", FMargin(4.0f / 64.0f))); - StyleSet->Set("HoudiniEngine.ThumbnailBackground", new IMAGE_BRUSH("Common/ClassBackground_64x", FVector2D(64.f, 64.f), FLinearColor(0.75f, 0.75f, 0.75f, 1.0f))); - - // Register Slate style. - FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); -}; - -END_SLATE_FUNCTION_BUILD_OPTIMIZATION - -#undef IMAGE_BRUSH -#undef BOX_BRUSH -#undef BORDER_BRUSH -#undef TTF_FONT -#undef TTF_CORE_FONT -#undef OTF_FONT -#undef OTF_CORE_FONT - -void -FHoudiniEngineStyle::Shutdown() -{ - if (StyleSet.IsValid()) - { - FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); - ensure(StyleSet.IsUnique()); - StyleSet.Reset(); - } -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineStyle.h" + +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineUtils.h" + +#include "EditorStyleSet.h" +#include "Styling/SlateStyleRegistry.h" +#include "Styling/SlateTypes.h" +#include "SlateOptMacros.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define TTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) +#define OTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +TSharedPtr FHoudiniEngineStyle::StyleSet = nullptr; + +TSharedPtr +FHoudiniEngineStyle::Get() +{ + return StyleSet; +} + +FName +FHoudiniEngineStyle::GetStyleSetName() +{ + static FName HoudiniStyleName(TEXT("HoudiniEngineStyle")); + return HoudiniStyleName; +} + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void +FHoudiniEngineStyle::Initialize() +{ + // Only register the StyleSet once + if (StyleSet.IsValid()) + return; + + StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName())); + StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); + StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); + + // Note, these sizes are in Slate Units. + // Slate Units do NOT have to map to pixels. + const FVector2D Icon5x16(5.0f, 16.0f); + const FVector2D Icon8x4(8.0f, 4.0f); + const FVector2D Icon8x8(8.0f, 8.0f); + const FVector2D Icon10x10(10.0f, 10.0f); + const FVector2D Icon12x12(12.0f, 12.0f); + const FVector2D Icon12x16(12.0f, 16.0f); + const FVector2D Icon14x14(14.0f, 14.0f); + const FVector2D Icon16x16(16.0f, 16.0f); + const FVector2D Icon20x20(20.0f, 20.0f); + const FVector2D Icon22x22(22.0f, 22.0f); + const FVector2D Icon24x24(24.0f, 24.0f); + const FVector2D Icon25x25(25.0f, 25.0f); + const FVector2D Icon32x32(32.0f, 32.0f); + const FVector2D Icon40x40(40.0f, 40.0f); + const FVector2D Icon64x64(64.0f, 64.0f); + const FVector2D Icon36x24(36.0f, 24.0f); + const FVector2D Icon128x128(128.0f, 128.0f); + + static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); + StyleSet->Set( + "HoudiniEngine.HoudiniEngineLogo", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( + "ClassIcon.HoudiniAssetActor", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( + "HoudiniEngine.HoudiniEngineLogo40", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); + + StyleSet->Set( + "ClassIcon.HoudiniAsset", + new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset.png"), Icon16x16)); + + StyleSet->Set( + "ClassThumbnail.HoudiniAsset", + new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset_128.png"), Icon64x64)); + + static FString ResourcesDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/"); + + FString AssetHelpIcon = IconsDir + TEXT("asset_help16x16.png"); + FString BakeAllIcon = IconsDir + TEXT("bake_all16x16.png"); + FString BakeSelIcon = IconsDir + TEXT("bake_selected16x16.png"); + FString CleanTempIcon = IconsDir + TEXT("clean_temp16x16.png"); + FString CookAllIcon = IconsDir + TEXT("cook_all16x16.png"); + FString CookLogIcon = IconsDir + TEXT("cook_log16x16.png"); + FString CookSelIcon = IconsDir + TEXT("cook_selected16x16.png"); + FString DigitalAssetIcon = IconsDir + TEXT("digital_asset16x16.png"); + FString OnlineForumIcon = IconsDir + TEXT("online_forum16x16.png"); + FString OnlineHelpIcon = IconsDir + TEXT("online_help16x16.png"); + FString OpenInHIcon = IconsDir + TEXT("open_in_houdini16x16.png"); + FString PauseIcon = IconsDir + TEXT("pause16x16.png"); + FString PDGCancelIcon = IconsDir + TEXT("pdg_cancel16x16.png"); + FString PDGDirtyAllIcon = IconsDir + TEXT("pdg_dirty_all16x16.png"); + FString PDGDirtyNodeIcon = IconsDir + TEXT("pdg_dirty_node16x16.png"); + FString PDGLinkIcon = IconsDir + TEXT("pdg_link16x16.png"); + FString PDGPauseIcon = IconsDir + TEXT("pdg_pause16x16.png"); + FString PDGRefreshIcon = IconsDir + TEXT("pdg_refresh16x16.png"); + FString PDGResetIcon = IconsDir + TEXT("pdg_reset16x16.png"); + FString RebuildAllIcon = IconsDir + TEXT("rebuild_all16x16.png"); + FString RebuildSelIcon = IconsDir + TEXT("rebuild_selected16x16.png"); + FString RefineAllIcon = IconsDir + TEXT("refine_all16x16.png"); + FString RefineSelIcon = IconsDir + TEXT("refine_selected16x16.png"); + FString ReportBugIcon = IconsDir + TEXT("report_bug16x16.png"); + FString ResetIcon = IconsDir + TEXT("reset16x16.png"); + FString ResetParamIcon = IconsDir + TEXT("reset_parameters16x16.png"); + FString SaveToHipIcon = IconsDir + TEXT("save_to_hip16x16.png"); + FString SessionConnectIcon = IconsDir + TEXT("session_connect16x16.png"); + FString SessionCreateIcon = IconsDir + TEXT("session_create16x16.png"); + FString SessionRestartIcon = IconsDir + TEXT("session_restart16x16.png"); + FString SessionStopIcon = IconsDir + TEXT("session_stop16x16.png"); + FString SessionSyncIcon = IconsDir + TEXT("session_sync16x16.png"); + FString SessionSyncStartIcon = IconsDir + TEXT("session_sync_start16x16.png"); + FString SessionSyncStopIcon = IconsDir + TEXT("session_sync_stop16x16.png"); + FString ViewportSyncIcon = IconsDir + TEXT("viewport_sync16x16.png"); + FString ViewportSyncBothIcon = IconsDir + TEXT("viewport_sync_both16x16.png"); + FString ViewportSyncHoudiniIcon = IconsDir + TEXT("viewport_sync_houdini16x16.png"); + FString ViewportSyncOffIcon = IconsDir + TEXT("viewport_sync_off16x16.png"); + FString ViewportSyncUnrealIcon = IconsDir + TEXT("viewport_sync_unreal16x16.png"); + + FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); + FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); + + StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(SessionCreateIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(SessionConnectIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(SessionStopIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(SessionRestartIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._SessionSync", new FSlateImageBrush(SessionSyncIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(SessionSyncStartIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(SessionSyncStopIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._SyncViewport", new FSlateImageBrush(ViewportSyncIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncNone", new FSlateImageBrush(ViewportSyncOffIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncBoth", new FSlateImageBrush(ViewportSyncBothIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncUnreal", new FSlateImageBrush(ViewportSyncUnrealIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncHoudini", new FSlateImageBrush(ViewportSyncHoudiniIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(OpenInHIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(SaveToHipIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(CleanTempIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(OnlineHelpIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(OnlineForumIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(ReportBugIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(CookAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(CookSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(BakeSelIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(BakeAllIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(RebuildAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(RebuildSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(RefineAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(RefineSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(PauseIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._Reset", new FSlateImageBrush(ResetIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine.DigitalAsset", new FSlateImageBrush(DigitalAssetIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine.PDGLink", new FSlateImageBrush(PDGLinkIcon, Icon16x16)); + + /* + FString StopIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Clear")->GetResourceName().ToString(); + FString RestartIcon = FEditorStyle::GetBrush("Tutorials.Browser.RestartButton")->GetResourceName().ToString(); + FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); + FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); + FString ClearIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Delete")->GetResourceName().ToString(); + FString HelpIcon = FEditorStyle::GetBrush("Icons.Help")->GetResourceName().ToString(); + FString WarningIcon = FEditorStyle::GetBrush("Icons.Warning")->GetResourceName().ToString(); + FString BPIcon = FEditorStyle::GetBrush("PropertyWindow.Button_CreateNewBlueprint")->GetResourceName().ToString(); + FString PauseIcon = FEditorStyle::GetBrush("Profiler.Pause")->GetResourceName().ToString(); + + StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(StopIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(RestartIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(StopIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); + + + StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(HelpIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(WarningIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + */ + + // We need some colors from Editor Style & this is the only way to do this at the moment + const FSlateColor DefaultForeground = FEditorStyle::GetSlateColor("DefaultForeground"); + const FSlateColor InvertedForeground = FEditorStyle::GetSlateColor("InvertedForeground"); + const FSlateColor SelectorColor = FEditorStyle::GetSlateColor("SelectorColor"); + const FSlateColor SelectionColor = FEditorStyle::GetSlateColor("SelectionColor"); + const FSlateColor SelectionColor_Inactive = FEditorStyle::GetSlateColor("SelectionColor_Inactive"); + + const FTableRowStyle &NormalTableRowStyle = FEditorStyle::Get().GetWidgetStyle("TableView.Row"); + StyleSet->Set( + "HoudiniEngine.TableRow", FTableRowStyle(NormalTableRowStyle) + .SetEvenRowBackgroundBrush(FSlateNoResource()) + .SetEvenRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) + .SetOddRowBackgroundBrush(FSlateNoResource()) + .SetOddRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) + .SetSelectorFocusedBrush(BORDER_BRUSH("Common/Selector", FMargin(4.f / 16.f), SelectorColor)) + .SetActiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) + .SetActiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) + .SetInactiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) + .SetInactiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) + .SetTextColor(DefaultForeground) + .SetSelectedTextColor(InvertedForeground) + ); + + // Normal Text + const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle("NormalText"); + StyleSet->Set( + "HoudiniEngine.ThumbnailText", FTextBlockStyle(NormalText) + .SetFont(TTF_CORE_FONT("Fonts/Roboto-Regular", 9)) + .SetColorAndOpacity(FSlateColor::UseForeground()) + .SetShadowOffset(FVector2D::ZeroVector) + .SetShadowColorAndOpacity(FLinearColor::Black) + .SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f)) + .SetHighlightShape(BOX_BRUSH("Common/TextBlockHighlightShape", FMargin(3.f / 8.f))) + ); + + StyleSet->Set("HoudiniEngine.ThumbnailShadow", new BOX_BRUSH("ContentBrowser/ThumbnailShadow", FMargin(4.0f / 64.0f))); + StyleSet->Set("HoudiniEngine.ThumbnailBackground", new IMAGE_BRUSH("Common/ClassBackground_64x", FVector2D(64.f, 64.f), FLinearColor(0.75f, 0.75f, 0.75f, 1.0f))); + + // Register Slate style. + FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); +}; + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef TTF_CORE_FONT +#undef OTF_FONT +#undef OTF_CORE_FONT + +void +FHoudiniEngineStyle::Shutdown() +{ + if (StyleSet.IsValid()) + { + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); + ensure(StyleSet.IsUnique()); + StyleSet.Reset(); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h index adc6c51a7..239935d92 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h @@ -1,42 +1,42 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Styling/SlateStyle.h" - -class FHoudiniEngineStyle -{ - public: - static void Initialize(); - static void Shutdown(); - static TSharedPtr Get(); - static FName GetStyleSetName(); - - private: - //static FString InContent(const FString &RelativePath, const ANSICHAR *Extension); - static TSharedPtr StyleSet; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Styling/SlateStyle.h" + +class FHoudiniEngineStyle +{ + public: + static void Initialize(); + static void Shutdown(); + static TSharedPtr Get(); + static FName GetStyleSetName(); + + private: + //static FString InContent(const FString &RelativePath, const ANSICHAR *Extension); + static TSharedPtr StyleSet; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h index 1bfdf8b0c..293eeb8ed 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h @@ -1,27 +1,27 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp index d2293b7fa..a8cfc925d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp @@ -1,382 +1,382 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniOutput.h" -#include "HoudiniEngine.h" - -#include "Engine/StaticMesh.h" -//#include "Engine/SkeletalMesh.h" - -#include "EditorFramework/AssetImportData.h" -#include "Misc/FileHelper.h" -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniGeoFactory::UHoudiniGeoFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - // This factory is responsible for manufacturing HoudiniEngine assets. - SupportedClass = nullptr;// UHoudiniAsset::StaticClass(); - - // This factory does not manufacture new objects from scratch. - bCreateNew = false; - - // This factory will not open the editor for each new object. - bEditAfterNew = false; - - // This factory will import objects from files. - bEditorImport = true; - - // Factory does not import objects from text. - bText = false; - - // Add supported formats. - Formats.Add(TEXT("bgeo;Houdini Geometry")); - Formats.Add(TEXT("bgeo.sc;Houdini Geometry (compressed)")); - Formats.Add(TEXT("sc;Houdini Geometry (compressed)")); -} - -bool -UHoudiniGeoFactory::FactoryCanImport(const FString& Filename) -{ - const FString Extension = FPaths::GetExtension(Filename); - if(FPaths::GetExtension(Filename) == TEXT("bgeo")) - return true; - if (FPaths::GetExtension(Filename) == TEXT("bgeo.sc")) - return true; - if (FPaths::GetExtension(Filename) == TEXT("sc")) - return true; - - return false; -} - -bool -UHoudiniGeoFactory::DoesSupportClass(UClass * Class) -{ - return Class == UStaticMesh::StaticClass(); //|| Class == USkeletalMesh::StaticClass()); -} - -UClass* -UHoudiniGeoFactory::ResolveSupportedClass() -{ - return UStaticMesh::StaticClass(); -} - -FText -UHoudiniGeoFactory::GetDisplayName() const -{ - return LOCTEXT("HoudiniGeoFactoryDescription", "Houdini Engine Geo"); -} - -UObject* -UHoudiniGeoFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) -{ - // Make sure we're loading bgeo / bgeo.sc files - FString FileExtension = FPaths::GetExtension(Filename); - if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) - return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); - - // - // TODO: - // Handle import settings here? - // - UObject* Success = Import(InClass, Cast(InParent), InName.ToString(), Filename, Flags, false); - if (!Success) - { - FString Notification = TEXT("BGEO Importer: Failed to load the BGEO file."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - return nullptr; - } - - // Notifiy we're done loading the bgeo - FString Notification = TEXT("BGEO Importer: BGEO file imported succesfully."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - return Success; -} - -bool -UHoudiniGeoFactory::CanReimport(UObject * Obj, TArray& OutFilenames) -{ - UAssetImportData* ImportData = nullptr; - if (Obj->GetClass() == UStaticMesh::StaticClass()) - { - UStaticMesh* Mesh = Cast(Obj); - ImportData = Mesh->AssetImportData; - } - /* - else if (Obj->GetClass() == USkeletalMesh::StaticClass()) - { - USkeletalMesh* Cache = Cast(Obj); - ImportData = Cache->AssetImportData; - } - */ - - if (ImportData) - { - if (FPaths::GetExtension(ImportData->GetFirstFilename()).Contains(TEXT("bgeo"))) - { - ImportData->ExtractFilenames(OutFilenames); - return true; - } - } - return false; -} - -void -UHoudiniGeoFactory::SetReimportPaths(UObject * Obj, const TArray& NewReimportPaths) -{ - UStaticMesh* Mesh = Cast(Obj); - if (Mesh && Mesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) - { - Mesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); - } - - /* - USkeletalMesh* SkeletalMesh = Cast(Obj); - if (SkeletalMesh && SkeletalMesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) - { - SkeletalMesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); - } - */ -} - - -UObject* -UHoudiniGeoFactory::Import(UClass* InClass, UPackage* InParent, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport) -{ - // Broadcast PreImport - GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, FName(FileName), TEXT("Houdini GEO")); - - // Create a new Geo importer - TArray DummyOldOutputs; - TArray NewOutputs; - UHoudiniGeoImporter* BGEOImporter = NewObject(this); - BGEOImporter->AddToRoot(); - - // Clean up lambda - auto CleanUp = [&NewOutputs, &BGEOImporter]() - { - // Remove the importer and output objects from the root set - BGEOImporter->RemoveFromRoot(); - for (auto Out : NewOutputs) - Out->RemoveFromRoot(); - }; - - // Failure lambda - auto FailImportAndReturnNull = [this, &CleanUp, &NewOutputs, &BGEOImporter]() - { - CleanUp(); - - // Failed to read the file info, fail the import - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, nullptr); - - return nullptr; - }; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!BGEOImporter->AutoStartHoudiniEngineSessionIfNeeded()) - return FailImportAndReturnNull(); - - // 2. Update the file paths - if (!BGEOImporter->SetFilePath(AbsoluteFilePath)) - return FailImportAndReturnNull(); - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - if (!BGEOImporter->LoadBGEOFileInHAPI(NodeId)) - return FailImportAndReturnNull(); - - // Prepare the package used for creating the mesh, landscape and instancer pacakges - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - PackageParams.HoudiniAssetName = FString(); - PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); - PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); - - if (bReimport) - { - PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - } - else - { - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - } - - // 4. Get the output from the file node - if (!BGEOImporter->BuildOutputsForNode(NodeId, DummyOldOutputs, NewOutputs)) - return FailImportAndReturnNull(); - - // 5. Create the static meshes in the outputs - if (!BGEOImporter->CreateStaticMeshes(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 6. Create the curves in the outputs - if (!BGEOImporter->CreateCurves(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 7. Create the landscape in the outputs - if (!BGEOImporter->CreateLandscapes(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 8. Create the instancers in the outputs - if (!BGEOImporter->CreateInstancers(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 9. Delete the created node in Houdini - if (!BGEOImporter->DeleteCreatedNode(NodeId)) - { - // Not good, but not fatal.. - //return false; - } - - // Get our result object and "finalize" them - TArray Results = BGEOImporter->GetOutputObjects(); - for (UObject* Object : Results) - { - if (!Object || Object->IsPendingKill()) - continue; - - Object->SetFlags(Flags); - - UAssetImportData * AssetImportData = nullptr; - if (Object->IsA()) - { - UStaticMesh* SM = Cast(Object); - AssetImportData = SM->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); - SM->AssetImportData = AssetImportData; - } - } - /* - else if (Object->IsA()) - { - USkeletalMesh * SkeletalMesh = Cast(Object); - AssetImportData = SkeletalMesh->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject(SkeletalMesh, USkeletalMesh::StaticClass()); - SkeletalMesh->AssetImportData = AssetImportData; - } - } - */ - - if (AssetImportData) - AssetImportData->Update(AbsoluteFilePath); - - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, Object); - Object->MarkPackageDirty(); - Object->PostEditChange(); - } - - CleanUp(); - - // Determine out parent according to the generated assets outer - UObject* OutParent = (Results.Num() > 0 && InParent != Results[0]->GetOutermost()) ? Results[0]->GetOutermost() : InParent; - return (Results.Num() > 0) ? OutParent : nullptr; -} - -EReimportResult::Type -UHoudiniGeoFactory::Reimport(UObject * Obj) -{ - auto FailReimport = []() - { - // Notifiy we failed to load the bgeo - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); - return EReimportResult::Failed; - }; - - if (!Obj || Obj->IsPendingKill()) - return FailReimport(); - - UPackage* Package = Cast(Obj->GetOuter()); - if (!Package || Package->IsPendingKill()) - return FailReimport(); - - UAssetImportData* ImportData = nullptr; - if (Obj->GetClass() == UStaticMesh::StaticClass()) - { - UStaticMesh* StaticMesh = Cast(Obj); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return FailReimport(); - - ImportData = StaticMesh->AssetImportData; - } - /* - else if(Obj->GetClass() == USkeletalMesh::StaticClass()) - { - USkeletalMesh* SkeletalMesh = Cast(Obj); - if (!SkeletalMesh || SkeletalMesh->IsPendingKill()) - return FailReimport(); - - ImportData = SkeletalMesh->AssetImportData; - } - */ - if (!ImportData || ImportData->IsPendingKill()) - return FailReimport(); - - if (ImportData->GetSourceFileCount() <= 0) - return FailReimport(); - - const FString RelativeFileName = ImportData->SourceData.SourceFiles[0].RelativeFilename; - const FString FileExtension = FPaths::GetExtension(RelativeFileName); - FString FileName = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativeFileName); - if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) - return FailReimport(); - - if (!Import(Obj->GetClass(), Package, Obj->GetName(), FileName, Obj->GetFlags(), true)) - return FailReimport(); - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has Succeed.")); - - // Notifiy we're done loading the bgeo - FString Notification = TEXT("BGEO Importer: BGEO file re-imported succesfully."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - return EReimportResult::Succeeded; -} - -int32 -UHoudiniGeoFactory::GetPriority() const -{ - return ImportPriority; -} - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniOutput.h" +#include "HoudiniEngine.h" + +#include "Engine/StaticMesh.h" +//#include "Engine/SkeletalMesh.h" + +#include "EditorFramework/AssetImportData.h" +#include "Misc/FileHelper.h" +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniGeoFactory::UHoudiniGeoFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + // This factory is responsible for manufacturing HoudiniEngine assets. + SupportedClass = nullptr;// UHoudiniAsset::StaticClass(); + + // This factory does not manufacture new objects from scratch. + bCreateNew = false; + + // This factory will not open the editor for each new object. + bEditAfterNew = false; + + // This factory will import objects from files. + bEditorImport = true; + + // Factory does not import objects from text. + bText = false; + + // Add supported formats. + Formats.Add(TEXT("bgeo;Houdini Geometry")); + Formats.Add(TEXT("bgeo.sc;Houdini Geometry (compressed)")); + Formats.Add(TEXT("sc;Houdini Geometry (compressed)")); +} + +bool +UHoudiniGeoFactory::FactoryCanImport(const FString& Filename) +{ + const FString Extension = FPaths::GetExtension(Filename); + if(FPaths::GetExtension(Filename) == TEXT("bgeo")) + return true; + if (FPaths::GetExtension(Filename) == TEXT("bgeo.sc")) + return true; + if (FPaths::GetExtension(Filename) == TEXT("sc")) + return true; + + return false; +} + +bool +UHoudiniGeoFactory::DoesSupportClass(UClass * Class) +{ + return Class == UStaticMesh::StaticClass(); //|| Class == USkeletalMesh::StaticClass()); +} + +UClass* +UHoudiniGeoFactory::ResolveSupportedClass() +{ + return UStaticMesh::StaticClass(); +} + +FText +UHoudiniGeoFactory::GetDisplayName() const +{ + return LOCTEXT("HoudiniGeoFactoryDescription", "Houdini Engine Geo"); +} + +UObject* +UHoudiniGeoFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) +{ + // Make sure we're loading bgeo / bgeo.sc files + FString FileExtension = FPaths::GetExtension(Filename); + if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) + return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); + + // + // TODO: + // Handle import settings here? + // + UObject* Success = Import(InClass, Cast(InParent), InName.ToString(), Filename, Flags, false); + if (!Success) + { + FString Notification = TEXT("BGEO Importer: Failed to load the BGEO file."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + return nullptr; + } + + // Notifiy we're done loading the bgeo + FString Notification = TEXT("BGEO Importer: BGEO file imported succesfully."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + return Success; +} + +bool +UHoudiniGeoFactory::CanReimport(UObject * Obj, TArray& OutFilenames) +{ + UAssetImportData* ImportData = nullptr; + if (Obj->GetClass() == UStaticMesh::StaticClass()) + { + UStaticMesh* Mesh = Cast(Obj); + ImportData = Mesh->AssetImportData; + } + /* + else if (Obj->GetClass() == USkeletalMesh::StaticClass()) + { + USkeletalMesh* Cache = Cast(Obj); + ImportData = Cache->AssetImportData; + } + */ + + if (ImportData) + { + if (FPaths::GetExtension(ImportData->GetFirstFilename()).Contains(TEXT("bgeo"))) + { + ImportData->ExtractFilenames(OutFilenames); + return true; + } + } + return false; +} + +void +UHoudiniGeoFactory::SetReimportPaths(UObject * Obj, const TArray& NewReimportPaths) +{ + UStaticMesh* Mesh = Cast(Obj); + if (Mesh && Mesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) + { + Mesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); + } + + /* + USkeletalMesh* SkeletalMesh = Cast(Obj); + if (SkeletalMesh && SkeletalMesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) + { + SkeletalMesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); + } + */ +} + + +UObject* +UHoudiniGeoFactory::Import(UClass* InClass, UPackage* InParent, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport) +{ + // Broadcast PreImport + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, FName(FileName), TEXT("Houdini GEO")); + + // Create a new Geo importer + TArray DummyOldOutputs; + TArray NewOutputs; + UHoudiniGeoImporter* BGEOImporter = NewObject(this); + BGEOImporter->AddToRoot(); + + // Clean up lambda + auto CleanUp = [&NewOutputs, &BGEOImporter]() + { + // Remove the importer and output objects from the root set + BGEOImporter->RemoveFromRoot(); + for (auto Out : NewOutputs) + Out->RemoveFromRoot(); + }; + + // Failure lambda + auto FailImportAndReturnNull = [this, &CleanUp, &NewOutputs, &BGEOImporter]() + { + CleanUp(); + + // Failed to read the file info, fail the import + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, nullptr); + + return nullptr; + }; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!BGEOImporter->AutoStartHoudiniEngineSessionIfNeeded()) + return FailImportAndReturnNull(); + + // 2. Update the file paths + if (!BGEOImporter->SetFilePath(AbsoluteFilePath)) + return FailImportAndReturnNull(); + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + if (!BGEOImporter->LoadBGEOFileInHAPI(NodeId)) + return FailImportAndReturnNull(); + + // Prepare the package used for creating the mesh, landscape and instancer pacakges + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + PackageParams.HoudiniAssetName = FString(); + PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); + PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); + + if (bReimport) + { + PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + } + else + { + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + } + + // 4. Get the output from the file node + if (!BGEOImporter->BuildOutputsForNode(NodeId, DummyOldOutputs, NewOutputs)) + return FailImportAndReturnNull(); + + // 5. Create the static meshes in the outputs + if (!BGEOImporter->CreateStaticMeshes(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 6. Create the curves in the outputs + if (!BGEOImporter->CreateCurves(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 7. Create the landscape in the outputs + if (!BGEOImporter->CreateLandscapes(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 8. Create the instancers in the outputs + if (!BGEOImporter->CreateInstancers(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 9. Delete the created node in Houdini + if (!BGEOImporter->DeleteCreatedNode(NodeId)) + { + // Not good, but not fatal.. + //return false; + } + + // Get our result object and "finalize" them + TArray Results = BGEOImporter->GetOutputObjects(); + for (UObject* Object : Results) + { + if (!Object || Object->IsPendingKill()) + continue; + + Object->SetFlags(Flags); + + UAssetImportData * AssetImportData = nullptr; + if (Object->IsA()) + { + UStaticMesh* SM = Cast(Object); + AssetImportData = SM->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); + SM->AssetImportData = AssetImportData; + } + } + /* + else if (Object->IsA()) + { + USkeletalMesh * SkeletalMesh = Cast(Object); + AssetImportData = SkeletalMesh->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject(SkeletalMesh, USkeletalMesh::StaticClass()); + SkeletalMesh->AssetImportData = AssetImportData; + } + } + */ + + if (AssetImportData) + AssetImportData->Update(AbsoluteFilePath); + + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, Object); + Object->MarkPackageDirty(); + Object->PostEditChange(); + } + + CleanUp(); + + // Determine out parent according to the generated assets outer + UObject* OutParent = (Results.Num() > 0 && InParent != Results[0]->GetOutermost()) ? Results[0]->GetOutermost() : InParent; + return (Results.Num() > 0) ? OutParent : nullptr; +} + +EReimportResult::Type +UHoudiniGeoFactory::Reimport(UObject * Obj) +{ + auto FailReimport = []() + { + // Notifiy we failed to load the bgeo + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); + return EReimportResult::Failed; + }; + + if (!Obj || Obj->IsPendingKill()) + return FailReimport(); + + UPackage* Package = Cast(Obj->GetOuter()); + if (!Package || Package->IsPendingKill()) + return FailReimport(); + + UAssetImportData* ImportData = nullptr; + if (Obj->GetClass() == UStaticMesh::StaticClass()) + { + UStaticMesh* StaticMesh = Cast(Obj); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return FailReimport(); + + ImportData = StaticMesh->AssetImportData; + } + /* + else if(Obj->GetClass() == USkeletalMesh::StaticClass()) + { + USkeletalMesh* SkeletalMesh = Cast(Obj); + if (!SkeletalMesh || SkeletalMesh->IsPendingKill()) + return FailReimport(); + + ImportData = SkeletalMesh->AssetImportData; + } + */ + if (!ImportData || ImportData->IsPendingKill()) + return FailReimport(); + + if (ImportData->GetSourceFileCount() <= 0) + return FailReimport(); + + const FString RelativeFileName = ImportData->SourceData.SourceFiles[0].RelativeFilename; + const FString FileExtension = FPaths::GetExtension(RelativeFileName); + FString FileName = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativeFileName); + if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) + return FailReimport(); + + if (!Import(Obj->GetClass(), Package, Obj->GetName(), FileName, Obj->GetFlags(), true)) + return FailReimport(); + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has Succeed.")); + + // Notifiy we're done loading the bgeo + FString Notification = TEXT("BGEO Importer: BGEO file re-imported succesfully."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + return EReimportResult::Succeeded; +} + +int32 +UHoudiniGeoFactory::GetPriority() const +{ + return ImportPriority; +} + + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h index 6865f2bc9..a8190ba9d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h @@ -1,83 +1,83 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "EditorReimportHandler.h" -#include "Factories/Factory.h" -#include "HoudiniGeoFactory.generated.h" - -class UClass; -class UObject; -class FFeedbackContext; - -UCLASS(config = Editor) -class UHoudiniGeoFactory : public UFactory, public FReimportHandler -{ - GENERATED_UCLASS_BODY() - - public: - - // - // UFactory Interface - // - // Returns the name of the factory for menus - virtual FText GetDisplayName() const override; - // return true if it supports this class - virtual bool DoesSupportClass(UClass * Class) override; - // - virtual UClass* ResolveSupportedClass() override; - // - //virtual UClass* ResolveSupportedClass() override; - // Return true if we can import the file - virtual bool FactoryCanImport(const FString& Filename) override; - - // Create a new object by importing it from a file name. - virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, - EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, - FFeedbackContext* Warn, bool& bOutOperationCanceled) override; - - // - // FReimportHandler Interface - // - UObject* Import(UClass* InClass, UPackage* OuterObject, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport); - - // Check to see if we have a handler to manage the reimporting of the object - virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; - - // Sets the reimport path(s) for the specified object - virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; - // Attempt to reimport the specified object from its source - virtual EReimportResult::Type Reimport(UObject * Obj) override; - - //void ShowImportOptionsWindow(TSharedPtr& Options, FString FilePath, const UHoudiniGeoImporter& Importer); - - virtual int32 GetPriority() const override; - - // - // - // -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "EditorReimportHandler.h" +#include "Factories/Factory.h" +#include "HoudiniGeoFactory.generated.h" + +class UClass; +class UObject; +class FFeedbackContext; + +UCLASS(config = Editor) +class UHoudiniGeoFactory : public UFactory, public FReimportHandler +{ + GENERATED_UCLASS_BODY() + + public: + + // + // UFactory Interface + // + // Returns the name of the factory for menus + virtual FText GetDisplayName() const override; + // return true if it supports this class + virtual bool DoesSupportClass(UClass * Class) override; + // + virtual UClass* ResolveSupportedClass() override; + // + //virtual UClass* ResolveSupportedClass() override; + // Return true if we can import the file + virtual bool FactoryCanImport(const FString& Filename) override; + + // Create a new object by importing it from a file name. + virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, + EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, + FFeedbackContext* Warn, bool& bOutOperationCanceled) override; + + // + // FReimportHandler Interface + // + UObject* Import(UClass* InClass, UPackage* OuterObject, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport); + + // Check to see if we have a handler to manage the reimporting of the object + virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; + + // Sets the reimport path(s) for the specified object + virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; + // Attempt to reimport the specified object from its source + virtual EReimportResult::Type Reimport(UObject * Obj) override; + + //void ShowImportOptionsWindow(TSharedPtr& Options, FString FilePath, const UHoudiniGeoImporter& Importer); + + virtual int32 GetPriority() const override; + + // + // + // +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp index 3b4d58dd0..f6553d476 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp @@ -1,260 +1,260 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleComponentVisualizer.h" - -#include "EditorViewportClient.h" - -#include "HoudiniHandleTranslator.h" -#include "HoudiniAssetComponent.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_HIT_PROXY(HHoudiniHandleVisProxy, HComponentVisProxy); - -HHoudiniHandleVisProxy::HHoudiniHandleVisProxy(const UActorComponent * InComponent) - : HComponentVisProxy(InComponent, HPP_Wireframe) -{} - - -FHoudiniHandleComponentVisualizerCommands::FHoudiniHandleComponentVisualizerCommands() - : TCommands< FHoudiniHandleComponentVisualizerCommands >( - "HoudiniHandleComponentVisualizer", - FText::FromString("Houdini handle Component Visualizer"), - NAME_None, - FEditorStyle::GetStyleSetName()) -{} - -void -FHoudiniHandleComponentVisualizerCommands::RegisterCommands() -{} - - -FHoudiniHandleComponentVisualizer::FHoudiniHandleComponentVisualizer() - : FComponentVisualizer() - , EditedComponent(nullptr) - , bEditing(false) -{ - FHoudiniHandleComponentVisualizerCommands::Register(); - VisualizerActions = MakeShareable(new FUICommandList); -} - -FHoudiniHandleComponentVisualizer::~FHoudiniHandleComponentVisualizer() -{ - FHoudiniHandleComponentVisualizerCommands::Unregister(); -} - -void -FHoudiniHandleComponentVisualizer::DrawVisualization(const UActorComponent * Component, - const FSceneView * View, FPrimitiveDrawInterface * PDI) -{ - const UHoudiniHandleComponent* HandleComponent = Cast(Component); - - if (!HandleComponent) - return; - - UHoudiniAssetComponent* HAC = Cast(HandleComponent->GetOuter()); - - if (!HAC) - return; - - - static TMap ColorMapActive; - static TMap ColorMapInactive; - - - int32 AssetId = HAC->GetAssetId(); - - if (!ColorMapActive.Contains(AssetId) || !ColorMapInactive.Contains(AssetId)) - { - FLinearColor NewActiveColor = FLinearColor::MakeRandomColor(); - FLinearColor NewInactiveColor = NewActiveColor.CopyWithNewOpacity(0.1)/2.5f; - - ColorMapActive.Add(AssetId, NewActiveColor); - ColorMapInactive.Add(AssetId, NewInactiveColor); - } - - - const FLinearColor& ActiveColor = ColorMapActive[AssetId]; - const FLinearColor& InactiveColor = ColorMapInactive[AssetId]; - - bool IsActive = EditedComponent != nullptr; - - if (IsActive) - { - UHoudiniAssetComponent* EditedComponentParent = Cast(EditedComponent->GetOuter()); - IsActive &= EditedComponentParent && EditedComponentParent->GetAssetId() == HAC->GetAssetId(); - } - - // Draw point and set hit box for it. - PDI->SetHitProxy(new HHoudiniHandleVisProxy(HandleComponent)); - { - static const float GrabHandleSizeActive = 24.0f; - static const float GrabHandleSizeInactive = 18.0f; - - PDI->DrawPoint(HandleComponent->GetComponentTransform().GetLocation(), IsActive ? ActiveColor : InactiveColor, IsActive ? GrabHandleSizeActive : GrabHandleSizeInactive, SDPG_Foreground); - } - - if (HandleComponent->HandleType == EHoudiniHandleType::Bounder) - { - // draw the scale box - FTransform BoxTransform = HandleComponent->GetComponentTransform(); - const float BoxRad = 50.f; - const FBox Box(FVector(-BoxRad, -BoxRad, -BoxRad), FVector(BoxRad, BoxRad, BoxRad)); - DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), Box, IsActive ? ActiveColor : InactiveColor, SDPG_Foreground); - } - - PDI->SetHitProxy(nullptr); -} - -bool -FHoudiniHandleComponentVisualizer::VisProxyHandleClick( - FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) -{ - bEditing = false; - - bAllowTranslate = false; - bAllowRotation = false; - bAllowScale = false; - - - if (VisProxy && VisProxy->Component.IsValid()) - { - const UHoudiniHandleComponent * Component = - CastChecked< const UHoudiniHandleComponent >(VisProxy->Component.Get()); - - const TArray &XformParms = Component->XformParms; - - if (!Component->CheckHandleValid()) - return bEditing; - - EditedComponent = const_cast(Component); - - if (Component) - { - if (VisProxy->IsA(HHoudiniHandleVisProxy::StaticGetType())) - bEditing = true; - - bAllowTranslate = - XformParms[int32(EXformParameter::TX)]->AssetParameter || - XformParms[int32(EXformParameter::TY)]->AssetParameter || - XformParms[int32(EXformParameter::TZ)]->AssetParameter; - - bAllowRotation = - XformParms[int32(EXformParameter::RX)]->AssetParameter || - XformParms[int32(EXformParameter::RY)]->AssetParameter || - XformParms[int32(EXformParameter::RZ)]->AssetParameter; - - bAllowScale = - XformParms[int32(EXformParameter::SX)]->AssetParameter || - XformParms[int32(EXformParameter::SY)]->AssetParameter || - XformParms[int32(EXformParameter::SZ)]->AssetParameter; - } - } - - return bEditing; -} - -void -FHoudiniHandleComponentVisualizer::EndEditing() -{ - EditedComponent = nullptr; -} - -bool -FHoudiniHandleComponentVisualizer::GetWidgetLocation( - const FEditorViewportClient * ViewportClient, - FVector & OutLocation) const -{ - if (EditedComponent) - { - OutLocation = EditedComponent->GetComponentTransform().GetLocation(); - return true; - } - - return false; -} - -bool -FHoudiniHandleComponentVisualizer::GetCustomInputCoordinateSystem( - const FEditorViewportClient * ViewportClient, - FMatrix & OutMatrix) const -{ - if (EditedComponent && ViewportClient->GetWidgetMode() == FWidget::WM_Scale) - { - OutMatrix = FRotationMatrix::Make(EditedComponent->GetComponentTransform().GetRotation()); - return true; - } - else - { - return false; - } -} - -bool -FHoudiniHandleComponentVisualizer::HandleInputDelta( - FEditorViewportClient * ViewportClient, FViewport * Viewport, - FVector& DeltaTranslate, FRotator & DeltaRotate, FVector & DeltaScale) -{ - if (!EditedComponent) - return false; - - if (!DeltaTranslate.IsZero() && bAllowTranslate) - { - EditedComponent->SetRelativeLocation(EditedComponent->GetRelativeTransform().GetLocation() + DeltaTranslate); - } - - if (!DeltaRotate.IsZero() && bAllowRotation) - { - EditedComponent->SetRelativeRotation(DeltaRotate.Quaternion() * EditedComponent->GetRelativeTransform().GetRotation()); - } - - if (!DeltaScale.IsZero() && bAllowScale) - { - EditedComponent->SetRelativeScale3D(EditedComponent->GetRelativeTransform().GetScale3D() + DeltaScale); - } - - return true; -} - -bool -FHoudiniHandleComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) -{ - if (EditedComponent) - { - if (Key == EKeys::LeftMouseButton && Event == IE_Released) - { - if (GEditor) - GEditor->RedrawLevelEditingViewports(true); - - FHoudiniHandleTranslator::UpdateTransformParameters(EditedComponent); - } - - } - return false; -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleComponentVisualizer.h" + +#include "EditorViewportClient.h" + +#include "HoudiniHandleTranslator.h" +#include "HoudiniAssetComponent.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_HIT_PROXY(HHoudiniHandleVisProxy, HComponentVisProxy); + +HHoudiniHandleVisProxy::HHoudiniHandleVisProxy(const UActorComponent * InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) +{} + + +FHoudiniHandleComponentVisualizerCommands::FHoudiniHandleComponentVisualizerCommands() + : TCommands< FHoudiniHandleComponentVisualizerCommands >( + "HoudiniHandleComponentVisualizer", + FText::FromString("Houdini handle Component Visualizer"), + NAME_None, + FEditorStyle::GetStyleSetName()) +{} + +void +FHoudiniHandleComponentVisualizerCommands::RegisterCommands() +{} + + +FHoudiniHandleComponentVisualizer::FHoudiniHandleComponentVisualizer() + : FComponentVisualizer() + , EditedComponent(nullptr) + , bEditing(false) +{ + FHoudiniHandleComponentVisualizerCommands::Register(); + VisualizerActions = MakeShareable(new FUICommandList); +} + +FHoudiniHandleComponentVisualizer::~FHoudiniHandleComponentVisualizer() +{ + FHoudiniHandleComponentVisualizerCommands::Unregister(); +} + +void +FHoudiniHandleComponentVisualizer::DrawVisualization(const UActorComponent * Component, + const FSceneView * View, FPrimitiveDrawInterface * PDI) +{ + const UHoudiniHandleComponent* HandleComponent = Cast(Component); + + if (!HandleComponent) + return; + + UHoudiniAssetComponent* HAC = Cast(HandleComponent->GetOuter()); + + if (!HAC) + return; + + + static TMap ColorMapActive; + static TMap ColorMapInactive; + + + int32 AssetId = HAC->GetAssetId(); + + if (!ColorMapActive.Contains(AssetId) || !ColorMapInactive.Contains(AssetId)) + { + FLinearColor NewActiveColor = FLinearColor::MakeRandomColor(); + FLinearColor NewInactiveColor = NewActiveColor.CopyWithNewOpacity(0.1)/2.5f; + + ColorMapActive.Add(AssetId, NewActiveColor); + ColorMapInactive.Add(AssetId, NewInactiveColor); + } + + + const FLinearColor& ActiveColor = ColorMapActive[AssetId]; + const FLinearColor& InactiveColor = ColorMapInactive[AssetId]; + + bool IsActive = EditedComponent != nullptr; + + if (IsActive) + { + UHoudiniAssetComponent* EditedComponentParent = Cast(EditedComponent->GetOuter()); + IsActive &= EditedComponentParent && EditedComponentParent->GetAssetId() == HAC->GetAssetId(); + } + + // Draw point and set hit box for it. + PDI->SetHitProxy(new HHoudiniHandleVisProxy(HandleComponent)); + { + static const float GrabHandleSizeActive = 24.0f; + static const float GrabHandleSizeInactive = 18.0f; + + PDI->DrawPoint(HandleComponent->GetComponentTransform().GetLocation(), IsActive ? ActiveColor : InactiveColor, IsActive ? GrabHandleSizeActive : GrabHandleSizeInactive, SDPG_Foreground); + } + + if (HandleComponent->HandleType == EHoudiniHandleType::Bounder) + { + // draw the scale box + FTransform BoxTransform = HandleComponent->GetComponentTransform(); + const float BoxRad = 50.f; + const FBox Box(FVector(-BoxRad, -BoxRad, -BoxRad), FVector(BoxRad, BoxRad, BoxRad)); + DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), Box, IsActive ? ActiveColor : InactiveColor, SDPG_Foreground); + } + + PDI->SetHitProxy(nullptr); +} + +bool +FHoudiniHandleComponentVisualizer::VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) +{ + bEditing = false; + + bAllowTranslate = false; + bAllowRotation = false; + bAllowScale = false; + + + if (VisProxy && VisProxy->Component.IsValid()) + { + const UHoudiniHandleComponent * Component = + CastChecked< const UHoudiniHandleComponent >(VisProxy->Component.Get()); + + const TArray &XformParms = Component->XformParms; + + if (!Component->CheckHandleValid()) + return bEditing; + + EditedComponent = const_cast(Component); + + if (Component) + { + if (VisProxy->IsA(HHoudiniHandleVisProxy::StaticGetType())) + bEditing = true; + + bAllowTranslate = + XformParms[int32(EXformParameter::TX)]->AssetParameter || + XformParms[int32(EXformParameter::TY)]->AssetParameter || + XformParms[int32(EXformParameter::TZ)]->AssetParameter; + + bAllowRotation = + XformParms[int32(EXformParameter::RX)]->AssetParameter || + XformParms[int32(EXformParameter::RY)]->AssetParameter || + XformParms[int32(EXformParameter::RZ)]->AssetParameter; + + bAllowScale = + XformParms[int32(EXformParameter::SX)]->AssetParameter || + XformParms[int32(EXformParameter::SY)]->AssetParameter || + XformParms[int32(EXformParameter::SZ)]->AssetParameter; + } + } + + return bEditing; +} + +void +FHoudiniHandleComponentVisualizer::EndEditing() +{ + EditedComponent = nullptr; +} + +bool +FHoudiniHandleComponentVisualizer::GetWidgetLocation( + const FEditorViewportClient * ViewportClient, + FVector & OutLocation) const +{ + if (EditedComponent) + { + OutLocation = EditedComponent->GetComponentTransform().GetLocation(); + return true; + } + + return false; +} + +bool +FHoudiniHandleComponentVisualizer::GetCustomInputCoordinateSystem( + const FEditorViewportClient * ViewportClient, + FMatrix & OutMatrix) const +{ + if (EditedComponent && ViewportClient->GetWidgetMode() == FWidget::WM_Scale) + { + OutMatrix = FRotationMatrix::Make(EditedComponent->GetComponentTransform().GetRotation()); + return true; + } + else + { + return false; + } +} + +bool +FHoudiniHandleComponentVisualizer::HandleInputDelta( + FEditorViewportClient * ViewportClient, FViewport * Viewport, + FVector& DeltaTranslate, FRotator & DeltaRotate, FVector & DeltaScale) +{ + if (!EditedComponent) + return false; + + if (!DeltaTranslate.IsZero() && bAllowTranslate) + { + EditedComponent->SetRelativeLocation(EditedComponent->GetRelativeTransform().GetLocation() + DeltaTranslate); + } + + if (!DeltaRotate.IsZero() && bAllowRotation) + { + EditedComponent->SetRelativeRotation(DeltaRotate.Quaternion() * EditedComponent->GetRelativeTransform().GetRotation()); + } + + if (!DeltaScale.IsZero() && bAllowScale) + { + EditedComponent->SetRelativeScale3D(EditedComponent->GetRelativeTransform().GetScale3D() + DeltaScale); + } + + return true; +} + +bool +FHoudiniHandleComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) +{ + if (EditedComponent) + { + if (Key == EKeys::LeftMouseButton && Event == IE_Released) + { + if (GEditor) + GEditor->RedrawLevelEditingViewports(true); + + FHoudiniHandleTranslator::UpdateTransformParameters(EditedComponent); + } + + } + return false; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h index eb09d1a4c..40cc299ca 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h @@ -1,114 +1,114 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ComponentVisualizer.h" -#include "Framework/Commands/Commands.h" -#include "Framework/Commands/UICommandList.h" - -#include "Components/ActorComponent.h" -#include "HoudiniHandleComponent.h" - -/** Base class for clickable editing proxies. **/ -struct HHoudiniHandleVisProxy : public HComponentVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniHandleVisProxy(const UActorComponent * InComponent); -}; - -/** Define commands for our component visualizer */ -class FHoudiniHandleComponentVisualizerCommands : public TCommands< FHoudiniHandleComponentVisualizerCommands > -{ -public: - - /** Constructor. **/ - FHoudiniHandleComponentVisualizerCommands(); - - /** Register commands. **/ - virtual void RegisterCommands() override; - -public: - - /** Command for adding a control point. **/ - TSharedPtr< FUICommandInfo > CommandAddControlPoint; - - /** Command for deleting a control point. **/ - TSharedPtr< FUICommandInfo > CommandDeleteControlPoint; -}; - - -/** Our handle visualizer. **/ -class FHoudiniHandleComponentVisualizer : public FComponentVisualizer -{ -public: - FHoudiniHandleComponentVisualizer(); - - virtual ~FHoudiniHandleComponentVisualizer(); - - /** FComponentVisualizer methods. **/ - - /** Draw visualization for the given component. **/ - virtual void DrawVisualization( - const UActorComponent * Component, const FSceneView * View, - FPrimitiveDrawInterface * PDI) override; - - /** Handle a click on a registered hit box. **/ - virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; - - virtual void EndEditing(); - - /** Returns location of a gizmo widget. **/ - virtual bool GetWidgetLocation( - const FEditorViewportClient *, FVector & OutLocation) const override; - - virtual bool GetCustomInputCoordinateSystem( - const FEditorViewportClient * ViewportClient, FMatrix & OutMatrix) const override; - - /** Handle input change. **/ - virtual bool HandleInputDelta( - FEditorViewportClient *, FViewport *, FVector & DeltaTranslate, - FRotator & DeltaRotate, FVector & DeltaScale) override; - - virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; - - void SetEditedComponent(UHoudiniHandleComponent* InComponent) { EditedComponent = InComponent; }; - void ClearEditedComponent() { EditedComponent = nullptr; }; - - -protected: - /** Visualizer actions. **/ - TSharedPtr< FUICommandList > VisualizerActions; - - /** Houdini component which is being edited. **/ - UHoudiniHandleComponent* EditedComponent; - - /** Is set to true if we are editing. **/ - uint32 bEditing : 1; - uint32 bAllowTranslate : 1; - uint32 bAllowRotation : 1; - uint32 bAllowScale : 1; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ComponentVisualizer.h" +#include "Framework/Commands/Commands.h" +#include "Framework/Commands/UICommandList.h" + +#include "Components/ActorComponent.h" +#include "HoudiniHandleComponent.h" + +/** Base class for clickable editing proxies. **/ +struct HHoudiniHandleVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniHandleVisProxy(const UActorComponent * InComponent); +}; + +/** Define commands for our component visualizer */ +class FHoudiniHandleComponentVisualizerCommands : public TCommands< FHoudiniHandleComponentVisualizerCommands > +{ +public: + + /** Constructor. **/ + FHoudiniHandleComponentVisualizerCommands(); + + /** Register commands. **/ + virtual void RegisterCommands() override; + +public: + + /** Command for adding a control point. **/ + TSharedPtr< FUICommandInfo > CommandAddControlPoint; + + /** Command for deleting a control point. **/ + TSharedPtr< FUICommandInfo > CommandDeleteControlPoint; +}; + + +/** Our handle visualizer. **/ +class FHoudiniHandleComponentVisualizer : public FComponentVisualizer +{ +public: + FHoudiniHandleComponentVisualizer(); + + virtual ~FHoudiniHandleComponentVisualizer(); + + /** FComponentVisualizer methods. **/ + + /** Draw visualization for the given component. **/ + virtual void DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI) override; + + /** Handle a click on a registered hit box. **/ + virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; + + virtual void EndEditing(); + + /** Returns location of a gizmo widget. **/ + virtual bool GetWidgetLocation( + const FEditorViewportClient *, FVector & OutLocation) const override; + + virtual bool GetCustomInputCoordinateSystem( + const FEditorViewportClient * ViewportClient, FMatrix & OutMatrix) const override; + + /** Handle input change. **/ + virtual bool HandleInputDelta( + FEditorViewportClient *, FViewport *, FVector & DeltaTranslate, + FRotator & DeltaRotate, FVector & DeltaScale) override; + + virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; + + void SetEditedComponent(UHoudiniHandleComponent* InComponent) { EditedComponent = InComponent; }; + void ClearEditedComponent() { EditedComponent = nullptr; }; + + +protected: + /** Visualizer actions. **/ + TSharedPtr< FUICommandList > VisualizerActions; + + /** Houdini component which is being edited. **/ + UHoudiniHandleComponent* EditedComponent; + + /** Is set to true if we are editing. **/ + uint32 bEditing : 1; + uint32 bAllowTranslate : 1; + uint32 bAllowRotation : 1; + uint32 bAllowScale : 1; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp index 9413728b8..1a162cfb1 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp @@ -1,400 +1,400 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniHandleDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniHandleComponent.h" -#include "HoudiniHandleTranslator.h" -#include "HoudiniHandleComponentVisualizer.h" - -#include "DetailCategoryBuilder.h" -#include "DetailWidgetRow.h" -#include "IDetailGroup.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Images/SImage.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -void -FHoudiniHandleDetails::CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles) -{ - - if (InHandles.Num() <= 0) - return; - - UHoudiniHandleComponent* MainHandle = InHandles[0]; - - if (!MainHandle || MainHandle->IsPendingKill()) - return; - - - FString HandleNameStr = MainHandle->GetHandleName() + TEXT(" (") + GetHandleTypeString(MainHandle->GetHandleType()) + TEXT(" )"); - FName HandleName = FName(*HandleNameStr); - IDetailGroup& Group = HouHandleCategory.AddGroup(HandleName, FText::FromString(HandleNameStr), false, false); - - // Create a widget row for this handle - FDetailWidgetRow& Row = Group.AddWidgetRow(); - - CreateNameWidget(Row); - - // Create value widget - - TSharedRef ValueWidgetVerticalBox = SNew(SVerticalBox); - - // Translate - auto OnLocationChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FVector Location = MainHandle->GetRelativeTransform().GetLocation(); - - if (Axis == 0) - Location.X = Val; - else if (Axis == 1) - Location.Y = Val; - else - Location.Z = Val; - - MainHandle->SetRelativeLocation(Location); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertLocationToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - FVector DefaultLocation = FVector(0.f, 0.f, 0.f); - MainHandle->SetRelativeLocation(DefaultLocation); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Z; }) - .OnXCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 0); - }) - .OnYCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 1); - }) - .OnZCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeLocation() == FVector::ZeroVector) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertLocationToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - // Rotation - auto OnRotationChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FQuat Rotation = MainHandle->GetRelativeTransform().GetRotation(); - - if (Axis == 0) - Rotation.X = Val; - else if (Axis == 1) - Rotation.Y = Val; - else - Rotation.Z = Val; - - MainHandle->SetRelativeRotation(Rotation); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertRotationToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - MainHandle->SetRelativeRotation(FQuat::Identity); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Z; }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 0); - }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 1); - }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeTransform().GetRotation() == FQuat::Identity) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertRotationToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - // Scale - auto OnScaleChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FVector Scale = MainHandle->GetRelativeTransform().GetScale3D(); - - if (Axis == 0) - Scale.X = Val; - else if (Axis == 1) - Scale.Y = Val; - else - Scale.Z = Val; - - MainHandle->SetRelativeScale3D(Scale); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertScaleToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - MainHandle->SetRelativeScale3D(FVector::OneVector); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Z; }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 0); - }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 1); - }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeTransform().GetScale3D() == FVector::OneVector) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertScaleToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - Row.ValueWidget.Widget = ValueWidgetVerticalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - - auto OnMouseEnterLambda = [MainHandle](const FGeometry& Geometry, const FPointerEvent& Event) - { - if (!MainHandle) - return; - - TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); - - if (HandleVisualizer.IsValid()) - { - HandleVisualizer->SetEditedComponent(MainHandle); - } - }; - - auto OnMouseLeaveLambda = [MainHandle](const FPointerEvent& Event) - { - if (!MainHandle) - return; - - TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); - - if (HandleVisualizer.IsValid()) - { - HandleVisualizer->SetEditedComponent(nullptr); - } - }; - - // Set on mouse leave UI widget callback functions - Row.NameWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); - Row.ValueWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); - - Row.NameWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); - Row.ValueWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); -} - -void -FHoudiniHandleDetails::CreateNameWidget(FDetailWidgetRow& Row) -{ - TSharedRef VerticalBox = SNew(SVerticalBox); - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Translate")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Rotation")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Scale")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - - Row.NameWidget.Widget = VerticalBox; -} - -FString -FHoudiniHandleDetails::GetHandleTypeString(const EHoudiniHandleType& HandleType) -{ - switch (HandleType) - { - case EHoudiniHandleType::Bounder: - return FString("Bounder"); - case EHoudiniHandleType::Xform: - return FString("Xform"); - case EHoudiniHandleType::Unsupported: - return FString("Unsupported"); - - default: - break; - } - - return FString(""); -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniHandleDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniHandleComponent.h" +#include "HoudiniHandleTranslator.h" +#include "HoudiniHandleComponentVisualizer.h" + +#include "DetailCategoryBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailGroup.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Images/SImage.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +void +FHoudiniHandleDetails::CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles) +{ + + if (InHandles.Num() <= 0) + return; + + UHoudiniHandleComponent* MainHandle = InHandles[0]; + + if (!MainHandle || MainHandle->IsPendingKill()) + return; + + + FString HandleNameStr = MainHandle->GetHandleName() + TEXT(" (") + GetHandleTypeString(MainHandle->GetHandleType()) + TEXT(" )"); + FName HandleName = FName(*HandleNameStr); + IDetailGroup& Group = HouHandleCategory.AddGroup(HandleName, FText::FromString(HandleNameStr), false, false); + + // Create a widget row for this handle + FDetailWidgetRow& Row = Group.AddWidgetRow(); + + CreateNameWidget(Row); + + // Create value widget + + TSharedRef ValueWidgetVerticalBox = SNew(SVerticalBox); + + // Translate + auto OnLocationChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FVector Location = MainHandle->GetRelativeTransform().GetLocation(); + + if (Axis == 0) + Location.X = Val; + else if (Axis == 1) + Location.Y = Val; + else + Location.Z = Val; + + MainHandle->SetRelativeLocation(Location); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertLocationToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + FVector DefaultLocation = FVector(0.f, 0.f, 0.f); + MainHandle->SetRelativeLocation(DefaultLocation); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Z; }) + .OnXCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 0); + }) + .OnYCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 1); + }) + .OnZCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeLocation() == FVector::ZeroVector) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertLocationToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + // Rotation + auto OnRotationChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FQuat Rotation = MainHandle->GetRelativeTransform().GetRotation(); + + if (Axis == 0) + Rotation.X = Val; + else if (Axis == 1) + Rotation.Y = Val; + else + Rotation.Z = Val; + + MainHandle->SetRelativeRotation(Rotation); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertRotationToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + MainHandle->SetRelativeRotation(FQuat::Identity); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Z; }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 0); + }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 1); + }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeTransform().GetRotation() == FQuat::Identity) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertRotationToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + // Scale + auto OnScaleChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FVector Scale = MainHandle->GetRelativeTransform().GetScale3D(); + + if (Axis == 0) + Scale.X = Val; + else if (Axis == 1) + Scale.Y = Val; + else + Scale.Z = Val; + + MainHandle->SetRelativeScale3D(Scale); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertScaleToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + MainHandle->SetRelativeScale3D(FVector::OneVector); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Z; }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 0); + }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 1); + }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeTransform().GetScale3D() == FVector::OneVector) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertScaleToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + Row.ValueWidget.Widget = ValueWidgetVerticalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + + auto OnMouseEnterLambda = [MainHandle](const FGeometry& Geometry, const FPointerEvent& Event) + { + if (!MainHandle) + return; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); + + if (HandleVisualizer.IsValid()) + { + HandleVisualizer->SetEditedComponent(MainHandle); + } + }; + + auto OnMouseLeaveLambda = [MainHandle](const FPointerEvent& Event) + { + if (!MainHandle) + return; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); + + if (HandleVisualizer.IsValid()) + { + HandleVisualizer->SetEditedComponent(nullptr); + } + }; + + // Set on mouse leave UI widget callback functions + Row.NameWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); + Row.ValueWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); + + Row.NameWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); + Row.ValueWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); +} + +void +FHoudiniHandleDetails::CreateNameWidget(FDetailWidgetRow& Row) +{ + TSharedRef VerticalBox = SNew(SVerticalBox); + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Translate")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Rotation")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Scale")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + + Row.NameWidget.Widget = VerticalBox; +} + +FString +FHoudiniHandleDetails::GetHandleTypeString(const EHoudiniHandleType& HandleType) +{ + switch (HandleType) + { + case EHoudiniHandleType::Bounder: + return FString("Bounder"); + case EHoudiniHandleType::Xform: + return FString("Xform"); + case EHoudiniHandleType::Unsupported: + return FString("Unsupported"); + + default: + break; + } + + return FString(""); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h index 3a9418326..e82898b34 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "DetailWidgetRow.h" - -class UHoudiniHandleComponent; -class IDetailCategoryBuilder; -enum class EHoudiniHandleType : uint8; - -class FHoudiniHandleDetails : public TSharedFromThis -{ -public: - static void CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles); - - static void CreateNameWidget(FDetailWidgetRow& Row); - - static FString GetHandleTypeString(const EHoudiniHandleType& HandleType); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "DetailWidgetRow.h" + +class UHoudiniHandleComponent; +class IDetailCategoryBuilder; +enum class EHoudiniHandleType : uint8; + +class FHoudiniHandleDetails : public TSharedFromThis +{ +public: + static void CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles); + + static void CreateNameWidget(FDetailWidgetRow& Row); + + static FString GetHandleTypeString(const EHoudiniHandleType& HandleType); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp index c3b1feb1d..6cd3311b2 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp @@ -1,4974 +1,4974 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniInputDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniInput.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetBlueprintComponent.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniPackageParams.h" - -#include "Editor.h" -#include "DetailLayoutBuilder.h" -#include "DetailCategoryBuilder.h" -#include "IDetailCustomization.h" -#include "DetailWidgetRow.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SRotatorInputBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SNumericEntryBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SEditableText.h" -#include "Widgets/Layout/SUniformGridPanel.h" -#include "Editor/UnrealEd/Public/AssetThumbnail.h" -#include "Editor/PropertyEditor/Private/SDetailsViewBase.h" -#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" -#include "SAssetDropTarget.h" -#include "ScopedTransaction.h" -#include "Engine/SkeletalMesh.h" -#include "Engine/Selection.h" -#include "EngineUtils.h" -#include "AssetData.h" -#include "Framework/SlateDelegates.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "Modules/ModuleManager.h" -#include "SceneOutlinerModule.h" - -#include "Editor/UnrealEdEngine.h" -#include "HoudiniSplineComponentVisualizer.h" -#include "UnrealEdGlobals.h" -#include "Widgets/SWidget.h" - -#include "HoudiniEngineRuntimeUtils.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// Customized TextBlock to show 'editing...' text if this Houdini Spline Component is being edited -class SCurveEditingTextBlock : public STextBlock -{ -public: - UHoudiniSplineComponent* HoudiniSplineComponent; - TSharedPtr HoudiniSplineComponentVisualizer; -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override - { - if (!HoudiniSplineComponentVisualizer.IsValid() || !HoudiniSplineComponent) - return LayerId; - - if (HoudiniSplineComponentVisualizer->GetEditedHoudiniSplineComponent() != HoudiniSplineComponent) - return LayerId; - - return STextBlock::OnPaint(Args, AllottedGeometry, MyClippingRect, - OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - } -}; - -void -FHoudiniInputDetails::CreateWidget( - IDetailCategoryBuilder& HouInputCategory, - TArray InInputs, - FDetailWidgetRow* InputRow) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouInputCategory.GetParentLayout().GetThumbnailPool(); - - EHoudiniInputType MainInputType = MainInput->GetInputType(); - UHoudiniAssetComponent* HAC = MainInput->GetTypedOuter(); - - // Create a widget row, or get the given row. - FDetailWidgetRow* Row = InputRow; - Row = InputRow == nullptr ? &(HouInputCategory.AddCustomRow(FText::GetEmpty())) : InputRow; - if (!Row) - return; - - // Create the standard input name widget if this is not a operator path parameter. - // Operator path parameter's name widget is handled by HoudiniParameterDetails. - if (!InputRow) - CreateNameWidget(MainInput, *Row, true, InInputs.Num()); - - // Create a vertical Box for storing the UI - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - // ComboBox : Input Type - const IDetailsView* DetailsView = HouInputCategory.GetParentLayout().GetDetailsView(); - AddInputTypeComboBox(HouInputCategory, VerticalBox, InInputs, DetailsView); - - // Checkbox : Keep World Transform - AddKeepWorldTransformCheckBox(VerticalBox, InInputs); - - - // Checkbox : CurveInput trigger cook on curve changed - AddCurveInputCookOnChangeCheckBox(VerticalBox, InInputs); - - - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) - { - // Checkbox : Pack before merging - AddPackBeforeMergeCheckbox(VerticalBox, InInputs); - } - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World || MainInputType == EHoudiniInputType::Asset) - { - AddImportAsReferenceCheckbox(VerticalBox, InInputs); - } - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) - { - // Checkboxes : Export LODs / Sockets / Collisions - AddExportCheckboxes(VerticalBox, InInputs); - } - - switch (MainInput->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - AddGeometryInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); - } - break; - - case EHoudiniInputType::Asset: - { - AddAssetInputUI(VerticalBox, InInputs); - } - break; - - case EHoudiniInputType::Curve: - { - AddCurveInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); - } - break; - - case EHoudiniInputType::Landscape: - { - AddLandscapeInputUI(VerticalBox, InInputs); - } - break; - - case EHoudiniInputType::World: - { - AddWorldInputUI(HouInputCategory, VerticalBox, InInputs, DetailsView); - } - break; - - case EHoudiniInputType::Skeletal: - { - AddSkeletalInputUI(VerticalBox, InInputs, AssetThumbnailPool); - } - break; - } - - - Row->ValueWidget.Widget = VerticalBox; - - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - //Row.ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniInputDetails::CreateNameWidget( - UHoudiniInput* InInput, FDetailWidgetRow & Row, bool bLabel, int32 InInputCount) -{ - if (!InInput || InInput->IsPendingKill()) - return; - - FString InputLabelStr = InInput->GetLabel(); - if (InInputCount > 1) - { - InputLabelStr += TEXT(" (") + FString::FromInt(InInputCount) + TEXT(")"); - } - - const FText & FinalInputLabelText = bLabel ? FText::FromString(InputLabelStr) : FText::GetEmpty(); - FText InputTooltip = GetInputTooltip(InInput); - { - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(FinalInputLabelText) - .ToolTipText(InputTooltip) - .Font(FEditorStyle::GetFontStyle(!InInput->HasChanged() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))); - } -} - -FText -FHoudiniInputDetails::GetInputTooltip(UHoudiniInput* InParam) -{ - // TODO - return FText(); -} - -void -FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuilder, TSharedRef VerticalBox, TArray& InInputs, const IDetailsView* DetailsView) -{ - // Get the details view name and locked status - bool bDetailsLocked = false; - FName DetailsPanelName = "LevelEditorSelectionDetails"; - if (DetailsView) - { - DetailsPanelName = DetailsView->GetIdentifier(); - if (DetailsView->IsLocked()) - bDetailsLocked = true; - } - - // Lambda return a FText correpsonding to an input's current type - auto GetInputText = [](UHoudiniInput* InInput) - { - return FText::FromString(InInput->GetInputTypeAsString()); - }; - - // Lambda for changing inputs type - auto OnSelChanged = [DetailsPanelName, &CategoryBuilder](TArray InInputsToUpdate, TSharedPtr InNewChoice) - { - if (!InNewChoice.IsValid()) - return; - - EHoudiniInputType NewInputType = UHoudiniInput::StringToInputType(*InNewChoice.Get()); - if (NewInputType != EHoudiniInputType::World) - { - Helper_CancelWorldSelection(InInputsToUpdate, DetailsPanelName); - } - - if (InInputsToUpdate.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputsToUpdate[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetBlueprintComponent* HAB = MainInput->GetTypedOuter(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Input Type"), - MainInput->GetOuter()); - - bool bBlueprintStructureModified = false; - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetInputType() == NewInputType) - continue; - - /* This causes multiple issues. It does not set reset the previous type variable to Invalid sometimes - and it causes re-cook infinitely after few undo changing type. - { - CurInput->SetInputType(NewInputType); - CurInput->Modify(); - } - */ - - { - // Cache the current input type for undo type changing (since new type becomes previous type after undo) - EHoudiniInputType PrevType = CurInput->GetPreviousInputType(); - CurInput->SetPreviousInputType(NewInputType); - - CurInput->Modify(); - CurInput->SetPreviousInputType(PrevType); - CurInput->SetInputType(NewInputType, bBlueprintStructureModified); // pass in false for 2nd parameter in order to avoid creating default curve if empty - } - CurInput->MarkChanged(true); - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - - } - - if (HAB) - { - if (bBlueprintStructureModified) - HAB->MarkAsBlueprintStructureModified(); - } - - }; - - UHoudiniInput* MainInput = InInputs[0]; - TArray>* SupportedChoices = nullptr; - UHoudiniAssetBlueprintComponent* HAC = MainInput->GetTypedOuter(); - if (HAC) - { - SupportedChoices = FHoudiniEngineEditor::Get().GetBlueprintInputTypeChoiceLabels(); - } - else - { - SupportedChoices = FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels(); - } - - // ComboBox : Input Type - TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBoxInputType; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(ComboBoxInputType, SComboBox>) - .OptionsSource(SupportedChoices) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels())[((int32)MainInput->GetInputType() - 1)]) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([=](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnSelChanged(InInputs, NewChoice); - }) - [ - SNew( STextBlock ) - .Text_Lambda([=]() - { - return GetInputText(MainInput); - }) - .Font( FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; -} - -void -FHoudiniInputDetails:: AddCurveInputCookOnChangeCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - - if (!MainInput || MainInput->GetInputType() != EHoudiniInputType::Curve) - return; - - auto IsCheckedCookOnChange = [MainInput]() - { - if (!MainInput) - return ECheckBoxState::Checked; - - return MainInput->GetCookOnCurveChange() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto CheckStateChangedCookOnChange = [InInputs](ECheckBoxState NewState) - { - bool bChecked = NewState == ECheckBoxState::Checked; - for (auto & NextInput : InInputs) - { - if (!NextInput) - continue; - - NextInput->SetCookOnCurveChange(bChecked); - } - }; - - // Checkbox : Trigger cook on input curve changed - TSharedPtr< SCheckBox > CheckBoxCookOnCurveChanged; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxCookOnCurveChanged, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookOnCurveChangedCheckbox", "Auto-update")) - .ToolTipText(LOCTEXT("CookOnCurveChangeCheckboxTip", "When checked, cook is triggered automatically when the curve is modified.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedCookOnChange, MainInput]() - { - return IsCheckedCookOnChange(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedCookOnChange](ECheckBoxState NewState) - { - return CheckStateChangedCookOnChange( NewState); - }) - ]; - -} - -void -FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current KeepWorldTransform state - auto IsCheckedKeepWorldTransform = [&](UHoudiniInput* InInput) - { - return InInput->GetKeepWorldTransform() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing KeepWorldTransform state - auto CheckStateChangedKeepWorldTransform = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (MainInput->GetKeepWorldTransform() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Keep World Transform"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetKeepWorldTransform() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetKeepWorldTransform(bNewState); - CurInput->MarkChanged(true); - } - }; - - - // Checkbox : Keep World Transform - TSharedPtr< SCheckBox > CheckBoxTranformType; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxTranformType, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("KeepWorldTransformCheckbox", "Keep World Transform")) - .ToolTipText(LOCTEXT("KeepWorldTransformCheckboxTip", "Set this Input's object_merge Transform Type to INTO_THIS_OBJECT. If unchecked, it will be set to NONE.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedKeepWorldTransform(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedKeepWorldTransform(InInputs, NewState); - }) - ]; - - // the checkbox is read only for geo inputs - if (MainInput->GetInputType() == EHoudiniInputType::Geometry) - CheckBoxTranformType->SetEnabled(false); - - // Checkbox is read only if the input is an object-path parameter - //if (MainInput->IsObjectPathParameter() ) - // CheckBoxTranformType->SetEnabled(false); -} - -void -FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current PackBeforeMerge state - auto IsCheckedPackBeforeMerge = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetPackBeforeMerge() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing PackBeforeMerge state - auto CheckStateChangedPackBeforeMerge = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetPackBeforeMerge() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetPackBeforeMerge() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetPackBeforeMerge(bNewState); - CurInput->MarkChanged(true); - } - }; - - TSharedPtr< SCheckBox > CheckBoxPackBeforeMerge; - VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() - [ - SAssignNew( CheckBoxPackBeforeMerge, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "PackBeforeMergeCheckbox", "Pack Geometry before merging" ) ) - .ToolTipText( LOCTEXT( "PackBeforeMergeCheckboxTip", "Pack each separate piece of geometry before merging them into the input." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedPackBeforeMerge(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedPackBeforeMerge(InInputs, NewState); - }) - ]; -} - -void -FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current PackBeforeMerge state - auto IsCheckedImportAsReference= [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetImportAsReference() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing PackBeforeMerge state - auto CheckStateChangedImportAsReference= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetImportAsReference() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetImportAsReference() == bNewState) - continue; - - TArray * InputObjs = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (InputObjs) - { - // Mark all its input objects as changed to trigger recook. - for (auto CurInputObj : *InputObjs) - { - if (!CurInputObj || CurInputObj->IsPendingKill()) - continue; - - if (CurInputObj->GetImportAsReference() != bNewState) - CurInputObj->MarkChanged(true); - } - } - - CurInput->Modify(); - CurInput->SetImportAsReference(bNewState); - } - }; - - TSharedPtr< SCheckBox > CheckBoxImportAsReference; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxImportAsReference, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("ImportInputAsRefCheckbox", "Import input as references")) - .ToolTipText(LOCTEXT("ImportInputAsRefCheckboxTip", "Import input objects as references. (Geometry, World and Asset input types only)")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedImportAsReference(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedImportAsReference(InInputs, NewState); - }) - ]; -} -void -FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current ExportLODs state - auto IsCheckedExportLODs = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportLODs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda returning a CheckState from the input's current ExportSockets state - auto IsCheckedExportSockets = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportSockets() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda returning a CheckState from the input's current ExportColliders state - auto IsCheckedExportColliders = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportColliders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing ExportLODs state - auto CheckStateChangedExportLODs = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportLODs() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export LODs"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportLODs() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportLODs(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - // Lambda for changing ExportSockets state - auto CheckStateChangedExportSockets = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportSockets() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Sockets"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportSockets() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportSockets(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - // Lambda for changing ExportColliders state - auto CheckStateChangedExportColliders = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportColliders() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Colliders"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportColliders() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportColliders(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - TSharedPtr< SCheckBox > CheckBoxExportLODs; - TSharedPtr< SCheckBox > CheckBoxExportSockets; - TSharedPtr< SCheckBox > CheckBoxExportColliders; - VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew(CheckBoxExportLODs, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportAllLOD", "Export LODs" ) ) - .ToolTipText( LOCTEXT( "ExportAllLODCheckboxTip", "If enabled, all LOD Meshes in this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportLODs(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportLODs(InInputs, NewState); - }) - ] - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( CheckBoxExportSockets, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportSockets", "Export Sockets" ) ) - .ToolTipText( LOCTEXT( "ExportSocketsTip", "If enabled, all Mesh Sockets in this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportSockets(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportSockets(InInputs, NewState); - }) - ] - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( CheckBoxExportColliders, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportColliders", "Export Colliders" ) ) - .ToolTipText( LOCTEXT( "ExportCollidersTip", "If enabled, collision geometry for this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportColliders(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportColliders(InInputs, NewState); - }) - ] - ]; -} - -void -FHoudiniInputDetails::AddGeometryInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool ) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - - // Lambda for changing ExportColliders state - auto SetGeometryInputObjectsCount = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& NewInputCount) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing the number of Geometry Input Objects"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry) == NewInputCount) - continue; - - CurInput->Modify(); - - CurInput->SetInputObjectsNumber(EHoudiniInputType::Geometry, NewInputCount); - CurInput->MarkChanged(true); - - // - if (GEditor) - GEditor->RedrawAllViewports(); - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - }; - - InVerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs, NumInputObjects]() - { - return SetGeometryInputObjectsCount(InInputs, NumInputObjects + 1); - }), - LOCTEXT("AddInput", "Adds a Geometry Input"), true) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs]() - { - return SetGeometryInputObjectsCount(InInputs, 0); - }), - LOCTEXT("EmptyInputs", "Removes All Inputs"), true) - ] - ]; - - for (int32 GeometryObjectIdx = 0; GeometryObjectIdx < NumInputObjects; GeometryObjectIdx++) - { - //UObject* InputObject = InParam.GetInputObject(Idx); - Helper_CreateGeometryWidget(CategoryBuilder, InInputs, GeometryObjectIdx, AssetThumbnailPool, InVerticalBox); - } -} - - - -// Create a single geometry widget for the given input object -void -FHoudiniInputDetails::Helper_CreateGeometryWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InGeometryObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox ) -{ - UHoudiniInput* MainInput = InInputs[0]; - - // Access the object used in the corresponding geometry input - UHoudiniInputObject* HoudiniInputObject = MainInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - UObject* InputObject = HoudiniInputObject ? HoudiniInputObject->GetObject() : nullptr; - - // Create thumbnail for this static mesh. - TSharedPtr StaticMeshThumbnail = MakeShareable( - new FAssetThumbnail(InputObject, 64, 64, AssetThumbnailPool)); - - // Lambda for adding new geometry input objects - auto UpdateGeometryObjectAt = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& AtIndex, UObject* InObject) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - if (!InObject || InObject->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing a Geometry Input Object"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - UObject* InputObject = CurInput->GetInputObjectAt(EHoudiniInputType::Geometry, AtIndex); - if (InObject == InputObject) - continue; - - UHoudiniInputObject* CurrentInputObjectWrapper = CurInput->GetHoudiniInputObjectAt(AtIndex); - if (CurrentInputObjectWrapper) - CurrentInputObjectWrapper->Modify(); - - CurInput->Modify(); - - CurInput->SetInputObjectAt(EHoudiniInputType::Geometry, AtIndex, InObject); - CurInput->MarkChanged(true); - - // TODO: Not needed? - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - }; - - // Drop Target: Static/Skeletal Mesh - TSharedPtr HorizontalBox = NULL; - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop_Lambda([]( const UObject* InObject) - { - return UHoudiniInput::IsObjectAcceptable(EHoudiniInputType::Geometry, InObject); - }) - .OnAssetDropped_Lambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt](UObject* InObject) - { - return UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, InObject); - }) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - // Thumbnail : Static Mesh - FText ParameterLabelText = FText::FromString(MainInput->GetLabel()); - - TSharedPtr< SBorder > StaticMeshThumbnailBorder; - HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(StaticMeshThumbnailBorder, SBorder) - .Padding(5.0f) - .OnMouseDoubleClick_Lambda([MainInput, InGeometryObjectIdx](const FGeometry&, const FPointerEvent&) - { - UObject* InputObject = MainInput->GetInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - if (GEditor && InputObject) - GEditor->EditObject(InputObject); - - return FReply::Handled(); - }) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(ParameterLabelText) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - StaticMeshThumbnailBorder->SetBorderImage(TAttribute::Create( - TAttribute::FGetter::CreateLambda([StaticMeshThumbnailBorder]() - { - if (StaticMeshThumbnailBorder.IsValid() && StaticMeshThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ) ) ); - - FText MeshNameText = FText::GetEmpty(); - if (InputObject) - MeshNameText = FText::FromString(InputObject->GetName()); - - - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add Combo box : Static Mesh - TSharedPtr StaticMeshComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SAssignNew(StaticMeshComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(MeshNameText) - ] - ] - ]; - - - StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( - [MainInput, InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt]() - { - TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); - UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); - - TArray< UFactory * > NewAssetFactories; - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(DefaultObj), - true, - AllowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) - { - if (StaticMeshComboButton.IsValid()) - { - StaticMeshComboButton->SetIsOpen(false); - UObject * Object = AssetData.GetAsset(); - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); - } - }), - FSimpleDelegate::CreateLambda([]() {})); - })); - - - // Add buttons - TSharedPtr ButtonHorizontalBox; - ComboAndButtonBox->AddSlot() - .FillHeight(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ButtonHorizontalBox, SHorizontalBox) - ]; - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add( TEXT( "Asset" ), MeshNameText ); - FText StaticMeshTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", - "Browse to '{Asset}' in Content Browser" ), Args ); - - // Button : Use selected in content browser - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton(FSimpleDelegate::CreateLambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt]() - { - if (GEditor) - { - TArray CBSelections; - GEditor->GetContentBrowserSelections(CBSelections); - - // Get the first selected static mesh object - UObject* Object = nullptr; - for (auto & CurAssetData : CBSelections) - { - if (CurAssetData.AssetClass != UStaticMesh::StaticClass()->GetFName()) - continue; - - Object = CurAssetData.GetAsset(); - break; - } - - if (Object && !Object->IsPendingKill()) - { - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); - } - } - }), TAttribute< FText >(LOCTEXT("GeometryInputUseSelectedAssetFromCB", "Use Selected Asset from Content Browser"))) - ]; - - // Button : Browse Static Mesh - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateLambda([MainInput, InGeometryObjectIdx]() - { - UObject* InputObject = MainInput->GetInputObjectAt(InGeometryObjectIdx); - if (GEditor && InputObject) - { - TArray Objects; - Objects.Add(InputObject); - GEditor->SyncBrowserToObjects(Objects); - } - }), - TAttribute< FText >( StaticMeshTooltip ) - ) - ]; - - // ButtonBox : Reset - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default static mesh" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked_Lambda( [UpdateGeometryObjectAt, InInputs, InGeometryObjectIdx]() - { - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, nullptr); - return FReply::Handled(); - }) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ]; - - // Insert/Delete/Duplicate - ButtonHorizontalBox->AddSlot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( - FExecuteAction::CreateLambda( [ InInputs, InGeometryObjectIdx, MainInput ]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: insert a Geometry Input Object"), - MainInput->GetOuter()); - // Insert - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->InsertInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - } - } ), - FExecuteAction::CreateLambda([MainInput, InInputs, InGeometryObjectIdx]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: delete a Geometry Input Object"), - MainInput->GetOuter()); - - // Delete - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->DeleteInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - - if (GEditor) - GEditor->RedrawAllViewports(); - } - } ), - FExecuteAction::CreateLambda([InInputs, InGeometryObjectIdx, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), - MainInput->GetOuter()); - - // Duplicate - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->DuplicateInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - } - } ) ) - ]; - - // TRANSFORM OFFSET EXPANDER - { - TSharedPtr ExpanderArrow; - TSharedPtr ExpanderImage; - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( ExpanderArrow, SButton ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ClickMethod( EButtonClickMethod::MouseDown ) - .Visibility( EVisibility::Visible ) - .OnClicked(FOnClicked::CreateLambda([InInputs, InGeometryObjectIdx, MainInput, &CategoryBuilder]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled();; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), - MainInput->GetOuter()); - - // Expand transform - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->OnTransformUIExpand(InGeometryObjectIdx); - } - - // TODO: Not needed? - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - })) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ] - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( STextBlock ) - .Text( LOCTEXT("GeoInputTransform", "Transform Offset") ) - .ToolTipText( LOCTEXT( "GeoInputTransformTooltip", "Transform offset used for correction before sending the asset to Houdini" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - ]; - - // Set delegate for image - ExpanderImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda( [=]() { - FName ResourceName; - if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx) ) - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - return FEditorStyle::GetBrush( ResourceName ); - } ) ) ); - } - - // Lambda for changing the transform values - auto ChangeTransformOffsetAt = [&](const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex, const bool& DoChange, TArray InInputs) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputTransformChange", "Houdini Input: Changing Transform offset"), - InInputs[0]->GetOuter()); - - bool bChanged = true; - for (int Idx = 0; Idx < InInputs.Num(); Idx++) - { - if (!InInputs[Idx] || InInputs[Idx]->IsPendingKill()) - continue; - - UHoudiniInputObject* InputObject = InInputs[Idx]->GetHoudiniInputObjectAt(AtIndex); - if (InputObject) - InputObject->Modify(); - - bChanged &= InInputs[Idx]->SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); - } - - if (bChanged && DoChange) - { - // Mark the values as changed to trigger an update - for (int Idx = 0; Idx < InInputs.Num(); Idx++) - { - InInputs[Idx]->MarkChanged(true); - } - } - else - { - // Cancel the transaction - Transaction.Cancel(); - } - }; - - // Get Visibility of reset buttons - bool bResetButtonVisiblePosition = false; - bool bResetButtonVisibleRotation = false; - bool bResetButtonVisibleScale = false; - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - FTransform* CurTransform = CurInput->GetTransformOffset(InGeometryObjectIdx); - if (!CurTransform) - continue; - - if (CurTransform->GetLocation() != FVector::ZeroVector) - bResetButtonVisiblePosition = true; - - FRotator Rotator = CurTransform->Rotator(); - if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) - bResetButtonVisibleRotation = true; - - if (CurTransform->GetScale3D() != FVector::OneVector) - bResetButtonVisibleScale = true; - } - - auto ChangeTransformOffsetUniformlyAt = [InGeometryObjectIdx, InInputs, ChangeTransformOffsetAt](const float & Val, const int32& PosRotScaleIndex) - { - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 0, true, InInputs); - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 1, true, InInputs); - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 2, true, InInputs); - }; - - // TRANSFORM OFFSET - if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) - { - // Position - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew(STextBlock) - .Text( LOCTEXT("GeoInputTranslate", "T") ) - .ToolTipText( LOCTEXT( "GeoInputTranslateTooltip", "Translate" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SVectorInputBox ) - .bColorAxisLabels( true ) - .AllowSpin(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetX, InGeometryObjectIdx))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetY, InGeometryObjectIdx))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetZ, InGeometryObjectIdx))) - .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 0, true, InInputs); }) - .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 1, true, InInputs); }) - .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 2, true, InInputs); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([MainInput, ChangeTransformOffsetUniformlyAt, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 0); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - - // Rotation - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text( LOCTEXT("GeoInputRotate", "R") ) - .ToolTipText( LOCTEXT( "GeoInputRotateTooltip", "Rotate" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SRotatorInputBox ) - .AllowSpin( true ) - .bColorAxisLabels( true ) - .Roll(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetRoll, InGeometryObjectIdx))) - .Pitch(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetPitch, InGeometryObjectIdx))) - .Yaw(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetYaw, InGeometryObjectIdx))) - .OnRollCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 0, true, InInputs); }) - .OnPitchCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 1, true, InInputs); }) - .OnYawCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 2, true, InInputs); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (Not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 1); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - - // Scale - bool bLocked = false; - if (HoudiniInputObject) - bLocked = HoudiniInputObject->IsUniformScaleLocked(); - - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "GeoInputScale", "S" ) ) - .ToolTipText( LOCTEXT( "GeoInputScaleTooltip", "Scale" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SVectorInputBox ) - .bColorAxisLabels( true ) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetX, InGeometryObjectIdx))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetY, InGeometryObjectIdx))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetZ, InGeometryObjectIdx))) - .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 0, true, InInputs); - }) - .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 1, true, InInputs); - }) - .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 2, true, InInputs); - }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - SNew(SHorizontalBox) - // Lock Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ToolTipText(HoudiniInputObject ? - LOCTEXT("GeoInputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the input object maintains its shape in each direction when scaled") : - LOCTEXT("GeoInputLockButtonToolTipNoObject", "No input object selected")) - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(bLocked ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([InInputs, MainInput, InGeometryObjectIdx, HoudiniInputObject, &CategoryBuilder]() - { - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - UHoudiniInputObject*CurInputObject = CurInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - CurInputObject->SwitchUniformScaleLock(); - } - - if (HoudiniInputObject) - { - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); - }) - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(1.0f, 2); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - } -} - -void -FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Houdini Asset Picker Widget - { - FMenuBuilder MenuBuilder = Helper_CreateHoudiniAssetPickerWidget(InInputs); - - VerticalBox->AddSlot() - .Padding(2.0f, 2.0f, 5.0f, 2.0f) - .AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - // Button : Clear Selection - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - auto IsClearButtonEnabled = [MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return false; - - TArray* AssetInputObjectsArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - - if (!AssetInputObjectsArray) - return false; - - if (AssetInputObjectsArray->Num() <= 0) - return false; - - return true; - }; - - FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChangeClear", "Houdini Input: Clearing asset input selection"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - TArray* AssetInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - if (!AssetInputObjectsArray) - continue; - - CurrentInput->Modify(); - - AssetInputObjectsArray->Empty(); - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }); - - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - // Button : Clear Selection - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("ClearSelection", "Clear Selection")) - .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) - .IsEnabled_Lambda(IsClearButtonEnabled) - .OnClicked(OnClearSelect) - ] - ]; - - // Do not enable select all/clear select when selection has been started and details are locked - //HorizontalBox->SetEnabled(!bDetailsLocked); - } - - -} - -void -FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs, TSharedPtr AssetThumbnailPool) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Curve); - - // lambda for inserting an input Houdini curve. - auto InsertAnInputCurve = [MainInput, &CategoryBuilder](const int32& NewInputCount) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetComponent* OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - // Do not insert input object when the HAC does not finish cooking - EHoudiniAssetState CurrentHACState = OuterHAC->GetAssetState(); - if (CurrentHACState >= EHoudiniAssetState::PreCook && CurrentHACState<= EHoudiniAssetState::Processing) - return; - - // Clear the to be inserted object array, which records the pointers of the input objects to be inserted. - MainInput->LastInsertedInputs.Empty(); - // Record the pointer of the object to be inserted before transaction for undo the insert action. - bool bBlueprintStructureModified = false; - UHoudiniInputHoudiniSplineComponent* NewInput = MainInput->CreateHoudiniSplineInput(nullptr, true, false, bBlueprintStructureModified); - MainInput->LastInsertedInputs.Add(NewInput); - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction(FText::FromString("Modifying Houdini input: Adding curve input.")); - MainInput->Modify(); - - // Modify the MainInput. - MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType())->Add(NewInput); - - MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, NewInputCount); - - if (bBlueprintStructureModified) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([InsertAnInputCurve, NumInputObjects]() - { - return InsertAnInputCurve(NumInputObjects+1); - //return SetCurveInputObjectsCount(NumInputObjects+1); - }), - - LOCTEXT("AddInputCurve", "Adds a Curve Input"), true) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateLambda([InInputs, MainInput, &CategoryBuilder]() - { - TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - - // Detach all curves before deleting. - for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) - { - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - } - - // Clear the insert objects buffer before transaction. - MainInput->LastInsertedInputs.Empty(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction(FText::FromString("Modifying Houdini Input: Delete curve inputs.")); - MainInput->Modify(); - - bool bBlueprintStructureModified = false; - - // actual delete. - for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) - { - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - MainInput->RemoveSplineFromInputObject(HoudiniInput, bBlueprintStructureModified); - - MainInput->DeleteInputObjectAt(EHoudiniInputType::Curve, n); - } - - MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, 0); - - if (bBlueprintStructureModified) - { - UActorComponent* OuterComponent = Cast(MainInput->GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterComponent); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - }), - LOCTEXT("EmptyInputsCurve", "Removes All Curve Inputs"), true) - ] - + SHorizontalBox::Slot().FillWidth(80.f).MaxWidth(80.f) - [ - SNew(SButton) - .Text(LOCTEXT("ResetCurveOffsetStr", "Reset Offset")) - .OnClicked_Lambda([MainInput]()->FReply - { - MainInput->ResetDefaultCurveOffset(); - return FReply::Handled(); - }) - ] - ]; - - //UHoudiniSplineComponent* SplineCompBeingEdited = nullptr; - TSharedPtr HouSplineComponentVisualizer; - if (GUnrealEd) - { - TSharedPtr Visualizer = - GUnrealEd->FindComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); - - HouSplineComponentVisualizer = StaticCastSharedPtr(Visualizer); - } - - - for (int n = 0; n < NumInputObjects; n++) - { - Helper_CreateCurveWidget(CategoryBuilder, InInputs, n, AssetThumbnailPool ,VerticalBox, HouSplineComponentVisualizer); - } -} - -void -FHoudiniInputDetails::Helper_CreateCurveWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InCurveObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox, - TSharedPtr HouSplineComponentVisualizer) -{ - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetComponent * OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - auto GetHoudiniSplineComponentAtIndex = [](UHoudiniInput * Input, int32 Index) - { - UHoudiniSplineComponent* FoundHoudiniSplineComponent = nullptr; - if (!Input || Input->IsPendingKill()) - return FoundHoudiniSplineComponent; - - // Get the TArray ptr to the curve objects in this input - TArray * CurveInputComponentArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - return FoundHoudiniSplineComponent; - - if (!CurveInputComponentArray->IsValidIndex(Index)) - return FoundHoudiniSplineComponent; - - // Access the object used in the corresponding Houdini curve input - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - FoundHoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - - return FoundHoudiniSplineComponent; - }; - - - // Get the TArray ptr to the curve objects in this input - TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - return; - - if (!CurveInputComponentArray->IsValidIndex(InCurveObjectIdx)) - return; - - // Access the object used in the corresponding Houdini curve input - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[InCurveObjectIdx]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent) - return; - - FString HoudiniSplineName = HoudiniSplineComponent->GetHoudiniSplineName(); - - // Editable label for the current Houdini curve - TSharedPtr LabelHorizontalBox; - VerticalBox->AddSlot() - .Padding(0, 2) - .AutoHeight() - [ - SAssignNew(LabelHorizontalBox, SHorizontalBox) - ]; - - - TSharedPtr LabelBlock; - LabelHorizontalBox->AddSlot() - .Padding(0, 15, 0, 2) - .MaxWidth(150.f) - .FillWidth(150.f) - .VAlign(VAlign_Bottom) - .HAlign(HAlign_Left) - [ - SNew(SBox).HeightOverride(20.f).WidthOverride(200.f).VAlign(VAlign_Center) - [ - SAssignNew(LabelBlock, SEditableText).Text(FText::FromString(HoudiniSplineName)) - .OnTextCommitted_Lambda([HoudiniSplineComponent](FText NewText, ETextCommit::Type CommitType) - { - if (CommitType == ETextCommit::Type::OnEnter) - { - HoudiniSplineComponent->SetHoudiniSplineName(NewText.ToString()); - } - }) - ] - ]; - - // 'Editing...' TextBlock showing if this component is being edited - TSharedPtr EditingTextBlock; - LabelHorizontalBox->AddSlot() - .Padding(0, 15, 0, 2) - .MaxWidth(55.f) - .FillWidth(55.f) - .VAlign(VAlign_Bottom) - .HAlign(HAlign_Left) - [ - SNew(SBox).HeightOverride(20.f).WidthOverride(75.f).VAlign(VAlign_Center) - [ - SAssignNew(EditingTextBlock, SCurveEditingTextBlock).Text(LOCTEXT("HoudiniCurveInputEditingLabel", "(editing...)")) - ] - ]; - - EditingTextBlock->HoudiniSplineComponent = HoudiniSplineComponent; - EditingTextBlock->HoudiniSplineComponentVisualizer = HouSplineComponentVisualizer; - - // Lambda for deleting the current curve input - auto DeleteHoudiniCurveAtIndex = [InInputs, InCurveObjectIdx, OuterHAC, CurveInputComponentArray, &CategoryBuilder]() - { - if (!OuterHAC|| OuterHAC->IsPendingKill()) - return; - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeDeleteACurve", "Houdini Input: Deleting a curve input"), - OuterHAC); - - int MainInputCurveArraySize = CurveInputComponentArray->Num(); - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - Input->Modify(); - - TArray* InputObjectArr = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!InputObjectArr) - continue; - - if (!InputObjectArr->IsValidIndex(InCurveObjectIdx)) - continue; - - if (MainInputCurveArraySize != InputObjectArr->Num()) - continue; - - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast((*InputObjectArr)[InCurveObjectIdx]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - return; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent) - return; - - // Detach the spline component before delete. - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - - // This input is marked changed when an input component is deleted. - Input->DeleteInputObjectAt(EHoudiniInputType::Curve, InCurveObjectIdx); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - // Add delete button UI - LabelHorizontalBox->AddSlot().Padding(0, 2, 0, 2).HAlign(HAlign_Right).VAlign(VAlign_Bottom).AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([DeleteHoudiniCurveAtIndex]() - { - return DeleteHoudiniCurveAtIndex(); - })) - ]; - - - TSharedPtr HorizontalBox = NULL; - VerticalBox->AddSlot().Padding(0, 2).AutoHeight()[SAssignNew(HorizontalBox, SHorizontalBox)]; - - // Closed check box - // Lambda returning a closed state - auto IsCheckedClosedCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsClosedCurve() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing Closed state - auto CheckStateChangedClosedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeClosed", "Houdini Input: Changing Curve Closed"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->IsClosedCurve() == bNewState) - continue; - - HoudiniSplineComponent->Modify(); - - HoudiniSplineComponent->SetClosedCurve(bNewState); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Add Closed check box UI - TSharedPtr CheckBoxClosed = NULL; - HorizontalBox->AddSlot().Padding(0, 2).AutoWidth() - [ - SAssignNew(CheckBoxClosed, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("ClosedCurveCheckBox", "Closed")) - .ToolTipText(LOCTEXT("ClosedCurveCheckboxTip", "Close this input curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedClosedCurve]() - { - return IsCheckedClosedCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedClosedCurve](ECheckBoxState NewState) - { - return CheckStateChangedClosedCurve(NewState); - }) - ]; - - // Reversed check box - // Lambda returning a reversed state - auto IsCheckedReversedCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsReversed() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing reversed state - auto CheckStateChangedReversedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeReversed", "Houdini Input: Changing Curve Reversed"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->IsReversed() == bNewState) - continue; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetReversed(bNewState); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Add reversed check box UI - TSharedPtr CheckBoxReversed = NULL; - HorizontalBox->AddSlot() - .Padding(2, 2) - .AutoWidth() - [ - SAssignNew(CheckBoxReversed, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("ReversedCurveCheckBox", "Reversed")) - .ToolTipText(LOCTEXT("ReversedCurveCheckboxTip", "Reverse this input curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedReversedCurve]() - { - return IsCheckedReversedCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedReversedCurve](ECheckBoxState NewState) - { - return CheckStateChangedReversedCurve(NewState); - }) - ]; - - // Visible check box - // Lambda returning a visible state - auto IsCheckedVisibleCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsHoudiniSplineVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing visible state - auto CheckStateChangedVisibleCurve = [GetHoudiniSplineComponentAtIndex, InInputs, OuterHAC, InCurveObjectIdx](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent) - continue; - - if (HoudiniSplineComponent->IsHoudiniSplineVisible() == bNewState) - return; - - HoudiniSplineComponent->SetHoudiniSplineVisible(bNewState); - } - - if (GEditor) - GEditor->RedrawAllViewports(); - - }; - - // Add visible check box UI - TSharedPtr CheckBoxVisible = NULL; - HorizontalBox->AddSlot().Padding(2, 2).AutoWidth() - [ - SAssignNew(CheckBoxVisible, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("VisibleCurveCheckBox", "Visible")) - .ToolTipText(LOCTEXT("VisibleCurveCheckboxTip", "Set the visibility of this curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedVisibleCurve]() - { - return IsCheckedVisibleCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedVisibleCurve](ECheckBoxState NewState) - { - return CheckStateChangedVisibleCurve(NewState); - }) - ]; - - // Curve type comboBox - // Lambda for changing Houdini curve type - auto OnCurveTypeChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - if (!InNewChoice.IsValid()) - return; - - EHoudiniCurveType NewInputType = UHoudiniInput::StringToHoudiniCurveType(*InNewChoice.Get()); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeType", "Houdini Input: Changing Curve Type"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->GetCurveType() == NewInputType) - continue; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetCurveType(NewInputType); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Lambda for getting Houdini curve type - auto GetCurveTypeText = [HoudiniSplineComponent]() - { - return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(HoudiniSplineComponent->GetCurveType())); - }; - - // Add curve type combo box UI - TSharedPtr CurveTypeHorizontalBox; - VerticalBox->AddSlot() - .Padding(0, 2, 2, 0) - .AutoHeight() - [ - SAssignNew(CurveTypeHorizontalBox, SHorizontalBox) - ]; - - // Add curve type label UI - CurveTypeHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() - [ - SNew(STextBlock).Text(LOCTEXT("CurveTypeText", "Curve Type ")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveType; - CurveTypeHorizontalBox->AddSlot() - .Padding(2, 2, 5, 2) - .FillWidth(150.f) - .MaxWidth(150.f) - [ - SAssignNew(ComboBoxCurveType, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels())[(int)HoudiniSplineComponent->GetCurveType()]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnCurveTypeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnCurveTypeChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([GetCurveTypeText]() - { - return GetCurveTypeText(); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - // Houdini curve method combo box - // Lambda for changing Houdini curve method - auto OnCurveMethodChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - if (!InNewChoice.IsValid()) - return; - - EHoudiniCurveMethod NewInputMethod = UHoudiniInput::StringToHoudiniCurveMethod(*InNewChoice.Get()); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeMethod", "Houdini Input: Changing Curve Method"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->GetCurveMethod() == NewInputMethod) - return; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetCurveMethod(NewInputMethod); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Lambda for getting Houdini curve method - auto GetCurveMethodText = [HoudiniSplineComponent]() - { - return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(HoudiniSplineComponent->GetCurveMethod())); - }; - - // Add curve method combo box UI - TSharedPtr< SHorizontalBox > CurveMethodHorizontalBox; - VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(CurveMethodHorizontalBox, SHorizontalBox)]; - - // Add curve method label UI - CurveMethodHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() - [ - SNew(STextBlock).Text(LOCTEXT("CurveMethodText", "Curve Method ")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveMethod; - CurveMethodHorizontalBox->AddSlot().Padding(2, 2, 5, 2).FillWidth(150.f).MaxWidth(150.f) - [ - SAssignNew(ComboBoxCurveMethod, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels())[(int)HoudiniSplineComponent->GetCurveMethod()]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnCurveMethodChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnCurveMethodChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([GetCurveMethodText]() - { - return GetCurveMethodText(); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - auto BakeInputCurveLambda = [](TArray Inputs, int32 Index, bool bBakeToBlueprint) - { - for (auto & NextInput : Inputs) - { - if (!NextInput || NextInput->IsPendingKill()) - continue; - - UHoudiniAssetComponent * OuterHAC = Cast(NextInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - continue; - - AActor * OwnerActor = OuterHAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - continue; - - TArray * CurveInputComponentArray = NextInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - continue; - - if (!CurveInputComponentArray->IsValidIndex(Index)) - continue; - - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - FHoudiniPackageParams PackageParams; - PackageParams.BakeFolder = OuterHAC->BakeFolder.Path; - PackageParams.HoudiniAssetName = OuterHAC->GetName(); - PackageParams.GeoId = NextInput->GetAssetNodeId(); - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.ObjectId = Index; - PackageParams.ObjectName = OwnerActor->GetName() + "InputHoudiniSpline" + FString::FromInt(Index); - - if (bBakeToBlueprint) - { - FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( - HoudiniSplineComponent, - PackageParams, - OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); - } - else - { - FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - HoudiniSplineComponent, - PackageParams, - OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); - } - } - - return FReply::Handled(); - }; - - // Add input curve bake button - TSharedPtr< SHorizontalBox > InputCurveBakeHorizontalBox; - VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(InputCurveBakeHorizontalBox, SHorizontalBox)]; - VerticalBox->AddSlot() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(110.f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("HoudiniInputCurveBakeToActorButton", "Bake to Actor")) - .IsEnabled(true) - .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() - { - return BakeInputCurveLambda(InInputs, InCurveObjectIdx, false); - }) - .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToActorButtonToolTip", "Bake this input curve to Actor")) - ] - - + SHorizontalBox::Slot().MaxWidth(110.f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("HoudiniInputCurveBakeToBPButton", "Bake to Blueprint")) - .IsEnabled(true) - .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() - { - return BakeInputCurveLambda(InInputs, InCurveObjectIdx, true); - }) - .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToBPButtonToolTip", "Bake this input curve to Blueprint")) - ] - ]; - - // Do we actually need to set enable the UI components? - if (MainInput->GetInputType() == EHoudiniInputType::Curve) - { - LabelBlock->SetEnabled(true); - CheckBoxClosed->SetEnabled(true); - CheckBoxReversed->SetEnabled(true); - CheckBoxVisible->SetEnabled(true); - ComboBoxCurveType->SetEnabled(true); - ComboBoxCurveMethod->SetEnabled(true); - } - else - { - LabelBlock->SetEnabled(false); - CheckBoxClosed->SetEnabled(false); - CheckBoxReversed->SetEnabled(false); - CheckBoxVisible->SetEnabled(false); - ComboBoxCurveType->SetEnabled(false); - ComboBoxCurveMethod->SetEnabled(false); - } -} - -void -FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - // Lambda returning a CheckState from the input's current KeepWorldTransform state - auto IsCheckedUpdateInputLandscape = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetUpdateInputLandscape() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing KeepWorldTransform state - auto CheckStateChangedUpdateInputLandscape = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangedUpdate", "Houdini Input: Changing Keep World Transform"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (bNewState == CurInput->GetUpdateInputLandscape()) - continue; - - CurInput->Modify(); - - UHoudiniAssetComponent* HAC = Cast(CurInput->GetOuter()); - if (!HAC) - continue; - - TArray* LandscapeInputObjects = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (!LandscapeInputObjects) - continue; - - for (UHoudiniInputObject* NextInputObj : *LandscapeInputObjects) - { - UHoudiniInputLandscape* CurrentInputLandscape = Cast(NextInputObj); - if (!CurrentInputLandscape) - continue; - - ALandscapeProxy* CurrentInputLandscapeProxy = CurrentInputLandscape->GetLandscapeProxy(); - if (!CurrentInputLandscapeProxy) - continue; - - if (bNewState) - { - // We want to update this landscape data directly, start by backing it up to image files in the temp folder - FString BackupBaseName = HAC->TemporaryCookFolder.Path - + TEXT("/") - + CurrentInputLandscapeProxy->GetName() - + TEXT("_") - + HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); - - // We need to cache the input landscape to a file - FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(BackupBaseName, CurrentInputLandscapeProxy); - - // Cache its transform on the input - CurrentInputLandscape->CachedInputLandscapeTraqnsform = CurrentInputLandscapeProxy->ActorToWorld(); - - HAC->SetMobility(EComponentMobility::Static); - CurrentInputLandscapeProxy->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); - } - else - { - // We are not updating this input landscape anymore, detach it and restore its backed-up values - CurrentInputLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - - // Restore the input landscape's backup data - FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(CurrentInputLandscapeProxy); - - // Reapply the source Landscape's transform - CurrentInputLandscapeProxy->SetActorTransform(CurrentInputLandscape->CachedInputLandscapeTraqnsform); - - // TODO: - // Clear the input obj map? - } - } - - CurInput->bUpdateInputLandscape = (NewState == ECheckBoxState::Checked); - CurInput->MarkChanged(true); - } - }; - - // CheckBox : Update Input Landscape Data - TSharedPtr< SCheckBox > CheckBoxUpdateInput; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew( CheckBoxUpdateInput, SCheckBox).Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) - .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedUpdateInputLandscape, MainInput]() - { - return IsCheckedUpdateInputLandscape(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedUpdateInputLandscape, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedUpdateInputLandscape(InInputs, NewState); - }) - ]; - - // Actor picker: Landscape. - FMenuBuilder MenuBuilder = Helper_CreateLandscapePickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - - // Checkboxes : Export landscape as Heightfield/Mesh/Points - { - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAs", "Export Landscape As")) - .ToolTipText(LOCTEXT("LandscapeExportAsToolTip", "Choose the type of data you want the ladscape to be exported to:\n * Heightfield\n * Mesh\n * Points")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr ButtonOptionsPanel; - VerticalBox->AddSlot().Padding(5, 2, 5, 2).AutoHeight() - [ - SAssignNew(ButtonOptionsPanel, SUniformGridPanel) - ]; - - auto IsCheckedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) - { - if (!Input || Input->IsPendingKill()) - return ECheckBoxState::Unchecked; - - if (Input->GetLandscapeExportType() == LandscapeExportType) - return ECheckBoxState::Checked; - else - return ECheckBoxState::Unchecked; - }; - - auto CheckStateChangedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) - { - if (!Input || Input->IsPendingKill()) - return false; - - if (Input->GetLandscapeExportType() == LandscapeExportType) - return false; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changed Landscape export type."), - Input->GetOuter()); - Input->Modify(); - - Input->SetLandscapeExportType(LandscapeExportType); - Input->SetHasLandscapeExportTypeChanged(true); - Input->MarkChanged(true); - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return true; - - for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) - { - if (!NextInputObj) - continue; - NextInputObj->MarkChanged(true); - } - - return true; - }; - - // Heightfield - FText HeightfieldTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a heightfield."); - ButtonOptionsPanel->AddSlot(0, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Start") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Heightfield); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for(auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Heightfield); - }) - .ToolTipText(HeightfieldTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("ClassIcon.LandscapeComponent")) - ] - + SHorizontalBox::Slot() - .FillWidth(1.f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsHeightfieldCheckbox", "Heightfield")) - .ToolTipText(HeightfieldTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - - // Mesh - FText MeshTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a mesh."); - ButtonOptionsPanel->AddSlot(1, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Mesh); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for (auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Mesh); - }) - .ToolTipText(MeshTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("ClassIcon.StaticMeshComponent")) - ] - - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsMeshCheckbox", "Mesh")) - .ToolTipText(MeshTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - - // Points - FText PointsTooltip = LOCTEXT("LandscapeExportAsPointsTooltip", "If enabled, the landscape will be exported to Houdini as points."); - ButtonOptionsPanel->AddSlot(2, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.End") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Points); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for (auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Points); - }) - .ToolTipText(PointsTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("Mobility.Static")) - ] - - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsPointsCheckbox", "Points")) - .ToolTipText(PointsTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - } - - // CheckBox : Export selected components only - { - TSharedPtr< SCheckBox > CheckBoxExportSelected; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportSelected, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeSelectedCheckbox", "Export Selected Landscape Components Only")) - .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, only the selected Landscape Components will be exported.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportSelectionOnly ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportSelectionOnly", "Houdini Input: Changing Landscape export only selected component."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportSelectionOnly) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportSelectionOnly = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - } - - // Checkbox: auto select components - { - TSharedPtr< SCheckBox > CheckBoxAutoSelectComponents; - VerticalBox->AddSlot().Padding(10, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxAutoSelectComponents, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("AutoSelectComponentCheckbox", "Auto-select component in asset bounds")) - .ToolTipText(LOCTEXT("AutoSelectComponentCheckboxTooltip", "If enabled, when no Landscape components are curremtly selected, the one within the asset's bounding box will be exported.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeAutoSelectComponent ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeAutoSelectComponent", "Houdini Input: Changing Landscape input auto-selects components."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeAutoSelectComponent) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeAutoSelectComponent = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - // Enable only when exporting selection or when exporting heighfield (for now) - bool bEnable = false; - for (auto CurrentInput : InInputs) - { - if (!MainInput->bLandscapeExportSelectionOnly) - continue; - - bEnable = true; - break; - } - CheckBoxAutoSelectComponents->SetEnabled(bEnable); - } - - - // The following checkbox are only added when not in heightfield mode - if (MainInput->LandscapeExportType != EHoudiniLandscapeExportType::Heightfield) - { - // Checkbox : Export materials - { - TSharedPtr< SCheckBox > CheckBoxExportMaterials; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportMaterials, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeMaterialsCheckbox", "Export Landscape Materials")) - .ToolTipText(LOCTEXT("LandscapeMaterialsTooltip", "If enabled, the landscape materials will be exported with it.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportMaterials", "Houdini Input: Changing Landscape input export materials."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportMaterials) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportMaterials = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportMaterials->SetEnabled(false); - */ - } - - // Checkbox : Export Tile UVs - { - TSharedPtr< SCheckBox > CheckBoxExportTileUVs; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportTileUVs, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeTileUVsCheckbox", "Export Landscape Tile UVs")) - .ToolTipText(LOCTEXT("LandscapeTileUVsTooltip", "If enabled, UVs will be exported separately for each Landscape tile.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportTileUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportTileUVs", "Houdini Input: Changing Landscape export tile UVs."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportTileUVs) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportTileUVs = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportTileUVs->SetEnabled(false); - */ - } - - // Checkbox : Export normalized UVs - { - TSharedPtr< SCheckBox > CheckBoxExportNormalizedUVs; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportNormalizedUVs, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeNormalizedUVsCheckbox", "Export Landscape Normalized UVs")) - .ToolTipText(LOCTEXT("LandscapeNormalizedUVsTooltip", "If enabled, landscape UVs will be exported in [0, 1].")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportNormalizedUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Changing Landscape export normalized UVs."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportNormalizedUVs) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportNormalizedUVs = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportNormalizedUVs->SetEnabled(false); - */ - } - - // Checkbox : Export lighting - { - TSharedPtr< SCheckBox > CheckBoxExportLighting; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportLighting, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeLightingCheckbox", "Export Landscape Lighting")) - .ToolTipText(LOCTEXT("LandscapeLightingTooltip", "If enabled, lightmap information will be exported with the landscape.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportLighting", "Houdini Input: Changing Landscape export lighting."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportLighting) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportLighting = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportLighting->SetEnabled(false); - */ - } - - } - - // Button : Recommit - { - auto OnButtonRecommitClicked = [InInputs]() - { - for (auto CurrentInput : InInputs) - { - TArray* LandscapeInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); - if (!LandscapeInputObjectsArray) - continue; - - for (UHoudiniInputObject* NextLandscapeInput : *LandscapeInputObjectsArray) - { - if (!NextLandscapeInput) - continue; - - NextLandscapeInput->MarkChanged(true); - } - - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1, 2, 4, 2) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("LandscapeInputRecommit", "Recommit Landscape")) - .ToolTipText(LOCTEXT("LandscapeInputRecommitTooltip", "Recommits the Landscape to Houdini.")) - .OnClicked_Lambda(OnButtonRecommitClicked) - ] - ]; - } - - - // Button : Clear Selection - { - auto IsClearButtonEnabled = [MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return false; - - if (MainInput->GetInputType() != EHoudiniInputType::Landscape) - return false; - - TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); - if (!MainInputObjectsArray) - return false; - - if (MainInputObjectsArray->Num() <= 0) - return false; - - return true; - }; - - auto OnButtonClearClicked = [InInputs]() - { - if (InInputs.Num() <= 0) - return FReply::Handled(); - - UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - if (MainInput->GetInputType() != EHoudiniInputType::Landscape) - return FReply::Handled(); - - TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); - if (!MainInputObjectsArray) - return FReply::Handled(); - - if (MainInputObjectsArray->Num() <= 0) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Clearing landscape input."), - MainInput->GetOuter()); - - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - TArray* LandscapeInputObjectsArray = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (!LandscapeInputObjectsArray) - continue; - - if (LandscapeInputObjectsArray->Num() <= 0) - continue; - - CurInput->MarkChanged(true); - CurInput->Modify(); - - LandscapeInputObjectsArray->Empty(); - } - - return FReply::Handled(); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().Padding(1, 2, 4, 2) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("ClearSelection", "Clear Selection")) - .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) - .IsEnabled_Lambda(IsClearButtonEnabled) - .OnClicked_Lambda(OnButtonClearClicked) - ] - ]; - } -} - -/* -FMenuBuilder -FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArray& InInputs, const TAttribute& HeadingText, const bool& bShowCurrentSelectionSection) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - - // Filters are only based on the MainInput - auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - if (!Actor->IsA()) - return false; - - ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); - if (!LandscapeProxy) - return false; - - // Get the landscape's actor - AActor* OwnerActor = LandscapeProxy->GetOwner(); - - // Get our Actor - UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); - AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; - - // TODO: FIX ME! - // IF the landscape is owned by ourself, skip it! - if (OwnerActor == MyOwner) - return false; - - return true; - }; - - auto OnShouldFilterWorld = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - const TArray* InputObjects = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurInputObject : *InputObjects) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - auto OnShouldFilterHoudiniAsset = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - // But not our own Asset Actor - if (const USceneComponent* RootComp = Cast(InInput->GetOuter())) - { - if (RootComp && Cast(RootComp->GetOwner()) != Actor) - return true; - } - - return false; - }; - - auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape, OnShouldFilterWorld, OnShouldFilterHoudiniAsset](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - switch (MainInput->GetInputType()) - { - case EHoudiniInputType::Landscape: - return OnShouldFilterLandscape(Actor, MainInput); - case EHoudiniInputType::World: - return OnShouldFilterWorld(Actor, MainInput); - case EHoudiniInputType::Asset: - return OnShouldFilterHoudiniAsset(Actor, MainInput); - default: - return true; - } - - return false; - }; - - - // Selection uses the input arrays - auto OnLandscapeSelected = [](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - ALandscapeProxy* LandscapeProxy = Cast(Actor); - if (!LandscapeProxy) - return; - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return; - - LandscapeInputObjectsArray->Empty(); - - FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); - - // Create a Houdini Input Object. - UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( - LandscapeProxy, Input, LandscapeName.ToString()); - - UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); - LandscapeInput->MarkChanged(true); - - LandscapeInputObjectsArray->Add(LandscapeInput); - Input->MarkChanged(true); - }; - - auto OnHoudiniAssetActorSelected = [](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); - if (!HoudiniAssetActor) - return; - - TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!AssetInputObjectsArray) - return; - - AssetInputObjectsArray->Empty(); - - FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); - - // Create a Houdini Asset Input Object - UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); - - UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); - AssetInput->MarkChanged(true); - - AssetInputObjectsArray->Add(AssetInput); - Input->MarkChanged(true); - }; - - auto OnWorldSelected = [](AActor* Actor, UHoudiniInput* Input) - { - // Do Nothing - }; - - auto OnActorSelected = [OnLandscapeSelected, OnWorldSelected, OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) - { - for (auto& CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - return; - - switch (CurInput->GetInputType()) - { - case EHoudiniInputType::Landscape: - return OnLandscapeSelected(Actor, CurInput); - case EHoudiniInputType::World: - return OnWorldSelected(Actor, CurInput); - case EHoudiniInputType::Asset: - return OnHoudiniAssetActorSelected(Actor, CurInput); - default: - return; - } - } - - return; - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - if (bShowCurrentSelectionSection) - { - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - } - - - MenuBuilder.BeginSection(NAME_None, HeadingText); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} -*/ - - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterHoudiniAsset = [InInputs](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - // But not our selected Asset Actor - for (auto & NextSelectedInput : InInputs) - { - if (!NextSelectedInput) - continue; - - const USceneComponent* RootComp = Cast(NextSelectedInput->GetOuter()); - if (RootComp && Cast(RootComp->GetOwner()) == Actor) - return false; - - } - - return true; - }; - - // Filters are only based on the MainInput - auto OnShouldFilterActor = [MainInput, OnShouldFilterHoudiniAsset](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - return OnShouldFilterHoudiniAsset(Actor); - }; - - auto OnHoudiniAssetActorSelected = [OnShouldFilterHoudiniAsset](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || Actor->IsPendingKill() || !Input || Input->IsPendingKill()) - return; - - AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); - if (!HoudiniAssetActor) - return; - - // Make sure that the actor is valid for this input - if (!OnShouldFilterHoudiniAsset(Actor)) - return; - - TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - if (!AssetInputObjectsArray) - return; - - FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); - - // Create a Houdini Asset Input Object - UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); - - UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); - AssetInput->MarkChanged(true); - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniAssetInputChange", "Houdini Input: Selecting an asset input"), - Input->GetOuter()); - - Input->Modify(); - - AssetInputObjectsArray->Empty(); - AssetInputObjectsArray->Add(AssetInput); - Input->MarkChanged(true); - }; - - auto OnActorSelected = [OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) - { - for (auto& CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - return; - - OnHoudiniAssetActorSelected(Actor, CurInput); - } - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - // Show current selection - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("AssetInputSelectableActors", "Houdini Assets")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - if (!Actor->IsA()) - return false; - - ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); - if (!LandscapeProxy) - return false; - - // Get the landscape's parent actor - // We need to get the AttachParent on the root componet, GteOwner will not return the parent actor! - AActor* OwnerActor = nullptr; - USceneComponent* RootComponent = LandscapeProxy->GetRootComponent(); - if (RootComponent && !RootComponent->IsPendingKill()) - OwnerActor = RootComponent->GetAttachParent() ? RootComponent->GetAttachParent()->GetOwner() : LandscapeProxy->GetOwner(); - - // Get our Actor - UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); - AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; - - // IF the landscape is owned by ourself, skip it! - if (OwnerActor && OwnerActor == MyOwner) - { - // ... buuuut we dont want to filter input landscapes that have the "Update Input Landscape Data" option enabled - // (and are, therefore, outputs as well) - for (int32 Idx = 0; Idx < MyHAC->GetNumInputs(); Idx++) - { - UHoudiniInput* CurrentInput = MyHAC->GetInputAt(Idx); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) - continue; - - if (!CurrentInput->GetUpdateInputLandscape()) - continue; - - // Don't filter our input landscapes - ALandscapeProxy* UpdatedInputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); - if (LandscapeProxy == UpdatedInputLandscape) - return true; - } - - return false; - } - - return true; - }; - - // Filters are only based on the MainInput - auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - return OnShouldFilterLandscape(Actor, MainInput); - }; - - // Selection uses the input arrays - auto OnLandscapeSelected = [OnShouldFilterLandscape](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - // Make sure that the actor is valid for this input - if (!OnShouldFilterLandscape(Actor, Input)) - return; - - ALandscapeProxy* LandscapeProxy = Cast(Actor); - if (!LandscapeProxy) - return; - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return; - - LandscapeInputObjectsArray->Empty(); - - FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); - - // Create a Houdini Input Object. - UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( - LandscapeProxy, Input, LandscapeName.ToString()); - - UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); - LandscapeInput->MarkChanged(true); - - LandscapeInputObjectsArray->Add(LandscapeInput); - Input->MarkChanged(true); - }; - - auto OnActorSelected = [OnLandscapeSelected](AActor* Actor, TArray InInputs) - { - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeSelections", "Houdini Input: Selecting input landscape."), - MainInput->GetOuter()); - - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - OnLandscapeSelected(Actor, CurInput); - } - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - // Show current selection - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("LandscapeInputSelectableActors", "Landscapes")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterWorld = [MainInput](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - const TArray* InputObjects = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurInputObject : *InputObjects) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) - { - // See if the input object is a HAC, if it is, get its parent actor - UHoudiniAssetComponent* CurHAC = Cast(CurInputObject->GetObject()); - if (CurHAC && !CurHAC->IsPendingKill()) - CurActor = CurHAC->GetOwner(); - } - - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - auto OnWorldSelected = [](AActor* Actor) - { - // Do Nothing - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterWorld); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputSelectedActors", "Currently Selected Actors")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnWorldSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilter = [MainInput](const AActor* const Actor) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - const TArray* BoundObjects = MainInput->GetBoundSelectorObjectArray(); - if (!BoundObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurActor : *BoundObjects) - { - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - - auto OnSelected = [](AActor* Actor) - { - // Do Nothing - }; - - FMenuBuilder MenuBuilder(true, nullptr); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputBoundSelectors", "Bound Selectors")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(FOnShouldFilterActor::CreateLambda(OnShouldFilter)); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -void -FHoudiniInputDetails::AddWorldInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - const IDetailsView* DetailsView) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); - - // Get the details view name and locked status - bool bDetailsLocked = false; - FName DetailsPanelName = "LevelEditorSelectionDetails"; - if (DetailsView) - { - DetailsPanelName = DetailsView->GetIdentifier(); - if (DetailsView->IsLocked()) - bDetailsLocked = true; - } - - // Check of we're in bound selector mode - bool bIsBoundSelector = MainInput->IsWorldInputBoundSelector(); - - // Button : Start Selection / Use current selection + refresh - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - //auto ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); - //auto ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); - FText ButtonLabel; - FText ButtonTooltip; - if (!bIsBoundSelector) - { - // Button : Start Selection / Use current selection - if (bDetailsLocked) - { - ButtonLabel = LOCTEXT("WorldInputUseCurrentSelection", "Use Current Selection (Unlocks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputUseCurrentSelectionTooltip", "Fill the asset's input with the current selection and unlocks the Details Panel."); - } - else - { - ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); - } - /* - FOnClicked OnSelectActors = FOnClicked::CreateStatic( - &FHoudiniInputDetails::Helper_OnButtonClickSelectActors, InInputs, DetailsPanelName); - */ - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ButtonLabel) - .ToolTipText(ButtonTooltip) - //.OnClicked(OnSelectActors) - .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() - { - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName); - }) - - ] - ]; - } - else - { - // Button : Start Selection / Use current selection as Bound selector - if (bDetailsLocked) - { - ButtonLabel = LOCTEXT("WorldInputUseSelectionAsBoundSelector", "Use Selection as Bound Selector (Unlocks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputUseSelectionAsBoundSelectorTooltip", "Fill the asset's input with all the actors contains in the current's selection bound, and unlocks the Details Panel."); - } - else - { - ButtonLabel = LOCTEXT("WorldInputStartBoundSelection", "Start Bound Selection (Locks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputStartBoundSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that will be used as bounds."); - } - - /* - FOnClicked OnSelectBounds = FOnClicked::CreateStatic( - &FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector, InInputs, DetailsPanelName); - */ - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ButtonLabel) - .ToolTipText(ButtonTooltip) - //.OnClicked(OnSelectBounds) - .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() - { - return Helper_OnButtonClickUseSelectionAsBoundSelector(CategoryBuilder, InInputs, DetailsPanelName); - }) - ] - ]; - } - } - - // Button : Select All + Clear Selection - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FOnClicked OnSelectAll = FOnClicked::CreateLambda([InInputs, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputSelectedAll", "Houdini Input: Selecting all actor in the current world"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // Get the parent component/actor/world of the current input - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - UWorld* MyWorld = CurrentInput->GetWorld(); - - TArray NewSelectedActors; - for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) - { - AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - NewSelectedActors.Add(CurrentActor); - } - - CurrentInput->Modify(); - - bool bHasChanged = CurrentInput->UpdateWorldSelection(NewSelectedActors); - } - - return FReply::Handled(); - }); - - FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput, &CategoryBuilder]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - const bool bMainInputBoundSelection = MainInput->IsWorldInputBoundSelector(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputClear", "Houdini Input: Clearing world input selection"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - // Do nothing if the current input has different selector settings from the main input - if (CurrentInput->IsWorldInputBoundSelector() != bMainInputBoundSelection) - continue; - - CurrentInput->Modify(); - - if (CurrentInput->IsWorldInputBoundSelector()) - { - CurrentInput->SetBoundSelectorObjectsNumber(0); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - else - { - TArray EmptySelection; - bool bHasChanged = CurrentInput->UpdateWorldSelection(EmptySelection); - } - } - - return FReply::Handled(); - }); - - FText ClearSelectionLabel; - FText ClearSelectionTooltip; - if (bIsBoundSelector) - { - ClearSelectionLabel = LOCTEXT("ClearBoundSelection", "Clear Bound Selection"); - ClearSelectionTooltip = LOCTEXT("ClearBoundSelectionTooltip", "Clear the input's current bound selection."); - } - else - { - ClearSelectionLabel = LOCTEXT("ClearSelection", "Clear Selection"); - ClearSelectionTooltip = LOCTEXT("ClearSelectionTooltip", "Clear the input's current selection."); - } - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - // Button : SelectAll - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("WorldInputSelectAll", "Select All")) - .ToolTipText(LOCTEXT("WorldInputSelectAll", "Fill the asset's input with all actors.")) - .OnClicked(OnSelectAll) - .IsEnabled(!bIsBoundSelector) - ] - + SHorizontalBox::Slot() - [ - // Button : Clear Selection - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ClearSelectionLabel) - .ToolTipText(ClearSelectionTooltip) - .OnClicked(OnClearSelect) - ] - ]; - - // Do not enable select all/clear select when selection has been started and details are locked - HorizontalBox->SetEnabled(!bDetailsLocked); - } - - // Checkbox: Bound Selector - { - // Lambda returning a CheckState from the input's current bound selector state - auto IsCheckedBoundSelector = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->IsWorldInputBoundSelector() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing bound selector state - auto CheckStateChangedIsBoundSelector = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeBoungSelector", "Houdini Input: Changing world input to bound selector"), - MainInput->GetOuter()); - - bool bNewState = (NewState == ECheckBoxState::Checked); - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->IsWorldInputBoundSelector() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetWorldInputBoundSelector(bNewState); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - // Checkbox : Is Bound Selector - TSharedPtr< SCheckBox > CheckBoxBoundSelector; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxBoundSelector, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("BoundSelector", "Bound Selector")) - .ToolTipText(LOCTEXT("BoundSelectorTip", "When enabled, this world input works as a bound selector, sending all the objects contained in the bound selector bounding boxes.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedBoundSelector, MainInput]() - { - return IsCheckedBoundSelector(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedIsBoundSelector, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedIsBoundSelector(InInputs, NewState); - }) - ]; - } - - // Checkbox: Bound Selector Auto update - { - // Lambda returning a CheckState from the input's current auto update state - auto IsCheckedAutoUpdate = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetWorldInputBoundSelectorAutoUpdates() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing the auto update state - auto CheckStateChangedBoundAutoUpdates = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeAutoUpdate", "Houdini Input: Changing bound selector auto-update state."), - MainInput->GetOuter()); - - bool bNewState = (NewState == ECheckBoxState::Checked); - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetWorldInputBoundSelectorAutoUpdates() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetWorldInputBoundSelectorAutoUpdates(bNewState); - CurInput->MarkChanged(true); - } - }; - - // Checkbox : Is Bound Selector - TSharedPtr< SCheckBox > CheckBoxBoundAutoUpdate; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxBoundAutoUpdate, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("BoundAutoUpdate", "Update bound selection automatically")) - .ToolTipText(LOCTEXT("BoundAutoUpdateTip", "If enabled and if this world input is set as a bound selector, the objects selected by the bounds will update automatically.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedAutoUpdate, MainInput]() - { - return IsCheckedAutoUpdate(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedBoundAutoUpdates, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedBoundAutoUpdates(InInputs, NewState); - }) - ]; - - CheckBoxBoundAutoUpdate->SetEnabled(MainInput->IsWorldInputBoundSelector()); - } - - // ActorPicker : Bound Selector - if(bIsBoundSelector) - { - FMenuBuilder MenuBuilder = Helper_CreateBoundSelectorPickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - // ActorPicker : World Outliner - { - FMenuBuilder MenuBuilder = Helper_CreateWorldActorPickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - { - // Spline Resolution - TSharedPtr> NumericEntryBox; - int32 Idx = 0; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(LOCTEXT("SplineRes", "Unreal Spline Resolution")) - .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm betweem control points)\nSet this to 0 to only export the control points.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SNumericEntryBox) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .MinValue(-1.0f) - .MaxValue(1000.0f) - .MinSliderValue(0.0f) - .MaxSliderValue(1000.0f) - .Value(MainInput->GetUnrealSplineResolution()) - .OnValueChanged_Lambda([MainInput, InInputs](float Val) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeSplineResolution", "Houdini Input: Changing world input spline resolution"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetUnrealSplineResolution() == Val) - continue; - - CurrentInput->Modify(); - - CurrentInput->SetUnrealSplineResolution(Val); - CurrentInput->MarkChanged(true); - } - }) - /* - .Value(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject( - &InParam, &UHoudiniAssetInput::GetSplineResolutionValue))) - .OnValueChanged(SNumericEntryBox< float >::FOnValueChanged::CreateUObject( - &InParam, &UHoudiniAssetInput::SetSplineResolutionValue)) - .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateUObject( - &InParam, &UHoudiniAssetInput::IsSplineResolutionEnabled))) - */ - .SliderExponent(1.0f) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("SplineResToDefault", "Reset to default value.")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - // TODO: FINISH ME! - //.OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) - .OnClicked_Lambda([MainInput, InInputs]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputRevertSplineResolution", "Houdini Input: Reverting world input spline resolution to default"), - MainInput->GetOuter()); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - float DefaultSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetUnrealSplineResolution() == DefaultSplineResolution) - continue; - - CurrentInput->Modify(); - - CurrentInput->SetUnrealSplineResolution(DefaultSplineResolution); - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - } -} - -void -FHoudiniInputDetails::AddSkeletalInputUI( - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool ) -{ -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) -{ - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, false); -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) -{ - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, true); -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName, const bool& bUseWorldInAsWorldSelector) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // There's no undo operation for button. - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Locate the details panel. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - return FReply::Handled(); - - class SLocalDetailsView : public SDetailsViewBase - { - public: - void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } - void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } - }; - auto * LocalDetailsView = static_cast(DetailsView.Get()); - - if (!DetailsView->IsLocked()) - { - // - // START SELECTION - // Locks the details view and select our currently selected actors - // - LocalDetailsView->LockDetailsView(); - check(DetailsView->IsLocked()); - - // Force refresh of details view. - TArray InputOuters; - for (auto CurIn : InInputs) - InputOuters.Add(CurIn->GetOuter()); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - //ReselectSelectedActors(); - - if (bUseWorldInAsWorldSelector) - { - // Bound Selection - // Select back the previously chosen bound selectors - GEditor->SelectNone(false, true); - int32 NumBoundSelectors = MainInput->GetNumberOfBoundSelectorObjects(); - for (int32 Idx = 0; Idx < NumBoundSelectors; Idx++) - { - AActor* Actor = MainInput->GetBoundSelectorObjectAt(Idx); - if (!Actor || Actor->IsPendingKill()) - continue; - - GEditor->SelectActor(Actor, true, true); - } - } - else - { - // Regular selection - // Select the already chosen input Actors from the World Outliner. - GEditor->SelectNone(false, true); - int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); - for (int32 Idx = 0; Idx < NumInputObjects; Idx++) - { - UHoudiniInputObject* CurInputObject = MainInput->GetHoudiniInputObjectAt(Idx); - if (!CurInputObject) - continue; - - AActor* Actor = nullptr; - UHoudiniInputActor* InputActor = Cast(CurInputObject); - if (InputActor && !InputActor->IsPendingKill()) - { - // Get the input actor - Actor = InputActor->GetActor(); - } - else - { - // See if the input object is a HAC - UHoudiniInputHoudiniAsset* InputHAC = Cast(CurInputObject); - if (InputHAC && !InputHAC->IsPendingKill()) - { - Actor = InputHAC->GetHoudiniAssetComponent() ? InputHAC->GetHoudiniAssetComponent()->GetOwner() : nullptr; - } - } - - if (!Actor || Actor->IsPendingKill()) - continue; - - GEditor->SelectActor(Actor, true, true); - } - } - - return FReply::Handled(); - } - else - { - // - // UPDATE SELECTION - // Unlocks the input's selection and select the HDA back. - // - - if (!GEditor || !GEditor->GetSelectedObjects()) - return FReply::Handled(); - - USelection * SelectedActors = GEditor->GetSelectedActors(); - if (!SelectedActors) - return FReply::Handled(); - - // Create a transaction - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniWorldInputSelectionChanged", "Changing Houdini world outliner input objects"), - MainInput->GetOuter()); - - - TArray AllActors; - for (auto CurrentInput : InInputs) - { - CurrentInput->Modify(); - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - AllActors.Add(ParentActor); - - bool bHasChanged = true; - if (bUseWorldInAsWorldSelector) - { - // - // Update bound selectors - - // Clean up the selected actors - TArray ValidBoundSelectedActors; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor* CurrentBoundActor = Cast(*It); - if (!CurrentBoundActor) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentBoundActor == ParentActor)) - continue; - - ValidBoundSelectedActors.Add(CurrentBoundActor); - } - - // See if the bound selector have changed - int32 PreviousBoundSelectorCount = CurrentInput->GetNumberOfBoundSelectorObjects(); - if (PreviousBoundSelectorCount == ValidBoundSelectedActors.Num()) - { - // Same number of BoundSelectors, see if they have changed - bHasChanged = false; - for (int32 BoundIdx = 0; BoundIdx < PreviousBoundSelectorCount; BoundIdx++) - { - AActor* PreviousBound = CurrentInput->GetBoundSelectorObjectAt(BoundIdx); - if (!PreviousBound) - continue; - - if (!ValidBoundSelectedActors.Contains(PreviousBound)) - { - bHasChanged = true; - break; - } - } - } - - if (bHasChanged) - { - // Only update the bound selector objects on the input if they have changed - CurrentInput->SetBoundSelectorObjectsNumber(ValidBoundSelectedActors.Num()); - int32 InputObjectIdx = 0; - for (auto CurActor : ValidBoundSelectedActors) - { - CurrentInput->SetBoundSelectorObjectAt(InputObjectIdx++, CurActor); - } - - // Update the current selection from the BoundSelectors - CurrentInput->UpdateWorldSelectionFromBoundSelectors(); - } - } - else - { - // - // Update our selection directly with the currently selected actors - // - - TArray ValidSelectedActors; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor* CurrentActor = Cast(*It); - if (!CurrentActor) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - ValidSelectedActors.Add(CurrentActor); - } - - // Update the input objects from the valid selected actors array - // Only new/remove input objects will be marked as changed - bHasChanged = CurrentInput->UpdateWorldSelection(ValidSelectedActors); - } - - // If we didnt change the selection, cancel the transaction - if (!bHasChanged) - Transaction.Cancel(); - } - - // We can now unlock the details view... - LocalDetailsView->UnlockDetailsView(); - check(!DetailsView->IsLocked()); - - // .. reset the selected actors, force refresh and override the lock. - DetailsView->SetObjects(AllActors, true, true); - - // We now need to reselect all our Asset Actors. - // If we don't do this, our Asset parameters will stop refreshing and the user will be very confused. - // It is also resetting the state of the selection before the input actor selection process was started. - GEditor->SelectNone(false, true); - for (auto CurrentActor : AllActors) - { - AActor* ParentActor = Cast(CurrentActor); - if (!ParentActor) - continue; - - GEditor->SelectActor(ParentActor, true, true); - } - - // Update the input details layout. - // if (CategoryBuilder.IsParentLayoutValid()) - // CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); -} - - -bool -FHoudiniInputDetails::Helper_CancelWorldSelection(TArray& InInputs, const FName& DetailsPanelName) -{ - if (InInputs.Num() <= 0) - return false; - - // Get the property module to access the details view - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Locate the details panel. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - return false; - - if (!DetailsView->IsLocked()) - return false; - - class SLocalDetailsView : public SDetailsViewBase - { - public: - void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } - void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } - }; - auto * LocalDetailsView = static_cast(DetailsView.Get()); - - // Get all our parent components / actors - TArray AllComponents; - TArray AllActors; - for (auto CurrentInput : InInputs) - { - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - if (!ParentComponent) - continue; - - AllComponents.Add(ParentComponent); - - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - if (!ParentActor) - continue; - - AllActors.Add(ParentActor); - } - - // Unlock the detail view and re-select our parent actors - { - LocalDetailsView->UnlockDetailsView(); - check(!DetailsView->IsLocked()); - - // Reset selected actor to itself, force refresh and override the lock. - DetailsView->SetObjects(AllActors, true, true); - } - - // Reselect the Asset Actor. If we don't do this, our Asset parameters will stop - // refreshing and the user will be very confused. It is also resetting the state - // of the selection before the input actor selection process was started. - GEditor->SelectNone(false, true); - for (auto ParentActorObj : AllActors) - { - AActor* ParentActor = Cast(ParentActorObj); - if (!ParentActor) - continue; - - GEditor->SelectActor(ParentActor, true, true); - } - - return true; -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniInputDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniInput.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetBlueprintComponent.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniPackageParams.h" + +#include "Editor.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SRotatorInputBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SEditableText.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Editor/UnrealEd/Public/AssetThumbnail.h" +#include "Editor/PropertyEditor/Private/SDetailsViewBase.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" +#include "SAssetDropTarget.h" +#include "ScopedTransaction.h" +#include "Engine/SkeletalMesh.h" +#include "Engine/Selection.h" +#include "EngineUtils.h" +#include "AssetData.h" +#include "Framework/SlateDelegates.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Modules/ModuleManager.h" +#include "SceneOutlinerModule.h" + +#include "Editor/UnrealEdEngine.h" +#include "HoudiniSplineComponentVisualizer.h" +#include "UnrealEdGlobals.h" +#include "Widgets/SWidget.h" + +#include "HoudiniEngineRuntimeUtils.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// Customized TextBlock to show 'editing...' text if this Houdini Spline Component is being edited +class SCurveEditingTextBlock : public STextBlock +{ +public: + UHoudiniSplineComponent* HoudiniSplineComponent; + TSharedPtr HoudiniSplineComponentVisualizer; +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override + { + if (!HoudiniSplineComponentVisualizer.IsValid() || !HoudiniSplineComponent) + return LayerId; + + if (HoudiniSplineComponentVisualizer->GetEditedHoudiniSplineComponent() != HoudiniSplineComponent) + return LayerId; + + return STextBlock::OnPaint(Args, AllottedGeometry, MyClippingRect, + OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + } +}; + +void +FHoudiniInputDetails::CreateWidget( + IDetailCategoryBuilder& HouInputCategory, + TArray InInputs, + FDetailWidgetRow* InputRow) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouInputCategory.GetParentLayout().GetThumbnailPool(); + + EHoudiniInputType MainInputType = MainInput->GetInputType(); + UHoudiniAssetComponent* HAC = MainInput->GetTypedOuter(); + + // Create a widget row, or get the given row. + FDetailWidgetRow* Row = InputRow; + Row = InputRow == nullptr ? &(HouInputCategory.AddCustomRow(FText::GetEmpty())) : InputRow; + if (!Row) + return; + + // Create the standard input name widget if this is not a operator path parameter. + // Operator path parameter's name widget is handled by HoudiniParameterDetails. + if (!InputRow) + CreateNameWidget(MainInput, *Row, true, InInputs.Num()); + + // Create a vertical Box for storing the UI + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + // ComboBox : Input Type + const IDetailsView* DetailsView = HouInputCategory.GetParentLayout().GetDetailsView(); + AddInputTypeComboBox(HouInputCategory, VerticalBox, InInputs, DetailsView); + + // Checkbox : Keep World Transform + AddKeepWorldTransformCheckBox(VerticalBox, InInputs); + + + // Checkbox : CurveInput trigger cook on curve changed + AddCurveInputCookOnChangeCheckBox(VerticalBox, InInputs); + + + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) + { + // Checkbox : Pack before merging + AddPackBeforeMergeCheckbox(VerticalBox, InInputs); + } + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World || MainInputType == EHoudiniInputType::Asset) + { + AddImportAsReferenceCheckbox(VerticalBox, InInputs); + } + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) + { + // Checkboxes : Export LODs / Sockets / Collisions + AddExportCheckboxes(VerticalBox, InInputs); + } + + switch (MainInput->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + AddGeometryInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); + } + break; + + case EHoudiniInputType::Asset: + { + AddAssetInputUI(VerticalBox, InInputs); + } + break; + + case EHoudiniInputType::Curve: + { + AddCurveInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); + } + break; + + case EHoudiniInputType::Landscape: + { + AddLandscapeInputUI(VerticalBox, InInputs); + } + break; + + case EHoudiniInputType::World: + { + AddWorldInputUI(HouInputCategory, VerticalBox, InInputs, DetailsView); + } + break; + + case EHoudiniInputType::Skeletal: + { + AddSkeletalInputUI(VerticalBox, InInputs, AssetThumbnailPool); + } + break; + } + + + Row->ValueWidget.Widget = VerticalBox; + + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + //Row.ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniInputDetails::CreateNameWidget( + UHoudiniInput* InInput, FDetailWidgetRow & Row, bool bLabel, int32 InInputCount) +{ + if (!InInput || InInput->IsPendingKill()) + return; + + FString InputLabelStr = InInput->GetLabel(); + if (InInputCount > 1) + { + InputLabelStr += TEXT(" (") + FString::FromInt(InInputCount) + TEXT(")"); + } + + const FText & FinalInputLabelText = bLabel ? FText::FromString(InputLabelStr) : FText::GetEmpty(); + FText InputTooltip = GetInputTooltip(InInput); + { + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(FinalInputLabelText) + .ToolTipText(InputTooltip) + .Font(FEditorStyle::GetFontStyle(!InInput->HasChanged() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))); + } +} + +FText +FHoudiniInputDetails::GetInputTooltip(UHoudiniInput* InParam) +{ + // TODO + return FText(); +} + +void +FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuilder, TSharedRef VerticalBox, TArray& InInputs, const IDetailsView* DetailsView) +{ + // Get the details view name and locked status + bool bDetailsLocked = false; + FName DetailsPanelName = "LevelEditorSelectionDetails"; + if (DetailsView) + { + DetailsPanelName = DetailsView->GetIdentifier(); + if (DetailsView->IsLocked()) + bDetailsLocked = true; + } + + // Lambda return a FText correpsonding to an input's current type + auto GetInputText = [](UHoudiniInput* InInput) + { + return FText::FromString(InInput->GetInputTypeAsString()); + }; + + // Lambda for changing inputs type + auto OnSelChanged = [DetailsPanelName, &CategoryBuilder](TArray InInputsToUpdate, TSharedPtr InNewChoice) + { + if (!InNewChoice.IsValid()) + return; + + EHoudiniInputType NewInputType = UHoudiniInput::StringToInputType(*InNewChoice.Get()); + if (NewInputType != EHoudiniInputType::World) + { + Helper_CancelWorldSelection(InInputsToUpdate, DetailsPanelName); + } + + if (InInputsToUpdate.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputsToUpdate[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetBlueprintComponent* HAB = MainInput->GetTypedOuter(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Input Type"), + MainInput->GetOuter()); + + bool bBlueprintStructureModified = false; + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetInputType() == NewInputType) + continue; + + /* This causes multiple issues. It does not set reset the previous type variable to Invalid sometimes + and it causes re-cook infinitely after few undo changing type. + { + CurInput->SetInputType(NewInputType); + CurInput->Modify(); + } + */ + + { + // Cache the current input type for undo type changing (since new type becomes previous type after undo) + EHoudiniInputType PrevType = CurInput->GetPreviousInputType(); + CurInput->SetPreviousInputType(NewInputType); + + CurInput->Modify(); + CurInput->SetPreviousInputType(PrevType); + CurInput->SetInputType(NewInputType, bBlueprintStructureModified); // pass in false for 2nd parameter in order to avoid creating default curve if empty + } + CurInput->MarkChanged(true); + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + + } + + if (HAB) + { + if (bBlueprintStructureModified) + HAB->MarkAsBlueprintStructureModified(); + } + + }; + + UHoudiniInput* MainInput = InInputs[0]; + TArray>* SupportedChoices = nullptr; + UHoudiniAssetBlueprintComponent* HAC = MainInput->GetTypedOuter(); + if (HAC) + { + SupportedChoices = FHoudiniEngineEditor::Get().GetBlueprintInputTypeChoiceLabels(); + } + else + { + SupportedChoices = FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels(); + } + + // ComboBox : Input Type + TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBoxInputType; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(ComboBoxInputType, SComboBox>) + .OptionsSource(SupportedChoices) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels())[((int32)MainInput->GetInputType() - 1)]) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([=](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnSelChanged(InInputs, NewChoice); + }) + [ + SNew( STextBlock ) + .Text_Lambda([=]() + { + return GetInputText(MainInput); + }) + .Font( FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; +} + +void +FHoudiniInputDetails:: AddCurveInputCookOnChangeCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + + if (!MainInput || MainInput->GetInputType() != EHoudiniInputType::Curve) + return; + + auto IsCheckedCookOnChange = [MainInput]() + { + if (!MainInput) + return ECheckBoxState::Checked; + + return MainInput->GetCookOnCurveChange() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto CheckStateChangedCookOnChange = [InInputs](ECheckBoxState NewState) + { + bool bChecked = NewState == ECheckBoxState::Checked; + for (auto & NextInput : InInputs) + { + if (!NextInput) + continue; + + NextInput->SetCookOnCurveChange(bChecked); + } + }; + + // Checkbox : Trigger cook on input curve changed + TSharedPtr< SCheckBox > CheckBoxCookOnCurveChanged; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxCookOnCurveChanged, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookOnCurveChangedCheckbox", "Auto-update")) + .ToolTipText(LOCTEXT("CookOnCurveChangeCheckboxTip", "When checked, cook is triggered automatically when the curve is modified.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedCookOnChange, MainInput]() + { + return IsCheckedCookOnChange(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedCookOnChange](ECheckBoxState NewState) + { + return CheckStateChangedCookOnChange( NewState); + }) + ]; + +} + +void +FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current KeepWorldTransform state + auto IsCheckedKeepWorldTransform = [&](UHoudiniInput* InInput) + { + return InInput->GetKeepWorldTransform() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing KeepWorldTransform state + auto CheckStateChangedKeepWorldTransform = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (MainInput->GetKeepWorldTransform() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Keep World Transform"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetKeepWorldTransform() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetKeepWorldTransform(bNewState); + CurInput->MarkChanged(true); + } + }; + + + // Checkbox : Keep World Transform + TSharedPtr< SCheckBox > CheckBoxTranformType; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxTranformType, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("KeepWorldTransformCheckbox", "Keep World Transform")) + .ToolTipText(LOCTEXT("KeepWorldTransformCheckboxTip", "Set this Input's object_merge Transform Type to INTO_THIS_OBJECT. If unchecked, it will be set to NONE.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedKeepWorldTransform(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedKeepWorldTransform(InInputs, NewState); + }) + ]; + + // the checkbox is read only for geo inputs + if (MainInput->GetInputType() == EHoudiniInputType::Geometry) + CheckBoxTranformType->SetEnabled(false); + + // Checkbox is read only if the input is an object-path parameter + //if (MainInput->IsObjectPathParameter() ) + // CheckBoxTranformType->SetEnabled(false); +} + +void +FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current PackBeforeMerge state + auto IsCheckedPackBeforeMerge = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetPackBeforeMerge() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing PackBeforeMerge state + auto CheckStateChangedPackBeforeMerge = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetPackBeforeMerge() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetPackBeforeMerge() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetPackBeforeMerge(bNewState); + CurInput->MarkChanged(true); + } + }; + + TSharedPtr< SCheckBox > CheckBoxPackBeforeMerge; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxPackBeforeMerge, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "PackBeforeMergeCheckbox", "Pack Geometry before merging" ) ) + .ToolTipText( LOCTEXT( "PackBeforeMergeCheckboxTip", "Pack each separate piece of geometry before merging them into the input." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedPackBeforeMerge(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedPackBeforeMerge(InInputs, NewState); + }) + ]; +} + +void +FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current PackBeforeMerge state + auto IsCheckedImportAsReference= [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetImportAsReference() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing PackBeforeMerge state + auto CheckStateChangedImportAsReference= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetImportAsReference() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetImportAsReference() == bNewState) + continue; + + TArray * InputObjs = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (InputObjs) + { + // Mark all its input objects as changed to trigger recook. + for (auto CurInputObj : *InputObjs) + { + if (!CurInputObj || CurInputObj->IsPendingKill()) + continue; + + if (CurInputObj->GetImportAsReference() != bNewState) + CurInputObj->MarkChanged(true); + } + } + + CurInput->Modify(); + CurInput->SetImportAsReference(bNewState); + } + }; + + TSharedPtr< SCheckBox > CheckBoxImportAsReference; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxImportAsReference, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ImportInputAsRefCheckbox", "Import input as references")) + .ToolTipText(LOCTEXT("ImportInputAsRefCheckboxTip", "Import input objects as references. (Geometry, World and Asset input types only)")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedImportAsReference(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedImportAsReference(InInputs, NewState); + }) + ]; +} +void +FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current ExportLODs state + auto IsCheckedExportLODs = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportLODs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda returning a CheckState from the input's current ExportSockets state + auto IsCheckedExportSockets = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportSockets() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda returning a CheckState from the input's current ExportColliders state + auto IsCheckedExportColliders = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportColliders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing ExportLODs state + auto CheckStateChangedExportLODs = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportLODs() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export LODs"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportLODs() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportLODs(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + // Lambda for changing ExportSockets state + auto CheckStateChangedExportSockets = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportSockets() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Sockets"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportSockets() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportSockets(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + // Lambda for changing ExportColliders state + auto CheckStateChangedExportColliders = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportColliders() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Colliders"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportColliders() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportColliders(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + TSharedPtr< SCheckBox > CheckBoxExportLODs; + TSharedPtr< SCheckBox > CheckBoxExportSockets; + TSharedPtr< SCheckBox > CheckBoxExportColliders; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew(CheckBoxExportLODs, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportAllLOD", "Export LODs" ) ) + .ToolTipText( LOCTEXT( "ExportAllLODCheckboxTip", "If enabled, all LOD Meshes in this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportLODs(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportLODs(InInputs, NewState); + }) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( CheckBoxExportSockets, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportSockets", "Export Sockets" ) ) + .ToolTipText( LOCTEXT( "ExportSocketsTip", "If enabled, all Mesh Sockets in this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportSockets(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportSockets(InInputs, NewState); + }) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( CheckBoxExportColliders, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportColliders", "Export Colliders" ) ) + .ToolTipText( LOCTEXT( "ExportCollidersTip", "If enabled, collision geometry for this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportColliders(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportColliders(InInputs, NewState); + }) + ] + ]; +} + +void +FHoudiniInputDetails::AddGeometryInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool ) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + + // Lambda for changing ExportColliders state + auto SetGeometryInputObjectsCount = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& NewInputCount) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing the number of Geometry Input Objects"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry) == NewInputCount) + continue; + + CurInput->Modify(); + + CurInput->SetInputObjectsNumber(EHoudiniInputType::Geometry, NewInputCount); + CurInput->MarkChanged(true); + + // + if (GEditor) + GEditor->RedrawAllViewports(); + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + }; + + InVerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs, NumInputObjects]() + { + return SetGeometryInputObjectsCount(InInputs, NumInputObjects + 1); + }), + LOCTEXT("AddInput", "Adds a Geometry Input"), true) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs]() + { + return SetGeometryInputObjectsCount(InInputs, 0); + }), + LOCTEXT("EmptyInputs", "Removes All Inputs"), true) + ] + ]; + + for (int32 GeometryObjectIdx = 0; GeometryObjectIdx < NumInputObjects; GeometryObjectIdx++) + { + //UObject* InputObject = InParam.GetInputObject(Idx); + Helper_CreateGeometryWidget(CategoryBuilder, InInputs, GeometryObjectIdx, AssetThumbnailPool, InVerticalBox); + } +} + + + +// Create a single geometry widget for the given input object +void +FHoudiniInputDetails::Helper_CreateGeometryWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InGeometryObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox ) +{ + UHoudiniInput* MainInput = InInputs[0]; + + // Access the object used in the corresponding geometry input + UHoudiniInputObject* HoudiniInputObject = MainInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + UObject* InputObject = HoudiniInputObject ? HoudiniInputObject->GetObject() : nullptr; + + // Create thumbnail for this static mesh. + TSharedPtr StaticMeshThumbnail = MakeShareable( + new FAssetThumbnail(InputObject, 64, 64, AssetThumbnailPool)); + + // Lambda for adding new geometry input objects + auto UpdateGeometryObjectAt = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& AtIndex, UObject* InObject) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + if (!InObject || InObject->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing a Geometry Input Object"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + UObject* InputObject = CurInput->GetInputObjectAt(EHoudiniInputType::Geometry, AtIndex); + if (InObject == InputObject) + continue; + + UHoudiniInputObject* CurrentInputObjectWrapper = CurInput->GetHoudiniInputObjectAt(AtIndex); + if (CurrentInputObjectWrapper) + CurrentInputObjectWrapper->Modify(); + + CurInput->Modify(); + + CurInput->SetInputObjectAt(EHoudiniInputType::Geometry, AtIndex, InObject); + CurInput->MarkChanged(true); + + // TODO: Not needed? + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + }; + + // Drop Target: Static/Skeletal Mesh + TSharedPtr HorizontalBox = NULL; + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop_Lambda([]( const UObject* InObject) + { + return UHoudiniInput::IsObjectAcceptable(EHoudiniInputType::Geometry, InObject); + }) + .OnAssetDropped_Lambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt](UObject* InObject) + { + return UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, InObject); + }) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + // Thumbnail : Static Mesh + FText ParameterLabelText = FText::FromString(MainInput->GetLabel()); + + TSharedPtr< SBorder > StaticMeshThumbnailBorder; + HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(StaticMeshThumbnailBorder, SBorder) + .Padding(5.0f) + .OnMouseDoubleClick_Lambda([MainInput, InGeometryObjectIdx](const FGeometry&, const FPointerEvent&) + { + UObject* InputObject = MainInput->GetInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + if (GEditor && InputObject) + GEditor->EditObject(InputObject); + + return FReply::Handled(); + }) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(ParameterLabelText) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + StaticMeshThumbnailBorder->SetBorderImage(TAttribute::Create( + TAttribute::FGetter::CreateLambda([StaticMeshThumbnailBorder]() + { + if (StaticMeshThumbnailBorder.IsValid() && StaticMeshThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ) ) ); + + FText MeshNameText = FText::GetEmpty(); + if (InputObject) + MeshNameText = FText::FromString(InputObject->GetName()); + + + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add Combo box : Static Mesh + TSharedPtr StaticMeshComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SAssignNew(StaticMeshComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(MeshNameText) + ] + ] + ]; + + + StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( + [MainInput, InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt]() + { + TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); + UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); + + TArray< UFactory * > NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(DefaultObj), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda([InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) + { + if (StaticMeshComboButton.IsValid()) + { + StaticMeshComboButton->SetIsOpen(false); + UObject * Object = AssetData.GetAsset(); + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); + } + }), + FSimpleDelegate::CreateLambda([]() {})); + })); + + + // Add buttons + TSharedPtr ButtonHorizontalBox; + ComboAndButtonBox->AddSlot() + .FillHeight(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ButtonHorizontalBox, SHorizontalBox) + ]; + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add( TEXT( "Asset" ), MeshNameText ); + FText StaticMeshTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", + "Browse to '{Asset}' in Content Browser" ), Args ); + + // Button : Use selected in content browser + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton(FSimpleDelegate::CreateLambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt]() + { + if (GEditor) + { + TArray CBSelections; + GEditor->GetContentBrowserSelections(CBSelections); + + // Get the first selected static mesh object + UObject* Object = nullptr; + for (auto & CurAssetData : CBSelections) + { + if (CurAssetData.AssetClass != UStaticMesh::StaticClass()->GetFName()) + continue; + + Object = CurAssetData.GetAsset(); + break; + } + + if (Object && !Object->IsPendingKill()) + { + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); + } + } + }), TAttribute< FText >(LOCTEXT("GeometryInputUseSelectedAssetFromCB", "Use Selected Asset from Content Browser"))) + ]; + + // Button : Browse Static Mesh + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateLambda([MainInput, InGeometryObjectIdx]() + { + UObject* InputObject = MainInput->GetInputObjectAt(InGeometryObjectIdx); + if (GEditor && InputObject) + { + TArray Objects; + Objects.Add(InputObject); + GEditor->SyncBrowserToObjects(Objects); + } + }), + TAttribute< FText >( StaticMeshTooltip ) + ) + ]; + + // ButtonBox : Reset + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default static mesh" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked_Lambda( [UpdateGeometryObjectAt, InInputs, InGeometryObjectIdx]() + { + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, nullptr); + return FReply::Handled(); + }) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + // Insert/Delete/Duplicate + ButtonHorizontalBox->AddSlot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( + FExecuteAction::CreateLambda( [ InInputs, InGeometryObjectIdx, MainInput ]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: insert a Geometry Input Object"), + MainInput->GetOuter()); + // Insert + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->InsertInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + } + } ), + FExecuteAction::CreateLambda([MainInput, InInputs, InGeometryObjectIdx]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: delete a Geometry Input Object"), + MainInput->GetOuter()); + + // Delete + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->DeleteInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + + if (GEditor) + GEditor->RedrawAllViewports(); + } + } ), + FExecuteAction::CreateLambda([InInputs, InGeometryObjectIdx, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), + MainInput->GetOuter()); + + // Duplicate + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->DuplicateInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + } + } ) ) + ]; + + // TRANSFORM OFFSET EXPANDER + { + TSharedPtr ExpanderArrow; + TSharedPtr ExpanderImage; + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( ExpanderArrow, SButton ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ClickMethod( EButtonClickMethod::MouseDown ) + .Visibility( EVisibility::Visible ) + .OnClicked(FOnClicked::CreateLambda([InInputs, InGeometryObjectIdx, MainInput, &CategoryBuilder]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled();; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), + MainInput->GetOuter()); + + // Expand transform + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->OnTransformUIExpand(InGeometryObjectIdx); + } + + // TODO: Not needed? + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + })) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ] + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT("GeoInputTransform", "Transform Offset") ) + .ToolTipText( LOCTEXT( "GeoInputTransformTooltip", "Transform offset used for correction before sending the asset to Houdini" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + // Set delegate for image + ExpanderImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda( [=]() { + FName ResourceName; + if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx) ) + { + ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + return FEditorStyle::GetBrush( ResourceName ); + } ) ) ); + } + + // Lambda for changing the transform values + auto ChangeTransformOffsetAt = [&](const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex, const bool& DoChange, TArray InInputs) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputTransformChange", "Houdini Input: Changing Transform offset"), + InInputs[0]->GetOuter()); + + bool bChanged = true; + for (int Idx = 0; Idx < InInputs.Num(); Idx++) + { + if (!InInputs[Idx] || InInputs[Idx]->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObject = InInputs[Idx]->GetHoudiniInputObjectAt(AtIndex); + if (InputObject) + InputObject->Modify(); + + bChanged &= InInputs[Idx]->SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); + } + + if (bChanged && DoChange) + { + // Mark the values as changed to trigger an update + for (int Idx = 0; Idx < InInputs.Num(); Idx++) + { + InInputs[Idx]->MarkChanged(true); + } + } + else + { + // Cancel the transaction + Transaction.Cancel(); + } + }; + + // Get Visibility of reset buttons + bool bResetButtonVisiblePosition = false; + bool bResetButtonVisibleRotation = false; + bool bResetButtonVisibleScale = false; + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + FTransform* CurTransform = CurInput->GetTransformOffset(InGeometryObjectIdx); + if (!CurTransform) + continue; + + if (CurTransform->GetLocation() != FVector::ZeroVector) + bResetButtonVisiblePosition = true; + + FRotator Rotator = CurTransform->Rotator(); + if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) + bResetButtonVisibleRotation = true; + + if (CurTransform->GetScale3D() != FVector::OneVector) + bResetButtonVisibleScale = true; + } + + auto ChangeTransformOffsetUniformlyAt = [InGeometryObjectIdx, InInputs, ChangeTransformOffsetAt](const float & Val, const int32& PosRotScaleIndex) + { + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 0, true, InInputs); + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 1, true, InInputs); + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 2, true, InInputs); + }; + + // TRANSFORM OFFSET + if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) + { + // Position + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew(STextBlock) + .Text( LOCTEXT("GeoInputTranslate", "T") ) + .ToolTipText( LOCTEXT( "GeoInputTranslateTooltip", "Translate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .AllowSpin(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetX, InGeometryObjectIdx))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetY, InGeometryObjectIdx))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetZ, InGeometryObjectIdx))) + .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 0, true, InInputs); }) + .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 1, true, InInputs); }) + .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 2, true, InInputs); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([MainInput, ChangeTransformOffsetUniformlyAt, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 0); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + + // Rotation + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text( LOCTEXT("GeoInputRotate", "R") ) + .ToolTipText( LOCTEXT( "GeoInputRotateTooltip", "Rotate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SRotatorInputBox ) + .AllowSpin( true ) + .bColorAxisLabels( true ) + .Roll(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetRoll, InGeometryObjectIdx))) + .Pitch(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetPitch, InGeometryObjectIdx))) + .Yaw(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetYaw, InGeometryObjectIdx))) + .OnRollCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 0, true, InInputs); }) + .OnPitchCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 1, true, InInputs); }) + .OnYawCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 2, true, InInputs); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (Not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 1); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + + // Scale + bool bLocked = false; + if (HoudiniInputObject) + bLocked = HoudiniInputObject->IsUniformScaleLocked(); + + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "GeoInputScale", "S" ) ) + .ToolTipText( LOCTEXT( "GeoInputScaleTooltip", "Scale" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetX, InGeometryObjectIdx))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetY, InGeometryObjectIdx))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetZ, InGeometryObjectIdx))) + .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 0, true, InInputs); + }) + .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 1, true, InInputs); + }) + .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 2, true, InInputs); + }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + SNew(SHorizontalBox) + // Lock Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ToolTipText(HoudiniInputObject ? + LOCTEXT("GeoInputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the input object maintains its shape in each direction when scaled") : + LOCTEXT("GeoInputLockButtonToolTipNoObject", "No input object selected")) + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(bLocked ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([InInputs, MainInput, InGeometryObjectIdx, HoudiniInputObject, &CategoryBuilder]() + { + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + UHoudiniInputObject*CurInputObject = CurInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + CurInputObject->SwitchUniformScaleLock(); + } + + if (HoudiniInputObject) + { + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); + }) + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(1.0f, 2); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + } +} + +void +FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Houdini Asset Picker Widget + { + FMenuBuilder MenuBuilder = Helper_CreateHoudiniAssetPickerWidget(InInputs); + + VerticalBox->AddSlot() + .Padding(2.0f, 2.0f, 5.0f, 2.0f) + .AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + // Button : Clear Selection + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + auto IsClearButtonEnabled = [MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return false; + + TArray* AssetInputObjectsArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + + if (!AssetInputObjectsArray) + return false; + + if (AssetInputObjectsArray->Num() <= 0) + return false; + + return true; + }; + + FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChangeClear", "Houdini Input: Clearing asset input selection"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + TArray* AssetInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + if (!AssetInputObjectsArray) + continue; + + CurrentInput->Modify(); + + AssetInputObjectsArray->Empty(); + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }); + + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + // Button : Clear Selection + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("ClearSelection", "Clear Selection")) + .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) + .IsEnabled_Lambda(IsClearButtonEnabled) + .OnClicked(OnClearSelect) + ] + ]; + + // Do not enable select all/clear select when selection has been started and details are locked + //HorizontalBox->SetEnabled(!bDetailsLocked); + } + + +} + +void +FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs, TSharedPtr AssetThumbnailPool) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Curve); + + // lambda for inserting an input Houdini curve. + auto InsertAnInputCurve = [MainInput, &CategoryBuilder](const int32& NewInputCount) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetComponent* OuterHAC = Cast(MainInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + // Do not insert input object when the HAC does not finish cooking + EHoudiniAssetState CurrentHACState = OuterHAC->GetAssetState(); + if (CurrentHACState >= EHoudiniAssetState::PreCook && CurrentHACState<= EHoudiniAssetState::Processing) + return; + + // Clear the to be inserted object array, which records the pointers of the input objects to be inserted. + MainInput->LastInsertedInputs.Empty(); + // Record the pointer of the object to be inserted before transaction for undo the insert action. + bool bBlueprintStructureModified = false; + UHoudiniInputHoudiniSplineComponent* NewInput = MainInput->CreateHoudiniSplineInput(nullptr, true, false, bBlueprintStructureModified); + MainInput->LastInsertedInputs.Add(NewInput); + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction(FText::FromString("Modifying Houdini input: Adding curve input.")); + MainInput->Modify(); + + // Modify the MainInput. + MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType())->Add(NewInput); + + MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, NewInputCount); + + if (bBlueprintStructureModified) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([InsertAnInputCurve, NumInputObjects]() + { + return InsertAnInputCurve(NumInputObjects+1); + //return SetCurveInputObjectsCount(NumInputObjects+1); + }), + + LOCTEXT("AddInputCurve", "Adds a Curve Input"), true) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateLambda([InInputs, MainInput, &CategoryBuilder]() + { + TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + + // Detach all curves before deleting. + for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) + { + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast ((*CurveInputComponentArray)[n]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + } + + // Clear the insert objects buffer before transaction. + MainInput->LastInsertedInputs.Empty(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction(FText::FromString("Modifying Houdini Input: Delete curve inputs.")); + MainInput->Modify(); + + bool bBlueprintStructureModified = false; + + // actual delete. + for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) + { + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast ((*CurveInputComponentArray)[n]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + MainInput->RemoveSplineFromInputObject(HoudiniInput, bBlueprintStructureModified); + + MainInput->DeleteInputObjectAt(EHoudiniInputType::Curve, n); + } + + MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, 0); + + if (bBlueprintStructureModified) + { + UActorComponent* OuterComponent = Cast(MainInput->GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterComponent); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + }), + LOCTEXT("EmptyInputsCurve", "Removes All Curve Inputs"), true) + ] + + SHorizontalBox::Slot().FillWidth(80.f).MaxWidth(80.f) + [ + SNew(SButton) + .Text(LOCTEXT("ResetCurveOffsetStr", "Reset Offset")) + .OnClicked_Lambda([MainInput]()->FReply + { + MainInput->ResetDefaultCurveOffset(); + return FReply::Handled(); + }) + ] + ]; + + //UHoudiniSplineComponent* SplineCompBeingEdited = nullptr; + TSharedPtr HouSplineComponentVisualizer; + if (GUnrealEd) + { + TSharedPtr Visualizer = + GUnrealEd->FindComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); + + HouSplineComponentVisualizer = StaticCastSharedPtr(Visualizer); + } + + + for (int n = 0; n < NumInputObjects; n++) + { + Helper_CreateCurveWidget(CategoryBuilder, InInputs, n, AssetThumbnailPool ,VerticalBox, HouSplineComponentVisualizer); + } +} + +void +FHoudiniInputDetails::Helper_CreateCurveWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InCurveObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox, + TSharedPtr HouSplineComponentVisualizer) +{ + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetComponent * OuterHAC = Cast(MainInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + auto GetHoudiniSplineComponentAtIndex = [](UHoudiniInput * Input, int32 Index) + { + UHoudiniSplineComponent* FoundHoudiniSplineComponent = nullptr; + if (!Input || Input->IsPendingKill()) + return FoundHoudiniSplineComponent; + + // Get the TArray ptr to the curve objects in this input + TArray * CurveInputComponentArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + return FoundHoudiniSplineComponent; + + if (!CurveInputComponentArray->IsValidIndex(Index)) + return FoundHoudiniSplineComponent; + + // Access the object used in the corresponding Houdini curve input + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + FoundHoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + + return FoundHoudiniSplineComponent; + }; + + + // Get the TArray ptr to the curve objects in this input + TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + return; + + if (!CurveInputComponentArray->IsValidIndex(InCurveObjectIdx)) + return; + + // Access the object used in the corresponding Houdini curve input + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[InCurveObjectIdx]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent) + return; + + FString HoudiniSplineName = HoudiniSplineComponent->GetHoudiniSplineName(); + + // Editable label for the current Houdini curve + TSharedPtr LabelHorizontalBox; + VerticalBox->AddSlot() + .Padding(0, 2) + .AutoHeight() + [ + SAssignNew(LabelHorizontalBox, SHorizontalBox) + ]; + + + TSharedPtr LabelBlock; + LabelHorizontalBox->AddSlot() + .Padding(0, 15, 0, 2) + .MaxWidth(150.f) + .FillWidth(150.f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Left) + [ + SNew(SBox).HeightOverride(20.f).WidthOverride(200.f).VAlign(VAlign_Center) + [ + SAssignNew(LabelBlock, SEditableText).Text(FText::FromString(HoudiniSplineName)) + .OnTextCommitted_Lambda([HoudiniSplineComponent](FText NewText, ETextCommit::Type CommitType) + { + if (CommitType == ETextCommit::Type::OnEnter) + { + HoudiniSplineComponent->SetHoudiniSplineName(NewText.ToString()); + } + }) + ] + ]; + + // 'Editing...' TextBlock showing if this component is being edited + TSharedPtr EditingTextBlock; + LabelHorizontalBox->AddSlot() + .Padding(0, 15, 0, 2) + .MaxWidth(55.f) + .FillWidth(55.f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Left) + [ + SNew(SBox).HeightOverride(20.f).WidthOverride(75.f).VAlign(VAlign_Center) + [ + SAssignNew(EditingTextBlock, SCurveEditingTextBlock).Text(LOCTEXT("HoudiniCurveInputEditingLabel", "(editing...)")) + ] + ]; + + EditingTextBlock->HoudiniSplineComponent = HoudiniSplineComponent; + EditingTextBlock->HoudiniSplineComponentVisualizer = HouSplineComponentVisualizer; + + // Lambda for deleting the current curve input + auto DeleteHoudiniCurveAtIndex = [InInputs, InCurveObjectIdx, OuterHAC, CurveInputComponentArray, &CategoryBuilder]() + { + if (!OuterHAC|| OuterHAC->IsPendingKill()) + return; + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeDeleteACurve", "Houdini Input: Deleting a curve input"), + OuterHAC); + + int MainInputCurveArraySize = CurveInputComponentArray->Num(); + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + Input->Modify(); + + TArray* InputObjectArr = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!InputObjectArr) + continue; + + if (!InputObjectArr->IsValidIndex(InCurveObjectIdx)) + continue; + + if (MainInputCurveArraySize != InputObjectArr->Num()) + continue; + + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast((*InputObjectArr)[InCurveObjectIdx]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + return; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent) + return; + + // Detach the spline component before delete. + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + + // This input is marked changed when an input component is deleted. + Input->DeleteInputObjectAt(EHoudiniInputType::Curve, InCurveObjectIdx); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + // Add delete button UI + LabelHorizontalBox->AddSlot().Padding(0, 2, 0, 2).HAlign(HAlign_Right).VAlign(VAlign_Bottom).AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([DeleteHoudiniCurveAtIndex]() + { + return DeleteHoudiniCurveAtIndex(); + })) + ]; + + + TSharedPtr HorizontalBox = NULL; + VerticalBox->AddSlot().Padding(0, 2).AutoHeight()[SAssignNew(HorizontalBox, SHorizontalBox)]; + + // Closed check box + // Lambda returning a closed state + auto IsCheckedClosedCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsClosedCurve() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing Closed state + auto CheckStateChangedClosedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeClosed", "Houdini Input: Changing Curve Closed"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->IsClosedCurve() == bNewState) + continue; + + HoudiniSplineComponent->Modify(); + + HoudiniSplineComponent->SetClosedCurve(bNewState); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Add Closed check box UI + TSharedPtr CheckBoxClosed = NULL; + HorizontalBox->AddSlot().Padding(0, 2).AutoWidth() + [ + SAssignNew(CheckBoxClosed, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("ClosedCurveCheckBox", "Closed")) + .ToolTipText(LOCTEXT("ClosedCurveCheckboxTip", "Close this input curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedClosedCurve]() + { + return IsCheckedClosedCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedClosedCurve](ECheckBoxState NewState) + { + return CheckStateChangedClosedCurve(NewState); + }) + ]; + + // Reversed check box + // Lambda returning a reversed state + auto IsCheckedReversedCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsReversed() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing reversed state + auto CheckStateChangedReversedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeReversed", "Houdini Input: Changing Curve Reversed"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->IsReversed() == bNewState) + continue; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetReversed(bNewState); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Add reversed check box UI + TSharedPtr CheckBoxReversed = NULL; + HorizontalBox->AddSlot() + .Padding(2, 2) + .AutoWidth() + [ + SAssignNew(CheckBoxReversed, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("ReversedCurveCheckBox", "Reversed")) + .ToolTipText(LOCTEXT("ReversedCurveCheckboxTip", "Reverse this input curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedReversedCurve]() + { + return IsCheckedReversedCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedReversedCurve](ECheckBoxState NewState) + { + return CheckStateChangedReversedCurve(NewState); + }) + ]; + + // Visible check box + // Lambda returning a visible state + auto IsCheckedVisibleCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsHoudiniSplineVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing visible state + auto CheckStateChangedVisibleCurve = [GetHoudiniSplineComponentAtIndex, InInputs, OuterHAC, InCurveObjectIdx](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent) + continue; + + if (HoudiniSplineComponent->IsHoudiniSplineVisible() == bNewState) + return; + + HoudiniSplineComponent->SetHoudiniSplineVisible(bNewState); + } + + if (GEditor) + GEditor->RedrawAllViewports(); + + }; + + // Add visible check box UI + TSharedPtr CheckBoxVisible = NULL; + HorizontalBox->AddSlot().Padding(2, 2).AutoWidth() + [ + SAssignNew(CheckBoxVisible, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("VisibleCurveCheckBox", "Visible")) + .ToolTipText(LOCTEXT("VisibleCurveCheckboxTip", "Set the visibility of this curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedVisibleCurve]() + { + return IsCheckedVisibleCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedVisibleCurve](ECheckBoxState NewState) + { + return CheckStateChangedVisibleCurve(NewState); + }) + ]; + + // Curve type comboBox + // Lambda for changing Houdini curve type + auto OnCurveTypeChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + if (!InNewChoice.IsValid()) + return; + + EHoudiniCurveType NewInputType = UHoudiniInput::StringToHoudiniCurveType(*InNewChoice.Get()); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeType", "Houdini Input: Changing Curve Type"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->GetCurveType() == NewInputType) + continue; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetCurveType(NewInputType); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Lambda for getting Houdini curve type + auto GetCurveTypeText = [HoudiniSplineComponent]() + { + return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(HoudiniSplineComponent->GetCurveType())); + }; + + // Add curve type combo box UI + TSharedPtr CurveTypeHorizontalBox; + VerticalBox->AddSlot() + .Padding(0, 2, 2, 0) + .AutoHeight() + [ + SAssignNew(CurveTypeHorizontalBox, SHorizontalBox) + ]; + + // Add curve type label UI + CurveTypeHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() + [ + SNew(STextBlock).Text(LOCTEXT("CurveTypeText", "Curve Type ")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveType; + CurveTypeHorizontalBox->AddSlot() + .Padding(2, 2, 5, 2) + .FillWidth(150.f) + .MaxWidth(150.f) + [ + SAssignNew(ComboBoxCurveType, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels())[(int)HoudiniSplineComponent->GetCurveType()]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnCurveTypeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnCurveTypeChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([GetCurveTypeText]() + { + return GetCurveTypeText(); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + // Houdini curve method combo box + // Lambda for changing Houdini curve method + auto OnCurveMethodChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + if (!InNewChoice.IsValid()) + return; + + EHoudiniCurveMethod NewInputMethod = UHoudiniInput::StringToHoudiniCurveMethod(*InNewChoice.Get()); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeMethod", "Houdini Input: Changing Curve Method"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->GetCurveMethod() == NewInputMethod) + return; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetCurveMethod(NewInputMethod); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Lambda for getting Houdini curve method + auto GetCurveMethodText = [HoudiniSplineComponent]() + { + return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(HoudiniSplineComponent->GetCurveMethod())); + }; + + // Add curve method combo box UI + TSharedPtr< SHorizontalBox > CurveMethodHorizontalBox; + VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(CurveMethodHorizontalBox, SHorizontalBox)]; + + // Add curve method label UI + CurveMethodHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() + [ + SNew(STextBlock).Text(LOCTEXT("CurveMethodText", "Curve Method ")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveMethod; + CurveMethodHorizontalBox->AddSlot().Padding(2, 2, 5, 2).FillWidth(150.f).MaxWidth(150.f) + [ + SAssignNew(ComboBoxCurveMethod, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels())[(int)HoudiniSplineComponent->GetCurveMethod()]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnCurveMethodChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnCurveMethodChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([GetCurveMethodText]() + { + return GetCurveMethodText(); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + auto BakeInputCurveLambda = [](TArray Inputs, int32 Index, bool bBakeToBlueprint) + { + for (auto & NextInput : Inputs) + { + if (!NextInput || NextInput->IsPendingKill()) + continue; + + UHoudiniAssetComponent * OuterHAC = Cast(NextInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + continue; + + AActor * OwnerActor = OuterHAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + continue; + + TArray * CurveInputComponentArray = NextInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + continue; + + if (!CurveInputComponentArray->IsValidIndex(Index)) + continue; + + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + FHoudiniPackageParams PackageParams; + PackageParams.BakeFolder = OuterHAC->BakeFolder.Path; + PackageParams.HoudiniAssetName = OuterHAC->GetName(); + PackageParams.GeoId = NextInput->GetAssetNodeId(); + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.ObjectId = Index; + PackageParams.ObjectName = OwnerActor->GetName() + "InputHoudiniSpline" + FString::FromInt(Index); + + if (bBakeToBlueprint) + { + FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( + HoudiniSplineComponent, + PackageParams, + OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); + } + else + { + FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + HoudiniSplineComponent, + PackageParams, + OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); + } + } + + return FReply::Handled(); + }; + + // Add input curve bake button + TSharedPtr< SHorizontalBox > InputCurveBakeHorizontalBox; + VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(InputCurveBakeHorizontalBox, SHorizontalBox)]; + VerticalBox->AddSlot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(110.f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("HoudiniInputCurveBakeToActorButton", "Bake to Actor")) + .IsEnabled(true) + .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() + { + return BakeInputCurveLambda(InInputs, InCurveObjectIdx, false); + }) + .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToActorButtonToolTip", "Bake this input curve to Actor")) + ] + + + SHorizontalBox::Slot().MaxWidth(110.f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("HoudiniInputCurveBakeToBPButton", "Bake to Blueprint")) + .IsEnabled(true) + .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() + { + return BakeInputCurveLambda(InInputs, InCurveObjectIdx, true); + }) + .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToBPButtonToolTip", "Bake this input curve to Blueprint")) + ] + ]; + + // Do we actually need to set enable the UI components? + if (MainInput->GetInputType() == EHoudiniInputType::Curve) + { + LabelBlock->SetEnabled(true); + CheckBoxClosed->SetEnabled(true); + CheckBoxReversed->SetEnabled(true); + CheckBoxVisible->SetEnabled(true); + ComboBoxCurveType->SetEnabled(true); + ComboBoxCurveMethod->SetEnabled(true); + } + else + { + LabelBlock->SetEnabled(false); + CheckBoxClosed->SetEnabled(false); + CheckBoxReversed->SetEnabled(false); + CheckBoxVisible->SetEnabled(false); + ComboBoxCurveType->SetEnabled(false); + ComboBoxCurveMethod->SetEnabled(false); + } +} + +void +FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + // Lambda returning a CheckState from the input's current KeepWorldTransform state + auto IsCheckedUpdateInputLandscape = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetUpdateInputLandscape() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing KeepWorldTransform state + auto CheckStateChangedUpdateInputLandscape = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangedUpdate", "Houdini Input: Changing Keep World Transform"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (bNewState == CurInput->GetUpdateInputLandscape()) + continue; + + CurInput->Modify(); + + UHoudiniAssetComponent* HAC = Cast(CurInput->GetOuter()); + if (!HAC) + continue; + + TArray* LandscapeInputObjects = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (!LandscapeInputObjects) + continue; + + for (UHoudiniInputObject* NextInputObj : *LandscapeInputObjects) + { + UHoudiniInputLandscape* CurrentInputLandscape = Cast(NextInputObj); + if (!CurrentInputLandscape) + continue; + + ALandscapeProxy* CurrentInputLandscapeProxy = CurrentInputLandscape->GetLandscapeProxy(); + if (!CurrentInputLandscapeProxy) + continue; + + if (bNewState) + { + // We want to update this landscape data directly, start by backing it up to image files in the temp folder + FString BackupBaseName = HAC->TemporaryCookFolder.Path + + TEXT("/") + + CurrentInputLandscapeProxy->GetName() + + TEXT("_") + + HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); + + // We need to cache the input landscape to a file + FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(BackupBaseName, CurrentInputLandscapeProxy); + + // Cache its transform on the input + CurrentInputLandscape->CachedInputLandscapeTraqnsform = CurrentInputLandscapeProxy->ActorToWorld(); + + HAC->SetMobility(EComponentMobility::Static); + CurrentInputLandscapeProxy->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + } + else + { + // We are not updating this input landscape anymore, detach it and restore its backed-up values + CurrentInputLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + // Restore the input landscape's backup data + FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(CurrentInputLandscapeProxy); + + // Reapply the source Landscape's transform + CurrentInputLandscapeProxy->SetActorTransform(CurrentInputLandscape->CachedInputLandscapeTraqnsform); + + // TODO: + // Clear the input obj map? + } + } + + CurInput->bUpdateInputLandscape = (NewState == ECheckBoxState::Checked); + CurInput->MarkChanged(true); + } + }; + + // CheckBox : Update Input Landscape Data + TSharedPtr< SCheckBox > CheckBoxUpdateInput; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew( CheckBoxUpdateInput, SCheckBox).Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) + .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedUpdateInputLandscape, MainInput]() + { + return IsCheckedUpdateInputLandscape(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedUpdateInputLandscape, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedUpdateInputLandscape(InInputs, NewState); + }) + ]; + + // Actor picker: Landscape. + FMenuBuilder MenuBuilder = Helper_CreateLandscapePickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + + // Checkboxes : Export landscape as Heightfield/Mesh/Points + { + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAs", "Export Landscape As")) + .ToolTipText(LOCTEXT("LandscapeExportAsToolTip", "Choose the type of data you want the ladscape to be exported to:\n * Heightfield\n * Mesh\n * Points")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr ButtonOptionsPanel; + VerticalBox->AddSlot().Padding(5, 2, 5, 2).AutoHeight() + [ + SAssignNew(ButtonOptionsPanel, SUniformGridPanel) + ]; + + auto IsCheckedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) + { + if (!Input || Input->IsPendingKill()) + return ECheckBoxState::Unchecked; + + if (Input->GetLandscapeExportType() == LandscapeExportType) + return ECheckBoxState::Checked; + else + return ECheckBoxState::Unchecked; + }; + + auto CheckStateChangedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) + { + if (!Input || Input->IsPendingKill()) + return false; + + if (Input->GetLandscapeExportType() == LandscapeExportType) + return false; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changed Landscape export type."), + Input->GetOuter()); + Input->Modify(); + + Input->SetLandscapeExportType(LandscapeExportType); + Input->SetHasLandscapeExportTypeChanged(true); + Input->MarkChanged(true); + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return true; + + for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) + { + if (!NextInputObj) + continue; + NextInputObj->MarkChanged(true); + } + + return true; + }; + + // Heightfield + FText HeightfieldTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a heightfield."); + ButtonOptionsPanel->AddSlot(0, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Start") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Heightfield); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for(auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Heightfield); + }) + .ToolTipText(HeightfieldTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("ClassIcon.LandscapeComponent")) + ] + + SHorizontalBox::Slot() + .FillWidth(1.f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsHeightfieldCheckbox", "Heightfield")) + .ToolTipText(HeightfieldTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + + // Mesh + FText MeshTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a mesh."); + ButtonOptionsPanel->AddSlot(1, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Mesh); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for (auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Mesh); + }) + .ToolTipText(MeshTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("ClassIcon.StaticMeshComponent")) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsMeshCheckbox", "Mesh")) + .ToolTipText(MeshTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + + // Points + FText PointsTooltip = LOCTEXT("LandscapeExportAsPointsTooltip", "If enabled, the landscape will be exported to Houdini as points."); + ButtonOptionsPanel->AddSlot(2, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.End") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Points); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for (auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Points); + }) + .ToolTipText(PointsTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("Mobility.Static")) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsPointsCheckbox", "Points")) + .ToolTipText(PointsTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + } + + // CheckBox : Export selected components only + { + TSharedPtr< SCheckBox > CheckBoxExportSelected; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportSelected, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeSelectedCheckbox", "Export Selected Landscape Components Only")) + .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, only the selected Landscape Components will be exported.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportSelectionOnly ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportSelectionOnly", "Houdini Input: Changing Landscape export only selected component."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportSelectionOnly) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportSelectionOnly = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + } + + // Checkbox: auto select components + { + TSharedPtr< SCheckBox > CheckBoxAutoSelectComponents; + VerticalBox->AddSlot().Padding(10, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxAutoSelectComponents, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AutoSelectComponentCheckbox", "Auto-select component in asset bounds")) + .ToolTipText(LOCTEXT("AutoSelectComponentCheckboxTooltip", "If enabled, when no Landscape components are curremtly selected, the one within the asset's bounding box will be exported.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeAutoSelectComponent ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeAutoSelectComponent", "Houdini Input: Changing Landscape input auto-selects components."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeAutoSelectComponent) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeAutoSelectComponent = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + // Enable only when exporting selection or when exporting heighfield (for now) + bool bEnable = false; + for (auto CurrentInput : InInputs) + { + if (!MainInput->bLandscapeExportSelectionOnly) + continue; + + bEnable = true; + break; + } + CheckBoxAutoSelectComponents->SetEnabled(bEnable); + } + + + // The following checkbox are only added when not in heightfield mode + if (MainInput->LandscapeExportType != EHoudiniLandscapeExportType::Heightfield) + { + // Checkbox : Export materials + { + TSharedPtr< SCheckBox > CheckBoxExportMaterials; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportMaterials, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeMaterialsCheckbox", "Export Landscape Materials")) + .ToolTipText(LOCTEXT("LandscapeMaterialsTooltip", "If enabled, the landscape materials will be exported with it.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportMaterials", "Houdini Input: Changing Landscape input export materials."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportMaterials) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportMaterials = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportMaterials->SetEnabled(false); + */ + } + + // Checkbox : Export Tile UVs + { + TSharedPtr< SCheckBox > CheckBoxExportTileUVs; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportTileUVs, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeTileUVsCheckbox", "Export Landscape Tile UVs")) + .ToolTipText(LOCTEXT("LandscapeTileUVsTooltip", "If enabled, UVs will be exported separately for each Landscape tile.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportTileUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportTileUVs", "Houdini Input: Changing Landscape export tile UVs."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportTileUVs) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportTileUVs = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportTileUVs->SetEnabled(false); + */ + } + + // Checkbox : Export normalized UVs + { + TSharedPtr< SCheckBox > CheckBoxExportNormalizedUVs; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportNormalizedUVs, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeNormalizedUVsCheckbox", "Export Landscape Normalized UVs")) + .ToolTipText(LOCTEXT("LandscapeNormalizedUVsTooltip", "If enabled, landscape UVs will be exported in [0, 1].")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportNormalizedUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Changing Landscape export normalized UVs."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportNormalizedUVs) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportNormalizedUVs = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportNormalizedUVs->SetEnabled(false); + */ + } + + // Checkbox : Export lighting + { + TSharedPtr< SCheckBox > CheckBoxExportLighting; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportLighting, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeLightingCheckbox", "Export Landscape Lighting")) + .ToolTipText(LOCTEXT("LandscapeLightingTooltip", "If enabled, lightmap information will be exported with the landscape.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportLighting", "Houdini Input: Changing Landscape export lighting."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportLighting) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportLighting = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportLighting->SetEnabled(false); + */ + } + + } + + // Button : Recommit + { + auto OnButtonRecommitClicked = [InInputs]() + { + for (auto CurrentInput : InInputs) + { + TArray* LandscapeInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); + if (!LandscapeInputObjectsArray) + continue; + + for (UHoudiniInputObject* NextLandscapeInput : *LandscapeInputObjectsArray) + { + if (!NextLandscapeInput) + continue; + + NextLandscapeInput->MarkChanged(true); + } + + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1, 2, 4, 2) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("LandscapeInputRecommit", "Recommit Landscape")) + .ToolTipText(LOCTEXT("LandscapeInputRecommitTooltip", "Recommits the Landscape to Houdini.")) + .OnClicked_Lambda(OnButtonRecommitClicked) + ] + ]; + } + + + // Button : Clear Selection + { + auto IsClearButtonEnabled = [MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return false; + + if (MainInput->GetInputType() != EHoudiniInputType::Landscape) + return false; + + TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); + if (!MainInputObjectsArray) + return false; + + if (MainInputObjectsArray->Num() <= 0) + return false; + + return true; + }; + + auto OnButtonClearClicked = [InInputs]() + { + if (InInputs.Num() <= 0) + return FReply::Handled(); + + UHoudiniInput * MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + if (MainInput->GetInputType() != EHoudiniInputType::Landscape) + return FReply::Handled(); + + TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); + if (!MainInputObjectsArray) + return FReply::Handled(); + + if (MainInputObjectsArray->Num() <= 0) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Clearing landscape input."), + MainInput->GetOuter()); + + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + TArray* LandscapeInputObjectsArray = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (!LandscapeInputObjectsArray) + continue; + + if (LandscapeInputObjectsArray->Num() <= 0) + continue; + + CurInput->MarkChanged(true); + CurInput->Modify(); + + LandscapeInputObjectsArray->Empty(); + } + + return FReply::Handled(); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1, 2, 4, 2) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("ClearSelection", "Clear Selection")) + .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) + .IsEnabled_Lambda(IsClearButtonEnabled) + .OnClicked_Lambda(OnButtonClearClicked) + ] + ]; + } +} + +/* +FMenuBuilder +FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArray& InInputs, const TAttribute& HeadingText, const bool& bShowCurrentSelectionSection) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + + // Filters are only based on the MainInput + auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + if (!Actor->IsA()) + return false; + + ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); + if (!LandscapeProxy) + return false; + + // Get the landscape's actor + AActor* OwnerActor = LandscapeProxy->GetOwner(); + + // Get our Actor + UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); + AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; + + // TODO: FIX ME! + // IF the landscape is owned by ourself, skip it! + if (OwnerActor == MyOwner) + return false; + + return true; + }; + + auto OnShouldFilterWorld = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + const TArray* InputObjects = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurInputObject : *InputObjects) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + AActor* CurActor = Cast(CurInputObject->GetObject()); + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + auto OnShouldFilterHoudiniAsset = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + // But not our own Asset Actor + if (const USceneComponent* RootComp = Cast(InInput->GetOuter())) + { + if (RootComp && Cast(RootComp->GetOwner()) != Actor) + return true; + } + + return false; + }; + + auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape, OnShouldFilterWorld, OnShouldFilterHoudiniAsset](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + switch (MainInput->GetInputType()) + { + case EHoudiniInputType::Landscape: + return OnShouldFilterLandscape(Actor, MainInput); + case EHoudiniInputType::World: + return OnShouldFilterWorld(Actor, MainInput); + case EHoudiniInputType::Asset: + return OnShouldFilterHoudiniAsset(Actor, MainInput); + default: + return true; + } + + return false; + }; + + + // Selection uses the input arrays + auto OnLandscapeSelected = [](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + ALandscapeProxy* LandscapeProxy = Cast(Actor); + if (!LandscapeProxy) + return; + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return; + + LandscapeInputObjectsArray->Empty(); + + FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); + + // Create a Houdini Input Object. + UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( + LandscapeProxy, Input, LandscapeName.ToString()); + + UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); + LandscapeInput->MarkChanged(true); + + LandscapeInputObjectsArray->Add(LandscapeInput); + Input->MarkChanged(true); + }; + + auto OnHoudiniAssetActorSelected = [](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); + if (!HoudiniAssetActor) + return; + + TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!AssetInputObjectsArray) + return; + + AssetInputObjectsArray->Empty(); + + FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); + + // Create a Houdini Asset Input Object + UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); + + UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); + AssetInput->MarkChanged(true); + + AssetInputObjectsArray->Add(AssetInput); + Input->MarkChanged(true); + }; + + auto OnWorldSelected = [](AActor* Actor, UHoudiniInput* Input) + { + // Do Nothing + }; + + auto OnActorSelected = [OnLandscapeSelected, OnWorldSelected, OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) + { + for (auto& CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + return; + + switch (CurInput->GetInputType()) + { + case EHoudiniInputType::Landscape: + return OnLandscapeSelected(Actor, CurInput); + case EHoudiniInputType::World: + return OnWorldSelected(Actor, CurInput); + case EHoudiniInputType::Asset: + return OnHoudiniAssetActorSelected(Actor, CurInput); + default: + return; + } + } + + return; + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + if (bShowCurrentSelectionSection) + { + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + } + + + MenuBuilder.BeginSection(NAME_None, HeadingText); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} +*/ + + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterHoudiniAsset = [InInputs](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + // But not our selected Asset Actor + for (auto & NextSelectedInput : InInputs) + { + if (!NextSelectedInput) + continue; + + const USceneComponent* RootComp = Cast(NextSelectedInput->GetOuter()); + if (RootComp && Cast(RootComp->GetOwner()) == Actor) + return false; + + } + + return true; + }; + + // Filters are only based on the MainInput + auto OnShouldFilterActor = [MainInput, OnShouldFilterHoudiniAsset](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + return OnShouldFilterHoudiniAsset(Actor); + }; + + auto OnHoudiniAssetActorSelected = [OnShouldFilterHoudiniAsset](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || Actor->IsPendingKill() || !Input || Input->IsPendingKill()) + return; + + AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); + if (!HoudiniAssetActor) + return; + + // Make sure that the actor is valid for this input + if (!OnShouldFilterHoudiniAsset(Actor)) + return; + + TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + if (!AssetInputObjectsArray) + return; + + FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); + + // Create a Houdini Asset Input Object + UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); + + UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); + AssetInput->MarkChanged(true); + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniAssetInputChange", "Houdini Input: Selecting an asset input"), + Input->GetOuter()); + + Input->Modify(); + + AssetInputObjectsArray->Empty(); + AssetInputObjectsArray->Add(AssetInput); + Input->MarkChanged(true); + }; + + auto OnActorSelected = [OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) + { + for (auto& CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + return; + + OnHoudiniAssetActorSelected(Actor, CurInput); + } + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + // Show current selection + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("AssetInputSelectableActors", "Houdini Assets")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + if (!Actor->IsA()) + return false; + + ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); + if (!LandscapeProxy) + return false; + + // Get the landscape's parent actor + // We need to get the AttachParent on the root componet, GteOwner will not return the parent actor! + AActor* OwnerActor = nullptr; + USceneComponent* RootComponent = LandscapeProxy->GetRootComponent(); + if (RootComponent && !RootComponent->IsPendingKill()) + OwnerActor = RootComponent->GetAttachParent() ? RootComponent->GetAttachParent()->GetOwner() : LandscapeProxy->GetOwner(); + + // Get our Actor + UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); + AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; + + // IF the landscape is owned by ourself, skip it! + if (OwnerActor && OwnerActor == MyOwner) + { + // ... buuuut we dont want to filter input landscapes that have the "Update Input Landscape Data" option enabled + // (and are, therefore, outputs as well) + for (int32 Idx = 0; Idx < MyHAC->GetNumInputs(); Idx++) + { + UHoudiniInput* CurrentInput = MyHAC->GetInputAt(Idx); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) + continue; + + if (!CurrentInput->GetUpdateInputLandscape()) + continue; + + // Don't filter our input landscapes + ALandscapeProxy* UpdatedInputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); + if (LandscapeProxy == UpdatedInputLandscape) + return true; + } + + return false; + } + + return true; + }; + + // Filters are only based on the MainInput + auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + return OnShouldFilterLandscape(Actor, MainInput); + }; + + // Selection uses the input arrays + auto OnLandscapeSelected = [OnShouldFilterLandscape](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + // Make sure that the actor is valid for this input + if (!OnShouldFilterLandscape(Actor, Input)) + return; + + ALandscapeProxy* LandscapeProxy = Cast(Actor); + if (!LandscapeProxy) + return; + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return; + + LandscapeInputObjectsArray->Empty(); + + FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); + + // Create a Houdini Input Object. + UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( + LandscapeProxy, Input, LandscapeName.ToString()); + + UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); + LandscapeInput->MarkChanged(true); + + LandscapeInputObjectsArray->Add(LandscapeInput); + Input->MarkChanged(true); + }; + + auto OnActorSelected = [OnLandscapeSelected](AActor* Actor, TArray InInputs) + { + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeSelections", "Houdini Input: Selecting input landscape."), + MainInput->GetOuter()); + + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + OnLandscapeSelected(Actor, CurInput); + } + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + // Show current selection + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("LandscapeInputSelectableActors", "Landscapes")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterWorld = [MainInput](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + const TArray* InputObjects = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurInputObject : *InputObjects) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + AActor* CurActor = Cast(CurInputObject->GetObject()); + if (!CurActor || CurActor->IsPendingKill()) + { + // See if the input object is a HAC, if it is, get its parent actor + UHoudiniAssetComponent* CurHAC = Cast(CurInputObject->GetObject()); + if (CurHAC && !CurHAC->IsPendingKill()) + CurActor = CurHAC->GetOwner(); + } + + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + auto OnWorldSelected = [](AActor* Actor) + { + // Do Nothing + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterWorld); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputSelectedActors", "Currently Selected Actors")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnWorldSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilter = [MainInput](const AActor* const Actor) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + const TArray* BoundObjects = MainInput->GetBoundSelectorObjectArray(); + if (!BoundObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurActor : *BoundObjects) + { + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + + auto OnSelected = [](AActor* Actor) + { + // Do Nothing + }; + + FMenuBuilder MenuBuilder(true, nullptr); + MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputBoundSelectors", "Bound Selectors")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(FOnShouldFilterActor::CreateLambda(OnShouldFilter)); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +void +FHoudiniInputDetails::AddWorldInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + const IDetailsView* DetailsView) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); + + // Get the details view name and locked status + bool bDetailsLocked = false; + FName DetailsPanelName = "LevelEditorSelectionDetails"; + if (DetailsView) + { + DetailsPanelName = DetailsView->GetIdentifier(); + if (DetailsView->IsLocked()) + bDetailsLocked = true; + } + + // Check of we're in bound selector mode + bool bIsBoundSelector = MainInput->IsWorldInputBoundSelector(); + + // Button : Start Selection / Use current selection + refresh + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + //auto ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); + //auto ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); + FText ButtonLabel; + FText ButtonTooltip; + if (!bIsBoundSelector) + { + // Button : Start Selection / Use current selection + if (bDetailsLocked) + { + ButtonLabel = LOCTEXT("WorldInputUseCurrentSelection", "Use Current Selection (Unlocks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputUseCurrentSelectionTooltip", "Fill the asset's input with the current selection and unlocks the Details Panel."); + } + else + { + ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); + } + /* + FOnClicked OnSelectActors = FOnClicked::CreateStatic( + &FHoudiniInputDetails::Helper_OnButtonClickSelectActors, InInputs, DetailsPanelName); + */ + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ButtonLabel) + .ToolTipText(ButtonTooltip) + //.OnClicked(OnSelectActors) + .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() + { + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName); + }) + + ] + ]; + } + else + { + // Button : Start Selection / Use current selection as Bound selector + if (bDetailsLocked) + { + ButtonLabel = LOCTEXT("WorldInputUseSelectionAsBoundSelector", "Use Selection as Bound Selector (Unlocks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputUseSelectionAsBoundSelectorTooltip", "Fill the asset's input with all the actors contains in the current's selection bound, and unlocks the Details Panel."); + } + else + { + ButtonLabel = LOCTEXT("WorldInputStartBoundSelection", "Start Bound Selection (Locks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputStartBoundSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that will be used as bounds."); + } + + /* + FOnClicked OnSelectBounds = FOnClicked::CreateStatic( + &FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector, InInputs, DetailsPanelName); + */ + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ButtonLabel) + .ToolTipText(ButtonTooltip) + //.OnClicked(OnSelectBounds) + .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() + { + return Helper_OnButtonClickUseSelectionAsBoundSelector(CategoryBuilder, InInputs, DetailsPanelName); + }) + ] + ]; + } + } + + // Button : Select All + Clear Selection + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FOnClicked OnSelectAll = FOnClicked::CreateLambda([InInputs, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputSelectedAll", "Houdini Input: Selecting all actor in the current world"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // Get the parent component/actor/world of the current input + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + UWorld* MyWorld = CurrentInput->GetWorld(); + + TArray NewSelectedActors; + for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) + { + AActor *CurrentActor = *ActorItr; + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + NewSelectedActors.Add(CurrentActor); + } + + CurrentInput->Modify(); + + bool bHasChanged = CurrentInput->UpdateWorldSelection(NewSelectedActors); + } + + return FReply::Handled(); + }); + + FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput, &CategoryBuilder]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + const bool bMainInputBoundSelection = MainInput->IsWorldInputBoundSelector(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputClear", "Houdini Input: Clearing world input selection"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + // Do nothing if the current input has different selector settings from the main input + if (CurrentInput->IsWorldInputBoundSelector() != bMainInputBoundSelection) + continue; + + CurrentInput->Modify(); + + if (CurrentInput->IsWorldInputBoundSelector()) + { + CurrentInput->SetBoundSelectorObjectsNumber(0); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + else + { + TArray EmptySelection; + bool bHasChanged = CurrentInput->UpdateWorldSelection(EmptySelection); + } + } + + return FReply::Handled(); + }); + + FText ClearSelectionLabel; + FText ClearSelectionTooltip; + if (bIsBoundSelector) + { + ClearSelectionLabel = LOCTEXT("ClearBoundSelection", "Clear Bound Selection"); + ClearSelectionTooltip = LOCTEXT("ClearBoundSelectionTooltip", "Clear the input's current bound selection."); + } + else + { + ClearSelectionLabel = LOCTEXT("ClearSelection", "Clear Selection"); + ClearSelectionTooltip = LOCTEXT("ClearSelectionTooltip", "Clear the input's current selection."); + } + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + // Button : SelectAll + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("WorldInputSelectAll", "Select All")) + .ToolTipText(LOCTEXT("WorldInputSelectAll", "Fill the asset's input with all actors.")) + .OnClicked(OnSelectAll) + .IsEnabled(!bIsBoundSelector) + ] + + SHorizontalBox::Slot() + [ + // Button : Clear Selection + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ClearSelectionLabel) + .ToolTipText(ClearSelectionTooltip) + .OnClicked(OnClearSelect) + ] + ]; + + // Do not enable select all/clear select when selection has been started and details are locked + HorizontalBox->SetEnabled(!bDetailsLocked); + } + + // Checkbox: Bound Selector + { + // Lambda returning a CheckState from the input's current bound selector state + auto IsCheckedBoundSelector = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->IsWorldInputBoundSelector() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing bound selector state + auto CheckStateChangedIsBoundSelector = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeBoungSelector", "Houdini Input: Changing world input to bound selector"), + MainInput->GetOuter()); + + bool bNewState = (NewState == ECheckBoxState::Checked); + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->IsWorldInputBoundSelector() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetWorldInputBoundSelector(bNewState); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + // Checkbox : Is Bound Selector + TSharedPtr< SCheckBox > CheckBoxBoundSelector; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxBoundSelector, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("BoundSelector", "Bound Selector")) + .ToolTipText(LOCTEXT("BoundSelectorTip", "When enabled, this world input works as a bound selector, sending all the objects contained in the bound selector bounding boxes.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedBoundSelector, MainInput]() + { + return IsCheckedBoundSelector(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedIsBoundSelector, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedIsBoundSelector(InInputs, NewState); + }) + ]; + } + + // Checkbox: Bound Selector Auto update + { + // Lambda returning a CheckState from the input's current auto update state + auto IsCheckedAutoUpdate = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetWorldInputBoundSelectorAutoUpdates() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing the auto update state + auto CheckStateChangedBoundAutoUpdates = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeAutoUpdate", "Houdini Input: Changing bound selector auto-update state."), + MainInput->GetOuter()); + + bool bNewState = (NewState == ECheckBoxState::Checked); + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetWorldInputBoundSelectorAutoUpdates() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetWorldInputBoundSelectorAutoUpdates(bNewState); + CurInput->MarkChanged(true); + } + }; + + // Checkbox : Is Bound Selector + TSharedPtr< SCheckBox > CheckBoxBoundAutoUpdate; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxBoundAutoUpdate, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("BoundAutoUpdate", "Update bound selection automatically")) + .ToolTipText(LOCTEXT("BoundAutoUpdateTip", "If enabled and if this world input is set as a bound selector, the objects selected by the bounds will update automatically.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedAutoUpdate, MainInput]() + { + return IsCheckedAutoUpdate(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedBoundAutoUpdates, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedBoundAutoUpdates(InInputs, NewState); + }) + ]; + + CheckBoxBoundAutoUpdate->SetEnabled(MainInput->IsWorldInputBoundSelector()); + } + + // ActorPicker : Bound Selector + if(bIsBoundSelector) + { + FMenuBuilder MenuBuilder = Helper_CreateBoundSelectorPickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + // ActorPicker : World Outliner + { + FMenuBuilder MenuBuilder = Helper_CreateWorldActorPickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + { + // Spline Resolution + TSharedPtr> NumericEntryBox; + int32 Idx = 0; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("SplineRes", "Unreal Spline Resolution")) + .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm betweem control points)\nSet this to 0 to only export the control points.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .MinValue(-1.0f) + .MaxValue(1000.0f) + .MinSliderValue(0.0f) + .MaxSliderValue(1000.0f) + .Value(MainInput->GetUnrealSplineResolution()) + .OnValueChanged_Lambda([MainInput, InInputs](float Val) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeSplineResolution", "Houdini Input: Changing world input spline resolution"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetUnrealSplineResolution() == Val) + continue; + + CurrentInput->Modify(); + + CurrentInput->SetUnrealSplineResolution(Val); + CurrentInput->MarkChanged(true); + } + }) + /* + .Value(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetSplineResolutionValue))) + .OnValueChanged(SNumericEntryBox< float >::FOnValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetSplineResolutionValue)) + .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsSplineResolutionEnabled))) + */ + .SliderExponent(1.0f) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("SplineResToDefault", "Reset to default value.")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + // TODO: FINISH ME! + //.OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) + .OnClicked_Lambda([MainInput, InInputs]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputRevertSplineResolution", "Houdini Input: Reverting world input spline resolution to default"), + MainInput->GetOuter()); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + float DefaultSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetUnrealSplineResolution() == DefaultSplineResolution) + continue; + + CurrentInput->Modify(); + + CurrentInput->SetUnrealSplineResolution(DefaultSplineResolution); + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + } +} + +void +FHoudiniInputDetails::AddSkeletalInputUI( + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool ) +{ +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) +{ + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, false); +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) +{ + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, true); +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName, const bool& bUseWorldInAsWorldSelector) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // There's no undo operation for button. + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Locate the details panel. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + return FReply::Handled(); + + class SLocalDetailsView : public SDetailsViewBase + { + public: + void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } + void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } + }; + auto * LocalDetailsView = static_cast(DetailsView.Get()); + + if (!DetailsView->IsLocked()) + { + // + // START SELECTION + // Locks the details view and select our currently selected actors + // + LocalDetailsView->LockDetailsView(); + check(DetailsView->IsLocked()); + + // Force refresh of details view. + TArray InputOuters; + for (auto CurIn : InInputs) + InputOuters.Add(CurIn->GetOuter()); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + //ReselectSelectedActors(); + + if (bUseWorldInAsWorldSelector) + { + // Bound Selection + // Select back the previously chosen bound selectors + GEditor->SelectNone(false, true); + int32 NumBoundSelectors = MainInput->GetNumberOfBoundSelectorObjects(); + for (int32 Idx = 0; Idx < NumBoundSelectors; Idx++) + { + AActor* Actor = MainInput->GetBoundSelectorObjectAt(Idx); + if (!Actor || Actor->IsPendingKill()) + continue; + + GEditor->SelectActor(Actor, true, true); + } + } + else + { + // Regular selection + // Select the already chosen input Actors from the World Outliner. + GEditor->SelectNone(false, true); + int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); + for (int32 Idx = 0; Idx < NumInputObjects; Idx++) + { + UHoudiniInputObject* CurInputObject = MainInput->GetHoudiniInputObjectAt(Idx); + if (!CurInputObject) + continue; + + AActor* Actor = nullptr; + UHoudiniInputActor* InputActor = Cast(CurInputObject); + if (InputActor && !InputActor->IsPendingKill()) + { + // Get the input actor + Actor = InputActor->GetActor(); + } + else + { + // See if the input object is a HAC + UHoudiniInputHoudiniAsset* InputHAC = Cast(CurInputObject); + if (InputHAC && !InputHAC->IsPendingKill()) + { + Actor = InputHAC->GetHoudiniAssetComponent() ? InputHAC->GetHoudiniAssetComponent()->GetOwner() : nullptr; + } + } + + if (!Actor || Actor->IsPendingKill()) + continue; + + GEditor->SelectActor(Actor, true, true); + } + } + + return FReply::Handled(); + } + else + { + // + // UPDATE SELECTION + // Unlocks the input's selection and select the HDA back. + // + + if (!GEditor || !GEditor->GetSelectedObjects()) + return FReply::Handled(); + + USelection * SelectedActors = GEditor->GetSelectedActors(); + if (!SelectedActors) + return FReply::Handled(); + + // Create a transaction + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniWorldInputSelectionChanged", "Changing Houdini world outliner input objects"), + MainInput->GetOuter()); + + + TArray AllActors; + for (auto CurrentInput : InInputs) + { + CurrentInput->Modify(); + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + AllActors.Add(ParentActor); + + bool bHasChanged = true; + if (bUseWorldInAsWorldSelector) + { + // + // Update bound selectors + + // Clean up the selected actors + TArray ValidBoundSelectedActors; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor* CurrentBoundActor = Cast(*It); + if (!CurrentBoundActor) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentBoundActor == ParentActor)) + continue; + + ValidBoundSelectedActors.Add(CurrentBoundActor); + } + + // See if the bound selector have changed + int32 PreviousBoundSelectorCount = CurrentInput->GetNumberOfBoundSelectorObjects(); + if (PreviousBoundSelectorCount == ValidBoundSelectedActors.Num()) + { + // Same number of BoundSelectors, see if they have changed + bHasChanged = false; + for (int32 BoundIdx = 0; BoundIdx < PreviousBoundSelectorCount; BoundIdx++) + { + AActor* PreviousBound = CurrentInput->GetBoundSelectorObjectAt(BoundIdx); + if (!PreviousBound) + continue; + + if (!ValidBoundSelectedActors.Contains(PreviousBound)) + { + bHasChanged = true; + break; + } + } + } + + if (bHasChanged) + { + // Only update the bound selector objects on the input if they have changed + CurrentInput->SetBoundSelectorObjectsNumber(ValidBoundSelectedActors.Num()); + int32 InputObjectIdx = 0; + for (auto CurActor : ValidBoundSelectedActors) + { + CurrentInput->SetBoundSelectorObjectAt(InputObjectIdx++, CurActor); + } + + // Update the current selection from the BoundSelectors + CurrentInput->UpdateWorldSelectionFromBoundSelectors(); + } + } + else + { + // + // Update our selection directly with the currently selected actors + // + + TArray ValidSelectedActors; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor* CurrentActor = Cast(*It); + if (!CurrentActor) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + ValidSelectedActors.Add(CurrentActor); + } + + // Update the input objects from the valid selected actors array + // Only new/remove input objects will be marked as changed + bHasChanged = CurrentInput->UpdateWorldSelection(ValidSelectedActors); + } + + // If we didnt change the selection, cancel the transaction + if (!bHasChanged) + Transaction.Cancel(); + } + + // We can now unlock the details view... + LocalDetailsView->UnlockDetailsView(); + check(!DetailsView->IsLocked()); + + // .. reset the selected actors, force refresh and override the lock. + DetailsView->SetObjects(AllActors, true, true); + + // We now need to reselect all our Asset Actors. + // If we don't do this, our Asset parameters will stop refreshing and the user will be very confused. + // It is also resetting the state of the selection before the input actor selection process was started. + GEditor->SelectNone(false, true); + for (auto CurrentActor : AllActors) + { + AActor* ParentActor = Cast(CurrentActor); + if (!ParentActor) + continue; + + GEditor->SelectActor(ParentActor, true, true); + } + + // Update the input details layout. + // if (CategoryBuilder.IsParentLayoutValid()) + // CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); +} + + +bool +FHoudiniInputDetails::Helper_CancelWorldSelection(TArray& InInputs, const FName& DetailsPanelName) +{ + if (InInputs.Num() <= 0) + return false; + + // Get the property module to access the details view + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Locate the details panel. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + return false; + + if (!DetailsView->IsLocked()) + return false; + + class SLocalDetailsView : public SDetailsViewBase + { + public: + void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } + void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } + }; + auto * LocalDetailsView = static_cast(DetailsView.Get()); + + // Get all our parent components / actors + TArray AllComponents; + TArray AllActors; + for (auto CurrentInput : InInputs) + { + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + if (!ParentComponent) + continue; + + AllComponents.Add(ParentComponent); + + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + if (!ParentActor) + continue; + + AllActors.Add(ParentActor); + } + + // Unlock the detail view and re-select our parent actors + { + LocalDetailsView->UnlockDetailsView(); + check(!DetailsView->IsLocked()); + + // Reset selected actor to itself, force refresh and override the lock. + DetailsView->SetObjects(AllActors, true, true); + } + + // Reselect the Asset Actor. If we don't do this, our Asset parameters will stop + // refreshing and the user will be very confused. It is also resetting the state + // of the selection before the input actor selection process was started. + GEditor->SelectNone(false, true); + for (auto ParentActorObj : AllActors) + { + AActor* ParentActor = Cast(ParentActorObj); + if (!ParentActor) + continue; + + GEditor->SelectActor(ParentActor, true, true); + } + + return true; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h index 4a025c964..3382ffd1f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h @@ -1,165 +1,165 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -class UHoudiniInput; -class UHoudiniSplineComponent; - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class FMenuBuilder; -class SVerticalBox; -class IDetailsView; -class FReply; -class FAssetThumbnailPool; - -class FHoudiniInputDetails : public TSharedFromThis -{ - public: - static void CreateWidget( - IDetailCategoryBuilder& HouInputCategoryBuilder, - TArray InInputs, FDetailWidgetRow* InputRow = nullptr); - - static void CreateNameWidget( - UHoudiniInput* InParam, - FDetailWidgetRow & Row, - bool bLabel, - int32 InInputCount); - - static FText GetInputTooltip( UHoudiniInput* InInput ); - - // ComboBox : Input Type - static void AddInputTypeComboBox( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - const IDetailsView* InDetailsView); - - // Checkbox : Keep World Transform - static void AddKeepWorldTransformCheckBox( - TSharedRef InVerticalBox, - TArray& InInputs); - - static void AddCurveInputCookOnChangeCheckBox( - TSharedRef< SVerticalBox > VerticalBox, - TArray& InInputs); - - // Checkbox : Pack before merging - static void AddPackBeforeMergeCheckbox( - TSharedRef InVerticalBox, - TArray& InInputs); - - static void AddImportAsReferenceCheckbox( - TSharedRef< SVerticalBox > VerticalBox, - TArray& InInputs); - - // Checkboxes : Export LODs / Sockets / Collisions - static void AddExportCheckboxes( - TSharedRef InVerticalBox, - TArray& InInputs); - - // Add Geometry Inputs UI Widgets - static void AddGeometryInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - - // Create a single geometry widget for the given input object - static void Helper_CreateGeometryWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const FPlatformTypes::int32& InGeometryObjectIdx, - TSharedPtr AssetThumbnailPool, TSharedRef VerticalBox); - - static void Helper_CreateCurveWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InCurveObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox, - TSharedPtr HouSplineComponentVisualizer); - - // Add Asset Inputs UI Widgets - static void AddAssetInputUI( - TSharedRef VerticalBox, - TArray& InInputs); - - // Add Curve Inputs UI Widgets - static void AddCurveInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - - // Add Landscape Inputs UI Widgets - static void AddLandscapeInputUI( - TSharedRef VerticalBox, - TArray& InInputs); - - // Add World Inputs UI Widgets - static void AddWorldInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - const IDetailsView* InDetailsView); - - // Add Skeletal Inputs UI Widgets - static void AddSkeletalInputUI( - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - /* - static FMenuBuilder Helper_CreateCustomActorPickerWidget( - UHoudiniInput* InParam, - const TAttribute& HeadingText, - const bool& bShowCurrentSelectionSection) - */ - - static FMenuBuilder Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateLandscapePickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateWorldActorPickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateBoundSelectorPickerWidget(TArray& InInputs); - - static FReply Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); - - static FReply Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); - - static FReply Helper_OnButtonClickSelectActors( - IDetailCategoryBuilder& CategoryBuilder, - TArray InInputs, - const FName& InDetailsPanelName, - const bool& bUseWorldInAsWorldSelector); - - static bool Helper_CancelWorldSelection( - TArray& InInputs, const FName& DetailsPanelName); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +class UHoudiniInput; +class UHoudiniSplineComponent; + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class FMenuBuilder; +class SVerticalBox; +class IDetailsView; +class FReply; +class FAssetThumbnailPool; + +class FHoudiniInputDetails : public TSharedFromThis +{ + public: + static void CreateWidget( + IDetailCategoryBuilder& HouInputCategoryBuilder, + TArray InInputs, FDetailWidgetRow* InputRow = nullptr); + + static void CreateNameWidget( + UHoudiniInput* InParam, + FDetailWidgetRow & Row, + bool bLabel, + int32 InInputCount); + + static FText GetInputTooltip( UHoudiniInput* InInput ); + + // ComboBox : Input Type + static void AddInputTypeComboBox( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + const IDetailsView* InDetailsView); + + // Checkbox : Keep World Transform + static void AddKeepWorldTransformCheckBox( + TSharedRef InVerticalBox, + TArray& InInputs); + + static void AddCurveInputCookOnChangeCheckBox( + TSharedRef< SVerticalBox > VerticalBox, + TArray& InInputs); + + // Checkbox : Pack before merging + static void AddPackBeforeMergeCheckbox( + TSharedRef InVerticalBox, + TArray& InInputs); + + static void AddImportAsReferenceCheckbox( + TSharedRef< SVerticalBox > VerticalBox, + TArray& InInputs); + + // Checkboxes : Export LODs / Sockets / Collisions + static void AddExportCheckboxes( + TSharedRef InVerticalBox, + TArray& InInputs); + + // Add Geometry Inputs UI Widgets + static void AddGeometryInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + + // Create a single geometry widget for the given input object + static void Helper_CreateGeometryWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const FPlatformTypes::int32& InGeometryObjectIdx, + TSharedPtr AssetThumbnailPool, TSharedRef VerticalBox); + + static void Helper_CreateCurveWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InCurveObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox, + TSharedPtr HouSplineComponentVisualizer); + + // Add Asset Inputs UI Widgets + static void AddAssetInputUI( + TSharedRef VerticalBox, + TArray& InInputs); + + // Add Curve Inputs UI Widgets + static void AddCurveInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + + // Add Landscape Inputs UI Widgets + static void AddLandscapeInputUI( + TSharedRef VerticalBox, + TArray& InInputs); + + // Add World Inputs UI Widgets + static void AddWorldInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + const IDetailsView* InDetailsView); + + // Add Skeletal Inputs UI Widgets + static void AddSkeletalInputUI( + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + /* + static FMenuBuilder Helper_CreateCustomActorPickerWidget( + UHoudiniInput* InParam, + const TAttribute& HeadingText, + const bool& bShowCurrentSelectionSection) + */ + + static FMenuBuilder Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateLandscapePickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateWorldActorPickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateBoundSelectorPickerWidget(TArray& InInputs); + + static FReply Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); + + static FReply Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); + + static FReply Helper_OnButtonClickSelectActors( + IDetailCategoryBuilder& CategoryBuilder, + TArray InInputs, + const FName& InDetailsPanelName, + const bool& bUseWorldInAsWorldSelector); + + static bool Helper_CancelWorldSelection( + TArray& InInputs, const FName& DetailsPanelName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp index 0bc5a9ce1..0a8d315fa 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp @@ -1,3245 +1,3245 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniOutputDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniAssetComponentDetails.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAsset.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniEngineCommands.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "DetailWidgetRow.h" -#include "IDetailGroup.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SRotatorInputBox.h" -#include "Widgets/Layout/SBox.h" -#include "Widgets/Text/STextBlock.h" -#include "Editor/UnrealEd/Public/AssetThumbnail.h" -#include "SAssetDropTarget.h" -#include "Engine/StaticMesh.h" -#include "Components/SplineComponent.h" -#include "Materials/Material.h" -#include "Materials/MaterialInstance.h" -#include "Materials/MaterialInstanceConstant.h" -#include "Sound/SoundBase.h" -#include "Engine/SkeletalMesh.h" -#include "Particles/ParticleSystem.h" -//#include "Landscape.h" -#include "LandscapeProxy.h" -#include "ScopedTransaction.h" -#include "PhysicsEngine/BodySetup.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" -#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -void -FHoudiniOutputDetails::CreateWidget( - IDetailCategoryBuilder& HouOutputCategory, - TArray InOutputs) -{ - if (InOutputs.Num() <= 0) - return; - - UHoudiniOutput* MainOutput = InOutputs[0]; - - // Don't create UI for editable curve. - if (!MainOutput || MainOutput->IsPendingKill() || MainOutput->IsEditableNode()) - return; - - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); - - // TODO - // For now we just handle Mesh Outputs - - switch (MainOutput->GetType()) - { - case EHoudiniOutputType::Mesh: - { - FHoudiniOutputDetails::CreateMeshOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Landscape: - { - FHoudiniOutputDetails::CreateLandscapeOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Instancer: - { - FHoudiniOutputDetails::CreateInstancerOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Curve: - { - FHoudiniOutputDetails::CreateCurveOutputWidget(HouOutputCategory, MainOutput); - break; - } - case EHoudiniOutputType::Skeletal: - default: - { - FHoudiniOutputDetails::CreateDefaultOutputWidget(HouOutputCategory, MainOutput); - break; - } - - } - -} - - -void -FHoudiniOutputDetails::CreateLandscapeOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // Go through this output's objects - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& CurrentOutputObj : OutputObjects) - { - UHoudiniLandscapePtr* LandscapePointer = Cast(CurrentOutputObj.Value.OutputObject); - if (!LandscapePointer) - continue; - - FHoudiniOutputObjectIdentifier& Identifier = CurrentOutputObj.Key; - const FHoudiniGeoPartObject *HGPO = nullptr; - for (const auto& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!Identifier.Matches(CurHGPO)) - continue; - - HGPO = &CurHGPO; - break; - } - - if (!HGPO) - continue; - - CreateLandscapeOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapePointer, Identifier); - } -} - -void -FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - const FHoudiniGeoPartObject& HGPO, - UHoudiniLandscapePtr* LandscapePointer, - const FHoudiniOutputObjectIdentifier & OutputIdentifier) -{ - if (!LandscapePointer || LandscapePointer->IsPendingKill() || !LandscapePointer->LandscapeSoftPtr.IsValid()) - return; - - if (!InOutput || InOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return; - - ALandscapeProxy * Landscape = LandscapePointer->LandscapeSoftPtr.Get(); - if (!Landscape || Landscape->IsPendingKill()) - return; - - // TODO: Get bake base name - FString Label = Landscape->GetName(); - - EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType = LandscapePointer->BakeType; - - // Get thumbnail pool for this builder - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - TArray>* BakeOptionString = FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels(); - - // Create bake mesh name textfield. - IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - LandscapeGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(Label)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - // Create the thumbnail for the landscape output object. - TSharedPtr< FAssetThumbnail > LandscapeThumbnail = - MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); - - TSharedPtr< SBorder > LandscapeThumbnailBorder; - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - LandscapeGrp.AddWidgetRow() - .NameContent() - [ - SNew(SSpacer) - .Size(FVector2D(250, 64)) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot().Padding(0, 2).AutoHeight() - [ - SNew(SBox).WidthOverride(175) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(LandscapeThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(Landscape->GetPathName())) - [ - LandscapeThumbnail->MakeThumbnailWidget() - ] - ] - ] - - + SHorizontalBox::Slot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SBox).WidthOverride(40.0f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("Bake", "Bake")) - .IsEnabled(true) - .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() - { - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - if (FoundOutputObject) - { - TArray AllOutputs; - AllOutputs.Reserve(HAC->GetNumOutputs()); - HAC->GetOutputs(AllOutputs); - FHoudiniOutputDetails::OnBakeOutputObject( - FoundOutputObject->BakeName, - Landscape, - OutputIdentifier, - HGPO, - OwnerActor->GetName(), - HAC->BakeFolder.Path, - HAC->TemporaryCookFolder.Path, - InOutput->GetType(), - LandscapeOutputBakeType, - AllOutputs); - } - - // TODO: Remove the output landscape if the landscape bake type is Detachment? - return FReply::Handled(); - }) - .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) - ] - ] - + SHorizontalBox::Slot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SBox).WidthOverride(120.f) - [ - SNew(SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - if (SelectType != ESelectInfo::Type::OnMouseClick) - return; - - FString *NewChoiceStr = NewChoice.Get(); - if (!NewChoiceStr) - return; - - if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); - } - else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); - } - else - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); - } - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - [ - SNew(STextBlock) - .Text_Lambda([LandscapePointer]() - { - FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); - return FText::FromString(BakeTypeString); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ] - ] - ]; - - // Store thumbnail for this landscape. - OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); - - // We need to add material box for each the landscape and landscape hole materials - for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) - { - UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); - TSharedPtr MaterialThumbnailBorder; - TSharedPtr HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if (MaterialInterface) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - - // Create thumbnail for this material. - TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = - MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(STextBlock) - .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().Padding(0, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) - .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(MaterialThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(MaterialPathName)) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this landscape and material index. - { - TPairInitializer Pair(Landscape, MaterialIdx); - MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); - } - - // Combox Box and Button Box - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Combo row - TSharedPtr< SComboButton > AssetComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) - [ - SAssignNew(AssetComboButton, SComboButton) - //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - // Buttons row - TSharedPtr ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Add use Content Browser selection arrow - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)Landscape, InOutput, MaterialIdx), - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) - ]; - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), - TAttribute< FText >(MaterialTooltip)) - ]; - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - - // Store combo button for this mesh and index. - { - TPairInitializer Pair(Landscape, MaterialIdx); - MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); - } - } - -} - -void -FHoudiniOutputDetails::CreateMeshOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent* HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - FString HoudiniAssetName; - if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) - { - HoudiniAssetName = HAC->GetOwner()->GetName(); - } - else if (HAC->GetHoudiniAsset()) - { - HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); - } - else - { - HoudiniAssetName = HAC->GetName(); - } - - // Go through this output's object - int32 OutputObjIdx = 0; - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& IterObject : OutputObjects) - { - UStaticMesh* StaticMesh = Cast(IterObject.Value.OutputObject); - UHoudiniStaticMesh* ProxyMesh = Cast(IterObject.Value.ProxyObject); - - if ((!StaticMesh || StaticMesh->IsPendingKill()) - && (!ProxyMesh || ProxyMesh->IsPendingKill())) - continue; - - FHoudiniOutputObjectIdentifier & OutputIdentifier = IterObject.Key; - - // Find the corresponding HGPO in the output - FHoudiniGeoPartObject HoudiniGeoPartObject; - for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!OutputIdentifier.Matches(curHGPO)) - continue; - - HoudiniGeoPartObject = curHGPO; - break; - } - - if (StaticMesh && !StaticMesh->IsPendingKill()) - { - bool bIsProxyMeshCurrent = IterObject.Value.bProxyIsCurrent; - - // If we have a static mesh, alway display its widget even if the proxy is more recent - CreateStaticMeshAndMaterialWidgets( - HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); - } - else - { - // If we only have a proxy mesh, then show the proxy widget - CreateProxyMeshAndMaterialWidgets( - HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject); - } - } -} - -void -FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - int32 OutputObjIdx = 0; - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& IterObject : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = IterObject.Value; - USceneComponent* SplineComponent = Cast(IterObject.Value.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - FHoudiniOutputObjectIdentifier& OutputIdentifier = IterObject.Key; - FHoudiniGeoPartObject HoudiniGeoPartObject; - for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!OutputIdentifier.Matches(curHGPO)) - continue; - - HoudiniGeoPartObject = curHGPO; - break; - } - - CreateCurveWidgets(HouOutputCategory, InOutput, SplineComponent, CurrentOutputObject, OutputIdentifier, HoudiniGeoPartObject); - } -} - -void -FHoudiniOutputDetails::CreateCurveWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - USceneComponent* SplineComponent, - FHoudiniOutputObject& OutputObject, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - FHoudiniGeoPartObject& HoudiniGeoPartObject) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // We support Unreal Spline out only for now - USplineComponent* SplineOutput = Cast(SplineComponent); - if (!SplineOutput || SplineOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return; - - FHoudiniCurveOutputProperties* OutputProperty = &(OutputObject.CurveOutputProperty); - EHoudiniCurveType OutputCurveType = OutputObject.CurveOutputProperty.CurveType; - - FString Label = SplineComponent->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - //Label += FString("_") + OutputIdentifier.SplitIdentifier; - - FString OutputCurveName = OutputObject.BakeName; - if(OutputCurveName.IsEmpty()) - OutputCurveName = OwnerActor->GetName() + "_" + Label; - - const FText& LabelText = FText::FromString("Unreal Spline"); - - IDetailGroup& CurveOutputGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label), false, false); - - // Bake name row UI - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(OutputObject.BakeName)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - ] - - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("OutputCurveSplineType", "Spline Type")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText_Lambda([SplineOutput, Label, OutputCurveType]() - { - FString ToolTipStr = FString::Printf(TEXT(" curve: %s\n Export type: Unreal Spline\n num points: %d\n curve type: %s\n closed: %s"), - *Label, - SplineOutput->GetNumberOfSplinePoints(), - *FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(OutputCurveType), - SplineOutput->IsClosedLoop() ? *(FString("yes")) : *(FString("no"))); - - return FText::FromString(ToolTipStr); - }) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(STextBlock) - // We support Unreal Spline output only for now... - .Text(LOCTEXT("OutputCurveSplineTypeUnreal", "Unreal Spline")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ]; - - //if (bIsUnrealSpline) - //{ - USplineComponent* UnrealSpline = Cast(SplineComponent); - - // Curve type combo box UI - auto InitialSelectionLambda = [OutputProperty]() - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - { - return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[0]; - } - else - { - return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[1]; - } - }; - - TSharedPtr>> UnrealCurveTypeComboBox; - - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Text(LOCTEXT("OutputCurveUnrealSplinePointType", "Spline Point Type")) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(UnrealCurveTypeComboBox, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels()) - .InitiallySelectedItem(InitialSelectionLambda()) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [OutputProperty, InOutput, SplineComponent](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - // Set the curve point type locally - USplineComponent* Spline = Cast(SplineComponent); - if (!Spline || Spline->IsPendingKill()) - return; - - FString *NewChoiceStr = NewChoice.Get(); - if (!NewChoiceStr) - return; - - if (*NewChoiceStr == "Linear") - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - return; - - OutputProperty->CurveType = EHoudiniCurveType::Polygon; - - for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) - { - Spline->SetSplinePointType(PtIdx, ESplinePointType::Linear); - } - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - } - else if (*NewChoiceStr == "Curve") - { - if (OutputProperty->CurveType != EHoudiniCurveType::Polygon) - return; - - OutputProperty->CurveType = EHoudiniCurveType::Bezier; - - for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) - { - Spline->SetSplinePointType(PtIdx, ESplinePointType::Curve); - } - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([OutputProperty]() - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - return FText::FromString(TEXT("Linear")); - else - return FText::FromString(TEXT("Curve")); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - // Add closed curve checkbox UI - TSharedPtr ClosedCheckBox; - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Text(LOCTEXT("OutputCurveUnrealSplineClosed", "Closed")) - ] - .ValueContent() - [ - SAssignNew(ClosedCheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([UnrealSpline, InOutput](ECheckBoxState NewState) - { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) - return; - - UnrealSpline->SetClosedLoop(NewState == ECheckBoxState::Checked); - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - .IsChecked_Lambda([UnrealSpline]() - { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return UnrealSpline->IsClosedLoop() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - ]; - //} - - // Add Bake Button UI - TSharedPtr BakeButton; - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - ] - .ValueContent() - [ - SAssignNew(BakeButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) - .IsEnabled(true) - .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) - .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName]() - { - TArray AllOutputs; - AllOutputs.Reserve(HAC->GetNumOutputs()); - HAC->GetOutputs(AllOutputs); - FHoudiniOutputDetails::OnBakeOutputObject( - OutputCurveName, - SplineComponent, - OutputIdentifier, - HoudiniGeoPartObject, - OwnerActor->GetName(), - HAC->BakeFolder.Path, - HAC->TemporaryCookFolder.Path, - InOutput->GetType(), - EHoudiniLandscapeOutputBakeType::InValid, - AllOutputs); - - return FReply::Handled(); - }) - ]; -} - -void -FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UStaticMesh * StaticMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject, - const bool& bIsProxyMeshCurrent) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return; - - UHoudiniAssetComponent* OwningHAC = Cast(InOutput->GetOuter()); - - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); - - // Get thumbnail pool for this builder. - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // TODO: GetBakingBaseName! - FString Label = StaticMesh->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - // Create thumbnail for this mesh. - TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = - MakeShareable(new FAssetThumbnail(StaticMesh, 64, 64, AssetThumbnailPool)); - TSharedPtr StaticMeshThumbnailBorder; - - TSharedRef VerticalBox = SNew(SVerticalBox); - - IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(BakeName)) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - }) - .ToolTipText( LOCTEXT( "BakeNameTip", "The base name of the baked asset") ) - ] - - +SHorizontalBox::Slot() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "RevertNameOverride", "Revert bake name override" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ] - ]; - - // Add details on the SM colliders - EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); - FString MeshLabel = TEXT( "Static Mesh" ); - - // If the Proxy mesh is more recent, indicate it in the details - if (bIsProxyMeshCurrent) - { - MeshLabel += TEXT("\n(unrefined)"); - } - - // Indicate that this mesh is instanced - if (HoudiniGeoPartObject.bIsInstanced) - { - MeshLabel += TEXT("\n(instanced)"); - } - - if (HoudiniGeoPartObject.bIsTemplated) - { - MeshLabel += TEXT("\n(templated)"); - } - - int32 NumSimpleColliders = 0; - if (StaticMesh->BodySetup && !StaticMesh->BodySetup->IsPendingKill()) - NumSimpleColliders = StaticMesh->BodySetup->AggGeom.GetElementCount(); - - if(NumSimpleColliders > 0) - { - MeshLabel += TEXT( "\n(") + FString::FromInt(NumSimpleColliders) + TEXT(" Simple Collider" ); - if (NumSimpleColliders > 1 ) - MeshLabel += TEXT("s"); - MeshLabel += TEXT(")"); - } - else if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - MeshLabel += TEXT( "\n(Rendered Complex Collider)" ); - } - else if(SplitType == EHoudiniSplitType::InvisibleComplexCollider ) - { - MeshLabel += TEXT( "\n(Invisible Complex Collider)" ); - } - - if ( StaticMesh->GetNumLODs() > 1 ) - MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)"); - - if ( StaticMesh->Sockets.Num() > 0 ) - MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->Sockets.Num() ) + TEXT(" sockets)"); - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew( STextBlock ) - .Text( FText::FromString(MeshLabel) ) - .Font( IDetailLayoutBuilder::GetDetailFont() ) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot() - .Padding( 0, 2 ) - .AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) - .AutoWidth() - [ - SAssignNew( StaticMeshThumbnailBorder, SBorder ) - .Padding( 5.0f ) - .BorderImage( this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)StaticMesh ) - .OnMouseDoubleClick( this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *) StaticMesh ) - [ - SNew( SBox ) - .WidthOverride( 64 ) - .HeightOverride( 64 ) - .ToolTipText( FText::FromString( StaticMesh->GetPathName() ) ) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ] - - +SHorizontalBox::Slot() - .FillWidth( 1.0f ) - .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SVerticalBox ) - +SVerticalBox::Slot() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .MaxWidth( 80.0f ) - [ - SNew( SButton ) - .VAlign( VAlign_Center ) - .HAlign( HAlign_Center ) - .Text( LOCTEXT( "Bake", "Bake" ) ) - .IsEnabled(true) - .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC]() - { - TArray AllOutputs; - FString TempCookFolder; - if (IsValid(OwningHAC)) - { - AllOutputs.Reserve(OwningHAC->GetNumOutputs()); - OwningHAC->GetOutputs(AllOutputs); - - TempCookFolder = OwningHAC->TemporaryCookFolder.Path; - } - FHoudiniOutputDetails::OnBakeOutputObject( - BakeName, - StaticMesh, - OutputIdentifier, - HoudiniGeoPartObject, - HoudiniAssetName, - BakeFolder, - TempCookFolder, - InOutput->GetType(), - EHoudiniLandscapeOutputBakeType::InValid, - AllOutputs); - - return FReply::Handled(); - }) - .ToolTipText( LOCTEXT( "HoudiniStaticMeshBakeButton", "Bake this generated static mesh" ) ) - ] - +SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)StaticMesh), - TAttribute(LOCTEXT("HoudiniStaticMeshBrowseButton", "Browse to this generated static mesh in the content browser"))) - ] - ] - ] - ]; - - // Store thumbnail for this mesh. - OutputObjectThumbnailBorders.Add((UObject*)StaticMesh, StaticMeshThumbnailBorder); - - // We need to add material box for each material present in this static mesh. - auto & StaticMeshMaterials = StaticMesh->StaticMaterials; - for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) - { - UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface; - TSharedPtr< SBorder > MaterialThumbnailBorder; - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if ( MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - else - { - MaterialInterface = nullptr; - MaterialName = TEXT("Material (invalid)") + FString::FromInt( MaterialIdx ) ; - MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - } - - // Create thumbnail for this material. - TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = - MakeShareable( new FAssetThumbnail( MaterialInterface, 64, 64, AssetThumbnailPool ) ); - - VerticalBox->AddSlot().Padding( 0, 2 ) - [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop( this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver ) - .OnAssetDropped( - this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx ) - [ - SAssignNew( HorizontalBox, SHorizontalBox ) - ] - ]; - - HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() - [ - SAssignNew( MaterialThumbnailBorder, SBorder ) - .Padding( 5.0f ) - .BorderImage( - this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject *)StaticMesh, MaterialIdx ) - .OnMouseDoubleClick( - this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface ) - [ - SNew( SBox ) - .WidthOverride( 64 ) - .HeightOverride( 64 ) - .ToolTipText( FText::FromString( MaterialPathName ) ) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this mesh and material index. - { - TPairInitializer Pair( StaticMesh, MaterialIdx ); - MaterialInterfaceThumbnailBorders.Add( Pair, MaterialThumbnailBorder ); - } - - // ComboBox and buttons - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add Combo box - TSharedPtr< SComboButton > AssetComboButton; - ComboAndButtonBox->AddSlot().VAlign(VAlign_Center).FillHeight(1.0f) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().VAlign(VAlign_Center).FillHeight(1.0f) - [ - SAssignNew(AssetComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, - MaterialInterface, (UObject*)StaticMesh, InOutput, MaterialIdx) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - - // Add buttons - TSharedPtr< SHorizontalBox > ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Use CB selection arrow button - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)StaticMesh, InOutput, MaterialIdx), - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) - ]; - - // Browse CB button - ButtonBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface ), TAttribute< FText >( MaterialTooltip ) ) - ]; - - // Reset button - ButtonBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "ResetToBaseMaterial", "Reset to base material" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked( - this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ]; - - // Store combo button for this mesh and index. - { - TPairInitializer Pair( StaticMesh, MaterialIdx ); - MaterialInterfaceComboButtons.Add( Pair, AssetComboButton ); - } - } -} - -void -FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UHoudiniStaticMesh * ProxyMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject) -{ - if (!ProxyMesh || ProxyMesh->IsPendingKill()) - return; - - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); - - // Get thumbnail pool for this builder. - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // TODO: GetBakingBaseName! - FString Label = ProxyMesh->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - // Create thumbnail for this mesh. - TSharedPtr MeshThumbnail = MakeShareable(new FAssetThumbnail(ProxyMesh, 64, 64, AssetThumbnailPool)); - TSharedPtr MeshThumbnailBorder; - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(BakeName)) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - }) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - // Add details on the Proxy Mesh - EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); - FString MeshLabel = TEXT("Proxy Mesh"); - - // Indicate that this mesh is instanced - if (HoudiniGeoPartObject.bIsInstanced) - { - MeshLabel += TEXT("\n(instanced)"); - } - - if (HoudiniGeoPartObject.bIsTemplated) - { - MeshLabel += TEXT("\n(templated)"); - } - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(FText::FromString(MeshLabel)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot() - .Padding(0, 2) - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(MeshThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)ProxyMesh) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)ProxyMesh) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(ProxyMesh->GetPathName())) - [ - MeshThumbnail->MakeThumbnailWidget() - ] - ] - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .MaxWidth(80.0f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("Refine", "Refine")) - .IsEnabled(true) - .OnClicked(this, &FHoudiniOutputDetails::OnRefineClicked, (UObject *)ProxyMesh, InOutput) - .ToolTipText(LOCTEXT("RefineTooltip", "Refine this Proxy Mesh to a Static Mesh")) - ] - ] - ] - ]; - - // Store thumbnail for this mesh. - OutputObjectThumbnailBorders.Add(ProxyMesh, MeshThumbnailBorder); - - // We need to add material box for each material present in this static mesh. - auto & ProxyMeshMaterials = ProxyMesh->GetStaticMaterials(); - for (int32 MaterialIdx = 0; MaterialIdx < ProxyMeshMaterials.Num(); ++MaterialIdx) - { - UMaterialInterface * MaterialInterface = ProxyMeshMaterials[MaterialIdx].MaterialInterface; - TSharedPtr< SBorder > MaterialThumbnailBorder; - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if (MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill()) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - else - { - MaterialInterface = nullptr; - MaterialName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - } - - // Create thumbnail for this material. - TSharedPtr MaterialInterfaceThumbnail = - MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); - - // No drop target - VerticalBox->AddSlot() - .Padding(0, 2) - [ - SNew(SAssetDropTarget) - //.OnIsAssetAcceptableForDrop(false) - //.OnAssetDropped( - // this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - HorizontalBox->AddSlot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(MaterialThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage( - this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)ProxyMesh, MaterialIdx) - .OnMouseDoubleClick( - this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(MaterialPathName)) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this mesh and material index. - { - TPairInitializer Pair((UObject*)ProxyMesh, MaterialIdx); - MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); - } - - // Combo box and buttons - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add combo box - TSharedPtr AssetComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SAssignNew(AssetComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - /*.OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, - MaterialInterface, StaticMesh, InOutput, MaterialIdx)*/ - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - - TSharedPtr ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Disable the combobutton for proxies - AssetComboButton->SetEnabled(false); - - // Add use selection form content browser array - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - /*FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)ProxyMesh, InOutput, MaterialIdx),*/ - FSimpleDelegate::CreateLambda([]() {}), // Do nothing for proxies - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser")), false) - // Disable the use CB selection button for proxies - ]; - - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP(this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), TAttribute(MaterialTooltip)) - ]; - - /* - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked( - this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - */ - - // Store combo button for this mesh and index. - { - TPairInitializer Pair(ProxyMesh, MaterialIdx); - MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); - } - } -} - -FText -FHoudiniOutputDetails::GetOutputDebugName(UHoudiniOutput* InOutput) -{ - // Get the name and type - FString OutputNameStr = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); - - // Then add the number of parts - OutputNameStr += TEXT(" (") + FString::FromInt(InOutput->GetHoudiniGeoPartObjects().Num()) + TEXT(" Part(s))\n"); - - return FText::FromString(OutputNameStr); -} -FText -FHoudiniOutputDetails::GetOutputDebugDescription(UHoudiniOutput* InOutput) -{ - const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); - - FString OutputValStr; - OutputValStr += TEXT("HGPOs:\n"); - for (auto& HGPO : HGPOs) - { - OutputValStr += TEXT(" - ") + HGPO.PartName + TEXT(" (") + FHoudiniGeoPartObject::HoudiniPartTypeToString(HGPO.Type) + TEXT(")"); - - if (HGPO.SplitGroups.Num() > 0) - { - OutputValStr += TEXT("( ") + FString::FromInt(HGPO.SplitGroups.Num()) + TEXT(" splits:"); - for (auto& split : HGPO.SplitGroups) - { - OutputValStr += TEXT(" ") + split; - } - OutputValStr += TEXT(")"); - } - - if (!HGPO.VolumeName.IsEmpty()) - { - OutputValStr += TEXT("( ") + HGPO.VolumeName; - if (HGPO.VolumeTileIndex >= 0) - OutputValStr += TEXT(" tile ") + FString::FromInt(HGPO.VolumeTileIndex); - OutputValStr += TEXT(" )"); - } - - OutputValStr += TEXT("\n"); - } - - // Add output objects if any - TMap AllOutputObj = InOutput->GetOutputObjects(); - if (AllOutputObj.Num() > 0) - { - bool TitleAdded = false; - for (const auto& Iter : AllOutputObj) - { - UObject* OutObject = Iter.Value.OutputObject; - if (OutObject) - { - OutputValStr += OutObject->GetFullName() + TEXT(" (obj)\n"); - } - - UObject* OutComp = Iter.Value.OutputComponent; - if (OutComp) - { - OutputValStr += OutObject->GetFullName() + TEXT(" (comp)\n"); - } - } - } - - return FText::FromString(OutputValStr); -} - -FText -FHoudiniOutputDetails::GetOutputTooltip(UHoudiniOutput* InOutput) -{ - // TODO - return FText(); -} - - -const FSlateBrush * -FHoudiniOutputDetails::GetThumbnailBorder(UObject* Mesh) const -{ - TSharedPtr ThumbnailBorder = OutputObjectThumbnailBorders[Mesh]; - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - - -const FSlateBrush * -FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(UObject* OutputObject, int32 MaterialIdx) const -{ - if (!OutputObject) - return nullptr; - - TPairInitializer Pair(OutputObject, MaterialIdx); - TSharedPtr ThumbnailBorder = MaterialInterfaceThumbnailBorders[Pair]; - - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - -/* -const FSlateBrush * -FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx) const -{ - if (!Landscape) - return nullptr; - - TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); - TSharedPtr< SBorder > ThumbnailBorder = LandscapeMaterialInterfaceThumbnailBorders[Pair]; - - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} -*/ - -FReply -FHoudiniOutputDetails::OnThumbnailDoubleClick( - const FGeometry & InMyGeometry, - const FPointerEvent & InMouseEvent, UObject * Object) -{ - if (Object && GEditor) - GEditor->EditObject(Object); - - return FReply::Handled(); -} - -/* -FReply -FHoudiniOutputDetails::OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject) -{ - if (HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill()) - { - FHoudiniPackageParams PackageParms; - - - FHoudiniEngineBakeUtils::BakeStaticMesh(HoudiniAssetComponent, GeoPartObject, StaticMesh, PackageParms); - // TODO: Bake the SM - - - // We need to locate corresponding geo part object in component. - const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(StaticMesh); - - // (void)FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( - // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); - - } - - return FReply::Handled(); -} -*/ - -bool -FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver(const UObject * InObject) const -{ - return (InObject && InObject->IsA(UMaterialInterface::StaticClass())); -} - - -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - UStaticMesh * StaticMesh, - UHoudiniOutput * HoudiniOutput, - int32 MaterialIdx) -{ - FReply RetValue = FReply::Handled(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return RetValue; - - if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) - return RetValue; - - // Retrieve material interface which is being replaced. - UMaterialInterface * MaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; - if (!MaterialInterface) - return RetValue; - - // Find the string corresponding to the material that is being replaced - const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); - if (!FoundString ) - { - // This material was not replaced, no need to reset it - return RetValue; - } - - // This material has been replaced previously. - FString MaterialString = *FoundString; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), HoudiniOutput); - - // Remove the replacement - HoudiniOutput->Modify(); - HoudiniOutput->GetReplacementMaterials().Remove(MaterialString); - - bool bViewportNeedsUpdate = true; - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - UMaterialInterface * const * FoundMat = HoudiniOutput->GetAssignementMaterials().Find(MaterialString); - if (FoundMat && (*FoundMat)) - AssignMaterial = *FoundMat; - - // Replace material on static mesh. - StaticMesh->Modify(); - StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = AssignMaterial; - - // Replace the material on any component (SMC/ISMC) that uses the above SM - // TODO: ?? Replace for all? - for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) - { - // Only look at MeshComponents - UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); - if (!SMC) - continue; - - if (SMC->GetStaticMesh() != StaticMesh) - continue; - - SMC->Modify(); - SMC->SetMaterial(MaterialIdx, AssignMaterial); - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - return RetValue; -} - -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - ALandscapeProxy* InLandscape, - UHoudiniOutput * InHoudiniOutput, - int32 InMaterialIdx) -{ - FReply RetValue = FReply::Handled(); - if (!InLandscape || InLandscape->IsPendingKill()) - return RetValue; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * MaterialInterface = InMaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); - UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - - // Find the string corresponding to the material that is being replaced - const FString* FoundString = InHoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); - if (!FoundString) - { - // This material was not replaced, no need to reset it - return RetValue; - } - - // This material has been replaced previously. - FString MaterialString = *FoundString; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), InHoudiniOutput); - - // Remove the replacement - InHoudiniOutput->Modify(); - InHoudiniOutput->GetReplacementMaterials().Remove(MaterialString); - - bool bViewportNeedsUpdate = true; - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - UMaterialInterface * const * FoundMat = InHoudiniOutput->GetAssignementMaterials().Find(MaterialString); - if (FoundMat && (*FoundMat)) - AssignMaterial = *FoundMat; - - // Replace material on Landscape - InLandscape->Modify(); - if (InMaterialIdx == 0) - InLandscape->LandscapeMaterial = AssignMaterial; - else - InLandscape->LandscapeHoleMaterial = AssignMaterial; - - InLandscape->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); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - */ - - FHoudiniEngineUtils::UpdateEditorProperties(InHoudiniOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - return RetValue; -} -/* -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - ALandscapeProxy * Landscape, UHoudiniOutput * InOutput, int32 MaterialIdx) -{ - bool bViewportNeedsUpdate = false; - - // TODO: Handle me! - for (TArray< UHoudiniAssetComponent * >::TIterator - IterComponents(HoudiniAssetComponents); IterComponents; ++IterComponents) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; - if (!HoudiniAssetComponent) - continue; - - TWeakObjectPtr* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find(*HoudiniGeoPartObject); - if (!FoundLandscapePtr) - continue; - - ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get(); - if (!FoundLandscape || !FoundLandscape->IsValidLowLevel()) - continue; - - if (FoundLandscape != Landscape) - continue; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); - UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - - bool bMaterialRestored = false; - FString MaterialShopName; - if (!HoudiniAssetComponent->GetReplacementMaterialShopName(*HoudiniGeoPartObject, MaterialInterface, MaterialShopName)) - { - // This material was not replaced so there's no need to reset it - continue; - } - - // Remove the replacement - HoudiniAssetComponent->RemoveReplacementMaterial(*HoudiniGeoPartObject, MaterialShopName); - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial(MaterialShopName); - if (AssignedMaterial) - MaterialInterfaceReplacement = AssignedMaterial; - - // Replace material on the landscape - Landscape->Modify(); - - if (MaterialIdx == 0) - Landscape->LandscapeMaterial = MaterialInterfaceReplacement; - else - Landscape->LandscapeHoleMaterial = MaterialInterfaceReplacement; - - //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); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - - HoudiniAssetComponent->UpdateEditorProperties(false); - bViewportNeedsUpdate = true; - } - - if (GEditor && bViewportNeedsUpdate) - { - GEditor->RedrawAllViewports(); - } - - return FReply::Handled(); -} -*/ - -void -FHoudiniOutputDetails::OnBrowseTo(UObject* InObject) -{ - if (GEditor) - { - TArray Objects; - Objects.Add(InObject); - GEditor->SyncBrowserToObjects(Objects); - } -} - -TSharedRef -FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent( - UMaterialInterface* MaterialInterface, - UObject* OutputObject, - UHoudiniOutput* InOutput, - int32 MaterialIdx) -{ - TArray AllowedClasses; - AllowedClasses.Add(UMaterialInterface::StaticClass()); - - TArray NewAssetFactories; - - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(MaterialInterface), - true, - AllowedClasses, - NewAssetFactories, - OnShouldFilterMaterialInterface, - FOnAssetSelected::CreateSP( - this, &FHoudiniOutputDetails::OnMaterialInterfaceSelected, OutputObject, InOutput, MaterialIdx), - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::CloseMaterialInterfaceComboButton)); -} - - -void -FHoudiniOutputDetails::CloseMaterialInterfaceComboButton() -{ - -} - -void -FHoudiniOutputDetails::OnMaterialInterfaceDropped( - UObject * InObject, - UStaticMesh * StaticMesh, - UHoudiniOutput * HoudiniOutput, - int32 MaterialIdx) -{ - UMaterialInterface * MaterialInterface = Cast(InObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - return; - - if (!StaticMesh || StaticMesh->IsPendingKill()) - return; - - if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) - return; - - bool bViewportNeedsUpdate = false; - - // Retrieve material interface which is being replaced. - UMaterialInterface * OldMaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; - if (OldMaterialInterface == MaterialInterface) - return; - - // Find the string corresponding to the material that is being replaced - FString MaterialString = FString(); - const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been replaced previously. - MaterialString = *FoundString; - } - else - { - // We have no previous replacement for this material, - // see if we can find it the material assignment list. - FoundString = HoudiniOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been assigned previously. - MaterialString = *FoundString; - } - else - { - UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - if (OldMaterialInterface == DefaultMaterial) - { - // This is replacement for default material. - MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - } - else - { - // External Material? - MaterialString = OldMaterialInterface->GetName(); - } - } - } - - if (MaterialString.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), HoudiniOutput); - - // Add a new material replacement entry. - HoudiniOutput->Modify(); - HoudiniOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); - - // Replace material on static mesh. - StaticMesh->Modify(); - StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = MaterialInterface; - - // Replace the material on any component (SMC/ISMC) that uses the above SM - for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) - { - // Only look at MeshComponents - UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); - if (SMC && !SMC->IsPendingKill()) - { - if (SMC->GetStaticMesh() == StaticMesh) - { - SMC->Modify(); - SMC->SetMaterial(MaterialIdx, MaterialInterface); - } - } - else - { - UStaticMesh* SM = Cast(OutputObject.Value.OutputObject); - if (SM && !SM->IsPendingKill()) - { - SM->Modify(); - SM->SetMaterial(MaterialIdx, MaterialInterface); - } - } - - - - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); - - /* - if(GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); -*/ - if (GEditor) - GEditor->RedrawAllViewports(); -} - -// Delegate used when a valid material has been drag and dropped on a landscape. -void -FHoudiniOutputDetails::OnMaterialInterfaceDropped( - UObject* InDroppedObject, - ALandscapeProxy* InLandscape, - UHoudiniOutput* InOutput, - int32 MaterialIdx) -{ - UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >(InDroppedObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - return; - - if (!InLandscape || InLandscape->IsPendingKill()) - return; - - bool bViewportNeedsUpdate = false; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * OldMaterialInterface = MaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); - if (OldMaterialInterface == MaterialInterface) - return; - - // Find the string corresponding to the material that is being replaced - FString MaterialString = FString(); - const FString* FoundString = InOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been replaced previously. - MaterialString = *FoundString; - } - else - { - // We have no previous replacement for this material, - // see if we can find it the material assignment list. - FoundString = InOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been assigned previously. - MaterialString = *FoundString; - } - else - { - UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - if (OldMaterialInterface == DefaultMaterial) - { - // This is replacement for default material. - MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - } - else - { - // External Material? - if (OldMaterialInterface && !OldMaterialInterface->IsPendingKill()) - MaterialString = OldMaterialInterface->GetName(); - } - } - } - - if (MaterialString.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), InOutput); - - // Add a new material replacement entry. - InOutput->Modify(); - InOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); - - // Replace material on the landscape - InLandscape->Modify(); - - if (MaterialIdx == 0) - InLandscape->LandscapeMaterial = MaterialInterface; - else - InLandscape->LandscapeHoleMaterial = MaterialInterface; - - // Update the landscape components Material instances - InLandscape->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 >(InLandscape->GetClass(), (MaterialIdx == 0) ? TEXT("LandscapeMaterial") : TEXT("LandscapeHoleMaterial")); - if (FoundProperty) - { - FPropertyChangedEvent PropChanged(FoundProperty, EPropertyChangeType::ValueSet); - InLandscape->PostEditChangeProperty(PropChanged); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - */ - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); -} - -void -FHoudiniOutputDetails::OnMaterialInterfaceSelected( - const FAssetData & AssetData, - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - TPairInitializer Pair(OutputObject, MaterialIdx); - TSharedPtr AssetComboButton = MaterialInterfaceComboButtons[Pair]; - if (AssetComboButton.IsValid()) - { - AssetComboButton->SetIsOpen(false); - - UObject * Object = AssetData.GetAsset(); - - UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); - } - - ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } - } -} - -void -FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - if (!OutputObject || OutputObject->IsPendingKill()) - return; - - if (!InOutput || InOutput->IsPendingKill()) - return; - - if (GEditor) - { - TArray CBSelections; - GEditor->GetContentBrowserSelections(CBSelections); - - // Get the first selected material object - UObject* Object = nullptr; - for (auto & CurAssetData : CBSelections) - { - if (CurAssetData.AssetClass != UMaterial::StaticClass()->GetFName() && - CurAssetData.AssetClass != UMaterialInstance::StaticClass()->GetFName() && - CurAssetData.AssetClass != UMaterialInstanceConstant::StaticClass()->GetFName()) - continue; - - Object = CurAssetData.GetAsset(); - break; - } - - if (Object && !Object->IsPendingKill()) - { - UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); - } - - ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } - } - } -} - -void -FHoudiniOutputDetails::CreateInstancerOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // Do not display instancer UI for one-instance instancers - bool OnlyOneInstanceInstancers = true; - for (auto& Iter : InOutput->GetInstancedOutputs()) - { - FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); - if (CurInstanceOutput.OriginalTransforms.Num() <= 1) - continue; - - OnlyOneInstanceInstancers = false; - break; - } - - // This output only has one-instance instancers (SMC), no need to display the instancer UI. - if (OnlyOneInstanceInstancers) - return; - - // Classes allowed for instance variations. - const TArray AllowedClasses = - { - UStaticMesh::StaticClass(), USkeletalMesh::StaticClass(), - AActor::StaticClass(), UBlueprint::StaticClass(), - UFXSystemAsset::StaticClass(), USoundBase::StaticClass() - }; - - // Classes not allowed for instances variations (useless?) - TArray DisallowedClasses = - { - UClass::StaticClass(), ULevel::StaticClass(), - UMaterial::StaticClass(), UTexture::StaticClass() - }; - - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // Lambda for adding new variation objects - auto AddObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) - { - // TODO: undo/redo? - InOutputToUpdate.VariationObjects.Insert(InObject, AtIndex); - InOutputToUpdate.VariationTransformOffsets.Insert(FTransform::Identity, AtIndex); - FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for adding new geometry input objects - auto RemoveObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex) - { - // Also keep one instance object - if (AtIndex < 0 || AtIndex >= InOutputToUpdate.VariationObjects.Num()) - return; - - if (InOutputToUpdate.VariationObjects.Num() == 1) - return; - - // TODO: undo/redo? - InOutputToUpdate.VariationObjects.RemoveAt(AtIndex); - InOutputToUpdate.VariationTransformOffsets.RemoveAt( AtIndex); - FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for updating a variation - auto SetObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) - { - if (!InOutputToUpdate.VariationObjects.IsValidIndex(AtIndex)) - return; - - InOutputToUpdate.VariationObjects[AtIndex] = InObject; - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for changing the transform offset values - auto ChangeTransformOffsetAt = [InOutput]( - FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, - const float& Value, const int32& PosRotScaleIndex, const int32& XYZIndex) - { - bool bChanged = InOutputToUpdate.SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); - if (!bChanged) - return; - - InOutputToUpdate.MarkChanged(true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Get this output's OutputObject - const TMap& OutputObjects = InOutput->GetOutputObjects(); - - // Iterate on all of the output's HGPO - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - // Not an instancer, skip - if (CurHGPO.Type != EHoudiniPartType::Instancer) - continue; - - // Get the label for that instancer - FString InstancerLabel = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); - if (CurHGPO.bHasCustomPartName) - InstancerLabel = CurHGPO.PartName; - - TSharedRef InstancerVerticalBox = SNew(SVerticalBox); - TSharedPtr InstancerHorizontalBox = nullptr; - - // Create a new Group for that instancer - IDetailGroup& InstancerGroup = HouOutputCategory.AddGroup(FName(*InstancerLabel), FText::FromString(InstancerLabel)); - - // Now iterate and display the instance outputs that matches this HGPO - for (auto& Iter : InOutput->GetInstancedOutputs()) - { - FHoudiniOutputObjectIdentifier& CurOutputObjectIdentifier = Iter.Key; - if (!CurOutputObjectIdentifier.Matches(CurHGPO)) - continue; - - FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); - - // Dont display instancer UI for one-instance instancers (SMC) - if (CurInstanceOutput.OriginalTransforms.Num() <= 1) - continue; - - for( int32 VariationIdx = 0; VariationIdx < CurInstanceOutput.VariationObjects.Num(); VariationIdx++ ) - { - UObject * InstancedObject = CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous(); - if ( !InstancedObject || InstancedObject->IsPendingKill() ) - { - HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); - continue; - } - - // Create thumbnail for this object. - TSharedPtr VariationThumbnail = - MakeShareable(new FAssetThumbnail(InstancedObject, 64, 64, AssetThumbnailPool)); - TSharedRef PickerVerticalBox = SNew(SVerticalBox); - TSharedPtr PickerHorizontalBox = nullptr; - TSharedPtr VariationThumbnailBorder; - - // For the variation name, reuse the instancer label and append the variation index if we have more than one variation - FString InstanceOutputLabel = InstancerLabel; - if(CurInstanceOutput.VariationObjects.Num() > 1) - InstanceOutputLabel += TEXT(" [") + FString::FromInt(VariationIdx) + TEXT("]"); - - IDetailGroup* DetailGroup = &InstancerGroup; - if (CurInstanceOutput.VariationObjects.Num() > 1) - { - // If we have more than one variation, add a new group for each variation - DetailGroup = &InstancerGroup.AddGroup(FName(*InstanceOutputLabel), FText::FromString(InstanceOutputLabel), true); - } - - // See if we can find the corresponding component to get its type - FString InstancerType = TEXT("(Instancer)"); - FHoudiniOutputObjectIdentifier CurVariationIdentifier = CurOutputObjectIdentifier; - CurVariationIdentifier.SplitIdentifier += TEXT("_") + FString::FromInt(VariationIdx); - const FHoudiniOutputObject* VariationOutputObject = OutputObjects.Find(CurVariationIdentifier); - if(VariationOutputObject) - InstancerType = FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(VariationOutputObject->OutputComponent); - - DetailGroup->AddWidgetRow() - .NameContent() - [ - //SNew(SSpacer) - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(InstancerType)) - //.Size(FVector2D(250, 64)) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - PickerVerticalBox - ]; - - // Add an asset drop target - PickerVerticalBox->AddSlot() - .Padding( 0, 2 ) - .AutoHeight() - [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop( SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( - [=]( const UObject* Obj ) { - for ( auto Klass : DisallowedClasses ) - { - if ( Obj && Obj->IsA( Klass ) ) - return false; - } - return true; - }) - ) - .OnAssetDropped_Lambda([&CurInstanceOutput, VariationIdx, SetObjectAt](UObject* InObject) - { - return SetObjectAt(CurInstanceOutput, VariationIdx, InObject); - }) - [ - SAssignNew( PickerHorizontalBox, SHorizontalBox ) - ] - ]; - - PickerHorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(VariationThumbnailBorder, SBorder) - .Padding( 5.0f ) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, InstancedObject) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(InstancedObject->GetPathName())) - [ - VariationThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - VariationThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush *>::Create( - TAttribute::FGetter::CreateLambda([=]() - { - if (VariationThumbnailBorder.IsValid() && VariationThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ) ) ); - - PickerHorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 28.0f, 0.0f, 28.0f) - [ - PropertyCustomizationHelpers::MakeAddButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, AddObjectAt]() - { - UObject* ObjToAdd = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? - CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() - : nullptr; - - return AddObjectAt(CurInstanceOutput, VariationIdx, ObjToAdd); - }), - LOCTEXT("AddAnotherInstanceToolTip", "Add Another Instance")) - ]; - - PickerHorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 28.0f, 4.0f, 28.0f ) - [ - PropertyCustomizationHelpers::MakeRemoveButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, RemoveObjectAt]() - { - return RemoveObjectAt(CurInstanceOutput, VariationIdx); - }), - LOCTEXT("RemoveLastInstanceToolTip", "Remove Last Instance")) - ]; - - TSharedPtr AssetComboButton; - TSharedPtr ButtonBox; - PickerHorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .HAlign(HAlign_Fill) - [ - SAssignNew(ButtonBox, SHorizontalBox) - +SHorizontalBox::Slot() - [ - SAssignNew(AssetComboButton, SComboButton) - //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) - /* TODO: Update UI - .OnMenuOpenChanged( FOnIsOpenChanged::CreateUObject( - &InParam, &UHoudiniAssetInstanceInput::ChangedStaticMeshComboButton, - CurInstanceOutput, InstOutIdx, VariationIdx ) ) - */ - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(InstancedObject->GetName())) - ] - ] - ] - ]; - - // Create asset picker for this combo button. - { - TArray NewAssetFactories; - TSharedRef PropertyMenuAssetPicker = PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(InstancedObject), - true, - AllowedClasses, - DisallowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([&CurInstanceOutput, VariationIdx, SetObjectAt, AssetComboButton](const FAssetData& AssetData) - { - if ( AssetComboButton.IsValid() ) - { - AssetComboButton->SetIsOpen( false ); - UObject * Object = AssetData.GetAsset(); - SetObjectAt( CurInstanceOutput, VariationIdx, Object); - } - }), - // Nothing to do on close - FSimpleDelegate::CreateLambda([](){}) - ); - - AssetComboButton->SetMenuContent(PropertyMenuAssetPicker); - } - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(InstancedObject->GetName())); - FText StaticMeshTooltip = - FText::Format(LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx]() - { - UObject* InputObject = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? - CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() - : nullptr; - - if (GEditor && InputObject) - { - TArray Objects; - Objects.Add(InputObject); - GEditor->SyncBrowserToObjects(Objects); - } - }), - TAttribute< FText >( StaticMeshTooltip ) ) - ]; - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f ) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT( "ResetToBase", "Reset to default static mesh")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([SetObjectAt, &CurInstanceOutput, VariationIdx]() - { - SetObjectAt(CurInstanceOutput, VariationIdx, CurInstanceOutput.OriginalObject.LoadSynchronous()); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - - - // Get Visibility of reset buttons - bool bResetButtonVisiblePosition = false; - bool bResetButtonVisibleRotation = false; - bool bResetButtonVisibleScale = false; - - FTransform CurTransform = CurInstanceOutput.VariationTransformOffsets[VariationIdx]; - - if (CurTransform.GetLocation() != FVector::ZeroVector) - bResetButtonVisiblePosition = true; - - FRotator Rotator = CurTransform.Rotator(); - if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) - bResetButtonVisibleRotation = true; - - if (CurTransform.GetScale3D() != FVector::OneVector) - bResetButtonVisibleScale = true; - - auto ChangeTransformOffsetUniformlyAt = [ChangeTransformOffsetAt, VariationIdx, &CurInstanceOutput](const float& Val, const int32& PosRotScaleIndex) - { - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 0); - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 1); - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 2); - }; - - TSharedRef OffsetVerticalBox = SNew(SVerticalBox); - FText LabelPositionText = LOCTEXT("HoudiniPositionOffset", "Position Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelPositionText) - .ToolTipText(LabelPositionText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .AllowSpin(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 0); } - ))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 1); } - ))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 2); } - ))) - .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 0); }) - .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 1); }) - .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 2); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("InstancerOutputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, CurInstanceOutput, InOutput]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 0); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - ]; - - FText LabelRotationText = LOCTEXT("HoudiniRotationOffset", "Rotation Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelRotationText) - .ToolTipText(LabelRotationText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SRotatorInputBox) - .AllowSpin(true) - .bColorAxisLabels(true) - .Roll(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 0); } - ))) - .Pitch(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 1); } - ))) - .Yaw(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 2); } - ))) - .OnRollCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 0); }) - .OnPitchCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 1); }) - .OnYawCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 2); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 1); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - ]; - - FText LabelScaleText = LOCTEXT("HoudiniScaleOffset", "Scale Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelScaleText) - .ToolTipText(LabelScaleText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 0); } - ))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 1); } - ))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 2); } - ))) - .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 0); - }) - .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 1); - }) - .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 2); - }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("InstancerOutputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the output object maintains its shape in each direction when scaled")) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(CurInstanceOutput.IsUnformScaleLocked() ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([&CurInstanceOutput, InOutput]() - { - CurInstanceOutput.SwitchUniformScaleLock(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() - { - ChangeTransformOffsetUniformlyAt(1.0f, 2); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - /* - // TODO: Add support for this back - + SHorizontalBox::Slot().AutoWidth() - [ - // Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "TransparentCheckBox") - .ToolTipText(LOCTEXT("PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled")) - *//* - .OnCheckStateChanged(FOnCheckStateChanged::CreateLambda([=](ECheckBoxState NewState) - { - if ( MyParam.IsValid() && InputFieldPtr.IsValid() ) - MyParam->CheckStateChanged( NewState == ECheckBoxState::Checked, InputFieldPtr.Get(), VariationIdx ); - })) - .IsChecked( TAttribute< ECheckBoxState >::Create( - TAttribute::FGetter::CreateLambda( [=]() - { - if (InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly(VariationIdx)) - return ECheckBoxState::Checked; - return ECheckBoxState::Unchecked; - } - ))) - *//* - [ - SNew(SImage) - *//*.Image(TAttribute::Create( - TAttribute::FGetter::CreateLambda( [=]() - { - if ( InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly( VariationIdx ) ) - { - return FEditorStyle::GetBrush( TEXT( "GenericLock" ) ); - } - return FEditorStyle::GetBrush( TEXT( "GenericUnlock" ) ); - } - ))) - *//* - .ColorAndOpacity( FSlateColor::UseForeground() ) - ] - ] - */ - ]; - } - } - } -} - -/* -void -FHoudiniOutputDetails::OnMaterialInterfaceSelected( - const FAssetData & AssetData, - ALandscapeProxy* Landscape, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); - TSharedPtr< SComboButton > AssetComboButton = LandscapeMaterialInterfaceComboButtons[Pair]; - if (AssetComboButton.IsValid()) - { - AssetComboButton->SetIsOpen(false); - - UObject * Object = AssetData.GetAsset(); - OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } -} -*/ - -void -FHoudiniOutputDetails::CreateDefaultOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput) - return; - - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); - - // TODO - // This is just a temporary placeholder displaying name/output type - { - FString OutputNameStr = InOutput->GetName(); - FText OutputTooltip = GetOutputTooltip(InOutput); - - // Create a new detail row - // Name - FText OutputNameTxt = GetOutputDebugName(InOutput); - FDetailWidgetRow & Row = HouOutputCategory.AddCustomRow(FText::GetEmpty()); - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(OutputNameTxt) - .ToolTipText(OutputTooltip) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - - // Value - FText OutputTypeTxt = GetOutputDebugDescription(InOutput); - Row.ValueWidget.Widget = - SNew(STextBlock) - .Text(OutputTypeTxt) - .ToolTipText(OutputTooltip) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - } -} - -void -FHoudiniOutputDetails::OnBakeOutputObject( - const FString& InBakeName, - UObject * BakedOutputObject, - const FHoudiniOutputObjectIdentifier & OutputIdentifier, - const FHoudiniGeoPartObject & HGPO, - const FString & HoudiniAssetName, - const FString & BakeFolder, - const FString & TempCookFolder, - const EHoudiniOutputType & Type, - const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, - const TArray& InAllOutputs) -{ - if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) - return; - - FString ObjectName = InBakeName; - - // Set Object name according to priority Default Name > Attrib Custom Name > UI Custom Name - if(InBakeName.IsEmpty()) - { - if (HGPO.bHasCustomPartName) - ObjectName = HGPO.PartName; - else - ObjectName = BakedOutputObject->GetName(); - } - - // Fill in the package params - FHoudiniPackageParams PackageParams; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - OutputIdentifier, - BakeFolder, - ObjectName, - HoudiniAssetName); - - switch (Type) - { - case EHoudiniOutputType::Mesh: - { - UStaticMesh* StaticMesh = Cast(BakedOutputObject); - if (StaticMesh) - { - FDirectoryPath TempCookFolderPath; - TempCookFolderPath.Path = TempCookFolder; - UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( - StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath); - } - } - break; - case EHoudiniOutputType::Curve: - { - USplineComponent* SplineComponent = Cast(BakedOutputObject); - if (SplineComponent) - { - AActor* BakedActor; - USplineComponent* BakedSplineComponent; - FHoudiniEngineBakeUtils::BakeCurve(SplineComponent, GWorld->GetCurrentLevel(), PackageParams, BakedActor, BakedSplineComponent); - } - } - break; - case EHoudiniOutputType::Landscape: - { - ALandscapeProxy* Landscape = Cast(BakedOutputObject); - if (Landscape) - { - FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType); - } - } - break; - } -} - -FReply -FHoudiniOutputDetails::OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput) -{ - // TODO: Actually refine only the selected ProxyMesh - // For now, refine all the selection - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true, true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); -} - -void -FHoudiniOutputDetails::OnBakeNameCommitted( - const FText& Val, ETextCommit::Type TextCommitType, - UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - if (!InOutput) - return; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - - if (!FoundOutputObject) - return; - - FoundOutputObject->BakeName = Val.ToString(); -} - -void -FHoudiniOutputDetails::OnRevertBakeNameToDefault(UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier & InIdentifier) -{ - if (!InOutput) - return; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - - if (!FoundOutputObject) - return; - - FoundOutputObject->BakeName = FString(); -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniOutputDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAsset.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniEngineCommands.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailGroup.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SRotatorInputBox.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Text/STextBlock.h" +#include "Editor/UnrealEd/Public/AssetThumbnail.h" +#include "SAssetDropTarget.h" +#include "Engine/StaticMesh.h" +#include "Components/SplineComponent.h" +#include "Materials/Material.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceConstant.h" +#include "Sound/SoundBase.h" +#include "Engine/SkeletalMesh.h" +#include "Particles/ParticleSystem.h" +//#include "Landscape.h" +#include "LandscapeProxy.h" +#include "ScopedTransaction.h" +#include "PhysicsEngine/BodySetup.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +void +FHoudiniOutputDetails::CreateWidget( + IDetailCategoryBuilder& HouOutputCategory, + TArray InOutputs) +{ + if (InOutputs.Num() <= 0) + return; + + UHoudiniOutput* MainOutput = InOutputs[0]; + + // Don't create UI for editable curve. + if (!MainOutput || MainOutput->IsPendingKill() || MainOutput->IsEditableNode()) + return; + + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); + + // TODO + // For now we just handle Mesh Outputs + + switch (MainOutput->GetType()) + { + case EHoudiniOutputType::Mesh: + { + FHoudiniOutputDetails::CreateMeshOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Landscape: + { + FHoudiniOutputDetails::CreateLandscapeOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Instancer: + { + FHoudiniOutputDetails::CreateInstancerOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Curve: + { + FHoudiniOutputDetails::CreateCurveOutputWidget(HouOutputCategory, MainOutput); + break; + } + case EHoudiniOutputType::Skeletal: + default: + { + FHoudiniOutputDetails::CreateDefaultOutputWidget(HouOutputCategory, MainOutput); + break; + } + + } + +} + + +void +FHoudiniOutputDetails::CreateLandscapeOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // Go through this output's objects + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& CurrentOutputObj : OutputObjects) + { + UHoudiniLandscapePtr* LandscapePointer = Cast(CurrentOutputObj.Value.OutputObject); + if (!LandscapePointer) + continue; + + FHoudiniOutputObjectIdentifier& Identifier = CurrentOutputObj.Key; + const FHoudiniGeoPartObject *HGPO = nullptr; + for (const auto& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!Identifier.Matches(CurHGPO)) + continue; + + HGPO = &CurHGPO; + break; + } + + if (!HGPO) + continue; + + CreateLandscapeOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapePointer, Identifier); + } +} + +void +FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + const FHoudiniGeoPartObject& HGPO, + UHoudiniLandscapePtr* LandscapePointer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier) +{ + if (!LandscapePointer || LandscapePointer->IsPendingKill() || !LandscapePointer->LandscapeSoftPtr.IsValid()) + return; + + if (!InOutput || InOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return; + + ALandscapeProxy * Landscape = LandscapePointer->LandscapeSoftPtr.Get(); + if (!Landscape || Landscape->IsPendingKill()) + return; + + // TODO: Get bake base name + FString Label = Landscape->GetName(); + + EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType = LandscapePointer->BakeType; + + // Get thumbnail pool for this builder + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + TArray>* BakeOptionString = FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels(); + + // Create bake mesh name textfield. + IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(Label)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + // Create the thumbnail for the landscape output object. + TSharedPtr< FAssetThumbnail > LandscapeThumbnail = + MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); + + TSharedPtr< SBorder > LandscapeThumbnailBorder; + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(SSpacer) + .Size(FVector2D(250, 64)) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot().Padding(0, 2).AutoHeight() + [ + SNew(SBox).WidthOverride(175) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(LandscapeThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(Landscape->GetPathName())) + [ + LandscapeThumbnail->MakeThumbnailWidget() + ] + ] + ] + + + SHorizontalBox::Slot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SBox).WidthOverride(40.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("Bake", "Bake")) + .IsEnabled(true) + .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() + { + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + if (FoundOutputObject) + { + TArray AllOutputs; + AllOutputs.Reserve(HAC->GetNumOutputs()); + HAC->GetOutputs(AllOutputs); + FHoudiniOutputDetails::OnBakeOutputObject( + FoundOutputObject->BakeName, + Landscape, + OutputIdentifier, + HGPO, + OwnerActor->GetName(), + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + InOutput->GetType(), + LandscapeOutputBakeType, + AllOutputs); + } + + // TODO: Remove the output landscape if the landscape bake type is Detachment? + return FReply::Handled(); + }) + .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) + ] + ] + + SHorizontalBox::Slot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SBox).WidthOverride(120.f) + [ + SNew(SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + if (SelectType != ESelectInfo::Type::OnMouseClick) + return; + + FString *NewChoiceStr = NewChoice.Get(); + if (!NewChoiceStr) + return; + + if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); + } + else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); + } + else + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); + } + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + [ + SNew(STextBlock) + .Text_Lambda([LandscapePointer]() + { + FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); + return FText::FromString(BakeTypeString); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ] + ] + ]; + + // Store thumbnail for this landscape. + OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); + + // We need to add material box for each the landscape and landscape hole materials + for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) + { + UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + TSharedPtr MaterialThumbnailBorder; + TSharedPtr HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if (MaterialInterface) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + + // Create thumbnail for this material. + TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(STextBlock) + .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().Padding(0, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) + .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(MaterialThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(MaterialPathName)) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this landscape and material index. + { + TPairInitializer Pair(Landscape, MaterialIdx); + MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + } + + // Combox Box and Button Box + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Combo row + TSharedPtr< SComboButton > AssetComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) + [ + SAssignNew(AssetComboButton, SComboButton) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + // Buttons row + TSharedPtr ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Add use Content Browser selection arrow + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)Landscape, InOutput, MaterialIdx), + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + ]; + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), + TAttribute< FText >(MaterialTooltip)) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + + // Store combo button for this mesh and index. + { + TPairInitializer Pair(Landscape, MaterialIdx); + MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + } + } + +} + +void +FHoudiniOutputDetails::CreateMeshOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent* HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + FString HoudiniAssetName; + if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) + { + HoudiniAssetName = HAC->GetOwner()->GetName(); + } + else if (HAC->GetHoudiniAsset()) + { + HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); + } + else + { + HoudiniAssetName = HAC->GetName(); + } + + // Go through this output's object + int32 OutputObjIdx = 0; + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& IterObject : OutputObjects) + { + UStaticMesh* StaticMesh = Cast(IterObject.Value.OutputObject); + UHoudiniStaticMesh* ProxyMesh = Cast(IterObject.Value.ProxyObject); + + if ((!StaticMesh || StaticMesh->IsPendingKill()) + && (!ProxyMesh || ProxyMesh->IsPendingKill())) + continue; + + FHoudiniOutputObjectIdentifier & OutputIdentifier = IterObject.Key; + + // Find the corresponding HGPO in the output + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!OutputIdentifier.Matches(curHGPO)) + continue; + + HoudiniGeoPartObject = curHGPO; + break; + } + + if (StaticMesh && !StaticMesh->IsPendingKill()) + { + bool bIsProxyMeshCurrent = IterObject.Value.bProxyIsCurrent; + + // If we have a static mesh, alway display its widget even if the proxy is more recent + CreateStaticMeshAndMaterialWidgets( + HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); + } + else + { + // If we only have a proxy mesh, then show the proxy widget + CreateProxyMeshAndMaterialWidgets( + HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject); + } + } +} + +void +FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + int32 OutputObjIdx = 0; + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& IterObject : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = IterObject.Value; + USceneComponent* SplineComponent = Cast(IterObject.Value.OutputComponent); + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + FHoudiniOutputObjectIdentifier& OutputIdentifier = IterObject.Key; + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!OutputIdentifier.Matches(curHGPO)) + continue; + + HoudiniGeoPartObject = curHGPO; + break; + } + + CreateCurveWidgets(HouOutputCategory, InOutput, SplineComponent, CurrentOutputObject, OutputIdentifier, HoudiniGeoPartObject); + } +} + +void +FHoudiniOutputDetails::CreateCurveWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + USceneComponent* SplineComponent, + FHoudiniOutputObject& OutputObject, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + FHoudiniGeoPartObject& HoudiniGeoPartObject) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // We support Unreal Spline out only for now + USplineComponent* SplineOutput = Cast(SplineComponent); + if (!SplineOutput || SplineOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return; + + FHoudiniCurveOutputProperties* OutputProperty = &(OutputObject.CurveOutputProperty); + EHoudiniCurveType OutputCurveType = OutputObject.CurveOutputProperty.CurveType; + + FString Label = SplineComponent->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + //Label += FString("_") + OutputIdentifier.SplitIdentifier; + + FString OutputCurveName = OutputObject.BakeName; + if(OutputCurveName.IsEmpty()) + OutputCurveName = OwnerActor->GetName() + "_" + Label; + + const FText& LabelText = FText::FromString("Unreal Spline"); + + IDetailGroup& CurveOutputGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label), false, false); + + // Bake name row UI + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(OutputObject.BakeName)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + ] + + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("OutputCurveSplineType", "Spline Type")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText_Lambda([SplineOutput, Label, OutputCurveType]() + { + FString ToolTipStr = FString::Printf(TEXT(" curve: %s\n Export type: Unreal Spline\n num points: %d\n curve type: %s\n closed: %s"), + *Label, + SplineOutput->GetNumberOfSplinePoints(), + *FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(OutputCurveType), + SplineOutput->IsClosedLoop() ? *(FString("yes")) : *(FString("no"))); + + return FText::FromString(ToolTipStr); + }) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(STextBlock) + // We support Unreal Spline output only for now... + .Text(LOCTEXT("OutputCurveSplineTypeUnreal", "Unreal Spline")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + + //if (bIsUnrealSpline) + //{ + USplineComponent* UnrealSpline = Cast(SplineComponent); + + // Curve type combo box UI + auto InitialSelectionLambda = [OutputProperty]() + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + { + return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[0]; + } + else + { + return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[1]; + } + }; + + TSharedPtr>> UnrealCurveTypeComboBox; + + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(LOCTEXT("OutputCurveUnrealSplinePointType", "Spline Point Type")) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(UnrealCurveTypeComboBox, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels()) + .InitiallySelectedItem(InitialSelectionLambda()) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [OutputProperty, InOutput, SplineComponent](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + // Set the curve point type locally + USplineComponent* Spline = Cast(SplineComponent); + if (!Spline || Spline->IsPendingKill()) + return; + + FString *NewChoiceStr = NewChoice.Get(); + if (!NewChoiceStr) + return; + + if (*NewChoiceStr == "Linear") + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + return; + + OutputProperty->CurveType = EHoudiniCurveType::Polygon; + + for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) + { + Spline->SetSplinePointType(PtIdx, ESplinePointType::Linear); + } + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + } + else if (*NewChoiceStr == "Curve") + { + if (OutputProperty->CurveType != EHoudiniCurveType::Polygon) + return; + + OutputProperty->CurveType = EHoudiniCurveType::Bezier; + + for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) + { + Spline->SetSplinePointType(PtIdx, ESplinePointType::Curve); + } + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([OutputProperty]() + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + return FText::FromString(TEXT("Linear")); + else + return FText::FromString(TEXT("Curve")); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + // Add closed curve checkbox UI + TSharedPtr ClosedCheckBox; + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(LOCTEXT("OutputCurveUnrealSplineClosed", "Closed")) + ] + .ValueContent() + [ + SAssignNew(ClosedCheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([UnrealSpline, InOutput](ECheckBoxState NewState) + { + if (!UnrealSpline || UnrealSpline->IsPendingKill()) + return; + + UnrealSpline->SetClosedLoop(NewState == ECheckBoxState::Checked); + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + .IsChecked_Lambda([UnrealSpline]() + { + if (!UnrealSpline || UnrealSpline->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return UnrealSpline->IsClosedLoop() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + ]; + //} + + // Add Bake Button UI + TSharedPtr BakeButton; + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + ] + .ValueContent() + [ + SAssignNew(BakeButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) + .IsEnabled(true) + .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) + .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName]() + { + TArray AllOutputs; + AllOutputs.Reserve(HAC->GetNumOutputs()); + HAC->GetOutputs(AllOutputs); + FHoudiniOutputDetails::OnBakeOutputObject( + OutputCurveName, + SplineComponent, + OutputIdentifier, + HoudiniGeoPartObject, + OwnerActor->GetName(), + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + InOutput->GetType(), + EHoudiniLandscapeOutputBakeType::InValid, + AllOutputs); + + return FReply::Handled(); + }) + ]; +} + +void +FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UStaticMesh * StaticMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + const bool& bIsProxyMeshCurrent) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return; + + UHoudiniAssetComponent* OwningHAC = Cast(InOutput->GetOuter()); + + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // TODO: GetBakingBaseName! + FString Label = StaticMesh->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + // Create thumbnail for this mesh. + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = + MakeShareable(new FAssetThumbnail(StaticMesh, 64, 64, AssetThumbnailPool)); + TSharedPtr StaticMeshThumbnailBorder; + + TSharedRef VerticalBox = SNew(SVerticalBox); + + IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(BakeName)) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + }) + .ToolTipText( LOCTEXT( "BakeNameTip", "The base name of the baked asset") ) + ] + + +SHorizontalBox::Slot() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "RevertNameOverride", "Revert bake name override" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ] + ]; + + // Add details on the SM colliders + EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); + FString MeshLabel = TEXT( "Static Mesh" ); + + // If the Proxy mesh is more recent, indicate it in the details + if (bIsProxyMeshCurrent) + { + MeshLabel += TEXT("\n(unrefined)"); + } + + // Indicate that this mesh is instanced + if (HoudiniGeoPartObject.bIsInstanced) + { + MeshLabel += TEXT("\n(instanced)"); + } + + if (HoudiniGeoPartObject.bIsTemplated) + { + MeshLabel += TEXT("\n(templated)"); + } + + int32 NumSimpleColliders = 0; + if (StaticMesh->BodySetup && !StaticMesh->BodySetup->IsPendingKill()) + NumSimpleColliders = StaticMesh->BodySetup->AggGeom.GetElementCount(); + + if(NumSimpleColliders > 0) + { + MeshLabel += TEXT( "\n(") + FString::FromInt(NumSimpleColliders) + TEXT(" Simple Collider" ); + if (NumSimpleColliders > 1 ) + MeshLabel += TEXT("s"); + MeshLabel += TEXT(")"); + } + else if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + MeshLabel += TEXT( "\n(Rendered Complex Collider)" ); + } + else if(SplitType == EHoudiniSplitType::InvisibleComplexCollider ) + { + MeshLabel += TEXT( "\n(Invisible Complex Collider)" ); + } + + if ( StaticMesh->GetNumLODs() > 1 ) + MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)"); + + if ( StaticMesh->Sockets.Num() > 0 ) + MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->Sockets.Num() ) + TEXT(" sockets)"); + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew( STextBlock ) + .Text( FText::FromString(MeshLabel) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot() + .Padding( 0, 2 ) + .AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) + .AutoWidth() + [ + SAssignNew( StaticMeshThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)StaticMesh ) + .OnMouseDoubleClick( this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *) StaticMesh ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( StaticMesh->GetPathName() ) ) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ] + + +SHorizontalBox::Slot() + .FillWidth( 1.0f ) + .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SVerticalBox ) + +SVerticalBox::Slot() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .MaxWidth( 80.0f ) + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .Text( LOCTEXT( "Bake", "Bake" ) ) + .IsEnabled(true) + .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC]() + { + TArray AllOutputs; + FString TempCookFolder; + if (IsValid(OwningHAC)) + { + AllOutputs.Reserve(OwningHAC->GetNumOutputs()); + OwningHAC->GetOutputs(AllOutputs); + + TempCookFolder = OwningHAC->TemporaryCookFolder.Path; + } + FHoudiniOutputDetails::OnBakeOutputObject( + BakeName, + StaticMesh, + OutputIdentifier, + HoudiniGeoPartObject, + HoudiniAssetName, + BakeFolder, + TempCookFolder, + InOutput->GetType(), + EHoudiniLandscapeOutputBakeType::InValid, + AllOutputs); + + return FReply::Handled(); + }) + .ToolTipText( LOCTEXT( "HoudiniStaticMeshBakeButton", "Bake this generated static mesh" ) ) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)StaticMesh), + TAttribute(LOCTEXT("HoudiniStaticMeshBrowseButton", "Browse to this generated static mesh in the content browser"))) + ] + ] + ] + ]; + + // Store thumbnail for this mesh. + OutputObjectThumbnailBorders.Add((UObject*)StaticMesh, StaticMeshThumbnailBorder); + + // We need to add material box for each material present in this static mesh. + auto & StaticMeshMaterials = StaticMesh->StaticMaterials; + for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) + { + UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface; + TSharedPtr< SBorder > MaterialThumbnailBorder; + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if ( MaterialInterface && !MaterialInterface->IsPendingKill() + && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + else + { + MaterialInterface = nullptr; + MaterialName = TEXT("Material (invalid)") + FString::FromInt( MaterialIdx ) ; + MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + } + + // Create thumbnail for this material. + TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + MakeShareable( new FAssetThumbnail( MaterialInterface, 64, 64, AssetThumbnailPool ) ); + + VerticalBox->AddSlot().Padding( 0, 2 ) + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver ) + .OnAssetDropped( + this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx ) + [ + SAssignNew( HorizontalBox, SHorizontalBox ) + ] + ]; + + HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() + [ + SAssignNew( MaterialThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( + this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject *)StaticMesh, MaterialIdx ) + .OnMouseDoubleClick( + this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( MaterialPathName ) ) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this mesh and material index. + { + TPairInitializer Pair( StaticMesh, MaterialIdx ); + MaterialInterfaceThumbnailBorders.Add( Pair, MaterialThumbnailBorder ); + } + + // ComboBox and buttons + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add Combo box + TSharedPtr< SComboButton > AssetComboButton; + ComboAndButtonBox->AddSlot().VAlign(VAlign_Center).FillHeight(1.0f) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().VAlign(VAlign_Center).FillHeight(1.0f) + [ + SAssignNew(AssetComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, + MaterialInterface, (UObject*)StaticMesh, InOutput, MaterialIdx) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + + // Add buttons + TSharedPtr< SHorizontalBox > ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Use CB selection arrow button + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)StaticMesh, InOutput, MaterialIdx), + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + ]; + + // Browse CB button + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface ), TAttribute< FText >( MaterialTooltip ) ) + ]; + + // Reset button + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBaseMaterial", "Reset to base material" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked( + this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + // Store combo button for this mesh and index. + { + TPairInitializer Pair( StaticMesh, MaterialIdx ); + MaterialInterfaceComboButtons.Add( Pair, AssetComboButton ); + } + } +} + +void +FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UHoudiniStaticMesh * ProxyMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject) +{ + if (!ProxyMesh || ProxyMesh->IsPendingKill()) + return; + + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // TODO: GetBakingBaseName! + FString Label = ProxyMesh->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + // Create thumbnail for this mesh. + TSharedPtr MeshThumbnail = MakeShareable(new FAssetThumbnail(ProxyMesh, 64, 64, AssetThumbnailPool)); + TSharedPtr MeshThumbnailBorder; + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(BakeName)) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + }) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + // Add details on the Proxy Mesh + EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); + FString MeshLabel = TEXT("Proxy Mesh"); + + // Indicate that this mesh is instanced + if (HoudiniGeoPartObject.bIsInstanced) + { + MeshLabel += TEXT("\n(instanced)"); + } + + if (HoudiniGeoPartObject.bIsTemplated) + { + MeshLabel += TEXT("\n(templated)"); + } + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(FText::FromString(MeshLabel)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot() + .Padding(0, 2) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(MeshThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)ProxyMesh) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)ProxyMesh) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(ProxyMesh->GetPathName())) + [ + MeshThumbnail->MakeThumbnailWidget() + ] + ] + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .MaxWidth(80.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("Refine", "Refine")) + .IsEnabled(true) + .OnClicked(this, &FHoudiniOutputDetails::OnRefineClicked, (UObject *)ProxyMesh, InOutput) + .ToolTipText(LOCTEXT("RefineTooltip", "Refine this Proxy Mesh to a Static Mesh")) + ] + ] + ] + ]; + + // Store thumbnail for this mesh. + OutputObjectThumbnailBorders.Add(ProxyMesh, MeshThumbnailBorder); + + // We need to add material box for each material present in this static mesh. + auto & ProxyMeshMaterials = ProxyMesh->GetStaticMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < ProxyMeshMaterials.Num(); ++MaterialIdx) + { + UMaterialInterface * MaterialInterface = ProxyMeshMaterials[MaterialIdx].MaterialInterface; + TSharedPtr< SBorder > MaterialThumbnailBorder; + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if (MaterialInterface && !MaterialInterface->IsPendingKill() + && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill()) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + else + { + MaterialInterface = nullptr; + MaterialName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + } + + // Create thumbnail for this material. + TSharedPtr MaterialInterfaceThumbnail = + MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + + // No drop target + VerticalBox->AddSlot() + .Padding(0, 2) + [ + SNew(SAssetDropTarget) + //.OnIsAssetAcceptableForDrop(false) + //.OnAssetDropped( + // this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + HorizontalBox->AddSlot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(MaterialThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage( + this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)ProxyMesh, MaterialIdx) + .OnMouseDoubleClick( + this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(MaterialPathName)) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this mesh and material index. + { + TPairInitializer Pair((UObject*)ProxyMesh, MaterialIdx); + MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + } + + // Combo box and buttons + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add combo box + TSharedPtr AssetComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SAssignNew(AssetComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + /*.OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, + MaterialInterface, StaticMesh, InOutput, MaterialIdx)*/ + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + + TSharedPtr ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Disable the combobutton for proxies + AssetComboButton->SetEnabled(false); + + // Add use selection form content browser array + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + /*FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)ProxyMesh, InOutput, MaterialIdx),*/ + FSimpleDelegate::CreateLambda([]() {}), // Do nothing for proxies + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser")), false) + // Disable the use CB selection button for proxies + ]; + + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP(this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), TAttribute(MaterialTooltip)) + ]; + + /* + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked( + this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + */ + + // Store combo button for this mesh and index. + { + TPairInitializer Pair(ProxyMesh, MaterialIdx); + MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + } + } +} + +FText +FHoudiniOutputDetails::GetOutputDebugName(UHoudiniOutput* InOutput) +{ + // Get the name and type + FString OutputNameStr = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); + + // Then add the number of parts + OutputNameStr += TEXT(" (") + FString::FromInt(InOutput->GetHoudiniGeoPartObjects().Num()) + TEXT(" Part(s))\n"); + + return FText::FromString(OutputNameStr); +} +FText +FHoudiniOutputDetails::GetOutputDebugDescription(UHoudiniOutput* InOutput) +{ + const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); + + FString OutputValStr; + OutputValStr += TEXT("HGPOs:\n"); + for (auto& HGPO : HGPOs) + { + OutputValStr += TEXT(" - ") + HGPO.PartName + TEXT(" (") + FHoudiniGeoPartObject::HoudiniPartTypeToString(HGPO.Type) + TEXT(")"); + + if (HGPO.SplitGroups.Num() > 0) + { + OutputValStr += TEXT("( ") + FString::FromInt(HGPO.SplitGroups.Num()) + TEXT(" splits:"); + for (auto& split : HGPO.SplitGroups) + { + OutputValStr += TEXT(" ") + split; + } + OutputValStr += TEXT(")"); + } + + if (!HGPO.VolumeName.IsEmpty()) + { + OutputValStr += TEXT("( ") + HGPO.VolumeName; + if (HGPO.VolumeTileIndex >= 0) + OutputValStr += TEXT(" tile ") + FString::FromInt(HGPO.VolumeTileIndex); + OutputValStr += TEXT(" )"); + } + + OutputValStr += TEXT("\n"); + } + + // Add output objects if any + TMap AllOutputObj = InOutput->GetOutputObjects(); + if (AllOutputObj.Num() > 0) + { + bool TitleAdded = false; + for (const auto& Iter : AllOutputObj) + { + UObject* OutObject = Iter.Value.OutputObject; + if (OutObject) + { + OutputValStr += OutObject->GetFullName() + TEXT(" (obj)\n"); + } + + UObject* OutComp = Iter.Value.OutputComponent; + if (OutComp) + { + OutputValStr += OutObject->GetFullName() + TEXT(" (comp)\n"); + } + } + } + + return FText::FromString(OutputValStr); +} + +FText +FHoudiniOutputDetails::GetOutputTooltip(UHoudiniOutput* InOutput) +{ + // TODO + return FText(); +} + + +const FSlateBrush * +FHoudiniOutputDetails::GetThumbnailBorder(UObject* Mesh) const +{ + TSharedPtr ThumbnailBorder = OutputObjectThumbnailBorders[Mesh]; + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + + +const FSlateBrush * +FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(UObject* OutputObject, int32 MaterialIdx) const +{ + if (!OutputObject) + return nullptr; + + TPairInitializer Pair(OutputObject, MaterialIdx); + TSharedPtr ThumbnailBorder = MaterialInterfaceThumbnailBorders[Pair]; + + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + +/* +const FSlateBrush * +FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx) const +{ + if (!Landscape) + return nullptr; + + TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); + TSharedPtr< SBorder > ThumbnailBorder = LandscapeMaterialInterfaceThumbnailBorders[Pair]; + + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} +*/ + +FReply +FHoudiniOutputDetails::OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, + const FPointerEvent & InMouseEvent, UObject * Object) +{ + if (Object && GEditor) + GEditor->EditObject(Object); + + return FReply::Handled(); +} + +/* +FReply +FHoudiniOutputDetails::OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject) +{ + if (HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill()) + { + FHoudiniPackageParams PackageParms; + + + FHoudiniEngineBakeUtils::BakeStaticMesh(HoudiniAssetComponent, GeoPartObject, StaticMesh, PackageParms); + // TODO: Bake the SM + + + // We need to locate corresponding geo part object in component. + const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(StaticMesh); + + // (void)FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( + // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); + + } + + return FReply::Handled(); +} +*/ + +bool +FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver(const UObject * InObject) const +{ + return (InObject && InObject->IsA(UMaterialInterface::StaticClass())); +} + + +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + UStaticMesh * StaticMesh, + UHoudiniOutput * HoudiniOutput, + int32 MaterialIdx) +{ + FReply RetValue = FReply::Handled(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return RetValue; + + if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) + return RetValue; + + // Retrieve material interface which is being replaced. + UMaterialInterface * MaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; + if (!MaterialInterface) + return RetValue; + + // Find the string corresponding to the material that is being replaced + const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); + if (!FoundString ) + { + // This material was not replaced, no need to reset it + return RetValue; + } + + // This material has been replaced previously. + FString MaterialString = *FoundString; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), HoudiniOutput); + + // Remove the replacement + HoudiniOutput->Modify(); + HoudiniOutput->GetReplacementMaterials().Remove(MaterialString); + + bool bViewportNeedsUpdate = true; + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + UMaterialInterface * const * FoundMat = HoudiniOutput->GetAssignementMaterials().Find(MaterialString); + if (FoundMat && (*FoundMat)) + AssignMaterial = *FoundMat; + + // Replace material on static mesh. + StaticMesh->Modify(); + StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = AssignMaterial; + + // Replace the material on any component (SMC/ISMC) that uses the above SM + // TODO: ?? Replace for all? + for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) + { + // Only look at MeshComponents + UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); + if (!SMC) + continue; + + if (SMC->GetStaticMesh() != StaticMesh) + continue; + + SMC->Modify(); + SMC->SetMaterial(MaterialIdx, AssignMaterial); + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + return RetValue; +} + +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + ALandscapeProxy* InLandscape, + UHoudiniOutput * InHoudiniOutput, + int32 InMaterialIdx) +{ + FReply RetValue = FReply::Handled(); + if (!InLandscape || InLandscape->IsPendingKill()) + return RetValue; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * MaterialInterface = InMaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); + UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + // Find the string corresponding to the material that is being replaced + const FString* FoundString = InHoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); + if (!FoundString) + { + // This material was not replaced, no need to reset it + return RetValue; + } + + // This material has been replaced previously. + FString MaterialString = *FoundString; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), InHoudiniOutput); + + // Remove the replacement + InHoudiniOutput->Modify(); + InHoudiniOutput->GetReplacementMaterials().Remove(MaterialString); + + bool bViewportNeedsUpdate = true; + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + UMaterialInterface * const * FoundMat = InHoudiniOutput->GetAssignementMaterials().Find(MaterialString); + if (FoundMat && (*FoundMat)) + AssignMaterial = *FoundMat; + + // Replace material on Landscape + InLandscape->Modify(); + if (InMaterialIdx == 0) + InLandscape->LandscapeMaterial = AssignMaterial; + else + InLandscape->LandscapeHoleMaterial = AssignMaterial; + + InLandscape->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); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + */ + + FHoudiniEngineUtils::UpdateEditorProperties(InHoudiniOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + return RetValue; +} +/* +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + ALandscapeProxy * Landscape, UHoudiniOutput * InOutput, int32 MaterialIdx) +{ + bool bViewportNeedsUpdate = false; + + // TODO: Handle me! + for (TArray< UHoudiniAssetComponent * >::TIterator + IterComponents(HoudiniAssetComponents); IterComponents; ++IterComponents) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if (!HoudiniAssetComponent) + continue; + + TWeakObjectPtr* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find(*HoudiniGeoPartObject); + if (!FoundLandscapePtr) + continue; + + ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get(); + if (!FoundLandscape || !FoundLandscape->IsValidLowLevel()) + continue; + + if (FoundLandscape != Landscape) + continue; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + bool bMaterialRestored = false; + FString MaterialShopName; + if (!HoudiniAssetComponent->GetReplacementMaterialShopName(*HoudiniGeoPartObject, MaterialInterface, MaterialShopName)) + { + // This material was not replaced so there's no need to reset it + continue; + } + + // Remove the replacement + HoudiniAssetComponent->RemoveReplacementMaterial(*HoudiniGeoPartObject, MaterialShopName); + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial(MaterialShopName); + if (AssignedMaterial) + MaterialInterfaceReplacement = AssignedMaterial; + + // Replace material on the landscape + Landscape->Modify(); + + if (MaterialIdx == 0) + Landscape->LandscapeMaterial = MaterialInterfaceReplacement; + else + Landscape->LandscapeHoleMaterial = MaterialInterfaceReplacement; + + //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); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + + HoudiniAssetComponent->UpdateEditorProperties(false); + bViewportNeedsUpdate = true; + } + + if (GEditor && bViewportNeedsUpdate) + { + GEditor->RedrawAllViewports(); + } + + return FReply::Handled(); +} +*/ + +void +FHoudiniOutputDetails::OnBrowseTo(UObject* InObject) +{ + if (GEditor) + { + TArray Objects; + Objects.Add(InObject); + GEditor->SyncBrowserToObjects(Objects); + } +} + +TSharedRef +FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent( + UMaterialInterface* MaterialInterface, + UObject* OutputObject, + UHoudiniOutput* InOutput, + int32 MaterialIdx) +{ + TArray AllowedClasses; + AllowedClasses.Add(UMaterialInterface::StaticClass()); + + TArray NewAssetFactories; + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(MaterialInterface), + true, + AllowedClasses, + NewAssetFactories, + OnShouldFilterMaterialInterface, + FOnAssetSelected::CreateSP( + this, &FHoudiniOutputDetails::OnMaterialInterfaceSelected, OutputObject, InOutput, MaterialIdx), + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::CloseMaterialInterfaceComboButton)); +} + + +void +FHoudiniOutputDetails::CloseMaterialInterfaceComboButton() +{ + +} + +void +FHoudiniOutputDetails::OnMaterialInterfaceDropped( + UObject * InObject, + UStaticMesh * StaticMesh, + UHoudiniOutput * HoudiniOutput, + int32 MaterialIdx) +{ + UMaterialInterface * MaterialInterface = Cast(InObject); + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + return; + + if (!StaticMesh || StaticMesh->IsPendingKill()) + return; + + if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) + return; + + bool bViewportNeedsUpdate = false; + + // Retrieve material interface which is being replaced. + UMaterialInterface * OldMaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; + if (OldMaterialInterface == MaterialInterface) + return; + + // Find the string corresponding to the material that is being replaced + FString MaterialString = FString(); + const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been replaced previously. + MaterialString = *FoundString; + } + else + { + // We have no previous replacement for this material, + // see if we can find it the material assignment list. + FoundString = HoudiniOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been assigned previously. + MaterialString = *FoundString; + } + else + { + UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + if (OldMaterialInterface == DefaultMaterial) + { + // This is replacement for default material. + MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + } + else + { + // External Material? + MaterialString = OldMaterialInterface->GetName(); + } + } + } + + if (MaterialString.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), HoudiniOutput); + + // Add a new material replacement entry. + HoudiniOutput->Modify(); + HoudiniOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); + + // Replace material on static mesh. + StaticMesh->Modify(); + StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = MaterialInterface; + + // Replace the material on any component (SMC/ISMC) that uses the above SM + for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) + { + // Only look at MeshComponents + UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); + if (SMC && !SMC->IsPendingKill()) + { + if (SMC->GetStaticMesh() == StaticMesh) + { + SMC->Modify(); + SMC->SetMaterial(MaterialIdx, MaterialInterface); + } + } + else + { + UStaticMesh* SM = Cast(OutputObject.Value.OutputObject); + if (SM && !SM->IsPendingKill()) + { + SM->Modify(); + SM->SetMaterial(MaterialIdx, MaterialInterface); + } + } + + + + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); + + /* + if(GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); +*/ + if (GEditor) + GEditor->RedrawAllViewports(); +} + +// Delegate used when a valid material has been drag and dropped on a landscape. +void +FHoudiniOutputDetails::OnMaterialInterfaceDropped( + UObject* InDroppedObject, + ALandscapeProxy* InLandscape, + UHoudiniOutput* InOutput, + int32 MaterialIdx) +{ + UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >(InDroppedObject); + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + return; + + if (!InLandscape || InLandscape->IsPendingKill()) + return; + + bool bViewportNeedsUpdate = false; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * OldMaterialInterface = MaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); + if (OldMaterialInterface == MaterialInterface) + return; + + // Find the string corresponding to the material that is being replaced + FString MaterialString = FString(); + const FString* FoundString = InOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been replaced previously. + MaterialString = *FoundString; + } + else + { + // We have no previous replacement for this material, + // see if we can find it the material assignment list. + FoundString = InOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been assigned previously. + MaterialString = *FoundString; + } + else + { + UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + if (OldMaterialInterface == DefaultMaterial) + { + // This is replacement for default material. + MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + } + else + { + // External Material? + if (OldMaterialInterface && !OldMaterialInterface->IsPendingKill()) + MaterialString = OldMaterialInterface->GetName(); + } + } + } + + if (MaterialString.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), InOutput); + + // Add a new material replacement entry. + InOutput->Modify(); + InOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); + + // Replace material on the landscape + InLandscape->Modify(); + + if (MaterialIdx == 0) + InLandscape->LandscapeMaterial = MaterialInterface; + else + InLandscape->LandscapeHoleMaterial = MaterialInterface; + + // Update the landscape components Material instances + InLandscape->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 >(InLandscape->GetClass(), (MaterialIdx == 0) ? TEXT("LandscapeMaterial") : TEXT("LandscapeHoleMaterial")); + if (FoundProperty) + { + FPropertyChangedEvent PropChanged(FoundProperty, EPropertyChangeType::ValueSet); + InLandscape->PostEditChangeProperty(PropChanged); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + */ + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); +} + +void +FHoudiniOutputDetails::OnMaterialInterfaceSelected( + const FAssetData & AssetData, + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + TPairInitializer Pair(OutputObject, MaterialIdx); + TSharedPtr AssetComboButton = MaterialInterfaceComboButtons[Pair]; + if (AssetComboButton.IsValid()) + { + AssetComboButton->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + + UStaticMesh* SM = Cast(OutputObject); + if (SM && !SM->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); + } + + ALandscapeProxy* Landscape = Cast(OutputObject); + if (Landscape && !Landscape->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } + } +} + +void +FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + if (!OutputObject || OutputObject->IsPendingKill()) + return; + + if (!InOutput || InOutput->IsPendingKill()) + return; + + if (GEditor) + { + TArray CBSelections; + GEditor->GetContentBrowserSelections(CBSelections); + + // Get the first selected material object + UObject* Object = nullptr; + for (auto & CurAssetData : CBSelections) + { + if (CurAssetData.AssetClass != UMaterial::StaticClass()->GetFName() && + CurAssetData.AssetClass != UMaterialInstance::StaticClass()->GetFName() && + CurAssetData.AssetClass != UMaterialInstanceConstant::StaticClass()->GetFName()) + continue; + + Object = CurAssetData.GetAsset(); + break; + } + + if (Object && !Object->IsPendingKill()) + { + UStaticMesh* SM = Cast(OutputObject); + if (SM && !SM->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); + } + + ALandscapeProxy* Landscape = Cast(OutputObject); + if (Landscape && !Landscape->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } + } + } +} + +void +FHoudiniOutputDetails::CreateInstancerOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // Do not display instancer UI for one-instance instancers + bool OnlyOneInstanceInstancers = true; + for (auto& Iter : InOutput->GetInstancedOutputs()) + { + FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); + if (CurInstanceOutput.OriginalTransforms.Num() <= 1) + continue; + + OnlyOneInstanceInstancers = false; + break; + } + + // This output only has one-instance instancers (SMC), no need to display the instancer UI. + if (OnlyOneInstanceInstancers) + return; + + // Classes allowed for instance variations. + const TArray AllowedClasses = + { + UStaticMesh::StaticClass(), USkeletalMesh::StaticClass(), + AActor::StaticClass(), UBlueprint::StaticClass(), + UFXSystemAsset::StaticClass(), USoundBase::StaticClass() + }; + + // Classes not allowed for instances variations (useless?) + TArray DisallowedClasses = + { + UClass::StaticClass(), ULevel::StaticClass(), + UMaterial::StaticClass(), UTexture::StaticClass() + }; + + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // Lambda for adding new variation objects + auto AddObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) + { + // TODO: undo/redo? + InOutputToUpdate.VariationObjects.Insert(InObject, AtIndex); + InOutputToUpdate.VariationTransformOffsets.Insert(FTransform::Identity, AtIndex); + FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for adding new geometry input objects + auto RemoveObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex) + { + // Also keep one instance object + if (AtIndex < 0 || AtIndex >= InOutputToUpdate.VariationObjects.Num()) + return; + + if (InOutputToUpdate.VariationObjects.Num() == 1) + return; + + // TODO: undo/redo? + InOutputToUpdate.VariationObjects.RemoveAt(AtIndex); + InOutputToUpdate.VariationTransformOffsets.RemoveAt( AtIndex); + FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for updating a variation + auto SetObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) + { + if (!InOutputToUpdate.VariationObjects.IsValidIndex(AtIndex)) + return; + + InOutputToUpdate.VariationObjects[AtIndex] = InObject; + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for changing the transform offset values + auto ChangeTransformOffsetAt = [InOutput]( + FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, + const float& Value, const int32& PosRotScaleIndex, const int32& XYZIndex) + { + bool bChanged = InOutputToUpdate.SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); + if (!bChanged) + return; + + InOutputToUpdate.MarkChanged(true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Get this output's OutputObject + const TMap& OutputObjects = InOutput->GetOutputObjects(); + + // Iterate on all of the output's HGPO + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + // Not an instancer, skip + if (CurHGPO.Type != EHoudiniPartType::Instancer) + continue; + + // Get the label for that instancer + FString InstancerLabel = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); + if (CurHGPO.bHasCustomPartName) + InstancerLabel = CurHGPO.PartName; + + TSharedRef InstancerVerticalBox = SNew(SVerticalBox); + TSharedPtr InstancerHorizontalBox = nullptr; + + // Create a new Group for that instancer + IDetailGroup& InstancerGroup = HouOutputCategory.AddGroup(FName(*InstancerLabel), FText::FromString(InstancerLabel)); + + // Now iterate and display the instance outputs that matches this HGPO + for (auto& Iter : InOutput->GetInstancedOutputs()) + { + FHoudiniOutputObjectIdentifier& CurOutputObjectIdentifier = Iter.Key; + if (!CurOutputObjectIdentifier.Matches(CurHGPO)) + continue; + + FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); + + // Dont display instancer UI for one-instance instancers (SMC) + if (CurInstanceOutput.OriginalTransforms.Num() <= 1) + continue; + + for( int32 VariationIdx = 0; VariationIdx < CurInstanceOutput.VariationObjects.Num(); VariationIdx++ ) + { + UObject * InstancedObject = CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous(); + if ( !InstancedObject || InstancedObject->IsPendingKill() ) + { + HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); + continue; + } + + // Create thumbnail for this object. + TSharedPtr VariationThumbnail = + MakeShareable(new FAssetThumbnail(InstancedObject, 64, 64, AssetThumbnailPool)); + TSharedRef PickerVerticalBox = SNew(SVerticalBox); + TSharedPtr PickerHorizontalBox = nullptr; + TSharedPtr VariationThumbnailBorder; + + // For the variation name, reuse the instancer label and append the variation index if we have more than one variation + FString InstanceOutputLabel = InstancerLabel; + if(CurInstanceOutput.VariationObjects.Num() > 1) + InstanceOutputLabel += TEXT(" [") + FString::FromInt(VariationIdx) + TEXT("]"); + + IDetailGroup* DetailGroup = &InstancerGroup; + if (CurInstanceOutput.VariationObjects.Num() > 1) + { + // If we have more than one variation, add a new group for each variation + DetailGroup = &InstancerGroup.AddGroup(FName(*InstanceOutputLabel), FText::FromString(InstanceOutputLabel), true); + } + + // See if we can find the corresponding component to get its type + FString InstancerType = TEXT("(Instancer)"); + FHoudiniOutputObjectIdentifier CurVariationIdentifier = CurOutputObjectIdentifier; + CurVariationIdentifier.SplitIdentifier += TEXT("_") + FString::FromInt(VariationIdx); + const FHoudiniOutputObject* VariationOutputObject = OutputObjects.Find(CurVariationIdentifier); + if(VariationOutputObject) + InstancerType = FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(VariationOutputObject->OutputComponent); + + DetailGroup->AddWidgetRow() + .NameContent() + [ + //SNew(SSpacer) + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(InstancerType)) + //.Size(FVector2D(250, 64)) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + PickerVerticalBox + ]; + + // Add an asset drop target + PickerVerticalBox->AddSlot() + .Padding( 0, 2 ) + .AutoHeight() + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( + [=]( const UObject* Obj ) { + for ( auto Klass : DisallowedClasses ) + { + if ( Obj && Obj->IsA( Klass ) ) + return false; + } + return true; + }) + ) + .OnAssetDropped_Lambda([&CurInstanceOutput, VariationIdx, SetObjectAt](UObject* InObject) + { + return SetObjectAt(CurInstanceOutput, VariationIdx, InObject); + }) + [ + SAssignNew( PickerHorizontalBox, SHorizontalBox ) + ] + ]; + + PickerHorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(VariationThumbnailBorder, SBorder) + .Padding( 5.0f ) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, InstancedObject) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(InstancedObject->GetPathName())) + [ + VariationThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + VariationThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush *>::Create( + TAttribute::FGetter::CreateLambda([=]() + { + if (VariationThumbnailBorder.IsValid() && VariationThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ) ) ); + + PickerHorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 28.0f, 0.0f, 28.0f) + [ + PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, AddObjectAt]() + { + UObject* ObjToAdd = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? + CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() + : nullptr; + + return AddObjectAt(CurInstanceOutput, VariationIdx, ObjToAdd); + }), + LOCTEXT("AddAnotherInstanceToolTip", "Add Another Instance")) + ]; + + PickerHorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 28.0f, 4.0f, 28.0f ) + [ + PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, RemoveObjectAt]() + { + return RemoveObjectAt(CurInstanceOutput, VariationIdx); + }), + LOCTEXT("RemoveLastInstanceToolTip", "Remove Last Instance")) + ]; + + TSharedPtr AssetComboButton; + TSharedPtr ButtonBox; + PickerHorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .HAlign(HAlign_Fill) + [ + SAssignNew(ButtonBox, SHorizontalBox) + +SHorizontalBox::Slot() + [ + SAssignNew(AssetComboButton, SComboButton) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) + /* TODO: Update UI + .OnMenuOpenChanged( FOnIsOpenChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::ChangedStaticMeshComboButton, + CurInstanceOutput, InstOutIdx, VariationIdx ) ) + */ + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(InstancedObject->GetName())) + ] + ] + ] + ]; + + // Create asset picker for this combo button. + { + TArray NewAssetFactories; + TSharedRef PropertyMenuAssetPicker = PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(InstancedObject), + true, + AllowedClasses, + DisallowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda([&CurInstanceOutput, VariationIdx, SetObjectAt, AssetComboButton](const FAssetData& AssetData) + { + if ( AssetComboButton.IsValid() ) + { + AssetComboButton->SetIsOpen( false ); + UObject * Object = AssetData.GetAsset(); + SetObjectAt( CurInstanceOutput, VariationIdx, Object); + } + }), + // Nothing to do on close + FSimpleDelegate::CreateLambda([](){}) + ); + + AssetComboButton->SetMenuContent(PropertyMenuAssetPicker); + } + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(InstancedObject->GetName())); + FText StaticMeshTooltip = + FText::Format(LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx]() + { + UObject* InputObject = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? + CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() + : nullptr; + + if (GEditor && InputObject) + { + TArray Objects; + Objects.Add(InputObject); + GEditor->SyncBrowserToObjects(Objects); + } + }), + TAttribute< FText >( StaticMeshTooltip ) ) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f ) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT( "ResetToBase", "Reset to default static mesh")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([SetObjectAt, &CurInstanceOutput, VariationIdx]() + { + SetObjectAt(CurInstanceOutput, VariationIdx, CurInstanceOutput.OriginalObject.LoadSynchronous()); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + + + // Get Visibility of reset buttons + bool bResetButtonVisiblePosition = false; + bool bResetButtonVisibleRotation = false; + bool bResetButtonVisibleScale = false; + + FTransform CurTransform = CurInstanceOutput.VariationTransformOffsets[VariationIdx]; + + if (CurTransform.GetLocation() != FVector::ZeroVector) + bResetButtonVisiblePosition = true; + + FRotator Rotator = CurTransform.Rotator(); + if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) + bResetButtonVisibleRotation = true; + + if (CurTransform.GetScale3D() != FVector::OneVector) + bResetButtonVisibleScale = true; + + auto ChangeTransformOffsetUniformlyAt = [ChangeTransformOffsetAt, VariationIdx, &CurInstanceOutput](const float& Val, const int32& PosRotScaleIndex) + { + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 0); + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 1); + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 2); + }; + + TSharedRef OffsetVerticalBox = SNew(SVerticalBox); + FText LabelPositionText = LOCTEXT("HoudiniPositionOffset", "Position Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelPositionText) + .ToolTipText(LabelPositionText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .AllowSpin(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 0); } + ))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 1); } + ))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 2); } + ))) + .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 0); }) + .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 1); }) + .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 2); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("InstancerOutputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, CurInstanceOutput, InOutput]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 0); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + ]; + + FText LabelRotationText = LOCTEXT("HoudiniRotationOffset", "Rotation Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelRotationText) + .ToolTipText(LabelRotationText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SRotatorInputBox) + .AllowSpin(true) + .bColorAxisLabels(true) + .Roll(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 0); } + ))) + .Pitch(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 1); } + ))) + .Yaw(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 2); } + ))) + .OnRollCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 0); }) + .OnPitchCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 1); }) + .OnYawCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 2); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 1); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + ]; + + FText LabelScaleText = LOCTEXT("HoudiniScaleOffset", "Scale Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelScaleText) + .ToolTipText(LabelScaleText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 0); } + ))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 1); } + ))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 2); } + ))) + .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 0); + }) + .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 1); + }) + .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 2); + }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("InstancerOutputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the output object maintains its shape in each direction when scaled")) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(CurInstanceOutput.IsUnformScaleLocked() ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([&CurInstanceOutput, InOutput]() + { + CurInstanceOutput.SwitchUniformScaleLock(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() + { + ChangeTransformOffsetUniformlyAt(1.0f, 2); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + /* + // TODO: Add support for this back + + SHorizontalBox::Slot().AutoWidth() + [ + // Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "TransparentCheckBox") + .ToolTipText(LOCTEXT("PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled")) + *//* + .OnCheckStateChanged(FOnCheckStateChanged::CreateLambda([=](ECheckBoxState NewState) + { + if ( MyParam.IsValid() && InputFieldPtr.IsValid() ) + MyParam->CheckStateChanged( NewState == ECheckBoxState::Checked, InputFieldPtr.Get(), VariationIdx ); + })) + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute::FGetter::CreateLambda( [=]() + { + if (InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly(VariationIdx)) + return ECheckBoxState::Checked; + return ECheckBoxState::Unchecked; + } + ))) + *//* + [ + SNew(SImage) + *//*.Image(TAttribute::Create( + TAttribute::FGetter::CreateLambda( [=]() + { + if ( InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly( VariationIdx ) ) + { + return FEditorStyle::GetBrush( TEXT( "GenericLock" ) ); + } + return FEditorStyle::GetBrush( TEXT( "GenericUnlock" ) ); + } + ))) + *//* + .ColorAndOpacity( FSlateColor::UseForeground() ) + ] + ] + */ + ]; + } + } + } +} + +/* +void +FHoudiniOutputDetails::OnMaterialInterfaceSelected( + const FAssetData & AssetData, + ALandscapeProxy* Landscape, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); + TSharedPtr< SComboButton > AssetComboButton = LandscapeMaterialInterfaceComboButtons[Pair]; + if (AssetComboButton.IsValid()) + { + AssetComboButton->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } +} +*/ + +void +FHoudiniOutputDetails::CreateDefaultOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput) + return; + + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); + + // TODO + // This is just a temporary placeholder displaying name/output type + { + FString OutputNameStr = InOutput->GetName(); + FText OutputTooltip = GetOutputTooltip(InOutput); + + // Create a new detail row + // Name + FText OutputNameTxt = GetOutputDebugName(InOutput); + FDetailWidgetRow & Row = HouOutputCategory.AddCustomRow(FText::GetEmpty()); + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(OutputNameTxt) + .ToolTipText(OutputTooltip) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + + // Value + FText OutputTypeTxt = GetOutputDebugDescription(InOutput); + Row.ValueWidget.Widget = + SNew(STextBlock) + .Text(OutputTypeTxt) + .ToolTipText(OutputTooltip) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + } +} + +void +FHoudiniOutputDetails::OnBakeOutputObject( + const FString& InBakeName, + UObject * BakedOutputObject, + const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniGeoPartObject & HGPO, + const FString & HoudiniAssetName, + const FString & BakeFolder, + const FString & TempCookFolder, + const EHoudiniOutputType & Type, + const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, + const TArray& InAllOutputs) +{ + if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) + return; + + FString ObjectName = InBakeName; + + // Set Object name according to priority Default Name > Attrib Custom Name > UI Custom Name + if(InBakeName.IsEmpty()) + { + if (HGPO.bHasCustomPartName) + ObjectName = HGPO.PartName; + else + ObjectName = BakedOutputObject->GetName(); + } + + // Fill in the package params + FHoudiniPackageParams PackageParams; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + OutputIdentifier, + BakeFolder, + ObjectName, + HoudiniAssetName); + + switch (Type) + { + case EHoudiniOutputType::Mesh: + { + UStaticMesh* StaticMesh = Cast(BakedOutputObject); + if (StaticMesh) + { + FDirectoryPath TempCookFolderPath; + TempCookFolderPath.Path = TempCookFolder; + UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( + StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath); + } + } + break; + case EHoudiniOutputType::Curve: + { + USplineComponent* SplineComponent = Cast(BakedOutputObject); + if (SplineComponent) + { + AActor* BakedActor; + USplineComponent* BakedSplineComponent; + FHoudiniEngineBakeUtils::BakeCurve(SplineComponent, GWorld->GetCurrentLevel(), PackageParams, BakedActor, BakedSplineComponent); + } + } + break; + case EHoudiniOutputType::Landscape: + { + ALandscapeProxy* Landscape = Cast(BakedOutputObject); + if (Landscape) + { + FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType); + } + } + break; + } +} + +FReply +FHoudiniOutputDetails::OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput) +{ + // TODO: Actually refine only the selected ProxyMesh + // For now, refine all the selection + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true, true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); +} + +void +FHoudiniOutputDetails::OnBakeNameCommitted( + const FText& Val, ETextCommit::Type TextCommitType, + UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + if (!InOutput) + return; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + + if (!FoundOutputObject) + return; + + FoundOutputObject->BakeName = Val.ToString(); +} + +void +FHoudiniOutputDetails::OnRevertBakeNameToDefault(UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier & InIdentifier) +{ + if (!InOutput) + return; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + + if (!FoundOutputObject) + return; + + FoundOutputObject->BakeName = FString(); +} #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h index f6d5b36d6..e3b9b2342 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h @@ -1,212 +1,212 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "ContentBrowserDelegates.h" - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class UHoudiniOutput; -class UHoudiniAssetComponent; -class FAssetThumbnailPool; -class ALandscapeProxy; -class USplineComponent; -class UHoudiniLandscapePtr; -class UHoudiniStaticMesh; -class UMaterialInterface; -class SBorder; -class SComboButton; - -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniOutputObject; - -enum class EHoudiniOutputType : uint8; -enum class EHoudiniLandscapeOutputBakeType : uint8; - -class FHoudiniOutputDetails : public TSharedFromThis -{ -public: - void CreateWidget( - IDetailCategoryBuilder& HouInputCategoryBuilder, - TArray InOutputs); - - void CreateMeshOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateCurveOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateStaticMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UStaticMesh * StaticMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject, - const bool& bIsProxyMeshCurrent); - - void CreateProxyMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UHoudiniStaticMesh * ProxyMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject); - - void CreateCurveWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - USceneComponent* SplineComponent, - FHoudiniOutputObject& OutputObject, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - FHoudiniGeoPartObject& HoudiniGeoPartObject); - - void CreateLandscapeOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateLandscapeOutputWidget_Helper( - IDetailCategoryBuilder & HouOutputCategory, - UHoudiniOutput * InOutput, - const FHoudiniGeoPartObject & HGPO, - UHoudiniLandscapePtr * LandscapePointer, - const FHoudiniOutputObjectIdentifier & OutputIdentifier); - - void CreateInstancerOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput * InOutput); - - void CreateDefaultOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - static FText GetOutputTooltip(UHoudiniOutput* MainOutput); - static FText GetOutputDebugName(UHoudiniOutput* InOutput); - static FText GetOutputDebugDescription(UHoudiniOutput* InOutput); - - static void OnBakeNameCommitted( - const FText& Val, ETextCommit::Type TextCommitType, - UHoudiniOutput * InOutput, - const FHoudiniOutputObjectIdentifier & InIdentifier); - - static void OnRevertBakeNameToDefault( - UHoudiniOutput * InOutput, - const FHoudiniOutputObjectIdentifier & InIdentifier); - - static void OnBakeOutputObject( - const FString& InBakeName, - UObject * BakedOutputObject, - const FHoudiniOutputObjectIdentifier & OutputIdentifier, - const FHoudiniGeoPartObject & HGPO, - const FString & HoudiniAssetName, - const FString & BakeFolder, - const FString & TempCookFolder, - const EHoudiniOutputType & Type, - const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, - const TArray& InAllOutputs); - - FReply OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput); - - // Gets the border brush to show around thumbnails, changes when the user hovers on it. - const FSlateBrush * GetThumbnailBorder(UObject* Mesh) const; - const FSlateBrush * GetMaterialInterfaceThumbnailBorder(UObject* Mesh, int32 MaterialIdx) const; - - // Delegate used to detect if valid object has been dragged and dropped. - bool OnMaterialInterfaceDraggedOver(const UObject * InObject) const; - - // Delegate used when a valid material has been drag and dropped on a mesh. - void OnMaterialInterfaceDropped( - UObject* InDroppedObject, - UStaticMesh* InStaticMesh, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Delegate used when a valid material has been drag and dropped on a landscape. - void OnMaterialInterfaceDropped( - UObject* InDroppedObject, - ALandscapeProxy* InLandscape, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Construct drop down menu content for material. - TSharedRef OnGetMaterialInterfaceMenuContent( - UMaterialInterface* MaterialInterface, - UObject* OutputObject, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Delegate for handling selection in content browser. - void OnMaterialInterfaceSelected( - const FAssetData & AssetData, - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx); - - // Delegate for handling Use CB selection arrow button clicked. - void OnUseContentBrowserSelectedMaterialInterface( - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx); - - // Closes the combo button. - void CloseMaterialInterfaceComboButton(); - - // Browse to material interface. - void OnBrowseTo(UObject* InObject); - - // Handler for reset material interface button. - FReply OnResetMaterialInterfaceClicked( - UStaticMesh * StaticMesh, UHoudiniOutput * InOutput, int32 MaterialIdx); - - FReply OnResetMaterialInterfaceClicked( - ALandscapeProxy* InLandscape, UHoudiniOutput * InHoudiniOutput, int32 InMaterialIdx); - - // Handler for when static mesh thumbnail is double clicked. We open editor in this case. - FReply OnThumbnailDoubleClick( - const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object); - - // Handler for bake individual static mesh action. - // static FReply OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject); - -private: - - // Map of meshes and corresponding thumbnail borders. - TMap> OutputObjectThumbnailBorders; - // Map of meshes / material indices to thumbnail borders. - TMap, TSharedPtr> MaterialInterfaceThumbnailBorders; - // Map of meshes / material indices to combo elements. - TMap, TSharedPtr> MaterialInterfaceComboButtons; - - /** Delegate for filtering material interfaces. **/ - FOnShouldFilterAsset OnShouldFilterMaterialInterface; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "ContentBrowserDelegates.h" + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class UHoudiniOutput; +class UHoudiniAssetComponent; +class FAssetThumbnailPool; +class ALandscapeProxy; +class USplineComponent; +class UHoudiniLandscapePtr; +class UHoudiniStaticMesh; +class UMaterialInterface; +class SBorder; +class SComboButton; + +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniOutputObject; + +enum class EHoudiniOutputType : uint8; +enum class EHoudiniLandscapeOutputBakeType : uint8; + +class FHoudiniOutputDetails : public TSharedFromThis +{ +public: + void CreateWidget( + IDetailCategoryBuilder& HouInputCategoryBuilder, + TArray InOutputs); + + void CreateMeshOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateCurveOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateStaticMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UStaticMesh * StaticMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + const bool& bIsProxyMeshCurrent); + + void CreateProxyMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UHoudiniStaticMesh * ProxyMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject); + + void CreateCurveWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + USceneComponent* SplineComponent, + FHoudiniOutputObject& OutputObject, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + FHoudiniGeoPartObject& HoudiniGeoPartObject); + + void CreateLandscapeOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateLandscapeOutputWidget_Helper( + IDetailCategoryBuilder & HouOutputCategory, + UHoudiniOutput * InOutput, + const FHoudiniGeoPartObject & HGPO, + UHoudiniLandscapePtr * LandscapePointer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier); + + void CreateInstancerOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput * InOutput); + + void CreateDefaultOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + static FText GetOutputTooltip(UHoudiniOutput* MainOutput); + static FText GetOutputDebugName(UHoudiniOutput* InOutput); + static FText GetOutputDebugDescription(UHoudiniOutput* InOutput); + + static void OnBakeNameCommitted( + const FText& Val, ETextCommit::Type TextCommitType, + UHoudiniOutput * InOutput, + const FHoudiniOutputObjectIdentifier & InIdentifier); + + static void OnRevertBakeNameToDefault( + UHoudiniOutput * InOutput, + const FHoudiniOutputObjectIdentifier & InIdentifier); + + static void OnBakeOutputObject( + const FString& InBakeName, + UObject * BakedOutputObject, + const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniGeoPartObject & HGPO, + const FString & HoudiniAssetName, + const FString & BakeFolder, + const FString & TempCookFolder, + const EHoudiniOutputType & Type, + const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, + const TArray& InAllOutputs); + + FReply OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput); + + // Gets the border brush to show around thumbnails, changes when the user hovers on it. + const FSlateBrush * GetThumbnailBorder(UObject* Mesh) const; + const FSlateBrush * GetMaterialInterfaceThumbnailBorder(UObject* Mesh, int32 MaterialIdx) const; + + // Delegate used to detect if valid object has been dragged and dropped. + bool OnMaterialInterfaceDraggedOver(const UObject * InObject) const; + + // Delegate used when a valid material has been drag and dropped on a mesh. + void OnMaterialInterfaceDropped( + UObject* InDroppedObject, + UStaticMesh* InStaticMesh, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Delegate used when a valid material has been drag and dropped on a landscape. + void OnMaterialInterfaceDropped( + UObject* InDroppedObject, + ALandscapeProxy* InLandscape, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Construct drop down menu content for material. + TSharedRef OnGetMaterialInterfaceMenuContent( + UMaterialInterface* MaterialInterface, + UObject* OutputObject, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Delegate for handling selection in content browser. + void OnMaterialInterfaceSelected( + const FAssetData & AssetData, + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx); + + // Delegate for handling Use CB selection arrow button clicked. + void OnUseContentBrowserSelectedMaterialInterface( + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx); + + // Closes the combo button. + void CloseMaterialInterfaceComboButton(); + + // Browse to material interface. + void OnBrowseTo(UObject* InObject); + + // Handler for reset material interface button. + FReply OnResetMaterialInterfaceClicked( + UStaticMesh * StaticMesh, UHoudiniOutput * InOutput, int32 MaterialIdx); + + FReply OnResetMaterialInterfaceClicked( + ALandscapeProxy* InLandscape, UHoudiniOutput * InHoudiniOutput, int32 InMaterialIdx); + + // Handler for when static mesh thumbnail is double clicked. We open editor in this case. + FReply OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object); + + // Handler for bake individual static mesh action. + // static FReply OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject); + +private: + + // Map of meshes and corresponding thumbnail borders. + TMap> OutputObjectThumbnailBorders; + // Map of meshes / material indices to thumbnail borders. + TMap, TSharedPtr> MaterialInterfaceThumbnailBorders; + // Map of meshes / material indices to combo elements. + TMap, TSharedPtr> MaterialInterfaceComboButtons; + + /** Delegate for filtering material interfaces. **/ + FOnShouldFilterAsset OnShouldFilterMaterialInterface; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp index 780a6d0b0..598a6ab9c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp @@ -1,2628 +1,2628 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPDGManager.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAssetActor.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniEngineDetails.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "IDetailGroup.h" -#include "IDetailCustomization.h" -#include "PropertyCustomizationHelpers.h" -#include "DetailWidgetRow.h" -#include "ScopedTransaction.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/SBoxPanel.h" -#include "Widgets/Layout/SSpacer.h" -#include "Framework/SlateDelegates.h" -#include "Templates/SharedPointer.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define HOUDINI_ENGINE_UI_SECTION_PDG_BAKE 2 - -void -FHoudiniPDGDetails::CreateWidget( - IDetailCategoryBuilder& HouPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // PDG ASSET - FHoudiniPDGDetails::AddPDGAssetWidget(HouPDGCategory, InPDGAssetLink); - - // TOP NETWORKS - FHoudiniPDGDetails::AddTOPNetworkWidget(HouPDGCategory, InPDGAssetLink); - - // PDG EVENT MESSAGES -} - - -void -FHoudiniPDGDetails::AddPDGAssetWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // PDG STATUS ROW - AddPDGAssetStatus(InPDGCategory, InPDGAssetLink); - - // Commandlet Status row - AddPDGCommandletStatus(InPDGCategory, FHoudiniEngine::Get().GetPDGCommandletStatus()); - - // REFRESH / RESET Buttons - { - TSharedRef RefreshHBox = SNew(SHorizontalBox); - TSharedPtr ResetHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGRefreshResetRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Refresh", "Refresh")) - .ToolTipText(LOCTEXT("RefreshTooltip", "Refreshes infos displayed by the the PDG Asset Link")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .OnClicked_Lambda([InPDGAssetLink]() - { - FHoudiniPDGDetails::RefreshPDGAssetLink(InPDGAssetLink); - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(RefreshHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Reset", "Reset")) - .ToolTipText(LOCTEXT("ResetTooltip", "Resets the PDG Asset Link")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .OnClicked_Lambda([InPDGAssetLink]() - { - // TODO: RESET USELESS? - FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(ResetHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr RefreshIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGRefreshIconBrush(); - if (RefreshIconBrush.IsValid()) - { - TSharedPtr RefreshImage; - RefreshHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RefreshImage, SImage) - ] - ]; - - RefreshImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([RefreshIconBrush]() { return RefreshIconBrush.Get(); }))); - } - - RefreshHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Refresh", "Refresh")) - ]; - - TSharedPtr ResetIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGResetIconBrush(); - if (ResetIconBrush.IsValid()) - { - TSharedPtr ResetImage; - ResetHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(ResetImage, SImage) - ] - ]; - - ResetImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([ResetIconBrush]() { return ResetIconBrush.Get(); }))); - } - - ResetHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Reset", "Reset")) - ]; - } - - // TOP NODE FILTER - { - FText Tooltip = FText::FromString(TEXT("When enabled, the TOP Node Filter will only display the TOP Nodes found in the current network that start with the filter prefix. Disabling the Filter will display all of the TOP Network's TOP Nodes.")); - // Lambda for changing the filter value - auto ChangeTOPNodeFilter = [InPDGAssetLink](const FString& NewValue) - { - if (InPDGAssetLink->TOPNodeFilter.Equals(NewValue)) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->TOPNodeFilter = NewValue; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter), InPDGAssetLink); - }; - - FDetailWidgetRow& PDGFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGFilterRow, InPDGAssetLink); - PDGFilterRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox enable filter - SNew(SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bUseTOPNodeFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (InPDGAssetLink->bUseTOPNodeFilter == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bUseTOPNodeFilter = bNewState; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node Filter"))) - .ToolTipText(Tooltip) - ]; - - PDGFilterRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .ToolTipText(Tooltip) - .Text_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return FText(); - return FText::FromString(InPDGAssetLink->TOPNodeFilter); - }) - .OnTextCommitted_Lambda([ChangeTOPNodeFilter](const FText& Val, ETextCommit::Type TextCommitType) - { - ChangeTOPNodeFilter(Val.ToString()); - }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([=]() - { - FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER); - ChangeTOPNodeFilter(DefaultFilter); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - } - - // TOP OUTPUT FILTER - { - // Lambda for changing the filter value - FText Tooltip = FText::FromString(TEXT("When enabled, the Work Item Output Files created for the TOP Nodes found in the current network that start with the filter prefix will be automatically loaded int the world after being cooked.")); - auto ChangeTOPOutputFilter = [InPDGAssetLink](const FString& NewValue) - { - if (InPDGAssetLink->TOPOutputFilter.Equals(NewValue)) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->TOPOutputFilter = NewValue; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter), InPDGAssetLink); - }; - - FDetailWidgetRow& PDGOutputFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGOutputFilterRow, InPDGAssetLink); - - PDGOutputFilterRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox enable filter - SNew(SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bUseTOPOutputFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (InPDGAssetLink->bUseTOPOutputFilter == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bUseTOPOutputFilter = bNewState; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Output Filter"))) - .ToolTipText(Tooltip) - ]; - - PDGOutputFilterRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return FText(); - return FText::FromString(InPDGAssetLink->TOPOutputFilter); - }) - .OnTextCommitted_Lambda([ChangeTOPOutputFilter](const FText& Val, ETextCommit::Type TextCommitType) - { - ChangeTOPOutputFilter(Val.ToString()); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([ChangeTOPOutputFilter]() - { - FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER); - ChangeTOPOutputFilter(DefaultFilter); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - } - - // Checkbox: Autocook - { - FText Tooltip = FText::FromString(TEXT("When enabled, the selected TOP Network's output will automatically cook after succesfully cooking the PDG Asset Link HDA.")); - FDetailWidgetRow& PDGAutocookRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGAutocookRow, InPDGAssetLink); - PDGAutocookRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Auto-cook"))) - .ToolTipText(Tooltip) - ]; - - TSharedPtr AutoCookCheckBox; - PDGAutocookRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(AutoCookCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bAutoCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (!InPDGAssetLink || InPDGAssetLink->bAutoCook == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bAutoCook = bNewState; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bAutoCook), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ]; - } - // Output parent actor selector - { - IDetailPropertyRow* PDGOutputParentActorRow = InPDGCategory.AddExternalObjectProperty({ InPDGAssetLink }, "OutputParentActor"); - if (PDGOutputParentActorRow) - { - TAttribute PDGOutputParentActorRowEnabled; - BindDisableIfPDGNotLinked(PDGOutputParentActorRowEnabled, InPDGAssetLink); - PDGOutputParentActorRow->IsEnabled(PDGOutputParentActorRowEnabled); - TSharedPtr NameWidget; - TSharedPtr ValueWidget; - PDGOutputParentActorRow->GetDefaultWidgets(NameWidget, ValueWidget); - PDGOutputParentActorRow->DisplayName(FText::FromString(TEXT("Output Parent Actor"))); - PDGOutputParentActorRow->ToolTip(FText::FromString( - TEXT("The PDG Output Actors will be created under this parent actor. If not set, then the PDG Output Actors will be created under a new folder."))); - } - } - - // Add bake widgets for PDG output - CreatePDGBakeWidgets(InPDGCategory, InPDGAssetLink); - - // TODO: move this to a better place: the baking code is in HoudiniEngineEditor, the PDG manager (that knows about - // when work object results are loaded is in HoudiniEngine and the PDGAssetLink is in HoudiniEngineRuntime). So - // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? - if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) - InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); - InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject); - - // WORK ITEM STATUS - { - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGStatusRow, InPDGAssetLink); - FHoudiniPDGDetails::AddWorkItemStatusWidget( - PDGStatusRow, TEXT("Asset Work Item Status"), InPDGAssetLink, false); - } -} - -bool -FHoudiniPDGDetails::GetPDGStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor) -{ - OutPDGStatusString = FString(); - OutPDGStatusColor = FLinearColor::White; - - if (!IsValid(InPDGAssetLink)) - return false; - - switch (InPDGAssetLink->LinkState) - { - case EPDGLinkState::Linked: - OutPDGStatusString = TEXT("PDG is READY"); - OutPDGStatusColor = FLinearColor::Green; - break; - case EPDGLinkState::Linking: - OutPDGStatusString = TEXT("PDG is Linking"); - OutPDGStatusColor = FLinearColor::Yellow; - break; - case EPDGLinkState::Error_Not_Linked: - OutPDGStatusString = TEXT("PDG is ERRORED"); - OutPDGStatusColor = FLinearColor::Red; - break; - case EPDGLinkState::Inactive: - OutPDGStatusString = TEXT("PDG is INACTIVE"); - OutPDGStatusColor = FLinearColor::White; - break; - default: - return false; - } - - return true; -} - -void -FHoudiniPDGDetails::AddPDGAssetStatus( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink) -{ - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - FString PDGStatusString; - FLinearColor PDGStatusColor; - GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); - return FText::FromString(PDGStatusString); - }) - .ColorAndOpacity_Lambda([InPDGAssetLink]() - { - FString PDGStatusString; - FLinearColor PDGStatusColor; - GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); - return FSlateColor(PDGStatusColor); - }) - ] - ]; -} - -void -FHoudiniPDGDetails::GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor) -{ - OutStatusString = FString(); - OutStatusColor = FLinearColor::White; - switch (FHoudiniEngine::Get().GetPDGCommandletStatus()) - { - case EHoudiniBGEOCommandletStatus::Connected: - OutStatusString = TEXT("Async importer is CONNECTED"); - OutStatusColor = FLinearColor::Green; - break; - case EHoudiniBGEOCommandletStatus::Running: - OutStatusString = TEXT("Async importer is Running"); - OutStatusColor = FLinearColor::Yellow; - break; - case EHoudiniBGEOCommandletStatus::Crashed: - OutStatusString = TEXT("Async importer has CRASHED"); - OutStatusColor = FLinearColor::Red; - break; - case EHoudiniBGEOCommandletStatus::NotStarted: - OutStatusString = TEXT("Async importer is NOT STARTED"); - OutStatusColor = FLinearColor::White; - break; - } -} - -void -FHoudiniPDGDetails::AddPDGCommandletStatus( - IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus) -{ - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Visibility_Lambda([]() - { - const UHoudiniRuntimeSettings* Settings = GetDefault(); - if (IsValid(Settings)) - { - return FHoudiniEngineCommands::IsPDGCommandletEnabled() ? EVisibility::Visible : EVisibility::Collapsed; - } - - return EVisibility::Visible; - }) - .Text_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetPDGCommandletStatus(StatusString, StatusColor); - return FText::FromString(StatusString); - }) - .ColorAndOpacity_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetPDGCommandletStatus(StatusString, StatusColor); - return FSlateColor(StatusColor); - }) - ] - ]; -} - -bool -FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( - UHoudiniPDGAssetLink* InAssetLink, - bool bInForSelectedNode, - const FString& InTallyItemString, - int32& OutValue, - FLinearColor& OutColor) -{ - OutValue = 0; - OutColor = FLinearColor::White; - - if (!IsValid(InAssetLink)) - return false; - - bool bFound = false; - FWorkItemTally* TallyPtr = nullptr; - if (bInForSelectedNode) - { - UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); - if (TOPNode && !TOPNode->bHidden) - TallyPtr = &(TOPNode->WorkItemTally); - } - else - TallyPtr = &(InAssetLink->WorkItemTally); - - if (TallyPtr) - { - if (InTallyItemString == TEXT("WAITING")) - { - // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI - OutValue = TallyPtr->WaitingWorkItems + TallyPtr->ScheduledWorkItems; - OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("COOKING")) - { - OutValue = TallyPtr->CookingWorkItems; - OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("COOKED")) - { - OutValue = TallyPtr->CookedWorkItems; - OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("FAILED")) - { - OutValue = TallyPtr->ErroredWorkItems; - OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; - bFound = true; - } - } - - return bFound; -} - -void -FHoudiniPDGDetails::AddWorkItemStatusWidget( - FDetailWidgetRow& InRow, const FString& InTitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode) -{ - auto AddGridBox = [InAssetLink, bInForSelectedNode](const FString& Title) -> SHorizontalBox::FSlot& - { - return SHorizontalBox::Slot() - .MaxWidth(500.0f) - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .AutoHeight() - .Padding(FMargin(1.0f, 2.0f)) - [ - SNew(SBorder) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) - .BorderBackgroundColor(FSlateColor(FLinearColor(0.6, 0.6, 0.6))) - .Padding(FMargin(1.0f, 5.0f)) - [ - SNew(SBox) - .WidthOverride(95.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString(Title)) - .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FSlateColor(Color); - }) - ] - ] - ] - + SVerticalBox::Slot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .AutoHeight() - .Padding(FMargin(1.0f, 2.0f)) - [ - SNew(SBorder) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) - .BorderBackgroundColor(FSlateColor(FLinearColor(0.8, 0.8, 0.8))) - .Padding(FMargin(1.0f, 5.0f)) - [ - SNew(SBox) - .WidthOverride(95.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FText::AsNumber(Value); - }) - .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FSlateColor(Color); - }) - ] - ] - ] - ]; - }; - - InRow.WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f) - .AutoWidth() - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - [ - SNew(SSpacer) - ] - + SVerticalBox::Slot() - .AutoHeight() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(FMargin(0.0f, 2.0f)) - [ - SNew(STextBlock) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .Text(FText::FromString(InTitleString)) - - ] - + SVerticalBox::Slot() - .AutoHeight() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(FMargin(0.0f, 2.0f)) - [ - SNew(SHorizontalBox) - + AddGridBox(TEXT("WAITING")) - + AddGridBox(TEXT("COOKING")) - + AddGridBox(TEXT("COOKED")) - + AddGridBox(TEXT("FAILED")) - ] - + SVerticalBox::Slot() - [ - SNew(SSpacer) - ] - ] - ]; -} - - -void -FHoudiniPDGDetails::AddTOPNetworkWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink ) -{ - if (!InPDGAssetLink->GetSelectedTOPNetwork()) - return; - - if (InPDGAssetLink->AllTOPNetworks.Num() <= 0) - return; - - TOPNetworksPtr.Reset(); - - FString GroupLabel = TEXT("TOP Networks"); - IDetailGroup& TOPNetWorkGrp = InPDGCategory.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), false, true); - - // Combobox: TOP Network - { - FDetailWidgetRow& PDGTOPNetRow = TOPNetWorkGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGTOPNetRow, InPDGAssetLink); - PDGTOPNetRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Network"))) - ]; - - // Fill the TOP Networks SharedString array - TOPNetworksPtr.SetNum(InPDGAssetLink->AllTOPNetworks.Num()); - for(int32 Idx = 0; Idx < InPDGAssetLink->AllTOPNetworks.Num(); Idx++) - { - const UTOPNetwork* Network = InPDGAssetLink->AllTOPNetworks[Idx]; - if (!IsValid(Network)) - { - TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( - Idx, - TEXT("Invalid"), - TEXT("Invalid") - )); - } - else - { - TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( - Idx, - FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Network->NodeName, Network->NodePath), - Network->NodePath - )); - } - } - - if(TOPNetworksPtr.Num() <= 0) - TOPNetworksPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, "----"))); - - // Lambda for selecting another TOPNet - auto OnTOPNetChanged = [InPDGAssetLink](TSharedPtr InNewChoice) - { - if (!InNewChoice.IsValid()) - return; - - const int32 NewChoice = InNewChoice->Value; - int32 NewSelectedIndex = -1; - if (InPDGAssetLink->AllTOPNetworks.IsValidIndex(NewChoice)) - NewSelectedIndex = NewChoice; - - if (InPDGAssetLink->SelectedTOPNetworkIndex == NewSelectedIndex) - return; - - if (NewSelectedIndex < 0) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->SelectedTOPNetworkIndex = NewSelectedIndex; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex), InPDGAssetLink); - }; - - TSharedPtr HorizontalBoxTOPNet; - TSharedPtr>> ComboBoxTOPNet; - int32 SelectedIndex = TOPNetworksPtr.IndexOfByPredicate([InPDGAssetLink](const TSharedPtr& InEntry) - { - return InEntry.IsValid() && InEntry->Value == InPDGAssetLink->SelectedTOPNetworkIndex; - }); - if (SelectedIndex < 0) - SelectedIndex = 0; - - PDGTOPNetRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .FillWidth(300.f) - .MaxWidth(300.f) - [ - SAssignNew(ComboBoxTOPNet, SComboBox>) - .OptionsSource(&TOPNetworksPtr) - .InitiallySelectedItem(TOPNetworksPtr[SelectedIndex]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); - const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryToolTip) - .Margin(2.0f) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnTOPNetChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnTOPNetChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString(InPDGAssetLink->GetSelectedTOPNetworkName()); - }) - .ToolTipText_Lambda([InPDGAssetLink]() - { - UTOPNetwork const * const Network = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(Network)) - { - if (!Network->NodePath.IsEmpty()) - return FText::FromString(Network->NodePath); - else - return FText::FromString(Network->NodeName); - } - else - { - return FText(); - } - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - } - - // Buttons: DIRTY ALL / COOK OUTPUT - { - TSharedRef DirtyAllHBox = SNew(SHorizontalBox); - TSharedPtr CookOutHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("DirtyAll", "Dirty All")) - .ToolTipText(LOCTEXT("DirtyAllTooltip", "Dirty all TOP nodes in the selected TOP network and clears all of its work item results.")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork()); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(TOPNetwork)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - FHoudiniPDGManager::DirtyAll(TOPNetwork); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - else - { - UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(TOPNetwork); - } - } - } - - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(DirtyAllHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("CookOut", "Cook Output")) - .ToolTipText(LOCTEXT("CookOutTooltip", "Cooks the output nodes of the selected TOP network")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!IsValid(SelectedTOPNet)) - return false; - - // Disable if there any nodes in the network that are already cooking - return !SelectedTOPNet->AnyWorkItemsPending(); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::CookOutput(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CookOutHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr DirtyAllIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyAllIconBrush(); - if (DirtyAllIconBrush.IsValid()) - { - TSharedPtr DirtyAllImage; - DirtyAllHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(DirtyAllImage, SImage) - ] - ]; - - DirtyAllImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([DirtyAllIconBrush]() { return DirtyAllIconBrush.Get(); }))); - } - - DirtyAllHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("DirtyAll", "Dirty All")) - ]; - - TSharedPtr CookOutIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - if (CookOutIconBrush.IsValid()) - { - TSharedPtr CookOutImage; - CookOutHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookOutImage, SImage) - ] - ]; - - CookOutImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookOutIconBrush]() { return CookOutIconBrush.Get(); }))); - } - - CookOutHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookOut", "Cook Output")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: PAUSE COOK / CANCEL COOK - { - TSharedRef PauseHBox = SNew(SHorizontalBox); - TSharedPtr CancelHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Pause", "Pause Cook")) - .ToolTipText(LOCTEXT("PauseTooltip", "Pauses cooking for the selected TOP Network")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::PauseCook(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(PauseHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Cancel", "Cancel Cook")) - .ToolTipText(LOCTEXT("CancelTooltip", "Cancels cooking the selected TOP network")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::CancelCook(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CancelHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr PauseIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGPauseIconBrush(); - if (PauseIconBrush.IsValid()) - { - TSharedPtr PauseImage; - PauseHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(PauseImage, SImage) - ] - ]; - - PauseImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([PauseIconBrush]() { return PauseIconBrush.Get(); }))); - } - - PauseHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Pause", "Pause Cook")) - ]; - - TSharedPtr CancelIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGCancelIconBrush(); - if (CancelIconBrush.IsValid()) - { - TSharedPtr CancelImage; - CancelHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CancelImage, SImage) - ] - ]; - - CancelImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CancelIconBrush]() { return CancelIconBrush.Get(); }))); - } - - CancelHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Cancel", "Cancel Cook")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: Unload Work Item Objects - { - FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork(); }) - .WidthOverride(200.0f) - [ - SNew(SButton) - .Text(LOCTEXT("UnloadWorkItemsForNetwork", "Unload All Work Item Objects")) - .ToolTipText(LOCTEXT("UnloadWorkItemsForNetworkTooltip", "Unloads / removes loaded work item results from level for all nodes in this network. Not undoable: use the \"Load Work Item Objects\" button on the individual TOP nodes to reload work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNetwork* const SelectedNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!IsValid(SelectedNet) || - INDEX_NONE == SelectedNet->AllTOPNodes.IndexOfByPredicate([](const UTOPNode* InNode) { return IsValid(InNode) && InNode->bCachedHaveLoadedWorkResults; })) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNetwork* const TOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(TOPNet)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - // Set the state to ToDelete, PDGManager will delete it when processing work items - TOPNet->SetLoadedWorkResultsToDelete(); - } - else - { - // Delete and unload the result objects and actors now - TOPNet->DeleteWorkResultOutputObjects(); - } - } - } - - return FReply::Handled(); - }) - ] - ] - ]; - } - - // TOP NODE WIDGETS - FHoudiniPDGDetails::AddTOPNodeWidget(TOPNetWorkGrp, InPDGAssetLink); -} - -bool -FHoudiniPDGDetails::GetSelectedTOPNodeStatusAndColor(UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor) -{ - OutTOPNodeStatus = FString(); - OutTOPNodeStatusColor = FLinearColor::White; - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode) && !TOPNode->bHidden) - { - OutTOPNodeStatus = UHoudiniPDGAssetLink::GetTOPNodeStatus(TOPNode); - OutTOPNodeStatusColor = UHoudiniPDGAssetLink::GetTOPNodeStatusColor(TOPNode); - - return true; - } - } - - return false; -} - -void -FHoudiniPDGDetails::AddTOPNodeWidget( - IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink ) -{ - if (!InPDGAssetLink->GetSelectedTOPNetwork()) - return; - - FString GroupLabel = TEXT("TOP Nodes"); - IDetailGroup& TOPNodesGrp = InGroup.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), true); - - // Combobox: TOP Node - { - FDetailWidgetRow& PDGTOPNodeRow = TOPNodesGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGTOPNodeRow, InPDGAssetLink); - PDGTOPNodeRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node"))) - ]; - - // Update the TOP Node SharedString - TOPNodesPtr.Reset(); - TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, LOCTEXT("ComboBoxEntryNoSelectedTOPNode", "- Select -").ToString()))); - const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNet)) - { - const int32 NumTOPNodes = SelectedTOPNet->AllTOPNodes.Num(); - for (int32 Idx = 0; Idx < NumTOPNodes; Idx++) - { - const UTOPNode* const Node = SelectedTOPNet->AllTOPNodes[Idx]; - if (!IsValid(Node) || Node->bHidden) - continue; - - TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip( - Idx, - FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Node->NodeName, Node->NodePath), - Node->NodePath - ))); - } - } - - FString NodeErrorText = FString(); - FString NodeErrorTooltip = FString(); - FLinearColor NodeErrorColor = FLinearColor::White; - if (!IsValid(SelectedTOPNet) || SelectedTOPNet->AllTOPNodes.Num() <= 0) - { - NodeErrorText = TEXT("No valid TOP Node found!"); - NodeErrorTooltip = TEXT("There is no valid TOP Node found in the selected TOP Network!"); - NodeErrorColor = FLinearColor::Red; - } - else if(TOPNodesPtr.Num() <= 0) - { - NodeErrorText = TEXT("No visible TOP Node found!"); - NodeErrorTooltip = TEXT("No visible TOP Node found, all nodes in this network are hidden. Please update your TOP Node Filter."); - NodeErrorColor = FLinearColor::Yellow; - } - - // Lambda for selecting a TOPNode - auto OnTOPNodeChanged = [InPDGAssetLink](TSharedPtr InNewChoice) - { - UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!InNewChoice.IsValid() || !IsValid(TOPNetwork)) - return; - - const int32 NewChoice = InNewChoice->Value; - int32 NewSelectedIndex = INDEX_NONE; - if (TOPNetwork->AllTOPNodes.IsValidIndex(NewChoice)) - NewSelectedIndex = NewChoice; - - if (TOPNetwork->SelectedTOPIndex != NewSelectedIndex) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNetwork); - - TOPNetwork->Modify(); - TOPNetwork->SelectedTOPIndex = NewSelectedIndex; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UTOPNetwork, SelectedTOPIndex), TOPNetwork); - } - }; - - TSharedPtr HorizontalBoxTOPNode; - TSharedPtr>> ComboBoxTOPNode; - int32 SelectedIndex = 0; - UTOPNetwork* const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNetwork) && SelectedTOPNetwork->SelectedTOPIndex >= 0) - { - //SelectedIndex = InPDGAssetLink->GetSelectedTOPNetwork()->SelectedTOPIndex; - - // We need to match the selection by the index in the AllTopNodes array - // Because of the nodefilter, it is possible that the selected index does not match the index in TOPNodesPtr - const int32 SelectedTOPNodeIndex = SelectedTOPNetwork->SelectedTOPIndex; - // Find the matching UI index - for (int32 UIIndex = 0; UIIndex < TOPNodesPtr.Num(); UIIndex++) - { - if (TOPNodesPtr[UIIndex] && TOPNodesPtr[UIIndex]->Value == SelectedTOPNodeIndex) - { - // We found the UI Index that matches the current TOP Node! - SelectedIndex = UIIndex; - break; - } - } - } - - TSharedPtr ErrorText; - - PDGTOPNodeRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .FillWidth(300.f) - .MaxWidth(300.f) - [ - SAssignNew(ComboBoxTOPNode, SComboBox>) - .OptionsSource(&TOPNodesPtr) - .InitiallySelectedItem(TOPNodesPtr[SelectedIndex]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); - const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryToolTip) - .Margin(2.0f) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnTOPNodeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnTOPNodeChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink, ComboBoxTOPNode, Options = TOPNodesPtr]() - { - if (IsValid(InPDGAssetLink)) - return FText::FromString(InPDGAssetLink->GetSelectedTOPNodeName()); - else - return FText(); - }) - .ToolTipText_Lambda([InPDGAssetLink]() - { - UTOPNode const * const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (!TOPNode->NodePath.IsEmpty()) - return FText::FromString(TOPNode->NodePath); - else - return FText::FromString(TOPNode->NodeName); - } - else - { - return FText(); - } - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .AutoWidth() - [ - SAssignNew(ErrorText, STextBlock) - .Text(FText::FromString(NodeErrorText)) - .ToolTipText(FText::FromString(NodeErrorText)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .ColorAndOpacity(FLinearColor::Red) - //.ShadowColorAndOpacity(FLinearColor::Black) - ]; - - // Update the error text if needed - ErrorText->SetText(FText::FromString(NodeErrorText)); - ErrorText->SetToolTipText(FText::FromString(NodeErrorTooltip)); - ErrorText->SetColorAndOpacity(NodeErrorColor); - - // Hide the combobox if we have an error - ComboBoxTOPNode->SetVisibility(NodeErrorText.IsEmpty() ? EVisibility::Visible : EVisibility::Hidden); - } - - // TOP Node State - { - FDetailWidgetRow& PDGNodeStateResultRow = TOPNodesGrp.AddWidgetRow(); - DisableIfPDGNotLinked(PDGNodeStateResultRow, InPDGAssetLink); - PDGNodeStateResultRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node State"))) - ]; - - PDGNodeStateResultRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - FString TOPNodeStatus = FString(); - FLinearColor TOPNodeStatusColor = FLinearColor::White; - GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); - return FText::FromString(TOPNodeStatus); - }) - .ColorAndOpacity_Lambda([InPDGAssetLink]() - { - FString TOPNodeStatus = FString(); - FLinearColor TOPNodeStatusColor = FLinearColor::White; - GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); - return FSlateColor(TOPNodeStatusColor); - }) - ]; - } - - // Checkbox: Load Work Item Output Files - { - auto ToolTipLambda = [InPDGAssetLink]() - { - bool bDisabled = false; - if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) - { - bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; - } - - return bDisabled - ? FText::FromString(TEXT("This node has child nodes, the auto-load setting must be set on the child nodes individually.")) - : FText::FromString(TEXT("When enabled, Output files produced by this TOP Node's Work Items will automatically be loaded when cooked.")); - }; - FDetailWidgetRow& PDGNodeAutoLoadRow = TOPNodesGrp.AddWidgetRow(); - - DisableIfPDGNotLinked(PDGNodeAutoLoadRow, InPDGAssetLink); - PDGNodeAutoLoadRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) - return true; - return false; - })); - - PDGNodeAutoLoadRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Auto-Load Work Item Output Files"))) - .ToolTipText_Lambda(ToolTipLambda) - ]; - - TSharedPtr AutoLoadCheckBox; - - PDGNodeAutoLoadRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(AutoLoadCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->GetSelectedTOPNode() - ? (InPDGAssetLink->GetSelectedTOPNode()->bAutoLoad ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - UTOPNode* TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(TOPNode) || TOPNode->bAutoLoad == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNode); - - TOPNode->Modify(); - TOPNode->bAutoLoad = bNewState; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UTOPNode, bAutoLoad), TOPNode); - - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - }) - .ToolTipText_Lambda(ToolTipLambda) - ]; - } - - // Checkbox: Work Item Output Files Visible - { - auto ToolTipLambda = [InPDGAssetLink]() - { - bool bDisabled = false; - if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) - { - bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; - } - - return bDisabled - ? FText::FromString(TEXT("This node has child nodes, visibility of work item outputs must be set on the child nodes individually.")) - : FText::FromString(TEXT("Toggles the visibility of the actors created from this TOP Node's Work Item File Outputs.")); - }; - - FDetailWidgetRow& PDGNodeShowResultRow = TOPNodesGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGNodeShowResultRow, InPDGAssetLink); - PDGNodeShowResultRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) - return true; - - return false; - })); - PDGNodeShowResultRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Work Item Output Files Visible"))) - .ToolTipText_Lambda(ToolTipLambda) - ]; - - TSharedPtr ShowResCheckBox; - PDGNodeShowResultRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(ShowResCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->GetSelectedTOPNode() - ? (InPDGAssetLink->GetSelectedTOPNode()->IsVisibleInLevel() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(TOPNode) || TOPNode->IsVisibleInLevel() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNode); - - TOPNode->Modify(); - TOPNode->SetVisibleInLevel(bNewState); - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(TEXT("bShow"), TOPNode); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - }) - .ToolTipText_Lambda(ToolTipLambda) - ]; - } - - // Buttons: DIRTY NODE / COOK NODE - { - TSharedRef DirtyHBox = SNew(SHorizontalBox); - TSharedPtr CookHBox = SNew(SHorizontalBox); - - TSharedPtr DirtyButton; - TSharedPtr CookButton; - - FDetailWidgetRow& PDGDirtyCookRow = TOPNodesGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); - }) - .WidthOverride(200.0f) - [ - SAssignNew(DirtyButton, SButton) - //.Text(LOCTEXT("DirtyNode", "Dirty Node")) - .ToolTipText(LOCTEXT("DirtyNodeTooltip", "Dirties the selected TOP node and clears its work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - FHoudiniPDGManager::DirtyTOPNode(TOPNode); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - else - { - UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(TOPNode); - } - } - } - - return FReply::Handled(); - }) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNode()) && !InPDGAssetLink->GetSelectedTOPNode()->bHidden) - return true; - return false; - }) - .Content() - [ - SAssignNew(DirtyHBox, SHorizontalBox) - ] - ] - ] - // + SHorizontalBox::Slot() - // .AutoWidth() - // [ - // SNew(SBox) - // .WidthOverride(200.0f) - // [ - // SAssignNew(DirtyButton, SButton) - // .Text(LOCTEXT("DirtyAllTasks", "Dirty All Tasks")) - // .ToolTipText(LOCTEXT("DirtyAllTasksTooltip", "Dirties all tasks/work items on the selected TOP node.")) - // .ContentPadding(FMargin(5.0f, 2.0f)) - // .VAlign(VAlign_Center) - // .HAlign(HAlign_Center) - // .OnClicked_Lambda([InPDGAssetLink]() - // { - // if(InPDGAssetLink->GetSelectedTOPNode()) - // { - // FHoudiniPDGManager::DirtyAllTasksOfTOPNode(*(InPDGAssetLink->GetSelectedTOPNode())); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - // } - // - // return FReply::Handled(); - // }) - // ] - // ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(200.0f) - [ - SAssignNew(CookButton, SButton) - //.Text(LOCTEXT("CookNode", "Cook Node")) - .ToolTipText(LOCTEXT("CookNodeTooltip", "Cooks the selected TOP Node.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode)) - return false; - // Disable Cook Node button if the node is already cooking - return !SelectedNode->bHidden && SelectedNode->NodeState != EPDGNodeState::Cooking && !SelectedNode->AnyWorkItemsPending(); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node)) - { - FHoudiniPDGManager::CookTOPNode(Node); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CookHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr DirtyIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyNodeIconBrush(); - if (DirtyIconBrush.IsValid()) - { - TSharedPtr DirtyImage; - DirtyHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(DirtyImage, SImage) - ] - ]; - - DirtyImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([DirtyIconBrush]() { return DirtyIconBrush.Get(); }))); - } - - DirtyHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("DirtyNode", "Dirty Node")) - ]; - - TSharedPtr CookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - if (CookIconBrush.IsValid()) - { - TSharedPtr CookImage; - CookHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookImage, SImage) - ] - ]; - - CookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookIconBrush]() { return CookIconBrush.Get(); }))); - } - - CookHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookNode", "Cook Node")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: Load Work Item Objects / Unload Work Item Objects - { - TSharedPtr UnloadWorkItemsButton; - TSharedPtr LoadWorkItemsButton; - - FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNodesGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode()); - }) - .WidthOverride(200.0f) - [ - SAssignNew(UnloadWorkItemsButton, SButton) - .Text(LOCTEXT("UnloadWorkItemsForNode", "Unload Work Item Objects")) - .ToolTipText(LOCTEXT("UnloadWorkItemsForNodeTooltip", "Unloads / removes loaded work item results from level. Not undoable: use the \"Load Work Item Objects\" button to reload the results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveLoadedWorkResults) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - // Set the state to ToDelete, PDGManager will delete it when processing work items - TOPNode->SetLoadedWorkResultsToDelete(); - } - else - { - // Delete and unload the result objects and actors now - TOPNode->DeleteWorkResultOutputObjects(); - } - } - } - - return FReply::Handled(); - }) - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(200.0f) - [ - SAssignNew(LoadWorkItemsButton, SButton) - .Text(LOCTEXT("LoadWorkItems", "Load Work Item Objects")) - .ToolTipText(LOCTEXT("LoadWorkItemsForNodeTooltip", "Loads any available but not loaded work items objects (this could include items from a previous cook). Creates output actors. Not undoable: use the \"Unload Work Item Objects\" button to unload/remove loaded work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveNotLoadedWorkResults) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(SelectedNode)) - { - SelectedNode->SetNotLoadedWorkResultsToLoad(true); - } - } - return FReply::Handled(); - }) - ] - ] - ]; - } - - // TOP Node WorkItem Status - { - if (InPDGAssetLink->GetSelectedTOPNode()) - { - FDetailWidgetRow& PDGNodeWorkItemStatsRow = TOPNodesGrp.AddWidgetRow(); - DisableIfPDGNotLinked(PDGNodeWorkItemStatsRow, InPDGAssetLink); - FHoudiniPDGDetails::AddWorkItemStatusWidget( - PDGNodeWorkItemStatsRow, TEXT("TOP Node Work Item Status"), InPDGAssetLink, true); - } - } -} - -void -FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // Repopulate the network and nodes for the assetlink - if (!FHoudiniPDGManager::UpdatePDGAssetLink(InPDGAssetLink)) - return; - - FHoudiniPDGDetails::RefreshUI(InPDGAssetLink, true); -} - -void -FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // Update the workitem stats - InPDGAssetLink->UpdateWorkItemTally(); - - // Update the editor properties - FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, InFullUpdate); -} - -void -FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); - - if (!InPDGAssetLink->bBakeMenuExpanded) - return; - - auto OnBakeButtonClickedLambda = [InPDGAssetLink]() - { - switch (InPDGAssetLink->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(InPDGAssetLink); - // else - FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink); - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceWithBlueprint(InPDGAssetLink); - // else - FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink); - } - break; - // - // case EHoudiniEngineBakeOption::ToFoliage: - // { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(InPDGAssetLink); - // else - // FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InPDGAssetLink); - // } - // break; - // - // case EHoudiniEngineBakeOption::ToWorldOutliner: - // { - // if (InPDGAssetLink->bIsReplace) - // { - // // Todo - // } - // else - // { - // //Todo - // } - // } - // break; - } - - return FReply::Handled(); - }; - - auto OnBakeFolderTextCommittedLambda = [InPDGAssetLink](const FText& Val, ETextCommit::Type TextCommitType) - { - FString NewPathStr = Val.ToString(); - if (NewPathStr.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - //Todo? Check if the new Bake folder path is valid - InPDGAssetLink->Modify(); - InPDGAssetLink->BakeFolder.Path = NewPathStr; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, BakeFolder), InPDGAssetLink); - }; - - // Button Row - FDetailWidgetRow & ButtonRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(ButtonRow, InPDGAssetLink); - - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - - // Bake Button - TSharedRef BakeHBox = SNew(SHorizontalBox); - TSharedPtr BakeButton; - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(15.f, 0.0f, 0.0f, 0.0f) - .MaxWidth(75.0f) - [ - SNew(SBox) - .WidthOverride(75.0f) - [ - SAssignNew(BakeButton, SButton) - //.Text(FText::FromString("Bake")) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - //.ToolTipText(LOCTEXT("HoudiniPDGDetailsBakeButton", "Bake the Houdini PDG TOP Node(s)")) - .ToolTipText_Lambda([InPDGAssetLink]() - { - switch (InPDGAssetLink->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - return LOCTEXT( - "HoudiniEnginePDGBakeButtonBakeToActorToolTip", - "Bake this Houdini PDG Asset's output assets and seperate the output actors from the PDG asset link."); - } - break; - case EHoudiniEngineBakeOption::ToBlueprint: - { - return LOCTEXT( - "HoudiniEnginePDGBakeButtonBakeToBlueprintToolTip", - "Bake this Houdini PDG Asset's output assets to blueprints and remove temporary output actors that no " - "longer has output components from the PDG asset link."); - } - break; - default: - { - return FText(); - } - } - }) - .Visibility(EVisibility::Visible) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda(OnBakeButtonClickedLambda) - .Content() - [ - SAssignNew(BakeHBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); - if (BakeIconBrush.IsValid()) - { - TSharedPtr BakeImage; - BakeHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(BakeImage, SImage) - ] - ]; - - BakeImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([BakeIconBrush]() { return BakeIconBrush.Get(); }))); - } - - BakeHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Bake")) - ]; - - // bake Type ComboBox - TSharedPtr>> TypeComboBox; - - TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeTypeOptionsLabels(); - TSharedPtr IntialSelec; - if (OptionSource) - { - // IntialSelec = (*OptionSource)[(int)InPDGAssetLink->HoudiniEngineBakeOption]; - const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption); - const TSharedPtr* DefaultOption = OptionSource->FindByPredicate( - [DefaultStr](TSharedPtr InStringPtr) - { - return InStringPtr.IsValid() && *InStringPtr == DefaultStr; - } - ); - if (DefaultOption) - IntialSelec = *DefaultOption; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(93.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(93.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid() || SelectType == ESelectInfo::Type::Direct) - return; - - const EHoudiniEngineBakeOption NewOption = - FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->HoudiniEngineBakeOption) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->HoudiniEngineBakeOption = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink, TypeComboBox, OptionSource]() - { - return FText::FromString(FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - // bake selection ComboBox - TSharedPtr>> BakeSelectionComboBox; - - TArray>* PDGBakeSelectionOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeSelectionOptionsLabels(); - TSharedPtr PDGBakeSelectionIntialSelec; - if (PDGBakeSelectionOptionSource) - { - PDGBakeSelectionIntialSelec = (*PDGBakeSelectionOptionSource)[(int)InPDGAssetLink->PDGBakeSelectionOption]; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(163.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(163.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(PDGBakeSelectionOptionSource) - .InitiallySelectedItem(PDGBakeSelectionIntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EPDGBakeSelectionOption NewOption = - FHoudiniEngineEditor::Get().StringToPDGBakeSelectionOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->PDGBakeSelectionOption) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->PDGBakeSelectionOption = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromPDGBakeTargetOption(InPDGAssetLink->PDGBakeSelectionOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; - - // Bake package replacement mode row - FDetailWidgetRow & BakePackageReplaceRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(BakePackageReplaceRow, InPDGAssetLink); - - TSharedRef BakePackageReplaceRowHorizontalBox = SNew(SHorizontalBox); - - BakePackageReplaceRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeLabel", "Replace Mode")) - .ToolTipText( - LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeTooltip", "Replacement mode " - "during baking. Create new assets, using numerical suffixes in package names when necessary, or " - "replace existing assets with matching names. Also replaces the previous bake's output actors in " - "replacement mode vs creating incremental ones.")) - ] - ]; - - // bake package replace mode ComboBox - TSharedPtr>> BakePackageReplaceModeComboBox; - - TArray>* PDGBakePackageReplaceModeOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels(); - TSharedPtr PDGBakePackageReplaceModeInitialSelec; - if (PDGBakePackageReplaceModeOptionSource) - { - const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode); - const TSharedPtr* DefaultOption = PDGBakePackageReplaceModeOptionSource->FindByPredicate( - [DefaultStr](TSharedPtr InStringPtr) - { - return InStringPtr.IsValid() && *InStringPtr == DefaultStr; - } - ); - if (DefaultOption) - PDGBakePackageReplaceModeInitialSelec = *DefaultOption; - } - - BakePackageReplaceRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(163.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(163.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(PDGBakePackageReplaceModeOptionSource) - .InitiallySelectedItem(PDGBakePackageReplaceModeInitialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - const FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EPDGBakePackageReplaceModeOption NewOption = - FHoudiniEngineEditor::Get().StringToPDGBakePackageReplaceModeOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->PDGBakePackageReplaceMode) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->PDGBakePackageReplaceMode = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - BakePackageReplaceRow.WholeRowWidget.Widget = BakePackageReplaceRowHorizontalBox; - - // Bake Folder Row - FDetailWidgetRow & BakeFolderRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(BakeFolderRow, InPDGAssetLink); - - TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) - ] - ]; - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .MaxWidth(235.0) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) - .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) - .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) - ] - ]; - - BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; - - // Add additional bake options - FDetailWidgetRow & AdditionalBakeSettingsRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); - - TSharedPtr CheckBoxAutoBake; - TSharedPtr CheckBoxRecenterBakedActors; - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bRecenterBakedActors = bNewState; - - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bRecenterBakedActors), InPDGAssetLink); - }) - ] - ]; - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxAutoBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake work result object as they are loaded.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bBakeAfterWorkResultObjectLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bBakeAfterWorkResultObjectLoaded = bNewState; - - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterWorkResultObjectLoaded), InPDGAssetLink); - }) - ] - ]; - - AdditionalBakeSettingsRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText) - : Text(InText) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText, const FString &InToolTip) - : Text(InText) - , ToolTip(InToolTip) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText) - : Text(InText) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText, FString&& InToolTip) - : Text(InText) - , ToolTip(InToolTip) - , Value(InValue) -{ -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPDGManager.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetActor.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniEngineDetails.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "IDetailGroup.h" +#include "IDetailCustomization.h" +#include "PropertyCustomizationHelpers.h" +#include "DetailWidgetRow.h" +#include "ScopedTransaction.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SSpacer.h" +#include "Framework/SlateDelegates.h" +#include "Templates/SharedPointer.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define HOUDINI_ENGINE_UI_SECTION_PDG_BAKE 2 + +void +FHoudiniPDGDetails::CreateWidget( + IDetailCategoryBuilder& HouPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // PDG ASSET + FHoudiniPDGDetails::AddPDGAssetWidget(HouPDGCategory, InPDGAssetLink); + + // TOP NETWORKS + FHoudiniPDGDetails::AddTOPNetworkWidget(HouPDGCategory, InPDGAssetLink); + + // PDG EVENT MESSAGES +} + + +void +FHoudiniPDGDetails::AddPDGAssetWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // PDG STATUS ROW + AddPDGAssetStatus(InPDGCategory, InPDGAssetLink); + + // Commandlet Status row + AddPDGCommandletStatus(InPDGCategory, FHoudiniEngine::Get().GetPDGCommandletStatus()); + + // REFRESH / RESET Buttons + { + TSharedRef RefreshHBox = SNew(SHorizontalBox); + TSharedPtr ResetHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGRefreshResetRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Refresh", "Refresh")) + .ToolTipText(LOCTEXT("RefreshTooltip", "Refreshes infos displayed by the the PDG Asset Link")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked_Lambda([InPDGAssetLink]() + { + FHoudiniPDGDetails::RefreshPDGAssetLink(InPDGAssetLink); + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(RefreshHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Reset", "Reset")) + .ToolTipText(LOCTEXT("ResetTooltip", "Resets the PDG Asset Link")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked_Lambda([InPDGAssetLink]() + { + // TODO: RESET USELESS? + FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(ResetHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr RefreshIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGRefreshIconBrush(); + if (RefreshIconBrush.IsValid()) + { + TSharedPtr RefreshImage; + RefreshHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RefreshImage, SImage) + ] + ]; + + RefreshImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([RefreshIconBrush]() { return RefreshIconBrush.Get(); }))); + } + + RefreshHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Refresh", "Refresh")) + ]; + + TSharedPtr ResetIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGResetIconBrush(); + if (ResetIconBrush.IsValid()) + { + TSharedPtr ResetImage; + ResetHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(ResetImage, SImage) + ] + ]; + + ResetImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([ResetIconBrush]() { return ResetIconBrush.Get(); }))); + } + + ResetHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Reset", "Reset")) + ]; + } + + // TOP NODE FILTER + { + FText Tooltip = FText::FromString(TEXT("When enabled, the TOP Node Filter will only display the TOP Nodes found in the current network that start with the filter prefix. Disabling the Filter will display all of the TOP Network's TOP Nodes.")); + // Lambda for changing the filter value + auto ChangeTOPNodeFilter = [InPDGAssetLink](const FString& NewValue) + { + if (InPDGAssetLink->TOPNodeFilter.Equals(NewValue)) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->TOPNodeFilter = NewValue; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter), InPDGAssetLink); + }; + + FDetailWidgetRow& PDGFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGFilterRow, InPDGAssetLink); + PDGFilterRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox enable filter + SNew(SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bUseTOPNodeFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (InPDGAssetLink->bUseTOPNodeFilter == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bUseTOPNodeFilter = bNewState; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node Filter"))) + .ToolTipText(Tooltip) + ]; + + PDGFilterRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .ToolTipText(Tooltip) + .Text_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return FText(); + return FText::FromString(InPDGAssetLink->TOPNodeFilter); + }) + .OnTextCommitted_Lambda([ChangeTOPNodeFilter](const FText& Val, ETextCommit::Type TextCommitType) + { + ChangeTOPNodeFilter(Val.ToString()); + }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([=]() + { + FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER); + ChangeTOPNodeFilter(DefaultFilter); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + } + + // TOP OUTPUT FILTER + { + // Lambda for changing the filter value + FText Tooltip = FText::FromString(TEXT("When enabled, the Work Item Output Files created for the TOP Nodes found in the current network that start with the filter prefix will be automatically loaded int the world after being cooked.")); + auto ChangeTOPOutputFilter = [InPDGAssetLink](const FString& NewValue) + { + if (InPDGAssetLink->TOPOutputFilter.Equals(NewValue)) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->TOPOutputFilter = NewValue; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter), InPDGAssetLink); + }; + + FDetailWidgetRow& PDGOutputFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGOutputFilterRow, InPDGAssetLink); + + PDGOutputFilterRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox enable filter + SNew(SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bUseTOPOutputFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (InPDGAssetLink->bUseTOPOutputFilter == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bUseTOPOutputFilter = bNewState; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Output Filter"))) + .ToolTipText(Tooltip) + ]; + + PDGOutputFilterRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return FText(); + return FText::FromString(InPDGAssetLink->TOPOutputFilter); + }) + .OnTextCommitted_Lambda([ChangeTOPOutputFilter](const FText& Val, ETextCommit::Type TextCommitType) + { + ChangeTOPOutputFilter(Val.ToString()); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([ChangeTOPOutputFilter]() + { + FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER); + ChangeTOPOutputFilter(DefaultFilter); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + } + + // Checkbox: Autocook + { + FText Tooltip = FText::FromString(TEXT("When enabled, the selected TOP Network's output will automatically cook after succesfully cooking the PDG Asset Link HDA.")); + FDetailWidgetRow& PDGAutocookRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGAutocookRow, InPDGAssetLink); + PDGAutocookRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Auto-cook"))) + .ToolTipText(Tooltip) + ]; + + TSharedPtr AutoCookCheckBox; + PDGAutocookRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(AutoCookCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bAutoCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (!InPDGAssetLink || InPDGAssetLink->bAutoCook == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bAutoCook = bNewState; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bAutoCook), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ]; + } + // Output parent actor selector + { + IDetailPropertyRow* PDGOutputParentActorRow = InPDGCategory.AddExternalObjectProperty({ InPDGAssetLink }, "OutputParentActor"); + if (PDGOutputParentActorRow) + { + TAttribute PDGOutputParentActorRowEnabled; + BindDisableIfPDGNotLinked(PDGOutputParentActorRowEnabled, InPDGAssetLink); + PDGOutputParentActorRow->IsEnabled(PDGOutputParentActorRowEnabled); + TSharedPtr NameWidget; + TSharedPtr ValueWidget; + PDGOutputParentActorRow->GetDefaultWidgets(NameWidget, ValueWidget); + PDGOutputParentActorRow->DisplayName(FText::FromString(TEXT("Output Parent Actor"))); + PDGOutputParentActorRow->ToolTip(FText::FromString( + TEXT("The PDG Output Actors will be created under this parent actor. If not set, then the PDG Output Actors will be created under a new folder."))); + } + } + + // Add bake widgets for PDG output + CreatePDGBakeWidgets(InPDGCategory, InPDGAssetLink); + + // TODO: move this to a better place: the baking code is in HoudiniEngineEditor, the PDG manager (that knows about + // when work object results are loaded is in HoudiniEngine and the PDGAssetLink is in HoudiniEngineRuntime). So + // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? + if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) + InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); + InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject); + + // WORK ITEM STATUS + { + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGStatusRow, InPDGAssetLink); + FHoudiniPDGDetails::AddWorkItemStatusWidget( + PDGStatusRow, TEXT("Asset Work Item Status"), InPDGAssetLink, false); + } +} + +bool +FHoudiniPDGDetails::GetPDGStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor) +{ + OutPDGStatusString = FString(); + OutPDGStatusColor = FLinearColor::White; + + if (!IsValid(InPDGAssetLink)) + return false; + + switch (InPDGAssetLink->LinkState) + { + case EPDGLinkState::Linked: + OutPDGStatusString = TEXT("PDG is READY"); + OutPDGStatusColor = FLinearColor::Green; + break; + case EPDGLinkState::Linking: + OutPDGStatusString = TEXT("PDG is Linking"); + OutPDGStatusColor = FLinearColor::Yellow; + break; + case EPDGLinkState::Error_Not_Linked: + OutPDGStatusString = TEXT("PDG is ERRORED"); + OutPDGStatusColor = FLinearColor::Red; + break; + case EPDGLinkState::Inactive: + OutPDGStatusString = TEXT("PDG is INACTIVE"); + OutPDGStatusColor = FLinearColor::White; + break; + default: + return false; + } + + return true; +} + +void +FHoudiniPDGDetails::AddPDGAssetStatus( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink) +{ + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + FString PDGStatusString; + FLinearColor PDGStatusColor; + GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); + return FText::FromString(PDGStatusString); + }) + .ColorAndOpacity_Lambda([InPDGAssetLink]() + { + FString PDGStatusString; + FLinearColor PDGStatusColor; + GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); + return FSlateColor(PDGStatusColor); + }) + ] + ]; +} + +void +FHoudiniPDGDetails::GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor) +{ + OutStatusString = FString(); + OutStatusColor = FLinearColor::White; + switch (FHoudiniEngine::Get().GetPDGCommandletStatus()) + { + case EHoudiniBGEOCommandletStatus::Connected: + OutStatusString = TEXT("Async importer is CONNECTED"); + OutStatusColor = FLinearColor::Green; + break; + case EHoudiniBGEOCommandletStatus::Running: + OutStatusString = TEXT("Async importer is Running"); + OutStatusColor = FLinearColor::Yellow; + break; + case EHoudiniBGEOCommandletStatus::Crashed: + OutStatusString = TEXT("Async importer has CRASHED"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniBGEOCommandletStatus::NotStarted: + OutStatusString = TEXT("Async importer is NOT STARTED"); + OutStatusColor = FLinearColor::White; + break; + } +} + +void +FHoudiniPDGDetails::AddPDGCommandletStatus( + IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus) +{ + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Visibility_Lambda([]() + { + const UHoudiniRuntimeSettings* Settings = GetDefault(); + if (IsValid(Settings)) + { + return FHoudiniEngineCommands::IsPDGCommandletEnabled() ? EVisibility::Visible : EVisibility::Collapsed; + } + + return EVisibility::Visible; + }) + .Text_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetPDGCommandletStatus(StatusString, StatusColor); + return FText::FromString(StatusString); + }) + .ColorAndOpacity_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetPDGCommandletStatus(StatusString, StatusColor); + return FSlateColor(StatusColor); + }) + ] + ]; +} + +bool +FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( + UHoudiniPDGAssetLink* InAssetLink, + bool bInForSelectedNode, + const FString& InTallyItemString, + int32& OutValue, + FLinearColor& OutColor) +{ + OutValue = 0; + OutColor = FLinearColor::White; + + if (!IsValid(InAssetLink)) + return false; + + bool bFound = false; + FWorkItemTally* TallyPtr = nullptr; + if (bInForSelectedNode) + { + UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); + if (TOPNode && !TOPNode->bHidden) + TallyPtr = &(TOPNode->WorkItemTally); + } + else + TallyPtr = &(InAssetLink->WorkItemTally); + + if (TallyPtr) + { + if (InTallyItemString == TEXT("WAITING")) + { + // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI + OutValue = TallyPtr->WaitingWorkItems + TallyPtr->ScheduledWorkItems; + OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("COOKING")) + { + OutValue = TallyPtr->CookingWorkItems; + OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("COOKED")) + { + OutValue = TallyPtr->CookedWorkItems; + OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("FAILED")) + { + OutValue = TallyPtr->ErroredWorkItems; + OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; + bFound = true; + } + } + + return bFound; +} + +void +FHoudiniPDGDetails::AddWorkItemStatusWidget( + FDetailWidgetRow& InRow, const FString& InTitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode) +{ + auto AddGridBox = [InAssetLink, bInForSelectedNode](const FString& Title) -> SHorizontalBox::FSlot& + { + return SHorizontalBox::Slot() + .MaxWidth(500.0f) + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(FMargin(1.0f, 2.0f)) + [ + SNew(SBorder) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .BorderBackgroundColor(FSlateColor(FLinearColor(0.6, 0.6, 0.6))) + .Padding(FMargin(1.0f, 5.0f)) + [ + SNew(SBox) + .WidthOverride(95.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(Title)) + .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FSlateColor(Color); + }) + ] + ] + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(FMargin(1.0f, 2.0f)) + [ + SNew(SBorder) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .BorderBackgroundColor(FSlateColor(FLinearColor(0.8, 0.8, 0.8))) + .Padding(FMargin(1.0f, 5.0f)) + [ + SNew(SBox) + .WidthOverride(95.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FText::AsNumber(Value); + }) + .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FSlateColor(Color); + }) + ] + ] + ] + ]; + }; + + InRow.WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f) + .AutoWidth() + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + [ + SNew(SSpacer) + ] + + SVerticalBox::Slot() + .AutoHeight() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(FMargin(0.0f, 2.0f)) + [ + SNew(STextBlock) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .Text(FText::FromString(InTitleString)) + + ] + + SVerticalBox::Slot() + .AutoHeight() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(FMargin(0.0f, 2.0f)) + [ + SNew(SHorizontalBox) + + AddGridBox(TEXT("WAITING")) + + AddGridBox(TEXT("COOKING")) + + AddGridBox(TEXT("COOKED")) + + AddGridBox(TEXT("FAILED")) + ] + + SVerticalBox::Slot() + [ + SNew(SSpacer) + ] + ] + ]; +} + + +void +FHoudiniPDGDetails::AddTOPNetworkWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink ) +{ + if (!InPDGAssetLink->GetSelectedTOPNetwork()) + return; + + if (InPDGAssetLink->AllTOPNetworks.Num() <= 0) + return; + + TOPNetworksPtr.Reset(); + + FString GroupLabel = TEXT("TOP Networks"); + IDetailGroup& TOPNetWorkGrp = InPDGCategory.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), false, true); + + // Combobox: TOP Network + { + FDetailWidgetRow& PDGTOPNetRow = TOPNetWorkGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGTOPNetRow, InPDGAssetLink); + PDGTOPNetRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Network"))) + ]; + + // Fill the TOP Networks SharedString array + TOPNetworksPtr.SetNum(InPDGAssetLink->AllTOPNetworks.Num()); + for(int32 Idx = 0; Idx < InPDGAssetLink->AllTOPNetworks.Num(); Idx++) + { + const UTOPNetwork* Network = InPDGAssetLink->AllTOPNetworks[Idx]; + if (!IsValid(Network)) + { + TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( + Idx, + TEXT("Invalid"), + TEXT("Invalid") + )); + } + else + { + TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( + Idx, + FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Network->NodeName, Network->NodePath), + Network->NodePath + )); + } + } + + if(TOPNetworksPtr.Num() <= 0) + TOPNetworksPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, "----"))); + + // Lambda for selecting another TOPNet + auto OnTOPNetChanged = [InPDGAssetLink](TSharedPtr InNewChoice) + { + if (!InNewChoice.IsValid()) + return; + + const int32 NewChoice = InNewChoice->Value; + int32 NewSelectedIndex = -1; + if (InPDGAssetLink->AllTOPNetworks.IsValidIndex(NewChoice)) + NewSelectedIndex = NewChoice; + + if (InPDGAssetLink->SelectedTOPNetworkIndex == NewSelectedIndex) + return; + + if (NewSelectedIndex < 0) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->SelectedTOPNetworkIndex = NewSelectedIndex; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex), InPDGAssetLink); + }; + + TSharedPtr HorizontalBoxTOPNet; + TSharedPtr>> ComboBoxTOPNet; + int32 SelectedIndex = TOPNetworksPtr.IndexOfByPredicate([InPDGAssetLink](const TSharedPtr& InEntry) + { + return InEntry.IsValid() && InEntry->Value == InPDGAssetLink->SelectedTOPNetworkIndex; + }); + if (SelectedIndex < 0) + SelectedIndex = 0; + + PDGTOPNetRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .FillWidth(300.f) + .MaxWidth(300.f) + [ + SAssignNew(ComboBoxTOPNet, SComboBox>) + .OptionsSource(&TOPNetworksPtr) + .InitiallySelectedItem(TOPNetworksPtr[SelectedIndex]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); + const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryToolTip) + .Margin(2.0f) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnTOPNetChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnTOPNetChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString(InPDGAssetLink->GetSelectedTOPNetworkName()); + }) + .ToolTipText_Lambda([InPDGAssetLink]() + { + UTOPNetwork const * const Network = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(Network)) + { + if (!Network->NodePath.IsEmpty()) + return FText::FromString(Network->NodePath); + else + return FText::FromString(Network->NodeName); + } + else + { + return FText(); + } + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + // Buttons: DIRTY ALL / COOK OUTPUT + { + TSharedRef DirtyAllHBox = SNew(SHorizontalBox); + TSharedPtr CookOutHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("DirtyAll", "Dirty All")) + .ToolTipText(LOCTEXT("DirtyAllTooltip", "Dirty all TOP nodes in the selected TOP network and clears all of its work item results.")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork()); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(TOPNetwork)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + FHoudiniPDGManager::DirtyAll(TOPNetwork); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + else + { + UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(TOPNetwork); + } + } + } + + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(DirtyAllHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("CookOut", "Cook Output")) + .ToolTipText(LOCTEXT("CookOutTooltip", "Cooks the output nodes of the selected TOP network")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNet)) + return false; + + // Disable if there any nodes in the network that are already cooking + return !SelectedTOPNet->AnyWorkItemsPending(); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::CookOutput(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CookOutHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr DirtyAllIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyAllIconBrush(); + if (DirtyAllIconBrush.IsValid()) + { + TSharedPtr DirtyAllImage; + DirtyAllHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(DirtyAllImage, SImage) + ] + ]; + + DirtyAllImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([DirtyAllIconBrush]() { return DirtyAllIconBrush.Get(); }))); + } + + DirtyAllHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("DirtyAll", "Dirty All")) + ]; + + TSharedPtr CookOutIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + if (CookOutIconBrush.IsValid()) + { + TSharedPtr CookOutImage; + CookOutHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookOutImage, SImage) + ] + ]; + + CookOutImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookOutIconBrush]() { return CookOutIconBrush.Get(); }))); + } + + CookOutHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookOut", "Cook Output")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: PAUSE COOK / CANCEL COOK + { + TSharedRef PauseHBox = SNew(SHorizontalBox); + TSharedPtr CancelHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Pause", "Pause Cook")) + .ToolTipText(LOCTEXT("PauseTooltip", "Pauses cooking for the selected TOP Network")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::PauseCook(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(PauseHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Cancel", "Cancel Cook")) + .ToolTipText(LOCTEXT("CancelTooltip", "Cancels cooking the selected TOP network")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::CancelCook(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CancelHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr PauseIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGPauseIconBrush(); + if (PauseIconBrush.IsValid()) + { + TSharedPtr PauseImage; + PauseHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(PauseImage, SImage) + ] + ]; + + PauseImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([PauseIconBrush]() { return PauseIconBrush.Get(); }))); + } + + PauseHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Pause", "Pause Cook")) + ]; + + TSharedPtr CancelIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGCancelIconBrush(); + if (CancelIconBrush.IsValid()) + { + TSharedPtr CancelImage; + CancelHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CancelImage, SImage) + ] + ]; + + CancelImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CancelIconBrush]() { return CancelIconBrush.Get(); }))); + } + + CancelHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Cancel", "Cancel Cook")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: Unload Work Item Objects + { + FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork(); }) + .WidthOverride(200.0f) + [ + SNew(SButton) + .Text(LOCTEXT("UnloadWorkItemsForNetwork", "Unload All Work Item Objects")) + .ToolTipText(LOCTEXT("UnloadWorkItemsForNetworkTooltip", "Unloads / removes loaded work item results from level for all nodes in this network. Not undoable: use the \"Load Work Item Objects\" button on the individual TOP nodes to reload work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNetwork* const SelectedNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!IsValid(SelectedNet) || + INDEX_NONE == SelectedNet->AllTOPNodes.IndexOfByPredicate([](const UTOPNode* InNode) { return IsValid(InNode) && InNode->bCachedHaveLoadedWorkResults; })) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNetwork* const TOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(TOPNet)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + // Set the state to ToDelete, PDGManager will delete it when processing work items + TOPNet->SetLoadedWorkResultsToDelete(); + } + else + { + // Delete and unload the result objects and actors now + TOPNet->DeleteWorkResultOutputObjects(); + } + } + } + + return FReply::Handled(); + }) + ] + ] + ]; + } + + // TOP NODE WIDGETS + FHoudiniPDGDetails::AddTOPNodeWidget(TOPNetWorkGrp, InPDGAssetLink); +} + +bool +FHoudiniPDGDetails::GetSelectedTOPNodeStatusAndColor(UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor) +{ + OutTOPNodeStatus = FString(); + OutTOPNodeStatusColor = FLinearColor::White; + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode) && !TOPNode->bHidden) + { + OutTOPNodeStatus = UHoudiniPDGAssetLink::GetTOPNodeStatus(TOPNode); + OutTOPNodeStatusColor = UHoudiniPDGAssetLink::GetTOPNodeStatusColor(TOPNode); + + return true; + } + } + + return false; +} + +void +FHoudiniPDGDetails::AddTOPNodeWidget( + IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink ) +{ + if (!InPDGAssetLink->GetSelectedTOPNetwork()) + return; + + FString GroupLabel = TEXT("TOP Nodes"); + IDetailGroup& TOPNodesGrp = InGroup.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), true); + + // Combobox: TOP Node + { + FDetailWidgetRow& PDGTOPNodeRow = TOPNodesGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGTOPNodeRow, InPDGAssetLink); + PDGTOPNodeRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node"))) + ]; + + // Update the TOP Node SharedString + TOPNodesPtr.Reset(); + TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, LOCTEXT("ComboBoxEntryNoSelectedTOPNode", "- Select -").ToString()))); + const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNet)) + { + const int32 NumTOPNodes = SelectedTOPNet->AllTOPNodes.Num(); + for (int32 Idx = 0; Idx < NumTOPNodes; Idx++) + { + const UTOPNode* const Node = SelectedTOPNet->AllTOPNodes[Idx]; + if (!IsValid(Node) || Node->bHidden) + continue; + + TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip( + Idx, + FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Node->NodeName, Node->NodePath), + Node->NodePath + ))); + } + } + + FString NodeErrorText = FString(); + FString NodeErrorTooltip = FString(); + FLinearColor NodeErrorColor = FLinearColor::White; + if (!IsValid(SelectedTOPNet) || SelectedTOPNet->AllTOPNodes.Num() <= 0) + { + NodeErrorText = TEXT("No valid TOP Node found!"); + NodeErrorTooltip = TEXT("There is no valid TOP Node found in the selected TOP Network!"); + NodeErrorColor = FLinearColor::Red; + } + else if(TOPNodesPtr.Num() <= 0) + { + NodeErrorText = TEXT("No visible TOP Node found!"); + NodeErrorTooltip = TEXT("No visible TOP Node found, all nodes in this network are hidden. Please update your TOP Node Filter."); + NodeErrorColor = FLinearColor::Yellow; + } + + // Lambda for selecting a TOPNode + auto OnTOPNodeChanged = [InPDGAssetLink](TSharedPtr InNewChoice) + { + UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!InNewChoice.IsValid() || !IsValid(TOPNetwork)) + return; + + const int32 NewChoice = InNewChoice->Value; + int32 NewSelectedIndex = INDEX_NONE; + if (TOPNetwork->AllTOPNodes.IsValidIndex(NewChoice)) + NewSelectedIndex = NewChoice; + + if (TOPNetwork->SelectedTOPIndex != NewSelectedIndex) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNetwork); + + TOPNetwork->Modify(); + TOPNetwork->SelectedTOPIndex = NewSelectedIndex; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UTOPNetwork, SelectedTOPIndex), TOPNetwork); + } + }; + + TSharedPtr HorizontalBoxTOPNode; + TSharedPtr>> ComboBoxTOPNode; + int32 SelectedIndex = 0; + UTOPNetwork* const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNetwork) && SelectedTOPNetwork->SelectedTOPIndex >= 0) + { + //SelectedIndex = InPDGAssetLink->GetSelectedTOPNetwork()->SelectedTOPIndex; + + // We need to match the selection by the index in the AllTopNodes array + // Because of the nodefilter, it is possible that the selected index does not match the index in TOPNodesPtr + const int32 SelectedTOPNodeIndex = SelectedTOPNetwork->SelectedTOPIndex; + // Find the matching UI index + for (int32 UIIndex = 0; UIIndex < TOPNodesPtr.Num(); UIIndex++) + { + if (TOPNodesPtr[UIIndex] && TOPNodesPtr[UIIndex]->Value == SelectedTOPNodeIndex) + { + // We found the UI Index that matches the current TOP Node! + SelectedIndex = UIIndex; + break; + } + } + } + + TSharedPtr ErrorText; + + PDGTOPNodeRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .FillWidth(300.f) + .MaxWidth(300.f) + [ + SAssignNew(ComboBoxTOPNode, SComboBox>) + .OptionsSource(&TOPNodesPtr) + .InitiallySelectedItem(TOPNodesPtr[SelectedIndex]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); + const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryToolTip) + .Margin(2.0f) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnTOPNodeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnTOPNodeChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink, ComboBoxTOPNode, Options = TOPNodesPtr]() + { + if (IsValid(InPDGAssetLink)) + return FText::FromString(InPDGAssetLink->GetSelectedTOPNodeName()); + else + return FText(); + }) + .ToolTipText_Lambda([InPDGAssetLink]() + { + UTOPNode const * const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (!TOPNode->NodePath.IsEmpty()) + return FText::FromString(TOPNode->NodePath); + else + return FText::FromString(TOPNode->NodeName); + } + else + { + return FText(); + } + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .AutoWidth() + [ + SAssignNew(ErrorText, STextBlock) + .Text(FText::FromString(NodeErrorText)) + .ToolTipText(FText::FromString(NodeErrorText)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .ColorAndOpacity(FLinearColor::Red) + //.ShadowColorAndOpacity(FLinearColor::Black) + ]; + + // Update the error text if needed + ErrorText->SetText(FText::FromString(NodeErrorText)); + ErrorText->SetToolTipText(FText::FromString(NodeErrorTooltip)); + ErrorText->SetColorAndOpacity(NodeErrorColor); + + // Hide the combobox if we have an error + ComboBoxTOPNode->SetVisibility(NodeErrorText.IsEmpty() ? EVisibility::Visible : EVisibility::Hidden); + } + + // TOP Node State + { + FDetailWidgetRow& PDGNodeStateResultRow = TOPNodesGrp.AddWidgetRow(); + DisableIfPDGNotLinked(PDGNodeStateResultRow, InPDGAssetLink); + PDGNodeStateResultRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node State"))) + ]; + + PDGNodeStateResultRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + FString TOPNodeStatus = FString(); + FLinearColor TOPNodeStatusColor = FLinearColor::White; + GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); + return FText::FromString(TOPNodeStatus); + }) + .ColorAndOpacity_Lambda([InPDGAssetLink]() + { + FString TOPNodeStatus = FString(); + FLinearColor TOPNodeStatusColor = FLinearColor::White; + GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); + return FSlateColor(TOPNodeStatusColor); + }) + ]; + } + + // Checkbox: Load Work Item Output Files + { + auto ToolTipLambda = [InPDGAssetLink]() + { + bool bDisabled = false; + if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) + { + bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; + } + + return bDisabled + ? FText::FromString(TEXT("This node has child nodes, the auto-load setting must be set on the child nodes individually.")) + : FText::FromString(TEXT("When enabled, Output files produced by this TOP Node's Work Items will automatically be loaded when cooked.")); + }; + FDetailWidgetRow& PDGNodeAutoLoadRow = TOPNodesGrp.AddWidgetRow(); + + DisableIfPDGNotLinked(PDGNodeAutoLoadRow, InPDGAssetLink); + PDGNodeAutoLoadRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) + return true; + return false; + })); + + PDGNodeAutoLoadRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Auto-Load Work Item Output Files"))) + .ToolTipText_Lambda(ToolTipLambda) + ]; + + TSharedPtr AutoLoadCheckBox; + + PDGNodeAutoLoadRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(AutoLoadCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->GetSelectedTOPNode() + ? (InPDGAssetLink->GetSelectedTOPNode()->bAutoLoad ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + UTOPNode* TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(TOPNode) || TOPNode->bAutoLoad == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNode); + + TOPNode->Modify(); + TOPNode->bAutoLoad = bNewState; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UTOPNode, bAutoLoad), TOPNode); + + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + }) + .ToolTipText_Lambda(ToolTipLambda) + ]; + } + + // Checkbox: Work Item Output Files Visible + { + auto ToolTipLambda = [InPDGAssetLink]() + { + bool bDisabled = false; + if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) + { + bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; + } + + return bDisabled + ? FText::FromString(TEXT("This node has child nodes, visibility of work item outputs must be set on the child nodes individually.")) + : FText::FromString(TEXT("Toggles the visibility of the actors created from this TOP Node's Work Item File Outputs.")); + }; + + FDetailWidgetRow& PDGNodeShowResultRow = TOPNodesGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGNodeShowResultRow, InPDGAssetLink); + PDGNodeShowResultRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) + return true; + + return false; + })); + PDGNodeShowResultRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Work Item Output Files Visible"))) + .ToolTipText_Lambda(ToolTipLambda) + ]; + + TSharedPtr ShowResCheckBox; + PDGNodeShowResultRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(ShowResCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->GetSelectedTOPNode() + ? (InPDGAssetLink->GetSelectedTOPNode()->IsVisibleInLevel() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(TOPNode) || TOPNode->IsVisibleInLevel() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNode); + + TOPNode->Modify(); + TOPNode->SetVisibleInLevel(bNewState); + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(TEXT("bShow"), TOPNode); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + }) + .ToolTipText_Lambda(ToolTipLambda) + ]; + } + + // Buttons: DIRTY NODE / COOK NODE + { + TSharedRef DirtyHBox = SNew(SHorizontalBox); + TSharedPtr CookHBox = SNew(SHorizontalBox); + + TSharedPtr DirtyButton; + TSharedPtr CookButton; + + FDetailWidgetRow& PDGDirtyCookRow = TOPNodesGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); + }) + .WidthOverride(200.0f) + [ + SAssignNew(DirtyButton, SButton) + //.Text(LOCTEXT("DirtyNode", "Dirty Node")) + .ToolTipText(LOCTEXT("DirtyNodeTooltip", "Dirties the selected TOP node and clears its work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + FHoudiniPDGManager::DirtyTOPNode(TOPNode); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + else + { + UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(TOPNode); + } + } + } + + return FReply::Handled(); + }) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNode()) && !InPDGAssetLink->GetSelectedTOPNode()->bHidden) + return true; + return false; + }) + .Content() + [ + SAssignNew(DirtyHBox, SHorizontalBox) + ] + ] + ] + // + SHorizontalBox::Slot() + // .AutoWidth() + // [ + // SNew(SBox) + // .WidthOverride(200.0f) + // [ + // SAssignNew(DirtyButton, SButton) + // .Text(LOCTEXT("DirtyAllTasks", "Dirty All Tasks")) + // .ToolTipText(LOCTEXT("DirtyAllTasksTooltip", "Dirties all tasks/work items on the selected TOP node.")) + // .ContentPadding(FMargin(5.0f, 2.0f)) + // .VAlign(VAlign_Center) + // .HAlign(HAlign_Center) + // .OnClicked_Lambda([InPDGAssetLink]() + // { + // if(InPDGAssetLink->GetSelectedTOPNode()) + // { + // FHoudiniPDGManager::DirtyAllTasksOfTOPNode(*(InPDGAssetLink->GetSelectedTOPNode())); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + // } + // + // return FReply::Handled(); + // }) + // ] + // ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(200.0f) + [ + SAssignNew(CookButton, SButton) + //.Text(LOCTEXT("CookNode", "Cook Node")) + .ToolTipText(LOCTEXT("CookNodeTooltip", "Cooks the selected TOP Node.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode)) + return false; + // Disable Cook Node button if the node is already cooking + return !SelectedNode->bHidden && SelectedNode->NodeState != EPDGNodeState::Cooking && !SelectedNode->AnyWorkItemsPending(); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node)) + { + FHoudiniPDGManager::CookTOPNode(Node); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CookHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr DirtyIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyNodeIconBrush(); + if (DirtyIconBrush.IsValid()) + { + TSharedPtr DirtyImage; + DirtyHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(DirtyImage, SImage) + ] + ]; + + DirtyImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([DirtyIconBrush]() { return DirtyIconBrush.Get(); }))); + } + + DirtyHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("DirtyNode", "Dirty Node")) + ]; + + TSharedPtr CookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + if (CookIconBrush.IsValid()) + { + TSharedPtr CookImage; + CookHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookImage, SImage) + ] + ]; + + CookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookIconBrush]() { return CookIconBrush.Get(); }))); + } + + CookHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookNode", "Cook Node")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: Load Work Item Objects / Unload Work Item Objects + { + TSharedPtr UnloadWorkItemsButton; + TSharedPtr LoadWorkItemsButton; + + FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNodesGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode()); + }) + .WidthOverride(200.0f) + [ + SAssignNew(UnloadWorkItemsButton, SButton) + .Text(LOCTEXT("UnloadWorkItemsForNode", "Unload Work Item Objects")) + .ToolTipText(LOCTEXT("UnloadWorkItemsForNodeTooltip", "Unloads / removes loaded work item results from level. Not undoable: use the \"Load Work Item Objects\" button to reload the results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveLoadedWorkResults) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + // Set the state to ToDelete, PDGManager will delete it when processing work items + TOPNode->SetLoadedWorkResultsToDelete(); + } + else + { + // Delete and unload the result objects and actors now + TOPNode->DeleteWorkResultOutputObjects(); + } + } + } + + return FReply::Handled(); + }) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(200.0f) + [ + SAssignNew(LoadWorkItemsButton, SButton) + .Text(LOCTEXT("LoadWorkItems", "Load Work Item Objects")) + .ToolTipText(LOCTEXT("LoadWorkItemsForNodeTooltip", "Loads any available but not loaded work items objects (this could include items from a previous cook). Creates output actors. Not undoable: use the \"Unload Work Item Objects\" button to unload/remove loaded work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveNotLoadedWorkResults) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(SelectedNode)) + { + SelectedNode->SetNotLoadedWorkResultsToLoad(true); + } + } + return FReply::Handled(); + }) + ] + ] + ]; + } + + // TOP Node WorkItem Status + { + if (InPDGAssetLink->GetSelectedTOPNode()) + { + FDetailWidgetRow& PDGNodeWorkItemStatsRow = TOPNodesGrp.AddWidgetRow(); + DisableIfPDGNotLinked(PDGNodeWorkItemStatsRow, InPDGAssetLink); + FHoudiniPDGDetails::AddWorkItemStatusWidget( + PDGNodeWorkItemStatsRow, TEXT("TOP Node Work Item Status"), InPDGAssetLink, true); + } + } +} + +void +FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // Repopulate the network and nodes for the assetlink + if (!FHoudiniPDGManager::UpdatePDGAssetLink(InPDGAssetLink)) + return; + + FHoudiniPDGDetails::RefreshUI(InPDGAssetLink, true); +} + +void +FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // Update the workitem stats + InPDGAssetLink->UpdateWorkItemTally(); + + // Update the editor properties + FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, InFullUpdate); +} + +void +FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); + + if (!InPDGAssetLink->bBakeMenuExpanded) + return; + + auto OnBakeButtonClickedLambda = [InPDGAssetLink]() + { + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(InPDGAssetLink); + // else + FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink); + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceWithBlueprint(InPDGAssetLink); + // else + FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink); + } + break; + // + // case EHoudiniEngineBakeOption::ToFoliage: + // { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(InPDGAssetLink); + // else + // FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InPDGAssetLink); + // } + // break; + // + // case EHoudiniEngineBakeOption::ToWorldOutliner: + // { + // if (InPDGAssetLink->bIsReplace) + // { + // // Todo + // } + // else + // { + // //Todo + // } + // } + // break; + } + + return FReply::Handled(); + }; + + auto OnBakeFolderTextCommittedLambda = [InPDGAssetLink](const FText& Val, ETextCommit::Type TextCommitType) + { + FString NewPathStr = Val.ToString(); + if (NewPathStr.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + //Todo? Check if the new Bake folder path is valid + InPDGAssetLink->Modify(); + InPDGAssetLink->BakeFolder.Path = NewPathStr; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, BakeFolder), InPDGAssetLink); + }; + + // Button Row + FDetailWidgetRow & ButtonRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(ButtonRow, InPDGAssetLink); + + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + + // Bake Button + TSharedRef BakeHBox = SNew(SHorizontalBox); + TSharedPtr BakeButton; + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(15.f, 0.0f, 0.0f, 0.0f) + .MaxWidth(75.0f) + [ + SNew(SBox) + .WidthOverride(75.0f) + [ + SAssignNew(BakeButton, SButton) + //.Text(FText::FromString("Bake")) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + //.ToolTipText(LOCTEXT("HoudiniPDGDetailsBakeButton", "Bake the Houdini PDG TOP Node(s)")) + .ToolTipText_Lambda([InPDGAssetLink]() + { + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + return LOCTEXT( + "HoudiniEnginePDGBakeButtonBakeToActorToolTip", + "Bake this Houdini PDG Asset's output assets and seperate the output actors from the PDG asset link."); + } + break; + case EHoudiniEngineBakeOption::ToBlueprint: + { + return LOCTEXT( + "HoudiniEnginePDGBakeButtonBakeToBlueprintToolTip", + "Bake this Houdini PDG Asset's output assets to blueprints and remove temporary output actors that no " + "longer has output components from the PDG asset link."); + } + break; + default: + { + return FText(); + } + } + }) + .Visibility(EVisibility::Visible) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda(OnBakeButtonClickedLambda) + .Content() + [ + SAssignNew(BakeHBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); + if (BakeIconBrush.IsValid()) + { + TSharedPtr BakeImage; + BakeHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(BakeImage, SImage) + ] + ]; + + BakeImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([BakeIconBrush]() { return BakeIconBrush.Get(); }))); + } + + BakeHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Bake")) + ]; + + // bake Type ComboBox + TSharedPtr>> TypeComboBox; + + TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeTypeOptionsLabels(); + TSharedPtr IntialSelec; + if (OptionSource) + { + // IntialSelec = (*OptionSource)[(int)InPDGAssetLink->HoudiniEngineBakeOption]; + const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption); + const TSharedPtr* DefaultOption = OptionSource->FindByPredicate( + [DefaultStr](TSharedPtr InStringPtr) + { + return InStringPtr.IsValid() && *InStringPtr == DefaultStr; + } + ); + if (DefaultOption) + IntialSelec = *DefaultOption; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(93.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(93.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid() || SelectType == ESelectInfo::Type::Direct) + return; + + const EHoudiniEngineBakeOption NewOption = + FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->HoudiniEngineBakeOption) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->HoudiniEngineBakeOption = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink, TypeComboBox, OptionSource]() + { + return FText::FromString(FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + // bake selection ComboBox + TSharedPtr>> BakeSelectionComboBox; + + TArray>* PDGBakeSelectionOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeSelectionOptionsLabels(); + TSharedPtr PDGBakeSelectionIntialSelec; + if (PDGBakeSelectionOptionSource) + { + PDGBakeSelectionIntialSelec = (*PDGBakeSelectionOptionSource)[(int)InPDGAssetLink->PDGBakeSelectionOption]; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(163.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(163.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(PDGBakeSelectionOptionSource) + .InitiallySelectedItem(PDGBakeSelectionIntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EPDGBakeSelectionOption NewOption = + FHoudiniEngineEditor::Get().StringToPDGBakeSelectionOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->PDGBakeSelectionOption) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->PDGBakeSelectionOption = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromPDGBakeTargetOption(InPDGAssetLink->PDGBakeSelectionOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; + + // Bake package replacement mode row + FDetailWidgetRow & BakePackageReplaceRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(BakePackageReplaceRow, InPDGAssetLink); + + TSharedRef BakePackageReplaceRowHorizontalBox = SNew(SHorizontalBox); + + BakePackageReplaceRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeLabel", "Replace Mode")) + .ToolTipText( + LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeTooltip", "Replacement mode " + "during baking. Create new assets, using numerical suffixes in package names when necessary, or " + "replace existing assets with matching names. Also replaces the previous bake's output actors in " + "replacement mode vs creating incremental ones.")) + ] + ]; + + // bake package replace mode ComboBox + TSharedPtr>> BakePackageReplaceModeComboBox; + + TArray>* PDGBakePackageReplaceModeOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels(); + TSharedPtr PDGBakePackageReplaceModeInitialSelec; + if (PDGBakePackageReplaceModeOptionSource) + { + const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode); + const TSharedPtr* DefaultOption = PDGBakePackageReplaceModeOptionSource->FindByPredicate( + [DefaultStr](TSharedPtr InStringPtr) + { + return InStringPtr.IsValid() && *InStringPtr == DefaultStr; + } + ); + if (DefaultOption) + PDGBakePackageReplaceModeInitialSelec = *DefaultOption; + } + + BakePackageReplaceRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(163.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(163.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(PDGBakePackageReplaceModeOptionSource) + .InitiallySelectedItem(PDGBakePackageReplaceModeInitialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + const FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EPDGBakePackageReplaceModeOption NewOption = + FHoudiniEngineEditor::Get().StringToPDGBakePackageReplaceModeOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->PDGBakePackageReplaceMode) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->PDGBakePackageReplaceMode = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + BakePackageReplaceRow.WholeRowWidget.Widget = BakePackageReplaceRowHorizontalBox; + + // Bake Folder Row + FDetailWidgetRow & BakeFolderRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(BakeFolderRow, InPDGAssetLink); + + TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) + .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) + ] + ]; + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .MaxWidth(235.0) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) + .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) + .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) + ] + ]; + + BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; + + // Add additional bake options + FDetailWidgetRow & AdditionalBakeSettingsRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); + + TSharedPtr CheckBoxAutoBake; + TSharedPtr CheckBoxRecenterBakedActors; + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bRecenterBakedActors = bNewState; + + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bRecenterBakedActors), InPDGAssetLink); + }) + ] + ]; + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxAutoBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake work result object as they are loaded.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bBakeAfterWorkResultObjectLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bBakeAfterWorkResultObjectLoaded = bNewState; + + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterWorkResultObjectLoaded), InPDGAssetLink); + }) + ] + ]; + + AdditionalBakeSettingsRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText) + : Text(InText) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText, const FString &InToolTip) + : Text(InText) + , ToolTip(InToolTip) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText) + : Text(InText) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText, FString&& InToolTip) + : Text(InText) + , ToolTip(InToolTip) + , Value(InValue) +{ +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h index 75c79b1e2..dd11aa32e 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h @@ -1,140 +1,140 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Templates/SharedPointer.h" -#include "DetailWidgetRow.h" - -#include "HoudiniPDGAssetLink.h" - -class IDetailGroup; -class IDetailCategoryBuilder; - -struct FWorkItemTally; -enum class EPDGLinkState : uint8; -enum class EHoudiniBGEOCommandletStatus : uint8; - -// Convenience struct to hold a label and tooltip for widgets. -struct FTextAndTooltip -{ -public: - FTextAndTooltip(int32 InValue, const FString& InText); - FTextAndTooltip(int32 InValue, const FString& InText, const FString &InTooltip); - FTextAndTooltip(int32 InValue, FString&& InText); - FTextAndTooltip(int32 InValue, FString&& InText, FString&& InTooltip); - - FString Text; - - FString ToolTip; - - int32 Value; -}; - -class FHoudiniPDGDetails : public TSharedFromThis -{ - public: - - void CreateWidget( - IDetailCategoryBuilder & HouPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink); - //UHoudiniAssetComponent* InHAC); - - void AddPDGAssetWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - - void AddWorkItemStatusWidget( - FDetailWidgetRow& InRow, const FString& TitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode); - - void AddPDGAssetStatus( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink); - - void AddPDGCommandletStatus( - IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus); - - void AddTOPNetworkWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - - void AddTOPNodeWidget( - IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink); - - static void RefreshPDGAssetLink( - UHoudiniPDGAssetLink* InPDGAssetLink); - - static void RefreshUI( - UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate = true); - - static void - CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - protected: - // Helper function for getting the work item tally and color - static bool GetWorkItemTallyValueAndColor( - UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode, const FString& InTallyItemString, - int32& OutValue, FLinearColor& OutColor); - - // Helper to get the status text for the selected TOP node, and the color with which to display it on the UI. - // Returns false if the InPDGAssetLink is invalid, or there is no selected TOP node. - static bool GetSelectedTOPNodeStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor); - - // Helper to get asset link status and status color for UI - static bool GetPDGStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor); - - // Helper for getting the commandlet status text and color for the UI - static void GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor); - - // Helper to check if the asset link state is Linked - static FORCEINLINE bool IsPDGLinked(UHoudiniPDGAssetLink* InPDGAssetLink) - { - return IsValid(InPDGAssetLink) && InPDGAssetLink->LinkState == EPDGLinkState::Linked; - } - - // Helper for binding IsPDGLinked to a TAttribute - static FORCEINLINE void BindDisableIfPDGNotLinked(TAttribute &InAttrToBind, UHoudiniPDGAssetLink* InPDGAssetLink) - { - InAttrToBind.Bind( - TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink); - }) - ); - } - - // Helper to disable a UI row if InPDGAssetLink is not linked - static FORCEINLINE void DisableIfPDGNotLinked(FDetailWidgetRow& InRow, UHoudiniPDGAssetLink* InPDGAssetLink) - { - BindDisableIfPDGNotLinked(InRow.IsEnabledAttr, InPDGAssetLink); - } - - private: - - TArray> TOPNetworksPtr; - - TArray> TOPNodesPtr; - -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Templates/SharedPointer.h" +#include "DetailWidgetRow.h" + +#include "HoudiniPDGAssetLink.h" + +class IDetailGroup; +class IDetailCategoryBuilder; + +struct FWorkItemTally; +enum class EPDGLinkState : uint8; +enum class EHoudiniBGEOCommandletStatus : uint8; + +// Convenience struct to hold a label and tooltip for widgets. +struct FTextAndTooltip +{ +public: + FTextAndTooltip(int32 InValue, const FString& InText); + FTextAndTooltip(int32 InValue, const FString& InText, const FString &InTooltip); + FTextAndTooltip(int32 InValue, FString&& InText); + FTextAndTooltip(int32 InValue, FString&& InText, FString&& InTooltip); + + FString Text; + + FString ToolTip; + + int32 Value; +}; + +class FHoudiniPDGDetails : public TSharedFromThis +{ + public: + + void CreateWidget( + IDetailCategoryBuilder & HouPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink); + //UHoudiniAssetComponent* InHAC); + + void AddPDGAssetWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + + void AddWorkItemStatusWidget( + FDetailWidgetRow& InRow, const FString& TitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode); + + void AddPDGAssetStatus( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink); + + void AddPDGCommandletStatus( + IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus); + + void AddTOPNetworkWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + + void AddTOPNodeWidget( + IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink); + + static void RefreshPDGAssetLink( + UHoudiniPDGAssetLink* InPDGAssetLink); + + static void RefreshUI( + UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate = true); + + static void + CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + protected: + // Helper function for getting the work item tally and color + static bool GetWorkItemTallyValueAndColor( + UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode, const FString& InTallyItemString, + int32& OutValue, FLinearColor& OutColor); + + // Helper to get the status text for the selected TOP node, and the color with which to display it on the UI. + // Returns false if the InPDGAssetLink is invalid, or there is no selected TOP node. + static bool GetSelectedTOPNodeStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor); + + // Helper to get asset link status and status color for UI + static bool GetPDGStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor); + + // Helper for getting the commandlet status text and color for the UI + static void GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor); + + // Helper to check if the asset link state is Linked + static FORCEINLINE bool IsPDGLinked(UHoudiniPDGAssetLink* InPDGAssetLink) + { + return IsValid(InPDGAssetLink) && InPDGAssetLink->LinkState == EPDGLinkState::Linked; + } + + // Helper for binding IsPDGLinked to a TAttribute + static FORCEINLINE void BindDisableIfPDGNotLinked(TAttribute &InAttrToBind, UHoudiniPDGAssetLink* InPDGAssetLink) + { + InAttrToBind.Bind( + TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink); + }) + ); + } + + // Helper to disable a UI row if InPDGAssetLink is not linked + static FORCEINLINE void DisableIfPDGNotLinked(FDetailWidgetRow& InRow, UHoudiniPDGAssetLink* InPDGAssetLink) + { + BindDisableIfPDGNotLinked(InRow.IsEnabledAttr, InPDGAssetLink); + } + + private: + + TArray> TOPNetworksPtr; + + TArray> TOPNodesPtr; + +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp index e88a2121e..dfdabfce4 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp @@ -1,7309 +1,7308 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterDetails.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniInput.h" -#include "HoudiniAsset.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "SNewFilePathPicker.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "IDetailGroup.h" -#include "IDetailCustomization.h" -#include "PropertyCustomizationHelpers.h" -#include "DetailWidgetRow.h" -#include "Math/UnitConversion.h" -#include "ScopedTransaction.h" -#include "EditorDirectories.h" - -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SNumericEntryBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SMultiLineEditableTextBox.h" -#include "Widgets/Colors/SColorPicker.h" -#include "Widgets/Views/SExpanderArrow.h" -#include "Widgets/Layout/SExpandableArea.h" -#include "Widgets/Views/STableRow.h" -#include "Widgets/Input/NumericUnitTypeInterface.inl" -#include "Widgets/Images/SImage.h" -#include "Widgets/Layout/SSeparator.h" -#include "Widgets/Layout/SSplitter.h" -#include "SCurveEditorView.h" -#include "SAssetDropTarget.h" -#include "AssetThumbnail.h" - -#include "HoudiniInputDetails.h" - -#include "Framework/SlateDelegates.h" -//#include "Programs/UnrealLightmass/Private/ImportExport/3DVisualizer.h" -#include "Templates/SharedPointer.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -int32 -SCustomizedButton::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const -{ - TSharedPtr Content = GetContent(); - - // 0. Initialize Line Buffer. - TArray Line; - Line.SetNumUninitialized(2); - - // Initialize Color buffer. - FLinearColor Color = FLinearColor::White; - - // 1. Draw the radio button. - if (bIsRadioButton) - { - // Construct the radio button circles exactly once, - // All radio buttons share the same circles then - if (FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER || - FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER) - { - ConstructRadioButtonCircles(); - } - - DrawRadioButton(AllottedGeometry, OutDrawElements, LayerId, bChosen); - } - - // 2. Draw background color (if selected) - if (bChosen) - { - Line[0].X = AllottedGeometry.Size.X - AllottedGeometry.Size.Y / 2.0f + 2.5f; - Line[0].Y = Content->GetDesiredSize().Y / 2.0f; - Line[1].X = AllottedGeometry.Size.Y / 2.0f - 2.5f; - Line[1].Y = Content->GetDesiredSize().Y / 2.0f; - - Color = FLinearColor::White; - Color.A = bIsRadioButton ? 0.05 : 0.1; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, AllottedGeometry.Size.Y); - } - - // 3. Drawing square around the text - { - // Switch the point order for each line to save few value assignment cycles - Line[0].X = 0.0f; - Line[0].Y = 0.0f; - Line[1].X = 0.0f; - Line[1].Y = Content->GetDesiredSize().Y; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); - - //Line[0].X = 0.0f; - //Line[0].Y = Content->GetDesiredSize().Y; - Line[0].X = AllottedGeometry.Size.X; - Line[0].Y = Content->GetDesiredSize().Y; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, bChosen ? FLinearColor::Gray : FLinearColor::Black, true, 1.0f); - - //Line[0].X = AllottedGeometry.Size.X; - //Line[0].Y = Content->GetDesiredSize().Y; - Line[1].X = AllottedGeometry.Size.X; - Line[1].Y = 0.0f; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); /* draw gray bottom line if this tab is selected, black otherwise*/ - - //Line[0].X = AllottedGeometry.Size.X; - //Line[0].Y = 0.0f; - Line[0].X = 0.0f; - Line[0].Y = 0.0f; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); - } - - // 4. Draw child widget - Content->Paint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - - return LayerId; -}; - -void -SCustomizedButton::ConstructRadioButtonCircles() const -{ - TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); - TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); - OuterPoints.Empty(); - InnerPoints.Empty(); - - OuterPoints.SetNumZeroed(HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER); - InnerPoints.SetNumZeroed(8); - - // Construct outer circle - int32 CurDegree = 0; - int32 DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; - - for (int32 Idx = 0; Idx < HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; ++Idx) - { - OuterPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); - OuterPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); - - CurDegree += DegStep; - } - - // Construct inner circle - CurDegree = 0; - DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER; - for (int32 Idx = 0; Idx < 8; ++Idx) - { - InnerPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); - InnerPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); - - CurDegree += DegStep; - } -} - -void -SCustomizedButton::DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const -{ - TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); - TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); - if (OuterPoints.Num() <= 1 || InnerPoints.Num() <= 1) - return; - - FLinearColor ColorNonSelected = FLinearColor::White; - FLinearColor ColorSelected = FLinearColor::Yellow; - - // initialize line buffer - TArray Line; - Line.SetNumZeroed(2); - bool alternator = false; - - // Draw outer circle - Line[0] = OuterPoints.Last(); - for (int32 Idx = 0; Idx < OuterPoints.Num(); ++Idx) - { - // alternate the points order each time to some some assignment cycles - if (alternator) - { - Line[0].X = OuterPoints[Idx].X; - Line[0].Y = OuterPoints[Idx].Y; - } - else - { - Line[1].X = OuterPoints[Idx].X; - Line[1].Y = OuterPoints[Idx].Y; - } - - alternator = !alternator; - - // Draw a line segment - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, ColorNonSelected, true, 1.0f); - } - - // Draw inner circle - alternator = false; - Line[0] = InnerPoints.Last(); - for (int32 Idx = 0; Idx < InnerPoints.Num(); ++Idx) - { - // alternate the points order each time to some some assignment cycles - if (alternator) - { - Line[0].X = InnerPoints[Idx].X; - Line[0].Y = InnerPoints[Idx].Y; - } - else - { - Line[1].X = InnerPoints[Idx].X; - Line[1].Y = InnerPoints[Idx].Y; - } - - alternator = !alternator; - - // Draw a line segment - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, bSelected ? ColorSelected : ColorNonSelected, true, 3.0f); - } -} - -void -SCustomizedBox::SetHoudiniParameter(TArray& InParams) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - - bool bIsMultiparmInstanceHeader = MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 0; - - switch (MainParam->GetParameterType()) - { - case EHoudiniParameterType::Button: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON; - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP; - } - break; - - case EHoudiniParameterType::Color: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR; - } - break; - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor * ColorRampParameter = Cast(MainParam); - if (!ColorRampParameter || ColorRampParameter->IsPendingKill()) - return; - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP; - if (ColorRampParameter->CachedPoints.Num() > 0) - MarginHeight = MarginHeight + HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE * (float)(ColorRampParameter->CachedPoints.Num() - 1); - } - break; - - case EHoudiniParameterType::File: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE; - } - break; - - case EHoudiniParameterType::FileDir: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR; - } - break; - - case EHoudiniParameterType::FileGeo: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO; - } - break; - - case EHoudiniParameterType::FileImage: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE; - } - break; - - case EHoudiniParameterType::Float: - { - if (MainParam->GetTupleSize() == 3) - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3; - } - else - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT - + (MainParam->GetTupleSize() - 1)* HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE; - } - } - } - break; - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat * FloatRampParameter = Cast(MainParam); - if (!FloatRampParameter || FloatRampParameter->IsPendingKill()) - return; - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP; - - if (FloatRampParameter->CachedPoints.Num() > 0) - MarginHeight = MarginHeight + Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE * (float)(FloatRampParameter->CachedPoints.Num() - 1); - } - break; - - case EHoudiniParameterType::Folder: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER; - } - break; - - case EHoudiniParameterType::FolderList: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST; - } - break; - - case EHoudiniParameterType::Input: - { - UHoudiniParameterOperatorPath* InputParam = Cast(MainParam); - - if (!InputParam || InputParam->IsPendingKill() || !InputParam->HoudiniInput.IsValid()) - break; - - UHoudiniInput* Input = InputParam->HoudiniInput.Get(); - - if (!Input || Input->IsPendingKill()) - break; - - - if (bIsMultiparmInstanceHeader) - { - switch (Input->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - int32 ExpandedTransformUIs = 0; - for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) - { - if (Input->IsTransformUIExpanded(Idx)) - ExpandedTransformUIs += 1; - } - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER - + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Curve: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Asset: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Landscape: - { - if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::World: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Skeletal: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER; - } - break; - default: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER; - break; - } - } - else - { - switch (Input->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - int32 ExpandedTransformUIs = 0; - for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) - { - if (Input->IsTransformUIExpanded(Idx)) - ExpandedTransformUIs += 1; - } - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE - + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM; - } - break; - case EHoudiniInputType::Curve: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE; - } - break; - case EHoudiniInputType::Asset: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET; - } - break; - case EHoudiniInputType::Landscape: - { - if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH; - } - break; - case EHoudiniInputType::World: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD; - } - break; - case EHoudiniInputType::Skeletal: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL; - } - break; - default: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT; - break; - - } - } - } - break; - - case EHoudiniParameterType::Int: - { - if (MainParam->GetTupleSize() == 3) - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3; - } - else - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER + - (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE; - } - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE; - } - break; - - case EHoudiniParameterType::Label: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL; - } - break; - - case EHoudiniParameterType::MultiParm: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM; - } - break; - - case EHoudiniParameterType::Separator: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR; - bIsSeparator = true; - } - break; - - case EHoudiniParameterType::String: - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE; - } - } - break; - - case EHoudiniParameterType::StringAssetRef: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF; - } - break; - - case EHoudiniParameterType::StringChoice: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE; - } - break; - - case EHoudiniParameterType::Toggle: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE; - } - break; - - case EHoudiniParameterType::Invalid: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID; - break; - - default: - MarginHeight = 0.0f; - break; - } -} - -float -SCustomizedBox::AddIndentation(UHoudiniParameter* InParam, - TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists) -{ - if (!InParam || InParam->IsPendingKill()) - return 0.0f; - - bool bIsMainParmSimpleFolder = false; - // Get if this Parameter is a simple / collapsible folder - if (InParam->GetParameterType() == EHoudiniParameterType::Folder) - { - UHoudiniParameterFolder* FolderParm = Cast(InParam); - if (FolderParm) - bIsMainParmSimpleFolder = !FolderParm->IsTab(); - } - - int32 ParentId = InParam->GetParentParmId(); - UHoudiniParameter* CurParm = InParam; - float Indentation = 0.0f; - - while (ParentId >= 0) - { - UHoudiniParameter* ParentFolder = nullptr; - UHoudiniParameterMultiParm* ParentMultiParm = nullptr; - - if (InAllFoldersAndFolderLists.Contains(ParentId)) - ParentFolder = InAllFoldersAndFolderLists[ParentId]; - - if (InAllMultiParms.Contains(ParentId)) - ParentMultiParm = InAllMultiParms[ParentId]; - - // The parent is a folder, add one unit of indentation - if (ParentFolder) - { - // Update the parent parm id - ParentId = ParentFolder->GetParentParmId(); - - if (ParentFolder->GetParameterType() == EHoudiniParameterType::FolderList) - continue; - - UHoudiniParameterFolder* Folder = Cast(ParentFolder); - - if (!Folder) - continue; - - // update the current parm, find the parent of new cur param in the next round - CurParm = Folder; - Indentation += 1.0f; - } - // The parent is a multiparm - else if (ParentMultiParm) - { - // Update the parent parm id - ParentId = ParentMultiParm->GetParentParmId(); - - if (CurParm->GetChildIndex() == 0) - { - Indentation += 0.0f; - } - else - { - Indentation += 2.0f; - } - - // update the current parm, find the parent of new cur param in the next round - CurParm = ParentMultiParm; - } - else - { - // no folder/multiparm parent, end the loop - ParentId = -1; - } - } - - - float IndentationWidth = INDENTATION_UNIT_WIDTH * Indentation; - - // Add a base indentation to non simple/collapsible param - // Since it needs more space to offset the arrow width - if (!bIsMainParmSimpleFolder) - IndentationWidth += NON_FOLDER_OFFSET_WIDTH; - - this->AddSlot().AutoWidth() - [ - SNew(SBox).WidthOverride(IndentationWidth) - ]; - - - return IndentationWidth; -}; - -int32 -SCustomizedBox::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const -{ - - SHorizontalBox::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - - // Initialize line buffer - TArray Line; - Line.SetNumZeroed(2); - // Initialize color buffer - FLinearColor Color = FLinearColor::White; - Color.A = 0.3; - - // draw the bottom line if this row is the tab folder list - if (bIsTabFolderListRow) - { - // Get the start position of the tabs bottom line (right bottom pt of the right most child widget) - float VerticalLineStartPosX = 0.0f; - float VerticalLineStartPosY = 0.0f; - float BottomLineStartPosX = 0.0f; - float BottomLineStartPosY = -1.0f; - - for (int32 Idx = 0; Idx < Children.Num(); ++Idx) - { - TSharedPtr CurChild = Children.GetChildAt(Idx); - if (!CurChild.IsValid()) - continue; - - if (Idx == 0) - { - VerticalLineStartPosX = CurChild->GetDesiredSize().X; - VerticalLineStartPosY = CurChild->GetDesiredSize().Y; - } - - BottomLineStartPosX += CurChild->GetDesiredSize().X; - - if (BottomLineStartPosY < 0.0f) - BottomLineStartPosY= CurChild->GetDesiredSize().Y; - } - - // Draw bottom line - Line[0].X = BottomLineStartPosX; - Line[0].Y = BottomLineStartPosY; - Line[1].X = AllottedGeometry.Size.X; - Line[1].Y = BottomLineStartPosY; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - - // Draw divider lines - { - Line[0].Y = -MarginHeight; - Line[1].Y = AllottedGeometry.Size.Y + MarginHeight; - - int32 NumOfLinesToDraw = bIsTabFolderListRow ? DividerLinePositions.Num() - 1 : DividerLinePositions.Num(); - for (int32 Idx = 0; Idx < NumOfLinesToDraw; ++Idx) - { - const float& CurDivider = DividerLinePositions[Idx]; - Line[0].X = CurDivider; - Line[1].X = CurDivider; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - - // Draw the last inner most divider line differently when this the tabs' row. - if (bIsTabFolderListRow && DividerLinePositions.Num() > 0) - { - const float& TabDivider = DividerLinePositions.Last(); - Line[0].X = TabDivider; - Line[1].X = TabDivider; - Line[0].Y = 0.f; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - } - - // Draw tab ending lines - { - float YPos = 0.0f; - - for (const float & CurEndingDivider : EndingDividerLinePositions) - { - // Draw cur ending line (vertical) - - Line[0].X = CurEndingDivider; - Line[0].Y = -2.3f; - Line[1].X = CurEndingDivider; - Line[1].Y = YPos; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - - // Draw cur ending line (horizontal) - - // Line[0].X = CurEndingDivider; - Line[0].Y = YPos; - Line[1].X = AllottedGeometry.Size.X; - // Line[1].Y = YPos; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - - YPos += 2.0f; - } - } - - // Draw the separator line if this is the row of a separator parameter - { - if (bIsSeparator) - { - Line[0].X = 25.f; - if (DividerLinePositions.Num() > 0) - Line[0].X += DividerLinePositions.Last(); - - Line[0].Y = AllottedGeometry.Size.Y / 2.f; - Line[1].X = AllottedGeometry.Size.X - 20.f; - Line[1].Y = Line[0].Y; - - Color.A = 0.7; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.5f); - } - } - - return LayerId; -}; - -void -SHoudiniFloatRampCurveEditor::Construct(const FArguments & InArgs) -{ - SCurveEditor::Construct(SCurveEditor::FArguments() - .ViewMinInput(InArgs._ViewMinInput) - .ViewMaxInput(InArgs._ViewMaxInput) - .ViewMinOutput(InArgs._ViewMinOutput) - .ViewMaxOutput(InArgs._ViewMaxOutput) - .XAxisName(InArgs._XAxisName) - .YAxisName(InArgs._YAxisName) - .HideUI(InArgs._HideUI) - .DrawCurve(InArgs._DrawCurve) - .TimelineLength(InArgs._TimelineLength) - .AllowZoomOutput(InArgs._AllowZoomOutput) - .ShowInputGridNumbers(InArgs._ShowInputGridNumbers) - .ShowOutputGridNumbers(InArgs._ShowOutputGridNumbers) - .ShowZoomButtons(InArgs._ShowZoomButtons) - .ZoomToFitHorizontal(InArgs._ZoomToFitHorizontal) - .ZoomToFitVertical(InArgs._ZoomToFitVertical) - ); - - - UCurveEditorSettings * CurveEditorSettings = GetSettings(); - if (CurveEditorSettings) - { - CurveEditorSettings->SetTangentVisibility(ECurveEditorTangentVisibility::NoTangents); - } -} - -void -SHoudiniColorRampCurveEditor::Construct(const FArguments & InArgs) -{ - SColorGradientEditor::Construct(SColorGradientEditor::FArguments() - .ViewMinInput(InArgs._ViewMinInput) - .ViewMaxInput(InArgs._ViewMaxInput) - ); -} - - -FReply -SHoudiniFloatRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - FReply Reply = SCurveEditor::OnMouseButtonUp(MyGeometry, MouseEvent); - - if (!HoudiniFloatRampCurve.IsValid()) - return Reply; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - FRichCurve& FloatCurve = HoudiniFloatRampCurve.Get()->FloatCurve; - - TArray>& FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; - - if (FloatRampParameters.Num() < 1) - return Reply; - - if (!FloatRampParameters[0].IsValid()) - return Reply; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - // Do not allow modification when the parent HDA of the main param is being cooked. - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points of the main float ramp param to other parameters - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - // Modification is based on the main parameter, use synced points if the main param is on auto update mode, use cached points otherwise. - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - // On mouse button up handler handles point modification only - if (FloatCurve.GetNumKeys() != NumMainPoints) - return Reply; - - bool bNeedToRefreshEditor= false; - - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float& CurvePosition = FloatCurve.Keys[Idx].Time; - float& CurveValue = FloatCurve.Keys[Idx].Value; - - // This point is modified - if (MainPoint->GetPosition() != CurvePosition || MainPoint->GetValue() != CurveValue) - { - - // The editor needs refresh only if the main parameter is on manual mode, and has been modified - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedToRefreshEditor = true; - - // Iterate through the float ramp parameter of all selected HDAs. - for (auto & NextRampFloat : FloatRampParameters) - { - if (!NextRampFloat.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedRampFloat = NextRampFloat.Get(); - - if (!SelectedRampFloat) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedRampFloat)) - continue; - - if (SelectedRampFloat->IsAutoUpdate() && bCookingEnabled) - { - // The selected float ramp parameter is on auto update mode, use its synced points. - TArray &SelectedRampPoints = SelectedRampFloat->Points; - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // Synced points in the selected ramp is more than or the same number as that in the main parameter, - // modify the position and value of the synced point and mark them as changed. - - UHoudiniParameterRampFloatPoint*& ModifiedPoint = SelectedRampPoints[Idx]; - - if (!ModifiedPoint) - continue; - - if (ModifiedPoint->GetPosition() != CurvePosition && ModifiedPoint->PositionParentParm) - { - ModifiedPoint->SetPosition(CurvePosition); - ModifiedPoint->PositionParentParm->MarkChanged(true); - } - - if (ModifiedPoint->GetValue() != CurveValue && ModifiedPoint->ValueParentParm) - { - ModifiedPoint->SetValue(CurveValue); - ModifiedPoint->ValueParentParm->MarkChanged(true); - } - } - else - { - // Synced points in the selected ramp is less than that in the main parameter - // Since we have pushed the points of the main param to all of the selected ramps, - // We need to modify the insert event. - - int32 IndexInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedRampFloat->ModificationEvents.IsValidIndex(Idx)) - { - UHoudiniParameterRampModificationEvent*& ModEvent = SelectedRampFloat->ModificationEvents[Idx]; - if (!ModEvent) - continue; - - if (ModEvent->InsertPosition != CurvePosition) - ModEvent->InsertPosition = CurvePosition; - - if (ModEvent->InsertFloat != CurveValue) - ModEvent->InsertFloat = CurveValue; - } - - } - } - else - { - // The selected float ramp is on manual update mode, use the cached points. - TArray &FloatRampCachedPoints = SelectedRampFloat->CachedPoints; - - // Since we have pushed the points in main param to all the selected float ramp, - // we need to modify the corresponding cached point in the selected float ramp. - - if (FloatRampCachedPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampFloatPoint*& ModifiedCachedPoint = FloatRampCachedPoints[Idx]; - - if (!ModifiedCachedPoint) - continue; - - if (ModifiedCachedPoint->Position != CurvePosition) - { - ModifiedCachedPoint->Position = CurvePosition; - SelectedRampFloat->bCaching = true; - if (!bCookingEnabled) - { - //SelectedRampFloat->MarkChanged(true); - if (ModifiedCachedPoint->PositionParentParm) - ModifiedCachedPoint->PositionParentParm->MarkChanged(true); - } - } - - if (ModifiedCachedPoint->Value != CurveValue) - { - ModifiedCachedPoint->Value = CurveValue; - SelectedRampFloat->bCaching = true; - if (!bCookingEnabled) - { - //SelectedRampFloat->MarkChanged(true); - if (ModifiedCachedPoint->ValueParentParm) - ModifiedCachedPoint->ValueParentParm->MarkChanged(true); - } - } - } - } - } - } - } - - - if (bNeedToRefreshEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } - - return Reply; -} - -FReply -SHoudiniFloatRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) -{ - FReply Reply = SCurveEditor::OnKeyDown(MyGeometry, InKeyEvent); - - if (InKeyEvent.GetKey().ToString() != FString("Enter")) - return Reply; - - if (!HoudiniFloatRampCurve.IsValid() || !HoudiniFloatRampCurve.Get()) - return Reply; - - TArray> FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; - - if (FloatRampParameters.Num() < 1) - return Reply; - - if (!FloatRampParameters[0].IsValid()) - return Reply; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Do nothing if the main param is on auto update mode - if (MainParam->IsAutoUpdate() && bCookingEnabled) - return Reply; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points in the main float ramp to the float ramp parameters in all selected HDAs. - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - for (auto& NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - if (!SelectedFloatRamp) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not sync the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - // Sync the cached points if the selected float ramp parameter is on manual update mode - //FHoudiniParameterDetails::SyncCachedFloatRampPoints(SelectedFloatRamp); - SelectedFloatRamp->SyncCachedPoints(); - } - - return Reply; -} - -void -UHoudiniFloatRampCurve::OnCurveChanged(const TArray& ChangedCurveEditInfos) -{ - Super::OnCurveChanged(ChangedCurveEditInfos); - - if (FloatRampParameters.Num() < 1) - return; - - if (!FloatRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return; - - // Do not allow modification when the parent HDA of the main param is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - // Push all the points of the Main parameter to other parameters - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode, otherwise use its cached points. - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - bool bNeedUpdateEditor = false; - - // OnCurveChanged handler handles point delete and insertion only - - // A point is deleted. - if (FloatCurve.GetNumKeys() < NumMainPoints) - { - // Find the index of the deleted point - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float CurPointPosition = MainPoint->GetPosition(); - float CurCurvePosition = -1.0f; - - if (FloatCurve.Keys.IsValidIndex(Idx)) - CurCurvePosition = FloatCurve.Keys[Idx].Time; - - // Delete the point at Idx - if (CurCurvePosition != CurPointPosition) - { - // Iterate through all the float ramp parameter in all the selected HDAs - for (auto & NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - if (!SelectedFloatRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - TArray & SelectedRampPoints = SelectedFloatRamp->Points; - - // The selected float ramp is on auto update mode: - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // If the number of synced points of the selected float ramp is greater or equal to the number of points of that in the main param, - // Create a Houdini engine manager event to delete the point at Idx of the selected float ramp; - - UHoudiniParameterRampFloatPoint* PointToDelete = SelectedRampPoints[Idx]; - - if (!PointToDelete) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(SelectedFloatRamp, PointToDelete->InstanceIndex); - SelectedFloatRamp->MarkChanged(true); - } - else - { - // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, - // delete the corresponding inserting event. - - int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - SelectedFloatRamp->ModificationEvents.RemoveAt(IdxInEventsArray); - } - } - else - { - // The selected float ramp is on manual update mode: - // Since we have pushed all the points in main param to the cached points of the selected float ramp, - // remove the corresponding points from the cached points array. - - if (SelectedFloatRamp->CachedPoints.IsValidIndex(Idx)) - { - SelectedFloatRamp->CachedPoints.RemoveAt(Idx); - SelectedFloatRamp->bCaching = true; - } - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point is inserted - else if (FloatCurve.GetNumKeys() > NumMainPoints) - { - // Find the index of the inserted point - for (int32 Idx = 0; Idx < FloatCurve.GetNumKeys(); ++Idx) - { - - float CurPointPosition = -1.0f; - float CurCurvePosition = FloatCurve.Keys[Idx].Time; - - if (MainPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - CurPointPosition = MainPoint->GetPosition(); - } - - // Insert instance at Idx - if (CurPointPosition != CurCurvePosition) - { - // Iterate through the float ramp parameter of all selected HDAs. - for (auto & NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - // If the selected float ramp is on auto update mode: - // Since we have pushed all the points of main parameter to the selected, - // create a Houdini engine manager event to insert a point. - - FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent( - SelectedFloatRamp, CurCurvePosition, FloatCurve.Keys[Idx].Value, EHoudiniRampInterpolationType::LINEAR); - - SelectedFloatRamp->MarkChanged(true); - } - else - { - // If the selected float ramp is on manual update mode: - // push a new point to the cached points array - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(SelectedFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = CurCurvePosition; - NewCachedPoint->Value = FloatCurve.Keys[Idx].Value; - NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; - - if (Idx >= SelectedFloatRamp->CachedPoints.Num()) - SelectedFloatRamp->CachedPoints.Add(NewCachedPoint); - else - SelectedFloatRamp->CachedPoints.Insert(NewCachedPoint, Idx); - - SelectedFloatRamp->bCaching = true; - - if (!bCookingEnabled) - SelectedFloatRamp->MarkChanged(true); - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - if (bNeedUpdateEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } - -} - - -FReply -SHoudiniColorRampCurveEditor::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - FReply Reply = SColorGradientEditor::OnMouseButtonDown(MyGeometry, MouseEvent); - - if (HoudiniColorRampCurve.IsValid()) - { - UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); - if (Curve) - Curve->bEditing = true; - } - - return Reply; -} - -FReply -SHoudiniColorRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - - FReply Reply = SColorGradientEditor::OnMouseButtonUp(MyGeometry, MouseEvent); - - if (HoudiniColorRampCurve.IsValid()) - { - UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); - - if (Curve) - { - Curve->bEditing = false; - Curve->OnColorRampCurveChanged(true); - } - } - - return Reply; - -} - -FReply -SHoudiniColorRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) -{ - FReply Reply = SColorGradientEditor::OnKeyDown(MyGeometry, InKeyEvent); - - if (InKeyEvent.GetKey().ToString() != FString("Enter")) - return Reply; - - if (!HoudiniColorRampCurve.IsValid() || !HoudiniColorRampCurve.Get()) - return Reply; - - TArray> &ColorRampParameters = HoudiniColorRampCurve.Get()->ColorRampParameters; - - if (ColorRampParameters.Num() < 1) - return Reply; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - // Do nothing if the main param is on auto update mode - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - if (MainParam->IsAutoUpdate() && bCookingEnabled) - return Reply; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points in the main color ramp to the color ramp parameters in all selected HDAs. - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); - - for (auto& NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not sync the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - // Sync the cached points if the selected color ramp is on manual update mode - FHoudiniParameterDetails::SyncCachedColorRampPoints(SelectedColorRamp); - } - - return Reply; -} - -void -UHoudiniColorRampCurve::OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) -{ - Super::OnCurveChanged(ChangedCurveEditInfos); - - OnColorRampCurveChanged(); -} - -void -UHoudiniColorRampCurve::OnColorRampCurveChanged(bool bModificationOnly) -{ - // Array is always true in this case - // if (!FloatCurves) - // return; - - if (ColorRampParameters.Num() < 1) - return; - - if (!ColorRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return; - - // Do not allow modification when the parent HDA of the main param is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - // Push all the points of the main parameter to other parameters - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); - - // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode,otherwise use its cached points. - bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - bool bNeedUpdateEditor = false; - - // OnCurveChanged handler of color ramp curve editor handles point delete, insert and color change - - // A point is deleted - if (FloatCurves->GetNumKeys() < NumMainPoints) - { - if (bModificationOnly) - return; - - // Find the index of the deleted point - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float CurPointPosition = MainPoint->GetPosition(); - float CurCurvePosition = -1.0f; - - if (FloatCurves[0].Keys.IsValidIndex(Idx)) - CurCurvePosition = FloatCurves[0].Keys[Idx].Time; - - // Delete the point at Idx - if (CurCurvePosition != CurPointPosition) - { - // Iterate through all the color ramp parameter in all the selected HDAs - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - TArray & SelectedRampPoints = SelectedColorRamp->Points; - - // The selected color ramp is on auto update mode: - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // If the number of synced points of the selected color ramp is greater or equal to the number of points of that in the main param, - // create a Houdini engine manager event to delete the point at Idx of the selected float ramp; - - UHoudiniParameterRampColorPoint* PointToDelete = SelectedRampPoints[Idx]; - - if (!PointToDelete) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(SelectedColorRamp, PointToDelete->InstanceIndex); - SelectedColorRamp->MarkChanged(true); - } - else - { - // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, - // delete the corresponding inserting event. - - int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - SelectedColorRamp->ModificationEvents.RemoveAt(IdxInEventsArray); - } - } - else - { - // The selected color ramp is on manual update mode: - // Since we have pushed all the points in main param to the cached points of the selected float ramp, - // remove the corresponding points from the cached points array - if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) - { - SelectedColorRamp->CachedPoints.RemoveAt(Idx); - SelectedColorRamp->bCaching = true; - } - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point is inserted - else if (FloatCurves[0].GetNumKeys() > NumMainPoints) - { - - if (bModificationOnly) - return; - - // Find the index of the inserted point - for (int32 Idx = 0; Idx < FloatCurves[0].GetNumKeys(); ++Idx) - { - - float CurPointPosition = -1.0f; - float CurCurvePosition = FloatCurves[0].Keys[Idx].Time; - - if (MainPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - CurPointPosition = MainPoint->GetPosition(); - } - - // Insert a point at Idx - if (CurPointPosition != CurCurvePosition) - { - // Get the interpolation value of inserted color point - - FLinearColor ColorPrev = FLinearColor::Black; - FLinearColor ColorNext = FLinearColor::White; - float PositionPrev = 0.0f; - float PositionNext = 1.0f; - - if (MainParam->IsAutoUpdate() && bCookingEnabled) - { - // Try to get its previous point's color - if (MainParam->Points.IsValidIndex(Idx - 1)) - { - ColorPrev = MainParam->Points[Idx - 1]->GetValue(); - PositionPrev = MainParam->Points[Idx - 1]->GetPosition(); - } - - // Try to get its next point's color - if (MainParam->Points.IsValidIndex(Idx)) - { - ColorNext = MainParam->Points[Idx]->GetValue(); - PositionNext = MainParam->Points[Idx]->GetPosition(); - } - } - else - { - // Try to get its previous point's color - if (MainParam->CachedPoints.IsValidIndex(Idx - 1)) - { - ColorPrev = MainParam->CachedPoints[Idx - 1]->GetValue(); - PositionPrev = MainParam->CachedPoints[Idx - 1]->GetPosition(); - } - - // Try to get its next point's color - if (MainParam->CachedPoints.IsValidIndex(Idx)) - { - ColorNext = MainParam->CachedPoints[Idx]->GetValue(); - PositionNext = MainParam->CachedPoints[Idx]->GetPosition(); - } - } - - float TotalWeight = FMath::Abs(PositionNext - PositionPrev); - float PrevWeight = FMath::Abs(CurCurvePosition - PositionPrev); - float NextWeight = FMath::Abs(PositionNext - CurCurvePosition); - - FLinearColor InsertedColor = ColorPrev * (PrevWeight / TotalWeight) + ColorNext * (NextWeight / TotalWeight); - - // Iterate through the color ramp parameter of all selected HDAs. - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - // If the selected color ramp is on auto update mode: - // Since we have pushed all the points of main parameter to the selected, - // create a Houdini engine manager event to insert a point. - - FHoudiniParameterDetails::CreateColorRampParameterInsertEvent( - SelectedColorRamp, CurCurvePosition, InsertedColor, EHoudiniRampInterpolationType::LINEAR); - - SelectedColorRamp->MarkChanged(true); - } - else - { - // If the selected color ramp is on manual update mode: - // Push a new point to the cached points array - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(SelectedColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = CurCurvePosition; - NewCachedPoint->Value = InsertedColor; - NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; - - if (Idx >= SelectedColorRamp->CachedPoints.Num()) - SelectedColorRamp->CachedPoints.Add(NewCachedPoint); - else - SelectedColorRamp->CachedPoints.Insert(NewCachedPoint, Idx); - - SelectedColorRamp->bCaching = true; - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!MainParam->IsAutoUpdate() && bCookingEnabled) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point's color is changed - else - { - if (bEditing) - return; - - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - // Only handle color change - { - float CurvePosition = FloatCurves[0].Keys[Idx].Time; - float PointPosition = MainPoint->GetPosition(); - - FLinearColor CurveColor = FLinearColor::Black; - FLinearColor PointColor = MainPoint->GetValue(); - - CurveColor.R = FloatCurves[0].Keys[Idx].Value; - CurveColor.G = FloatCurves[1].Keys[Idx].Value; - CurveColor.B = FloatCurves[2].Keys[Idx].Value; - - // Color is changed at Idx - if (CurveColor != PointColor || CurvePosition != PointPosition) - { - // Iterate through the all selected color ramp parameters - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - // The selected color ramp parameter is on auto update mode - - if (SelectedColorRamp->Points.IsValidIndex(Idx)) - { - // If the number of synced points in the selected color ramp is more or equal to that in the main parameter: - // Modify the corresponding synced point of the selected color ramp, and marked it as changed. - - UHoudiniParameterRampColorPoint* Point = SelectedColorRamp->Points[Idx]; - - if (!Point) - continue; - - if (Point->GetValue() != CurveColor && Point->ValueParentParm) - { - Point->SetValue(CurveColor); - Point->ValueParentParm->MarkChanged(true); - } - - if (Point->GetPosition() != CurvePosition && Point->PositionParentParm) - { - Point->SetPosition(CurvePosition); - Point->PositionParentParm->MarkChanged(true); - } - } - else - { - // If the number of synced points in the selected color ramp is less than that in the main parameter: - // Since we have push the points in the main parameter to all selected parameters, - // we need to modify the corresponding insert event. - - int32 IdxInEventsArray = Idx - SelectedColorRamp->Points.Num(); - - if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = SelectedColorRamp->ModificationEvents[IdxInEventsArray]; - - if (!Event) - continue; - - if (Event->InsertColor != CurveColor) - Event->InsertColor = CurveColor; - - if (Event->InsertPosition != CurvePosition) - Event->InsertPosition = CurvePosition; - } - } - } - else - { - // The selected color ramp is on manual update mode - // Since we have push the points in the main parameter to all selected parameters, - // modify the corresponding point in the cached points array of the selected color ramp. - if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampColorPoint* CachedPoint = SelectedColorRamp->CachedPoints[Idx]; - - if (!CachedPoint) - continue; - - if (CachedPoint->Value != CurveColor) - { - CachedPoint->Value = CurveColor; - bNeedUpdateEditor = true; - } - - if (CachedPoint->Position != CurvePosition) - { - CachedPoint->Position = CurvePosition; - SelectedColorRamp->bCaching = true; - bNeedUpdateEditor = true; - } - } - } - } - } - } - } - } - - - if (bNeedUpdateEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } -} - -template< class T > -bool FHoudiniParameterDetails::CastParameters( - TArray InParams, TArray& OutCastedParams ) -{ - for (auto CurrentParam : InParams) - { - T* CastedParam = Cast(CurrentParam); - if (CastedParam && !CastedParam->IsPendingKill()) - OutCastedParams.Add(CastedParam); - } - - return (OutCastedParams.Num() == InParams.Num()); -} - - -void -FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* InParam = InParams[0]; - if (!InParam || InParam->IsPendingKill()) - return; - - // The directory won't parse if parameter ids are -1 - // simply return - if (InParam->GetParmId() < 0) - return; - - // This parameter is a part of the last float ramp. - if (CurrentRampFloat) - { - CreateWidgetFloatRamp(HouParameterCategory, InParams); - return; - } - // This parameter is a part of the last float ramp. - if (CurrentRampColor) - { - CreateWidgetColorRamp(HouParameterCategory, InParams); - return; - } - - switch (InParam->GetParameterType()) - { - case EHoudiniParameterType::Float: - { - CreateWidgetFloat(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Int: - { - CreateWidgetInt(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::String: - { - CreateWidgetString(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::IntChoice: - case EHoudiniParameterType::StringChoice: - { - CreateWidgetChoice(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Separator: - { - TArray SepParams; - if (CastParameters(InParams, SepParams)) - { - bool bEnabled = InParams.IsValidIndex(0) ? !SepParams[0]->IsDisabled() : true; - CreateWidgetSeparator(HouParameterCategory, InParams, bEnabled); - } - } - break; - - case EHoudiniParameterType::Color: - { - CreateWidgetColor(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Button: - { - CreateWidgetButton(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - CreateWidgetButtonStrip(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Label: - { - CreateWidgetLabel(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Toggle: - { - CreateWidgetToggle(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - CreateWidgetFile(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::FolderList: - { - CreateWidgetFolderList(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Folder: - { - CreateWidgetFolder(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::MultiParm: - { - CreateWidgetMultiParm(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::FloatRamp: - { - CreateWidgetFloatRamp(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::ColorRamp: - { - CreateWidgetColorRamp(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Input: - { - CreateWidgetOperatorPath(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Invalid: - { - HandleUnsupportedParmType(HouParameterCategory, InParams); - } - break; - - default: - { - HandleUnsupportedParmType(HouParameterCategory, InParams); - } - break; - } - - // Remove a divider lines recurrsively if current parameter hits the end of a tabs - RemoveTabDividers(HouParameterCategory, InParam); - -} - -void -FHoudiniParameterDetails::CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory) -{ - FDetailWidgetRow & Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()); - TSharedPtr TabEndingRow = SNew(SCustomizedBox); - - TabEndingRow->DividerLinePositions = DividerLinePositions; - - if (TabEndingRow.IsValid()) - CurrentTabEndingRow = TabEndingRow.Get(); - - Row.WholeRowWidget.Widget = TabEndingRow.ToSharedRef(); - Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniParameterDetails::CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam|| MainParam->IsPendingKill()) - return; - - if (!Row) - return; - - TSharedRef< SCustomizedBox > HorizontalBox = SNew(SCustomizedBox); - - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - - - if (MainParam->IsDirectChildOfMultiParm()) - { - FString ParameterLabelStr = MainParam->GetParameterLabel(); - - // If it is head of an multiparm instance - if (MainParam->GetChildIndex() == 0) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - } - ParameterLabelStr += TEXT(" (") + FString("") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - HorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - else - { - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(MainParam->GetParameterLabel()) : FText::GetEmpty(); - HorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - - Row->NameWidget.Widget = HorizontalBox; -} - -void -FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) -{ - if (!Row) - return; - - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - FString ParameterLabelStr = MainParam->GetParameterLabel(); - TSharedRef HorizontalBox = SNew(SCustomizedBox); - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - - TSharedPtr VerticalBox; - HorizontalBox->AddSlot() - [ - SAssignNew(VerticalBox, SVerticalBox) - ]; - - if (MainParam->IsDirectChildOfMultiParm()) - { - // If it is head of an multiparm instance - if (MainParam->GetChildIndex() == 0) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - } - - ParameterLabelStr += TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor* RampParameter = Cast(MainParam); - if (RampParameter) - { - if (RampParameter->bCaching) - ParameterLabelStr += "*"; - } - } - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - else - { - // TODO: Refactor me...extend 'auto/manual update' to all parameters? (It only applies to color and float ramps for now.) - bool bParamNeedUpdate = false; - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor* RampParameter = Cast(MainParam); - if (RampParameter) - bParamNeedUpdate = RampParameter->bCaching; - } - else if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat* RampParameter = Cast(MainParam); - if (RampParameter) - bParamNeedUpdate = RampParameter->bCaching; - } - - if (bParamNeedUpdate) - ParameterLabelStr += "*"; - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - - auto IsAutoUpdateChecked = [MainParam]() - { - if (!MainParam || MainParam->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainParam->IsAutoUpdate() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - auto OnAutoUpdateCheckBoxStateChanged = [MainParam, InParams, bCookingEnabled](ECheckBoxState NewState) - { - if (NewState == ECheckBoxState::Checked) - { - for (auto & NextSelectedParam : InParams) - { - if (!NextSelectedParam) - continue; - - if (NextSelectedParam->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not allow mode change when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) - continue; - - switch (MainParam->GetParameterType()) - { - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* ColorRampParameter = Cast(NextSelectedParam); - - if (!ColorRampParameter) - continue; - - // Do not sync the selected color ramp parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) - continue; - - // Sync the Cached curve points at update mode switch. - FHoudiniParameterDetails::SyncCachedColorRampPoints(ColorRampParameter); - } - break; - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParameter = Cast(NextSelectedParam); - - if (!FloatRampParameter) - continue; - - // Do not sync the selected float ramp parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) - continue; - - // Sync the Cached curve points at update mode switch. - //FHoudiniParameterDetails::SyncCachedFloatRampPoints(FloatRampParameter); - FloatRampParameter->SyncCachedPoints(); - } - break; - - default: - break; - } - - NextSelectedParam->SetAutoUpdate(true); - } - } - else - { - for (auto & NextSelectedParam : InParams) - { - if (!NextSelectedParam) - continue; - - if (!(NextSelectedParam->IsAutoUpdate() && bCookingEnabled)) - continue; - - // Do not allow mode change when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) - continue; - - NextSelectedParam->SetAutoUpdate(false); - } - } - }; - - // Auto update check box - TSharedPtr CheckBox; - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(SHorizontalBox) - - + SHorizontalBox::Slot() - [ - SAssignNew(CheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([OnAutoUpdateCheckBoxStateChanged](ECheckBoxState NewState) - { - OnAutoUpdateCheckBoxStateChanged(NewState); - }) - .IsChecked_Lambda([IsAutoUpdateChecked]() - { - return IsAutoUpdateChecked(); - }) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("AutoUpdate", "Auto-update")) - .ToolTipText(LOCTEXT("AutoUpdateTip", "When enabled, this parameter will automatically update its value while editing. Turning this off will allow you to more easily update it, and the update can be pushed by checking the toggle again.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - if ((MainParam->GetParameterType() != EHoudiniParameterType::FloatRamp) && (MainParam->GetParameterType() != EHoudiniParameterType::ColorRamp)) - CheckBox->SetVisibility(EVisibility::Hidden); - - Row->NameWidget.Widget = HorizontalBox; -} - -FDetailWidgetRow* -FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount) -{ - if (InParams.Num() <= 0) - return nullptr; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return nullptr; - - // Created row for the current parameter (if there is not a row created, do not show the parameter). - FDetailWidgetRow* Row = nullptr; - - // Current parameter is in a multiparm instance (directly) - if (MainParam->IsDirectChildOfMultiParm()) - { - int32 ParentMultiParmId = MainParam->GetParentParmId(); - - // If this is a folder param, its folder list parent parm is the multiparm - if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) - { - if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) // This should not happen - return nullptr; - - UHoudiniParameterFolderList* ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); - if (!ParentFolderList || ParentFolderList->IsPendingKill()) - return nullptr; // This should not happen - - ParentMultiParmId = ParentFolderList->GetParentParmId(); - } - - if (!AllMultiParms.Contains(ParentMultiParmId)) // This should not happen normally - return nullptr; - - // Get the parent multiparm - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentMultiParmId]; - - // The parent multiparm is visible. - if (ParentMultiParm && ParentMultiParm->IsShown() && MainParam->ShouldDisplay()) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - - } - // This item is not a direct child of a multiparm. - else - { - bool bIsFolder = MainParam->GetParameterType() == EHoudiniParameterType::Folder; - - // If this parameter is a folder, its parent folder should be the second top of the stack - int32 NestedMinStackDepth = bIsFolder ? 1 : 0; - - // Current parameter is inside a folder. - if (FolderStack.Num() > NestedMinStackDepth) - { - // If the current parameter is a folder, we take the top second queue on the stack, since the top one represents itself. - // Otherwise take the top queue on the stack. - TArray & CurrentLayerFolderQueue = bIsFolder ? - FolderStack[FolderStack.Num() - 2] : FolderStack.Last(); - - if (CurrentLayerFolderQueue.Num() <= 0) // Error state - return nullptr; - - bool bParentFolderVisible = CurrentLayerFolderQueue[0]->IsContentShown(); - - bool bIsSelectedTabVisible = false; - - // If its parent folder is visible, display current parameter, - // Otherwise, just prune the stacks. - if (bParentFolderVisible) - { - int32 ParentFolderId = MainParam->GetParentParmId(); - - // If the current parameter is a folder, its parent is a folderlist. - // So we need to continue to get the parent of the folderlist. - if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) - { - if (AllFoldersAndFolderLists.Contains(ParentFolderId)) - ParentFolderId = AllFoldersAndFolderLists[ParentFolderId]->GetParentParmId(); - else - return nullptr; // error state - } - - UHoudiniParameterFolder* ParentFolder = nullptr; - - if (AllFoldersAndFolderLists.Contains(ParentFolderId)) - ParentFolder = Cast(AllFoldersAndFolderLists[ParentFolderId]); - - bool bShouldDisplayRow = MainParam->ShouldDisplay(); - - // This row should be shown if its parent folder is shown. - if (ParentFolder) - bShouldDisplayRow &= (ParentFolder->IsTab() && ParentFolder->IsChosen()) || (!ParentFolder->IsTab() && ParentFolder->IsExpanded()); - - if (bShouldDisplayRow) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - } - - // prune the stack finally - if (bDecreaseChildCount) - { - CurrentLayerFolderQueue[0]->GetChildCounter() -= 1; - PruneStack(); - } - } - // If this parameter is in the root dir, just create a row. - else - { - if (MainParam->ShouldDisplay()) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - } - } - - if (!MainParam->IsVisible()) - return nullptr; - - - if (Row) - CurrentTabEndingRow = nullptr; - - return Row; -} - -void -FHoudiniParameterDetails::HandleUnsupportedParmType(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - CreateNestedRow(HouParameterCategory, (TArray)InParams); -} - -void -FHoudiniParameterDetails::CreateWidgetFloat( - IDetailCategoryBuilder & HouParameterCategory, - TArray& InParams ) -{ - TArray FloatParams; - if (!CastParameters(InParams, FloatParams)) - return; - - if (FloatParams.Num() <= 0) - return; - - UHoudiniParameterFloat* MainParam = FloatParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - // Helper function to find a unit from a string (name or abbreviation) - auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); - EUnit Unit = EUnit::Unspecified; - if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) - Unit = ParmUnit.GetValue(); - - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - // Lambdas for slider begin - auto SliderBegin = [&](TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), - FloatParams[0]->GetOuter()); - - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - FloatParams[Idx]->Modify(); - } - }; - - // Lambdas for slider end - auto SliderEnd = [&](TArray FloatParams) - { - // Mark the value as changed to trigger an update - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - FloatParams[Idx]->MarkChanged(true); - } - }; - - // Lambdas for changing the parameter value - auto ChangeFloatValueAt = [&](const float& Value, const int32& Index, const bool& DoChange, TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), - FloatParams[0]->GetOuter() ); - - bool bChanged = false; - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - FloatParams[Idx]->Modify(); - if (FloatParams[Idx]->SetValueAt(Value, Index)) - { - // Only mark the param has changed if DoChange is true!!! - if(DoChange) - FloatParams[Idx]->MarkChanged(true); - bChanged = true; - } - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if no parameter's value has actually been changed - Transaction.Cancel(); - } - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Revert to default value"), - FloatParams[0]->GetOuter()); - - if (TupleIndex < 0) - { - for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - if (FloatParams[Idx]->IsDefault()) - continue; - - FloatParams[Idx]->RevertToDefault(-1); - } - } - else - { - for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - if (FloatParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - FloatParams[Idx]->RevertToDefault(TupleIndex); - } - } - return FReply::Handled(); - }; - - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - //TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - if (MainParam->GetTupleSize() == 3) - { - // Should we swap Y and Z fields (only relevant for Vector3) - // Ignore the swapping if that parameter has the noswap tag - bool SwapVector3 = !MainParam->GetNoSwap(); - - auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float & Val) - { - ChangeFloatValueAt(Val, 0, true, FloatParams); - ChangeFloatValueAt(Val, 1, true, FloatParams); - ChangeFloatValueAt(Val, 2, true, FloatParams); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, 0))) - .Y(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 2 : 1))) - .Z(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 1 : 2))) - .OnXCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); - else - ChangeFloatValueAt( Val, 0, true, FloatParams); - }) - .OnYCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); - else - ChangeFloatValueAt( Val, SwapVector3 ? 2 : 1, true, FloatParams); - }) - .OnZCommitted_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); - else - ChangeFloatValueAt( Val, SwapVector3 ? 1 : 2, true, FloatParams); - }) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("FloatParameterLockButtonToolTip", "When locked, change the vector value uniformly.")) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(MainParam->IsUniformLocked() ? FEditorStyle::GetBrush("Genericlock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([FloatParams, MainParam]() - { - if (!MainParam || MainParam->IsPendingKill()) - return FReply::Handled(); - - for (auto & CurParam : FloatParams) - { - if (!CurParam || CurParam->IsPendingKill()) - continue; - - CurParam->SwitchUniformLock(); - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - - return FReply::Handled(); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([FloatParams]() - { - for (auto & SelectedParam : FloatParams) - { - if (!SelectedParam) - continue; - - if (!SelectedParam->IsDefault()) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([FloatParams, RevertToDefault]() { return RevertToDefault(-1, FloatParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - else - { - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - TSharedPtr> NumericEntryBox; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< float >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - - .MinValue(MainParam->GetMin()) - .MaxValue(MainParam->GetMax()) - - .MinSliderValue(MainParam->GetUIMin()) - .MaxSliderValue(MainParam->GetUIMax()) - - .Value(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, Idx))) - .OnValueChanged_Lambda([=](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) - .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) - .OnBeginSliderMovement_Lambda([=]() { SliderBegin(FloatParams); }) - .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(FloatParams); }) - .SliderExponent(MainParam->IsLogarithmic() ?8.0f : 1.0f) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .OnClicked_Lambda([Idx, FloatParams, RevertToDefault]() { return RevertToDefault(Idx, FloatParams); }) - .Visibility_Lambda([Idx, FloatParams]() - { - for (auto & SelectedParam :FloatParams) - { - if (!SelectedParam) - continue; - - if (!SelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - } - } - - Row->ValueWidget.Widget =VerticalBox; - - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetInt(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray IntParams; - if (!CastParameters(InParams, IntParams)) - - if (IntParams.Num() <= 0) - return; - - UHoudiniParameterInt* MainParam = IntParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - // Helper function to find a unit from a string (name or abbreviation) - auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); - EUnit Unit = EUnit::Unspecified; - if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) - Unit = ParmUnit.GetValue(); - - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - // Lambda for slider begin - auto SliderBegin = [&](TArray IntParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), - IntParams[0]->GetOuter()); - - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - IntParams[Idx]->Modify(); - } - }; - - // Lambda for slider end - auto SliderEnd = [&](TArray IntParams) - { - // Mark the value as changed to trigger an update - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - IntParams[Idx]->MarkChanged(true); - } - }; - - // Lambda for changing the parameter value - auto ChangeIntValueAt = [&](const int32& Value, const int32& Index, const bool& DoChange, TArray IntParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), - IntParams[0]->GetOuter()); - - bool bChanged = false; - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - if (!IntParams[Idx]) - continue; - - IntParams[Idx]->Modify(); - if (IntParams[Idx]->SetValueAt(Value, Index)) - { - // Only mark the param has changed if DoChange is true!!! - if (DoChange) - IntParams[Idx]->MarkChanged(true); - bChanged = true; - } - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if there is no param has actually been changed - Transaction.Cancel(); - } - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray IntParams) - { - for (int32 Idx = 0; Idx < IntParams.Num(); Idx++) - { - if (!IntParams[Idx]) - continue; - - if (IntParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - IntParams[Idx]->RevertToDefault(TupleIndex); - } - - return FReply::Handled(); - }; - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - - .MinValue(MainParam->GetMin()) - .MaxValue(MainParam->GetMax()) - - .MinSliderValue(MainParam->GetUIMin()) - .MaxSliderValue(MainParam->GetUIMax()) - - .Value( TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterInt::GetValue, Idx))) - .OnValueChanged_Lambda( [=](int32 Val) { ChangeIntValueAt(Val, Idx, false, IntParams); } ) - .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeIntValueAt(Val, Idx, true, IntParams); }) - .OnBeginSliderMovement_Lambda( [=]() { SliderBegin(IntParams); }) - .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(IntParams); }) - .SliderExponent(MainParam->IsLogarithmic() ? 8.0f : 1.0f) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, IntParams]() - { - for (auto & NextSelectedParam : IntParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, IntParams, RevertToDefault]() { return RevertToDefault(Idx, IntParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - /* - if (NumericEntryBox.IsValid()) - NumericEntryBox->SetEnabled(!MainParam->IsDisabled()); - */ - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray StringParams; - if (!CastParameters(InParams, StringParams)) - return; - - if (StringParams.Num() <= 0) - return; - - UHoudiniParameterString* MainParam = StringParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - bool bIsMultiLine = false; - bool bIsUnrealRef = false; - UClass* UnrealRefClass = UObject::StaticClass(); - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - TMap& Tags = MainParam->GetTags(); - if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_TAG) && FCString::Atoi(*Tags[HOUDINI_PARAMETER_STRING_REF_TAG]) == 1) - { - bIsUnrealRef = true; - - if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_CLASS_TAG)) - { - UClass * FoundClass = FindObject(ANY_PACKAGE, *Tags[HOUDINI_PARAMETER_STRING_REF_CLASS_TAG]); - if (FoundClass != nullptr) - { - UnrealRefClass = FoundClass; - } - } - } - - if (Tags.Contains(HOUDINI_PARAMETER_STRING_MULTILINE_TAG)) - { - bIsMultiLine = true; - } - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - // Lambda for changing the parameter value - auto ChangeStringValueAt = [&](const FString& Value, UObject* ChosenObj, const int32& Index, const bool& DoChange, TArray StringParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterSrtingChange", "Houdini Parameter String: Changing a value"), - StringParams[0]->GetOuter()); - - bool bChanged = false; - for (int Idx = 0; Idx < StringParams.Num(); Idx++) - { - if (!StringParams[Idx]) - continue; - - StringParams[Idx]->Modify(); - if (StringParams[Idx]->SetValueAt(Value, Index)) - { - StringParams[Idx]->MarkChanged(true); - bChanged = true; - } - - StringParams[Idx]->SetAssetAt(ChosenObj, Index); - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if there is no param actually has been changed - Transaction.Cancel(); - } - - FHoudiniEngineUtils::UpdateEditorProperties(StringParams[0], false); - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray StringParams) - { - for (int32 Idx = 0; Idx < StringParams.Num(); Idx++) - { - if (!StringParams[Idx]) - continue; - - if (StringParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - StringParams[Idx]->RevertToDefault(TupleIndex); - } - - return FReply::Handled(); - }; - - if (bIsUnrealRef) - { - TSharedPtr< SEditableTextBox > EditableTextBox; - TSharedPtr< SHorizontalBox > HorizontalBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([UnrealRefClass](const UObject* InObject) - { - return InObject->IsA(UnrealRefClass); - }) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - ChangeStringValueAt(ReferenceStr, InObject, Idx, true, StringParams); - }) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - // Thumbnail - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouParameterCategory.GetParentLayout().GetThumbnailPool(); - - // Create a thumbnail for the selected object / class - UObject* EditObject = nullptr; - const FString AssetPath = MainParam->GetValueAt(Idx); - EditObject = StaticFindObject(nullptr, nullptr, *AssetPath, true); - - FAssetData AssetData; - if (IsValid(EditObject)) - { - AssetData = FAssetData(EditObject); - } - else - { - AssetData.AssetClass = UnrealRefClass->GetFName(); - } - - TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = MakeShareable( - new FAssetThumbnail(AssetData, 64, 64, AssetThumbnailPool)); - - TSharedPtr ThumbnailBorder; - HorizontalBox->AddSlot().Padding(0.f, 0.f, 2.f, 0.f).AutoWidth() - [ - SAssignNew(ThumbnailBorder, SBorder) - .OnMouseDoubleClick_Lambda([EditObject, Idx](const FGeometry&, const FPointerEvent&) - { - if (EditObject && GEditor) - GEditor->EditObject(EditObject); - - return FReply::Handled(); - }) - .Padding(5.f) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - ThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush * >::Create( - TAttribute< const FSlateBrush * >::FGetter::CreateLambda([ThumbnailBorder]() - { - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ))); - - FText MeshNameText = FText::GetEmpty(); - //if (InputObject) - // MeshNameText = FText::FromString(InputObject->GetName()); - - TSharedPtr StaticMeshComboButton; - - TSharedPtr ButtonBox; - HorizontalBox->AddSlot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .HAlign(HAlign_Fill) - [ - SAssignNew(ButtonBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SAssignNew(StaticMeshComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromName(AssetData.AssetName)) - .ToolTipText(FText::FromString(MainParam->GetValueAt(Idx))) - ] - ] - ] - ]; - - StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda([UnrealRefClass, StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() - { - TArray AllowedClasses; - AllowedClasses.Add(UnrealRefClass); - TArray NewAssetFactories; - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(nullptr), - true, - AllowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) - { - UObject * Object = AssetData.GetAsset(); - - // Get the asset reference string for this object - // !! Accept null objects to allow clearing the asset picker !! - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); - - StaticMeshComboButton->SetIsOpen(false); - ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); - }), - FSimpleDelegate::CreateLambda([]() {})); - }) - ); - } - else if (bIsMultiLine) - { - TSharedPtr< SMultiLineEditableTextBox > MultiLineEditableTextBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) - {return true;}) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - FString NewString = ReferenceStr; - if (StringParams[0]->GetValueAt(Idx).Len() > 0) - NewString = StringParams[0]->GetValueAt(Idx) + "\n" + NewString; - - ChangeStringValueAt(NewString, nullptr, Idx, true, StringParams); - }) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Top).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(MultiLineEditableTextBox, SMultiLineEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainParam->GetValueAt(Idx))) - .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, StringParams]() - { - for (auto & NextSelectedParam : StringParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() { return RevertToDefault(Idx, StringParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - else - { - TSharedPtr< SEditableTextBox > EditableTextBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) - {return true;}) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - ChangeStringValueAt(ReferenceStr, nullptr, Idx, true, StringParams); - }) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(EditableTextBox, SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainParam->GetValueAt(Idx))) - .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) - { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, StringParams]() - { - for (auto & NextSelectedParam : StringParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() - { return RevertToDefault(Idx, StringParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ColorParams; - if (!CastParameters(InParams, ColorParams)) - return; - - if (ColorParams.Num() <= 0) - return; - - UHoudiniParameterColor* MainParam = ColorParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - bool bHasAlpha = (MainParam->GetTupleSize() == 4); - - // Add color picker UI. - TSharedPtr ColorBlock; - TSharedRef VerticalBox = SNew(SVerticalBox); - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(ColorBlock, SColorBlock) - .Color(MainParam->GetColorValue()) - .ShowBackgroundForAlpha(bHasAlpha) - .OnMouseButtonDown(FPointerEventHandler::CreateLambda( - [MainParam, ColorParams, ColorBlock, bHasAlpha](const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) - { - if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) - return FReply::Unhandled(); - - FColorPickerArgs PickerArgs; - PickerArgs.ParentWidget = ColorBlock; - PickerArgs.bUseAlpha = bHasAlpha; - PickerArgs.DisplayGamma = TAttribute< float >::Create( - TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), - MainParam->GetOuter(), true); - - bool bChanged = false; - for (auto & Param : ColorParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetColorValue(InColor)) - { - Param->MarkChanged(true); - bChanged = true; - } - } - - // cancel the transaction if there is actually no value changed - if (!bChanged) - { - Transaction.Cancel(); - } - - }); - PickerArgs.InitialColorOverride = MainParam->GetColorValue(); - PickerArgs.bOnlyRefreshOnOk = true; - OpenColorPicker(PickerArgs); - return FReply::Handled(); - })) - ]; - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetButton(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ButtonParams; - if (!CastParameters(InParams, ButtonParams)) - return; - - if (ButtonParams.Num() <= 0) - return; - - UHoudiniParameterButton* MainParam = ButtonParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - FText ParameterTooltip = GetParameterTooltip(MainParam); - - TSharedRef HorizontalBox = SNew(SHorizontalBox); - TSharedPtr Button; - - // Add button UI. - HorizontalBox->AddSlot().Padding(1, 2, 4, 2) - [ - SAssignNew(Button, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ParameterLabelText) - .ToolTipText(ParameterTooltip) - .OnClicked(FOnClicked::CreateLambda( [MainParam, ButtonParams]() - { - for (auto & Param : ButtonParams) - { - if (!Param) - continue; - - // There is no undo redo operation for button - Param->MarkChanged(true); - } - - return FReply::Handled(); - })) - ]; - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ButtonStripParams; - if (!CastParameters(InParams, ButtonStripParams)) - return; - - if (ButtonStripParams.Num() <= 0) - return; - - UHoudiniParameterButtonStrip* MainParam = ButtonStripParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - if (!Row) - return; - - auto OnButtonStateChanged = [MainParam, ButtonStripParams](ECheckBoxState NewState, int32 Idx) - { - - /* - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterButtonStripChange", "Houdini Parameter Button Strip: Changing value"), - MainParam->GetOuter(), true); - */ - int32 StateInt = NewState == ECheckBoxState::Checked ? 1 : 0; - bool bChanged = false; - - for (auto & NextParam : ButtonStripParams) - { - if (!NextParam || NextParam->IsPendingKill()) - continue; - - if (!NextParam->Values.IsValidIndex(Idx)) - continue; - - //NextParam->Modify(); - if (NextParam->SetValueAt(Idx, StateInt)) - { - NextParam->MarkChanged(true); - bChanged = true; - } - } - - //if (!bChanged) - // Transaction.Cancel(); - - }; - - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - FText ParameterTooltip = GetParameterTooltip(MainParam); - - TSharedRef HorizontalBox = SNew(SHorizontalBox); - FLinearColor BgColor(0.53f, 0.81f, 0.82f, 1.0f); // Sky Blue Backgroud color - - for (int32 Idx = 0; Idx < MainParam->Count; ++Idx) - { - if (!MainParam->Values.IsValidIndex(Idx) || !MainParam->Labels.IsValidIndex(Idx)) - continue; - - bool bPressed = MainParam->Values[Idx] > 0; - FText LabelText = FText::FromString(MainParam->Labels[Idx]); - - TSharedPtr Button; - - HorizontalBox->AddSlot().Padding(0).FillWidth(1.0f) - [ - SAssignNew(Button, SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") - .IsChecked(bPressed ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - .OnCheckStateChanged_Lambda([OnButtonStateChanged, Idx](ECheckBoxState NewState) - { - OnButtonStateChanged(NewState, Idx); - }) - .Content() - [ - SNew(STextBlock) - .Text(LabelText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - Button->SetColorAndOpacity(BgColor); - } - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetLabel(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray LabelParams; - if (!CastParameters(InParams, LabelParams)) - return; - - if (LabelParams.Num() <= 0) - return; - - UHoudiniParameterLabel* MainParam = LabelParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - - for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) - { - FString NextLabelString = MainParam->GetStringAtIndex(Index); - FText ParameterLabelText = FText::FromString(NextLabelString); - - TSharedPtr TextBlock; - - // Add Label UI. - VerticalBox->AddSlot().Padding(1, 2, 4, 2) - [ - SAssignNew(TextBlock, STextBlock).Text(ParameterLabelText) - ]; - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetToggle(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray ToggleParams; - if (!CastParameters(InParams, ToggleParams)) - return; - - if (ToggleParams.Num() <= 0) - return; - - UHoudiniParameterToggle* MainParam = ToggleParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - - TSharedRef VerticalBox = SNew(SVerticalBox); - auto IsToggleCheckedLambda = [MainParam](int32 Index) - { - if (Index >= MainParam->GetNumValues()) - return ECheckBoxState::Unchecked; - - if (MainParam->GetValueAt(Index)) - return ECheckBoxState::Checked; - - return ECheckBoxState::Unchecked; - }; - - auto OnToggleCheckStateChanged = [MainParam, ToggleParams](ECheckBoxState NewState, int32 Index) - { - if (Index >= MainParam->GetNumValues()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterToggleChange", "Houdini Parameter Toggle: Changing value"), - MainParam->GetOuter(), true); - - bool bState = (NewState == ECheckBoxState::Checked); - - bool bChanged = false; - for (auto & Param : ToggleParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetValueAt(bState, Index)) - { - bChanged = true; - Param->MarkChanged(true); - } - } - - // Cancel the transaction if no parameter has actually been changed - if (!bChanged) - { - Transaction.Cancel(); - } - }; - - for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) - { - TSharedPtr< SCheckBox > CheckBox; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - [ - SAssignNew(CheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([OnToggleCheckStateChanged, Index](ECheckBoxState NewState) { - OnToggleCheckStateChanged(NewState, Index); - - }) - .IsChecked_Lambda([IsToggleCheckedLambda, Index]() { - return IsToggleCheckedLambda(Index); - }) - .Content() - [ - SNew(STextBlock) - .Text(ParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FileParams; - if (!CastParameters(InParams, FileParams)) - return; - - if (FileParams.Num() <= 0) - return; - - UHoudiniParameterFile* MainParam = FileParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - - FString FileTypeWidgetFilter = TEXT("All files (*.*)|*.*"); - if (!MainParam->GetFileFilters().IsEmpty()) - FileTypeWidgetFilter = FString::Printf(TEXT("%s files (%s)|%s"), *MainParam->GetFileFilters(), *MainParam->GetFileFilters(), *MainParam->GetFileFilters()); - - FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); - - auto UpdateCheckRelativePath = [MainParam](const FString & PickedPath) - { - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(MainParam->GetOuter()); - if (MainParam->GetOuter() && !PickedPath.IsEmpty() && FPaths::IsRelative(PickedPath)) - { - // Check if the path is relative to the UE4 project - FString AbsolutePath = FPaths::ConvertRelativePathToFull(PickedPath); - if (FPaths::FileExists(AbsolutePath)) - { - return AbsolutePath; - } - - // Check if the path is relative to the asset - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) - { - if (HoudiniAssetComponent->HoudiniAsset && !HoudiniAssetComponent->HoudiniAsset->IsPendingKill()) - { - FString AssetFilePath = FPaths::GetPath(HoudiniAssetComponent->HoudiniAsset->AssetFileName); - if (FPaths::FileExists(AssetFilePath)) - { - FString UpdatedFileWidgetPath = FPaths::Combine(*AssetFilePath, *PickedPath); - if (FPaths::FileExists(UpdatedFileWidgetPath)) - { - return UpdatedFileWidgetPath; - } - } - } - } - } - - return PickedPath; - }; - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - FString FileWidgetPath = MainParam->GetValueAt(Idx); - FString FileWidgetBrowsePath = BrowseWidgetDirectory; - - if (!FileWidgetPath.IsEmpty()) - { - FString FileWidgetDirPath = FPaths::GetPath(FileWidgetPath); - if (!FileWidgetDirPath.IsEmpty()) - FileWidgetBrowsePath = FileWidgetDirPath; - } - - bool IsDirectoryPicker = MainParam->GetParameterType() == EHoudiniParameterType::FileDir; - bool bIsNewFile = !MainParam->IsReadOnly(); - - FText BrowseTooltip = LOCTEXT("FileButtonToolTipText", "Choose a file from this computer"); - if (IsDirectoryPicker) - BrowseTooltip = LOCTEXT("DirButtonToolTipText", "Choose a directory from this computer"); - - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SNewFilePathPicker) - .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) - .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") - .BrowseButtonToolTip(BrowseTooltip) - .BrowseDirectory(FileWidgetBrowsePath) - .BrowseTitle(LOCTEXT("PropertyEditorTitle", "File picker...")) - .FilePath(FileWidgetPath) - .FileTypeFilter(FileTypeWidgetFilter) - .IsNewFile(bIsNewFile) - .IsDirectoryPicker(IsDirectoryPicker) - .ToolTipText_Lambda([MainParam]() - { - // return the current param value as a tooltip - FString FileValue = MainParam ? MainParam->GetValueAt(0) : FString(); - return FText::FromString(FileValue); - }) - .OnPathPicked(FOnPathPicked::CreateLambda([MainParam, FileParams, UpdateCheckRelativePath, Idx](const FString & PickedPath) - { - if (MainParam->GetNumValues() <= Idx) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFileChange", "Houdini Parameter File: Changing a file path"), - MainParam->GetOuter(), true); - - bool bChanged = false; - - for (auto & Param : FileParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetValueAt(UpdateCheckRelativePath(PickedPath), Idx)) - { - bChanged = true; - Param->MarkChanged(true); - } - } - - // Cancel the transaction if no value has actually been changed - if (!bChanged) - { - Transaction.Cancel(); - } - })) - ] - ]; - - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - - -void -FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ChoiceParams; - if (!CastParameters(InParams, ChoiceParams)) - return; - - if (ChoiceParams.Num() <= 0) - return; - - UHoudiniParameterChoice* MainParam = ChoiceParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - // Lambda for changing the parameter value - auto ChangeSelectionLambda = [ChoiceParams](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterChoiceChange", "Houdini Parameter Choice: Changing selection"), - ChoiceParams[0]->GetOuter()); - - const int32 NewIntValue = ChoiceParams[0]->GetIntValueFromLabel(*NewChoice); - - bool bChanged = false; - for (int Idx = 0; Idx < ChoiceParams.Num(); Idx++) - { - if (!ChoiceParams[Idx]) - continue; - - ChoiceParams[Idx]->Modify(); - if (ChoiceParams[Idx]->SetIntValue(NewIntValue)) - { - bChanged = true; - ChoiceParams[Idx]->MarkChanged(true); - ChoiceParams[Idx]->UpdateStringValueFromInt(); - } - } - - if (!bChanged) - { - // Cancel the transaction if no parameter was changed - Transaction.Cancel(); - } - }; - - // - MainParam->UpdateChoiceLabelsPtr(); - TArray>* OptionSource = MainParam->GetChoiceLabelsPtr(); - TSharedPtr IntialSelec; - if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValue())) - { - IntialSelec = (*OptionSource)[MainParam->GetIntValue()]; - } - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBox; - HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) - [ - SAssignNew( ComboBox, SComboBox< TSharedPtr< FString > > ) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - []( TSharedPtr< FString > InItem ) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [ChangeSelectionLambda](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - ChangeSelectionLambda(NewChoice, SelectType); - }) - [ - SNew(STextBlock) - .Text_Lambda([MainParam]() { return FText::FromString(MainParam->GetLabel()); }) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - ]; - - if ( ComboBox.IsValid() ) - ComboBox->SetEnabled( !MainParam->IsDisabled() ); - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetSeparator(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - TSharedRef HorizontalBox = SNew(SCustomizedBox); - - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - - Row->WholeRowWidget.Widget = HorizontalBox; -} - -void -FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray OperatorPathParams; - if (!CastParameters(InParams, OperatorPathParams)) - return; - - if (OperatorPathParams.Num() <= 0) - return; - - UHoudiniParameterOperatorPath* MainParam = OperatorPathParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - UHoudiniInput* MainInput = MainParam->HoudiniInput.Get(); - if (!MainInput) - return; - - // Build an array of edited inputs for multi edition - TArray EditedInputs; - EditedInputs.Add(MainInput); - - // Add the corresponding inputs found in the other HAC - for (int LinkedIdx = 1; LinkedIdx < OperatorPathParams.Num(); LinkedIdx++) - { - UHoudiniInput* LinkedInput = OperatorPathParams[LinkedIdx]->HoudiniInput.Get(); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if (!LinkedInput->Matches(*MainInput)) - continue; - - EditedInputs.Add(LinkedInput); - } - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FHoudiniInputDetails::CreateWidget(HouParameterCategory, EditedInputs, Row); - - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetFloatRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Parsing a float ramp: 1->(2->3->4)*->5 // - switch (MainParam->GetParameterType()) - { - //*****State 1: Float Ramp*****// - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); - if (FloatRampParameter) - { - CurrentRampFloat = FloatRampParameter; - CurrentRampFloatPointsArray.Empty(); - - CurrentRampParameterList = InParams; - - FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - CurrentRampRow = Row; - } - break; - } - - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParameter = Cast(MainParam); - if (FloatParameter) - { - bool bCreateNewPoint = true; - if (CurrentRampFloatPointsArray.Num() > 0) - { - UHoudiniParameterRampFloatPoint* LastPtInArr = CurrentRampFloatPointsArray.Last(); - if (LastPtInArr && !LastPtInArr->ValueParentParm) - bCreateNewPoint = false; - } - - //*****State 2: Float Parameter (position)*****// - if (bCreateNewPoint) - { - UHoudiniParameterRampFloatPoint* NewRampFloatPoint = nullptr; - - int32 PointIndex = CurrentRampFloatPointsArray.Num(); - if (CurrentRampFloat->Points.IsValidIndex(PointIndex)) - { - - // TODO: We should reuse existing point objects, if they exist. Currently - // this causes results in unexpected behaviour in other parts of this detail code. - // Give this code a bit of an overhaul at some point. - // NewRampFloatPoint = CurrentRampFloat->Points[PointIndex]; - } - - if (!NewRampFloatPoint) - { - // Create a new float ramp point, and add its pointer to the current float points buffer array. - NewRampFloatPoint = NewObject< UHoudiniParameterRampFloatPoint >(CurrentRampFloat, FName(), CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); - - } - - CurrentRampFloatPointsArray.Add(NewRampFloatPoint); - - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Set the float ramp point's position parent parm, and value - NewRampFloatPoint->PositionParentParm = FloatParameter; - NewRampFloatPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); - } - //*****State 3: Float Parameter (value)*****// - else - { - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Get the last point in the buffer array - if (CurrentRampFloatPointsArray.Num() > 0) - { - // Set the last inserted float ramp point's float parent parm, and value - UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); - LastAddedFloatRampPoint->ValueParentParm = FloatParameter; - LastAddedFloatRampPoint->SetValue(FloatParameter->GetValuesPtr()[0]); - } - } - } - - break; - } - //*****State 4: Choice parameter*****// - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); - if (ChoiceParameter && CurrentRampFloatPointsArray.Num() > 0) - { - // Set the last inserted float ramp point's interpolation parent parm, and value - UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); - - LastAddedFloatRampPoint->InterpolationParentParm = ChoiceParameter; - LastAddedFloatRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); - - // Set the index of this point in the multi parm. - LastAddedFloatRampPoint->InstanceIndex = CurrentRampFloatPointsArray.Num() - 1; - } - - - //*****State 5: All ramp points have been parsed, finish!*****// - if (CurrentRampFloatPointsArray.Num() >= (int32)CurrentRampFloat->MultiParmInstanceCount) - { - CurrentRampFloatPointsArray.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { - return P1.Position < P2.Position; - }); - - CurrentRampFloat->Points = CurrentRampFloatPointsArray; - - // Not caching, points are synced, update cached points - if (!CurrentRampFloat->bCaching) - { - const int32 NumPoints = CurrentRampFloat->Points.Num(); - CurrentRampFloat->CachedPoints.SetNum(NumPoints); - for (int32 i = 0; i < NumPoints; ++i) - { - UHoudiniParameterRampFloatPoint* FromPoint = CurrentRampFloat->Points[i]; - UHoudiniParameterRampFloatPoint* ToPoint = CurrentRampFloat->CachedPoints[i]; - ToPoint = nullptr; - check(FromPoint) - if (!ToPoint) - { - ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampFloat, RF_NoFlags, CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); - } - else - { - ToPoint->CopyStateFrom(FromPoint, true); - } - CurrentRampFloat->CachedPoints[i] = ToPoint; - } - } - - CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampFloat, CurrentRampParameterList); - - CurrentRampFloat->SetDefaultValues(); - - CurrentRampFloat = nullptr; - CurrentRampRow = nullptr; - } - - break; - } - - default: - break; - } - -} - -void -FHoudiniParameterDetails::CreateWidgetColorRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Parsing a color ramp: 1->(2->3->4)*->5 // - switch (MainParam->GetParameterType()) - { - //*****State 1: Color Ramp*****// - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* RampColor = Cast(MainParam); - if (RampColor) - { - CurrentRampColor = RampColor; - CurrentRampColorPointsArray.Empty(); - - CurrentRampParameterList = InParams; - - FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - CurrentRampRow = Row; - } - - break; - } - //*****State 2: Float parameter*****// - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParameter = Cast(MainParam); - if (FloatParameter) - { - // Create a new color ramp point, and add its pointer to the current color points buffer array. - UHoudiniParameterRampColorPoint* NewRampColorPoint = nullptr; - int32 PointIndex = CurrentRampColorPointsArray.Num(); - - if (CurrentRampColor->Points.IsValidIndex(PointIndex)) - { - NewRampColorPoint = CurrentRampColor->Points[PointIndex]; - } - - if (!NewRampColorPoint) - { - NewRampColorPoint = NewObject< UHoudiniParameterRampColorPoint >(CurrentRampColor, FName(), CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); - } - - CurrentRampColorPointsArray.Add(NewRampColorPoint); - - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Set the color ramp point's position parent parm, and value - NewRampColorPoint->PositionParentParm = FloatParameter; - NewRampColorPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); - } - - break; - } - //*****State 3: Color parameter*****// - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* ColorParameter = Cast(MainParam); - if (ColorParameter && CurrentRampColorPointsArray.Num() > 0) - { - // Set the last inserted color ramp point's color parent parm, and value - UHoudiniParameterRampColorPoint* LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); - LastAddedColorRampPoint->ValueParentParm = ColorParameter; - LastAddedColorRampPoint->SetValue(ColorParameter->GetColorValue()); - } - - break; - } - //*****State 4: Choice Parameter*****// - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); - if (ChoiceParameter) - { - // Set the last inserted color ramp point's interpolation parent parm, and value - UHoudiniParameterRampColorPoint*& LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); - - LastAddedColorRampPoint->InterpolationParentParm = ChoiceParameter; - LastAddedColorRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); - - // Set the index of this point in the multi parm. - LastAddedColorRampPoint->InstanceIndex = CurrentRampColorPointsArray.Num() - 1; - } - - - //*****State 5: All ramp points have been parsed, finish!*****// - if (CurrentRampColorPointsArray.Num() >= (int32)CurrentRampColor->MultiParmInstanceCount) - { - CurrentRampColorPointsArray.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) - { - return P1.Position < P2.Position; - }); - - CurrentRampColor->Points = CurrentRampColorPointsArray; - - // Not caching, points are synced, update cached points - - if (!CurrentRampColor->bCaching) - { - const int32 NumPoints = CurrentRampColor->Points.Num(); - CurrentRampColor->CachedPoints.SetNum(NumPoints); - - for (int32 i = 0; i < NumPoints; ++i) - { - UHoudiniParameterRampColorPoint* FromPoint = CurrentRampColor->Points[i]; - UHoudiniParameterRampColorPoint* ToPoint = CurrentRampColor->CachedPoints[i]; - - if (!ToPoint) - { - ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampColor, RF_NoFlags, CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); - } - else - { - ToPoint->CopyStateFrom(FromPoint, true); - } - CurrentRampColor->CachedPoints[i] = ToPoint; - } - } - - - CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampColor, CurrentRampParameterList); - - CurrentRampColor->SetDefaultValues(); - - CurrentRampColor = nullptr; - CurrentRampRow = nullptr; - } - - break; - } - - default: - break; - } - -} - - -FDetailWidgetRow* -FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() <= 0) - return nullptr; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam) - return nullptr; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, (TArray)InParams); - if (!Row) - return nullptr; - - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - - // Create the standard parameter name widget with an added autoupdate checkbox. - CreateNameWidgetWithAutoUpdate(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor *RampColorParam = Cast(MainParam); - if (!RampColorParam) - return nullptr; - - TSharedPtr ColorGradientEditor; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - SAssignNew(ColorGradientEditor, SHoudiniColorRampCurveEditor) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - ] - ]; - - if (!ColorGradientEditor.IsValid()) - return nullptr; - - // Avoid showing tooltips inside of the curve editor - ColorGradientEditor->EnableToolTipForceField(true); - - CurrentRampParameterColorCurve = NewObject( - MainParam, UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); - - if (!CurrentRampParameterColorCurve) - return nullptr; - - CreatedColorRampCurves.Add(CurrentRampParameterColorCurve); - - // Add the ramp curve to root to avoid garabage collected. - CurrentRampParameterColorCurve->AddToRoot(); - - TArray ColorRampParameters; - CastParameters(InParams, ColorRampParameters); - - for (auto NextColorRamp : ColorRampParameters) - { - CurrentRampParameterColorCurve->ColorRampParameters.Add(NextColorRamp); - } - ColorGradientEditor->HoudiniColorRampCurve = CurrentRampParameterColorCurve; - - // Clear default curve points - for (int Idx = 0; Idx < 4; ++Idx) - { - FRichCurve& RichCurve = (CurrentRampParameterColorCurve->FloatCurves)[Idx]; - if (RichCurve.GetNumKeys() > 0) - RichCurve.Keys.Empty(); - } - ColorGradientEditor->SetCurveOwner(CurrentRampParameterColorCurve); - } - else if(MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat *RampFloatParam = Cast(MainParam); - if (!RampFloatParam) - return nullptr; - - TSharedPtr FloatCurveEditor; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - SAssignNew(FloatCurveEditor, SHoudiniFloatRampCurveEditor) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - .HideUI(true) - .DrawCurve(true) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - .ViewMinOutput(0.0f) - .ViewMaxOutput(1.0f) - .TimelineLength(1.0f) - .AllowZoomOutput(false) - .ShowInputGridNumbers(false) - .ShowOutputGridNumbers(false) - .ShowZoomButtons(false) - .ZoomToFitHorizontal(false) - .ZoomToFitVertical(false) - .XAxisName(FString("X")) - .YAxisName(FString("Y")) - .ShowCurveSelector(false) - - ] - ]; - - if (!FloatCurveEditor.IsValid()) - return nullptr; - - // Avoid showing tooltips inside of the curve editor - FloatCurveEditor->EnableToolTipForceField(true); - - CurrentRampParameterFloatCurve = NewObject( - MainParam, UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); - - if (!CurrentRampParameterFloatCurve) - return nullptr; - - CreatedFloatRampCurves.Add(CurrentRampParameterFloatCurve); - - // Add the ramp curve to root to avoid garbage collected - CurrentRampParameterFloatCurve->AddToRoot(); - - TArray FloatRampParameters; - CastParameters(InParams, FloatRampParameters); - for (auto NextFloatRamp : FloatRampParameters) - { - CurrentRampParameterFloatCurve->FloatRampParameters.Add(NextFloatRamp); - } - FloatCurveEditor->HoudiniFloatRampCurve = CurrentRampParameterFloatCurve; - - FloatCurveEditor->SetCurveOwner(CurrentRampParameterFloatCurve, true); - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - return Row; -} - - -void -FHoudiniParameterDetails::CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray& InParams) -{ - if (!Row || !InParameter) - return; - - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - UHoudiniParameterRampFloat * MainFloatRampParameter = nullptr; - UHoudiniParameterRampColor * MainColorRampParameter = nullptr; - - TArray FloatRampParameterList; - TArray ColorRampParameterList; - if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - MainFloatRampParameter = Cast(MainParam); - - if (!MainFloatRampParameter) - return; - - if (!CastParameters(InParams, FloatRampParameterList)) - return; - } - else if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - MainColorRampParameter = Cast(MainParam); - - if (!MainColorRampParameter) - return; - - if (!CastParameters(InParams, ColorRampParameterList)) - return; - } - else - { - return; - } - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Lambda for computing the float point to be inserted - auto GetInsertFloatPointLambda = [MainFloatRampParameter]( - const int32& InsertAtIndex, - float& OutPosition, - float& OutValue, - EHoudiniRampInterpolationType& OutInterpType) mutable - { - if (!MainFloatRampParameter) - return; - - float PrevPosition = 0.0f; - float NextPosition = 1.0f; - - TArray &CurrentPoints = MainFloatRampParameter->Points; - TArray &CachedPoints = MainFloatRampParameter->CachedPoints; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - int32 NumPoints = 0; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - { - NumPoints = CurrentPoints.Num(); - } - else - { - MainFloatRampParameter->SetCaching(true); - NumPoints = CachedPoints.Num(); - } - - if (InsertAtIndex >= NumPoints) - { - // Insert at the end - if (NumPoints > 0) - { - UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - PrevPoint = CurrentPoints.Last(); - else - PrevPoint = CachedPoints.Last(); - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - } - } - else if (InsertAtIndex <= 0) - { - // Insert at the beginning - if (NumPoints > 0) - { - UHoudiniParameterRampFloatPoint* NextPoint = nullptr; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - NextPoint = CurrentPoints[0]; - else - NextPoint = CachedPoints[0]; - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - OutInterpType = NextPoint->GetInterpolation(); - } - } - } - else - { - // Insert in the middle - if (NumPoints > 1) - { - UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; - UHoudiniParameterRampFloatPoint* NextPoint = nullptr; - - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - { - PrevPoint = CurrentPoints[InsertAtIndex - 1]; - NextPoint = CurrentPoints[InsertAtIndex]; - } - else - { - PrevPoint = CachedPoints[InsertAtIndex - 1]; - NextPoint = CachedPoints[InsertAtIndex]; - } - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - } - - if (PrevPoint && NextPoint) - { - OutValue = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; - } - } - } - - OutPosition = (PrevPosition + NextPosition) / 2.0f; - }; - - - // Lambda for computing the color point to be inserted - auto GetInsertColorPointLambda = [MainColorRampParameter]( - const int32& InsertAtIndex, - float& OutPosition, - FLinearColor& OutColor, - EHoudiniRampInterpolationType& OutInterpType) mutable - { - if (!MainColorRampParameter) - return; - - float PrevPosition = 0.0f; - float NextPosition = 1.0f; - - TArray &CurrentPoints = MainColorRampParameter->Points; - TArray &CachedPoints = MainColorRampParameter->CachedPoints; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - int32 NumPoints = 0; - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NumPoints = CurrentPoints.Num(); - else - NumPoints = CachedPoints.Num(); - - if (InsertAtIndex >= NumPoints) - { - // Insert at the end - if (NumPoints > 0) - { - UHoudiniParameterRampColorPoint* PrevPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - PrevPoint = CurrentPoints.Last(); - else - PrevPoint = CachedPoints.Last(); - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - } - } - else if (InsertAtIndex <= 0) - { - // Insert at the beginning - if (NumPoints > 0) - { - UHoudiniParameterRampColorPoint* NextPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NextPoint = CurrentPoints[0]; - else - NextPoint = CachedPoints[0]; - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - OutInterpType = NextPoint->GetInterpolation(); - } - } - } - else - { - // Insert in the middle - if (NumPoints > 1) - { - UHoudiniParameterRampColorPoint* PrevPoint = nullptr; - UHoudiniParameterRampColorPoint* NextPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - { - PrevPoint = CurrentPoints[InsertAtIndex - 1]; - NextPoint = CurrentPoints[InsertAtIndex]; - } - else - { - PrevPoint = CachedPoints[InsertAtIndex - 1]; - NextPoint = CachedPoints[InsertAtIndex]; - } - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - } - - if (PrevPoint && NextPoint) - { - OutColor = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; - } - } - } - - OutPosition = (PrevPosition + NextPosition) / 2.0f; - }; - - int32 RowIndex = 0; - auto InsertRampPoint_Lambda = [GetInsertColorPointLambda, GetInsertFloatPointLambda, &CategoryBuilder, bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, - UHoudiniParameterRampColor* MainRampColor, - TArray &RampFloatList, - TArray &RampColorList, - const int32& Index) mutable - { - if (MainRampFloat) - { - float InsertPosition = 0.0f; - float InsertValue = 1.0f; - EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; - - GetInsertFloatPointLambda(Index, InsertPosition, InsertValue, InsertInterp); - - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); - - for (auto & NextFloatRamp : RampFloatList) - { - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - CreateFloatRampParameterInsertEvent( - NextFloatRamp, InsertPosition, InsertValue, InsertInterp); - - NextFloatRamp->MarkChanged(true); - } - else - { - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject - (NextFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); - NewCachedPoint->Position = InsertPosition; - NewCachedPoint->Value = InsertValue; - NewCachedPoint->Interpolation = InsertInterp; - - NextFloatRamp->CachedPoints.Add(NewCachedPoint); - NextFloatRamp->bCaching = true; - if (!bCookingEnabled) - { - // If cooking is not enabled, be sure to mark this parameter as changed - // so that it triggers an update once cooking is enabled again. - NextFloatRamp->MarkChanged(true); - } - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - { - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - } - else if (MainRampColor) - { - float InsertPosition = 0.0f; - FLinearColor InsertColor = FLinearColor::Black; - EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; - - GetInsertColorPointLambda(Index, InsertPosition, InsertColor, InsertInterp); - - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); - for (auto& NextColorRamp : RampColorList) - { - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - CreateColorRampParameterInsertEvent( - NextColorRamp, InsertPosition, InsertColor, InsertInterp); - - NextColorRamp->MarkChanged(true); - } - else - { - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject - (NextColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); - NewCachedPoint->Position = InsertPosition; - NewCachedPoint->Value = InsertColor; - NewCachedPoint->Interpolation = InsertInterp; - - NextColorRamp->CachedPoints.Add(NewCachedPoint); - NextColorRamp->bCaching = true; - if (!bCookingEnabled) - NextColorRamp->MarkChanged(true); - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - auto DeleteRampPoint_Lambda = [bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, - UHoudiniParameterRampColor* MainRampColor, - TArray &FloatRampList, - TArray &ColorRampList, - const int32& Index) mutable - { - if (MainRampFloat) - { - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampList); - - for (auto& NextFloatRamp : FloatRampList) - { - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextFloatRamp->Points.Num() == 0) - return; - - UHoudiniParameterRampFloatPoint* PointToDelete = nullptr; - - if (Index == -1) - PointToDelete = NextFloatRamp->Points.Last(); - else if (NextFloatRamp->Points.IsValidIndex(Index)) - PointToDelete = NextFloatRamp->Points[Index]; - - if (!PointToDelete) - return; - - const int32 & InstanceIndexToDelete = PointToDelete->InstanceIndex; - - CreateFloatRampParameterDeleteEvent(NextFloatRamp, InstanceIndexToDelete); - NextFloatRamp->MarkChanged(true); - } - else - { - if (NextFloatRamp->CachedPoints.Num() == 0) - return; - - if (Index == -1) - NextFloatRamp->CachedPoints.Pop(); - else if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) - NextFloatRamp->CachedPoints.RemoveAt(Index); - else - return; - - NextFloatRamp->bCaching = true; - if (!bCookingEnabled) - NextFloatRamp->MarkChanged(true); - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); - } - } - else - { - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampList); - - for (auto& NextColorRamp : ColorRampList) - { - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextColorRamp->Points.Num() == 0) - return; - - UHoudiniParameterRampColorPoint* PointToRemove = nullptr; - - if (Index == -1) - PointToRemove = NextColorRamp->Points.Last(); - else if (NextColorRamp->Points.IsValidIndex(Index)) - PointToRemove = NextColorRamp->Points[Index]; - - if (!PointToRemove) - return; - - const int32 & InstanceIndexToDelete = PointToRemove->InstanceIndex; - - CreateColorRampParameterDeleteEvent(NextColorRamp, InstanceIndexToDelete); - - NextColorRamp->MarkChanged(true); - } - else - { - if (NextColorRamp->CachedPoints.Num() == 0) - return; - - if (Index == -1) - NextColorRamp->CachedPoints.Pop(); - else if (NextColorRamp->CachedPoints.IsValidIndex(Index)) - NextColorRamp->CachedPoints.RemoveAt(Index); - else - return; - - NextColorRamp->bCaching = true; - if (!bCookingEnabled) - NextColorRamp->MarkChanged(true); - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - - TSharedRef VerticalBox = StaticCastSharedRef(Row->ValueWidget.Widget); - - TSharedPtr GridPanel; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SAssignNew(GridPanel, SUniformGridPanel) - ]; - - //AllUniformGridPanels.Add(GridPanel.Get()); - - GridPanel->SetSlotPadding(FMargin(2.f, 2.f, 5.f, 3.f)); - GridPanel->AddSlot(0, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Position"))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - FString ValueString = TEXT("Value"); - if (!MainFloatRampParameter) - ValueString = TEXT("Color"); - - GridPanel->AddSlot(1, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(ValueString)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - GridPanel->AddSlot(2, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Interp."))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - - GridPanel->AddSlot(3, RowIndex) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton( - FSimpleDelegate::CreateLambda([InsertRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, bCookingEnabled]() mutable - { - int32 InsertAtIndex = -1; - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - InsertAtIndex = MainFloatRampParameter->Points.Num(); - else - InsertAtIndex = MainFloatRampParameter->CachedPoints.Num(); - } - else if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - InsertAtIndex = MainColorRampParameter->Points.Num(); - else - InsertAtIndex = MainColorRampParameter->CachedPoints.Num(); - } - - InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, InsertAtIndex); - }), - LOCTEXT("AddRampPoint", "Add a ramp point to the end"), true) - ] - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeRemoveButton( - FSimpleDelegate::CreateLambda([DeleteRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList]() mutable - { - DeleteRampPoint_Lambda( - MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, -1); - }), - LOCTEXT("DeleteRampPoint", "Delete the last ramp point"), true) - ] - - ]; - - EUnit Unit = EUnit::Unspecified; - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - int32 PointCount = 0; - // Use Synced points on auto update mode - // Use Cached points on manual update mode - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - PointCount = MainFloatRampParameter->Points.Num(); - else - PointCount = MainFloatRampParameter->CachedPoints.Num(); - } - - if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate()) - PointCount = MainColorRampParameter->Points.Num(); - else - PointCount = MainColorRampParameter->CachedPoints.Num(); - } - - // Lambda function for changing a ramp point - auto OnPointChangeCommit = [bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, UHoudiniParameterRampColor* MainRampColor, - UHoudiniParameterRampFloatPoint* MainRampFloatPoint, UHoudiniParameterRampColorPoint* MainRampColorPoint, - TArray &RampFloatList, TArray &RampColorList, - const int32& Index, const FString& ChangedDataName, - const float& NewPosition, const float& NewFloat, - const FLinearColor& NewColor, - const EHoudiniRampInterpolationType& NewInterpType) mutable - { - if (MainRampFloat && MainRampFloatPoint) - { - if (ChangedDataName == FString("position") && MainRampFloatPoint->GetPosition() == NewPosition) - return; - if (ChangedDataName == FString("value") && MainRampFloatPoint->GetValue() == NewFloat) - return; - if (ChangedDataName == FString("interp") && MainRampFloatPoint->GetInterpolation() == NewInterpType) - return; - - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); - for (auto NextFloatRamp : RampFloatList) - { - if (!NextFloatRamp) - continue; - - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextFloatRamp->Points.IsValidIndex(Index)) - { - UHoudiniParameterRampFloatPoint* CurrentFloatRampPoint = NextFloatRamp->Points[Index]; - if (!CurrentFloatRampPoint) - continue; - - if (ChangedDataName == FString("position")) - { - if (!CurrentFloatRampPoint->PositionParentParm) - continue; - - CurrentFloatRampPoint->SetPosition(NewPosition); - CurrentFloatRampPoint->PositionParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("value")) - { - if (!CurrentFloatRampPoint->PositionParentParm) - continue; - - CurrentFloatRampPoint->SetValue(NewFloat); - CurrentFloatRampPoint->ValueParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("interp")) - { - if (!CurrentFloatRampPoint->InterpolationParentParm) - continue; - - CurrentFloatRampPoint->SetInterpolation(NewInterpType); - CurrentFloatRampPoint->InterpolationParentParm->MarkChanged(true); - } - } - else - { - int32 IdxInEventsArray = Index - NextFloatRamp->Points.Num(); - if (NextFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = NextFloatRamp->ModificationEvents[IdxInEventsArray]; - if (!Event) - continue; - - if (ChangedDataName == FString("position")) - { - Event->InsertPosition = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - Event->InsertFloat = NewFloat; - } - else if (ChangedDataName == FString("interp")) - { - Event->InsertInterpolation = NewInterpType; - } - } - } - } - else - { - if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) - { - UHoudiniParameterRampFloatPoint* CachedPoint = NextFloatRamp->CachedPoints[Index]; - - if (!CachedPoint) - continue; - - if (ChangedDataName == FString("position")) - { - CachedPoint->Position = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - CachedPoint->Value = NewFloat; - } - else if (ChangedDataName == FString("interp")) - { - CachedPoint->Interpolation = NewInterpType; - } - - NextFloatRamp->bCaching = true; - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); - } - } - else if (MainRampColor && MainRampColorPoint) - { - if (ChangedDataName == FString("position") && MainRampColorPoint->GetPosition() == NewPosition) - return; - - if (ChangedDataName == FString("value") && MainRampColorPoint->GetValue() == NewColor) - return; - - if (ChangedDataName == FString("interp") && MainRampColorPoint->GetInterpolation() == NewInterpType) - return; - - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); - for (auto NextColorRamp : RampColorList) - { - if (!NextColorRamp) - continue; - - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextColorRamp->Points.IsValidIndex(Index)) - { - UHoudiniParameterRampColorPoint* CurrentColorRampPoint = NextColorRamp->Points[Index]; - if (!CurrentColorRampPoint) - continue; - - if (ChangedDataName == FString("position")) - { - if (!CurrentColorRampPoint->PositionParentParm) - continue; - - CurrentColorRampPoint->SetPosition(NewPosition); - CurrentColorRampPoint->PositionParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("value")) - { - if (!CurrentColorRampPoint->PositionParentParm) - continue; - - CurrentColorRampPoint->SetValue(NewColor); - CurrentColorRampPoint->ValueParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("interp")) - { - if (!CurrentColorRampPoint->InterpolationParentParm) - continue; - - CurrentColorRampPoint->SetInterpolation(NewInterpType); - CurrentColorRampPoint->InterpolationParentParm->MarkChanged(true); - } - } - else - { - int32 IdxInEventsArray = Index - NextColorRamp->Points.Num(); - if (NextColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = NextColorRamp->ModificationEvents[IdxInEventsArray]; - if (!Event) - continue; - - if (ChangedDataName == FString("position")) - { - Event->InsertPosition = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - Event->InsertColor = NewColor; - } - else if (ChangedDataName == FString("interp")) - { - Event->InsertInterpolation = NewInterpType; - } - - } - } - } - else - { - if (NextColorRamp->CachedPoints.IsValidIndex(Index)) - { - UHoudiniParameterRampColorPoint* CachedPoint = NextColorRamp->CachedPoints[Index]; - - if (!CachedPoint) - continue; - - if (ChangedDataName == FString("position")) - { - CachedPoint->Position = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - CachedPoint->Value = NewColor; - } - else if (ChangedDataName == FString("interp")) - { - CachedPoint->Interpolation = NewInterpType; - } - - NextColorRamp->bCaching = true; - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - for (int32 Index = 0; Index < PointCount; ++Index) - { - UHoudiniParameterRampFloatPoint* NextFloatRampPoint = nullptr; - UHoudiniParameterRampColorPoint* NextColorRampPoint = nullptr; - - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - NextFloatRampPoint = MainFloatRampParameter->Points[Index]; - else - NextFloatRampPoint = MainFloatRampParameter->CachedPoints[Index]; - } - if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NextColorRampPoint = MainColorRampParameter->Points[Index]; - else - NextColorRampPoint = MainColorRampParameter->CachedPoints[Index]; - } - - if (!NextFloatRampPoint && !NextColorRampPoint) - continue; - - RowIndex += 1; - - float CurPos = 0.f; - if (NextFloatRampPoint) - CurPos = NextFloatRampPoint->Position; - else - CurPos = NextColorRampPoint->Position; - - - GridPanel->AddSlot(0, RowIndex) - [ - SNew(SNumericEntryBox) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Value(CurPos) - .OnValueChanged_Lambda([](float Val) {}) - .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("position"), - Val, float(-1.0), - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .OnBeginSliderMovement_Lambda([]() {}) - .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("position"), - Val, float(-1.0), - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .SliderExponent(1.0f) - .TypeInterface(paramTypeInterface) - ]; - - if (NextFloatRampPoint) - { - GridPanel->AddSlot(1, RowIndex) - [ - SNew(SNumericEntryBox< float >) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Value(NextFloatRampPoint->Value) - .OnValueChanged_Lambda([](float Val){}) - .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), Val, - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .OnBeginSliderMovement_Lambda([]() {}) - .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), Val, - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .SliderExponent(1.0f) - .TypeInterface(paramTypeInterface) - ]; - } - else if (NextColorRampPoint) - { - auto OnColorChangeLambda = [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](FLinearColor InColor) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), float(-1.0), - InColor, - EHoudiniRampInterpolationType::LINEAR); - }; - - // Add color picker UI. - //TSharedPtr ColorBlock; - GridPanel->AddSlot(1, RowIndex) - [ - SNew(SColorBlock) - .Color(NextColorRampPoint->Value) - .OnMouseButtonDown( FPointerEventHandler::CreateLambda( - [NextColorRampPoint, OnColorChangeLambda](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) - { - if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) - return FReply::Unhandled(); - - FColorPickerArgs PickerArgs; - PickerArgs.bUseAlpha = true; - PickerArgs.DisplayGamma = TAttribute< float >::Create( - TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda(OnColorChangeLambda); - FLinearColor InitColor = NextColorRampPoint->Value; - PickerArgs.InitialColorOverride = InitColor; - PickerArgs.bOnlyRefreshOnOk = true; - OpenColorPicker(PickerArgs); - return FReply::Handled(); - })) - ]; - } - - int32 CurChoice = 0; - if (NextFloatRampPoint) - CurChoice = (int)NextFloatRampPoint->Interpolation; - else - CurChoice = (int)NextColorRampPoint->Interpolation; - - TSharedPtr >> ComboBoxCurveMethod; - GridPanel->AddSlot(2, RowIndex) - [ - SAssignNew(ComboBoxCurveMethod, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels())[CurChoice]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, - ColorRampParameterList, Index](TSharedPtr NewChoice, ESelectInfo::Type SelectType) mutable - { - EHoudiniRampInterpolationType NewInterpType = UHoudiniParameter::GetHoudiniInterpMethodFromString(*NewChoice.Get()); - - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("interp"), - float(-1.0), float(-1.0), - FLinearColor(), - NewInterpType); - }) - [ - SNew(STextBlock) - .Text_Lambda([NextFloatRampPoint, NextColorRampPoint]() - { - EHoudiniRampInterpolationType CurInterpType = EHoudiniRampInterpolationType::InValid; - if (NextFloatRampPoint) - CurInterpType = NextFloatRampPoint->GetInterpolation(); - else - CurInterpType = NextColorRampPoint->GetInterpolation(); - - return FText::FromString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(CurInterpType)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - GridPanel->AddSlot(3, RowIndex) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateLambda( - [InsertRampPoint_Lambda, MainFloatRampParameter, - MainColorRampParameter, FloatRampParameterList, - ColorRampParameterList, Index]() mutable - { - InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); - }), - LOCTEXT("AddRampPoint", "Add a ramp point before this point"), true) - ] - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateLambda( - [DeleteRampPoint_Lambda, MainFloatRampParameter, - MainColorRampParameter, FloatRampParameterList, - ColorRampParameterList, Index]() mutable - { - DeleteRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); - }), - LOCTEXT("DeleteFloatRampPoint", "Delete this ramp point"), true) - ] - ]; - - if (MainFloatRampParameter && CurrentRampParameterFloatCurve) - { - ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextFloatRampPoint->GetInterpolation()); - FRichCurve & RichCurve = CurrentRampParameterFloatCurve->FloatCurve; - FKeyHandle const KeyHandle = RichCurve.AddKey(NextFloatRampPoint->GetPosition(), NextFloatRampPoint->GetValue()); - RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); - } - - if (MainColorRampParameter && CurrentRampParameterColorCurve) - { - ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextColorRampPoint->GetInterpolation()); - for (int32 CurveIdx = 0; CurveIdx < 4; ++CurveIdx) - { - FRichCurve & RichCurve = CurrentRampParameterColorCurve->FloatCurves[CurveIdx]; - - FKeyHandle const KeyHandle = RichCurve.AddKey(NextColorRampPoint->GetPosition(), NextColorRampPoint->GetValue().Component(CurveIdx)); - RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); - } - } - } - - if (MainFloatRampParameter) - GridPanel->SetEnabled(!MainFloatRampParameter->IsDisabled()); - - if (MainColorRampParameter) - GridPanel->SetEnabled(!MainColorRampParameter->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetFolderList(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FolderListParams; - if (!CastParameters(InParams, FolderListParams)) - return; - - if (FolderListParams.Num() <= 0) - return; - - UHoudiniParameterFolderList* MainParam = FolderListParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Add this folder list to the folder map - AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); - - MainParam->GetTabs().Empty(); - - // A folder list will be followed by all its child folders, - // so set the CurrentFolderListSize to the tuple size, we'll process such many folder parameters right after - CurrentFolderListSize = MainParam->GetTupleSize(); - - if (MainParam->IsDirectChildOfMultiParm()) - MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); - - if (CurrentFolderListSize <= 0) // There should not be empty folder list, this will not happen normally - return; - - // The following folders belong to current folder list - CurrentFolderList = MainParam; - - // If the tab is either a tabs or radio button and the parameter is visible - if (MainParam->IsTabMenu() && MainParam->ShouldDisplay()) - { - // Set the current tabs to be not shown by default now. CreateWidgetTab will decide if the tabs is shown. - CurrentFolderList->SetTabsShown(false); - - // Create a row to hold tab buttons if the folder list is a tabs or radio button - - // CreateNestedRow does not actually create a row for tabs, it is responsible to prune the folder stack. - // ( CreateWidgetTab will be responsible to create a row according to the visibility of its outer level folders ) - FDetailWidgetRow* TabRow = CreateNestedRow(HouParameterCategory, InParams, false); - - } - - // When see a folder list, go depth first search at this step. - // Push an empty queue to the stack. - FolderStack.Add(TArray()); -} - - -void -FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FolderParams; - if (!CastParameters(InParams, FolderParams)) - return; - - if (FolderParams.Num() <= 0) - return; - - UHoudiniParameterFolder* MainParam = FolderParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen - return; - // If a folder is invisible, its children won't be listed by HAPI. - // So just reduce FolderListSize by 1, reduce the child counter of its parent folder by 1 if necessary, - // and prune the stack in such case. - if (!MainParam->IsVisible()) - { - CurrentFolderListSize -= 1; - - if (CurrentFolderListSize == 0) - { - if (FolderStack.Num() > 1) - { - TArray &ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) - ParentFolderQueue[0]->GetChildCounter() -= 1; - } - - PruneStack(); - } - - return; - } - - // We expect 'TupleSize' children param of this folder after finish processing all the child folders of cur folderlist - MainParam->ResetChildCounter(); - - // Add this folder to the folder map - AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); - - // Set the parent param to current folderList. - // it was parent multiparm's id if this folder is a child of a multiparms. - // This will cause problem if the folder is inside of a multiparm - MainParam->SetParentParmId(CurrentFolderList->GetParmId()); - - - // Case 1: The folder is a direct child of a multiparm. - if (MainParam->IsDirectChildOfMultiParm()) - { - if (FolderStack.Num() <= 0) // This should not happen - return; - - // Get its parent multiparm first - UHoudiniParameterMultiParm* ParentMultiParm = nullptr; - { - UHoudiniParameterFolderList * ParentFolderList = nullptr; - if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) - return; - - ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); - - if (!ParentFolderList) - return; - - if (AllMultiParms.Contains(ParentFolderList->GetParentParmId())) - ParentMultiParm = AllMultiParms[ParentFolderList->GetParentParmId()]; - - if (!ParentMultiParm) // This should not happen - return; - } - - bool bShown = ParentMultiParm->IsShown(); - - // Case 1-1: The folder is NOT tabs - if (!MainParam->IsTab()) - { - bShown = MainParam->IsExpanded() && bShown; - - // If the parent multiparm is shown. - if (ParentMultiParm->IsShown()) - { - FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderHeaderRow, InParams); - } - } - // Case 1-2: The folder IS tabs. - else - { - CreateWidgetTab(HouParameterCategory, MainParam, ParentMultiParm->IsShown()); - } - - // Push the folder to the queue if it is not a tab folder - // This step is handled by CreateWidgetTab() if it is tabs - if ((!MainParam->IsTab() || !ParentMultiParm->IsShown()) && MainParam->GetTupleSize() > 0) - { - TArray & MyQueue = FolderStack.Last(); - MainParam->SetIsContentShown(bShown); - MyQueue.Add(MainParam); - } - } - - // Case 2: The folder is NOT a direct child of a multiparm. - else - { - // Case 2-1: The folder is in another folder. - if (FolderStack.Num() > 1 && CurrentFolderListSize > 0) - { - TArray & MyFolderQueue = FolderStack.Last(); - TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - - if (ParentFolderQueue.Num() <= 0) //This should happen - return; - - // Peek the folder queue of the last layer to get its parent folder parm. - bool ParentFolderVisible = ParentFolderQueue[0]->IsContentShown(); - - // If this folder is expanded (selected if is tabs) - bool bExpanded = ParentFolderVisible; - - // Case 2-1-1: The folder is NOT in a tab menu. - if (!MainParam->IsTab()) - { - bExpanded &= MainParam->IsExpanded(); - - // The parent folder is visible. - if (ParentFolderVisible) - { - // Add the folder header UI. - FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderHeaderRow, InParams); - } - - MainParam->SetIsContentShown(bExpanded); - MyFolderQueue.Add(MainParam); - } - // Case 2-1-2: The folder IS in a tab menu. - else - { - bExpanded &= MainParam->IsChosen(); - - CreateWidgetTab(HouParameterCategory, MainParam, ParentFolderVisible); - } - } - // Case 2-2: The folder is in the root. - else - { - bool bExpanded = true; - - // Case 2-2-1: The folder is NOT under a tab menu. - if (!MainParam->IsTab()) - { - if (FolderStack.Num() <= 0) // This will not happen - return; - - // Create Folder header under root. - FDetailWidgetRow* FolderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderRow, InParams); - - if (FolderStack.Num() == 0) // This should not happen - return; - - TArray& MyFolderQueue = FolderStack[0]; - bExpanded &= MainParam->IsExpanded(); - MainParam->SetIsContentShown(bExpanded); - MyFolderQueue.Add(MainParam); - } - // Case 2-2-2: The folder IS under a tab menu. - else - { - // Tabs in root is always visible - CreateWidgetTab(HouParameterCategory, MainParam, true); - } - } - } - - - CurrentFolderListSize -= 1; - - // Prune the stack if finished parsing current folderlist - if (CurrentFolderListSize == 0) - { - if (FolderStack.Num() > 1 && !MainParam->IsDirectChildOfMultiParm()) - { - TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) - ParentFolderQueue[0]->GetChildCounter() -= 1; - } - - PruneStack(); - - CurrentFolderList = nullptr; - } -} - -void -FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray &InParams) -{ - if (!HeaderRow) // The folder is invisible. - return; - - TArray FolderParams; - if (!CastParameters(InParams, FolderParams)) - return; - - if (FolderParams.Num() <= 0) - return; - - UHoudiniParameterFolder* MainParam = FolderParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return; - - TSharedPtr VerticalBox; - - FString LabelStr = MainParam->GetParameterLabel(); - - TSharedPtr HorizontalBox; - TSharedPtr ExpanderArrow; - TSharedPtr ExpanderImage; - - HeaderRow->NameWidget.Widget = SAssignNew(HorizontalBox, SCustomizedBox); - - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - - if (MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 1) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - LabelStr = LabelStr + TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex) + TEXT(")"); - } - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SAssignNew(ExpanderArrow, SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([=]() - { - MainParam->ExpandButtonClicked(); - - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - - return FReply::Handled(); - }) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - - FText LabelText = FText::FromString(LabelStr); - - HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SNew(STextBlock) - .Text(LabelText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - ExpanderImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([=]() { - FName ResourceName; - if(MainParam->IsExpanded()) - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - }))); - - if(MainParam->GetFolderType() == EHoudiniFolderParameterType::Simple) - ExpanderArrow->SetEnabled(false); - -} - -void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InFolder, const bool& bIsShown) -{ - if (!InFolder || InFolder->IsPendingKill() || !CurrentFolderList) - return; - - if (FolderStack.Num() <= 0) // error state - return; - - TArray & FolderQueue = FolderStack.Last(); - - // Cache all tabs of current tab folder list. - CurrentFolderList->AddTabFolder(InFolder); - - // If the tabs is not shown, just push the folder param into the queue. - if (!bIsShown) - { - InFolder->SetIsContentShown(bIsShown); - FolderQueue.Add(InFolder); - return; - } - - // tabs currently being processed - CurrentTabs.Add(InFolder); - - if (CurrentFolderListSize > 1) - return; - - // The tabs belong to current folder list - UHoudiniParameterFolderList* CurrentTabMenuFolderList = CurrentFolderList; - - // Create a row (UI) for current tabs - TSharedPtr HorizontalBox; - FDetailWidgetRow &Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()) - [ - SAssignNew(HorizontalBox, SCustomizedBox) - ]; - - // Put current tab folder list param into an array - TArray CurrentTabMenuFolderListArr; - CurrentTabMenuFolderListArr.Add(CurrentTabMenuFolderList); - - HorizontalBox->SetHoudiniParameter(CurrentTabMenuFolderListArr); - DividerLinePositions.Add(HorizontalBox->AddIndentation(InFolder, AllMultiParms, AllFoldersAndFolderLists)); - HorizontalBox->DividerLinePositions = DividerLinePositions; - - float DesiredHeight = 0.0f; - float DesiredWidth = 0.0f; - - // Process all tabs of current folder list at once when done. - - for (auto & CurTab : CurrentTabs) - { - if (!CurTab || CurTab->IsPendingKill()) - continue; - - CurTab->SetIsContentShown(CurTab->IsChosen()); - FolderQueue.Add(CurTab); - - auto OnTabClickedLambda = [CurrentTabMenuFolderList, CurTab, &HouParameterCategory]() - { - if (CurrentTabMenuFolderList) - { - if (!CurrentTabMenuFolderList->bIsTabMenu || CurrentTabMenuFolderList->TabFolders.Num() <= 0) - return FReply::Handled(); - - if (CurTab->IsChosen()) - return FReply::Handled(); - - CurTab->SetChosen(true); - - for (UHoudiniParameterFolder* NextFolder : CurrentTabMenuFolderList->TabFolders) - { - if (CurTab->GetParmId() != NextFolder->GetParmId() && NextFolder->IsChosen()) - NextFolder->SetChosen(false); - } - - HouParameterCategory.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); - }; - - FString FolderLabelString = TEXT(" ") + CurTab->GetParameterLabel(); - if (CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio) - FolderLabelString = TEXT(" ") + FolderLabelString; - - bool bChosen = CurTab->IsTab() && CurTab->IsChosen(); - - TSharedPtr CurCustomizedButton; - - HorizontalBox->AddSlot().VAlign(VAlign_Bottom) - .AutoWidth() - .Padding(0.f) - .HAlign(HAlign_Left) - [ - SAssignNew(CurCustomizedButton, SCustomizedButton) - .OnClicked_Lambda(OnTabClickedLambda) - .Content() - [ - SNew(STextBlock) - .Text(FText::FromString(FolderLabelString)) - ] - ]; - - CurCustomizedButton->bChosen = bChosen; - CurCustomizedButton->bIsRadioButton = CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio; - - DesiredHeight = CurCustomizedButton->GetDesiredSize().Y; - DesiredWidth += CurCustomizedButton->GetDesiredSize().X; - } - - HorizontalBox->bIsTabFolderListRow = true; - - Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - // Set the current tabs to be shown, since slate widgets have been created - CurrentTabMenuFolderList->SetTabsShown(true); - - // Clear the temporary tabs - CurrentTabs.Empty(); -} - -void -FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray MultiParmParams; - if (!CastParameters(InParams, MultiParmParams)) - return; - - if (MultiParmParams.Num() <= 0) - return; - - UHoudiniParameterMultiParm* MainParam = MultiParmParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Add current multiparm parameter to AllmultiParms map - AllMultiParms.Add(MainParam->GetParmId(), MainParam); - - // Create a new detail row - FDetailWidgetRow * Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - { - MainParam->SetIsShown(false); - return; - } - - MainParam->SetIsShown(true); - - MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); - - CreateNameWidget(Row, InParams, true); - - auto OnInstanceValueChangedLambda = [MainParam](int32 InValue) - { - if (InValue < 0) - return; - - int32 ChangesCount = FMath::Abs(MainParam->MultiParmInstanceLastModifyArray.Num() - InValue); - - if (MainParam->MultiParmInstanceLastModifyArray.Num() > InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->RemoveElement(-1); - - MainParam->MarkChanged(true); - } - else if (MainParam->MultiParmInstanceLastModifyArray.Num() < InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->InsertElement(); - - MainParam->MarkChanged(true); - } - }; - - // Add multiparm UI. - TSharedRef HorizontalBox = SNew(SHorizontalBox); - TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; - int32 NumericalCount = MainParam->MultiParmInstanceCount; - HorizontalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateLambda([OnInstanceValueChangedLambda](int32 InValue) { - OnInstanceValueChangedLambda(InValue); - })) - .Value(NumericalCount) - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamAddInstance", "Houdini Parameter Multi Parameter: Adding an instance"), - MainParam->GetOuter(), true); - - for (auto& Param : MultiParmParams) - { - if (!Param) - continue; - - // Add a reverse step for redo/undo - Param->MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Removed); - - Param->MarkChanged(true); - Param->Modify(); - - if (Param->MultiParmInstanceLastModifyArray.Num() > 0) - Param->MultiParmInstanceLastModifyArray.RemoveAt(Param->MultiParmInstanceLastModifyArray.Num() - 1); - - Param->InsertElement(); - - } - }), - LOCTEXT("AddMultiparmInstanceToolTipAddLastInstance", "Add an Instance"), true) - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - // Remove the last multiparm instance - PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamDeleteInstance", "Houdini Parameter Multi Parameter: Deleting an instance"), - MainParam->GetOuter(), true); - - for (auto & Param : MultiParmParams) - { - TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; - int32 RemovedIndex = LastModifiedArray.Num() - 1; - while (LastModifiedArray.IsValidIndex(RemovedIndex) && LastModifiedArray[RemovedIndex] == EHoudiniMultiParmModificationType::Removed) - RemovedIndex -= 1; - - // Add a reverse step for redo/undo - EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; - if (LastModifiedArray.IsValidIndex(RemovedIndex)) - { - PreviousModType = LastModifiedArray[RemovedIndex]; - LastModifiedArray[RemovedIndex] = EHoudiniMultiParmModificationType::Inserted; - } - - Param->MarkChanged(true); - - Param->Modify(); - - if (LastModifiedArray.IsValidIndex(RemovedIndex)) - { - LastModifiedArray[RemovedIndex] = PreviousModType; - } - - Param->RemoveElement(RemovedIndex); - } - - }), - LOCTEXT("RemoveLastMultiParamLastToolTipRemoveLastInstance", "Remove the last instance"), true) - - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamDeleteAllInstances", "Houdini Parameter Multi Parameter: Deleting all instances"), - MainParam->GetOuter(), true); - - for (auto & Param : MultiParmParams) - { - TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; - TArray IndicesToReverse; - - for (int32 Index = 0; Index < LastModifiedArray.Num(); ++Index) - { - if (LastModifiedArray[Index] == EHoudiniMultiParmModificationType::None) - { - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; - IndicesToReverse.Add(Index); - } - } - - Param->MarkChanged(true); - - Param->Modify(); - - for (int32 & Index : IndicesToReverse) - { - if (LastModifiedArray.IsValidIndex(Index)) - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::None; - } - - - Param->EmptyElements(); - } - - }), - LOCTEXT("HoudiniParameterRemoveAllMultiparmInstancesToolTip", "Remove all instances"), true) - ]; - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniParameterDetails::CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams) -{ - - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return; - - if (!HorizontalBox || !AllMultiParms.Contains(MainParam->GetParentParmId()) || !MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - return; - - UHoudiniParameterMultiParm* MainParentMultiParm = AllMultiParms[MainParam->GetParentParmId()]; - - if (!MainParentMultiParm) - return; - - if (!MainParentMultiParm->IsShown()) - return; - - // push all parent multiparm of the InParams to the array - TArray ParentMultiParams; - for (auto & InParam : InParams) - { - if (!InParam) - continue; - - if (!MultiParmInstanceIndices.Contains(InParam->GetParentParmId())) - continue; - - if (InParam->GetChildIndex() == 0) - { - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[InParam->GetParentParmId()]; - - if (ParentMultiParm) - ParentMultiParams.Add(ParentMultiParm); - } - } - - - int32 InstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - - TSharedRef AddButton = PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() - { - for (auto & ParentParam : ParentMultiParams) - { - // Add button call back - if (!ParentParam) - continue; - - TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; - - if (!LastModifiedArray.IsValidIndex(InstanceIndex)) - continue; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParmAddBeforeCurInstance", "Houdini Parameter Multi Parm: Adding an instance"), - ParentParam->GetOuter(), true); - - - int32 Index = InstanceIndex; - - // Add a reverse step for undo/redo - if (Index >= LastModifiedArray.Num()) - LastModifiedArray.Add(EHoudiniMultiParmModificationType::Removed); - else - LastModifiedArray.Insert(EHoudiniMultiParmModificationType::Removed, Index); - - ParentParam->MarkChanged(true); - ParentParam->Modify(); - - if (Index >= LastModifiedArray.Num() - 1 && LastModifiedArray.Num()) - LastModifiedArray.RemoveAt(LastModifiedArray.Num() - 1); - else - LastModifiedArray.RemoveAt(Index); - - ParentParam->InsertElementAt(InstanceIndex); - - } - }), - LOCTEXT("HoudiniParameterMultiParamAddBeforeCurrentInstanceToolTip", "Insert an instance before this instance")); - - - TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() - { - for (auto & ParentParam : ParentMultiParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParmDeleteCurInstance", "Houdini Parameter Multi Parm: Deleting an instance"), - ParentParam->GetOuter(), true); - - - TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; - - int32 Index = InstanceIndex; - EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; - while (LastModifiedArray.IsValidIndex(Index) && LastModifiedArray[Index] == EHoudiniMultiParmModificationType::Removed) - { - Index -= 1; - } - - if (LastModifiedArray.IsValidIndex(Index)) - { - PreviousModType = LastModifiedArray[Index]; - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; - } - - ParentParam->MarkChanged(true); - - ParentParam->Modify(); - - if (LastModifiedArray.IsValidIndex(Index)) - { - LastModifiedArray[Index] = PreviousModType; - } - - ParentParam->RemoveElement(InstanceIndex); - } - - }), - LOCTEXT("HoudiniParameterMultiParamDeleteCurrentInstanceToolTip", "Remove an instance"), true); - - - HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[AddButton]; - HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[RemoveButton]; - - int32 StartIdx = MainParam->GetParameterType() == EHoudiniParameterType::Folder ? 1 : 0; - if (MainParam->GetChildIndex() != StartIdx) - { - AddButton.Get().SetVisibility(EVisibility::Hidden); - RemoveButton.Get().SetVisibility(EVisibility::Hidden); - } - -} - -void -FHoudiniParameterDetails::PruneStack() -{ - for (int32 StackItr = FolderStack.Num() - 1; StackItr >= 0; --StackItr) - { - TArray &CurrentQueue = FolderStack[StackItr]; - - for (int32 QueueItr = CurrentQueue.Num() - 1; QueueItr >= 0; --QueueItr) - { - UHoudiniParameterFolder * CurrentFolder = CurrentQueue[QueueItr]; - if (!CurrentFolder || CurrentFolder->IsPendingKill()) - continue; - - if (CurrentFolder->GetChildCounter() == 0) - { - CurrentQueue.RemoveAt(QueueItr); - } - } - - if (CurrentQueue.Num() == 0) - { - FolderStack.RemoveAt(StackItr); - } - } -} - -FText -FHoudiniParameterDetails::GetParameterTooltip(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return FText(); - - // Tooltip starts with Label (name) - FString Tooltip = InParam->GetParameterLabel() + TEXT(" (") + InParam->GetParameterName() + TEXT(")"); - - // Append the parameter type - FString ParmTypeStr = GetParameterTypeString(InParam->GetParameterType(), InParam->GetTupleSize()); - if (!ParmTypeStr.IsEmpty()) - Tooltip += TEXT("\n") + ParmTypeStr; - - // If the parameter has some help, append it - FString Help = InParam->GetParameterHelp(); - if (!Help.IsEmpty()) - Tooltip += TEXT("\n") + Help; - - // If the parameter has an expression, append it - if (InParam->HasExpression()) - { - FString Expr = InParam->GetExpression(); - if (!Expr.IsEmpty()) - Tooltip += TEXT("\nExpression: ") + Expr; - } - - return FText::FromString(Tooltip); -} - -FString -FHoudiniParameterDetails::GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize) -{ - FString ParamStr; - - switch (InType) - { - case EHoudiniParameterType::Button: - ParamStr = TEXT("Button"); - break; - - case EHoudiniParameterType::ButtonStrip: - ParamStr = TEXT("Button Strip"); - break; - - case EHoudiniParameterType::Color: - { - if (InTupleSize == 4) - ParamStr = TEXT("Color with Alpha"); - else - ParamStr = TEXT("Color"); - } - break; - - case EHoudiniParameterType::ColorRamp: - ParamStr = TEXT("Color Ramp"); - break; - - case EHoudiniParameterType::File: - ParamStr = TEXT("File (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileDir: - ParamStr = TEXT("File Dir (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileGeo: - ParamStr = TEXT("File Geo (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileImage: - ParamStr = TEXT("File Image (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::Float: - ParamStr = TEXT("Float (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); - break; - - case EHoudiniParameterType::FloatRamp: - ParamStr = TEXT("Float Ramp"); - break; - - case EHoudiniParameterType::Folder: - case EHoudiniParameterType::FolderList: - break; - - case EHoudiniParameterType::Input: - ParamStr = TEXT("Opearator Path"); - break; - - case EHoudiniParameterType::Int: - ParamStr = TEXT("Integer (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); - break; - - case EHoudiniParameterType::IntChoice: - ParamStr = TEXT("Int Choice"); - break; - - case EHoudiniParameterType::Label: - ParamStr = TEXT("Label (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::MultiParm: - ParamStr = TEXT("MultiParm"); - break; - - case EHoudiniParameterType::Separator: - break; - - case EHoudiniParameterType::String: - ParamStr = TEXT("String (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::StringAssetRef: - ParamStr = TEXT("String Asset Ref (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::StringChoice: - ParamStr = TEXT("String Choice"); - break; - - case EHoudiniParameterType::Toggle: - ParamStr = TEXT("Toggle (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - default: - ParamStr = TEXT("invalid parameter type"); - break; - } - - - return ParamStr; -} - -void -FHoudiniParameterDetails::SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter) -{ - if (!ColorRampParameter) - return; - - // Do not sync when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) - return; - - TArray &CachedPoints = ColorRampParameter->CachedPoints; - TArray &CurrentPoints = ColorRampParameter->Points; - - bool bCurveNeedsUpdate = false; - bool bRampParmNeedsUpdate = false; - - int32 Idx = 0; - - while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) - { - UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[Idx]; - UHoudiniParameterRampColorPoint* CurrentPoint = CurrentPoints[Idx]; - - if (!CachedPoint || !CurrentPoint) - continue; - - if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) - { - if (CurrentPoint->PositionParentParm) - { - CurrentPoint->SetPosition(CachedPoint->GetPosition()); - CurrentPoint->PositionParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - if (CachedPoint->GetValue() != CurrentPoint->GetValue()) - { - if (CurrentPoint->ValueParentParm) - { - CurrentPoint->SetValue(CachedPoint->GetValue()); - CurrentPoint->ValueParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) - { - if (CurrentPoint->InterpolationParentParm) - { - CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); - CurrentPoint->InterpolationParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - Idx += 1; - } - - // Insert points - for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) - { - UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[IdxCachedPointLeft]; - - CreateColorRampParameterInsertEvent( - ColorRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); - - bCurveNeedsUpdate = true; - bRampParmNeedsUpdate = true; - - } - - // Delete points - for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) - { - ColorRampParameter->RemoveElement(IdxCurrentPointLeft); - - UHoudiniParameterRampColorPoint* Point = CurrentPoints[IdxCurrentPointLeft]; - - if (!Point) - continue; - - CreateColorRampParameterDeleteEvent(ColorRampParameter, Point->InstanceIndex); - - bCurveNeedsUpdate = true; - bRampParmNeedsUpdate = true; - } - - - ColorRampParameter->MarkChanged(bRampParmNeedsUpdate); -} - -//void -//FHoudiniParameterDetails::SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter) -//{ -// if (!FloatRampParameter) -// return; -// -// // Do not sync when the Houdini asset component is cooking -// if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) -// return; -// -// TArray &CachedPoints = FloatRampParameter->CachedPoints; -// TArray &CurrentPoints = FloatRampParameter->Points; -// -// int32 Idx = 0; -// -// while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) -// { -// UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; -// UHoudiniParameterRampFloatPoint* &CurrentPoint = CurrentPoints[Idx]; -// -// if (!CachedPoint || !CurrentPoint) -// continue; -// -// if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) -// { -// if (CurrentPoint->PositionParentParm) -// { -// CurrentPoint->SetPosition(CachedPoint->GetPosition()); -// CurrentPoint->PositionParentParm->MarkChanged(true); -// } -// } -// -// if (CachedPoint->GetValue() != CurrentPoint->GetValue()) -// { -// if (CurrentPoint->ValueParentParm) -// { -// CurrentPoint->SetValue(CachedPoint->GetValue()); -// CurrentPoint->ValueParentParm->MarkChanged(true); -// } -// } -// -// if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) -// { -// if (CurrentPoint->InterpolationParentParm) -// { -// CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); -// CurrentPoint->InterpolationParentParm->MarkChanged(true); -// } -// } -// -// Idx += 1; -// } -// -// // Insert points -// for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) -// { -// UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; -// if (!CachedPoint) -// continue; -// -// CreateFloatRampParameterInsertEvent( -// FloatRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); -// -// FloatRampParameter->MarkChanged(true); -// } -// -// // Remove points -// for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) -// { -// FloatRampParameter->RemoveElement(IdxCurrentPointLeft); -// -// UHoudiniParameterRampFloatPoint* Point = CurrentPoints[IdxCurrentPointLeft]; -// -// if (!Point) -// continue; -// -// CreateFloatRampParameterDeleteEvent(FloatRampParameter, Point->InstanceIndex); -// -// FloatRampParameter->MarkChanged(true); -// } -//} - -void -FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetFloatRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - InParam->ModificationEvents.Add(DeleteEvent); -} - -void -FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetColorRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - InParam->ModificationEvents.Add(DeleteEvent); -} - -void -FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, - const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetFloatRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertFloat = InValue; - InsertEvent->InsertInterpolation = InInterp; - - InParam->ModificationEvents.Add(InsertEvent); -} - -void -FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, - const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetColorRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertColor = InColor; - InsertEvent->InsertInterpolation = InInterp; - - InParam->ModificationEvents.Add(InsertEvent); -} - -void -FHoudiniParameterDetails:: ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters) -{ - if (FloatRampParameters.Num() < 1) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0]; - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) - { - UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx]; - - if (!NextFloatRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); - } -} - -void -FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters) -{ - if (FloatRampParameters.Num() < 1) - return; - - if (!FloatRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - - for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) - { - if (!FloatRampParameters[Idx].IsValid()) - continue; - - UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx].Get(); - - if (!NextFloatRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); - } - -} - -void -FHoudiniParameterDetails:: ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam) -{ - if (!Param || !MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) - return; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Use Synced points if the MainParam is on auto update mode - // Use Cached points if the Mainparam is on manual update mode - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - if (Param->IsAutoUpdate() && bCookingEnabled) - { - TArray & Points = Param->Points; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (MainPoint->GetPosition() != Point->GetPosition()) - { - if (Point->PositionParentParm) - { - Point->SetPosition(MainPoint->GetPosition()); - Point->PositionParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetValue() != Point->GetValue()) - { - if (Point->ValueParentParm) - { - Point->SetValue(MainPoint->GetValue()); - Point->ValueParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetInterpolation() != Point->GetInterpolation()) - { - if (Point->InterpolationParentParm) - { - Point->SetInterpolation(MainPoint->GetInterpolation()); - Point->InterpolationParentParm->MarkChanged(true); - } - } - - PointIdx += 1; - } - - int32 PointInsertIdx = PointIdx; - int32 PointDeleteIdx = PointIdx; - - // skip the pending modification events - for (auto & Event : Param->ModificationEvents) - { - if (!Event) - continue; - - if (Event->IsInsertEvent()) - PointInsertIdx += 1; - - if (Event->IsDeleteEvent()) - PointDeleteIdx += 1; - } - - // There are more points in MainPoints array - for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) - { - UHoudiniParameterRampFloatPoint*& NextMainPoint = MainPoints[PointInsertIdx]; - - if (!NextMainPoint) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(Param, - NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); - - Param->MarkChanged(true); - } - - // There are more points in Points array - for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) - { - UHoudiniParameterRampFloatPoint*& NextPoint = Points[PointDeleteIdx]; - - if (!NextPoint) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); - - Param->MarkChanged(true); - } - - } - else - { - TArray &Points = Param->CachedPoints; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (Point->Position != MainPoint->Position) - { - Point->Position = MainPoint->Position; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->PositionParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Value != MainPoint->Value) - { - Point->Value = MainPoint->Value; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->ValueParentParm) - Point->ValueParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Interpolation != MainPoint->Interpolation) - { - Point->Interpolation = MainPoint->Interpolation; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->InterpolationParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - PointIdx += 1; - } - - // There are more points in MainPoints array - for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) - { - UHoudiniParameterRampFloatPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; - - if (!NextMainPoint) - continue; - - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampFloatPoint::StaticClass()); - - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = NextMainPoint->GetPosition(); - NewCachedPoint->Value = NextMainPoint->GetValue(); - NewCachedPoint->Interpolation = NextMainPoint->GetInterpolation(); - - Points.Add(NewCachedPoint); - - Param->bCaching = true; - } - - // there are more points in Points array - for (int32 PointsLeftIdx = PointIdx; PointIdx < Points.Num(); ++PointIdx) - { - Points.Pop(); - Param->bCaching = true; - } - } - -} - - -void -FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters) -{ - if (ColorRampParameters.Num() < 1) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0]; - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) - { - UHoudiniParameterRampColor* NextColorRampParam = ColorRampParameters[Idx]; - - if (!NextColorRampParam) - continue; - - FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParam); - } -} - -void -FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters) -{ - if (ColorRampParameters.Num() < 1) - return; - - if (!ColorRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) - { - if (!ColorRampParameters[Idx].IsValid()) - continue; - - UHoudiniParameterRampColor* NextColorRampParameter = ColorRampParameters[Idx].Get(); - - if (!NextColorRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParameter); - - } - -} - -void -FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParam) -{ - if (!Param || !MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) - return; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Use Synced points if the MainParam is on auto update mode - // Use Cached points if the Mainparam is on manual update mode - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - if (Param->IsAutoUpdate() && bCookingEnabled) - { - TArray & Points = Param->Points; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (MainPoint->GetPosition() != Point->GetPosition()) - { - if (Point->PositionParentParm) - { - Point->SetPosition(MainPoint->GetPosition()); - Point->PositionParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetValue() != Point->GetValue()) - { - if (Point->ValueParentParm) - { - Point->SetValue(MainPoint->GetValue()); - Point->ValueParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetInterpolation() != Point->GetInterpolation()) - { - if (Point->InterpolationParentParm) - { - Point->SetInterpolation(MainPoint->GetInterpolation()); - Point->InterpolationParentParm->MarkChanged(true); - } - } - - PointIdx += 1; - - } - - int32 PointInsertIdx = PointIdx; - int32 PointDeleteIdx = PointIdx; - - // skip the pending modification events - for (auto & Event : Param->ModificationEvents) - { - if (!Event) - continue; - - if (Event->IsInsertEvent()) - PointInsertIdx += 1; - - if (Event->IsDeleteEvent()) - PointDeleteIdx += 1; - } - - // There are more points in MainPoints array - for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) - { - UHoudiniParameterRampColorPoint*& NextMainPoint = MainPoints[PointInsertIdx]; - - if (!NextMainPoint) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(Param, - NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); - - Param->MarkChanged(true); - } - - // There are more points in Points array - for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) - { - UHoudiniParameterRampColorPoint*& NextPoint = Points[PointDeleteIdx]; - - if (!NextPoint) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); - - Param->MarkChanged(true); - } - } - else - { - TArray &Points = Param->CachedPoints; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (Point->Position != MainPoint->Position) - { - Point->Position = MainPoint->Position; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->PositionParentParm) - Point->PositionParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Value != MainPoint->Value) - { - Point->Value = MainPoint->Value; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->ValueParentParm) - Point->ValueParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Interpolation != MainPoint->Interpolation) - { - Point->Interpolation = MainPoint->Interpolation; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->InterpolationParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - PointIdx += 1; - } - - // There are more points in Main Points array. - for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) - { - UHoudiniParameterRampColorPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; - - if (!NextMainPoint) - continue; - - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampColorPoint::StaticClass()); - - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = NextMainPoint->Position; - NewCachedPoint->Value = NextMainPoint->Value; - NewCachedPoint->Interpolation = NextMainPoint->Interpolation; - - Points.Add(NewCachedPoint); - - Param->bCaching = true; - } - - // There are more points in Points array - for (int32 PointsleftIdx = PointIdx; PointIdx < MainPoints.Num(); ++PointsleftIdx) - { - Points.Pop(); - - Param->bCaching = true; - } - } -} - -// Check recussively if a parameter hits the end of a visible tabs -void -FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - // When the paramId is invalid, the directory won't parse. - // So simply return the function - if (InParam->GetParmId() < 0) - return; - - // Do not end the tab if this param is a non empty parent type, leave it to its children - EHoudiniParameterType ParmType = InParam->GetParameterType(); - if ((ParmType == EHoudiniParameterType::FolderList || - ParmType == EHoudiniParameterType::Folder) && InParam->GetTupleSize() > 0) - return; - - if (ParmType == EHoudiniParameterType::MultiParm) - { - UHoudiniParameterMultiParm * InMultiParm = Cast(InParam); - if (!InMultiParm) - return; - - if (InMultiParm->MultiParmInstanceCount * InMultiParm->MultiParmInstanceLength > 0) - return; - } - - int32 ParentParamId = InParam->GetParentParmId(); - UHoudiniParameter* CurParam = InParam; - - while (AllFoldersAndFolderLists.Contains(ParentParamId) || AllMultiParms.Contains(ParentParamId)) - { - // The parent is a multiparm - if (AllMultiParms.Contains(ParentParamId)) - { - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentParamId]; - if (!ParentMultiParm || ParentMultiParm->IsPendingKill()) - return; - - if (ParentMultiParm->MultiParmInstanceCount * ParentMultiParm->MultiParmInstanceLength - 1 == CurParam->GetChildIndex()) - { - ParentParamId = ParentMultiParm->GetParentParmId(); - CurParam = ParentMultiParm; - - continue; - } - else - { - // return directly if the parameter is not the last child param of the multiparm - return; - } - } - // The parent is a folder or folderlist - else - { - UHoudiniParameter* ParentFolderParam = AllFoldersAndFolderLists[ParentParamId]; - CurParam = ParentFolderParam; - - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) - return; - - // The parent is a folder - if (ParentFolderParam->GetParameterType() == EHoudiniParameterType::Folder) - { - ParentParamId = ParentFolderParam->GetParentParmId(); - - continue; - } - // The parent is a folderlist - else - { - UHoudiniParameterFolderList* ParentFolderList = Cast(ParentFolderParam); - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) - return; - - if (ParentFolderList->IsTabMenu() && ParentFolderList->IsTabsShown() && ParentFolderList->IsTabParseFinished() && DividerLinePositions.Num() > 0) - { - if (!CurrentTabEndingRow) - CreateTabEndingRow(HouParameterCategory); - - if (CurrentTabEndingRow) - { - CurrentTabEndingRow->EndingDividerLinePositions.Add(DividerLinePositions.Top()); - CurrentTabEndingRow->DividerLinePositions.Pop(); - } - - DividerLinePositions.Pop(); - - ParentParamId = ParentFolderList->GetParentParmId(); - } - else - { - return; - } - - } - - } - } -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterDetails.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniInput.h" +#include "HoudiniAsset.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "SNewFilePathPicker.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "IDetailGroup.h" +#include "IDetailCustomization.h" +#include "PropertyCustomizationHelpers.h" +#include "DetailWidgetRow.h" +#include "Math/UnitConversion.h" +#include "ScopedTransaction.h" +#include "EditorDirectories.h" + +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "Widgets/Colors/SColorPicker.h" +#include "Widgets/Views/SExpanderArrow.h" +#include "Widgets/Layout/SExpandableArea.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Input/NumericUnitTypeInterface.inl" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SSplitter.h" +#include "SCurveEditorView.h" +#include "SAssetDropTarget.h" +#include "AssetThumbnail.h" + +#include "HoudiniInputDetails.h" + +#include "Framework/SlateDelegates.h" +#include "Templates/SharedPointer.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +int32 +SCustomizedButton::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + TSharedPtr Content = GetContent(); + + // 0. Initialize Line Buffer. + TArray Line; + Line.SetNumUninitialized(2); + + // Initialize Color buffer. + FLinearColor Color = FLinearColor::White; + + // 1. Draw the radio button. + if (bIsRadioButton) + { + // Construct the radio button circles exactly once, + // All radio buttons share the same circles then + if (FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER || + FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER) + { + ConstructRadioButtonCircles(); + } + + DrawRadioButton(AllottedGeometry, OutDrawElements, LayerId, bChosen); + } + + // 2. Draw background color (if selected) + if (bChosen) + { + Line[0].X = AllottedGeometry.Size.X - AllottedGeometry.Size.Y / 2.0f + 2.5f; + Line[0].Y = Content->GetDesiredSize().Y / 2.0f; + Line[1].X = AllottedGeometry.Size.Y / 2.0f - 2.5f; + Line[1].Y = Content->GetDesiredSize().Y / 2.0f; + + Color = FLinearColor::White; + Color.A = bIsRadioButton ? 0.05 : 0.1; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, AllottedGeometry.Size.Y); + } + + // 3. Drawing square around the text + { + // Switch the point order for each line to save few value assignment cycles + Line[0].X = 0.0f; + Line[0].Y = 0.0f; + Line[1].X = 0.0f; + Line[1].Y = Content->GetDesiredSize().Y; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); + + //Line[0].X = 0.0f; + //Line[0].Y = Content->GetDesiredSize().Y; + Line[0].X = AllottedGeometry.Size.X; + Line[0].Y = Content->GetDesiredSize().Y; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, bChosen ? FLinearColor::Gray : FLinearColor::Black, true, 1.0f); + + //Line[0].X = AllottedGeometry.Size.X; + //Line[0].Y = Content->GetDesiredSize().Y; + Line[1].X = AllottedGeometry.Size.X; + Line[1].Y = 0.0f; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); /* draw gray bottom line if this tab is selected, black otherwise*/ + + //Line[0].X = AllottedGeometry.Size.X; + //Line[0].Y = 0.0f; + Line[0].X = 0.0f; + Line[0].Y = 0.0f; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); + } + + // 4. Draw child widget + Content->Paint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + return LayerId; +}; + +void +SCustomizedButton::ConstructRadioButtonCircles() const +{ + TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); + TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); + OuterPoints.Empty(); + InnerPoints.Empty(); + + OuterPoints.SetNumZeroed(HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER); + InnerPoints.SetNumZeroed(8); + + // Construct outer circle + int32 CurDegree = 0; + int32 DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; + + for (int32 Idx = 0; Idx < HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; ++Idx) + { + OuterPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); + OuterPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); + + CurDegree += DegStep; + } + + // Construct inner circle + CurDegree = 0; + DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER; + for (int32 Idx = 0; Idx < 8; ++Idx) + { + InnerPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); + InnerPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); + + CurDegree += DegStep; + } +} + +void +SCustomizedButton::DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const +{ + TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); + TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); + if (OuterPoints.Num() <= 1 || InnerPoints.Num() <= 1) + return; + + FLinearColor ColorNonSelected = FLinearColor::White; + FLinearColor ColorSelected = FLinearColor::Yellow; + + // initialize line buffer + TArray Line; + Line.SetNumZeroed(2); + bool alternator = false; + + // Draw outer circle + Line[0] = OuterPoints.Last(); + for (int32 Idx = 0; Idx < OuterPoints.Num(); ++Idx) + { + // alternate the points order each time to some some assignment cycles + if (alternator) + { + Line[0].X = OuterPoints[Idx].X; + Line[0].Y = OuterPoints[Idx].Y; + } + else + { + Line[1].X = OuterPoints[Idx].X; + Line[1].Y = OuterPoints[Idx].Y; + } + + alternator = !alternator; + + // Draw a line segment + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, ColorNonSelected, true, 1.0f); + } + + // Draw inner circle + alternator = false; + Line[0] = InnerPoints.Last(); + for (int32 Idx = 0; Idx < InnerPoints.Num(); ++Idx) + { + // alternate the points order each time to some some assignment cycles + if (alternator) + { + Line[0].X = InnerPoints[Idx].X; + Line[0].Y = InnerPoints[Idx].Y; + } + else + { + Line[1].X = InnerPoints[Idx].X; + Line[1].Y = InnerPoints[Idx].Y; + } + + alternator = !alternator; + + // Draw a line segment + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, bSelected ? ColorSelected : ColorNonSelected, true, 3.0f); + } +} + +void +SCustomizedBox::SetHoudiniParameter(TArray& InParams) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + + bool bIsMultiparmInstanceHeader = MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 0; + + switch (MainParam->GetParameterType()) + { + case EHoudiniParameterType::Button: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON; + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP; + } + break; + + case EHoudiniParameterType::Color: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR; + } + break; + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor * ColorRampParameter = Cast(MainParam); + if (!ColorRampParameter || ColorRampParameter->IsPendingKill()) + return; + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP; + if (ColorRampParameter->CachedPoints.Num() > 0) + MarginHeight = MarginHeight + HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE * (float)(ColorRampParameter->CachedPoints.Num() - 1); + } + break; + + case EHoudiniParameterType::File: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE; + } + break; + + case EHoudiniParameterType::FileDir: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR; + } + break; + + case EHoudiniParameterType::FileGeo: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO; + } + break; + + case EHoudiniParameterType::FileImage: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE; + } + break; + + case EHoudiniParameterType::Float: + { + if (MainParam->GetTupleSize() == 3) + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3; + } + else + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT + + (MainParam->GetTupleSize() - 1)* HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE; + } + } + } + break; + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat * FloatRampParameter = Cast(MainParam); + if (!FloatRampParameter || FloatRampParameter->IsPendingKill()) + return; + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP; + + if (FloatRampParameter->CachedPoints.Num() > 0) + MarginHeight = MarginHeight + Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE * (float)(FloatRampParameter->CachedPoints.Num() - 1); + } + break; + + case EHoudiniParameterType::Folder: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER; + } + break; + + case EHoudiniParameterType::FolderList: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST; + } + break; + + case EHoudiniParameterType::Input: + { + UHoudiniParameterOperatorPath* InputParam = Cast(MainParam); + + if (!InputParam || InputParam->IsPendingKill() || !InputParam->HoudiniInput.IsValid()) + break; + + UHoudiniInput* Input = InputParam->HoudiniInput.Get(); + + if (!Input || Input->IsPendingKill()) + break; + + + if (bIsMultiparmInstanceHeader) + { + switch (Input->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + int32 ExpandedTransformUIs = 0; + for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) + { + if (Input->IsTransformUIExpanded(Idx)) + ExpandedTransformUIs += 1; + } + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER + + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Curve: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Asset: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Landscape: + { + if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::World: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Skeletal: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER; + } + break; + default: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER; + break; + } + } + else + { + switch (Input->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + int32 ExpandedTransformUIs = 0; + for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) + { + if (Input->IsTransformUIExpanded(Idx)) + ExpandedTransformUIs += 1; + } + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE + + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM; + } + break; + case EHoudiniInputType::Curve: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE; + } + break; + case EHoudiniInputType::Asset: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET; + } + break; + case EHoudiniInputType::Landscape: + { + if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH; + } + break; + case EHoudiniInputType::World: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD; + } + break; + case EHoudiniInputType::Skeletal: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL; + } + break; + default: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT; + break; + + } + } + } + break; + + case EHoudiniParameterType::Int: + { + if (MainParam->GetTupleSize() == 3) + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3; + } + else + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE; + } + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE; + } + break; + + case EHoudiniParameterType::Label: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL; + } + break; + + case EHoudiniParameterType::MultiParm: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM; + } + break; + + case EHoudiniParameterType::Separator: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR; + bIsSeparator = true; + } + break; + + case EHoudiniParameterType::String: + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE; + } + } + break; + + case EHoudiniParameterType::StringAssetRef: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF; + } + break; + + case EHoudiniParameterType::StringChoice: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE; + } + break; + + case EHoudiniParameterType::Toggle: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE; + } + break; + + case EHoudiniParameterType::Invalid: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID; + break; + + default: + MarginHeight = 0.0f; + break; + } +} + +float +SCustomizedBox::AddIndentation(UHoudiniParameter* InParam, + TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists) +{ + if (!InParam || InParam->IsPendingKill()) + return 0.0f; + + bool bIsMainParmSimpleFolder = false; + // Get if this Parameter is a simple / collapsible folder + if (InParam->GetParameterType() == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParm = Cast(InParam); + if (FolderParm) + bIsMainParmSimpleFolder = !FolderParm->IsTab(); + } + + int32 ParentId = InParam->GetParentParmId(); + UHoudiniParameter* CurParm = InParam; + float Indentation = 0.0f; + + while (ParentId >= 0) + { + UHoudiniParameter* ParentFolder = nullptr; + UHoudiniParameterMultiParm* ParentMultiParm = nullptr; + + if (InAllFoldersAndFolderLists.Contains(ParentId)) + ParentFolder = InAllFoldersAndFolderLists[ParentId]; + + if (InAllMultiParms.Contains(ParentId)) + ParentMultiParm = InAllMultiParms[ParentId]; + + // The parent is a folder, add one unit of indentation + if (ParentFolder) + { + // Update the parent parm id + ParentId = ParentFolder->GetParentParmId(); + + if (ParentFolder->GetParameterType() == EHoudiniParameterType::FolderList) + continue; + + UHoudiniParameterFolder* Folder = Cast(ParentFolder); + + if (!Folder) + continue; + + // update the current parm, find the parent of new cur param in the next round + CurParm = Folder; + Indentation += 1.0f; + } + // The parent is a multiparm + else if (ParentMultiParm) + { + // Update the parent parm id + ParentId = ParentMultiParm->GetParentParmId(); + + if (CurParm->GetChildIndex() == 0) + { + Indentation += 0.0f; + } + else + { + Indentation += 2.0f; + } + + // update the current parm, find the parent of new cur param in the next round + CurParm = ParentMultiParm; + } + else + { + // no folder/multiparm parent, end the loop + ParentId = -1; + } + } + + + float IndentationWidth = INDENTATION_UNIT_WIDTH * Indentation; + + // Add a base indentation to non simple/collapsible param + // Since it needs more space to offset the arrow width + if (!bIsMainParmSimpleFolder) + IndentationWidth += NON_FOLDER_OFFSET_WIDTH; + + this->AddSlot().AutoWidth() + [ + SNew(SBox).WidthOverride(IndentationWidth) + ]; + + + return IndentationWidth; +}; + +int32 +SCustomizedBox::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + + SHorizontalBox::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + // Initialize line buffer + TArray Line; + Line.SetNumZeroed(2); + // Initialize color buffer + FLinearColor Color = FLinearColor::White; + Color.A = 0.3; + + // draw the bottom line if this row is the tab folder list + if (bIsTabFolderListRow) + { + // Get the start position of the tabs bottom line (right bottom pt of the right most child widget) + float VerticalLineStartPosX = 0.0f; + float VerticalLineStartPosY = 0.0f; + float BottomLineStartPosX = 0.0f; + float BottomLineStartPosY = -1.0f; + + for (int32 Idx = 0; Idx < Children.Num(); ++Idx) + { + TSharedPtr CurChild = Children.GetChildAt(Idx); + if (!CurChild.IsValid()) + continue; + + if (Idx == 0) + { + VerticalLineStartPosX = CurChild->GetDesiredSize().X; + VerticalLineStartPosY = CurChild->GetDesiredSize().Y; + } + + BottomLineStartPosX += CurChild->GetDesiredSize().X; + + if (BottomLineStartPosY < 0.0f) + BottomLineStartPosY= CurChild->GetDesiredSize().Y; + } + + // Draw bottom line + Line[0].X = BottomLineStartPosX; + Line[0].Y = BottomLineStartPosY; + Line[1].X = AllottedGeometry.Size.X; + Line[1].Y = BottomLineStartPosY; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + + // Draw divider lines + { + Line[0].Y = -MarginHeight; + Line[1].Y = AllottedGeometry.Size.Y + MarginHeight; + + int32 NumOfLinesToDraw = bIsTabFolderListRow ? DividerLinePositions.Num() - 1 : DividerLinePositions.Num(); + for (int32 Idx = 0; Idx < NumOfLinesToDraw; ++Idx) + { + const float& CurDivider = DividerLinePositions[Idx]; + Line[0].X = CurDivider; + Line[1].X = CurDivider; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + + // Draw the last inner most divider line differently when this the tabs' row. + if (bIsTabFolderListRow && DividerLinePositions.Num() > 0) + { + const float& TabDivider = DividerLinePositions.Last(); + Line[0].X = TabDivider; + Line[1].X = TabDivider; + Line[0].Y = 0.f; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + } + + // Draw tab ending lines + { + float YPos = 0.0f; + + for (const float & CurEndingDivider : EndingDividerLinePositions) + { + // Draw cur ending line (vertical) + + Line[0].X = CurEndingDivider; + Line[0].Y = -2.3f; + Line[1].X = CurEndingDivider; + Line[1].Y = YPos; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + + // Draw cur ending line (horizontal) + + // Line[0].X = CurEndingDivider; + Line[0].Y = YPos; + Line[1].X = AllottedGeometry.Size.X; + // Line[1].Y = YPos; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + + YPos += 2.0f; + } + } + + // Draw the separator line if this is the row of a separator parameter + { + if (bIsSeparator) + { + Line[0].X = 25.f; + if (DividerLinePositions.Num() > 0) + Line[0].X += DividerLinePositions.Last(); + + Line[0].Y = AllottedGeometry.Size.Y / 2.f; + Line[1].X = AllottedGeometry.Size.X - 20.f; + Line[1].Y = Line[0].Y; + + Color.A = 0.7; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.5f); + } + } + + return LayerId; +}; + +void +SHoudiniFloatRampCurveEditor::Construct(const FArguments & InArgs) +{ + SCurveEditor::Construct(SCurveEditor::FArguments() + .ViewMinInput(InArgs._ViewMinInput) + .ViewMaxInput(InArgs._ViewMaxInput) + .ViewMinOutput(InArgs._ViewMinOutput) + .ViewMaxOutput(InArgs._ViewMaxOutput) + .XAxisName(InArgs._XAxisName) + .YAxisName(InArgs._YAxisName) + .HideUI(InArgs._HideUI) + .DrawCurve(InArgs._DrawCurve) + .TimelineLength(InArgs._TimelineLength) + .AllowZoomOutput(InArgs._AllowZoomOutput) + .ShowInputGridNumbers(InArgs._ShowInputGridNumbers) + .ShowOutputGridNumbers(InArgs._ShowOutputGridNumbers) + .ShowZoomButtons(InArgs._ShowZoomButtons) + .ZoomToFitHorizontal(InArgs._ZoomToFitHorizontal) + .ZoomToFitVertical(InArgs._ZoomToFitVertical) + ); + + + UCurveEditorSettings * CurveEditorSettings = GetSettings(); + if (CurveEditorSettings) + { + CurveEditorSettings->SetTangentVisibility(ECurveEditorTangentVisibility::NoTangents); + } +} + +void +SHoudiniColorRampCurveEditor::Construct(const FArguments & InArgs) +{ + SColorGradientEditor::Construct(SColorGradientEditor::FArguments() + .ViewMinInput(InArgs._ViewMinInput) + .ViewMaxInput(InArgs._ViewMaxInput) + ); +} + + +FReply +SHoudiniFloatRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = SCurveEditor::OnMouseButtonUp(MyGeometry, MouseEvent); + + if (!HoudiniFloatRampCurve.IsValid()) + return Reply; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + FRichCurve& FloatCurve = HoudiniFloatRampCurve.Get()->FloatCurve; + + TArray>& FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; + + if (FloatRampParameters.Num() < 1) + return Reply; + + if (!FloatRampParameters[0].IsValid()) + return Reply; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + // Do not allow modification when the parent HDA of the main param is being cooked. + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points of the main float ramp param to other parameters + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + // Modification is based on the main parameter, use synced points if the main param is on auto update mode, use cached points otherwise. + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + // On mouse button up handler handles point modification only + if (FloatCurve.GetNumKeys() != NumMainPoints) + return Reply; + + bool bNeedToRefreshEditor= false; + + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float& CurvePosition = FloatCurve.Keys[Idx].Time; + float& CurveValue = FloatCurve.Keys[Idx].Value; + + // This point is modified + if (MainPoint->GetPosition() != CurvePosition || MainPoint->GetValue() != CurveValue) + { + + // The editor needs refresh only if the main parameter is on manual mode, and has been modified + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedToRefreshEditor = true; + + // Iterate through the float ramp parameter of all selected HDAs. + for (auto & NextRampFloat : FloatRampParameters) + { + if (!NextRampFloat.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedRampFloat = NextRampFloat.Get(); + + if (!SelectedRampFloat) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedRampFloat)) + continue; + + if (SelectedRampFloat->IsAutoUpdate() && bCookingEnabled) + { + // The selected float ramp parameter is on auto update mode, use its synced points. + TArray &SelectedRampPoints = SelectedRampFloat->Points; + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // Synced points in the selected ramp is more than or the same number as that in the main parameter, + // modify the position and value of the synced point and mark them as changed. + + UHoudiniParameterRampFloatPoint*& ModifiedPoint = SelectedRampPoints[Idx]; + + if (!ModifiedPoint) + continue; + + if (ModifiedPoint->GetPosition() != CurvePosition && ModifiedPoint->PositionParentParm) + { + ModifiedPoint->SetPosition(CurvePosition); + ModifiedPoint->PositionParentParm->MarkChanged(true); + } + + if (ModifiedPoint->GetValue() != CurveValue && ModifiedPoint->ValueParentParm) + { + ModifiedPoint->SetValue(CurveValue); + ModifiedPoint->ValueParentParm->MarkChanged(true); + } + } + else + { + // Synced points in the selected ramp is less than that in the main parameter + // Since we have pushed the points of the main param to all of the selected ramps, + // We need to modify the insert event. + + int32 IndexInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedRampFloat->ModificationEvents.IsValidIndex(Idx)) + { + UHoudiniParameterRampModificationEvent*& ModEvent = SelectedRampFloat->ModificationEvents[Idx]; + if (!ModEvent) + continue; + + if (ModEvent->InsertPosition != CurvePosition) + ModEvent->InsertPosition = CurvePosition; + + if (ModEvent->InsertFloat != CurveValue) + ModEvent->InsertFloat = CurveValue; + } + + } + } + else + { + // The selected float ramp is on manual update mode, use the cached points. + TArray &FloatRampCachedPoints = SelectedRampFloat->CachedPoints; + + // Since we have pushed the points in main param to all the selected float ramp, + // we need to modify the corresponding cached point in the selected float ramp. + + if (FloatRampCachedPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampFloatPoint*& ModifiedCachedPoint = FloatRampCachedPoints[Idx]; + + if (!ModifiedCachedPoint) + continue; + + if (ModifiedCachedPoint->Position != CurvePosition) + { + ModifiedCachedPoint->Position = CurvePosition; + SelectedRampFloat->bCaching = true; + if (!bCookingEnabled) + { + //SelectedRampFloat->MarkChanged(true); + if (ModifiedCachedPoint->PositionParentParm) + ModifiedCachedPoint->PositionParentParm->MarkChanged(true); + } + } + + if (ModifiedCachedPoint->Value != CurveValue) + { + ModifiedCachedPoint->Value = CurveValue; + SelectedRampFloat->bCaching = true; + if (!bCookingEnabled) + { + //SelectedRampFloat->MarkChanged(true); + if (ModifiedCachedPoint->ValueParentParm) + ModifiedCachedPoint->ValueParentParm->MarkChanged(true); + } + } + } + } + } + } + } + + + if (bNeedToRefreshEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } + + return Reply; +} + +FReply +SHoudiniFloatRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + FReply Reply = SCurveEditor::OnKeyDown(MyGeometry, InKeyEvent); + + if (InKeyEvent.GetKey().ToString() != FString("Enter")) + return Reply; + + if (!HoudiniFloatRampCurve.IsValid() || !HoudiniFloatRampCurve.Get()) + return Reply; + + TArray> FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; + + if (FloatRampParameters.Num() < 1) + return Reply; + + if (!FloatRampParameters[0].IsValid()) + return Reply; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Do nothing if the main param is on auto update mode + if (MainParam->IsAutoUpdate() && bCookingEnabled) + return Reply; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points in the main float ramp to the float ramp parameters in all selected HDAs. + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + for (auto& NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + if (!SelectedFloatRamp) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not sync the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + // Sync the cached points if the selected float ramp parameter is on manual update mode + //FHoudiniParameterDetails::SyncCachedFloatRampPoints(SelectedFloatRamp); + SelectedFloatRamp->SyncCachedPoints(); + } + + return Reply; +} + +void +UHoudiniFloatRampCurve::OnCurveChanged(const TArray& ChangedCurveEditInfos) +{ + Super::OnCurveChanged(ChangedCurveEditInfos); + + if (FloatRampParameters.Num() < 1) + return; + + if (!FloatRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return; + + // Do not allow modification when the parent HDA of the main param is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + // Push all the points of the Main parameter to other parameters + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode, otherwise use its cached points. + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + bool bNeedUpdateEditor = false; + + // OnCurveChanged handler handles point delete and insertion only + + // A point is deleted. + if (FloatCurve.GetNumKeys() < NumMainPoints) + { + // Find the index of the deleted point + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float CurPointPosition = MainPoint->GetPosition(); + float CurCurvePosition = -1.0f; + + if (FloatCurve.Keys.IsValidIndex(Idx)) + CurCurvePosition = FloatCurve.Keys[Idx].Time; + + // Delete the point at Idx + if (CurCurvePosition != CurPointPosition) + { + // Iterate through all the float ramp parameter in all the selected HDAs + for (auto & NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + if (!SelectedFloatRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + TArray & SelectedRampPoints = SelectedFloatRamp->Points; + + // The selected float ramp is on auto update mode: + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // If the number of synced points of the selected float ramp is greater or equal to the number of points of that in the main param, + // Create a Houdini engine manager event to delete the point at Idx of the selected float ramp; + + UHoudiniParameterRampFloatPoint* PointToDelete = SelectedRampPoints[Idx]; + + if (!PointToDelete) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(SelectedFloatRamp, PointToDelete->InstanceIndex); + SelectedFloatRamp->MarkChanged(true); + } + else + { + // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, + // delete the corresponding inserting event. + + int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + SelectedFloatRamp->ModificationEvents.RemoveAt(IdxInEventsArray); + } + } + else + { + // The selected float ramp is on manual update mode: + // Since we have pushed all the points in main param to the cached points of the selected float ramp, + // remove the corresponding points from the cached points array. + + if (SelectedFloatRamp->CachedPoints.IsValidIndex(Idx)) + { + SelectedFloatRamp->CachedPoints.RemoveAt(Idx); + SelectedFloatRamp->bCaching = true; + } + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point is inserted + else if (FloatCurve.GetNumKeys() > NumMainPoints) + { + // Find the index of the inserted point + for (int32 Idx = 0; Idx < FloatCurve.GetNumKeys(); ++Idx) + { + + float CurPointPosition = -1.0f; + float CurCurvePosition = FloatCurve.Keys[Idx].Time; + + if (MainPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + CurPointPosition = MainPoint->GetPosition(); + } + + // Insert instance at Idx + if (CurPointPosition != CurCurvePosition) + { + // Iterate through the float ramp parameter of all selected HDAs. + for (auto & NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + // If the selected float ramp is on auto update mode: + // Since we have pushed all the points of main parameter to the selected, + // create a Houdini engine manager event to insert a point. + + FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent( + SelectedFloatRamp, CurCurvePosition, FloatCurve.Keys[Idx].Value, EHoudiniRampInterpolationType::LINEAR); + + SelectedFloatRamp->MarkChanged(true); + } + else + { + // If the selected float ramp is on manual update mode: + // push a new point to the cached points array + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(SelectedFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = CurCurvePosition; + NewCachedPoint->Value = FloatCurve.Keys[Idx].Value; + NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; + + if (Idx >= SelectedFloatRamp->CachedPoints.Num()) + SelectedFloatRamp->CachedPoints.Add(NewCachedPoint); + else + SelectedFloatRamp->CachedPoints.Insert(NewCachedPoint, Idx); + + SelectedFloatRamp->bCaching = true; + + if (!bCookingEnabled) + SelectedFloatRamp->MarkChanged(true); + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + if (bNeedUpdateEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } + +} + + +FReply +SHoudiniColorRampCurveEditor::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = SColorGradientEditor::OnMouseButtonDown(MyGeometry, MouseEvent); + + if (HoudiniColorRampCurve.IsValid()) + { + UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); + if (Curve) + Curve->bEditing = true; + } + + return Reply; +} + +FReply +SHoudiniColorRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + + FReply Reply = SColorGradientEditor::OnMouseButtonUp(MyGeometry, MouseEvent); + + if (HoudiniColorRampCurve.IsValid()) + { + UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); + + if (Curve) + { + Curve->bEditing = false; + Curve->OnColorRampCurveChanged(true); + } + } + + return Reply; + +} + +FReply +SHoudiniColorRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + FReply Reply = SColorGradientEditor::OnKeyDown(MyGeometry, InKeyEvent); + + if (InKeyEvent.GetKey().ToString() != FString("Enter")) + return Reply; + + if (!HoudiniColorRampCurve.IsValid() || !HoudiniColorRampCurve.Get()) + return Reply; + + TArray> &ColorRampParameters = HoudiniColorRampCurve.Get()->ColorRampParameters; + + if (ColorRampParameters.Num() < 1) + return Reply; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + // Do nothing if the main param is on auto update mode + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + if (MainParam->IsAutoUpdate() && bCookingEnabled) + return Reply; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points in the main color ramp to the color ramp parameters in all selected HDAs. + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); + + for (auto& NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not sync the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + // Sync the cached points if the selected color ramp is on manual update mode + FHoudiniParameterDetails::SyncCachedColorRampPoints(SelectedColorRamp); + } + + return Reply; +} + +void +UHoudiniColorRampCurve::OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) +{ + Super::OnCurveChanged(ChangedCurveEditInfos); + + OnColorRampCurveChanged(); +} + +void +UHoudiniColorRampCurve::OnColorRampCurveChanged(bool bModificationOnly) +{ + // Array is always true in this case + // if (!FloatCurves) + // return; + + if (ColorRampParameters.Num() < 1) + return; + + if (!ColorRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return; + + // Do not allow modification when the parent HDA of the main param is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + // Push all the points of the main parameter to other parameters + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); + + // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode,otherwise use its cached points. + bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + bool bNeedUpdateEditor = false; + + // OnCurveChanged handler of color ramp curve editor handles point delete, insert and color change + + // A point is deleted + if (FloatCurves->GetNumKeys() < NumMainPoints) + { + if (bModificationOnly) + return; + + // Find the index of the deleted point + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float CurPointPosition = MainPoint->GetPosition(); + float CurCurvePosition = -1.0f; + + if (FloatCurves[0].Keys.IsValidIndex(Idx)) + CurCurvePosition = FloatCurves[0].Keys[Idx].Time; + + // Delete the point at Idx + if (CurCurvePosition != CurPointPosition) + { + // Iterate through all the color ramp parameter in all the selected HDAs + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + TArray & SelectedRampPoints = SelectedColorRamp->Points; + + // The selected color ramp is on auto update mode: + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // If the number of synced points of the selected color ramp is greater or equal to the number of points of that in the main param, + // create a Houdini engine manager event to delete the point at Idx of the selected float ramp; + + UHoudiniParameterRampColorPoint* PointToDelete = SelectedRampPoints[Idx]; + + if (!PointToDelete) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(SelectedColorRamp, PointToDelete->InstanceIndex); + SelectedColorRamp->MarkChanged(true); + } + else + { + // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, + // delete the corresponding inserting event. + + int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + SelectedColorRamp->ModificationEvents.RemoveAt(IdxInEventsArray); + } + } + else + { + // The selected color ramp is on manual update mode: + // Since we have pushed all the points in main param to the cached points of the selected float ramp, + // remove the corresponding points from the cached points array + if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) + { + SelectedColorRamp->CachedPoints.RemoveAt(Idx); + SelectedColorRamp->bCaching = true; + } + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point is inserted + else if (FloatCurves[0].GetNumKeys() > NumMainPoints) + { + + if (bModificationOnly) + return; + + // Find the index of the inserted point + for (int32 Idx = 0; Idx < FloatCurves[0].GetNumKeys(); ++Idx) + { + + float CurPointPosition = -1.0f; + float CurCurvePosition = FloatCurves[0].Keys[Idx].Time; + + if (MainPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + CurPointPosition = MainPoint->GetPosition(); + } + + // Insert a point at Idx + if (CurPointPosition != CurCurvePosition) + { + // Get the interpolation value of inserted color point + + FLinearColor ColorPrev = FLinearColor::Black; + FLinearColor ColorNext = FLinearColor::White; + float PositionPrev = 0.0f; + float PositionNext = 1.0f; + + if (MainParam->IsAutoUpdate() && bCookingEnabled) + { + // Try to get its previous point's color + if (MainParam->Points.IsValidIndex(Idx - 1)) + { + ColorPrev = MainParam->Points[Idx - 1]->GetValue(); + PositionPrev = MainParam->Points[Idx - 1]->GetPosition(); + } + + // Try to get its next point's color + if (MainParam->Points.IsValidIndex(Idx)) + { + ColorNext = MainParam->Points[Idx]->GetValue(); + PositionNext = MainParam->Points[Idx]->GetPosition(); + } + } + else + { + // Try to get its previous point's color + if (MainParam->CachedPoints.IsValidIndex(Idx - 1)) + { + ColorPrev = MainParam->CachedPoints[Idx - 1]->GetValue(); + PositionPrev = MainParam->CachedPoints[Idx - 1]->GetPosition(); + } + + // Try to get its next point's color + if (MainParam->CachedPoints.IsValidIndex(Idx)) + { + ColorNext = MainParam->CachedPoints[Idx]->GetValue(); + PositionNext = MainParam->CachedPoints[Idx]->GetPosition(); + } + } + + float TotalWeight = FMath::Abs(PositionNext - PositionPrev); + float PrevWeight = FMath::Abs(CurCurvePosition - PositionPrev); + float NextWeight = FMath::Abs(PositionNext - CurCurvePosition); + + FLinearColor InsertedColor = ColorPrev * (PrevWeight / TotalWeight) + ColorNext * (NextWeight / TotalWeight); + + // Iterate through the color ramp parameter of all selected HDAs. + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + // If the selected color ramp is on auto update mode: + // Since we have pushed all the points of main parameter to the selected, + // create a Houdini engine manager event to insert a point. + + FHoudiniParameterDetails::CreateColorRampParameterInsertEvent( + SelectedColorRamp, CurCurvePosition, InsertedColor, EHoudiniRampInterpolationType::LINEAR); + + SelectedColorRamp->MarkChanged(true); + } + else + { + // If the selected color ramp is on manual update mode: + // Push a new point to the cached points array + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(SelectedColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = CurCurvePosition; + NewCachedPoint->Value = InsertedColor; + NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; + + if (Idx >= SelectedColorRamp->CachedPoints.Num()) + SelectedColorRamp->CachedPoints.Add(NewCachedPoint); + else + SelectedColorRamp->CachedPoints.Insert(NewCachedPoint, Idx); + + SelectedColorRamp->bCaching = true; + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!MainParam->IsAutoUpdate() && bCookingEnabled) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point's color is changed + else + { + if (bEditing) + return; + + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + // Only handle color change + { + float CurvePosition = FloatCurves[0].Keys[Idx].Time; + float PointPosition = MainPoint->GetPosition(); + + FLinearColor CurveColor = FLinearColor::Black; + FLinearColor PointColor = MainPoint->GetValue(); + + CurveColor.R = FloatCurves[0].Keys[Idx].Value; + CurveColor.G = FloatCurves[1].Keys[Idx].Value; + CurveColor.B = FloatCurves[2].Keys[Idx].Value; + + // Color is changed at Idx + if (CurveColor != PointColor || CurvePosition != PointPosition) + { + // Iterate through the all selected color ramp parameters + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + // The selected color ramp parameter is on auto update mode + + if (SelectedColorRamp->Points.IsValidIndex(Idx)) + { + // If the number of synced points in the selected color ramp is more or equal to that in the main parameter: + // Modify the corresponding synced point of the selected color ramp, and marked it as changed. + + UHoudiniParameterRampColorPoint* Point = SelectedColorRamp->Points[Idx]; + + if (!Point) + continue; + + if (Point->GetValue() != CurveColor && Point->ValueParentParm) + { + Point->SetValue(CurveColor); + Point->ValueParentParm->MarkChanged(true); + } + + if (Point->GetPosition() != CurvePosition && Point->PositionParentParm) + { + Point->SetPosition(CurvePosition); + Point->PositionParentParm->MarkChanged(true); + } + } + else + { + // If the number of synced points in the selected color ramp is less than that in the main parameter: + // Since we have push the points in the main parameter to all selected parameters, + // we need to modify the corresponding insert event. + + int32 IdxInEventsArray = Idx - SelectedColorRamp->Points.Num(); + + if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = SelectedColorRamp->ModificationEvents[IdxInEventsArray]; + + if (!Event) + continue; + + if (Event->InsertColor != CurveColor) + Event->InsertColor = CurveColor; + + if (Event->InsertPosition != CurvePosition) + Event->InsertPosition = CurvePosition; + } + } + } + else + { + // The selected color ramp is on manual update mode + // Since we have push the points in the main parameter to all selected parameters, + // modify the corresponding point in the cached points array of the selected color ramp. + if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampColorPoint* CachedPoint = SelectedColorRamp->CachedPoints[Idx]; + + if (!CachedPoint) + continue; + + if (CachedPoint->Value != CurveColor) + { + CachedPoint->Value = CurveColor; + bNeedUpdateEditor = true; + } + + if (CachedPoint->Position != CurvePosition) + { + CachedPoint->Position = CurvePosition; + SelectedColorRamp->bCaching = true; + bNeedUpdateEditor = true; + } + } + } + } + } + } + } + } + + + if (bNeedUpdateEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } +} + +template< class T > +bool FHoudiniParameterDetails::CastParameters( + TArray InParams, TArray& OutCastedParams ) +{ + for (auto CurrentParam : InParams) + { + T* CastedParam = Cast(CurrentParam); + if (CastedParam && !CastedParam->IsPendingKill()) + OutCastedParams.Add(CastedParam); + } + + return (OutCastedParams.Num() == InParams.Num()); +} + + +void +FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* InParam = InParams[0]; + if (!InParam || InParam->IsPendingKill()) + return; + + // The directory won't parse if parameter ids are -1 + // simply return + if (InParam->GetParmId() < 0) + return; + + // This parameter is a part of the last float ramp. + if (CurrentRampFloat) + { + CreateWidgetFloatRamp(HouParameterCategory, InParams); + return; + } + // This parameter is a part of the last float ramp. + if (CurrentRampColor) + { + CreateWidgetColorRamp(HouParameterCategory, InParams); + return; + } + + switch (InParam->GetParameterType()) + { + case EHoudiniParameterType::Float: + { + CreateWidgetFloat(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Int: + { + CreateWidgetInt(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::String: + { + CreateWidgetString(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::StringChoice: + { + CreateWidgetChoice(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Separator: + { + TArray SepParams; + if (CastParameters(InParams, SepParams)) + { + bool bEnabled = InParams.IsValidIndex(0) ? !SepParams[0]->IsDisabled() : true; + CreateWidgetSeparator(HouParameterCategory, InParams, bEnabled); + } + } + break; + + case EHoudiniParameterType::Color: + { + CreateWidgetColor(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Button: + { + CreateWidgetButton(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + CreateWidgetButtonStrip(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Label: + { + CreateWidgetLabel(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Toggle: + { + CreateWidgetToggle(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + CreateWidgetFile(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::FolderList: + { + CreateWidgetFolderList(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Folder: + { + CreateWidgetFolder(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::MultiParm: + { + CreateWidgetMultiParm(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::FloatRamp: + { + CreateWidgetFloatRamp(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::ColorRamp: + { + CreateWidgetColorRamp(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Input: + { + CreateWidgetOperatorPath(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Invalid: + { + HandleUnsupportedParmType(HouParameterCategory, InParams); + } + break; + + default: + { + HandleUnsupportedParmType(HouParameterCategory, InParams); + } + break; + } + + // Remove a divider lines recurrsively if current parameter hits the end of a tabs + RemoveTabDividers(HouParameterCategory, InParam); + +} + +void +FHoudiniParameterDetails::CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory) +{ + FDetailWidgetRow & Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()); + TSharedPtr TabEndingRow = SNew(SCustomizedBox); + + TabEndingRow->DividerLinePositions = DividerLinePositions; + + if (TabEndingRow.IsValid()) + CurrentTabEndingRow = TabEndingRow.Get(); + + Row.WholeRowWidget.Widget = TabEndingRow.ToSharedRef(); + Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniParameterDetails::CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam|| MainParam->IsPendingKill()) + return; + + if (!Row) + return; + + TSharedRef< SCustomizedBox > HorizontalBox = SNew(SCustomizedBox); + + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + + + if (MainParam->IsDirectChildOfMultiParm()) + { + FString ParameterLabelStr = MainParam->GetParameterLabel(); + + // If it is head of an multiparm instance + if (MainParam->GetChildIndex() == 0) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + } + ParameterLabelStr += TEXT(" (") + FString("") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + HorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + else + { + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(MainParam->GetParameterLabel()) : FText::GetEmpty(); + HorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + + Row->NameWidget.Widget = HorizontalBox; +} + +void +FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) +{ + if (!Row) + return; + + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + FString ParameterLabelStr = MainParam->GetParameterLabel(); + TSharedRef HorizontalBox = SNew(SCustomizedBox); + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + + TSharedPtr VerticalBox; + HorizontalBox->AddSlot() + [ + SAssignNew(VerticalBox, SVerticalBox) + ]; + + if (MainParam->IsDirectChildOfMultiParm()) + { + // If it is head of an multiparm instance + if (MainParam->GetChildIndex() == 0) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + } + + ParameterLabelStr += TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* RampParameter = Cast(MainParam); + if (RampParameter) + { + if (RampParameter->bCaching) + ParameterLabelStr += "*"; + } + } + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + else + { + // TODO: Refactor me...extend 'auto/manual update' to all parameters? (It only applies to color and float ramps for now.) + bool bParamNeedUpdate = false; + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* RampParameter = Cast(MainParam); + if (RampParameter) + bParamNeedUpdate = RampParameter->bCaching; + } + else if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat* RampParameter = Cast(MainParam); + if (RampParameter) + bParamNeedUpdate = RampParameter->bCaching; + } + + if (bParamNeedUpdate) + ParameterLabelStr += "*"; + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + + auto IsAutoUpdateChecked = [MainParam]() + { + if (!MainParam || MainParam->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainParam->IsAutoUpdate() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + auto OnAutoUpdateCheckBoxStateChanged = [MainParam, InParams, bCookingEnabled](ECheckBoxState NewState) + { + if (NewState == ECheckBoxState::Checked) + { + for (auto & NextSelectedParam : InParams) + { + if (!NextSelectedParam) + continue; + + if (NextSelectedParam->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not allow mode change when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) + continue; + + switch (MainParam->GetParameterType()) + { + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* ColorRampParameter = Cast(NextSelectedParam); + + if (!ColorRampParameter) + continue; + + // Do not sync the selected color ramp parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) + continue; + + // Sync the Cached curve points at update mode switch. + FHoudiniParameterDetails::SyncCachedColorRampPoints(ColorRampParameter); + } + break; + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* FloatRampParameter = Cast(NextSelectedParam); + + if (!FloatRampParameter) + continue; + + // Do not sync the selected float ramp parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) + continue; + + // Sync the Cached curve points at update mode switch. + //FHoudiniParameterDetails::SyncCachedFloatRampPoints(FloatRampParameter); + FloatRampParameter->SyncCachedPoints(); + } + break; + + default: + break; + } + + NextSelectedParam->SetAutoUpdate(true); + } + } + else + { + for (auto & NextSelectedParam : InParams) + { + if (!NextSelectedParam) + continue; + + if (!(NextSelectedParam->IsAutoUpdate() && bCookingEnabled)) + continue; + + // Do not allow mode change when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) + continue; + + NextSelectedParam->SetAutoUpdate(false); + } + } + }; + + // Auto update check box + TSharedPtr CheckBox; + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + [ + SAssignNew(CheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([OnAutoUpdateCheckBoxStateChanged](ECheckBoxState NewState) + { + OnAutoUpdateCheckBoxStateChanged(NewState); + }) + .IsChecked_Lambda([IsAutoUpdateChecked]() + { + return IsAutoUpdateChecked(); + }) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AutoUpdate", "Auto-update")) + .ToolTipText(LOCTEXT("AutoUpdateTip", "When enabled, this parameter will automatically update its value while editing. Turning this off will allow you to more easily update it, and the update can be pushed by checking the toggle again.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + if ((MainParam->GetParameterType() != EHoudiniParameterType::FloatRamp) && (MainParam->GetParameterType() != EHoudiniParameterType::ColorRamp)) + CheckBox->SetVisibility(EVisibility::Hidden); + + Row->NameWidget.Widget = HorizontalBox; +} + +FDetailWidgetRow* +FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount) +{ + if (InParams.Num() <= 0) + return nullptr; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return nullptr; + + // Created row for the current parameter (if there is not a row created, do not show the parameter). + FDetailWidgetRow* Row = nullptr; + + // Current parameter is in a multiparm instance (directly) + if (MainParam->IsDirectChildOfMultiParm()) + { + int32 ParentMultiParmId = MainParam->GetParentParmId(); + + // If this is a folder param, its folder list parent parm is the multiparm + if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) + { + if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) // This should not happen + return nullptr; + + UHoudiniParameterFolderList* ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); + if (!ParentFolderList || ParentFolderList->IsPendingKill()) + return nullptr; // This should not happen + + ParentMultiParmId = ParentFolderList->GetParentParmId(); + } + + if (!AllMultiParms.Contains(ParentMultiParmId)) // This should not happen normally + return nullptr; + + // Get the parent multiparm + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentMultiParmId]; + + // The parent multiparm is visible. + if (ParentMultiParm && ParentMultiParm->IsShown() && MainParam->ShouldDisplay()) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + + } + // This item is not a direct child of a multiparm. + else + { + bool bIsFolder = MainParam->GetParameterType() == EHoudiniParameterType::Folder; + + // If this parameter is a folder, its parent folder should be the second top of the stack + int32 NestedMinStackDepth = bIsFolder ? 1 : 0; + + // Current parameter is inside a folder. + if (FolderStack.Num() > NestedMinStackDepth) + { + // If the current parameter is a folder, we take the top second queue on the stack, since the top one represents itself. + // Otherwise take the top queue on the stack. + TArray & CurrentLayerFolderQueue = bIsFolder ? + FolderStack[FolderStack.Num() - 2] : FolderStack.Last(); + + if (CurrentLayerFolderQueue.Num() <= 0) // Error state + return nullptr; + + bool bParentFolderVisible = CurrentLayerFolderQueue[0]->IsContentShown(); + + bool bIsSelectedTabVisible = false; + + // If its parent folder is visible, display current parameter, + // Otherwise, just prune the stacks. + if (bParentFolderVisible) + { + int32 ParentFolderId = MainParam->GetParentParmId(); + + // If the current parameter is a folder, its parent is a folderlist. + // So we need to continue to get the parent of the folderlist. + if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) + { + if (AllFoldersAndFolderLists.Contains(ParentFolderId)) + ParentFolderId = AllFoldersAndFolderLists[ParentFolderId]->GetParentParmId(); + else + return nullptr; // error state + } + + UHoudiniParameterFolder* ParentFolder = nullptr; + + if (AllFoldersAndFolderLists.Contains(ParentFolderId)) + ParentFolder = Cast(AllFoldersAndFolderLists[ParentFolderId]); + + bool bShouldDisplayRow = MainParam->ShouldDisplay(); + + // This row should be shown if its parent folder is shown. + if (ParentFolder) + bShouldDisplayRow &= (ParentFolder->IsTab() && ParentFolder->IsChosen()) || (!ParentFolder->IsTab() && ParentFolder->IsExpanded()); + + if (bShouldDisplayRow) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + } + + // prune the stack finally + if (bDecreaseChildCount) + { + CurrentLayerFolderQueue[0]->GetChildCounter() -= 1; + PruneStack(); + } + } + // If this parameter is in the root dir, just create a row. + else + { + if (MainParam->ShouldDisplay()) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + } + } + + if (!MainParam->IsVisible()) + return nullptr; + + + if (Row) + CurrentTabEndingRow = nullptr; + + return Row; +} + +void +FHoudiniParameterDetails::HandleUnsupportedParmType(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + CreateNestedRow(HouParameterCategory, (TArray)InParams); +} + +void +FHoudiniParameterDetails::CreateWidgetFloat( + IDetailCategoryBuilder & HouParameterCategory, + TArray& InParams ) +{ + TArray FloatParams; + if (!CastParameters(InParams, FloatParams)) + return; + + if (FloatParams.Num() <= 0) + return; + + UHoudiniParameterFloat* MainParam = FloatParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + // Helper function to find a unit from a string (name or abbreviation) + auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); + EUnit Unit = EUnit::Unspecified; + if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) + Unit = ParmUnit.GetValue(); + + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + // Lambdas for slider begin + auto SliderBegin = [&](TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), + FloatParams[0]->GetOuter()); + + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + FloatParams[Idx]->Modify(); + } + }; + + // Lambdas for slider end + auto SliderEnd = [&](TArray FloatParams) + { + // Mark the value as changed to trigger an update + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + FloatParams[Idx]->MarkChanged(true); + } + }; + + // Lambdas for changing the parameter value + auto ChangeFloatValueAt = [&](const float& Value, const int32& Index, const bool& DoChange, TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), + FloatParams[0]->GetOuter() ); + + bool bChanged = false; + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + FloatParams[Idx]->Modify(); + if (FloatParams[Idx]->SetValueAt(Value, Index)) + { + // Only mark the param has changed if DoChange is true!!! + if(DoChange) + FloatParams[Idx]->MarkChanged(true); + bChanged = true; + } + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if no parameter's value has actually been changed + Transaction.Cancel(); + } + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Revert to default value"), + FloatParams[0]->GetOuter()); + + if (TupleIndex < 0) + { + for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + if (FloatParams[Idx]->IsDefault()) + continue; + + FloatParams[Idx]->RevertToDefault(-1); + } + } + else + { + for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + if (FloatParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + FloatParams[Idx]->RevertToDefault(TupleIndex); + } + } + return FReply::Handled(); + }; + + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + //TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + if (MainParam->GetTupleSize() == 3) + { + // Should we swap Y and Z fields (only relevant for Vector3) + // Ignore the swapping if that parameter has the noswap tag + bool SwapVector3 = !MainParam->GetNoSwap(); + + auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float & Val) + { + ChangeFloatValueAt(Val, 0, true, FloatParams); + ChangeFloatValueAt(Val, 1, true, FloatParams); + ChangeFloatValueAt(Val, 2, true, FloatParams); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, 0))) + .Y(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 2 : 1))) + .Z(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 1 : 2))) + .OnXCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val); + else + ChangeFloatValueAt( Val, 0, true, FloatParams); + }) + .OnYCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val); + else + ChangeFloatValueAt( Val, SwapVector3 ? 2 : 1, true, FloatParams); + }) + .OnZCommitted_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val); + else + ChangeFloatValueAt( Val, SwapVector3 ? 1 : 2, true, FloatParams); + }) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("FloatParameterLockButtonToolTip", "When locked, change the vector value uniformly.")) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(MainParam->IsUniformLocked() ? FEditorStyle::GetBrush("Genericlock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([FloatParams, MainParam]() + { + if (!MainParam || MainParam->IsPendingKill()) + return FReply::Handled(); + + for (auto & CurParam : FloatParams) + { + if (!CurParam || CurParam->IsPendingKill()) + continue; + + CurParam->SwitchUniformLock(); + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + + return FReply::Handled(); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([FloatParams]() + { + for (auto & SelectedParam : FloatParams) + { + if (!SelectedParam) + continue; + + if (!SelectedParam->IsDefault()) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([FloatParams, RevertToDefault]() { return RevertToDefault(-1, FloatParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + else + { + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + TSharedPtr> NumericEntryBox; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< float >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + + .MinValue(MainParam->GetMin()) + .MaxValue(MainParam->GetMax()) + + .MinSliderValue(MainParam->GetUIMin()) + .MaxSliderValue(MainParam->GetUIMax()) + + .Value(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, Idx))) + .OnValueChanged_Lambda([=](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) + .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) + .OnBeginSliderMovement_Lambda([=]() { SliderBegin(FloatParams); }) + .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(FloatParams); }) + .SliderExponent(MainParam->IsLogarithmic() ?8.0f : 1.0f) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .OnClicked_Lambda([Idx, FloatParams, RevertToDefault]() { return RevertToDefault(Idx, FloatParams); }) + .Visibility_Lambda([Idx, FloatParams]() + { + for (auto & SelectedParam :FloatParams) + { + if (!SelectedParam) + continue; + + if (!SelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + } + } + + Row->ValueWidget.Widget =VerticalBox; + + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetInt(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray IntParams; + if (!CastParameters(InParams, IntParams)) + + if (IntParams.Num() <= 0) + return; + + UHoudiniParameterInt* MainParam = IntParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + // Helper function to find a unit from a string (name or abbreviation) + auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); + EUnit Unit = EUnit::Unspecified; + if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) + Unit = ParmUnit.GetValue(); + + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + // Lambda for slider begin + auto SliderBegin = [&](TArray IntParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), + IntParams[0]->GetOuter()); + + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + IntParams[Idx]->Modify(); + } + }; + + // Lambda for slider end + auto SliderEnd = [&](TArray IntParams) + { + // Mark the value as changed to trigger an update + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + IntParams[Idx]->MarkChanged(true); + } + }; + + // Lambda for changing the parameter value + auto ChangeIntValueAt = [&](const int32& Value, const int32& Index, const bool& DoChange, TArray IntParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), + IntParams[0]->GetOuter()); + + bool bChanged = false; + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + if (!IntParams[Idx]) + continue; + + IntParams[Idx]->Modify(); + if (IntParams[Idx]->SetValueAt(Value, Index)) + { + // Only mark the param has changed if DoChange is true!!! + if (DoChange) + IntParams[Idx]->MarkChanged(true); + bChanged = true; + } + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if there is no param has actually been changed + Transaction.Cancel(); + } + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray IntParams) + { + for (int32 Idx = 0; Idx < IntParams.Num(); Idx++) + { + if (!IntParams[Idx]) + continue; + + if (IntParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + IntParams[Idx]->RevertToDefault(TupleIndex); + } + + return FReply::Handled(); + }; + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + + .MinValue(MainParam->GetMin()) + .MaxValue(MainParam->GetMax()) + + .MinSliderValue(MainParam->GetUIMin()) + .MaxSliderValue(MainParam->GetUIMax()) + + .Value( TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterInt::GetValue, Idx))) + .OnValueChanged_Lambda( [=](int32 Val) { ChangeIntValueAt(Val, Idx, false, IntParams); } ) + .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeIntValueAt(Val, Idx, true, IntParams); }) + .OnBeginSliderMovement_Lambda( [=]() { SliderBegin(IntParams); }) + .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(IntParams); }) + .SliderExponent(MainParam->IsLogarithmic() ? 8.0f : 1.0f) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, IntParams]() + { + for (auto & NextSelectedParam : IntParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, IntParams, RevertToDefault]() { return RevertToDefault(Idx, IntParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + /* + if (NumericEntryBox.IsValid()) + NumericEntryBox->SetEnabled(!MainParam->IsDisabled()); + */ + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray StringParams; + if (!CastParameters(InParams, StringParams)) + return; + + if (StringParams.Num() <= 0) + return; + + UHoudiniParameterString* MainParam = StringParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + bool bIsMultiLine = false; + bool bIsUnrealRef = false; + UClass* UnrealRefClass = UObject::StaticClass(); + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + TMap& Tags = MainParam->GetTags(); + if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_TAG) && FCString::Atoi(*Tags[HOUDINI_PARAMETER_STRING_REF_TAG]) == 1) + { + bIsUnrealRef = true; + + if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_CLASS_TAG)) + { + UClass * FoundClass = FindObject(ANY_PACKAGE, *Tags[HOUDINI_PARAMETER_STRING_REF_CLASS_TAG]); + if (FoundClass != nullptr) + { + UnrealRefClass = FoundClass; + } + } + } + + if (Tags.Contains(HOUDINI_PARAMETER_STRING_MULTILINE_TAG)) + { + bIsMultiLine = true; + } + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + // Lambda for changing the parameter value + auto ChangeStringValueAt = [&](const FString& Value, UObject* ChosenObj, const int32& Index, const bool& DoChange, TArray StringParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterSrtingChange", "Houdini Parameter String: Changing a value"), + StringParams[0]->GetOuter()); + + bool bChanged = false; + for (int Idx = 0; Idx < StringParams.Num(); Idx++) + { + if (!StringParams[Idx]) + continue; + + StringParams[Idx]->Modify(); + if (StringParams[Idx]->SetValueAt(Value, Index)) + { + StringParams[Idx]->MarkChanged(true); + bChanged = true; + } + + StringParams[Idx]->SetAssetAt(ChosenObj, Index); + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if there is no param actually has been changed + Transaction.Cancel(); + } + + FHoudiniEngineUtils::UpdateEditorProperties(StringParams[0], false); + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray StringParams) + { + for (int32 Idx = 0; Idx < StringParams.Num(); Idx++) + { + if (!StringParams[Idx]) + continue; + + if (StringParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + StringParams[Idx]->RevertToDefault(TupleIndex); + } + + return FReply::Handled(); + }; + + if (bIsUnrealRef) + { + TSharedPtr< SEditableTextBox > EditableTextBox; + TSharedPtr< SHorizontalBox > HorizontalBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([UnrealRefClass](const UObject* InObject) + { + return InObject->IsA(UnrealRefClass); + }) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + ChangeStringValueAt(ReferenceStr, InObject, Idx, true, StringParams); + }) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + // Thumbnail + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouParameterCategory.GetParentLayout().GetThumbnailPool(); + + // Create a thumbnail for the selected object / class + UObject* EditObject = nullptr; + const FString AssetPath = MainParam->GetValueAt(Idx); + EditObject = StaticFindObject(nullptr, nullptr, *AssetPath, true); + + FAssetData AssetData; + if (IsValid(EditObject)) + { + AssetData = FAssetData(EditObject); + } + else + { + AssetData.AssetClass = UnrealRefClass->GetFName(); + } + + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = MakeShareable( + new FAssetThumbnail(AssetData, 64, 64, AssetThumbnailPool)); + + TSharedPtr ThumbnailBorder; + HorizontalBox->AddSlot().Padding(0.f, 0.f, 2.f, 0.f).AutoWidth() + [ + SAssignNew(ThumbnailBorder, SBorder) + .OnMouseDoubleClick_Lambda([EditObject, Idx](const FGeometry&, const FPointerEvent&) + { + if (EditObject && GEditor) + GEditor->EditObject(EditObject); + + return FReply::Handled(); + }) + .Padding(5.f) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + ThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush * >::Create( + TAttribute< const FSlateBrush * >::FGetter::CreateLambda([ThumbnailBorder]() + { + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ))); + + FText MeshNameText = FText::GetEmpty(); + //if (InputObject) + // MeshNameText = FText::FromString(InputObject->GetName()); + + TSharedPtr StaticMeshComboButton; + + TSharedPtr ButtonBox; + HorizontalBox->AddSlot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + [ + SAssignNew(ButtonBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SAssignNew(StaticMeshComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromName(AssetData.AssetName)) + .ToolTipText(FText::FromString(MainParam->GetValueAt(Idx))) + ] + ] + ] + ]; + + StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda([UnrealRefClass, StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() + { + TArray AllowedClasses; + AllowedClasses.Add(UnrealRefClass); + TArray NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(nullptr), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda([StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) + { + UObject * Object = AssetData.GetAsset(); + + // Get the asset reference string for this object + // !! Accept null objects to allow clearing the asset picker !! + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); + + StaticMeshComboButton->SetIsOpen(false); + ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); + }), + FSimpleDelegate::CreateLambda([]() {})); + }) + ); + } + else if (bIsMultiLine) + { + TSharedPtr< SMultiLineEditableTextBox > MultiLineEditableTextBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) + {return true;}) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + FString NewString = ReferenceStr; + if (StringParams[0]->GetValueAt(Idx).Len() > 0) + NewString = StringParams[0]->GetValueAt(Idx) + "\n" + NewString; + + ChangeStringValueAt(NewString, nullptr, Idx, true, StringParams); + }) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Top).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(MultiLineEditableTextBox, SMultiLineEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainParam->GetValueAt(Idx))) + .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, StringParams]() + { + for (auto & NextSelectedParam : StringParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() { return RevertToDefault(Idx, StringParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + else + { + TSharedPtr< SEditableTextBox > EditableTextBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) + {return true;}) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + ChangeStringValueAt(ReferenceStr, nullptr, Idx, true, StringParams); + }) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(EditableTextBox, SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainParam->GetValueAt(Idx))) + .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) + { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, StringParams]() + { + for (auto & NextSelectedParam : StringParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() + { return RevertToDefault(Idx, StringParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ColorParams; + if (!CastParameters(InParams, ColorParams)) + return; + + if (ColorParams.Num() <= 0) + return; + + UHoudiniParameterColor* MainParam = ColorParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + bool bHasAlpha = (MainParam->GetTupleSize() == 4); + + // Add color picker UI. + TSharedPtr ColorBlock; + TSharedRef VerticalBox = SNew(SVerticalBox); + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(ColorBlock, SColorBlock) + .Color(MainParam->GetColorValue()) + .ShowBackgroundForAlpha(bHasAlpha) + .OnMouseButtonDown(FPointerEventHandler::CreateLambda( + [MainParam, ColorParams, ColorBlock, bHasAlpha](const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) + { + if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) + return FReply::Unhandled(); + + FColorPickerArgs PickerArgs; + PickerArgs.ParentWidget = ColorBlock; + PickerArgs.bUseAlpha = bHasAlpha; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) { + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), + MainParam->GetOuter(), true); + + bool bChanged = false; + for (auto & Param : ColorParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetColorValue(InColor)) + { + Param->MarkChanged(true); + bChanged = true; + } + } + + // cancel the transaction if there is actually no value changed + if (!bChanged) + { + Transaction.Cancel(); + } + + }); + PickerArgs.InitialColorOverride = MainParam->GetColorValue(); + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker(PickerArgs); + return FReply::Handled(); + })) + ]; + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetButton(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ButtonParams; + if (!CastParameters(InParams, ButtonParams)) + return; + + if (ButtonParams.Num() <= 0) + return; + + UHoudiniParameterButton* MainParam = ButtonParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + FText ParameterTooltip = GetParameterTooltip(MainParam); + + TSharedRef HorizontalBox = SNew(SHorizontalBox); + TSharedPtr Button; + + // Add button UI. + HorizontalBox->AddSlot().Padding(1, 2, 4, 2) + [ + SAssignNew(Button, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ParameterLabelText) + .ToolTipText(ParameterTooltip) + .OnClicked(FOnClicked::CreateLambda( [MainParam, ButtonParams]() + { + for (auto & Param : ButtonParams) + { + if (!Param) + continue; + + // There is no undo redo operation for button + Param->MarkChanged(true); + } + + return FReply::Handled(); + })) + ]; + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ButtonStripParams; + if (!CastParameters(InParams, ButtonStripParams)) + return; + + if (ButtonStripParams.Num() <= 0) + return; + + UHoudiniParameterButtonStrip* MainParam = ButtonStripParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + if (!Row) + return; + + auto OnButtonStateChanged = [MainParam, ButtonStripParams](ECheckBoxState NewState, int32 Idx) + { + + /* + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterButtonStripChange", "Houdini Parameter Button Strip: Changing value"), + MainParam->GetOuter(), true); + */ + int32 StateInt = NewState == ECheckBoxState::Checked ? 1 : 0; + bool bChanged = false; + + for (auto & NextParam : ButtonStripParams) + { + if (!NextParam || NextParam->IsPendingKill()) + continue; + + if (!NextParam->Values.IsValidIndex(Idx)) + continue; + + //NextParam->Modify(); + if (NextParam->SetValueAt(Idx, StateInt)) + { + NextParam->MarkChanged(true); + bChanged = true; + } + } + + //if (!bChanged) + // Transaction.Cancel(); + + }; + + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + FText ParameterTooltip = GetParameterTooltip(MainParam); + + TSharedRef HorizontalBox = SNew(SHorizontalBox); + FLinearColor BgColor(0.53f, 0.81f, 0.82f, 1.0f); // Sky Blue Backgroud color + + for (int32 Idx = 0; Idx < MainParam->Count; ++Idx) + { + if (!MainParam->Values.IsValidIndex(Idx) || !MainParam->Labels.IsValidIndex(Idx)) + continue; + + bool bPressed = MainParam->Values[Idx] > 0; + FText LabelText = FText::FromString(MainParam->Labels[Idx]); + + TSharedPtr Button; + + HorizontalBox->AddSlot().Padding(0).FillWidth(1.0f) + [ + SAssignNew(Button, SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") + .IsChecked(bPressed ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged_Lambda([OnButtonStateChanged, Idx](ECheckBoxState NewState) + { + OnButtonStateChanged(NewState, Idx); + }) + .Content() + [ + SNew(STextBlock) + .Text(LabelText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + Button->SetColorAndOpacity(BgColor); + } + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetLabel(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray LabelParams; + if (!CastParameters(InParams, LabelParams)) + return; + + if (LabelParams.Num() <= 0) + return; + + UHoudiniParameterLabel* MainParam = LabelParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + + for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) + { + FString NextLabelString = MainParam->GetStringAtIndex(Index); + FText ParameterLabelText = FText::FromString(NextLabelString); + + TSharedPtr TextBlock; + + // Add Label UI. + VerticalBox->AddSlot().Padding(1, 2, 4, 2) + [ + SAssignNew(TextBlock, STextBlock).Text(ParameterLabelText) + ]; + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetToggle(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray ToggleParams; + if (!CastParameters(InParams, ToggleParams)) + return; + + if (ToggleParams.Num() <= 0) + return; + + UHoudiniParameterToggle* MainParam = ToggleParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + + TSharedRef VerticalBox = SNew(SVerticalBox); + auto IsToggleCheckedLambda = [MainParam](int32 Index) + { + if (Index >= MainParam->GetNumValues()) + return ECheckBoxState::Unchecked; + + if (MainParam->GetValueAt(Index)) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; + }; + + auto OnToggleCheckStateChanged = [MainParam, ToggleParams](ECheckBoxState NewState, int32 Index) + { + if (Index >= MainParam->GetNumValues()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterToggleChange", "Houdini Parameter Toggle: Changing value"), + MainParam->GetOuter(), true); + + bool bState = (NewState == ECheckBoxState::Checked); + + bool bChanged = false; + for (auto & Param : ToggleParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetValueAt(bState, Index)) + { + bChanged = true; + Param->MarkChanged(true); + } + } + + // Cancel the transaction if no parameter has actually been changed + if (!bChanged) + { + Transaction.Cancel(); + } + }; + + for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) + { + TSharedPtr< SCheckBox > CheckBox; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + [ + SAssignNew(CheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([OnToggleCheckStateChanged, Index](ECheckBoxState NewState) { + OnToggleCheckStateChanged(NewState, Index); + + }) + .IsChecked_Lambda([IsToggleCheckedLambda, Index]() { + return IsToggleCheckedLambda(Index); + }) + .Content() + [ + SNew(STextBlock) + .Text(ParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FileParams; + if (!CastParameters(InParams, FileParams)) + return; + + if (FileParams.Num() <= 0) + return; + + UHoudiniParameterFile* MainParam = FileParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + + FString FileTypeWidgetFilter = TEXT("All files (*.*)|*.*"); + if (!MainParam->GetFileFilters().IsEmpty()) + FileTypeWidgetFilter = FString::Printf(TEXT("%s files (%s)|%s"), *MainParam->GetFileFilters(), *MainParam->GetFileFilters(), *MainParam->GetFileFilters()); + + FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); + + auto UpdateCheckRelativePath = [MainParam](const FString & PickedPath) + { + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(MainParam->GetOuter()); + if (MainParam->GetOuter() && !PickedPath.IsEmpty() && FPaths::IsRelative(PickedPath)) + { + // Check if the path is relative to the UE4 project + FString AbsolutePath = FPaths::ConvertRelativePathToFull(PickedPath); + if (FPaths::FileExists(AbsolutePath)) + { + return AbsolutePath; + } + + // Check if the path is relative to the asset + if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + { + if (HoudiniAssetComponent->HoudiniAsset && !HoudiniAssetComponent->HoudiniAsset->IsPendingKill()) + { + FString AssetFilePath = FPaths::GetPath(HoudiniAssetComponent->HoudiniAsset->AssetFileName); + if (FPaths::FileExists(AssetFilePath)) + { + FString UpdatedFileWidgetPath = FPaths::Combine(*AssetFilePath, *PickedPath); + if (FPaths::FileExists(UpdatedFileWidgetPath)) + { + return UpdatedFileWidgetPath; + } + } + } + } + } + + return PickedPath; + }; + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + FString FileWidgetPath = MainParam->GetValueAt(Idx); + FString FileWidgetBrowsePath = BrowseWidgetDirectory; + + if (!FileWidgetPath.IsEmpty()) + { + FString FileWidgetDirPath = FPaths::GetPath(FileWidgetPath); + if (!FileWidgetDirPath.IsEmpty()) + FileWidgetBrowsePath = FileWidgetDirPath; + } + + bool IsDirectoryPicker = MainParam->GetParameterType() == EHoudiniParameterType::FileDir; + bool bIsNewFile = !MainParam->IsReadOnly(); + + FText BrowseTooltip = LOCTEXT("FileButtonToolTipText", "Choose a file from this computer"); + if (IsDirectoryPicker) + BrowseTooltip = LOCTEXT("DirButtonToolTipText", "Choose a directory from this computer"); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SNewFilePathPicker) + .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) + .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") + .BrowseButtonToolTip(BrowseTooltip) + .BrowseDirectory(FileWidgetBrowsePath) + .BrowseTitle(LOCTEXT("PropertyEditorTitle", "File picker...")) + .FilePath(FileWidgetPath) + .FileTypeFilter(FileTypeWidgetFilter) + .IsNewFile(bIsNewFile) + .IsDirectoryPicker(IsDirectoryPicker) + .ToolTipText_Lambda([MainParam]() + { + // return the current param value as a tooltip + FString FileValue = MainParam ? MainParam->GetValueAt(0) : FString(); + return FText::FromString(FileValue); + }) + .OnPathPicked(FOnPathPicked::CreateLambda([MainParam, FileParams, UpdateCheckRelativePath, Idx](const FString & PickedPath) + { + if (MainParam->GetNumValues() <= Idx) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFileChange", "Houdini Parameter File: Changing a file path"), + MainParam->GetOuter(), true); + + bool bChanged = false; + + for (auto & Param : FileParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetValueAt(UpdateCheckRelativePath(PickedPath), Idx)) + { + bChanged = true; + Param->MarkChanged(true); + } + } + + // Cancel the transaction if no value has actually been changed + if (!bChanged) + { + Transaction.Cancel(); + } + })) + ] + ]; + + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + + +void +FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ChoiceParams; + if (!CastParameters(InParams, ChoiceParams)) + return; + + if (ChoiceParams.Num() <= 0) + return; + + UHoudiniParameterChoice* MainParam = ChoiceParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + // Lambda for changing the parameter value + auto ChangeSelectionLambda = [ChoiceParams](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterChoiceChange", "Houdini Parameter Choice: Changing selection"), + ChoiceParams[0]->GetOuter()); + + const int32 NewIntValue = ChoiceParams[0]->GetIntValueFromLabel(*NewChoice); + + bool bChanged = false; + for (int Idx = 0; Idx < ChoiceParams.Num(); Idx++) + { + if (!ChoiceParams[Idx]) + continue; + + ChoiceParams[Idx]->Modify(); + if (ChoiceParams[Idx]->SetIntValue(NewIntValue)) + { + bChanged = true; + ChoiceParams[Idx]->MarkChanged(true); + ChoiceParams[Idx]->UpdateStringValueFromInt(); + } + } + + if (!bChanged) + { + // Cancel the transaction if no parameter was changed + Transaction.Cancel(); + } + }; + + // + MainParam->UpdateChoiceLabelsPtr(); + TArray>* OptionSource = MainParam->GetChoiceLabelsPtr(); + TSharedPtr IntialSelec; + if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValue())) + { + IntialSelec = (*OptionSource)[MainParam->GetIntValue()]; + } + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBox; + HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SAssignNew( ComboBox, SComboBox< TSharedPtr< FString > > ) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + []( TSharedPtr< FString > InItem ) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [ChangeSelectionLambda](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + ChangeSelectionLambda(NewChoice, SelectType); + }) + [ + SNew(STextBlock) + .Text_Lambda([MainParam]() { return FText::FromString(MainParam->GetLabel()); }) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + if ( ComboBox.IsValid() ) + ComboBox->SetEnabled( !MainParam->IsDisabled() ); + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetSeparator(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + TSharedRef HorizontalBox = SNew(SCustomizedBox); + + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + + Row->WholeRowWidget.Widget = HorizontalBox; +} + +void +FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray OperatorPathParams; + if (!CastParameters(InParams, OperatorPathParams)) + return; + + if (OperatorPathParams.Num() <= 0) + return; + + UHoudiniParameterOperatorPath* MainParam = OperatorPathParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + UHoudiniInput* MainInput = MainParam->HoudiniInput.Get(); + if (!MainInput) + return; + + // Build an array of edited inputs for multi edition + TArray EditedInputs; + EditedInputs.Add(MainInput); + + // Add the corresponding inputs found in the other HAC + for (int LinkedIdx = 1; LinkedIdx < OperatorPathParams.Num(); LinkedIdx++) + { + UHoudiniInput* LinkedInput = OperatorPathParams[LinkedIdx]->HoudiniInput.Get(); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if (!LinkedInput->Matches(*MainInput)) + continue; + + EditedInputs.Add(LinkedInput); + } + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FHoudiniInputDetails::CreateWidget(HouParameterCategory, EditedInputs, Row); + + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetFloatRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Parsing a float ramp: 1->(2->3->4)*->5 // + switch (MainParam->GetParameterType()) + { + //*****State 1: Float Ramp*****// + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); + if (FloatRampParameter) + { + CurrentRampFloat = FloatRampParameter; + CurrentRampFloatPointsArray.Empty(); + + CurrentRampParameterList = InParams; + + FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + CurrentRampRow = Row; + } + break; + } + + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* FloatParameter = Cast(MainParam); + if (FloatParameter) + { + bool bCreateNewPoint = true; + if (CurrentRampFloatPointsArray.Num() > 0) + { + UHoudiniParameterRampFloatPoint* LastPtInArr = CurrentRampFloatPointsArray.Last(); + if (LastPtInArr && !LastPtInArr->ValueParentParm) + bCreateNewPoint = false; + } + + //*****State 2: Float Parameter (position)*****// + if (bCreateNewPoint) + { + UHoudiniParameterRampFloatPoint* NewRampFloatPoint = nullptr; + + int32 PointIndex = CurrentRampFloatPointsArray.Num(); + if (CurrentRampFloat->Points.IsValidIndex(PointIndex)) + { + + // TODO: We should reuse existing point objects, if they exist. Currently + // this causes results in unexpected behaviour in other parts of this detail code. + // Give this code a bit of an overhaul at some point. + // NewRampFloatPoint = CurrentRampFloat->Points[PointIndex]; + } + + if (!NewRampFloatPoint) + { + // Create a new float ramp point, and add its pointer to the current float points buffer array. + NewRampFloatPoint = NewObject< UHoudiniParameterRampFloatPoint >(CurrentRampFloat, FName(), CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); + + } + + CurrentRampFloatPointsArray.Add(NewRampFloatPoint); + + if (FloatParameter->GetNumberOfValues() <= 0) + return; + // Set the float ramp point's position parent parm, and value + NewRampFloatPoint->PositionParentParm = FloatParameter; + NewRampFloatPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); + } + //*****State 3: Float Parameter (value)*****// + else + { + if (FloatParameter->GetNumberOfValues() <= 0) + return; + // Get the last point in the buffer array + if (CurrentRampFloatPointsArray.Num() > 0) + { + // Set the last inserted float ramp point's float parent parm, and value + UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); + LastAddedFloatRampPoint->ValueParentParm = FloatParameter; + LastAddedFloatRampPoint->SetValue(FloatParameter->GetValuesPtr()[0]); + } + } + } + + break; + } + //*****State 4: Choice parameter*****// + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); + if (ChoiceParameter && CurrentRampFloatPointsArray.Num() > 0) + { + // Set the last inserted float ramp point's interpolation parent parm, and value + UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); + + LastAddedFloatRampPoint->InterpolationParentParm = ChoiceParameter; + LastAddedFloatRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + + // Set the index of this point in the multi parm. + LastAddedFloatRampPoint->InstanceIndex = CurrentRampFloatPointsArray.Num() - 1; + } + + + //*****State 5: All ramp points have been parsed, finish!*****// + if (CurrentRampFloatPointsArray.Num() >= (int32)CurrentRampFloat->MultiParmInstanceCount) + { + CurrentRampFloatPointsArray.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { + return P1.Position < P2.Position; + }); + + CurrentRampFloat->Points = CurrentRampFloatPointsArray; + + // Not caching, points are synced, update cached points + if (!CurrentRampFloat->bCaching) + { + const int32 NumPoints = CurrentRampFloat->Points.Num(); + CurrentRampFloat->CachedPoints.SetNum(NumPoints); + for (int32 i = 0; i < NumPoints; ++i) + { + UHoudiniParameterRampFloatPoint* FromPoint = CurrentRampFloat->Points[i]; + UHoudiniParameterRampFloatPoint* ToPoint = CurrentRampFloat->CachedPoints[i]; + ToPoint = nullptr; + check(FromPoint) + if (!ToPoint) + { + ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampFloat, RF_NoFlags, CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); + } + else + { + ToPoint->CopyStateFrom(FromPoint, true); + } + CurrentRampFloat->CachedPoints[i] = ToPoint; + } + } + + CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampFloat, CurrentRampParameterList); + + CurrentRampFloat->SetDefaultValues(); + + CurrentRampFloat = nullptr; + CurrentRampRow = nullptr; + } + + break; + } + + default: + break; + } + +} + +void +FHoudiniParameterDetails::CreateWidgetColorRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Parsing a color ramp: 1->(2->3->4)*->5 // + switch (MainParam->GetParameterType()) + { + //*****State 1: Color Ramp*****// + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* RampColor = Cast(MainParam); + if (RampColor) + { + CurrentRampColor = RampColor; + CurrentRampColorPointsArray.Empty(); + + CurrentRampParameterList = InParams; + + FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + CurrentRampRow = Row; + } + + break; + } + //*****State 2: Float parameter*****// + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* FloatParameter = Cast(MainParam); + if (FloatParameter) + { + // Create a new color ramp point, and add its pointer to the current color points buffer array. + UHoudiniParameterRampColorPoint* NewRampColorPoint = nullptr; + int32 PointIndex = CurrentRampColorPointsArray.Num(); + + if (CurrentRampColor->Points.IsValidIndex(PointIndex)) + { + NewRampColorPoint = CurrentRampColor->Points[PointIndex]; + } + + if (!NewRampColorPoint) + { + NewRampColorPoint = NewObject< UHoudiniParameterRampColorPoint >(CurrentRampColor, FName(), CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); + } + + CurrentRampColorPointsArray.Add(NewRampColorPoint); + + if (FloatParameter->GetNumberOfValues() <= 0) + return; + // Set the color ramp point's position parent parm, and value + NewRampColorPoint->PositionParentParm = FloatParameter; + NewRampColorPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); + } + + break; + } + //*****State 3: Color parameter*****// + case EHoudiniParameterType::Color: + { + UHoudiniParameterColor* ColorParameter = Cast(MainParam); + if (ColorParameter && CurrentRampColorPointsArray.Num() > 0) + { + // Set the last inserted color ramp point's color parent parm, and value + UHoudiniParameterRampColorPoint* LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); + LastAddedColorRampPoint->ValueParentParm = ColorParameter; + LastAddedColorRampPoint->SetValue(ColorParameter->GetColorValue()); + } + + break; + } + //*****State 4: Choice Parameter*****// + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); + if (ChoiceParameter) + { + // Set the last inserted color ramp point's interpolation parent parm, and value + UHoudiniParameterRampColorPoint*& LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); + + LastAddedColorRampPoint->InterpolationParentParm = ChoiceParameter; + LastAddedColorRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + + // Set the index of this point in the multi parm. + LastAddedColorRampPoint->InstanceIndex = CurrentRampColorPointsArray.Num() - 1; + } + + + //*****State 5: All ramp points have been parsed, finish!*****// + if (CurrentRampColorPointsArray.Num() >= (int32)CurrentRampColor->MultiParmInstanceCount) + { + CurrentRampColorPointsArray.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) + { + return P1.Position < P2.Position; + }); + + CurrentRampColor->Points = CurrentRampColorPointsArray; + + // Not caching, points are synced, update cached points + + if (!CurrentRampColor->bCaching) + { + const int32 NumPoints = CurrentRampColor->Points.Num(); + CurrentRampColor->CachedPoints.SetNum(NumPoints); + + for (int32 i = 0; i < NumPoints; ++i) + { + UHoudiniParameterRampColorPoint* FromPoint = CurrentRampColor->Points[i]; + UHoudiniParameterRampColorPoint* ToPoint = CurrentRampColor->CachedPoints[i]; + + if (!ToPoint) + { + ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampColor, RF_NoFlags, CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); + } + else + { + ToPoint->CopyStateFrom(FromPoint, true); + } + CurrentRampColor->CachedPoints[i] = ToPoint; + } + } + + + CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampColor, CurrentRampParameterList); + + CurrentRampColor->SetDefaultValues(); + + CurrentRampColor = nullptr; + CurrentRampRow = nullptr; + } + + break; + } + + default: + break; + } + +} + + +FDetailWidgetRow* +FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() <= 0) + return nullptr; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam) + return nullptr; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, (TArray)InParams); + if (!Row) + return nullptr; + + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + + // Create the standard parameter name widget with an added autoupdate checkbox. + CreateNameWidgetWithAutoUpdate(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor *RampColorParam = Cast(MainParam); + if (!RampColorParam) + return nullptr; + + TSharedPtr ColorGradientEditor; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SBorder) + .VAlign(VAlign_Fill) + [ + SAssignNew(ColorGradientEditor, SHoudiniColorRampCurveEditor) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + ] + ]; + + if (!ColorGradientEditor.IsValid()) + return nullptr; + + // Avoid showing tooltips inside of the curve editor + ColorGradientEditor->EnableToolTipForceField(true); + + CurrentRampParameterColorCurve = NewObject( + MainParam, UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + + if (!CurrentRampParameterColorCurve) + return nullptr; + + CreatedColorRampCurves.Add(CurrentRampParameterColorCurve); + + // Add the ramp curve to root to avoid garabage collected. + CurrentRampParameterColorCurve->AddToRoot(); + + TArray ColorRampParameters; + CastParameters(InParams, ColorRampParameters); + + for (auto NextColorRamp : ColorRampParameters) + { + CurrentRampParameterColorCurve->ColorRampParameters.Add(NextColorRamp); + } + ColorGradientEditor->HoudiniColorRampCurve = CurrentRampParameterColorCurve; + + // Clear default curve points + for (int Idx = 0; Idx < 4; ++Idx) + { + FRichCurve& RichCurve = (CurrentRampParameterColorCurve->FloatCurves)[Idx]; + if (RichCurve.GetNumKeys() > 0) + RichCurve.Keys.Empty(); + } + ColorGradientEditor->SetCurveOwner(CurrentRampParameterColorCurve); + } + else if(MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat *RampFloatParam = Cast(MainParam); + if (!RampFloatParam) + return nullptr; + + TSharedPtr FloatCurveEditor; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SBorder) + .VAlign(VAlign_Fill) + [ + SAssignNew(FloatCurveEditor, SHoudiniFloatRampCurveEditor) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + .HideUI(true) + .DrawCurve(true) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + .ViewMinOutput(0.0f) + .ViewMaxOutput(1.0f) + .TimelineLength(1.0f) + .AllowZoomOutput(false) + .ShowInputGridNumbers(false) + .ShowOutputGridNumbers(false) + .ShowZoomButtons(false) + .ZoomToFitHorizontal(false) + .ZoomToFitVertical(false) + .XAxisName(FString("X")) + .YAxisName(FString("Y")) + .ShowCurveSelector(false) + + ] + ]; + + if (!FloatCurveEditor.IsValid()) + return nullptr; + + // Avoid showing tooltips inside of the curve editor + FloatCurveEditor->EnableToolTipForceField(true); + + CurrentRampParameterFloatCurve = NewObject( + MainParam, UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + + if (!CurrentRampParameterFloatCurve) + return nullptr; + + CreatedFloatRampCurves.Add(CurrentRampParameterFloatCurve); + + // Add the ramp curve to root to avoid garbage collected + CurrentRampParameterFloatCurve->AddToRoot(); + + TArray FloatRampParameters; + CastParameters(InParams, FloatRampParameters); + for (auto NextFloatRamp : FloatRampParameters) + { + CurrentRampParameterFloatCurve->FloatRampParameters.Add(NextFloatRamp); + } + FloatCurveEditor->HoudiniFloatRampCurve = CurrentRampParameterFloatCurve; + + FloatCurveEditor->SetCurveOwner(CurrentRampParameterFloatCurve, true); + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + return Row; +} + + +void +FHoudiniParameterDetails::CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray& InParams) +{ + if (!Row || !InParameter) + return; + + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + UHoudiniParameterRampFloat * MainFloatRampParameter = nullptr; + UHoudiniParameterRampColor * MainColorRampParameter = nullptr; + + TArray FloatRampParameterList; + TArray ColorRampParameterList; + if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + MainFloatRampParameter = Cast(MainParam); + + if (!MainFloatRampParameter) + return; + + if (!CastParameters(InParams, FloatRampParameterList)) + return; + } + else if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + MainColorRampParameter = Cast(MainParam); + + if (!MainColorRampParameter) + return; + + if (!CastParameters(InParams, ColorRampParameterList)) + return; + } + else + { + return; + } + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Lambda for computing the float point to be inserted + auto GetInsertFloatPointLambda = [MainFloatRampParameter]( + const int32& InsertAtIndex, + float& OutPosition, + float& OutValue, + EHoudiniRampInterpolationType& OutInterpType) mutable + { + if (!MainFloatRampParameter) + return; + + float PrevPosition = 0.0f; + float NextPosition = 1.0f; + + TArray &CurrentPoints = MainFloatRampParameter->Points; + TArray &CachedPoints = MainFloatRampParameter->CachedPoints; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + int32 NumPoints = 0; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + { + NumPoints = CurrentPoints.Num(); + } + else + { + MainFloatRampParameter->SetCaching(true); + NumPoints = CachedPoints.Num(); + } + + if (InsertAtIndex >= NumPoints) + { + // Insert at the end + if (NumPoints > 0) + { + UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + PrevPoint = CurrentPoints.Last(); + else + PrevPoint = CachedPoints.Last(); + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + } + } + else if (InsertAtIndex <= 0) + { + // Insert at the beginning + if (NumPoints > 0) + { + UHoudiniParameterRampFloatPoint* NextPoint = nullptr; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + NextPoint = CurrentPoints[0]; + else + NextPoint = CachedPoints[0]; + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + OutInterpType = NextPoint->GetInterpolation(); + } + } + } + else + { + // Insert in the middle + if (NumPoints > 1) + { + UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; + UHoudiniParameterRampFloatPoint* NextPoint = nullptr; + + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + { + PrevPoint = CurrentPoints[InsertAtIndex - 1]; + NextPoint = CurrentPoints[InsertAtIndex]; + } + else + { + PrevPoint = CachedPoints[InsertAtIndex - 1]; + NextPoint = CachedPoints[InsertAtIndex]; + } + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + } + + if (PrevPoint && NextPoint) + { + OutValue = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; + } + } + } + + OutPosition = (PrevPosition + NextPosition) / 2.0f; + }; + + + // Lambda for computing the color point to be inserted + auto GetInsertColorPointLambda = [MainColorRampParameter]( + const int32& InsertAtIndex, + float& OutPosition, + FLinearColor& OutColor, + EHoudiniRampInterpolationType& OutInterpType) mutable + { + if (!MainColorRampParameter) + return; + + float PrevPosition = 0.0f; + float NextPosition = 1.0f; + + TArray &CurrentPoints = MainColorRampParameter->Points; + TArray &CachedPoints = MainColorRampParameter->CachedPoints; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + int32 NumPoints = 0; + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NumPoints = CurrentPoints.Num(); + else + NumPoints = CachedPoints.Num(); + + if (InsertAtIndex >= NumPoints) + { + // Insert at the end + if (NumPoints > 0) + { + UHoudiniParameterRampColorPoint* PrevPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + PrevPoint = CurrentPoints.Last(); + else + PrevPoint = CachedPoints.Last(); + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + } + } + else if (InsertAtIndex <= 0) + { + // Insert at the beginning + if (NumPoints > 0) + { + UHoudiniParameterRampColorPoint* NextPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NextPoint = CurrentPoints[0]; + else + NextPoint = CachedPoints[0]; + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + OutInterpType = NextPoint->GetInterpolation(); + } + } + } + else + { + // Insert in the middle + if (NumPoints > 1) + { + UHoudiniParameterRampColorPoint* PrevPoint = nullptr; + UHoudiniParameterRampColorPoint* NextPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + { + PrevPoint = CurrentPoints[InsertAtIndex - 1]; + NextPoint = CurrentPoints[InsertAtIndex]; + } + else + { + PrevPoint = CachedPoints[InsertAtIndex - 1]; + NextPoint = CachedPoints[InsertAtIndex]; + } + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + } + + if (PrevPoint && NextPoint) + { + OutColor = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; + } + } + } + + OutPosition = (PrevPosition + NextPosition) / 2.0f; + }; + + int32 RowIndex = 0; + auto InsertRampPoint_Lambda = [GetInsertColorPointLambda, GetInsertFloatPointLambda, &CategoryBuilder, bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, + UHoudiniParameterRampColor* MainRampColor, + TArray &RampFloatList, + TArray &RampColorList, + const int32& Index) mutable + { + if (MainRampFloat) + { + float InsertPosition = 0.0f; + float InsertValue = 1.0f; + EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; + + GetInsertFloatPointLambda(Index, InsertPosition, InsertValue, InsertInterp); + + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); + + for (auto & NextFloatRamp : RampFloatList) + { + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + CreateFloatRampParameterInsertEvent( + NextFloatRamp, InsertPosition, InsertValue, InsertInterp); + + NextFloatRamp->MarkChanged(true); + } + else + { + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject + (NextFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); + NewCachedPoint->Position = InsertPosition; + NewCachedPoint->Value = InsertValue; + NewCachedPoint->Interpolation = InsertInterp; + + NextFloatRamp->CachedPoints.Add(NewCachedPoint); + NextFloatRamp->bCaching = true; + if (!bCookingEnabled) + { + // If cooking is not enabled, be sure to mark this parameter as changed + // so that it triggers an update once cooking is enabled again. + NextFloatRamp->MarkChanged(true); + } + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + { + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + } + else if (MainRampColor) + { + float InsertPosition = 0.0f; + FLinearColor InsertColor = FLinearColor::Black; + EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; + + GetInsertColorPointLambda(Index, InsertPosition, InsertColor, InsertInterp); + + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); + for (auto& NextColorRamp : RampColorList) + { + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + CreateColorRampParameterInsertEvent( + NextColorRamp, InsertPosition, InsertColor, InsertInterp); + + NextColorRamp->MarkChanged(true); + } + else + { + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject + (NextColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); + NewCachedPoint->Position = InsertPosition; + NewCachedPoint->Value = InsertColor; + NewCachedPoint->Interpolation = InsertInterp; + + NextColorRamp->CachedPoints.Add(NewCachedPoint); + NextColorRamp->bCaching = true; + if (!bCookingEnabled) + NextColorRamp->MarkChanged(true); + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + auto DeleteRampPoint_Lambda = [bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, + UHoudiniParameterRampColor* MainRampColor, + TArray &FloatRampList, + TArray &ColorRampList, + const int32& Index) mutable + { + if (MainRampFloat) + { + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampList); + + for (auto& NextFloatRamp : FloatRampList) + { + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextFloatRamp->Points.Num() == 0) + return; + + UHoudiniParameterRampFloatPoint* PointToDelete = nullptr; + + if (Index == -1) + PointToDelete = NextFloatRamp->Points.Last(); + else if (NextFloatRamp->Points.IsValidIndex(Index)) + PointToDelete = NextFloatRamp->Points[Index]; + + if (!PointToDelete) + return; + + const int32 & InstanceIndexToDelete = PointToDelete->InstanceIndex; + + CreateFloatRampParameterDeleteEvent(NextFloatRamp, InstanceIndexToDelete); + NextFloatRamp->MarkChanged(true); + } + else + { + if (NextFloatRamp->CachedPoints.Num() == 0) + return; + + if (Index == -1) + NextFloatRamp->CachedPoints.Pop(); + else if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) + NextFloatRamp->CachedPoints.RemoveAt(Index); + else + return; + + NextFloatRamp->bCaching = true; + if (!bCookingEnabled) + NextFloatRamp->MarkChanged(true); + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); + } + } + else + { + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampList); + + for (auto& NextColorRamp : ColorRampList) + { + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextColorRamp->Points.Num() == 0) + return; + + UHoudiniParameterRampColorPoint* PointToRemove = nullptr; + + if (Index == -1) + PointToRemove = NextColorRamp->Points.Last(); + else if (NextColorRamp->Points.IsValidIndex(Index)) + PointToRemove = NextColorRamp->Points[Index]; + + if (!PointToRemove) + return; + + const int32 & InstanceIndexToDelete = PointToRemove->InstanceIndex; + + CreateColorRampParameterDeleteEvent(NextColorRamp, InstanceIndexToDelete); + + NextColorRamp->MarkChanged(true); + } + else + { + if (NextColorRamp->CachedPoints.Num() == 0) + return; + + if (Index == -1) + NextColorRamp->CachedPoints.Pop(); + else if (NextColorRamp->CachedPoints.IsValidIndex(Index)) + NextColorRamp->CachedPoints.RemoveAt(Index); + else + return; + + NextColorRamp->bCaching = true; + if (!bCookingEnabled) + NextColorRamp->MarkChanged(true); + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + + TSharedRef VerticalBox = StaticCastSharedRef(Row->ValueWidget.Widget); + + TSharedPtr GridPanel; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SAssignNew(GridPanel, SUniformGridPanel) + ]; + + //AllUniformGridPanels.Add(GridPanel.Get()); + + GridPanel->SetSlotPadding(FMargin(2.f, 2.f, 5.f, 3.f)); + GridPanel->AddSlot(0, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Position"))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + FString ValueString = TEXT("Value"); + if (!MainFloatRampParameter) + ValueString = TEXT("Color"); + + GridPanel->AddSlot(1, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(ValueString)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + GridPanel->AddSlot(2, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Interp."))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + + GridPanel->AddSlot(3, RowIndex) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateLambda([InsertRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, bCookingEnabled]() mutable + { + int32 InsertAtIndex = -1; + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + InsertAtIndex = MainFloatRampParameter->Points.Num(); + else + InsertAtIndex = MainFloatRampParameter->CachedPoints.Num(); + } + else if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + InsertAtIndex = MainColorRampParameter->Points.Num(); + else + InsertAtIndex = MainColorRampParameter->CachedPoints.Num(); + } + + InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, InsertAtIndex); + }), + LOCTEXT("AddRampPoint", "Add a ramp point to the end"), true) + ] + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateLambda([DeleteRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList]() mutable + { + DeleteRampPoint_Lambda( + MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, -1); + }), + LOCTEXT("DeleteRampPoint", "Delete the last ramp point"), true) + ] + + ]; + + EUnit Unit = EUnit::Unspecified; + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + int32 PointCount = 0; + // Use Synced points on auto update mode + // Use Cached points on manual update mode + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + PointCount = MainFloatRampParameter->Points.Num(); + else + PointCount = MainFloatRampParameter->CachedPoints.Num(); + } + + if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate()) + PointCount = MainColorRampParameter->Points.Num(); + else + PointCount = MainColorRampParameter->CachedPoints.Num(); + } + + // Lambda function for changing a ramp point + auto OnPointChangeCommit = [bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, UHoudiniParameterRampColor* MainRampColor, + UHoudiniParameterRampFloatPoint* MainRampFloatPoint, UHoudiniParameterRampColorPoint* MainRampColorPoint, + TArray &RampFloatList, TArray &RampColorList, + const int32& Index, const FString& ChangedDataName, + const float& NewPosition, const float& NewFloat, + const FLinearColor& NewColor, + const EHoudiniRampInterpolationType& NewInterpType) mutable + { + if (MainRampFloat && MainRampFloatPoint) + { + if (ChangedDataName == FString("position") && MainRampFloatPoint->GetPosition() == NewPosition) + return; + if (ChangedDataName == FString("value") && MainRampFloatPoint->GetValue() == NewFloat) + return; + if (ChangedDataName == FString("interp") && MainRampFloatPoint->GetInterpolation() == NewInterpType) + return; + + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); + for (auto NextFloatRamp : RampFloatList) + { + if (!NextFloatRamp) + continue; + + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextFloatRamp->Points.IsValidIndex(Index)) + { + UHoudiniParameterRampFloatPoint* CurrentFloatRampPoint = NextFloatRamp->Points[Index]; + if (!CurrentFloatRampPoint) + continue; + + if (ChangedDataName == FString("position")) + { + if (!CurrentFloatRampPoint->PositionParentParm) + continue; + + CurrentFloatRampPoint->SetPosition(NewPosition); + CurrentFloatRampPoint->PositionParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("value")) + { + if (!CurrentFloatRampPoint->PositionParentParm) + continue; + + CurrentFloatRampPoint->SetValue(NewFloat); + CurrentFloatRampPoint->ValueParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("interp")) + { + if (!CurrentFloatRampPoint->InterpolationParentParm) + continue; + + CurrentFloatRampPoint->SetInterpolation(NewInterpType); + CurrentFloatRampPoint->InterpolationParentParm->MarkChanged(true); + } + } + else + { + int32 IdxInEventsArray = Index - NextFloatRamp->Points.Num(); + if (NextFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = NextFloatRamp->ModificationEvents[IdxInEventsArray]; + if (!Event) + continue; + + if (ChangedDataName == FString("position")) + { + Event->InsertPosition = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + Event->InsertFloat = NewFloat; + } + else if (ChangedDataName == FString("interp")) + { + Event->InsertInterpolation = NewInterpType; + } + } + } + } + else + { + if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) + { + UHoudiniParameterRampFloatPoint* CachedPoint = NextFloatRamp->CachedPoints[Index]; + + if (!CachedPoint) + continue; + + if (ChangedDataName == FString("position")) + { + CachedPoint->Position = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + CachedPoint->Value = NewFloat; + } + else if (ChangedDataName == FString("interp")) + { + CachedPoint->Interpolation = NewInterpType; + } + + NextFloatRamp->bCaching = true; + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); + } + } + else if (MainRampColor && MainRampColorPoint) + { + if (ChangedDataName == FString("position") && MainRampColorPoint->GetPosition() == NewPosition) + return; + + if (ChangedDataName == FString("value") && MainRampColorPoint->GetValue() == NewColor) + return; + + if (ChangedDataName == FString("interp") && MainRampColorPoint->GetInterpolation() == NewInterpType) + return; + + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); + for (auto NextColorRamp : RampColorList) + { + if (!NextColorRamp) + continue; + + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextColorRamp->Points.IsValidIndex(Index)) + { + UHoudiniParameterRampColorPoint* CurrentColorRampPoint = NextColorRamp->Points[Index]; + if (!CurrentColorRampPoint) + continue; + + if (ChangedDataName == FString("position")) + { + if (!CurrentColorRampPoint->PositionParentParm) + continue; + + CurrentColorRampPoint->SetPosition(NewPosition); + CurrentColorRampPoint->PositionParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("value")) + { + if (!CurrentColorRampPoint->PositionParentParm) + continue; + + CurrentColorRampPoint->SetValue(NewColor); + CurrentColorRampPoint->ValueParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("interp")) + { + if (!CurrentColorRampPoint->InterpolationParentParm) + continue; + + CurrentColorRampPoint->SetInterpolation(NewInterpType); + CurrentColorRampPoint->InterpolationParentParm->MarkChanged(true); + } + } + else + { + int32 IdxInEventsArray = Index - NextColorRamp->Points.Num(); + if (NextColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = NextColorRamp->ModificationEvents[IdxInEventsArray]; + if (!Event) + continue; + + if (ChangedDataName == FString("position")) + { + Event->InsertPosition = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + Event->InsertColor = NewColor; + } + else if (ChangedDataName == FString("interp")) + { + Event->InsertInterpolation = NewInterpType; + } + + } + } + } + else + { + if (NextColorRamp->CachedPoints.IsValidIndex(Index)) + { + UHoudiniParameterRampColorPoint* CachedPoint = NextColorRamp->CachedPoints[Index]; + + if (!CachedPoint) + continue; + + if (ChangedDataName == FString("position")) + { + CachedPoint->Position = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + CachedPoint->Value = NewColor; + } + else if (ChangedDataName == FString("interp")) + { + CachedPoint->Interpolation = NewInterpType; + } + + NextColorRamp->bCaching = true; + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + for (int32 Index = 0; Index < PointCount; ++Index) + { + UHoudiniParameterRampFloatPoint* NextFloatRampPoint = nullptr; + UHoudiniParameterRampColorPoint* NextColorRampPoint = nullptr; + + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + NextFloatRampPoint = MainFloatRampParameter->Points[Index]; + else + NextFloatRampPoint = MainFloatRampParameter->CachedPoints[Index]; + } + if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NextColorRampPoint = MainColorRampParameter->Points[Index]; + else + NextColorRampPoint = MainColorRampParameter->CachedPoints[Index]; + } + + if (!NextFloatRampPoint && !NextColorRampPoint) + continue; + + RowIndex += 1; + + float CurPos = 0.f; + if (NextFloatRampPoint) + CurPos = NextFloatRampPoint->Position; + else + CurPos = NextColorRampPoint->Position; + + + GridPanel->AddSlot(0, RowIndex) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Value(CurPos) + .OnValueChanged_Lambda([](float Val) {}) + .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("position"), + Val, float(-1.0), + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .OnBeginSliderMovement_Lambda([]() {}) + .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("position"), + Val, float(-1.0), + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .SliderExponent(1.0f) + .TypeInterface(paramTypeInterface) + ]; + + if (NextFloatRampPoint) + { + GridPanel->AddSlot(1, RowIndex) + [ + SNew(SNumericEntryBox< float >) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Value(NextFloatRampPoint->Value) + .OnValueChanged_Lambda([](float Val){}) + .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), Val, + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .OnBeginSliderMovement_Lambda([]() {}) + .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), Val, + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .SliderExponent(1.0f) + .TypeInterface(paramTypeInterface) + ]; + } + else if (NextColorRampPoint) + { + auto OnColorChangeLambda = [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](FLinearColor InColor) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), float(-1.0), + InColor, + EHoudiniRampInterpolationType::LINEAR); + }; + + // Add color picker UI. + //TSharedPtr ColorBlock; + GridPanel->AddSlot(1, RowIndex) + [ + SNew(SColorBlock) + .Color(NextColorRampPoint->Value) + .OnMouseButtonDown( FPointerEventHandler::CreateLambda( + [NextColorRampPoint, OnColorChangeLambda](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) + { + if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) + return FReply::Unhandled(); + + FColorPickerArgs PickerArgs; + PickerArgs.bUseAlpha = true; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda(OnColorChangeLambda); + FLinearColor InitColor = NextColorRampPoint->Value; + PickerArgs.InitialColorOverride = InitColor; + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker(PickerArgs); + return FReply::Handled(); + })) + ]; + } + + int32 CurChoice = 0; + if (NextFloatRampPoint) + CurChoice = (int)NextFloatRampPoint->Interpolation; + else + CurChoice = (int)NextColorRampPoint->Interpolation; + + TSharedPtr >> ComboBoxCurveMethod; + GridPanel->AddSlot(2, RowIndex) + [ + SAssignNew(ComboBoxCurveMethod, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels())[CurChoice]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, + ColorRampParameterList, Index](TSharedPtr NewChoice, ESelectInfo::Type SelectType) mutable + { + EHoudiniRampInterpolationType NewInterpType = UHoudiniParameter::GetHoudiniInterpMethodFromString(*NewChoice.Get()); + + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("interp"), + float(-1.0), float(-1.0), + FLinearColor(), + NewInterpType); + }) + [ + SNew(STextBlock) + .Text_Lambda([NextFloatRampPoint, NextColorRampPoint]() + { + EHoudiniRampInterpolationType CurInterpType = EHoudiniRampInterpolationType::InValid; + if (NextFloatRampPoint) + CurInterpType = NextFloatRampPoint->GetInterpolation(); + else + CurInterpType = NextColorRampPoint->GetInterpolation(); + + return FText::FromString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(CurInterpType)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + GridPanel->AddSlot(3, RowIndex) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateLambda( + [InsertRampPoint_Lambda, MainFloatRampParameter, + MainColorRampParameter, FloatRampParameterList, + ColorRampParameterList, Index]() mutable + { + InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); + }), + LOCTEXT("AddRampPoint", "Add a ramp point before this point"), true) + ] + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateLambda( + [DeleteRampPoint_Lambda, MainFloatRampParameter, + MainColorRampParameter, FloatRampParameterList, + ColorRampParameterList, Index]() mutable + { + DeleteRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); + }), + LOCTEXT("DeleteFloatRampPoint", "Delete this ramp point"), true) + ] + ]; + + if (MainFloatRampParameter && CurrentRampParameterFloatCurve) + { + ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextFloatRampPoint->GetInterpolation()); + FRichCurve & RichCurve = CurrentRampParameterFloatCurve->FloatCurve; + FKeyHandle const KeyHandle = RichCurve.AddKey(NextFloatRampPoint->GetPosition(), NextFloatRampPoint->GetValue()); + RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); + } + + if (MainColorRampParameter && CurrentRampParameterColorCurve) + { + ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextColorRampPoint->GetInterpolation()); + for (int32 CurveIdx = 0; CurveIdx < 4; ++CurveIdx) + { + FRichCurve & RichCurve = CurrentRampParameterColorCurve->FloatCurves[CurveIdx]; + + FKeyHandle const KeyHandle = RichCurve.AddKey(NextColorRampPoint->GetPosition(), NextColorRampPoint->GetValue().Component(CurveIdx)); + RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); + } + } + } + + if (MainFloatRampParameter) + GridPanel->SetEnabled(!MainFloatRampParameter->IsDisabled()); + + if (MainColorRampParameter) + GridPanel->SetEnabled(!MainColorRampParameter->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetFolderList(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FolderListParams; + if (!CastParameters(InParams, FolderListParams)) + return; + + if (FolderListParams.Num() <= 0) + return; + + UHoudiniParameterFolderList* MainParam = FolderListParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Add this folder list to the folder map + AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); + + MainParam->GetTabs().Empty(); + + // A folder list will be followed by all its child folders, + // so set the CurrentFolderListSize to the tuple size, we'll process such many folder parameters right after + CurrentFolderListSize = MainParam->GetTupleSize(); + + if (MainParam->IsDirectChildOfMultiParm()) + MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); + + if (CurrentFolderListSize <= 0) // There should not be empty folder list, this will not happen normally + return; + + // The following folders belong to current folder list + CurrentFolderList = MainParam; + + // If the tab is either a tabs or radio button and the parameter is visible + if (MainParam->IsTabMenu() && MainParam->ShouldDisplay()) + { + // Set the current tabs to be not shown by default now. CreateWidgetTab will decide if the tabs is shown. + CurrentFolderList->SetTabsShown(false); + + // Create a row to hold tab buttons if the folder list is a tabs or radio button + + // CreateNestedRow does not actually create a row for tabs, it is responsible to prune the folder stack. + // ( CreateWidgetTab will be responsible to create a row according to the visibility of its outer level folders ) + FDetailWidgetRow* TabRow = CreateNestedRow(HouParameterCategory, InParams, false); + + } + + // When see a folder list, go depth first search at this step. + // Push an empty queue to the stack. + FolderStack.Add(TArray()); +} + + +void +FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FolderParams; + if (!CastParameters(InParams, FolderParams)) + return; + + if (FolderParams.Num() <= 0) + return; + + UHoudiniParameterFolder* MainParam = FolderParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen + return; + // If a folder is invisible, its children won't be listed by HAPI. + // So just reduce FolderListSize by 1, reduce the child counter of its parent folder by 1 if necessary, + // and prune the stack in such case. + if (!MainParam->IsVisible()) + { + CurrentFolderListSize -= 1; + + if (CurrentFolderListSize == 0) + { + if (FolderStack.Num() > 1) + { + TArray &ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + ParentFolderQueue[0]->GetChildCounter() -= 1; + } + + PruneStack(); + } + + return; + } + + // We expect 'TupleSize' children param of this folder after finish processing all the child folders of cur folderlist + MainParam->ResetChildCounter(); + + // Add this folder to the folder map + AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); + + // Set the parent param to current folderList. + // it was parent multiparm's id if this folder is a child of a multiparms. + // This will cause problem if the folder is inside of a multiparm + MainParam->SetParentParmId(CurrentFolderList->GetParmId()); + + + // Case 1: The folder is a direct child of a multiparm. + if (MainParam->IsDirectChildOfMultiParm()) + { + if (FolderStack.Num() <= 0) // This should not happen + return; + + // Get its parent multiparm first + UHoudiniParameterMultiParm* ParentMultiParm = nullptr; + { + UHoudiniParameterFolderList * ParentFolderList = nullptr; + if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) + return; + + ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); + + if (!ParentFolderList) + return; + + if (AllMultiParms.Contains(ParentFolderList->GetParentParmId())) + ParentMultiParm = AllMultiParms[ParentFolderList->GetParentParmId()]; + + if (!ParentMultiParm) // This should not happen + return; + } + + bool bShown = ParentMultiParm->IsShown(); + + // Case 1-1: The folder is NOT tabs + if (!MainParam->IsTab()) + { + bShown = MainParam->IsExpanded() && bShown; + + // If the parent multiparm is shown. + if (ParentMultiParm->IsShown()) + { + FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderHeaderRow, InParams); + } + } + // Case 1-2: The folder IS tabs. + else + { + CreateWidgetTab(HouParameterCategory, MainParam, ParentMultiParm->IsShown()); + } + + // Push the folder to the queue if it is not a tab folder + // This step is handled by CreateWidgetTab() if it is tabs + if ((!MainParam->IsTab() || !ParentMultiParm->IsShown()) && MainParam->GetTupleSize() > 0) + { + TArray & MyQueue = FolderStack.Last(); + MainParam->SetIsContentShown(bShown); + MyQueue.Add(MainParam); + } + } + + // Case 2: The folder is NOT a direct child of a multiparm. + else + { + // Case 2-1: The folder is in another folder. + if (FolderStack.Num() > 1 && CurrentFolderListSize > 0) + { + TArray & MyFolderQueue = FolderStack.Last(); + TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + + if (ParentFolderQueue.Num() <= 0) //This should happen + return; + + // Peek the folder queue of the last layer to get its parent folder parm. + bool ParentFolderVisible = ParentFolderQueue[0]->IsContentShown(); + + // If this folder is expanded (selected if is tabs) + bool bExpanded = ParentFolderVisible; + + // Case 2-1-1: The folder is NOT in a tab menu. + if (!MainParam->IsTab()) + { + bExpanded &= MainParam->IsExpanded(); + + // The parent folder is visible. + if (ParentFolderVisible) + { + // Add the folder header UI. + FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderHeaderRow, InParams); + } + + MainParam->SetIsContentShown(bExpanded); + MyFolderQueue.Add(MainParam); + } + // Case 2-1-2: The folder IS in a tab menu. + else + { + bExpanded &= MainParam->IsChosen(); + + CreateWidgetTab(HouParameterCategory, MainParam, ParentFolderVisible); + } + } + // Case 2-2: The folder is in the root. + else + { + bool bExpanded = true; + + // Case 2-2-1: The folder is NOT under a tab menu. + if (!MainParam->IsTab()) + { + if (FolderStack.Num() <= 0) // This will not happen + return; + + // Create Folder header under root. + FDetailWidgetRow* FolderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderRow, InParams); + + if (FolderStack.Num() == 0) // This should not happen + return; + + TArray& MyFolderQueue = FolderStack[0]; + bExpanded &= MainParam->IsExpanded(); + MainParam->SetIsContentShown(bExpanded); + MyFolderQueue.Add(MainParam); + } + // Case 2-2-2: The folder IS under a tab menu. + else + { + // Tabs in root is always visible + CreateWidgetTab(HouParameterCategory, MainParam, true); + } + } + } + + + CurrentFolderListSize -= 1; + + // Prune the stack if finished parsing current folderlist + if (CurrentFolderListSize == 0) + { + if (FolderStack.Num() > 1 && !MainParam->IsDirectChildOfMultiParm()) + { + TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + ParentFolderQueue[0]->GetChildCounter() -= 1; + } + + PruneStack(); + + CurrentFolderList = nullptr; + } +} + +void +FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray &InParams) +{ + if (!HeaderRow) // The folder is invisible. + return; + + TArray FolderParams; + if (!CastParameters(InParams, FolderParams)) + return; + + if (FolderParams.Num() <= 0) + return; + + UHoudiniParameterFolder* MainParam = FolderParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return; + + TSharedPtr VerticalBox; + + FString LabelStr = MainParam->GetParameterLabel(); + + TSharedPtr HorizontalBox; + TSharedPtr ExpanderArrow; + TSharedPtr ExpanderImage; + + HeaderRow->NameWidget.Widget = SAssignNew(HorizontalBox, SCustomizedBox); + + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + + if (MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 1) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + LabelStr = LabelStr + TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex) + TEXT(")"); + } + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SAssignNew(ExpanderArrow, SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([=]() + { + MainParam->ExpandButtonClicked(); + + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + + return FReply::Handled(); + }) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + + FText LabelText = FText::FromString(LabelStr); + + HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SNew(STextBlock) + .Text(LabelText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + ExpanderImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([=]() { + FName ResourceName; + if(MainParam->IsExpanded()) + { + ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + }))); + + if(MainParam->GetFolderType() == EHoudiniFolderParameterType::Simple) + ExpanderArrow->SetEnabled(false); + +} + +void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InFolder, const bool& bIsShown) +{ + if (!InFolder || InFolder->IsPendingKill() || !CurrentFolderList) + return; + + if (FolderStack.Num() <= 0) // error state + return; + + TArray & FolderQueue = FolderStack.Last(); + + // Cache all tabs of current tab folder list. + CurrentFolderList->AddTabFolder(InFolder); + + // If the tabs is not shown, just push the folder param into the queue. + if (!bIsShown) + { + InFolder->SetIsContentShown(bIsShown); + FolderQueue.Add(InFolder); + return; + } + + // tabs currently being processed + CurrentTabs.Add(InFolder); + + if (CurrentFolderListSize > 1) + return; + + // The tabs belong to current folder list + UHoudiniParameterFolderList* CurrentTabMenuFolderList = CurrentFolderList; + + // Create a row (UI) for current tabs + TSharedPtr HorizontalBox; + FDetailWidgetRow &Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()) + [ + SAssignNew(HorizontalBox, SCustomizedBox) + ]; + + // Put current tab folder list param into an array + TArray CurrentTabMenuFolderListArr; + CurrentTabMenuFolderListArr.Add(CurrentTabMenuFolderList); + + HorizontalBox->SetHoudiniParameter(CurrentTabMenuFolderListArr); + DividerLinePositions.Add(HorizontalBox->AddIndentation(InFolder, AllMultiParms, AllFoldersAndFolderLists)); + HorizontalBox->DividerLinePositions = DividerLinePositions; + + float DesiredHeight = 0.0f; + float DesiredWidth = 0.0f; + + // Process all tabs of current folder list at once when done. + + for (auto & CurTab : CurrentTabs) + { + if (!CurTab || CurTab->IsPendingKill()) + continue; + + CurTab->SetIsContentShown(CurTab->IsChosen()); + FolderQueue.Add(CurTab); + + auto OnTabClickedLambda = [CurrentTabMenuFolderList, CurTab, &HouParameterCategory]() + { + if (CurrentTabMenuFolderList) + { + if (!CurrentTabMenuFolderList->bIsTabMenu || CurrentTabMenuFolderList->TabFolders.Num() <= 0) + return FReply::Handled(); + + if (CurTab->IsChosen()) + return FReply::Handled(); + + CurTab->SetChosen(true); + + for (UHoudiniParameterFolder* NextFolder : CurrentTabMenuFolderList->TabFolders) + { + if (CurTab->GetParmId() != NextFolder->GetParmId() && NextFolder->IsChosen()) + NextFolder->SetChosen(false); + } + + HouParameterCategory.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); + }; + + FString FolderLabelString = TEXT(" ") + CurTab->GetParameterLabel(); + if (CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio) + FolderLabelString = TEXT(" ") + FolderLabelString; + + bool bChosen = CurTab->IsTab() && CurTab->IsChosen(); + + TSharedPtr CurCustomizedButton; + + HorizontalBox->AddSlot().VAlign(VAlign_Bottom) + .AutoWidth() + .Padding(0.f) + .HAlign(HAlign_Left) + [ + SAssignNew(CurCustomizedButton, SCustomizedButton) + .OnClicked_Lambda(OnTabClickedLambda) + .Content() + [ + SNew(STextBlock) + .Text(FText::FromString(FolderLabelString)) + ] + ]; + + CurCustomizedButton->bChosen = bChosen; + CurCustomizedButton->bIsRadioButton = CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio; + + DesiredHeight = CurCustomizedButton->GetDesiredSize().Y; + DesiredWidth += CurCustomizedButton->GetDesiredSize().X; + } + + HorizontalBox->bIsTabFolderListRow = true; + + Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + // Set the current tabs to be shown, since slate widgets have been created + CurrentTabMenuFolderList->SetTabsShown(true); + + // Clear the temporary tabs + CurrentTabs.Empty(); +} + +void +FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray MultiParmParams; + if (!CastParameters(InParams, MultiParmParams)) + return; + + if (MultiParmParams.Num() <= 0) + return; + + UHoudiniParameterMultiParm* MainParam = MultiParmParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Add current multiparm parameter to AllmultiParms map + AllMultiParms.Add(MainParam->GetParmId(), MainParam); + + // Create a new detail row + FDetailWidgetRow * Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + { + MainParam->SetIsShown(false); + return; + } + + MainParam->SetIsShown(true); + + MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); + + CreateNameWidget(Row, InParams, true); + + auto OnInstanceValueChangedLambda = [MainParam](int32 InValue) + { + if (InValue < 0) + return; + + int32 ChangesCount = FMath::Abs(MainParam->MultiParmInstanceLastModifyArray.Num() - InValue); + + if (MainParam->MultiParmInstanceLastModifyArray.Num() > InValue) + { + for (int32 Idx = 0; Idx < ChangesCount; ++Idx) + MainParam->RemoveElement(-1); + + MainParam->MarkChanged(true); + } + else if (MainParam->MultiParmInstanceLastModifyArray.Num() < InValue) + { + for (int32 Idx = 0; Idx < ChangesCount; ++Idx) + MainParam->InsertElement(); + + MainParam->MarkChanged(true); + } + }; + + // Add multiparm UI. + TSharedRef HorizontalBox = SNew(SHorizontalBox); + TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; + int32 NumericalCount = MainParam->MultiParmInstanceCount; + HorizontalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateLambda([OnInstanceValueChangedLambda](int32 InValue) { + OnInstanceValueChangedLambda(InValue); + })) + .Value(NumericalCount) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamAddInstance", "Houdini Parameter Multi Parameter: Adding an instance"), + MainParam->GetOuter(), true); + + for (auto& Param : MultiParmParams) + { + if (!Param) + continue; + + // Add a reverse step for redo/undo + Param->MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Removed); + + Param->MarkChanged(true); + Param->Modify(); + + if (Param->MultiParmInstanceLastModifyArray.Num() > 0) + Param->MultiParmInstanceLastModifyArray.RemoveAt(Param->MultiParmInstanceLastModifyArray.Num() - 1); + + Param->InsertElement(); + + } + }), + LOCTEXT("AddMultiparmInstanceToolTipAddLastInstance", "Add an Instance"), true) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + // Remove the last multiparm instance + PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamDeleteInstance", "Houdini Parameter Multi Parameter: Deleting an instance"), + MainParam->GetOuter(), true); + + for (auto & Param : MultiParmParams) + { + TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; + int32 RemovedIndex = LastModifiedArray.Num() - 1; + while (LastModifiedArray.IsValidIndex(RemovedIndex) && LastModifiedArray[RemovedIndex] == EHoudiniMultiParmModificationType::Removed) + RemovedIndex -= 1; + + // Add a reverse step for redo/undo + EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; + if (LastModifiedArray.IsValidIndex(RemovedIndex)) + { + PreviousModType = LastModifiedArray[RemovedIndex]; + LastModifiedArray[RemovedIndex] = EHoudiniMultiParmModificationType::Inserted; + } + + Param->MarkChanged(true); + + Param->Modify(); + + if (LastModifiedArray.IsValidIndex(RemovedIndex)) + { + LastModifiedArray[RemovedIndex] = PreviousModType; + } + + Param->RemoveElement(RemovedIndex); + } + + }), + LOCTEXT("RemoveLastMultiParamLastToolTipRemoveLastInstance", "Remove the last instance"), true) + + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamDeleteAllInstances", "Houdini Parameter Multi Parameter: Deleting all instances"), + MainParam->GetOuter(), true); + + for (auto & Param : MultiParmParams) + { + TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; + TArray IndicesToReverse; + + for (int32 Index = 0; Index < LastModifiedArray.Num(); ++Index) + { + if (LastModifiedArray[Index] == EHoudiniMultiParmModificationType::None) + { + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; + IndicesToReverse.Add(Index); + } + } + + Param->MarkChanged(true); + + Param->Modify(); + + for (int32 & Index : IndicesToReverse) + { + if (LastModifiedArray.IsValidIndex(Index)) + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::None; + } + + + Param->EmptyElements(); + } + + }), + LOCTEXT("HoudiniParameterRemoveAllMultiparmInstancesToolTip", "Remove all instances"), true) + ]; + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniParameterDetails::CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams) +{ + + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return; + + if (!HorizontalBox || !AllMultiParms.Contains(MainParam->GetParentParmId()) || !MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + return; + + UHoudiniParameterMultiParm* MainParentMultiParm = AllMultiParms[MainParam->GetParentParmId()]; + + if (!MainParentMultiParm) + return; + + if (!MainParentMultiParm->IsShown()) + return; + + // push all parent multiparm of the InParams to the array + TArray ParentMultiParams; + for (auto & InParam : InParams) + { + if (!InParam) + continue; + + if (!MultiParmInstanceIndices.Contains(InParam->GetParentParmId())) + continue; + + if (InParam->GetChildIndex() == 0) + { + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[InParam->GetParentParmId()]; + + if (ParentMultiParm) + ParentMultiParams.Add(ParentMultiParm); + } + } + + + int32 InstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + + TSharedRef AddButton = PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() + { + for (auto & ParentParam : ParentMultiParams) + { + // Add button call back + if (!ParentParam) + continue; + + TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; + + if (!LastModifiedArray.IsValidIndex(InstanceIndex)) + continue; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParmAddBeforeCurInstance", "Houdini Parameter Multi Parm: Adding an instance"), + ParentParam->GetOuter(), true); + + + int32 Index = InstanceIndex; + + // Add a reverse step for undo/redo + if (Index >= LastModifiedArray.Num()) + LastModifiedArray.Add(EHoudiniMultiParmModificationType::Removed); + else + LastModifiedArray.Insert(EHoudiniMultiParmModificationType::Removed, Index); + + ParentParam->MarkChanged(true); + ParentParam->Modify(); + + if (Index >= LastModifiedArray.Num() - 1 && LastModifiedArray.Num()) + LastModifiedArray.RemoveAt(LastModifiedArray.Num() - 1); + else + LastModifiedArray.RemoveAt(Index); + + ParentParam->InsertElementAt(InstanceIndex); + + } + }), + LOCTEXT("HoudiniParameterMultiParamAddBeforeCurrentInstanceToolTip", "Insert an instance before this instance")); + + + TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() + { + for (auto & ParentParam : ParentMultiParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParmDeleteCurInstance", "Houdini Parameter Multi Parm: Deleting an instance"), + ParentParam->GetOuter(), true); + + + TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; + + int32 Index = InstanceIndex; + EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; + while (LastModifiedArray.IsValidIndex(Index) && LastModifiedArray[Index] == EHoudiniMultiParmModificationType::Removed) + { + Index -= 1; + } + + if (LastModifiedArray.IsValidIndex(Index)) + { + PreviousModType = LastModifiedArray[Index]; + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; + } + + ParentParam->MarkChanged(true); + + ParentParam->Modify(); + + if (LastModifiedArray.IsValidIndex(Index)) + { + LastModifiedArray[Index] = PreviousModType; + } + + ParentParam->RemoveElement(InstanceIndex); + } + + }), + LOCTEXT("HoudiniParameterMultiParamDeleteCurrentInstanceToolTip", "Remove an instance"), true); + + + HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[AddButton]; + HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[RemoveButton]; + + int32 StartIdx = MainParam->GetParameterType() == EHoudiniParameterType::Folder ? 1 : 0; + if (MainParam->GetChildIndex() != StartIdx) + { + AddButton.Get().SetVisibility(EVisibility::Hidden); + RemoveButton.Get().SetVisibility(EVisibility::Hidden); + } + +} + +void +FHoudiniParameterDetails::PruneStack() +{ + for (int32 StackItr = FolderStack.Num() - 1; StackItr >= 0; --StackItr) + { + TArray &CurrentQueue = FolderStack[StackItr]; + + for (int32 QueueItr = CurrentQueue.Num() - 1; QueueItr >= 0; --QueueItr) + { + UHoudiniParameterFolder * CurrentFolder = CurrentQueue[QueueItr]; + if (!CurrentFolder || CurrentFolder->IsPendingKill()) + continue; + + if (CurrentFolder->GetChildCounter() == 0) + { + CurrentQueue.RemoveAt(QueueItr); + } + } + + if (CurrentQueue.Num() == 0) + { + FolderStack.RemoveAt(StackItr); + } + } +} + +FText +FHoudiniParameterDetails::GetParameterTooltip(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return FText(); + + // Tooltip starts with Label (name) + FString Tooltip = InParam->GetParameterLabel() + TEXT(" (") + InParam->GetParameterName() + TEXT(")"); + + // Append the parameter type + FString ParmTypeStr = GetParameterTypeString(InParam->GetParameterType(), InParam->GetTupleSize()); + if (!ParmTypeStr.IsEmpty()) + Tooltip += TEXT("\n") + ParmTypeStr; + + // If the parameter has some help, append it + FString Help = InParam->GetParameterHelp(); + if (!Help.IsEmpty()) + Tooltip += TEXT("\n") + Help; + + // If the parameter has an expression, append it + if (InParam->HasExpression()) + { + FString Expr = InParam->GetExpression(); + if (!Expr.IsEmpty()) + Tooltip += TEXT("\nExpression: ") + Expr; + } + + return FText::FromString(Tooltip); +} + +FString +FHoudiniParameterDetails::GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize) +{ + FString ParamStr; + + switch (InType) + { + case EHoudiniParameterType::Button: + ParamStr = TEXT("Button"); + break; + + case EHoudiniParameterType::ButtonStrip: + ParamStr = TEXT("Button Strip"); + break; + + case EHoudiniParameterType::Color: + { + if (InTupleSize == 4) + ParamStr = TEXT("Color with Alpha"); + else + ParamStr = TEXT("Color"); + } + break; + + case EHoudiniParameterType::ColorRamp: + ParamStr = TEXT("Color Ramp"); + break; + + case EHoudiniParameterType::File: + ParamStr = TEXT("File (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileDir: + ParamStr = TEXT("File Dir (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileGeo: + ParamStr = TEXT("File Geo (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileImage: + ParamStr = TEXT("File Image (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::Float: + ParamStr = TEXT("Float (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); + break; + + case EHoudiniParameterType::FloatRamp: + ParamStr = TEXT("Float Ramp"); + break; + + case EHoudiniParameterType::Folder: + case EHoudiniParameterType::FolderList: + break; + + case EHoudiniParameterType::Input: + ParamStr = TEXT("Opearator Path"); + break; + + case EHoudiniParameterType::Int: + ParamStr = TEXT("Integer (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); + break; + + case EHoudiniParameterType::IntChoice: + ParamStr = TEXT("Int Choice"); + break; + + case EHoudiniParameterType::Label: + ParamStr = TEXT("Label (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::MultiParm: + ParamStr = TEXT("MultiParm"); + break; + + case EHoudiniParameterType::Separator: + break; + + case EHoudiniParameterType::String: + ParamStr = TEXT("String (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::StringAssetRef: + ParamStr = TEXT("String Asset Ref (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::StringChoice: + ParamStr = TEXT("String Choice"); + break; + + case EHoudiniParameterType::Toggle: + ParamStr = TEXT("Toggle (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + default: + ParamStr = TEXT("invalid parameter type"); + break; + } + + + return ParamStr; +} + +void +FHoudiniParameterDetails::SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter) +{ + if (!ColorRampParameter) + return; + + // Do not sync when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) + return; + + TArray &CachedPoints = ColorRampParameter->CachedPoints; + TArray &CurrentPoints = ColorRampParameter->Points; + + bool bCurveNeedsUpdate = false; + bool bRampParmNeedsUpdate = false; + + int32 Idx = 0; + + while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) + { + UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[Idx]; + UHoudiniParameterRampColorPoint* CurrentPoint = CurrentPoints[Idx]; + + if (!CachedPoint || !CurrentPoint) + continue; + + if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) + { + if (CurrentPoint->PositionParentParm) + { + CurrentPoint->SetPosition(CachedPoint->GetPosition()); + CurrentPoint->PositionParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + if (CachedPoint->GetValue() != CurrentPoint->GetValue()) + { + if (CurrentPoint->ValueParentParm) + { + CurrentPoint->SetValue(CachedPoint->GetValue()); + CurrentPoint->ValueParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) + { + if (CurrentPoint->InterpolationParentParm) + { + CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); + CurrentPoint->InterpolationParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + Idx += 1; + } + + // Insert points + for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) + { + UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[IdxCachedPointLeft]; + + CreateColorRampParameterInsertEvent( + ColorRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); + + bCurveNeedsUpdate = true; + bRampParmNeedsUpdate = true; + + } + + // Delete points + for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) + { + ColorRampParameter->RemoveElement(IdxCurrentPointLeft); + + UHoudiniParameterRampColorPoint* Point = CurrentPoints[IdxCurrentPointLeft]; + + if (!Point) + continue; + + CreateColorRampParameterDeleteEvent(ColorRampParameter, Point->InstanceIndex); + + bCurveNeedsUpdate = true; + bRampParmNeedsUpdate = true; + } + + + ColorRampParameter->MarkChanged(bRampParmNeedsUpdate); +} + +//void +//FHoudiniParameterDetails::SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter) +//{ +// if (!FloatRampParameter) +// return; +// +// // Do not sync when the Houdini asset component is cooking +// if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) +// return; +// +// TArray &CachedPoints = FloatRampParameter->CachedPoints; +// TArray &CurrentPoints = FloatRampParameter->Points; +// +// int32 Idx = 0; +// +// while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) +// { +// UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; +// UHoudiniParameterRampFloatPoint* &CurrentPoint = CurrentPoints[Idx]; +// +// if (!CachedPoint || !CurrentPoint) +// continue; +// +// if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) +// { +// if (CurrentPoint->PositionParentParm) +// { +// CurrentPoint->SetPosition(CachedPoint->GetPosition()); +// CurrentPoint->PositionParentParm->MarkChanged(true); +// } +// } +// +// if (CachedPoint->GetValue() != CurrentPoint->GetValue()) +// { +// if (CurrentPoint->ValueParentParm) +// { +// CurrentPoint->SetValue(CachedPoint->GetValue()); +// CurrentPoint->ValueParentParm->MarkChanged(true); +// } +// } +// +// if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) +// { +// if (CurrentPoint->InterpolationParentParm) +// { +// CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); +// CurrentPoint->InterpolationParentParm->MarkChanged(true); +// } +// } +// +// Idx += 1; +// } +// +// // Insert points +// for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) +// { +// UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; +// if (!CachedPoint) +// continue; +// +// CreateFloatRampParameterInsertEvent( +// FloatRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); +// +// FloatRampParameter->MarkChanged(true); +// } +// +// // Remove points +// for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) +// { +// FloatRampParameter->RemoveElement(IdxCurrentPointLeft); +// +// UHoudiniParameterRampFloatPoint* Point = CurrentPoints[IdxCurrentPointLeft]; +// +// if (!Point) +// continue; +// +// CreateFloatRampParameterDeleteEvent(FloatRampParameter, Point->InstanceIndex); +// +// FloatRampParameter->MarkChanged(true); +// } +//} + +void +FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetFloatRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + InParam->ModificationEvents.Add(DeleteEvent); +} + +void +FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetColorRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + InParam->ModificationEvents.Add(DeleteEvent); +} + +void +FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, + const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetFloatRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertFloat = InValue; + InsertEvent->InsertInterpolation = InInterp; + + InParam->ModificationEvents.Add(InsertEvent); +} + +void +FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, + const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetColorRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertColor = InColor; + InsertEvent->InsertInterpolation = InInterp; + + InParam->ModificationEvents.Add(InsertEvent); +} + +void +FHoudiniParameterDetails:: ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters) +{ + if (FloatRampParameters.Num() < 1) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0]; + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) + { + UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx]; + + if (!NextFloatRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); + } +} + +void +FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters) +{ + if (FloatRampParameters.Num() < 1) + return; + + if (!FloatRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + + for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) + { + if (!FloatRampParameters[Idx].IsValid()) + continue; + + UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx].Get(); + + if (!NextFloatRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); + } + +} + +void +FHoudiniParameterDetails:: ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam) +{ + if (!Param || !MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) + return; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Use Synced points if the MainParam is on auto update mode + // Use Cached points if the Mainparam is on manual update mode + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + if (Param->IsAutoUpdate() && bCookingEnabled) + { + TArray & Points = Param->Points; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (MainPoint->GetPosition() != Point->GetPosition()) + { + if (Point->PositionParentParm) + { + Point->SetPosition(MainPoint->GetPosition()); + Point->PositionParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetValue() != Point->GetValue()) + { + if (Point->ValueParentParm) + { + Point->SetValue(MainPoint->GetValue()); + Point->ValueParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetInterpolation() != Point->GetInterpolation()) + { + if (Point->InterpolationParentParm) + { + Point->SetInterpolation(MainPoint->GetInterpolation()); + Point->InterpolationParentParm->MarkChanged(true); + } + } + + PointIdx += 1; + } + + int32 PointInsertIdx = PointIdx; + int32 PointDeleteIdx = PointIdx; + + // skip the pending modification events + for (auto & Event : Param->ModificationEvents) + { + if (!Event) + continue; + + if (Event->IsInsertEvent()) + PointInsertIdx += 1; + + if (Event->IsDeleteEvent()) + PointDeleteIdx += 1; + } + + // There are more points in MainPoints array + for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) + { + UHoudiniParameterRampFloatPoint*& NextMainPoint = MainPoints[PointInsertIdx]; + + if (!NextMainPoint) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(Param, + NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); + + Param->MarkChanged(true); + } + + // There are more points in Points array + for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) + { + UHoudiniParameterRampFloatPoint*& NextPoint = Points[PointDeleteIdx]; + + if (!NextPoint) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); + + Param->MarkChanged(true); + } + + } + else + { + TArray &Points = Param->CachedPoints; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (Point->Position != MainPoint->Position) + { + Point->Position = MainPoint->Position; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->PositionParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Value != MainPoint->Value) + { + Point->Value = MainPoint->Value; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->ValueParentParm) + Point->ValueParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Interpolation != MainPoint->Interpolation) + { + Point->Interpolation = MainPoint->Interpolation; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->InterpolationParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + PointIdx += 1; + } + + // There are more points in MainPoints array + for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) + { + UHoudiniParameterRampFloatPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; + + if (!NextMainPoint) + continue; + + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampFloatPoint::StaticClass()); + + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = NextMainPoint->GetPosition(); + NewCachedPoint->Value = NextMainPoint->GetValue(); + NewCachedPoint->Interpolation = NextMainPoint->GetInterpolation(); + + Points.Add(NewCachedPoint); + + Param->bCaching = true; + } + + // there are more points in Points array + for (int32 PointsLeftIdx = PointIdx; PointIdx < Points.Num(); ++PointIdx) + { + Points.Pop(); + Param->bCaching = true; + } + } + +} + + +void +FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters) +{ + if (ColorRampParameters.Num() < 1) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0]; + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) + { + UHoudiniParameterRampColor* NextColorRampParam = ColorRampParameters[Idx]; + + if (!NextColorRampParam) + continue; + + FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParam); + } +} + +void +FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters) +{ + if (ColorRampParameters.Num() < 1) + return; + + if (!ColorRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) + { + if (!ColorRampParameters[Idx].IsValid()) + continue; + + UHoudiniParameterRampColor* NextColorRampParameter = ColorRampParameters[Idx].Get(); + + if (!NextColorRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParameter); + + } + +} + +void +FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParam) +{ + if (!Param || !MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) + return; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Use Synced points if the MainParam is on auto update mode + // Use Cached points if the Mainparam is on manual update mode + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + if (Param->IsAutoUpdate() && bCookingEnabled) + { + TArray & Points = Param->Points; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (MainPoint->GetPosition() != Point->GetPosition()) + { + if (Point->PositionParentParm) + { + Point->SetPosition(MainPoint->GetPosition()); + Point->PositionParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetValue() != Point->GetValue()) + { + if (Point->ValueParentParm) + { + Point->SetValue(MainPoint->GetValue()); + Point->ValueParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetInterpolation() != Point->GetInterpolation()) + { + if (Point->InterpolationParentParm) + { + Point->SetInterpolation(MainPoint->GetInterpolation()); + Point->InterpolationParentParm->MarkChanged(true); + } + } + + PointIdx += 1; + + } + + int32 PointInsertIdx = PointIdx; + int32 PointDeleteIdx = PointIdx; + + // skip the pending modification events + for (auto & Event : Param->ModificationEvents) + { + if (!Event) + continue; + + if (Event->IsInsertEvent()) + PointInsertIdx += 1; + + if (Event->IsDeleteEvent()) + PointDeleteIdx += 1; + } + + // There are more points in MainPoints array + for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) + { + UHoudiniParameterRampColorPoint*& NextMainPoint = MainPoints[PointInsertIdx]; + + if (!NextMainPoint) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(Param, + NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); + + Param->MarkChanged(true); + } + + // There are more points in Points array + for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) + { + UHoudiniParameterRampColorPoint*& NextPoint = Points[PointDeleteIdx]; + + if (!NextPoint) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); + + Param->MarkChanged(true); + } + } + else + { + TArray &Points = Param->CachedPoints; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (Point->Position != MainPoint->Position) + { + Point->Position = MainPoint->Position; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->PositionParentParm) + Point->PositionParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Value != MainPoint->Value) + { + Point->Value = MainPoint->Value; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->ValueParentParm) + Point->ValueParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Interpolation != MainPoint->Interpolation) + { + Point->Interpolation = MainPoint->Interpolation; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->InterpolationParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + PointIdx += 1; + } + + // There are more points in Main Points array. + for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) + { + UHoudiniParameterRampColorPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; + + if (!NextMainPoint) + continue; + + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampColorPoint::StaticClass()); + + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = NextMainPoint->Position; + NewCachedPoint->Value = NextMainPoint->Value; + NewCachedPoint->Interpolation = NextMainPoint->Interpolation; + + Points.Add(NewCachedPoint); + + Param->bCaching = true; + } + + // There are more points in Points array + for (int32 PointsleftIdx = PointIdx; PointIdx < MainPoints.Num(); ++PointsleftIdx) + { + Points.Pop(); + + Param->bCaching = true; + } + } +} + +// Check recussively if a parameter hits the end of a visible tabs +void +FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + // When the paramId is invalid, the directory won't parse. + // So simply return the function + if (InParam->GetParmId() < 0) + return; + + // Do not end the tab if this param is a non empty parent type, leave it to its children + EHoudiniParameterType ParmType = InParam->GetParameterType(); + if ((ParmType == EHoudiniParameterType::FolderList || + ParmType == EHoudiniParameterType::Folder) && InParam->GetTupleSize() > 0) + return; + + if (ParmType == EHoudiniParameterType::MultiParm) + { + UHoudiniParameterMultiParm * InMultiParm = Cast(InParam); + if (!InMultiParm) + return; + + if (InMultiParm->MultiParmInstanceCount * InMultiParm->MultiParmInstanceLength > 0) + return; + } + + int32 ParentParamId = InParam->GetParentParmId(); + UHoudiniParameter* CurParam = InParam; + + while (AllFoldersAndFolderLists.Contains(ParentParamId) || AllMultiParms.Contains(ParentParamId)) + { + // The parent is a multiparm + if (AllMultiParms.Contains(ParentParamId)) + { + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentParamId]; + if (!ParentMultiParm || ParentMultiParm->IsPendingKill()) + return; + + if (ParentMultiParm->MultiParmInstanceCount * ParentMultiParm->MultiParmInstanceLength - 1 == CurParam->GetChildIndex()) + { + ParentParamId = ParentMultiParm->GetParentParmId(); + CurParam = ParentMultiParm; + + continue; + } + else + { + // return directly if the parameter is not the last child param of the multiparm + return; + } + } + // The parent is a folder or folderlist + else + { + UHoudiniParameter* ParentFolderParam = AllFoldersAndFolderLists[ParentParamId]; + CurParam = ParentFolderParam; + + if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) + return; + + // The parent is a folder + if (ParentFolderParam->GetParameterType() == EHoudiniParameterType::Folder) + { + ParentParamId = ParentFolderParam->GetParentParmId(); + + continue; + } + // The parent is a folderlist + else + { + UHoudiniParameterFolderList* ParentFolderList = Cast(ParentFolderParam); + if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) + return; + + if (ParentFolderList->IsTabMenu() && ParentFolderList->IsTabsShown() && ParentFolderList->IsTabParseFinished() && DividerLinePositions.Num() > 0) + { + if (!CurrentTabEndingRow) + CreateTabEndingRow(HouParameterCategory); + + if (CurrentTabEndingRow) + { + CurrentTabEndingRow->EndingDividerLinePositions.Add(DividerLinePositions.Top()); + CurrentTabEndingRow->DividerLinePositions.Pop(); + } + + DividerLinePositions.Pop(); + + ParentParamId = ParentFolderList->GetParentParmId(); + } + else + { + return; + } + + } + + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h index 98a392de9..f0f4662dd 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h @@ -1,484 +1,484 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" - -#include "CoreMinimal.h" - -#include "Widgets/Layout/SUniformGridPanel.h" -#include "SCurveEditor.h" -#include "Editor/CurveEditor/Public/CurveEditorSettings.h" -#include "HoudiniParameterTranslator.h" -#include "Curves/CurveFloat.h" -#include "SColorGradientEditor.h" -#include "Curves/CurveLinearColor.h" - -#include "Widgets/SBoxPanel.h" -#include "Widgets/Input/SButton.h" - -#include "HoudiniParameterDetails.generated.h" - -class UHoudiniAssetComponent; -class UHoudiniParameter; -class UHoudiniParameterFloat; -class UHoudiniParameterInt; -class UHoudiniParameterString; -class UHoudiniParameterColor; -class UHoudiniParameterButton; -class UHoudiniParameterButtonStrip; -class UHoudiniParameterLabel; -class UHoudiniParameterToggle; -class UHoudiniParameterFile; -class UHoudiniParameterChoice; -class UHoudiniParameterFolder; -class UHoudiniParameterFolderList; -class UHoudiniParameterMultiParm; -class UHoudiniParameterRampFloat; -class UHoudiniParameterRampColor; -class UHoudiniParameterOperatorPath; - -class UHoudiniParameterRampColorPoint; -class UHoudiniParameterRampFloatPoint; - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class SHorizontalBox; -class SHoudiniAssetParameterRampCurveEditor; - -enum class EHoudiniRampInterpolationType : int8; - -class SCustomizedButton : public SButton -{ -public: - bool bChosen; - - bool bIsRadioButton; - -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; - - // Construct the circles for all radio buttons. Initialize at first use - void ConstructRadioButtonCircles() const; - - void DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const; -}; - -class SCustomizedBox : public SHorizontalBox -{ -public: - bool bIsTabFolderListRow; - - bool bIsSeparator; - - TArray DividerLinePositions; - - TArray EndingDividerLinePositions; - - float MarginHeight; - -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; - - // Add indentation to current row, computed by tracing the directory hierarchy, - // return the indentation width of this parameter row. - float AddIndentation(UHoudiniParameter* InParam, TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists); - - void SetHoudiniParameter(TArray& InParams); -}; - -class SHoudiniFloatRampCurveEditor : public SCurveEditor -{ -public: - SLATE_BEGIN_ARGS(SHoudiniFloatRampCurveEditor) - : _ViewMinInput(0.0f) - , _ViewMaxInput(10.0f) - , _ViewMinOutput(0.0f) - , _ViewMaxOutput(1.0f) - , _InputSnap(0.1f) - , _OutputSnap(0.05f) - , _InputSnappingEnabled(false) - , _OutputSnappingEnabled(false) - , _ShowTimeInFrames(false) - , _TimelineLength(5.0f) - , _DesiredSize(FVector2D::ZeroVector) - , _DrawCurve(true) - , _HideUI(true) - , _AllowZoomOutput(true) - , _AlwaysDisplayColorCurves(false) - , _ZoomToFitVertical(true) - , _ZoomToFitHorizontal(true) - , _ShowZoomButtons(true) - , _XAxisName() - , _YAxisName() - , _ShowInputGridNumbers(true) - , _ShowOutputGridNumbers(true) - , _ShowCurveSelector(true) - , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) - { - _Clipping = EWidgetClipping::ClipToBounds; - } - - SLATE_ATTRIBUTE(float, ViewMinInput) - SLATE_ATTRIBUTE(float, ViewMaxInput) - SLATE_ATTRIBUTE(TOptional, DataMinInput) - SLATE_ATTRIBUTE(TOptional, DataMaxInput) - SLATE_ATTRIBUTE(float, ViewMinOutput) - SLATE_ATTRIBUTE(float, ViewMaxOutput) - SLATE_ATTRIBUTE(float, InputSnap) - SLATE_ATTRIBUTE(float, OutputSnap) - SLATE_ATTRIBUTE(bool, InputSnappingEnabled) - SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) - SLATE_ATTRIBUTE(bool, ShowTimeInFrames) - SLATE_ATTRIBUTE(float, TimelineLength) - SLATE_ATTRIBUTE(FVector2D, DesiredSize) - SLATE_ATTRIBUTE(bool, AreCurvesVisible) - SLATE_ARGUMENT(bool, DrawCurve) - SLATE_ARGUMENT(bool, HideUI) - SLATE_ARGUMENT(bool, AllowZoomOutput) - SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) - SLATE_ARGUMENT(bool, ZoomToFitVertical) - SLATE_ARGUMENT(bool, ZoomToFitHorizontal) - SLATE_ARGUMENT(bool, ShowZoomButtons) - SLATE_ARGUMENT(TOptional, XAxisName) - SLATE_ARGUMENT(TOptional, YAxisName) - SLATE_ARGUMENT(bool, ShowInputGridNumbers) - SLATE_ARGUMENT(bool, ShowOutputGridNumbers) - SLATE_ARGUMENT(bool, ShowCurveSelector) - SLATE_ARGUMENT(FLinearColor, GridColor) - SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) - SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) - SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) - SLATE_EVENT(FSimpleDelegate, OnCreateAsset) - SLATE_END_ARGS() - - public: - TWeakObjectPtr HoudiniFloatRampCurve; - - /** Widget construction. **/ - void Construct(const FArguments & InArgs); - - virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; - -}; - - -class SHoudiniColorRampCurveEditor : public SColorGradientEditor -{ - -public: - SLATE_BEGIN_ARGS(SHoudiniColorRampCurveEditor) - : _ViewMinInput(0.0f) - , _ViewMaxInput(10.0f) - , _InputSnap(0.1f) - , _OutputSnap(0.05f) - , _InputSnappingEnabled(false) - , _OutputSnappingEnabled(false) - , _ShowTimeInFrames(false) - , _TimelineLength(5.0f) - , _DesiredSize(FVector2D::ZeroVector) - , _DrawCurve(true) - , _HideUI(true) - , _AllowZoomOutput(true) - , _AlwaysDisplayColorCurves(false) - , _ZoomToFitVertical(true) - , _ZoomToFitHorizontal(true) - , _ShowZoomButtons(true) - , _XAxisName() - , _YAxisName() - , _ShowInputGridNumbers(true) - , _ShowOutputGridNumbers(true) - , _ShowCurveSelector(true) - , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) - { - _Clipping = EWidgetClipping::ClipToBounds; - } - - SLATE_ATTRIBUTE(float, ViewMinInput) - SLATE_ATTRIBUTE(float, ViewMaxInput) - SLATE_ATTRIBUTE(TOptional, DataMinInput) - SLATE_ATTRIBUTE(TOptional, DataMaxInput) - SLATE_ATTRIBUTE(float, InputSnap) - SLATE_ATTRIBUTE(float, OutputSnap) - SLATE_ATTRIBUTE(bool, InputSnappingEnabled) - SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) - SLATE_ATTRIBUTE(bool, ShowTimeInFrames) - SLATE_ATTRIBUTE(float, TimelineLength) - SLATE_ATTRIBUTE(FVector2D, DesiredSize) - SLATE_ATTRIBUTE(bool, AreCurvesVisible) - SLATE_ARGUMENT(bool, DrawCurve) - SLATE_ARGUMENT(bool, HideUI) - SLATE_ARGUMENT(bool, AllowZoomOutput) - SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) - SLATE_ARGUMENT(bool, ZoomToFitVertical) - SLATE_ARGUMENT(bool, ZoomToFitHorizontal) - SLATE_ARGUMENT(bool, ShowZoomButtons) - SLATE_ARGUMENT(TOptional, XAxisName) - SLATE_ARGUMENT(TOptional, YAxisName) - SLATE_ARGUMENT(bool, ShowInputGridNumbers) - SLATE_ARGUMENT(bool, ShowOutputGridNumbers) - SLATE_ARGUMENT(bool, ShowCurveSelector) - SLATE_ARGUMENT(FLinearColor, GridColor) - SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) - SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) - SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) - SLATE_EVENT(FSimpleDelegate, OnCreateAsset) - SLATE_END_ARGS() - - public: - /** Widget construction. **/ - void Construct(const FArguments & InArgs); - - TWeakObjectPtr HoudiniColorRampCurve; - - virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; -}; - -UCLASS() -class UHoudiniFloatRampCurve : public UCurveFloat -{ - GENERATED_BODY() - - public: - - TArray> FloatRampParameters; - - virtual void OnCurveChanged(const TArray& ChangedCurveEditInfos) override; -}; - - -UCLASS() -class UHoudiniColorRampCurve : public UCurveLinearColor -{ - GENERATED_BODY() - - public: - bool bEditing = false; - - TArray> ColorRampParameters; - - virtual void OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) override; - - void OnColorRampCurveChanged(bool bModificationOnly = false); - -}; - - -//class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface -class FHoudiniParameterDetails : public TSharedFromThis -{ - public: - void CreateWidget( - IDetailCategoryBuilder & HouParameterCategory, - TArray &InParams); - - void CreateWidgetInt( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetFloat( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetString( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetColor( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetButton( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetButtonStrip( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetLabel( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetToggle( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFile( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetChoice( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetSeparator( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled); - void CreateWidgetFolderList( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFolder( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetMultiParm( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetOperatorPath( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFloatRamp( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetColorRamp( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - - void CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory); - - - void HandleUnsupportedParmType( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams - ); - - - static FText GetParameterTooltip(UHoudiniParameter* InParam); - - static FString GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize); - - static void SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter); - - //static void SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter); - - // replace the children parameter values of all (multi-selected) float ramp parameters with the main parameter (weak object pointer version) - static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters); - // raw pointer version - static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters); - // helper - static void ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam); - - - // replace the children parameter values of all (multi-selected) color ramp parameters with the main parameter (weak object pointer version) - static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters); - // raw pointer version - static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters); - // helper - static void ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParame); - - - - // Create an insert event for a float ramp parameter - static void CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, - const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); - - // Create an insert event for a color ramp parameter - static void CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, - const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp); - - // Create a delete event for a float ramp parameter - static void CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex); - - // Create a delete event for a color ramp parameter - static void CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex); - - - private: - - template< class T > - static bool CastParameters( - TArray InParams, TArray& OutCastedParams); - - // - // Private helper functions for widget creation - // - - // Creates the default name widget, the parameter will then fill the value after - void CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); - - // Creates the default name widget, with an extra checkbox for disabling the the parameter update - void CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); - - FDetailWidgetRow* CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount = true); // - - void CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray& InParams); // - - void CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InParam, const bool& bIsShown); // - - void CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams); // - - // Create the UI for ramp's curve editor. - FDetailWidgetRow* CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); // - - // Create the UI for ramp's stop points. - void CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray< - UHoudiniParameter*>& InParams); // - - void PruneStack(); - - void RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam); - - public: - // Stores the created ramp curves - // In order to avoid being grabage collected, curves are added to root, thus need to handle GC manually. - // These points are for releasing the memory when the detail class are destroyed - TArray CreatedFloatRampCurves; - TArray CreatedColorRampCurves; - - private: - // The parameter directory is flattened with BFS inside of DFS. - // When a folderlist is encountered, it goes 'one step' of DFS, otherwise BFS. - // So that use a Stack structure to reconstruct the tree. - TArray> FolderStack; - - // Float Ramp currently being processed - UHoudiniParameterRampFloat* CurrentRampFloat; - - // Color Ramp currently being processed - UHoudiniParameterRampColor* CurrentRampColor; - - TArray CurrentRampParameterList; - - // Cached curve points of float ramp which being processed - TArray CurrentRampFloatPointsArray; - - // Cached curve points of color ramp which being processed - TArray CurrentRampColorPointsArray; - - // Cached color ramp curve which being processed - UHoudiniColorRampCurve* CurrentRampParameterColorCurve; - - // Cached float ramp curve which being processed - UHoudiniFloatRampCurve* CurrentRampParameterFloatCurve; - - FDetailWidgetRow * CurrentRampRow; - - - /* Variables for keeping expansion state after adding multiparm instance*/ - TMap AllMultiParms; - - // Cached the map of parameter id and folders/folder lists - TMap AllFoldersAndFolderLists; - - /* Variables for keeping expansion state after adding multiparm instance*/ - - TMap MultiParmInstanceIndices; - - // Number of remaining folders for current folder list - int32 CurrentFolderListSize = 0; - - // The folder list currently being processed - UHoudiniParameterFolderList* CurrentFolderList; - - // Cached child folders of current tabs - TArray CurrentTabs; - - TArray DividerLinePositions; - - SCustomizedBox* CurrentTabEndingRow; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" + +#include "CoreMinimal.h" + +#include "Widgets/Layout/SUniformGridPanel.h" +#include "SCurveEditor.h" +#include "Editor/CurveEditor/Public/CurveEditorSettings.h" +#include "HoudiniParameterTranslator.h" +#include "Curves/CurveFloat.h" +#include "SColorGradientEditor.h" +#include "Curves/CurveLinearColor.h" + +#include "Widgets/SBoxPanel.h" +#include "Widgets/Input/SButton.h" + +#include "HoudiniParameterDetails.generated.h" + +class UHoudiniAssetComponent; +class UHoudiniParameter; +class UHoudiniParameterFloat; +class UHoudiniParameterInt; +class UHoudiniParameterString; +class UHoudiniParameterColor; +class UHoudiniParameterButton; +class UHoudiniParameterButtonStrip; +class UHoudiniParameterLabel; +class UHoudiniParameterToggle; +class UHoudiniParameterFile; +class UHoudiniParameterChoice; +class UHoudiniParameterFolder; +class UHoudiniParameterFolderList; +class UHoudiniParameterMultiParm; +class UHoudiniParameterRampFloat; +class UHoudiniParameterRampColor; +class UHoudiniParameterOperatorPath; + +class UHoudiniParameterRampColorPoint; +class UHoudiniParameterRampFloatPoint; + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class SHorizontalBox; +class SHoudiniAssetParameterRampCurveEditor; + +enum class EHoudiniRampInterpolationType : int8; + +class SCustomizedButton : public SButton +{ +public: + bool bChosen; + + bool bIsRadioButton; + +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + // Construct the circles for all radio buttons. Initialize at first use + void ConstructRadioButtonCircles() const; + + void DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const; +}; + +class SCustomizedBox : public SHorizontalBox +{ +public: + bool bIsTabFolderListRow; + + bool bIsSeparator; + + TArray DividerLinePositions; + + TArray EndingDividerLinePositions; + + float MarginHeight; + +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + // Add indentation to current row, computed by tracing the directory hierarchy, + // return the indentation width of this parameter row. + float AddIndentation(UHoudiniParameter* InParam, TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists); + + void SetHoudiniParameter(TArray& InParams); +}; + +class SHoudiniFloatRampCurveEditor : public SCurveEditor +{ +public: + SLATE_BEGIN_ARGS(SHoudiniFloatRampCurveEditor) + : _ViewMinInput(0.0f) + , _ViewMaxInput(10.0f) + , _ViewMinOutput(0.0f) + , _ViewMaxOutput(1.0f) + , _InputSnap(0.1f) + , _OutputSnap(0.05f) + , _InputSnappingEnabled(false) + , _OutputSnappingEnabled(false) + , _ShowTimeInFrames(false) + , _TimelineLength(5.0f) + , _DesiredSize(FVector2D::ZeroVector) + , _DrawCurve(true) + , _HideUI(true) + , _AllowZoomOutput(true) + , _AlwaysDisplayColorCurves(false) + , _ZoomToFitVertical(true) + , _ZoomToFitHorizontal(true) + , _ShowZoomButtons(true) + , _XAxisName() + , _YAxisName() + , _ShowInputGridNumbers(true) + , _ShowOutputGridNumbers(true) + , _ShowCurveSelector(true) + , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + + SLATE_ATTRIBUTE(float, ViewMinInput) + SLATE_ATTRIBUTE(float, ViewMaxInput) + SLATE_ATTRIBUTE(TOptional, DataMinInput) + SLATE_ATTRIBUTE(TOptional, DataMaxInput) + SLATE_ATTRIBUTE(float, ViewMinOutput) + SLATE_ATTRIBUTE(float, ViewMaxOutput) + SLATE_ATTRIBUTE(float, InputSnap) + SLATE_ATTRIBUTE(float, OutputSnap) + SLATE_ATTRIBUTE(bool, InputSnappingEnabled) + SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) + SLATE_ATTRIBUTE(bool, ShowTimeInFrames) + SLATE_ATTRIBUTE(float, TimelineLength) + SLATE_ATTRIBUTE(FVector2D, DesiredSize) + SLATE_ATTRIBUTE(bool, AreCurvesVisible) + SLATE_ARGUMENT(bool, DrawCurve) + SLATE_ARGUMENT(bool, HideUI) + SLATE_ARGUMENT(bool, AllowZoomOutput) + SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) + SLATE_ARGUMENT(bool, ZoomToFitVertical) + SLATE_ARGUMENT(bool, ZoomToFitHorizontal) + SLATE_ARGUMENT(bool, ShowZoomButtons) + SLATE_ARGUMENT(TOptional, XAxisName) + SLATE_ARGUMENT(TOptional, YAxisName) + SLATE_ARGUMENT(bool, ShowInputGridNumbers) + SLATE_ARGUMENT(bool, ShowOutputGridNumbers) + SLATE_ARGUMENT(bool, ShowCurveSelector) + SLATE_ARGUMENT(FLinearColor, GridColor) + SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) + SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) + SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) + SLATE_EVENT(FSimpleDelegate, OnCreateAsset) + SLATE_END_ARGS() + + public: + TWeakObjectPtr HoudiniFloatRampCurve; + + /** Widget construction. **/ + void Construct(const FArguments & InArgs); + + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + +}; + + +class SHoudiniColorRampCurveEditor : public SColorGradientEditor +{ + +public: + SLATE_BEGIN_ARGS(SHoudiniColorRampCurveEditor) + : _ViewMinInput(0.0f) + , _ViewMaxInput(10.0f) + , _InputSnap(0.1f) + , _OutputSnap(0.05f) + , _InputSnappingEnabled(false) + , _OutputSnappingEnabled(false) + , _ShowTimeInFrames(false) + , _TimelineLength(5.0f) + , _DesiredSize(FVector2D::ZeroVector) + , _DrawCurve(true) + , _HideUI(true) + , _AllowZoomOutput(true) + , _AlwaysDisplayColorCurves(false) + , _ZoomToFitVertical(true) + , _ZoomToFitHorizontal(true) + , _ShowZoomButtons(true) + , _XAxisName() + , _YAxisName() + , _ShowInputGridNumbers(true) + , _ShowOutputGridNumbers(true) + , _ShowCurveSelector(true) + , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + + SLATE_ATTRIBUTE(float, ViewMinInput) + SLATE_ATTRIBUTE(float, ViewMaxInput) + SLATE_ATTRIBUTE(TOptional, DataMinInput) + SLATE_ATTRIBUTE(TOptional, DataMaxInput) + SLATE_ATTRIBUTE(float, InputSnap) + SLATE_ATTRIBUTE(float, OutputSnap) + SLATE_ATTRIBUTE(bool, InputSnappingEnabled) + SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) + SLATE_ATTRIBUTE(bool, ShowTimeInFrames) + SLATE_ATTRIBUTE(float, TimelineLength) + SLATE_ATTRIBUTE(FVector2D, DesiredSize) + SLATE_ATTRIBUTE(bool, AreCurvesVisible) + SLATE_ARGUMENT(bool, DrawCurve) + SLATE_ARGUMENT(bool, HideUI) + SLATE_ARGUMENT(bool, AllowZoomOutput) + SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) + SLATE_ARGUMENT(bool, ZoomToFitVertical) + SLATE_ARGUMENT(bool, ZoomToFitHorizontal) + SLATE_ARGUMENT(bool, ShowZoomButtons) + SLATE_ARGUMENT(TOptional, XAxisName) + SLATE_ARGUMENT(TOptional, YAxisName) + SLATE_ARGUMENT(bool, ShowInputGridNumbers) + SLATE_ARGUMENT(bool, ShowOutputGridNumbers) + SLATE_ARGUMENT(bool, ShowCurveSelector) + SLATE_ARGUMENT(FLinearColor, GridColor) + SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) + SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) + SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) + SLATE_EVENT(FSimpleDelegate, OnCreateAsset) + SLATE_END_ARGS() + + public: + /** Widget construction. **/ + void Construct(const FArguments & InArgs); + + TWeakObjectPtr HoudiniColorRampCurve; + + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; +}; + +UCLASS() +class UHoudiniFloatRampCurve : public UCurveFloat +{ + GENERATED_BODY() + + public: + + TArray> FloatRampParameters; + + virtual void OnCurveChanged(const TArray& ChangedCurveEditInfos) override; +}; + + +UCLASS() +class UHoudiniColorRampCurve : public UCurveLinearColor +{ + GENERATED_BODY() + + public: + bool bEditing = false; + + TArray> ColorRampParameters; + + virtual void OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) override; + + void OnColorRampCurveChanged(bool bModificationOnly = false); + +}; + + +//class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface +class FHoudiniParameterDetails : public TSharedFromThis +{ + public: + void CreateWidget( + IDetailCategoryBuilder & HouParameterCategory, + TArray &InParams); + + void CreateWidgetInt( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetFloat( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetString( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetColor( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetButton( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetButtonStrip( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetLabel( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetToggle( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFile( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetChoice( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetSeparator( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled); + void CreateWidgetFolderList( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFolder( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetMultiParm( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetOperatorPath( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFloatRamp( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetColorRamp( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + + void CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory); + + + void HandleUnsupportedParmType( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams + ); + + + static FText GetParameterTooltip(UHoudiniParameter* InParam); + + static FString GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize); + + static void SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter); + + //static void SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter); + + // replace the children parameter values of all (multi-selected) float ramp parameters with the main parameter (weak object pointer version) + static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters); + // raw pointer version + static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters); + // helper + static void ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam); + + + // replace the children parameter values of all (multi-selected) color ramp parameters with the main parameter (weak object pointer version) + static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters); + // raw pointer version + static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters); + // helper + static void ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParame); + + + + // Create an insert event for a float ramp parameter + static void CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, + const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); + + // Create an insert event for a color ramp parameter + static void CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, + const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp); + + // Create a delete event for a float ramp parameter + static void CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex); + + // Create a delete event for a color ramp parameter + static void CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex); + + + private: + + template< class T > + static bool CastParameters( + TArray InParams, TArray& OutCastedParams); + + // + // Private helper functions for widget creation + // + + // Creates the default name widget, the parameter will then fill the value after + void CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); + + // Creates the default name widget, with an extra checkbox for disabling the the parameter update + void CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); + + FDetailWidgetRow* CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount = true); // + + void CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray& InParams); // + + void CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InParam, const bool& bIsShown); // + + void CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams); // + + // Create the UI for ramp's curve editor. + FDetailWidgetRow* CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); // + + // Create the UI for ramp's stop points. + void CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray< + UHoudiniParameter*>& InParams); // + + void PruneStack(); + + void RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam); + + public: + // Stores the created ramp curves + // In order to avoid being grabage collected, curves are added to root, thus need to handle GC manually. + // These points are for releasing the memory when the detail class are destroyed + TArray CreatedFloatRampCurves; + TArray CreatedColorRampCurves; + + private: + // The parameter directory is flattened with BFS inside of DFS. + // When a folderlist is encountered, it goes 'one step' of DFS, otherwise BFS. + // So that use a Stack structure to reconstruct the tree. + TArray> FolderStack; + + // Float Ramp currently being processed + UHoudiniParameterRampFloat* CurrentRampFloat; + + // Color Ramp currently being processed + UHoudiniParameterRampColor* CurrentRampColor; + + TArray CurrentRampParameterList; + + // Cached curve points of float ramp which being processed + TArray CurrentRampFloatPointsArray; + + // Cached curve points of color ramp which being processed + TArray CurrentRampColorPointsArray; + + // Cached color ramp curve which being processed + UHoudiniColorRampCurve* CurrentRampParameterColorCurve; + + // Cached float ramp curve which being processed + UHoudiniFloatRampCurve* CurrentRampParameterFloatCurve; + + FDetailWidgetRow * CurrentRampRow; + + + /* Variables for keeping expansion state after adding multiparm instance*/ + TMap AllMultiParms; + + // Cached the map of parameter id and folders/folder lists + TMap AllFoldersAndFolderLists; + + /* Variables for keeping expansion state after adding multiparm instance*/ + + TMap MultiParmInstanceIndices; + + // Number of remaining folders for current folder list + int32 CurrentFolderListSize = 0; + + // The folder list currently being processed + UHoudiniParameterFolderList* CurrentFolderList; + + // Cached child folders of current tabs + TArray CurrentTabs; + + TArray DividerLinePositions; + + SCustomizedBox* CurrentTabEndingRow; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp index 0a43be77d..fbba21eb9 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp @@ -1,323 +1,323 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniRuntimeSettingsDetails.h" - -#include "HoudiniApi.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimeUtils.h" - -#include "HAPI/HAPI_Version.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "Internationalization/Internationalization.h" -#include "DetailLayoutBuilder.h" -#include "DetailWidgetRow.h" -#include "Widgets/Input/SNumericEntryBox.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -TSharedRef< IDetailCustomization > -FHoudiniRuntimeSettingsDetails::MakeInstance() -{ - return MakeShareable(new FHoudiniRuntimeSettingsDetails); -} - -FHoudiniRuntimeSettingsDetails::FHoudiniRuntimeSettingsDetails() -{} - -FHoudiniRuntimeSettingsDetails::~FHoudiniRuntimeSettingsDetails() -{} - -void -FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) -{ - // Create basic categories. - DetailBuilder.EditCategory("Session", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Instantiating", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Cooking", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Parameters", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("PDGSettings", FText::GetEmpty(), ECategoryPriority::Important); - - // Create Plugin Information category. - { - static const FName InformationCategory = TEXT("Plugin Information"); - IDetailCategoryBuilder & InformationCategoryBuilder = DetailBuilder.EditCategory(InformationCategory); - - // Add built Houdini version. - CreateHoudiniEntry( - LOCTEXT("HInformationBuilt", "Built against Houdini"), - InformationCategoryBuilder, HAPI_VERSION_HOUDINI_MAJOR, HAPI_VERSION_HOUDINI_MINOR, - HAPI_VERSION_HOUDINI_BUILD, HAPI_VERSION_HOUDINI_PATCH); - - // Add built against Houdini Engine version. - CreateHoudiniEngineEntry( - LOCTEXT("HEngineInformationBuilt", "Built against Houdini Engine"), - InformationCategoryBuilder, HAPI_VERSION_HOUDINI_ENGINE_MAJOR, - HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API); - - // Add running against Houdini version. - { - int32 RunningMajor = 0; - int32 RunningMinor = 0; - int32 RunningBuild = 0; - int32 RunningPatch = 0; - - if (FHoudiniApi::IsHAPIInitialized()) - { - const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); - // Retrieve version numbers for running Houdini. - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MAJOR, &RunningMajor); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MINOR, &RunningMinor); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_BUILD, &RunningBuild); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_PATCH, &RunningPatch); - } - - CreateHoudiniEntry( - LOCTEXT("HInformationRunning", "Running against Houdini"), - InformationCategoryBuilder, RunningMajor, RunningMinor, RunningBuild, RunningPatch); - } - - // Add running against Houdini Engine version. - { - int32 RunningEngineMajor = 0; - int32 RunningEngineMinor = 0; - int32 RunningEngineApi = 0; - - if (FHoudiniApi::IsHAPIInitialized()) - { - 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); - } - - CreateHoudiniEngineEntry( - LOCTEXT("HEngineInformationRunning", "Running against Houdini Engine"), - InformationCategoryBuilder, RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - } - - // Add path of libHAPI. - { - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - if (LibHAPILocation.IsEmpty()) - LibHAPILocation = TEXT("Not Found"); - - CreateHAPILibraryPathEntry(LibHAPILocation, InformationCategoryBuilder); - } - - // Add licensing info. - { - FString HAPILicenseType = TEXT(""); - if (!FHoudiniEngineUtils::GetLicenseType(HAPILicenseType)) - HAPILicenseType = TEXT("Unknown"); - - CreateHAPILicenseEntry(HAPILicenseType, InformationCategoryBuilder); - } - } - - DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHoudiniEntry( - const FText & EntryName, - IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, - int32 VersionPatch) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(EntryName) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMajor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMinor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionBuild) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionPatch) - ]; - NumericEntryBox->SetEnabled(false); - } - - Row.ValueWidget.Widget = HorizontalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHoudiniEngineEntry( - const FText & EntryName, - IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionApi) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(EntryName) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMajor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMinor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionApi) - ]; - NumericEntryBox->SetEnabled(false); - } - - Row.ValueWidget.Widget = HorizontalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHAPILibraryPathEntry( - const FString & LibHAPIPath, - IDetailCategoryBuilder & DetailCategoryBuilder) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - FString LibHAPIName = FString::Printf(TEXT("Location of %s"), *FHoudiniEngineRuntimeUtils::GetLibHAPIName()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(FText::FromString(LibHAPIName)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef TextBlock = - SNew(STextBlock) - .Text(FText::FromString(LibHAPIPath)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TextBlock->SetEnabled(false); - Row.ValueWidget.Widget = TextBlock; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHAPILicenseEntry( - const FString & LibHAPILicense, - IDetailCategoryBuilder & DetailCategoryBuilder) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - FString LibHAPILicenseTypeText = TEXT("Acquired License Type"); - - Row.NameWidget.Widget = SNew(STextBlock) - .Text(FText::FromString(LibHAPILicenseTypeText)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef TextBlock = SNew(STextBlock) - .Text(FText::FromString(LibHAPILicense)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TextBlock->SetEnabled(false); - Row.ValueWidget.Widget = TextBlock; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniRuntimeSettingsDetails.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimeUtils.h" + +#include "HAPI/HAPI_Version.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "Internationalization/Internationalization.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SNumericEntryBox.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +TSharedRef< IDetailCustomization > +FHoudiniRuntimeSettingsDetails::MakeInstance() +{ + return MakeShareable(new FHoudiniRuntimeSettingsDetails); +} + +FHoudiniRuntimeSettingsDetails::FHoudiniRuntimeSettingsDetails() +{} + +FHoudiniRuntimeSettingsDetails::~FHoudiniRuntimeSettingsDetails() +{} + +void +FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) +{ + // Create basic categories. + DetailBuilder.EditCategory("Session", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Instantiating", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Cooking", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Parameters", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("PDGSettings", FText::GetEmpty(), ECategoryPriority::Important); + + // Create Plugin Information category. + { + static const FName InformationCategory = TEXT("Plugin Information"); + IDetailCategoryBuilder & InformationCategoryBuilder = DetailBuilder.EditCategory(InformationCategory); + + // Add built Houdini version. + CreateHoudiniEntry( + LOCTEXT("HInformationBuilt", "Built against Houdini"), + InformationCategoryBuilder, HAPI_VERSION_HOUDINI_MAJOR, HAPI_VERSION_HOUDINI_MINOR, + HAPI_VERSION_HOUDINI_BUILD, HAPI_VERSION_HOUDINI_PATCH); + + // Add built against Houdini Engine version. + CreateHoudiniEngineEntry( + LOCTEXT("HEngineInformationBuilt", "Built against Houdini Engine"), + InformationCategoryBuilder, HAPI_VERSION_HOUDINI_ENGINE_MAJOR, + HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API); + + // Add running against Houdini version. + { + int32 RunningMajor = 0; + int32 RunningMinor = 0; + int32 RunningBuild = 0; + int32 RunningPatch = 0; + + if (FHoudiniApi::IsHAPIInitialized()) + { + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + // Retrieve version numbers for running Houdini. + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MAJOR, &RunningMajor); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MINOR, &RunningMinor); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_BUILD, &RunningBuild); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_PATCH, &RunningPatch); + } + + CreateHoudiniEntry( + LOCTEXT("HInformationRunning", "Running against Houdini"), + InformationCategoryBuilder, RunningMajor, RunningMinor, RunningBuild, RunningPatch); + } + + // Add running against Houdini Engine version. + { + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + if (FHoudiniApi::IsHAPIInitialized()) + { + 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); + } + + CreateHoudiniEngineEntry( + LOCTEXT("HEngineInformationRunning", "Running against Houdini Engine"), + InformationCategoryBuilder, RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + } + + // Add path of libHAPI. + { + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + if (LibHAPILocation.IsEmpty()) + LibHAPILocation = TEXT("Not Found"); + + CreateHAPILibraryPathEntry(LibHAPILocation, InformationCategoryBuilder); + } + + // Add licensing info. + { + FString HAPILicenseType = TEXT(""); + if (!FHoudiniEngineUtils::GetLicenseType(HAPILicenseType)) + HAPILicenseType = TEXT("Unknown"); + + CreateHAPILicenseEntry(HAPILicenseType, InformationCategoryBuilder); + } + } + + DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHoudiniEntry( + const FText & EntryName, + IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, + int32 VersionPatch) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(EntryName) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMajor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMinor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionBuild) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionPatch) + ]; + NumericEntryBox->SetEnabled(false); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHoudiniEngineEntry( + const FText & EntryName, + IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionApi) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(EntryName) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMajor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMinor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionApi) + ]; + NumericEntryBox->SetEnabled(false); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHAPILibraryPathEntry( + const FString & LibHAPIPath, + IDetailCategoryBuilder & DetailCategoryBuilder) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + FString LibHAPIName = FString::Printf(TEXT("Location of %s"), *FHoudiniEngineRuntimeUtils::GetLibHAPIName()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(FText::FromString(LibHAPIName)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef TextBlock = + SNew(STextBlock) + .Text(FText::FromString(LibHAPIPath)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TextBlock->SetEnabled(false); + Row.ValueWidget.Widget = TextBlock; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHAPILicenseEntry( + const FString & LibHAPILicense, + IDetailCategoryBuilder & DetailCategoryBuilder) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + FString LibHAPILicenseTypeText = TEXT("Acquired License Type"); + + Row.NameWidget.Widget = SNew(STextBlock) + .Text(FText::FromString(LibHAPILicenseTypeText)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef TextBlock = SNew(STextBlock) + .Text(FText::FromString(LibHAPILicense)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TextBlock->SetEnabled(false); + Row.ValueWidget.Widget = TextBlock; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h index 60fcf14df..c0372513e 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "DetailCategoryBuilder.h" -#include "IDetailCustomization.h" - -class FHoudiniRuntimeSettingsDetails : public IDetailCustomization -{ -public: - - /** Constructor. **/ - FHoudiniRuntimeSettingsDetails(); - - /** Destructor. **/ - virtual ~FHoudiniRuntimeSettingsDetails(); - - /** IDetailCustomization methods. **/ -public: - - virtual void CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) override; - -public: - - /** Create an instance of this detail layout class. **/ - static TSharedRef< IDetailCustomization > MakeInstance(); - -protected: - - /** Used to create Houdini version entry. **/ - void CreateHoudiniEntry( - const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, int32 VersionPatch); - - /** Used to create Houdini Engine version entry. **/ - void CreateHoudiniEngineEntry( - const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionApi); - - /** Used to create libHAPI dynamic library path entry. **/ - void CreateHAPILibraryPathEntry( - const FString & LibHAPIPath, IDetailCategoryBuilder & DetailCategoryBuilder); - - /** Used to create libHAPI license information entry. **/ - void CreateHAPILicenseEntry( - const FString & LibHAPILicense, IDetailCategoryBuilder & DetailCategoryBuilder); -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" + +class FHoudiniRuntimeSettingsDetails : public IDetailCustomization +{ +public: + + /** Constructor. **/ + FHoudiniRuntimeSettingsDetails(); + + /** Destructor. **/ + virtual ~FHoudiniRuntimeSettingsDetails(); + + /** IDetailCustomization methods. **/ +public: + + virtual void CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) override; + +public: + + /** Create an instance of this detail layout class. **/ + static TSharedRef< IDetailCustomization > MakeInstance(); + +protected: + + /** Used to create Houdini version entry. **/ + void CreateHoudiniEntry( + const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, int32 VersionPatch); + + /** Used to create Houdini Engine version entry. **/ + void CreateHoudiniEngineEntry( + const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionApi); + + /** Used to create libHAPI dynamic library path entry. **/ + void CreateHAPILibraryPathEntry( + const FString & LibHAPIPath, IDetailCategoryBuilder & DetailCategoryBuilder); + + /** Used to create libHAPI license information entry. **/ + void CreateHAPILicenseEntry( + const FString & LibHAPILicense, IDetailCategoryBuilder & DetailCategoryBuilder); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp index 99294fe7c..66f772465 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp @@ -1,1033 +1,1033 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineComponentVisualizer.h" - -#include "ActorEditorUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniApi.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInputObject.h" -#include "HoudiniInput.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniEngineUtils.h" - -#include "Editor/UnrealEdEngine.h" -#include "UnrealEdGlobals.h" -#include "ComponentVisualizerManager.h" - -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "ScopedTransaction.h" -#include "EditorViewportClient.h" -#include "Engine/Selection.h" -#include "HModel.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_HIT_PROXY(HHoudiniSplineVisProxy, HComponentVisProxy); -IMPLEMENT_HIT_PROXY(HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy); -IMPLEMENT_HIT_PROXY(HHoudiniSplineCurveSegmentVisProxy, HHoudiniSplineVisProxy); - -HHoudiniSplineVisProxy::HHoudiniSplineVisProxy(const UActorComponent * InComponent) - : HComponentVisProxy(InComponent, HPP_Wireframe) -{} - -HHoudiniSplineControlPointVisProxy::HHoudiniSplineControlPointVisProxy( - const UActorComponent * InComponent, int32 InControlPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , ControlPointIndex(InControlPointIndex) -{} - -HHoudiniSplineCurveSegmentVisProxy::HHoudiniSplineCurveSegmentVisProxy( - const UActorComponent * InComponent, int32 InDisplayPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , DisplayPointIndex(InDisplayPointIndex) -{} - -FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() - : TCommands< FHoudiniSplineComponentVisualizerCommands >( - "HoudiniSplineComponentVisualizer", - LOCTEXT("HoudiniSplineComponentVisualizer", "Houdini Spline Component Visualizer"), - NAME_None, - FEditorStyle::GetStyleSetName()) -{} - -void -FHoudiniSplineComponentVisualizerCommands::RegisterCommands() -{ - UI_COMMAND( - CommandAddControlPoint, "Add Control Point", "Add control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND( - CommandDuplicateControlPoint, "Duplicate Control Point", "Duplicate control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND( - CommandDeleteControlPoint, "Delete Control Point", "delete control points.", - EUserInterfaceActionType::Button, FInputChord(EKeys::Delete)); - - UI_COMMAND(CommandDeselectAllControlPoints, "Deselect All", "Deselect all control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(CommandInsertControlPoint, "Insert Control Point", "Insert a control point on curve.", - EUserInterfaceActionType::Button, FInputChord()); -} - - -FHoudiniSplineComponentVisualizer::FHoudiniSplineComponentVisualizer() - :FComponentVisualizer() - ,bAllowDuplication(false) - ,EditedCurveSegmentIndex(-1) - ,CachedRotation(FQuat::Identity) - ,CachedScale3D(FVector::OneVector) - ,bMovingPoints(false) - ,bInsertingOnCurveControlPoints(false) - ,bRecordingMovingPoints(false) -{ - FHoudiniSplineComponentVisualizerCommands::Register(); - VisualizerActions = MakeShareable(new FUICommandList); -} - -void -FHoudiniSplineComponentVisualizer::OnRegister() -{ - HOUDINI_LOG_MESSAGE(TEXT("Houdini Spline Component Visualizer Registered!")); - const auto & Commands = FHoudiniSplineComponentVisualizerCommands::Get(); - - VisualizerActions->MapAction( - Commands.CommandAddControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnAddControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsAddControlPointValid)); - - VisualizerActions->MapAction( - Commands.CommandDuplicateControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid)); - - VisualizerActions->MapAction( - Commands.CommandDeleteControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeleteControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid)); - - VisualizerActions->MapAction(Commands.CommandDeselectAllControlPoints, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid)); - - VisualizerActions->MapAction(Commands.CommandInsertControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnInsertControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsInsertControlPointValid)); -} - - -void -FHoudiniSplineComponentVisualizer::DrawVisualization( - const UActorComponent * Component, - const FSceneView * View, - FPrimitiveDrawInterface * PDI) -{ - const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >(Component); - - if (!HoudiniSplineComponent - || !PDI - || HoudiniSplineComponent->IsPendingKill() - || !HoudiniSplineComponent->IsVisible() - || !HoudiniSplineComponent->IsHoudiniSplineVisible()) - return; - - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - - // Note: Undo a transaction clears the active visualizer in ComponnetVisMangaer, which is private to Visualizer manager. - // HandleProxyForComponentVis() sets the active visualizer. So the selection will be lost after undo. - - // A Way to bypass this annoying UE4 implementation: - // If the drawing spline is the one being edited and an undo just happened, - // force to trigger a 'bubble' hit proxy to re-activate the visualizer. - if (HoudiniSplineComponent == EditedHoudiniSplineComponent && EditedHoudiniSplineComponent->bPostUndo) - { - EditedHoudiniSplineComponent->bPostUndo = false; - - FEditorViewportClient * FoundViewportClient = FindViewportClient(EditedHoudiniSplineComponent, View); - HComponentVisProxy * BubbleComponentHitProxy = new HComponentVisProxy(EditedHoudiniSplineComponent); - - if (FoundViewportClient && BubbleComponentHitProxy) - { - FViewportClick BubbleClick(View, FoundViewportClient, FKey(), EInputEvent::IE_Axis, 0, 0); - GUnrealEd->ComponentVisManager.HandleProxyForComponentVis(FoundViewportClient, BubbleComponentHitProxy, BubbleClick); - } - } - - static const FColor ColorNormal = FColor(255.f, 255.f, 255.f); - static const FColor ColorNormalHandleFirst(172.f, 255.f, 172.f); - static const FColor ColorNormalHandleSecond(254.f, 216.f, 177.f); - - static const FColor ColorSelectedHandle(255.f, 0.f, 0.f); - static const FColor ColorSelectedHandleFirst(0.f, 192.f, 0.f); - static const FColor ColorSelectedHandleSecond(255.f, 159.f, 0.f); - - static const float SizeGrabHandleSelected = 15.f; - static const float SizeGrabHandleNormalLarge = 18.f; - static const float SizeGrabHandleNormalSmall = 12.f; - - FVector PreviousPosition; - - if (HoudiniSplineComponent) - { - const FTransform & HoudiniSplineComponentTransform = HoudiniSplineComponent->GetComponentTransform(); - - const TArray< FVector > & DisplayPoints = HoudiniSplineComponent->DisplayPoints; // not used yet - const TArray< FTransform > & CurvePoints = HoudiniSplineComponent->CurvePoints; - - // Draw display points (simply linearly connect the control points for temporary) - for (int32 Index = 0; Index < DisplayPoints.Num(); ++Index) - { - const FVector & CurrentPoint = DisplayPoints[Index]; - FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); - //CurrentPosition = CurrentPoint; - if (Index > 0) - { - // Add a hitproxy for the line segment - PDI->SetHitProxy(new HHoudiniSplineCurveSegmentVisProxy(HoudiniSplineComponent, Index)); - // Draw a line connecting the previous point and the current point - PDI->DrawLine(PreviousPosition, CurrentPosition, ColorNormal, SDPG_Foreground); - PDI->SetHitProxy(nullptr); - } - - PreviousPosition = CurrentPosition; - } - - // Draw control points (do not draw control points if the curve is an output) - if (!HoudiniSplineComponent->bIsOutputCurve) - { - for (int32 Index = 0; Index < CurvePoints.Num(); ++Index) - { - const FVector & ControlPoint = HoudiniSplineComponentTransform.TransformPosition(CurvePoints[Index].GetLocation()); - - HHoudiniSplineControlPointVisProxy * HitProxy = new HHoudiniSplineControlPointVisProxy(HoudiniSplineComponent, Index); - PDI->SetHitProxy(HitProxy); - - FColor DrawColor = ColorNormal; - float DrawSize = SizeGrabHandleNormalSmall; - - if (Index == 0) - { - DrawColor = ColorNormalHandleFirst; - DrawSize = SizeGrabHandleNormalLarge; - } - - if (Index == 1) - DrawColor = ColorNormalHandleSecond; - - - // If this is an point that being editted - if (EditedHoudiniSplineComponent == HoudiniSplineComponent && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Contains(Index)) - { - if (Index == 0) - { - DrawColor = ColorSelectedHandleFirst; - } - - else if (Index == 1) - { - DrawColor = ColorSelectedHandleSecond; - DrawSize = SizeGrabHandleSelected; - } - - else - { - DrawColor = ColorSelectedHandle; - DrawSize = SizeGrabHandleSelected; - - } - } - - PDI->DrawPoint(ControlPoint, DrawColor, DrawSize, SDPG_Foreground); - PDI->SetHitProxy(nullptr); - } - - } - } -} - - -bool -FHoudiniSplineComponentVisualizer::VisProxyHandleClick( - FEditorViewportClient* InViewportClient, - HComponentVisProxy* VisProxy, - const FViewportClick& Click) -{ - if (!InViewportClient || !VisProxy || !VisProxy->Component.IsValid()) - return false; - - const UHoudiniSplineComponent * HoudiniSplineComponent = CastChecked< const UHoudiniSplineComponent >(VisProxy->Component.Get()); - - AActor* OldSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); - SplinePropertyPath = FComponentPropertyPath(HoudiniSplineComponent); - AActor* NewSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); - - if (!SplinePropertyPath.IsValid()) - { - SplinePropertyPath.Reset(); - return false; - } - - if (OldSplineOwningActor != NewSplineOwningActor) - { - // Reset selection state if we are selecting a different actor to the one previously selected - EditedCurveSegmentIndex = INDEX_NONE; - } - - // Note: This is for re-activating the component visualizer an undo. - // Return true if the hit proxy is a bubble (Neither HHoudiniSplineControlPointVisProxy nor HHoudiniSplineCurveSegmentVisProxy ) - // - if (!VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType()) && !VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) - return true; - - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - - EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); - - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - bool editingCurve = false; - - // If VisProxy is a HHoudiniSplineControlPointVisProxy - if (VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType())) - { - HHoudiniSplineControlPointVisProxy * ControlPointProxy = (HHoudiniSplineControlPointVisProxy*)VisProxy; - - if (!ControlPointProxy) - return editingCurve; - - editingCurve = true; - - // Clear the edited curve segment if a control point is clicked. - EditedCurveSegmentIndex = -1; - - if (Click.GetKey() != EKeys::LeftMouseButton) - return editingCurve; - - - if (InViewportClient->IsCtrlPressed()) - { - if (EditedControlPointsIndexes.Contains(ControlPointProxy->ControlPointIndex)) - { - EditedControlPointsIndexes.Remove(ControlPointProxy->ControlPointIndex); - } - else - { - EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); - } - } - else - { - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); - } - } - // VisProxy is a HHoudiniSplineCurveSegmentProxy - else if (VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) - { - //HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = Cast(VisProxy); - - HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = (HHoudiniSplineCurveSegmentVisProxy*)(VisProxy); - - if (!CurveSegmentProxy) - return false; - - editingCurve = true; - - if (Click.GetKey() == EKeys::LeftMouseButton && InViewportClient->IsAltPressed() && EditedHoudiniSplineComponent) - { - // Continuesly (Alt) inserting on-curve control points is only valid with Breakpoints mode, otherwise it has to be on linear curve type. - if (EditedHoudiniSplineComponent->CurveType != EHoudiniCurveType::Polygon && EditedHoudiniSplineComponent->CurveMethod != EHoudiniCurveMethod::Breakpoints) - return editingCurve; - - bInsertingOnCurveControlPoints = true; - - editingCurve = true; - EditedControlPointsIndexes.Empty(); - - EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; - int32 InsertedIndex = OnInsertControlPointWithoutUpdate(); - - if (InsertedIndex < 0) return false; - EditedControlPointsIndexes.Add(InsertedIndex); - - EditedCurveSegmentIndex = -1; - bInsertingOnCurveControlPoints = true; - - RefreshViewport(); - } - // Insert one on-curve control point. - else - { - EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; - return editingCurve; - } - } - - return editingCurve; -} - -bool -FHoudiniSplineComponentVisualizer::HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - if (Key == EKeys::Enter) - { - EditedHoudiniSplineComponent->MarkChanged(true); - - return true; - } - - bool bHandled = false; - - if (Key == EKeys::LeftMouseButton) - { - if (Event == IE_Pressed) - { - bMovingPoints = true; // Started moving points when the left mouse button is pressed - bAllowDuplication = true; - bRecordingMovingPoints = false; - } - - if (Event == IE_Released) - { - bMovingPoints = false; // Stopped moving points when the left mouse button is released - bAllowDuplication = false; - - if (bRecordingMovingPoints) - { - // Only mark the component as changed if a point was actually moved otherwise it will - // cook even if a point was selected. - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - } - - bRecordingMovingPoints = false; // allow recording pt moving again - } - } - - - if (Key == EKeys::Delete) - { - if (Event == IE_Pressed) return true; - - if (IsDeleteControlPointValid()) - { - OnDeleteControlPoint(); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - return true; - } - } - - - if (Event == IE_Pressed && VisualizerActions) - { - if (FSlateApplication::IsInitialized()) - bHandled = VisualizerActions->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), false); - } - - RefreshViewport(); - - return bHandled; -} - -void -FHoudiniSplineComponentVisualizer::EndEditing() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - // Clear edited spline if the EndEditing() function is not called from postUndo - if (!EditedHoudiniSplineComponent->bPostUndo) - { - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); - - EditedHoudiniSplineComponent = nullptr; - EditedCurveSegmentIndex = -1; - } - - //RefreshViewport(); -} - -bool -FHoudiniSplineComponentVisualizer::GetWidgetLocation( - const FEditorViewportClient* ViewportClient, - FVector& OutLocation) const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - // Set the widget location to the center of mass of the selected control points - FVector CenterLocation = FVector::ZeroVector; - - for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i) - { - CenterLocation += CurvePoints[EditedControlPointsIndexes[i]].GetLocation(); - } - - CenterLocation /= EditedControlPointsIndexes.Num(); - OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(CenterLocation); - - return true; -} - -bool -FHoudiniSplineComponentVisualizer::IsVisualizingArchetype() const -{ - UHoudiniSplineComponent* SplineComp = GetEditedHoudiniSplineComponent(); - return (SplineComp && SplineComp->GetOwner() && FActorEditorUtils::IsAPreviewOrInactiveActor(SplineComp->GetOwner())); -} - -bool -FHoudiniSplineComponentVisualizer::HandleInputDelta( - FEditorViewportClient* ViewportClient, - FViewport* Viewport, - FVector& DeltaTranslate, - FRotator& DeltaRotate, - FVector& DeltaScale) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!ViewportClient || !EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - if (ViewportClient->IsAltPressed() && bAllowDuplication) - { - OnDuplicateControlPoint(); - bAllowDuplication = false; - } - else - { - if (!bRecordingMovingPoints) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentMovingPointsTransaction", "Houdini Spline Component: Moving curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - - EditedHoudiniSplineComponent->Modify(); - - bRecordingMovingPoints = true; - } - } - - - TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - const FTransform & HoudiniSplineComponentTransform = EditedHoudiniSplineComponent->GetComponentTransform(); - - for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i ) - { - - FTransform CurrentPoint = EditedHoudiniSplineComponent->CurvePoints[EditedControlPointsIndexes[i]]; - - if (!DeltaTranslate.IsZero()) - { - FVector OldWorldPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint.GetLocation()); - FVector NewWorldPosition = OldWorldPosition + DeltaTranslate; - FVector NewLocalPosition = HoudiniSplineComponentTransform.InverseTransformPosition(NewWorldPosition); - CurrentPoint.SetLocation( NewLocalPosition ); - } - - if (!DeltaRotate.IsZero()) - { - FQuat OldWorldRotation = HoudiniSplineComponentTransform.GetRotation() * CurrentPoint.GetRotation(); - FQuat NewWorldRotation = DeltaRotate.Quaternion() * OldWorldRotation; - FQuat NewLocalRotation = HoudiniSplineComponentTransform.GetRotation().Inverse() * NewWorldRotation; - CurrentPoint.SetRotation(NewLocalRotation); - } - - if (!DeltaScale.IsZero()) - { - FVector NewScale = CurrentPoint.GetScale3D() * (FVector(1.f, 1.f, 1.f) + DeltaScale); - CurrentPoint.SetScale3D(NewScale); - } - - - EditedHoudiniSplineComponent->EditPointAtindex(CurrentPoint, EditedControlPointsIndexes[i]); - } - - RefreshViewport(); - - return true; -} - -TSharedPtr -FHoudiniSplineComponentVisualizer::GenerateContextMenu() const -{ - FHoudiniEngineEditor& HoudiniEngineEditor = FHoudiniEngineEditor::Get(); - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - FMenuBuilder MenuBuilder(true, VisualizerActions); - MenuBuilder.BeginSection("Houdini Spline actions"); - - // Create the context menu section - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - { - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, - NAME_None, TAttribute(), TAttribute(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDuplicateControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDeleteControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDeselectAllControlPoints, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandInsertControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - } - - MenuBuilder.EndSection(); - TSharedPtr MenuWidget = MenuBuilder.MakeWidget(); - return MenuWidget; -} - -// Used by alt-pressed on-curve control port insertion. -// We don't want it to be cooked before finishing editing. -// * Need to call WaitForHoudiniInputUpdate() after done. -int32 -FHoudiniSplineComponentVisualizer::OnInsertControlPointWithoutUpdate() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return -1; - - TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - TArray & DisplayPoints = EditedHoudiniSplineComponent->DisplayPoints; - - if (EditedCurveSegmentIndex >= DisplayPoints.Num()) - return -1; - - // ... // - int InsertAfterIndex = 0; - - TArray & DisplayPointIndexDivider = EditedHoudiniSplineComponent->DisplayPointIndexDivider; - for (int itr = 0; itr < DisplayPointIndexDivider.Num(); ++itr) - { - if (DisplayPointIndexDivider[itr] >= EditedCurveSegmentIndex) - { - InsertAfterIndex = itr; - break; - } - } - // ... // - - if (InsertAfterIndex >= CurvePoints.Num()) return -1; - - FTransform NewPoint = CurvePoints[InsertAfterIndex]; - NewPoint.SetLocation(DisplayPoints[EditedCurveSegmentIndex]); - // To Do: Should interpolate the rotation and scale as well here. - // ... - - // Insert new control point on curve, and add it to selected CP. - int32 NewPointIndex = AddControlPointAfter(NewPoint, InsertAfterIndex); - - // Don't have to reconstruct the index divider each time. - //EditedHoudiniSplineComponent->Construct(EditedHoudiniSplineComponent->DisplayPoints); - EditedHoudiniSplineComponent->DisplayPointIndexDivider.Insert(EditedCurveSegmentIndex, InsertAfterIndex); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - return NewPointIndex; -} - -void -FHoudiniSplineComponentVisualizer::OnInsertControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - int32 NewPointIndex = OnInsertControlPointWithoutUpdate(); - - if (NewPointIndex < 0) return; - - - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Add(NewPointIndex); - - RefreshViewport(); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); -} - -bool -FHoudiniSplineComponentVisualizer::IsInsertControlPointValid() const -{ - return EditedCurveSegmentIndex >= 0; -} - -void -FHoudiniSplineComponentVisualizer::OnAddControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentInsertingPointsTransaction", "Houdini Spline Component: Inserting curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - - EditedHoudiniSplineComponent->Modify(); - - EditedControlPointsIndexes.Sort(); - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - TArray tNewSelectedPoints; - - if (EditedControlPointsIndexes.Num() == 1) - { - FTransform Point = CurvePoints[EditedControlPointsIndexes[0]]; - FTransform NewTransform = FTransform::Identity; - FVector Location = Point.GetLocation(); - //FQuat Rotation = Point.GetRotation(); - //FVector Scale = Point.GetScale3D(); - - NewTransform.SetLocation(Location + 1.f); - //NewTransform.SetRotation(Rotation); - //NewTransform.SetScale3D(Scale); - - - - int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[0]); - tNewSelectedPoints.Add(NewPointIndex); - } - else - { - int IndexIncrement = 0; - int CurrentPointIndex, LastPointIndex; - FTransform CurrentPoint, LastPoint; - - for (int32 n = 0; n < EditedControlPointsIndexes.Num(); ++n) - { - // Insert a new point between each adjacent pair of points - if (n > 0) - { - CurrentPointIndex = EditedControlPointsIndexes[n]; - LastPointIndex = EditedControlPointsIndexes[n - 1]; - CurrentPoint = CurvePoints[CurrentPointIndex + IndexIncrement]; - LastPoint = CurvePoints[LastPointIndex + IndexIncrement]; - - // Insert a point in the middle of LastPoint and CurrentPoint - FVector NewPointLocation = LastPoint.GetLocation() + (CurrentPoint.GetLocation() - LastPoint.GetLocation()) / 2.f; - FVector NewPointScale = LastPoint.GetScale3D() + (CurrentPoint.GetScale3D() - LastPoint.GetScale3D()) / 2.f; - FQuat NewPointRotation = FQuat::Slerp(LastPoint.GetRotation(), CurrentPoint.GetRotation(), .5f); - - FTransform NewTransform = FTransform::Identity; - NewTransform.SetLocation(NewPointLocation); - NewTransform.SetScale3D(NewPointScale); - NewTransform.SetRotation(NewPointRotation); - - int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[n - 1] + IndexIncrement); - tNewSelectedPoints.Add(NewPointIndex); - - - IndexIncrement += 1; - } - } - } - - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes = tNewSelectedPoints; - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - RefreshViewport(); -} - - -bool -FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - return EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill() && - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; -} - -void -FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentDeletingPointsTransaction", "Houdini Spline Component: Deleting curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - EditedHoudiniSplineComponent->Modify(); - - EditedControlPointsIndexes.Sort(); - - int32 SelectedIndexAfterDelete = EditedControlPointsIndexes[0] - 1; - SelectedIndexAfterDelete = FMath::Max(SelectedIndexAfterDelete, 0); - - for (int32 n = EditedControlPointsIndexes.Num() - 1; n >= 0; --n) - { - int32 RemoveIndex = EditedControlPointsIndexes[n]; - EditedHoudiniSplineComponent->RemovePointAtIndex(RemoveIndex); - - } - - EditedControlPointsIndexes.Empty(); - OnDeselectAllControlPoints(); - EditedControlPointsIndexes.Add(SelectedIndexAfterDelete); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - // Force refresh the viewport after deleting points to ensure the consistency of HitProxy - RefreshViewport(); - -} - -bool -FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - // We only allow the number of Control Points is at least 2 after delete - if (EditedHoudiniSplineComponent->GetCurvePointCount() - EditedControlPointsIndexes.Num() < 2) - return false; - - return true; -} - -void -FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentDuplicatingPointsTransaction", "Houdini Spline Component: Duplicating curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - EditedHoudiniSplineComponent->Modify(); - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - EditedControlPointsIndexes.Sort(); - - TArray tNewSelectedPoints; - int IncrementIndex = 0; - for (int n = 0; n < EditedControlPointsIndexes.Num(); ++n) - { - int32 IndexAfter = EditedControlPointsIndexes[n] + IncrementIndex; - FTransform CurrentPoint = CurvePoints[IndexAfter]; - if (IndexAfter == 0) - IndexAfter = -1; - int32 NewPointIndex = AddControlPointAfter(CurrentPoint, IndexAfter); - tNewSelectedPoints.Add(NewPointIndex); - IncrementIndex ++; - } - - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes = tNewSelectedPoints; - - EditedHoudiniSplineComponent->MarkModified(true); - - RefreshViewport(); -} - -bool -FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if(!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill() - || EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() == 0) - return false; - - return true; -} - -void -FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); -} - -bool -FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - return EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; - - return false; -} - -int32 -FHoudiniSplineComponentVisualizer::AddControlPointAfter( - const FTransform & NewPoint, - const int32 & nIndex) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return nIndex; - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - int32 NewControlPointIndex = nIndex + 1; - - if (NewControlPointIndex == CurvePoints.Num()) - EditedHoudiniSplineComponent->AppendPoint(NewPoint); - else - EditedHoudiniSplineComponent->InsertPointAtIndex(NewPoint, NewControlPointIndex); - - // Return the index of the inserted control point - return NewControlPointIndex; -} - -void -FHoudiniSplineComponentVisualizer::RefreshViewport() -{ - if (GEditor) - GEditor->RedrawLevelEditingViewports(true); -} - -// Find the EditorViewportClient of the viewport where the Houdini Spline Component lives in -FEditorViewportClient * -FHoudiniSplineComponentVisualizer::FindViewportClient( - const UHoudiniSplineComponent * InHoudiniSplineComponent, - const FSceneView * View) -{ - if (!View || !InHoudiniSplineComponent) - return nullptr; - - UWorld * World = InHoudiniSplineComponent->GetWorld(); - uint32 ViewKey = View->GetViewKey(); - - const TArray & AllViewportClients = GUnrealEd->GetAllViewportClients(); - - for (auto & NextViewportClient : AllViewportClients) - { - if (!NextViewportClient) - continue; - - if (NextViewportClient->GetWorld() != World) - continue; - - // Found the viewport client which matches the unique key of the current scene view - if (NextViewportClient->ViewState.GetReference()->GetViewKey() == ViewKey) - return NextViewportClient; - } - - return nullptr; -} - -bool -FHoudiniSplineComponentVisualizer::IsCookOnCurveChanged(UHoudiniSplineComponent * InHoudiniSplineComponent) -{ - if (!InHoudiniSplineComponent) - return true; - - return InHoudiniSplineComponent->bCookOnCurveChanged; - - // UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - // UHoudiniInputObject * InputObject = Cast(EditedHoudiniSplineComponent->GetOuter()); - // if (!InputObject) - // return true; - // - // UHoudiniInput * Input = Cast(InputObject->GetOuter()); - // - // if (!Input) - // return true; - // - // return Input->GetCookOnCurveChange(); -}; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineComponentVisualizer.h" + +#include "ActorEditorUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniApi.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInputObject.h" +#include "HoudiniInput.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniEngineUtils.h" + +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" +#include "ComponentVisualizerManager.h" + +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "ScopedTransaction.h" +#include "EditorViewportClient.h" +#include "Engine/Selection.h" +#include "HModel.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_HIT_PROXY(HHoudiniSplineVisProxy, HComponentVisProxy); +IMPLEMENT_HIT_PROXY(HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy); +IMPLEMENT_HIT_PROXY(HHoudiniSplineCurveSegmentVisProxy, HHoudiniSplineVisProxy); + +HHoudiniSplineVisProxy::HHoudiniSplineVisProxy(const UActorComponent * InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) +{} + +HHoudiniSplineControlPointVisProxy::HHoudiniSplineControlPointVisProxy( + const UActorComponent * InComponent, int32 InControlPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , ControlPointIndex(InControlPointIndex) +{} + +HHoudiniSplineCurveSegmentVisProxy::HHoudiniSplineCurveSegmentVisProxy( + const UActorComponent * InComponent, int32 InDisplayPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , DisplayPointIndex(InDisplayPointIndex) +{} + +FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() + : TCommands< FHoudiniSplineComponentVisualizerCommands >( + "HoudiniSplineComponentVisualizer", + LOCTEXT("HoudiniSplineComponentVisualizer", "Houdini Spline Component Visualizer"), + NAME_None, + FEditorStyle::GetStyleSetName()) +{} + +void +FHoudiniSplineComponentVisualizerCommands::RegisterCommands() +{ + UI_COMMAND( + CommandAddControlPoint, "Add Control Point", "Add control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND( + CommandDuplicateControlPoint, "Duplicate Control Point", "Duplicate control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND( + CommandDeleteControlPoint, "Delete Control Point", "delete control points.", + EUserInterfaceActionType::Button, FInputChord(EKeys::Delete)); + + UI_COMMAND(CommandDeselectAllControlPoints, "Deselect All", "Deselect all control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(CommandInsertControlPoint, "Insert Control Point", "Insert a control point on curve.", + EUserInterfaceActionType::Button, FInputChord()); +} + + +FHoudiniSplineComponentVisualizer::FHoudiniSplineComponentVisualizer() + :FComponentVisualizer() + ,bAllowDuplication(false) + ,EditedCurveSegmentIndex(-1) + ,CachedRotation(FQuat::Identity) + ,CachedScale3D(FVector::OneVector) + ,bMovingPoints(false) + ,bInsertingOnCurveControlPoints(false) + ,bRecordingMovingPoints(false) +{ + FHoudiniSplineComponentVisualizerCommands::Register(); + VisualizerActions = MakeShareable(new FUICommandList); +} + +void +FHoudiniSplineComponentVisualizer::OnRegister() +{ + HOUDINI_LOG_MESSAGE(TEXT("Houdini Spline Component Visualizer Registered!")); + const auto & Commands = FHoudiniSplineComponentVisualizerCommands::Get(); + + VisualizerActions->MapAction( + Commands.CommandAddControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnAddControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsAddControlPointValid)); + + VisualizerActions->MapAction( + Commands.CommandDuplicateControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid)); + + VisualizerActions->MapAction( + Commands.CommandDeleteControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeleteControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid)); + + VisualizerActions->MapAction(Commands.CommandDeselectAllControlPoints, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid)); + + VisualizerActions->MapAction(Commands.CommandInsertControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnInsertControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsInsertControlPointValid)); +} + + +void +FHoudiniSplineComponentVisualizer::DrawVisualization( + const UActorComponent * Component, + const FSceneView * View, + FPrimitiveDrawInterface * PDI) +{ + const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >(Component); + + if (!HoudiniSplineComponent + || !PDI + || HoudiniSplineComponent->IsPendingKill() + || !HoudiniSplineComponent->IsVisible() + || !HoudiniSplineComponent->IsHoudiniSplineVisible()) + return; + + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + + // Note: Undo a transaction clears the active visualizer in ComponnetVisMangaer, which is private to Visualizer manager. + // HandleProxyForComponentVis() sets the active visualizer. So the selection will be lost after undo. + + // A Way to bypass this annoying UE4 implementation: + // If the drawing spline is the one being edited and an undo just happened, + // force to trigger a 'bubble' hit proxy to re-activate the visualizer. + if (HoudiniSplineComponent == EditedHoudiniSplineComponent && EditedHoudiniSplineComponent->bPostUndo) + { + EditedHoudiniSplineComponent->bPostUndo = false; + + FEditorViewportClient * FoundViewportClient = FindViewportClient(EditedHoudiniSplineComponent, View); + HComponentVisProxy * BubbleComponentHitProxy = new HComponentVisProxy(EditedHoudiniSplineComponent); + + if (FoundViewportClient && BubbleComponentHitProxy) + { + FViewportClick BubbleClick(View, FoundViewportClient, FKey(), EInputEvent::IE_Axis, 0, 0); + GUnrealEd->ComponentVisManager.HandleProxyForComponentVis(FoundViewportClient, BubbleComponentHitProxy, BubbleClick); + } + } + + static const FColor ColorNormal = FColor(255.f, 255.f, 255.f); + static const FColor ColorNormalHandleFirst(172.f, 255.f, 172.f); + static const FColor ColorNormalHandleSecond(254.f, 216.f, 177.f); + + static const FColor ColorSelectedHandle(255.f, 0.f, 0.f); + static const FColor ColorSelectedHandleFirst(0.f, 192.f, 0.f); + static const FColor ColorSelectedHandleSecond(255.f, 159.f, 0.f); + + static const float SizeGrabHandleSelected = 15.f; + static const float SizeGrabHandleNormalLarge = 18.f; + static const float SizeGrabHandleNormalSmall = 12.f; + + FVector PreviousPosition; + + if (HoudiniSplineComponent) + { + const FTransform & HoudiniSplineComponentTransform = HoudiniSplineComponent->GetComponentTransform(); + + const TArray< FVector > & DisplayPoints = HoudiniSplineComponent->DisplayPoints; // not used yet + const TArray< FTransform > & CurvePoints = HoudiniSplineComponent->CurvePoints; + + // Draw display points (simply linearly connect the control points for temporary) + for (int32 Index = 0; Index < DisplayPoints.Num(); ++Index) + { + const FVector & CurrentPoint = DisplayPoints[Index]; + FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); + //CurrentPosition = CurrentPoint; + if (Index > 0) + { + // Add a hitproxy for the line segment + PDI->SetHitProxy(new HHoudiniSplineCurveSegmentVisProxy(HoudiniSplineComponent, Index)); + // Draw a line connecting the previous point and the current point + PDI->DrawLine(PreviousPosition, CurrentPosition, ColorNormal, SDPG_Foreground); + PDI->SetHitProxy(nullptr); + } + + PreviousPosition = CurrentPosition; + } + + // Draw control points (do not draw control points if the curve is an output) + if (!HoudiniSplineComponent->bIsOutputCurve) + { + for (int32 Index = 0; Index < CurvePoints.Num(); ++Index) + { + const FVector & ControlPoint = HoudiniSplineComponentTransform.TransformPosition(CurvePoints[Index].GetLocation()); + + HHoudiniSplineControlPointVisProxy * HitProxy = new HHoudiniSplineControlPointVisProxy(HoudiniSplineComponent, Index); + PDI->SetHitProxy(HitProxy); + + FColor DrawColor = ColorNormal; + float DrawSize = SizeGrabHandleNormalSmall; + + if (Index == 0) + { + DrawColor = ColorNormalHandleFirst; + DrawSize = SizeGrabHandleNormalLarge; + } + + if (Index == 1) + DrawColor = ColorNormalHandleSecond; + + + // If this is an point that being editted + if (EditedHoudiniSplineComponent == HoudiniSplineComponent && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Contains(Index)) + { + if (Index == 0) + { + DrawColor = ColorSelectedHandleFirst; + } + + else if (Index == 1) + { + DrawColor = ColorSelectedHandleSecond; + DrawSize = SizeGrabHandleSelected; + } + + else + { + DrawColor = ColorSelectedHandle; + DrawSize = SizeGrabHandleSelected; + + } + } + + PDI->DrawPoint(ControlPoint, DrawColor, DrawSize, SDPG_Foreground); + PDI->SetHitProxy(nullptr); + } + + } + } +} + + +bool +FHoudiniSplineComponentVisualizer::VisProxyHandleClick( + FEditorViewportClient* InViewportClient, + HComponentVisProxy* VisProxy, + const FViewportClick& Click) +{ + if (!InViewportClient || !VisProxy || !VisProxy->Component.IsValid()) + return false; + + const UHoudiniSplineComponent * HoudiniSplineComponent = CastChecked< const UHoudiniSplineComponent >(VisProxy->Component.Get()); + + AActor* OldSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); + SplinePropertyPath = FComponentPropertyPath(HoudiniSplineComponent); + AActor* NewSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); + + if (!SplinePropertyPath.IsValid()) + { + SplinePropertyPath.Reset(); + return false; + } + + if (OldSplineOwningActor != NewSplineOwningActor) + { + // Reset selection state if we are selecting a different actor to the one previously selected + EditedCurveSegmentIndex = INDEX_NONE; + } + + // Note: This is for re-activating the component visualizer an undo. + // Return true if the hit proxy is a bubble (Neither HHoudiniSplineControlPointVisProxy nor HHoudiniSplineCurveSegmentVisProxy ) + // + if (!VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType()) && !VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) + return true; + + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + + EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); + + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + bool editingCurve = false; + + // If VisProxy is a HHoudiniSplineControlPointVisProxy + if (VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType())) + { + HHoudiniSplineControlPointVisProxy * ControlPointProxy = (HHoudiniSplineControlPointVisProxy*)VisProxy; + + if (!ControlPointProxy) + return editingCurve; + + editingCurve = true; + + // Clear the edited curve segment if a control point is clicked. + EditedCurveSegmentIndex = -1; + + if (Click.GetKey() != EKeys::LeftMouseButton) + return editingCurve; + + + if (InViewportClient->IsCtrlPressed()) + { + if (EditedControlPointsIndexes.Contains(ControlPointProxy->ControlPointIndex)) + { + EditedControlPointsIndexes.Remove(ControlPointProxy->ControlPointIndex); + } + else + { + EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); + } + } + else + { + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); + } + } + // VisProxy is a HHoudiniSplineCurveSegmentProxy + else if (VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) + { + //HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = Cast(VisProxy); + + HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = (HHoudiniSplineCurveSegmentVisProxy*)(VisProxy); + + if (!CurveSegmentProxy) + return false; + + editingCurve = true; + + if (Click.GetKey() == EKeys::LeftMouseButton && InViewportClient->IsAltPressed() && EditedHoudiniSplineComponent) + { + // Continuesly (Alt) inserting on-curve control points is only valid with Breakpoints mode, otherwise it has to be on linear curve type. + if (EditedHoudiniSplineComponent->CurveType != EHoudiniCurveType::Polygon && EditedHoudiniSplineComponent->CurveMethod != EHoudiniCurveMethod::Breakpoints) + return editingCurve; + + bInsertingOnCurveControlPoints = true; + + editingCurve = true; + EditedControlPointsIndexes.Empty(); + + EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; + int32 InsertedIndex = OnInsertControlPointWithoutUpdate(); + + if (InsertedIndex < 0) return false; + EditedControlPointsIndexes.Add(InsertedIndex); + + EditedCurveSegmentIndex = -1; + bInsertingOnCurveControlPoints = true; + + RefreshViewport(); + } + // Insert one on-curve control point. + else + { + EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; + return editingCurve; + } + } + + return editingCurve; +} + +bool +FHoudiniSplineComponentVisualizer::HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + if (Key == EKeys::Enter) + { + EditedHoudiniSplineComponent->MarkChanged(true); + + return true; + } + + bool bHandled = false; + + if (Key == EKeys::LeftMouseButton) + { + if (Event == IE_Pressed) + { + bMovingPoints = true; // Started moving points when the left mouse button is pressed + bAllowDuplication = true; + bRecordingMovingPoints = false; + } + + if (Event == IE_Released) + { + bMovingPoints = false; // Stopped moving points when the left mouse button is released + bAllowDuplication = false; + + if (bRecordingMovingPoints) + { + // Only mark the component as changed if a point was actually moved otherwise it will + // cook even if a point was selected. + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + } + + bRecordingMovingPoints = false; // allow recording pt moving again + } + } + + + if (Key == EKeys::Delete) + { + if (Event == IE_Pressed) return true; + + if (IsDeleteControlPointValid()) + { + OnDeleteControlPoint(); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + return true; + } + } + + + if (Event == IE_Pressed && VisualizerActions) + { + if (FSlateApplication::IsInitialized()) + bHandled = VisualizerActions->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), false); + } + + RefreshViewport(); + + return bHandled; +} + +void +FHoudiniSplineComponentVisualizer::EndEditing() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + // Clear edited spline if the EndEditing() function is not called from postUndo + if (!EditedHoudiniSplineComponent->bPostUndo) + { + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); + + EditedHoudiniSplineComponent = nullptr; + EditedCurveSegmentIndex = -1; + } + + //RefreshViewport(); +} + +bool +FHoudiniSplineComponentVisualizer::GetWidgetLocation( + const FEditorViewportClient* ViewportClient, + FVector& OutLocation) const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + // Set the widget location to the center of mass of the selected control points + FVector CenterLocation = FVector::ZeroVector; + + for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i) + { + CenterLocation += CurvePoints[EditedControlPointsIndexes[i]].GetLocation(); + } + + CenterLocation /= EditedControlPointsIndexes.Num(); + OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(CenterLocation); + + return true; +} + +bool +FHoudiniSplineComponentVisualizer::IsVisualizingArchetype() const +{ + UHoudiniSplineComponent* SplineComp = GetEditedHoudiniSplineComponent(); + return (SplineComp && SplineComp->GetOwner() && FActorEditorUtils::IsAPreviewOrInactiveActor(SplineComp->GetOwner())); +} + +bool +FHoudiniSplineComponentVisualizer::HandleInputDelta( + FEditorViewportClient* ViewportClient, + FViewport* Viewport, + FVector& DeltaTranslate, + FRotator& DeltaRotate, + FVector& DeltaScale) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!ViewportClient || !EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + if (ViewportClient->IsAltPressed() && bAllowDuplication) + { + OnDuplicateControlPoint(); + bAllowDuplication = false; + } + else + { + if (!bRecordingMovingPoints) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentMovingPointsTransaction", "Houdini Spline Component: Moving curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + + EditedHoudiniSplineComponent->Modify(); + + bRecordingMovingPoints = true; + } + } + + + TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + const FTransform & HoudiniSplineComponentTransform = EditedHoudiniSplineComponent->GetComponentTransform(); + + for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i ) + { + + FTransform CurrentPoint = EditedHoudiniSplineComponent->CurvePoints[EditedControlPointsIndexes[i]]; + + if (!DeltaTranslate.IsZero()) + { + FVector OldWorldPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint.GetLocation()); + FVector NewWorldPosition = OldWorldPosition + DeltaTranslate; + FVector NewLocalPosition = HoudiniSplineComponentTransform.InverseTransformPosition(NewWorldPosition); + CurrentPoint.SetLocation( NewLocalPosition ); + } + + if (!DeltaRotate.IsZero()) + { + FQuat OldWorldRotation = HoudiniSplineComponentTransform.GetRotation() * CurrentPoint.GetRotation(); + FQuat NewWorldRotation = DeltaRotate.Quaternion() * OldWorldRotation; + FQuat NewLocalRotation = HoudiniSplineComponentTransform.GetRotation().Inverse() * NewWorldRotation; + CurrentPoint.SetRotation(NewLocalRotation); + } + + if (!DeltaScale.IsZero()) + { + FVector NewScale = CurrentPoint.GetScale3D() * (FVector(1.f, 1.f, 1.f) + DeltaScale); + CurrentPoint.SetScale3D(NewScale); + } + + + EditedHoudiniSplineComponent->EditPointAtindex(CurrentPoint, EditedControlPointsIndexes[i]); + } + + RefreshViewport(); + + return true; +} + +TSharedPtr +FHoudiniSplineComponentVisualizer::GenerateContextMenu() const +{ + FHoudiniEngineEditor& HoudiniEngineEditor = FHoudiniEngineEditor::Get(); + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + FMenuBuilder MenuBuilder(true, VisualizerActions); + MenuBuilder.BeginSection("Houdini Spline actions"); + + // Create the context menu section + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + { + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, + NAME_None, TAttribute(), TAttribute(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDuplicateControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDeleteControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDeselectAllControlPoints, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandInsertControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + } + + MenuBuilder.EndSection(); + TSharedPtr MenuWidget = MenuBuilder.MakeWidget(); + return MenuWidget; +} + +// Used by alt-pressed on-curve control port insertion. +// We don't want it to be cooked before finishing editing. +// * Need to call WaitForHoudiniInputUpdate() after done. +int32 +FHoudiniSplineComponentVisualizer::OnInsertControlPointWithoutUpdate() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return -1; + + TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + TArray & DisplayPoints = EditedHoudiniSplineComponent->DisplayPoints; + + if (EditedCurveSegmentIndex >= DisplayPoints.Num()) + return -1; + + // ... // + int InsertAfterIndex = 0; + + TArray & DisplayPointIndexDivider = EditedHoudiniSplineComponent->DisplayPointIndexDivider; + for (int itr = 0; itr < DisplayPointIndexDivider.Num(); ++itr) + { + if (DisplayPointIndexDivider[itr] >= EditedCurveSegmentIndex) + { + InsertAfterIndex = itr; + break; + } + } + // ... // + + if (InsertAfterIndex >= CurvePoints.Num()) return -1; + + FTransform NewPoint = CurvePoints[InsertAfterIndex]; + NewPoint.SetLocation(DisplayPoints[EditedCurveSegmentIndex]); + // To Do: Should interpolate the rotation and scale as well here. + // ... + + // Insert new control point on curve, and add it to selected CP. + int32 NewPointIndex = AddControlPointAfter(NewPoint, InsertAfterIndex); + + // Don't have to reconstruct the index divider each time. + //EditedHoudiniSplineComponent->Construct(EditedHoudiniSplineComponent->DisplayPoints); + EditedHoudiniSplineComponent->DisplayPointIndexDivider.Insert(EditedCurveSegmentIndex, InsertAfterIndex); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + return NewPointIndex; +} + +void +FHoudiniSplineComponentVisualizer::OnInsertControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + int32 NewPointIndex = OnInsertControlPointWithoutUpdate(); + + if (NewPointIndex < 0) return; + + + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Add(NewPointIndex); + + RefreshViewport(); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); +} + +bool +FHoudiniSplineComponentVisualizer::IsInsertControlPointValid() const +{ + return EditedCurveSegmentIndex >= 0; +} + +void +FHoudiniSplineComponentVisualizer::OnAddControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentInsertingPointsTransaction", "Houdini Spline Component: Inserting curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + + EditedHoudiniSplineComponent->Modify(); + + EditedControlPointsIndexes.Sort(); + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + TArray tNewSelectedPoints; + + if (EditedControlPointsIndexes.Num() == 1) + { + FTransform Point = CurvePoints[EditedControlPointsIndexes[0]]; + FTransform NewTransform = FTransform::Identity; + FVector Location = Point.GetLocation(); + //FQuat Rotation = Point.GetRotation(); + //FVector Scale = Point.GetScale3D(); + + NewTransform.SetLocation(Location + 1.f); + //NewTransform.SetRotation(Rotation); + //NewTransform.SetScale3D(Scale); + + + + int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[0]); + tNewSelectedPoints.Add(NewPointIndex); + } + else + { + int IndexIncrement = 0; + int CurrentPointIndex, LastPointIndex; + FTransform CurrentPoint, LastPoint; + + for (int32 n = 0; n < EditedControlPointsIndexes.Num(); ++n) + { + // Insert a new point between each adjacent pair of points + if (n > 0) + { + CurrentPointIndex = EditedControlPointsIndexes[n]; + LastPointIndex = EditedControlPointsIndexes[n - 1]; + CurrentPoint = CurvePoints[CurrentPointIndex + IndexIncrement]; + LastPoint = CurvePoints[LastPointIndex + IndexIncrement]; + + // Insert a point in the middle of LastPoint and CurrentPoint + FVector NewPointLocation = LastPoint.GetLocation() + (CurrentPoint.GetLocation() - LastPoint.GetLocation()) / 2.f; + FVector NewPointScale = LastPoint.GetScale3D() + (CurrentPoint.GetScale3D() - LastPoint.GetScale3D()) / 2.f; + FQuat NewPointRotation = FQuat::Slerp(LastPoint.GetRotation(), CurrentPoint.GetRotation(), .5f); + + FTransform NewTransform = FTransform::Identity; + NewTransform.SetLocation(NewPointLocation); + NewTransform.SetScale3D(NewPointScale); + NewTransform.SetRotation(NewPointRotation); + + int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[n - 1] + IndexIncrement); + tNewSelectedPoints.Add(NewPointIndex); + + + IndexIncrement += 1; + } + } + } + + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes = tNewSelectedPoints; + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + RefreshViewport(); +} + + +bool +FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + return EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill() && + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; +} + +void +FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentDeletingPointsTransaction", "Houdini Spline Component: Deleting curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + EditedHoudiniSplineComponent->Modify(); + + EditedControlPointsIndexes.Sort(); + + int32 SelectedIndexAfterDelete = EditedControlPointsIndexes[0] - 1; + SelectedIndexAfterDelete = FMath::Max(SelectedIndexAfterDelete, 0); + + for (int32 n = EditedControlPointsIndexes.Num() - 1; n >= 0; --n) + { + int32 RemoveIndex = EditedControlPointsIndexes[n]; + EditedHoudiniSplineComponent->RemovePointAtIndex(RemoveIndex); + + } + + EditedControlPointsIndexes.Empty(); + OnDeselectAllControlPoints(); + EditedControlPointsIndexes.Add(SelectedIndexAfterDelete); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + // Force refresh the viewport after deleting points to ensure the consistency of HitProxy + RefreshViewport(); + +} + +bool +FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + // We only allow the number of Control Points is at least 2 after delete + if (EditedHoudiniSplineComponent->GetCurvePointCount() - EditedControlPointsIndexes.Num() < 2) + return false; + + return true; +} + +void +FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentDuplicatingPointsTransaction", "Houdini Spline Component: Duplicating curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + EditedHoudiniSplineComponent->Modify(); + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + EditedControlPointsIndexes.Sort(); + + TArray tNewSelectedPoints; + int IncrementIndex = 0; + for (int n = 0; n < EditedControlPointsIndexes.Num(); ++n) + { + int32 IndexAfter = EditedControlPointsIndexes[n] + IncrementIndex; + FTransform CurrentPoint = CurvePoints[IndexAfter]; + if (IndexAfter == 0) + IndexAfter = -1; + int32 NewPointIndex = AddControlPointAfter(CurrentPoint, IndexAfter); + tNewSelectedPoints.Add(NewPointIndex); + IncrementIndex ++; + } + + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes = tNewSelectedPoints; + + EditedHoudiniSplineComponent->MarkModified(true); + + RefreshViewport(); +} + +bool +FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if(!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill() + || EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() == 0) + return false; + + return true; +} + +void +FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); +} + +bool +FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + return EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; + + return false; +} + +int32 +FHoudiniSplineComponentVisualizer::AddControlPointAfter( + const FTransform & NewPoint, + const int32 & nIndex) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return nIndex; + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + int32 NewControlPointIndex = nIndex + 1; + + if (NewControlPointIndex == CurvePoints.Num()) + EditedHoudiniSplineComponent->AppendPoint(NewPoint); + else + EditedHoudiniSplineComponent->InsertPointAtIndex(NewPoint, NewControlPointIndex); + + // Return the index of the inserted control point + return NewControlPointIndex; +} + +void +FHoudiniSplineComponentVisualizer::RefreshViewport() +{ + if (GEditor) + GEditor->RedrawLevelEditingViewports(true); +} + +// Find the EditorViewportClient of the viewport where the Houdini Spline Component lives in +FEditorViewportClient * +FHoudiniSplineComponentVisualizer::FindViewportClient( + const UHoudiniSplineComponent * InHoudiniSplineComponent, + const FSceneView * View) +{ + if (!View || !InHoudiniSplineComponent) + return nullptr; + + UWorld * World = InHoudiniSplineComponent->GetWorld(); + uint32 ViewKey = View->GetViewKey(); + + const TArray & AllViewportClients = GUnrealEd->GetAllViewportClients(); + + for (auto & NextViewportClient : AllViewportClients) + { + if (!NextViewportClient) + continue; + + if (NextViewportClient->GetWorld() != World) + continue; + + // Found the viewport client which matches the unique key of the current scene view + if (NextViewportClient->ViewState.GetReference()->GetViewKey() == ViewKey) + return NextViewportClient; + } + + return nullptr; +} + +bool +FHoudiniSplineComponentVisualizer::IsCookOnCurveChanged(UHoudiniSplineComponent * InHoudiniSplineComponent) +{ + if (!InHoudiniSplineComponent) + return true; + + return InHoudiniSplineComponent->bCookOnCurveChanged; + + // UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + // UHoudiniInputObject * InputObject = Cast(EditedHoudiniSplineComponent->GetOuter()); + // if (!InputObject) + // return true; + // + // UHoudiniInput * Input = Cast(InputObject->GetOuter()); + // + // if (!Input) + // return true; + // + // return Input->GetCookOnCurveChange(); +}; + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h index 17a006068..53ab9547f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h @@ -1,176 +1,176 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniSplineComponent.h" - -#include "ComponentVisualizer.h" -#include "Framework/Commands/UICommandList.h" -#include "Framework/Commands/Commands.h" - -class FEditorViewportClient; - -/** Base class for clickable spline editing proxies. **/ -struct HHoudiniSplineVisProxy : public HComponentVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineVisProxy(const UActorComponent * InComponent); -}; - -/** Proxy for a spline control point. **/ -struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex); - - int32 ControlPointIndex; -}; - -/** Proxy for a spline display point. **/ -struct HHoudiniSplineCurveSegmentVisProxy : public HHoudiniSplineVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 IndisplayPointIndex); - - int32 DisplayPointIndex; -}; - -class FHoudiniSplineComponentVisualizerCommands : public TCommands< FHoudiniSplineComponentVisualizerCommands > -{ - public: - FHoudiniSplineComponentVisualizerCommands(); - - /** Register commands. **/ - virtual void RegisterCommands() override; - - public: - TSharedPtr CommandAddControlPoint; - - TSharedPtr CommandDuplicateControlPoint; - - TSharedPtr CommandDeleteControlPoint; - - TSharedPtr CommandDeselectAllControlPoints; - - TSharedPtr CommandInsertControlPoint; -}; - - -/** **/ -class FHoudiniSplineComponentVisualizer : public FComponentVisualizer -{ - public: - FHoudiniSplineComponentVisualizer(); - - private: - void RefreshViewport(); - - public: - virtual void OnRegister() override; - - virtual void DrawVisualization( - const UActorComponent * Component, const FSceneView * View, - FPrimitiveDrawInterface * PDI) override; - - virtual bool VisProxyHandleClick( - FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, - const FViewportClick& Click) override; - - virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; - virtual bool IsVisualizingArchetype() const override; - - virtual void EndEditing() override; - - virtual bool HandleInputDelta( - FEditorViewportClient* ViewportClient, FViewport* Viewport, - FVector& DeltaTranslate, FRotator& DeltaRotate, - FVector& DeltaScale) override; - - virtual bool HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) override; - - virtual TSharedPtr GenerateContextMenu() const override; - - protected: - - /** Callbacks for add control point action**/ - void OnAddControlPoint(); - bool IsAddControlPointValid() const; - - /** Callbacks for delete control point action. **/ - void OnDeleteControlPoint(); - bool IsDeleteControlPointValid() const; - - /** Callbacks for duplicate control point action. **/ - void OnDuplicateControlPoint(); - bool IsDuplicateControlPointValid() const; - - /** Callbacks for deselect all control points action. **/ - void OnDeselectAllControlPoints(); - bool IsDeselectAllControlPointsValid() const; - - /** Callbacks for inserting a control point action.**/ - void OnInsertControlPoint(); - bool IsInsertControlPointValid() const; - // For alt-pressed inserting control point on curve. - int32 OnInsertControlPointWithoutUpdate(); - - int32 AddControlPointAfter(const FTransform & NewPoint, const int32 & nIndex); - - public: - /** Property path from the parent actor to the component */ - // NOTE: We need to use SplinePropertyPath on the visualizer as opposed to a direct pointer since the - // direct pointer breaks during Blueprint reconstructions properly - // (see SplineComponent / SplineMeshComponent visualizers). - FComponentPropertyPath SplinePropertyPath; - UHoudiniSplineComponent* GetEditedHoudiniSplineComponent() const { return Cast(SplinePropertyPath.GetComponent()); } - - protected: - - bool bAllowDuplication; - - int32 EditedCurveSegmentIndex; - - TSharedPtr VisualizerActions; - - /** Rotation used for the gizmo widgets **/ - FQuat CachedRotation; - - FVector CachedScale3D; - - /** Indicates wether or not a transaction should be recorded when moving a point **/ - bool bMovingPoints; - - bool bInsertingOnCurveControlPoints; - - bool bRecordingMovingPoints; - - private: - FEditorViewportClient * FindViewportClient(const UHoudiniSplineComponent * InHoudiniSplineComponent, const FSceneView * View); - - bool IsCookOnCurveChanged(UHoudiniSplineComponent* InHoudiniSplineComponent); - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniSplineComponent.h" + +#include "ComponentVisualizer.h" +#include "Framework/Commands/UICommandList.h" +#include "Framework/Commands/Commands.h" + +class FEditorViewportClient; + +/** Base class for clickable spline editing proxies. **/ +struct HHoudiniSplineVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineVisProxy(const UActorComponent * InComponent); +}; + +/** Proxy for a spline control point. **/ +struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex); + + int32 ControlPointIndex; +}; + +/** Proxy for a spline display point. **/ +struct HHoudiniSplineCurveSegmentVisProxy : public HHoudiniSplineVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 IndisplayPointIndex); + + int32 DisplayPointIndex; +}; + +class FHoudiniSplineComponentVisualizerCommands : public TCommands< FHoudiniSplineComponentVisualizerCommands > +{ + public: + FHoudiniSplineComponentVisualizerCommands(); + + /** Register commands. **/ + virtual void RegisterCommands() override; + + public: + TSharedPtr CommandAddControlPoint; + + TSharedPtr CommandDuplicateControlPoint; + + TSharedPtr CommandDeleteControlPoint; + + TSharedPtr CommandDeselectAllControlPoints; + + TSharedPtr CommandInsertControlPoint; +}; + + +/** **/ +class FHoudiniSplineComponentVisualizer : public FComponentVisualizer +{ + public: + FHoudiniSplineComponentVisualizer(); + + private: + void RefreshViewport(); + + public: + virtual void OnRegister() override; + + virtual void DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI) override; + + virtual bool VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, + const FViewportClick& Click) override; + + virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; + virtual bool IsVisualizingArchetype() const override; + + virtual void EndEditing() override; + + virtual bool HandleInputDelta( + FEditorViewportClient* ViewportClient, FViewport* Viewport, + FVector& DeltaTranslate, FRotator& DeltaRotate, + FVector& DeltaScale) override; + + virtual bool HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) override; + + virtual TSharedPtr GenerateContextMenu() const override; + + protected: + + /** Callbacks for add control point action**/ + void OnAddControlPoint(); + bool IsAddControlPointValid() const; + + /** Callbacks for delete control point action. **/ + void OnDeleteControlPoint(); + bool IsDeleteControlPointValid() const; + + /** Callbacks for duplicate control point action. **/ + void OnDuplicateControlPoint(); + bool IsDuplicateControlPointValid() const; + + /** Callbacks for deselect all control points action. **/ + void OnDeselectAllControlPoints(); + bool IsDeselectAllControlPointsValid() const; + + /** Callbacks for inserting a control point action.**/ + void OnInsertControlPoint(); + bool IsInsertControlPointValid() const; + // For alt-pressed inserting control point on curve. + int32 OnInsertControlPointWithoutUpdate(); + + int32 AddControlPointAfter(const FTransform & NewPoint, const int32 & nIndex); + + public: + /** Property path from the parent actor to the component */ + // NOTE: We need to use SplinePropertyPath on the visualizer as opposed to a direct pointer since the + // direct pointer breaks during Blueprint reconstructions properly + // (see SplineComponent / SplineMeshComponent visualizers). + FComponentPropertyPath SplinePropertyPath; + UHoudiniSplineComponent* GetEditedHoudiniSplineComponent() const { return Cast(SplinePropertyPath.GetComponent()); } + + protected: + + bool bAllowDuplication; + + int32 EditedCurveSegmentIndex; + + TSharedPtr VisualizerActions; + + /** Rotation used for the gizmo widgets **/ + FQuat CachedRotation; + + FVector CachedScale3D; + + /** Indicates wether or not a transaction should be recorded when moving a point **/ + bool bMovingPoints; + + bool bInsertingOnCurveControlPoints; + + bool bRecordingMovingPoints; + + private: + FEditorViewportClient * FindViewportClient(const UHoudiniSplineComponent * InHoudiniSplineComponent, const FSceneView * View); + + bool IsCookOnCurveChanged(UHoudiniSplineComponent* InHoudiniSplineComponent); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp b/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp index 685d4d24c..17ac8fd8a 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp @@ -1,26 +1,26 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniTool.h b/Source/HoudiniEngineEditor/Private/HoudiniTool.h index 76b955f05..bacb6e09f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniTool.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniTool.h @@ -1,57 +1,57 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#pragma once - -UENUM() -enum class EHoudiniToolType : uint8 -{ - // For tools that generates geometry, and do not need input - HTOOLTYPE_GENERATOR UMETA(DisplayName = "Generator"), - - // For tools that have a single input, the selection will be merged in that single input - HTOOLTYPE_OPERATOR_SINGLE UMETA(DisplayName = "Operator (single)"), - - // For Tools that have multiple input, a single selected asset will be applied to each input - HTOOLTYPE_OPERATOR_MULTI UMETA(DisplayName = "Operator (multiple)"), - - // For tools that needs to be applied each time for each single selected - HTOOLTYPE_OPERATOR_BATCH UMETA(DisplayName = "Batch Operator") -}; - -UENUM() -enum class EHoudiniToolSelectionType : uint8 -{ - // For tools that can be applied both to Content Browser and World selection - HTOOL_SELECTION_ALL UMETA(DisplayName = "Content Browser AND World"), - - // For tools that can be applied only to World selection - HTOOL_SELECTION_WORLD_ONLY UMETA(DisplayName = "World selection only"), - - // For tools that can be applied only to Content Browser selection - HTOOL_SELECTION_CB_ONLY UMETA(DisplayName = "Content browser selection only") +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#pragma once + +UENUM() +enum class EHoudiniToolType : uint8 +{ + // For tools that generates geometry, and do not need input + HTOOLTYPE_GENERATOR UMETA(DisplayName = "Generator"), + + // For tools that have a single input, the selection will be merged in that single input + HTOOLTYPE_OPERATOR_SINGLE UMETA(DisplayName = "Operator (single)"), + + // For Tools that have multiple input, a single selected asset will be applied to each input + HTOOLTYPE_OPERATOR_MULTI UMETA(DisplayName = "Operator (multiple)"), + + // For tools that needs to be applied each time for each single selected + HTOOLTYPE_OPERATOR_BATCH UMETA(DisplayName = "Batch Operator") +}; + +UENUM() +enum class EHoudiniToolSelectionType : uint8 +{ + // For tools that can be applied both to Content Browser and World selection + HTOOL_SELECTION_ALL UMETA(DisplayName = "Content Browser AND World"), + + // For tools that can be applied only to World selection + HTOOL_SELECTION_WORLD_ONLY UMETA(DisplayName = "World selection only"), + + // For tools that can be applied only to Content Browser selection + HTOOL_SELECTION_CB_ONLY UMETA(DisplayName = "Content browser selection only") }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp index 044461aec..bbcfde8ec 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp @@ -1,349 +1,349 @@ -/* -* 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 "SNewFilePathPicker.h" - -#include "HoudiniApi.h" -#include "DesktopPlatformModule.h" -#include "Widgets/SBoxPanel.h" -#include "Framework/Application/SlateApplication.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SButton.h" - -#define LOCTEXT_NAMESPACE "SNewFilePathPicker" - -/* SNewFilePathPicker interface - *****************************************************************************/ - -void SNewFilePathPicker::Construct( const FArguments& InArgs ) -{ - BrowseDirectory = InArgs._BrowseDirectory; - BrowseTitle = InArgs._BrowseTitle; - FilePath = InArgs._FilePath; - FileTypeFilter = InArgs._FileTypeFilter; - OnPathPicked = InArgs._OnPathPicked; - IsNewFile = InArgs._IsNewFile; - IsDirectoryPicker = InArgs._IsDirectoryPicker; - - ChildSlot - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - [ - // file path text box - SAssignNew(TextBox, SEditableTextBox) - .Text(this, &SNewFilePathPicker::HandleTextBoxText) - .Font(InArgs._Font) - .SelectAllTextWhenFocused(true) - .ClearKeyboardFocusOnCommit(false) - .OnTextCommitted(this, &SNewFilePathPicker::HandleTextBoxTextCommitted) - .SelectAllTextOnCommit(false) - .IsReadOnly(InArgs._IsReadOnly) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(4.0f, 0.0f, 0.0f, 0.0f) - .VAlign(VAlign_Center) - [ - // browse button - SNew(SButton) - .ButtonStyle(InArgs._BrowseButtonStyle) - .ToolTipText(InArgs._BrowseButtonToolTip) - .OnClicked(this, &SNewFilePathPicker::HandleBrowseButtonClicked) - .ContentPadding(2.0f) - .ForegroundColor(FSlateColor::UseForeground()) - .IsFocusable(false) - [ - SNew(SImage) - .Image(InArgs._BrowseButtonImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ] - ]; -} - - -/* SNewFilePathPicker callbacks - *****************************************************************************/ -#if PLATFORM_WINDOWS - -#include "Windows/WindowsHWrapper.h" -#include "Windows/COMPointer.h" -//#include "Misc/Paths.h" -//#include "Misc/Guid.h" -#include "HAL/FileManager.h" -#include "Windows/AllowWindowsPlatformTypes.h" -#include -//#include -#include -//#include -//#include -//#include -//#include -#include "Windows/HideWindowsPlatformTypes.h" -//#pragma comment( lib, "version.lib" ) - -bool FileDialogShared( bool bSave, const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames, int32& OutFilterIndex ) -{ - FScopedSystemModalMode SystemModalScope; - - bool bSuccess = false; - TComPtr FileDialog; - if ( SUCCEEDED( ::CoCreateInstance( bSave ? CLSID_FileSaveDialog : CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, bSave ? IID_IFileSaveDialog : IID_IFileOpenDialog, IID_PPV_ARGS_Helper( &FileDialog ) ) ) ) - { - if ( bSave ) - { - // Set the default "filename" - if ( !DefaultFile.IsEmpty() ) - { - FileDialog->SetFileName( *FPaths::GetCleanFilename( DefaultFile ) ); - } - DWORD dwFlags = 0; - FileDialog->GetOptions( &dwFlags ); - FileDialog->SetOptions( dwFlags & ~FOS_OVERWRITEPROMPT ); - } - else - { - // Set this up as a multi-select picker - if ( Flags & EFileDialogFlags::Multiple ) - { - DWORD dwFlags = 0; - FileDialog->GetOptions( &dwFlags ); - FileDialog->SetOptions( dwFlags | FOS_ALLOWMULTISELECT ); - } - } - - // Set up common settings - FileDialog->SetTitle( *DialogTitle ); - if ( !DefaultPath.IsEmpty() ) - { - // SHCreateItemFromParsingName requires the given path be absolute and use \ rather than / as our normalized paths do - FString DefaultWindowsPath = FPaths::ConvertRelativePathToFull( DefaultPath ); - DefaultWindowsPath.ReplaceInline( TEXT( "/" ), TEXT( "\\" ), ESearchCase::CaseSensitive ); - - TComPtr DefaultPathItem; - if ( SUCCEEDED( ::SHCreateItemFromParsingName( *DefaultWindowsPath, nullptr, IID_PPV_ARGS( &DefaultPathItem ) ) ) ) - { - FileDialog->SetFolder( DefaultPathItem ); - } - } - - // Set-up the file type filters - TArray UnformattedExtensions; - TArray FileDialogFilters; - { - // Split the given filter string (formatted as "Pair1String1|Pair1String2|Pair2String1|Pair2String2") into the Windows specific filter struct - FileTypes.ParseIntoArray( UnformattedExtensions, TEXT( "|" ), true ); - - if ( UnformattedExtensions.Num() % 2 == 0 ) - { - FileDialogFilters.Reserve( UnformattedExtensions.Num() / 2 ); - for ( int32 ExtensionIndex = 0; ExtensionIndex < UnformattedExtensions.Num();) - { - COMDLG_FILTERSPEC& NewFilterSpec = FileDialogFilters[FileDialogFilters.AddDefaulted()]; - NewFilterSpec.pszName = *UnformattedExtensions[ExtensionIndex++]; - NewFilterSpec.pszSpec = *UnformattedExtensions[ExtensionIndex++]; - } - } - } - FileDialog->SetFileTypes( FileDialogFilters.Num(), FileDialogFilters.GetData() ); - - // Show the picker - if ( SUCCEEDED( FileDialog->Show( (HWND)ParentWindowHandle ) ) ) - { - OutFilterIndex = 0; - if ( SUCCEEDED( FileDialog->GetFileTypeIndex( (UINT*)&OutFilterIndex ) ) ) - { - OutFilterIndex -= 1; // GetFileTypeIndex returns a 1-based index - } - - auto AddOutFilename = [&OutFilenames]( const FString& InFilename ) - { - FString& OutFilename = OutFilenames[OutFilenames.Add( InFilename )]; - OutFilename = IFileManager::Get().ConvertToRelativePath( *OutFilename ); - FPaths::NormalizeFilename( OutFilename ); - }; - - if ( bSave ) - { - TComPtr Result; - if ( SUCCEEDED( FileDialog->GetResult( &Result ) ) ) - { - PWSTR pFilePath = nullptr; - if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) - { - bSuccess = true; - - // Apply the selected extension if the given filename doesn't already have one - FString SaveFilePath = pFilePath; - if ( FileDialogFilters.IsValidIndex( OutFilterIndex ) ) - { - // Build a "clean" version of the selected extension (without the wildcard) - FString CleanExtension = FileDialogFilters[OutFilterIndex].pszSpec; - if ( CleanExtension == TEXT( "*.*" ) ) - { - CleanExtension.Reset(); - } - else - { - const int32 WildCardIndex = CleanExtension.Find( TEXT( "*" ) ); - if ( WildCardIndex != INDEX_NONE ) - { - CleanExtension = CleanExtension.RightChop( WildCardIndex + 1 ); - } - } - - // We need to split these before testing the extension to avoid anything within the path being treated as a file extension - FString SaveFileName = FPaths::GetCleanFilename( SaveFilePath ); - SaveFilePath = FPaths::GetPath( SaveFilePath ); - - // Apply the extension if the file name doesn't already have one - if ( FPaths::GetExtension( SaveFileName ).IsEmpty() && !CleanExtension.IsEmpty() ) - { - SaveFileName = FPaths::SetExtension( SaveFileName, CleanExtension ); - } - - SaveFilePath /= SaveFileName; - } - AddOutFilename( SaveFilePath ); - - ::CoTaskMemFree( pFilePath ); - } - } - } - else - { - IFileOpenDialog* FileOpenDialog = static_cast( FileDialog.Get() ); - - TComPtr Results; - if ( SUCCEEDED( FileOpenDialog->GetResults( &Results ) ) ) - { - DWORD NumResults = 0; - Results->GetCount( &NumResults ); - for ( DWORD ResultIndex = 0; ResultIndex < NumResults; ++ResultIndex ) - { - TComPtr Result; - if ( SUCCEEDED( Results->GetItemAt( ResultIndex, &Result ) ) ) - { - PWSTR pFilePath = nullptr; - if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) - { - bSuccess = true; - AddOutFilename( pFilePath ); - ::CoTaskMemFree( pFilePath ); - } - } - } - } - } - } - } - - return bSuccess; -} - -bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) -{ - int32 DummyFilterIndex = 0; - return FileDialogShared( true, ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames, DummyFilterIndex ); -} - -#else - -bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) -{ - IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); - return DesktopPlatform->SaveFileDialog( ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames ); -} -#endif - -FReply SNewFilePathPicker::HandleBrowseButtonClicked() -{ - IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); - - if (DesktopPlatform == nullptr) - { - return FReply::Handled(); - } - - const FString DefaultPath = BrowseDirectory.IsSet() - ? BrowseDirectory.Get() - : FPaths::GetPath(FilePath.Get()); - - // show the file browse dialog - if (!FSlateApplication::IsInitialized()) - return FReply::Handled(); - - TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); - void* ParentWindowHandle = (ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid()) - ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() - : nullptr; - - if(!IsDirectoryPicker.Get()) - { - TArray OutFiles; - // CG: Use SaveFileDialog instead of OpenFileDialog - if ( IsNewFile.Get() ) - { - if ( SaveFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) - { - OnPathPicked.ExecuteIfBound( OutFiles[0] ); - } - } - else - { - if ( DesktopPlatform->OpenFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) - { - OnPathPicked.ExecuteIfBound( OutFiles[0] ); - } - } - } - else - { - FString OutDir; - if (DesktopPlatform->OpenDirectoryDialog(ParentWindowHandle, LOCTEXT("FolderDialogTitle", "Choose a directory").ToString(), DefaultPath, OutDir)) - { - OnPathPicked.ExecuteIfBound(OutDir); - } - } - - return FReply::Handled(); -} - - -FText SNewFilePathPicker::HandleTextBoxText() const -{ - return FText::FromString(FilePath.Get()); -} - - -void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type CommitInfo ) -{ - OnPathPicked.ExecuteIfBound(NewText.ToString()); -} - -#undef LOCTEXT_NAMESPACE \ No newline at end of file +/* +* 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 "SNewFilePathPicker.h" + +#include "HoudiniApi.h" +#include "DesktopPlatformModule.h" +#include "Widgets/SBoxPanel.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SButton.h" + +#define LOCTEXT_NAMESPACE "SNewFilePathPicker" + +/* SNewFilePathPicker interface + *****************************************************************************/ + +void SNewFilePathPicker::Construct( const FArguments& InArgs ) +{ + BrowseDirectory = InArgs._BrowseDirectory; + BrowseTitle = InArgs._BrowseTitle; + FilePath = InArgs._FilePath; + FileTypeFilter = InArgs._FileTypeFilter; + OnPathPicked = InArgs._OnPathPicked; + IsNewFile = InArgs._IsNewFile; + IsDirectoryPicker = InArgs._IsDirectoryPicker; + + ChildSlot + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + // file path text box + SAssignNew(TextBox, SEditableTextBox) + .Text(this, &SNewFilePathPicker::HandleTextBoxText) + .Font(InArgs._Font) + .SelectAllTextWhenFocused(true) + .ClearKeyboardFocusOnCommit(false) + .OnTextCommitted(this, &SNewFilePathPicker::HandleTextBoxTextCommitted) + .SelectAllTextOnCommit(false) + .IsReadOnly(InArgs._IsReadOnly) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(4.0f, 0.0f, 0.0f, 0.0f) + .VAlign(VAlign_Center) + [ + // browse button + SNew(SButton) + .ButtonStyle(InArgs._BrowseButtonStyle) + .ToolTipText(InArgs._BrowseButtonToolTip) + .OnClicked(this, &SNewFilePathPicker::HandleBrowseButtonClicked) + .ContentPadding(2.0f) + .ForegroundColor(FSlateColor::UseForeground()) + .IsFocusable(false) + [ + SNew(SImage) + .Image(InArgs._BrowseButtonImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ] + ]; +} + + +/* SNewFilePathPicker callbacks + *****************************************************************************/ +#if PLATFORM_WINDOWS + +#include "Windows/WindowsHWrapper.h" +#include "Windows/COMPointer.h" +//#include "Misc/Paths.h" +//#include "Misc/Guid.h" +#include "HAL/FileManager.h" +#include "Windows/AllowWindowsPlatformTypes.h" +#include +//#include +#include +//#include +//#include +//#include +//#include +#include "Windows/HideWindowsPlatformTypes.h" +//#pragma comment( lib, "version.lib" ) + +bool FileDialogShared( bool bSave, const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames, int32& OutFilterIndex ) +{ + FScopedSystemModalMode SystemModalScope; + + bool bSuccess = false; + TComPtr FileDialog; + if ( SUCCEEDED( ::CoCreateInstance( bSave ? CLSID_FileSaveDialog : CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, bSave ? IID_IFileSaveDialog : IID_IFileOpenDialog, IID_PPV_ARGS_Helper( &FileDialog ) ) ) ) + { + if ( bSave ) + { + // Set the default "filename" + if ( !DefaultFile.IsEmpty() ) + { + FileDialog->SetFileName( *FPaths::GetCleanFilename( DefaultFile ) ); + } + DWORD dwFlags = 0; + FileDialog->GetOptions( &dwFlags ); + FileDialog->SetOptions( dwFlags & ~FOS_OVERWRITEPROMPT ); + } + else + { + // Set this up as a multi-select picker + if ( Flags & EFileDialogFlags::Multiple ) + { + DWORD dwFlags = 0; + FileDialog->GetOptions( &dwFlags ); + FileDialog->SetOptions( dwFlags | FOS_ALLOWMULTISELECT ); + } + } + + // Set up common settings + FileDialog->SetTitle( *DialogTitle ); + if ( !DefaultPath.IsEmpty() ) + { + // SHCreateItemFromParsingName requires the given path be absolute and use \ rather than / as our normalized paths do + FString DefaultWindowsPath = FPaths::ConvertRelativePathToFull( DefaultPath ); + DefaultWindowsPath.ReplaceInline( TEXT( "/" ), TEXT( "\\" ), ESearchCase::CaseSensitive ); + + TComPtr DefaultPathItem; + if ( SUCCEEDED( ::SHCreateItemFromParsingName( *DefaultWindowsPath, nullptr, IID_PPV_ARGS( &DefaultPathItem ) ) ) ) + { + FileDialog->SetFolder( DefaultPathItem ); + } + } + + // Set-up the file type filters + TArray UnformattedExtensions; + TArray FileDialogFilters; + { + // Split the given filter string (formatted as "Pair1String1|Pair1String2|Pair2String1|Pair2String2") into the Windows specific filter struct + FileTypes.ParseIntoArray( UnformattedExtensions, TEXT( "|" ), true ); + + if ( UnformattedExtensions.Num() % 2 == 0 ) + { + FileDialogFilters.Reserve( UnformattedExtensions.Num() / 2 ); + for ( int32 ExtensionIndex = 0; ExtensionIndex < UnformattedExtensions.Num();) + { + COMDLG_FILTERSPEC& NewFilterSpec = FileDialogFilters[FileDialogFilters.AddDefaulted()]; + NewFilterSpec.pszName = *UnformattedExtensions[ExtensionIndex++]; + NewFilterSpec.pszSpec = *UnformattedExtensions[ExtensionIndex++]; + } + } + } + FileDialog->SetFileTypes( FileDialogFilters.Num(), FileDialogFilters.GetData() ); + + // Show the picker + if ( SUCCEEDED( FileDialog->Show( (HWND)ParentWindowHandle ) ) ) + { + OutFilterIndex = 0; + if ( SUCCEEDED( FileDialog->GetFileTypeIndex( (UINT*)&OutFilterIndex ) ) ) + { + OutFilterIndex -= 1; // GetFileTypeIndex returns a 1-based index + } + + auto AddOutFilename = [&OutFilenames]( const FString& InFilename ) + { + FString& OutFilename = OutFilenames[OutFilenames.Add( InFilename )]; + OutFilename = IFileManager::Get().ConvertToRelativePath( *OutFilename ); + FPaths::NormalizeFilename( OutFilename ); + }; + + if ( bSave ) + { + TComPtr Result; + if ( SUCCEEDED( FileDialog->GetResult( &Result ) ) ) + { + PWSTR pFilePath = nullptr; + if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) + { + bSuccess = true; + + // Apply the selected extension if the given filename doesn't already have one + FString SaveFilePath = pFilePath; + if ( FileDialogFilters.IsValidIndex( OutFilterIndex ) ) + { + // Build a "clean" version of the selected extension (without the wildcard) + FString CleanExtension = FileDialogFilters[OutFilterIndex].pszSpec; + if ( CleanExtension == TEXT( "*.*" ) ) + { + CleanExtension.Reset(); + } + else + { + const int32 WildCardIndex = CleanExtension.Find( TEXT( "*" ) ); + if ( WildCardIndex != INDEX_NONE ) + { + CleanExtension = CleanExtension.RightChop( WildCardIndex + 1 ); + } + } + + // We need to split these before testing the extension to avoid anything within the path being treated as a file extension + FString SaveFileName = FPaths::GetCleanFilename( SaveFilePath ); + SaveFilePath = FPaths::GetPath( SaveFilePath ); + + // Apply the extension if the file name doesn't already have one + if ( FPaths::GetExtension( SaveFileName ).IsEmpty() && !CleanExtension.IsEmpty() ) + { + SaveFileName = FPaths::SetExtension( SaveFileName, CleanExtension ); + } + + SaveFilePath /= SaveFileName; + } + AddOutFilename( SaveFilePath ); + + ::CoTaskMemFree( pFilePath ); + } + } + } + else + { + IFileOpenDialog* FileOpenDialog = static_cast( FileDialog.Get() ); + + TComPtr Results; + if ( SUCCEEDED( FileOpenDialog->GetResults( &Results ) ) ) + { + DWORD NumResults = 0; + Results->GetCount( &NumResults ); + for ( DWORD ResultIndex = 0; ResultIndex < NumResults; ++ResultIndex ) + { + TComPtr Result; + if ( SUCCEEDED( Results->GetItemAt( ResultIndex, &Result ) ) ) + { + PWSTR pFilePath = nullptr; + if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) + { + bSuccess = true; + AddOutFilename( pFilePath ); + ::CoTaskMemFree( pFilePath ); + } + } + } + } + } + } + } + + return bSuccess; +} + +bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) +{ + int32 DummyFilterIndex = 0; + return FileDialogShared( true, ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames, DummyFilterIndex ); +} + +#else + +bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + return DesktopPlatform->SaveFileDialog( ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames ); +} +#endif + +FReply SNewFilePathPicker::HandleBrowseButtonClicked() +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + + if (DesktopPlatform == nullptr) + { + return FReply::Handled(); + } + + const FString DefaultPath = BrowseDirectory.IsSet() + ? BrowseDirectory.Get() + : FPaths::GetPath(FilePath.Get()); + + // show the file browse dialog + if (!FSlateApplication::IsInitialized()) + return FReply::Handled(); + + TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + void* ParentWindowHandle = (ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid()) + ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() + : nullptr; + + if(!IsDirectoryPicker.Get()) + { + TArray OutFiles; + // CG: Use SaveFileDialog instead of OpenFileDialog + if ( IsNewFile.Get() ) + { + if ( SaveFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) + { + OnPathPicked.ExecuteIfBound( OutFiles[0] ); + } + } + else + { + if ( DesktopPlatform->OpenFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) + { + OnPathPicked.ExecuteIfBound( OutFiles[0] ); + } + } + } + else + { + FString OutDir; + if (DesktopPlatform->OpenDirectoryDialog(ParentWindowHandle, LOCTEXT("FolderDialogTitle", "Choose a directory").ToString(), DefaultPath, OutDir)) + { + OnPathPicked.ExecuteIfBound(OutDir); + } + } + + return FReply::Handled(); +} + + +FText SNewFilePathPicker::HandleTextBoxText() const +{ + return FText::FromString(FilePath.Get()); +} + + +void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type CommitInfo ) +{ + OnPathPicked.ExecuteIfBound(NewText.ToString()); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h index 3892580f5..64b256546 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h @@ -1,147 +1,147 @@ -/* -* 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. -* -*/ - -// This is the same as SFilePathPicker but it uses SaveFileDialog instead of OpenFileDialog -// to allow browsing to a new path - -#pragma once - -#include "CoreMinimal.h" -#include "Misc/Attribute.h" -#include "Fonts/SlateFontInfo.h" -#include "Input/Reply.h" -#include "Styling/SlateWidgetStyleAsset.h" -#include "Styling/ISlateStyle.h" -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SCompoundWidget.h" -#include "Styling/SlateTypes.h" - -class SEditableTextBox; - -/** - * Declares a delegate that is executed when a file was picked in the SFilePathPicker widget. - * - * The first parameter will contain the path to the picked file. - */ -DECLARE_DELEGATE_OneParam(FOnPathPicked, const FString& /*PickedPath*/); - - -/** - * Implements an editable text box with a browse button. - */ -class SNewFilePathPicker - : public SCompoundWidget -{ -public: - - SLATE_BEGIN_ARGS(SNewFilePathPicker) - : _BrowseButtonToolTip(NSLOCTEXT("SNewFilePathPicker", "BrowseButtonToolTip", "Choose a file from this computer")) - , _FileTypeFilter(TEXT("All files (*.*)|*.*")) - , _Font() - , _IsReadOnly(false) - , _IsNewFile(true) - , _IsDirectoryPicker(false) - { } - - /** Browse button image resource. */ - SLATE_ATTRIBUTE(const FSlateBrush*, BrowseButtonImage) - - /** Browse button visual style. */ - SLATE_STYLE_ARGUMENT(FButtonStyle, BrowseButtonStyle) - - /** Browse button tool tip text. */ - SLATE_ATTRIBUTE(FText, BrowseButtonToolTip) - - /** The directory to browse by default */ - SLATE_ATTRIBUTE(FString, BrowseDirectory) - - /** Title for the browse dialog window. */ - SLATE_ATTRIBUTE(FText, BrowseTitle) - - /** The currently selected file path. */ - SLATE_ATTRIBUTE(FString, FilePath) - - /** File type filter string. */ - SLATE_ATTRIBUTE(FString, FileTypeFilter) - - /** Font color and opacity of the path text box. */ - SLATE_ATTRIBUTE(FSlateFontInfo, Font) - - /** Whether the path text box can be modified by the user. */ - SLATE_ATTRIBUTE(bool, IsReadOnly) - - /** Whether to use the new-file dialog instead of open-file */ - SLATE_ATTRIBUTE(bool, IsNewFile) - - /** Whether to use the a directory picker dialog */ - SLATE_ATTRIBUTE(bool, IsDirectoryPicker) - - /** Called when a file path has been picked. */ - SLATE_EVENT(FOnPathPicked, OnPathPicked) - - SLATE_END_ARGS() - - /** - * Constructs a new widget. - * - * @param InArgs The construction arguments. - */ - void Construct( const FArguments& InArgs ); - -private: - - /** Callback for clicking the browse button. */ - FReply HandleBrowseButtonClicked( ); - - /** Callback for getting the text in the path text box. */ - FText HandleTextBoxText( ) const; - - /** Callback for committing the text in the path text box. */ - void HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type /*CommitInfo*/ ); - -private: - - /** Holds the directory path to browse by default. */ - TAttribute BrowseDirectory; - - /** Holds the title for the browse dialog window. */ - TAttribute BrowseTitle; - - /** Holds the currently selected file path. */ - TAttribute FilePath; - - /** Holds the file type filter string. */ - TAttribute FileTypeFilter; - - /** Holds the editable text box. */ - TSharedPtr TextBox; - - TAttribute IsNewFile; - - TAttribute IsDirectoryPicker; - -private: - - /** Holds a delegate that is executed when a file was picked. */ - FOnPathPicked OnPathPicked; -}; +/* +* 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. +* +*/ + +// This is the same as SFilePathPicker but it uses SaveFileDialog instead of OpenFileDialog +// to allow browsing to a new path + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Attribute.h" +#include "Fonts/SlateFontInfo.h" +#include "Input/Reply.h" +#include "Styling/SlateWidgetStyleAsset.h" +#include "Styling/ISlateStyle.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Styling/SlateTypes.h" + +class SEditableTextBox; + +/** + * Declares a delegate that is executed when a file was picked in the SFilePathPicker widget. + * + * The first parameter will contain the path to the picked file. + */ +DECLARE_DELEGATE_OneParam(FOnPathPicked, const FString& /*PickedPath*/); + + +/** + * Implements an editable text box with a browse button. + */ +class SNewFilePathPicker + : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SNewFilePathPicker) + : _BrowseButtonToolTip(NSLOCTEXT("SNewFilePathPicker", "BrowseButtonToolTip", "Choose a file from this computer")) + , _FileTypeFilter(TEXT("All files (*.*)|*.*")) + , _Font() + , _IsReadOnly(false) + , _IsNewFile(true) + , _IsDirectoryPicker(false) + { } + + /** Browse button image resource. */ + SLATE_ATTRIBUTE(const FSlateBrush*, BrowseButtonImage) + + /** Browse button visual style. */ + SLATE_STYLE_ARGUMENT(FButtonStyle, BrowseButtonStyle) + + /** Browse button tool tip text. */ + SLATE_ATTRIBUTE(FText, BrowseButtonToolTip) + + /** The directory to browse by default */ + SLATE_ATTRIBUTE(FString, BrowseDirectory) + + /** Title for the browse dialog window. */ + SLATE_ATTRIBUTE(FText, BrowseTitle) + + /** The currently selected file path. */ + SLATE_ATTRIBUTE(FString, FilePath) + + /** File type filter string. */ + SLATE_ATTRIBUTE(FString, FileTypeFilter) + + /** Font color and opacity of the path text box. */ + SLATE_ATTRIBUTE(FSlateFontInfo, Font) + + /** Whether the path text box can be modified by the user. */ + SLATE_ATTRIBUTE(bool, IsReadOnly) + + /** Whether to use the new-file dialog instead of open-file */ + SLATE_ATTRIBUTE(bool, IsNewFile) + + /** Whether to use the a directory picker dialog */ + SLATE_ATTRIBUTE(bool, IsDirectoryPicker) + + /** Called when a file path has been picked. */ + SLATE_EVENT(FOnPathPicked, OnPathPicked) + + SLATE_END_ARGS() + + /** + * Constructs a new widget. + * + * @param InArgs The construction arguments. + */ + void Construct( const FArguments& InArgs ); + +private: + + /** Callback for clicking the browse button. */ + FReply HandleBrowseButtonClicked( ); + + /** Callback for getting the text in the path text box. */ + FText HandleTextBoxText( ) const; + + /** Callback for committing the text in the path text box. */ + void HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type /*CommitInfo*/ ); + +private: + + /** Holds the directory path to browse by default. */ + TAttribute BrowseDirectory; + + /** Holds the title for the browse dialog window. */ + TAttribute BrowseTitle; + + /** Holds the currently selected file path. */ + TAttribute FilePath; + + /** Holds the file type filter string. */ + TAttribute FileTypeFilter; + + /** Holds the editable text box. */ + TSharedPtr TextBox; + + TAttribute IsNewFile; + + TAttribute IsDirectoryPicker; + +private: + + /** Holds a delegate that is executed when a file was picked. */ + FOnPathPicked OnPathPicked; +}; diff --git a/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h b/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h index 3a722a67b..940f0ab60 100644 --- a/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h +++ b/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Modules/ModuleInterface.h" - -class IHoudiniEngineEditor : public IModuleInterface -{ - public: - /** Register and unregister component visualizers used by this module. **/ - virtual void RegisterComponentVisualizers() {} - virtual void UnregisterComponentVisualizers() {} - - /** Register and unregister detail presenters used by this module. **/ - virtual void RegisterDetails() {} - virtual void UnregisterDetails() {} - - /** Register and unregister asset type actions. **/ - virtual void RegisterAssetTypeActions() {} - virtual void UnregisterAssetTypeActions() {} - - /** Create and register / unregister asset brokers. **/ - virtual void RegisterAssetBrokers() {} - virtual void UnregisterAssetBrokers() {} - - /** Create and register actor factories. **/ - virtual void RegisterActorFactories() {} - - /** Extend menu. **/ - virtual void ExtendMenu() {} - - /** Register and unregister thumbnails. **/ - virtual void RegisterThumbnails() {} - virtual void UnregisterThumbnails() {} - - /** Register and unregister for undo/redo notifications. **/ - virtual void RegisterForUndo() {} - virtual void UnregisterForUndo() {} - - /** Create custom modes **/ - virtual void RegisterModes() {} - virtual void UnregisterModes() {} - - /** Create custom placement extensions */ - virtual void RegisterPlacementModeExtensions() {} - virtual void UnregisterPlacementModeExtensions() {} -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Modules/ModuleInterface.h" + +class IHoudiniEngineEditor : public IModuleInterface +{ + public: + /** Register and unregister component visualizers used by this module. **/ + virtual void RegisterComponentVisualizers() {} + virtual void UnregisterComponentVisualizers() {} + + /** Register and unregister detail presenters used by this module. **/ + virtual void RegisterDetails() {} + virtual void UnregisterDetails() {} + + /** Register and unregister asset type actions. **/ + virtual void RegisterAssetTypeActions() {} + virtual void UnregisterAssetTypeActions() {} + + /** Create and register / unregister asset brokers. **/ + virtual void RegisterAssetBrokers() {} + virtual void UnregisterAssetBrokers() {} + + /** Create and register actor factories. **/ + virtual void RegisterActorFactories() {} + + /** Extend menu. **/ + virtual void ExtendMenu() {} + + /** Register and unregister thumbnails. **/ + virtual void RegisterThumbnails() {} + virtual void UnregisterThumbnails() {} + + /** Register and unregister for undo/redo notifications. **/ + virtual void RegisterForUndo() {} + virtual void UnregisterForUndo() {} + + /** Create custom modes **/ + virtual void RegisterModes() {} + virtual void UnregisterModes() {} + + /** Create custom placement extensions */ + virtual void RegisterPlacementModeExtensions() {} + virtual void UnregisterPlacementModeExtensions() {} +}; diff --git a/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs b/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs index b0e189cba..e6caf2291 100644 --- a/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs +++ b/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs @@ -1,96 +1,96 @@ -/* - * Copyright (c) <2020> 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. - * - * Produced by: - * Side Effects Software Inc - * 123 Front Street West, Suite 1401 - * Toronto, Ontario - * Canada M5J 2M2 - * 416-504-9876 - * - */ - -using UnrealBuildTool; -using System; -using System.IO; - -public class HoudiniEngineRuntime : ModuleRules -{ - public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) - { - bPrecompile = true; - PCHUsage = PCHUsageMode.NoSharedPCHs; - PrivatePCHHeaderFile = "Private/HoudiniEngineRuntimePrivatePCH.h"; - - // Check if we are compiling for unsupported platforms. - if ( Target.Platform != UnrealTargetPlatform.Win64 && - Target.Platform != UnrealTargetPlatform.Mac && - Target.Platform != UnrealTargetPlatform.Linux && - Target.Platform != UnrealTargetPlatform.Switch ) - { - System.Console.WriteLine( string.Format( "Houdini Engine Runtime: Compiling for untested target platform. Please let us know how it goes!" ) ); - } - - - PublicIncludePaths.AddRange( - new string[] {} - ); - - PrivateIncludePaths.AddRange( - new string[] { } - ); - - // Add common dependencies. - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "Engine", - "RenderCore", - "InputCore", - "RHI", - "Foliage", - "Landscape" - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "Landscape", - "PropertyPath" - - } - ); - - if (Target.bBuildEditor == true) - { - PrivateDependencyModuleNames.AddRange( - new string[] - { - "UnrealEd", - "Kismet", - } - ); - } - } -} +/* + * Copyright (c) <2020> 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. + * + * Produced by: + * Side Effects Software Inc + * 123 Front Street West, Suite 1401 + * Toronto, Ontario + * Canada M5J 2M2 + * 416-504-9876 + * + */ + +using UnrealBuildTool; +using System; +using System.IO; + +public class HoudiniEngineRuntime : ModuleRules +{ + public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) + { + bPrecompile = true; + PCHUsage = PCHUsageMode.NoSharedPCHs; + PrivatePCHHeaderFile = "Private/HoudiniEngineRuntimePrivatePCH.h"; + + // Check if we are compiling for unsupported platforms. + if ( Target.Platform != UnrealTargetPlatform.Win64 && + Target.Platform != UnrealTargetPlatform.Mac && + Target.Platform != UnrealTargetPlatform.Linux && + Target.Platform != UnrealTargetPlatform.Switch ) + { + System.Console.WriteLine( string.Format( "Houdini Engine Runtime: Compiling for untested target platform. Please let us know how it goes!" ) ); + } + + + PublicIncludePaths.AddRange( + new string[] {} + ); + + PrivateIncludePaths.AddRange( + new string[] { } + ); + + // Add common dependencies. + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "RenderCore", + "InputCore", + "RHI", + "Foliage", + "Landscape" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Landscape", + "PropertyPath", + "PhysicsCore" + } + ); + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "Kismet", + } + ); + } + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp index 42532d617..2f7587ca8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp @@ -1,200 +1,200 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAsset.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Misc/Paths.h" -#include "HAL/UnrealMemory.h" - -UHoudiniAsset::UHoudiniAsset(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , AssetFileName(TEXT("")) - , AssetBytesCount(0) - , bAssetLimitedCommercial(false) - , bAssetNonCommercial(false) - , bAssetExpanded(false) -{} - -void -UHoudiniAsset::CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName) -{ - AssetFileName = InFileName; - - // Calculate buffer size. - AssetBytesCount = BufferEnd - BufferStart; - - if (AssetBytesCount) - { - // Allocate buffer to store the raw data. - AssetBytes.SetNumUninitialized(AssetBytesCount); - // Copy data into the newly allocated buffer. - FMemory::Memcpy(AssetBytes.GetData(), BufferStart, AssetBytesCount); - } - - FString FileExtension = FPaths::GetExtension(InFileName); - - // Expanded HDAs are imported via a "houdini.hdalibrary" file inside the .hda directory - // Identify them first, then update the file path to point to the .hda dir - if (FileExtension.Equals(TEXT("hdalibrary"), ESearchCase::IgnoreCase)) - { - bAssetExpanded = true; - - // Use the parent ".hda" directory as the filename - AssetFileName = FPaths::GetPath(AssetFileName); - FileExtension = FPaths::GetExtension(AssetFileName); - } - - if (FileExtension.Equals(TEXT("hdalc"), ESearchCase::IgnoreCase) - || FileExtension.Equals(TEXT("otlc"), ESearchCase::IgnoreCase)) - { - // Check if the HDA is limited (Indie) ... - bAssetLimitedCommercial = true; - } - else if (FileExtension.Equals(TEXT("hdanc"), ESearchCase::IgnoreCase) - || FileExtension.Equals(TEXT("otlnc"), ESearchCase::IgnoreCase)) - { - // ... or non commercial (Apprentice) - bAssetNonCommercial = true; - } -} - -void -UHoudiniAsset::FinishDestroy() -{ - // Release buffer which was used to store raw OTL data. - AssetBytes.Empty(); - Super::FinishDestroy(); -} - -const uint8 * -UHoudiniAsset::GetAssetBytes() const -{ - return AssetBytes.GetData(); -} - -const FString & -UHoudiniAsset::GetAssetFileName() const -{ - return AssetFileName; -} - -uint32 -UHoudiniAsset::GetAssetBytesCount() const -{ - return AssetBytesCount; -} - -void -UHoudiniAsset::Serialize(FArchive & Ar) -{ - // Serializes our UProperties - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Get the version - uint32 HoudiniAssetVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - - // Only version 1 assets needs manual serialization - if ( HoudiniAssetVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE - || HoudiniAssetVersion > VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION ) - return SerializeLegacy(Ar); -} - -void -UHoudiniAsset::SerializeLegacy(FArchive & Ar) -{ - uint32 FileFormatVersion; - Ar << FileFormatVersion; - - Ar << AssetBytesCount; - if (Ar.IsLoading()) - { - // Allocate sufficient space to read stored raw OTL data. - AssetBytes.SetNumUninitialized(AssetBytesCount); - } - - if (AssetBytesCount) - Ar.Serialize(AssetBytes.GetData(), AssetBytesCount); - - // Serialized flags. - union - { - struct - { - uint32 bLegacyPreviewHoudiniLogo : 1; - uint32 bLegacyAssetLimitedCommercial : 1; - uint32 bLegacyAssetNonCommercial : 1; - }; - uint32 HoudiniAssetFlagsPacked; - }; - Ar << HoudiniAssetFlagsPacked; - - bAssetNonCommercial = bLegacyAssetNonCommercial; - bAssetLimitedCommercial = bLegacyAssetLimitedCommercial; - - // Serialize asset file path. - Ar << AssetFileName; -} - -void -UHoudiniAsset::GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const -{ - // Filename - OutTags.Add(FAssetRegistryTag("FileName", AssetFileName, FAssetRegistryTag::TT_Alphabetical)); - - // Bytes - OutTags.Add(FAssetRegistryTag("Bytes", FString::FromInt(AssetBytesCount), FAssetRegistryTag::TT_Numerical)); - - // Indicate if the Asset is Full / Indie / NC - FString AssetType = TEXT("Full"); - if (bAssetLimitedCommercial) - AssetType = TEXT("Limited Commercial (LC)"); - else if (bAssetNonCommercial) - AssetType = TEXT("Non Commercial (NC)"); - - OutTags.Add(FAssetRegistryTag("Asset Type", AssetType, FAssetRegistryTag::TT_Alphabetical)); - - Super::GetAssetRegistryTags(OutTags); -} - -bool -UHoudiniAsset::IsAssetLimitedCommercial() const -{ - return bAssetLimitedCommercial; -} - -bool -UHoudiniAsset::IsAssetNonCommercial() const -{ - return bAssetNonCommercial; -} - -bool -UHoudiniAsset::IsExpandedHDA() const -{ - return bAssetExpanded; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAsset.h" +#include "HoudiniPluginSerializationVersion.h" + +#include "Misc/Paths.h" +#include "HAL/UnrealMemory.h" + +UHoudiniAsset::UHoudiniAsset(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , AssetFileName(TEXT("")) + , AssetBytesCount(0) + , bAssetLimitedCommercial(false) + , bAssetNonCommercial(false) + , bAssetExpanded(false) +{} + +void +UHoudiniAsset::CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName) +{ + AssetFileName = InFileName; + + // Calculate buffer size. + AssetBytesCount = BufferEnd - BufferStart; + + if (AssetBytesCount) + { + // Allocate buffer to store the raw data. + AssetBytes.SetNumUninitialized(AssetBytesCount); + // Copy data into the newly allocated buffer. + FMemory::Memcpy(AssetBytes.GetData(), BufferStart, AssetBytesCount); + } + + FString FileExtension = FPaths::GetExtension(InFileName); + + // Expanded HDAs are imported via a "houdini.hdalibrary" file inside the .hda directory + // Identify them first, then update the file path to point to the .hda dir + if (FileExtension.Equals(TEXT("hdalibrary"), ESearchCase::IgnoreCase)) + { + bAssetExpanded = true; + + // Use the parent ".hda" directory as the filename + AssetFileName = FPaths::GetPath(AssetFileName); + FileExtension = FPaths::GetExtension(AssetFileName); + } + + if (FileExtension.Equals(TEXT("hdalc"), ESearchCase::IgnoreCase) + || FileExtension.Equals(TEXT("otlc"), ESearchCase::IgnoreCase)) + { + // Check if the HDA is limited (Indie) ... + bAssetLimitedCommercial = true; + } + else if (FileExtension.Equals(TEXT("hdanc"), ESearchCase::IgnoreCase) + || FileExtension.Equals(TEXT("otlnc"), ESearchCase::IgnoreCase)) + { + // ... or non commercial (Apprentice) + bAssetNonCommercial = true; + } +} + +void +UHoudiniAsset::FinishDestroy() +{ + // Release buffer which was used to store raw OTL data. + AssetBytes.Empty(); + Super::FinishDestroy(); +} + +const uint8 * +UHoudiniAsset::GetAssetBytes() const +{ + return AssetBytes.GetData(); +} + +const FString & +UHoudiniAsset::GetAssetFileName() const +{ + return AssetFileName; +} + +uint32 +UHoudiniAsset::GetAssetBytesCount() const +{ + return AssetBytesCount; +} + +void +UHoudiniAsset::Serialize(FArchive & Ar) +{ + // Serializes our UProperties + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Get the version + uint32 HoudiniAssetVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + + // Only version 1 assets needs manual serialization + if ( HoudiniAssetVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE + || HoudiniAssetVersion > VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION ) + return SerializeLegacy(Ar); +} + +void +UHoudiniAsset::SerializeLegacy(FArchive & Ar) +{ + uint32 FileFormatVersion; + Ar << FileFormatVersion; + + Ar << AssetBytesCount; + if (Ar.IsLoading()) + { + // Allocate sufficient space to read stored raw OTL data. + AssetBytes.SetNumUninitialized(AssetBytesCount); + } + + if (AssetBytesCount) + Ar.Serialize(AssetBytes.GetData(), AssetBytesCount); + + // Serialized flags. + union + { + struct + { + uint32 bLegacyPreviewHoudiniLogo : 1; + uint32 bLegacyAssetLimitedCommercial : 1; + uint32 bLegacyAssetNonCommercial : 1; + }; + uint32 HoudiniAssetFlagsPacked; + }; + Ar << HoudiniAssetFlagsPacked; + + bAssetNonCommercial = bLegacyAssetNonCommercial; + bAssetLimitedCommercial = bLegacyAssetLimitedCommercial; + + // Serialize asset file path. + Ar << AssetFileName; +} + +void +UHoudiniAsset::GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const +{ + // Filename + OutTags.Add(FAssetRegistryTag("FileName", AssetFileName, FAssetRegistryTag::TT_Alphabetical)); + + // Bytes + OutTags.Add(FAssetRegistryTag("Bytes", FString::FromInt(AssetBytesCount), FAssetRegistryTag::TT_Numerical)); + + // Indicate if the Asset is Full / Indie / NC + FString AssetType = TEXT("Full"); + if (bAssetLimitedCommercial) + AssetType = TEXT("Limited Commercial (LC)"); + else if (bAssetNonCommercial) + AssetType = TEXT("Non Commercial (NC)"); + + OutTags.Add(FAssetRegistryTag("Asset Type", AssetType, FAssetRegistryTag::TT_Alphabetical)); + + Super::GetAssetRegistryTags(OutTags); +} + +bool +UHoudiniAsset::IsAssetLimitedCommercial() const +{ + return bAssetLimitedCommercial; +} + +bool +UHoudiniAsset::IsAssetNonCommercial() const +{ + return bAssetNonCommercial; +} + +bool +UHoudiniAsset::IsExpandedHDA() const +{ + return bAssetExpanded; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h index 508867722..676a3db95 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h @@ -1,104 +1,104 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/Object.h" -#include "HoudiniAsset.generated.h" - -class UAssetImportData; - -UCLASS(BlueprintType, EditInlineNew, config = Engine) -class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject -{ - GENERATED_UCLASS_BODY() - - public: - - // UOBject functions - virtual void FinishDestroy() override; - virtual void Serialize(FArchive & Ar) override; - virtual void GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const override; - - // Creates and initialize this asset from a given buffer / file. - void CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName); - - // Return buffer containing the raw Houdini OTL data. - const uint8* GetAssetBytes() const; - - // Return path of the corresponding OTL/HDA file. - const FString& GetAssetFileName() const; - - // Return the size in bytes of raw Houdini OTL data. - uint32 GetAssetBytesCount() const; - - // Return true if this asset is a limited commercial asset. - bool IsAssetLimitedCommercial() const; - - // Return true if this asset is a non commercial asset. - bool IsAssetNonCommercial() const; - - // Return true if this asset is an expanded HDA (HDA dir) - bool IsExpandedHDA() const; - - private: - // Used to load old (version1) versions of HoudiniAssets - void SerializeLegacy(FArchive & Ar); - - public: - - // Source filename of the OTL. - UPROPERTY() - FString AssetFileName; - -#if WITH_EDITORONLY_DATA - // Importing data and options used for this Houdini asset. - UPROPERTY(Category = ImportSettings, VisibleAnywhere, Instanced) - UAssetImportData * AssetImportData; -#endif - - private: - - // Buffer containing the raw HDA OTL data. - UPROPERTY() - TArray AssetBytes; - - // Size in bytes of the raw HDA data. - UPROPERTY() - uint32 AssetBytesCount; - - // Indicates if this is a limited commercial asset. - UPROPERTY() - bool bAssetLimitedCommercial; - - // Indicates if this is a non-commercial license asset. - UPROPERTY() - bool bAssetNonCommercial; - - // Indicates if this is an expanded HDA file - UPROPERTY() - bool bAssetExpanded; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/Object.h" +#include "HoudiniAsset.generated.h" + +class UAssetImportData; + +UCLASS(BlueprintType, EditInlineNew, config = Engine) +class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject +{ + GENERATED_UCLASS_BODY() + + public: + + // UOBject functions + virtual void FinishDestroy() override; + virtual void Serialize(FArchive & Ar) override; + virtual void GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const override; + + // Creates and initialize this asset from a given buffer / file. + void CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName); + + // Return buffer containing the raw Houdini OTL data. + const uint8* GetAssetBytes() const; + + // Return path of the corresponding OTL/HDA file. + const FString& GetAssetFileName() const; + + // Return the size in bytes of raw Houdini OTL data. + uint32 GetAssetBytesCount() const; + + // Return true if this asset is a limited commercial asset. + bool IsAssetLimitedCommercial() const; + + // Return true if this asset is a non commercial asset. + bool IsAssetNonCommercial() const; + + // Return true if this asset is an expanded HDA (HDA dir) + bool IsExpandedHDA() const; + + private: + // Used to load old (version1) versions of HoudiniAssets + void SerializeLegacy(FArchive & Ar); + + public: + + // Source filename of the OTL. + UPROPERTY() + FString AssetFileName; + +#if WITH_EDITORONLY_DATA + // Importing data and options used for this Houdini asset. + UPROPERTY(Category = ImportSettings, VisibleAnywhere, Instanced) + UAssetImportData * AssetImportData; +#endif + + private: + + // Buffer containing the raw HDA OTL data. + UPROPERTY() + TArray AssetBytes; + + // Size in bytes of the raw HDA data. + UPROPERTY() + uint32 AssetBytesCount; + + // Indicates if this is a limited commercial asset. + UPROPERTY() + bool bAssetLimitedCommercial; + + // Indicates if this is a non-commercial license asset. + UPROPERTY() + bool bAssetNonCommercial; + + // Indicates if this is an expanded HDA file + UPROPERTY() + bool bAssetExpanded; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp index aece71f01..5ad5af6d2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp @@ -1,145 +1,145 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniPDGAssetLink.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -AHoudiniAssetActor::AHoudiniAssetActor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - SetCanBeDamaged(false); - //PrimaryActorTick.bCanEverTick = true; - //PrimaryActorTick.bStartWithTickEnabled = true; - - // Create Houdini component and attach it to a root component. - HoudiniAssetComponent = - ObjectInitializer.CreateDefaultSubobject< UHoudiniAssetComponent >(this, TEXT("HoudiniAssetComponent")); - - //HoudiniAssetComponent->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); - - RootComponent = HoudiniAssetComponent; -} - -UHoudiniAssetComponent * -AHoudiniAssetActor::GetHoudiniAssetComponent() const -{ - return HoudiniAssetComponent; -} - -/* -#if WITH_EDITOR -bool -AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) -{ - if (!ActorPropString) - return false; - - // Locate actor which is being copied in clipboard string. - AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor(this, *ActorPropString); - - // We no longer need clipboard string and can empty it. This seems to avoid occasional crash bug in UE4 which - // happens on copy / paste. - ActorPropString->Empty(); - - if (!CopiedActor || CopiedActor->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import from copy: Duplicated actor not found")); - return false; - } - - // Get Houdini component of an actor which is being copied. - UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; - if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill()) - return false; - - HoudiniAssetComponent->OnComponentClipboardCopy(CopiedActorHoudiniAssetComponent); - - // If actor is copied through moving, we need to copy main transform. - const FTransform & ComponentWorldTransform = CopiedActorHoudiniAssetComponent->GetComponentTransform(); - HoudiniAssetComponent->SetWorldLocationAndRotation( - ComponentWorldTransform.GetLocation(), - ComponentWorldTransform.GetRotation()); - - // We also need to copy actor label. - const FString & CopiedActorLabel = CopiedActor->GetActorLabel(); - FActorLabelUtilities::SetActorLabelUnique(this, CopiedActorLabel); - - return true; -} -#endif -*/ -#if WITH_EDITOR -bool -AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) const -{ - Super::GetReferencedContentObjects(Objects); - - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) - { - UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); - if (HoudiniAsset && !HoudiniAsset->IsPendingKill()) - Objects.AddUnique(HoudiniAsset); - } - - return true; -} -#endif - -#if WITH_EDITOR -void -AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - // Some property changes need to be forwarded to the component (ie Transform) - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return; - - FProperty* Property = PropertyChangedEvent.MemberProperty; - if (!Property) - return; - - FName PropertyName = Property->GetFName(); - if (PropertyName == HoudiniAssetComponent->GetRelativeLocationPropertyName() - || PropertyName == HoudiniAssetComponent->GetRelativeRotationPropertyName() - || PropertyName == HoudiniAssetComponent->GetRelativeScale3DPropertyName()) - { - HoudiniAssetComponent->SetHasComponentTransformChanged(true); - } -} -#endif - - -bool -AHoudiniAssetActor::IsUsedForPreview() const -{ - return HasAnyFlags(RF_Transient); -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniPDGAssetLink.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +AHoudiniAssetActor::AHoudiniAssetActor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + SetCanBeDamaged(false); + //PrimaryActorTick.bCanEverTick = true; + //PrimaryActorTick.bStartWithTickEnabled = true; + + // Create Houdini component and attach it to a root component. + HoudiniAssetComponent = + ObjectInitializer.CreateDefaultSubobject< UHoudiniAssetComponent >(this, TEXT("HoudiniAssetComponent")); + + //HoudiniAssetComponent->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); + + RootComponent = HoudiniAssetComponent; +} + +UHoudiniAssetComponent * +AHoudiniAssetActor::GetHoudiniAssetComponent() const +{ + return HoudiniAssetComponent; +} + +/* +#if WITH_EDITOR +bool +AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) +{ + if (!ActorPropString) + return false; + + // Locate actor which is being copied in clipboard string. + AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor(this, *ActorPropString); + + // We no longer need clipboard string and can empty it. This seems to avoid occasional crash bug in UE4 which + // happens on copy / paste. + ActorPropString->Empty(); + + if (!CopiedActor || CopiedActor->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import from copy: Duplicated actor not found")); + return false; + } + + // Get Houdini component of an actor which is being copied. + UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; + if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill()) + return false; + + HoudiniAssetComponent->OnComponentClipboardCopy(CopiedActorHoudiniAssetComponent); + + // If actor is copied through moving, we need to copy main transform. + const FTransform & ComponentWorldTransform = CopiedActorHoudiniAssetComponent->GetComponentTransform(); + HoudiniAssetComponent->SetWorldLocationAndRotation( + ComponentWorldTransform.GetLocation(), + ComponentWorldTransform.GetRotation()); + + // We also need to copy actor label. + const FString & CopiedActorLabel = CopiedActor->GetActorLabel(); + FActorLabelUtilities::SetActorLabelUnique(this, CopiedActorLabel); + + return true; +} +#endif +*/ +#if WITH_EDITOR +bool +AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) const +{ + Super::GetReferencedContentObjects(Objects); + + if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + { + UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); + if (HoudiniAsset && !HoudiniAsset->IsPendingKill()) + Objects.AddUnique(HoudiniAsset); + } + + return true; +} +#endif + +#if WITH_EDITOR +void +AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + // Some property changes need to be forwarded to the component (ie Transform) + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return; + + FProperty* Property = PropertyChangedEvent.MemberProperty; + if (!Property) + return; + + FName PropertyName = Property->GetFName(); + if (PropertyName == HoudiniAssetComponent->GetRelativeLocationPropertyName() + || PropertyName == HoudiniAssetComponent->GetRelativeRotationPropertyName() + || PropertyName == HoudiniAssetComponent->GetRelativeScale3DPropertyName()) + { + HoudiniAssetComponent->SetHasComponentTransformChanged(true); + } +} +#endif + + +bool +AHoudiniAssetActor::IsUsedForPreview() const +{ + return HasAnyFlags(RF_Transient); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h index 6736c8fd2..39079d617 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h @@ -1,77 +1,77 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" - -#include "UObject/ObjectMacros.h" -#include "Components/ActorComponent.h" -#include "GameFramework/Actor.h" - -#include "HoudiniAssetActor.generated.h" - -class UHoudiniPDGAssetLink; - -UCLASS(hidecategories = (Input), ConversionRoot, meta = (ChildCanTick), Blueprintable) -class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor -{ - GENERATED_UCLASS_BODY() - - // Pointer to the root HoudiniAssetComponent - UPROPERTY(Category = HoudiniAssetActor, VisibleAnywhere, BlueprintReadOnly, meta = (ExposeFunctionCategories = "Mesh,Rendering,Physics,Components|HoudiniEngine")/*, AllowPrivateAccess = "true"*/) - UHoudiniAssetComponent * HoudiniAssetComponent; - -public: - - // Returns the actor's houdini component. - UHoudiniAssetComponent* GetHoudiniAssetComponent() const; - - bool IsUsedForPreview() const; - - // Gets the Houdini PDG asset link associated with this actor, if it has one. - UHoudiniPDGAssetLink* GetPDGAssetLink() const { return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; } - -#if WITH_EDITOR - - // Called after a property has been changed - // Used to forward property changes to the HAC - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; - - // Used by the "Sync to Content Browser" right-click menu option in the editor. - virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; - -/* -public: - - // Called before editor paste, true allow import - virtual bool ShouldImport(FString * ActorPropString, bool IsMovingLevel) override; - - // Used by the "Sync to Content Browser" right-click menu option in the editor. - virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; -*/ -#endif -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" + +#include "UObject/ObjectMacros.h" +#include "Components/ActorComponent.h" +#include "GameFramework/Actor.h" + +#include "HoudiniAssetActor.generated.h" + +class UHoudiniPDGAssetLink; + +UCLASS(hidecategories = (Input), ConversionRoot, meta = (ChildCanTick), Blueprintable) +class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor +{ + GENERATED_UCLASS_BODY() + + // Pointer to the root HoudiniAssetComponent + UPROPERTY(Category = HoudiniAssetActor, VisibleAnywhere, BlueprintReadOnly, meta = (ExposeFunctionCategories = "Mesh,Rendering,Physics,Components|HoudiniEngine")/*, AllowPrivateAccess = "true"*/) + UHoudiniAssetComponent * HoudiniAssetComponent; + +public: + + // Returns the actor's houdini component. + UHoudiniAssetComponent* GetHoudiniAssetComponent() const; + + bool IsUsedForPreview() const; + + // Gets the Houdini PDG asset link associated with this actor, if it has one. + UHoudiniPDGAssetLink* GetPDGAssetLink() const { return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; } + +#if WITH_EDITOR + + // Called after a property has been changed + // Used to forward property changes to the HAC + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; + + // Used by the "Sync to Content Browser" right-click menu option in the editor. + virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; + +/* +public: + + // Called before editor paste, true allow import + virtual bool ShouldImport(FString * ActorPropString, bool IsMovingLevel) override; + + // Used by the "Sync to Content Browser" right-click menu option in the editor. + virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; +*/ +#endif +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp index 44ed741b4..113b0f8ef 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp @@ -1,2369 +1,2369 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniAssetBlueprintComponent.h" - -#include "HoudiniEngineCopyPropertiesInterface.h" -#include "HoudiniOutput.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Engine/SCS_Node.h" -#include "Engine/SimpleConstructionScript.h" -#include "UObject/Object.h" -#include "Logging/LogMacros.h" - -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniInput.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Kismet2/BlueprintEditorUtils.h" - #include "Kismet2/KismetEditorUtilities.h" - #include "Toolkits/AssetEditorManager.h" - #include "Kismet2/ComponentEditorUtils.h" - #include "ComponentAssetBroker.h" -#endif - -HOUDINI_BP_DEFINE_LOG_CATEGORY(); - -UHoudiniAssetBlueprintComponent::UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - -#if WITH_EDITOR - if (IsTemplate()) - { - // CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); - //GEditor->GetEditorSubsystem()->OnAssetEditorRequestClose().AddUObject( this, &UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent ); - } -#endif - - bForceNeedUpdate = false; - bHoudiniAssetChanged = false; - bIsInBlueprintEditor = false; - bCanDeleteHoudiniNodes = false; - - // AssetState will be updated by changes to the HoudiniAsset - // or parameter changes on the Component template. - AssetState = EHoudiniAssetState::None; - bHasRegisteredComponentTemplate = false; - bHasBeenLoaded = false; - bUpdatedFromTemplate = false; - - // Disable proxy mesh by default (unsupported for now) - bOverrideGlobalProxyStaticMeshSettings = true; - bEnableProxyStaticMeshOverride = false; - bEnableProxyStaticMeshRefinementByTimerOverride = false; - bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = false; - bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = false; - StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; - - // Set default mobility to Movable - Mobility = EComponentMobility::Movable; -} - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() -{ - // We need to propagate changes made here back to the corresponding component in - // the Blueprint Generated Class ("_GEN_VARIABLE"). The reason being that - // the Blueprint editor works directly with the GEN_VARIABLE component (all - // PostEditChange() calls, Details Customizations, etc will receive the GEN_VARIABLE instance) BUT - // when the Editor runs the construction script it uses a different component instance, so all changes - // made to that instance won't write back to the Blueprint definition. - // To Summarize: - // Be sure to sync the Parameters array (and any other relevant properties) back - // to the corresponding component on the Blueprint Generated class otherwise these wont be - // accessible in the Details Customization callbacks. - - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] To Component: %s"), *(CachedTemplateComponent->GetPathName())); - - // This should never be called by component templates. - check(!IsTemplate()); - - if (!CachedTemplateComponent.IsValid()) - return; - - USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; - check(SCS); - - /* - USCS_Node* SCSNodeForInstance = FindSCSNodeForInstanceComponent(SCS, this); - if (SCSNodeForInstance) - { - - } - else - { - - } - */ - - //// If we don't have an SCS node for this preview instance, we need to create one, regardless - //// of whether output updates are required. - //if (!CachedTemplateComponent->bOutputsRequireUpdate && SCSNodeForInstance != nullptr) - // return; - - // TODO: If the blueprint editor is NOT open, then we shouldn't attempting - // to copy state back to the BPGC at all! - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - check(BlueprintEditor); - - TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); - check(SCSEditor); - - USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - // check(SCSHACNode); - - // This is the actor instance that is being used for component editing. - AActor* PreviewActor = GetPreviewActor(); - check(PreviewActor); - - // NOTE: Inputs are only from component templates to instances, not the other way around ... I think. - - // ----------------------------------------------------- - // Copy outputs to component template - // ----------------------------------------------------- - - // Populate / update the outputs for the template from the preview / instance. - // TODO: Wrap the Blueprint manipulation in a transaction - TArray& TemplateOutputs = CachedTemplateComponent->Outputs; - TSet StaleTemplateOutputs(TemplateOutputs); - - TemplateOutputs.SetNum(Outputs.Num()); - CachedOutputNodes.Empty(); - - for (int i = 0; i < Outputs.Num(); i++) - { - // Find a output on the template that corresponds to this output from the instance. - UHoudiniOutput* TemplateOutput = nullptr; - UHoudiniOutput* InstanceOutput = nullptr; - InstanceOutput = Outputs[i]; - - check(InstanceOutput) - // Ensure that instance outputs won't delete houdini content. - // Houdini content should only be allowed to be deleted from - // the component template. - InstanceOutput->SetCanDeleteHoudiniNodes(false); - - TemplateOutput = TemplateOutputs[i]; - - if (TemplateOutput) - { - check(TemplateOutput->GetOuter() == CachedTemplateComponent.Get()); - StaleTemplateOutputs.Remove(TemplateOutput); - } - - - if (TemplateOutput) - { - // Copy properties from the current instance component while preserving output objects - // and instanced outputs. - TemplateOutput->CopyPropertiesFrom(InstanceOutput, true); - } - else - { - // NOTE: If the template output is NULL it means that the HDA spawned a new component / output in the transient world - // and the new output object needs to be copied back to the BPGC. - - // Corresponding template output could not be found. Create one by duplicating the instance output. - TemplateOutput = InstanceOutput->DuplicateAndCopyProperties(CachedTemplateComponent.Get(), FName(InstanceOutput->GetName())); - // Treat these the same one would components created by CreateDefaultSubObject. - // NOTE: CreateDefaultSubobject performs lots of checks, and unfortunately we can't use it directly (it is - // only allowed to be used in a constructor). Not sure whether we need to either. For now, we just set the - // object flags to be similar to components created by CreateDefaultSubobject. - TemplateOutput->SetFlags(RF_Public|RF_ArchetypeObject|RF_DefaultSubObject); - TemplateOutputs[i] = TemplateOutput; - } - - check(TemplateOutput); - TemplateOutput->SetCanDeleteHoudiniNodes(false); - - // Keep track of potential stale output objects on the template component, for this output. - TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); - TArray StaleTemplateObjects; - TemplateOutputObjects.GetKeys(StaleTemplateObjects); - - for (auto& Entry : InstanceOutput->GetOutputObjects()) - { - - // Prepare the FHoudiniOutputObject for the template component - const FHoudiniOutputObject& InstanceObj = Entry.Value; - FHoudiniOutputObject TemplateObj; - - // Any output present in the Instance Outputs should be - // transferred to the template. - // Remove this output object from stale outputs list. - StaleTemplateObjects.Remove(Entry.Key); - - if (TemplateOutputObjects.Contains(Entry.Key)) - { - // Reuse the existing template object - TemplateObj = TemplateOutputObjects.FindChecked(Entry.Key); - } - else - { - // Create a new template output object object by duplicating the instance object. - // Keep the output object, but clear the output component since we have to - // create a new component template. - TemplateObj = InstanceObj; - TemplateObj.ProxyComponent = nullptr; - TemplateObj.OutputComponent = nullptr; - TemplateObj.ProxyObject = nullptr; - } - - USceneComponent* ComponentInstance = Cast(InstanceObj.OutputComponent); - USceneComponent* ComponentTemplate = Cast(TemplateObj.OutputComponent); - UObject* OutputObject = InstanceObj.OutputObject; - - if (ComponentInstance) - { - // The translation process has either constructed new components, or it is - // reusing existing components, or changed an output (or all or none of the aforementioned). - // Carefully inspect the SCS graph to determine whether there is a corresponding - // (and compatible) node for this output. If not, create a new node and remove unusable node, if any. - - USCS_Node* ComponentNode = nullptr; - { - // Check whether the current OutputComponent being referenced by the template is still valid. - // Even if it was removed in the editor, it doesn't have any associated destroyed / pendingkill state. - // Instead we're going to check for validity by finding an SCS node with a matching template component. - bool bValidComponentTemplate = (ComponentTemplate != nullptr); - if (ComponentTemplate) - { - - ComponentNode = FindSCSNodeForTemplateComponentInClassHierarchy(ComponentTemplate); - bValidComponentTemplate = bValidComponentTemplate && (ComponentNode != nullptr); - } - - if (!bValidComponentTemplate) - { - // Either this component was removed from the editor or it doesn't exist yet. - // Ensure the references are cleared - TemplateObj.OutputComponent = nullptr; - ComponentTemplate = nullptr; - } - } - - // NOTE: we can't use the component instance name directly due to the Blueprint compiler performing an internal checking - // using FComponentEditorUtils::IsValidVariableNameString(), which will return false if the name looks like an autogenerated name... - //FString ComponentName = ComponentInstance->GetName(); - FString ComponentName = FBlueprintEditorUtils::GetClassNameWithoutSuffix(ComponentInstance->GetClass()); - FName ComponentFName = FName(ComponentName); - - - const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )( - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | - EditorUtilities::ECopyOptions::CallPostEditChangeProperty | - EditorUtilities::ECopyOptions::CallPostEditMove); - - if (IsValid(ComponentNode)) - { - // Check if we have an existing and compatible SCS node containing a USceneComponent as a template component. - bool bComponentNodeIsValid = true; - - ComponentTemplate = Cast(ComponentNode->ComponentTemplate); - - bComponentNodeIsValid = bComponentNodeIsValid && ComponentInstance->GetClass() == ComponentNode->ComponentClass; - bComponentNodeIsValid = bComponentNodeIsValid && ComponentTemplate != nullptr; - // TODO: Do we need to perform any other compatibility checks? - - if (!bComponentNodeIsValid) - { - // Component template is not compatible. We can't reuse it. - - SCSHACNode->RemoveChildNode(ComponentNode); - SCS->RemoveNode(ComponentNode); - ComponentNode = nullptr; - ComponentTemplate = nullptr; - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - } - - if (ComponentNode) - { - // We found a reusable SCS node. Just copy the component instance - // properties over to the existing template. - check(ComponentNode->ComponentTemplate); - - // UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - // //Params.bReplaceObjectClassReferences = false; - // Params.bDoDelta = false; // Perform a deep copy - // Params.bClearReferences = false; - // UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - - FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - - ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; - } - else - { - // We couldn't find a reusable SCS node. - // Duplicate the instance component and create a new corresponding SCS node - ComponentNode = SCS->CreateNode(ComponentInstance->GetClass(), ComponentFName); - - UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // We need a deep copy of parameters here so the CDO values get copied as well - UEditorEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - // FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); - - // { - // UInstancedStaticMeshComponent* Component = Cast(ComponentNode->ComponentTemplate); - // if (Component) - // { - // } - // } - - // NOTE: The EComponentCreationMethod here is currently set to be the same as a component that was - // created manually in the editor. - ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; - - // Add this node to the SCS root set. - - // Attach the new node the HAC SCS node - // NOTE: This will add the node to the SCS->AllNodes list too but it won't update - // the nodename map. We can't forcibly update the Node/Name map either since the - // relevant functions have not been exported. - SCSHACNode->AddChildNode(ComponentNode); - - // Set the output component. - TemplateObj.OutputComponent = ComponentNode->ComponentTemplate; - - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - - // Cache the mapping between the output and the SCS node. - check(ComponentNode); - CachedOutputNodes.Add(Entry.Key, ComponentNode->VariableGuid); - } // if (ComponentInstance) - /* - else if (InstanceObj.OutputObject) - { - - } - */ - - // Add the updated output object to the template output - TemplateOutputObjects.Add(Entry.Key, TemplateObj); - } - - // Cleanup stale objects for this template output. - for (const auto& StaleId : StaleTemplateObjects) - { - FHoudiniOutputObject& OutputObj = TemplateOutputObjects.FindChecked(StaleId); - - // Ensure the component template is no longer referencing this output. - TemplateOutputObjects.Remove(StaleId); - - USceneComponent* TemplateComponent = Cast(OutputObj.OutputComponent); - - if (TemplateComponent) - { - USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(TemplateComponent); - if (StaleNode) - { - - SCS->RemoveNode(StaleNode, false); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - /* - else - { - - } - */ - } - /* - else - { - - } - */ - } - } //for (int i = 0; i < Outputs.Num(); i++) - - // Clean up stale outputs on the component template. - for (UHoudiniOutput* StaleOutput : StaleTemplateOutputs) - { - if (!StaleOutput) - continue; - - // Remove any components contained in this output from the SCS graph - for (auto& Entry : StaleOutput->GetOutputObjects()) - { - FHoudiniOutputObject& StaleObject = Entry.Value; - USceneComponent* OutputComponent = Cast(StaleObject.OutputComponent); - - if (OutputComponent) - { - - USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(OutputComponent); - if (StaleNode) - { - - SCS->RemoveNode(StaleNode, false); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - } - } - - TemplateOutputs.Remove(StaleOutput); - //StaleOutput->ConditionalBeginDestroy(); - } - - SCS->ValidateSceneRootNodes(); - - // Copy parameters from this component to the template component. - // NOTE: We need to do this since the preview component will be cooking the HDA and get populated with - // all the parameters. This data needs to be sent back to the component template. - UClass* ComponentClass = CachedTemplateComponent->GetClass(); - UHoudiniAssetBlueprintComponent* DefaultObj = Cast(ComponentClass->GetDefaultObject()); - bool bBPStructureModified = false; - CachedTemplateComponent->CopyDetailsFromComponent( - this, - true, - true, - true, - false, - true, - bBPStructureModified, - /* SetFlags */ CachedTemplateComponent->GetMaskedFlags(RF_PropagateToSubObjects)); - - if (bBPStructureModified) - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - - // Copy the cached output nodes back to the template so that - // reconstructed actors can correctly update output objects - // with newly constructed components during ApplyComponentInstanceData() calls. - CachedTemplateComponent->CachedOutputNodes = CachedOutputNodes; - - CachedTemplateComponent->MarkPackageDirty(); - PostEditChange(); - - CachedTemplateComponent->AssetId = AssetId; - CachedTemplateComponent->HapiGUID = HapiGUID; - CachedTemplateComponent->AssetCookCount = AssetCookCount; - CachedTemplateComponent->AssetStateResult = AssetStateResult; - CachedTemplateComponent->bLastCookSuccess = bLastCookSuccess; - -#if WITH_EDITOR - // TODO: Do we need to handle this right now or can we wait for the next Houdini Engine manager tick to process it? - if (CachedTemplateComponent->NeedBlueprintStructureUpdate()) - { - // We are about to recompile the blueprint. This will reconstruct the preview actor so we need to ensure - // that the old actor won't release the houdini nodes. - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); - SetCanDeleteHoudiniNodes(false); - } - /*else if (CachedTemplateComponent->NeedBlueprintUpdate()) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(CachedTemplateComponent.Get()); - }*/ -#endif -} -#endif - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties) -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] From Component: %s"), *(FromComponent->GetPathName())); - - // This should never be called by component templates. - check(!IsTemplate()); - - // Make sure all TransientDuplicate properties from the Template Component needed by this transient component - // gets copied. - - ComponentGUID = FromComponent->ComponentGUID; - - /* - { - const TArray Children = GetAttachChildren(); - for (USceneComponent* Child : Children) - { - if (!Child) - continue; - } - } - */ - - // AssetState = FromComponent->PreviewAssetState; - - // This state should not be shared between template / instance components. - //bFullyLoaded = FromComponent->bFullyLoaded; - - bNoProxyMeshNextCookRequested = FromComponent->bNoProxyMeshNextCookRequested; - - // Reconstruct outputs and update them to point to component instances as opposed to templates. - UObject* TemplateOuter = CachedTemplateComponent->GetOuter(); - - USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; - check(SCS); - - // NOTE: We can find the SCS node for the HoudiniAssetComponent from either the template component or the instance (editor preview) component. - USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - check(SCSHACNode); - - // ----------------------------------------------------- - // Copy outputs to component template - // ----------------------------------------------------- - - TArray& TemplateOutputs = CachedTemplateComponent->Outputs; - - TSet StaleInstanceOutputs(Outputs); - - Outputs.SetNum(TemplateOutputs.Num()); - - for (int i = 0; i < TemplateOutputs.Num(); i++) - { - UHoudiniOutput* TemplateOutput = TemplateOutputs[i]; - if (!IsValid(TemplateOutput)) - continue; - - UHoudiniOutput* InstanceOutput = Outputs[i]; - if (!(InstanceOutput->GetOuter() == this)) - InstanceOutput = nullptr; - - if (InstanceOutput) - { - StaleInstanceOutputs.Remove(InstanceOutput); - } - - if (InstanceOutput) - { - // Copy properties from the current instance component while preserving output objects - // and instanced outputs. - InstanceOutput->CopyPropertiesFrom(TemplateOutput, true); - } - else - { - InstanceOutput = TemplateOutput->DuplicateAndCopyProperties(this, FName(TemplateOutput->GetName())); - InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); - } - - InstanceOutput->SetCanDeleteHoudiniNodes(false); - Outputs[i] = InstanceOutput; - - TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); - TMap& InstanceOutputObjects = InstanceOutput->GetOutputObjects(); - TArray StaleOutputObjects; - InstanceOutputObjects.GetKeys(StaleOutputObjects); - - for (auto& Entry : TemplateOutputObjects) - { - const FHoudiniOutputObject& TemplateObj = Entry.Value; - FHoudiniOutputObject InstanceObj = TemplateObj; - - if (!InstanceOutputObjects.Contains(Entry.Key)) - continue; - - StaleOutputObjects.Remove(Entry.Key); - InstanceObj = InstanceOutputObjects.FindChecked(Entry.Key); - - } // for (auto& Entry : TemplateOutputObjects) - - // Cleanup stale output objects for this output. - for (const auto& StaleId : StaleOutputObjects) - { - //TemplateOutput - //check(TemplateOutputs); - - FHoudiniOutputObject& OutputObj = InstanceOutputObjects.FindChecked(StaleId); - - InstanceOutputObjects.Remove(StaleId); - if (OutputObj.OutputComponent) - { - //OutputObj.OutputComponent->ConditionalBeginDestroy(); - OutputObj.OutputComponent = nullptr; - } - } - } // for (int i = 0; i < TemplateOutputs.Num(); i++) - - // Cleanup any stale outputs found on the component instance. - for (UHoudiniOutput* StaleOutput : StaleInstanceOutputs) - { - if (!StaleOutput) - continue; - - if (!(StaleOutput->GetOuter() == this)) - continue; - - // We don't want to clear stale outputs on components instances. Only on template components. - StaleOutput->SetCanDeleteHoudiniNodes(false); - } - - // Copy parameters from the component template to the instance. - bool bBlueprintStructureChanged = false; - CopyDetailsFromComponent(FromComponent, - false, - bClearFromInputs, - bClearToInputs, - false, - true, - bBlueprintStructureChanged, - /*SetFlags*/ RF_Public, - /*ClearFlags*/ RF_DefaultSubObject|RF_ArchetypeObject); -} -#endif - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent( - UHoudiniAssetBlueprintComponent* FromComponent, - const bool bCreateSCSNodes, - const bool bClearChangedToInputs, - const bool bClearChangedFromInputs, - const bool bInCanDeleteHoudiniNodes, - const bool bCopyInputObjectComponentProperties, - bool &bOutBlueprintStructureChanged, - EObjectFlags SetFlags, - EObjectFlags ClearFlags) -{ - check(FromComponent); - - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] FromComponent: %s"), *(FromComponent->GetPathName())); - - /* - if (!FromComponent->HoudiniAsset) - { - return; - } - */ - - // TODO: Try to reuse objects here when we're able. - //// Copy UHoudiniOutput state from instance to template - //UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - ////Params.bReplaceObjectClassReferences = false; - ////Params.bClearReferences = false; - //Params.bDoDelta = true; - //UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - - // Record input remapping that will need to take place when duplicating parameters. - TMap InputMapping; - - // ----------------------------------------------------- - // Copy inputs - // ----------------------------------------------------- - - // TODO: Add support for input components - { - TArray& FromInputs = FromComponent->Inputs; - TSet StaleInputs(Inputs); - USimpleConstructionScript* SCS = GetSCS(); - USCS_Node* SCSHACNode = nullptr; - - if (bCreateSCSNodes) - { - SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - } - - Inputs.SetNum(FromInputs.Num()); - for (int i = 0; i < FromInputs.Num(); i++) - { - UHoudiniInput* FromInput = nullptr; - UHoudiniInput* ToInput = nullptr; - FromInput = FromInputs[i]; - - check(FromInput); - - ToInput = Inputs[i]; - - if (ToInput) - { - // Check whether the instance and template input objects are compatible. - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - - if (!bIsValid) - { - ToInput = nullptr; - } - } - - // TODO: Process stale input objects - - // NOTE: The CopyStateFrom() / DuplicateAndCopyState() will copy/duplicate/cleanup internal inputs to - // ensure that there aren't any shared instances between the ToInput/FromInput. - if (ToInput) - { - // We have a compatible input that we can reuse. - StaleInputs.Remove(ToInput); - ToInput->CopyStateFrom(FromInput, true, bInCanDeleteHoudiniNodes); - } - else - { - - // We don't have an existing / compatible input. Create a new one. - ToInput = FromInput->DuplicateAndCopyState(this, bInCanDeleteHoudiniNodes); - if (SetFlags != RF_NoFlags) - ToInput->SetFlags(SetFlags); - if (ClearFlags != RF_NoFlags) - ToInput->ClearFlags( ClearFlags ); - } - - check(ToInput); - - - UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, bCopyInputObjectComponentProperties, bCreateSCSNodes, SCSHACNode, &bOutBlueprintStructureChanged); - - Inputs[i] = ToInput; - InputMapping.Add(FromInput, ToInput); - - if (bClearChangedToInputs) - { - // Clear the changed flags on the FromInput so that it doesn't trigger - // another update. The ToInput will now be carrying to changed/update flags. - ToInput->MarkChanged(false); - ToInput->MarkAllInputObjectsChanged(false); - } - - if (bClearChangedFromInputs) - { - // Clear the changed flags on the FromInput so that it doesn't trigger - // another update. The ToInput will now be carrying to changed/update flags. - FromInput->MarkChanged(false); - FromInput->MarkAllInputObjectsChanged(false); - } - } - - // Cleanup any stale inputs from this component. - // NOTE: We would typically only have stale inputs when copying state from - // the component instance to the component template. Garbage collection - // eventually picks up the input objects and removes the content - // but until such time we are stuck with those nodes as inputs in the Houdini session - // so we get rid of those nodes immediately here to avoid some user confusion. - for (UHoudiniInput* StaleInput : StaleInputs) - { - if (!IsValid(StaleInput)) - continue; - - check(StaleInput->GetOuter() == this); - - if (StaleInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - StaleInput->ConditionalBeginDestroy(); - } - } - - - // ----------------------------------------------------- - // Copy parameters (and optionally remap inputs). - // ----------------------------------------------------- - TMap ParameterMapping; - - TArray& FromParameters = FromComponent->Parameters; - Parameters.SetNum(FromParameters.Num()); - - for (int i = 0; i < FromParameters.Num(); i++) - { - UHoudiniParameter* FromParameter = nullptr; - UHoudiniParameter* ToParameter = nullptr; - - FromParameter = FromParameters[i]; - - check(FromParameter); - - if (Parameters.IsValidIndex(i)) - { - ToParameter = Parameters[i]; - } - - if (ToParameter) - { - bool bIsValid = true; - // Check whether To/From parameters are compatible - bIsValid = bIsValid && ToParameter->Matches(*FromParameter); - bIsValid = bIsValid && ToParameter->GetOuter() == this; - - if (!bIsValid) - ToParameter = nullptr; - } - - if (ToParameter) - { - // Parameter already exists. Simply sync the state. - ToParameter->CopyStateFrom(FromParameter, true, ClearFlags, SetFlags); - } - else - { - // TODO: Check whether parameters are the same to avoid recreating them. - ToParameter = FromParameter->DuplicateAndCopyState(this, ClearFlags, SetFlags); - Parameters[i] = ToParameter; - } - - check(ToParameter); - ParameterMapping.Add(FromParameter, ToParameter); - - if (bClearChangedFromInputs) - { - // We clear the Changed flag on the FromParameter (most likely on the component template) - // since the template parameter state has now been transfered to the preview component and - // will resume processing from there. - FromParameter->MarkChanged(false); - } - } - - // Apply remappings on the new parameters - for (UHoudiniParameter* ToParameter : Parameters) - { - ToParameter->RemapParameters(ParameterMapping); - ToParameter->RemapInputs(InputMapping); - } - - FProperty* ParametersProperty = GetClass()->FindPropertyByName(TEXT("Parameters")); - FPropertyChangedEvent Evt(ParametersProperty); - PostEditChangeProperty(Evt); - - bEnableCooking = FromComponent->bEnableCooking; - bRecookRequested = FromComponent->bRecookRequested; - bRebuildRequested = FromComponent->bRebuildRequested; -} - -void -UHoudiniAssetBlueprintComponent::UpdateInputObjectComponentReferences( - USimpleConstructionScript* SCS, - UHoudiniInput* FromInput, - UHoudiniInput* ToInput, - const bool bCopyInputObjectProperties, - const bool bCreateMissingSCSNodes, - USCS_Node* SCSHACParent, - bool* bOutSCSNodeCreated) -{ - TArray ToInputObjects; - TArray FromInputObjects; - TArray StaleInputObjects; - - ToInput->GetAllHoudiniInputSplineComponents(ToInputObjects); - FromInput->GetAllHoudiniInputSplineComponents(FromInputObjects); - - StaleInputObjects = ToInputObjects; - - const int32 NumInputObjects = FromInputObjects.Num(); - ToInputObjects.SetNum(NumInputObjects); - - const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - //Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; - - for(int32 InputObjectIndex = 0; InputObjectIndex < NumInputObjects; ++InputObjectIndex) - { - UHoudiniInputHoudiniSplineComponent* FromInputObject = FromInputObjects[InputObjectIndex]; - UHoudiniInputHoudiniSplineComponent* ToInputObject = ToInputObjects[InputObjectIndex]; - if (!FromInputObject) - continue; - if (!ToInputObject) - continue; - - USCS_Node* SCSNode = nullptr; - if (CachedInputNodes.Contains(ToInputObject->Guid)) - { - // Reuse / update the existing SCS node. - SCSNode = SCS->FindSCSNodeByGuid( CachedInputNodes.FindChecked(ToInputObject->Guid) ); - } - - if (!SCSNode) - { - if (!bCreateMissingSCSNodes) - continue; // This input object should be removed. - } - - USceneComponent* ToComponent = nullptr; - USceneComponent* FromComponent = Cast(FromInputObject->GetObject()); - - StaleInputObjects.Remove(ToInputObject); - - if (FromComponent) - { - if (!SCSNode) - { - if (bCreateMissingSCSNodes) - { - // Create a new SCS node - SCSNode = SCS->CreateNode(FromComponent->GetClass()); - SCSHACParent->AddChildNode(SCSNode); - if (bOutSCSNodeCreated) - { - *bOutSCSNodeCreated = true; - } - AddInputObjectMapping(ToInputObject->Guid, SCSNode->VariableGuid); - } - } - - if (SCSNode) - { - if (bCreateMissingSCSNodes) - { - // If we have been instructed to create missing SCS nodes, assume we are copying - // the the component template. - ToComponent = Cast(SCSNode->ComponentTemplate); - } - else - { - // We are not copying to the component template, so we're assuming this is a - // component instance. Find the component on the owning actor that matches the SCS node. - AActor* ToOwningActor = ToInput->GetTypedOuter(); - check(ToOwningActor); - - ToComponent = Cast(FindComponentInstanceInActor(ToOwningActor, SCSNode)); - } - - if (bCopyInputObjectProperties && ToComponent) - { - USceneComponent* ToAttachParent = ToComponent->GetAttachParent(); - // Copy specific properties from the component template to the instance, if supported by the component. - // We typically resort to this in order to transfer Transient and TransientDuplicate properties from the - // component template over to the instance (typically HasChanged / NeedsToTriggerUpdate flags) in order for - // the instance to cook properly. - IHoudiniEngineCopyPropertiesInterface* ToCopyableComponent = Cast(ToComponent); - if (ToCopyableComponent) - { - // Let the component manage its own data copying. - ToCopyableComponent->CopyPropertiesFrom(FromComponent); - } - else - { - // The component doesn't implement the property copy interface. Simply do a general property copy. - //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, ToComponent, Params); - FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, ToComponent, ComponentCopyOptions); - } - ToComponent->PostEditChange(); - } - } - } - - ToInputObject->Update(ToComponent); - ToInputObjects[InputObjectIndex] = ToInputObject; - } - - for (UHoudiniInputObject* StaleInputObject : StaleInputObjects) - { - if (!StaleInputObject) - continue; - StaleInputObject->InvalidateData(); - ToInput->RemoveHoudiniInputObject(StaleInputObject); - ToInput->MarkChanged(true); - // TODO: Find the corresponding SCS node and remove it - } -} - -#endif - -#if WITH_EDITOR -bool -UHoudiniAssetBlueprintComponent::HasOpenEditor() const -{ - if (IsTemplate()) - { - IAssetEditorInstance* EditorInstance = FindEditorInstance(); - - return EditorInstance != nullptr; - } - - return false; -} -#endif - -#if WITH_EDITOR -IAssetEditorInstance* -UHoudiniAssetBlueprintComponent::FindEditorInstance() const -{ - UClass* BPGC = Cast(GetOuter()); - if (!IsValid(BPGC)) - return nullptr; - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!IsValid(Blueprint)) - return nullptr; - if (!CachedAssetEditorSubsystem.IsValid()) - return nullptr; - - IAssetEditorInstance* EditorInstance = CachedAssetEditorSubsystem->FindEditorForAsset(Blueprint, false); - - return EditorInstance; -} -#endif - -#if WITH_EDITOR -AActor* -UHoudiniAssetBlueprintComponent::GetPreviewActor() const -{ - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - if (BlueprintEditor) - { - return BlueprintEditor->GetPreviewActor(); - } - return nullptr; -} -#endif - -UHoudiniAssetComponent* -UHoudiniAssetBlueprintComponent::GetCachedTemplate() const -{ - return CachedTemplateComponent.Get(); -} - -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateParameters() const -//{ -// return IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateInputs() const -//{ -// return !IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateOutputs() const -//{ -// return !IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanProcessOutputs() const -//{ -// return !IsTemplate(); -//} - -//bool -//UHoudiniAssetBlueprintComponent::CanInstantiateAsset() const -//{ -// // If this is a preview component, it should not trigger an asset instantiation. It should wait -// // for the BPGC template component to finish the cook, get the synced data and then translate. -// -// if (IsPreview()) -// return false; -// -// return true; -//} -// -//// Check whether the HAC can translate Houdini outputs at all -//bool -//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini() const -//{ -// // Template components can't translate Houdini output since they typically do not exist in a world. -// if (IsTemplate()) -// return false; -// // Preview components and normally instanced actors can translate Houdini outputs. -// return true; -//} -// -//// Check whether the HAC can translate a specific output type. -//bool -//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini(EHoudiniOutputType OutputType) const -//{ -// // Blueprint components have limited translation support, for now. -// if (OutputType == EHoudiniOutputType::Mesh) -// return true; -// -// return false; -//} -// -bool -UHoudiniAssetBlueprintComponent::CanDeleteHoudiniNodes() const -{ - return bCanDeleteHoudiniNodes; -} - -void -UHoudiniAssetBlueprintComponent::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; - - for (UHoudiniInput* Input : Inputs) - { - Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for (UHoudiniOutput* Output : Outputs) - { - Output->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -bool -UHoudiniAssetBlueprintComponent::IsValidComponent() const -{ - if (!Super::IsValidComponent()) - return false; - - if (IsTemplate()) - { - UObject* Outer = this->GetOuter(); - if (!IsValid(Outer)) - return false; - UBlueprintGeneratedClass* BPGC = Cast(Outer); - if (!BPGC) - return false; - // Ensure this component is still in the SCS - USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; - if (!SCS) - return false; - USCS_Node* SCSNode = FindSCSNodeForTemplateComponentInClassHierarchy(this); - if (!SCSNode) - return false; - /*UClass* OwnerClass = Outer->GetClass(); - if (!IsValid(OwnerClass)) - return false;*/ - /*UBlueprint* Blueprint = Cast(Outhe); - if (Blueprint) - { - - }*/ - } - -#if WITH_EDITOR - if (!IsTemplate()) - { - if (!GetOwner()) - { - // If it's not a template, it needs an owner! - return false; - } - - USimpleConstructionScript* SCS = GetSCS(); - if (SCS) - { - // We're dealing with a Blueprint related component. - AActor* PreviewActor = GetPreviewActor(); - AActor* OwningActor = GetOwner(); - if (!OwningActor) - return false; - if (OwningActor != PreviewActor) - { - return false; - } - } - - } - - if (IsPreview() && false) - { - USimpleConstructionScript* SCS = GetSCS(); - if (!SCS) - return false; // Preview components should have an SCS. - - // We want to specifically detect whether an editor component is still being previewed. We do this - // by checking whether the owning actor is still the active editor actor in the SCS. - AActor* PreviewActor = GetPreviewActor(); - if (!PreviewActor) - { - return false; - } - - // Ensure this component still belongs the to the current preview actor. - if (PreviewActor != GetOwner()) - { - return false; - } - - /* - AActor* EditorActor = SCS->GetComponentEditorActorInstance(); - if (GetOwner() != EditorActor) - { - return false; - } - */ - } -#endif - - return true; -} - -bool -UHoudiniAssetBlueprintComponent::IsInputTypeSupported(EHoudiniInputType InType) const -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - case EHoudiniInputType::Curve: - return true; - break; - default: - break; - } - return false; -} - -bool -UHoudiniAssetBlueprintComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const -{ - switch (InType) - { - case EHoudiniOutputType::Mesh: - case EHoudiniOutputType::Instancer: - return true; - break; - default: - break; - } - return false; -} - -bool -UHoudiniAssetBlueprintComponent::IsProxyStaticMeshEnabled() const -{ - // TODO: Investigate adding support for proxy meshes in BP - // Disabled for now - return false; -} - -//void -//UHoudiniAssetBlueprintComponent::BroadcastPreAssetCook() -//{ -// // ------------------------------------------------ -// // NOTE: This code will run on TEMPLATE components -// // ------------------------------------------------ -// -// // The HoudiniAsset is about to be recooked. This flag will indicate to -// // the transient components that output processing needs to be baked -// // back to the BP definition. -// bOutputsRequireUpdate = true; -// -// Super::BroadcastPreAssetCook(); -//} - -void -UHoudiniAssetBlueprintComponent::OnPrePreCook() -{ - check(IsPreview()); - - Super::OnPrePreCook(); - - // We need to allow deleting houdini nodes - SetCanDeleteHoudiniNodes(true); -} - -void -UHoudiniAssetBlueprintComponent::OnPostPreCook() -{ - check(IsPreview()); - - Super::OnPostPreCook(); - - // Ensure the houdini nodes can be deleted during the translation process. - SetCanDeleteHoudiniNodes(false); -} - -void -UHoudiniAssetBlueprintComponent::OnPreOutputProcessing() -{ - check(IsPreview()); - - Super::OnPreOutputProcessing(); - - // Ensure the houdini nodes can be deleted during the translation process. - SetCanDeleteHoudiniNodes(true); -} - -void -UHoudiniAssetBlueprintComponent::OnPostOutputProcessing() -{ - Super::OnPostOutputProcessing(); - - // ------------------------------------------------ - // NOTE: - // In Blueprint editor mode, this code will run on PREVIEW components - // In Map editor mode, this code will run on component instances. - // ------------------------------------------------ - if (IsPreview()) - { - // Ensure all the inputs / outputs belonging to the - // preview actor won't be deleted by PreviewActor destruction. - SetCanDeleteHoudiniNodes(false); - -#if WITH_EDITOR - CopyStateToTemplateComponent(); -#endif - - } - bUpdatedFromTemplate = false; -} - -void UHoudiniAssetBlueprintComponent::OnPrePreInstantiation() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnPrePreInstantiation] Component: %s"), *(GetPathName())); - - check(IsPreview()); - - if (bUpdatedFromTemplate) - return; - - check(CachedTemplateComponent.IsValid()); - - // This HDA is about to be cooked but not through template parameter changes. It is likely that an input changed directly in the preview world. - // We need to flag our inputs and parameters appropriately in order to preserve their values. - - // We need to mark all our parameters as changed/not triggering update - for (auto CurrentParam : Parameters) - { - if (CurrentParam) - { - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(false); - } - } - - // We need to mark all our inputs as changed/not triggering update - for (auto CurrentInput : Inputs) - { - if (CurrentInput) - { - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(false); - CurrentInput->MarkDataUploadNeeded(true); - } - } -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniRegisterCompleted() -{ - if (IsTemplate()) - { - // TODO: Do we need to set any status flags or clear stuff to ensure - // the BP HAC will cook properly when the BP is opened again... - - // If the template is being registered, we need to invalidate the AssetId here since it likely - // contains a stale asset id from its last cook. - AssetId = -1; - // Template component's have very limited update requirements / capabilities. - // Mostly just cache parameters and cook state. - AssetState = EHoudiniAssetState::ProcessTemplate; - } - - Super::NotifyHoudiniRegisterCompleted(); -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniPreUnregister() -{ - if (IsTemplate()) - { - // Templates can delete Houdini nodes when they get deregistered. - SetCanDeleteHoudiniNodes(true); - } - Super::NotifyHoudiniPreUnregister(); -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniPostUnregister() -{ - InvalidateData(); - - Super::NotifyHoudiniPostUnregister(); - - if (IsTemplate()) - { - SetCanDeleteHoudiniNodes(false); - } -} - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::OnComponentCreated() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnComponentCreated] Component: %s"), *(GetPathName())); - - Super::OnComponentCreated(); - bUpdatedFromTemplate = false; - - CachePreviewState(); - - if (IsPreview()) - { - // Don't set an initial AssetState here. Preview components should only cook when template's - // Houdini Asset or HDA parameters have changed. - - // Clear these to ensure that we're not sharing references with the component template (otherwise - // the shared objects will get deleted when the component instance gets destroyed). - // These objects will be properly duplicated when copying state from the component template. - Inputs.Empty(); - Parameters.Empty(); - } - - // Wait until InitializeComponent() for blueprint construction to complete before we start caching blueprint data. - -} -#endif - - -void -UHoudiniAssetBlueprintComponent::OnRegister() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnRegister] Component: %s"), *(GetPathName())); - - Super::OnRegister(); - - // We run our Blueprint caching functions here since this the last hook that we have before - // entering HoudiniEngineTick(); - - CacheBlueprintData(); - CachePreviewState(); - - if (IsPreview()) - { - check(CachedTemplateComponent.Get()); - // Ensure that the component template has been registered since it needs to be processed for parameter updates by the HE manager. - if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(CachedTemplateComponent.Get())) - { - // The template component has not been registered yet, which means that we're probably busy opening a Blueprint editor and this - // preview component will need to be updated. - - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(CachedTemplateComponent.Get(), true); - CachedTemplateComponent->SetCanDeleteHoudiniNodes(false); - // Since we're likely opening a fresh blueprint editor, we'll need to instantiate the HDA. - bHasRegisteredComponentTemplate = true; - } - } - - if (IsTemplate()) - { - // We're initializing the asset id for HAC template here since it doesn't get unloaded - // from memory, for example, between Blueprint Editor open/close so we need to make sure - // that the AssetId has indeed been reset between registrations. - AssetId = -1; - } - - //TickInitialization(); -} - -void -UHoudiniAssetBlueprintComponent::BeginDestroy() -{ - Super::BeginDestroy(); -} - -void -UHoudiniAssetBlueprintComponent::DestroyComponent(bool bPromoteChildren) -{ - //FDebug::DumpStackTraceToLog(); - if (CachedTemplateComponent.IsValid() && TemplatePropertiesChangedHandle.IsValid()) - { - CachedTemplateComponent->Modify(); - CachedTemplateComponent->OnParametersChangedEvent.Remove(TemplatePropertiesChangedHandle); - } - Super::DestroyComponent(bPromoteChildren); -} - -void -UHoudiniAssetBlueprintComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - Super::OnComponentDestroyed(bDestroyingHierarchy); -} - -TStructOnScope -UHoudiniAssetBlueprintComponent::GetComponentInstanceData() const -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::GetComponentInstanceData] Component: %s"), *(GetPathName())); - - TStructOnScope ComponentInstanceData = MakeStructOnScope(this); - FHoudiniAssetBlueprintInstanceData* InstanceData = ComponentInstanceData.Cast(); - - InstanceData->AssetId = AssetId; - InstanceData->AssetState = AssetState; - InstanceData->SubAssetIndex = SubAssetIndex; - InstanceData->ComponentGUID = ComponentGUID; - InstanceData->HapiGUID = HapiGUID; - InstanceData->HoudiniAsset = HoudiniAsset; - InstanceData->SourceName = GetPathName(); - InstanceData->AssetCookCount = AssetCookCount; - InstanceData->bHasBeenLoaded = bHasBeenLoaded; - InstanceData->bHasBeenDuplicated = bHasBeenDuplicated; - InstanceData->bPendingDelete = bPendingDelete; - InstanceData->bRecookRequested = bRecookRequested; - InstanceData->bEnableCooking = bEnableCooking; - InstanceData->bForceNeedUpdate = bForceNeedUpdate; - InstanceData->bLastCookSuccess = bLastCookSuccess; - InstanceData->bRegisteredComponentTemplate = bHasRegisteredComponentTemplate; - - InstanceData->Inputs.Empty(); - - for (UHoudiniInput* Input : Inputs) - { - if (!Input) - continue; - UHoudiniInput* TransientInput = Input->DuplicateAndCopyState(GetTransientPackage(), false); - InstanceData->Inputs.Add(TransientInput); - } - - // Cache the current outputs - InstanceData->Outputs.Empty(); - int OutputIndex = 0; - for(UHoudiniOutput* Output : Outputs) - { - if (!Output) - continue; - - TMap OutputObjects = Output->GetOutputObjects(); - for (auto& Entry : OutputObjects) - { - FHoudiniAssetBlueprintOutput OutputObjectData; - OutputObjectData.OutputIndex = OutputIndex; - OutputObjectData.OutputObject = Entry.Value; - InstanceData->Outputs.Add(Entry.Key, OutputObjectData); - } - - ++OutputIndex; - } - - return ComponentInstanceData; - -} - -void -UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData(FHoudiniAssetBlueprintInstanceData* InstanceData, const bool bPostUCS) -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData] Component: %s"), *(GetPathName())); - check(InstanceData); - - if (!bPostUCS) - { - // Initialize the component before the User Construction Script runs - USimpleConstructionScript* SCS = GetSCS(); - check(SCS); - - TArray StaleInputs(Inputs); - - // We need to update references contain in inputs / outputs to point to new reconstructed components. - const int32 NumInputs = InstanceData->Inputs.Num(); - Inputs.SetNum(NumInputs); - for (int i = 0; i < NumInputs; ++i) - { - UHoudiniInput* FromInput = InstanceData->Inputs[i]; - UHoudiniInput* ToInput = Inputs[i]; - - if (ToInput) - { - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - if (!bIsValid) - { - ToInput = nullptr; - } - } - - if (ToInput) - { - // Reuse input - StaleInputs.Remove(ToInput); - ToInput->CopyStateFrom(FromInput, true, false); - } - else - { - // Create new input - ToInput = FromInput->DuplicateAndCopyState(this, false); - } - -#if WITH_EDITOR - // We can't create missing SCS nodes here since we're likely already in the middle of a - // Blueprint reconstruction. We'll have to recreate missing SCS nodes next time the - // component state if copied to the template. - UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, true, false); -#endif - - Inputs[i] = ToInput; - } - - // We need to update FHoudiniOutputObject SceneComponent references to - // the newly created components. Since we cached a map of Output Object IDs to - // SCSNodes (during CopyStateToTemplateComponent), we can the SCSNode that corresponds to this output objects and find - // the SceneComponent that matches the SCSNode's variable name. - // It is important to note that it is safe to do it this way since we're in the pre-UCS - // phase so that current components should match the SCS graph exactly (no user construction script - // interference here yet). - - for (auto& Entry : InstanceData->Outputs) - { - FHoudiniOutputObjectIdentifier& ObjectId = Entry.Key; - FHoudiniAssetBlueprintOutput& OutputData = Entry.Value; - - // NOTE: Output objects are going to be empty here since they dissapear during actor reconstruction. - // We'll need to repopulate from the instance data. - - check(Outputs.IsValidIndex(OutputData.OutputIndex)); - UHoudiniOutput* Output = Outputs[OutputData.OutputIndex]; - check(Output); - TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniOutputObject NewObject = OutputData.OutputObject; - - if (OutputData.OutputObject.OutputComponent) - { - // Update the output component reference. - check(CachedOutputNodes.Contains(ObjectId)) - const FGuid VariableGuid = CachedOutputNodes.FindChecked(ObjectId); - USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(VariableGuid); - - if (SCSNode) - { - // Find the component that corresponds to the SCS node. - USceneComponent* SceneComponent = FindActorComponentByName(GetOwner(), SCSNode->GetVariableName()); - NewObject.OutputComponent = SceneComponent; - } - else - { - NewObject.OutputComponent = nullptr; - } - } - - OutputObjects.Add(ObjectId, NewObject); - } - - if (CachedTemplateComponent.IsValid()) - { -#if WITH_EDITOR - CopyStateFromTemplateComponent( CachedTemplateComponent.Get(), false, false, true); -#endif - } - - AssetId = InstanceData->AssetId; - SubAssetIndex = InstanceData->SubAssetIndex; - ComponentGUID = InstanceData->ComponentGUID; - HapiGUID = InstanceData->HapiGUID; - - // Apply the previous HoudiniAsset to the component - // so that we can compare it against the template during CopyStateFromTemplate() calls to see whether it changed. - HoudiniAsset = InstanceData->HoudiniAsset; - - AssetCookCount = InstanceData->AssetCookCount; - bHasBeenLoaded = InstanceData->bHasBeenLoaded; - bHasBeenDuplicated = InstanceData->bHasBeenDuplicated; - bPendingDelete = InstanceData->bPendingDelete; - bRecookRequested = InstanceData->bRecookRequested; - bEnableCooking = InstanceData->bEnableCooking; - bForceNeedUpdate = InstanceData->bForceNeedUpdate; - bLastCookSuccess = InstanceData->bLastCookSuccess; - bHasRegisteredComponentTemplate = InstanceData->bRegisteredComponentTemplate; - - AssetState = InstanceData->AssetState; - - SetCanDeleteHoudiniNodes(false); - - } // if (!bPostUCS) - /* - else - { - // PostUCS - - } - */ -} - - -void -UHoudiniAssetBlueprintComponent::HoudiniEngineTick() -{ - if (!IsFullyLoaded()) - { - USimpleConstructionScript* SCS = GetSCS(); - if (SCS == nullptr) - { - OnFullyLoaded(); - } - else if (IsPreview()) - { - AActor* OwningActor = GetOwner(); - check(OwningActor); - - // If this is a *preview component*, it is important to wait for the template component to be fully loaded - // since it needs to be initialized so that the component instance can copy initial values from the template. - check(CachedTemplateComponent.Get()); - - if (CachedTemplateComponent->IsFullyLoaded()) - { -#if WITH_EDITOR - if(SCS->IsConstructingEditorComponents()) - { - // We're stuck in an editor blueprint construction / preview actor update. Wait some more. - } - else - { - OnFullyLoaded(); - } -#else - OnFullyLoaded(); -#endif - } - } - else - { - // Anything else can go onto being fully loaded at this point. - OnFullyLoaded(); - } - } -} - -void -UHoudiniAssetBlueprintComponent::OnFullyLoaded() -{ - Super::OnFullyLoaded(); - - // Check whether this component is inside a Blueprint editor. If this object lives outside the blueprint editor (in , then we need to ensure that this - // component won't be influencing the Blueprint asset. - - if (!IsTemplate()) - { -#if WITH_EDITOR - AActor* PreviewActor = GetPreviewActor(); -#else - AActor* PreviewActor = nullptr; -#endif - AActor* OwningActor = GetOwner(); - - if (!PreviewActor) - { - bIsInBlueprintEditor = false; - AssetState = EHoudiniAssetState::None; - return; - } - - if (OwningActor && PreviewActor != OwningActor) - { - bIsInBlueprintEditor = false; - AssetState = EHoudiniAssetState::None; - return; - } - } - - bIsInBlueprintEditor = true; - - CachePreviewState(); - CacheBlueprintData(); - - /* - for (UHoudiniOutput* Output : Outputs) - { - if (!Output) - continue; - } - */ - - if (IsTemplate()) - { - AssetId = -1; - AssetState = EHoudiniAssetState::ProcessTemplate; - } - - if (IsPreview()) - { - check(CachedTemplateComponent.Get()); - - // If this is a preview actor, sync initial settings from the component template -#if WITH_EDITOR - CopyStateFromTemplateComponent(CachedTemplateComponent.Get(), false, false, true); -#endif - - TemplatePropertiesChangedHandle = CachedTemplateComponent->OnParametersChangedEvent.AddUObject(this, &UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler); - if (bHoudiniAssetChanged) - { - - // The HoudiniAsset has changed, so we need to force the PreviewInstance to re-instantiate - AssetState = EHoudiniAssetState::NeedInstantiation; - bForceNeedUpdate = true; - bHoudiniAssetChanged = false; - // TODO: Make this better? - CachedTemplateComponent->bHoudiniAssetChanged = false; - } - - if (bHasRegisteredComponentTemplate) - { - // We have a newly registered component template. One of two things happened to cause this: - // 1. A new HoudiniAssetBlueprintComponent was created and registered. - // 2. The Blueprint Editor was closed / opened. - // The problem that arises in the #2 is that the template component was never fully unloaded - // from memory (it was deregistered but not destroyed). After deregistration we had the - // opportunity to invalidate asset/node ids but now that it has reregistered (without going - // through the normal initialization process) we will have to force a call to MarkAsNeedInstantiation - // during the next OnTemplateParametersChangedHandler() invocation. - bHasBeenLoaded = true; - } - } -} - -void -UHoudiniAssetBlueprintComponent::OnTemplateParametersChanged() -{ - OnParametersChangedEvent.Broadcast(this); -} - -void UHoudiniAssetBlueprintComponent::OnBlueprintStructureModified() -{ - check(IsTemplate()); - bBlueprintStructureModified = false; - -#if WITH_EDITOR - if (IsTemplate()) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(this); - } - else - { - check(CachedTemplateComponent.IsValid()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); - } -#endif -} - -void UHoudiniAssetBlueprintComponent::OnBlueprintModified() -{ - check(IsTemplate()); - bBlueprintModified = false; -#if WITH_EDITOR - FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(this); -#endif -} - -void -UHoudiniAssetBlueprintComponent::OnHoudiniAssetChanged() -{ - if (IsTemplate()) - { - // Invalidate data associated with this component since we're about to change and reinstantiate the Houdini Asset. - SetCanDeleteHoudiniNodes(true); - InvalidateData(); - SetCanDeleteHoudiniNodes(false); - Parameters.Empty(); - Inputs.Empty(); - } - - Super::OnHoudiniAssetChanged(); - - // Set on template components, then copied to preview components, and - // then used (and reset) during OnFullyLoaded. - bHoudiniAssetChanged = true; -} - -void -UHoudiniAssetBlueprintComponent::RegisterHoudiniComponent(UHoudiniAssetComponent *InComponent) -{ - // We only want to register this component if it is the preview actor for the Blueprint editor. -#if WITH_EDITOR - AActor* PreviewActor = GetPreviewActor(); -#else - AActor* PreviewActor = nullptr; -#endif - AActor* OwningActor = GetOwner(); - if (!OwningActor) - return; - - if (PreviewActor != OwningActor) - return; - - Super::RegisterHoudiniComponent(InComponent); -} - - - - -//bool UHoudiniAssetBlueprintComponent::TickInitialization() -//{ -// return true; -// -// if (IsFullyLoaded()) -// return true; -// -// bool bHasFinishedLoading = Super::TickInitialization(); -// -// if (!bHasFinishedLoading) -// return false; -// -// if (!IsTemplate()) -// { -// -// if (CachedTemplateComponent.Get()) -// { -// // Now that that SCS has finished constructing editor components, we can continue. -// // Copy the current state from the template component, in case there is something that can be processed. -// CopyStateFromTemplateComponent(CachedTemplateComponent.Get()); -// } -// -// AssetStateResult = EHoudiniAssetStateResult::None; -// AssetState = EHoudiniAssetState::None; -// -// bForceNeedUpdate = true; -// AssetState = EHoudiniAssetState::PostCook; -// } -// -// return true; -//} - -template -inline void -UHoudiniAssetBlueprintComponent::SetTypedValueAt(const FString& Name, ValueT& Value, int Index) -{ - ParamT* Parameter = Cast(FindParameterByName(Name)); - if (!Parameter) - return; - - Parameter->SetValueAt(Value, Index); -} - -bool -UHoudiniAssetBlueprintComponent::HasParameter(FString Name) -{ - return FindParameterByName(Name) != nullptr; -} - -void -UHoudiniAssetBlueprintComponent::SetFloatParameter(FString Name, float Value, int Index) -{ - SetTypedValueAt(Name, Value, Index); -} - -void -UHoudiniAssetBlueprintComponent::SetToggleValueAt(FString Name, bool Value, int Index) -{ - UHoudiniParameterToggle* Parameter = Cast(FindParameterByName(Name)); - if (!Parameter) - return; - - Parameter->SetValueAt(Value, Index); -} - -//void UHoudiniAssetBlueprintComponent::OnPostCookHandler(UHoudiniAssetComponent* InComponent) -//{ -// -// // Before this component handles any translation, we need to make sure that it still belongs to the editor actor. -// // When a blueprint gets recompiled, the editor actor gets replaced with a new one but the old actor has not yet -// // been ftroyed / garbage collected so its components still receive cook events from the template. -// -// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponent); -// if (!IsValid(ComponentTemplate)) -// return; -// -// CopyStateFromTemplateComponent(ComponentTemplate); -// bForceNeedUpdate = true; -//} - -void -UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler(UHoudiniAssetComponent* InComponentTemplate) -{ - if (!(AssetState == EHoudiniAssetState::None || AssetState == EHoudiniAssetState::NeedInstantiation || AssetState == EHoudiniAssetState::NeedRebuild)) - // Don't process parameter changes since we're already cooking -- it is going to break things badly if we do. - return; - - if (!IsValidComponent()) - return; - - UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); - if (!ComponentTemplate) - return; - - // The component instance needs to copy values from the template. - bool bBlueprintStructureChanged = false; -#if WITH_EDITOR - CopyDetailsFromComponent(ComponentTemplate, - false, - false, - true, - false, - true, - bBlueprintStructureChanged, - RF_Public, - RF_ClassDefaultObject|RF_ArchetypeObject); -#endif - - SetCanDeleteHoudiniNodes(false); - - if (bHasRegisteredComponentTemplate) - { - // NOTE: It is very important to call this *after* CopyDetailsFromComponent(), since CopyDetailsFromComponent - // will clobber the inputs and parameter states on this component. - - // If we already have a valid asset id, keep it. - if (AssetId >= 0) - { - MarkAsNeedCook(); - } - else - { - MarkAsNeedInstantiation(); - } - - bHasRegisteredComponentTemplate = false; - bFullyLoaded = true; // MarkAsNeedInstantiation sets this to false. Force to true. - // While MarkAsNeedInstantiation() sets ParametersChanged to true, it does not - // set the 'NeedToTriggerUpdate' flag (both of which needs to be true in order - // to trigger an HDA update) so we are going to force NeedUpdate() to return true - // in order to get an initial cook. - bForceNeedUpdate = true; - } - - bUpdatedFromTemplate = true; -} - -void -UHoudiniAssetBlueprintComponent::InvalidateData() -{ - if (IsTemplate()) - { - // Ensure transient properties are invalidated/released for parameters, inputs and outputs as if the - // the object was undergoing destruction since the template component will likely be reregistered - // without being destroyed. - for(UHoudiniParameter* Param : Parameters) - { - Param->InvalidateData(); - } - - for(UHoudiniInput* Input : Inputs) - { - Input->InvalidateData(); - } - - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - AssetId = -1; - } -} - -//void UHoudiniAssetBlueprintComponent::OnTemplateHoudiniAssetChangedHandler(UHoudiniAssetComponent* InComponentTemplate) -//{ -// -// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); -// if (!ComponentTemplate) -// return; -// check(IsPreview()); -// -// // The Houdini Asset was changed on the template. We need to recook. -// AssetState = EHoudiniAssetState::NeedInstantiation; -// -//} - -USceneComponent* -UHoudiniAssetBlueprintComponent::FindOwnerComponentByName(FName ComponentName) const -{ - AActor* Owner = GetOwner(); - if (!Owner) - return nullptr; - - return FindActorComponentByName(Owner, ComponentName); -} - -USceneComponent* -UHoudiniAssetBlueprintComponent::FindActorComponentByName(AActor* InActor, FName ComponentName) const -{ - const TSet& Components = InActor->GetComponents(); - - for (UActorComponent* Component : Components) - { - USceneComponent* SceneComponent = Cast(Component); - if (!IsValid(SceneComponent)) - continue; - if (FName(SceneComponent->GetName()) == ComponentName) - return SceneComponent; - } - - return nullptr; -} - -bool UHoudiniAssetBlueprintComponent::GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid) -{ - FGuid* SCSGuid = CachedInputNodes.Find(InputGuid); - if (!SCSGuid) - return false; - OutSCSGuid = *SCSGuid; - return true; -} - -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const -{ - const TArray& AllNodes = SCS->GetAllNodes(); - - for (USCS_Node* Node : AllNodes) - { - if (!Node) - continue; - - if (Node->ComponentTemplate == InComponent) - return Node; - } - - return nullptr; -} - -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponentInClassHierarchy( - const UActorComponent* InComponent) const -{ - UObject* Outer = this->GetOuter(); - if (!IsValid(Outer)) - return nullptr; - UBlueprintGeneratedClass* MainBPGC; - if (IsTemplate()) - { - MainBPGC = Cast(Outer); - } - else - { - AActor* OwningActor = GetOwner(); - MainBPGC = Cast(OwningActor->GetClass()); - } - - check(MainBPGC); - TArray BPGCStack; - UBlueprintGeneratedClass::GetGeneratedClassesHierarchy(MainBPGC, BPGCStack); - for(const UBlueprintGeneratedClass* BPGC : BPGCStack) - { - USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; - if (!SCS) - return nullptr; - USCS_Node* SCSNode = FindSCSNodeForTemplateComponent(SCS, InComponent); - SCSNode = SCS->FindSCSNode(InComponent->GetFName()); - if (SCSNode) - return SCSNode; - } - - return nullptr; -} - -#if WITH_EDITOR -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const -{ - const TArray& AllNodes = SCS->GetAllNodes(); - - if (!InComponent) - return nullptr; - - for (USCS_Node* Node : AllNodes) - { - if (!Node) - continue; - if (Node->EditorComponentInstance.Get() == InComponent) - return Node; - } - - return nullptr; -} -#endif - -UActorComponent* -UHoudiniAssetBlueprintComponent::FindComponentInstanceInActor(const AActor* InActor, - USCS_Node* SCSNode) const -{ - UActorComponent* ComponentTemplate = SCSNode->ComponentTemplate; - - UActorComponent* ComponentInstance = NULL; - if (InActor != NULL) - { - if (SCSNode != NULL) - { - FName VariableName = SCSNode->GetVariableName(); - if (VariableName != NAME_None) - { - UWorld* World = InActor->GetWorld(); - FObjectPropertyBase* Property = FindFProperty(InActor->GetClass(), VariableName); - if (Property != NULL) - { - // Return the component instance that's stored in the property with the given variable name - ComponentInstance = Cast(Property->GetObjectPropertyValue_InContainer(InActor)); - } - else if (World != nullptr && World->WorldType == EWorldType::EditorPreview) - { - // If this is the preview actor, return the cached component instance that's being used for the pmnaview actor prior to recompiling the Blueprint -#if WITH_EDITOR - ComponentInstance = SCSNode->EditorComponentInstance.Get(); -#endif - } - } - } - else if (ComponentTemplate != NULL) - { -#if WITH_EDITOR - TInlineComponentArray Components; - InActor->GetComponents(Components); - ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components); -#endif - } - } - - return ComponentInstance; -} - - -//void UHoudiniAssetBlueprintComponent::OnOutputProcessingCompletedHandler(UHoudiniAssetComponent* InComponent) -//{ -// -// UHoudiniAssetBlueprintComponent* TemplateComponent = Cast(InComponent); -// if (!IsValid(TemplateComponent)) -// return; -// -// CopyStateFromComponent(TemplateComponent); -// bForceNeedUpdate = true; -//} - -//#if WITH_EDITOR -//void UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason) -//{ -// -// if (CachedBlueprint.Get()) -// { -// } -// -// if (Asset) -// { -// -// } -// -//} -//#endif - -void -UHoudiniAssetBlueprintComponent::CachePreviewState() -{ - bCachedIsPreview = false; - -#if WITH_EDITOR - AActor* ComponentOwner = GetOwner(); - if (!IsValid(ComponentOwner)) - return; - - USimpleConstructionScript* SCS = GetSCS(); - if (SCS == nullptr) - return; - - // Get the preview actor directly from the BlueprintEditor. - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - if (BlueprintEditor) - { - AActor* PreviewActor = BlueprintEditor->GetPreviewActor(); - if (PreviewActor == ComponentOwner) - { - bCachedIsPreview = true; - return; - } - } -#endif -} - -void -UHoudiniAssetBlueprintComponent::CacheBlueprintData() -{ - CachedBlueprint = nullptr; - CachedActorCDO = nullptr; - CachedTemplateComponent = IsTemplate() ? this : nullptr; - -#if WITH_EDITOR - CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); -#endif - - UBlueprintGeneratedClass* BPGC = Cast(GetOuter()); - if (BPGC) - { - // Dealing with a component template - CachedBlueprint = Cast(BPGC->ClassGeneratedBy); - } - else - { - // Dealing with a component instance. - CachedBlueprint = Cast(GetOuter()->GetClass()->ClassGeneratedBy); - } - - if (CreationMethod != EComponentCreationMethod::SimpleConstructionScript) - return; - - AActor* ComponentOwner = this->GetOwner(); - if (!IsValid(ComponentOwner)) - return; - UClass* OwnerClass = ComponentOwner->GetClass(); - if (!IsValid(OwnerClass)) - return; - - if (!IsTemplate()) - { - // NOTE: The following code allows us to find the component template from an instance. - CachedActorCDO = Cast< AActor >(CachedBlueprint->GeneratedClass->GetDefaultObject()); - if (!CachedActorCDO.IsValid() || (CachedActorCDO.Get() == ComponentOwner)) - return; -#if WITH_EDITOR - UActorComponent* TargetComponent = EditorUtilities::FindMatchingComponentInstance(this, CachedActorCDO.Get()); - CachedTemplateComponent = Cast(TargetComponent); -#endif - } - -} - -USimpleConstructionScript* -UHoudiniAssetBlueprintComponent::GetSCS() const -{ - if (!CachedBlueprint.Get()) - return nullptr; - - return CachedBlueprint->SimpleConstructionScript; -} - -//------------------------------------------------------------------------------------------------ -// FHoudiniAssetBlueprintInstanceData -//------------------------------------------------------------------------------------------------ - -FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData() - : HoudiniAsset(nullptr) - , AssetId(-1) - , AssetState(EHoudiniAssetState::None) - , SubAssetIndex(-1) - , AssetCookCount(0) - , bHasBeenLoaded(false) - , bHasBeenDuplicated(false) - , bPendingDelete(false) - , bRecookRequested(false) - , bRebuildRequested(false) - , bEnableCooking(true) - , bForceNeedUpdate(false) - , bLastCookSuccess(false) - , ComponentGUID(FGuid()) - , HapiGUID(FGuid()) - , bRegisteredComponentTemplate(false) - , SourceName() -{ - -} - -FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent) - : FActorComponentInstanceData(SourceComponent) -{ - -} - -void -FHoudiniAssetBlueprintInstanceData::AddReferencedObjects(FReferenceCollector & Collector) -{ - Super::AddReferencedObjects(Collector); - // TODO: Do we need to add references to output objects here? - // Any other references? - // What are the implications? -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniAssetBlueprintComponent.h" + +#include "HoudiniEngineCopyPropertiesInterface.h" +#include "HoudiniOutput.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Engine/SCS_Node.h" +#include "Engine/SimpleConstructionScript.h" +#include "UObject/Object.h" +#include "Logging/LogMacros.h" + +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniInput.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Kismet2/BlueprintEditorUtils.h" + #include "Kismet2/KismetEditorUtilities.h" + #include "Toolkits/AssetEditorManager.h" + #include "Kismet2/ComponentEditorUtils.h" + #include "ComponentAssetBroker.h" +#endif + +HOUDINI_BP_DEFINE_LOG_CATEGORY(); + +UHoudiniAssetBlueprintComponent::UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + +#if WITH_EDITOR + if (IsTemplate()) + { + // CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); + //GEditor->GetEditorSubsystem()->OnAssetEditorRequestClose().AddUObject( this, &UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent ); + } +#endif + + bForceNeedUpdate = false; + bHoudiniAssetChanged = false; + bIsInBlueprintEditor = false; + bCanDeleteHoudiniNodes = false; + + // AssetState will be updated by changes to the HoudiniAsset + // or parameter changes on the Component template. + AssetState = EHoudiniAssetState::None; + bHasRegisteredComponentTemplate = false; + bHasBeenLoaded = false; + bUpdatedFromTemplate = false; + + // Disable proxy mesh by default (unsupported for now) + bOverrideGlobalProxyStaticMeshSettings = true; + bEnableProxyStaticMeshOverride = false; + bEnableProxyStaticMeshRefinementByTimerOverride = false; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = false; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = false; + StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; + + // Set default mobility to Movable + Mobility = EComponentMobility::Movable; +} + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() +{ + // We need to propagate changes made here back to the corresponding component in + // the Blueprint Generated Class ("_GEN_VARIABLE"). The reason being that + // the Blueprint editor works directly with the GEN_VARIABLE component (all + // PostEditChange() calls, Details Customizations, etc will receive the GEN_VARIABLE instance) BUT + // when the Editor runs the construction script it uses a different component instance, so all changes + // made to that instance won't write back to the Blueprint definition. + // To Summarize: + // Be sure to sync the Parameters array (and any other relevant properties) back + // to the corresponding component on the Blueprint Generated class otherwise these wont be + // accessible in the Details Customization callbacks. + + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] To Component: %s"), *(CachedTemplateComponent->GetPathName())); + + // This should never be called by component templates. + check(!IsTemplate()); + + if (!CachedTemplateComponent.IsValid()) + return; + + USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; + check(SCS); + + /* + USCS_Node* SCSNodeForInstance = FindSCSNodeForInstanceComponent(SCS, this); + if (SCSNodeForInstance) + { + + } + else + { + + } + */ + + //// If we don't have an SCS node for this preview instance, we need to create one, regardless + //// of whether output updates are required. + //if (!CachedTemplateComponent->bOutputsRequireUpdate && SCSNodeForInstance != nullptr) + // return; + + // TODO: If the blueprint editor is NOT open, then we shouldn't attempting + // to copy state back to the BPGC at all! + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + check(BlueprintEditor); + + TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); + check(SCSEditor); + + USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + // check(SCSHACNode); + + // This is the actor instance that is being used for component editing. + AActor* PreviewActor = GetPreviewActor(); + check(PreviewActor); + + // NOTE: Inputs are only from component templates to instances, not the other way around ... I think. + + // ----------------------------------------------------- + // Copy outputs to component template + // ----------------------------------------------------- + + // Populate / update the outputs for the template from the preview / instance. + // TODO: Wrap the Blueprint manipulation in a transaction + TArray& TemplateOutputs = CachedTemplateComponent->Outputs; + TSet StaleTemplateOutputs(TemplateOutputs); + + TemplateOutputs.SetNum(Outputs.Num()); + CachedOutputNodes.Empty(); + + for (int i = 0; i < Outputs.Num(); i++) + { + // Find a output on the template that corresponds to this output from the instance. + UHoudiniOutput* TemplateOutput = nullptr; + UHoudiniOutput* InstanceOutput = nullptr; + InstanceOutput = Outputs[i]; + + check(InstanceOutput) + // Ensure that instance outputs won't delete houdini content. + // Houdini content should only be allowed to be deleted from + // the component template. + InstanceOutput->SetCanDeleteHoudiniNodes(false); + + TemplateOutput = TemplateOutputs[i]; + + if (TemplateOutput) + { + check(TemplateOutput->GetOuter() == CachedTemplateComponent.Get()); + StaleTemplateOutputs.Remove(TemplateOutput); + } + + + if (TemplateOutput) + { + // Copy properties from the current instance component while preserving output objects + // and instanced outputs. + TemplateOutput->CopyPropertiesFrom(InstanceOutput, true); + } + else + { + // NOTE: If the template output is NULL it means that the HDA spawned a new component / output in the transient world + // and the new output object needs to be copied back to the BPGC. + + // Corresponding template output could not be found. Create one by duplicating the instance output. + TemplateOutput = InstanceOutput->DuplicateAndCopyProperties(CachedTemplateComponent.Get(), FName(InstanceOutput->GetName())); + // Treat these the same one would components created by CreateDefaultSubObject. + // NOTE: CreateDefaultSubobject performs lots of checks, and unfortunately we can't use it directly (it is + // only allowed to be used in a constructor). Not sure whether we need to either. For now, we just set the + // object flags to be similar to components created by CreateDefaultSubobject. + TemplateOutput->SetFlags(RF_Public|RF_ArchetypeObject|RF_DefaultSubObject); + TemplateOutputs[i] = TemplateOutput; + } + + check(TemplateOutput); + TemplateOutput->SetCanDeleteHoudiniNodes(false); + + // Keep track of potential stale output objects on the template component, for this output. + TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); + TArray StaleTemplateObjects; + TemplateOutputObjects.GetKeys(StaleTemplateObjects); + + for (auto& Entry : InstanceOutput->GetOutputObjects()) + { + + // Prepare the FHoudiniOutputObject for the template component + const FHoudiniOutputObject& InstanceObj = Entry.Value; + FHoudiniOutputObject TemplateObj; + + // Any output present in the Instance Outputs should be + // transferred to the template. + // Remove this output object from stale outputs list. + StaleTemplateObjects.Remove(Entry.Key); + + if (TemplateOutputObjects.Contains(Entry.Key)) + { + // Reuse the existing template object + TemplateObj = TemplateOutputObjects.FindChecked(Entry.Key); + } + else + { + // Create a new template output object object by duplicating the instance object. + // Keep the output object, but clear the output component since we have to + // create a new component template. + TemplateObj = InstanceObj; + TemplateObj.ProxyComponent = nullptr; + TemplateObj.OutputComponent = nullptr; + TemplateObj.ProxyObject = nullptr; + } + + USceneComponent* ComponentInstance = Cast(InstanceObj.OutputComponent); + USceneComponent* ComponentTemplate = Cast(TemplateObj.OutputComponent); + UObject* OutputObject = InstanceObj.OutputObject; + + if (ComponentInstance) + { + // The translation process has either constructed new components, or it is + // reusing existing components, or changed an output (or all or none of the aforementioned). + // Carefully inspect the SCS graph to determine whether there is a corresponding + // (and compatible) node for this output. If not, create a new node and remove unusable node, if any. + + USCS_Node* ComponentNode = nullptr; + { + // Check whether the current OutputComponent being referenced by the template is still valid. + // Even if it was removed in the editor, it doesn't have any associated destroyed / pendingkill state. + // Instead we're going to check for validity by finding an SCS node with a matching template component. + bool bValidComponentTemplate = (ComponentTemplate != nullptr); + if (ComponentTemplate) + { + + ComponentNode = FindSCSNodeForTemplateComponentInClassHierarchy(ComponentTemplate); + bValidComponentTemplate = bValidComponentTemplate && (ComponentNode != nullptr); + } + + if (!bValidComponentTemplate) + { + // Either this component was removed from the editor or it doesn't exist yet. + // Ensure the references are cleared + TemplateObj.OutputComponent = nullptr; + ComponentTemplate = nullptr; + } + } + + // NOTE: we can't use the component instance name directly due to the Blueprint compiler performing an internal checking + // using FComponentEditorUtils::IsValidVariableNameString(), which will return false if the name looks like an autogenerated name... + //FString ComponentName = ComponentInstance->GetName(); + FString ComponentName = FBlueprintEditorUtils::GetClassNameWithoutSuffix(ComponentInstance->GetClass()); + FName ComponentFName = FName(ComponentName); + + + const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )( + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | + EditorUtilities::ECopyOptions::CallPostEditChangeProperty | + EditorUtilities::ECopyOptions::CallPostEditMove); + + if (IsValid(ComponentNode)) + { + // Check if we have an existing and compatible SCS node containing a USceneComponent as a template component. + bool bComponentNodeIsValid = true; + + ComponentTemplate = Cast(ComponentNode->ComponentTemplate); + + bComponentNodeIsValid = bComponentNodeIsValid && ComponentInstance->GetClass() == ComponentNode->ComponentClass; + bComponentNodeIsValid = bComponentNodeIsValid && ComponentTemplate != nullptr; + // TODO: Do we need to perform any other compatibility checks? + + if (!bComponentNodeIsValid) + { + // Component template is not compatible. We can't reuse it. + + SCSHACNode->RemoveChildNode(ComponentNode); + SCS->RemoveNode(ComponentNode); + ComponentNode = nullptr; + ComponentTemplate = nullptr; + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + } + + if (ComponentNode) + { + // We found a reusable SCS node. Just copy the component instance + // properties over to the existing template. + check(ComponentNode->ComponentTemplate); + + // UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + // //Params.bReplaceObjectClassReferences = false; + // Params.bDoDelta = false; // Perform a deep copy + // Params.bClearReferences = false; + // UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + + FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + + ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; + } + else + { + // We couldn't find a reusable SCS node. + // Duplicate the instance component and create a new corresponding SCS node + ComponentNode = SCS->CreateNode(ComponentInstance->GetClass(), ComponentFName); + + UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // We need a deep copy of parameters here so the CDO values get copied as well + UEditorEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + // FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); + + // { + // UInstancedStaticMeshComponent* Component = Cast(ComponentNode->ComponentTemplate); + // if (Component) + // { + // } + // } + + // NOTE: The EComponentCreationMethod here is currently set to be the same as a component that was + // created manually in the editor. + ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; + + // Add this node to the SCS root set. + + // Attach the new node the HAC SCS node + // NOTE: This will add the node to the SCS->AllNodes list too but it won't update + // the nodename map. We can't forcibly update the Node/Name map either since the + // relevant functions have not been exported. + SCSHACNode->AddChildNode(ComponentNode); + + // Set the output component. + TemplateObj.OutputComponent = ComponentNode->ComponentTemplate; + + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + + // Cache the mapping between the output and the SCS node. + check(ComponentNode); + CachedOutputNodes.Add(Entry.Key, ComponentNode->VariableGuid); + } // if (ComponentInstance) + /* + else if (InstanceObj.OutputObject) + { + + } + */ + + // Add the updated output object to the template output + TemplateOutputObjects.Add(Entry.Key, TemplateObj); + } + + // Cleanup stale objects for this template output. + for (const auto& StaleId : StaleTemplateObjects) + { + FHoudiniOutputObject& OutputObj = TemplateOutputObjects.FindChecked(StaleId); + + // Ensure the component template is no longer referencing this output. + TemplateOutputObjects.Remove(StaleId); + + USceneComponent* TemplateComponent = Cast(OutputObj.OutputComponent); + + if (TemplateComponent) + { + USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(TemplateComponent); + if (StaleNode) + { + + SCS->RemoveNode(StaleNode, false); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + /* + else + { + + } + */ + } + /* + else + { + + } + */ + } + } //for (int i = 0; i < Outputs.Num(); i++) + + // Clean up stale outputs on the component template. + for (UHoudiniOutput* StaleOutput : StaleTemplateOutputs) + { + if (!StaleOutput) + continue; + + // Remove any components contained in this output from the SCS graph + for (auto& Entry : StaleOutput->GetOutputObjects()) + { + FHoudiniOutputObject& StaleObject = Entry.Value; + USceneComponent* OutputComponent = Cast(StaleObject.OutputComponent); + + if (OutputComponent) + { + + USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(OutputComponent); + if (StaleNode) + { + + SCS->RemoveNode(StaleNode, false); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + } + } + + TemplateOutputs.Remove(StaleOutput); + //StaleOutput->ConditionalBeginDestroy(); + } + + SCS->ValidateSceneRootNodes(); + + // Copy parameters from this component to the template component. + // NOTE: We need to do this since the preview component will be cooking the HDA and get populated with + // all the parameters. This data needs to be sent back to the component template. + UClass* ComponentClass = CachedTemplateComponent->GetClass(); + UHoudiniAssetBlueprintComponent* DefaultObj = Cast(ComponentClass->GetDefaultObject()); + bool bBPStructureModified = false; + CachedTemplateComponent->CopyDetailsFromComponent( + this, + true, + true, + true, + false, + true, + bBPStructureModified, + /* SetFlags */ CachedTemplateComponent->GetMaskedFlags(RF_PropagateToSubObjects)); + + if (bBPStructureModified) + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + + // Copy the cached output nodes back to the template so that + // reconstructed actors can correctly update output objects + // with newly constructed components during ApplyComponentInstanceData() calls. + CachedTemplateComponent->CachedOutputNodes = CachedOutputNodes; + + CachedTemplateComponent->MarkPackageDirty(); + PostEditChange(); + + CachedTemplateComponent->AssetId = AssetId; + CachedTemplateComponent->HapiGUID = HapiGUID; + CachedTemplateComponent->AssetCookCount = AssetCookCount; + CachedTemplateComponent->AssetStateResult = AssetStateResult; + CachedTemplateComponent->bLastCookSuccess = bLastCookSuccess; + +#if WITH_EDITOR + // TODO: Do we need to handle this right now or can we wait for the next Houdini Engine manager tick to process it? + if (CachedTemplateComponent->NeedBlueprintStructureUpdate()) + { + // We are about to recompile the blueprint. This will reconstruct the preview actor so we need to ensure + // that the old actor won't release the houdini nodes. + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); + SetCanDeleteHoudiniNodes(false); + } + /*else if (CachedTemplateComponent->NeedBlueprintUpdate()) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(CachedTemplateComponent.Get()); + }*/ +#endif +} +#endif + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties) +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] From Component: %s"), *(FromComponent->GetPathName())); + + // This should never be called by component templates. + check(!IsTemplate()); + + // Make sure all TransientDuplicate properties from the Template Component needed by this transient component + // gets copied. + + ComponentGUID = FromComponent->ComponentGUID; + + /* + { + const TArray Children = GetAttachChildren(); + for (USceneComponent* Child : Children) + { + if (!Child) + continue; + } + } + */ + + // AssetState = FromComponent->PreviewAssetState; + + // This state should not be shared between template / instance components. + //bFullyLoaded = FromComponent->bFullyLoaded; + + bNoProxyMeshNextCookRequested = FromComponent->bNoProxyMeshNextCookRequested; + + // Reconstruct outputs and update them to point to component instances as opposed to templates. + UObject* TemplateOuter = CachedTemplateComponent->GetOuter(); + + USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; + check(SCS); + + // NOTE: We can find the SCS node for the HoudiniAssetComponent from either the template component or the instance (editor preview) component. + USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + check(SCSHACNode); + + // ----------------------------------------------------- + // Copy outputs to component template + // ----------------------------------------------------- + + TArray& TemplateOutputs = CachedTemplateComponent->Outputs; + + TSet StaleInstanceOutputs(Outputs); + + Outputs.SetNum(TemplateOutputs.Num()); + + for (int i = 0; i < TemplateOutputs.Num(); i++) + { + UHoudiniOutput* TemplateOutput = TemplateOutputs[i]; + if (!IsValid(TemplateOutput)) + continue; + + UHoudiniOutput* InstanceOutput = Outputs[i]; + if (!(InstanceOutput->GetOuter() == this)) + InstanceOutput = nullptr; + + if (InstanceOutput) + { + StaleInstanceOutputs.Remove(InstanceOutput); + } + + if (InstanceOutput) + { + // Copy properties from the current instance component while preserving output objects + // and instanced outputs. + InstanceOutput->CopyPropertiesFrom(TemplateOutput, true); + } + else + { + InstanceOutput = TemplateOutput->DuplicateAndCopyProperties(this, FName(TemplateOutput->GetName())); + InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); + } + + InstanceOutput->SetCanDeleteHoudiniNodes(false); + Outputs[i] = InstanceOutput; + + TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); + TMap& InstanceOutputObjects = InstanceOutput->GetOutputObjects(); + TArray StaleOutputObjects; + InstanceOutputObjects.GetKeys(StaleOutputObjects); + + for (auto& Entry : TemplateOutputObjects) + { + const FHoudiniOutputObject& TemplateObj = Entry.Value; + FHoudiniOutputObject InstanceObj = TemplateObj; + + if (!InstanceOutputObjects.Contains(Entry.Key)) + continue; + + StaleOutputObjects.Remove(Entry.Key); + InstanceObj = InstanceOutputObjects.FindChecked(Entry.Key); + + } // for (auto& Entry : TemplateOutputObjects) + + // Cleanup stale output objects for this output. + for (const auto& StaleId : StaleOutputObjects) + { + //TemplateOutput + //check(TemplateOutputs); + + FHoudiniOutputObject& OutputObj = InstanceOutputObjects.FindChecked(StaleId); + + InstanceOutputObjects.Remove(StaleId); + if (OutputObj.OutputComponent) + { + //OutputObj.OutputComponent->ConditionalBeginDestroy(); + OutputObj.OutputComponent = nullptr; + } + } + } // for (int i = 0; i < TemplateOutputs.Num(); i++) + + // Cleanup any stale outputs found on the component instance. + for (UHoudiniOutput* StaleOutput : StaleInstanceOutputs) + { + if (!StaleOutput) + continue; + + if (!(StaleOutput->GetOuter() == this)) + continue; + + // We don't want to clear stale outputs on components instances. Only on template components. + StaleOutput->SetCanDeleteHoudiniNodes(false); + } + + // Copy parameters from the component template to the instance. + bool bBlueprintStructureChanged = false; + CopyDetailsFromComponent(FromComponent, + false, + bClearFromInputs, + bClearToInputs, + false, + true, + bBlueprintStructureChanged, + /*SetFlags*/ RF_Public, + /*ClearFlags*/ RF_DefaultSubObject|RF_ArchetypeObject); +} +#endif + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent( + UHoudiniAssetBlueprintComponent* FromComponent, + const bool bCreateSCSNodes, + const bool bClearChangedToInputs, + const bool bClearChangedFromInputs, + const bool bInCanDeleteHoudiniNodes, + const bool bCopyInputObjectComponentProperties, + bool &bOutBlueprintStructureChanged, + EObjectFlags SetFlags, + EObjectFlags ClearFlags) +{ + check(FromComponent); + + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] FromComponent: %s"), *(FromComponent->GetPathName())); + + /* + if (!FromComponent->HoudiniAsset) + { + return; + } + */ + + // TODO: Try to reuse objects here when we're able. + //// Copy UHoudiniOutput state from instance to template + //UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + ////Params.bReplaceObjectClassReferences = false; + ////Params.bClearReferences = false; + //Params.bDoDelta = true; + //UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + + // Record input remapping that will need to take place when duplicating parameters. + TMap InputMapping; + + // ----------------------------------------------------- + // Copy inputs + // ----------------------------------------------------- + + // TODO: Add support for input components + { + TArray& FromInputs = FromComponent->Inputs; + TSet StaleInputs(Inputs); + USimpleConstructionScript* SCS = GetSCS(); + USCS_Node* SCSHACNode = nullptr; + + if (bCreateSCSNodes) + { + SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + } + + Inputs.SetNum(FromInputs.Num()); + for (int i = 0; i < FromInputs.Num(); i++) + { + UHoudiniInput* FromInput = nullptr; + UHoudiniInput* ToInput = nullptr; + FromInput = FromInputs[i]; + + check(FromInput); + + ToInput = Inputs[i]; + + if (ToInput) + { + // Check whether the instance and template input objects are compatible. + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + + if (!bIsValid) + { + ToInput = nullptr; + } + } + + // TODO: Process stale input objects + + // NOTE: The CopyStateFrom() / DuplicateAndCopyState() will copy/duplicate/cleanup internal inputs to + // ensure that there aren't any shared instances between the ToInput/FromInput. + if (ToInput) + { + // We have a compatible input that we can reuse. + StaleInputs.Remove(ToInput); + ToInput->CopyStateFrom(FromInput, true, bInCanDeleteHoudiniNodes); + } + else + { + + // We don't have an existing / compatible input. Create a new one. + ToInput = FromInput->DuplicateAndCopyState(this, bInCanDeleteHoudiniNodes); + if (SetFlags != RF_NoFlags) + ToInput->SetFlags(SetFlags); + if (ClearFlags != RF_NoFlags) + ToInput->ClearFlags( ClearFlags ); + } + + check(ToInput); + + + UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, bCopyInputObjectComponentProperties, bCreateSCSNodes, SCSHACNode, &bOutBlueprintStructureChanged); + + Inputs[i] = ToInput; + InputMapping.Add(FromInput, ToInput); + + if (bClearChangedToInputs) + { + // Clear the changed flags on the FromInput so that it doesn't trigger + // another update. The ToInput will now be carrying to changed/update flags. + ToInput->MarkChanged(false); + ToInput->MarkAllInputObjectsChanged(false); + } + + if (bClearChangedFromInputs) + { + // Clear the changed flags on the FromInput so that it doesn't trigger + // another update. The ToInput will now be carrying to changed/update flags. + FromInput->MarkChanged(false); + FromInput->MarkAllInputObjectsChanged(false); + } + } + + // Cleanup any stale inputs from this component. + // NOTE: We would typically only have stale inputs when copying state from + // the component instance to the component template. Garbage collection + // eventually picks up the input objects and removes the content + // but until such time we are stuck with those nodes as inputs in the Houdini session + // so we get rid of those nodes immediately here to avoid some user confusion. + for (UHoudiniInput* StaleInput : StaleInputs) + { + if (!IsValid(StaleInput)) + continue; + + check(StaleInput->GetOuter() == this); + + if (StaleInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + StaleInput->ConditionalBeginDestroy(); + } + } + + + // ----------------------------------------------------- + // Copy parameters (and optionally remap inputs). + // ----------------------------------------------------- + TMap ParameterMapping; + + TArray& FromParameters = FromComponent->Parameters; + Parameters.SetNum(FromParameters.Num()); + + for (int i = 0; i < FromParameters.Num(); i++) + { + UHoudiniParameter* FromParameter = nullptr; + UHoudiniParameter* ToParameter = nullptr; + + FromParameter = FromParameters[i]; + + check(FromParameter); + + if (Parameters.IsValidIndex(i)) + { + ToParameter = Parameters[i]; + } + + if (ToParameter) + { + bool bIsValid = true; + // Check whether To/From parameters are compatible + bIsValid = bIsValid && ToParameter->Matches(*FromParameter); + bIsValid = bIsValid && ToParameter->GetOuter() == this; + + if (!bIsValid) + ToParameter = nullptr; + } + + if (ToParameter) + { + // Parameter already exists. Simply sync the state. + ToParameter->CopyStateFrom(FromParameter, true, ClearFlags, SetFlags); + } + else + { + // TODO: Check whether parameters are the same to avoid recreating them. + ToParameter = FromParameter->DuplicateAndCopyState(this, ClearFlags, SetFlags); + Parameters[i] = ToParameter; + } + + check(ToParameter); + ParameterMapping.Add(FromParameter, ToParameter); + + if (bClearChangedFromInputs) + { + // We clear the Changed flag on the FromParameter (most likely on the component template) + // since the template parameter state has now been transfered to the preview component and + // will resume processing from there. + FromParameter->MarkChanged(false); + } + } + + // Apply remappings on the new parameters + for (UHoudiniParameter* ToParameter : Parameters) + { + ToParameter->RemapParameters(ParameterMapping); + ToParameter->RemapInputs(InputMapping); + } + + FProperty* ParametersProperty = GetClass()->FindPropertyByName(TEXT("Parameters")); + FPropertyChangedEvent Evt(ParametersProperty); + PostEditChangeProperty(Evt); + + bEnableCooking = FromComponent->bEnableCooking; + bRecookRequested = FromComponent->bRecookRequested; + bRebuildRequested = FromComponent->bRebuildRequested; +} + +void +UHoudiniAssetBlueprintComponent::UpdateInputObjectComponentReferences( + USimpleConstructionScript* SCS, + UHoudiniInput* FromInput, + UHoudiniInput* ToInput, + const bool bCopyInputObjectProperties, + const bool bCreateMissingSCSNodes, + USCS_Node* SCSHACParent, + bool* bOutSCSNodeCreated) +{ + TArray ToInputObjects; + TArray FromInputObjects; + TArray StaleInputObjects; + + ToInput->GetAllHoudiniInputSplineComponents(ToInputObjects); + FromInput->GetAllHoudiniInputSplineComponents(FromInputObjects); + + StaleInputObjects = ToInputObjects; + + const int32 NumInputObjects = FromInputObjects.Num(); + ToInputObjects.SetNum(NumInputObjects); + + const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + //Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; + + for(int32 InputObjectIndex = 0; InputObjectIndex < NumInputObjects; ++InputObjectIndex) + { + UHoudiniInputHoudiniSplineComponent* FromInputObject = FromInputObjects[InputObjectIndex]; + UHoudiniInputHoudiniSplineComponent* ToInputObject = ToInputObjects[InputObjectIndex]; + if (!FromInputObject) + continue; + if (!ToInputObject) + continue; + + USCS_Node* SCSNode = nullptr; + if (CachedInputNodes.Contains(ToInputObject->Guid)) + { + // Reuse / update the existing SCS node. + SCSNode = SCS->FindSCSNodeByGuid( CachedInputNodes.FindChecked(ToInputObject->Guid) ); + } + + if (!SCSNode) + { + if (!bCreateMissingSCSNodes) + continue; // This input object should be removed. + } + + USceneComponent* ToComponent = nullptr; + USceneComponent* FromComponent = Cast(FromInputObject->GetObject()); + + StaleInputObjects.Remove(ToInputObject); + + if (FromComponent) + { + if (!SCSNode) + { + if (bCreateMissingSCSNodes) + { + // Create a new SCS node + SCSNode = SCS->CreateNode(FromComponent->GetClass()); + SCSHACParent->AddChildNode(SCSNode); + if (bOutSCSNodeCreated) + { + *bOutSCSNodeCreated = true; + } + AddInputObjectMapping(ToInputObject->Guid, SCSNode->VariableGuid); + } + } + + if (SCSNode) + { + if (bCreateMissingSCSNodes) + { + // If we have been instructed to create missing SCS nodes, assume we are copying + // the the component template. + ToComponent = Cast(SCSNode->ComponentTemplate); + } + else + { + // We are not copying to the component template, so we're assuming this is a + // component instance. Find the component on the owning actor that matches the SCS node. + AActor* ToOwningActor = ToInput->GetTypedOuter(); + check(ToOwningActor); + + ToComponent = Cast(FindComponentInstanceInActor(ToOwningActor, SCSNode)); + } + + if (bCopyInputObjectProperties && ToComponent) + { + USceneComponent* ToAttachParent = ToComponent->GetAttachParent(); + // Copy specific properties from the component template to the instance, if supported by the component. + // We typically resort to this in order to transfer Transient and TransientDuplicate properties from the + // component template over to the instance (typically HasChanged / NeedsToTriggerUpdate flags) in order for + // the instance to cook properly. + IHoudiniEngineCopyPropertiesInterface* ToCopyableComponent = Cast(ToComponent); + if (ToCopyableComponent) + { + // Let the component manage its own data copying. + ToCopyableComponent->CopyPropertiesFrom(FromComponent); + } + else + { + // The component doesn't implement the property copy interface. Simply do a general property copy. + //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, ToComponent, Params); + FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, ToComponent, ComponentCopyOptions); + } + ToComponent->PostEditChange(); + } + } + } + + ToInputObject->Update(ToComponent); + ToInputObjects[InputObjectIndex] = ToInputObject; + } + + for (UHoudiniInputObject* StaleInputObject : StaleInputObjects) + { + if (!StaleInputObject) + continue; + StaleInputObject->InvalidateData(); + ToInput->RemoveHoudiniInputObject(StaleInputObject); + ToInput->MarkChanged(true); + // TODO: Find the corresponding SCS node and remove it + } +} + +#endif + +#if WITH_EDITOR +bool +UHoudiniAssetBlueprintComponent::HasOpenEditor() const +{ + if (IsTemplate()) + { + IAssetEditorInstance* EditorInstance = FindEditorInstance(); + + return EditorInstance != nullptr; + } + + return false; +} +#endif + +#if WITH_EDITOR +IAssetEditorInstance* +UHoudiniAssetBlueprintComponent::FindEditorInstance() const +{ + UClass* BPGC = Cast(GetOuter()); + if (!IsValid(BPGC)) + return nullptr; + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!IsValid(Blueprint)) + return nullptr; + if (!CachedAssetEditorSubsystem.IsValid()) + return nullptr; + + IAssetEditorInstance* EditorInstance = CachedAssetEditorSubsystem->FindEditorForAsset(Blueprint, false); + + return EditorInstance; +} +#endif + +#if WITH_EDITOR +AActor* +UHoudiniAssetBlueprintComponent::GetPreviewActor() const +{ + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + if (BlueprintEditor) + { + return BlueprintEditor->GetPreviewActor(); + } + return nullptr; +} +#endif + +UHoudiniAssetComponent* +UHoudiniAssetBlueprintComponent::GetCachedTemplate() const +{ + return CachedTemplateComponent.Get(); +} + +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateParameters() const +//{ +// return IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateInputs() const +//{ +// return !IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateOutputs() const +//{ +// return !IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanProcessOutputs() const +//{ +// return !IsTemplate(); +//} + +//bool +//UHoudiniAssetBlueprintComponent::CanInstantiateAsset() const +//{ +// // If this is a preview component, it should not trigger an asset instantiation. It should wait +// // for the BPGC template component to finish the cook, get the synced data and then translate. +// +// if (IsPreview()) +// return false; +// +// return true; +//} +// +//// Check whether the HAC can translate Houdini outputs at all +//bool +//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini() const +//{ +// // Template components can't translate Houdini output since they typically do not exist in a world. +// if (IsTemplate()) +// return false; +// // Preview components and normally instanced actors can translate Houdini outputs. +// return true; +//} +// +//// Check whether the HAC can translate a specific output type. +//bool +//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini(EHoudiniOutputType OutputType) const +//{ +// // Blueprint components have limited translation support, for now. +// if (OutputType == EHoudiniOutputType::Mesh) +// return true; +// +// return false; +//} +// +bool +UHoudiniAssetBlueprintComponent::CanDeleteHoudiniNodes() const +{ + return bCanDeleteHoudiniNodes; +} + +void +UHoudiniAssetBlueprintComponent::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; + + for (UHoudiniInput* Input : Inputs) + { + Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for (UHoudiniOutput* Output : Outputs) + { + Output->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +bool +UHoudiniAssetBlueprintComponent::IsValidComponent() const +{ + if (!Super::IsValidComponent()) + return false; + + if (IsTemplate()) + { + UObject* Outer = this->GetOuter(); + if (!IsValid(Outer)) + return false; + UBlueprintGeneratedClass* BPGC = Cast(Outer); + if (!BPGC) + return false; + // Ensure this component is still in the SCS + USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; + if (!SCS) + return false; + USCS_Node* SCSNode = FindSCSNodeForTemplateComponentInClassHierarchy(this); + if (!SCSNode) + return false; + /*UClass* OwnerClass = Outer->GetClass(); + if (!IsValid(OwnerClass)) + return false;*/ + /*UBlueprint* Blueprint = Cast(Outhe); + if (Blueprint) + { + + }*/ + } + +#if WITH_EDITOR + if (!IsTemplate()) + { + if (!GetOwner()) + { + // If it's not a template, it needs an owner! + return false; + } + + USimpleConstructionScript* SCS = GetSCS(); + if (SCS) + { + // We're dealing with a Blueprint related component. + AActor* PreviewActor = GetPreviewActor(); + AActor* OwningActor = GetOwner(); + if (!OwningActor) + return false; + if (OwningActor != PreviewActor) + { + return false; + } + } + + } + + if (IsPreview() && false) + { + USimpleConstructionScript* SCS = GetSCS(); + if (!SCS) + return false; // Preview components should have an SCS. + + // We want to specifically detect whether an editor component is still being previewed. We do this + // by checking whether the owning actor is still the active editor actor in the SCS. + AActor* PreviewActor = GetPreviewActor(); + if (!PreviewActor) + { + return false; + } + + // Ensure this component still belongs the to the current preview actor. + if (PreviewActor != GetOwner()) + { + return false; + } + + /* + AActor* EditorActor = SCS->GetComponentEditorActorInstance(); + if (GetOwner() != EditorActor) + { + return false; + } + */ + } +#endif + + return true; +} + +bool +UHoudiniAssetBlueprintComponent::IsInputTypeSupported(EHoudiniInputType InType) const +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + case EHoudiniInputType::Curve: + return true; + break; + default: + break; + } + return false; +} + +bool +UHoudiniAssetBlueprintComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const +{ + switch (InType) + { + case EHoudiniOutputType::Mesh: + case EHoudiniOutputType::Instancer: + return true; + break; + default: + break; + } + return false; +} + +bool +UHoudiniAssetBlueprintComponent::IsProxyStaticMeshEnabled() const +{ + // TODO: Investigate adding support for proxy meshes in BP + // Disabled for now + return false; +} + +//void +//UHoudiniAssetBlueprintComponent::BroadcastPreAssetCook() +//{ +// // ------------------------------------------------ +// // NOTE: This code will run on TEMPLATE components +// // ------------------------------------------------ +// +// // The HoudiniAsset is about to be recooked. This flag will indicate to +// // the transient components that output processing needs to be baked +// // back to the BP definition. +// bOutputsRequireUpdate = true; +// +// Super::BroadcastPreAssetCook(); +//} + +void +UHoudiniAssetBlueprintComponent::OnPrePreCook() +{ + check(IsPreview()); + + Super::OnPrePreCook(); + + // We need to allow deleting houdini nodes + SetCanDeleteHoudiniNodes(true); +} + +void +UHoudiniAssetBlueprintComponent::OnPostPreCook() +{ + check(IsPreview()); + + Super::OnPostPreCook(); + + // Ensure the houdini nodes can be deleted during the translation process. + SetCanDeleteHoudiniNodes(false); +} + +void +UHoudiniAssetBlueprintComponent::OnPreOutputProcessing() +{ + check(IsPreview()); + + Super::OnPreOutputProcessing(); + + // Ensure the houdini nodes can be deleted during the translation process. + SetCanDeleteHoudiniNodes(true); +} + +void +UHoudiniAssetBlueprintComponent::OnPostOutputProcessing() +{ + Super::OnPostOutputProcessing(); + + // ------------------------------------------------ + // NOTE: + // In Blueprint editor mode, this code will run on PREVIEW components + // In Map editor mode, this code will run on component instances. + // ------------------------------------------------ + if (IsPreview()) + { + // Ensure all the inputs / outputs belonging to the + // preview actor won't be deleted by PreviewActor destruction. + SetCanDeleteHoudiniNodes(false); + +#if WITH_EDITOR + CopyStateToTemplateComponent(); +#endif + + } + bUpdatedFromTemplate = false; +} + +void UHoudiniAssetBlueprintComponent::OnPrePreInstantiation() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnPrePreInstantiation] Component: %s"), *(GetPathName())); + + check(IsPreview()); + + if (bUpdatedFromTemplate) + return; + + check(CachedTemplateComponent.IsValid()); + + // This HDA is about to be cooked but not through template parameter changes. It is likely that an input changed directly in the preview world. + // We need to flag our inputs and parameters appropriately in order to preserve their values. + + // We need to mark all our parameters as changed/not triggering update + for (auto CurrentParam : Parameters) + { + if (CurrentParam) + { + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(false); + } + } + + // We need to mark all our inputs as changed/not triggering update + for (auto CurrentInput : Inputs) + { + if (CurrentInput) + { + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(false); + CurrentInput->MarkDataUploadNeeded(true); + } + } +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniRegisterCompleted() +{ + if (IsTemplate()) + { + // TODO: Do we need to set any status flags or clear stuff to ensure + // the BP HAC will cook properly when the BP is opened again... + + // If the template is being registered, we need to invalidate the AssetId here since it likely + // contains a stale asset id from its last cook. + AssetId = -1; + // Template component's have very limited update requirements / capabilities. + // Mostly just cache parameters and cook state. + AssetState = EHoudiniAssetState::ProcessTemplate; + } + + Super::NotifyHoudiniRegisterCompleted(); +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniPreUnregister() +{ + if (IsTemplate()) + { + // Templates can delete Houdini nodes when they get deregistered. + SetCanDeleteHoudiniNodes(true); + } + Super::NotifyHoudiniPreUnregister(); +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniPostUnregister() +{ + InvalidateData(); + + Super::NotifyHoudiniPostUnregister(); + + if (IsTemplate()) + { + SetCanDeleteHoudiniNodes(false); + } +} + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::OnComponentCreated() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnComponentCreated] Component: %s"), *(GetPathName())); + + Super::OnComponentCreated(); + bUpdatedFromTemplate = false; + + CachePreviewState(); + + if (IsPreview()) + { + // Don't set an initial AssetState here. Preview components should only cook when template's + // Houdini Asset or HDA parameters have changed. + + // Clear these to ensure that we're not sharing references with the component template (otherwise + // the shared objects will get deleted when the component instance gets destroyed). + // These objects will be properly duplicated when copying state from the component template. + Inputs.Empty(); + Parameters.Empty(); + } + + // Wait until InitializeComponent() for blueprint construction to complete before we start caching blueprint data. + +} +#endif + + +void +UHoudiniAssetBlueprintComponent::OnRegister() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnRegister] Component: %s"), *(GetPathName())); + + Super::OnRegister(); + + // We run our Blueprint caching functions here since this the last hook that we have before + // entering HoudiniEngineTick(); + + CacheBlueprintData(); + CachePreviewState(); + + if (IsPreview()) + { + check(CachedTemplateComponent.Get()); + // Ensure that the component template has been registered since it needs to be processed for parameter updates by the HE manager. + if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(CachedTemplateComponent.Get())) + { + // The template component has not been registered yet, which means that we're probably busy opening a Blueprint editor and this + // preview component will need to be updated. + + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(CachedTemplateComponent.Get(), true); + CachedTemplateComponent->SetCanDeleteHoudiniNodes(false); + // Since we're likely opening a fresh blueprint editor, we'll need to instantiate the HDA. + bHasRegisteredComponentTemplate = true; + } + } + + if (IsTemplate()) + { + // We're initializing the asset id for HAC template here since it doesn't get unloaded + // from memory, for example, between Blueprint Editor open/close so we need to make sure + // that the AssetId has indeed been reset between registrations. + AssetId = -1; + } + + //TickInitialization(); +} + +void +UHoudiniAssetBlueprintComponent::BeginDestroy() +{ + Super::BeginDestroy(); +} + +void +UHoudiniAssetBlueprintComponent::DestroyComponent(bool bPromoteChildren) +{ + //FDebug::DumpStackTraceToLog(); + if (CachedTemplateComponent.IsValid() && TemplatePropertiesChangedHandle.IsValid()) + { + CachedTemplateComponent->Modify(); + CachedTemplateComponent->OnParametersChangedEvent.Remove(TemplatePropertiesChangedHandle); + } + Super::DestroyComponent(bPromoteChildren); +} + +void +UHoudiniAssetBlueprintComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); +} + +TStructOnScope +UHoudiniAssetBlueprintComponent::GetComponentInstanceData() const +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::GetComponentInstanceData] Component: %s"), *(GetPathName())); + + TStructOnScope ComponentInstanceData = MakeStructOnScope(this); + FHoudiniAssetBlueprintInstanceData* InstanceData = ComponentInstanceData.Cast(); + + InstanceData->AssetId = AssetId; + InstanceData->AssetState = AssetState; + InstanceData->SubAssetIndex = SubAssetIndex; + InstanceData->ComponentGUID = ComponentGUID; + InstanceData->HapiGUID = HapiGUID; + InstanceData->HoudiniAsset = HoudiniAsset; + InstanceData->SourceName = GetPathName(); + InstanceData->AssetCookCount = AssetCookCount; + InstanceData->bHasBeenLoaded = bHasBeenLoaded; + InstanceData->bHasBeenDuplicated = bHasBeenDuplicated; + InstanceData->bPendingDelete = bPendingDelete; + InstanceData->bRecookRequested = bRecookRequested; + InstanceData->bEnableCooking = bEnableCooking; + InstanceData->bForceNeedUpdate = bForceNeedUpdate; + InstanceData->bLastCookSuccess = bLastCookSuccess; + InstanceData->bRegisteredComponentTemplate = bHasRegisteredComponentTemplate; + + InstanceData->Inputs.Empty(); + + for (UHoudiniInput* Input : Inputs) + { + if (!Input) + continue; + UHoudiniInput* TransientInput = Input->DuplicateAndCopyState(GetTransientPackage(), false); + InstanceData->Inputs.Add(TransientInput); + } + + // Cache the current outputs + InstanceData->Outputs.Empty(); + int OutputIndex = 0; + for(UHoudiniOutput* Output : Outputs) + { + if (!Output) + continue; + + TMap OutputObjects = Output->GetOutputObjects(); + for (auto& Entry : OutputObjects) + { + FHoudiniAssetBlueprintOutput OutputObjectData; + OutputObjectData.OutputIndex = OutputIndex; + OutputObjectData.OutputObject = Entry.Value; + InstanceData->Outputs.Add(Entry.Key, OutputObjectData); + } + + ++OutputIndex; + } + + return ComponentInstanceData; + +} + +void +UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData(FHoudiniAssetBlueprintInstanceData* InstanceData, const bool bPostUCS) +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData] Component: %s"), *(GetPathName())); + check(InstanceData); + + if (!bPostUCS) + { + // Initialize the component before the User Construction Script runs + USimpleConstructionScript* SCS = GetSCS(); + check(SCS); + + TArray StaleInputs(Inputs); + + // We need to update references contain in inputs / outputs to point to new reconstructed components. + const int32 NumInputs = InstanceData->Inputs.Num(); + Inputs.SetNum(NumInputs); + for (int i = 0; i < NumInputs; ++i) + { + UHoudiniInput* FromInput = InstanceData->Inputs[i]; + UHoudiniInput* ToInput = Inputs[i]; + + if (ToInput) + { + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + if (!bIsValid) + { + ToInput = nullptr; + } + } + + if (ToInput) + { + // Reuse input + StaleInputs.Remove(ToInput); + ToInput->CopyStateFrom(FromInput, true, false); + } + else + { + // Create new input + ToInput = FromInput->DuplicateAndCopyState(this, false); + } + +#if WITH_EDITOR + // We can't create missing SCS nodes here since we're likely already in the middle of a + // Blueprint reconstruction. We'll have to recreate missing SCS nodes next time the + // component state if copied to the template. + UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, true, false); +#endif + + Inputs[i] = ToInput; + } + + // We need to update FHoudiniOutputObject SceneComponent references to + // the newly created components. Since we cached a map of Output Object IDs to + // SCSNodes (during CopyStateToTemplateComponent), we can the SCSNode that corresponds to this output objects and find + // the SceneComponent that matches the SCSNode's variable name. + // It is important to note that it is safe to do it this way since we're in the pre-UCS + // phase so that current components should match the SCS graph exactly (no user construction script + // interference here yet). + + for (auto& Entry : InstanceData->Outputs) + { + FHoudiniOutputObjectIdentifier& ObjectId = Entry.Key; + FHoudiniAssetBlueprintOutput& OutputData = Entry.Value; + + // NOTE: Output objects are going to be empty here since they dissapear during actor reconstruction. + // We'll need to repopulate from the instance data. + + check(Outputs.IsValidIndex(OutputData.OutputIndex)); + UHoudiniOutput* Output = Outputs[OutputData.OutputIndex]; + check(Output); + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject NewObject = OutputData.OutputObject; + + if (OutputData.OutputObject.OutputComponent) + { + // Update the output component reference. + check(CachedOutputNodes.Contains(ObjectId)) + const FGuid VariableGuid = CachedOutputNodes.FindChecked(ObjectId); + USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(VariableGuid); + + if (SCSNode) + { + // Find the component that corresponds to the SCS node. + USceneComponent* SceneComponent = FindActorComponentByName(GetOwner(), SCSNode->GetVariableName()); + NewObject.OutputComponent = SceneComponent; + } + else + { + NewObject.OutputComponent = nullptr; + } + } + + OutputObjects.Add(ObjectId, NewObject); + } + + if (CachedTemplateComponent.IsValid()) + { +#if WITH_EDITOR + CopyStateFromTemplateComponent( CachedTemplateComponent.Get(), false, false, true); +#endif + } + + AssetId = InstanceData->AssetId; + SubAssetIndex = InstanceData->SubAssetIndex; + ComponentGUID = InstanceData->ComponentGUID; + HapiGUID = InstanceData->HapiGUID; + + // Apply the previous HoudiniAsset to the component + // so that we can compare it against the template during CopyStateFromTemplate() calls to see whether it changed. + HoudiniAsset = InstanceData->HoudiniAsset; + + AssetCookCount = InstanceData->AssetCookCount; + bHasBeenLoaded = InstanceData->bHasBeenLoaded; + bHasBeenDuplicated = InstanceData->bHasBeenDuplicated; + bPendingDelete = InstanceData->bPendingDelete; + bRecookRequested = InstanceData->bRecookRequested; + bEnableCooking = InstanceData->bEnableCooking; + bForceNeedUpdate = InstanceData->bForceNeedUpdate; + bLastCookSuccess = InstanceData->bLastCookSuccess; + bHasRegisteredComponentTemplate = InstanceData->bRegisteredComponentTemplate; + + AssetState = InstanceData->AssetState; + + SetCanDeleteHoudiniNodes(false); + + } // if (!bPostUCS) + /* + else + { + // PostUCS + + } + */ +} + + +void +UHoudiniAssetBlueprintComponent::HoudiniEngineTick() +{ + if (!IsFullyLoaded()) + { + USimpleConstructionScript* SCS = GetSCS(); + if (SCS == nullptr) + { + OnFullyLoaded(); + } + else if (IsPreview()) + { + AActor* OwningActor = GetOwner(); + check(OwningActor); + + // If this is a *preview component*, it is important to wait for the template component to be fully loaded + // since it needs to be initialized so that the component instance can copy initial values from the template. + check(CachedTemplateComponent.Get()); + + if (CachedTemplateComponent->IsFullyLoaded()) + { +#if WITH_EDITOR + if(SCS->IsConstructingEditorComponents()) + { + // We're stuck in an editor blueprint construction / preview actor update. Wait some more. + } + else + { + OnFullyLoaded(); + } +#else + OnFullyLoaded(); +#endif + } + } + else + { + // Anything else can go onto being fully loaded at this point. + OnFullyLoaded(); + } + } +} + +void +UHoudiniAssetBlueprintComponent::OnFullyLoaded() +{ + Super::OnFullyLoaded(); + + // Check whether this component is inside a Blueprint editor. If this object lives outside the blueprint editor (in , then we need to ensure that this + // component won't be influencing the Blueprint asset. + + if (!IsTemplate()) + { +#if WITH_EDITOR + AActor* PreviewActor = GetPreviewActor(); +#else + AActor* PreviewActor = nullptr; +#endif + AActor* OwningActor = GetOwner(); + + if (!PreviewActor) + { + bIsInBlueprintEditor = false; + AssetState = EHoudiniAssetState::None; + return; + } + + if (OwningActor && PreviewActor != OwningActor) + { + bIsInBlueprintEditor = false; + AssetState = EHoudiniAssetState::None; + return; + } + } + + bIsInBlueprintEditor = true; + + CachePreviewState(); + CacheBlueprintData(); + + /* + for (UHoudiniOutput* Output : Outputs) + { + if (!Output) + continue; + } + */ + + if (IsTemplate()) + { + AssetId = -1; + AssetState = EHoudiniAssetState::ProcessTemplate; + } + + if (IsPreview()) + { + check(CachedTemplateComponent.Get()); + + // If this is a preview actor, sync initial settings from the component template +#if WITH_EDITOR + CopyStateFromTemplateComponent(CachedTemplateComponent.Get(), false, false, true); +#endif + + TemplatePropertiesChangedHandle = CachedTemplateComponent->OnParametersChangedEvent.AddUObject(this, &UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler); + if (bHoudiniAssetChanged) + { + + // The HoudiniAsset has changed, so we need to force the PreviewInstance to re-instantiate + AssetState = EHoudiniAssetState::NeedInstantiation; + bForceNeedUpdate = true; + bHoudiniAssetChanged = false; + // TODO: Make this better? + CachedTemplateComponent->bHoudiniAssetChanged = false; + } + + if (bHasRegisteredComponentTemplate) + { + // We have a newly registered component template. One of two things happened to cause this: + // 1. A new HoudiniAssetBlueprintComponent was created and registered. + // 2. The Blueprint Editor was closed / opened. + // The problem that arises in the #2 is that the template component was never fully unloaded + // from memory (it was deregistered but not destroyed). After deregistration we had the + // opportunity to invalidate asset/node ids but now that it has reregistered (without going + // through the normal initialization process) we will have to force a call to MarkAsNeedInstantiation + // during the next OnTemplateParametersChangedHandler() invocation. + bHasBeenLoaded = true; + } + } +} + +void +UHoudiniAssetBlueprintComponent::OnTemplateParametersChanged() +{ + OnParametersChangedEvent.Broadcast(this); +} + +void UHoudiniAssetBlueprintComponent::OnBlueprintStructureModified() +{ + check(IsTemplate()); + bBlueprintStructureModified = false; + +#if WITH_EDITOR + if (IsTemplate()) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(this); + } + else + { + check(CachedTemplateComponent.IsValid()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); + } +#endif +} + +void UHoudiniAssetBlueprintComponent::OnBlueprintModified() +{ + check(IsTemplate()); + bBlueprintModified = false; +#if WITH_EDITOR + FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(this); +#endif +} + +void +UHoudiniAssetBlueprintComponent::OnHoudiniAssetChanged() +{ + if (IsTemplate()) + { + // Invalidate data associated with this component since we're about to change and reinstantiate the Houdini Asset. + SetCanDeleteHoudiniNodes(true); + InvalidateData(); + SetCanDeleteHoudiniNodes(false); + Parameters.Empty(); + Inputs.Empty(); + } + + Super::OnHoudiniAssetChanged(); + + // Set on template components, then copied to preview components, and + // then used (and reset) during OnFullyLoaded. + bHoudiniAssetChanged = true; +} + +void +UHoudiniAssetBlueprintComponent::RegisterHoudiniComponent(UHoudiniAssetComponent *InComponent) +{ + // We only want to register this component if it is the preview actor for the Blueprint editor. +#if WITH_EDITOR + AActor* PreviewActor = GetPreviewActor(); +#else + AActor* PreviewActor = nullptr; +#endif + AActor* OwningActor = GetOwner(); + if (!OwningActor) + return; + + if (PreviewActor != OwningActor) + return; + + Super::RegisterHoudiniComponent(InComponent); +} + + + + +//bool UHoudiniAssetBlueprintComponent::TickInitialization() +//{ +// return true; +// +// if (IsFullyLoaded()) +// return true; +// +// bool bHasFinishedLoading = Super::TickInitialization(); +// +// if (!bHasFinishedLoading) +// return false; +// +// if (!IsTemplate()) +// { +// +// if (CachedTemplateComponent.Get()) +// { +// // Now that that SCS has finished constructing editor components, we can continue. +// // Copy the current state from the template component, in case there is something that can be processed. +// CopyStateFromTemplateComponent(CachedTemplateComponent.Get()); +// } +// +// AssetStateResult = EHoudiniAssetStateResult::None; +// AssetState = EHoudiniAssetState::None; +// +// bForceNeedUpdate = true; +// AssetState = EHoudiniAssetState::PostCook; +// } +// +// return true; +//} + +template +inline void +UHoudiniAssetBlueprintComponent::SetTypedValueAt(const FString& Name, ValueT& Value, int Index) +{ + ParamT* Parameter = Cast(FindParameterByName(Name)); + if (!Parameter) + return; + + Parameter->SetValueAt(Value, Index); +} + +bool +UHoudiniAssetBlueprintComponent::HasParameter(FString Name) +{ + return FindParameterByName(Name) != nullptr; +} + +void +UHoudiniAssetBlueprintComponent::SetFloatParameter(FString Name, float Value, int Index) +{ + SetTypedValueAt(Name, Value, Index); +} + +void +UHoudiniAssetBlueprintComponent::SetToggleValueAt(FString Name, bool Value, int Index) +{ + UHoudiniParameterToggle* Parameter = Cast(FindParameterByName(Name)); + if (!Parameter) + return; + + Parameter->SetValueAt(Value, Index); +} + +//void UHoudiniAssetBlueprintComponent::OnPostCookHandler(UHoudiniAssetComponent* InComponent) +//{ +// +// // Before this component handles any translation, we need to make sure that it still belongs to the editor actor. +// // When a blueprint gets recompiled, the editor actor gets replaced with a new one but the old actor has not yet +// // been ftroyed / garbage collected so its components still receive cook events from the template. +// +// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponent); +// if (!IsValid(ComponentTemplate)) +// return; +// +// CopyStateFromTemplateComponent(ComponentTemplate); +// bForceNeedUpdate = true; +//} + +void +UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler(UHoudiniAssetComponent* InComponentTemplate) +{ + if (!(AssetState == EHoudiniAssetState::None || AssetState == EHoudiniAssetState::NeedInstantiation || AssetState == EHoudiniAssetState::NeedRebuild)) + // Don't process parameter changes since we're already cooking -- it is going to break things badly if we do. + return; + + if (!IsValidComponent()) + return; + + UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); + if (!ComponentTemplate) + return; + + // The component instance needs to copy values from the template. + bool bBlueprintStructureChanged = false; +#if WITH_EDITOR + CopyDetailsFromComponent(ComponentTemplate, + false, + false, + true, + false, + true, + bBlueprintStructureChanged, + RF_Public, + RF_ClassDefaultObject|RF_ArchetypeObject); +#endif + + SetCanDeleteHoudiniNodes(false); + + if (bHasRegisteredComponentTemplate) + { + // NOTE: It is very important to call this *after* CopyDetailsFromComponent(), since CopyDetailsFromComponent + // will clobber the inputs and parameter states on this component. + + // If we already have a valid asset id, keep it. + if (AssetId >= 0) + { + MarkAsNeedCook(); + } + else + { + MarkAsNeedInstantiation(); + } + + bHasRegisteredComponentTemplate = false; + bFullyLoaded = true; // MarkAsNeedInstantiation sets this to false. Force to true. + // While MarkAsNeedInstantiation() sets ParametersChanged to true, it does not + // set the 'NeedToTriggerUpdate' flag (both of which needs to be true in order + // to trigger an HDA update) so we are going to force NeedUpdate() to return true + // in order to get an initial cook. + bForceNeedUpdate = true; + } + + bUpdatedFromTemplate = true; +} + +void +UHoudiniAssetBlueprintComponent::InvalidateData() +{ + if (IsTemplate()) + { + // Ensure transient properties are invalidated/released for parameters, inputs and outputs as if the + // the object was undergoing destruction since the template component will likely be reregistered + // without being destroyed. + for(UHoudiniParameter* Param : Parameters) + { + Param->InvalidateData(); + } + + for(UHoudiniInput* Input : Inputs) + { + Input->InvalidateData(); + } + + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + AssetId = -1; + } +} + +//void UHoudiniAssetBlueprintComponent::OnTemplateHoudiniAssetChangedHandler(UHoudiniAssetComponent* InComponentTemplate) +//{ +// +// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); +// if (!ComponentTemplate) +// return; +// check(IsPreview()); +// +// // The Houdini Asset was changed on the template. We need to recook. +// AssetState = EHoudiniAssetState::NeedInstantiation; +// +//} + +USceneComponent* +UHoudiniAssetBlueprintComponent::FindOwnerComponentByName(FName ComponentName) const +{ + AActor* Owner = GetOwner(); + if (!Owner) + return nullptr; + + return FindActorComponentByName(Owner, ComponentName); +} + +USceneComponent* +UHoudiniAssetBlueprintComponent::FindActorComponentByName(AActor* InActor, FName ComponentName) const +{ + const TSet& Components = InActor->GetComponents(); + + for (UActorComponent* Component : Components) + { + USceneComponent* SceneComponent = Cast(Component); + if (!IsValid(SceneComponent)) + continue; + if (FName(SceneComponent->GetName()) == ComponentName) + return SceneComponent; + } + + return nullptr; +} + +bool UHoudiniAssetBlueprintComponent::GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid) +{ + FGuid* SCSGuid = CachedInputNodes.Find(InputGuid); + if (!SCSGuid) + return false; + OutSCSGuid = *SCSGuid; + return true; +} + +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const +{ + const TArray& AllNodes = SCS->GetAllNodes(); + + for (USCS_Node* Node : AllNodes) + { + if (!Node) + continue; + + if (Node->ComponentTemplate == InComponent) + return Node; + } + + return nullptr; +} + +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponentInClassHierarchy( + const UActorComponent* InComponent) const +{ + UObject* Outer = this->GetOuter(); + if (!IsValid(Outer)) + return nullptr; + UBlueprintGeneratedClass* MainBPGC; + if (IsTemplate()) + { + MainBPGC = Cast(Outer); + } + else + { + AActor* OwningActor = GetOwner(); + MainBPGC = Cast(OwningActor->GetClass()); + } + + check(MainBPGC); + TArray BPGCStack; + UBlueprintGeneratedClass::GetGeneratedClassesHierarchy(MainBPGC, BPGCStack); + for(const UBlueprintGeneratedClass* BPGC : BPGCStack) + { + USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; + if (!SCS) + return nullptr; + USCS_Node* SCSNode = FindSCSNodeForTemplateComponent(SCS, InComponent); + SCSNode = SCS->FindSCSNode(InComponent->GetFName()); + if (SCSNode) + return SCSNode; + } + + return nullptr; +} + +#if WITH_EDITOR +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const +{ + const TArray& AllNodes = SCS->GetAllNodes(); + + if (!InComponent) + return nullptr; + + for (USCS_Node* Node : AllNodes) + { + if (!Node) + continue; + if (Node->EditorComponentInstance.Get() == InComponent) + return Node; + } + + return nullptr; +} +#endif + +UActorComponent* +UHoudiniAssetBlueprintComponent::FindComponentInstanceInActor(const AActor* InActor, + USCS_Node* SCSNode) const +{ + UActorComponent* ComponentTemplate = SCSNode->ComponentTemplate; + + UActorComponent* ComponentInstance = NULL; + if (InActor != NULL) + { + if (SCSNode != NULL) + { + FName VariableName = SCSNode->GetVariableName(); + if (VariableName != NAME_None) + { + UWorld* World = InActor->GetWorld(); + FObjectPropertyBase* Property = FindFProperty(InActor->GetClass(), VariableName); + if (Property != NULL) + { + // Return the component instance that's stored in the property with the given variable name + ComponentInstance = Cast(Property->GetObjectPropertyValue_InContainer(InActor)); + } + else if (World != nullptr && World->WorldType == EWorldType::EditorPreview) + { + // If this is the preview actor, return the cached component instance that's being used for the pmnaview actor prior to recompiling the Blueprint +#if WITH_EDITOR + ComponentInstance = SCSNode->EditorComponentInstance.Get(); +#endif + } + } + } + else if (ComponentTemplate != NULL) + { +#if WITH_EDITOR + TInlineComponentArray Components; + InActor->GetComponents(Components); + ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components); +#endif + } + } + + return ComponentInstance; +} + + +//void UHoudiniAssetBlueprintComponent::OnOutputProcessingCompletedHandler(UHoudiniAssetComponent* InComponent) +//{ +// +// UHoudiniAssetBlueprintComponent* TemplateComponent = Cast(InComponent); +// if (!IsValid(TemplateComponent)) +// return; +// +// CopyStateFromComponent(TemplateComponent); +// bForceNeedUpdate = true; +//} + +//#if WITH_EDITOR +//void UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason) +//{ +// +// if (CachedBlueprint.Get()) +// { +// } +// +// if (Asset) +// { +// +// } +// +//} +//#endif + +void +UHoudiniAssetBlueprintComponent::CachePreviewState() +{ + bCachedIsPreview = false; + +#if WITH_EDITOR + AActor* ComponentOwner = GetOwner(); + if (!IsValid(ComponentOwner)) + return; + + USimpleConstructionScript* SCS = GetSCS(); + if (SCS == nullptr) + return; + + // Get the preview actor directly from the BlueprintEditor. + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + if (BlueprintEditor) + { + AActor* PreviewActor = BlueprintEditor->GetPreviewActor(); + if (PreviewActor == ComponentOwner) + { + bCachedIsPreview = true; + return; + } + } +#endif +} + +void +UHoudiniAssetBlueprintComponent::CacheBlueprintData() +{ + CachedBlueprint = nullptr; + CachedActorCDO = nullptr; + CachedTemplateComponent = IsTemplate() ? this : nullptr; + +#if WITH_EDITOR + CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); +#endif + + UBlueprintGeneratedClass* BPGC = Cast(GetOuter()); + if (BPGC) + { + // Dealing with a component template + CachedBlueprint = Cast(BPGC->ClassGeneratedBy); + } + else + { + // Dealing with a component instance. + CachedBlueprint = Cast(GetOuter()->GetClass()->ClassGeneratedBy); + } + + if (CreationMethod != EComponentCreationMethod::SimpleConstructionScript) + return; + + AActor* ComponentOwner = this->GetOwner(); + if (!IsValid(ComponentOwner)) + return; + UClass* OwnerClass = ComponentOwner->GetClass(); + if (!IsValid(OwnerClass)) + return; + + if (!IsTemplate()) + { + // NOTE: The following code allows us to find the component template from an instance. + CachedActorCDO = Cast< AActor >(CachedBlueprint->GeneratedClass->GetDefaultObject()); + if (!CachedActorCDO.IsValid() || (CachedActorCDO.Get() == ComponentOwner)) + return; +#if WITH_EDITOR + UActorComponent* TargetComponent = EditorUtilities::FindMatchingComponentInstance(this, CachedActorCDO.Get()); + CachedTemplateComponent = Cast(TargetComponent); +#endif + } + +} + +USimpleConstructionScript* +UHoudiniAssetBlueprintComponent::GetSCS() const +{ + if (!CachedBlueprint.Get()) + return nullptr; + + return CachedBlueprint->SimpleConstructionScript; +} + +//------------------------------------------------------------------------------------------------ +// FHoudiniAssetBlueprintInstanceData +//------------------------------------------------------------------------------------------------ + +FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData() + : HoudiniAsset(nullptr) + , AssetId(-1) + , AssetState(EHoudiniAssetState::None) + , SubAssetIndex(-1) + , AssetCookCount(0) + , bHasBeenLoaded(false) + , bHasBeenDuplicated(false) + , bPendingDelete(false) + , bRecookRequested(false) + , bRebuildRequested(false) + , bEnableCooking(true) + , bForceNeedUpdate(false) + , bLastCookSuccess(false) + , ComponentGUID(FGuid()) + , HapiGUID(FGuid()) + , bRegisteredComponentTemplate(false) + , SourceName() +{ + +} + +FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent) + : FActorComponentInstanceData(SourceComponent) +{ + +} + +void +FHoudiniAssetBlueprintInstanceData::AddReferencedObjects(FReferenceCollector & Collector) +{ + Super::AddReferencedObjects(Collector); + // TODO: Do we need to add references to output objects here? + // Any other references? + // What are the implications? +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h index a035a1345..194d2977c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h @@ -1,351 +1,351 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Delegates/IDelegateInstance.h" -#include "Engine/Blueprint.h" -#include "HoudiniAssetComponent.h" - -#if WITH_EDITOR - #include "Subsystems/AssetEditorSubsystem.h" -#endif - -#include "HoudiniAssetBlueprintComponent.generated.h" - -class USCS_Node; - -UCLASS(NotBlueprintType, Experimental, meta=(BlueprintSpawnableComponent, DisplayName="Houdini Asset")) -class HOUDINIENGINERUNTIME_API UHoudiniAssetBlueprintComponent : public UHoudiniAssetComponent -{ - GENERATED_BODY() - -public: - UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer); - -#if WITH_EDITOR - // Sync certain variables of this HoudiniAssetComponent to the blueprint generated class. - // This is typically used when the Blueprint definition is being edited and the HAC cook - // took place in a transient HoudiniAssetComponent. Certain properties needs to be copied - // from the transient component back to the Blueprint generated class in order to be retained - // as part of the Client MeetingBlueprint definition. - - void CopyStateToTemplateComponent(); - - void CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties); - - void CopyDetailsFromComponent( - UHoudiniAssetBlueprintComponent* FromComponent, - const bool bCreateSCSNodes, - const bool bClearChangedToInputs, - const bool bClearChangedFromInputs, - const bool bInCanDeleteHoudiniNodes, - const bool bCopyInputObjectComponentProperties, - bool &bOutBlueprintStructureChanged, - EObjectFlags SetFlags=RF_NoFlags, - EObjectFlags ClearFlags=RF_NoFlags); - - // Update references on ToInput by looking up component references on FromInput in the SCS graph, on locating the correct component for ToInput. - void UpdateInputObjectComponentReferences( - USimpleConstructionScript* SCS, - UHoudiniInput* FromInput, - UHoudiniInput* ToInput, - const bool bCopyInputObjectProperties, - const bool bCreateMissingSCSNodes=false, - USCS_Node* ParentSCSNode=nullptr, - bool* bOutSCSNodeCreated=nullptr); - - virtual bool HasOpenEditor() const override; - IAssetEditorInstance* FindEditorInstance() const; - AActor* GetPreviewActor() const; -#endif - - virtual UHoudiniAssetComponent* GetCachedTemplate() const override; - - //------------------------------------------------------------------------------------------------ - // Supported Features - //------------------------------------------------------------------------------------------------ - - // Some features may be unavaible depending on the context in which the Houdini Asset Component - // has been instantiated. - - virtual bool CanDeleteHoudiniNodes() const override; - - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - - virtual bool IsValidComponent() const override; - - virtual bool IsInputTypeSupported(EHoudiniInputType InType) const override; - virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const override; - - virtual bool IsProxyStaticMeshEnabled() const override; - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - //virtual void BroadcastPreAssetCook() override; - virtual void OnPrePreCook() override; - virtual void OnPostPreCook() override; - virtual void OnPreOutputProcessing() override; - virtual void OnPostOutputProcessing() override; - virtual void OnPrePreInstantiation() override; - virtual void NotifyHoudiniRegisterCompleted() override; - virtual void NotifyHoudiniPreUnregister() override; - virtual void NotifyHoudiniPostUnregister() override; - - //------------------------------------------------------------------------------------------------ - // UActorComponent overrides - //------------------------------------------------------------------------------------------------ -#if WITH_EDITOR - virtual void OnComponentCreated() override; -#endif - - virtual void OnRegister() override; - - virtual void BeginDestroy() override; - virtual void DestroyComponent(bool bPromoteChildren = false) override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - // Refer USplineComponent for a decent reference on how to use component instance data. - virtual TStructOnScope GetComponentInstanceData() const override; - void ApplyComponentInstanceData(struct FHoudiniAssetBlueprintInstanceData* ComponentInstanceData, const bool bPostUCS); - - //------------------------------------------------------------------------------------------------ - // UHoudiniAssetComponent overrides - //------------------------------------------------------------------------------------------------ - - FHoudiniAssetComponentEvent OnParametersChangedEvent; - FHoudiniAssetComponentEvent OnHoudiniAssetChangedEvent; - - virtual void HoudiniEngineTick() override; - virtual void OnFullyLoaded() override; - virtual void OnTemplateParametersChanged() override; - virtual void OnHoudiniAssetChanged() override; - virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) override; - - virtual void OnBlueprintStructureModified() override; - virtual void OnBlueprintModified() override; - - - //------------------------------------------------------------------------------------------------ - // Blueprint functions - //------------------------------------------------------------------------------------------------ - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - bool HasParameter(FString Name); - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - void SetFloatParameter(FString Name, float Value, int Index=0); - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - void SetToggleValueAt(FString Name, bool Value, int Index=0); - - void AddInputObjectMapping(const FGuid& InputGuid, const FGuid& SCSVariableGuid) { CachedInputNodes.Add(InputGuid, SCSVariableGuid); } - bool GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid); - void RemoveInputObjectSCSVariableGuid(const FGuid& InputGuid) { CachedInputNodes.Remove(InputGuid); }; - - USCS_Node* FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; - USCS_Node* FindSCSNodeForTemplateComponentInClassHierarchy(const UActorComponent* InComponent) const; -#if WITH_EDITOR - USCS_Node* FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; -#endif // WITH_EDITOR - UActorComponent* FindComponentInstanceInActor(const AActor* InActor, USCS_Node* SCSNode) const; - -protected: - - template - void SetTypedValueAt(const FString& Name, ValueT& Value, int Index=0); - - void OnTemplateParametersChangedHandler(UHoudiniAssetComponent* ComponentTemplate); - void InvalidateData(); - - USceneComponent* FindOwnerComponentByName(FName ComponentName) const; - USceneComponent* FindActorComponentByName(AActor * InActor, FName ComponentName) const; - - void CachePreviewState(); - void CacheBlueprintData(); - - USimpleConstructionScript* GetSCS() const; - - //// The output translation has finished. - //void OnOutputProcessingCompletedHandler(UHoudiniAssetComponent * InComponent); - -#if WITH_EDITOR - //void ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason); - TWeakObjectPtr CachedAssetEditorSubsystem; -#endif - - TWeakObjectPtr CachedBlueprint; - TWeakObjectPtr CachedActorCDO; - TWeakObjectPtr CachedTemplateComponent; - - /*UPROPERTY(DuplicateTransient) - bool bOutputsRequireUpdate;*/ - UPROPERTY() - bool FauxBPProperty; - - UPROPERTY() - bool bHoudiniAssetChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bUpdatedFromTemplate; - - UPROPERTY() - bool bIsInBlueprintEditor; - - UPROPERTY(Transient, DuplicateTransient) - bool bCanDeleteHoudiniNodes; - - UPROPERTY(Transient, DuplicateTransient) - bool bHasRegisteredComponentTemplate; - - FDelegateHandle TemplatePropertiesChangedHandle; - - // This is used to keep track of which SCS variable names correspond to which - // output objects. - // This seems like it will cause issues in the map. - UPROPERTY() - TMap CachedOutputNodes; - - // This is used to keep track of which (SCS) variable guids correspond to which - // input objects. - UPROPERTY() - TMap CachedInputNodes; -}; - - -///** Used to keep track of output data and mappings during reconstruction */ -USTRUCT() -struct FHoudiniAssetBlueprintOutput -{ - GENERATED_BODY() - - UPROPERTY() - int32 OutputIndex; - - UPROPERTY() - FHoudiniOutputObject OutputObject; - - FHoudiniAssetBlueprintOutput() - : OutputIndex(INDEX_NONE) - { - - } -}; - - -/** Used to store HoudiniAssetComponent data during BP reconstruction */ -USTRUCT() -struct FHoudiniAssetBlueprintInstanceData : public FActorComponentInstanceData -{ - GENERATED_BODY() -public: - FHoudiniAssetBlueprintInstanceData(); - FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent); - - virtual ~FHoudiniAssetBlueprintInstanceData() = default; - - /*virtual bool ContainsData() const override - { - return (HAC != nullptr) || Super::ContainsData(); - }*/ - - virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override - { - Super::ApplyToComponent(Component, CacheApplyPhase); - CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); - } - - virtual void AddReferencedObjects(FReferenceCollector& Collector) override; - - // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. - UPROPERTY() - UHoudiniAsset* HoudiniAsset; - - UPROPERTY() - int32 AssetId; - - UPROPERTY() - EHoudiniAssetState AssetState; - - // Subasset index - UPROPERTY() - uint32 SubAssetIndex; - - UPROPERTY() - uint32 AssetCookCount; - - UPROPERTY() - bool bHasBeenLoaded; - - UPROPERTY() - bool bHasBeenDuplicated; - - UPROPERTY() - bool bPendingDelete; - - UPROPERTY() - bool bRecookRequested; - - UPROPERTY() - bool bRebuildRequested; - - UPROPERTY() - bool bEnableCooking; - - UPROPERTY() - bool bForceNeedUpdate; - - UPROPERTY() - bool bLastCookSuccess; - - /*UPROPERTY(DuplicateTransient) - TSet DownstreamHoudiniAssets;*/ - - UPROPERTY() - FGuid ComponentGUID; - - UPROPERTY() - FGuid HapiGUID; - - UPROPERTY() - bool bRegisteredComponentTemplate; - - // Name of the component from which this - // data was copied. Used for debugging purposes. - UPROPERTY() - FString SourceName; - - UPROPERTY() - TMap Outputs; - - UPROPERTY() - TArray Inputs; -}; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Delegates/IDelegateInstance.h" +#include "Engine/Blueprint.h" +#include "HoudiniAssetComponent.h" + +#if WITH_EDITOR + #include "Subsystems/AssetEditorSubsystem.h" +#endif + +#include "HoudiniAssetBlueprintComponent.generated.h" + +class USCS_Node; + +UCLASS(NotBlueprintType, Experimental, meta=(BlueprintSpawnableComponent, DisplayName="Houdini Asset")) +class HOUDINIENGINERUNTIME_API UHoudiniAssetBlueprintComponent : public UHoudiniAssetComponent +{ + GENERATED_BODY() + +public: + UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer); + +#if WITH_EDITOR + // Sync certain variables of this HoudiniAssetComponent to the blueprint generated class. + // This is typically used when the Blueprint definition is being edited and the HAC cook + // took place in a transient HoudiniAssetComponent. Certain properties needs to be copied + // from the transient component back to the Blueprint generated class in order to be retained + // as part of the Client MeetingBlueprint definition. + + void CopyStateToTemplateComponent(); + + void CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties); + + void CopyDetailsFromComponent( + UHoudiniAssetBlueprintComponent* FromComponent, + const bool bCreateSCSNodes, + const bool bClearChangedToInputs, + const bool bClearChangedFromInputs, + const bool bInCanDeleteHoudiniNodes, + const bool bCopyInputObjectComponentProperties, + bool &bOutBlueprintStructureChanged, + EObjectFlags SetFlags=RF_NoFlags, + EObjectFlags ClearFlags=RF_NoFlags); + + // Update references on ToInput by looking up component references on FromInput in the SCS graph, on locating the correct component for ToInput. + void UpdateInputObjectComponentReferences( + USimpleConstructionScript* SCS, + UHoudiniInput* FromInput, + UHoudiniInput* ToInput, + const bool bCopyInputObjectProperties, + const bool bCreateMissingSCSNodes=false, + USCS_Node* ParentSCSNode=nullptr, + bool* bOutSCSNodeCreated=nullptr); + + virtual bool HasOpenEditor() const override; + IAssetEditorInstance* FindEditorInstance() const; + AActor* GetPreviewActor() const; +#endif + + virtual UHoudiniAssetComponent* GetCachedTemplate() const override; + + //------------------------------------------------------------------------------------------------ + // Supported Features + //------------------------------------------------------------------------------------------------ + + // Some features may be unavaible depending on the context in which the Houdini Asset Component + // has been instantiated. + + virtual bool CanDeleteHoudiniNodes() const override; + + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + + virtual bool IsValidComponent() const override; + + virtual bool IsInputTypeSupported(EHoudiniInputType InType) const override; + virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const override; + + virtual bool IsProxyStaticMeshEnabled() const override; + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + //virtual void BroadcastPreAssetCook() override; + virtual void OnPrePreCook() override; + virtual void OnPostPreCook() override; + virtual void OnPreOutputProcessing() override; + virtual void OnPostOutputProcessing() override; + virtual void OnPrePreInstantiation() override; + virtual void NotifyHoudiniRegisterCompleted() override; + virtual void NotifyHoudiniPreUnregister() override; + virtual void NotifyHoudiniPostUnregister() override; + + //------------------------------------------------------------------------------------------------ + // UActorComponent overrides + //------------------------------------------------------------------------------------------------ +#if WITH_EDITOR + virtual void OnComponentCreated() override; +#endif + + virtual void OnRegister() override; + + virtual void BeginDestroy() override; + virtual void DestroyComponent(bool bPromoteChildren = false) override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + // Refer USplineComponent for a decent reference on how to use component instance data. + virtual TStructOnScope GetComponentInstanceData() const override; + void ApplyComponentInstanceData(struct FHoudiniAssetBlueprintInstanceData* ComponentInstanceData, const bool bPostUCS); + + //------------------------------------------------------------------------------------------------ + // UHoudiniAssetComponent overrides + //------------------------------------------------------------------------------------------------ + + FHoudiniAssetComponentEvent OnParametersChangedEvent; + FHoudiniAssetComponentEvent OnHoudiniAssetChangedEvent; + + virtual void HoudiniEngineTick() override; + virtual void OnFullyLoaded() override; + virtual void OnTemplateParametersChanged() override; + virtual void OnHoudiniAssetChanged() override; + virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) override; + + virtual void OnBlueprintStructureModified() override; + virtual void OnBlueprintModified() override; + + + //------------------------------------------------------------------------------------------------ + // Blueprint functions + //------------------------------------------------------------------------------------------------ + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + bool HasParameter(FString Name); + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + void SetFloatParameter(FString Name, float Value, int Index=0); + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + void SetToggleValueAt(FString Name, bool Value, int Index=0); + + void AddInputObjectMapping(const FGuid& InputGuid, const FGuid& SCSVariableGuid) { CachedInputNodes.Add(InputGuid, SCSVariableGuid); } + bool GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid); + void RemoveInputObjectSCSVariableGuid(const FGuid& InputGuid) { CachedInputNodes.Remove(InputGuid); }; + + USCS_Node* FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; + USCS_Node* FindSCSNodeForTemplateComponentInClassHierarchy(const UActorComponent* InComponent) const; +#if WITH_EDITOR + USCS_Node* FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; +#endif // WITH_EDITOR + UActorComponent* FindComponentInstanceInActor(const AActor* InActor, USCS_Node* SCSNode) const; + +protected: + + template + void SetTypedValueAt(const FString& Name, ValueT& Value, int Index=0); + + void OnTemplateParametersChangedHandler(UHoudiniAssetComponent* ComponentTemplate); + void InvalidateData(); + + USceneComponent* FindOwnerComponentByName(FName ComponentName) const; + USceneComponent* FindActorComponentByName(AActor * InActor, FName ComponentName) const; + + void CachePreviewState(); + void CacheBlueprintData(); + + USimpleConstructionScript* GetSCS() const; + + //// The output translation has finished. + //void OnOutputProcessingCompletedHandler(UHoudiniAssetComponent * InComponent); + +#if WITH_EDITOR + //void ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason); + TWeakObjectPtr CachedAssetEditorSubsystem; +#endif + + TWeakObjectPtr CachedBlueprint; + TWeakObjectPtr CachedActorCDO; + TWeakObjectPtr CachedTemplateComponent; + + /*UPROPERTY(DuplicateTransient) + bool bOutputsRequireUpdate;*/ + UPROPERTY() + bool FauxBPProperty; + + UPROPERTY() + bool bHoudiniAssetChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bUpdatedFromTemplate; + + UPROPERTY() + bool bIsInBlueprintEditor; + + UPROPERTY(Transient, DuplicateTransient) + bool bCanDeleteHoudiniNodes; + + UPROPERTY(Transient, DuplicateTransient) + bool bHasRegisteredComponentTemplate; + + FDelegateHandle TemplatePropertiesChangedHandle; + + // This is used to keep track of which SCS variable names correspond to which + // output objects. + // This seems like it will cause issues in the map. + UPROPERTY() + TMap CachedOutputNodes; + + // This is used to keep track of which (SCS) variable guids correspond to which + // input objects. + UPROPERTY() + TMap CachedInputNodes; +}; + + +///** Used to keep track of output data and mappings during reconstruction */ +USTRUCT() +struct FHoudiniAssetBlueprintOutput +{ + GENERATED_BODY() + + UPROPERTY() + int32 OutputIndex; + + UPROPERTY() + FHoudiniOutputObject OutputObject; + + FHoudiniAssetBlueprintOutput() + : OutputIndex(INDEX_NONE) + { + + } +}; + + +/** Used to store HoudiniAssetComponent data during BP reconstruction */ +USTRUCT() +struct FHoudiniAssetBlueprintInstanceData : public FActorComponentInstanceData +{ + GENERATED_BODY() +public: + FHoudiniAssetBlueprintInstanceData(); + FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent); + + virtual ~FHoudiniAssetBlueprintInstanceData() = default; + + /*virtual bool ContainsData() const override + { + return (HAC != nullptr) || Super::ContainsData(); + }*/ + + virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override + { + Super::ApplyToComponent(Component, CacheApplyPhase); + CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); + } + + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + + // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. + UPROPERTY() + UHoudiniAsset* HoudiniAsset; + + UPROPERTY() + int32 AssetId; + + UPROPERTY() + EHoudiniAssetState AssetState; + + // Subasset index + UPROPERTY() + uint32 SubAssetIndex; + + UPROPERTY() + uint32 AssetCookCount; + + UPROPERTY() + bool bHasBeenLoaded; + + UPROPERTY() + bool bHasBeenDuplicated; + + UPROPERTY() + bool bPendingDelete; + + UPROPERTY() + bool bRecookRequested; + + UPROPERTY() + bool bRebuildRequested; + + UPROPERTY() + bool bEnableCooking; + + UPROPERTY() + bool bForceNeedUpdate; + + UPROPERTY() + bool bLastCookSuccess; + + /*UPROPERTY(DuplicateTransient) + TSet DownstreamHoudiniAssets;*/ + + UPROPERTY() + FGuid ComponentGUID; + + UPROPERTY() + FGuid HapiGUID; + + UPROPERTY() + bool bRegisteredComponentTemplate; + + // Name of the component from which this + // data was copied. Used for debugging purposes. + UPROPERTY() + FString SourceName; + + UPROPERTY() + TMap Outputs; + + UPROPERTY() + TArray Inputs; +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp index 6d1198be0..b4b57d8f6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp @@ -1,2317 +1,2317 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetComponent.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniHandleComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "Engine/StaticMesh.h" -#include "Components/StaticMeshComponent.h" -#include "TimerManager.h" -#include "Landscape.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -void -UHoudiniAssetComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - - // Legacy serialization - // Either try to convert or skip depending the setting value - if (bEnableBackwardCompatibility) - { - // Attemp to convert the v1 object to v2 - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - // Deserialize the legacy data, we'll do the actual conversion in PostLoad() - // After everything has been deserialized - Version1CompatibilityHAC = NewObject(this); - Version1CompatibilityHAC->Serialize(Ar); - } - else - { - // Skip the v1 object - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -bool -UHoudiniAssetComponent::ConvertLegacyData() -{ - if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill()) - return false; - - // Set the Houdini Asset - if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill()) - return false; - - HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset; - - // Convert all parameters - for (auto& LegacyParmPair : Version1CompatibilityHAC->Parameters) - { - if (!LegacyParmPair.Value) - continue; - - UHoudiniParameter* Parm = LegacyParmPair.Value->ConvertLegacyData(this); - LegacyParmPair.Value->CopyLegacyParameterData(Parm); - Parameters.Add(Parm); - } - - // Convert all inputs - for (auto& LegacyInput : Version1CompatibilityHAC->Inputs) - { - // Convert v1 input to v2 - UHoudiniInput* Input = LegacyInput->ConvertLegacyInput(this); - - Inputs.Add(Input); - } - - // Lambdas for finding/creating outputs from an HGPO - auto FindOrCreateOutput = [&](FHoudiniGeoPartObject& InNewHGPO, bool& bNew) - { - UHoudiniOutput* NewOutput = nullptr; - - // See if we can add to an existing output - UHoudiniOutput** FoundOutput = nullptr; - FoundOutput = Outputs.FindByPredicate( - [InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; }); - - if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill()) - { - // FoundOutput is valid, add to it - NewOutput = *FoundOutput; - bNew = false; - } - else - { - // Create a new output object - NewOutput = NewObject( - this, UHoudiniOutput::StaticClass(), NAME_None, RF_NoFlags); - bNew = true; - } - - return NewOutput; - }; - - - // Convert all outputs - // Start by handling the Static Meshes - for (auto& LegacySM : Version1CompatibilityHAC->StaticMeshes) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacySM.Key.ConvertLegacyData(); - - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(NewHGPO); - // Mark if the HoudiniOutput is editable - NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); - } - - // Build a new output object identifier - FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); - Identifier.bLoaded = true; - Identifier.PartName = NewHGPO.PartName; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); - OutputObj.OutputObject = LegacySM.Value; - OutputObj.OutputComponent = nullptr; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - // Handle the SMC for this SM / HGPO - if (LegacySM.Value && !LegacySM.Value->IsPendingKill()) - { - UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value); - if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill()) - OutputObj.OutputComponent = *FoundSMC; - } - - // Add to the outputs - Outputs.AddUnique(NewOutput); - - //NewOutput->StaleCount; - //NewOutput->bLandscapeWorldComposition; - //NewOutput->HoudiniCreatedSocketActors; - //NewOutput->HoudiniAttachedSocketActors; - //NewOutput->bHasEditableNodeBuilt - false; - } - - // ... then Landscapes - for (auto& LegacyLandscape : Version1CompatibilityHAC->LandscapeComponents) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacyLandscape.Key.ConvertLegacyData(); - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(NewHGPO); - // Mark if the HoudiniOutput is editable - NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); - } - - // Build a new output object identifier - FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); - Identifier.bLoaded = true; - Identifier.PartName = NewHGPO.PartName; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); - - // We need to create a LandscapePtr wrapper for the landscaope - UHoudiniLandscapePtr* LandscapePtr = NewObject(NewOutput); - LandscapePtr->SetSoftPtr(LegacyLandscape.Value.IsValid() ? LegacyLandscape.Value.Get() : nullptr); - - OutputObj.OutputObject = LandscapePtr; - OutputObj.OutputComponent = nullptr; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - // Add to the outputs - Outputs.AddUnique(NewOutput); - } - - // ... instancers - for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs) - { - if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill()) - continue; - - FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData(); - - // Prepare this output object's output identifier - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = InstancerHGPO.ObjectId; - OutputIdentifier.GeoId = InstancerHGPO.GeoId; - OutputIdentifier.PartId = InstancerHGPO.PartId; - OutputIdentifier.PartName = InstancerHGPO.PartName; - - EHoudiniInstancerType InstancerType = EHoudiniInstancerType::ObjectInstancer; - if (LegacyInstanceIn->Flags.bIsPackedPrimitiveInstancer) - InstancerType = EHoudiniInstancerType::PackedPrimitive; - else if (LegacyInstanceIn->Flags.bAttributeInstancerOverride) - InstancerType = EHoudiniInstancerType::AttributeInstancer; - else if (LegacyInstanceIn->Flags.bIsAttributeInstancer) - InstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - else if (LegacyInstanceIn->ObjectToInstanceId >= 0) - InstancerType = EHoudiniInstancerType::ObjectInstancer; - - InstancerHGPO.InstancerType = InstancerType; - - //bool bIsMSIC = LegacyInstanceIn->Flags.bIsSplitMeshInstancer; - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(InstancerHGPO); - } - - // Get the output's instanced outputs - TMap& InstancedOutputs = NewOutput->GetInstancedOutputs(); - - int32 InstFieldIdx = 0; - for (auto& LegacyInstanceInputField : LegacyInstanceIn->InstanceInputFields) - { - FHoudiniGeoPartObject InstInputFieldHGPO = LegacyInstanceInputField->HoudiniGeoPartObject.ConvertLegacyData(); - - // Create an instanced output for this object - FHoudiniInstancedOutput NewInstOut; - NewInstOut.OriginalObject = LegacyInstanceInputField->OriginalObject; - NewInstOut.OriginalObjectIndex = -1; - NewInstOut.OriginalTransforms = LegacyInstanceInputField->InstancedTransforms; - - for (auto& InstObj : LegacyInstanceInputField->InstancedObjects) - NewInstOut.VariationObjects.Add(InstObj); - - int32 NumVar = LegacyInstanceInputField->RotationOffsets.Num(); - for (int32 Idx = 0; Idx < NumVar; Idx++) - { - FTransform TransOffset; - TransOffset.SetLocation(FVector::ZeroVector); - if (LegacyInstanceInputField->RotationOffsets.IsValidIndex(Idx)) - TransOffset.SetRotation(LegacyInstanceInputField->RotationOffsets[Idx].Quaternion()); - - if (LegacyInstanceInputField->ScaleOffsets.IsValidIndex(Idx)) - TransOffset.SetScale3D(LegacyInstanceInputField->ScaleOffsets[Idx]); - - NewInstOut.VariationTransformOffsets.Add(TransOffset); - } - - // Build an identifier for the instance output - FHoudiniOutputObjectIdentifier Identifier; - Identifier.ObjectId = InstInputFieldHGPO.ObjectId; - Identifier.GeoId = InstInputFieldHGPO.GeoId; - Identifier.PartId = InstInputFieldHGPO.PartId; - Identifier.PartName = InstInputFieldHGPO.PartName; - Identifier.SplitIdentifier = FString::FromInt(InstFieldIdx); - Identifier.bLoaded = true; - - // Add the instance output to the outputs - InstancedOutputs.Add(Identifier, NewInstOut); - - // Now create an Output object for each variation - int32 VarIdx = 0; - for (auto& LegacyComp : LegacyInstanceInputField->InstancerComponents) - { - // Build a new output object identifier for this variation - FHoudiniOutputObjectIdentifier VarIdentifier; - VarIdentifier.ObjectId = InstInputFieldHGPO.ObjectId; - VarIdentifier.GeoId = InstInputFieldHGPO.GeoId; - VarIdentifier.PartId = InstInputFieldHGPO.PartId; - VarIdentifier.PartName = InstInputFieldHGPO.PartName; - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - VarIdentifier.SplitIdentifier = - FString::FromInt(InstFieldIdx) + TEXT("_") + FString::FromInt(VarIdx); - VarIdentifier.bLoaded = true; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(VarIdentifier); - - OutputObj.OutputObject = nullptr; - OutputObj.OutputComponent = LegacyComp; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - VarIdx++; - } - - // ??? - //LegacyInstanceInputField->VariationTransformsArray; - //LegacyInstanceInputField->InstanceColorOverride; - //LegacyInstanceInputField->VariationInstanceColorOverrideArray; - - // Index of the variation used for each transform - //NewInstOut.TransformVariationIndices; - //NewInstOut.bUniformScaleLocked = false; - - InstFieldIdx++; - } - - // Add to the outputs - Outputs.AddUnique(NewOutput); - } - - // ... then Spline Components (for Curve IN) - for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents) - { - UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value; - if (!CurSplineComp || CurSplineComp->IsPendingKill()) - continue; - - // TODO: Needed? - // Attach the spline to the HAC - CurSplineComp->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - - // Editable curve? / Should create an output for it! - if (CurSplineComp->IsEditableOutputCurve()) - { - FHoudiniGeoPartObject CurHGPO = LegacyCurve.Key.ConvertLegacyData(); - - // Look for an output for that HGPO - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(CurHGPO); - } - - // Build an output object id for the editable curve output - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; - EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; - EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; - EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; - - TMap& OutputObjects = NewOutput->GetOutputObjects(); - FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - FoundOutputObject.OutputComponent = CurSplineComp; - - //CurSplineComp->SetHasEditableNodeBuilt(true); - CurSplineComp->SetIsInputCurve(false); - } - else - { - // Input! - // Conversion of the inputs should have done the job already - CurSplineComp->SetIsInputCurve(true); - } - } - - // ... Handles - for (auto& LegacyHandle : Version1CompatibilityHAC->HandleComponents) - { - // TODO: Handles!! - UHoudiniHandleComponent* NewHandle = nullptr; - HandleComponents.Add(NewHandle); - } - - // ... Materials - UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials; - if(LegacyMaterials && !LegacyMaterials->IsPendingKill()) - { - // Assignements: Apply to all outputs since they're not tied to an HGPO... - for (auto& CurOutput : Outputs) - { - TMap& CurrAssign = CurOutput->GetAssignementMaterials(); - for (auto& LegacyMaterial : LegacyMaterials->Assignments) - { - CurrAssign.Add(LegacyMaterial.Key, LegacyMaterial.Value); - } - } - - // Replacements - // Try to find the output matching the HGPO - for (auto& LegacyMaterial : LegacyMaterials->Replacements) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacyMaterial.Key.ConvertLegacyData(); - - TMap& LegacyReplacement = LegacyMaterial.Value; - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - if (bCreatedNew) - continue; - - TMap& CurReplacement = NewOutput->GetReplacementMaterials(); - for (auto& CurLegacyReplacement : LegacyReplacement) - { - CurReplacement.Add(CurLegacyReplacement.Key, CurLegacyReplacement.Value); - } - } - } - - - // ... Bake Name overrides - for (auto& LegacyBakeNameOverride : Version1CompatibilityHAC->BakeNameOverrides) - { - // In Outputs? - } - - // ... then Downstream asset connections (due to Asset inputs) - for (auto& LegacyDownstreamHAC : Version1CompatibilityHAC->DownstreamAssetConnections) - { - //TSet DownstreamHoudiniAssets; - } - - // Then convert all remaing flags and properties - bGeneratedDoubleSidedGeometry = Version1CompatibilityHAC->bGeneratedDoubleSidedGeometry; - GeneratedPhysMaterial = Version1CompatibilityHAC->GeneratedPhysMaterial; - DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; - GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; - GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; - GeneratedLpvBiasMultiplier = Version1CompatibilityHAC->GeneratedLpvBiasMultiplier; - GeneratedDistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; - GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; - GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; - bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; - GeneratedStreamingDistanceMultiplier = Version1CompatibilityHAC->GeneratedStreamingDistanceMultiplier; - //GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; - GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; - - BakeFolder.Path = Version1CompatibilityHAC->BakeFolder.ToString(); - TemporaryCookFolder.Path = Version1CompatibilityHAC->TempCookFolder.ToString(); - - ComponentGUID = Version1CompatibilityHAC->ComponentGUID; - - bEnableCooking = Version1CompatibilityHAC->bEnableCooking; - bUploadTransformsToHoudiniEngine = Version1CompatibilityHAC->bUploadTransformsToHoudiniEngine; - bCookOnTransformChange = Version1CompatibilityHAC->bTransformChangeTriggersCooks; - bCookOnParameterChange = true; - //Version1CompatibilityHAC->bCookingTriggersDownstreamCooks; - bCookOnAssetInputCook = true; - bOutputless = false; - bOutputTemplateGeos = false; - bFullyLoaded = Version1CompatibilityHAC->bFullyLoaded; - - //bContainsHoudiniLogoGeometry = Version1CompatibilityHAC->bContainsHoudiniLogoGeometry; - //bIsNativeComponent = Version1CompatibilityHAC->bIsNativeComponent; - //bIsPreviewComponent = Version1CompatibilityHAC->bIsPreviewComponent; - //bLoadedComponent = Version1CompatibilityHAC->bLoadedComponent; - //bIsPlayModeActive_Unused = Version1CompatibilityHAC->bIsPlayModeActive_Unused; - //Version1CompatibilityHAC->bTimeCookInPlaymode_Unused; - //Version1CompatibilityHAC->bUseHoudiniMaterials; - - //Version1CompatibilityHAC->GeneratedGeometryScaleFactor; - //Version1CompatibilityHAC->TransformScaleFactor; - //Version1CompatibilityHAC->PresetBuffer; - //Version1CompatibilityHAC->DefaultPresetBuffer; - //Version1CompatibilityHAC->ParameterByName; - - // Now that we're done, update all the output's types - for (auto& CurOutput : Outputs) - { - CurOutput->UpdateOutputType(); - } - - // - // Clean up the legacy HAC - // - - Version1CompatibilityHAC->Parameters.Empty(); - Version1CompatibilityHAC->Inputs.Empty(); - Version1CompatibilityHAC->StaticMeshes.Empty(); - Version1CompatibilityHAC->LandscapeComponents.Empty(); - Version1CompatibilityHAC->InstanceInputs.Empty(); - Version1CompatibilityHAC->SplineComponents.Empty(); - Version1CompatibilityHAC->HandleComponents.Empty(); - //Version1CompatibilityHAC->HoudiniAssetComponentMaterials.Empty(); - Version1CompatibilityHAC->BakeNameOverrides.Empty(); - Version1CompatibilityHAC->DownstreamAssetConnections.Empty(); - Version1CompatibilityHAC->MarkPendingKill(); - Version1CompatibilityHAC = nullptr; - - return true; -} - - -UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - HoudiniAsset = nullptr; - bCookOnParameterChange = true; - bUploadTransformsToHoudiniEngine = true; - bCookOnTransformChange = false; - //bUseNativeHoudiniMaterials = true; - bCookOnAssetInputCook = true; - - AssetId = -1; - AssetState = EHoudiniAssetState::PreInstantiation; - AssetStateResult = EHoudiniAssetStateResult::None; - AssetCookCount = 0; - - SubAssetIndex = -1; - - // Make an invalid GUID, since we do not have any cooking requests. - HapiGUID.Invalidate(); - - // Create unique component GUID. - ComponentGUID = FGuid::NewGuid(); - - bUploadTransformsToHoudiniEngine = true; - - bHasBeenLoaded = false; - bHasBeenDuplicated = false; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = false; - bEnableCooking = true; - bForceNeedUpdate = false; - bLastCookSuccess = false; - bBlueprintStructureModified = false; - bBlueprintModified = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // Folder used for cooking, the value is initialized by Output Translator - // TemporaryCookFolder.Path = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - - // Folder used for baking this asset's outputs, the value is initialized by Output Translator - // BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - bHasComponentTransformChanged = false; - - bFullyLoaded = false; - - bOutputless = false; - - bOutputTemplateGeos = false; - - PDGAssetLink = nullptr; - - StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bEnableProxyStaticMeshOverride = HoudiniRuntimeSettings->bEnableProxyStaticMesh; - bEnableProxyStaticMeshRefinementByTimerOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; - ProxyMeshAutoRefineTimeoutSecondsOverride = HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; - bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; - bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; - } - - bNoProxyMeshNextCookRequested = false; - bBakeAfterNextCook = false; - -#if WITH_EDITORONLY_DATA - bGenerateMenuExpanded = true; - bBakeMenuExpanded = true; - bAssetOptionMenuExpanded = true; - bHelpAndDebugMenuExpanded = true; - - HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; - - bRemoveOutputAfterBake = false; - bRecenterBakedActors = false; - bReplacePreviousBake = false; -#endif - - // - // Set component properties. - // - - Mobility = EComponentMobility::Static; - - 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; - - Bounds = FBox(ForceInitToZero); -} - -UHoudiniAssetComponent::~UHoudiniAssetComponent() -{ - // Unregister ourself so our houdini node can be delete. - - // This gets called in UnRegisterHoudiniComponent, with appropriate checks. Don't call it here. - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); -} - -void UHoudiniAssetComponent::PostInitProperties() -{ - Super::PostInitProperties(); - - // Register ourself to the HER singleton - RegisterHoudiniComponent(this); -} - -UHoudiniAsset * -UHoudiniAssetComponent::GetHoudiniAsset() const -{ - return HoudiniAsset; -} - -FString -UHoudiniAssetComponent::GetDisplayName() const -{ - return GetOwner() ? GetOwner()->GetName() : GetName(); -} - -void -UHoudiniAssetComponent::GetOutputs(TArray& OutOutputs) const -{ - for (UHoudiniOutput* Output : Outputs) - { - OutOutputs.Add(Output); - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh; - } - else - { - return false; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementByTimerEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; - } - else - { - return false; - } - } -} - -float -UHoudiniAssetComponent::GetProxyMeshAutoRefineTimeoutSeconds() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return ProxyMeshAutoRefineTimeoutSecondsOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; - } - else - { - return 5.0f; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; - } - else - { - return false; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; - } - else - { - return false; - } - } -} - -void -UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) -{ - // Check the asset validity - if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill()) - return; - - // If it is the same asset, do nothing. - if ( InHoudiniAsset == HoudiniAsset ) - return; - - HoudiniAsset = InHoudiniAsset; -} - - -void -UHoudiniAssetComponent::OnHoudiniAssetChanged() -{ - // TODO: clear input/params/outputs? - Parameters.Empty(); - - // The asset has been changed, mark us as needing to be reinstantiated - MarkAsNeedInstantiation(); - - // Force an update on the next tick - bForceNeedUpdate = true; -} - -bool -UHoudiniAssetComponent::NeedUpdateParameters() const -{ - // This is being split into a separate function to that it can - // be called separately for component templates. - - if (!bCookOnParameterChange) - return false; - - // Go through all our parameters, return true if they have been updated - for (auto CurrentParm : Parameters) - { - if (!CurrentParm || CurrentParm->IsPendingKill()) - continue; - - if (!CurrentParm->HasChanged()) - continue; - - // See if the parameter doesn't require an update - // (because it has failed to upload previously or has been loaded) - if (!CurrentParm->NeedsToTriggerUpdate()) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateParameters()] Parameters need update for component: %s"), *(GetPathName())); - return true; - } - - return false; -} - -bool -UHoudiniAssetComponent::NeedUpdateInputs() const -{ - // Go through all our inputs, return true if they have been updated - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (!CurrentInput->HasChanged()) - continue; - - // See if the input doesn't require an update - // (because it has failed to upload previously or has been loaded) - if (!CurrentInput->NeedsToTriggerUpdate()) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateInputs()] Inputs need update for component: %s"), *(GetPathName())); - return true; - } - - return false; -} - -bool -UHoudiniAssetComponent::HasPreviousBakeOutput() const -{ - // Look for any bake output objects in the output array - for (const UHoudiniOutput* Output : Outputs) - { - if (!IsValid(Output)) - continue; - - if (BakedOutputs.Num() == 0) - return false; - - for (const FHoudiniBakedOutput& BakedOutput : BakedOutputs) - { - if (BakedOutput.BakedOutputObjects.Num() > 0) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::NeedUpdate() const -{ - if (AssetState != DebugLastAssetState) - { - DebugLastAssetState = AssetState; - } - - // It is important to check this when dealing with Blueprints since the - // preview components start receiving events from the template component - // before the preview component have finished initialization. - if (!IsFullyLoaded()) - return false; - - // We must have a valid asset - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return false; - - if (bForceNeedUpdate) - return true; - - // If we don't want to cook on parameter/input change dont bother looking for updates - if (!bCookOnParameterChange && !bRecookRequested && !bRebuildRequested) - return false; - - // Check if the HAC's transform has changed and transform triggers cook is enabled - if (bCookOnTransformChange && bHasComponentTransformChanged) - return true; - - if (NeedUpdateParameters()) - return true; - - if (NeedUpdateInputs()) - return true; - - // Go through all outputs, filter the editable nodes. Return true if they have been updated. - for (auto CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - // We only care about editable outputs - if (!CurrentOutput->IsEditableNode()) - continue; - - // Trigger an update if the output object is marked as modified by user. - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - for (auto& NextPair : OutputObjects) - { - // For now, only editable curves can trigger update - UHoudiniSplineComponent* HoudiniSplineComponent = Cast(NextPair.Value.OutputComponent); - if (!HoudiniSplineComponent) - continue; - - // Output curves cant trigger an update! - if (HoudiniSplineComponent->bIsOutputCurve) - continue; - - if (HoudiniSplineComponent->NeedsToTriggerUpdate()) - return true; - } - } - - return false; -} - -// Indicates if any of the HAC's output components needs to be updated (no recook needed) -bool -UHoudiniAssetComponent::NeedOutputUpdate() const -{ - // Go through all outputs - for (auto CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs()) - { - if (InstOutput.Value.bChanged) - return true; - } - } - - return false; -} - -bool UHoudiniAssetComponent::NeedBlueprintStructureUpdate() const -{ - // TODO: Add similar flags to inputs, parametsr - return bBlueprintStructureModified; -} - -bool UHoudiniAssetComponent::NeedBlueprintUpdate() const -{ - // TODO: Add similar flags to inputs, parametsr - return bBlueprintModified; -} - -bool -UHoudiniAssetComponent::NotifyCookedToDownstreamAssets() -{ - // Before notifying, clean up our downstream assets - // - check that they are still valid - // - check that we are still connected to one of its asset input - // - check that the asset as the CookOnAssetInputCook trigger enabled - TArray DownstreamToDelete; - for(auto& CurrentDownstreamHAC : DownstreamHoudiniAssets) - { - // Remove the downstream connection by default, - // unless we actually were properly connected to one of this HDa's input. - bool bRemoveDownstream = true; - if (CurrentDownstreamHAC && !CurrentDownstreamHAC->IsPendingKill()) - { - // Go through the HAC's input - for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs) - { - if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill()) - continue; - - EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType(); - if (CurrentDownstreamInputType != EHoudiniInputType::Asset - && CurrentDownstreamInputType != EHoudiniInputType::World) - continue; - - if (!CurrentDownstreamInput->ContainsInputObject(this, CurrentDownstreamInputType)) - continue; - - if (CurrentDownstreamHAC->bCookOnAssetInputCook) - { - // Mark that HAC's input has changed - CurrentDownstreamInput->MarkChanged(true); - } - bRemoveDownstream = false; - } - } - - if (bRemoveDownstream) - { - DownstreamToDelete.Add(CurrentDownstreamHAC); - } - } - - for (auto ToDelete : DownstreamToDelete) - { - DownstreamHoudiniAssets.Remove(ToDelete); - } - - return true; -} - -bool -UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() -{ - for (auto& CurrentInput : Inputs) - { - EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World) - continue; - - TArray* ObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInputType); - if (!ObjectArray) - continue; - - for (auto& CurrentInputObject : (*ObjectArray)) - { - // Get the input HDA - UHoudiniAssetComponent* InputHAC = CurrentInputObject - ? Cast(CurrentInputObject->GetObject()) - : nullptr; - - if (!InputHAC) - continue; - - // If the input HDA needs to be instantiated, force him to instantiate - // if the input HDA is in any other state than None, we need to wait for him - // to finish whatever it's doing - if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) - { - // Tell the input HAC to instantiate - InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; - - // We need to wait - return true; - } - else if (InputHAC->GetAssetState() != EHoudiniAssetState::None) - { - // We need to wait - return true; - } - } - } - - return false; -} - -void -UHoudiniAssetComponent::BeginDestroy() -{ - if (CanDeleteHoudiniNodes()) - { - } - - // Gets called through UnRegisterHoudiniComponent(). - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - - // Unregister ourself so our houdini node can be deleted - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - Super::BeginDestroy(); -} - -void -UHoudiniAssetComponent::MarkAsNeedCook() -{ - // Force the asset state to NeedCook - //AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = true; - bRebuildRequested = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/trigger update - for (auto CurrentParam : Parameters) - { - if (!IsValid(CurrentParam)) - continue; - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(true); - } - - // We need to mark all our inputs as changed/trigger update - for (auto CurrentInput : Inputs) - { - if (!IsValid(CurrentInput)) - continue; - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(true); - CurrentInput->MarkDataUploadNeeded(true); - } - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -void -UHoudiniAssetComponent::MarkAsNeedRebuild() -{ - // Invalidate the asset ID - //AssetId = -1; - - // Force the asset state to NeedRebuild - AssetState = EHoudiniAssetState::NeedRebuild; - AssetStateResult = EHoudiniAssetStateResult::None; - - // Reset some of the asset's flag - //AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = true; - bFullyLoaded = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/trigger update - for (auto CurrentParam : Parameters) - { - if (!IsValid(CurrentParam)) - continue; - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(true); - } - - // We need to mark all our inputs as changed/trigger update - for (auto CurrentInput : Inputs) - { - if (!IsValid(CurrentInput)) - continue; - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(true); - CurrentInput->MarkDataUploadNeeded(true); - } - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -// Marks the asset as needing to be instantiated -void -UHoudiniAssetComponent::MarkAsNeedInstantiation() -{ - // Invalidate the asset ID - AssetId = -1; - - if (Parameters.Num() <= 0 && Inputs.Num() <= 0 && Outputs.Num() <= 0) - { - // The asset has no parameters or inputs. - // This likely indicates it has never cooked/been instantiated. - // Set its state to PreInstantiation to force its instantiation - // so that we can have its parameters/input interface - AssetState = EHoudiniAssetState::PreInstantiation; - } - else - { - // The asset has cooked before since we have a parameter/input interface - // Set its state to need instantiation so that the asset is instantiated - // after being modified - AssetState = EHoudiniAssetState::NeedInstantiation; - } - - AssetStateResult = EHoudiniAssetStateResult::None; - - // Reset some of the asset's flag - AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = false; - bFullyLoaded = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/not triggering update - for (auto CurrentParam : Parameters) - { - if (CurrentParam) - { - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(false); - } - } - - // We need to mark all our inputs as changed/not triggering update - for (auto CurrentInput : Inputs) - { - if (CurrentInput) - { - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(false); - CurrentInput->MarkDataUploadNeeded(true); - } - } - - /*if (!CanInstantiateAsset()) - { - AssetState = EHoudiniAssetState::None; - AssetStateResult = EHoudiniAssetStateResult::None; - }*/ - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -void UHoudiniAssetComponent::MarkAsBlueprintStructureModified() -{ - bBlueprintStructureModified = true; -} - -void UHoudiniAssetComponent::MarkAsBlueprintModified() -{ - bBlueprintModified = true; -} - -void -UHoudiniAssetComponent::PostLoad() -{ - Super::PostLoad(); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - bool bAutomaticLegacyHDARebuild = HoudiniRuntimeSettings->bAutomaticLegacyHDARebuild; - - // Legacy serialization: either try to convert or skip depending the setting value - if (bEnableBackwardCompatibility && Version1CompatibilityHAC != nullptr) - { - // If we have deserialized legacy v1 data, attempt to convert it now - ConvertLegacyData(); - - if(bAutomaticLegacyHDARebuild) - MarkAsNeedRebuild(); - else - MarkAsNeedInstantiation(); - } - else - { - // Normal v2 objet, mark as need instantiation - MarkAsNeedInstantiation(); - } - - // Component has been loaded, not duplicated - bHasBeenDuplicated = false; - - // We need to register ourself - RegisterHoudiniComponent(this); - - // Register our PDG Asset link if we have any - -} - -void -UHoudiniAssetComponent::PostEditImport() -{ - Super::PostEditImport(); - - MarkAsNeedInstantiation(); - - // Component has been duplicated, not loaded - // We do need the loaded flag to reapply parameters, inputs - // and properly update some of the output objects - bHasBeenDuplicated = true; - - //RemoveAllAttachedComponents(); - - AssetState = EHoudiniAssetState::PreInstantiation; - AssetStateResult = EHoudiniAssetStateResult::None; - - // TODO? - // REGISTER? -} - -void -UHoudiniAssetComponent::UpdatePostDuplicate() -{ - // TODO: - // - Keep the output objects/components (remove duplicatetransient on the output object uproperties) - // - Duplicate created objects (ie SM) and materials - // - Update the output components to use these instead - // This should remove the need for a cook on duplicate - - // For now, we simply clean some of the HAC's component manually - const TArray Children = GetAttachChildren(); - - for (auto & NextChild : Children) - { - if (!NextChild || NextChild->IsPendingKill()) - continue; - - USceneComponent * ComponentToRemove = nullptr; - if (NextChild->IsA()) - { - ComponentToRemove = NextChild; - } - else if (NextChild->IsA()) - { - ComponentToRemove = NextChild; - } - /* do not destroy attached duplicated editable curves, they are needed to restore editable curves - else if (NextChild->IsA()) - { - // Remove duplicated editable curve output's Houdini Spline Component, since they will be re-built at duplication. - UHoudiniSplineComponent * HoudiniSplineComponent = Cast(NextChild); - if (HoudiniSplineComponent && HoudiniSplineComponent->IsEditableOutputCurve()) - ComponentToRemove = NextChild; - } - */ - if (ComponentToRemove) - { - ComponentToRemove->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - ComponentToRemove->UnregisterComponent(); - ComponentToRemove->DestroyComponent(); - } - } - - // if there is an associated PDG asset link, call its UpdatePostDuplicate to cleanup references to - // to the original instance's PDG output actors - if (IsValid(PDGAssetLink)) - { - PDGAssetLink->UpdatePostDuplicate(); - } - - SetHasBeenDuplicated(false); -} - -bool UHoudiniAssetComponent::IsInputTypeSupported(EHoudiniInputType InType) const -{ - return true; -} - -bool UHoudiniAssetComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const -{ - return true; -} - -bool -UHoudiniAssetComponent::IsPreview() const -{ - return bCachedIsPreview; -} - -bool UHoudiniAssetComponent::IsValidComponent() const -{ - return true; -} - -void UHoudiniAssetComponent::OnFullyLoaded() -{ - bFullyLoaded = true; -} - - -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); - } - */ -} - -void -UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - - if (CanDeleteHoudiniNodes()) - { - } - - // Unregister ourself so our houdini node can be deleted - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - HoudiniAsset = nullptr; - - // Clear Parameters - for (UHoudiniParameter*& CurrentParm : Parameters) - { - if (CurrentParm && !CurrentParm->IsPendingKill()) - { - CurrentParm->ConditionalBeginDestroy(); - } - else if (GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE) - { - // TODO unneeded log? - // Avoid spamming that error when leaving PIE mode - HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); - } - - CurrentParm = nullptr; - } - - Parameters.Empty(); - - // Clear Inputs - for (UHoudiniInput*& CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - // Destroy connected Houdini asset. - CurrentInput->ConditionalBeginDestroy(); - CurrentInput = nullptr; - } - - Inputs.Empty(); - - // Clear Output - for (UHoudiniOutput*& CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - // Destroy all Houdini created socket actors. - TArray & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors(); - for (auto & CurCreatedActor : CurCreatedSocketActors) - { - if (!CurCreatedActor || CurCreatedActor->IsPendingKill()) - continue; - - CurCreatedActor->Destroy(); - } - CurCreatedSocketActors.Empty(); - - // Detach all Houdini attached socket actors - TArray & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors(); - for (auto & CurAttachedSocketActor : CurAttachedSocketActors) - { - if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill()) - continue; - - CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - } - CurAttachedSocketActors.Empty(); - -#if WITH_EDITOR - // Clean up foliages instances - for (auto& CurrentOutputObject : CurrentOutput->GetOutputObjects()) - { - // Foliage instancers store a HISMC in the components - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentOutputObject.Value.OutputComponent); - if (!FoliageHISMC) - continue; - - UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - continue; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(FoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - continue; - - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - continue; - - // Clean up the instances generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(this, FoliageType); - - if (FoliageHISMC->GetInstanceCount() > 0) - { - // If the component still has instances left after the cleanup, - // make sure that we dont delete it, as the leftover instances are likely hand-placed - CurrentOutputObject.Value.OutputComponent = nullptr; - } - else - { - // Remove the foliage type if it doesn't have any more instances - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - } - } -#endif - - CurrentOutput->Clear(); - // Destroy connected Houdini asset. - CurrentOutput->ConditionalBeginDestroy(); - CurrentOutput = nullptr; - } - - Outputs.Empty(); - - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - // Unregister ourself so our houdini node can be delete. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); - - - // Clear all TOP data and temporary geo/objects from the PDG asset link (if valid) - if (IsValid(PDGAssetLink)) - { -#if WITH_EDITOR - const UWorld* const World = GetWorld(); - if (IsValid(World)) - { - // Only do this for editor worlds, only interactively (not during engine shutdown or garbage collection) - if (World->WorldType == EWorldType::Editor && GIsRunning && !GIsGarbageCollecting) - { - // In case we are recording a transaction (undo, for example) notify that the object will be - // modified. - PDGAssetLink->Modify(); - PDGAssetLink->ClearAllTOPData(); - } - } -#endif - } - - Super::OnComponentDestroyed(bDestroyingHierarchy); -} - -void UHoudiniAssetComponent::RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) -{ - // Registration of this component is wrapped in this virtual function to allow - // derived classed to override this behaviour. - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(InComponent); -} - -void -UHoudiniAssetComponent::OnRegister() -{ - Super::OnRegister(); - - // NOTE: Wait until HoudiniEngineTick() before deciding to mark this object as fully loaded - // since preview components need to wait for component templates to finish their initialization - // before being able to perform state transfers. - - /* - // 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(); - } - } - */ - - // Let TickInitialization() take care of manipulating the bFullyLoaded state. - //bFullyLoaded = true; - - //// If we're constructing editable components in the SCS editor, set the component instance corresponding to this node for editing purposes - // - //USimpleConstructionScript* SCS = GetSCS(); - //if (SCS == nullptr) - //{ - // bFullyLoaded = true; - //} - //else - //{ - // if(SCS->IsConstructingEditorComponents()) - // { - // // We're not fully loaded yet. We're expecting - // } - // else - // { - // bFullyLoaded = true; - // } - //} - -} - -UHoudiniParameter* -UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) -{ - if (!InOtherParam || InOtherParam->IsPendingKill()) - return nullptr; - - for (auto CurrentParam : Parameters) - { - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - if (CurrentParam->Matches(*InOtherParam)) - return CurrentParam; - } - - return nullptr; -} - -UHoudiniInput* -UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) -{ - if (!InOtherInput || InOtherInput->IsPendingKill()) - return nullptr; - - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->Matches(*InOtherInput)) - return CurrentInput; - } - - return nullptr; -} - -UHoudiniHandleComponent* -UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle) -{ - if (!InOtherHandle || InOtherHandle->IsPendingKill()) - return nullptr; - - for (auto CurrentHandle : HandleComponents) - { - if (!CurrentHandle || CurrentHandle->IsPendingKill()) - continue; - - if (CurrentHandle->Matches(*InOtherHandle)) - return CurrentHandle; - } - - return nullptr; -} - -UHoudiniParameter* -UHoudiniAssetComponent::FindParameterByName(const FString& InParamName) -{ - for (auto CurrentParam : Parameters) - { - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - if (CurrentParam->GetParameterName().Equals(InParamName)) - return CurrentParam; - } - - return nullptr; -} - - -void -UHoudiniAssetComponent::OnChildAttached(USceneComponent* ChildComponent) -{ - Super::OnChildAttached(ChildComponent); - - // ... Do corresponding things for other houdini component types. - // ... -} - - -void -UHoudiniAssetComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) -{ - Super::OnUpdateTransform(UpdateTransformFlags, Teleport); - - SetHasComponentTransformChanged(true); -} - -void UHoudiniAssetComponent::HoudiniEngineTick() -{ - if (!IsFullyLoaded()) - { - OnFullyLoaded(); - } -} - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - FProperty * Property = PropertyChangedEvent.MemberProperty; - if (!Property) - return; - - FName PropertyName = Property->GetFName(); - - // Changing the Houdini Asset? - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, HoudiniAsset)) - { - OnHoudiniAssetChanged(); - } - else if (PropertyName == GetRelativeLocationPropertyName() - || PropertyName == GetRelativeRotationPropertyName() - || PropertyName == GetRelativeScale3DPropertyName()) - { - SetHasComponentTransformChanged(true); - } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bOverrideGlobalProxyStaticMeshSettings) - || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bEnableProxyStaticMeshRefinementByTimerOverride) - || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, ProxyMeshAutoRefineTimeoutSecondsOverride)) - { - ClearRefineMeshesTimer(); - // Reset the timer - // SetRefineMeshesTimer will check the relevant settings and only set the timer if enabled via settings - SetRefineMeshesTimer(); - } - //else if (PropertyName == TEXT("Mobility")) - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, 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); - } - } - //else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bVisible)) - else if (PropertyName == TEXT("bVisible")) - { - // Visibility has changed, propagate it to children. - SetVisibility(IsVisible(), true); - } - //else if (PropertyName == TEXT("bHiddenInGame")) - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bHiddenInGame)) - { - // Visibility has changed, propagate it to children. - SetHiddenInGame(bHiddenInGame, true); - } - else - { - // TODO: - // Propagate properties (mobility/visibility etc.. to children components) - // Look in v1 for: if (Property->HasMetaData(TEXT("Category"))) {} and HOUDINI_UPDATE_ALL_CHILD_COMPONENTS - } - -} -#endif - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::PostEditUndo() -{ - Super::PostEditUndo(); - - if (!IsPendingKill()) - { - // Make sure we are registered with the HER singleton - // We could be undoing a HoudiniActor delete - if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(this)) - { - MarkAsNeedInstantiation(); - - // Component has been loaded, not duplicated - bHasBeenDuplicated = false; - - RegisterHoudiniComponent(this); - } - } -} - -#endif - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::OnActorMoved(AActor* Actor) -{ - if (GetOwner() != Actor) - return; - - SetHasComponentTransformChanged(true); -} -#endif - -void -UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged) -{ - // Only update the value if we're fully loaded - // This avoid triggering a recook when loading a level - if(bFullyLoaded) - bHasComponentTransformChanged = InHasChanged; -} - -void -UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // Check the object validity - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // If it is the same object, do nothing. - if (InPDGAssetLink == PDGAssetLink) - return; - - PDGAssetLink = InPDGAssetLink; -} - - -FBoxSphereBounds -UHoudiniAssetComponent::CalcBounds(const FTransform & LocalToWorld) const -{ - FBoxSphereBounds LocalBounds; - FBox BoundingBox = GetAssetBounds(nullptr, false); - 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; -} - - -FBox -UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const -{ - FBox BoxBounds(ForceInitToZero); - - // Query the bounds for all output objects - for (auto & CurOutput : Outputs) - { - if (!CurOutput || CurOutput->IsPendingKill()) - continue; - - BoxBounds += CurOutput->GetBounds(); - } - - // Query the bounds for all our inputs - for (auto & CurInput : Inputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - BoxBounds += CurInput->GetBounds(); - } - - // Query the bounds for all input parameters - for (auto & CurParam : Parameters) - { - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (CurParam->GetParameterType() != EHoudiniParameterType::Input) - continue; - - UHoudiniParameterOperatorPath* InputParam = Cast(CurParam); - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (!InputParam->HoudiniInput.IsValid()) - continue; - - BoxBounds += InputParam->HoudiniInput.Get()->GetBounds(); - } - - // Query the bounds for all our Houdini handles - for (auto & CurHandleComp : HandleComponents) - { - if (!CurHandleComp || CurHandleComp->IsPendingKill()) - continue; - - BoxBounds += CurHandleComp->GetBounds(); - } - - // Also scan all our decendants for SMC bounds not just top-level children - // ( split mesh instances' mesh bounds were not gathered proiperly ) - TArray 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(pChild)) - { - if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) - continue; - - FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); - if (StaticMeshBounds.IsValid) - BoxBounds += StaticMeshBounds; - } - } - - // If nothing was found, init with the asset's location - if (BoxBounds.GetVolume() == 0.0f) - BoxBounds += GetComponentLocation(); - - return BoxBounds; -} - -void -UHoudiniAssetComponent::ClearRefineMeshesTimer() -{ - UWorld *World = GetWorld(); - if (!World) - { - //HOUDINI_LOG_ERROR(TEXT("Cannot ClearRefineMeshesTimer, World is nullptr!")); - return; - } - - World->GetTimerManager().ClearTimer(RefineMeshesTimer); -} - -void -UHoudiniAssetComponent::SetRefineMeshesTimer() -{ - UWorld *World = GetWorld(); - if (!World) - { - HOUDINI_LOG_ERROR(TEXT("Cannot SetRefineMeshesTimer, World is nullptr!")); - return; - } - - // Check if timer-based proxy mesh refinement is enable for this component - const bool bEnableTimer = IsProxyStaticMeshRefinementByTimerEnabled(); - const float TimeSeconds = GetProxyMeshAutoRefineTimeoutSeconds(); - if (bEnableTimer) - { - World->GetTimerManager().SetTimer(RefineMeshesTimer, this, &UHoudiniAssetComponent::OnRefineMeshesTimerFired, 1.0f, false, TimeSeconds); - } - else - { - World->GetTimerManager().ClearTimer(RefineMeshesTimer); - } -} - -void -UHoudiniAssetComponent::OnRefineMeshesTimerFired() -{ - HOUDINI_LOG_MESSAGE(TEXT("UHoudiniAssetComponent::OnRefineMeshesTimerFired()")); - if (OnRefineMeshesTimerDelegate.IsBound()) - { - OnRefineMeshesTimerDelegate.Broadcast(this); - } -} - -bool -UHoudiniAssetComponent::HasAnyCurrentProxyOutput() const -{ - for (const UHoudiniOutput *Output : Outputs) - { - if (Output->HasAnyCurrentProxy()) - { - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasAnyProxyOutput() const -{ - for (const UHoudiniOutput *Output : Outputs) - { - if (Output->HasAnyProxy()) - { - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasAnyOutputComponent() const -{ - for (UHoudiniOutput *Output : Outputs) - { - for(auto& CurrentOutputObject : Output->GetOutputObjects()) - { - if(CurrentOutputObject.Value.OutputComponent) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasOutputObject(UObject* InOutputObjectToFind) const -{ - for (const auto& CurOutput : Outputs) - { - for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) - { - if (CurOutputObject.Value.OutputObject == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.OutputComponent == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.ProxyObject == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.ProxyComponent == InOutputObjectToFind) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const -{ - // Get the state of the asset and check if it is pre-cook, cooked, pending delete/rebuild or invalid - bOutNeedsRebuildOrDelete = false; - bOutInvalidState = false; - switch (AssetState) - { - case EHoudiniAssetState::NeedInstantiation: - case EHoudiniAssetState::PreInstantiation: - case EHoudiniAssetState::Instantiating: - case EHoudiniAssetState::PreCook: - case EHoudiniAssetState::Cooking: - case EHoudiniAssetState::PostCook: - case EHoudiniAssetState::PreProcess: - case EHoudiniAssetState::Processing: - return false; - break; - case EHoudiniAssetState::None: - return true; - break; - case EHoudiniAssetState::NeedRebuild: - case EHoudiniAssetState::NeedDelete: - case EHoudiniAssetState::Deleting: - bOutNeedsRebuildOrDelete = true; - break; - default: - bOutInvalidState = true; - break; - } - - return false; -} - -void -UHoudiniAssetComponent::SetInputPresets(const TMap& InPresets) -{ - // Set the input preset for this HAC -#if WITH_EDITOR - InputPresets = InPresets; -#endif -} - - -void -UHoudiniAssetComponent::ApplyInputPresets() -{ - if (InputPresets.Num() <= 0) - return; - -#if WITH_EDITOR - // Ignore inputs that have been preset to curve - TArray InputArray; - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetInputType() != EHoudiniInputType::Curve) - InputArray.Add(CurrentInput); - } - - // Try to apply the supplied Object to the Input - for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset) - { - UObject * Object = IterToolPreset.Key(); - if (!Object || Object->IsPendingKill()) - continue; - - int32 InputNumber = IterToolPreset.Value(); - if (!InputArray.IsValidIndex(InputNumber)) - continue; - - // If the object is a landscape, add a new landscape input - if (Object->IsA()) - { - // selecting a landscape - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Landscape); - if (InsertNum == 0) - { - // Landscape inputs only support one object! - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Landscape, InsertNum, Object); - } - } - - // If the object is an actor, add a new world input - if (Object->IsA()) - { - // selecting an actor - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::World); - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::World, InsertNum, Object); - } - - // If the object is a static mesh, add a new geometry input (TODO: or BP ? ) - if (Object->IsA()) - { - // selecting a Staticn Mesh - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Geometry, InsertNum, Object); - } - - if (Object->IsA()) - { - // selecting a Houdini Asset - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Asset); - if (InsertNum == 0) - { - // Assert inputs only support one object! - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Asset, InsertNum, Object); - } - } - } - - // The input objects have been set, now change the input type - bool bBPStructureModified = false; - for (auto CurrentInput : Inputs) - { - int32 NumGeo = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - int32 NumAsset = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Asset); - int32 NumWorld = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::World); - int32 NumLandscape = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Landscape); - - EHoudiniInputType NewInputType = EHoudiniInputType::Invalid; - if (NumLandscape > 0 && NumLandscape >= NumGeo && NumLandscape >= NumAsset && NumLandscape >= NumWorld) - NewInputType = EHoudiniInputType::Landscape; - else if (NumWorld > 0 && NumWorld >= NumGeo && NumWorld >= NumAsset && NumWorld >= NumLandscape) - NewInputType = EHoudiniInputType::World; - else if (NumAsset > 0 && NumAsset >= NumGeo && NumAsset >= NumWorld && NumAsset >= NumLandscape) - NewInputType = EHoudiniInputType::Asset; - else if (NumGeo > 0 && NumGeo >= NumAsset && NumGeo >= NumWorld && NumGeo >= NumLandscape) - NewInputType = EHoudiniInputType::Geometry; - - if (NewInputType == EHoudiniInputType::Invalid) - continue; - - // Change the input type, unless if it was preset to a different type and we have object for the preset type - if (CurrentInput->GetInputType() == EHoudiniInputType::Geometry && NewInputType != EHoudiniInputType::Geometry) - { - CurrentInput->SetInputType(NewInputType, bBPStructureModified); - } - else - { - // Input type was preset, only change if that type is empty - if(CurrentInput->GetNumberOfInputObjects() <= 0) - CurrentInput->SetInputType(NewInputType, bBPStructureModified); - } - } - if (bBPStructureModified) - { - MarkAsBlueprintStructureModified(); - } -#endif - - // Discard the tool presets after their first setup - InputPresets.Empty(); -} - - -bool -UHoudiniAssetComponent::IsComponentValid() const -{ - if (!IsValidLowLevel()) - return false; - - if (IsTemplate()) - return false; - - if (IsPendingKillOrUnreachable()) - return false; - - if (!GetOuter()) //|| !GetOuter()->GetLevel() ) - return false; - - return true; -} - -bool -UHoudiniAssetComponent::IsInstantiatingOrCooking() const -{ - return HapiGUID.IsValid(); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" +#include "TimerManager.h" +#include "Landscape.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +void +UHoudiniAssetComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + + // Legacy serialization + // Either try to convert or skip depending the setting value + if (bEnableBackwardCompatibility) + { + // Attemp to convert the v1 object to v2 + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + // Deserialize the legacy data, we'll do the actual conversion in PostLoad() + // After everything has been deserialized + Version1CompatibilityHAC = NewObject(this); + Version1CompatibilityHAC->Serialize(Ar); + } + else + { + // Skip the v1 object + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +bool +UHoudiniAssetComponent::ConvertLegacyData() +{ + if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill()) + return false; + + // Set the Houdini Asset + if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill()) + return false; + + HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset; + + // Convert all parameters + for (auto& LegacyParmPair : Version1CompatibilityHAC->Parameters) + { + if (!LegacyParmPair.Value) + continue; + + UHoudiniParameter* Parm = LegacyParmPair.Value->ConvertLegacyData(this); + LegacyParmPair.Value->CopyLegacyParameterData(Parm); + Parameters.Add(Parm); + } + + // Convert all inputs + for (auto& LegacyInput : Version1CompatibilityHAC->Inputs) + { + // Convert v1 input to v2 + UHoudiniInput* Input = LegacyInput->ConvertLegacyInput(this); + + Inputs.Add(Input); + } + + // Lambdas for finding/creating outputs from an HGPO + auto FindOrCreateOutput = [&](FHoudiniGeoPartObject& InNewHGPO, bool& bNew) + { + UHoudiniOutput* NewOutput = nullptr; + + // See if we can add to an existing output + UHoudiniOutput** FoundOutput = nullptr; + FoundOutput = Outputs.FindByPredicate( + [InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; }); + + if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill()) + { + // FoundOutput is valid, add to it + NewOutput = *FoundOutput; + bNew = false; + } + else + { + // Create a new output object + NewOutput = NewObject( + this, UHoudiniOutput::StaticClass(), NAME_None, RF_NoFlags); + bNew = true; + } + + return NewOutput; + }; + + + // Convert all outputs + // Start by handling the Static Meshes + for (auto& LegacySM : Version1CompatibilityHAC->StaticMeshes) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacySM.Key.ConvertLegacyData(); + + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(NewHGPO); + // Mark if the HoudiniOutput is editable + NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); + } + + // Build a new output object identifier + FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); + Identifier.bLoaded = true; + Identifier.PartName = NewHGPO.PartName; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); + OutputObj.OutputObject = LegacySM.Value; + OutputObj.OutputComponent = nullptr; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + // Handle the SMC for this SM / HGPO + if (LegacySM.Value && !LegacySM.Value->IsPendingKill()) + { + UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value); + if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill()) + OutputObj.OutputComponent = *FoundSMC; + } + + // Add to the outputs + Outputs.AddUnique(NewOutput); + + //NewOutput->StaleCount; + //NewOutput->bLandscapeWorldComposition; + //NewOutput->HoudiniCreatedSocketActors; + //NewOutput->HoudiniAttachedSocketActors; + //NewOutput->bHasEditableNodeBuilt - false; + } + + // ... then Landscapes + for (auto& LegacyLandscape : Version1CompatibilityHAC->LandscapeComponents) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacyLandscape.Key.ConvertLegacyData(); + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(NewHGPO); + // Mark if the HoudiniOutput is editable + NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); + } + + // Build a new output object identifier + FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); + Identifier.bLoaded = true; + Identifier.PartName = NewHGPO.PartName; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); + + // We need to create a LandscapePtr wrapper for the landscaope + UHoudiniLandscapePtr* LandscapePtr = NewObject(NewOutput); + LandscapePtr->SetSoftPtr(LegacyLandscape.Value.IsValid() ? LegacyLandscape.Value.Get() : nullptr); + + OutputObj.OutputObject = LandscapePtr; + OutputObj.OutputComponent = nullptr; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + // Add to the outputs + Outputs.AddUnique(NewOutput); + } + + // ... instancers + for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs) + { + if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill()) + continue; + + FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData(); + + // Prepare this output object's output identifier + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = InstancerHGPO.ObjectId; + OutputIdentifier.GeoId = InstancerHGPO.GeoId; + OutputIdentifier.PartId = InstancerHGPO.PartId; + OutputIdentifier.PartName = InstancerHGPO.PartName; + + EHoudiniInstancerType InstancerType = EHoudiniInstancerType::ObjectInstancer; + if (LegacyInstanceIn->Flags.bIsPackedPrimitiveInstancer) + InstancerType = EHoudiniInstancerType::PackedPrimitive; + else if (LegacyInstanceIn->Flags.bAttributeInstancerOverride) + InstancerType = EHoudiniInstancerType::AttributeInstancer; + else if (LegacyInstanceIn->Flags.bIsAttributeInstancer) + InstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + else if (LegacyInstanceIn->ObjectToInstanceId >= 0) + InstancerType = EHoudiniInstancerType::ObjectInstancer; + + InstancerHGPO.InstancerType = InstancerType; + + //bool bIsMSIC = LegacyInstanceIn->Flags.bIsSplitMeshInstancer; + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(InstancerHGPO); + } + + // Get the output's instanced outputs + TMap& InstancedOutputs = NewOutput->GetInstancedOutputs(); + + int32 InstFieldIdx = 0; + for (auto& LegacyInstanceInputField : LegacyInstanceIn->InstanceInputFields) + { + FHoudiniGeoPartObject InstInputFieldHGPO = LegacyInstanceInputField->HoudiniGeoPartObject.ConvertLegacyData(); + + // Create an instanced output for this object + FHoudiniInstancedOutput NewInstOut; + NewInstOut.OriginalObject = LegacyInstanceInputField->OriginalObject; + NewInstOut.OriginalObjectIndex = -1; + NewInstOut.OriginalTransforms = LegacyInstanceInputField->InstancedTransforms; + + for (auto& InstObj : LegacyInstanceInputField->InstancedObjects) + NewInstOut.VariationObjects.Add(InstObj); + + int32 NumVar = LegacyInstanceInputField->RotationOffsets.Num(); + for (int32 Idx = 0; Idx < NumVar; Idx++) + { + FTransform TransOffset; + TransOffset.SetLocation(FVector::ZeroVector); + if (LegacyInstanceInputField->RotationOffsets.IsValidIndex(Idx)) + TransOffset.SetRotation(LegacyInstanceInputField->RotationOffsets[Idx].Quaternion()); + + if (LegacyInstanceInputField->ScaleOffsets.IsValidIndex(Idx)) + TransOffset.SetScale3D(LegacyInstanceInputField->ScaleOffsets[Idx]); + + NewInstOut.VariationTransformOffsets.Add(TransOffset); + } + + // Build an identifier for the instance output + FHoudiniOutputObjectIdentifier Identifier; + Identifier.ObjectId = InstInputFieldHGPO.ObjectId; + Identifier.GeoId = InstInputFieldHGPO.GeoId; + Identifier.PartId = InstInputFieldHGPO.PartId; + Identifier.PartName = InstInputFieldHGPO.PartName; + Identifier.SplitIdentifier = FString::FromInt(InstFieldIdx); + Identifier.bLoaded = true; + + // Add the instance output to the outputs + InstancedOutputs.Add(Identifier, NewInstOut); + + // Now create an Output object for each variation + int32 VarIdx = 0; + for (auto& LegacyComp : LegacyInstanceInputField->InstancerComponents) + { + // Build a new output object identifier for this variation + FHoudiniOutputObjectIdentifier VarIdentifier; + VarIdentifier.ObjectId = InstInputFieldHGPO.ObjectId; + VarIdentifier.GeoId = InstInputFieldHGPO.GeoId; + VarIdentifier.PartId = InstInputFieldHGPO.PartId; + VarIdentifier.PartName = InstInputFieldHGPO.PartName; + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + VarIdentifier.SplitIdentifier = + FString::FromInt(InstFieldIdx) + TEXT("_") + FString::FromInt(VarIdx); + VarIdentifier.bLoaded = true; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(VarIdentifier); + + OutputObj.OutputObject = nullptr; + OutputObj.OutputComponent = LegacyComp; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + VarIdx++; + } + + // ??? + //LegacyInstanceInputField->VariationTransformsArray; + //LegacyInstanceInputField->InstanceColorOverride; + //LegacyInstanceInputField->VariationInstanceColorOverrideArray; + + // Index of the variation used for each transform + //NewInstOut.TransformVariationIndices; + //NewInstOut.bUniformScaleLocked = false; + + InstFieldIdx++; + } + + // Add to the outputs + Outputs.AddUnique(NewOutput); + } + + // ... then Spline Components (for Curve IN) + for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents) + { + UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value; + if (!CurSplineComp || CurSplineComp->IsPendingKill()) + continue; + + // TODO: Needed? + // Attach the spline to the HAC + CurSplineComp->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + // Editable curve? / Should create an output for it! + if (CurSplineComp->IsEditableOutputCurve()) + { + FHoudiniGeoPartObject CurHGPO = LegacyCurve.Key.ConvertLegacyData(); + + // Look for an output for that HGPO + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(CurHGPO); + } + + // Build an output object id for the editable curve output + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; + EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; + EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; + EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; + + TMap& OutputObjects = NewOutput->GetOutputObjects(); + FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + FoundOutputObject.OutputComponent = CurSplineComp; + + //CurSplineComp->SetHasEditableNodeBuilt(true); + CurSplineComp->SetIsInputCurve(false); + } + else + { + // Input! + // Conversion of the inputs should have done the job already + CurSplineComp->SetIsInputCurve(true); + } + } + + // ... Handles + for (auto& LegacyHandle : Version1CompatibilityHAC->HandleComponents) + { + // TODO: Handles!! + UHoudiniHandleComponent* NewHandle = nullptr; + HandleComponents.Add(NewHandle); + } + + // ... Materials + UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials; + if(LegacyMaterials && !LegacyMaterials->IsPendingKill()) + { + // Assignements: Apply to all outputs since they're not tied to an HGPO... + for (auto& CurOutput : Outputs) + { + TMap& CurrAssign = CurOutput->GetAssignementMaterials(); + for (auto& LegacyMaterial : LegacyMaterials->Assignments) + { + CurrAssign.Add(LegacyMaterial.Key, LegacyMaterial.Value); + } + } + + // Replacements + // Try to find the output matching the HGPO + for (auto& LegacyMaterial : LegacyMaterials->Replacements) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacyMaterial.Key.ConvertLegacyData(); + + TMap& LegacyReplacement = LegacyMaterial.Value; + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + if (bCreatedNew) + continue; + + TMap& CurReplacement = NewOutput->GetReplacementMaterials(); + for (auto& CurLegacyReplacement : LegacyReplacement) + { + CurReplacement.Add(CurLegacyReplacement.Key, CurLegacyReplacement.Value); + } + } + } + + + // ... Bake Name overrides + for (auto& LegacyBakeNameOverride : Version1CompatibilityHAC->BakeNameOverrides) + { + // In Outputs? + } + + // ... then Downstream asset connections (due to Asset inputs) + for (auto& LegacyDownstreamHAC : Version1CompatibilityHAC->DownstreamAssetConnections) + { + //TSet DownstreamHoudiniAssets; + } + + // Then convert all remaing flags and properties + bGeneratedDoubleSidedGeometry = Version1CompatibilityHAC->bGeneratedDoubleSidedGeometry; + GeneratedPhysMaterial = Version1CompatibilityHAC->GeneratedPhysMaterial; + DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; + GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; + GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; + GeneratedLpvBiasMultiplier = Version1CompatibilityHAC->GeneratedLpvBiasMultiplier; + GeneratedDistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; + GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; + GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; + bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; + GeneratedStreamingDistanceMultiplier = Version1CompatibilityHAC->GeneratedStreamingDistanceMultiplier; + //GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; + GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; + + BakeFolder.Path = Version1CompatibilityHAC->BakeFolder.ToString(); + TemporaryCookFolder.Path = Version1CompatibilityHAC->TempCookFolder.ToString(); + + ComponentGUID = Version1CompatibilityHAC->ComponentGUID; + + bEnableCooking = Version1CompatibilityHAC->bEnableCooking; + bUploadTransformsToHoudiniEngine = Version1CompatibilityHAC->bUploadTransformsToHoudiniEngine; + bCookOnTransformChange = Version1CompatibilityHAC->bTransformChangeTriggersCooks; + bCookOnParameterChange = true; + //Version1CompatibilityHAC->bCookingTriggersDownstreamCooks; + bCookOnAssetInputCook = true; + bOutputless = false; + bOutputTemplateGeos = false; + bFullyLoaded = Version1CompatibilityHAC->bFullyLoaded; + + //bContainsHoudiniLogoGeometry = Version1CompatibilityHAC->bContainsHoudiniLogoGeometry; + //bIsNativeComponent = Version1CompatibilityHAC->bIsNativeComponent; + //bIsPreviewComponent = Version1CompatibilityHAC->bIsPreviewComponent; + //bLoadedComponent = Version1CompatibilityHAC->bLoadedComponent; + //bIsPlayModeActive_Unused = Version1CompatibilityHAC->bIsPlayModeActive_Unused; + //Version1CompatibilityHAC->bTimeCookInPlaymode_Unused; + //Version1CompatibilityHAC->bUseHoudiniMaterials; + + //Version1CompatibilityHAC->GeneratedGeometryScaleFactor; + //Version1CompatibilityHAC->TransformScaleFactor; + //Version1CompatibilityHAC->PresetBuffer; + //Version1CompatibilityHAC->DefaultPresetBuffer; + //Version1CompatibilityHAC->ParameterByName; + + // Now that we're done, update all the output's types + for (auto& CurOutput : Outputs) + { + CurOutput->UpdateOutputType(); + } + + // + // Clean up the legacy HAC + // + + Version1CompatibilityHAC->Parameters.Empty(); + Version1CompatibilityHAC->Inputs.Empty(); + Version1CompatibilityHAC->StaticMeshes.Empty(); + Version1CompatibilityHAC->LandscapeComponents.Empty(); + Version1CompatibilityHAC->InstanceInputs.Empty(); + Version1CompatibilityHAC->SplineComponents.Empty(); + Version1CompatibilityHAC->HandleComponents.Empty(); + //Version1CompatibilityHAC->HoudiniAssetComponentMaterials.Empty(); + Version1CompatibilityHAC->BakeNameOverrides.Empty(); + Version1CompatibilityHAC->DownstreamAssetConnections.Empty(); + Version1CompatibilityHAC->MarkPendingKill(); + Version1CompatibilityHAC = nullptr; + + return true; +} + + +UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + HoudiniAsset = nullptr; + bCookOnParameterChange = true; + bUploadTransformsToHoudiniEngine = true; + bCookOnTransformChange = false; + //bUseNativeHoudiniMaterials = true; + bCookOnAssetInputCook = true; + + AssetId = -1; + AssetState = EHoudiniAssetState::PreInstantiation; + AssetStateResult = EHoudiniAssetStateResult::None; + AssetCookCount = 0; + + SubAssetIndex = -1; + + // Make an invalid GUID, since we do not have any cooking requests. + HapiGUID.Invalidate(); + + // Create unique component GUID. + ComponentGUID = FGuid::NewGuid(); + + bUploadTransformsToHoudiniEngine = true; + + bHasBeenLoaded = false; + bHasBeenDuplicated = false; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = false; + bEnableCooking = true; + bForceNeedUpdate = false; + bLastCookSuccess = false; + bBlueprintStructureModified = false; + bBlueprintModified = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // Folder used for cooking, the value is initialized by Output Translator + // TemporaryCookFolder.Path = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + + // Folder used for baking this asset's outputs, the value is initialized by Output Translator + // BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + bHasComponentTransformChanged = false; + + bFullyLoaded = false; + + bOutputless = false; + + bOutputTemplateGeos = false; + + PDGAssetLink = nullptr; + + StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bEnableProxyStaticMeshOverride = HoudiniRuntimeSettings->bEnableProxyStaticMesh; + bEnableProxyStaticMeshRefinementByTimerOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; + ProxyMeshAutoRefineTimeoutSecondsOverride = HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; + } + + bNoProxyMeshNextCookRequested = false; + bBakeAfterNextCook = false; + +#if WITH_EDITORONLY_DATA + bGenerateMenuExpanded = true; + bBakeMenuExpanded = true; + bAssetOptionMenuExpanded = true; + bHelpAndDebugMenuExpanded = true; + + HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; + + bRemoveOutputAfterBake = false; + bRecenterBakedActors = false; + bReplacePreviousBake = false; +#endif + + // + // Set component properties. + // + + Mobility = EComponentMobility::Static; + + 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; + + Bounds = FBox(ForceInitToZero); +} + +UHoudiniAssetComponent::~UHoudiniAssetComponent() +{ + // Unregister ourself so our houdini node can be delete. + + // This gets called in UnRegisterHoudiniComponent, with appropriate checks. Don't call it here. + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); +} + +void UHoudiniAssetComponent::PostInitProperties() +{ + Super::PostInitProperties(); + + // Register ourself to the HER singleton + RegisterHoudiniComponent(this); +} + +UHoudiniAsset * +UHoudiniAssetComponent::GetHoudiniAsset() const +{ + return HoudiniAsset; +} + +FString +UHoudiniAssetComponent::GetDisplayName() const +{ + return GetOwner() ? GetOwner()->GetName() : GetName(); +} + +void +UHoudiniAssetComponent::GetOutputs(TArray& OutOutputs) const +{ + for (UHoudiniOutput* Output : Outputs) + { + OutOutputs.Add(Output); + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh; + } + else + { + return false; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementByTimerEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; + } + else + { + return false; + } + } +} + +float +UHoudiniAssetComponent::GetProxyMeshAutoRefineTimeoutSeconds() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return ProxyMeshAutoRefineTimeoutSecondsOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; + } + else + { + return 5.0f; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; + } + else + { + return false; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; + } + else + { + return false; + } + } +} + +void +UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) +{ + // Check the asset validity + if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill()) + return; + + // If it is the same asset, do nothing. + if ( InHoudiniAsset == HoudiniAsset ) + return; + + HoudiniAsset = InHoudiniAsset; +} + + +void +UHoudiniAssetComponent::OnHoudiniAssetChanged() +{ + // TODO: clear input/params/outputs? + Parameters.Empty(); + + // The asset has been changed, mark us as needing to be reinstantiated + MarkAsNeedInstantiation(); + + // Force an update on the next tick + bForceNeedUpdate = true; +} + +bool +UHoudiniAssetComponent::NeedUpdateParameters() const +{ + // This is being split into a separate function to that it can + // be called separately for component templates. + + if (!bCookOnParameterChange) + return false; + + // Go through all our parameters, return true if they have been updated + for (auto CurrentParm : Parameters) + { + if (!CurrentParm || CurrentParm->IsPendingKill()) + continue; + + if (!CurrentParm->HasChanged()) + continue; + + // See if the parameter doesn't require an update + // (because it has failed to upload previously or has been loaded) + if (!CurrentParm->NeedsToTriggerUpdate()) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateParameters()] Parameters need update for component: %s"), *(GetPathName())); + return true; + } + + return false; +} + +bool +UHoudiniAssetComponent::NeedUpdateInputs() const +{ + // Go through all our inputs, return true if they have been updated + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (!CurrentInput->HasChanged()) + continue; + + // See if the input doesn't require an update + // (because it has failed to upload previously or has been loaded) + if (!CurrentInput->NeedsToTriggerUpdate()) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateInputs()] Inputs need update for component: %s"), *(GetPathName())); + return true; + } + + return false; +} + +bool +UHoudiniAssetComponent::HasPreviousBakeOutput() const +{ + // Look for any bake output objects in the output array + for (const UHoudiniOutput* Output : Outputs) + { + if (!IsValid(Output)) + continue; + + if (BakedOutputs.Num() == 0) + return false; + + for (const FHoudiniBakedOutput& BakedOutput : BakedOutputs) + { + if (BakedOutput.BakedOutputObjects.Num() > 0) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::NeedUpdate() const +{ + if (AssetState != DebugLastAssetState) + { + DebugLastAssetState = AssetState; + } + + // It is important to check this when dealing with Blueprints since the + // preview components start receiving events from the template component + // before the preview component have finished initialization. + if (!IsFullyLoaded()) + return false; + + // We must have a valid asset + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return false; + + if (bForceNeedUpdate) + return true; + + // If we don't want to cook on parameter/input change dont bother looking for updates + if (!bCookOnParameterChange && !bRecookRequested && !bRebuildRequested) + return false; + + // Check if the HAC's transform has changed and transform triggers cook is enabled + if (bCookOnTransformChange && bHasComponentTransformChanged) + return true; + + if (NeedUpdateParameters()) + return true; + + if (NeedUpdateInputs()) + return true; + + // Go through all outputs, filter the editable nodes. Return true if they have been updated. + for (auto CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + // We only care about editable outputs + if (!CurrentOutput->IsEditableNode()) + continue; + + // Trigger an update if the output object is marked as modified by user. + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + for (auto& NextPair : OutputObjects) + { + // For now, only editable curves can trigger update + UHoudiniSplineComponent* HoudiniSplineComponent = Cast(NextPair.Value.OutputComponent); + if (!HoudiniSplineComponent) + continue; + + // Output curves cant trigger an update! + if (HoudiniSplineComponent->bIsOutputCurve) + continue; + + if (HoudiniSplineComponent->NeedsToTriggerUpdate()) + return true; + } + } + + return false; +} + +// Indicates if any of the HAC's output components needs to be updated (no recook needed) +bool +UHoudiniAssetComponent::NeedOutputUpdate() const +{ + // Go through all outputs + for (auto CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs()) + { + if (InstOutput.Value.bChanged) + return true; + } + } + + return false; +} + +bool UHoudiniAssetComponent::NeedBlueprintStructureUpdate() const +{ + // TODO: Add similar flags to inputs, parametsr + return bBlueprintStructureModified; +} + +bool UHoudiniAssetComponent::NeedBlueprintUpdate() const +{ + // TODO: Add similar flags to inputs, parametsr + return bBlueprintModified; +} + +bool +UHoudiniAssetComponent::NotifyCookedToDownstreamAssets() +{ + // Before notifying, clean up our downstream assets + // - check that they are still valid + // - check that we are still connected to one of its asset input + // - check that the asset as the CookOnAssetInputCook trigger enabled + TArray DownstreamToDelete; + for(auto& CurrentDownstreamHAC : DownstreamHoudiniAssets) + { + // Remove the downstream connection by default, + // unless we actually were properly connected to one of this HDa's input. + bool bRemoveDownstream = true; + if (CurrentDownstreamHAC && !CurrentDownstreamHAC->IsPendingKill()) + { + // Go through the HAC's input + for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs) + { + if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill()) + continue; + + EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType(); + if (CurrentDownstreamInputType != EHoudiniInputType::Asset + && CurrentDownstreamInputType != EHoudiniInputType::World) + continue; + + if (!CurrentDownstreamInput->ContainsInputObject(this, CurrentDownstreamInputType)) + continue; + + if (CurrentDownstreamHAC->bCookOnAssetInputCook) + { + // Mark that HAC's input has changed + CurrentDownstreamInput->MarkChanged(true); + } + bRemoveDownstream = false; + } + } + + if (bRemoveDownstream) + { + DownstreamToDelete.Add(CurrentDownstreamHAC); + } + } + + for (auto ToDelete : DownstreamToDelete) + { + DownstreamHoudiniAssets.Remove(ToDelete); + } + + return true; +} + +bool +UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() +{ + for (auto& CurrentInput : Inputs) + { + EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World) + continue; + + TArray* ObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInputType); + if (!ObjectArray) + continue; + + for (auto& CurrentInputObject : (*ObjectArray)) + { + // Get the input HDA + UHoudiniAssetComponent* InputHAC = CurrentInputObject + ? Cast(CurrentInputObject->GetObject()) + : nullptr; + + if (!InputHAC) + continue; + + // If the input HDA needs to be instantiated, force him to instantiate + // if the input HDA is in any other state than None, we need to wait for him + // to finish whatever it's doing + if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) + { + // Tell the input HAC to instantiate + InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; + + // We need to wait + return true; + } + else if (InputHAC->GetAssetState() != EHoudiniAssetState::None) + { + // We need to wait + return true; + } + } + } + + return false; +} + +void +UHoudiniAssetComponent::BeginDestroy() +{ + if (CanDeleteHoudiniNodes()) + { + } + + // Gets called through UnRegisterHoudiniComponent(). + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + + // Unregister ourself so our houdini node can be deleted + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + Super::BeginDestroy(); +} + +void +UHoudiniAssetComponent::MarkAsNeedCook() +{ + // Force the asset state to NeedCook + //AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = true; + bRebuildRequested = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/trigger update + for (auto CurrentParam : Parameters) + { + if (!IsValid(CurrentParam)) + continue; + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(true); + } + + // We need to mark all our inputs as changed/trigger update + for (auto CurrentInput : Inputs) + { + if (!IsValid(CurrentInput)) + continue; + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(true); + CurrentInput->MarkDataUploadNeeded(true); + } + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +void +UHoudiniAssetComponent::MarkAsNeedRebuild() +{ + // Invalidate the asset ID + //AssetId = -1; + + // Force the asset state to NeedRebuild + AssetState = EHoudiniAssetState::NeedRebuild; + AssetStateResult = EHoudiniAssetStateResult::None; + + // Reset some of the asset's flag + //AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = true; + bFullyLoaded = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/trigger update + for (auto CurrentParam : Parameters) + { + if (!IsValid(CurrentParam)) + continue; + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(true); + } + + // We need to mark all our inputs as changed/trigger update + for (auto CurrentInput : Inputs) + { + if (!IsValid(CurrentInput)) + continue; + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(true); + CurrentInput->MarkDataUploadNeeded(true); + } + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +// Marks the asset as needing to be instantiated +void +UHoudiniAssetComponent::MarkAsNeedInstantiation() +{ + // Invalidate the asset ID + AssetId = -1; + + if (Parameters.Num() <= 0 && Inputs.Num() <= 0 && Outputs.Num() <= 0) + { + // The asset has no parameters or inputs. + // This likely indicates it has never cooked/been instantiated. + // Set its state to PreInstantiation to force its instantiation + // so that we can have its parameters/input interface + AssetState = EHoudiniAssetState::PreInstantiation; + } + else + { + // The asset has cooked before since we have a parameter/input interface + // Set its state to need instantiation so that the asset is instantiated + // after being modified + AssetState = EHoudiniAssetState::NeedInstantiation; + } + + AssetStateResult = EHoudiniAssetStateResult::None; + + // Reset some of the asset's flag + AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = false; + bFullyLoaded = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/not triggering update + for (auto CurrentParam : Parameters) + { + if (CurrentParam) + { + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(false); + } + } + + // We need to mark all our inputs as changed/not triggering update + for (auto CurrentInput : Inputs) + { + if (CurrentInput) + { + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(false); + CurrentInput->MarkDataUploadNeeded(true); + } + } + + /*if (!CanInstantiateAsset()) + { + AssetState = EHoudiniAssetState::None; + AssetStateResult = EHoudiniAssetStateResult::None; + }*/ + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +void UHoudiniAssetComponent::MarkAsBlueprintStructureModified() +{ + bBlueprintStructureModified = true; +} + +void UHoudiniAssetComponent::MarkAsBlueprintModified() +{ + bBlueprintModified = true; +} + +void +UHoudiniAssetComponent::PostLoad() +{ + Super::PostLoad(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + bool bAutomaticLegacyHDARebuild = HoudiniRuntimeSettings->bAutomaticLegacyHDARebuild; + + // Legacy serialization: either try to convert or skip depending the setting value + if (bEnableBackwardCompatibility && Version1CompatibilityHAC != nullptr) + { + // If we have deserialized legacy v1 data, attempt to convert it now + ConvertLegacyData(); + + if(bAutomaticLegacyHDARebuild) + MarkAsNeedRebuild(); + else + MarkAsNeedInstantiation(); + } + else + { + // Normal v2 objet, mark as need instantiation + MarkAsNeedInstantiation(); + } + + // Component has been loaded, not duplicated + bHasBeenDuplicated = false; + + // We need to register ourself + RegisterHoudiniComponent(this); + + // Register our PDG Asset link if we have any + +} + +void +UHoudiniAssetComponent::PostEditImport() +{ + Super::PostEditImport(); + + MarkAsNeedInstantiation(); + + // Component has been duplicated, not loaded + // We do need the loaded flag to reapply parameters, inputs + // and properly update some of the output objects + bHasBeenDuplicated = true; + + //RemoveAllAttachedComponents(); + + AssetState = EHoudiniAssetState::PreInstantiation; + AssetStateResult = EHoudiniAssetStateResult::None; + + // TODO? + // REGISTER? +} + +void +UHoudiniAssetComponent::UpdatePostDuplicate() +{ + // TODO: + // - Keep the output objects/components (remove duplicatetransient on the output object uproperties) + // - Duplicate created objects (ie SM) and materials + // - Update the output components to use these instead + // This should remove the need for a cook on duplicate + + // For now, we simply clean some of the HAC's component manually + const TArray Children = GetAttachChildren(); + + for (auto & NextChild : Children) + { + if (!NextChild || NextChild->IsPendingKill()) + continue; + + USceneComponent * ComponentToRemove = nullptr; + if (NextChild->IsA()) + { + ComponentToRemove = NextChild; + } + else if (NextChild->IsA()) + { + ComponentToRemove = NextChild; + } + /* do not destroy attached duplicated editable curves, they are needed to restore editable curves + else if (NextChild->IsA()) + { + // Remove duplicated editable curve output's Houdini Spline Component, since they will be re-built at duplication. + UHoudiniSplineComponent * HoudiniSplineComponent = Cast(NextChild); + if (HoudiniSplineComponent && HoudiniSplineComponent->IsEditableOutputCurve()) + ComponentToRemove = NextChild; + } + */ + if (ComponentToRemove) + { + ComponentToRemove->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + ComponentToRemove->UnregisterComponent(); + ComponentToRemove->DestroyComponent(); + } + } + + // if there is an associated PDG asset link, call its UpdatePostDuplicate to cleanup references to + // to the original instance's PDG output actors + if (IsValid(PDGAssetLink)) + { + PDGAssetLink->UpdatePostDuplicate(); + } + + SetHasBeenDuplicated(false); +} + +bool UHoudiniAssetComponent::IsInputTypeSupported(EHoudiniInputType InType) const +{ + return true; +} + +bool UHoudiniAssetComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const +{ + return true; +} + +bool +UHoudiniAssetComponent::IsPreview() const +{ + return bCachedIsPreview; +} + +bool UHoudiniAssetComponent::IsValidComponent() const +{ + return true; +} + +void UHoudiniAssetComponent::OnFullyLoaded() +{ + bFullyLoaded = true; +} + + +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); + } + */ +} + +void +UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + + if (CanDeleteHoudiniNodes()) + { + } + + // Unregister ourself so our houdini node can be deleted + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + HoudiniAsset = nullptr; + + // Clear Parameters + for (UHoudiniParameter*& CurrentParm : Parameters) + { + if (CurrentParm && !CurrentParm->IsPendingKill()) + { + CurrentParm->ConditionalBeginDestroy(); + } + else if (GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE) + { + // TODO unneeded log? + // Avoid spamming that error when leaving PIE mode + HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } + + CurrentParm = nullptr; + } + + Parameters.Empty(); + + // Clear Inputs + for (UHoudiniInput*& CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + // Destroy connected Houdini asset. + CurrentInput->ConditionalBeginDestroy(); + CurrentInput = nullptr; + } + + Inputs.Empty(); + + // Clear Output + for (UHoudiniOutput*& CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + // Destroy all Houdini created socket actors. + TArray & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors(); + for (auto & CurCreatedActor : CurCreatedSocketActors) + { + if (!CurCreatedActor || CurCreatedActor->IsPendingKill()) + continue; + + CurCreatedActor->Destroy(); + } + CurCreatedSocketActors.Empty(); + + // Detach all Houdini attached socket actors + TArray & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors(); + for (auto & CurAttachedSocketActor : CurAttachedSocketActors) + { + if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill()) + continue; + + CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + } + CurAttachedSocketActors.Empty(); + +#if WITH_EDITOR + // Clean up foliages instances + for (auto& CurrentOutputObject : CurrentOutput->GetOutputObjects()) + { + // Foliage instancers store a HISMC in the components + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentOutputObject.Value.OutputComponent); + if (!FoliageHISMC) + continue; + + UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + continue; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(FoliageHISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + continue; + + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + if (!FoliageType || FoliageType->IsPendingKill()) + continue; + + // Clean up the instances generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(this, FoliageType); + + if (FoliageHISMC->GetInstanceCount() > 0) + { + // If the component still has instances left after the cleanup, + // make sure that we dont delete it, as the leftover instances are likely hand-placed + CurrentOutputObject.Value.OutputComponent = nullptr; + } + else + { + // Remove the foliage type if it doesn't have any more instances + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + } + } +#endif + + CurrentOutput->Clear(); + // Destroy connected Houdini asset. + CurrentOutput->ConditionalBeginDestroy(); + CurrentOutput = nullptr; + } + + Outputs.Empty(); + + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + // Unregister ourself so our houdini node can be delete. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); + + + // Clear all TOP data and temporary geo/objects from the PDG asset link (if valid) + if (IsValid(PDGAssetLink)) + { +#if WITH_EDITOR + const UWorld* const World = GetWorld(); + if (IsValid(World)) + { + // Only do this for editor worlds, only interactively (not during engine shutdown or garbage collection) + if (World->WorldType == EWorldType::Editor && GIsRunning && !GIsGarbageCollecting) + { + // In case we are recording a transaction (undo, for example) notify that the object will be + // modified. + PDGAssetLink->Modify(); + PDGAssetLink->ClearAllTOPData(); + } + } +#endif + } + + Super::OnComponentDestroyed(bDestroyingHierarchy); +} + +void UHoudiniAssetComponent::RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) +{ + // Registration of this component is wrapped in this virtual function to allow + // derived classed to override this behaviour. + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(InComponent); +} + +void +UHoudiniAssetComponent::OnRegister() +{ + Super::OnRegister(); + + // NOTE: Wait until HoudiniEngineTick() before deciding to mark this object as fully loaded + // since preview components need to wait for component templates to finish their initialization + // before being able to perform state transfers. + + /* + // 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(); + } + } + */ + + // Let TickInitialization() take care of manipulating the bFullyLoaded state. + //bFullyLoaded = true; + + //// If we're constructing editable components in the SCS editor, set the component instance corresponding to this node for editing purposes + // + //USimpleConstructionScript* SCS = GetSCS(); + //if (SCS == nullptr) + //{ + // bFullyLoaded = true; + //} + //else + //{ + // if(SCS->IsConstructingEditorComponents()) + // { + // // We're not fully loaded yet. We're expecting + // } + // else + // { + // bFullyLoaded = true; + // } + //} + +} + +UHoudiniParameter* +UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) +{ + if (!InOtherParam || InOtherParam->IsPendingKill()) + return nullptr; + + for (auto CurrentParam : Parameters) + { + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + if (CurrentParam->Matches(*InOtherParam)) + return CurrentParam; + } + + return nullptr; +} + +UHoudiniInput* +UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) +{ + if (!InOtherInput || InOtherInput->IsPendingKill()) + return nullptr; + + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->Matches(*InOtherInput)) + return CurrentInput; + } + + return nullptr; +} + +UHoudiniHandleComponent* +UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle) +{ + if (!InOtherHandle || InOtherHandle->IsPendingKill()) + return nullptr; + + for (auto CurrentHandle : HandleComponents) + { + if (!CurrentHandle || CurrentHandle->IsPendingKill()) + continue; + + if (CurrentHandle->Matches(*InOtherHandle)) + return CurrentHandle; + } + + return nullptr; +} + +UHoudiniParameter* +UHoudiniAssetComponent::FindParameterByName(const FString& InParamName) +{ + for (auto CurrentParam : Parameters) + { + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + if (CurrentParam->GetParameterName().Equals(InParamName)) + return CurrentParam; + } + + return nullptr; +} + + +void +UHoudiniAssetComponent::OnChildAttached(USceneComponent* ChildComponent) +{ + Super::OnChildAttached(ChildComponent); + + // ... Do corresponding things for other houdini component types. + // ... +} + + +void +UHoudiniAssetComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + + SetHasComponentTransformChanged(true); +} + +void UHoudiniAssetComponent::HoudiniEngineTick() +{ + if (!IsFullyLoaded()) + { + OnFullyLoaded(); + } +} + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + if (!Property) + return; + + FName PropertyName = Property->GetFName(); + + // Changing the Houdini Asset? + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, HoudiniAsset)) + { + OnHoudiniAssetChanged(); + } + else if (PropertyName == GetRelativeLocationPropertyName() + || PropertyName == GetRelativeRotationPropertyName() + || PropertyName == GetRelativeScale3DPropertyName()) + { + SetHasComponentTransformChanged(true); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bOverrideGlobalProxyStaticMeshSettings) + || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bEnableProxyStaticMeshRefinementByTimerOverride) + || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, ProxyMeshAutoRefineTimeoutSecondsOverride)) + { + ClearRefineMeshesTimer(); + // Reset the timer + // SetRefineMeshesTimer will check the relevant settings and only set the timer if enabled via settings + SetRefineMeshesTimer(); + } + //else if (PropertyName == TEXT("Mobility")) + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, 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); + } + } + //else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bVisible)) + else if (PropertyName == TEXT("bVisible")) + { + // Visibility has changed, propagate it to children. + SetVisibility(IsVisible(), true); + } + //else if (PropertyName == TEXT("bHiddenInGame")) + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bHiddenInGame)) + { + // Visibility has changed, propagate it to children. + SetHiddenInGame(bHiddenInGame, true); + } + else + { + // TODO: + // Propagate properties (mobility/visibility etc.. to children components) + // Look in v1 for: if (Property->HasMetaData(TEXT("Category"))) {} and HOUDINI_UPDATE_ALL_CHILD_COMPONENTS + } + +} +#endif + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::PostEditUndo() +{ + Super::PostEditUndo(); + + if (!IsPendingKill()) + { + // Make sure we are registered with the HER singleton + // We could be undoing a HoudiniActor delete + if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(this)) + { + MarkAsNeedInstantiation(); + + // Component has been loaded, not duplicated + bHasBeenDuplicated = false; + + RegisterHoudiniComponent(this); + } + } +} + +#endif + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::OnActorMoved(AActor* Actor) +{ + if (GetOwner() != Actor) + return; + + SetHasComponentTransformChanged(true); +} +#endif + +void +UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged) +{ + // Only update the value if we're fully loaded + // This avoid triggering a recook when loading a level + if(bFullyLoaded) + bHasComponentTransformChanged = InHasChanged; +} + +void +UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // Check the object validity + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // If it is the same object, do nothing. + if (InPDGAssetLink == PDGAssetLink) + return; + + PDGAssetLink = InPDGAssetLink; +} + + +FBoxSphereBounds +UHoudiniAssetComponent::CalcBounds(const FTransform & LocalToWorld) const +{ + FBoxSphereBounds LocalBounds; + FBox BoundingBox = GetAssetBounds(nullptr, false); + 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; +} + + +FBox +UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const +{ + FBox BoxBounds(ForceInitToZero); + + // Query the bounds for all output objects + for (auto & CurOutput : Outputs) + { + if (!CurOutput || CurOutput->IsPendingKill()) + continue; + + BoxBounds += CurOutput->GetBounds(); + } + + // Query the bounds for all our inputs + for (auto & CurInput : Inputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + BoxBounds += CurInput->GetBounds(); + } + + // Query the bounds for all input parameters + for (auto & CurParam : Parameters) + { + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (CurParam->GetParameterType() != EHoudiniParameterType::Input) + continue; + + UHoudiniParameterOperatorPath* InputParam = Cast(CurParam); + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (!InputParam->HoudiniInput.IsValid()) + continue; + + BoxBounds += InputParam->HoudiniInput.Get()->GetBounds(); + } + + // Query the bounds for all our Houdini handles + for (auto & CurHandleComp : HandleComponents) + { + if (!CurHandleComp || CurHandleComp->IsPendingKill()) + continue; + + BoxBounds += CurHandleComp->GetBounds(); + } + + // Also scan all our decendants for SMC bounds not just top-level children + // ( split mesh instances' mesh bounds were not gathered proiperly ) + TArray 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(pChild)) + { + if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + continue; + + FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); + if (StaticMeshBounds.IsValid) + BoxBounds += StaticMeshBounds; + } + } + + // If nothing was found, init with the asset's location + if (BoxBounds.GetVolume() == 0.0f) + BoxBounds += GetComponentLocation(); + + return BoxBounds; +} + +void +UHoudiniAssetComponent::ClearRefineMeshesTimer() +{ + UWorld *World = GetWorld(); + if (!World) + { + //HOUDINI_LOG_ERROR(TEXT("Cannot ClearRefineMeshesTimer, World is nullptr!")); + return; + } + + World->GetTimerManager().ClearTimer(RefineMeshesTimer); +} + +void +UHoudiniAssetComponent::SetRefineMeshesTimer() +{ + UWorld *World = GetWorld(); + if (!World) + { + HOUDINI_LOG_ERROR(TEXT("Cannot SetRefineMeshesTimer, World is nullptr!")); + return; + } + + // Check if timer-based proxy mesh refinement is enable for this component + const bool bEnableTimer = IsProxyStaticMeshRefinementByTimerEnabled(); + const float TimeSeconds = GetProxyMeshAutoRefineTimeoutSeconds(); + if (bEnableTimer) + { + World->GetTimerManager().SetTimer(RefineMeshesTimer, this, &UHoudiniAssetComponent::OnRefineMeshesTimerFired, 1.0f, false, TimeSeconds); + } + else + { + World->GetTimerManager().ClearTimer(RefineMeshesTimer); + } +} + +void +UHoudiniAssetComponent::OnRefineMeshesTimerFired() +{ + HOUDINI_LOG_MESSAGE(TEXT("UHoudiniAssetComponent::OnRefineMeshesTimerFired()")); + if (OnRefineMeshesTimerDelegate.IsBound()) + { + OnRefineMeshesTimerDelegate.Broadcast(this); + } +} + +bool +UHoudiniAssetComponent::HasAnyCurrentProxyOutput() const +{ + for (const UHoudiniOutput *Output : Outputs) + { + if (Output->HasAnyCurrentProxy()) + { + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasAnyProxyOutput() const +{ + for (const UHoudiniOutput *Output : Outputs) + { + if (Output->HasAnyProxy()) + { + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasAnyOutputComponent() const +{ + for (UHoudiniOutput *Output : Outputs) + { + for(auto& CurrentOutputObject : Output->GetOutputObjects()) + { + if(CurrentOutputObject.Value.OutputComponent) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasOutputObject(UObject* InOutputObjectToFind) const +{ + for (const auto& CurOutput : Outputs) + { + for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) + { + if (CurOutputObject.Value.OutputObject == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.OutputComponent == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.ProxyObject == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.ProxyComponent == InOutputObjectToFind) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const +{ + // Get the state of the asset and check if it is pre-cook, cooked, pending delete/rebuild or invalid + bOutNeedsRebuildOrDelete = false; + bOutInvalidState = false; + switch (AssetState) + { + case EHoudiniAssetState::NeedInstantiation: + case EHoudiniAssetState::PreInstantiation: + case EHoudiniAssetState::Instantiating: + case EHoudiniAssetState::PreCook: + case EHoudiniAssetState::Cooking: + case EHoudiniAssetState::PostCook: + case EHoudiniAssetState::PreProcess: + case EHoudiniAssetState::Processing: + return false; + break; + case EHoudiniAssetState::None: + return true; + break; + case EHoudiniAssetState::NeedRebuild: + case EHoudiniAssetState::NeedDelete: + case EHoudiniAssetState::Deleting: + bOutNeedsRebuildOrDelete = true; + break; + default: + bOutInvalidState = true; + break; + } + + return false; +} + +void +UHoudiniAssetComponent::SetInputPresets(const TMap& InPresets) +{ + // Set the input preset for this HAC +#if WITH_EDITOR + InputPresets = InPresets; +#endif +} + + +void +UHoudiniAssetComponent::ApplyInputPresets() +{ + if (InputPresets.Num() <= 0) + return; + +#if WITH_EDITOR + // Ignore inputs that have been preset to curve + TArray InputArray; + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetInputType() != EHoudiniInputType::Curve) + InputArray.Add(CurrentInput); + } + + // Try to apply the supplied Object to the Input + for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset) + { + UObject * Object = IterToolPreset.Key(); + if (!Object || Object->IsPendingKill()) + continue; + + int32 InputNumber = IterToolPreset.Value(); + if (!InputArray.IsValidIndex(InputNumber)) + continue; + + // If the object is a landscape, add a new landscape input + if (Object->IsA()) + { + // selecting a landscape + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Landscape); + if (InsertNum == 0) + { + // Landscape inputs only support one object! + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Landscape, InsertNum, Object); + } + } + + // If the object is an actor, add a new world input + if (Object->IsA()) + { + // selecting an actor + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::World); + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::World, InsertNum, Object); + } + + // If the object is a static mesh, add a new geometry input (TODO: or BP ? ) + if (Object->IsA()) + { + // selecting a Staticn Mesh + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Geometry, InsertNum, Object); + } + + if (Object->IsA()) + { + // selecting a Houdini Asset + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Asset); + if (InsertNum == 0) + { + // Assert inputs only support one object! + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Asset, InsertNum, Object); + } + } + } + + // The input objects have been set, now change the input type + bool bBPStructureModified = false; + for (auto CurrentInput : Inputs) + { + int32 NumGeo = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + int32 NumAsset = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Asset); + int32 NumWorld = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::World); + int32 NumLandscape = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Landscape); + + EHoudiniInputType NewInputType = EHoudiniInputType::Invalid; + if (NumLandscape > 0 && NumLandscape >= NumGeo && NumLandscape >= NumAsset && NumLandscape >= NumWorld) + NewInputType = EHoudiniInputType::Landscape; + else if (NumWorld > 0 && NumWorld >= NumGeo && NumWorld >= NumAsset && NumWorld >= NumLandscape) + NewInputType = EHoudiniInputType::World; + else if (NumAsset > 0 && NumAsset >= NumGeo && NumAsset >= NumWorld && NumAsset >= NumLandscape) + NewInputType = EHoudiniInputType::Asset; + else if (NumGeo > 0 && NumGeo >= NumAsset && NumGeo >= NumWorld && NumGeo >= NumLandscape) + NewInputType = EHoudiniInputType::Geometry; + + if (NewInputType == EHoudiniInputType::Invalid) + continue; + + // Change the input type, unless if it was preset to a different type and we have object for the preset type + if (CurrentInput->GetInputType() == EHoudiniInputType::Geometry && NewInputType != EHoudiniInputType::Geometry) + { + CurrentInput->SetInputType(NewInputType, bBPStructureModified); + } + else + { + // Input type was preset, only change if that type is empty + if(CurrentInput->GetNumberOfInputObjects() <= 0) + CurrentInput->SetInputType(NewInputType, bBPStructureModified); + } + } + if (bBPStructureModified) + { + MarkAsBlueprintStructureModified(); + } +#endif + + // Discard the tool presets after their first setup + InputPresets.Empty(); +} + + +bool +UHoudiniAssetComponent::IsComponentValid() const +{ + if (!IsValidLowLevel()) + return false; + + if (IsTemplate()) + return false; + + if (IsPendingKillOrUnreachable()) + return false; + + if (!GetOuter()) //|| !GetOuter()->GetLevel() ) + return false; + + return true; +} + +bool +UHoudiniAssetComponent::IsInstantiatingOrCooking() const +{ + return HapiGUID.IsValid(); } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h index 1c05dfea2..11aa53915 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h @@ -1,806 +1,806 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniOutput.h" -#include "HoudiniInputTypes.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Components/PrimitiveComponent.h" -#include "Components/SceneComponent.h" - -#include "HoudiniAssetComponent.generated.h" - -class UHoudiniAsset; -class UHoudiniParameter; -class UHoudiniInput; -class UHoudiniOutput; -class UHoudiniHandleComponent; -class UHoudiniPDGAssetLink; -class UHoudiniAssetComponent_V1; - -UENUM() -enum class EHoudiniAssetState : uint8 -{ - // Loaded / Duplicated HDA, - // Will need to be instantiated upon change/update - NeedInstantiation, - - // Newly created HDA, needs to be instantiated immediately - PreInstantiation, - - // Instantiating task in progress - Instantiating, - - // Instantiated HDA, needs to be cooked immediately - PreCook, - - // Cooking task in progress - Cooking, - - // Cooking has finished - PostCook, - - // Cooked HDA, needs to be processed immediately - PreProcess, - - // Processing task in progress - Processing, - - // Processed / Updated HDA - // Will need to be cooked upon change/update - None, - - // Asset needs to be rebuilt (Deleted/Instantiated/Cooked) - NeedRebuild, - - // Asset needs to be deleted - NeedDelete, - - // Deleting - Deleting, - - // Process component template. This is ticking has very limited - // functionality, typically limited to checking for parameter updates - // in order to trigger PostEditChange() to run construction scripts again. - ProcessTemplate, -}; - -UENUM() -enum class EHoudiniAssetStateResult : uint8 -{ - None, - Working, - Success, - FinishedWithError, - FinishedWithFatalError, - Aborted -}; - -UENUM() -enum class EHoudiniStaticMeshMethod : uint8 -{ - // Use the RawMesh method to build the UStaticMesh - RawMesh, - // Use the FMeshDescription method to build the UStaticMesh - FMeshDescription, - // Build a fast proxy mesh: UHoudiniStaticMesh - UHoudiniStaticMesh, -}; - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EHoudiniEngineBakeOption : uint8 -{ - ToActor, - ToBlueprint, - ToFoliage, - ToWorldOutliner, -}; -#endif - -class UHoudiniAssetComponent; - -DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetEvent, UHoudiniAsset*); -DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetComponentEvent, UHoudiniAssetComponent*) - -UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility), editinlinenew) -class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent -{ - GENERATED_UCLASS_BODY() - - // Declare translators as friend so they can easily directly modify - // Inputs, outputs and parameters - friend class FHoudiniEngineManager; - friend struct FHoudiniOutputTranslator; - friend struct FHoudiniInputTranslator; - friend struct FHoudiniSplineTranslator; - friend struct FHoudiniParameterTranslator; - friend struct FHoudiniPDGManager; - friend struct FHoudiniHandleTranslator; - -#if WITH_EDITORONLY_DATA - friend class FHoudiniAssetComponentDetails; -#endif - -public: - - // Declare the delegate that is broadcast when RefineMeshesTimer fires - DECLARE_MULTICAST_DELEGATE_OneParam(FOnRefineMeshesTimerDelegate, UHoudiniAssetComponent*); - DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPostCookBakeDelegate, UHoudiniAssetComponent*); - - virtual ~UHoudiniAssetComponent(); - - virtual void Serialize(FArchive & Ar) override; - - virtual bool ConvertLegacyData(); - - // Called after the C++ constructor and after the properties have been initialized, including those loaded from config. - // This is called before any serialization or other setup has happened. - virtual void PostInitProperties() override; - - // Returns the Owner actor / HAC name - FString GetDisplayName() const; - - // Indicates if the HAC needs to be updated - bool NeedUpdate() const; - - // Indicates if the HAC's transform needs to be updated - bool NeedTransformUpdate() const { return (bHasComponentTransformChanged && bUploadTransformsToHoudiniEngine); }; - - // Indicates if any of the HAC's output components needs to be updated (no recook needed) - bool NeedOutputUpdate() const; - - // Check whether any inputs / outputs / parameters have made blueprint modifications. - bool NeedBlueprintStructureUpdate() const; - bool NeedBlueprintUpdate() const; - - // Try to find one of our parameter that matches another (name, type, size and enabled) - UHoudiniParameter* FindMatchingParameter(UHoudiniParameter* InOtherParam); - - // Try to find one of our input that matches another one (name, isobjpath, index / parmId) - UHoudiniInput* FindMatchingInput(UHoudiniInput* InOtherInput); - - // Try to find one of our handle that matches another one (name and handle type) - UHoudiniHandleComponent* FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle); - - // Finds a parameter by name - UHoudiniParameter* FindParameterByName(const FString& InParamName); - - // Returns True if the component has at least one mesh output of class U - template - bool HasMeshOutputObjectOfClass() const; - - // Returns True if the component has at least one mesh output with a current proxy - bool HasAnyCurrentProxyOutput() const; - - // Returns True if the component has at least one proxy mesh output (not necessarily current/displayed) - bool HasAnyProxyOutput() const; - - // Returns True if the component has at least one non-proxy output component amongst its outputs - bool HasAnyOutputComponent() const; - - // Returns true if the component has InOutputObjectToFind in its output object - bool HasOutputObject(UObject* InOutputObjectToFind) const; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - UHoudiniAsset * GetHoudiniAsset() const; - int32 GetAssetId() const { return AssetId; }; - EHoudiniAssetState GetAssetState() const { return AssetState; }; - FString GetAssetStateAsString() const { return FHoudiniEngineRuntimeUtils::EnumToString(TEXT("EHoudiniAssetState"), GetAssetState()); }; - EHoudiniAssetStateResult GetAssetStateResult() const { return AssetStateResult; }; - FGuid GetHapiGUID() const { return HapiGUID; }; - FGuid GetComponentGUID() const { return ComponentGUID; }; - - int32 GetNumInputs() const { return Inputs.Num(); }; - int32 GetNumOutputs() const { return Outputs.Num(); }; - int32 GetNumParameters() const { return Parameters.Num(); }; - int32 GetNumHandles() const { return HandleComponents.Num(); }; - - UHoudiniInput* GetInputAt(const int32& Idx) { return Inputs.IsValidIndex(Idx) ? Inputs[Idx] : nullptr; }; - UHoudiniOutput* GetOutputAt(const int32& Idx) { return Outputs.IsValidIndex(Idx) ? Outputs[Idx] : nullptr;}; - UHoudiniParameter* GetParameterAt(const int32& Idx) { return Parameters.IsValidIndex(Idx) ? Parameters[Idx] : nullptr;}; - UHoudiniHandleComponent* GetHandleComponentAt(const int32& Idx) { return HandleComponents.IsValidIndex(Idx) ? HandleComponents[Idx] : nullptr; }; - - void GetOutputs(TArray& OutOutputs) const; - - TArray& GetBakedOutputs() { return BakedOutputs; } - const TArray& GetBakedOutputs() const { return BakedOutputs; } - - /* - TArray& GetParameters() { return Parameters; }; - TArray& GetInputs() { return Inputs; }; - TArray& GetOutputs() { return Outputs; }; - */ - - bool IsCookingEnabled() const { return bEnableCooking; }; - bool HasBeenLoaded() const { return bHasBeenLoaded; }; - bool HasBeenDuplicated() const { return bHasBeenDuplicated; }; - bool HasRecookBeenRequested() const { return bRecookRequested; }; - bool HasRebuildBeenRequested() const { return bRebuildRequested; }; - - //bool GetEditorPropertiesNeedFullUpdate() const { return bEditorPropertiesNeedFullUpdate; }; - - int32 GetAssetCookCount() const { return AssetCookCount; }; - - bool IsFullyLoaded() const { return bFullyLoaded; }; - - UHoudiniPDGAssetLink * GetPDGAssetLink() const { return PDGAssetLink; }; - - virtual bool IsProxyStaticMeshEnabled() const; - bool IsProxyStaticMeshRefinementByTimerEnabled() const; - float GetProxyMeshAutoRefineTimeoutSeconds() const; - bool IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const; - bool IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const; - // If true, then the next cook should not build proxy meshes, regardless of global or override settings, - // but should instead directly build UStaticMesh - bool HasNoProxyMeshNextCookBeenRequested() const { return bNoProxyMeshNextCookRequested; } - // Returns true if the asset state indicates that it has been cooked in this session, false otherwise. - bool IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const; - // Returns true if the asset should be bake after the next cook - bool IsBakeAfterNextCookEnabled() const { return bBakeAfterNextCook; } - - FOnPostCookBakeDelegate& GetOnPostCookBakeDelegate() { return OnPostCookBakeDelegate; } - - // Derived blueprint based components will check whether the template - // component contains updates that needs to processed. - bool NeedUpdateParameters() const; - bool NeedUpdateInputs() const; - - // Returns true if the component has any previous baked output recorded in its outputs - bool HasPreviousBakeOutput() const; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - //void SetAssetId(const int& InAssetId); - //void SetAssetState(const EHoudiniAssetState& InAssetState) { AssetState = InAssetState; }; - //void SetAssetStateResult(const EHoudiniAssetStateResult& InAssetStateResult) { AssetStateResult = InAssetStateResult; }; - - //void SetHapiGUID(const FGuid& InGUID) { HapiGUID = InGUID; }; - //void SetComponentGUID(const FGuid& InGUID) { ComponentGUID = InGUID; }; - - //UFUNCTION(BlueprintSetter) - virtual void SetHoudiniAsset(UHoudiniAsset * NewHoudiniAsset); - - void SetHasBeenLoaded(const bool& InLoaded) { bHasBeenLoaded = InLoaded; }; - - void SetHasBeenDuplicated(const bool& InDuplicated) { bHasBeenDuplicated = InDuplicated; }; - - //void SetEditorPropertiesNeedFullUpdate(const bool& InUpdate) { bEditorPropertiesNeedFullUpdate = InUpdate; }; - - // Marks the assets as needing a recook - void MarkAsNeedCook(); - // Marks the assets as needing a full rebuild - void MarkAsNeedRebuild(); - // Marks the asset as needing to be instantiated - void MarkAsNeedInstantiation(); - // The blueprint has been structurally modified - void MarkAsBlueprintStructureModified(); - // The blueprint has been modified but not structurally changed. - void MarkAsBlueprintModified(); - - // - void SetAssetCookCount(const int32& InCount) { AssetCookCount = InCount; }; - // - void SetRecookRequested(const bool& InRecook) { bRecookRequested = InRecook; }; - // - void SetRebuildRequested(const bool& InRebuild) { bRebuildRequested = InRebuild; }; - // - void SetHasComponentTransformChanged(const bool& InHasChanged); - - // Set to True to force the next cook to not build a proxy mesh (regardless of global or override settings) and - // instead build a UStaticMesh directly (if applicable for the output type). - void SetNoProxyMeshNextCookRequested(bool bInNoProxyMeshNextCookRequested) { bNoProxyMeshNextCookRequested = bInNoProxyMeshNextCookRequested; } - - // Set to True to force the next cook to bake the asset after the cook completes. - void SetBakeAfterNextCookEnabled(bool bInEnabled) { bBakeAfterNextCook = bInEnabled; } - - // - void SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink); - // - virtual void OnHoudiniAssetChanged(); - - // - void AddDownstreamHoudiniAsset(UHoudiniAssetComponent* InDownstreamAsset) { DownstreamHoudiniAssets.Add(InDownstreamAsset); }; - // - void RemoveDownstreamHoudiniAsset(UHoudiniAssetComponent* InRemoveDownstreamAsset) { DownstreamHoudiniAssets.Remove(InRemoveDownstreamAsset); }; - // - void ClearDownstreamHoudiniAsset() { DownstreamHoudiniAssets.Empty(); }; - // - bool NotifyCookedToDownstreamAssets(); - // - bool NeedsToWaitForInputHoudiniAssets(); - - // Clear/disable the RefineMeshesTimer. - void ClearRefineMeshesTimer(); - - // Re-set the RefineMeshesTimer to its default value. - void SetRefineMeshesTimer(); - - virtual void OnRefineMeshesTimerFired(); - - // Called by RefineMeshesTimer when the timer is triggered. - // Checks for any UHoudiniStaticMesh in Outputs and bakes UStaticMesh for them via FHoudiniMeshTranslator. - FOnRefineMeshesTimerDelegate& GetOnRefineMeshesTimerDelegate() { return OnRefineMeshesTimerDelegate; } - - // Returns true if the asset is valid for cook/bake - virtual bool IsComponentValid() const; - // Return false if this component has no cooking or instantiation in progress. - bool IsInstantiatingOrCooking() const; - - // HoudiniEngineTick will be called by HoudiniEngineManager::Tick() - virtual void HoudiniEngineTick(); - -#if WITH_EDITOR - // This alternate version of PostEditChange is called when properties inside structs are modified. The property that was actually modified - // is located at the tail of the list. The head of the list of the FStructProperty member variable that contains the property that was modified. - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; - - //Called after applying a transaction to the object. Default implementation simply calls PostEditChange. - virtual void PostEditUndo() override; - - // Whether this component is currently open in a Blueprint editor. This - // method is overridden by HoudiniAssetBlueprintComponent. - virtual bool HasOpenEditor() const { return false; }; - -#endif - - virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent); - - virtual void OnRegister() override; - - // USceneComponent methods. - virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override; - virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; - - FBox GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const; - - // Set this component's input presets - void SetInputPresets(const TMap& InPresets); - // Apply the preset input for HoudiniTools - void ApplyInputPresets(); - - // return the cached component template, if available. - virtual UHoudiniAssetComponent* GetCachedTemplate() const { return nullptr; } - - //------------------------------------------------------------------------------------------------ - // Supported Features - //------------------------------------------------------------------------------------------------ - - // Whether or not this component should be able to delete the Houdini nodes - // that correspond to the HoudiniAsset when being deregistered. - virtual bool CanDeleteHoudiniNodes() const { return true; } - - virtual bool IsInputTypeSupported(EHoudiniInputType InType) const; - virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const; - - //------------------------------------------------------------------------------------------------5 - // Characteristics - //------------------------------------------------------------------------------------------------ - - // Try to determine whether this component belongs to a preview actor. - // Preview / Template components need to sync their data for HDA cooks and output translations. - bool IsPreview() const; - - virtual bool IsValidComponent() const; - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - // TODO: After the cook worfklow rework, most of these won't be needed anymore, so clean up! - //FHoudiniAssetComponentEvent OnTemplateParametersChanged; - //FHoudiniAssetComponentEvent OnPreAssetCook; - //FHoudiniAssetComponentEvent OnCookCompleted; - //FHoudiniAssetComponentEvent OnOutputProcessingCompleted; - - /*virtual void BroadcastParametersChanged(); - virtual void BroadcastPreAssetCook(); - virtual void BroadcastCookCompleted();*/ - - virtual void OnPrePreCook() {}; - virtual void OnPostPreCook() {}; - virtual void OnPreOutputProcessing() {}; - virtual void OnPostOutputProcessing() {}; - virtual void OnPrePreInstantiation() {}; - - - virtual void NotifyHoudiniRegisterCompleted() {}; - virtual void NotifyHoudiniPreUnregister() {}; - virtual void NotifyHoudiniPostUnregister() {}; - - virtual void OnFullyLoaded(); - - // Component template parameters have been updated. - // Broadcast delegate, and let preview components take care of the rest. - virtual void OnTemplateParametersChanged() { }; - virtual void OnBlueprintStructureModified() { }; - virtual void OnBlueprintModified() { }; - - -protected: - - // UActorComponents Method - virtual void OnComponentCreated() override; - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - - virtual void OnChildAttached(USceneComponent* ChildComponent) override; - - virtual void BeginDestroy() override; - - // Do any object - specific cleanup required immediately after loading an object. - // This is not called for newly - created objects, and by default will always execute on the game thread. - virtual void PostLoad() override; - - // Called after importing property values for this object (paste, duplicate or .t3d import) - // Allow the object to perform any cleanup for properties which shouldn't be duplicated or - // Are unsupported by the script serialization - virtual void PostEditImport() override; - - // - void OnActorMoved(AActor* Actor); - - // - void UpdatePostDuplicate(); - - // - //static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); - -public: - - // Houdini Asset associated with this component. - /*Category = HoudiniAsset, EditAnywhere, meta = (DisplayPriority=0)*/ - UPROPERTY(Category = HoudiniAsset, EditAnywhere)// BlueprintSetter = SetHoudiniAsset, BlueprintReadWrite, ) - UHoudiniAsset* HoudiniAsset; - - // Automatically cook when a parameter or input is changed - UPROPERTY() - bool bCookOnParameterChange; - - // Enables uploading of transformation changes back to Houdini Engine. - UPROPERTY() - bool bUploadTransformsToHoudiniEngine; - - // Transform changes automatically trigger cooks. - UPROPERTY() - bool bCookOnTransformChange; - - // Houdini materials will be converted to Unreal Materials. - //UPROPERTY() - //bool bUseNativeHoudiniMaterials; - - // This asset will cook when its asset input cook - UPROPERTY() - bool bCookOnAssetInputCook; - - // Enabling this will prevent the HDA from producing any output after cooking. - UPROPERTY() - bool bOutputless; - - // Enabling this will allow outputing the asset's templated geos - UPROPERTY() - bool bOutputTemplateGeos; - - // Temporary cook folder - UPROPERTY() - FDirectoryPath TemporaryCookFolder; - - // Folder used for baking this asset's outputs - UPROPERTY() - FDirectoryPath BakeFolder; - - // HoudiniGeneratedStaticMeshSettings - /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Double Sided Geometry")) - uint32 bGeneratedDoubleSidedGeometry : 1; - - /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * GeneratedPhysMaterial; - - /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ - UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Collision Complexity")) - TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; - - /** Resolution of lightmap. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 GeneratedLightMapResolution; - - /** Bias multiplier for Light Propagation Volume lighting. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float GeneratedLpvBiasMultiplier; - - /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) - float GeneratedDistanceFieldResolutionScale; - - /** Custom walkable slope setting for generated mesh's body. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride GeneratedWalkableSlopeOverride; - - /** The light map coordinate index. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light map coordinate index")) - int32 GeneratedLightMapCoordinateIndex; - - /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; - - /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Streaming Distance Multiplier")) - float GeneratedStreamingDistanceMultiplier; - - /** Default settings when using this mesh for instanced foliage. */ - /* - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; - */ - - /** Array of user data stored with the asset. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Asset User Data")) - TArray GeneratedAssetUserData; - - // Override the global fast proxy mesh settings on this component? - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayPriority = 0)) - bool bOverrideGlobalProxyStaticMeshSettings; - - // For StaticMesh outputs: should a fast proxy be created first? - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Enable Proxy Static Mesh", EditCondition="bOverrideGlobalProxyStaticMeshSettings", DisplayPriority = 0)) - bool bEnableProxyStaticMeshOverride; - - // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes After a Timeout", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementByTimerOverride; - - // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride")) - float ProxyMeshAutoRefineTimeoutSecondsOverride; - - // Automatically refine proxy meshes to UStaticMesh before the map is saved - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes When Saving a Map", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; - - // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; - - // The method to use to create the mesh - UPROPERTY(Category = "HoudiniAsset | Development", EditAnywhere, meta = (DisplayPriority = 0)) - EHoudiniStaticMeshMethod StaticMeshMethod; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bGenerateMenuExpanded; - - UPROPERTY() - bool bBakeMenuExpanded; - - UPROPERTY() - bool bAssetOptionMenuExpanded; - - UPROPERTY() - bool bHelpAndDebugMenuExpanded; - - UPROPERTY() - EHoudiniEngineBakeOption HoudiniEngineBakeOption; - - // If true, then after a successful bake, the HACs outputs will be cleared and removed. - UPROPERTY() - bool bRemoveOutputAfterBake; - - // If true, recenter baked actors to their bounding box center after bake - UPROPERTY() - bool bRecenterBakedActors; - - // If true, replace the previously baked output (if any) instead of creating new objects - UPROPERTY() - bool bReplacePreviousBake; -#endif - -protected: - - // Id of corresponding Houdini asset. - UPROPERTY(DuplicateTransient) - int32 AssetId; - - // List of dependent downstream HACs that have us as an asset input - UPROPERTY(DuplicateTransient) - TSet DownstreamHoudiniAssets; - - // Unique GUID created by component. - UPROPERTY(DuplicateTransient) - FGuid ComponentGUID; - - // GUID used to track asynchronous cooking requests. - UPROPERTY(DuplicateTransient) - FGuid HapiGUID; - - // Current state of the asset - UPROPERTY(DuplicateTransient) - EHoudiniAssetState AssetState; - - // Last asset state logged. - UPROPERTY(DuplicateTransient) - mutable EHoudiniAssetState DebugLastAssetState; - - // Result of the current asset's state - UPROPERTY(DuplicateTransient) - EHoudiniAssetStateResult AssetStateResult; - - //// Contains the context for keeping track of shared - //// Houdini data. - //UPROPERTY(DuplicateTransient) - //UHoudiniAssetContext* AssetContext; - - // Subasset index - UPROPERTY() - uint32 SubAssetIndex; - - // Number of times this asset has been cooked. - UPROPERTY(DuplicateTransient) - int32 AssetCookCount; - - // - UPROPERTY(DuplicateTransient) - bool bHasBeenLoaded; - - UPROPERTY(DuplicateTransient) - bool bHasBeenDuplicated; - - UPROPERTY(DuplicateTransient) - bool bPendingDelete; - - UPROPERTY(DuplicateTransient) - bool bRecookRequested; - - UPROPERTY(DuplicateTransient) - bool bRebuildRequested; - - UPROPERTY(DuplicateTransient) - bool bEnableCooking; - - UPROPERTY(DuplicateTransient) - bool bForceNeedUpdate; - - UPROPERTY(DuplicateTransient) - bool bLastCookSuccess; - - UPROPERTY(DuplicateTransient) - bool bBlueprintStructureModified; - - UPROPERTY(DuplicateTransient) - bool bBlueprintModified; - - //UPROPERTY(DuplicateTransient) - //bool bEditorPropertiesNeedFullUpdate; - - UPROPERTY(Instanced) - TArray Parameters; - - UPROPERTY(Instanced) - TArray Inputs; - - UPROPERTY(Instanced) - TArray Outputs; - - // The baked outputs from the last bake. - UPROPERTY() - TArray BakedOutputs; - - // Any actors that aren't explicitly - // tracked by output objects should be registered - // here so that they can be cleaned up. - UPROPERTY() - TArray> UntrackedOutputs; - - UPROPERTY() - TArray HandleComponents; - - UPROPERTY(Transient, DuplicateTransient) - bool bHasComponentTransformChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bFullyLoaded; - - UPROPERTY() - UHoudiniPDGAssetLink* PDGAssetLink; - - // Timer that is used to trigger creation of UStaticMesh for all mesh outputs - // that still have UHoudiniStaticMeshes. The timer is cleared on PreCook and reset - // at the end of the PostCook. - UPROPERTY() - FTimerHandle RefineMeshesTimer; - - // Delegate that is used to broadcast when RefineMeshesTimer fires - FOnRefineMeshesTimerDelegate OnRefineMeshesTimerDelegate; - - // If true, don't build a proxy mesh next cook (regardless of global or override settings), - // instead build the UStaticMesh directly (if applicable for the output types). - UPROPERTY(DuplicateTransient) - bool bNoProxyMeshNextCookRequested; - - // Maps a UObject to an Input number, used to preset the asset's inputs - UPROPERTY(Transient, DuplicateTransient) - TMap InputPresets; - - // If true, bake the asset after its next cook. - UPROPERTY(DuplicateTransient) - bool bBakeAfterNextCook; - - // Delegate to broadcast when baking after a cook. - // Currently we cannot call the bake functions from here (Runtime module) - // or from the HoudiniEngineManager (HoudiniEngine) module, so we use - // a delegate. - FOnPostCookBakeDelegate OnPostCookBakeDelegate; - - // Cached flag of whether this object is considered to be a 'preview' component or not. - // This is typically useful in destructors when references to the World, for example, - // is no longer available. - UPROPERTY(Transient, DuplicateTransient) - bool bCachedIsPreview; - - USimpleConstructionScript* GetSCS() const; - - // Object used to convert V1 HAC to V2 HAC - UHoudiniAssetComponent_V1* Version1CompatibilityHAC; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniOutput.h" +#include "HoudiniInputTypes.h" +#include "HoudiniPluginSerializationVersion.h" + +#include "Components/PrimitiveComponent.h" +#include "Components/SceneComponent.h" + +#include "HoudiniAssetComponent.generated.h" + +class UHoudiniAsset; +class UHoudiniParameter; +class UHoudiniInput; +class UHoudiniOutput; +class UHoudiniHandleComponent; +class UHoudiniPDGAssetLink; +class UHoudiniAssetComponent_V1; + +UENUM() +enum class EHoudiniAssetState : uint8 +{ + // Loaded / Duplicated HDA, + // Will need to be instantiated upon change/update + NeedInstantiation, + + // Newly created HDA, needs to be instantiated immediately + PreInstantiation, + + // Instantiating task in progress + Instantiating, + + // Instantiated HDA, needs to be cooked immediately + PreCook, + + // Cooking task in progress + Cooking, + + // Cooking has finished + PostCook, + + // Cooked HDA, needs to be processed immediately + PreProcess, + + // Processing task in progress + Processing, + + // Processed / Updated HDA + // Will need to be cooked upon change/update + None, + + // Asset needs to be rebuilt (Deleted/Instantiated/Cooked) + NeedRebuild, + + // Asset needs to be deleted + NeedDelete, + + // Deleting + Deleting, + + // Process component template. This is ticking has very limited + // functionality, typically limited to checking for parameter updates + // in order to trigger PostEditChange() to run construction scripts again. + ProcessTemplate, +}; + +UENUM() +enum class EHoudiniAssetStateResult : uint8 +{ + None, + Working, + Success, + FinishedWithError, + FinishedWithFatalError, + Aborted +}; + +UENUM() +enum class EHoudiniStaticMeshMethod : uint8 +{ + // Use the RawMesh method to build the UStaticMesh + RawMesh, + // Use the FMeshDescription method to build the UStaticMesh + FMeshDescription, + // Build a fast proxy mesh: UHoudiniStaticMesh + UHoudiniStaticMesh, +}; + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EHoudiniEngineBakeOption : uint8 +{ + ToActor, + ToBlueprint, + ToFoliage, + ToWorldOutliner, +}; +#endif + +class UHoudiniAssetComponent; + +DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetEvent, UHoudiniAsset*); +DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetComponentEvent, UHoudiniAssetComponent*) + +UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility), editinlinenew) +class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent +{ + GENERATED_UCLASS_BODY() + + // Declare translators as friend so they can easily directly modify + // Inputs, outputs and parameters + friend class FHoudiniEngineManager; + friend struct FHoudiniOutputTranslator; + friend struct FHoudiniInputTranslator; + friend struct FHoudiniSplineTranslator; + friend struct FHoudiniParameterTranslator; + friend struct FHoudiniPDGManager; + friend struct FHoudiniHandleTranslator; + +#if WITH_EDITORONLY_DATA + friend class FHoudiniAssetComponentDetails; +#endif + +public: + + // Declare the delegate that is broadcast when RefineMeshesTimer fires + DECLARE_MULTICAST_DELEGATE_OneParam(FOnRefineMeshesTimerDelegate, UHoudiniAssetComponent*); + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPostCookBakeDelegate, UHoudiniAssetComponent*); + + virtual ~UHoudiniAssetComponent(); + + virtual void Serialize(FArchive & Ar) override; + + virtual bool ConvertLegacyData(); + + // Called after the C++ constructor and after the properties have been initialized, including those loaded from config. + // This is called before any serialization or other setup has happened. + virtual void PostInitProperties() override; + + // Returns the Owner actor / HAC name + FString GetDisplayName() const; + + // Indicates if the HAC needs to be updated + bool NeedUpdate() const; + + // Indicates if the HAC's transform needs to be updated + bool NeedTransformUpdate() const { return (bHasComponentTransformChanged && bUploadTransformsToHoudiniEngine); }; + + // Indicates if any of the HAC's output components needs to be updated (no recook needed) + bool NeedOutputUpdate() const; + + // Check whether any inputs / outputs / parameters have made blueprint modifications. + bool NeedBlueprintStructureUpdate() const; + bool NeedBlueprintUpdate() const; + + // Try to find one of our parameter that matches another (name, type, size and enabled) + UHoudiniParameter* FindMatchingParameter(UHoudiniParameter* InOtherParam); + + // Try to find one of our input that matches another one (name, isobjpath, index / parmId) + UHoudiniInput* FindMatchingInput(UHoudiniInput* InOtherInput); + + // Try to find one of our handle that matches another one (name and handle type) + UHoudiniHandleComponent* FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle); + + // Finds a parameter by name + UHoudiniParameter* FindParameterByName(const FString& InParamName); + + // Returns True if the component has at least one mesh output of class U + template + bool HasMeshOutputObjectOfClass() const; + + // Returns True if the component has at least one mesh output with a current proxy + bool HasAnyCurrentProxyOutput() const; + + // Returns True if the component has at least one proxy mesh output (not necessarily current/displayed) + bool HasAnyProxyOutput() const; + + // Returns True if the component has at least one non-proxy output component amongst its outputs + bool HasAnyOutputComponent() const; + + // Returns true if the component has InOutputObjectToFind in its output object + bool HasOutputObject(UObject* InOutputObjectToFind) const; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + UHoudiniAsset * GetHoudiniAsset() const; + int32 GetAssetId() const { return AssetId; }; + EHoudiniAssetState GetAssetState() const { return AssetState; }; + FString GetAssetStateAsString() const { return FHoudiniEngineRuntimeUtils::EnumToString(TEXT("EHoudiniAssetState"), GetAssetState()); }; + EHoudiniAssetStateResult GetAssetStateResult() const { return AssetStateResult; }; + FGuid GetHapiGUID() const { return HapiGUID; }; + FGuid GetComponentGUID() const { return ComponentGUID; }; + + int32 GetNumInputs() const { return Inputs.Num(); }; + int32 GetNumOutputs() const { return Outputs.Num(); }; + int32 GetNumParameters() const { return Parameters.Num(); }; + int32 GetNumHandles() const { return HandleComponents.Num(); }; + + UHoudiniInput* GetInputAt(const int32& Idx) { return Inputs.IsValidIndex(Idx) ? Inputs[Idx] : nullptr; }; + UHoudiniOutput* GetOutputAt(const int32& Idx) { return Outputs.IsValidIndex(Idx) ? Outputs[Idx] : nullptr;}; + UHoudiniParameter* GetParameterAt(const int32& Idx) { return Parameters.IsValidIndex(Idx) ? Parameters[Idx] : nullptr;}; + UHoudiniHandleComponent* GetHandleComponentAt(const int32& Idx) { return HandleComponents.IsValidIndex(Idx) ? HandleComponents[Idx] : nullptr; }; + + void GetOutputs(TArray& OutOutputs) const; + + TArray& GetBakedOutputs() { return BakedOutputs; } + const TArray& GetBakedOutputs() const { return BakedOutputs; } + + /* + TArray& GetParameters() { return Parameters; }; + TArray& GetInputs() { return Inputs; }; + TArray& GetOutputs() { return Outputs; }; + */ + + bool IsCookingEnabled() const { return bEnableCooking; }; + bool HasBeenLoaded() const { return bHasBeenLoaded; }; + bool HasBeenDuplicated() const { return bHasBeenDuplicated; }; + bool HasRecookBeenRequested() const { return bRecookRequested; }; + bool HasRebuildBeenRequested() const { return bRebuildRequested; }; + + //bool GetEditorPropertiesNeedFullUpdate() const { return bEditorPropertiesNeedFullUpdate; }; + + int32 GetAssetCookCount() const { return AssetCookCount; }; + + bool IsFullyLoaded() const { return bFullyLoaded; }; + + UHoudiniPDGAssetLink * GetPDGAssetLink() const { return PDGAssetLink; }; + + virtual bool IsProxyStaticMeshEnabled() const; + bool IsProxyStaticMeshRefinementByTimerEnabled() const; + float GetProxyMeshAutoRefineTimeoutSeconds() const; + bool IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const; + bool IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const; + // If true, then the next cook should not build proxy meshes, regardless of global or override settings, + // but should instead directly build UStaticMesh + bool HasNoProxyMeshNextCookBeenRequested() const { return bNoProxyMeshNextCookRequested; } + // Returns true if the asset state indicates that it has been cooked in this session, false otherwise. + bool IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const; + // Returns true if the asset should be bake after the next cook + bool IsBakeAfterNextCookEnabled() const { return bBakeAfterNextCook; } + + FOnPostCookBakeDelegate& GetOnPostCookBakeDelegate() { return OnPostCookBakeDelegate; } + + // Derived blueprint based components will check whether the template + // component contains updates that needs to processed. + bool NeedUpdateParameters() const; + bool NeedUpdateInputs() const; + + // Returns true if the component has any previous baked output recorded in its outputs + bool HasPreviousBakeOutput() const; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + //void SetAssetId(const int& InAssetId); + //void SetAssetState(const EHoudiniAssetState& InAssetState) { AssetState = InAssetState; }; + //void SetAssetStateResult(const EHoudiniAssetStateResult& InAssetStateResult) { AssetStateResult = InAssetStateResult; }; + + //void SetHapiGUID(const FGuid& InGUID) { HapiGUID = InGUID; }; + //void SetComponentGUID(const FGuid& InGUID) { ComponentGUID = InGUID; }; + + //UFUNCTION(BlueprintSetter) + virtual void SetHoudiniAsset(UHoudiniAsset * NewHoudiniAsset); + + void SetHasBeenLoaded(const bool& InLoaded) { bHasBeenLoaded = InLoaded; }; + + void SetHasBeenDuplicated(const bool& InDuplicated) { bHasBeenDuplicated = InDuplicated; }; + + //void SetEditorPropertiesNeedFullUpdate(const bool& InUpdate) { bEditorPropertiesNeedFullUpdate = InUpdate; }; + + // Marks the assets as needing a recook + void MarkAsNeedCook(); + // Marks the assets as needing a full rebuild + void MarkAsNeedRebuild(); + // Marks the asset as needing to be instantiated + void MarkAsNeedInstantiation(); + // The blueprint has been structurally modified + void MarkAsBlueprintStructureModified(); + // The blueprint has been modified but not structurally changed. + void MarkAsBlueprintModified(); + + // + void SetAssetCookCount(const int32& InCount) { AssetCookCount = InCount; }; + // + void SetRecookRequested(const bool& InRecook) { bRecookRequested = InRecook; }; + // + void SetRebuildRequested(const bool& InRebuild) { bRebuildRequested = InRebuild; }; + // + void SetHasComponentTransformChanged(const bool& InHasChanged); + + // Set to True to force the next cook to not build a proxy mesh (regardless of global or override settings) and + // instead build a UStaticMesh directly (if applicable for the output type). + void SetNoProxyMeshNextCookRequested(bool bInNoProxyMeshNextCookRequested) { bNoProxyMeshNextCookRequested = bInNoProxyMeshNextCookRequested; } + + // Set to True to force the next cook to bake the asset after the cook completes. + void SetBakeAfterNextCookEnabled(bool bInEnabled) { bBakeAfterNextCook = bInEnabled; } + + // + void SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink); + // + virtual void OnHoudiniAssetChanged(); + + // + void AddDownstreamHoudiniAsset(UHoudiniAssetComponent* InDownstreamAsset) { DownstreamHoudiniAssets.Add(InDownstreamAsset); }; + // + void RemoveDownstreamHoudiniAsset(UHoudiniAssetComponent* InRemoveDownstreamAsset) { DownstreamHoudiniAssets.Remove(InRemoveDownstreamAsset); }; + // + void ClearDownstreamHoudiniAsset() { DownstreamHoudiniAssets.Empty(); }; + // + bool NotifyCookedToDownstreamAssets(); + // + bool NeedsToWaitForInputHoudiniAssets(); + + // Clear/disable the RefineMeshesTimer. + void ClearRefineMeshesTimer(); + + // Re-set the RefineMeshesTimer to its default value. + void SetRefineMeshesTimer(); + + virtual void OnRefineMeshesTimerFired(); + + // Called by RefineMeshesTimer when the timer is triggered. + // Checks for any UHoudiniStaticMesh in Outputs and bakes UStaticMesh for them via FHoudiniMeshTranslator. + FOnRefineMeshesTimerDelegate& GetOnRefineMeshesTimerDelegate() { return OnRefineMeshesTimerDelegate; } + + // Returns true if the asset is valid for cook/bake + virtual bool IsComponentValid() const; + // Return false if this component has no cooking or instantiation in progress. + bool IsInstantiatingOrCooking() const; + + // HoudiniEngineTick will be called by HoudiniEngineManager::Tick() + virtual void HoudiniEngineTick(); + +#if WITH_EDITOR + // This alternate version of PostEditChange is called when properties inside structs are modified. The property that was actually modified + // is located at the tail of the list. The head of the list of the FStructProperty member variable that contains the property that was modified. + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; + + //Called after applying a transaction to the object. Default implementation simply calls PostEditChange. + virtual void PostEditUndo() override; + + // Whether this component is currently open in a Blueprint editor. This + // method is overridden by HoudiniAssetBlueprintComponent. + virtual bool HasOpenEditor() const { return false; }; + +#endif + + virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent); + + virtual void OnRegister() override; + + // USceneComponent methods. + virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; + + FBox GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const; + + // Set this component's input presets + void SetInputPresets(const TMap& InPresets); + // Apply the preset input for HoudiniTools + void ApplyInputPresets(); + + // return the cached component template, if available. + virtual UHoudiniAssetComponent* GetCachedTemplate() const { return nullptr; } + + //------------------------------------------------------------------------------------------------ + // Supported Features + //------------------------------------------------------------------------------------------------ + + // Whether or not this component should be able to delete the Houdini nodes + // that correspond to the HoudiniAsset when being deregistered. + virtual bool CanDeleteHoudiniNodes() const { return true; } + + virtual bool IsInputTypeSupported(EHoudiniInputType InType) const; + virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const; + + //------------------------------------------------------------------------------------------------5 + // Characteristics + //------------------------------------------------------------------------------------------------ + + // Try to determine whether this component belongs to a preview actor. + // Preview / Template components need to sync their data for HDA cooks and output translations. + bool IsPreview() const; + + virtual bool IsValidComponent() const; + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + // TODO: After the cook worfklow rework, most of these won't be needed anymore, so clean up! + //FHoudiniAssetComponentEvent OnTemplateParametersChanged; + //FHoudiniAssetComponentEvent OnPreAssetCook; + //FHoudiniAssetComponentEvent OnCookCompleted; + //FHoudiniAssetComponentEvent OnOutputProcessingCompleted; + + /*virtual void BroadcastParametersChanged(); + virtual void BroadcastPreAssetCook(); + virtual void BroadcastCookCompleted();*/ + + virtual void OnPrePreCook() {}; + virtual void OnPostPreCook() {}; + virtual void OnPreOutputProcessing() {}; + virtual void OnPostOutputProcessing() {}; + virtual void OnPrePreInstantiation() {}; + + + virtual void NotifyHoudiniRegisterCompleted() {}; + virtual void NotifyHoudiniPreUnregister() {}; + virtual void NotifyHoudiniPostUnregister() {}; + + virtual void OnFullyLoaded(); + + // Component template parameters have been updated. + // Broadcast delegate, and let preview components take care of the rest. + virtual void OnTemplateParametersChanged() { }; + virtual void OnBlueprintStructureModified() { }; + virtual void OnBlueprintModified() { }; + + +protected: + + // UActorComponents Method + virtual void OnComponentCreated() override; + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + virtual void OnChildAttached(USceneComponent* ChildComponent) override; + + virtual void BeginDestroy() override; + + // Do any object - specific cleanup required immediately after loading an object. + // This is not called for newly - created objects, and by default will always execute on the game thread. + virtual void PostLoad() override; + + // Called after importing property values for this object (paste, duplicate or .t3d import) + // Allow the object to perform any cleanup for properties which shouldn't be duplicated or + // Are unsupported by the script serialization + virtual void PostEditImport() override; + + // + void OnActorMoved(AActor* Actor); + + // + void UpdatePostDuplicate(); + + // + //static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); + +public: + + // Houdini Asset associated with this component. + /*Category = HoudiniAsset, EditAnywhere, meta = (DisplayPriority=0)*/ + UPROPERTY(Category = HoudiniAsset, EditAnywhere)// BlueprintSetter = SetHoudiniAsset, BlueprintReadWrite, ) + UHoudiniAsset* HoudiniAsset; + + // Automatically cook when a parameter or input is changed + UPROPERTY() + bool bCookOnParameterChange; + + // Enables uploading of transformation changes back to Houdini Engine. + UPROPERTY() + bool bUploadTransformsToHoudiniEngine; + + // Transform changes automatically trigger cooks. + UPROPERTY() + bool bCookOnTransformChange; + + // Houdini materials will be converted to Unreal Materials. + //UPROPERTY() + //bool bUseNativeHoudiniMaterials; + + // This asset will cook when its asset input cook + UPROPERTY() + bool bCookOnAssetInputCook; + + // Enabling this will prevent the HDA from producing any output after cooking. + UPROPERTY() + bool bOutputless; + + // Enabling this will allow outputing the asset's templated geos + UPROPERTY() + bool bOutputTemplateGeos; + + // Temporary cook folder + UPROPERTY() + FDirectoryPath TemporaryCookFolder; + + // Folder used for baking this asset's outputs + UPROPERTY() + FDirectoryPath BakeFolder; + + // HoudiniGeneratedStaticMeshSettings + /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Double Sided Geometry")) + uint32 bGeneratedDoubleSidedGeometry : 1; + + /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * GeneratedPhysMaterial; + + /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ + UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Collision Complexity")) + TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; + + /** Resolution of lightmap. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 GeneratedLightMapResolution; + + /** Bias multiplier for Light Propagation Volume lighting. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) + float GeneratedLpvBiasMultiplier; + + /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /** Custom walkable slope setting for generated mesh's body. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride GeneratedWalkableSlopeOverride; + + /** The light map coordinate index. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Light map coordinate index")) + int32 GeneratedLightMapCoordinateIndex; + + /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; + + /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Streaming Distance Multiplier")) + float GeneratedStreamingDistanceMultiplier; + + /** Default settings when using this mesh for instanced foliage. */ + /* + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; + */ + + /** Array of user data stored with the asset. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Asset User Data")) + TArray GeneratedAssetUserData; + + // Override the global fast proxy mesh settings on this component? + UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayPriority = 0)) + bool bOverrideGlobalProxyStaticMeshSettings; + + // For StaticMesh outputs: should a fast proxy be created first? + UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Enable Proxy Static Mesh", EditCondition="bOverrideGlobalProxyStaticMeshSettings", DisplayPriority = 0)) + bool bEnableProxyStaticMeshOverride; + + // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? + UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes After a Timeout", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementByTimerOverride; + + // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. + UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride")) + float ProxyMeshAutoRefineTimeoutSecondsOverride; + + // Automatically refine proxy meshes to UStaticMesh before the map is saved + UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes When Saving a Map", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; + + // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session + UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; + + // The method to use to create the mesh + UPROPERTY(Category = "HoudiniAsset | Development", EditAnywhere, meta = (DisplayPriority = 0)) + EHoudiniStaticMeshMethod StaticMeshMethod; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bGenerateMenuExpanded; + + UPROPERTY() + bool bBakeMenuExpanded; + + UPROPERTY() + bool bAssetOptionMenuExpanded; + + UPROPERTY() + bool bHelpAndDebugMenuExpanded; + + UPROPERTY() + EHoudiniEngineBakeOption HoudiniEngineBakeOption; + + // If true, then after a successful bake, the HACs outputs will be cleared and removed. + UPROPERTY() + bool bRemoveOutputAfterBake; + + // If true, recenter baked actors to their bounding box center after bake + UPROPERTY() + bool bRecenterBakedActors; + + // If true, replace the previously baked output (if any) instead of creating new objects + UPROPERTY() + bool bReplacePreviousBake; +#endif + +protected: + + // Id of corresponding Houdini asset. + UPROPERTY(DuplicateTransient) + int32 AssetId; + + // List of dependent downstream HACs that have us as an asset input + UPROPERTY(DuplicateTransient) + TSet DownstreamHoudiniAssets; + + // Unique GUID created by component. + UPROPERTY(DuplicateTransient) + FGuid ComponentGUID; + + // GUID used to track asynchronous cooking requests. + UPROPERTY(DuplicateTransient) + FGuid HapiGUID; + + // Current state of the asset + UPROPERTY(DuplicateTransient) + EHoudiniAssetState AssetState; + + // Last asset state logged. + UPROPERTY(DuplicateTransient) + mutable EHoudiniAssetState DebugLastAssetState; + + // Result of the current asset's state + UPROPERTY(DuplicateTransient) + EHoudiniAssetStateResult AssetStateResult; + + //// Contains the context for keeping track of shared + //// Houdini data. + //UPROPERTY(DuplicateTransient) + //UHoudiniAssetContext* AssetContext; + + // Subasset index + UPROPERTY() + uint32 SubAssetIndex; + + // Number of times this asset has been cooked. + UPROPERTY(DuplicateTransient) + int32 AssetCookCount; + + // + UPROPERTY(DuplicateTransient) + bool bHasBeenLoaded; + + UPROPERTY(DuplicateTransient) + bool bHasBeenDuplicated; + + UPROPERTY(DuplicateTransient) + bool bPendingDelete; + + UPROPERTY(DuplicateTransient) + bool bRecookRequested; + + UPROPERTY(DuplicateTransient) + bool bRebuildRequested; + + UPROPERTY(DuplicateTransient) + bool bEnableCooking; + + UPROPERTY(DuplicateTransient) + bool bForceNeedUpdate; + + UPROPERTY(DuplicateTransient) + bool bLastCookSuccess; + + UPROPERTY(DuplicateTransient) + bool bBlueprintStructureModified; + + UPROPERTY(DuplicateTransient) + bool bBlueprintModified; + + //UPROPERTY(DuplicateTransient) + //bool bEditorPropertiesNeedFullUpdate; + + UPROPERTY(Instanced) + TArray Parameters; + + UPROPERTY(Instanced) + TArray Inputs; + + UPROPERTY(Instanced) + TArray Outputs; + + // The baked outputs from the last bake. + UPROPERTY() + TArray BakedOutputs; + + // Any actors that aren't explicitly + // tracked by output objects should be registered + // here so that they can be cleaned up. + UPROPERTY() + TArray> UntrackedOutputs; + + UPROPERTY() + TArray HandleComponents; + + UPROPERTY(Transient, DuplicateTransient) + bool bHasComponentTransformChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bFullyLoaded; + + UPROPERTY() + UHoudiniPDGAssetLink* PDGAssetLink; + + // Timer that is used to trigger creation of UStaticMesh for all mesh outputs + // that still have UHoudiniStaticMeshes. The timer is cleared on PreCook and reset + // at the end of the PostCook. + UPROPERTY() + FTimerHandle RefineMeshesTimer; + + // Delegate that is used to broadcast when RefineMeshesTimer fires + FOnRefineMeshesTimerDelegate OnRefineMeshesTimerDelegate; + + // If true, don't build a proxy mesh next cook (regardless of global or override settings), + // instead build the UStaticMesh directly (if applicable for the output types). + UPROPERTY(DuplicateTransient) + bool bNoProxyMeshNextCookRequested; + + // Maps a UObject to an Input number, used to preset the asset's inputs + UPROPERTY(Transient, DuplicateTransient) + TMap InputPresets; + + // If true, bake the asset after its next cook. + UPROPERTY(DuplicateTransient) + bool bBakeAfterNextCook; + + // Delegate to broadcast when baking after a cook. + // Currently we cannot call the bake functions from here (Runtime module) + // or from the HoudiniEngineManager (HoudiniEngine) module, so we use + // a delegate. + FOnPostCookBakeDelegate OnPostCookBakeDelegate; + + // Cached flag of whether this object is considered to be a 'preview' component or not. + // This is typically useful in destructors when references to the World, for example, + // is no longer available. + UPROPERTY(Transient, DuplicateTransient) + bool bCachedIsPreview; + + USimpleConstructionScript* GetSCS() const; + + // Object used to convert V1 HAC to V2 HAC + UHoudiniAssetComponent_V1* Version1CompatibilityHAC; +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp index 5fcced98b..a9671bb7f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp @@ -1,1799 +1,1799 @@ -/* -* Copyright (c) <2020> 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 "HoudiniCompatibilityHelpers.h" - -#include "HoudiniPluginSerializationVersion.h" - -#include "HoudiniInput.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniHandleComponent.h" - -#include "Engine/StaticMesh.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "EngineUtils.h" // for TActorIterator<> - -#if WITH_EDITOR - #include "Editor.h" - #include "Editor/UnrealEdEngine.h" - #include "UnrealEdGlobals.h" -#endif - -// TODO: -// HoudiniInstancedActorComponent ? -// HoudiniMeshSplitInstancerComponent ? - -UHoudiniAssetComponent_V1::UHoudiniAssetComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - - -void -UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) -{ - if (!Ar.IsLoading()) - return; - - //Super::Serialize(Ar); - - //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Serialize component flags. - Ar << HoudiniAssetComponentFlagsPacked; - - // Serialize format version. - uint32 HoudiniAssetComponentVersion = 0;//Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - Ar << HoudiniAssetComponentVersion; - - // ComponenState Enum, saved as uint8 - // 0 invalid - // 1 None - // 2 Instantiated - // 3 BeingCooked - // Serialize component state. - uint8 ComponentState = 0; - Ar << ComponentState; - - // Serialize scaling information and import axis. - Ar << GeneratedGeometryScaleFactor; - Ar << TransformScaleFactor; - - // ImportAxis Enum, saved as uint8 - // 0 unreal 1 Houdini - //uint8 ImportAxis = 0; - Ar << ImportAxis; - - // Serialize generated component GUID. - Ar << ComponentGUID; - - // If component is in invalid state, we can skip the rest of serialization. - //if (ComponentState == EHoudiniAssetComponentState_V1::Invalid) - if (ComponentState == 0) - return; - - // Serialize Houdini asset. - Ar << HoudiniAsset; - - // 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; - int TempId; - Ar << TempId; - bIsPlayModeActive_Unused = false; - } - - // Serialization of default preset. - Ar << DefaultPresetBuffer; - - // Serialization of preset. - { - bool bPresetSaved = false; - Ar << bPresetSaved; - - if (bPresetSaved) - { - Ar << PresetBuffer; - } - } - - // Serialize parameters. - //SerializeParameters(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::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; - } - - // 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::TIterator IterParams(Parameters); IterParams; ++IterParams) - { - UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); - if (HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill()) - ParameterByName.Add(HoudiniAssetParameter->ParameterName, HoudiniAssetParameter); - } - } - } - - // Serialize inputs. - //SerializeInputs(Ar); - { - Ar << Inputs; - - /* - if (Ar.IsLoading()) - { - for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) - { - UHoudiniAssetInput_V1 * HoudiniAssetInput = Inputs[InputIdx]; - if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) - Inputs[InputIdx]->SetHoudiniAssetComponent(this); - } - } - */ - } - - // 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); - { - //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; - int HoudiniInstanceInputKey = -1; - - Ar << HoudiniInstanceInputKey; - Ar << InstanceInputs[InstanceInputIdx]; - } - } - } - - // 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 DirtyPackages; - if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES) - { - TMap SavedPackages; - Ar << SavedPackages; - } - - if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS) - { - // Temporary Mesh Packages - TMap MeshPackages; - Ar << MeshPackages; - - // Temporary Landscape Layers Packages - TMap LayerPackages; - Ar << LayerPackages; - } -} - -uint32 -GetTypeHash(const FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) -{ - return HoudiniGeoPartObject.GetTypeHash(); -} - -FArchive & -operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) -{ - HoudiniGeoPartObject.Serialize(Ar); - return Ar; -} - -uint32 -FHoudiniGeoPartObject_V1::GetTypeHash() const -{ - int32 HashBuffer[4] = { ObjectId, GeoId, PartId, SplitId }; - int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); - return FCrc::StrCrc32(*SplitName, Hash); -} - -bool -FHoudiniGeoPartObject_V1SortPredicate::operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const -{ - /*if (!A.IsValid() || !B.IsValid()) - return false;*/ - - if (A.ObjectId == B.ObjectId) - { - if (A.GeoId == B.GeoId) - { - if (A.PartId == B.PartId) - return A.SplitId < B.SplitId; - else - return A.PartId < B.PartId; - } - else - { - return A.GeoId < B.GeoId; - } - } - - return A.ObjectId < B.ObjectId; -} - -void -FHoudiniGeoPartObject_V1::Serialize(FArchive & Ar) -{ - //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniGeoPartObjectVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniGeoPartObjectVersion; - - Ar << TransformMatrix; - - Ar << ObjectName; - Ar << PartName; - Ar << SplitName; - - // Serialize instancer material. - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME) - Ar << InstancerMaterialName; - - // Serialize instancer attribute material. - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME) - Ar << InstancerAttributeMaterialName; - - Ar << AssetId; - Ar << ObjectId; - Ar << GeoId; - Ar << PartId; - Ar << SplitId; - - Ar << HoudiniGeoPartObjectFlagsPacked; - - if (HoudiniGeoPartObjectVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - // Prior to this version the unused flags space was not zero-initialized, so - // zero them out now to prevent misinterpreting any 1s - HoudiniGeoPartObjectFlagsPacked &= 0x3FFFF; - } - - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH) - { - Ar << NodePath; - } -} - - -FHoudiniGeoPartObject -FHoudiniGeoPartObject_V1::ConvertLegacyData() -{ - FHoudiniGeoPartObject NewHGPO; - - NewHGPO.AssetId = AssetId; - // NewHGPO.AssetName; - - NewHGPO.ObjectId = ObjectId; - NewHGPO.ObjectName = ObjectName; - - NewHGPO.GeoId = GeoId; - - NewHGPO.PartId = PartId; - NewHGPO.PartName = PartName; - NewHGPO.bHasCustomPartName = bHasCustomName; - - NewHGPO.SplitGroups.Add(SplitName); - - NewHGPO.TransformMatrix = TransformMatrix; - NewHGPO.NodePath = NodePath; - - // NewHGPO.VolumeName; - // NewHGPO.VolumeTileIndex; - - NewHGPO.bIsVisible = bIsVisible; - NewHGPO.bIsEditable = bIsEditable; - // NewHGPO.bIsTemplated; - // NewHGPO.bIsInstanced; - - NewHGPO.bHasGeoChanged = bHasGeoChanged; - // NewHGPO.bHasPartChanged; - // NewHGPO.bHasTransformChanged; - // NewHGPO.bHasMaterialsChanged; - NewHGPO.bLoaded = true; //bIsLoaded; - - // Hamdle Part Type - if (bIsCurve) - { - NewHGPO.Type = EHoudiniPartType::Curve; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - else if (bIsVolume) - { - NewHGPO.Type = EHoudiniPartType::Volume; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - else if (bIsInstancer) - { - NewHGPO.Type = EHoudiniPartType::Instancer; - NewHGPO.InstancerType = EHoudiniInstancerType::ObjectInstancer; - } - else if (bIsPackedPrimitiveInstancer) - { - NewHGPO.Type = EHoudiniPartType::Instancer; - NewHGPO.InstancerType = EHoudiniInstancerType::PackedPrimitive; - } - else - { - NewHGPO.Type = EHoudiniPartType::Mesh; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - - // Instancer specific flags - if (NewHGPO.Type == EHoudiniPartType::Instancer) - { - //bInstancerMaterialAvailable - //bInstancerAttributeMaterialAvailable - //InstancerMaterialName - //InstancerAttributeMaterialName - } - - // Collision specific flags - if (NewHGPO.Type == EHoudiniPartType::Mesh) - { - //bIsCollidable - //bIsRenderCollidable - //bIsUCXCollisionGeo - //bIsSimpleCollisionGeo - //bHasCollisionBeenAdded - //bHasSocketBeenAdded - } - - //bIsBox - //bIsSphere - - if (NewHGPO.SplitGroups.Num() <= 0) - { - NewHGPO.SplitGroups.Add("main_geo"); - } - - return NewHGPO; -} - -UHoudiniAssetInput::UHoudiniAssetInput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} -void -UHoudiniAssetInput::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Serialize current choice selection. - // Enum serialized as uint8 - // 0 GeometryInput - // 1 AssetInput - // 2 CurveInput - // 3 LandscapeInput - // 4 WorldInput - // 5 SkeletonInput - //SerializeEnumeration< EHoudiniAssetInputType::Enum >(Ar, ChoiceIndex); - Ar << ChoiceIndex; - - Ar << ChoiceStringValue; - - // We need these temporary variables for undo state tracking. - bool bLocalInputAssetConnectedInHoudini = bInputAssetConnectedInHoudini; - UHoudiniAssetComponent_V1 * LocalInputAssetComponent = InputAssetComponent; - - Ar << HoudiniAssetInputFlagsPacked; - - // Serialize input index. - Ar << InputIndex; - - // Serialize input objects (if it's assigned). - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT) - { - Ar << InputObjects; - } - else - { - UObject* InputObject = nullptr; - Ar << InputObject; - InputObjects.Empty(); - InputObjects.Add(InputObject); - } - - // Serialize input asset. - Ar << InputAssetComponent; - - // Serialize curve and curve parameters (if we have those). - Ar << InputCurve; - Ar << InputCurveParameters; - - // Serialize landscape used for input. - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT) - { - if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF) - { - ALandscapeProxy* InputLandscapePtr = nullptr; - Ar << InputLandscapePtr; - - InputLandscapeProxy = InputLandscapePtr; - } - else - { - Ar << InputLandscapeProxy; - } - - } - - // Serialize world outliner inputs. - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT) - { - Ar << InputOutlinerMeshArray; - } - - // Create necessary widget resources. - bLoadedParameter = true; - // If we're loading for real for the first time we need to reset this - // flag so we can reconnect when we get our parameters uploaded. - bInputAssetConnectedInHoudini = false; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT) - Ar << UnrealSplineResolution; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS) - { - Ar << InputTransforms; - } - else - { - InputTransforms.SetNum(InputObjects.Num()); - for (int32 n = 0; n < InputTransforms.Num(); n++) - InputTransforms[n] = FTransform::Identity; - } - - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - Ar << InputLandscapeTransform; -} - -UHoudiniInput* -UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) -{ - UHoudiniInput* Input = NewObject( - InOuter, UHoudiniInput::StaticClass(), FName(*ParameterLabel), RF_Transactional); - - EHoudiniInputType InputType = EHoudiniInputType::Invalid; - if (ChoiceIndex == 0) - InputType = EHoudiniInputType::Geometry; - else if (ChoiceIndex == 1) - InputType = EHoudiniInputType::Asset; - else if (ChoiceIndex == 2) - InputType = EHoudiniInputType::Curve; - else if (ChoiceIndex == 3) - InputType = EHoudiniInputType::Landscape; - else if (ChoiceIndex == 4) - InputType = EHoudiniInputType::World; - else if (ChoiceIndex == 5) - { - //InputType = EHoudiniInputType::Skeletal; - InputType = EHoudiniInputType::Invalid; - } - else - { - // Invalid - InputType = EHoudiniInputType::Invalid; - } - - bool bBlueprintStructureModified = false; - Input->SetInputType(InputType, bBlueprintStructureModified); - - Input->SetExportColliders(false); - Input->SetExportLODs(bExportAllLODs); - Input->SetExportSockets(bExportSockets); - Input->SetCookOnCurveChange(true); - - // If KeepWorldTransform is set to 2, use the default value - if (bKeepWorldTransform == 2) - Input->SetKeepWorldTransform((bool)Input->GetDefaultXTransformType()); - else - Input->SetKeepWorldTransform((bool)bKeepWorldTransform); - - Input->SetUnrealSplineResolution(UnrealSplineResolution); - Input->SetPackBeforeMerge(bPackBeforeMerge); - if(bIsObjectPathParameter) - Input->SetObjectPathParameter(ParmId); - else - Input->SetSOPInput(InputIndex); - - Input->SetImportAsReference(false); - Input->SetHelp(ParameterHelp); - //Input->SetInputNodeId(-1); - - if (bLandscapeExportAsHeightfield) - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Heightfield); - else if (bLandscapeExportAsMesh) - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Mesh); - else - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Points); - - Input->SetLabel(ParameterLabel); - Input->SetName(ParameterName); - Input->SetUpdateInputLandscape(bUpdateInputLandscape); - - if (InputType == EHoudiniInputType::Geometry) - { - // Get the geo input object array - bool bNeedToEmpty = true; - TArray* GeoInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(GeoInputObjectsPtr)) - { - // Add the geometry input objects - for (int32 AtIndex = 0; AtIndex < InputObjects.Num(); AtIndex++) - { - // Create a new InputObject wrapper - UObject* CurObject = InputObjects[AtIndex]; - if (!CurObject || CurObject->IsPendingKill()) - continue; - - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurObject, Input, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - continue; - - // Remove the default/null object - if (bNeedToEmpty) - { - GeoInputObjectsPtr->Empty(); - bNeedToEmpty = false; - } - - // Add to the geo input array - GeoInputObjectsPtr->Add(NewInputObject); - } - } - } - else if (InputType == EHoudiniInputType::Asset && InputAssetComponent != nullptr && !InputAssetComponent->IsPendingKill()) - { - // Get the asset input object array - TArray* AssetInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(AssetInputObjectsPtr)) - { - // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent - // We can simply use the v1's HAC outer for that - UHoudiniAssetComponent* InputHAC = Cast(InputAssetComponent->GetOuter()); - if (InputHAC && !InputHAC->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputHAC, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the asset input array - AssetInputObjectsPtr->Add(NewInputObject); - } - } - } - } - else if (InputType == EHoudiniInputType::Curve) - { - // Get the curve input object array - TArray* CurveInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(CurveInputObjectsPtr)) - { - if (InputCurve && !InputCurve->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputCurve, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the curve input array - CurveInputObjectsPtr->Add(NewInputObject); - } - - // InputCurve->SetInputObject(NewInputObject); - - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = Cast(NewInputObject); - if(HoudiniSplineInput) - HoudiniSplineInput->Update(InputCurve); - } - } - - // TODO ??? - //InputCurveParameters; - } - else if (InputType == EHoudiniInputType::Landscape) - { - // Get the Landscape input object array - TArray* LandscapeInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(LandscapeInputObjectsPtr)) - { - // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent - // We can simply use the v1's HAC outer for that - ALandscapeProxy* InLandscape = InputLandscapeProxy.Get(); - if (InLandscape && !InLandscape->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InLandscape, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the geo input array - LandscapeInputObjectsPtr->Add(NewInputObject); - } - } - } - - Input->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; - Input->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; - Input->bLandscapeExportLighting = bLandscapeExportLighting; - Input->bLandscapeExportMaterials = bLandscapeExportMaterials; - Input->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; - Input->bLandscapeExportTileUVs = bLandscapeExportTileUVs; - - //bLandscapeExportCurves; - } - else if (InputType == EHoudiniInputType::World) - { - // Get the world input object array - TArray* WorldInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - - UWorld* MyWorld = InOuter->GetWorld(); - if (ensure(WorldInputObjectsPtr)) - { - // Add the geometry input objects - for (int32 AtIndex = 0; AtIndex < InputOutlinerMeshArray.Num(); AtIndex++) - { - FHoudiniAssetInputOutlinerMesh_V1& CurWorldInObj = InputOutlinerMeshArray[AtIndex]; - - AActor* CurActor = nullptr; - if (CurWorldInObj.ActorPtr.IsValid()) - { - CurActor = CurWorldInObj.ActorPtr.Get(); - } - else - { - // Try to update the actor ptr via the pathname - CurWorldInObj.TryToUpdateActorPtrFromActorPathName(MyWorld); - if (CurWorldInObj.ActorPtr.IsValid()) - { - CurActor = CurWorldInObj.ActorPtr.Get(); - } - } - - if(!CurActor || CurActor->IsPendingKill()) - continue; - - // Create a new InputObject wrapper for the actor - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurActor, Input, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - continue; - - // Add to the geo input array - WorldInputObjectsPtr->Add(NewInputObject); - } - } - - /* - CurWorldInObj->HoudiniAssetParameterVersion; - CurWorldInObj->ActorPtr; - CurWorldInObj->ActorPathName; - CurWorldInObj->StaticMeshComponent; - CurWorldInObj->StaticMesh; - CurWorldInObj->SplineComponent; - CurWorldInObj->NumberOfSplineControlPoints; - CurWorldInObj->SplineControlPointsTransform; - CurWorldInObj->SplineLength; - CurWorldInObj->SplineResolution; - CurWorldInObj->ActorTransform; - CurWorldInObj->ComponentTransform; - CurWorldInObj->AssetId; - CurWorldInObj->KeepWorldTransform; - CurWorldInObj->MeshComponentsMaterials; - CurWorldInObj->InstanceIndex; - */ - - //InputOutlinerMeshArray; - } - else - { - // Invalid - } - - //ChoiceStringValue; - //bStaticMeshChanged; - //bSwitchedToCurve; - //bLoadedParameter = true; - //bInputAssetConnectedInHoudini; - //InputTransforms; - //InputLandscapeTransform; - - return Input; -} - -FArchive& -operator<<(FArchive& Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh) -{ - HoudiniAssetInputOutlinerMesh.Serialize(Ar); - return Ar; -} - -void -FHoudiniAssetInputOutlinerMesh_V1::Serialize(FArchive& Ar) -{ - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniAssetParameterVersion; - - Ar << ActorPtr; - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - { - Ar << ActorPathName; - } - - if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) - { - Ar << StaticMeshComponent; - Ar << StaticMesh; - } - - Ar << ActorTransform; - - Ar << AssetId; - if (Ar.IsLoading() && !Ar.IsTransacting()) - AssetId = -1; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE - && HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) - { - Ar << SplineComponent; - Ar << NumberOfSplineControlPoints; - Ar << SplineLength; - Ar << SplineResolution; - Ar << ComponentTransform; - } - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM) - Ar << KeepWorldTransform; - - // UE4.19 SERIALIZATION FIX: - // The component materials serialization (24) was actually missing in the UE4.19 H17.0 / H16.5 plugin. - // However subsequent serialized changes (25+) were present in those version. This caused crashes when loading - // a level that was saved with 4.19+16.5/17.0 on a newer version of Unreal or Houdini... - // If the serialized version is exactly that of the fix, we can ignore the materials paths as well - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - Ar << MeshComponentsMaterials; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX) - Ar << InstanceIndex; -} - -bool -FHoudiniAssetInputOutlinerMesh_V1::TryToUpdateActorPtrFromActorPathName(UWorld* InWorld) -{ - // Ensure our current ActorPathName looks valid - if (ActorPathName.IsEmpty() || ActorPathName.Equals(TEXT("None"), ESearchCase::IgnoreCase)) - return false; - - // We'll try to find the corresponding actor by browsing through all the actors in the world.. - // Get the editor world - UWorld* World = InWorld; - if (!World) - return false; - - // Then try to find the actor corresponding to our path/name - bool FoundActor = false; - for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) - { - if (ActorIt->GetPathName() != ActorPathName) - continue; - - // We found the actor - ActorPtr = *ActorIt; - FoundActor = true; - - break; - } - - if (FoundActor) - { - // We need to invalid our components so they can be updated later - // from the new actor - StaticMesh = NULL; - StaticMeshComponent = NULL; - SplineComponent = NULL; - } - - return FoundActor; -} - -UHoudiniAssetComponentMaterials_V1::UHoudiniAssetComponentMaterials_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} -void -UHoudiniAssetComponentMaterials_V1::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Assignments; - Ar << Replacements; -} - -UHoudiniHandleComponent_V1::UHoudiniHandleComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniAssetInstanceInput::UHoudiniAssetInstanceInput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetInstanceInput::Serialize(FArchive& Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Flags.HoudiniAssetInstanceInputFlagsPacked; - Ar << HoudiniGeoPartObject; - - Ar << ObjectToInstanceId; - // Object id is transient - if (Ar.IsLoading() && !Ar.IsTransacting()) - ObjectToInstanceId = -1; - - // Serialize fields. - Ar << InstanceInputFields; -} - -UHoudiniAssetInstanceInputField::UHoudiniAssetInstanceInputField(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetInstanceInputField::Serialize(FArchive& Ar) -{ - // Call base implementation first. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - const int32 InstanceInputFieldVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - - Ar << HoudiniAssetInstanceInputFieldFlagsPacked; - Ar << HoudiniGeoPartObject; - - FString UnusedInstancePathName; - Ar << UnusedInstancePathName; - Ar << RotationOffsets; - Ar << ScaleOffsets; - Ar << bScaleOffsetsLinearlyArray; - - Ar << InstancedTransforms; - Ar << VariationTransformsArray; - - if (Ar.IsSaving() || (Ar.IsLoading() && InstanceInputFieldVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS)) - { - Ar << InstanceColorOverride; - Ar << VariationInstanceColorOverrideArray; - } - - Ar << InstancerComponents; - Ar << InstancedObjects; - Ar << OriginalObject; -} - -void -UHoudiniHandleComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // XFormn Parames is an array of 9 float params + tuple index - // TX TY TZ - // RX RY RZ - // SX SY SZ - - //UHoudiniAssetParameterFloat_V1* XFormParams[9]; - //int32 XFormParamsTupleIndex[9]; - for (int32 i = 0; i < 9; ++i) - { - Ar << XFormParams[i]; - Ar << XFormParamsTupleIndex[i]; - } - - //UHoudiniAssetParameterChoice_V1* RSTParm; - Ar << RSTParm; - //int32 RSTParmTupleIdx; - Ar << RSTParmTupleIdx; - - //UHoudiniAssetParameterChoice_V1* RotOrderParm; - Ar << RotOrderParm; - //int32 RotOrderParmTupleIdx; - Ar << RotOrderParmTupleIdx; -} - -/* -UHoudiniHandleComponent* -UHoudiniHandleComponent_V1::ConvertLegacyData(UObject* Outer) -{ - UHoudiniHandleComponent* NewHandle = nullptr; - - return NewHandle; -} -*/ - -bool -UHoudiniHandleComponent_V1::UpdateFromLegacyData(UHoudiniHandleComponent* NewHC) -{ - if (!NewHC || NewHC->IsPendingKill()) - return false; - - // TODO - //NewHC->XformParms; - //NewHC->RSTParm; - //NewHC->RotOrderParm; - //NewHC->HandleType; - //NewHC->HandleName; - - return true; -} - -UHoudiniSplineComponent_V1::UHoudiniSplineComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniSplineComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - int32 Version = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << Version; - - Ar << HoudiniGeoPartObject; - - if (Version < VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM) - { - // Before, curve points where stored as Vectors, not Transforms - TArray OldCurvePoints; - Ar << OldCurvePoints; - - CurvePoints.SetNumUninitialized(OldCurvePoints.Num()); - - FTransform trans = FTransform::Identity; - for (int32 n = 0; n < CurvePoints.Num(); n++) - { - trans.SetLocation(OldCurvePoints[n]); - CurvePoints[n] = trans; - } - } - else - { - Ar << CurvePoints; - } - - Ar << CurveDisplayPoints; - - Ar << CurveType; - Ar << CurveMethod; - Ar << bClosedCurve; -} - -UHoudiniSplineComponent* -UHoudiniSplineComponent_V1::ConvertLegacyData(UObject* Outer) -{ - UHoudiniSplineComponent* NewSpline = NewObject( - GetOuter(), UHoudiniSplineComponent::StaticClass()); - - UpdateFromLegacyData(NewSpline); - - return NewSpline; -} - -bool -UHoudiniSplineComponent_V1::UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline) -{ - if (!NewSpline || NewSpline->IsPendingKill()) - return false; - - NewSpline->SetFlags(RF_Transactional); - - NewSpline->CurvePoints = CurvePoints; - NewSpline->DisplayPoints = CurveDisplayPoints; - //NewSpline->DisplayPointIndexDivider; - //NewSpline->HoudiniSplineName; - NewSpline->bClosed = bClosedCurve; - NewSpline->bReversed = false; - NewSpline->bIsHoudiniSplineVisible = true; - - //0 Polygon 1 Nurbs 2 Bezier - if (CurveType == 0) - NewSpline->CurveType = EHoudiniCurveType::Polygon; - else if (CurveType == 1) - NewSpline->CurveType = EHoudiniCurveType::Nurbs; - else if (CurveType == 2) - NewSpline->CurveType = EHoudiniCurveType::Bezier; - else - NewSpline->CurveType = EHoudiniCurveType::Invalid; - - // 0 CVs, 1 Breakpoints, 2 Freehand - if (CurveMethod == 0) - NewSpline->CurveMethod = EHoudiniCurveMethod::CVs; - else if (CurveMethod == 1) - NewSpline->CurveMethod = EHoudiniCurveMethod::Breakpoints; - else if (CurveMethod == 2) - NewSpline->CurveMethod = EHoudiniCurveMethod::Freehand; - else - NewSpline->CurveMethod = EHoudiniCurveMethod::Invalid; - - NewSpline->bIsOutputCurve = false; - - NewSpline->HoudiniGeoPartObject = HoudiniGeoPartObject.ConvertLegacyData(); - - if (NewSpline->HoudiniGeoPartObject.bIsEditable) - { - NewSpline->bIsEditableOutputCurve = true; - NewSpline->bIsInputCurve = false; - } - else - { - NewSpline->bIsInputCurve = false; - NewSpline->bIsEditableOutputCurve = true; - } - - // Create a default Houdini spline input if a null pointer is passed in. - FName HoudiniSplineName = MakeUniqueObjectName(GetOuter(), UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); - NewSpline->SetHoudiniSplineName(HoudiniSplineName.ToString()); - - //NewSpline->bHasChanged; - //NewSpline->bNeedsToTriggerUpdate; - //NewSpline->InputObject; - //NewSpline->NodeId; - //NewSpline->PartName; - - return true; -} - -UHoudiniAssetParameter::UHoudiniAssetParameter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameter::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniAssetParameterVersion; - - Ar << HoudiniAssetParameterFlagsPacked; - - if (Ar.IsLoading()) - bChanged = false; - - Ar << ParameterName; - Ar << ParameterLabel; - - Ar << NodeId; - if (!Ar.IsTransacting() && Ar.IsLoading()) - { - // NodeId is invalid after load - NodeId = -1; - } - Ar << ParmId; - - Ar << ParmParentId; - Ar << ChildIndex; - - Ar << TupleSize; - Ar << ValuesIndex; - Ar << MultiparmInstanceIndex; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER) - { - UObject* Dummy = nullptr; - Ar << Dummy; - } - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP) - { - Ar << ParameterHelp; - } - else - { - ParameterHelp = TEXT(""); - } - /* - if (Ar.IsTransacting()) - { - Ar << PrimaryObject; - Ar << ParentParameter; - } - */ -} - -UHoudiniParameter* -UHoudiniAssetParameter::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameter::Create(Outer, ParameterName); -} - -void -UHoudiniAssetParameter::CopyLegacyParameterData(UHoudiniParameter* InNewParm) -{ - if (!InNewParm || InNewParm->IsPendingKill()) - return; - - InNewParm->Name = ParameterName; - InNewParm->Label = ParameterLabel; - //InNewParm->ParmType; - InNewParm->TupleSize = TupleSize; - InNewParm->NodeId = NodeId; - InNewParm->ParmId = ParmId; - InNewParm->ParentParmId = ParmParentId; - InNewParm->ChildIndex = ChildIndex; - InNewParm->bIsVisible = true; - InNewParm->bIsDisabled = bIsDisabled; - InNewParm->bHasChanged = bChanged; - //InNewParm->bNeedsToTriggerUpdate; - //InNewParm->bIsDefault; - InNewParm->bIsSpare = bIsSpare; - InNewParm->bJoinNext = false; - InNewParm->bIsChildOfMultiParm = bIsChildOfMultiparm; - // TODO: MultiparmInstanceIndex ? - //InNewParm->bIsDirectChildOfMultiParm; - InNewParm->bPendingRevertToDefault = false; - //InNewParm->TuplePendingRevertToDefault = false; - InNewParm->Help = ParameterHelp; - InNewParm->TagCount = 0; - InNewParm->ValueIndex = ValuesIndex; - //InNewParm->bHasExpression; - //InNewParm->bShowExpression; - //InNewParm->ParamExpression; - //InNewParm->Tags; - InNewParm->bAutoUpdate = true; -} - -UHoudiniAssetParameterChoice::UHoudiniAssetParameterChoice(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterChoice::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - { - StringChoiceValues.Empty(); - StringChoiceLabels.Empty(); - } - - int32 NumChoices = StringChoiceValues.Num(); - Ar << NumChoices; - - int32 NumLabels = StringChoiceLabels.Num(); - Ar << NumLabels; - - if (Ar.IsLoading()) - { - FString Temp; - for (int32 ChoiceIdx = 0; ChoiceIdx < NumChoices; ++ChoiceIdx) - { - Ar << Temp; - StringChoiceValues.Add(Temp); - } - - for (int32 LabelIdx = 0; LabelIdx < NumLabels; ++LabelIdx) - { - Ar << Temp; - StringChoiceLabels.Add(Temp); - } - } - - Ar << StringValue; - Ar << CurrentValue; - - Ar << bStringChoiceList; -} - -UHoudiniParameter* -UHoudiniAssetParameterChoice::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterChoice* Parm = nullptr; - if (bStringChoiceList) - { - Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::StringChoice); - } - else - { - Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::IntChoice); - } - - Parm->SetNumChoices(StringChoiceValues.Num()); - for (int32 Idx = 0; Idx < StringChoiceValues.Num(); Idx++) - { - FString * ChoiceValue = Parm->GetStringChoiceValueAt(Idx); - if (ChoiceValue) - *ChoiceValue = StringChoiceValues[Idx]; - FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); - } - - for (int32 Idx = 0; Idx < StringChoiceLabels.Num(); Idx++) - { - FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); - if (ChoiceLabel) - *ChoiceLabel = StringChoiceValues[Idx]; - } - - Parm->SetStringValue(StringValue); - Parm->SetIntValue(CurrentValue); - //Parm->DefaultStringValue = StringValue; - //Parm->SetDefault(); - //Parm->DefaultIntValue = CurrentValue; - - return Parm; -} - -UHoudiniAssetParameterButton::UHoudiniAssetParameterButton(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterButton::ConvertLegacyData(UObject* Outer) -{ - // Button strips where not supported in v1, just create a normal button - return UHoudiniParameterButton::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterColor::UHoudiniAssetParameterColor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterColor::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - Color = FColor::White; - - Ar << Color; -} - -UHoudiniParameter* -UHoudiniAssetParameterColor::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterColor* Parm = UHoudiniParameterColor::Create(Outer, ParameterName); - Parm->SetColorValue(Color); - - //Parm->DefaultColor = Color; - Parm->SetDefaultValue(); - - //Parm->bIsChildOfRamp = false; - - return Parm; -} - -UHoudiniAssetParameterFile::UHoudiniAssetParameterFile(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterFile::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - Ar << Filters; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY) - Ar << IsReadOnly; -} - -UHoudiniParameter* -UHoudiniAssetParameterFile::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterFile* Parm = UHoudiniParameterFile::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - Parm->SetFileFilters(Filters); - Parm->SetReadOnly(IsReadOnly); - - return Parm; -} - -UHoudiniAssetParameterFloat::UHoudiniAssetParameterFloat(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterFloat::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - - Ar << ValueMin; - Ar << ValueMax; - - Ar << ValueUIMin; - Ar << ValueUIMax; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) - Ar << ValueUnit; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP) - Ar << NoSwap; -} - -UHoudiniParameter* -UHoudiniAssetParameterFloat::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterFloat* Parm = UHoudiniParameterFloat::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - Parm->SetUnit(ValueUnit); - Parm->SetHasMin(true); - Parm->SetHasMax(true); - Parm->SetHasUIMin(true); - Parm->SetHasUIMax(true); - Parm->SetIsLogarithmic(false); - Parm->SetMin(ValueMin); - Parm->SetMax(ValueMax); - Parm->SetUIMin(ValueUIMin); - Parm->SetUIMax(ValueUIMax); - - //Parm->bIsChildOfRamp = false; - - return Parm; -} - -UHoudiniAssetParameterFolder::UHoudiniAssetParameterFolder(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterFolder::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterFolder::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterFolderList::UHoudiniAssetParameterFolderList(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterFolderList::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterFolderList::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterInt::UHoudiniAssetParameterInt(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterInt::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - - Ar << ValueMin; - Ar << ValueMax; - - Ar << ValueUIMin; - Ar << ValueUIMax; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) - Ar << ValueUnit; -} - -UHoudiniParameter* -UHoudiniAssetParameterInt::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterInt* Parm = UHoudiniParameterInt::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - - //Parm->DefaultValues = Values; - Parm->SetUnit(ValueUnit); - Parm->SetHasMin(true); - Parm->SetHasMax(true); - Parm->SetHasUIMin(true); - Parm->SetHasUIMax(true); - Parm->SetIsLogarithmic(false); - Parm->SetMin(ValueMin); - Parm->SetMax(ValueMax); - Parm->SetUIMin(ValueUIMin); - Parm->SetUIMax(ValueUIMax); - - return Parm; -} - -UHoudiniAssetParameterLabel::UHoudiniAssetParameterLabel(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterLabel::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterLabel::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterMultiparm::UHoudiniAssetParameterMultiparm(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterMultiparm::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - MultiparmValue = 0; - - Ar << MultiparmValue; -} - -UHoudiniParameter* -UHoudiniAssetParameterMultiparm::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterMultiParm* Parm = UHoudiniParameterMultiParm::Create(Outer, ParameterName); - - //Parm->bIsShown; - //Parm->Value; - //Parm->TemplateName; - Parm->MultiparmValue = MultiparmValue; - //Parm->MultiParmInstanceNum; - //Parm->MultiParmInstanceLength; - //Parm->MultiParmInstanceCount; - //Parm->InstanceStartOffset; - //Parm->DefaultInstanceCount; - - // TODO: - // MultiparmInstanceIndex? - - return Parm; -} - -UHoudiniAssetParameterRamp::UHoudiniAssetParameterRamp(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterRamp::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - int32 multiparmvalue = 0; - Ar << multiparmvalue; - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << HoudiniAssetParameterRampCurveFloat; - Ar << HoudiniAssetParameterRampCurveColor; - - Ar << bIsFloatRamp; -} - -UHoudiniParameter* -UHoudiniAssetParameterRamp::ConvertLegacyData(UObject* Outer) -{ - if (bIsFloatRamp) - { - UHoudiniParameterRampFloat* Parm = UHoudiniParameterRampFloat::Create(Outer, ParameterName); - - // TODO: - // Convert HoudiniAssetParameterRampCurveFloat - - return Parm; - } - else - { - UHoudiniParameterRampColor* Parm = UHoudiniParameterRampColor::Create(Outer, ParameterName); - - // TODO: - // Convert HoudiniAssetParameterRampCurveColor - - return Parm; - } -} - -UHoudiniAssetParameterSeparator::UHoudiniAssetParameterSeparator(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterSeparator::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterSeparator::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterString::UHoudiniAssetParameterString(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterString::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; -} - -UHoudiniParameter* -UHoudiniAssetParameterString::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterString* Parm = UHoudiniParameterString::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetIsAssetRef(false); - Parm->SetDefaultValues(); - - //Parm->DefaultValues = Values; - //Parm->ChosenAssets.Empty(); - //Parm->bIsAssetRef = false; - - return Parm; -} - -UHoudiniAssetParameterToggle::UHoudiniAssetParameterToggle(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterToggle::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; -} - -UHoudiniParameter* -UHoudiniAssetParameterToggle::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterToggle* Parm = UHoudiniParameterToggle::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt((bool)Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - return Parm; -} - -UHoudiniMeshSplitInstancerComponent_V1::UHoudiniMeshSplitInstancerComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniMeshSplitInstancerComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << InstancedMesh; - Ar << OverrideMaterial; - Ar << Instances; -} - -bool -UHoudiniMeshSplitInstancerComponent_V1::UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC) -{ - if (!NewMSIC || NewMSIC->IsPendingKill()) - return false; - - NewMSIC->Instances = Instances; - NewMSIC->OverrideMaterials.Add(OverrideMaterial); - NewMSIC->InstancedMesh = InstancedMesh; - - return true; -} - -UHoudiniInstancedActorComponent_V1::UHoudiniInstancedActorComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniInstancedActorComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << InstancedAsset; - Ar << Instances; -} - -bool -UHoudiniInstancedActorComponent_V1::UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC) -{ - if (!NewIAC || NewIAC->IsPendingKill()) - return false; - - //NewIAC->SetInstancedObject(InstancedAsset); - NewIAC->InstancedObject = InstancedAsset; - NewIAC->InstancedActors = Instances; - - return true; +/* +* Copyright (c) <2020> 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 "HoudiniCompatibilityHelpers.h" + +#include "HoudiniPluginSerializationVersion.h" + +#include "HoudiniInput.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniHandleComponent.h" + +#include "Engine/StaticMesh.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "EngineUtils.h" // for TActorIterator<> + +#if WITH_EDITOR + #include "Editor.h" + #include "Editor/UnrealEdEngine.h" + #include "UnrealEdGlobals.h" +#endif + +// TODO: +// HoudiniInstancedActorComponent ? +// HoudiniMeshSplitInstancerComponent ? + +UHoudiniAssetComponent_V1::UHoudiniAssetComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + + +void +UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) +{ + if (!Ar.IsLoading()) + return; + + //Super::Serialize(Ar); + + //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Serialize component flags. + Ar << HoudiniAssetComponentFlagsPacked; + + // Serialize format version. + uint32 HoudiniAssetComponentVersion = 0;//Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + Ar << HoudiniAssetComponentVersion; + + // ComponenState Enum, saved as uint8 + // 0 invalid + // 1 None + // 2 Instantiated + // 3 BeingCooked + // Serialize component state. + uint8 ComponentState = 0; + Ar << ComponentState; + + // Serialize scaling information and import axis. + Ar << GeneratedGeometryScaleFactor; + Ar << TransformScaleFactor; + + // ImportAxis Enum, saved as uint8 + // 0 unreal 1 Houdini + //uint8 ImportAxis = 0; + Ar << ImportAxis; + + // Serialize generated component GUID. + Ar << ComponentGUID; + + // If component is in invalid state, we can skip the rest of serialization. + //if (ComponentState == EHoudiniAssetComponentState_V1::Invalid) + if (ComponentState == 0) + return; + + // Serialize Houdini asset. + Ar << HoudiniAsset; + + // 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; + int TempId; + Ar << TempId; + bIsPlayModeActive_Unused = false; + } + + // Serialization of default preset. + Ar << DefaultPresetBuffer; + + // Serialization of preset. + { + bool bPresetSaved = false; + Ar << bPresetSaved; + + if (bPresetSaved) + { + Ar << PresetBuffer; + } + } + + // Serialize parameters. + //SerializeParameters(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::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; + } + + // 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::TIterator IterParams(Parameters); IterParams; ++IterParams) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill()) + ParameterByName.Add(HoudiniAssetParameter->ParameterName, HoudiniAssetParameter); + } + } + } + + // Serialize inputs. + //SerializeInputs(Ar); + { + Ar << Inputs; + + /* + if (Ar.IsLoading()) + { + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) + { + UHoudiniAssetInput_V1 * HoudiniAssetInput = Inputs[InputIdx]; + if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) + Inputs[InputIdx]->SetHoudiniAssetComponent(this); + } + } + */ + } + + // 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); + { + //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; + int HoudiniInstanceInputKey = -1; + + Ar << HoudiniInstanceInputKey; + Ar << InstanceInputs[InstanceInputIdx]; + } + } + } + + // 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 DirtyPackages; + if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES) + { + TMap SavedPackages; + Ar << SavedPackages; + } + + if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS) + { + // Temporary Mesh Packages + TMap MeshPackages; + Ar << MeshPackages; + + // Temporary Landscape Layers Packages + TMap LayerPackages; + Ar << LayerPackages; + } +} + +uint32 +GetTypeHash(const FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) +{ + return HoudiniGeoPartObject.GetTypeHash(); +} + +FArchive & +operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) +{ + HoudiniGeoPartObject.Serialize(Ar); + return Ar; +} + +uint32 +FHoudiniGeoPartObject_V1::GetTypeHash() const +{ + int32 HashBuffer[4] = { ObjectId, GeoId, PartId, SplitId }; + int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitName, Hash); +} + +bool +FHoudiniGeoPartObject_V1SortPredicate::operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const +{ + /*if (!A.IsValid() || !B.IsValid()) + return false;*/ + + if (A.ObjectId == B.ObjectId) + { + if (A.GeoId == B.GeoId) + { + if (A.PartId == B.PartId) + return A.SplitId < B.SplitId; + else + return A.PartId < B.PartId; + } + else + { + return A.GeoId < B.GeoId; + } + } + + return A.ObjectId < B.ObjectId; +} + +void +FHoudiniGeoPartObject_V1::Serialize(FArchive & Ar) +{ + //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniGeoPartObjectVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniGeoPartObjectVersion; + + Ar << TransformMatrix; + + Ar << ObjectName; + Ar << PartName; + Ar << SplitName; + + // Serialize instancer material. + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME) + Ar << InstancerMaterialName; + + // Serialize instancer attribute material. + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME) + Ar << InstancerAttributeMaterialName; + + Ar << AssetId; + Ar << ObjectId; + Ar << GeoId; + Ar << PartId; + Ar << SplitId; + + Ar << HoudiniGeoPartObjectFlagsPacked; + + if (HoudiniGeoPartObjectVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + // Prior to this version the unused flags space was not zero-initialized, so + // zero them out now to prevent misinterpreting any 1s + HoudiniGeoPartObjectFlagsPacked &= 0x3FFFF; + } + + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH) + { + Ar << NodePath; + } +} + + +FHoudiniGeoPartObject +FHoudiniGeoPartObject_V1::ConvertLegacyData() +{ + FHoudiniGeoPartObject NewHGPO; + + NewHGPO.AssetId = AssetId; + // NewHGPO.AssetName; + + NewHGPO.ObjectId = ObjectId; + NewHGPO.ObjectName = ObjectName; + + NewHGPO.GeoId = GeoId; + + NewHGPO.PartId = PartId; + NewHGPO.PartName = PartName; + NewHGPO.bHasCustomPartName = bHasCustomName; + + NewHGPO.SplitGroups.Add(SplitName); + + NewHGPO.TransformMatrix = TransformMatrix; + NewHGPO.NodePath = NodePath; + + // NewHGPO.VolumeName; + // NewHGPO.VolumeTileIndex; + + NewHGPO.bIsVisible = bIsVisible; + NewHGPO.bIsEditable = bIsEditable; + // NewHGPO.bIsTemplated; + // NewHGPO.bIsInstanced; + + NewHGPO.bHasGeoChanged = bHasGeoChanged; + // NewHGPO.bHasPartChanged; + // NewHGPO.bHasTransformChanged; + // NewHGPO.bHasMaterialsChanged; + NewHGPO.bLoaded = true; //bIsLoaded; + + // Hamdle Part Type + if (bIsCurve) + { + NewHGPO.Type = EHoudiniPartType::Curve; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + else if (bIsVolume) + { + NewHGPO.Type = EHoudiniPartType::Volume; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + else if (bIsInstancer) + { + NewHGPO.Type = EHoudiniPartType::Instancer; + NewHGPO.InstancerType = EHoudiniInstancerType::ObjectInstancer; + } + else if (bIsPackedPrimitiveInstancer) + { + NewHGPO.Type = EHoudiniPartType::Instancer; + NewHGPO.InstancerType = EHoudiniInstancerType::PackedPrimitive; + } + else + { + NewHGPO.Type = EHoudiniPartType::Mesh; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + + // Instancer specific flags + if (NewHGPO.Type == EHoudiniPartType::Instancer) + { + //bInstancerMaterialAvailable + //bInstancerAttributeMaterialAvailable + //InstancerMaterialName + //InstancerAttributeMaterialName + } + + // Collision specific flags + if (NewHGPO.Type == EHoudiniPartType::Mesh) + { + //bIsCollidable + //bIsRenderCollidable + //bIsUCXCollisionGeo + //bIsSimpleCollisionGeo + //bHasCollisionBeenAdded + //bHasSocketBeenAdded + } + + //bIsBox + //bIsSphere + + if (NewHGPO.SplitGroups.Num() <= 0) + { + NewHGPO.SplitGroups.Add("main_geo"); + } + + return NewHGPO; +} + +UHoudiniAssetInput::UHoudiniAssetInput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} +void +UHoudiniAssetInput::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Serialize current choice selection. + // Enum serialized as uint8 + // 0 GeometryInput + // 1 AssetInput + // 2 CurveInput + // 3 LandscapeInput + // 4 WorldInput + // 5 SkeletonInput + //SerializeEnumeration< EHoudiniAssetInputType::Enum >(Ar, ChoiceIndex); + Ar << ChoiceIndex; + + Ar << ChoiceStringValue; + + // We need these temporary variables for undo state tracking. + bool bLocalInputAssetConnectedInHoudini = bInputAssetConnectedInHoudini; + UHoudiniAssetComponent_V1 * LocalInputAssetComponent = InputAssetComponent; + + Ar << HoudiniAssetInputFlagsPacked; + + // Serialize input index. + Ar << InputIndex; + + // Serialize input objects (if it's assigned). + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT) + { + Ar << InputObjects; + } + else + { + UObject* InputObject = nullptr; + Ar << InputObject; + InputObjects.Empty(); + InputObjects.Add(InputObject); + } + + // Serialize input asset. + Ar << InputAssetComponent; + + // Serialize curve and curve parameters (if we have those). + Ar << InputCurve; + Ar << InputCurveParameters; + + // Serialize landscape used for input. + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT) + { + if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF) + { + ALandscapeProxy* InputLandscapePtr = nullptr; + Ar << InputLandscapePtr; + + InputLandscapeProxy = InputLandscapePtr; + } + else + { + Ar << InputLandscapeProxy; + } + + } + + // Serialize world outliner inputs. + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT) + { + Ar << InputOutlinerMeshArray; + } + + // Create necessary widget resources. + bLoadedParameter = true; + // If we're loading for real for the first time we need to reset this + // flag so we can reconnect when we get our parameters uploaded. + bInputAssetConnectedInHoudini = false; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT) + Ar << UnrealSplineResolution; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS) + { + Ar << InputTransforms; + } + else + { + InputTransforms.SetNum(InputObjects.Num()); + for (int32 n = 0; n < InputTransforms.Num(); n++) + InputTransforms[n] = FTransform::Identity; + } + + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + Ar << InputLandscapeTransform; +} + +UHoudiniInput* +UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) +{ + UHoudiniInput* Input = NewObject( + InOuter, UHoudiniInput::StaticClass(), FName(*ParameterLabel), RF_Transactional); + + EHoudiniInputType InputType = EHoudiniInputType::Invalid; + if (ChoiceIndex == 0) + InputType = EHoudiniInputType::Geometry; + else if (ChoiceIndex == 1) + InputType = EHoudiniInputType::Asset; + else if (ChoiceIndex == 2) + InputType = EHoudiniInputType::Curve; + else if (ChoiceIndex == 3) + InputType = EHoudiniInputType::Landscape; + else if (ChoiceIndex == 4) + InputType = EHoudiniInputType::World; + else if (ChoiceIndex == 5) + { + //InputType = EHoudiniInputType::Skeletal; + InputType = EHoudiniInputType::Invalid; + } + else + { + // Invalid + InputType = EHoudiniInputType::Invalid; + } + + bool bBlueprintStructureModified = false; + Input->SetInputType(InputType, bBlueprintStructureModified); + + Input->SetExportColliders(false); + Input->SetExportLODs(bExportAllLODs); + Input->SetExportSockets(bExportSockets); + Input->SetCookOnCurveChange(true); + + // If KeepWorldTransform is set to 2, use the default value + if (bKeepWorldTransform == 2) + Input->SetKeepWorldTransform((bool)Input->GetDefaultXTransformType()); + else + Input->SetKeepWorldTransform((bool)bKeepWorldTransform); + + Input->SetUnrealSplineResolution(UnrealSplineResolution); + Input->SetPackBeforeMerge(bPackBeforeMerge); + if(bIsObjectPathParameter) + Input->SetObjectPathParameter(ParmId); + else + Input->SetSOPInput(InputIndex); + + Input->SetImportAsReference(false); + Input->SetHelp(ParameterHelp); + //Input->SetInputNodeId(-1); + + if (bLandscapeExportAsHeightfield) + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Heightfield); + else if (bLandscapeExportAsMesh) + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Mesh); + else + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Points); + + Input->SetLabel(ParameterLabel); + Input->SetName(ParameterName); + Input->SetUpdateInputLandscape(bUpdateInputLandscape); + + if (InputType == EHoudiniInputType::Geometry) + { + // Get the geo input object array + bool bNeedToEmpty = true; + TArray* GeoInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(GeoInputObjectsPtr)) + { + // Add the geometry input objects + for (int32 AtIndex = 0; AtIndex < InputObjects.Num(); AtIndex++) + { + // Create a new InputObject wrapper + UObject* CurObject = InputObjects[AtIndex]; + if (!CurObject || CurObject->IsPendingKill()) + continue; + + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurObject, Input, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + continue; + + // Remove the default/null object + if (bNeedToEmpty) + { + GeoInputObjectsPtr->Empty(); + bNeedToEmpty = false; + } + + // Add to the geo input array + GeoInputObjectsPtr->Add(NewInputObject); + } + } + } + else if (InputType == EHoudiniInputType::Asset && InputAssetComponent != nullptr && !InputAssetComponent->IsPendingKill()) + { + // Get the asset input object array + TArray* AssetInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(AssetInputObjectsPtr)) + { + // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent + // We can simply use the v1's HAC outer for that + UHoudiniAssetComponent* InputHAC = Cast(InputAssetComponent->GetOuter()); + if (InputHAC && !InputHAC->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputHAC, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the asset input array + AssetInputObjectsPtr->Add(NewInputObject); + } + } + } + } + else if (InputType == EHoudiniInputType::Curve) + { + // Get the curve input object array + TArray* CurveInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(CurveInputObjectsPtr)) + { + if (InputCurve && !InputCurve->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputCurve, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the curve input array + CurveInputObjectsPtr->Add(NewInputObject); + } + + // InputCurve->SetInputObject(NewInputObject); + + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = Cast(NewInputObject); + if(HoudiniSplineInput) + HoudiniSplineInput->Update(InputCurve); + } + } + + // TODO ??? + //InputCurveParameters; + } + else if (InputType == EHoudiniInputType::Landscape) + { + // Get the Landscape input object array + TArray* LandscapeInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(LandscapeInputObjectsPtr)) + { + // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent + // We can simply use the v1's HAC outer for that + ALandscapeProxy* InLandscape = InputLandscapeProxy.Get(); + if (InLandscape && !InLandscape->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InLandscape, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the geo input array + LandscapeInputObjectsPtr->Add(NewInputObject); + } + } + } + + Input->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; + Input->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; + Input->bLandscapeExportLighting = bLandscapeExportLighting; + Input->bLandscapeExportMaterials = bLandscapeExportMaterials; + Input->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; + Input->bLandscapeExportTileUVs = bLandscapeExportTileUVs; + + //bLandscapeExportCurves; + } + else if (InputType == EHoudiniInputType::World) + { + // Get the world input object array + TArray* WorldInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + + UWorld* MyWorld = InOuter->GetWorld(); + if (ensure(WorldInputObjectsPtr)) + { + // Add the geometry input objects + for (int32 AtIndex = 0; AtIndex < InputOutlinerMeshArray.Num(); AtIndex++) + { + FHoudiniAssetInputOutlinerMesh_V1& CurWorldInObj = InputOutlinerMeshArray[AtIndex]; + + AActor* CurActor = nullptr; + if (CurWorldInObj.ActorPtr.IsValid()) + { + CurActor = CurWorldInObj.ActorPtr.Get(); + } + else + { + // Try to update the actor ptr via the pathname + CurWorldInObj.TryToUpdateActorPtrFromActorPathName(MyWorld); + if (CurWorldInObj.ActorPtr.IsValid()) + { + CurActor = CurWorldInObj.ActorPtr.Get(); + } + } + + if(!CurActor || CurActor->IsPendingKill()) + continue; + + // Create a new InputObject wrapper for the actor + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurActor, Input, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + continue; + + // Add to the geo input array + WorldInputObjectsPtr->Add(NewInputObject); + } + } + + /* + CurWorldInObj->HoudiniAssetParameterVersion; + CurWorldInObj->ActorPtr; + CurWorldInObj->ActorPathName; + CurWorldInObj->StaticMeshComponent; + CurWorldInObj->StaticMesh; + CurWorldInObj->SplineComponent; + CurWorldInObj->NumberOfSplineControlPoints; + CurWorldInObj->SplineControlPointsTransform; + CurWorldInObj->SplineLength; + CurWorldInObj->SplineResolution; + CurWorldInObj->ActorTransform; + CurWorldInObj->ComponentTransform; + CurWorldInObj->AssetId; + CurWorldInObj->KeepWorldTransform; + CurWorldInObj->MeshComponentsMaterials; + CurWorldInObj->InstanceIndex; + */ + + //InputOutlinerMeshArray; + } + else + { + // Invalid + } + + //ChoiceStringValue; + //bStaticMeshChanged; + //bSwitchedToCurve; + //bLoadedParameter = true; + //bInputAssetConnectedInHoudini; + //InputTransforms; + //InputLandscapeTransform; + + return Input; +} + +FArchive& +operator<<(FArchive& Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh) +{ + HoudiniAssetInputOutlinerMesh.Serialize(Ar); + return Ar; +} + +void +FHoudiniAssetInputOutlinerMesh_V1::Serialize(FArchive& Ar) +{ + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniAssetParameterVersion; + + Ar << ActorPtr; + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + { + Ar << ActorPathName; + } + + if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) + { + Ar << StaticMeshComponent; + Ar << StaticMesh; + } + + Ar << ActorTransform; + + Ar << AssetId; + if (Ar.IsLoading() && !Ar.IsTransacting()) + AssetId = -1; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE + && HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) + { + Ar << SplineComponent; + Ar << NumberOfSplineControlPoints; + Ar << SplineLength; + Ar << SplineResolution; + Ar << ComponentTransform; + } + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM) + Ar << KeepWorldTransform; + + // UE4.19 SERIALIZATION FIX: + // The component materials serialization (24) was actually missing in the UE4.19 H17.0 / H16.5 plugin. + // However subsequent serialized changes (25+) were present in those version. This caused crashes when loading + // a level that was saved with 4.19+16.5/17.0 on a newer version of Unreal or Houdini... + // If the serialized version is exactly that of the fix, we can ignore the materials paths as well + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + Ar << MeshComponentsMaterials; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX) + Ar << InstanceIndex; +} + +bool +FHoudiniAssetInputOutlinerMesh_V1::TryToUpdateActorPtrFromActorPathName(UWorld* InWorld) +{ + // Ensure our current ActorPathName looks valid + if (ActorPathName.IsEmpty() || ActorPathName.Equals(TEXT("None"), ESearchCase::IgnoreCase)) + return false; + + // We'll try to find the corresponding actor by browsing through all the actors in the world.. + // Get the editor world + UWorld* World = InWorld; + if (!World) + return false; + + // Then try to find the actor corresponding to our path/name + bool FoundActor = false; + for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) + { + if (ActorIt->GetPathName() != ActorPathName) + continue; + + // We found the actor + ActorPtr = *ActorIt; + FoundActor = true; + + break; + } + + if (FoundActor) + { + // We need to invalid our components so they can be updated later + // from the new actor + StaticMesh = NULL; + StaticMeshComponent = NULL; + SplineComponent = NULL; + } + + return FoundActor; +} + +UHoudiniAssetComponentMaterials_V1::UHoudiniAssetComponentMaterials_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} +void +UHoudiniAssetComponentMaterials_V1::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Assignments; + Ar << Replacements; +} + +UHoudiniHandleComponent_V1::UHoudiniHandleComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniAssetInstanceInput::UHoudiniAssetInstanceInput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetInstanceInput::Serialize(FArchive& Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Flags.HoudiniAssetInstanceInputFlagsPacked; + Ar << HoudiniGeoPartObject; + + Ar << ObjectToInstanceId; + // Object id is transient + if (Ar.IsLoading() && !Ar.IsTransacting()) + ObjectToInstanceId = -1; + + // Serialize fields. + Ar << InstanceInputFields; +} + +UHoudiniAssetInstanceInputField::UHoudiniAssetInstanceInputField(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetInstanceInputField::Serialize(FArchive& Ar) +{ + // Call base implementation first. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + const int32 InstanceInputFieldVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + + Ar << HoudiniAssetInstanceInputFieldFlagsPacked; + Ar << HoudiniGeoPartObject; + + FString UnusedInstancePathName; + Ar << UnusedInstancePathName; + Ar << RotationOffsets; + Ar << ScaleOffsets; + Ar << bScaleOffsetsLinearlyArray; + + Ar << InstancedTransforms; + Ar << VariationTransformsArray; + + if (Ar.IsSaving() || (Ar.IsLoading() && InstanceInputFieldVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS)) + { + Ar << InstanceColorOverride; + Ar << VariationInstanceColorOverrideArray; + } + + Ar << InstancerComponents; + Ar << InstancedObjects; + Ar << OriginalObject; +} + +void +UHoudiniHandleComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // XFormn Parames is an array of 9 float params + tuple index + // TX TY TZ + // RX RY RZ + // SX SY SZ + + //UHoudiniAssetParameterFloat_V1* XFormParams[9]; + //int32 XFormParamsTupleIndex[9]; + for (int32 i = 0; i < 9; ++i) + { + Ar << XFormParams[i]; + Ar << XFormParamsTupleIndex[i]; + } + + //UHoudiniAssetParameterChoice_V1* RSTParm; + Ar << RSTParm; + //int32 RSTParmTupleIdx; + Ar << RSTParmTupleIdx; + + //UHoudiniAssetParameterChoice_V1* RotOrderParm; + Ar << RotOrderParm; + //int32 RotOrderParmTupleIdx; + Ar << RotOrderParmTupleIdx; +} + +/* +UHoudiniHandleComponent* +UHoudiniHandleComponent_V1::ConvertLegacyData(UObject* Outer) +{ + UHoudiniHandleComponent* NewHandle = nullptr; + + return NewHandle; +} +*/ + +bool +UHoudiniHandleComponent_V1::UpdateFromLegacyData(UHoudiniHandleComponent* NewHC) +{ + if (!NewHC || NewHC->IsPendingKill()) + return false; + + // TODO + //NewHC->XformParms; + //NewHC->RSTParm; + //NewHC->RotOrderParm; + //NewHC->HandleType; + //NewHC->HandleName; + + return true; +} + +UHoudiniSplineComponent_V1::UHoudiniSplineComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniSplineComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + int32 Version = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << Version; + + Ar << HoudiniGeoPartObject; + + if (Version < VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM) + { + // Before, curve points where stored as Vectors, not Transforms + TArray OldCurvePoints; + Ar << OldCurvePoints; + + CurvePoints.SetNumUninitialized(OldCurvePoints.Num()); + + FTransform trans = FTransform::Identity; + for (int32 n = 0; n < CurvePoints.Num(); n++) + { + trans.SetLocation(OldCurvePoints[n]); + CurvePoints[n] = trans; + } + } + else + { + Ar << CurvePoints; + } + + Ar << CurveDisplayPoints; + + Ar << CurveType; + Ar << CurveMethod; + Ar << bClosedCurve; +} + +UHoudiniSplineComponent* +UHoudiniSplineComponent_V1::ConvertLegacyData(UObject* Outer) +{ + UHoudiniSplineComponent* NewSpline = NewObject( + GetOuter(), UHoudiniSplineComponent::StaticClass()); + + UpdateFromLegacyData(NewSpline); + + return NewSpline; +} + +bool +UHoudiniSplineComponent_V1::UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline) +{ + if (!NewSpline || NewSpline->IsPendingKill()) + return false; + + NewSpline->SetFlags(RF_Transactional); + + NewSpline->CurvePoints = CurvePoints; + NewSpline->DisplayPoints = CurveDisplayPoints; + //NewSpline->DisplayPointIndexDivider; + //NewSpline->HoudiniSplineName; + NewSpline->bClosed = bClosedCurve; + NewSpline->bReversed = false; + NewSpline->bIsHoudiniSplineVisible = true; + + //0 Polygon 1 Nurbs 2 Bezier + if (CurveType == 0) + NewSpline->CurveType = EHoudiniCurveType::Polygon; + else if (CurveType == 1) + NewSpline->CurveType = EHoudiniCurveType::Nurbs; + else if (CurveType == 2) + NewSpline->CurveType = EHoudiniCurveType::Bezier; + else + NewSpline->CurveType = EHoudiniCurveType::Invalid; + + // 0 CVs, 1 Breakpoints, 2 Freehand + if (CurveMethod == 0) + NewSpline->CurveMethod = EHoudiniCurveMethod::CVs; + else if (CurveMethod == 1) + NewSpline->CurveMethod = EHoudiniCurveMethod::Breakpoints; + else if (CurveMethod == 2) + NewSpline->CurveMethod = EHoudiniCurveMethod::Freehand; + else + NewSpline->CurveMethod = EHoudiniCurveMethod::Invalid; + + NewSpline->bIsOutputCurve = false; + + NewSpline->HoudiniGeoPartObject = HoudiniGeoPartObject.ConvertLegacyData(); + + if (NewSpline->HoudiniGeoPartObject.bIsEditable) + { + NewSpline->bIsEditableOutputCurve = true; + NewSpline->bIsInputCurve = false; + } + else + { + NewSpline->bIsInputCurve = false; + NewSpline->bIsEditableOutputCurve = true; + } + + // Create a default Houdini spline input if a null pointer is passed in. + FName HoudiniSplineName = MakeUniqueObjectName(GetOuter(), UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); + NewSpline->SetHoudiniSplineName(HoudiniSplineName.ToString()); + + //NewSpline->bHasChanged; + //NewSpline->bNeedsToTriggerUpdate; + //NewSpline->InputObject; + //NewSpline->NodeId; + //NewSpline->PartName; + + return true; +} + +UHoudiniAssetParameter::UHoudiniAssetParameter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameter::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniAssetParameterVersion; + + Ar << HoudiniAssetParameterFlagsPacked; + + if (Ar.IsLoading()) + bChanged = false; + + Ar << ParameterName; + Ar << ParameterLabel; + + Ar << NodeId; + if (!Ar.IsTransacting() && Ar.IsLoading()) + { + // NodeId is invalid after load + NodeId = -1; + } + Ar << ParmId; + + Ar << ParmParentId; + Ar << ChildIndex; + + Ar << TupleSize; + Ar << ValuesIndex; + Ar << MultiparmInstanceIndex; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER) + { + UObject* Dummy = nullptr; + Ar << Dummy; + } + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP) + { + Ar << ParameterHelp; + } + else + { + ParameterHelp = TEXT(""); + } + /* + if (Ar.IsTransacting()) + { + Ar << PrimaryObject; + Ar << ParentParameter; + } + */ +} + +UHoudiniParameter* +UHoudiniAssetParameter::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameter::Create(Outer, ParameterName); +} + +void +UHoudiniAssetParameter::CopyLegacyParameterData(UHoudiniParameter* InNewParm) +{ + if (!InNewParm || InNewParm->IsPendingKill()) + return; + + InNewParm->Name = ParameterName; + InNewParm->Label = ParameterLabel; + //InNewParm->ParmType; + InNewParm->TupleSize = TupleSize; + InNewParm->NodeId = NodeId; + InNewParm->ParmId = ParmId; + InNewParm->ParentParmId = ParmParentId; + InNewParm->ChildIndex = ChildIndex; + InNewParm->bIsVisible = true; + InNewParm->bIsDisabled = bIsDisabled; + InNewParm->bHasChanged = bChanged; + //InNewParm->bNeedsToTriggerUpdate; + //InNewParm->bIsDefault; + InNewParm->bIsSpare = bIsSpare; + InNewParm->bJoinNext = false; + InNewParm->bIsChildOfMultiParm = bIsChildOfMultiparm; + // TODO: MultiparmInstanceIndex ? + //InNewParm->bIsDirectChildOfMultiParm; + InNewParm->bPendingRevertToDefault = false; + //InNewParm->TuplePendingRevertToDefault = false; + InNewParm->Help = ParameterHelp; + InNewParm->TagCount = 0; + InNewParm->ValueIndex = ValuesIndex; + //InNewParm->bHasExpression; + //InNewParm->bShowExpression; + //InNewParm->ParamExpression; + //InNewParm->Tags; + InNewParm->bAutoUpdate = true; +} + +UHoudiniAssetParameterChoice::UHoudiniAssetParameterChoice(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterChoice::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + { + StringChoiceValues.Empty(); + StringChoiceLabels.Empty(); + } + + int32 NumChoices = StringChoiceValues.Num(); + Ar << NumChoices; + + int32 NumLabels = StringChoiceLabels.Num(); + Ar << NumLabels; + + if (Ar.IsLoading()) + { + FString Temp; + for (int32 ChoiceIdx = 0; ChoiceIdx < NumChoices; ++ChoiceIdx) + { + Ar << Temp; + StringChoiceValues.Add(Temp); + } + + for (int32 LabelIdx = 0; LabelIdx < NumLabels; ++LabelIdx) + { + Ar << Temp; + StringChoiceLabels.Add(Temp); + } + } + + Ar << StringValue; + Ar << CurrentValue; + + Ar << bStringChoiceList; +} + +UHoudiniParameter* +UHoudiniAssetParameterChoice::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterChoice* Parm = nullptr; + if (bStringChoiceList) + { + Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::StringChoice); + } + else + { + Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::IntChoice); + } + + Parm->SetNumChoices(StringChoiceValues.Num()); + for (int32 Idx = 0; Idx < StringChoiceValues.Num(); Idx++) + { + FString * ChoiceValue = Parm->GetStringChoiceValueAt(Idx); + if (ChoiceValue) + *ChoiceValue = StringChoiceValues[Idx]; + FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); + } + + for (int32 Idx = 0; Idx < StringChoiceLabels.Num(); Idx++) + { + FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); + if (ChoiceLabel) + *ChoiceLabel = StringChoiceValues[Idx]; + } + + Parm->SetStringValue(StringValue); + Parm->SetIntValue(CurrentValue); + //Parm->DefaultStringValue = StringValue; + //Parm->SetDefault(); + //Parm->DefaultIntValue = CurrentValue; + + return Parm; +} + +UHoudiniAssetParameterButton::UHoudiniAssetParameterButton(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterButton::ConvertLegacyData(UObject* Outer) +{ + // Button strips where not supported in v1, just create a normal button + return UHoudiniParameterButton::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterColor::UHoudiniAssetParameterColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterColor::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + Color = FColor::White; + + Ar << Color; +} + +UHoudiniParameter* +UHoudiniAssetParameterColor::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterColor* Parm = UHoudiniParameterColor::Create(Outer, ParameterName); + Parm->SetColorValue(Color); + + //Parm->DefaultColor = Color; + Parm->SetDefaultValue(); + + //Parm->bIsChildOfRamp = false; + + return Parm; +} + +UHoudiniAssetParameterFile::UHoudiniAssetParameterFile(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterFile::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + Ar << Filters; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY) + Ar << IsReadOnly; +} + +UHoudiniParameter* +UHoudiniAssetParameterFile::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterFile* Parm = UHoudiniParameterFile::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + Parm->SetFileFilters(Filters); + Parm->SetReadOnly(IsReadOnly); + + return Parm; +} + +UHoudiniAssetParameterFloat::UHoudiniAssetParameterFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterFloat::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + + Ar << ValueMin; + Ar << ValueMax; + + Ar << ValueUIMin; + Ar << ValueUIMax; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) + Ar << ValueUnit; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP) + Ar << NoSwap; +} + +UHoudiniParameter* +UHoudiniAssetParameterFloat::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterFloat* Parm = UHoudiniParameterFloat::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + Parm->SetUnit(ValueUnit); + Parm->SetHasMin(true); + Parm->SetHasMax(true); + Parm->SetHasUIMin(true); + Parm->SetHasUIMax(true); + Parm->SetIsLogarithmic(false); + Parm->SetMin(ValueMin); + Parm->SetMax(ValueMax); + Parm->SetUIMin(ValueUIMin); + Parm->SetUIMax(ValueUIMax); + + //Parm->bIsChildOfRamp = false; + + return Parm; +} + +UHoudiniAssetParameterFolder::UHoudiniAssetParameterFolder(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterFolder::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterFolder::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterFolderList::UHoudiniAssetParameterFolderList(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterFolderList::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterFolderList::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterInt::UHoudiniAssetParameterInt(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterInt::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + + Ar << ValueMin; + Ar << ValueMax; + + Ar << ValueUIMin; + Ar << ValueUIMax; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) + Ar << ValueUnit; +} + +UHoudiniParameter* +UHoudiniAssetParameterInt::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterInt* Parm = UHoudiniParameterInt::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + + //Parm->DefaultValues = Values; + Parm->SetUnit(ValueUnit); + Parm->SetHasMin(true); + Parm->SetHasMax(true); + Parm->SetHasUIMin(true); + Parm->SetHasUIMax(true); + Parm->SetIsLogarithmic(false); + Parm->SetMin(ValueMin); + Parm->SetMax(ValueMax); + Parm->SetUIMin(ValueUIMin); + Parm->SetUIMax(ValueUIMax); + + return Parm; +} + +UHoudiniAssetParameterLabel::UHoudiniAssetParameterLabel(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterLabel::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterLabel::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterMultiparm::UHoudiniAssetParameterMultiparm(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterMultiparm::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + MultiparmValue = 0; + + Ar << MultiparmValue; +} + +UHoudiniParameter* +UHoudiniAssetParameterMultiparm::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterMultiParm* Parm = UHoudiniParameterMultiParm::Create(Outer, ParameterName); + + //Parm->bIsShown; + //Parm->Value; + //Parm->TemplateName; + Parm->MultiparmValue = MultiparmValue; + //Parm->MultiParmInstanceNum; + //Parm->MultiParmInstanceLength; + //Parm->MultiParmInstanceCount; + //Parm->InstanceStartOffset; + //Parm->DefaultInstanceCount; + + // TODO: + // MultiparmInstanceIndex? + + return Parm; +} + +UHoudiniAssetParameterRamp::UHoudiniAssetParameterRamp(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterRamp::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + int32 multiparmvalue = 0; + Ar << multiparmvalue; + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << HoudiniAssetParameterRampCurveFloat; + Ar << HoudiniAssetParameterRampCurveColor; + + Ar << bIsFloatRamp; +} + +UHoudiniParameter* +UHoudiniAssetParameterRamp::ConvertLegacyData(UObject* Outer) +{ + if (bIsFloatRamp) + { + UHoudiniParameterRampFloat* Parm = UHoudiniParameterRampFloat::Create(Outer, ParameterName); + + // TODO: + // Convert HoudiniAssetParameterRampCurveFloat + + return Parm; + } + else + { + UHoudiniParameterRampColor* Parm = UHoudiniParameterRampColor::Create(Outer, ParameterName); + + // TODO: + // Convert HoudiniAssetParameterRampCurveColor + + return Parm; + } +} + +UHoudiniAssetParameterSeparator::UHoudiniAssetParameterSeparator(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterSeparator::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterSeparator::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterString::UHoudiniAssetParameterString(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterString::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; +} + +UHoudiniParameter* +UHoudiniAssetParameterString::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterString* Parm = UHoudiniParameterString::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetIsAssetRef(false); + Parm->SetDefaultValues(); + + //Parm->DefaultValues = Values; + //Parm->ChosenAssets.Empty(); + //Parm->bIsAssetRef = false; + + return Parm; +} + +UHoudiniAssetParameterToggle::UHoudiniAssetParameterToggle(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterToggle::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; +} + +UHoudiniParameter* +UHoudiniAssetParameterToggle::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterToggle* Parm = UHoudiniParameterToggle::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt((bool)Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + return Parm; +} + +UHoudiniMeshSplitInstancerComponent_V1::UHoudiniMeshSplitInstancerComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniMeshSplitInstancerComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << InstancedMesh; + Ar << OverrideMaterial; + Ar << Instances; +} + +bool +UHoudiniMeshSplitInstancerComponent_V1::UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC) +{ + if (!NewMSIC || NewMSIC->IsPendingKill()) + return false; + + NewMSIC->Instances = Instances; + NewMSIC->OverrideMaterials.Add(OverrideMaterial); + NewMSIC->InstancedMesh = InstancedMesh; + + return true; +} + +UHoudiniInstancedActorComponent_V1::UHoudiniInstancedActorComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniInstancedActorComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << InstancedAsset; + Ar << Instances; +} + +bool +UHoudiniInstancedActorComponent_V1::UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC) +{ + if (!NewIAC || NewIAC->IsPendingKill()) + return false; + + //NewIAC->SetInstancedObject(InstancedAsset); + NewIAC->InstancedObject = InstancedAsset; + NewIAC->InstancedActors = Instances; + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h index 391669c40..f6701285a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h @@ -1,1098 +1,1098 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniAsset.h" - -#include "Components/PrimitiveComponent.h" - -#include "Curves/CurveFloat.h" -#include "Curves/CurveLinearColor.h" - -#include "HoudiniCompatibilityHelpers.generated.h" - -class UStaticMesh; -class UStaticMeshComponent; -class USplineComponent; -class ALandscapeProxy; -class UMaterialInterface; -class UHoudiniInput; -class UHoudiniParameter; -class UHoudiniHandleComponent; -class UHoudiniSplineComponent; -class UHoudiniInstancedActorComponent; -class UHoudiniMeshSplitInstancerComponent; -class UFoliageType_InstancedStaticMesh; - - -struct FHoudiniGeoPartObject; - - -struct FHoudiniGeoPartObject_V1 -{ -public: - - void Serialize(FArchive & Ar); - - FHoudiniGeoPartObject ConvertLegacyData(); - - /** Return hash value for this object, used when using this object as a key inside hashing containers. **/ - uint32 GetTypeHash() const; - - /** Transform of this geo part object. **/ - FTransform TransformMatrix; - - /** Name of associated object. **/ - FString ObjectName; - - /** Name of associated part. **/ - FString PartName; - - /** Name of group which was used for splitting, empty if there's none. **/ - FString SplitName; - - /** Name of the instancer material, if available. **/ - FString InstancerMaterialName; - - /** Name of attribute material, if available. **/ - FString InstancerAttributeMaterialName; - - /** Id of corresponding HAPI Asset. **/ - //HAPI_NodeId AssetId; - int AssetId; - - /** Id of corresponding HAPI Object. **/ - //HAPI_NodeId ObjectId; - int ObjectId; - - /** Id of corresponding HAPI Geo. **/ - //HAPI_NodeId GeoId; - int GeoId; - - /** Id of corresponding HAPI Part. **/ - //HAPI_PartId PartId; - int PartId; - - /** Id of a split. In most cases this will be 0. **/ - int32 SplitId; - - /** Path to the corresponding node */ - mutable FString NodePath; - - /** Flags used by geo part object. **/ - union - { - struct - { - /* Is set to true when referenced object is visible. This is typically used by instancers. **/ - uint32 bIsVisible : 1; - - /** Is set to true when referenced object is an instancer. **/ - uint32 bIsInstancer : 1; - - /** Is set to true when referenced object is a curve. **/ - uint32 bIsCurve : 1; - - /** Is set to true when referenced object is editable. **/ - uint32 bIsEditable : 1; - - /** Is set to true when geometry has changed. **/ - uint32 bHasGeoChanged : 1; - - /** Is set to true when referenced object is collidable. **/ - uint32 bIsCollidable : 1; - - /** Is set to true when referenced object is collidable and is renderable. **/ - uint32 bIsRenderCollidable : 1; - - /** Is set to true when referenced object has just been loaded. **/ - uint32 bIsLoaded : 1; - - /** Unused flags. **/ - uint32 bPlaceHolderFlags : 3; - - /** Is set to true when referenced object has been loaded during transaction. **/ - uint32 bIsTransacting : 1; - - /** Is set to true when referenced object has a custom name. **/ - uint32 bHasCustomName : 1; - - /** Is set to true when referenced object is a box. **/ - uint32 bIsBox : 1; - - /** Is set to true when referenced object is a sphere. **/ - uint32 bIsSphere : 1; - - /** Is set to true when instancer material is available. **/ - uint32 bInstancerMaterialAvailable : 1; - - /** Is set to true when referenced object is a volume. **/ - uint32 bIsVolume : 1; - - /** Is set to true when instancer attribute material is available. **/ - uint32 bInstancerAttributeMaterialAvailable : 1; - - /** Is set when referenced object contains packed primitive instancing */ - uint32 bIsPackedPrimitiveInstancer : 1; - - /** Is set to true when referenced object is a UCX collision geo. **/ - uint32 bIsUCXCollisionGeo : 1; - - /** Is set to true when referenced object is a rendered UCX collision geo. **/ - uint32 bIsSimpleCollisionGeo : 1; - - /** Is set to true when new collision geo has been generated **/ - uint32 bHasCollisionBeenAdded : 1; - - /** Is set to true when new sockets have been added **/ - uint32 bHasSocketBeenAdded : 1; - - /** unused flag space is zero initialized */ - uint32 UnusedFlagsSpace : 14; - }; - - uint32 HoudiniGeoPartObjectFlagsPacked; - }; - - /** Temporary variable holding serialization version. **/ - uint32 HoudiniGeoPartObjectVersion; -}; - -/** Function used by hashing containers to create a unique hash for this type of object. **/ -uint32 GetTypeHash(const FHoudiniGeoPartObject_V1 & HoudiniGeoPartObject); - -/** Serialization function. **/ -FArchive& operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject); - -/** Functor used to sort geo part objects. **/ -struct FHoudiniGeoPartObject_V1SortPredicate -{ - bool operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const; -}; - - -struct FHoudiniAssetInputOutlinerMesh_V1 -{ - /** Serialization. **/ - void Serialize(FArchive & Ar); - - /** Update the Actor pointer from the store Actor path/name **/ - bool TryToUpdateActorPtrFromActorPathName(UWorld* InWorld); - - /** Temporary variable holding serialization version. **/ - uint32 HoudiniAssetParameterVersion; - - /** Selected Actor. **/ - TWeakObjectPtr ActorPtr = nullptr; - - /** Selected Actor's path, used to find the actor back after loading. **/ - FString ActorPathName = TEXT("NONE"); - - /** Selected mesh's component, for reference. **/ - UStaticMeshComponent * StaticMeshComponent = nullptr; - - /** The selected mesh. **/ - UStaticMesh * StaticMesh = nullptr; - - /** Spline Component **/ - USplineComponent * SplineComponent = nullptr; - - /** Number of CVs used by the spline component, used to detect modification **/ - int32 NumberOfSplineControlPoints = -1; - - /** Transform of the UnrealSpline CVs, used to detect modification of the spline (Rotation/Scale) **/ - TArray SplineControlPointsTransform; - - /** Spline Length, used to detect modification of the spline.. **/ - float SplineLength = -1.0f; - - /** Spline resolution used to generate the asset, used to detect setting modification **/ - float SplineResolution = -1.0f; - - /** Actor transform used to see if the transfrom changed since last marshal into Houdini. **/ - FTransform ActorTransform; - - /** Component transform used to see if the transform has changed since last marshalling **/ - FTransform ComponentTransform; - - /** Mesh's input asset id. **/ - //HAPI_NodeId AssetId = -1; - int AssetId = -1; - - /** TranformType used to generate the asset **/ - int32 KeepWorldTransform = 2; - - /** Path Materials assigned on the SMC **/ - TArray MeshComponentsMaterials; - - /** If the world In is a ISM, index of this instance **/ - uint32 InstanceIndex = -1; -}; - -/** Serialization function. **/ -FArchive & operator<<(FArchive & Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh); - -/* -UCLASS(EditInlineNew, config = Engine) -class UHoudiniAsset_V1 -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; -}; -*/ - -UCLASS() -class UHoudiniAssetParameter : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer); - - void CopyLegacyParameterData(UHoudiniParameter* InNewParm); - - /** Name of this parameter. **/ - FString ParameterName; - - /** Label of this parameter. **/ - FString ParameterLabel; - - /** Node this parameter belongs to. **/ - int NodeId; - - /** Id of this parameter. **/ - int ParmId; - - /** Id of parent parameter, -1 if root is parent. **/ - int ParmParentId; - - /** Child index within its parent parameter. **/ - int32 ChildIndex; - - /** Tuple size - arrays. **/ - int32 TupleSize; - - /** Internal HAPI cached value index. **/ - int32 ValuesIndex; - - /** The multiparm instance index. **/ - int32 MultiparmInstanceIndex; - - /** The parameter's help, to be used as a tooltip **/ - FString ParameterHelp; - - /** Flags used by this parameter. **/ - union - { - struct - { - /** Is set to true if this parameter is spare, that is, created by Houdini Engine only. **/ - uint32 bIsSpare : 1; - - /** Is set to true if this parameter is disabled. **/ - uint32 bIsDisabled : 1; - - /** Is set to true if value of this parameter has been changed by user. **/ - uint32 bChanged : 1; - - /** Is set to true when parameter's slider (if it has one) is being dragged. Transient. **/ - uint32 bSliderDragged : 1; - - /** Is set to true if the parameter is a multiparm child parameter. **/ - uint32 bIsChildOfMultiparm : 1; - - /** Is set to true if this parameter is a Substance parameter. **/ - uint32 bIsSubstanceParameter : 1; - - /** Is set to true if this parameter is a multiparm **/ - uint32 bIsMultiparm : 1; - }; - - uint32 HoudiniAssetParameterFlagsPacked; - }; - - /** Temporary variable holding parameter serialization version. **/ - uint32 HoudiniAssetParameterVersion; -}; - -UCLASS() -class UHoudiniAssetParameterButton : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterChoice : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Choice values for this property. **/ - TArray StringChoiceValues; - - /** Choice labels for this property. **/ - TArray StringChoiceLabels; - - /** Value of this property. **/ - FString StringValue; - - /** Current value for this property. **/ - int32 CurrentValue; - - /** Is set to true when this choice list is a string choice list. **/ - bool bStringChoiceList; -}; - -UCLASS() -class UHoudiniAssetParameterColor : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Color for this property. **/ - FLinearColor Color; -}; - -UCLASS() -class UHoudiniAssetParameterFile : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< FString > Values; - - /** Filters of this property. **/ - FString Filters; - - /** Is the file parameter read-only? **/ - bool IsReadOnly; -}; - -UCLASS() -class UHoudiniAssetParameterFloat : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< float > Values; - - /** Min and Max values for this property. **/ - float ValueMin; - float ValueMax; - - /** Min and Max values for UI for this property. **/ - float ValueUIMin; - float ValueUIMax; - - /** Unit for this property **/ - FString ValueUnit; - - /** Do we have the noswap tag? **/ - bool NoSwap; -}; - -UCLASS() -class UHoudiniAssetParameterFolder : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterFolderList : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterInt : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< int32 > Values; - - /** Min and Max values for this property. **/ - int32 ValueMin; - int32 ValueMax; - - /** Min and Max values for UI for this property. **/ - int32 ValueUIMin; - int32 ValueUIMax; - - /** Unit for this property **/ - FString ValueUnit; -}; - -UCLASS() -class UHoudiniAssetParameterLabel : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterMultiparm : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Value of this property. **/ - int32 MultiparmValue; -}; - -UCLASS() -class UHoudiniAssetParameterRamp : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - void CopyLegacyParameterData(UHoudiniParameter* InNewParm); - - //! Curves which are being edited. - UCurveFloat * HoudiniAssetParameterRampCurveFloat; - UCurveLinearColor * HoudiniAssetParameterRampCurveColor; - - //! Set to true if this ramp is a float ramp. Otherwise is considered a color ramp. - bool bIsFloatRamp; -}; - -UCLASS() -class UHoudiniAssetParameterSeparator : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterString : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< FString > Values; -}; - -UCLASS() -class UHoudiniAssetParameterToggle : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< int32 > Values; -}; - -UCLASS() -class UHoudiniAssetComponentMaterials_V1 : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** Material assignments. **/ - TMap Assignments; - - /** Material replacements. **/ - TMap> Replacements; -}; - -UCLASS() -class UHoudiniHandleComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniHandleComponent* NewHC); - - //virtual UHoudiniHandleComponent* ConvertLegacyData(UObject* Outer); - - UHoudiniAssetParameterFloat* XFormParams[9]; - int32 XFormParamsTupleIndex[9]; - - UHoudiniAssetParameterChoice* RSTParm; - int32 RSTParmTupleIdx; - - UHoudiniAssetParameterChoice* RotOrderParm; - int32 RotOrderParmTupleIdx; -}; - -UCLASS() -class UHoudiniSplineComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniSplineComponent* ConvertLegacyData(UObject* Outer); - - bool UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline); - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** List of points composing this curve. **/ - TArray CurvePoints; - - /** List of refined points used for drawing. **/ - TArray CurveDisplayPoints; - - /** Type of this curve. **/ - // 0 Polygon 1 Nurbs 2 Bezier - uint8 CurveType; - - /** Method used for this curve. **/ - // 0 CVs, 1 Breakpoints, 2 Freehand - uint8 CurveMethod; - - /** Whether this spline is closed. **/ - bool bClosedCurve; -}; - -UCLASS() -class UHoudiniAssetInput : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - UHoudiniInput* ConvertLegacyInput(UObject* Outer); - - // Input type: - // 0 GeometryInput - // 1 AssetInput - // 2 CurveInput - // 3 LandscapeInput - // 4 WorldInput - // 5 SkeletonInput - uint8 ChoiceIndex; - - /** Value of choice option. **/ - FString ChoiceStringValue; - - /** Index of this input. **/ - int32 InputIndex; - - /** Objects used for geometry input. **/ - TArray InputObjects; - - /** Houdini spline component which is used for curve input. **/ - UHoudiniSplineComponent * InputCurve; - - /** Houdini asset component pointer of the input asset (actor). **/ - UHoudiniAssetComponent_V1 * InputAssetComponent; - - /** Landscape actor used for input. **/ - TSoftObjectPtr InputLandscapeProxy; - - /** List of selected meshes and actors from the World Outliner. **/ - TArray InputOutlinerMeshArray; - - /** Parameters used by a curve input asset. **/ - TMap InputCurveParameters; - - float UnrealSplineResolution; - - /** Array containing the transform corrections for the assets in a geometry input **/ - TArray InputTransforms; - - /** Transform used by the input landscape **/ - FTransform InputLandscapeTransform; - - /** Flags used by this input. **/ - union - { - struct - { - /** Is set to true when static mesh used for geometry input has changed. **/ - uint32 bStaticMeshChanged : 1; - - /** Is set to true when choice switches to curve mode. **/ - uint32 bSwitchedToCurve : 1; - - /** Is set to true if this parameter has been loaded. **/ - uint32 bLoadedParameter : 1; - - /** Is set to true if the asset input is actually connected inside Houdini. **/ - uint32 bInputAssetConnectedInHoudini : 1; - - /** Is set to true when landscape input is set to selection only. **/ - uint32 bLandscapeExportSelectionOnly : 1; - - /** Is set to true when landscape curves are to be exported. **/ - uint32 bLandscapeExportCurves : 1; - - /** Is set to true when the landscape is to be exported as a mesh, not just points. **/ - uint32 bLandscapeExportAsMesh : 1; - - /** Is set to true when materials are to be exported. **/ - uint32 bLandscapeExportMaterials : 1; - - /** Is set to true when lightmap information export is desired. **/ - uint32 bLandscapeExportLighting : 1; - - /** Is set to true when uvs should be exported in [0,1] space. **/ - uint32 bLandscapeExportNormalizedUVs : 1; - - /** Is set to true when uvs should be exported for each tile separately. **/ - uint32 bLandscapeExportTileUVs : 1; - - /** Is set to true when being used as an object-path parameter instead of an input */ - uint32 bIsObjectPathParameter : 1; - - /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value **/ - uint32 bKeepWorldTransform : 2; - - /** Is set to true when the landscape is to be exported as a heightfield **/ - uint32 bLandscapeExportAsHeightfield : 1; - - /** Is set to true when the automatic selection of landscape component is active **/ - uint32 bLandscapeAutoSelectComponent : 1; - - /** Indicates that the geometry must be packed before merging it into the input **/ - uint32 bPackBeforeMerge : 1; - - /** Indicates that all LODs in the input should be marshalled to Houdini **/ - uint32 bExportAllLODs : 1; - - /** Indicates that all sockets in the input should be marshalled to Houdini **/ - uint32 bExportSockets : 1; - - /** Indicates that the landscape input's source landscape should be updated instead of creating a new component **/ - uint32 bUpdateInputLandscape : 1; - }; - - uint32 HoudiniAssetInputFlagsPacked; - }; -}; - -UCLASS() -class UHoudiniAssetInstanceInput : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** List of fields created by this instance input. **/ - TArray InstanceInputFields; - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** Id of an object to instance. **/ - int ObjectToInstanceId; - -public: - /** Flags used by this input. **/ - union FHoudiniAssetInstanceInputFlags - { - struct - { - /** Set to true if this is an attribute instancer. **/ - uint32 bIsAttributeInstancer : 1; - - /** Set to true if this attribute instancer uses overrides. **/ - uint32 bAttributeInstancerOverride : 1; - - /** Set to true if this is a packed primitive instancer **/ - uint32 bIsPackedPrimitiveInstancer : 1; - - /** Set to true if this is a split mesh instancer */ - uint32 bIsSplitMeshInstancer : 1; - }; - - uint32 HoudiniAssetInstanceInputFlagsPacked; - }; - FHoudiniAssetInstanceInputFlags Flags; -}; - -UCLASS() -class UHoudiniAssetInstanceInputField : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** Original object used by the instancer. **/ - UObject* OriginalObject; - - /** Currently used Objects */ - TArray< UObject* > InstancedObjects; - - /** Used instanced actor component. **/ - TArray< USceneComponent * > InstancerComponents; - - /** Flags used by this input field. **/ - uint32 HoudiniAssetInstanceInputFieldFlagsPacked; - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** Rotation offset for instanced component. **/ - TArray< FRotator > RotationOffsets; - - /** Scale offset for instanced component. **/ - TArray< FVector > ScaleOffsets; - - /** Whether to scale linearly for all fields. **/ - TArray< bool > bScaleOffsetsLinearlyArray; - - /** Transforms, one for each instance. **/ - TArray< FTransform > InstancedTransforms; - - /** Assignment of Transforms to each variation **/ - TArray< TArray< FTransform > > VariationTransformsArray; - - /** Color overrides, one per instance **/ - TArray InstanceColorOverride; - - /** Per-variation color override assignments */ - TArray< TArray< FLinearColor > > VariationInstanceColorOverrideArray; -}; - -//UCLASS() -UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), - ShowCategories = (Mobility), editinlinenew) -class UHoudiniAssetComponent_V1 : public UPrimitiveComponent//, public IHoudiniCookHandler -{ - GENERATED_UCLASS_BODY() - -public: - /* - // IHoudiniCookHandler interface - virtual FString GetBakingBaseName(const struct FHoudiniGeoPartObject_V1& GeoPartObject) override { return FString(); }; - virtual void SetStaticMeshGenerationParameters(class UStaticMesh* StaticMesh) override {}; - virtual class UMaterialInterface * GetAssignmentMaterial(const FString& MaterialName) override { return nullptr; }; - virtual void ClearAssignmentMaterials() override {}; - virtual void AddAssignmentMaterial(const FString& MaterialName, class UMaterialInterface* MaterialInterface) override {}; - virtual class UMaterialInterface * GetReplacementMaterial(const struct FHoudiniGeoPartObject_V1& GeoPartObject, const FString& MaterialName) override { return nullptr; }; - */ - - /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Double Sided Geometry")) - uint32 bGeneratedDoubleSidedGeometry : 1; - - /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * GeneratedPhysMaterial; - - /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ - UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Collision Complexity")) - TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; - - /** Resolution of lightmap. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 GeneratedLightMapResolution; - - /** Bias multiplier for Light Propagation Volume lighting. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float GeneratedLpvBiasMultiplier; - - /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) - float GeneratedDistanceFieldResolutionScale; - - /** Custom walkable slope setting for generated mesh's body. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride GeneratedWalkableSlopeOverride; - - /** The light map coordinate index. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light map coordinate index")) - int32 GeneratedLightMapCoordinateIndex; - - /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; - - /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Streaming Distance Multiplier")) - float GeneratedStreamingDistanceMultiplier; - - /** Default settings when using this mesh for instanced foliage. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; - - /** Array of user data stored with the asset. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Asset User Data")) - TArray GeneratedAssetUserData; - - /** The output folder for baking actions */ - UPROPERTY() - FText BakeFolder; - - /** The temporary output folder for cooking actions */ - UPROPERTY() - FText TempCookFolder; - - virtual void Serialize(FArchive & Ar) override; - - /** Houdini Asset associated with this component. **/ - UHoudiniAsset* HoudiniAsset; - - /** Unique GUID created by component. **/ - FGuid ComponentGUID; - - /** Scale factor used for generated geometry of this component. **/ - float GeneratedGeometryScaleFactor; - - /** Scale factor used for geo transforms of this component. **/ - float TransformScaleFactor; - - /** Buffer to hold preset data for serialization purposes. Used only during serialization. **/ - TArray PresetBuffer; - - /** Buffer to hold default preset for reset purposes. **/ - TArray DefaultPresetBuffer; - - /** Parameters for this component's asset, indexed by parameter id. **/ - //TMap Parameters; - TMap Parameters; - - /** Parameters for this component's asset, indexed by name for fast look up. **/ - TMap ParameterByName; - - /** Inputs for this component's asset. **/ - TArray Inputs; - - /** Instance inputs for this component's asset **/ - TArray InstanceInputs; - - /** Material assignments. **/ - UHoudiniAssetComponentMaterials_V1 * HoudiniAssetComponentMaterials; - - /** Map of HAPI objects and corresponding static meshes. Also map of static meshes and corresponding components. **/ - TMap StaticMeshes; - TMap StaticMeshComponents; - - /** List of dependent downstream asset connections that have this asset as an asset input. **/ - TMap> DownstreamAssetConnections; - - /** Map of asset handle components. **/ - TMap HandleComponents; - - /** Map of curve / spline components. **/ - TMap SplineComponents; - - /** Map of Landscape / Heightfield components. **/ - TMap> LandscapeComponents; - - /** Overrides for baking names per part */ - TMap BakeNameOverrides; - - /** Import axis. **/ - uint8 ImportAxis; - - /** Flags used by Houdini component. **/ - union - { - struct - { - /** Enables cooking for this Houdini Asset. **/ - uint32 bEnableCooking : 1; - - /** Enables uploading of transformation changes back to Houdini Engine. **/ - uint32 bUploadTransformsToHoudiniEngine : 1; - - /** Enables cooking upon transformation changes. **/ - uint32 bTransformChangeTriggersCooks : 1; - - /** Is set to true when this component contains Houdini logo geometry. **/ - uint32 bContainsHoudiniLogoGeometry : 1; - - /** Is set to true when this component is native and false is when it is dynamic. **/ - uint32 bIsNativeComponent : 1; - - /** Is set to true when this component belongs to a preview actor. **/ - uint32 bIsPreviewComponent : 1; - - /** Is set to true if this component has been loaded. **/ - uint32 bLoadedComponent : 1; - - /** Unused **/ - uint32 bIsPlayModeActive_Unused : 1; - - /** unused flag **/ - uint32 bTimeCookInPlaymode_Unused : 1; - - /** Is set to true when Houdini materials are used. **/ - uint32 bUseHoudiniMaterials : 1; - - /** Is set to true when cooking this asset will trigger cooks of downstream connected assets. **/ - uint32 bCookingTriggersDownstreamCooks : 1; - - /** Is set to true after the asset is fully loaded and registered **/ - uint32 bFullyLoaded : 1; - }; - - uint32 HoudiniAssetComponentFlagsPacked; - }; -}; - -UCLASS() -class UHoudiniInstancedActorComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC); - - //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) - UObject* InstancedAsset; - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - TArray Instances; -}; - -UCLASS() -class UHoudiniMeshSplitInstancerComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC); - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - TArray Instances; - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - UMaterialInterface* OverrideMaterial; - - //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) - UStaticMesh* InstancedMesh; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniAsset.h" + +#include "Components/PrimitiveComponent.h" + +#include "Curves/CurveFloat.h" +#include "Curves/CurveLinearColor.h" + +#include "HoudiniCompatibilityHelpers.generated.h" + +class UStaticMesh; +class UStaticMeshComponent; +class USplineComponent; +class ALandscapeProxy; +class UMaterialInterface; +class UHoudiniInput; +class UHoudiniParameter; +class UHoudiniHandleComponent; +class UHoudiniSplineComponent; +class UHoudiniInstancedActorComponent; +class UHoudiniMeshSplitInstancerComponent; +class UFoliageType_InstancedStaticMesh; + + +struct FHoudiniGeoPartObject; + + +struct FHoudiniGeoPartObject_V1 +{ +public: + + void Serialize(FArchive & Ar); + + FHoudiniGeoPartObject ConvertLegacyData(); + + /** Return hash value for this object, used when using this object as a key inside hashing containers. **/ + uint32 GetTypeHash() const; + + /** Transform of this geo part object. **/ + FTransform TransformMatrix; + + /** Name of associated object. **/ + FString ObjectName; + + /** Name of associated part. **/ + FString PartName; + + /** Name of group which was used for splitting, empty if there's none. **/ + FString SplitName; + + /** Name of the instancer material, if available. **/ + FString InstancerMaterialName; + + /** Name of attribute material, if available. **/ + FString InstancerAttributeMaterialName; + + /** Id of corresponding HAPI Asset. **/ + //HAPI_NodeId AssetId; + int AssetId; + + /** Id of corresponding HAPI Object. **/ + //HAPI_NodeId ObjectId; + int ObjectId; + + /** Id of corresponding HAPI Geo. **/ + //HAPI_NodeId GeoId; + int GeoId; + + /** Id of corresponding HAPI Part. **/ + //HAPI_PartId PartId; + int PartId; + + /** Id of a split. In most cases this will be 0. **/ + int32 SplitId; + + /** Path to the corresponding node */ + mutable FString NodePath; + + /** Flags used by geo part object. **/ + union + { + struct + { + /* Is set to true when referenced object is visible. This is typically used by instancers. **/ + uint32 bIsVisible : 1; + + /** Is set to true when referenced object is an instancer. **/ + uint32 bIsInstancer : 1; + + /** Is set to true when referenced object is a curve. **/ + uint32 bIsCurve : 1; + + /** Is set to true when referenced object is editable. **/ + uint32 bIsEditable : 1; + + /** Is set to true when geometry has changed. **/ + uint32 bHasGeoChanged : 1; + + /** Is set to true when referenced object is collidable. **/ + uint32 bIsCollidable : 1; + + /** Is set to true when referenced object is collidable and is renderable. **/ + uint32 bIsRenderCollidable : 1; + + /** Is set to true when referenced object has just been loaded. **/ + uint32 bIsLoaded : 1; + + /** Unused flags. **/ + uint32 bPlaceHolderFlags : 3; + + /** Is set to true when referenced object has been loaded during transaction. **/ + uint32 bIsTransacting : 1; + + /** Is set to true when referenced object has a custom name. **/ + uint32 bHasCustomName : 1; + + /** Is set to true when referenced object is a box. **/ + uint32 bIsBox : 1; + + /** Is set to true when referenced object is a sphere. **/ + uint32 bIsSphere : 1; + + /** Is set to true when instancer material is available. **/ + uint32 bInstancerMaterialAvailable : 1; + + /** Is set to true when referenced object is a volume. **/ + uint32 bIsVolume : 1; + + /** Is set to true when instancer attribute material is available. **/ + uint32 bInstancerAttributeMaterialAvailable : 1; + + /** Is set when referenced object contains packed primitive instancing */ + uint32 bIsPackedPrimitiveInstancer : 1; + + /** Is set to true when referenced object is a UCX collision geo. **/ + uint32 bIsUCXCollisionGeo : 1; + + /** Is set to true when referenced object is a rendered UCX collision geo. **/ + uint32 bIsSimpleCollisionGeo : 1; + + /** Is set to true when new collision geo has been generated **/ + uint32 bHasCollisionBeenAdded : 1; + + /** Is set to true when new sockets have been added **/ + uint32 bHasSocketBeenAdded : 1; + + /** unused flag space is zero initialized */ + uint32 UnusedFlagsSpace : 14; + }; + + uint32 HoudiniGeoPartObjectFlagsPacked; + }; + + /** Temporary variable holding serialization version. **/ + uint32 HoudiniGeoPartObjectVersion; +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +uint32 GetTypeHash(const FHoudiniGeoPartObject_V1 & HoudiniGeoPartObject); + +/** Serialization function. **/ +FArchive& operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject); + +/** Functor used to sort geo part objects. **/ +struct FHoudiniGeoPartObject_V1SortPredicate +{ + bool operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const; +}; + + +struct FHoudiniAssetInputOutlinerMesh_V1 +{ + /** Serialization. **/ + void Serialize(FArchive & Ar); + + /** Update the Actor pointer from the store Actor path/name **/ + bool TryToUpdateActorPtrFromActorPathName(UWorld* InWorld); + + /** Temporary variable holding serialization version. **/ + uint32 HoudiniAssetParameterVersion; + + /** Selected Actor. **/ + TWeakObjectPtr ActorPtr = nullptr; + + /** Selected Actor's path, used to find the actor back after loading. **/ + FString ActorPathName = TEXT("NONE"); + + /** Selected mesh's component, for reference. **/ + UStaticMeshComponent * StaticMeshComponent = nullptr; + + /** The selected mesh. **/ + UStaticMesh * StaticMesh = nullptr; + + /** Spline Component **/ + USplineComponent * SplineComponent = nullptr; + + /** Number of CVs used by the spline component, used to detect modification **/ + int32 NumberOfSplineControlPoints = -1; + + /** Transform of the UnrealSpline CVs, used to detect modification of the spline (Rotation/Scale) **/ + TArray SplineControlPointsTransform; + + /** Spline Length, used to detect modification of the spline.. **/ + float SplineLength = -1.0f; + + /** Spline resolution used to generate the asset, used to detect setting modification **/ + float SplineResolution = -1.0f; + + /** Actor transform used to see if the transfrom changed since last marshal into Houdini. **/ + FTransform ActorTransform; + + /** Component transform used to see if the transform has changed since last marshalling **/ + FTransform ComponentTransform; + + /** Mesh's input asset id. **/ + //HAPI_NodeId AssetId = -1; + int AssetId = -1; + + /** TranformType used to generate the asset **/ + int32 KeepWorldTransform = 2; + + /** Path Materials assigned on the SMC **/ + TArray MeshComponentsMaterials; + + /** If the world In is a ISM, index of this instance **/ + uint32 InstanceIndex = -1; +}; + +/** Serialization function. **/ +FArchive & operator<<(FArchive & Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh); + +/* +UCLASS(EditInlineNew, config = Engine) +class UHoudiniAsset_V1 +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; +}; +*/ + +UCLASS() +class UHoudiniAssetParameter : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer); + + void CopyLegacyParameterData(UHoudiniParameter* InNewParm); + + /** Name of this parameter. **/ + FString ParameterName; + + /** Label of this parameter. **/ + FString ParameterLabel; + + /** Node this parameter belongs to. **/ + int NodeId; + + /** Id of this parameter. **/ + int ParmId; + + /** Id of parent parameter, -1 if root is parent. **/ + int ParmParentId; + + /** Child index within its parent parameter. **/ + int32 ChildIndex; + + /** Tuple size - arrays. **/ + int32 TupleSize; + + /** Internal HAPI cached value index. **/ + int32 ValuesIndex; + + /** The multiparm instance index. **/ + int32 MultiparmInstanceIndex; + + /** The parameter's help, to be used as a tooltip **/ + FString ParameterHelp; + + /** Flags used by this parameter. **/ + union + { + struct + { + /** Is set to true if this parameter is spare, that is, created by Houdini Engine only. **/ + uint32 bIsSpare : 1; + + /** Is set to true if this parameter is disabled. **/ + uint32 bIsDisabled : 1; + + /** Is set to true if value of this parameter has been changed by user. **/ + uint32 bChanged : 1; + + /** Is set to true when parameter's slider (if it has one) is being dragged. Transient. **/ + uint32 bSliderDragged : 1; + + /** Is set to true if the parameter is a multiparm child parameter. **/ + uint32 bIsChildOfMultiparm : 1; + + /** Is set to true if this parameter is a Substance parameter. **/ + uint32 bIsSubstanceParameter : 1; + + /** Is set to true if this parameter is a multiparm **/ + uint32 bIsMultiparm : 1; + }; + + uint32 HoudiniAssetParameterFlagsPacked; + }; + + /** Temporary variable holding parameter serialization version. **/ + uint32 HoudiniAssetParameterVersion; +}; + +UCLASS() +class UHoudiniAssetParameterButton : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterChoice : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Choice values for this property. **/ + TArray StringChoiceValues; + + /** Choice labels for this property. **/ + TArray StringChoiceLabels; + + /** Value of this property. **/ + FString StringValue; + + /** Current value for this property. **/ + int32 CurrentValue; + + /** Is set to true when this choice list is a string choice list. **/ + bool bStringChoiceList; +}; + +UCLASS() +class UHoudiniAssetParameterColor : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Color for this property. **/ + FLinearColor Color; +}; + +UCLASS() +class UHoudiniAssetParameterFile : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< FString > Values; + + /** Filters of this property. **/ + FString Filters; + + /** Is the file parameter read-only? **/ + bool IsReadOnly; +}; + +UCLASS() +class UHoudiniAssetParameterFloat : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< float > Values; + + /** Min and Max values for this property. **/ + float ValueMin; + float ValueMax; + + /** Min and Max values for UI for this property. **/ + float ValueUIMin; + float ValueUIMax; + + /** Unit for this property **/ + FString ValueUnit; + + /** Do we have the noswap tag? **/ + bool NoSwap; +}; + +UCLASS() +class UHoudiniAssetParameterFolder : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterFolderList : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterInt : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< int32 > Values; + + /** Min and Max values for this property. **/ + int32 ValueMin; + int32 ValueMax; + + /** Min and Max values for UI for this property. **/ + int32 ValueUIMin; + int32 ValueUIMax; + + /** Unit for this property **/ + FString ValueUnit; +}; + +UCLASS() +class UHoudiniAssetParameterLabel : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterMultiparm : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Value of this property. **/ + int32 MultiparmValue; +}; + +UCLASS() +class UHoudiniAssetParameterRamp : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + void CopyLegacyParameterData(UHoudiniParameter* InNewParm); + + //! Curves which are being edited. + UCurveFloat * HoudiniAssetParameterRampCurveFloat; + UCurveLinearColor * HoudiniAssetParameterRampCurveColor; + + //! Set to true if this ramp is a float ramp. Otherwise is considered a color ramp. + bool bIsFloatRamp; +}; + +UCLASS() +class UHoudiniAssetParameterSeparator : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterString : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< FString > Values; +}; + +UCLASS() +class UHoudiniAssetParameterToggle : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< int32 > Values; +}; + +UCLASS() +class UHoudiniAssetComponentMaterials_V1 : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** Material assignments. **/ + TMap Assignments; + + /** Material replacements. **/ + TMap> Replacements; +}; + +UCLASS() +class UHoudiniHandleComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniHandleComponent* NewHC); + + //virtual UHoudiniHandleComponent* ConvertLegacyData(UObject* Outer); + + UHoudiniAssetParameterFloat* XFormParams[9]; + int32 XFormParamsTupleIndex[9]; + + UHoudiniAssetParameterChoice* RSTParm; + int32 RSTParmTupleIdx; + + UHoudiniAssetParameterChoice* RotOrderParm; + int32 RotOrderParmTupleIdx; +}; + +UCLASS() +class UHoudiniSplineComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniSplineComponent* ConvertLegacyData(UObject* Outer); + + bool UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline); + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** List of points composing this curve. **/ + TArray CurvePoints; + + /** List of refined points used for drawing. **/ + TArray CurveDisplayPoints; + + /** Type of this curve. **/ + // 0 Polygon 1 Nurbs 2 Bezier + uint8 CurveType; + + /** Method used for this curve. **/ + // 0 CVs, 1 Breakpoints, 2 Freehand + uint8 CurveMethod; + + /** Whether this spline is closed. **/ + bool bClosedCurve; +}; + +UCLASS() +class UHoudiniAssetInput : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + UHoudiniInput* ConvertLegacyInput(UObject* Outer); + + // Input type: + // 0 GeometryInput + // 1 AssetInput + // 2 CurveInput + // 3 LandscapeInput + // 4 WorldInput + // 5 SkeletonInput + uint8 ChoiceIndex; + + /** Value of choice option. **/ + FString ChoiceStringValue; + + /** Index of this input. **/ + int32 InputIndex; + + /** Objects used for geometry input. **/ + TArray InputObjects; + + /** Houdini spline component which is used for curve input. **/ + UHoudiniSplineComponent * InputCurve; + + /** Houdini asset component pointer of the input asset (actor). **/ + UHoudiniAssetComponent_V1 * InputAssetComponent; + + /** Landscape actor used for input. **/ + TSoftObjectPtr InputLandscapeProxy; + + /** List of selected meshes and actors from the World Outliner. **/ + TArray InputOutlinerMeshArray; + + /** Parameters used by a curve input asset. **/ + TMap InputCurveParameters; + + float UnrealSplineResolution; + + /** Array containing the transform corrections for the assets in a geometry input **/ + TArray InputTransforms; + + /** Transform used by the input landscape **/ + FTransform InputLandscapeTransform; + + /** Flags used by this input. **/ + union + { + struct + { + /** Is set to true when static mesh used for geometry input has changed. **/ + uint32 bStaticMeshChanged : 1; + + /** Is set to true when choice switches to curve mode. **/ + uint32 bSwitchedToCurve : 1; + + /** Is set to true if this parameter has been loaded. **/ + uint32 bLoadedParameter : 1; + + /** Is set to true if the asset input is actually connected inside Houdini. **/ + uint32 bInputAssetConnectedInHoudini : 1; + + /** Is set to true when landscape input is set to selection only. **/ + uint32 bLandscapeExportSelectionOnly : 1; + + /** Is set to true when landscape curves are to be exported. **/ + uint32 bLandscapeExportCurves : 1; + + /** Is set to true when the landscape is to be exported as a mesh, not just points. **/ + uint32 bLandscapeExportAsMesh : 1; + + /** Is set to true when materials are to be exported. **/ + uint32 bLandscapeExportMaterials : 1; + + /** Is set to true when lightmap information export is desired. **/ + uint32 bLandscapeExportLighting : 1; + + /** Is set to true when uvs should be exported in [0,1] space. **/ + uint32 bLandscapeExportNormalizedUVs : 1; + + /** Is set to true when uvs should be exported for each tile separately. **/ + uint32 bLandscapeExportTileUVs : 1; + + /** Is set to true when being used as an object-path parameter instead of an input */ + uint32 bIsObjectPathParameter : 1; + + /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value **/ + uint32 bKeepWorldTransform : 2; + + /** Is set to true when the landscape is to be exported as a heightfield **/ + uint32 bLandscapeExportAsHeightfield : 1; + + /** Is set to true when the automatic selection of landscape component is active **/ + uint32 bLandscapeAutoSelectComponent : 1; + + /** Indicates that the geometry must be packed before merging it into the input **/ + uint32 bPackBeforeMerge : 1; + + /** Indicates that all LODs in the input should be marshalled to Houdini **/ + uint32 bExportAllLODs : 1; + + /** Indicates that all sockets in the input should be marshalled to Houdini **/ + uint32 bExportSockets : 1; + + /** Indicates that the landscape input's source landscape should be updated instead of creating a new component **/ + uint32 bUpdateInputLandscape : 1; + }; + + uint32 HoudiniAssetInputFlagsPacked; + }; +}; + +UCLASS() +class UHoudiniAssetInstanceInput : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** List of fields created by this instance input. **/ + TArray InstanceInputFields; + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** Id of an object to instance. **/ + int ObjectToInstanceId; + +public: + /** Flags used by this input. **/ + union FHoudiniAssetInstanceInputFlags + { + struct + { + /** Set to true if this is an attribute instancer. **/ + uint32 bIsAttributeInstancer : 1; + + /** Set to true if this attribute instancer uses overrides. **/ + uint32 bAttributeInstancerOverride : 1; + + /** Set to true if this is a packed primitive instancer **/ + uint32 bIsPackedPrimitiveInstancer : 1; + + /** Set to true if this is a split mesh instancer */ + uint32 bIsSplitMeshInstancer : 1; + }; + + uint32 HoudiniAssetInstanceInputFlagsPacked; + }; + FHoudiniAssetInstanceInputFlags Flags; +}; + +UCLASS() +class UHoudiniAssetInstanceInputField : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** Original object used by the instancer. **/ + UObject* OriginalObject; + + /** Currently used Objects */ + TArray< UObject* > InstancedObjects; + + /** Used instanced actor component. **/ + TArray< USceneComponent * > InstancerComponents; + + /** Flags used by this input field. **/ + uint32 HoudiniAssetInstanceInputFieldFlagsPacked; + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** Rotation offset for instanced component. **/ + TArray< FRotator > RotationOffsets; + + /** Scale offset for instanced component. **/ + TArray< FVector > ScaleOffsets; + + /** Whether to scale linearly for all fields. **/ + TArray< bool > bScaleOffsetsLinearlyArray; + + /** Transforms, one for each instance. **/ + TArray< FTransform > InstancedTransforms; + + /** Assignment of Transforms to each variation **/ + TArray< TArray< FTransform > > VariationTransformsArray; + + /** Color overrides, one per instance **/ + TArray InstanceColorOverride; + + /** Per-variation color override assignments */ + TArray< TArray< FLinearColor > > VariationInstanceColorOverrideArray; +}; + +//UCLASS() +UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), + ShowCategories = (Mobility), editinlinenew) +class UHoudiniAssetComponent_V1 : public UPrimitiveComponent//, public IHoudiniCookHandler +{ + GENERATED_UCLASS_BODY() + +public: + /* + // IHoudiniCookHandler interface + virtual FString GetBakingBaseName(const struct FHoudiniGeoPartObject_V1& GeoPartObject) override { return FString(); }; + virtual void SetStaticMeshGenerationParameters(class UStaticMesh* StaticMesh) override {}; + virtual class UMaterialInterface * GetAssignmentMaterial(const FString& MaterialName) override { return nullptr; }; + virtual void ClearAssignmentMaterials() override {}; + virtual void AddAssignmentMaterial(const FString& MaterialName, class UMaterialInterface* MaterialInterface) override {}; + virtual class UMaterialInterface * GetReplacementMaterial(const struct FHoudiniGeoPartObject_V1& GeoPartObject, const FString& MaterialName) override { return nullptr; }; + */ + + /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Double Sided Geometry")) + uint32 bGeneratedDoubleSidedGeometry : 1; + + /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * GeneratedPhysMaterial; + + /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ + UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Collision Complexity")) + TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; + + /** Resolution of lightmap. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 GeneratedLightMapResolution; + + /** Bias multiplier for Light Propagation Volume lighting. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) + float GeneratedLpvBiasMultiplier; + + /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /** Custom walkable slope setting for generated mesh's body. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride GeneratedWalkableSlopeOverride; + + /** The light map coordinate index. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Light map coordinate index")) + int32 GeneratedLightMapCoordinateIndex; + + /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; + + /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Streaming Distance Multiplier")) + float GeneratedStreamingDistanceMultiplier; + + /** Default settings when using this mesh for instanced foliage. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; + + /** Array of user data stored with the asset. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Asset User Data")) + TArray GeneratedAssetUserData; + + /** The output folder for baking actions */ + UPROPERTY() + FText BakeFolder; + + /** The temporary output folder for cooking actions */ + UPROPERTY() + FText TempCookFolder; + + virtual void Serialize(FArchive & Ar) override; + + /** Houdini Asset associated with this component. **/ + UHoudiniAsset* HoudiniAsset; + + /** Unique GUID created by component. **/ + FGuid ComponentGUID; + + /** Scale factor used for generated geometry of this component. **/ + float GeneratedGeometryScaleFactor; + + /** Scale factor used for geo transforms of this component. **/ + float TransformScaleFactor; + + /** Buffer to hold preset data for serialization purposes. Used only during serialization. **/ + TArray PresetBuffer; + + /** Buffer to hold default preset for reset purposes. **/ + TArray DefaultPresetBuffer; + + /** Parameters for this component's asset, indexed by parameter id. **/ + //TMap Parameters; + TMap Parameters; + + /** Parameters for this component's asset, indexed by name for fast look up. **/ + TMap ParameterByName; + + /** Inputs for this component's asset. **/ + TArray Inputs; + + /** Instance inputs for this component's asset **/ + TArray InstanceInputs; + + /** Material assignments. **/ + UHoudiniAssetComponentMaterials_V1 * HoudiniAssetComponentMaterials; + + /** Map of HAPI objects and corresponding static meshes. Also map of static meshes and corresponding components. **/ + TMap StaticMeshes; + TMap StaticMeshComponents; + + /** List of dependent downstream asset connections that have this asset as an asset input. **/ + TMap> DownstreamAssetConnections; + + /** Map of asset handle components. **/ + TMap HandleComponents; + + /** Map of curve / spline components. **/ + TMap SplineComponents; + + /** Map of Landscape / Heightfield components. **/ + TMap> LandscapeComponents; + + /** Overrides for baking names per part */ + TMap BakeNameOverrides; + + /** Import axis. **/ + uint8 ImportAxis; + + /** Flags used by Houdini component. **/ + union + { + struct + { + /** Enables cooking for this Houdini Asset. **/ + uint32 bEnableCooking : 1; + + /** Enables uploading of transformation changes back to Houdini Engine. **/ + uint32 bUploadTransformsToHoudiniEngine : 1; + + /** Enables cooking upon transformation changes. **/ + uint32 bTransformChangeTriggersCooks : 1; + + /** Is set to true when this component contains Houdini logo geometry. **/ + uint32 bContainsHoudiniLogoGeometry : 1; + + /** Is set to true when this component is native and false is when it is dynamic. **/ + uint32 bIsNativeComponent : 1; + + /** Is set to true when this component belongs to a preview actor. **/ + uint32 bIsPreviewComponent : 1; + + /** Is set to true if this component has been loaded. **/ + uint32 bLoadedComponent : 1; + + /** Unused **/ + uint32 bIsPlayModeActive_Unused : 1; + + /** unused flag **/ + uint32 bTimeCookInPlaymode_Unused : 1; + + /** Is set to true when Houdini materials are used. **/ + uint32 bUseHoudiniMaterials : 1; + + /** Is set to true when cooking this asset will trigger cooks of downstream connected assets. **/ + uint32 bCookingTriggersDownstreamCooks : 1; + + /** Is set to true after the asset is fully loaded and registered **/ + uint32 bFullyLoaded : 1; + }; + + uint32 HoudiniAssetComponentFlagsPacked; + }; +}; + +UCLASS() +class UHoudiniInstancedActorComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC); + + //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) + UObject* InstancedAsset; + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + TArray Instances; +}; + +UCLASS() +class UHoudiniMeshSplitInstancerComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC); + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + TArray Instances; + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + UMaterialInterface* OverrideMaterial; + + //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) + UStaticMesh* InstancedMesh; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp index 3e0e48ec4..272172316 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp @@ -1,32 +1,32 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineCopyPropertiesInterface.h" - -void IHoudiniEngineCopyPropertiesInterface::CopyPropertiesFrom(UObject* FromObject) -{ - -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineCopyPropertiesInterface.h" + +void IHoudiniEngineCopyPropertiesInterface::CopyPropertiesFrom(UObject* FromObject) +{ + +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h index 71fd0b034..b2d51a8dc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Engine/Engine.h" -#include "UObject/ObjectMacros.h" -#include "UObject/Interface.h" -#include "HoudiniEngineCopyPropertiesInterface.generated.h" - - -UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) -class UHoudiniEngineCopyPropertiesInterface : public UInterface -{ - GENERATED_BODY() -}; - -class HOUDINIENGINERUNTIME_API IHoudiniEngineCopyPropertiesInterface -{ - GENERATED_BODY() - -public: - virtual void CopyPropertiesFrom(UObject* FromObject); -}; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Engine/Engine.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Interface.h" +#include "HoudiniEngineCopyPropertiesInterface.generated.h" + + +UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) +class UHoudiniEngineCopyPropertiesInterface : public UInterface +{ + GENERATED_BODY() +}; + +class HOUDINIENGINERUNTIME_API IHoudiniEngineCopyPropertiesInterface +{ + GENERATED_BODY() + +public: + virtual void CopyPropertiesFrom(UObject* FromObject); +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp index 61799b71e..b4170bf63 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp @@ -1,323 +1,323 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" - -#include "HoudiniAssetComponent.h" - -#include "Modules/ModuleManager.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FHoudiniEngineRuntime, HoudiniEngineRuntime); -DEFINE_LOG_CATEGORY(LogHoudiniEngineRuntime); - -FHoudiniEngineRuntime * -FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; - - -FHoudiniEngineRuntime & -FHoudiniEngineRuntime::Get() -{ - return *HoudiniEngineRuntimeInstance; -} - - -bool -FHoudiniEngineRuntime::IsInitialized() -{ - return FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance != nullptr; -} - - -FHoudiniEngineRuntime::FHoudiniEngineRuntime() -{ -} - - -void FHoudiniEngineRuntime::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module - // Store the instance. - FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = this; -} - - -void FHoudiniEngineRuntime::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. - FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; -} - - -int32 -FHoudiniEngineRuntime::GetRegisteredHoudiniComponentCount() -{ - if (!IsInitialized()) - return 0; - - FScopeLock ScopeLock(&CriticalSection); - return RegisteredHoudiniComponents.Num(); -} - - -UHoudiniAssetComponent* -FHoudiniEngineRuntime::GetRegisteredHoudiniComponentAt(const int32& Index) -{ - if (!IsInitialized()) - return nullptr; - - FScopeLock ScopeLock(&CriticalSection); - - if (!RegisteredHoudiniComponents.IsValidIndex(Index)) - return nullptr; - - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Index]; - if (!Ptr.IsValid()) - return nullptr; - - if (Ptr.IsStale()) - return nullptr; - - return Ptr.Get(); -} - - -void -FHoudiniEngineRuntime::CleanUpRegisteredHoudiniComponents() -{ - // Remove Stale and invalid components - FScopeLock ScopeLock(&CriticalSection); - for (int Idx = RegisteredHoudiniComponents.Num() - 1; Idx >= 0; Idx--) - { - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Idx]; - if ( !Ptr.IsValid() || Ptr.IsStale() ) - { - UnRegisterHoudiniComponent(Idx); - continue; - } - - UHoudiniAssetComponent* CurrentHAC = Ptr.Get(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) - { - UnRegisterHoudiniComponent(Idx); - continue; - } - } -} - - -bool -FHoudiniEngineRuntime::IsComponentRegistered(UHoudiniAssetComponent* HAC) const -{ - // No need for duplicates - if (HAC && RegisteredHoudiniComponents.Find(HAC) != INDEX_NONE) - return true; - - return false; -} - - -void -FHoudiniEngineRuntime::RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype) -{ - if (!FHoudiniEngineRuntime::IsInitialized()) - return; - - if (!HAC || HAC->IsPendingKill()) - return; - - // RF_Transient indicates a temporary/preview object - // No need to instantiate/cook those in Houdini - // RF_ArchetypeObject is the template for blueprinted HDA, so we need to be able to register those. - if (HAC->HasAnyFlags(RF_Transient) || (HAC->HasAnyFlags(RF_ArchetypeObject) && !bAllowArchetype) || HAC->HasAnyFlags(RF_ClassDefaultObject)) - return; - - // No need for duplicates - if (IsComponentRegistered(HAC)) - return; - - HOUDINI_BP_MESSAGE(TEXT("[FHoudiniEngineRuntime::RegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); - - // Before adding, clean up the all ready registered - CleanUpRegisteredHoudiniComponents(); - - // Add the new component - { - FScopeLock ScopeLock(&CriticalSection); - RegisteredHoudiniComponents.Add(HAC); - } - - HAC->NotifyHoudiniRegisterCompleted(); -} - - -void -FHoudiniEngineRuntime::MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent) -{ - if (InNodeId >= 0) - { - // FDebug::DumpStackTraceToLog(); - - NodeIdsPendingDelete.AddUnique(InNodeId); - - if (bDeleteParent) - { - NodeIdsParentPendingDelete.AddUnique(InNodeId); - } - } -} - - -void -FHoudiniEngineRuntime::UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC) -{ - if (!IsInitialized()) - return; - - if (!HAC || HAC->IsPendingKill()) - return; - - // Calling GetPathName here may lead to some crashes due to invalid outers... - //HOUDINI_LOG_DISPLAY(TEXT("[FHoudiniEngineRuntime::UnRegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); - - FScopeLock ScopeLock(&CriticalSection); - - int32 FoundIdx = RegisteredHoudiniComponents.Find(HAC); - if (!RegisteredHoudiniComponents.IsValidIndex(FoundIdx)) - return; - HAC->NotifyHoudiniPreUnregister(); - UnRegisterHoudiniComponent(FoundIdx); - HAC->NotifyHoudiniPostUnregister(); -} - - -void -FHoudiniEngineRuntime::UnRegisterHoudiniComponent(const int32& ValidIndex) -{ - if (!IsInitialized()) - return; - - FScopeLock ScopeLock(&CriticalSection); - - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[ValidIndex]; - if (Ptr.IsValid(true, false)) - { - UHoudiniAssetComponent* HAC = Ptr.Get(); - if (HAC && HAC->CanDeleteHoudiniNodes()) - { - MarkNodeIdAsPendingDelete(HAC->GetAssetId(), true); - } - } - - RegisteredHoudiniComponents.RemoveAt(ValidIndex); -} - - -int32 -FHoudiniEngineRuntime::GetNodeIdsPendingDeleteCount() -{ - if (!IsInitialized()) - return 0; - - FScopeLock ScopeLock(&CriticalSection); - - return NodeIdsPendingDelete.Num(); -} - - -int32 -FHoudiniEngineRuntime::GetNodeIdsPendingDeleteAt(const int32& Index) -{ - if (!IsInitialized()) - return -1; - - FScopeLock ScopeLock(&CriticalSection); - - if (!NodeIdsPendingDelete.IsValidIndex(Index)) - return -1; - - return NodeIdsPendingDelete[Index]; -} - - -void -FHoudiniEngineRuntime::RemoveNodeIdPendingDeleteAt(const int32& Index) -{ - if (!IsInitialized()) - return; - - FScopeLock ScopeLock(&CriticalSection); - if (!NodeIdsPendingDelete.IsValidIndex(Index)) - return; - - NodeIdsPendingDelete.RemoveAt(Index); -} - - -bool -FHoudiniEngineRuntime::IsParentNodePendingDelete(const int32& NodeId) -{ - return NodeIdsParentPendingDelete.Contains(NodeId); -} - - -void -FHoudiniEngineRuntime::RemoveParentNodePendingDelete(const int32& NodeId) -{ - if (NodeIdsParentPendingDelete.Contains(NodeId)) - NodeIdsParentPendingDelete.Remove(NodeId); -} - - -FString -FHoudiniEngineRuntime::GetDefaultTemporaryCookFolder() const -{ - // Get Runtime settings to get the Temp Cook Folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (!HoudiniRuntimeSettings) - return HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - - return HoudiniRuntimeSettings->DefaultTemporaryCookFolder; -} - - -FString -FHoudiniEngineRuntime::GetDefaultBakeFolder() const -{ - // Get Runtime settings to get the default bake Folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (!HoudiniRuntimeSettings) - return HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - return HoudiniRuntimeSettings->DefaultBakeFolder; -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" + +#include "HoudiniAssetComponent.h" + +#include "Modules/ModuleManager.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHoudiniEngineRuntime, HoudiniEngineRuntime); +DEFINE_LOG_CATEGORY(LogHoudiniEngineRuntime); + +FHoudiniEngineRuntime * +FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; + + +FHoudiniEngineRuntime & +FHoudiniEngineRuntime::Get() +{ + return *HoudiniEngineRuntimeInstance; +} + + +bool +FHoudiniEngineRuntime::IsInitialized() +{ + return FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance != nullptr; +} + + +FHoudiniEngineRuntime::FHoudiniEngineRuntime() +{ +} + + +void FHoudiniEngineRuntime::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + // Store the instance. + FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = this; +} + + +void FHoudiniEngineRuntime::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. + FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; +} + + +int32 +FHoudiniEngineRuntime::GetRegisteredHoudiniComponentCount() +{ + if (!IsInitialized()) + return 0; + + FScopeLock ScopeLock(&CriticalSection); + return RegisteredHoudiniComponents.Num(); +} + + +UHoudiniAssetComponent* +FHoudiniEngineRuntime::GetRegisteredHoudiniComponentAt(const int32& Index) +{ + if (!IsInitialized()) + return nullptr; + + FScopeLock ScopeLock(&CriticalSection); + + if (!RegisteredHoudiniComponents.IsValidIndex(Index)) + return nullptr; + + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Index]; + if (!Ptr.IsValid()) + return nullptr; + + if (Ptr.IsStale()) + return nullptr; + + return Ptr.Get(); +} + + +void +FHoudiniEngineRuntime::CleanUpRegisteredHoudiniComponents() +{ + // Remove Stale and invalid components + FScopeLock ScopeLock(&CriticalSection); + for (int Idx = RegisteredHoudiniComponents.Num() - 1; Idx >= 0; Idx--) + { + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Idx]; + if ( !Ptr.IsValid() || Ptr.IsStale() ) + { + UnRegisterHoudiniComponent(Idx); + continue; + } + + UHoudiniAssetComponent* CurrentHAC = Ptr.Get(); + if (!CurrentHAC || CurrentHAC->IsPendingKill()) + { + UnRegisterHoudiniComponent(Idx); + continue; + } + } +} + + +bool +FHoudiniEngineRuntime::IsComponentRegistered(UHoudiniAssetComponent* HAC) const +{ + // No need for duplicates + if (HAC && RegisteredHoudiniComponents.Find(HAC) != INDEX_NONE) + return true; + + return false; +} + + +void +FHoudiniEngineRuntime::RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype) +{ + if (!FHoudiniEngineRuntime::IsInitialized()) + return; + + if (!HAC || HAC->IsPendingKill()) + return; + + // RF_Transient indicates a temporary/preview object + // No need to instantiate/cook those in Houdini + // RF_ArchetypeObject is the template for blueprinted HDA, so we need to be able to register those. + if (HAC->HasAnyFlags(RF_Transient) || (HAC->HasAnyFlags(RF_ArchetypeObject) && !bAllowArchetype) || HAC->HasAnyFlags(RF_ClassDefaultObject)) + return; + + // No need for duplicates + if (IsComponentRegistered(HAC)) + return; + + HOUDINI_BP_MESSAGE(TEXT("[FHoudiniEngineRuntime::RegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); + + // Before adding, clean up the all ready registered + CleanUpRegisteredHoudiniComponents(); + + // Add the new component + { + FScopeLock ScopeLock(&CriticalSection); + RegisteredHoudiniComponents.Add(HAC); + } + + HAC->NotifyHoudiniRegisterCompleted(); +} + + +void +FHoudiniEngineRuntime::MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent) +{ + if (InNodeId >= 0) + { + // FDebug::DumpStackTraceToLog(); + + NodeIdsPendingDelete.AddUnique(InNodeId); + + if (bDeleteParent) + { + NodeIdsParentPendingDelete.AddUnique(InNodeId); + } + } +} + + +void +FHoudiniEngineRuntime::UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC) +{ + if (!IsInitialized()) + return; + + if (!HAC || HAC->IsPendingKill()) + return; + + // Calling GetPathName here may lead to some crashes due to invalid outers... + //HOUDINI_LOG_DISPLAY(TEXT("[FHoudiniEngineRuntime::UnRegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); + + FScopeLock ScopeLock(&CriticalSection); + + int32 FoundIdx = RegisteredHoudiniComponents.Find(HAC); + if (!RegisteredHoudiniComponents.IsValidIndex(FoundIdx)) + return; + HAC->NotifyHoudiniPreUnregister(); + UnRegisterHoudiniComponent(FoundIdx); + HAC->NotifyHoudiniPostUnregister(); +} + + +void +FHoudiniEngineRuntime::UnRegisterHoudiniComponent(const int32& ValidIndex) +{ + if (!IsInitialized()) + return; + + FScopeLock ScopeLock(&CriticalSection); + + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[ValidIndex]; + if (Ptr.IsValid(true, false)) + { + UHoudiniAssetComponent* HAC = Ptr.Get(); + if (HAC && HAC->CanDeleteHoudiniNodes()) + { + MarkNodeIdAsPendingDelete(HAC->GetAssetId(), true); + } + } + + RegisteredHoudiniComponents.RemoveAt(ValidIndex); +} + + +int32 +FHoudiniEngineRuntime::GetNodeIdsPendingDeleteCount() +{ + if (!IsInitialized()) + return 0; + + FScopeLock ScopeLock(&CriticalSection); + + return NodeIdsPendingDelete.Num(); +} + + +int32 +FHoudiniEngineRuntime::GetNodeIdsPendingDeleteAt(const int32& Index) +{ + if (!IsInitialized()) + return -1; + + FScopeLock ScopeLock(&CriticalSection); + + if (!NodeIdsPendingDelete.IsValidIndex(Index)) + return -1; + + return NodeIdsPendingDelete[Index]; +} + + +void +FHoudiniEngineRuntime::RemoveNodeIdPendingDeleteAt(const int32& Index) +{ + if (!IsInitialized()) + return; + + FScopeLock ScopeLock(&CriticalSection); + if (!NodeIdsPendingDelete.IsValidIndex(Index)) + return; + + NodeIdsPendingDelete.RemoveAt(Index); +} + + +bool +FHoudiniEngineRuntime::IsParentNodePendingDelete(const int32& NodeId) +{ + return NodeIdsParentPendingDelete.Contains(NodeId); +} + + +void +FHoudiniEngineRuntime::RemoveParentNodePendingDelete(const int32& NodeId) +{ + if (NodeIdsParentPendingDelete.Contains(NodeId)) + NodeIdsParentPendingDelete.Remove(NodeId); +} + + +FString +FHoudiniEngineRuntime::GetDefaultTemporaryCookFolder() const +{ + // Get Runtime settings to get the Temp Cook Folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (!HoudiniRuntimeSettings) + return HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + + return HoudiniRuntimeSettings->DefaultTemporaryCookFolder; +} + + +FString +FHoudiniEngineRuntime::GetDefaultBakeFolder() const +{ + // Get Runtime settings to get the default bake Folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (!HoudiniRuntimeSettings) + return HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + return HoudiniRuntimeSettings->DefaultBakeFolder; +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h index fd29f49f3..d1ebc3c95 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h @@ -1,107 +1,107 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" -#include "HoudiniPDGAssetLink.h" - -#include "Modules/ModuleInterface.h" -#include "Misc/ScopeLock.h" -#include "UObject/WeakObjectPtrTemplates.h" - -class HOUDINIENGINERUNTIME_API FHoudiniEngineRuntime : public IModuleInterface -{ - public: - FHoudiniEngineRuntime(); - - // - // IModuleInterface methods. - // - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // Return singleton instance of Houdini Engine Runtime, used internally. - static FHoudiniEngineRuntime & Get(); - - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // - // Houdini Asset Component registry - // - // Ensure that the registered components are all still valid - void CleanUpRegisteredHoudiniComponents(); - - void RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype=false); - - void UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC); - void UnRegisterHoudiniComponent(const int32& ValidIdx); - - bool IsComponentRegistered(UHoudiniAssetComponent* HAC) const; - int32 GetRegisteredHoudiniComponentCount(); - UHoudiniAssetComponent* GetRegisteredHoudiniComponentAt(const int32& Index); - - virtual TArray>* GetRegisteredHoudiniComponents() { return &RegisteredHoudiniComponents; }; - - // - // Node deletion - // - void MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent = false); - - int32 GetNodeIdsPendingDeleteCount(); - int32 GetNodeIdsPendingDeleteAt(const int32& Index); - void RemoveNodeIdPendingDeleteAt(const int32& Index); - - bool IsParentNodePendingDelete(const int32& NodeId); - - void RemoveParentNodePendingDelete(const int32& NodeId); - - // - // - // - - // Returns the folder to be used for temporary cook content - FString GetDefaultTemporaryCookFolder() const; - - // Returns the defualt folder used for baking - FString GetDefaultBakeFolder() const; - - private: - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // Singleton instance. - static FHoudiniEngineRuntime * HoudiniEngineRuntimeInstance; - - // - TArray> RegisteredHoudiniComponents; - - TArray NodeIdsPendingDelete; - - TArray NodeIdsParentPendingDelete; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" +#include "HoudiniPDGAssetLink.h" + +#include "Modules/ModuleInterface.h" +#include "Misc/ScopeLock.h" +#include "UObject/WeakObjectPtrTemplates.h" + +class HOUDINIENGINERUNTIME_API FHoudiniEngineRuntime : public IModuleInterface +{ + public: + FHoudiniEngineRuntime(); + + // + // IModuleInterface methods. + // + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // Return singleton instance of Houdini Engine Runtime, used internally. + static FHoudiniEngineRuntime & Get(); + + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // + // Houdini Asset Component registry + // + // Ensure that the registered components are all still valid + void CleanUpRegisteredHoudiniComponents(); + + void RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype=false); + + void UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC); + void UnRegisterHoudiniComponent(const int32& ValidIdx); + + bool IsComponentRegistered(UHoudiniAssetComponent* HAC) const; + int32 GetRegisteredHoudiniComponentCount(); + UHoudiniAssetComponent* GetRegisteredHoudiniComponentAt(const int32& Index); + + virtual TArray>* GetRegisteredHoudiniComponents() { return &RegisteredHoudiniComponents; }; + + // + // Node deletion + // + void MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent = false); + + int32 GetNodeIdsPendingDeleteCount(); + int32 GetNodeIdsPendingDeleteAt(const int32& Index); + void RemoveNodeIdPendingDeleteAt(const int32& Index); + + bool IsParentNodePendingDelete(const int32& NodeId); + + void RemoveParentNodePendingDelete(const int32& NodeId); + + // + // + // + + // Returns the folder to be used for temporary cook content + FString GetDefaultTemporaryCookFolder() const; + + // Returns the defualt folder used for baking + FString GetDefaultBakeFolder() const; + + private: + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // Singleton instance. + static FHoudiniEngineRuntime * HoudiniEngineRuntimeInstance; + + // + TArray> RegisteredHoudiniComponents; + + TArray NodeIdsPendingDelete; + + TArray NodeIdsParentPendingDelete; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h index 4e5378913..9c5329d83 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h @@ -1,219 +1,219 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Logging/LogMacros.h" - -// Define module names. -#define HOUDINI_MODULE "HoudiniEngine" -#define HOUDINI_MODULE_EDITOR "HoudiniEngineEditor" -#define HOUDINI_MODULE_RUNTIME "HoudiniEngineRuntime" - -// Declare the log category depending on the module we're in -#ifdef HOUDINI_ENGINE_EDITOR - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_EDITOR - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, All); -#else - #ifdef HOUDINI_ENGINE - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngine, Log, All); - #else - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineRuntime, Log, All); - #endif -#endif - -//--------------------------------------------------------------------------------------------------------------------- -// Default session settings -//--------------------------------------------------------------------------------------------------------------------- - -#define HAPI_UNREAL_SESSION_SERVER_AUTOSTART true -#define HAPI_UNREAL_SESSION_SERVER_TIMEOUT 3000.0f -#define HAPI_UNREAL_SESSION_SERVER_HOST TEXT( "localhost" ) -#define HAPI_UNREAL_SESSION_SERVER_PORT 9090 -#if PLATFORM_MAC - #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "/tmp/hapi" ) -#else - #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "hapi" ) -#endif - - - -// Names of HAPI libraries on different platforms. -#define HAPI_LIB_OBJECT_WINDOWS TEXT( "libHAPIL.dll" ) -#define HAPI_LIB_OBJECT_MAC TEXT( "libHAPIL.dylib" ) -#define HAPI_LIB_OBJECT_LINUX TEXT( "libHAPIL.so" ) - -//--------------------------------------------------------------------------------------------------------------------- -// LOG MACROS -//--------------------------------------------------------------------------------------------------------------------- - -// Whether to enable logging. -#define HOUDINI_ENGINE_LOGGING 1 - -#ifdef HOUDINI_ENGINE_LOGGING - #ifdef HOUDINI_ENGINE - #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngine, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #else - #ifdef HOUDINI_ENGINE_EDITOR - #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngineEditor, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #else - #define HOUDINI_LOG_HELPER( VERBOSITY, HOUDINI_LOG_TEXT, ... ) \ - do \ - { \ - UE_LOG( LogHoudiniEngineRuntime, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #endif - #endif - - #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Display, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) -#endif - -// HOUDINI_ENGINE_DEBUG_BP: blueprint related debug logging -#ifndef HOUDINI_ENGINE_DEBUG_BP - #define HOUDINI_ENGINE_DEBUG_BP 0 -#endif - -// NOTE: Set HOUDINI_ENGINE_DEBUG_BP to enable BP logging -#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_BP - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); - - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); - - #define HOUDINI_BP_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngineBlueprint, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - - #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() - #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - -// HOUDINI_ENGINE_DEBUG_PDG: PDG related debug logging -#ifndef HOUDINI_ENGINE_DEBUG_PDG - #define HOUDINI_ENGINE_DEBUG_PDG 0 -#endif - -// NOTE: Set HOUDINI_ENGINE_DEBUG_PDG to enable BP logging -#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_PDG - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEnginePDG, Log, All); - - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); - - #define HOUDINI_PDG_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEnginePDG, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - - #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() - #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - -// HAPI_Common -enum HAPI_UNREAL_NodeType -{ - HAPI_UNREAL_NODETYPE_ANY = -1, - HAPU_UNREAL_NODETYPE_NONE = 0 -}; - -enum HAPI_UNREAL_NodeFlags -{ - HAPI_UNREAL_NODEFLAGS_ANY = -1, - HAPI_UNREAL_NODEFLAGS_NONE = 0 -}; - -// Default cook/bake folder -#define HAPI_UNREAL_DEFAULT_BAKE_FOLDER TEXT("/Game/HoudiniEngine/Bake"); -#define HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER TEXT("/Game/HoudiniEngine/Temp"); - -// Default PDG Filters -#define HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER "HE_"; -#define HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER "HE_OUT_"; - - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" + +// Define module names. +#define HOUDINI_MODULE "HoudiniEngine" +#define HOUDINI_MODULE_EDITOR "HoudiniEngineEditor" +#define HOUDINI_MODULE_RUNTIME "HoudiniEngineRuntime" + +// Declare the log category depending on the module we're in +#ifdef HOUDINI_ENGINE_EDITOR + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_EDITOR + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, All); +#else + #ifdef HOUDINI_ENGINE + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngine, Log, All); + #else + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineRuntime, Log, All); + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------- +// Default session settings +//--------------------------------------------------------------------------------------------------------------------- + +#define HAPI_UNREAL_SESSION_SERVER_AUTOSTART true +#define HAPI_UNREAL_SESSION_SERVER_TIMEOUT 3000.0f +#define HAPI_UNREAL_SESSION_SERVER_HOST TEXT( "localhost" ) +#define HAPI_UNREAL_SESSION_SERVER_PORT 9090 +#if PLATFORM_MAC + #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "/tmp/hapi" ) +#else + #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "hapi" ) +#endif + + + +// Names of HAPI libraries on different platforms. +#define HAPI_LIB_OBJECT_WINDOWS TEXT( "libHAPIL.dll" ) +#define HAPI_LIB_OBJECT_MAC TEXT( "libHAPIL.dylib" ) +#define HAPI_LIB_OBJECT_LINUX TEXT( "libHAPIL.so" ) + +//--------------------------------------------------------------------------------------------------------------------- +// LOG MACROS +//--------------------------------------------------------------------------------------------------------------------- + +// Whether to enable logging. +#define HOUDINI_ENGINE_LOGGING 1 + +#ifdef HOUDINI_ENGINE_LOGGING + #ifdef HOUDINI_ENGINE + #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngine, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #else + #ifdef HOUDINI_ENGINE_EDITOR + #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngineEditor, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #else + #define HOUDINI_LOG_HELPER( VERBOSITY, HOUDINI_LOG_TEXT, ... ) \ + do \ + { \ + UE_LOG( LogHoudiniEngineRuntime, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #endif + #endif + + #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Display, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) +#endif + +// HOUDINI_ENGINE_DEBUG_BP: blueprint related debug logging +#ifndef HOUDINI_ENGINE_DEBUG_BP + #define HOUDINI_ENGINE_DEBUG_BP 0 +#endif + +// NOTE: Set HOUDINI_ENGINE_DEBUG_BP to enable BP logging +#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_BP + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); + + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); + + #define HOUDINI_BP_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngineBlueprint, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + + #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_BP_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_BP_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_BP_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_BP_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() + #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + +// HOUDINI_ENGINE_DEBUG_PDG: PDG related debug logging +#ifndef HOUDINI_ENGINE_DEBUG_PDG + #define HOUDINI_ENGINE_DEBUG_PDG 0 +#endif + +// NOTE: Set HOUDINI_ENGINE_DEBUG_PDG to enable BP logging +#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_PDG + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEnginePDG, Log, All); + + #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); + + #define HOUDINI_PDG_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEnginePDG, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + + #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_PDG_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_PDG_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_PDG_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_PDG_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() + #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + +// HAPI_Common +enum HAPI_UNREAL_NodeType +{ + HAPI_UNREAL_NODETYPE_ANY = -1, + HAPU_UNREAL_NODETYPE_NONE = 0 +}; + +enum HAPI_UNREAL_NodeFlags +{ + HAPI_UNREAL_NODEFLAGS_ANY = -1, + HAPI_UNREAL_NODEFLAGS_NONE = 0 +}; + +// Default cook/bake folder +#define HAPI_UNREAL_DEFAULT_BAKE_FOLDER TEXT("/Game/HoudiniEngine/Bake"); +#define HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER TEXT("/Game/HoudiniEngine/Temp"); + +// Default PDG Filters +#define HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER "HE_"; +#define HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER "HE_OUT_"; + + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp index db7103133..bf3c028f4 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp @@ -1,535 +1,535 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineRuntimeUtils.h" -#include "EngineUtils.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Kismet2/BlueprintEditorUtils.h" -#endif - - -FString -FHoudiniEngineRuntimeUtils::GetLibHAPIName() -{ - static const FString LibHAPIName = - -#if PLATFORM_WINDOWS - HAPI_LIB_OBJECT_WINDOWS; -#elif PLATFORM_MAC - HAPI_LIB_OBJECT_MAC; -#elif PLATFORM_LINUX - HAPI_LIB_OBJECT_LINUX; -#else - TEXT(""); -#endif - - return LibHAPIName; -} - - -void -FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes) -{ - OutBBoxes.Empty(); - - for (auto CurrentActor : InActors) - { - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true)); - } -} - - -bool -FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors) -{ - if (!IsValid(World)) - return false; - - OutActors.Empty(); - for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) - { - AActor* CurrentActor = *ActorItr; - if (!IsValid(CurrentActor)) - continue; - - if (!CurrentActor->GetClass()->IsChildOf(ActorType.Get())) - continue; - - if (ExcludeActors && ExcludeActors->Contains(CurrentActor)) - continue; - - // Special case - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); - for (auto InBounds : BBoxes) - { - // Check if both actor's bounds intersects - if (!ActorBounds.Intersect(InBounds)) - continue; - - OutActors.Add(CurrentActor); - break; - } - } - - return true; -} - -bool -FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly) -{ - bool bDeleted = false; - OutPackage = nullptr; - bOutPackageIsInMemoryOnly = false; - - if (!IsValid(InObjectToDelete)) - return false; - - // Don't try to delete the object if it has references (we do this here to avoid the FMessageDialog in DeleteSingleObject - bool bIsReferenced = false; - bool bIsReferencedByUndo = false; - if (!GatherObjectReferencersForDeletion(InObjectToDelete, bIsReferenced, bIsReferencedByUndo)) - return false; - - if (bIsReferenced) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject] Not deleting %s: there are still references to it."), *InObjectToDelete->GetFullName()); - } - else - { - // Even though we already checked for references, we still let DeleteSingleObject check for references, since - // we want that code path where it'll clean up in-memory references (undo buffer/transactions) - const bool bCheckForReferences = true; - if (DeleteSingleObject(InObjectToDelete, bCheckForReferences)) - { - bDeleted = true; - - OutPackage = InObjectToDelete->GetOutermost(); - - FString PackageFilename; - if (!IsValid(OutPackage) || !FPackageName::DoesPackageExist(OutPackage->GetName(), nullptr, &PackageFilename)) - { - // Package is in memory only, we don't have call CleanUpAfterSuccessfulDelete on it, just do garbage - // collection to pick up the stale package - bOutPackageIsInMemoryOnly = true; - } - else - { - // There is an on-disk package that is now potentially empty, CleanUpAfterSuccessfulDelete must be - // called on this. Since CleanUpAfterSuccessfulDelete does garbage collection, we return the Package - // as part of this function so that the caller can collect all Packages and do one call to - // CleanUpAfterSuccessfulDelete with an array - } - } - } - - return bDeleted; -} - -int32 -FHoudiniEngineRuntimeUtils::SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted) -{ - int32 NumDeleted = 0; - bool bGarbageCollectionRequired = false; - TSet PackagesToCleanUp; - TSet ProcessedObjects; - while (InObjectsToDelete.Num() > 0) - { - UObject* const ObjectToDelete = InObjectsToDelete.Pop(); - - if (ProcessedObjects.Contains(ObjectToDelete)) - continue; - - ProcessedObjects.Add(ObjectToDelete); - - if (!IsValid(ObjectToDelete)) - continue; - - UPackage* Package = nullptr; - bool bInMemoryPackageOnly = false; - if (SafeDeleteSingleObject(ObjectToDelete, Package, bInMemoryPackageOnly)) - { - NumDeleted++; - if (bInMemoryPackageOnly) - { - // Packages that are in-memory only are cleaned up by garbage collection - if (!bGarbageCollectionRequired) - bGarbageCollectionRequired = true; - } - else - { - // Clean up potentially empty packages in one call to CleanupAfterSuccessfulDelete at the end - PackagesToCleanUp.Add(Package); - } - } - else if (OutObjectsNotDeleted) - { - OutObjectsNotDeleted->Add(ObjectToDelete); - } - } - - // CleanupAfterSuccessfulDelete calls CollectGarbage, so don't call it here if we have PackagesToCleanUp - if (bGarbageCollectionRequired && PackagesToCleanUp.Num() <= 0) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - if (PackagesToCleanUp.Num() > 0) - CleanupAfterSuccessfulDelete(PackagesToCleanUp.Array()); - - return NumDeleted; -} - - -#if WITH_EDITOR -int32 -FHoudiniEngineRuntimeUtils::CopyComponentProperties(UActorComponent* SourceComponent, UActorComponent* TargetComponent, const EditorUtilities::FCopyOptions& Options) -{ - UClass* ComponentClass = SourceComponent->GetClass(); - check( ComponentClass == TargetComponent->GetClass() ); - - const bool bIsPreviewing = ( Options.Flags & EditorUtilities::ECopyOptions::PreviewOnly ) != 0; - int32 CopiedPropertyCount = 0; - bool bTransformChanged = false; - - // Build a list of matching component archetype instances for propagation (if requested) - TArray ComponentArchetypeInstances; - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - TArray Instances; - TargetComponent->GetArchetypeInstances(Instances); - for(UObject* ObjInstance : Instances) - { - UActorComponent* ComponentInstance = Cast(ObjInstance); - if (ComponentInstance && ComponentInstance != SourceComponent && ComponentInstance != TargetComponent) - { - ComponentArchetypeInstances.Add(ComponentInstance); - } - } - } - - TSet SourceUCSModifiedProperties; - SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties); - - TArray ComponentInstancesToReregister; - - // Copy component properties - for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) - { - const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); - const bool bIsIdentical = Property->Identical_InContainer( SourceComponent, TargetComponent ); - const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); - const bool bIsTransform = - Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); - - // auto SourceComponentIsRoot = [&]() - // { - // USceneComponent* RootComponent = SourceActor->GetRootComponent(); - // if (SourceComponent == RootComponent) - // { - // return true; - // } - // else if (RootComponent == nullptr && bSourceActorIsBPCDO) - // { - // // If we're dealing with a BP CDO as source, then look at the target for whether this is the root component - // return (TargetComponent == TargetActor->GetRootComponent()); - // } - // return false; - // }; - - TSet ModifiedObjects; - // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - // && ( !bIsTransform || (!bSourceActorIsCDO && !bTargetActorIsCDO) || !SourceComponentIsRoot() ) ) - if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - && ( !bIsTransform )) - { - const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) - && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); - if( bIsSafeToCopy ) - { - // if (!Options.CanCopyProperty(*Property, *SourceActor)) - // { - // continue; - // } - if (!Options.CanCopyProperty(*Property, *SourceComponent)) - { - continue; - } - - if( !bIsPreviewing ) - { - if( !ModifiedObjects.Contains(TargetComponent) ) - { - TargetComponent->SetFlags(RF_Transactional); - TargetComponent->Modify(); - ModifiedObjects.Add(TargetComponent); - } - - if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) - { - TargetComponent->PreEditChange( Property ); - } - - // Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances. - TArray ComponentArchetypeInstancesToChange; - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances) - { - if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) ) - { - bool bAdd = true; - // We also need to double check that either the direct archetype of the target is also identical - if (ComponentArchetypeInstance->GetArchetype() != TargetComponent) - { - UActorComponent* CheckComponent = CastChecked(ComponentArchetypeInstance->GetArchetype()); - while (CheckComponent != ComponentArchetypeInstance) - { - if (!Property->Identical_InContainer( CheckComponent, TargetComponent )) - { - bAdd = false; - break; - } - CheckComponent = CastChecked(CheckComponent->GetArchetype()); - } - } - - if (bAdd) - { - ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance ); - } - } - } - } - - EditorUtilities::CopySingleProperty(SourceComponent, TargetComponent, Property); - - if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) - { - FPropertyChangedEvent PropertyChangedEvent( Property ); - TargetComponent->PostEditChangeProperty( PropertyChangedEvent ); - } - - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex ) - { - UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex]; - if( ComponentArchetypeInstance != nullptr ) - { - if( !ModifiedObjects.Contains(ComponentArchetypeInstance) ) - { - // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. - // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. - if (!ComponentArchetypeInstance->IsCreatedByConstructionScript()) - { - ComponentArchetypeInstance->SetFlags(RF_Transactional); - ComponentArchetypeInstance->Modify(); - ModifiedObjects.Add(ComponentArchetypeInstance); - } - - // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. - AActor* Owner = ComponentArchetypeInstance->GetOwner(); - if( Owner != nullptr && !ModifiedObjects.Contains(Owner)) - { - Owner->Modify(); - ModifiedObjects.Add(Owner); - } - } - - if (ComponentArchetypeInstance->IsRegistered()) - { - ComponentArchetypeInstance->UnregisterComponent(); - ComponentInstancesToReregister.Add(ComponentArchetypeInstance); - } - - EditorUtilities::CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property ); - } - } - } - } - - ++CopiedPropertyCount; - - if( bIsTransform ) - { - bTransformChanged = true; - } - } - } - } - - for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister) - { - ModifiedComponentInstance->RegisterComponent(); - } - - return CopiedPropertyCount; -} -#endif - - -#if WITH_EDITOR -FBlueprintEditor* -FHoudiniEngineRuntimeUtils::GetBlueprintEditor(const UObject* InObject) -{ - if (!IsValid(InObject)) - return nullptr; - - UObject* Outer = InObject->GetOuter(); - if (!IsValid(Outer)) - return nullptr; - - UBlueprintGeneratedClass* OuterBPClass = Cast(Outer->GetClass()); - - if (!OuterBPClass) - return nullptr; - - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - return static_cast(AssetEditorSubsystem->FindEditorForAsset(OuterBPClass->ClassGeneratedBy, false)); -} -#endif - - -#if WITH_EDITOR -void -FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate) -{ - if (!ComponentTemplate) - return; - - UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); - if (!BPGC) - return; - - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!Blueprint) - return; - - Blueprint->Modify(); - - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(Blueprint, false)); - check(BlueprintEditor); - - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - TSharedPtr SCSEditor = nullptr; - - SCSEditor = BlueprintEditor->GetSCSEditor(); - check(SCSEditor); - SCSEditor->SaveSCSCurrentState(SCS); - - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); - - SCSEditor->UpdateTree(true); -} -#endif - -#if WITH_EDITOR -void -FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTemplate) -{ - if (!ComponentTemplate) - return; - - UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); - if (!BPGC) - return; - - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!Blueprint) - return; - - Blueprint->Modify(); - - FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); -} -#endif - - -#if WITH_EDITOR -void -FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) -{ - FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); - Obj->PostEditChangeProperty(Evt); -} - -void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FProperty* Property) -{ - FPropertyChangedEvent Evt(Property); - Obj->PostEditChangeProperty(Evt); -} - -void FHoudiniEngineRuntimeUtils::PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange) -{ - if (!InObject) - return; - if (!InObject->HasAnyFlags(RF_ArchetypeObject)) - return; - - // Iterate over the modified properties and propagate value changed to all archetype instances - TArray ArchetypeInstances; - InObject->GetArchetypeInstances(ArchetypeInstances); - for (UObject* Instance : ArchetypeInstances) - { - UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Found Archetype instance: %s"), *(Instance->GetPathName())); - for (FName PropertyName : DeltaChange.ChangedProperties) - { - UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Changed property: %s"), *(PropertyName.ToString())); - // FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation()); - } - } -} - -void FHoudiniEngineRuntimeUtils::ForAllArchetypeInstances(UObject* InTemplateObj, TFunctionRef Operation) -{ - if (!InTemplateObj) - return; - if (!InTemplateObj->HasAnyFlags(RF_ArchetypeObject|RF_DefaultSubObject)) - return; - - TArray Instances; - InTemplateObj->GetArchetypeInstances(Instances); - - for(UObject* Instance : Instances) - { - Operation(Instance); - } -} - - -#endif - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineRuntimeUtils.h" +#include "EngineUtils.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Kismet2/BlueprintEditorUtils.h" +#endif + + +FString +FHoudiniEngineRuntimeUtils::GetLibHAPIName() +{ + static const FString LibHAPIName = + +#if PLATFORM_WINDOWS + HAPI_LIB_OBJECT_WINDOWS; +#elif PLATFORM_MAC + HAPI_LIB_OBJECT_MAC; +#elif PLATFORM_LINUX + HAPI_LIB_OBJECT_LINUX; +#else + TEXT(""); +#endif + + return LibHAPIName; +} + + +void +FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes) +{ + OutBBoxes.Empty(); + + for (auto CurrentActor : InActors) + { + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true)); + } +} + + +bool +FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors) +{ + if (!IsValid(World)) + return false; + + OutActors.Empty(); + for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) + { + AActor* CurrentActor = *ActorItr; + if (!IsValid(CurrentActor)) + continue; + + if (!CurrentActor->GetClass()->IsChildOf(ActorType.Get())) + continue; + + if (ExcludeActors && ExcludeActors->Contains(CurrentActor)) + continue; + + // Special case + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); + for (auto InBounds : BBoxes) + { + // Check if both actor's bounds intersects + if (!ActorBounds.Intersect(InBounds)) + continue; + + OutActors.Add(CurrentActor); + break; + } + } + + return true; +} + +bool +FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly) +{ + bool bDeleted = false; + OutPackage = nullptr; + bOutPackageIsInMemoryOnly = false; + + if (!IsValid(InObjectToDelete)) + return false; + + // Don't try to delete the object if it has references (we do this here to avoid the FMessageDialog in DeleteSingleObject + bool bIsReferenced = false; + bool bIsReferencedByUndo = false; + if (!GatherObjectReferencersForDeletion(InObjectToDelete, bIsReferenced, bIsReferencedByUndo)) + return false; + + if (bIsReferenced) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject] Not deleting %s: there are still references to it."), *InObjectToDelete->GetFullName()); + } + else + { + // Even though we already checked for references, we still let DeleteSingleObject check for references, since + // we want that code path where it'll clean up in-memory references (undo buffer/transactions) + const bool bCheckForReferences = true; + if (DeleteSingleObject(InObjectToDelete, bCheckForReferences)) + { + bDeleted = true; + + OutPackage = InObjectToDelete->GetOutermost(); + + FString PackageFilename; + if (!IsValid(OutPackage) || !FPackageName::DoesPackageExist(OutPackage->GetName(), nullptr, &PackageFilename)) + { + // Package is in memory only, we don't have call CleanUpAfterSuccessfulDelete on it, just do garbage + // collection to pick up the stale package + bOutPackageIsInMemoryOnly = true; + } + else + { + // There is an on-disk package that is now potentially empty, CleanUpAfterSuccessfulDelete must be + // called on this. Since CleanUpAfterSuccessfulDelete does garbage collection, we return the Package + // as part of this function so that the caller can collect all Packages and do one call to + // CleanUpAfterSuccessfulDelete with an array + } + } + } + + return bDeleted; +} + +int32 +FHoudiniEngineRuntimeUtils::SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted) +{ + int32 NumDeleted = 0; + bool bGarbageCollectionRequired = false; + TSet PackagesToCleanUp; + TSet ProcessedObjects; + while (InObjectsToDelete.Num() > 0) + { + UObject* const ObjectToDelete = InObjectsToDelete.Pop(); + + if (ProcessedObjects.Contains(ObjectToDelete)) + continue; + + ProcessedObjects.Add(ObjectToDelete); + + if (!IsValid(ObjectToDelete)) + continue; + + UPackage* Package = nullptr; + bool bInMemoryPackageOnly = false; + if (SafeDeleteSingleObject(ObjectToDelete, Package, bInMemoryPackageOnly)) + { + NumDeleted++; + if (bInMemoryPackageOnly) + { + // Packages that are in-memory only are cleaned up by garbage collection + if (!bGarbageCollectionRequired) + bGarbageCollectionRequired = true; + } + else + { + // Clean up potentially empty packages in one call to CleanupAfterSuccessfulDelete at the end + PackagesToCleanUp.Add(Package); + } + } + else if (OutObjectsNotDeleted) + { + OutObjectsNotDeleted->Add(ObjectToDelete); + } + } + + // CleanupAfterSuccessfulDelete calls CollectGarbage, so don't call it here if we have PackagesToCleanUp + if (bGarbageCollectionRequired && PackagesToCleanUp.Num() <= 0) + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + if (PackagesToCleanUp.Num() > 0) + CleanupAfterSuccessfulDelete(PackagesToCleanUp.Array()); + + return NumDeleted; +} + + +#if WITH_EDITOR +int32 +FHoudiniEngineRuntimeUtils::CopyComponentProperties(UActorComponent* SourceComponent, UActorComponent* TargetComponent, const EditorUtilities::FCopyOptions& Options) +{ + UClass* ComponentClass = SourceComponent->GetClass(); + check( ComponentClass == TargetComponent->GetClass() ); + + const bool bIsPreviewing = ( Options.Flags & EditorUtilities::ECopyOptions::PreviewOnly ) != 0; + int32 CopiedPropertyCount = 0; + bool bTransformChanged = false; + + // Build a list of matching component archetype instances for propagation (if requested) + TArray ComponentArchetypeInstances; + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + TArray Instances; + TargetComponent->GetArchetypeInstances(Instances); + for(UObject* ObjInstance : Instances) + { + UActorComponent* ComponentInstance = Cast(ObjInstance); + if (ComponentInstance && ComponentInstance != SourceComponent && ComponentInstance != TargetComponent) + { + ComponentArchetypeInstances.Add(ComponentInstance); + } + } + } + + TSet SourceUCSModifiedProperties; + SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties); + + TArray ComponentInstancesToReregister; + + // Copy component properties + for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) + { + const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); + const bool bIsIdentical = Property->Identical_InContainer( SourceComponent, TargetComponent ); + const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); + const bool bIsTransform = + Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); + + // auto SourceComponentIsRoot = [&]() + // { + // USceneComponent* RootComponent = SourceActor->GetRootComponent(); + // if (SourceComponent == RootComponent) + // { + // return true; + // } + // else if (RootComponent == nullptr && bSourceActorIsBPCDO) + // { + // // If we're dealing with a BP CDO as source, then look at the target for whether this is the root component + // return (TargetComponent == TargetActor->GetRootComponent()); + // } + // return false; + // }; + + TSet ModifiedObjects; + // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + // && ( !bIsTransform || (!bSourceActorIsCDO && !bTargetActorIsCDO) || !SourceComponentIsRoot() ) ) + if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + && ( !bIsTransform )) + { + const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) + && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); + if( bIsSafeToCopy ) + { + // if (!Options.CanCopyProperty(*Property, *SourceActor)) + // { + // continue; + // } + if (!Options.CanCopyProperty(*Property, *SourceComponent)) + { + continue; + } + + if( !bIsPreviewing ) + { + if( !ModifiedObjects.Contains(TargetComponent) ) + { + TargetComponent->SetFlags(RF_Transactional); + TargetComponent->Modify(); + ModifiedObjects.Add(TargetComponent); + } + + if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) + { + TargetComponent->PreEditChange( Property ); + } + + // Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances. + TArray ComponentArchetypeInstancesToChange; + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances) + { + if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) ) + { + bool bAdd = true; + // We also need to double check that either the direct archetype of the target is also identical + if (ComponentArchetypeInstance->GetArchetype() != TargetComponent) + { + UActorComponent* CheckComponent = CastChecked(ComponentArchetypeInstance->GetArchetype()); + while (CheckComponent != ComponentArchetypeInstance) + { + if (!Property->Identical_InContainer( CheckComponent, TargetComponent )) + { + bAdd = false; + break; + } + CheckComponent = CastChecked(CheckComponent->GetArchetype()); + } + } + + if (bAdd) + { + ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance ); + } + } + } + } + + EditorUtilities::CopySingleProperty(SourceComponent, TargetComponent, Property); + + if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) + { + FPropertyChangedEvent PropertyChangedEvent( Property ); + TargetComponent->PostEditChangeProperty( PropertyChangedEvent ); + } + + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex ) + { + UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex]; + if( ComponentArchetypeInstance != nullptr ) + { + if( !ModifiedObjects.Contains(ComponentArchetypeInstance) ) + { + // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. + // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. + if (!ComponentArchetypeInstance->IsCreatedByConstructionScript()) + { + ComponentArchetypeInstance->SetFlags(RF_Transactional); + ComponentArchetypeInstance->Modify(); + ModifiedObjects.Add(ComponentArchetypeInstance); + } + + // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. + AActor* Owner = ComponentArchetypeInstance->GetOwner(); + if( Owner != nullptr && !ModifiedObjects.Contains(Owner)) + { + Owner->Modify(); + ModifiedObjects.Add(Owner); + } + } + + if (ComponentArchetypeInstance->IsRegistered()) + { + ComponentArchetypeInstance->UnregisterComponent(); + ComponentInstancesToReregister.Add(ComponentArchetypeInstance); + } + + EditorUtilities::CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property ); + } + } + } + } + + ++CopiedPropertyCount; + + if( bIsTransform ) + { + bTransformChanged = true; + } + } + } + } + + for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister) + { + ModifiedComponentInstance->RegisterComponent(); + } + + return CopiedPropertyCount; +} +#endif + + +#if WITH_EDITOR +FBlueprintEditor* +FHoudiniEngineRuntimeUtils::GetBlueprintEditor(const UObject* InObject) +{ + if (!IsValid(InObject)) + return nullptr; + + UObject* Outer = InObject->GetOuter(); + if (!IsValid(Outer)) + return nullptr; + + UBlueprintGeneratedClass* OuterBPClass = Cast(Outer->GetClass()); + + if (!OuterBPClass) + return nullptr; + + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + return static_cast(AssetEditorSubsystem->FindEditorForAsset(OuterBPClass->ClassGeneratedBy, false)); +} +#endif + + +#if WITH_EDITOR +void +FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate) +{ + if (!ComponentTemplate) + return; + + UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); + if (!BPGC) + return; + + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!Blueprint) + return; + + Blueprint->Modify(); + + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(Blueprint, false)); + check(BlueprintEditor); + + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + TSharedPtr SCSEditor = nullptr; + + SCSEditor = BlueprintEditor->GetSCSEditor(); + check(SCSEditor); + SCSEditor->SaveSCSCurrentState(SCS); + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + + SCSEditor->UpdateTree(true); +} +#endif + +#if WITH_EDITOR +void +FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTemplate) +{ + if (!ComponentTemplate) + return; + + UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); + if (!BPGC) + return; + + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!Blueprint) + return; + + Blueprint->Modify(); + + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); +} +#endif + + +#if WITH_EDITOR +void +FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) +{ + FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); + Obj->PostEditChangeProperty(Evt); +} + +void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FProperty* Property) +{ + FPropertyChangedEvent Evt(Property); + Obj->PostEditChangeProperty(Evt); +} + +void FHoudiniEngineRuntimeUtils::PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange) +{ + if (!InObject) + return; + if (!InObject->HasAnyFlags(RF_ArchetypeObject)) + return; + + // Iterate over the modified properties and propagate value changed to all archetype instances + TArray ArchetypeInstances; + InObject->GetArchetypeInstances(ArchetypeInstances); + for (UObject* Instance : ArchetypeInstances) + { + UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Found Archetype instance: %s"), *(Instance->GetPathName())); + for (FName PropertyName : DeltaChange.ChangedProperties) + { + UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Changed property: %s"), *(PropertyName.ToString())); + // FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation()); + } + } +} + +void FHoudiniEngineRuntimeUtils::ForAllArchetypeInstances(UObject* InTemplateObj, TFunctionRef Operation) +{ + if (!InTemplateObj) + return; + if (!InTemplateObj->HasAnyFlags(RF_ArchetypeObject|RF_DefaultSubObject)) + return; + + TArray Instances; + InTemplateObj->GetArchetypeInstances(Instances); + + for(UObject* Instance : Instances) + { + Operation(Instance); + } +} + + +#endif + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h index cf8df231d..7bffd43af 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h @@ -1,359 +1,337 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/ObjectMacros.h" - -#if WITH_EDITOR - #include "SSCSEditor.h" - #include "ObjectTools.h" - #include "Kismet2/ComponentEditorUtils.h" - #include "Editor/Transactor.h" -#endif - -class AActor; -class UWorld; - -template -class TSubclassOf; - -struct FBox; - -struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils -{ - public: - - // Return platform specific name of libHAPI. - static FString GetLibHAPIName(); - - // ----------------------------------------------- - // Bounding Box utilities - // ----------------------------------------------- - - // Collect all the bounding boxes form the specified list of actors. OutBBoxes will be emptied. - static void GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes); - - // Collect actors that derive from the given class that intersect with the given array of bounding boxes. - static bool FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors); - - // ----------------------------------------------- - // File path utilities - // ----------------------------------------------- - - // Joins paths by taking into account whether paths - // successive paths are relative or absolute. - // Truncate everything preceding an absolute path. - // Taken and adapted from FPaths::Combine(). - template - FORCEINLINE static FString JoinPaths(PathTypes&&... InPaths) - { - const TCHAR* Paths[] = { GetTCharPtr(Forward(InPaths))... }; - const int32 NumPaths = UE_ARRAY_COUNT(Paths); - - FString Out = TEXT(""); - if (NumPaths <= 0) - return Out; - Out = Paths[NumPaths-1]; - // Process paths in reverse and terminate when we reach an absolute path. - for (int32 i=NumPaths-2; i >= 0; --i) - { - if (FCString::Strlen(Paths[i]) == 0) - continue; - if (Out[0] == '/') - { - // We already have an absolute path. Terminate. - break; - } - Out = Paths[i] / Out; - } - if (Out.Len() > 0 && Out[0] != '/') - Out = TEXT("/") + Out; - return Out; - } - - // ------------------------------------------------------------------ - // ObjectTools (Make some editor only ObjectTools functions available - // in editor builds) - // ------------------------------------------------------------------ - - // Check/gather references to InObject. - // Returns true if the function could execute (editor vs runtime) - FORCEINLINE static bool GatherObjectReferencersForDeletion(UObject* InObject, bool& bOutIsReferenced, bool& bOutIsReferencedByUndo, FReferencerInformationList* OutMemoryReferences = nullptr, bool bInRequireReferencingProperties = false) - { -#if WITH_EDITOR - // DOESN'T EXIST IN 4.25 USING LEGACY EQUIVALENT - //ObjectTools::GatherObjectReferencersForDeletion(InObject, bOutIsReferenced, bOutIsReferencedByUndo, OutMemoryReferences, bInRequireReferencingProperties); - - bOutIsReferenced = false; - bOutIsReferencedByUndo = false; - - // Check and see whether we are referenced by any objects that won't be garbage collected. - bOutIsReferenced = IsReferenced(InObject, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, OutMemoryReferences); - if (bOutIsReferenced) - { - // determine whether the transaction buffer is the only thing holding a reference to the object - // and if so, offer the user the option to reset the transaction buffer. - GEditor->Trans->DisableObjectSerialization(); - bOutIsReferenced = IsReferenced(InObject, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, OutMemoryReferences); - GEditor->Trans->EnableObjectSerialization(); - - // If object is referenced both in undo and non-undo, we can't determine which one it is but - // it doesn't matter since the undo stack is only cleared if objects are only referenced by it. - if (!bOutIsReferenced) - { - bOutIsReferencedByUndo = true; - } - } - - return true; -#else - return false; -#endif - } - - // Delete a single object from its package. Returns true if the object was deleted. In non-editor - // builds this function returns false. - FORCEINLINE static bool DeleteSingleObject(UObject* InObjectToDelete, bool bInPerformReferenceCheck=true) - { -#if WITH_EDITOR - return ObjectTools::DeleteSingleObject(InObjectToDelete, bInPerformReferenceCheck); -#else - return false; -#endif - } - - // Collects garbage and marks truely empty packages for delete - // Returns true if the function could execute (editor vs runtime) - FORCEINLINE static bool CleanupAfterSuccessfulDelete(const TArray& InObjectsDeletedSuccessfully, bool bInPerformReferenceCheck=true) - { -#if WITH_EDITOR - ObjectTools::CleanupAfterSuccessfulDelete(InObjectsDeletedSuccessfully, bInPerformReferenceCheck); - return true; -#else - return false; -#endif - } - - // Deletes a single object. Returns true if the object was deleted. - // The object is only deleted if there are no references to it. - // If the package is on disk then bOutPackageIsInMemoryOnly is false and the CleanUpAfterSuccessfulDelete - // must be called on the package after execution of this function. - static bool SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly); - - // Deletes and cleans up on disk empty-packages for the objects in InObjectsToDelete. - // Objects are popped from InObjectsToDelete as they are processed (ran into cases where objects are garbage collected - // before we can properly delete them and cleanup their packages, so we tend to pass in a UPROPERTY based TArray - // that holds references to UObject to prevent garbage collection until we can delete them in this function) - // OutObjectsNotDeleted can optionally be used to return objects that could not be deleted. - // The function returns the number of objects that were deleted. - static int32 SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted=nullptr); - - // ------------------------------------------------- - // Type utilities - // ------------------------------------------------- - - // Taken from here: https://answers.unrealengine.com/questions/330496/conversion-of-enum-to-string.html - // Return the string representation of an enum value. - template - static FString EnumToString(const FString& enumName, const T value) - { - UEnum* pEnum = FindObject((UObject*)ANY_PACKAGE, *enumName); - return *(pEnum ? pEnum->GetNameStringByIndex(static_cast(value)) : "null"); - } - - // ------------------------------------------------- - // Blueprint utilities - // ------------------------------------------------- -#if WITH_EDITOR - // This function contains an excerpt from UEditorUtilities::CopyActorProperties() - // for specifically dealing with copying properties between components as well as propagating - // property changes to archetype instances. - static int32 CopyComponentProperties(UActorComponent* FromComponent, UActorComponent* ToComponent, const EditorUtilities::FCopyOptions& Options); - - // Get the SCSEditor for the given HoudiniAssetComponent - static FBlueprintEditor* GetBlueprintEditor(const UObject* InObject); - - static void MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate); - static void MarkBlueprintAsModified(UActorComponent* ComponentTemplate); -#endif - - // ------------------------------------------------- - // Editor Helpers - // ------------------------------------------------- -#if WITH_EDITOR - static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); - - static void DoPostEditChangeProperty(UObject* Obj, FProperty* Property); - - static void PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange); - - template - static TSet PropagateDefaultValueChange(USceneComponent* InSceneComponentTemplate, const FName& PropertyName, const T& OldValue, const T& NewValue) - { - TSet UpdatedInstances; - FComponentEditorUtils::PropagateDefaultValueChange(InSceneComponentTemplate, FindFieldChecked(InSceneComponentTemplate->GetClass(), PropertyName), OldValue, NewValue, UpdatedInstances); - return UpdatedInstances; - } - - // Perform this operation on the given archetype as well as each archetype instance. If the given - // object in not an archetype instance, then don't do anything. - static void ForAllArchetypeInstances(UObject* Archetype, TFunctionRef Operation); - -#endif - - /** - // * Set the value on an UObject using reflection. - // * @param Object The object to copy the value into. - // * @param PropertyName The name of the property to set. - // * @param Value The value to assign to the property. - // * - // * @return true if the value was set correctly - // */ - //template - //static bool SetPropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) - //{ - // // Get the property addresses for the source and destination objects. - // FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - - // // Get the property addresses for the object - // ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); - - // if ( SourceAddr == NULL ) - // { - // return false; - // } - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - // { - // FEditPropertyChain PropertyChain; - // PropertyChain.AddHead(Property); - // Object->PreEditChange(PropertyChain); - // } - - // // Set the value on the destination object. - // *SourceAddr = Value; - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - // { - // FPropertyChangedEvent PropertyEvent(Property); - // Object->PostEditChangeProperty(PropertyEvent); - // } - - // return true; - //} -#if WITH_EDITOR - template - static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) - { - // Get the property addresses for the source and destination objects. - FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - - // Get the property addresses for the object - ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); - - if ( SourceAddr == NULL ) - { - return false; - } - - if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FEditPropertyChain PropertyChain; - PropertyChain.AddHead(Property); - ((UObject*)Object)->PreEditChange(PropertyChain); - } - - // Set the value on the destination object. - if (*SourceAddr != Value) - { - TSet UpdatedInstances; - *SourceAddr = Value; - PropagateDefaultValueChange(Object, Property, *SourceAddr, Value, UpdatedInstances); - } - - if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FPropertyChangedEvent PropertyEvent(Property); - Object->PostEditChangeProperty(PropertyEvent); - } - - return true; - } -#endif - -#if WITH_EDITOR - // Bool specialization - template - static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, bool NewBool) - { - // Get the property addresses for the source and destination objects. - FBoolProperty* BoolProperty = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - check(BoolProperty); - - // Get the property addresses for the object - const int32 PropertyOffset = INDEX_NONE; - void* CurrentValue = PropertyOffset == INDEX_NONE ? BoolProperty->ContainerPtrToValuePtr(Object) : ((uint8*)Object + PropertyOffset); - check(CurrentValue); - - const bool CurrentBool = BoolProperty->GetPropertyValue(CurrentValue); - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FEditPropertyChain PropertyChain; - PropertyChain.AddHead(BoolProperty); - ((UObject*)Object)->PreEditChange(PropertyChain); - } - - // Set the value on the destination object. - if (CurrentBool != NewBool) - { - TSet UpdatedInstances; - BoolProperty->SetPropertyValue(CurrentValue, NewBool); - FComponentEditorUtils::PropagateDefaultValueChange(Object, BoolProperty, CurrentBool, NewBool, UpdatedInstances); - } - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FPropertyChangedEvent PropertyEvent(BoolProperty); - Object->PostEditChangeProperty(PropertyEvent); - } - - return true; - } -#endif - - protected: - // taken from FPaths::GetTCharPtr - FORCEINLINE static const TCHAR* GetTCharPtr(const TCHAR* Ptr) - { - return Ptr; - } - FORCEINLINE static const TCHAR* GetTCharPtr(const FString& Str) - { - return *Str; - } +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/ObjectMacros.h" + +#if WITH_EDITOR + #include "SSCSEditor.h" + #include "ObjectTools.h" + #include "Kismet2/ComponentEditorUtils.h" + #include "Editor/Transactor.h" +#endif + +class AActor; +class UWorld; + +template +class TSubclassOf; + +struct FBox; + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils +{ + public: + + // Return platform specific name of libHAPI. + static FString GetLibHAPIName(); + + // ----------------------------------------------- + // Bounding Box utilities + // ----------------------------------------------- + + // Collect all the bounding boxes form the specified list of actors. OutBBoxes will be emptied. + static void GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes); + + // Collect actors that derive from the given class that intersect with the given array of bounding boxes. + static bool FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors); + + // ----------------------------------------------- + // File path utilities + // ----------------------------------------------- + + // Joins paths by taking into account whether paths + // successive paths are relative or absolute. + // Truncate everything preceding an absolute path. + // Taken and adapted from FPaths::Combine(). + template + FORCEINLINE static FString JoinPaths(PathTypes&&... InPaths) + { + const TCHAR* Paths[] = { GetTCharPtr(Forward(InPaths))... }; + const int32 NumPaths = UE_ARRAY_COUNT(Paths); + + FString Out = TEXT(""); + if (NumPaths <= 0) + return Out; + Out = Paths[NumPaths-1]; + // Process paths in reverse and terminate when we reach an absolute path. + for (int32 i=NumPaths-2; i >= 0; --i) + { + if (FCString::Strlen(Paths[i]) == 0) + continue; + if (Out[0] == '/') + { + // We already have an absolute path. Terminate. + break; + } + Out = Paths[i] / Out; + } + if (Out.Len() > 0 && Out[0] != '/') + Out = TEXT("/") + Out; + return Out; + } + + // ------------------------------------------------------------------ + // ObjectTools (Make some editor only ObjectTools functions available + // in editor builds) + // ------------------------------------------------------------------ + + // Check/gather references to InObject. + // Returns true if the function could execute (editor vs runtime) + FORCEINLINE static bool GatherObjectReferencersForDeletion(UObject* InObject, bool& bOutIsReferenced, bool& bOutIsReferencedByUndo, FReferencerInformationList* OutMemoryReferences = nullptr, bool bInRequireReferencingProperties = false) + { +#if WITH_EDITOR + ObjectTools::GatherObjectReferencersForDeletion(InObject, bOutIsReferenced, bOutIsReferencedByUndo, OutMemoryReferences, bInRequireReferencingProperties); + + return true; +#else + return false; +#endif + } + + // Delete a single object from its package. Returns true if the object was deleted. In non-editor + // builds this function returns false. + FORCEINLINE static bool DeleteSingleObject(UObject* InObjectToDelete, bool bInPerformReferenceCheck=true) + { +#if WITH_EDITOR + return ObjectTools::DeleteSingleObject(InObjectToDelete, bInPerformReferenceCheck); +#else + return false; +#endif + } + + // Collects garbage and marks truely empty packages for delete + // Returns true if the function could execute (editor vs runtime) + FORCEINLINE static bool CleanupAfterSuccessfulDelete(const TArray& InObjectsDeletedSuccessfully, bool bInPerformReferenceCheck=true) + { +#if WITH_EDITOR + ObjectTools::CleanupAfterSuccessfulDelete(InObjectsDeletedSuccessfully, bInPerformReferenceCheck); + return true; +#else + return false; +#endif + } + + // Deletes a single object. Returns true if the object was deleted. + // The object is only deleted if there are no references to it. + // If the package is on disk then bOutPackageIsInMemoryOnly is false and the CleanUpAfterSuccessfulDelete + // must be called on the package after execution of this function. + static bool SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly); + + // Deletes and cleans up on disk empty-packages for the objects in InObjectsToDelete. + // Objects are popped from InObjectsToDelete as they are processed (ran into cases where objects are garbage collected + // before we can properly delete them and cleanup their packages, so we tend to pass in a UPROPERTY based TArray + // that holds references to UObject to prevent garbage collection until we can delete them in this function) + // OutObjectsNotDeleted can optionally be used to return objects that could not be deleted. + // The function returns the number of objects that were deleted. + static int32 SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted=nullptr); + + // ------------------------------------------------- + // Type utilities + // ------------------------------------------------- + + // Taken from here: https://answers.unrealengine.com/questions/330496/conversion-of-enum-to-string.html + // Return the string representation of an enum value. + template + static FString EnumToString(const FString& enumName, const T value) + { + UEnum* pEnum = FindObject((UObject*)ANY_PACKAGE, *enumName); + return *(pEnum ? pEnum->GetNameStringByIndex(static_cast(value)) : "null"); + } + + // ------------------------------------------------- + // Blueprint utilities + // ------------------------------------------------- +#if WITH_EDITOR + // This function contains an excerpt from UEditorUtilities::CopyActorProperties() + // for specifically dealing with copying properties between components as well as propagating + // property changes to archetype instances. + static int32 CopyComponentProperties(UActorComponent* FromComponent, UActorComponent* ToComponent, const EditorUtilities::FCopyOptions& Options); + + // Get the SCSEditor for the given HoudiniAssetComponent + static FBlueprintEditor* GetBlueprintEditor(const UObject* InObject); + + static void MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate); + static void MarkBlueprintAsModified(UActorComponent* ComponentTemplate); +#endif + + // ------------------------------------------------- + // Editor Helpers + // ------------------------------------------------- +#if WITH_EDITOR + static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); + + static void DoPostEditChangeProperty(UObject* Obj, FProperty* Property); + + static void PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange); + + template + static TSet PropagateDefaultValueChange(USceneComponent* InSceneComponentTemplate, const FName& PropertyName, const T& OldValue, const T& NewValue) + { + TSet UpdatedInstances; + FComponentEditorUtils::PropagateDefaultValueChange(InSceneComponentTemplate, FindFieldChecked(InSceneComponentTemplate->GetClass(), PropertyName), OldValue, NewValue, UpdatedInstances); + return UpdatedInstances; + } + + // Perform this operation on the given archetype as well as each archetype instance. If the given + // object in not an archetype instance, then don't do anything. + static void ForAllArchetypeInstances(UObject* Archetype, TFunctionRef Operation); + +#endif + + /** + // * Set the value on an UObject using reflection. + // * @param Object The object to copy the value into. + // * @param PropertyName The name of the property to set. + // * @param Value The value to assign to the property. + // * + // * @return true if the value was set correctly + // */ + //template + //static bool SetPropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) + //{ + // // Get the property addresses for the source and destination objects. + // FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + + // // Get the property addresses for the object + // ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); + + // if ( SourceAddr == NULL ) + // { + // return false; + // } + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + // { + // FEditPropertyChain PropertyChain; + // PropertyChain.AddHead(Property); + // Object->PreEditChange(PropertyChain); + // } + + // // Set the value on the destination object. + // *SourceAddr = Value; + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + // { + // FPropertyChangedEvent PropertyEvent(Property); + // Object->PostEditChangeProperty(PropertyEvent); + // } + + // return true; + //} +#if WITH_EDITOR + template + static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) + { + // Get the property addresses for the source and destination objects. + FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + + // Get the property addresses for the object + ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); + + if ( SourceAddr == NULL ) + { + return false; + } + + if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FEditPropertyChain PropertyChain; + PropertyChain.AddHead(Property); + ((UObject*)Object)->PreEditChange(PropertyChain); + } + + // Set the value on the destination object. + if (*SourceAddr != Value) + { + TSet UpdatedInstances; + *SourceAddr = Value; + PropagateDefaultValueChange(Object, Property, *SourceAddr, Value, UpdatedInstances); + } + + if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FPropertyChangedEvent PropertyEvent(Property); + Object->PostEditChangeProperty(PropertyEvent); + } + + return true; + } +#endif + +#if WITH_EDITOR + // Bool specialization + template + static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, bool NewBool) + { + // Get the property addresses for the source and destination objects. + FBoolProperty* BoolProperty = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + check(BoolProperty); + + // Get the property addresses for the object + const int32 PropertyOffset = INDEX_NONE; + void* CurrentValue = PropertyOffset == INDEX_NONE ? BoolProperty->ContainerPtrToValuePtr(Object) : ((uint8*)Object + PropertyOffset); + check(CurrentValue); + + const bool CurrentBool = BoolProperty->GetPropertyValue(CurrentValue); + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FEditPropertyChain PropertyChain; + PropertyChain.AddHead(BoolProperty); + ((UObject*)Object)->PreEditChange(PropertyChain); + } + + // Set the value on the destination object. + if (CurrentBool != NewBool) + { + TSet UpdatedInstances; + BoolProperty->SetPropertyValue(CurrentValue, NewBool); + FComponentEditorUtils::PropagateDefaultValueChange(Object, BoolProperty, CurrentBool, NewBool, UpdatedInstances); + } + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FPropertyChangedEvent PropertyEvent(BoolProperty); + Object->PostEditChangeProperty(PropertyEvent); + } + + return true; + } +#endif + + protected: + // taken from FPaths::GetTCharPtr + FORCEINLINE static const TCHAR* GetTCharPtr(const TCHAR* Ptr) + { + return Ptr; + } + FORCEINLINE static const TCHAR* GetTCharPtr(const FString& Str) + { + return *Str; + } }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp index 5a51bef74..ef258164a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp @@ -1,1077 +1,1077 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGenericAttribute.h" - -#include "Engine/StaticMesh.h" -#include "Components/ActorComponent.h" -#include "Components/PrimitiveComponent.h" -#include "Components/StaticMeshComponent.h" - -#include "PhysicsEngine/BodySetup.h" -#include "EditorFramework/AssetImportData.h" -#include "AI/Navigation/NavCollisionBase.h" - -double -FHoudiniGenericAttribute::GetDoubleValue(int32 index) -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return DoubleValues[index]; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return (double)IntValues[index]; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return FCString::Atod(*StringValues[index]); - } - - return 0.0f; -} - -void -FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 index) -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetDoubleValue(index * AttributeTupleSize + n); -} - -int64 -FHoudiniGenericAttribute::GetIntValue(int32 index) -{ - if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return IntValues[index]; - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return (int64)DoubleValues[index]; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return FCString::Atoi64(*StringValues[index]); - } - - return 0; -} - -void -FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetIntValue(index * AttributeTupleSize + n); -} - -FString -FHoudiniGenericAttribute::GetStringValue(int32 index) -{ - if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return StringValues[index]; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return FString::FromInt((int32)IntValues[index]); - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return FString::SanitizeFloat(DoubleValues[index]); - } - - return FString(); -} - -void -FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 index) -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetStringValue(index * AttributeTupleSize + n); -} - -bool -FHoudiniGenericAttribute::GetBoolValue(int32 index) -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return DoubleValues[index] == 0.0 ? false : true; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return IntValues[index] == 0 ? false : true; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return StringValues[index].Equals(TEXT("true"), ESearchCase::IgnoreCase) ? true : false; - } - - return false; -} - -void -FHoudiniGenericAttribute::GetBoolTuple(TArray& TupleValues, int32 index) -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetBoolValue(index * AttributeTupleSize + n); -} - -void* -FHoudiniGenericAttribute::GetData() -{ - if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.Num() > 0) - return StringValues.GetData(); - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.Num() > 0) - return IntValues.GetData(); - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.Num() > 0) - return DoubleValues.GetData(); - } - - return nullptr; -} - -bool -FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( - UObject* InObject, FHoudiniGenericAttribute InPropertyAttribute, const int32& AtIndex) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Get the Property name - const FString& PropertyName = InPropertyAttribute.AttributeName; - if (PropertyName.IsEmpty()) - return false; - - // Some Properties need to be handle and modified manually... - if (PropertyName == "CollisionProfileName") - { - UPrimitiveComponent* PC = Cast(InObject); - if (PC && !PC->IsPendingKill()) - { - FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); - FName Value = FName(*StringValue); - PC->SetCollisionProfileName(Value); - - return true; - } - return false; - } - - // Handle Component Tags manually here - if (PropertyName.Contains("Tags")) - { - UActorComponent* AC = Cast< UActorComponent >(InObject); - if (AC && !AC->IsPendingKill()) - { - FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); - if (!AC->ComponentTags.Contains(NameAttr)) - AC->ComponentTags.Add(NameAttr); - /* - for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++) - { - FName NameAttr = FName(*InPropertyAttribute.GetStringValue(nIdx)); - if (!AC->ComponentTags.Contains(NameAttr)) - AC->ComponentTags.Add(NameAttr); - } - */ - return true; - } - return false; - } - - // Try to find the corresponding UProperty - void* OutContainer = nullptr; - FProperty* FoundProperty = nullptr; - UObject* FoundPropertyObject = nullptr; - if (!FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) - return false; - - // Modify the Property we found - if (!ModifyPropertyValueOnObject(FoundPropertyObject, InPropertyAttribute, FoundProperty, OutContainer, AtIndex)) - return false; - - return true; -} - - -bool -FHoudiniGenericAttribute::FindPropertyOnObject( - UObject* InObject, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - UObject*& OutFoundPropertyObject, - void*& OutContainer) -{ -#if WITH_EDITOR - if (!InObject || InObject->IsPendingKill()) - return false; - - if (InPropertyName.IsEmpty()) - return false; - - UClass* ObjectClass = InObject->GetClass(); - if (!ObjectClass || ObjectClass->IsPendingKill()) - return false; - - // Set the result pointer to null - OutContainer = nullptr; - OutFoundProperty = nullptr; - OutFoundPropertyObject = InObject; - - bool bPropertyHasBeenFound = false; - FHoudiniGenericAttribute::TryToFindProperty( - InObject, - ObjectClass, - InPropertyName, - OutFoundProperty, - bPropertyHasBeenFound, - OutContainer); - - /* - // TODO: Parsing needs to be made recursively! - // Iterate manually on the properties, in order to handle StructProperties correctly - for (TFieldIterator PropIt(ObjectClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) - { - FProperty* CurrentProperty = *PropIt; - if (!CurrentProperty) - continue; - - FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = CurrentProperty->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - OutFoundProperty = CurrentProperty; - - // If it's an equality, we dont need to keep searching - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bPropertyHasBeenFound = true; - break; - } - } - - // StructProperty need to be a nested struct - //if (UStructProperty* StructProperty = Cast< UStructProperty >(CurrentProperty)) - // bPropertyHasBeenFound = TryToFindInStructProperty(InObject, InPropertyName, StructProperty, OutFoundProperty, OutStructContainer); - //else if (UArrayProperty* ArrayProperty = Cast< UArrayProperty >(CurrentProperty)) - // bPropertyHasBeenFound = TryToFindInArrayProperty(InObject, InPropertyName, ArrayProperty, OutFoundProperty, OutStructContainer); - - // Handle StructProperty - FStructProperty* StructProperty = CastField(CurrentProperty); - if (StructProperty) - { - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) - continue; - - for (TFieldIterator It(Struct); It; ++It) - { - FProperty* Property = *It; - if (!Property) - continue; - - DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - Name = Property->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - OutFoundProperty = Property; - OutStructContainer = StructProperty->ContainerPtrToValuePtr< void >(InObject, 0); - - // If it's an equality, we dont need to keep searching - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bPropertyHasBeenFound = true; - break; - } - } - } - } - - if (bPropertyHasBeenFound) - break; - } - - if (bPropertyHasBeenFound) - return true; - */ - - // Try with FindField?? - if (!OutFoundProperty) - OutFoundProperty = FindFProperty(ObjectClass, *InPropertyName); - - // Try with FindPropertyByName ?? - if (!OutFoundProperty) - OutFoundProperty = ObjectClass->FindPropertyByName(*InPropertyName); - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - - // Handle common properties nested in classes - // Static Meshes - UStaticMesh* SM = Cast(InObject); - if (SM && !SM->IsPendingKill()) - { - if (SM->BodySetup && FindPropertyOnObject( - SM->BodySetup, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - - if (SM->AssetImportData && FindPropertyOnObject( - SM->AssetImportData, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - - if (SM->NavCollision && FindPropertyOnObject( - SM->NavCollision, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - } - - // For Actors, parse their components - AActor* Actor = Cast(InObject); - if (Actor && !Actor->IsPendingKill()) - { - TArray AllComponents; - Actor->GetComponents(AllComponents, true); - - int32 CompIdx = 0; - for (USceneComponent * SceneComponent : AllComponents) - { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; - - if (FindPropertyOnObject( - SceneComponent, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - } - } - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - -#endif - return false; -} - - -bool -FHoudiniGenericAttribute::TryToFindProperty( - void* InContainer, - UStruct* InStruct, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - bool& bOutPropertyHasBeenFound, - void*& OutContainer) -{ -#if WITH_EDITOR - if (!InContainer) - return false; - - if (!InStruct || InStruct->IsPendingKill()) - return false; - - if (InPropertyName.IsEmpty()) - return false; - - // Iterate manually on the properties, in order to handle StructProperties correctly - for (TFieldIterator PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) - { - FProperty* CurrentProperty = *PropIt; - if (!CurrentProperty) - continue; - - FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = CurrentProperty->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - OutFoundProperty = CurrentProperty; - OutContainer = InContainer; - - // If it's an equality, we dont need to keep searching anymore - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bOutPropertyHasBeenFound = true; - break; - } - } - - // Do a recursive parsing for StructProperties - FStructProperty* StructProperty = CastField(CurrentProperty); - if (StructProperty) - { - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) - continue; - - TryToFindProperty( - StructProperty->ContainerPtrToValuePtr(InContainer, 0), - Struct, - InPropertyName, - OutFoundProperty, - bOutPropertyHasBeenFound, - OutContainer); - } - - if (bOutPropertyHasBeenFound) - break; - } - - if (bOutPropertyHasBeenFound) - return true; - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - -#endif - return false; -} - - -bool -FHoudiniGenericAttribute::ModifyPropertyValueOnObject( - UObject* InObject, - FHoudiniGenericAttribute InGenericAttribute, - FProperty* FoundProperty, - void* InContainer, - const int32& InAtIndex) -{ - if (!InObject || InObject->IsPendingKill() || !FoundProperty) - return false; - - // Determine the container to use (either InContainer if specified, or InObject) - void* Container = InContainer ? InContainer : InObject; - - // Initialize using the found property - FProperty* InnerProperty = FoundProperty; - - AActor* InOwner = Cast(InObject->GetOuter()); - bool bHasModifiedProperty = false; - - - auto OnPropertyChanged = [InObject, InOwner, &bHasModifiedProperty](FProperty* InProperty) - { -#if WITH_EDITOR - FPropertyChangedEvent Evt(InProperty); - InObject->PostEditChangeProperty(Evt); - if (InOwner) - { - // If we are setting properties on an Actor component, we want to notify the - // actor of the changes too since the property change might be handled in the actor's - // PostEditChange callbacks (one such an example occurs when changing the material for a decal actor). - InOwner->PostEditChangeProperty(Evt); - } -#endif - bHasModifiedProperty = true; - }; - - int32 NumberOfProperties = 1; - FArrayProperty* ArrayProperty = CastField(FoundProperty); - if (ArrayProperty) - { - InnerProperty = ArrayProperty->Inner; - NumberOfProperties = ArrayProperty->ArrayDim; - - // Do we need to add values to the array? - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - - //ArrayHelper.ExpandForIndex( InGenericAttribute.AttributeTupleSize - 1 ); - if (InGenericAttribute.AttributeTupleSize > NumberOfProperties) - { - ArrayHelper.Resize(InGenericAttribute.AttributeTupleSize); - NumberOfProperties = InGenericAttribute.AttributeTupleSize; - } - } - - // Get the "proper" AtIndex in the flat array by using the attribute tuple size - // TODO: fix the issue when changing array of tuple properties! - int32 AtIndex = InAtIndex * InGenericAttribute.AttributeTupleSize; - - for (int32 nPropIdx = 0; nPropIdx < NumberOfProperties; nPropIdx++) - { - if (FFloatProperty* FloatProperty = CastField(InnerProperty)) - { - // FLOAT PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - FloatProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(FloatProperty); - } - } - else - { - double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - FloatProperty->SetFloatingPointPropertyValue(ValuePtr, Value); - OnPropertyChanged(FloatProperty); - } - } - } - else if (FIntProperty* IntProperty = CastField(InnerProperty)) - { - // INT PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - IntProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(IntProperty); - } - } - else - { - int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - IntProperty->SetIntPropertyValue(ValuePtr, Value); - OnPropertyChanged(IntProperty); - } - } - } - else if (FBoolProperty* BoolProperty = CastField(InnerProperty)) - { - // BOOL PROPERTY - bool Value = InGenericAttribute.GetBoolValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - BoolProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(BoolProperty); - } - } - else if (FStrProperty* StringProperty = CastField(InnerProperty)) - { - // STRING PROPERTY - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - StringProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(StringProperty); - } - } - else if (FNumericProperty *NumericProperty = CastField(InnerProperty)) - { - // NUMERIC PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - NumericProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(NumericProperty); - } - } - else if (NumericProperty->IsInteger()) - { - int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - NumericProperty->SetIntPropertyValue(ValuePtr, (int64)Value); - OnPropertyChanged(NumericProperty); - } - } - else if (NumericProperty->IsFloatingPoint()) - { - double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - NumericProperty->SetFloatingPointPropertyValue(ValuePtr, Value); - OnPropertyChanged(NumericProperty); - } - } - else - { - // Numeric Property was found, but is of an unsupported type - HOUDINI_LOG_MESSAGE(TEXT("Unsupported Numeric UProperty")); - } - } - else if (FNameProperty* NameProperty = CastField(InnerProperty)) - { - // NAME PROPERTY - FString StringValue = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - FName Value = FName(*StringValue); - - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - NameProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(NameProperty); - } - } - else if (FStructProperty* StructProperty = CastField(InnerProperty)) - { - // STRUCT PROPERTY - const FName PropertyName = StructProperty->Struct->GetFName(); - if (PropertyName == NAME_Vector) - { - FVector* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - // Found a vector property, fill it with the 3 tuple values - PropertyValue->X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); - PropertyValue->Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - PropertyValue->Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - OnPropertyChanged(StructProperty); - } - } - else if (PropertyName == NAME_Transform) - { - FTransform* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - // Found a transform property fill it with the attribute tuple values - FVector Translation; - Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); - Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - - FQuat Rotation; - Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); - Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 4); - Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 5); - Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 6); - - FVector Scale; - Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 7); - Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 8); - Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 9); - - PropertyValue->SetTranslation(Translation); - PropertyValue->SetRotation(Rotation); - PropertyValue->SetScale3D(Scale); - - OnPropertyChanged(StructProperty); - } - } - else if (PropertyName == NAME_Color) - { - FColor* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->R = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - PropertyValue->G = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); - PropertyValue->B = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 2); - if (InGenericAttribute.AttributeTupleSize == 4) - PropertyValue->A = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 3); - - OnPropertyChanged(StructProperty); - } - } - else if (PropertyName == NAME_LinearColor) - { - FLinearColor* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->R = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - PropertyValue->G = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - PropertyValue->B = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - if (InGenericAttribute.AttributeTupleSize == 4) - PropertyValue->A = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); - - OnPropertyChanged(StructProperty); - } - } - else if (PropertyName == "Int32Interval") - { - FInt32Interval* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->Min = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - PropertyValue->Max = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); - - OnPropertyChanged(StructProperty); - } - } - else if (PropertyName == "FloatInterval") - { - FFloatInterval* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - PropertyValue->Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - - OnPropertyChanged(StructProperty); - } - } - } - else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) - { - // OBJECT PATH PROPERTY - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - TSoftObjectPtr ValueObjectPtr; - ValueObjectPtr = Value; - UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); - - // Ensure the ObjectProperty class matches the ValueObject that we just loaded - if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) - { - ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); - OnPropertyChanged(ObjectProperty); - } - } - } - else - { - // Property was found, but is of an unsupported type - FString PropertyClass = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); - HOUDINI_LOG_MESSAGE(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClass, *InGenericAttribute.AttributeName); - return false; - } - } - - if (bHasModifiedProperty) - { -#if WITH_EDITOR - InObject->PostEditChange(); - if (InOwner) - { - InOwner->PostEditChange(); - } -#endif - } - - return true; -} - -/* -bool -FHoudiniEngineUtils::TryToFindInStructProperty( - UObject* Object, FString UPropertyNameToFind, UStructProperty* StructProperty, UProperty*& FoundProperty, void*& StructContainer ) -{ - if ( !StructProperty || !Object ) - return false; - - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - for (TFieldIterator< UProperty > It(Struct); It; ++It) - { - UProperty* Property = *It; - if ( !Property ) - continue; - - FString DisplayName = It->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = It->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - FoundProperty = Property; - StructContainer = StructProperty->ContainerPtrToValuePtr< void >( Object, 0); - - // If it's an equality, we dont need to keep searching - if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) - return true; - } - - if ( FoundProperty ) - continue; - - UStructProperty* NestedStruct = Cast( Property ); - if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) - return true; - - UArrayProperty* ArrayProp = Cast( Property ); - if ( ArrayProp && TryToFindInArrayProperty( Object, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::TryToFindInArrayProperty( - UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ) -{ - if ( !ArrayProperty || !Object ) - return false; - - UProperty* Property = ArrayProperty->Inner; - if ( !Property ) - return false; - - FString DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = Property->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - FoundProperty = Property; - StructContainer = ArrayProperty->ContainerPtrToValuePtr< void >( Object, 0); - - // If it's an equality, we dont need to keep searching - if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) - return true; - } - - if ( !FoundProperty ) - { - UStructProperty* NestedStruct = Cast( Property ); - if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) - return true; - - UArrayProperty* ArrayProp = Cast( Property ); - if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) - return true; - } - - return false; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGenericAttribute.h" + +#include "Engine/StaticMesh.h" +#include "Components/ActorComponent.h" +#include "Components/PrimitiveComponent.h" +#include "Components/StaticMeshComponent.h" + +#include "PhysicsEngine/BodySetup.h" +#include "EditorFramework/AssetImportData.h" +#include "AI/Navigation/NavCollisionBase.h" + +double +FHoudiniGenericAttribute::GetDoubleValue(int32 index) +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return DoubleValues[index]; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return (double)IntValues[index]; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return FCString::Atod(*StringValues[index]); + } + + return 0.0f; +} + +void +FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 index) +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetDoubleValue(index * AttributeTupleSize + n); +} + +int64 +FHoudiniGenericAttribute::GetIntValue(int32 index) +{ + if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return IntValues[index]; + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return (int64)DoubleValues[index]; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return FCString::Atoi64(*StringValues[index]); + } + + return 0; +} + +void +FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetIntValue(index * AttributeTupleSize + n); +} + +FString +FHoudiniGenericAttribute::GetStringValue(int32 index) +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return StringValues[index]; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return FString::FromInt((int32)IntValues[index]); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return FString::SanitizeFloat(DoubleValues[index]); + } + + return FString(); +} + +void +FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 index) +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetStringValue(index * AttributeTupleSize + n); +} + +bool +FHoudiniGenericAttribute::GetBoolValue(int32 index) +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return DoubleValues[index] == 0.0 ? false : true; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return IntValues[index] == 0 ? false : true; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return StringValues[index].Equals(TEXT("true"), ESearchCase::IgnoreCase) ? true : false; + } + + return false; +} + +void +FHoudiniGenericAttribute::GetBoolTuple(TArray& TupleValues, int32 index) +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetBoolValue(index * AttributeTupleSize + n); +} + +void* +FHoudiniGenericAttribute::GetData() +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.Num() > 0) + return StringValues.GetData(); + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.Num() > 0) + return IntValues.GetData(); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.Num() > 0) + return DoubleValues.GetData(); + } + + return nullptr; +} + +bool +FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( + UObject* InObject, FHoudiniGenericAttribute InPropertyAttribute, const int32& AtIndex) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Get the Property name + const FString& PropertyName = InPropertyAttribute.AttributeName; + if (PropertyName.IsEmpty()) + return false; + + // Some Properties need to be handle and modified manually... + if (PropertyName == "CollisionProfileName") + { + UPrimitiveComponent* PC = Cast(InObject); + if (PC && !PC->IsPendingKill()) + { + FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); + FName Value = FName(*StringValue); + PC->SetCollisionProfileName(Value); + + return true; + } + return false; + } + + // Handle Component Tags manually here + if (PropertyName.Contains("Tags")) + { + UActorComponent* AC = Cast< UActorComponent >(InObject); + if (AC && !AC->IsPendingKill()) + { + FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); + if (!AC->ComponentTags.Contains(NameAttr)) + AC->ComponentTags.Add(NameAttr); + /* + for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++) + { + FName NameAttr = FName(*InPropertyAttribute.GetStringValue(nIdx)); + if (!AC->ComponentTags.Contains(NameAttr)) + AC->ComponentTags.Add(NameAttr); + } + */ + return true; + } + return false; + } + + // Try to find the corresponding UProperty + void* OutContainer = nullptr; + FProperty* FoundProperty = nullptr; + UObject* FoundPropertyObject = nullptr; + if (!FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) + return false; + + // Modify the Property we found + if (!ModifyPropertyValueOnObject(FoundPropertyObject, InPropertyAttribute, FoundProperty, OutContainer, AtIndex)) + return false; + + return true; +} + + +bool +FHoudiniGenericAttribute::FindPropertyOnObject( + UObject* InObject, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + UObject*& OutFoundPropertyObject, + void*& OutContainer) +{ +#if WITH_EDITOR + if (!InObject || InObject->IsPendingKill()) + return false; + + if (InPropertyName.IsEmpty()) + return false; + + UClass* ObjectClass = InObject->GetClass(); + if (!ObjectClass || ObjectClass->IsPendingKill()) + return false; + + // Set the result pointer to null + OutContainer = nullptr; + OutFoundProperty = nullptr; + OutFoundPropertyObject = InObject; + + bool bPropertyHasBeenFound = false; + FHoudiniGenericAttribute::TryToFindProperty( + InObject, + ObjectClass, + InPropertyName, + OutFoundProperty, + bPropertyHasBeenFound, + OutContainer); + + /* + // TODO: Parsing needs to be made recursively! + // Iterate manually on the properties, in order to handle StructProperties correctly + for (TFieldIterator PropIt(ObjectClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) + { + FProperty* CurrentProperty = *PropIt; + if (!CurrentProperty) + continue; + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + OutFoundProperty = CurrentProperty; + + // If it's an equality, we dont need to keep searching + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bPropertyHasBeenFound = true; + break; + } + } + + // StructProperty need to be a nested struct + //if (UStructProperty* StructProperty = Cast< UStructProperty >(CurrentProperty)) + // bPropertyHasBeenFound = TryToFindInStructProperty(InObject, InPropertyName, StructProperty, OutFoundProperty, OutStructContainer); + //else if (UArrayProperty* ArrayProperty = Cast< UArrayProperty >(CurrentProperty)) + // bPropertyHasBeenFound = TryToFindInArrayProperty(InObject, InPropertyName, ArrayProperty, OutFoundProperty, OutStructContainer); + + // Handle StructProperty + FStructProperty* StructProperty = CastField(CurrentProperty); + if (StructProperty) + { + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + if (!Struct || Struct->IsPendingKill()) + continue; + + for (TFieldIterator It(Struct); It; ++It) + { + FProperty* Property = *It; + if (!Property) + continue; + + DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + Name = Property->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + OutFoundProperty = Property; + OutStructContainer = StructProperty->ContainerPtrToValuePtr< void >(InObject, 0); + + // If it's an equality, we dont need to keep searching + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bPropertyHasBeenFound = true; + break; + } + } + } + } + + if (bPropertyHasBeenFound) + break; + } + + if (bPropertyHasBeenFound) + return true; + */ + + // Try with FindField?? + if (!OutFoundProperty) + OutFoundProperty = FindFProperty(ObjectClass, *InPropertyName); + + // Try with FindPropertyByName ?? + if (!OutFoundProperty) + OutFoundProperty = ObjectClass->FindPropertyByName(*InPropertyName); + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + + // Handle common properties nested in classes + // Static Meshes + UStaticMesh* SM = Cast(InObject); + if (SM && !SM->IsPendingKill()) + { + if (SM->BodySetup && FindPropertyOnObject( + SM->BodySetup, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + + if (SM->AssetImportData && FindPropertyOnObject( + SM->AssetImportData, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + + if (SM->NavCollision && FindPropertyOnObject( + SM->NavCollision, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + } + + // For Actors, parse their components + AActor* Actor = Cast(InObject); + if (Actor && !Actor->IsPendingKill()) + { + TArray AllComponents; + Actor->GetComponents(AllComponents, true); + + int32 CompIdx = 0; + for (USceneComponent * SceneComponent : AllComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + if (FindPropertyOnObject( + SceneComponent, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + } + } + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + +#endif + return false; +} + + +bool +FHoudiniGenericAttribute::TryToFindProperty( + void* InContainer, + UStruct* InStruct, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + bool& bOutPropertyHasBeenFound, + void*& OutContainer) +{ +#if WITH_EDITOR + if (!InContainer) + return false; + + if (!InStruct || InStruct->IsPendingKill()) + return false; + + if (InPropertyName.IsEmpty()) + return false; + + // Iterate manually on the properties, in order to handle StructProperties correctly + for (TFieldIterator PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) + { + FProperty* CurrentProperty = *PropIt; + if (!CurrentProperty) + continue; + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + OutFoundProperty = CurrentProperty; + OutContainer = InContainer; + + // If it's an equality, we dont need to keep searching anymore + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bOutPropertyHasBeenFound = true; + break; + } + } + + // Do a recursive parsing for StructProperties + FStructProperty* StructProperty = CastField(CurrentProperty); + if (StructProperty) + { + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + if (!Struct || Struct->IsPendingKill()) + continue; + + TryToFindProperty( + StructProperty->ContainerPtrToValuePtr(InContainer, 0), + Struct, + InPropertyName, + OutFoundProperty, + bOutPropertyHasBeenFound, + OutContainer); + } + + if (bOutPropertyHasBeenFound) + break; + } + + if (bOutPropertyHasBeenFound) + return true; + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + +#endif + return false; +} + + +bool +FHoudiniGenericAttribute::ModifyPropertyValueOnObject( + UObject* InObject, + FHoudiniGenericAttribute InGenericAttribute, + FProperty* FoundProperty, + void* InContainer, + const int32& InAtIndex) +{ + if (!InObject || InObject->IsPendingKill() || !FoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Initialize using the found property + FProperty* InnerProperty = FoundProperty; + + AActor* InOwner = Cast(InObject->GetOuter()); + bool bHasModifiedProperty = false; + + + auto OnPropertyChanged = [InObject, InOwner, &bHasModifiedProperty](FProperty* InProperty) + { +#if WITH_EDITOR + FPropertyChangedEvent Evt(InProperty); + InObject->PostEditChangeProperty(Evt); + if (InOwner) + { + // If we are setting properties on an Actor component, we want to notify the + // actor of the changes too since the property change might be handled in the actor's + // PostEditChange callbacks (one such an example occurs when changing the material for a decal actor). + InOwner->PostEditChangeProperty(Evt); + } +#endif + bHasModifiedProperty = true; + }; + + int32 NumberOfProperties = 1; + FArrayProperty* ArrayProperty = CastField(FoundProperty); + if (ArrayProperty) + { + InnerProperty = ArrayProperty->Inner; + NumberOfProperties = ArrayProperty->ArrayDim; + + // Do we need to add values to the array? + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + + //ArrayHelper.ExpandForIndex( InGenericAttribute.AttributeTupleSize - 1 ); + if (InGenericAttribute.AttributeTupleSize > NumberOfProperties) + { + ArrayHelper.Resize(InGenericAttribute.AttributeTupleSize); + NumberOfProperties = InGenericAttribute.AttributeTupleSize; + } + } + + // Get the "proper" AtIndex in the flat array by using the attribute tuple size + // TODO: fix the issue when changing array of tuple properties! + int32 AtIndex = InAtIndex * InGenericAttribute.AttributeTupleSize; + + for (int32 nPropIdx = 0; nPropIdx < NumberOfProperties; nPropIdx++) + { + if (FFloatProperty* FloatProperty = CastField(InnerProperty)) + { + // FLOAT PROPERTY + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + FloatProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); + OnPropertyChanged(FloatProperty); + } + } + else + { + double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + FloatProperty->SetFloatingPointPropertyValue(ValuePtr, Value); + OnPropertyChanged(FloatProperty); + } + } + } + else if (FIntProperty* IntProperty = CastField(InnerProperty)) + { + // INT PROPERTY + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + IntProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); + OnPropertyChanged(IntProperty); + } + } + else + { + int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + IntProperty->SetIntPropertyValue(ValuePtr, Value); + OnPropertyChanged(IntProperty); + } + } + } + else if (FBoolProperty* BoolProperty = CastField(InnerProperty)) + { + // BOOL PROPERTY + bool Value = InGenericAttribute.GetBoolValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + BoolProperty->SetPropertyValue(ValuePtr, Value); + OnPropertyChanged(BoolProperty); + } + } + else if (FStrProperty* StringProperty = CastField(InnerProperty)) + { + // STRING PROPERTY + FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + StringProperty->SetPropertyValue(ValuePtr, Value); + OnPropertyChanged(StringProperty); + } + } + else if (FNumericProperty *NumericProperty = CastField(InnerProperty)) + { + // NUMERIC PROPERTY + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + NumericProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); + OnPropertyChanged(NumericProperty); + } + } + else if (NumericProperty->IsInteger()) + { + int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + NumericProperty->SetIntPropertyValue(ValuePtr, (int64)Value); + OnPropertyChanged(NumericProperty); + } + } + else if (NumericProperty->IsFloatingPoint()) + { + double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + NumericProperty->SetFloatingPointPropertyValue(ValuePtr, Value); + OnPropertyChanged(NumericProperty); + } + } + else + { + // Numeric Property was found, but is of an unsupported type + HOUDINI_LOG_MESSAGE(TEXT("Unsupported Numeric UProperty")); + } + } + else if (FNameProperty* NameProperty = CastField(InnerProperty)) + { + // NAME PROPERTY + FString StringValue = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + FName Value = FName(*StringValue); + + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + NameProperty->SetPropertyValue(ValuePtr, Value); + OnPropertyChanged(NameProperty); + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // STRUCT PROPERTY + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + FVector* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + // Found a vector property, fill it with the 3 tuple values + PropertyValue->X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); + PropertyValue->Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); + PropertyValue->Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); + OnPropertyChanged(StructProperty); + } + } + else if (PropertyName == NAME_Transform) + { + FTransform* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + // Found a transform property fill it with the attribute tuple values + FVector Translation; + Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); + Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); + Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); + + FQuat Rotation; + Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); + Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 4); + Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 5); + Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 6); + + FVector Scale; + Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 7); + Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 8); + Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 9); + + PropertyValue->SetTranslation(Translation); + PropertyValue->SetRotation(Rotation); + PropertyValue->SetScale3D(Scale); + + OnPropertyChanged(StructProperty); + } + } + else if (PropertyName == NAME_Color) + { + FColor* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + PropertyValue->R = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); + PropertyValue->G = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); + PropertyValue->B = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 2); + if (InGenericAttribute.AttributeTupleSize == 4) + PropertyValue->A = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 3); + + OnPropertyChanged(StructProperty); + } + } + else if (PropertyName == NAME_LinearColor) + { + FLinearColor* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + PropertyValue->R = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); + PropertyValue->G = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); + PropertyValue->B = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); + if (InGenericAttribute.AttributeTupleSize == 4) + PropertyValue->A = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); + + OnPropertyChanged(StructProperty); + } + } + else if (PropertyName == "Int32Interval") + { + FInt32Interval* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + PropertyValue->Min = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); + PropertyValue->Max = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); + + OnPropertyChanged(StructProperty); + } + } + else if (PropertyName == "FloatInterval") + { + FFloatInterval* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + PropertyValue->Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); + PropertyValue->Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); + + OnPropertyChanged(StructProperty); + } + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + // OBJECT PATH PROPERTY + FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + TSoftObjectPtr ValueObjectPtr; + ValueObjectPtr = Value; + UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); + + // Ensure the ObjectProperty class matches the ValueObject that we just loaded + if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) + { + ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); + OnPropertyChanged(ObjectProperty); + } + } + } + else + { + // Property was found, but is of an unsupported type + FString PropertyClass = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); + HOUDINI_LOG_MESSAGE(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClass, *InGenericAttribute.AttributeName); + return false; + } + } + + if (bHasModifiedProperty) + { +#if WITH_EDITOR + InObject->PostEditChange(); + if (InOwner) + { + InOwner->PostEditChange(); + } +#endif + } + + return true; +} + +/* +bool +FHoudiniEngineUtils::TryToFindInStructProperty( + UObject* Object, FString UPropertyNameToFind, UStructProperty* StructProperty, UProperty*& FoundProperty, void*& StructContainer ) +{ + if ( !StructProperty || !Object ) + return false; + + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + for (TFieldIterator< UProperty > It(Struct); It; ++It) + { + UProperty* Property = *It; + if ( !Property ) + continue; + + FString DisplayName = It->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = It->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = StructProperty->ContainerPtrToValuePtr< void >( Object, 0); + + // If it's an equality, we dont need to keep searching + if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) + return true; + } + + if ( FoundProperty ) + continue; + + UStructProperty* NestedStruct = Cast( Property ); + if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) + return true; + + UArrayProperty* ArrayProp = Cast( Property ); + if ( ArrayProp && TryToFindInArrayProperty( Object, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::TryToFindInArrayProperty( + UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ) +{ + if ( !ArrayProperty || !Object ) + return false; + + UProperty* Property = ArrayProperty->Inner; + if ( !Property ) + return false; + + FString DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = Property->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = ArrayProperty->ContainerPtrToValuePtr< void >( Object, 0); + + // If it's an equality, we dont need to keep searching + if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) + return true; + } + + if ( !FoundProperty ) + { + UStructProperty* NestedStruct = Cast( Property ); + if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) + return true; + + UArrayProperty* ArrayProp = Cast( Property ); + if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) + return true; + } + + return false; +} */ \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h index d59071484..778ef5fba 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h @@ -1,123 +1,123 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniGenericAttribute.generated.h" - -UENUM() -enum class EAttribStorageType : int8 -{ - Invalid = -1, - - INT = 0, - INT64 = 1, - FLOAT = 2, - FLOAT64 = 3, - STRING = 4 -}; - -UENUM() -enum class EAttribOwner : int8 -{ - Invalid = -1, - - Vertex, - Point, - Prim, - Detail, -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY() - FString AttributeName; - - UPROPERTY() - EAttribStorageType AttributeType; - UPROPERTY() - EAttribOwner AttributeOwner; - - UPROPERTY() - int32 AttributeCount; - UPROPERTY() - int32 AttributeTupleSize; - - UPROPERTY() - TArray DoubleValues; - UPROPERTY() - TArray IntValues; - UPROPERTY() - TArray StringValues; - - double GetDoubleValue(int32 index = 0); - void GetDoubleTuple(TArray& TupleValues, int32 index = 0); - - int64 GetIntValue(int32 index = 0); - void GetIntTuple(TArray& TupleValues, int32 index = 0); - - FString GetStringValue(int32 index = 0); - void GetStringTuple(TArray& TupleValues, int32 index = 0); - - bool GetBoolValue(int32 index = 0); - void GetBoolTuple(TArray& TupleValues, int32 index = 0); - - void* GetData(); - - // - static bool UpdatePropertyAttributeOnObject( - UObject* InObject, FHoudiniGenericAttribute InPropertyAttribute, const int32& AtIndex = 0); - - // Tries to find a Uproperty by name/label on an object - // FoundPropertyObject will be the object that actually contains the property - // and can be different from InObject if the property is nested. - static bool FindPropertyOnObject( - UObject* InObject, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - UObject*& OutFoundPropertyObject, - void*& OutContainer); - - // Modifies the value of a found Property - static bool ModifyPropertyValueOnObject( - UObject* InObject, - FHoudiniGenericAttribute InGenericAttribute, - FProperty* FoundProperty, - void* InContainer, - const int32& AtIndex = 0 ); - - // Recursive search for a given property on a UObject - static bool TryToFindProperty( - void* InContainer, - UStruct* InStruct, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - bool& bOutPropertyHasBeenFound, - void*& OutContainer); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniGenericAttribute.generated.h" + +UENUM() +enum class EAttribStorageType : int8 +{ + Invalid = -1, + + INT = 0, + INT64 = 1, + FLOAT = 2, + FLOAT64 = 3, + STRING = 4 +}; + +UENUM() +enum class EAttribOwner : int8 +{ + Invalid = -1, + + Vertex, + Point, + Prim, + Detail, +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY() + FString AttributeName; + + UPROPERTY() + EAttribStorageType AttributeType; + UPROPERTY() + EAttribOwner AttributeOwner; + + UPROPERTY() + int32 AttributeCount; + UPROPERTY() + int32 AttributeTupleSize; + + UPROPERTY() + TArray DoubleValues; + UPROPERTY() + TArray IntValues; + UPROPERTY() + TArray StringValues; + + double GetDoubleValue(int32 index = 0); + void GetDoubleTuple(TArray& TupleValues, int32 index = 0); + + int64 GetIntValue(int32 index = 0); + void GetIntTuple(TArray& TupleValues, int32 index = 0); + + FString GetStringValue(int32 index = 0); + void GetStringTuple(TArray& TupleValues, int32 index = 0); + + bool GetBoolValue(int32 index = 0); + void GetBoolTuple(TArray& TupleValues, int32 index = 0); + + void* GetData(); + + // + static bool UpdatePropertyAttributeOnObject( + UObject* InObject, FHoudiniGenericAttribute InPropertyAttribute, const int32& AtIndex = 0); + + // Tries to find a Uproperty by name/label on an object + // FoundPropertyObject will be the object that actually contains the property + // and can be different from InObject if the property is nested. + static bool FindPropertyOnObject( + UObject* InObject, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + UObject*& OutFoundPropertyObject, + void*& OutContainer); + + // Modifies the value of a found Property + static bool ModifyPropertyValueOnObject( + UObject* InObject, + FHoudiniGenericAttribute InGenericAttribute, + FProperty* FoundProperty, + void* InContainer, + const int32& AtIndex = 0 ); + + // Recursive search for a given property on a UObject + static bool TryToFindProperty( + void* InContainer, + UStruct* InStruct, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + bool& bOutPropertyHasBeenFound, + void*& OutContainer); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp index c5fdea932..8a63839cc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp @@ -1,25 +1,25 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h b/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h index 6830f53b3..2853e8148 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h @@ -1,28 +1,28 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp index a8ee8dc58..bd9cb8ebf 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp @@ -1,185 +1,185 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoPartObject.h" - -// -FHoudiniGeoPartObject::FHoudiniGeoPartObject() - : AssetId(-1) - , AssetName(TEXT("")) - , ObjectId(-1) - , ObjectName(TEXT("")) - , GeoId(-1) - , PartId(-1) - , PartName(TEXT("")) - , bHasCustomPartName(false) - , TransformMatrix(FMatrix::Identity) - , NodePath(TEXT("")) - , Type(EHoudiniPartType::Invalid) - , InstancerType(EHoudiniInstancerType::Invalid) - , VolumeName(TEXT("")) - , VolumeTileIndex(-1) - , bIsVisible(false) - , bIsEditable(false) - , bIsTemplated(false) - , bIsInstanced(false) - , bHasGeoChanged(true) - , bHasPartChanged(true) - , bHasTransformChanged(true) - , bHasMaterialsChanged(true) - , bLoaded(false) -{ - -} - -bool -FHoudiniGeoPartObject::IsValid() const -{ - return (ObjectId >= 0 && GeoId >= 0 && PartId >= 0); -} - -bool -FHoudiniGeoPartObject::operator==(const FHoudiniGeoPartObject & InGeoPartObject) const -{ - // TODO: split?? - return Equals(InGeoPartObject, true); -} - -bool -FHoudiniGeoPartObject::Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const -{ - // TODO: This will likely need some improvement! - - /* - // Object/Geo/Part IDs must match - if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) - return false; - - if (!bIgnoreSplit) - { - // If the split type and index match, we're equal... - if (SplitType == GeoPartObject.SplitType && SplitIndex == GeoPartObject.SplitIndex) - return true; - - // ... if not we should compare our names - return CompareNames(GeoPartObject, bIgnoreSplit); - } - */ - - // See if objects / geo / part ids match - bool MatchingIDs = true; - if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) - MatchingIDs = false; - - // See if the type matches - bool MatchingType = (Type == GeoPartObject.Type); - // Both IDs and type match, consider the two HGPO as equals - if (MatchingIDs && MatchingType) - return true; - - // Both IDs and type do not match, consider the two HGPOs as different - if (!MatchingIDs && !MatchingType) - return false; - - // If only the ID dont match we can do some further checks - - // If one of the two HGPO has been loaded - if ((bLoaded && !GeoPartObject.bLoaded) - || (!bLoaded && GeoPartObject.bLoaded)) - { - // For loaded HGPOs, part names should be a sufficent comparison - if (PartName.Equals(GeoPartObject.PartName)) - return true; - } - - // TODO: This was causing issues somehow with tiled landscapes - // ... if not, compare by names - if(!MatchingIDs) - return CompareNames(GeoPartObject, bIgnoreSplit); - - return false; -} - -void -FHoudiniGeoPartObject::SetCustomPartName(const FString & InName) -{ - if (InName.IsEmpty()) - return; - - PartName = InName; - bHasCustomPartName = true; -} - -bool -FHoudiniGeoPartObject::CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const -{ - //TODO: AssetName? - - // Object, part and split names must match - if (!ObjectName.Equals(HoudiniGeoPartObject.ObjectName) - || !PartName.Equals(HoudiniGeoPartObject.PartName)) - { - return false; - } - - /* - // Split should also match if we dont ignore it - if (!bIgnoreSplit && !SplitName.Equals(HoudiniGeoPartObject.SplitName)) - { - return false; - } - */ - - return true; -} - -FString -FHoudiniGeoPartObject::HoudiniPartTypeToString(const EHoudiniPartType& InType) -{ - FString OutTypeStr; - switch (InType) - { - case EHoudiniPartType::Mesh: - OutTypeStr = TEXT("Mesh"); - break; - case EHoudiniPartType::Instancer: - OutTypeStr = TEXT("Instancer"); - break; - case EHoudiniPartType::Curve: - OutTypeStr = TEXT("Curve"); - break; - case EHoudiniPartType::Volume: - OutTypeStr = TEXT("Volume"); - break; - - default: - case EHoudiniPartType::Invalid: - OutTypeStr = TEXT("Invalid"); - break; - } - - return OutTypeStr; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoPartObject.h" + +// +FHoudiniGeoPartObject::FHoudiniGeoPartObject() + : AssetId(-1) + , AssetName(TEXT("")) + , ObjectId(-1) + , ObjectName(TEXT("")) + , GeoId(-1) + , PartId(-1) + , PartName(TEXT("")) + , bHasCustomPartName(false) + , TransformMatrix(FMatrix::Identity) + , NodePath(TEXT("")) + , Type(EHoudiniPartType::Invalid) + , InstancerType(EHoudiniInstancerType::Invalid) + , VolumeName(TEXT("")) + , VolumeTileIndex(-1) + , bIsVisible(false) + , bIsEditable(false) + , bIsTemplated(false) + , bIsInstanced(false) + , bHasGeoChanged(true) + , bHasPartChanged(true) + , bHasTransformChanged(true) + , bHasMaterialsChanged(true) + , bLoaded(false) +{ + +} + +bool +FHoudiniGeoPartObject::IsValid() const +{ + return (ObjectId >= 0 && GeoId >= 0 && PartId >= 0); +} + +bool +FHoudiniGeoPartObject::operator==(const FHoudiniGeoPartObject & InGeoPartObject) const +{ + // TODO: split?? + return Equals(InGeoPartObject, true); +} + +bool +FHoudiniGeoPartObject::Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const +{ + // TODO: This will likely need some improvement! + + /* + // Object/Geo/Part IDs must match + if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) + return false; + + if (!bIgnoreSplit) + { + // If the split type and index match, we're equal... + if (SplitType == GeoPartObject.SplitType && SplitIndex == GeoPartObject.SplitIndex) + return true; + + // ... if not we should compare our names + return CompareNames(GeoPartObject, bIgnoreSplit); + } + */ + + // See if objects / geo / part ids match + bool MatchingIDs = true; + if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) + MatchingIDs = false; + + // See if the type matches + bool MatchingType = (Type == GeoPartObject.Type); + // Both IDs and type match, consider the two HGPO as equals + if (MatchingIDs && MatchingType) + return true; + + // Both IDs and type do not match, consider the two HGPOs as different + if (!MatchingIDs && !MatchingType) + return false; + + // If only the ID dont match we can do some further checks + + // If one of the two HGPO has been loaded + if ((bLoaded && !GeoPartObject.bLoaded) + || (!bLoaded && GeoPartObject.bLoaded)) + { + // For loaded HGPOs, part names should be a sufficent comparison + if (PartName.Equals(GeoPartObject.PartName)) + return true; + } + + // TODO: This was causing issues somehow with tiled landscapes + // ... if not, compare by names + if(!MatchingIDs) + return CompareNames(GeoPartObject, bIgnoreSplit); + + return false; +} + +void +FHoudiniGeoPartObject::SetCustomPartName(const FString & InName) +{ + if (InName.IsEmpty()) + return; + + PartName = InName; + bHasCustomPartName = true; +} + +bool +FHoudiniGeoPartObject::CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const +{ + //TODO: AssetName? + + // Object, part and split names must match + if (!ObjectName.Equals(HoudiniGeoPartObject.ObjectName) + || !PartName.Equals(HoudiniGeoPartObject.PartName)) + { + return false; + } + + /* + // Split should also match if we dont ignore it + if (!bIgnoreSplit && !SplitName.Equals(HoudiniGeoPartObject.SplitName)) + { + return false; + } + */ + + return true; +} + +FString +FHoudiniGeoPartObject::HoudiniPartTypeToString(const EHoudiniPartType& InType) +{ + FString OutTypeStr; + switch (InType) + { + case EHoudiniPartType::Mesh: + OutTypeStr = TEXT("Mesh"); + break; + case EHoudiniPartType::Instancer: + OutTypeStr = TEXT("Instancer"); + break; + case EHoudiniPartType::Curve: + OutTypeStr = TEXT("Curve"); + break; + case EHoudiniPartType::Volume: + OutTypeStr = TEXT("Volume"); + break; + + default: + case EHoudiniPartType::Invalid: + OutTypeStr = TEXT("Invalid"); + break; + } + + return OutTypeStr; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h index 4209e8240..5255c4d99 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h @@ -1,419 +1,419 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniGeoPartObject.generated.h" - -UENUM() -enum class EHoudiniGeoType : uint8 -{ - Invalid, - - Default, - Intermediate, - Input, - Curve -}; - -UENUM() -enum class EHoudiniPartType : uint8 -{ - Invalid, - - Mesh, - Instancer, - Curve, - Volume -}; - -UENUM() -enum class EHoudiniInstancerType : uint8 -{ - Invalid, - - ObjectInstancer, - PackedPrimitive, - AttributeInstancer, - OldSchoolAttributeInstancer -}; - -UENUM() -enum class EHoudiniCurveType : int8 -{ - Invalid = -1, - - Polygon = 0, - Nurbs = 1, - Bezier = 2, - Points = 3 -}; - -UENUM() -enum class EHoudiniCurveMethod : int8 -{ - Invalid = -1, - - CVs = 0, - Breakpoints = 1, - Freehand = 2 -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniObjectInfo -{ - GENERATED_USTRUCT_BODY() - - FString Name = TEXT(""); - - int32 NodeId = -1; - int32 ObjectToInstanceID = -1; - - bool bHasTransformChanged = false; - bool bHaveGeosChanged = false; - bool bIsVisible = false; - bool bIsInstancer = false; - bool bIsInstanced = false; - - int32 GeoCount = -1; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGeoInfo -{ - GENERATED_USTRUCT_BODY() - - EHoudiniGeoType Type = EHoudiniGeoType::Invalid; - FString Name = TEXT(""); - int32 NodeId = -1; - - bool bIsEditable = false; - bool bIsTemplated = false; - bool bIsDisplayGeo = false; - bool bHasGeoChanged = false; - bool bHasMaterialChanged = false; - - int32 PartCount = -1; - int32 PointGroupCount = -1; - int32 PrimitiveGroupCount = -1; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniPartInfo -{ - GENERATED_USTRUCT_BODY() - - int32 PartId = -1; - FString Name = TEXT(""); - - EHoudiniPartType Type = EHoudiniPartType::Invalid; - - int32 FaceCount = -1; - int32 VertexCount = -1; - int32 PointCount = -1; - - int32 PointAttributeCounts = -1; - int32 VertexAttributeCounts = -1; - int32 PrimitiveAttributeCounts = -1; - int32 DetailAttributeCounts = -1; - - bool bIsInstanced = false; - - int32 InstancedPartCount = -1; - int32 InstanceCount = -1; - - bool bHasChanged = false; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniVolumeInfo -{ - GENERATED_USTRUCT_BODY() - - FString Name = TEXT(""); - bool bIsVDB = false; // replaces VolumeType Type; - - int32 TupleSize = -1; - bool bIsFloat = false; // replaces StorageType StorageType; - int32 TileSize = -1; - - FTransform Transform = FTransform::Identity; - bool bHasTaper = false; - - int32 XLength = -1; - int32 YLength = -1; - int32 ZLength = -1; - - int32 MinX = -1; - int32 MinY = -1; - int32 MinZ = -1; - - float XTaper = 0.0f; - float YTaper = 0.0f; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniCurveInfo -{ - GENERATED_USTRUCT_BODY() - - EHoudiniCurveType Type = EHoudiniCurveType::Invalid; - - int32 CurveCount = -1; - int32 VertexCount = -1; - int32 KnotCount = -1; - - bool bIsPeriodic = false; - bool bIsRational = false; - - int32 Order = -1; - - bool bHasKnots = false; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniMeshSocket -{ - GENERATED_USTRUCT_BODY() - - // Equality operator, used by containers - bool operator==(const FHoudiniMeshSocket& InSocket) const - { - return Transform.Equals(InSocket.Transform) - && Name == InSocket.Name - && Actor == InSocket.Actor - && Tag == InSocket.Tag; - } - - // Members - FTransform Transform = FTransform::Identity; - FString Name = TEXT("Socket"); - FString Actor = FString(); - FString Tag = FString(); -}; - -/* -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniSplitDataCache -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY() - FString SplitName; - //UPROPERTY() - //FHoudiniOutputObjectIdentifier SplitIdentifier; - //EHoudiniSplitType SplitType; - - UPROPERTY() - TArray Positions; - UPROPERTY() - TArray Indices; - - UPROPERTY() - TArray Normals; - UPROPERTY() - TArray Tangents; - UPROPERTY() - TArray Binormals; - - UPROPERTY() - TArray Colors; - - //UPROPERTY() - //TArray> UVs; - - //TArray EdgeHardnesses; - UPROPERTY() - TArray FaceSmoothingMasks; - UPROPERTY() - TArray LightMapResolutions; - - UPROPERTY() - TArray MaterialIndices; - UPROPERTY() - TArray Materials; - - UPROPERTY() - float lod_screensize; - - UPROPERTY() - FKAggregateGeom AggregateCollisions; -}; -*/ - - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject -{ -public: - - GENERATED_USTRUCT_BODY() - - FHoudiniGeoPartObject(); - - // Indicates if this HGPO is valid - bool IsValid() const; - - // Equality operator, used by containers - bool operator==(const FHoudiniGeoPartObject & GeoPartObject) const; - - // Checks equality, with the possibility to ignore the HGPO's splits - bool Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const; - - // Comparison based on object/part/split name. - bool CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const; - - void SetCustomPartName(const FString & InName); - - static FString HoudiniPartTypeToString(const EHoudiniPartType& InType); - -public: - - // NodeId of corresponding HAPI Asset. - UPROPERTY() - int32 AssetId; - - // Name of corresponding HDA. - UPROPERTY() - FString AssetName; - - // NodeId of corresponding HAPI Object. - UPROPERTY() - int32 ObjectId; - - // Name of associated object. - UPROPERTY() - FString ObjectName; - - // NodeId of corresponding HAPI Geo. - UPROPERTY() - int32 GeoId; - - // PartId of corresponding HAPI Part. - UPROPERTY() - int32 PartId; - - // Name of associated part. - UPROPERTY() - FString PartName; - - UPROPERTY() - bool bHasCustomPartName; - - /* - // Type of the split. - UPROPERTY() - EHoudiniSplitType SplitType; - - // Index of a split. In most cases this will be 0. - UPROPERTY() - int32 SplitIndex; - - // Name of group which was used for splitting, empty if there's none. - UPROPERTY() - FString SplitName; - */ - - // Split groups handled by this HGPO - UPROPERTY() - TArray SplitGroups; - - // Transform of this geo part object. - UPROPERTY() - FTransform TransformMatrix; - - // Path to the corresponding node - UPROPERTY() - FString NodePath; - - // Indicates the type of the referenced object - UPROPERTY() - EHoudiniPartType Type; - - // Indicates the type of instancer - UPROPERTY() - EHoudiniInstancerType InstancerType; - - // - UPROPERTY() - FString VolumeName; - - // - UPROPERTY() - int32 VolumeTileIndex; - - // Is set to true when referenced object is visible. - UPROPERTY() - bool bIsVisible; - - // Is set to true when referenced object is editable. - UPROPERTY() - bool bIsEditable; - - // Is set to true when referenced object is templated. - UPROPERTY() - bool bIsTemplated; - - // Is set to true when the referenced object is instanced. - UPROPERTY() - bool bIsInstanced; - - // Indicates the parent geo has changed and needs to be rebuilt - UPROPERTY() - bool bHasGeoChanged; - - // Indicates the part has changed and needs to be rebuilt - UPROPERTY() - bool bHasPartChanged; - - // Indicates only the transform needs to be updated - UPROPERTY() - bool bHasTransformChanged; - - // Indicates only the material needs to be updated - UPROPERTY() - bool bHasMaterialsChanged; - - // Indicates this object has been loaded - bool bLoaded; - - // We also keep a cache of the various info objects - // That we've extracted from HAPI - - // ObjectInfo cache - FHoudiniObjectInfo ObjectInfo; - // GeoInfo cache - FHoudiniGeoInfo GeoInfo; - // PartInfo cache - FHoudiniPartInfo PartInfo; - // VolumeInfo cache - FHoudiniVolumeInfo VolumeInfo; - // CurveInfo cache - FHoudiniCurveInfo CurveInfo; - - // Cache of this HGPO split data - //TArray SplitCache; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniGeoPartObject.generated.h" + +UENUM() +enum class EHoudiniGeoType : uint8 +{ + Invalid, + + Default, + Intermediate, + Input, + Curve +}; + +UENUM() +enum class EHoudiniPartType : uint8 +{ + Invalid, + + Mesh, + Instancer, + Curve, + Volume +}; + +UENUM() +enum class EHoudiniInstancerType : uint8 +{ + Invalid, + + ObjectInstancer, + PackedPrimitive, + AttributeInstancer, + OldSchoolAttributeInstancer +}; + +UENUM() +enum class EHoudiniCurveType : int8 +{ + Invalid = -1, + + Polygon = 0, + Nurbs = 1, + Bezier = 2, + Points = 3 +}; + +UENUM() +enum class EHoudiniCurveMethod : int8 +{ + Invalid = -1, + + CVs = 0, + Breakpoints = 1, + Freehand = 2 +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniObjectInfo +{ + GENERATED_USTRUCT_BODY() + + FString Name = TEXT(""); + + int32 NodeId = -1; + int32 ObjectToInstanceID = -1; + + bool bHasTransformChanged = false; + bool bHaveGeosChanged = false; + bool bIsVisible = false; + bool bIsInstancer = false; + bool bIsInstanced = false; + + int32 GeoCount = -1; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGeoInfo +{ + GENERATED_USTRUCT_BODY() + + EHoudiniGeoType Type = EHoudiniGeoType::Invalid; + FString Name = TEXT(""); + int32 NodeId = -1; + + bool bIsEditable = false; + bool bIsTemplated = false; + bool bIsDisplayGeo = false; + bool bHasGeoChanged = false; + bool bHasMaterialChanged = false; + + int32 PartCount = -1; + int32 PointGroupCount = -1; + int32 PrimitiveGroupCount = -1; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniPartInfo +{ + GENERATED_USTRUCT_BODY() + + int32 PartId = -1; + FString Name = TEXT(""); + + EHoudiniPartType Type = EHoudiniPartType::Invalid; + + int32 FaceCount = -1; + int32 VertexCount = -1; + int32 PointCount = -1; + + int32 PointAttributeCounts = -1; + int32 VertexAttributeCounts = -1; + int32 PrimitiveAttributeCounts = -1; + int32 DetailAttributeCounts = -1; + + bool bIsInstanced = false; + + int32 InstancedPartCount = -1; + int32 InstanceCount = -1; + + bool bHasChanged = false; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniVolumeInfo +{ + GENERATED_USTRUCT_BODY() + + FString Name = TEXT(""); + bool bIsVDB = false; // replaces VolumeType Type; + + int32 TupleSize = -1; + bool bIsFloat = false; // replaces StorageType StorageType; + int32 TileSize = -1; + + FTransform Transform = FTransform::Identity; + bool bHasTaper = false; + + int32 XLength = -1; + int32 YLength = -1; + int32 ZLength = -1; + + int32 MinX = -1; + int32 MinY = -1; + int32 MinZ = -1; + + float XTaper = 0.0f; + float YTaper = 0.0f; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniCurveInfo +{ + GENERATED_USTRUCT_BODY() + + EHoudiniCurveType Type = EHoudiniCurveType::Invalid; + + int32 CurveCount = -1; + int32 VertexCount = -1; + int32 KnotCount = -1; + + bool bIsPeriodic = false; + bool bIsRational = false; + + int32 Order = -1; + + bool bHasKnots = false; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniMeshSocket +{ + GENERATED_USTRUCT_BODY() + + // Equality operator, used by containers + bool operator==(const FHoudiniMeshSocket& InSocket) const + { + return Transform.Equals(InSocket.Transform) + && Name == InSocket.Name + && Actor == InSocket.Actor + && Tag == InSocket.Tag; + } + + // Members + FTransform Transform = FTransform::Identity; + FString Name = TEXT("Socket"); + FString Actor = FString(); + FString Tag = FString(); +}; + +/* +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniSplitDataCache +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY() + FString SplitName; + //UPROPERTY() + //FHoudiniOutputObjectIdentifier SplitIdentifier; + //EHoudiniSplitType SplitType; + + UPROPERTY() + TArray Positions; + UPROPERTY() + TArray Indices; + + UPROPERTY() + TArray Normals; + UPROPERTY() + TArray Tangents; + UPROPERTY() + TArray Binormals; + + UPROPERTY() + TArray Colors; + + //UPROPERTY() + //TArray> UVs; + + //TArray EdgeHardnesses; + UPROPERTY() + TArray FaceSmoothingMasks; + UPROPERTY() + TArray LightMapResolutions; + + UPROPERTY() + TArray MaterialIndices; + UPROPERTY() + TArray Materials; + + UPROPERTY() + float lod_screensize; + + UPROPERTY() + FKAggregateGeom AggregateCollisions; +}; +*/ + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject +{ +public: + + GENERATED_USTRUCT_BODY() + + FHoudiniGeoPartObject(); + + // Indicates if this HGPO is valid + bool IsValid() const; + + // Equality operator, used by containers + bool operator==(const FHoudiniGeoPartObject & GeoPartObject) const; + + // Checks equality, with the possibility to ignore the HGPO's splits + bool Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const; + + // Comparison based on object/part/split name. + bool CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const; + + void SetCustomPartName(const FString & InName); + + static FString HoudiniPartTypeToString(const EHoudiniPartType& InType); + +public: + + // NodeId of corresponding HAPI Asset. + UPROPERTY() + int32 AssetId; + + // Name of corresponding HDA. + UPROPERTY() + FString AssetName; + + // NodeId of corresponding HAPI Object. + UPROPERTY() + int32 ObjectId; + + // Name of associated object. + UPROPERTY() + FString ObjectName; + + // NodeId of corresponding HAPI Geo. + UPROPERTY() + int32 GeoId; + + // PartId of corresponding HAPI Part. + UPROPERTY() + int32 PartId; + + // Name of associated part. + UPROPERTY() + FString PartName; + + UPROPERTY() + bool bHasCustomPartName; + + /* + // Type of the split. + UPROPERTY() + EHoudiniSplitType SplitType; + + // Index of a split. In most cases this will be 0. + UPROPERTY() + int32 SplitIndex; + + // Name of group which was used for splitting, empty if there's none. + UPROPERTY() + FString SplitName; + */ + + // Split groups handled by this HGPO + UPROPERTY() + TArray SplitGroups; + + // Transform of this geo part object. + UPROPERTY() + FTransform TransformMatrix; + + // Path to the corresponding node + UPROPERTY() + FString NodePath; + + // Indicates the type of the referenced object + UPROPERTY() + EHoudiniPartType Type; + + // Indicates the type of instancer + UPROPERTY() + EHoudiniInstancerType InstancerType; + + // + UPROPERTY() + FString VolumeName; + + // + UPROPERTY() + int32 VolumeTileIndex; + + // Is set to true when referenced object is visible. + UPROPERTY() + bool bIsVisible; + + // Is set to true when referenced object is editable. + UPROPERTY() + bool bIsEditable; + + // Is set to true when referenced object is templated. + UPROPERTY() + bool bIsTemplated; + + // Is set to true when the referenced object is instanced. + UPROPERTY() + bool bIsInstanced; + + // Indicates the parent geo has changed and needs to be rebuilt + UPROPERTY() + bool bHasGeoChanged; + + // Indicates the part has changed and needs to be rebuilt + UPROPERTY() + bool bHasPartChanged; + + // Indicates only the transform needs to be updated + UPROPERTY() + bool bHasTransformChanged; + + // Indicates only the material needs to be updated + UPROPERTY() + bool bHasMaterialsChanged; + + // Indicates this object has been loaded + bool bLoaded; + + // We also keep a cache of the various info objects + // That we've extracted from HAPI + + // ObjectInfo cache + FHoudiniObjectInfo ObjectInfo; + // GeoInfo cache + FHoudiniGeoInfo GeoInfo; + // PartInfo cache + FHoudiniPartInfo PartInfo; + // VolumeInfo cache + FHoudiniVolumeInfo VolumeInfo; + // CurveInfo cache + FHoudiniCurveInfo CurveInfo; + + // Cache of this HGPO split data + //TArray SplitCache; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp index 2b81de958..d306f8808 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp @@ -1,253 +1,253 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleComponent.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniRuntimeSettings.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -void -UHoudiniHandleComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniHandleComponent_V1* CompatibilityHC = NewObject(); - CompatibilityHC->Serialize(Ar); - CompatibilityHC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : serialized data will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -UHoudiniHandleParameter::UHoudiniHandleParameter(const FObjectInitializer & ObjectInitializer) - :Super(ObjectInitializer) -{}; - -UHoudiniHandleComponent::UHoudiniHandleComponent(const FObjectInitializer & ObjectInitializer) - :Super(ObjectInitializer) -{}; - - -bool -UHoudiniHandleParameter::Bind(float & OutValue, const char * CmpName, int32 InTupleIdx, - const FString & HandleParmName, UHoudiniParameter* Parameter) -{ - if (!Parameter) - return false; - - if (HandleParmName != CmpName) - return false; - - UHoudiniParameterFloat* FloatParameter = Cast(Parameter); - - if (!FloatParameter) - return false; - - AssetParameter = Parameter; - - if (FloatParameter) - { - // It is possible that the handle param is bound to a single tuple param. - // Ignore the preset tuple index if that's the case or we'll crash. - if (Parameter->GetTupleSize() <= InTupleIdx) - InTupleIdx = 0; - - auto Optional = FloatParameter->GetValue(InTupleIdx); - if (Optional.IsSet()) - { - TupleIndex = InTupleIdx; - OutValue = Optional.GetValue(); - return true; - } - } - - return false; -} - -bool -UHoudiniHandleParameter::Bind(TSharedPtr & OutValue, const char * CmpName, - int32 InTupleIdx, const FString & HandleParmName, UHoudiniParameter* Parameter) -{ - if (!Parameter) - return false; - - if (HandleParmName != CmpName) - return false; - - UHoudiniParameterChoice* ChoiceParameter = Cast(Parameter); - - if (!ChoiceParameter) - return false; - - AssetParameter = Parameter; - - if (ChoiceParameter) - { - // It is possible that the handle param is bound to a single tuple param. - // Ignore the preset tuple index if that's the case or we'll crash. - if (Parameter->GetTupleSize() <= InTupleIdx) - InTupleIdx = 0; - - auto Optional = ChoiceParameter->GetValue(InTupleIdx); - if (Optional.IsSet()) - { - TupleIndex = InTupleIdx; - OutValue = Optional.GetValue(); - return true; - } - } - - return false; -} - -TSharedPtr -UHoudiniHandleParameter::Get(TSharedPtr DefaultValue) const -{ - UHoudiniParameterChoice* ChoiceParameter = Cast(AssetParameter); - if (ChoiceParameter) - { - auto Optional = ChoiceParameter->GetValue(TupleIndex); - if (Optional.IsSet()) - return Optional.GetValue(); - } - - return DefaultValue; -} - -UHoudiniHandleParameter & -UHoudiniHandleParameter::operator=(float Value) -{ - UHoudiniParameterFloat* FloatParameter = Cast(AssetParameter); - if (FloatParameter) - { - FloatParameter->SetValue(Value, TupleIndex); - FloatParameter->MarkChanged(true); - } - - return *this; -} - -void -UHoudiniHandleComponent::InitializeHandleParameters() -{ - if (XformParms.Num() < int32(EXformParameter::COUNT)) - { - XformParms.Empty(); - for (int32 n = 0; n < int32(EXformParameter::COUNT); ++n) - { - UHoudiniHandleParameter* XformHandle = NewObject(this, UHoudiniHandleParameter::StaticClass()); - XformParms.Add(XformHandle); - } - } - - if (!RSTParm) - { - RSTParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); - } - - if (!RotOrderParm) - { - RotOrderParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); - } -} - -bool -UHoudiniHandleComponent::CheckHandleValid() const -{ - if (XformParms.Num() < int32(EXformParameter::COUNT)) - return false; - - for (auto& XformParm : XformParms) - { - if (!XformParm) - return false; - } - - if (!RSTParm) - return false; - - if (!RotOrderParm) - return false; - - return true; -} - -FBox -UHoudiniHandleComponent::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - return BoxBounds + GetComponentLocation(); -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleComponent.h" + +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniRuntimeSettings.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +void +UHoudiniHandleComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniHandleComponent_V1* CompatibilityHC = NewObject(); + CompatibilityHC->Serialize(Ar); + CompatibilityHC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : serialized data will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +UHoudiniHandleParameter::UHoudiniHandleParameter(const FObjectInitializer & ObjectInitializer) + :Super(ObjectInitializer) +{}; + +UHoudiniHandleComponent::UHoudiniHandleComponent(const FObjectInitializer & ObjectInitializer) + :Super(ObjectInitializer) +{}; + + +bool +UHoudiniHandleParameter::Bind(float & OutValue, const char * CmpName, int32 InTupleIdx, + const FString & HandleParmName, UHoudiniParameter* Parameter) +{ + if (!Parameter) + return false; + + if (HandleParmName != CmpName) + return false; + + UHoudiniParameterFloat* FloatParameter = Cast(Parameter); + + if (!FloatParameter) + return false; + + AssetParameter = Parameter; + + if (FloatParameter) + { + // It is possible that the handle param is bound to a single tuple param. + // Ignore the preset tuple index if that's the case or we'll crash. + if (Parameter->GetTupleSize() <= InTupleIdx) + InTupleIdx = 0; + + auto Optional = FloatParameter->GetValue(InTupleIdx); + if (Optional.IsSet()) + { + TupleIndex = InTupleIdx; + OutValue = Optional.GetValue(); + return true; + } + } + + return false; +} + +bool +UHoudiniHandleParameter::Bind(TSharedPtr & OutValue, const char * CmpName, + int32 InTupleIdx, const FString & HandleParmName, UHoudiniParameter* Parameter) +{ + if (!Parameter) + return false; + + if (HandleParmName != CmpName) + return false; + + UHoudiniParameterChoice* ChoiceParameter = Cast(Parameter); + + if (!ChoiceParameter) + return false; + + AssetParameter = Parameter; + + if (ChoiceParameter) + { + // It is possible that the handle param is bound to a single tuple param. + // Ignore the preset tuple index if that's the case or we'll crash. + if (Parameter->GetTupleSize() <= InTupleIdx) + InTupleIdx = 0; + + auto Optional = ChoiceParameter->GetValue(InTupleIdx); + if (Optional.IsSet()) + { + TupleIndex = InTupleIdx; + OutValue = Optional.GetValue(); + return true; + } + } + + return false; +} + +TSharedPtr +UHoudiniHandleParameter::Get(TSharedPtr DefaultValue) const +{ + UHoudiniParameterChoice* ChoiceParameter = Cast(AssetParameter); + if (ChoiceParameter) + { + auto Optional = ChoiceParameter->GetValue(TupleIndex); + if (Optional.IsSet()) + return Optional.GetValue(); + } + + return DefaultValue; +} + +UHoudiniHandleParameter & +UHoudiniHandleParameter::operator=(float Value) +{ + UHoudiniParameterFloat* FloatParameter = Cast(AssetParameter); + if (FloatParameter) + { + FloatParameter->SetValue(Value, TupleIndex); + FloatParameter->MarkChanged(true); + } + + return *this; +} + +void +UHoudiniHandleComponent::InitializeHandleParameters() +{ + if (XformParms.Num() < int32(EXformParameter::COUNT)) + { + XformParms.Empty(); + for (int32 n = 0; n < int32(EXformParameter::COUNT); ++n) + { + UHoudiniHandleParameter* XformHandle = NewObject(this, UHoudiniHandleParameter::StaticClass()); + XformParms.Add(XformHandle); + } + } + + if (!RSTParm) + { + RSTParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); + } + + if (!RotOrderParm) + { + RotOrderParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); + } +} + +bool +UHoudiniHandleComponent::CheckHandleValid() const +{ + if (XformParms.Num() < int32(EXformParameter::COUNT)) + return false; + + for (auto& XformParm : XformParms) + { + if (!XformParm) + return false; + } + + if (!RSTParm) + return false; + + if (!RotOrderParm) + return false; + + return true; +} + +FBox +UHoudiniHandleComponent::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + return BoxBounds + GetComponentLocation(); +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h index 5454b5e20..93f327bbe 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h @@ -1,135 +1,135 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" - -#include "HoudiniHandleComponent.generated.h" - -class UHoudiniParameter; - -UENUM() -enum class EXformParameter : uint8 -{ - TX, TY, TZ, - RX, RY, RZ, - SX, SY, SZ, - COUNT -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniHandleParameter : public UObject -{ -public: - GENERATED_UCLASS_BODY() - - UPROPERTY() - UHoudiniParameter* AssetParameter; - - UPROPERTY() - int32 TupleIndex; - - - bool Bind( - float & OutValue, - const char * CmpName, - int32 InTupleIdx, - const FString & HandleParmName, - UHoudiniParameter* Parameter); - - bool Bind( - TSharedPtr & OutValue, - const char * CmpName, - int32 InTupleIdx, - const FString & HandleParmName, - UHoudiniParameter* Parameter); - - TSharedPtr Get(TSharedPtr DefaultValue) const; - - UHoudiniHandleParameter & operator=(float Value); - -}; - -UENUM() -enum class EHoudiniHandleType : uint8 -{ - Xform, - Bounder, - Unsupported -}; - -UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) -class HOUDINIENGINERUNTIME_API UHoudiniHandleComponent : public USceneComponent -{ -public: - - friend class UHoudiniAssetComponent; - - friend class FHoudiniHandleComponentVisualizer; - - GENERATED_UCLASS_BODY() - - virtual void Serialize(FArchive & Ar) override; - - FString GetHandleName() const { return HandleName; }; - EHoudiniHandleType GetHandleType() const { return HandleType; }; - - void SetHandleName(const FString& InHandleName) { HandleName = InHandleName; }; - void SetHandleType(const EHoudiniHandleType& InHandleType) { HandleType = InHandleType; }; - - // Equality, consider two handle equals if they have the same name, type, tuple size and disabled status - bool operator==(const UHoudiniHandleComponent& other) const - { - return (HandleType == other.HandleType && HandleName.Equals(other.HandleName)); - } - - bool Matches(const UHoudiniHandleComponent& other) const { return (*this == other); }; - - void InitializeHandleParameters(); - - bool CheckHandleValid() const; - - FBox GetBounds() const; - -public: - UPROPERTY() - TArray XformParms; - - UPROPERTY() - UHoudiniHandleParameter* RSTParm; - - UPROPERTY() - UHoudiniHandleParameter* RotOrderParm; - -private: - UPROPERTY() - EHoudiniHandleType HandleType; - - UPROPERTY() - FString HandleName; - -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" + +#include "HoudiniHandleComponent.generated.h" + +class UHoudiniParameter; + +UENUM() +enum class EXformParameter : uint8 +{ + TX, TY, TZ, + RX, RY, RZ, + SX, SY, SZ, + COUNT +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniHandleParameter : public UObject +{ +public: + GENERATED_UCLASS_BODY() + + UPROPERTY() + UHoudiniParameter* AssetParameter; + + UPROPERTY() + int32 TupleIndex; + + + bool Bind( + float & OutValue, + const char * CmpName, + int32 InTupleIdx, + const FString & HandleParmName, + UHoudiniParameter* Parameter); + + bool Bind( + TSharedPtr & OutValue, + const char * CmpName, + int32 InTupleIdx, + const FString & HandleParmName, + UHoudiniParameter* Parameter); + + TSharedPtr Get(TSharedPtr DefaultValue) const; + + UHoudiniHandleParameter & operator=(float Value); + +}; + +UENUM() +enum class EHoudiniHandleType : uint8 +{ + Xform, + Bounder, + Unsupported +}; + +UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) +class HOUDINIENGINERUNTIME_API UHoudiniHandleComponent : public USceneComponent +{ +public: + + friend class UHoudiniAssetComponent; + + friend class FHoudiniHandleComponentVisualizer; + + GENERATED_UCLASS_BODY() + + virtual void Serialize(FArchive & Ar) override; + + FString GetHandleName() const { return HandleName; }; + EHoudiniHandleType GetHandleType() const { return HandleType; }; + + void SetHandleName(const FString& InHandleName) { HandleName = InHandleName; }; + void SetHandleType(const EHoudiniHandleType& InHandleType) { HandleType = InHandleType; }; + + // Equality, consider two handle equals if they have the same name, type, tuple size and disabled status + bool operator==(const UHoudiniHandleComponent& other) const + { + return (HandleType == other.HandleType && HandleName.Equals(other.HandleName)); + } + + bool Matches(const UHoudiniHandleComponent& other) const { return (*this == other); }; + + void InitializeHandleParameters(); + + bool CheckHandleValid() const; + + FBox GetBounds() const; + +public: + UPROPERTY() + TArray XformParms; + + UPROPERTY() + UHoudiniHandleParameter* RSTParm; + + UPROPERTY() + UHoudiniHandleParameter* RotOrderParm; + +private: + UPROPERTY() + EHoudiniHandleType HandleType; + + UPROPERTY() + FString HandleName; + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp index ce9ef72fb..8a3001058 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp @@ -1,2583 +1,2584 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInput.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutput.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniAsset.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetBlueprintComponent.h" - -#include "EngineUtils.h" -#include "Engine/Brush.h" -#include "Engine/Engine.h" -#include "Engine/DataTable.h" -#include "Model.h" -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "UObject/UObjectGlobals.h" - -#include "Components/SplineComponent.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Landscape.h" - -#if WITH_EDITOR - -#include "Kismet2/BlueprintEditorUtils.h" -#include "Kismet2/KismetEditorUtilities.h" - -#endif - -// -UHoudiniInput::UHoudiniInput() - : Type(EHoudiniInputType::Invalid) - , PreviousType(EHoudiniInputType::Invalid) - , AssetNodeId(-1) - , InputNodeId(-1) - , InputIndex(0) - , ParmId(-1) - , bIsObjectPathParameter(false) - , bHasChanged(false) - , bPackBeforeMerge(false) - , bExportLODs(false) - , bExportSockets(false) - , bExportColliders(false) - , bCookOnCurveChanged(true) - , bStaticMeshChanged(false) - , bInputAssetConnectedInHoudini(false) - , bSwitchedToCurve(false) - , DefaultCurveOffset(0.f) - , bIsWorldInputBoundSelector(false) - , bWorldInputBoundSelectorAutoUpdate(false) - , UnrealSplineResolution(50.0f) - , bUpdateInputLandscape(false) - , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) - , bLandscapeExportSelectionOnly(false) - , bLandscapeAutoSelectComponent(false) - , bLandscapeExportMaterials(false) - , bLandscapeExportLighting(false) - , bLandscapeExportNormalizedUVs(false) - , bLandscapeExportTileUVs(false) -{ - Name = TEXT(""); - Label = TEXT(""); - SetFlags(RF_Transactional); - - // Geometry inputs always have one null default object - GeometryInputObjects.Add(nullptr); - - KeepWorldTransform = GetDefaultXTransformType(); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; -} - -void -UHoudiniInput::BeginDestroy() -{ - InvalidateData(); - - // DO NOT MANUALLY DESTROY OUR INPUT OBJECTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - - // Mark all our input objects for destruction - ForAllHoudiniInputObjectArrays([](TArray& ObjectArray) { - ObjectArray.Empty(); - }); - - Super::BeginDestroy(); -} - -#if WITH_EDITOR -void UHoudiniInput::PostEditUndo() -{ - Super::PostEditUndo(); - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!InputObjectsPtr) - return; - - MarkChanged(true); - bool bBlueprintStructureChanged = false; - - if (HasInputTypeChanged()) - { - // If the input type has changed on undo, previousType becomes new type - /* This does not work properly, see the corresponding part in FHoudiniInputDetails::AddInputTypeComboBox(...), after Transaction(... - ) - { - EHoudiniInputType NewType = PreviousType; - SetInputType(NewType); - } - */ - EHoudiniInputType Temp = Type; - Type = PreviousType; - PreviousType = EHoudiniInputType::Invalid; - - // If the undo action caused input type changing, treat it as a regular type changing - // after set up the new and prev types properly - SetInputType(Temp, bBlueprintStructureChanged); - } - else - { - if (Type == EHoudiniInputType::Asset) - { - // Mark the input asset object as changed, since only undo changing asset will get into here. - // The input array will be empty when undo adding asset (only support single asset input object in an input now) - for (auto & NextAssetInputObj : *InputObjectsPtr) - { - if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) - continue; - - NextAssetInputObj->MarkChanged(true); - } - } - - - if (Type == EHoudiniInputType::World) - { - if (WorldInputObjects.Num() == 0 && InputNodeId >= 0) - { - for (auto & NextNodeId : CreatedDataNodeIds) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NextNodeId, true); - } - - CreatedDataNodeIds.Empty(); - - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - InputNodeId = -1; - } - } - - if (Type == EHoudiniInputType::Curve) - { - if (PreviousType != EHoudiniInputType::Curve) - { - for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); - if (!SplineInput || SplineInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - USceneComponent* OuterComponent = Cast(GetOuter()); - - // Attach the new Houdini spline component to it's owner - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - HoudiniSplineComponent->MarkChanged(true); - } - return; - } - bool bUndoDelete = false; - bool bUndoInsert = false; - bool bUndoDeletedObjArrayEmptied = false; - - TArray< USceneComponent* > childActor; - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) - childActor = OuterHAC->GetAttachChildren(); - - // Undo delete input objects action - for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) - { - UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; - if (!InputObject || InputObject->IsPendingKill()) - continue; - - UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); - - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) - continue; - - UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - // If the last change deleted this curve input, recreate this Houdini Spline input. - if (!SplineComponent->GetAttachParent()) - { - bUndoDelete = true; - - if (!bUndoDeletedObjArrayEmptied) - LastUndoDeletedInputs.Empty(); - - bUndoDeletedObjArrayEmptied = true; - - UHoudiniSplineComponent * ReconstructedSpline = NewObject( - GetOuter(), UHoudiniSplineComponent::StaticClass()); - - if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) - continue; - - ReconstructedSpline->SetFlags(RF_Transactional); - ReconstructedSpline->CopyHoudiniData(SplineComponent); - - UHoudiniInputObject * ReconstructedInputObject = UHoudiniInputHoudiniSplineComponent::Create( - ReconstructedSpline, GetOuter(), ReconstructedSpline->GetHoudiniSplineName()); - UHoudiniInputHoudiniSplineComponent *ReconstructedHoudiniSplineInput = (UHoudiniInputHoudiniSplineComponent*)ReconstructedInputObject; - (*InputObjectsPtr)[Index] = ReconstructedHoudiniSplineInput; - - ReconstructedSpline->RegisterComponent(); - ReconstructedSpline->SetFlags(RF_Transactional); - - CreateHoudiniSplineInput(ReconstructedHoudiniSplineInput, true, true, bBlueprintStructureChanged); - - // Cast the reconstructed Houdini Spline Input to a generic HoudiniInput object. - UHoudiniInputObject * ReconstructedHoudiniInput = Cast(ReconstructedHoudiniSplineInput); - - LastUndoDeletedInputs.Add(ReconstructedHoudiniInput); - // Reset the LastInsertedInputsArray for redoing this undo action. - } - } - - if (bUndoDelete) - return; - - // Undo insert input objects action - for (int Index = 0; Index < LastInsertedInputs.Num(); ++Index) - { - bUndoInsert = true; - UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - HoudiniSplineComponent->DestroyComponent(); - } - - if (bUndoInsert) - return; - - for (int Index = 0; Index < LastUndoDeletedInputs.Num(); ++Index) - { - UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; - - UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) - continue; - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - - HoudiniSplineComponent->DestroyComponent(); - } - } - } - - if (bBlueprintStructureChanged) - { - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); - } - -} -#endif - - -FBox -UHoudiniInput::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - - switch (Type) - { - case EHoudiniInputType::Curve: - { - for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) - { - const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); - if (!CurInCurve || CurInCurve->IsPendingKill()) - continue; - - UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); - if (!CurCurve || CurCurve->IsPendingKill()) - continue; - - FBox CurCurveBound(ForceInitToZero); - for (auto & Trans : CurCurve->CurvePoints) - { - CurCurveBound += Trans.GetLocation(); - } - - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - - if (OuterHAC && !OuterHAC->IsPendingKill()) - BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); - } - } - break; - - case EHoudiniInputType::Asset: - { - for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) - { - UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); - if (!CurInAsset || CurInAsset->IsPendingKill()) - continue; - - UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) - continue; - - BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); - } - } - break; - - case EHoudiniInputType::World: - { - for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) - { - UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); - if (CurInActor && !CurInActor->IsPendingKill()) - { - AActor* Actor = CurInActor->GetActor(); - if (!Actor || Actor->IsPendingKill()) - continue; - - FVector Origin, Extent; - Actor->GetActorBounds(false, Origin, Extent); - - BoxBounds += FBox::BuildAABB(Origin, Extent); - } - else - { - // World Input now also support HoudiniAssets - UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); - if (CurInAsset && !CurInAsset->IsPendingKill()) - { - UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) - continue; - - BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); - continue; - } - } - } - } - break; - - case EHoudiniInputType::Landscape: - { - for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) - { - UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); - if (!CurInLandscape || CurInLandscape->IsPendingKill()) - continue; - - ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); - if (!CurLandscape || CurLandscape->IsPendingKill()) - continue; - - FVector Origin, Extent; - CurLandscape->GetActorBounds(false, Origin, Extent); - - BoxBounds += FBox::BuildAABB(Origin, Extent); - } - } - break; - - case EHoudiniInputType::Skeletal: - case EHoudiniInputType::Invalid: - default: - break; - } - - return BoxBounds; -} - -FString -UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) -{ - FString InputTypeStr; - switch (InInputType) - { - case EHoudiniInputType::Geometry: - { - InputTypeStr = TEXT("Geometry Input"); - } - break; - - case EHoudiniInputType::Asset: - { - InputTypeStr = TEXT("Asset Input"); - } - break; - - case EHoudiniInputType::Curve: - { - InputTypeStr = TEXT("Curve Input"); - } - break; - - case EHoudiniInputType::Landscape: - { - InputTypeStr = TEXT("Landscape Input"); - } - break; - - case EHoudiniInputType::World: - { - InputTypeStr = TEXT("World Outliner Input"); - } - break; - - case EHoudiniInputType::Skeletal: - { - InputTypeStr = TEXT("Skeletal Mesh Input"); - } - break; - } - - return InputTypeStr; -} - - -EHoudiniInputType -UHoudiniInput::StringToInputType(const FString& InInputTypeString) -{ - if (InInputTypeString.StartsWith(TEXT("Geometry"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Geometry; - } - else if (InInputTypeString.StartsWith(TEXT("Asset"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Asset; - } - else if (InInputTypeString.StartsWith(TEXT("Curve"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Curve; - } - else if (InInputTypeString.StartsWith(TEXT("Landscape"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Landscape; - } - else if (InInputTypeString.StartsWith(TEXT("World"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::World; - } - else if (InInputTypeString.StartsWith(TEXT("Skeletal"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Skeletal; - } - - return EHoudiniInputType::Invalid; -} - - -EHoudiniCurveType UHoudiniInput::StringToHoudiniCurveType(const FString& HoudiniCurveTypeString) -{ - if (HoudiniCurveTypeString.StartsWith(TEXT("Polygon"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Polygon; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Nurbs"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Nurbs; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Bezier"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Bezier; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Points"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Points; - } - - return EHoudiniCurveType::Invalid; -} - -EHoudiniCurveMethod UHoudiniInput::StringToHoudiniCurveMethod(const FString& HoudiniCurveMethodString) -{ - if (HoudiniCurveMethodString.StartsWith(TEXT("CVs"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::CVs; - } - else if (HoudiniCurveMethodString.StartsWith(TEXT("Breakpoints"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::Breakpoints; - } - - else if (HoudiniCurveMethodString.StartsWith(TEXT("Freehand"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::Freehand; - } - - return EHoudiniCurveMethod::Invalid; - -} - -// -void -UHoudiniInput::SetSOPInput(const int32& InInputIndex) -{ - // Set the input index - InputIndex = InInputIndex; - - // Invalidate objpath parameter - ParmId = -1; - bIsObjectPathParameter = false; -} - -void -UHoudiniInput::SetObjectPathParameter(const int32& InParmId) -{ - // Set as objpath parameter - ParmId = InParmId; - bIsObjectPathParameter = true; - - // Invalidate the geo input - InputIndex = -1; -} - -EHoudiniXformType -UHoudiniInput::GetDefaultXTransformType() -{ - switch (Type) - { - case EHoudiniInputType::Curve: - case EHoudiniInputType::Geometry: - case EHoudiniInputType::Skeletal: - return EHoudiniXformType::None; - case EHoudiniInputType::Asset: - case EHoudiniInputType::Landscape: - case EHoudiniInputType::World: - return EHoudiniXformType::IntoThisObject; - } - - return EHoudiniXformType::Auto; -} - -bool -UHoudiniInput::GetKeepWorldTransform() const -{ - bool bReturn = false; - switch (KeepWorldTransform) - { - case EHoudiniXformType::Auto: - { - // Return default values corresponding to the input type: - if (Type == EHoudiniInputType::Curve - || Type == EHoudiniInputType::Geometry - || Type == EHoudiniInputType::Skeletal ) - { - // NONE for Geo, Curve and skeletal mesh IN - bReturn = false; - } - else - { - // INTO THIS OBJECT for Asset, Landscape and World IN - bReturn = true; - } - break; - } - - case EHoudiniXformType::None: - { - bReturn = false; - break; - } - - case EHoudiniXformType::IntoThisObject: - { - bReturn = true; - break; - } - } - - return bReturn; -} - -void -UHoudiniInput::SetKeepWorldTransform(const bool& bInKeepWorldTransform) -{ - if (bInKeepWorldTransform) - { - KeepWorldTransform = EHoudiniXformType::IntoThisObject; - } - else - { - KeepWorldTransform = EHoudiniXformType::None; - } -} - -void -UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlueprintStructureModified) -{ - if (InInputType == Type) - return; - - SetPreviousInputType(Type); - - // Mark this input as changed - MarkChanged(true); - bOutBlueprintStructureModified = true; - - // Check previous input type - switch (PreviousType) - { - case EHoudiniInputType::Asset: - { - break; - } - - case EHoudiniInputType::Curve: - { - // detach the input curves from the asset component - if (GetNumberOfInputObjects() > 0) - { - for (UHoudiniInputObject * CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); - - if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); - - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - HoudiniSplineComponent->Modify(); - - const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); - - if (bIsArchetype) - { -#if WITH_EDITOR - check(HoudiniSplineComponent->IsTemplate()); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); -#endif - } - else - { - AActor* OwningActor = HoudiniSplineComponent->GetOwner(); - check(OwningActor); - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - HoudiniSplineComponent->SetVisibility(false, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(false); - HoudiniSplineComponent->SetHiddenInGame(true, true); - - // This NodeId shouldn't be invalidated like this. If a spline component - // or curve input is no longer valid, the input object should be removed from the HoudinInput - // to get cleaned up properly. - // HoudiniSplineComponent->SetNodeId(-1); - HoudiniSplineComponent->MarkChanged(true); - } - - bOutBlueprintStructureModified = true; - } - } - break; - } - - case EHoudiniInputType::Geometry: - { - break; - } - - case EHoudiniInputType::Landscape: - { - TArray* InputObjectsArray = GetHoudiniInputObjectArray(PreviousType); - - if (!InputObjectsArray) - break; - - for (int32 Idx = 0; Idx < InputObjectsArray->Num(); ++Idx) - { - UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; - - if (!InputObj || InputObj->IsPendingKill()) - continue; - - UHoudiniInputLandscape* InputLandscape = Cast(InputObj); - - if (!InputLandscape || InputLandscape->IsPendingKill()) - continue; - - // do something? - } - - break; - } - - case EHoudiniInputType::Skeletal: - { - break; - } - - case EHoudiniInputType::World: - { - break; - } - - default: - break; - } - - - Type = InInputType; - - // TODO: NOPE, not needed - // Set keep world transform to default w.r.t to new input type. - //KeepWorldTransform = GetDefaultXTransformType(); - - // Check current input type - switch (InInputType) - { - case EHoudiniInputType::World: - case EHoudiniInputType::Asset: - { - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !bImportAsReference) - { - for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) - continue; - - UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) - continue; - - CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); - } - } - } - break; - - case EHoudiniInputType::Curve: - { - if (GetNumberOfInputObjects() == 0) - { - CreateNewCurveInputObject(bOutBlueprintStructureModified); - MarkChanged(true); - } - else - { - for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(CurrentInput); - if (!IsValid(SplineInput)) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); - if (!IsValid(HoudiniSplineComponent)) - continue; - - HoudiniSplineComponent->Modify(); - - const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); - - if (bIsArchetype) - { -#if WITH_EDITOR - check(HoudiniSplineComponent->IsTemplate()); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); -#endif - } - else - { - // Attach the new Houdini spline component to it's owner - AActor* OwningActor = HoudiniSplineComponent->GetOwner(); - check(OwningActor); - USceneComponent* OuterComponent = Cast(GetOuter()); - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->MarkChanged(true); - - } - - bOutBlueprintStructureModified = true; - } - } - } - break; - - case EHoudiniInputType::Geometry: - { - - } - break; - - case EHoudiniInputType::Landscape: - { - // Need to do anything on select? - } - break; - - case EHoudiniInputType::Skeletal: - { - } - break; - - default: - { - } - break; - } -} - -UHoudiniInputObject* -UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) -{ - if (CurveInputObjects.Num() > 0) - return nullptr; - - UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); - if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) - return nullptr; - - UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - // Default Houdini spline component input should not be visible at initialization - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - - CurveInputObjects.Add(NewCurveInputObject); - SetInputObjectsNumber(EHoudiniInputType::Curve, 1); - CurveInputObjects.SetNum(1); - - return NewCurveInputObject; -} - -void -UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) -{ - MarkDataUploadNeeded(bInChanged); - - // Mark all the objects from this input has changed so they upload themselves - - TSet InputTypes; - InputTypes.Add(Type); - InputTypes.Add(EHoudiniInputType::Curve); - - TArray* NewInputObjects = GetHoudiniInputObjectArray(Type); - if (NewInputObjects) - { - for (auto CurInputObject : *NewInputObjects) - { - if (CurInputObject && !CurInputObject->IsPendingKill()) - CurInputObject->MarkChanged(bInChanged); - } - } -} - -UHoudiniInput * UHoudiniInput::DuplicateAndCopyState(UObject * DestOuter, bool bInCanDeleteHoudiniNodes) -{ - UHoudiniInput* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); - - NewInput->CopyStateFrom(this, false, bInCanDeleteHoudiniNodes); - - return NewInput; -} - -void UHoudiniInput::CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes) -{ - - // Preserve the current input objects before the copy to ensure we don't lose - // access to input objects and have them end up in the garbage. - - TMap*> PrevInputObjectsMap; - - for(EHoudiniInputType InputType : HoudiniInputTypeList) - { - PrevInputObjectsMap.Add(InputType, GetHoudiniInputObjectArray(InputType)); - } - - // TArray PrevInputObjects; - // TArray* OldToInputObjects = GetHoudiniInputObjectArray(Type); - // if (OldToInputObjects) - // PrevInputObjects = *OldToInputObjects; - - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - } - - AssetNodeId = InInput->AssetNodeId; - InputNodeId = InInput->InputNodeId; - ParmId = InInput->ParmId; - bCanDeleteHoudiniNodes = bInCanDeleteHoudiniNodes; - - //if (bInCanDeleteHoudiniNodes) - //{ - // // Delete stale data nodes before they get overwritten. - // TSet NewNodeIds(InInput->CreatedDataNodeIds); - // for (int32 NodeId : CreatedDataNodeIds) - // { - // if (!NewNodeIds.Contains(NodeId)) - // { - // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); - // } - // } - //} - - CreatedDataNodeIds = InInput->CreatedDataNodeIds; - - // Important note: At this point the new object may still share objects with InInput. - // The CopyInputs() will properly duplicate inputs where necessary. - - // Copy states of Input Objects that correspond to the current type. - - for(auto& Entry : PrevInputObjectsMap) - { - EHoudiniInputType InputType = Entry.Key; - TArray* PrevInputObjects = Entry.Value; - TArray* ToInputObjects = GetHoudiniInputObjectArray(InputType); - TArray* FromInputObjects = InInput->GetHoudiniInputObjectArray(InputType); - - if (ToInputObjects && FromInputObjects) - { - *ToInputObjects = *PrevInputObjects; - CopyInputs(*ToInputObjects, *FromInputObjects, bInCanDeleteHoudiniNodes); - } - } - -} - -void UHoudiniInput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -void UHoudiniInput::InvalidateData() -{ - // If valid, mark our input node for deletion - if (InputNodeId >= 0) - { - // .. but if we're an asset input, don't delete the node as InputNodeId - // is set to the input HDA's node ID! - if (Type != EHoudiniInputType::Asset) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - } - - InputNodeId = -1; - } - - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - if (!InputObject) - continue; - - if (InputObject->IsA()) - { - // When the input object is a HoudiniAssetComponent, - // we need to be sure that this HDA node id is not in CreatedDataNodeIds - // We dont want to delete the input HDA node! - CreatedDataNodeIds.Remove(InputObject->InputNodeId); - } - - InputObject->InvalidateData(); - } - - if (bCanDeleteHoudiniNodes) - { - auto& HoudiniEngineRuntime = FHoudiniEngineRuntime::Get(); - for(int32 NodeId : CreatedDataNodeIds) - { - HoudiniEngineRuntime.MarkNodeIdAsPendingDelete(NodeId, true); - } - } - - CreatedDataNodeIds.Empty(); -} - -void UHoudiniInput::CopyInputs(TArray& ToInputObjects, TArray& FromInputObjects, bool bInCanDeleteHoudiniNodes) -{ - TSet StaleObjects(ToInputObjects); - - const int32 NumInputs = FromInputObjects.Num(); - UObject* TargetOuter = GetOuter(); - - ToInputObjects.SetNum(NumInputs); - - - for (int i = 0; i < NumInputs; i++) - { - UHoudiniInputObject* FromObject = FromInputObjects[i]; - UHoudiniInputObject* ToObject = ToInputObjects[i]; - - if (!FromObject) - { - ToInputObjects[i] = nullptr; - continue; - } - - if (ToObject) - { - bool IsValid = true; - // Is ToInput and FromInput the same or do we have to create a input object? - IsValid = IsValid && ToObject->Matches(*FromObject); - IsValid = IsValid && ToObject->GetOuter() == TargetOuter; - - if (!IsValid) - { - ToObject = nullptr; - } - } - - if (ToObject) - { - // We have an existing (matching) object. Copy the - // state from the incoming input. - StaleObjects.Remove(ToObject); - ToObject->CopyStateFrom(FromObject, true); - } - else - { - // We need to create a new input here. - ToObject = FromObject->DuplicateAndCopyState(TargetOuter); - ToInputObjects[i] = ToObject; - } - - ToObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); - } - - - for (UHoudiniInputObject* StaleInputObject : StaleObjects) - { - if (!StaleInputObject) - continue; - if (StaleInputObject->GetOuter() == this) - { - StaleInputObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); - } - } -} - - -UHoudiniInputHoudiniSplineComponent* -UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * FromHoudiniSplineInputComponent, const bool & bAttachToparent, const bool & bAppendToInputArray, bool& bOutBlueprintStructureModified) -{ - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = nullptr; - UHoudiniSplineComponent* HoudiniSplineComponent = nullptr; - - UObject* OuterObj = GetOuter(); - USceneComponent* OuterComp = Cast(GetOuter()); - bool bOuterIsTemplate = (OuterObj && OuterObj->IsTemplate()); - - if (!FromHoudiniSplineInputComponent) - { - // NOTE: If we're inside the Blueprint editor, the outer here is going to the be HAC component template. - check(OuterObj) - - // Create a default Houdini spline input if a null pointer is passed in. - FName HoudiniSplineName = MakeUniqueObjectName(OuterComp, UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); - - // Create a Houdini Input Object. - UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( - nullptr, OuterObj, HoudiniSplineName.ToString()); - - if (!NewInputObject || NewInputObject->IsPendingKill()) - return nullptr; - - HoudiniSplineInput = Cast(NewInputObject); - if (!HoudiniSplineInput) - return nullptr; - - HoudiniSplineComponent = NewObject( - HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - HoudiniSplineInput->Update(HoudiniSplineComponent); - - HoudiniSplineComponent->SetHoudiniSplineName(HoudiniSplineName.ToString()); - HoudiniSplineComponent->SetFlags(RF_Transactional); - - // Set the default position of curve to avoid overlapping. - HoudiniSplineComponent->SetOffset(DefaultCurveOffset); - DefaultCurveOffset += 100.f; - - if (!bOuterIsTemplate) - { - HoudiniSplineComponent->RegisterComponent(); - - // Attach the new Houdini spline component to it's owner. - if (bAttachToparent) - HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); - } - - //push the new input object to the array for new type. - if (bAppendToInputArray && Type == EHoudiniInputType::Curve) - GetHoudiniInputObjectArray(Type)->Add(NewInputObject); - -#if WITH_EDITOR - if (bOuterIsTemplate) - { - UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); - if (HAB) - { - UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (Blueprint) - { - TArray Components; - Components.Add(HoudiniSplineComponent); - - USCS_Node* HABNode = HAB->FindSCSNodeForTemplateComponentInClassHierarchy(HAB); - - // NOTE: FAddComponentsToBlueprintParams was introduced in 4.26 so for the sake of - // backwards compatibility, manually determine which SCSNode was added instead of - // relying on Params.OutNodes. - //FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - //Params.OptionalNewRootNode = HABNode; - const TSet PreviousSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, FKismetEditorUtilities::EAddComponentToBPHarvestMode::None, HABNode, false); - USCS_Node* NewNode = nullptr; - const TSet CurrentSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); - const TSet AddedNodes = CurrentSCSNodes.Difference(PreviousSCSNodes); - - if (AddedNodes.Num() > 0) - { - // Record Input / SCS node mapping - USCS_Node* SCSNode = AddedNodes.Array()[0]; - HAB->AddInputObjectMapping(NewInputObject->GetInputGuid(), SCSNode->VariableGuid); - SCSNode->ComponentTemplate->SetFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject); - } - - Blueprint->Modify(); - bOutBlueprintStructureModified = true; - } - } - } -#endif - } - else - { - // Otherwise, get the Houdini spline, and Houdini spline input from the argument. - HoudiniSplineInput = FromHoudiniSplineInputComponent; - HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - // Attach the new Houdini spline component to it's owner. - HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); - } - - // Mark the created UHoudiniSplineComponent as an input, and set its InputObject. - HoudiniSplineComponent->SetIsInputCurve(true); - - // HoudiniSplineComponent->SetInputObject(HoudiniSplineInput); - - // Set Houdini Spline Component bHasChanged and bNeedsToTrigerUpdate to true. - HoudiniSplineComponent->MarkChanged(true); - - - - return HoudiniSplineInput; -} - -void -UHoudiniInput::RemoveSplineFromInputObject( - UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, - bool& bOutBlueprintStructureModified) const -{ - if (!InHoudiniSplineInputObject) - return; - - UObject* OuterObj = GetOuter(); - const bool bOuterIsTemplate = OuterObj && OuterObj->IsTemplate(); - - if (bOuterIsTemplate) - { -#if WITH_EDITOR - // Find the SCS node that corresponds to this input and remove it. - UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); - if (HAB) - { - const UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (Blueprint) - { - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - check(SCS); - FGuid SCSGuid; - if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) - { - // TODO: Move this SCS variable removal code to a reusable utility function. We're - // going to need to reuse this in a few other places too. - USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(SCSGuid); - if (SCSNode) - { - SCS->RemoveNodeAndPromoteChildren(SCSNode); - SCSNode->SetOnNameChanged(FSCSNodeNameChanged()); - bOutBlueprintStructureModified = true; - HAB->RemoveInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid); - - if (SCSNode->ComponentTemplate != nullptr) - { - const FName TemplateName = SCSNode->ComponentTemplate->GetFName(); - const FString RemovedName = SCSNode->GetVariableName().ToString() + TEXT("_REMOVED_") + FGuid::NewGuid().ToString(); - - SCSNode->ComponentTemplate->Modify(); - SCSNode->ComponentTemplate->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); - - TArray ArchetypeInstances; - auto DestroyArchetypeInstances = [&ArchetypeInstances, &RemovedName](UActorComponent* ComponentTemplate) - { - ComponentTemplate->GetArchetypeInstances(ArchetypeInstances); - for (UObject* ArchetypeInstance : ArchetypeInstances) - { - if (!ArchetypeInstance->HasAllFlags(RF_ArchetypeObject | RF_InheritableComponentTemplate)) - { - CastChecked(ArchetypeInstance)->DestroyComponent(); - ArchetypeInstance->Rename(*RemovedName, nullptr, REN_DontCreateRedirectors); - } - } - }; - - DestroyArchetypeInstances(SCSNode->ComponentTemplate); - - if (Blueprint) - { - // Children need to have their inherited component template instance of the component renamed out of the way as well - TArray ChildrenOfClass; - GetDerivedClasses(Blueprint->GeneratedClass, ChildrenOfClass); - - for (UClass* ChildClass : ChildrenOfClass) - { - UBlueprintGeneratedClass* BPChildClass = CastChecked(ChildClass); - - if (UActorComponent* Component = (UActorComponent*)FindObjectWithOuter(BPChildClass, UActorComponent::StaticClass(), TemplateName)) - { - Component->Modify(); - Component->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); - - DestroyArchetypeInstances(Component); - } - } - } - } - } - } // if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) - } // if (Blueprint) - } -#endif - } // if (bIsOuterTemplate) - else - { - UHoudiniSplineComponent* HoudiniSplineComponent = InHoudiniSplineInputObject->GetCurveComponent(); - if (HoudiniSplineComponent) - { - // detach the input curves from the asset component - //FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - //HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - // Destroy the Houdini Spline Component - //InputObjectsPtr->RemoveAt(AtIndex); - HoudiniSplineComponent->DestroyComponent(); - } - } - InHoudiniSplineInputObject->Update(nullptr); -} - - -TArray* -UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - return &GeometryInputObjects; - - case EHoudiniInputType::Curve: - return &CurveInputObjects; - - case EHoudiniInputType::Asset: - return &AssetInputObjects; - - case EHoudiniInputType::Landscape: - return &LandscapeInputObjects; - - case EHoudiniInputType::World: - return &WorldInputObjects; - - case EHoudiniInputType::Skeletal: - return &SkeletalInputObjects; - - default: - case EHoudiniInputType::Invalid: - return nullptr; - } - - return nullptr; -} - -TArray* -UHoudiniInput::GetBoundSelectorObjectArray() -{ - return &WorldInputBoundSelectorObjects; -} - -const TArray* -UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - return &GeometryInputObjects; - - case EHoudiniInputType::Curve: - return &CurveInputObjects; - - case EHoudiniInputType::Asset: - return &AssetInputObjects; - - case EHoudiniInputType::Landscape: - return &LandscapeInputObjects; - - case EHoudiniInputType::World: - return &WorldInputObjects; - - case EHoudiniInputType::Skeletal: - return &SkeletalInputObjects; - - default: - case EHoudiniInputType::Invalid: - return nullptr; - } - - return nullptr; -} - -UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) -{ - return GetHoudiniInputObjectAt(Type, AtIndex); -} - -const UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) const -{ - const TArray* InputObjectsArray = GetHoudiniInputObjectArray(Type); - if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) - return nullptr; - - return (*InputObjectsArray)[AtIndex]; -} - -UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsArray = GetHoudiniInputObjectArray(InType); - if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) - return nullptr; - - return (*InputObjectsArray)[AtIndex]; -} - -UObject* -UHoudiniInput::GetInputObjectAt(const int32& AtIndex) -{ - return GetInputObjectAt(Type, AtIndex); -} - -AActor* -UHoudiniInput::GetBoundSelectorObjectAt(const int32& AtIndex) -{ - if (!WorldInputBoundSelectorObjects.IsValidIndex(AtIndex)) - return nullptr; - - return WorldInputBoundSelectorObjects[AtIndex]; -} - -UObject* -UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); - if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) - return nullptr; - - return HoudiniInputObject->GetObject(); -} - -void -UHoudiniInput::InsertInputObjectAt(const int32& AtIndex) -{ - InsertInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - InputObjectsPtr->InsertDefaulted(AtIndex, 1); - MarkChanged(true); -} - -void -UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) -{ - DeleteInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (!InputObjectsPtr->IsValidIndex(AtIndex)) - return; - - bool bBlueprintStructureModified = false; - - if (Type == EHoudiniInputType::Asset) - { - // ... TODO operations for removing asset input type - } - else if (Type == EHoudiniInputType::Curve) - { - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast((*InputObjectsPtr)[AtIndex]); - if (HoudiniSplineInputObject) - { - RemoveSplineFromInputObject(HoudiniSplineInputObject, bBlueprintStructureModified); - } - } - else if (Type == EHoudiniInputType::Geometry) - { - // ... TODO operations for removing geometry input type - } - else if (Type == EHoudiniInputType::Landscape) - { - // ... TODO operations for removing landscape input type - } - else if (Type == EHoudiniInputType::Skeletal) - { - // ... TODO operations for removing skeletal input type - } - else if (Type == EHoudiniInputType::World) - { - // ... TODO operations for removing world input type - } - else - { - // ... invalid input type - } - - MarkChanged(true); - - UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; - if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) - { - // Mark the input object's nodes for deletion - InputObjectToDelete->InvalidateData(); - - // If the deleted object wasnt null, trigger a re upload of the input data - MarkDataUploadNeeded(true); - } - - InputObjectsPtr->RemoveAt(AtIndex); - - // Delete the merge node when all the input objects are deleted. - if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); - InputNodeId = -1; - } - -#if WITH_EDITOR - if (bBlueprintStructureModified) - { - UActorComponent* Component = Cast(GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(Component); - } -#endif -} - -void -UHoudiniInput::DuplicateInputObjectAt(const int32& AtIndex) -{ - DuplicateInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (!InputObjectsPtr->IsValidIndex(AtIndex)) - return; - - // If the duplicated object is not null, trigger a re upload of the input data - bool bTriggerUpload = (*InputObjectsPtr)[AtIndex] != nullptr; - - // TODO: Duplicate the UHoudiniInputObject!! - UHoudiniInputObject* DuplicateInput = (*InputObjectsPtr)[AtIndex]; - InputObjectsPtr->Insert(DuplicateInput, AtIndex); - - MarkChanged(true); - - if (bTriggerUpload) - MarkDataUploadNeeded(true); -} - -int32 -UHoudiniInput::GetNumberOfInputObjects() -{ - return GetNumberOfInputObjects(Type); -} - -int32 -UHoudiniInput::GetNumberOfInputObjects(const EHoudiniInputType& InType) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return 0; - - return InputObjectsPtr->Num(); -} - -int32 -UHoudiniInput::GetNumberOfInputMeshes() -{ - return GetNumberOfInputMeshes(Type); -} - -int32 -UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return 0; - - // TODO? - // If geometry input, and we only have one null object, return 0 - int32 Num = InputObjectsPtr->Num(); - - // TODO: Fix BP properly! - // Special case for SM in BP: - // we need to add extra input objects store in BlueprintStaticMeshes - // Same thing for Actor InputObjects! - for (auto InputObj : *InputObjectsPtr) - { - if (!InputObj || InputObj->IsPendingKill()) - continue; - - UHoudiniInputStaticMesh* InputSM = Cast(InputObj); - if (InputSM && !InputSM->IsPendingKill()) - { - if (InputSM->BlueprintStaticMeshes.Num() > 0) - { - Num += (InputSM->BlueprintStaticMeshes.Num() - 1); - } - } - - UHoudiniInputActor* InputActor = Cast(InputObj); - if (InputActor && !InputActor->IsPendingKill()) - { - if (InputActor->ActorComponents.Num() > 0) - { - Num += (InputActor->ActorComponents.Num() - 1); - } - } - } - - return Num; -} - - -int32 -UHoudiniInput::GetNumberOfBoundSelectorObjects() const -{ - return WorldInputBoundSelectorObjects.Num(); -} - -void -UHoudiniInput::SetInputObjectAt(const int32& AtIndex, UObject* InObject) -{ - return SetInputObjectAt(Type, AtIndex, InObject); -} - -void -UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject) -{ - // Start by making sure we have the proper number of input objects - int32 NumIntObject = GetNumberOfInputObjects(InType); - if (NumIntObject <= AtIndex) - { - // We need to resize the array - SetInputObjectsNumber(InType, AtIndex + 1); - } - - UObject* CurrentInputObject = GetInputObjectAt(InType, AtIndex); - if (CurrentInputObject == InObject) - { - // Nothing to do - return; - } - - UHoudiniInputObject* CurrentInputObjectWrapper = GetHoudiniInputObjectAt(InType, AtIndex); - if (!InObject) - { - // We want to set the input object to null - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!ensure(InputObjectsPtr != nullptr && InputObjectsPtr->IsValidIndex(AtIndex))) - return; - - if (CurrentInputObjectWrapper) - { - // TODO: Check this case - // Do not destroy the input object manually! this messes up GC - //CurrentInputObjectWrapper->ConditionalBeginDestroy(); - MarkDataUploadNeeded(true); - } - - (*InputObjectsPtr)[AtIndex] = nullptr; - return; - } - - // Get the type of the previous and new input objects - EHoudiniInputObjectType NewObjectType = InObject ? UHoudiniInputObject::GetInputObjectTypeFromObject(InObject) : EHoudiniInputObjectType::Invalid; - EHoudiniInputObjectType CurrentObjectType = CurrentInputObjectWrapper ? CurrentInputObjectWrapper->Type : EHoudiniInputObjectType::Invalid; - - // See if we can reuse the existing InputObject - if (CurrentObjectType == NewObjectType && NewObjectType != EHoudiniInputObjectType::Invalid) - { - // The InputObjectTypes match, we can just update the existing object - CurrentInputObjectWrapper->Update(InObject); - CurrentInputObjectWrapper->MarkChanged(true); - return; - } - - // Destroy the existing input object - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!ensure(InputObjectsPtr)) - return; - - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InObject, this, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - return; - - // Mark that input object as changed so we know we need to update it - NewInputObject->MarkChanged(true); - if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) - { - // TODO: - // For some input type, we may have to copy some of the previous object's property before deleting it - - // Delete the previous object - CurrentInputObjectWrapper->MarkPendingKill(); - (*InputObjectsPtr)[AtIndex] = nullptr; - } - - // Update the input object array with the newly created input object - (*InputObjectsPtr)[AtIndex] = NewInputObject; -} - -void -UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (InputObjectsPtr->Num() == InNewCount) - { - // Nothing to do - return; - } - - if (InNewCount > InputObjectsPtr->Num()) - { - // Simply add new default InputObjects - InputObjectsPtr->SetNum(InNewCount); - } - else - { - // TODO: Check this case! - // Do not destroy the input object themselves manually, - // destroy the input object's nodes and reduce the array's size - for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - if (bCanDeleteHoudiniNodes) - CurrentInputObject->InvalidateData(); - - /*/ - //FHoudiniInputTranslator::DestroyInput(Inputs[InputIdx]); - CurrentObject->ConditionalBeginDestroy(); - (*InputObjectsPtr)[InObjIdx] = nullptr; - */ - } - - // Decrease the input object array size - InputObjectsPtr->SetNum(InNewCount); - } - - // Also delete the input's merge node when all the input objects are deleted. - if (InNewCount == 0 && InputNodeId >= 0) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - InputNodeId = -1; - } -} - -void -UHoudiniInput::SetBoundSelectorObjectsNumber(const int32& InNewCount) -{ - if (WorldInputBoundSelectorObjects.Num() == InNewCount) - { - // Nothing to do - return; - } - - if (InNewCount > WorldInputBoundSelectorObjects.Num()) - { - // Simply add new default InputObjects - WorldInputBoundSelectorObjects.SetNum(InNewCount); - } - else - { - /* - // TODO: Not Needed? - // Do not destroy the input object themselves manually, - // destroy the input object's nodes and reduce the array's size - for (int32 InObjIdx = WorldInputBoundSelectorObjects.Num() - 1; InObjIdx >= InNewCount; InObjIdx--) - { - UHoudiniInputObject* CurrentInputObject = WorldInputBoundSelectorObjects[InObjIdx]; - if (!CurrentInputObject) - continue; - - CurrentInputObject->MarkInputNodesForDeletion(); - } - */ - - // Decrease the input object array size - WorldInputBoundSelectorObjects.SetNum(InNewCount); - } -} - -void -UHoudiniInput::SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor) -{ - // Start by making sure we have the proper number of objects - int32 NumIntObject = GetNumberOfBoundSelectorObjects(); - if (NumIntObject <= AtIndex) - { - // We need to resize the array - SetBoundSelectorObjectsNumber(AtIndex + 1); - } - - AActor* CurrentActor = GetBoundSelectorObjectAt(AtIndex); - if (CurrentActor == InActor) - { - // Nothing to do - return; - } - - // Update the array with the new object - WorldInputBoundSelectorObjects[AtIndex] = InActor; -} - -// Helper function indicating what classes are supported by an input type -TArray -UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) -{ - TArray AllowedClasses; - switch (InInputType) - { - case EHoudiniInputType::Geometry: - AllowedClasses.Add(UStaticMesh::StaticClass()); - AllowedClasses.Add(USkeletalMesh::StaticClass()); - AllowedClasses.Add(UBlueprint::StaticClass()); - AllowedClasses.Add(UDataTable::StaticClass()); - break; - - case EHoudiniInputType::Curve: - AllowedClasses.Add(USplineComponent::StaticClass()); - AllowedClasses.Add(UHoudiniSplineComponent::StaticClass()); - break; - - case EHoudiniInputType::Asset: - AllowedClasses.Add(UHoudiniAssetComponent::StaticClass()); - break; - - case EHoudiniInputType::Landscape: - AllowedClasses.Add(ALandscapeProxy::StaticClass()); - break; - - case EHoudiniInputType::World: - AllowedClasses.Add(AActor::StaticClass()); - break; - - case EHoudiniInputType::Skeletal: - AllowedClasses.Add(USkeletalMesh::StaticClass()); - break; - - default: - break; - } - - return AllowedClasses; -} - -// Helper function indicating if an object is supported by an input type -bool -UHoudiniInput::IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject) -{ - TArray AllowedClasses = GetAllowedClasses(InInputType); - for (auto CurClass : AllowedClasses) - { - if (InObject->IsA(CurClass)) - return true; - } - - return false; -} - -bool -UHoudiniInput::IsDataUploadNeeded() -{ - if (bDataUploadNeeded) - return true; - - return HasChanged(); -} - -// Indicates if this input has changed and should be updated -bool -UHoudiniInput::HasChanged() -{ - if (bHasChanged) - return true; - - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->HasChanged()) - return true; - } - - return false; -} - -bool -UHoudiniInput::IsTransformUploadNeeded() -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->HasTransformChanged()) - return true; - } - - return false; -} - -// Indicates if this input needs to trigger an update -bool -UHoudiniInput::NeedsToTriggerUpdate() -{ - if (bNeedsToTriggerUpdate) - return true; - - const TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->NeedsToTriggerUpdate()) - return true; - } - - return false; -} - -FString -UHoudiniInput::GetNodeBaseName() const -{ - UHoudiniAssetComponent* HAC = Cast(GetOuter()); - FString NodeBaseName = HAC ? HAC->GetDisplayName() : TEXT("HoudiniAsset"); - - // Unfortunately CreateInputNode always prefix with input_... - if (IsObjectPathParameter()) - NodeBaseName += TEXT("_") + GetName(); - else - NodeBaseName += TEXT("_input") + FString::FromInt(GetInputIndex()); - - return NodeBaseName; -} - -void -UHoudiniInput::OnTransformUIExpand(const int32& AtIndex) -{ -#if WITH_EDITORONLY_DATA - if (TransformUIExpanded.IsValidIndex(AtIndex)) - { - TransformUIExpanded[AtIndex] = !TransformUIExpanded[AtIndex]; - } - else - { - // We need to append values to the expanded array - for (int32 Index = TransformUIExpanded.Num(); Index <= AtIndex; Index++) - { - TransformUIExpanded.Add(Index == AtIndex ? true : false); - } - } -#endif -} - -bool -UHoudiniInput::IsTransformUIExpanded(const int32& AtIndex) -{ -#if WITH_EDITORONLY_DATA - return TransformUIExpanded.IsValidIndex(AtIndex) ? TransformUIExpanded[AtIndex] : false; -#else - return false; -#endif -}; - -FTransform* -UHoudiniInput::GetTransformOffset(const int32& AtIndex) -{ - UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); - if (InObject) - return &(InObject->Transform); - - return nullptr; -} - -const FTransform -UHoudiniInput::GetTransformOffset(const int32& AtIndex) const -{ - const UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); - if (InObject) - return InObject->Transform; - - return FTransform::Identity; -} - -TOptional -UHoudiniInput::GetPositionOffsetX(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().X; -} - -TOptional -UHoudiniInput::GetPositionOffsetY(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().Y; -} - -TOptional -UHoudiniInput::GetPositionOffsetZ(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().Z; -} - -TOptional -UHoudiniInput::GetRotationOffsetRoll(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Roll; -} - -TOptional -UHoudiniInput::GetRotationOffsetPitch(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Pitch; -} - -TOptional -UHoudiniInput::GetRotationOffsetYaw(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Yaw; -} - -TOptional -UHoudiniInput::GetScaleOffsetX(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().X; -} - -TOptional -UHoudiniInput::GetScaleOffsetY(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().Y; -} - -TOptional -UHoudiniInput::GetScaleOffsetZ(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().Z; -} - -bool -UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = GetTransformOffset(AtIndex); - if (!Transform) - return false; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - if (Position[XYZIndex] == Value) - return false; - Position[XYZIndex] = Value; - Transform->SetLocation(Position); - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - if (Rotator.Roll == Value) - return false; - Rotator.Roll = Value; - break; - } - - case 1: - { - if (Rotator.Pitch == Value) - return false; - Rotator.Pitch = Value; - break; - } - - case 2: - { - if (Rotator.Yaw == Value) - return false; - Rotator.Yaw = Value; - break; - } - } - Transform->SetRotation(Rotator.Quaternion()); - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - if (Scale[XYZIndex] == Value) - return false; - - Scale[XYZIndex] = Value; - Transform->SetScale3D(Scale); - } - - MarkChanged(true); - bStaticMeshChanged = true; - - return true; -} - -#if WITH_EDITOR -FText -UHoudiniInput::GetCurrentSelectionText() const -{ - FText CurrentSelectionText; - switch (Type) - { - case EHoudiniInputType::Landscape : - { - if (LandscapeInputObjects.Num() > 0) - { - UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; - - UHoudiniInputLandscape* InputLandscape = Cast(InputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) - return CurrentSelectionText; - - ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) - return CurrentSelectionText; - - CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); - } - } - break; - - case EHoudiniInputType::Asset : - { - if (AssetInputObjects.Num() > 0) - { - UHoudiniInputObject* InputObject = AssetInputObjects[0]; - - UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) - return CurrentSelectionText; - - UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!HAC || HAC->IsPendingKill()) - return CurrentSelectionText; - - UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return CurrentSelectionText; - - CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); - } - } - break; - - default: - break; - } - - return CurrentSelectionText; -} -#endif - -bool -UHoudiniInput::HasLandscapeExportTypeChanged () const -{ - if (Type != EHoudiniInputType::Landscape) - return false; - - return bLandscapeHasExportTypeChanged; -} - -void -UHoudiniInput::SetHasLandscapeExportTypeChanged(const bool InChanged) -{ - if (Type != EHoudiniInputType::Landscape) - return; - - bLandscapeHasExportTypeChanged = InChanged; -} - -bool -UHoudiniInput::GetUpdateInputLandscape() const -{ - if (Type != EHoudiniInputType::Landscape) - return false; - - return bUpdateInputLandscape; -} - -void -UHoudiniInput::SetUpdateInputLandscape(const bool bInUpdateInputLandcape) -{ - if (Type != EHoudiniInputType::Landscape) - return; - - bUpdateInputLandscape = bInUpdateInputLandcape; -} - - -bool -UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() -{ - // Dont do anything if we're not a World Input - if (Type != EHoudiniInputType::World) - return false; - - // Build an array of the current selection's bounds - TArray AllBBox; - for (auto CurrentActor : WorldInputBoundSelectorObjects) - { - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); - } - - // - // Select all actors in our bound selectors bounding boxes - // - - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - - //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); - UWorld* MyWorld = GetWorld(); - TArray NewSelectedActors; - for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) - { - AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - // Check that actor is currently not selected - if (WorldInputBoundSelectorObjects.Contains(CurrentActor)) - continue; - - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - // For BrushActors, both the actor and its brush must be valid - ABrush* BrushActor = Cast(CurrentActor); - if (BrushActor) - { - if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) - continue; - } - - FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); - for (auto InBounds : AllBBox) - { - // Check if both actor's bounds intersects - if (!ActorBounds.Intersect(InBounds)) - continue; - - NewSelectedActors.Add(CurrentActor); - break; - } - } - - return UpdateWorldSelection(NewSelectedActors); -} - -bool -UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) -{ - TArray NewSelectedActors = InNewSelection; - - // Update our current selection with the new one - // Keep actors that are still selected, remove the one that are not selected anymore - bool bHasSelectionChanged = false; - for (int32 Idx = WorldInputObjects.Num() - 1; Idx >= 0; Idx--) - { - UHoudiniInputActor* InputActor = Cast(WorldInputObjects[Idx]); - AActor* CurActor = InputActor ? InputActor->GetActor() : nullptr; - - if (CurActor && NewSelectedActors.Contains(CurActor)) - { - // The actor is still selected, remove it from the new selection - NewSelectedActors.Remove(CurActor); - } - else - { - // That actor is no longer selected, remove itr from our current selection - DeleteInputObjectAt(EHoudiniInputType::World, Idx); - bHasSelectionChanged = true; - } - } - - if (NewSelectedActors.Num() > 0) - bHasSelectionChanged = true; - - // Then add the newly selected Actors - int32 InputObjectIdx = GetNumberOfInputObjects(EHoudiniInputType::World); - int32 NewInputObjectNumber = InputObjectIdx + NewSelectedActors.Num(); - SetInputObjectsNumber(EHoudiniInputType::World, NewInputObjectNumber); - for (const auto& CurActor : NewSelectedActors) - { - // Update the input objects from the valid selected actors array - SetInputObjectAt(InputObjectIdx++, CurActor); - } - - MarkChanged(bHasSelectionChanged); - - return bHasSelectionChanged; -} - - -bool -UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Returns true if the object is one of our input object for the given type - const TArray* ObjectArray = GetHoudiniInputObjectArray(InType); - if (!ObjectArray) - return false; - - for (auto& CurrentInputObject : (*ObjectArray)) - { - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - if (CurrentInputObject->GetObject() == InObject) - return true; - } - - return false; -} - -void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const -{ - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : SkeletalInputObjects) - { - Fn(InputObject); - } -} - -TArray*> UHoudiniInput::GetAllObjectArrays() const -{ - return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; -} - -TArray*> UHoudiniInput::GetAllObjectArrays() -{ - return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; -} - -void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const -{ - TArray*> ObjectArrays = GetAllObjectArrays(); - for (const TArray* ObjectArrayPtr : ObjectArrays) - { - if (!ObjectArrayPtr) - continue; - Fn(*ObjectArrayPtr); - } -} - -void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) -{ - TArray*> ObjectArrays = GetAllObjectArrays(); - for (TArray* ObjectArrayPtr : ObjectArrays) - { - if (!ObjectArrayPtr) - continue; - Fn(*ObjectArrayPtr); - } -} - -void UHoudiniInput::GetAllHoudiniInputObjects(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddInputObject = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (InputObject) - OutObjects.Add(InputObject); - }; - ForAllHoudiniInputObjects(AddInputObject); -} - -void UHoudiniInput::ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const -{ - auto ProcessSceneComponent = [Fn](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - Fn(SceneComponentInput); - }; - ForAllHoudiniInputObjects(ProcessSceneComponent); -} - -void UHoudiniInput::GetAllHoudiniInputSceneComponents(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - OutObjects.Add(SceneComponentInput); - }; - ForAllHoudiniInputObjects(AddSceneComponent); -} - -void UHoudiniInput::GetAllHoudiniInputSplineComponents(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputHoudiniSplineComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - OutObjects.Add(SceneComponentInput); - }; - ForAllHoudiniInputObjects(AddSceneComponent); -} - - -void UHoudiniInput::RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject) -{ - if (!InInputObject) - return; - - ForAllHoudiniInputObjectArrays([InInputObject](TArray& ObjectArray) { - ObjectArray.Remove(InInputObject); - }); - - return; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInput.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutput.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniAsset.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetBlueprintComponent.h" + +#include "EngineUtils.h" +#include "Engine/Brush.h" +#include "Engine/Engine.h" +#include "Engine/DataTable.h" +#include "Model.h" +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "UObject/UObjectGlobals.h" + +#include "Components/SplineComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Landscape.h" + +#if WITH_EDITOR + +#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" + +#endif + +// +UHoudiniInput::UHoudiniInput() + : Type(EHoudiniInputType::Invalid) + , PreviousType(EHoudiniInputType::Invalid) + , AssetNodeId(-1) + , InputNodeId(-1) + , InputIndex(0) + , ParmId(-1) + , bIsObjectPathParameter(false) + , bHasChanged(false) + , bPackBeforeMerge(false) + , bExportLODs(false) + , bExportSockets(false) + , bExportColliders(false) + , bCookOnCurveChanged(true) + , bStaticMeshChanged(false) + , bInputAssetConnectedInHoudini(false) + , bSwitchedToCurve(false) + , DefaultCurveOffset(0.f) + , bIsWorldInputBoundSelector(false) + , bWorldInputBoundSelectorAutoUpdate(false) + , UnrealSplineResolution(50.0f) + , bUpdateInputLandscape(false) + , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) + , bLandscapeExportSelectionOnly(false) + , bLandscapeAutoSelectComponent(false) + , bLandscapeExportMaterials(false) + , bLandscapeExportLighting(false) + , bLandscapeExportNormalizedUVs(false) + , bLandscapeExportTileUVs(false) +{ + Name = TEXT(""); + Label = TEXT(""); + SetFlags(RF_Transactional); + + // Geometry inputs always have one null default object + GeometryInputObjects.Add(nullptr); + + KeepWorldTransform = GetDefaultXTransformType(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; +} + +void +UHoudiniInput::BeginDestroy() +{ + InvalidateData(); + + // DO NOT MANUALLY DESTROY OUR INPUT OBJECTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + + // Mark all our input objects for destruction + ForAllHoudiniInputObjectArrays([](TArray& ObjectArray) { + ObjectArray.Empty(); + }); + + Super::BeginDestroy(); +} + +#if WITH_EDITOR +void UHoudiniInput::PostEditUndo() +{ + Super::PostEditUndo(); + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!InputObjectsPtr) + return; + + MarkChanged(true); + bool bBlueprintStructureChanged = false; + + if (HasInputTypeChanged()) + { + // If the input type has changed on undo, previousType becomes new type + /* This does not work properly, see the corresponding part in FHoudiniInputDetails::AddInputTypeComboBox(...), after Transaction(... + ) + { + EHoudiniInputType NewType = PreviousType; + SetInputType(NewType); + } + */ + EHoudiniInputType Temp = Type; + Type = PreviousType; + PreviousType = EHoudiniInputType::Invalid; + + // If the undo action caused input type changing, treat it as a regular type changing + // after set up the new and prev types properly + SetInputType(Temp, bBlueprintStructureChanged); + } + else + { + if (Type == EHoudiniInputType::Asset) + { + // Mark the input asset object as changed, since only undo changing asset will get into here. + // The input array will be empty when undo adding asset (only support single asset input object in an input now) + for (auto & NextAssetInputObj : *InputObjectsPtr) + { + if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) + continue; + + NextAssetInputObj->MarkChanged(true); + } + } + + + if (Type == EHoudiniInputType::World) + { + if (WorldInputObjects.Num() == 0 && InputNodeId >= 0) + { + for (auto & NextNodeId : CreatedDataNodeIds) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NextNodeId, true); + } + + CreatedDataNodeIds.Empty(); + + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + InputNodeId = -1; + } + } + + if (Type == EHoudiniInputType::Curve) + { + if (PreviousType != EHoudiniInputType::Curve) + { + for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); + if (!SplineInput || SplineInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + USceneComponent* OuterComponent = Cast(GetOuter()); + + // Attach the new Houdini spline component to it's owner + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + HoudiniSplineComponent->MarkChanged(true); + } + return; + } + bool bUndoDelete = false; + bool bUndoInsert = false; + bool bUndoDeletedObjArrayEmptied = false; + + TArray< USceneComponent* > childActor; + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !OuterHAC->IsPendingKill()) + childActor = OuterHAC->GetAttachChildren(); + + // Undo delete input objects action + for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) + { + UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; + if (!InputObject || InputObject->IsPendingKill()) + continue; + + UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); + + if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + continue; + + UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + // If the last change deleted this curve input, recreate this Houdini Spline input. + if (!SplineComponent->GetAttachParent()) + { + bUndoDelete = true; + + if (!bUndoDeletedObjArrayEmptied) + LastUndoDeletedInputs.Empty(); + + bUndoDeletedObjArrayEmptied = true; + + UHoudiniSplineComponent * ReconstructedSpline = NewObject( + GetOuter(), UHoudiniSplineComponent::StaticClass()); + + if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) + continue; + + ReconstructedSpline->SetFlags(RF_Transactional); + ReconstructedSpline->CopyHoudiniData(SplineComponent); + + UHoudiniInputObject * ReconstructedInputObject = UHoudiniInputHoudiniSplineComponent::Create( + ReconstructedSpline, GetOuter(), ReconstructedSpline->GetHoudiniSplineName()); + UHoudiniInputHoudiniSplineComponent *ReconstructedHoudiniSplineInput = (UHoudiniInputHoudiniSplineComponent*)ReconstructedInputObject; + (*InputObjectsPtr)[Index] = ReconstructedHoudiniSplineInput; + + ReconstructedSpline->RegisterComponent(); + ReconstructedSpline->SetFlags(RF_Transactional); + + CreateHoudiniSplineInput(ReconstructedHoudiniSplineInput, true, true, bBlueprintStructureChanged); + + // Cast the reconstructed Houdini Spline Input to a generic HoudiniInput object. + UHoudiniInputObject * ReconstructedHoudiniInput = Cast(ReconstructedHoudiniSplineInput); + + LastUndoDeletedInputs.Add(ReconstructedHoudiniInput); + // Reset the LastInsertedInputsArray for redoing this undo action. + } + } + + if (bUndoDelete) + return; + + // Undo insert input objects action + for (int Index = 0; Index < LastInsertedInputs.Num(); ++Index) + { + bUndoInsert = true; + UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; + if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + HoudiniSplineComponent->DestroyComponent(); + } + + if (bUndoInsert) + return; + + for (int Index = 0; Index < LastUndoDeletedInputs.Num(); ++Index) + { + UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; + + UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); + if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); + if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) + continue; + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + + HoudiniSplineComponent->DestroyComponent(); + } + } + } + + if (bBlueprintStructureChanged) + { + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); + } + +} +#endif + + +FBox +UHoudiniInput::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + + switch (Type) + { + case EHoudiniInputType::Curve: + { + for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) + { + const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); + if (!CurInCurve || CurInCurve->IsPendingKill()) + continue; + + UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); + if (!CurCurve || CurCurve->IsPendingKill()) + continue; + + FBox CurCurveBound(ForceInitToZero); + for (auto & Trans : CurCurve->CurvePoints) + { + CurCurveBound += Trans.GetLocation(); + } + + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + + if (OuterHAC && !OuterHAC->IsPendingKill()) + BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); + } + } + break; + + case EHoudiniInputType::Asset: + { + for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) + { + UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); + if (!CurInAsset || CurInAsset->IsPendingKill()) + continue; + + UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); + if (!CurInHAC || CurInHAC->IsPendingKill()) + continue; + + BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); + } + } + break; + + case EHoudiniInputType::World: + { + for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) + { + UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); + if (CurInActor && !CurInActor->IsPendingKill()) + { + AActor* Actor = CurInActor->GetActor(); + if (!Actor || Actor->IsPendingKill()) + continue; + + FVector Origin, Extent; + Actor->GetActorBounds(false, Origin, Extent); + + BoxBounds += FBox::BuildAABB(Origin, Extent); + } + else + { + // World Input now also support HoudiniAssets + UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); + if (CurInAsset && !CurInAsset->IsPendingKill()) + { + UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); + if (!CurInHAC || CurInHAC->IsPendingKill()) + continue; + + BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); + continue; + } + } + } + } + break; + + case EHoudiniInputType::Landscape: + { + for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) + { + UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); + if (!CurInLandscape || CurInLandscape->IsPendingKill()) + continue; + + ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); + if (!CurLandscape || CurLandscape->IsPendingKill()) + continue; + + FVector Origin, Extent; + CurLandscape->GetActorBounds(false, Origin, Extent); + + BoxBounds += FBox::BuildAABB(Origin, Extent); + } + } + break; + + case EHoudiniInputType::Skeletal: + case EHoudiniInputType::Invalid: + default: + break; + } + + return BoxBounds; +} + +FString +UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) +{ + FString InputTypeStr; + switch (InInputType) + { + case EHoudiniInputType::Geometry: + { + InputTypeStr = TEXT("Geometry Input"); + } + break; + + case EHoudiniInputType::Asset: + { + InputTypeStr = TEXT("Asset Input"); + } + break; + + case EHoudiniInputType::Curve: + { + InputTypeStr = TEXT("Curve Input"); + } + break; + + case EHoudiniInputType::Landscape: + { + InputTypeStr = TEXT("Landscape Input"); + } + break; + + case EHoudiniInputType::World: + { + InputTypeStr = TEXT("World Outliner Input"); + } + break; + + case EHoudiniInputType::Skeletal: + { + InputTypeStr = TEXT("Skeletal Mesh Input"); + } + break; + } + + return InputTypeStr; +} + + +EHoudiniInputType +UHoudiniInput::StringToInputType(const FString& InInputTypeString) +{ + if (InInputTypeString.StartsWith(TEXT("Geometry"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Geometry; + } + else if (InInputTypeString.StartsWith(TEXT("Asset"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Asset; + } + else if (InInputTypeString.StartsWith(TEXT("Curve"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Curve; + } + else if (InInputTypeString.StartsWith(TEXT("Landscape"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Landscape; + } + else if (InInputTypeString.StartsWith(TEXT("World"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::World; + } + else if (InInputTypeString.StartsWith(TEXT("Skeletal"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Skeletal; + } + + return EHoudiniInputType::Invalid; +} + + +EHoudiniCurveType UHoudiniInput::StringToHoudiniCurveType(const FString& HoudiniCurveTypeString) +{ + if (HoudiniCurveTypeString.StartsWith(TEXT("Polygon"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Polygon; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Nurbs"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Nurbs; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Bezier"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Bezier; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Points"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Points; + } + + return EHoudiniCurveType::Invalid; +} + +EHoudiniCurveMethod UHoudiniInput::StringToHoudiniCurveMethod(const FString& HoudiniCurveMethodString) +{ + if (HoudiniCurveMethodString.StartsWith(TEXT("CVs"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::CVs; + } + else if (HoudiniCurveMethodString.StartsWith(TEXT("Breakpoints"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::Breakpoints; + } + + else if (HoudiniCurveMethodString.StartsWith(TEXT("Freehand"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::Freehand; + } + + return EHoudiniCurveMethod::Invalid; + +} + +// +void +UHoudiniInput::SetSOPInput(const int32& InInputIndex) +{ + // Set the input index + InputIndex = InInputIndex; + + // Invalidate objpath parameter + ParmId = -1; + bIsObjectPathParameter = false; +} + +void +UHoudiniInput::SetObjectPathParameter(const int32& InParmId) +{ + // Set as objpath parameter + ParmId = InParmId; + bIsObjectPathParameter = true; + + // Invalidate the geo input + InputIndex = -1; +} + +EHoudiniXformType +UHoudiniInput::GetDefaultXTransformType() +{ + switch (Type) + { + case EHoudiniInputType::Curve: + case EHoudiniInputType::Geometry: + case EHoudiniInputType::Skeletal: + return EHoudiniXformType::None; + case EHoudiniInputType::Asset: + case EHoudiniInputType::Landscape: + case EHoudiniInputType::World: + return EHoudiniXformType::IntoThisObject; + } + + return EHoudiniXformType::Auto; +} + +bool +UHoudiniInput::GetKeepWorldTransform() const +{ + bool bReturn = false; + switch (KeepWorldTransform) + { + case EHoudiniXformType::Auto: + { + // Return default values corresponding to the input type: + if (Type == EHoudiniInputType::Curve + || Type == EHoudiniInputType::Geometry + || Type == EHoudiniInputType::Skeletal ) + { + // NONE for Geo, Curve and skeletal mesh IN + bReturn = false; + } + else + { + // INTO THIS OBJECT for Asset, Landscape and World IN + bReturn = true; + } + break; + } + + case EHoudiniXformType::None: + { + bReturn = false; + break; + } + + case EHoudiniXformType::IntoThisObject: + { + bReturn = true; + break; + } + } + + return bReturn; +} + +void +UHoudiniInput::SetKeepWorldTransform(const bool& bInKeepWorldTransform) +{ + if (bInKeepWorldTransform) + { + KeepWorldTransform = EHoudiniXformType::IntoThisObject; + } + else + { + KeepWorldTransform = EHoudiniXformType::None; + } +} + +void +UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlueprintStructureModified) +{ + if (InInputType == Type) + return; + + SetPreviousInputType(Type); + + // Mark this input as changed + MarkChanged(true); + bOutBlueprintStructureModified = true; + + // Check previous input type + switch (PreviousType) + { + case EHoudiniInputType::Asset: + { + break; + } + + case EHoudiniInputType::Curve: + { + // detach the input curves from the asset component + if (GetNumberOfInputObjects() > 0) + { + for (UHoudiniInputObject * CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); + + if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); + + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + HoudiniSplineComponent->Modify(); + + const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); + + if (bIsArchetype) + { +#if WITH_EDITOR + check(HoudiniSplineComponent->IsTemplate()); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); +#endif + } + else + { + AActor* OwningActor = HoudiniSplineComponent->GetOwner(); + check(OwningActor); + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + HoudiniSplineComponent->SetVisibility(false, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(false); + HoudiniSplineComponent->SetHiddenInGame(true, true); + + // This NodeId shouldn't be invalidated like this. If a spline component + // or curve input is no longer valid, the input object should be removed from the HoudinInput + // to get cleaned up properly. + // HoudiniSplineComponent->SetNodeId(-1); + HoudiniSplineComponent->MarkChanged(true); + } + + bOutBlueprintStructureModified = true; + } + } + break; + } + + case EHoudiniInputType::Geometry: + { + break; + } + + case EHoudiniInputType::Landscape: + { + TArray* InputObjectsArray = GetHoudiniInputObjectArray(PreviousType); + + if (!InputObjectsArray) + break; + + for (int32 Idx = 0; Idx < InputObjectsArray->Num(); ++Idx) + { + UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; + + if (!InputObj || InputObj->IsPendingKill()) + continue; + + UHoudiniInputLandscape* InputLandscape = Cast(InputObj); + + if (!InputLandscape || InputLandscape->IsPendingKill()) + continue; + + // do something? + } + + break; + } + + case EHoudiniInputType::Skeletal: + { + break; + } + + case EHoudiniInputType::World: + { + break; + } + + default: + break; + } + + + Type = InInputType; + + // TODO: NOPE, not needed + // Set keep world transform to default w.r.t to new input type. + //KeepWorldTransform = GetDefaultXTransformType(); + + // Check current input type + switch (InInputType) + { + case EHoudiniInputType::World: + case EHoudiniInputType::Asset: + { + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !bImportAsReference) + { + for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); + if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + continue; + + UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); + if (!CurrentHAC || CurrentHAC->IsPendingKill()) + continue; + + CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); + } + } + } + break; + + case EHoudiniInputType::Curve: + { + if (GetNumberOfInputObjects() == 0) + { + CreateNewCurveInputObject(bOutBlueprintStructureModified); + MarkChanged(true); + } + else + { + for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(CurrentInput); + if (!IsValid(SplineInput)) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); + if (!IsValid(HoudiniSplineComponent)) + continue; + + HoudiniSplineComponent->Modify(); + + const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); + + if (bIsArchetype) + { +#if WITH_EDITOR + check(HoudiniSplineComponent->IsTemplate()); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); +#endif + } + else + { + // Attach the new Houdini spline component to it's owner + AActor* OwningActor = HoudiniSplineComponent->GetOwner(); + check(OwningActor); + USceneComponent* OuterComponent = Cast(GetOuter()); + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->MarkChanged(true); + + } + + bOutBlueprintStructureModified = true; + } + } + } + break; + + case EHoudiniInputType::Geometry: + { + + } + break; + + case EHoudiniInputType::Landscape: + { + // Need to do anything on select? + } + break; + + case EHoudiniInputType::Skeletal: + { + } + break; + + default: + { + } + break; + } +} + +UHoudiniInputObject* +UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) +{ + if (CurveInputObjects.Num() > 0) + return nullptr; + + UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); + if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) + return nullptr; + + UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + // Default Houdini spline component input should not be visible at initialization + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + + CurveInputObjects.Add(NewCurveInputObject); + SetInputObjectsNumber(EHoudiniInputType::Curve, 1); + CurveInputObjects.SetNum(1); + + return NewCurveInputObject; +} + +void +UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) +{ + MarkDataUploadNeeded(bInChanged); + + // Mark all the objects from this input has changed so they upload themselves + + TSet InputTypes; + InputTypes.Add(Type); + InputTypes.Add(EHoudiniInputType::Curve); + + TArray* NewInputObjects = GetHoudiniInputObjectArray(Type); + if (NewInputObjects) + { + for (auto CurInputObject : *NewInputObjects) + { + if (CurInputObject && !CurInputObject->IsPendingKill()) + CurInputObject->MarkChanged(bInChanged); + } + } +} + +UHoudiniInput * UHoudiniInput::DuplicateAndCopyState(UObject * DestOuter, bool bInCanDeleteHoudiniNodes) +{ + UHoudiniInput* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); + + NewInput->CopyStateFrom(this, false, bInCanDeleteHoudiniNodes); + + return NewInput; +} + +void UHoudiniInput::CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes) +{ + + // Preserve the current input objects before the copy to ensure we don't lose + // access to input objects and have them end up in the garbage. + + TMap*> PrevInputObjectsMap; + + for(EHoudiniInputType InputType : HoudiniInputTypeList) + { + PrevInputObjectsMap.Add(InputType, GetHoudiniInputObjectArray(InputType)); + } + + // TArray PrevInputObjects; + // TArray* OldToInputObjects = GetHoudiniInputObjectArray(Type); + // if (OldToInputObjects) + // PrevInputObjects = *OldToInputObjects; + + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + } + + AssetNodeId = InInput->AssetNodeId; + InputNodeId = InInput->InputNodeId; + ParmId = InInput->ParmId; + bCanDeleteHoudiniNodes = bInCanDeleteHoudiniNodes; + + //if (bInCanDeleteHoudiniNodes) + //{ + // // Delete stale data nodes before they get overwritten. + // TSet NewNodeIds(InInput->CreatedDataNodeIds); + // for (int32 NodeId : CreatedDataNodeIds) + // { + // if (!NewNodeIds.Contains(NodeId)) + // { + // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); + // } + // } + //} + + CreatedDataNodeIds = InInput->CreatedDataNodeIds; + + // Important note: At this point the new object may still share objects with InInput. + // The CopyInputs() will properly duplicate inputs where necessary. + + // Copy states of Input Objects that correspond to the current type. + + for(auto& Entry : PrevInputObjectsMap) + { + EHoudiniInputType InputType = Entry.Key; + TArray* PrevInputObjects = Entry.Value; + TArray* ToInputObjects = GetHoudiniInputObjectArray(InputType); + TArray* FromInputObjects = InInput->GetHoudiniInputObjectArray(InputType); + + if (ToInputObjects && FromInputObjects) + { + *ToInputObjects = *PrevInputObjects; + CopyInputs(*ToInputObjects, *FromInputObjects, bInCanDeleteHoudiniNodes); + } + } + +} + +void UHoudiniInput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +void UHoudiniInput::InvalidateData() +{ + // If valid, mark our input node for deletion + if (InputNodeId >= 0) + { + // .. but if we're an asset input, don't delete the node as InputNodeId + // is set to the input HDA's node ID! + if (Type != EHoudiniInputType::Asset) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + } + + InputNodeId = -1; + } + + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + if (!InputObject) + continue; + + if (InputObject->IsA()) + { + // When the input object is a HoudiniAssetComponent, + // we need to be sure that this HDA node id is not in CreatedDataNodeIds + // We dont want to delete the input HDA node! + CreatedDataNodeIds.Remove(InputObject->InputNodeId); + } + + InputObject->InvalidateData(); + } + + if (bCanDeleteHoudiniNodes) + { + auto& HoudiniEngineRuntime = FHoudiniEngineRuntime::Get(); + for(int32 NodeId : CreatedDataNodeIds) + { + HoudiniEngineRuntime.MarkNodeIdAsPendingDelete(NodeId, true); + } + } + + CreatedDataNodeIds.Empty(); +} + +void UHoudiniInput::CopyInputs(TArray& ToInputObjects, TArray& FromInputObjects, bool bInCanDeleteHoudiniNodes) +{ + TSet StaleObjects(ToInputObjects); + + const int32 NumInputs = FromInputObjects.Num(); + UObject* TargetOuter = GetOuter(); + + ToInputObjects.SetNum(NumInputs); + + + for (int i = 0; i < NumInputs; i++) + { + UHoudiniInputObject* FromObject = FromInputObjects[i]; + UHoudiniInputObject* ToObject = ToInputObjects[i]; + + if (!FromObject) + { + ToInputObjects[i] = nullptr; + continue; + } + + if (ToObject) + { + bool IsValid = true; + // Is ToInput and FromInput the same or do we have to create a input object? + IsValid = IsValid && ToObject->Matches(*FromObject); + IsValid = IsValid && ToObject->GetOuter() == TargetOuter; + + if (!IsValid) + { + ToObject = nullptr; + } + } + + if (ToObject) + { + // We have an existing (matching) object. Copy the + // state from the incoming input. + StaleObjects.Remove(ToObject); + ToObject->CopyStateFrom(FromObject, true); + } + else + { + // We need to create a new input here. + ToObject = FromObject->DuplicateAndCopyState(TargetOuter); + ToInputObjects[i] = ToObject; + } + + ToObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); + } + + + for (UHoudiniInputObject* StaleInputObject : StaleObjects) + { + if (!StaleInputObject) + continue; + if (StaleInputObject->GetOuter() == this) + { + StaleInputObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); + } + } +} + + +UHoudiniInputHoudiniSplineComponent* +UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * FromHoudiniSplineInputComponent, const bool & bAttachToparent, const bool & bAppendToInputArray, bool& bOutBlueprintStructureModified) +{ + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = nullptr; + UHoudiniSplineComponent* HoudiniSplineComponent = nullptr; + + UObject* OuterObj = GetOuter(); + USceneComponent* OuterComp = Cast(GetOuter()); + bool bOuterIsTemplate = (OuterObj && OuterObj->IsTemplate()); + + if (!FromHoudiniSplineInputComponent) + { + // NOTE: If we're inside the Blueprint editor, the outer here is going to the be HAC component template. + check(OuterObj) + + // Create a default Houdini spline input if a null pointer is passed in. + FName HoudiniSplineName = MakeUniqueObjectName(OuterComp, UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); + + // Create a Houdini Input Object. + UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( + nullptr, OuterObj, HoudiniSplineName.ToString()); + + if (!NewInputObject || NewInputObject->IsPendingKill()) + return nullptr; + + HoudiniSplineInput = Cast(NewInputObject); + if (!HoudiniSplineInput) + return nullptr; + + HoudiniSplineComponent = NewObject( + HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + HoudiniSplineInput->Update(HoudiniSplineComponent); + + HoudiniSplineComponent->SetHoudiniSplineName(HoudiniSplineName.ToString()); + HoudiniSplineComponent->SetFlags(RF_Transactional); + + // Set the default position of curve to avoid overlapping. + HoudiniSplineComponent->SetOffset(DefaultCurveOffset); + DefaultCurveOffset += 100.f; + + if (!bOuterIsTemplate) + { + HoudiniSplineComponent->RegisterComponent(); + + // Attach the new Houdini spline component to it's owner. + if (bAttachToparent) + HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); + } + + //push the new input object to the array for new type. + if (bAppendToInputArray && Type == EHoudiniInputType::Curve) + GetHoudiniInputObjectArray(Type)->Add(NewInputObject); + +#if WITH_EDITOR + if (bOuterIsTemplate) + { + UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); + if (HAB) + { + UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (Blueprint) + { + TArray Components; + Components.Add(HoudiniSplineComponent); + + USCS_Node* HABNode = HAB->FindSCSNodeForTemplateComponentInClassHierarchy(HAB); + + // NOTE: FAddComponentsToBlueprintParams was introduced in 4.26 so for the sake of + // backwards compatibility, manually determine which SCSNode was added instead of + // relying on Params.OutNodes. + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.OptionalNewRootNode = HABNode; + const TSet PreviousSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); + + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, Params); + USCS_Node* NewNode = nullptr; + const TSet CurrentSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); + const TSet AddedNodes = CurrentSCSNodes.Difference(PreviousSCSNodes); + + if (AddedNodes.Num() > 0) + { + // Record Input / SCS node mapping + USCS_Node* SCSNode = AddedNodes.Array()[0]; + HAB->AddInputObjectMapping(NewInputObject->GetInputGuid(), SCSNode->VariableGuid); + SCSNode->ComponentTemplate->SetFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject); + } + + Blueprint->Modify(); + bOutBlueprintStructureModified = true; + } + } + } +#endif + } + else + { + // Otherwise, get the Houdini spline, and Houdini spline input from the argument. + HoudiniSplineInput = FromHoudiniSplineInputComponent; + HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + // Attach the new Houdini spline component to it's owner. + HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); + } + + // Mark the created UHoudiniSplineComponent as an input, and set its InputObject. + HoudiniSplineComponent->SetIsInputCurve(true); + + // HoudiniSplineComponent->SetInputObject(HoudiniSplineInput); + + // Set Houdini Spline Component bHasChanged and bNeedsToTrigerUpdate to true. + HoudiniSplineComponent->MarkChanged(true); + + + + return HoudiniSplineInput; +} + +void +UHoudiniInput::RemoveSplineFromInputObject( + UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, + bool& bOutBlueprintStructureModified) const +{ + if (!InHoudiniSplineInputObject) + return; + + UObject* OuterObj = GetOuter(); + const bool bOuterIsTemplate = OuterObj && OuterObj->IsTemplate(); + + if (bOuterIsTemplate) + { +#if WITH_EDITOR + // Find the SCS node that corresponds to this input and remove it. + UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); + if (HAB) + { + const UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (Blueprint) + { + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + check(SCS); + FGuid SCSGuid; + if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) + { + // TODO: Move this SCS variable removal code to a reusable utility function. We're + // going to need to reuse this in a few other places too. + USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(SCSGuid); + if (SCSNode) + { + SCS->RemoveNodeAndPromoteChildren(SCSNode); + SCSNode->SetOnNameChanged(FSCSNodeNameChanged()); + bOutBlueprintStructureModified = true; + HAB->RemoveInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid); + + if (SCSNode->ComponentTemplate != nullptr) + { + const FName TemplateName = SCSNode->ComponentTemplate->GetFName(); + const FString RemovedName = SCSNode->GetVariableName().ToString() + TEXT("_REMOVED_") + FGuid::NewGuid().ToString(); + + SCSNode->ComponentTemplate->Modify(); + SCSNode->ComponentTemplate->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); + + TArray ArchetypeInstances; + auto DestroyArchetypeInstances = [&ArchetypeInstances, &RemovedName](UActorComponent* ComponentTemplate) + { + ComponentTemplate->GetArchetypeInstances(ArchetypeInstances); + for (UObject* ArchetypeInstance : ArchetypeInstances) + { + if (!ArchetypeInstance->HasAllFlags(RF_ArchetypeObject | RF_InheritableComponentTemplate)) + { + CastChecked(ArchetypeInstance)->DestroyComponent(); + ArchetypeInstance->Rename(*RemovedName, nullptr, REN_DontCreateRedirectors); + } + } + }; + + DestroyArchetypeInstances(SCSNode->ComponentTemplate); + + if (Blueprint) + { + // Children need to have their inherited component template instance of the component renamed out of the way as well + TArray ChildrenOfClass; + GetDerivedClasses(Blueprint->GeneratedClass, ChildrenOfClass); + + for (UClass* ChildClass : ChildrenOfClass) + { + UBlueprintGeneratedClass* BPChildClass = CastChecked(ChildClass); + + if (UActorComponent* Component = (UActorComponent*)FindObjectWithOuter(BPChildClass, UActorComponent::StaticClass(), TemplateName)) + { + Component->Modify(); + Component->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); + + DestroyArchetypeInstances(Component); + } + } + } + } + } + } // if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) + } // if (Blueprint) + } +#endif + } // if (bIsOuterTemplate) + else + { + UHoudiniSplineComponent* HoudiniSplineComponent = InHoudiniSplineInputObject->GetCurveComponent(); + if (HoudiniSplineComponent) + { + // detach the input curves from the asset component + //FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + //HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + // Destroy the Houdini Spline Component + //InputObjectsPtr->RemoveAt(AtIndex); + HoudiniSplineComponent->DestroyComponent(); + } + } + InHoudiniSplineInputObject->Update(nullptr); +} + + +TArray* +UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + return &GeometryInputObjects; + + case EHoudiniInputType::Curve: + return &CurveInputObjects; + + case EHoudiniInputType::Asset: + return &AssetInputObjects; + + case EHoudiniInputType::Landscape: + return &LandscapeInputObjects; + + case EHoudiniInputType::World: + return &WorldInputObjects; + + case EHoudiniInputType::Skeletal: + return &SkeletalInputObjects; + + default: + case EHoudiniInputType::Invalid: + return nullptr; + } + + return nullptr; +} + +TArray* +UHoudiniInput::GetBoundSelectorObjectArray() +{ + return &WorldInputBoundSelectorObjects; +} + +const TArray* +UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + return &GeometryInputObjects; + + case EHoudiniInputType::Curve: + return &CurveInputObjects; + + case EHoudiniInputType::Asset: + return &AssetInputObjects; + + case EHoudiniInputType::Landscape: + return &LandscapeInputObjects; + + case EHoudiniInputType::World: + return &WorldInputObjects; + + case EHoudiniInputType::Skeletal: + return &SkeletalInputObjects; + + default: + case EHoudiniInputType::Invalid: + return nullptr; + } + + return nullptr; +} + +UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) +{ + return GetHoudiniInputObjectAt(Type, AtIndex); +} + +const UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) const +{ + const TArray* InputObjectsArray = GetHoudiniInputObjectArray(Type); + if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) + return nullptr; + + return (*InputObjectsArray)[AtIndex]; +} + +UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsArray = GetHoudiniInputObjectArray(InType); + if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) + return nullptr; + + return (*InputObjectsArray)[AtIndex]; +} + +UObject* +UHoudiniInput::GetInputObjectAt(const int32& AtIndex) +{ + return GetInputObjectAt(Type, AtIndex); +} + +AActor* +UHoudiniInput::GetBoundSelectorObjectAt(const int32& AtIndex) +{ + if (!WorldInputBoundSelectorObjects.IsValidIndex(AtIndex)) + return nullptr; + + return WorldInputBoundSelectorObjects[AtIndex]; +} + +UObject* +UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); + if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) + return nullptr; + + return HoudiniInputObject->GetObject(); +} + +void +UHoudiniInput::InsertInputObjectAt(const int32& AtIndex) +{ + InsertInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + InputObjectsPtr->InsertDefaulted(AtIndex, 1); + MarkChanged(true); +} + +void +UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) +{ + DeleteInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (!InputObjectsPtr->IsValidIndex(AtIndex)) + return; + + bool bBlueprintStructureModified = false; + + if (Type == EHoudiniInputType::Asset) + { + // ... TODO operations for removing asset input type + } + else if (Type == EHoudiniInputType::Curve) + { + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast((*InputObjectsPtr)[AtIndex]); + if (HoudiniSplineInputObject) + { + RemoveSplineFromInputObject(HoudiniSplineInputObject, bBlueprintStructureModified); + } + } + else if (Type == EHoudiniInputType::Geometry) + { + // ... TODO operations for removing geometry input type + } + else if (Type == EHoudiniInputType::Landscape) + { + // ... TODO operations for removing landscape input type + } + else if (Type == EHoudiniInputType::Skeletal) + { + // ... TODO operations for removing skeletal input type + } + else if (Type == EHoudiniInputType::World) + { + // ... TODO operations for removing world input type + } + else + { + // ... invalid input type + } + + MarkChanged(true); + + UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; + if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) + { + // Mark the input object's nodes for deletion + InputObjectToDelete->InvalidateData(); + + // If the deleted object wasnt null, trigger a re upload of the input data + MarkDataUploadNeeded(true); + } + + InputObjectsPtr->RemoveAt(AtIndex); + + // Delete the merge node when all the input objects are deleted. + if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); + InputNodeId = -1; + } + +#if WITH_EDITOR + if (bBlueprintStructureModified) + { + UActorComponent* Component = Cast(GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(Component); + } +#endif +} + +void +UHoudiniInput::DuplicateInputObjectAt(const int32& AtIndex) +{ + DuplicateInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (!InputObjectsPtr->IsValidIndex(AtIndex)) + return; + + // If the duplicated object is not null, trigger a re upload of the input data + bool bTriggerUpload = (*InputObjectsPtr)[AtIndex] != nullptr; + + // TODO: Duplicate the UHoudiniInputObject!! + UHoudiniInputObject* DuplicateInput = (*InputObjectsPtr)[AtIndex]; + InputObjectsPtr->Insert(DuplicateInput, AtIndex); + + MarkChanged(true); + + if (bTriggerUpload) + MarkDataUploadNeeded(true); +} + +int32 +UHoudiniInput::GetNumberOfInputObjects() +{ + return GetNumberOfInputObjects(Type); +} + +int32 +UHoudiniInput::GetNumberOfInputObjects(const EHoudiniInputType& InType) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return 0; + + return InputObjectsPtr->Num(); +} + +int32 +UHoudiniInput::GetNumberOfInputMeshes() +{ + return GetNumberOfInputMeshes(Type); +} + +int32 +UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return 0; + + // TODO? + // If geometry input, and we only have one null object, return 0 + int32 Num = InputObjectsPtr->Num(); + + // TODO: Fix BP properly! + // Special case for SM in BP: + // we need to add extra input objects store in BlueprintStaticMeshes + // Same thing for Actor InputObjects! + for (auto InputObj : *InputObjectsPtr) + { + if (!InputObj || InputObj->IsPendingKill()) + continue; + + UHoudiniInputStaticMesh* InputSM = Cast(InputObj); + if (InputSM && !InputSM->IsPendingKill()) + { + if (InputSM->BlueprintStaticMeshes.Num() > 0) + { + Num += (InputSM->BlueprintStaticMeshes.Num() - 1); + } + } + + UHoudiniInputActor* InputActor = Cast(InputObj); + if (InputActor && !InputActor->IsPendingKill()) + { + if (InputActor->ActorComponents.Num() > 0) + { + Num += (InputActor->ActorComponents.Num() - 1); + } + } + } + + return Num; +} + + +int32 +UHoudiniInput::GetNumberOfBoundSelectorObjects() const +{ + return WorldInputBoundSelectorObjects.Num(); +} + +void +UHoudiniInput::SetInputObjectAt(const int32& AtIndex, UObject* InObject) +{ + return SetInputObjectAt(Type, AtIndex, InObject); +} + +void +UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject) +{ + // Start by making sure we have the proper number of input objects + int32 NumIntObject = GetNumberOfInputObjects(InType); + if (NumIntObject <= AtIndex) + { + // We need to resize the array + SetInputObjectsNumber(InType, AtIndex + 1); + } + + UObject* CurrentInputObject = GetInputObjectAt(InType, AtIndex); + if (CurrentInputObject == InObject) + { + // Nothing to do + return; + } + + UHoudiniInputObject* CurrentInputObjectWrapper = GetHoudiniInputObjectAt(InType, AtIndex); + if (!InObject) + { + // We want to set the input object to null + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!ensure(InputObjectsPtr != nullptr && InputObjectsPtr->IsValidIndex(AtIndex))) + return; + + if (CurrentInputObjectWrapper) + { + // TODO: Check this case + // Do not destroy the input object manually! this messes up GC + //CurrentInputObjectWrapper->ConditionalBeginDestroy(); + MarkDataUploadNeeded(true); + } + + (*InputObjectsPtr)[AtIndex] = nullptr; + return; + } + + // Get the type of the previous and new input objects + EHoudiniInputObjectType NewObjectType = InObject ? UHoudiniInputObject::GetInputObjectTypeFromObject(InObject) : EHoudiniInputObjectType::Invalid; + EHoudiniInputObjectType CurrentObjectType = CurrentInputObjectWrapper ? CurrentInputObjectWrapper->Type : EHoudiniInputObjectType::Invalid; + + // See if we can reuse the existing InputObject + if (CurrentObjectType == NewObjectType && NewObjectType != EHoudiniInputObjectType::Invalid) + { + // The InputObjectTypes match, we can just update the existing object + CurrentInputObjectWrapper->Update(InObject); + CurrentInputObjectWrapper->MarkChanged(true); + return; + } + + // Destroy the existing input object + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!ensure(InputObjectsPtr)) + return; + + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InObject, this, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + return; + + // Mark that input object as changed so we know we need to update it + NewInputObject->MarkChanged(true); + if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) + { + // TODO: + // For some input type, we may have to copy some of the previous object's property before deleting it + + // Delete the previous object + CurrentInputObjectWrapper->MarkPendingKill(); + (*InputObjectsPtr)[AtIndex] = nullptr; + } + + // Update the input object array with the newly created input object + (*InputObjectsPtr)[AtIndex] = NewInputObject; +} + +void +UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (InputObjectsPtr->Num() == InNewCount) + { + // Nothing to do + return; + } + + if (InNewCount > InputObjectsPtr->Num()) + { + // Simply add new default InputObjects + InputObjectsPtr->SetNum(InNewCount); + } + else + { + // TODO: Check this case! + // Do not destroy the input object themselves manually, + // destroy the input object's nodes and reduce the array's size + for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + if (bCanDeleteHoudiniNodes) + CurrentInputObject->InvalidateData(); + + /*/ + //FHoudiniInputTranslator::DestroyInput(Inputs[InputIdx]); + CurrentObject->ConditionalBeginDestroy(); + (*InputObjectsPtr)[InObjIdx] = nullptr; + */ + } + + // Decrease the input object array size + InputObjectsPtr->SetNum(InNewCount); + } + + // Also delete the input's merge node when all the input objects are deleted. + if (InNewCount == 0 && InputNodeId >= 0) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + InputNodeId = -1; + } +} + +void +UHoudiniInput::SetBoundSelectorObjectsNumber(const int32& InNewCount) +{ + if (WorldInputBoundSelectorObjects.Num() == InNewCount) + { + // Nothing to do + return; + } + + if (InNewCount > WorldInputBoundSelectorObjects.Num()) + { + // Simply add new default InputObjects + WorldInputBoundSelectorObjects.SetNum(InNewCount); + } + else + { + /* + // TODO: Not Needed? + // Do not destroy the input object themselves manually, + // destroy the input object's nodes and reduce the array's size + for (int32 InObjIdx = WorldInputBoundSelectorObjects.Num() - 1; InObjIdx >= InNewCount; InObjIdx--) + { + UHoudiniInputObject* CurrentInputObject = WorldInputBoundSelectorObjects[InObjIdx]; + if (!CurrentInputObject) + continue; + + CurrentInputObject->MarkInputNodesForDeletion(); + } + */ + + // Decrease the input object array size + WorldInputBoundSelectorObjects.SetNum(InNewCount); + } +} + +void +UHoudiniInput::SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor) +{ + // Start by making sure we have the proper number of objects + int32 NumIntObject = GetNumberOfBoundSelectorObjects(); + if (NumIntObject <= AtIndex) + { + // We need to resize the array + SetBoundSelectorObjectsNumber(AtIndex + 1); + } + + AActor* CurrentActor = GetBoundSelectorObjectAt(AtIndex); + if (CurrentActor == InActor) + { + // Nothing to do + return; + } + + // Update the array with the new object + WorldInputBoundSelectorObjects[AtIndex] = InActor; +} + +// Helper function indicating what classes are supported by an input type +TArray +UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) +{ + TArray AllowedClasses; + switch (InInputType) + { + case EHoudiniInputType::Geometry: + AllowedClasses.Add(UStaticMesh::StaticClass()); + AllowedClasses.Add(USkeletalMesh::StaticClass()); + AllowedClasses.Add(UBlueprint::StaticClass()); + AllowedClasses.Add(UDataTable::StaticClass()); + break; + + case EHoudiniInputType::Curve: + AllowedClasses.Add(USplineComponent::StaticClass()); + AllowedClasses.Add(UHoudiniSplineComponent::StaticClass()); + break; + + case EHoudiniInputType::Asset: + AllowedClasses.Add(UHoudiniAssetComponent::StaticClass()); + break; + + case EHoudiniInputType::Landscape: + AllowedClasses.Add(ALandscapeProxy::StaticClass()); + break; + + case EHoudiniInputType::World: + AllowedClasses.Add(AActor::StaticClass()); + break; + + case EHoudiniInputType::Skeletal: + AllowedClasses.Add(USkeletalMesh::StaticClass()); + break; + + default: + break; + } + + return AllowedClasses; +} + +// Helper function indicating if an object is supported by an input type +bool +UHoudiniInput::IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject) +{ + TArray AllowedClasses = GetAllowedClasses(InInputType); + for (auto CurClass : AllowedClasses) + { + if (InObject->IsA(CurClass)) + return true; + } + + return false; +} + +bool +UHoudiniInput::IsDataUploadNeeded() +{ + if (bDataUploadNeeded) + return true; + + return HasChanged(); +} + +// Indicates if this input has changed and should be updated +bool +UHoudiniInput::HasChanged() +{ + if (bHasChanged) + return true; + + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->HasChanged()) + return true; + } + + return false; +} + +bool +UHoudiniInput::IsTransformUploadNeeded() +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->HasTransformChanged()) + return true; + } + + return false; +} + +// Indicates if this input needs to trigger an update +bool +UHoudiniInput::NeedsToTriggerUpdate() +{ + if (bNeedsToTriggerUpdate) + return true; + + const TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->NeedsToTriggerUpdate()) + return true; + } + + return false; +} + +FString +UHoudiniInput::GetNodeBaseName() const +{ + UHoudiniAssetComponent* HAC = Cast(GetOuter()); + FString NodeBaseName = HAC ? HAC->GetDisplayName() : TEXT("HoudiniAsset"); + + // Unfortunately CreateInputNode always prefix with input_... + if (IsObjectPathParameter()) + NodeBaseName += TEXT("_") + GetName(); + else + NodeBaseName += TEXT("_input") + FString::FromInt(GetInputIndex()); + + return NodeBaseName; +} + +void +UHoudiniInput::OnTransformUIExpand(const int32& AtIndex) +{ +#if WITH_EDITORONLY_DATA + if (TransformUIExpanded.IsValidIndex(AtIndex)) + { + TransformUIExpanded[AtIndex] = !TransformUIExpanded[AtIndex]; + } + else + { + // We need to append values to the expanded array + for (int32 Index = TransformUIExpanded.Num(); Index <= AtIndex; Index++) + { + TransformUIExpanded.Add(Index == AtIndex ? true : false); + } + } +#endif +} + +bool +UHoudiniInput::IsTransformUIExpanded(const int32& AtIndex) +{ +#if WITH_EDITORONLY_DATA + return TransformUIExpanded.IsValidIndex(AtIndex) ? TransformUIExpanded[AtIndex] : false; +#else + return false; +#endif +}; + +FTransform* +UHoudiniInput::GetTransformOffset(const int32& AtIndex) +{ + UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); + if (InObject) + return &(InObject->Transform); + + return nullptr; +} + +const FTransform +UHoudiniInput::GetTransformOffset(const int32& AtIndex) const +{ + const UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); + if (InObject) + return InObject->Transform; + + return FTransform::Identity; +} + +TOptional +UHoudiniInput::GetPositionOffsetX(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().X; +} + +TOptional +UHoudiniInput::GetPositionOffsetY(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().Y; +} + +TOptional +UHoudiniInput::GetPositionOffsetZ(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().Z; +} + +TOptional +UHoudiniInput::GetRotationOffsetRoll(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Roll; +} + +TOptional +UHoudiniInput::GetRotationOffsetPitch(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Pitch; +} + +TOptional +UHoudiniInput::GetRotationOffsetYaw(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Yaw; +} + +TOptional +UHoudiniInput::GetScaleOffsetX(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().X; +} + +TOptional +UHoudiniInput::GetScaleOffsetY(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().Y; +} + +TOptional +UHoudiniInput::GetScaleOffsetZ(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().Z; +} + +bool +UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = GetTransformOffset(AtIndex); + if (!Transform) + return false; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + if (Position[XYZIndex] == Value) + return false; + Position[XYZIndex] = Value; + Transform->SetLocation(Position); + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + if (Rotator.Roll == Value) + return false; + Rotator.Roll = Value; + break; + } + + case 1: + { + if (Rotator.Pitch == Value) + return false; + Rotator.Pitch = Value; + break; + } + + case 2: + { + if (Rotator.Yaw == Value) + return false; + Rotator.Yaw = Value; + break; + } + } + Transform->SetRotation(Rotator.Quaternion()); + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + if (Scale[XYZIndex] == Value) + return false; + + Scale[XYZIndex] = Value; + Transform->SetScale3D(Scale); + } + + MarkChanged(true); + bStaticMeshChanged = true; + + return true; +} + +#if WITH_EDITOR +FText +UHoudiniInput::GetCurrentSelectionText() const +{ + FText CurrentSelectionText; + switch (Type) + { + case EHoudiniInputType::Landscape : + { + if (LandscapeInputObjects.Num() > 0) + { + UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; + + UHoudiniInputLandscape* InputLandscape = Cast(InputObject); + if (!InputLandscape || InputLandscape->IsPendingKill()) + return CurrentSelectionText; + + ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); + if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + return CurrentSelectionText; + + CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); + } + } + break; + + case EHoudiniInputType::Asset : + { + if (AssetInputObjects.Num() > 0) + { + UHoudiniInputObject* InputObject = AssetInputObjects[0]; + + UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); + if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + return CurrentSelectionText; + + UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); + if (!HAC || HAC->IsPendingKill()) + return CurrentSelectionText; + + UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return CurrentSelectionText; + + CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); + } + } + break; + + default: + break; + } + + return CurrentSelectionText; +} +#endif + +bool +UHoudiniInput::HasLandscapeExportTypeChanged () const +{ + if (Type != EHoudiniInputType::Landscape) + return false; + + return bLandscapeHasExportTypeChanged; +} + +void +UHoudiniInput::SetHasLandscapeExportTypeChanged(const bool InChanged) +{ + if (Type != EHoudiniInputType::Landscape) + return; + + bLandscapeHasExportTypeChanged = InChanged; +} + +bool +UHoudiniInput::GetUpdateInputLandscape() const +{ + if (Type != EHoudiniInputType::Landscape) + return false; + + return bUpdateInputLandscape; +} + +void +UHoudiniInput::SetUpdateInputLandscape(const bool bInUpdateInputLandcape) +{ + if (Type != EHoudiniInputType::Landscape) + return; + + bUpdateInputLandscape = bInUpdateInputLandcape; +} + + +bool +UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() +{ + // Dont do anything if we're not a World Input + if (Type != EHoudiniInputType::World) + return false; + + // Build an array of the current selection's bounds + TArray AllBBox; + for (auto CurrentActor : WorldInputBoundSelectorObjects) + { + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); + } + + // + // Select all actors in our bound selectors bounding boxes + // + + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + + //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); + UWorld* MyWorld = GetWorld(); + TArray NewSelectedActors; + for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) + { + AActor *CurrentActor = *ActorItr; + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + // Check that actor is currently not selected + if (WorldInputBoundSelectorObjects.Contains(CurrentActor)) + continue; + + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + // For BrushActors, both the actor and its brush must be valid + ABrush* BrushActor = Cast(CurrentActor); + if (BrushActor) + { + if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) + continue; + } + + FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); + for (auto InBounds : AllBBox) + { + // Check if both actor's bounds intersects + if (!ActorBounds.Intersect(InBounds)) + continue; + + NewSelectedActors.Add(CurrentActor); + break; + } + } + + return UpdateWorldSelection(NewSelectedActors); +} + +bool +UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) +{ + TArray NewSelectedActors = InNewSelection; + + // Update our current selection with the new one + // Keep actors that are still selected, remove the one that are not selected anymore + bool bHasSelectionChanged = false; + for (int32 Idx = WorldInputObjects.Num() - 1; Idx >= 0; Idx--) + { + UHoudiniInputActor* InputActor = Cast(WorldInputObjects[Idx]); + AActor* CurActor = InputActor ? InputActor->GetActor() : nullptr; + + if (CurActor && NewSelectedActors.Contains(CurActor)) + { + // The actor is still selected, remove it from the new selection + NewSelectedActors.Remove(CurActor); + } + else + { + // That actor is no longer selected, remove itr from our current selection + DeleteInputObjectAt(EHoudiniInputType::World, Idx); + bHasSelectionChanged = true; + } + } + + if (NewSelectedActors.Num() > 0) + bHasSelectionChanged = true; + + // Then add the newly selected Actors + int32 InputObjectIdx = GetNumberOfInputObjects(EHoudiniInputType::World); + int32 NewInputObjectNumber = InputObjectIdx + NewSelectedActors.Num(); + SetInputObjectsNumber(EHoudiniInputType::World, NewInputObjectNumber); + for (const auto& CurActor : NewSelectedActors) + { + // Update the input objects from the valid selected actors array + SetInputObjectAt(InputObjectIdx++, CurActor); + } + + MarkChanged(bHasSelectionChanged); + + return bHasSelectionChanged; +} + + +bool +UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Returns true if the object is one of our input object for the given type + const TArray* ObjectArray = GetHoudiniInputObjectArray(InType); + if (!ObjectArray) + return false; + + for (auto& CurrentInputObject : (*ObjectArray)) + { + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + if (CurrentInputObject->GetObject() == InObject) + return true; + } + + return false; +} + +void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const +{ + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : SkeletalInputObjects) + { + Fn(InputObject); + } +} + +TArray*> UHoudiniInput::GetAllObjectArrays() const +{ + return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; +} + +TArray*> UHoudiniInput::GetAllObjectArrays() +{ + return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; +} + +void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const +{ + TArray*> ObjectArrays = GetAllObjectArrays(); + for (const TArray* ObjectArrayPtr : ObjectArrays) + { + if (!ObjectArrayPtr) + continue; + Fn(*ObjectArrayPtr); + } +} + +void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) +{ + TArray*> ObjectArrays = GetAllObjectArrays(); + for (TArray* ObjectArrayPtr : ObjectArrays) + { + if (!ObjectArrayPtr) + continue; + Fn(*ObjectArrayPtr); + } +} + +void UHoudiniInput::GetAllHoudiniInputObjects(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddInputObject = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (InputObject) + OutObjects.Add(InputObject); + }; + ForAllHoudiniInputObjects(AddInputObject); +} + +void UHoudiniInput::ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const +{ + auto ProcessSceneComponent = [Fn](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + Fn(SceneComponentInput); + }; + ForAllHoudiniInputObjects(ProcessSceneComponent); +} + +void UHoudiniInput::GetAllHoudiniInputSceneComponents(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + OutObjects.Add(SceneComponentInput); + }; + ForAllHoudiniInputObjects(AddSceneComponent); +} + +void UHoudiniInput::GetAllHoudiniInputSplineComponents(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputHoudiniSplineComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + OutObjects.Add(SceneComponentInput); + }; + ForAllHoudiniInputObjects(AddSceneComponent); +} + + +void UHoudiniInput::RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject) +{ + if (!InInputObject) + return; + + ForAllHoudiniInputObjectArrays([InInputObject](TArray& ObjectArray) { + ObjectArray.Remove(InInputObject); + }); + + return; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h index f51b86438..68c033317 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h @@ -1,561 +1,561 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreTypes.h" -#include "UObject/Object.h" -#include "UObject/ObjectMacros.h" -#include "Misc/Optional.h" - -#include "HoudiniInputTypes.h" -#include "HoudiniInputObject.h" - -#include "GameFramework/Actor.h" -#include "LandscapeProxy.h" - -#include "HoudiniInput.generated.h" - - - -class FReply; - -enum class EHoudiniCurveType : int8; -enum class ECheckBoxState : unsigned char; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject -{ - GENERATED_BODY() - -public: - - UHoudiniInput(); - - // Equality operator, - // We consider two inputs equals if they have the same name, objparam state, and input index/parmId - // TODO: ParmId might be an incorrect condition - bool operator==(const UHoudiniInput& other) const - { - return (bIsObjectPathParameter == other.bIsObjectPathParameter - && InputIndex == other.InputIndex - && ParmId == other.ParmId - && Name.Equals(other.Name) - && Label.Equals(other.Label)); - } - - bool Matches(const UHoudiniInput& other) const { return (*this == other); }; - - // Helper function returning a string from an InputType - static FString InputTypeToString(const EHoudiniInputType& InInputType); - - // Helper function returning an InputType from a string - static EHoudiniInputType StringToInputType(const FString& InInputTypeString); - // Helper function returning a Houdini curve type from a string - static EHoudiniCurveType StringToHoudiniCurveType(const FString& CurveTypeString); - // Helper function returning a Houdini curve method from a string - static EHoudiniCurveMethod StringToHoudiniCurveMethod(const FString& CurveMethodString); - - // Helper function indicating what classes are supported by an input type - static TArray GetAllowedClasses(const EHoudiniInputType& InInputType); - - // Helper function indicating if an object is supported by an input type - static bool IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject); - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - // Returns the NodeId of the asset / object merge we are associated with - int32 GetAssetNodeId() const { return AssetNodeId; }; - // For objpath parameter, return the associated ParamId, -1 if we're a Geo input - int32 GetParameterId() const { return bIsObjectPathParameter ? ParmId : -1; }; - // Returns the NodeId of the node plugged into this input - int32 GetInputNodeId() const { return InputNodeId; }; - - // For Geo inputs, returns the InputIndex, -1 if we're an object path parameter - int32 GetInputIndex() const { return bIsObjectPathParameter ? -1 : InputIndex; }; - // Return the array containing all the nodes created for this input's data - TArray& GetCreatedDataNodeIds() { return CreatedDataNodeIds; }; - // Returns the current input type - EHoudiniInputType GetInputType() const { return Type; }; - // Returns the previous input type - EHoudiniInputType GetPreviousInputType() const { return PreviousType; }; - // Returns the current input type as a string - FString GetInputTypeAsString() const { return InputTypeToString(Type); }; - - EHoudiniXformType GetDefaultXTransformType(); - // Returns true when this input's Transform Type is set to NONE, - // false if set to INTO_THIS_OBJECT, 2 will use the input's type default value - bool GetKeepWorldTransform() const; - // Indicates if this input has changed and should be updated - bool HasChanged(); - // Indicates if this input needs to trigger an update - bool NeedsToTriggerUpdate(); - // Indicates this input should upload its data - bool IsDataUploadNeeded(); - // Indicates this input's transform need to be uploaded - bool IsTransformUploadNeeded(); - // Indicates if this input type has been changed - bool HasInputTypeChanged() const { return PreviousType != EHoudiniInputType::Invalid ? PreviousType != Type : false; } - // - bool GetUpdateInputLandscape() const; - - void SetUpdateInputLandscape(const bool bInUpdateInputLandcape); - - FString GetName() const { return Name; }; - FString GetLabel() const { return Label; }; - FString GetHelp() const { return Help; }; - bool GetPackBeforeMerge() const { return bPackBeforeMerge; }; - bool GetImportAsReference() const { return bImportAsReference; }; - bool GetExportLODs() const { return bExportLODs; }; - bool GetExportSockets() const { return bExportSockets; }; - bool GetExportColliders() const { return bExportColliders; }; - bool IsObjectPathParameter() const { return bIsObjectPathParameter; }; - float GetUnrealSplineResolution() const { return UnrealSplineResolution; }; - - virtual bool GetCookOnCurveChange() const { return bCookOnCurveChanged; }; - - TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType); - const TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const; - TArray* GetBoundSelectorObjectArray(); - - UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex); - const UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex) const; - AActor* GetBoundSelectorObjectAt(const int32& AtIndex); - - UHoudiniInputObject* GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - UObject* GetInputObjectAt(const int32& AtIndex); - UObject* GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - int32 GetNumberOfInputObjects(); - int32 GetNumberOfInputObjects(const EHoudiniInputType& InType); - - int32 GetNumberOfInputMeshes(); - int32 GetNumberOfInputMeshes(const EHoudiniInputType& InType); - - int32 GetNumberOfBoundSelectorObjects() const; - - bool IsWorldInputBoundSelector() const { return bIsWorldInputBoundSelector; }; - bool GetWorldInputBoundSelectorAutoUpdates() const { return bWorldInputBoundSelectorAutoUpdate; }; - - FString GetNodeBaseName() const; - - bool IsTransformUIExpanded(const int32& AtIndex); - - // Return the transform offset for a given input object - FTransform* GetTransformOffset(const int32& AtIndex); - const FTransform GetTransformOffset(const int32& AtIndex) const; - - // Returns the position offset for a given input object - TOptional GetPositionOffsetX(int32 AtIndex) const; - TOptional GetPositionOffsetY(int32 AtIndex) const; - TOptional GetPositionOffsetZ(int32 AtIndex) const; - - // Returns the rotation offset for a given input object - TOptional GetRotationOffsetRoll(int32 AtIndex) const; - TOptional GetRotationOffsetPitch(int32 AtIndex) const; - TOptional GetRotationOffsetYaw(int32 AtIndex) const; - - // Returns the scale offset for a given input object - TOptional GetScaleOffsetX(int32 AtIndex) const; - TOptional GetScaleOffsetY(int32 AtIndex) const; - TOptional GetScaleOffsetZ(int32 AtIndex) const; - - // Returns true if the object is one of our input object for the given type - bool ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const; - - // Get all input object arrays - TArray*> GetAllObjectArrays() const; - TArray*> GetAllObjectArrays(); - - // Iterate over all input object arrays - void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const; - void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn); - - void ForAllHoudiniInputObjects(TFunctionRef Fn) const; - // Collect top-level HoudiniInputObjects from this UHoudiniInput. Does not traverse nested input objects. - void GetAllHoudiniInputObjects(TArray& OutObjects) const; - // Collect top-level UHoudiniInputSceneComponent from this UHoudiniInput. Does not traverse nested input objects. - void ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const; - void GetAllHoudiniInputSceneComponents(TArray& OutObjects) const; - void GetAllHoudiniInputSplineComponents(TArray& OutObjects) const; - - - // Remove all instances of this input object from all object arrays. - void RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject); - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - - void MarkChanged(const bool& bInChanged) - { - bHasChanged = bInChanged; - SetNeedsToTriggerUpdate(bInChanged); - }; - void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - void MarkDataUploadNeeded(const bool& bInDataUploadNeeded) { bDataUploadNeeded = bInDataUploadNeeded; }; - void MarkAllInputObjectsChanged(const bool& bInChanged); - - void SetSOPInput(const int32& InInputIndex); - void SetObjectPathParameter(const int32& InParmId); - void SetKeepWorldTransform(const bool& bInKeepWorldTransform); - - void SetName(const FString& InName) { Name = InName; }; - void SetLabel(const FString& InLabel) { Label = InLabel; }; - void SetHelp(const FString& InHelp) { Help = InHelp; }; - void SetAssetNodeId(const int32& InNodeId) { AssetNodeId = InNodeId; }; - void SetInputType(const EHoudiniInputType &InInputType, bool& bOutBlueprintStructureModified); - void SetPreviousInputType(const EHoudiniInputType& InType) { PreviousType = InType; }; - void SetPackBeforeMerge(const bool& bInPackBeforeMerge) { bPackBeforeMerge = bInPackBeforeMerge; }; - void SetImportAsReference(const bool& bInImportAsReference) { bImportAsReference = bInImportAsReference; }; - void SetExportLODs(const bool& bInExportLODs) { bExportLODs = bInExportLODs; }; - void SetExportSockets(const bool& bInExportSockets) { bExportSockets = bInExportSockets; }; - void SetExportColliders(const bool& bInExportColliders) { bExportColliders = bInExportColliders; }; - void SetInputNodeId(const int32& InCreatedNodeId) { InputNodeId = InCreatedNodeId; }; - void SetUnrealSplineResolution(const float& InResolution) { UnrealSplineResolution = InResolution; }; - - virtual void SetCookOnCurveChange(const bool & bInCookOnCurveChanged) { bCookOnCurveChanged = bInCookOnCurveChanged; }; - - void ResetDefaultCurveOffset() { DefaultCurveOffset = 0.f; } - - UHoudiniInputObject* CreateNewCurveInputObject(bool& bBlueprintStructureModified); - - void SetGeometryInputObjectsNumber(const int32& NewCount); - void SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount); - - void InsertInputObjectAt(const int32& AtIndex); - void InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void DeleteInputObjectAt(const int32& AtIndex); - void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void DuplicateInputObjectAt(const int32& AtIndex); - void DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void SetInputObjectAt(const int32& AtIndex, UObject* InObject); - void SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject); - - void SetBoundSelectorObjectsNumber(const int32& InNewCount); - void SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor); - void SetWorldInputBoundSelector(const bool& InIsBoundSelector) { bIsWorldInputBoundSelector = InIsBoundSelector; }; - void SetWorldInputBoundSelectorAutoUpdates(const bool& InAutoUpdate) { bWorldInputBoundSelectorAutoUpdate = InAutoUpdate; }; - - // Updates the world selection using bound selectors - // returns false if the selection hasn't changed - bool UpdateWorldSelectionFromBoundSelectors(); - // Updates the world selection - // returns false if the selection hasn't changed - bool UpdateWorldSelection(const TArray& InNewSelection); - - void OnTransformUIExpand(const int32& AtIndex); - - // Sets the input's transform offset - bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - - // Sets the input's transform scale values - void SetPositionOffsetX(float InValue, int32 AtIndex); - void SetPositionOffsetY(float InValue, int32 AtIndex); - void SetPositionOffsetZ(float InValue, int32 AtIndex); - - // Sets the input's transform rotation value - void SetRotationOffsetRoll(float InValue, int32 AtIndex); - void SetRotationOffsetPitch(float InValue, int32 AtIndex); - void SetRotationOffsetYaw(float InValue, int32 AtIndex); - - // Sets the input's transform scale values - void SetScaleOffsetX(float InValue, int32 AtIndex); - void SetScaleOffsetY(float InValue, int32 AtIndex); - void SetScaleOffsetZ(float InValue, int32 AtIndex); - - - // Duplicate this object and copy its state to the resulting object. - // This is typically used to transfer state between between template and instance components. - UHoudiniInput* DuplicateAndCopyState(UObject* DestOuter, bool bInCanDeleteHoudiniNodes); - virtual void CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes); - - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() { return bCanDeleteHoudiniNodes; } - - virtual void InvalidateData(); - -protected: - void CopyInputs(TArray& ToInputs, TArray& FromInputs, bool bInCanDeleteHoudiniNodes); - -public: - - // Create a Houdini Spline input component, with an existing Houdini Spline input Object. - // Pass in nullptr to create a default Houdini Spline - UHoudiniInputHoudiniSplineComponent* CreateHoudiniSplineInput( - UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputObject, - const bool & bAttachToParent, - const bool & bAppendToInputArray, - bool& bOutBlueprintStructureModified); - - // Given an existing spline input object, remove the associated - // Houdini Spline component from the owning actor / blueprint. - void RemoveSplineFromInputObject( - UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, - bool& bOutBlueprintStructureModified) const; - - bool HasLandscapeExportTypeChanged () const; - - void SetHasLandscapeExportTypeChanged(const bool InChanged); - -#if WITH_EDITOR - FText GetCurrentSelectionText() const; -#endif - - EHoudiniLandscapeExportType GetLandscapeExportType() const { return LandscapeExportType; }; - - void SetLandscapeExportType(const EHoudiniLandscapeExportType InType) { LandscapeExportType = InType; }; - - virtual void BeginDestroy() override; - -#if WITH_EDITOR - virtual void PostEditUndo() override; -#endif - - FBox GetBounds() const; - -protected: - - // Name of the input / Object path parameter - UPROPERTY() - FString Name; - - // Label of the SOP input or of the object path parameter - UPROPERTY() - FString Label; - - // Input type - UPROPERTY() - EHoudiniInputType Type; - - // Previous type, used to detect input type changes - UPROPERTY(Transient, DuplicateTransient) - EHoudiniInputType PreviousType; - - // NodeId of the asset / object merge we are associated with - UPROPERTY(Transient, DuplicateTransient) - int32 AssetNodeId; - - // NodeId of the created input node - // when there is multiple inputs objects, this will be the merge node. - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputNodeId; - - // SOP input index (-1 if we're an object path input) - UPROPERTY() - int32 InputIndex; - - // Parameter Id of the associated object path parameter (-1 if we're a SOP input) - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 ParmId; - - // Indicates if we're an object path parameter input - UPROPERTY() - bool bIsObjectPathParameter; - - // Array containing all the node Ids created by this input - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - TArray CreatedDataNodeIds; - - // Indicates data connected to this input should be uploaded - UPROPERTY(Transient, DuplicateTransient) - bool bHasChanged; - - // Indicates this input should trigger an HDA update/cook - UPROPERTY(Transient, DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Indicates data for this input needs to be uploaded - // If this is false but the input has changed, we may have just updated in input parameter, - // and don't need to resend all the input data - bool bDataUploadNeeded; - - // Help for this parameter/input - UPROPERTY() - FString Help; - - //------------------------------------------------------------------------------------------------------------------------- - // General Input options - - // Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value - UPROPERTY() - EHoudiniXformType KeepWorldTransform; - - // Indicates that the geometry must be packed before merging it into the input - UPROPERTY() - bool bPackBeforeMerge; - - // Indicates that all the input objects are imported to Houdini as references instead of actual geo - // (for Geo/World/Asset input types only) - UPROPERTY() - bool bImportAsReference = false; - - // Indicates that all LODs in the input should be marshalled to Houdini - UPROPERTY() - bool bExportLODs; - - // Indicates that all sockets in the input should be marshalled to Houdini - UPROPERTY() - bool bExportSockets; - - // Indicates that all colliders in the input should be marshalled to Houdini - UPROPERTY() - bool bExportColliders; - - // Indicates that if trigger cook automatically on curve Input spline modified - UPROPERTY() - bool bCookOnCurveChanged; - - //------------------------------------------------------------------------------------------------------------------------- - // Geometry objects - UPROPERTY() - TArray GeometryInputObjects; - // ?? TArray GeometryInputObjects; - - // Is set to true when static mesh used for geometry input has changed. - UPROPERTY() - bool bStaticMeshChanged; - -#if WITH_EDITORONLY_DATA - // Are the transform UI expanded ? - // Values default to false and are actually added to the array in OnTransformUIExpand() - UPROPERTY() - TArray TransformUIExpanded; -#endif - - //------------------------------------------------------------------------------------------------------------------------- - // Asset inputs - UPROPERTY() - TArray AssetInputObjects; - // ?? TArray AssetInputObjects; - - // Is set to true if the asset input is actually connected inside Houdini. - UPROPERTY() - bool bInputAssetConnectedInHoudini; - - //------------------------------------------------------------------------------------------------------------------------- - // Curve/Spline inputs - UPROPERTY() - TArray CurveInputObjects; - // ?? TArray CurveInputObjects; - - // Is set to true when choice switches to curve mode. - UPROPERTY() - bool bSwitchedToCurve; - - UPROPERTY() - float DefaultCurveOffset; - - //------------------------------------------------------------------------------------------------------------------------- - // Landscape inputs - UPROPERTY() - TArray LandscapeInputObjects; - // ?? TArray LandscapeInputObjects; - - UPROPERTY() - bool bLandscapeHasExportTypeChanged = false; - - //------------------------------------------------------------------------------------------------------------------------- - // World inputs - UPROPERTY() - TArray WorldInputObjects; - // ?? TArray WorldInputObjects; - - // Objects used for automatic bound selection - // ?? TArray WorldInputBoundSelectorObjects; - UPROPERTY() - TArray WorldInputBoundSelectorObjects; - - // Indicates that this world input is in "BoundSelector" mode - UPROPERTY() - bool bIsWorldInputBoundSelector; - - // Indicates that selected actors by the bound selectors should update automatically - UPROPERTY() - bool bWorldInputBoundSelectorAutoUpdate; - - // Resolution used when converting unreal splines to houdini curves - UPROPERTY() - float UnrealSplineResolution; - - //------------------------------------------------------------------------------------------------------------------------- - // Skeletal Inputs - UPROPERTY() - TArray SkeletalInputObjects; - // ?? TArray SkeletalInputObjects; - -public: - - // This array is to record the last insert action, for undo input insertion actions. - UPROPERTY(Transient, DuplicateTransient) - TArray LastInsertedInputs; - - // This array is to cache the action of last undo delete action, and redo that action. - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - TArray LastUndoDeletedInputs; - - - // Indicates that the landscape input's source landscape should be updated instead of creating a new component - UPROPERTY() - bool bUpdateInputLandscape; - - // Indicates if the landscape should be exported as heightfield, mesh or points - UPROPERTY() - EHoudiniLandscapeExportType LandscapeExportType = EHoudiniLandscapeExportType::Heightfield; - - // Is set to true when landscape input is set to selection only. - UPROPERTY() - bool bLandscapeExportSelectionOnly = false; - - // Is set to true when the automatic selection of landscape component is active - UPROPERTY() - bool bLandscapeAutoSelectComponent = false; - - // Is set to true when materials are to be exported. - UPROPERTY() - bool bLandscapeExportMaterials = false; - - // Is set to true when lightmap information export is desired. - UPROPERTY() - bool bLandscapeExportLighting = false; - - // Is set to true when uvs should be exported in [0,1] space. - UPROPERTY() - bool bLandscapeExportNormalizedUVs = false; - - // Is set to true when uvs should be exported for each tile separately. - UPROPERTY() - bool bLandscapeExportTileUVs = false; - - UPROPERTY() - bool bCanDeleteHoudiniNodes = true; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreTypes.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" +#include "Misc/Optional.h" + +#include "HoudiniInputTypes.h" +#include "HoudiniInputObject.h" + +#include "GameFramework/Actor.h" +#include "LandscapeProxy.h" + +#include "HoudiniInput.generated.h" + + + +class FReply; + +enum class EHoudiniCurveType : int8; +enum class ECheckBoxState : unsigned char; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject +{ + GENERATED_BODY() + +public: + + UHoudiniInput(); + + // Equality operator, + // We consider two inputs equals if they have the same name, objparam state, and input index/parmId + // TODO: ParmId might be an incorrect condition + bool operator==(const UHoudiniInput& other) const + { + return (bIsObjectPathParameter == other.bIsObjectPathParameter + && InputIndex == other.InputIndex + && ParmId == other.ParmId + && Name.Equals(other.Name) + && Label.Equals(other.Label)); + } + + bool Matches(const UHoudiniInput& other) const { return (*this == other); }; + + // Helper function returning a string from an InputType + static FString InputTypeToString(const EHoudiniInputType& InInputType); + + // Helper function returning an InputType from a string + static EHoudiniInputType StringToInputType(const FString& InInputTypeString); + // Helper function returning a Houdini curve type from a string + static EHoudiniCurveType StringToHoudiniCurveType(const FString& CurveTypeString); + // Helper function returning a Houdini curve method from a string + static EHoudiniCurveMethod StringToHoudiniCurveMethod(const FString& CurveMethodString); + + // Helper function indicating what classes are supported by an input type + static TArray GetAllowedClasses(const EHoudiniInputType& InInputType); + + // Helper function indicating if an object is supported by an input type + static bool IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject); + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + // Returns the NodeId of the asset / object merge we are associated with + int32 GetAssetNodeId() const { return AssetNodeId; }; + // For objpath parameter, return the associated ParamId, -1 if we're a Geo input + int32 GetParameterId() const { return bIsObjectPathParameter ? ParmId : -1; }; + // Returns the NodeId of the node plugged into this input + int32 GetInputNodeId() const { return InputNodeId; }; + + // For Geo inputs, returns the InputIndex, -1 if we're an object path parameter + int32 GetInputIndex() const { return bIsObjectPathParameter ? -1 : InputIndex; }; + // Return the array containing all the nodes created for this input's data + TArray& GetCreatedDataNodeIds() { return CreatedDataNodeIds; }; + // Returns the current input type + EHoudiniInputType GetInputType() const { return Type; }; + // Returns the previous input type + EHoudiniInputType GetPreviousInputType() const { return PreviousType; }; + // Returns the current input type as a string + FString GetInputTypeAsString() const { return InputTypeToString(Type); }; + + EHoudiniXformType GetDefaultXTransformType(); + // Returns true when this input's Transform Type is set to NONE, + // false if set to INTO_THIS_OBJECT, 2 will use the input's type default value + bool GetKeepWorldTransform() const; + // Indicates if this input has changed and should be updated + bool HasChanged(); + // Indicates if this input needs to trigger an update + bool NeedsToTriggerUpdate(); + // Indicates this input should upload its data + bool IsDataUploadNeeded(); + // Indicates this input's transform need to be uploaded + bool IsTransformUploadNeeded(); + // Indicates if this input type has been changed + bool HasInputTypeChanged() const { return PreviousType != EHoudiniInputType::Invalid ? PreviousType != Type : false; } + // + bool GetUpdateInputLandscape() const; + + void SetUpdateInputLandscape(const bool bInUpdateInputLandcape); + + FString GetName() const { return Name; }; + FString GetLabel() const { return Label; }; + FString GetHelp() const { return Help; }; + bool GetPackBeforeMerge() const { return bPackBeforeMerge; }; + bool GetImportAsReference() const { return bImportAsReference; }; + bool GetExportLODs() const { return bExportLODs; }; + bool GetExportSockets() const { return bExportSockets; }; + bool GetExportColliders() const { return bExportColliders; }; + bool IsObjectPathParameter() const { return bIsObjectPathParameter; }; + float GetUnrealSplineResolution() const { return UnrealSplineResolution; }; + + virtual bool GetCookOnCurveChange() const { return bCookOnCurveChanged; }; + + TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType); + const TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const; + TArray* GetBoundSelectorObjectArray(); + + UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex); + const UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex) const; + AActor* GetBoundSelectorObjectAt(const int32& AtIndex); + + UHoudiniInputObject* GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + UObject* GetInputObjectAt(const int32& AtIndex); + UObject* GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + int32 GetNumberOfInputObjects(); + int32 GetNumberOfInputObjects(const EHoudiniInputType& InType); + + int32 GetNumberOfInputMeshes(); + int32 GetNumberOfInputMeshes(const EHoudiniInputType& InType); + + int32 GetNumberOfBoundSelectorObjects() const; + + bool IsWorldInputBoundSelector() const { return bIsWorldInputBoundSelector; }; + bool GetWorldInputBoundSelectorAutoUpdates() const { return bWorldInputBoundSelectorAutoUpdate; }; + + FString GetNodeBaseName() const; + + bool IsTransformUIExpanded(const int32& AtIndex); + + // Return the transform offset for a given input object + FTransform* GetTransformOffset(const int32& AtIndex); + const FTransform GetTransformOffset(const int32& AtIndex) const; + + // Returns the position offset for a given input object + TOptional GetPositionOffsetX(int32 AtIndex) const; + TOptional GetPositionOffsetY(int32 AtIndex) const; + TOptional GetPositionOffsetZ(int32 AtIndex) const; + + // Returns the rotation offset for a given input object + TOptional GetRotationOffsetRoll(int32 AtIndex) const; + TOptional GetRotationOffsetPitch(int32 AtIndex) const; + TOptional GetRotationOffsetYaw(int32 AtIndex) const; + + // Returns the scale offset for a given input object + TOptional GetScaleOffsetX(int32 AtIndex) const; + TOptional GetScaleOffsetY(int32 AtIndex) const; + TOptional GetScaleOffsetZ(int32 AtIndex) const; + + // Returns true if the object is one of our input object for the given type + bool ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const; + + // Get all input object arrays + TArray*> GetAllObjectArrays() const; + TArray*> GetAllObjectArrays(); + + // Iterate over all input object arrays + void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const; + void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn); + + void ForAllHoudiniInputObjects(TFunctionRef Fn) const; + // Collect top-level HoudiniInputObjects from this UHoudiniInput. Does not traverse nested input objects. + void GetAllHoudiniInputObjects(TArray& OutObjects) const; + // Collect top-level UHoudiniInputSceneComponent from this UHoudiniInput. Does not traverse nested input objects. + void ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const; + void GetAllHoudiniInputSceneComponents(TArray& OutObjects) const; + void GetAllHoudiniInputSplineComponents(TArray& OutObjects) const; + + + // Remove all instances of this input object from all object arrays. + void RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject); + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + + void MarkChanged(const bool& bInChanged) + { + bHasChanged = bInChanged; + SetNeedsToTriggerUpdate(bInChanged); + }; + void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + void MarkDataUploadNeeded(const bool& bInDataUploadNeeded) { bDataUploadNeeded = bInDataUploadNeeded; }; + void MarkAllInputObjectsChanged(const bool& bInChanged); + + void SetSOPInput(const int32& InInputIndex); + void SetObjectPathParameter(const int32& InParmId); + void SetKeepWorldTransform(const bool& bInKeepWorldTransform); + + void SetName(const FString& InName) { Name = InName; }; + void SetLabel(const FString& InLabel) { Label = InLabel; }; + void SetHelp(const FString& InHelp) { Help = InHelp; }; + void SetAssetNodeId(const int32& InNodeId) { AssetNodeId = InNodeId; }; + void SetInputType(const EHoudiniInputType &InInputType, bool& bOutBlueprintStructureModified); + void SetPreviousInputType(const EHoudiniInputType& InType) { PreviousType = InType; }; + void SetPackBeforeMerge(const bool& bInPackBeforeMerge) { bPackBeforeMerge = bInPackBeforeMerge; }; + void SetImportAsReference(const bool& bInImportAsReference) { bImportAsReference = bInImportAsReference; }; + void SetExportLODs(const bool& bInExportLODs) { bExportLODs = bInExportLODs; }; + void SetExportSockets(const bool& bInExportSockets) { bExportSockets = bInExportSockets; }; + void SetExportColliders(const bool& bInExportColliders) { bExportColliders = bInExportColliders; }; + void SetInputNodeId(const int32& InCreatedNodeId) { InputNodeId = InCreatedNodeId; }; + void SetUnrealSplineResolution(const float& InResolution) { UnrealSplineResolution = InResolution; }; + + virtual void SetCookOnCurveChange(const bool & bInCookOnCurveChanged) { bCookOnCurveChanged = bInCookOnCurveChanged; }; + + void ResetDefaultCurveOffset() { DefaultCurveOffset = 0.f; } + + UHoudiniInputObject* CreateNewCurveInputObject(bool& bBlueprintStructureModified); + + void SetGeometryInputObjectsNumber(const int32& NewCount); + void SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount); + + void InsertInputObjectAt(const int32& AtIndex); + void InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void DeleteInputObjectAt(const int32& AtIndex); + void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void DuplicateInputObjectAt(const int32& AtIndex); + void DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void SetInputObjectAt(const int32& AtIndex, UObject* InObject); + void SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject); + + void SetBoundSelectorObjectsNumber(const int32& InNewCount); + void SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor); + void SetWorldInputBoundSelector(const bool& InIsBoundSelector) { bIsWorldInputBoundSelector = InIsBoundSelector; }; + void SetWorldInputBoundSelectorAutoUpdates(const bool& InAutoUpdate) { bWorldInputBoundSelectorAutoUpdate = InAutoUpdate; }; + + // Updates the world selection using bound selectors + // returns false if the selection hasn't changed + bool UpdateWorldSelectionFromBoundSelectors(); + // Updates the world selection + // returns false if the selection hasn't changed + bool UpdateWorldSelection(const TArray& InNewSelection); + + void OnTransformUIExpand(const int32& AtIndex); + + // Sets the input's transform offset + bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + + // Sets the input's transform scale values + void SetPositionOffsetX(float InValue, int32 AtIndex); + void SetPositionOffsetY(float InValue, int32 AtIndex); + void SetPositionOffsetZ(float InValue, int32 AtIndex); + + // Sets the input's transform rotation value + void SetRotationOffsetRoll(float InValue, int32 AtIndex); + void SetRotationOffsetPitch(float InValue, int32 AtIndex); + void SetRotationOffsetYaw(float InValue, int32 AtIndex); + + // Sets the input's transform scale values + void SetScaleOffsetX(float InValue, int32 AtIndex); + void SetScaleOffsetY(float InValue, int32 AtIndex); + void SetScaleOffsetZ(float InValue, int32 AtIndex); + + + // Duplicate this object and copy its state to the resulting object. + // This is typically used to transfer state between between template and instance components. + UHoudiniInput* DuplicateAndCopyState(UObject* DestOuter, bool bInCanDeleteHoudiniNodes); + virtual void CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes); + + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() { return bCanDeleteHoudiniNodes; } + + virtual void InvalidateData(); + +protected: + void CopyInputs(TArray& ToInputs, TArray& FromInputs, bool bInCanDeleteHoudiniNodes); + +public: + + // Create a Houdini Spline input component, with an existing Houdini Spline input Object. + // Pass in nullptr to create a default Houdini Spline + UHoudiniInputHoudiniSplineComponent* CreateHoudiniSplineInput( + UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputObject, + const bool & bAttachToParent, + const bool & bAppendToInputArray, + bool& bOutBlueprintStructureModified); + + // Given an existing spline input object, remove the associated + // Houdini Spline component from the owning actor / blueprint. + void RemoveSplineFromInputObject( + UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, + bool& bOutBlueprintStructureModified) const; + + bool HasLandscapeExportTypeChanged () const; + + void SetHasLandscapeExportTypeChanged(const bool InChanged); + +#if WITH_EDITOR + FText GetCurrentSelectionText() const; +#endif + + EHoudiniLandscapeExportType GetLandscapeExportType() const { return LandscapeExportType; }; + + void SetLandscapeExportType(const EHoudiniLandscapeExportType InType) { LandscapeExportType = InType; }; + + virtual void BeginDestroy() override; + +#if WITH_EDITOR + virtual void PostEditUndo() override; +#endif + + FBox GetBounds() const; + +protected: + + // Name of the input / Object path parameter + UPROPERTY() + FString Name; + + // Label of the SOP input or of the object path parameter + UPROPERTY() + FString Label; + + // Input type + UPROPERTY() + EHoudiniInputType Type; + + // Previous type, used to detect input type changes + UPROPERTY(Transient, DuplicateTransient) + EHoudiniInputType PreviousType; + + // NodeId of the asset / object merge we are associated with + UPROPERTY(Transient, DuplicateTransient) + int32 AssetNodeId; + + // NodeId of the created input node + // when there is multiple inputs objects, this will be the merge node. + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputNodeId; + + // SOP input index (-1 if we're an object path input) + UPROPERTY() + int32 InputIndex; + + // Parameter Id of the associated object path parameter (-1 if we're a SOP input) + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 ParmId; + + // Indicates if we're an object path parameter input + UPROPERTY() + bool bIsObjectPathParameter; + + // Array containing all the node Ids created by this input + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + TArray CreatedDataNodeIds; + + // Indicates data connected to this input should be uploaded + UPROPERTY(Transient, DuplicateTransient) + bool bHasChanged; + + // Indicates this input should trigger an HDA update/cook + UPROPERTY(Transient, DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Indicates data for this input needs to be uploaded + // If this is false but the input has changed, we may have just updated in input parameter, + // and don't need to resend all the input data + bool bDataUploadNeeded; + + // Help for this parameter/input + UPROPERTY() + FString Help; + + //------------------------------------------------------------------------------------------------------------------------- + // General Input options + + // Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value + UPROPERTY() + EHoudiniXformType KeepWorldTransform; + + // Indicates that the geometry must be packed before merging it into the input + UPROPERTY() + bool bPackBeforeMerge; + + // Indicates that all the input objects are imported to Houdini as references instead of actual geo + // (for Geo/World/Asset input types only) + UPROPERTY() + bool bImportAsReference = false; + + // Indicates that all LODs in the input should be marshalled to Houdini + UPROPERTY() + bool bExportLODs; + + // Indicates that all sockets in the input should be marshalled to Houdini + UPROPERTY() + bool bExportSockets; + + // Indicates that all colliders in the input should be marshalled to Houdini + UPROPERTY() + bool bExportColliders; + + // Indicates that if trigger cook automatically on curve Input spline modified + UPROPERTY() + bool bCookOnCurveChanged; + + //------------------------------------------------------------------------------------------------------------------------- + // Geometry objects + UPROPERTY() + TArray GeometryInputObjects; + // ?? TArray GeometryInputObjects; + + // Is set to true when static mesh used for geometry input has changed. + UPROPERTY() + bool bStaticMeshChanged; + +#if WITH_EDITORONLY_DATA + // Are the transform UI expanded ? + // Values default to false and are actually added to the array in OnTransformUIExpand() + UPROPERTY() + TArray TransformUIExpanded; +#endif + + //------------------------------------------------------------------------------------------------------------------------- + // Asset inputs + UPROPERTY() + TArray AssetInputObjects; + // ?? TArray AssetInputObjects; + + // Is set to true if the asset input is actually connected inside Houdini. + UPROPERTY() + bool bInputAssetConnectedInHoudini; + + //------------------------------------------------------------------------------------------------------------------------- + // Curve/Spline inputs + UPROPERTY() + TArray CurveInputObjects; + // ?? TArray CurveInputObjects; + + // Is set to true when choice switches to curve mode. + UPROPERTY() + bool bSwitchedToCurve; + + UPROPERTY() + float DefaultCurveOffset; + + //------------------------------------------------------------------------------------------------------------------------- + // Landscape inputs + UPROPERTY() + TArray LandscapeInputObjects; + // ?? TArray LandscapeInputObjects; + + UPROPERTY() + bool bLandscapeHasExportTypeChanged = false; + + //------------------------------------------------------------------------------------------------------------------------- + // World inputs + UPROPERTY() + TArray WorldInputObjects; + // ?? TArray WorldInputObjects; + + // Objects used for automatic bound selection + // ?? TArray WorldInputBoundSelectorObjects; + UPROPERTY() + TArray WorldInputBoundSelectorObjects; + + // Indicates that this world input is in "BoundSelector" mode + UPROPERTY() + bool bIsWorldInputBoundSelector; + + // Indicates that selected actors by the bound selectors should update automatically + UPROPERTY() + bool bWorldInputBoundSelectorAutoUpdate; + + // Resolution used when converting unreal splines to houdini curves + UPROPERTY() + float UnrealSplineResolution; + + //------------------------------------------------------------------------------------------------------------------------- + // Skeletal Inputs + UPROPERTY() + TArray SkeletalInputObjects; + // ?? TArray SkeletalInputObjects; + +public: + + // This array is to record the last insert action, for undo input insertion actions. + UPROPERTY(Transient, DuplicateTransient) + TArray LastInsertedInputs; + + // This array is to cache the action of last undo delete action, and redo that action. + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + TArray LastUndoDeletedInputs; + + + // Indicates that the landscape input's source landscape should be updated instead of creating a new component + UPROPERTY() + bool bUpdateInputLandscape; + + // Indicates if the landscape should be exported as heightfield, mesh or points + UPROPERTY() + EHoudiniLandscapeExportType LandscapeExportType = EHoudiniLandscapeExportType::Heightfield; + + // Is set to true when landscape input is set to selection only. + UPROPERTY() + bool bLandscapeExportSelectionOnly = false; + + // Is set to true when the automatic selection of landscape component is active + UPROPERTY() + bool bLandscapeAutoSelectComponent = false; + + // Is set to true when materials are to be exported. + UPROPERTY() + bool bLandscapeExportMaterials = false; + + // Is set to true when lightmap information export is desired. + UPROPERTY() + bool bLandscapeExportLighting = false; + + // Is set to true when uvs should be exported in [0,1] space. + UPROPERTY() + bool bLandscapeExportNormalizedUVs = false; + + // Is set to true when uvs should be exported for each tile separately. + UPROPERTY() + bool bLandscapeExportTileUVs = false; + + UPROPERTY() + bool bCanDeleteHoudiniNodes = true; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp index 191782bbc..a03c3cdae 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp @@ -27,12 +27,14 @@ #include "HoudiniInputObject.h" #include "HoudiniEngineRuntime.h" +#include "HoudiniAssetActor.h" #include "HoudiniAssetComponent.h" #include "HoudiniSplineComponent.h" #include "HoudiniInput.h" #include "Engine/StaticMesh.h" #include "Engine/SkeletalMesh.h" +#include "Engine/DataTable.h" #include "Components/StaticMeshComponent.h" #include "Components/InstancedStaticMeshComponent.h" #include "Components/SplineComponent.h" @@ -65,7 +67,7 @@ UHoudiniInputObject::UHoudiniInputObject(const FObjectInitializer& ObjectInitial , bImportAsReference(false) , bCanDeleteHoudiniNodes(true) { - + Guid = FGuid::NewGuid(); } // @@ -226,7 +228,7 @@ UHoudiniInputBrush::UHoudiniInputBrush() //----------------------------------------------------------------------------------------------------------------------------- UObject* -UHoudiniInputObject::GetObject() +UHoudiniInputObject::GetObject() const { return InputObject.LoadSynchronous(); } @@ -285,9 +287,10 @@ UHoudiniInputSplineComponent::GetSplineComponent() } UHoudiniSplineComponent* -UHoudiniInputHoudiniSplineComponent::GetCurveComponent() +UHoudiniInputHoudiniSplineComponent::GetCurveComponent() const { - return MyHoudiniSplineComponent; + return Cast(GetObject()); + //return Cast(InputObject.LoadSynchronous()); } UCameraComponent* @@ -374,6 +377,21 @@ UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter case EHoudiniInputObjectType::HoudiniSplineComponent: HoudiniInputObject = UHoudiniInputHoudiniSplineComponent::Create(InObject, InOuter, InName); break; + + case EHoudiniInputObjectType::HoudiniAssetActor: + { + AHoudiniAssetActor* HoudiniActor = Cast(InObject); + if (HoudiniActor) + { + HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniActor->GetHoudiniAssetComponent(), InOuter, InName); + } + else + { + HoudiniInputObject = nullptr; + } + } + break; + case EHoudiniInputObjectType::HoudiniAssetComponent: HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(InObject, InOuter, InName); break; @@ -393,6 +411,10 @@ UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter HoudiniInputObject = UHoudiniInputCameraComponent::Create(InObject, InOuter, InName); break; + case EHoudiniInputObjectType::DataTable: + HoudiniInputObject = UHoudiniInputDataTable::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::Invalid: default: break; @@ -470,15 +492,6 @@ UHoudiniInputHoudiniSplineComponent::Create(UObject * InObject, UObject* InOuter return HoudiniInputObject; } -void -UHoudiniInputHoudiniSplineComponent::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - Super::CopyStateFrom(InInput, bCopyAllProperties); - // Clear component references since we don't currently support duplicating input components. - InputObject.Reset(); - MyHoudiniSplineComponent = nullptr; -} - UHoudiniInputObject * UHoudiniInputCameraComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) { @@ -511,6 +524,7 @@ UHoudiniInputHoudiniAsset::Create(UObject * InObject, UObject* InOuter, const FS InOuter, UHoudiniInputHoudiniAsset::StaticClass(), InputObjectName, RF_Public | RF_Transactional); HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniAssetComponent; + HoudiniInputObject->InputNodeId = InHoudiniAssetComponent->GetAssetId(); HoudiniInputObject->InputObjectNodeId = InHoudiniAssetComponent->GetAssetId(); @@ -771,6 +785,8 @@ UHoudiniInputObject::InvalidateData() FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputObjectNodeId); InputObjectNodeId = -1; } + + } void @@ -1032,14 +1048,22 @@ UHoudiniInputSplineComponent::Update(UObject * InObject) } void -UHoudiniInputHoudiniSplineComponent::Update(UObject * InObject) +UHoudiniInputHoudiniSplineComponent::Update(UObject* InObject) { Super::Update(InObject); + // We store the component references as a normal pointer property instead of using a soft object reference. + // If we use a soft object reference, the editor will complain about deleting a reference that is in use + // everytime we try to delete the actor, even though everything is contained within the actor. + + CachedComponent = Cast(InObject); + InputObject = nullptr; + // We need a strong ref to the spline component to prevent it from being GCed - MyHoudiniSplineComponent = Cast(InObject); + //MyHoudiniSplineComponent = Cast(InObject); + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (!MyHoudiniSplineComponent || MyHoudiniSplineComponent->IsPendingKill()) + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) { // Use default values CurveType = EHoudiniCurveType::Polygon; @@ -1048,12 +1072,67 @@ UHoudiniInputHoudiniSplineComponent::Update(UObject * InObject) } else { - CurveType = MyHoudiniSplineComponent->GetCurveType(); - CurveMethod = MyHoudiniSplineComponent->GetCurveMethod(); + CurveType = HoudiniSplineComponent->GetCurveType(); + CurveMethod = HoudiniSplineComponent->GetCurveMethod(); Reversed = false;//Spline->IsReversed(); } } +UObject* +UHoudiniInputHoudiniSplineComponent::GetObject() const +{ + return CachedComponent; +} + +void +UHoudiniInputHoudiniSplineComponent::MarkChanged(const bool& bInChanged) +{ + Super::MarkChanged(bInChanged); + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent) + { + HoudiniSplineComponent->MarkChanged(bInChanged); + } +} + +void +UHoudiniInputHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) +{ + Super::SetNeedsToTriggerUpdate(bInTriggersUpdate); + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent) + { + HoudiniSplineComponent->SetNeedsToTriggerUpdate(bInTriggersUpdate); + } +} + +bool +UHoudiniInputHoudiniSplineComponent::HasChanged() const +{ + if (Super::HasChanged()) + return true; + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent && HoudiniSplineComponent->HasChanged()) + return true; + + return false; +} + +bool +UHoudiniInputHoudiniSplineComponent::NeedsToTriggerUpdate() const +{ + if (Super::NeedsToTriggerUpdate()) + return true; + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent && HoudiniSplineComponent->NeedsToTriggerUpdate()) + return true; + + return false; +} + void UHoudiniInputHoudiniAsset::Update(UObject * InObject) { @@ -1066,6 +1145,8 @@ UHoudiniInputHoudiniAsset::Update(UObject * InObject) if (HAC) { // TODO: Notify HAC that we're a downstream? + InputNodeId = HAC->GetAssetId(); + InputObjectNodeId = HAC->GetAssetId(); // TODO: Allow selection of the asset output AssetOutputIndex = 0; @@ -1085,9 +1166,6 @@ UHoudiniInputActor::Update(UObject * InObject) { Transform = Actor->GetTransform(); - // TODO: - // Iterate on all our scene component, creating child inputs for them if necessary - // // The actor's components that can be sent as inputs ActorComponents.Empty(); @@ -1203,6 +1281,10 @@ UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) { return EHoudiniInputObjectType::Brush; } + else if (InObject->IsA(AHoudiniAssetActor::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniAssetActor; + } else { return EHoudiniInputObjectType::Actor; @@ -1222,6 +1304,10 @@ UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) { return EHoudiniInputObjectType::SkeletalMesh; } + else if (InObject->IsA(UDataTable::StaticClass())) + { + return EHoudiniInputObjectType::DataTable; + } else { return EHoudiniInputObjectType::Object; @@ -1247,11 +1333,6 @@ FHoudiniBrushInfo::FHoudiniBrushInfo() } FHoudiniBrushInfo::FHoudiniBrushInfo(ABrush* InBrushActor) - : CachedTransform() - , CachedOrigin(ForceInitToZero) - , CachedExtent(ForceInitToZero) - , CachedBrushType(EBrushType::Brush_Default) - , CachedSurfaceHash(0) { if (!InBrushActor) return; @@ -1545,6 +1626,7 @@ UHoudiniInputObject::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllPr bHasChanged = InInput->bHasChanged; bNeedsToTriggerUpdate = InInput->bNeedsToTriggerUpdate; bTransformChanged = InInput->bTransformChanged; + Guid = InInput->Guid; #if WITH_EDITORONLY_DATA bUniformScaleLocked = InInput->bUniformScaleLocked; @@ -1556,4 +1638,35 @@ void UHoudiniInputObject::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) { bCanDeleteHoudiniNodes = bInCanDeleteNodes; -} +} + + +// +UHoudiniInputDataTable::UHoudiniInputDataTable(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniInputObject * +UHoudiniInputDataTable::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_DT_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputDataTable::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputDataTable * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputDataTable::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::DataTable; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UDataTable* +UHoudiniInputDataTable::GetDataTable() const +{ + return Cast(InputObject.LoadSynchronous()); +} \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h index f3afb1168..775b375bd 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h @@ -1,805 +1,805 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include - -#include "HoudiniSplineComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "CoreTypes.h" -#include "Materials/MaterialInterface.h" -#include "UObject/Object.h" -#include "UObject/ObjectMacros.h" - -#include "Engine/Brush.h" -#include "Engine/Polys.h" -#include "UObject/SoftObjectPtr.h" - -#include "HoudiniInputObject.generated.h" - -class UStaticMesh; -class USkeletalMesh; -class USceneComponent; -class UStaticMeshComponent; -class UInstancedStaticMeshComponent; -class USplineComponent; -class UHoudiniAssetComponent; -class AActor; -class ALandscapeProxy; -class ABrush; -class UHoudiniInput; -class ALandscapeProxy; -class UModel; -class UHoudiniInput; -class UCameraComponent; - -UENUM() -enum class EHoudiniInputObjectType : uint8 -{ - Invalid, - - Object, - StaticMesh, - SkeletalMesh, - SceneComponent, - StaticMeshComponent, - InstancedStaticMeshComponent, - SplineComponent, - HoudiniSplineComponent, - HoudiniAssetComponent, - Actor, - Landscape, - Brush, - CameraComponent, - DataTable, - HoudiniAssetActor, -}; - -//----------------------------------------------------------------------------------------------------------------------------- -// UObjects input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - // Create the proper input object - static UHoudiniInputObject * CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InParamName); - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // Check whether two input objects match - virtual bool Matches(const UHoudiniInputObject& Other) const; - - // - static EHoudiniInputObjectType GetInputObjectTypeFromObject(UObject* InObject); - - // - virtual void Update(UObject * InObject); - - // Invalidate and ask for the deletion of this input object's node - virtual void InvalidateData(); - - // UObject accessor - virtual UObject* GetObject() const; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const { return bHasChanged; }; - - // Indicates if this input has changed and should be updated - virtual bool HasTransformChanged() const { return bTransformChanged; }; - - // Indicates if this input needs to trigger an update - virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; - - virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - void MarkTransformChanged(const bool& bInChanged) { bTransformChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - - void SetImportAsReference(const bool& bInImportAsRef) { bImportAsReference = bInImportAsRef; }; - bool GetImportAsReference() const { return bImportAsReference; }; - -#if WITH_EDITOR - void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; - bool IsUniformScaleLocked() const { return bUniformScaleLocked; }; - - void PostEditUndo() override; -#endif - - virtual UHoudiniInputObject* DuplicateAndCopyState(UObject* DestOuter); - virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties); - - // Set whether this object can delete Houdini nodes. - virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } - - FGuid GetInputGuid() const { return Guid; } - - -protected: - - virtual void BeginDestroy() override; - -public: - - // The object referenced by this input - // This property should be protected. Don't access this directly. Use GetObject() / Update() instead. - UPROPERTY() - TSoftObjectPtr InputObject; - - // The object's transform/transform offset - UPROPERTY() - FTransform Transform; - - // The type of Object this input refers to - UPROPERTY() - EHoudiniInputObjectType Type; - - // This input object's "main" (SOP) NodeId - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputNodeId; - - // This input object's "container" (OBJ) NodeId - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputObjectNodeId; - - // Guid that uniquely identifies this input object. - // Also useful to correlate inputs between blueprint component templates and instances. - UPROPERTY(DuplicateTransient) - FGuid Guid; - -protected: - - // Indicates this input object has changed - UPROPERTY(DuplicateTransient) - bool bHasChanged; - - // Indicates this input object should trigger an input update/cook - UPROPERTY(DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Indicates that this input transform should be updated - UPROPERTY(DuplicateTransient) - bool bTransformChanged; - - UPROPERTY() - bool bImportAsReference; - - // Indicates if change the scale of Transfrom Offset of this object uniformly -#if WITH_EDITORONLY_DATA - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - bool bUniformScaleLocked; -#endif - - UPROPERTY() - bool bCanDeleteHoudiniNodes; -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UStaticMesh input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputStaticMesh : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // UHoudiniInputObject overrides - - // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; - virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; - virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) override; - virtual void InvalidateData() override; - - // - virtual void Update(UObject * InObject) override; - - // Nothing to add for Static Meshes? - - // StaticMesh accessor - class UStaticMesh* GetStaticMesh(); - - // Blueprint accessor - class UBlueprint* GetBlueprint(); - - // Check if this SM Input object is passed in as a BP - bool bIsBlueprint() const; - - // The Blueprint's Static Meshe Components that can be sent as inputs - UPROPERTY() - TArray BlueprintStaticMeshes; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USkeletalMesh input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSkeletalMesh : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // Nothing to add for SkeletalMesh Meshes? - - // StaticMesh accessor - class USkeletalMesh* GetSkeletalMesh(); -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USceneComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSceneComponent : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // SceneComponent accessor - class USceneComponent* GetSceneComponent(); - - // Returns true if the attached actor's (parent) transform has been modified - virtual bool HasActorTransformChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const; - - // Return true if the component itself has been modified - virtual bool HasComponentChanged() const; - -public: - - // This component's parent Actor transform - UPROPERTY() - FTransform ActorTransform = FTransform::Identity; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UStaticMeshComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputMeshComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // StaticMeshComponent accessor - UStaticMeshComponent* GetStaticMeshComponent(); - - // Get the referenced StaticMesh - UStaticMesh* GetStaticMesh(); - - // Returns true if the attached component's materials have been modified - bool HasComponentMaterialsChanged() const; - - // Return true if SMC's static mesh has been modified - virtual bool HasComponentChanged() const override; - -public: - - // Keep track of the selected Static Mesh - UPROPERTY() - TSoftObjectPtr StaticMesh = nullptr; - - // Path to the materials assigned on the SMC - UPROPERTY() - TArray MeshComponentsMaterials; -}; - - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UInstancedStaticMeshComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputInstancedMeshComponent : public UHoudiniInputMeshComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // InstancedStaticMeshComponent accessor - UInstancedStaticMeshComponent* GetInstancedStaticMeshComponent(); - - // Returns true if the instances have changed - bool HasInstancesChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const override; - -public: - - // Array of transform for this ISMC's instances - UPROPERTY() - TArray InstanceTransforms; -}; - - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USplineComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSplineComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // USplineComponent accessor - USplineComponent* GetSplineComponent(); - - // Returns true if the attached spline component has been modified - bool HasSplineComponentChanged(float fCurrentSplineResolution) const; - - // Returns true if the attached actor's (parent) transform has been modified - virtual bool HasActorTransformChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const; - - // Return true if the component itself has been modified - virtual bool HasComponentChanged() const; - -public: - - // Number of CVs used by the spline component, used to detect modification - UPROPERTY() - int32 NumberOfSplineControlPoints = -1; - - // Spline Length, used for fast detection of modifications of the spline.. - UPROPERTY() - float SplineLength = -1.0f; - - // Spline resolution used to generate the asset, used to detect setting modification - UPROPERTY() - float SplineResolution = -1.0f; - - // Is the spline closed? - UPROPERTY() - bool SplineClosed = false; - - // Transforms of each of the spline's control points - UPROPERTY() - TArray SplineControlPoints; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniSplineComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniSplineComponent : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - virtual void Update(UObject * InObject) override; - - virtual UObject* GetObject() const override; - - virtual void MarkChanged(const bool& bInChanged) override; - - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) override; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const override; - - // Indicates if this input needs to trigger an update - virtual bool NeedsToTriggerUpdate() const override; - - // UHoudiniSplineComponent accessor - UHoudiniSplineComponent* GetCurveComponent() const; - -public: - - // The type of curve (polygon, NURBS, bezier) - UPROPERTY() - EHoudiniCurveType CurveType = EHoudiniCurveType::Polygon; - - // The curve's method (CVs, Breakpoint, Freehand) - UPROPERTY() - EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::CVs; - - UPROPERTY() - bool Reversed = false; - - -protected: - - // NOTE: We are using this reference to the component since the component, for now, - // lives on the same actor as this input object. If we use a Soft Object Reference instead the editor - // will complain about breaking references everytime we try to delete the actor. - UPROPERTY(Instanced) - UHoudiniSplineComponent* CachedComponent; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UCameraComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputCameraComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // UCameraComponent accessor - UCameraComponent* GetCameraComponent(); - - // Return true if SMC's static mesh has been modified - virtual bool HasComponentChanged() const override; - -public: - - float FOV; - float AspectRatio; - - //TEnumAsByte ProjectionType; - bool bIsOrthographic; - - float OrthoWidth; - float OrthoNearClipPlane; - float OrthoFarClipPlane; - -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniAssetComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniAsset : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // UHoudiniAssetComponent accessor - UHoudiniAssetComponent* GetHoudiniAssetComponent(); -public: - - // The output index of the node that we want to use as input - UPROPERTY() - int32 AssetOutputIndex; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// AActor input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // - virtual bool HasActorTransformChanged(); - - // Return true if any content of this actor has possibly changed (for example geometry edits on a - // Brush or changes on procedurally generated content). - // NOTE: This is more generally applicable and could be moved to the HoudiniInputObject class. - virtual bool HasContentChanged() const; - - // AActor accessor - AActor* GetActor(); - -public: - - // The actor's components that can be sent as inputs - UPROPERTY() - TArray ActorComponents; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// ALandscapeProxy input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActor -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - virtual bool HasActorTransformChanged() override; - - // ALandscapeProxy accessor - ALandscapeProxy* GetLandscapeProxy(); - - void SetLandscapeProxy(UObject* InLandscapeProxy); - - // Used to restore an input landscape's transform to its original state - UPROPERTY() - FTransform CachedInputLandscapeTraqnsform; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// ABrush input -//----------------------------------------------------------------------------------------------------------------------------- -// Cache info for a brush in order to determine whether it has changed. - -#define BRUSH_HASH_SURFACE_PROPERTIES 0 - -//USTRUCT() -//struct FHoudiniBrushSurfaceInfo { -// GENERATED_BODY() -// -// FVector Base; -// FVector Normal; -// FVector TextureU; -// FVector TextureV; -// TSoftObjectPtr Material; -// -// FHoudiniBrushSurfaceInfo(const FVector& InBase, const FVector& InNormal, const FVector& InTextureU, const FVector& InTextureV, UMaterialInterface* InMaterial) -// : Base(InBase) -// , Normal(InNormal) -// , TextureU(InTextureU) -// , TextureV(InTextureV) -// , Material(InMaterial) -// { } -// -// inline bool operator==(const FHoudiniBrushSurfaceInfo& Other) { -// return Base.Equals(Other.Base) -// && Normal.Equals(Other.Normal) -// && TextureU.Equals(Other.TextureU) -// && TextureV.Equals(Other.TextureV) -// && Material.Get() == Other.Material.Get(); -// } -// -// inline bool operator==(const FPoly& Poly) { -// return Base.Equals(Poly.Base) -// && Normal.Equals(Poly.Normal) -// && TextureU.Equals(Poly.TextureU) -// && TextureV.Equals(Poly.TextureV) -// && Material.Get() == Poly.Material; -// } -//}; - -USTRUCT() -struct FHoudiniBrushInfo -{ - GENERATED_BODY() - - UPROPERTY() - TWeakObjectPtr BrushActor; - UPROPERTY() - FTransform CachedTransform; - UPROPERTY() - FVector CachedOrigin; - UPROPERTY() - FVector CachedExtent; - UPROPERTY() - TEnumAsByte CachedBrushType; - - UPROPERTY() - uint64 CachedSurfaceHash; - - bool HasChanged() const; - - static int32 GetNumVertexIndicesFromModel(const UModel* Model); - - FHoudiniBrushInfo(); - FHoudiniBrushInfo(ABrush* InBrushActor); - - template - inline void HashCombine(uint64& s, const T & v) const - { - std::hash h; - s^= h(v) + 0x9e3779b9 + (s<< 6) + (s>> 2); - } - - inline void HashCombine(uint64& s, const FVector & V) const - { - HashCombine(s, V.X); - HashCombine(s, V.Y); - HashCombine(s, V.Z); - } - - inline void CombinePolyHash(uint64& Hash, const FPoly& Poly) const - { - HashCombine(Hash, Poly.Base); - HashCombine(Hash, Poly.TextureU); - HashCombine(Hash, Poly.TextureV); - HashCombine(Hash, Poly.Normal); - HashCombine(Hash, (uint64)(Poly.Material)); - } -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor -{ - GENERATED_BODY() - -public: - - UHoudiniInputBrush(); - - // Factory function - static UHoudiniInputBrush* Create(UObject* InObject, UObject* InOuter, const FString& InName); - - //---------------------------------------------------------------------- - // UHoudiniInputActor Interface - Begin - //---------------------------------------------------------------------- - - virtual void Update(UObject * InObject) override; - - // Indicates if this input has changed and should be updated - virtual bool HasContentChanged() const override; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const override { return (!bIgnoreInputObject) && bHasChanged; }; - - // Indicates if this input has changed and should be updated - virtual bool HasTransformChanged() const override { return (!bIgnoreInputObject) && bTransformChanged; }; - - virtual bool HasActorTransformChanged() override; - - virtual bool NeedsToTriggerUpdate() const override { return (!bIgnoreInputObject) && bNeedsToTriggerUpdate; }; - - //---------------------------------------------------------------------- - // UHoudiniInputActor Interface - End - //---------------------------------------------------------------------- - - - // ABrush accessor - ABrush* GetBrush() const; - - UModel* GetCachedModel() const; - - // Check whether any of the brushes, or their transforms, used to generate this model have changed. - bool HasBrushesChanged(const TArray& InBrushes) const; - - // Cache the combined model as well as the input brushes. - void UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes); - - // Returns whether this input object should be ignored when uploading objects to Houdini. - // This mechanism could be implemented on UHoudiniInputObject. - bool ShouldIgnoreThisInput(); - - - // Find only the subtractive brush actors that intersect with the InputObject (Brush actor) bounding box but - // excluding any selector bounds actors. - static bool FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes); - -protected: - UPROPERTY() - TArray BrushesInfo; - - UPROPERTY(Transient, DuplicateTransient) - UModel* CombinedModel; - - UPROPERTY() - bool bIgnoreInputObject; - - UPROPERTY() - TEnumAsByte CachedInputBrushType; -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UDataTable input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputDataTable : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // DataTable accessor - class UDataTable* GetDataTable() const; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include + +#include "HoudiniSplineComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "CoreTypes.h" +#include "Materials/MaterialInterface.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" + +#include "Engine/Brush.h" +#include "Engine/Polys.h" +#include "UObject/SoftObjectPtr.h" + +#include "HoudiniInputObject.generated.h" + +class UStaticMesh; +class USkeletalMesh; +class USceneComponent; +class UStaticMeshComponent; +class UInstancedStaticMeshComponent; +class USplineComponent; +class UHoudiniAssetComponent; +class AActor; +class ALandscapeProxy; +class ABrush; +class UHoudiniInput; +class ALandscapeProxy; +class UModel; +class UHoudiniInput; +class UCameraComponent; + +UENUM() +enum class EHoudiniInputObjectType : uint8 +{ + Invalid, + + Object, + StaticMesh, + SkeletalMesh, + SceneComponent, + StaticMeshComponent, + InstancedStaticMeshComponent, + SplineComponent, + HoudiniSplineComponent, + HoudiniAssetComponent, + Actor, + Landscape, + Brush, + CameraComponent, + DataTable, + HoudiniAssetActor, +}; + +//----------------------------------------------------------------------------------------------------------------------------- +// UObjects input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + // Create the proper input object + static UHoudiniInputObject * CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InParamName); + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // Check whether two input objects match + virtual bool Matches(const UHoudiniInputObject& Other) const; + + // + static EHoudiniInputObjectType GetInputObjectTypeFromObject(UObject* InObject); + + // + virtual void Update(UObject * InObject); + + // Invalidate and ask for the deletion of this input object's node + virtual void InvalidateData(); + + // UObject accessor + virtual UObject* GetObject() const; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const { return bHasChanged; }; + + // Indicates if this input has changed and should be updated + virtual bool HasTransformChanged() const { return bTransformChanged; }; + + // Indicates if this input needs to trigger an update + virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; + + virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + void MarkTransformChanged(const bool& bInChanged) { bTransformChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + + void SetImportAsReference(const bool& bInImportAsRef) { bImportAsReference = bInImportAsRef; }; + bool GetImportAsReference() const { return bImportAsReference; }; + +#if WITH_EDITOR + void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; + bool IsUniformScaleLocked() const { return bUniformScaleLocked; }; + + void PostEditUndo() override; +#endif + + virtual UHoudiniInputObject* DuplicateAndCopyState(UObject* DestOuter); + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties); + + // Set whether this object can delete Houdini nodes. + virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } + + FGuid GetInputGuid() const { return Guid; } + + +protected: + + virtual void BeginDestroy() override; + +public: + + // The object referenced by this input + // This property should be protected. Don't access this directly. Use GetObject() / Update() instead. + UPROPERTY() + TSoftObjectPtr InputObject; + + // The object's transform/transform offset + UPROPERTY() + FTransform Transform; + + // The type of Object this input refers to + UPROPERTY() + EHoudiniInputObjectType Type; + + // This input object's "main" (SOP) NodeId + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputNodeId; + + // This input object's "container" (OBJ) NodeId + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputObjectNodeId; + + // Guid that uniquely identifies this input object. + // Also useful to correlate inputs between blueprint component templates and instances. + UPROPERTY(DuplicateTransient) + FGuid Guid; + +protected: + + // Indicates this input object has changed + UPROPERTY(DuplicateTransient) + bool bHasChanged; + + // Indicates this input object should trigger an input update/cook + UPROPERTY(DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Indicates that this input transform should be updated + UPROPERTY(DuplicateTransient) + bool bTransformChanged; + + UPROPERTY() + bool bImportAsReference; + + // Indicates if change the scale of Transfrom Offset of this object uniformly +#if WITH_EDITORONLY_DATA + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + bool bUniformScaleLocked; +#endif + + UPROPERTY() + bool bCanDeleteHoudiniNodes; +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UStaticMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputStaticMesh : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // UHoudiniInputObject overrides + + // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; + virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) override; + virtual void InvalidateData() override; + + // + virtual void Update(UObject * InObject) override; + + // Nothing to add for Static Meshes? + + // StaticMesh accessor + class UStaticMesh* GetStaticMesh(); + + // Blueprint accessor + class UBlueprint* GetBlueprint(); + + // Check if this SM Input object is passed in as a BP + bool bIsBlueprint() const; + + // The Blueprint's Static Meshe Components that can be sent as inputs + UPROPERTY() + TArray BlueprintStaticMeshes; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USkeletalMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSkeletalMesh : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // Nothing to add for SkeletalMesh Meshes? + + // StaticMesh accessor + class USkeletalMesh* GetSkeletalMesh(); +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USceneComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSceneComponent : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // SceneComponent accessor + class USceneComponent* GetSceneComponent(); + + // Returns true if the attached actor's (parent) transform has been modified + virtual bool HasActorTransformChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const; + + // Return true if the component itself has been modified + virtual bool HasComponentChanged() const; + +public: + + // This component's parent Actor transform + UPROPERTY() + FTransform ActorTransform = FTransform::Identity; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UStaticMeshComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputMeshComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // StaticMeshComponent accessor + UStaticMeshComponent* GetStaticMeshComponent(); + + // Get the referenced StaticMesh + UStaticMesh* GetStaticMesh(); + + // Returns true if the attached component's materials have been modified + bool HasComponentMaterialsChanged() const; + + // Return true if SMC's static mesh has been modified + virtual bool HasComponentChanged() const override; + +public: + + // Keep track of the selected Static Mesh + UPROPERTY() + TSoftObjectPtr StaticMesh = nullptr; + + // Path to the materials assigned on the SMC + UPROPERTY() + TArray MeshComponentsMaterials; +}; + + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UInstancedStaticMeshComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputInstancedMeshComponent : public UHoudiniInputMeshComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // InstancedStaticMeshComponent accessor + UInstancedStaticMeshComponent* GetInstancedStaticMeshComponent(); + + // Returns true if the instances have changed + bool HasInstancesChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const override; + +public: + + // Array of transform for this ISMC's instances + UPROPERTY() + TArray InstanceTransforms; +}; + + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USplineComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSplineComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // USplineComponent accessor + USplineComponent* GetSplineComponent(); + + // Returns true if the attached spline component has been modified + bool HasSplineComponentChanged(float fCurrentSplineResolution) const; + + // Returns true if the attached actor's (parent) transform has been modified + virtual bool HasActorTransformChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const; + + // Return true if the component itself has been modified + virtual bool HasComponentChanged() const; + +public: + + // Number of CVs used by the spline component, used to detect modification + UPROPERTY() + int32 NumberOfSplineControlPoints = -1; + + // Spline Length, used for fast detection of modifications of the spline.. + UPROPERTY() + float SplineLength = -1.0f; + + // Spline resolution used to generate the asset, used to detect setting modification + UPROPERTY() + float SplineResolution = -1.0f; + + // Is the spline closed? + UPROPERTY() + bool SplineClosed = false; + + // Transforms of each of the spline's control points + UPROPERTY() + TArray SplineControlPoints; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniSplineComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniSplineComponent : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + virtual void Update(UObject * InObject) override; + + virtual UObject* GetObject() const override; + + virtual void MarkChanged(const bool& bInChanged) override; + + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) override; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const override; + + // Indicates if this input needs to trigger an update + virtual bool NeedsToTriggerUpdate() const override; + + // UHoudiniSplineComponent accessor + UHoudiniSplineComponent* GetCurveComponent() const; + +public: + + // The type of curve (polygon, NURBS, bezier) + UPROPERTY() + EHoudiniCurveType CurveType = EHoudiniCurveType::Polygon; + + // The curve's method (CVs, Breakpoint, Freehand) + UPROPERTY() + EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::CVs; + + UPROPERTY() + bool Reversed = false; + + +protected: + + // NOTE: We are using this reference to the component since the component, for now, + // lives on the same actor as this input object. If we use a Soft Object Reference instead the editor + // will complain about breaking references everytime we try to delete the actor. + UPROPERTY(Instanced) + UHoudiniSplineComponent* CachedComponent; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UCameraComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputCameraComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // UCameraComponent accessor + UCameraComponent* GetCameraComponent(); + + // Return true if SMC's static mesh has been modified + virtual bool HasComponentChanged() const override; + +public: + + float FOV; + float AspectRatio; + + //TEnumAsByte ProjectionType; + bool bIsOrthographic; + + float OrthoWidth; + float OrthoNearClipPlane; + float OrthoFarClipPlane; + +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniAssetComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniAsset : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // UHoudiniAssetComponent accessor + UHoudiniAssetComponent* GetHoudiniAssetComponent(); +public: + + // The output index of the node that we want to use as input + UPROPERTY() + int32 AssetOutputIndex; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// AActor input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // + virtual bool HasActorTransformChanged(); + + // Return true if any content of this actor has possibly changed (for example geometry edits on a + // Brush or changes on procedurally generated content). + // NOTE: This is more generally applicable and could be moved to the HoudiniInputObject class. + virtual bool HasContentChanged() const; + + // AActor accessor + AActor* GetActor(); + +public: + + // The actor's components that can be sent as inputs + UPROPERTY() + TArray ActorComponents; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// ALandscapeProxy input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActor +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + virtual bool HasActorTransformChanged() override; + + // ALandscapeProxy accessor + ALandscapeProxy* GetLandscapeProxy(); + + void SetLandscapeProxy(UObject* InLandscapeProxy); + + // Used to restore an input landscape's transform to its original state + UPROPERTY() + FTransform CachedInputLandscapeTraqnsform; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// ABrush input +//----------------------------------------------------------------------------------------------------------------------------- +// Cache info for a brush in order to determine whether it has changed. + +#define BRUSH_HASH_SURFACE_PROPERTIES 0 + +//USTRUCT() +//struct FHoudiniBrushSurfaceInfo { +// GENERATED_BODY() +// +// FVector Base; +// FVector Normal; +// FVector TextureU; +// FVector TextureV; +// TSoftObjectPtr Material; +// +// FHoudiniBrushSurfaceInfo(const FVector& InBase, const FVector& InNormal, const FVector& InTextureU, const FVector& InTextureV, UMaterialInterface* InMaterial) +// : Base(InBase) +// , Normal(InNormal) +// , TextureU(InTextureU) +// , TextureV(InTextureV) +// , Material(InMaterial) +// { } +// +// inline bool operator==(const FHoudiniBrushSurfaceInfo& Other) { +// return Base.Equals(Other.Base) +// && Normal.Equals(Other.Normal) +// && TextureU.Equals(Other.TextureU) +// && TextureV.Equals(Other.TextureV) +// && Material.Get() == Other.Material.Get(); +// } +// +// inline bool operator==(const FPoly& Poly) { +// return Base.Equals(Poly.Base) +// && Normal.Equals(Poly.Normal) +// && TextureU.Equals(Poly.TextureU) +// && TextureV.Equals(Poly.TextureV) +// && Material.Get() == Poly.Material; +// } +//}; + +USTRUCT() +struct FHoudiniBrushInfo +{ + GENERATED_BODY() + + UPROPERTY() + TWeakObjectPtr BrushActor; + UPROPERTY() + FTransform CachedTransform; + UPROPERTY() + FVector CachedOrigin; + UPROPERTY() + FVector CachedExtent; + UPROPERTY() + TEnumAsByte CachedBrushType; + + UPROPERTY() + uint64 CachedSurfaceHash; + + bool HasChanged() const; + + static int32 GetNumVertexIndicesFromModel(const UModel* Model); + + FHoudiniBrushInfo(); + FHoudiniBrushInfo(ABrush* InBrushActor); + + template + inline void HashCombine(uint64& s, const T & v) const + { + std::hash h; + s^= h(v) + 0x9e3779b9 + (s<< 6) + (s>> 2); + } + + inline void HashCombine(uint64& s, const FVector & V) const + { + HashCombine(s, V.X); + HashCombine(s, V.Y); + HashCombine(s, V.Z); + } + + inline void CombinePolyHash(uint64& Hash, const FPoly& Poly) const + { + HashCombine(Hash, Poly.Base); + HashCombine(Hash, Poly.TextureU); + HashCombine(Hash, Poly.TextureV); + HashCombine(Hash, Poly.Normal); + HashCombine(Hash, (uint64)(Poly.Material)); + } +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor +{ + GENERATED_BODY() + +public: + + UHoudiniInputBrush(); + + // Factory function + static UHoudiniInputBrush* Create(UObject* InObject, UObject* InOuter, const FString& InName); + + //---------------------------------------------------------------------- + // UHoudiniInputActor Interface - Begin + //---------------------------------------------------------------------- + + virtual void Update(UObject * InObject) override; + + // Indicates if this input has changed and should be updated + virtual bool HasContentChanged() const override; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const override { return (!bIgnoreInputObject) && bHasChanged; }; + + // Indicates if this input has changed and should be updated + virtual bool HasTransformChanged() const override { return (!bIgnoreInputObject) && bTransformChanged; }; + + virtual bool HasActorTransformChanged() override; + + virtual bool NeedsToTriggerUpdate() const override { return (!bIgnoreInputObject) && bNeedsToTriggerUpdate; }; + + //---------------------------------------------------------------------- + // UHoudiniInputActor Interface - End + //---------------------------------------------------------------------- + + + // ABrush accessor + ABrush* GetBrush() const; + + UModel* GetCachedModel() const; + + // Check whether any of the brushes, or their transforms, used to generate this model have changed. + bool HasBrushesChanged(const TArray& InBrushes) const; + + // Cache the combined model as well as the input brushes. + void UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes); + + // Returns whether this input object should be ignored when uploading objects to Houdini. + // This mechanism could be implemented on UHoudiniInputObject. + bool ShouldIgnoreThisInput(); + + + // Find only the subtractive brush actors that intersect with the InputObject (Brush actor) bounding box but + // excluding any selector bounds actors. + static bool FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes); + +protected: + UPROPERTY() + TArray BrushesInfo; + + UPROPERTY(Transient, DuplicateTransient) + UModel* CombinedModel; + + UPROPERTY() + bool bIgnoreInputObject; + + UPROPERTY() + TEnumAsByte CachedInputBrushType; +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UDataTable input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputDataTable : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // DataTable accessor + class UDataTable* GetDataTable() const; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h index 0242d0ba1..3c17896d1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h @@ -1,68 +1,68 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -UENUM() -enum class EHoudiniInputType : uint8 -{ - Invalid, - - Geometry, - Curve, - Asset, - Landscape, - World, - Skeletal, - -}; -// Maintain an iterable list of houdini input types -static const EHoudiniInputType HoudiniInputTypeList[] = { - EHoudiniInputType::Geometry, - EHoudiniInputType::Curve, - EHoudiniInputType::Asset, - EHoudiniInputType::Landscape, - EHoudiniInputType::World, - EHoudiniInputType::Skeletal }; - -UENUM() -enum class EHoudiniXformType : uint8 -{ - None, - IntoThisObject, - Auto -}; - -UENUM() -enum class EHoudiniLandscapeExportType : uint8 -{ - Heightfield, - Mesh, - Points +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +UENUM() +enum class EHoudiniInputType : uint8 +{ + Invalid, + + Geometry, + Curve, + Asset, + Landscape, + World, + Skeletal, + +}; +// Maintain an iterable list of houdini input types +static const EHoudiniInputType HoudiniInputTypeList[] = { + EHoudiniInputType::Geometry, + EHoudiniInputType::Curve, + EHoudiniInputType::Asset, + EHoudiniInputType::Landscape, + EHoudiniInputType::World, + EHoudiniInputType::Skeletal }; + +UENUM() +enum class EHoudiniXformType : uint8 +{ + None, + IntoThisObject, + Auto +}; + +UENUM() +enum class EHoudiniLandscapeExportType : uint8 +{ + Heightfield, + Mesh, + Points }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp index 9db53ec8e..f6c6f1eed 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp @@ -1,245 +1,245 @@ -/* -* 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 "HoudiniInstancedActorComponent.h" - -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniInstancedActorComponent::UHoudiniInstancedActorComponent( const FObjectInitializer& ObjectInitializer ) -: Super( ObjectInitializer ) -, InstancedObject( nullptr ) -{ - // - // Set default component properties. - // - Mobility = EComponentMobility::Static; - bCanEverAffectNavigation = true; - bNeverNeedsRenderUpdate = false; - Bounds = FBox(ForceInitToZero); -} - - -void -UHoudiniInstancedActorComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on the setting value - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniInstancedActorComponent_V1* CompatibilityIAC = NewObject(); - CompatibilityIAC->Serialize(Ar); - CompatibilityIAC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - - -void UHoudiniInstancedActorComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) -{ - ClearAllInstances(); - Super::OnComponentDestroyed( bDestroyingHierarchy ); -} - - -void -UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector ) -{ - UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); - if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) - { - if ( ThisHIAC->InstancedObject && !ThisHIAC->InstancedObject->IsPendingKill() ) - Collector.AddReferencedObject( ThisHIAC->InstancedObject, ThisHIAC ); - - Collector.AddReferencedObjects(ThisHIAC->InstancedActors, ThisHIAC ); - } -} - - -int32 -UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform, AActor * NewActor) -{ - if (!NewActor || NewActor->IsPendingKill()) - return -1; - - NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - NewActor->SetActorRelativeTransform(InstanceTransform); - return InstancedActors.Add(NewActor); -} - - -bool -UHoudiniInstancedActorComponent::SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor) -{ - if (!NewActor || NewActor->IsPendingKill()) - return false; - - if (!InstancedActors.IsValidIndex(Idx)) - return false; - - NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - NewActor->SetActorRelativeTransform(InstanceTransform); - NewActor->RegisterAllComponents(); - InstancedActors[Idx] = NewActor; - - return true; -} - - -bool -UHoudiniInstancedActorComponent::SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform) -{ - if (!InstancedActors.IsValidIndex(Idx)) - return false; - - InstancedActors[Idx]->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - InstancedActors[Idx]->SetActorRelativeTransform(InstanceTransform); - - return true; -} - - -void -UHoudiniInstancedActorComponent::ClearAllInstances() -{ - for ( AActor* Instance : InstancedActors ) - { - if ( Instance && !Instance->IsPendingKill() ) - Instance->Destroy(); - } - InstancedActors.Empty(); -} - - -void -UHoudiniInstancedActorComponent::SetNumberOfInstances(const int32& NewInstanceNum) -{ - int32 OldInstanceNum = InstancedActors.Num(); - - // If we want less instances than we already have, destroy the extra properly - if (NewInstanceNum < OldInstanceNum) - { - for (int32 Idx = NewInstanceNum - 1; Idx < InstancedActors.Num(); Idx++) - { - AActor* Instance = InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; - if (Instance && !Instance->IsPendingKill()) - Instance->Destroy(); - } - } - - // Grow the array with nulls if needed - InstancedActors.SetNumZeroed(NewInstanceNum); -} - - -void -UHoudiniInstancedActorComponent::OnComponentCreated() -{ - Super::OnComponentCreated(); - - // If our instances are parented to another actor we should duplicate them - bool bNeedDuplicate = false; - for (auto CurrentInstance : InstancedActors) - { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) - continue; - - if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) - bNeedDuplicate = true; - } - - if ( !bNeedDuplicate ) - return; - - // TODO: CHECK ME! - // We need to duplicate our instances - TArray SourceInstances = InstancedActors; - InstancedActors.Empty(); - for (AActor* CurrentInstance : SourceInstances) - { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) - continue; - - FTransform InstanceTransform; - if ( CurrentInstance->GetRootComponent() ) - InstanceTransform = CurrentInstance->GetRootComponent()->GetRelativeTransform(); - - // AddInstance( InstanceTransform ); - } -} - +/* +* 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 "HoudiniInstancedActorComponent.h" + +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniInstancedActorComponent::UHoudiniInstancedActorComponent( const FObjectInitializer& ObjectInitializer ) +: Super( ObjectInitializer ) +, InstancedObject( nullptr ) +{ + // + // Set default component properties. + // + Mobility = EComponentMobility::Static; + bCanEverAffectNavigation = true; + bNeverNeedsRenderUpdate = false; + Bounds = FBox(ForceInitToZero); +} + + +void +UHoudiniInstancedActorComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on the setting value + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniInstancedActorComponent_V1* CompatibilityIAC = NewObject(); + CompatibilityIAC->Serialize(Ar); + CompatibilityIAC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + + +void UHoudiniInstancedActorComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + ClearAllInstances(); + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + + +void +UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); + if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) + { + if ( ThisHIAC->InstancedObject && !ThisHIAC->InstancedObject->IsPendingKill() ) + Collector.AddReferencedObject( ThisHIAC->InstancedObject, ThisHIAC ); + + Collector.AddReferencedObjects(ThisHIAC->InstancedActors, ThisHIAC ); + } +} + + +int32 +UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform, AActor * NewActor) +{ + if (!NewActor || NewActor->IsPendingKill()) + return -1; + + NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + NewActor->SetActorRelativeTransform(InstanceTransform); + return InstancedActors.Add(NewActor); +} + + +bool +UHoudiniInstancedActorComponent::SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor) +{ + if (!NewActor || NewActor->IsPendingKill()) + return false; + + if (!InstancedActors.IsValidIndex(Idx)) + return false; + + NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + NewActor->SetActorRelativeTransform(InstanceTransform); + NewActor->RegisterAllComponents(); + InstancedActors[Idx] = NewActor; + + return true; +} + + +bool +UHoudiniInstancedActorComponent::SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform) +{ + if (!InstancedActors.IsValidIndex(Idx)) + return false; + + InstancedActors[Idx]->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + InstancedActors[Idx]->SetActorRelativeTransform(InstanceTransform); + + return true; +} + + +void +UHoudiniInstancedActorComponent::ClearAllInstances() +{ + for ( AActor* Instance : InstancedActors ) + { + if ( Instance && !Instance->IsPendingKill() ) + Instance->Destroy(); + } + InstancedActors.Empty(); +} + + +void +UHoudiniInstancedActorComponent::SetNumberOfInstances(const int32& NewInstanceNum) +{ + int32 OldInstanceNum = InstancedActors.Num(); + + // If we want less instances than we already have, destroy the extra properly + if (NewInstanceNum < OldInstanceNum) + { + for (int32 Idx = NewInstanceNum - 1; Idx < InstancedActors.Num(); Idx++) + { + AActor* Instance = InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; + if (Instance && !Instance->IsPendingKill()) + Instance->Destroy(); + } + } + + // Grow the array with nulls if needed + InstancedActors.SetNumZeroed(NewInstanceNum); +} + + +void +UHoudiniInstancedActorComponent::OnComponentCreated() +{ + Super::OnComponentCreated(); + + // If our instances are parented to another actor we should duplicate them + bool bNeedDuplicate = false; + for (auto CurrentInstance : InstancedActors) + { + if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + continue; + + if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) + bNeedDuplicate = true; + } + + if ( !bNeedDuplicate ) + return; + + // TODO: CHECK ME! + // We need to duplicate our instances + TArray SourceInstances = InstancedActors; + InstancedActors.Empty(); + for (AActor* CurrentInstance : SourceInstances) + { + if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + continue; + + FTransform InstanceTransform; + if ( CurrentInstance->GetRootComponent() ) + InstanceTransform = CurrentInstance->GetRootComponent()->GetRelativeTransform(); + + // AddInstance( InstanceTransform ); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h index 707b60f87..8ab2b8cae 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h @@ -1,91 +1,91 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" - -#include "HoudiniInstancedActorComponent.generated.h" - - -UCLASS()//( config = Engine ) -class HOUDINIENGINERUNTIME_API UHoudiniInstancedActorComponent : public USceneComponent -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniInstancedActorComponent_V1; - - public: - - virtual void Serialize(FArchive & Ar) override; - - virtual void OnComponentCreated() override; - virtual void OnComponentDestroyed( bool bDestroyingHierarchy ) override; - - static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); - - // Object mutator - void SetInstancedObject(class UObject* InObject) { InstancedObject = InObject; } - // Object accessor - class UObject* GetInstancedObject() const { return InstancedObject; } - - - // Instance Accessor - TArray& GetInstancedActorsForWrite() { return InstancedActors; } - // const Instance accessor - const TArray& GetInstancedActors() const { return InstancedActors; } - - // Returns the instanced actor at a given index - AActor* GetInstancedActorAt(const int32& Idx) { return InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; } - - // Add an instance to this component. Transform is given in local space of this component. - int32 AddInstance(const FTransform& InstanceTransform, AActor * NewActor); - - // Sets the instance at a given index in this component. Transform is given in local space of this component. - bool SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor); - - // Updates the transform for a given actor. Transform is given in local space of this component. - bool SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform); - - // Destroy all existing instances - void ClearAllInstances(); - - // Sets the number of instances needed - // Properly deletes extras, new instance actors are nulled - void SetNumberOfInstances(const int32& NewInstanceNum); - - // Set the instances. Transforms are given in local space of this component. - bool SetInstanceTransforms(const TArray& InstanceTransforms); - - private: - - UPROPERTY(VisibleAnywhere, Category = Instances ) - UObject* InstancedObject; - - UPROPERTY(VisibleInstanceOnly, Category = Instances ) - TArray InstancedActors; - -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" + +#include "HoudiniInstancedActorComponent.generated.h" + + +UCLASS()//( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniInstancedActorComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniInstancedActorComponent_V1; + + public: + + virtual void Serialize(FArchive & Ar) override; + + virtual void OnComponentCreated() override; + virtual void OnComponentDestroyed( bool bDestroyingHierarchy ) override; + + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + + // Object mutator + void SetInstancedObject(class UObject* InObject) { InstancedObject = InObject; } + // Object accessor + class UObject* GetInstancedObject() const { return InstancedObject; } + + + // Instance Accessor + TArray& GetInstancedActorsForWrite() { return InstancedActors; } + // const Instance accessor + const TArray& GetInstancedActors() const { return InstancedActors; } + + // Returns the instanced actor at a given index + AActor* GetInstancedActorAt(const int32& Idx) { return InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; } + + // Add an instance to this component. Transform is given in local space of this component. + int32 AddInstance(const FTransform& InstanceTransform, AActor * NewActor); + + // Sets the instance at a given index in this component. Transform is given in local space of this component. + bool SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor); + + // Updates the transform for a given actor. Transform is given in local space of this component. + bool SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform); + + // Destroy all existing instances + void ClearAllInstances(); + + // Sets the number of instances needed + // Properly deletes extras, new instance actors are nulled + void SetNumberOfInstances(const int32& NewInstanceNum); + + // Set the instances. Transforms are given in local space of this component. + bool SetInstanceTransforms(const TArray& InstanceTransforms); + + private: + + UPROPERTY(VisibleAnywhere, Category = Instances ) + UObject* InstancedObject; + + UPROPERTY(VisibleInstanceOnly, Category = Instances ) + TArray InstancedActors; + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp index c5fdea932..8a63839cc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp @@ -1,25 +1,25 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h b/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h index 6830f53b3..2853e8148 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h @@ -1,28 +1,28 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp index dfc703863..5c10bd98a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp @@ -1,244 +1,244 @@ -/* -* 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 "HoudiniMeshSplitInstancerComponent.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -#include "Components/StaticMeshComponent.h" - -/* -#if WITH_EDITOR - #include "ScopedTransaction.h" - #include "LevelEditorViewport.h" - #include "MeshPaintHelpers.h" -#endif -*/ - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniMeshSplitInstancerComponent::UHoudiniMeshSplitInstancerComponent(const FObjectInitializer& ObjectInitializer) - : Super( ObjectInitializer ) - , InstancedMesh( nullptr ) -{ -} - -void -UHoudiniMeshSplitInstancerComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on the setting value - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniMeshSplitInstancerComponent_V1* CompatibilityMSIC = NewObject(); - CompatibilityMSIC->Serialize(Ar); - CompatibilityMSIC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -void -UHoudiniMeshSplitInstancerComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) -{ - ClearInstances(0); - Super::OnComponentDestroyed( bDestroyingHierarchy ); -} - - -void -UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) -{ - UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); - if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) - { - Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); - for(auto& Mat : ThisMSIC->OverrideMaterials) - Collector.AddReferencedObject(Mat, ThisMSIC); - Collector.AddReferencedObjects(ThisMSIC->Instances, ThisMSIC); - } -} - -bool -UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( - const TArray& InstanceTransforms) -{ - if (Instances.Num() <= 0 && InstanceTransforms.Num() <= 0) - return false; - - if (!GetOwner() || GetOwner()->IsPendingKill()) - return false; - - // Destroy previous instances while keeping some of the one that we'll be able to reuse - ClearInstances(InstanceTransforms.Num()); - - // - if( !InstancedMesh || InstancedMesh->IsPendingKill() ) - { - HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); - return false; - } - - // Only create new SMC for newly added instances - for (int32 iAdd = Instances.Num(); iAdd < InstanceTransforms.Num(); iAdd++) - { - const FTransform& InstanceTransform = InstanceTransforms[iAdd]; - UStaticMeshComponent* SMC = NewObject< UStaticMeshComponent >( - GetOwner(), UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - SMC->SetRelativeTransform(InstanceTransform); - Instances.Add(SMC); - GetOwner()->AddInstanceComponent(SMC); - } - - // We should now have the same number of instances than transform - ensure(InstanceTransforms.Num() == Instances.Num()); - if (InstanceTransforms.Num() != Instances.Num()) - return false; - - for (int32 iIns = 0; iIns < Instances.Num(); ++iIns) - { - UStaticMeshComponent* SMC = Instances[iIns]; - const FTransform& InstanceTransform = InstanceTransforms[iIns]; - - if (!SMC || SMC->IsPendingKill()) - continue; - - SMC->SetRelativeTransform(InstanceTransform); - - // Attach created static mesh component to this thing - SMC->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - - SMC->SetStaticMesh(InstancedMesh); - SMC->SetVisibility(IsVisible()); - SMC->SetMobility(Mobility); - - // TODO: Revert to default if override is null?? - UMaterialInterface* MI = nullptr; - if (OverrideMaterials.Num() > 0) - { - if (OverrideMaterials.IsValidIndex(iIns)) - MI = OverrideMaterials[iIns]; - else - MI = OverrideMaterials[0]; - } - - if (MI && !MI->IsPendingKill()) - { - int32 MeshMaterialCount = InstancedMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - SMC->SetMaterial(Idx, MI); - } - - SMC->RegisterComponent(); - - /* - // TODO: - // Properties not being propagated to newly created UStaticMeshComponents - if (UHoudiniAssetComponent * pHoudiniAsset = Cast(GetAttachParent())) - { - pHoudiniAsset->CopyComponentPropertiesTo(SMC); - } - */ - } - - return true; -} - -void -UHoudiniMeshSplitInstancerComponent::ClearInstances(int32 NumToKeep) -{ - if (NumToKeep <= 0) - { - for (auto&& Instance : Instances) - { - if (Instance) - { - Instance->ConditionalBeginDestroy(); - } - } - Instances.Empty(); - } - else if (NumToKeep > 0 && NumToKeep < Instances.Num()) - { - for (int32 i = NumToKeep; i < Instances.Num(); ++i) - { - UStaticMeshComponent * Instance = Instances[i]; - if (Instance) - { - Instance->ConditionalBeginDestroy(); - } - } - Instances.SetNum(NumToKeep); - } -} - +/* +* 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 "HoudiniMeshSplitInstancerComponent.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +#include "Components/StaticMeshComponent.h" + +/* +#if WITH_EDITOR + #include "ScopedTransaction.h" + #include "LevelEditorViewport.h" + #include "MeshPaintHelpers.h" +#endif +*/ + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniMeshSplitInstancerComponent::UHoudiniMeshSplitInstancerComponent(const FObjectInitializer& ObjectInitializer) + : Super( ObjectInitializer ) + , InstancedMesh( nullptr ) +{ +} + +void +UHoudiniMeshSplitInstancerComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on the setting value + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniMeshSplitInstancerComponent_V1* CompatibilityMSIC = NewObject(); + CompatibilityMSIC->Serialize(Ar); + CompatibilityMSIC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +void +UHoudiniMeshSplitInstancerComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + ClearInstances(0); + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + + +void +UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); + if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) + { + Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); + for(auto& Mat : ThisMSIC->OverrideMaterials) + Collector.AddReferencedObject(Mat, ThisMSIC); + Collector.AddReferencedObjects(ThisMSIC->Instances, ThisMSIC); + } +} + +bool +UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( + const TArray& InstanceTransforms) +{ + if (Instances.Num() <= 0 && InstanceTransforms.Num() <= 0) + return false; + + if (!GetOwner() || GetOwner()->IsPendingKill()) + return false; + + // Destroy previous instances while keeping some of the one that we'll be able to reuse + ClearInstances(InstanceTransforms.Num()); + + // + if( !InstancedMesh || InstancedMesh->IsPendingKill() ) + { + HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); + return false; + } + + // Only create new SMC for newly added instances + for (int32 iAdd = Instances.Num(); iAdd < InstanceTransforms.Num(); iAdd++) + { + const FTransform& InstanceTransform = InstanceTransforms[iAdd]; + UStaticMeshComponent* SMC = NewObject< UStaticMeshComponent >( + GetOwner(), UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + SMC->SetRelativeTransform(InstanceTransform); + Instances.Add(SMC); + GetOwner()->AddInstanceComponent(SMC); + } + + // We should now have the same number of instances than transform + ensure(InstanceTransforms.Num() == Instances.Num()); + if (InstanceTransforms.Num() != Instances.Num()) + return false; + + for (int32 iIns = 0; iIns < Instances.Num(); ++iIns) + { + UStaticMeshComponent* SMC = Instances[iIns]; + const FTransform& InstanceTransform = InstanceTransforms[iIns]; + + if (!SMC || SMC->IsPendingKill()) + continue; + + SMC->SetRelativeTransform(InstanceTransform); + + // Attach created static mesh component to this thing + SMC->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + SMC->SetStaticMesh(InstancedMesh); + SMC->SetVisibility(IsVisible()); + SMC->SetMobility(Mobility); + + // TODO: Revert to default if override is null?? + UMaterialInterface* MI = nullptr; + if (OverrideMaterials.Num() > 0) + { + if (OverrideMaterials.IsValidIndex(iIns)) + MI = OverrideMaterials[iIns]; + else + MI = OverrideMaterials[0]; + } + + if (MI && !MI->IsPendingKill()) + { + int32 MeshMaterialCount = InstancedMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + SMC->SetMaterial(Idx, MI); + } + + SMC->RegisterComponent(); + + /* + // TODO: + // Properties not being propagated to newly created UStaticMeshComponents + if (UHoudiniAssetComponent * pHoudiniAsset = Cast(GetAttachParent())) + { + pHoudiniAsset->CopyComponentPropertiesTo(SMC); + } + */ + } + + return true; +} + +void +UHoudiniMeshSplitInstancerComponent::ClearInstances(int32 NumToKeep) +{ + if (NumToKeep <= 0) + { + for (auto&& Instance : Instances) + { + if (Instance) + { + Instance->ConditionalBeginDestroy(); + } + } + Instances.Empty(); + } + else if (NumToKeep > 0 && NumToKeep < Instances.Num()) + { + for (int32 i = NumToKeep; i < Instances.Num(); ++i) + { + UStaticMeshComponent * Instance = Instances[i]; + if (Instance) + { + Instance->ConditionalBeginDestroy(); + } + } + Instances.SetNum(NumToKeep); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h index 2a4b4f31c..263b210ba 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h @@ -1,85 +1,85 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" -#include "Engine/StaticMesh.h" -#include "Materials/MaterialInterface.h" -#include "HoudiniMeshSplitInstancerComponent.generated.h" - -/** -* UHoudiniMeshSplitInstancerComponent is used to manage a single static mesh being -* 'instanced' multiple times by multiple UStaticMeshComponents. This is as opposed to the -* UInstancedStaticMeshComponent wherein a single mesh is instanced multiple times by one component. -*/ - -UCLASS()//( config = Engine ) -class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USceneComponent -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniMeshSplitInstancerComponent_V1; - - public: - - virtual void Serialize(FArchive & Ar) override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - - static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); - - // Static Mesh mutator - void SetStaticMesh(class UStaticMesh* StaticMesh) { InstancedMesh = StaticMesh; } - - // Static mesh accessor - class UStaticMesh* GetStaticMesh() const { return InstancedMesh; } - - // Overide material mutator - void SetOverrideMaterials(const TArray& InMaterialOverrides) { OverrideMaterials = InMaterialOverrides; } - - // Destroy existing instances, keeping a given number of them to be reused - void ClearInstances(int32 NumToKeep); - - // Set the instances. Transforms are given in local space of this component. - bool SetInstanceTransforms(const TArray& InstanceTransforms); - - // Instance Accessor - TArray& GetInstancesForWrite() { return Instances; } - // const Instance accessor - const TArray& GetInstances() const { return Instances; } - - private: - - UPROPERTY(VisibleInstanceOnly, Category = Instances) - TArray Instances; - - UPROPERTY(VisibleInstanceOnly, Category = Instances) - TArray OverrideMaterials; - - UPROPERTY(VisibleAnywhere, Category = Instances) - class UStaticMesh* InstancedMesh; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" +#include "Engine/StaticMesh.h" +#include "Materials/MaterialInterface.h" +#include "HoudiniMeshSplitInstancerComponent.generated.h" + +/** +* UHoudiniMeshSplitInstancerComponent is used to manage a single static mesh being +* 'instanced' multiple times by multiple UStaticMeshComponents. This is as opposed to the +* UInstancedStaticMeshComponent wherein a single mesh is instanced multiple times by one component. +*/ + +UCLASS()//( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniMeshSplitInstancerComponent_V1; + + public: + + virtual void Serialize(FArchive & Ar) override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); + + // Static Mesh mutator + void SetStaticMesh(class UStaticMesh* StaticMesh) { InstancedMesh = StaticMesh; } + + // Static mesh accessor + class UStaticMesh* GetStaticMesh() const { return InstancedMesh; } + + // Overide material mutator + void SetOverrideMaterials(const TArray& InMaterialOverrides) { OverrideMaterials = InMaterialOverrides; } + + // Destroy existing instances, keeping a given number of them to be reused + void ClearInstances(int32 NumToKeep); + + // Set the instances. Transforms are given in local space of this component. + bool SetInstanceTransforms(const TArray& InstanceTransforms); + + // Instance Accessor + TArray& GetInstancesForWrite() { return Instances; } + // const Instance accessor + const TArray& GetInstances() const { return Instances; } + + private: + + UPROPERTY(VisibleInstanceOnly, Category = Instances) + TArray Instances; + + UPROPERTY(VisibleInstanceOnly, Category = Instances) + TArray OverrideMaterials; + + UPROPERTY(VisibleAnywhere, Category = Instances) + class UStaticMesh* InstancedMesh; +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp index 07fdbdee0..59dadef63 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp @@ -1,858 +1,858 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniOutput.h" -#include "HoudiniAssetComponent.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniSplineComponent.h" - -#include "Components/SceneComponent.h" -#include "Components/MeshComponent.h" -#include "Components/SplineComponent.h" -#include "Misc/StringFormatArg.h" -#include "Engine/Engine.h" - -UHoudiniLandscapePtr::UHoudiniLandscapePtr(class FObjectInitializer const& Initializer) -{ - // bIsWorldCompositionLandscape = false; - // BakeType = EHoudiniLandscapeOutputBakeType::Detachment; -}; - -uint32 -GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier) -{ - return HoudiniOutputObjectIdentifier.GetTypeHash(); -} - -void -FHoudiniInstancedOutput::SetVariationObjectAt(const int32& AtIndex, UObject* InObject) -{ - // Resize the array if needed - if (VariationObjects.Num() <= AtIndex) - VariationObjects.SetNum(AtIndex + 1); - - if (VariationTransformOffsets.Num() <= AtIndex) - VariationTransformOffsets.SetNum(AtIndex + 1); - - UObject* CurrentObject = VariationObjects[AtIndex].LoadSynchronous(); - if (CurrentObject == InObject) - return; - - VariationObjects[AtIndex] = InObject; -} - -bool -FHoudiniInstancedOutput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; - if (!Transform) - return false; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - if (Position[XYZIndex] == Value) - return false; - Position[XYZIndex] = Value; - Transform->SetLocation(Position); - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - if (Rotator.Roll == Value) - return false; - Rotator.Roll = Value; - break; - } - - case 1: - { - if (Rotator.Pitch == Value) - return false; - Rotator.Pitch = Value; - break; - } - - case 2: - { - if (Rotator.Yaw == Value) - return false; - Rotator.Yaw = Value; - break; - } - } - Transform->SetRotation(Rotator.Quaternion()); - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - if (Scale[XYZIndex] == Value) - return false; - - Scale[XYZIndex] = Value; - Transform->SetScale3D(Scale); - } - - MarkChanged(true); - - return true; -} - -float -FHoudiniInstancedOutput::GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; - if (!Transform) - return 0.0f; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - return Position[XYZIndex]; - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - return Rotator.Roll; - } - - case 1: - { - return Rotator.Pitch; - } - - case 2: - { - return Rotator.Yaw; - } - } - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - return Scale[XYZIndex]; - } - - return 0.0f; -} - -// ---------------------------------------------------- -// FHoudiniOutputObjectIdentifier -// ---------------------------------------------------- - -FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier() -{ - ObjectId = -1; - GeoId = -1; - PartId = -1; - SplitIdentifier = FString(); - PartName = FString(); -} - -FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier( - const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier) -{ - ObjectId = InObjectId; - GeoId = InGeoId; - PartId = InPartId; - SplitIdentifier = InSplitIdentifier; -} - -uint32 -FHoudiniOutputObjectIdentifier::GetTypeHash() const -{ - int32 HashBuffer[3] = { ObjectId, GeoId, PartId }; - int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); - return FCrc::StrCrc32(*SplitIdentifier, Hash); -} - -bool -FHoudiniOutputObjectIdentifier::operator==(const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier) const -{ - // Object/Geo/Part IDs must match - bool bMatchingIds = true; - if (ObjectId != InOutputObjectIdentifier.ObjectId - || GeoId != InOutputObjectIdentifier.GeoId - || PartId != InOutputObjectIdentifier.PartId) - bMatchingIds = false; - - if ((bLoaded && !InOutputObjectIdentifier.bLoaded) - || (!bLoaded && InOutputObjectIdentifier.bLoaded)) - { - // If one of the two identifier is loaded, - // we can simply compare the part names - if (PartName.Equals(InOutputObjectIdentifier.PartName) - && SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) - return true; - } - - if (!bMatchingIds) - { - return false; - } - - // If split ID and name match, we're equal... - if (SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) - return true; - - // ... if not we're different - return false; -} - -bool -FHoudiniOutputObjectIdentifier::Matches(const FHoudiniGeoPartObject& InHGPO) const -{ - // Object/Geo/Part IDs must match - bool bMatchingIds = true; - if (ObjectId != InHGPO.ObjectId - || GeoId != InHGPO.GeoId - || PartId != InHGPO.PartId) - bMatchingIds = false; - - if ((bLoaded && !InHGPO.bLoaded) || (!bLoaded && InHGPO.bLoaded)) - { - // If either the HGPO or the Identifer is nmarked as loaded, - // we can simply compare the part names - if (PartName.Equals(InHGPO.PartName)) - return true; - } - - if (!bMatchingIds) - { - return false; - } - - // If the HGPO has our split identifier - //if (InHGPO.SplitGroups.Contains(SplitIdentifier)) - // return true; - - // - return true; -} - - -FHoudiniBakedOutputObject::FHoudiniBakedOutputObject() - : Actor() - , ActorBakeName(NAME_None) - , BakedObject() - , BakedComponent() -{ -} - - -FHoudiniBakedOutputObject::FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject, UObject* InBakedComponent) - : Actor(FSoftObjectPath(InActor).ToString()) - , ActorBakeName(InActorBakeName) - , BakedObject(FSoftObjectPath(InBakeObject).ToString()) - , BakedComponent(FSoftObjectPath(InBakedComponent).ToString()) -{ -} - - -AActor* -FHoudiniBakedOutputObject::GetActorIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ActorPath(Actor); - - if (!ActorPath.IsValid()) - return nullptr; - - UObject* Object = ActorPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ActorPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Cast(Object); -} - -UObject* -FHoudiniBakedOutputObject::GetBakedObjectIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ObjectPath(BakedObject); - - if (!ObjectPath.IsValid()) - return nullptr; - - UObject* Object = ObjectPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ObjectPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Object; -} - -UObject* -FHoudiniBakedOutputObject::GetBakedComponentIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ComponentPath(BakedComponent); - - if (!ComponentPath.IsValid()) - return nullptr; - - UObject* Object = ComponentPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ComponentPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Object; -} - -UBlueprint* -FHoudiniBakedOutputObject::GetBlueprintIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath BlueprintPath(Blueprint); - - if (!BlueprintPath.IsValid()) - return nullptr; - - UObject* Object = BlueprintPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = BlueprintPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Cast(Object); -} - - -UHoudiniOutput::UHoudiniOutput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Type(EHoudiniOutputType::Invalid) - , StaleCount(0) - , bLandscapeWorldComposition(false) - , bIsEditableNode(false) - , bHasEditableNodeBuilt(false) - , bCanDeleteHoudiniNodes(true) -{ - -} - -UHoudiniOutput::~UHoudiniOutput() -{ - Type = EHoudiniOutputType::Invalid; - StaleCount = 0; - bIsUpdating = false; - - HoudiniGeoPartObjects.Empty(); - OutputObjects.Empty(); - InstancedOutputs.Empty(); - AssignementMaterials.Empty(); - ReplacementMaterials.Empty(); -} - -void -UHoudiniOutput::BeginDestroy() -{ - Super::BeginDestroy(); -} - -FBox -UHoudiniOutput::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - - switch (GetType()) - { - case EHoudiniOutputType::Mesh: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - - UMeshComponent* MeshComp = nullptr; - if (CurObj.bProxyIsCurrent) - { - MeshComp = Cast(CurObj.ProxyComponent); - } - else - { - MeshComp = Cast(CurObj.OutputComponent); - } - - if (!MeshComp || MeshComp->IsPendingKill()) - continue; - - BoxBounds += MeshComp->Bounds.GetBox(); - } - } - break; - - case EHoudiniOutputType::Landscape: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - UHoudiniLandscapePtr* CurLandscapeObj = Cast(CurObj.OutputObject); - if (!CurLandscapeObj || CurLandscapeObj->IsPendingKill()) - continue; - - ALandscapeProxy* Landscape = Cast(CurLandscapeObj->GetRawPtr()); - if (!Landscape || Landscape->IsPendingKill()) - continue; - - FVector Origin, Extent; - Landscape->GetActorBounds(false, Origin, Extent); - - FBox LandscapeBounds = FBox::BuildAABB(Origin, Extent); - BoxBounds += LandscapeBounds; - } - } - break; - - case EHoudiniOutputType::Instancer: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - USceneComponent* InstancedComp = Cast(CurObj.OutputObject); - if (!InstancedComp || InstancedComp->IsPendingKill()) - continue; - - BoxBounds += InstancedComp->Bounds.GetBox(); - } - } - break; - - case EHoudiniOutputType::Curve: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - UHoudiniSplineComponent* CurHoudiniSplineComp = Cast(CurObj.OutputComponent); - if (!CurHoudiniSplineComp || CurHoudiniSplineComp->IsPendingKill()) - continue; - - FBox CurCurveBound(ForceInitToZero); - for (auto & Trans : CurHoudiniSplineComp->CurvePoints) - { - CurCurveBound += Trans.GetLocation(); - } - - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) - BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); - } - - } - break; - - case EHoudiniOutputType::Skeletal: - case EHoudiniOutputType::Invalid: - break; - - default: - break; - } - - return BoxBounds; -} - -void -UHoudiniOutput::Clear() -{ - StaleCount = 0; - - HoudiniGeoPartObjects.Empty(); - - for (auto& CurrentOutputObject : OutputObjects) - { - // Clear the output component - USceneComponent* SceneComp = Cast(CurrentOutputObject.Value.OutputComponent); - if (SceneComp && !SceneComp->IsPendingKill()) - { - SceneComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComp->UnregisterComponent(); - SceneComp->DestroyComponent(); - } - - // Also destroy proxy components - USceneComponent* ProxyComp = Cast(CurrentOutputObject.Value.ProxyComponent); - if (ProxyComp && !ProxyComp->IsPendingKill()) - { - ProxyComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - ProxyComp->UnregisterComponent(); - ProxyComp->DestroyComponent(); - } - - if (Type == EHoudiniOutputType::Landscape && !bLandscapeWorldComposition && !IsGarbageCollecting()) - { - // NOTE: We cannot resolve soft pointers during garbage collection. Any Get() or IsValid() call - // will result in a StaticFindObject() call which will raise an exception during GC. - UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentOutputObject.Value.OutputObject); - ALandscapeProxy* LandscapeProxy = LandscapePtr ? LandscapePtr->GetRawPtr() : nullptr; - if (LandscapeProxy && !LandscapeProxy->IsPendingKill()) - { - LandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - LandscapeProxy->ConditionalBeginDestroy(); - LandscapeProxy->Destroy(); - } - } - } - - OutputObjects.Empty(); - InstancedOutputs.Empty(); - AssignementMaterials.Empty(); - ReplacementMaterials.Empty(); - - Type = EHoudiniOutputType::Invalid; -} - -const bool -UHoudiniOutput::HasGeoChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasGeoChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasTransformChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasTransformChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasMaterialsChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasMaterialsChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const -{ - return HoudiniGeoPartObjects.Find(InHGPO) != INDEX_NONE; -} - -const bool -UHoudiniOutput::HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const -{ - if (InHGPO.Type != EHoudiniPartType::Volume) - return false; - - if (InHGPO.VolumeName.IsEmpty()) - return false; - - for (auto& currentHGPO : HoudiniGeoPartObjects) - { - // Asset/Object/Geo IDs should match - if (currentHGPO.AssetId != InHGPO.AssetId - || currentHGPO.ObjectId != InHGPO.ObjectId - || currentHGPO.GeoId != InHGPO.GeoId) - { - continue; - } - - // Both HGPO type should be volumes - if (currentHGPO.Type != EHoudiniPartType::Volume) - { - continue; - } - - // Volume tile index should match - if (currentHGPO.VolumeTileIndex != InHGPO.VolumeTileIndex) - { - continue; - } - - // We've specified if we want the name to match/to be different: - // when looking in previous outputs, we want the name to match - // when looking in newly created outputs, we want to be sure the names are different - bool bNameMatch = InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase); - if (bNameMatch != bVolumeNameShouldMatch) - continue; - - return true; - } - - return false; -} - -void -UHoudiniOutput::MarkAllHGPOsAsStale(const bool& bInStale) -{ - // Since objects can only be added to this array, - // Simply keep track of the current number of HoudiniGeoPartObject - StaleCount = bInStale ? HoudiniGeoPartObjects.Num() : 0; -} - -void -UHoudiniOutput::DeleteAllStaleHGPOs() -{ - // Simply delete the first "StaleCount" objects and reset the stale marker - HoudiniGeoPartObjects.RemoveAt(0, StaleCount); - StaleCount = 0; -} - -void -UHoudiniOutput::AddNewHGPO(const FHoudiniGeoPartObject& InHGPO) -{ - HoudiniGeoPartObjects.Add(InHGPO); -} - -void -UHoudiniOutput::UpdateOutputType() -{ - int32 MeshCount = 0; - int32 CurveCount = 0; - int32 VolumeCount = 0; - int32 InstancerCount = 0; - for (auto& HGPO : HoudiniGeoPartObjects) - { - switch (HGPO.Type) - { - case EHoudiniPartType::Mesh: - MeshCount++; - break; - case EHoudiniPartType::Curve: - CurveCount++; - break; - case EHoudiniPartType::Volume: - VolumeCount++; - break; - case EHoudiniPartType::Instancer: - InstancerCount++; - break; - default: - case EHoudiniPartType::Invalid: - break; - } - } - - if (VolumeCount > 0) - { - // If we have a volume, we're a landscape - Type = EHoudiniOutputType::Landscape; - } - else if (InstancerCount > 0) - { - // if we have at least an instancer, we're one - Type = EHoudiniOutputType::Instancer; - } - else if (MeshCount > 0) - { - Type = EHoudiniOutputType::Mesh; - } - else if (CurveCount > 0) - { - Type = EHoudiniOutputType::Curve; - } - else - { - // No valid HGPO detected... - Type = EHoudiniOutputType::Invalid; - } -} - -UHoudiniOutput* -UHoudiniOutput::DuplicateAndCopyProperties(UObject* DestOuter, FName NewName) -{ - UHoudiniOutput* NewOutput = Cast(StaticDuplicateObject(this, DestOuter, NewName)); - - NewOutput->CopyPropertiesFrom(this, false); - - return NewOutput; -} - -void -UHoudiniOutput::CopyPropertiesFrom(UHoudiniOutput* InInput, bool bCopyAllProperties) -{ - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - // Stash all the data that we want to preserve, and re-apply after property copy took place - // (similar to Get/Apply component instance data). This is typically only needed - // for certain properties that require cleanup when being replaced / removed. - TMap PrevOutputObjects = OutputObjects; - TMap PrevInstancedOutputs = InstancedOutputs; - - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - - // Restore the desired properties. - OutputObjects = PrevOutputObjects; - InstancedOutputs = PrevInstancedOutputs; - } - - // Copy any additional DuplicateTransient properties. - bHasEditableNodeBuilt = InInput->bHasEditableNodeBuilt; -} - -void -UHoudiniOutput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; -} - -FString -UHoudiniOutput::OutputTypeToString(const EHoudiniOutputType& InOutputType) -{ - FString OutputTypeStr; - switch (InOutputType) - { - case EHoudiniOutputType::Mesh: - OutputTypeStr = TEXT("Mesh"); - break; - case EHoudiniOutputType::Instancer: - OutputTypeStr = TEXT("Instancer"); - break; - case EHoudiniOutputType::Landscape: - OutputTypeStr = TEXT("Landscape"); - break; - case EHoudiniOutputType::Curve: - OutputTypeStr = TEXT("Curve"); - break; - case EHoudiniOutputType::Skeletal: - OutputTypeStr = TEXT("Skeletal"); - break; - - default: - case EHoudiniOutputType::Invalid: - OutputTypeStr = TEXT("Invalid"); - break; - } - - return OutputTypeStr; -} - -void -UHoudiniOutput::MarkAsLoaded(const bool& InLoaded) -{ - // Mark all HGPO as loaded - for (auto& HGPO : HoudiniGeoPartObjects) - { - HGPO.bLoaded = InLoaded; - } - - // Mark all output object's identifier as loaded - for (auto& Iter : OutputObjects) - { - FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; - Identifier.bLoaded = InLoaded; - } - - // Instanced outputs - for (auto& Iter : InstancedOutputs) - { - FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; - Identifier.bLoaded = InLoaded; - } -} - - -const bool -UHoudiniOutput::HasAnyProxy() const -{ - for (const auto& Pair : OutputObjects) - { - UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) - { - return true; - } - } - - return false; -} - -const bool -UHoudiniOutput::HasProxy(const FHoudiniOutputObjectIdentifier& InIdentifier) const -{ - const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - if (!FoundOutputObject) - return false; - - UObject* FoundProxy = FoundOutputObject->ProxyObject; - if (!FoundProxy || FoundProxy->IsPendingKill()) - return false; - - return true; -} - -const bool -UHoudiniOutput::HasAnyCurrentProxy() const -{ - for (const auto& Pair : OutputObjects) - { - UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) - { - if(Pair.Value.bProxyIsCurrent) - { - return true; - } - } - } - - return false; -} - -const bool -UHoudiniOutput::IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const -{ - if (!HasProxy(InIdentifier)) - return false; - - const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - if (!FoundOutputObject) - return false; - - return FoundOutputObject->bProxyIsCurrent; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniOutput.h" +#include "HoudiniAssetComponent.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniSplineComponent.h" + +#include "Components/SceneComponent.h" +#include "Components/MeshComponent.h" +#include "Components/SplineComponent.h" +#include "Misc/StringFormatArg.h" +#include "Engine/Engine.h" + +UHoudiniLandscapePtr::UHoudiniLandscapePtr(class FObjectInitializer const& Initializer) +{ + // bIsWorldCompositionLandscape = false; + // BakeType = EHoudiniLandscapeOutputBakeType::Detachment; +}; + +uint32 +GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier) +{ + return HoudiniOutputObjectIdentifier.GetTypeHash(); +} + +void +FHoudiniInstancedOutput::SetVariationObjectAt(const int32& AtIndex, UObject* InObject) +{ + // Resize the array if needed + if (VariationObjects.Num() <= AtIndex) + VariationObjects.SetNum(AtIndex + 1); + + if (VariationTransformOffsets.Num() <= AtIndex) + VariationTransformOffsets.SetNum(AtIndex + 1); + + UObject* CurrentObject = VariationObjects[AtIndex].LoadSynchronous(); + if (CurrentObject == InObject) + return; + + VariationObjects[AtIndex] = InObject; +} + +bool +FHoudiniInstancedOutput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; + if (!Transform) + return false; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + if (Position[XYZIndex] == Value) + return false; + Position[XYZIndex] = Value; + Transform->SetLocation(Position); + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + if (Rotator.Roll == Value) + return false; + Rotator.Roll = Value; + break; + } + + case 1: + { + if (Rotator.Pitch == Value) + return false; + Rotator.Pitch = Value; + break; + } + + case 2: + { + if (Rotator.Yaw == Value) + return false; + Rotator.Yaw = Value; + break; + } + } + Transform->SetRotation(Rotator.Quaternion()); + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + if (Scale[XYZIndex] == Value) + return false; + + Scale[XYZIndex] = Value; + Transform->SetScale3D(Scale); + } + + MarkChanged(true); + + return true; +} + +float +FHoudiniInstancedOutput::GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; + if (!Transform) + return 0.0f; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + return Position[XYZIndex]; + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + return Rotator.Roll; + } + + case 1: + { + return Rotator.Pitch; + } + + case 2: + { + return Rotator.Yaw; + } + } + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + return Scale[XYZIndex]; + } + + return 0.0f; +} + +// ---------------------------------------------------- +// FHoudiniOutputObjectIdentifier +// ---------------------------------------------------- + +FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier() +{ + ObjectId = -1; + GeoId = -1; + PartId = -1; + SplitIdentifier = FString(); + PartName = FString(); +} + +FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier( + const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier) +{ + ObjectId = InObjectId; + GeoId = InGeoId; + PartId = InPartId; + SplitIdentifier = InSplitIdentifier; +} + +uint32 +FHoudiniOutputObjectIdentifier::GetTypeHash() const +{ + int32 HashBuffer[3] = { ObjectId, GeoId, PartId }; + int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitIdentifier, Hash); +} + +bool +FHoudiniOutputObjectIdentifier::operator==(const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier) const +{ + // Object/Geo/Part IDs must match + bool bMatchingIds = true; + if (ObjectId != InOutputObjectIdentifier.ObjectId + || GeoId != InOutputObjectIdentifier.GeoId + || PartId != InOutputObjectIdentifier.PartId) + bMatchingIds = false; + + if ((bLoaded && !InOutputObjectIdentifier.bLoaded) + || (!bLoaded && InOutputObjectIdentifier.bLoaded)) + { + // If one of the two identifier is loaded, + // we can simply compare the part names + if (PartName.Equals(InOutputObjectIdentifier.PartName) + && SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) + return true; + } + + if (!bMatchingIds) + { + return false; + } + + // If split ID and name match, we're equal... + if (SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) + return true; + + // ... if not we're different + return false; +} + +bool +FHoudiniOutputObjectIdentifier::Matches(const FHoudiniGeoPartObject& InHGPO) const +{ + // Object/Geo/Part IDs must match + bool bMatchingIds = true; + if (ObjectId != InHGPO.ObjectId + || GeoId != InHGPO.GeoId + || PartId != InHGPO.PartId) + bMatchingIds = false; + + if ((bLoaded && !InHGPO.bLoaded) || (!bLoaded && InHGPO.bLoaded)) + { + // If either the HGPO or the Identifer is nmarked as loaded, + // we can simply compare the part names + if (PartName.Equals(InHGPO.PartName)) + return true; + } + + if (!bMatchingIds) + { + return false; + } + + // If the HGPO has our split identifier + //if (InHGPO.SplitGroups.Contains(SplitIdentifier)) + // return true; + + // + return true; +} + + +FHoudiniBakedOutputObject::FHoudiniBakedOutputObject() + : Actor() + , ActorBakeName(NAME_None) + , BakedObject() + , BakedComponent() +{ +} + + +FHoudiniBakedOutputObject::FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject, UObject* InBakedComponent) + : Actor(FSoftObjectPath(InActor).ToString()) + , ActorBakeName(InActorBakeName) + , BakedObject(FSoftObjectPath(InBakeObject).ToString()) + , BakedComponent(FSoftObjectPath(InBakedComponent).ToString()) +{ +} + + +AActor* +FHoudiniBakedOutputObject::GetActorIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ActorPath(Actor); + + if (!ActorPath.IsValid()) + return nullptr; + + UObject* Object = ActorPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ActorPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + +UObject* +FHoudiniBakedOutputObject::GetBakedObjectIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ObjectPath(BakedObject); + + if (!ObjectPath.IsValid()) + return nullptr; + + UObject* Object = ObjectPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ObjectPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Object; +} + +UObject* +FHoudiniBakedOutputObject::GetBakedComponentIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ComponentPath(BakedComponent); + + if (!ComponentPath.IsValid()) + return nullptr; + + UObject* Object = ComponentPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ComponentPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Object; +} + +UBlueprint* +FHoudiniBakedOutputObject::GetBlueprintIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath BlueprintPath(Blueprint); + + if (!BlueprintPath.IsValid()) + return nullptr; + + UObject* Object = BlueprintPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = BlueprintPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + + +UHoudiniOutput::UHoudiniOutput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Type(EHoudiniOutputType::Invalid) + , StaleCount(0) + , bLandscapeWorldComposition(false) + , bIsEditableNode(false) + , bHasEditableNodeBuilt(false) + , bCanDeleteHoudiniNodes(true) +{ + +} + +UHoudiniOutput::~UHoudiniOutput() +{ + Type = EHoudiniOutputType::Invalid; + StaleCount = 0; + bIsUpdating = false; + + HoudiniGeoPartObjects.Empty(); + OutputObjects.Empty(); + InstancedOutputs.Empty(); + AssignementMaterials.Empty(); + ReplacementMaterials.Empty(); +} + +void +UHoudiniOutput::BeginDestroy() +{ + Super::BeginDestroy(); +} + +FBox +UHoudiniOutput::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + + switch (GetType()) + { + case EHoudiniOutputType::Mesh: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + + UMeshComponent* MeshComp = nullptr; + if (CurObj.bProxyIsCurrent) + { + MeshComp = Cast(CurObj.ProxyComponent); + } + else + { + MeshComp = Cast(CurObj.OutputComponent); + } + + if (!MeshComp || MeshComp->IsPendingKill()) + continue; + + BoxBounds += MeshComp->Bounds.GetBox(); + } + } + break; + + case EHoudiniOutputType::Landscape: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + UHoudiniLandscapePtr* CurLandscapeObj = Cast(CurObj.OutputObject); + if (!CurLandscapeObj || CurLandscapeObj->IsPendingKill()) + continue; + + ALandscapeProxy* Landscape = Cast(CurLandscapeObj->GetRawPtr()); + if (!Landscape || Landscape->IsPendingKill()) + continue; + + FVector Origin, Extent; + Landscape->GetActorBounds(false, Origin, Extent); + + FBox LandscapeBounds = FBox::BuildAABB(Origin, Extent); + BoxBounds += LandscapeBounds; + } + } + break; + + case EHoudiniOutputType::Instancer: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + USceneComponent* InstancedComp = Cast(CurObj.OutputObject); + if (!InstancedComp || InstancedComp->IsPendingKill()) + continue; + + BoxBounds += InstancedComp->Bounds.GetBox(); + } + } + break; + + case EHoudiniOutputType::Curve: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + UHoudiniSplineComponent* CurHoudiniSplineComp = Cast(CurObj.OutputComponent); + if (!CurHoudiniSplineComp || CurHoudiniSplineComp->IsPendingKill()) + continue; + + FBox CurCurveBound(ForceInitToZero); + for (auto & Trans : CurHoudiniSplineComp->CurvePoints) + { + CurCurveBound += Trans.GetLocation(); + } + + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !OuterHAC->IsPendingKill()) + BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); + } + + } + break; + + case EHoudiniOutputType::Skeletal: + case EHoudiniOutputType::Invalid: + break; + + default: + break; + } + + return BoxBounds; +} + +void +UHoudiniOutput::Clear() +{ + StaleCount = 0; + + HoudiniGeoPartObjects.Empty(); + + for (auto& CurrentOutputObject : OutputObjects) + { + // Clear the output component + USceneComponent* SceneComp = Cast(CurrentOutputObject.Value.OutputComponent); + if (SceneComp && !SceneComp->IsPendingKill()) + { + SceneComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComp->UnregisterComponent(); + SceneComp->DestroyComponent(); + } + + // Also destroy proxy components + USceneComponent* ProxyComp = Cast(CurrentOutputObject.Value.ProxyComponent); + if (ProxyComp && !ProxyComp->IsPendingKill()) + { + ProxyComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + ProxyComp->UnregisterComponent(); + ProxyComp->DestroyComponent(); + } + + if (Type == EHoudiniOutputType::Landscape && !bLandscapeWorldComposition && !IsGarbageCollecting()) + { + // NOTE: We cannot resolve soft pointers during garbage collection. Any Get() or IsValid() call + // will result in a StaticFindObject() call which will raise an exception during GC. + UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentOutputObject.Value.OutputObject); + ALandscapeProxy* LandscapeProxy = LandscapePtr ? LandscapePtr->GetRawPtr() : nullptr; + if (LandscapeProxy && !LandscapeProxy->IsPendingKill()) + { + LandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + LandscapeProxy->ConditionalBeginDestroy(); + LandscapeProxy->Destroy(); + } + } + } + + OutputObjects.Empty(); + InstancedOutputs.Empty(); + AssignementMaterials.Empty(); + ReplacementMaterials.Empty(); + + Type = EHoudiniOutputType::Invalid; +} + +const bool +UHoudiniOutput::HasGeoChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasGeoChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasTransformChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasTransformChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasMaterialsChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasMaterialsChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const +{ + return HoudiniGeoPartObjects.Find(InHGPO) != INDEX_NONE; +} + +const bool +UHoudiniOutput::HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const +{ + if (InHGPO.Type != EHoudiniPartType::Volume) + return false; + + if (InHGPO.VolumeName.IsEmpty()) + return false; + + for (auto& currentHGPO : HoudiniGeoPartObjects) + { + // Asset/Object/Geo IDs should match + if (currentHGPO.AssetId != InHGPO.AssetId + || currentHGPO.ObjectId != InHGPO.ObjectId + || currentHGPO.GeoId != InHGPO.GeoId) + { + continue; + } + + // Both HGPO type should be volumes + if (currentHGPO.Type != EHoudiniPartType::Volume) + { + continue; + } + + // Volume tile index should match + if (currentHGPO.VolumeTileIndex != InHGPO.VolumeTileIndex) + { + continue; + } + + // We've specified if we want the name to match/to be different: + // when looking in previous outputs, we want the name to match + // when looking in newly created outputs, we want to be sure the names are different + bool bNameMatch = InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase); + if (bNameMatch != bVolumeNameShouldMatch) + continue; + + return true; + } + + return false; +} + +void +UHoudiniOutput::MarkAllHGPOsAsStale(const bool& bInStale) +{ + // Since objects can only be added to this array, + // Simply keep track of the current number of HoudiniGeoPartObject + StaleCount = bInStale ? HoudiniGeoPartObjects.Num() : 0; +} + +void +UHoudiniOutput::DeleteAllStaleHGPOs() +{ + // Simply delete the first "StaleCount" objects and reset the stale marker + HoudiniGeoPartObjects.RemoveAt(0, StaleCount); + StaleCount = 0; +} + +void +UHoudiniOutput::AddNewHGPO(const FHoudiniGeoPartObject& InHGPO) +{ + HoudiniGeoPartObjects.Add(InHGPO); +} + +void +UHoudiniOutput::UpdateOutputType() +{ + int32 MeshCount = 0; + int32 CurveCount = 0; + int32 VolumeCount = 0; + int32 InstancerCount = 0; + for (auto& HGPO : HoudiniGeoPartObjects) + { + switch (HGPO.Type) + { + case EHoudiniPartType::Mesh: + MeshCount++; + break; + case EHoudiniPartType::Curve: + CurveCount++; + break; + case EHoudiniPartType::Volume: + VolumeCount++; + break; + case EHoudiniPartType::Instancer: + InstancerCount++; + break; + default: + case EHoudiniPartType::Invalid: + break; + } + } + + if (VolumeCount > 0) + { + // If we have a volume, we're a landscape + Type = EHoudiniOutputType::Landscape; + } + else if (InstancerCount > 0) + { + // if we have at least an instancer, we're one + Type = EHoudiniOutputType::Instancer; + } + else if (MeshCount > 0) + { + Type = EHoudiniOutputType::Mesh; + } + else if (CurveCount > 0) + { + Type = EHoudiniOutputType::Curve; + } + else + { + // No valid HGPO detected... + Type = EHoudiniOutputType::Invalid; + } +} + +UHoudiniOutput* +UHoudiniOutput::DuplicateAndCopyProperties(UObject* DestOuter, FName NewName) +{ + UHoudiniOutput* NewOutput = Cast(StaticDuplicateObject(this, DestOuter, NewName)); + + NewOutput->CopyPropertiesFrom(this, false); + + return NewOutput; +} + +void +UHoudiniOutput::CopyPropertiesFrom(UHoudiniOutput* InInput, bool bCopyAllProperties) +{ + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + // Stash all the data that we want to preserve, and re-apply after property copy took place + // (similar to Get/Apply component instance data). This is typically only needed + // for certain properties that require cleanup when being replaced / removed. + TMap PrevOutputObjects = OutputObjects; + TMap PrevInstancedOutputs = InstancedOutputs; + + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + + // Restore the desired properties. + OutputObjects = PrevOutputObjects; + InstancedOutputs = PrevInstancedOutputs; + } + + // Copy any additional DuplicateTransient properties. + bHasEditableNodeBuilt = InInput->bHasEditableNodeBuilt; +} + +void +UHoudiniOutput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; +} + +FString +UHoudiniOutput::OutputTypeToString(const EHoudiniOutputType& InOutputType) +{ + FString OutputTypeStr; + switch (InOutputType) + { + case EHoudiniOutputType::Mesh: + OutputTypeStr = TEXT("Mesh"); + break; + case EHoudiniOutputType::Instancer: + OutputTypeStr = TEXT("Instancer"); + break; + case EHoudiniOutputType::Landscape: + OutputTypeStr = TEXT("Landscape"); + break; + case EHoudiniOutputType::Curve: + OutputTypeStr = TEXT("Curve"); + break; + case EHoudiniOutputType::Skeletal: + OutputTypeStr = TEXT("Skeletal"); + break; + + default: + case EHoudiniOutputType::Invalid: + OutputTypeStr = TEXT("Invalid"); + break; + } + + return OutputTypeStr; +} + +void +UHoudiniOutput::MarkAsLoaded(const bool& InLoaded) +{ + // Mark all HGPO as loaded + for (auto& HGPO : HoudiniGeoPartObjects) + { + HGPO.bLoaded = InLoaded; + } + + // Mark all output object's identifier as loaded + for (auto& Iter : OutputObjects) + { + FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; + Identifier.bLoaded = InLoaded; + } + + // Instanced outputs + for (auto& Iter : InstancedOutputs) + { + FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; + Identifier.bLoaded = InLoaded; + } +} + + +const bool +UHoudiniOutput::HasAnyProxy() const +{ + for (const auto& Pair : OutputObjects) + { + UObject* FoundProxy = Pair.Value.ProxyObject; + if (FoundProxy && !FoundProxy->IsPendingKill()) + { + return true; + } + } + + return false; +} + +const bool +UHoudiniOutput::HasProxy(const FHoudiniOutputObjectIdentifier& InIdentifier) const +{ + const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + if (!FoundOutputObject) + return false; + + UObject* FoundProxy = FoundOutputObject->ProxyObject; + if (!FoundProxy || FoundProxy->IsPendingKill()) + return false; + + return true; +} + +const bool +UHoudiniOutput::HasAnyCurrentProxy() const +{ + for (const auto& Pair : OutputObjects) + { + UObject* FoundProxy = Pair.Value.ProxyObject; + if (FoundProxy && !FoundProxy->IsPendingKill()) + { + if(Pair.Value.bProxyIsCurrent) + { + return true; + } + } + } + + return false; +} + +const bool +UHoudiniOutput::IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const +{ + if (!HasProxy(InIdentifier)) + return false; + + const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + if (!FoundOutputObject) + return false; + + return FoundOutputObject->bProxyIsCurrent; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h index b61e071f4..14136730e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h @@ -1,561 +1,561 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "HoudiniGeoPartObject.h" -#include "LandscapeProxy.h" -#include "Misc/StringFormatArg.h" -#include "UObject/SoftObjectPtr.h" - -#include "HoudiniOutput.generated.h" - -class UMaterialInterface; - -UENUM() -enum class EHoudiniOutputType : uint8 -{ - Invalid, - - Mesh, - Instancer, - Landscape, - Curve, - Skeletal -}; - -UENUM() -enum class EHoudiniCurveOutputType : uint8 -{ - UnrealSpline, - HoudiniSpline, -}; - -UENUM() -enum class EHoudiniLandscapeOutputBakeType : uint8 -{ - Detachment, - BakeToImage, - BakeToWorld, - InValid, -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniCurveOutputProperties -{ - GENERATED_USTRUCT_BODY() - - // Curve output properties - UPROPERTY() - EHoudiniCurveOutputType CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; - - UPROPERTY() - int32 NumPoints = -1; - - UPROPERTY() - bool bClosed = false; - - UPROPERTY() - EHoudiniCurveType CurveType = EHoudiniCurveType::Invalid; - - UPROPERTY() - EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::Invalid; -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniLandscapePtr : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - FORCEINLINE - void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; - - FORCEINLINE - TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; - - // Calling Get() during GC will raise an exception because Get calls StaticFindObject. - FORCEINLINE - ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; - - FORCEINLINE - FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; - - FORCEINLINE - void SetLandscapeOutputBakeType(const EHoudiniLandscapeOutputBakeType & InBakeType) { BakeType = InBakeType; }; - - FORCEINLINE - EHoudiniLandscapeOutputBakeType GetLandscapeOutputBakeType() const { return BakeType; }; - - UPROPERTY() - TSoftObjectPtr LandscapeSoftPtr; - - UPROPERTY() - EHoudiniLandscapeOutputBakeType BakeType; -}; - - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniOutputObjectIdentifier -{ - GENERATED_USTRUCT_BODY() - -public: - // Constructors - FHoudiniOutputObjectIdentifier(); - FHoudiniOutputObjectIdentifier(const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier); - - // Return hash value for this object, used when using this object as a key inside hashing containers. - uint32 GetTypeHash() const; - - // Comparison operator, used by hashing containers. - bool operator==(const FHoudiniOutputObjectIdentifier& HoudiniGeoPartObject) const; - - bool Matches(const FHoudiniGeoPartObject& HoudiniGeoPartObject) const; - -public: - - // NodeId of corresponding Houdini Object. - UPROPERTY() - int32 ObjectId = -1; - - // NodeId of corresponding Houdini Geo. - UPROPERTY() - int32 GeoId = -1; - - // PartId - UPROPERTY() - int32 PartId = -1; - - // String identifier for the split that created this - UPROPERTY() - FString SplitIdentifier = FString(); - - // Name of the part used to generate the output - UPROPERTY() - FString PartName = FString(); - - // First valid primitive index for this output - // (used to read generic attributes) - UPROPERTY() - int32 PrimitiveIndex = -1; - - // First valid point index for this output - // (used to read generic attributes) - UPROPERTY() - int32 PointIndex = -1; - - bool bLoaded = false; -}; - -/** Function used by hashing containers to create a unique hash for this type of object. **/ -HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier); - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput -{ - GENERATED_USTRUCT_BODY() - -public: - - void MarkChanged(const bool& InChanged) { bChanged = InChanged; }; - - void SetVariationObjectAt(const int32& AtIndex, UObject* InObject); - - bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - - float GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - -#if WITH_EDITOR - void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; - bool IsUnformScaleLocked() const { return bUniformScaleLocked; }; -#endif - -public: - - // Original object used by the instancer. - UPROPERTY() - TSoftObjectPtr OriginalObject = nullptr; - - UPROPERTY() - int32 OriginalObjectIndex = -1; - - // Original HoudiniGeoPartObject used by the instancer - //UPROPERTY() - //FHoudiniGeoPartObject OriginalHGPO; - - // Original Instance transforms - UPROPERTY() - TArray OriginalTransforms; - - // Variation objects currently used for instancing - UPROPERTY() - TArray> VariationObjects; - - // Transform offsets, one for each variation. - UPROPERTY() - TArray VariationTransformOffsets; - - // Index of the variation used for each transform - UPROPERTY() - TArray TransformVariationIndices; - - // Indicates this instanced output's component should be recreated - UPROPERTY() - bool bChanged = false; - - // Indicates this instanced output is stale and should be removed - UPROPERTY() - bool bStale = false; - - // Indicates if change the scale of Transfrom Offset of this object uniformly -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bUniformScaleLocked = false; -#endif - // TODO - // Color overrides?? -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject -{ - GENERATED_USTRUCT_BODY() - - public: - FHoudiniBakedOutputObject(); - - FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject=nullptr, UObject* InBakedComponent=nullptr); - - // Returns Actor if valid, otherwise nullptr - AActor* GetActorIfValid(bool bInTryLoad=true) const; - - // Returns BakedObject if valid, otherwise nullptr - UObject* GetBakedObjectIfValid(bool bInTryLoad=true) const; - - // Returns BakedComponent if valid, otherwise nullptr - UObject* GetBakedComponentIfValid(bool bInTryLoad=true) const; - - // Returns Blueprint if valid, otherwise nullptr - UBlueprint* GetBlueprintIfValid(bool bInTryLoad=true) const; - - // The actor that the baked output was associated with - UPROPERTY() - FString Actor; - - // The blueprint that baked output was associated with, if any - UPROPERTY() - FString Blueprint; - - // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. - UPROPERTY() - FName ActorBakeName = NAME_None; - - // The baked output asset - UPROPERTY() - FString BakedObject; - - // The baked output component - UPROPERTY() - FString BakedComponent; - - // In the case of instance actor component baking, this is the array of instanced actors - UPROPERTY() - TArray InstancedActors; - - // In the case of mesh split instancer baking: this is the array of instance components - UPROPERTY() - TArray InstancedComponents; -}; - -// Container to hold the map of baked objects. There should be one of -// these for each UHoudiniOutput. We manage this separately from UHoudiniOutput so -// that the "previous/last" bake objects can survive output reconstruction or PDG -// dirty/dirty all operations. -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutput -{ - GENERATED_USTRUCT_BODY() - - public: - UPROPERTY() - TMap BakedOutputObjects; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject -{ - GENERATED_USTRUCT_BODY() - - public: - - // The main output object - UPROPERTY() - UObject* OutputObject = nullptr; - - // The main output component - UPROPERTY() - UObject* OutputComponent = nullptr; - - // Proxy object - UPROPERTY() - UObject* ProxyObject = nullptr; - - // Proxy Component - UPROPERTY() - UObject* ProxyComponent = nullptr; - - // Mesh output properties - // If this is true the proxy mesh is "current", - // in other words, it is newer than the UStaticMesh - UPROPERTY() - bool bProxyIsCurrent = false; - - // Bake Name override for this output object - UPROPERTY() - FString BakeName; - - UPROPERTY() - FHoudiniCurveOutputProperties CurveOutputProperty; - - - // NOTE: The idea behind CachedAttributes and CachedTokens is to - // collect attributes (such as unreal_level_path and unreal_output_name) - // and context-specific tokens (hda name, hda actor name, geo and part ids, tile_id, etc) - // and cache them directly on the output object. When the object gets baked, - // certain tokens can be updated specifically for the bake pass afterwhich the - // the string / path attributes can be resolved with the updated tokens. - // - // A more concrete example: - // unreal_output_name = "{hda_actor_name}_PurplePlants_{geo_id}_{part_id}" - // unreal_level_path = "{out}/{hda_name}/{guid}/PurplePlants/{geo_id}/{part_id}" - // - // All of the aforementions tokens and attributes would be cached on the output object - // when it is being cooked so that the same values are available at bake time. During - // a bake some tokens may be updated, such as `{out}` to change where assets get serialized. - - UPROPERTY() - TMap CachedAttributes; - - // Cache any tokens here that is needed for string resolving - // at bake time. - UPROPERTY() - TMap CachedTokens; -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject -{ - GENERATED_UCLASS_BODY() - - // Declare translators as friend so they can easily directly modify - // and access our HGPO and Output objects - friend struct FHoudiniMeshTranslator; - friend struct FHoudiniInstanceTranslator; - - virtual ~UHoudiniOutput(); - -public: - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - const EHoudiniOutputType& GetType() const { return Type; }; - - const TArray& GetHoudiniGeoPartObjects() const { return HoudiniGeoPartObjects; }; - - // Returns true if we have a HGPO that matches - const bool HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const; - - // Returns true if the HGPO is fromn the same HF as us - const bool HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const; - - // Returns the output objects and their corresponding identifiers - TMap& GetOutputObjects() { return OutputObjects; }; - - // Returns the output objects and their corresponding identifiers - const TMap& GetOutputObjects() const { return OutputObjects; }; - - // Returns this output's assignement material map - TMap& GetAssignementMaterials() { return AssignementMaterials; }; - - // Returns this output's replacement material map - TMap& GetReplacementMaterials() { return ReplacementMaterials; }; - - // Returns the instanced outputs maps - TMap& GetInstancedOutputs() { return InstancedOutputs; }; - - const bool HasGeoChanged() const; - const bool HasTransformChanged() const; - const bool HasMaterialsChanged() const; - - // Returns true if there are any proxy objects in output (current or not) - const bool HasAnyProxy() const; - // Returns true if the specified identifier has a proxy object (current or not) - const bool HasProxy(const FHoudiniOutputObjectIdentifier &InIdentifier) const; - // Returns true if there are any current (most up to date and visible) proxy in the output - const bool HasAnyCurrentProxy() const; - // Returns true if the specified identifier's proxy is "current" (in other words, newer than - // the non-proxy and the proxy should thus be shown instead. - const bool IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const; - - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - void UpdateOutputType(); - - // Adds a new HoudiniGeoPartObject to our array - void AddNewHGPO(const FHoudiniGeoPartObject& InHGPO); - - // Mark all the current HGPO as stale (from a previous cook) - // So we can delte them all by calling DeleteAllStaleHGPOs after. - void MarkAllHGPOsAsStale(const bool& InStale); - - // Delete all the HGPO that were marked as stale - void DeleteAllStaleHGPOs(); - - void SetOutputObjects(const TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; - - void SetInstancedOutputs(const TMap& InInstancedOuput) { InstancedOutputs = InInstancedOuput; }; - - // Marks all HGPO and OutputIdentifier as loaded - void MarkAsLoaded(const bool& InLoaded); - - FORCEINLINE - const bool IsEditableNode() { return bIsEditableNode; }; - - FORCEINLINE - void SetIsEditableNode(bool IsEditable) { bIsEditableNode = IsEditable; } - - FORCEINLINE - const bool HasEditableNodeBuilt() { return bHasEditableNodeBuilt; }; - - FORCEINLINE - void SetHasEditableNodeBuilt(bool HasBuilt) { bHasEditableNodeBuilt = HasBuilt; } - - FORCEINLINE - void SetIsUpdating(bool bInIsUpdating) { bIsUpdating = bInIsUpdating; }; - - FORCEINLINE - bool IsUpdating() const { return bIsUpdating; }; - - FORCEINLINE - void SetLandscapeWorldComposition(const bool bInLandscapeWorldComposition) { bLandscapeWorldComposition = bInLandscapeWorldComposition; }; - - FORCEINLINE - bool IsLandscapeWorldComposition () const { return bLandscapeWorldComposition; }; - - FORCEINLINE - TArray & GetHoudiniCreatedSocketActors() { return HoudiniCreatedSocketActors; }; - - FORCEINLINE - TArray & GetHoudiniAttachedSocketActors() { return HoudiniAttachedSocketActors; } - - // Duplicate this object and copy its state to the resulting object. - // This is typically used to transfer state between between template and instance components. - UHoudiniOutput* DuplicateAndCopyProperties(UObject* DestOuter, FName NewName); - - // Copy properties but preserve output objects - virtual void CopyPropertiesFrom(UHoudiniOutput* InOutput, bool bCopyAllProperties); - - // Set whether this object can delete Houdini nodes. - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } - - //------------------------------------------------------------------------------------------------ - // Helpers - //------------------------------------------------------------------------------------------------ - static FString OutputTypeToString(const EHoudiniOutputType& InOutputType); - - FBox GetBounds() const; - - void Clear(); - -protected: - - virtual void BeginDestroy() override; - -protected: - - // Indicates the type of output we're dealing with - UPROPERTY() - EHoudiniOutputType Type; - - // The output's corresponding HGPO - UPROPERTY() - TArray HoudiniGeoPartObjects; - - // - UPROPERTY(DuplicateTransient) - TMap OutputObjects; - - // Instanced outputs - // Stores the instance variations objects (replacement), transform offsets - UPROPERTY() - TMap InstancedOutputs; - - // The material assignments for this output - UPROPERTY() - TMap AssignementMaterials; - - UPROPERTY() - TMap ReplacementMaterials; - - // Indicates the number of stale HGPO - int32 StaleCount; - - UPROPERTY() - bool bLandscapeWorldComposition; - - // stores the created actors for sockets with actor references. - // - UPROPERTY() - TArray HoudiniCreatedSocketActors; - - UPROPERTY() - TArray HoudiniAttachedSocketActors; - -private: - // Use HoudiniOutput to represent an editable curve. - // This flag tells whether this output is an editable curve. - UPROPERTY() - bool bIsEditableNode; - - // An editable node is only built once. This flag indicates whether this node has been built. - UPROPERTY(DuplicateTransient) - bool bHasEditableNodeBuilt; - - // The IsUpdating flag is set to true when this out exists and is being updated. - UPROPERTY() - bool bIsUpdating; - - UPROPERTY() - bool bCanDeleteHoudiniNodes; -}; - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "HoudiniGeoPartObject.h" +#include "LandscapeProxy.h" +#include "Misc/StringFormatArg.h" +#include "UObject/SoftObjectPtr.h" + +#include "HoudiniOutput.generated.h" + +class UMaterialInterface; + +UENUM() +enum class EHoudiniOutputType : uint8 +{ + Invalid, + + Mesh, + Instancer, + Landscape, + Curve, + Skeletal +}; + +UENUM() +enum class EHoudiniCurveOutputType : uint8 +{ + UnrealSpline, + HoudiniSpline, +}; + +UENUM() +enum class EHoudiniLandscapeOutputBakeType : uint8 +{ + Detachment, + BakeToImage, + BakeToWorld, + InValid, +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniCurveOutputProperties +{ + GENERATED_USTRUCT_BODY() + + // Curve output properties + UPROPERTY() + EHoudiniCurveOutputType CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; + + UPROPERTY() + int32 NumPoints = -1; + + UPROPERTY() + bool bClosed = false; + + UPROPERTY() + EHoudiniCurveType CurveType = EHoudiniCurveType::Invalid; + + UPROPERTY() + EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::Invalid; +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniLandscapePtr : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + FORCEINLINE + void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; + + FORCEINLINE + TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; + + // Calling Get() during GC will raise an exception because Get calls StaticFindObject. + FORCEINLINE + ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; + + FORCEINLINE + FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; + + FORCEINLINE + void SetLandscapeOutputBakeType(const EHoudiniLandscapeOutputBakeType & InBakeType) { BakeType = InBakeType; }; + + FORCEINLINE + EHoudiniLandscapeOutputBakeType GetLandscapeOutputBakeType() const { return BakeType; }; + + UPROPERTY() + TSoftObjectPtr LandscapeSoftPtr; + + UPROPERTY() + EHoudiniLandscapeOutputBakeType BakeType; +}; + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniOutputObjectIdentifier +{ + GENERATED_USTRUCT_BODY() + +public: + // Constructors + FHoudiniOutputObjectIdentifier(); + FHoudiniOutputObjectIdentifier(const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier); + + // Return hash value for this object, used when using this object as a key inside hashing containers. + uint32 GetTypeHash() const; + + // Comparison operator, used by hashing containers. + bool operator==(const FHoudiniOutputObjectIdentifier& HoudiniGeoPartObject) const; + + bool Matches(const FHoudiniGeoPartObject& HoudiniGeoPartObject) const; + +public: + + // NodeId of corresponding Houdini Object. + UPROPERTY() + int32 ObjectId = -1; + + // NodeId of corresponding Houdini Geo. + UPROPERTY() + int32 GeoId = -1; + + // PartId + UPROPERTY() + int32 PartId = -1; + + // String identifier for the split that created this + UPROPERTY() + FString SplitIdentifier = FString(); + + // Name of the part used to generate the output + UPROPERTY() + FString PartName = FString(); + + // First valid primitive index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PrimitiveIndex = -1; + + // First valid point index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PointIndex = -1; + + bool bLoaded = false; +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier); + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput +{ + GENERATED_USTRUCT_BODY() + +public: + + void MarkChanged(const bool& InChanged) { bChanged = InChanged; }; + + void SetVariationObjectAt(const int32& AtIndex, UObject* InObject); + + bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + + float GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + +#if WITH_EDITOR + void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; + bool IsUnformScaleLocked() const { return bUniformScaleLocked; }; +#endif + +public: + + // Original object used by the instancer. + UPROPERTY() + TSoftObjectPtr OriginalObject = nullptr; + + UPROPERTY() + int32 OriginalObjectIndex = -1; + + // Original HoudiniGeoPartObject used by the instancer + //UPROPERTY() + //FHoudiniGeoPartObject OriginalHGPO; + + // Original Instance transforms + UPROPERTY() + TArray OriginalTransforms; + + // Variation objects currently used for instancing + UPROPERTY() + TArray> VariationObjects; + + // Transform offsets, one for each variation. + UPROPERTY() + TArray VariationTransformOffsets; + + // Index of the variation used for each transform + UPROPERTY() + TArray TransformVariationIndices; + + // Indicates this instanced output's component should be recreated + UPROPERTY() + bool bChanged = false; + + // Indicates this instanced output is stale and should be removed + UPROPERTY() + bool bStale = false; + + // Indicates if change the scale of Transfrom Offset of this object uniformly +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bUniformScaleLocked = false; +#endif + // TODO + // Color overrides?? +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject +{ + GENERATED_USTRUCT_BODY() + + public: + FHoudiniBakedOutputObject(); + + FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject=nullptr, UObject* InBakedComponent=nullptr); + + // Returns Actor if valid, otherwise nullptr + AActor* GetActorIfValid(bool bInTryLoad=true) const; + + // Returns BakedObject if valid, otherwise nullptr + UObject* GetBakedObjectIfValid(bool bInTryLoad=true) const; + + // Returns BakedComponent if valid, otherwise nullptr + UObject* GetBakedComponentIfValid(bool bInTryLoad=true) const; + + // Returns Blueprint if valid, otherwise nullptr + UBlueprint* GetBlueprintIfValid(bool bInTryLoad=true) const; + + // The actor that the baked output was associated with + UPROPERTY() + FString Actor; + + // The blueprint that baked output was associated with, if any + UPROPERTY() + FString Blueprint; + + // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. + UPROPERTY() + FName ActorBakeName = NAME_None; + + // The baked output asset + UPROPERTY() + FString BakedObject; + + // The baked output component + UPROPERTY() + FString BakedComponent; + + // In the case of instance actor component baking, this is the array of instanced actors + UPROPERTY() + TArray InstancedActors; + + // In the case of mesh split instancer baking: this is the array of instance components + UPROPERTY() + TArray InstancedComponents; +}; + +// Container to hold the map of baked objects. There should be one of +// these for each UHoudiniOutput. We manage this separately from UHoudiniOutput so +// that the "previous/last" bake objects can survive output reconstruction or PDG +// dirty/dirty all operations. +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutput +{ + GENERATED_USTRUCT_BODY() + + public: + UPROPERTY() + TMap BakedOutputObjects; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject +{ + GENERATED_USTRUCT_BODY() + + public: + + // The main output object + UPROPERTY() + UObject* OutputObject = nullptr; + + // The main output component + UPROPERTY() + UObject* OutputComponent = nullptr; + + // Proxy object + UPROPERTY() + UObject* ProxyObject = nullptr; + + // Proxy Component + UPROPERTY() + UObject* ProxyComponent = nullptr; + + // Mesh output properties + // If this is true the proxy mesh is "current", + // in other words, it is newer than the UStaticMesh + UPROPERTY() + bool bProxyIsCurrent = false; + + // Bake Name override for this output object + UPROPERTY() + FString BakeName; + + UPROPERTY() + FHoudiniCurveOutputProperties CurveOutputProperty; + + + // NOTE: The idea behind CachedAttributes and CachedTokens is to + // collect attributes (such as unreal_level_path and unreal_output_name) + // and context-specific tokens (hda name, hda actor name, geo and part ids, tile_id, etc) + // and cache them directly on the output object. When the object gets baked, + // certain tokens can be updated specifically for the bake pass afterwhich the + // the string / path attributes can be resolved with the updated tokens. + // + // A more concrete example: + // unreal_output_name = "{hda_actor_name}_PurplePlants_{geo_id}_{part_id}" + // unreal_level_path = "{out}/{hda_name}/{guid}/PurplePlants/{geo_id}/{part_id}" + // + // All of the aforementions tokens and attributes would be cached on the output object + // when it is being cooked so that the same values are available at bake time. During + // a bake some tokens may be updated, such as `{out}` to change where assets get serialized. + + UPROPERTY() + TMap CachedAttributes; + + // Cache any tokens here that is needed for string resolving + // at bake time. + UPROPERTY() + TMap CachedTokens; +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject +{ + GENERATED_UCLASS_BODY() + + // Declare translators as friend so they can easily directly modify + // and access our HGPO and Output objects + friend struct FHoudiniMeshTranslator; + friend struct FHoudiniInstanceTranslator; + + virtual ~UHoudiniOutput(); + +public: + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + const EHoudiniOutputType& GetType() const { return Type; }; + + const TArray& GetHoudiniGeoPartObjects() const { return HoudiniGeoPartObjects; }; + + // Returns true if we have a HGPO that matches + const bool HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const; + + // Returns true if the HGPO is fromn the same HF as us + const bool HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const; + + // Returns the output objects and their corresponding identifiers + TMap& GetOutputObjects() { return OutputObjects; }; + + // Returns the output objects and their corresponding identifiers + const TMap& GetOutputObjects() const { return OutputObjects; }; + + // Returns this output's assignement material map + TMap& GetAssignementMaterials() { return AssignementMaterials; }; + + // Returns this output's replacement material map + TMap& GetReplacementMaterials() { return ReplacementMaterials; }; + + // Returns the instanced outputs maps + TMap& GetInstancedOutputs() { return InstancedOutputs; }; + + const bool HasGeoChanged() const; + const bool HasTransformChanged() const; + const bool HasMaterialsChanged() const; + + // Returns true if there are any proxy objects in output (current or not) + const bool HasAnyProxy() const; + // Returns true if the specified identifier has a proxy object (current or not) + const bool HasProxy(const FHoudiniOutputObjectIdentifier &InIdentifier) const; + // Returns true if there are any current (most up to date and visible) proxy in the output + const bool HasAnyCurrentProxy() const; + // Returns true if the specified identifier's proxy is "current" (in other words, newer than + // the non-proxy and the proxy should thus be shown instead. + const bool IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const; + + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + void UpdateOutputType(); + + // Adds a new HoudiniGeoPartObject to our array + void AddNewHGPO(const FHoudiniGeoPartObject& InHGPO); + + // Mark all the current HGPO as stale (from a previous cook) + // So we can delte them all by calling DeleteAllStaleHGPOs after. + void MarkAllHGPOsAsStale(const bool& InStale); + + // Delete all the HGPO that were marked as stale + void DeleteAllStaleHGPOs(); + + void SetOutputObjects(const TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; + + void SetInstancedOutputs(const TMap& InInstancedOuput) { InstancedOutputs = InInstancedOuput; }; + + // Marks all HGPO and OutputIdentifier as loaded + void MarkAsLoaded(const bool& InLoaded); + + FORCEINLINE + const bool IsEditableNode() { return bIsEditableNode; }; + + FORCEINLINE + void SetIsEditableNode(bool IsEditable) { bIsEditableNode = IsEditable; } + + FORCEINLINE + const bool HasEditableNodeBuilt() { return bHasEditableNodeBuilt; }; + + FORCEINLINE + void SetHasEditableNodeBuilt(bool HasBuilt) { bHasEditableNodeBuilt = HasBuilt; } + + FORCEINLINE + void SetIsUpdating(bool bInIsUpdating) { bIsUpdating = bInIsUpdating; }; + + FORCEINLINE + bool IsUpdating() const { return bIsUpdating; }; + + FORCEINLINE + void SetLandscapeWorldComposition(const bool bInLandscapeWorldComposition) { bLandscapeWorldComposition = bInLandscapeWorldComposition; }; + + FORCEINLINE + bool IsLandscapeWorldComposition () const { return bLandscapeWorldComposition; }; + + FORCEINLINE + TArray & GetHoudiniCreatedSocketActors() { return HoudiniCreatedSocketActors; }; + + FORCEINLINE + TArray & GetHoudiniAttachedSocketActors() { return HoudiniAttachedSocketActors; } + + // Duplicate this object and copy its state to the resulting object. + // This is typically used to transfer state between between template and instance components. + UHoudiniOutput* DuplicateAndCopyProperties(UObject* DestOuter, FName NewName); + + // Copy properties but preserve output objects + virtual void CopyPropertiesFrom(UHoudiniOutput* InOutput, bool bCopyAllProperties); + + // Set whether this object can delete Houdini nodes. + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } + + //------------------------------------------------------------------------------------------------ + // Helpers + //------------------------------------------------------------------------------------------------ + static FString OutputTypeToString(const EHoudiniOutputType& InOutputType); + + FBox GetBounds() const; + + void Clear(); + +protected: + + virtual void BeginDestroy() override; + +protected: + + // Indicates the type of output we're dealing with + UPROPERTY() + EHoudiniOutputType Type; + + // The output's corresponding HGPO + UPROPERTY() + TArray HoudiniGeoPartObjects; + + // + UPROPERTY(DuplicateTransient) + TMap OutputObjects; + + // Instanced outputs + // Stores the instance variations objects (replacement), transform offsets + UPROPERTY() + TMap InstancedOutputs; + + // The material assignments for this output + UPROPERTY() + TMap AssignementMaterials; + + UPROPERTY() + TMap ReplacementMaterials; + + // Indicates the number of stale HGPO + int32 StaleCount; + + UPROPERTY() + bool bLandscapeWorldComposition; + + // stores the created actors for sockets with actor references. + // + UPROPERTY() + TArray HoudiniCreatedSocketActors; + + UPROPERTY() + TArray HoudiniAttachedSocketActors; + +private: + // Use HoudiniOutput to represent an editable curve. + // This flag tells whether this output is an editable curve. + UPROPERTY() + bool bIsEditableNode; + + // An editable node is only built once. This flag indicates whether this node has been built. + UPROPERTY(DuplicateTransient) + bool bHasEditableNodeBuilt; + + // The IsUpdating flag is set to true when this out exists and is being updated. + UPROPERTY() + bool bIsUpdating; + + UPROPERTY() + bool bCanDeleteHoudiniNodes; +}; + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h index 5b6ac4591..f59a4c09d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h @@ -1,30 +1,30 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp index fb868672b..fe8a371c6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp @@ -1,1481 +1,1481 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGAssetLink.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniOutput.h" - -#include "Engine/StaticMesh.h" -#include "GameFramework/Actor.h" -#include "Landscape.h" - -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#if WITH_EDITOR - #include "FileHelpers.h" - #include "EditorModeManager.h" - #include "EditorModes.h" -#endif - -// -UHoudiniPDGAssetLink::UHoudiniPDGAssetLink(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , AssetName() - , AssetNodePath() - , AssetID(-1) - , SelectedTOPNetworkIndex(-1) - , LinkState(EPDGLinkState::Inactive) - , bAutoCook(false) - , bUseTOPNodeFilter(true) - , bUseTOPOutputFilter(true) - , NumWorkitems(0) - , WorkItemTally() - , OutputCachePath() - , bNeedsUIRefresh(false) - , OutputParentActor(nullptr) -{ - TOPNodeFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER; - TOPOutputFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER; - -#if WITH_EDITORONLY_DATA - bBakeMenuExpanded = true; - HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; - PDGBakeSelectionOption = EPDGBakeSelectionOption::All; - PDGBakePackageReplaceMode = EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - bRecenterBakedActors = false; - bBakeAfterWorkResultObjectLoaded = false; -#endif - - // Folder used for baking PDG outputs - BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - // TODO: - // Update init, move default filter to PCH -} - -FTOPWorkResultObject::FTOPWorkResultObject() -{ - // ResultObjects = nullptr; - Name = FString(); - FilePath = FString(); - State = EPDGWorkResultState::None; -} - -FTOPWorkResultObject::~FTOPWorkResultObject() -{ - // DestroyResultOutputs(); -} - -FTOPWorkResult::FTOPWorkResult() -{ - WorkItemIndex = -1; - WorkItemID = -1; - - ResultObjects.SetNum(0); -} - -bool -FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const -{ - if (WorkItemIndex != OtherWorkResult.WorkItemIndex) - return false; - if (WorkItemID != OtherWorkResult.WorkItemID) - return false; - /* - if (ResultObjects != OtherWorkResult.ResultObjects) - return false; - */ - - return true; -} - - -FWorkItemTally::FWorkItemTally() -{ - TotalWorkItems = 0; - WaitingWorkItems = 0; - ScheduledWorkItems = 0; - CookingWorkItems = 0; - CookedWorkItems = 0; - ErroredWorkItems = 0; -} - -void -FWorkItemTally::ZeroAll() -{ - TotalWorkItems = 0; - WaitingWorkItems = 0; - ScheduledWorkItems = 0; - CookingWorkItems = 0; - CookedWorkItems = 0; - ErroredWorkItems = 0; -} - -bool -FWorkItemTally::AreAllWorkItemsComplete() const -{ - return ( - WaitingWorkItems == 0 && CookingWorkItems == 0 && ScheduledWorkItems == 0 - && (TotalWorkItems == (CookedWorkItems + ErroredWorkItems)) ); -} - -bool -FWorkItemTally::AnyWorkItemsFailed() const -{ - return ErroredWorkItems > 0; -} - -bool -FWorkItemTally::AnyWorkItemsPending() const -{ - return (TotalWorkItems > 0 && (WaitingWorkItems > 0 || CookingWorkItems > 0 || ScheduledWorkItems > 0)); -} - -FString -FWorkItemTally::ProgressRatio() const -{ - float Ratio = TotalWorkItems > 0 ? (CookedWorkItems / TotalWorkItems) * 100.f : 0; - - return FString::Printf(TEXT("%.1f%%"), Ratio); -} - - -UTOPNode::UTOPNode() -{ - NodeId = -1; - NodeName = FString(); - NodePath = FString(); - ParentName = FString(); - - WorkResultParent = nullptr; - WorkResult.SetNum(0); - - bHidden = false; - bAutoLoad = false; - - NodeState = EPDGNodeState::None; - - bCachedHaveNotLoadedWorkResults = false; - bCachedHaveLoadedWorkResults = false; - bHasChildNodes = false; - - bShow = false; -} - -bool -UTOPNode::operator==(const UTOPNode& Other) const -{ - if (!NodeName.Equals(Other.NodeName)) - return false; - - if (!ParentName.Equals(Other.ParentName)) - return false; - - //if (NodeId != Other.NodeId) - // return false; - - return true; -} - -void -UTOPNode::Reset() -{ - NodeState = EPDGNodeState::None; - WorkItemTally.ZeroAll(); -} - -void -UTOPNode::SetVisibleInLevel(bool bInVisible) -{ - if (bShow == bInVisible) - return; - - bShow = bInVisible; - UpdateOutputVisibilityInLevel(); -} - -void -UTOPNode::UpdateOutputVisibilityInLevel() -{ - AActor* Actor = OutputActorOwner.GetOutputActor(); - if (IsValid(Actor)) - { - Actor->SetHidden(!bShow); -#if WITH_EDITOR - Actor->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - AActor* WROActor = WRO.GetOutputActorOwner().GetOutputActor(); - if (IsValid(WROActor)) - { - WROActor->SetHidden(!bShow); -#if WITH_EDITOR - WROActor->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - - // We need to manually handle child landscape's visiblity - for (UHoudiniOutput* ResultOutput : WRO.GetResultOutputs()) - { - if (!ResultOutput || ResultOutput->IsPendingKill()) - continue; - - for (auto Pair : ResultOutput->GetOutputObjects()) - { - FHoudiniOutputObject OutputObject = Pair.Value; - ALandscapeProxy* LandscapeProxy = Cast(OutputObject.OutputObject); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) - continue; - - ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); - if (!Landscape || Landscape->IsPendingKill()) - continue; - - Landscape->SetHidden(!bShow); -#if WITH_EDITOR - Landscape->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - } - } - } -} - -void -UTOPNode::SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad) -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::NotLoaded || - (WRO.State == EPDGWorkResultState::Deleted && bInAlsoSetDeletedToLoad)) - WRO.State = EPDGWorkResultState::ToLoad; - } - } -} - -void -UTOPNode::SetLoadedWorkResultsToDelete() -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::Loaded) - WRO.State = EPDGWorkResultState::ToDelete; - } - } -} - - -void -UTOPNode::DeleteWorkResultOutputObjects() -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::Loaded) - { - // Delete and clean up that WRObj - WRO.DestroyResultOutputs(); - WRO.GetOutputActorOwner().DestroyOutputActor(); - WRO.State = EPDGWorkResultState::Deleted; - } - } - } - bCachedHaveLoadedWorkResults = false; -} - -FString -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex) -{ - return FString::Printf(TEXT("%d_%d"), InWorkResultIndex, InWorkResultObjectIndex); -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const -{ - // Check that indices are valid - if (!WorkResult.IsValidIndex(InWorkResultIndex)) - return false; - if (!WorkResult[InWorkResultIndex].ResultObjects.IsValidIndex(InWorkResultObjectIndex)) - return false; - - OutKey = GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex); - - return true; -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) -{ - FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) - return false; - OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); - if (!OutBakedOutput) - return false; - - return true; -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const -{ - FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) - return false; - OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); - if (!OutBakedOutput) - return false; - - return true; -} - -#if WITH_EDITOR -void -UTOPNode::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) -{ - Super::PostEditChangeChainProperty(InPropertyChangedEvent); - - const FName PropertyName = InPropertyChangedEvent.GetPropertyName(); - if (PropertyName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) - { - UpdateOutputVisibilityInLevel(); - } -} -#endif - -#if WITH_EDITOR -void -UTOPNode::PostTransacted(const FTransactionObjectEvent& TransactionEvent) -{ - Super::PostTransacted(TransactionEvent); - - if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) - return; - - bool bUpdateVisibility = false; - for (const FName& PropName : TransactionEvent.GetChangedProperties()) - { - if (PropName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) - { - bUpdateVisibility = true; - } - } - - if (bUpdateVisibility) - UpdateOutputVisibilityInLevel(); -} -#endif - -UTOPNetwork::UTOPNetwork() -{ - NodeId = -1; - NodeName = FString(); - - AllTOPNodes.SetNum(0); - SelectedTOPIndex = -1; - - ParentName = FString(); - - bShowResults = false; - bAutoLoadResults = false; -} - -bool -UTOPNetwork::operator==(const UTOPNetwork& Other) const -{ - if (!NodeName.Equals(Other.NodeName)) - return false; - - if (!ParentName.Equals(Other.ParentName)) - return false; - - //if (NodeId != Other.NodeId) - // return false; - - return true; -} - -void -UTOPNetwork::SetLoadedWorkResultsToDelete() -{ - for (UTOPNode* Node : AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - Node->SetLoadedWorkResultsToDelete(); - } -} - -void -UTOPNetwork::DeleteWorkResultOutputObjects() -{ - for (UTOPNode* Node : AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - Node->DeleteWorkResultOutputObjects(); - } -} - -bool -UTOPNetwork::AnyWorkItemsPending() const -{ - for (const UTOPNode* const TOPNode : AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->AnyWorkItemsPending()) - return true; - } - - return false; -} - - -void -UHoudiniPDGAssetLink::SelectTOPNetwork(const int32& AtIndex) -{ - if (!AllTOPNetworks.IsValidIndex(AtIndex)) - return; - - SelectedTOPNetworkIndex = AtIndex; -} - - -void -UHoudiniPDGAssetLink::SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex) -{ - if (!IsValid(InTOPNetwork)) - return; - - if (!InTOPNetwork->AllTOPNodes.IsValidIndex(AtIndex)) - return; - - InTOPNetwork->SelectedTOPIndex = AtIndex; -} - - -UTOPNetwork* -UHoudiniPDGAssetLink::GetSelectedTOPNetwork() -{ - return GetTOPNetwork(SelectedTOPNetworkIndex); -} - - -UTOPNode* -UHoudiniPDGAssetLink::GetSelectedTOPNode() -{ - UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); - if (!IsValid(SelectedTOPNetwork)) - return nullptr; - - if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) - return nullptr; - - UTOPNode* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; - if (!IsValid(SelectedTOPNode)) - return nullptr; - - return SelectedTOPNode; -} - -FString -UHoudiniPDGAssetLink::GetSelectedTOPNodeName() -{ - FString NodeName = FString(); - - const UTOPNode* const SelectedTOPNode = GetSelectedTOPNode(); - if (IsValid(SelectedTOPNode)) - NodeName = SelectedTOPNode->NodeName; - - return NodeName; -} - -FString -UHoudiniPDGAssetLink::GetSelectedTOPNetworkName() -{ - FString NetworkName = FString(); - - const UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNetwork)) - NetworkName = SelectedTOPNetwork->NodeName; - - return NetworkName; -} - -UTOPNetwork* -UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) -{ - if(AllTOPNetworks.IsValidIndex(AtIndex)) - { - return AllTOPNetworks[AtIndex]; - } - - return nullptr; -} - -UTOPNetwork* -UHoudiniPDGAssetLink::GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex) -{ - OutIndex = INDEX_NONE; - int32 Index = -1; - for (UTOPNetwork* CurrentTOPNet : InTOPNetworks) - { - Index += 1; - - if (!IsValid(CurrentTOPNet)) - continue; - - if (CurrentTOPNet->NodePath.Equals(InNodePath)) - { - OutIndex = Index; - return CurrentTOPNet; - } - } - - return nullptr; -} - -UTOPNode* -UHoudiniPDGAssetLink::GetParentTOPNode(const UTOPNode* InNode) -{ - if (!IsValid(InNode)) - return nullptr; - - FString NodePath = InNode->NodePath; - FString ParentPath; - - if (NodePath.EndsWith("/")) - NodePath.LeftChopInline(1); - - if (NodePath.Split("/", &ParentPath, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromEnd) && !ParentPath.IsEmpty()) - { - for (UTOPNetwork* TOPNet : AllTOPNetworks) - { - if (!IsValid(TOPNet)) - continue; - - for (UTOPNode* TOPNode : TOPNet->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->NodePath == ParentPath && InNode->NodeId != TOPNode->NodeId) - { - return TOPNode; - } - } - } - } - - return nullptr; -} - -UTOPNode* -UHoudiniPDGAssetLink::GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex) -{ - OutIndex = INDEX_NONE; - int32 Index = -1; - for (UTOPNode* CurrentTOPNode : InTOPNodes) - { - Index += 1; - - if (!IsValid(CurrentTOPNode)) - continue; - - if (CurrentTOPNode->NodePath.Equals(InNodePath)) - { - OutIndex = Index; - return CurrentTOPNode; - } - } - - return nullptr; -} - -void -UHoudiniPDGAssetLink::ClearAllTOPData() -{ - // Clears all TOP data - for(UTOPNetwork* CurrentNetwork : AllTOPNetworks) - { - if (!IsValid(CurrentNetwork)) - continue; - - for(UTOPNode* CurrentTOPNode : CurrentNetwork->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - ClearTOPNodeWorkItemResults(CurrentTOPNode); - } - } - - AllTOPNetworks.Empty(); -} - -void -UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork) -{ - if (!IsValid(TOPNetwork)) - return; - - for(UTOPNode* CurrentTOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - ClearTOPNodeWorkItemResults(CurrentTOPNode); - } -} - -void -UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) -{ - if (!IsValid(TOPNode)) - return; - - for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) - { - DestroyWorkItemResultData(CurrentWorkResult, TOPNode); - } - TOPNode->WorkResult.Empty(); - - FOutputActorOwner& OutputActorOwner = TOPNode->GetOutputActorOwner(); - AActor* OutputActor = OutputActorOwner.GetOutputActor(); - if (IsValid(OutputActor)) - { - // Destroy any attached actors (which we'll assume that any attachments left - // are untracked actors associated with the TOPNode) - TArray AttachedActors; - OutputActor->GetAttachedActors(AttachedActors); - for (AActor* Actor : AttachedActors) - { - if (!IsValid(Actor)) - continue; - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - } - } - - if (TOPNode->WorkResultParent && !TOPNode->WorkResultParent->IsPendingKill()) - { - - // TODO: Destroy the Parent Object - // DestroyImmediate(topNode._workResultParentGO); - } - - OutputActorOwner.DestroyOutputActor(); -} - - -void -UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); - if (WorkResult) - { - DestroyWorkItemResultData(*WorkResult, InTOPNode); - // TODO: Should we destroy the FTOPWorkResult struct entirely here? - //TOPNode.WorkResult.RemoveByPredicate - } -} - -void -UHoudiniPDGAssetLink::DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - // TODO: Update ClearWorkItemResultByID or GetWorkResultByID to return the index of the work item - // so that we don't have to find its index again to remove it from the array - ClearWorkItemResultByID(InWorkItemID, InTOPNode); - // Find the index of the FTOPWorkResult for InWorkItemID in InTOPNode.WorkResult and remove it - const int32 Index = InTOPNode->WorkResult.IndexOfByPredicate( - [InWorkItemID](const FTOPWorkResult& InWorkItem) { return InWorkItem.WorkItemID == InWorkItemID; }); - if (Index != INDEX_NONE && Index >= 0) - InTOPNode->WorkResult.RemoveAt(Index); -} - -FTOPWorkResult* -UHoudiniPDGAssetLink::GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return nullptr; - - for(FTOPWorkResult& CurResult : InTOPNode->WorkResult) - { - if (CurResult.WorkItemID == InWorkItemID) - { - return &CurResult; - } - } - - return nullptr; -} - -FDirectoryPath -UHoudiniPDGAssetLink::GetTemporaryCookFolder() const -{ - UObject* Owner = GetOuter(); - UHoudiniAssetComponent* HAC = Cast(Owner); - if (HAC) - return HAC->TemporaryCookFolder; - - FDirectoryPath TempPath; - TempPath.Path = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - return TempPath; -} - -void -UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) -{ - ResultObject.DestroyResultOutputs(); - ResultObject.GetOutputActorOwner().DestroyOutputActor(); -} - -void -UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode) -{ - if (Result.ResultObjects.Num() <= 0) - return; - - for (FTOPWorkResultObject& ResultObject : Result.ResultObjects) - { - DestoryWorkResultObjectData(ResultObject); - } - - Result.ResultObjects.Empty(); -} - - -UTOPNode* -UHoudiniPDGAssetLink::GetTOPNode(const int32& InNodeID) -{ - for (UTOPNetwork* CurrentTOPNet : AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - if (CurrentTOPNode->NodeId == InNodeID) - return CurrentTOPNode; - } - } - - return nullptr; -} - -void -UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork) -{ - if (!IsValid(InNode) || !IsValid(InNetwork)) - return; - - if (!InNode->bHasChildNodes) - return; - - FString PrefixPath = InNode->NodePath; - if (!PrefixPath.EndsWith("/")) - PrefixPath += "/"; - InNode->WorkItemTally.ZeroAll(); - InNode->NodeState = EPDGNodeState::None; - - TMap NodeStateOrder; - NodeStateOrder.Add(EPDGNodeState::None, 0); - NodeStateOrder.Add(EPDGNodeState::Cook_Complete, 1); - NodeStateOrder.Add(EPDGNodeState::Dirtied, 2); - NodeStateOrder.Add(EPDGNodeState::Cook_Failed, 3); - NodeStateOrder.Add(EPDGNodeState::Dirtying, 4); - NodeStateOrder.Add(EPDGNodeState::Cooking, 5); - - int8 CurrentState = 0; - - for (const UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - if (Node->NodePath.StartsWith(PrefixPath) && !Node->bHasChildNodes) - { - InNode->WorkItemTally.TotalWorkItems += Node->WorkItemTally.TotalWorkItems; - InNode->WorkItemTally.WaitingWorkItems += Node->WorkItemTally.WaitingWorkItems; - InNode->WorkItemTally.ScheduledWorkItems += Node->WorkItemTally.ScheduledWorkItems; - InNode->WorkItemTally.CookingWorkItems += Node->WorkItemTally.CookingWorkItems; - InNode->WorkItemTally.CookedWorkItems += Node->WorkItemTally.CookedWorkItems; - InNode->WorkItemTally.ErroredWorkItems += Node->WorkItemTally.ErroredWorkItems; - const int8 VisistedNodeState = NodeStateOrder.FindChecked(Node->NodeState); - if (VisistedNodeState > CurrentState) - CurrentState = VisistedNodeState; - } - } - - EPDGNodeState const* const NewState = NodeStateOrder.FindKey(CurrentState); - if (NewState) - InNode->NodeState = *NewState; -} - -void -UHoudiniPDGAssetLink::UpdateWorkItemTally() -{ - WorkItemTally.ZeroAll(); - for(UTOPNetwork* CurrentTOPNet : AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - for(UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - // Only add up the tallys from nodes without children (since parent's aggregate the child work items counts) - if (CurrentTOPNode->bHasChildNodes) - { - UpdateTOPNodeWithChildrenWorkItemTallyAndState(CurrentTOPNode, CurrentTOPNet); - } - else - { - WorkItemTally.TotalWorkItems += CurrentTOPNode->WorkItemTally.TotalWorkItems; - WorkItemTally.WaitingWorkItems += CurrentTOPNode->WorkItemTally.WaitingWorkItems; - WorkItemTally.ScheduledWorkItems += CurrentTOPNode->WorkItemTally.ScheduledWorkItems; - WorkItemTally.CookingWorkItems += CurrentTOPNode->WorkItemTally.CookingWorkItems; - WorkItemTally.CookedWorkItems += CurrentTOPNode->WorkItemTally.CookedWorkItems; - WorkItemTally.ErroredWorkItems += CurrentTOPNode->WorkItemTally.ErroredWorkItems; - } - } - } -} - - -void -UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork) -{ - if (!IsValid(TOPNetwork)) - return; - - for (UTOPNode* CurTOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(CurTOPNode)) - continue; - - CurTOPNode->WorkItemTally.ZeroAll(); - } -} - - -FString -UHoudiniPDGAssetLink::GetAssetLinkStatus(const EPDGLinkState& InLinkState) -{ - FString Status; - switch (InLinkState) - { - case EPDGLinkState::Inactive: - Status = TEXT("Inactive"); - case EPDGLinkState::Linking: - Status = TEXT("Linking"); - case EPDGLinkState::Linked: - Status = TEXT("Linked"); - case EPDGLinkState::Error_Not_Linked: - Status = TEXT("Not Linked"); - default: - Status = TEXT(""); - } - - return Status; -} - -FString -UHoudiniPDGAssetLink::GetTOPNodeStatus(const UTOPNode* InTOPNode) -{ - static const FString InvalidOrUnknownStatus = TEXT(""); - - if (!IsValid(InTOPNode)) - return InvalidOrUnknownStatus; - - if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) - { - return TEXT("Cook Failed"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) - { - return TEXT("Cook Completed"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Cooking) - { - return TEXT("Cook In Progress"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) - { - return TEXT("Dirtied"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) - { - return TEXT("Dirtying"); - } - - return InvalidOrUnknownStatus; -} - -FLinearColor -UHoudiniPDGAssetLink::GetTOPNodeStatusColor(const UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return FLinearColor::White; - - if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) - { - return FLinearColor::Red; - } - else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) - { - return FLinearColor::Green; - } - else if (InTOPNode->NodeState == EPDGNodeState::Cooking) - { - return FLinearColor(0.0, 1.0f, 1.0f); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) - { - return FLinearColor(1.0f, 0.5f, 0.0f); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) - { - return FLinearColor::Yellow; - } - - return FLinearColor::White; -} - -AActor* -UHoudiniPDGAssetLink::GetOwnerActor() const -{ - UObject* Outer = GetOuter(); - UActorComponent* Component = Cast(Outer); - if (IsValid(Component)) - return Component->GetOwner(); - else - return Cast(Outer); -} - -bool -UHoudiniPDGAssetLink::HasTemporaryOutputs() const -{ - // Loop over all networks, all nodes, all work items and check for any valid output objects - for (const UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (const UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - for (const FTOPWorkResult& WorkResult : TOPNode->WorkResult) - { - for (const FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) - { - // If the WorkResultObject's actor is not valid, then it no longer has temporary objects in the - // scene - if (!IsValid(WorkResultObject.GetOutputActorOwner().GetOutputActor())) - continue; - - for (UHoudiniOutput* Output : WorkResultObject.GetResultOutputs()) - { - if (!IsValid(Output)) - continue; - - const EHoudiniOutputType OutputType = Output->GetType(); - for (const auto& OutputObjectPair : Output->GetOutputObjects()) - { - if ((OutputType == EHoudiniOutputType::Landscape && IsValid(OutputObjectPair.Value.OutputObject)) || - IsValid(OutputObjectPair.Value.OutputComponent)) - { - return true; - } - } - } - } - } - } - } - - return false; -} - -void -UHoudiniPDGAssetLink::UpdatePostDuplicate() -{ - // Loop over all networks, all nodes, all work items and clear output actors - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - for (FTOPWorkResult& WorkResult : TOPNode->WorkResult) - { - for (FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) - { - WorkResultObject.GetOutputActorOwner().SetOutputActor(nullptr); - WorkResultObject.State = EPDGWorkResultState::None; - WorkResultObject.SetResultOutputs(TArray()); - } - } - TOPNode->GetOutputActorOwner().SetOutputActor(nullptr); - TOPNode->bCachedHaveNotLoadedWorkResults = false; - TOPNode->bCachedHaveLoadedWorkResults = false; - } - } -} - -void -UHoudiniPDGAssetLink::UpdateTOPNodeAutoloadAndVisibility() -{ - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->bAutoLoad) - { - // // Set work results that are cooked but in NotLoaded state to ToLoad - // TOPNode.SetNotLoadedWorkResultsToLoad(); - } - - TOPNode->UpdateOutputVisibilityInLevel(); - } - } -} - -void -UHoudiniPDGAssetLink::FilterTOPNodesAndOutputs() -{ - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - // TOP Node visibility filter via TOPNodeFilter - if (bUseTOPNodeFilter) - { - TOPNode->bHidden = !TOPNodeFilter.IsEmpty() && !TOPNode->NodeName.StartsWith(TOPNodeFilter); - } - else - { - TOPNode->bHidden = false; - } - - // Auto load results filter via TOPNodeOutputFilter - if (bUseTOPOutputFilter) - { - const bool bNewAutoLoad = TOPOutputFilter.IsEmpty() || TOPNode->NodeName.StartsWith(TOPOutputFilter); - if (bNewAutoLoad != TOPNode->bAutoLoad) - { - if (bNewAutoLoad) - { - // Set work results that are cooked but in NotLoaded state to ToLoad - TOPNode->bAutoLoad = true; - // TOPNode->SetNotLoadedWorkResultsToLoad(); - TOPNode->SetVisibleInLevel(true); - } - else - { - TOPNode->bAutoLoad = false; - TOPNode->SetVisibleInLevel(false); - } - TOPNode->UpdateOutputVisibilityInLevel(); - } - } - } - } -} - -#if WITH_EDITORONLY_DATA -void -UHoudiniPDGAssetLink::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedChainEvent) -{ - Super::PostEditChangeChainProperty(InPropertyChangedChainEvent); - - const FName PropertyName = InPropertyChangedChainEvent.GetPropertyName(); - if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) - { - // Refilter TOP nodes - FilterTOPNodesAndOutputs(); - bNeedsUIRefresh = true; - } - else if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) - { - bNeedsUIRefresh = true; - } -} - -#endif - -#if WITH_EDITORONLY_DATA -void -UHoudiniPDGAssetLink::PostTransacted(const FTransactionObjectEvent& TransactionEvent) -{ - Super::PostTransacted(TransactionEvent); - - if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) - return; - - bool bDoFilterTOPNodesAndOutputs = false; - for (const FName& PropName : TransactionEvent.GetChangedProperties()) - { - if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) - { - bDoFilterTOPNodesAndOutputs = true; - bNeedsUIRefresh = true; - } - else if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) - { - bNeedsUIRefresh = true; - } - } - - if (bDoFilterTOPNodesAndOutputs) - FilterTOPNodesAndOutputs(); -} -#endif - -void -FTOPWorkResultObject::DestroyResultOutputs() -{ - // Delete output components and gather output objects for deletion - bool bDidDestroyObjects = false; - bool bDidModifyFoliage = false; - - AActor* const OutputActor = OutputActorOwner.GetOutputActor(); - - for (UHoudiniOutput* CurOutput : ResultOutputs) - { - for (auto& Pair : CurOutput->GetOutputObjects()) - { - FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - FHoudiniOutputObject& OutputObject = Pair.Value; - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) - { - // Instancer components require some special handling around foliage - // TODO: move/refactor so that we can use the InstanceTranslator's helper functions (RemoveAndDestroyComponent and CleanupFoliageInstances) - bool bDestroyComponent = true; - if (OutputObject.OutputComponent->IsA()) - { - UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OutputObject.OutputComponent); - if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) - { - // Make sure foliage our foliage instances have been removed - USceneComponent* ParentComponent = nullptr; - if (IsValid(OutputActor)) - ParentComponent = Cast(OutputActor->GetRootComponent()); - else - ParentComponent = Cast(HISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) - { - UStaticMesh* FoliageSM = HISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - return; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(HISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return; - - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - return; -#if WITH_EDITOR - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); - - // Remove the foliage type if it doesn't have any more instances - if(HISMC->GetInstanceCount() == 0) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - bDidModifyFoliage = true; -#endif - } - - // do not delete FISMC that still have instances left - // as we have cleaned up our instances before, these have been hand-placed - if (HISMC->GetInstanceCount() > 0) - bDestroyComponent = false; - } - - if (bDestroyComponent) - { - USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from its actor first - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - // Detach from its parent component if attached - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - bDidDestroyObjects = true; - - OutputObject.OutputComponent = nullptr; - } - } - } - } - if (OutputObject.OutputObject && !OutputObject.OutputObject->IsPendingKill()) - { - // For actors we detach them first and then destroy - AActor* Actor = Cast(OutputObject.OutputObject); - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - if (LandscapePtr) - { - Actor = LandscapePtr->GetRawPtr(); - } - - if (Actor) - { - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - - bDidDestroyObjects = true; - } - else - { - // ... if not an actor, mark as pending kill - // OutputObject.OutputObject->MarkPendingKill(); - if (IsValid(OutputObject.OutputObject)) - OutputObjectsToDelete.Add(OutputObject.OutputObject); - OutputObject.OutputObject = nullptr; - } - } - } - } - - ResultOutputs.Empty(); - - if (bDidDestroyObjects) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - // Delete the output objects we found - if (OutputObjectsToDelete.Num() > 0) - FHoudiniEngineRuntimeUtils::SafeDeleteObjects(OutputObjectsToDelete); - -#if WITH_EDITOR - if (bDidModifyFoliage) - { - // Repopulate the foliage types in the foliage mode UI if foliage mode is active - // There is a helper function in FHoudiniEngineUtils for this, but we cannot access it from this module. - // TODO: refactor? - FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); - if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) - { - EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); - EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); - } - } -#endif -} - -bool -FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) -{ - // InAssetLink and InWorld must not be null - if (!InAssetLink || InAssetLink->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InAssetLink is null!")); - return false; - } - if (!InWorld || InWorld->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InWorld is null!")); - return false; - } - - AActor* AssetLinkActor = InAssetLink->GetOwnerActor(); - - const bool bParentActorIsValid = IsValid(InParentActor); - ULevel* LevelToSpawnIn = nullptr; - if (bParentActorIsValid) - { - LevelToSpawnIn = InParentActor->GetLevel(); - } - else - { - // Get the level containing the asset link's actor - if (IsValid(AssetLinkActor)) - LevelToSpawnIn = AssetLinkActor->GetLevel(); - } - - // Fallback to InWorld's current level - UWorld* WorldToSpawnIn = nullptr; - if (!IsValid(LevelToSpawnIn)) - { - LevelToSpawnIn = InWorld->GetCurrentLevel(); - WorldToSpawnIn = InWorld; - } - else - { - WorldToSpawnIn = LevelToSpawnIn->GetWorld(); - } - - if (!IsValid(WorldToSpawnIn) || !IsValid(LevelToSpawnIn)) - { - HOUDINI_LOG_WARNING( - TEXT("Could not determine level and world to spawn PDG output actor in: asset link %s, name %s"), - *(InAssetLink->GetPathName()), - *(InName.ToString())); - return false; - } - - FActorSpawnParameters SpawnParams; - SpawnParams.Name = MakeUniqueObjectName(InWorld, AActor::StaticClass(), InName); - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; - SpawnParams.OverrideLevel = LevelToSpawnIn; - AActor *Actor = WorldToSpawnIn->SpawnActor(SpawnParams); - SetOutputActor(Actor); -#if WITH_EDITOR - Actor->SetActorLabel(InName.ToString()); -#endif - - // Set the actor transform: create a root component if it does not have one - USceneComponent* RootComponent = Actor->GetRootComponent(); - if (!RootComponent || RootComponent->IsPendingKill()) - { - RootComponent = NewObject(Actor, USceneComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - RootComponent->CreationMethod = EComponentCreationMethod::Instance; - Actor->SetRootComponent(RootComponent); - RootComponent->OnComponentCreated(); - RootComponent->RegisterComponent(); - } - - RootComponent->SetVisibility(true); - RootComponent->SetMobility(EComponentMobility::Static); - - const FVector ActorSpawnLocation = InParentActor ? InParentActor->GetActorLocation() : FVector::ZeroVector; - const FRotator ActorSpawnRotator = InParentActor ? InParentActor->GetActorRotation() : FRotator::ZeroRotator; - Actor->SetActorLocation(ActorSpawnLocation); - Actor->SetActorRotation(ActorSpawnRotator); - -#if WITH_EDITOR - if (IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) - { - Actor->SetFolderPath(InParentActor->GetFolderPath()); - Actor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); - } - else if (IsValid(AssetLinkActor) && AssetLinkActor->GetLevel() == LevelToSpawnIn) - { - Actor->SetFolderPath(*FString::Format( - TEXT("{0}/{1}_Output"), - { FStringFormatArg(AssetLinkActor->GetFolderPath().ToString()), FStringFormatArg(AssetLinkActor->GetActorLabel()) } - )); - } - else - { - Actor->SetFolderPath(*FString::Format(TEXT("{0}_Output"), { FStringFormatArg(InAssetLink->GetName()) })); - } -#else - if(IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) - { - OutputActor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); - } -#endif - - return true; -} - -bool -FOutputActorOwner::DestroyOutputActor() -{ - bool bDestroyed = false; - AActor *Actor = GetOutputActor(); - if (IsValid(Actor)) - { - // Detach from parent before destroying the actor - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - - bDestroyed = true; - } - - SetOutputActor(nullptr); - - return bDestroyed; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGAssetLink.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniOutput.h" + +#include "Engine/StaticMesh.h" +#include "GameFramework/Actor.h" +#include "Landscape.h" + +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#if WITH_EDITOR + #include "FileHelpers.h" + #include "EditorModeManager.h" + #include "EditorModes.h" +#endif + +// +UHoudiniPDGAssetLink::UHoudiniPDGAssetLink(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , AssetName() + , AssetNodePath() + , AssetID(-1) + , SelectedTOPNetworkIndex(-1) + , LinkState(EPDGLinkState::Inactive) + , bAutoCook(false) + , bUseTOPNodeFilter(true) + , bUseTOPOutputFilter(true) + , NumWorkitems(0) + , WorkItemTally() + , OutputCachePath() + , bNeedsUIRefresh(false) + , OutputParentActor(nullptr) +{ + TOPNodeFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER; + TOPOutputFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER; + +#if WITH_EDITORONLY_DATA + bBakeMenuExpanded = true; + HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; + PDGBakeSelectionOption = EPDGBakeSelectionOption::All; + PDGBakePackageReplaceMode = EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + bRecenterBakedActors = false; + bBakeAfterWorkResultObjectLoaded = false; +#endif + + // Folder used for baking PDG outputs + BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + // TODO: + // Update init, move default filter to PCH +} + +FTOPWorkResultObject::FTOPWorkResultObject() +{ + // ResultObjects = nullptr; + Name = FString(); + FilePath = FString(); + State = EPDGWorkResultState::None; +} + +FTOPWorkResultObject::~FTOPWorkResultObject() +{ + // DestroyResultOutputs(); +} + +FTOPWorkResult::FTOPWorkResult() +{ + WorkItemIndex = -1; + WorkItemID = -1; + + ResultObjects.SetNum(0); +} + +bool +FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const +{ + if (WorkItemIndex != OtherWorkResult.WorkItemIndex) + return false; + if (WorkItemID != OtherWorkResult.WorkItemID) + return false; + /* + if (ResultObjects != OtherWorkResult.ResultObjects) + return false; + */ + + return true; +} + + +FWorkItemTally::FWorkItemTally() +{ + TotalWorkItems = 0; + WaitingWorkItems = 0; + ScheduledWorkItems = 0; + CookingWorkItems = 0; + CookedWorkItems = 0; + ErroredWorkItems = 0; +} + +void +FWorkItemTally::ZeroAll() +{ + TotalWorkItems = 0; + WaitingWorkItems = 0; + ScheduledWorkItems = 0; + CookingWorkItems = 0; + CookedWorkItems = 0; + ErroredWorkItems = 0; +} + +bool +FWorkItemTally::AreAllWorkItemsComplete() const +{ + return ( + WaitingWorkItems == 0 && CookingWorkItems == 0 && ScheduledWorkItems == 0 + && (TotalWorkItems == (CookedWorkItems + ErroredWorkItems)) ); +} + +bool +FWorkItemTally::AnyWorkItemsFailed() const +{ + return ErroredWorkItems > 0; +} + +bool +FWorkItemTally::AnyWorkItemsPending() const +{ + return (TotalWorkItems > 0 && (WaitingWorkItems > 0 || CookingWorkItems > 0 || ScheduledWorkItems > 0)); +} + +FString +FWorkItemTally::ProgressRatio() const +{ + float Ratio = TotalWorkItems > 0 ? (CookedWorkItems / TotalWorkItems) * 100.f : 0; + + return FString::Printf(TEXT("%.1f%%"), Ratio); +} + + +UTOPNode::UTOPNode() +{ + NodeId = -1; + NodeName = FString(); + NodePath = FString(); + ParentName = FString(); + + WorkResultParent = nullptr; + WorkResult.SetNum(0); + + bHidden = false; + bAutoLoad = false; + + NodeState = EPDGNodeState::None; + + bCachedHaveNotLoadedWorkResults = false; + bCachedHaveLoadedWorkResults = false; + bHasChildNodes = false; + + bShow = false; +} + +bool +UTOPNode::operator==(const UTOPNode& Other) const +{ + if (!NodeName.Equals(Other.NodeName)) + return false; + + if (!ParentName.Equals(Other.ParentName)) + return false; + + //if (NodeId != Other.NodeId) + // return false; + + return true; +} + +void +UTOPNode::Reset() +{ + NodeState = EPDGNodeState::None; + WorkItemTally.ZeroAll(); +} + +void +UTOPNode::SetVisibleInLevel(bool bInVisible) +{ + if (bShow == bInVisible) + return; + + bShow = bInVisible; + UpdateOutputVisibilityInLevel(); +} + +void +UTOPNode::UpdateOutputVisibilityInLevel() +{ + AActor* Actor = OutputActorOwner.GetOutputActor(); + if (IsValid(Actor)) + { + Actor->SetHidden(!bShow); +#if WITH_EDITOR + Actor->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + AActor* WROActor = WRO.GetOutputActorOwner().GetOutputActor(); + if (IsValid(WROActor)) + { + WROActor->SetHidden(!bShow); +#if WITH_EDITOR + WROActor->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + + // We need to manually handle child landscape's visiblity + for (UHoudiniOutput* ResultOutput : WRO.GetResultOutputs()) + { + if (!ResultOutput || ResultOutput->IsPendingKill()) + continue; + + for (auto Pair : ResultOutput->GetOutputObjects()) + { + FHoudiniOutputObject OutputObject = Pair.Value; + ALandscapeProxy* LandscapeProxy = Cast(OutputObject.OutputObject); + if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + continue; + + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (!Landscape || Landscape->IsPendingKill()) + continue; + + Landscape->SetHidden(!bShow); +#if WITH_EDITOR + Landscape->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + } + } + } +} + +void +UTOPNode::SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad) +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::NotLoaded || + (WRO.State == EPDGWorkResultState::Deleted && bInAlsoSetDeletedToLoad)) + WRO.State = EPDGWorkResultState::ToLoad; + } + } +} + +void +UTOPNode::SetLoadedWorkResultsToDelete() +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::Loaded) + WRO.State = EPDGWorkResultState::ToDelete; + } + } +} + + +void +UTOPNode::DeleteWorkResultOutputObjects() +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::Loaded) + { + // Delete and clean up that WRObj + WRO.DestroyResultOutputs(); + WRO.GetOutputActorOwner().DestroyOutputActor(); + WRO.State = EPDGWorkResultState::Deleted; + } + } + } + bCachedHaveLoadedWorkResults = false; +} + +FString +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex) +{ + return FString::Printf(TEXT("%d_%d"), InWorkResultIndex, InWorkResultObjectIndex); +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const +{ + // Check that indices are valid + if (!WorkResult.IsValidIndex(InWorkResultIndex)) + return false; + if (!WorkResult[InWorkResultIndex].ResultObjects.IsValidIndex(InWorkResultObjectIndex)) + return false; + + OutKey = GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex); + + return true; +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) +{ + FString Key; + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) + return false; + OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); + if (!OutBakedOutput) + return false; + + return true; +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const +{ + FString Key; + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) + return false; + OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); + if (!OutBakedOutput) + return false; + + return true; +} + +#if WITH_EDITOR +void +UTOPNode::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) +{ + Super::PostEditChangeChainProperty(InPropertyChangedEvent); + + const FName PropertyName = InPropertyChangedEvent.GetPropertyName(); + if (PropertyName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) + { + UpdateOutputVisibilityInLevel(); + } +} +#endif + +#if WITH_EDITOR +void +UTOPNode::PostTransacted(const FTransactionObjectEvent& TransactionEvent) +{ + Super::PostTransacted(TransactionEvent); + + if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) + return; + + bool bUpdateVisibility = false; + for (const FName& PropName : TransactionEvent.GetChangedProperties()) + { + if (PropName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) + { + bUpdateVisibility = true; + } + } + + if (bUpdateVisibility) + UpdateOutputVisibilityInLevel(); +} +#endif + +UTOPNetwork::UTOPNetwork() +{ + NodeId = -1; + NodeName = FString(); + + AllTOPNodes.SetNum(0); + SelectedTOPIndex = -1; + + ParentName = FString(); + + bShowResults = false; + bAutoLoadResults = false; +} + +bool +UTOPNetwork::operator==(const UTOPNetwork& Other) const +{ + if (!NodeName.Equals(Other.NodeName)) + return false; + + if (!ParentName.Equals(Other.ParentName)) + return false; + + //if (NodeId != Other.NodeId) + // return false; + + return true; +} + +void +UTOPNetwork::SetLoadedWorkResultsToDelete() +{ + for (UTOPNode* Node : AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + Node->SetLoadedWorkResultsToDelete(); + } +} + +void +UTOPNetwork::DeleteWorkResultOutputObjects() +{ + for (UTOPNode* Node : AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + Node->DeleteWorkResultOutputObjects(); + } +} + +bool +UTOPNetwork::AnyWorkItemsPending() const +{ + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->AnyWorkItemsPending()) + return true; + } + + return false; +} + + +void +UHoudiniPDGAssetLink::SelectTOPNetwork(const int32& AtIndex) +{ + if (!AllTOPNetworks.IsValidIndex(AtIndex)) + return; + + SelectedTOPNetworkIndex = AtIndex; +} + + +void +UHoudiniPDGAssetLink::SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex) +{ + if (!IsValid(InTOPNetwork)) + return; + + if (!InTOPNetwork->AllTOPNodes.IsValidIndex(AtIndex)) + return; + + InTOPNetwork->SelectedTOPIndex = AtIndex; +} + + +UTOPNetwork* +UHoudiniPDGAssetLink::GetSelectedTOPNetwork() +{ + return GetTOPNetwork(SelectedTOPNetworkIndex); +} + + +UTOPNode* +UHoudiniPDGAssetLink::GetSelectedTOPNode() +{ + UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNetwork)) + return nullptr; + + if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) + return nullptr; + + UTOPNode* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; + if (!IsValid(SelectedTOPNode)) + return nullptr; + + return SelectedTOPNode; +} + +FString +UHoudiniPDGAssetLink::GetSelectedTOPNodeName() +{ + FString NodeName = FString(); + + const UTOPNode* const SelectedTOPNode = GetSelectedTOPNode(); + if (IsValid(SelectedTOPNode)) + NodeName = SelectedTOPNode->NodeName; + + return NodeName; +} + +FString +UHoudiniPDGAssetLink::GetSelectedTOPNetworkName() +{ + FString NetworkName = FString(); + + const UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNetwork)) + NetworkName = SelectedTOPNetwork->NodeName; + + return NetworkName; +} + +UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) +{ + if(AllTOPNetworks.IsValidIndex(AtIndex)) + { + return AllTOPNetworks[AtIndex]; + } + + return nullptr; +} + +UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex) +{ + OutIndex = INDEX_NONE; + int32 Index = -1; + for (UTOPNetwork* CurrentTOPNet : InTOPNetworks) + { + Index += 1; + + if (!IsValid(CurrentTOPNet)) + continue; + + if (CurrentTOPNet->NodePath.Equals(InNodePath)) + { + OutIndex = Index; + return CurrentTOPNet; + } + } + + return nullptr; +} + +UTOPNode* +UHoudiniPDGAssetLink::GetParentTOPNode(const UTOPNode* InNode) +{ + if (!IsValid(InNode)) + return nullptr; + + FString NodePath = InNode->NodePath; + FString ParentPath; + + if (NodePath.EndsWith("/")) + NodePath.LeftChopInline(1); + + if (NodePath.Split("/", &ParentPath, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromEnd) && !ParentPath.IsEmpty()) + { + for (UTOPNetwork* TOPNet : AllTOPNetworks) + { + if (!IsValid(TOPNet)) + continue; + + for (UTOPNode* TOPNode : TOPNet->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->NodePath == ParentPath && InNode->NodeId != TOPNode->NodeId) + { + return TOPNode; + } + } + } + } + + return nullptr; +} + +UTOPNode* +UHoudiniPDGAssetLink::GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex) +{ + OutIndex = INDEX_NONE; + int32 Index = -1; + for (UTOPNode* CurrentTOPNode : InTOPNodes) + { + Index += 1; + + if (!IsValid(CurrentTOPNode)) + continue; + + if (CurrentTOPNode->NodePath.Equals(InNodePath)) + { + OutIndex = Index; + return CurrentTOPNode; + } + } + + return nullptr; +} + +void +UHoudiniPDGAssetLink::ClearAllTOPData() +{ + // Clears all TOP data + for(UTOPNetwork* CurrentNetwork : AllTOPNetworks) + { + if (!IsValid(CurrentNetwork)) + continue; + + for(UTOPNode* CurrentTOPNode : CurrentNetwork->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + ClearTOPNodeWorkItemResults(CurrentTOPNode); + } + } + + AllTOPNetworks.Empty(); +} + +void +UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork) +{ + if (!IsValid(TOPNetwork)) + return; + + for(UTOPNode* CurrentTOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + ClearTOPNodeWorkItemResults(CurrentTOPNode); + } +} + +void +UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) +{ + if (!IsValid(TOPNode)) + return; + + for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) + { + DestroyWorkItemResultData(CurrentWorkResult, TOPNode); + } + TOPNode->WorkResult.Empty(); + + FOutputActorOwner& OutputActorOwner = TOPNode->GetOutputActorOwner(); + AActor* OutputActor = OutputActorOwner.GetOutputActor(); + if (IsValid(OutputActor)) + { + // Destroy any attached actors (which we'll assume that any attachments left + // are untracked actors associated with the TOPNode) + TArray AttachedActors; + OutputActor->GetAttachedActors(AttachedActors); + for (AActor* Actor : AttachedActors) + { + if (!IsValid(Actor)) + continue; + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + } + } + + if (TOPNode->WorkResultParent && !TOPNode->WorkResultParent->IsPendingKill()) + { + + // TODO: Destroy the Parent Object + // DestroyImmediate(topNode._workResultParentGO); + } + + OutputActorOwner.DestroyOutputActor(); +} + + +void +UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); + if (WorkResult) + { + DestroyWorkItemResultData(*WorkResult, InTOPNode); + // TODO: Should we destroy the FTOPWorkResult struct entirely here? + //TOPNode.WorkResult.RemoveByPredicate + } +} + +void +UHoudiniPDGAssetLink::DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + // TODO: Update ClearWorkItemResultByID or GetWorkResultByID to return the index of the work item + // so that we don't have to find its index again to remove it from the array + ClearWorkItemResultByID(InWorkItemID, InTOPNode); + // Find the index of the FTOPWorkResult for InWorkItemID in InTOPNode.WorkResult and remove it + const int32 Index = InTOPNode->WorkResult.IndexOfByPredicate( + [InWorkItemID](const FTOPWorkResult& InWorkItem) { return InWorkItem.WorkItemID == InWorkItemID; }); + if (Index != INDEX_NONE && Index >= 0) + InTOPNode->WorkResult.RemoveAt(Index); +} + +FTOPWorkResult* +UHoudiniPDGAssetLink::GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return nullptr; + + for(FTOPWorkResult& CurResult : InTOPNode->WorkResult) + { + if (CurResult.WorkItemID == InWorkItemID) + { + return &CurResult; + } + } + + return nullptr; +} + +FDirectoryPath +UHoudiniPDGAssetLink::GetTemporaryCookFolder() const +{ + UObject* Owner = GetOuter(); + UHoudiniAssetComponent* HAC = Cast(Owner); + if (HAC) + return HAC->TemporaryCookFolder; + + FDirectoryPath TempPath; + TempPath.Path = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + return TempPath; +} + +void +UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) +{ + ResultObject.DestroyResultOutputs(); + ResultObject.GetOutputActorOwner().DestroyOutputActor(); +} + +void +UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode) +{ + if (Result.ResultObjects.Num() <= 0) + return; + + for (FTOPWorkResultObject& ResultObject : Result.ResultObjects) + { + DestoryWorkResultObjectData(ResultObject); + } + + Result.ResultObjects.Empty(); +} + + +UTOPNode* +UHoudiniPDGAssetLink::GetTOPNode(const int32& InNodeID) +{ + for (UTOPNetwork* CurrentTOPNet : AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + if (CurrentTOPNode->NodeId == InNodeID) + return CurrentTOPNode; + } + } + + return nullptr; +} + +void +UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork) +{ + if (!IsValid(InNode) || !IsValid(InNetwork)) + return; + + if (!InNode->bHasChildNodes) + return; + + FString PrefixPath = InNode->NodePath; + if (!PrefixPath.EndsWith("/")) + PrefixPath += "/"; + InNode->WorkItemTally.ZeroAll(); + InNode->NodeState = EPDGNodeState::None; + + TMap NodeStateOrder; + NodeStateOrder.Add(EPDGNodeState::None, 0); + NodeStateOrder.Add(EPDGNodeState::Cook_Complete, 1); + NodeStateOrder.Add(EPDGNodeState::Dirtied, 2); + NodeStateOrder.Add(EPDGNodeState::Cook_Failed, 3); + NodeStateOrder.Add(EPDGNodeState::Dirtying, 4); + NodeStateOrder.Add(EPDGNodeState::Cooking, 5); + + int8 CurrentState = 0; + + for (const UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + if (Node->NodePath.StartsWith(PrefixPath) && !Node->bHasChildNodes) + { + InNode->WorkItemTally.TotalWorkItems += Node->WorkItemTally.TotalWorkItems; + InNode->WorkItemTally.WaitingWorkItems += Node->WorkItemTally.WaitingWorkItems; + InNode->WorkItemTally.ScheduledWorkItems += Node->WorkItemTally.ScheduledWorkItems; + InNode->WorkItemTally.CookingWorkItems += Node->WorkItemTally.CookingWorkItems; + InNode->WorkItemTally.CookedWorkItems += Node->WorkItemTally.CookedWorkItems; + InNode->WorkItemTally.ErroredWorkItems += Node->WorkItemTally.ErroredWorkItems; + const int8 VisistedNodeState = NodeStateOrder.FindChecked(Node->NodeState); + if (VisistedNodeState > CurrentState) + CurrentState = VisistedNodeState; + } + } + + EPDGNodeState const* const NewState = NodeStateOrder.FindKey(CurrentState); + if (NewState) + InNode->NodeState = *NewState; +} + +void +UHoudiniPDGAssetLink::UpdateWorkItemTally() +{ + WorkItemTally.ZeroAll(); + for(UTOPNetwork* CurrentTOPNet : AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + for(UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + // Only add up the tallys from nodes without children (since parent's aggregate the child work items counts) + if (CurrentTOPNode->bHasChildNodes) + { + UpdateTOPNodeWithChildrenWorkItemTallyAndState(CurrentTOPNode, CurrentTOPNet); + } + else + { + WorkItemTally.TotalWorkItems += CurrentTOPNode->WorkItemTally.TotalWorkItems; + WorkItemTally.WaitingWorkItems += CurrentTOPNode->WorkItemTally.WaitingWorkItems; + WorkItemTally.ScheduledWorkItems += CurrentTOPNode->WorkItemTally.ScheduledWorkItems; + WorkItemTally.CookingWorkItems += CurrentTOPNode->WorkItemTally.CookingWorkItems; + WorkItemTally.CookedWorkItems += CurrentTOPNode->WorkItemTally.CookedWorkItems; + WorkItemTally.ErroredWorkItems += CurrentTOPNode->WorkItemTally.ErroredWorkItems; + } + } + } +} + + +void +UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork) +{ + if (!IsValid(TOPNetwork)) + return; + + for (UTOPNode* CurTOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(CurTOPNode)) + continue; + + CurTOPNode->WorkItemTally.ZeroAll(); + } +} + + +FString +UHoudiniPDGAssetLink::GetAssetLinkStatus(const EPDGLinkState& InLinkState) +{ + FString Status; + switch (InLinkState) + { + case EPDGLinkState::Inactive: + Status = TEXT("Inactive"); + case EPDGLinkState::Linking: + Status = TEXT("Linking"); + case EPDGLinkState::Linked: + Status = TEXT("Linked"); + case EPDGLinkState::Error_Not_Linked: + Status = TEXT("Not Linked"); + default: + Status = TEXT(""); + } + + return Status; +} + +FString +UHoudiniPDGAssetLink::GetTOPNodeStatus(const UTOPNode* InTOPNode) +{ + static const FString InvalidOrUnknownStatus = TEXT(""); + + if (!IsValid(InTOPNode)) + return InvalidOrUnknownStatus; + + if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) + { + return TEXT("Cook Failed"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) + { + return TEXT("Cook Completed"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Cooking) + { + return TEXT("Cook In Progress"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) + { + return TEXT("Dirtied"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) + { + return TEXT("Dirtying"); + } + + return InvalidOrUnknownStatus; +} + +FLinearColor +UHoudiniPDGAssetLink::GetTOPNodeStatusColor(const UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return FLinearColor::White; + + if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) + { + return FLinearColor::Red; + } + else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) + { + return FLinearColor::Green; + } + else if (InTOPNode->NodeState == EPDGNodeState::Cooking) + { + return FLinearColor(0.0, 1.0f, 1.0f); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) + { + return FLinearColor(1.0f, 0.5f, 0.0f); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) + { + return FLinearColor::Yellow; + } + + return FLinearColor::White; +} + +AActor* +UHoudiniPDGAssetLink::GetOwnerActor() const +{ + UObject* Outer = GetOuter(); + UActorComponent* Component = Cast(Outer); + if (IsValid(Component)) + return Component->GetOwner(); + else + return Cast(Outer); +} + +bool +UHoudiniPDGAssetLink::HasTemporaryOutputs() const +{ + // Loop over all networks, all nodes, all work items and check for any valid output objects + for (const UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (const UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + for (const FTOPWorkResult& WorkResult : TOPNode->WorkResult) + { + for (const FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) + { + // If the WorkResultObject's actor is not valid, then it no longer has temporary objects in the + // scene + if (!IsValid(WorkResultObject.GetOutputActorOwner().GetOutputActor())) + continue; + + for (UHoudiniOutput* Output : WorkResultObject.GetResultOutputs()) + { + if (!IsValid(Output)) + continue; + + const EHoudiniOutputType OutputType = Output->GetType(); + for (const auto& OutputObjectPair : Output->GetOutputObjects()) + { + if ((OutputType == EHoudiniOutputType::Landscape && IsValid(OutputObjectPair.Value.OutputObject)) || + IsValid(OutputObjectPair.Value.OutputComponent)) + { + return true; + } + } + } + } + } + } + } + + return false; +} + +void +UHoudiniPDGAssetLink::UpdatePostDuplicate() +{ + // Loop over all networks, all nodes, all work items and clear output actors + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + for (FTOPWorkResult& WorkResult : TOPNode->WorkResult) + { + for (FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) + { + WorkResultObject.GetOutputActorOwner().SetOutputActor(nullptr); + WorkResultObject.State = EPDGWorkResultState::None; + WorkResultObject.SetResultOutputs(TArray()); + } + } + TOPNode->GetOutputActorOwner().SetOutputActor(nullptr); + TOPNode->bCachedHaveNotLoadedWorkResults = false; + TOPNode->bCachedHaveLoadedWorkResults = false; + } + } +} + +void +UHoudiniPDGAssetLink::UpdateTOPNodeAutoloadAndVisibility() +{ + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->bAutoLoad) + { + // // Set work results that are cooked but in NotLoaded state to ToLoad + // TOPNode.SetNotLoadedWorkResultsToLoad(); + } + + TOPNode->UpdateOutputVisibilityInLevel(); + } + } +} + +void +UHoudiniPDGAssetLink::FilterTOPNodesAndOutputs() +{ + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + // TOP Node visibility filter via TOPNodeFilter + if (bUseTOPNodeFilter) + { + TOPNode->bHidden = !TOPNodeFilter.IsEmpty() && !TOPNode->NodeName.StartsWith(TOPNodeFilter); + } + else + { + TOPNode->bHidden = false; + } + + // Auto load results filter via TOPNodeOutputFilter + if (bUseTOPOutputFilter) + { + const bool bNewAutoLoad = TOPOutputFilter.IsEmpty() || TOPNode->NodeName.StartsWith(TOPOutputFilter); + if (bNewAutoLoad != TOPNode->bAutoLoad) + { + if (bNewAutoLoad) + { + // Set work results that are cooked but in NotLoaded state to ToLoad + TOPNode->bAutoLoad = true; + // TOPNode->SetNotLoadedWorkResultsToLoad(); + TOPNode->SetVisibleInLevel(true); + } + else + { + TOPNode->bAutoLoad = false; + TOPNode->SetVisibleInLevel(false); + } + TOPNode->UpdateOutputVisibilityInLevel(); + } + } + } + } +} + +#if WITH_EDITORONLY_DATA +void +UHoudiniPDGAssetLink::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedChainEvent) +{ + Super::PostEditChangeChainProperty(InPropertyChangedChainEvent); + + const FName PropertyName = InPropertyChangedChainEvent.GetPropertyName(); + if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) + { + // Refilter TOP nodes + FilterTOPNodesAndOutputs(); + bNeedsUIRefresh = true; + } + else if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) + { + bNeedsUIRefresh = true; + } +} + +#endif + +#if WITH_EDITORONLY_DATA +void +UHoudiniPDGAssetLink::PostTransacted(const FTransactionObjectEvent& TransactionEvent) +{ + Super::PostTransacted(TransactionEvent); + + if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) + return; + + bool bDoFilterTOPNodesAndOutputs = false; + for (const FName& PropName : TransactionEvent.GetChangedProperties()) + { + if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) + { + bDoFilterTOPNodesAndOutputs = true; + bNeedsUIRefresh = true; + } + else if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) + { + bNeedsUIRefresh = true; + } + } + + if (bDoFilterTOPNodesAndOutputs) + FilterTOPNodesAndOutputs(); +} +#endif + +void +FTOPWorkResultObject::DestroyResultOutputs() +{ + // Delete output components and gather output objects for deletion + bool bDidDestroyObjects = false; + bool bDidModifyFoliage = false; + + AActor* const OutputActor = OutputActorOwner.GetOutputActor(); + + for (UHoudiniOutput* CurOutput : ResultOutputs) + { + for (auto& Pair : CurOutput->GetOutputObjects()) + { + FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniOutputObject& OutputObject = Pair.Value; + if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) + { + // Instancer components require some special handling around foliage + // TODO: move/refactor so that we can use the InstanceTranslator's helper functions (RemoveAndDestroyComponent and CleanupFoliageInstances) + bool bDestroyComponent = true; + if (OutputObject.OutputComponent->IsA()) + { + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OutputObject.OutputComponent); + if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) + { + // Make sure foliage our foliage instances have been removed + USceneComponent* ParentComponent = nullptr; + if (IsValid(OutputActor)) + ParentComponent = Cast(OutputActor->GetRootComponent()); + else + ParentComponent = Cast(HISMC->GetOuter()); + if (ParentComponent && !ParentComponent->IsPendingKill()) + { + UStaticMesh* FoliageSM = HISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + return; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(HISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return; + + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + if (!FoliageType || FoliageType->IsPendingKill()) + return; +#if WITH_EDITOR + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); + + // Remove the foliage type if it doesn't have any more instances + if(HISMC->GetInstanceCount() == 0) + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + + bDidModifyFoliage = true; +#endif + } + + // do not delete FISMC that still have instances left + // as we have cleaned up our instances before, these have been hand-placed + if (HISMC->GetInstanceCount() > 0) + bDestroyComponent = false; + } + + if (bDestroyComponent) + { + USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from its actor first + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + // Detach from its parent component if attached + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + bDidDestroyObjects = true; + + OutputObject.OutputComponent = nullptr; + } + } + } + } + if (OutputObject.OutputObject && !OutputObject.OutputObject->IsPendingKill()) + { + // For actors we detach them first and then destroy + AActor* Actor = Cast(OutputObject.OutputObject); + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + if (LandscapePtr) + { + Actor = LandscapePtr->GetRawPtr(); + } + + if (Actor) + { + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + + bDidDestroyObjects = true; + } + else + { + // ... if not an actor, mark as pending kill + // OutputObject.OutputObject->MarkPendingKill(); + if (IsValid(OutputObject.OutputObject)) + OutputObjectsToDelete.Add(OutputObject.OutputObject); + OutputObject.OutputObject = nullptr; + } + } + } + } + + ResultOutputs.Empty(); + + if (bDidDestroyObjects) + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Delete the output objects we found + if (OutputObjectsToDelete.Num() > 0) + FHoudiniEngineRuntimeUtils::SafeDeleteObjects(OutputObjectsToDelete); + +#if WITH_EDITOR + if (bDidModifyFoliage) + { + // Repopulate the foliage types in the foliage mode UI if foliage mode is active + // There is a helper function in FHoudiniEngineUtils for this, but we cannot access it from this module. + // TODO: refactor? + FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); + if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) + { + EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); + EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); + } + } +#endif +} + +bool +FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) +{ + // InAssetLink and InWorld must not be null + if (!InAssetLink || InAssetLink->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InAssetLink is null!")); + return false; + } + if (!InWorld || InWorld->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InWorld is null!")); + return false; + } + + AActor* AssetLinkActor = InAssetLink->GetOwnerActor(); + + const bool bParentActorIsValid = IsValid(InParentActor); + ULevel* LevelToSpawnIn = nullptr; + if (bParentActorIsValid) + { + LevelToSpawnIn = InParentActor->GetLevel(); + } + else + { + // Get the level containing the asset link's actor + if (IsValid(AssetLinkActor)) + LevelToSpawnIn = AssetLinkActor->GetLevel(); + } + + // Fallback to InWorld's current level + UWorld* WorldToSpawnIn = nullptr; + if (!IsValid(LevelToSpawnIn)) + { + LevelToSpawnIn = InWorld->GetCurrentLevel(); + WorldToSpawnIn = InWorld; + } + else + { + WorldToSpawnIn = LevelToSpawnIn->GetWorld(); + } + + if (!IsValid(WorldToSpawnIn) || !IsValid(LevelToSpawnIn)) + { + HOUDINI_LOG_WARNING( + TEXT("Could not determine level and world to spawn PDG output actor in: asset link %s, name %s"), + *(InAssetLink->GetPathName()), + *(InName.ToString())); + return false; + } + + FActorSpawnParameters SpawnParams; + SpawnParams.Name = MakeUniqueObjectName(InWorld, AActor::StaticClass(), InName); + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; + SpawnParams.OverrideLevel = LevelToSpawnIn; + AActor *Actor = WorldToSpawnIn->SpawnActor(SpawnParams); + SetOutputActor(Actor); +#if WITH_EDITOR + Actor->SetActorLabel(InName.ToString()); +#endif + + // Set the actor transform: create a root component if it does not have one + USceneComponent* RootComponent = Actor->GetRootComponent(); + if (!RootComponent || RootComponent->IsPendingKill()) + { + RootComponent = NewObject(Actor, USceneComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + RootComponent->CreationMethod = EComponentCreationMethod::Instance; + Actor->SetRootComponent(RootComponent); + RootComponent->OnComponentCreated(); + RootComponent->RegisterComponent(); + } + + RootComponent->SetVisibility(true); + RootComponent->SetMobility(EComponentMobility::Static); + + const FVector ActorSpawnLocation = InParentActor ? InParentActor->GetActorLocation() : FVector::ZeroVector; + const FRotator ActorSpawnRotator = InParentActor ? InParentActor->GetActorRotation() : FRotator::ZeroRotator; + Actor->SetActorLocation(ActorSpawnLocation); + Actor->SetActorRotation(ActorSpawnRotator); + +#if WITH_EDITOR + if (IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) + { + Actor->SetFolderPath(InParentActor->GetFolderPath()); + Actor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); + } + else if (IsValid(AssetLinkActor) && AssetLinkActor->GetLevel() == LevelToSpawnIn) + { + Actor->SetFolderPath(*FString::Format( + TEXT("{0}/{1}_Output"), + { FStringFormatArg(AssetLinkActor->GetFolderPath().ToString()), FStringFormatArg(AssetLinkActor->GetActorLabel()) } + )); + } + else + { + Actor->SetFolderPath(*FString::Format(TEXT("{0}_Output"), { FStringFormatArg(InAssetLink->GetName()) })); + } +#else + if(IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) + { + OutputActor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); + } +#endif + + return true; +} + +bool +FOutputActorOwner::DestroyOutputActor() +{ + bool bDestroyed = false; + AActor *Actor = GetOutputActor(); + if (IsValid(Actor)) + { + // Detach from parent before destroying the actor + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + + bDestroyed = true; + } + + SetOutputActor(nullptr); + + return bDestroyed; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h index 627f9c1d9..6d95a7b79 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h @@ -1,620 +1,620 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -//#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" - - -#include "HoudiniPDGAssetLink.generated.h" - -struct FHoudiniPackageParams; - -UENUM() -enum class EPDGLinkState : uint8 -{ - Inactive, - Linking, - Linked, - Error_Not_Linked -}; - - -UENUM() -enum class EPDGNodeState : uint8 -{ - None, - Dirtied, - Dirtying, - Cooking, - Cook_Complete, - Cook_Failed -}; - -UENUM() -enum class EPDGWorkResultState : uint8 -{ - None, - ToLoad, - Loading, - Loaded, - ToDelete, - Deleting, - Deleted, - NotLoaded -}; - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EPDGBakeSelectionOption : uint8 -{ - All, - SelectedNetwork, - SelectedNode -}; -#endif - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EPDGBakePackageReplaceModeOption : uint8 -{ - CreateNewAssets, - ReplaceExistingAssets -}; -#endif - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FOutputActorOwner -{ - GENERATED_BODY(); -public: - FOutputActorOwner() - : OutputActor(nullptr) {}; - - virtual ~FOutputActorOwner() {}; - - // Create OutputActor, an actor to hold work result output - virtual bool CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName); - - // Return OutputActor - virtual AActor* GetOutputActor() const { return OutputActor; } - - // Setter for OutputActor - virtual void SetOutputActor(AActor* InActor) { OutputActor = InActor; } - - // Destroy OutputActor if it is valid. - virtual bool DestroyOutputActor(); - -private: - UPROPERTY(NonTransactional) - AActor* OutputActor; - -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FTOPWorkResultObject(); - - // Call DestroyResultObjects in the destructor - virtual ~FTOPWorkResultObject(); - - // Set ResultObjects to a copy of InUpdatedOutputs - void SetResultOutputs(const TArray& InUpdatedOutputs) { ResultOutputs = InUpdatedOutputs; } - - // Getter for ResultOutputs - TArray& GetResultOutputs() { return ResultOutputs; } - - // Getter for ResultOutputs - const TArray& GetResultOutputs() const { return ResultOutputs; } - - // Destroy ResultOutputs - void DestroyResultOutputs(); - - // Get the OutputActor owner struct - FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } - - // Get the OutputActor owner struct - const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } - -public: - - UPROPERTY(NonTransactional) - FString Name; - UPROPERTY(NonTransactional) - FString FilePath; - UPROPERTY(NonTransactional) - EPDGWorkResultState State; - -protected: - // UPROPERTY() - // TArray ResultObjects; - - UPROPERTY(NonTransactional) - TArray ResultOutputs; - -private: - // List of objects to delete, internal use only (DestroyResultOutputs) - UPROPERTY(NonTransactional) - TArray OutputObjectsToDelete; - - UPROPERTY(NonTransactional) - FOutputActorOwner OutputActorOwner; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FTOPWorkResult -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FTOPWorkResult(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const FTOPWorkResult& OtherWorkResult) const; - -public: - - UPROPERTY(NonTransactional) - int32 WorkItemIndex; - UPROPERTY(Transient) - int32 WorkItemID; - - UPROPERTY(NonTransactional) - TArray ResultObjects; - - /* - UPROPERTY() - TArray ResultObjects; - - UPROPERTY() - TArray ResultNames; - UPROPERTY() - TArray ResultFilePaths; - UPROPERTY() - TArray ResultStates; - */ -}; - - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FWorkItemTally -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FWorkItemTally(); - - void ZeroAll(); - - bool AreAllWorkItemsComplete() const; - bool AnyWorkItemsFailed() const; - bool AnyWorkItemsPending() const; - - FString ProgressRatio() const; - -public: - - UPROPERTY() - int32 TotalWorkItems; - UPROPERTY() - int32 WaitingWorkItems; - UPROPERTY() - int32 ScheduledWorkItems; - UPROPERTY() - int32 CookingWorkItems; - UPROPERTY() - int32 CookedWorkItems; - UPROPERTY() - int32 ErroredWorkItems; -}; - -// Container for baked outputs of a PDG work result object. -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniPDGWorkResultObjectBakedOutput -{ - GENERATED_BODY() - - public: - // Array of baked output per output index of the work result object's outputs. - UPROPERTY() - TArray BakedOutputs; -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UTOPNode : public UObject -{ - GENERATED_BODY() - -public: - - // Constructor - UTOPNode(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const UTOPNode& Other) const; - - void Reset(); - - bool AreAllWorkItemsComplete() const { return WorkItemTally.AreAllWorkItemsComplete(); }; - bool AnyWorkItemsFailed() const { return WorkItemTally.AnyWorkItemsFailed(); }; - bool AnyWorkItemsPending() const { return WorkItemTally.AnyWorkItemsPending(); }; - - bool IsVisibleInLevel() const { return bShow; } - void SetVisibleInLevel(bool bInVisible); - void UpdateOutputVisibilityInLevel(); - - // Sets all WorkResultObjects that are in the NotLoaded state to ToLoad. - void SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad=false); - - // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output - // actors). - void SetLoadedWorkResultsToDelete(); - - // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output - // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); - - // Get the OutputActor owner struct - FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } - - // Get the OutputActor owner struct - const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } - - // Get the baked outputs from the last bake. The map keys are [work_result_index]_[work_result_object_index] - TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } - const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } - bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const; - static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex); - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; - -#if WITH_EDITOR - void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; -#endif - -#if WITH_EDITOR - void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; -#endif - -public: - - UPROPERTY(Transient, NonTransactional) - int32 NodeId; - UPROPERTY(NonTransactional) - FString NodeName; - UPROPERTY(NonTransactional) - FString NodePath; - UPROPERTY(NonTransactional) - FString ParentName; - - UPROPERTY() - UObject* WorkResultParent; - UPROPERTY(NonTransactional) - TArray WorkResult; - - // Hidden in the nodes combobox - UPROPERTY() - bool bHidden; - UPROPERTY() - bool bAutoLoad; - - UPROPERTY(Transient, NonTransactional) - EPDGNodeState NodeState; - - UPROPERTY(Transient, NonTransactional) - FWorkItemTally WorkItemTally; - - // This is set when the TOP node's work items are processed by - // FHoudiniPDGManager based on if any NotLoaded work result objects are found - UPROPERTY(NonTransactional) - bool bCachedHaveNotLoadedWorkResults; - - // This is set when the TOP node's work items are processed by - // FHoudiniPDGManager based on if any Loaded work result objects are found - UPROPERTY(NonTransactional) - bool bCachedHaveLoadedWorkResults; - - // True if this node has child nodes - UPROPERTY(NonTransactional) - bool bHasChildNodes; - -protected: - // Visible in the level - UPROPERTY() - bool bShow; - - // Map of [work_result_index]_[work_result_object_index] to the work result object's baked outputs. - UPROPERTY() - TMap BakedWorkResultObjectOutputs; - -private: - UPROPERTY() - FOutputActorOwner OutputActorOwner; -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject -{ - GENERATED_BODY() - -public: - - // Constructor - UTOPNetwork(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const UTOPNetwork& Other) const; - - // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output - // actors). - void SetLoadedWorkResultsToDelete(); - - // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output - // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); - - // Returns true if any node in this TOP net has pending (waiting, scheduled, cooking) work items. - bool AnyWorkItemsPending() const; - -public: - - UPROPERTY(Transient, NonTransactional) - int32 NodeId; - UPROPERTY(NonTransactional) - FString NodeName; - // HAPI path to this node (relative to the HDA) - UPROPERTY(NonTransactional) - FString NodePath; - - UPROPERTY() - TArray AllTOPNodes; - - // TODO: Should be using SelectedNodeName instead? - // Index is not consistent after updating filter - UPROPERTY() - int32 SelectedTOPIndex; - - UPROPERTY(NonTransactional) - FString ParentName; - - UPROPERTY() - bool bShowResults; - UPROPERTY() - bool bAutoLoadResults; -}; - - -class UHoudiniPDGAssetLink; -DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemId*/, const FString& /*WorkResultObjectName*/); - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - friend class UHoudiniAssetComponent; - - static FString GetAssetLinkStatus(const EPDGLinkState& InLinkState); - static FString GetTOPNodeStatus(const UTOPNode* InTOPNode); - static FLinearColor GetTOPNodeStatusColor(const UTOPNode* InTOPNode); - - void UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork); - void UpdateWorkItemTally(); - static void ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork); - - // Set the TOP network at the given index as currently selected TOP network - void SelectTOPNetwork(const int32& AtIndex); - // Set the TOP node at the given index in the given TOP network as currently selected TOP node - void SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex); - - UTOPNode* GetSelectedTOPNode(); - UTOPNetwork* GetSelectedTOPNetwork(); - - FString GetSelectedTOPNodeName(); - FString GetSelectedTOPNetworkName(); - - UTOPNode* GetTOPNode(const int32& InNodeID); - UTOPNetwork* GetTOPNetwork(const int32& AtIndex); - - // Find the node with relative path 'InNodePath' from its topnet. - static UTOPNode* GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex); - // Find the network with relative path 'InNetPath' from the HDA - static UTOPNetwork* GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex); - - // Get the parent TOP node of the specified node. This is resolved - UTOPNode* GetParentTOPNode(const UTOPNode* InNode); - - static void ClearTOPNodeWorkItemResults(UTOPNode* TOPNode); - static void ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork); - // Clear the result objects of a work item (FTOPWorkResult.ResultObjects), but don't delete the work item from - // TOPNode.WorkResults (for example, the work item was dirtied but not removed from PDG) - static void ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - // Calls ClearWorkItemResultByID and then deletes the FTOPWorkResult from InTOPNode.Result as well. For example: - // the work item was removed in PDG. - static void DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - static FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - - // This should be called after the owner and this PDG asset link is duplicated. Set all output parent actors to - // null in all TOP networks/nodes. Since the TOP Networks/TOP nodes are all structs, we cannot set - // DuplicateTransient property on their OutputActor properties. - void UpdatePostDuplicate(); - - // Load the geometry generated as results of the given work item, of the given TOP node. - // The load will be done asynchronously. - // Results must be tagged with 'file', and must have a file path, otherwise will not be loaded. - //void LoadResults(FTOPNode TOPNode, HAPI_PDG_WorkitemInfo workItemInfo, HAPI_PDG_WorkitemResultInfo[] resultInfos, HAPI_PDG_WorkitemId workItemID) - - // Gets the temporary cook folder. If the parent of this asset link is a HoudiniAssetComponent use that, otherwise - // use the default static mesh temporary cook folder. - FDirectoryPath GetTemporaryCookFolder() const; - - // Get the actor that owns this PDG asset link. If the asset link is owned by a component, - // then the component's owning actor is returned. Can return null if this is now owned by - // an actor or component. - AActor* GetOwnerActor() const; - - // Checks if the asset link has any temporary outputs and returns true if it has - bool HasTemporaryOutputs() const; - - // Filter TOP nodes and outputs (hidden/visible) by TOPNodeFilter and TOPOutputFilter. - void FilterTOPNodesAndOutputs(); - - // On all FTOPNodes: Load not loaded items if bAutoload is true, and update the level visibility of work items - // result. Used when FTOPNode.bShow and/or FTOPNode.bAutoload changed. - void UpdateTOPNodeAutoloadAndVisibility(); - -#if WITH_EDITORONLY_DATA - void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; -#endif - -#if WITH_EDITORONLY_DATA - void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; -#endif - -private: - - void ClearAllTOPData(); - - static void DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode); - - static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); - -public: - - //UPROPERTY() - //UHoudiniAsset* HoudiniAsset; - - //UPROPERTY() - //UHoudiniAssetComponent* ParentHAC; - - UPROPERTY(DuplicateTransient, NonTransactional) - FString AssetName; - - // The full path to the HDA in HAPI - UPROPERTY(DuplicateTransient, NonTransactional) - FString AssetNodePath; - - UPROPERTY(DuplicateTransient, NonTransactional) - int32 AssetID; - - UPROPERTY() - TArray AllTOPNetworks; - - UPROPERTY() - int32 SelectedTOPNetworkIndex; - - UPROPERTY(Transient, NonTransactional) - EPDGLinkState LinkState; - - UPROPERTY() - bool bAutoCook; - UPROPERTY() - bool bUseTOPNodeFilter; - UPROPERTY() - bool bUseTOPOutputFilter; - UPROPERTY() - FString TOPNodeFilter; - UPROPERTY() - FString TOPOutputFilter; - - UPROPERTY(NonTransactional) - int32 NumWorkitems; - UPROPERTY(Transient, NonTransactional) - FWorkItemTally WorkItemTally; - - UPROPERTY() - FString OutputCachePath; - - UPROPERTY(Transient) - bool bNeedsUIRefresh; - - // A parent actor to serve as the parent of any output actors - // that are created. - // If null, then output actors are created under a folder - UPROPERTY(EditAnywhere, Category="Output") - AActor* OutputParentActor; - - // Folder used for baking PDG outputs - UPROPERTY() - FDirectoryPath BakeFolder; - - // - // Notifications - // - - // Delegate that is broadcast when a work result object has been loaded - FHoudiniPDGAssetLinkWorkResultObjectLoaded OnWorkResultObjectLoaded; - - // - // End: Notifications - // - -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bBakeMenuExpanded; - - // What kind of output to bake, for example, bake actors, bake to blueprint - UPROPERTY() - EHoudiniEngineBakeOption HoudiniEngineBakeOption; - - // Which outputs to bake, for example, all, selected network, selected node - UPROPERTY() - EPDGBakeSelectionOption PDGBakeSelectionOption; - - // This determines if the baked assets should replace existing assets with the same name, - // or always generate new assets (with numerical suffixes if needed to create unique names) - UPROPERTY() - EPDGBakePackageReplaceModeOption PDGBakePackageReplaceMode; - - // If true, recenter baked actors to their bounding box center after bake - UPROPERTY() - bool bRecenterBakedActors; - - // Auto-bake: if this is true, it indicates that a work result object should be baked after it is loaded. - UPROPERTY() - bool bBakeAfterWorkResultObjectLoaded; - - // The delegate handle of the auto bake helper function bound to OnWorkResultObjectLoaded. - FDelegateHandle AutoBakeDelegateHandle; -#endif -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +//#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" + + +#include "HoudiniPDGAssetLink.generated.h" + +struct FHoudiniPackageParams; + +UENUM() +enum class EPDGLinkState : uint8 +{ + Inactive, + Linking, + Linked, + Error_Not_Linked +}; + + +UENUM() +enum class EPDGNodeState : uint8 +{ + None, + Dirtied, + Dirtying, + Cooking, + Cook_Complete, + Cook_Failed +}; + +UENUM() +enum class EPDGWorkResultState : uint8 +{ + None, + ToLoad, + Loading, + Loaded, + ToDelete, + Deleting, + Deleted, + NotLoaded +}; + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EPDGBakeSelectionOption : uint8 +{ + All, + SelectedNetwork, + SelectedNode +}; +#endif + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EPDGBakePackageReplaceModeOption : uint8 +{ + CreateNewAssets, + ReplaceExistingAssets +}; +#endif + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FOutputActorOwner +{ + GENERATED_BODY(); +public: + FOutputActorOwner() + : OutputActor(nullptr) {}; + + virtual ~FOutputActorOwner() {}; + + // Create OutputActor, an actor to hold work result output + virtual bool CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName); + + // Return OutputActor + virtual AActor* GetOutputActor() const { return OutputActor; } + + // Setter for OutputActor + virtual void SetOutputActor(AActor* InActor) { OutputActor = InActor; } + + // Destroy OutputActor if it is valid. + virtual bool DestroyOutputActor(); + +private: + UPROPERTY(NonTransactional) + AActor* OutputActor; + +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FTOPWorkResultObject(); + + // Call DestroyResultObjects in the destructor + virtual ~FTOPWorkResultObject(); + + // Set ResultObjects to a copy of InUpdatedOutputs + void SetResultOutputs(const TArray& InUpdatedOutputs) { ResultOutputs = InUpdatedOutputs; } + + // Getter for ResultOutputs + TArray& GetResultOutputs() { return ResultOutputs; } + + // Getter for ResultOutputs + const TArray& GetResultOutputs() const { return ResultOutputs; } + + // Destroy ResultOutputs + void DestroyResultOutputs(); + + // Get the OutputActor owner struct + FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } + + // Get the OutputActor owner struct + const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } + +public: + + UPROPERTY(NonTransactional) + FString Name; + UPROPERTY(NonTransactional) + FString FilePath; + UPROPERTY(NonTransactional) + EPDGWorkResultState State; + +protected: + // UPROPERTY() + // TArray ResultObjects; + + UPROPERTY(NonTransactional) + TArray ResultOutputs; + +private: + // List of objects to delete, internal use only (DestroyResultOutputs) + UPROPERTY(NonTransactional) + TArray OutputObjectsToDelete; + + UPROPERTY(NonTransactional) + FOutputActorOwner OutputActorOwner; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FTOPWorkResult +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FTOPWorkResult(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const FTOPWorkResult& OtherWorkResult) const; + +public: + + UPROPERTY(NonTransactional) + int32 WorkItemIndex; + UPROPERTY(Transient) + int32 WorkItemID; + + UPROPERTY(NonTransactional) + TArray ResultObjects; + + /* + UPROPERTY() + TArray ResultObjects; + + UPROPERTY() + TArray ResultNames; + UPROPERTY() + TArray ResultFilePaths; + UPROPERTY() + TArray ResultStates; + */ +}; + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FWorkItemTally +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FWorkItemTally(); + + void ZeroAll(); + + bool AreAllWorkItemsComplete() const; + bool AnyWorkItemsFailed() const; + bool AnyWorkItemsPending() const; + + FString ProgressRatio() const; + +public: + + UPROPERTY() + int32 TotalWorkItems; + UPROPERTY() + int32 WaitingWorkItems; + UPROPERTY() + int32 ScheduledWorkItems; + UPROPERTY() + int32 CookingWorkItems; + UPROPERTY() + int32 CookedWorkItems; + UPROPERTY() + int32 ErroredWorkItems; +}; + +// Container for baked outputs of a PDG work result object. +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniPDGWorkResultObjectBakedOutput +{ + GENERATED_BODY() + + public: + // Array of baked output per output index of the work result object's outputs. + UPROPERTY() + TArray BakedOutputs; +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UTOPNode : public UObject +{ + GENERATED_BODY() + +public: + + // Constructor + UTOPNode(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const UTOPNode& Other) const; + + void Reset(); + + bool AreAllWorkItemsComplete() const { return WorkItemTally.AreAllWorkItemsComplete(); }; + bool AnyWorkItemsFailed() const { return WorkItemTally.AnyWorkItemsFailed(); }; + bool AnyWorkItemsPending() const { return WorkItemTally.AnyWorkItemsPending(); }; + + bool IsVisibleInLevel() const { return bShow; } + void SetVisibleInLevel(bool bInVisible); + void UpdateOutputVisibilityInLevel(); + + // Sets all WorkResultObjects that are in the NotLoaded state to ToLoad. + void SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad=false); + + // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output + // actors). + void SetLoadedWorkResultsToDelete(); + + // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output + // objects and actors and sets the state to Deleted. + void DeleteWorkResultOutputObjects(); + + // Get the OutputActor owner struct + FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } + + // Get the OutputActor owner struct + const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } + + // Get the baked outputs from the last bake. The map keys are [work_result_index]_[work_result_object_index] + TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } + const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } + bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const; + static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex); + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; + +#if WITH_EDITOR + void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif + +#if WITH_EDITOR + void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; +#endif + +public: + + UPROPERTY(Transient, NonTransactional) + int32 NodeId; + UPROPERTY(NonTransactional) + FString NodeName; + UPROPERTY(NonTransactional) + FString NodePath; + UPROPERTY(NonTransactional) + FString ParentName; + + UPROPERTY() + UObject* WorkResultParent; + UPROPERTY(NonTransactional) + TArray WorkResult; + + // Hidden in the nodes combobox + UPROPERTY() + bool bHidden; + UPROPERTY() + bool bAutoLoad; + + UPROPERTY(Transient, NonTransactional) + EPDGNodeState NodeState; + + UPROPERTY(Transient, NonTransactional) + FWorkItemTally WorkItemTally; + + // This is set when the TOP node's work items are processed by + // FHoudiniPDGManager based on if any NotLoaded work result objects are found + UPROPERTY(NonTransactional) + bool bCachedHaveNotLoadedWorkResults; + + // This is set when the TOP node's work items are processed by + // FHoudiniPDGManager based on if any Loaded work result objects are found + UPROPERTY(NonTransactional) + bool bCachedHaveLoadedWorkResults; + + // True if this node has child nodes + UPROPERTY(NonTransactional) + bool bHasChildNodes; + +protected: + // Visible in the level + UPROPERTY() + bool bShow; + + // Map of [work_result_index]_[work_result_object_index] to the work result object's baked outputs. + UPROPERTY() + TMap BakedWorkResultObjectOutputs; + +private: + UPROPERTY() + FOutputActorOwner OutputActorOwner; +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject +{ + GENERATED_BODY() + +public: + + // Constructor + UTOPNetwork(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const UTOPNetwork& Other) const; + + // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output + // actors). + void SetLoadedWorkResultsToDelete(); + + // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output + // objects and actors and sets the state to Deleted. + void DeleteWorkResultOutputObjects(); + + // Returns true if any node in this TOP net has pending (waiting, scheduled, cooking) work items. + bool AnyWorkItemsPending() const; + +public: + + UPROPERTY(Transient, NonTransactional) + int32 NodeId; + UPROPERTY(NonTransactional) + FString NodeName; + // HAPI path to this node (relative to the HDA) + UPROPERTY(NonTransactional) + FString NodePath; + + UPROPERTY() + TArray AllTOPNodes; + + // TODO: Should be using SelectedNodeName instead? + // Index is not consistent after updating filter + UPROPERTY() + int32 SelectedTOPIndex; + + UPROPERTY(NonTransactional) + FString ParentName; + + UPROPERTY() + bool bShowResults; + UPROPERTY() + bool bAutoLoadResults; +}; + + +class UHoudiniPDGAssetLink; +DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemId*/, const FString& /*WorkResultObjectName*/); + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + friend class UHoudiniAssetComponent; + + static FString GetAssetLinkStatus(const EPDGLinkState& InLinkState); + static FString GetTOPNodeStatus(const UTOPNode* InTOPNode); + static FLinearColor GetTOPNodeStatusColor(const UTOPNode* InTOPNode); + + void UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork); + void UpdateWorkItemTally(); + static void ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork); + + // Set the TOP network at the given index as currently selected TOP network + void SelectTOPNetwork(const int32& AtIndex); + // Set the TOP node at the given index in the given TOP network as currently selected TOP node + void SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex); + + UTOPNode* GetSelectedTOPNode(); + UTOPNetwork* GetSelectedTOPNetwork(); + + FString GetSelectedTOPNodeName(); + FString GetSelectedTOPNetworkName(); + + UTOPNode* GetTOPNode(const int32& InNodeID); + UTOPNetwork* GetTOPNetwork(const int32& AtIndex); + + // Find the node with relative path 'InNodePath' from its topnet. + static UTOPNode* GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex); + // Find the network with relative path 'InNetPath' from the HDA + static UTOPNetwork* GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex); + + // Get the parent TOP node of the specified node. This is resolved + UTOPNode* GetParentTOPNode(const UTOPNode* InNode); + + static void ClearTOPNodeWorkItemResults(UTOPNode* TOPNode); + static void ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork); + // Clear the result objects of a work item (FTOPWorkResult.ResultObjects), but don't delete the work item from + // TOPNode.WorkResults (for example, the work item was dirtied but not removed from PDG) + static void ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + // Calls ClearWorkItemResultByID and then deletes the FTOPWorkResult from InTOPNode.Result as well. For example: + // the work item was removed in PDG. + static void DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + static FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + + // This should be called after the owner and this PDG asset link is duplicated. Set all output parent actors to + // null in all TOP networks/nodes. Since the TOP Networks/TOP nodes are all structs, we cannot set + // DuplicateTransient property on their OutputActor properties. + void UpdatePostDuplicate(); + + // Load the geometry generated as results of the given work item, of the given TOP node. + // The load will be done asynchronously. + // Results must be tagged with 'file', and must have a file path, otherwise will not be loaded. + //void LoadResults(FTOPNode TOPNode, HAPI_PDG_WorkitemInfo workItemInfo, HAPI_PDG_WorkitemResultInfo[] resultInfos, HAPI_PDG_WorkitemId workItemID) + + // Gets the temporary cook folder. If the parent of this asset link is a HoudiniAssetComponent use that, otherwise + // use the default static mesh temporary cook folder. + FDirectoryPath GetTemporaryCookFolder() const; + + // Get the actor that owns this PDG asset link. If the asset link is owned by a component, + // then the component's owning actor is returned. Can return null if this is now owned by + // an actor or component. + AActor* GetOwnerActor() const; + + // Checks if the asset link has any temporary outputs and returns true if it has + bool HasTemporaryOutputs() const; + + // Filter TOP nodes and outputs (hidden/visible) by TOPNodeFilter and TOPOutputFilter. + void FilterTOPNodesAndOutputs(); + + // On all FTOPNodes: Load not loaded items if bAutoload is true, and update the level visibility of work items + // result. Used when FTOPNode.bShow and/or FTOPNode.bAutoload changed. + void UpdateTOPNodeAutoloadAndVisibility(); + +#if WITH_EDITORONLY_DATA + void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif + +#if WITH_EDITORONLY_DATA + void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; +#endif + +private: + + void ClearAllTOPData(); + + static void DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode); + + static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); + +public: + + //UPROPERTY() + //UHoudiniAsset* HoudiniAsset; + + //UPROPERTY() + //UHoudiniAssetComponent* ParentHAC; + + UPROPERTY(DuplicateTransient, NonTransactional) + FString AssetName; + + // The full path to the HDA in HAPI + UPROPERTY(DuplicateTransient, NonTransactional) + FString AssetNodePath; + + UPROPERTY(DuplicateTransient, NonTransactional) + int32 AssetID; + + UPROPERTY() + TArray AllTOPNetworks; + + UPROPERTY() + int32 SelectedTOPNetworkIndex; + + UPROPERTY(Transient, NonTransactional) + EPDGLinkState LinkState; + + UPROPERTY() + bool bAutoCook; + UPROPERTY() + bool bUseTOPNodeFilter; + UPROPERTY() + bool bUseTOPOutputFilter; + UPROPERTY() + FString TOPNodeFilter; + UPROPERTY() + FString TOPOutputFilter; + + UPROPERTY(NonTransactional) + int32 NumWorkitems; + UPROPERTY(Transient, NonTransactional) + FWorkItemTally WorkItemTally; + + UPROPERTY() + FString OutputCachePath; + + UPROPERTY(Transient) + bool bNeedsUIRefresh; + + // A parent actor to serve as the parent of any output actors + // that are created. + // If null, then output actors are created under a folder + UPROPERTY(EditAnywhere, Category="Output") + AActor* OutputParentActor; + + // Folder used for baking PDG outputs + UPROPERTY() + FDirectoryPath BakeFolder; + + // + // Notifications + // + + // Delegate that is broadcast when a work result object has been loaded + FHoudiniPDGAssetLinkWorkResultObjectLoaded OnWorkResultObjectLoaded; + + // + // End: Notifications + // + +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bBakeMenuExpanded; + + // What kind of output to bake, for example, bake actors, bake to blueprint + UPROPERTY() + EHoudiniEngineBakeOption HoudiniEngineBakeOption; + + // Which outputs to bake, for example, all, selected network, selected node + UPROPERTY() + EPDGBakeSelectionOption PDGBakeSelectionOption; + + // This determines if the baked assets should replace existing assets with the same name, + // or always generate new assets (with numerical suffixes if needed to create unique names) + UPROPERTY() + EPDGBakePackageReplaceModeOption PDGBakePackageReplaceMode; + + // If true, recenter baked actors to their bounding box center after bake + UPROPERTY() + bool bRecenterBakedActors; + + // Auto-bake: if this is true, it indicates that a work result object should be baked after it is loaded. + UPROPERTY() + bool bBakeAfterWorkResultObjectLoaded; + + // The delegate handle of the auto bake helper function bound to OnWorkResultObjectLoaded. + FDelegateHandle AutoBakeDelegateHandle; +#endif +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp index 93312206b..7d20cbb5b 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp @@ -1,259 +1,258 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameter.h" -#include "Engine/Engine.h" - -UHoudiniParameter::UHoudiniParameter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , ParmType(EHoudiniParameterType::Invalid) - , TupleSize(0) - , NodeId(-1) - , ParmId(-1) - , ParentParmId(-1) - , ChildIndex(-1) - , bIsVisible(true) - , bIsDisabled(false) - , bHasChanged(false) - , bNeedsToTriggerUpdate(true) - , bIsDefault(false) - , bIsSpare(false) - , bJoinNext(false) - , bIsChildOfMultiParm(false) - , bIsDirectChildOfMultiParm(false) - , TagCount(0) - , ValueIndex(-1) - , bHasExpression(false) - , bShowExpression(false) -{ - Name = TEXT(""); - Label = TEXT(""); - Help = TEXT(""); - -} - -UHoudiniParameter * -UHoudiniParameter::Create( UObject* InOuter, const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameter_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameter::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameter * HoudiniAssetParameter = NewObject< UHoudiniParameter >( - InOuter, UHoudiniParameter::StaticClass(), ParamName, RF_Public | RF_Transactional); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameter::IsChildParameter() const -{ - return ParentParmId >= 0; -} - -void -UHoudiniParameter::RevertToDefault() -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); -} - -void -UHoudiniParameter::RevertToDefault(const int32& InAtTupleIndex) -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.AddUnique(InAtTupleIndex); - - MarkChanged(true); -} - -void -UHoudiniParameter::MarkDefault(const bool& bInDefault) -{ - bIsDefault = bInDefault; - - if (bInDefault) - { - // No need to revert default parameter - bPendingRevertToDefault = false; - TuplePendingRevertToDefault.Empty(); - } -} - -EHoudiniRampInterpolationType UHoudiniParameter::GetHoudiniInterpMethodFromInt(int32 InInt) -{ - EHoudiniRampInterpolationType Result = EHoudiniRampInterpolationType::InValid; - switch (InInt) - { - case 0: - Result = EHoudiniRampInterpolationType::CONSTANT; - break; - case 1: - Result = EHoudiniRampInterpolationType::LINEAR; - break; - case 2: - Result = EHoudiniRampInterpolationType::CATMULL_ROM; - break; - case 3: - Result = EHoudiniRampInterpolationType::MONOTONE_CUBIC; - break; - case 4: - Result = EHoudiniRampInterpolationType::BEZIER; - break; - case 5: - Result = EHoudiniRampInterpolationType::BSPLINE; - break; - case 6: - Result = EHoudiniRampInterpolationType::HERMITE; - break; - } - - return Result; -} - -FString -UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType) -{ - FString Result = FString("InValid"); - switch (InType) - { - case EHoudiniRampInterpolationType::CONSTANT: - Result = FString("Constant"); - break; - case EHoudiniRampInterpolationType::LINEAR: - Result = FString("Linear"); - break; - case EHoudiniRampInterpolationType::BEZIER: - Result = FString("Bezier"); - break; - case EHoudiniRampInterpolationType::BSPLINE: - Result = FString("B-Spline"); - break; - case EHoudiniRampInterpolationType::MONOTONE_CUBIC: - Result = FString("Monotone Cubic"); - break; - case EHoudiniRampInterpolationType::CATMULL_ROM: - Result = FString("Catmull Rom"); - break; - case EHoudiniRampInterpolationType::HERMITE: - Result = FString("Hermite"); - break; - } - - return Result; -} -EHoudiniRampInterpolationType -UHoudiniParameter::GetHoudiniInterpMethodFromString(const FString& InString) -{ - if (InString.StartsWith(TEXT("Constant"))) - return EHoudiniRampInterpolationType::CONSTANT; - - else if (InString.StartsWith("Linear")) - return EHoudiniRampInterpolationType::LINEAR; - - else if (InString.StartsWith("Bezier")) - return EHoudiniRampInterpolationType::BEZIER; - - else if (InString.StartsWith("B-Spline")) - return EHoudiniRampInterpolationType::BSPLINE; - - else if (InString.StartsWith("Monotone Cubic")) - return EHoudiniRampInterpolationType::MONOTONE_CUBIC; - - else if (InString.StartsWith("Catmull Rom")) - return EHoudiniRampInterpolationType::CATMULL_ROM; - - else if (InString.StartsWith("Hermite")) - return EHoudiniRampInterpolationType::HERMITE; - - return EHoudiniRampInterpolationType::InValid; -} - - - -ERichCurveInterpMode -UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType) -{ - switch (InType) - { - case EHoudiniRampInterpolationType::CONSTANT: - return ERichCurveInterpMode::RCIM_Constant; - - case EHoudiniRampInterpolationType::LINEAR: - return ERichCurveInterpMode::RCIM_Linear; - - default: - break; - } - - return ERichCurveInterpMode::RCIM_Cubic; -} - -UHoudiniParameter* -UHoudiniParameter::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags, EObjectFlags SetFlags) -{ - UHoudiniParameter* NewParameter = Cast(StaticDuplicateObject(this, DestOuter)); - - NewParameter->CopyStateFrom(this, false); - - return NewParameter; -} - -void -UHoudiniParameter::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - - NodeId = InParameter->NodeId; - ParmId = InParameter->ParmId; - ParentParmId = InParameter->ParentParmId; - bIsDefault = InParameter->bIsDefault; - bPendingRevertToDefault = InParameter->bPendingRevertToDefault; - TuplePendingRevertToDefault = InParameter->TuplePendingRevertToDefault; - bShowExpression = InParameter->bShowExpression; -} - -void -UHoudiniParameter::InvalidateData() -{ - -} - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameter.h" + +UHoudiniParameter::UHoudiniParameter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , ParmType(EHoudiniParameterType::Invalid) + , TupleSize(0) + , NodeId(-1) + , ParmId(-1) + , ParentParmId(-1) + , ChildIndex(-1) + , bIsVisible(true) + , bIsDisabled(false) + , bHasChanged(false) + , bNeedsToTriggerUpdate(true) + , bIsDefault(false) + , bIsSpare(false) + , bJoinNext(false) + , bIsChildOfMultiParm(false) + , bIsDirectChildOfMultiParm(false) + , TagCount(0) + , ValueIndex(-1) + , bHasExpression(false) + , bShowExpression(false) +{ + Name = TEXT(""); + Label = TEXT(""); + Help = TEXT(""); + +} + +UHoudiniParameter * +UHoudiniParameter::Create( UObject* InOuter, const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameter_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameter::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameter * HoudiniAssetParameter = NewObject< UHoudiniParameter >( + InOuter, UHoudiniParameter::StaticClass(), ParamName, RF_Public | RF_Transactional); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameter::IsChildParameter() const +{ + return ParentParmId >= 0; +} + +void +UHoudiniParameter::RevertToDefault() +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); +} + +void +UHoudiniParameter::RevertToDefault(const int32& InAtTupleIndex) +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.AddUnique(InAtTupleIndex); + + MarkChanged(true); +} + +void +UHoudiniParameter::MarkDefault(const bool& bInDefault) +{ + bIsDefault = bInDefault; + + if (bInDefault) + { + // No need to revert default parameter + bPendingRevertToDefault = false; + TuplePendingRevertToDefault.Empty(); + } +} + +EHoudiniRampInterpolationType UHoudiniParameter::GetHoudiniInterpMethodFromInt(int32 InInt) +{ + EHoudiniRampInterpolationType Result = EHoudiniRampInterpolationType::InValid; + switch (InInt) + { + case 0: + Result = EHoudiniRampInterpolationType::CONSTANT; + break; + case 1: + Result = EHoudiniRampInterpolationType::LINEAR; + break; + case 2: + Result = EHoudiniRampInterpolationType::CATMULL_ROM; + break; + case 3: + Result = EHoudiniRampInterpolationType::MONOTONE_CUBIC; + break; + case 4: + Result = EHoudiniRampInterpolationType::BEZIER; + break; + case 5: + Result = EHoudiniRampInterpolationType::BSPLINE; + break; + case 6: + Result = EHoudiniRampInterpolationType::HERMITE; + break; + } + + return Result; +} + +FString +UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType) +{ + FString Result = FString("InValid"); + switch (InType) + { + case EHoudiniRampInterpolationType::CONSTANT: + Result = FString("Constant"); + break; + case EHoudiniRampInterpolationType::LINEAR: + Result = FString("Linear"); + break; + case EHoudiniRampInterpolationType::BEZIER: + Result = FString("Bezier"); + break; + case EHoudiniRampInterpolationType::BSPLINE: + Result = FString("B-Spline"); + break; + case EHoudiniRampInterpolationType::MONOTONE_CUBIC: + Result = FString("Monotone Cubic"); + break; + case EHoudiniRampInterpolationType::CATMULL_ROM: + Result = FString("Catmull Rom"); + break; + case EHoudiniRampInterpolationType::HERMITE: + Result = FString("Hermite"); + break; + } + + return Result; +} +EHoudiniRampInterpolationType +UHoudiniParameter::GetHoudiniInterpMethodFromString(const FString& InString) +{ + if (InString.StartsWith(TEXT("Constant"))) + return EHoudiniRampInterpolationType::CONSTANT; + + else if (InString.StartsWith("Linear")) + return EHoudiniRampInterpolationType::LINEAR; + + else if (InString.StartsWith("Bezier")) + return EHoudiniRampInterpolationType::BEZIER; + + else if (InString.StartsWith("B-Spline")) + return EHoudiniRampInterpolationType::BSPLINE; + + else if (InString.StartsWith("Monotone Cubic")) + return EHoudiniRampInterpolationType::MONOTONE_CUBIC; + + else if (InString.StartsWith("Catmull Rom")) + return EHoudiniRampInterpolationType::CATMULL_ROM; + + else if (InString.StartsWith("Hermite")) + return EHoudiniRampInterpolationType::HERMITE; + + return EHoudiniRampInterpolationType::InValid; +} + + + +ERichCurveInterpMode +UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType) +{ + switch (InType) + { + case EHoudiniRampInterpolationType::CONSTANT: + return ERichCurveInterpMode::RCIM_Constant; + + case EHoudiniRampInterpolationType::LINEAR: + return ERichCurveInterpMode::RCIM_Linear; + + default: + break; + } + + return ERichCurveInterpMode::RCIM_Cubic; +} + +UHoudiniParameter* +UHoudiniParameter::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags, EObjectFlags SetFlags) +{ + UHoudiniParameter* NewParameter = Cast(StaticDuplicateObject(this, DestOuter)); + + NewParameter->CopyStateFrom(this, false); + + return NewParameter; +} + +void +UHoudiniParameter::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + + NodeId = InParameter->NodeId; + ParmId = InParameter->ParmId; + ParentParmId = InParameter->ParentParmId; + bIsDefault = InParameter->bIsDefault; + bPendingRevertToDefault = InParameter->bPendingRevertToDefault; + TuplePendingRevertToDefault = InParameter->TuplePendingRevertToDefault; + bShowExpression = InParameter->bShowExpression; +} + +void +UHoudiniParameter::InvalidateData() +{ + +} + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h index 355caebd7..1d1d5ec19 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h @@ -1,325 +1,325 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/Object.h" -#include "Curves/RealCurve.h" -#include "HoudiniInput.h" - -#include "HoudiniParameter.generated.h" - -UENUM() -enum class EHoudiniParameterType : uint8 -{ - Invalid, - - Button, - ButtonStrip, - Color, - ColorRamp, - File, - FileDir, - FileGeo, - FileImage, - Float, - FloatRamp, - Folder, - FolderList, - Input, - Int, - IntChoice, - Label, - MultiParm, - Separator, - String, - StringChoice, - StringAssetRef, - Toggle, -}; - -UENUM() -enum class EHoudiniRampInterpolationType : int8 -{ - InValid = -1, - - CONSTANT = 0, - LINEAR = 1, - CATMULL_ROM = 2, - MONOTONE_CUBIC = 3, - BEZIER = 4, - BSPLINE = 5, - HERMITE = 6 -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject -{ - -public: - - GENERATED_UCLASS_BODY() - - friend class UHoudiniAssetParameter; - - // - static UHoudiniParameter * Create(UObject* Outer, const FString& ParamName); - - // Equality, consider two param equals if they have the same name, type, tuple size and disabled status - bool operator==(const UHoudiniParameter& other) const - { - return ( TupleSize == other.TupleSize && ParmType == other.ParmType - && Name.Equals(other.Name) && bIsDisabled == other.bIsDisabled ); - } - - bool Matches(const UHoudiniParameter& other) const { return (*this == other); }; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - // Get parent parameter for this parameter. - virtual const FString & GetParameterName() const { return Name; }; - virtual const FString & GetParameterLabel() const { return Label; }; - virtual const FString GetParameterHelp() const { return Help; }; - - virtual EHoudiniParameterType GetParameterType() const { return ParmType; }; - virtual int32 GetTupleSize() const { return TupleSize; }; - virtual int32 GetNodeId() const { return NodeId; }; - virtual int32 GetParmId() const { return ParmId; }; - virtual int32 GetParentParmId() const { return ParentParmId; }; - virtual int32 GetChildIndex() const { return ChildIndex; }; - - virtual bool IsVisible() const { return bIsVisible; }; - virtual bool ShouldDisplay() const{ return bIsVisible && ParmType != EHoudiniParameterType::Invalid; }; - virtual bool IsDisabled() const { return bIsDisabled; }; - virtual bool HasChanged() const { return bHasChanged; }; - virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; - virtual bool IsDefault() const { return true; }; - virtual bool IsSpare() const { return bIsSpare; }; - virtual bool GetJoinNext() const { return bJoinNext; }; - virtual bool IsAutoUpdate() const { return bAutoUpdate; }; - - virtual int32 GetTagCount() const { return TagCount; }; - virtual int32 GetValueIndex() const { return ValueIndex; }; - - virtual TMap& GetTags() { return Tags; }; - - virtual bool IsChildParameter() const; - - virtual bool IsPendingRevertToDefault() const { return bPendingRevertToDefault; }; - virtual void GetTuplePendingRevertToDefault(TArray& OutArray) { OutArray = TuplePendingRevertToDefault; }; - - virtual bool HasExpression() const { return bHasExpression; }; - virtual bool IsShowingExpression() const { return bShowExpression; }; - virtual FString GetExpression() const { return ParamExpression; }; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - - // Set parent parameter for this parameter. - virtual void SetParameterName(const FString& InName) { Name = InName; }; - virtual void SetParameterLabel(const FString& InLabel) { Label = InLabel; }; - virtual void SetParameterHelp(const FString& InHelp) { Help = InHelp; }; - - virtual void SetParameterType(const EHoudiniParameterType& InType) { ParmType = InType; }; - virtual void SetTupleSize(const uint32& InTupleSize) { TupleSize = InTupleSize; }; - virtual void SetNodeId(const int32& InNodeId) { NodeId = InNodeId; }; - virtual void SetParmId(const int32& InParmId) { ParmId = InParmId; }; - virtual void SetParentParmId(const int32& InParentParmId) { ParentParmId = InParentParmId; }; - virtual void SetChildIndex(const int32& InChildIndex) { ChildIndex = InChildIndex; }; - - virtual void SetIsChildOfMultiParm(const bool& IsChildOfMultiParam) { bIsChildOfMultiParm = IsChildOfMultiParam; }; - virtual bool GetIsChildOfMultiParm() const { return bIsChildOfMultiParm; }; - - virtual void SetIsDirectChildOfMultiParm(const bool& IsDirectChildOfMultiParam) { bIsDirectChildOfMultiParm = IsDirectChildOfMultiParam; }; - virtual bool IsDirectChildOfMultiParm() const { return bIsDirectChildOfMultiParm; }; - - virtual void SetVisible(const bool& InIsVisible) { bIsVisible = InIsVisible; }; - virtual void SetDisabled(const bool& InIsDisabled) { bIsDisabled = InIsDisabled; }; - virtual void SetDefault(const bool& InIsDefault) { bIsDefault = InIsDefault; }; - virtual void SetSpare(const bool& InIsSpare) { bIsSpare = InIsSpare; }; - virtual void SetJoinNext(const bool& InJoinNext) { bJoinNext = InJoinNext; }; - - virtual void SetTagCount(const uint32& InTagCount) { TagCount = InTagCount; }; - virtual void SetValueIndex(const uint32& InValueIndex) { ValueIndex = InValueIndex; }; - - virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - virtual void RevertToDefault(); - virtual void RevertToDefault(const int32& TupleIndex); - virtual void MarkDefault(const bool& bInDefault); - - virtual void SetHasExpression(const bool& InHasExpression) { bHasExpression = InHasExpression; }; - virtual void SetShowExpression(const bool& InShowExpression) { bShowExpression = InShowExpression; }; - virtual void SetExpression(const FString& InParamExpression) { ParamExpression = InParamExpression; }; - - virtual void SetAutoUpdate(const bool& InAutoUpdate) { bAutoUpdate = InAutoUpdate; }; - - static FString GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType); - static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromInt(int32 InInt); - static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromString(const FString& InString); - - static ERichCurveInterpMode EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType); - - // Duplicate this object for state transfer between component instances and templates - UHoudiniParameter* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - // Replace any input references using the provided mapping - virtual void RemapInputs(const TMap& InputMapping) {}; - - // Replace any parameter references using the provided mapping - virtual void RemapParameters(const TMap& ParameterMapping) {}; - - // Invalidate ids - virtual void InvalidateData(); - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - virtual void OnPreCook() {}; - -protected: - - //--------------------------------------------------------------------------------------------- - // ParmInfos - //--------------------------------------------------------------------------------------------- - - // - UPROPERTY() - FString Name; - - // - UPROPERTY() - FString Label; - - // Unreal type of the parameter - UPROPERTY() - EHoudiniParameterType ParmType; - - // Tuple size. For scalar parameters this value is 1, but for vector parameters this value can be greater. - UPROPERTY() - uint32 TupleSize; - - // Node this parameter belongs to. - UPROPERTY(DuplicateTransient) - int32 NodeId; - - // Id of this parameter. - UPROPERTY(DuplicateTransient) - int32 ParmId; - - // Id of parent parameter, -1 if root is parent. - UPROPERTY(DuplicateTransient) - int32 ParentParmId; - - // Child index within its parent parameter. - UPROPERTY() - int32 ChildIndex; - - // - UPROPERTY() - bool bIsVisible; - - // - UPROPERTY() - bool bIsDisabled; - - // Is set to true if value of this parameter has been changed by user. - UPROPERTY() - bool bHasChanged; - - // Is set to true if value of this parameter will trigger an update of the asset - UPROPERTY() - bool bNeedsToTriggerUpdate; - - // Indicates that this parameter is still using its default value - UPROPERTY(Transient, DuplicateTransient) - bool bIsDefault; - - // Permissions for file parms - UPROPERTY() - bool bIsSpare; - - // - UPROPERTY() - bool bJoinNext; - - // - UPROPERTY() - bool bIsChildOfMultiParm; - - UPROPERTY() - bool bIsDirectChildOfMultiParm; - - // Indicates a parameter value needs to be reverted to its default - UPROPERTY(DuplicateTransient) - bool bPendingRevertToDefault; - - UPROPERTY(DuplicateTransient) - TArray TuplePendingRevertToDefault; - - // - UPROPERTY() - FString Help; - - // Number of tags on this parameter - UPROPERTY() - uint32 TagCount; - - // The index to use to look into the values array in order to retrieve the actual value(s) of this parameter. - UPROPERTY() - int32 ValueIndex; - - //------------------------------------------------------------------------------------------------------------------------- - // Expression - // TODO: Use tuple array for this - // Indicates the parameters has an expression value - UPROPERTY() - bool bHasExpression; - - // Indicates we are currently displaying the parameter's value - UPROPERTY(DuplicateTransient) - bool bShowExpression; - - // The parameter's expression - UPROPERTY() - FString ParamExpression; - - UPROPERTY() - TMap Tags; - - UPROPERTY() - bool bAutoUpdate = true; - - -}; - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/Object.h" +#include "Curves/RealCurve.h" +#include "HoudiniInput.h" + +#include "HoudiniParameter.generated.h" + +UENUM() +enum class EHoudiniParameterType : uint8 +{ + Invalid, + + Button, + ButtonStrip, + Color, + ColorRamp, + File, + FileDir, + FileGeo, + FileImage, + Float, + FloatRamp, + Folder, + FolderList, + Input, + Int, + IntChoice, + Label, + MultiParm, + Separator, + String, + StringChoice, + StringAssetRef, + Toggle, +}; + +UENUM() +enum class EHoudiniRampInterpolationType : int8 +{ + InValid = -1, + + CONSTANT = 0, + LINEAR = 1, + CATMULL_ROM = 2, + MONOTONE_CUBIC = 3, + BEZIER = 4, + BSPLINE = 5, + HERMITE = 6 +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject +{ + +public: + + GENERATED_UCLASS_BODY() + + friend class UHoudiniAssetParameter; + + // + static UHoudiniParameter * Create(UObject* Outer, const FString& ParamName); + + // Equality, consider two param equals if they have the same name, type, tuple size and disabled status + bool operator==(const UHoudiniParameter& other) const + { + return ( TupleSize == other.TupleSize && ParmType == other.ParmType + && Name.Equals(other.Name) && bIsDisabled == other.bIsDisabled ); + } + + bool Matches(const UHoudiniParameter& other) const { return (*this == other); }; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + // Get parent parameter for this parameter. + virtual const FString & GetParameterName() const { return Name; }; + virtual const FString & GetParameterLabel() const { return Label; }; + virtual const FString GetParameterHelp() const { return Help; }; + + virtual EHoudiniParameterType GetParameterType() const { return ParmType; }; + virtual int32 GetTupleSize() const { return TupleSize; }; + virtual int32 GetNodeId() const { return NodeId; }; + virtual int32 GetParmId() const { return ParmId; }; + virtual int32 GetParentParmId() const { return ParentParmId; }; + virtual int32 GetChildIndex() const { return ChildIndex; }; + + virtual bool IsVisible() const { return bIsVisible; }; + virtual bool ShouldDisplay() const{ return bIsVisible && ParmType != EHoudiniParameterType::Invalid; }; + virtual bool IsDisabled() const { return bIsDisabled; }; + virtual bool HasChanged() const { return bHasChanged; }; + virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; + virtual bool IsDefault() const { return true; }; + virtual bool IsSpare() const { return bIsSpare; }; + virtual bool GetJoinNext() const { return bJoinNext; }; + virtual bool IsAutoUpdate() const { return bAutoUpdate; }; + + virtual int32 GetTagCount() const { return TagCount; }; + virtual int32 GetValueIndex() const { return ValueIndex; }; + + virtual TMap& GetTags() { return Tags; }; + + virtual bool IsChildParameter() const; + + virtual bool IsPendingRevertToDefault() const { return bPendingRevertToDefault; }; + virtual void GetTuplePendingRevertToDefault(TArray& OutArray) { OutArray = TuplePendingRevertToDefault; }; + + virtual bool HasExpression() const { return bHasExpression; }; + virtual bool IsShowingExpression() const { return bShowExpression; }; + virtual FString GetExpression() const { return ParamExpression; }; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + + // Set parent parameter for this parameter. + virtual void SetParameterName(const FString& InName) { Name = InName; }; + virtual void SetParameterLabel(const FString& InLabel) { Label = InLabel; }; + virtual void SetParameterHelp(const FString& InHelp) { Help = InHelp; }; + + virtual void SetParameterType(const EHoudiniParameterType& InType) { ParmType = InType; }; + virtual void SetTupleSize(const uint32& InTupleSize) { TupleSize = InTupleSize; }; + virtual void SetNodeId(const int32& InNodeId) { NodeId = InNodeId; }; + virtual void SetParmId(const int32& InParmId) { ParmId = InParmId; }; + virtual void SetParentParmId(const int32& InParentParmId) { ParentParmId = InParentParmId; }; + virtual void SetChildIndex(const int32& InChildIndex) { ChildIndex = InChildIndex; }; + + virtual void SetIsChildOfMultiParm(const bool& IsChildOfMultiParam) { bIsChildOfMultiParm = IsChildOfMultiParam; }; + virtual bool GetIsChildOfMultiParm() const { return bIsChildOfMultiParm; }; + + virtual void SetIsDirectChildOfMultiParm(const bool& IsDirectChildOfMultiParam) { bIsDirectChildOfMultiParm = IsDirectChildOfMultiParam; }; + virtual bool IsDirectChildOfMultiParm() const { return bIsDirectChildOfMultiParm; }; + + virtual void SetVisible(const bool& InIsVisible) { bIsVisible = InIsVisible; }; + virtual void SetDisabled(const bool& InIsDisabled) { bIsDisabled = InIsDisabled; }; + virtual void SetDefault(const bool& InIsDefault) { bIsDefault = InIsDefault; }; + virtual void SetSpare(const bool& InIsSpare) { bIsSpare = InIsSpare; }; + virtual void SetJoinNext(const bool& InJoinNext) { bJoinNext = InJoinNext; }; + + virtual void SetTagCount(const uint32& InTagCount) { TagCount = InTagCount; }; + virtual void SetValueIndex(const uint32& InValueIndex) { ValueIndex = InValueIndex; }; + + virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + virtual void RevertToDefault(); + virtual void RevertToDefault(const int32& TupleIndex); + virtual void MarkDefault(const bool& bInDefault); + + virtual void SetHasExpression(const bool& InHasExpression) { bHasExpression = InHasExpression; }; + virtual void SetShowExpression(const bool& InShowExpression) { bShowExpression = InShowExpression; }; + virtual void SetExpression(const FString& InParamExpression) { ParamExpression = InParamExpression; }; + + virtual void SetAutoUpdate(const bool& InAutoUpdate) { bAutoUpdate = InAutoUpdate; }; + + static FString GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType); + static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromInt(int32 InInt); + static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromString(const FString& InString); + + static ERichCurveInterpMode EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType); + + // Duplicate this object for state transfer between component instances and templates + UHoudiniParameter* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + // Replace any input references using the provided mapping + virtual void RemapInputs(const TMap& InputMapping) {}; + + // Replace any parameter references using the provided mapping + virtual void RemapParameters(const TMap& ParameterMapping) {}; + + // Invalidate ids + virtual void InvalidateData(); + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + virtual void OnPreCook() {}; + +protected: + + //--------------------------------------------------------------------------------------------- + // ParmInfos + //--------------------------------------------------------------------------------------------- + + // + UPROPERTY() + FString Name; + + // + UPROPERTY() + FString Label; + + // Unreal type of the parameter + UPROPERTY() + EHoudiniParameterType ParmType; + + // Tuple size. For scalar parameters this value is 1, but for vector parameters this value can be greater. + UPROPERTY() + uint32 TupleSize; + + // Node this parameter belongs to. + UPROPERTY(DuplicateTransient) + int32 NodeId; + + // Id of this parameter. + UPROPERTY(DuplicateTransient) + int32 ParmId; + + // Id of parent parameter, -1 if root is parent. + UPROPERTY(DuplicateTransient) + int32 ParentParmId; + + // Child index within its parent parameter. + UPROPERTY() + int32 ChildIndex; + + // + UPROPERTY() + bool bIsVisible; + + // + UPROPERTY() + bool bIsDisabled; + + // Is set to true if value of this parameter has been changed by user. + UPROPERTY() + bool bHasChanged; + + // Is set to true if value of this parameter will trigger an update of the asset + UPROPERTY() + bool bNeedsToTriggerUpdate; + + // Indicates that this parameter is still using its default value + UPROPERTY(Transient, DuplicateTransient) + bool bIsDefault; + + // Permissions for file parms + UPROPERTY() + bool bIsSpare; + + // + UPROPERTY() + bool bJoinNext; + + // + UPROPERTY() + bool bIsChildOfMultiParm; + + UPROPERTY() + bool bIsDirectChildOfMultiParm; + + // Indicates a parameter value needs to be reverted to its default + UPROPERTY(DuplicateTransient) + bool bPendingRevertToDefault; + + UPROPERTY(DuplicateTransient) + TArray TuplePendingRevertToDefault; + + // + UPROPERTY() + FString Help; + + // Number of tags on this parameter + UPROPERTY() + uint32 TagCount; + + // The index to use to look into the values array in order to retrieve the actual value(s) of this parameter. + UPROPERTY() + int32 ValueIndex; + + //------------------------------------------------------------------------------------------------------------------------- + // Expression + // TODO: Use tuple array for this + // Indicates the parameters has an expression value + UPROPERTY() + bool bHasExpression; + + // Indicates we are currently displaying the parameter's value + UPROPERTY(DuplicateTransient) + bool bShowExpression; + + // The parameter's expression + UPROPERTY() + FString ParamExpression; + + UPROPERTY() + TMap Tags; + + UPROPERTY() + bool bAutoUpdate = true; + + +}; + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp index 067e6326f..f24c5a07b 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterButton.h" - -UHoudiniParameterButton::UHoudiniParameterButton(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Button; -} - -UHoudiniParameterButton * -UHoudiniParameterButton::Create( UObject* InOuter, const FString& InParamName ) -{ - FString ParamNameStr = "HoudiniParameterButton_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButton::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterButton * HoudiniAssetParameter = NewObject< UHoudiniParameterButton >( - InOuter, UHoudiniParameterButton::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Button); - - //HoudiniAssetParameterButton->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterButton.h" + +UHoudiniParameterButton::UHoudiniParameterButton(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Button; +} + +UHoudiniParameterButton * +UHoudiniParameterButton::Create( UObject* InOuter, const FString& InParamName ) +{ + FString ParamNameStr = "HoudiniParameterButton_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButton::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterButton * HoudiniAssetParameter = NewObject< UHoudiniParameterButton >( + InOuter, UHoudiniParameterButton::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Button); + + //HoudiniAssetParameterButton->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h index 2ca48e2ad..ff285a923 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterButton.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterButton : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterButton * Create( - UObject* Outer, - const FString& ParamName); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterButton.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterButton : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterButton * Create( + UObject* Outer, + const FString& ParamName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp index 36df73246..be51713f0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp @@ -1,74 +1,74 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterButtonStrip.h" - -UHoudiniParameterButtonStrip::UHoudiniParameterButtonStrip(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::ButtonStrip; -} - -UHoudiniParameterButtonStrip * -UHoudiniParameterButtonStrip::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterButton_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButtonStrip::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterButtonStrip * HoudiniAssetParameter = NewObject< UHoudiniParameterButtonStrip >( - InOuter, UHoudiniParameterButtonStrip::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::ButtonStrip); - - HoudiniAssetParameter->Count = 0; - - return HoudiniAssetParameter; -} - -FString * -UHoudiniParameterButtonStrip::GetStringLabelAt(const int32 & InIndex) -{ - if (!Labels.IsValidIndex(InIndex)) - return nullptr; - - return &(Labels[InIndex]); -} - -bool -UHoudiniParameterButtonStrip::SetValueAt(const int32 & InIdx, int32 InVal) -{ - if (!Values.IsValidIndex(InIdx)) - return false; - - if (Values[InIdx] == InVal) - return false; - - Values[InIdx] = InVal; - return true; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterButtonStrip.h" + +UHoudiniParameterButtonStrip::UHoudiniParameterButtonStrip(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::ButtonStrip; +} + +UHoudiniParameterButtonStrip * +UHoudiniParameterButtonStrip::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterButton_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButtonStrip::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterButtonStrip * HoudiniAssetParameter = NewObject< UHoudiniParameterButtonStrip >( + InOuter, UHoudiniParameterButtonStrip::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::ButtonStrip); + + HoudiniAssetParameter->Count = 0; + + return HoudiniAssetParameter; +} + +FString * +UHoudiniParameterButtonStrip::GetStringLabelAt(const int32 & InIndex) +{ + if (!Labels.IsValidIndex(InIndex)) + return nullptr; + + return &(Labels[InIndex]); +} + +bool +UHoudiniParameterButtonStrip::SetValueAt(const int32 & InIdx, int32 InVal) +{ + if (!Values.IsValidIndex(InIdx)) + return false; + + if (Values[InIdx] == InVal) + return false; + + Values[InIdx] = InVal; + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h index b470e8145..c923c6ffa 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h @@ -1,66 +1,66 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterButtonStrip.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterButtonStrip : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterButtonStrip * Create( - UObject* InOuter, - const FString& InParamName); - - UPROPERTY() - int32 Count; - - UPROPERTY() - TArray Labels; - - UPROPERTY() - TArray Values; - - - void InitializeLabels(const int32 & InSize) { Labels.SetNumZeroed(InSize); Values.SetNumZeroed(InSize); Count = InSize; }; - - bool SetValueAt(const int32 & InIdx, int32 InVal); - - - FString * GetStringLabelAt(const int32 & InIndex); - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterButtonStrip.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterButtonStrip : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterButtonStrip * Create( + UObject* InOuter, + const FString& InParamName); + + UPROPERTY() + int32 Count; + + UPROPERTY() + TArray Labels; + + UPROPERTY() + TArray Values; + + + void InitializeLabels(const int32 & InSize) { Labels.SetNumZeroed(InSize); Values.SetNumZeroed(InSize); Count = InSize; }; + + bool SetValueAt(const int32 & InIdx, int32 InVal); + + + FString * GetStringLabelAt(const int32 & InIndex); + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp index 5ece33aca..58ae4ecd2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp @@ -1,260 +1,260 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterChoice.h" - -UHoudiniParameterChoice::UHoudiniParameterChoice(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , IntValue(-1) -{ - ParmType = EHoudiniParameterType::IntChoice; -} - -UHoudiniParameterChoice * -UHoudiniParameterChoice::Create( UObject* InOuter, const FString& InParamName, const EHoudiniParameterType& InParmType) -{ - FString ParamNameStr = "HoudiniParameterChoice_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterChoice::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterChoice * HoudiniAssetParameter = NewObject< UHoudiniParameterChoice >( - InOuter, UHoudiniParameterChoice::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(InParmType); - - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -void -UHoudiniParameterChoice::BeginDestroy() -{ - // We need to clean up our arrays - for (auto& Ptr : ChoiceLabelsPtr) - { - Ptr.Reset(); - } - ChoiceLabelsPtr.Empty(); - - // Then the string arrays - StringChoiceLabels.Empty(); - StringChoiceValues.Empty(); - - Super::BeginDestroy(); -} - - -FString* -UHoudiniParameterChoice::GetStringChoiceValueAt(const int32& InAtIndex) -{ - if (!StringChoiceValues.IsValidIndex(InAtIndex)) - return nullptr; - - return &(StringChoiceValues[InAtIndex]); -} - -FString* -UHoudiniParameterChoice::GetStringChoiceLabelAt(const int32& InAtIndex) -{ - if (!StringChoiceLabels.IsValidIndex(InAtIndex)) - return nullptr; - - return &(StringChoiceLabels[InAtIndex]); -} - -void -UHoudiniParameterChoice::SetNumChoices(const int32& InNumChoices) -{ - // Set the array sizes - StringChoiceValues.SetNumZeroed(InNumChoices); - StringChoiceLabels.SetNumZeroed(InNumChoices); - - UpdateChoiceLabelsPtr(); -} - -// Update the pointers to the ChoiceLabels -bool -UHoudiniParameterChoice::UpdateChoiceLabelsPtr() -{ - /* - bool bNeedUpdate = false; - if (StringChoiceLabels.Num() != ChoiceLabelsPtr.Num()) - { - bNeedUpdate = true; - } - else - { - for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) - { - if (ChoiceLabelsPtr[Idx].Get() == &(StringChoiceLabels[Idx])) - continue; - - bNeedUpdate = true; - break; - } - } - - if (!bNeedUpdate) - return true; - */ - - // Updates the Label Ptr array - ChoiceLabelsPtr.SetNumZeroed(StringChoiceLabels.Num()); - for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) - { - ChoiceLabelsPtr[Idx] = MakeShared(StringChoiceLabels[Idx]); - } - - return true; -} - -bool -UHoudiniParameterChoice::SetIntValue(const int32& InIntValue) -{ - if (InIntValue == IntValue) - return false; - - IntValue = InIntValue; - - return true; -} - -bool -UHoudiniParameterChoice::SetStringValue(const FString& InStringValue) -{ - if (InStringValue.Equals(StringValue)) - return false; - - StringValue = InStringValue; - - return true; -}; - -bool -UHoudiniParameterChoice::UpdateIntValueFromString() -{ - int32 FoundInt = INDEX_NONE; - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - // Update the int values from the string value - FoundInt = StringChoiceLabels.Find(StringValue); - } - else - { - // Update the int values from the string value - FoundInt = StringChoiceValues.Find(StringValue); - } - - if (FoundInt == INDEX_NONE) - return false; - - if (IntValue == FoundInt) - return false; - - IntValue = FoundInt; - - return true; -} - -bool -UHoudiniParameterChoice::UpdateStringValueFromInt() -{ - // Update the string value from the int value - FString NewStringValue; - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - // IntChoices only have labels - if (!StringChoiceLabels.IsValidIndex(IntValue)) - return false; - - NewStringValue = StringChoiceLabels[IntValue]; - } - else - { - // StringChoices should use values - if (!StringChoiceValues.IsValidIndex(IntValue)) - return false; - - NewStringValue = StringChoiceValues[IntValue]; - } - - if (StringValue.Equals(NewStringValue)) - return false; - - StringValue = NewStringValue; - - return true; -} - -const int32 -UHoudiniParameterChoice::GetIntValueFromLabel(const FString& InSelectedLabel) const -{ - return StringChoiceLabels.Find(InSelectedLabel); -} - -TOptional< TSharedPtr > -UHoudiniParameterChoice::GetValue(int32 Idx) const -{ - if (Idx == 0 && StringChoiceValues.IsValidIndex(IntValue)) - { - return TOptional< TSharedPtr< FString > >(MakeShared(StringChoiceValues[IntValue])); - } - - return TOptional< TSharedPtr< FString > >(); -} - -bool -UHoudiniParameterChoice::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - return IntValue == DefaultIntValue; - } - - if (GetParameterType() == EHoudiniParameterType::StringChoice) - { - return StringValue == DefaultStringValue; - } - - return true; -} - -void -UHoudiniParameterChoice::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterChoice.h" + +UHoudiniParameterChoice::UHoudiniParameterChoice(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , IntValue(-1) +{ + ParmType = EHoudiniParameterType::IntChoice; +} + +UHoudiniParameterChoice * +UHoudiniParameterChoice::Create( UObject* InOuter, const FString& InParamName, const EHoudiniParameterType& InParmType) +{ + FString ParamNameStr = "HoudiniParameterChoice_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterChoice::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterChoice * HoudiniAssetParameter = NewObject< UHoudiniParameterChoice >( + InOuter, UHoudiniParameterChoice::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(InParmType); + + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +void +UHoudiniParameterChoice::BeginDestroy() +{ + // We need to clean up our arrays + for (auto& Ptr : ChoiceLabelsPtr) + { + Ptr.Reset(); + } + ChoiceLabelsPtr.Empty(); + + // Then the string arrays + StringChoiceLabels.Empty(); + StringChoiceValues.Empty(); + + Super::BeginDestroy(); +} + + +FString* +UHoudiniParameterChoice::GetStringChoiceValueAt(const int32& InAtIndex) +{ + if (!StringChoiceValues.IsValidIndex(InAtIndex)) + return nullptr; + + return &(StringChoiceValues[InAtIndex]); +} + +FString* +UHoudiniParameterChoice::GetStringChoiceLabelAt(const int32& InAtIndex) +{ + if (!StringChoiceLabels.IsValidIndex(InAtIndex)) + return nullptr; + + return &(StringChoiceLabels[InAtIndex]); +} + +void +UHoudiniParameterChoice::SetNumChoices(const int32& InNumChoices) +{ + // Set the array sizes + StringChoiceValues.SetNumZeroed(InNumChoices); + StringChoiceLabels.SetNumZeroed(InNumChoices); + + UpdateChoiceLabelsPtr(); +} + +// Update the pointers to the ChoiceLabels +bool +UHoudiniParameterChoice::UpdateChoiceLabelsPtr() +{ + /* + bool bNeedUpdate = false; + if (StringChoiceLabels.Num() != ChoiceLabelsPtr.Num()) + { + bNeedUpdate = true; + } + else + { + for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) + { + if (ChoiceLabelsPtr[Idx].Get() == &(StringChoiceLabels[Idx])) + continue; + + bNeedUpdate = true; + break; + } + } + + if (!bNeedUpdate) + return true; + */ + + // Updates the Label Ptr array + ChoiceLabelsPtr.SetNumZeroed(StringChoiceLabels.Num()); + for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) + { + ChoiceLabelsPtr[Idx] = MakeShared(StringChoiceLabels[Idx]); + } + + return true; +} + +bool +UHoudiniParameterChoice::SetIntValue(const int32& InIntValue) +{ + if (InIntValue == IntValue) + return false; + + IntValue = InIntValue; + + return true; +} + +bool +UHoudiniParameterChoice::SetStringValue(const FString& InStringValue) +{ + if (InStringValue.Equals(StringValue)) + return false; + + StringValue = InStringValue; + + return true; +}; + +bool +UHoudiniParameterChoice::UpdateIntValueFromString() +{ + int32 FoundInt = INDEX_NONE; + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + // Update the int values from the string value + FoundInt = StringChoiceLabels.Find(StringValue); + } + else + { + // Update the int values from the string value + FoundInt = StringChoiceValues.Find(StringValue); + } + + if (FoundInt == INDEX_NONE) + return false; + + if (IntValue == FoundInt) + return false; + + IntValue = FoundInt; + + return true; +} + +bool +UHoudiniParameterChoice::UpdateStringValueFromInt() +{ + // Update the string value from the int value + FString NewStringValue; + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + // IntChoices only have labels + if (!StringChoiceLabels.IsValidIndex(IntValue)) + return false; + + NewStringValue = StringChoiceLabels[IntValue]; + } + else + { + // StringChoices should use values + if (!StringChoiceValues.IsValidIndex(IntValue)) + return false; + + NewStringValue = StringChoiceValues[IntValue]; + } + + if (StringValue.Equals(NewStringValue)) + return false; + + StringValue = NewStringValue; + + return true; +} + +const int32 +UHoudiniParameterChoice::GetIntValueFromLabel(const FString& InSelectedLabel) const +{ + return StringChoiceLabels.Find(InSelectedLabel); +} + +TOptional< TSharedPtr > +UHoudiniParameterChoice::GetValue(int32 Idx) const +{ + if (Idx == 0 && StringChoiceValues.IsValidIndex(IntValue)) + { + return TOptional< TSharedPtr< FString > >(MakeShared(StringChoiceValues[IntValue])); + } + + return TOptional< TSharedPtr< FString > >(); +} + +bool +UHoudiniParameterChoice::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + return IntValue == DefaultIntValue; + } + + if (GetParameterType() == EHoudiniParameterType::StringChoice) + { + return StringValue == DefaultStringValue; + } + + return true; +} + +void +UHoudiniParameterChoice::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h index 54804876f..2c91cb0ea 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h @@ -1,125 +1,125 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterChoice.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create an instance of this class. - static UHoudiniParameterChoice * Create( - UObject* Outer, - const FString& ParamName, - const EHoudiniParameterType& ParmType); - - virtual void BeginDestroy() override; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - const int32 GetIntValue() const { return IntValue; }; - const FString GetStringValue() const { return StringValue; }; - const int32 GetNumChoices() const { return StringChoiceLabels.Num(); }; - const FString GetLabel() const { return StringChoiceLabels.IsValidIndex(IntValue) ? StringChoiceLabels[IntValue] : FString(); }; - const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; - - bool IsDefault() const override; - - TOptional> GetValue(int32 Idx) const; - - const int32 GetIntValueFromLabel(const FString& InSelectedLabel) const; - - FString* GetStringChoiceValueAt(const int32& InAtIndex); - FString* GetStringChoiceLabelAt(const int32& InAtIndex); - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Returns the ChoiceLabel SharedPtr array, used for UI only - TArray>* GetChoiceLabelsPtr() { return &ChoiceLabelsPtr; }; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - bool SetIntValue(const int32& InIntValue); - bool SetStringValue(const FString& InStringValue); - void SetNumChoices(const int32& InNumChoices); - - // For string choices only, update the int values from the string value - bool UpdateIntValueFromString(); - // For int choices only, update the string value from the int value - bool UpdateStringValueFromInt(); - // Update the pointers to the ChoiceLabels - bool UpdateChoiceLabelsPtr(); - - void SetDefaultIntValue() { DefaultIntValue = IntValue; }; - void SetDefaultStringValue() { DefaultStringValue = StringValue; }; - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - void RevertToDefault() override; - -protected: - - // Current int value for this property. - UPROPERTY() - int32 IntValue; - - // Default int value for this property, assigned at creating the parameter. - UPROPERTY() - int32 DefaultIntValue; - - // Current string value for this property - UPROPERTY() - FString StringValue; - - // Default string value for this property, assigned at creating the parameter. - UPROPERTY() - FString DefaultStringValue; - - // Used only for StringChoices! - // All the possible string values for this parameter's choices - UPROPERTY() - TArray StringChoiceValues; - - // Labels corresponding to this parameter's choices. - UPROPERTY() - TArray StringChoiceLabels; - - // Array of SharedPtr pointing to this parameter's label, used for UI only - TArray> ChoiceLabelsPtr; - - UPROPERTY() - bool bIsChildOfRamp; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterChoice.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create an instance of this class. + static UHoudiniParameterChoice * Create( + UObject* Outer, + const FString& ParamName, + const EHoudiniParameterType& ParmType); + + virtual void BeginDestroy() override; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + const int32 GetIntValue() const { return IntValue; }; + const FString GetStringValue() const { return StringValue; }; + const int32 GetNumChoices() const { return StringChoiceLabels.Num(); }; + const FString GetLabel() const { return StringChoiceLabels.IsValidIndex(IntValue) ? StringChoiceLabels[IntValue] : FString(); }; + const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; + + bool IsDefault() const override; + + TOptional> GetValue(int32 Idx) const; + + const int32 GetIntValueFromLabel(const FString& InSelectedLabel) const; + + FString* GetStringChoiceValueAt(const int32& InAtIndex); + FString* GetStringChoiceLabelAt(const int32& InAtIndex); + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Returns the ChoiceLabel SharedPtr array, used for UI only + TArray>* GetChoiceLabelsPtr() { return &ChoiceLabelsPtr; }; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + bool SetIntValue(const int32& InIntValue); + bool SetStringValue(const FString& InStringValue); + void SetNumChoices(const int32& InNumChoices); + + // For string choices only, update the int values from the string value + bool UpdateIntValueFromString(); + // For int choices only, update the string value from the int value + bool UpdateStringValueFromInt(); + // Update the pointers to the ChoiceLabels + bool UpdateChoiceLabelsPtr(); + + void SetDefaultIntValue() { DefaultIntValue = IntValue; }; + void SetDefaultStringValue() { DefaultStringValue = StringValue; }; + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + void RevertToDefault() override; + +protected: + + // Current int value for this property. + UPROPERTY() + int32 IntValue; + + // Default int value for this property, assigned at creating the parameter. + UPROPERTY() + int32 DefaultIntValue; + + // Current string value for this property + UPROPERTY() + FString StringValue; + + // Default string value for this property, assigned at creating the parameter. + UPROPERTY() + FString DefaultStringValue; + + // Used only for StringChoices! + // All the possible string values for this parameter's choices + UPROPERTY() + TArray StringChoiceValues; + + // Labels corresponding to this parameter's choices. + UPROPERTY() + TArray StringChoiceLabels; + + // Array of SharedPtr pointing to this parameter's label, used for UI only + TArray> ChoiceLabelsPtr; + + UPROPERTY() + bool bIsChildOfRamp; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp index 0af1918e0..a77d5cf41 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp @@ -1,84 +1,84 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterColor.h" - -UHoudiniParameterColor::UHoudiniParameterColor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Color( FLinearColor::White ) -{ - ParmType = EHoudiniParameterType::Color; -} - -UHoudiniParameterColor * -UHoudiniParameterColor::Create( UObject* InOuter, const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterColor_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterColor::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterColor * HoudiniAssetParameter = NewObject< UHoudiniParameterColor >( - InOuter, UHoudiniParameterColor::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Color); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->bIsChildOfRamp = false; - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterColor::SetColorValue(const FLinearColor& InColor) -{ - if (InColor == Color) - return false; - - Color = InColor; - - return true; -} - -bool -UHoudiniParameterColor::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - return Color == DefaultColor; -} - -void -UHoudiniParameterColor::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterColor.h" + +UHoudiniParameterColor::UHoudiniParameterColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Color( FLinearColor::White ) +{ + ParmType = EHoudiniParameterType::Color; +} + +UHoudiniParameterColor * +UHoudiniParameterColor::Create( UObject* InOuter, const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterColor_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterColor::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterColor * HoudiniAssetParameter = NewObject< UHoudiniParameterColor >( + InOuter, UHoudiniParameterColor::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Color); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->bIsChildOfRamp = false; + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterColor::SetColorValue(const FLinearColor& InColor) +{ + if (InColor == Color) + return false; + + Color = InColor; + + return true; +} + +bool +UHoudiniParameterColor::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + return Color == DefaultColor; +} + +void +UHoudiniParameterColor::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h index 20e73e131..3703524f0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h @@ -1,73 +1,73 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterColor.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterColor : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - - public: - - // Create instance of this class. - static UHoudiniParameterColor * Create( - UObject* Outer, - const FString& ParamName); - - // Accessor - FLinearColor GetColorValue() const { return Color; }; - - bool IsDefault() const override; - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Mutators - bool SetColorValue(const FLinearColor& InColor); - - void SetDefaultValue() { DefaultColor = Color; }; - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - void RevertToDefault() override; - - protected: - - // Color for this property. - UPROPERTY() - FLinearColor Color; - - // Default color for this property - UPROPERTY() - FLinearColor DefaultColor; - - UPROPERTY() - bool bIsChildOfRamp; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterColor.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterColor : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + + public: + + // Create instance of this class. + static UHoudiniParameterColor * Create( + UObject* Outer, + const FString& ParamName); + + // Accessor + FLinearColor GetColorValue() const { return Color; }; + + bool IsDefault() const override; + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Mutators + bool SetColorValue(const FLinearColor& InColor); + + void SetDefaultValue() { DefaultColor = Color; }; + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + void RevertToDefault() override; + + protected: + + // Color for this property. + UPROPERTY() + FLinearColor Color; + + // Default color for this property + UPROPERTY() + FLinearColor DefaultColor; + + UPROPERTY() + bool bIsChildOfRamp; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp index 9f89f52ee..97b5f7992 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp @@ -1,101 +1,101 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFile.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" - -#include "Misc/Paths.h" - -UHoudiniParameterFile::UHoudiniParameterFile(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Filters() - , bIsReadOnly(false) -{ - ParmType = EHoudiniParameterType::File; -} - -UHoudiniParameterFile * -UHoudiniParameterFile::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFile_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFile::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFile * HoudiniAssetParameter = NewObject< UHoudiniParameterFile >( - InOuter, UHoudiniParameterFile::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::File); - - return HoudiniAssetParameter; -} - - -bool -UHoudiniParameterFile::SetValueAt(const FString& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index] == InValue) - return false; - - Values[Index] = InValue; - - return true; -} - -bool -UHoudiniParameterFile::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!DefaultValues.IsValidIndex(Idx)) - break; - - if (Values[Idx] != DefaultValues[Idx]) - return false; - } - - return true; -} - -void -UHoudiniParameterFile::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFile.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" + +#include "Misc/Paths.h" + +UHoudiniParameterFile::UHoudiniParameterFile(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Filters() + , bIsReadOnly(false) +{ + ParmType = EHoudiniParameterType::File; +} + +UHoudiniParameterFile * +UHoudiniParameterFile::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFile_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFile::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFile * HoudiniAssetParameter = NewObject< UHoudiniParameterFile >( + InOuter, UHoudiniParameterFile::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::File); + + return HoudiniAssetParameter; +} + + +bool +UHoudiniParameterFile::SetValueAt(const FString& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index] == InValue) + return false; + + Values[Index] = InValue; + + return true; +} + +bool +UHoudiniParameterFile::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!DefaultValues.IsValidIndex(Idx)) + break; + + if (Values[Idx] != DefaultValues[Idx]) + return false; + } + + return true; +} + +void +UHoudiniParameterFile::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h index ca7f19e15..3e8bf344c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h @@ -1,78 +1,78 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFile.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFile : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFile * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetFileFilters() const { return Filters; }; - bool IsReadOnly() const { return bIsReadOnly; }; - FString GetValueAt(int32 Index) { return Values[Index]; }; - int32 GetNumValues() { return Values.Num(); }; - - void SetNumberOfValues(const uint32& NumValues) { Values.SetNum(NumValues); }; - bool SetValueAt(const FString& InValue, const uint32& Index); - - bool IsDefault() const override; - - // Mutators - void SetFileFilters(const FString& InFilters) { Filters = InFilters; }; - void SetReadOnly(const bool& InReadOnly) { bIsReadOnly = InReadOnly; }; - - void SetDefaultValues(); - -protected: - - // Values of this property. - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; - - // Filters of this property. - UPROPERTY() - FString Filters; - - // Is the file parameter read-only? - UPROPERTY() - bool bIsReadOnly; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFile.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFile : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFile * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetFileFilters() const { return Filters; }; + bool IsReadOnly() const { return bIsReadOnly; }; + FString GetValueAt(int32 Index) { return Values[Index]; }; + int32 GetNumValues() { return Values.Num(); }; + + void SetNumberOfValues(const uint32& NumValues) { Values.SetNum(NumValues); }; + bool SetValueAt(const FString& InValue, const uint32& Index); + + bool IsDefault() const override; + + // Mutators + void SetFileFilters(const FString& InFilters) { Filters = InFilters; }; + void SetReadOnly(const bool& InReadOnly) { bIsReadOnly = InReadOnly; }; + + void SetDefaultValues(); + +protected: + + // Values of this property. + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; + + // Filters of this property. + UPROPERTY() + FString Filters; + + // Is the file parameter read-only? + UPROPERTY() + bool bIsReadOnly; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp index ad7780d87..fb2361814 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp @@ -1,158 +1,158 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFloat.h" - -UHoudiniParameterFloat::UHoudiniParameterFloat(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Unit(TEXT("")) - , bNoSwap(false) - , bHasMin(false) - , bHasMax(false) - , bHasUIMin(false) - , bHasUIMax(false) - , bIsLogarithmic(false) - , Min(TNumericLimits::Lowest()) - , Max(TNumericLimits::Max()) - , UIMin(TNumericLimits::Lowest()) - , UIMax(TNumericLimits::Max()) -{ - ParmType = EHoudiniParameterType::Float; -} - -UHoudiniParameterFloat * -UHoudiniParameterFloat::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFloat_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFloat::StaticClass(), *ParamNameStr); - // We need to create a new parameter - UHoudiniParameterFloat * HoudiniAssetParameter = NewObject< UHoudiniParameterFloat >( - InOuter, UHoudiniParameterFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); - - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Float); - - HoudiniAssetParameter->bIsChildOfRamp = false; - HoudiniAssetParameter->bIsLogarithmic = false; - return HoudiniAssetParameter; -} - -TOptional< float > -UHoudiniParameterFloat::GetValue(int32 Idx) const -{ - if (Values.IsValidIndex(Idx)) - return TOptional< float >(Values[Idx]); - else - return TOptional< float >(); -} - -bool -UHoudiniParameterFloat::SetValueAt(const float& InValue, const int32& AtIndex) -{ - if (!Values.IsValidIndex(AtIndex)) - return false; - - if (InValue == Values[AtIndex]) - return false; - - Values[AtIndex] = FMath::Clamp< float >(InValue, Min, Max); - - return true; -} - -void -UHoudiniParameterFloat::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterFloat::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterFloat::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; -} - -void -UHoudiniParameterFloat::SetValue(float InValue, int32 Idx) -{ - if (!Values.IsValidIndex(Idx)) - return; - - if (InValue == Values[Idx]) - return; - - Values[Idx] = FMath::Clamp< float >(InValue, Min, Max); -} - -void -UHoudiniParameterFloat::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } -} - -void -UHoudiniParameterFloat::RevertToDefault(const int32& TupleIndex) -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.AddUnique(TupleIndex); - - MarkChanged(true); -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFloat.h" + +UHoudiniParameterFloat::UHoudiniParameterFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Unit(TEXT("")) + , bNoSwap(false) + , bHasMin(false) + , bHasMax(false) + , bHasUIMin(false) + , bHasUIMax(false) + , bIsLogarithmic(false) + , Min(TNumericLimits::Lowest()) + , Max(TNumericLimits::Max()) + , UIMin(TNumericLimits::Lowest()) + , UIMax(TNumericLimits::Max()) +{ + ParmType = EHoudiniParameterType::Float; +} + +UHoudiniParameterFloat * +UHoudiniParameterFloat::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFloat_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFloat::StaticClass(), *ParamNameStr); + // We need to create a new parameter + UHoudiniParameterFloat * HoudiniAssetParameter = NewObject< UHoudiniParameterFloat >( + InOuter, UHoudiniParameterFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); + + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Float); + + HoudiniAssetParameter->bIsChildOfRamp = false; + HoudiniAssetParameter->bIsLogarithmic = false; + return HoudiniAssetParameter; +} + +TOptional< float > +UHoudiniParameterFloat::GetValue(int32 Idx) const +{ + if (Values.IsValidIndex(Idx)) + return TOptional< float >(Values[Idx]); + else + return TOptional< float >(); +} + +bool +UHoudiniParameterFloat::SetValueAt(const float& InValue, const int32& AtIndex) +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + if (InValue == Values[AtIndex]) + return false; + + Values[AtIndex] = FMath::Clamp< float >(InValue, Min, Max); + + return true; +} + +void +UHoudiniParameterFloat::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterFloat::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterFloat::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; +} + +void +UHoudiniParameterFloat::SetValue(float InValue, int32 Idx) +{ + if (!Values.IsValidIndex(Idx)) + return; + + if (InValue == Values[Idx]) + return; + + Values[Idx] = FMath::Clamp< float >(InValue, Min, Max); +} + +void +UHoudiniParameterFloat::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } +} + +void +UHoudiniParameterFloat::RevertToDefault(const int32& TupleIndex) +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.AddUnique(TupleIndex); + + MarkChanged(true); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h index 2b95865fe..38291bd04 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h @@ -1,165 +1,165 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFloat.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFloat : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFloat * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetUnit() const { return Unit; }; - bool GetNoSwap() const { return bNoSwap; }; - - bool HasMin() const { return bHasMin; }; - bool HasMax() const { return bHasMax; }; - bool HasUIMin() const { return bHasUIMin; }; - bool HasUIMax() const { return bHasUIMax; }; - bool IsLogarithmic() const { return bIsLogarithmic; }; - - float GetMin() const { return Min; }; - float GetMax() const { return Max; }; - float GetUIMin() const { return UIMin; }; - float GetUIMax() const { return UIMax; }; - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Check if current value at Index is the default - bool IsDefaultValueAtIndex(const int32& Idx) const; - - bool IsDefault() const override; - - // Get value of this property - TOptional< float > GetValue(int32 Idx) const; - - // Write access to the value array - float* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - // Mutators - void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; - void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; - void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; - void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; - void SetIsLogarithmic(const bool& bInLog) { bIsLogarithmic = bInLog; }; - - void SetMin(const float& InMin) { Min = InMin; }; - void SetMax(const float& InMax) { Max = InMax; }; - void SetUIMin(const float& InUIMin) { UIMin = InUIMin; }; - void SetUIMax(const float& InUIMax) { UIMax = InUIMax; }; - - void SetUnit(const FString& InUnit) { Unit = InUnit; }; - void SetNoSwap(const bool& InNoSwap) { bNoSwap = InNoSwap; }; - - void SetDefaultValues(); - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - bool SetValueAt(const float& InValue, const int32& AtIndex); - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - /** Set value of this property, used by Slate. **/ - void SetValue(float InValue, int32 Idx); - - void RevertToDefault() override; - void RevertToDefault(const int32& TupleIndex) override; - -#if WITH_EDITOR - void SwitchUniformLock() { bUniformLocked = !bUniformLocked; }; - bool IsUniformLocked() const { return bUniformLocked; }; -#endif - -protected: - - // Float Values - UPROPERTY() - TArray Values; - - // Default float values, assigned at creating the parameter - UPROPERTY() - TArray DefaultValues; - - // Unit for this property - UPROPERTY() - FString Unit; - - // Do we have the noswap tag? - UPROPERTY() - bool bNoSwap; - - // Indicates we have a min/max value - UPROPERTY() - bool bHasMin; - // - UPROPERTY() - bool bHasMax; - - // Indicates we have a UI min/max - UPROPERTY() - bool bHasUIMin; - // - UPROPERTY() - bool bHasUIMax; - - UPROPERTY() - bool bIsLogarithmic; - - // Min and Max values for this property. - UPROPERTY() - float Min; - // - UPROPERTY() - float Max; - - // Min and Max values of this property for slider UI - UPROPERTY() - float UIMin; - // - UPROPERTY() - float UIMax; - - UPROPERTY() - bool bIsChildOfRamp; - -#if WITH_EDITORONLY_DATA - // Indicates whether the float VEC change value uniformly - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - bool bUniformLocked; -#endif +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFloat.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFloat : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFloat * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetUnit() const { return Unit; }; + bool GetNoSwap() const { return bNoSwap; }; + + bool HasMin() const { return bHasMin; }; + bool HasMax() const { return bHasMax; }; + bool HasUIMin() const { return bHasUIMin; }; + bool HasUIMax() const { return bHasUIMax; }; + bool IsLogarithmic() const { return bIsLogarithmic; }; + + float GetMin() const { return Min; }; + float GetMax() const { return Max; }; + float GetUIMin() const { return UIMin; }; + float GetUIMax() const { return UIMax; }; + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Check if current value at Index is the default + bool IsDefaultValueAtIndex(const int32& Idx) const; + + bool IsDefault() const override; + + // Get value of this property + TOptional< float > GetValue(int32 Idx) const; + + // Write access to the value array + float* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + // Mutators + void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; + void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; + void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; + void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; + void SetIsLogarithmic(const bool& bInLog) { bIsLogarithmic = bInLog; }; + + void SetMin(const float& InMin) { Min = InMin; }; + void SetMax(const float& InMax) { Max = InMax; }; + void SetUIMin(const float& InUIMin) { UIMin = InUIMin; }; + void SetUIMax(const float& InUIMax) { UIMax = InUIMax; }; + + void SetUnit(const FString& InUnit) { Unit = InUnit; }; + void SetNoSwap(const bool& InNoSwap) { bNoSwap = InNoSwap; }; + + void SetDefaultValues(); + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + bool SetValueAt(const float& InValue, const int32& AtIndex); + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + /** Set value of this property, used by Slate. **/ + void SetValue(float InValue, int32 Idx); + + void RevertToDefault() override; + void RevertToDefault(const int32& TupleIndex) override; + +#if WITH_EDITOR + void SwitchUniformLock() { bUniformLocked = !bUniformLocked; }; + bool IsUniformLocked() const { return bUniformLocked; }; +#endif + +protected: + + // Float Values + UPROPERTY() + TArray Values; + + // Default float values, assigned at creating the parameter + UPROPERTY() + TArray DefaultValues; + + // Unit for this property + UPROPERTY() + FString Unit; + + // Do we have the noswap tag? + UPROPERTY() + bool bNoSwap; + + // Indicates we have a min/max value + UPROPERTY() + bool bHasMin; + // + UPROPERTY() + bool bHasMax; + + // Indicates we have a UI min/max + UPROPERTY() + bool bHasUIMin; + // + UPROPERTY() + bool bHasUIMax; + + UPROPERTY() + bool bIsLogarithmic; + + // Min and Max values for this property. + UPROPERTY() + float Min; + // + UPROPERTY() + float Max; + + // Min and Max values of this property for slider UI + UPROPERTY() + float UIMin; + // + UPROPERTY() + float UIMax; + + UPROPERTY() + bool bIsChildOfRamp; + +#if WITH_EDITORONLY_DATA + // Indicates whether the float VEC change value uniformly + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + bool bUniformLocked; +#endif }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp index 3beff2598..e2b9d79b4 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp @@ -1,52 +1,52 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFolder.h" - -UHoudiniParameterFolder::UHoudiniParameterFolder(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - ,bExpanded(true) - ,bChosen(false) -{ - ParmType = EHoudiniParameterType::Folder; -} - -UHoudiniParameterFolder * -UHoudiniParameterFolder::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFolder_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolder::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFolder * HoudiniAssetParameter = NewObject< UHoudiniParameterFolder >( - InOuter, UHoudiniParameterFolder::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Folder); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFolder.h" + +UHoudiniParameterFolder::UHoudiniParameterFolder(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + ,bExpanded(true) + ,bChosen(false) +{ + ParmType = EHoudiniParameterType::Folder; +} + +UHoudiniParameterFolder * +UHoudiniParameterFolder::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFolder_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolder::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFolder * HoudiniAssetParameter = NewObject< UHoudiniParameterFolder >( + InOuter, UHoudiniParameterFolder::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Folder); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h index ce08b2b6f..a75f3c87a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h @@ -1,110 +1,110 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFolder.generated.h" - -UENUM() -enum class EHoudiniFolderParameterType : uint8 -{ - Invalid, - - Collapsible, - Simple, - Tabs, - Radio, - Other, -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFolder : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFolder * Create( - UObject* Outer, - const FString& ParamName); - - FORCEINLINE - EHoudiniFolderParameterType GetFolderType() const { return FolderType; }; - - FORCEINLINE - void SetFolderType(EHoudiniFolderParameterType Type) { FolderType = Type; }; - - FORCEINLINE - void SetExpanded(const bool InExpanded) { bExpanded = InExpanded; }; - FORCEINLINE - bool IsExpanded() const { return bExpanded; }; - FORCEINLINE - void ExpandButtonClicked() { bExpanded = !bExpanded; }; - - FORCEINLINE - void SetChosen(const bool InChosen) { bChosen = InChosen; }; - FORCEINLINE - bool IsChosen() const { return bChosen; }; - - FORCEINLINE - bool IsTab() const { return FolderType == EHoudiniFolderParameterType::Tabs || FolderType == EHoudiniFolderParameterType::Radio; }; - - - FORCEINLINE - void ResetChildCounter() { ChildCounter = TupleSize; } - - FORCEINLINE - int32& GetChildCounter() { return ChildCounter; }; - - FORCEINLINE - bool IsContentShown() const { return bIsContentShown; }; - - FORCEINLINE - void SetIsContentShown(const bool& bInShown) { bIsContentShown = bInShown; }; - - - -private: - UPROPERTY() - EHoudiniFolderParameterType FolderType; - - UPROPERTY() - bool bExpanded; - - UPROPERTY() - bool bChosen; - - - UPROPERTY() - int32 ChildCounter; - - UPROPERTY() - bool bIsContentShown; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFolder.generated.h" + +UENUM() +enum class EHoudiniFolderParameterType : uint8 +{ + Invalid, + + Collapsible, + Simple, + Tabs, + Radio, + Other, +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFolder : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFolder * Create( + UObject* Outer, + const FString& ParamName); + + FORCEINLINE + EHoudiniFolderParameterType GetFolderType() const { return FolderType; }; + + FORCEINLINE + void SetFolderType(EHoudiniFolderParameterType Type) { FolderType = Type; }; + + FORCEINLINE + void SetExpanded(const bool InExpanded) { bExpanded = InExpanded; }; + FORCEINLINE + bool IsExpanded() const { return bExpanded; }; + FORCEINLINE + void ExpandButtonClicked() { bExpanded = !bExpanded; }; + + FORCEINLINE + void SetChosen(const bool InChosen) { bChosen = InChosen; }; + FORCEINLINE + bool IsChosen() const { return bChosen; }; + + FORCEINLINE + bool IsTab() const { return FolderType == EHoudiniFolderParameterType::Tabs || FolderType == EHoudiniFolderParameterType::Radio; }; + + + FORCEINLINE + void ResetChildCounter() { ChildCounter = TupleSize; } + + FORCEINLINE + int32& GetChildCounter() { return ChildCounter; }; + + FORCEINLINE + bool IsContentShown() const { return bIsContentShown; }; + + FORCEINLINE + void SetIsContentShown(const bool& bInShown) { bIsContentShown = bInShown; }; + + + +private: + UPROPERTY() + EHoudiniFolderParameterType FolderType; + + UPROPERTY() + bool bExpanded; + + UPROPERTY() + bool bChosen; + + + UPROPERTY() + int32 ChildCounter; + + UPROPERTY() + bool bIsContentShown; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp index 8ad011670..84d383c0f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp @@ -1,108 +1,108 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterFolder.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -UHoudiniParameterFolderList::UHoudiniParameterFolderList(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), bIsTabMenu(false), bIsTabsShown(false) -{ - ParmType = EHoudiniParameterType::FolderList; -} - -UHoudiniParameterFolderList * -UHoudiniParameterFolderList::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFolderList_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolderList::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFolderList * HoudiniAssetParameter = NewObject< UHoudiniParameterFolderList >( - InOuter, UHoudiniParameterFolderList::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::FolderList); - - HoudiniAssetParameter->bIsTabMenu = false; - return HoudiniAssetParameter; -} - -void -UHoudiniParameterFolderList::AddTabFolder(UHoudiniParameterFolder* InFolderParm) -{ - TabFolders.Add(InFolderParm); -} - -bool -UHoudiniParameterFolderList::IsTabParseFinished() const -{ - for (auto & CurTab : TabFolders) - { - if (!CurTab || CurTab->IsPendingKill()) - continue; - - if (!CurTab->IsTab()) - continue; - - // Go through visible tab only - if (!CurTab->IsChosen()) - continue; - - if (CurTab->GetChildCounter() > 0) - return false; - } - - return true; -} - -void -UHoudiniParameterFolderList::RemapParameters(const TMap& InputMapping) -{ - const int32 NumFolders = TabFolders.Num(); - for (int i = 0; i < NumFolders; i++) - { - UHoudiniParameter* FromParameter = TabFolders[i]; - - if (!FromParameter) - continue; - - UHoudiniParameterFolder* ToParameter = nullptr; - if (InputMapping.Contains(FromParameter)) - { - ToParameter = Cast(InputMapping.FindRef(FromParameter)); - } - - if (!ToParameter) - { - HOUDINI_LOG_WARNING(TEXT("[UHoudiniParameterFolderList::RemapParameters] Could not find mapping for existing parameter %s (%s)."), *(FromParameter->GetParameterName()), *(FromParameter->GetPathName()) ); - } - - TabFolders[i] = ToParameter; - } -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterFolder.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +UHoudiniParameterFolderList::UHoudiniParameterFolderList(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), bIsTabMenu(false), bIsTabsShown(false) +{ + ParmType = EHoudiniParameterType::FolderList; +} + +UHoudiniParameterFolderList * +UHoudiniParameterFolderList::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFolderList_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolderList::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFolderList * HoudiniAssetParameter = NewObject< UHoudiniParameterFolderList >( + InOuter, UHoudiniParameterFolderList::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::FolderList); + + HoudiniAssetParameter->bIsTabMenu = false; + return HoudiniAssetParameter; +} + +void +UHoudiniParameterFolderList::AddTabFolder(UHoudiniParameterFolder* InFolderParm) +{ + TabFolders.Add(InFolderParm); +} + +bool +UHoudiniParameterFolderList::IsTabParseFinished() const +{ + for (auto & CurTab : TabFolders) + { + if (!CurTab || CurTab->IsPendingKill()) + continue; + + if (!CurTab->IsTab()) + continue; + + // Go through visible tab only + if (!CurTab->IsChosen()) + continue; + + if (CurTab->GetChildCounter() > 0) + return false; + } + + return true; +} + +void +UHoudiniParameterFolderList::RemapParameters(const TMap& InputMapping) +{ + const int32 NumFolders = TabFolders.Num(); + for (int i = 0; i < NumFolders; i++) + { + UHoudiniParameter* FromParameter = TabFolders[i]; + + if (!FromParameter) + continue; + + UHoudiniParameterFolder* ToParameter = nullptr; + if (InputMapping.Contains(FromParameter)) + { + ToParameter = Cast(InputMapping.FindRef(FromParameter)); + } + + if (!ToParameter) + { + HOUDINI_LOG_WARNING(TEXT("[UHoudiniParameterFolderList::RemapParameters] Could not find mapping for existing parameter %s (%s)."), *(FromParameter->GetParameterName()), *(FromParameter->GetPathName()) ); + } + + TabFolders[i] = ToParameter; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h index a4ea6532e..61358252d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h @@ -1,81 +1,81 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFolderList.generated.h" - -class UHoudiniParameterFolder; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFolderList : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFolderList * Create( - UObject* Outer, - const FString& ParamName); - - void AddTabFolder(UHoudiniParameterFolder* InFolderParm); - - FORCEINLINE - TArray& GetTabs() { return TabFolders; }; - - FORCEINLINE - bool IsTabMenu() const { return bIsTabMenu; }; - - FORCEINLINE - void SetIsTabMenu(const bool InIsTabMenu) { bIsTabMenu = InIsTabMenu; }; - - FORCEINLINE - bool IsTabsShown() const { return bIsTabsShown; }; - - FORCEINLINE - void SetTabsShown(const bool& bInTabsShown) { bIsTabsShown = bInTabsShown; }; - - bool IsTabParseFinished() const; - - UPROPERTY() - bool bIsTabMenu; - - UPROPERTY() - bool bIsTabsShown; - - UPROPERTY() - TArray TabFolders; - - - //------------------------------------------------------------------------------------------------ - // UHoudiniParameter overrides - //------------------------------------------------------------------------------------------------ - virtual void RemapParameters(const TMap& InputMapping) override; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFolderList.generated.h" + +class UHoudiniParameterFolder; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFolderList : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFolderList * Create( + UObject* Outer, + const FString& ParamName); + + void AddTabFolder(UHoudiniParameterFolder* InFolderParm); + + FORCEINLINE + TArray& GetTabs() { return TabFolders; }; + + FORCEINLINE + bool IsTabMenu() const { return bIsTabMenu; }; + + FORCEINLINE + void SetIsTabMenu(const bool InIsTabMenu) { bIsTabMenu = InIsTabMenu; }; + + FORCEINLINE + bool IsTabsShown() const { return bIsTabsShown; }; + + FORCEINLINE + void SetTabsShown(const bool& bInTabsShown) { bIsTabsShown = bInTabsShown; }; + + bool IsTabParseFinished() const; + + UPROPERTY() + bool bIsTabMenu; + + UPROPERTY() + bool bIsTabsShown; + + UPROPERTY() + TArray TabFolders; + + + //------------------------------------------------------------------------------------------------ + // UHoudiniParameter overrides + //------------------------------------------------------------------------------------------------ + virtual void RemapParameters(const TMap& InputMapping) override; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp index e7015190c..81a99fdd5 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp @@ -1,119 +1,119 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterInt.h" - -UHoudiniParameterInt::UHoudiniParameterInt(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Unit() - , bHasMin(false) - , bHasMax(false) - , bHasUIMin(false) - , bHasUIMax(false) - , bIsLogarithmic(false) - , Min(0) - , Max(0) - , UIMin(0) - , UIMax(0) -{ - ParmType = EHoudiniParameterType::Int; -} - -UHoudiniParameterInt * -UHoudiniParameterInt::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterInt_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterInt::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterInt * HoudiniAssetParameter = NewObject< UHoudiniParameterInt >( - InOuter, UHoudiniParameterInt::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Int); - HoudiniAssetParameter->bIsLogarithmic = false; - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -TOptional -UHoudiniParameterInt::GetValue(int32 Idx) const -{ - if (Values.IsValidIndex(Idx)) - return TOptional(Values[Idx]); - else - return TOptional(); -} - -bool -UHoudiniParameterInt::SetValueAt(const int32& InValue, const int32& AtIndex) -{ - if (!Values.IsValidIndex(AtIndex)) - return false; - - if (InValue == Values[AtIndex]) - return false; - - Values[AtIndex] = FMath::Clamp< int32 >(InValue, Min, Max); - - return true; -} - -void -UHoudiniParameterInt::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterInt::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterInt::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterInt.h" + +UHoudiniParameterInt::UHoudiniParameterInt(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Unit() + , bHasMin(false) + , bHasMax(false) + , bHasUIMin(false) + , bHasUIMax(false) + , bIsLogarithmic(false) + , Min(0) + , Max(0) + , UIMin(0) + , UIMax(0) +{ + ParmType = EHoudiniParameterType::Int; +} + +UHoudiniParameterInt * +UHoudiniParameterInt::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterInt_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterInt::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterInt * HoudiniAssetParameter = NewObject< UHoudiniParameterInt >( + InOuter, UHoudiniParameterInt::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Int); + HoudiniAssetParameter->bIsLogarithmic = false; + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +TOptional +UHoudiniParameterInt::GetValue(int32 Idx) const +{ + if (Values.IsValidIndex(Idx)) + return TOptional(Values[Idx]); + else + return TOptional(); +} + +bool +UHoudiniParameterInt::SetValueAt(const int32& InValue, const int32& AtIndex) +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + if (InValue == Values[AtIndex]) + return false; + + Values[AtIndex] = FMath::Clamp< int32 >(InValue, Min, Max); + + return true; +} + +void +UHoudiniParameterInt::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterInt::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterInt::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h index 7b31b1d3e..a90b09e80 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h @@ -1,131 +1,131 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterInt.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterInt : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterInt * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetUnit() const { return Unit; }; - - bool HasMin() const { return bHasMin; }; - bool HasMax() const { return bHasMax; }; - bool HasUIMin() const { return bHasUIMin; }; - bool HasUIMax() const { return bHasUIMax; }; - bool IsLogarithmic() const { return bIsLogarithmic; }; - - int32 GetMin() const { return Min; }; - int32 GetMax() const { return Max; }; - int32 GetUIMin() const { return UIMin; }; - int32 GetUIMax() const { return UIMax; }; - - // Get value of this property - TOptional GetValue(int32 Idx) const; - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - bool IsDefaultValueAtIndex(const int32& Idx) const; - bool IsDefault() const override; - - // Mutators - void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; - void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; - void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; - void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; - - void SetMin(const int32& InMin) { Min = InMin; }; - void SetMax(const int32& InMax) { Max = InMax; }; - void SetUIMin(const int32& InUIMin) { UIMin = InUIMin; }; - void SetUIMax(const int32& InUIMax) { UIMax = InUIMax; }; - void SetIsLogarithmic(const bool& bInIsLog) { bIsLogarithmic = bInIsLog; }; - - void SetUnit(const FString& InUnit) { Unit = InUnit; }; - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - bool SetValueAt(const int32& InValue, const int32& AtIndex); - - void SetDefaultValues(); - -protected: - - // Int Values - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; - - // Unit for this property - UPROPERTY() - FString Unit; - - // Indicates we have a min/max value - UPROPERTY() - bool bHasMin; - // - UPROPERTY() - bool bHasMax; - - // Indicates we have a UI min/max - UPROPERTY() - bool bHasUIMin; - // - UPROPERTY() - bool bHasUIMax; - - UPROPERTY() - bool bIsLogarithmic; - - // Min and Max values for this property. - UPROPERTY() - int32 Min; - // - UPROPERTY() - int32 Max; - - // Min and Max values of this property for slider UI - UPROPERTY() - int32 UIMin; - // - UPROPERTY() - int32 UIMax; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterInt.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterInt : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterInt * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetUnit() const { return Unit; }; + + bool HasMin() const { return bHasMin; }; + bool HasMax() const { return bHasMax; }; + bool HasUIMin() const { return bHasUIMin; }; + bool HasUIMax() const { return bHasUIMax; }; + bool IsLogarithmic() const { return bIsLogarithmic; }; + + int32 GetMin() const { return Min; }; + int32 GetMax() const { return Max; }; + int32 GetUIMin() const { return UIMin; }; + int32 GetUIMax() const { return UIMax; }; + + // Get value of this property + TOptional GetValue(int32 Idx) const; + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + bool IsDefaultValueAtIndex(const int32& Idx) const; + bool IsDefault() const override; + + // Mutators + void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; + void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; + void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; + void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; + + void SetMin(const int32& InMin) { Min = InMin; }; + void SetMax(const int32& InMax) { Max = InMax; }; + void SetUIMin(const int32& InUIMin) { UIMin = InUIMin; }; + void SetUIMax(const int32& InUIMax) { UIMax = InUIMax; }; + void SetIsLogarithmic(const bool& bInIsLog) { bIsLogarithmic = bInIsLog; }; + + void SetUnit(const FString& InUnit) { Unit = InUnit; }; + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + bool SetValueAt(const int32& InValue, const int32& AtIndex); + + void SetDefaultValues(); + +protected: + + // Int Values + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; + + // Unit for this property + UPROPERTY() + FString Unit; + + // Indicates we have a min/max value + UPROPERTY() + bool bHasMin; + // + UPROPERTY() + bool bHasMax; + + // Indicates we have a UI min/max + UPROPERTY() + bool bHasUIMin; + // + UPROPERTY() + bool bHasUIMax; + + UPROPERTY() + bool bIsLogarithmic; + + // Min and Max values for this property. + UPROPERTY() + int32 Min; + // + UPROPERTY() + int32 Max; + + // Min and Max values of this property for slider UI + UPROPERTY() + int32 UIMin; + // + UPROPERTY() + int32 UIMax; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp index abbd27049..5cf1efc72 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp @@ -1,62 +1,62 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterLabel.h" - -UHoudiniParameterLabel::UHoudiniParameterLabel(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Label; -} - -UHoudiniParameterLabel * -UHoudiniParameterLabel::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterLabel_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterLabel::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterLabel * HoudiniAssetParameter = NewObject< UHoudiniParameterLabel >( - InOuter, UHoudiniParameterLabel::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Label); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -FString -UHoudiniParameterLabel::GetStringAtIndex(int32 Index) -{ - if (LabelStrings.IsValidIndex(Index)) - { - return LabelStrings[Index]; - } - - return FString(""); -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterLabel.h" + +UHoudiniParameterLabel::UHoudiniParameterLabel(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Label; +} + +UHoudiniParameterLabel * +UHoudiniParameterLabel::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterLabel_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterLabel::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterLabel * HoudiniAssetParameter = NewObject< UHoudiniParameterLabel >( + InOuter, UHoudiniParameterLabel::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Label); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +FString +UHoudiniParameterLabel::GetStringAtIndex(int32 Index) +{ + if (LabelStrings.IsValidIndex(Index)) + { + return LabelStrings[Index]; + } + + return FString(""); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h index fa9d13e72..99e469a5b 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h @@ -1,56 +1,56 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterLabel.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterLabel : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterLabel * Create( - UObject* Outer, - const FString& ParamName); - - UPROPERTY() - TArray LabelStrings; - - FORCEINLINE - void AddLabelString(FString &NewString) { LabelStrings.Add(NewString); }; - - FString GetStringAtIndex(int32 Index); - - FORCEINLINE - void EmptyLabelString() { LabelStrings.Empty(); }; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterLabel.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterLabel : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterLabel * Create( + UObject* Outer, + const FString& ParamName); + + UPROPERTY() + TArray LabelStrings; + + FORCEINLINE + void AddLabelString(FString &NewString) { LabelStrings.Add(NewString); }; + + FString GetStringAtIndex(int32 Index); + + FORCEINLINE + void EmptyLabelString() { LabelStrings.Empty(); }; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp index 396934443..92eb805a0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp @@ -1,144 +1,144 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterMultiParm.h" - -UHoudiniParameterMultiParm::UHoudiniParameterMultiParm(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), bIsShown(false), InstanceStartOffset(0) -{ - // TODO Proper Init - ParmType = EHoudiniParameterType::MultiParm; -} - -UHoudiniParameterMultiParm * -UHoudiniParameterMultiParm::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterMultiParm::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterMultiParm * HoudiniAssetParameter = NewObject< UHoudiniParameterMultiParm >( - InOuter, UHoudiniParameterMultiParm::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::MultiParm); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->DefaultInstanceCount = -1; - - return HoudiniAssetParameter; -} - - -void -UHoudiniParameterMultiParm::InsertElement() -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); -} - -void -UHoudiniParameterMultiParm::InsertElementAt(int32 Index) -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - if (Index >= MultiParmInstanceLastModifyArray.Num()) - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); - else - MultiParmInstanceLastModifyArray.Insert(EHoudiniMultiParmModificationType::Inserted, Index); -} - -/** Decrement value, used by Slate. **/ -void -UHoudiniParameterMultiParm::RemoveElement(int32 Index) -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - // Remove the last element - if (Index == -1) - { - Index = MultiParmInstanceLastModifyArray.Num() - 1; - while (MultiParmInstanceLastModifyArray.IsValidIndex(Index) && MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Removed) - Index -= 1; - } - - if (MultiParmInstanceLastModifyArray.IsValidIndex(Index)) - { - // If the removed is a to be inserted instance, simply remove it. - if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) - MultiParmInstanceLastModifyArray.RemoveAt(Index); - // Otherwise mark it as to be removed. - else - MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; - } -} - -void -UHoudiniParameterMultiParm::EmptyElements() -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - for (int32 Index = MultiParmInstanceLastModifyArray.Num() - 1; Index >= 0; --Index) - { - // If the removed is a to be inserted instance, simply remove it. - // Interation starts from the tail, so that the indices won't be changed by element removal. - if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) - MultiParmInstanceLastModifyArray.RemoveAt(Index); - else // Otherwise mark it as to be removed. - MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; - } -} - -void -UHoudiniParameterMultiParm::InitializeModifyArray() -{ - for (uint32 Index = 0; Index < MultiParmInstanceCount; ++Index) - { - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::None); - } -} - -bool -UHoudiniParameterMultiParm::IsDefault() const -{ - //UE_LOG(LogTemp, Warning, TEXT("%d, %d"), MultiParmInstanceNum, MultiParmInstanceCount); - return DefaultInstanceCount == MultiParmInstanceCount; -} - -void -UHoudiniParameterMultiParm::SetDefaultInstanceCount(int32 InCount) -{ - if (DefaultInstanceCount >= 0) - return; - - DefaultInstanceCount = InCount; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterMultiParm.h" + +UHoudiniParameterMultiParm::UHoudiniParameterMultiParm(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), bIsShown(false), InstanceStartOffset(0) +{ + // TODO Proper Init + ParmType = EHoudiniParameterType::MultiParm; +} + +UHoudiniParameterMultiParm * +UHoudiniParameterMultiParm::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterMultiParm::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterMultiParm * HoudiniAssetParameter = NewObject< UHoudiniParameterMultiParm >( + InOuter, UHoudiniParameterMultiParm::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::MultiParm); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->DefaultInstanceCount = -1; + + return HoudiniAssetParameter; +} + + +void +UHoudiniParameterMultiParm::InsertElement() +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); +} + +void +UHoudiniParameterMultiParm::InsertElementAt(int32 Index) +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + if (Index >= MultiParmInstanceLastModifyArray.Num()) + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); + else + MultiParmInstanceLastModifyArray.Insert(EHoudiniMultiParmModificationType::Inserted, Index); +} + +/** Decrement value, used by Slate. **/ +void +UHoudiniParameterMultiParm::RemoveElement(int32 Index) +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + // Remove the last element + if (Index == -1) + { + Index = MultiParmInstanceLastModifyArray.Num() - 1; + while (MultiParmInstanceLastModifyArray.IsValidIndex(Index) && MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Removed) + Index -= 1; + } + + if (MultiParmInstanceLastModifyArray.IsValidIndex(Index)) + { + // If the removed is a to be inserted instance, simply remove it. + if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) + MultiParmInstanceLastModifyArray.RemoveAt(Index); + // Otherwise mark it as to be removed. + else + MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; + } +} + +void +UHoudiniParameterMultiParm::EmptyElements() +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + for (int32 Index = MultiParmInstanceLastModifyArray.Num() - 1; Index >= 0; --Index) + { + // If the removed is a to be inserted instance, simply remove it. + // Interation starts from the tail, so that the indices won't be changed by element removal. + if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) + MultiParmInstanceLastModifyArray.RemoveAt(Index); + else // Otherwise mark it as to be removed. + MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; + } +} + +void +UHoudiniParameterMultiParm::InitializeModifyArray() +{ + for (uint32 Index = 0; Index < MultiParmInstanceCount; ++Index) + { + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::None); + } +} + +bool +UHoudiniParameterMultiParm::IsDefault() const +{ + //UE_LOG(LogTemp, Warning, TEXT("%d, %d"), MultiParmInstanceNum, MultiParmInstanceCount); + return DefaultInstanceCount == MultiParmInstanceCount; +} + +void +UHoudiniParameterMultiParm::SetDefaultInstanceCount(int32 InCount) +{ + if (DefaultInstanceCount >= 0) + return; + + DefaultInstanceCount = InCount; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h index fc4afae8b..226cdcdef 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h @@ -1,129 +1,129 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterMultiParm.generated.h" - -UENUM() -enum class EHoudiniMultiParmModificationType : uint8 -{ - None, - - Inserted, - Removed, - Modified -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterMultiParm * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FORCEINLINE - int32 GetValue() const { return Value; }; - FORCEINLINE - int32 GetInstanceCount() const { return MultiParmInstanceCount; }; - - // Mutators - FORCEINLINE - void SetValue(const int32& InValue) { Value = InValue; }; - FORCEINLINE - void SetInstanceCount(const int32 InCount) { MultiParmInstanceCount = InCount; }; - - FORCEINLINE - void SetIsShown(const bool InIsShown) { bIsShown = InIsShown; }; - - FORCEINLINE - bool IsShown() const { return bIsShown; }; - - - /** Increment value, used by Slate. **/ - void InsertElement(); - - void InsertElementAt(int32 Index); - - /** Decrement value, used by Slate. **/ - void RemoveElement(int32 Index); - - /** Empty the values, used by Slate. **/ - void EmptyElements(); - - UPROPERTY() - bool bIsShown; - - // Value of the multiparm - UPROPERTY() - int32 Value; - - // - UPROPERTY() - FString TemplateName; - - // Value of this property. - UPROPERTY() - int32 MultiparmValue; - - // - UPROPERTY() - uint32 MultiParmInstanceNum; - - // - UPROPERTY() - uint32 MultiParmInstanceLength; - - // - UPROPERTY() - uint32 MultiParmInstanceCount; - - UPROPERTY() - uint32 InstanceStartOffset; - - // This array records the last modified instance of the multiparm - UPROPERTY() - TArray MultiParmInstanceLastModifyArray; - - UPROPERTY() - int32 DefaultInstanceCount; - - bool IsDefault() const override; - - void SetDefaultInstanceCount(int32 InCount); - -private: - void InitializeModifyArray(); - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterMultiParm.generated.h" + +UENUM() +enum class EHoudiniMultiParmModificationType : uint8 +{ + None, + + Inserted, + Removed, + Modified +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterMultiParm * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FORCEINLINE + int32 GetValue() const { return Value; }; + FORCEINLINE + int32 GetInstanceCount() const { return MultiParmInstanceCount; }; + + // Mutators + FORCEINLINE + void SetValue(const int32& InValue) { Value = InValue; }; + FORCEINLINE + void SetInstanceCount(const int32 InCount) { MultiParmInstanceCount = InCount; }; + + FORCEINLINE + void SetIsShown(const bool InIsShown) { bIsShown = InIsShown; }; + + FORCEINLINE + bool IsShown() const { return bIsShown; }; + + + /** Increment value, used by Slate. **/ + void InsertElement(); + + void InsertElementAt(int32 Index); + + /** Decrement value, used by Slate. **/ + void RemoveElement(int32 Index); + + /** Empty the values, used by Slate. **/ + void EmptyElements(); + + UPROPERTY() + bool bIsShown; + + // Value of the multiparm + UPROPERTY() + int32 Value; + + // + UPROPERTY() + FString TemplateName; + + // Value of this property. + UPROPERTY() + int32 MultiparmValue; + + // + UPROPERTY() + uint32 MultiParmInstanceNum; + + // + UPROPERTY() + uint32 MultiParmInstanceLength; + + // + UPROPERTY() + uint32 MultiParmInstanceCount; + + UPROPERTY() + uint32 InstanceStartOffset; + + // This array records the last modified instance of the multiparm + UPROPERTY() + TArray MultiParmInstanceLastModifyArray; + + UPROPERTY() + int32 DefaultInstanceCount; + + bool IsDefault() const override; + + void SetDefaultInstanceCount(int32 InCount); + +private: + void InitializeModifyArray(); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp index 09740047e..75f1187cb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp @@ -1,95 +1,95 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniInput.h" - - -UHoudiniParameterOperatorPath::UHoudiniParameterOperatorPath( - const FObjectInitializer &ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -UHoudiniParameterOperatorPath * -UHoudiniParameterOperatorPath::Create(UObject *InOuter, const FString &InParamName) -{ - FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; - FName ParamName = MakeUniqueObjectName( - InOuter, UHoudiniParameterOperatorPath::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterOperatorPath *HoudiniAssetParameter = - NewObject( - InOuter, UHoudiniParameterOperatorPath::StaticClass(), ParamName, - RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Input); - - - return HoudiniAssetParameter; -} - -bool UHoudiniParameterOperatorPath::HasChanged() const -{ - if (Super::HasChanged()) - return true; - if (HoudiniInput.IsValid() && HoudiniInput->HasChanged()) - return true; - return false; -} - -bool UHoudiniParameterOperatorPath::NeedsToTriggerUpdate() const -{ - if (Super::NeedsToTriggerUpdate()) - return true; - if (HoudiniInput.IsValid() && HoudiniInput->NeedsToTriggerUpdate()) - return true; - return false; -} - -void UHoudiniParameterOperatorPath::MarkChanged(const bool & bInChanged) -{ - Super::MarkChanged(bInChanged); -} - -void -UHoudiniParameterOperatorPath::RemapInputs(const TMap& InputMapping) -{ - if (!HoudiniInput.IsValid()) - return; - - if (InputMapping.Contains(HoudiniInput.Get())) - { - HoudiniInput = InputMapping.FindRef(HoudiniInput.Get()); - } - else - { - HoudiniInput = nullptr; - } -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniInput.h" + + +UHoudiniParameterOperatorPath::UHoudiniParameterOperatorPath( + const FObjectInitializer &ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniParameterOperatorPath * +UHoudiniParameterOperatorPath::Create(UObject *InOuter, const FString &InParamName) +{ + FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; + FName ParamName = MakeUniqueObjectName( + InOuter, UHoudiniParameterOperatorPath::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterOperatorPath *HoudiniAssetParameter = + NewObject( + InOuter, UHoudiniParameterOperatorPath::StaticClass(), ParamName, + RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Input); + + + return HoudiniAssetParameter; +} + +bool UHoudiniParameterOperatorPath::HasChanged() const +{ + if (Super::HasChanged()) + return true; + if (HoudiniInput.IsValid() && HoudiniInput->HasChanged()) + return true; + return false; +} + +bool UHoudiniParameterOperatorPath::NeedsToTriggerUpdate() const +{ + if (Super::NeedsToTriggerUpdate()) + return true; + if (HoudiniInput.IsValid() && HoudiniInput->NeedsToTriggerUpdate()) + return true; + return false; +} + +void UHoudiniParameterOperatorPath::MarkChanged(const bool & bInChanged) +{ + Super::MarkChanged(bInChanged); +} + +void +UHoudiniParameterOperatorPath::RemapInputs(const TMap& InputMapping) +{ + if (!HoudiniInput.IsValid()) + return; + + if (InputMapping.Contains(HoudiniInput.Get())) + { + HoudiniInput = InputMapping.FindRef(HoudiniInput.Get()); + } + else + { + HoudiniInput = nullptr; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h index 8a2e6d5ea..a16ef2aee 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h @@ -1,57 +1,57 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterOperatorPath.generated.h" - - -class UHoudiniInput; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterOperatorPath - : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - // Create instance of this class. - static UHoudiniParameterOperatorPath * - Create(UObject *Outer, const FString &ParamName); - - virtual bool HasChanged() const override; - virtual bool NeedsToTriggerUpdate() const override; - virtual void MarkChanged(const bool& bInChanged) override; - - //------------------------------------------------------------------------------------------------ - // UHoudiniParameter overrides - //------------------------------------------------------------------------------------------------ - virtual void RemapInputs(const TMap& InputMapping) override; - - UPROPERTY() - TWeakObjectPtr HoudiniInput; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterOperatorPath.generated.h" + + +class UHoudiniInput; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterOperatorPath + : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + // Create instance of this class. + static UHoudiniParameterOperatorPath * + Create(UObject *Outer, const FString &ParamName); + + virtual bool HasChanged() const override; + virtual bool NeedsToTriggerUpdate() const override; + virtual void MarkChanged(const bool& bInChanged) override; + + //------------------------------------------------------------------------------------------------ + // UHoudiniParameter overrides + //------------------------------------------------------------------------------------------------ + virtual void RemapInputs(const TMap& InputMapping) override; + + UPROPERTY() + TWeakObjectPtr HoudiniInput; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp index 62e5208f4..fc2898720 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp @@ -1,683 +1,683 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterRamp.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterChoice.h" -#include "UObject/UnrealType.h" - - -void -UHoudiniParameterRampFloatPoint::SetPosition(const float InPosition) -{ - if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Position = InPosition; - PositionParentParm->SetValueAt(InPosition, 0); - PositionParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampFloatPoint::SetValue(const float InValue) -{ - if (ValueParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Value = InValue; - ValueParentParm->SetValueAt(InValue, 0); - ValueParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampFloatPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) -{ - if (InterpolationParentParm) - { - Interpolation = InInterpolation; - InterpolationParentParm->SetIntValue((int32)InInterpolation); - InterpolationParentParm->UpdateStringValueFromInt(); - InterpolationParentParm->SetIsChildOfRamp(); - } -} - -UHoudiniParameterRampFloatPoint* -UHoudiniParameterRampFloatPoint::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - UHoudiniParameterRampFloatPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); - - NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); - - return NewPoint; -} - -void -UHoudiniParameterRampFloatPoint::CopyStateFrom(UHoudiniParameterRampFloatPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - // Ensure this object's flags match the desired flags. - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -} - -void -UHoudiniParameterRampFloatPoint::RemapParameters( - const TMap& ParameterMapping) -{ - if (ParameterMapping.Contains(PositionParentParm)) - { - PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); - } - else - { - PositionParentParm = nullptr; - } - - if (ParameterMapping.Contains(ValueParentParm)) - { - ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); - } - else - { - ValueParentParm = nullptr; - } - - if (ParameterMapping.Contains(InterpolationParentParm)) - { - InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); - } - else - { - InterpolationParentParm = nullptr; - } -}; - - -void -UHoudiniParameterRampColorPoint::SetPosition(const float InPosition) -{ - if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Position = InPosition; - PositionParentParm->SetValueAt(InPosition, 0); - PositionParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampColorPoint::SetValue(const FLinearColor InValue) -{ - if (!ValueParentParm) - return; - - Value = InValue; - ValueParentParm->SetColorValue(InValue); - ValueParentParm->SetIsChildOfRamp(); -}; - -void -UHoudiniParameterRampColorPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) -{ - if (!InterpolationParentParm) - return; - - Interpolation = InInterpolation; - InterpolationParentParm->SetIntValue((int32)InInterpolation); - InterpolationParentParm->UpdateStringValueFromInt(); - InterpolationParentParm->SetIsChildOfRamp(); -} -UHoudiniParameterRampColorPoint * UHoudiniParameterRampColorPoint::DuplicateAndCopyState(UObject * DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - - UHoudiniParameterRampColorPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); - - NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); - - return NewPoint; - -} -void UHoudiniParameterRampColorPoint::CopyStateFrom(UHoudiniParameterRampColorPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ -#if WITH_EDITOR - PreEditChange(nullptr); -#endif - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - // Ensure this object's flags match the desired flags. - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -#if WITH_EDITOR - PostEditChange(); -#endif -} - -void UHoudiniParameterRampColorPoint::RemapParameters(const TMap& ParameterMapping) -{ - if (ParameterMapping.Contains(PositionParentParm)) - { - PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); - } - else - { - PositionParentParm = nullptr; - } - - if (ParameterMapping.Contains(ValueParentParm)) - { - ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); - } - else - { - ValueParentParm = nullptr; - } - - if (ParameterMapping.Contains(InterpolationParentParm)) - { - InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); - } - else - { - InterpolationParentParm = nullptr; - } -} - - -void -UHoudiniParameterRampFloat::OnPreCook() -{ - if (bCaching) - { - SyncCachedPoints(); - bCaching = false; - } -} - -UHoudiniParameterRampFloat * -UHoudiniParameterRampFloat::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampFloat::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterRampFloat * HoudiniParameter = NewObject< UHoudiniParameterRampFloat >( - InOuter, UHoudiniParameterRampFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniParameter->SetParameterType(EHoudiniParameterType::FloatRamp); - - HoudiniParameter->NumDefaultPoints = -1; - - HoudiniParameter->bCaching = false; - - return HoudiniParameter; -} - -void UHoudiniParameterRampFloat::CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ -#if WITH_EDITOR - PreEditChange(nullptr); -#endif - - UHoudiniParameterRampFloat* FromParameter = Cast(InParameter); - check(FromParameter); - - TArray PrevCachedPoints = CachedPoints; - TArray PrevPoints = Points; - - Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); - - CachedPoints = PrevCachedPoints; - Points = PrevPoints; - - - auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) - { - const int32 NumPts = FromArray.Num(); - - ToArray.SetNum(NumPts); - - for(int32 i = 0; i < NumPts; ++i) - { - UHoudiniParameterRampFloatPoint* FromPoint = FromArray[i]; - UHoudiniParameterRampFloatPoint* ToPoint = ToArray[i]; - - check(FromPoint); - - if (ToPoint) - { - // Ensure the destination point is outered to this parameter - bool bIsValid = ToPoint->GetOuter() == NewOuter; - if (!bIsValid) - ToPoint = nullptr; - } - - if (!ToPoint) - { - // Duplicate a new copy using FromPoint - ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); - } - else - { - // We have a valid point that we can reuse. Simply copy state. - ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); - } - - ToArray[i] = ToPoint; - } - }; - - CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); - CopyPointsStateFn(FromParameter->Points, Points, this); - -#if WITH_EDITOR - PostEditChange(); -#endif -} - -void -UHoudiniParameterRampFloat::RemapParameters(const TMap& ParameterMapping) -{ - Super::RemapParameters(ParameterMapping); - - AActor* OuterActor = GetTypedOuter(); - - - for(UHoudiniParameterRampFloatPoint* CurrentPoint : Points) - { - CurrentPoint->RemapParameters(ParameterMapping); - } - - for(UHoudiniParameterRampFloatPoint* CurrentPoint : CachedPoints) - { - CurrentPoint->RemapParameters(ParameterMapping); - } -} - -void -UHoudiniParameterRampFloat::SyncCachedPoints() -{ - int32 Idx = 0; - - while (Idx < CachedPoints.Num() && Idx < Points.Num()) - { - UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; - UHoudiniParameterRampFloatPoint* &CurrentPoint = Points[Idx]; - - if (!CachedPoint || !CurrentPoint) - continue; - - if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) - { - if (CurrentPoint->PositionParentParm) - { - CurrentPoint->SetPosition(CachedPoint->GetPosition()); - CurrentPoint->PositionParentParm->MarkChanged(true); - } - } - - if (CachedPoint->GetValue() != CurrentPoint->GetValue()) - { - if (CurrentPoint->ValueParentParm) - { - CurrentPoint->SetValue(CachedPoint->GetValue()); - CurrentPoint->ValueParentParm->MarkChanged(true); - } - } - - if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) - { - if (CurrentPoint->InterpolationParentParm) - { - CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); - CurrentPoint->InterpolationParentParm->MarkChanged(true); - } - } - - Idx += 1; - } - - // Insert points - for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) - { - UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; - if (!CachedPoint) - continue; - - CreateInsertEvent(CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); - - MarkChanged(true); - } - - // Remove points - for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < Points.Num(); ++IdxCurrentPointLeft) - { - RemoveElement(IdxCurrentPointLeft); - - UHoudiniParameterRampFloatPoint* Point = Points[IdxCurrentPointLeft]; - - if (!Point) - continue; - - CreateDeleteEvent(Point->InstanceIndex); - - MarkChanged(true); - } -} - - -void -UHoudiniParameterRampFloat::CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) -{ - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - this, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetFloatRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertFloat = InValue; - InsertEvent->InsertInterpolation = InInterp; - - ModificationEvents.Add(InsertEvent); -} - -void -UHoudiniParameterRampFloat::CreateDeleteEvent(const int32 &InDeleteIndex) -{ - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - this, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetFloatRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - ModificationEvents.Add(DeleteEvent); -} - - -UHoudiniParameterRampColor * -UHoudiniParameterRampColor::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampColor::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterRampColor * HoudiniParameter = NewObject< UHoudiniParameterRampColor >( - InOuter, UHoudiniParameterRampColor::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniParameter->SetParameterType(EHoudiniParameterType::ColorRamp); - - HoudiniParameter->NumDefaultPoints = -1; - - HoudiniParameter->bCaching = false; - - return HoudiniParameter; -} - - -bool -UHoudiniParameterRampFloat::IsDefault() const -{ - if (NumDefaultPoints < 0) - return true; - - if (NumDefaultPoints != Points.Num()) - { - return false; - } - - TArray Positions = DefaultPositions; - TArray Values = DefaultValues; - TArray Choices = DefaultChoices; - - for (auto & NextPt : Points) - { - if (!NextPt) - return false; - - bool bFoundMatch = false; - for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) - { - if (Positions[DefaultIdx] == NextPt->Position && - Values[DefaultIdx] == NextPt->Value && - Choices[DefaultIdx] == (int32)NextPt->Interpolation) - { - Positions.RemoveAt(DefaultIdx); - Values.RemoveAt(DefaultIdx); - Choices.RemoveAt(DefaultIdx); - bFoundMatch = true; - } - } - - if (!bFoundMatch) - return false; - } - - if (Positions.Num() > 0) - return false; - - return true; -} - -void UHoudiniParameterRampColor::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - UHoudiniParameterRampColor* FromParameter = Cast(InParameter); - check(FromParameter); - - TArray PrevCachedPoints = CachedPoints; - TArray PrevPoints = Points; - - Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); - - CachedPoints = PrevCachedPoints; - Points = PrevPoints; - - - auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) - { - const int32 NumPts = FromArray.Num(); - - ToArray.SetNum(NumPts); - - for(int32 i = 0; i < NumPts; ++i) - { - UHoudiniParameterRampColorPoint* FromPoint = FromArray[i]; - UHoudiniParameterRampColorPoint* ToPoint = ToArray[i]; - - check(FromPoint); - - if (ToPoint) - { - // Ensure the destination point is outered to this parameter - bool bIsValid = ToPoint->GetOuter() == NewOuter; - if (!bIsValid) - ToPoint = nullptr; - } - - if (!ToPoint) - { - // Duplicate a new copy using FromPoint - ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); - } - else - { - // We have a valid point that we can reuse. Simply copy state. - ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); - } - - ToArray[i] = ToPoint; - } - }; - - CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); - CopyPointsStateFn(FromParameter->Points, Points, this); - - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -} - -void UHoudiniParameterRampColor::RemapParameters(const TMap& ParameterMapping) -{ - Super::RemapParameters(ParameterMapping); - - AActor* OuterActor = GetTypedOuter(); - - - for(UHoudiniParameterRampColorPoint* CurrentPoint : Points) - { - CurrentPoint->RemapParameters(ParameterMapping); - } - - for(UHoudiniParameterRampColorPoint* CurrentPoint : CachedPoints) - { - CurrentPoint->RemapParameters(ParameterMapping); - } -} - -bool -UHoudiniParameterRampColor::IsDefault() const -{ - if (NumDefaultPoints < 0) - return true; - - if (NumDefaultPoints != Points.Num()) - return false; - - TArray Positions = DefaultPositions; - TArray Values = DefaultValues; - TArray Choices = DefaultChoices; - - for (auto & NextPt : Points) - { - if (!NextPt) - return false; - - bool bFoundMatch = false; - for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) - { - if (Positions[DefaultIdx] == NextPt->Position && - Values[DefaultIdx] == NextPt->Value && - Choices[DefaultIdx] == (int32)NextPt->Interpolation) - { - Positions.RemoveAt(DefaultIdx); - Values.RemoveAt(DefaultIdx); - Choices.RemoveAt(DefaultIdx); - bFoundMatch = true; - } - } - - if (!bFoundMatch) - return false; - } - - if (Positions.Num() > 0) - return false; - - return true; -} - - -void -UHoudiniParameterRampColor::SetDefaultValues() -{ - if (NumDefaultPoints >= 0) - return; - - - if (DefaultPositions.Num() > 0) - return; - - DefaultPositions.Empty(); - DefaultValues.Empty(); - DefaultChoices.Empty(); - - for (auto & NextPoint : Points) - { - if (!NextPoint) - continue; - - DefaultPositions.Add(NextPoint->Position); - DefaultValues.Add(NextPoint->Value); - DefaultChoices.Add((int32)NextPoint->Interpolation); - - } - - NumDefaultPoints = Points.Num(); -} - -void -UHoudiniParameterRampFloat::SetDefaultValues() -{ - if (DefaultPositions.Num() > 0) - return; - - DefaultPositions.Empty(); - DefaultValues.Empty(); - DefaultChoices.Empty(); - - for (auto & NextPoint : Points) - { - if (!NextPoint) - continue; - - DefaultPositions.Add(NextPoint->Position); - DefaultValues.Add(NextPoint->Value); - DefaultChoices.Add((int32)NextPoint->Interpolation); - - } - - NumDefaultPoints = Points.Num(); - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterRamp.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterChoice.h" +#include "UObject/UnrealType.h" + + +void +UHoudiniParameterRampFloatPoint::SetPosition(const float InPosition) +{ + if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Position = InPosition; + PositionParentParm->SetValueAt(InPosition, 0); + PositionParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampFloatPoint::SetValue(const float InValue) +{ + if (ValueParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Value = InValue; + ValueParentParm->SetValueAt(InValue, 0); + ValueParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampFloatPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) +{ + if (InterpolationParentParm) + { + Interpolation = InInterpolation; + InterpolationParentParm->SetIntValue((int32)InInterpolation); + InterpolationParentParm->UpdateStringValueFromInt(); + InterpolationParentParm->SetIsChildOfRamp(); + } +} + +UHoudiniParameterRampFloatPoint* +UHoudiniParameterRampFloatPoint::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + UHoudiniParameterRampFloatPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); + + NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); + + return NewPoint; +} + +void +UHoudiniParameterRampFloatPoint::CopyStateFrom(UHoudiniParameterRampFloatPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + // Ensure this object's flags match the desired flags. + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +} + +void +UHoudiniParameterRampFloatPoint::RemapParameters( + const TMap& ParameterMapping) +{ + if (ParameterMapping.Contains(PositionParentParm)) + { + PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); + } + else + { + PositionParentParm = nullptr; + } + + if (ParameterMapping.Contains(ValueParentParm)) + { + ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); + } + else + { + ValueParentParm = nullptr; + } + + if (ParameterMapping.Contains(InterpolationParentParm)) + { + InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); + } + else + { + InterpolationParentParm = nullptr; + } +}; + + +void +UHoudiniParameterRampColorPoint::SetPosition(const float InPosition) +{ + if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Position = InPosition; + PositionParentParm->SetValueAt(InPosition, 0); + PositionParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampColorPoint::SetValue(const FLinearColor InValue) +{ + if (!ValueParentParm) + return; + + Value = InValue; + ValueParentParm->SetColorValue(InValue); + ValueParentParm->SetIsChildOfRamp(); +}; + +void +UHoudiniParameterRampColorPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) +{ + if (!InterpolationParentParm) + return; + + Interpolation = InInterpolation; + InterpolationParentParm->SetIntValue((int32)InInterpolation); + InterpolationParentParm->UpdateStringValueFromInt(); + InterpolationParentParm->SetIsChildOfRamp(); +} +UHoudiniParameterRampColorPoint * UHoudiniParameterRampColorPoint::DuplicateAndCopyState(UObject * DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + + UHoudiniParameterRampColorPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); + + NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); + + return NewPoint; + +} +void UHoudiniParameterRampColorPoint::CopyStateFrom(UHoudiniParameterRampColorPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ +#if WITH_EDITOR + PreEditChange(nullptr); +#endif + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + // Ensure this object's flags match the desired flags. + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +#if WITH_EDITOR + PostEditChange(); +#endif +} + +void UHoudiniParameterRampColorPoint::RemapParameters(const TMap& ParameterMapping) +{ + if (ParameterMapping.Contains(PositionParentParm)) + { + PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); + } + else + { + PositionParentParm = nullptr; + } + + if (ParameterMapping.Contains(ValueParentParm)) + { + ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); + } + else + { + ValueParentParm = nullptr; + } + + if (ParameterMapping.Contains(InterpolationParentParm)) + { + InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); + } + else + { + InterpolationParentParm = nullptr; + } +} + + +void +UHoudiniParameterRampFloat::OnPreCook() +{ + if (bCaching) + { + SyncCachedPoints(); + bCaching = false; + } +} + +UHoudiniParameterRampFloat * +UHoudiniParameterRampFloat::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampFloat::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterRampFloat * HoudiniParameter = NewObject< UHoudiniParameterRampFloat >( + InOuter, UHoudiniParameterRampFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniParameter->SetParameterType(EHoudiniParameterType::FloatRamp); + + HoudiniParameter->NumDefaultPoints = -1; + + HoudiniParameter->bCaching = false; + + return HoudiniParameter; +} + +void UHoudiniParameterRampFloat::CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ +#if WITH_EDITOR + PreEditChange(nullptr); +#endif + + UHoudiniParameterRampFloat* FromParameter = Cast(InParameter); + check(FromParameter); + + TArray PrevCachedPoints = CachedPoints; + TArray PrevPoints = Points; + + Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); + + CachedPoints = PrevCachedPoints; + Points = PrevPoints; + + + auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) + { + const int32 NumPts = FromArray.Num(); + + ToArray.SetNum(NumPts); + + for(int32 i = 0; i < NumPts; ++i) + { + UHoudiniParameterRampFloatPoint* FromPoint = FromArray[i]; + UHoudiniParameterRampFloatPoint* ToPoint = ToArray[i]; + + check(FromPoint); + + if (ToPoint) + { + // Ensure the destination point is outered to this parameter + bool bIsValid = ToPoint->GetOuter() == NewOuter; + if (!bIsValid) + ToPoint = nullptr; + } + + if (!ToPoint) + { + // Duplicate a new copy using FromPoint + ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); + } + else + { + // We have a valid point that we can reuse. Simply copy state. + ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); + } + + ToArray[i] = ToPoint; + } + }; + + CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); + CopyPointsStateFn(FromParameter->Points, Points, this); + +#if WITH_EDITOR + PostEditChange(); +#endif +} + +void +UHoudiniParameterRampFloat::RemapParameters(const TMap& ParameterMapping) +{ + Super::RemapParameters(ParameterMapping); + + AActor* OuterActor = GetTypedOuter(); + + + for(UHoudiniParameterRampFloatPoint* CurrentPoint : Points) + { + CurrentPoint->RemapParameters(ParameterMapping); + } + + for(UHoudiniParameterRampFloatPoint* CurrentPoint : CachedPoints) + { + CurrentPoint->RemapParameters(ParameterMapping); + } +} + +void +UHoudiniParameterRampFloat::SyncCachedPoints() +{ + int32 Idx = 0; + + while (Idx < CachedPoints.Num() && Idx < Points.Num()) + { + UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; + UHoudiniParameterRampFloatPoint* &CurrentPoint = Points[Idx]; + + if (!CachedPoint || !CurrentPoint) + continue; + + if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) + { + if (CurrentPoint->PositionParentParm) + { + CurrentPoint->SetPosition(CachedPoint->GetPosition()); + CurrentPoint->PositionParentParm->MarkChanged(true); + } + } + + if (CachedPoint->GetValue() != CurrentPoint->GetValue()) + { + if (CurrentPoint->ValueParentParm) + { + CurrentPoint->SetValue(CachedPoint->GetValue()); + CurrentPoint->ValueParentParm->MarkChanged(true); + } + } + + if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) + { + if (CurrentPoint->InterpolationParentParm) + { + CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); + CurrentPoint->InterpolationParentParm->MarkChanged(true); + } + } + + Idx += 1; + } + + // Insert points + for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) + { + UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; + if (!CachedPoint) + continue; + + CreateInsertEvent(CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); + + MarkChanged(true); + } + + // Remove points + for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < Points.Num(); ++IdxCurrentPointLeft) + { + RemoveElement(IdxCurrentPointLeft); + + UHoudiniParameterRampFloatPoint* Point = Points[IdxCurrentPointLeft]; + + if (!Point) + continue; + + CreateDeleteEvent(Point->InstanceIndex); + + MarkChanged(true); + } +} + + +void +UHoudiniParameterRampFloat::CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) +{ + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + this, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetFloatRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertFloat = InValue; + InsertEvent->InsertInterpolation = InInterp; + + ModificationEvents.Add(InsertEvent); +} + +void +UHoudiniParameterRampFloat::CreateDeleteEvent(const int32 &InDeleteIndex) +{ + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + this, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetFloatRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + ModificationEvents.Add(DeleteEvent); +} + + +UHoudiniParameterRampColor * +UHoudiniParameterRampColor::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampColor::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterRampColor * HoudiniParameter = NewObject< UHoudiniParameterRampColor >( + InOuter, UHoudiniParameterRampColor::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniParameter->SetParameterType(EHoudiniParameterType::ColorRamp); + + HoudiniParameter->NumDefaultPoints = -1; + + HoudiniParameter->bCaching = false; + + return HoudiniParameter; +} + + +bool +UHoudiniParameterRampFloat::IsDefault() const +{ + if (NumDefaultPoints < 0) + return true; + + if (NumDefaultPoints != Points.Num()) + { + return false; + } + + TArray Positions = DefaultPositions; + TArray Values = DefaultValues; + TArray Choices = DefaultChoices; + + for (auto & NextPt : Points) + { + if (!NextPt) + return false; + + bool bFoundMatch = false; + for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) + { + if (Positions[DefaultIdx] == NextPt->Position && + Values[DefaultIdx] == NextPt->Value && + Choices[DefaultIdx] == (int32)NextPt->Interpolation) + { + Positions.RemoveAt(DefaultIdx); + Values.RemoveAt(DefaultIdx); + Choices.RemoveAt(DefaultIdx); + bFoundMatch = true; + } + } + + if (!bFoundMatch) + return false; + } + + if (Positions.Num() > 0) + return false; + + return true; +} + +void UHoudiniParameterRampColor::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + UHoudiniParameterRampColor* FromParameter = Cast(InParameter); + check(FromParameter); + + TArray PrevCachedPoints = CachedPoints; + TArray PrevPoints = Points; + + Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); + + CachedPoints = PrevCachedPoints; + Points = PrevPoints; + + + auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) + { + const int32 NumPts = FromArray.Num(); + + ToArray.SetNum(NumPts); + + for(int32 i = 0; i < NumPts; ++i) + { + UHoudiniParameterRampColorPoint* FromPoint = FromArray[i]; + UHoudiniParameterRampColorPoint* ToPoint = ToArray[i]; + + check(FromPoint); + + if (ToPoint) + { + // Ensure the destination point is outered to this parameter + bool bIsValid = ToPoint->GetOuter() == NewOuter; + if (!bIsValid) + ToPoint = nullptr; + } + + if (!ToPoint) + { + // Duplicate a new copy using FromPoint + ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); + } + else + { + // We have a valid point that we can reuse. Simply copy state. + ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); + } + + ToArray[i] = ToPoint; + } + }; + + CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); + CopyPointsStateFn(FromParameter->Points, Points, this); + + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +} + +void UHoudiniParameterRampColor::RemapParameters(const TMap& ParameterMapping) +{ + Super::RemapParameters(ParameterMapping); + + AActor* OuterActor = GetTypedOuter(); + + + for(UHoudiniParameterRampColorPoint* CurrentPoint : Points) + { + CurrentPoint->RemapParameters(ParameterMapping); + } + + for(UHoudiniParameterRampColorPoint* CurrentPoint : CachedPoints) + { + CurrentPoint->RemapParameters(ParameterMapping); + } +} + +bool +UHoudiniParameterRampColor::IsDefault() const +{ + if (NumDefaultPoints < 0) + return true; + + if (NumDefaultPoints != Points.Num()) + return false; + + TArray Positions = DefaultPositions; + TArray Values = DefaultValues; + TArray Choices = DefaultChoices; + + for (auto & NextPt : Points) + { + if (!NextPt) + return false; + + bool bFoundMatch = false; + for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) + { + if (Positions[DefaultIdx] == NextPt->Position && + Values[DefaultIdx] == NextPt->Value && + Choices[DefaultIdx] == (int32)NextPt->Interpolation) + { + Positions.RemoveAt(DefaultIdx); + Values.RemoveAt(DefaultIdx); + Choices.RemoveAt(DefaultIdx); + bFoundMatch = true; + } + } + + if (!bFoundMatch) + return false; + } + + if (Positions.Num() > 0) + return false; + + return true; +} + + +void +UHoudiniParameterRampColor::SetDefaultValues() +{ + if (NumDefaultPoints >= 0) + return; + + + if (DefaultPositions.Num() > 0) + return; + + DefaultPositions.Empty(); + DefaultValues.Empty(); + DefaultChoices.Empty(); + + for (auto & NextPoint : Points) + { + if (!NextPoint) + continue; + + DefaultPositions.Add(NextPoint->Position); + DefaultValues.Add(NextPoint->Value); + DefaultChoices.Add((int32)NextPoint->Interpolation); + + } + + NumDefaultPoints = Points.Num(); +} + +void +UHoudiniParameterRampFloat::SetDefaultValues() +{ + if (DefaultPositions.Num() > 0) + return; + + DefaultPositions.Empty(); + DefaultValues.Empty(); + DefaultChoices.Empty(); + + for (auto & NextPoint : Points) + { + if (!NextPoint) + continue; + + DefaultPositions.Add(NextPoint->Position); + DefaultValues.Add(NextPoint->Value); + DefaultChoices.Add((int32)NextPoint->Interpolation); + + } + + NumDefaultPoints = Points.Num(); + } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h index 90d938a8a..11aa9fd87 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h @@ -1,312 +1,312 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Curves/CurveFloat.h" -#include "Curves/CurveLinearColor.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.generated.h" - -class UHoudiniParameterRampFloat; -class UHoudiniParameterRampColor; -class UHoudiniParameter; -class UHoudiniParameterFloat; -class UHoudiniParameterChoice; -class UHoudiniParameterColor; - -UENUM() -enum class EHoudiniRampPointConstructStatus : uint8 -{ - None, - - INITIALIZED, - POSITION_INSERTED, - VALUE_INSERTED, - INTERPTYPE_INSERTED -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampModificationEvent : public UObject -{ - GENERATED_BODY() -public: - FORCEINLINE - void SetInsertEvent() { bIsInsertEvent = true; }; - - FORCEINLINE - void SetDeleteEvent() { bIsInsertEvent = false; }; - - FORCEINLINE - void SetFloatRampEvent() { bIsFloatRamp = true; }; - - FORCEINLINE - void SetColorRampEvent() { bIsFloatRamp = false; }; - - FORCEINLINE - bool IsInsertEvent() const { return bIsInsertEvent; }; - - FORCEINLINE - bool IsDeleteEvent() const { return !bIsInsertEvent; }; - - FORCEINLINE - bool IsFloatRampEvent() { return bIsFloatRamp; }; - - FORCEINLINE - bool IsColorRampEvent() { return !bIsFloatRamp; }; - - -private: - UPROPERTY() - bool bIsInsertEvent = false; - - UPROPERTY() - bool bIsFloatRamp = false; - -public: - UPROPERTY() - int32 DeleteInstanceIndex = -1; - - UPROPERTY() - float InsertPosition; - - UPROPERTY() - float InsertFloat; - - UPROPERTY() - FLinearColor InsertColor; - - UPROPERTY() - EHoudiniRampInterpolationType InsertInterpolation; -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloatPoint : public UObject -{ - GENERATED_BODY() - -public: - - UPROPERTY() - float Position; - - UPROPERTY() - float Value; - - UPROPERTY() - EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; - - UPROPERTY() - int32 InstanceIndex = -1; - - UPROPERTY() - UHoudiniParameterFloat* PositionParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterFloat* ValueParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterChoice* InterpolationParentParm = nullptr; - - FORCEINLINE - float GetPosition() const { return Position; }; - - void SetPosition(const float InPosition); - - FORCEINLINE - float GetValue() const { return Value; }; - - void SetValue(const float InValue); - - FORCEINLINE - EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; - - void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); - - UHoudiniParameterRampFloatPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); - - void CopyStateFrom(UHoudiniParameterRampFloatPoint* InParameter, bool bCopyAllProperties, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); - - void RemapParameters(const TMap& ParameterMapping); - -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColorPoint : public UObject -{ - GENERATED_BODY() - -public: - - UPROPERTY() - float Position; - - UPROPERTY() - FLinearColor Value; - - UPROPERTY() - EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; - - UPROPERTY() - int32 InstanceIndex = -1; - - UPROPERTY() - UHoudiniParameterFloat * PositionParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterColor* ValueParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterChoice* InterpolationParentParm = nullptr; - - FORCEINLINE - float GetPosition() const { return Position; }; - - void SetPosition(const float InPosition); - FORCEINLINE - FLinearColor GetValue() const { return Value; }; - - void SetValue(const FLinearColor InValue); - - FORCEINLINE - EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; - - void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); - - UHoudiniParameterRampColorPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - void CopyStateFrom(UHoudiniParameterRampColorPoint* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - void RemapParameters(const TMap& ParameterMapping); -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParameterMultiParm -{ - GENERATED_BODY() - -public: - - virtual void OnPreCook() override; - - // Create instance of this class. - static UHoudiniParameterRampFloat * Create( - UObject* Outer, - const FString& ParamName); - - FORCEINLINE - bool IsCaching() const { return bCaching; }; - - FORCEINLINE - void SetCaching(const bool bInCaching) { bCaching = bInCaching; }; - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; - - virtual void RemapParameters(const TMap& ParameterMapping) override; - - void SyncCachedPoints(); - - void CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); - - void CreateDeleteEvent(const int32 &InDeleteIndex); - - UPROPERTY() - TArray Points; - - UPROPERTY() - TArray CachedPoints; - - UPROPERTY() - TArray DefaultPositions; - - UPROPERTY() - TArray DefaultValues; - - UPROPERTY() - TArray DefaultChoices; - - UPROPERTY() - int32 NumDefaultPoints; - - UPROPERTY() - bool bCaching; - - UPROPERTY() - TArray ModificationEvents; - - bool IsDefault() const override; - - void SetDefaultValues(); - -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColor : public UHoudiniParameterMultiParm -{ - GENERATED_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterRampColor * Create( - UObject* Outer, - const FString& ParamName); - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; - - virtual void RemapParameters(const TMap& ParameterMapping) override; - - UPROPERTY(Instanced) - TArray Points; - - UPROPERTY() - bool bCaching; - - UPROPERTY(Instanced) - TArray CachedPoints; - - UPROPERTY() - TArray DefaultPositions; - - UPROPERTY() - TArray DefaultValues; - - UPROPERTY() - TArray DefaultChoices; - - UPROPERTY() - int32 NumDefaultPoints; - - UPROPERTY() - TArray ModificationEvents; - - bool IsDefault() const override; - - void SetDefaultValues(); - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Curves/CurveFloat.h" +#include "Curves/CurveLinearColor.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.generated.h" + +class UHoudiniParameterRampFloat; +class UHoudiniParameterRampColor; +class UHoudiniParameter; +class UHoudiniParameterFloat; +class UHoudiniParameterChoice; +class UHoudiniParameterColor; + +UENUM() +enum class EHoudiniRampPointConstructStatus : uint8 +{ + None, + + INITIALIZED, + POSITION_INSERTED, + VALUE_INSERTED, + INTERPTYPE_INSERTED +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampModificationEvent : public UObject +{ + GENERATED_BODY() +public: + FORCEINLINE + void SetInsertEvent() { bIsInsertEvent = true; }; + + FORCEINLINE + void SetDeleteEvent() { bIsInsertEvent = false; }; + + FORCEINLINE + void SetFloatRampEvent() { bIsFloatRamp = true; }; + + FORCEINLINE + void SetColorRampEvent() { bIsFloatRamp = false; }; + + FORCEINLINE + bool IsInsertEvent() const { return bIsInsertEvent; }; + + FORCEINLINE + bool IsDeleteEvent() const { return !bIsInsertEvent; }; + + FORCEINLINE + bool IsFloatRampEvent() { return bIsFloatRamp; }; + + FORCEINLINE + bool IsColorRampEvent() { return !bIsFloatRamp; }; + + +private: + UPROPERTY() + bool bIsInsertEvent = false; + + UPROPERTY() + bool bIsFloatRamp = false; + +public: + UPROPERTY() + int32 DeleteInstanceIndex = -1; + + UPROPERTY() + float InsertPosition; + + UPROPERTY() + float InsertFloat; + + UPROPERTY() + FLinearColor InsertColor; + + UPROPERTY() + EHoudiniRampInterpolationType InsertInterpolation; +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloatPoint : public UObject +{ + GENERATED_BODY() + +public: + + UPROPERTY() + float Position; + + UPROPERTY() + float Value; + + UPROPERTY() + EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; + + UPROPERTY() + int32 InstanceIndex = -1; + + UPROPERTY() + UHoudiniParameterFloat* PositionParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterFloat* ValueParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterChoice* InterpolationParentParm = nullptr; + + FORCEINLINE + float GetPosition() const { return Position; }; + + void SetPosition(const float InPosition); + + FORCEINLINE + float GetValue() const { return Value; }; + + void SetValue(const float InValue); + + FORCEINLINE + EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; + + void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); + + UHoudiniParameterRampFloatPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); + + void CopyStateFrom(UHoudiniParameterRampFloatPoint* InParameter, bool bCopyAllProperties, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); + + void RemapParameters(const TMap& ParameterMapping); + +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColorPoint : public UObject +{ + GENERATED_BODY() + +public: + + UPROPERTY() + float Position; + + UPROPERTY() + FLinearColor Value; + + UPROPERTY() + EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; + + UPROPERTY() + int32 InstanceIndex = -1; + + UPROPERTY() + UHoudiniParameterFloat * PositionParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterColor* ValueParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterChoice* InterpolationParentParm = nullptr; + + FORCEINLINE + float GetPosition() const { return Position; }; + + void SetPosition(const float InPosition); + FORCEINLINE + FLinearColor GetValue() const { return Value; }; + + void SetValue(const FLinearColor InValue); + + FORCEINLINE + EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; + + void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); + + UHoudiniParameterRampColorPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + void CopyStateFrom(UHoudiniParameterRampColorPoint* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + void RemapParameters(const TMap& ParameterMapping); +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParameterMultiParm +{ + GENERATED_BODY() + +public: + + virtual void OnPreCook() override; + + // Create instance of this class. + static UHoudiniParameterRampFloat * Create( + UObject* Outer, + const FString& ParamName); + + FORCEINLINE + bool IsCaching() const { return bCaching; }; + + FORCEINLINE + void SetCaching(const bool bInCaching) { bCaching = bInCaching; }; + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; + + virtual void RemapParameters(const TMap& ParameterMapping) override; + + void SyncCachedPoints(); + + void CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); + + void CreateDeleteEvent(const int32 &InDeleteIndex); + + UPROPERTY() + TArray Points; + + UPROPERTY() + TArray CachedPoints; + + UPROPERTY() + TArray DefaultPositions; + + UPROPERTY() + TArray DefaultValues; + + UPROPERTY() + TArray DefaultChoices; + + UPROPERTY() + int32 NumDefaultPoints; + + UPROPERTY() + bool bCaching; + + UPROPERTY() + TArray ModificationEvents; + + bool IsDefault() const override; + + void SetDefaultValues(); + +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColor : public UHoudiniParameterMultiParm +{ + GENERATED_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterRampColor * Create( + UObject* Outer, + const FString& ParamName); + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; + + virtual void RemapParameters(const TMap& ParameterMapping) override; + + UPROPERTY(Instanced) + TArray Points; + + UPROPERTY() + bool bCaching; + + UPROPERTY(Instanced) + TArray CachedPoints; + + UPROPERTY() + TArray DefaultPositions; + + UPROPERTY() + TArray DefaultValues; + + UPROPERTY() + TArray DefaultChoices; + + UPROPERTY() + int32 NumDefaultPoints; + + UPROPERTY() + TArray ModificationEvents; + + bool IsDefault() const override; + + void SetDefaultValues(); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp index c109c5ba6..d59f4177f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp @@ -1,51 +1,51 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterSeparator.h" - -UHoudiniParameterSeparator::UHoudiniParameterSeparator(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Separator; -} - -UHoudiniParameterSeparator * -UHoudiniParameterSeparator::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterSeparator_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterSeparator::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterSeparator * HoudiniAssetParameter = NewObject< UHoudiniParameterSeparator >( - InOuter, UHoudiniParameterSeparator::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Separator); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterSeparator.h" + +UHoudiniParameterSeparator::UHoudiniParameterSeparator(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Separator; +} + +UHoudiniParameterSeparator * +UHoudiniParameterSeparator::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterSeparator_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterSeparator::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterSeparator * HoudiniAssetParameter = NewObject< UHoudiniParameterSeparator >( + InOuter, UHoudiniParameterSeparator::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Separator); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h index 3394999aa..35b8e3f45 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterSeparator.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterSeparator : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterSeparator * Create( - UObject* Outer, - const FString& ParamName); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterSeparator.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterSeparator : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterSeparator * Create( + UObject* Outer, + const FString& ParamName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp index 646120f95..99daaa002 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp @@ -1,136 +1,136 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterString.h" - -UHoudiniParameterString::UHoudiniParameterString(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , bIsAssetRef(false) -{ - ParmType = EHoudiniParameterType::String; -} - -UHoudiniParameterString * -UHoudiniParameterString::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterString_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterString::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterString * HoudiniAssetParameter = NewObject< UHoudiniParameterString >( - InOuter, UHoudiniParameterString::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::String); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterString::SetValueAt(const FString& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index].Equals(InValue, ESearchCase::CaseSensitive)) - return false; - - Values[Index] = InValue; - - return true; -} - -void -UHoudiniParameterString::SetAssetAt(UObject* InObject, const uint32& Index) -{ - if (!ChosenAssets.IsValidIndex(Index)) - return; - - ChosenAssets[Index] = InObject; - -} - -FString -UHoudiniParameterString::GetAssetReference(UObject* InObject) -{ - // Get the asset reference string for a given UObject - if (!InObject || InObject->IsPendingKill()) - return FString(); - - // Start by getting the Object's full name - FString AssetReference = InObject->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - return AssetReference; -} - -void -UHoudiniParameterString::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterString::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterString::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterString.h" + +UHoudiniParameterString::UHoudiniParameterString(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , bIsAssetRef(false) +{ + ParmType = EHoudiniParameterType::String; +} + +UHoudiniParameterString * +UHoudiniParameterString::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterString_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterString::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterString * HoudiniAssetParameter = NewObject< UHoudiniParameterString >( + InOuter, UHoudiniParameterString::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::String); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterString::SetValueAt(const FString& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index].Equals(InValue, ESearchCase::CaseSensitive)) + return false; + + Values[Index] = InValue; + + return true; +} + +void +UHoudiniParameterString::SetAssetAt(UObject* InObject, const uint32& Index) +{ + if (!ChosenAssets.IsValidIndex(Index)) + return; + + ChosenAssets[Index] = InObject; + +} + +FString +UHoudiniParameterString::GetAssetReference(UObject* InObject) +{ + // Get the asset reference string for a given UObject + if (!InObject || InObject->IsPendingKill()) + return FString(); + + // Start by getting the Object's full name + FString AssetReference = InObject->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + return AssetReference; +} + +void +UHoudiniParameterString::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterString::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterString::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h index 0fac749d3..2e76f0c35 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h @@ -1,91 +1,91 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterString.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterString : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterString * Create( - UObject* Outer, const FString& ParamName); - - // Accessor - FString GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? Values[Index] : FString(); }; - - UObject* GetAssetAt(const uint32& Index) const { return ChosenAssets.IsValidIndex(Index) ? ChosenAssets[Index] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - bool IsAssetRef() const { return bIsAssetRef; }; - - bool IsDefaultValueAtIndex(const int32& Idx) const; - bool IsDefault() const override; - - // Mutators - void SetIsAssetRef(const bool& InIsAssetRef) { bIsAssetRef = InIsAssetRef; }; - - bool SetValueAt(const FString& InValue, const uint32& Index); - - void SetAssetAt(UObject* InObject, const uint32& Index); - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNum(InNumValues); ChosenAssets.SetNum(InNumValues); }; - - TArray & GetChosenAssets() { return ChosenAssets; }; - - void SetDefaultValues(); - - // Utility - - // Get the asset reference string for a given UObject - static FString GetAssetReference(UObject* InObject); - - -protected: - - // Values of this property. - UPROPERTY() - TArray< FString > Values; - - UPROPERTY() - TArray< FString > DefaultValues; - - UPROPERTY() - TArray ChosenAssets; - - // Indicates this string parameter should be treated as an asset reference - // and display an object picker - UPROPERTY() - bool bIsAssetRef; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterString.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterString : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterString * Create( + UObject* Outer, const FString& ParamName); + + // Accessor + FString GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? Values[Index] : FString(); }; + + UObject* GetAssetAt(const uint32& Index) const { return ChosenAssets.IsValidIndex(Index) ? ChosenAssets[Index] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + bool IsAssetRef() const { return bIsAssetRef; }; + + bool IsDefaultValueAtIndex(const int32& Idx) const; + bool IsDefault() const override; + + // Mutators + void SetIsAssetRef(const bool& InIsAssetRef) { bIsAssetRef = InIsAssetRef; }; + + bool SetValueAt(const FString& InValue, const uint32& Index); + + void SetAssetAt(UObject* InObject, const uint32& Index); + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNum(InNumValues); ChosenAssets.SetNum(InNumValues); }; + + TArray & GetChosenAssets() { return ChosenAssets; }; + + void SetDefaultValues(); + + // Utility + + // Get the asset reference string for a given UObject + static FString GetAssetReference(UObject* InObject); + + +protected: + + // Values of this property. + UPROPERTY() + TArray< FString > Values; + + UPROPERTY() + TArray< FString > DefaultValues; + + UPROPERTY() + TArray ChosenAssets; + + // Indicates this string parameter should be treated as an asset reference + // and display an object picker + UPROPERTY() + bool bIsAssetRef; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp index 37c2e21c5..6fa8ff2e2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp @@ -1,95 +1,95 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterToggle.h" - -UHoudiniParameterToggle::UHoudiniParameterToggle(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Toggle; -} - -UHoudiniParameterToggle * -UHoudiniParameterToggle::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterToggle_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterToggle::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterToggle * HoudiniAssetParameter = NewObject< UHoudiniParameterToggle >( - InOuter, UHoudiniParameterToggle::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Toggle); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterToggle::SetValueAt(const bool& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index] == 0 && !InValue) - return false; - - if (Values[Index] == 1 && InValue) - return false; - - Values[Index] = InValue ? 1 : 0; - return true; -} - -bool -UHoudiniParameterToggle::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!DefaultValues.IsValidIndex(Idx)) - break; - - if (Values[Idx] != DefaultValues[Idx]) - return false; - } - - return true; -} - -void -UHoudiniParameterToggle::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterToggle.h" + +UHoudiniParameterToggle::UHoudiniParameterToggle(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Toggle; +} + +UHoudiniParameterToggle * +UHoudiniParameterToggle::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterToggle_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterToggle::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterToggle * HoudiniAssetParameter = NewObject< UHoudiniParameterToggle >( + InOuter, UHoudiniParameterToggle::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Toggle); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterToggle::SetValueAt(const bool& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index] == 0 && !InValue) + return false; + + if (Values[Index] == 1 && InValue) + return false; + + Values[Index] = InValue ? 1 : 0; + return true; +} + +bool +UHoudiniParameterToggle::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!DefaultValues.IsValidIndex(Idx)) + break; + + if (Values[Idx] != DefaultValues[Idx]) + return false; + } + + return true; +} + +void +UHoudiniParameterToggle::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h index 0a5a60569..c80572fb7 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h @@ -1,68 +1,68 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" -#include "Styling/SlateTypes.h" -#include "HoudiniParameterToggle.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterToggle : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterToggle * Create( - UObject* Outer, - const FString& ParamName); - - // Accessor - bool GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? (bool)Values[Index] : false; }; - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - bool IsDefault() const override; - - // Mutators - bool SetValueAt(const bool& InValue, const uint32& Index); - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - - int32 GetNumValues() { return Values.Num(); }; - - void SetDefaultValues(); - -protected: - - // Values of this property. - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" +#include "Styling/SlateTypes.h" +#include "HoudiniParameterToggle.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterToggle : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterToggle * Create( + UObject* Outer, + const FString& ParamName); + + // Accessor + bool GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? (bool)Values[Index] : false; }; + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + bool IsDefault() const override; + + // Mutators + bool SetValueAt(const bool& InValue, const uint32& Index); + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + + int32 GetNumValues() { return Values.Num(); }; + + void SetDefaultValues(); + +protected: + + // Values of this property. + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp index 1d5e7bc32..f774d31b3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp @@ -1,33 +1,33 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPluginSerializationVersion.h" -#include "Serialization/CustomVersion.h" - -const FGuid FHoudiniCustomSerializationVersion::GUID( 0x1AB9CECC, 0x6913, 0x4875, 0x203d51fb ); - -// Register the custom version with core -FCustomVersionRegistration GRegisterHoudiniCustomVersion( FHoudiniCustomSerializationVersion::GUID, VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION, TEXT( "HoudiniUE4PluginVer" ) ); +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPluginSerializationVersion.h" +#include "Serialization/CustomVersion.h" + +const FGuid FHoudiniCustomSerializationVersion::GUID( 0x1AB9CECC, 0x6913, 0x4875, 0x203d51fb ); + +// Register the custom version with core +FCustomVersionRegistration GRegisterHoudiniCustomVersion( FHoudiniCustomSerializationVersion::GUID, VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION, TEXT( "HoudiniUE4PluginVer" ) ); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h index de1b6fbf9..6d9a95fe7 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h @@ -1,92 +1,92 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - - -// Deprecated per-class versions used to load old files -// -// Serialization of parameter name map. -#define VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP 2 -// Serialization of instancer material, if it is available. -#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME 1 -// Serialization of attribute instancer material, if it is available. -#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME 2 -// Landscape serialization in asset inputs. -#define VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT 1 -// Asset instance member. -#define VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER 2 -// World Outliner inputs. -#define VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT 3 - - -enum EHoudiniPluginSerializationVersion -{ - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE = 5, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE = 6, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM = 7, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT = 8, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH = 9, - VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM = 10, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT = 11, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_CUSTOM_LINKER = 12, // added custom linker version to archives - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ACTOR_INSTANCING = 13, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES = 14, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT = 15, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE = 16, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES = 17, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS = 18, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY = 19, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS = 20, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP = 21, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS = 22, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP = 23, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT = 24, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY = 25, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX = 26, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM = 27, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME = 28, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX = 29, // Version 29 is a fix for a serialization issue with UE4.19 / H17.0/16.5, 29 is actually version 26 minus the version 24 changes... - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_POST_419_SERIALIZATION_FIX = 30, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF = 31, - - //------------------------------------------------------------ - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE = 100, - - // ------------------------------------------------------ - // - this needs to be the last line (see note below) - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE, - VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION = VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE - 1 -}; - -struct FHoudiniCustomSerializationVersion -{ - // The GUID for this custom version number - const static FGuid GUID; - -private: - FHoudiniCustomSerializationVersion() {} -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + + +// Deprecated per-class versions used to load old files +// +// Serialization of parameter name map. +#define VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP 2 +// Serialization of instancer material, if it is available. +#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME 1 +// Serialization of attribute instancer material, if it is available. +#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME 2 +// Landscape serialization in asset inputs. +#define VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT 1 +// Asset instance member. +#define VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER 2 +// World Outliner inputs. +#define VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT 3 + + +enum EHoudiniPluginSerializationVersion +{ + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE = 5, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE = 6, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM = 7, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT = 8, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH = 9, + VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM = 10, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT = 11, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_CUSTOM_LINKER = 12, // added custom linker version to archives + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ACTOR_INSTANCING = 13, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES = 14, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT = 15, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE = 16, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES = 17, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS = 18, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY = 19, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS = 20, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP = 21, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS = 22, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP = 23, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT = 24, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY = 25, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX = 26, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM = 27, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME = 28, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX = 29, // Version 29 is a fix for a serialization issue with UE4.19 / H17.0/16.5, 29 is actually version 26 minus the version 24 changes... + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_POST_419_SERIALIZATION_FIX = 30, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF = 31, + + //------------------------------------------------------------ + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE = 100, + + // ------------------------------------------------------ + // - this needs to be the last line (see note below) + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE, + VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION = VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE - 1 +}; + +struct FHoudiniCustomSerializationVersion +{ + // The GUID for this custom version number + const static FGuid GUID; + +private: + FHoudiniCustomSerializationVersion() {} +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp index 9a8327060..c08b809ca 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp @@ -1,352 +1,352 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniRuntimeSettings.h" - - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Misc/Paths.h" -// #include "Internationalization/Internationalization.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) - : Super( ObjectInitializer ) -{ - // Session options. - SessionType = HRSST_NamedPipe; - ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; - ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; - ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; - bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; - AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; - - bSyncWithHoudiniCook = true; - bCookUsingHoudiniTime = true; - bSyncViewport = false; - bSyncHoudiniViewport = false; - bSyncUnrealViewport = false; - - // Instantiating options. - bShowMultiAssetDialog = true; - - // Cooking options. - bPauseCookingOnStart = false; - bDisplaySlateCookingNotifications = true; - DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - // Parameter options - //bTreatRampParametersAsMultiparms = false; - - // Custom Houdini location. - bUseCustomHoudiniLocation = false; - CustomHoudiniLocation.Path = TEXT(""); - - // Arguments for HAPI_Initialize - CookingThreadStackSize = -1; - - // Landscape marshalling default values. - MarshallingLandscapesUseDefaultUnrealScaling = false; - MarshallingLandscapesUseFullResolution = true; - MarshallingLandscapesForceMinMaxValues = false; - MarshallingLandscapesForcedMinValue = -2000.0f; - MarshallingLandscapesForcedMaxValue = 4553.0f; - - // Spline marshalling - MarshallingSplineResolution = 50.0f; - - // Static mesh proxy refinement settings - bEnableProxyStaticMesh = false; - bShowDefaultMesh = true; - bEnableProxyStaticMeshRefinementByTimer = true; - ProxyMeshAutoRefineTimeoutSeconds = 10.0f; - bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; - bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; - - bPDGAsyncCommandletImportEnabled = false; - - // Legacy settings - bEnableBackwardCompatibility = true; - bAutomaticLegacyHDARebuild = false; -} - -UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() -{} - - -FProperty * -UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const -{ - for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) - { - FProperty * Property = *PropIt; - - if (Property->GetNameCPP() == PropertyName) - return Property; - } - - return nullptr; -} - - -void -UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) -{ - FProperty * Property = LocateProperty(PropertyName); - if (Property) - { - if (bReadOnly) - Property->SetPropertyFlags(CPF_EditConst); - else - Property->ClearPropertyFlags(CPF_EditConst); - } -} - - -void -UHoudiniRuntimeSettings::PostInitProperties() -{ - Super::PostInitProperties(); - - // Set Collision generation options as read only - { - if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Set marshalling attributes options as read only - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - /* - // Set Cook Folder as read-only - { - if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) - Property->SetPropertyFlags( CPF_EditConst ); - } - */ - - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - Property->SetPropertyFlags(CPF_EditConst); - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Disable UI elements depending on current session type. -#if WITH_EDITOR - - UpdateSessionUI(); - -#endif // WITH_EDITOR - - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); -} - - -#if WITH_EDITOR - -void -UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - FProperty * Property = PropertyChangedEvent.MemberProperty; - FProperty * LookupProperty = nullptr; - - if (!Property) - return; - if (Property->GetName() == TEXT("SessionType")) - UpdateSessionUI(); - else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); - else if (Property->GetName() == TEXT("CustomHoudiniLocation")) - { - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; - FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); - - // If path does not point to libHAPI location, we need to let user know. - if (!FPaths::FileExists(LibHAPICustomPath)) - { - FString MessageString = FString::Printf( - TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); - - FPlatformMisc::MessageBoxExt( - EAppMsgType::Ok, *MessageString, - TEXT("Invalid Custom Location Specified, resetting.")); - - CustomHoudiniLocationPath = TEXT(""); - } - } - else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) - { - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->SetPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->SetPropertyFlags(CPF_EditConst); - } - else - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->ClearPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->ClearPropertyFlags(CPF_EditConst); - } - } - - /* - if ( Property->GetName() == TEXT( "bEnableCooking" ) ) - { - // Cooking is disabled, we need to disable transform change triggers cooks option is as well. - if ( bEnableCooking ) - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) - { - // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. - if ( bUploadTransformsToHoudiniEngine ) - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - */ -} - - - -void -UHoudiniRuntimeSettings::UpdateSessionUI() -{ - SetPropertyReadOnly(TEXT("ServerHost"), true); - SetPropertyReadOnly(TEXT("ServerPort"), true); - SetPropertyReadOnly(TEXT("ServerPipeName"), true); - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); - - bool bServerType = false; - - switch (SessionType) - { - case HRSST_Socket: - { - SetPropertyReadOnly(TEXT("ServerHost"), false); - SetPropertyReadOnly(TEXT("ServerPort"), false); - bServerType = true; - break; - } - - case HRSST_NamedPipe: - { - SetPropertyReadOnly(TEXT("ServerPipeName"), false); - bServerType = true; - break; - } - - default: - break; - } - - if (bServerType) - { - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); - } -} - -#endif // WITH_EDITOR - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniRuntimeSettings.h" + + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Misc/Paths.h" +// #include "Internationalization/Internationalization.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // Session options. + SessionType = HRSST_NamedPipe; + ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; + ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; + ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; + bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; + AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; + + bSyncWithHoudiniCook = true; + bCookUsingHoudiniTime = true; + bSyncViewport = false; + bSyncHoudiniViewport = false; + bSyncUnrealViewport = false; + + // Instantiating options. + bShowMultiAssetDialog = true; + + // Cooking options. + bPauseCookingOnStart = false; + bDisplaySlateCookingNotifications = true; + DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + // Parameter options + //bTreatRampParametersAsMultiparms = false; + + // Custom Houdini location. + bUseCustomHoudiniLocation = false; + CustomHoudiniLocation.Path = TEXT(""); + + // Arguments for HAPI_Initialize + CookingThreadStackSize = -1; + + // Landscape marshalling default values. + MarshallingLandscapesUseDefaultUnrealScaling = false; + MarshallingLandscapesUseFullResolution = true; + MarshallingLandscapesForceMinMaxValues = false; + MarshallingLandscapesForcedMinValue = -2000.0f; + MarshallingLandscapesForcedMaxValue = 4553.0f; + + // Spline marshalling + MarshallingSplineResolution = 50.0f; + + // Static mesh proxy refinement settings + bEnableProxyStaticMesh = false; + bShowDefaultMesh = true; + bEnableProxyStaticMeshRefinementByTimer = true; + ProxyMeshAutoRefineTimeoutSeconds = 10.0f; + bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; + bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; + + bPDGAsyncCommandletImportEnabled = false; + + // Legacy settings + bEnableBackwardCompatibility = true; + bAutomaticLegacyHDARebuild = false; +} + +UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() +{} + + +FProperty * +UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const +{ + for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) + { + FProperty * Property = *PropIt; + + if (Property->GetNameCPP() == PropertyName) + return Property; + } + + return nullptr; +} + + +void +UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) +{ + FProperty * Property = LocateProperty(PropertyName); + if (Property) + { + if (bReadOnly) + Property->SetPropertyFlags(CPF_EditConst); + else + Property->ClearPropertyFlags(CPF_EditConst); + } +} + + +void +UHoudiniRuntimeSettings::PostInitProperties() +{ + Super::PostInitProperties(); + + // Set Collision generation options as read only + { + if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Set marshalling attributes options as read only + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + /* + // Set Cook Folder as read-only + { + if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + } + */ + + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + Property->SetPropertyFlags(CPF_EditConst); + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Disable UI elements depending on current session type. +#if WITH_EDITOR + + UpdateSessionUI(); + +#endif // WITH_EDITOR + + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); +} + + +#if WITH_EDITOR + +void +UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + FProperty * LookupProperty = nullptr; + + if (!Property) + return; + if (Property->GetName() == TEXT("SessionType")) + UpdateSessionUI(); + else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); + else if (Property->GetName() == TEXT("CustomHoudiniLocation")) + { + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; + FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); + + // If path does not point to libHAPI location, we need to let user know. + if (!FPaths::FileExists(LibHAPICustomPath)) + { + FString MessageString = FString::Printf( + TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); + + FPlatformMisc::MessageBoxExt( + EAppMsgType::Ok, *MessageString, + TEXT("Invalid Custom Location Specified, resetting.")); + + CustomHoudiniLocationPath = TEXT(""); + } + } + else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) + { + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->SetPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->SetPropertyFlags(CPF_EditConst); + } + else + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->ClearPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->ClearPropertyFlags(CPF_EditConst); + } + } + + /* + if ( Property->GetName() == TEXT( "bEnableCooking" ) ) + { + // Cooking is disabled, we need to disable transform change triggers cooks option is as well. + if ( bEnableCooking ) + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) + { + // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. + if ( bUploadTransformsToHoudiniEngine ) + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + */ +} + + + +void +UHoudiniRuntimeSettings::UpdateSessionUI() +{ + SetPropertyReadOnly(TEXT("ServerHost"), true); + SetPropertyReadOnly(TEXT("ServerPort"), true); + SetPropertyReadOnly(TEXT("ServerPipeName"), true); + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); + + bool bServerType = false; + + switch (SessionType) + { + case HRSST_Socket: + { + SetPropertyReadOnly(TEXT("ServerHost"), false); + SetPropertyReadOnly(TEXT("ServerPort"), false); + bServerType = true; + break; + } + + case HRSST_NamedPipe: + { + SetPropertyReadOnly(TEXT("ServerPipeName"), false); + bServerType = true; + break; + } + + default: + break; + } + + if (bServerType) + { + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); + } +} + +#endif // WITH_EDITOR + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h index 84476cd9d..89df901c0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h @@ -1,277 +1,277 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Object.h" -#include "Engine/EngineTypes.h" - -#include "HoudiniRuntimeSettings.generated.h" - -UENUM() -enum EHoudiniRuntimeSettingsSessionType -{ - // In process session. - HRSST_InProcess UMETA(Hidden), - - // TCP socket connection to Houdini Engine server. - HRSST_Socket UMETA(DisplayName = "TCP socket"), - - // Connection to Houdini Engine server via pipe connection. - HRSST_NamedPipe UMETA(DisplayName = "Named pipe or domain socket"), - - // No session, prevents license/Engine cook - HRSST_None UMETA(DisplayName = "None"), - - HRSST_MAX -}; - -UCLASS(config = Engine, defaultconfig) -class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject -{ - GENERATED_UCLASS_BODY() - - public: - - // Destructor. - virtual ~UHoudiniRuntimeSettings(); - - // - virtual void PostInitProperties() override; - -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; -#endif - -protected: - - // Locate property of this class by name. - FProperty * LocateProperty(const FString & PropertyName) const; - - // Make specified property read only. - void SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly = true); - -#if WITH_EDITOR - // Update session ui elements. - void UpdateSessionUI(); -#endif - - public: - - //------------------------------------------------------------------------------------------------------------- - // Session options. - //------------------------------------------------------------------------------------------------------------- - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - TEnumAsByte SessionType; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - FString ServerHost; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - int32 ServerPort; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - FString ServerPipeName; - - // Whether to automatically start a HARS process - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - bool bStartAutomaticServer; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - float AutomaticServerTimeout; - - // If enabled, changes made in Houdini, when connected to Houdini running in Session Sync mode will be automatically be pushed to Unreal. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bSyncWithHoudiniCook; - - // If enabled, the Houdini Timeline time will be used to cook assets. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bCookUsingHoudiniTime; - - // Enable when wanting to sync the Houdini and Unreal viewport when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bSyncViewport; - - // If enabled, Houdini's viewport will be synchronized to Unreal's when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Houdini Viewport to Unreal's viewport.", EditCondition = "bSyncViewport")) - bool bSyncHoudiniViewport; - - // If enabled, Unreal's viewport will be synchronized to Houdini's when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Unreal Viewport to Houdini's viewport", EditCondition = "bSyncViewport")) - bool bSyncUnrealViewport; - - //------------------------------------------------------------------------------------------------------------- - // Instantiating options. - //------------------------------------------------------------------------------------------------------------- - - // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. - // TODO: PORT THE DIALOG!! - UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) - bool bShowMultiAssetDialog; - - //------------------------------------------------------------------------------------------------------------- - // Cooking options. - //------------------------------------------------------------------------------------------------------------- - - // Whether houdini engine cooking is paused or not upon initializing the plugin - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - bool bPauseCookingOnStart; - - // Whether to display instantiation and cooking Slate notifications. - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - bool bDisplaySlateCookingNotifications; - - // Default content folder storing all the temporary cook data (Static meshes, materials, textures, landscape layer infos...) - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - FString DefaultTemporaryCookFolder; - - // Default content folder used when baking houdini asset data to native unreal objects - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - FString DefaultBakeFolder; - - //------------------------------------------------------------------------------------------------------------- - // Parameter options. - //------------------------------------------------------------------------------------------------------------- - - /* Deprecated! - // Forces the treatment of ramp parameters as multiparms. - UPROPERTY(GlobalConfig, EditAnywhere, Category = Parameters) - bool bTreatRampParametersAsMultiparms; - */ - - //------------------------------------------------------------------------------------------------------------- - // Geometry Marshalling - //------------------------------------------------------------------------------------------------------------- - - // If true, generated Landscapes will be marshalled using default unreal scaling. - // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms - // as Unreal's default landscape - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - bool MarshallingLandscapesUseDefaultUnrealScaling; - // If true, generated Landscapes will be using full precision for their ZAxis, - // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - bool MarshallingLandscapesUseFullResolution; - // If true, the min/max values used to convert heightfields to landscape will be forced values - // This is usefull when importing multiple landscapes from different HDAs - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - bool MarshallingLandscapesForceMinMaxValues; - // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - float MarshallingLandscapesForcedMinValue; - // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - float MarshallingLandscapesForcedMaxValue; - - // Default resolution used when converting Unreal Spline Components to Houdini Curves (step in cm between control points, 0 only send the control points) - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - float MarshallingSplineResolution; - - //------------------------------------------------------------------------------------------------------------- - // Static Mesh Options - //------------------------------------------------------------------------------------------------------------- - - // For StaticMesh outputs: should a fast proxy be created first? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Enable Proxy Static Mesh")) - bool bEnableProxyStaticMesh; - - // For static mesh outputs and socket actors: should spawn a default actor if the reference is invalid? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Show Default Mesh")) - bool bShowDefaultMesh; - - // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes After a Timeout", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementByTimer; - - // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bEnableProxyStaticMesh && bEnableProxyStaticMeshRefinementByTimer")) - float ProxyMeshAutoRefineTimeoutSeconds; - - // Automatically refine proxy meshes to UStaticMesh before the map is saved - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes When Saving a Map", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementOnPreSaveWorld; - - // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes On PIE", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementOnPreBeginPIE; - - //------------------------------------------------------------------------------------------------------------- - // Legacy - //------------------------------------------------------------------------------------------------------------- - // Whether to enable backward compatibility - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", Meta = (DisplayName = "Enable backward compatibility with Version 1")) - bool bEnableBackwardCompatibility; - - // Automatically rebuild legacy HAC - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", meta = (DisplayName = "Automatically rebuild legacy Houdini Asset Components", EditCondition = "bEnableBackwardCompatibility")) - bool bAutomaticLegacyHDARebuild; - - //------------------------------------------------------------------------------------------------------------- - // Custom Houdini Location - //------------------------------------------------------------------------------------------------------------- - // Whether to use custom Houdini location. - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Use custom Houdini location (requires restart)")) - bool bUseCustomHoudiniLocation; - - // Custom Houdini location (where HAPI library is located). - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Custom Houdini location")) - FDirectoryPath CustomHoudiniLocation; - - //------------------------------------------------------------------------------------------------------------- - // HAPI_Initialize - //------------------------------------------------------------------------------------------------------------- - // Evaluation thread stack size in bytes. -1 for default - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - int32 CookingThreadStackSize; - - // List of paths to Houdini-compatible .env files (; separated on Windows, : otherwise) - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString HoudiniEnvironmentFiles; - - // Path to find other OTL/HDA files - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString OtlSearchPath; - - // Sets HOUDINI_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString DsoSearchPath; - - // Sets HOUDINI_IMAGE_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString ImageDsoSearchPath; - - // Sets HOUDINI_AUDIO_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString AudioDsoSearchPath; - - //------------------------------------------------------------------------------------------------------------- - // PDG Commandlet import - //------------------------------------------------------------------------------------------------------------- - // Is the PDG commandlet enabled? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta=(DisplayName="Async Importer Enabled")) - bool bPDGAsyncCommandletImportEnabled; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" + +#include "HoudiniRuntimeSettings.generated.h" + +UENUM() +enum EHoudiniRuntimeSettingsSessionType +{ + // In process session. + HRSST_InProcess UMETA(Hidden), + + // TCP socket connection to Houdini Engine server. + HRSST_Socket UMETA(DisplayName = "TCP socket"), + + // Connection to Houdini Engine server via pipe connection. + HRSST_NamedPipe UMETA(DisplayName = "Named pipe or domain socket"), + + // No session, prevents license/Engine cook + HRSST_None UMETA(DisplayName = "None"), + + HRSST_MAX +}; + +UCLASS(config = Engine, defaultconfig) +class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject +{ + GENERATED_UCLASS_BODY() + + public: + + // Destructor. + virtual ~UHoudiniRuntimeSettings(); + + // + virtual void PostInitProperties() override; + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; +#endif + +protected: + + // Locate property of this class by name. + FProperty * LocateProperty(const FString & PropertyName) const; + + // Make specified property read only. + void SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly = true); + +#if WITH_EDITOR + // Update session ui elements. + void UpdateSessionUI(); +#endif + + public: + + //------------------------------------------------------------------------------------------------------------- + // Session options. + //------------------------------------------------------------------------------------------------------------- + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + TEnumAsByte SessionType; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + FString ServerHost; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + int32 ServerPort; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + FString ServerPipeName; + + // Whether to automatically start a HARS process + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + bool bStartAutomaticServer; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + float AutomaticServerTimeout; + + // If enabled, changes made in Houdini, when connected to Houdini running in Session Sync mode will be automatically be pushed to Unreal. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bSyncWithHoudiniCook; + + // If enabled, the Houdini Timeline time will be used to cook assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bCookUsingHoudiniTime; + + // Enable when wanting to sync the Houdini and Unreal viewport when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bSyncViewport; + + // If enabled, Houdini's viewport will be synchronized to Unreal's when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Houdini Viewport to Unreal's viewport.", EditCondition = "bSyncViewport")) + bool bSyncHoudiniViewport; + + // If enabled, Unreal's viewport will be synchronized to Houdini's when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Unreal Viewport to Houdini's viewport", EditCondition = "bSyncViewport")) + bool bSyncUnrealViewport; + + //------------------------------------------------------------------------------------------------------------- + // Instantiating options. + //------------------------------------------------------------------------------------------------------------- + + // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. + // TODO: PORT THE DIALOG!! + UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) + bool bShowMultiAssetDialog; + + //------------------------------------------------------------------------------------------------------------- + // Cooking options. + //------------------------------------------------------------------------------------------------------------- + + // Whether houdini engine cooking is paused or not upon initializing the plugin + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + bool bPauseCookingOnStart; + + // Whether to display instantiation and cooking Slate notifications. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + bool bDisplaySlateCookingNotifications; + + // Default content folder storing all the temporary cook data (Static meshes, materials, textures, landscape layer infos...) + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + FString DefaultTemporaryCookFolder; + + // Default content folder used when baking houdini asset data to native unreal objects + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + FString DefaultBakeFolder; + + //------------------------------------------------------------------------------------------------------------- + // Parameter options. + //------------------------------------------------------------------------------------------------------------- + + /* Deprecated! + // Forces the treatment of ramp parameters as multiparms. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Parameters) + bool bTreatRampParametersAsMultiparms; + */ + + //------------------------------------------------------------------------------------------------------------- + // Geometry Marshalling + //------------------------------------------------------------------------------------------------------------- + + // If true, generated Landscapes will be marshalled using default unreal scaling. + // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms + // as Unreal's default landscape + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + bool MarshallingLandscapesUseDefaultUnrealScaling; + // If true, generated Landscapes will be using full precision for their ZAxis, + // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + bool MarshallingLandscapesUseFullResolution; + // If true, the min/max values used to convert heightfields to landscape will be forced values + // This is usefull when importing multiple landscapes from different HDAs + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + bool MarshallingLandscapesForceMinMaxValues; + // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + float MarshallingLandscapesForcedMinValue; + // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + float MarshallingLandscapesForcedMaxValue; + + // Default resolution used when converting Unreal Spline Components to Houdini Curves (step in cm between control points, 0 only send the control points) + UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) + float MarshallingSplineResolution; + + //------------------------------------------------------------------------------------------------------------- + // Static Mesh Options + //------------------------------------------------------------------------------------------------------------- + + // For StaticMesh outputs: should a fast proxy be created first? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Enable Proxy Static Mesh")) + bool bEnableProxyStaticMesh; + + // For static mesh outputs and socket actors: should spawn a default actor if the reference is invalid? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Show Default Mesh")) + bool bShowDefaultMesh; + + // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes After a Timeout", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementByTimer; + + // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bEnableProxyStaticMesh && bEnableProxyStaticMeshRefinementByTimer")) + float ProxyMeshAutoRefineTimeoutSeconds; + + // Automatically refine proxy meshes to UStaticMesh before the map is saved + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes When Saving a Map", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementOnPreSaveWorld; + + // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes On PIE", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementOnPreBeginPIE; + + //------------------------------------------------------------------------------------------------------------- + // Legacy + //------------------------------------------------------------------------------------------------------------- + // Whether to enable backward compatibility + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", Meta = (DisplayName = "Enable backward compatibility with Version 1")) + bool bEnableBackwardCompatibility; + + // Automatically rebuild legacy HAC + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", meta = (DisplayName = "Automatically rebuild legacy Houdini Asset Components", EditCondition = "bEnableBackwardCompatibility")) + bool bAutomaticLegacyHDARebuild; + + //------------------------------------------------------------------------------------------------------------- + // Custom Houdini Location + //------------------------------------------------------------------------------------------------------------- + // Whether to use custom Houdini location. + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Use custom Houdini location (requires restart)")) + bool bUseCustomHoudiniLocation; + + // Custom Houdini location (where HAPI library is located). + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Custom Houdini location")) + FDirectoryPath CustomHoudiniLocation; + + //------------------------------------------------------------------------------------------------------------- + // HAPI_Initialize + //------------------------------------------------------------------------------------------------------------- + // Evaluation thread stack size in bytes. -1 for default + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + int32 CookingThreadStackSize; + + // List of paths to Houdini-compatible .env files (; separated on Windows, : otherwise) + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString HoudiniEnvironmentFiles; + + // Path to find other OTL/HDA files + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString OtlSearchPath; + + // Sets HOUDINI_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString DsoSearchPath; + + // Sets HOUDINI_IMAGE_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString ImageDsoSearchPath; + + // Sets HOUDINI_AUDIO_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString AudioDsoSearchPath; + + //------------------------------------------------------------------------------------------------------------- + // PDG Commandlet import + //------------------------------------------------------------------------------------------------------------- + // Is the PDG commandlet enabled? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta=(DisplayName="Async Importer Enabled")) + bool bPDGAsyncCommandletImportEnabled; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp index 1d71bd834..47a0b2e14 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp @@ -1,689 +1,689 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineComponent.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniInput.h" -#include "HoudiniInputObject.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Components/MeshComponent.h" -#include "Algo/Reverse.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -void -UHoudiniSplineComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on HOUDINI_ENGINE_ENABLE_BACKWARD_COMPATIBILITY - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniSplineComponent_V1* CompatibilitySC = NewObject(); - CompatibilitySC->Serialize(Ar); - CompatibilitySC->UpdateFromLegacyData(this); - - Construct(CompatibilitySC->CurveDisplayPoints); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -UHoudiniSplineComponent::UHoudiniSplineComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , bClosed(false) - , bReversed(false) - , bIsHoudiniSplineVisible(true) - , CurveType(EHoudiniCurveType::Polygon) - , CurveMethod(EHoudiniCurveMethod::CVs) - , bHasChanged(false) - , bNeedsToTriggerUpdate(false) - , bIsInputCurve(false) - , bIsEditableOutputCurve(false) - , NodeId(-1) -{ - - // Add two default points to the curve - FTransform defaultPoint = FTransform::Identity; - - // Set this component to not tick? - // SetComponentTickEnabled(false); - - // Default curve. - CurvePoints.Add(defaultPoint); - DisplayPoints.Add(defaultPoint.GetLocation()); - - defaultPoint.SetTranslation(FVector(200.f, 0.f, 0.f)); - CurvePoints.Add(defaultPoint); - DisplayPoints.Add(defaultPoint.GetLocation()); - - bIsOutputCurve = false; - bCookOnCurveChanged = true; - -#if WITH_EDITOR - bPostUndo = false; -#endif -} - -void -UHoudiniSplineComponent::Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint) -{ - DisplayPoints.Empty(); - DisplayPointIndexDivider.Empty(); - - float DisplayPointStepSize; - - // Resample the display points for linear curve. - - if (InCurveDisplayPoints.Num() <= 0) - return; - - // Add an additional displaypoint to the end for closed curve - if (bClosed && InCurveDisplayPoints.Num() > 2) - { - FVector & FirstPoint = InCurveDisplayPoints[0]; - FVector ClosingPoint; - ClosingPoint.X = FirstPoint.X; - ClosingPoint.Y = FirstPoint.Y; - ClosingPoint.Z = FirstPoint.Z; - - InCurveDisplayPoints.Add(ClosingPoint); - } - - - if (CurveType == EHoudiniCurveType::Polygon) - { - FVector Pt1, Pt2; - Pt1 = InCurveDisplayPoints[0]; - int32 CurrentDisplayPointIndex = 0; - - for (int Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) - { - DisplayPointStepSize = 10.f; - - Pt2 = InCurveDisplayPoints[Index]; - - FVector Direction = Pt2 - Pt1; - - float SegmentLength = Direction.Size(); - - int32 NumOfDisplayPt = SegmentLength / DisplayPointStepSize; - - // Make sure there are at least 20 display points on a segment. - while( NumOfDisplayPt < 20 && SegmentLength > 0.01) - { - DisplayPointStepSize /= 2.f; - NumOfDisplayPt = SegmentLength / DisplayPointStepSize; - } - - Direction.Normalize(0.01f); - - FVector StepVector = Direction * DisplayPointStepSize; - - // Always add the start point of a line segment - FVector NextDisplayPt = Pt1; - if (NumOfDisplayPt == 0) DisplayPoints.Add(NextDisplayPt); - - for (int32 itr = 0; itr < NumOfDisplayPt; ++itr) - { - DisplayPoints.Add(NextDisplayPt); - NextDisplayPt += StepVector; - CurrentDisplayPointIndex += 1; - } - - DisplayPointIndexDivider.Add(CurrentDisplayPointIndex); - - Pt1 = Pt2; - } - - // Add the ending point - DisplayPoints.Add(Pt1); - // Duplicate the last index, to make the DisplaPointyIndexDivider array matches the length of DP array - DisplayPointIndexDivider.Add(CurrentDisplayPointIndex + 1); - } - else if (CurveType == EHoudiniCurveType::Points) - { - // do not add display points for Points curve type, just show the CVs - } - else - { - // Needs a better algorithm to divide the display points - // Refined display points does not strictly interpolate the curve points. - - FVector Pt1, Pt2; - Pt1 = InCurveDisplayPoints[0]; - - int Itr = 1; - - int32 ClosestIndex = -1; - float ClosestDistance = -1.f; - - for (int32 Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) - { - if (Itr >= CurvePoints.Num()) break; - - Pt2 = InCurveDisplayPoints[Index]; - - FVector ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); - - float Distance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); - - if (ClosestDistance < 0.f || Distance < ClosestDistance) - { - ClosestDistance = Distance; - ClosestIndex = Index; - } - else - { - Itr += 1; - if (Itr >= CurvePoints.Num()) break; - - DisplayPointIndexDivider.Add(Index-1); - - ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); - ClosestDistance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); - - } - } - - DisplayPointIndexDivider.Add(InCurveDisplayPoints.Num()); - - DisplayPoints = InCurveDisplayPoints; - } -} - - -void -UHoudiniSplineComponent::CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent) -{ - if (!OtherHoudiniSplineComponent) - return; - - CurvePoints = OtherHoudiniSplineComponent->CurvePoints; - DisplayPoints = OtherHoudiniSplineComponent->DisplayPoints; - DisplayPointIndexDivider = OtherHoudiniSplineComponent->DisplayPointIndexDivider; - CurveType = OtherHoudiniSplineComponent->CurveType; - CurveMethod = OtherHoudiniSplineComponent->CurveMethod; - bClosed = OtherHoudiniSplineComponent->bClosed; -#if WITH_EDITORONLY_DATA - bVisualizeComponent = OtherHoudiniSplineComponent->bVisualizeComponent; -#endif - bReversed = OtherHoudiniSplineComponent->bReversed; - SetVisibility(OtherHoudiniSplineComponent->IsVisible()); - - HoudiniSplineName = OtherHoudiniSplineComponent->HoudiniSplineName; -} - -UHoudiniSplineComponent::~UHoudiniSplineComponent() -{} - -void -UHoudiniSplineComponent::AppendPoint(const FTransform& NewPoint) -{ - CurvePoints.Add(NewPoint); -} - -void -UHoudiniSplineComponent::InsertPointAtIndex(const FTransform& NewPoint, const int32& Index) -{ - check(Index >= 0 && Index < CurvePoints.Num()); - CurvePoints.Insert(NewPoint, Index); - bHasChanged = true; -} - - -void -UHoudiniSplineComponent::RemovePointAtIndex(const int32& Index) -{ - check(Index >= 0 && Index < CurvePoints.Num()); - CurvePoints.RemoveAt(Index); - bHasChanged = true; -} - -void -UHoudiniSplineComponent::SetReversed(const bool& InReversed) -{ - // don't need to do anything if the reversed state doesn't change. - if (InReversed == bReversed) - return; - - bReversed = InReversed; - ReverseCurvePoints(); - MarkChanged(true); -} - -void -UHoudiniSplineComponent::ReverseCurvePoints() -{ - if (CurvePoints.Num() < 2) - return; - - Algo::Reverse(CurvePoints); -} - -void -UHoudiniSplineComponent::EditPointAtindex(const FTransform& NewPoint, const int32& Index) -{ - if (!CurvePoints.IsValidIndex(Index)) - return; - - CurvePoints[Index] = NewPoint; - bHasChanged = true; -} - -#if WITH_EDITOR -void -UHoudiniSplineComponent::PostEditChangeProperty(FPropertyChangedEvent& PeopertyChangedEvent) -{ - Super::PostEditChangeProperty(PeopertyChangedEvent); - - FName PropertyName = (PeopertyChangedEvent.Property != nullptr) ? PeopertyChangedEvent.Property->GetFName() : NAME_None; - - // Responses to the uproperty changes - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bClosed)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bClosed")); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bReversed)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bReversed")); - ReverseCurvePoints(); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveType)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveType")); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveMethod)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveMethod")); - MarkChanged(true); - } -} -#endif - -void -UHoudiniSplineComponent::PostLoad() -{ - Super::PostLoad(); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::PostLoad()] Component: %s"), *GetPathName()); - -} - -TStructOnScope -UHoudiniSplineComponent::GetComponentInstanceData() const -{ - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::GetComponentInstanceData()] Component: %s"), *GetPathName()); - TStructOnScope ComponentInstanceData = MakeStructOnScope(this); - FHoudiniSplineComponentInstanceData* InstanceData = ComponentInstanceData.Cast(); - - - // NOTE: We need to capture these properties here before the component gets torn down - // since the Spline visualizer changed values on the instance directly and is not present on the - // template yet. - /*InstanceData->CurvePoints = CurvePoints; - InstanceData->DisplayPoints = DisplayPoints; - InstanceData->DisplayPointIndexDivider = DisplayPointIndexDivider; - -#if WITH_EDITOR_DATA - InstanceData->EditedControlPointsIndexes = EditedControlPointsIndexes; -#endif*/ - - return ComponentInstanceData; -} - -void -UHoudiniSplineComponent::ApplyComponentInstanceData(FHoudiniSplineComponentInstanceData* ComponentInstanceData, - const bool bPostUCS) -{ - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %s"), *GetPathName()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %p"), this); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] bHiddenInGame: %d"), bHiddenInGame); - - check(ComponentInstanceData); - - if (!bPostUCS) - { - //bHasChanged = ComponentInstanceData->bHasChanged; - //bNeedsToTriggerUpdate = ComponentInstanceData->bNeedsToTriggerUpdate; - /*CurvePoints = ComponentInstanceData->CurvePoints; - DisplayPoints = ComponentInstanceData->DisplayPoints; - DisplayPointIndexDivider = ComponentInstanceData->DisplayPointIndexDivider; - -#if WITH_EDITOR_DATA - EditedControlPointsIndexes = ComponentInstanceData->EditedControlPointsIndexes; -#endif*/ - } -} - -void -UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) -{ - // Capture properties that we want to preserve during copy - const int32 PrevNodeId = NodeId; - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - bHiddenInGame: %d"), bHiddenInGame); - - UActorComponent* FromComponent = Cast(FromObject); - check(FromComponent); - - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - //Params.bDoDelta = false; // Perform a deep copy - //Params.bClearReferences = false; - //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, this, Params); - - /*const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); - FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, this, ComponentCopyOptions);*/ - - UHoudiniSplineComponent* FromSplineComponent = Cast(FromObject); - if (FromSplineComponent) - { - CurvePoints = FromSplineComponent->CurvePoints; - DisplayPoints = FromSplineComponent->DisplayPoints; - DisplayPointIndexDivider = FromSplineComponent->DisplayPointIndexDivider; -#if WITH_EDITORONLY_DATA - EditedControlPointsIndexes = FromSplineComponent->EditedControlPointsIndexes; -#endif - - HoudiniSplineName = FromSplineComponent->HoudiniSplineName; - bClosed = FromSplineComponent->bClosed; - bIsHoudiniSplineVisible = FromSplineComponent->bIsHoudiniSplineVisible; - CurveType = FromSplineComponent->CurveType; - CurveMethod = FromSplineComponent->CurveMethod; - bIsInputCurve = FromSplineComponent->bIsInputCurve; - bIsOutputCurve = FromSplineComponent->bIsOutputCurve; - bIsEditableOutputCurve = FromSplineComponent->bIsEditableOutputCurve; - bCookOnCurveChanged = FromSplineComponent->bCookOnCurveChanged; - - - - bHasChanged = FromSplineComponent->bHasChanged; - bNeedsToTriggerUpdate = FromSplineComponent->bNeedsToTriggerUpdate; - } - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - bHiddenInGame: %d"), bHiddenInGame); - - // Restore properties that we want to preserve - NodeId = PrevNodeId; -} - - -void -UHoudiniSplineComponent::OnUnregister() -{ - Super::OnUnregister(); -} - -void -UHoudiniSplineComponent::OnComponentCreated() -{ - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] Component: %s"), *GetPathName()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bHiddenInGame: %d"), bHiddenInGame); - Super::OnComponentCreated(); -} - -void -UHoudiniSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - Super::OnComponentDestroyed(bDestroyingHierarchy); - - if (IsInputCurve()) - { - // This component can't just come out of nowhere and decide to delete an input object! - // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! - - // InputObject->MarkPendingKill(); - - // if(NodeId > -1) - // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); - - SetNodeId(-1); // Set nodeId to invalid for reconstruct on re-do - } -} - - -#if WITH_EDITOR -void -UHoudiniSplineComponent::PostEditUndo() -{ - Super::PostEditUndo(); - - bPostUndo = true; - - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return; - // - // CurrentInputObject->MarkChanged(true); - // } - // - // if (bIsEditableOutputCurve) - // { - // MarkChanged(true); - // } - - MarkChanged(true); - -} -#endif - -void -UHoudiniSplineComponent::SetOffset(const float& Offset) -{ - for (int n = 0; n < CurvePoints.Num(); ++n) - CurvePoints[n].AddToTranslation(FVector(0.f, Offset, 0.f)); - - for (int n = 0; n < DisplayPoints.Num(); ++n) - DisplayPoints[n] += FVector(0.f, Offset, 0.f); -} - -void -UHoudiniSplineComponent::ResetCurvePoints() -{ - CurvePoints.Empty(); -} - -void -UHoudiniSplineComponent::ResetDisplayPoints() -{ - DisplayPoints.Empty(); -} - -void -UHoudiniSplineComponent::AddCurvePoints(const TArray& Points) -{ - CurvePoints.Append(Points); -} - -void -UHoudiniSplineComponent::AddDisplayPoints(const TArray& Points) -{ - DisplayPoints.Append(Points); -} - -bool -UHoudiniSplineComponent::NeedsToTriggerUpdate() const -{ - return bNeedsToTriggerUpdate; - - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return false; - // - // return CurrentInputObject->NeedsToTriggerUpdate(); - // } - // - // if (bIsEditableOutputCurve) - // { - // return bNeedsToTriggerUpdate; - // } - // - // return false; -} - -void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate) -{ - bNeedsToTriggerUpdate = NeedsToTriggerUpdate; -} - -void UHoudiniSplineComponent::SetCurveType(const EHoudiniCurveType & NewCurveType) -{ - CurveType = NewCurveType; -#if WITH_EDITOR - //FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(this, TEXT("CurveType")); -#endif -} - -void -UHoudiniSplineComponent::MarkInputObjectChanged() -{ - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return; - // - // if (HasChanged()) - // CurrentInputObject->MarkChanged(true); - // } - // - // if (bIsEditableOutputCurve) - // { - // if (HasChanged()) - // MarkChanged(true); - // } - - // NOTE: This component should be trying to push ANY state changes to Input or Output objects. This - // component should strictly be a data container. Input / Output objects that reference this component should - // be polling this component's state. - MarkChanged(true); -} - -bool UHoudiniSplineComponent::HasChanged() const -{ - return bHasChanged; -} - -void UHoudiniSplineComponent::MarkChanged(const bool& Changed) -{ - bHasChanged = Changed; - bNeedsToTriggerUpdate = Changed; -} - -// UHoudiniAssetComponent* -// UHoudiniSplineComponent::GetParentHAC() -// { -// UHoudiniAssetComponent* ParentHAC = nullptr; -// if (bIsInputCurve) -// { -// if (!InputObject) -// return nullptr; -// -// UHoudiniInput* Input = Cast(InputObject->GetOuter()); -// if (!Input) -// return nullptr; -// -// ParentHAC = Cast(Input->GetOuter()); -// } -// else -// { -// // may do something else if this is not an input curve instead of returning Null. -// } -// -// return ParentHAC; -// -// } - -FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() -{ -} - -FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent) - : FActorComponentInstanceData(SourceComponent) -{ -} - - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniInput.h" +#include "HoudiniInputObject.h" +#include "HoudiniPluginSerializationVersion.h" + +#include "Components/MeshComponent.h" +#include "Algo/Reverse.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +void +UHoudiniSplineComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on HOUDINI_ENGINE_ENABLE_BACKWARD_COMPATIBILITY + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniSplineComponent_V1* CompatibilitySC = NewObject(); + CompatibilitySC->Serialize(Ar); + CompatibilitySC->UpdateFromLegacyData(this); + + Construct(CompatibilitySC->CurveDisplayPoints); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +UHoudiniSplineComponent::UHoudiniSplineComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , bClosed(false) + , bReversed(false) + , bIsHoudiniSplineVisible(true) + , CurveType(EHoudiniCurveType::Polygon) + , CurveMethod(EHoudiniCurveMethod::CVs) + , bHasChanged(false) + , bNeedsToTriggerUpdate(false) + , bIsInputCurve(false) + , bIsEditableOutputCurve(false) + , NodeId(-1) +{ + + // Add two default points to the curve + FTransform defaultPoint = FTransform::Identity; + + // Set this component to not tick? + // SetComponentTickEnabled(false); + + // Default curve. + CurvePoints.Add(defaultPoint); + DisplayPoints.Add(defaultPoint.GetLocation()); + + defaultPoint.SetTranslation(FVector(200.f, 0.f, 0.f)); + CurvePoints.Add(defaultPoint); + DisplayPoints.Add(defaultPoint.GetLocation()); + + bIsOutputCurve = false; + bCookOnCurveChanged = true; + +#if WITH_EDITOR + bPostUndo = false; +#endif +} + +void +UHoudiniSplineComponent::Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint) +{ + DisplayPoints.Empty(); + DisplayPointIndexDivider.Empty(); + + float DisplayPointStepSize; + + // Resample the display points for linear curve. + + if (InCurveDisplayPoints.Num() <= 0) + return; + + // Add an additional displaypoint to the end for closed curve + if (bClosed && InCurveDisplayPoints.Num() > 2) + { + FVector & FirstPoint = InCurveDisplayPoints[0]; + FVector ClosingPoint; + ClosingPoint.X = FirstPoint.X; + ClosingPoint.Y = FirstPoint.Y; + ClosingPoint.Z = FirstPoint.Z; + + InCurveDisplayPoints.Add(ClosingPoint); + } + + + if (CurveType == EHoudiniCurveType::Polygon) + { + FVector Pt1, Pt2; + Pt1 = InCurveDisplayPoints[0]; + int32 CurrentDisplayPointIndex = 0; + + for (int Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) + { + DisplayPointStepSize = 10.f; + + Pt2 = InCurveDisplayPoints[Index]; + + FVector Direction = Pt2 - Pt1; + + float SegmentLength = Direction.Size(); + + int32 NumOfDisplayPt = SegmentLength / DisplayPointStepSize; + + // Make sure there are at least 20 display points on a segment. + while( NumOfDisplayPt < 20 && SegmentLength > 0.01) + { + DisplayPointStepSize /= 2.f; + NumOfDisplayPt = SegmentLength / DisplayPointStepSize; + } + + Direction.Normalize(0.01f); + + FVector StepVector = Direction * DisplayPointStepSize; + + // Always add the start point of a line segment + FVector NextDisplayPt = Pt1; + if (NumOfDisplayPt == 0) DisplayPoints.Add(NextDisplayPt); + + for (int32 itr = 0; itr < NumOfDisplayPt; ++itr) + { + DisplayPoints.Add(NextDisplayPt); + NextDisplayPt += StepVector; + CurrentDisplayPointIndex += 1; + } + + DisplayPointIndexDivider.Add(CurrentDisplayPointIndex); + + Pt1 = Pt2; + } + + // Add the ending point + DisplayPoints.Add(Pt1); + // Duplicate the last index, to make the DisplaPointyIndexDivider array matches the length of DP array + DisplayPointIndexDivider.Add(CurrentDisplayPointIndex + 1); + } + else if (CurveType == EHoudiniCurveType::Points) + { + // do not add display points for Points curve type, just show the CVs + } + else + { + // Needs a better algorithm to divide the display points + // Refined display points does not strictly interpolate the curve points. + + FVector Pt1, Pt2; + Pt1 = InCurveDisplayPoints[0]; + + int Itr = 1; + + int32 ClosestIndex = -1; + float ClosestDistance = -1.f; + + for (int32 Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) + { + if (Itr >= CurvePoints.Num()) break; + + Pt2 = InCurveDisplayPoints[Index]; + + FVector ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); + + float Distance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); + + if (ClosestDistance < 0.f || Distance < ClosestDistance) + { + ClosestDistance = Distance; + ClosestIndex = Index; + } + else + { + Itr += 1; + if (Itr >= CurvePoints.Num()) break; + + DisplayPointIndexDivider.Add(Index-1); + + ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); + ClosestDistance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); + + } + } + + DisplayPointIndexDivider.Add(InCurveDisplayPoints.Num()); + + DisplayPoints = InCurveDisplayPoints; + } +} + + +void +UHoudiniSplineComponent::CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent) +{ + if (!OtherHoudiniSplineComponent) + return; + + CurvePoints = OtherHoudiniSplineComponent->CurvePoints; + DisplayPoints = OtherHoudiniSplineComponent->DisplayPoints; + DisplayPointIndexDivider = OtherHoudiniSplineComponent->DisplayPointIndexDivider; + CurveType = OtherHoudiniSplineComponent->CurveType; + CurveMethod = OtherHoudiniSplineComponent->CurveMethod; + bClosed = OtherHoudiniSplineComponent->bClosed; +#if WITH_EDITORONLY_DATA + bVisualizeComponent = OtherHoudiniSplineComponent->bVisualizeComponent; +#endif + bReversed = OtherHoudiniSplineComponent->bReversed; + SetVisibility(OtherHoudiniSplineComponent->IsVisible()); + + HoudiniSplineName = OtherHoudiniSplineComponent->HoudiniSplineName; +} + +UHoudiniSplineComponent::~UHoudiniSplineComponent() +{} + +void +UHoudiniSplineComponent::AppendPoint(const FTransform& NewPoint) +{ + CurvePoints.Add(NewPoint); +} + +void +UHoudiniSplineComponent::InsertPointAtIndex(const FTransform& NewPoint, const int32& Index) +{ + check(Index >= 0 && Index < CurvePoints.Num()); + CurvePoints.Insert(NewPoint, Index); + bHasChanged = true; +} + + +void +UHoudiniSplineComponent::RemovePointAtIndex(const int32& Index) +{ + check(Index >= 0 && Index < CurvePoints.Num()); + CurvePoints.RemoveAt(Index); + bHasChanged = true; +} + +void +UHoudiniSplineComponent::SetReversed(const bool& InReversed) +{ + // don't need to do anything if the reversed state doesn't change. + if (InReversed == bReversed) + return; + + bReversed = InReversed; + ReverseCurvePoints(); + MarkChanged(true); +} + +void +UHoudiniSplineComponent::ReverseCurvePoints() +{ + if (CurvePoints.Num() < 2) + return; + + Algo::Reverse(CurvePoints); +} + +void +UHoudiniSplineComponent::EditPointAtindex(const FTransform& NewPoint, const int32& Index) +{ + if (!CurvePoints.IsValidIndex(Index)) + return; + + CurvePoints[Index] = NewPoint; + bHasChanged = true; +} + +#if WITH_EDITOR +void +UHoudiniSplineComponent::PostEditChangeProperty(FPropertyChangedEvent& PeopertyChangedEvent) +{ + Super::PostEditChangeProperty(PeopertyChangedEvent); + + FName PropertyName = (PeopertyChangedEvent.Property != nullptr) ? PeopertyChangedEvent.Property->GetFName() : NAME_None; + + // Responses to the uproperty changes + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bClosed)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bClosed")); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bReversed)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bReversed")); + ReverseCurvePoints(); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveType)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveType")); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveMethod)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveMethod")); + MarkChanged(true); + } +} +#endif + +void +UHoudiniSplineComponent::PostLoad() +{ + Super::PostLoad(); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::PostLoad()] Component: %s"), *GetPathName()); + +} + +TStructOnScope +UHoudiniSplineComponent::GetComponentInstanceData() const +{ + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::GetComponentInstanceData()] Component: %s"), *GetPathName()); + TStructOnScope ComponentInstanceData = MakeStructOnScope(this); + FHoudiniSplineComponentInstanceData* InstanceData = ComponentInstanceData.Cast(); + + + // NOTE: We need to capture these properties here before the component gets torn down + // since the Spline visualizer changed values on the instance directly and is not present on the + // template yet. + /*InstanceData->CurvePoints = CurvePoints; + InstanceData->DisplayPoints = DisplayPoints; + InstanceData->DisplayPointIndexDivider = DisplayPointIndexDivider; + +#if WITH_EDITOR_DATA + InstanceData->EditedControlPointsIndexes = EditedControlPointsIndexes; +#endif*/ + + return ComponentInstanceData; +} + +void +UHoudiniSplineComponent::ApplyComponentInstanceData(FHoudiniSplineComponentInstanceData* ComponentInstanceData, + const bool bPostUCS) +{ + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %s"), *GetPathName()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %p"), this); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] IsVisible: %d"), IsVisible()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] bHiddenInGame: %d"), bHiddenInGame); + + check(ComponentInstanceData); + + if (!bPostUCS) + { + //bHasChanged = ComponentInstanceData->bHasChanged; + //bNeedsToTriggerUpdate = ComponentInstanceData->bNeedsToTriggerUpdate; + /*CurvePoints = ComponentInstanceData->CurvePoints; + DisplayPoints = ComponentInstanceData->DisplayPoints; + DisplayPointIndexDivider = ComponentInstanceData->DisplayPointIndexDivider; + +#if WITH_EDITOR_DATA + EditedControlPointsIndexes = ComponentInstanceData->EditedControlPointsIndexes; +#endif*/ + } +} + +void +UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) +{ + // Capture properties that we want to preserve during copy + const int32 PrevNodeId = NodeId; + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - IsVisible: %d"), IsVisible()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - bHiddenInGame: %d"), bHiddenInGame); + + UActorComponent* FromComponent = Cast(FromObject); + check(FromComponent); + + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + //Params.bDoDelta = false; // Perform a deep copy + //Params.bClearReferences = false; + //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, this, Params); + + /*const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); + FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, this, ComponentCopyOptions);*/ + + UHoudiniSplineComponent* FromSplineComponent = Cast(FromObject); + if (FromSplineComponent) + { + CurvePoints = FromSplineComponent->CurvePoints; + DisplayPoints = FromSplineComponent->DisplayPoints; + DisplayPointIndexDivider = FromSplineComponent->DisplayPointIndexDivider; +#if WITH_EDITORONLY_DATA + EditedControlPointsIndexes = FromSplineComponent->EditedControlPointsIndexes; +#endif + + HoudiniSplineName = FromSplineComponent->HoudiniSplineName; + bClosed = FromSplineComponent->bClosed; + bIsHoudiniSplineVisible = FromSplineComponent->bIsHoudiniSplineVisible; + CurveType = FromSplineComponent->CurveType; + CurveMethod = FromSplineComponent->CurveMethod; + bIsInputCurve = FromSplineComponent->bIsInputCurve; + bIsOutputCurve = FromSplineComponent->bIsOutputCurve; + bIsEditableOutputCurve = FromSplineComponent->bIsEditableOutputCurve; + bCookOnCurveChanged = FromSplineComponent->bCookOnCurveChanged; + + + + bHasChanged = FromSplineComponent->bHasChanged; + bNeedsToTriggerUpdate = FromSplineComponent->bNeedsToTriggerUpdate; + } + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - IsVisible: %d"), IsVisible()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - bHiddenInGame: %d"), bHiddenInGame); + + // Restore properties that we want to preserve + NodeId = PrevNodeId; +} + + +void +UHoudiniSplineComponent::OnUnregister() +{ + Super::OnUnregister(); +} + +void +UHoudiniSplineComponent::OnComponentCreated() +{ + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] Component: %s"), *GetPathName()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bVisible: %d"), IsVisible()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bHiddenInGame: %d"), bHiddenInGame); + Super::OnComponentCreated(); +} + +void +UHoudiniSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); + + if (IsInputCurve()) + { + // This component can't just come out of nowhere and decide to delete an input object! + // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! + + // InputObject->MarkPendingKill(); + + // if(NodeId > -1) + // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); + + SetNodeId(-1); // Set nodeId to invalid for reconstruct on re-do + } +} + + +#if WITH_EDITOR +void +UHoudiniSplineComponent::PostEditUndo() +{ + Super::PostEditUndo(); + + bPostUndo = true; + + // if (bIsInputCurve) + // { + // UHoudiniInputObject * CurrentInputObject = GetInputObject(); + // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + // return; + // + // CurrentInputObject->MarkChanged(true); + // } + // + // if (bIsEditableOutputCurve) + // { + // MarkChanged(true); + // } + + MarkChanged(true); + +} +#endif + +void +UHoudiniSplineComponent::SetOffset(const float& Offset) +{ + for (int n = 0; n < CurvePoints.Num(); ++n) + CurvePoints[n].AddToTranslation(FVector(0.f, Offset, 0.f)); + + for (int n = 0; n < DisplayPoints.Num(); ++n) + DisplayPoints[n] += FVector(0.f, Offset, 0.f); +} + +void +UHoudiniSplineComponent::ResetCurvePoints() +{ + CurvePoints.Empty(); +} + +void +UHoudiniSplineComponent::ResetDisplayPoints() +{ + DisplayPoints.Empty(); +} + +void +UHoudiniSplineComponent::AddCurvePoints(const TArray& Points) +{ + CurvePoints.Append(Points); +} + +void +UHoudiniSplineComponent::AddDisplayPoints(const TArray& Points) +{ + DisplayPoints.Append(Points); +} + +bool +UHoudiniSplineComponent::NeedsToTriggerUpdate() const +{ + return bNeedsToTriggerUpdate; + + // if (bIsInputCurve) + // { + // UHoudiniInputObject * CurrentInputObject = GetInputObject(); + // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + // return false; + // + // return CurrentInputObject->NeedsToTriggerUpdate(); + // } + // + // if (bIsEditableOutputCurve) + // { + // return bNeedsToTriggerUpdate; + // } + // + // return false; +} + +void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate) +{ + bNeedsToTriggerUpdate = NeedsToTriggerUpdate; +} + +void UHoudiniSplineComponent::SetCurveType(const EHoudiniCurveType & NewCurveType) +{ + CurveType = NewCurveType; +#if WITH_EDITOR + //FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(this, TEXT("CurveType")); +#endif +} + +void +UHoudiniSplineComponent::MarkInputObjectChanged() +{ + // if (bIsInputCurve) + // { + // UHoudiniInputObject * CurrentInputObject = GetInputObject(); + // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + // return; + // + // if (HasChanged()) + // CurrentInputObject->MarkChanged(true); + // } + // + // if (bIsEditableOutputCurve) + // { + // if (HasChanged()) + // MarkChanged(true); + // } + + // NOTE: This component should be trying to push ANY state changes to Input or Output objects. This + // component should strictly be a data container. Input / Output objects that reference this component should + // be polling this component's state. + MarkChanged(true); +} + +bool UHoudiniSplineComponent::HasChanged() const +{ + return bHasChanged; +} + +void UHoudiniSplineComponent::MarkChanged(const bool& Changed) +{ + bHasChanged = Changed; + bNeedsToTriggerUpdate = Changed; +} + +// UHoudiniAssetComponent* +// UHoudiniSplineComponent::GetParentHAC() +// { +// UHoudiniAssetComponent* ParentHAC = nullptr; +// if (bIsInputCurve) +// { +// if (!InputObject) +// return nullptr; +// +// UHoudiniInput* Input = Cast(InputObject->GetOuter()); +// if (!Input) +// return nullptr; +// +// ParentHAC = Cast(Input->GetOuter()); +// } +// else +// { +// // may do something else if this is not an input curve instead of returning Null. +// } +// +// return ParentHAC; +// +// } + +FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() +{ +} + +FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent) + : FActorComponentInstanceData(SourceComponent) +{ +} + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h index b777fb90f..8e45a2e9d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h @@ -1,299 +1,299 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "HoudiniEngineCopyPropertiesInterface.h" -#include "UObject/ObjectMacros.h" -#include "Components/SceneComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniSplineComponent.generated.h" - -class UHoudiniAssetComponent; - -enum class EHoudiniCurveType : int8; - -enum class EHoudiniCurveMethod : int8; - -class UHoudiniInputObject; - -UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) -class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, public IHoudiniEngineCopyPropertiesInterface -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniSplineComponent_V1; - - virtual ~UHoudiniSplineComponent(); - - virtual void Serialize(FArchive & Ar) override; - - public: - - void Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint = -1); - - void CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent); - - void ResetCurvePoints(); - - void ResetDisplayPoints(); - - void AddCurvePoints(const TArray& Points); - - void AddDisplayPoints(const TArray& Points); - - void AppendPoint(const FTransform& NewPoint); - - void InsertPointAtIndex(const FTransform& NewPoint, const int32& Index); - - void RemovePointAtIndex(const int32& Index); - - void EditPointAtindex(const FTransform& NewPoint, const int32& Index); - - // UHoudiniAssetComponent* GetParentHAC(); - - void MarkModified(const bool & InModified) { bHasChanged = InModified; }; - - // To set the offset of default position of houdini curve - void SetOffset(const float& Offset); - - UE_DEPRECATED(4.25, "Use MarkChanged() instead") - // This component should not be aware of whether it is being referenced by any input - // or output objects. - void MarkInputObjectChanged(); - - bool HasChanged() const; - - void MarkChanged(const bool& Changed); - - FORCEINLINE - FString& GetHoudiniSplineName() { return HoudiniSplineName; } - - FORCEINLINE - void SetHoudiniSplineName(const FString& NewName) { HoudiniSplineName = NewName; } - - bool NeedsToTriggerUpdate() const; - - void SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate); - - // FORCEINLINE - // UHoudiniInputObject* GetInputObject() const { return InputObject; } - - // FORCEINLINE - // void SetInputObject(UHoudiniInputObject* NewInputObject) { InputObject = NewInputObject; } - - FORCEINLINE - EHoudiniCurveType GetCurveType() const { return CurveType; } - - void SetCurveType(const EHoudiniCurveType& NewCurveType); - - FORCEINLINE - EHoudiniCurveMethod GetCurveMethod() const { return CurveMethod; } - - FORCEINLINE - void SetCurveMethod(const EHoudiniCurveMethod& NewCurveMethod) { CurveMethod = NewCurveMethod; } - - FORCEINLINE - int32 GetCurvePointCount() const { return CurvePoints.Num(); } - - FORCEINLINE - bool IsClosedCurve() const { return bClosed; } - - FORCEINLINE - void SetClosedCurve(const bool& Closed) { bClosed = Closed; } - - FORCEINLINE - bool IsReversed() const { return bReversed; } - - void SetReversed(const bool& Reversed); - - FORCEINLINE - bool IsInputCurve() const { return bIsInputCurve; } - - FORCEINLINE - void SetIsInputCurve(const bool& bIsInput) { bIsInputCurve = bIsInput; } - - FORCEINLINE - bool IsEditableOutputCurve() const { return bIsEditableOutputCurve; } - - FORCEINLINE - void SetIsEditableOutputCurve(const bool& bInIsEditable) { bIsEditableOutputCurve = bInIsEditable; }; - - FORCEINLINE - int32 GetNodeId() const { return NodeId; } - - FORCEINLINE - void SetNodeId(const int32& NewNodeId) { NodeId = NewNodeId; } - - FORCEINLINE - FString GetGeoPartName() const { return PartName; } - - FORCEINLINE - bool IsHoudiniSplineVisible() const { return bIsHoudiniSplineVisible; } - - FORCEINLINE - void SetHoudiniSplineVisible(bool Visible) { bIsHoudiniSplineVisible = Visible; } - - FORCEINLINE - void SetGeoPartName(const FString & InPartName) { PartName = InPartName; } - - virtual void OnUnregister() override; - - virtual void OnComponentCreated() override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - -#if WITH_EDITOR - virtual void PostEditUndo() override; - virtual void PostEditChangeProperty(FPropertyChangedEvent & PeopertyChangedEvent) override; -#endif - - virtual void PostLoad() override; - - virtual TStructOnScope GetComponentInstanceData() const override; - void ApplyComponentInstanceData(struct FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS); - - virtual void CopyPropertiesFrom(UObject* FromObject) override; - - private: - - void ReverseCurvePoints(); - - public: - - UPROPERTY() - TArray CurvePoints; - - UPROPERTY() - TArray DisplayPoints; - - UPROPERTY() - TArray DisplayPointIndexDivider; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - FString HoudiniSplineName; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bClosed; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bReversed; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bIsHoudiniSplineVisible; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - EHoudiniCurveType CurveType; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - EHoudiniCurveMethod CurveMethod; - - UPROPERTY() - bool bIsOutputCurve; - - UPROPERTY() - bool bCookOnCurveChanged; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - TArray EditedControlPointsIndexes; - - UPROPERTY(NonTransactional) - bool bPostUndo; -#endif - - protected: - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject HoudiniGeoPartObject; - - private: - UPROPERTY(Transient, DuplicateTransient) - bool bHasChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Whether this is a Houdini curve input - UPROPERTY() - bool bIsInputCurve; - - UPROPERTY() - bool bIsEditableOutputCurve; - - // UPROPERTY() - // UHoudiniInputObject * InputObject; - - // Corresponds to the Curve NodeId in Houdini - UPROPERTY(Transient, DuplicateTransient) - int32 NodeId; - - UPROPERTY() - FString PartName; -}; - -/** Used to store HoudiniAssetComponent data during BP reconstruction */ -USTRUCT() -struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData -{ - GENERATED_BODY() -public: - - FHoudiniSplineComponentInstanceData(); - FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent); - - virtual ~FHoudiniSplineComponentInstanceData() = default; - - virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override - { - Super::ApplyToComponent(Component, CacheApplyPhase); - CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); - } - - // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. - /*UPROPERTY() - bool bHasChanged; - - UPROPERTY() - bool bNeedsToTriggerUpdate;*/ - - UPROPERTY() - TArray CurvePoints; - - UPROPERTY() - TArray DisplayPoints; - - UPROPERTY() - TArray DisplayPointIndexDivider; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - TArray EditedControlPointsIndexes; -#endif - -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniEngineCopyPropertiesInterface.h" +#include "UObject/ObjectMacros.h" +#include "Components/SceneComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniSplineComponent.generated.h" + +class UHoudiniAssetComponent; + +enum class EHoudiniCurveType : int8; + +enum class EHoudiniCurveMethod : int8; + +class UHoudiniInputObject; + +UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) +class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, public IHoudiniEngineCopyPropertiesInterface +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniSplineComponent_V1; + + virtual ~UHoudiniSplineComponent(); + + virtual void Serialize(FArchive & Ar) override; + + public: + + void Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint = -1); + + void CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent); + + void ResetCurvePoints(); + + void ResetDisplayPoints(); + + void AddCurvePoints(const TArray& Points); + + void AddDisplayPoints(const TArray& Points); + + void AppendPoint(const FTransform& NewPoint); + + void InsertPointAtIndex(const FTransform& NewPoint, const int32& Index); + + void RemovePointAtIndex(const int32& Index); + + void EditPointAtindex(const FTransform& NewPoint, const int32& Index); + + // UHoudiniAssetComponent* GetParentHAC(); + + void MarkModified(const bool & InModified) { bHasChanged = InModified; }; + + // To set the offset of default position of houdini curve + void SetOffset(const float& Offset); + + UE_DEPRECATED(4.25, "Use MarkChanged() instead") + // This component should not be aware of whether it is being referenced by any input + // or output objects. + void MarkInputObjectChanged(); + + bool HasChanged() const; + + void MarkChanged(const bool& Changed); + + FORCEINLINE + FString& GetHoudiniSplineName() { return HoudiniSplineName; } + + FORCEINLINE + void SetHoudiniSplineName(const FString& NewName) { HoudiniSplineName = NewName; } + + bool NeedsToTriggerUpdate() const; + + void SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate); + + // FORCEINLINE + // UHoudiniInputObject* GetInputObject() const { return InputObject; } + + // FORCEINLINE + // void SetInputObject(UHoudiniInputObject* NewInputObject) { InputObject = NewInputObject; } + + FORCEINLINE + EHoudiniCurveType GetCurveType() const { return CurveType; } + + void SetCurveType(const EHoudiniCurveType& NewCurveType); + + FORCEINLINE + EHoudiniCurveMethod GetCurveMethod() const { return CurveMethod; } + + FORCEINLINE + void SetCurveMethod(const EHoudiniCurveMethod& NewCurveMethod) { CurveMethod = NewCurveMethod; } + + FORCEINLINE + int32 GetCurvePointCount() const { return CurvePoints.Num(); } + + FORCEINLINE + bool IsClosedCurve() const { return bClosed; } + + FORCEINLINE + void SetClosedCurve(const bool& Closed) { bClosed = Closed; } + + FORCEINLINE + bool IsReversed() const { return bReversed; } + + void SetReversed(const bool& Reversed); + + FORCEINLINE + bool IsInputCurve() const { return bIsInputCurve; } + + FORCEINLINE + void SetIsInputCurve(const bool& bIsInput) { bIsInputCurve = bIsInput; } + + FORCEINLINE + bool IsEditableOutputCurve() const { return bIsEditableOutputCurve; } + + FORCEINLINE + void SetIsEditableOutputCurve(const bool& bInIsEditable) { bIsEditableOutputCurve = bInIsEditable; }; + + FORCEINLINE + int32 GetNodeId() const { return NodeId; } + + FORCEINLINE + void SetNodeId(const int32& NewNodeId) { NodeId = NewNodeId; } + + FORCEINLINE + FString GetGeoPartName() const { return PartName; } + + FORCEINLINE + bool IsHoudiniSplineVisible() const { return bIsHoudiniSplineVisible; } + + FORCEINLINE + void SetHoudiniSplineVisible(bool Visible) { bIsHoudiniSplineVisible = Visible; } + + FORCEINLINE + void SetGeoPartName(const FString & InPartName) { PartName = InPartName; } + + virtual void OnUnregister() override; + + virtual void OnComponentCreated() override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + +#if WITH_EDITOR + virtual void PostEditUndo() override; + virtual void PostEditChangeProperty(FPropertyChangedEvent & PeopertyChangedEvent) override; +#endif + + virtual void PostLoad() override; + + virtual TStructOnScope GetComponentInstanceData() const override; + void ApplyComponentInstanceData(struct FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS); + + virtual void CopyPropertiesFrom(UObject* FromObject) override; + + private: + + void ReverseCurvePoints(); + + public: + + UPROPERTY() + TArray CurvePoints; + + UPROPERTY() + TArray DisplayPoints; + + UPROPERTY() + TArray DisplayPointIndexDivider; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + FString HoudiniSplineName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bClosed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bReversed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bIsHoudiniSplineVisible; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + EHoudiniCurveType CurveType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + EHoudiniCurveMethod CurveMethod; + + UPROPERTY() + bool bIsOutputCurve; + + UPROPERTY() + bool bCookOnCurveChanged; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + TArray EditedControlPointsIndexes; + + UPROPERTY(NonTransactional) + bool bPostUndo; +#endif + + protected: + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject HoudiniGeoPartObject; + + private: + UPROPERTY(Transient, DuplicateTransient) + bool bHasChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Whether this is a Houdini curve input + UPROPERTY() + bool bIsInputCurve; + + UPROPERTY() + bool bIsEditableOutputCurve; + + // UPROPERTY() + // UHoudiniInputObject * InputObject; + + // Corresponds to the Curve NodeId in Houdini + UPROPERTY(Transient, DuplicateTransient) + int32 NodeId; + + UPROPERTY() + FString PartName; +}; + +/** Used to store HoudiniAssetComponent data during BP reconstruction */ +USTRUCT() +struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData +{ + GENERATED_BODY() +public: + + FHoudiniSplineComponentInstanceData(); + FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent); + + virtual ~FHoudiniSplineComponentInstanceData() = default; + + virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override + { + Super::ApplyToComponent(Component, CacheApplyPhase); + CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); + } + + // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. + /*UPROPERTY() + bool bHasChanged; + + UPROPERTY() + bool bNeedsToTriggerUpdate;*/ + + UPROPERTY() + TArray CurvePoints; + + UPROPERTY() + TArray DisplayPoints; + + UPROPERTY() + TArray DisplayPointIndexDivider; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + TArray EditedControlPointsIndexes; +#endif + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp index c51f98505..34cc1be89 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp @@ -1,303 +1,303 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMesh.h" - -UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - bHasNormals = false; - bHasTangents = false; - bHasColors = false; - NumUVLayers = false; - bHasPerFaceMaterials = false; -} - -void UHoudiniStaticMesh::Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials) -{ - // Initialize the vertex positions and triangle indices arrays - VertexPositions.Init(FVector::ZeroVector, InNumVertices); - TriangleIndices.Init(FIntVector(-1, -1, -1), InNumTriangles); - if (InInitialNumStaticMaterials > 0) - StaticMaterials.Init(FStaticMaterial(), InInitialNumStaticMaterials); - else - StaticMaterials.Empty(); - - SetNumUVLayers(InNumUVLayers); - SetHasNormals(bInHasNormals); - SetHasTangents(bInHasTangents); - SetHasColors(bInHasColors); - SetHasPerFaceMaterials(bInHasPerFaceMaterials); -} - -void UHoudiniStaticMesh::SetHasPerFaceMaterials(bool bInHasPerFaceMaterials) -{ - bHasPerFaceMaterials = bInHasPerFaceMaterials; - if (bHasPerFaceMaterials) - MaterialIDsPerTriangle.Init(-1, GetNumTriangles()); - else - MaterialIDsPerTriangle.Empty(); -} - -void UHoudiniStaticMesh::SetHasNormals(bool bInHasNormals) -{ - bHasNormals = bInHasNormals; - if (bHasNormals) - VertexInstanceNormals.Init(FVector(0, 0, 1), GetNumVertexInstances()); - else - VertexInstanceNormals.Empty(); -} - -void UHoudiniStaticMesh::SetHasTangents(bool bInHasTangents) -{ - bHasTangents = bInHasTangents; - if (bHasTangents) - { - VertexInstanceUTangents.Init(FVector(1, 0, 0), GetNumVertexInstances()); - VertexInstanceVTangents.Init(FVector(0, 1, 0), GetNumVertexInstances()); - } - else - { - VertexInstanceUTangents.Empty(); - VertexInstanceVTangents.Empty(); - } -} - -void UHoudiniStaticMesh::SetHasColors(bool bInHasColors) -{ - bHasColors = bInHasColors; - if (bHasColors) - VertexInstanceColors.Init(FColor(127, 127, 127), GetNumVertexInstances()); - else - VertexInstanceColors.Empty(); -} - -void UHoudiniStaticMesh::SetNumUVLayers(uint32 InNumUVLayers) -{ - NumUVLayers = InNumUVLayers; - if (NumUVLayers > 0) - VertexInstanceUVs.Init(FVector2D::ZeroVector, GetNumVertexInstances() * NumUVLayers); - else - VertexInstanceUVs.Empty(); -} - -void UHoudiniStaticMesh::SetNumStaticMaterials(uint32 InNumMaterials) -{ - if (InNumMaterials > 0) - StaticMaterials.SetNum(InNumMaterials); - else - StaticMaterials.Empty(); -} - -void UHoudiniStaticMesh::SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition) -{ - check(VertexPositions.IsValidIndex(InVertexIndex)); - - VertexPositions[InVertexIndex] = InPosition; -} - -void UHoudiniStaticMesh::SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices) -{ - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[0])); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[1])); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[2])); - - TriangleIndices[InTriangleIndex] = InTriangleVertexIndices; -} - -void UHoudiniStaticMesh::SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal) -{ - if (!bHasNormals) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceNormals.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceNormals[VertexInstanceIndex] = InNormal; -} - -void UHoudiniStaticMesh::SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent) -{ - if (!bHasTangents) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceUTangents.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceUTangents[VertexInstanceIndex] = InUTangent; -} - -void UHoudiniStaticMesh::SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent) -{ - if (!bHasTangents) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceVTangents.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceVTangents[VertexInstanceIndex] = InVTangent; -} - -void UHoudiniStaticMesh::SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor) -{ - if (!bHasColors) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceColors.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceColors[VertexInstanceIndex] = InColor; -} - -void UHoudiniStaticMesh::SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV) -{ - if (NumUVLayers <= 0) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceUVIndex = InUVLayer * GetNumVertexInstances() + InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceUVs.IsValidIndex(VertexInstanceUVIndex)); - - VertexInstanceUVs[VertexInstanceUVIndex] = InUV; -} - -void UHoudiniStaticMesh::SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID) -{ - if (!bHasPerFaceMaterials) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - check(MaterialIDsPerTriangle.IsValidIndex(InTriangleIndex)); - - MaterialIDsPerTriangle[InTriangleIndex] = InMaterialID; -} - -void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial) -{ - check(StaticMaterials.IsValidIndex(InMaterialIndex)); - StaticMaterials[InMaterialIndex] = InStaticMaterial; -} - -void UHoudiniStaticMesh::Optimize() -{ - VertexPositions.Shrink(); - TriangleIndices.Shrink(); - VertexInstanceColors.Shrink(); - VertexInstanceNormals.Shrink(); - VertexInstanceUTangents.Shrink(); - VertexInstanceVTangents.Shrink(); - VertexInstanceUVs.Shrink(); - MaterialIDsPerTriangle.Shrink(); - StaticMaterials.Shrink(); -} - -FBox UHoudiniStaticMesh::CalcBounds() const -{ - const uint32 NumVertices = VertexPositions.Num(); - - if (NumVertices == 0) - return FBox(); - - const FVector InitPosition = VertexPositions[0]; - double MinX = InitPosition.X, MaxX = InitPosition.X, MinY = InitPosition.Y, MaxY = InitPosition.Y, MinZ = InitPosition.Z, MaxZ = InitPosition.Z; - for (uint32 VertIdx = 0; VertIdx < NumVertices; ++VertIdx) - { - const FVector Position = VertexPositions[VertIdx]; - if (Position.X < MinX) MinX = Position.X; else if (Position.X > MaxX) MaxX = Position.X; - if (Position.Y < MinY) MinY = Position.Y; else if (Position.Y > MaxY) MaxY = Position.Y; - if (Position.Z < MinZ) MinZ = Position.Z; else if (Position.Z > MaxZ) MaxZ = Position.Z; - } - - return FBox(FVector(MinX, MinY, MinZ), FVector(MaxX, MaxY, MaxZ)); -} - -UMaterialInterface* UHoudiniStaticMesh::GetMaterial(int32 InMaterialIndex) -{ - check(StaticMaterials.IsValidIndex(InMaterialIndex)); - - return StaticMaterials[InMaterialIndex].MaterialInterface; -} - -int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const -{ - if (InMaterialSlotName == NAME_None) - return -1; - - const uint32 NumMaterials = StaticMaterials.Num(); - for (uint32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) - { - if (StaticMaterials[MaterialIndex].MaterialSlotName == InMaterialSlotName) - return (int32)MaterialIndex; - } - - return -1; -} - -void UHoudiniStaticMesh::Serialize(FArchive &InArchive) -{ - Super::Serialize(InArchive); - - VertexPositions.Shrink(); - VertexPositions.BulkSerialize(InArchive); - - TriangleIndices.Shrink(); - TriangleIndices.BulkSerialize(InArchive); - - VertexInstanceColors.Shrink(); - VertexInstanceColors.BulkSerialize(InArchive); - - VertexInstanceNormals.Shrink(); - VertexInstanceNormals.BulkSerialize(InArchive); - - VertexInstanceUTangents.Shrink(); - VertexInstanceUTangents.BulkSerialize(InArchive); - - VertexInstanceVTangents.Shrink(); - VertexInstanceVTangents.BulkSerialize(InArchive); - - VertexInstanceUVs.Shrink(); - VertexInstanceUVs.BulkSerialize(InArchive); - - MaterialIDsPerTriangle.Shrink(); - MaterialIDsPerTriangle.BulkSerialize(InArchive); -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMesh.h" + +UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bHasNormals = false; + bHasTangents = false; + bHasColors = false; + NumUVLayers = false; + bHasPerFaceMaterials = false; +} + +void UHoudiniStaticMesh::Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials) +{ + // Initialize the vertex positions and triangle indices arrays + VertexPositions.Init(FVector::ZeroVector, InNumVertices); + TriangleIndices.Init(FIntVector(-1, -1, -1), InNumTriangles); + if (InInitialNumStaticMaterials > 0) + StaticMaterials.Init(FStaticMaterial(), InInitialNumStaticMaterials); + else + StaticMaterials.Empty(); + + SetNumUVLayers(InNumUVLayers); + SetHasNormals(bInHasNormals); + SetHasTangents(bInHasTangents); + SetHasColors(bInHasColors); + SetHasPerFaceMaterials(bInHasPerFaceMaterials); +} + +void UHoudiniStaticMesh::SetHasPerFaceMaterials(bool bInHasPerFaceMaterials) +{ + bHasPerFaceMaterials = bInHasPerFaceMaterials; + if (bHasPerFaceMaterials) + MaterialIDsPerTriangle.Init(-1, GetNumTriangles()); + else + MaterialIDsPerTriangle.Empty(); +} + +void UHoudiniStaticMesh::SetHasNormals(bool bInHasNormals) +{ + bHasNormals = bInHasNormals; + if (bHasNormals) + VertexInstanceNormals.Init(FVector(0, 0, 1), GetNumVertexInstances()); + else + VertexInstanceNormals.Empty(); +} + +void UHoudiniStaticMesh::SetHasTangents(bool bInHasTangents) +{ + bHasTangents = bInHasTangents; + if (bHasTangents) + { + VertexInstanceUTangents.Init(FVector(1, 0, 0), GetNumVertexInstances()); + VertexInstanceVTangents.Init(FVector(0, 1, 0), GetNumVertexInstances()); + } + else + { + VertexInstanceUTangents.Empty(); + VertexInstanceVTangents.Empty(); + } +} + +void UHoudiniStaticMesh::SetHasColors(bool bInHasColors) +{ + bHasColors = bInHasColors; + if (bHasColors) + VertexInstanceColors.Init(FColor(127, 127, 127), GetNumVertexInstances()); + else + VertexInstanceColors.Empty(); +} + +void UHoudiniStaticMesh::SetNumUVLayers(uint32 InNumUVLayers) +{ + NumUVLayers = InNumUVLayers; + if (NumUVLayers > 0) + VertexInstanceUVs.Init(FVector2D::ZeroVector, GetNumVertexInstances() * NumUVLayers); + else + VertexInstanceUVs.Empty(); +} + +void UHoudiniStaticMesh::SetNumStaticMaterials(uint32 InNumMaterials) +{ + if (InNumMaterials > 0) + StaticMaterials.SetNum(InNumMaterials); + else + StaticMaterials.Empty(); +} + +void UHoudiniStaticMesh::SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition) +{ + check(VertexPositions.IsValidIndex(InVertexIndex)); + + VertexPositions[InVertexIndex] = InPosition; +} + +void UHoudiniStaticMesh::SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices) +{ + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[0])); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[1])); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[2])); + + TriangleIndices[InTriangleIndex] = InTriangleVertexIndices; +} + +void UHoudiniStaticMesh::SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal) +{ + if (!bHasNormals) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceNormals.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceNormals[VertexInstanceIndex] = InNormal; +} + +void UHoudiniStaticMesh::SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent) +{ + if (!bHasTangents) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceUTangents.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceUTangents[VertexInstanceIndex] = InUTangent; +} + +void UHoudiniStaticMesh::SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent) +{ + if (!bHasTangents) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceVTangents.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceVTangents[VertexInstanceIndex] = InVTangent; +} + +void UHoudiniStaticMesh::SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor) +{ + if (!bHasColors) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceColors.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceColors[VertexInstanceIndex] = InColor; +} + +void UHoudiniStaticMesh::SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV) +{ + if (NumUVLayers <= 0) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceUVIndex = InUVLayer * GetNumVertexInstances() + InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceUVs.IsValidIndex(VertexInstanceUVIndex)); + + VertexInstanceUVs[VertexInstanceUVIndex] = InUV; +} + +void UHoudiniStaticMesh::SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID) +{ + if (!bHasPerFaceMaterials) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + check(MaterialIDsPerTriangle.IsValidIndex(InTriangleIndex)); + + MaterialIDsPerTriangle[InTriangleIndex] = InMaterialID; +} + +void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial) +{ + check(StaticMaterials.IsValidIndex(InMaterialIndex)); + StaticMaterials[InMaterialIndex] = InStaticMaterial; +} + +void UHoudiniStaticMesh::Optimize() +{ + VertexPositions.Shrink(); + TriangleIndices.Shrink(); + VertexInstanceColors.Shrink(); + VertexInstanceNormals.Shrink(); + VertexInstanceUTangents.Shrink(); + VertexInstanceVTangents.Shrink(); + VertexInstanceUVs.Shrink(); + MaterialIDsPerTriangle.Shrink(); + StaticMaterials.Shrink(); +} + +FBox UHoudiniStaticMesh::CalcBounds() const +{ + const uint32 NumVertices = VertexPositions.Num(); + + if (NumVertices == 0) + return FBox(); + + const FVector InitPosition = VertexPositions[0]; + double MinX = InitPosition.X, MaxX = InitPosition.X, MinY = InitPosition.Y, MaxY = InitPosition.Y, MinZ = InitPosition.Z, MaxZ = InitPosition.Z; + for (uint32 VertIdx = 0; VertIdx < NumVertices; ++VertIdx) + { + const FVector Position = VertexPositions[VertIdx]; + if (Position.X < MinX) MinX = Position.X; else if (Position.X > MaxX) MaxX = Position.X; + if (Position.Y < MinY) MinY = Position.Y; else if (Position.Y > MaxY) MaxY = Position.Y; + if (Position.Z < MinZ) MinZ = Position.Z; else if (Position.Z > MaxZ) MaxZ = Position.Z; + } + + return FBox(FVector(MinX, MinY, MinZ), FVector(MaxX, MaxY, MaxZ)); +} + +UMaterialInterface* UHoudiniStaticMesh::GetMaterial(int32 InMaterialIndex) +{ + check(StaticMaterials.IsValidIndex(InMaterialIndex)); + + return StaticMaterials[InMaterialIndex].MaterialInterface; +} + +int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const +{ + if (InMaterialSlotName == NAME_None) + return -1; + + const uint32 NumMaterials = StaticMaterials.Num(); + for (uint32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) + { + if (StaticMaterials[MaterialIndex].MaterialSlotName == InMaterialSlotName) + return (int32)MaterialIndex; + } + + return -1; +} + +void UHoudiniStaticMesh::Serialize(FArchive &InArchive) +{ + Super::Serialize(InArchive); + + VertexPositions.Shrink(); + VertexPositions.BulkSerialize(InArchive); + + TriangleIndices.Shrink(); + TriangleIndices.BulkSerialize(InArchive); + + VertexInstanceColors.Shrink(); + VertexInstanceColors.BulkSerialize(InArchive); + + VertexInstanceNormals.Shrink(); + VertexInstanceNormals.BulkSerialize(InArchive); + + VertexInstanceUTangents.Shrink(); + VertexInstanceUTangents.BulkSerialize(InArchive); + + VertexInstanceVTangents.Shrink(); + VertexInstanceVTangents.BulkSerialize(InArchive); + + VertexInstanceUVs.Shrink(); + VertexInstanceUVs.BulkSerialize(InArchive); + + MaterialIDsPerTriangle.Shrink(); + MaterialIDsPerTriangle.BulkSerialize(InArchive); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h index c5e9b37ae..4cda5c8da 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h @@ -1,226 +1,226 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Engine/StaticMesh.h" - -#include "HoudiniStaticMesh.generated.h" - -/** - * This is a simple static mesh that is meant to be built in one go, without modifications afterwards. - * The number of vertices and triangles must be known before hand. - */ -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject -{ - GENERATED_BODY() - -public: - - UHoudiniStaticMesh(const FObjectInitializer &ObjectInitializer); - - // Clears all existing data and initializes internal arrays to the relevant sizes to accommodate the - // mesh based InNumVertices, InNumTriangles, UVs etc. - UFUNCTION() - void Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials); - - UFUNCTION() - bool HasPerFaceMaterials() const { return bHasPerFaceMaterials; } - - UFUNCTION() - void SetHasPerFaceMaterials(bool bInHasPerFaceMaterials); - - UFUNCTION() - bool HasNormals() const { return bHasNormals; } - - UFUNCTION() - void SetHasNormals(bool bInHasNormals); - - UFUNCTION() - bool HasTangents() const { return bHasTangents; } - - UFUNCTION() - void SetHasTangents(bool bInHasTangents); - - UFUNCTION() - bool HasColors() const { return bHasColors; } - - UFUNCTION() - void SetHasColors(bool bInHasColors); - - UFUNCTION() - uint32 GetNumUVLayers() const { return NumUVLayers; } - - UFUNCTION() - void SetNumUVLayers(uint32 InNumUVLayers); - - UFUNCTION() - uint32 GetNumStaticMaterials() const { return StaticMaterials.Num(); } - - UFUNCTION() - void SetNumStaticMaterials(uint32 InNumStaticMaterials); - - UFUNCTION() - uint32 GetNumVertices() const { return VertexPositions.Num(); } - - UFUNCTION() - uint32 GetNumTriangles() const { return TriangleIndices.Num(); } - - UFUNCTION() - uint32 GetNumVertexInstances() const { return TriangleIndices.Num() * 3; } - - UFUNCTION() - void SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition); - - UFUNCTION() - void SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices); - - UFUNCTION() - void SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal); - - UFUNCTION() - void SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent); - - UFUNCTION() - void SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent); - - UFUNCTION() - void SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor); - - UFUNCTION() - void SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV); - - UFUNCTION() - void SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID); - - UFUNCTION() - void SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial); - - UFUNCTION() - uint32 AddStaticMaterial(const FStaticMaterial& InStaticMaterial) { return StaticMaterials.Add(InStaticMaterial); } - - // Meant to be called after the mesh data arrays are populated. - // Currently only calls Shrink on the arrays - UFUNCTION() - void Optimize(); - - UFUNCTION() - FBox CalcBounds() const; - - UFUNCTION() - const TArray& GetVertexPositions() const { return VertexPositions; } - - UFUNCTION() - const TArray& GetTriangleIndices() const { return TriangleIndices; } - - UFUNCTION() - const TArray& GetVertexInstanceColors() const { return VertexInstanceColors; } - - UFUNCTION() - const TArray& GetVertexInstanceNormals() const { return VertexInstanceNormals; } - - UFUNCTION() - const TArray& GetVertexInstanceUTangents() const { return VertexInstanceUTangents; } - - UFUNCTION() - const TArray& GetVertexInstanceVTangents() const { return VertexInstanceVTangents; } - - UFUNCTION() - const TArray& GetVertexInstanceUVs() const { return VertexInstanceUVs; } - - UFUNCTION() - const TArray& GetMaterialIDsPerTriangle() const { return MaterialIDsPerTriangle; } - - UFUNCTION() - const TArray& GetStaticMaterials() const { return StaticMaterials; } - - UFUNCTION() - UMaterialInterface* GetMaterial(int32 InMaterialIndex); - - UFUNCTION() - int32 GetMaterialIndex(FName InMaterialSlotName) const; - - // Custom serialization: we use TArray::BulkSerialize to speed up array serialization - virtual void Serialize(FArchive &InArchive) override; - -protected: - - UPROPERTY() - bool bHasNormals; - - UPROPERTY() - bool bHasTangents; - - UPROPERTY() - bool bHasColors; - - /** The number of UV layers that the mesh has */ - UPROPERTY() - uint32 NumUVLayers; - - UPROPERTY() - bool bHasPerFaceMaterials; - - /** Vertex positions. The vertex id == vertex index => indexes into this array. */ - UPROPERTY(SkipSerialization) - TArray VertexPositions; - - /** Triangle vertices. Triangle id == triangle index => indexes into this array, which returns a FIntVector of - * vertex ids/indices for VertexPositions. - */ - UPROPERTY(SkipSerialization) - TArray TriangleIndices; - - /** Array of colors per vertex instance, in other words, a color per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceColors; - - /** Array of normals per vertex instance, in other words, a normal per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceNormals; - - /** Array of U tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceUTangents; - - /** Array of V tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceVTangents; - - /** Array of UV layers to array of per triangle-vertex UVs. Index: UVLayerIndex * (NumVertexInstances) + 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceUVs; - - /** Array of material ID per triangle. Indexed by Triangle ID/Index. */ - UPROPERTY(SkipSerialization) - TArray MaterialIDsPerTriangle; - - /** The materials of the mesh. Index by MaterialID (MaterialIndex). */ - UPROPERTY() - TArray StaticMaterials; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/StaticMesh.h" + +#include "HoudiniStaticMesh.generated.h" + +/** + * This is a simple static mesh that is meant to be built in one go, without modifications afterwards. + * The number of vertices and triangles must be known before hand. + */ +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject +{ + GENERATED_BODY() + +public: + + UHoudiniStaticMesh(const FObjectInitializer &ObjectInitializer); + + // Clears all existing data and initializes internal arrays to the relevant sizes to accommodate the + // mesh based InNumVertices, InNumTriangles, UVs etc. + UFUNCTION() + void Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials); + + UFUNCTION() + bool HasPerFaceMaterials() const { return bHasPerFaceMaterials; } + + UFUNCTION() + void SetHasPerFaceMaterials(bool bInHasPerFaceMaterials); + + UFUNCTION() + bool HasNormals() const { return bHasNormals; } + + UFUNCTION() + void SetHasNormals(bool bInHasNormals); + + UFUNCTION() + bool HasTangents() const { return bHasTangents; } + + UFUNCTION() + void SetHasTangents(bool bInHasTangents); + + UFUNCTION() + bool HasColors() const { return bHasColors; } + + UFUNCTION() + void SetHasColors(bool bInHasColors); + + UFUNCTION() + uint32 GetNumUVLayers() const { return NumUVLayers; } + + UFUNCTION() + void SetNumUVLayers(uint32 InNumUVLayers); + + UFUNCTION() + uint32 GetNumStaticMaterials() const { return StaticMaterials.Num(); } + + UFUNCTION() + void SetNumStaticMaterials(uint32 InNumStaticMaterials); + + UFUNCTION() + uint32 GetNumVertices() const { return VertexPositions.Num(); } + + UFUNCTION() + uint32 GetNumTriangles() const { return TriangleIndices.Num(); } + + UFUNCTION() + uint32 GetNumVertexInstances() const { return TriangleIndices.Num() * 3; } + + UFUNCTION() + void SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition); + + UFUNCTION() + void SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices); + + UFUNCTION() + void SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal); + + UFUNCTION() + void SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent); + + UFUNCTION() + void SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent); + + UFUNCTION() + void SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor); + + UFUNCTION() + void SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV); + + UFUNCTION() + void SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID); + + UFUNCTION() + void SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial); + + UFUNCTION() + uint32 AddStaticMaterial(const FStaticMaterial& InStaticMaterial) { return StaticMaterials.Add(InStaticMaterial); } + + // Meant to be called after the mesh data arrays are populated. + // Currently only calls Shrink on the arrays + UFUNCTION() + void Optimize(); + + UFUNCTION() + FBox CalcBounds() const; + + UFUNCTION() + const TArray& GetVertexPositions() const { return VertexPositions; } + + UFUNCTION() + const TArray& GetTriangleIndices() const { return TriangleIndices; } + + UFUNCTION() + const TArray& GetVertexInstanceColors() const { return VertexInstanceColors; } + + UFUNCTION() + const TArray& GetVertexInstanceNormals() const { return VertexInstanceNormals; } + + UFUNCTION() + const TArray& GetVertexInstanceUTangents() const { return VertexInstanceUTangents; } + + UFUNCTION() + const TArray& GetVertexInstanceVTangents() const { return VertexInstanceVTangents; } + + UFUNCTION() + const TArray& GetVertexInstanceUVs() const { return VertexInstanceUVs; } + + UFUNCTION() + const TArray& GetMaterialIDsPerTriangle() const { return MaterialIDsPerTriangle; } + + UFUNCTION() + const TArray& GetStaticMaterials() const { return StaticMaterials; } + + UFUNCTION() + UMaterialInterface* GetMaterial(int32 InMaterialIndex); + + UFUNCTION() + int32 GetMaterialIndex(FName InMaterialSlotName) const; + + // Custom serialization: we use TArray::BulkSerialize to speed up array serialization + virtual void Serialize(FArchive &InArchive) override; + +protected: + + UPROPERTY() + bool bHasNormals; + + UPROPERTY() + bool bHasTangents; + + UPROPERTY() + bool bHasColors; + + /** The number of UV layers that the mesh has */ + UPROPERTY() + uint32 NumUVLayers; + + UPROPERTY() + bool bHasPerFaceMaterials; + + /** Vertex positions. The vertex id == vertex index => indexes into this array. */ + UPROPERTY(SkipSerialization) + TArray VertexPositions; + + /** Triangle vertices. Triangle id == triangle index => indexes into this array, which returns a FIntVector of + * vertex ids/indices for VertexPositions. + */ + UPROPERTY(SkipSerialization) + TArray TriangleIndices; + + /** Array of colors per vertex instance, in other words, a color per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceColors; + + /** Array of normals per vertex instance, in other words, a normal per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceNormals; + + /** Array of U tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceUTangents; + + /** Array of V tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceVTangents; + + /** Array of UV layers to array of per triangle-vertex UVs. Index: UVLayerIndex * (NumVertexInstances) + 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceUVs; + + /** Array of material ID per triangle. Indexed by Triangle ID/Index. */ + UPROPERTY(SkipSerialization) + TArray MaterialIDsPerTriangle; + + /** The materials of the mesh. Index by MaterialID (MaterialIndex). */ + UPROPERTY() + TArray StaticMaterials; +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp index 61a907df9..460a17b60 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp @@ -1,213 +1,213 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMeshComponent.h" - -#include "Components/BillboardComponent.h" -#include "Engine/CollisionProfile.h" - -#include "HoudiniStaticMesh.h" -#include "HoudiniStaticMeshSceneProxy.h" - - -UHoudiniStaticMeshComponent::UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer) : - Super(InInitialzer) -{ - PrimaryComponentTick.bCanEverTick = false; - - SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); - - Mesh = nullptr; - bHoudiniIconVisible = true; - -#if WITH_EDITOR - bVisualizeComponent = true; -#endif -} - -void UHoudiniStaticMeshComponent::OnRegister() -{ - Super::OnRegister(); - -#if WITH_EDITORONLY_DATA - if (bVisualizeComponent && SpriteComponent != nullptr && GetOwner()) - { - SpriteComponent->SetSprite(LoadObject(nullptr, TEXT("/HoudiniEngine/Textures/icon_houdini_logo_128.icon_houdini_logo_128"))); - UpdateSpriteComponent(); - } -#endif -} - -//void -//UHoudiniStaticMeshComponent::PostLoad() -//{ -// Super::PostLoad(); -// -// //NotifyMeshUpdated(); -//} - -void UHoudiniStaticMeshComponent::SetMesh(UHoudiniStaticMesh *InMesh) -{ - if (Mesh == InMesh) - return; - - Mesh = InMesh; - NotifyMeshUpdated(); -} - -FPrimitiveSceneProxy* UHoudiniStaticMeshComponent::CreateSceneProxy() -{ - check(SceneProxy == nullptr); - - FHoudiniStaticMeshSceneProxy* NewProxy = nullptr; - if (Mesh && Mesh->GetNumTriangles() > 0) - { - NewProxy = new FHoudiniStaticMeshSceneProxy(this, GetScene()->GetFeatureLevel()); - NewProxy->Build(); - } - return NewProxy; -} - -FBoxSphereBounds UHoudiniStaticMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const -{ - FBox LocalBoundingBox = LocalBounds; - FBoxSphereBounds Ret(LocalBoundingBox.TransformBy(InLocalToWorld)); - Ret.BoxExtent *= BoundsScale; - Ret.SphereRadius *= BoundsScale; - return Ret; -} - -#if WITH_EDITOR -void UHoudiniStaticMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - const FName NAME_HoudiniIconVisible(TEXT("bHoudiniIconVisible")); - const FName NAME_Mesh(TEXT("Mesh")); - const FName NAME_PropertyChanged = PropertyChangedEvent.GetPropertyName(); - if (NAME_PropertyChanged == NAME_HoudiniIconVisible) - { - UpdateSpriteComponent(); - } - else if (NAME_PropertyChanged == NAME_Mesh) - { - NotifyMeshUpdated(); - } -} -#endif - -void UHoudiniStaticMeshComponent::SetHoudiniIconVisible(bool bInHoudiniIconVisible) -{ - bHoudiniIconVisible = bInHoudiniIconVisible; -#if WITH_EDITORONLY_DATA - UpdateSpriteComponent(); -#endif -} - -void UHoudiniStaticMeshComponent::NotifyMeshUpdated() -{ - MarkRenderStateDirty(); - if (Mesh) - { - LocalBounds = Mesh->CalcBounds(); - } - else - { - LocalBounds.Init(); - } - -#if WITH_EDITORONLY_DATA - UpdateSpriteComponent(); -#endif - - UpdateBounds(); -} - -#if WITH_EDITORONLY_DATA -void UHoudiniStaticMeshComponent::UpdateSpriteComponent() -{ - if (SpriteComponent) - { - SpriteComponent->SetRelativeLocation(FVector(0, 0, LocalBounds.GetExtent().Z)); - SpriteComponent->SetVisibility(bHoudiniIconVisible); - } -} -#endif - -int32 UHoudiniStaticMeshComponent::GetNumMaterials() const -{ - // From UStaticMesh: - // @note : you don't have to consider Materials.Num() - // that only counts if overridden and it can't be more than GetStaticMesh()->Materials. - if (Mesh) - { - return Mesh->GetNumStaticMaterials(); - } - else - { - return 0; - } -} - -int32 UHoudiniStaticMeshComponent::GetMaterialIndex(FName MaterialSlotName) const -{ - return Mesh ? Mesh->GetMaterialIndex(MaterialSlotName) : -1; -} - -TArray UHoudiniStaticMeshComponent::GetMaterialSlotNames() const -{ - TArray MaterialNames; - if (Mesh) - { - const TArray &StaticMaterials = Mesh->GetStaticMaterials(); - const int32 NumMaterials = StaticMaterials.Num(); - for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) - { - const FStaticMaterial &StaticMaterial = StaticMaterials[MaterialIndex]; - MaterialNames.Add(StaticMaterial.MaterialSlotName); - } - } - return MaterialNames; -} - -bool UHoudiniStaticMeshComponent::IsMaterialSlotNameValid(FName MaterialSlotName) const -{ - return GetMaterialIndex(MaterialSlotName) >= 0; -} - -UMaterialInterface* UHoudiniStaticMeshComponent::GetMaterial(int32 MaterialIndex) const -{ - // From UStaticMesh: - // If we have a base materials array, use that - if (OverrideMaterials.IsValidIndex(MaterialIndex) && OverrideMaterials[MaterialIndex]) - { - return OverrideMaterials[MaterialIndex]; - } - // Otherwise get from static mesh - else - { - return Mesh ? Mesh->GetMaterial(MaterialIndex) : nullptr; - } -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMeshComponent.h" + +#include "Components/BillboardComponent.h" +#include "Engine/CollisionProfile.h" + +#include "HoudiniStaticMesh.h" +#include "HoudiniStaticMeshSceneProxy.h" + + +UHoudiniStaticMeshComponent::UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer) : + Super(InInitialzer) +{ + PrimaryComponentTick.bCanEverTick = false; + + SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); + + Mesh = nullptr; + bHoudiniIconVisible = true; + +#if WITH_EDITOR + bVisualizeComponent = true; +#endif +} + +void UHoudiniStaticMeshComponent::OnRegister() +{ + Super::OnRegister(); + +#if WITH_EDITORONLY_DATA + if (bVisualizeComponent && SpriteComponent != nullptr && GetOwner()) + { + SpriteComponent->SetSprite(LoadObject(nullptr, TEXT("/HoudiniEngine/Textures/icon_houdini_logo_128.icon_houdini_logo_128"))); + UpdateSpriteComponent(); + } +#endif +} + +//void +//UHoudiniStaticMeshComponent::PostLoad() +//{ +// Super::PostLoad(); +// +// //NotifyMeshUpdated(); +//} + +void UHoudiniStaticMeshComponent::SetMesh(UHoudiniStaticMesh *InMesh) +{ + if (Mesh == InMesh) + return; + + Mesh = InMesh; + NotifyMeshUpdated(); +} + +FPrimitiveSceneProxy* UHoudiniStaticMeshComponent::CreateSceneProxy() +{ + check(SceneProxy == nullptr); + + FHoudiniStaticMeshSceneProxy* NewProxy = nullptr; + if (Mesh && Mesh->GetNumTriangles() > 0) + { + NewProxy = new FHoudiniStaticMeshSceneProxy(this, GetScene()->GetFeatureLevel()); + NewProxy->Build(); + } + return NewProxy; +} + +FBoxSphereBounds UHoudiniStaticMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const +{ + FBox LocalBoundingBox = LocalBounds; + FBoxSphereBounds Ret(LocalBoundingBox.TransformBy(InLocalToWorld)); + Ret.BoxExtent *= BoundsScale; + Ret.SphereRadius *= BoundsScale; + return Ret; +} + +#if WITH_EDITOR +void UHoudiniStaticMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + const FName NAME_HoudiniIconVisible(TEXT("bHoudiniIconVisible")); + const FName NAME_Mesh(TEXT("Mesh")); + const FName NAME_PropertyChanged = PropertyChangedEvent.GetPropertyName(); + if (NAME_PropertyChanged == NAME_HoudiniIconVisible) + { + UpdateSpriteComponent(); + } + else if (NAME_PropertyChanged == NAME_Mesh) + { + NotifyMeshUpdated(); + } +} +#endif + +void UHoudiniStaticMeshComponent::SetHoudiniIconVisible(bool bInHoudiniIconVisible) +{ + bHoudiniIconVisible = bInHoudiniIconVisible; +#if WITH_EDITORONLY_DATA + UpdateSpriteComponent(); +#endif +} + +void UHoudiniStaticMeshComponent::NotifyMeshUpdated() +{ + MarkRenderStateDirty(); + if (Mesh) + { + LocalBounds = Mesh->CalcBounds(); + } + else + { + LocalBounds.Init(); + } + +#if WITH_EDITORONLY_DATA + UpdateSpriteComponent(); +#endif + + UpdateBounds(); +} + +#if WITH_EDITORONLY_DATA +void UHoudiniStaticMeshComponent::UpdateSpriteComponent() +{ + if (SpriteComponent) + { + SpriteComponent->SetRelativeLocation(FVector(0, 0, LocalBounds.GetExtent().Z)); + SpriteComponent->SetVisibility(bHoudiniIconVisible); + } +} +#endif + +int32 UHoudiniStaticMeshComponent::GetNumMaterials() const +{ + // From UStaticMesh: + // @note : you don't have to consider Materials.Num() + // that only counts if overridden and it can't be more than GetStaticMesh()->Materials. + if (Mesh) + { + return Mesh->GetNumStaticMaterials(); + } + else + { + return 0; + } +} + +int32 UHoudiniStaticMeshComponent::GetMaterialIndex(FName MaterialSlotName) const +{ + return Mesh ? Mesh->GetMaterialIndex(MaterialSlotName) : -1; +} + +TArray UHoudiniStaticMeshComponent::GetMaterialSlotNames() const +{ + TArray MaterialNames; + if (Mesh) + { + const TArray &StaticMaterials = Mesh->GetStaticMaterials(); + const int32 NumMaterials = StaticMaterials.Num(); + for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) + { + const FStaticMaterial &StaticMaterial = StaticMaterials[MaterialIndex]; + MaterialNames.Add(StaticMaterial.MaterialSlotName); + } + } + return MaterialNames; +} + +bool UHoudiniStaticMeshComponent::IsMaterialSlotNameValid(FName MaterialSlotName) const +{ + return GetMaterialIndex(MaterialSlotName) >= 0; +} + +UMaterialInterface* UHoudiniStaticMeshComponent::GetMaterial(int32 MaterialIndex) const +{ + // From UStaticMesh: + // If we have a base materials array, use that + if (OverrideMaterials.IsValidIndex(MaterialIndex) && OverrideMaterials[MaterialIndex]) + { + return OverrideMaterials[MaterialIndex]; + } + // Otherwise get from static mesh + else + { + return Mesh ? Mesh->GetMaterial(MaterialIndex) : nullptr; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h index 663beb3ff..98910af02 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h @@ -1,98 +1,98 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Components/MeshComponent.h" - -#include "HoudiniStaticMeshComponent.generated.h" - -class UHoudiniStaticMesh; -class UBillboardComponent; - -UCLASS(EditInlineNew, ClassGroup = "Houdini Engine | Rendering") -class HOUDINIENGINERUNTIME_API UHoudiniStaticMeshComponent : public UMeshComponent -{ - GENERATED_BODY() - -public: - UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer); - - UFUNCTION() - void SetMesh(UHoudiniStaticMesh *InMesh); - - UFUNCTION() - UHoudiniStaticMesh* GetMesh() { return Mesh; } - - // Call this if the mesh updated (outside of calling SetMesh). - UFUNCTION() - void NotifyMeshUpdated(); - - virtual void OnRegister() override; - - //virtual void PostLoad() override; - - // UPrimitiveComponent interface - virtual FPrimitiveSceneProxy* CreateSceneProxy() override; - virtual int32 GetNumMaterials() const override; - virtual UMaterialInterface* GetMaterial(int32 ElementIndex) const override; - virtual int32 GetMaterialIndex(FName MaterialSlotName) const override; - virtual TArray GetMaterialSlotNames() const override; - virtual bool IsMaterialSlotNameValid(FName MaterialSlotName) const override; - // end - UPrimitiveComponent interface - - // USceneComponent Interface. - virtual FBoxSphereBounds CalcBounds(const FTransform& InLocalToWorld) const override; - // end - USceneComponent Interface. - -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; -#endif - - UFUNCTION() - bool IsHoudiniIconVisible() const { return bHoudiniIconVisible; } - - UFUNCTION() - void SetHoudiniIconVisible(bool bInHoudiniIconVisible); - -protected: -#if WITH_EDITORONLY_DATA - virtual void UpdateSpriteComponent(); -#endif - - /** The mesh. */ - UPROPERTY(EditAnywhere, Category = "Mesh") - UHoudiniStaticMesh *Mesh; - - /** Local space bounds of mesh. */ - UPROPERTY() - FBox LocalBounds; - - UPROPERTY(EditAnywhere, Category = "Icons") - bool bHoudiniIconVisible; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Components/MeshComponent.h" + +#include "HoudiniStaticMeshComponent.generated.h" + +class UHoudiniStaticMesh; +class UBillboardComponent; + +UCLASS(EditInlineNew, ClassGroup = "Houdini Engine | Rendering") +class HOUDINIENGINERUNTIME_API UHoudiniStaticMeshComponent : public UMeshComponent +{ + GENERATED_BODY() + +public: + UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer); + + UFUNCTION() + void SetMesh(UHoudiniStaticMesh *InMesh); + + UFUNCTION() + UHoudiniStaticMesh* GetMesh() { return Mesh; } + + // Call this if the mesh updated (outside of calling SetMesh). + UFUNCTION() + void NotifyMeshUpdated(); + + virtual void OnRegister() override; + + //virtual void PostLoad() override; + + // UPrimitiveComponent interface + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + virtual int32 GetNumMaterials() const override; + virtual UMaterialInterface* GetMaterial(int32 ElementIndex) const override; + virtual int32 GetMaterialIndex(FName MaterialSlotName) const override; + virtual TArray GetMaterialSlotNames() const override; + virtual bool IsMaterialSlotNameValid(FName MaterialSlotName) const override; + // end - UPrimitiveComponent interface + + // USceneComponent Interface. + virtual FBoxSphereBounds CalcBounds(const FTransform& InLocalToWorld) const override; + // end - USceneComponent Interface. + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + UFUNCTION() + bool IsHoudiniIconVisible() const { return bHoudiniIconVisible; } + + UFUNCTION() + void SetHoudiniIconVisible(bool bInHoudiniIconVisible); + +protected: +#if WITH_EDITORONLY_DATA + virtual void UpdateSpriteComponent(); +#endif + + /** The mesh. */ + UPROPERTY(EditAnywhere, Category = "Mesh") + UHoudiniStaticMesh *Mesh; + + /** Local space bounds of mesh. */ + UPROPERTY() + FBox LocalBounds; + + UPROPERTY(EditAnywhere, Category = "Icons") + bool bHoudiniIconVisible; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp index 6a20ff8b6..da8c7c112 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp @@ -1,541 +1,541 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMeshSceneProxy.h" - -#include "Async/ParallelFor.h" -#include "Materials/Material.h" -#include "PrimitiveViewRelevance.h" -#include "Engine/Engine.h" - -#include "ProfilingDebugging/CpuProfilerTrace.h" - -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniStaticMesh.h" - -// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h - -// -// FHoudiniStaticMeshRenderBufferSet -// - -FHoudiniStaticMeshRenderBufferSet::FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type InFeatureLevel) - : LocalVertexFactory(InFeatureLevel, "FHoudiniStaticMeshRenderBufferSet") -{ -} - - -FHoudiniStaticMeshRenderBufferSet::~FHoudiniStaticMeshRenderBufferSet() -{ - check(IsInRenderingThread()); - - if (NumTriangles > 0) - { - PositionVertexBuffer.ReleaseResource(); - ColorVertexBuffer.ReleaseResource(); - StaticMeshVertexBuffer.ReleaseResource(); - LocalVertexFactory.ReleaseResource(); - if (TriangleIndexBuffer.IsInitialized()) - { - TriangleIndexBuffer.ReleaseResource(); - } - } -} - -void FHoudiniStaticMeshRenderBufferSet::CopyBuffers() -{ - check(IsInRenderingThread()); - - if (NumTriangles == 0) - { - return; - } - - InitOrUpdateResource(&PositionVertexBuffer); - InitOrUpdateResource(&ColorVertexBuffer); - InitOrUpdateResource(&StaticMeshVertexBuffer); - - FLocalVertexFactory::FDataType Data; - PositionVertexBuffer.BindPositionVertexBuffer(&LocalVertexFactory, Data); - StaticMeshVertexBuffer.BindTangentVertexBuffer(&LocalVertexFactory, Data); - StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&LocalVertexFactory, Data); - ColorVertexBuffer.BindColorVertexBuffer(&LocalVertexFactory, Data); - - LocalVertexFactory.SetData(Data); - InitOrUpdateResource(&LocalVertexFactory); - - if (TriangleIndexBuffer.Indices.Num() > 0) - { - TriangleIndexBuffer.InitResource(); - } -} - -void FHoudiniStaticMeshRenderBufferSet::InitOrUpdateResource(FRenderResource* Resource) -{ - check(IsInRenderingThread()); - - if (Resource->IsInitialized()) - Resource->UpdateRHI(); - else - Resource->InitResource(); -} - -void FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet) -{ - if (BufferSet->NumTriangles == 0) - { - return; - } - - ENQUEUE_RENDER_COMMAND(FMeshRenderBufferSetDestroy)( - [BufferSet](FRHICommandListImmediate& RHICmdList) - { - delete BufferSet; - }); -} - -// -// End - FHoudiniStaticMeshRenderBufferSet -// - -// -// FHoudiniStaticMeshSceneProxy -// - -FHoudiniStaticMeshSceneProxy::FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel) - : FPrimitiveSceneProxy(InComponent) - , DefaultVertexColor(255, 255, 255) - , FeatureLevel(InFeatureLevel) - , Component(InComponent) - , MaterialRelevance(InComponent ? InComponent->GetMaterialRelevance(InFeatureLevel) : FMaterialRelevance()) -{ -} - -FHoudiniStaticMeshSceneProxy::~FHoudiniStaticMeshSceneProxy() -{ - check(IsInRenderingThread()); - - for (FHoudiniStaticMeshRenderBufferSet* BufferSet : BufferSets) - { - FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); - } -} - -uint32 FHoudiniStaticMeshSceneProxy::AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet) -{ - OutBufferSet = MakeNewBufferSet(); - OutBufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); - - BufferSetsLock.Lock(); - const uint32 NewIndex = BufferSets.Add(OutBufferSet); - BufferSetsLock.Unlock(); - return NewIndex; -} - -void FHoudiniStaticMeshSceneProxy::ReleaseRenderBufferSet(uint32 InIndex) -{ - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - - BufferSetsLock.Lock(); - check(BufferSets.IsValidIndex(InIndex)); - BufferSet = BufferSets[InIndex]; - BufferSets.RemoveAt(InIndex); - BufferSetsLock.Unlock(); - - FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); -} - -void FHoudiniStaticMeshSceneProxy::UpdatedReferencedMaterials() -{ - // copied from FPrimitiveSceneProxy::FPrimitiveSceneProxy() -#if WITH_EDITOR - TArray Materials; - Component->GetUsedMaterials(Materials, true); - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshRenderBufferSetDestroy)( - [this, Materials](FRHICommandListImmediate& RHICmdList) - { - this->SetUsedMaterialForVerification(Materials); - }); -#endif -} - -void FHoudiniStaticMeshSceneProxy::Build() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::Build")); - - // Allocate a buffer set per material - const uint32 NumMaterials = GetNumMaterials(); - if (NumMaterials == 0) - { - // No materials, allocate a singel buffer set using the default material - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - AllocateNewRenderBufferSet(BufferSet); - BufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); - } - else - { - for (uint32 MaterialIdx = 0; MaterialIdx < NumMaterials; ++MaterialIdx) - { - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - AllocateNewRenderBufferSet(BufferSet); - BufferSet->Material = GetMaterial(MaterialIdx); - } - } - - if (Component) - { - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (Mesh) - { - if (NumMaterials > 1 && Mesh->HasPerFaceMaterials()) - { - BuildBufferSetsByMaterial(); - } - else - { - BuildSingleBufferSet(); - } - } - } -} - -void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const -{ - const bool bRenderAsWireframe = (AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe); - - // Set up the wireframe material - FMaterialRenderProxy *WireframeMaterialProxy = nullptr; - if (bRenderAsWireframe) - { - FColoredMaterialRenderProxy *WireframeMaterialInstance = new FColoredMaterialRenderProxy( - GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, - FLinearColor(0.6f, 0.6f, 0.6f) - ); - Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); - WireframeMaterialProxy = WireframeMaterialInstance; - } - - ESceneDepthPriorityGroup DepthPriority = SDPG_World; - - const int32 NumViews = Views.Num(); - for (int32 ViewIdx = 0; ViewIdx < NumViews; ++ViewIdx) - { - if (!(VisibilityMap & (1 << ViewIdx))) - continue; - - const FSceneView *View = Views[ViewIdx]; - - bool bHasPrecomputedVolumetricLightmap; - FMatrix PreviousLocalToWorld; - int32 SingleCaptureIndex; - bool bOutputVelocity; - GetScene().GetPrimitiveUniformShaderParameters_RenderThread( - GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity); - - const uint32 NumBufferSets = BufferSets.Num(); - for (uint32 BufferSetIdx = 0; BufferSetIdx < NumBufferSets; ++BufferSetIdx) - { - FHoudiniStaticMeshRenderBufferSet *BufferSet = BufferSets[BufferSetIdx]; - - UMaterialInterface *Material = BufferSet->Material; - FMaterialRenderProxy *MaterialProxy = Material->GetRenderProxy(); - - if (BufferSet->NumTriangles == 0) - continue; - - FDynamicPrimitiveUniformBuffer &DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); - DynamicPrimitiveUniformBuffer.Set( - GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity); - - if (BufferSet->TriangleIndexBuffer.Indices.Num() > 0) - { - FMeshBatch& Mesh = Collector.AllocateMesh(); - if (PopulateMeshElement(Mesh, *BufferSet, MaterialProxy, false, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) - { - Collector.AddMesh(ViewIdx, Mesh); - } - if (bRenderAsWireframe) - { - FMeshBatch& WireframeMesh = Collector.AllocateMesh(); - if (PopulateMeshElement(WireframeMesh, *BufferSet, WireframeMaterialProxy, true, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) - { - Collector.AddMesh(ViewIdx, WireframeMesh); - } - } - } - } - } -} - -bool FHoudiniStaticMeshSceneProxy::PopulateMeshElement( - FMeshBatch &InMeshBatch, - const FHoudiniStaticMeshRenderBufferSet& Buffers, - FMaterialRenderProxy* Material, - bool bRenderAsWireframe, - ESceneDepthPriorityGroup DepthPriority, - int ViewIndex, - FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const -{ - FMeshBatchElement& BatchElement = InMeshBatch.Elements[0]; - BatchElement.IndexBuffer = &Buffers.TriangleIndexBuffer; - InMeshBatch.bWireframe = bRenderAsWireframe; - InMeshBatch.VertexFactory = &Buffers.LocalVertexFactory; - InMeshBatch.MaterialRenderProxy = Material; - - BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; - - BatchElement.FirstIndex = 0; - BatchElement.NumPrimitives = Buffers.NumTriangles; - BatchElement.MinVertexIndex = 0; - BatchElement.MaxVertexIndex = Buffers.PositionVertexBuffer.GetNumVertices() - 1; - InMeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative(); - InMeshBatch.Type = PT_TriangleList; - InMeshBatch.DepthPriorityGroup = DepthPriority; - InMeshBatch.bCanApplyViewModeOverrides = false; - - return true; -} - - -FPrimitiveViewRelevance FHoudiniStaticMeshSceneProxy::GetViewRelevance(const FSceneView* View) const -{ - FPrimitiveViewRelevance Result; - - Result.bDrawRelevance = IsShown(View); - Result.bDynamicRelevance = true; - Result.bRenderCustomDepth = ShouldRenderCustomDepth(); - Result.bRenderInMainPass = ShouldRenderInMainPass(); - Result.bShadowRelevance = IsShadowCast(View); - Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; - Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); - MaterialRelevance.SetPrimitiveViewRelevance(Result); - Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass; - - return Result; -} - -bool FHoudiniStaticMeshSceneProxy::CanBeOccluded() const -{ - return !MaterialRelevance.bDisableDepthTest; -} - -void FHoudiniStaticMeshSceneProxy::PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs, uint32 InTriangleGroupStartIdx, uint32 InNumTrianglesInGroup) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::PopulateBuffers")); - - check(InMesh); - check(InBuffers); - - const uint32 NumTriangles = InTriangleIDs ? InNumTrianglesInGroup : InMesh->GetNumTriangles(); - InBuffers->NumTriangles = NumTriangles; - - if (NumTriangles == 0) - return; - - const uint32 NumVertices = NumTriangles * 3; - const uint32 NumUVLayers = InMesh->GetNumUVLayers(); - - InBuffers->PositionVertexBuffer.Init(NumVertices); - // There must be at least one UV layer - // TODO: Would it be possible to have no UV layers and bind to a dummy 0/black SRV? - InBuffers->StaticMeshVertexBuffer.Init(NumVertices, NumUVLayers > 0 ? NumUVLayers : 1); - InBuffers->ColorVertexBuffer.Init(NumVertices); - InBuffers->TriangleIndexBuffer.Indices.AddUninitialized(NumTriangles * 3); - - const TArray& VertexPositions = InMesh->GetVertexPositions(); - const TArray& TriangleIndices = InMesh->GetTriangleIndices(); - const TArray& VertexInstanceColors = InMesh->GetVertexInstanceColors(); - const TArray& VertexInstanceNormals = InMesh->GetVertexInstanceNormals(); - const TArray& VertexInstanceUTangents = InMesh->GetVertexInstanceUTangents(); - const TArray& VertexInstanceVTangents = InMesh->GetVertexInstanceVTangents(); - const TArray& VertexInstanceUVs = InMesh->GetVertexInstanceUVs(); - - const bool bHasColors = InMesh->HasColors(); - const bool bHasNormals = InMesh->HasNormals(); - const bool bHasTangents = InMesh->HasTangents(); - - FThreadSafeCounter VertCounter(0); - //for (uint32 TriangleIDIdx = 0; TriangleIDIdx < NumTriangles; ++TriangleIDIdx) - ParallelFor(NumTriangles, [&](uint32 TriangleIDIdx) - { - const uint32 TriangleID = InTriangleIDs ? (*InTriangleIDs)[InTriangleGroupStartIdx + TriangleIDIdx] : TriangleIDIdx; - const FIntVector &TriIndices = TriangleIndices[TriangleID]; - - FVector TangentU; - FVector TangentV; - uint32 VertIdx = VertCounter.Add(3); - for (uint8 TriVertIdx = 0; TriVertIdx < 3; ++TriVertIdx) - { - const uint32 MeshVtxIdx = TriIndices[TriVertIdx]; - const uint32 MeshVtxInstanceIdx = TriangleID * 3 + TriVertIdx; - - InBuffers->PositionVertexBuffer.VertexPosition(VertIdx) = VertexPositions[TriIndices[TriVertIdx]]; - - FVector Normal = bHasNormals ? VertexInstanceNormals[MeshVtxInstanceIdx] : FVector(0, 0, 1); - if (bHasTangents) - { - TangentU = VertexInstanceUTangents[MeshVtxInstanceIdx]; - TangentV = VertexInstanceVTangents[MeshVtxInstanceIdx]; - } - else - { - Normal.FindBestAxisVectors(TangentU, TangentV); - } - InBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, TangentU, TangentV, Normal); - - if (NumUVLayers > 0) - { - for (uint8 UVLayerIdx = 0; UVLayerIdx < NumUVLayers; ++UVLayerIdx) - { - InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, UVLayerIdx, VertexInstanceUVs[MeshVtxInstanceIdx]); - } - } - else - { - InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, 0, FVector2D::ZeroVector); - } - - InBuffers->ColorVertexBuffer.VertexColor(VertIdx) = bHasColors ? VertexInstanceColors[MeshVtxInstanceIdx] : DefaultVertexColor; - - InBuffers->TriangleIndexBuffer.Indices[VertIdx] = VertIdx; - VertIdx++; - } - }); -} - -void FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet")); - - if (!Component) - return; - - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (!Mesh) - return; - - if (BufferSets.Num() == 0) - return; - - FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets.Last(); - - PopulateBuffers(Mesh, Buffers); - - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( - [Buffers](FRHICommandListImmediate& RHICMdList) - { - Buffers->CopyBuffers(); - }); -} - -void FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial")); - - // We need to group tris by which material they use, and populate a buffer set for each group - if (!Component) - return; - - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (!Mesh) - return; - - if (BufferSets.Num() == 0) - return; - - const TArray& MaterialIDsPerTriangle = Mesh->GetMaterialIDsPerTriangle(); - - const uint32 NumTriangles = MaterialIDsPerTriangle.Num(); - const uint32 NumMaterials = GetNumMaterials(); - TArray TriCountPerMaterialSafe; - TriCountPerMaterialSafe.Init(FThreadSafeCounter(0), NumMaterials); - ParallelFor(NumTriangles, [&](uint32 TriangleID) - { - const int32 MatID = MaterialIDsPerTriangle[TriangleID]; - if (MatID >= 0 && (uint32) MatID < NumMaterials) - { - TriCountPerMaterialSafe[MatID].Increment(); - } - }); - - TArray TriCountPerMaterial; - TArray OffsetPerMaterial; - TArray WrittenPerMaterial; - TriCountPerMaterial.Init(0, NumMaterials); - OffsetPerMaterial.Init(0, NumMaterials); - WrittenPerMaterial.Init(FThreadSafeCounter(0), NumMaterials); - for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) - { - const uint32 Count = TriCountPerMaterialSafe[MatID].GetValue(); - TriCountPerMaterial[MatID] = Count; - if (MatID > 0) - { - OffsetPerMaterial[MatID] = OffsetPerMaterial[MatID - 1] + TriCountPerMaterial[MatID - 1]; - } - } - - TArray GroupTriangleIDs; - GroupTriangleIDs.Init(0, NumTriangles); - ParallelFor(NumTriangles, [&](uint32 TriangleID) - { - const int32 MatID = MaterialIDsPerTriangle[TriangleID]; - if (MatID >= 0 && (uint32) MatID < NumMaterials) - { - GroupTriangleIDs[OffsetPerMaterial[MatID] + WrittenPerMaterial[MatID].Add(1)] = TriangleID; - } - }); - - for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) - { - if (TriCountPerMaterial[MatID] == 0) - continue; - - FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets[MatID]; - - PopulateBuffers( - Mesh, Buffers, - &GroupTriangleIDs, OffsetPerMaterial[MatID], TriCountPerMaterial[MatID] - ); - - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( - [Buffers](FRHICommandListImmediate& RHICMdList) - { - Buffers->CopyBuffers(); - }); - } -} - -UMaterialInterface* FHoudiniStaticMeshSceneProxy::GetMaterial(uint32 InMaterialIdx) const -{ - if (!Component) - return UMaterial::GetDefaultMaterial(MD_Surface); - UMaterialInterface *Material = Component->GetMaterial(InMaterialIdx); - return Material ? Material : UMaterial::GetDefaultMaterial(MD_Surface); -} - -// -// End - FHoudiniStaticMeshSceneProxy -// +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMeshSceneProxy.h" + +#include "Async/ParallelFor.h" +#include "Materials/Material.h" +#include "PrimitiveViewRelevance.h" +#include "Engine/Engine.h" + +#include "ProfilingDebugging/CpuProfilerTrace.h" + +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniStaticMesh.h" + +// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h + +// +// FHoudiniStaticMeshRenderBufferSet +// + +FHoudiniStaticMeshRenderBufferSet::FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type InFeatureLevel) + : LocalVertexFactory(InFeatureLevel, "FHoudiniStaticMeshRenderBufferSet") +{ +} + + +FHoudiniStaticMeshRenderBufferSet::~FHoudiniStaticMeshRenderBufferSet() +{ + check(IsInRenderingThread()); + + if (NumTriangles > 0) + { + PositionVertexBuffer.ReleaseResource(); + ColorVertexBuffer.ReleaseResource(); + StaticMeshVertexBuffer.ReleaseResource(); + LocalVertexFactory.ReleaseResource(); + if (TriangleIndexBuffer.IsInitialized()) + { + TriangleIndexBuffer.ReleaseResource(); + } + } +} + +void FHoudiniStaticMeshRenderBufferSet::CopyBuffers() +{ + check(IsInRenderingThread()); + + if (NumTriangles == 0) + { + return; + } + + InitOrUpdateResource(&PositionVertexBuffer); + InitOrUpdateResource(&ColorVertexBuffer); + InitOrUpdateResource(&StaticMeshVertexBuffer); + + FLocalVertexFactory::FDataType Data; + PositionVertexBuffer.BindPositionVertexBuffer(&LocalVertexFactory, Data); + StaticMeshVertexBuffer.BindTangentVertexBuffer(&LocalVertexFactory, Data); + StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&LocalVertexFactory, Data); + ColorVertexBuffer.BindColorVertexBuffer(&LocalVertexFactory, Data); + + LocalVertexFactory.SetData(Data); + InitOrUpdateResource(&LocalVertexFactory); + + if (TriangleIndexBuffer.Indices.Num() > 0) + { + TriangleIndexBuffer.InitResource(); + } +} + +void FHoudiniStaticMeshRenderBufferSet::InitOrUpdateResource(FRenderResource* Resource) +{ + check(IsInRenderingThread()); + + if (Resource->IsInitialized()) + Resource->UpdateRHI(); + else + Resource->InitResource(); +} + +void FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet) +{ + if (BufferSet->NumTriangles == 0) + { + return; + } + + ENQUEUE_RENDER_COMMAND(FMeshRenderBufferSetDestroy)( + [BufferSet](FRHICommandListImmediate& RHICmdList) + { + delete BufferSet; + }); +} + +// +// End - FHoudiniStaticMeshRenderBufferSet +// + +// +// FHoudiniStaticMeshSceneProxy +// + +FHoudiniStaticMeshSceneProxy::FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel) + : FPrimitiveSceneProxy(InComponent) + , DefaultVertexColor(255, 255, 255) + , FeatureLevel(InFeatureLevel) + , Component(InComponent) + , MaterialRelevance(InComponent ? InComponent->GetMaterialRelevance(InFeatureLevel) : FMaterialRelevance()) +{ +} + +FHoudiniStaticMeshSceneProxy::~FHoudiniStaticMeshSceneProxy() +{ + check(IsInRenderingThread()); + + for (FHoudiniStaticMeshRenderBufferSet* BufferSet : BufferSets) + { + FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); + } +} + +uint32 FHoudiniStaticMeshSceneProxy::AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet) +{ + OutBufferSet = MakeNewBufferSet(); + OutBufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); + + BufferSetsLock.Lock(); + const uint32 NewIndex = BufferSets.Add(OutBufferSet); + BufferSetsLock.Unlock(); + return NewIndex; +} + +void FHoudiniStaticMeshSceneProxy::ReleaseRenderBufferSet(uint32 InIndex) +{ + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + + BufferSetsLock.Lock(); + check(BufferSets.IsValidIndex(InIndex)); + BufferSet = BufferSets[InIndex]; + BufferSets.RemoveAt(InIndex); + BufferSetsLock.Unlock(); + + FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); +} + +void FHoudiniStaticMeshSceneProxy::UpdatedReferencedMaterials() +{ + // copied from FPrimitiveSceneProxy::FPrimitiveSceneProxy() +#if WITH_EDITOR + TArray Materials; + Component->GetUsedMaterials(Materials, true); + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshRenderBufferSetDestroy)( + [this, Materials](FRHICommandListImmediate& RHICmdList) + { + this->SetUsedMaterialForVerification(Materials); + }); +#endif +} + +void FHoudiniStaticMeshSceneProxy::Build() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::Build")); + + // Allocate a buffer set per material + const uint32 NumMaterials = GetNumMaterials(); + if (NumMaterials == 0) + { + // No materials, allocate a singel buffer set using the default material + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + AllocateNewRenderBufferSet(BufferSet); + BufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); + } + else + { + for (uint32 MaterialIdx = 0; MaterialIdx < NumMaterials; ++MaterialIdx) + { + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + AllocateNewRenderBufferSet(BufferSet); + BufferSet->Material = GetMaterial(MaterialIdx); + } + } + + if (Component) + { + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (Mesh) + { + if (NumMaterials > 1 && Mesh->HasPerFaceMaterials()) + { + BuildBufferSetsByMaterial(); + } + else + { + BuildSingleBufferSet(); + } + } + } +} + +void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const +{ + const bool bRenderAsWireframe = (AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe); + + // Set up the wireframe material + FMaterialRenderProxy *WireframeMaterialProxy = nullptr; + if (bRenderAsWireframe) + { + FColoredMaterialRenderProxy *WireframeMaterialInstance = new FColoredMaterialRenderProxy( + GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, + FLinearColor(0.6f, 0.6f, 0.6f) + ); + Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); + WireframeMaterialProxy = WireframeMaterialInstance; + } + + ESceneDepthPriorityGroup DepthPriority = SDPG_World; + + const int32 NumViews = Views.Num(); + for (int32 ViewIdx = 0; ViewIdx < NumViews; ++ViewIdx) + { + if (!(VisibilityMap & (1 << ViewIdx))) + continue; + + const FSceneView *View = Views[ViewIdx]; + + bool bHasPrecomputedVolumetricLightmap; + FMatrix PreviousLocalToWorld; + int32 SingleCaptureIndex; + bool bOutputVelocity; + GetScene().GetPrimitiveUniformShaderParameters_RenderThread( + GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity); + + const uint32 NumBufferSets = BufferSets.Num(); + for (uint32 BufferSetIdx = 0; BufferSetIdx < NumBufferSets; ++BufferSetIdx) + { + FHoudiniStaticMeshRenderBufferSet *BufferSet = BufferSets[BufferSetIdx]; + + UMaterialInterface *Material = BufferSet->Material; + FMaterialRenderProxy *MaterialProxy = Material->GetRenderProxy(); + + if (BufferSet->NumTriangles == 0) + continue; + + FDynamicPrimitiveUniformBuffer &DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); + DynamicPrimitiveUniformBuffer.Set( + GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity); + + if (BufferSet->TriangleIndexBuffer.Indices.Num() > 0) + { + FMeshBatch& Mesh = Collector.AllocateMesh(); + if (PopulateMeshElement(Mesh, *BufferSet, MaterialProxy, false, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) + { + Collector.AddMesh(ViewIdx, Mesh); + } + if (bRenderAsWireframe) + { + FMeshBatch& WireframeMesh = Collector.AllocateMesh(); + if (PopulateMeshElement(WireframeMesh, *BufferSet, WireframeMaterialProxy, true, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) + { + Collector.AddMesh(ViewIdx, WireframeMesh); + } + } + } + } + } +} + +bool FHoudiniStaticMeshSceneProxy::PopulateMeshElement( + FMeshBatch &InMeshBatch, + const FHoudiniStaticMeshRenderBufferSet& Buffers, + FMaterialRenderProxy* Material, + bool bRenderAsWireframe, + ESceneDepthPriorityGroup DepthPriority, + int ViewIndex, + FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const +{ + FMeshBatchElement& BatchElement = InMeshBatch.Elements[0]; + BatchElement.IndexBuffer = &Buffers.TriangleIndexBuffer; + InMeshBatch.bWireframe = bRenderAsWireframe; + InMeshBatch.VertexFactory = &Buffers.LocalVertexFactory; + InMeshBatch.MaterialRenderProxy = Material; + + BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; + + BatchElement.FirstIndex = 0; + BatchElement.NumPrimitives = Buffers.NumTriangles; + BatchElement.MinVertexIndex = 0; + BatchElement.MaxVertexIndex = Buffers.PositionVertexBuffer.GetNumVertices() - 1; + InMeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative(); + InMeshBatch.Type = PT_TriangleList; + InMeshBatch.DepthPriorityGroup = DepthPriority; + InMeshBatch.bCanApplyViewModeOverrides = false; + + return true; +} + + +FPrimitiveViewRelevance FHoudiniStaticMeshSceneProxy::GetViewRelevance(const FSceneView* View) const +{ + FPrimitiveViewRelevance Result; + + Result.bDrawRelevance = IsShown(View); + Result.bDynamicRelevance = true; + Result.bRenderCustomDepth = ShouldRenderCustomDepth(); + Result.bRenderInMainPass = ShouldRenderInMainPass(); + Result.bShadowRelevance = IsShadowCast(View); + Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; + Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); + MaterialRelevance.SetPrimitiveViewRelevance(Result); + Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass; + + return Result; +} + +bool FHoudiniStaticMeshSceneProxy::CanBeOccluded() const +{ + return !MaterialRelevance.bDisableDepthTest; +} + +void FHoudiniStaticMeshSceneProxy::PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs, uint32 InTriangleGroupStartIdx, uint32 InNumTrianglesInGroup) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::PopulateBuffers")); + + check(InMesh); + check(InBuffers); + + const uint32 NumTriangles = InTriangleIDs ? InNumTrianglesInGroup : InMesh->GetNumTriangles(); + InBuffers->NumTriangles = NumTriangles; + + if (NumTriangles == 0) + return; + + const uint32 NumVertices = NumTriangles * 3; + const uint32 NumUVLayers = InMesh->GetNumUVLayers(); + + InBuffers->PositionVertexBuffer.Init(NumVertices); + // There must be at least one UV layer + // TODO: Would it be possible to have no UV layers and bind to a dummy 0/black SRV? + InBuffers->StaticMeshVertexBuffer.Init(NumVertices, NumUVLayers > 0 ? NumUVLayers : 1); + InBuffers->ColorVertexBuffer.Init(NumVertices); + InBuffers->TriangleIndexBuffer.Indices.AddUninitialized(NumTriangles * 3); + + const TArray& VertexPositions = InMesh->GetVertexPositions(); + const TArray& TriangleIndices = InMesh->GetTriangleIndices(); + const TArray& VertexInstanceColors = InMesh->GetVertexInstanceColors(); + const TArray& VertexInstanceNormals = InMesh->GetVertexInstanceNormals(); + const TArray& VertexInstanceUTangents = InMesh->GetVertexInstanceUTangents(); + const TArray& VertexInstanceVTangents = InMesh->GetVertexInstanceVTangents(); + const TArray& VertexInstanceUVs = InMesh->GetVertexInstanceUVs(); + + const bool bHasColors = InMesh->HasColors(); + const bool bHasNormals = InMesh->HasNormals(); + const bool bHasTangents = InMesh->HasTangents(); + + FThreadSafeCounter VertCounter(0); + //for (uint32 TriangleIDIdx = 0; TriangleIDIdx < NumTriangles; ++TriangleIDIdx) + ParallelFor(NumTriangles, [&](uint32 TriangleIDIdx) + { + const uint32 TriangleID = InTriangleIDs ? (*InTriangleIDs)[InTriangleGroupStartIdx + TriangleIDIdx] : TriangleIDIdx; + const FIntVector &TriIndices = TriangleIndices[TriangleID]; + + FVector TangentU; + FVector TangentV; + uint32 VertIdx = VertCounter.Add(3); + for (uint8 TriVertIdx = 0; TriVertIdx < 3; ++TriVertIdx) + { + const uint32 MeshVtxIdx = TriIndices[TriVertIdx]; + const uint32 MeshVtxInstanceIdx = TriangleID * 3 + TriVertIdx; + + InBuffers->PositionVertexBuffer.VertexPosition(VertIdx) = VertexPositions[TriIndices[TriVertIdx]]; + + FVector Normal = bHasNormals ? VertexInstanceNormals[MeshVtxInstanceIdx] : FVector(0, 0, 1); + if (bHasTangents) + { + TangentU = VertexInstanceUTangents[MeshVtxInstanceIdx]; + TangentV = VertexInstanceVTangents[MeshVtxInstanceIdx]; + } + else + { + Normal.FindBestAxisVectors(TangentU, TangentV); + } + InBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, TangentU, TangentV, Normal); + + if (NumUVLayers > 0) + { + for (uint8 UVLayerIdx = 0; UVLayerIdx < NumUVLayers; ++UVLayerIdx) + { + InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, UVLayerIdx, VertexInstanceUVs[MeshVtxInstanceIdx]); + } + } + else + { + InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, 0, FVector2D::ZeroVector); + } + + InBuffers->ColorVertexBuffer.VertexColor(VertIdx) = bHasColors ? VertexInstanceColors[MeshVtxInstanceIdx] : DefaultVertexColor; + + InBuffers->TriangleIndexBuffer.Indices[VertIdx] = VertIdx; + VertIdx++; + } + }); +} + +void FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet")); + + if (!Component) + return; + + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (!Mesh) + return; + + if (BufferSets.Num() == 0) + return; + + FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets.Last(); + + PopulateBuffers(Mesh, Buffers); + + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( + [Buffers](FRHICommandListImmediate& RHICMdList) + { + Buffers->CopyBuffers(); + }); +} + +void FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial")); + + // We need to group tris by which material they use, and populate a buffer set for each group + if (!Component) + return; + + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (!Mesh) + return; + + if (BufferSets.Num() == 0) + return; + + const TArray& MaterialIDsPerTriangle = Mesh->GetMaterialIDsPerTriangle(); + + const uint32 NumTriangles = MaterialIDsPerTriangle.Num(); + const uint32 NumMaterials = GetNumMaterials(); + TArray TriCountPerMaterialSafe; + TriCountPerMaterialSafe.Init(FThreadSafeCounter(0), NumMaterials); + ParallelFor(NumTriangles, [&](uint32 TriangleID) + { + const int32 MatID = MaterialIDsPerTriangle[TriangleID]; + if (MatID >= 0 && (uint32) MatID < NumMaterials) + { + TriCountPerMaterialSafe[MatID].Increment(); + } + }); + + TArray TriCountPerMaterial; + TArray OffsetPerMaterial; + TArray WrittenPerMaterial; + TriCountPerMaterial.Init(0, NumMaterials); + OffsetPerMaterial.Init(0, NumMaterials); + WrittenPerMaterial.Init(FThreadSafeCounter(0), NumMaterials); + for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) + { + const uint32 Count = TriCountPerMaterialSafe[MatID].GetValue(); + TriCountPerMaterial[MatID] = Count; + if (MatID > 0) + { + OffsetPerMaterial[MatID] = OffsetPerMaterial[MatID - 1] + TriCountPerMaterial[MatID - 1]; + } + } + + TArray GroupTriangleIDs; + GroupTriangleIDs.Init(0, NumTriangles); + ParallelFor(NumTriangles, [&](uint32 TriangleID) + { + const int32 MatID = MaterialIDsPerTriangle[TriangleID]; + if (MatID >= 0 && (uint32) MatID < NumMaterials) + { + GroupTriangleIDs[OffsetPerMaterial[MatID] + WrittenPerMaterial[MatID].Add(1)] = TriangleID; + } + }); + + for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) + { + if (TriCountPerMaterial[MatID] == 0) + continue; + + FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets[MatID]; + + PopulateBuffers( + Mesh, Buffers, + &GroupTriangleIDs, OffsetPerMaterial[MatID], TriCountPerMaterial[MatID] + ); + + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( + [Buffers](FRHICommandListImmediate& RHICMdList) + { + Buffers->CopyBuffers(); + }); + } +} + +UMaterialInterface* FHoudiniStaticMeshSceneProxy::GetMaterial(uint32 InMaterialIdx) const +{ + if (!Component) + return UMaterial::GetDefaultMaterial(MD_Surface); + UMaterialInterface *Material = Component->GetMaterial(InMaterialIdx); + return Material ? Material : UMaterial::GetDefaultMaterial(MD_Surface); +} + +// +// End - FHoudiniStaticMeshSceneProxy +// diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h index b9b1e2a66..66e474054 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h @@ -1,168 +1,168 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h - -#pragma once - -#include "CoreMinimal.h" -#include "PrimitiveSceneProxy.h" -#include "VertexFactory.h" -#include "LocalVertexFactory.h" -#include "Rendering/ColorVertexBuffer.h" -#include "Rendering/PositionVertexBuffer.h" -#include "Rendering/StaticMeshVertexBuffer.h" -#include "DynamicMeshBuilder.h" - -#include "HoudiniStaticMeshComponent.h" - -class UHoudiniStaticMesh; - -class FHoudiniStaticMeshRenderBufferSet -{ -public: - // Data members - - /** The number of triangles in the buffer set. */ - int NumTriangles; - - /** The static mesh data buffer. */ - FStaticMeshVertexBuffer StaticMeshVertexBuffer; - - /** The position buffer. */ - FPositionVertexBuffer PositionVertexBuffer; - - /** The triangle indices buffer. */ - FDynamicMeshIndexBuffer32 TriangleIndexBuffer; - - /** The color buffer */ - FColorVertexBuffer ColorVertexBuffer; - - FLocalVertexFactory LocalVertexFactory; - - /** Default material for this mesh. */ - UMaterialInterface* Material = nullptr; - - // Functions - - FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type FeatureLevelType); - - virtual ~FHoudiniStaticMeshRenderBufferSet(); - - /** - * Copy buffers to GPU. - * @warning render thread only. - */ - virtual void CopyBuffers(); - - /** - * Initialize (or update) a render resource. - * @warning Render thread only. - */ - void InitOrUpdateResource(FRenderResource* Resource); - -protected: - friend class FHoudiniStaticMeshSceneProxy; - - // Queue a command on the render thread to destroy the given buffer set - static void DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet); -}; - - -class FHoudiniStaticMeshSceneProxy : public FPrimitiveSceneProxy -{ -public: - FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel); - - virtual ~FHoudiniStaticMeshSceneProxy(); - - uint32 AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet); - - void ReleaseRenderBufferSet(uint32 InIndex); - - void UpdatedReferencedMaterials(); - - // Build buffer sets to render the mesh. - virtual void Build(); - - // FPrimitiveSceneProxy - virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; - - virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; - - virtual bool CanBeOccluded() const override; - - virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + FPrimitiveSceneProxy::GetAllocatedSize()); } - - SIZE_T GetTypeHash() const override - { - static size_t UniquePointer; - return reinterpret_cast(&UniquePointer); - } - - // end - FPrimitiveSceneProxy - - // Color to use if vertex does not have an assigned color. - FColor DefaultVertexColor; - - ERHIFeatureLevel::Type FeatureLevel; - -protected: - void PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs=nullptr, uint32 InTriangleGroupStartIdx=0u, uint32 InNumTrianglesInGroup=0u); - - // Virtual function for creating a new buffer set instances. - // Subclasses can overwrite this is they use a different buffer set with - // different instantiation requirements. - virtual FHoudiniStaticMeshRenderBufferSet* MakeNewBufferSet() { return new FHoudiniStaticMeshRenderBufferSet(FeatureLevel); } - - // Build a single buffer set for the entire mesh (one material for the entire mesh). - void BuildSingleBufferSet(); - - void BuildBufferSetsByMaterial(); - - // Get the number of materials from the parent mesh/component - uint32 GetNumMaterials() const { return Component ? Component->GetNumMaterials() : 0; } - - virtual bool PopulateMeshElement( - FMeshBatch &InMeshBatch, - const FHoudiniStaticMeshRenderBufferSet& Buffers, - FMaterialRenderProxy* Material, - bool bRenderAsWireframe, - ESceneDepthPriorityGroup DepthPriority, - int ViewIndex, - FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const; - - virtual UMaterialInterface* GetMaterial(uint32 InMaterialIdx) const; - - UHoudiniStaticMeshComponent *Component; - - TArray BufferSets; - - FCriticalSection BufferSetsLock; - - FMaterialRelevance MaterialRelevance; - +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h + +#pragma once + +#include "CoreMinimal.h" +#include "PrimitiveSceneProxy.h" +#include "VertexFactory.h" +#include "LocalVertexFactory.h" +#include "Rendering/ColorVertexBuffer.h" +#include "Rendering/PositionVertexBuffer.h" +#include "Rendering/StaticMeshVertexBuffer.h" +#include "DynamicMeshBuilder.h" + +#include "HoudiniStaticMeshComponent.h" + +class UHoudiniStaticMesh; + +class FHoudiniStaticMeshRenderBufferSet +{ +public: + // Data members + + /** The number of triangles in the buffer set. */ + int NumTriangles; + + /** The static mesh data buffer. */ + FStaticMeshVertexBuffer StaticMeshVertexBuffer; + + /** The position buffer. */ + FPositionVertexBuffer PositionVertexBuffer; + + /** The triangle indices buffer. */ + FDynamicMeshIndexBuffer32 TriangleIndexBuffer; + + /** The color buffer */ + FColorVertexBuffer ColorVertexBuffer; + + FLocalVertexFactory LocalVertexFactory; + + /** Default material for this mesh. */ + UMaterialInterface* Material = nullptr; + + // Functions + + FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type FeatureLevelType); + + virtual ~FHoudiniStaticMeshRenderBufferSet(); + + /** + * Copy buffers to GPU. + * @warning render thread only. + */ + virtual void CopyBuffers(); + + /** + * Initialize (or update) a render resource. + * @warning Render thread only. + */ + void InitOrUpdateResource(FRenderResource* Resource); + +protected: + friend class FHoudiniStaticMeshSceneProxy; + + // Queue a command on the render thread to destroy the given buffer set + static void DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet); +}; + + +class FHoudiniStaticMeshSceneProxy : public FPrimitiveSceneProxy +{ +public: + FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel); + + virtual ~FHoudiniStaticMeshSceneProxy(); + + uint32 AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet); + + void ReleaseRenderBufferSet(uint32 InIndex); + + void UpdatedReferencedMaterials(); + + // Build buffer sets to render the mesh. + virtual void Build(); + + // FPrimitiveSceneProxy + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; + + virtual bool CanBeOccluded() const override; + + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + FPrimitiveSceneProxy::GetAllocatedSize()); } + + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast(&UniquePointer); + } + + // end - FPrimitiveSceneProxy + + // Color to use if vertex does not have an assigned color. + FColor DefaultVertexColor; + + ERHIFeatureLevel::Type FeatureLevel; + +protected: + void PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs=nullptr, uint32 InTriangleGroupStartIdx=0u, uint32 InNumTrianglesInGroup=0u); + + // Virtual function for creating a new buffer set instances. + // Subclasses can overwrite this is they use a different buffer set with + // different instantiation requirements. + virtual FHoudiniStaticMeshRenderBufferSet* MakeNewBufferSet() { return new FHoudiniStaticMeshRenderBufferSet(FeatureLevel); } + + // Build a single buffer set for the entire mesh (one material for the entire mesh). + void BuildSingleBufferSet(); + + void BuildBufferSetsByMaterial(); + + // Get the number of materials from the parent mesh/component + uint32 GetNumMaterials() const { return Component ? Component->GetNumMaterials() : 0; } + + virtual bool PopulateMeshElement( + FMeshBatch &InMeshBatch, + const FHoudiniStaticMeshRenderBufferSet& Buffers, + FMaterialRenderProxy* Material, + bool bRenderAsWireframe, + ESceneDepthPriorityGroup DepthPriority, + int ViewIndex, + FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const; + + virtual UMaterialInterface* GetMaterial(uint32 InMaterialIdx) const; + + UHoudiniStaticMeshComponent *Component; + + TArray BufferSets; + + FCriticalSection BufferSetsLock; + + FMaterialRelevance MaterialRelevance; + }; \ No newline at end of file From 9cbc67ec49f49333f93cc0e2fff5ac3910b0ee24 Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Tue, 19 Jan 2021 16:51:40 -0500 Subject: [PATCH 02/16] Houdini Engine for Unreal - Version 2.0 Official release of version 2.0. The plug-in is now linked to Houdini 18.5.462 / HAPI 3.5.1. Documentation for version 2.0 of the plug-in is available: https://www.sidefx.com/docs/unreal/ ------------------------------------------------------- New features: ------------------------------------------------------- - Added back the Static Mesh Build Settings from v1 (tangent/normal generation, lightmap index etc.) Their values can be changed in the plugin's setting dialog. - Added back the Static Mesh Generation Properties from v1. Their default values can be set in the plugin settings, or overridden per HDA in the Houdini Mesh Generation section. - Added back the Houdini Engine commands from v1 (Houdini.BakeAll, Houdini.CookAll etc...) - Added support for 4.25 on Mac OS. ------------------------------------------------------- Bug fixes: ------------------------------------------------------- - Fixed instantiation loop that could lock up the editor when not having access to a license, and attempting to load an HDA without the source .hda file. - Fixed Open In Houdini / Open Session Sync not using the proper executable when using the steam version of Houdini Indie. We now try to use hindie.steam executable if houdini wasn't found - Fixed button parameters being triggered upon rebuild/recook. - Fixed button parameters being triggered on first cook after loading a level. - Fixed properties modified by attributes on instanced actors being lost upon baking. - Fixed possible crash with foliage instancers after load/new level - Fixed HARS/Houdini crashing when using linear and closed editable curves in a HDA. - Fixed primitive attributes on LODs (generic attributes, screensize) not being properly set due to invalid Prim/Vertex Index. - Fixed the lodX_screensize/unreal_uproperty_screensize attributes being ignored. - Restricted the classes displayed in the drop down menu when using string as asset picker without specify a class. Too many object types were listed, causing the drop down to be very slow to open. Any UObject can still be dragged and dropped on the asset picker. - Removed unnecessary Outer checks when baking landscape. This should fix Landscape Baking failing when using PDG. - Disabled the creation of attributes for material parameters of input meshes. This would cause crashes in HARC when a lot of of string attributes were sent. - Ticking is now limited to 1 tick per frame. This reduces game-thread starvation during cook, DXGI_ERROR_DEVICE_HUNG at the end of a cook when the command list is too big. - Optimization: Reduced unnecessary/redundant string fetch via HAPI when listing Generic Attributes, fixed slow object copy on some for loops. - Added some Insights tag for better profiling overview. --- Content/MaterialFunctions/MF_VAT_Fluid.uasset | Bin 0 -> 124637 bytes Content/MaterialFunctions/MF_VAT_Rigid.uasset | Bin 0 -> 142528 bytes Content/MaterialFunctions/MF_VAT_Soft.uasset | Bin 0 -> 167505 bytes .../MaterialFunctions/MF_VAT_Sprite.uasset | Bin 0 -> 145706 bytes .../HoudiniVertexAnimationFluid.uasset | Bin 122413 -> 0 bytes .../HoudiniVertexAnimationRigid.uasset | Bin 92650 -> 0 bytes .../HoudiniVertexAnimationSoft.uasset | Bin 117581 -> 0 bytes .../HoudiniVertexAnimationSprite.uasset | Bin 125942 -> 0 bytes Content/Materials/M_VAT_Fluid.uasset | Bin 0 -> 93225 bytes Content/Materials/M_VAT_Rigid.uasset | Bin 0 -> 102889 bytes Content/Materials/M_VAT_Soft.uasset | Bin 0 -> 101786 bytes Content/Materials/M_VAT_Sprite.uasset | Bin 0 -> 91689 bytes HoudiniEngine.uplugin | 6 +- LICENSE.md | 186 +- README.md | 210 +- Source/HoudiniEngine/HoudiniEngine.Build.cs | 34 +- Source/HoudiniEngine/Private/HBSPOps.cpp | 2945 ++-- Source/HoudiniEngine/Private/HBSPOps.h | 338 +- Source/HoudiniEngine/Private/HCsgUtils.cpp | 2897 +-- Source/HoudiniEngine/Private/HCsgUtils.h | 531 +- Source/HoudiniEngine/Private/HoudiniApi.cpp | 7542 ++++---- .../HoudiniEngine/Private/HoudiniEngine.cpp | 2242 ++- Source/HoudiniEngine/Private/HoudiniEngine.h | 614 +- .../Private/HoudiniEngineCommandlet.cpp | 25 - .../Private/HoudiniEngineCommandlet.h | 27 - .../Private/HoudiniEngineManager.cpp | 3106 ++-- .../Private/HoudiniEngineManager.h | 362 +- .../Private/HoudiniEngineOutputStats.cpp | 93 +- .../Private/HoudiniEngineOutputStats.h | 142 +- .../Private/HoudiniEnginePrivatePCH.h | 748 +- .../Private/HoudiniEngineProcessor.cpp | 25 - .../Private/HoudiniEngineProcessor.h | 27 - .../Private/HoudiniEngineScheduler.cpp | 1238 +- .../Private/HoudiniEngineScheduler.h | 234 +- .../Private/HoudiniEngineString.cpp | 398 +- .../Private/HoudiniEngineString.h | 144 +- .../Private/HoudiniEngineTask.cpp | 96 +- .../HoudiniEngine/Private/HoudiniEngineTask.h | 200 +- .../Private/HoudiniEngineTaskInfo.cpp | 92 +- .../Private/HoudiniEngineTaskInfo.h | 156 +- .../Private/HoudiniEngineUtils.cpp | 9747 +++++----- .../Private/HoudiniEngineUtils.h | 1246 +- .../Private/HoudiniGeoImportCommandlet.cpp | 1554 +- .../Private/HoudiniGeoImportCommandlet.h | 304 +- .../Private/HoudiniGeoImporter.cpp | 1686 +- .../Private/HoudiniGeoImporter.h | 242 +- .../Private/HoudiniHandleTranslator.cpp | 739 +- .../Private/HoudiniHandleTranslator.h | 105 +- .../Private/HoudiniInputTranslator.cpp | 5778 +++--- .../Private/HoudiniInputTranslator.h | 404 +- .../Private/HoudiniInstanceTranslator.cpp | 6103 +++---- .../Private/HoudiniInstanceTranslator.h | 724 +- .../Private/HoudiniLandscapeTranslator.cpp | 7345 ++++---- .../Private/HoudiniLandscapeTranslator.h | 800 +- .../Private/HoudiniMaterialTranslator.cpp | 6468 ++++--- .../Private/HoudiniMaterialTranslator.h | 438 +- .../Private/HoudiniMeshTranslator.cpp | 12724 +++++++------- .../Private/HoudiniMeshTranslator.h | 822 +- .../Private/HoudiniOutputTranslator.cpp | 4092 ++--- .../Private/HoudiniOutputTranslator.h | 188 +- .../Private/HoudiniPDGImporterMessages.cpp | 187 +- .../Private/HoudiniPDGImporterMessages.h | 348 +- .../Private/HoudiniPDGManager.cpp | 4116 ++--- .../HoudiniEngine/Private/HoudiniPDGManager.h | 397 +- .../Private/HoudiniPDGTranslator.cpp | 983 +- .../Private/HoudiniPDGTranslator.h | 150 +- .../Private/HoudiniPackageParams.cpp | 870 +- .../Private/HoudiniPackageParams.h | 478 +- .../Private/HoudiniParameterTranslator.cpp | 5874 +++---- .../Private/HoudiniParameterTranslator.h | 296 +- .../Private/HoudiniSplineTranslator.cpp | 3218 ++-- .../Private/HoudiniSplineTranslator.h | 230 +- .../Private/HoudiniStringResolver.cpp | 277 +- .../Private/HoudiniStringResolver.h | 175 +- .../Private/SAssetSelectionWidget.cpp | 316 +- .../Private/SAssetSelectionWidget.h | 198 +- .../Private/UnrealBrushTranslator.cpp | 870 +- .../Private/UnrealBrushTranslator.h | 98 +- .../Private/UnrealInstanceTranslator.cpp | 422 +- .../Private/UnrealInstanceTranslator.h | 96 +- .../Private/UnrealLandscapeTranslator.cpp | 3979 ++--- .../Private/UnrealLandscapeTranslator.h | 466 +- .../Private/UnrealMeshTranslator.cpp | 8373 ++++----- .../Private/UnrealMeshTranslator.h | 338 +- .../Private/UnrealSplineTranslator.cpp | 247 +- .../Private/UnrealSplineTranslator.h | 76 +- Source/HoudiniEngine/Public/HAPI/HAPI.h | 39 +- .../HoudiniEngine/Public/HAPI/HAPI_Common.h | 46 +- .../HoudiniEngine/Public/HAPI/HAPI_Version.h | 4 +- Source/HoudiniEngine/Public/HoudiniApi.h | 1954 +-- .../HoudiniEngineEditor.Build.cs | 242 +- .../Private/AssetTypeActions_HoudiniAsset.cpp | 916 +- .../Private/AssetTypeActions_HoudiniAsset.h | 168 +- .../Private/HoudiniAssetActorFactory.cpp | 232 +- .../Private/HoudiniAssetActorFactory.h | 116 +- .../Private/HoudiniAssetBroker.cpp | 142 +- .../Private/HoudiniAssetBroker.h | 98 +- .../Private/HoudiniAssetComponentDetails.cpp | 930 +- .../Private/HoudiniAssetComponentDetails.h | 170 +- .../Private/HoudiniAssetFactory.cpp | 416 +- .../Private/HoudiniAssetFactory.h | 142 +- .../Private/HoudiniEngineBakeUtils.cpp | 10417 +++++------ .../Private/HoudiniEngineBakeUtils.h | 1243 +- .../Private/HoudiniEngineCommands.cpp | 3436 ++-- .../Private/HoudiniEngineCommands.h | 525 +- .../Private/HoudiniEngineDetails.cpp | 3899 ++-- .../Private/HoudiniEngineDetails.h | 239 +- .../Private/HoudiniEngineEditor.cpp | 3017 ++-- .../Private/HoudiniEngineEditor.h | 695 +- .../HoudiniEngineEditorLocalization.cpp | 25 - .../Private/HoudiniEngineEditorLocalization.h | 27 - .../Private/HoudiniEngineEditorPrivatePCH.h | 296 +- .../Private/HoudiniEngineEditorUtils.cpp | 1306 +- .../Private/HoudiniEngineEditorUtils.h | 170 +- .../Private/HoudiniEngineStyle.cpp | 620 +- .../Private/HoudiniEngineStyle.h | 82 +- .../Private/HoudiniEngineTool.h | 27 - .../Private/HoudiniGeoFactory.cpp | 762 +- .../Private/HoudiniGeoFactory.h | 166 +- .../HoudiniHandleComponentVisualizer.cpp | 518 +- .../HoudiniHandleComponentVisualizer.h | 226 +- .../Private/HoudiniHandleDetails.cpp | 795 +- .../Private/HoudiniHandleDetails.h | 86 +- .../Private/HoudiniInputDetails.cpp | 9944 ++++++----- .../Private/HoudiniInputDetails.h | 331 +- .../Private/HoudiniOutputDetails.cpp | 6486 ++++--- .../Private/HoudiniOutputDetails.h | 426 +- .../Private/HoudiniPDGDetails.cpp | 5254 +++--- .../Private/HoudiniPDGDetails.h | 280 +- .../Private/HoudiniParameterDetails.cpp | 14644 ++++++++-------- .../Private/HoudiniParameterDetails.h | 966 +- .../Private/HoudiniRuntimeSettingsDetails.cpp | 644 +- .../Private/HoudiniRuntimeSettingsDetails.h | 142 +- .../HoudiniSplineComponentVisualizer.cpp | 2064 +-- .../HoudiniSplineComponentVisualizer.h | 350 +- .../Private/HoudiniTool.cpp | 52 +- .../HoudiniEngineEditor/Private/HoudiniTool.h | 111 +- .../Private/SNewFilePathPicker.cpp | 701 +- .../Private/SNewFilePathPicker.h | 297 +- .../Public/IHoudiniEngineEditor.h | 142 +- .../HoudiniEngineRuntime.Build.cs | 188 +- .../Private/HoudiniAsset.cpp | 400 +- .../Private/HoudiniAsset.h | 206 +- .../Private/HoudiniAssetActor.cpp | 290 +- .../Private/HoudiniAssetActor.h | 154 +- .../HoudiniAssetBlueprintComponent.cpp | 4737 +++-- .../Private/HoudiniAssetBlueprintComponent.h | 702 +- .../Private/HoudiniAssetComponent.cpp | 5021 +++--- .../Private/HoudiniAssetComponent.h | 1540 +- .../Private/HoudiniCompatibilityHelpers.cpp | 3599 ++-- .../Private/HoudiniCompatibilityHelpers.h | 2194 +-- .../HoudiniEngineCopyPropertiesInterface.cpp | 64 +- .../HoudiniEngineCopyPropertiesInterface.h | 100 +- .../Private/HoudiniEngineRuntime.cpp | 646 +- .../Private/HoudiniEngineRuntime.h | 212 +- .../Private/HoudiniEngineRuntimePrivatePCH.h | 453 +- .../Private/HoudiniEngineRuntimeUtils.cpp | 1097 +- .../Private/HoudiniEngineRuntimeUtils.h | 679 +- .../Private/HoudiniGenericAttribute.cpp | 2154 +-- .../Private/HoudiniGenericAttribute.h | 244 +- .../Private/HoudiniGeoAsset.cpp | 25 - .../Private/HoudiniGeoAsset.h | 28 - .../Private/HoudiniGeoPartObject.cpp | 368 +- .../Private/HoudiniGeoPartObject.h | 836 +- .../Private/HoudiniHandleComponent.cpp | 508 +- .../Private/HoudiniHandleComponent.h | 270 +- .../Private/HoudiniInput.cpp | 5168 +++--- .../Private/HoudiniInput.h | 1120 +- .../Private/HoudiniInputObject.cpp | 3342 ++-- .../Private/HoudiniInputObject.h | 1608 +- .../Private/HoudiniInputTypes.h | 134 +- .../HoudiniInstancedActorComponent.cpp | 491 +- .../Private/HoudiniInstancedActorComponent.h | 182 +- .../Private/HoudiniMaterialMap.cpp | 25 - .../Private/HoudiniMaterialMap.h | 28 - .../HoudiniMeshSplitInstancerComponent.cpp | 489 +- .../HoudiniMeshSplitInstancerComponent.h | 170 +- .../Private/HoudiniOutput.cpp | 1715 +- .../Private/HoudiniOutput.h | 1122 +- .../Private/HoudiniOutputTypes.h | 30 - .../Private/HoudiniPDGAssetLink.cpp | 2962 ++-- .../Private/HoudiniPDGAssetLink.h | 1240 +- .../Private/HoudiniParameter.cpp | 516 +- .../Private/HoudiniParameter.h | 650 +- .../Private/HoudiniParameterButton.cpp | 100 +- .../Private/HoudiniParameterButton.h | 86 +- .../Private/HoudiniParameterButtonStrip.cpp | 146 +- .../Private/HoudiniParameterButtonStrip.h | 130 +- .../Private/HoudiniParameterChoice.cpp | 518 +- .../Private/HoudiniParameterChoice.h | 248 +- .../Private/HoudiniParameterColor.cpp | 166 +- .../Private/HoudiniParameterColor.h | 144 +- .../Private/HoudiniParameterFile.cpp | 202 +- .../Private/HoudiniParameterFile.h | 154 +- .../Private/HoudiniParameterFloat.cpp | 316 +- .../Private/HoudiniParameterFloat.h | 328 +- .../Private/HoudiniParameterFolder.cpp | 104 +- .../Private/HoudiniParameterFolder.h | 218 +- .../Private/HoudiniParameterFolderList.cpp | 216 +- .../Private/HoudiniParameterFolderList.h | 160 +- .../Private/HoudiniParameterInt.cpp | 236 +- .../Private/HoudiniParameterInt.h | 260 +- .../Private/HoudiniParameterLabel.cpp | 124 +- .../Private/HoudiniParameterLabel.h | 110 +- .../Private/HoudiniParameterMultiParm.cpp | 288 +- .../Private/HoudiniParameterMultiParm.h | 256 +- .../Private/HoudiniParameterOperatorPath.cpp | 190 +- .../Private/HoudiniParameterOperatorPath.h | 113 +- .../Private/HoudiniParameterRamp.cpp | 1382 +- .../Private/HoudiniParameterRamp.h | 622 +- .../Private/HoudiniParameterSeparator.cpp | 102 +- .../Private/HoudiniParameterSeparator.h | 86 +- .../Private/HoudiniParameterString.cpp | 270 +- .../Private/HoudiniParameterString.h | 180 +- .../Private/HoudiniParameterToggle.cpp | 190 +- .../Private/HoudiniParameterToggle.h | 134 +- .../HoudiniPluginSerializationVersion.cpp | 68 +- .../HoudiniPluginSerializationVersion.h | 185 +- .../Private/HoudiniRuntimeSettings.cpp | 754 +- .../Private/HoudiniRuntimeSettings.h | 766 +- .../Private/HoudiniSplineComponent.cpp | 1378 +- .../Private/HoudiniSplineComponent.h | 598 +- .../Private/HoudiniStaticMesh.cpp | 606 +- .../Private/HoudiniStaticMesh.h | 452 +- .../Private/HoudiniStaticMeshComponent.cpp | 426 +- .../Private/HoudiniStaticMeshComponent.h | 194 +- .../Private/HoudiniStaticMeshSceneProxy.cpp | 1082 +- .../Private/HoudiniStaticMeshSceneProxy.h | 334 +- 228 files changed, 129829 insertions(+), 128771 deletions(-) create mode 100644 Content/MaterialFunctions/MF_VAT_Fluid.uasset create mode 100644 Content/MaterialFunctions/MF_VAT_Rigid.uasset create mode 100644 Content/MaterialFunctions/MF_VAT_Soft.uasset create mode 100644 Content/MaterialFunctions/MF_VAT_Sprite.uasset delete mode 100644 Content/Materials/HoudiniVertexAnimationFluid.uasset delete mode 100644 Content/Materials/HoudiniVertexAnimationRigid.uasset delete mode 100644 Content/Materials/HoudiniVertexAnimationSoft.uasset delete mode 100644 Content/Materials/HoudiniVertexAnimationSprite.uasset create mode 100644 Content/Materials/M_VAT_Fluid.uasset create mode 100644 Content/Materials/M_VAT_Rigid.uasset create mode 100644 Content/Materials/M_VAT_Soft.uasset create mode 100644 Content/Materials/M_VAT_Sprite.uasset delete mode 100644 Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp delete mode 100644 Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h delete mode 100644 Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp delete mode 100644 Source/HoudiniEngine/Private/HoudiniEngineProcessor.h delete mode 100644 Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp delete mode 100644 Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h delete mode 100644 Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h delete mode 100644 Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp delete mode 100644 Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h delete mode 100644 Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp delete mode 100644 Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h delete mode 100644 Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h diff --git a/Content/MaterialFunctions/MF_VAT_Fluid.uasset b/Content/MaterialFunctions/MF_VAT_Fluid.uasset new file mode 100644 index 0000000000000000000000000000000000000000..aaf670e17c6f35a927c4c645cf9a9659828dc25d GIT binary patch literal 124637 zcmcHh1z6Ni_dgCVwR8xQl7fN)f;316X8vzG3fj?$g2R?(xSdKv+P8pO05wMu<=LtgMW{Ss__|0YTxj5L7+LClUw*G@Yh^K-f{&Oi~DBSsX>6 zl9KGtJY|ak5xXtEuwbV0?#BDmE3Dp#A)_9F>CDWCxatsMrWZyaF!N+@&gg7Kb074gVB+1NS3_AaXCaG0B& zxq~~;B~v+=wYi4_+*Hlk&C%Qel6B`!FyntT(1N);d$?J`+^vx6XxLT0&{ccakR6CIq>MpT;f4@ zOToj*5^m@0Fgx1owt0 z7kst@^79bd3)$*~D9S?C*}=iw#T{k^F>p$vq54_m=m>LypZBn^aDeIBx!a*G5KT65 zae!$d@8#m`4!h)h(ayrn+|3(OT|~wY1v-D25Jw0$MhrM-XNP}a$*>C|=s;T?cbJA7 z%+n5bRoC3X1196XH%$=bE%Apxnt8)fHTHTtuEt#pG|M`eySqc+SH151;G7+t-5|Ml zU^WCm1~_I_a~H_l2R_Gu-^n^VTA+NwO4-T91AfWd1?CPJZcum+OpF$(d%*vZNr>}u z;r@_0xx>#x?m2l#lKo(v-K^}KP#&f1WCinr+_uopC;F}29io_YU;0NMJlx^Vjyk%$ zkT*#d)B8)#LfnIJZ|yJPgJeJZ#s-GEH}oHdfRMdg<_8N{4(4tNK~Cqh0W81(mq7Up z1ncxDKIoU6vj=K6nD^Yz9b)U|I|xQCkM^#Ah7F)HVz}$dMAmx1z zjZ1J3*-h3NHS9}n=1%U`sD-2bV|73>Uop%9FGS6}8_eAu)g470WjTo2$g?oe zrC-HAup!Jd*_gof6dat*;r~qLX2$R~J}BK=1rWEzvYQ}YpbO-!?0$6Mhwr&V>iWAW zz?}Uf{s-J0lKtX)9T*u}_5;^c+tS>@+znDFCe;gq$M3>DgYn5hxCc@Q!5UD6xG_Jd z0Uo93=Ir4Dfvj8BgDFr1i&9o&!xjW!Wh)50Y||Rl`Z+kih#S|yeE#6nP-`A?xluUi zpOPQ8{d~v20?ePX)34z_?GjP}i)(My$f6v}#@Wpd<_<-O#L#c444%S zXLmc`1$x>LwGzK_pyz;^R#p%lTW>6;gQYGmFej^@lML|M!|dR zz=8*e9Z)U=dBv5>36}E#a_1kj2O0bDz>oR=Rl^0fNjbq)&E4%GMevHLWB-qOsO4zx z1c%%xIZ{b`U==w#Pdh6ZBsAq6Im5vfoPqtVJWzp@F^7-!;4c4kJAFvaYCrOCH4Gql zQ#IiSn+4_RsE`yQZ}|0Kt17seTS6Sv$yb;U?B{Qu3ei5UMGdy?|FkZ;-p}v_>AxLl zaxmTx7ha`?V6U{{=5RYp?W=ZhOWR*c zm*yqe4(_40hXovEdN@R=sLBpRyZ^N5UR?MQ_kM(Xh+FvGr_6t=ySF#{S_umMwcgKg z3wiTmdi>yNx@3n6AByQwGY8MXKVcZ+^ETJyU?@FjHwP~q*f9@T$0+QrgfR3LmGuW7;WEHP~?c*PxV*b&Es@QigKxfrF94%mO%<9(6 zsAb}a+O458WQL&rPdopEDl7txUt*$_i%1 ztc_X_%=!?iZ%b$YEH!}KSdP>ES-R)#Ke~94?G-UFAbO5a7V(1kx&+bxUTEh8xf|t4 z3hePu%{|wI5XAU#f-U|}0W%-utt7^?AA|jQsDjjt(=`83yzh_&VfEYkap=>6*u@Ca z0DJ8}2&7#U-U8aSWmGjT%4=OR(2z$Tnw1iQ%b0=466OTEWQ&?7Cv!UoWhZNAJv+Fq zhMS!uYFF}vLGH;hB!L|gm`5MdaOZS2nlpd|MymIL9%(zfp@Oj;YRf|%L!&ouu>7dv z0z;iUheUTOz#zcthM_iGtEZLsfAkXW_Rr*>L%6X4pU{T8p~Cq;#k`QjEnu_%IPZTJ zqV5JR`eS!Rh)UamJ^<#Qrf)v|WxbBLU4XF1qvu*{E!qD+iVYxNzHHKgO@FU8 z>R7R}E!_d8f3E}`By?EWf0XXG9;EOhf6&PVSz<+YY}p|6k&OxV3*gqE3f{17hHQoN(vE!2$7f zKTeO!zsK47!w`_`v(rO=i%Vk{efe6^>A=N zTmb%-yr(|P>u_+a{=iZ6`S&=RfA}>x7op?mI*JDAi|@b3+4utooUy=-BCs3Pp88?G ze~$y+|DaUUIU5PUc|;JgNK$oAC7g#3G)wLkm->~ZLL&Aq2SHS} z`r+W%?#&MxXDRG(aM1C36@>>9^v2=fpyM?<3~U_|4mw_=?dBcv@5l4~55ERyDxejt z9|S6c6nX34n~JP!2tcJ5ej>AfY5u>&0nbVI<1nHh_@T=GSDg8Mwgl&0 z`8{0#2OX~q^8WpJ4#GkE8~VJHm47%muBfqq@t|>Po*xd5+aEYR1&4#T(S8IRa zpo;$|zdi^Dz5mGVsqaM`4i0+XMvsTS?r?B$|FE0?o5R5YbF+VbhTk3z4*DFAwwqM_ z;ozXp@o1dXhJTMU{fA$JXYuHGfL=csjlXaJ^@~F>2PBlr|2X$f?JEQ4H}EVT=mGOH zyQgqt9}cSc{|pDcet!IM14{2i^Y0`0odd|fr~h_5==%jU&V1|dnt$hh#{k?xIFo(1G;oyL^3g!om!}Z~CaM16~p6uCe=8$mE@6FIScRv36 z@l5>T2jH71pcU9{U~fEvy}xik(eK<|rG1x!*3UsW=(vNur;x1gcg??Z`vCUH{}u-w zcMyB+j`shq`FHMr$3Z`*K;!iMAK-lbV}8JQX`sLR<4)Yb?<4q~`|rkc5Dt2N&~bEZ z@ORC>bN@RIdVbJ2e8azM{+;{ZanSRF#t9nvUGwkUe}^;v$NZ?HnxW4J=ylqP{G}OC zzqtRz03`?c^_M?zz;}jdoa1{6-9~?D{$Fv%{=fm>ETVDHpB?eXX&3hLFuP`b@uKCTD4Fa&ISYzr-p5m+Xv%%O6p9AVOwsMZ`$(F7ya|dvw#Nf|10K``G|1eh>Cyz=OVkwTPzK_tD^+Cv^GA186K%83`(*v4Ae{9Wc7h@qM)9J{r6yMb|Od zqtX2bI>38JwA^qXZMKgF*yuXo{tQi@-$&E!qk#{j>wxcd(KL8wfu>FO(fjQH{1nu| zLS=ByM2`{h;Jg813|a?RFKF6gj|TNPP#J9}z@I^7aLxyH0S}&O(C*`@Q8d#&{=_~W z*a*ZK&=%0F`)Ia38g0-2f3HBF+@225FTn2~KQ@X7V+Pnj4q~hEJ|6gt@ID?q$49SW zpbLEe0owkz{Q_DuZ}vfyy+f3}Od}XTTP~ zuk=tE_z+mHAa((N1Z&a^m4PpTm;u&;AS#3T26GDH9as}Ws0{1@d=$h?X;hX$W#FH@ zsC*Wc`B0f3l?6~4*cPmDU~{m(fo;K>25TA23-EhjTeNQge^NqaWmHx{J-^31^~+K7Y{aY3}Sll_%9 zY0ILY`;y68YqhitZfVjr$6E#21Egz3m5|G1bJ20#cQ$u+cEaLygdNjFU!LZvRN86p zo%Dti2F(-5z#z{~ZMispdN!ZD`c2!f_M^!JktE`K7MxJk^1L&tFpp_9+{m*stjEJO_%oO3RfmCW>Zz+GSL28!SXtLU*GI#`E1rhdMz^Vc z-ROKRc)j8r{FS{b1!WJBc69~k&<%nh8E6!zL#57Hhir4?d=(w@&e&cGcuW9 zh^~~Vti;t~TQ6;n)`iZG3YzrxAz^2;Q2(>uUf30@It0LgpcDp9rC-|6JH#HfR2sVz3(A+ z)mSj;9Z$$XYV?ppm5Ta_i|<}PzN9dF68CKICk^s5*zqsaX;$Jw&yoq=uv9XeT~_?G zT{lBS-;+3r{5tz05$7oZDXUarwSNU}T6=e-8+&ylU9}5NxP0~Mvc0f+vN--Q{sU}R z*cvkUeJg)(q^sFYnTeruiRVb>nYWQ<%@(R2yjFRkXX@jUm3#QhvDmQ^HC4Eg6|yAs zfm@7gxWrCK4EF7I!i^j?d<)#+`?I+eH^N@FlhAv_;;r@*?#vC3+@2kdAlZTz2)Npk z7cdE!A!Iv!7swtZRhk=n1#D6!e6l2UW^$4Y+$o!Hs#+d{>hIM z7`wRvS*!@AY9nI>=ZAxloV+9_=SS5$Pn4eLk4;UpGMT)2^BM7XHOJY>XJ6Y=4Cji? zR~~zL#pHS&Rla*m_!C*p&Cti^y8M}>6eK6x>(Ue89TH(!vnvB1sl`eay|CHBMYY^b zUGk*F;zx^PD{`xKs9c_Wda{K_QV<$6T zGO2Pg8F`}bOmwcC7`0M%pbf#cB>WO`eJ2kZb4B9x`-{-)C!<>}&8)rPW0d4#XNxBI z#PXuP8PcbvU>sq@>_VjTW!1Hd__dYe=q=o(PMJBvPvGL=pCz_;JC}*P=#Sid|ZYUwCT| zmEKo*nN_sZ8-p=b7jBb{fErp)S(FN1t}@6sx#+YVKN@k9SLG4=7-P~M{MPa#mJLN~ zvT;~Zi^>$eNc!`ki{H93k~K`K{Z5UgF>G{j`d@CEoE<+RsmLd@a2Gpqom>Em_tax0 z9@!?3E4Z^eR#66_nxF63*neM{cBT;{#pQC3i_N5y$8!Jl_Bc?tnDgy+ zu=AL_?nGmWqr(k;eTm9gt&9|rLZUuPclq*)L~{pDdWq-yY|kw1b_1Xsm?1*PD54XN zGO+6t))C|y;`Dzu>VMa~=v`;mlTI1q*gz%6N7g;@CHTa1t(mWsJv5k$(E?4BBb^n< zlq=u10}_c`Y#jcn0f%gB)M z3G5WHNUWFsa8fe4v$5KK(HMf)5@d>u&4R4wWpVqURvP&98|m9{PFakIqM^wL z)i1OiVGiV9`-(DzYc_}GLI&q*Ul6=w`P9{ecxa2buE~a*BjCj@@qvjVwuHa@8Pl>? z9%?DiJ;y0eT$l5@CY;E?A90!$PuXxm;A&N(h)cq^Ui<0~1gS+uUNUEj{5a(|&d+7Z zoP=aXvy5=IYu<=Wid!XO7P(2G^C`gq?`yQCr9l_Y1oM4ZkrD$cl)EmkAJhkf#S?cQxi^Rgb69%nyOm^o;NiN)#HF%ac^&$2nW6Nmx z$6`|HZ-_K!>NpY3R=FB6D?420OLfe~#&1SSqZ02(ges4=TaJ!a1i*^Eb-q;in8x4y z?eX$$y&Kr$mj+*6g)x&pk`cm1+FG(0jsy(C37FSi{A08H@aR_n#(ONdPBd8 znd;vy;y)MBG;P`w-=($kR*54-rdC-xcagu*MqOPZVkN??NrW@fQmFLS=Z&Bv799om zJ%ZI8-Ef*L9{R|Bs9?V;tp3v3dK?CmzB=*Y&UC3|uKUwB7ei;KE6It&IUM4;3eGdD zw8gzDk_bIt%+0l8-`t-jD*hT*B$V`oLs`OGdww&~@PZ`T_VHR}t5pXc8ZVjZT!INf zkH(QJRL;J1&@+|~;1s>BtJhD|^i+$G&}}i^@YB8a1kXA0agL_`mX38X%{e@b#^Kea zHmMS>7p5DS^+ZF`amT!+qM9eNPPrJ>mvm^Z&~M5;@0}6H!xauMyM9V?tFiiNL_hRF ztZ@G4&G}N?@HR=`XVuQ#+A4+Q^RzQNU&*M3+AXBE-Vm^t-!#GY#EGY|ke)T4k-$+n zcdV|-r8`wBdRAmt_vl5Pn7rDTh8i7tX2~sV`dtIxo&6S_p`6EvBCKy4=F|mXr;!oW zI+SuyiC{p@hJ(vW!ey`nAZHa;GE5=FA0@bmRXC{2uYcx%B;cQ%@3tv@7D>`h>gY+c zT+&V1$EpaWhhGaQG_4m2#%Q~%ca*c!+k%w6mNKpq-#B)W(Lks=FU6^-GkT|iC6S?9 z>Zv^oECwsT)nP|2?0snP=jFQOA{t%Z+T>zO@vB<%(zA84YfoEGd}|+w4ZigNJ1CWO zAg$ir#nR(_+H~Bp$cLw1>0mj}MBSF^&A1~)hgB%9s~~3lDT2oz+a~3vW34Qoz1$^& zRrblybDpwi9`=Qh`C?@|_PDPPaHias_bgUfgF_!GQB))1W$QA-vJ!VI6eSR0S0>zs zLl_^%gkpS4_6?4{ zkK+{`z3O!Fs(#3I_c_JD+1(=bYi;d~LuBPf+63JD6(#ixYSmgK`Mv88 zPAIa~^V^8Kr`q^j@y$gLiMbF-_jReTJj9Z3OyRM%CLqx z$DfC3QuE=8!J>uU^7|*+54*MI2J*v{Z+~E59-(nWm`Oe5dZZ&A0IOZHz=x{BM0@X@ z&1Vu9#ex_a)f*I6-fDF_*%?g8VCdZFTG`7|o)Jh-hmr2m?`tNXG}?2iUe9hmt$yax z3z#k~krL6RqVrJ*;HmbbW!vd_<{u^pQuk(_OBmTFxK;uqpWfE0x?|{6c~Uq7Ykm zojVk}Xe7A%LIz@GLZtUSLzpV=a885ypo00hcI+6zOhtxPk%Agg)Yj7;ag9&I(~8*) zFXvm4q3-fu@1?a&T_n-yd!`nQSHgThH>oEGmkB{;j2r6x=+)}@Erg=WxP6GyM)pu= z7s379iH_Z@556mwRBt%FoCSH8FNR6p59XF_>=oYPJLg)cR<+4TUCTN{&LBb_V6%iE zxX7!)wwOicpo=$D7uQ-LoU1>iEN!TW%~J00I-2R&$kXakyA>w0HB2eK(8rbO(~oog zXXn0SdBC6hpK{(H3hN=}Mi?;Oimh%-k?MkULZTRvFG30YJ1BRm)Y(sZz2_$N%Jxj~=a9?%`B_VG z31#uf(0ryRB5@HHY7t*sp17Fg(}&>bv)g8Kz~AAU=rtF@Mv36%(uO%iMn!7GJd*NX z14_ouCDjGF-TN@ALGvm~UKCDx{IssRNSx2}ceS(jJX5bs*&jSOLfFP-GLY&!XOyaeCOyuUdWqZEp|Vz1CmbTnTxm9 z2|9Ya-pAH#i+KH3iok1S{5~{@dXn=&jB9WZ-__ANJpCPnIyvKmj!wZMzSKDQx;}g6 z5>cp#w!KU32puG*ML9dDc+^Y@w#`!tDURFz8In+V&OF>~P!c5`V+o6?()nHO;uGHpaM#bRM6sU}YgOa>o=`eh)3BcmD3 zmxz^2n*?|@*N6K%@^5rxpDGN(dhkqpgg1;tgv{@0ovMTjpV`IN9Y-X?oRo<;oENHs zRi2$YIx$E+AT_)}e@0T}5fk?0MH~u-o6QB8f;X7AbcnESebCqvCD#hrUVIf9QTST7 z%*s1G@3TttOYwOBgmStg3kKuqG?=l2R4>VcEg`XlGVD9klnRMQbIlbWSn5$KIse^`I7m#|No#R03gYbrOPw->NM|a#7ILOzv1o=mdrF z_ez|^-r$~?pyjqwQljNl`-iX7Pkxjo-Y8~K%c$u;>u15eMrGuYHATeMu0m#5&s^8{ zvU}OY584(fI{%K~xY&*6&Znzo3-}XOx3K7==kwV_i~LkoM?-mvXey~}oEQ}E+-G3& znK!7eUlb>=0~9ec@bII<~v&OyYppkhN9_T`>Tn2hNM0h7S|x6;9HquSg0rG;wGX1CL& zDV5FI@wphq^7Cs(h2HQz8$w|bHm>@Ye^`?e3#gixk(!8w>IGvI=@5EceWRk%}_$wX17R%6v zOI13G!J2u?Z&Fh*+HD{Py0u&+m%p^9skuP~<;vmHH{RCK;>9x4X+3^pS(D)S1&@D& z^`5gLf}QUsi|un6ga@tFgFE6{x08Y_Q@wY6DzUUeY?k@esK~mk5-*c~Fel0;bQwR} zsh}~YMrOl{`N6N%GRH`kU7h^XmT!&)tHP3Ik_MRwGBo@imb3oyllTD|+uZL}q!}$^ zFHfi5rvBDrKXmNF6vG9*S82Fb!6e^nVhid#Cz~f+-$BXT1YO%Ni5G@s=fn=->W_Vy zqi3$&-mKZ(9LHcx@ZWqn))uhJADbCiz?+|<-N3e+YZCjI4&Qb-g(}J;le6OE_IuUnLC2|j2Nbj!wofJm(tH+ zL)Y}pRj%FV^BSe-E$LZ{VEbH*#AsqyTYq9UM#@!gfvh%pi~7fl!)yZ=#Z8)+{I6K( zK5B!!R?3P?=VoJgs3n;wFNqa=dcfuGXiJXp`h!!Hio1lRGWLll(!@(#Rb1T+KV^RQ z>BLc+Y-hnD5%mgY*y_z?9p!KOosNyVz#INrB^M^=kIC9&JE#VC1HEVO&qy2N1hvs^<^TQxdkcRDiXSc za9&f6Qj1_;@y~A8s}!ZQ2{HA$&STk>{>V9C=}{9?>QZ{(?u&uYb6cZt5J&vdqPTl2 z3PseAH6I&EXmue(%ro&f?YVCWO1p84Bc)gF9ueypfJOJ>e}FhNj@9XSjFF1uDN*J& z(hYxvj3EfzIVo-Oy=+4I%AF;|JziOye2Y~+<}T7(54(f9^L!!Y$+-TxInIBA#eL>J zA_c?jW7^mHk|f%v+S}&1x-DyEtq{I+t1&G5dx%0D^Wz$m=3>u9w60@>`IPx)zl|^%5wR18I$Uy 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 K-_~OS0 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:Xe{L{anQ>sR@0#O&o= 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 literal 0 HcmV?d00001 diff --git a/Content/MaterialFunctions/MF_VAT_Soft.uasset b/Content/MaterialFunctions/MF_VAT_Soft.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c436a8fde1aa1b786d10a3ad1bb631c8b09053a3 GIT binary patch literal 167505 zcmcGX2|QHa`}oHak~Jx^lU>Qa?^||RQ+5WEWtbU6WQ`QEFImf0DcK{kRY+wkS&|Uh z*CImw@0HtUzP^2W{eQoHb6>B=oO7OY&-I_mXQTcEv zE&0*ii-sQ;Js~WzQvn8JSP%TS3czhU*V;;_IhYs$h`KSmhWn|40p|Pxj#n7S3#z$0 zA>84DN+^sm66uN-G=#f5z){9XWB3J(7YZ)8`-v755|(g6!F^#O_ z^g`Lg(SpjZw)QSCWiHVlpcSC@_tr9VMquDZo?f;n_$~oP_A16q4eJ(lFL!$k0_l$4 zZNpmE76V5iY+ZjTP6`VGWzK@eC|h^5BNF8%azO+rKp`%`E{3lL90waah4gYjxFdFl z`Cm;4>Z)6tDj8cFAssQWjpmmPrzwVf;GCSju3$ekmYv0ue^~$zr1ljtE~qb?f^4)&!jv2v&hX2?v7FRva@rAnL)oHyVfE!yB4F2l&hb8R_&8`1I~`jz#sCHPM!ia>c+jw`6bZ+~$h`YV6wKliu-0 z5UAp6i$=pRJ{RJ4a!6Ms3U)6J?nDaez#OZttq1H`sviRwI4VdtJHV+OG~GSCFvh+f za5QYVP5n7IwUA2R3-e1SCwo$0|BlWbjj?sd!0ww~Rsij%0yrN=8J6hoB~P`JMWO&- z0=!TYh>;7hXgi}~vi~SY!!$CJ6n8r1g~lM=OiYDf=pf>2*d@ZSy9Lw7uuDW>Otn!e z*d?Mcx>>P{pfz@_vtuh5Rm&F3P_X+5-`NC zpflPDeAw+urbW;j0G~m@(P*GM8YY^mFue~2;b52kDc)hjSm*EJ11sd-a!m&q-DZdg z=zag(@pH0Z{FZ#^ol*QE{8Rw}_3bQLr*;g??=EPv!3X39G`6zso)V8uveLM#E?#&{Cjrq2iz2K*LUCqc*|i zLj#6lO>F=}MgxWP@_@lM>{~$x((pn!z*G#rIfGjyU_MOErXyJE=P>=RUI_!eaVMu| z>jsCNZI=l8RkGt5AL9aQLGROa|Hs_e`}RV9D1;q`;1W86}t#S==&|Q-;;^ z1zJHl4Op*|ZZVWIgmo;B><6)sQ~xs&N)8S%6K7unmfveVJb?Z0r`;8vC<8ZL?x2gWD|5n2TIElDpcC1j2shwr` zmwLNP)|H3Ub?kqvBCGu_o;AA^^0ox&Skub;a z2lq3k&cGVh21ou>!yHC3+Ys@40|H?TctVKUxyj$#A8;;vn5+K5Ro3736AFi)?rg+v zcmzDf{r7;Odb`m(7#(Q!d)`jGtQ~)d60W5+==F?&VluRTKN}M>0S8@2-*!R!Jre4An0dyXFo}c7qbBm$IyO7@Q$$q z9HRm7=)k($KTo`&2(S)VX6popp*611g8sOh*Fge1C&CYKGNe1$CfdW+9u7N*@x20W zh`W`388K5J5B?M2urmXG@H4RGLQ2ekmYKuUADyQG&k?^ocBi{K$12G|+d{{0z*xW& zhDQ+Ne-l@*ypsHIucvpN@}E}YoMcMXl!nvX8m(f`njj>&kU1h1@-oD zcet@LFpuuG2v<#aN2D17<7|LJxB-t9-f&o=DpMx7i5dZ`$r)H%9A7<@GlvC6=?{S2 zGD4z&T>t@W;=pst_0216J5@a3z{&r;)c0C&Bw*~rfn$Y3eqGW|S23tx&LPu;ItKcT z5e5b9BfpAI!ZNl%Pu)2dG)XBUKopqyRpY5N<{{WPWKpy3nZ{r7{?8Mq zY_a1raE5=W;0~<2Tker&y*BVPVr;0SXQZxgsB5HeVyFU45$MCPas8qF;Gyx?jXw|6 zJI5?v{WB?`wM>A`MMU_QErtaowFrS?;K>z6a%!JFXqTTXQP^C4(HYR_Kl4AgKtBt- z1@N%_vkZ8+haQ+=G~xDN!CwF7d7Rot_#N15GbGB@f%TW;>N`53;TRYyEJFoQ!oWZW zmo@b>U~cD|5OA|qIVmY5rJ^n@B_XP!Dk&l^sU{_*E~ca|DkY{YBqbpQa|SB?Pae)s zV6A^AetVkaIm!Eb#ZITd&OsMAo!|u?gTQML_(H_(XL9FDATSB(1^}JTA9T8Z(CPg_ zr~e0?!5?&||DZGcgU;v=I^#d+O#YzzIpx3?bGvo0ZD#(5`_BGBckT~5%RlIVZ`gLO z-}S}%4?3Ga=xqO>v-^Y2{tr5bKj`3p&^i7==ky017-!)1JG&r$s(!0G{|BARA9Sw& zqU#6Z=<2Qx+%dols{0PmrT;AY)t8IAdF&5p4A?sG>r?Rhoeq2i2Cv`gz$ahu`kf9c zgFdl&P~U&21J^?Eg6bsw%)*9&@qtS;c*wIjHnMKY=EHwFzl{#{un#&3W+RpO*hGIRSe(+b?m$xwMC) z-GLiU;2us|CvG^PFJsToWEXBY;E@BHBi8fxoNX*01pOM^Pr+8PIias{!-4!dXSez8 zH@M-X?2ZTGc)rCACv}hAsQYolN!!D*9>5Lf#vac7LELb_y$9RhHs9ffvo}sP-{Xb@ z?jzXaNg2is2i)(lITIiLp0l-QJpj+p47;2S;1j$=M*p6(xrYOu$D#G$@NV-0W4PgP z0xZyO5J&mr-*dk084vgj5xL7T-fjNOIBq!Lxego;)o%0mCUC>Kw#%X3ZNBakZa7i9 z9Om8TUrpkM6TQnp>^46;g&R)HE{A-#dE*(}aIWuiXm*>ooy83&c9$ct+dO6tHymia zhWt8b9yc85Tm|`c$->`rz?WtKGwod9xeEN=0BjYEQ)qsg7IDLY*3rP-=HD#ghLgKH z9*ASKf*TI_42EsDp3k`9}h-fg}YM;z!}1&wFu+ux7p>mIv--#bEfn*h?_6|{*P4rDio z1K+|82l6+FqqY6_oV7i61HY4n>;`?mBlsOR9LR1v?;--+fFHQwfWHEQ@dKTEN%27P z&kH2}LxoEJ^Aj-9uRRUY{LXIBxfkNF;^T$`oqM6@5^)0DaG-N9G#)`h+;E_CFT_!U z-niYJoByl%fzG`U=Nu7k#si&uq51J7#tjEL_d;=co&+}>Xx)M0w0b{oIM8!FG(Tzw zaKi!D0#Km&IY)*Y4){I=_PS$CjvEf?9?p4+zvrxCt$E-%9{fEHY!w_2hkKz_h@5H}oXJP?PL=I=S5_l(CJ7!SBkV*544A>44F^BXiC zV%opwtn3*N_**SBo zK>h~V?Y1;-IA3<{mbp6-^)k5OtnPB4@tl*x4QFkSA9%{+hVymLcp4ON!&%?M=~cuH z2R!eB@#C`V2eV4J;e6ZWK=EU$f*a1}9!{PrZaC2S02$)WHpB@Ao05x_{4^-?MMq05;qET<@y) z_nf&s99w_`?Q_ucHe4S!94Jm9j+Fs!IJ~?5264LWyYutv3~o5k z{sWDN`YdiZ(EbCB2X*f6IWv3w+8*!$uvO6Cpg66v_=f|Ua@U+d>h~AW~selF8<#tlb%*WVzH>G{9sOzrV&IKY7X06JG$x&FiX-^S_h zIFKLc?Zywr4L2OyUAsZ!X?DjA$8MJcji(=p8xFXZg6j^%;qv@@&g33Ha0J>1rv>CR z0Rmna6mB?RtJoYdG;TP^J)AWhaXj{L9%67a9?w0T3toTE`LxFmoB#&o2k(G1cu9Hx z!vXW4{!bUsE*_nh%PevJV9ngI}SJ;(zDyvjoV;ef8b>+UZgmlV&l%e@KP~_RiXX@iWF!BcGm6Cl z5HNS}4{(9MHL-#V18|LCaekKk{x3LuP=4ocd&|Io8w_6H2$XhGz@M1EAI}KZc))$( zDj;B=Kz}0x1iX~6I6q5%{};Ruy!Q4TECYSZ77(zh8Vx`Tn(rj31Qmiy9R(d~H8jOFiP0-&D0YU?aA|O!N zH6RVXFO6M>0rFm0W#GBu1F#r40-te!!~>E52-Mf>yJ>LVl>s@9l?UToVmA-kBpOSX z1f&hIbl}HmLB0Xj^9CevR|n>m0KxVN4ImHp32OHQkmkh7gL@0q zKk$BbtUTCX@EtqoUKEhV_7QNMg8GE*M-*7)l2~aetn^8&w9sxE@}2+bC)iIXto9JQ zX>dH?+(EVj^TdEa{RZ==0RiV58lygt2K@xe8vtq0N1!}-k2hA{2S{Jo&4c~0!_t9q z2FKEY@5n;!fU$lKEAIiMJ$Li}zvBmaZvnpq+uWP(#VYUHO@nQ%AyU|tO?54H#Cp>cqH-EvcQ9^1-vakMFrGl)Iu8gKGvL}K0tjS#a9tGz1l*^V z0C4~W^f_?95d?%95YT_YHO>GKQ$V5tAp!(k3&7a72E+ysTR@Zn(F8;b5N$wo0MP|R z1(3^tr~(oWNDLrqfJ6YI4#*Wit^;BO2pD5vT$upE0!R=b!GMqeq6dfuAZ~!T0)hm@ z1rT>YFo1Xg;tdECSD;UL00PE_A0T{y@B<$Bo&bVxIn`LLV6w#7HOcTK~8#<6euCrJf#XW`%_5*8q5N9+Yxj>O;si1 z3wW5vW(mjNdlvL9^mkI-!V}u6dDIhqDalTml=qM@-4#N`pz$cjQx}#*TD`w^f38D1 z&d(4ydo5bN$m_HyFii{CR`^c&W1G9Dx3Eb2QBA$O?e(gaA0$I@ZNo!k8A;7Oml{d5 zlBoK$3t8`ME^IC$e6<#j`ZXi(=eM?MoIfS5BRzF`cqVkwfT~5oOXNZL%_B_71k!oB z(wF1Zdg~g`_;fK$hXt<94fM{>TN+zyQZP}}_b{Age8r2NB1cZAQ&b-&I5IY-k$sJ_ zqvWB4L}+)Yti0{RC48ij7f*IS;iC@?-IRH>QTN<5;pqh;4dN6-W3N0-$eTv&dD{l+ zG-s|~*WWhG;zTX?t}|blcawgSkKf*z^|<=c<65`Vsh7{mxhj=J#~B>Gi7)K&w#doB zB<}v?nT1~a&ylZ!UdX1j+Wfg5>gFy13m9tAzCHts}^={I_ zw=0ChhVhKjuXzUqgidHC?5P{sM{QpL>7%*E~wvWc{Fjw4Ss6{#^t z)!5u9Z?}9bZ=qiM7(r?FmE}Xa8-7<4;qBAa=e1`?n^^l5tM_O4>+V0~tT3#gN`IS7 zz~kX%6QX@_k1D(syU!qc+Lnr+=JU##9RJXxqY>eFRJFcdE%{wht&`8qk15gxA{5Jn zG{N74Zgy%`S8B`=iP*BlA?KM~8aureR!s8xkJR$i!>_=}ssLU>Q@7A3Sa#oUHZh^j|-cIpo$5*;Lxj8vZ zbPHK>`Zj0K0ndWOrJWfQ1Tv;YY~B|W(OsqZnxn+hqBUswq?OA~?6$nzFjt9}U#aI> zd3w=gXEx`_DiN3U%}r^Z^GZtZ-oKaQa60d{F5D&{QekGEKp4@eeHN#upmDs}c@>HFsbUT;n z2v5VM?PtC3Bvc}bxt=%M+Iudi^U^RR=(jkH1)si^$rRRejV?0u+_#wLsU-I`?s2|X zESR2IFvtpr^`r(-H26O&_2zrMac=#tSi9$yIMPEO-4vB&+h=5DN|o=$g}mx}wTh{3 z(yztix8~%wEbJrwtSmm+QUqI-*7STPw~9y2lg-mFS%vypV~WIxsXw=vgl<5@Yvf3L^xKkG)O zKkClhZsxaiKg#g_4`v13O)`Tjhf0DR*~DI@KKci!z9UsuV`uIaaL6yakr*&l?^{ac zYFIbEUm>1cX>E(@?!Q<#ftJ6W+Hc) z>R73Lw-2}wuQZF{-3&NY>zZPEP`B2v#G=^p7U`JHN7uz}O;)O#oX5Up>B)tu1V6=# zMm!jvdNFg&>!{c2!^|HaBd(iuo{3XC+%~`~zt}&>A>$dMY6@0_1jFIQ+$ zj?o@j))P8PKgu8puRZBY%3bEMCNUC=cwJ=n;6eTNrw_b_lZ3&368FwjUbCqd(`}~- zBrGVA$cmSDVU!4wPL-wmEJkDZnfP(miKMshC-EGeQo7j=``Q*OJkJ!3CbiWXA$wW% zoM*mq;S@5xLZL1&Vuc4Y8XJSz-Z*vC^htR}Pb5b~Z&`LOQ`rPFhaLWG7yB(+y%=xi z{p`VM=3Z4F4YYep{er#+wOk>)%pUHR+#y`X82;R(rPta)(QbX|nOJD<`>pa)nk*JG&WVzq|aLRpbJNcU2$-M`qW-;6sx7ktM-568Ei*io%S<;`HU1&a?{)z0L6J`AdQ>vQ%$d4*_zV;knIW zk00WH`lg&nrm_SXk)B{yPlvOYVQGF!x2a@B$=dsOKJ>x&P?wSu_{k)c=N$KZsk(WtkedRs4gJ|(C74hi!{3rnfQ z%6{%Xa$$tSDpl^OuA`=uh3K)>q>2ZN#_k78ikXXrJiFEpiccn}I4Ox7(B(=06h#8Uja0WTfxFzO-Pr8YxKQ#3*eN=E7&2@{pNX>K}{jf`by zQL%EI8&|Ef;^$`g83IW&-aNmzY(`hau=!N!(BZeej75T5WxV3zRar0B^;Fjv>iG7J zt`-Rv4?n(WWbAyjs95xUYH^t@;c@$-!j~^J{34ZVP1jQjJp*1%dpTqqO}W0s_x?KK z+LYZQQz#HB+HSv3F0sTXL$S8vtplG00o}=-gqvgM4~-5#o`^T%$G?lpm(eb1qJBaV zm`50eA`^4ye?Llg*7yl&zDvnDavB4|eED#p3+nRk#u+5jZqsN=>X4uh+-V;vubQqg z(q{^j^BEVA@@$tn9_95;>UElMyEQ98VZF(s^U3NCP03X`?pd;M-5J{BBr~1UQf(v+ z&;7qodFaYL67aUE(Df@I8c|bx7uB&OA6bzp`Hf+J1oC$D7>kZJHC=Y)?-qY(F_`+TnPPjhrN=lhdU{D_y7TvwoXMwEDU&R>5m zKf^qCFZabaHsPx`AL}|~KAu2&kTQQoMa%nh>SZoT?K5;*Uaz`y^3xZ6CC;Ap^Q?us zEK~jOJcS8YGx?9eQ}p^BKJh;`j_pmJRQlrjf`od(pKX28j5?;ke&6Xqi%nnQkMA}a z+q{iLFw|4eX(RZuBFmPe$y-VUt?M3r`(BGQ5*NSK86BD^u2@m}kUum_T=QOO?sa=j zidk;YwTctVC*+INACwGDtCAo5=)zedUeb|U7#iHtzH)MIojtNM`G#niNGR+8&1k#i^dOsZHq0}D8Wdem9cr(UWzDI2t^|L@j1SzYR|OC<4aC5 zokjMcE?xsBXL&0V==;XpiA&u^59|BBqdfYcIm*`#>2<_$H2X`^jnX2nFC+plWwS<= z6CPw;jUjIg=&)+6wYX|^g?YANqW{rTiqKK>lrMKmU)}IDBck{3&3r9C@tjmKC+eBf z*rOSXhw|ly@5x1*boh*(JS00smewsNqjbNsrXkl+dZ^KGN|z&1<4nXsT4@u){!F-w z#QsYcpTr>Xea=pO$WEP(I25XM^J3xf{`7l=2U{odgVDaU!!fk)*t%|V%o%rKTH9~t zZeDhDDkn+_?IH@FwEWcB{D54h$VA=gEHRA9?%-tVG+ac>JiCLI>1a@uWwK9_X0|K$ z(1=mtz$szEX)NGrr3T;O znIkpw0^EzhXFFhQLW@IWZpF?LhzQerrYlU(NWyohOH@sqX|HJc5; z?^~Q^{=*u@u{0JkeqxS@;00x8FZwD`5&B1o1#&|iC#nWE@g0Ok%kWDw@~cS65DGc9sEW)O3!VPld-RZHR)b6<(9`^ z7&ePMo1G@goSH;*lBx7rU6-s6#dKOl96!HNXefvOHovu3IJdACdg4H!qPOXO7I@By zx_u{4C$~0NmU&I0y;IUxgqAvm4+zRnULIxgAF#>pb%Mt$N~k0~)*P}NHRj%zn;^x* zlc6)&pG2hgnyh7>G3h>eOOc4ko05b&U6z`P_2pa%r%1_2E{PB9udd|DPEweq1rZ3Y z3D#Um9H{B%AG6ia4I!ca&>tDy?9C^on$3G6t<&txE!pn$m03ae2bX?q*67NJ+S*5b zm}WTm&CJcdSt^ha z^S)XIUwI-&NnIyzu*lWC`jER{PbHjHd)NEK{OiKG3y)5>pkDj#&rnryOX?1^p;)_1 zs$+9!C}<#98IMs`_hw|hE_=L6(z;WTTUeI7#o15fCFLyn7cr?`spG;^VyuU)dnkOp z1<;p@awpemEYC|+5FyNrjE0J>2k7n z({!}&rep8T>W38~UaXulTIXu%JJQqYC^Mq|veGxXC+WD|bF%Z@t&IF)ZlhPZLLIgO zy3xpvyHS}zZ)2vfafI>t`lI)v$C(xKvvGH4ZGb2Ai2pLBJMFr9c8B+bdr zusMtlbn-JAAv0CK|FO#2cf?73`W^|XufLOu$CS$=8S~N_(f%Kzw=_m&0}pYH_-iiu zcPfpH1}sjTMDWJf?avd*c**3%a#QM_lVI>WFYEV~Cr*bjYS?AFmZ{EEi-}%8ZK<9R z10(-zN~G6B>As?#LOgTWOsenGE#1fBAMhMxN?+-3(VanzE$eoPl%NOnHq4F;zET*k z8p0z|5O3ylS(J06InD80g}yZZTV7^Rl~K#WJ?1J7HQkXU&*{9-mm|y5x!y@%sq(1; zyoZ>@#*bfO>t6i!eWrJ|Z$4v;!q2hI;6Qg{#6xL8i}ST8>6wvS#sQCvwXS0g;tj?e zxdc5+L*LdJ*k>6&K8qPvLgX|m9dexr6BBy2+{>>=u<*gi>@riTZp$}nWNuHlkKPtj z?4#BKm%FnI``S9cUrb3OYw2jC<`Y;y+Ds{FuQBP?bAbkR?7+D_tb0Uo9Q=Te2LGbswR-&{L^5AUo`-T`o*0e?Hn>nDF%*^1e?5P6w)gbcCwbIPi5% z<#sM)m47WQ=k(VZ7@|=Z=7f8V1J9^R*#iAnsfwyasbctAoBV}NSs89mcR{@e@6N7uF|F+ zgN~1E%mftX$Q9U&tOtUxy3Z|-m)TM4uqfDehMfMO_wWn4B6@uOvBs?q*d1%*8r#{q zmD#{eq?w>qJ?U|Y&^PA>kZ4VsmZKhou^8e=@5T%iTU3Q^l|n1!qOb60O%0*-)dSvF z`TSRTwS>1=2*mvBx5j4e#g0WcTU&3hNi=X-2l$8>nZpD2TRo!E-n=Ioa^_}K{$sc~ zJqPc~P@tsSrTnZjD{l_0A0rkLJ~8j(i+sElHqETdcTaB+&-cYqxP8AoTQToNJBAEW zwaRZq-t9upHcqjH^8Nnz1$*Ic^NIueiz~(_RXt{lnI%UlulHH|$KhE?B}dHH4AB#b zizY~V3+vZ}FzM@4Bv$qer_SDQG`=A3oU2rUkvP+%n7{f}@_0O9s(`YiYPlCx{>br$ z$CE6n=z~NDy{E)V@RtSeTQlylB0SHt2r4=M4OI!HMXb=-k#9r0ZY zui*4-qyNB=z-iCjRWO}ldbcRh$%??GyskI5N>g{G=-PVPv(AJp=l-o6-Wy+5r}O$< zR*Lyfc#524-n!x#o~!L`=`4F+c4m2Q<68e>SZriv!e^9WR?kbWt);cJ8|eevlrlDX z+mTHzCKuOWk6sUd7Frff6}kS;yvQ>L|H#{sXH`vi^sFaWyA;ob^pynT z{0GA#HkU5^_&#&o`$f$$#dcGni&t%eBwe(S6CRCi?ctAZUncUF_f4Y_4{WFLOX3#3 z98s@MGGXAWz43{XO3suU zWaK>1Zm)Rr{3=yPg?x51P5=FO0VJyor_1@`bdO!nsQfTl_U-o4L$U(>HFj42xqU%J z-`hRS=`AqGsI}pQHj#r)Ner10iVR|@lM+?2pPYGR9+dLz^K5H1r<(IJQ-yT}=hns_ zObxq#l$EGl5B2H%NtVzs%rRL+=0lUL&Cqx}PE@+?GdmB)ntO5a>T!S=WR(-^4X6VvqR{65r z_eW5s6@6Zjr8W!En0`R4k$a^-dAp?qy$^Uqf;(2-xn|asQqv?^AK?YI<(PTHOSkLG3KLRnhLDRB}(OV(W&N(g+sG~ z4F|`lbeLQ7`0hp?FjWBQBXh5q&3!NUA8?& zClHM!k&I0VKhDthDdEcj*yWpCVSWpDMLV)AixsT4M~DMy+sA6Sm$@rkS>1>Y^4d|c zZt9MC4;P1*d(swTB)h(rJ$!Qev-jcYr$0uXtyS5+F4iLLljLhzp-k{c)RwwkzS_~F zHK|e*dZeNf{rDW|RhL?C$wJHS%;4zfX7>nnUfjK$ES@ZS0dtTdW8G%uTrqjbJZ~cc z{meCNjzsXvc!2-sGmhs5B6(x{L@#KZzHXKKMOUhc43#9BDtzX4Qivvzed}$PB%xEo z7s^G6#rercp1luNRO`_1zRY7S!*4eFR=P)FzksjpJ^Otkm}i_ys+2T)b|BL?m1q1B}?gIUvE@e?v0=M}|_waGtH)UyuA8tY4QyZqT+C*VoD z7O$fd|CohB>+od9r<@nOch(|apCV(K5jNLs430sT%QLVJB|NoEoVjOtS4cWu$nkJ# z#hsTXA;J77>jp$K-Un(Pm@RPm5FMmL7`#?x#L%EYc1=*$t;MD14I(rn`CQFdPjb;A zCBEtpWM5nlFF(9`ACEv-`4JwCBnfwP&QZTk&6Jjums?VWMCxbL*%Mo=gPqTec6x|q zF+EOok+uDLDP$u5BZXQD^@Rwf0==WebCC@#vfdGwGsRbh8KyT-rj-j*jT#(ErFfi6 zw=cF@J!AQ?bdgv6u*70w;Olm?Lho`tqy6PoB?Jzf%9b8@lBJfXZKdiHBw%9>L{y!8 z^?CC;KbD45A}uT*Ul8&Uxs~vI?TBD?sJpbG&^4FZruX(Tp76@tfhh&Uwh!!NgYHi- zm*5_v9AZIt=Cuv4x;IW_S=%tB$Q8%eg8!_T2ha>I1I%at7i+qZg5Rp;*r`2BB5b6>b(iu6j zYj0$q{X9aTWk{*#)Sq-@>YAVxyeba)#amHL-*Q6n)^NA^8_h}7R7@P@G)2{_dB{1x zHU!G!D3w;y3-N577cGk~>r1KU+u@rltGA_4HDRG{(Snr;-ghS5rDV?8JmH&0)9TT0^>v8b+l@jC%5sJFjgDR_b>sAjE5qM|i9f0coY=2LHKC9)ct6O% zLOGf3#oy(idap=tQh#9^F0Ey5A~}7mQRr>+^U2PRHgCoh zk=09WPi?6p$qM~81m}8^a$mgsYq*NZ+s(Y1DBv2OU8}N2FJ0vuAVoM#d=I=3pw9cTWKqENmMfdn`opJ z{cuz58L7&6b~XIZODDFCTayQoX3mzp9F31LzEcpD9ih?H*5VM`S7Myb#gN`Ulzgh%1E29R;B;i*lcDb-cZ&IBkA0kFp8S^sw&;blmvH9_#JSurf10dRi>DL;Op8C%Sn0lSL|@(|*o*(#EEo zmHTqasSn5)Vd2b};=aSgjSj8^r_S*_EO*=P_N!MJpV0P+Oz-1f{P5Lq@I^|Ubs$m5 zaFIz}$I;UM?wkD8{wgLerdMKVJMl^~%g8wb*YQ=)3FeL(Oneee80ES2DBWihL44;C z-usG6^7~5tyG+i7SA?>dn;lws*XES)Cd2KuW?_%i`~A6fCrZvw=!)LtRia^15T-n{ zrD|mjN)6FnU@x-VY4nulJmr?E?t3}33?Mt!JE8ROFdK*BD^X# zqxb912GAU%8|m|YdzGf?E`vE&K@93Ga?+Abb(xb;H3!MUyv6shq`7}QU5D?~zH+Ne zE~n5PIb$*OBeRI*1ErJ1u}W|h@vzPpr&POdpp zA`Cal&2RQRgFKsLRBz=YC<%l6;CSo_W0q}V4~6^B51yU=hAEq<(+>W~ZKlSUS!lw= z;F_2aW}@#peO^Tp!O%xPr{TLjAINQ|eOSkaEIRp|VD!_FB-fBmLgowG?3ai<9Vb2PP~c$1&>>$tm=L9&C*%nOr3+M`CkhI_}zZ`V#dAN!b+Y^=G(HF5cnl zn&jf->lt8kwVCi=8Hc5`0hPX`TW|Me_0hzts-2Rvr#hu^D|a)@p;OL_@fD_8V762< zhH&~el^n%od#w}iP=I0GEE4Kr* zWij86{uo`hC*qoj^_J!>TjQ~qO?t7$b(!93sru8+gH9Cl>7UWlhw!S2dU|B;;w?3$ zGKHO#(frPwz0!Pud-yT)5B#BfR4-1wQTasV3vbJbxNW6Cd4O)fHzp|T=F;l9v;#+E zG!E7m$ms|SYFhSKg^}lpR_12D4<7Z@GhY$qsW(&)_`vfV1h`7*T4-1iW2b#iY-LAsg!?N{?w zO9y=6uzOzn@${}AK79Cj?UALGq@p0Zh#28324$w4ALQSRZJbL3T($*oIkE2ghuL@E zlnVH2Uho}J7!9`7c|i3vO{C>@-1li#!Dlwq>$iR4kp-6UTddJzoL!_Se<$_sI|*TC zrap}ktKx8zuI{oe6$U-?8K6oDAu)ehX8iV@4#)FZ+l9w64 z+MVC<;;rmcA+Dh#=k9(bYFU_hA-HBh^;!Sb@FRTI!oJ&ar#RXZsD0dyC@ycP9uW4d zkfq`5t6@?3o_@mIZFq2Xz|`4VCiE4{CnYxhMGAG%HbD&ME!VOS_g;YLqP5>>6W+bF)b-tq}ddr`_#^Ks=&N2rs9 z780ROFj9M3s%+qtN}wh8;rp5QTjbw2+-8!dlPA&) zQbC(@8m{l;nn(~I+D0xNT$j6uH+;jL)IX8+!90^q*0B$P9)2gut>OsIF*Y3V2!DWg zCpc!UgVpAAGqrextGb;1^mRg|>uhbBvP>acqWK-(w3eSX2MeOk-t^eGJNkw+gN?mz zBeb9_Dz}6GdZ>Ke)(^C1D%y}}HgQ?bCw@ES$4aLevi=9fb~ioOiA9k)JZ{a^w(0$D zhV3=XHDo%k&F8-q=XuYZUOQmMbLyfYCh;VU!r{b|+#-wo(wycDLyR2n9cH32G|ThK z;U9E&gdfTh9=k?X%#-Zus66q;tU~h#)7nxZtH_bt2l$iFHd(U~4DX52 zrcoBpS7@`|$mOF$XqaoVxMo(YWr;}?+V3;{Sb6YWnxG3f=GV$*W!V%My|vCireNW% z`Gjk1AZUGgSy?f=n|4H2{bTY1dl42$+>^k zReTYnw-Nm6-Q@6VrS~_=%D0KM)e>?;uV8d~I;fx44vBHsmBqe!rF;B(2p?fJy-DU{ z^1w;GQ6^2fBh!xLB^;MY!_pWyA{@FLVKuV%SOeN}PIc6lN3vfC)en|K-oH5~*)?YU zD5kzBOq6WBuJvS0Z5!L%FfC8Ua|-tPsKzJSS>BRPb%7g`)?Yg0$XInAg&$T&{?LsX zszO|IYZu77sbe3Q5~3;Ne8ws)*gPP!26abhzVeB0P;5uHn0iJoMcIOY3ys(8HlY&L zxMq+=d%NL9E%T~~YW&k(Yek{nbrTW?Yo6v-e4zcT+^;z*-Zyj+oz!$A zcsPcjp)1sB>bl}vUF~s`Vl+yeqHcBjK_o9-%35m#@LyxkGIFT*A@8R~xQ9))~| z;!6uHmytqUz4nw3iaNnprPgNNs-eBaCQw$i$bBDLEPSTS_H9sq^_?c2To>lnJT4+x zXn6d<2c_nKxJGg}+Yhq-t&SrfjvWp?X+7gZR`!-WrnI#FoLbaa?&kxw$d=Qc*ZhKX zh~IZC0{=t#2ZG5{e601FpNRBRjUP5vyxw=rI)u>O=1}nkv9tC;(%(&&CMx0ZA=Z6! z_!1SDUl3R4@p(5!=GGCd>;L%Px+O}}kbEGAa$K@ag1q>PlZ~SC0mA4|yl}7SlE$Bp}>3? z9*b1)@qM>Xof={!NER#_M7TMlo84Dv8&f|;SokhSYJY0j7`-lh@+mQ!d3|$uc2TH- z(WLfSmT-C4IV%#+t^R?8JKa65I)bklr?(0Ui(+RoA78JaxK3?n_CfnxAWvxLZCTwf z5oC5RmU{-3n!dSxK9u&kAhVRvi)+sPuFVN;Y2~##M23iT{n}7qGA)s^KuZ%fZL@|` zvb(yW`bg-*q&yNC;a&sU1xc23f?>r)aUEsS+8h^Nt^|%8g&pq~;!n)TyC#nD_8K}W zXumzi64>IF{J}BywOH2l1$PA@^Ury68P*%9LhIXe0@GKUULE6~P$uQox|vwO2yKR^&smt8ans) z1nT`gD)xXb&nBg&nB(&tuX;~x<{(Pt z9_d$J#6x5VZeO?*bgwRw=iBTWgHrY!-h~C^;ERjf$0ai64P}p~l(H*4kN#|Ta&YbT zifv5DB(?iu`=FWHK-`SQ=+*2F*m2^dC-jMzEB%M_v<-}qLEa8ULBVQ+Cw%VZzP=SO z!O!G!{9^HsYk^yc6ATm779;L+r$`fHxE;7gGtAnJ=$S?s8Pi4GKD#`uFiL{UG5C5L z5S(LHc3Pv>KFe4xBj+BC9w{#(TwS*ApNhS8hWwDAlV3+sOuBeOW-IDrP4S{6-o(hE z?mF4Tfuq|@wLP)Yv-`$A=1kWI$;?~g@#!98I&K>OnmS~*{A9_Udj#XGHXCb~3GlX< zG@mRGR`3|R$r25GMpnj=NWGe1R%0EG z`*nAxo};)cX)7|LuO5b9BwV5UUrvwcKgbV^C{+KMoB)GcjAKdOgD}x*iJsc zyrbrHDSMSaCm8shQdamI*CwSwsvc4%Cf4edA?l=L4`)N@yQ*{UKe`TJO!51*gVHjm z^Ss$pzG+MzITIkYz^n80;cy; z===uoUX!e=9|?&f(G43?_RCX`9=V#QhgF>xTH4Jq9PHt&yB8wb0 z*FG2d>FEChVnCh0)ypS;|C9fV`}^lKAUX?RfH)8|ZwFjCtT`N=<^>wq9AwtP!&T4? z4^Zvp7zYD)(cf`&_T*jc?ceC+sCyQ-OZS4Pu?Vv3PHD=UgSAo+d+PY2xGbrxSg2BRv-s>DjB`9l`v zkQ3D$mDSE~-p6nKIFKeja}`QNVRJB-Ov_tYnCNy2_Ekt*7=JZ1=fSLG{+89iZ-4!( zoKt6C0ji;5XNIisJ`S$U_B@?P&1|0zZ{P@PJYm0FCRCgdiHKd5Zh)QIjyG=!=Cx%L z{FJMC{GoqRO$_DP1*$?DJ3YKl-`kg=7OCGk-4#v)jn3M32cvE`rriim`DKYeT~g)C85w(=rkJNLv?;Ah1UP zHm~3Hv9nq7avt5XN$N8x$mWVFGESp7kuR1J;ga;R;?xF89<%nSeQ8`>+)w%8-~I=5 z`Ed37-~JXKdA~|U5TQEYGwqmxPRmfFv&V1g{9tf&V9s5^BmbIQS$!5E)hAJvQyI^( zpx{{*=#Mfg8SGTz>ZM#ZUh`Xm-(5ZDzJ~~H)g~rL+sP=HCKkpaV>3;m<|2;eHPHx-z$w&rw_Ai%e=)?(nMM`^l1qyNX?Ir`& z+0(#uQ?Sc6G>uvd*rS2sa^D9zw8=YW&PU32(N5XHO^X4|;xy~#vQ6dg`ZDrjK0mc8t55a3D4RH$qI}fHV&odK>HJoX zt%Q*F4}bKdtAG8k-2Istr%`0mzEGtlzUf3Z?cIjHIzaLL9Z6#%{P5MFr`(5gtf5t4 zbI5o6{QAj1?m>9frBKCTzK`z{H!{#zBqi}O_O4p1zdR~SCbdXVYOap7WquXTmqnN& zaK1r0N4jm}tXC}m2P8Og7>Maq3pbPmNV`WRAvq8TyN<9uZ8SSVnipo_gH!KDnYM*g z0(1E<4G4#J=tA2^dH>Uo|Idu<_w#&pv7dh1*{cKc;e^h{8cze)$k`WB4L%-h%OII0 zKMqgFq@LNN_PX$pUT)>31Bvs+mtUsy=NFd723cB$Rrzh_lZyB*@ifKGcrlAURv1!` zSc3BUt4+1Z+FaOkKFfLP(jg+t37>Lkjn5KzWe^)^XXD1BgTP+BiC^U7-}uCQ`~*U2 zw(M_5zGvBQip~s>VG-M>dT5lM_xk&!-+iv5kMe+tGtj_eZ%6y2NI_0XFR4CmT?f?i zckun;kN$2}8W)`S=Yp;y;&ZD4HT*p|$V+oYvfX@QES1v|C(@RCX6pAW0qG01*Ghes z1KbQ0oxmU)`@1o#4c7KycnCHU$d->Wq>a4OhGiAYi~wGMT(-J0*py{FLEyP;U?pS% zMML{>2KO()MY{&@e7Lj=E^|A245aeX8MfjDe(0u+{L)^yGz)tyk0evR!sf;z^m@xI z`?G)e3GayG=gG2Z(%+5C3~xX~D6=@T-xC>SkhDw0Iwk=Il9z6@nM;0EXz+xZF+r+v zVuuo9l zp&b{k@T2m3~UIttS)+M>*v_n_nt%bzZS6_Vj z#7HHkbCC_jWtKo1rJwOy=)R*|Q4g3`3h;j?iRuT>MqcyUGrS zK|}aqky|8x`qLj@z4_K3C6$k(aW^?*LwGpKht^?Oips!W9&QOu0I^S8i-YC)jCZWd zw@!rXI)3E#P`Omhc8}&lC3#EgEVVA_oP*gLIwb}lVIT0hbuFj?z%^3UK$xJ0WrSnAM1sU z_zctvpbR5t42D94dFzFoKsqXY$UT4zV~d|VwA(ausM2Q8o_p!|OL?jiY>x1w@drz17A@;!UTa}_k@kz5A@ zTb8E*CVEiGnCD{vT==9hW$TVMVFC7U)8@6W8&kH3ZKu{QzRy6Jlun%`mKk}=18;rc zZ0Aq9(z*AwW2^++voN`Hea{A4ug+ZkNVPkwURJv=c(KdE zk|})-c_;}w(NDIt-F^LoABaCfDAO#%7qRqt*WDkle*Noz3+PlOZ^uZ0M8M&N5(aTe zgxw*K&pa=FTS6vF!R9XCw&yZmEkubL_~`81(WLWD@|GxHzx?@6bDf{qg^zD2;`s$G z$=SWVROKdC_VIzw+&-IMm8pY3I)S4!=^4kpw?07&;9=ZAAQf_0r}Ga{%~o6jSYgUR zlnx@7vH|glfOTNZa-UmzUtIHRFmu1UIJQY5xK>Px?YDpoO+9YgS>B2wqW~hXvohX4zZ`B5W@33CAH@#g9BP?i(|JYaKZ&M`Ek@Ug+1*4RNHkEM`Qa5RC82vllfo|H zg%2zc$}0WD*$KyAze8?edNpmT@=(r~`p?W-tZb(*+i=9P>sbDN09=Oj;*cjLfb~av7`u)}7(C z0TulO#P-4}?4N*7sftT`1DW?T=-n~=Oc9Y-HOdD7_l$V6%>YY^qMf=0C zu?etOas1i4)lZtrsGfaZM*SsQ`nK%~BXBDR$LMyEs5BgGV?*03Q|NIzBFzymvWveH zLz%YS6~K4jjV@JILh)&*HKis|$BOAv6@t!2&NdNDD1Q{gpCuyN1dTK`k}t#XeR+1b z8Iu#rcAyL-f=gnOBksa)U5?P+C86a~Hv_z<1+ZQo#=HTR!GU&@Gvz=3^5;0p=NO!^ zQVs4S_euAieb#R>NE`%G2eU`6-#7@EiI$cQEIWKP&lEcA?nBQA~%i3>gtJi8U4q*(4jMaF?MdMuyyUVzu^ z*S+Hxs5TvVtY!`q(#W#_Msxt^fca07*na zRGk4A9QP11KvC__a%F?oo)ea?1YYp~nH`LZ5!E;sr!m)h#k=Ol0DUC7Fe1ph1L%Pq z0kmg+RF`8;jZ&+c#N;zzt1g-J5>p^&D<_JSo88$eb`B!-1(#A38dBBGDkg%M$}cB6fZb?kQSprDX~Y6!K4crsXd8JT8n|J$6o=(iGxk ztJy*H)OvvhD#48`gC8n77Wgi|6{&*dz6%=Ov5VU)0YaCLG?l4NUf#44QI3%wwTr77 zS3SZTF#C%@Vu+jI)Sx6qlBVx{bmQ!(Oj~7Cg6Jb~@~gk~=1Z%M((4RaXc5wKe7V|i zCEzvuwNw15yx1z|-N=;5CY`r5gbh5hr&C7<>$Hjb+VPDuS+zbn%F^A*%iF;1%s$*! zNWleKn^fR*u7R*dRX@?ml(k9w@*><2G+cA~%==>bmT2v88Em@} zuzc)RLoNf~nXZN@Yn6#@(~;CEK^>tZo1pq8K6Q|w-KSEWj9&Vnk-`gPntaqbc((o4y;*F zjCE76OCoar&Q;11IO&>Ss{&qc9!wSRoZj*JVIO%+T4OH}!G`~C#(0I{7d*}P(TycKzngV-ev`)p5irb;1d}6!VBGRtkQ|*g zw)5LTHxQp%U=VT`2IMt``0-Ung?7m3C4nU(+z6%~cFZD$V8FyNz~0gMbH4)l3juHv z{9;dM(#F0#QsyDr(;R6Z{e-ssNqY?=OW=?4h$5Lyk~?^BJpiqrHg$^R6=56K^U@pc zXH`Ra2YxRNI>3t?|A6CT*qzCp(e2C*2o=Iktm!aj=qzDjveDaG0n!wjmWGZU-@7oC z%?>OrPqRL9YLH4Btn_HwJVk5b*W{2FT8eT`cbwW3M*C{7OCK4EkhHd#TPKZ;kL4lj zzzQ!opB2lzgO;#<(tZ#Vm}Hr(ZBA-0x!Ei&(hGp?t`bHwg&}DYKr5Aq;&!UC{*mn5( z>O7N$F7^7seE4}J*)h_oBthc_UmB6Gl%dJr#({0yV!rhnW@2Tq;>Q5c;!Q>|suh!l z=e9WmzrSWvkQuX=1nb`muf~DoD`&JBV$qU%VOrJG4?5?7dD&5Y^CFwXTu&d2yC(0Q z_bx>4Rp2z}@Xu#{%H@a9tX%>Z+ZSbR<$%_=^URL$-5cP$FrsLOXv70E4 zxc4CMZHp%vj?E+<+y~P`I>%>uG^EjbK3_$HO!#^UpZv z_ZHAxk)XA=dkSa1M+3&cO$icTj^JPb^W(|~^bH7u=h~n6BFW>dQ!D*e?w+qTh?G{E zI+O!QF|bGe(mL|g@c{^oZC=*0gXjMFoQ8p=s7fhBlm*PT3xZ1H)3xDCz zocnB@*v_!F856mJuylgJ18BUu&zqL2T&ehNAHESOq9~*v3JRVjX*4~m#YuNuP>&|@ zds*%>Vp%7hT*ec~bU2XUH}IrCJPII4ot;j$@OdZxC+_S2@zwndOveZ?h}-$g#+h!W zWhy$35CER|QH!ohhExxVMkg_;OJBbQEr_ki(hYVzOfLV{v|c<@JB~qA zS{{{G+jzI+-e4qrX&W>KT5%GXAzRvjrQKl6l@Ct|NYXT@{dh^L4|##3F8LFWyq&Jq z!8^WaltbmNFD;w)Xr?wO4eP~{Zn@ZQ{>Mr!RuzeF2{dQu_1<;>2r< zPmSmO#6FrC!xBtM8L92)z}vTEW(S>PW+~q$w022Mc9hL+kLk?Lvc+*E*5FcK6#4jA z?Hz(O_2@bOKmPP5EU_5HsLIkF0E1s6nG7B`HcYvZgld06i5-H_ceYhjp|9uEK?5$n zPSi>sb+ug|>IjEzRXTS_UVCK&@X9Cmb!65Y&^?a9;%gaBL-fg4hz3tuE@i6E zDcBcoXsx~}+D6*C+=uY#WnTgkUeLkZ5lj7%)k}J@UJNs~wJrjLSo9Rfhso^lHi_#U zmKGlNO;=|{4>_C3N)_0>XJF~4(-W`u6Jw*$9%nFM(#mNV{Ecg0m*>qwCgq4JJyQ16 zEV`G-u-L0!LbPH1*7%GyBo|uJ>?!rar-d(92kxbm?@^1Xtltgvi%`hpC~3g%2q(ig z-1EGnjOS-wmU3BXkV@T-rxL|c7)M#mv#^100H#ANEWF}Oj1;;UuIEGGlJ_IL5fIuO z4dkEE`R&a1$0YiR8+<1Fn4QedZ`W0RZ=3X91e0~WFJX{l%OzC9M#ezsT?0kk?d|^Jb zI>(;QTx0;FbShx|h+Ip11Rtr@SvmUQaHQx}vZ+Ii(6HXTUt#e%GUdo@>)^{f(9`CU z*%w;|Kh%5Llf41y+mUs204`7!uI11)hU}EYj5aflA{_a|cw`;?k!6UMD@Z<%k{?Tr zQqn+{x!)*h^=3fb!C-f#z&g z9QZiU z1CTH*sI*p9B}mh@SEPgH+9i4(z~X>#D!VBtOar+q!}1Vlh@zvZgMljpnev%~ZR+mh z+NzStjTtu+R)DAjRzF-)k<}@klm@Rhxj~pXh0yM|=xbq=U3mYL4bSEy&G4mci>Cvo z`nWh7`?ky3qp2|FTC;;@`p&5?NI>PZF$JWFp`<1@e@m33_{!`39==KHXaiP^{xC>! z#m^H@-U_=kth3pl$LgHxfB8Op0&o;FrL;8o1_pag%O3e4j|@O2f7|&b#vLWHIPz zmJ}eV$^{moVj~tB22>rja2yR*=>%9>Rtd+oXa?Bhw1NifvTN_?9hjwa;TLY92aLx_ zF}XDOYk=x-S*xqAw^P_*$y3On?9l?qXIcUXAH|Zfa7tlz!ijEzgD0(;y$Iq9p92t{ z8|8lp&tcoKdLCo_j#sw1Pjj)S_UWmllT4Y6ARxz%Fv)eJwLIu&eyWS`t5{DqVWL?_tCWX?Yvx+I`FyS=8~kmaiI+ ziI(JD1wV3|`#Fhk%rvIjQtk0KJa!Ve=;crNy-s~}7X>&hWTphNDMfImICX&&1R+wT zm-%G2Z!5+M1cx8NDUlK^M;p{ z2y9+UrE`W-ztSPoVQG(SwWqDZHb{#L=X4H(M@L5v;0y}sY!L2wr#!98Kyji~&Zln` zzOW5WT4M;lrY*ACYW@0j@%CsFf19cSjY}h~?R?7Ztl6DxePPmed;_n3?vpI5*pa$D z{mH2uG(` zGD7ZTHSD0XdI^RM(QV_Sv1m2_hn|u;^KV`i1~Aa2CB3Y0BtV)&@=lgHjkj_dZbD=r z^GYG|njrEA5Y?irKuxwMI5A!oGN1*vrOBn-pG)hUFo4No==7y&=l?n9{XWcq{nK$n z%MBhf*7>ooUyeH73T0yhkaKM5x$J4jsH0r2t@6sApg|6C^o^^^z?_6vvk(SXnm+rj zzV+qFX(3`T3tmCgPlx&OZMUW_UFBn&;_ycxce9I1XBcUbJZwIYtp*SJ$YW=g_EZPQ z0WY0}<0kn?Ot`?4PX`fTcx}5?hhrvRkPu1Rq9{{=8z)cj#N8yS32^yu-eV>O8q_nZ zLjqO6oxP(!ZOb$D>Ky&#FHycv`bt+Dh*N&;DWaW>hmEFkAp&3gqppt#by z46rg*0wcMn7S%21*~E(JmKCTv7&{)9j#wsfm2+Xl753u8whLX@+DhEQDuMbs8L6kX zD|8yf(q0sfA9}R`J-C0NJLfL%-|}Oa3Ie?U5yC)o@f(xW5eKyFZ>O&yUSxY{k+B3(f;O!dWq2aa~y&WBu5$c8r8rE{SFg>%oPCw z!2!k-1%DZ4rD|s`m3Alr_y?dtsR|emx-HxJM*yhAROyJKVRR{Le1Qx`2LbZR=9Lv4 z<9WY)6D$Fj3TY36-`i+A;6Ma`I#JS^a^>)RRsXp0C7*~u5F`MAOXm@LxV%0-xHnFjnIilht0mi2FRqvaUjJ#U#5*(j>B$#}0l#8o$=gIT)u5L#Ku2j&diWgA^DL&qbz|t*|b1I6g^f zcv90sh+Onru*f_;xHDCK+^Yed;UXdWL|p7pso_sx94jaY#zqnbP`kBrmZv&m$;gsC z?fPSN$b?7CY>u00)S$13OSvt+gvs&>c2;cI+^ld%qu*Lj`3oz&*m1xIV~<;Y8UlUel?Mhg8F*Iaele9^4GY)+cbF9R7E`(q3;Gby;qvb*thg@I=PBPfg$ zC|M3yc%ryiw0pmLMFuvM`sK_W%1Cq=n7p&SPiy(!eg|fX^naHMv-9tO0B)1@raIjL zz#saMU+)EQ)zbZe3;^78!l;WKg1o-W&v8qhl-Zx7*OvPp!X1<} zxIt6xYKyfU2;O`GUu%2?`0+L(WOX-xgHmH$=h|&MEaugW;C5GE$1QF1i>F*^gMQ@a zkU`EUsu-9{8@S{wZE1EeaJA4^zKh94v{~MMuPDsTkTP`kKVn_X0X;&BJs1EHg z(#+G5W5+BK$Va+SbXUV!Z5lKyyTXyOYD!Mp^|eJ=?<_7wRagItltTK|CpV)HTLv;P z`f`wBwdAR}#GqW1rpVf0ByU5$xiN?{MVc`8h4hI@AMU6SNZ zyy?!uk$m6TK1Qk%F(SBjBDWd9l^qRP%{LlaXCn)OET2zwuh9CD6 zeB+FRffK~BkC1@oeE)v$5oqV6I-Nm@*BJ)F-3y-Ub|%&=1eYDgy94dW{(F;CJ61bQ z%m~aQ1amW!LMgT92ofjx+`VfVt)j6FFlTz<4W{ootun1M(w-VC8R+cs+2K_x2ckfy zuTGMUoR7C1G0tw3f6&L(3_{S%b(1kSwaLfMkt&G!k`N8b9aU#xWI+<=#aWgHX= zp$5OKjE1kUyV8jdYzNW5FbQVkmu#L3aNtZ*2kLxD}0n02(Jk6!VvbP zZX|fzAr$OL5kmWW0Vy;x;W$gBU;SF4)DWTNA470N}Xe?`y}L`Ar(Z z8Q%cc9WDUqE(v;Lfs7TLGjY8(Io?9lh6K1({#cbh@EQ5OLtY2-u04&ijF7Rjg2AbJ z)lW6Tc=&`xzEpThh#;I}fKepWZ=mB3U|+5{FS6#gX0Z9Y*e8_B}ApURXPzTot^0IePQ zg)6T^pYShX?GkPhpA;L?5nuX(OX`hNnVavd4!!tl?BvQ)Uow%b=nj6tgXZ$xO+Xdb z9wysNzA(gPet1|n^=Xvvl%p!Ny?M!wclZj%OEaB6g5W~}Ne!zl_vW+5VX6=1-L}|} zQfK<%cQHw5ADy52`qvmC5AqXnr3pj(#EC?3#EabMf9K}ZK4zQFrcu%<6bn=gdk}DK zI;H*P)T|B+Tss?oF;WmZ8z;M(K{x%_9$VX|CwS*o-aDZ3=O#Np{4?V_AlSZj=DpNs zV3Y5vz+5TRDJM3-xjpdwd7lLIMUS^WwR8wVmZY;bAuhpolqgh&c#^fD4+m|hK7zxb z#OONvEk8cG?^v+i0U`S%w0X<3PIHx|3<(D6d_Fwap5xGgNnRa=Lqp1$`N!kUm#5;& zOaAE~tWd&-n)s9YHku(pD`{}_SA5pmY4dQdH9vUbQ&f}&Ic1x+%t$w4Mh+=0sgzBlP5GkvKNk#gqp%kaq zZrAf}yi&MfE|#Nm-lS*GD&5FOctsVOAT{xt)a_)Wq48^EL!scf{x^DZ^#CwgA$5 z!*8{jHeft8a^`O5^)5hPJx0*jL0}cTIJi-8wGyYnP}?1=C+OJ$cJLL}Al5gjvkl>- zmEfzOh}BaQgUXw1lmUqFBJ=;7lzrtdJq0zG#nu{pb|>As(7ljT2kVs6q^zYUt}>j` z%9>-NZK3=z8c!2*zCS4DdG;-`NGpTg_{@CnI~u-`1NgPc{`ibAzDuIl`H@kElPY_w zDk+U=x1?Ttr|cCkZx-V2PJHY{7(39e7MYloXKgd1)iYj{>XU<>nl+WKSlUQh4sJ~= zm~9Sxu67D9Jp(<%$-LhzBU16d*RU6E@~Kr%=vvvS5};{9pL>1I==(Q3AS*HMboLRfa+pGBQtz?Wgr7O@~#X* zI2ieNNyU`^S6_cEM4GgXwDgN9@`sD%3%{J^K(laXPR5^;Z}FwEDX|>;w?>HaasVWE zxMOC){Kx?U))yE@gMqZ+uCRw}%Y`bHii0@{9W1h8CbpR{tHFI@>YIIkxpW-$P-6KU zOrw|p?g=o`0T&og53DwkF;G-4ztYQ5DNM&~PEXUN%Y|O?a2L3E z%6gtzLR>?t$8<~w2eUiNX`46Se+!?v+A*ZGv&kzim7k!6LF7kJZP&z}f^TXBUdNg8 z+<Ib_2?pgjqkjLYJe1lk%r4QVuk@$B&Q>5Bcs2!@Q~T zjNH(WpEB*hDP3WLr=z6hM>}%V2HP2|KBwT$WOU7{Ld_dzd?TlHU9x(qSAXrq)d3P} zu+vvb6eBA*rZsK+mKtw};RT@#04s-jgz2=We(EIiw3(7J(hxXsl+l}o`V4_uPG69$ zaY;O=&qaSu1+qaW!%PiN_*=X39!D!RB&o}N^{b^u)z~;S@kQlUNZV^wzb#0I*x7T` z7>p`~{OAuk^=PS zAO4>hF|%Y_f|(_`py?fENaS4-rSr@nF_N_z5%Iy1++FV|3ZA>&$lyyz{Go@fSC)1n z&jDZNdi^yk0h5=ZG`x;YU$5O`NX&pZ!)$Fq(ZK)qyKl18mqwZrJy?-8UqRK!RT~EB zC>%iDeZKEvNQrjgI_xuOt!-AI9FcHfI*69% z4g&2mbog}su8c)9ZnVAnXLLwg<6k+mf|+2G<|dc*dwH?}-%g);Y+=f*ZxBwj?tX?p=eC!jc5tZE49yN$ zi<5e238#GxDvuj8k`8|=0|T6GU3oQMgdihxmGQgutZ&_NDCGGYNbp|2dQ~2Ceypuc z9VC>a<;AQV`qj8QcB zuF1BK#PrvH@fTDq5s!t)0G#S+TM((D&$$o6DkO~M3$3M8#NZ?={bDn5^ae+b5h66* z^6KO>5ei9vYTUsQm!Mso1XOjv5ECp$vk(O18}r8Q9{>O#07*naRCdR|`}Ui3E<69O z0EBqzKFZT>hmLQZpars{SV!mffsG6Rd|hOm6#%d*o~1BL$H>F1;Y=tF{n2lf<|nND z88HR`e{I$V2Lc30zxm;Qy` z5*rG}gEr2kYn=zFzDp6CF(`yrWUK$B;hsxZbO<^&Jjj~ax)oG4q-)c(Z8><>340(8 z9CcPJqdmw;rQJ+=6125Cl1W|+b$3$h&wJPB5)#@$IyM1cT|%#9(AP}iYWtQin}N0- z!di%4Rj&TK`;uP}Ss}ATBKQi~!|Lda;fNF=6vhVs5o&i;13SWrom*5Xrz+srSHJ$% zzvU;pGr9qDbbgFRw1%nQdm5gZI33CwI-_v--FrXm{7P#`_vnnQSbp1yB-wzcV)70G z%{y3Jvl__YrJN2bv^{#7Ku|v8#vvW2L35)8yl?3oeV4mh$puT`yzjpG_u7NH1T=V5 z$F}JlqH!I_Ky(1`*vF+yC$X$up>OBcE(V`>3Fe$P&c;*9$>)YQ+~mBHpwBNN*$YYQ zi|u54I+b&Oq&3*KE(m@ZSn~FdynjlrLJpqFVUlkXK`VS33MY7INNd67*k!bXN^P^v z8+Rg5aL^TI!0x_WGhW$ z!+|E{2&8o?Zvhri`Sp)G@4SVF*m2Mk_@EUn$KKHJew!DsxGKPJ40vF2O z;0R~1OnnRvozmBFI?}(Zt8c%*%>L>E6|7)x(hLNa{EasUrYOfDsj)tVHl?>w6UC2~ zI?tjqRv+DFMGr9!GAr2wtC+m~h#|$%H6tl?&$$0vuQ~|!&M&nPrvst`oxgtTW}K%( z%Ao$|!Oh!?o$+<{{-RP}eFlAIv`r35`AtCWO6ONTKiXh-@P$n)_!l95)%) zF-H&syGg@$fr=d9xUz9+>V}pBh=H}i<~{HZK(!;XV;5%;gHSnjg!B&kkT-bt2`?dB zWt3hq+KH=UaZ4k?GK>w#@>FX43(sO(b`Vng^|@tXRC@w7ZbvST$`F45U_QzGDrp5} z|Mq1e$|Y>z4>*ZYLtBzZ%EpR6HP+jNI|(_EW`Lu8`G^`Fiyh+9_YBLUX(;Eo`LNa<)a>Tne`%D!WU>%vd%k5NQKh!`gWy7^_8ycC5X> zTmjC^GNLi6g^_~r`Hq`oe*JI%+U46yHS7Hh2-F#3h?LEdfH?VMxJ|+thY7B!Y*_d1d|LLa~jHX=Ua5}4n8-Lb~WUAn=6Mq zmgKp@kWNwO_TeE0NNme^pN=nGJ7+G<(fPAH)OOJJ{6G5@?CQIDNLA`G7#lRgz`Ho` zP`dp4CBALsN$tESq@HruXaF0G4uDT*Y2gVB17Xto-r&foYgi2JSf%omI{0I=LX=1b zUbE62(#DALm~tACyu_(ZbzxVQ=rcOWR^P@B*x7g9zkAD_g5JePIkMy!@}akbTIdKO zE1SvZSkQbQI&YoulTM~7qWH=~yBck!@_n9i^@4Nx>;nPos#)7)X&)Yy#48A{;@%Te zJ2)7hOj8UbJU+~ykm>&p4CJLz8(DGU4Pf?owkjQ>lIep^2*k|tmxzDabUcUKX{^S3|rLexKH)wv@w;crNBqohxF6aK{_;;quY-iqq zigrJ-HJR0IkLs-9K2Jwy!}vsOEclQ+A1|1yr0jk&I3Q030zqgrXnm?sebmj4{N>kQ zW=4{}18E#9b4-!W4~$E>;6g*%4g#H}2?#qRFyi~O<=%(UD*<-0Y7sk<_1xFIG)$2d zM*dc#Hc}qt&1FGgcF#dV4iK&)90YtGB0&(J>mwNrGjbb%vPn>Tv=ET;mVYX?X!FRn zVB#e2S27-&X+V>guFcbKvvgMHv;*UVSEVHtK#^#Kl-~2{`R{+ ziQ$Aa6sF%vE2n88MB{~h#EIUC+pw}z1SCB56MwEL>+C}zGABX_{=pBQXa!C>QHd;q z1Z8R`K9NY>|MtKB&)q01I-e&;PF&r=)RMH{s_!k+(HyM=jAvWfNl_L;s;M(R=e1E{jL1p&} zPjiASnZ91_abSz1>ak z^Lltzp4)>srz&IdaQE;nmdvU;9pNaRahmVq^u9CDCoK1uZ~f3HIUs*er`x^iU(Ih{ zY#de!d*v74uv6SW!v=Z)D?>mMAtf&`o8ec!n&`Xw87p0ItDm; zS?xrx7o7ls$Hrk?pegF0udM3>X-s)(_e()7@fpM6H5i8PnbGv}8mv_k9QMG8YVD(g zbQqW)9G#^JR^1-^!|(scv!CJ+P5k=(1cGiYrnc|zGPCk}B0+UUV|JVGR}&5<2t+CY z7zhF~25GC^o-3YP+W^d1K3cX1h2=a-o`$0kj(~d97Uzt!$H5wP&v(lUaIbSPXwU6= z1n~1<2Ek@&np0{@hs(#0d%vABlKPEAX=~)2^_SYt*qy=c88cQ80t4Ekn{iGDhYd_I zdb51E9e%R{a4K9HJBD1ZzSB@z=vk$_&bf-)h&G_hE7)+&4w~L2L=!NTWNW~%o#~J3!iwe(*)K%^yj&4WA*bY zpRGWDuokRzhGNRMG+1sGrTC{H?=X+Nv6q!p$xA4oY}*{{$&911r#p;1gWqv$jFx$Il z9aDvxU8~DnB`5g~?mqeHdhb(RG6>H7EG#Zv@#U=**^xO0j7+GHivR` z=AfcKmDQzoCA0X5qvg}>@&>R16Ib3q(D6H@GN!f+&cQcZqPIVK=*r7>X2v#r&_T+0 zo3|RdF{tXI2COeH#qmid=V+TKB72M%Uf) z9oap&o*bL#Zv&RUW!gv#Iou*%frGEaqF96U*D_{LW6G-L^zy)`&FufrfB2^=HP##t zI$HqFd^z;Yv{P&ZfOJwS&A1s!%L0SM?dsIwvDen(Jc4cOz!*afCAI#g9t`FhJ1*0d z)cv33Kq`eB4)F@j3~Khxvl8e>M%$MYj1Zs!M-~kM2?S{N7g$}}K6RGfBK#VPpQ?6{ zNEt7(M6~tDkI_H-_!hI7<;@uQYSM<+6c{_ANpNvA%S@{;D}(G*D)WyS1D!Rv=qQh; z7{}_8G6z$T9o%G6+-L<9nAoOuz8JR^(^gOWTTo1qd^H|H(Zg5qe7OuvQTDf8x=g%3y`xglU&)!53xij#>M{^C-YG9!Dz^{As@!2|~<-Ve{!xasC z5S9jYyjx2M(KCs^2kQpJ|<9csCfpQ15BFx&T+ITjpH;Rgi@E? z0ouoUJMeU=Q4itbAaJr)fYayp$AYGO=WTe#_dNSB_LTk+zE|%cIK_`+F(MHjP6?d} z5RqoaVzn_aGj?YP&NU?U9It$+z$4Y6k!VN1{SVCAz+O52^3_kZMd4O4Xbz5{hsNjv zfw?tRnt^wq>dD6=fo_ckHme7#J}E444!;YDp8fF94tHnv>^MS)oqT38*oS8bCF6lZ zVxt|?$*cRc{HOq2K%>6{{WN_V?6ex|{W|$a&ut&5ycMhO3ch3e(GqMO^-QN>70HIv^)c3BeBz%SC&A>Ffdfd&Q-(+ zFlXNTupEm2c{xM!=q&&513js<@2-(mF2Nie@r?*P4suYhnAaFBO@I2~Z5}|;X&mx(S{BkL)k5&}V(F z^qPE&&?dn{R%BfVN*x#>`i^7zaaI8#ZRz9Mz?Z93T;xXnY3wynbB-a<%r?wQp-c7P zSiFL%$7Rim4#@C0UtVG56T?0H*KpG~fl}az(@~>bg_dVU6 z|DI>hKZk|uQH#$eRhv(iEF|N5no=(8)_YVr=K(*OwT*{NO*o@RedRsuWE+stwtu&Z zvBOORKR7!vl67R$G^~_A{48(72#9RAARyc;9sWj{sZUweeLzc~g2MUfQ`ihbz5f5# zU;mqDZ@*s$9}3acv-~XP1c6j3zsPB%M3MKzuld?_z{e>vQ8y4wbSU^b?0T;9d_ZTM zUXNg@^Uv&Kyd1((pTmzKLDr#U7PCY%+gJ%aXNU<*4x3)S39q_)B{7QbzsVu5Q$5!h zbp(;81!sv)0Svmf5kU0icYLd)R6j}Z!&_S6MVe({=oQ~8APp%4b}2u}m|;MGo$6e} z8$JHvfBsWiXU46L10dirG`<0glDkJFXZp@WvhbUD zE8x^O*tZXU(4zmn&IEDcj2zk3Xvio!q67~gn-+Xxb2(i7F4wP~f7!YKb+xoS=rQsa zSkfngt~~NSxb?unBk3dUbxQMo$Gi3SNoX97oU|_12PP|)moFdZ7kDz>+9V^0O#b38 z|0>&nzke_gG=ncF+0UX?)R$j8NR;e@kMPnDip75FYVIUO+2u{AAgdsw2;zom3~WH; zcQ#C86TvYnr`i*pQ=P6qkWkW{UXp&PGG|%IP%BGWvCe_S}?~4 zYS=ZrzNNA)-iOBG(IRoI{xO{3E{BAd{(=C5bx>HiG%_xF{3*`l+j7jT-OC3Tr`{lq z*BV@O1nWcQL#F}bL^5<=W~%9}(u?mP430hE^p$2FXWo?#Ic$JL2Yz;<9t!6t%Ib}Y zysQX}zh@w+57?7L4%aMuc&o4jrER1Wwx0Bw!jtv@>@dhn-c=&gP58dj0v1^9v{k;{ z8ROgRJrm06(XIA3Z`=~h?@1mW;oT>2FZ?ItxfbTTY5N!`Q`0fEJF&mGEczZdD}@x7XgnERX6VKjQ@lmnFVBeQW_- z>E0Kc^qGk>vh6?evCP-!tPDsGANrojp`Tcdk@q(u!l&VZ-!zgSN;ds4edTStfp52S z9eHl$5pRq4yo&1h5H_}^y%zX^i|*W3wgu|pVB{|dNVDb0Vc>6nn}N(kkjxX_3kWvt zUhXPwVn;hJqrP;%0Qxn5j6he=5NcL>_$4XPFL#W)Uee0H6GuZ8G$ z4)F1YzdqT;-z~d4nGTu+grtYAYB*gPq;6;Df`W-XWD>aGSLSWc+lyah`5qV_@@WE_ ztT9X84Jkg;ya3~&8;THdSDB2N@y(1HLk!~qXF3-lD0Ye%>il}OA8O-D|F!C;I*;f1t+l&wkPli zHh?=Bq`!k%c-Ce|k5(GAOGL~I?j9VkPW;bV8(x)*Y`i=2T8IWpk#FsL-O|h zbNY>+27t)M!z>#ah-x0GAb2=-~~&IG(Y9 z1QR;xR#{gM3C6+c%Q$@LXP5wXrIq(pQ7kurNN2dXLp;rwhf@W;6tO)?WWDmZ8UTL+ zs3_RdC*NECI5K%lo&});cS0m`ac6`qJ2*(n$8a_}bPM?egxl4fvTl`_a!fs|jnfI{ zGAJ`?`E|vhF%r-IDM?+bv@tDV=5PMy-*iTA<9VBJ8REh|S(wx}19%!#7}lm4eoA3B z~%}9;lHoIbc~`;(#PCS?=^3)b3kcE z0YFm4mdq&EOq)XZgNaP91ipNy2iYN>wAYHRyp7B>*QrrKC>58(o_xoUrmiG-5U~91 za(2gFPOFcCl)hZI1L0+j(;Q*k(#VHn&T_oVbTUg@`7)lqh@W%M?BC9(KRDxf?DZ#@ zz%6i%!Rmp|tOk_uj}QIT(-FNHXIBixYddmTL}9?My$Gh3&ad*tp_NIipHoi;^q3v0a2Em}TZ>b#MKgx;V3fKM&4eW{H`m!vFzoBIrqvx^1hl6~d%G-{*Jme)o_6z0LO} z|IGaH`#97t`#;YYH~gv8CG}uvoJfGIbBXLRrM9@*0xV~zvUTzvqUk5(lJDM1WyY3~ zCNtxxvBMdG^-DU)&AsWs5Cel{%$(tfWm?j`fhE_@%weKSvI24ZahB?;N*_gH;hwsL zx1A6O@}jsJLD_rEAR_iG_s3|!uTIK48u_QkH7qb@#nW`ij^imz&=|fr&Nyji%skt11NN#9$4x=?H&ZWOp6BiGS^ z72?X~(fY`NE1$Js*z%bf&;EZxr?L(#dGr^wId}WG26(z2#M?R7!+yk5pFnH;Wo&J? zuhD%F0S=%;l)kE@RaT;X{wUH5Lz`daQqJtVK0N!|zxnGr_&Wbwv-CeKx^&6y(VINo zML+YJJ)rg;8p#}Im*f1sS|NCa?U+q(_YFVO{w{{Z5OSHBFIUDDd#S*SByas$d?y|J>6up@10Tf@7y&^(%~dEcg{1vb&j~0y zBZKBh1?dt|4M%PNRzo&pd(@RgVTR5@aQJGlNMHO7Gee^Wox1WJSPst_b+ff2qgR4p z=&R$zYuxIVz9np(5{|&o3pni!qPf<&;Uw>j5lwwuXN8idJ`)7ULz?ZO+hx&jx^%`5 z9*GS7>3gbLpQ_UJ(3XQuayyck_V#5US20<^ZvGbU)r#D(`>0B}!RdyiW#c`67qAS$ z-I!E_bj~$SdvF=ArnYaNcrl!R{uh5$Tb$25U?B3z*c(*OHe%#R9&#Q$Uo2UJu^7?6-tx#Ek)M$6F7E)SX6GQC^TBM@zC~ zd#Kl8CEwJ#*{6yUor%-%CPs+1A~YCw^zo7e0r`h0{&sT@vbdIcaWr zk{2hPeWyolXT}~{t9LW`$Znk-3Hlgb=1l1^CV_bZ;|5ajcvSD@PJ@s78t<$qv_EWd z`k&c9^;UNh`X;Z+t2NV4-F@{^9jyY69#;N8bNma>$pk(mpQKOA8jB|lcV1Gs`Zdr6 zsy2PbjV0s!sgLsxs4*e96O39q@{LlJaoU}MunSv)ZX;qpuDul+n)TH zY86$`#ovlyqRvCHw~-&%Izy^=puW`b#~u;uX5p}nkAOJ~3K~!O+W=gY>`Yz3bg-Im`e=+T%nE?4MxS$cfR44pPdwx__x?=|4uRDTT|8PXOrV%${!A2Bjj^BL zkZ|#UJL634-ezRz90Xozxc2@?4Xi27lKvRcqxtH4t7?|a|8zoZg5&@Ojsm^3q#0ih zTGe!djcp0&zEPUY$W^MYOkU~ZFwiQ?TQTe1To-e&%XTX z%PRGbYxCT4L=i=v%D7iFeG0kCifmG&Y6Ls^nQd%Qu*yKCI-+ufQ6r6k7>jYb+WUKu zsoyV0VA|4;Q+Ejp2f5{18I-}`4qQJ&0H3yP&o#vBTqCoQ={gLV?kuzh-&rE~HZgLk zeR9QXQ!dp`%B@Qm<)-!@6Hr#%F%Cf9)6Z8{4PV2vJ08%3wR#vQA7?cFHBLP4?7ICg z8DFa!wveq_@AnI!WIfAPW10Z!Y+`sW&93Oc4VNzC{DKi@J{BPe1PBjvP|FT7rv;{G z0q6St`Lrc$#f+o(D>sl>zyctEs-;2U#bzG`}QJ`@Jh2PHHyE z_cq~$C!Hb9Wuo`=Q}yk)-#q)DzyDp))cF&cTm^QsKfiw=8k|YotRM1$v7~>It-zOE z`t3|$`7Mg;Ct7Bm5IA*NnsfP#a}CjzAqfI|cvKL^fcK;-K4noi(4BEC-7T;BL7t#Y z?42c(87`kZn|>W^^JY|u5y%l`9V(7cr^}f1S|_fk5SB6EwE!ZQYq-UO=|7-tUP zEm7vqw53_(co;yl!gXsh{R)Fij0U#G>8dSqC?A@Sp*&`F@*d-{C>pxtdki)`N8ZCP zTpUn;G)m{tHwO9~Lp;GNaH}`EhgV-qf7gX~)eZ+38Mwi;K6C}Aff1b2H$ld&YW({S z1~$|cT=-!_9e40)H-5OA&y_g+LenN~QpLRODMvD`4{-hoaEic-BNcV71Jz3#r@~m+ z(fvujaaMLU+cdPh1ut@)rB=qiv)zp!k?A^qZ|y}clUy;i2fDKzfP3K^^a9eA$0|c- zNas&!gf_Xncc6Mr%{>xo)c2^gD0_{z*jx zLzLeXMe3`>`BTj6C^415>fi!g3OoDk3c?J;72GcJ-GB`U5!Bo4YoCq}UZb3SF$MPU zt}-E#{2iVqvT7JZuehR7vw}k@eHm*m6)E|z)67WHHI3tNyUQW&dwpuxxtUKj%@Mk)dRxNlQ#}F@E8k9^H5GQ zJ&fxaL-Gs|+&Vb8Sy_>bF;4)3lg@B$oK^u~Ern<70t`b>aCA>RILkv|IFJto;oWMW z4=RxpnNdm=bMlI(oNQsP>%G2}1B*_DsV+(JNrmF3H1;o7dJ&}T%`bR;wOI}ud-$B^ zb08?w|_$CFAwpgUShm{(yJ_vz@eQ7&Mm z`S1Vzzs*;Et@qmR=dn=}e)s;n1xU3eK~k4=%_+yo()Z2hZUJc(~N6~rJbZS5;_Zq$v2?)cv$|S7^ewK=s3Rzxz%gUs zc;g^o<<*lzALzNt!~Z&IZ=srjZ>fiEDRWM`!ZSY5lH~P(o1>FCdY%wm*_ZFOodCx0 z^MWJYuRPqlqOvN`-%A=@&RGHbp>q#<^+6A=PC#|=Rj9*t=>w9wC4}qxJ&RB z{ag=BYmyH@`BMhJSA8DAZ|bkc0uB7ztaRRmw+1t~`}ef3$*eL=yD?sPLolU28oacl z_2`k^W2Oj8cHj8vs;?e##zuk-2P;s*5}lTCg_Kc^5e^fpOxMB%vwoc&vZvW(&);CZ zle~}cCDlFKATVUMPaTKPyEhha ze>DY9@^Hc5mB7h!4ya<9HO@n^0jjv+T09VYNxJDP1@Q*uo&6j6>nuaJfncCZUv`3m z8OH~0;()=|J#};;;dg0U6)YRtz}wpk1eIC(HhAMH9UFX>KK@caD>ddfW%%X*mM!Ey ze-3of>r$D{)89+qD?k)y=*RZ-+0_>~bo(p2|keu)Vi_R3q^1w|{*0hd=x-gYp5NBrCNz z|KsF;4g@u6=e}J5SPh(tI=Y5@(W7t3`7RF>F%#zig$H>5aganw0}#b(PA5{l%$Gxs zQ>HZy+NJp-O|(-pvS|JN?WLc0&f%X=UvWlHEp8&76}|}0+2anRcqIm^UR6U>1X!^~ zM)sHe{ecVm;7c3$X-h60Y77EwF6Y-SSs9ptr7rh_!`}Y0&jbm3{P`jk1koL%9o}HQ z;{rnj>O5^ZD~*Ky*vzg3AYstMCqoigNmEuJV~E3-@dyU*Kjb7m{BAHrqcRAbDZ#36 zRy0L%wH1-K8Gh^at?ajP5d}{H12B`o1SLVX35}ccZuiC2tw8!t0O!DgD6w2ZgKzc& zSH9aa4CUl2{5V%211b3PSN}Kx!WWPf#{lGg`iz=3O@hjVYV>+BMUk8ZCJ za{l~Fo(p$qwB=UER@lq?eE(iQC=|F+jFavVIek4HI8oTrF`QI+oMV=hWHPcrX%Z1p zisaqbgB`K8o)eCoN9GL!?WD)*K{okOM6ZgA2>!(hm$Fqr>)VHu30BjI9&6l@4Vj_S zJ|)B99>_WH?Ega}M6~G(Wb^uBhLeKDjXd0(kHI@&9!DJf`f68Sa>39UsskbC)1R}( zpgY5dPyI?Q3ghI;_y@z8>C4y!Be%-v+V*92)BfR2keb`6>0S)*OoC_W;Hksg#&aFI zWbm|MWO3@!(Ky&3oVGqbUR`5T4=lVE=0;?KMOqJXx|;{l!b@ySHTtd|mfBPuUi?gO z3FS7=jE`oem%38FE}g^*S)$X(I0s^sKb;Fz;mfV^!TrO3{fDFEIFrWX*S-DMGl0~k zf#245&HSo1U`?s--Xf#CetPz+U;XmgPv2A64KQup8sz#e%XQ}g1c~F}Tl`}hiGW1} zBU%n#GTdXJ4uu^4f$nK?4gr0q+VjINf!9z@q*(D-Zi<2AI7|NIEBq)%wTQw1s@R9* zx<^f&gTU26z(=X+2^Z)ZVBp}YVV7_bpEl^n!1PC}#*rRtggEKerdWb$apWqHjS;Cw zV+>LqqvycGgKoH(v=7{b+(k!A9W-#W3IHE|00sHZVBnxyXVvc3oV2S4y4%^EEa0_t z?{YqUjfaB_-We1Q_xuMeZ{bQNBU!Kn$u&v@q8wn(_`FGuscE|MY+VhdT4RscUcj zor)f+ajd@5c$lH^Nttn@q!S)mMb(_{hoLd{2fP58?`EKkM=}G<((97SjX8>)+ZV&{zOV5XPXdi^_^Nxx;W_{PvEce` z0JFd>(1{VPdH!OY+M@qOzN$y$e4drSFMs*VXW!&^1?%)#nwFj=Ix4OaOomg(ZXrgU z;U}AhI9we+f%&fYeN;F7H^J|Z0rg|B*&q9>0oF(|`xsl= zC!rs{!Uw}113mZxQn`iiSN)LIY!~!xPz~69Ci$r!{|&9+={FjM?{#k9m^48M-NTPu z=!&0@u}7*g#DwtHH7EoQy*}kuriXXi3aP7rj(POuL9NOtcwR<#UDc3Fp55TsUB0JZ z1AS!CZ*t0*UioF7^zn_$FJ5|zuIL}~n_`{yWW(dw4T<^sEwYwIzTtCNZJoRTL=Iiy zOy;UC<s*Lr@M=JWlyz zewW>K1~-!S0Sbwz8+{pB6~z$agzE2MIbx$k1bx%b=toDR*;$pyj%&+*J~{uJXKgOx1^tR&=ybL=DNaPc%W%p{(TCvu=EsuoKHlu zfW|p9ZhD^|Y{66Ps(eEv#0-I+Lj|uOx+|8k!*TL2zQ{Hp=^mu%_%WWb*Db?yQ0tX` zdG9cjG2Ss4D2D-c1Tjcchw;`yIfDJq|M@>gz_Y*Tp8s}o4%4P?15RQ5dzNCVqF3+U z;@ER-;k*e79Q%u}zkK#(e)s*yIPbgMEf6%wO&lA=8v`Rso3W-brKyYLtO}S%AN0u< zfRP*jhpbXw=X2zfTGMR5kioFQUDzr9e7UQtyDOF zCoxoyOgy>gfjM`{p5?zjcZXND z64<&Yvp`|B!;l$0gT4oW3@L=-IQf5{w^ARBnH)d z%AN_pIYq`D^Ea`Wuo#^$koe(`|6li19aP>X8V5e;2$56t0FkneOk7|ZiX~gcG!O+0 z&G{&1H5NSNHbJPX6Q38)pXKU&wJUj-$s!|rblaZ1aNyV923mLM-rob`7ys@q=3();z+(YGbVB-7T3=7(O-Er7FadrC8+Gf zSliclcMGA;A1WM-+rr82dWe@6{TtLmpA88F*lI8s_G&*?26eFD1hc=y)U$!?%juUq zEVbt*vys6QUY{i88SA~8{;I1pK4bGRRh=(wjBOpg{Zg(m4Z2j33z4SYoS<-C#@tn0 zg5L-}O;&VCwSa=QbDzZg>Z@P2?RtYpsyvQRS~2y;IDV}yE3H4~8GcFeMQ<&H7*gG4h zYOJjRs&bCdDyH=N=ste}PYpRym=Y77YJeLQ5O5cu_vTko&=xisI?^p^p6*iRP$w0E z_hTY$KG!PK=op;#yeRix57*W~K6#burwvSlQV3navot&y#lgohE3@PUr9M3vy1R#m zL%VXvt0D%zX4F|W29xe>A0ss2k+G3lC2V;P5ofOtXJ>MqBM<$`n9={9KO4v2=LoXwA~J69J}i557Cw@Z^NI9RW2S{ zd#ZN?dq5jbedYZ`=Njc1Ks)h)lN=mWKvlqL$i;8k{mVb+cmEFDsE(eRAPbLr$BZtG!<9PioIu}kzSACKMw( zbLrJ#(%04i+&GI_dSnK!v!Tcy^p@JYWCsuD6|jP*w4w`Fa*)Oe!+V!t9Lbl2I1@+^ zkFh*fGvqgpG!8uqgGP`rBkxN)JVTp&JgeXqGU!7%xhW^}vvgp>HA3PZNjKbIjs!7%1vFCDmRsvufgf1SJ|I;({;n`s= zKH&1JOXd7Dn3+6EJA^1g@~toc{YtQJ*`dfE;w*@-WBvW#|EDP1GAi(Sy?*mp=EEWF zvVZaqXWwAa;Bnprf)DxSpG>3^^>j)>aN^cgukz@5z6T(CpkIFVt7kuEkMzSIbC4Gv zHW9L$4Idd!wgOj2@(Mym`{Vcd;R|DL6v~)GrG}TPZbg<>VB7)mORQv8MeDcBAi8=; zy{r!+1)>JKay1D3eR8eN)jrd4jQ}bLdg{zLGkcpAt>Ql9XUgU_~oCNTDBvM>%j^j3BI#B8&AeV;S2r)hLa+>wtve|LQltdG^Eif6QaVKW0z=CNOy-EcM!~ z020aSummW`yw8%cPo%+>AsU~b5Q|0w9g%0{VYS!ruS{3OO$qe~fz zE@Ly!=h1fqM&UWXd+B3H_Re$b4UIvz0fRGvgV*eAq8Qx-c^xvm)^PH@1G&{`^#vZR zw732iZ@7xS2B$hRI5`GM$0HCFf7)itHLDyrZBrlLPJQG+o@bmj+7!XT?d34p-5w^; zl)6VkckuLZG_)=E;p?{T;2o2R&BTv(CG9#+j$cj}yGpX%^o#>Nb%NpOf%Th|bQW@@ zSuVoM$ZA$+oL_Kz`HPw4w0SLVd1Kn^Y_lPJq~cGJy*4m?^Uc5XLpBTI(50_Gco-(<%4EZ;345PZm!f<@F3W%kU#YCM(6{6v*cATrP@!@vVnXU;w4_i>7s zF^*7817SxkV-ta|lu}Pb&<8ArKjJ(C7nk z6`nSWkVNT5n5(+Vzt1T_-oMr2DM+dGxc^10T=sEoZqaDph0GhGu!{N==`ESCb zl~u@gwpD3$AW&E?TUp{ET{E5|ui!?fI!#;0S=J-h0GSbA=d{;{Y2dlWtxq4*_Q-H% zd#lw182I}gc!(qkGJTdHHSQIUbDRCGaAwBeE9d8c_XELO zUc}351HqXPigRPn+av7qhbb)K7jL(;Jo*fP?TXp!iv)o``||4=sPh5NFEJ3Q%fO73 z!gi3BoZUAAs9q_O6GvF|bp+j|JprCQS+y7))X5JF6p)Ob)<|+a$`x?dkVkRGMk&)X z)QVO~)dmmzQEF%R1+G@d5a4LBc-9yPcW14`%Fwn-csTQNiJ#MbnW^*7Oio;5pf%z` zjFW7c!>_Ey0NI2(yc59~CO&O5=5B{GgbBXJYSbNLfZ(MAt1yl<=M;fR{d}e%R-1Zb z(Weiq-NyMteq7Ul;4w11(L0>3Jz#7boZ4eUu`y>(o-_C{{XHml={wxLO+ffy6&HG5 zRMCBTGH@!x!#P*3MD;N^$#&%OXsLdyi_pgx8MCE;jNBp{2ujN++N35)sjcNjOX+)i zk{eByq`1cguoDG24Ekb)IYEY)MSNY|s z_1F4fN;TI+2m`1t|O=GL0W}H?I^8Hc~Gy*$MFb+TZm=!rEf_varnsE${soj=964-m2FL`{R<*oo8 zY+umwxUl-JuEIlgAK3EG27;HK;q+ywKYss1aM<6HKx`m-=(GKy9?;n^2o zjS97JVGe!+<6hlExl(PA$ zsqw4)-FM5%!FLSnphcHcN#h(D58C^mTTgl2f9sB)>5aiW8bR4__$QWvCX|g5i4DMky0WkFOL2m3s{_@LYoCodpsL8C?Qn*cDmOc7myH>3uGc?iw_HB3eJlT26a@oOqmN&?dhbozE+mU6@il34vy>_C z*MMdqz5`Ht9s|x)kS7U0uM+;4pD*(elb^oE#|9vZj%Fi=)W<*q-2T-GJ`+5MV4YlJ zjV#wuA`ziEGag(Ys+m*;3H-ZMOigKx(5Y)?F0>w9n7uzK?KbpDyAoJSnx*D+^qKLd z+5872{qw@-_@)*7EZ+bCAOJ~3K~zx32nVi4m3(KVblmY+N{=(gnG*IKa*;=u*p<;3 za-C`@UVZUHA8=2=omEqv^zc&pVPPEEO6SN7F09Q^J9-Xf>~-!_56sLDk1gFkv*5(t zY)uaWf%X{-UdB3&5k52d=lNibc=}yFDTqHavY8uS@PF336wV(^R_PyD!2=G?3;q`= z3r2!*m4h+B8#);?Ijzh!Lo#}5c4{i4 z=sbV(?ZdIjV>Fmd=TKRFKo z>p=|$@CX9Rn*^O%d7PTaj%z*F;Y0|0MqF7Tj9|St*M`|sZq$s_5(Hj6`}UhWMVRbY zU-_BxNg{rQNXV3tzpgHbaJ;6g_Q~Q$Ou6P-vdkDz5hW7CnhFJVlF0KGf_&5!L;SecJ-;9mJbftId^{X+b z{5~rpv!2_*;+k2SqwVcRhFGri1_s|X;2ld^w{Gx`L8_ZElIG+)emDS1p)aU59sQc^ zX60Zt!0C)xfASr}CMOO*!6I#f#~HA(`^qrzU*y}U{opAQV0(8U)c4jQx>f-b&;=W} zCG&7n1c56~iFb)in!`i#oE~qLb!f5cu40TS!AaYs@D@9V%N9%D9wvt8$I1`Rv5~>Y z2FWR01mDDY91Sl=vsam2k8PO<22bS?h{FYgzo&uu@pHC5r%SAsPmZW_ik$u#9E>YvgMt%jj*;2K4P`;Xsw zdN3SiiT1%S1*g+27e^cpd_>@pC&Q0|int<=d{2~*6wTUBz|7Xp0&m^?ZX2kABV^x1N^5eJ6DulazT zfG|AmK6?Vi)GcnX=;0OKE;&;^Br8MX#)mpE5va&k(aI#&8s#wVzl z?Uh~0QEyi`>2ZjRPFGR_tv+AnVEC(iMV%Lg!Jy5_ExsqRoP$$4z6_2U(8FzppX?-zQv+_?lP7QF|M{81*HpojAv4!{4qA=H5kStF@kYY#-ED3I*#;*L0t_s11V3bY!FF%$8m6g zT}LZU{qw4E6vln}dG20NAw{WNIt0vc>DwuYfw1EJIv@z-{s%9 zC)jY%S&`tY+npm1Vz`gRN7|)M7HJ#5+*RX@u{6R3`jc;$yoGksY)<6rnDM;)=UYNj z5kt-UI0=AB%KaqnDfiz0ZU5idJ-Ei<2kokUy7-n9fEj+T zLx8img3lIUzMvufQ{FUsx3W0nIfif3Mic& zPkq~y-vGjDQtZpyEJKSRIMbFthU0fK5XH`pI%s2@54qdxTPX~m zg^eNONu8^t5B`-o7+%O?XDKi}&|6s`mV`G>SosGb{Fe5=`qi)deUhJNIlmt+nwh@c zg4amyDP{Z!{%L!i6~a0cocMA0SLaVKK@V@rd$6di-e5S|$_1xZLEv`VfU;oGZ#7E3 z`~CDo(DNk z%YVOYlq1Ex0@fd%2FYbeop%zcd#SIUeFJp%{ju&PdP!1f2?yQlr ze77`_4SHmE;%v)|uMQl><{Nwk-!a5>xQ;ceC*Q;PCI@M6bLQb7kuxx6x(8zx$66hm zyi}%QR&AjZUgN3#<)1!*nAN5O*ZJ5t2@Arz(KAdzCM2Z{^dBq zpxeE~qTZk|-x~mCNk!!;S^nQQ8-vBLmNfY0Q@N{y-WgYqS7v%I|s8i#80~h7bHx>=ttOC+*FyI(pt^xvF zy)~E$24}wmXWiv;fH4{$IbMQ{_*#Wz4A((nV0JtVnc*=?#!Gb41b|S0AI>C!kDROw z(tv@h-}81XQ*{z>4Z4A&69<0w&FsXCD%dU$HT?89o^Vrgu9YDoeT~E6LmD|uzRnFS z$sLdH%Jaa~*@B8r$-Xkrcs$N)R_CmA+%1st2M4$ebyBXzYc=A*H25kj_+nt~pE~_< zXvJXMGYQRPgp&q^@MWy<+2$C(l>ynfGeKWGtL&4x%%&8licVG~RyUj{X-yCDI(e$Y z@8zj&=WDr*CYjZ99@_lA?J8i)dmW$I0I?4QrB(Dkix7d}5d`#zU~kcBTq~|)j8ZDJ z<8|CA4gOC%W3&c{m|N#+`TyBxU$ncj4ih~oI4C2)4_OjGAuvQ|&DELG)~H?ipd&Kp z8t1oVAU8^?qV$Ticgc?iWph$1i;vIAA|@JUeUmFix>YF6>_3WwgYv-^=IFE4twZOu zgQD|7=PgNolwEU2@fcmMF_``)VjU;Ec6`H2XPK1IUmJpJf(!#nDg82l!`Crf2PsL* zw?4}R!)~{OkQ0ntLnDHG+h{w>?Kg4md4g`7OW^5&qVW|e+a#~0r+%EHBm*<1&CD;j9zCYdIJ=dZfD!vT_Ok({1H9f~8d>dAgj>#!gB~bW zFF*c0mE(ua!(WFu@O!|wzh|B!m%!}|Klx7XddEYdg>KFU%%~ca?~b4H`Dh_p6A2IC zaqMNcQ*oNgb_72LH8`K=sX@WPkPThi`zX|>10}W#kMNL6?pX&YG63l~BFmmG?VJP5 zhs^lsasH+hs^mt37|jLnQDRZd6NBZE?I8{+zI zf*VS>83VXBhK?ZiF?+A&1566}AN_Py(GKd$W=iDAj*TX`)5pqU6I_dLdsI<{9^H43C*o!BzMUe>Hk{jr8DuLkjOS7vWV9B4}^lpXUEkdqI4hh?q zvx4lTC#XzQXg&=F0J|Cq5Beb9*(oKwH0?EzYXnn21~EX-`f09dk8`z&Lmw23hk#R| z&KbzqV2maec@j7Z1d~Qfv~{>J{-%D35MERMWc#fe;KT z?%?e=xdKQENP3SF8S&1#vzmzUk$zYy!*Q0G3E{@W%q&nbjv8PMampCM3*&53ZU)}K zC=);sly=6o#sn8y?U{UoVql$Vz&@X|po7s1N5_Ymd{#z+npG6}kJEE@OI>4>@U~ZU znik7ixXzMk&s@# z1rg^+vyw~y(3|ZM>QzYKf-5vCU)S17oIHKSosqVTt>nYj!RgCB^zG{GmAex8nfmX( z%faM)E!*~A8J8H@4A~Bjb6YJ>+!Zy^UE&-2J&t*t?4W^m#GOoU z^;yd9HsI+&-$7Fw*Bz#eIpu{KO=gmif2`Z)*IynIR>6`0|Kd+CZW}~8JPZNLJHc{*)oMbJd!B$A^XV6s`Z8T zG4$tsxFI{dq}hjo#<&fpBEn(QTw`J_Eu2{}=9AIJi3Nce?aV{@u!N?a(D!;KDNRkv z+Ja&zPxZ0i7WQCp>Od_|$%e^35`2O{d5#c^wn`Qq8S3<-`0u{|uF5jo3ltkB#=%E1 z9Laa2TVm^9VFX2OKLw%_oNQsxq>yoph#)fXy@JxtCca@u0&5lfvUd^Su(T}KF_c** z_ZDR8(Gb!5DK82Sn2(=QF#9j(RrJgd`1g%AZ5OC1wpdUOFBy%4J|-kEoxwElpz|@e zkWSCRRO?|1phxUz*7 zd_mi>%e3E&TVNAZJ$$smjokfrY9N>?AjCJpw^VhH8ZCi9^WZ#yDf`?2;fdy5+BFD_(ZDsco7}XU4X{ozU8*HdAwI-1 z$^xs-OzxmafXJRP^6Hn4WaYt_vLL7FQSPi-pq_X-9mw9=VD)7~%M zj}b9EF=&>_0$#8J3Ec27PFvdv?54Ye6g~}%viLD1C3rMJbgH`ltrFoMK1+y!2H4Y; z#gn|NwEDdE@dX^7;h0$&)05ik^u1`Lsj$o2P}?qt`c=Hf51c(*JRJ~|q_Y4xyD}u3 z@;;7CkJ=49BR#7NThOyAJOQR-BZsDe>jZ3a$};~7Am9Twpz|T7-P&|qMi3h`_L-;_ z=WX1NszXnpBj2&nYHRk=D)WMDuP6Vw#Q;aFasDsIp8ZaOKu#wayktA@#PK)O>@_y` zaQ>;><$vdMG>R;5$LHCQvjBfx1!N$^y?fP>Z-;bkS182;KoSEUBOVy~YgEp%*vyV^ zP-=WLV6Z4DV_ki!Z3lC^M`K!>I`ki%kZ{% z3ZFist7MWIgokL$+ee{xac~t`>iWsQT+t$naee{Z>HUncXB0;!Xot(f4!<)#TLNE3 zGIEp6ln#I25+o*P&af^9l6MNjO`CLz4$Ss}I4F;I zg$H^wtX}0~#PXJbyT|N#{OMAB@q_j027)+#CaT;^6%YcE{1puN%z3$uIIaWBJcinR zzfO;a$)-F(Xd8xs@_MSOK-^FmzRRt9wYWh8#SwOeSGkWu+)6R)x?Sj@3XUfb z2n>~D(7IM0={GYv7&EuoV(5~=yNu1Zzitu;e##e@o-jR5-H;sxgvQ{4JkGX8m?D&O z;_bjKwEBv}*D8#$9u<6;J9_PkG2HVxh zp{1D-)?tr~%WK+QH`QrFy9nyc!{fLSW92*pZ>E!9Wq}Sk?Ko%i%s9soVKgzX?vmOw%qj~&_;m3F#CM>YcjJMfU3bD*EYH?{0Rb& zmB0gNf*IKoit8=ForSxT1VJ<2@VvpC*~h~{5XBn)GH;?)KGww^Tx>1@AXSXg!$|KujFceo;@qn0)*<*PoT8;Q0GRqjTk2~NH^Vr# zU-#q1)-&%mhAs7-Jgq)-eR{aDO_G%`ljXqzlerpDz>rwjY_Le{C~%%l6?#{8 z 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 Qo4aSXM-n@l(Q)nn0U%I3YXATM literal 0 HcmV?d00001 diff --git a/Content/MaterialFunctions/MF_VAT_Sprite.uasset b/Content/MaterialFunctions/MF_VAT_Sprite.uasset new file mode 100644 index 0000000000000000000000000000000000000000..97388727e1a61409485a5ae99c9f171b2b13b7e7 GIT binary patch literal 145706 zcmcG$1z40p*FU_3l$3yqw4~A{Asvze3Q_`+OGvs)cPS_m5+aQPqJp%fk_rft1|=aO zAxL+9_b978Jih+F>-A;4X78CfGv_zw%sFS~o?Wa7U`<}%-Q6|9fWeNS!+s=8=(FEx z>@161+K_W6anjUhc7|h6n`q57!A*rxthqqg3-24O%+L^+gyUA5uWX*oAlbbrBpvlh z2B>YuGH-NJI(q47FV7>X3OP=CsLihc4R!>2s@mJZp$8`H5%j5#27{f3o>EdWyyv*3 zr3Hogd4zemp?`eRd_qFP+}u(^JhFWJFsL6)0h)pt^mI4@gPnn%#3WFbfC^C3x$ra3 z<)OhMq8+!QtkpDg?vNWQ-raZ&#)5GG5EFJ1`ZQ?tf{{W`Ha5)*{A}uQ8@MqN&cV;g z%lH5801f+*_g|J;IjHl@EeJSh>PHZY_I>0A}*Wws0<4xHH_w-T{t4a@ks0!)>^|iR}%@}>#AjJ;{=yN%EHZ!oopOoid&-bKZdzELOu4B@>P|i z8UV`J7$cD|M;F(7KWg?ikm+RKgInMM8mQ5!7(2k;JaDH3{sl>B0@=QqqMd`2;}tgt zI1)C{DEAhuD~hD%O3}^??g|Sv(a6F7k9H(XJ}E|$3bg&j1%^Dz{C~*dgmkpG)zspFA$_q! z_P6lDo;{vq1j^Y{)(_Rei0hYrER8H2X$l*f&S3=TpaLuxm<$H%7hEt?S$ijFjp_D0 z840sQczgzWx`6VwUy6dAU^ky-1SFKB{Pa^9dq~z_9;fjmmcX98pq>Tm519-CjzmI; z@|udWFxA1ww*czz<{#`~bTiM;q0o7-UC|6izUj{gjPLh|pUVSdGhjvjnCLHrp9E+O zvW!6;IEkE%y)k5bd-k%KIj{}Z4kZ{2u~2p!gkDs`1v4us?CiM!5?0syfs}kd`4`a7 zIY$Ljk)8S@eQ$`!xOG6D)V;f@xtl+#v_kQG`#Jwfr zm4$Kir5pvyMKS(CN5V*hkitN@sOF#3K*Bgv5St)+$ionH52}EZ$Rq5X9AL0@(+1$a z@=jJ}FqummmcWn|;8qrvjxdQz-p61og|z8dwrLIq{gm4CPH-TwzaKSKD3-#mHVOLv zY5^Vv8@cCI0qjlD?zi?c-U}#!5VIFMWFXhDut!+Ikw7e%e?kF07;zDDO=D;YfDe<& zAmzaHRE&|C7$JAf+*wO*VD`lrfKPhLHlQ_g9F^oY;Vi*@P69>P8>PinuF$dK8|DAd z4+?4^iUpS(d--QA{fK)|7rs3KCq+#QK@9lQw0<%Jn*p$6I83ki85}ssfA^KOa<(#q z!va$3N&YP3Pgm4|Rju?6{jG;C4Ch-_(4W;PhcGsU*{G2$)BSM{C@=YG)*AHcC&8)j zzvqhTx98Nop+49CjQ7KhR!+ilSpLWd#Tms<#@NONX3L{O_~*4zf%ZKs2OBq7I0i94 z*d|cpf7_)hbigsTv2usQdMYd@|Ga)bBg!RX1Y~}Y2f*BvE^C5=>VI?7a5Q$bGSzUg zax}I4-G6aTobk^S)NnF!gf#336DWLT^=FgVi&#H4h##9F3=tHWLH9@ay}o92oYjVo4AXp&D0oB6Zue(hH<_cz(s{tTsUkFYUAc`PjQ^>b~o z5ur4PT1Oc1>cC5|uc*Ksjb)7;jbWp{MNwe;`OniMR?3Da*rq|u{Iw_fFW@MF82951 zWC`O90SLiA*A?cSbfEK8AmzQQRme9ut1 zd^yd*G4E&fN2HF6JZFUSs|g9i=%Z={^-IvP-`D~UL&^sqr~X~PXb-uul{;h>_I6-A z(!tmi4kK}N3kJK)-k@JnC$Xo)fz+3b&CK9tbQ+Ld(&@k?CKq`Qw(7!ym+ou*Y~8cV zAJe_u*fiHBA}XIJ@>2!h8N+<1`Y)W33R-$H{xiI ze+c{YFbb>sO8)K#DO0Tywu1?9P#3@krd_bUljaFMU@$LxStE{r<tQG{^i6SifH+$ddMU zo|CnzjbMMiqAsPXA*ZIUqM@d#E_1=~hn2!c)js2b^VC7A7WCc$lbHx30SB>PTz?)? zf1Fj^)O2EjxSG&mg^%}_vIf|NlrW)Y==BeVqj1C&DDLM(cwj*v zEZSCPj+QW^xSNlFLVkoPx?dKrW^RszJHilwi82ro1|ot6wAsq(fZC5s3$V}0@$kzC z@bmDU5^N)wVGu&tYO!zTL zFj-UrmID$vOn~$my34ToS^RN-1BxhGA4F6B3+>`xXexi9ss4qg_7~cvztArKg{J-& zn#NyfSN=lN{0r@;c|iAVdwurnQ}-|9uKtCl{}-CUUuf6Kcq0MnAgv#wMgAoHT6g4Leg6Yx z6Z>gse;J4V7aGQ2Xeb=ib3Yzx?th|z7zYxnPuR~YYzUGM#C?zs(gq<~%1;t(2P*Fs zVZatZ`~X1`1od<%g9PqQQ3;fO*HEqh`Gi>h;)$Yc?`s1%AQk~W07rF?AyeUR&Y<+W z237u${{P~PKw|;Uvpt5$qTihVFL6fqa8UA47atDJ*Z~~tlEcCIdH|=a^l)&-_i$YH z*khL;4$i~@dAuwB9cSx+U4wn}%N|b39{YDM4+m%X0FHI#;ozYB2W8iI)rW&~(0_Dl z{vBuY!1{o*40wIoAKxC={yWab0UU5H;@HDkhn^rQ)%`mT_-^ngH|R?Pr~uAbfIpCj zcaOcykMG!^1}K51->5&D{_6u``HLrt_$QnVhzM{9_ZT|8`OW$N5@%}<=hzKAw)(qGII8Otv0Ecvsec0Q>!9hQO^RVe~aEKtb z`{gNWJ{%n412_%u4hIL=^L}}HTMh?@%Zfy9C~h<^WDe=i%VU9>8&U|L-`<2i6C?bEEcCzCHGu zAO0O@=>QIRe+OK_`k>As!5C)c^JD72M6;24x;DZaTX7(54b}) zc;2b*{db&&132J52HaT!J3t-Z%lZxn2VC{<$8qdG92}JYpyc5iJRBTw{{ZC4++#mA zbT~NRP6FVdUKj5T|2xk70XqP9Z-6Vn$=+kHIC?lZsCeMC$9{S2aBxuT6S>F!`uO4C zpyB}vXKUhcaKL>PSRWKl*wo?RfcvfeICRthjx%?_-oSk>D!!5JUAh&_{KkREKkN}a z|EC18{IB<{KjCmf41hdu!3UJ;m;N1Rb|1D0#1q^>1Fir9wa={O{uh66Z}XAVc6#yy;L=n0a>*~7tk3-Jbe zL*cCcAK*+Lume+wKkyHL(+6dc5?FuB1M2@9|M?RR;JP2jneA|JQ2Qy$-Y&EMJI=%b zy_xOl1;~Sn2P7PagM-o=3de}^aBxt1L*e9b{X5S10lmQ?eWC7=Pw|Uf z-*HCw;Xv$R*TDyrz@I5_0mFmRvwb)}TmJlsvJr^of9=}`+nn}T012e_$~{8=F3-q5 zY!isDAC$qo!1{n6e1k-B;P0@1w*L1g7zfh9xq~)91N?!Cz@0zezB<4H?{_FVI1lfy zpM&ZG`{-g&S#Dol94britD}BH0r;UqS#OWFzYOL9{Ob4~4b=Ie4DPzYIMjT>7|{c5 zDBAz@1Y^MW>VVI|GVn1}`~EWU2~@k*Uip9Gf3O{3knfEFa!^27abH~tDudsH0{(zb z2<82B@EcAQD5LO2_La5wmBDv|s6OEPJ5WZ=1sy7byJ!?0;GoLDH&A8pZh$I-^E;}{ zy{}BRR|fB2Um#-wf4n6OW$^wF-p#=nOehaSG#ThgwbuqP&p?@OuZ|8q!F@mI4``>M z4F1{xRX+)p8TQrb_tn8zo;}+CR0ceypbYF8#q%sw26I5w8KLriU4!4Pf!}(e=(JGz z{Jy#oR6ez@E&-Jl_SI#fvi!cfBveM>gL#7As{!7iY`9lOt`(^EU=4syz`6pT0R9B*0K^gC zd-PBSz7K2>*dT~GKo(#ZAhv<|f%pgF7|iWcl2Dd{GMG28 zCm<78dmxVyltFxzhB6(LDWJ>?Ww3^NPzL@1Vlg_Dfo-0KGC!0Vpp1&Uz<)tJ1M!j# z%Ir`EXCF=|1AT*70MVOehlI(7nFft06##*8dOX|*^4|x19naf}T49fqJfS^zghAy6OUs9DP!29uMT?C2>vd}bt5^;bM^$}0Zf!K<&QdeBj z9AD`sFe*AH$LhwsSmwn*e}IO&t#2?XU3KT=2Zn2N_i%_G#H7;Qsh;NRf8nI1IptL~ zOXboQ{}?V;TGrEZk`IXt5ANID_SrO~r4jckcPq6jTXyj9h#D#CaZ&DL8M5(Fkm<9@ zWZHEW_jI@Pap>J*f>(!TWcFD6t}WY$%frHH$ABFAVLXRHl`nHZM4 zUwijW7aEAYPFRr0fbR~u>u+*C78MW6#eC6UtY=oJQ_{V&v0U;TSuhh6dh&92%(u=H zLi3U~NYha|h}Wy?=s1<$;i6Z|QL3qPF<08u9}Gvk;7WMhqD>YTLwlMrct0dbz&l)^ zJY|Aw9Y>I9yg9+yswa!T%XGA2Na?6a_|UM&z}2 zizZq2#Cj2KTsu!IL1GKF1N~r;8?ju4-f)p0pQKt}KbASE0?i4lBXXN_Ea5EeQ@-lt zkh7R^(~x^CO`fJ*+n6FPzhyrZ=+6~!bq(!3%zpseX8JbM%t8_xQw!z?44#E!9;dRU zMpIRME8bkwF|-n_ItyQkvSx&k+pH-e!GhN2Z^u}tVF282W-nO~v(WrU-%Xe}G&y4n$eh(N09-iq8=E^HXy!I|q zoW%%8@Xg}y_bX}Pqt- zaw!gH-uCVI_QjXBADjtSLhHIo75R)VbX@;bDkI?7gLq9^o^5<_^rDXM%@h3S@~U^U z{NAuMuAou&Y4_9ERjv&;C#>2JV%odCu5WZ9TVqSW7$VqZx?aiaib?3MLOXOf1{ON= z_5!nn)wT73r(4V~7>R0vMBAmfT~(O^ZH&@Y>@Q9h;G8e9jY?M4EpOeSt=zOrO(HdU zE_B5&Af^M4!ues5UEUSdvB`DTGk5dyEm#|wpBE+7cS&UsiSsk2@z~j)EEc8Gra2x# zqGJ-k7;1sFfkV>QeoR;K2^o<;lBVo}l12arp9h7b!VGc(JA0*o&cN*5?eV$0mG4vP z=E}T$C-YV@ew-=Yh1#75X7Lc%6~?(9={j=FDC+)B2WlqGDkL&=f(Zj_sj{wZ)Vsz6EE@L9gnp+8cWklpHfToz|S$s$@urYVGk>)_i*x{+{D8ZjnP<1#2*UV%CP2){Ty54%I=k?N+X0 zXx}RybyV@X+`6_ABg}L2k|O)fv{H&1jHk-76`rg8+XQ-OXOpcf-bF7YJxbOcu%{fo zo0+6=3ps&n;dwTpVUgG9-q^QydZlB>Ds$S&@I^xwJZTg2x)z4VvJ&ifml^~*GF~>h z$a_;AiEKh}`Sh|KE>0@=3X{P6Q>tWGf}g~o3R z8Cf%KEQ!Ozm8}D=3R7RK^w{OZqCG)p$^L{clQI1y&!|NTi{C>$@kxfK3$aXG@?w3S z%{F#oQc0g!)y6rp&@V^Nv?s*PGUE+wEov?%7ZI4;G(UJETlp1+ zf9DO8yOHS+PvZ768d-L@OoVY9m8Z=35Eizn%Gs(39hsu-VN8P_H{<=dZT#Yuc$Wd|nbYfl3kb;ER8J6X#XVXPkYwKXiX6T1eDfMYYN-e}rq*fmkF zE#=*$--jwqxQ3`b(DJR#?q4(2^p5FxkbJzAH;<^#tWfJT8AX(EdwH$!jaQ%HhPTK+ zRJyRH!z!II)Z!_w_!H5ldEuCok+!B&()a~Dl)Foi{>JdWAo>kX8N=BD_itS3Q;c(g z9;Ny^IsRBOO<1vx_H7xLEYTfobiAHnNAtY2xQfn!<>{Ne93#S8Zgz);R6o)6DKS;f z3DFJaS3_F#ODAU|=NzwB3As+w8#oE0*E`>FICVw1g!77khxyYJscuop2)i5S@|Hdf zs*ADi*1ByK!Y~&u!_YNo8OGokm+f+F1iqwaoE@tWwZ$`>;l;?Xy-&@4^ZDq6GQDP3 z?HbLlS38bH!i!go%2}1;H$UMeFb}4W8FhCC&lfCU#ESK7D$36w@$rgZ;GU|8${{pq zU}4^D;&teq^UaUi?TxszLc7LZh|Q9H)@cE)+8^}F&(5qQO6O|B;uSxL4Rah1z{MwzQ z#)TW5&nn?D%yCcoGWH6I9RHA{Tt173i=VesM|;ZGjTi1h+>;JxHqq@!O`~M6bwulL zO=Z!MISKeWMIK9L$f&O>LYQwh}1cUVim_eCBT!t^p-J-_%YR?J0 zyJKccUKyBqGTtny~KOE#PZDnv0|6j79r= zbj3LKO0(-1GZEW|<8+1GJoKu4E%!eXMSuS2aPEhv7U27y!=olgM|L zM5|Tf$t@S%;$!hS#P+9T3G6IFC#1Ho)B1fJnuI#RETbI_b5+AR}BL#-d zUY3@j>rr)KK@;Of9HC<|rI)|bEy{iDS;t%4dJ~dR^7$Cf(}3KBZu2=Z#r9a`(q>uX zMKnQflTx*>=;bOgJ>8Xj4#?)5WWv|)KQ|vCtI1_5yhHYwif=pd=**eyf}M;@KC9oo zp6PNn1)2Dm%eOW^t{><*Cy9%nG;*%+?Z-#k3v+c%R6_o>8A(+@W>b)U1d_Y>W1 zmq#d)tdT27-MZh>K`-ZW>-qew=SEvxxRFZUMdI%leI_`1@(mM@o(qrjQ^2`V$AEtH zv#*H1gVE7w7c+gz%RxjjTz9Pwv6+~6+D zrS8^iPn!GkHwpc)1ulBgFo;Lf@;*gGvMB{UB@8B`%?R*aPz@=Z{CpbEH#Ce=H1_g) zf@|L&U1=REUjKSuxm!l=(u>)8+a>bS?uM99*^}B{46y{ZY7+TH-eLGJ`^8xt5F}^a zM51=)tcuW`9SPFYsmELDmXL%=C)*51Ro|kGa3)u_ZtoO*RS_}gJO48O_!2qcDLRja zEa%r)@2-8t&ynRnm#cbFw4bcoNHc8G(+rNn@GSo# zLstulhvQAm`XpzIq&axaHHjlX4mSAIrTDc%vG#ME{MBaG@@e6g;W(gbgQzQ!jsEG|6_0qq4!2j!kdwpH96TO?*o?b=?{BL``Vo80qPZk6HjbD`X7y%0zEWJFaMye{A(TQE3rc6Z;EuZu8DrR`~%+ z*~oYJxW-PEwZi>lRk)Mt8|UW~8CC?G*VO!O6%2^HRneOk?r#{n9GP#A9deyqufWO{ z-xasu!sQQoPhMvVydNMu!FsmPca?OB{XMT9%p$%!XzS}|$l+A^wFP)VxE~N4OmX~l zfe!3t>F}ub*@4*?RqEZ+5?DMvi-~PRvbkz|$_ zR$u(}UxZ;_M?8~16GyF9Y<09nf4o>GL6I-^vh;`OFG+z3iv&+*)5_l7XjJ4LyEm<9 z^L22p^?IYEpA)*~0OrD%S;^xorAPU;s4a6GzBQ<2*RoPNYH+T65#2d0Ti_n-Q62c0 z`A*v_e4Q)v!W{Od8O{BS^(QU!HyP*KMgl2SZ8o0gyq;QW)WQ`E)JvX+~Ao8h-%iTI3E8O8W_}U{?C|_OrzPO?-Iwv);y&9Bv$FR$iYxvCp_~#T?0RC zdO#yws)f((Xr{oi0m%Tt+-ocjsS5A!t;#r$*$y4?w3ze8fxn~DS-wnN5zId7SDJJPZcO)kqTmP2P|?{~}ZoLASc`0AQ5F3JA9 zTiq;nOQ_j;#Bldp)n`$U8azyLiz>a`k6#|@EAysRkfqLkjVSKIifJjH7Znrt*`ZEp zR-he!h<0tw!yKa#E3YdYtD6%!S+>6LjnmDe}TKeVa)l zd~IH>)Ymd{jc#6e;iHa<^T_%coac$eR0h!&h+u*Bbcc5FiaXr`5{yfmgY&1*sIMM7 zr!e|1z%qc-^ZRnpveY?mVeOiS@tO|ecd@Fb(H?BY*-nP@1j;m#A9*a?6kOhHb0qjg z?PbT_TK2HQ-T8OB6tYLsOXBY)A+k84H%?Go840|p(nW3@bDt(dYH&lnZQ^3pqg$`% zTJIK?kWNN6ppA4IS#tJj-pcg6-rC_|+?4NpHBQWKq3b>g70rO~hfR_c(Tffp5!Tnr zU&mD5o-}VqqG?=+qJJmI-P@c{t^PG8A(SH9+YFERL1vA<;AjTm40nDJqZDYkQw_o}lj29UXBqMT5JyLU&c12k zm~cvq1@o{5ZilxrOOX796kC&T-XY?1Wxiy`q+{>!51(OEm1R5|o3y(6SuyGcjw4Jo z!Zxj0cHAoXL&NL-(l?36uA6o3ET1ZFJ$L6~qKXnvDPF(z7%!I>oaLC`xM7-G-{Na64)g@ct7$ix-F<2Yi9i62e*X`$SbrOH6Ll6+`(gFWE$?MH0Vr{FqyD)Ud+a#tQM1e z|IDmY_L@O1mYH#;szFeO?LgxC*3siQuk)L5sb`q%=q{NXdwtTgYrQi&F#owk(QN3t zA$q^=i(<4`I&1#`qUY4UW?Tb=tSL{94q0qgEakpCws>7|Y6>sYalP2N<+595Gt#hP z*0}%Mw5QRLYr=+GV%m*q(Gk4bc2QsJS`2qShSwT+_W3OK8Oj`Gyc8qW^{%NVh5N+J zvlh9cBC7#PEu>!>i~LYnAO(J3*Gli1X!Bb>(b?*;vz_Geyc(_DF*`AeR=j60>b--)Qa%(nht&?JPK=om$WDMF;WyecZ#e*!GKprc}qU*)9v>z@Ayx~ z&#sqFrtGW*O`VF#syd@nQ?hbnB0!m8@*(;h=4wUB=TE0`{UiDWhHix`OE2AMS28=v z^Kp!a_ZuyBl9^)+Qs-ui0M-DDke-3~JS{Jekk@bu7~?>Jg+e%1+vPu!D) zgmIH?Ekw9qOJO}H&?gAE_RN_q{9{>Jzr)Sm9lg@G4El1QCpq zwOU-CbB}HfhLUp>-cF_-?@hI>Jsn%>cWWYDIUd#G zm%%GwR32}cTGf_ugpEJ>+({`B48kpflGbkQj{1iZPpcKL!*IuSZ;aN&ZNGfemUBLA z=(>nf5z))0QD26K#3N5yd&jMuXS<3|;Rn*6rNXDu(f{aV6!xrUY^EJeovHb<$V&57 z+w>ty>)U8*W)cRUkQQ=6Aq%Iv8n%(teUEY~)v;7J%`UKrZT2Y_2%Z&Fi=DyeFMCOO zW`a%ACX)D>g3^P2eb7(}exCGNTY7}NUQleJB90|cERjn?6TF`1h@txgAd{Yvg61QV0 zw7tEF-Jh3dmeu6a+#MO4go3P~{x_lb%q63CzDQ0bS^07YzZcwksBCI>U)8Kv*JSh( zZ67rZf9gdS?e3h%k?)q@NNp8YrWg_fWZoQM)`?L(MOFGe@5M5E*DdWxitWyvWM)=w zJZehSSGYPdPh6%ScVS%3VUBIKnXg(K)cQmw&AweT{3tx;EoyA(FH7L8Ls;jhrrcejIkeb1_!uVY7)Ck#2^as8hRP zH~LwzSfguXZ!)Q-C1p>B(5}C%GZv)iXY_Vm+sJ$Bun~8We79q0SN~SOfw;Kgd51JE z0dnzmabKLe1hl9Ik*C`F!N#%yBWIKzY;jkAelAY1K6cXZjUP69?d$IL<+g&k&Ue#s zpDKIC@2lk&c+0y0p=6|+}n7uJ_C zvvSkXfQ(CTeX;8NllM?KU+*Ml?&%QoefgZgU)-zL%CPoLN)fdHER5@j(~_m>gWLkg zJMDUY;|ew~TC^OQb<(!iZuPu~sAtlJHpqzX%Nl1>*$7CRt2l7kF&H{732k1fhI{uO zX%HVSv*F9ne25)dnKw+cq^2{e*i5IP^{r3K!OFR)q(8DPev{gx7i%M-tLjS))ojPL zkXZf(C+-PmF?;Nkaq$ef4sq*~aBj3mp>r#3^Ovc~&-+rcaM<|0R;u#*>UDkj#)*8d zw~DJrh)5MPh6Y0C6br9WkW(o|sD|mvTMMSTj%Y&8g#nWX=g2nlIJZYo} z+%p0b_e@oZuSyKk^ekmE_6wW~#3E}l6dI$(^h14hBH@E&*2!Ew?`*n-VqW)du@(e; z$ia_BFeWu?ya~F!66ok2YA~r}zf|VbCi#F2_7*$p{0-HL&f6aNud8Q_xSFu5rs4}6 z$Czd^TZccrpIkgWs{^mXpQWEu&GdX%gP!Xwa^ntx%SaDr^&`^rb9BUfpDvc;O*Fn> zy8Y#h>|$-+`KGeG)4>s+syx@>a!W%^fg~)Cwn(dz6RHTxXY^t(h&ZIXkW13_tu{6_ek64J?es$|t}`D{XHXf`#xdzdC<5S-FytFJ2P zBAJr4$}gJ7@Cg%BF-q;A;pTWRt9G&_XAzs(X1`lD#ixs{?lYwJ%+$Ob-1FIPcuN(& z(z6wACwXcFXXIJ+DKSfB(!le{7FiimxnG?Gyyu7EB$7GbB%`X!OCC$he@z_knd)IY zHbA1|if666*1z`f%Nb>taO*&JI5KJyMqPf+8ekSV9F1ci{#W-DJojrNPaZt6OZa z-K8UKa+V3ATXCLE+0*%G0#j4fF)9jkw|pKXB@4^+R8db#_?~#c()!&k|7@e2v3aw8 zVaS=M+;}$TT$aK_vopG}FK-j|xl(@+#yvG}i)}yF2n(b+D>Eqjsou}o*9q2i+Zon5 zC_>(U^97@BMhJ(5L$ek}=`oiUT1C-3BD(2uc-4`o?)MF_HMLXf#ML5rOQ-9Ui)F5? zmf_(VjA<~2Z8WYabOhnvd3!o4BC^Jue46P}U)FU~&lwpa$~5~25uQR%NFU%o9FwTU zp{3@Ob&q5HERdYHZs42FyL~K1P@efn_~oFhm`UY|MtI~l!!JjB4eNYTjB{JH8X1GM zntd>Idrxd`DJP}PV7K_Eb8S) z=o43f56ib=tiSGbIXT@r!Ki^rwYDI=$fh=hP2)s+LCe{qGoTz+$&cP}v+Wj>iW<_z z4<@fgYt-p7RgtStXyB^ETtdiY%YXA_DRX6!+T$fZGcz-zH#<3m?Ax030Vz_qHQz1j zRa&HQJ>2GuQnSK}@E(~?6)t};oqr5}n1!yuzgf~O(eAW!U`F;E#?y9Kd}bokcFhp6#W2|(&@@12N=P_p1*6W049M8X{aAe!J zCCXQ-;5|ve+^d*w>bVa%oFi!%MldR^v&nDfN4& z{XIX}rnL=I)A-mCT00XP#h6HEwT)-QD)L;exaF*M+?%VWGie2%UPk`avdl`}oTSgI zU|ol`?K!Sp0^0QTnHI5Ffw6ZYyYd$;>pDaiTCTIIFih35CaUyyr3>8pq;+)mnWco_ z>6blIQ#|2)M6>}H2FqAOa^Fp0M&o!kHjj1P=dMe}uhl(bC-QU{Ydj=;Jd@`AO<%$X z_fD@|z3riQqvPZ!wEN>QW(Rp=kyqok*7Ag`i9!_XHml5`4)@2~3?C^l83edykHAvd zOy(XB?-*=KW3C;4A@}ejV`5bX+w^HJU%wZZdoeFq%EvQ1X;Ypo#VDQ=i{ayLdY9ri zgk0aHa?l`vT`&eB;bE?}<67Iy+5DL#&`z&EAzpsc_cMieG z7w|E15m)XTI^X!Ab}Ej9H{%?Ku;Yr0ucyD^c_ZB8-h)9h38H*-cnxej7skofFE6w8 zU*pc&kcwS<)RLjsxF}ZIV2oCr?{8d22e%HB!Q&QxE4#D?mb z1OcbIuGdJqHOEE!ovGZyra6Z9-g!#;q^{ZtX(s7&?tPli{VkPWS&B~Mu}D^y`E%ck zVY*;7Pq~)vY?g}{VzqW}Th$5D45SRI8C9taO>1(Y>lp8+Sm8gbN=6kO9$SK3M!KHC z^XlPKP|dXwb6+njQLZD?F^xCGD$isnN+Wx5Hv%s%^wa6-REM?~AHNg1e&F_Yx`$PT z*U=IEO+!wi&-#qHsa(NGg4M%>=nJF)VsAz2)uk-lEXz z_lua5l8T+hO5{G#=k&4&Nf}z4VM_ASG-!EuJ=1VtZ8X_g^^tPJv8&=`Lzj~|S9v{J zCT41s(UKThZmQB*XRbdj>{cMQgj=y>H(6mMUJ}6+5xq2xQ}i9<3MqE%PL{+?4cZCy zleVAXBA!-iZ*R2qkymu~n5Bo87Y*mqX@+flKkb3hV=8Q(!$gyAbfv18-xvmhQg|h zieA2sNKCjjue?wrYu+ADj`#75#zq`d4`GeR!(V=$yWE*gSO7Ao0if@ z5{^VE=Qb7JRwO5HSdUP8yJ^>#r7eNCEftKLC>uGA&LQzE=hDgo(sp_D!PwY^w+Mz0 zLb_&QZqhBTfu(OokODiATSpC_Hss$__J5-*V`C(%nD^zwE!^pBd$Uf%&-YB{_^!?Y&VLj4Id?htVxl%Tx<*{!8Yg97EYq`WCTR=dZinY5ON!7>xVBN}CE;&{ zy|EZ@Gje$Hc{uiJUWShsM}R-(cspG3NZARmB~j07pSPC7y%S~C-pN+o6Fjfrh@&Q6 zdM8V9c0dvC^;K zkWb{VwM&+L;3s&4*8_vPeA2P{RDj_V%RKfh?8lrA+~>y#)+V_6YrE-Wu!$mDj|b7D z25*L>>@Iz6DTy8AkZgF>Hl2Z!e7?1-bfC8{@^hOJcK|8D23=PcZ0srjnux1Vvk(0} z6C$%lRw<)v1ks<4cTsf{c#ULKmDmMR~k)I?gcPc5~@uW!~h2$~2MC6(6jC zyEr7)Vn=i~6_7JA74${xA3ruxRl^-JUYLC}-H0h;`SgVHstr5Sv4t`mZCB5bXy=+r zCBFuusnc9MhN-!nR_Ei=)5OuDozsa3up6*RHX3%n+ZXa~wdC$FvC&=NAQ1Z6Y;N;V z=;)>LND;25CP!`kef$N{3ADC&@Ay-zkhay{k5yC+-JRk*smlB*F(Q#lm12c5YWYUS zb@rEIQ5klhv;r48?KaqBIpO3By~FX^Q}@Jt$rDGL5X6<^{uhgB>*!>UN75&V&{*fM!e-#0SBvubNyjXps__&!>Fsw2fo>Gdb_C)`qV7o3Ide5zfo zGi;jVM>dal4NxqtgjHP~VY>aMQmdU=;GRW7m`m$X!6k%_tI+%`UV|>b#5HyQmtE2i zt@Q$V>Ez^ENvXKm)zH{C&^NO(y^2JYyd7^>W%heTj&qeQvxQC6OnB%#R#A`HMOVko z?D<};VVo5pf-5>I-Jx|#l53)vb}sqi?V`o%@DGIdudo{2&Q6@?%L=32o!Lb|SEswI zmAEwR>o+t^uSCT^dd6nwX6*sGjYhf#C zscX3Iy2cJ49v#16tW(Q}b8+c)cPDQNLN5W$41OuB{z!LzOE)2}s_(h;Z234NaZC3m zUdqksrz`pK5|bJZ8%FCw z&6k*runeyiIPo#u?g!R$=+LF$`*4fdGUttq!X3fEWEbI!#2gihoUhJSP;?uuP>8NB zo%9{qxj$5Urg*R^{@bNU{$@`>Z7$`0gVT(q=}eujF4(xnHepFmS4pyQw-f!C3g0uj z_zNhJ+#OmoF-knMRs%@3=h5exop-e#B54bR_~ zF5%pHYrnzh9DQ_YPGEg-5ndpEuB_^=vbNIF9sZ{CwvAJYYMb7T(iP>eRedcGq{m=f zBHl}ay8^MsRmOAYcn1MKX1vm$mf``CW<;I zq~DkDZA=a^9NcLelI{#^8-i^YwG4e@9m1Aa`$D3SVjq*4cjs~MeTI@JgKk+3kpwjN zjgN3~d~|S}pC-GRq+aEAtT2+)`(!|;S9u&FsKY9_G*2mOE@xJN7t^~-HuuM zn160uz%YGT<@V<#UfgVsx~8bhcE*zhhP|_8OiM&QyFU33g^8#-ir?rZUNcF*yZD@P zNu@Dv`~9uf+B3Jt=bq4rt~UhG6XHl#xFugUgAXxUY|eGtsiJ z_+|2Gm*fQv+Ua}6=g-znp1z*Q7S6P#At)(UVjmev8pWZu@i1g#dgq7{N5IH!%4Z&% zN)!X9-_i}q$dpAp4)L6oOJ*XW_|~lPDXUEuLHB`K{5$J2HKUCtL7p@V$Iul93GR

5$8A?$bRCn+eC&G+8);YdypkS!!Sk(#!h4d^c9>Cs-VoNc`H@m$ zoMq_OCvK!zD>tHB{RDZtNuS8vhhd8|=*C^3x*^sd<6o*c4J(q+Bb=%(h9)MQo(cYD7D3X6xNXTz>k)qJ~SbqO}LHa)J@`GZHK4XG#=A*r#aBhKCz9A-|~)YSg_8N z`mDx9QS|=-OF*>0+PA^mUaf`Y7xX+rPjgBOvQs(bVZ;X?ppoL-~7952LJ9i zzhPixpJvfGX7%IJIeO(lt5HHCIU~bMN|r;focX2`l!FyE@X{U4b2QEpv%&R=0btvf{$9F-fQ`J&2?suX$K@%@Q#Ss*M%}Uwa#fIP z3p)@l_|N;vF_+P0qRVM`?texfg$(m0L8B5Z5BS zNiF?tO9yYEZ2|)7eR_9F|NcM!CqDGTmwM2jWhIr)>NEi7W>aUvy7f$cXK^VTWJ1m0 zV3T+Z7TtM2Z?SaKPZDvkN=)agVOCFlLw7R_G6{wiW$Eex6Zt3xUR|2DSL4pTz;pC7{>!8aRJC2RP+0 zCc9D?L>f#cgZFE{QX8WAS}fZ9+rclR6!c5Oz$)!y=3hY+VtC~mciMX}unG{H95^*< zP8E=nTf8)6W={)moOdA7KF+42vxuM#s_Y|!zH<=h{e5LVOZ-|^)kGTpkInp|iwuaM zC~KdGXksiCEg)ZU3|uee@^$L8E6Y{KVTC@eDPvgrW?fp8Tp9?%={~_1f1&G-Sq

tXrIr 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

n{7CVrfiwLRN&JHu%T}kA5RC1@H^Nh>|$J8nC${ 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�H*QWSVA z;F>%kyUmU|pG{FLKI{vI`N3+btL~CGnPUOCYsArwbH{G@uJL_f>YYNb1%!=0-?}Y6 zDFPX;{dfAuad<-^Jv5M=vTHIKYRQZ@*rCIy_YmuRq&hqbN=kTfW7YgvE8TfGwnOCk z7WzNi2ETu`D^wcGg1Yo=&MFYHPVR0IyZky$wt>DV3#syCqJZLT8n?|sh+A13t(scVDQ^5R2{bE)SLLqs**Ekh}PLI?$8OJm>I;kHt(^$sjGtYkyk5T zF=m|hN^RbW-Wp(7n}l~(roX4W|a zjU=vFTc^-1-i=V=On2-mCrh2BrXtQ;+}*gYMw}YZ0!LQOWknmWY4C!Ep@}$++_5y5 zM&YBr#>p3Tg&(dE(e9+f-#bzIkXrG)%}E7HmNTEf(-C_lyDzeWCNt z=x2>arvp9;G1fVo!5fep;UPpmAy=_)JHs=*h)ZxXLTGqAH&R)7W3oKb{3@#8u*ceR zqhsu53PG1m_0pm1smTysoShoF$fDRMsBh_;G=o)cDM#T^$+{q;F0(C`DZlgaGB&58H+!vnn3CU^}ITbpRB_4z#6rLD9fBU^+M+hdD`LFp{`{LD?W2L?mNjTY6-+$Pb6h$gWUiM?0oXhz7 zqt7xP_CmQbtsG~@XtXKph=Z$rR|k=8UcFprm4aYHWa#*%HS@5(>TE^pIP-WK3ZZLc zR|n2KK5?Jf=N$ZTFyKvcjk)*#theWB;lsbrNzp6&$(-lJU`rZJ*+2-@lp0f~A9SKG z>A;|`U8=0iVN`qQj9Pyttu}Sxgb^@KMfmF1fBtK0R%iV#&bUEyoN%;_&mneBpqrwl z71ErHE~eGd&ph`?Wnt@EW%cSOKGt5J1=Ho>fBn|Rj%a964vzKWDC>T86Z zTJ)>!@>!obSFv)wfu z0iC=yW{Db8_EVP={iU&U5IzYcL=Fcs^;?{L15v8upJV%5;!uCM)7LQ6cH){kG)E&< zyD^r_l}5YfCD&wgus^^u%r^}5RBaz*taA{)hmUZ6^X6To(N)so8-BFDfdsd*ndG~uwQE61-swnoQL|1G-z1IZYeEs!bd^7aR;iQb^FlE03Nw~J% z5jJ*RcCQ>3$O?407mCQwc30_orBQ$VY2P~a_n3nT(T*@WaaK;|u%%eb%fxQgAsfxp zW&}Ty->U~_yG=O_uJQ}djZxbl%daxo-orDNORj61NTVsWW`{YBd zItgd{ZCRHyLe&`;{oaj$Miw4{w|>-&95_dX8QjQyPWI~K^B12#_ZzVcE_xHMFaM3V zaEU3#c`Go%T6aJp=VizbnK8VUZDa6lL_h6}S5+P8hDE;58;?s720HQokNb^7uw7`~ zwrI&%wI)e%Lper_C)zoC9rs6wv_r&K|1ewdo5ZS05i6LS^77tq&}`Cn=8T4@9t3I4 z(c6hx8|%bur^u<2`l&AN4mj#s;5W*pgARJi{OKffF3pFsxhkDSIVOkkKlje-#eH$t zV9y&GWJ`lBt z!DOn<)*YO1u*YW|?UdXPSM0q-*mBjkg^D{_n7E>w<64Dfcx)_N$^W)|`SnXd@?VhT z*q6!^+j7csmVr@-wS^~dXR$x7p58e3VIcSBUc#o0G|~0@eeu7%vPB1c-_O!wNFEWi zkHrPSL+b3quYP#>>ZgDDr$32`L-$`Mm}N)~xr>_@+bIH=cdCcuXUlKrB00D_I@6AoZ52M`gu!+C4fQcpnxy?6V&NWXVNWM(dulTa&EaY2% zWGXVY9^jn(@!ywy#X+~0*opqz-~L~}wZm0dI72kJ#F!c_w}TZRVqMu;4{EiSminPG z^-HxlyGoE<=dlAnW=1Pk{p!nebrL}NU36N%{#M`f;5Q(}M;zsM;?_om93643o>K9m z!0w1K`l(F)kxSzG@$s1_{kA?hI6d%0nS93Ek@sZd;8*=l6!DJok;9==|7Z@Lxyvz} zlgY`;);GTPg^j!I1E9Jk6k9e5BX2BqstPX`{_w>YzxqkOF)Mbmjdt}}u6v#sz`%cA zO_)`5fP{|@eceezVNpEtqr+8fKlzh?)GryFXnYM$dNE+5*z&vYe)s#o`A>f}|Dp)FBtgp42rN`=@M`lt zX?bZw@|>X;edN5~+F%-x0Q|~}fzEPCT?CXi0RJ2Q$qXhd-PrTGeI}NKg$RgOnN(6DM}^wKTRp?%=QN(r95) zoweCm|M+=z?@va`U=rWOuGYC-F1xSpeNKsC$tOA5pLm=5q8@3(uLEh@^v8p0ayGd7 zU+!}F|BE%=r|p-wvLDBp^R$VT$%JY-R@|G<@;zqf+z8lbv+XM4&5m5c>r)`Bwwa>d)9jJ@m zA*R~f3C-0D1cz_=Ui9yO|6M=Aeo>Y-V?=!$B)8W6FFOukWCqLqgKx_{9EpwdPjC4W z0tcEp;US8I^WFh3#K`k%LmHd5XRQOOb4~^fc8*YO_)A^5;fhn#_X`;HsCyfQcTcg* z@l3zbWJ~hW`*f0|hyl0P?|%DETT3{`24MgQ@@dKhp&%>j(j@;e9ec!uuu9q zB%0XPk%#J7s+$nYIV!Q`T)xSN`#G&h@w3#Vx;L%jLpX4-@xVnMGuNyUy)5U;V{5fA-(){LZ?ego>RiIte|F7> zcD`~3hDdPb^8LX7VZ;kwkuMfHD(pC^P5C8s91MDK#tuF1D<>Z~dHUJ2&-e!x-#a{p zf|b0fhj;D!-5>#j4s`u=Uazncqc}4~nR84M2k$q2g^&EhOjM4>laGT@M`I&r4*MmX zO~SAQ%|Y&q4nju}EV(!@`Lanhw~=zY05src&h$<6-us(W^_P!Vele>X2Mse(Kx%le zK7z#Id2Ke@^}pEOvzJ*iG90uI7WgOT^>}T<^6<4B6F*fQpd5%BKkNUx7o)|tc5tlk z+EAAe+gbLu&$OM~!+88f*$BdGv9J3Kz<^g*zac58N3+jKaITX;C)x%T<@&1fg)7)0 zIJsOO7hW4LqUz#zx!mWl;P>}uzK1{6#?}H>Vr-%k^Cax_f#y8$t=;X7|FiKId`+%WAnWer>yaigLnV>TG z3`(lz0B~S;uZLh<2iD1N>_effc8i~}JMpeq#c17LT~FJInj+4f001BWNklo?5KE3b1r!(@jTy*_EmV6}| z16akZSjAHxS)ZPnZMOtu*C@GHSwc(`T~Prd7U<$%xX3{GvWw_ zgGS@N@?~I~IjA=0m7lq(QQAg`nK(UfPTS_@rX1DRXL26rVtw~ymUMTHQs{g|7(~B9B18pfeHA$YVzT4`Glc${JBGRkXH(3 zX;^6uKid}8@#AG>Wwott475oYt22p-vm)yFu%~)EvHN;lr0&nQJ?(aOmTv=5w>;b? zTSH6wtFKn_hOivUufE41O0x;4##B4xHJQEb)22K8Cx>0$i_#8c|FOv?h7gp>oG$6miMW5^g zTl;zlxro30_AkHvGya9y*7DU`d57%wiG+y}eL3%B)joAoG5_2q99Gvo>cj`b;2-qFp9ES1vKm z;Qt#EAvKVM1%-X8#c%1pTs5>Ovh1z zG{PNZOVa=nLhP@3)phapH9X~u+_>0OZk0zf$fI!YoRjFPB@zbWWVgl_UL(J9(JnqG zUmlm6-KQ~nF51d-k{HC6uPA6#UBvz^g zFIP;>eQI$fjIy;4EE|1A_W(Q&e4F`~fn);@oiqpW*$uYm)%zm%mOy=D2&xJ9kfn7~+nY(btwMc#F#y4Ff)UFZmCviAcu+V`zZx^T2ab zsZ6ilTQKXSJ|b2)F2t%ogIDaj5zBydNl7EBoHO`omJ<&w?DKBHw{L&@Hjy(Y#W)*v z{PScWYmQwzU-y^JQrPf_?K%_zZsTL@5nfC3(E$_PiAt`Co%71&-L~KJ)K5&upn%K9 zd0%h1jPJ$LT#EJQd;@sQECCb`;&X@MQ<>SJ7Ek=jTj$a=FKJd*wa;}EZ-uu7f z-hZD|#yrVzIl&U2n}T^uB_#glb)+P@2qv<+srH3Kb+pm{_}pYghIG!Cd)21HZp?{C%EK8O z5sRCHIW7@vqr34$rFp5Hew)*49wyvqmyd_qP3|Y3NQSp)#=qy)KjNm^rRQ=fR&@il zWHbxs9klXm*R(#LIsFnF1u!Co^qaUp>nrwM?!At*(LY@uY-F(IWCHQAN81!~J zy4sP<^5-41ywOenS3LE0`R?(VoBUTFz9G0}t=#+Ce7fJ@7();@2Dhuk$JY z-mbUH0Ki9>e1Bp7sUSgR@TXf?$FKii7{SBzqH(utGc+9519<}O!ztTeu7?|F!= z>yT8WcZG={I1{1(r+fno3i=b2oyscnp}&;bD@*0TH2+;{su!Ca%qHdmvsU5Zr~U>% z6&&kFDp8xbqv9q9n87c9#S5q5n!4CFk2Reft79EMjf*qpQ*y3&~M3L^#{DCC%t(VSrHi)_LeZ z>@DbeEEKDCJ~hD4v{R%8{^re_w|wPr{;^^cSp&^=;+ybo_-m0AE1dZvGwbRM-G2E% zqDrPVxyQd2Z?egmLGY>oS9)H)n2yC>w6|~n>di#OsbR4Trhe3@zim9v#XZZ}QLEFL zzrMGwFhZYYdB87t>qmLD7DE~t#EQ3W4$7sCGgfCf6^ytDoA*R^gyBK+!DS^!shaRN z_WGJ2YVrrUSeKm`?eKw)IF{!B;O?LwJO)7AZ7zn#@?E^>XeEx=ZgT92r?Z0aSVZd7 zsZS@88EDF{rRGRBek|gkO7+J|Ofzi#(O2=^d47OgkWW8ySe)`uzxi0LgP)ASQ$ITR z;y)RG<-aqpmGSimjr^0^!>J}i#dpFMWA$6tNnpLU&X`<+>ksE=OmJ{@aNv7@3IfLZ zg7@YZLpJTP4Ef{%;Z=U?e;|832 z6`tt9!PigYL46PSr=$IDK1tTUT=|Z)+b*A6lvu?|gV*`9T{y8@kdp6ki_IqvD%F}Kkd^TZkSi+#CW1`*&V$AnaO;h|GI+mzv^ zg7^6reF6|_F=W%Uj#Yg3IKCSQmI3J=+;T9?J@)84)>o6zqj~j6jmkIAIm2IES|`W) z-~A9wUCrfh9LBV^wF%E?f(1@FP5UN<`ajzMJ5>*yLTKmQj$OHi?j{0!WbGHwL*RcJ`jP4OiE`<#+$tKpezvZVC7%pr@=P^ew!bypf0d|Mz}1Ur6h))%h1({4en* zJJ?swza!%-S>L@)4=^!pps@2%YjE^1(e-e6M4G|ogSfe}Y+w-cr$N{3#DVvP-W~BZ zA~k%C|8w7tk2}I}9(t6245Eq(w?0LD|L%?C41&LPN9w&J5GB^uwvo?^HaS6EE6x3& z!D%0f6?ulxj+fJm7Z(@#rJ{}e;nUywg@zV(Hjv;RvyUG!de8vvHjS)X zA;b@^5R`D74&d?0xi-;X5{Q1xPyOXk#Nn;yjc43jU*8vj9FtJ9q5MADN*5dD93Hw3 zj);_ueITj_o9LJK)-T7R?pDo1Pe1&f`7Yi#LvaU^)*V>&ubC6`_|<<7v5(AsZNm6E zxFOT)9UHfAZ|HcIxGvqob}BRIRo{a8|meV?RJS6Qe&Y(4B=3Tn=)~t8Ppf zyy}+9Zq9WF^!$2Ze3jirDa#Pgr$#z*@{~r+YzJ8A0roe3vaeku{^rV_da0Q3FgPSVFu%Q`TJMj1EY+X=wQ1G4)fArjvAx9%u9~aO-Z=P6+|g@Y+CjAO8|fS;eo@3zCIw2>6!<%i?!txqIujKso#i*Bs6JpOL-Z-v!~jlRYoU+I_sC0Oox z^{!vG>3acQ{qXx&6OFbhI`eIV{?&CLgN)55-lY2=Y#*|NZImBzngP}WhO&-`xtk058sO^Yzh`}hSDfQU_=Jm7mAjIP>?| z^L7T0wiLT(r*D0pcGqJuYLo38$3B9KFNDUP_~k%-Vpy4bCmXA-IN^KgFr2r8&)&|y zF%4!y4~f%%95%4oS~oGuHGJ3ia9JMgr#gy1&V@NoiT>Gkm0G(z*!z3L-+@{@JouZ* za`eSJ9Uoz$6rAOOMa0Qkq(FMoK=tpgV|8UhYor4(8f4bWS=?0q9a4y538n1MlOvH9NeC`!& z@f(g+)GZG~jXrW=Vp_iHds=%3ytVOHoG$MxH$qFh0T|| zw=7QfbpTC$*tFHtZp(&}b56o5dev=i7XOl8c~Z7bVxs-1-jheu&D!^PxlSU8N8|Nn zPTL>UZrOg}8NNaueeF0tVh&C-sLmO0v6G9#=FG)7Id_FEZk7VJ-9&1vHenj5zWtwD z8Yd22QSTT;o-2L5$0j;^TOIl6OA8ws{Xzpk{P?L^fSe$T6gH^wPksCcDExoK51;tY zKp#H7qA-)>oqT>NZ}Jgh{i-e>&L=(N)FkoQWY_D5+qLl}KA%9^G3xeIa+#k-G832L z*N4HC*BSV9A7O(4RmKh8p|Z4tMGgJi9c~?CZBS zY1JCrQl~)<+3eWSpZaeo3O2lj3&PhK)C$@5#%KIHzp>=C1 zi}N(L*3DqAKh8<)mFp1;wx>>@vT40ME%Lu#QZK>Ku%16!d~<)z!IkTYt3H3m%iopH zdJ@%g#y|Os{w3y2a;M&P((}03#p<^0Q+${@;h>jL%w6J@*J65{YgsPfT>Jc%>aTzG ztAF(?l~@s9{GLk-4m=PN3&E4mp zfARd)tM6a=&pkio9YWw=8#>9Yr5x58zezkInH?25HgFgo%bw_}!{71IUX+?ZgdF4E ztTGq*(ca{o-5!`^JD`&KbDsoEvaPYCE**UZ{F}d`u+ZLU%HbNF!Ir{D|T&djwNeAlC;8OM!dBtzLi&*y8h(SJoRIf zmP)wVk`|XeG3Gq(@#|g~i+C3o#Z5RNiFRjVJFuK_8oM@vKCBNXN)GO{Z3N9lQLEA8M9s!r7>hsA zMPgmKoNOXu6U|L-s@ddRyC3ma`_MTNsuwTKnRy&+J)C(GTTDA@$W{-~`#OrPk5+Wt zv`>ib-^91vqo7IgC0fk;w?V3ji+OctAGw>u&REBGbw}ysj<1fd?1LL_Vz-UsD0W^K zd+{(1#sUi3d&Uvl%}eTe<)2Dx_(MN^SbF;)u(`>&41RpZ>GTnH!KVxLorI&NqF<|j z$9D%lee~*w?_T)8a{pL6uemr5EZ(8)u&Y==Hz1A^`3m!lkyVX*G`je;uYucu;RgZ6 zzwa61-aZiS^81|PrvZ5m*bW>83=w$c=k=iubQf@v<@4DJKWom+Fak@Z4C?Sk%>zPK5du{nR+@Lryt5>pQ1< zJoTe?Y$~_Ss>8YX;j1qOt5uY?YbTDLcWFM=waMf{?b@q20QDhG2dOdCw$aq5rYG0a z=IYlzbn0MnVj{OL^`p{VZPuKFfv!5aB=DXX1nh)wi2KZ6IByoxug zni!3b@o>dAz=U}4&|F>`dYJb0sB(^CLA`bNO6HuK-LW35LBT3F}vUrYtKY+T|~myahlVVLpjhXMVl zuId(aN)%&l5acVx1=fJX@g<>top}HEV6D0}8D9OtPc#B8=ir7ARgup?@hnQ!RnPP6 z=T>1cVt#b|Hg?gZR=ei0gCLz@2)3b;ki?gX#N?+VIdD;w^_<5H*uuZSnF%HS)}!{eM<<%o*G_Kr(iBQe$K|^NQH1sB@xdoXwXy!dp5xVZ7Hn*lxIHpr zK93ye+W4Xn<-@PE>hmf8P6CRiT9v%XsCwDDmT=8J@X(|9;9q3{IqhM$-hCYR9*V5@ z$JK&~TAIjGl;qxHNgf*Wh95yfMG|Ez&ibvojn#6aH{wJ|;CZ^YRLQ=aWyYrp3U ztlYs{d#@IqX|0z}vnVe+b0-Yz7EjySi@*;yIluZ1J!{8qxW(tn*jr*d^;$H()-88s z=B0lUjbW6^dG%MT%Bn#;m5Doso~GuIi`=-7%LS8I89(PaCcfnpomdMc7W_7^+NwJ; zkHy~eBCr1-57NpG;2dmx=e}6tBzNpB>woo>o%N?5>%&jJ$wQc_ew50OQ^CikIY?ia zyslPp<hhOe|B)m+yX-j|fg3|C|i3k3Su2d*m_FM;{8Btm4!|Zx;Z{$Ox-VB6`eXzJ;+| zmKCSBuWw$5;<;`q^^vTzi4 z2W@OrtAqOH_`)N6gFHA#-86deYF}=ci+hnDxI;}1T}CeU_^+i6>jbsA5N+@$0X+-A zRBgSE7thRH;%=3m)!^?SlN!&`B|~gf<_cbgv{VkW3BPoQ^51wv%QjpdYl4S5nYg1s zodRF zdD(vS`HL@}i%O-h7WnNw{C5L`O3`C7%EaQo;wwVE{ zw!i)D|M^>k%}X28$kwHq4G?pK3CuyIfv17=E!L&3oR#(J5@Ruo1YZqQ(j0kSgNnDkBzIk1)%{%h& zn0-LY_ZnB)qYmpQ{6L+YMp&~RUghCozB)-n0j$z1#{D`q+J54%Z(C^nDLZqZdYy~2 z-MxGLtV{&M-L}$?Nkq!;-~HYH^S3Wv{Mi>ux1gI(=;niRb&)DDER4HHz9vxIh{6$` zd`5>`@_|oHP4c~(j&pQU{Cwa}JNddI?mBXAxELl=+U5m}+%VLUTXg)cp@+^K1`%H4 zNOQlnL_GBXilN7~jJ_vRHbI`QcB^1o2@tZ%V*{(Sj%*vM`ffK#qjH=_BN*)v;WOt; zUC?$w!(t%lBxUvnIyTiaP1b=XSB~9FVteN}001BWNklaW8=Byj9JNj zZq14OsY!L~k35i@*q9G0)K3Z6 z#-mZlv-3&)@J6~FO`J0?A{bOP2TI|@q^~7H?#Vm-?p}Sd+Wco;+peB(S^xTrp4naK zw1?PIx#HKloZEH@Nz?EWz}U}0bm$It^rMyWGk_6j8XSFwlQ`j`+F~!z`YgvIFGURC zfiX_)LCLIsJU;vt%i53_d7wCMKX2~A%s_wn@~fB6p8uQ`xsyVZQ!Ss-kyLpSkWtAdg~m?)4Ibvt0*EKp{eDS41*J*Re1Y-Ie3|(ew`st(n-0 zzqt^r@dG*Sq1v{)I9jwLrL?V*S7x6 z{q&dMs}vwSq}|FhCU)#8=KbKX^0vo{@A*=Q@y0wS;iK$|tDUmtXRJAvL*gn2Wb4z`15meQm#AB154wwmZLJ4v+4b1dm35GZ$C%r1;x9hR)O!Cm zR_R)z8*K2j2X}plam0eO}F7h;WgT08j9M011T$p7^(U#g^yG3Is6<%Q9lb2S<3$m?%PrTLVPFeUOiisjf6iiSjPetQ0e zf6n=PdZ?iH>;3-$P8oNkalf}#Y`UAQ6umvj*u`gzd{m${<8fP(FwIgHp+Pi){EhLw&6M9PJ z&VAz8WVxeMqg>oAd9M)r;rZIzxDJaC*R{zUTf%o9yV#~assGJ$DDk_q<$y1@)R><3 zo8SEYc`M$@N|t|&i*`9@4LO;4DsL_=%((J51^zsN@(FMJy?*`&p?G?y@em;e5_mcB z5F+L}0=&2kLBmwGUN|P`o4E-v*G(UE_;YU<1LqLE9ppklW0qv*@PSP9vxQQ|=?RHl zWnO&E-A`Bb!P1;|Hh$t+RKMu8m>z&Ge8m}8OR#NhbDeSXNzMfT35q`87rFi50haZT z{TmPaa`T4v7=Bt?| z+?ybB`Jm&Do`G$R!@BtWd_6_@QU4bJ&C}wDv+h@xsq`8qnua3Z8o}N=+XEvzvrBjb zpI0=kfX!(cuNVuLLVhmJC(R=DUMRX3mz0tNItmW<@Cly;>()(npu)krr%b_$ ztxbh-H?SnK-4Ej%x1~1LY`cz8?L;Al>ju_pr~`qhe{mu%ilGZcrxUP{XE^QUS4<>`^Jbb=P}$~{q{h5?985Z zx-mDJwnn@g;7vq15{!pF8ug91@NoSkEjJz==!1Vh`CoK78B~OjYaUkQpARf5h}iMS z;CWgQqSE2WRf-P=goQrq{3rk8pBhlkEXyh8va&bG!Oj4RjXJ^Uv$$ttS3e`t= zFj#fttOjO9-RkplA&~9rgtP5|durqRsb8u+>xM0Tp1!1pWCaTYx<56REV^Wi+0hBi zp2n-)^cd#+b3B;3jlcBZ(7Xq*Ke;eY)7ygo@(=#t-}@Vp_~?I|XY&~ui;D)|to=M< zKYGW|pA>vyQ@Z&KQhXi=eBoqnct_x=0FA%ia}M^BD!+2#T9ZIn7SsDc$+%gnM+ux9 zRzOm=IraoTn_bq}Edc#cHHxFW4d9ZS3#tvWpqjml;n-kNY%UBWz-g&w9S_@>B;LE} zE|*}=CcU+nS3so7CNw0`G9Z9kHYs?UYakv7?xqS)e-1%rm`2A50e9*-`>#$C*C!lE zl6c$bC2L=g1?;!3s6*%&AMDuTud#4iu}_Y+yS6aBu>!aa4sU@4_u;sl(Oc|;%w!v^ z<&vDkL}Ty{>(<#A$kUR?<;RMxj%P0zL$T$%3&?V&Km5MRE!c_kG64=BnlsYy#X0M7 z{y;5ytZBhb+`RRfeWrU_8(6P1N>crKUA}Z@T=*J4+7CZ|r%&F3H7>UXdW}Gz@U5kB{!DDh_h$x~Q3mO^3} zd=#sUEEtPT&xyGvn_}56U{f~4YDqv0;&5@A4|1zH&^lj!k`TjMQyY))#3TB|V+}mV zfzWD^$xrj?&D+`-vBuQv%Et#I*7oY5X4}dQgVi|8b>qczFZ;-YMXtkrF-%=p!;ld# zu=~@0$Uc0eE|EM3wzwB$EAYiw5^T^gBF!ViV_XloX#B zgm6*ZF4*JD%CkdbsNbU-vaYY!2kvHJLAVtOxkEOTNifMv}Qn zZ<%>Wf;o9l9#M}trFI&8St1fm5$cQEg$#P}VjqeeNV^lq+a33Pm zpb%<4MC-*9h6_|>iV5vq)l0~ZjFp!zF88zG#7ohgtNHexo^aD8FD=FCg*8R%gr&I86U0R3K$}CW(*3ucNwtHO3r#jSA*dGs z1Hm2pZMx4Gi6>j~#m}y(FZc+sN#6B!nk;?lLZ6r@_}#o5i2re7N`72%U_$KJd)*0r z{1_ewnJq_fE3a=7m@Sat0v7DRPEQ|hj@dN_-?&nx34i%gM?oF zh<+Qk0*HTky4J)q->t*t9&hxkaZI;H>!XkLU;gDk`IpNF2~W|Z!HU$z-QNqyf8$Mf zbQo$*zPvzhNAicaw7CGp%4h=9g8&@jyx#wYwC^dR455_K5Bq0G@3!POUynfv*p}dt zVL?${SjmcDf3gW2z7jnzKUzxlmFH;d8D4J_p)dt>T@zTUF^NNfix=eQ&1hj#ffr%p)2ap}y)_wpY+*elrU z@tf|8he-}rsN>nb{3$xW#{R-*_Tu)9zq-qR_+ucXY8YzH+kSk!Zz_wKXrM6{qF^6&R(K$xq`@}N@@zta= zj{}Qcld>%QVoEH2lY{-GII&RsgyJNPjk|{@*x1w3H(t%HIJ#j+x|(?A;JtSHB7i_3 z?aZn7*zonH>`9dknt)|hfX52fpu`lqV0Og*WY z>%=-S&IR)$uhx;!wsCD2Q$q2>{%xzFmjs2N{YOC>rcNQXINLAtGbRV!B3o|p19Cp$ zUv84aG5aI7Cq*<8EhZS?`zZcH)~5h{o=Pr}?#*>)u5;D@f74uKyKEKbC%&~CJLh#! zbdRqO{^@l6R(IOi|8)LSFC6@b1u!2HEXqI*{_1c3Q?N5XgdvvrVp}$gpMKi1bQ}^U9?iYDIv?Z0Gxb654dntLPEMZM|KX(Q0)Ylz zI3B)>fyT|I#J90yARi~j-sG5sCZeZF_{iT)Z$Dc$K0L|?{8Pgu5eAN$zE4wx4=G0{!`_Og6(XpWW4ZU&1ZD~r&(K80{x;f`WbdvJ*Z7+d(&!oyS*8{-I0WfQ9dkqbwyG zyfLU9i$B}YIY-;NR@l_m2-y#phsen!wXv7zzI*2K$MAB3dJ_AYeOZKa_hz6C2F6*( zlU1CM2QN=&711?{ef-{Sm;r?K91+oDRz3)NkDJq zTLd3{rI242kd@x!*Ta0w0JJHKBO~3z^EU%Le~4Im{J1b+t{4CHa6?}D*vOt?aVcfq_NfR}R-9mB8=tPy;_acu@Lc^9KOrS?|Y;}$R6hj;uwc;iwIW-M&gFVM(W?Y zpJ7sJOv;rDXZXlYKW=n4L`1TMmky@uHLKB$nLB~NEbsWeu@=VeLjPQALZ@CQf759V ztxFsp1^oW+|GR$!96pT?F}@Fg-YKl^_1|lL`d7c0XglCYb@)61U)Sd^1o<@|2j-sx z`HzO=i;+xZto(V-K$Dc)Z3L5;i-rqg2CRpJ5KR|}6eXg%GjVIdHuI_>IroR-Q5N*~mhW8C!Z47TMoQO4xHp%L}}Yc(~FYZu)`HcZa43&)1BkAI%J%SbjCh%_*s2l8RNz0 zi+0ROF`>kH?0RYJB{WYC`Ct%t)L|%zN^Xq^{qVc*IpdYFkn{Ln?u}v$0%)!qYy8<7 z$*=xPlVaH9geMdM=tC8PDQ@Bf^b=YX^g~P0*d%M_QVe;SE0iPurHw#r$M|}k}Z9!TGq<^~M)tP3c%*MK^WgVQ3P-eUf_pRnBxI@D(I%uzD*XZ<}- zqo1}2L4);PId9)NUW1k!2GGdU>L{;BB{yF@BIT#`}05lhkr&dJa;jZw*DW<5zhvsG)~|76^VRVv4D$8+K>G1VsL>0TD2|yq`izZh&kKv+=y7i;Zu1V&1yatA+1yEb3wW z!XsQr5FZ`zhnB42ck?dy$`?00anX-+gIw!qe~{X!p@EqP&%vgbeNqbs;?xxL;J_y~ z$ZX))jGfDrw%6RN&R zaiB+KTtibI*O)a1EqgC}7W>GKXHv>kurEJb2lSXTIQvFeZXbRCB8X~<@R<1)P$Pvb1=H8B^(o$^3D#ZF_q7A^G4I+jxBkXsnud&ZeC~bA%h<$Jd-FUw;pZyc z4%;cBjGJxq@DPi({ka^&vx%oe0;`4)3U}C8yLTwIG+wWhB^E z>XQ%1wM*{F1z1|@A5O7LU52~fZwYQK6iQDE^b)?GrmrGJDduuXt~NM;YBQ>Y$W$`FebA2=sYbUXto*w4`Z+c=S$FtUfZb&D@0i@+Ofz2l&FSrfoSP1^W9ViX6{;kE zAjG$T>z8$BWF_zJ>F)zHZMXspotD=G`7gkzxdk9|J%rE|U=kw#>7V|CKY4f{Qftv% zoNP`PR&c|if5+RzFV@DfBP#w-cQ%^BuQF!lY>^HrHa6fSetqqfg|@W|6D%1H;Pl5I zNn>MoPORahue-T}Wjik~&RD?Iy~mCh{qnV)iyV%rRrwO%?w^|pEc-hI_0Q7}M{A6R zw_PF3WA#mZ01Wcz7`DF4sr5y(wIf$!ae;75I%N0Z`t|{SZnqQ_+`K;Wl%0KByHmsZ z6X(>=GU6t_j>e|SwlODU<%^sd4yTF7VtLO^zx^azImPoh9S$FSu3DqT3=Vw8dwuGk zI;^(nzWeT<{+8vZ37=>1ccGA3lS6sQ?AhlhKb`_~_d=(EeMR8mKkooEJ81{t68X=9 z38c3U8V>o(V+(=RT}+3o$IrUDuK+hDmQp@?FH)c-1|O=7I%XtpSpW*9n=+g1yb66t z6wH3%8()oG(ghOr6wrA>$;N3*2QDMY?XeQVh&g9G`YMj zGKOXLUtOXj^8AVQOE}3%L8RB?Z@+-Q6u1cB4~~7o&~*VU-Ex2%rwP{!tMxYE$9w-W zL!-w30px!M`VqiLuMm1!;JojT4JkV)0|6UD(rqyNcTpn4UW?Ii!zVFm`U_a>JkSCA zTbvb8KDe9Hus|7D2DuOx86|jQNzYHcgs zF>`Moy_7`7nbv%~^qwZy*%CT73}mIamWhLHPCfx7Prz-XW9l6Y^0CA<0T0O|GneJj zK5&Y4FAT&zaa!N;{oG4%bQnb;udPM*kFb*iTrxiE1T*In-8mMI`PE|^3;UCO1BWZR zRD_)M6+gU)H)?j|-+@I^9M>g&I-mV|onN2h?RhY`lSe>6S_b~wE}q}({xU^!F9$U} z-jMN_Iy|hj02-e6}_dRJ`c-hdhkcs((K@%Qqu-e)IqlB=H z$1;G|3bygtoQ?yj@B%P4oM_F>I!+=sV4F6G?U0bCZEKIo=IHEnITQP_wEvwi0(oZ> z%D}O75Nu95>KQ$uTIaG;BoP=+_|Dcdsh+(6*^3o(3}QpfvDFw`xXwT3pRrwz`itueg;ay)TtVTBIxNY2#Tbpp$<%iCsgs61;}k^Huo-nwA3<~1>HxwxkAdu

  • Eknmb}OItqfMwB)3d9RTf`APbhHS3&2;cn}OH& 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

    _V_y{AU^opdVo6(fCKmqxQYXf_-{XJ|KR@T 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

    3<_slx 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 Jrm(Zw{|6}zbHo4u literal 0 HcmV?d00001 diff --git a/Content/Materials/M_VAT_Rigid.uasset b/Content/Materials/M_VAT_Rigid.uasset new file mode 100644 index 0000000000000000000000000000000000000000..766007310335ff58d08c6c5c32beec8b8a8c1651 GIT binary patch literal 102889 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 literal 0 HcmV?d00001 diff --git a/Content/Materials/M_VAT_Soft.uasset b/Content/Materials/M_VAT_Soft.uasset new file mode 100644 index 0000000000000000000000000000000000000000..aa075655545243512981e3e3286b00d6f12cd212 GIT binary patch literal 101786 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

    Ri-rc))L^08vKZ48{(=_C!H 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

    Wc@rw-es55}HFdu%To6(jz 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 literal 0 HcmV?d00001 diff --git a/Content/Materials/M_VAT_Sprite.uasset b/Content/Materials/M_VAT_Sprite.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0809fc5c1f350ae5dab6e76411ce02f4c89cef8b GIT binary patch literal 91689 zcmbSy1z42b*7gw60)o;hsdRU@(v2WB3x7#O`Of5*P<8`AmEI2b>ESe>7ewU7 z0V-yNGs6T0Upm%rlEQU&KWiq}tI+4kqEf#a@T)K(gzLnd&ij3(OQdFRN5wc&krUFu zbz2rCOKXy^4lpM;G9_wdS?|Ji7{Umk8}O&Hoh=0Z0D*$wp9BODh#LNsB7eXb?VJVq(&g;bu~YSVK%4AuQai zoLoN|fIy_jKL4o!yxWgL)RPNL2lNrnPiGF;_`ATj;tH5A1zg1@V+*ykh5UM`Lmcg# zVPJ?Oo20b~_z6gop8JaFzqHb^uylfGI5?ZYAes<2CubN0gw-3aBl>^p!uki6EnK!Y zvj4`SZD;Ll195^`f*sjp?Vyep5Qr0-l8F-pW@%#W$fju|qiqD&YXEv9Srcn(J9Bd* zb}mpl$m*l=zp%+V+k%}e?Q9)?P21?2<1c`n6X3OAlY&9u^qLlyFf$dHlZ74B&ep_Q z8v=H+gMr9h0^4Q&zgRV4Cbo{|b}$<*H!iptn57%Yog6{5`hAgXz`*3~oXsq4 zEr0pTzpWUXlB|)ogr<>(Jq+#>ppL@tqV)f{nz&jgtUtWU6Um3xyMF*qeZV z?LMf^^mV}xMt3L3UyLitERDbbrL0XH9YId6Zc#s^A5$xgfm{g^0~l8HU2E#WCC zkaT`%u6I|#*526(L^Q|G0px2bc*HnKf?nA=i;@B2e~FDbj~YK!5`(Y=5xz(-`vEa8#)Yr7ml8sDu_(f(5Ff}#X2k^YwTK=iapWQ z*~1`?j_`~ir==hbQl8CufeUo|OZ{^lsJ03afj$0*Ucn4RbmqqeIOE?4Jd}W?V|skw z5m?oK0`NYLH8kp2zd?>5H`2sbVA*~|;a?khmOFES4rqU+Ef8$KCK!IQ{H6Gt{pUbq z0?2LyzI5H;Z~WJ=mhCu&fr(!?_|eS~gdgZA08IQ^{j;?rh&2s%2KbH~%+A>!1UdnC z0DJaBQZ})HfUFk9bAg5Vq0z8|IjO=d?ciy^9b{A+MhC<`?FEsGcr zP2B_*YgJ9exIqk^9Mk|*4z$cyNh)Jr1yXJ0zgMlIY;ga zkZ!NhU&H*-Q+}~o{D(h4oJ^!moJ>GVz7;V*-2Q$BqNJUA0!|9V$X^cWw~zJ!aL6AQ z%>$&in?Pp$d5D9&Q)GI9c_=&E{GK@d&4br~c>T4i*Wn^DS-uYBGZpwz07Tnd8PkuD z`X&GH`RC5f#DIxkkK|t#G)kx`jWm1^4AwUBBYqNgjh#!kq+Ci-e zEZSetkI^X7R{prm|D{!NfxuiWA+B(Lh2NUsMnN%|CFsC1soFVO0;8b1zI$RsdNTbm z_78xH6VxV(kORb$I>a0TgV=&0X1_WCq2vhS(oBT_WBL6?Ro0RM{;7rgCOjPhQ2+r= zwZ_c?p7Ap#uKnX@QiB?Cf1&z;L2TjIMrRvSTN6ubz-e?Woh;yK(FQ(Z7YOK;GVEyTqzCc|RT&31Tf@%W={k&@EI>XPlNo?F{gz)N&Vs0a^t6XS%s|ru zm$ks={=9=x0mr&DGe{HIObsXaJr;iIn7yfs1#+Z@6YMXS6l#Le{xIkSa|ZvNjM+iS z=fElQAFtK@S{s&tD+4PDY7!8)1Pop8h0b8y6>v)YlHu`uc_BUsnEs{tvD-57)Vjb{ z{?h%mJD`Dw<%~e&{sQ2`g_zjDx8R580r0EhVr{*HOq7{3S1{^2{&Eo06}AV2BY!K}@w{&IE| zb8|0!hgXY z{1@!uf58<03#Rm6Fy;S%t-$B^{@sI{MY$iuhpM0Aaelax{cdU5wr_mPauGR@=t6Bjy?Yg z{TsFo$DDs*m+<

    W_!Wzw}A`H5lN9$^TM-R=@{N`5PzD_u^l4KpP;gPT(J4y*Pi- zXUYDmz;!_FZwW5^q5Q9Q&ft9kJD9%+Kg<2i{Qqm`{2%iHasj{v*s%d_1Mo5@|F<1L z{#^nzzn4GF=jBXC!ifMoAAb=l>HWlHcduIj{ek=qybkLB9_pX>ndGywksd>Pn(;Q9^l17{y#3-}HQ91p;`1I!VKt$jGr0sQj~{yBty z_TV4j@_0Sh>nFemU;xiG2fzp501sdTuzM{7b-?zud_9(HKHv$AVF}K8U0?nW^~GQM z>%Q=AKMElKRephAs1QKGs>*UVF-U-6-c*p6R==JN8ZZe1Se`iuL1*9HjZ2 z6W8TEJbg)caaKDzs#7T)kF-WntD$vPJXYBRn&}A=7&ol0w6<%CUTwKRDx|?=JbZBU z+3j|H|JHP^t80Xc3YfRKG~@vXYENDlje;V7xWN0=y*G%3IV*;fs3H&Mo*ipz9~##S z*mhS?2<$(t!HFDf5W70p5Fmt&)Otl)oMN1GvX)7;pDUxvLJ<`94EJPg+cfrHt?7(n z-sWE_$($%$nP-O^ zqqW6GVsF_~#Z~=}x0Kw1$eKLdy|ld!@=!MRw!1!rI=I!>$>$)6X0B-G=1YE;;Vdp@ z`o|or?YG2v`dCh|44u-;!5SE5x%IfFO&gIxL9W|N#@f`o6eGar|fp` zlog?OlX|usiu-YK7fss%QcNfzUXxUjuEVm;;gh{jTPHhFA(02Dde%bmSFd=E1g#=!hE=P$skV+|&>jlv`m-GAzS zX^OfV#xGrzLNX2w>&<313`==8-*8#V&S8`^MY$j&tSHmG3+-dH#UP8G=S9eiB4Q(U zu0_@eM4+cY&&o!<;VVwJ8|xA0=#(vUek1`+Q6w`nC8`_!T72teK`Bw_mM%$JrJHcW zJ=F(Aqk9TaQ+m#ONWP6dMVEA?h%e)X80U_q2hdkVN=fch7%N&?D3L=SplXJw-7h`4 z+`c&be)Kuzo*v@qesV&(R3=NkItJsO#ijw1+^Y3=nGx%i&-f4(GyayH**e*tKBKH70`ud?wmF@dn0#Dr)#fjcG9XOk zlnqCJAi%lX%G!~wDB2Ue=u|mL!&gh%waDacrkt)MR(G#G6u)wVayiZCWO;iraABC1 zQqw84?M+qbxC~DC$aiE7;=+NW5z)6($+wkj7!WHpKz!pBt*Xns3PSNjH>HE`;@2QY zo7ddeQ!#$$6q$z} z+J+9x1x{7qEVnwjN6PLF&_qEwOZC(Ho9MPH^szG{9agq%+E*_qG*$wqZZW(fq2wF< zV2UNXL@Wetb~z=p-DC1pt~uaylQ7}Cu>Y=h#OL#90m(TjD{-75LP){EvST~WsrwM7 zRz0;wp;*gR&UxyWE$DF{H3uzuJH4&8{k$5%oHK zRZd$!saz#EW`k&#LMMk}qP-h94$DR2%wId_S{@3};;Y=1c+qzkH!^_FtO5^bhWJP(>XNYMu#{o-Q&ih#-!)@Ss!B%NR@{k3$fjQ^KeS= zNLWEr!RN5>foJ}#n>1B^ZCspyYTOP-8YRX!~$ zNuGW?QI+=VoiVmFv2lluarzh${hah2SHTWuwYZuq)gmr0O>*S4*tNQ`7oe3*wycMi zXQk7JR77`X9+Vaioqvnmb6CUf$A{Q^;v#5jwkz= z5&Lj!4M~th|2y+WJ?&yAl@vXZJ=&L66};n!hR$>o?C@GM=D3MFLOtS9r*mqMDWXR< z1ayoamP{FBwiQJ*rX7x!)2Kc!F7P*#HM0qiMEg)64cqPFdh=6LGTGS)BIHH#YY-dZ z2X?o9fhbbnYY9o~4zFIv^Ks%)3p6_=5E_Z$x7>bga$=U8laj#C2PU5CelzPIsIudv zP;-nvX|36CuH{4gWYaXwAyPDiEN14l6z3wZzk zbLky*K&eL6yuHR*H|rFW&24Zene!@sYWQKc~5wUyf6Ajzb5wWH5 z$~lH}d1E^7SEU72Ew(l#TArRm3{U+NEXzE;Q}uQuL`~lmG**k* z=voRQKP2}2{y}WW%zFxn|8|qt!AByLq-a8{Q{~#w`|I+gFiGvybv?v&?DO_$vcwD zL^nhC=5llh6iW1n%07RUmwUf0IF9s)_<`!DQkRx^!O6op$)HoTjtEi#0^;1xg`*lY z?a@8rE(%!8-pjyC(gZ($)O<{8$T^LB=_^&`11ui8N|CZ?4K(uaJwr?sm^`pM>e$4a zkK#DrHTTe}&4VD!+2mOIM{Z-B3k->s=~<%pLYy8BMLu@mZ$iKn zW!G2E%%miH$!H>e-vL;-&ggIezAb9ZnAKEa3T8KL_$HOHTz9Q(U+ZKnUJG^m&$RT^2YWJ1JMq)1i{E?|y5_=ext z8e}y``v&)`;>!%&cNt%yeNPK2k@Rj+Qp%yI#6otdhXXd_*(j$S&~WSL9={)DlsJ!i zHxzbh7zS>E-gCijLDDz4DwJso4?sYoccZrzK*8#IM$$kmtU#ngch&#Q?qsw!&HJ*g zUe}eoyVzSQ1UCqFuf|C%#W?(v`h+>C8&q_{yk6-K}}BY`#kfS|Q&m>RK^=V|nu~ zf>~pvh~Ijbjjo?(`z$l|;QGc53t(6uJG3Kf>9J;w17*vin%20IwlgERwjhs37k&UO?^GQc>kgyUu$8 zGomrC*;%xquyI}_7(3-G1l6G({5V=3-@EvdOxi`cKyNBzg~jL7hg1a%qrGPHASO&M z%zJ(#m=70gIhtfmpHE@wD)0`*1tcHzW~i9@3QhWJJwE)zm%xl@)Zl%K;KQ1JFY{T= zVdtSVxxi-8AXU=E*>Mg z+?~e}@^cVcG=5p#UyZ+O>0#2wYs$~LTK9oI=L@R8$_C58;9(--7K)M!^IL5Fgt0FT zl|4iTf}gA8a>=(-P$oc(dk9pTk%VEY102xuS6oC-+m#+e%I%$?ceL%zpfb@X0`w~d z3eSbo5F*QcmtT0eSgpFuK_3dg6g3vR2vo;&7MRSvno28JN|h@Ox)VJ8nM6rbepKJ& zu-~oJ$#aFtjt6#cytLfDjRz|4pzXn4X57;A5KXzjUT8jukaF{`ZcRXIuE!QwGhb7VeaZ$`KssL5v%V#5{S6K2A{eCTO_JfkeT6)=5grl{=!T<#j9ZvnH@iYa_0_?SiUOlAs}r zN_mfcHQ9h7vzuwp%jk{AYWDKSZH;SliR+fbyUnbzj%9bNvnd999M?8j8PX62E>J7w z3}>+9R%zSPyucMVJ;X<#OOEFB4A}K@QRp#TtaSxG$}dK4T`_-HwQES!TpS!3jWAX7 zdVGL~i#s1|0C~RDgfAUIKcdxICDT?rJ)h)JkyXT=ap=_lM%L_QFRcNA%kGpbp0c_K zbaxxeK*6Z1$WHvj-IPv#A^5U3v+ZTjqBJjJot!HsqD!RA@IQqG-^w;9u2*ZS%70zX z@73VifF>&nmV_`6_>RdL*FYyfGItR*6V}9nnjes=o-mf5)#BN#v%}h8&BIqeb^ohTd$sEX?Qf+<~1C zg38UO+g`St*jRs}$wkxWiZFty!1)49?4%oXGrZG*S3in?UI^=aEn7BT`LrNOsFCI& zshe=z`YQ#E{!HBFuK7GU->|5Hg$B-=wddvw29cG`-El8MY6S9_Z|9* zsm)k2=K|mTJmXtR_}B$B6)Sxb=Ym$&cxd+J;Hb!I-eFNA*%RcDeYw(Pw3Ag`ScCXg z5MT8HPSw-9dBPW4E?qo&%4KgUOEZtWd~%{m2);R`^R&+AruiKCVeuKSWwqvN(|Dyi zJn`<4eb22+^m_L>(G4#5_vqP+L#4^|OC+dEo!=x0Jn}L7TN#Un+fn_ivE*m7DcqG` zz2owo5v1QE^`@Q0Z$W?4o*09%n0LC7)hJrt3+8=dK&GVB#6cieMcY2rMAHpAE?^;6 zrY4<^^vM4jcSlc)kIFG|EzYj0)<0#dS2}`=-c5t-mI?j9tA4Kh7Yi7Jrq2sqX;*b= z>X}n-^nZQ37@;&abvPj?x^P?6#uSHwyVatp;7;PHE&CcI^HXN#gHo7AY7s5vcrt`! z(dE8Z4Oldq^Yx9VXOft(-EmRpsAzH?s*l{(A4JvUh4^EiVUBLP8=^IA;?6j=SCV&; z&_Am`7S(EejCYxfbAu`Jv)slfT!)4BfW=c{*+cY{&7f}1kbC+kb75?q5_k==uV_ND ztv)5eI)*8BhT?6EtQa4jr{u+**R&wbE=npNlnGrzGA0(0ls#9LKDsb{Kt-X{xrJ$8 zxr(&iiYQbo8*8#oWa5#U99pJH>J(~R#sV?VyHkgB=xZ*qpVmVTsSNl#PXp2@87KfoQ;5FTC|LQmPO!{J*=Aj3BHru^nzNWR0 zh*k=%ebAUxI&~Yd!BV#9XM}rubgy?A7xBFgY>XEAJtI4L7x2l1f;$vl{0vYO2iEbw z>sj*@(4Q7^WuQxtxRV$2xoPEIT^0pzBXK|L?TOKd&042fbW>Tda2iD+L|PYt8cb;M zeLnV6K_y3qsmR1?>kxayW=$VCFO@Aktx0*_GB;PNLj$Z`AbtTH|PfjKYCLP{BP%k~hhqcE+;z2wXu+v>auFC;Q6}5d(eS;S12+ zO^2mOMAY?IwaoauvajA6VE%0TRUKPo4=7{-x02} zgU5}!c8_&N!hHz&xtn&sTQe%#p5muL$0-(K(&c#X;D(gO0V?4=o2t)4H_grP)5g%1 z##1)F+mN2&T6gJ>|e^+&mVrG#fu|#TEgnn z;}3tq)OYHjpg5*RPeb``x`B-f?WASAe)Os`Ek!o%YSh4mR+yxjw`|P&sz>OX(`_5J zm{zE)+kKwnGetbbiB)4Wh49jnjqiE2Ved`-2=&(7G9V_hru5+xCztJsFN2d`>(rHX zc6o-etLuuY?X!t4$;_GIQelf{0fl{4{&N>&TG-2M1r9DxtDOr?1xDeX# z742R1fcMUEIz`%R5{B^jhY!}uFpn?N`lACu#n}&&+ttpgi#qqPnNBUk7&MifT^6zI zq~9*NaG00z>k*~cs_{OPAxO+`m#9j^x=YH5i7ubeXbQ!#8n5SXGbSV$Jrq}m|NjC) z6ceO`b?XpI$LKmsBHGic=RRfM3f>(xze41tsMHOurfz(bA)L>hbI`P!IDDvXFsl^* z3XG2WaY%2DDwvd+Kq$w~TRWN8QJuGA%2;w{4^gMA!dVA*rAa!6BdRqLQf5pS~RIeyTA&acVlNx@T6t>UeeYrt>D*L63vqgt% z{`Qs4_3AvZzQFyeUzz}m0Egg4mW`@lf$-o>bCk&i9x^+INU#0z9+b_8i}Sf;1yw$R z3m&dF_Q)SH&y?lsED!8p2D_z6oppU4R#z;kq9I9MdX9OqD!MnPqS}VoXsI%DxoJGPuL>JH zi6(Ef)f3zr4L9}-+1b^wmDuI#j5khj~8j;WOz}K{_O795ZoP?985v zhxH+E&wPkL!lOFg8tJC#38Mpd6UpJ>Kj+1hOSV_FtFj+Tc!=1kJ7qU6xs8$DxMxN# z)E?VpKmEb}{NTiqf=dvwsm5o3Sh3&C%haFo?fa20Rb#jXUmI8%w%rN(d`T@--9p<3 zEt9MI3kPUOPd;+3Hk!u7lA7<`5STcRF&}!IpwZA*C7jG>bntDh1nsNHq&$HO&1q-) z1U{&J4au%@t8u>>R%=AlQ{CM<1JgxYWrAT;XWr@Y+$s{CYD5kfs7;kM^v-9xK{M!mMp9OgP2ltV`4tZ5bTlRA|cZBtTplVkak>M1F z8pA+(Z)4O3!ywd{Cj3KlV{jU8@Ldts1gZTdU;KF7-RZT>;w4L|2pKwIM$|$lm(-oB6ty=F{En zS7iJvx$i|&b0{twhjzwOXc(wZdIHzIy)HsbsrU;E4{1&bf9aUgW=+MHS8qAUwk!L;8$E9nv zt`n?=Y3ri4mBcbBv{agM3Rc%v?MQCgU|duZBhK+YZ;pShohiELYuiK-T>W+E?p7w) z$O25LWhipgDrKZ%ADYAwuI;&yK>qbu=%9=r+QbytY3`~kRDwp2xzxTa=X}h2e@h6$ z@UdHpWl;#T)hcFY#=#Qxr4({|J9j#gq5%G+5TY)JUYLNlqL{%>xAlm__pCGTThY^dmyS?$Q6_LO+eOJW&!$l-Ru$Idge$tC z=}lc!NQ`X}rjr{Xs@Xx?eB6f{3FBioU0rUBG*uQIpO!2HIn z-By_Bz=HLic1F6BLTXra`ec(h%K^9|iP%_86>V>zL zhgqL)eMY!s9vj`T=jk->XwK_F3AqD)zITJV>*ktBeq%fIwX8Ph!JYj?{ACRBXfTt2 zrM!Mwv>cryDzRB*p4Um?}NL zxm&T^D8-BJ&MnBlSIp9_%SJZffT~N7(cP_LgXCV=)$Qf(#4v8#u!U(QH3nHmCFj2C zF{i1@m)Jh%$;LaVd|S#JKY?R~609cDxiFgWJ>Al$<_6K5gYnjqP0y#d3JORmIkDPk zm*1#(oLXkB$1 zL&Gc8j`{2{G_ zptL)A^_+`idhVRN7PPCu5F1h)tVe({=>QF52AhA-Nqgz_I?25tgMb!u!?tew_5DzB zm5-fgANVG#j|o~pd*8zZCEl`@;ckoak#r8ONp;Cpy&IQF zE*oAG^xwhm6oJ26%jETLY;UNrnl0%sqUY0q<#Slm9W(1=-PKlftD+%C-S)QzyR^D4 z(*=cf(reF|<}R$Hm*2iuw$#jr84^}=YgQmjApCZrgwdxrw_0D1cwg9fn_{pw3?@L2 zVo}!WxbLFS_QA%x-h2{LJFJBHvEPQtP9X`-NN~i{ztTXJ?eHJeoOUR6k!zLf-gWf! zk`%|+YY@VoOJjwG($!a@h8M?HmXm6kIeG(A}`eU~XY;t`Kfec-!K z{l;H=#E(a3*isuE=>@q*Pb2U2n90m_P}b0^Q5}CRmv~UO!-Dn3y8o%o0?9ayVRol- z6VC!WLX;i0qA6+e*2*f@fUG?B)C35bkPFSCxMhy7U(M!z!@9l$h4Frm1>!=dM?VSG zhfl1}Zm3+z_t2}JCzUHMvjpaSf^797+t>Cn?>70U*IyQXLcFQdp?J3x2e&?B@imzo zs&Iqmlb7vKD$~I36d1}Z1oqaB&fviMN>2cf%N|+CEn29FLBVuma!(H1w2asN=EH{3g!^(?*DR6rxE9}g3$HBv ztwO;w5PvC`ID2y#lAGsqzQfqwanYx9xuSxb&BLZ;-QGhxD3^Ktled$!&1}!YK77P{ z?eMY$Z|qU`&@Fy58hfo5Pf&egR(D^O%M@|R5X3QJ;=`yuG#pH%X5xrCX#zG|49sXP|j!C$1wddP4iuyBq5+vy(&OkCm zWD9ReBX6my*^F$ITzP6j?(lWdw}Z^?Mn3$+m^-txU2CWku4I3qfBEV?lYK`SQN4E=7r8>($^OTJ=2enWoQ30-H4>|t_gaVE9K_V!9_ zpKDHi^_>}XDO%-aiaX5DDL6*>vODtYW<;;z^qgv6IusWvotwxkpEJO*DMPfxSN&1o z?PgjfW0pCoB3_H(Cmp8MAH103N88jV+S8wTtYAIS`plilbd$(ji_of-c3&zb?oE^; zgHQF-g_14O%`NPwxlKz#1Q;)G^Su$d2VuHwdA8?%Aae&>^E)*sA6b5X2>CO-J{Iq} z?L89Fk~!z>s2#V*y+Qe5L`I%!DR%0-Afz_P0-={+>WtX;P|0<2Y0JyWhCKtkp}|VMJ1}#BzOtbVt|KcH8G1dlvkd_U zzdM|1K2>5;xJe=&lc~1?n8r80rdf6#R$L^BiU+o}L(SF(nvSGo(P0gePpsEUjBl+@ zu?2BV49w$u(suhftdHNNTezj?q7g=z;>Qm??R;Cpe%wkYR??Kq74?7+{=1oJ1r!b> z7J}+HPxAqjAZh>o)`SX_vezFcRVmxYD)L*6FiC85ub#Yy_`cV$^o_in!Ll;^L4^v!}1%!ea| z4qWc~#VHx#lT2bbbTpSf%lo`{g0oZaaY8Xd{C<1m#ztsI8`@_aBObbnTjnL>D9A4u zQM|%5G4IMuMm(kHsu9qV!Wo9!x? z1&SARu1mN#MaQ^Km>B7UHNvBmuIB7;C1%MGQv?g?SuyRM7LTG}w@Jw%jZqeM}8YNOP`nxQSYbu-+; z_EQvFGQ-*JoG!->!6wZ3DKJr&=30tOzru(38fpWEd#zOoC71(X3H8&P+{?mm`C!w5 zsIaK8@H9jay8+MYSGhaO0#}A{*gsYPq7WbjW(!R z(AH9?1?oKVDu080M?^&n4b-gpE2*U+_nr4eG|zQvcMOCwkH!R8S@v*&Dw2Rbwksz7h39->QnUC{?iVwkr0jLtw(l0Xah0c_V>WRRp!wq5VqNZbvZHML7ZoV0O6LL9wzG5aXo;Zub0@La<0F|TXOmb&By{w0b3*rB&`A}%%Q8v@lykoV#7u_J1l!n2HHH|pCi#T|u<^d|x) zFCiavL&$~Dcm(==nOGK{&9n-usXKaQBT^1jVueIA-H1|%AGV931)*iqMPQC<8pbnI;sZ09^E|SS+zEutCA+*9?|BkdNA&6 z+(U(wm0%SLJx`BLSVER{FQFG^dQqU9Y`7!3y4cikam4mT28rY`?R@N|0@A6Wy(GU6 z1(`Kg*`wE_We-Nq-nZ;?EHBS^To`abn`p2J%r_EQGCwoqHQ5Fd6RNN4R?%$uivWXh`?6ZqR`r=tFm7@Cdr^A9OLyP8& zO0EG=kcZ~QknHtCJ?@)Px_cgmbsDCUjh%(UG%;??l;s%= zbosZukwATbf*w=Ow_)0YB*B#-bbB!fZ6E8%_KwoPaSe02h^1p!Z=r*^8F95c_j|I6 zEUb1fchorR^|be=sflg94qzkq;doviYyN@2IiYm9k7Ac^_hN1vn6@L@g?^w#5Dk9M zI<6_(#~d8&YLl3*t;y^%>x=E8Oho>6i}X=~$cf|o8_pY*i(c-ZpSZ9>xPDP)SNz{XnSKSd_ zc}4!AD5zbad=-89rvoD#0kL0R76_No7YaiYCivRzB~uIO=pwK}y3zG7$ut`D!z`H* z#XZ8Y61v%ZXx}5EGB}PT#gM!yiB!w6MhY{T5E;<2E04qXU3SW@!)_9{pI0`IWl4#q zR+VE}6*BrL@Fkbx9amKkwQ$v*dPNr`p5KJ~UQHcTM>U6xDYvd%gyNET#gDVIEkoWw zpyk8+xQjNNL8&P(dwb+(f&^dbUvG6tL{)t`?9q$m^XYQ=y684(9=h`w*_Ya1Y3VrL z;THE!Gwm?{!kv7Ex?#fonTs(!jQKH#5JUR~G%;U4#Tj+Et|J>1){*oMgKjaAySO!aelB==h>tu zQSboPdEDq?-Lb~gWzWiKyH|&T${U(Ep?q1sQ z_!BxeUw_5E_S#U0_L;XaT0VUr_QsxeFj_~NQG=LII*s>9)Mv&?mGMQz&y?RRgmE1d zzu0w!P$3X)GMHrfphP{EyBCVywaT1VFYPSHUYlk|`M8wJc#xhRndXs2D%7rAL0p0q+E}UdtZf7i)Q{RTluU(IbEKAP>TyxTg zJ9f7dGK=!}Y!kuDrHLH!=6y#CAJ=IgUWNwSP!(Q{rbsgnczfVaVEUT6o$N>1?}epE zUnr!}^UgRtfaa3#pdx!*FukA;PDjNc7Fr?syao+Ec$3B@5v|$c2QtsK(RK{&oJ zDN2?-UtV|Lf~m;gtZ|Gw$$cmHWn;t_NL^N}Sg7Uf)a%zZyLL0{?h- zLbG{>@)?BJL`0Jx(iQwLCRW64Vip$=<@ef7M~K0--8&S=L*NadA)lxrM(5kJznDR)k72clxZ2ZhY6+Pa@-Uj5t*s zxv5B2hMkOg(?`7`D#+}FF>%sXZ?>Q%#Ahmzdxx*ujFJ4%y7cwbBqk^2ZW*Z+lh zwtur2^u8HZ2A@Mz+QohL%8HZ1JYFM8efs^*p_PO@(J{+f13nDesF4S6i9X;Tay<$6 zn~6(vVlNw6qug^LRtN}$p32>c%u1zq;f_`j@E(@SmfKQy!RW_gNAYw!_+m(~jqv8P zIn2i#yh(;cM2zPo|8-7DiDV)^HpK8eG-d3YbFCYs5DkH^mqsq4g*2(6d;@J|Wr0CY z9E;CCf9g`eA!<+hrlrYN>AVN`4Oh7U-;UFp4aoy|X9o;r>y+M4773)3JaqhTgJoQz zpspBpUG~043*?ieNxYVyY`^f*o-Ox;_()WHA-{O$H0F)+_U5JF?t(?nx~C7Udwr;1 z&B&nP?C`yhb7UhOopA?5RU9eP29y&CO&>Ey7K?d4jg*Ty=f3OD%)|<1>*7-IdLlA4=Sa?63ZeMWRk6q? zU{W>>E_xAipMH5IVco&gz*xOxn$Kh%(w|anr&NYkC_&Bc7KeHHEJ8RqArey(CAp^m z3%fx$hq5rNL4FOxt5O>w~- zu5*v&lGg9UbUc5%khi36CmyAU>p6a_cAnU%S}{=}aPxr4oK=4_m|AxZUzb98UVni- z!cOb!-WpYTxo|^|pQM{Jhos=_YRJqt&jnQ^!#mOMQw&~CK1vyhin&{LXmn5X)m=Dpd7)L6oe z`DX}JwBKT0>cjCEjaW&i)wo*i=GDF9UA{!{nGnGyAcgn7u!A; z{XXwmti$!3+4N?&he5?o&2Chm2H~cS5~08vgG`Nrgsu58L~yZGDJ8 zU8cu4Goka?*7Z)Wqp0=Hz^w2DDfER6xW}h)GE4;Zr0KS*YqcKLsEu~H@m`!=9q$}< zd9f&L4#a$Q{wAR3fzRX+S$CTY#T?3hs;V?4V{4I+vmXqmm))Z7-+qZ7Ko!mD%W5#i z?k3PQ;%-XBlV&348d=A+fD<@#6uemEO=rUzY~+K{?@7Q57KgfCJk**A_E^x|FHPY8K(FzD8D9NblwwXfGpgkDaO|f~ z^Ovr^REjPuTJsI3S2&40Q`bpDs}h3UzVCq&Yx`Ds;>5laIR>eo!`RtLVQ_^KJsDfR zo6r#cE+s~eQ9hUA1FhTbGvU>^tC=z|MC8ES02BHt}NUH*et^R)t;o`!5$V zL`FGaX^raQ88y#yRvp|eMQV-PqAvDP9u}q6)LIXD4~p~UW+_{a+qB29PbYG;XT+Hp z^68@^s1lGSNHeG1abO~I1TlX&;|zS+$@k(R!63D`{t%gSy6slDzQ|!K=lugr0+*}X zU)-$D=253}nd-ZPgO{L&;U?rx?ok`J5YddQzVfmo;~M&`^q5+qA;lafo<%-S5Zg>g zkSqH(9PZd@_m#zO;F0{?N3@o}huELTO`OB`=vup9KjWJ7Lrimdp@R=0s9YuOez(Un zI{5zpTtK70YIK4Z+wJ^f(YdltY~XSv!obAxDzEx*j{rX8tdRPuVPjtdPAxu*`@SCm zq4?mAf6OEVv$1n5kF$csrH1??-`KM{Z*eX=1MF>V@N%OQbCSh@Nj>AlChdBueaTla z)%F#k4HG+A2!l&olkay}7)JCNDZ7dKh;7&ExcSEe+(7Qc4@t&B`pNL2;j!`d_Jegg znFik2xW43JqQbhFHlc9{;L1drR1At82Wg5lW3=3l@QMd#?;(dXBo`we*|dk3qQru_ z-wv@lqV^>&P1ZG>7jhlP$@lGRsW!jnmHJ~qW!ytr;@;RXlV)@6{2?XOHY>H7bUDR#0Tk;A40hC6we z(_)+8W%1?aAzYLspYh$3O}R7Sa-%VV<;dcKjAB1w93=S~!1#jwXksLNL^<`u^_3g8 z^1FIzLpGNaa10h|rOvFC=f9(uw~TDnLKsK)75#W6RG!0btH5wf5=I|rJPX+1~x$< z?GCBkHf1u<#gekKV{5i|(GJe`sov0<)Kxspi>1X#{4+tRi>#?tQI_kQ+aYqK_>l_Q zDWiX4p`ZFacv!&f&w&?xMql;o*ASaSV-u^`jKABr)#6Oz);f-dQT%JuBCQH4N38r2 zL&SJhqCEfTr86GLr;Lp%jK12u+d~Ep0zP>fYY4QyeF!dzdgPKirYKRzqV~mO8HRbK zt&3*uMqYgC|EB7u+XZ}yopM-E*?qyA(-w$-UP%&*ib!tn5bI4EyBju?s*JBfW9+!t zR^pfYZ4=8w4*sR|#3!nV4@UFoqa>xMiT}r+|GabzvKRtfpR8Zq2np)BN%YN@M39_0 z9{JN)xGTAm_Rn2I4q&$>XxyFU&u-CT$CM-SC(8Wk1b9#}Z~k_o@IC3#Q4==&IqQm1 z=eFT5pGoFJ+uY>KnS$Mlq@kshPp>6U;GNX=O#GaVN?FHW&LB>4%~<>-8mYK0=G--JI)9=B7DO$v8h7T%O|D%-kYAO zK_@)7{1C?hS=cxCD3f9*@sIvd?f%7&q&?oe2uR%8V}erVOKcGuR5`YP!NF%1*q?PW z`|F24GrmVQ?d-bqSrHy2p+dcZoSC1V27>je++$mM3+KplAH5@`!txx0vPfM?QPi0J%8pjOIx1%Le5H$lOWbioF5l?4ZH zsZHGCSIXFmf(PK_(WQMgMr7X6r`=31EfiMmfom=*`&b^uu$>JvYGXh3=2j5|#<|9b zrpBpxAH}9|^U)j(fCeH;J*Kp(;CHmO&!`&n7n_Y0a}@8IrWa2>}k0wGY~=B(t| zevPj>!t34pAABPxTf*^c$t#n-wH^hq%t3iR$Z}2fgEsu4l{YwTf2z|bAdBG0qHO8D z`3N@sh5aC?7>!m~Yjfi6HUFH35I*}piYj}ZiwswW#Al$PEL+&bp7^Y5`ILu&+pm3} zK}a2X=##*$9A$h-?Y=(mxJrBvd0}WY2_tw6^6L6Ku#{{IzGHCe*iCVN2j4hjmY151 zm&JimgkHu5*>XV6lEe=wXmdn{tP;mOz2ca@%e+#<;u9U@u@xUECDmT}_eiS|ZA`fu zRlO%Pok*SSq>)FTpFe-HonZrv7d+jK%1yxDrF6@@WviqPnP;Rcs0yC{TF^|Olf~MW zPTYs*{QjDNUGIoG`1yhl2CoBzZ5OW)R`WRePTLl;ynyP_+q90MqoFsfjF0pT46dd8 zQV%wu_{a4>K4ixtE%Rj?&KH#O^unDjD2`k*Wq}bH5`>hPY>jC<4}4zy*|FlTn5v@J zziEqMuzhEFcm&-RwDt9ELlqK*VBKy7!&7FDttoW?!e{rdY6lB^e!W>=W0nB2XFq(i zm6a?q?k@nR+_T|LFGvdDDl|yTQ2!es^OaB1{)Xjg)S~elh8_YF8@w(!a{rRn>DV{ z)VR}PM6#TnNS@1yWirBk$6ud}Lv^(f&%MKG^}RpWa=FwSLws!dp#!zzJ@VH_&cWNf z2Lu3k$dM!>J<~P_sb*m0&VS1sN%@K<9*oDXC5R5Yputw#m|#*a$+4R!C+$|sl7hLgz6Zq#SByX zVH6_`V%o~3lD0Qksf+)WvrV;g#8$j;%GCe>AOJ~3K~#@s>U!h@KwDG%Q|YK{8@j7~ zD)Ep0kBv;g1fq7^M<{H2js0neGubV^waU{rhuHY$D&FKoVokdmX~&5v(#}GKjlc~K z(pnI<-7-U0uTVxPxO%6Or%E)}?;7%MYeOGljQ+M`An9w&!xbGJF+-l1mS>KU_ij<` z_`1MbmWXkV2L6=T0L4IHa>DECIdxX%H^;wt+pzTtYEo?bWgJHuaGZGM!}BGb{8duSwF6)}~sR0%hCgv#=xNDsg~@wlRDpR8{5Tqvf+g)18@`LG%rl zMHKv;d2l|L29fLg-lEmHaKfjxRbKH3r$;@z-zhe_I-B^iPe;oK?+on4Y6d?sDpqia zM|Wo1Mshx@uFAoae73xBqYpM7JC7{76QOBOi~XJzrH~YrbUR3D2`rJuL9X{`VOS!myoU1C+u`Fh~wT|sE>{xRnjm0?qy4z2mD-TX$ zq#B7L9XpZu_B#W_HshjUKI0epv1u@HVq?%%-toBQ7y?}K<|Z||NdfbGUbtyt6OV6S z=LbYb!3TW~fNLl~qD8R({PPbvnN={?7HX3SXGq#}jYB5}ifxluylOyt7zU3TPd78nczR8o9n$Neh%9TIiLcMJsISAV-yxfmaj7l+u`^}aU&guZJ*vJSe+L!p-*Dw#*#^XJ>(=y+NW+e}Ut`sq8s z6lkK&Gi?TI+_u_95}1K0Im3Uu}*baB15848953R)6>J zw2Ms^81QQ&IP~ZK4M8|1WqvUJTs1!U{t07``2!V;>y5Gw`IF({7(1XzUgLW~r^@yF zi$ej!@1%)o%h-_#=6}W<6*My9;WIu^b?E$}pYZ!X`eUg`Z~Iy#mk5tl?|ab{A!(r z51TIYHu@Vkdc3@ zhhuLFBZEcK(iKkhTphibo{H-sm!D;%Hk-;>7)fw#U-`9wanf3RuwMf`QkVMaHkZRO z^*LFnnnYC$laW0Zj-=Ygh8OfU9LFsm``F1VbItHHIkV>q72!7x%MZySKkjgE>)ktj zM0m>e6F%-2te+G=IeeD}tXR@1EfCAL)jj9f0LqHK~w{*_Ol zxmp1j70CCww0muOgbwg?R|z7`5b#m${?ruFg0IRrOh?oQ4;ND-fcJMl|NN_;#qW-g zakX=X+|PC)6ju0sd-DdIavHO?+fKrd1b!TwgYkVeviSejY$p; zyRB|xGw2vZgdlyGKQ=DHcU z#T_qnz%MqpueHWbY>k zU|r*F=2##lSg=|i*^YM}6Gddxjgi;8b*&|tU^FPxrg|`F>*dX;RXu)VS1&jsOIm)o zg?eg-BU$G;Mm|UQM|NNkaHDxqM0C(c!xW96XV zcw)pz1K0Y-0#))-QL>`!x#0LkZd%n4ZfWnk<%N%|CwUu$XKd?DO-VZS!Nmb#?3#*&f{olcCl{7vSde^zI4UncocB?`n^pt6lcvK^!f+zGIx)nk zr#&KITszUQep`=T#)&yt-hoKi7JjyOhK>o@2#@l`{-3Xpg2h3AvD9I?#bIe)NJLFxf3GcEK4g!`!y~(NfyMA9*B+$$1uOOkGzw zV~#FLIAIVM)*Jq(@1p3uf=o>6NBPALt{Qw{knsXnEI}2Ae&u%}DOaDP`x_k0I&Dl> zx1-fod1FXnm~LsHpVUZGxmx_ozRF$Zv|0?1Bo~$uQGYl zzHPH4&9=c4fe-q0AO)Cd*O6@p+wYJ1;Ex>)dh*g_LoZvG78Kiq)!G&x2^O%ZHwQBx$-E9$Y_r0?dzTJ%&~1zbF`qp!wB96Y z9Q?sXPdwJ459Lmt8%JZ+tH^HU-J>oiXi9$@E7!N&;7UXMBcDEG!*snbT=(fj5jzSH=u zkNBE~hk@R}(P3}hubdcHECT6Yl+Y$MRt8Gdyk}fpWobHX1V=LZkBvOhK&trUvC&H} zv!>E1mz$Xbc0o8yet0^56`UhBSWGIL_`xw}{1Nl`K!(doenFUDAEN&heF(hW{qZ~B z_ooVF*iVU(=Y*W9b5Nr?m>Vhj=YfU_jryNX9lfO>N9N9dv?3eAMmGMkCOvTT8*hNS zdDw}#C+5@*N3eR9OUt~YD~LTIb}N${z6oxv(>_XDbmelkR-_Qe4ciUD+Tdu{RgE0^ z`g$Ibqn#JYCmYaLCuRbM3pWBixJ7q;y_J5)=Acn`H(?O zlP?Jl(W3!dvBO}@V-WcCuLPlka~TYSfVvWTp3ufnK_++{1a7unrfRv=%??Mq9CT6W zIO5nK=2*U6UGzWMfp8>S_><_CDkF`^cxZb(^ zDVK7U9HNddf+=I0gpJ^5otsC+p~qj8f#MFqZ@DN?!fPZEhZfN#1>^BjXT|(KMd9 zICR8H&6UFVO5~%P{s42w3o)>xB3b`ue5>C5(HMhcVau*32p3!En2?SmJd7J(W6T@g z@P~b#*g-1(Pi#>>>W2f|K{wZS1g_6;3u59bu*LTePblv}AnP2h!^c#WXJGi~U}*Bs z-Qda>(_6%RBR)Y*>@1%yI?HC3p+x{BvEc^9lT6+8Z@Bv&%RXD$1P8> z_Q{`o<12QEciPZAWvb)YQcwafv=7@u_i7nAVvZIqBV>%$(}v9*r$fy3w3q7|7ba02 zNGM?oW!nUQzucTwi^9jTHsSEPU`vW}c%|Lg0m8(Qu3j(c5#j(B7CFX+G3_LJ*kJM% zV_9+C+S%T8O1Xwa$tZ?v4pG-vq_&;dst;?o7CI4@F7ogW$M^syRD8BCfUBXuf`>K<@u%N_8@ zCPs;um4wlLOnEQ(lsOZ@yY^yhe4kiW9~r)wQ@>9gGi*b8TIk!C$YODvAeNFp`=ohb z7}S-;Z|cCPOk)Bfi5*+?f{z^X?4-D<`Kqx&+2ds>fEydcPT4qNIr8mKS0k#__@akS zd96ie?CzMNV@>Plt-2UVjUwSZ<9dxrH>F?Qu5vFCT2^bMW*j#@_(}}l>)WqSAN;Z3 zxr+$MSoM}w`n`5LmHj8Pg1t!MN$(tE6(c0{aJbv=I@P}RoqUlw@zG1?=&t7raxE%qa$07EcuirGx@gHqVKi>VG&sx-uo=RZDH4ohPO_(KoynN&*~rf+UepEL4jk- zZ`jIeM-B3p6&88USZlyKpjJdwDI{&yZPcCZl&{I$5Lxoc_6%@xvgR!u)Q`I4j|$n( zS;lT{<0H}?!`6!rTkx9O$jK=r_WByn>d-iNGT_)_$kmGL#3p?bxfO39s2X+R5ROw( zTtt>kqE%VyJuWQ6n4_G!>hzwEy@^Ybmdv@3S}A%^&LZ2Lk1A|`c18OTTmO>G%XRnZVt7yr8hA+E`zQ~c&p z10$T%AI4UlebOTn&UNX#SY!9|`l{#gDi+&}>y#S0s1jG^9SPZt2TGmXjeUeP6qGU+ zi05_8L>38B( zit9%kY8Th$rfGMsa!ynnBz8QN4(oOlLW!FOUra7NGS;DfVqPqc zwu8Mh&cgoEClF)z1nlAo4;!RW`R?eJ9bqQ_&J&~JZKto?r$_81=PXy9=!E@Qr@HRav|q?b6sGkLvXzM8zFxb1A4k#x*0J&?fgkC%`iUq;-0cNuT1qhv>4 z=^waDcN-H!G>-h!ufP6kr&$j=v2_|6!k$`b^f|v!Jg0I1=l#TrBh(Z}KxpB-?vEscn4- z#;3FliDBBUG0gXPKr&BU>Nj}UATH^jwwy=oTrc&|!vV-LK>yZAlk zG9+VZ_fL4N%N?0_?8KPZbMMLghU;{Y{b3<=TK(o~^JyF+u;2Tn?$*AP{1JNh{}y+S z7$EPYqyI7x71X8kvbA8la3+54Ybn~Dw4##*fMAmboE*5WI_s$CCQ9+zNeD7f`kiv* zsL~t%kzc!sZ3`KKy17{#FsDA3HdJz-nOq%$I9NfczI_{B7IB0MxcN*ebn4Nkfx%{M@_*5qnefJn@K}T%F zj`~PlP+p=;tIOHB97@V!gHIiP{G>>E2Y2nOH~nzFc?!_E0LPZb1F8MhBjbdfB7Y?~ z+KP|<6UvtmS~}&)LX^AsMuaiZa=h7`AUCQAIHBW3?Z9+T&}qk?cPH*9pm#W~9h}Pf z^Wd;k!U*IEWR9+$Hjf0P^a3y`#F!YnyxSy;z>+6Vm7KGDuW^Q$>a#e|m=EDJs2L2m zc4CxwFt6NVi%#XAn+d86^WtpD1n{39lPG+UVL`)pl5HbTgN`7so9~jhT}viz3&gsc zt8q}qK3v?M1%+C-tG2PH&Z#z}z}W*I9lP?YTo5yW;i}}ptSo-9Eytt8Jvi8Wo81wy z-Kl+(SlLP50D`}c=?gS6v1pnQ%_Q1r&80m%##27^U?(LO!J{TV@3BXF{TP*X;AUrh zvA3#^EJwIncS$#s8-Sj0haa{P=cUJ@lEy?gy7Z+JOM!A6$CSpSX+c^o0cA+Zc7cB?gSstR}Q`7Ik=Kve(&d+k>XA)uVuKZI__=s5d;QEr1X#Q8w5f4cLS#5phngmuL-t3gra%}*O*-D@LhxB zo=QeM>eg{*$p+T#nci3giFpR08f-`nIwlU51VU*HDd(-HjUoeGQImJ!`9zDT^dKfx{K&!Nbm34?q0y)0Tq^v{#~gBbd2HK{cKV2$ zn`Y-Xe2y)AYF;yln&M$4H zSEtHY#KHUq$1bIt6gz$)i3y4?#Y^5_uP;!V{NASyr@!P$_9+pPwbaF4pNgB20>?aaUp5+SD_$ z?Fh^`Y7Aarmad4|mKMUwBN0g1G#@0GEN~;9`(+#mljabnMm5gDLS$qtUU?~y?e)KT zV9(|!9uvE(UKE25wonF(y5Nl8V4=*LGZd7RM=h$crBd4F-6hF&(YiDWfVldJkgsJ4-%)v)? z+d4YIOKVJeBC8%;TxhH- zvBdvafZ7o9SvUrxTnBw5hOA2d;F6fG4hBokgm*v^2dbmD+AJGLORBxH5k-F6vJHzD zC!vi$!Qs6aSJy9}XQH$i>dNKx|;P-dMJC<${xPknR0u6)EJu z=rPHcFQ259!DX2d{PtIdqQ@l9*Op;KC+)$+f|tt>+LY*|84%a+C!`sgbX+eDJiyUG zRsYy}zrFFI>`LC9%CF@f!SH2>mEu?1hUw$ zRc}5d^li0Vp3mJ^6g9~q%8jgd@95vA0N~0uPc|%~CyJNv*TT%THg!H2ld;6lpe1Ew#ws_I^~g=K84u-vBxAiFWEwC7=!7AK z7xA+cC)gTO1+mBp6i%v`c)=N4s{-Xo*!Impl{+r0cl|Xj1o)r%#aPlpm5C1)4Le;2 zk?ZY8Sgo!1MF-NyYH`9z?%SQg_ORP)>^LB{6*ep6#=F&_^$>;+{}q(@gOU|}_9P@$;>g7#-;CxIO%-}{ZrOE+i303fqRNq_})pN`= z%C*b*EMDud1rGJai(iRfeM~ztw&pkbKe}avU%8%Vd?HKxPQv7utW?+f(B?fSLiCG? z&rt^hok4=k#9-@3ZzOz(9WE`xi%gkjHy*%pK}l8+Mq}Bw4OO!+R2H5KOF2%`8u6_R zFmQS4{hZ8_Yfsn~WvA*<0JP;0iVW`=l)Joh>%M?hv9sGw(x!GaUinog_V)tSj>~R_ z$A3sH6DxyzM`7U7A;R!AESe6tj{q4ElYb8&717lR3@^iG9Nqa_Q9b|uYs774&+Q2ISQ`Ya+p;WZM>#ALMpM4yIu{! zK@JVd*Sgk&(l`$gRhGxvsJrQV`P!*&%bQ1A!sXV#@XC+eq^;8=@~}U-m#YSwG@o8JCQ={WV47GrssWImWzo%v@WME-n zY@e~g1YL&IhK{%RRiedQvLf<@1&dF6+SumvAsPg)?O`abPie4?Dcs98pJ0z}w^+qH_11P% zjm9y3`0<3IfA}$jJbskl$uYjO%A*5LWqZW-xg|ciAmahzIGuxtYhpbHoX@Bu*Mz)3 zkfn+Y`NXyD+2*IT^@OdehxPT+(FuL=#-~Jg$3hAc^KlHz7DH6wc^HVB;2833`KuZ0&&WFTMmqI5rw!DZW0B?KP;>q?9|l zl}%j;Gr97zkwb*LwsfvP&g&0&jGhi~-jg2|nf#?iyZze4SGiRlOgKx0o&!7Rn$T2{ z&~G{yL$~RTh-Hw(mg`3c-44KDWRk~r7GCpHI4En@qj}OYJ3r-lIuSu(yIZ}y5 zLq>vkCMLY$yW`3t3cn~t2W3qzEoz=DD$k4lS$+ZKlvp7F7CQ1h%a7jx?&Ga#M;j?(E;%LWKcXAj9svODh@sx0&cgYkPwHfDgebG)<%MvH(l()Z{Z^Ejf90)w1DMzKWM85n)= zGB7P~ry^&W0!or#Amae}7&!v5>Ju8{XCP+)kD9gwe$ksdg*hHT)+hqQh0OdfA{plYj1t~X2;Z%y! zU-x>bub!p1Z+sNiI>EtH>{6$93G*6Xe9~#`!Kp6olP8A?N7q%p_S1+j_;mIebvyRu z6Rc3km|>##X;f?p6J?E=#se)|q@E-}u<7L{bZK^9>8MS(eXol90M#>&pc*14onoz{ zc}O6{WHWtNtDNa`nbjI;N+&E8G24O^5Xk7M&05)PnGU-BzB%dvg!Gn&Rt@FR)ed+} zwjG^5CZprp3OPR}(;##LZZ*y)nQY={FdZIwu@ZIxP~`-L$7{J}43HhAS;2uH*MGjJ zAVYAeL3FOyjU8OM?#}hFG^9d5RIiUh-Jz?E_PJgk1ymArH_i9sie0#f>-_dtAxx z>x^N*ksZnvnbx^-QVl- z&@quN*Wsv4BKAx!j&@4Np(Kg?hDPjA8y~CNIQleOB^;5$_a5zmLoA46G2=)0lV9JN z2~6TFgMRer^=%A=4iwit0*_U*W?{NX=awr?gdt#%a&VUp^|6q_9XSG!R|I2_I;5jx z33X7heT=t}-3E#F!VmFdk1?43N27TH?JAA z(n)>L(Z43i3m6%<7$Xp|=Vn*5jJ_v_GrYxOaw)`Sber!HL)ORO(7`NqnUvv-Jzmz+ z+jTrrPs{?6SdJ_dXtQn>N~BcldfG^xtaaIyev_QX4h2At7U^z|!NU#-{@ER*Y~o|< z#F$neXg+U5?n@{5?|2|=e|(hJD)1M77Bx87mTL6hy8~AdBbvlpN3wc_@iJzV@j~rc zkyyiaomKU=7Y!>-b>yH_I8!$=a|p-E0unC1$tQc( z(6OGNPw;ppl~0b_-ZTec4V<>y6|4!zb%FyCd%;cxa*+WqJe?Z=7`j&C);*Zgp|unA zU_=AEVDswQ9{jC?N$S8!Tda^L2H+_fXhf&zYThJQU5nXYWP=g%NWr7W4&2kO!6INP z!$$|=Q!2Iyj$VthRmH<6k=l-)O3@zx^omja@{jSh^8{vJnx!`H=y^wRRDw~w9d877 zKfq;iY+x*cL-4(^H(Y};3l)7CanPjAf+Ia;ZJ*=(BWk(cr%erZKA_i|!;sL9fyYV> z5YA^yn?^v>4cwLNC-oLX2|?!nGWKR`lI+NlChMH6g~j)O$DT`04_V!Hwvk^J03Mm- zM?|;*P^gNEDq!Yj=9`WzB6IDKT^&xHIfXl1Y!Tx2r*~$hV3Y9hnd`8~s-P;kc{d@6 zi~q>umw4Wzr)@RFvp)xwypv9TL})uI?ejHg*O^<~LFQRux~uUSJAPyB`@%gBe8KAH z#~1eGRWHp(|PU3!TmZz}daw+eM8%sElyPWcJ=bMa67V)Wl zDbnb+M&Lua^X+bEsmbE=I}(kr95Jj7_V~43{UB>7-nnvFcV+RF0|$2V9jbsRC%msi zFYT_3Cw+41S9~`3)c2*fN2n&B4*l1^{ax=A9cq+rR^Uv?R^G# znu5pD+CoE__PEFOjpyb{JSB!_au&@SBnQP@iX4U}MXn!w%k<3Bnz8r@N5t#Put}u$ zzvJ+ZTle9O51u)QjYk-q{96|e@W&q-Wr_Mz65ADGGd!!+40F>P&dIQ}WOIE#erjVJ z?4&eK$l^75j!t=?>hbiqgF6le7^~`Tuq7`Y6Y|@ietd_k4~PH?r;v$39p*JsM(%uq z$(e9`OKx|GY5M5I&p4E*ppGBeA?5KNTns1Sxch8`KAQQr3Y*zY*I<%|W6mmM;-aqw z&m#B?>^g!N&c+Z!g7Bn5Tn%*C4m6nCAyKlgY-kIBXVxoN+vrYY2ftJ-i0g;JSWK-8 zdTBbJ*$hO+(5gP%0<#qds9i5j;Y+fRO=YjwISd8uLs5rPYIJ%$hsPTjlUZ9U3 zb?j=R!S%884MzebM+jEX^8u%V*G_F~V}P8^Dc2vO`C6Uz7k~OfkQkDnyhF++4un?? z7{zc$75_R(Jf2D6@&G!~#-0L+y=3*0(@?vds%K(jYq2&Qj5wO&cjIsScfCpCM}=6z zb&D^){-kd+;u3)PvJhguHvWFn~gZbad*qHNj z4uF>n+;jQzC6{1oEC=Z)^2%Z4gS6@oj#c%uBtboe?((d2q%(rNu;(D%kgUUcD&p80r)Ob=9=NBpGUlMfwGfs zCw7RulPUjp#KdJGBmp6kHrI*S$sLFElSJ(K(NV~kfGbAX&2oL&AvngdC;TVB+_-xR z;L3_5>FsHJH8`L2s+Ssu>`(gC8+s|1D@pt(*WFL?M4+p&Kg*7SE%IPAJ{@rQCZUX> zg&aQrqMu~S_rJ}K(Y5b2UtXTGKgKmj*i8?c6_|6)H|D#fa9^9e40z)+hUX^VG!q}O zW(=;>?{&UHF?ah^n=kQ*EISw&@vG~6?(5VYIxC2N`-NO3Ub~9BzBv8akLR@vJ_xy&L@V*8C7zx?{pN+gU`J9J zv-TZ_+T81)n0c-l$&UW<@)VvWF#KB3i(O)H%vB=*c3|qy5`oV&1hDbdU_vkx`5H61 z_`|^~4?1!VG_m{iNKp9(qD3M5^0Ysm(o~$7gB^t(CGi+uYJ{Y@n9jjUauZ91!=-Cw zF_Z$$z$BK$C7qVV=~3IoKk{oIAI7m=;=7&$7V}pcjF5PB>O7fV=hVgt-Ov0nUybQ1 z!&f~$unUJz<2Po;gFy`8iajz4lJCxbJd%y)DQ5E3M&0Fx*L+96uzF(HnQKF;8h-!6 z4cual2iDl`SvTs$A6)ha{H@G>ly-)VJaNi!boFXLn|L{~gZ1mqL_T5+#>YpZr5o%g zpZHoF!gX72$Fm23D1pCVa7-ci{m~Ec=nWXTB>qnuB9*B!)sjFulC)nVe1A@~1TNG& zZ81wgZ93R%cLT;0U!4BCpv*&#kiQd)xJs@V+X29WDokx>D8x1#$7L7OlBN9xneF8n zZrD)fZ~Y3*)$MM=JNk&oS!|js@bsV;X}ef>?m_;-evv!^p!h`nTt@2`b9`1b94U+6Ix_pI`(&ewOLUSwi9Ih8o` zA$$FE|ATj;T4qt_OKoJ%joRvHqkLA*2ZQ*qj_(6_+-dV%v3Xy{y-d=&@4Pgm=O`ol z)h)094F}qAVGcPUc+9!e#)NVfa1I2rn++J`)Ni$~Cu68hO-|VtzxusY4q9MbzKkL}u!QoFUeDKRNhqKZl+UavWxbmtvNgCTh z0Q-YsM>#5^=LaAKpyK5qmrGAwI(??es3lHt$=k01(A^`-xo1)y=-0PLYafm~ut>Ll z-U^xH(I~DK7Z7U)^RA#hXL?0BD#_&1p;H9*7dV<8y3a zl*dCSIhUbi_@<=Jj!dwuMQ0E@`uZExUP44}kYgWz<|Dhgw3;@tpA_2_YuKlCYP-DP z`<_+I#gd4UFH@X=fkAH zMbFy|G=Ah2)}R^W2RL2SmV9es+lTyHbX^)sPQ9-d}&5DX)JkL|=qPzz(sju@}P$CHEP z?-*K5k>k65ul?3vB$=D#;$p=oSje1%ad6E5V-pTveaD1F?xJ&$IC`kZ)clTo<}ucK zlmDom_}~I`IAz|WC1H8I&+tbX?9u|0V!Lry&00KIQE42X2ac~gb=$q3L>8z~#a*tlEWb}ftD4aGuj0;jashD-$|8jQ1e7{+>U@}QqP!o1{&?p>t6 z>3j7!RmW%Sb1D>H;FT+Y*a~2Aam4k6;J2UC279(OmdUj_++iU_9&j3_=wJN|xG(j( z2P=2~ktHqxNSA|{!O(S_Xr#f`T-69a0#cD8v-g(lfVESMV8l;s`-#8A zsj=sPjlLq*c5|5d__PZ@{MB3Q@JR>16>rpWfnK{#!Y8(jG*6A}1Aw*s7UL{MzIn_j zmJ`A=?+5s2=kbjBpk-iNcb0+rZV1$0w#^KwAd@uDddE7*ZJcv)`f{hqjjxAavQOc3N$NZKRXyN?rlL z4M-(zoqIaop;<~lFa0?{tR8!mxK^|1SlmXw{Ih->bA11yA5Gvo*1rxcb;sElu;$v3 zYi@<~rR1oZYv2qmiQ|`-W?1aOy#V(*57}}yd+uZ8)RJB0<-`vEd{+Ua$`xB4-*e>v zlO6cI!#rQ)A20cS_{-3(ViF@eI`f47Eqw-L?CX&9*s1Q0JgeAmF6H98+-Cjk_4QAG z_|KPQ1dli}XTizA{PhJf3h&9t&%B%J3#-wa*XUnfIVI<%GV-*!WjuM+8RUQHUY2q+ zo8=bWye#W!l9$VscHVydg$Pttyj}&!AKX*IZCn#~WL3~S@jDhe(8=WRP-on~t^tsT zuzlpZ%&3pGPP9=&Ckq0%SzsnABuF_)MxSZIf@m`N1k4~JJ!$%C@ zKLw5`#jytP#rSh@q${0aQ(vDk1OnR?bSRMv&HZ5DfYGSbMuOxmxw7%cCU_>gX2+Kk zk3BZQyYp8{)(9`NP=A1z|AE;MS_K#mevKou%i(Fu7TxHD2l}j$w}2ZyHy7_X?H$Cp z1cqPj>{ka2LhkVy34-$6%nzAclS z`aG(SFRZEkR$#YFges>u05ie{FC+z7PNxRyDdpbjQT&1s(tYDU@=e^PhS7$HQR9o;w|GoSteRN!T7W>4m`}59}F)5$C4e`B5nMx zNZH}@EvL_zF*ZtX2a(98d;C-t$nYRO3FUuotlJLu$M%CaKydRGFdDAhu%E~b9;TLh zg564NaV1^rL?w|)QXY|Z488*K0o%#?TpJ)!Je1~XzcORP-!Vqt@wAUAJQ&Yk!5RQq zjw9S==L>|J$;?z9lM z(@&CjvkIH&p2Qwfk)uIz65yb}b4Nbu@p$av8LrxGCAVi^q7XDXoH{LN`fD>0Ya39u znSN}AI* zUi6aJ=-Z_bcCAl;Pfqhi?RSxCaBFWlSq*0N!fo+}0x{inlOKOvtiSuRi^pPU z;kftlCytW|X()Gcpe%jNseMX~`~;J6tz3emq1rLxBQ)Aj)$!l_9gkoeJ>worek5=~ zlm_ccaGFnDxY-AMiQ2ynXh-Ux#7@HlJ`@|48%FF*p3i4Inp~&zc1?D=N$krVzW8&C zblAfo5&1m9wWEa|XBE88Ih%`(le|x1N4G4_I7h}M2et(2;$Ud8sB+4kg-S>ylpTZ7 zd@;luD6u8`F)%6NmDjqFEk=(K4Kh7Er;aXmHr8fqVf|#r%>4TL4sb`km>0PDg?GjpsFeIOUtB(@c=8XiJ=0XKaVLrZ^vF(J zpAM?rfpQ$dT&|4I=9J_BcPDP!-7^m4i^|+;yO``@3I|sWfudvFS6(#e8sotWJ`sku zSP=$6s}S2AiP@_wB?95`l=aYbBonCT*Vo-&0CbMSJlj2ej~)C^-MGBA=3&_5Fwl5# zP!Guy?<0H6>{3`cU9Tr&ubnqZ<=~2;qE$6Jw6Z=KdVm=am+fRB_yF5(H_A~?2}0+> z9h@aD=NPW!S37txQOa5<1PuM>zGI8uPrG2kv9}cAPjGo0F#7sS5kwPAtnULC9JTJk zi`DQra-&a4rLBxM4sM{K8N$(?pIA+2wj6W{J9XE7Nkk5FU_qmp21t9p$9b^<@X^MW zf6VlFU;3J!>yVQkr&~Lf7eDdn5YTGhJZ=CTPbxmJ4D>lhM3ki>am;sYILLAgUswKrn5+HJBxcht{mN*;>!l{u~pOk zcSo?`Luc>b$MT~{)p==9sb#^&!zMw@$KLL{og%D;je`oy>6q(HlOqYEt8qW zls>j8v4(&bLFGF;Ai1%dK9jaPv}$9gp~2DK>)W%Sj^1o9hb=t&kod2E`Rh5|B!Spb zuwc8syKFRUjvhtuU+G+*1w+~8^J0N0W);bBty zHDlE_MbO|lLVnZxs{i)4zx8xJ(2hkqOm>c<;a^iK$1}bbG`X|gwWRw|aM<+OS$$K; z@+8{%0z?1!Bo=fj4cC3!?q!}cz8v#aT6#abjkICBDF%-m@odAui%pyH`NhaSkxLt& z|L_0vzdmxj`PJ}qDO7_qt(;x)xS!d`dH^+X@A?;Kjjlpt$KmPhP zSPph?ta0q9od@0zLOvGbYJBeA4ejtH*u>3y`~RwsV*6vx9z?0*Y9&@=EpVntCHJSA zcLYpI*D3h^WhS5~i&F)hHg_mv&NdSit8R;d%^pl7C4D!9MH0fmi@`d< zpb5tHs(pIq=Ls_V$61WA3d=x-EA+g2U&- zma$;f%#~r^z)V(}ha`@|Yn;d><*g%<+(QGDt^T7ISupOiOU@XLW=!N}Fdz^{6&gXbwdNANTJh#D+xnS&qhFb?xy&Ko{YnWq;iSuHKFE@ZgDfNOu zh7a7FYiS3X9h7m3^-BZ{^lfE=JBn3WS$gxCNlG#p>8Nyl^i+4;lAQ328x{y9HpWmh z&ji}pdM%9HJZnmFgzESn^EAuRoyQgWJaXjpXjG~bA8@^6Zk7L8$l`ikgm)|+J}*S= z(Uhel&GssN`hIXshD90fv&cNf_nV`QyB@VCp7F-t{U8nT5R|#G&Xpc|i(>L|_m2dJ( z`loyC;%{6$MG=_`zP%GyMi{|~0KA;_ssC_i6kEoFnf2f{Jcx_S7X4B3J^%sabnXk1 zRdRizr=F$&03ZNKL_t)PohE{j?yyfG*ETUFjT$|EaIwCo;+ipz;OykqkyGpaUl&P!twqV9Dap9mQDZzO66p0=$Wj`?8IXSud{E9<8nx?t>7oqpym3xhUe z-jV%UKcs@``S7@&aaE&h8JPOZGx6uECBeo^@c9tdpL}DOXKk==Nvu2L%$QtY>LC0P z+^Zhn<$ySsn~g^{LkEK#Q-5)J&}>^j5zbmGg3Ha2(2$i}vqt}{`$>%D2X1OJ6=|Kh zZ4SdB3dDPJP}?}ScH%t=sBaYbNII8|PD?F5hQ0ro>ef@0@#nL8VZfV%f=esEK~D4U z1HjkUm{D{hulWMQi?adJr|dlk;cn4c6eNP3DXMZrLTtt+zRs%_a^*Z2B0IvK1r|r@fPm>8 z${!voUbz9`S10OY_VLdp*z+9;&aXV17k*{yorx3$*ObcZr$6O&^_`u>E*gLOQ$7v( z>Vrq|j?Dd#^whUQ`^68>FngBS4qj8~a{or0WkQbdxkcu_dOWF6z%cSdET4&pKaAaK zQs#F)H?PJ)H()u#6QRLk&OHM37o!PugPa}MYesPo-_B+0*T`+lI_TOemSd6Mt-s;! z4C?;iC>d%#L}I?YFW4wCTV5Z$T%AHNYB>+o9PAhg-tyo9$;h{Pb>0$B)-Vvfi7RIj zl1WGI2mnCO3G;v63T){%5a&bYA84BTjW^t4oiRd7X)@c39N&or zwQx#*6NkL{KQ;b`i%skb>4KkR&c#ZdslM~}3KIH^`!bfM^Xs7>>JGO5_ z3+o_AULW{$$%+f_ffJ#|w>HS~(KlAOO=dmm&*pevT5tC2J=g^s`M=ETNA} zCm{{s%_B*QVG@Z`1@$J*XBNV+!Z=EcXEZ+~ivLo3Omz;dIM0G;TPCN=bYlK$mW9FRfOz6gwyfJQF?~IAp?vd9PezS8< zF=DhS*u5C+5}z5^FKd1A9Sj_{u%Gx6fW}DA03F%{jpG5#n6bTMZjRrY>l!W(C|_-H z#*Vs|PvZ05A?KQlYpU<;>Kt3HO?`eu?WtF-EbwEIOf8syaEakD3=_ByN#r(~QM8NZx2 zemNk4^Y?{s1kTH>)R_YoAz4r%0F}KYH(wT(JPUhF7q7?JXuy6iV29+uoHsWI!Q<=M zwjHUfbZ49`LdPHZ)*hPV>#iL@8k6enj20p{1{Xnb_A_e>&PTG5eC0Q}{^O72GkRmJ zc==p~x=oMcG%Q%;8edzF=07iR&V?9~uVvTkU*+j@=Gplj=4vSrpAT9IDZyY^9bY^W z$mVI+k?6el2C_hqrg%r{H9C@=#w>YW%yRDZG@&(h)!!voJE!dkrG5(j>~r~DEe(Bu zAz4;?*I-0MBKT@>eCI|V(s2khNBNrz`Bn$N~7c6NqLVhD{si!x~7%CKL+K+ItH zRSUVHP2m0R2$WU!9)Nf!HhfxDZBLRF@h=nKM+g?V{ahem~QQan^es3 zhVw@)Ik`Hy)+l+~9O84&Y|C@l3tut&r^@7O`uJbD>qFy?Q%ZDho_(N80pYK+bF)M|9QS+q3 zj)?tukl*-(o7T|3EsHN{&T`nDvLQpj_jYF^m%VOFl>yY zn(tUVoMyUOKz<^2Zm!U;1gNY1{R(d@Hwi}KM|Botf+5omP-H%eceab_WJxEs($o)Ul3SQb8p4@LFt> zKX7L7gw;4=58lejg9K|Y4#(_+%lFTE@x9m~cDXZe<90wxZgTsWIb@U+@-hxQ!A*Bu zT{n5GJ;s->=*X|+?ELSmF~N*8+O`8*k@IIQQ;3wH3lwXM&@ z79ip;We0Dwu^#h`lCYyoQeLa?lQcrBLmJ2g2Vg*R@UR}Bb~G<2Mc)k#+pT0)6##Zg zKERjLl0=Q4@d$4eBj=hZXCsjf#C|pzh8=inaLeX6wWvLk7@Y{r!%Sm;&N$SClX+$6 z;MG#qe1#SA#dCES+q^vN59YOveSI20V+`x@#;{yu&{p9o#W%L#J@}rJ=(D9H?`gB0 z{o-Iz;@fg++g!}kzoH8!KHxCqy2)f+B%aJge0~pbo8F$W;b>I*X z3F9R|e9oU+#RLoA;GvTQ#Sd={=JEQ*_ANQZhXW4G*2D3~(U$JypY@4Z8cVc|u`mS| zMGsN}AehkJNX6LW$FgXXRAv3#)r&8oJHWl<9kZ4)Ur2V>?UmlYV2-^>S?nJ2ZL zwqe|loW61n$>TE^S?%^0^n4L$6J;pWcj|l=-VMe+j8KzHwO{lVrIT^l$%yrWEf^qGoIIZsb7Ee zDL?=GcVGYI=3?*~zsI?O1MkU<>!64$-$`hqFKM+&D5rk8f%J(RZd`}uwRk(Sg_2&J zT|0hqW(E@zJO9sLekXnl2aJ5?JozC1w{XD90h1m3N5ylc8@ZmiDKuslyT2>0Q#nS- zj~Gah8;r>}=3ZDRDn{ODlGoMgt0Xm@ukYvTjX$r}mNA%#G{q5K_Uf<5vxT)>jT*vBxd>8^7H9ioa^u-a0;@ZmpcrG=#(ctby*Bmac1y-k2Z@`EudVfES8<;*0Ucn)!KiI3BCfHiV!ei!X8$yhLKXZYTRq zGV|%S&Dq>sqq@sP+Kg<5$=&Ne-W4l40*0;8bntl*8OgY69b8tD4_WZ<90-fE3l(gT z4JSVjj=HyaQ-Y!tGSKn!#%H;78Od2#?BXu5CT1xZ7tj>$C!E*> z;AL31%MuM^$}esYb*1JgR#zWXL>oIT+06Aix?e#pQ?^p=*s%`*-2C^;r@R~NB$x0HL^MpKElX2|SH8;jT)`|vn&n)jjA0e)P{`g(rIR+sWoAHNf)zKt-IDMAC#gvQvbXpKpRb`N&7`u> zH~?^p#RMgJmp25s2I9K$LZ-E35jXzE#U3E8!lYpDcDKn(B)-Pib!YNaBkN`3Yh`_( z`!{JM-{Pk>>Yh{7q`UZ$n4vz2V3@putToHFJt!}~SDb6*OGo#l(6Fz|L9xYSs1SbN-8iSOZ};&CbRWW!|udCR3TfDNooU#ma)@Vtvt z_~myj7miKrA20aPt606($N&6#Ohz0~nKyH<+wiyf`A)#}zTa|x4Jt=h5<_4ses)(Mc{pce31ZxB z$FgKc7fr^p15Orx!Ml!@*rp^7Ynw%AW{H73@Ye|m;f&j!aYH}&j$p9SgoFwO1b@><2N~s+ zy*xZ<%&$5gIU9TjEAti>@>b`2!`jg~v6qi|HeVwQI&D1&5nDn!LbZ@{K=+tBjw4FZ z6t5jHKjW$sbRmfk@bnjsHn;mc%eP-H^f2>&P*&0S+`=chEvee8Qi-qmLw4rQ(d;ZNP!|8Ufu85If=SDc`IL2pS>awl?UGJnc&~SnT|^DsM6|A`sn#+YW5>^gxFP z_V}8O+fL`kIC9sh-0V;r@RPJG2q%F-2B-w;IMV%S+qdzu*fCmCZC9J%cpHx98V8I0 zQC0KiEb+lTWih%3&WK|3w7qNCPhx-%<;Kzgk-afU?7kJR4)!0ujh39X37I@p`Jha< zM$U&YonNp}9{;&nZNSgXd=HONb85_q0r!C1d`*v<0?uC7k0jgJrM)K%dRZG!9hHwQ zw#UC5@GI8$m4yeKE9QjWu#dD3`E!f$x_Rq+tVi!yFGjw-4i1JLfi6AIS76OTXIb;xw zBr-w0@8L#c?%{qGDxfM$s@v}GvLSZHmfdRf;*Dg^uN&TQT{qq1S0j{@yL7#kwrl5@ zJSQZyD<6MI#9wT(2xpHnv0%T40kjDw*azH0&e;(a6%78YD_Mg`|0vu3`2G~nt!=|e zx#G--k%W76+>nfUnJ;AXx7vF3110-bAFi9z>5O}Dy!B8BtXZ&OMl24G{*mP@VEpj8 zHapp#u&S;K&;VyAd`1w<$EJcafVG#KQ9wRFAULkTCVt7h6MIk_8>Y6MbbXzVX!8hn z{SiYcV(s$neMygJ_>0Imp5p;9j(Q^=XNGhFc`e)DZ)U3`E5Ri299%*wlLh`Ixz{+i zz^u~%z9yV84>g`Y|3x1H;yc@P&H1&!frATiE|%!r*ql(hD#kupl|bRceXmJhjBC$p z=}UGnuvy%B-^8^;gYwwx{z5~&{yVYY=B6eS8_QtDK0DF%mE1%zvF}C|vg;Ehuzgn7 zRgpz9A6YcHi5K0BWbxXAHuwDxU{_B~RtEFN_3}-IHLMpdvyZJ58%R_V_p_E}Ui_?A z2_N&d?O(pPR<;)LfsEp7c)K*9WoIJFKSFJ#LcSIkYmMCJK0wB;>%{PWfZY=s0t5I> z?R!cSp;9}V6dz#VYMaPrOYeLDs6&g%`;V~p;~~f8AR50vP^ZDX6HtRB^PWOKd`yg+ z5BV{ITyru{&WILenERVjB=et#)N0bQh<8m_TltJ>p27D{r3GTX^?}R@kt;;BeOe8G z1hn&^7Y*j$<{6ei-~Iy!Papspb&#-+ZN;jHVTt4->9=24H1JKVfmmA#8q2BFuuV%y z@wtOvFful*9DKOE30}O%KXK>HODqJ_HA&=PhDV+e7+60!1P?vtZPxQaEIhym*zavJFmD{K-+j^dEEF!TKbJd{vN(AocJYibbJfu_0%2L_pvBlxb zkv0e2_=OCQ`hOg`nW1p1H>&Zn;pFZF_l$>N)Vz(LunFKO53Ny2zPTHK`!dd4qNhjH zs}kAfn)&is%e&^@w$VJi?VCX`2n=*AN-B@;DBpcbB=o-*sw3i z9Zwt_*m7XcPLA)WW_+A1)CPAA%WHtsblb#C;~C9bm{n@9P@9I^(vQtht6Ls-pSoDH z=Sh72cx@`DF#@zB&`AWfn>iY;ZK07~$00a-%yB?WXvJ9oofw1QCNjSdzW!{~@n+xl zqXgz#{GDmqiM%6eY?uo$2cL#yA8}-E(&t%fWs_K1a&Pt?e(8PX;JBp~%p}+TW#nfL z0fKq*p!V?r=Ow#D7Q=Xb`h!#LA;m)_T)WdJamF5LC5_;X)t`VAQ9i#^9m^D+jT@^oLmovRxgx|^FFpo5G~xH|Xl2~Dx^Jp}+P*`wzt7c3++j+PB3`jenNP`|gHH#;)txNDED)T; z_w;Vfn6N_N)Dn&DRfU7(VFXWXsnbs0E*4{#w>8r*_O-t_XyL(87Jo9!l{^1+o;GLH zs5`YDlNFt}(1k0TDAJ11W7`RO6Hc;vv({9pw{~8N$;-t^5W%o*`Q%BXJ{W+6#uy$R ze#y_*+Bn+e#&>)bc0OsmhxK z3`df-wH`(E_UwrZ#vO2Ru<&V=Ylbn7p9SUv(9vf+0S2qOkIlsoR-R$+sTFo90rhEa zCATV1ao*`WvF}luRwqd-p2&kQeGPaAcx=%f4jQq~gk5fEi6;OTe(5*%O4+by8ueRNQEnIO5H)X69pAIwKe3YkmU*)(%O z?h;RTi~-GYu`H%7<(PXb0hd2hu53f7e~I&preU|SZWZjx1GC85-=*)uIMO(2Wp1b`_@6EKSw$IK?q1!~7AUw}BKYwo=FKI(M4{fkDB*x3j2%lc zix7e^OA<`ZRJR#c`m;xSgdNrhx(*f=3vjr*^axroVvjPXI6A%eX&Qdvh~|=d`6slD z;{zQEJ3l3M^z&G(@QOXhb{Im4C=Hs zpyBBsy$Ak-1IiyI(Phz*pFH3RMfas|Urhx0bH7ltfdg%9Q^(QflHS@r=ssKmy#s&! z7Ryf%!sP!HxP9?sI5?qdTk4!9GnR~vbo34$qnktvud@@o$79bnIe0E1CA?OENFbYC zr;jf%$CuiB7Dsl+g2dK-fK!Q%j3_5Y?&GLmF%?4{M$@_Ku<^hghj07f!9l1=u5C#m zZ+SGVM(#64CSKP_zD}`Gh~QA(b7sIbV%|Ii#wKQM`uB|_mw^M9K6+fI5X@}iNk0CN z@4x8HNBV#dX92_ZEG!w=*MIDi(zUri;e!+@vX73Srjq*`pV#LZ$u|7^CZ0lrv+~{_ zNpu@rK3M>K!Kwdi#?F^qk;8kZ{o1@(&cEzsk{E1AIfxufOP5XXV5b>I9Ai zihB2*Kj|f9#Nc^2Iy`%Gb1~1dSsd&T_@~g$4Wb!q@+D7_0?8+~(h@dqq?(<>2;O

    E0OyBS9V?wBuFV~2MaE{L6=d+!w-@e@n(=?Y^Bd}4j}j7BXsA%l@#A! zTi|Z4IPiVqDj$`jUGMif0EI#~BjYBe@2V4n{>Z_L7kkZv zQ;YA<19F}~>c7--uz>$J`PUluwX|3{FY?iKNVuHiaS+kqjlpnYq5S)^|2ccAD%(ra>h#%t?XPRCB>zuLho;X-#aE2}) zB5^cw5GmY|9c4ouPE)c}dZ@Z=;4>eQI$HoccFV51t?g2+z$NbvI^fxeskCeW03ZNK zL_t)!riAU<9nTwj3aYWgx0rr*_GDsE(o%O0CC|b51S6fqfi@GgGi0CYkGlu6*&IRZ zi>1v`ZOeD5~l@K}OylWV77(T@&zwDt0io;f&mN?)T@ zy>$Y$cH^6Fuz+b|A-EzOc#O;H4KK>qgmxb6EGBri$mjQKdzjcT`LV6<#)A&_X8{Dh z{IL02kOYtVM6nyIl~nPVtC?k7h4EX;Jb0j%!e`L45exv8*I7Z$eHr)Cs~bQ z+_9luJnCDDSY76S#az|docM``-hq=Y_=T80_O$r}ZjQNs&`d{dgCkkI;o8V1lRYY$5(i z(j!rJQV>6Ho?-YT$uV=fDShi@Z2ZyX$n0@CDxQdP2@N{qbFqh!XBV438%1FZMI!gp z1si$b(FyMQ0bmZ(3I?Og+guHTCQ4|^t@ajvhpMuaEm@t-`ggJ)qNrCk4LV)Gv8;B=GcXq5w(~(!p&g>8Wy2`a{bFAIbkkj0t889QS5_#kv1M=niwrSlnwb`M3+h6aGV6#HJ zzv@F?oW@@MpIpwHumo#l4Tq1ssLecbwnzK=tp^9KYjGW{vj)%y0F1^XRn}A15qGpR zenQYVYcbB%IpN_j^SEEvoevE}k6LQJ#;*t~V-H7l)(~r$(ZwQ&21+#ytAIg4&dx3V z{$`|ayJWa=PXGJ0_z)0O)`25}0g>57HrXHCo>@noV$pUCA2uiu&&3Ic8d7OUp9bW} z2VwPfq@n(~U*lvtwfETpZ9Vw@wGp6XKmieHJUte z0LlHPKMBn2@u3HKqRv4OfALMt%A$aoH&d|#D;G98l>xr}q#Ko-tre3R4A-jsQ)7BU z!`5QPHMn?kqgVb+N<*`iKB~;)uew>9xw3I=)l3gOUR_~LVGNW;?5}<84_=QXSmnN# z0kt>;pC%f}ZXQABlb3r&g<{6fx|H3Y`_*vdjDk-0D)+rPH}@)nXSqhA&KxD60GVTL zwu73s*=m4}NvQ(xDGLM&EZ^e0FNOO+AcQF=-V0O)~??WW$3|{)NUy zWj1OeS2sSlND!d+76V9-+H+Eo+ZqWM@=6I-;ze@vZ0x$oxYaQdk38$+-eWSQc5WYk z%QHUtqt7tycgITtG-e6`;9y_n_^>k@17Hq8k_<$gcT&LN0ELN*8|kq{7rx^>rO4C+ zAT&ILoPwR>Sf3|20k&(M7zTSbD4qP5BmEp4!V7=NP7thyjB84jcuo#|02q0k@7g#{ z5ZJ~68djo|4480rLXKyyj#*KYNsS#1J)F>zXXyA+8!l%;(ZOyH17X3lLY^J3XvA1N zNv6#uPSR;$iA9o{WM={yN&KZL6Oii|r?dXYH z?22EEEgbClV-i(`qiQ(p0dM`QM9=eUi}*WsjLQgJnMWUx_HGT*5Cc9wHFL%0#v8=X z@gK+}iVfWI42R$49y-|ppLxQM|A=3F$~g?pLYwv&^Y}#SI|RnnX501Zj_s_FG1=mS z%MA0xtmM+n!QnSw3hk`+V$TOL1TdUG@~44=4LzBMHaQ8$Txtaa+u1YzwyW;Qj8Uhw zcgBlo^VyeRlW-dt0S9Jf^K2lJH&;-jHph&w&CZ+rSizAjAO=VT{pjPQzJ!U4fU*N) zZXqCJA z*klxc@##hD#7WI?!NiDGa=IQqzxri|j&d-`SD_GFf4ot&cB}Y8C;7PJXd$OmOD#*? zCl0t%PL1QIq}#=)&WU+?#Xlx8HxJ3@d*0Ex9OHuPLsZrtFp z#@MOiTY6EQTKy{yVrRH9nS=T+GR8Nj>%3|eEJmWR(5cP;jM)0f@IgsF5+_)D3@29I z7IU`LA33X9FoK_W;Thg=*Dr1JMut)Dr_0c{14xZL*VR1ha&r!gx`wZGQb@-839SSX zjA(FnaIWbjT4J2DV*R2YUkIZRUtk3D`_I4s%isR>U;Qbu90b5c4-%;f)JJTFEWdun zC6^GvNIEuW*o|CUM)vx~1-cxulb?s! z1CcYW`%);O&c*QZX*Z<9AQ#~0VD2~4XgG`FjxA53g7I;8yI4Gf`%{mN3p@T#GSnmw_#AKA6!2;B zsl6G^Z(y>spqo$4N}I{KK)yi;4&vN&?STU$lZY>0ik#SQobw9?-*RL^w(SPhK%C3( zj8ympy36n5_OZq0a42BD55O^TQz{>A1Qxr*nO@onFbQTFt7u&&RTFh??%iZt&B|OJ z+`HODwbMR_qK!}YaORS_b0u98qy^_!C^!qq=fw!!BI%GNjK`K70gf3(YsDggG0(bmW*>Ecby`LUtCCeEZDTu-*8{ zX&0n%_cAB}C$7v*F2npTH2t`S2TiT`NYwNJ2H*UJfNyjT%O>7r9>7TsR?83eTyil+ z0&!t1HI6&*+p#$Iwn3dgC%^WRQIJzOP5o8*Z&$Fv%BXsp)Ia&IZzRS`)AnqB&D;1w zP5sX^fULXSSDIgfTYCo}1A0Q_z%;a`wB~gi&<6-UKK+y4K{>nWVCN?V;vfgiBM=G2 z`SHF$kV&UxtZ(V*&$arzGZPj4f7lMkPp(?qjA!%IYO?YdSR{P+PX|4i@NZj63T*W0(s%(=#4AS=iKD z)U+u@mTg~dlHdGgFgypidcI{Zh428)gig&^^2Hd1NyEmS)2BDtuokmzq#rx*rW=sW zN5+F|PDEj_EEX>u8{1nHwOu||GeGhd@y9s@d@a{#>d;b@!%LubC5qPsTmHEg12 zXR?I($Gujr6l9K7AwljsfwYjLm$cDGYDXShT(;NX?9sZVp!T`;M$?bo=a_RPwdFn*@trn1IpgVV z3)k@o|BbBv91lLeum7_PeE~1q`UK}<4gDj`+Jl$5r(Rw!ccC24& zf@UK42GzXtZiZvQSkG8OqURJiUGQn=?mnj|*sMThbRfTLD8^@ZbcKm52P=-YwRIMP zN+Obkp}yYGm%WF2w6*u>FhPAJBVLnleMQ)B*$Euq#;H}10e3Em$~Z2-(Hrc3 z?@pxN%(11=_qs3!czM7loVj=PYxmzT_NJfxa!>f*{`%LyNm+~F@Bda0s^C$=5$2iM zr*a-@7B(mrV4TyWM3_;vmuS91iDGDoyM3|H^CV*b7E~m{{-6K+zk1h|ep8MGLorC2 zY;$&?X+Qj5g}w^m8v(N^_`1coN9);Br;z9zWE6}3yrAXJ6D}2Gd243J)aT!`Wr*Bw zxg0ZeD94Rr$qm|eowqkNeg~g$Gx0b`LB~C9f{_@mzr?~ezrXhwsu}IbJg;Affs=zB zUN~Ux)U}ot#|J2X|F?e=pPP%<7CE_eKIi9!35|dGyVj?NH8%TrSFCDqjqaP3)h|S1 zk%G@4yk-ngEmeTD9E|Zmu4E3&2g_mD2FG`OitQceG zu^5gtrSfVOKmPMSe1!8Nl;P?T@kfl|cWH!g`<`UK zW9llze|BK-_>KYWX}`sZlz>+l_U zc}U@m9*q08ZghTB^6VV`8Q*-Q=L6XfajIcqD>3 zd+T7>XQSNQ#x_5;`j3D7=gql%F}69(+w8x*#T;_@EwA7Bo5=jCmNxEP6Q!qRdj4( z-iyOH+OYHZy~qt>7;xbgkG;@<&qP7&l+@sTtn+HQ(wNUg^c@oDrG)d16>?yJf0V3> z4DR`ex<04Ux8-iFSxE}!`H|1Ldho#FU}hiWTRP7$7p1_*B4WuUE+Tpu0=dyj_ zTE8_hQdZ+SdF}*v+>GomPO%~;|6!#6VbDsUF8)OBN7LKDUD^gZ%H^6xY&ZDPaAK_+ zS3+apqr@(vja3F^0WNnlTMX3ODQ`~dTgq9VG@wop!KXcpVP*^+XRG4N=Z;?sx2h5@ zNM@tfi4|N%?wAE1o1FPgX6PVu5A6a4jIw$x{j8E9ZJnm}9(rp&7z-@C^vr8&16m4@HX*kC0wi_( zv*Y`X7@m;qz)W-=j%`ijjl1i^Y1r{?D3Wpl9yisfjN-)YNBzO@xcY(z%eb?dOZtzm z=)8l&96o#6;ypV4}^iFx)J zX&%7QpSgdR(2KFPVr|phIxA<9K{5#1FQ;oCOzT=1`^M0n=6XFt7UjJNG-VSL%dPV_EL?x61b|AH|r zE+6>P1&nwrur6%v6Z}oQgI7R#8Ib{;@Snw3hmK`7R*)h(lq76-&cgwDc93YW&DT)T z`cY{eq&5gAaAz@r?{ z0ppwj%5wayCcVrpH*bTHx~^K~M&p&uc6>42H9q!-!UqrDn-Mp7`t(CihoKQE&_6iV zPoJ+yOMoxm05i|w5nXMoI}5nwlE?YhLdBA@9fx-I7h2$S2QCzL{FZ^wG}WGc2wxoY zkWJQHg8|Tip$UG(Kjt)P=w^(PzC(vQ#`JfU2+!{&kzA~MW!5bS2;rU<%1y}3B6yax&b)25CsP{^m2EdwtA912NAUVa#Jpy z&|vZA8a)daUhrNlU??lWl5S$d#bj~{S1PVIvk7>uJ*y>#e{J?{vFlb=DYS~q0`QLL%MSA;5mvdnVL(8p&`mK_A>WvIByo~`US~cDgN5`+B*EGpaFo@w zs{=N#y(drxu*VAJB)}~x-ei=Mq z92-p9kG~(;YB-eHMJTUbc}>pHD;hKyJHXg{0}q@Gz0)$Z9H~8Tb^(sZBHdc?SQXxD z`0aFeJT2eQELZZETxN{9^Z^R+@;GeOx0zy@AM-(>c%Xp68Q`%)_>wKQ*TR(tApyq0 z3t&A|RLo0*d9%y3Vt?_Fvsid(zROJ}V zAB)I$#B^#UeityP^lg$(_|pC_ihY9vEB&7-8x8Xy?g7w99yUSa|6z|72P1P-d$~i< z;vItS!A=`pFzt!9F|J+AU!zL!p}+AtbrW*)GX2yL_%4s;`TAr*qW>u~$)o#@4g0A- z?8hIQJs#b!P^j}b@~?spkOPP^{NEJT^bg~)A^FICgxrZQ&=N>3W5&oap3O!wufI#u zdf@}7`xw1*CI<+(1@k=G4^N(4vc(6(r)V4;|2XI-5Fh1NGJAH$Y;FT%Vv_E(s=iDA zb;ac8x)GEQDssq@i{IE>|HP~#sUE-o@z4ASrL;NV@Q2ai-F~il5*Q*E!=~eB_==6u z_FH)pMjtY#y!-~P!8Ugw1ovLczV-*`P85Mt9=HgYhkl|cXQJdwQqi%#U;N2+X67cBBwXI3yE!ly+4vw%DC2uLq0txx9A0Ioe&$DgwYluE zxxn{&ed4OUkk09MQQ>-uI^;DJh=q1F%q=*`c%*l+jWc!SmXbKD+vC$+J@2vbeyZSf`6AIF|Yb5I2ENX$)`w{#Xfv zw+x4c@w4s%%ZvY)hpU-8(R>+al5|Jj*5YwKJg}t3ig$NS=hP=Wj_HWX*Q|uR@8pEqn5Dr8>0JtB9<1s&8 zZ`?nj`~U;-`OJR#I@$#znd4DRE-wacid=tvr|0+clso;nAQ$A8wiAm?2N*uF>N8i} z{G?1T`v{Wf2K$qEXXxqwqIU%z8xPtPCvMmm@ze$>ZF8cj2Rxix52By~W$E=2ATqY# z@W5aoz@!ZWN_TRafR-3K)1X1mvR7ygjD)E_d~G=G?$ltTAxwUPI*w9V&qm|bVPTzQ zVa>#}tWAv$bsi|72dB^Sn8UmrKP3aa~QF5zf-LU%-by3Ovtw<`m}J3+Sj? zznd^1(QmQ`3a?F+$=TUqeTV=SpU2XR2DyF9gqwfd$@DWF17JxGM>j0WQ62ePz&3JB zG#rI+OZ)OqLiTKF$`UOn^uc-$ipJ3*Ma zm*B>q1eGTj>Z<=@7{bEn+D;I-!PKYzYQ!$Qhoxbc^y_HCAJ)?`vU%X&`Ir7^%ye@4 zr#*m>;HYJPD<5dt_@HO&b~L-zFAa-^j9fP>Y^bjHOoV#E)HUJ(;-C zgE3>MadLy*b!a&|FyndH%dw|tEQ=o=QL|U(Jv<`x{HZy-GuFn~G%ponJHPj`CoEH8iI(LYaa8I6(#d@wz;QtXZ=xu1bGxR{M1fUB=-I<8-I77)LLuTm$- z)}KEo2Om>H9la{r7j5^0g^Wa7q)olp&uJIOO6a3!gObrW#XRs0D0)Gq@xck**i+l- z>08tG#lFrkbHR@tfQlb;i3El8=Uue~{E?TU8i!5n3-F8@V-yw#`mfHB&mKhlQ7`Y~ zlQFg{FV6$WE4>{PwD1tj$qIOLbc`ekmiAG9EPV~2IBfU z1;4~;;N5Hq4*XI`VddCCpPi-0|l)ZlY7n(qkA zjXaI%%)@7^(s|iruFJNZwk|LZx0Kv#LipT+h+ZEH8h2;MhKVpv7df_`-|C zb|)z%Qs$Qhm5d}OjFVnGht0l9-1?Gj5~e|d{c3}pb{aH<6XD?#Sh{dF2bvR9&3zrT zy!XrU$!k~zj~xY!bJ?On!z0s6o-hX6uM!~BUlzagMnq8lV$_|rh{8&I1vCdn5bZ%K zt-%qt7bzEkz7T}p_l5c4A$1iY`R%o-!9~p3(+&X0&g;{-I|Um$6Ux5?*Q>5oi8hXC z)iU+u1lNU*Mlj7`2HruEyny5T15rmb76^Eb zGC(IGB!<8E!%3jwaN9KX_mR0#h&RTgZ%rZynDK&-1>Nnm!?Uj4fsq{wy5^11DF#zd zc-|WA1Z)n+?Vc@TgAe>U595k1h5YOf$1bbk@jlDd!G={Y4*UlnO8G08E7G?-yOxxC zQH=-6Czkm!!u8c|D(f^CmK5<7eHeV=%*EjW_mhWf_`~VH!%qvup!&3WBXVHe)yU_R z+U=K3+V~~M&hu)!s4h9|ljQe#j|0nzsdkZ;nB<6&c@E26pz&F`{Y#fi>6OWzr|3X4 zuq5KwidbpbP5|n&n^aq9WT7H(Q0DwD-l}@sT36r@93i8X=X5VzDhna=Kq=u@9di<3 z7%2+06A>-%o=e+D#=)HAFD+*2IFhQ|!?IL}$>ocMuhU!CQmO z%#QkzuesVuyol^D*B1G!$oj*erA^0xn);jTXv`M*HA08`V7!5eK#$FWM%PKiJNu7- zUte^wgPUvg@~~QE$HrJ_OAAGnV^6<$9WbVT803W%i8t_zY-Lc{aTAE#$P~^CjGWSE z<3-);#lyyUVTP@CtHpnCuqXNO@ZEp;+d_4X9X_aZ6Tv&CzGUxbnVSIBCiMPxZ2l5vqj7|oe z^ZyA>Ubz_f9F`a{=s;+k4Ndi_$*)hnAK4`yES}-LKP-53+y3JZ);Zr4tQ*?O&#;m| z?Qcy2eSX^P&&$te0*-fo(dY7ZL-9EC&3XdI2c==GmKNibyt9@_e8Vfffenj+#2Btu5!>2N_;#nh+LN`;t2pD%OzBTrs?IV%kKaMGT%mUMy?cmwqU zU`z%%vZy%%5*`PbsDpkSF>(|cO>k0Thr1Xa%BEm>cFTu=Z0Aw1wwTnGAAb;uF?AQ* zNkrv=gcyRW=QjOEX4~q8Iw{hhlzjTnm^$|5x&W$NaKp_S^qRBD!J+tBzlGsCAx6Ao)b@)U(2V((9~7K@s_W4QFIDDq|K`?L)*t`U@&n( z@pP{2wSU{y@H+x^FkT^Me0=c4pGYzP&YhI)38D;9(N9! z<;KL5_`=JFfdsZw!8wUTP+MjZK4HWVy&-m!VB3RRg)6PNnVb@jaAg=gT2v+?bD+$LsfEhLvyD+BQ692F95)C#)l1DNO8ZdobU}lvDd`*TZa{^7z_X z%IK%PWis_m=ofZ@f{?c!&kpL-(AjC?y8)=o_%|4`6%X|fUtB-DBDP7#jc1E6 zC^L_)o!W_k%`D^JJ^aHbZ?u}u>P1ICzU!;G0^-cC+Pjv1XrV%40;PI8+^{{qjp4BAHVT=LErOjTl#&B57f%$e=eo%Qk+DT3cgQt? zygl{djolzPGVd*xAMa2@p-CVB(m{O^>cCANZyvXIZd%glcKXP8#}+<_i-~>@7`(zj zGPeNsYJ#D>`QnRXU`=Eh1mjS7Zpn?u2uk71Z3>f*420A7$MymD|yZNFNORbh@C6r_)2x!;)y?kSi$ z7biw(BS8A)l`n8u_uORW8T{wYr(<=1*agoJniFFKXnaeFz**;@<(Jtdww}qLtA47( z1siny2tnh}r!Z%JD=-V-zHfhnNrHQ_;8Z7fR)JjsDpN~~MY#pO#pFdeCeq!)dG0wN)o+I6CTW6|r5aB)6vy8pF-Dm%k*8I{+GE@zZfzc1)b^_PReurEH;B0zas! zpP0fAi)1lbU&@l{c}bjd*Z9o|yy&}@{k@|O4&^vIZJ*}q?W$i#E$9ZE+-S!Auwqfl zJ{Wyni(fHWi^B;QG=>MaymH|kmrg`cSzF?)#YceRcvbAV*Zf;yQ!$V2Vrz}D?VHlMtkLP_lRtS6M$;G_%83Ip3U zhIMq|R0wv;94Tk?E!s#xZb(U#jO^kC00!@)(!kG{O=XI$!wWrK(6pVB9zcUq zjLrLiPtfDGDJ<+_UyqKB>md;qjI&mdg|o51~omfzx;yPnD45ZE&xkoueYs? zcFe{LC!^H~DdXdz!Cr3MooCux433t0#z{mY2xzx>=%}k_Zp#|bn1xHHH|D&|_iw6O44agP)ywZ!QpqQH7QYfXTKH>QkdJK5&$r;R<_-W>;% zgA8BY!!p}rYoqL}H@^frT)=A1#w`}2MZp#un~mDd07JDfTAb8)c}5o-#s|cHu$~`C zUwtIQzkPFUT+e`ADBKvo^AlD=>VDc|D6yZ2hV`gY1AGO`6Q7GzLprw&V&}6_*A5`H zsWUUi=U<$^`nr&VFI?B1xYYNqOAL30;DxF!aM2c6e!R*`dxIg-JV_-$t*mgaM-WJk zrm>F#8}7HZ?3iou`B{NH>nE9GTmW_oZeaL80={l*UW84MQy9O#TOLPw!s7jXIa18Q zn(%$lkl!2A;Tl`^6+mO&-m?$2g^mlvpiRQn+&xCvxXV(pb6YoDYrB!m)-Dn; z;_IEmW9CjO4jnqL#sPorj^k&OK-vVqWBthI|Muox0XR7*;4KX25`c^?o^|^>b^e|% z`Z7hxxMZfT^y7l8Ij_cZvz;@0_%VF&4$l~0tQna?dB}#Bw4cWaIZOQ$ClQl{p(jvp zTlYGve#oW^$8o!TSSRt0UxQf;h#2=0b5!6g21Xl? z1GX>z^kS3ziodwU<^kb4N^J}Lq~rDE(Gf0M+d3!B(O2w#3kI5^>IW8vyKAWSscS@+k?m^(HoogM0BQc5CI6C7_1Im| zjPaWn0*((*&Mo>VWPXk%6K2#;b7qq)o8|*bUTQYpK*g_|Cj=ndZ^Dcb$1$v zurn6g`MC)4cV7g{4vS|Zk^m1__r6HT)nYBGALI$do0MT3pZE05w!-iZDb6mijCp-_ zawQQ%Su3<~!`FOD6}fZmd_K-?7%r9g@vJX{EdZQ0M&5PTurY+~)4(dQHJhYJIn2vA z@SFVf8&GRqG{VMlvJ-&i_%b6-4(|BKuJ3#hV=Y{{lAR}trXzKIG$L=Bv4GKyIB(57 z#`XXN4{KWT)IhL4FppC84rj}3ST{vZ#Mtf&Q0Qn1^45%OVrvLjt^b6OL{i$mle{H*OP@|Sqj?wNn26k#sd~R;V z*=I`}R7Jqnsdyv4u90a)b(%5-eRK5d{E2S8iri&ei%>8-i5OGYWL%4BQ|G6qp0Lj2R z3n$OK`*D9MsAakQz~rFB72Y3wfpmkCbX?&j|8Yqy9_%>)5SJXmWBjuQc>w5$%-coH z4Sd4hj{?Wfoc!7Qn~!UcvG~9N4AyROf)8NVO?rRgkasWorOVln(i&9#+O>P+YP4L} z|KSz&26wvf(mntd^S7YGwA6gEFI^st?1YILx#hu>xkVontl1}XfWr#Oi<7ZW&Amq0 z{BdJnI&WQ@{AD%|9$b5e=R>syUUMn_`Tyw}mw5d`c0lU)askm8QzNf2GOeAzTL4b? zm7k2r))ybOKdr&R`_dK!n<)vEg@M{zY+0;!3^m>kOh2rzkLY|L*dY~aEI+z1AP){C z-&5c`XvT3dvZDrs@og=VL@by|m~iOeZ(n30bA2iAgCq4~8Qb=W@{uDljd58y^T2e% zjZgl&a2ZT(pODBtQzpvol{HG9A#qS!J~6-6%gvbyC%kvijxXSxb*06Zttm@PZ=Axu zY$_7OMxcO|doUO~4YpHhT)DmR&s+l^B-~3k9?ZWD`!}wpe|C4!k7%4!hwZWWow97i5#-$gFgS%05Mu)GDC5;hV7_E5B?XROM&$Kj> zu$^LP>LE3XGQOA$0}C7B$_^Ah2`0txtmuPRW4NukMw$@AxmD*p4t9K;({(fsRNzC8 zzeD2p@nu@U{D01^re>x6G4EQ|cUa?S?yb@~XC#c4>JoBXYKqs7_K5Jy%N7aCB|eW4 z80eh$BPR)3isJfIkj}Ajv~&6rU2a?BI=JKUEEB&6fP`IeY_d~4#|NV~fb?mv2TN8XD@SiwnJ*EW*gbL{ z0RO4e|J$TgQZ9^!=LlF>r-z)x>I5A4-jU7aqBXi-2O-6dQ()xy(IiT|N>V}#Q&^0; zvv_^v7=A@q>;cx^u`OdG#$DXISQw5;ov{&~IhdIAj~}Ap99Lk#hyQ`e!gp%ZSj95+ z0!vcjH4JQigf--e2}m39{0>LwSz{1r^l)HY78*M*L~5u$a~mVElWo2m*4!Xp@jF|> z*tKrK0~2KodpD*bTd3 zB4B)d&kpnzpVNPAiFuL<yJvUT3}NC$X4iD+A;wmZjm4Q@)xtNoz)1{lScjjc-gF zxJE&NTMb+2#|^OfOP6r3HV;k=E`}!9+KXXF^tk?5V28#;x?sRwH*9ekw@l3&^vfc) zKVS{G&@<*a=Eakd8}SI{9qoyChlw`40jqi&8@V6;;Kjro<&M6$Vx%D<4CfwVo9oH^?`zx=@ZQ*?)NL{mH@KX5ed_X!N zR-T~Cq;l5w66W#gHAA!om|F86pC<*Qbh>&W^5elI$LvDtp9E7W3Gh9NM7xVop2Xn~ zf0^WCqIn%PcT>;?U;CGMlDcOng*afoiVSRh-J3B*hMROc@{SW@H&lJygwD27c-*4S zKMvaBGk(}wk!>dsvBo@#yV&1ecI6Dra9WQG?Xx*>?mhwOY8YPT<33`HUA2T7Udny)G@< zIX25xQ1UfD1q_5GY)))0qU?MbPTZk+wbF&zyxzlAou!ju}+Wj z;pm?>vk0=$F(Ce$Uq?Py_{~SkupuNl>nEo4PC=Tmq_sStnu5oNQ^39lQ`8PyPmUDZ z4-irNq@@9{YMyHy_b$(V<_aBo(&D4J4?5!FFC+zmcsvIOZ#ZHr5!A>F9Ph-#lROu> zf^V^a9IpuwlibMrypv)!$T6zD`Zq`9UT=Y#cS1Nmy5RL~XyarbvrhYh&9%8-C=0&;RUcmZvRx zYa-Fs*g36UL6WX~umf*3KeZeo8$|CNPzNU0&<~JY%czOF)UyO3$)EF(y4(oOm{@PT zgadj&N2UOPZeb5ePJvP+J zIk-l`^5pjD|Du1Wajs7!T=N%y&Ed$C(5cs{O;}|;aKx>B9JPP+Ai%s$Q1@&iC3-l< zh|dFX7O=H89A_xNuok*e_Z`3uLvH4#oV}13lmGo{FM-UYJRJ=_l7nF#!ZTnW{~=%+ zVz~59aJRS5;yPBv$BY2FXWohZ7g-J)O3tZV-u}~MAToKQ;ci~YWn;4k1N}VE9~Ge} zDuQ8yY>Y*SZ}Mggy&}|Pc&E0NA0OtrC$_AsXTZ>BN`MRiF6CoW$+1KY-yAfh(Re$a zyiYS9jMdnVhq0LZ7P)m#?w&V}^9&47&U-lw(+X`y+1RBY3W=JSV=%9u{U7KL7DQ6{ zQ~z;WU${J|hxICvZB8AZDZZ#y3%k!2_KyxjtFE4d4$bQ!_C}^Ynj=Oh}LhA?N}!=3aF5 z_^g)L(SFYN%>MNaE;2~u^yofl7r|B|A9zKTRthIynQ0MQcYgs0-09pW&J}Ui5uBTk zK#$WI5J-RTakAX$q_a;&hX+NN><(}Ug&#%Qhcj4MRN1i(fPHpA@H*MfhK>(guZ76q zMU7$?im8<}tQWSn!jKs_>e}YJesX%!HCFAJr=bY$u-DIGeYk8E3c;OBk;e!8jn~gi zy$`CG9EV5gAy^}eVehY*^xVQ8MUy(Q=R`))f$93L0Cl;r6VU%U#*LRtrxvA%so9kC z&W6F*K7mizCfcrpp#S7bWCF<;-a89|4R>Qmf6g5fxS)D);JcOrZI*ry3eRdgA5WVU zvZyOK;qeS<4if$#1)|vxti`|^yDUQx$pUU$m@EnpR%B0mFi_;RSoGkoa0!Vd-YFm) z#jkLTV`m`8M`>GZ>{{!<*b|P6Q-M3c&PO>J}5yPx)7?{~&hV2wX~PAz}b$A~t3y}#ow>%k)LG<#oTro+Ny zW>|;-500D9j8W3&ke3OwqavSr*3=JL2Sf1kxVSuaco}dWL*=u5dodIs2(FWin3FVj zqpO$LkBja%me0;s+Q=rn7$?|b_|l+7!E^wEBnHAb1gl1x?C2Z$S6hC@`kn#I`eOP* zhc1Ssm^^T&BRP$kYUqncQ&~iD@?fBMbKplN|LI&@<~9uwMcD0x?SRn4FLI=kv1rRL zK^I!5hbZ*tg~f5s7LyyYGD2rQghfcc z!#hE3-3^%BW0d{0A}o$C6E)$T2ln#B2JGT<;g!eF_CNdKx@4OM587W}1^{z|Qp;b~ zO#0b?=@cak_Y=io^<4noZ@i#5`pTkYm~OEGVp9Y0OsF+?8y{@m#>F*0B{z~_U(6i2 z^htYPjpy_B=UHp{J$U$@gO20}2V3m?#4oANUi=F`#wbBZJwC?b-4Fckl`)N(OUxqb zma`HfwLT2moQrC}shnSSJUW{Q#}vdn$%~;1001BWNkljC|mj9ROZ^Nh5qedmQeK_ku8%9nV;BfKv8_>`FFWKFOj7t3J-+I0N_Ui^-=6kpW2UyznwY=mbrzp;TfsJm+eZjD2wT3IDcWKDTh1H6Z{kGHLUD@>x?V}TZJYduJ@nvqje$J0}wCGz65Z&taul&>)GY8e+ z1ec7rAo=hg-vB(HlgCB{SV6Xk{`n#n=)_8-cT1fA?w=>TSUF1X!2_?SF%cMFOo9u| zXEK!|;@}4!1vxv8O|>aU_|)a!e*b*RF*)MyJZr=QmOPic1G6cI;qsGV-Z>ZTyuVYEzwLI{2bJwwb<)(fAlV{L^ z2f60wca{LDpYgPD3_<3lr0Tm=WCko|HSRo>=X3*NDi7)J%w-`Q7f z?5BbzSiu65%PDrp;^LM9%&*|l#6VlZgJld%)^O_`oZtV!5tV1^o7Aqh;<7ZHltRU*8h+-oMtwu6X7!DObh@pMuL?PB@WF{c z_L~pq?T7b~HSlT{^h8VyjU%jE=g1~6!My(M#7f50<}sdnJ*ZO;&!T674ef~JEOM^t zd}EQdd$2j6e$EnRFMpe*U_Pq?&vybznY9(L!$pUTd_v_31Wwy`ts~5NokOx%+6j5z zyUlBG{S6+xM_6C8Ib#6`=OtQZw^#z=OoCJYeYnR2m*C+LK6%!7b`{8(;ImG`b4*ue zS*XX<+&ae3DHKEVqkr5OQqo{%X0`DM-+@(6O%A?f{82PQK8q0-u#K3_AS%RuYR95x zIt4YTx8W8Vd2nwRSdYJRE08-o44KffcI?PrPLYRLs_a z7~x+k%+^(HJgnmytZI$tnxt?^ z1Y72hDYzT}{_L;o63DIx#$i%~v)>#pFOIWNCOV*Sb~hOMwNZh?cON`&13H`^{Kr_{ z?-(!_Ovrh5j8C#!IAj@5g5NLAuIw251>9@x7}B3)Kt@P~aU#rhKi(~v;~gP> zmIM{A*qr26GOjL)gP?FhvEb?Vq+gCqcjK&Hm} z{HE&w=LvW2=#QC%8v7-I2HOwK2|Ijh@5cz&CnS2CO^Dm1so83X9naH`eaGsuXjN70 zonFtfX$Uu_-x#e>m+Vw8X7Tt3Ai0+CjTgS`I5iaZMke+LD~R47CYnxhSEwx_{Qc2a z*lG;^Va*)sG%$|B0*j~0MAkL4ADdxxyakJ&SV-?1Juz_k1_}r9a_+X55p!EVv?Rc0 zchuWCfi{H)J+C=~<=FB~izQP_$f@_}!*dMqjmNw*OCGVk{zjDH+iyJd_+<8MZ8lr- zM7IaQm7jya<9dcNgO_Xh)H!o7%J(Tg+?EhZQIXuJb)eo3Y|Cz)WGn}&RBG6j-^y-UsY($EQO^ErjH1xL3$JM&h` z!If;z!}}Uu{YwpJEYb3$gKfT+U8P9*a+c+-=ZR(i}{% zkQ`fVFfCul!d;pY8Wc5A==W_?UcrQs?D5CAPT584BU< zJRHDEZ(<8irUkbqfm4!)2&Tou?8JUuKRA!?VHIrr{S5i3yX^HhTx!bx?GKWn@RyA6 zZzG>G?7Ba)QQ1X6egWzqdMV)licDN`82~-dl2iX(JoUPd)B2B3`4E@k8=tA3#B;sr z{CjW~E*Il?XqJZ|-!TH7Ww@E8!LL2GX*oTM@807_69eR2$)?!%LI^ua^d#t=qaMtE z*E##+W9 z-p_vU{D5r$dwLu^Vq%Ki-y+n`9I7RZf!cN!cJ{ygO{Y^;M;@E;g6O|=kof!;pPCRX z+#~qyw=8fn`V|KWwf)CG|6vYUhwvFw6Q(wL>y-E8O^qBWnEeULPD4|ze}7&hW5$Ed z_Go#Vi3>)s)K+ruc=gU4=&adYD}x8dfzW41g@-R{!OebP3jy~|i|5yYaGyr9t zOwTkgr*NFDEsbNj+}YRmKqJzP-AecwnzKl0dGhMKAeonjp9H;6qMa~f_|jMp4w~$5 zr{mQ>c87xrKBxNz9=e*S`ME`1r$DaZJhyv?3N~oZaD$9aBEIHeEvyYjbSk7kEO@#>Znz zz8fEWY#aWzJHE`WJQ0Wznd{K}Y-w?wWfKlx|KLgXir+BE4KbW4nNNn%F~lH^fWut@ zj%vO$K86$cK*E7Db(a2daUC_M_dW^!^m6c8fYh-;rZ1PQ1;WPG@L);WrxH-+$(V-2 zSH{8}(bUC_JrgZe_mFmgsf|Q-AYSwAOF+@flSG&d69C=0IT5BH!GreF+gOQjC`0?t z*7z-qWkA$8g#y2^roa#$#d2|j#u%e~6jfqAIHuodKzm3t!8$4niU1G4kE&52b z1(zBfKNzLi&xYDe*#6qf7CzH6ZC;>+J0o~{>+wlJvV~r6gKie*vU;^}f9H%%*!jfg zTZ5lCx!wdwuztebd2oM5^L#4zpUL3?PO$FCz9cZ0B@;u}?#4rS3@gsQ#u+d5Jo}(< z-Zf=xo$;{^j^#pfICPTlrCLplGY<2^Au%}v06?b?fiaZEFVT$~_@@Z~G)K(N7_1~! zrMfsSF`kKi5z55tPXZn;=DgH9`M{(gg{R z%eyk?DZ^r6Zy(2N{$EJP86rLEnV3&8Uv9(=kM0a`suP`Y#zlfHZrGsD&4$Dq9?rWP zM@Zss{oBh3KDpb(ex$jua(CO@r_c*1O>(Odgnwzoh3l^kU)S}A78&j2*ZEMUAL8ch zZ}|LSL##WtspD!dxx!iAEi=wu|4XdU`w@l!ui}n<4Mr~~cvg13uvAgPBQWEXmu-@< zE>U58Z6)7Xsn_Ym>;dHX$gT1I%%bIr=Ct$*a3W;z5^}W=Un7g`DOo%Yz)1`}jn^{2 z^ID77rD`cKeVRR+7&gei@oPvYW1rGw=-9SBd)DQF`EcmDc}K&xI_ASeiuV=Y$EGg^ZHvGhkaK7`3JuKAV<2x~95zXYXk~p8|E|R!r)GfhFAUg~4V4XafMwFPu zR*OD;B-w_4U=!ulX1v|N{jh(SfwR0&nT9s_nC1Kih7GT4o1uNAgQvM3%^brK-5-i*#dvWCC_pcE{#JZML{veOlpi+1@~D4GZzj;VajqvLxJ$c{_&(fhgc z*yXS*%i^KqHJ-mHBN^J|L48hq<+FMmnQVzkt}&D*f98v7w(rhHF3kPf5*v+acil4I zHqYEL=mogga&S&fGB*=rLB|b%5EM4AQxgNc!%TYrf=zw)Ksl}gaC+mYxohnwCQeXy zx z@x}hi80V$S)v-oCi*<_K7bUY_i-phr1wC-dX*CHxvOKu^GWq3Y2iak&##cdp`+xt} z|9T!2k`CE~HOEQgoI>3tfS7kW(Davac6Pw=vFydMA~t{f>)-zEKmO1E%i$0=jQg5t z%#kq(4raRb`1$t)L-XT5oAoI&r&`~5!w%oP>o6bs*^_I0#Y z*)vA+FV^8esL0M2Z<-VP22I(z=fMrPdmx6Te0575V@JGwMr3t%5o-(BwV(^j#3QbSw;lmIO?&_ANuJG50}%2N!kK8t^MtlC2&Rx_#z^(*8T13o>4- za~}AA`nUh_KmLbp(O+66R(!B>M{sqn-!u2Y33bzRUyO7pbN~R%r`hz1dgdZ-63Aqj zquq;kg98KsF9J^kZQ#uVu!M~A{64y*OPJVUoG!%!FTCP;5Npd0wFgBkvRyt`=0dX| zCGv;Y67>y6c9yrerX|Vh6Fxt_%<)ja5#y7$cs^23k}r{P-oie0{RNYpct(t3{(x;~ z3J#JyXpc@VpSQs5gBD-qtD5Au=8SN1W!;IxMWa=Ir8cGQVX38o1+w2jW5ULtnb5~Z{R8O;K*b8lNXp;2mVhXi0==p*2&!t#BgwH z&CF3EbCwL;^N2gSo`GEBud>ZJ378n**aEIQZ&4x76h(s*0%4njc5eGA)X$CtXnX(y zSqebHNQbQne@pVjLv{}^)8_$;4vaOhH%>qMNnJE13hS}@o$7^4m3H{z8T)^OKib6I z-B4#kzFDT#<)|2-{?St2KTtrZ32#Y2Haa~2z-4Pf?&jwzBq`?8)7zaMa7<#BA#o+I zD2x}+<{!-m3k|002mHqO*mj9OUI(>#JMe_?90$pqBm16ZHG2I})PDO@vzAy2xug!* z@j%Uf2oicDu0E{=y#2D-7VJN%Gdwp2(=QL{b$0ThFi7$XkWG}-(9U`C;B(>wED z2G_uE1FoR!vG-VZ5OpBQUef5{V7(nWmi@&E$zTzbLQ#ZX^uSN;+Gma(%@;j9@YI_v z3Q490-)C!i;b~i9rS;j6DbN$6Y!2awV9ND1qH}uyI!uk=16a-IHO;ePbm&vB8DU#P z6N#^F5dSpOJUd7fgeT-_XWYT&?T7p6jS&O*F=D$q&-TQ4>|a@lS%fcs+(uz*;TnLU zJ6TMRKR)xtZqIhH(N=%PO%WW6jUTY6DTeP7&ITXz(7@<)%?{hu7`4W9wDDgbY2?=_ z_MbeRL#)l`1jyq8S>&>DFa<4#ob8Oz`Y}2TY!5H|uri(p9ZTxkNw+ej$ALNzOD}0Q zL>5re@kD(V5Mt;K`QTicSEl|HwXI0OmE3dr=JhEtV(7SYaxY7cu!1sKARSYiN0vme z#i?MfYB*$zoTA6Vv7$H&u?2x2J5!L@iNRglB>l^|yORyUcw@wyfcQ~EZ4FU7q!!rI zWAiCT+|YBQ8Oc!E%*KjLhHc|HoCyqq^ZJ6n8kpd48)DHdh6_PRB!A(g80e|RiJ>{s zkPRQ5aU^#GhXz*1>!YX=B=ZT@Fzs!>i;B+t=po>_hT5mbCw)}0?HF5{yN)BdYf~Os zx0pJN{m5Udq&<@n@ChqOy&#oEye?+yg%tUbCmTSwrk(*=MH7;JdN) z%ylyky>4soc(&8Y!9&wg;`q;#(Q>m?3dekMClT8_XEHjH6GBcJ2Mhm`Yb{>hg0r5B z%D%vB&y=en0*B$@SH|@%?8x|umhS{`0Qk-CG{j=1KI<(p?>^{7^vQcze zhEo=Ck?yy@|L1?EU=~q;!b!=8P5|IH$<)n4iNw{wJQ()r z#84B;$*V{6ruHl}UIEA23#SJe(3c~C#8h87SmOuA{ON-o zA$k1ePk-&(Yvq)xVd#sT*z>^VvkbIx8t%M;8{9yxRx7yIqV<^dK$HDi0g-ob1i@X=64SVkUOOlj=E!*>rL zIl@DK_z?4Vef#mYj*E+xeX*WzO*|J6`?c9hkL;hk-KX|1jpZCa0=6yMU;dnzvy+?A z-e;B&e&F)S*VBqg9yB`kspOICZBcBIFHYo|>+Ed@0l6B3k|>;7crxeXAX^jHY_T!Z zSRVzxNi9DLzbNLEpTl13y+ z&LB}mq5?x0L^4Q5_-1rw_r3M;cAvZ7Is4x_r@Ojt-Ku;0cW>XSuI}l11c(nx$rPuR zWG)sKzsU)M#mz6aIQ(d8=K45EW#ysiu^q#UPlW?*1S*hZ-+_l#(qKg;hV{lQx_~>{ z*9jV1r?5X!wg_9isw0$KM(_7KqFlxCt|;oZJrfrSClGP&j+@?r&iN2sp!(G2*53wM zyqqnWOvN(mYJw>kLQzAX*7@mZ({ApiMk!yn(S9vWq`{3iT!JnB3}I4Gs(b#v`k)lF zj6wiakZy`MoQ~fy*BXz55Su#FL+y-QPRBzD@TOocmTK)7e#UUi zZj^uU$J3%-^vfEx3o9D$O)rqHyA6%k#>qx6SI!>bL!3 zOYPcibbKTH#c6&qZmE}M1B|6(P+d+v!J9Xfrw+K76NMgg zEey*xTA-^QNtX=V{+ zF>LvD2Vogrj=w{F+qSF_D>p!}J!J%Z@6kj1rtDOekWQSyVHK-eB)ro5m4vjG^PaFc40l_Yhy|1->0(y_$1Te%6JUkAS!d5LAoIRfC z3XU~R2j+z*r`F&Qa0iXdnu+1P67xvPYhZ*@UyuI_DU$j5tvmmoQTsk`MU z&!2}TMSpWh_Tp>;Dd;q>;X*&miP8x}x`q5`*LTA$T;p=M>DEmsjrUxD@$9)tTno9n zJ-mXENhQO&lxD}z#Uw)%Zl&vsl4CO~&2{4QfLpG3$L>JMO-e6VAI}CCM|5Z~GLrK2 zzMrg*mUL;sDZtv6?T#Do(|%vw;U+$>Y3H$*uH_oN>&Mx_Bj0wQ&-PMC*szY;3EEJt zSm*m7(rRgGYe87JxHk{#;%oYCP^0~=evE2GibWh`X`>gr?Yf33vjEgt@N6Xp4caBt z6%!I70}A7i-p9iok6v`K460Iv($x3J+?5Pnz32gW&`JVoKeqoAG$16F(%-eJ8?T+) zqwx}ucKs!P#12KgTDqN@69;k7dqd+JCtL*Tp;zoH4d;MbkyS2nobNu%+(|pgSP}ay z{jSuopstE@jI~8cKc?OYb6{Udq9TY0KtvflG|xs0ZjE6`!oHg=fZcSg z3=*r0?^Uy4fpqJ&jL|v1tVjyI42U<$FYlqj>g!(~a=#QOY02|;!+N{C$jgqMmT~BK zu1AQNrY_|{WKyNcJcvt4Qzp@{V+rK~eoSh9miWC_>|A>%F1P26BkH1v8DZZOCw)ic z`nKbprst}s1k>?HlmiccU+U-1(~?aUU>T9}#j*y8Jj+Y8&&QuK$Ip=Me^`!ej;*fr zO0rOl@3NzH+v9Q!dyyb1c&gln;})v3>fOt)ULM|!-$H4(Gz@KsXBX`5A`A`L1lvnw zNvnjp`a#E&c^@tul(7ptIubf0S60>xy3Q46Jn^YLOevx?HsURklNb+dR%cg!^i<1$ zgEGX1Bg%c>NA>J$&kXFBy9ordM`)4!-0=%#Wa17oTn-6ip%aF!kML5Cws`364oauM zrB1GCf%LH;f$^Zyc*;|)V>J;hSLp>4>c%*5{xB)=QY`4r{1fFpvPL!RuI6e`&sa3Q zw(}t4^*M>L4Q-}nYl@Uv4*d)@dwFxGfgM?G*v?v*-gU=% zz%shj`e8=2sAeqrNQ-3k^*q6`AWd-faQEUGb~&4Q}P~K zTr-={Y<*|2yVJ(R-H7UT3yCyv%3Ax~Tc=tiO>~B0Q?u`d9y&Tu_&W4>Z)_-9hiLX+ z3YhTpkfOj8%aJgMW43DE%&C1n(41JkQ*Wd1*c2?-?82WdP*&s|oi&u6Et6^}Vj34s zhdY05dL&o@H{Do-v~{fHwr7|g8Ya1EPM6+i$=%Rg6W#uh@iTI_#Pn8)&zI5q7cs26 zZrwcf^zT}53N%}jzP#gaZD$>~rQ@M+xXs>IdPQhjQbx&k@u0k^^bpcDzf6#s6O0iR zi{fgO7=tEIZMUIISr`Uxohb|G`iP}|!IGp4qFBpYRngo% z@^J(C^*${35A9A4O-dEwJL4aLj+yfkCb$K@ zx6h_HaIIEv9J+)nFz@xu67Tk%03Be_T+L%N5{B%-))%-fuK3m68fZfFV*@+kI^`b; zwuyFBL%|o-FaVx;kdR{#>aNBxdTZ+8%FCHmEOwJ}Qavwf3GI9>T*s01YQ~!fno*K2 z8D=UQ7q4}4DRMoh8(nBkI9ohG_m9|lMz>TfKGsn?+U@gEB6drn zqp*lkzkWvnUgnFPY%DWX0>S1A7KvUt^alTcmnPG{JEDt-PeFi+;C_?9IHovk*r#5eBxcGxJQy5rCy4U_a#PJS2@A5 z7lM9~8|1U}ytJ9MRza*h`&DsjC!6l4#AP1ij`r0?OIJ*LDy;_rWjPCR;TE>tQlG76 zgl_d|W0!Dl_%`Amnho6^xJ|1nwz78Ml;goZ-?n-1L8Y^G5PU*uqQ9vhMe!y?n^`}t zRd%Ws6>q<>da-S~LTABegr_oZYHSoD7k$;yZ;i$Aesa(Iu>6Y}BGAzs#^=y9298>0 z9STuW$0`i>!%Ty%8|g`GcX9p16pNwW!Y#Xq<^tCd)40Yii}v` z4W0F+{@V(I>$V>%x){>*u}%D3OA21HbyJJZkMLF^+swq*%fZdpY#Ndp&y|$0hUl_kFnFL9pwcS;Qgn; z-w3n@&dT;#*;LC*9X^?PrYIM zS8b=3*Zu((i#{tUS zuyXJ&XJHkTX@*?Dzw6-T`t&~Ld);PoAqjy8?`7UEW0Nxz z8zpa7nV7E@PLXY$2}9rbbMz1`bcaWloa@UScKOP_$KjY-fWS zQ$^dKgL(E=Cn%@#k(Y5`HOmDo8qKDRlHrZ38Og?-J)}=+rG}{1aP`IP^^K(hZQQxB zQeTAo0)j_FGv>C^D+xkARg-sY>9XQ|>#Ny&Pp&>SkzF`rG>CTPjA3hTLVsf=-gBET z>M-ZE3hXZ&*7c99n?q7l>Z032VV|2KYSl*_NvKvw_d3<3Hhq^(pg=+|W^U#JdjJO(q-fn%+EocfA`=qC^S(5xA*chkhQ z_vMM;#M(u-*JKbJ(~O}~r}OG-8Qo;K5i=jX+TSC)lcR6{cr&ouBaMA%q=Z@r9aMRIAv|-}%ONH$_f&hyMQO$H9}ILb z-jAvx$|Emb;UqY%6VhnkL%D)s800*oAM0uE)%{ry@9?46O~EB`^)FkC0aI0ZVpRrA z30v4b6WO9xenNa3CHI~LRgBHP$me&4NU;WvjaOOB*j4(G>_*fX*+G5ueh$Z1JXr6Ei`X$%oQ1m<7Io$2xi?q zCG^8>r)m1wDn8x`h@{eOjV+ZGxLZ-lACD%NArhXSMBLCd@C>~546hxu{p`{xca$1> zPNISf@5pEYgt~tmzl+D=WI=OSdMMyyBWBwamUEP}*glOY{oKtphL#ew{mN_BS#m0B z4%RlNINt8b_k|?$I`S%PN2WTGR7+|XI=Kje?{ zyoTpPeFffc!ixWI(GEN65P`sEB-+O~TfdmJ1uM*wz3m-|f8U?2Ky9Fg$giL3I9ORrj> z5e{U`vs!Dd(rA#fyvR9cs?Sg)wxcCthe0MP$|3FODmA;|k%+cNvu>NS%1+4DFgz<0 zVRZQrOGRGn9P(8XemHWf=llxxP&&XzJW7s_r3RCV(GJMTT#?@$ir< z)IBG$P_4Sohv?y@2A4>FLARu?Ze0w%8kthett8AFQHit3CG0u=>_ce4x{?~3kgQ07 z!bRRQE7k+nWin7>e}A;Fm;L8RD+*U@R^wIcb?}GA1K!T{j#m!-bG~bFyy~jtWHHlw zJWF86#R-pVux~PQkd~_J2o@<-hiB0XJ$ki;hl8_m+4;pyscOp&BUzuoD_yPvHhNrN zAE^|yIbY`0Y4guiJUI^ z9!}J_b4Bso+>P9OTxEPBvb(z+A{CGYa(Z$Siq~PA4|@|vH}^8m2Do*tA`K3hPN8Sv zWk8D7wT9}uQm?u?Z0OTev}Jm2%nd-)R6P$BZk{@y9s$_r=L(xPqpTKcEaiB3uRDiE zo`%3)yV{FXta#>rp?ne&##$mbO?hx)`;MMBL4U zL3t)D=-WC}<{>WAdk2<}b`)!rLxmuhMx3q!I+6Qt>^1x7Qx%WR@D_CyCc+>(r% z^;cr>j-+KIYdIa5&%{!aSjR2^n4|d>AmS5EH+#!J?Ny=*mmht`k7Tnf0$-iL)-q+m8nnPM6tp-@MOcP>PP{Xj-K zV9!^!;}l8Wit#ayhv6P5IGJ#6hZm$ zi%YM5c*wuaOdLK$7vvIyvUhNVvwb2KU{cr)d>l1E`As&DrlR7~EQQ~$g9(1UmWp)O zqPW4Cr&(#US#&>YsT`RMeS7U1v5(bL#9$%YEY1wZ*UhZ64Kw4E8Cum4x~oi%Fq9dE3B~ceAh3 zDrp9o?9L*oW7w5y#_1WEgqiN9f`KtKf(I^@;MJ`@u_mx=8uX(`wdO&5G>YA<0 zq;3KALFwFfZbRu;b0vdyl>r$C^|F`F_Fn1}0p1I*$=dQriCHY)sfp2!pBi`=t5)xOc?pC!bDul}bj@^OkWN1CZ&77H8O~2WEImW% zQ9r0N^=NxLM^lw1*NJmgyAPZyEDx&X>8ZU*0oWjX*ls~GP^0}V6{1$(v^k&aG3OaC ziQ)EQg6ortfYAyarEC7C`ch~sdf?1}fD5*ZupFI1wasjL($2$^O*aX!&`YNa)tl@3 zFXNT*N^v6RUjPQ(rKvVQvm0NbWdN2laob3!Dy3T19GK&I+q|=4U1^P?O&_~lwNGU0 z$NFy4&)LFyX|)_za;>%SQ8d<=ZYfg+%PnOSt3lL+p#ij|$|kD@&!#Su4QK+_w!6R%MZ%z?-|MTvrp=s@QXH^<0I(it>|(IgBU$$f9R=y(OGS3l|p(Z!LMm#qC8rXlFQ!Xe#?h-jjZQVXslJ83@g_CYD)V znWO9FJqMK0ya`G^UG&)USeu8yzRkD7WjQFl@!mZ^CmL40C!7(%!x-=0yQtb5+Bq3@ zGon>@QVlI&IaSfsMm6MW3r9Y*@CR%1F1ZYh)F$WO_aVxdk(ysI#M>>cDTyJ<8{5Cc zy6Owy%1?dIvBg~TAT0fUG*1|m(?%u1dtKz>0Z3xi zxfhaj#^`v8p}sA0aVC12U$H!c?cb|?a-AGezL?{vTQ&A|b5hqa50wN2eaYgm502T> z8*0ZEuM+l(gVc2`S-s;g_;kV>$n9XtaqYIW2c-}?Bmafbu6Cofw9x#XDhSE#DdRC- z;wN5=SU&R=-ccM~#cUEUf$OKm(L-O_aavsXuY zd^tFN`l^e}Hr@DFqzilNF%{&NyziO!um#UOPoKT?+AQ^8_JGU?_4@sS+%MOj(sU0seLzI=l#{`OvZ(1VgD$M`9wxq9I?j3)JZ(U`b7rc>t>c;cCGB~efUAC zZ*778x6`h9aC8XQxFp57-xtgq(L4?@4tS0~@?=k{Ynnm8_S2ZYPRw*e1wGx-PIB5r zY{A)JovvptAi?&=RySwUEC=Rx2`!rNXgdCSAbwHlDty<5+%8n&vy;zt4Rw?9?0ilVT6tTdSQL0adhN_^;>s~>)ev^$<)BTjR`yO3evVnD zX7k~01qRq^zG@Av;b!H$<-6dk2c)^)8FEaz-dWwut2e5}6J zQd2-LDuA~9N%?pi%--a3r_f+&FSzKzSBF7vmkYL?#cTzyIRifmGEGk=5e>N%C`*cs zyc&r6_GW=<&@*tW!8PlG`4xP1lbk3sKKbJ(DVwo*UztIflR+_OoB4U|vbhV@18q|ii=bZCTU#Q1(?_*GXgTu*s9`@qY9yajDG*{zAld$;9`uA;G)8CFVx z?gTksZ(5LCns=+Ep26)R_g62<&UiNR#C&PU7$tps(BD7y0CXDjXE)xJ#?9T z0eX4Ho`B4jUvwYh*47Fp_ClQ9($320II1M%~jH)ZRY)H%_t&DcNTi#QG<4y-S`B#pCL% zd|C0CV!{#{m0LJd+RZIVmgk_xU^4~P13Zt-Qw+qhjIr{ zMi6{lJ#OV*GyVD7>}2zUv6Hk2EHjDVNE+7-w8eCSS3l9Kcg9BgZM-M$*;L;+ z?FYDT-cnGOO<%Ow{2~dMUVx=Q;Hf>3Q+%nWs7gTs zXJ*3|eYvvq9`S_ARD+7MGBkE)_t=4Khslofof$(dQEM zgKcv!MfdjVE4I6HCzS3@@+X%X&{s3D04>6qR*^%fW>OG#UT_0`zoSjMb6S!oV>vPZ zESg#!b_&fkasxSMo6lbjlX5QHI#r>Iu_sF)k4(6&XYo zkOAlHS)f82KHA)8MJJ8`_MKx~>*(0HGcM=GITKY$DafrR*`1o9 zHy1$?vGY;Y@^iP}aq8frx1gh0$mdTarq$@kPHJn`<=!kTnt$^kQ{}t63HxmoQ|TnOdD?8kaN+K# z)+_RB#li(Mb5cD%yNohBe$e30d!`D`aj}IqrsC=g`&hy+xj2I7NvLMNdL@X?*O9E< zRTLPQiV3^Jy{i2&7BV2iYmi^fNx4l<%`5BbOdfhjeBF$Tz%k|~UXy#!vNT@0@#9<} z4C7$uYuJ7xq5f|%`6QySvM>eE0mCBHQ6+c9P&PX8=PdKbfb;-u>}V)&$8jd zWVSgZ%qS3x%&ZUZv0A1--$__SaqNfp*)Iy>M>`u7O%|G~V|1tw8wG6@3>?4e5zaMw zshZl+j@qO&&u`RGn^Bx~ws`r)!amV0<{gr@Lef;AvyC&{8wp#dX9|u6^|eM(O9*o& z$rnxcL}pvYl0|~u%^zmju{Cb#a8`V9-LsJ!n5+p9Fwr)SD9))Wp}O!fKjvCu zU#}fH_I07Yho^7AIn8)gIO_}X3=`0}TV`CL!Jo?HN5;YUHGuIA4G{zVFGQE(%e=k@ z7->cE^$e-Z6ZL7_4eue28QS6_Qre;mvcY2R={V|%H*!(uHM~!vw@E|d*p`=PQRJ@3 z#JT^LnNk|Bd^$#(bba9{_a=m3)UD&JBa`z&)!Tq$_D6Vc`pAd17`kk~coF5TRkSFl z>{F0e&6RkI6S*{4h*?}hNgz+8IE>l3o zXULLI1NYk%f*P{!J*nj;XiZH#ql+2y3m`{sHsLb?)%f6f+;ryHScuW-Me=GZfn4x0 ze>Wo?D3^}_4a50fVj`Q{l-WQ)?GcwT$Bw6=@Yd{1RU9Ds-XslVgVg|~Y=fvl5tjbb$L!wWHPy1{;7XwcvPHw0-GNuMRfCN}y0d zF^wHnmeWUxsoqv<4ot=A=jA4Ym4~0&Dn1AMyD;(Q#_A-58q>`aHVbx_IeyUZf$-)z z)*itA*mrVy@f8@n^?{~@Y)sw0#0ta+ZQA9(dx(L3Ls_lNkx3We4tpy^NTG(q1AOiDOE;O>G~`DcH>J=6bQ#0rj-|(O|dGNvo0y<2(7viwUY*bj+xe#Cou2 z%EnyaLZX2A-D~xfL`377B+#P-KGE1fu|G=2_&1?S)&WW-%`uiC5+0!iBE7 zm4kgT_#R(JD|65epg3^1Eo1E?4>=gmFjCvmex>w!O&vql%=j7b)gePOJL>Cemqf9e z=$V5R<)~f1q1jhNIFsQR`@M3fT`<)eUU5FjR*Y?YFF<-7YG<|Fs+nbMIl)9aU30B@ z{5Rd^=TFaOOTSvoC~y4nuI%`#8**p1iTafVYOm)kWkUy5ro z1?)e7z4Bsfo?^9XNSK@=k{e52647&yxvwZRL^1l!fL~>1rqv99jX%^s^H|MZFI|1#*L^*sKAxt)MQY;ZdQP3+en7EH0S;f@zTN_%XmO`W7$kY#b!a zo0epru2olLzQUtYcBZ%{B+~3;UzRj5)cAEHp~ha(B#E8u(rJ5)lk@3F&7OIZ-!sN( z2|`Bmy6AEO`fV4PFZ1h_fDP6Atv>THZ$zDZ{Sxygua4!6p1BS51YYmSrum=-vQG-G z^VodGZ#>tQLPbaDn^o|6bDBkzWU9|;t;7n$sPZV}VvKHRYRv4TIV@5C%`>b*%j`LkPC9N_V}P=}x?GuzNeBw;7zehg0o^QIZGiRwJb1zQ zuJmnVZD&^-Hw!=&1pol&`l;e~&9Gn`_^?e02+}sQaI}zhvUhR?V8SR0Kzon{fB<%c zlp*Kj0JL!gT!fS4jJ4feErDhh0NA$<05bL#j^=7k<`w{Acu5ter15Q6pd-lM9oDHE zfb_S$SeOCrZ&`qxog88P!<2+|cFXv?Ty-Z`2UwkxyOp)01qk$Ab!}^)xs9V0ymR{> z+Gwj8%UM_g-R<2}ZQQ;q|63l=cVX9mOH#10us0XyVfilX?`oNOtJ+vuyCFIU(7-gg ztqOzC$==<;!p+sjOx?**&c@Zk%+1Eh5r!}ki~*itzZdzQZXj}a!#_+I2)ZSVjEw>CU#!9xu^`Y^#Z zctUmt=Yyxebq2=@zQNDN6t=+=l6eYlDOf^c`&}B62}vwKFd^wbn*3%jfH(#ra=3cm z6Z{-tfx`kIh#>rg6`YQ=)BmS#ke9~~r<1<@Ys&oJ??nq?1ElSF;Z6ZdNHYWA)Os)tjvH>BYEcA!mFrsE`ib1mVMY+5&{Re`flmqa10Z&MO{oB>IeRg(+ zbdA3(g?E~({pC@_%@Xh(*H^8!BIio^`tL}2e#DVZ%0{jFB(hCqQi~dVu9TgBM~a#P z`YUg7Zkgvv&QPr;VClJ1cK;nIEuw`6A5>)ZTVx7H5PT3hQWxZi;E16AlpzAacRKQl^G9&~bXme-Fh^8MAo44S9611xBFf)0 zc<*o>*|~YXj|C|3Q4CgqP0s-+qTgBm9)t)H<#SCtQFoHhC#1}tAUDIjqbVbR)EChX zV2?QX3A#j>EgjCQgrATHq7r!$NY%cN7mBdk0au_sy}FZ|1xSQm4QOXU4{~?4p!ari zr?&=rSkUv*%esTyoE+%y+@+Vb20A)g*n8~@Ku2?W zX?tgD;Q3HE!{W*LP<%T(`^!+gV2yAG2uaJ7&on>cG1_?kFGHbm9%k^E$BU>$#sL-h zgbJ3B7WzJZBAJjxQUufY`_&)(zu&VVipU24)Xs`(5g{Tq>a(ve#xd9OtECJ7LkEnA zGVAZ<{JsP`owI=7lYUo1`o~|^?nOO)+9JN9*~4wY_b8uHb^o$<$Y|#LJAHm%_+{-t zE+)INPI8axVgDV`F1h*(&)Q%6qgS^%p;9~|X zA+M6}_bEswBvA;##Pfsq=RG@uglzCnuQhQ8@h7d=Dvd?Aj4PO(6l@Itp@Y|m@{ONm z{h-`KRQxnnQz0rO5&2KU6`U^nXO%L4Rw?~wl~R9JiL?MR|I3&ew_PHhXO!hin=&0L z=|yM%&tV2%nuDkRJ)8x&ofELeZ=se!wh)`@W1D01bv^*-Z*I7jUL6l zfxmJ19~=X&{$KI4bMIoLU}tdd)SVDE_5I6J_ts{Nm(MP$G)4KEyEQmvZvN@i-3S37 za+Z(;yJ3MR9V{UO(?7j&N8H~0Zt>10cuPp#o94_8XPoVFGTvh}O z21IY>1Y4vta|6-4Inm1iL5Row#0Wl8BtIfY`5i=m*?4fw;A$c7tNx+)@5R4tJX}~a zc*n?2;R1e};N^4e17BJ{*FI9PmVbHhmVSA5Z1~}gQqzbyzDUM3!M|i5&aeBtUR z`)D`=&1~Gfzpp1EkJW%6yYKH7=7Kl;vrf-7 zmnLi-{d{x%f4`iNI>Ys6fbU@G58^?Pq}h);z)wpa&WH*YM2-wmNEpvu@;73odcOZ! zIy&pib|tE=tSA3@5<(Ca=VJKi&z73u*TY|S`O&1~_$|yT`NGW)<3u{o<^R;>e~aO{ zFj4<1Oaf)1EvwrK*D7y_dCu-*h5b{QoDhqFTVNOZxl!PkJot0qy@@ViS&?vgMkj(Ynw@_iV9Q_i;C}D)m9`~Swa`UrO|l{_ z(q9J{hSDzrJp5+>SNuwd_|F6E^2-4CewD?aQW8iH+2tH6`j&t0MLz#$J~#}kJl6*k ze`O@vX1|K{HXp1KEB;dG>tR{-zwCny2qgY-ED=M{{~!y3Em8e_J2wmm{wl_-RPH!& z`>KWaZ{B{6dTrk6+*tXiFt+^4bI){^Z6(tx9_iGQyc~}X3!Q%%#@`N1@biYxa!lX{ zuyh5s$?)+C$tZBk@X7IU$#N^m^T`M(@W>1C^T^3^bIA+y{%uABZ_pabn*09%DE13~ literal 0 HcmV?d00001 diff --git a/HoudiniEngine.uplugin b/HoudiniEngine.uplugin index e5e325ad4..f3abb5cda 100644 --- a/HoudiniEngine.uplugin +++ b/HoudiniEngine.uplugin @@ -1,14 +1,14 @@ { "FileVersion" : 3, "FriendlyName" : "Houdini Engine v2", - "Version" : 18050408, - "VersionName" : "v2 - Beta2 - H18.5.408", + "Version" : 18050462, + "VersionName" : "v2.0 - H18.5.462", "CreatedBy" : "Side Effects Software Inc.", "CreatedByURL" : "http://www.sidefx.com", "DocsURL" : "http://www.sidefx.com/docs/unreal/", "SupportURL" : "https://www.sidefx.com/bugs/submit/", "Description" : "Houdini Engine for Unreal Engine (v2).", - "IsBetaVersion" : true, + "IsBetaVersion" : false, "Category" : "Rendering", "EnabledByDefault" : true, diff --git a/LICENSE.md b/LICENSE.md index 1fffeda34..109dbc2ff 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,93 +1,93 @@ - ALPHA AND BETA SOFTWARE - CONFIDENTIAL DISCLOSURE AGREEMENT -Revised 10/2011 -This Agreement is made today between Side Effects Software Inc., a corporation -incorporated under the laws of Ontario, Canada and having a place of business -at 123 Front Street West, Suite 1401, Toronto ("Side Effects Software") and -you ("Beta Tester"). - -BACKGROUND: - -1. Side Effects Software is in the business of developing and marketing certain - computer graphics software and related materials. -2. Beta Tester, in order to permit Side Effects Software in refining and - perfecting such software and materials, has expressed an interest in testing - certain alpha/beta versions of software more fully described in Schedule A - (the "Software & Materials"). -3. Each Animator, as an employee, contractor or agent of the Beta Tester, may - have access to the Software & Materials and perhaps to other confidential - information of Side Effects Software such as trade secrets, business or - product plans, which might be disclosed during the course of the software - testing (the "Confidential Information"). -4. Side Effects Software wishes to ensure that the Software & Materials are not - used by Beta Tester for purposes other than alpha/beta testing and that they - are not disclosed to any other party without the prior written consent of - Side Effects Software; - -NOW THEREFORE, in consideration of this background and the provision of -such materials to Beta Tester and other good and valuable consideration (the -receipt and sufficiency of which are hereby acknowledged), Beta Tester agrees -with Side Effects Software as follows: - -1. Side Effects Software hereby grants to Beta Tester on the terms set out - herein a personal, non-transferable and non-exclusive license to use the - object code version of the Software & Materials for its internal operations - on its computers. Any commercial exploitation of the Software is at the - Beta Tester's risk. Beta Tester's right to use the Software & Materials is - limited to those rights expressly set out in this Agreement. Beta Tester - shall carry out testing of the Software & Materials in accordance with such - reasonable instructions as Side Effects Software may provide to it from time - to time. -2. Beta Tester shall use all reasonable efforts (which shall consist of at - least the same level of diligence as it uses to protect its own proprietary - information and trade secrets) to protect the confidentiality of all - Software & Materials, including all product features, and other Confidential - Information of Side Effects Software that may come to the attention of or - knowledge of Beta Tester as a result of undertaking such testing. Beta - Tester shall not discuss product features or show the Software & Materials - to anyone. Beta Tester shall not copy, publish, disclose, attempt to - recreate the source code version of the Software or make any use other than - as contemplated herein of any of the Software & Material or any such - Confidential Information. For the purposes hereof, Confidential Information - shall not include any information that: - - At the time of such disclosure, is generally available to the public - through no fault of Beta Tester; - - Was in possession of Beta Tester without any obligation of confidentiality - prior to the date hereof and was not acquired directly or indirectly from - Side Effects Software; or - - Was received by Beta Tester after the date hereof from a third party who - imposed no obligation of confidentiality and who did not acquire any such - information directly or indirectly from Side Effects Software. -3. Beta Tester shall not communicate or otherwise disclose to Side Effects - Software during the term of this Agreement any confidential or proprietary - information of any other third party. -4. In accepting this Agreement the Beta Tester agrees to test and evaluate the - Software & Materials and to report all problems, concerns, deficiencies and - suggestions for improvements to Side Effects Software. A representative from - Side Effects Software may be contacting Beta Tester weekly for a report. -5. Upon completion of such testing or at any time on the request of Side - Effects Software, Beta Tester shall promptly return to Side Effects Software - all copies of the Software & Materials, as well as any Confidential - Information, then in its possession or control and shall, if requested, - provide Side Effects Software with a certificate signed by an authorized - representative of Beta Tester to such effect from an officer of Beta Tester. -6. All Software & Materials, as well as any Confidential Information, is - provided "as is". Side Effects Software makes no representation, warranty or - guarantee with respect to any such material and assumes no liability for the - use and performance of any alpha and beta software. Side Effects Software - reserves the right to alter all aspects of the Software and Documentation - from one alpha or beta version to the next, including the user interface, - screen displays, fonts and functionality. -7. The Software will timeout and cease to function one month after its build - date, regardless of when it was downloaded or installed. - -SCHEDULE A -SOFTWARE AND MATERIALS -The following Software and related Materials are bound by the attached Alpha -and Beta Software -Test Agreement: -Software: Houdini Engine for Unreal -Version: Version 2.0 - alpha - -You must accept these terms and conditions to install the Software and -Materials. + ALPHA AND BETA SOFTWARE + CONFIDENTIAL DISCLOSURE AGREEMENT +Revised 10/2011 +This Agreement is made today between Side Effects Software Inc., a corporation +incorporated under the laws of Ontario, Canada and having a place of business +at 123 Front Street West, Suite 1401, Toronto ("Side Effects Software") and +you ("Beta Tester"). + +BACKGROUND: + +1. Side Effects Software is in the business of developing and marketing certain + computer graphics software and related materials. +2. Beta Tester, in order to permit Side Effects Software in refining and + perfecting such software and materials, has expressed an interest in testing + certain alpha/beta versions of software more fully described in Schedule A + (the "Software & Materials"). +3. Each Animator, as an employee, contractor or agent of the Beta Tester, may + have access to the Software & Materials and perhaps to other confidential + information of Side Effects Software such as trade secrets, business or + product plans, which might be disclosed during the course of the software + testing (the "Confidential Information"). +4. Side Effects Software wishes to ensure that the Software & Materials are not + used by Beta Tester for purposes other than alpha/beta testing and that they + are not disclosed to any other party without the prior written consent of + Side Effects Software; + +NOW THEREFORE, in consideration of this background and the provision of +such materials to Beta Tester and other good and valuable consideration (the +receipt and sufficiency of which are hereby acknowledged), Beta Tester agrees +with Side Effects Software as follows: + +1. Side Effects Software hereby grants to Beta Tester on the terms set out + herein a personal, non-transferable and non-exclusive license to use the + object code version of the Software & Materials for its internal operations + on its computers. Any commercial exploitation of the Software is at the + Beta Tester's risk. Beta Tester's right to use the Software & Materials is + limited to those rights expressly set out in this Agreement. Beta Tester + shall carry out testing of the Software & Materials in accordance with such + reasonable instructions as Side Effects Software may provide to it from time + to time. +2. Beta Tester shall use all reasonable efforts (which shall consist of at + least the same level of diligence as it uses to protect its own proprietary + information and trade secrets) to protect the confidentiality of all + Software & Materials, including all product features, and other Confidential + Information of Side Effects Software that may come to the attention of or + knowledge of Beta Tester as a result of undertaking such testing. Beta + Tester shall not discuss product features or show the Software & Materials + to anyone. Beta Tester shall not copy, publish, disclose, attempt to + recreate the source code version of the Software or make any use other than + as contemplated herein of any of the Software & Material or any such + Confidential Information. For the purposes hereof, Confidential Information + shall not include any information that: + - At the time of such disclosure, is generally available to the public + through no fault of Beta Tester; + - Was in possession of Beta Tester without any obligation of confidentiality + prior to the date hereof and was not acquired directly or indirectly from + Side Effects Software; or + - Was received by Beta Tester after the date hereof from a third party who + imposed no obligation of confidentiality and who did not acquire any such + information directly or indirectly from Side Effects Software. +3. Beta Tester shall not communicate or otherwise disclose to Side Effects + Software during the term of this Agreement any confidential or proprietary + information of any other third party. +4. In accepting this Agreement the Beta Tester agrees to test and evaluate the + Software & Materials and to report all problems, concerns, deficiencies and + suggestions for improvements to Side Effects Software. A representative from + Side Effects Software may be contacting Beta Tester weekly for a report. +5. Upon completion of such testing or at any time on the request of Side + Effects Software, Beta Tester shall promptly return to Side Effects Software + all copies of the Software & Materials, as well as any Confidential + Information, then in its possession or control and shall, if requested, + provide Side Effects Software with a certificate signed by an authorized + representative of Beta Tester to such effect from an officer of Beta Tester. +6. All Software & Materials, as well as any Confidential Information, is + provided "as is". Side Effects Software makes no representation, warranty or + guarantee with respect to any such material and assumes no liability for the + use and performance of any alpha and beta software. Side Effects Software + reserves the right to alter all aspects of the Software and Documentation + from one alpha or beta version to the next, including the user interface, + screen displays, fonts and functionality. +7. The Software will timeout and cease to function one month after its build + date, regardless of when it was downloaded or installed. + +SCHEDULE A +SOFTWARE AND MATERIALS +The following Software and related Materials are bound by the attached Alpha +and Beta Software +Test Agreement: +Software: Houdini Engine for Unreal +Version: Version 2.0 - alpha + +You must accept these terms and conditions to install the Software and +Materials. diff --git a/README.md b/README.md index 7d72075e4..9fb56de55 100644 --- a/README.md +++ b/README.md @@ -1,105 +1,105 @@ -# Houdini Engine for Unreal - Version 2 - Beta - -Welcome to the repository for the Beta of Version 2 of the Houdini Engine For Unreal Plugin. - -This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. - -Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. - -Here are some of the new features and improvements currently available in Beta1: - - -Core: -- New and redesigned core architecture, more modular and lightweight. - All the Houdini Engine/HAPI logic is now Editor-only and contained in the “HoudiniEngine” module. All the custom runtime components and actors used by the plugin now simply acts as data-holders, and are processed by the HoudiniEngine modules, removing the need to bake HDA before packaging a game. -- The plugin now relies exclusively on native, UProperties based serialization, so operations like cut and paste, move between level, duplicate etc.. do not exhibit any of the issues that version 1 had with those operations. - -Outputs: -- Static Mesh creation time has been optimized and now uses Mesh Descriptions. -- Alternatively, you can also decide to use an even faster Proxy Mesh generation while editing the HDA. -  Those can then be automatically refined to Static Meshes, either on a timer, or when saving/playing the level. -- World composition support: Tiled heightfields can now be baked to multiple landscape actors/steaming proxies, and will create/update the levels needed for world composition. -  You can specify the level's path by using the new “unreal_level_path” attribute, that is also used by meshes and instancers so they can also be baked out to separate levels. -- Material overrides and generic uproperty attributes can either be applied on the instancer, or per instance (when using mesh split instancers or instanced Actors). -- It is possible to create foliage instances directly, without baking, when using the “unreal_foliage” attribute on an instancer. -- A class can be directly instantiated by the "unreal_instance" attribute (ie “PointLight”, “AudioVolume”… ). -- Curves can be outputed to SplineComponents by using the "unreal_output_curve" primitive attribute. - -Inputs: - -- Colliders on a Static Mesh can now be imported as group geometry. -- World inputs can now read data from BSP brushes. -- Instancers and Foliage are now imported as packed primitives. -- World inputs have an improved bound selector mode, that lets them send all the actors and objects contained in the bounds of the selected object. -- World inputs can now import data from all supported input objects (landscape, houdini asset actors..) -- World inputs can now import data from actors placed in a different level than the Houdini Asset Actors's. -- A single curve input can now create and import any number of curves. -- You can alt-click on curve inputs or editable curves to create new points. -  -Parameters: -- HDA parameters and inputs editing now support multi-selection. -- Parameter UI/UX has been improved: -- Folder UI (tabs, radio, collapsible) has been improved -- Ramps UI has been improved, and it is easy to turn off auto-update while editing them. -- When an asset is dropped on a string parameter, it automatically sets its value to the asset ref. -- String parameters can now be turned into an asset picker via the “unreal_ref” tag. -- Support for File parameters has been improved (custom extension, directory, new file...) -- Multi-line strings, Column Labels, Button Strip, Log Int and Floats are now supported. - -General: -- The plugin's UI has been completely revamped, a new Houdini Engine menu has been added to the editor. -- bgeo/bgeo.sc files can be imported natively in the content browser (Mesh and instancers). -- The PDG Asset Link has been added, allowing control of TOP networks nested in HDAs, and works similarly to the one in the Unity plugin. -- Session Sync is supported, allowing the plugin to connect to a session of Houdini Engine running inside Houdini. -  The state of Houdini Engine can be viewed in Houdini while working with the plugin in Unreal, and changes on either end, whether in Unreal via the plugin or in Houdini via its various interfaces, will be synchronized across so that both applications will be able to make changes and see the same results. -- Blueprint support: It is now possible to use Houdini Asset Components in the Blueprint Editor. - This lets you preset and use HDAs on Blueprint Actors, and changing parameters/inputs on the Houdini Asset will automatically update all placed instances of that Blueprint. - - -For more details on the new features and improvements available in the Beta, please visit the [Wiki](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/wiki/What's-new-%3F). - -Some of the new features in the beta are still in their early stage, and will be improved upon in subsequent release of the plugin. - -# Feedback - -Please use this repository's "Issues" to report any bugs, problems, or simply to give us feedback on your experience with version2. - -# Compatibility - -Currently, the [Beta1](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/releases) release of V2 has binaries that have been built for UE4.25 and UE4.24, and is linked with the latest production build of Houdini, H18.0.597. - -Source code for the plugin is available on this repository for UE4.25, UE4.24, UE4.23 and the master branch of Unreal (4.26). - -As of Beta1, Version 2 is now backward compatible with version 1 of the Houdini Engine for Unreal plugin. - -When loading a level that contains Houdini objects made with version 1, the plugin now tries to convert the V1 components, parameters, inputs and outputs to their v2 equivalents. - -Some HDAs might need to be rebuilt after the conversion for their parameters and inputs to be displayed properly by the v2 plugin. - -The conversion of the legacy v1 data is still in progress and will be improved upon in the future. -However, the Houdini Digital Assets themselves (the HDA files), that were created for version 1 of the plugin are fully compatible with version 2, as it supports most of version 1 workflows. - -# Installing the plugin - -01. Download the pre-built binaries of the plugin in the "Releases" section of this repository. - -01. Extract the "HoudiniEngine" folder in the release to the "Plugins/Runtime" folder of Unreal. - You can install the plugin either directly in the engine folder (in "Engine/Plugins/Runtime/HoudiniEngine") or in your project folder (in "Plugins/Runtime/HoudiniEngine"). -01. Start Unreal Engine, open the Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. -01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". -01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. -01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters and inputs in the `Details` panel. Modifying any of the parameters will force the asset to recook and possibly update its geometry. - - -# Building from source - -01. Get the UE4 source code from: https://github.com/EpicGames/UnrealEngine/releases -01. Within the UE4 source, navigate to `Engine/Plugins/Runtime`, and clone this repo into a folder named `HoudiniEngine`. -01. Download and install the correct build of 64-bit Houdini. To get the build number, look at the header of `Source/HoudiniEngine/HoudiniEngine.Build.cs`, under `Houdini Version`. -01. Generate the UE4 Project Files (by running `GenerateProjectFiles`) and build Unreal, either in x64 `Debug Editor` or x64 `Development Editor`. -01. When starting the Unreal Engine editor, go to Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. -01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". -01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. -01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters in the `Details` pane. Modifying any of the parameters will force the asset to recook and possibly update its geometry. - - +# Houdini Engine for Unreal - Version 2 - Beta + +Welcome to the repository for the Beta of Version 2 of the Houdini Engine For Unreal Plugin. + +This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. + +Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. + +Here are some of the new features and improvements currently available in Beta1: + + +Core: +- New and redesigned core architecture, more modular and lightweight. + All the Houdini Engine/HAPI logic is now Editor-only and contained in the “HoudiniEngine” module. All the custom runtime components and actors used by the plugin now simply acts as data-holders, and are processed by the HoudiniEngine modules, removing the need to bake HDA before packaging a game. +- The plugin now relies exclusively on native, UProperties based serialization, so operations like cut and paste, move between level, duplicate etc.. do not exhibit any of the issues that version 1 had with those operations. + +Outputs: +- Static Mesh creation time has been optimized and now uses Mesh Descriptions. +- Alternatively, you can also decide to use an even faster Proxy Mesh generation while editing the HDA. +  Those can then be automatically refined to Static Meshes, either on a timer, or when saving/playing the level. +- World composition support: Tiled heightfields can now be baked to multiple landscape actors/steaming proxies, and will create/update the levels needed for world composition. +  You can specify the level's path by using the new “unreal_level_path” attribute, that is also used by meshes and instancers so they can also be baked out to separate levels. +- Material overrides and generic uproperty attributes can either be applied on the instancer, or per instance (when using mesh split instancers or instanced Actors). +- It is possible to create foliage instances directly, without baking, when using the “unreal_foliage” attribute on an instancer. +- A class can be directly instantiated by the "unreal_instance" attribute (ie “PointLight”, “AudioVolume”… ). +- Curves can be outputed to SplineComponents by using the "unreal_output_curve" primitive attribute. + +Inputs: + +- Colliders on a Static Mesh can now be imported as group geometry. +- World inputs can now read data from BSP brushes. +- Instancers and Foliage are now imported as packed primitives. +- World inputs have an improved bound selector mode, that lets them send all the actors and objects contained in the bounds of the selected object. +- World inputs can now import data from all supported input objects (landscape, houdini asset actors..) +- World inputs can now import data from actors placed in a different level than the Houdini Asset Actors's. +- A single curve input can now create and import any number of curves. +- You can alt-click on curve inputs or editable curves to create new points. +  +Parameters: +- HDA parameters and inputs editing now support multi-selection. +- Parameter UI/UX has been improved: +- Folder UI (tabs, radio, collapsible) has been improved +- Ramps UI has been improved, and it is easy to turn off auto-update while editing them. +- When an asset is dropped on a string parameter, it automatically sets its value to the asset ref. +- String parameters can now be turned into an asset picker via the “unreal_ref” tag. +- Support for File parameters has been improved (custom extension, directory, new file...) +- Multi-line strings, Column Labels, Button Strip, Log Int and Floats are now supported. + +General: +- The plugin's UI has been completely revamped, a new Houdini Engine menu has been added to the editor. +- bgeo/bgeo.sc files can be imported natively in the content browser (Mesh and instancers). +- The PDG Asset Link has been added, allowing control of TOP networks nested in HDAs, and works similarly to the one in the Unity plugin. +- Session Sync is supported, allowing the plugin to connect to a session of Houdini Engine running inside Houdini. +  The state of Houdini Engine can be viewed in Houdini while working with the plugin in Unreal, and changes on either end, whether in Unreal via the plugin or in Houdini via its various interfaces, will be synchronized across so that both applications will be able to make changes and see the same results. +- Blueprint support: It is now possible to use Houdini Asset Components in the Blueprint Editor. + This lets you preset and use HDAs on Blueprint Actors, and changing parameters/inputs on the Houdini Asset will automatically update all placed instances of that Blueprint. + + +For more details on the new features and improvements available in the Beta, please visit the [Wiki](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/wiki/What's-new-%3F). + +Some of the new features in the beta are still in their early stage, and will be improved upon in subsequent release of the plugin. + +# Feedback + +Please use this repository's "Issues" to report any bugs, problems, or simply to give us feedback on your experience with version2. + +# Compatibility + +Currently, the [Beta1](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/releases) release of V2 has binaries that have been built for UE4.25 and UE4.24, and is linked with the latest production build of Houdini, H18.0.597. + +Source code for the plugin is available on this repository for UE4.25, UE4.24, UE4.23 and the master branch of Unreal (4.26). + +As of Beta1, Version 2 is now backward compatible with version 1 of the Houdini Engine for Unreal plugin. + +When loading a level that contains Houdini objects made with version 1, the plugin now tries to convert the V1 components, parameters, inputs and outputs to their v2 equivalents. + +Some HDAs might need to be rebuilt after the conversion for their parameters and inputs to be displayed properly by the v2 plugin. + +The conversion of the legacy v1 data is still in progress and will be improved upon in the future. +However, the Houdini Digital Assets themselves (the HDA files), that were created for version 1 of the plugin are fully compatible with version 2, as it supports most of version 1 workflows. + +# Installing the plugin + +01. Download the pre-built binaries of the plugin in the "Releases" section of this repository. + +01. Extract the "HoudiniEngine" folder in the release to the "Plugins/Runtime" folder of Unreal. + You can install the plugin either directly in the engine folder (in "Engine/Plugins/Runtime/HoudiniEngine") or in your project folder (in "Plugins/Runtime/HoudiniEngine"). +01. Start Unreal Engine, open the Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. +01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". +01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. +01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters and inputs in the `Details` panel. Modifying any of the parameters will force the asset to recook and possibly update its geometry. + + +# Building from source + +01. Get the UE4 source code from: https://github.com/EpicGames/UnrealEngine/releases +01. Within the UE4 source, navigate to `Engine/Plugins/Runtime`, and clone this repo into a folder named `HoudiniEngine`. +01. Download and install the correct build of 64-bit Houdini. To get the build number, look at the header of `Source/HoudiniEngine/HoudiniEngine.Build.cs`, under `Houdini Version`. +01. Generate the UE4 Project Files (by running `GenerateProjectFiles`) and build Unreal, either in x64 `Debug Editor` or x64 `Development Editor`. +01. When starting the Unreal Engine editor, go to Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. +01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". +01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. +01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters in the `Details` pane. Modifying any of the parameters will force the asset to recook and possibly update its geometry. + + diff --git a/Source/HoudiniEngine/HoudiniEngine.Build.cs b/Source/HoudiniEngine/HoudiniEngine.Build.cs index a17effe5b..8986c9afc 100644 --- a/Source/HoudiniEngine/HoudiniEngine.Build.cs +++ b/Source/HoudiniEngine/HoudiniEngine.Build.cs @@ -1,5 +1,5 @@ /* - * Copyright (c) <2020> Side Effects Software Inc. + * Copyright (c) <2021> 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 @@ -32,7 +32,7 @@ /* - Houdini Version: 18.5.408 + Houdini Version: 18.5.462 Houdini Engine Version: 3.5.1 Unreal Version: 4.26.0 @@ -47,7 +47,7 @@ public class HoudiniEngine : ModuleRules { private string GetHFSPath() { - string HoudiniVersion = "18.5.408"; + string HoudiniVersion = "18.5.462"; bool bIsRelease = true; string HFSPath = ""; string RegistryPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Side Effects Software"; @@ -75,7 +75,7 @@ private string GetHFSPath() if ( Directory.Exists( HPath ) ) return HPath; } - + HEngineRegistry = Registry32Path + string.Format(@"\Houdini Engine {0}", HoudiniVersion); HPath = Microsoft.Win32.Registry.GetValue(HEngineRegistry, "InstallPath", null) as string; if ( HPath != null ) @@ -106,7 +106,7 @@ private string GetHFSPath() if ( Directory.Exists( HPath ) ) return HPath; } - + // Look for the Houdini registry install path for the version the plug-in was compiled for HoudiniRegistry = Registry32Path + string.Format(@"\Houdini {0}", HoudiniVersion); HPath = Microsoft.Win32.Registry.GetValue(HoudiniRegistry, "InstallPath", null) as string; @@ -137,10 +137,10 @@ private string GetHFSPath() // We couldn't find the exact version the plug-in was built for, we can still try with the active version in the registry string ActiveHEngine = Microsoft.Win32.Registry.GetValue(RegistryPath, "ActiveEngineVersion", null) as string; - if ( ActiveHEngine == null ) - { - ActiveHEngine = Microsoft.Win32.Registry.GetValue(Registry32Path, "ActiveEngineVersion", null) as string; - } + if ( ActiveHEngine == null ) + { + ActiveHEngine = Microsoft.Win32.Registry.GetValue(Registry32Path, "ActiveEngineVersion", null) as string; + } if ( ActiveHEngine != null ) { // See if the latest active HEngine version has the proper major/minor version @@ -166,10 +166,10 @@ private string GetHFSPath() // Active HEngine version didn't match, so try with the active Houdini version string ActiveHoudini = Microsoft.Win32.Registry.GetValue(RegistryPath, "ActiveVersion", null) as string; - if ( ActiveHoudini == null ) + if ( ActiveHoudini == null ) { - ActiveHoudini = Microsoft.Win32.Registry.GetValue(Registry32Path, "ActiveVersion", null) as string; - } + ActiveHoudini = Microsoft.Win32.Registry.GetValue(Registry32Path, "ActiveVersion", null) as string; + } if ( ActiveHoudini != null ) { // See if the latest active Houdini version has the proper major/minor version @@ -276,10 +276,10 @@ public HoudiniEngine( ReadOnlyTargetRules Target ) : base( Target ) PrivateIncludePaths.AddRange( new string[] - { + { "HoudiniEngineRuntime/Private" } - ); + ); // Add common dependencies. PublicDependencyModuleNames.AddRange( @@ -294,7 +294,7 @@ public HoudiniEngine( ReadOnlyTargetRules Target ) : base( Target ) "RHI", "Foliage", "Landscape", - "StaticMeshDescription", + "StaticMeshDescription", } ); @@ -309,8 +309,8 @@ public HoudiniEngine( ReadOnlyTargetRules Target ) : base( Target ) if (Target.bBuildEditor == true) { PrivateDependencyModuleNames.AddRange( - new string[] - { + new string[] + { "AppFramework", "AssetTools", "EditorStyle", diff --git a/Source/HoudiniEngine/Private/HBSPOps.cpp b/Source/HoudiniEngine/Private/HBSPOps.cpp index 5d57e502a..e1101a6df 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.cpp +++ b/Source/HoudiniEngine/Private/HBSPOps.cpp @@ -1,1462 +1,1483 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - - -#include "HBSPOps.h" -#include "EngineDefines.h" -#include "Model.h" -#include "Materials/Material.h" -#include "Engine/BrushBuilder.h" -#include "Editor/EditorEngine.h" -#include "Components/BrushComponent.h" -#include "GameFramework/Volume.h" - -DEFINE_LOG_CATEGORY_STATIC(LogBSPOps, Log, All); - -/** Errors encountered in Csg operation. */ -int32 FHBSPOps::GErrors = 0; -bool FHBSPOps::GFastRebuild = false; - -static void TagReferencedNodes( UModel *Model, int32 *NodeRef, int32 *PolyRef, int32 iNode ) -{ - FBspNode &Node = Model->Nodes[iNode]; - - NodeRef[iNode ] = 0; - PolyRef[Node.iSurf] = 0; - - if( Node.iFront != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iFront); - if( Node.iBack != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iBack ); - if( Node.iPlane != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iPlane); -} - -// -// Update a bounding volume by expanding it to enclose a list of polys. -// -static void UpdateBoundWithPolys( FBox& Bound, FPoly** PolyList, int32 nPolys ) -{ - for( int32 i=0; iVertices.Num(); j++ ) - Bound += PolyList[i]->Vertices[j]; -} - -// -// Update a convolution hull with a list of polys. -// -static void UpdateConvolutionWithPolys( UModel *Model, int32 iNode, FPoly **PolyList, int32 nPolys ) -{ - FBox Box(ForceInit); - - FBspNode &Node = Model->Nodes[iNode]; - Node.iCollisionBound = Model->LeafHulls.Num(); - for( int32 i=0; iiBrushPoly != INDEX_NONE ) - { - int32 j; - for( j=0; jiBrushPoly == PolyList[i]->iBrushPoly ) - break; - if( j >= i ) - Model->LeafHulls.Add(PolyList[i]->iBrushPoly); - } - for( int32 j=0; jVertices.Num(); j++ ) - Box += PolyList[i]->Vertices[j]; - } - Model->LeafHulls.Add(INDEX_NONE); - - // Add bounds. - Model->LeafHulls.Add( *(int32*)&Box.Min.X ); - Model->LeafHulls.Add( *(int32*)&Box.Min.Y ); - Model->LeafHulls.Add( *(int32*)&Box.Min.Z ); - Model->LeafHulls.Add( *(int32*)&Box.Max.X ); - Model->LeafHulls.Add( *(int32*)&Box.Max.Y ); - Model->LeafHulls.Add( *(int32*)&Box.Max.Z ); - -} - -// -// Cut a partitioning poly by a list of polys, and add the resulting inside pieces to the -// front list and back list. -// -static void SplitPartitioner -( - UModel* Model, - FPoly** PolyList, - FPoly** FrontList, - FPoly** BackList, - int32 n, - int32 nPolys, - int32& nFront, - int32& nBack, - FPoly InfiniteEdPoly, - TArray& AllocatedFPolys -) -{ - FPoly FrontPoly,BackPoly; - while( n < nPolys ) - { - FPoly* Poly = PolyList[n]; - switch( InfiniteEdPoly.SplitWithPlane(Poly->Vertices[0],Poly->Normal,&FrontPoly,&BackPoly,0) ) - { - case SP_Coplanar: - // May occasionally happen. -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got inficoplanar") ); - break; - - case SP_Front: - // Shouldn't happen if hull is correct. -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got infifront") ); - return; - - case SP_Split: - InfiniteEdPoly = BackPoly; - break; - - case SP_Back: - break; - } - n++; - } - - FPoly* New = new FPoly; - *New = InfiniteEdPoly; - New->Reverse(); - New->iBrushPoly |= 0x40000000; - FrontList[nFront++] = New; - AllocatedFPolys.Add( New ); - - New = new FPoly; - *New = InfiniteEdPoly; - BackList[nBack++] = New; - AllocatedFPolys.Add( New ); -} - -// -// Build an FPoly representing an "infinite" plane (which exceeds the maximum -// dimensions of the world in all directions) for a particular Bsp node. -// -FPoly FHBSPOps::BuildInfiniteFPoly( UModel* Model, int32 iNode ) -{ - FBspNode &Node = Model->Nodes [iNode ]; - FBspSurf &Poly = Model->Surfs [Node.iSurf ]; - FVector Base = Poly.Plane * Poly.Plane.W; - FVector Normal = Poly.Plane; - FVector Axis1,Axis2; - - // Find two non-problematic axis vectors. - Normal.FindBestAxisVectors( Axis1, Axis2 ); - - // Set up the FPoly. - FPoly EdPoly; - EdPoly.Init(); - EdPoly.Normal = Normal; - EdPoly.Base = Base; - new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX + Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX + Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX - Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX - Axis2*WORLD_MAX); - - return EdPoly; -} - -// -// Recursively filter a set of polys defining a convex hull down the Bsp, -// splitting it into two halves at each node and adding in the appropriate -// face polys at splits. -// -static void FilterBound -( - UModel* Model, - FBox* ParentBound, - int32 iNode, - FPoly** PolyList, - int32 nPolys, - int32 Outside -) -{ - FMemMark Mark(FMemStack::Get()); - FBspNode& Node = Model->Nodes [iNode]; - FBspSurf& Surf = Model->Surfs [Node.iSurf]; - FVector Base = Surf.Plane * Surf.Plane.W; - FVector& Normal = Model->Vectors[Surf.vNormal]; - FBox Bound(ForceInit); - - Bound.Min.X = Bound.Min.Y = Bound.Min.Z = +WORLD_MAX; - Bound.Max.X = Bound.Max.Y = Bound.Max.Z = -WORLD_MAX; - - // Split bound into front half and back half. - FPoly** FrontList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nFront=0; - FPoly** BackList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nBack=0; - - // Keeping track of allocated FPoly structures to delete later on. - TArray AllocatedFPolys; - - FPoly* FrontPoly = new FPoly; - FPoly* BackPoly = new FPoly; - - // Keep track of allocations. - AllocatedFPolys.Add( FrontPoly ); - AllocatedFPolys.Add( BackPoly ); - - for( int32 i=0; iSplitWithPlane( Base, Normal, FrontPoly, BackPoly, 0 ) ) - { - case SP_Coplanar: -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got coplanar") ); - FrontList[nFront++] = Poly; - BackList[nBack++] = Poly; - break; - - case SP_Front: - FrontList[nFront++] = Poly; - break; - - case SP_Back: - BackList[nBack++] = Poly; - break; - - case SP_Split: - FrontList[nFront++] = FrontPoly; - BackList [nBack++] = BackPoly; - - FrontPoly = new FPoly; - BackPoly = new FPoly; - - // Keep track of allocations. - AllocatedFPolys.Add( FrontPoly ); - AllocatedFPolys.Add( BackPoly ); - - break; - - default: - UE_LOG(LogBSPOps, Fatal, TEXT("FZoneFilter::FilterToLeaf: Unknown split code") ); - } - } - if( nFront && nBack ) - { - // Add partitioner plane to front and back. - FPoly InfiniteEdPoly = FHBSPOps::BuildInfiniteFPoly( Model, iNode ); - InfiniteEdPoly.iBrushPoly = iNode; - - SplitPartitioner(Model,PolyList,FrontList,BackList,0,nPolys,nFront,nBack,InfiniteEdPoly,AllocatedFPolys); - } - else - { -// if( !nFront ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty fronthull") ); -// if( !nBack ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty backhull") ); - } - - // Recursively update all our childrens' bounding volumes. - if( nFront > 0 ) - { - if( Node.iFront != INDEX_NONE ) - FilterBound( Model, &Bound, Node.iFront, FrontList, nFront, Outside || Node.IsCsg() ); - else if( Outside || Node.IsCsg() ) - UpdateBoundWithPolys( Bound, FrontList, nFront ); - else - UpdateConvolutionWithPolys( Model, iNode, FrontList, nFront ); - } - if( nBack > 0 ) - { - if( Node.iBack != INDEX_NONE) - FilterBound( Model, &Bound,Node.iBack, BackList, nBack, Outside && !Node.IsCsg() ); - else if( Outside && !Node.IsCsg() ) - UpdateBoundWithPolys( Bound, BackList, nBack ); - else - UpdateConvolutionWithPolys( Model, iNode, BackList, nBack ); - } - - // Update parent bound to enclose this bound. - if( ParentBound ) - *ParentBound += Bound; - - // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. - for( int32 i=0; i0); - - // No need to test if only one poly. - if( NumPolys==1 ) - return PolyList[0]; - - FPoly *Poly, *Best=NULL; - float Score, BestScore; - int32 i, Index, j, Inc; - int32 Splits, Front, Back, Coplanar, AllSemiSolids; - - //PortalBias -- added by Legend on 4/12/2000 - float PortalBias = InPortalBias / 100.0f; - Balance &= 0xFF; // keep only the low byte to recover "Balance" - //UE_LOG(LogBSPOps, Log, TEXT("Balance=%d PortalBias=%f"), Balance, PortalBias ); - - if (Opt==FHBSPOps::BSP_Optimal) Inc = 1; // Test lots of nodes. - else if (Opt==FHBSPOps::BSP_Good) Inc = FMath::Max(1,NumPolys/20); // Test 20 nodes. - else /* BSP_Lame */ Inc = FMath::Max(1,NumPolys/4); // Test 4 nodes. - - // See if there are any non-semisolid polygons here. - for( i=0; iPolyFlags & PF_AddLast) ) - break; - AllSemiSolids = (i>=NumPolys); - - // Search through all polygons in the pool and find: - // A. The number of splits each poly would make. - // B. The number of front and back nodes the polygon would create. - // C. Number of coplanars. - BestScore = 0; - for( i=0; iPolyFlags & PF_AddLast) && !(Poly->PolyFlags & PF_Portal) ) - && !AllSemiSolids ); - if( Index>=i+Inc || Index>=NumPolys ) - continue; - - for( j=0; jSplitWithPlaneFast( FPlane( Poly->Vertices[0], Poly->Normal), NULL, NULL ) ) - { - case SP_Coplanar: - Coplanar++; - break; - - case SP_Front: - Front++; - break; - - case SP_Back: - Back++; - break; - - case SP_Split: - // Disfavor splitting polys that are zone portals. - if( !(OtherPoly->PolyFlags & PF_Portal) ) - Splits++; - else - Splits += 16; - break; - } - } - // added by Legend 1/31/1999 - // Score optimization: minimize cuts vs. balance tree (as specified in BSP Rebuilder dialog) - Score = ( 100.0 - float(Balance) ) * Splits + float(Balance) * FMath::Abs( Front - Back ); - if( Poly->PolyFlags & PF_Portal ) - { - // PortalBias -- added by Legend on 4/12/2000 - // - // PortalBias enables level designers to control the effect of Portals on the BSP. - // This effect can range from 0.0 (ignore portals), to 1.0 (portals cut everything). - // - // In builds prior to this (since the 221 build dating back to 1/31/1999) the bias - // has been 1.0 causing the portals to cut the BSP in ways that will potentially - // degrade level performance, and increase the BSP complexity. - // - // By setting the bias to a value between 0.3 and 0.7 the positive effects of - // the portals are preserved without giving them unreasonable priority in the BSP. - // - // Portals should be weighted high enough in the BSP to separate major parts of the - // level from each other (pushing entire rooms down the branches of the BSP), but - // should not be so high that portals cut through adjacent geometry in a way that - // increases complexity of the room being (typically, accidentally) cut. - // - Score -= ( 100.0 - float(Balance) ) * Splits * PortalBias; // ignore PortalBias of the split polys -- bias toward portal selection for cutting planes! - } - //UE_LOG(LogBSPOps, Log, " %4d: Score = %f (Front = %4d, Back = %4d, Splits = %4d, Flags = %08X)", Index, Score, Front, Back, Splits, Poly->PolyFlags ); //LEC - - if( Score AllocatedFPolys; - - // To account for big EdPolys split up. - int32 NumPolysToAlloc = NumPolys + 8 + NumPolys/4; - int32 NumFront=0; FPoly **FrontList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; - int32 NumBack =0; FPoly **BackList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; - - FPoly *SplitPoly = FindBestSplit( NumPolys, PolyList, Opt, Balance, PortalBias ); - - // Add the splitter poly to the Bsp with either a new BspSurf or an existing one. - if( RebuildSimplePolys ) - { - SplitPoly->iLinkSurf = Model->Surfs.Num(); - } - - int32 iOurNode = bspAddNode(Model,iParent,NodePlace,0,SplitPoly, BspPoints, BspVectors); - int32 iPlaneNode = iOurNode; - - // Now divide all polygons in the pool into (A) polygons that are - // in front of Poly, and (B) polygons that are in back of Poly. - // Coplanar polys are inserted immediately, before recursing. - - // If any polygons are split by Poly, we ignrore the original poly, - // split it into two polys, and add two new polys to the pool. - FPoly *FrontEdPoly = new FPoly; - FPoly *BackEdPoly = new FPoly; - // Keep track of allocations. - AllocatedFPolys.Add( FrontEdPoly ); - AllocatedFPolys.Add( BackEdPoly ); - - for( int32 i=0; iSplitWithPlane( SplitPoly->Vertices[0], SplitPoly->Normal, FrontEdPoly, BackEdPoly, 0 ) ) - { - case SP_Coplanar: - if( RebuildSimplePolys ) - { - EdPoly->iLinkSurf = Model->Surfs.Num()-1; - } - iPlaneNode = bspAddNode( Model, iPlaneNode, NODE_Plane, 0, EdPoly, BspPoints, BspVectors ); - break; - - case SP_Front: - FrontList[NumFront++] = PolyList[i]; - break; - - case SP_Back: - BackList[NumBack++] = PolyList[i]; - break; - - case SP_Split: - - // Create front & back nodes. - FrontList[NumFront++] = FrontEdPoly; - BackList [NumBack ++] = BackEdPoly; - - FrontEdPoly = new FPoly; - BackEdPoly = new FPoly; - // Keep track of allocations. - AllocatedFPolys.Add( FrontEdPoly ); - AllocatedFPolys.Add( BackEdPoly ); - - break; - } - } - - // Recursively split the front and back pools. - if( NumFront > 0 ) SplitPolyList( Model, iOurNode, NODE_Front, NumFront, FrontList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); - if( NumBack > 0 ) SplitPolyList( Model, iOurNode, NODE_Back, NumBack, BackList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); - - // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. - for( int32 i=0; iGetName() ); // moved here so that we can easily debug when an actor has lost parts of its brush - - check(Actor->GetBrushComponent()); - check(Actor->Brush); - check(Actor->Brush->RootOutside); - - RebuildBrush(Actor->Brush, BspPoints, BspVectors); - - // Make sure simplified collision is up to date. - Actor->GetBrushComponent()->BuildSimpleBrushCollision(); - Actor->RebuildNavigationData(); -} - -/** - * Duplicates the specified brush and makes it into a CSG-able level brush. - * @return The new brush, or NULL if the original was empty. - */ -void FHBSPOps::csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - check(Src); - check(Src->GetBrushComponent()); - check(Src->Brush); - - // Handle empty brush. - if( !bAllowEmpty && !Src->Brush->Polys->Element.Num() ) - { - Dest->Brush = NULL; - Dest->GetBrushComponent()->Brush = NULL; - return; - } - - // Duplicate the brush and its polys. - Dest->PolyFlags = PolyFlags; - Dest->Brush = NewObject(Dest, NAME_None, ResFlags); - Dest->Brush->Initialize(nullptr, Src->Brush->RootOutside); - Dest->Brush->Polys = NewObject(Dest->Brush, NAME_None, ResFlags); - Dest->Brush->Polys->Element = Src->Brush->Polys->Element; - Dest->GetBrushComponent()->Brush = Dest->Brush; - if(Src->BrushBuilder != nullptr) - { - Dest->BrushBuilder = DuplicateObject(Src->BrushBuilder, Dest); - } - - // Update poly textures. - for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) - { - Dest->Brush->Polys->Element[i].iBrushPoly = INDEX_NONE; - } - - // Copy positioning, and build bounding box. - if(bCopyPosRotScale) - { - Dest->CopyPosRotScaleFrom( Src ); - } - - // If it's a moving brush, prep it. - if( bNeedsPrep ) - { - csgPrepMovingBrush( Dest, BspPoints, BspVectors ); - } -} - -/** - * Adds a brush to the list of CSG brushes in the level, using a CSG operation. - * - * @return A newly-created copy of the brush. - */ -ABrush* FHBSPOps::csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - check(Actor); - check(Actor->GetBrushComponent()); - check(Actor->Brush); - check(Actor->Brush->Polys); - check(Actor->GetWorld()); - - // Can't do this if brush has no polys. - if( !Actor->Brush->Polys->Element.Num() ) - return NULL; - - // Spawn a new actor for the brush. - - ABrush* Result = Actor->GetWorld()->SpawnBrush(); - Result->SetNotForClientOrServer(); - - // Duplicate the brush. - csgCopyBrush - ( - Result, - Actor, - PolyFlags, - RF_Transactional, - 0, - true, - false, - BspPoints, - BspVectors - ); - check(Result->Brush); - - if( Result->GetBrushBuilder() ) - { - FActorLabelUtilities::SetActorLabelUnique(Result, FText::Format(NSLOCTEXT("BSPBrushOps", "BrushName", "{0} Brush"), FText::FromString(Result->GetBrushBuilder()->GetClass()->GetDescription())).ToString()); - } - // Assign the default material to the brush's polys. - for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) - { - FPoly& CurrentPoly = Result->Brush->Polys->Element[i]; - if ( !CurrentPoly.Material ) - { - CurrentPoly.Material = UMaterial::GetDefaultMaterial(MD_Surface); - } - } - - // Set add-info. - Result->BrushType = BrushType; - - Result->ReregisterAllComponents(); - - return Result; -} - -/** Add a new point to the model (preventing duplicates) and return its index. */ -static int32 AddThing( TArray& Vectors, FVector& V, float Thresh, int32 Check ) -{ - if( Check ) - { - // See if this is very close to an existing point/vector. - for( int32 i=0; i -Thresh) && (Temp < Thresh) ) - { - Temp=(V.Y - TableVect.Y); - if( (Temp > -Thresh) && (Temp < Thresh) ) - { - Temp=(V.Z - TableVect.Z); - if( (Temp > -Thresh) && (Temp < Thresh) ) - { - // Found nearly-matching vector. - return i; - } - } - } - } - } - return Vectors.Add( V ); -} - -/** Add a new vector to the model, merging near-duplicates, and return its index. */ -int32 FHBSPOps::bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ) -{ - const float Thresh = Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR; - - if (BspVectors) - { - // If a points grid has been built for quick vector lookup, use that instead of doing a linear search - const int32 NextIndex = Model->Vectors.Num(); - const int32 ReturnedIndex = BspVectors->FindOrAddPoint(*V, NextIndex, Thresh); - if (ReturnedIndex == NextIndex) - { - Model->Vectors.Add(*V); - } - - return ReturnedIndex; - } - - return AddThing - ( - Model->Vectors, - *V, - Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR, - 1 - ); -} - -/** Add a new point to the model, merging near-duplicates, and return its index. */ -int32 FHBSPOps::bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ) -{ - const float Thresh = Exact ? THRESH_POINTS_ARE_SAME : THRESH_POINTS_ARE_NEAR; - - if (BspPoints) - { - // If a points grid has been built for quick point lookup, use that instead of doing a linear search - const int32 NextIndex = Model->Points.Num(); - // Always look for points with a low threshold; a generous threshold can result in 'leaks' in the BSP and unwanted polys being generated - const int32 ReturnedIndex = BspPoints->FindOrAddPoint(*V, NextIndex, THRESH_POINTS_ARE_SAME); - if (ReturnedIndex == NextIndex) - { - Model->Points.Add(*V); - } - - return ReturnedIndex; - } - - // Try to find a match quickly from the Bsp. This finds all potential matches - // except for any dissociated from nodes/surfaces during a rebuild. - FVector Temp; - int32 pVertex; - float NearestDist = Model->FindNearestVertex(*V,Temp,Thresh,pVertex); - if( (NearestDist >= 0.0) && (NearestDist <= Thresh) ) - { - // Found an existing point. - return pVertex; - } - else - { - // No match found; add it slowly to find duplicates. - return AddThing(Model->Points, *V, Thresh, !GFastRebuild); - } -} - - -/** - * Builds Bsp from the editor polygon set (EdPolys) of a model. - * - * Opt = Bsp optimization, BSP_Lame (fast), BSP_Good (medium), BSP_Optimal (slow) - * Balance = 0-100, 0=only worry about minimizing splits, 100=only balance tree. - */ -void FHBSPOps::bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - int32 OriginalPolys = Model->Polys->Element.Num(); - - // Empty the model's tables. - if( RebuildSimplePolys==1 ) - { - // Empty everything but polys. - Model->EmptyModel( 1, 0 ); - } - else if( RebuildSimplePolys==0 ) - { - // Empty node vertices. - for( int32 i=0; iNodes.Num(); i++ ) - Model->Nodes[i].NumVertices = 0; - - // Refresh the Bsp. - bspRefresh(Model,1); - - // Empty nodes. - Model->EmptyModel( 0, 0 ); - } - if( Model->Polys->Element.Num() ) - { - // Allocate polygon pool. - FMemMark Mark(FMemStack::Get()); - FPoly** PolyList = new( FMemStack::Get(), Model->Polys->Element.Num() )FPoly*; - - // Add all FPolys to active list. - for( int32 i=0; iPolys->Element.Num(); i++ ) - if( Model->Polys->Element[i].Vertices.Num() ) - PolyList[i] = &Model->Polys->Element[i]; - - // Now split the entire Bsp by splitting the list of all polygons. - SplitPolyList - ( - Model, - INDEX_NONE, - NODE_Root, - Model->Polys->Element.Num(), - PolyList, - Opt, - Balance, - PortalBias, - RebuildSimplePolys, - BspPoints, - BspVectors - ); - - // Now build the bounding boxes for all nodes. - if( RebuildSimplePolys==0 ) - { - // Remove unreferenced things. - bspRefresh( Model, 1 ); - - // Rebuild all bounding boxes. - bspBuildBounds( Model ); - } - - Mark.Pop(); - } - -// UE_LOG(LogBSPOps, Log, TEXT("bspBuild built %i convex polys into %i nodes"), OriginalPolys, Model->Nodes.Num() ); -} - -/** - * If the Bsp's point and vector tables are nearly full, reorder them and delete unused ones. - */ -void FHBSPOps::bspRefresh( UModel* Model, bool NoRemapSurfs ) -{ - FMemStack& MemStack = FMemStack::Get(); - - FMemMark Mark(MemStack); - - int32 NumNodes = Model->Nodes.Num(); - int32 NumSurfs = Model->Surfs.Num(); - int32 NumVectors = Model->Vectors.Num(); - int32 NumPoints = Model->Points.Num(); - - // Remove unreferenced Bsp surfs. - int32* PolyRef; - if( NoRemapSurfs ) - { - PolyRef = NewZeroed(MemStack, NumSurfs); - } - else - { - PolyRef = NewOned(MemStack, NumSurfs); - } - - int32* NodeRef = NewOned(MemStack, NumNodes); - if( NumNodes > 0 ) - { - TagReferencedNodes( Model, NodeRef, PolyRef, 0 ); - } - - // Remap Bsp surfs. - { - int32 n=0; - for( int32 i=0; iSurfs[n] = Model->Surfs[i]; - PolyRef[i]=n++; - } - } - //UE_LOG(LogBSPOps, Log, TEXT("Polys: %i -> %i"), NumSurfs, n ); - Model->Surfs.RemoveAt( n, NumSurfs-n ); - NumSurfs = n; - } - - // Remap Bsp nodes. - { - int32 n=0; - for( int32 i=0; iNodes[n] = Model->Nodes[i]; - NodeRef[i]=n++; - } - } - //UE_LOG(LogBSPOps, Log, TEXT("Nodes: %i -> %i"), NumNodes, n ); - Model->Nodes.RemoveAt( n, NumNodes-n ); - NumNodes = n; - } - - // Update Bsp nodes. - for( int32 i=0; iNodes[i]; - Node->iSurf = PolyRef[Node->iSurf]; - if (Node->iFront != INDEX_NONE) Node->iFront = NodeRef[Node->iFront]; - if (Node->iBack != INDEX_NONE) Node->iBack = NodeRef[Node->iBack]; - if (Node->iPlane != INDEX_NONE) Node->iPlane = NodeRef[Node->iPlane]; - } - - // Remove unreferenced points and vectors. - int32* VectorRef = NewOned(MemStack, NumVectors); - int32* PointRef = NewOned(MemStack, NumPoints); - - // Check Bsp surfs. - TArray VertexRef; - for( int32 i=0; iSurfs[i]; - VectorRef [Surf->vNormal ] = 0; - VectorRef [Surf->vTextureU ] = 0; - VectorRef [Surf->vTextureV ] = 0; - PointRef [Surf->pBase ] = 0; - } - - // Check Bsp nodes. - for( int32 i=0; iNodes[i]; - FVert* VertPool = &Model->Verts[Node->iVertPool]; - for( int B=0; BNumVertices; B++ ) - { - PointRef[VertPool->pVertex] = 0; - VertPool++; - } - } - - // Remap points. - { - int32 n=0; - for( int32 i=0; iPoints[n] = Model->Points[i]; - PointRef[i] = n++; - } - //UE_LOG(LogBSPOps, Log, TEXT("Points: %i -> %i"), NumPoints, n ); - Model->Points.RemoveAt( n, NumPoints-n ); - NumPoints = n; - } - - // Remap vectors. - { - int32 n=0; - for (int32 i=0; iVectors[n] = Model->Vectors[i]; - VectorRef[i] = n++; - } - //UE_LOG(LogBSPOps, Log, TEXT("Vectors: %i -> %i"), NumVectors, n ); - Model->Vectors.RemoveAt( n, NumVectors-n ); - NumVectors = n; - } - - // Update Bsp surfs. - for( int32 i=0; iSurfs[i]; - Surf->vNormal = VectorRef [Surf->vNormal ]; - Surf->vTextureU = VectorRef [Surf->vTextureU]; - Surf->vTextureV = VectorRef [Surf->vTextureV]; - Surf->pBase = PointRef [Surf->pBase ]; - } - - // Update Bsp nodes. - for( int32 i=0; iNodes[i]; - FVert* VertPool = &Model->Verts[Node->iVertPool]; - for( int B=0; BNumVertices; B++ ) - { - VertPool->pVertex = PointRef [VertPool->pVertex]; - VertPool++; - } - } - - // Shrink the objects. - Model->ShrinkModel(); - - Mark.Pop(); -} - -// Build bounding volumes for all Bsp nodes. The bounding volume of the node -// completely encloses the "outside" space occupied by the nodes. Note that -// this is not the same as representing the bounding volume of all of the -// polygons within the node. -// -// We start with a practically-infinite cube and filter it down the Bsp, -// whittling it away until all of its convex volume fragments land in leaves. -void FHBSPOps::bspBuildBounds( UModel* Model ) -{ - if( Model->Nodes.Num()==0 ) - return; - - FPoly Polys[6], *PolyList[6]; - for( int32 i=0; i<6; i++ ) - { - PolyList[i] = &Polys[i]; - PolyList[i]->Init(); - PolyList[i]->iBrushPoly = INDEX_NONE; - } - - new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); - Polys[0].Normal =FVector( 0.000000, 0.000000, 1.000000 ); - Polys[0].Base =Polys[0].Vertices[0]; - - new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[1].Normal =FVector( 0.000000, 0.000000, -1.000000 ); - Polys[1].Base =Polys[1].Vertices[0]; - - new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[2].Normal =FVector( 0.000000, 1.000000, 0.000000 ); - Polys[2].Base =Polys[2].Vertices[0]; - - new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[3].Normal =FVector( 0.000000, -1.000000, 0.000000 ); - Polys[3].Base =Polys[3].Vertices[0]; - - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[4].Normal =FVector( 1.000000, 0.000000, 0.000000 ); - Polys[4].Base =Polys[4].Vertices[0]; - - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[5].Normal =FVector(-1.000000, 0.000000, 0.000000 ); - Polys[5].Base =Polys[5].Vertices[0]; - // Empty hulls. - Model->LeafHulls.Empty(); - for( int32 i=0; iNodes.Num(); i++ ) - Model->Nodes[i].iCollisionBound = INDEX_NONE; - FilterBound( Model, NULL, 0, PolyList, 6, Model->RootOutside ); -// UE_LOG(LogBSPOps, Log, TEXT("bspBuildBounds: Generated %i hulls"), Model->LeafHulls.Num() ); -} - -/** - * Validate a brush, and set iLinks on all EdPolys to index of the - * first identical EdPoly in the list, or its index if it's the first. - * Not transactional. - */ -void FHBSPOps::bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ) -{ - check(Brush != nullptr); - Brush->Modify(); - if( ForceValidate || !Brush->Linked ) - { - Brush->Linked = 1; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - Brush->Polys->Element[i].iLink = i; - } - int32 n=0; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - FPoly* EdPoly = &Brush->Polys->Element[i]; - if( EdPoly->iLink==i ) - { - for( int32 j=i+1; jPolys->Element.Num(); j++ ) - { - FPoly* OtherPoly = &Brush->Polys->Element[j]; - if - ( OtherPoly->iLink == j - && OtherPoly->Material == EdPoly->Material - && OtherPoly->TextureU == EdPoly->TextureU - && OtherPoly->TextureV == EdPoly->TextureV - && OtherPoly->PolyFlags == EdPoly->PolyFlags - && (OtherPoly->Normal | EdPoly->Normal)>0.9999 ) - { - float Dist = FVector::PointPlaneDist( OtherPoly->Vertices[0], EdPoly->Vertices[0], EdPoly->Normal ); - if( Dist>-0.001 && Dist<0.001 ) - { - OtherPoly->iLink = i; - n++; - } - } - } - } - } -// UE_LOG(LogBSPOps, Log, TEXT("BspValidateBrush linked %i of %i polys"), n, Brush->Polys->Element.Num() ); - } - - // Build bounds. - Brush->BuildBound(); -} - -void FHBSPOps::bspUnlinkPolys( UModel* Brush ) -{ - Brush->Modify(); - Brush->Linked = 1; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - Brush->Polys->Element[i].iLink = i; - } -} - -// Add an editor polygon to the Bsp, and also stick a reference to it -// in the editor polygon's BspNodes list. If the editor polygon has more sides -// than the Bsp will allow, split it up into several sub-polygons. -// -// Returns: Index to newly-created node of Bsp. If several nodes were created because -// of split polys, returns the parent (highest one up in the Bsp). -int32 FHBSPOps::bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - if( NodePlace == NODE_Plane ) - { - // Make sure coplanars are added at the end of the coplanar list so that - // we don't insert NF_IsNew nodes with non NF_IsNew coplanar children. - while( Model->Nodes[iParent].iPlane != INDEX_NONE ) - { - iParent = Model->Nodes[iParent].iPlane; - } - } - FBspSurf* Surf = NULL; - if( EdPoly->iLinkSurf == Model->Surfs.Num() ) - { - int32 NewIndex = Model->Surfs.AddZeroed(); - Surf = &Model->Surfs[NewIndex]; - - // This node has a new polygon being added by bspBrushCSG; must set its properties here. - Surf->pBase = bspAddPoint (Model,&EdPoly->Base,1,BspPoints); - Surf->vNormal = bspAddVector (Model,&EdPoly->Normal,1,BspVectors); - Surf->vTextureU = bspAddVector (Model,&EdPoly->TextureU,0,BspVectors); - Surf->vTextureV = bspAddVector (Model,&EdPoly->TextureV,0,BspVectors); - Surf->Material = EdPoly->Material; - Surf->Actor = NULL; - - Surf->PolyFlags = EdPoly->PolyFlags & ~PF_NoAddToBSP; - Surf->LightMapScale= EdPoly->LightMapScale; - - // Find the LightmassPrimitiveSettings in the UModel... - int32 FoundLightmassIndex = INDEX_NONE; - if (Model->LightmassSettings.Find(EdPoly->LightmassSettings, FoundLightmassIndex) == false) - { - FoundLightmassIndex = Model->LightmassSettings.Add(EdPoly->LightmassSettings); - } - Surf->iLightmassIndex = FoundLightmassIndex; - - Surf->Actor = EdPoly->Actor; - Surf->iBrushPoly = EdPoly->iBrushPoly; - - if (EdPoly->Actor) - { - Surf->bHiddenEdTemporary = EdPoly->Actor->IsTemporarilyHiddenInEditor(); - Surf->bHiddenEdLevel = EdPoly->Actor->bHiddenEdLevel; - Surf->bHiddenEdLayer = EdPoly->Actor->bHiddenEdLayer; - } - - Surf->Plane = FPlane(EdPoly->Vertices[0],EdPoly->Normal); - } - else - { - check(EdPoly->iLinkSurf!=INDEX_NONE); - check(EdPoly->iLinkSurfSurfs.Num()); - Surf = &Model->Surfs[EdPoly->iLinkSurf]; - } - - // Set NodeFlags. - if( Surf->PolyFlags & PF_NotSolid ) NodeFlags |= NF_NotCsg; - if( Surf->PolyFlags & (PF_Invisible|PF_Portal) ) NodeFlags |= NF_NotVisBlocking; - - if( EdPoly->Vertices.Num() > FBspNode::MAX_NODE_VERTICES ) - { - // Split up into two coplanar sub-polygons (one with MAX_NODE_VERTICES vertices and - // one with all the remaining vertices) and recursively add them. - - // EdPoly1 is just the first MAX_NODE_VERTICES from EdPoly. - FMemMark Mark(FMemStack::Get()); - FPoly *EdPoly1 = new FPoly; - *EdPoly1 = *EdPoly; - EdPoly1->Vertices.RemoveAt(FBspNode::MAX_NODE_VERTICES,EdPoly->Vertices.Num() - FBspNode::MAX_NODE_VERTICES); - - // EdPoly2 is the first vertex from EdPoly, and the last EdPoly->Vertices.Num() - MAX_NODE_VERTICES + 1. - FPoly *EdPoly2 = new FPoly; - *EdPoly2 = *EdPoly; - EdPoly2->Vertices.RemoveAt(1,FBspNode::MAX_NODE_VERTICES - 2); - - int32 iNode = bspAddNode( Model, iParent, NodePlace, NodeFlags, EdPoly1, BspPoints, BspVectors ); // Add this poly first. - bspAddNode( Model, iNode, NODE_Plane, NodeFlags, EdPoly2, BspPoints, BspVectors ); // Then add other (may be bigger). - - delete EdPoly1; - delete EdPoly2; - - Mark.Pop(); - return iNode; // Return coplanar "parent" node (not coplanar child) - } - else - { - // Add node. - int32 iNode = Model->Nodes.AddZeroed(); - FBspNode& Node = Model->Nodes[iNode]; - - // Tell transaction tracking system that parent is about to be modified. - FBspNode* Parent=NULL; - if( NodePlace!=NODE_Root ) - Parent = &Model->Nodes[iParent]; - - // Set node properties. - Node.iSurf = EdPoly->iLinkSurf; - Node.NodeFlags = NodeFlags; - Node.iCollisionBound = INDEX_NONE; - Node.Plane = FPlane( EdPoly->Vertices[0], EdPoly->Normal ); - Node.iVertPool = Model->Verts.AddUninitialized(EdPoly->Vertices.Num()); - Node.iFront = INDEX_NONE; - Node.iBack = INDEX_NONE; - Node.iPlane = INDEX_NONE; - if( NodePlace==NODE_Root ) - { - Node.iLeaf[0] = INDEX_NONE; - Node.iLeaf[1] = INDEX_NONE; - Node.iZone[0] = 0; - Node.iZone[1] = 0; - } - else if( NodePlace==NODE_Front || NodePlace==NODE_Back ) - { - int32 ZoneFront=NodePlace==NODE_Front; - Node.iLeaf[0] = Parent->iLeaf[ZoneFront]; - Node.iLeaf[1] = Parent->iLeaf[ZoneFront]; - Node.iZone[0] = Parent->iZone[ZoneFront]; - Node.iZone[1] = Parent->iZone[ZoneFront]; - } - else - { - int32 IsFlipped = (Node.Plane|Parent->Plane)<0.0; - Node.iLeaf[0] = Parent->iLeaf[IsFlipped ]; - Node.iLeaf[1] = Parent->iLeaf[1-IsFlipped]; - Node.iZone[0] = Parent->iZone[IsFlipped ]; - Node.iZone[1] = Parent->iZone[1-IsFlipped]; - } - - // Link parent to this node. - if (NodePlace == NODE_Front) - { - Parent->iFront = iNode; - } - else if (NodePlace == NODE_Back) - { - Parent->iBack = iNode; - } - else if (NodePlace == NODE_Plane) - { - Parent->iPlane = iNode; - } - - // Add all points to point table, merging nearly-overlapping polygon points - // with other points in the poly to prevent criscrossing vertices from - // being generated. - - // Must maintain Node->NumVertices on the fly so that bspAddPoint is always - // called with the Bsp in a clean state. - Node.NumVertices = 0; - FVert* VertPool = &Model->Verts[ Node.iVertPool ]; - for( uint8 i=0; iVertices.Num(); i++ ) - { - int32 pVertex = bspAddPoint(Model,&EdPoly->Vertices[i],0, BspPoints); - if( Node.NumVertices==0 || VertPool[Node.NumVertices-1].pVertex!=pVertex ) - { - VertPool[Node.NumVertices].iSide = INDEX_NONE; - VertPool[Node.NumVertices].pVertex = pVertex; - Node.NumVertices++; - } - } - if( Node.NumVertices>=2 && VertPool[0].pVertex==VertPool[Node.NumVertices-1].pVertex ) - { - Node.NumVertices--; - } - if( Node.NumVertices < 3 ) - { - GErrors++; -// UE_LOG(LogBSPOps, Warning, TEXT("bspAddNode: Infinitesimal polygon %i (%i)"), Node.NumVertices, EdPoly->Vertices.Num() ); - Node.NumVertices = 0; - } - - return iNode; - } -} - -/** - * Rebuild some brush internals - */ -void FHBSPOps::RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - Brush->Modify(); - Brush->EmptyModel(1, 0); - - // Build bounding box. - Brush->BuildBound(); - - // Build BSP for the brush. - bspBuild(Brush, BSP_Good, 15, 70, 1, 0, BspPoints, BspVectors); - bspRefresh(Brush, 1); - bspBuildBounds(Brush); -} - -/** - * Rotates the specified brush's vertices. - */ -void FHBSPOps::RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - if(Brush->GetBrushComponent()->Brush && Brush->GetBrushComponent()->Brush->Polys) - { - for( int32 poly = 0 ; poly < Brush->GetBrushComponent()->Brush->Polys->Element.Num() ; poly++ ) - { - FPoly* Poly = &(Brush->GetBrushComponent()->Brush->Polys->Element[poly]); - - // Rotate the vertices. - const FRotationMatrix RotMatrix( Rotation ); - for( int32 vertex = 0 ; vertex < Poly->Vertices.Num() ; vertex++ ) - { - Poly->Vertices[vertex] = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Vertices[vertex] - Brush->GetPivotOffset()); - } - Poly->Base = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Base - Brush->GetPivotOffset()); - - // Rotate the texture vectors. - Poly->TextureU = RotMatrix.TransformVector( Poly->TextureU ); - Poly->TextureV = RotMatrix.TransformVector( Poly->TextureV ); - - // Recalc the normal for the poly. - Poly->Normal = FVector::ZeroVector; - Poly->Finalize(Brush,0); - } - - Brush->GetBrushComponent()->Brush->BuildBound(); - - if( !Brush->IsStaticBrush() ) - { - csgPrepMovingBrush( Brush, BspPoints, BspVectors ); - } - - if ( bClearComponents ) - { - Brush->ReregisterAllComponents(); - } - } -} - - -void FHBSPOps::HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - // The default physics volume doesn't have an associated UModel, so we need to handle that case gracefully. - if(Volume.Brush) - { - FHBSPOps::csgPrepMovingBrush( &Volume, BspPoints, BspVectors); - } -} - -UHBspPointsGrid* UHBspPointsGrid::Create(float InGranularity, float InThreshold, int32 InitialSize) -{ - check(InThreshold / InGranularity <= 0.5f); - - UHBspPointsGrid* Obj = NewObject(GetTransientPackage(), UHBspPointsGrid::StaticClass()); - Obj->OneOverGranularity = 1.0f / InGranularity; - Obj->Threshold = InThreshold; - Obj->Clear(InitialSize); - - return Obj; -} - -void UHBspPointsGrid::Clear(int32 InitialSize) -{ - GridMap.Empty(InitialSize); -} - - -// Given a grid index in one axis, a real position on the grid and a threshold radius, -// return either: -// - the additional grid index it can overlap in that axis, or -// - the original grid index if there is no overlap. -int32 UHBspPointsGrid::GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold) -{ - if (GridPos - GridIndex < GridThreshold) - { - return GridIndex - 1; - } - else if (1.0f - (GridPos - GridIndex) < GridThreshold) - { - return GridIndex + 1; - } - else - { - return GridIndex; - } -} - -int32 UHBspPointsGrid::FindOrAddPoint(const FVector& Point, int32 Index, float PointThreshold) -{ - // Offset applied to the grid coordinates so aligned vertices (the normal case) don't overlap several grid items (taking into account the threshold) - const float GridOffset = 0.12345f; - - const float AdjustedPointX = Point.X - GridOffset; - const float AdjustedPointY = Point.Y - GridOffset; - const float AdjustedPointZ = Point.Z - GridOffset; - - const float GridX = AdjustedPointX * OneOverGranularity; - const float GridY = AdjustedPointY * OneOverGranularity; - const float GridZ = AdjustedPointZ * OneOverGranularity; - - // Get the grid indices corresponding to the point coordinates - const int32 GridIndexX = FMath::FloorToInt(GridX); - const int32 GridIndexY = FMath::FloorToInt(GridY); - const int32 GridIndexZ = FMath::FloorToInt(GridZ); - - // Find grid item in map - FHBspPointsGridItem& GridItem = GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, GridIndexZ)); - - // Iterate through grid item points and return a point if it's close to the threshold - const float PointThresholdSquared = PointThreshold * PointThreshold; - for (const FHBspIndexedPoint& IndexedPoint : GridItem.IndexedPoints) - { - if (FVector::DistSquared(IndexedPoint.Point, Point) <= PointThresholdSquared) - { - return IndexedPoint.Index; - } - } - - // Otherwise, the point is new: add it to the grid item. - GridItem.IndexedPoints.Emplace(Point, Index); - - // The grid has a maximum threshold of a certain radius. If the point is near the edge of a grid cube, it may overlap into other items. - // Add it to all grid items it can be seen from. - const float GridThreshold = Threshold * OneOverGranularity; - const int32 NeighbourX = GetAdjacentIndexIfOverlapping(GridIndexX, GridX, GridThreshold); - const int32 NeighbourY = GetAdjacentIndexIfOverlapping(GridIndexY, GridY, GridThreshold); - const int32 NeighbourZ = GetAdjacentIndexIfOverlapping(GridIndexZ, GridZ, GridThreshold); - - const bool bOverlapsInX = (NeighbourX != GridIndexX); - const bool bOverlapsInY = (NeighbourY != GridIndexY); - const bool bOverlapsInZ = (NeighbourZ != GridIndexZ); - - if (bOverlapsInX) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInY) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else - { - if (bOverlapsInY) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - - return Index; -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HBSPOps.h" +#include "EngineDefines.h" +#include "Model.h" +#include "Materials/Material.h" +#include "Engine/BrushBuilder.h" +#include "Editor/EditorEngine.h" +#include "Components/BrushComponent.h" +#include "GameFramework/Volume.h" + +DEFINE_LOG_CATEGORY_STATIC(LogBSPOps, Log, All); + +/** Errors encountered in Csg operation. */ +int32 FHBSPOps::GErrors = 0; +bool FHBSPOps::GFastRebuild = false; + +static void TagReferencedNodes( UModel *Model, int32 *NodeRef, int32 *PolyRef, int32 iNode ) +{ + FBspNode &Node = Model->Nodes[iNode]; + + NodeRef[iNode ] = 0; + PolyRef[Node.iSurf] = 0; + + if( Node.iFront != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iFront); + if( Node.iBack != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iBack ); + if( Node.iPlane != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iPlane); +} + +// +// Update a bounding volume by expanding it to enclose a list of polys. +// +static void UpdateBoundWithPolys( FBox& Bound, FPoly** PolyList, int32 nPolys ) +{ + for( int32 i=0; iVertices.Num(); j++ ) + Bound += PolyList[i]->Vertices[j]; +} + +// +// Update a convolution hull with a list of polys. +// +static void UpdateConvolutionWithPolys( UModel *Model, int32 iNode, FPoly **PolyList, int32 nPolys ) +{ + FBox Box(ForceInit); + + FBspNode &Node = Model->Nodes[iNode]; + Node.iCollisionBound = Model->LeafHulls.Num(); + for( int32 i=0; iiBrushPoly != INDEX_NONE ) + { + int32 j; + for( j=0; jiBrushPoly == PolyList[i]->iBrushPoly ) + break; + if( j >= i ) + Model->LeafHulls.Add(PolyList[i]->iBrushPoly); + } + for( int32 j=0; jVertices.Num(); j++ ) + Box += PolyList[i]->Vertices[j]; + } + Model->LeafHulls.Add(INDEX_NONE); + + // Add bounds. + Model->LeafHulls.Add( *(int32*)&Box.Min.X ); + Model->LeafHulls.Add( *(int32*)&Box.Min.Y ); + Model->LeafHulls.Add( *(int32*)&Box.Min.Z ); + Model->LeafHulls.Add( *(int32*)&Box.Max.X ); + Model->LeafHulls.Add( *(int32*)&Box.Max.Y ); + Model->LeafHulls.Add( *(int32*)&Box.Max.Z ); + +} + +// +// Cut a partitioning poly by a list of polys, and add the resulting inside pieces to the +// front list and back list. +// +static void SplitPartitioner +( + UModel* Model, + FPoly** PolyList, + FPoly** FrontList, + FPoly** BackList, + int32 n, + int32 nPolys, + int32& nFront, + int32& nBack, + FPoly InfiniteEdPoly, + TArray& AllocatedFPolys +) +{ + FPoly FrontPoly,BackPoly; + while( n < nPolys ) + { + FPoly* Poly = PolyList[n]; + switch( InfiniteEdPoly.SplitWithPlane(Poly->Vertices[0],Poly->Normal,&FrontPoly,&BackPoly,0) ) + { + case SP_Coplanar: + // May occasionally happen. +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got inficoplanar") ); + break; + + case SP_Front: + // Shouldn't happen if hull is correct. +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got infifront") ); + return; + + case SP_Split: + InfiniteEdPoly = BackPoly; + break; + + case SP_Back: + break; + } + n++; + } + + FPoly* New = new FPoly; + *New = InfiniteEdPoly; + New->Reverse(); + New->iBrushPoly |= 0x40000000; + FrontList[nFront++] = New; + AllocatedFPolys.Add( New ); + + New = new FPoly; + *New = InfiniteEdPoly; + BackList[nBack++] = New; + AllocatedFPolys.Add( New ); +} + +// +// Build an FPoly representing an "infinite" plane (which exceeds the maximum +// dimensions of the world in all directions) for a particular Bsp node. +// +FPoly FHBSPOps::BuildInfiniteFPoly( UModel* Model, int32 iNode ) +{ + FBspNode &Node = Model->Nodes [iNode ]; + FBspSurf &Poly = Model->Surfs [Node.iSurf ]; + FVector Base = Poly.Plane * Poly.Plane.W; + FVector Normal = Poly.Plane; + FVector Axis1,Axis2; + + // Find two non-problematic axis vectors. + Normal.FindBestAxisVectors( Axis1, Axis2 ); + + // Set up the FPoly. + FPoly EdPoly; + EdPoly.Init(); + EdPoly.Normal = Normal; + EdPoly.Base = Base; + new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX + Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX + Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX - Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX - Axis2*WORLD_MAX); + + return EdPoly; +} + +// +// Recursively filter a set of polys defining a convex hull down the Bsp, +// splitting it into two halves at each node and adding in the appropriate +// face polys at splits. +// +static void FilterBound +( + UModel* Model, + FBox* ParentBound, + int32 iNode, + FPoly** PolyList, + int32 nPolys, + int32 Outside +) +{ + FMemMark Mark(FMemStack::Get()); + FBspNode& Node = Model->Nodes [iNode]; + FBspSurf& Surf = Model->Surfs [Node.iSurf]; + FVector Base = Surf.Plane * Surf.Plane.W; + FVector& Normal = Model->Vectors[Surf.vNormal]; + FBox Bound(ForceInit); + + Bound.Min.X = Bound.Min.Y = Bound.Min.Z = +WORLD_MAX; + Bound.Max.X = Bound.Max.Y = Bound.Max.Z = -WORLD_MAX; + + // Split bound into front half and back half. + FPoly** FrontList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nFront=0; + FPoly** BackList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nBack=0; + + // Keeping track of allocated FPoly structures to delete later on. + TArray AllocatedFPolys; + + FPoly* FrontPoly = new FPoly; + FPoly* BackPoly = new FPoly; + + // Keep track of allocations. + AllocatedFPolys.Add( FrontPoly ); + AllocatedFPolys.Add( BackPoly ); + + for( int32 i=0; iSplitWithPlane( Base, Normal, FrontPoly, BackPoly, 0 ) ) + { + case SP_Coplanar: +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got coplanar") ); + FrontList[nFront++] = Poly; + BackList[nBack++] = Poly; + break; + + case SP_Front: + FrontList[nFront++] = Poly; + break; + + case SP_Back: + BackList[nBack++] = Poly; + break; + + case SP_Split: + FrontList[nFront++] = FrontPoly; + BackList [nBack++] = BackPoly; + + FrontPoly = new FPoly; + BackPoly = new FPoly; + + // Keep track of allocations. + AllocatedFPolys.Add( FrontPoly ); + AllocatedFPolys.Add( BackPoly ); + + break; + + default: + UE_LOG(LogBSPOps, Fatal, TEXT("FZoneFilter::FilterToLeaf: Unknown split code") ); + } + } + if( nFront && nBack ) + { + // Add partitioner plane to front and back. + FPoly InfiniteEdPoly = FHBSPOps::BuildInfiniteFPoly( Model, iNode ); + InfiniteEdPoly.iBrushPoly = iNode; + + SplitPartitioner(Model,PolyList,FrontList,BackList,0,nPolys,nFront,nBack,InfiniteEdPoly,AllocatedFPolys); + } + else + { +// if( !nFront ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty fronthull") ); +// if( !nBack ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty backhull") ); + } + + // Recursively update all our childrens' bounding volumes. + if( nFront > 0 ) + { + if( Node.iFront != INDEX_NONE ) + FilterBound( Model, &Bound, Node.iFront, FrontList, nFront, Outside || Node.IsCsg() ); + else if( Outside || Node.IsCsg() ) + UpdateBoundWithPolys( Bound, FrontList, nFront ); + else + UpdateConvolutionWithPolys( Model, iNode, FrontList, nFront ); + } + if( nBack > 0 ) + { + if( Node.iBack != INDEX_NONE) + FilterBound( Model, &Bound,Node.iBack, BackList, nBack, Outside && !Node.IsCsg() ); + else if( Outside && !Node.IsCsg() ) + UpdateBoundWithPolys( Bound, BackList, nBack ); + else + UpdateConvolutionWithPolys( Model, iNode, BackList, nBack ); + } + + // Update parent bound to enclose this bound. + if( ParentBound ) + *ParentBound += Bound; + + // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. + for( int32 i=0; i0); + + // No need to test if only one poly. + if( NumPolys==1 ) + return PolyList[0]; + + FPoly *Poly, *Best=NULL; + float Score, BestScore; + int32 i, Index, j, Inc; + int32 Splits, Front, Back, Coplanar, AllSemiSolids; + + //PortalBias -- added by Legend on 4/12/2000 + float PortalBias = InPortalBias / 100.0f; + Balance &= 0xFF; // keep only the low byte to recover "Balance" + //UE_LOG(LogBSPOps, Log, TEXT("Balance=%d PortalBias=%f"), Balance, PortalBias ); + + if (Opt==FHBSPOps::BSP_Optimal) Inc = 1; // Test lots of nodes. + else if (Opt==FHBSPOps::BSP_Good) Inc = FMath::Max(1,NumPolys/20); // Test 20 nodes. + else /* BSP_Lame */ Inc = FMath::Max(1,NumPolys/4); // Test 4 nodes. + + // See if there are any non-semisolid polygons here. + for( i=0; iPolyFlags & PF_AddLast) ) + break; + AllSemiSolids = (i>=NumPolys); + + // Search through all polygons in the pool and find: + // A. The number of splits each poly would make. + // B. The number of front and back nodes the polygon would create. + // C. Number of coplanars. + BestScore = 0; + for( i=0; iPolyFlags & PF_AddLast) && !(Poly->PolyFlags & PF_Portal) ) + && !AllSemiSolids ); + if( Index>=i+Inc || Index>=NumPolys ) + continue; + + for( j=0; jSplitWithPlaneFast( FPlane( Poly->Vertices[0], Poly->Normal), NULL, NULL ) ) + { + case SP_Coplanar: + Coplanar++; + break; + + case SP_Front: + Front++; + break; + + case SP_Back: + Back++; + break; + + case SP_Split: + // Disfavor splitting polys that are zone portals. + if( !(OtherPoly->PolyFlags & PF_Portal) ) + Splits++; + else + Splits += 16; + break; + } + } + // added by Legend 1/31/1999 + // Score optimization: minimize cuts vs. balance tree (as specified in BSP Rebuilder dialog) + Score = ( 100.0 - float(Balance) ) * Splits + float(Balance) * FMath::Abs( Front - Back ); + if( Poly->PolyFlags & PF_Portal ) + { + // PortalBias -- added by Legend on 4/12/2000 + // + // PortalBias enables level designers to control the effect of Portals on the BSP. + // This effect can range from 0.0 (ignore portals), to 1.0 (portals cut everything). + // + // In builds prior to this (since the 221 build dating back to 1/31/1999) the bias + // has been 1.0 causing the portals to cut the BSP in ways that will potentially + // degrade level performance, and increase the BSP complexity. + // + // By setting the bias to a value between 0.3 and 0.7 the positive effects of + // the portals are preserved without giving them unreasonable priority in the BSP. + // + // Portals should be weighted high enough in the BSP to separate major parts of the + // level from each other (pushing entire rooms down the branches of the BSP), but + // should not be so high that portals cut through adjacent geometry in a way that + // increases complexity of the room being (typically, accidentally) cut. + // + Score -= ( 100.0 - float(Balance) ) * Splits * PortalBias; // ignore PortalBias of the split polys -- bias toward portal selection for cutting planes! + } + //UE_LOG(LogBSPOps, Log, " %4d: Score = %f (Front = %4d, Back = %4d, Splits = %4d, Flags = %08X)", Index, Score, Front, Back, Splits, Poly->PolyFlags ); //LEC + + if( Score AllocatedFPolys; + + // To account for big EdPolys split up. + int32 NumPolysToAlloc = NumPolys + 8 + NumPolys/4; + int32 NumFront=0; FPoly **FrontList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; + int32 NumBack =0; FPoly **BackList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; + + FPoly *SplitPoly = FindBestSplit( NumPolys, PolyList, Opt, Balance, PortalBias ); + + // Add the splitter poly to the Bsp with either a new BspSurf or an existing one. + if( RebuildSimplePolys ) + { + SplitPoly->iLinkSurf = Model->Surfs.Num(); + } + + int32 iOurNode = bspAddNode(Model,iParent,NodePlace,0,SplitPoly, BspPoints, BspVectors); + int32 iPlaneNode = iOurNode; + + // Now divide all polygons in the pool into (A) polygons that are + // in front of Poly, and (B) polygons that are in back of Poly. + // Coplanar polys are inserted immediately, before recursing. + + // If any polygons are split by Poly, we ignrore the original poly, + // split it into two polys, and add two new polys to the pool. + FPoly *FrontEdPoly = new FPoly; + FPoly *BackEdPoly = new FPoly; + // Keep track of allocations. + AllocatedFPolys.Add( FrontEdPoly ); + AllocatedFPolys.Add( BackEdPoly ); + + for( int32 i=0; iSplitWithPlane( SplitPoly->Vertices[0], SplitPoly->Normal, FrontEdPoly, BackEdPoly, 0 ) ) + { + case SP_Coplanar: + if( RebuildSimplePolys ) + { + EdPoly->iLinkSurf = Model->Surfs.Num()-1; + } + iPlaneNode = bspAddNode( Model, iPlaneNode, NODE_Plane, 0, EdPoly, BspPoints, BspVectors ); + break; + + case SP_Front: + FrontList[NumFront++] = PolyList[i]; + break; + + case SP_Back: + BackList[NumBack++] = PolyList[i]; + break; + + case SP_Split: + + // Create front & back nodes. + FrontList[NumFront++] = FrontEdPoly; + BackList [NumBack ++] = BackEdPoly; + + FrontEdPoly = new FPoly; + BackEdPoly = new FPoly; + // Keep track of allocations. + AllocatedFPolys.Add( FrontEdPoly ); + AllocatedFPolys.Add( BackEdPoly ); + + break; + } + } + + // Recursively split the front and back pools. + if( NumFront > 0 ) SplitPolyList( Model, iOurNode, NODE_Front, NumFront, FrontList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); + if( NumBack > 0 ) SplitPolyList( Model, iOurNode, NODE_Back, NumBack, BackList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); + + // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. + for( int32 i=0; iGetName() ); // moved here so that we can easily debug when an actor has lost parts of its brush + + check(Actor->GetBrushComponent()); + check(Actor->Brush); + check(Actor->Brush->RootOutside); + + RebuildBrush(Actor->Brush, BspPoints, BspVectors); + + // Make sure simplified collision is up to date. + Actor->GetBrushComponent()->BuildSimpleBrushCollision(); + Actor->RebuildNavigationData(); +} + +/** + * Duplicates the specified brush and makes it into a CSG-able level brush. + * @return The new brush, or NULL if the original was empty. + */ +void FHBSPOps::csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + check(Src); + check(Src->GetBrushComponent()); + check(Src->Brush); + + // Handle empty brush. + if( !bAllowEmpty && !Src->Brush->Polys->Element.Num() ) + { + Dest->Brush = NULL; + Dest->GetBrushComponent()->Brush = NULL; + return; + } + + // Duplicate the brush and its polys. + Dest->PolyFlags = PolyFlags; + Dest->Brush = NewObject(Dest, NAME_None, ResFlags); + Dest->Brush->Initialize(nullptr, Src->Brush->RootOutside); + Dest->Brush->Polys = NewObject(Dest->Brush, NAME_None, ResFlags); + Dest->Brush->Polys->Element = Src->Brush->Polys->Element; + Dest->GetBrushComponent()->Brush = Dest->Brush; + if(Src->BrushBuilder != nullptr) + { + Dest->BrushBuilder = DuplicateObject(Src->BrushBuilder, Dest); + } + + // Update poly textures. + for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) + { + Dest->Brush->Polys->Element[i].iBrushPoly = INDEX_NONE; + } + + // Copy positioning, and build bounding box. + if(bCopyPosRotScale) + { + Dest->CopyPosRotScaleFrom( Src ); + } + + // If it's a moving brush, prep it. + if( bNeedsPrep ) + { + csgPrepMovingBrush( Dest, BspPoints, BspVectors ); + } +} + +/** + * Adds a brush to the list of CSG brushes in the level, using a CSG operation. + * + * @return A newly-created copy of the brush. + */ +ABrush* FHBSPOps::csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + check(Actor); + check(Actor->GetBrushComponent()); + check(Actor->Brush); + check(Actor->Brush->Polys); + check(Actor->GetWorld()); + + // Can't do this if brush has no polys. + if( !Actor->Brush->Polys->Element.Num() ) + return NULL; + + // Spawn a new actor for the brush. + + ABrush* Result = Actor->GetWorld()->SpawnBrush(); + Result->SetNotForClientOrServer(); + + // Duplicate the brush. + csgCopyBrush + ( + Result, + Actor, + PolyFlags, + RF_Transactional, + 0, + true, + false, + BspPoints, + BspVectors + ); + check(Result->Brush); + + if( Result->GetBrushBuilder() ) + { + FActorLabelUtilities::SetActorLabelUnique(Result, FText::Format(NSLOCTEXT("BSPBrushOps", "BrushName", "{0} Brush"), FText::FromString(Result->GetBrushBuilder()->GetClass()->GetDescription())).ToString()); + } + // Assign the default material to the brush's polys. + for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) + { + FPoly& CurrentPoly = Result->Brush->Polys->Element[i]; + if ( !CurrentPoly.Material ) + { + CurrentPoly.Material = UMaterial::GetDefaultMaterial(MD_Surface); + } + } + + // Set add-info. + Result->BrushType = BrushType; + + Result->ReregisterAllComponents(); + + return Result; +} + +/** Add a new point to the model (preventing duplicates) and return its index. */ +static int32 AddThing( TArray& Vectors, FVector& V, float Thresh, int32 Check ) +{ + if( Check ) + { + // See if this is very close to an existing point/vector. + for( int32 i=0; i -Thresh) && (Temp < Thresh) ) + { + Temp=(V.Y - TableVect.Y); + if( (Temp > -Thresh) && (Temp < Thresh) ) + { + Temp=(V.Z - TableVect.Z); + if( (Temp > -Thresh) && (Temp < Thresh) ) + { + // Found nearly-matching vector. + return i; + } + } + } + } + } + return Vectors.Add( V ); +} + +/** Add a new vector to the model, merging near-duplicates, and return its index. */ +int32 FHBSPOps::bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ) +{ + const float Thresh = Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR; + + if (BspVectors) + { + // If a points grid has been built for quick vector lookup, use that instead of doing a linear search + const int32 NextIndex = Model->Vectors.Num(); + const int32 ReturnedIndex = BspVectors->FindOrAddPoint(*V, NextIndex, Thresh); + if (ReturnedIndex == NextIndex) + { + Model->Vectors.Add(*V); + } + + return ReturnedIndex; + } + + return AddThing + ( + Model->Vectors, + *V, + Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR, + 1 + ); +} + +/** Add a new point to the model, merging near-duplicates, and return its index. */ +int32 FHBSPOps::bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ) +{ + const float Thresh = Exact ? THRESH_POINTS_ARE_SAME : THRESH_POINTS_ARE_NEAR; + + if (BspPoints) + { + // If a points grid has been built for quick point lookup, use that instead of doing a linear search + const int32 NextIndex = Model->Points.Num(); + // Always look for points with a low threshold; a generous threshold can result in 'leaks' in the BSP and unwanted polys being generated + const int32 ReturnedIndex = BspPoints->FindOrAddPoint(*V, NextIndex, THRESH_POINTS_ARE_SAME); + if (ReturnedIndex == NextIndex) + { + Model->Points.Add(*V); + } + + return ReturnedIndex; + } + + // Try to find a match quickly from the Bsp. This finds all potential matches + // except for any dissociated from nodes/surfaces during a rebuild. + FVector Temp; + int32 pVertex; + float NearestDist = Model->FindNearestVertex(*V,Temp,Thresh,pVertex); + if( (NearestDist >= 0.0) && (NearestDist <= Thresh) ) + { + // Found an existing point. + return pVertex; + } + else + { + // No match found; add it slowly to find duplicates. + return AddThing(Model->Points, *V, Thresh, !GFastRebuild); + } +} + + +/** + * Builds Bsp from the editor polygon set (EdPolys) of a model. + * + * Opt = Bsp optimization, BSP_Lame (fast), BSP_Good (medium), BSP_Optimal (slow) + * Balance = 0-100, 0=only worry about minimizing splits, 100=only balance tree. + */ +void FHBSPOps::bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + int32 OriginalPolys = Model->Polys->Element.Num(); + + // Empty the model's tables. + if( RebuildSimplePolys==1 ) + { + // Empty everything but polys. + Model->EmptyModel( 1, 0 ); + } + else if( RebuildSimplePolys==0 ) + { + // Empty node vertices. + for( int32 i=0; iNodes.Num(); i++ ) + Model->Nodes[i].NumVertices = 0; + + // Refresh the Bsp. + bspRefresh(Model,1); + + // Empty nodes. + Model->EmptyModel( 0, 0 ); + } + if( Model->Polys->Element.Num() ) + { + // Allocate polygon pool. + FMemMark Mark(FMemStack::Get()); + FPoly** PolyList = new( FMemStack::Get(), Model->Polys->Element.Num() )FPoly*; + + // Add all FPolys to active list. + for( int32 i=0; iPolys->Element.Num(); i++ ) + if( Model->Polys->Element[i].Vertices.Num() ) + PolyList[i] = &Model->Polys->Element[i]; + + // Now split the entire Bsp by splitting the list of all polygons. + SplitPolyList + ( + Model, + INDEX_NONE, + NODE_Root, + Model->Polys->Element.Num(), + PolyList, + Opt, + Balance, + PortalBias, + RebuildSimplePolys, + BspPoints, + BspVectors + ); + + // Now build the bounding boxes for all nodes. + if( RebuildSimplePolys==0 ) + { + // Remove unreferenced things. + bspRefresh( Model, 1 ); + + // Rebuild all bounding boxes. + bspBuildBounds( Model ); + } + + Mark.Pop(); + } + +// UE_LOG(LogBSPOps, Log, TEXT("bspBuild built %i convex polys into %i nodes"), OriginalPolys, Model->Nodes.Num() ); +} + +/** + * If the Bsp's point and vector tables are nearly full, reorder them and delete unused ones. + */ +void FHBSPOps::bspRefresh( UModel* Model, bool NoRemapSurfs ) +{ + FMemStack& MemStack = FMemStack::Get(); + + FMemMark Mark(MemStack); + + int32 NumNodes = Model->Nodes.Num(); + int32 NumSurfs = Model->Surfs.Num(); + int32 NumVectors = Model->Vectors.Num(); + int32 NumPoints = Model->Points.Num(); + + // Remove unreferenced Bsp surfs. + int32* PolyRef; + if( NoRemapSurfs ) + { + PolyRef = NewZeroed(MemStack, NumSurfs); + } + else + { + PolyRef = NewOned(MemStack, NumSurfs); + } + + int32* NodeRef = NewOned(MemStack, NumNodes); + if( NumNodes > 0 ) + { + TagReferencedNodes( Model, NodeRef, PolyRef, 0 ); + } + + // Remap Bsp surfs. + { + int32 n=0; + for( int32 i=0; iSurfs[n] = Model->Surfs[i]; + PolyRef[i]=n++; + } + } + //UE_LOG(LogBSPOps, Log, TEXT("Polys: %i -> %i"), NumSurfs, n ); + Model->Surfs.RemoveAt( n, NumSurfs-n ); + NumSurfs = n; + } + + // Remap Bsp nodes. + { + int32 n=0; + for( int32 i=0; iNodes[n] = Model->Nodes[i]; + NodeRef[i]=n++; + } + } + //UE_LOG(LogBSPOps, Log, TEXT("Nodes: %i -> %i"), NumNodes, n ); + Model->Nodes.RemoveAt( n, NumNodes-n ); + NumNodes = n; + } + + // Update Bsp nodes. + for( int32 i=0; iNodes[i]; + Node->iSurf = PolyRef[Node->iSurf]; + if (Node->iFront != INDEX_NONE) Node->iFront = NodeRef[Node->iFront]; + if (Node->iBack != INDEX_NONE) Node->iBack = NodeRef[Node->iBack]; + if (Node->iPlane != INDEX_NONE) Node->iPlane = NodeRef[Node->iPlane]; + } + + // Remove unreferenced points and vectors. + int32* VectorRef = NewOned(MemStack, NumVectors); + int32* PointRef = NewOned(MemStack, NumPoints); + + // Check Bsp surfs. + TArray VertexRef; + for( int32 i=0; iSurfs[i]; + VectorRef [Surf->vNormal ] = 0; + VectorRef [Surf->vTextureU ] = 0; + VectorRef [Surf->vTextureV ] = 0; + PointRef [Surf->pBase ] = 0; + } + + // Check Bsp nodes. + for( int32 i=0; iNodes[i]; + FVert* VertPool = &Model->Verts[Node->iVertPool]; + for( int B=0; BNumVertices; B++ ) + { + PointRef[VertPool->pVertex] = 0; + VertPool++; + } + } + + // Remap points. + { + int32 n=0; + for( int32 i=0; iPoints[n] = Model->Points[i]; + PointRef[i] = n++; + } + //UE_LOG(LogBSPOps, Log, TEXT("Points: %i -> %i"), NumPoints, n ); + Model->Points.RemoveAt( n, NumPoints-n ); + NumPoints = n; + } + + // Remap vectors. + { + int32 n=0; + for (int32 i=0; iVectors[n] = Model->Vectors[i]; + VectorRef[i] = n++; + } + //UE_LOG(LogBSPOps, Log, TEXT("Vectors: %i -> %i"), NumVectors, n ); + Model->Vectors.RemoveAt( n, NumVectors-n ); + NumVectors = n; + } + + // Update Bsp surfs. + for( int32 i=0; iSurfs[i]; + Surf->vNormal = VectorRef [Surf->vNormal ]; + Surf->vTextureU = VectorRef [Surf->vTextureU]; + Surf->vTextureV = VectorRef [Surf->vTextureV]; + Surf->pBase = PointRef [Surf->pBase ]; + } + + // Update Bsp nodes. + for( int32 i=0; iNodes[i]; + FVert* VertPool = &Model->Verts[Node->iVertPool]; + for( int B=0; BNumVertices; B++ ) + { + VertPool->pVertex = PointRef [VertPool->pVertex]; + VertPool++; + } + } + + // Shrink the objects. + Model->ShrinkModel(); + + Mark.Pop(); +} + +// Build bounding volumes for all Bsp nodes. The bounding volume of the node +// completely encloses the "outside" space occupied by the nodes. Note that +// this is not the same as representing the bounding volume of all of the +// polygons within the node. +// +// We start with a practically-infinite cube and filter it down the Bsp, +// whittling it away until all of its convex volume fragments land in leaves. +void FHBSPOps::bspBuildBounds( UModel* Model ) +{ + if( Model->Nodes.Num()==0 ) + return; + + FPoly Polys[6], *PolyList[6]; + for( int32 i=0; i<6; i++ ) + { + PolyList[i] = &Polys[i]; + PolyList[i]->Init(); + PolyList[i]->iBrushPoly = INDEX_NONE; + } + + new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); + Polys[0].Normal =FVector( 0.000000, 0.000000, 1.000000 ); + Polys[0].Base =Polys[0].Vertices[0]; + + new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[1].Normal =FVector( 0.000000, 0.000000, -1.000000 ); + Polys[1].Base =Polys[1].Vertices[0]; + + new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[2].Normal =FVector( 0.000000, 1.000000, 0.000000 ); + Polys[2].Base =Polys[2].Vertices[0]; + + new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[3].Normal =FVector( 0.000000, -1.000000, 0.000000 ); + Polys[3].Base =Polys[3].Vertices[0]; + + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[4].Normal =FVector( 1.000000, 0.000000, 0.000000 ); + Polys[4].Base =Polys[4].Vertices[0]; + + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[5].Normal =FVector(-1.000000, 0.000000, 0.000000 ); + Polys[5].Base =Polys[5].Vertices[0]; + // Empty hulls. + Model->LeafHulls.Empty(); + for( int32 i=0; iNodes.Num(); i++ ) + Model->Nodes[i].iCollisionBound = INDEX_NONE; + FilterBound( Model, NULL, 0, PolyList, 6, Model->RootOutside ); +// UE_LOG(LogBSPOps, Log, TEXT("bspBuildBounds: Generated %i hulls"), Model->LeafHulls.Num() ); +} + +/** + * Validate a brush, and set iLinks on all EdPolys to index of the + * first identical EdPoly in the list, or its index if it's the first. + * Not transactional. + */ +void FHBSPOps::bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ) +{ + check(Brush != nullptr); + Brush->Modify(); + if( ForceValidate || !Brush->Linked ) + { + Brush->Linked = 1; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + Brush->Polys->Element[i].iLink = i; + } + int32 n=0; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + FPoly* EdPoly = &Brush->Polys->Element[i]; + if( EdPoly->iLink==i ) + { + for( int32 j=i+1; jPolys->Element.Num(); j++ ) + { + FPoly* OtherPoly = &Brush->Polys->Element[j]; + if + ( OtherPoly->iLink == j + && OtherPoly->Material == EdPoly->Material + && OtherPoly->TextureU == EdPoly->TextureU + && OtherPoly->TextureV == EdPoly->TextureV + && OtherPoly->PolyFlags == EdPoly->PolyFlags + && (OtherPoly->Normal | EdPoly->Normal)>0.9999 ) + { + float Dist = FVector::PointPlaneDist( OtherPoly->Vertices[0], EdPoly->Vertices[0], EdPoly->Normal ); + if( Dist>-0.001 && Dist<0.001 ) + { + OtherPoly->iLink = i; + n++; + } + } + } + } + } +// UE_LOG(LogBSPOps, Log, TEXT("BspValidateBrush linked %i of %i polys"), n, Brush->Polys->Element.Num() ); + } + + // Build bounds. + Brush->BuildBound(); +} + +void FHBSPOps::bspUnlinkPolys( UModel* Brush ) +{ + Brush->Modify(); + Brush->Linked = 1; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + Brush->Polys->Element[i].iLink = i; + } +} + +// Add an editor polygon to the Bsp, and also stick a reference to it +// in the editor polygon's BspNodes list. If the editor polygon has more sides +// than the Bsp will allow, split it up into several sub-polygons. +// +// Returns: Index to newly-created node of Bsp. If several nodes were created because +// of split polys, returns the parent (highest one up in the Bsp). +int32 FHBSPOps::bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + if( NodePlace == NODE_Plane ) + { + // Make sure coplanars are added at the end of the coplanar list so that + // we don't insert NF_IsNew nodes with non NF_IsNew coplanar children. + while( Model->Nodes[iParent].iPlane != INDEX_NONE ) + { + iParent = Model->Nodes[iParent].iPlane; + } + } + FBspSurf* Surf = NULL; + if( EdPoly->iLinkSurf == Model->Surfs.Num() ) + { + int32 NewIndex = Model->Surfs.AddZeroed(); + Surf = &Model->Surfs[NewIndex]; + + // This node has a new polygon being added by bspBrushCSG; must set its properties here. + Surf->pBase = bspAddPoint (Model,&EdPoly->Base,1,BspPoints); + Surf->vNormal = bspAddVector (Model,&EdPoly->Normal,1,BspVectors); + Surf->vTextureU = bspAddVector (Model,&EdPoly->TextureU,0,BspVectors); + Surf->vTextureV = bspAddVector (Model,&EdPoly->TextureV,0,BspVectors); + Surf->Material = EdPoly->Material; + Surf->Actor = NULL; + + Surf->PolyFlags = EdPoly->PolyFlags & ~PF_NoAddToBSP; + Surf->LightMapScale= EdPoly->LightMapScale; + + // Find the LightmassPrimitiveSettings in the UModel... + int32 FoundLightmassIndex = INDEX_NONE; + if (Model->LightmassSettings.Find(EdPoly->LightmassSettings, FoundLightmassIndex) == false) + { + FoundLightmassIndex = Model->LightmassSettings.Add(EdPoly->LightmassSettings); + } + Surf->iLightmassIndex = FoundLightmassIndex; + + Surf->Actor = EdPoly->Actor; + Surf->iBrushPoly = EdPoly->iBrushPoly; + + if (EdPoly->Actor) + { + Surf->bHiddenEdTemporary = EdPoly->Actor->IsTemporarilyHiddenInEditor(); + Surf->bHiddenEdLevel = EdPoly->Actor->bHiddenEdLevel; + Surf->bHiddenEdLayer = EdPoly->Actor->bHiddenEdLayer; + } + + Surf->Plane = FPlane(EdPoly->Vertices[0],EdPoly->Normal); + } + else + { + check(EdPoly->iLinkSurf!=INDEX_NONE); + check(EdPoly->iLinkSurfSurfs.Num()); + Surf = &Model->Surfs[EdPoly->iLinkSurf]; + } + + // Set NodeFlags. + if( Surf->PolyFlags & PF_NotSolid ) NodeFlags |= NF_NotCsg; + if( Surf->PolyFlags & (PF_Invisible|PF_Portal) ) NodeFlags |= NF_NotVisBlocking; + + if( EdPoly->Vertices.Num() > FBspNode::MAX_NODE_VERTICES ) + { + // Split up into two coplanar sub-polygons (one with MAX_NODE_VERTICES vertices and + // one with all the remaining vertices) and recursively add them. + + // EdPoly1 is just the first MAX_NODE_VERTICES from EdPoly. + FMemMark Mark(FMemStack::Get()); + FPoly *EdPoly1 = new FPoly; + *EdPoly1 = *EdPoly; + EdPoly1->Vertices.RemoveAt(FBspNode::MAX_NODE_VERTICES,EdPoly->Vertices.Num() - FBspNode::MAX_NODE_VERTICES); + + // EdPoly2 is the first vertex from EdPoly, and the last EdPoly->Vertices.Num() - MAX_NODE_VERTICES + 1. + FPoly *EdPoly2 = new FPoly; + *EdPoly2 = *EdPoly; + EdPoly2->Vertices.RemoveAt(1,FBspNode::MAX_NODE_VERTICES - 2); + + int32 iNode = bspAddNode( Model, iParent, NodePlace, NodeFlags, EdPoly1, BspPoints, BspVectors ); // Add this poly first. + bspAddNode( Model, iNode, NODE_Plane, NodeFlags, EdPoly2, BspPoints, BspVectors ); // Then add other (may be bigger). + + delete EdPoly1; + delete EdPoly2; + + Mark.Pop(); + return iNode; // Return coplanar "parent" node (not coplanar child) + } + else + { + // Add node. + int32 iNode = Model->Nodes.AddZeroed(); + FBspNode& Node = Model->Nodes[iNode]; + + // Tell transaction tracking system that parent is about to be modified. + FBspNode* Parent=NULL; + if( NodePlace!=NODE_Root ) + Parent = &Model->Nodes[iParent]; + + // Set node properties. + Node.iSurf = EdPoly->iLinkSurf; + Node.NodeFlags = NodeFlags; + Node.iCollisionBound = INDEX_NONE; + Node.Plane = FPlane( EdPoly->Vertices[0], EdPoly->Normal ); + Node.iVertPool = Model->Verts.AddUninitialized(EdPoly->Vertices.Num()); + Node.iFront = INDEX_NONE; + Node.iBack = INDEX_NONE; + Node.iPlane = INDEX_NONE; + if( NodePlace==NODE_Root ) + { + Node.iLeaf[0] = INDEX_NONE; + Node.iLeaf[1] = INDEX_NONE; + Node.iZone[0] = 0; + Node.iZone[1] = 0; + } + else if( NodePlace==NODE_Front || NodePlace==NODE_Back ) + { + int32 ZoneFront=NodePlace==NODE_Front; + Node.iLeaf[0] = Parent->iLeaf[ZoneFront]; + Node.iLeaf[1] = Parent->iLeaf[ZoneFront]; + Node.iZone[0] = Parent->iZone[ZoneFront]; + Node.iZone[1] = Parent->iZone[ZoneFront]; + } + else + { + int32 IsFlipped = (Node.Plane|Parent->Plane)<0.0; + Node.iLeaf[0] = Parent->iLeaf[IsFlipped ]; + Node.iLeaf[1] = Parent->iLeaf[1-IsFlipped]; + Node.iZone[0] = Parent->iZone[IsFlipped ]; + Node.iZone[1] = Parent->iZone[1-IsFlipped]; + } + + // Link parent to this node. + if (NodePlace == NODE_Front) + { + Parent->iFront = iNode; + } + else if (NodePlace == NODE_Back) + { + Parent->iBack = iNode; + } + else if (NodePlace == NODE_Plane) + { + Parent->iPlane = iNode; + } + + // Add all points to point table, merging nearly-overlapping polygon points + // with other points in the poly to prevent criscrossing vertices from + // being generated. + + // Must maintain Node->NumVertices on the fly so that bspAddPoint is always + // called with the Bsp in a clean state. + Node.NumVertices = 0; + FVert* VertPool = &Model->Verts[ Node.iVertPool ]; + for( uint8 i=0; iVertices.Num(); i++ ) + { + int32 pVertex = bspAddPoint(Model,&EdPoly->Vertices[i],0, BspPoints); + if( Node.NumVertices==0 || VertPool[Node.NumVertices-1].pVertex!=pVertex ) + { + VertPool[Node.NumVertices].iSide = INDEX_NONE; + VertPool[Node.NumVertices].pVertex = pVertex; + Node.NumVertices++; + } + } + if( Node.NumVertices>=2 && VertPool[0].pVertex==VertPool[Node.NumVertices-1].pVertex ) + { + Node.NumVertices--; + } + if( Node.NumVertices < 3 ) + { + GErrors++; +// UE_LOG(LogBSPOps, Warning, TEXT("bspAddNode: Infinitesimal polygon %i (%i)"), Node.NumVertices, EdPoly->Vertices.Num() ); + Node.NumVertices = 0; + } + + return iNode; + } +} + +/** + * Rebuild some brush internals + */ +void FHBSPOps::RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + Brush->Modify(); + Brush->EmptyModel(1, 0); + + // Build bounding box. + Brush->BuildBound(); + + // Build BSP for the brush. + bspBuild(Brush, BSP_Good, 15, 70, 1, 0, BspPoints, BspVectors); + bspRefresh(Brush, 1); + bspBuildBounds(Brush); +} + +/** + * Rotates the specified brush's vertices. + */ +void FHBSPOps::RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + if(Brush->GetBrushComponent()->Brush && Brush->GetBrushComponent()->Brush->Polys) + { + for( int32 poly = 0 ; poly < Brush->GetBrushComponent()->Brush->Polys->Element.Num() ; poly++ ) + { + FPoly* Poly = &(Brush->GetBrushComponent()->Brush->Polys->Element[poly]); + + // Rotate the vertices. + const FRotationMatrix RotMatrix( Rotation ); + for( int32 vertex = 0 ; vertex < Poly->Vertices.Num() ; vertex++ ) + { + Poly->Vertices[vertex] = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Vertices[vertex] - Brush->GetPivotOffset()); + } + Poly->Base = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Base - Brush->GetPivotOffset()); + + // Rotate the texture vectors. + Poly->TextureU = RotMatrix.TransformVector( Poly->TextureU ); + Poly->TextureV = RotMatrix.TransformVector( Poly->TextureV ); + + // Recalc the normal for the poly. + Poly->Normal = FVector::ZeroVector; + Poly->Finalize(Brush,0); + } + + Brush->GetBrushComponent()->Brush->BuildBound(); + + if( !Brush->IsStaticBrush() ) + { + csgPrepMovingBrush( Brush, BspPoints, BspVectors ); + } + + if ( bClearComponents ) + { + Brush->ReregisterAllComponents(); + } + } +} + + +void FHBSPOps::HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + // The default physics volume doesn't have an associated UModel, so we need to handle that case gracefully. + if(Volume.Brush) + { + FHBSPOps::csgPrepMovingBrush( &Volume, BspPoints, BspVectors); + } +} + +UHBspPointsGrid* UHBspPointsGrid::Create(float InGranularity, float InThreshold, int32 InitialSize) +{ + check(InThreshold / InGranularity <= 0.5f); + + UHBspPointsGrid* Obj = NewObject(GetTransientPackage(), UHBspPointsGrid::StaticClass()); + Obj->OneOverGranularity = 1.0f / InGranularity; + Obj->Threshold = InThreshold; + Obj->Clear(InitialSize); + + return Obj; +} + +void UHBspPointsGrid::Clear(int32 InitialSize) +{ + GridMap.Empty(InitialSize); +} + + +// Given a grid index in one axis, a real position on the grid and a threshold radius, +// return either: +// - the additional grid index it can overlap in that axis, or +// - the original grid index if there is no overlap. +int32 UHBspPointsGrid::GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold) +{ + if (GridPos - GridIndex < GridThreshold) + { + return GridIndex - 1; + } + else if (1.0f - (GridPos - GridIndex) < GridThreshold) + { + return GridIndex + 1; + } + else + { + return GridIndex; + } +} + +int32 UHBspPointsGrid::FindOrAddPoint(const FVector& Point, int32 Index, float PointThreshold) +{ + // Offset applied to the grid coordinates so aligned vertices (the normal case) don't overlap several grid items (taking into account the threshold) + const float GridOffset = 0.12345f; + + const float AdjustedPointX = Point.X - GridOffset; + const float AdjustedPointY = Point.Y - GridOffset; + const float AdjustedPointZ = Point.Z - GridOffset; + + const float GridX = AdjustedPointX * OneOverGranularity; + const float GridY = AdjustedPointY * OneOverGranularity; + const float GridZ = AdjustedPointZ * OneOverGranularity; + + // Get the grid indices corresponding to the point coordinates + const int32 GridIndexX = FMath::FloorToInt(GridX); + const int32 GridIndexY = FMath::FloorToInt(GridY); + const int32 GridIndexZ = FMath::FloorToInt(GridZ); + + // Find grid item in map + FHBspPointsGridItem& GridItem = GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, GridIndexZ)); + + // Iterate through grid item points and return a point if it's close to the threshold + const float PointThresholdSquared = PointThreshold * PointThreshold; + for (const FHBspIndexedPoint& IndexedPoint : GridItem.IndexedPoints) + { + if (FVector::DistSquared(IndexedPoint.Point, Point) <= PointThresholdSquared) + { + return IndexedPoint.Index; + } + } + + // Otherwise, the point is new: add it to the grid item. + GridItem.IndexedPoints.Emplace(Point, Index); + + // The grid has a maximum threshold of a certain radius. If the point is near the edge of a grid cube, it may overlap into other items. + // Add it to all grid items it can be seen from. + const float GridThreshold = Threshold * OneOverGranularity; + const int32 NeighbourX = GetAdjacentIndexIfOverlapping(GridIndexX, GridX, GridThreshold); + const int32 NeighbourY = GetAdjacentIndexIfOverlapping(GridIndexY, GridY, GridThreshold); + const int32 NeighbourZ = GetAdjacentIndexIfOverlapping(GridIndexZ, GridZ, GridThreshold); + + const bool bOverlapsInX = (NeighbourX != GridIndexX); + const bool bOverlapsInY = (NeighbourY != GridIndexY); + const bool bOverlapsInZ = (NeighbourZ != GridIndexZ); + + if (bOverlapsInX) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInY) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else + { + if (bOverlapsInY) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + + return Index; +} diff --git a/Source/HoudiniEngine/Private/HBSPOps.h b/Source/HoudiniEngine/Private/HBSPOps.h index 0e843ecd3..8e359b070 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.h +++ b/Source/HoudiniEngine/Private/HBSPOps.h @@ -1,157 +1,181 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Engine/Brush.h" -#include "Engine/Polys.h" - -#include "HBSPOps.generated.h" - -class AVolume; -class UModel; - -// This codebase have been localised from UnrealEd/HBSPOps to remove static/global variables. -class FHBSPOps -{ -public: - FHBSPOps(); - - /** Quality level for rebuilding Bsp. */ - enum EBspOptimization - { - BSP_Lame, - BSP_Good, - BSP_Optimal - }; - - /** Possible positions of a child Bsp node relative to its parent (for BspAddToNode) */ - enum ENodePlace - { - NODE_Back = 0, // Node is in back of parent -> Bsp[iParent].iBack. - NODE_Front = 1, // Node is in front of parent -> Bsp[iParent].iFront. - NODE_Plane = 2, // Node is coplanar with parent -> Bsp[iParent].iPlane. - NODE_Root = 3, // Node is the Bsp root and has no parent -> Bsp[0]. - }; - - static void csgPrepMovingBrush( ABrush* Actor, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static void csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static ABrush* csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - static int32 bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ); - static int32 bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ); - static void bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static void bspRefresh( UModel* Model, bool NoRemapSurfs ); - - static void bspBuildBounds( UModel* Model ); - - static void bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ); - static void bspUnlinkPolys( UModel* Brush ); - static int32 bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - /** - * Rebuild some brush internals - */ - static void RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - static FPoly BuildInfiniteFPoly( UModel* Model, int32 iNode ); - - /** - * Rotates the specified brush's vertices. - */ - static void RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - /** Called when an AVolume shape is changed*/ - static void HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - /** Errors encountered in Csg operation. */ - static int32 GErrors; - static bool GFastRebuild; - -protected: - static void SplitPolyList - ( - UModel *Model, - int32 iParent, - FHBSPOps::ENodePlace NodePlace, - int32 NumPolys, - FPoly **PolyList, - EBspOptimization Opt, - int32 Balance, - int32 PortalBias, - int32 RebuildSimplePolys, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); -}; - - -struct FHBspPointsKey -{ - int32 X; - int32 Y; - int32 Z; - - FHBspPointsKey(int32 InX, int32 InY, int32 InZ) - : X(InX) - , Y(InY) - , Z(InZ) - {} - - friend FORCEINLINE bool operator == (const FHBspPointsKey& A, const FHBspPointsKey& B) - { - return A.X == B.X && A.Y == B.Y && A.Z == B.Z; - } - - friend FORCEINLINE uint32 GetTypeHash(const FHBspPointsKey& Key) - { - return HashCombine(static_cast(Key.X), HashCombine(static_cast(Key.Y), static_cast(Key.Z))); - } -}; - -struct FHBspIndexedPoint -{ - FHBspIndexedPoint(const FVector& InPoint, int32 InIndex) - : Point(InPoint) - , Index(InIndex) - {} - - FVector Point; - int32 Index; -}; - - -struct FHBspPointsGridItem -{ - TArray> IndexedPoints; -}; - - -// Represents a sparse granular 3D grid into which points are added for quick (~O(1)) lookup. -// The 3D space is divided into a grid with a given granularity. -// Points are considered to have a given radius (threshold) and are added to the grid cube they fall in, and to up to seven neighbours if they overlap. -UCLASS() -class HOUDINIENGINE_API UHBspPointsGrid : public UObject -{ - GENERATED_BODY() -protected: - - UHBspPointsGrid() {} - -public: - // Create a new instance of this grid with the given arguments. - static UHBspPointsGrid* Create(float InGranularity, float InThreshold, int32 InitialSize = 0); - - void Clear(int32 InitialSize = 0); - - int32 FindOrAddPoint(const FVector& Point, int32 Index, float Threshold); - - static FORCEINLINE int32 GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold); - -private: - float OneOverGranularity; - float Threshold; - - typedef TMap FGridMap; - FGridMap GridMap; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Brush.h" +#include "Engine/Polys.h" + +#include "HBSPOps.generated.h" + +class AVolume; +class UModel; + +// This codebase have been localised from UnrealEd/HBSPOps to remove static/global variables. +class FHBSPOps +{ +public: + FHBSPOps(); + + /** Quality level for rebuilding Bsp. */ + enum EBspOptimization + { + BSP_Lame, + BSP_Good, + BSP_Optimal + }; + + /** Possible positions of a child Bsp node relative to its parent (for BspAddToNode) */ + enum ENodePlace + { + NODE_Back = 0, // Node is in back of parent -> Bsp[iParent].iBack. + NODE_Front = 1, // Node is in front of parent -> Bsp[iParent].iFront. + NODE_Plane = 2, // Node is coplanar with parent -> Bsp[iParent].iPlane. + NODE_Root = 3, // Node is the Bsp root and has no parent -> Bsp[0]. + }; + + static void csgPrepMovingBrush( ABrush* Actor, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static void csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static ABrush* csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + static int32 bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ); + static int32 bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ); + static void bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static void bspRefresh( UModel* Model, bool NoRemapSurfs ); + + static void bspBuildBounds( UModel* Model ); + + static void bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ); + static void bspUnlinkPolys( UModel* Brush ); + static int32 bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + /** + * Rebuild some brush internals + */ + static void RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + static FPoly BuildInfiniteFPoly( UModel* Model, int32 iNode ); + + /** + * Rotates the specified brush's vertices. + */ + static void RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + /** Called when an AVolume shape is changed*/ + static void HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + /** Errors encountered in Csg operation. */ + static int32 GErrors; + static bool GFastRebuild; + +protected: + static void SplitPolyList + ( + UModel *Model, + int32 iParent, + FHBSPOps::ENodePlace NodePlace, + int32 NumPolys, + FPoly **PolyList, + EBspOptimization Opt, + int32 Balance, + int32 PortalBias, + int32 RebuildSimplePolys, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); +}; + + +struct FHBspPointsKey +{ + int32 X; + int32 Y; + int32 Z; + + FHBspPointsKey(int32 InX, int32 InY, int32 InZ) + : X(InX) + , Y(InY) + , Z(InZ) + {} + + friend FORCEINLINE bool operator == (const FHBspPointsKey& A, const FHBspPointsKey& B) + { + return A.X == B.X && A.Y == B.Y && A.Z == B.Z; + } + + friend FORCEINLINE uint32 GetTypeHash(const FHBspPointsKey& Key) + { + return HashCombine(static_cast(Key.X), HashCombine(static_cast(Key.Y), static_cast(Key.Z))); + } +}; + +struct FHBspIndexedPoint +{ + FHBspIndexedPoint(const FVector& InPoint, int32 InIndex) + : Point(InPoint) + , Index(InIndex) + {} + + FVector Point; + int32 Index; +}; + + +struct FHBspPointsGridItem +{ + TArray> IndexedPoints; +}; + + +// Represents a sparse granular 3D grid into which points are added for quick (~O(1)) lookup. +// The 3D space is divided into a grid with a given granularity. +// Points are considered to have a given radius (threshold) and are added to the grid cube they fall in, and to up to seven neighbours if they overlap. +UCLASS() +class HOUDINIENGINE_API UHBspPointsGrid : public UObject +{ + GENERATED_BODY() +protected: + + UHBspPointsGrid() {} + +public: + // Create a new instance of this grid with the given arguments. + static UHBspPointsGrid* Create(float InGranularity, float InThreshold, int32 InitialSize = 0); + + void Clear(int32 InitialSize = 0); + + int32 FindOrAddPoint(const FVector& Point, int32 Index, float Threshold); + + static FORCEINLINE int32 GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold); + +private: + float OneOverGranularity; + float Threshold; + + typedef TMap FGridMap; + FGridMap GridMap; +}; diff --git a/Source/HoudiniEngine/Private/HCsgUtils.cpp b/Source/HoudiniEngine/Private/HCsgUtils.cpp index 35ba4e3ec..9fd98873b 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.cpp +++ b/Source/HoudiniEngine/Private/HCsgUtils.cpp @@ -1,1436 +1,1461 @@ - -#include "HCsgUtils.h" - -#include "Engine/Engine.h" -#include "Engine/Polys.h" -#include "Engine/Selection.h" -#include "Materials/Material.h" -#include "Misc/FeedbackContext.h" - -#include "ActorEditorUtils.h" -#include "Misc/ScopedSlowTask.h" - - -DEFINE_LOG_CATEGORY_STATIC(LogHCsgUtils, Log, All); - -#if WITH_EDITOR -#include "Editor.h" -#endif - -// Magic numbers. -#define THRESH_OPTGEOM_COPLANAR (0.25) /* Threshold for Bsp geometry optimization */ -#define THRESH_OPTGEOM_COSIDAL (0.25) /* Threshold for Bsp geometry optimization */ - - -UHCsgUtils::UHCsgUtils() -{ - // A TempModel is allocated for the HCsgUtils instance to avoid reallocation during inner loops. - TempModel = NewObject(); - TempModel->Initialize(nullptr, 1); - - /*GBspPoints = NewObject(); - GBspVectors = NewObject();*/ -} - - -/*---------------------------------------------------------------------------- - CSG leaf filter callbacks. -----------------------------------------------------------------------------*/ - -void UHCsgUtils::AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); - break; - case F_COSPATIAL_FACING_OUT: - if( !(EdPoly->PolyFlags & PF_Semisolid) ) - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_IN: - break; - } -} - -void UHCsgUtils::AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - // Only affect the world poly if it has been cut. - if( EdPoly->PolyFlags & PF_EdCut ) - FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors ); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_IN: - case F_COSPATIAL_FACING_OUT: - // Discard original poly. - GDiscarded++; - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - break; - } -} - -void UHCsgUtils::SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch (Filter) - { - case F_OUTSIDE: - case F_COSPATIAL_FACING_OUT: - case F_COSPATIAL_FACING_IN: - case F_COPLANAR_OUTSIDE: - break; - case F_COPLANAR_INSIDE: - case F_INSIDE: - EdPoly->Reverse(); - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); // Add to Bsp back - EdPoly->Reverse(); - break; - } -} - -void UHCsgUtils::SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - // Only affect the world poly if it has been cut. - if( EdPoly->PolyFlags & PF_EdCut ) - FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - // Discard original poly. - GDiscarded++; - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - break; - } -} - -void UHCsgUtils::IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - case F_COSPATIAL_FACING_OUT: - // Ignore. - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - if( EdPoly->Fix()>=3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - // Ignore. - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - if( EdPoly->Fix() >= 3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - case F_COSPATIAL_FACING_IN: - // Ignore. - break; - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - if( EdPoly->Fix()>=3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_OUT: - // Ignore. - break; - case F_COPLANAR_INSIDE: - case F_INSIDE: - case F_COSPATIAL_FACING_IN: - if( EdPoly->Fix() >= 3 ) - { - EdPoly->Reverse(); - new(GModel->Polys->Element)FPoly(*EdPoly); - EdPoly->Reverse(); - } - break; - } -} - -/*---------------------------------------------------------------------------- - CSG polygon filtering routine (calls the callbacks). -----------------------------------------------------------------------------*/ - -// -// Handle a piece of a polygon that was filtered to a leaf. -// -void UHCsgUtils::FilterLeaf -( - BspFilterFunc FilterFunc, - UModel* Model, - int32 iNode, - FPoly* EdPoly, - FCoplanarInfo CoplanarInfo, - int32 LeafOutside, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - EPolyNodeFilter FilterType; - - if( CoplanarInfo.iOriginalNode == INDEX_NONE ) - { - // Processing regular, non-coplanar polygons. - FilterType = LeafOutside ? F_OUTSIDE : F_INSIDE; - (this->*FilterFunc)( Model, iNode, EdPoly, FilterType, ENodePlace, BspPoints, BspVectors ); - } - else if( CoplanarInfo.ProcessingBack ) - { - // Finished filtering polygon through tree in back of parent coplanar. - DoneFilteringBack: - if ((!LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_INSIDE; - else if (( LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_OUTSIDE; - else if ((!LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_OUT; - else if (( LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_IN; - else - { - UE_LOG(LogHCsgUtils, Fatal, TEXT("FilterLeaf: Bad Locs")); - return; - } - (this->*FilterFunc)( Model, CoplanarInfo.iOriginalNode, EdPoly, FilterType, FHBSPOps::NODE_Plane, BspPoints, BspVectors ); - } - else - { - CoplanarInfo.FrontLeafOutside = LeafOutside; - - if( CoplanarInfo.iBackNode == INDEX_NONE ) - { - // Back tree is empty. - LeafOutside = CoplanarInfo.BackNodeOutside; - goto DoneFilteringBack; - } - else - { - // Call FilterEdPoly to filter through the back. This will result in - // another call to FilterLeaf with iNode = leaf this falls into in the - // back tree and EdPoly = the final EdPoly to insert. - CoplanarInfo.ProcessingBack=1; - FilterEdPoly( FilterFunc, Model, CoplanarInfo.iBackNode, EdPoly,CoplanarInfo, CoplanarInfo.BackNodeOutside, BspPoints, BspVectors ); - } - } -} - -// -// Filter an EdPoly through the Bsp recursively, calling FilterFunc -// for all chunks that fall into leaves. FCoplanarInfo is used to -// handle the tricky case of double-recursion for polys that must be -// filtered through a node's front, then filtered through the node's back, -// in order to handle coplanar CSG properly. -// -void UHCsgUtils::FilterEdPoly -( - BspFilterFunc FilterFunc, - UModel *Model, - int32 iNode, - FPoly *EdPoly, - FCoplanarInfo CoplanarInfo, - int32 Outside, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - int32 SplitResult,iOurFront,iOurBack; - int32 NewFrontOutside,NewBackOutside; - - FilterLoop: - - // Split em. - FPoly TempFrontEdPoly,TempBackEdPoly; - SplitResult = EdPoly->SplitWithPlane - ( - Model->Points [Model->Verts[Model->Nodes[iNode].iVertPool].pVertex], - Model->Vectors[Model->Surfs[Model->Nodes[iNode].iSurf].vNormal], - &TempFrontEdPoly, - &TempBackEdPoly, - 0 - ); - - // Process split results. - if( SplitResult == SP_Front ) - { - Front: - - FBspNode *Node = &Model->Nodes[iNode]; - Outside = Outside || Node->IsCsg(); - - if( Node->iFront == INDEX_NONE ) - { - FilterLeaf(FilterFunc,Model,iNode,EdPoly,CoplanarInfo,Outside,FHBSPOps::NODE_Front, BspPoints, BspVectors); - } - else - { - iNode = Node->iFront; - goto FilterLoop; - } - } - else if( SplitResult == SP_Back ) - { - FBspNode *Node = &Model->Nodes[iNode]; - Outside = Outside && !Node->IsCsg(); - - if( Node->iBack == INDEX_NONE ) - { - FilterLeaf( FilterFunc, Model, iNode, EdPoly, CoplanarInfo, Outside, FHBSPOps::NODE_Back, BspPoints, BspVectors); - } - else - { - iNode=Node->iBack; - goto FilterLoop; - } - } - else if( SplitResult == SP_Coplanar ) - { - if( CoplanarInfo.iOriginalNode != INDEX_NONE ) - { - // This will happen once in a blue moon when a polygon is barely outside the - // coplanar threshold and is split up into a new polygon that is - // is barely inside the coplanar threshold. To handle this, just classify - // it as front and it will be handled propery. - FHBSPOps::GErrors++; -// UE_LOG(LogEditorBsp, Warning, TEXT("FilterEdPoly: Encountered out-of-place coplanar") ); - goto Front; - } - CoplanarInfo.iOriginalNode = iNode; - CoplanarInfo.iBackNode = INDEX_NONE; - CoplanarInfo.ProcessingBack = 0; - CoplanarInfo.BackNodeOutside = Outside; - NewFrontOutside = Outside; - - // See whether Node's iFront or iBack points to the side of the tree on the front - // of this polygon (will be as expected if this polygon is facing the same - // way as first coplanar in link, otherwise opposite). - if( (FVector(Model->Nodes[iNode].Plane) | EdPoly->Normal) >= 0.0 ) - { - iOurFront = Model->Nodes[iNode].iFront; - iOurBack = Model->Nodes[iNode].iBack; - - if( Model->Nodes[iNode].IsCsg() ) - { - CoplanarInfo.BackNodeOutside = 0; - NewFrontOutside = 1; - } - } - else - { - iOurFront = Model->Nodes[iNode].iBack; - iOurBack = Model->Nodes[iNode].iFront; - - if( Model->Nodes[iNode].IsCsg() ) - { - CoplanarInfo.BackNodeOutside = 1; - NewFrontOutside = 0; - } - } - - // Process front and back. - if ((iOurFront==INDEX_NONE)&&(iOurBack==INDEX_NONE)) - { - // No front or back. - CoplanarInfo.ProcessingBack = 1; - CoplanarInfo.FrontLeafOutside = NewFrontOutside; - FilterLeaf - ( - FilterFunc, - Model, - iNode, - EdPoly, - CoplanarInfo, - CoplanarInfo.BackNodeOutside, - FHBSPOps::NODE_Plane, - BspPoints, - BspVectors - ); - } - else if( iOurFront==INDEX_NONE && iOurBack!=INDEX_NONE ) - { - // Back but no front. - CoplanarInfo.ProcessingBack = 1; - CoplanarInfo.iBackNode = iOurBack; - CoplanarInfo.FrontLeafOutside = NewFrontOutside; - - iNode = iOurBack; - Outside = CoplanarInfo.BackNodeOutside; - goto FilterLoop; - } - else - { - // Has a front and maybe a back. - - // Set iOurBack up to process back on next call to FilterLeaf, and loop - // to process front. Next call to FilterLeaf will set FrontLeafOutside. - CoplanarInfo.ProcessingBack = 0; - - // May be a node or may be INDEX_NONE. - CoplanarInfo.iBackNode = iOurBack; - - iNode = iOurFront; - Outside = NewFrontOutside; - goto FilterLoop; - } - } - else if( SplitResult == SP_Split ) - { - // Front half of split. - if( Model->Nodes[iNode].IsCsg() ) - { - NewFrontOutside = 1; - NewBackOutside = 0; - } - else - { - NewFrontOutside = Outside; - NewBackOutside = Outside; - } - - if( Model->Nodes[iNode].iFront==INDEX_NONE ) - { - FilterLeaf - ( - FilterFunc, - Model, - iNode, - &TempFrontEdPoly, - CoplanarInfo, - NewFrontOutside, - FHBSPOps::NODE_Front, - BspPoints, - BspVectors - ); - } - else - { - FilterEdPoly - ( - FilterFunc, - Model, - Model->Nodes[iNode].iFront, - &TempFrontEdPoly, - CoplanarInfo, - NewFrontOutside, - BspPoints, - BspVectors - ); - } - - // Back half of split. - if( Model->Nodes[iNode].iBack==INDEX_NONE ) - { - FilterLeaf - ( - FilterFunc, - Model, - iNode, - &TempBackEdPoly, - CoplanarInfo, - NewBackOutside, - FHBSPOps::NODE_Back, - BspPoints, - BspVectors - ); - } - else - { - FilterEdPoly - ( - FilterFunc, - Model, - Model->Nodes[iNode].iBack, - &TempBackEdPoly, - CoplanarInfo, - NewBackOutside, - BspPoints, - BspVectors - ); - } - } -} - -// -// Regular entry into FilterEdPoly (so higher-level callers don't have to -// deal with unnecessary info). Filters starting at root. -// -void UHCsgUtils::BspFilterFPoly( BspFilterFunc FilterFunc, UModel *Model, FPoly *EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - FCoplanarInfo StartingCoplanarInfo; - StartingCoplanarInfo.iOriginalNode = INDEX_NONE; - if( Model->Nodes.Num() == 0 ) - { - // If Bsp is empty, process at root. - (this->*FilterFunc)( Model, 0, EdPoly, Model->RootOutside ? F_OUTSIDE : F_INSIDE, FHBSPOps::NODE_Root, BspPoints, BspVectors ); - } - else - { - // Filter through Bsp. - FilterEdPoly( FilterFunc, Model, 0, EdPoly, StartingCoplanarInfo, Model->RootOutside, BspPoints, BspVectors ); - } -} - -int UHCsgUtils::bspNodeToFPoly -( - UModel* Model, - int32 iNode, - FPoly* EdPoly -) -{ - FPoly MasterEdPoly; - - FBspNode &Node = Model->Nodes[iNode]; - FBspSurf &Poly = Model->Surfs[Node.iSurf]; - FVert *VertPool = &Model->Verts[ Node.iVertPool ]; - - EdPoly->Base = Model->Points [Poly.pBase]; - EdPoly->Normal = Model->Vectors[Poly.vNormal]; - - EdPoly->PolyFlags = Poly.PolyFlags & ~(PF_EdCut | PF_EdProcessed | PF_Selected | PF_Memorized); - EdPoly->iLinkSurf = Node.iSurf; - EdPoly->Material = Poly.Material; - - EdPoly->Actor = Poly.Actor; - EdPoly->iBrushPoly = Poly.iBrushPoly; - - if( polyFindMaster(Model,Node.iSurf,MasterEdPoly) ) - EdPoly->ItemName = MasterEdPoly.ItemName; - else - EdPoly->ItemName = NAME_None; - - EdPoly->TextureU = Model->Vectors[Poly.vTextureU]; - EdPoly->TextureV = Model->Vectors[Poly.vTextureV]; - - EdPoly->LightMapScale = Poly.LightMapScale; - - EdPoly->LightmassSettings = Model->LightmassSettings[Poly.iLightmassIndex]; - - EdPoly->Vertices.Empty(); - - for(int32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++) - { - new(EdPoly->Vertices) FVector(Model->Points[VertPool[VertexIndex].pVertex]); - } - - if(EdPoly->Vertices.Num() < 3) - { - EdPoly->Vertices.Empty(); - } - else - { - // Remove colinear points and identical points (which will appear - // if T-joints were eliminated). - EdPoly->RemoveColinears(); - } - - return EdPoly->Vertices.Num(); -} - -/*--------------------------------------------------------------------------------------- - World filtering. ----------------------------------------------------------------------------------------*/ - -// -// Filter all relevant world polys through the brush. -// -void UHCsgUtils::FilterWorldThroughBrush -( - UModel* Model, - UModel* Brush, - EBrushType BrushType, - ECsgOper CSGOper, - int32 iNode, - FSphere* BrushSphere, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - // Loop through all coplanars. - while( iNode != INDEX_NONE ) - { - // Get surface. - int32 iSurf = Model->Nodes[iNode].iSurf; - - // Skip new nodes and their children, which are guaranteed new. - if( Model->Nodes[iNode].NodeFlags & NF_IsNew ) - return; - - // Sphere reject. - int DoFront = 1, DoBack = 1; - if( BrushSphere ) - { - float Dist = Model->Nodes[iNode].Plane.PlaneDot( BrushSphere->Center ); - DoFront = (Dist >= -BrushSphere->W); - DoBack = (Dist <= +BrushSphere->W); - } - - // Process only polys that aren't empty. - FPoly TempEdPoly; - if( DoFront && DoBack && (GEditor->bspNodeToFPoly(Model,iNode,&TempEdPoly)>0) ) - { - TempEdPoly.Actor = Model->Surfs[iSurf].Actor; - TempEdPoly.iBrushPoly = Model->Surfs[iSurf].iBrushPoly; - - if( BrushType==Brush_Add || BrushType==Brush_Subtract ) - { - // Add and subtract work the same in this step. - GNode = iNode; - GModel = Model; - GDiscarded = 0; - GNumNodes = Model->Nodes.Num(); - - // Find last coplanar in chain. - GLastCoplanar = iNode; - while( Model->Nodes[GLastCoplanar].iPlane != INDEX_NONE ) - GLastCoplanar = Model->Nodes[GLastCoplanar].iPlane; - - // Do the filter operation. - BspFilterFPoly - ( - BrushType==Brush_Add ? &UHCsgUtils::AddWorldToBrushFunc : &UHCsgUtils::SubtractWorldToBrushFunc, - Brush, - &TempEdPoly, - BspPoints, - BspVectors - ); - - if( GDiscarded == 0 ) - { - // Get rid of all the fragments we added. - Model->Nodes[GLastCoplanar].iPlane = INDEX_NONE; - const bool bAllowShrinking = false; - Model->Nodes.RemoveAt( GNumNodes, Model->Nodes.Num()-GNumNodes, bAllowShrinking ); - } - else - { - // Tag original world poly for deletion; has been deleted or replaced by partial fragments. - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - } - } - else if( CSGOper == CSG_Intersect ) - { - BspFilterFPoly( &UHCsgUtils::IntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); - } - else if( CSGOper == CSG_Deintersect ) - { - BspFilterFPoly( &UHCsgUtils::DeIntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); - } - } - - // Now recurse to filter all of the world's children nodes. - if( DoFront && (Model->Nodes[iNode].iFront != INDEX_NONE)) FilterWorldThroughBrush - ( - Model, - Brush, - BrushType, - CSGOper, - Model->Nodes[iNode].iFront, - BrushSphere, - BspPoints, - BspVectors - ); - if( DoBack && (Model->Nodes[iNode].iBack != INDEX_NONE) ) FilterWorldThroughBrush - ( - Model, - Brush, - BrushType, - CSGOper, - Model->Nodes[iNode].iBack, - BrushSphere, - BspPoints, - BspVectors - ); - iNode = Model->Nodes[iNode].iPlane; - } -} - -void UHCsgUtils::RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic) -{ - if (!IsValid(Model)) - return; - - UHCsgUtils* CsgUtils = NewObject(); - int32 CsgErrors = 0; - - UHBspPointsGrid* BspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* BspVectors = UHBspPointsGrid::Create(1/16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - - // Empty the model out. - const int32 NumPoints = Model->Points.Num(); - const int32 NumNodes = Model->Nodes.Num(); - const int32 NumVerts = Model->Verts.Num(); - const int32 NumVectors = Model->Vectors.Num(); - const int32 NumSurfs = Model->Surfs.Num(); - - Model->Modify(); - Model->EmptyModel(1, 1); - - // Reserve arrays an eighth bigger than the previous allocation - Model->Points.Empty(NumPoints + NumPoints / 8); - Model->Nodes.Empty(NumNodes + NumNodes / 8); - Model->Verts.Empty(NumVerts + NumVerts / 8); - Model->Vectors.Empty(NumVectors + NumVectors / 8); - Model->Surfs.Empty(NumSurfs + NumSurfs / 8); - - // Build list of all static brushes, first structural brushes and portals - TArray StaticBrushes; - for (ABrush* Brush : Brushes) - { - if ((Brush && (Brush->IsStaticBrush() || bTreatMovableBrushesAsStatic) && !FActorEditorUtils::IsABuilderBrush(Brush)) && - (!(Brush->PolyFlags & PF_Semisolid) || (Brush->BrushType != Brush_Add) || (Brush->PolyFlags & PF_Portal))) - { - StaticBrushes.Add(Brush); - - // Treat portals as solids for cutting. - if (Brush->PolyFlags & PF_Portal) - { - Brush->PolyFlags = (Brush->PolyFlags & ~PF_Semisolid) | PF_NotSolid; - } - } - } - - // Next append all detail brushes - for (ABrush* Brush : Brushes) - { - if (Brush && Brush->IsStaticBrush() && !FActorEditorUtils::IsABuilderBrush(Brush) && - (Brush->PolyFlags & PF_Semisolid) && !(Brush->PolyFlags & PF_Portal) && (Brush->BrushType == Brush_Add)) - { - StaticBrushes.Add(Brush); - } - } - - // Build list of dynamic brushes - TArray DynamicBrushes; - if (!bTreatMovableBrushesAsStatic) - { - for (ABrush* DynamicBrush : Brushes) - { - if (DynamicBrush && DynamicBrush->Brush && !DynamicBrush->IsStaticBrush()) - { - DynamicBrushes.Add(DynamicBrush); - } - } - } - - FScopedSlowTask SlowTask(StaticBrushes.Num() + DynamicBrushes.Num()); - SlowTask.MakeDialogDelayed(3.0f); - - // Compose all static brushes - for (ABrush* Brush : StaticBrushes) - { - SlowTask.EnterProgressFrame(1); - Brush->Modify(); - int32 Errors = CsgUtils->ComposeBrushCSG(Brush, Model, Brush->PolyFlags, (EBrushType)Brush->BrushType, CSG_None, false, true, false, false, BspPoints, BspVectors); - if (Errors > 1) - CsgErrors += Errors - 1; - } - - // Rebuild dynamic brush BSP's (if they weren't handled earlier) - for (ABrush* DynamicBrush : DynamicBrushes) - { - SlowTask.EnterProgressFrame(1); - UHBspPointsGrid* LocalBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* LocalBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - - FHBSPOps::csgPrepMovingBrush(DynamicBrush, LocalBspPoints, LocalBspVectors); - } -} - - - -UModel* UHCsgUtils::BuildModelFromBrushes(TArray& Brushes) -{ - // Generally UModels are initialized using ABrush. Here we manually - // initialize using relevant parts from - UModel* OutModel = NewObject(); - OutModel->SetFlags(RF_Transactional); - OutModel->RootOutside = true; - OutModel->EmptyModel(1,1); - OutModel->UpdateVertices(); - - if (!IsValid(OutModel)) - return nullptr; - - // Can we combine the brushes without modifying the actors here ...? - - //FVector Location(0.0f, 0.0f, 0.0f); - //FRotator Rotation(0.0f, 0.0f, 0.0f); - //for(int32 BrushesIdx = 0; BrushesIdx < Brushes.Num(); ++BrushesIdx ) - //{ - // // Cache the location and rotation. - // Location = Brushes[BrushesIdx]->GetActorLocation(); - // Rotation = Brushes[BrushesIdx]->GetActorRotation(); - - - // // Leave the actor's rotation but move it to origin so the Static Mesh will generate correctly. - // Brushes[BrushesIdx]->TeleportTo(Location - InPivotLocation, Rotation, false, true); - //} - - RebuildModelFromBrushes(OutModel, Brushes, true); - //GEditor->bspBuildFPolys(OutModel, true, 0); - - //if (0 < ConversionTempModel->Polys->Element.Num()) - //{ - // UStaticMesh* NewMesh = CreateStaticMeshFromBrush(Pkg, ObjName, NULL, ConversionTempModel); - // NewActor = FActorFactoryAssetProxy::AddActorForAsset( NewMesh ); - - // NewActor->Modify(); - - // NewActor->InvalidateLightingCache(); - // NewActor->PostEditChange(); - // NewActor->PostEditMove( true ); - // NewActor->Modify(); - // ULayersSubsystem* LayersSubsystem = GetEditorSubsystem(); - // LayersSubsystem->InitializeNewActorLayers(NewActor); - - // // Teleport the new actor to the old location but not the old rotation. The static mesh is built to the rotation already. - // NewActor->TeleportTo(InPivotLocation, FRotator(0.0f, 0.0f, 0.0f), false, true); - - // // Destroy the old brushes. - // for( int32 BrushIdx = 0; BrushIdx < InBrushesToConvert.Num(); ++BrushIdx ) - // { - // LayersSubsystem->DisassociateActorFromLayers(InBrushesToConvert[BrushIdx]); - // GWorld->EditorDestroyActor( InBrushesToConvert[BrushIdx], true ); - // } - - // // Notify the asset registry - // FAssetRegistryModule::AssetCreated(NewMesh); - //} - - //ConversionTempModel->EmptyModel(1, 1); - //RebuildAlteredBSP(); - //RedrawLevelEditingViewports(); - - //return NewActor; - - return OutModel; -} - -int UHCsgUtils::ComposeBrushCSG -( - ABrush* Actor, - UModel* Model, - uint32 PolyFlags, - EBrushType BrushType, - ECsgOper CSGOper, - bool bBuildBounds, - bool bMergePolys, - bool bReplaceNULLMaterialRefs, - bool bShowProgressBar, /*=true*/ - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - uint32 NotPolyFlags = 0; - int32 NumPolysFromBrush=0,i,j,ReallyBig; - UModel* Brush = Actor->Brush; - int32 Errors = 0; - - // Make sure we're in an acceptable state. - if( !Brush ) - { - return 0; - } - - // Non-solid and semisolid stuff can only be added. - if( BrushType != Brush_Add ) - { - NotPolyFlags |= (PF_Semisolid | PF_NotSolid); - } - - TempModel->EmptyModel(1,1); - - // Update status. - ReallyBig = (Brush->Polys->Element.Num() > 200) && bShowProgressBar; - if( ReallyBig ) - { - FText Description = NSLOCTEXT("UnrealEd", "PerformingCSGOperation", "Performing CSG operation"); - - if (BrushType != Brush_MAX) - { - if (BrushType == Brush_Add) - { - Description = NSLOCTEXT("UnrealEd", "AddingBrushToWorld", "Adding brush to world"); - } - else if (BrushType == Brush_Subtract) - { - Description = NSLOCTEXT("UnrealEd", "SubtractingBrushFromWorld", "Subtracting brush from world"); - } - } - else if (CSGOper != CSG_None) - { - if (CSGOper == CSG_Intersect) - { - Description = NSLOCTEXT("UnrealEd", "IntersectingBrushWithWorld", "Intersecting brush with world"); - } - else if (CSGOper == CSG_Deintersect) - { - Description = NSLOCTEXT("UnrealEd", "DeintersectingBrushWithWorld", "Deintersecting brush with world"); - } - } - - GWarn->BeginSlowTask( Description, true ); - // Transform original brush poly into same coordinate system as world - // so Bsp filtering operations make sense. - GWarn->StatusUpdate(0, 0, NSLOCTEXT("UnrealEd", "Transforming", "Transforming")); - } - - - //UMaterialInterface* SelectedMaterialInstance = GEditor->GetSelectedObjects()->GetTop(); - UMaterialInterface* SelectedMaterialInstance = nullptr; - - const FVector Scale = Actor->GetActorScale(); - const FRotator Rotation = Actor->GetActorRotation(); - const FVector Location = Actor->GetActorLocation(); - - const bool bIsMirrored = (Scale.X * Scale.Y * Scale.Z < 0.0f); - - // Cache actor transform which is used for the geometry being built - Brush->OwnerLocationWhenLastBuilt = Location; - Brush->OwnerRotationWhenLastBuilt = Rotation; - Brush->OwnerScaleWhenLastBuilt = Scale; - Brush->bCachedOwnerTransformValid = true; - - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly& CurrentPoly = Brush->Polys->Element[i]; - - // Set texture the first time. - if ( bReplaceNULLMaterialRefs ) - { - UMaterialInterface*& PolyMat = CurrentPoly.Material; - if ( !PolyMat || PolyMat == UMaterial::GetDefaultMaterial(MD_Surface) ) - { - PolyMat = SelectedMaterialInstance; - } - } - - // Get the brush poly. - FPoly DestEdPoly = CurrentPoly; - check(CurrentPoly.iLinkPolys->Element.Num()); - - // Set its backward brush link. - DestEdPoly.Actor = Actor; - DestEdPoly.iBrushPoly = i; - - // Update its flags. - DestEdPoly.PolyFlags = (DestEdPoly.PolyFlags | PolyFlags) & ~NotPolyFlags; - - // Set its internal link. - if (DestEdPoly.iLink == INDEX_NONE) - { - DestEdPoly.iLink = i; - } - - // Transform it. - DestEdPoly.Scale( Scale ); - DestEdPoly.Rotate( Rotation ); - DestEdPoly.Transform( Location ); - - // Reverse winding and normal if the parent brush is mirrored - if (bIsMirrored) - { - DestEdPoly.Reverse(); - DestEdPoly.CalcNormal(); - } - - // Add poly to the temp model. - new(TempModel->Polys->Element)FPoly( DestEdPoly ); - } - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringBrush", "Filtering brush") ); - - // Pass the brush polys through the world Bsp. - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - // Empty the brush. - Brush->EmptyModel(1,1); - - // Intersect and deintersect. - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly EdPoly = TempModel->Polys->Element[i]; - GModel = Brush; - // TODO: iLink / iLinkSurf in EdPoly / TempModel->Polys->Element[i] ? - BspFilterFPoly( CSGOper==CSG_Intersect ? &UHCsgUtils::IntersectBrushWithWorldFunc : &UHCsgUtils::DeIntersectBrushWithWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); - } - NumPolysFromBrush = Brush->Polys->Element.Num(); - } - else - { - // Add and subtract. - TMap SurfaceIndexRemap; - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly EdPoly = TempModel->Polys->Element[i]; - - // Mark the polygon as non-cut so that it won't be harmed unless it must - // be split, and set iLink so that BspAddNode will know to add its information - // if a node is added based on this poly. - EdPoly.PolyFlags &= ~(PF_EdCut); - const int32* SurfaceIndexPtr = SurfaceIndexRemap.Find(EdPoly.iLink); - if (SurfaceIndexPtr == nullptr) - { - const int32 NewSurfaceIndex = Model->Surfs.Num(); - SurfaceIndexRemap.Add(EdPoly.iLink, NewSurfaceIndex); - EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = NewSurfaceIndex; - } - else - { - EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = *SurfaceIndexPtr; - } - - // Filter brush through the world. - BspFilterFPoly( BrushType==Brush_Add ? &UHCsgUtils::AddBrushToWorldFunc : &UHCsgUtils::SubtractBrushFromWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); - } - } - if( Model->Nodes.Num() && !(PolyFlags & (PF_NotSolid | PF_Semisolid)) ) - { - // Quickly build a Bsp for the brush, tending to minimize splits rather than balance - // the tree. We only need the cutting planes, though the entire Bsp struct (polys and - // all) is built. - - /*FHBspPointsGrid* LevelModelPointsGrid = FHBspPointsGrid::GBspPoints; - FHBspPointsGrid* LevelModelVectorsGrid = FHBspPointsGrid::GBspVectors;*/ - - // For the bspBuild call, temporarily create a new pair of BspPointsGrids for the TempModel. - UHBspPointsGrid* TempBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* TempBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - /*FHBspPointsGrid::GBspPoints = BspPoints.Get(); - FHBspPointsGrid::GBspVectors = BspVectors.Get();*/ - - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "BuildingBSP", "Building BSP") ); - - FHBSPOps::bspBuild( TempModel, FHBSPOps::BSP_Lame, 0, 70, 1, 0, TempBspPoints, TempBspVectors ); - - // Reinstate the original BspPointsGrids used for building the level Model. - /*FHBspPointsGrid::GBspPoints = LevelModelPointsGrid; - FHBspPointsGrid::GBspVectors = LevelModelVectorsGrid;*/ - - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringWorld", "Filtering world") ); - GModel = Brush; - TempModel->BuildBound(); - - FSphere BrushSphere = TempModel->Bounds.GetSphere(); - FilterWorldThroughBrush( Model, TempModel, BrushType, CSGOper, 0, &BrushSphere, BspPoints, BspVectors); - } - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "AdjustingBrush", "Adjusting brush") ); - - // Link polys obtained from the original brush. - for( i=NumPolysFromBrush-1; i>=0; i-- ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - for( j=0; jiLink == Brush->Polys->Element[j].iLink ) - { - DestEdPoly->iLink = j; - break; - } - } - if( j >= i ) DestEdPoly->iLink = i; - } - - // Link polys obtained from the world. - for( i=Brush->Polys->Element.Num()-1; i>=NumPolysFromBrush; i-- ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - for( j=NumPolysFromBrush; jiLink == Brush->Polys->Element[j].iLink ) - { - DestEdPoly->iLink = j; - break; - } - } - if( j >= i ) DestEdPoly->iLink = i; - } - Brush->Linked = 1; - - // Detransform the obtained brush back into its original coordinate system. - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - DestEdPoly->Transform(-Location); - DestEdPoly->Rotate(Rotation.GetInverse()); - DestEdPoly->Scale(FVector(1.0f) / Scale); - DestEdPoly->Fix(); - DestEdPoly->Actor = NULL; - DestEdPoly->iBrushPoly = i; - } - } - - if( BrushType==Brush_Add || BrushType==Brush_Subtract ) - { - // Clean up nodes, reset node flags. - bspCleanup( Model ); - - // Rebuild bounding volumes. - if( bBuildBounds ) - { - FHBSPOps::bspBuildBounds( Model ); - } - } - - Brush->NumUniqueVertices = TempModel->Points.Num(); - // Release TempModel. - TempModel->EmptyModel(1,1); - - // Merge coplanars if needed. - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - if( ReallyBig ) - { - GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "Merging", "Merging") ); - } - if( bMergePolys ) - { - bspMergeCoplanars( Brush, 1, 0 ); - } - } - if( ReallyBig ) - { - GWarn->EndSlowTask(); - } - - return 1 + FHBSPOps::GErrors; -} - -/*---------------------------------------------------------------------------- - EdPoly building and compacting. -----------------------------------------------------------------------------*/ - -// -// Trys to merge two polygons. If they can be merged, replaces Poly1 and emptys Poly2 -// and returns 1. Otherwise, returns 0. -// -int UHCsgUtils::TryToMerge( FPoly *Poly1, FPoly *Poly2 ) -{ - // Find one overlapping point. - int32 Start1=0, Start2=0; - for( Start1=0; Start1Vertices.Num(); Start1++ ) - for( Start2=0; Start2Vertices.Num(); Start2++ ) - if( FVector::PointsAreSame(Poly1->Vertices[Start1], Poly2->Vertices[Start2]) ) - goto FoundOverlap; - return 0; - FoundOverlap: - - // Wrap around trying to merge. - int32 End1 = Start1; - int32 End2 = Start2; - int32 Test1 = Start1+1; if (Test1>=Poly1->Vertices.Num()) Test1 = 0; - int32 Test2 = Start2-1; if (Test2<0) Test2 = Poly2->Vertices.Num()-1; - if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) - { - End1 = Test1; - Start2 = Test2; - } - else - { - Test1 = Start1-1; if (Test1<0) Test1=Poly1->Vertices.Num()-1; - Test2 = Start2+1; if (Test2>=Poly2->Vertices.Num()) Test2=0; - if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) - { - Start1 = Test1; - End2 = Test2; - } - else return 0; - } - - // Build a new edpoly containing both polygons merged. - FPoly NewPoly = *Poly1; - NewPoly.Vertices.Empty(); - int32 Vertex = End1; - for( int32 i=0; iVertices.Num(); i++ ) - { - new(NewPoly.Vertices) FVector(Poly1->Vertices[Vertex]); - if( ++Vertex >= Poly1->Vertices.Num() ) - Vertex=0; - } - Vertex = End2; - for( int32 i=0; i<(Poly2->Vertices.Num()-2); i++ ) - { - if( ++Vertex >= Poly2->Vertices.Num() ) - Vertex=0; - new(NewPoly.Vertices) FVector(Poly2->Vertices[Vertex]); - } - - // Remove colinear vertices and check convexity. - if( NewPoly.RemoveColinears() ) - { - *Poly1 = NewPoly; - Poly2->Vertices.Empty(); - return true; - } - else return 0; -} - -// -// Merge all polygons in coplanar list that can be merged convexly. -// -void UHCsgUtils::MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ) -{ - int32 MergeAgain = 1; - while( MergeAgain ) - { - MergeAgain = 0; - for( int32 i=0; iPolys->Element[PolyList[i]]; - if( Poly1.Vertices.Num() > 0 ) - { - for( int32 j=i+1; jPolys->Element[PolyList[j]]; - if( Poly2.Vertices.Num() > 0 ) - { - if( TryToMerge( &Poly1, &Poly2 ) ) - MergeAgain=1; - } - } - } - } - } -} - -void UHCsgUtils::bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ) -{ - int32 OriginalNum = Model->Polys->Element.Num(); - - // Mark all polys as unprocessed. - for( int32 i=0; iPolys->Element.Num(); i++ ) - Model->Polys->Element[i].PolyFlags &= ~PF_EdProcessed; - - // Find matching coplanars and merge them. - FMemMark Mark(FMemStack::Get()); - int32* PolyList = new(FMemStack::Get(),Model->Polys->Element.Num())int32; - int32 n=0; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - FPoly* EdPoly = &Model->Polys->Element[i]; - if( EdPoly->Vertices.Num()>0 && !(EdPoly->PolyFlags & PF_EdProcessed) ) - { - int32 PolyCount = 0; - PolyList[PolyCount++] = i; - EdPoly->PolyFlags |= PF_EdProcessed; - for( int32 j=i+1; jPolys->Element.Num(); j++ ) - { - FPoly* OtherPoly = &Model->Polys->Element[j]; - if( OtherPoly->iLink == EdPoly->iLink && OtherPoly->Vertices.Num() ) - { - float Dist = (OtherPoly->Vertices[0] - EdPoly->Vertices[0]) | EdPoly->Normal; - if - ( Dist>-0.001 - && Dist<0.001 - && (OtherPoly->Normal|EdPoly->Normal)>0.9999 - && (MergeDisparateTextures - || ( FVector::PointsAreNear(OtherPoly->TextureU,EdPoly->TextureU,THRESH_VECTORS_ARE_NEAR) - && FVector::PointsAreNear(OtherPoly->TextureV,EdPoly->TextureV,THRESH_VECTORS_ARE_NEAR) ) ) ) - { - OtherPoly->PolyFlags |= PF_EdProcessed; - PolyList[PolyCount++] = j; - } - } - } - if( PolyCount > 1 ) - { - MergeCoplanars( Model, PolyList, PolyCount ); - n++; - } - } - } -// UE_LOG(LogEditorBsp, Log, TEXT("Found %i coplanar sets in %i"), n, Model->Polys->Element.Num() ); - Mark.Pop(); - - // Get rid of empty EdPolys while remapping iLinks. - FMemMark Mark2(FMemStack::Get()); - int32 j=0; - int32* Remap = new(FMemStack::Get(),Model->Polys->Element.Num())int32; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - if( Model->Polys->Element[i].Vertices.Num() ) - { - Remap[i] = j; - Model->Polys->Element[j] = Model->Polys->Element[i]; - j++; - } - } - Model->Polys->Element.RemoveAt( j, Model->Polys->Element.Num()-j ); - if( RemapLinks ) - { - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - if (Model->Polys->Element[i].iLink != INDEX_NONE) - { - CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*Remap'. - Model->Polys->Element[i].iLink = Remap[Model->Polys->Element[i].iLink]; - } - } - } -// UE_LOG(LogEditorBsp, Log, TEXT("BspMergeCoplanars reduced %i->%i"), OriginalNum, Model->Polys->Element.Num() ); - Mark2.Pop(); -} - -bool UHCsgUtils::polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly) -{ - FBspSurf &Surf = InModel->Surfs[iSurf]; - if( !Surf.Actor || !Surf.Actor->Brush->Polys->Element.IsValidIndex(Surf.iBrushPoly) ) - { - return false; - } - else - { - Poly = Surf.Actor->Brush->Polys->Element[Surf.iBrushPoly]; - return true; - } -} - -void UHCsgUtils::CleanupNodes( UModel *Model, int32 iNode, int32 iParent ) -{ - FBspNode *Node = &Model->Nodes[iNode]; - - // Transactionally empty vertices of tag-for-empty nodes. - Node->NodeFlags &= ~(NF_IsNew | NF_IsFront | NF_IsBack); - - // Recursively clean up front, back, and plane nodes. - if( Node->iFront != INDEX_NONE ) CleanupNodes( Model, Node->iFront, iNode ); - if( Node->iBack != INDEX_NONE ) CleanupNodes( Model, Node->iBack , iNode ); - if( Node->iPlane != INDEX_NONE ) CleanupNodes( Model, Node->iPlane, iNode ); - - // Reload Node since the recusive call aliases it. - Node = &Model->Nodes[iNode]; - - // If this is an empty node with a coplanar, replace it with the coplanar. - if( Node->NumVertices==0 && Node->iPlane!=INDEX_NONE ) - { - FBspNode* PlaneNode = &Model->Nodes[ Node->iPlane ]; - - // Stick our front, back, and parent nodes on the coplanar. - if( (Node->Plane | PlaneNode->Plane) >= 0.0 ) - { - PlaneNode->iFront = Node->iFront; - PlaneNode->iBack = Node->iBack; - } - else - { - PlaneNode->iFront = Node->iBack; - PlaneNode->iBack = Node->iFront; - } - - if( iParent == INDEX_NONE ) - { - // This node is the root. - *Node = *PlaneNode; // Replace root. - PlaneNode->NumVertices = 0; // Mark as unused. - } - else - { - // This is a child node. - FBspNode *ParentNode = &Model->Nodes[iParent]; - - if ( ParentNode->iFront == iNode ) ParentNode->iFront = Node->iPlane; - else if ( ParentNode->iBack == iNode ) ParentNode->iBack = Node->iPlane; - else if ( ParentNode->iPlane == iNode ) ParentNode->iPlane = Node->iPlane; - else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); - } - } - else if( Node->NumVertices == 0 && ( Node->iFront==INDEX_NONE || Node->iBack==INDEX_NONE ) ) - { - // Delete empty nodes with no fronts or backs. - // Replace empty nodes with only fronts. - // Replace empty nodes with only backs. - int32 iReplacementNode; - if ( Node->iFront != INDEX_NONE ) iReplacementNode = Node->iFront; - else if( Node->iBack != INDEX_NONE ) iReplacementNode = Node->iBack; - else iReplacementNode = INDEX_NONE; - - if( iParent == INDEX_NONE ) - { - // Root. - if( iReplacementNode == INDEX_NONE ) - { - Model->Nodes.Empty(); - } - else - { - *Node = Model->Nodes[iReplacementNode]; - } - } - else - { - // Regular node. - FBspNode *ParentNode = &Model->Nodes[iParent]; - - if ( ParentNode->iFront == iNode ) ParentNode->iFront = iReplacementNode; - else if( ParentNode->iBack == iNode ) ParentNode->iBack = iReplacementNode; - else if( ParentNode->iPlane == iNode ) ParentNode->iPlane = iReplacementNode; - else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); - } - } -} - - -void UHCsgUtils::bspCleanup( UModel *Model ) -{ - if( Model->Nodes.Num() > 0 ) - CleanupNodes( Model, 0, INDEX_NONE ); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HCsgUtils.h" + +#include "Engine/Engine.h" +#include "Engine/Polys.h" +#include "Engine/Selection.h" +#include "Materials/Material.h" +#include "Misc/FeedbackContext.h" + +#include "ActorEditorUtils.h" +#include "Misc/ScopedSlowTask.h" + + +DEFINE_LOG_CATEGORY_STATIC(LogHCsgUtils, Log, All); + +#if WITH_EDITOR +#include "Editor.h" +#endif + +// Magic numbers. +#define THRESH_OPTGEOM_COPLANAR (0.25) /* Threshold for Bsp geometry optimization */ +#define THRESH_OPTGEOM_COSIDAL (0.25) /* Threshold for Bsp geometry optimization */ + + +UHCsgUtils::UHCsgUtils() +{ + // A TempModel is allocated for the HCsgUtils instance to avoid reallocation during inner loops. + TempModel = NewObject(); + TempModel->Initialize(nullptr, 1); + + /*GBspPoints = NewObject(); + GBspVectors = NewObject();*/ +} + + +/*---------------------------------------------------------------------------- + CSG leaf filter callbacks. +----------------------------------------------------------------------------*/ + +void UHCsgUtils::AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); + break; + case F_COSPATIAL_FACING_OUT: + if( !(EdPoly->PolyFlags & PF_Semisolid) ) + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_IN: + break; + } +} + +void UHCsgUtils::AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + // Only affect the world poly if it has been cut. + if( EdPoly->PolyFlags & PF_EdCut ) + FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors ); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_IN: + case F_COSPATIAL_FACING_OUT: + // Discard original poly. + GDiscarded++; + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + break; + } +} + +void UHCsgUtils::SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch (Filter) + { + case F_OUTSIDE: + case F_COSPATIAL_FACING_OUT: + case F_COSPATIAL_FACING_IN: + case F_COPLANAR_OUTSIDE: + break; + case F_COPLANAR_INSIDE: + case F_INSIDE: + EdPoly->Reverse(); + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); // Add to Bsp back + EdPoly->Reverse(); + break; + } +} + +void UHCsgUtils::SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + // Only affect the world poly if it has been cut. + if( EdPoly->PolyFlags & PF_EdCut ) + FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + // Discard original poly. + GDiscarded++; + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + break; + } +} + +void UHCsgUtils::IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + case F_COSPATIAL_FACING_OUT: + // Ignore. + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + if( EdPoly->Fix()>=3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + // Ignore. + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + if( EdPoly->Fix() >= 3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + case F_COSPATIAL_FACING_IN: + // Ignore. + break; + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + if( EdPoly->Fix()>=3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_OUT: + // Ignore. + break; + case F_COPLANAR_INSIDE: + case F_INSIDE: + case F_COSPATIAL_FACING_IN: + if( EdPoly->Fix() >= 3 ) + { + EdPoly->Reverse(); + new(GModel->Polys->Element)FPoly(*EdPoly); + EdPoly->Reverse(); + } + break; + } +} + +/*---------------------------------------------------------------------------- + CSG polygon filtering routine (calls the callbacks). +----------------------------------------------------------------------------*/ + +// +// Handle a piece of a polygon that was filtered to a leaf. +// +void UHCsgUtils::FilterLeaf +( + BspFilterFunc FilterFunc, + UModel* Model, + int32 iNode, + FPoly* EdPoly, + FCoplanarInfo CoplanarInfo, + int32 LeafOutside, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + EPolyNodeFilter FilterType; + + if( CoplanarInfo.iOriginalNode == INDEX_NONE ) + { + // Processing regular, non-coplanar polygons. + FilterType = LeafOutside ? F_OUTSIDE : F_INSIDE; + (this->*FilterFunc)( Model, iNode, EdPoly, FilterType, ENodePlace, BspPoints, BspVectors ); + } + else if( CoplanarInfo.ProcessingBack ) + { + // Finished filtering polygon through tree in back of parent coplanar. + DoneFilteringBack: + if ((!LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_INSIDE; + else if (( LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_OUTSIDE; + else if ((!LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_OUT; + else if (( LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_IN; + else + { + UE_LOG(LogHCsgUtils, Fatal, TEXT("FilterLeaf: Bad Locs")); + return; + } + (this->*FilterFunc)( Model, CoplanarInfo.iOriginalNode, EdPoly, FilterType, FHBSPOps::NODE_Plane, BspPoints, BspVectors ); + } + else + { + CoplanarInfo.FrontLeafOutside = LeafOutside; + + if( CoplanarInfo.iBackNode == INDEX_NONE ) + { + // Back tree is empty. + LeafOutside = CoplanarInfo.BackNodeOutside; + goto DoneFilteringBack; + } + else + { + // Call FilterEdPoly to filter through the back. This will result in + // another call to FilterLeaf with iNode = leaf this falls into in the + // back tree and EdPoly = the final EdPoly to insert. + CoplanarInfo.ProcessingBack=1; + FilterEdPoly( FilterFunc, Model, CoplanarInfo.iBackNode, EdPoly,CoplanarInfo, CoplanarInfo.BackNodeOutside, BspPoints, BspVectors ); + } + } +} + +// +// Filter an EdPoly through the Bsp recursively, calling FilterFunc +// for all chunks that fall into leaves. FCoplanarInfo is used to +// handle the tricky case of double-recursion for polys that must be +// filtered through a node's front, then filtered through the node's back, +// in order to handle coplanar CSG properly. +// +void UHCsgUtils::FilterEdPoly +( + BspFilterFunc FilterFunc, + UModel *Model, + int32 iNode, + FPoly *EdPoly, + FCoplanarInfo CoplanarInfo, + int32 Outside, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + int32 SplitResult,iOurFront,iOurBack; + int32 NewFrontOutside,NewBackOutside; + + FilterLoop: + + // Split em. + FPoly TempFrontEdPoly,TempBackEdPoly; + SplitResult = EdPoly->SplitWithPlane + ( + Model->Points [Model->Verts[Model->Nodes[iNode].iVertPool].pVertex], + Model->Vectors[Model->Surfs[Model->Nodes[iNode].iSurf].vNormal], + &TempFrontEdPoly, + &TempBackEdPoly, + 0 + ); + + // Process split results. + if( SplitResult == SP_Front ) + { + Front: + + FBspNode *Node = &Model->Nodes[iNode]; + Outside = Outside || Node->IsCsg(); + + if( Node->iFront == INDEX_NONE ) + { + FilterLeaf(FilterFunc,Model,iNode,EdPoly,CoplanarInfo,Outside,FHBSPOps::NODE_Front, BspPoints, BspVectors); + } + else + { + iNode = Node->iFront; + goto FilterLoop; + } + } + else if( SplitResult == SP_Back ) + { + FBspNode *Node = &Model->Nodes[iNode]; + Outside = Outside && !Node->IsCsg(); + + if( Node->iBack == INDEX_NONE ) + { + FilterLeaf( FilterFunc, Model, iNode, EdPoly, CoplanarInfo, Outside, FHBSPOps::NODE_Back, BspPoints, BspVectors); + } + else + { + iNode=Node->iBack; + goto FilterLoop; + } + } + else if( SplitResult == SP_Coplanar ) + { + if( CoplanarInfo.iOriginalNode != INDEX_NONE ) + { + // This will happen once in a blue moon when a polygon is barely outside the + // coplanar threshold and is split up into a new polygon that is + // is barely inside the coplanar threshold. To handle this, just classify + // it as front and it will be handled propery. + FHBSPOps::GErrors++; +// UE_LOG(LogEditorBsp, Warning, TEXT("FilterEdPoly: Encountered out-of-place coplanar") ); + goto Front; + } + CoplanarInfo.iOriginalNode = iNode; + CoplanarInfo.iBackNode = INDEX_NONE; + CoplanarInfo.ProcessingBack = 0; + CoplanarInfo.BackNodeOutside = Outside; + NewFrontOutside = Outside; + + // See whether Node's iFront or iBack points to the side of the tree on the front + // of this polygon (will be as expected if this polygon is facing the same + // way as first coplanar in link, otherwise opposite). + if( (FVector(Model->Nodes[iNode].Plane) | EdPoly->Normal) >= 0.0 ) + { + iOurFront = Model->Nodes[iNode].iFront; + iOurBack = Model->Nodes[iNode].iBack; + + if( Model->Nodes[iNode].IsCsg() ) + { + CoplanarInfo.BackNodeOutside = 0; + NewFrontOutside = 1; + } + } + else + { + iOurFront = Model->Nodes[iNode].iBack; + iOurBack = Model->Nodes[iNode].iFront; + + if( Model->Nodes[iNode].IsCsg() ) + { + CoplanarInfo.BackNodeOutside = 1; + NewFrontOutside = 0; + } + } + + // Process front and back. + if ((iOurFront==INDEX_NONE)&&(iOurBack==INDEX_NONE)) + { + // No front or back. + CoplanarInfo.ProcessingBack = 1; + CoplanarInfo.FrontLeafOutside = NewFrontOutside; + FilterLeaf + ( + FilterFunc, + Model, + iNode, + EdPoly, + CoplanarInfo, + CoplanarInfo.BackNodeOutside, + FHBSPOps::NODE_Plane, + BspPoints, + BspVectors + ); + } + else if( iOurFront==INDEX_NONE && iOurBack!=INDEX_NONE ) + { + // Back but no front. + CoplanarInfo.ProcessingBack = 1; + CoplanarInfo.iBackNode = iOurBack; + CoplanarInfo.FrontLeafOutside = NewFrontOutside; + + iNode = iOurBack; + Outside = CoplanarInfo.BackNodeOutside; + goto FilterLoop; + } + else + { + // Has a front and maybe a back. + + // Set iOurBack up to process back on next call to FilterLeaf, and loop + // to process front. Next call to FilterLeaf will set FrontLeafOutside. + CoplanarInfo.ProcessingBack = 0; + + // May be a node or may be INDEX_NONE. + CoplanarInfo.iBackNode = iOurBack; + + iNode = iOurFront; + Outside = NewFrontOutside; + goto FilterLoop; + } + } + else if( SplitResult == SP_Split ) + { + // Front half of split. + if( Model->Nodes[iNode].IsCsg() ) + { + NewFrontOutside = 1; + NewBackOutside = 0; + } + else + { + NewFrontOutside = Outside; + NewBackOutside = Outside; + } + + if( Model->Nodes[iNode].iFront==INDEX_NONE ) + { + FilterLeaf + ( + FilterFunc, + Model, + iNode, + &TempFrontEdPoly, + CoplanarInfo, + NewFrontOutside, + FHBSPOps::NODE_Front, + BspPoints, + BspVectors + ); + } + else + { + FilterEdPoly + ( + FilterFunc, + Model, + Model->Nodes[iNode].iFront, + &TempFrontEdPoly, + CoplanarInfo, + NewFrontOutside, + BspPoints, + BspVectors + ); + } + + // Back half of split. + if( Model->Nodes[iNode].iBack==INDEX_NONE ) + { + FilterLeaf + ( + FilterFunc, + Model, + iNode, + &TempBackEdPoly, + CoplanarInfo, + NewBackOutside, + FHBSPOps::NODE_Back, + BspPoints, + BspVectors + ); + } + else + { + FilterEdPoly + ( + FilterFunc, + Model, + Model->Nodes[iNode].iBack, + &TempBackEdPoly, + CoplanarInfo, + NewBackOutside, + BspPoints, + BspVectors + ); + } + } +} + +// +// Regular entry into FilterEdPoly (so higher-level callers don't have to +// deal with unnecessary info). Filters starting at root. +// +void UHCsgUtils::BspFilterFPoly( BspFilterFunc FilterFunc, UModel *Model, FPoly *EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + FCoplanarInfo StartingCoplanarInfo; + StartingCoplanarInfo.iOriginalNode = INDEX_NONE; + if( Model->Nodes.Num() == 0 ) + { + // If Bsp is empty, process at root. + (this->*FilterFunc)( Model, 0, EdPoly, Model->RootOutside ? F_OUTSIDE : F_INSIDE, FHBSPOps::NODE_Root, BspPoints, BspVectors ); + } + else + { + // Filter through Bsp. + FilterEdPoly( FilterFunc, Model, 0, EdPoly, StartingCoplanarInfo, Model->RootOutside, BspPoints, BspVectors ); + } +} + +int UHCsgUtils::bspNodeToFPoly +( + UModel* Model, + int32 iNode, + FPoly* EdPoly +) +{ + FPoly MasterEdPoly; + + FBspNode &Node = Model->Nodes[iNode]; + FBspSurf &Poly = Model->Surfs[Node.iSurf]; + FVert *VertPool = &Model->Verts[ Node.iVertPool ]; + + EdPoly->Base = Model->Points [Poly.pBase]; + EdPoly->Normal = Model->Vectors[Poly.vNormal]; + + EdPoly->PolyFlags = Poly.PolyFlags & ~(PF_EdCut | PF_EdProcessed | PF_Selected | PF_Memorized); + EdPoly->iLinkSurf = Node.iSurf; + EdPoly->Material = Poly.Material; + + EdPoly->Actor = Poly.Actor; + EdPoly->iBrushPoly = Poly.iBrushPoly; + + if( polyFindMaster(Model,Node.iSurf,MasterEdPoly) ) + EdPoly->ItemName = MasterEdPoly.ItemName; + else + EdPoly->ItemName = NAME_None; + + EdPoly->TextureU = Model->Vectors[Poly.vTextureU]; + EdPoly->TextureV = Model->Vectors[Poly.vTextureV]; + + EdPoly->LightMapScale = Poly.LightMapScale; + + EdPoly->LightmassSettings = Model->LightmassSettings[Poly.iLightmassIndex]; + + EdPoly->Vertices.Empty(); + + for(int32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++) + { + new(EdPoly->Vertices) FVector(Model->Points[VertPool[VertexIndex].pVertex]); + } + + if(EdPoly->Vertices.Num() < 3) + { + EdPoly->Vertices.Empty(); + } + else + { + // Remove colinear points and identical points (which will appear + // if T-joints were eliminated). + EdPoly->RemoveColinears(); + } + + return EdPoly->Vertices.Num(); +} + +/*--------------------------------------------------------------------------------------- + World filtering. +---------------------------------------------------------------------------------------*/ + +// +// Filter all relevant world polys through the brush. +// +void UHCsgUtils::FilterWorldThroughBrush +( + UModel* Model, + UModel* Brush, + EBrushType BrushType, + ECsgOper CSGOper, + int32 iNode, + FSphere* BrushSphere, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + // Loop through all coplanars. + while( iNode != INDEX_NONE ) + { + // Get surface. + int32 iSurf = Model->Nodes[iNode].iSurf; + + // Skip new nodes and their children, which are guaranteed new. + if( Model->Nodes[iNode].NodeFlags & NF_IsNew ) + return; + + // Sphere reject. + int DoFront = 1, DoBack = 1; + if( BrushSphere ) + { + float Dist = Model->Nodes[iNode].Plane.PlaneDot( BrushSphere->Center ); + DoFront = (Dist >= -BrushSphere->W); + DoBack = (Dist <= +BrushSphere->W); + } + + // Process only polys that aren't empty. + FPoly TempEdPoly; + if( DoFront && DoBack && (GEditor->bspNodeToFPoly(Model,iNode,&TempEdPoly)>0) ) + { + TempEdPoly.Actor = Model->Surfs[iSurf].Actor; + TempEdPoly.iBrushPoly = Model->Surfs[iSurf].iBrushPoly; + + if( BrushType==Brush_Add || BrushType==Brush_Subtract ) + { + // Add and subtract work the same in this step. + GNode = iNode; + GModel = Model; + GDiscarded = 0; + GNumNodes = Model->Nodes.Num(); + + // Find last coplanar in chain. + GLastCoplanar = iNode; + while( Model->Nodes[GLastCoplanar].iPlane != INDEX_NONE ) + GLastCoplanar = Model->Nodes[GLastCoplanar].iPlane; + + // Do the filter operation. + BspFilterFPoly + ( + BrushType==Brush_Add ? &UHCsgUtils::AddWorldToBrushFunc : &UHCsgUtils::SubtractWorldToBrushFunc, + Brush, + &TempEdPoly, + BspPoints, + BspVectors + ); + + if( GDiscarded == 0 ) + { + // Get rid of all the fragments we added. + Model->Nodes[GLastCoplanar].iPlane = INDEX_NONE; + const bool bAllowShrinking = false; + Model->Nodes.RemoveAt( GNumNodes, Model->Nodes.Num()-GNumNodes, bAllowShrinking ); + } + else + { + // Tag original world poly for deletion; has been deleted or replaced by partial fragments. + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + } + } + else if( CSGOper == CSG_Intersect ) + { + BspFilterFPoly( &UHCsgUtils::IntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); + } + else if( CSGOper == CSG_Deintersect ) + { + BspFilterFPoly( &UHCsgUtils::DeIntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); + } + } + + // Now recurse to filter all of the world's children nodes. + if( DoFront && (Model->Nodes[iNode].iFront != INDEX_NONE)) FilterWorldThroughBrush + ( + Model, + Brush, + BrushType, + CSGOper, + Model->Nodes[iNode].iFront, + BrushSphere, + BspPoints, + BspVectors + ); + if( DoBack && (Model->Nodes[iNode].iBack != INDEX_NONE) ) FilterWorldThroughBrush + ( + Model, + Brush, + BrushType, + CSGOper, + Model->Nodes[iNode].iBack, + BrushSphere, + BspPoints, + BspVectors + ); + iNode = Model->Nodes[iNode].iPlane; + } +} + +void UHCsgUtils::RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic) +{ + if (!IsValid(Model)) + return; + + UHCsgUtils* CsgUtils = NewObject(); + int32 CsgErrors = 0; + + UHBspPointsGrid* BspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* BspVectors = UHBspPointsGrid::Create(1/16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + + // Empty the model out. + const int32 NumPoints = Model->Points.Num(); + const int32 NumNodes = Model->Nodes.Num(); + const int32 NumVerts = Model->Verts.Num(); + const int32 NumVectors = Model->Vectors.Num(); + const int32 NumSurfs = Model->Surfs.Num(); + + Model->Modify(); + Model->EmptyModel(1, 1); + + // Reserve arrays an eighth bigger than the previous allocation + Model->Points.Empty(NumPoints + NumPoints / 8); + Model->Nodes.Empty(NumNodes + NumNodes / 8); + Model->Verts.Empty(NumVerts + NumVerts / 8); + Model->Vectors.Empty(NumVectors + NumVectors / 8); + Model->Surfs.Empty(NumSurfs + NumSurfs / 8); + + // Build list of all static brushes, first structural brushes and portals + TArray StaticBrushes; + for (ABrush* Brush : Brushes) + { + if ((Brush && (Brush->IsStaticBrush() || bTreatMovableBrushesAsStatic) && !FActorEditorUtils::IsABuilderBrush(Brush)) && + (!(Brush->PolyFlags & PF_Semisolid) || (Brush->BrushType != Brush_Add) || (Brush->PolyFlags & PF_Portal))) + { + StaticBrushes.Add(Brush); + + // Treat portals as solids for cutting. + if (Brush->PolyFlags & PF_Portal) + { + Brush->PolyFlags = (Brush->PolyFlags & ~PF_Semisolid) | PF_NotSolid; + } + } + } + + // Next append all detail brushes + for (ABrush* Brush : Brushes) + { + if (Brush && Brush->IsStaticBrush() && !FActorEditorUtils::IsABuilderBrush(Brush) && + (Brush->PolyFlags & PF_Semisolid) && !(Brush->PolyFlags & PF_Portal) && (Brush->BrushType == Brush_Add)) + { + StaticBrushes.Add(Brush); + } + } + + // Build list of dynamic brushes + TArray DynamicBrushes; + if (!bTreatMovableBrushesAsStatic) + { + for (ABrush* DynamicBrush : Brushes) + { + if (DynamicBrush && DynamicBrush->Brush && !DynamicBrush->IsStaticBrush()) + { + DynamicBrushes.Add(DynamicBrush); + } + } + } + + FScopedSlowTask SlowTask(StaticBrushes.Num() + DynamicBrushes.Num()); + SlowTask.MakeDialogDelayed(3.0f); + + // Compose all static brushes + for (ABrush* Brush : StaticBrushes) + { + SlowTask.EnterProgressFrame(1); + Brush->Modify(); + int32 Errors = CsgUtils->ComposeBrushCSG(Brush, Model, Brush->PolyFlags, (EBrushType)Brush->BrushType, CSG_None, false, true, false, false, BspPoints, BspVectors); + if (Errors > 1) + CsgErrors += Errors - 1; + } + + // Rebuild dynamic brush BSP's (if they weren't handled earlier) + for (ABrush* DynamicBrush : DynamicBrushes) + { + SlowTask.EnterProgressFrame(1); + UHBspPointsGrid* LocalBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* LocalBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + + FHBSPOps::csgPrepMovingBrush(DynamicBrush, LocalBspPoints, LocalBspVectors); + } +} + + + +UModel* UHCsgUtils::BuildModelFromBrushes(TArray& Brushes) +{ + // Generally UModels are initialized using ABrush. Here we manually + // initialize using relevant parts from + UModel* OutModel = NewObject(); + OutModel->SetFlags(RF_Transactional); + OutModel->RootOutside = true; + OutModel->EmptyModel(1,1); + OutModel->UpdateVertices(); + + if (!IsValid(OutModel)) + return nullptr; + + // Can we combine the brushes without modifying the actors here ...? + + //FVector Location(0.0f, 0.0f, 0.0f); + //FRotator Rotation(0.0f, 0.0f, 0.0f); + //for(int32 BrushesIdx = 0; BrushesIdx < Brushes.Num(); ++BrushesIdx ) + //{ + // // Cache the location and rotation. + // Location = Brushes[BrushesIdx]->GetActorLocation(); + // Rotation = Brushes[BrushesIdx]->GetActorRotation(); + + + // // Leave the actor's rotation but move it to origin so the Static Mesh will generate correctly. + // Brushes[BrushesIdx]->TeleportTo(Location - InPivotLocation, Rotation, false, true); + //} + + RebuildModelFromBrushes(OutModel, Brushes, true); + //GEditor->bspBuildFPolys(OutModel, true, 0); + + //if (0 < ConversionTempModel->Polys->Element.Num()) + //{ + // UStaticMesh* NewMesh = CreateStaticMeshFromBrush(Pkg, ObjName, NULL, ConversionTempModel); + // NewActor = FActorFactoryAssetProxy::AddActorForAsset( NewMesh ); + + // NewActor->Modify(); + + // NewActor->InvalidateLightingCache(); + // NewActor->PostEditChange(); + // NewActor->PostEditMove( true ); + // NewActor->Modify(); + // ULayersSubsystem* LayersSubsystem = GetEditorSubsystem(); + // LayersSubsystem->InitializeNewActorLayers(NewActor); + + // // Teleport the new actor to the old location but not the old rotation. The static mesh is built to the rotation already. + // NewActor->TeleportTo(InPivotLocation, FRotator(0.0f, 0.0f, 0.0f), false, true); + + // // Destroy the old brushes. + // for( int32 BrushIdx = 0; BrushIdx < InBrushesToConvert.Num(); ++BrushIdx ) + // { + // LayersSubsystem->DisassociateActorFromLayers(InBrushesToConvert[BrushIdx]); + // GWorld->EditorDestroyActor( InBrushesToConvert[BrushIdx], true ); + // } + + // // Notify the asset registry + // FAssetRegistryModule::AssetCreated(NewMesh); + //} + + //ConversionTempModel->EmptyModel(1, 1); + //RebuildAlteredBSP(); + //RedrawLevelEditingViewports(); + + //return NewActor; + + return OutModel; +} + +int UHCsgUtils::ComposeBrushCSG +( + ABrush* Actor, + UModel* Model, + uint32 PolyFlags, + EBrushType BrushType, + ECsgOper CSGOper, + bool bBuildBounds, + bool bMergePolys, + bool bReplaceNULLMaterialRefs, + bool bShowProgressBar, /*=true*/ + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + uint32 NotPolyFlags = 0; + int32 NumPolysFromBrush=0,i,j,ReallyBig; + UModel* Brush = Actor->Brush; + int32 Errors = 0; + + // Make sure we're in an acceptable state. + if( !Brush ) + { + return 0; + } + + // Non-solid and semisolid stuff can only be added. + if( BrushType != Brush_Add ) + { + NotPolyFlags |= (PF_Semisolid | PF_NotSolid); + } + + TempModel->EmptyModel(1,1); + + // Update status. + ReallyBig = (Brush->Polys->Element.Num() > 200) && bShowProgressBar; + if( ReallyBig ) + { + FText Description = NSLOCTEXT("UnrealEd", "PerformingCSGOperation", "Performing CSG operation"); + + if (BrushType != Brush_MAX) + { + if (BrushType == Brush_Add) + { + Description = NSLOCTEXT("UnrealEd", "AddingBrushToWorld", "Adding brush to world"); + } + else if (BrushType == Brush_Subtract) + { + Description = NSLOCTEXT("UnrealEd", "SubtractingBrushFromWorld", "Subtracting brush from world"); + } + } + else if (CSGOper != CSG_None) + { + if (CSGOper == CSG_Intersect) + { + Description = NSLOCTEXT("UnrealEd", "IntersectingBrushWithWorld", "Intersecting brush with world"); + } + else if (CSGOper == CSG_Deintersect) + { + Description = NSLOCTEXT("UnrealEd", "DeintersectingBrushWithWorld", "Deintersecting brush with world"); + } + } + + GWarn->BeginSlowTask( Description, true ); + // Transform original brush poly into same coordinate system as world + // so Bsp filtering operations make sense. + GWarn->StatusUpdate(0, 0, NSLOCTEXT("UnrealEd", "Transforming", "Transforming")); + } + + + //UMaterialInterface* SelectedMaterialInstance = GEditor->GetSelectedObjects()->GetTop(); + UMaterialInterface* SelectedMaterialInstance = nullptr; + + const FVector Scale = Actor->GetActorScale(); + const FRotator Rotation = Actor->GetActorRotation(); + const FVector Location = Actor->GetActorLocation(); + + const bool bIsMirrored = (Scale.X * Scale.Y * Scale.Z < 0.0f); + + // Cache actor transform which is used for the geometry being built + Brush->OwnerLocationWhenLastBuilt = Location; + Brush->OwnerRotationWhenLastBuilt = Rotation; + Brush->OwnerScaleWhenLastBuilt = Scale; + Brush->bCachedOwnerTransformValid = true; + + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly& CurrentPoly = Brush->Polys->Element[i]; + + // Set texture the first time. + if ( bReplaceNULLMaterialRefs ) + { + UMaterialInterface*& PolyMat = CurrentPoly.Material; + if ( !PolyMat || PolyMat == UMaterial::GetDefaultMaterial(MD_Surface) ) + { + PolyMat = SelectedMaterialInstance; + } + } + + // Get the brush poly. + FPoly DestEdPoly = CurrentPoly; + check(CurrentPoly.iLinkPolys->Element.Num()); + + // Set its backward brush link. + DestEdPoly.Actor = Actor; + DestEdPoly.iBrushPoly = i; + + // Update its flags. + DestEdPoly.PolyFlags = (DestEdPoly.PolyFlags | PolyFlags) & ~NotPolyFlags; + + // Set its internal link. + if (DestEdPoly.iLink == INDEX_NONE) + { + DestEdPoly.iLink = i; + } + + // Transform it. + DestEdPoly.Scale( Scale ); + DestEdPoly.Rotate( Rotation ); + DestEdPoly.Transform( Location ); + + // Reverse winding and normal if the parent brush is mirrored + if (bIsMirrored) + { + DestEdPoly.Reverse(); + DestEdPoly.CalcNormal(); + } + + // Add poly to the temp model. + new(TempModel->Polys->Element)FPoly( DestEdPoly ); + } + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringBrush", "Filtering brush") ); + + // Pass the brush polys through the world Bsp. + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + // Empty the brush. + Brush->EmptyModel(1,1); + + // Intersect and deintersect. + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly EdPoly = TempModel->Polys->Element[i]; + GModel = Brush; + // TODO: iLink / iLinkSurf in EdPoly / TempModel->Polys->Element[i] ? + BspFilterFPoly( CSGOper==CSG_Intersect ? &UHCsgUtils::IntersectBrushWithWorldFunc : &UHCsgUtils::DeIntersectBrushWithWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); + } + NumPolysFromBrush = Brush->Polys->Element.Num(); + } + else + { + // Add and subtract. + TMap SurfaceIndexRemap; + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly EdPoly = TempModel->Polys->Element[i]; + + // Mark the polygon as non-cut so that it won't be harmed unless it must + // be split, and set iLink so that BspAddNode will know to add its information + // if a node is added based on this poly. + EdPoly.PolyFlags &= ~(PF_EdCut); + const int32* SurfaceIndexPtr = SurfaceIndexRemap.Find(EdPoly.iLink); + if (SurfaceIndexPtr == nullptr) + { + const int32 NewSurfaceIndex = Model->Surfs.Num(); + SurfaceIndexRemap.Add(EdPoly.iLink, NewSurfaceIndex); + EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = NewSurfaceIndex; + } + else + { + EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = *SurfaceIndexPtr; + } + + // Filter brush through the world. + BspFilterFPoly( BrushType==Brush_Add ? &UHCsgUtils::AddBrushToWorldFunc : &UHCsgUtils::SubtractBrushFromWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); + } + } + if( Model->Nodes.Num() && !(PolyFlags & (PF_NotSolid | PF_Semisolid)) ) + { + // Quickly build a Bsp for the brush, tending to minimize splits rather than balance + // the tree. We only need the cutting planes, though the entire Bsp struct (polys and + // all) is built. + + /*FHBspPointsGrid* LevelModelPointsGrid = FHBspPointsGrid::GBspPoints; + FHBspPointsGrid* LevelModelVectorsGrid = FHBspPointsGrid::GBspVectors;*/ + + // For the bspBuild call, temporarily create a new pair of BspPointsGrids for the TempModel. + UHBspPointsGrid* TempBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* TempBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + /*FHBspPointsGrid::GBspPoints = BspPoints.Get(); + FHBspPointsGrid::GBspVectors = BspVectors.Get();*/ + + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "BuildingBSP", "Building BSP") ); + + FHBSPOps::bspBuild( TempModel, FHBSPOps::BSP_Lame, 0, 70, 1, 0, TempBspPoints, TempBspVectors ); + + // Reinstate the original BspPointsGrids used for building the level Model. + /*FHBspPointsGrid::GBspPoints = LevelModelPointsGrid; + FHBspPointsGrid::GBspVectors = LevelModelVectorsGrid;*/ + + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringWorld", "Filtering world") ); + GModel = Brush; + TempModel->BuildBound(); + + FSphere BrushSphere = TempModel->Bounds.GetSphere(); + FilterWorldThroughBrush( Model, TempModel, BrushType, CSGOper, 0, &BrushSphere, BspPoints, BspVectors); + } + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "AdjustingBrush", "Adjusting brush") ); + + // Link polys obtained from the original brush. + for( i=NumPolysFromBrush-1; i>=0; i-- ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + for( j=0; jiLink == Brush->Polys->Element[j].iLink ) + { + DestEdPoly->iLink = j; + break; + } + } + if( j >= i ) DestEdPoly->iLink = i; + } + + // Link polys obtained from the world. + for( i=Brush->Polys->Element.Num()-1; i>=NumPolysFromBrush; i-- ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + for( j=NumPolysFromBrush; jiLink == Brush->Polys->Element[j].iLink ) + { + DestEdPoly->iLink = j; + break; + } + } + if( j >= i ) DestEdPoly->iLink = i; + } + Brush->Linked = 1; + + // Detransform the obtained brush back into its original coordinate system. + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + DestEdPoly->Transform(-Location); + DestEdPoly->Rotate(Rotation.GetInverse()); + DestEdPoly->Scale(FVector(1.0f) / Scale); + DestEdPoly->Fix(); + DestEdPoly->Actor = NULL; + DestEdPoly->iBrushPoly = i; + } + } + + if( BrushType==Brush_Add || BrushType==Brush_Subtract ) + { + // Clean up nodes, reset node flags. + bspCleanup( Model ); + + // Rebuild bounding volumes. + if( bBuildBounds ) + { + FHBSPOps::bspBuildBounds( Model ); + } + } + + Brush->NumUniqueVertices = TempModel->Points.Num(); + // Release TempModel. + TempModel->EmptyModel(1,1); + + // Merge coplanars if needed. + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + if( ReallyBig ) + { + GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "Merging", "Merging") ); + } + if( bMergePolys ) + { + bspMergeCoplanars( Brush, 1, 0 ); + } + } + if( ReallyBig ) + { + GWarn->EndSlowTask(); + } + + return 1 + FHBSPOps::GErrors; +} + +/*---------------------------------------------------------------------------- + EdPoly building and compacting. +----------------------------------------------------------------------------*/ + +// +// Trys to merge two polygons. If they can be merged, replaces Poly1 and emptys Poly2 +// and returns 1. Otherwise, returns 0. +// +int UHCsgUtils::TryToMerge( FPoly *Poly1, FPoly *Poly2 ) +{ + // Find one overlapping point. + int32 Start1=0, Start2=0; + for( Start1=0; Start1Vertices.Num(); Start1++ ) + for( Start2=0; Start2Vertices.Num(); Start2++ ) + if( FVector::PointsAreSame(Poly1->Vertices[Start1], Poly2->Vertices[Start2]) ) + goto FoundOverlap; + return 0; + FoundOverlap: + + // Wrap around trying to merge. + int32 End1 = Start1; + int32 End2 = Start2; + int32 Test1 = Start1+1; if (Test1>=Poly1->Vertices.Num()) Test1 = 0; + int32 Test2 = Start2-1; if (Test2<0) Test2 = Poly2->Vertices.Num()-1; + if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) + { + End1 = Test1; + Start2 = Test2; + } + else + { + Test1 = Start1-1; if (Test1<0) Test1=Poly1->Vertices.Num()-1; + Test2 = Start2+1; if (Test2>=Poly2->Vertices.Num()) Test2=0; + if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) + { + Start1 = Test1; + End2 = Test2; + } + else return 0; + } + + // Build a new edpoly containing both polygons merged. + FPoly NewPoly = *Poly1; + NewPoly.Vertices.Empty(); + int32 Vertex = End1; + for( int32 i=0; iVertices.Num(); i++ ) + { + new(NewPoly.Vertices) FVector(Poly1->Vertices[Vertex]); + if( ++Vertex >= Poly1->Vertices.Num() ) + Vertex=0; + } + Vertex = End2; + for( int32 i=0; i<(Poly2->Vertices.Num()-2); i++ ) + { + if( ++Vertex >= Poly2->Vertices.Num() ) + Vertex=0; + new(NewPoly.Vertices) FVector(Poly2->Vertices[Vertex]); + } + + // Remove colinear vertices and check convexity. + if( NewPoly.RemoveColinears() ) + { + *Poly1 = NewPoly; + Poly2->Vertices.Empty(); + return true; + } + else return 0; +} + +// +// Merge all polygons in coplanar list that can be merged convexly. +// +void UHCsgUtils::MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ) +{ + int32 MergeAgain = 1; + while( MergeAgain ) + { + MergeAgain = 0; + for( int32 i=0; iPolys->Element[PolyList[i]]; + if( Poly1.Vertices.Num() > 0 ) + { + for( int32 j=i+1; jPolys->Element[PolyList[j]]; + if( Poly2.Vertices.Num() > 0 ) + { + if( TryToMerge( &Poly1, &Poly2 ) ) + MergeAgain=1; + } + } + } + } + } +} + +void UHCsgUtils::bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ) +{ + int32 OriginalNum = Model->Polys->Element.Num(); + + // Mark all polys as unprocessed. + for( int32 i=0; iPolys->Element.Num(); i++ ) + Model->Polys->Element[i].PolyFlags &= ~PF_EdProcessed; + + // Find matching coplanars and merge them. + FMemMark Mark(FMemStack::Get()); + int32* PolyList = new(FMemStack::Get(),Model->Polys->Element.Num())int32; + int32 n=0; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + FPoly* EdPoly = &Model->Polys->Element[i]; + if( EdPoly->Vertices.Num()>0 && !(EdPoly->PolyFlags & PF_EdProcessed) ) + { + int32 PolyCount = 0; + PolyList[PolyCount++] = i; + EdPoly->PolyFlags |= PF_EdProcessed; + for( int32 j=i+1; jPolys->Element.Num(); j++ ) + { + FPoly* OtherPoly = &Model->Polys->Element[j]; + if( OtherPoly->iLink == EdPoly->iLink && OtherPoly->Vertices.Num() ) + { + float Dist = (OtherPoly->Vertices[0] - EdPoly->Vertices[0]) | EdPoly->Normal; + if + ( Dist>-0.001 + && Dist<0.001 + && (OtherPoly->Normal|EdPoly->Normal)>0.9999 + && (MergeDisparateTextures + || ( FVector::PointsAreNear(OtherPoly->TextureU,EdPoly->TextureU,THRESH_VECTORS_ARE_NEAR) + && FVector::PointsAreNear(OtherPoly->TextureV,EdPoly->TextureV,THRESH_VECTORS_ARE_NEAR) ) ) ) + { + OtherPoly->PolyFlags |= PF_EdProcessed; + PolyList[PolyCount++] = j; + } + } + } + if( PolyCount > 1 ) + { + MergeCoplanars( Model, PolyList, PolyCount ); + n++; + } + } + } +// UE_LOG(LogEditorBsp, Log, TEXT("Found %i coplanar sets in %i"), n, Model->Polys->Element.Num() ); + Mark.Pop(); + + // Get rid of empty EdPolys while remapping iLinks. + FMemMark Mark2(FMemStack::Get()); + int32 j=0; + int32* Remap = new(FMemStack::Get(),Model->Polys->Element.Num())int32; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + if( Model->Polys->Element[i].Vertices.Num() ) + { + Remap[i] = j; + Model->Polys->Element[j] = Model->Polys->Element[i]; + j++; + } + } + Model->Polys->Element.RemoveAt( j, Model->Polys->Element.Num()-j ); + if( RemapLinks ) + { + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + if (Model->Polys->Element[i].iLink != INDEX_NONE) + { + CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*Remap'. + Model->Polys->Element[i].iLink = Remap[Model->Polys->Element[i].iLink]; + } + } + } +// UE_LOG(LogEditorBsp, Log, TEXT("BspMergeCoplanars reduced %i->%i"), OriginalNum, Model->Polys->Element.Num() ); + Mark2.Pop(); +} + +bool UHCsgUtils::polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly) +{ + FBspSurf &Surf = InModel->Surfs[iSurf]; + if( !Surf.Actor || !Surf.Actor->Brush->Polys->Element.IsValidIndex(Surf.iBrushPoly) ) + { + return false; + } + else + { + Poly = Surf.Actor->Brush->Polys->Element[Surf.iBrushPoly]; + return true; + } +} + +void UHCsgUtils::CleanupNodes( UModel *Model, int32 iNode, int32 iParent ) +{ + FBspNode *Node = &Model->Nodes[iNode]; + + // Transactionally empty vertices of tag-for-empty nodes. + Node->NodeFlags &= ~(NF_IsNew | NF_IsFront | NF_IsBack); + + // Recursively clean up front, back, and plane nodes. + if( Node->iFront != INDEX_NONE ) CleanupNodes( Model, Node->iFront, iNode ); + if( Node->iBack != INDEX_NONE ) CleanupNodes( Model, Node->iBack , iNode ); + if( Node->iPlane != INDEX_NONE ) CleanupNodes( Model, Node->iPlane, iNode ); + + // Reload Node since the recusive call aliases it. + Node = &Model->Nodes[iNode]; + + // If this is an empty node with a coplanar, replace it with the coplanar. + if( Node->NumVertices==0 && Node->iPlane!=INDEX_NONE ) + { + FBspNode* PlaneNode = &Model->Nodes[ Node->iPlane ]; + + // Stick our front, back, and parent nodes on the coplanar. + if( (Node->Plane | PlaneNode->Plane) >= 0.0 ) + { + PlaneNode->iFront = Node->iFront; + PlaneNode->iBack = Node->iBack; + } + else + { + PlaneNode->iFront = Node->iBack; + PlaneNode->iBack = Node->iFront; + } + + if( iParent == INDEX_NONE ) + { + // This node is the root. + *Node = *PlaneNode; // Replace root. + PlaneNode->NumVertices = 0; // Mark as unused. + } + else + { + // This is a child node. + FBspNode *ParentNode = &Model->Nodes[iParent]; + + if ( ParentNode->iFront == iNode ) ParentNode->iFront = Node->iPlane; + else if ( ParentNode->iBack == iNode ) ParentNode->iBack = Node->iPlane; + else if ( ParentNode->iPlane == iNode ) ParentNode->iPlane = Node->iPlane; + else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); + } + } + else if( Node->NumVertices == 0 && ( Node->iFront==INDEX_NONE || Node->iBack==INDEX_NONE ) ) + { + // Delete empty nodes with no fronts or backs. + // Replace empty nodes with only fronts. + // Replace empty nodes with only backs. + int32 iReplacementNode; + if ( Node->iFront != INDEX_NONE ) iReplacementNode = Node->iFront; + else if( Node->iBack != INDEX_NONE ) iReplacementNode = Node->iBack; + else iReplacementNode = INDEX_NONE; + + if( iParent == INDEX_NONE ) + { + // Root. + if( iReplacementNode == INDEX_NONE ) + { + Model->Nodes.Empty(); + } + else + { + *Node = Model->Nodes[iReplacementNode]; + } + } + else + { + // Regular node. + FBspNode *ParentNode = &Model->Nodes[iParent]; + + if ( ParentNode->iFront == iNode ) ParentNode->iFront = iReplacementNode; + else if( ParentNode->iBack == iNode ) ParentNode->iBack = iReplacementNode; + else if( ParentNode->iPlane == iNode ) ParentNode->iPlane = iReplacementNode; + else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); + } + } +} + + +void UHCsgUtils::bspCleanup( UModel *Model ) +{ + if( Model->Nodes.Num() > 0 ) + CleanupNodes( Model, 0, INDEX_NONE ); +} diff --git a/Source/HoudiniEngine/Private/HCsgUtils.h b/Source/HoudiniEngine/Private/HCsgUtils.h index a14957ddb..cc48c0b48 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.h +++ b/Source/HoudiniEngine/Private/HCsgUtils.h @@ -1,253 +1,278 @@ - -#pragma once - -#include "CoreMinimal.h" - -#include "HBSPOps.h" -#include "Engine/Brush.h" -#include "Model.h" - -#include "HCsgUtils.generated.h" - -//USTRUCT() -//struct FHCsgContext -//{ -// GENERATED_BODY() -// -// int32 Errors; -// -// -// UPROPERTY() -// class UModel* TempModel; -// -// UPROPERTY() -// class UModel* ConversionTempModel; -//}; - -// This HCsgUtils is one big fork of the codebase located UnrealEd/Private/EditorBsp.cpp. -// The main purpose was to remove parts of the code that store state in global/static variables as well -// as dependency on editor state (such as retrieving selected brushes). -UCLASS() -class HOUDINIENGINE_API UHCsgUtils : public UObject -{ - GENERATED_BODY() -public: - - UHCsgUtils(); - - /** - * Builds up a model from a set of brushes. Used by RebuildLevel. - * - * @param Model The model to be rebuilt. - * @param bSelectedBrushesOnly Use all brushes in the current level or just the selected ones?. - * @param bTreatMovableBrushesAsStatic Treat moveable brushes as static?. - */ - static void RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic); - - /** - * Converts passed in brushes into a single static mesh actor. - * Note: This replaces all the brushes with a single actor. This actor will not be attached to anything unless a single brush was converted. - * - * @param InStaticMeshPackageName The name to save the brushes to. - * @param InBrushesToConvert A list of brushes being converted. - * - * @return Returns the newly created actor with the newly created static mesh. - */ - static UModel* BuildModelFromBrushes(TArray& Brushes); - - /** - * Forked version of UEditorEngine::bspBrushCSG() from UnrealEd/Private/EditorBsp.cpp. - * - * Apply the appropriate CSG operation required in order to compose the brush actor onto the given model. - * - * @param Actor The brush actor to apply. - * @param Model The model to apply the CSG operation to; typically the world's model. - * @param PolyFlags PolyFlags to set on brush's polys. - * @param BrushType The type of brush. - * @param CSGOper The CSG operation to perform. - * @param bBuildBounds If true, updates bounding volumes on Model for CSG_Add or CSG_Subtract operations. - * @param bMergePolys If true, coplanar polygons are merged for CSG_Intersect or CSG_Deintersect operations. - * @param bReplaceNULLMaterialRefs If true, replace NULL material references with a reference to the GB-selected material. - * @param bShowProgressBar If true, display progress bar for complex brushes - * @return 0 if nothing happened, 1 if the operation was error-free, or 1+N if N CSG errors occurred. - */ - int ComposeBrushCSG( - ABrush* Actor, - UModel* Model, - uint32 PolyFlags, - EBrushType BrushType, - ECsgOper CSGOper, - bool bBuildBounds, - bool bMergePolys, - bool bReplaceNULLMaterialRefs, - bool bShowProgressBar, /*=true*/ - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - -protected: - // - // Status of filtered polygons: - // - enum EPolyNodeFilter - { - F_OUTSIDE = 0, // Leaf is an exterior leaf (visible to viewers). - F_INSIDE = 1, // Leaf is an interior leaf (non-visible, hidden behind backface). - F_COPLANAR_OUTSIDE = 2, // Poly is coplanar and in the exterior (visible to viewers). - F_COPLANAR_INSIDE = 3, // Poly is coplanar and inside (invisible to viewers). - F_COSPATIAL_FACING_IN = 4, // Poly is coplanar, cospatial, and facing in. - F_COSPATIAL_FACING_OUT = 5, // Poly is coplanar, cospatial, and facing out. - }; - - - // - // Information used by FilterEdPoly. - // - class FCoplanarInfo - { - public: - int32 iOriginalNode; - int32 iBackNode; - int BackNodeOutside; - int FrontLeafOutside; - int ProcessingBack; - }; - - // - // Generic filter function called by BspFilterEdPolys. A and B are pointers - // to any integers that your specific routine requires (or NULL if not needed). - // - typedef void (UHCsgUtils::*BspFilterFunc) - ( - UModel* Model, - int32 iNode, - FPoly* EdPoly, - EPolyNodeFilter Leaf, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // State shared between bspBrushCSG and AddWorldToBrushFunc. These are very - // tightly tied into the function AddWorldToBrush, not for general use. - // - int32 GDiscarded; // Number of polys discarded and not added. - int32 GNode; // Node AddBrushToWorld is adding to. - int32 GLastCoplanar; // Last coplanar beneath GNode at start of AddWorldToBrush. - int32 GNumNodes; // Number of Bsp nodes at start of AddWorldToBrush. - - UPROPERTY() - UModel* GModel; // Level map Model we're adding to. - - UPROPERTY() - class UModel* TempModel; - - //// Globals removed from FBspPointsGrid - //UPROPERTY() - //UHBspPointsGrid* GBspPoints; - - //UPROPERTY() - //UHBspPointsGrid* GBspVectors; - - /*struct BspFilterOp { - void Apply(UHCsgUtils* Obj, UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) {}; - };*/ - - // - // Handle a piece of a polygon that was filtered to a leaf. - // - void FilterLeaf( - BspFilterFunc FilterFunc, - UModel* Model, - int32 iNode, - FPoly* EdPoly, - FCoplanarInfo CoplanarInfo, - int32 LeafOutside, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // Function to filter an EdPoly through the Bsp, calling a callback - // function for all chunks that fall into leaves. - // - void FilterEdPoly - ( - BspFilterFunc FilterFunc, - UModel *Model, - int32 iNode, - FPoly *EdPoly, - FCoplanarInfo CoplanarInfo, - int32 Outside, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // Regular entry into FilterEdPoly (so higher-level callers don't have to - // deal with unnecessary info). Filters starting at root. - // - void BspFilterFPoly - ( - BspFilterFunc FilterFunc, - UModel *Model, - FPoly *EdPoly, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - - int bspNodeToFPoly - ( - UModel* Model, - int32 iNode, - FPoly* EdPoly - ); - - - //---------------------------------------------------------------------------- - // World Filtering - //---------------------------------------------------------------------------- - - // - // Filter all relevant world polys through the brush. - // - void FilterWorldThroughBrush - ( - UModel* Model, - UModel* Brush, - EBrushType BrushType, - ECsgOper CSGOper, - int32 iNode, - FSphere* BrushSphere, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - //---------------------------------------------------------------------------- - // CSG leaf filter callbacks / operations. - // --------------------------------------------------------------------------- - void AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - - //---------------------------------------------------------------------------- - // Forked various functions located in: EditorBsp.cpp, EditorCsg.cpp - //---------------------------------------------------------------------------- - static int TryToMerge( FPoly *Poly1, FPoly *Poly2 ); - static void MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ); - void bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ); - bool polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly); - static void CleanupNodes( UModel *Model, int32 iNode, int32 iParent ); - void bspCleanup( UModel *Model ); - -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HBSPOps.h" +#include "Engine/Brush.h" +#include "Model.h" + +#include "HCsgUtils.generated.h" + +//USTRUCT() +//struct FHCsgContext +//{ +// GENERATED_BODY() +// +// int32 Errors; +// +// +// UPROPERTY() +// class UModel* TempModel; +// +// UPROPERTY() +// class UModel* ConversionTempModel; +//}; + +// This HCsgUtils is one big fork of the codebase located UnrealEd/Private/EditorBsp.cpp. +// The main purpose was to remove parts of the code that store state in global/static variables as well +// as dependency on editor state (such as retrieving selected brushes). +UCLASS() +class HOUDINIENGINE_API UHCsgUtils : public UObject +{ + GENERATED_BODY() +public: + + UHCsgUtils(); + + /** + * Builds up a model from a set of brushes. Used by RebuildLevel. + * + * @param Model The model to be rebuilt. + * @param bSelectedBrushesOnly Use all brushes in the current level or just the selected ones?. + * @param bTreatMovableBrushesAsStatic Treat moveable brushes as static?. + */ + static void RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic); + + /** + * Converts passed in brushes into a single static mesh actor. + * Note: This replaces all the brushes with a single actor. This actor will not be attached to anything unless a single brush was converted. + * + * @param InStaticMeshPackageName The name to save the brushes to. + * @param InBrushesToConvert A list of brushes being converted. + * + * @return Returns the newly created actor with the newly created static mesh. + */ + static UModel* BuildModelFromBrushes(TArray& Brushes); + + /** + * Forked version of UEditorEngine::bspBrushCSG() from UnrealEd/Private/EditorBsp.cpp. + * + * Apply the appropriate CSG operation required in order to compose the brush actor onto the given model. + * + * @param Actor The brush actor to apply. + * @param Model The model to apply the CSG operation to; typically the world's model. + * @param PolyFlags PolyFlags to set on brush's polys. + * @param BrushType The type of brush. + * @param CSGOper The CSG operation to perform. + * @param bBuildBounds If true, updates bounding volumes on Model for CSG_Add or CSG_Subtract operations. + * @param bMergePolys If true, coplanar polygons are merged for CSG_Intersect or CSG_Deintersect operations. + * @param bReplaceNULLMaterialRefs If true, replace NULL material references with a reference to the GB-selected material. + * @param bShowProgressBar If true, display progress bar for complex brushes + * @return 0 if nothing happened, 1 if the operation was error-free, or 1+N if N CSG errors occurred. + */ + int ComposeBrushCSG( + ABrush* Actor, + UModel* Model, + uint32 PolyFlags, + EBrushType BrushType, + ECsgOper CSGOper, + bool bBuildBounds, + bool bMergePolys, + bool bReplaceNULLMaterialRefs, + bool bShowProgressBar, /*=true*/ + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + +protected: + // + // Status of filtered polygons: + // + enum EPolyNodeFilter + { + F_OUTSIDE = 0, // Leaf is an exterior leaf (visible to viewers). + F_INSIDE = 1, // Leaf is an interior leaf (non-visible, hidden behind backface). + F_COPLANAR_OUTSIDE = 2, // Poly is coplanar and in the exterior (visible to viewers). + F_COPLANAR_INSIDE = 3, // Poly is coplanar and inside (invisible to viewers). + F_COSPATIAL_FACING_IN = 4, // Poly is coplanar, cospatial, and facing in. + F_COSPATIAL_FACING_OUT = 5, // Poly is coplanar, cospatial, and facing out. + }; + + + // + // Information used by FilterEdPoly. + // + class FCoplanarInfo + { + public: + int32 iOriginalNode; + int32 iBackNode; + int BackNodeOutside; + int FrontLeafOutside; + int ProcessingBack; + }; + + // + // Generic filter function called by BspFilterEdPolys. A and B are pointers + // to any integers that your specific routine requires (or NULL if not needed). + // + typedef void (UHCsgUtils::*BspFilterFunc) + ( + UModel* Model, + int32 iNode, + FPoly* EdPoly, + EPolyNodeFilter Leaf, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // State shared between bspBrushCSG and AddWorldToBrushFunc. These are very + // tightly tied into the function AddWorldToBrush, not for general use. + // + int32 GDiscarded; // Number of polys discarded and not added. + int32 GNode; // Node AddBrushToWorld is adding to. + int32 GLastCoplanar; // Last coplanar beneath GNode at start of AddWorldToBrush. + int32 GNumNodes; // Number of Bsp nodes at start of AddWorldToBrush. + + UPROPERTY() + UModel* GModel; // Level map Model we're adding to. + + UPROPERTY() + class UModel* TempModel; + + //// Globals removed from FBspPointsGrid + //UPROPERTY() + //UHBspPointsGrid* GBspPoints; + + //UPROPERTY() + //UHBspPointsGrid* GBspVectors; + + /*struct BspFilterOp { + void Apply(UHCsgUtils* Obj, UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) {}; + };*/ + + // + // Handle a piece of a polygon that was filtered to a leaf. + // + void FilterLeaf( + BspFilterFunc FilterFunc, + UModel* Model, + int32 iNode, + FPoly* EdPoly, + FCoplanarInfo CoplanarInfo, + int32 LeafOutside, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // Function to filter an EdPoly through the Bsp, calling a callback + // function for all chunks that fall into leaves. + // + void FilterEdPoly + ( + BspFilterFunc FilterFunc, + UModel *Model, + int32 iNode, + FPoly *EdPoly, + FCoplanarInfo CoplanarInfo, + int32 Outside, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // Regular entry into FilterEdPoly (so higher-level callers don't have to + // deal with unnecessary info). Filters starting at root. + // + void BspFilterFPoly + ( + BspFilterFunc FilterFunc, + UModel *Model, + FPoly *EdPoly, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + + int bspNodeToFPoly + ( + UModel* Model, + int32 iNode, + FPoly* EdPoly + ); + + + //---------------------------------------------------------------------------- + // World Filtering + //---------------------------------------------------------------------------- + + // + // Filter all relevant world polys through the brush. + // + void FilterWorldThroughBrush + ( + UModel* Model, + UModel* Brush, + EBrushType BrushType, + ECsgOper CSGOper, + int32 iNode, + FSphere* BrushSphere, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + //---------------------------------------------------------------------------- + // CSG leaf filter callbacks / operations. + // --------------------------------------------------------------------------- + void AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + + //---------------------------------------------------------------------------- + // Forked various functions located in: EditorBsp.cpp, EditorCsg.cpp + //---------------------------------------------------------------------------- + static int TryToMerge( FPoly *Poly1, FPoly *Poly2 ); + static void MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ); + void bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ); + bool polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly); + static void CleanupNodes( UModel *Model, int32 iNode, int32 iParent ); + void bspCleanup( UModel *Model ); + +}; + diff --git a/Source/HoudiniEngine/Private/HoudiniApi.cpp b/Source/HoudiniEngine/Private/HoudiniApi.cpp index 727e30bb4..71a183d9e 100644 --- a/Source/HoudiniEngine/Private/HoudiniApi.cpp +++ b/Source/HoudiniEngine/Private/HoudiniApi.cpp @@ -1,3771 +1,3771 @@ -/* - * Copyright (c) <2020> 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. - * - * - * COMMENTS: - * This file is generated. Do not modify directly. - */ - -#include "HoudiniApi.h" -#include "HoudiniEnginePrivatePCH.h" - - -FHoudiniApi::AddAttributeFuncPtr -FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; - -FHoudiniApi::AddGroupFuncPtr -FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; - -FHoudiniApi::AssetInfo_CreateFuncPtr -FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; - -FHoudiniApi::AssetInfo_InitFuncPtr -FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; - -FHoudiniApi::AttributeInfo_CreateFuncPtr -FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; - -FHoudiniApi::AttributeInfo_InitFuncPtr -FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; - -FHoudiniApi::BindCustomImplementationFuncPtr -FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; - -FHoudiniApi::CancelPDGCookFuncPtr -FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; - -FHoudiniApi::CheckForSpecificErrorsFuncPtr -FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; - -FHoudiniApi::CleanupFuncPtr -FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; - -FHoudiniApi::ClearConnectionErrorFuncPtr -FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; - -FHoudiniApi::CloseSessionFuncPtr -FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; - -FHoudiniApi::CommitGeoFuncPtr -FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; - -FHoudiniApi::CommitWorkitemsFuncPtr -FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; - -FHoudiniApi::ComposeChildNodeListFuncPtr -FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; - -FHoudiniApi::ComposeNodeCookResultFuncPtr -FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; - -FHoudiniApi::ComposeObjectListFuncPtr -FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; - -FHoudiniApi::ConnectNodeInputFuncPtr -FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; - -FHoudiniApi::ConvertMatrixToEulerFuncPtr -FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; - -FHoudiniApi::ConvertMatrixToQuatFuncPtr -FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; - -FHoudiniApi::ConvertTransformFuncPtr -FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; - -FHoudiniApi::ConvertTransformEulerToMatrixFuncPtr -FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; - -FHoudiniApi::ConvertTransformQuatToMatrixFuncPtr -FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; - -FHoudiniApi::CookNodeFuncPtr -FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; - -FHoudiniApi::CookOptions_AreEqualFuncPtr -FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; - -FHoudiniApi::CookOptions_CreateFuncPtr -FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; - -FHoudiniApi::CookOptions_InitFuncPtr -FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; - -FHoudiniApi::CookPDGFuncPtr -FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; - -FHoudiniApi::CreateCustomSessionFuncPtr -FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; - -FHoudiniApi::CreateHeightFieldInputFuncPtr -FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; - -FHoudiniApi::CreateHeightfieldInputVolumeNodeFuncPtr -FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; - -FHoudiniApi::CreateInProcessSessionFuncPtr -FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; - -FHoudiniApi::CreateInputNodeFuncPtr -FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; - -FHoudiniApi::CreateNodeFuncPtr -FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; - -FHoudiniApi::CreateThriftNamedPipeSessionFuncPtr -FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; - -FHoudiniApi::CreateThriftSocketSessionFuncPtr -FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; - -FHoudiniApi::CreateWorkitemFuncPtr -FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; - -FHoudiniApi::CurveInfo_CreateFuncPtr -FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; - -FHoudiniApi::CurveInfo_InitFuncPtr -FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; - -FHoudiniApi::DeleteAttributeFuncPtr -FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; - -FHoudiniApi::DeleteGroupFuncPtr -FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; - -FHoudiniApi::DeleteNodeFuncPtr -FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; - -FHoudiniApi::DirtyPDGNodeFuncPtr -FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; - -FHoudiniApi::DisconnectNodeInputFuncPtr -FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; - -FHoudiniApi::DisconnectNodeOutputsAtFuncPtr -FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; - -FHoudiniApi::ExtractImageToFileFuncPtr -FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; - -FHoudiniApi::ExtractImageToMemoryFuncPtr -FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; - -FHoudiniApi::GeoInfo_CreateFuncPtr -FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; - -FHoudiniApi::GeoInfo_GetGroupCountByTypeFuncPtr -FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; - -FHoudiniApi::GeoInfo_InitFuncPtr -FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; - -FHoudiniApi::GetActiveCacheCountFuncPtr -FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; - -FHoudiniApi::GetActiveCacheNamesFuncPtr -FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmCountsFuncPtr -FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmInfosFuncPtr -FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmValuesFuncPtr -FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; - -FHoudiniApi::GetAssetInfoFuncPtr -FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; - -FHoudiniApi::GetAttributeFloat64ArrayDataFuncPtr -FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeFloat64DataFuncPtr -FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; - -FHoudiniApi::GetAttributeFloatArrayDataFuncPtr -FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; - -FHoudiniApi::GetAttributeFloatDataFuncPtr -FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; - -FHoudiniApi::GetAttributeInfoFuncPtr -FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; - -FHoudiniApi::GetAttributeInt64ArrayDataFuncPtr -FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeInt64DataFuncPtr -FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; - -FHoudiniApi::GetAttributeIntArrayDataFuncPtr -FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; - -FHoudiniApi::GetAttributeIntDataFuncPtr -FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; - -FHoudiniApi::GetAttributeNamesFuncPtr -FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; - -FHoudiniApi::GetAttributeStringArrayDataFuncPtr -FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; - -FHoudiniApi::GetAttributeStringDataFuncPtr -FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; - -FHoudiniApi::GetAvailableAssetCountFuncPtr -FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; - -FHoudiniApi::GetAvailableAssetsFuncPtr -FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; - -FHoudiniApi::GetBoxInfoFuncPtr -FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; - -FHoudiniApi::GetCachePropertyFuncPtr -FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; - -FHoudiniApi::GetComposedChildNodeListFuncPtr -FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; - -FHoudiniApi::GetComposedNodeCookResultFuncPtr -FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; - -FHoudiniApi::GetComposedObjectListFuncPtr -FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; - -FHoudiniApi::GetComposedObjectTransformsFuncPtr -FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; - -FHoudiniApi::GetConnectionErrorFuncPtr -FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; - -FHoudiniApi::GetConnectionErrorLengthFuncPtr -FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; - -FHoudiniApi::GetCookingCurrentCountFuncPtr -FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; - -FHoudiniApi::GetCookingTotalCountFuncPtr -FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; - -FHoudiniApi::GetCurveCountsFuncPtr -FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; - -FHoudiniApi::GetCurveInfoFuncPtr -FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; - -FHoudiniApi::GetCurveKnotsFuncPtr -FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; - -FHoudiniApi::GetCurveOrdersFuncPtr -FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; - -FHoudiniApi::GetDisplayGeoInfoFuncPtr -FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; - -FHoudiniApi::GetEnvIntFuncPtr -FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; - -FHoudiniApi::GetFaceCountsFuncPtr -FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; - -FHoudiniApi::GetFirstVolumeTileFuncPtr -FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; - -FHoudiniApi::GetGeoInfoFuncPtr -FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; - -FHoudiniApi::GetGeoSizeFuncPtr -FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; - -FHoudiniApi::GetGroupCountOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetGroupMembershipFuncPtr -FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; - -FHoudiniApi::GetGroupMembershipOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetGroupNamesFuncPtr -FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; - -FHoudiniApi::GetGroupNamesOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetHIPFileNodeCountFuncPtr -FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; - -FHoudiniApi::GetHIPFileNodeIdsFuncPtr -FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; - -FHoudiniApi::GetHandleBindingInfoFuncPtr -FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; - -FHoudiniApi::GetHandleInfoFuncPtr -FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; - -FHoudiniApi::GetHeightFieldDataFuncPtr -FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; - -FHoudiniApi::GetImageFilePathFuncPtr -FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; - -FHoudiniApi::GetImageInfoFuncPtr -FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; - -FHoudiniApi::GetImageMemoryBufferFuncPtr -FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; - -FHoudiniApi::GetImagePlaneCountFuncPtr -FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; - -FHoudiniApi::GetImagePlanesFuncPtr -FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; - -FHoudiniApi::GetInstanceTransformsOnPartFuncPtr -FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; - -FHoudiniApi::GetInstancedObjectIdsFuncPtr -FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; - -FHoudiniApi::GetInstancedPartIdsFuncPtr -FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; - -FHoudiniApi::GetInstancerPartTransformsFuncPtr -FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; - -FHoudiniApi::GetManagerNodeIdFuncPtr -FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; - -FHoudiniApi::GetMaterialInfoFuncPtr -FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; - -FHoudiniApi::GetMaterialNodeIdsOnFacesFuncPtr -FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; - -FHoudiniApi::GetNextVolumeTileFuncPtr -FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; - -FHoudiniApi::GetNodeInfoFuncPtr -FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; - -FHoudiniApi::GetNodeInputNameFuncPtr -FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; - -FHoudiniApi::GetNodeOutputNameFuncPtr -FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; - -FHoudiniApi::GetNodePathFuncPtr -FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; - -FHoudiniApi::GetNumWorkitemsFuncPtr -FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; - -FHoudiniApi::GetObjectInfoFuncPtr -FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; - -FHoudiniApi::GetObjectTransformFuncPtr -FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; - -FHoudiniApi::GetOutputNodeIdFuncPtr -FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; - -FHoudiniApi::GetPDGEventsFuncPtr -FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; - -FHoudiniApi::GetPDGGraphContextIdFuncPtr -FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; - -FHoudiniApi::GetPDGGraphContextsFuncPtr -FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; - -FHoudiniApi::GetPDGStateFuncPtr -FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; - -FHoudiniApi::GetParametersFuncPtr -FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; - -FHoudiniApi::GetParmChoiceListsFuncPtr -FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; - -FHoudiniApi::GetParmExpressionFuncPtr -FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; - -FHoudiniApi::GetParmFileFuncPtr -FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; - -FHoudiniApi::GetParmFloatValueFuncPtr -FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; - -FHoudiniApi::GetParmFloatValuesFuncPtr -FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; - -FHoudiniApi::GetParmIdFromNameFuncPtr -FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; - -FHoudiniApi::GetParmInfoFuncPtr -FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; - -FHoudiniApi::GetParmInfoFromNameFuncPtr -FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; - -FHoudiniApi::GetParmIntValueFuncPtr -FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; - -FHoudiniApi::GetParmIntValuesFuncPtr -FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; - -FHoudiniApi::GetParmNodeValueFuncPtr -FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; - -FHoudiniApi::GetParmStringValueFuncPtr -FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; - -FHoudiniApi::GetParmStringValuesFuncPtr -FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; - -FHoudiniApi::GetParmTagNameFuncPtr -FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; - -FHoudiniApi::GetParmTagValueFuncPtr -FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; - -FHoudiniApi::GetParmWithTagFuncPtr -FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; - -FHoudiniApi::GetPartInfoFuncPtr -FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; - -FHoudiniApi::GetPresetFuncPtr -FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; - -FHoudiniApi::GetPresetBufLengthFuncPtr -FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; - -FHoudiniApi::GetServerEnvIntFuncPtr -FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; - -FHoudiniApi::GetServerEnvStringFuncPtr -FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; - -FHoudiniApi::GetServerEnvVarCountFuncPtr -FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; - -FHoudiniApi::GetServerEnvVarListFuncPtr -FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; - -FHoudiniApi::GetSessionEnvIntFuncPtr -FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; - -FHoudiniApi::GetSessionSyncInfoFuncPtr -FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; - -FHoudiniApi::GetSphereInfoFuncPtr -FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; - -FHoudiniApi::GetStatusFuncPtr -FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; - -FHoudiniApi::GetStatusStringFuncPtr -FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; - -FHoudiniApi::GetStatusStringBufLengthFuncPtr -FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; - -FHoudiniApi::GetStringFuncPtr -FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; - -FHoudiniApi::GetStringBatchFuncPtr -FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; - -FHoudiniApi::GetStringBatchSizeFuncPtr -FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; - -FHoudiniApi::GetStringBufLengthFuncPtr -FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; - -FHoudiniApi::GetSupportedImageFileFormatCountFuncPtr -FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; - -FHoudiniApi::GetSupportedImageFileFormatsFuncPtr -FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; - -FHoudiniApi::GetTimeFuncPtr -FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; - -FHoudiniApi::GetTimelineOptionsFuncPtr -FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; - -FHoudiniApi::GetTotalCookCountFuncPtr -FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; - -FHoudiniApi::GetUseHoudiniTimeFuncPtr -FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; - -FHoudiniApi::GetVertexListFuncPtr -FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; - -FHoudiniApi::GetViewportFuncPtr -FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; - -FHoudiniApi::GetVolumeBoundsFuncPtr -FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; - -FHoudiniApi::GetVolumeInfoFuncPtr -FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; - -FHoudiniApi::GetVolumeTileFloatDataFuncPtr -FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; - -FHoudiniApi::GetVolumeTileIntDataFuncPtr -FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; - -FHoudiniApi::GetVolumeVisualInfoFuncPtr -FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; - -FHoudiniApi::GetVolumeVoxelFloatDataFuncPtr -FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; - -FHoudiniApi::GetVolumeVoxelIntDataFuncPtr -FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; - -FHoudiniApi::GetWorkitemDataLengthFuncPtr -FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; - -FHoudiniApi::GetWorkitemFloatDataFuncPtr -FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; - -FHoudiniApi::GetWorkitemInfoFuncPtr -FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; - -FHoudiniApi::GetWorkitemIntDataFuncPtr -FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; - -FHoudiniApi::GetWorkitemResultInfoFuncPtr -FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; - -FHoudiniApi::GetWorkitemStringDataFuncPtr -FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; - -FHoudiniApi::GetWorkitemsFuncPtr -FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; - -FHoudiniApi::HandleBindingInfo_CreateFuncPtr -FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; - -FHoudiniApi::HandleBindingInfo_InitFuncPtr -FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; - -FHoudiniApi::HandleInfo_CreateFuncPtr -FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; - -FHoudiniApi::HandleInfo_InitFuncPtr -FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; - -FHoudiniApi::ImageFileFormat_CreateFuncPtr -FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; - -FHoudiniApi::ImageFileFormat_InitFuncPtr -FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; - -FHoudiniApi::ImageInfo_CreateFuncPtr -FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; - -FHoudiniApi::ImageInfo_InitFuncPtr -FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; - -FHoudiniApi::InitializeFuncPtr -FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; - -FHoudiniApi::InsertMultiparmInstanceFuncPtr -FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; - -FHoudiniApi::InterruptFuncPtr -FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; - -FHoudiniApi::IsInitializedFuncPtr -FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; - -FHoudiniApi::IsNodeValidFuncPtr -FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; - -FHoudiniApi::IsSessionValidFuncPtr -FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; - -FHoudiniApi::Keyframe_CreateFuncPtr -FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; - -FHoudiniApi::Keyframe_InitFuncPtr -FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; - -FHoudiniApi::LoadAssetLibraryFromFileFuncPtr -FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; - -FHoudiniApi::LoadAssetLibraryFromMemoryFuncPtr -FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; - -FHoudiniApi::LoadGeoFromFileFuncPtr -FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; - -FHoudiniApi::LoadGeoFromMemoryFuncPtr -FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; - -FHoudiniApi::LoadHIPFileFuncPtr -FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; - -FHoudiniApi::LoadNodeFromFileFuncPtr -FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; - -FHoudiniApi::MaterialInfo_CreateFuncPtr -FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; - -FHoudiniApi::MaterialInfo_InitFuncPtr -FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; - -FHoudiniApi::MergeHIPFileFuncPtr -FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; - -FHoudiniApi::NodeInfo_CreateFuncPtr -FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; - -FHoudiniApi::NodeInfo_InitFuncPtr -FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; - -FHoudiniApi::ObjectInfo_CreateFuncPtr -FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; - -FHoudiniApi::ObjectInfo_InitFuncPtr -FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; - -FHoudiniApi::ParmChoiceInfo_CreateFuncPtr -FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; - -FHoudiniApi::ParmChoiceInfo_InitFuncPtr -FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; - -FHoudiniApi::ParmHasExpressionFuncPtr -FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; - -FHoudiniApi::ParmHasTagFuncPtr -FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; - -FHoudiniApi::ParmInfo_CreateFuncPtr -FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; - -FHoudiniApi::ParmInfo_GetFloatValueCountFuncPtr -FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; - -FHoudiniApi::ParmInfo_GetIntValueCountFuncPtr -FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; - -FHoudiniApi::ParmInfo_GetStringValueCountFuncPtr -FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; - -FHoudiniApi::ParmInfo_InitFuncPtr -FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; - -FHoudiniApi::ParmInfo_IsFloatFuncPtr -FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; - -FHoudiniApi::ParmInfo_IsIntFuncPtr -FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; - -FHoudiniApi::ParmInfo_IsNodeFuncPtr -FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; - -FHoudiniApi::ParmInfo_IsNonValueFuncPtr -FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; - -FHoudiniApi::ParmInfo_IsPathFuncPtr -FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; - -FHoudiniApi::ParmInfo_IsStringFuncPtr -FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; - -FHoudiniApi::PartInfo_CreateFuncPtr -FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; - -FHoudiniApi::PartInfo_GetAttributeCountByOwnerFuncPtr -FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; - -FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerFuncPtr -FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; - -FHoudiniApi::PartInfo_GetElementCountByGroupTypeFuncPtr -FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; - -FHoudiniApi::PartInfo_InitFuncPtr -FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; - -FHoudiniApi::PausePDGCookFuncPtr -FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; - -FHoudiniApi::PythonThreadInterpreterLockFuncPtr -FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; - -FHoudiniApi::QueryNodeInputFuncPtr -FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; - -FHoudiniApi::QueryNodeOutputConnectedCountFuncPtr -FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; - -FHoudiniApi::QueryNodeOutputConnectedNodesFuncPtr -FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; - -FHoudiniApi::RemoveCustomStringFuncPtr -FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; - -FHoudiniApi::RemoveMultiparmInstanceFuncPtr -FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; - -FHoudiniApi::RemoveParmExpressionFuncPtr -FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; - -FHoudiniApi::RenameNodeFuncPtr -FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; - -FHoudiniApi::RenderCOPToImageFuncPtr -FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; - -FHoudiniApi::RenderTextureToImageFuncPtr -FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; - -FHoudiniApi::ResetSimulationFuncPtr -FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; - -FHoudiniApi::RevertGeoFuncPtr -FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; - -FHoudiniApi::RevertParmToDefaultFuncPtr -FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; - -FHoudiniApi::RevertParmToDefaultsFuncPtr -FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; - -FHoudiniApi::SaveGeoToFileFuncPtr -FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; - -FHoudiniApi::SaveGeoToMemoryFuncPtr -FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; - -FHoudiniApi::SaveHIPFileFuncPtr -FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; - -FHoudiniApi::SaveNodeToFileFuncPtr -FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; - -FHoudiniApi::SessionSyncInfo_CreateFuncPtr -FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; - -FHoudiniApi::SetAnimCurveFuncPtr -FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; - -FHoudiniApi::SetAttributeFloat64DataFuncPtr -FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; - -FHoudiniApi::SetAttributeFloatDataFuncPtr -FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; - -FHoudiniApi::SetAttributeInt64DataFuncPtr -FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; - -FHoudiniApi::SetAttributeIntDataFuncPtr -FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; - -FHoudiniApi::SetAttributeStringDataFuncPtr -FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; - -FHoudiniApi::SetCachePropertyFuncPtr -FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; - -FHoudiniApi::SetCurveCountsFuncPtr -FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; - -FHoudiniApi::SetCurveInfoFuncPtr -FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; - -FHoudiniApi::SetCurveKnotsFuncPtr -FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; - -FHoudiniApi::SetCurveOrdersFuncPtr -FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; - -FHoudiniApi::SetCustomStringFuncPtr -FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; - -FHoudiniApi::SetFaceCountsFuncPtr -FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; - -FHoudiniApi::SetGroupMembershipFuncPtr -FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; - -FHoudiniApi::SetHeightFieldDataFuncPtr -FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; - -FHoudiniApi::SetImageInfoFuncPtr -FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; - -FHoudiniApi::SetNodeDisplayFuncPtr -FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; - -FHoudiniApi::SetObjectTransformFuncPtr -FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; - -FHoudiniApi::SetParmExpressionFuncPtr -FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; - -FHoudiniApi::SetParmFloatValueFuncPtr -FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; - -FHoudiniApi::SetParmFloatValuesFuncPtr -FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; - -FHoudiniApi::SetParmIntValueFuncPtr -FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; - -FHoudiniApi::SetParmIntValuesFuncPtr -FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; - -FHoudiniApi::SetParmNodeValueFuncPtr -FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; - -FHoudiniApi::SetParmStringValueFuncPtr -FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; - -FHoudiniApi::SetPartInfoFuncPtr -FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; - -FHoudiniApi::SetPresetFuncPtr -FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; - -FHoudiniApi::SetServerEnvIntFuncPtr -FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; - -FHoudiniApi::SetServerEnvStringFuncPtr -FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; - -FHoudiniApi::SetSessionSyncFuncPtr -FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; - -FHoudiniApi::SetSessionSyncInfoFuncPtr -FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; - -FHoudiniApi::SetTimeFuncPtr -FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; - -FHoudiniApi::SetTimelineOptionsFuncPtr -FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; - -FHoudiniApi::SetTransformAnimCurveFuncPtr -FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; - -FHoudiniApi::SetUseHoudiniTimeFuncPtr -FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; - -FHoudiniApi::SetVertexListFuncPtr -FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; - -FHoudiniApi::SetViewportFuncPtr -FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; - -FHoudiniApi::SetVolumeInfoFuncPtr -FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; - -FHoudiniApi::SetVolumeTileFloatDataFuncPtr -FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; - -FHoudiniApi::SetVolumeTileIntDataFuncPtr -FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; - -FHoudiniApi::SetVolumeVoxelFloatDataFuncPtr -FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; - -FHoudiniApi::SetVolumeVoxelIntDataFuncPtr -FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; - -FHoudiniApi::SetWorkitemFloatDataFuncPtr -FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; - -FHoudiniApi::SetWorkitemIntDataFuncPtr -FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; - -FHoudiniApi::SetWorkitemStringDataFuncPtr -FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; - -FHoudiniApi::StartThriftNamedPipeServerFuncPtr -FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; - -FHoudiniApi::StartThriftSocketServerFuncPtr -FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; - -FHoudiniApi::ThriftServerOptions_CreateFuncPtr -FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; - -FHoudiniApi::ThriftServerOptions_InitFuncPtr -FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; - -FHoudiniApi::TimelineOptions_CreateFuncPtr -FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; - -FHoudiniApi::TimelineOptions_InitFuncPtr -FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; - -FHoudiniApi::TransformEuler_CreateFuncPtr -FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; - -FHoudiniApi::TransformEuler_InitFuncPtr -FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; - -FHoudiniApi::Transform_CreateFuncPtr -FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; - -FHoudiniApi::Transform_InitFuncPtr -FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; - -FHoudiniApi::Viewport_CreateFuncPtr -FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; - -FHoudiniApi::VolumeInfo_CreateFuncPtr -FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; - -FHoudiniApi::VolumeInfo_InitFuncPtr -FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; - -FHoudiniApi::VolumeTileInfo_CreateFuncPtr -FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; - -FHoudiniApi::VolumeTileInfo_InitFuncPtr -FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; - - -void -FHoudiniApi::InitializeHAPI(void* LibraryHandle) -{ - if(!LibraryHandle) return; - - FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute")); - FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup")); - FHoudiniApi::AssetInfo_Create = (AssetInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Create")); - FHoudiniApi::AssetInfo_Init = (AssetInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Init")); - FHoudiniApi::AttributeInfo_Create = (AttributeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Create")); - FHoudiniApi::AttributeInfo_Init = (AttributeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Init")); - FHoudiniApi::BindCustomImplementation = (BindCustomImplementationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_BindCustomImplementation")); - FHoudiniApi::CancelPDGCook = (CancelPDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CancelPDGCook")); - FHoudiniApi::CheckForSpecificErrors = (CheckForSpecificErrorsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CheckForSpecificErrors")); - FHoudiniApi::Cleanup = (CleanupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Cleanup")); - FHoudiniApi::ClearConnectionError = (ClearConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ClearConnectionError")); - FHoudiniApi::CloseSession = (CloseSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CloseSession")); - FHoudiniApi::CommitGeo = (CommitGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitGeo")); - FHoudiniApi::CommitWorkitems = (CommitWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitWorkitems")); - FHoudiniApi::ComposeChildNodeList = (ComposeChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeChildNodeList")); - FHoudiniApi::ComposeNodeCookResult = (ComposeNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeNodeCookResult")); - FHoudiniApi::ComposeObjectList = (ComposeObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeObjectList")); - FHoudiniApi::ConnectNodeInput = (ConnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConnectNodeInput")); - FHoudiniApi::ConvertMatrixToEuler = (ConvertMatrixToEulerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToEuler")); - FHoudiniApi::ConvertMatrixToQuat = (ConvertMatrixToQuatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToQuat")); - FHoudiniApi::ConvertTransform = (ConvertTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransform")); - FHoudiniApi::ConvertTransformEulerToMatrix = (ConvertTransformEulerToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformEulerToMatrix")); - FHoudiniApi::ConvertTransformQuatToMatrix = (ConvertTransformQuatToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformQuatToMatrix")); - FHoudiniApi::CookNode = (CookNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookNode")); - FHoudiniApi::CookOptions_AreEqual = (CookOptions_AreEqualFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_AreEqual")); - FHoudiniApi::CookOptions_Create = (CookOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Create")); - FHoudiniApi::CookOptions_Init = (CookOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Init")); - FHoudiniApi::CookPDG = (CookPDGFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookPDG")); - FHoudiniApi::CreateCustomSession = (CreateCustomSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateCustomSession")); - FHoudiniApi::CreateHeightFieldInput = (CreateHeightFieldInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightFieldInput")); - FHoudiniApi::CreateHeightfieldInputVolumeNode = (CreateHeightfieldInputVolumeNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightfieldInputVolumeNode")); - FHoudiniApi::CreateInProcessSession = (CreateInProcessSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInProcessSession")); - FHoudiniApi::CreateInputNode = (CreateInputNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInputNode")); - FHoudiniApi::CreateNode = (CreateNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateNode")); - FHoudiniApi::CreateThriftNamedPipeSession = (CreateThriftNamedPipeSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftNamedPipeSession")); - FHoudiniApi::CreateThriftSocketSession = (CreateThriftSocketSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftSocketSession")); - FHoudiniApi::CreateWorkitem = (CreateWorkitemFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateWorkitem")); - FHoudiniApi::CurveInfo_Create = (CurveInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Create")); - FHoudiniApi::CurveInfo_Init = (CurveInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Init")); - FHoudiniApi::DeleteAttribute = (DeleteAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteAttribute")); - FHoudiniApi::DeleteGroup = (DeleteGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteGroup")); - FHoudiniApi::DeleteNode = (DeleteNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteNode")); - FHoudiniApi::DirtyPDGNode = (DirtyPDGNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DirtyPDGNode")); - FHoudiniApi::DisconnectNodeInput = (DisconnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeInput")); - FHoudiniApi::DisconnectNodeOutputsAt = (DisconnectNodeOutputsAtFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeOutputsAt")); - FHoudiniApi::ExtractImageToFile = (ExtractImageToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToFile")); - FHoudiniApi::ExtractImageToMemory = (ExtractImageToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToMemory")); - FHoudiniApi::GeoInfo_Create = (GeoInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Create")); - FHoudiniApi::GeoInfo_GetGroupCountByType = (GeoInfo_GetGroupCountByTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_GetGroupCountByType")); - FHoudiniApi::GeoInfo_Init = (GeoInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Init")); - FHoudiniApi::GetActiveCacheCount = (GetActiveCacheCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheCount")); - FHoudiniApi::GetActiveCacheNames = (GetActiveCacheNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheNames")); - FHoudiniApi::GetAssetDefinitionParmCounts = (GetAssetDefinitionParmCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmCounts")); - FHoudiniApi::GetAssetDefinitionParmInfos = (GetAssetDefinitionParmInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmInfos")); - FHoudiniApi::GetAssetDefinitionParmValues = (GetAssetDefinitionParmValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmValues")); - FHoudiniApi::GetAssetInfo = (GetAssetInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetInfo")); - FHoudiniApi::GetAttributeFloat64ArrayData = (GetAttributeFloat64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64ArrayData")); - FHoudiniApi::GetAttributeFloat64Data = (GetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64Data")); - FHoudiniApi::GetAttributeFloatArrayData = (GetAttributeFloatArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatArrayData")); - FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); - FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); - FHoudiniApi::GetAttributeInt64ArrayData = (GetAttributeInt64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64ArrayData")); - FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); - FHoudiniApi::GetAttributeIntArrayData = (GetAttributeIntArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntArrayData")); - FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); - FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); - FHoudiniApi::GetAttributeStringArrayData = (GetAttributeStringArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringArrayData")); - FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); - FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); - FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); - FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); - FHoudiniApi::GetCacheProperty = (GetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCacheProperty")); - FHoudiniApi::GetComposedChildNodeList = (GetComposedChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedChildNodeList")); - FHoudiniApi::GetComposedNodeCookResult = (GetComposedNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedNodeCookResult")); - FHoudiniApi::GetComposedObjectList = (GetComposedObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectList")); - FHoudiniApi::GetComposedObjectTransforms = (GetComposedObjectTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectTransforms")); - FHoudiniApi::GetConnectionError = (GetConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionError")); - FHoudiniApi::GetConnectionErrorLength = (GetConnectionErrorLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionErrorLength")); - FHoudiniApi::GetCookingCurrentCount = (GetCookingCurrentCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingCurrentCount")); - FHoudiniApi::GetCookingTotalCount = (GetCookingTotalCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingTotalCount")); - FHoudiniApi::GetCurveCounts = (GetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveCounts")); - FHoudiniApi::GetCurveInfo = (GetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveInfo")); - FHoudiniApi::GetCurveKnots = (GetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveKnots")); - FHoudiniApi::GetCurveOrders = (GetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveOrders")); - FHoudiniApi::GetDisplayGeoInfo = (GetDisplayGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetDisplayGeoInfo")); - FHoudiniApi::GetEnvInt = (GetEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEnvInt")); - FHoudiniApi::GetFaceCounts = (GetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFaceCounts")); - FHoudiniApi::GetFirstVolumeTile = (GetFirstVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFirstVolumeTile")); - FHoudiniApi::GetGeoInfo = (GetGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoInfo")); - FHoudiniApi::GetGeoSize = (GetGeoSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoSize")); - FHoudiniApi::GetGroupCountOnPackedInstancePart = (GetGroupCountOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupCountOnPackedInstancePart")); - FHoudiniApi::GetGroupMembership = (GetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembership")); - FHoudiniApi::GetGroupMembershipOnPackedInstancePart = (GetGroupMembershipOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembershipOnPackedInstancePart")); - FHoudiniApi::GetGroupNames = (GetGroupNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNames")); - FHoudiniApi::GetGroupNamesOnPackedInstancePart = (GetGroupNamesOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNamesOnPackedInstancePart")); - FHoudiniApi::GetHIPFileNodeCount = (GetHIPFileNodeCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeCount")); - FHoudiniApi::GetHIPFileNodeIds = (GetHIPFileNodeIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeIds")); - FHoudiniApi::GetHandleBindingInfo = (GetHandleBindingInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleBindingInfo")); - FHoudiniApi::GetHandleInfo = (GetHandleInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleInfo")); - FHoudiniApi::GetHeightFieldData = (GetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHeightFieldData")); - FHoudiniApi::GetImageFilePath = (GetImageFilePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageFilePath")); - FHoudiniApi::GetImageInfo = (GetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageInfo")); - FHoudiniApi::GetImageMemoryBuffer = (GetImageMemoryBufferFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageMemoryBuffer")); - FHoudiniApi::GetImagePlaneCount = (GetImagePlaneCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlaneCount")); - FHoudiniApi::GetImagePlanes = (GetImagePlanesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlanes")); - FHoudiniApi::GetInstanceTransformsOnPart = (GetInstanceTransformsOnPartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstanceTransformsOnPart")); - FHoudiniApi::GetInstancedObjectIds = (GetInstancedObjectIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedObjectIds")); - FHoudiniApi::GetInstancedPartIds = (GetInstancedPartIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedPartIds")); - FHoudiniApi::GetInstancerPartTransforms = (GetInstancerPartTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancerPartTransforms")); - FHoudiniApi::GetManagerNodeId = (GetManagerNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetManagerNodeId")); - FHoudiniApi::GetMaterialInfo = (GetMaterialInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialInfo")); - FHoudiniApi::GetMaterialNodeIdsOnFaces = (GetMaterialNodeIdsOnFacesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialNodeIdsOnFaces")); - FHoudiniApi::GetNextVolumeTile = (GetNextVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNextVolumeTile")); - FHoudiniApi::GetNodeInfo = (GetNodeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInfo")); - FHoudiniApi::GetNodeInputName = (GetNodeInputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInputName")); - FHoudiniApi::GetNodeOutputName = (GetNodeOutputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeOutputName")); - FHoudiniApi::GetNodePath = (GetNodePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodePath")); - FHoudiniApi::GetNumWorkitems = (GetNumWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNumWorkitems")); - FHoudiniApi::GetObjectInfo = (GetObjectInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectInfo")); - FHoudiniApi::GetObjectTransform = (GetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectTransform")); - FHoudiniApi::GetOutputNodeId = (GetOutputNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputNodeId")); - FHoudiniApi::GetPDGEvents = (GetPDGEventsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGEvents")); - FHoudiniApi::GetPDGGraphContextId = (GetPDGGraphContextIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContextId")); - FHoudiniApi::GetPDGGraphContexts = (GetPDGGraphContextsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContexts")); - FHoudiniApi::GetPDGState = (GetPDGStateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGState")); - FHoudiniApi::GetParameters = (GetParametersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParameters")); - FHoudiniApi::GetParmChoiceLists = (GetParmChoiceListsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmChoiceLists")); - FHoudiniApi::GetParmExpression = (GetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmExpression")); - FHoudiniApi::GetParmFile = (GetParmFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFile")); - FHoudiniApi::GetParmFloatValue = (GetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValue")); - FHoudiniApi::GetParmFloatValues = (GetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValues")); - FHoudiniApi::GetParmIdFromName = (GetParmIdFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIdFromName")); - FHoudiniApi::GetParmInfo = (GetParmInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfo")); - FHoudiniApi::GetParmInfoFromName = (GetParmInfoFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfoFromName")); - FHoudiniApi::GetParmIntValue = (GetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValue")); - FHoudiniApi::GetParmIntValues = (GetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValues")); - FHoudiniApi::GetParmNodeValue = (GetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmNodeValue")); - FHoudiniApi::GetParmStringValue = (GetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValue")); - FHoudiniApi::GetParmStringValues = (GetParmStringValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValues")); - FHoudiniApi::GetParmTagName = (GetParmTagNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagName")); - FHoudiniApi::GetParmTagValue = (GetParmTagValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagValue")); - FHoudiniApi::GetParmWithTag = (GetParmWithTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmWithTag")); - FHoudiniApi::GetPartInfo = (GetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPartInfo")); - FHoudiniApi::GetPreset = (GetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPreset")); - FHoudiniApi::GetPresetBufLength = (GetPresetBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPresetBufLength")); - FHoudiniApi::GetServerEnvInt = (GetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvInt")); - FHoudiniApi::GetServerEnvString = (GetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvString")); - FHoudiniApi::GetServerEnvVarCount = (GetServerEnvVarCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarCount")); - FHoudiniApi::GetServerEnvVarList = (GetServerEnvVarListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarList")); - FHoudiniApi::GetSessionEnvInt = (GetSessionEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionEnvInt")); - FHoudiniApi::GetSessionSyncInfo = (GetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionSyncInfo")); - FHoudiniApi::GetSphereInfo = (GetSphereInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSphereInfo")); - FHoudiniApi::GetStatus = (GetStatusFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatus")); - FHoudiniApi::GetStatusString = (GetStatusStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusString")); - FHoudiniApi::GetStatusStringBufLength = (GetStatusStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusStringBufLength")); - FHoudiniApi::GetString = (GetStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetString")); - FHoudiniApi::GetStringBatch = (GetStringBatchFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatch")); - FHoudiniApi::GetStringBatchSize = (GetStringBatchSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatchSize")); - FHoudiniApi::GetStringBufLength = (GetStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBufLength")); - FHoudiniApi::GetSupportedImageFileFormatCount = (GetSupportedImageFileFormatCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormatCount")); - FHoudiniApi::GetSupportedImageFileFormats = (GetSupportedImageFileFormatsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormats")); - FHoudiniApi::GetTime = (GetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTime")); - FHoudiniApi::GetTimelineOptions = (GetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTimelineOptions")); - FHoudiniApi::GetTotalCookCount = (GetTotalCookCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTotalCookCount")); - FHoudiniApi::GetUseHoudiniTime = (GetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetUseHoudiniTime")); - FHoudiniApi::GetVertexList = (GetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVertexList")); - FHoudiniApi::GetViewport = (GetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetViewport")); - FHoudiniApi::GetVolumeBounds = (GetVolumeBoundsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeBounds")); - FHoudiniApi::GetVolumeInfo = (GetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeInfo")); - FHoudiniApi::GetVolumeTileFloatData = (GetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileFloatData")); - FHoudiniApi::GetVolumeTileIntData = (GetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileIntData")); - FHoudiniApi::GetVolumeVisualInfo = (GetVolumeVisualInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVisualInfo")); - FHoudiniApi::GetVolumeVoxelFloatData = (GetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelFloatData")); - FHoudiniApi::GetVolumeVoxelIntData = (GetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelIntData")); - FHoudiniApi::GetWorkitemDataLength = (GetWorkitemDataLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemDataLength")); - FHoudiniApi::GetWorkitemFloatData = (GetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemFloatData")); - FHoudiniApi::GetWorkitemInfo = (GetWorkitemInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemInfo")); - FHoudiniApi::GetWorkitemIntData = (GetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemIntData")); - FHoudiniApi::GetWorkitemResultInfo = (GetWorkitemResultInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemResultInfo")); - FHoudiniApi::GetWorkitemStringData = (GetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemStringData")); - FHoudiniApi::GetWorkitems = (GetWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitems")); - FHoudiniApi::HandleBindingInfo_Create = (HandleBindingInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Create")); - FHoudiniApi::HandleBindingInfo_Init = (HandleBindingInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Init")); - FHoudiniApi::HandleInfo_Create = (HandleInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Create")); - FHoudiniApi::HandleInfo_Init = (HandleInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Init")); - FHoudiniApi::ImageFileFormat_Create = (ImageFileFormat_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Create")); - FHoudiniApi::ImageFileFormat_Init = (ImageFileFormat_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Init")); - FHoudiniApi::ImageInfo_Create = (ImageInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Create")); - FHoudiniApi::ImageInfo_Init = (ImageInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Init")); - FHoudiniApi::Initialize = (InitializeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Initialize")); - FHoudiniApi::InsertMultiparmInstance = (InsertMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_InsertMultiparmInstance")); - FHoudiniApi::Interrupt = (InterruptFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Interrupt")); - FHoudiniApi::IsInitialized = (IsInitializedFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsInitialized")); - FHoudiniApi::IsNodeValid = (IsNodeValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsNodeValid")); - FHoudiniApi::IsSessionValid = (IsSessionValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsSessionValid")); - FHoudiniApi::Keyframe_Create = (Keyframe_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Create")); - FHoudiniApi::Keyframe_Init = (Keyframe_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Init")); - FHoudiniApi::LoadAssetLibraryFromFile = (LoadAssetLibraryFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromFile")); - FHoudiniApi::LoadAssetLibraryFromMemory = (LoadAssetLibraryFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromMemory")); - FHoudiniApi::LoadGeoFromFile = (LoadGeoFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromFile")); - FHoudiniApi::LoadGeoFromMemory = (LoadGeoFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromMemory")); - FHoudiniApi::LoadHIPFile = (LoadHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadHIPFile")); - FHoudiniApi::LoadNodeFromFile = (LoadNodeFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadNodeFromFile")); - FHoudiniApi::MaterialInfo_Create = (MaterialInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Create")); - FHoudiniApi::MaterialInfo_Init = (MaterialInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Init")); - FHoudiniApi::MergeHIPFile = (MergeHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MergeHIPFile")); - FHoudiniApi::NodeInfo_Create = (NodeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Create")); - FHoudiniApi::NodeInfo_Init = (NodeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Init")); - FHoudiniApi::ObjectInfo_Create = (ObjectInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Create")); - FHoudiniApi::ObjectInfo_Init = (ObjectInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Init")); - FHoudiniApi::ParmChoiceInfo_Create = (ParmChoiceInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Create")); - FHoudiniApi::ParmChoiceInfo_Init = (ParmChoiceInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Init")); - FHoudiniApi::ParmHasExpression = (ParmHasExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasExpression")); - FHoudiniApi::ParmHasTag = (ParmHasTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasTag")); - FHoudiniApi::ParmInfo_Create = (ParmInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Create")); - FHoudiniApi::ParmInfo_GetFloatValueCount = (ParmInfo_GetFloatValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetFloatValueCount")); - FHoudiniApi::ParmInfo_GetIntValueCount = (ParmInfo_GetIntValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetIntValueCount")); - FHoudiniApi::ParmInfo_GetStringValueCount = (ParmInfo_GetStringValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetStringValueCount")); - FHoudiniApi::ParmInfo_Init = (ParmInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Init")); - FHoudiniApi::ParmInfo_IsFloat = (ParmInfo_IsFloatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsFloat")); - FHoudiniApi::ParmInfo_IsInt = (ParmInfo_IsIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsInt")); - FHoudiniApi::ParmInfo_IsNode = (ParmInfo_IsNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNode")); - FHoudiniApi::ParmInfo_IsNonValue = (ParmInfo_IsNonValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNonValue")); - FHoudiniApi::ParmInfo_IsPath = (ParmInfo_IsPathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsPath")); - FHoudiniApi::ParmInfo_IsString = (ParmInfo_IsStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsString")); - FHoudiniApi::PartInfo_Create = (PartInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Create")); - FHoudiniApi::PartInfo_GetAttributeCountByOwner = (PartInfo_GetAttributeCountByOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetAttributeCountByOwner")); - FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = (PartInfo_GetElementCountByAttributeOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByAttributeOwner")); - FHoudiniApi::PartInfo_GetElementCountByGroupType = (PartInfo_GetElementCountByGroupTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByGroupType")); - FHoudiniApi::PartInfo_Init = (PartInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Init")); - FHoudiniApi::PausePDGCook = (PausePDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PausePDGCook")); - FHoudiniApi::PythonThreadInterpreterLock = (PythonThreadInterpreterLockFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PythonThreadInterpreterLock")); - FHoudiniApi::QueryNodeInput = (QueryNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeInput")); - FHoudiniApi::QueryNodeOutputConnectedCount = (QueryNodeOutputConnectedCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedCount")); - FHoudiniApi::QueryNodeOutputConnectedNodes = (QueryNodeOutputConnectedNodesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedNodes")); - FHoudiniApi::RemoveCustomString = (RemoveCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveCustomString")); - FHoudiniApi::RemoveMultiparmInstance = (RemoveMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveMultiparmInstance")); - FHoudiniApi::RemoveParmExpression = (RemoveParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveParmExpression")); - FHoudiniApi::RenameNode = (RenameNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenameNode")); - FHoudiniApi::RenderCOPToImage = (RenderCOPToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderCOPToImage")); - FHoudiniApi::RenderTextureToImage = (RenderTextureToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderTextureToImage")); - FHoudiniApi::ResetSimulation = (ResetSimulationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ResetSimulation")); - FHoudiniApi::RevertGeo = (RevertGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertGeo")); - FHoudiniApi::RevertParmToDefault = (RevertParmToDefaultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefault")); - FHoudiniApi::RevertParmToDefaults = (RevertParmToDefaultsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefaults")); - FHoudiniApi::SaveGeoToFile = (SaveGeoToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToFile")); - FHoudiniApi::SaveGeoToMemory = (SaveGeoToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToMemory")); - FHoudiniApi::SaveHIPFile = (SaveHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveHIPFile")); - FHoudiniApi::SaveNodeToFile = (SaveNodeToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveNodeToFile")); - FHoudiniApi::SessionSyncInfo_Create = (SessionSyncInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SessionSyncInfo_Create")); - FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); - FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); - FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); - FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); - FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); - FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); - FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); - FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); - FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); - FHoudiniApi::SetCurveKnots = (SetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveKnots")); - FHoudiniApi::SetCurveOrders = (SetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveOrders")); - FHoudiniApi::SetCustomString = (SetCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCustomString")); - FHoudiniApi::SetFaceCounts = (SetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetFaceCounts")); - FHoudiniApi::SetGroupMembership = (SetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetGroupMembership")); - FHoudiniApi::SetHeightFieldData = (SetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetHeightFieldData")); - FHoudiniApi::SetImageInfo = (SetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetImageInfo")); - FHoudiniApi::SetNodeDisplay = (SetNodeDisplayFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetNodeDisplay")); - FHoudiniApi::SetObjectTransform = (SetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetObjectTransform")); - FHoudiniApi::SetParmExpression = (SetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmExpression")); - FHoudiniApi::SetParmFloatValue = (SetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValue")); - FHoudiniApi::SetParmFloatValues = (SetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValues")); - FHoudiniApi::SetParmIntValue = (SetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValue")); - FHoudiniApi::SetParmIntValues = (SetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValues")); - FHoudiniApi::SetParmNodeValue = (SetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmNodeValue")); - FHoudiniApi::SetParmStringValue = (SetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmStringValue")); - FHoudiniApi::SetPartInfo = (SetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPartInfo")); - FHoudiniApi::SetPreset = (SetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPreset")); - FHoudiniApi::SetServerEnvInt = (SetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvInt")); - FHoudiniApi::SetServerEnvString = (SetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvString")); - FHoudiniApi::SetSessionSync = (SetSessionSyncFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSync")); - FHoudiniApi::SetSessionSyncInfo = (SetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSyncInfo")); - FHoudiniApi::SetTime = (SetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTime")); - FHoudiniApi::SetTimelineOptions = (SetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTimelineOptions")); - FHoudiniApi::SetTransformAnimCurve = (SetTransformAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTransformAnimCurve")); - FHoudiniApi::SetUseHoudiniTime = (SetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetUseHoudiniTime")); - FHoudiniApi::SetVertexList = (SetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVertexList")); - FHoudiniApi::SetViewport = (SetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetViewport")); - FHoudiniApi::SetVolumeInfo = (SetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeInfo")); - FHoudiniApi::SetVolumeTileFloatData = (SetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileFloatData")); - FHoudiniApi::SetVolumeTileIntData = (SetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileIntData")); - FHoudiniApi::SetVolumeVoxelFloatData = (SetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelFloatData")); - FHoudiniApi::SetVolumeVoxelIntData = (SetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelIntData")); - FHoudiniApi::SetWorkitemFloatData = (SetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemFloatData")); - FHoudiniApi::SetWorkitemIntData = (SetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemIntData")); - FHoudiniApi::SetWorkitemStringData = (SetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemStringData")); - FHoudiniApi::StartThriftNamedPipeServer = (StartThriftNamedPipeServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftNamedPipeServer")); - FHoudiniApi::StartThriftSocketServer = (StartThriftSocketServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftSocketServer")); - FHoudiniApi::ThriftServerOptions_Create = (ThriftServerOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Create")); - FHoudiniApi::ThriftServerOptions_Init = (ThriftServerOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Init")); - FHoudiniApi::TimelineOptions_Create = (TimelineOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Create")); - FHoudiniApi::TimelineOptions_Init = (TimelineOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Init")); - FHoudiniApi::TransformEuler_Create = (TransformEuler_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Create")); - FHoudiniApi::TransformEuler_Init = (TransformEuler_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Init")); - FHoudiniApi::Transform_Create = (Transform_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Create")); - FHoudiniApi::Transform_Init = (Transform_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Init")); - FHoudiniApi::Viewport_Create = (Viewport_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Viewport_Create")); - FHoudiniApi::VolumeInfo_Create = (VolumeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Create")); - FHoudiniApi::VolumeInfo_Init = (VolumeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Init")); - FHoudiniApi::VolumeTileInfo_Create = (VolumeTileInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Create")); - FHoudiniApi::VolumeTileInfo_Init = (VolumeTileInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Init")); -} - - -void -FHoudiniApi::FinalizeHAPI() -{ - FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; - FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; - FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; - FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; - FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; - FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; - FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; - FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; - FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; - FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; - FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; - FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; - FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; - FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; - FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; - FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; - FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; - FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; - FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; - FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; - FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; - FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; - FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; - FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; - FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; - FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; - FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; - FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; - FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; - FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; - FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; - FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; - FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; - FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; - FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; - FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; - FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; - FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; - FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; - FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; - FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; - FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; - FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; - FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; - FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; - FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; - FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; - FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; - FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; - FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; - FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; - FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; - FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; - FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; - FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; - FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; - FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; - FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; - FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; - FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; - FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; - FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; - FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; - FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; - FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; - FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; - FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; - FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; - FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; - FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; - FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; - FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; - FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; - FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; - FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; - FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; - FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; - FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; - FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; - FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; - FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; - FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; - FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; - FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; - FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; - FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; - FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; - FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; - FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; - FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; - FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; - FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; - FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; - FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; - FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; - FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; - FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; - FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; - FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; - FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; - FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; - FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; - FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; - FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; - FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; - FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; - FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; - FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; - FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; - FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; - FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; - FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; - FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; - FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; - FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; - FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; - FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; - FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; - FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; - FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; - FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; - FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; - FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; - FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; - FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; - FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; - FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; - FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; - FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; - FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; - FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; - FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; - FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; - FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; - FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; - FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; - FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; - FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; - FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; - FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; - FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; - FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; - FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; - FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; - FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; - FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; - FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; - FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; - FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; - FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; - FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; - FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; - FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; - FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; - FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; - FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; - FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; - FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; - FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; - FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; - FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; - FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; - FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; - FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; - FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; - FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; - FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; - FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; - FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; - FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; - FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; - FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; - FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; - FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; - FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; - FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; - FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; - FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; - FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; - FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; - FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; - FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; - FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; - FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; - FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; - FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; - FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; - FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; - FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; - FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; - FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; - FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; - FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; - FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; - FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; - FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; - FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; - FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; - FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; - FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; - FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; - FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; - FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; - FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; - FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; - FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; - FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; - FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; - FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; - FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; - FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; - FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; - FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; - FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; - FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; - FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; - FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; - FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; - FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; - FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; - FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; - FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; - FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; - FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; - FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; - FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; - FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; - FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; - FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; - FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; - FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; - FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; - FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; - FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; - FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; - FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; - FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; - FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; - FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; - FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; - FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; - FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; - FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; - FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; - FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; - FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; - FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; - FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; - FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; - FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; - FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; - FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; - FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; - FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; - FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; - FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; - FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; - FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; - FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; - FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; - FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; - FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; - FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; - FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; - FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; - FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; - FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; - FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; - FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; - FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; - FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; - FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; - FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; - FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; - FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; - FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; - FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; - FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; - FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; - FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; - FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; - FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; - FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; - FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; - FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; - FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; - FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; - FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; - FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; - FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; - FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; - FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; - FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; - FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; - FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; - FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; - FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; - FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; - FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; - FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; - FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; - FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; - FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; - FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; - FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; - FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; - FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; - FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; - FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; - FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; -} - - -bool -FHoudiniApi::IsHAPIInitialized() -{ - return ( FHoudiniApi::IsInitialized != &FHoudiniApi::IsInitializedEmptyStub ); -} - - -HAPI_Result -FHoudiniApi::AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_AssetInfo -FHoudiniApi::AssetInfo_CreateEmptyStub() -{ - return HAPI_AssetInfo(); -} - - -void -FHoudiniApi::AssetInfo_InitEmptyStub(HAPI_AssetInfo * in) -{ - return; -} - - -HAPI_AttributeInfo -FHoudiniApi::AttributeInfo_CreateEmptyStub() -{ - return HAPI_AttributeInfo(); -} - - -void -FHoudiniApi::AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CleanupEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ClearConnectionErrorEmptyStub() -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CloseSessionEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Bool -FHoudiniApi::CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right) -{ - return HAPI_Bool(); -} - - -HAPI_CookOptions -FHoudiniApi::CookOptions_CreateEmptyStub() -{ - return HAPI_CookOptions(); -} - - -void -FHoudiniApi::CookOptions_InitEmptyStub(HAPI_CookOptions * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateInProcessSessionEmptyStub(HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_CurveInfo -FHoudiniApi::CurveInfo_CreateEmptyStub() -{ - return HAPI_CurveInfo(); -} - - -void -FHoudiniApi::CurveInfo_InitEmptyStub(HAPI_CurveInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_GeoInfo -FHoudiniApi::GeoInfo_CreateEmptyStub() -{ - return HAPI_GeoInfo(); -} - - -int -FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type) -{ - return -1; -} - - -void -FHoudiniApi::GeoInfo_InitEmptyStub(HAPI_GeoInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetConnectionErrorLengthEmptyStub(int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTimeEmptyStub(const HAPI_Session * session, float * time) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_HandleBindingInfo -FHoudiniApi::HandleBindingInfo_CreateEmptyStub() -{ - return HAPI_HandleBindingInfo(); -} - - -void -FHoudiniApi::HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in) -{ - return; -} - - -HAPI_HandleInfo -FHoudiniApi::HandleInfo_CreateEmptyStub() -{ - return HAPI_HandleInfo(); -} - - -void -FHoudiniApi::HandleInfo_InitEmptyStub(HAPI_HandleInfo * in) -{ - return; -} - - -HAPI_ImageFileFormat -FHoudiniApi::ImageFileFormat_CreateEmptyStub() -{ - return HAPI_ImageFileFormat(); -} - - -void -FHoudiniApi::ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in) -{ - return; -} - - -HAPI_ImageInfo -FHoudiniApi::ImageInfo_CreateEmptyStub() -{ - return HAPI_ImageInfo(); -} - - -void -FHoudiniApi::ImageInfo_InitEmptyStub(HAPI_ImageInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::InterruptEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsInitializedEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsSessionValidEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Keyframe -FHoudiniApi::Keyframe_CreateEmptyStub() -{ - return HAPI_Keyframe(); -} - - -void -FHoudiniApi::Keyframe_InitEmptyStub(HAPI_Keyframe * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_MaterialInfo -FHoudiniApi::MaterialInfo_CreateEmptyStub() -{ - return HAPI_MaterialInfo(); -} - - -void -FHoudiniApi::MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_NodeInfo -FHoudiniApi::NodeInfo_CreateEmptyStub() -{ - return HAPI_NodeInfo(); -} - - -void -FHoudiniApi::NodeInfo_InitEmptyStub(HAPI_NodeInfo * in) -{ - return; -} - - -HAPI_ObjectInfo -FHoudiniApi::ObjectInfo_CreateEmptyStub() -{ - return HAPI_ObjectInfo(); -} - - -void -FHoudiniApi::ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in) -{ - return; -} - - -HAPI_ParmChoiceInfo -FHoudiniApi::ParmChoiceInfo_CreateEmptyStub() -{ - return HAPI_ParmChoiceInfo(); -} - - -void -FHoudiniApi::ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_ParmInfo -FHoudiniApi::ParmInfo_CreateEmptyStub() -{ - return HAPI_ParmInfo(); -} - - -int -FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in) -{ - return -1; -} - - -int -FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in) -{ - return -1; -} - - -int -FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in) -{ - return -1; -} - - -void -FHoudiniApi::ParmInfo_InitEmptyStub(HAPI_ParmInfo * in) -{ - return; -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_PartInfo -FHoudiniApi::PartInfo_CreateEmptyStub() -{ - return HAPI_PartInfo(); -} - - -int -FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) -{ - return -1; -} - - -int -FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) -{ - return -1; -} - - -int -FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type) -{ - return -1; -} - - -void -FHoudiniApi::PartInfo_InitEmptyStub(HAPI_PartInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_SessionSyncInfo -FHoudiniApi::SessionSyncInfo_CreateEmptyStub() -{ - return HAPI_SessionSyncInfo(); -} - - -HAPI_Result -FHoudiniApi::SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTimeEmptyStub(const HAPI_Session * session, float time) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_ThriftServerOptions -FHoudiniApi::ThriftServerOptions_CreateEmptyStub() -{ - return HAPI_ThriftServerOptions(); -} - - -void -FHoudiniApi::ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in) -{ - return; -} - - -HAPI_TimelineOptions -FHoudiniApi::TimelineOptions_CreateEmptyStub() -{ - return HAPI_TimelineOptions(); -} - - -void -FHoudiniApi::TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in) -{ - return; -} - - -HAPI_TransformEuler -FHoudiniApi::TransformEuler_CreateEmptyStub() -{ - return HAPI_TransformEuler(); -} - - -void -FHoudiniApi::TransformEuler_InitEmptyStub(HAPI_TransformEuler * in) -{ - return; -} - - -HAPI_Transform -FHoudiniApi::Transform_CreateEmptyStub() -{ - return HAPI_Transform(); -} - - -void -FHoudiniApi::Transform_InitEmptyStub(HAPI_Transform * in) -{ - return; -} - - -HAPI_Viewport -FHoudiniApi::Viewport_CreateEmptyStub() -{ - return HAPI_Viewport(); -} - - -HAPI_VolumeInfo -FHoudiniApi::VolumeInfo_CreateEmptyStub() -{ - return HAPI_VolumeInfo(); -} - - -void -FHoudiniApi::VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in) -{ - return; -} - - -HAPI_VolumeTileInfo -FHoudiniApi::VolumeTileInfo_CreateEmptyStub() -{ - return HAPI_VolumeTileInfo(); -} - - -void -FHoudiniApi::VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in) -{ - return; -} - - +/* + * Copyright (c) <2021> 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. + * + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +#include "HoudiniApi.h" +#include "HoudiniEnginePrivatePCH.h" + + +FHoudiniApi::AddAttributeFuncPtr +FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; + +FHoudiniApi::AddGroupFuncPtr +FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; + +FHoudiniApi::AssetInfo_CreateFuncPtr +FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; + +FHoudiniApi::AssetInfo_InitFuncPtr +FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; + +FHoudiniApi::AttributeInfo_CreateFuncPtr +FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; + +FHoudiniApi::AttributeInfo_InitFuncPtr +FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; + +FHoudiniApi::BindCustomImplementationFuncPtr +FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; + +FHoudiniApi::CancelPDGCookFuncPtr +FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; + +FHoudiniApi::CheckForSpecificErrorsFuncPtr +FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; + +FHoudiniApi::CleanupFuncPtr +FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; + +FHoudiniApi::ClearConnectionErrorFuncPtr +FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; + +FHoudiniApi::CloseSessionFuncPtr +FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; + +FHoudiniApi::CommitGeoFuncPtr +FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; + +FHoudiniApi::CommitWorkitemsFuncPtr +FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; + +FHoudiniApi::ComposeChildNodeListFuncPtr +FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; + +FHoudiniApi::ComposeNodeCookResultFuncPtr +FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; + +FHoudiniApi::ComposeObjectListFuncPtr +FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + +FHoudiniApi::ConnectNodeInputFuncPtr +FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; + +FHoudiniApi::ConvertMatrixToEulerFuncPtr +FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; + +FHoudiniApi::ConvertMatrixToQuatFuncPtr +FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; + +FHoudiniApi::ConvertTransformFuncPtr +FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; + +FHoudiniApi::ConvertTransformEulerToMatrixFuncPtr +FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; + +FHoudiniApi::ConvertTransformQuatToMatrixFuncPtr +FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; + +FHoudiniApi::CookNodeFuncPtr +FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; + +FHoudiniApi::CookOptions_AreEqualFuncPtr +FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; + +FHoudiniApi::CookOptions_CreateFuncPtr +FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; + +FHoudiniApi::CookOptions_InitFuncPtr +FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; + +FHoudiniApi::CookPDGFuncPtr +FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; + +FHoudiniApi::CreateCustomSessionFuncPtr +FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; + +FHoudiniApi::CreateHeightFieldInputFuncPtr +FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; + +FHoudiniApi::CreateHeightfieldInputVolumeNodeFuncPtr +FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; + +FHoudiniApi::CreateInProcessSessionFuncPtr +FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; + +FHoudiniApi::CreateInputNodeFuncPtr +FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; + +FHoudiniApi::CreateNodeFuncPtr +FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; + +FHoudiniApi::CreateThriftNamedPipeSessionFuncPtr +FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; + +FHoudiniApi::CreateThriftSocketSessionFuncPtr +FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; + +FHoudiniApi::CreateWorkitemFuncPtr +FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; + +FHoudiniApi::CurveInfo_CreateFuncPtr +FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; + +FHoudiniApi::CurveInfo_InitFuncPtr +FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; + +FHoudiniApi::DeleteAttributeFuncPtr +FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; + +FHoudiniApi::DeleteGroupFuncPtr +FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; + +FHoudiniApi::DeleteNodeFuncPtr +FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; + +FHoudiniApi::DirtyPDGNodeFuncPtr +FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; + +FHoudiniApi::DisconnectNodeInputFuncPtr +FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; + +FHoudiniApi::DisconnectNodeOutputsAtFuncPtr +FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; + +FHoudiniApi::ExtractImageToFileFuncPtr +FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; + +FHoudiniApi::ExtractImageToMemoryFuncPtr +FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; + +FHoudiniApi::GeoInfo_CreateFuncPtr +FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; + +FHoudiniApi::GeoInfo_GetGroupCountByTypeFuncPtr +FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; + +FHoudiniApi::GeoInfo_InitFuncPtr +FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; + +FHoudiniApi::GetActiveCacheCountFuncPtr +FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; + +FHoudiniApi::GetActiveCacheNamesFuncPtr +FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmCountsFuncPtr +FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmInfosFuncPtr +FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmValuesFuncPtr +FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; + +FHoudiniApi::GetAssetInfoFuncPtr +FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; + +FHoudiniApi::GetAttributeFloat64ArrayDataFuncPtr +FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeFloat64DataFuncPtr +FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; + +FHoudiniApi::GetAttributeFloatArrayDataFuncPtr +FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; + +FHoudiniApi::GetAttributeFloatDataFuncPtr +FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; + +FHoudiniApi::GetAttributeInfoFuncPtr +FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + +FHoudiniApi::GetAttributeInt64ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt64DataFuncPtr +FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + +FHoudiniApi::GetAttributeIntArrayDataFuncPtr +FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; + +FHoudiniApi::GetAttributeIntDataFuncPtr +FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; + +FHoudiniApi::GetAttributeNamesFuncPtr +FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; + +FHoudiniApi::GetAttributeStringArrayDataFuncPtr +FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; + +FHoudiniApi::GetAttributeStringDataFuncPtr +FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + +FHoudiniApi::GetAvailableAssetCountFuncPtr +FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; + +FHoudiniApi::GetAvailableAssetsFuncPtr +FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; + +FHoudiniApi::GetBoxInfoFuncPtr +FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; + +FHoudiniApi::GetCachePropertyFuncPtr +FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; + +FHoudiniApi::GetComposedChildNodeListFuncPtr +FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; + +FHoudiniApi::GetComposedNodeCookResultFuncPtr +FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; + +FHoudiniApi::GetComposedObjectListFuncPtr +FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; + +FHoudiniApi::GetComposedObjectTransformsFuncPtr +FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + +FHoudiniApi::GetConnectionErrorFuncPtr +FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; + +FHoudiniApi::GetConnectionErrorLengthFuncPtr +FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; + +FHoudiniApi::GetCookingCurrentCountFuncPtr +FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; + +FHoudiniApi::GetCookingTotalCountFuncPtr +FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; + +FHoudiniApi::GetCurveCountsFuncPtr +FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; + +FHoudiniApi::GetCurveInfoFuncPtr +FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; + +FHoudiniApi::GetCurveKnotsFuncPtr +FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; + +FHoudiniApi::GetCurveOrdersFuncPtr +FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; + +FHoudiniApi::GetDisplayGeoInfoFuncPtr +FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + +FHoudiniApi::GetEnvIntFuncPtr +FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; + +FHoudiniApi::GetFaceCountsFuncPtr +FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; + +FHoudiniApi::GetFirstVolumeTileFuncPtr +FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; + +FHoudiniApi::GetGeoInfoFuncPtr +FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; + +FHoudiniApi::GetGeoSizeFuncPtr +FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; + +FHoudiniApi::GetGroupCountOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetGroupMembershipFuncPtr +FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; + +FHoudiniApi::GetGroupMembershipOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetGroupNamesFuncPtr +FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; + +FHoudiniApi::GetGroupNamesOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetHIPFileNodeCountFuncPtr +FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; + +FHoudiniApi::GetHIPFileNodeIdsFuncPtr +FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; + +FHoudiniApi::GetHandleBindingInfoFuncPtr +FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; + +FHoudiniApi::GetHandleInfoFuncPtr +FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; + +FHoudiniApi::GetHeightFieldDataFuncPtr +FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; + +FHoudiniApi::GetImageFilePathFuncPtr +FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; + +FHoudiniApi::GetImageInfoFuncPtr +FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; + +FHoudiniApi::GetImageMemoryBufferFuncPtr +FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; + +FHoudiniApi::GetImagePlaneCountFuncPtr +FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; + +FHoudiniApi::GetImagePlanesFuncPtr +FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; + +FHoudiniApi::GetInstanceTransformsOnPartFuncPtr +FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; + +FHoudiniApi::GetInstancedObjectIdsFuncPtr +FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; + +FHoudiniApi::GetInstancedPartIdsFuncPtr +FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; + +FHoudiniApi::GetInstancerPartTransformsFuncPtr +FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; + +FHoudiniApi::GetManagerNodeIdFuncPtr +FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; + +FHoudiniApi::GetMaterialInfoFuncPtr +FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; + +FHoudiniApi::GetMaterialNodeIdsOnFacesFuncPtr +FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; + +FHoudiniApi::GetNextVolumeTileFuncPtr +FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; + +FHoudiniApi::GetNodeInfoFuncPtr +FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; + +FHoudiniApi::GetNodeInputNameFuncPtr +FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; + +FHoudiniApi::GetNodeOutputNameFuncPtr +FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; + +FHoudiniApi::GetNodePathFuncPtr +FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; + +FHoudiniApi::GetNumWorkitemsFuncPtr +FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; + +FHoudiniApi::GetObjectInfoFuncPtr +FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; + +FHoudiniApi::GetObjectTransformFuncPtr +FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + +FHoudiniApi::GetOutputNodeIdFuncPtr +FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; + +FHoudiniApi::GetPDGEventsFuncPtr +FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; + +FHoudiniApi::GetPDGGraphContextIdFuncPtr +FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; + +FHoudiniApi::GetPDGGraphContextsFuncPtr +FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; + +FHoudiniApi::GetPDGStateFuncPtr +FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; + +FHoudiniApi::GetParametersFuncPtr +FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; + +FHoudiniApi::GetParmChoiceListsFuncPtr +FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; + +FHoudiniApi::GetParmExpressionFuncPtr +FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; + +FHoudiniApi::GetParmFileFuncPtr +FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; + +FHoudiniApi::GetParmFloatValueFuncPtr +FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; + +FHoudiniApi::GetParmFloatValuesFuncPtr +FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; + +FHoudiniApi::GetParmIdFromNameFuncPtr +FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; + +FHoudiniApi::GetParmInfoFuncPtr +FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; + +FHoudiniApi::GetParmInfoFromNameFuncPtr +FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; + +FHoudiniApi::GetParmIntValueFuncPtr +FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; + +FHoudiniApi::GetParmIntValuesFuncPtr +FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; + +FHoudiniApi::GetParmNodeValueFuncPtr +FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; + +FHoudiniApi::GetParmStringValueFuncPtr +FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; + +FHoudiniApi::GetParmStringValuesFuncPtr +FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; + +FHoudiniApi::GetParmTagNameFuncPtr +FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; + +FHoudiniApi::GetParmTagValueFuncPtr +FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; + +FHoudiniApi::GetParmWithTagFuncPtr +FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; + +FHoudiniApi::GetPartInfoFuncPtr +FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; + +FHoudiniApi::GetPresetFuncPtr +FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; + +FHoudiniApi::GetPresetBufLengthFuncPtr +FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; + +FHoudiniApi::GetServerEnvIntFuncPtr +FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; + +FHoudiniApi::GetServerEnvStringFuncPtr +FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; + +FHoudiniApi::GetServerEnvVarCountFuncPtr +FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; + +FHoudiniApi::GetServerEnvVarListFuncPtr +FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; + +FHoudiniApi::GetSessionEnvIntFuncPtr +FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; + +FHoudiniApi::GetSessionSyncInfoFuncPtr +FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; + +FHoudiniApi::GetSphereInfoFuncPtr +FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; + +FHoudiniApi::GetStatusFuncPtr +FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; + +FHoudiniApi::GetStatusStringFuncPtr +FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; + +FHoudiniApi::GetStatusStringBufLengthFuncPtr +FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; + +FHoudiniApi::GetStringFuncPtr +FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; + +FHoudiniApi::GetStringBatchFuncPtr +FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; + +FHoudiniApi::GetStringBatchSizeFuncPtr +FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; + +FHoudiniApi::GetStringBufLengthFuncPtr +FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; + +FHoudiniApi::GetSupportedImageFileFormatCountFuncPtr +FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; + +FHoudiniApi::GetSupportedImageFileFormatsFuncPtr +FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; + +FHoudiniApi::GetTimeFuncPtr +FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; + +FHoudiniApi::GetTimelineOptionsFuncPtr +FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; + +FHoudiniApi::GetTotalCookCountFuncPtr +FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; + +FHoudiniApi::GetUseHoudiniTimeFuncPtr +FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; + +FHoudiniApi::GetVertexListFuncPtr +FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; + +FHoudiniApi::GetViewportFuncPtr +FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; + +FHoudiniApi::GetVolumeBoundsFuncPtr +FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; + +FHoudiniApi::GetVolumeInfoFuncPtr +FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; + +FHoudiniApi::GetVolumeTileFloatDataFuncPtr +FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; + +FHoudiniApi::GetVolumeTileIntDataFuncPtr +FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; + +FHoudiniApi::GetVolumeVisualInfoFuncPtr +FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; + +FHoudiniApi::GetVolumeVoxelFloatDataFuncPtr +FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; + +FHoudiniApi::GetVolumeVoxelIntDataFuncPtr +FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; + +FHoudiniApi::GetWorkitemDataLengthFuncPtr +FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; + +FHoudiniApi::GetWorkitemFloatDataFuncPtr +FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; + +FHoudiniApi::GetWorkitemInfoFuncPtr +FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; + +FHoudiniApi::GetWorkitemIntDataFuncPtr +FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; + +FHoudiniApi::GetWorkitemResultInfoFuncPtr +FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; + +FHoudiniApi::GetWorkitemStringDataFuncPtr +FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; + +FHoudiniApi::GetWorkitemsFuncPtr +FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; + +FHoudiniApi::HandleBindingInfo_CreateFuncPtr +FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; + +FHoudiniApi::HandleBindingInfo_InitFuncPtr +FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; + +FHoudiniApi::HandleInfo_CreateFuncPtr +FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; + +FHoudiniApi::HandleInfo_InitFuncPtr +FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; + +FHoudiniApi::ImageFileFormat_CreateFuncPtr +FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; + +FHoudiniApi::ImageFileFormat_InitFuncPtr +FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; + +FHoudiniApi::ImageInfo_CreateFuncPtr +FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; + +FHoudiniApi::ImageInfo_InitFuncPtr +FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; + +FHoudiniApi::InitializeFuncPtr +FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; + +FHoudiniApi::InsertMultiparmInstanceFuncPtr +FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; + +FHoudiniApi::InterruptFuncPtr +FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; + +FHoudiniApi::IsInitializedFuncPtr +FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; + +FHoudiniApi::IsNodeValidFuncPtr +FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; + +FHoudiniApi::IsSessionValidFuncPtr +FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; + +FHoudiniApi::Keyframe_CreateFuncPtr +FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; + +FHoudiniApi::Keyframe_InitFuncPtr +FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; + +FHoudiniApi::LoadAssetLibraryFromFileFuncPtr +FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; + +FHoudiniApi::LoadAssetLibraryFromMemoryFuncPtr +FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; + +FHoudiniApi::LoadGeoFromFileFuncPtr +FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; + +FHoudiniApi::LoadGeoFromMemoryFuncPtr +FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; + +FHoudiniApi::LoadHIPFileFuncPtr +FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; + +FHoudiniApi::LoadNodeFromFileFuncPtr +FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; + +FHoudiniApi::MaterialInfo_CreateFuncPtr +FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; + +FHoudiniApi::MaterialInfo_InitFuncPtr +FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; + +FHoudiniApi::MergeHIPFileFuncPtr +FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; + +FHoudiniApi::NodeInfo_CreateFuncPtr +FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; + +FHoudiniApi::NodeInfo_InitFuncPtr +FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; + +FHoudiniApi::ObjectInfo_CreateFuncPtr +FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; + +FHoudiniApi::ObjectInfo_InitFuncPtr +FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; + +FHoudiniApi::ParmChoiceInfo_CreateFuncPtr +FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; + +FHoudiniApi::ParmChoiceInfo_InitFuncPtr +FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; + +FHoudiniApi::ParmHasExpressionFuncPtr +FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; + +FHoudiniApi::ParmHasTagFuncPtr +FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; + +FHoudiniApi::ParmInfo_CreateFuncPtr +FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; + +FHoudiniApi::ParmInfo_GetFloatValueCountFuncPtr +FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; + +FHoudiniApi::ParmInfo_GetIntValueCountFuncPtr +FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; + +FHoudiniApi::ParmInfo_GetStringValueCountFuncPtr +FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; + +FHoudiniApi::ParmInfo_InitFuncPtr +FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; + +FHoudiniApi::ParmInfo_IsFloatFuncPtr +FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; + +FHoudiniApi::ParmInfo_IsIntFuncPtr +FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; + +FHoudiniApi::ParmInfo_IsNodeFuncPtr +FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; + +FHoudiniApi::ParmInfo_IsNonValueFuncPtr +FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; + +FHoudiniApi::ParmInfo_IsPathFuncPtr +FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; + +FHoudiniApi::ParmInfo_IsStringFuncPtr +FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; + +FHoudiniApi::PartInfo_CreateFuncPtr +FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; + +FHoudiniApi::PartInfo_GetAttributeCountByOwnerFuncPtr +FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; + +FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerFuncPtr +FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; + +FHoudiniApi::PartInfo_GetElementCountByGroupTypeFuncPtr +FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; + +FHoudiniApi::PartInfo_InitFuncPtr +FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; + +FHoudiniApi::PausePDGCookFuncPtr +FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; + +FHoudiniApi::PythonThreadInterpreterLockFuncPtr +FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; + +FHoudiniApi::QueryNodeInputFuncPtr +FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; + +FHoudiniApi::QueryNodeOutputConnectedCountFuncPtr +FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; + +FHoudiniApi::QueryNodeOutputConnectedNodesFuncPtr +FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; + +FHoudiniApi::RemoveCustomStringFuncPtr +FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; + +FHoudiniApi::RemoveMultiparmInstanceFuncPtr +FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; + +FHoudiniApi::RemoveParmExpressionFuncPtr +FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; + +FHoudiniApi::RenameNodeFuncPtr +FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; + +FHoudiniApi::RenderCOPToImageFuncPtr +FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; + +FHoudiniApi::RenderTextureToImageFuncPtr +FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; + +FHoudiniApi::ResetSimulationFuncPtr +FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; + +FHoudiniApi::RevertGeoFuncPtr +FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; + +FHoudiniApi::RevertParmToDefaultFuncPtr +FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; + +FHoudiniApi::RevertParmToDefaultsFuncPtr +FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; + +FHoudiniApi::SaveGeoToFileFuncPtr +FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; + +FHoudiniApi::SaveGeoToMemoryFuncPtr +FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; + +FHoudiniApi::SaveHIPFileFuncPtr +FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; + +FHoudiniApi::SaveNodeToFileFuncPtr +FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; + +FHoudiniApi::SessionSyncInfo_CreateFuncPtr +FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; + +FHoudiniApi::SetAnimCurveFuncPtr +FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; + +FHoudiniApi::SetAttributeFloat64DataFuncPtr +FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; + +FHoudiniApi::SetAttributeFloatDataFuncPtr +FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + +FHoudiniApi::SetAttributeInt64DataFuncPtr +FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + +FHoudiniApi::SetAttributeIntDataFuncPtr +FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; + +FHoudiniApi::SetAttributeStringDataFuncPtr +FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + +FHoudiniApi::SetCachePropertyFuncPtr +FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + +FHoudiniApi::SetCurveCountsFuncPtr +FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; + +FHoudiniApi::SetCurveInfoFuncPtr +FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; + +FHoudiniApi::SetCurveKnotsFuncPtr +FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; + +FHoudiniApi::SetCurveOrdersFuncPtr +FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; + +FHoudiniApi::SetCustomStringFuncPtr +FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; + +FHoudiniApi::SetFaceCountsFuncPtr +FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; + +FHoudiniApi::SetGroupMembershipFuncPtr +FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; + +FHoudiniApi::SetHeightFieldDataFuncPtr +FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; + +FHoudiniApi::SetImageInfoFuncPtr +FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; + +FHoudiniApi::SetNodeDisplayFuncPtr +FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; + +FHoudiniApi::SetObjectTransformFuncPtr +FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; + +FHoudiniApi::SetParmExpressionFuncPtr +FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; + +FHoudiniApi::SetParmFloatValueFuncPtr +FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; + +FHoudiniApi::SetParmFloatValuesFuncPtr +FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; + +FHoudiniApi::SetParmIntValueFuncPtr +FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; + +FHoudiniApi::SetParmIntValuesFuncPtr +FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; + +FHoudiniApi::SetParmNodeValueFuncPtr +FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; + +FHoudiniApi::SetParmStringValueFuncPtr +FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; + +FHoudiniApi::SetPartInfoFuncPtr +FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; + +FHoudiniApi::SetPresetFuncPtr +FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; + +FHoudiniApi::SetServerEnvIntFuncPtr +FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; + +FHoudiniApi::SetServerEnvStringFuncPtr +FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; + +FHoudiniApi::SetSessionSyncFuncPtr +FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; + +FHoudiniApi::SetSessionSyncInfoFuncPtr +FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; + +FHoudiniApi::SetTimeFuncPtr +FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; + +FHoudiniApi::SetTimelineOptionsFuncPtr +FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; + +FHoudiniApi::SetTransformAnimCurveFuncPtr +FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; + +FHoudiniApi::SetUseHoudiniTimeFuncPtr +FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; + +FHoudiniApi::SetVertexListFuncPtr +FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; + +FHoudiniApi::SetViewportFuncPtr +FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; + +FHoudiniApi::SetVolumeInfoFuncPtr +FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; + +FHoudiniApi::SetVolumeTileFloatDataFuncPtr +FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; + +FHoudiniApi::SetVolumeTileIntDataFuncPtr +FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; + +FHoudiniApi::SetVolumeVoxelFloatDataFuncPtr +FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; + +FHoudiniApi::SetVolumeVoxelIntDataFuncPtr +FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; + +FHoudiniApi::SetWorkitemFloatDataFuncPtr +FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; + +FHoudiniApi::SetWorkitemIntDataFuncPtr +FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; + +FHoudiniApi::SetWorkitemStringDataFuncPtr +FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; + +FHoudiniApi::StartThriftNamedPipeServerFuncPtr +FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; + +FHoudiniApi::StartThriftSocketServerFuncPtr +FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; + +FHoudiniApi::ThriftServerOptions_CreateFuncPtr +FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; + +FHoudiniApi::ThriftServerOptions_InitFuncPtr +FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; + +FHoudiniApi::TimelineOptions_CreateFuncPtr +FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; + +FHoudiniApi::TimelineOptions_InitFuncPtr +FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; + +FHoudiniApi::TransformEuler_CreateFuncPtr +FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; + +FHoudiniApi::TransformEuler_InitFuncPtr +FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; + +FHoudiniApi::Transform_CreateFuncPtr +FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; + +FHoudiniApi::Transform_InitFuncPtr +FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; + +FHoudiniApi::Viewport_CreateFuncPtr +FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; + +FHoudiniApi::VolumeInfo_CreateFuncPtr +FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; + +FHoudiniApi::VolumeInfo_InitFuncPtr +FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; + +FHoudiniApi::VolumeTileInfo_CreateFuncPtr +FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; + +FHoudiniApi::VolumeTileInfo_InitFuncPtr +FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; + + +void +FHoudiniApi::InitializeHAPI(void* LibraryHandle) +{ + if(!LibraryHandle) return; + + FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute")); + FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup")); + FHoudiniApi::AssetInfo_Create = (AssetInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Create")); + FHoudiniApi::AssetInfo_Init = (AssetInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Init")); + FHoudiniApi::AttributeInfo_Create = (AttributeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Create")); + FHoudiniApi::AttributeInfo_Init = (AttributeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Init")); + FHoudiniApi::BindCustomImplementation = (BindCustomImplementationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_BindCustomImplementation")); + FHoudiniApi::CancelPDGCook = (CancelPDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CancelPDGCook")); + FHoudiniApi::CheckForSpecificErrors = (CheckForSpecificErrorsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CheckForSpecificErrors")); + FHoudiniApi::Cleanup = (CleanupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Cleanup")); + FHoudiniApi::ClearConnectionError = (ClearConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ClearConnectionError")); + FHoudiniApi::CloseSession = (CloseSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CloseSession")); + FHoudiniApi::CommitGeo = (CommitGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitGeo")); + FHoudiniApi::CommitWorkitems = (CommitWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitWorkitems")); + FHoudiniApi::ComposeChildNodeList = (ComposeChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeChildNodeList")); + FHoudiniApi::ComposeNodeCookResult = (ComposeNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeNodeCookResult")); + FHoudiniApi::ComposeObjectList = (ComposeObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeObjectList")); + FHoudiniApi::ConnectNodeInput = (ConnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConnectNodeInput")); + FHoudiniApi::ConvertMatrixToEuler = (ConvertMatrixToEulerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToEuler")); + FHoudiniApi::ConvertMatrixToQuat = (ConvertMatrixToQuatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToQuat")); + FHoudiniApi::ConvertTransform = (ConvertTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransform")); + FHoudiniApi::ConvertTransformEulerToMatrix = (ConvertTransformEulerToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformEulerToMatrix")); + FHoudiniApi::ConvertTransformQuatToMatrix = (ConvertTransformQuatToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformQuatToMatrix")); + FHoudiniApi::CookNode = (CookNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookNode")); + FHoudiniApi::CookOptions_AreEqual = (CookOptions_AreEqualFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_AreEqual")); + FHoudiniApi::CookOptions_Create = (CookOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Create")); + FHoudiniApi::CookOptions_Init = (CookOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Init")); + FHoudiniApi::CookPDG = (CookPDGFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookPDG")); + FHoudiniApi::CreateCustomSession = (CreateCustomSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateCustomSession")); + FHoudiniApi::CreateHeightFieldInput = (CreateHeightFieldInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightFieldInput")); + FHoudiniApi::CreateHeightfieldInputVolumeNode = (CreateHeightfieldInputVolumeNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightfieldInputVolumeNode")); + FHoudiniApi::CreateInProcessSession = (CreateInProcessSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInProcessSession")); + FHoudiniApi::CreateInputNode = (CreateInputNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInputNode")); + FHoudiniApi::CreateNode = (CreateNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateNode")); + FHoudiniApi::CreateThriftNamedPipeSession = (CreateThriftNamedPipeSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftNamedPipeSession")); + FHoudiniApi::CreateThriftSocketSession = (CreateThriftSocketSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftSocketSession")); + FHoudiniApi::CreateWorkitem = (CreateWorkitemFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateWorkitem")); + FHoudiniApi::CurveInfo_Create = (CurveInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Create")); + FHoudiniApi::CurveInfo_Init = (CurveInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Init")); + FHoudiniApi::DeleteAttribute = (DeleteAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteAttribute")); + FHoudiniApi::DeleteGroup = (DeleteGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteGroup")); + FHoudiniApi::DeleteNode = (DeleteNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteNode")); + FHoudiniApi::DirtyPDGNode = (DirtyPDGNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DirtyPDGNode")); + FHoudiniApi::DisconnectNodeInput = (DisconnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeInput")); + FHoudiniApi::DisconnectNodeOutputsAt = (DisconnectNodeOutputsAtFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeOutputsAt")); + FHoudiniApi::ExtractImageToFile = (ExtractImageToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToFile")); + FHoudiniApi::ExtractImageToMemory = (ExtractImageToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToMemory")); + FHoudiniApi::GeoInfo_Create = (GeoInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Create")); + FHoudiniApi::GeoInfo_GetGroupCountByType = (GeoInfo_GetGroupCountByTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_GetGroupCountByType")); + FHoudiniApi::GeoInfo_Init = (GeoInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Init")); + FHoudiniApi::GetActiveCacheCount = (GetActiveCacheCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheCount")); + FHoudiniApi::GetActiveCacheNames = (GetActiveCacheNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheNames")); + FHoudiniApi::GetAssetDefinitionParmCounts = (GetAssetDefinitionParmCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmCounts")); + FHoudiniApi::GetAssetDefinitionParmInfos = (GetAssetDefinitionParmInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmInfos")); + FHoudiniApi::GetAssetDefinitionParmValues = (GetAssetDefinitionParmValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmValues")); + FHoudiniApi::GetAssetInfo = (GetAssetInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetInfo")); + FHoudiniApi::GetAttributeFloat64ArrayData = (GetAttributeFloat64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64ArrayData")); + FHoudiniApi::GetAttributeFloat64Data = (GetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64Data")); + FHoudiniApi::GetAttributeFloatArrayData = (GetAttributeFloatArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatArrayData")); + FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); + FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); + FHoudiniApi::GetAttributeInt64ArrayData = (GetAttributeInt64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64ArrayData")); + FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); + FHoudiniApi::GetAttributeIntArrayData = (GetAttributeIntArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntArrayData")); + FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); + FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); + FHoudiniApi::GetAttributeStringArrayData = (GetAttributeStringArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringArrayData")); + FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); + FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); + FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); + FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); + FHoudiniApi::GetCacheProperty = (GetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCacheProperty")); + FHoudiniApi::GetComposedChildNodeList = (GetComposedChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedChildNodeList")); + FHoudiniApi::GetComposedNodeCookResult = (GetComposedNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedNodeCookResult")); + FHoudiniApi::GetComposedObjectList = (GetComposedObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectList")); + FHoudiniApi::GetComposedObjectTransforms = (GetComposedObjectTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectTransforms")); + FHoudiniApi::GetConnectionError = (GetConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionError")); + FHoudiniApi::GetConnectionErrorLength = (GetConnectionErrorLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionErrorLength")); + FHoudiniApi::GetCookingCurrentCount = (GetCookingCurrentCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingCurrentCount")); + FHoudiniApi::GetCookingTotalCount = (GetCookingTotalCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingTotalCount")); + FHoudiniApi::GetCurveCounts = (GetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveCounts")); + FHoudiniApi::GetCurveInfo = (GetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveInfo")); + FHoudiniApi::GetCurveKnots = (GetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveKnots")); + FHoudiniApi::GetCurveOrders = (GetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveOrders")); + FHoudiniApi::GetDisplayGeoInfo = (GetDisplayGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetDisplayGeoInfo")); + FHoudiniApi::GetEnvInt = (GetEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEnvInt")); + FHoudiniApi::GetFaceCounts = (GetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFaceCounts")); + FHoudiniApi::GetFirstVolumeTile = (GetFirstVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFirstVolumeTile")); + FHoudiniApi::GetGeoInfo = (GetGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoInfo")); + FHoudiniApi::GetGeoSize = (GetGeoSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoSize")); + FHoudiniApi::GetGroupCountOnPackedInstancePart = (GetGroupCountOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupCountOnPackedInstancePart")); + FHoudiniApi::GetGroupMembership = (GetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembership")); + FHoudiniApi::GetGroupMembershipOnPackedInstancePart = (GetGroupMembershipOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembershipOnPackedInstancePart")); + FHoudiniApi::GetGroupNames = (GetGroupNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNames")); + FHoudiniApi::GetGroupNamesOnPackedInstancePart = (GetGroupNamesOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNamesOnPackedInstancePart")); + FHoudiniApi::GetHIPFileNodeCount = (GetHIPFileNodeCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeCount")); + FHoudiniApi::GetHIPFileNodeIds = (GetHIPFileNodeIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeIds")); + FHoudiniApi::GetHandleBindingInfo = (GetHandleBindingInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleBindingInfo")); + FHoudiniApi::GetHandleInfo = (GetHandleInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleInfo")); + FHoudiniApi::GetHeightFieldData = (GetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHeightFieldData")); + FHoudiniApi::GetImageFilePath = (GetImageFilePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageFilePath")); + FHoudiniApi::GetImageInfo = (GetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageInfo")); + FHoudiniApi::GetImageMemoryBuffer = (GetImageMemoryBufferFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageMemoryBuffer")); + FHoudiniApi::GetImagePlaneCount = (GetImagePlaneCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlaneCount")); + FHoudiniApi::GetImagePlanes = (GetImagePlanesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlanes")); + FHoudiniApi::GetInstanceTransformsOnPart = (GetInstanceTransformsOnPartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstanceTransformsOnPart")); + FHoudiniApi::GetInstancedObjectIds = (GetInstancedObjectIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedObjectIds")); + FHoudiniApi::GetInstancedPartIds = (GetInstancedPartIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedPartIds")); + FHoudiniApi::GetInstancerPartTransforms = (GetInstancerPartTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancerPartTransforms")); + FHoudiniApi::GetManagerNodeId = (GetManagerNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetManagerNodeId")); + FHoudiniApi::GetMaterialInfo = (GetMaterialInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialInfo")); + FHoudiniApi::GetMaterialNodeIdsOnFaces = (GetMaterialNodeIdsOnFacesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialNodeIdsOnFaces")); + FHoudiniApi::GetNextVolumeTile = (GetNextVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNextVolumeTile")); + FHoudiniApi::GetNodeInfo = (GetNodeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInfo")); + FHoudiniApi::GetNodeInputName = (GetNodeInputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInputName")); + FHoudiniApi::GetNodeOutputName = (GetNodeOutputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeOutputName")); + FHoudiniApi::GetNodePath = (GetNodePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodePath")); + FHoudiniApi::GetNumWorkitems = (GetNumWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNumWorkitems")); + FHoudiniApi::GetObjectInfo = (GetObjectInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectInfo")); + FHoudiniApi::GetObjectTransform = (GetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectTransform")); + FHoudiniApi::GetOutputNodeId = (GetOutputNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputNodeId")); + FHoudiniApi::GetPDGEvents = (GetPDGEventsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGEvents")); + FHoudiniApi::GetPDGGraphContextId = (GetPDGGraphContextIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContextId")); + FHoudiniApi::GetPDGGraphContexts = (GetPDGGraphContextsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContexts")); + FHoudiniApi::GetPDGState = (GetPDGStateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGState")); + FHoudiniApi::GetParameters = (GetParametersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParameters")); + FHoudiniApi::GetParmChoiceLists = (GetParmChoiceListsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmChoiceLists")); + FHoudiniApi::GetParmExpression = (GetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmExpression")); + FHoudiniApi::GetParmFile = (GetParmFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFile")); + FHoudiniApi::GetParmFloatValue = (GetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValue")); + FHoudiniApi::GetParmFloatValues = (GetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValues")); + FHoudiniApi::GetParmIdFromName = (GetParmIdFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIdFromName")); + FHoudiniApi::GetParmInfo = (GetParmInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfo")); + FHoudiniApi::GetParmInfoFromName = (GetParmInfoFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfoFromName")); + FHoudiniApi::GetParmIntValue = (GetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValue")); + FHoudiniApi::GetParmIntValues = (GetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValues")); + FHoudiniApi::GetParmNodeValue = (GetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmNodeValue")); + FHoudiniApi::GetParmStringValue = (GetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValue")); + FHoudiniApi::GetParmStringValues = (GetParmStringValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValues")); + FHoudiniApi::GetParmTagName = (GetParmTagNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagName")); + FHoudiniApi::GetParmTagValue = (GetParmTagValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagValue")); + FHoudiniApi::GetParmWithTag = (GetParmWithTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmWithTag")); + FHoudiniApi::GetPartInfo = (GetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPartInfo")); + FHoudiniApi::GetPreset = (GetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPreset")); + FHoudiniApi::GetPresetBufLength = (GetPresetBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPresetBufLength")); + FHoudiniApi::GetServerEnvInt = (GetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvInt")); + FHoudiniApi::GetServerEnvString = (GetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvString")); + FHoudiniApi::GetServerEnvVarCount = (GetServerEnvVarCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarCount")); + FHoudiniApi::GetServerEnvVarList = (GetServerEnvVarListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarList")); + FHoudiniApi::GetSessionEnvInt = (GetSessionEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionEnvInt")); + FHoudiniApi::GetSessionSyncInfo = (GetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionSyncInfo")); + FHoudiniApi::GetSphereInfo = (GetSphereInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSphereInfo")); + FHoudiniApi::GetStatus = (GetStatusFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatus")); + FHoudiniApi::GetStatusString = (GetStatusStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusString")); + FHoudiniApi::GetStatusStringBufLength = (GetStatusStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusStringBufLength")); + FHoudiniApi::GetString = (GetStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetString")); + FHoudiniApi::GetStringBatch = (GetStringBatchFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatch")); + FHoudiniApi::GetStringBatchSize = (GetStringBatchSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatchSize")); + FHoudiniApi::GetStringBufLength = (GetStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBufLength")); + FHoudiniApi::GetSupportedImageFileFormatCount = (GetSupportedImageFileFormatCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormatCount")); + FHoudiniApi::GetSupportedImageFileFormats = (GetSupportedImageFileFormatsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormats")); + FHoudiniApi::GetTime = (GetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTime")); + FHoudiniApi::GetTimelineOptions = (GetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTimelineOptions")); + FHoudiniApi::GetTotalCookCount = (GetTotalCookCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTotalCookCount")); + FHoudiniApi::GetUseHoudiniTime = (GetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetUseHoudiniTime")); + FHoudiniApi::GetVertexList = (GetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVertexList")); + FHoudiniApi::GetViewport = (GetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetViewport")); + FHoudiniApi::GetVolumeBounds = (GetVolumeBoundsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeBounds")); + FHoudiniApi::GetVolumeInfo = (GetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeInfo")); + FHoudiniApi::GetVolumeTileFloatData = (GetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileFloatData")); + FHoudiniApi::GetVolumeTileIntData = (GetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileIntData")); + FHoudiniApi::GetVolumeVisualInfo = (GetVolumeVisualInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVisualInfo")); + FHoudiniApi::GetVolumeVoxelFloatData = (GetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelFloatData")); + FHoudiniApi::GetVolumeVoxelIntData = (GetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelIntData")); + FHoudiniApi::GetWorkitemDataLength = (GetWorkitemDataLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemDataLength")); + FHoudiniApi::GetWorkitemFloatData = (GetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemFloatData")); + FHoudiniApi::GetWorkitemInfo = (GetWorkitemInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemInfo")); + FHoudiniApi::GetWorkitemIntData = (GetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemIntData")); + FHoudiniApi::GetWorkitemResultInfo = (GetWorkitemResultInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemResultInfo")); + FHoudiniApi::GetWorkitemStringData = (GetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemStringData")); + FHoudiniApi::GetWorkitems = (GetWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitems")); + FHoudiniApi::HandleBindingInfo_Create = (HandleBindingInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Create")); + FHoudiniApi::HandleBindingInfo_Init = (HandleBindingInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Init")); + FHoudiniApi::HandleInfo_Create = (HandleInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Create")); + FHoudiniApi::HandleInfo_Init = (HandleInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Init")); + FHoudiniApi::ImageFileFormat_Create = (ImageFileFormat_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Create")); + FHoudiniApi::ImageFileFormat_Init = (ImageFileFormat_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Init")); + FHoudiniApi::ImageInfo_Create = (ImageInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Create")); + FHoudiniApi::ImageInfo_Init = (ImageInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Init")); + FHoudiniApi::Initialize = (InitializeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Initialize")); + FHoudiniApi::InsertMultiparmInstance = (InsertMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_InsertMultiparmInstance")); + FHoudiniApi::Interrupt = (InterruptFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Interrupt")); + FHoudiniApi::IsInitialized = (IsInitializedFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsInitialized")); + FHoudiniApi::IsNodeValid = (IsNodeValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsNodeValid")); + FHoudiniApi::IsSessionValid = (IsSessionValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsSessionValid")); + FHoudiniApi::Keyframe_Create = (Keyframe_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Create")); + FHoudiniApi::Keyframe_Init = (Keyframe_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Init")); + FHoudiniApi::LoadAssetLibraryFromFile = (LoadAssetLibraryFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromFile")); + FHoudiniApi::LoadAssetLibraryFromMemory = (LoadAssetLibraryFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromMemory")); + FHoudiniApi::LoadGeoFromFile = (LoadGeoFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromFile")); + FHoudiniApi::LoadGeoFromMemory = (LoadGeoFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromMemory")); + FHoudiniApi::LoadHIPFile = (LoadHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadHIPFile")); + FHoudiniApi::LoadNodeFromFile = (LoadNodeFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadNodeFromFile")); + FHoudiniApi::MaterialInfo_Create = (MaterialInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Create")); + FHoudiniApi::MaterialInfo_Init = (MaterialInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Init")); + FHoudiniApi::MergeHIPFile = (MergeHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MergeHIPFile")); + FHoudiniApi::NodeInfo_Create = (NodeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Create")); + FHoudiniApi::NodeInfo_Init = (NodeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Init")); + FHoudiniApi::ObjectInfo_Create = (ObjectInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Create")); + FHoudiniApi::ObjectInfo_Init = (ObjectInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Init")); + FHoudiniApi::ParmChoiceInfo_Create = (ParmChoiceInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Create")); + FHoudiniApi::ParmChoiceInfo_Init = (ParmChoiceInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Init")); + FHoudiniApi::ParmHasExpression = (ParmHasExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasExpression")); + FHoudiniApi::ParmHasTag = (ParmHasTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasTag")); + FHoudiniApi::ParmInfo_Create = (ParmInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Create")); + FHoudiniApi::ParmInfo_GetFloatValueCount = (ParmInfo_GetFloatValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetFloatValueCount")); + FHoudiniApi::ParmInfo_GetIntValueCount = (ParmInfo_GetIntValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetIntValueCount")); + FHoudiniApi::ParmInfo_GetStringValueCount = (ParmInfo_GetStringValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetStringValueCount")); + FHoudiniApi::ParmInfo_Init = (ParmInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Init")); + FHoudiniApi::ParmInfo_IsFloat = (ParmInfo_IsFloatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsFloat")); + FHoudiniApi::ParmInfo_IsInt = (ParmInfo_IsIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsInt")); + FHoudiniApi::ParmInfo_IsNode = (ParmInfo_IsNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNode")); + FHoudiniApi::ParmInfo_IsNonValue = (ParmInfo_IsNonValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNonValue")); + FHoudiniApi::ParmInfo_IsPath = (ParmInfo_IsPathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsPath")); + FHoudiniApi::ParmInfo_IsString = (ParmInfo_IsStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsString")); + FHoudiniApi::PartInfo_Create = (PartInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Create")); + FHoudiniApi::PartInfo_GetAttributeCountByOwner = (PartInfo_GetAttributeCountByOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetAttributeCountByOwner")); + FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = (PartInfo_GetElementCountByAttributeOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByAttributeOwner")); + FHoudiniApi::PartInfo_GetElementCountByGroupType = (PartInfo_GetElementCountByGroupTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByGroupType")); + FHoudiniApi::PartInfo_Init = (PartInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Init")); + FHoudiniApi::PausePDGCook = (PausePDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PausePDGCook")); + FHoudiniApi::PythonThreadInterpreterLock = (PythonThreadInterpreterLockFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PythonThreadInterpreterLock")); + FHoudiniApi::QueryNodeInput = (QueryNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeInput")); + FHoudiniApi::QueryNodeOutputConnectedCount = (QueryNodeOutputConnectedCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedCount")); + FHoudiniApi::QueryNodeOutputConnectedNodes = (QueryNodeOutputConnectedNodesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedNodes")); + FHoudiniApi::RemoveCustomString = (RemoveCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveCustomString")); + FHoudiniApi::RemoveMultiparmInstance = (RemoveMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveMultiparmInstance")); + FHoudiniApi::RemoveParmExpression = (RemoveParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveParmExpression")); + FHoudiniApi::RenameNode = (RenameNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenameNode")); + FHoudiniApi::RenderCOPToImage = (RenderCOPToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderCOPToImage")); + FHoudiniApi::RenderTextureToImage = (RenderTextureToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderTextureToImage")); + FHoudiniApi::ResetSimulation = (ResetSimulationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ResetSimulation")); + FHoudiniApi::RevertGeo = (RevertGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertGeo")); + FHoudiniApi::RevertParmToDefault = (RevertParmToDefaultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefault")); + FHoudiniApi::RevertParmToDefaults = (RevertParmToDefaultsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefaults")); + FHoudiniApi::SaveGeoToFile = (SaveGeoToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToFile")); + FHoudiniApi::SaveGeoToMemory = (SaveGeoToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToMemory")); + FHoudiniApi::SaveHIPFile = (SaveHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveHIPFile")); + FHoudiniApi::SaveNodeToFile = (SaveNodeToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveNodeToFile")); + FHoudiniApi::SessionSyncInfo_Create = (SessionSyncInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SessionSyncInfo_Create")); + FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); + FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); + FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); + FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); + FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); + FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); + FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); + FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); + FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); + FHoudiniApi::SetCurveKnots = (SetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveKnots")); + FHoudiniApi::SetCurveOrders = (SetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveOrders")); + FHoudiniApi::SetCustomString = (SetCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCustomString")); + FHoudiniApi::SetFaceCounts = (SetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetFaceCounts")); + FHoudiniApi::SetGroupMembership = (SetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetGroupMembership")); + FHoudiniApi::SetHeightFieldData = (SetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetHeightFieldData")); + FHoudiniApi::SetImageInfo = (SetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetImageInfo")); + FHoudiniApi::SetNodeDisplay = (SetNodeDisplayFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetNodeDisplay")); + FHoudiniApi::SetObjectTransform = (SetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetObjectTransform")); + FHoudiniApi::SetParmExpression = (SetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmExpression")); + FHoudiniApi::SetParmFloatValue = (SetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValue")); + FHoudiniApi::SetParmFloatValues = (SetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValues")); + FHoudiniApi::SetParmIntValue = (SetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValue")); + FHoudiniApi::SetParmIntValues = (SetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValues")); + FHoudiniApi::SetParmNodeValue = (SetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmNodeValue")); + FHoudiniApi::SetParmStringValue = (SetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmStringValue")); + FHoudiniApi::SetPartInfo = (SetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPartInfo")); + FHoudiniApi::SetPreset = (SetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPreset")); + FHoudiniApi::SetServerEnvInt = (SetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvInt")); + FHoudiniApi::SetServerEnvString = (SetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvString")); + FHoudiniApi::SetSessionSync = (SetSessionSyncFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSync")); + FHoudiniApi::SetSessionSyncInfo = (SetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSyncInfo")); + FHoudiniApi::SetTime = (SetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTime")); + FHoudiniApi::SetTimelineOptions = (SetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTimelineOptions")); + FHoudiniApi::SetTransformAnimCurve = (SetTransformAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTransformAnimCurve")); + FHoudiniApi::SetUseHoudiniTime = (SetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetUseHoudiniTime")); + FHoudiniApi::SetVertexList = (SetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVertexList")); + FHoudiniApi::SetViewport = (SetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetViewport")); + FHoudiniApi::SetVolumeInfo = (SetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeInfo")); + FHoudiniApi::SetVolumeTileFloatData = (SetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileFloatData")); + FHoudiniApi::SetVolumeTileIntData = (SetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileIntData")); + FHoudiniApi::SetVolumeVoxelFloatData = (SetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelFloatData")); + FHoudiniApi::SetVolumeVoxelIntData = (SetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelIntData")); + FHoudiniApi::SetWorkitemFloatData = (SetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemFloatData")); + FHoudiniApi::SetWorkitemIntData = (SetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemIntData")); + FHoudiniApi::SetWorkitemStringData = (SetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemStringData")); + FHoudiniApi::StartThriftNamedPipeServer = (StartThriftNamedPipeServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftNamedPipeServer")); + FHoudiniApi::StartThriftSocketServer = (StartThriftSocketServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftSocketServer")); + FHoudiniApi::ThriftServerOptions_Create = (ThriftServerOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Create")); + FHoudiniApi::ThriftServerOptions_Init = (ThriftServerOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Init")); + FHoudiniApi::TimelineOptions_Create = (TimelineOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Create")); + FHoudiniApi::TimelineOptions_Init = (TimelineOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Init")); + FHoudiniApi::TransformEuler_Create = (TransformEuler_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Create")); + FHoudiniApi::TransformEuler_Init = (TransformEuler_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Init")); + FHoudiniApi::Transform_Create = (Transform_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Create")); + FHoudiniApi::Transform_Init = (Transform_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Init")); + FHoudiniApi::Viewport_Create = (Viewport_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Viewport_Create")); + FHoudiniApi::VolumeInfo_Create = (VolumeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Create")); + FHoudiniApi::VolumeInfo_Init = (VolumeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Init")); + FHoudiniApi::VolumeTileInfo_Create = (VolumeTileInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Create")); + FHoudiniApi::VolumeTileInfo_Init = (VolumeTileInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Init")); +} + + +void +FHoudiniApi::FinalizeHAPI() +{ + FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; + FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; + FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; + FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; + FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; + FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; + FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; + FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; + FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; + FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; + FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; + FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; + FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; + FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; + FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; + FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; + FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; + FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; + FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; + FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; + FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; + FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; + FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; + FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; + FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; + FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; + FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; + FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; + FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; + FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; + FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; + FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; + FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; + FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; + FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; + FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; + FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; + FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; + FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; + FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; + FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; + FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; + FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; + FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; + FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; + FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; + FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; + FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; + FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; + FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; + FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; + FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; + FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; + FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; + FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; + FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; + FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; + FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; + FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; + FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; + FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; + FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; + FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; + FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; + FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; + FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; + FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; + FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; + FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; + FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; + FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; + FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; + FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; + FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; + FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; + FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; + FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; + FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; + FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; + FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; + FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; + FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; + FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; + FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; + FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; + FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; + FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; + FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; + FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; + FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; + FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; + FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; + FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; + FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; + FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; + FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; + FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; + FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; + FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; + FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; + FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; + FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; + FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; + FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; + FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; + FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; + FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; + FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; + FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; + FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; + FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; + FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; + FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; + FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; + FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; + FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; + FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; + FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; + FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; + FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; + FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; + FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; + FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; + FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; + FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; + FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; + FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; + FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; + FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; + FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; + FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; + FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; + FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; + FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; + FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; + FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; + FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; + FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; + FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; + FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; + FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; + FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; + FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; + FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; + FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; + FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; + FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; + FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; + FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; + FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; + FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; + FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; + FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; + FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; + FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; + FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; + FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; + FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; + FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; + FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; + FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; + FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; + FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; + FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; + FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; + FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; + FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; + FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; + FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; + FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; + FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; + FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; + FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; + FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; + FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; + FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; + FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; + FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; + FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; + FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; + FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; + FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; + FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; + FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; + FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; + FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; + FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; + FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; + FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; + FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; + FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; + FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; + FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; + FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; + FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; + FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; + FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; + FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; + FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; + FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; + FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; + FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; + FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; + FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; + FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; + FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; + FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; + FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; + FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; + FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; + FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; + FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; + FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; + FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; + FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; + FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; + FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; + FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; + FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; + FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; + FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; + FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; + FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; + FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; + FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; + FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; + FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; + FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; + FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; + FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; + FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; + FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; + FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; + FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; + FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; + FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; + FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; + FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; + FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; + FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; + FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; + FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; + FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; + FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; + FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; + FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; + FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; + FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; + FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; + FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; + FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; + FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; + FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; + FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; + FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; + FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; + FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; + FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; + FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; + FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; + FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; + FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; + FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; + FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; + FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; + FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; + FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; + FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; + FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; + FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; + FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; + FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; + FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; + FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; + FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; + FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; + FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; + FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; + FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; + FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; + FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; + FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; + FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; + FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; + FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; + FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; + FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; + FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; + FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; + FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; + FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; + FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; + FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; + FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; + FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; + FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; + FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; + FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; +} + + +bool +FHoudiniApi::IsHAPIInitialized() +{ + return ( FHoudiniApi::IsInitialized != &FHoudiniApi::IsInitializedEmptyStub ); +} + + +HAPI_Result +FHoudiniApi::AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_AssetInfo +FHoudiniApi::AssetInfo_CreateEmptyStub() +{ + return HAPI_AssetInfo(); +} + + +void +FHoudiniApi::AssetInfo_InitEmptyStub(HAPI_AssetInfo * in) +{ + return; +} + + +HAPI_AttributeInfo +FHoudiniApi::AttributeInfo_CreateEmptyStub() +{ + return HAPI_AttributeInfo(); +} + + +void +FHoudiniApi::AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CleanupEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ClearConnectionErrorEmptyStub() +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CloseSessionEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Bool +FHoudiniApi::CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right) +{ + return HAPI_Bool(); +} + + +HAPI_CookOptions +FHoudiniApi::CookOptions_CreateEmptyStub() +{ + return HAPI_CookOptions(); +} + + +void +FHoudiniApi::CookOptions_InitEmptyStub(HAPI_CookOptions * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateInProcessSessionEmptyStub(HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_CurveInfo +FHoudiniApi::CurveInfo_CreateEmptyStub() +{ + return HAPI_CurveInfo(); +} + + +void +FHoudiniApi::CurveInfo_InitEmptyStub(HAPI_CurveInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_GeoInfo +FHoudiniApi::GeoInfo_CreateEmptyStub() +{ + return HAPI_GeoInfo(); +} + + +int +FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type) +{ + return -1; +} + + +void +FHoudiniApi::GeoInfo_InitEmptyStub(HAPI_GeoInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetConnectionErrorLengthEmptyStub(int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTimeEmptyStub(const HAPI_Session * session, float * time) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_HandleBindingInfo +FHoudiniApi::HandleBindingInfo_CreateEmptyStub() +{ + return HAPI_HandleBindingInfo(); +} + + +void +FHoudiniApi::HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in) +{ + return; +} + + +HAPI_HandleInfo +FHoudiniApi::HandleInfo_CreateEmptyStub() +{ + return HAPI_HandleInfo(); +} + + +void +FHoudiniApi::HandleInfo_InitEmptyStub(HAPI_HandleInfo * in) +{ + return; +} + + +HAPI_ImageFileFormat +FHoudiniApi::ImageFileFormat_CreateEmptyStub() +{ + return HAPI_ImageFileFormat(); +} + + +void +FHoudiniApi::ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in) +{ + return; +} + + +HAPI_ImageInfo +FHoudiniApi::ImageInfo_CreateEmptyStub() +{ + return HAPI_ImageInfo(); +} + + +void +FHoudiniApi::ImageInfo_InitEmptyStub(HAPI_ImageInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::InterruptEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsInitializedEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsSessionValidEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Keyframe +FHoudiniApi::Keyframe_CreateEmptyStub() +{ + return HAPI_Keyframe(); +} + + +void +FHoudiniApi::Keyframe_InitEmptyStub(HAPI_Keyframe * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_MaterialInfo +FHoudiniApi::MaterialInfo_CreateEmptyStub() +{ + return HAPI_MaterialInfo(); +} + + +void +FHoudiniApi::MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_NodeInfo +FHoudiniApi::NodeInfo_CreateEmptyStub() +{ + return HAPI_NodeInfo(); +} + + +void +FHoudiniApi::NodeInfo_InitEmptyStub(HAPI_NodeInfo * in) +{ + return; +} + + +HAPI_ObjectInfo +FHoudiniApi::ObjectInfo_CreateEmptyStub() +{ + return HAPI_ObjectInfo(); +} + + +void +FHoudiniApi::ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in) +{ + return; +} + + +HAPI_ParmChoiceInfo +FHoudiniApi::ParmChoiceInfo_CreateEmptyStub() +{ + return HAPI_ParmChoiceInfo(); +} + + +void +FHoudiniApi::ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_ParmInfo +FHoudiniApi::ParmInfo_CreateEmptyStub() +{ + return HAPI_ParmInfo(); +} + + +int +FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in) +{ + return -1; +} + + +int +FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in) +{ + return -1; +} + + +int +FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in) +{ + return -1; +} + + +void +FHoudiniApi::ParmInfo_InitEmptyStub(HAPI_ParmInfo * in) +{ + return; +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_PartInfo +FHoudiniApi::PartInfo_CreateEmptyStub() +{ + return HAPI_PartInfo(); +} + + +int +FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) +{ + return -1; +} + + +int +FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) +{ + return -1; +} + + +int +FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type) +{ + return -1; +} + + +void +FHoudiniApi::PartInfo_InitEmptyStub(HAPI_PartInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_SessionSyncInfo +FHoudiniApi::SessionSyncInfo_CreateEmptyStub() +{ + return HAPI_SessionSyncInfo(); +} + + +HAPI_Result +FHoudiniApi::SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTimeEmptyStub(const HAPI_Session * session, float time) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_ThriftServerOptions +FHoudiniApi::ThriftServerOptions_CreateEmptyStub() +{ + return HAPI_ThriftServerOptions(); +} + + +void +FHoudiniApi::ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in) +{ + return; +} + + +HAPI_TimelineOptions +FHoudiniApi::TimelineOptions_CreateEmptyStub() +{ + return HAPI_TimelineOptions(); +} + + +void +FHoudiniApi::TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in) +{ + return; +} + + +HAPI_TransformEuler +FHoudiniApi::TransformEuler_CreateEmptyStub() +{ + return HAPI_TransformEuler(); +} + + +void +FHoudiniApi::TransformEuler_InitEmptyStub(HAPI_TransformEuler * in) +{ + return; +} + + +HAPI_Transform +FHoudiniApi::Transform_CreateEmptyStub() +{ + return HAPI_Transform(); +} + + +void +FHoudiniApi::Transform_InitEmptyStub(HAPI_Transform * in) +{ + return; +} + + +HAPI_Viewport +FHoudiniApi::Viewport_CreateEmptyStub() +{ + return HAPI_Viewport(); +} + + +HAPI_VolumeInfo +FHoudiniApi::VolumeInfo_CreateEmptyStub() +{ + return HAPI_VolumeInfo(); +} + + +void +FHoudiniApi::VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in) +{ + return; +} + + +HAPI_VolumeTileInfo +FHoudiniApi::VolumeTileInfo_CreateEmptyStub() +{ + return HAPI_VolumeTileInfo(); +} + + +void +FHoudiniApi::VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in) +{ + return; +} + + diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.cpp b/Source/HoudiniEngine/Private/HoudiniEngine.cpp index abacf22dc..42ef76d66 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngine.cpp @@ -1,1122 +1,1120 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngine.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "HoudiniApi.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineScheduler.h" -#include "HoudiniEngineManager.h" -#include "HoudiniEngineTask.h" -#include "HoudiniEngineTaskInfo.h" -#include "HoudiniAssetComponent.h" -#include "HAPI/HAPI_Version.h" - -#include "Modules/ModuleManager.h" -#include "Misc/ScopeLock.h" -#include "Engine/StaticMesh.h" -#include "Materials/Material.h" -#include "ISettingsModule.h" -#include "HAL/PlatformFilemanager.h" -#include "Async/Async.h" -#include "Logging/LogMacros.h" - -#if WITH_EDITOR - #include "Widgets/Notifications/SNotificationList.h" - #include "Framework/Notifications/NotificationManager.h" -#endif - -#define LOCTEXT_NAMESPACE "HoudiniEngine" - -IMPLEMENT_MODULE(FHoudiniEngine, HoudiniEngine) -DEFINE_LOG_CATEGORY( LogHoudiniEngine ); - -FHoudiniEngine * -FHoudiniEngine::HoudiniEngineInstance = nullptr; - -FHoudiniEngine::FHoudiniEngine() - : LicenseType(HAPI_LICENSE_NONE) - , HoudiniEngineSchedulerThread(nullptr) - , HoudiniEngineScheduler(nullptr) - , HoudiniEngineManagerThread(nullptr) - , HoudiniEngineManager(nullptr) - //, bHAPIVersionMismatch(false) - , bEnableCookingGlobal(true) - , UIRefreshCountWhenPauseCooking(0) - , bFirstSessionCreated(false) - , bEnableSessionSync(false) - , bCookUsingHoudiniTime(true) - , bSyncViewport(false) - , bSyncHoudiniViewport(true) - , bSyncUnrealViewport(false) - , HoudiniLogoStaticMesh(nullptr) - , HoudiniDefaultMaterial(nullptr) - , HoudiniTemplateMaterial(nullptr) - , HoudiniLogoBrush(nullptr) - , HoudiniDefaultReferenceMesh(nullptr) - , HoudiniDefaultReferenceMeshMaterial(nullptr) -{ - Session.type = HAPI_SESSION_MAX; - Session.id = -1; - - -#if WITH_EDITOR - HapiNotificationStarted = 0.0; -#endif -} - -FHoudiniEngine& -FHoudiniEngine::Get() -{ - check(FHoudiniEngine::HoudiniEngineInstance); - return *FHoudiniEngine::HoudiniEngineInstance; -} - -bool -FHoudiniEngine::IsInitialized() -{ - return FHoudiniEngine::HoudiniEngineInstance != nullptr && FHoudiniEngineUtils::IsInitialized(); -} - -void -FHoudiniEngine::StartupModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine module...")); - -#if WITH_EDITOR - // Register settings. - if (ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings")) - { - SettingsModule->RegisterSettings( - "Project", "Plugins", "HoudiniEngine", - LOCTEXT("RuntimeSettingsName", "Houdini Engine"), - LOCTEXT("RuntimeSettingsDescription", "Configure the HoudiniEngine plugin"), - GetMutableDefault< UHoudiniRuntimeSettings >()); - } -#endif - - // Before starting the module, we need to locate and load HAPI library. - { - void * HAPILibraryHandle = FHoudiniEngineUtils::LoadLibHAPI(LibHAPILocation); - if ( HAPILibraryHandle ) - { - FHoudiniApi::InitializeHAPI( HAPILibraryHandle ); - } - else - { - // Get platform specific name of libHAPI. - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - HOUDINI_LOG_MESSAGE(TEXT("Failed locating or loading %s"), *LibHAPIName); - } - } - - // Create static mesh Houdini logo. - HoudiniLogoStaticMesh = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_LOGO, nullptr, LOAD_None, nullptr); - if (HoudiniLogoStaticMesh.IsValid()) - HoudiniLogoStaticMesh->AddToRoot(); - - // Create default material. - HoudiniDefaultMaterial = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultMaterial.IsValid()) - HoudiniDefaultMaterial->AddToRoot(); - - HoudiniTemplateMaterial = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniTemplateMaterial.IsValid()) - HoudiniTemplateMaterial->AddToRoot(); - - // Houdini Logo Brush - FString Icon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icon128.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) - { - const FName BrushName(*Icon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Logo Brush - FString HEIcon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_logo_128.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) - { - const FName BrushName(*HEIcon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Create Houdini default reference mesh - HoudiniDefaultReferenceMesh = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultReferenceMesh.IsValid()) - HoudiniDefaultReferenceMesh->AddToRoot(); - - // Create Houdini default reference mesh material - HoudiniDefaultReferenceMeshMaterial = LoadObject - (nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultReferenceMeshMaterial.IsValid()) - HoudiniDefaultReferenceMeshMaterial->AddToRoot(); - - // We do not automatically try to start a session when starting up the module now. - bFirstSessionCreated = false; - - // Create HAPI scheduler and processing thread. - HoudiniEngineScheduler = new FHoudiniEngineScheduler(); - HoudiniEngineSchedulerThread = FRunnableThread::Create( - HoudiniEngineScheduler, TEXT("HoudiniSchedulerThread"), 0, TPri_Normal); - - // Create Houdini Asset Manager - HoudiniEngineManager = new FHoudiniEngineManager(); - - // Set the default value for pausing houdini engine cooking - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bEnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; - - // Check if a null session is set - bool bNoneSession = (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None); - - // Initialize the singleton with this instance - FHoudiniEngine::HoudiniEngineInstance = this; - - // See if we need to start the manager ticking if needed - // Don tick if we failed to load HAPI, if cooking is disabled or if we're using a null session - if (FHoudiniApi::IsHAPIInitialized()) - { - if (bEnableCookingGlobal && !bNoneSession) - { - PostEngineInitCallback = FCoreDelegates::OnPostEngineInit.AddLambda([]() - { - FHoudiniEngine& HEngine = FHoudiniEngine::Get(); - HEngine.UnregisterPostEngineInitCallback(); - FHoudiniEngineManager* const Manager = HEngine.GetHoudiniEngineManager(); - if (Manager) - Manager->StartHoudiniTicking(); - }); - } - } -} - -void -FHoudiniEngine::ShutdownModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine module.")); - - // We no longer need the Houdini logo static mesh. - if (HoudiniLogoStaticMesh.IsValid()) - { - HoudiniLogoStaticMesh->RemoveFromRoot(); - HoudiniLogoStaticMesh = nullptr; - } - - // We no longer need the Houdini default material. - if (HoudiniDefaultMaterial.IsValid()) - { - HoudiniDefaultMaterial->RemoveFromRoot(); - HoudiniDefaultMaterial = nullptr; - } - - // We no longer need the Houdini default material. - if (HoudiniTemplateMaterial.IsValid()) - { - HoudiniTemplateMaterial->RemoveFromRoot(); - HoudiniTemplateMaterial = nullptr; - } - - // We no longer need the Houdini default reference mesh - if (HoudiniDefaultReferenceMesh.IsValid()) - { - HoudiniDefaultReferenceMesh->RemoveFromRoot(); - HoudiniDefaultReferenceMesh = nullptr; - } - - // We no longer need the Houdini default reference mesh material - if (HoudiniDefaultReferenceMeshMaterial.IsValid()) - { - HoudiniDefaultReferenceMeshMaterial->RemoveFromRoot(); - HoudiniDefaultReferenceMeshMaterial = nullptr; - } - /* - // We no longer need Houdini digital asset used for loading bgeo files. - if (HoudiniBgeoAsset.IsValid()) - { - HoudiniBgeoAsset->RemoveFromRoot(); - HoudiniBgeoAsset = nullptr; - } - */ - -#if WITH_EDITOR - // Unregister settings. - ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings"); - if (SettingsModule) - SettingsModule->UnregisterSettings("Project", "Plugins", "HoudiniEngine"); -#endif - - // Do scheduler and thread clean up. - if (HoudiniEngineScheduler) - HoudiniEngineScheduler->Stop(); - - if (HoudiniEngineSchedulerThread) - { - //HoudiniEngineSchedulerThread->Kill( true ); - HoudiniEngineSchedulerThread->WaitForCompletion(); - - delete HoudiniEngineSchedulerThread; - HoudiniEngineSchedulerThread = nullptr; - } - - if ( HoudiniEngineScheduler ) - { - delete HoudiniEngineScheduler; - HoudiniEngineScheduler = nullptr; - } - - // Do manager clean up. - if (HoudiniEngineManager) - HoudiniEngineManager->StopHoudiniTicking(); - - if (HoudiniEngineManager) - { - delete HoudiniEngineManager; - HoudiniEngineManager = nullptr; - } - - // Perform HAPI finalization. - if ( FHoudiniApi::IsHAPIInitialized() ) - { - FHoudiniApi::Cleanup(GetSession()); - FHoudiniApi::CloseSession(GetSession()); - } - - FHoudiniApi::FinalizeHAPI(); - - FHoudiniEngine::HoudiniEngineInstance = nullptr; -} - -void -FHoudiniEngine::AddTask(const FHoudiniEngineTask & InTask) -{ - if ( HoudiniEngineScheduler ) - HoudiniEngineScheduler->AddTask(InTask); - - FScopeLock ScopeLock(&CriticalSection); - FHoudiniEngineTaskInfo TaskInfo; - TaskInfo.TaskType = InTask.TaskType; - TaskInfo.TaskState = EHoudiniEngineTaskState::Working; - - TaskInfos.Add(InTask.HapiGUID, TaskInfo); -} - -void -FHoudiniEngine::AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo) -{ - FScopeLock ScopeLock(&CriticalSection); - TaskInfos.Add(InHapiGUID, InTaskInfo); -} - -void -FHoudiniEngine::RemoveTaskInfo(const FGuid& InHapiGUID) -{ - FScopeLock ScopeLock(&CriticalSection); - TaskInfos.Remove(InHapiGUID); -} - -bool -FHoudiniEngine::RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo) -{ - FScopeLock ScopeLock(&CriticalSection); - - if (TaskInfos.Contains(InHapiGUID)) - { - OutTaskInfo = TaskInfos[InHapiGUID]; - return true; - } - - return false; -} - -/* -void -FHoudiniEngine::AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - if (HoudiniEngineManager) - HoudiniEngineManager->AddComponent(HAC); -} -*/ - -const FString & -FHoudiniEngine::GetLibHAPILocation() const -{ - return LibHAPILocation; -} - -const HAPI_Session * -FHoudiniEngine::GetSession() const -{ - return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; -} - -HAPI_CookOptions -FHoudiniEngine::GetDefaultCookOptions() -{ - // Default CookOptions - HAPI_CookOptions CookOptions; - FHoudiniApi::CookOptions_Init(&CookOptions); - - CookOptions.curveRefineLOD = 8.0f; - CookOptions.clearErrorsAndWarnings = false; - CookOptions.maxVerticesPerPrimitive = 3; - CookOptions.splitGeosByGroup = false; - CookOptions.splitGeosByAttribute = false; - CookOptions.splitAttrSH = 0; - CookOptions.refineCurveToLinear = true; - CookOptions.handleBoxPartTypes = false; - CookOptions.handleSpherePartTypes = false; - CookOptions.splitPointsByVertexAttributes = false; - CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; - CookOptions.cookTemplatedGeos = true; - - return CookOptions; -} - -bool -FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, - const bool& StartAutomaticServer, - const float& AutomaticServerTimeout, - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const int32& ServerPort, - const FString& ServerHost) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - // Only start a new Session if we dont already have a valid one - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) - return true; - - HAPI_Result SessionResult = HAPI_RESULT_FAILURE; - - HAPI_ThriftServerOptions ServerOptions; - FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); - ServerOptions.autoClose = true; - ServerOptions.timeoutMs = AutomaticServerTimeout; - - // Unless we automatically start the server, - // consider we're in SessionSync mode - bEnableSessionSync = true; - - auto UpdatePathForServer = [&] - { - // Modify our PATH so that HARC will find HARS.exe - const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); - - FString OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); - - FString ModifiedPath = -#if PLATFORM_MAC - // On Mac our binaries are split between two folders - LibHAPILocation + TEXT("/../Resources/bin") + PathDelimiter + -#endif - LibHAPILocation + PathDelimiter + OrigPathVar; - - FPlatformMisc::SetEnvironmentVar(TEXT("PATH"), *ModifiedPath); - }; - - switch ( SessionType ) - { - case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: - { - // Try to connect to an existing socket session first - SessionResult = FHoudiniApi::CreateThriftSocketSession( - SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort ); - - // Start a session and try to connect to it if we failed - if ( StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS ) - { - UpdatePathForServer(); - FHoudiniApi::StartThriftSocketServer( - &ServerOptions, ServerPort, nullptr); - - // We've started the server manually, disable session sync - bEnableSessionSync = false; - - SessionResult = FHoudiniApi::CreateThriftSocketSession( - SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort); - } - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: - { - // Try to connect to an existing pipe session first - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - SessionPtr, TCHAR_TO_UTF8(*ServerPipeName) ); - - // Start a session and try to connect to it if we failed - if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) - { - UpdatePathForServer(); - FHoudiniApi::StartThriftNamedPipeServer( - &ServerOptions, TCHAR_TO_UTF8(*ServerPipeName), nullptr); - - // We've started the server manually, disable session sync - bEnableSessionSync = false; - - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - SessionPtr, TCHAR_TO_UTF8(*ServerPipeName)); - } - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_None: - { - HOUDINI_LOG_MESSAGE(TEXT("Session type set to None, Cooking is disabled.")); - // Disable session sync - bEnableSessionSync = false; - break; - } - - // As of Unreal 4.19, InProcess sessions are not supported anymore - case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: - default: - HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine session type")); - // Disable session sync - bEnableSessionSync = false; - break; - } - - if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) - { - // Disable session sync as well? - bEnableSessionSync = false; - return false; - } - - // Update this session's license type - HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( - SessionPtr, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); - - return true; -} - -bool -FHoudiniEngine::SessionSyncConnect( - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const FString& ServerHost, - const int32& ServerPort) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - // Only start a new Session if we dont already have a valid one - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(&Session)) - return true; - - HAPI_Result SessionResult = HAPI_RESULT_FAILURE; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - - HAPI_ThriftServerOptions ServerOptions; - FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); - ServerOptions.autoClose = true; - ServerOptions.timeoutMs = HoudiniRuntimeSettings->AutomaticServerTimeout; - - switch (SessionType) - { - case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: - { - // Try to connect to an existing socket session first - SessionResult = FHoudiniApi::CreateThriftSocketSession( - &Session, TCHAR_TO_UTF8(*ServerHost), ServerPort); - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: - { - // Try to connect to an existing pipe session first - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - &Session, TCHAR_TO_UTF8(*ServerPipeName)); - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_None: - case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: - default: - HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine Session Sync Type!!")); - bEnableSessionSync = false; - break; - } - - if (SessionResult != HAPI_RESULT_SUCCESS) - return false; - - // Enable session sync - bEnableSessionSync = true; - - // Update this session's license type - HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( - &Session, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); - - // Update the default viewport sync settings - bSyncViewport = HoudiniRuntimeSettings->bSyncViewport; - bSyncHoudiniViewport = HoudiniRuntimeSettings->bSyncHoudiniViewport; - bSyncUnrealViewport = HoudiniRuntimeSettings->bSyncUnrealViewport; - - return true; -} - -bool -FHoudiniEngine::InitializeHAPISession() -{ - // The HAPI stubs needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The Houdini API stubs have not been properly initialized.")); - return false; - } - - // We need a Valid Session - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(GetSession())) - { - HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The session is invalid.")); - return false; - } - - // Now, initialize HAPI with the new session - // We need to make sure HAPI version is correct. - int32 RunningEngineMajor = 0; - int32 RunningEngineMinor = 0; - int32 RunningEngineApi = 0; - - // 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); - - // Compare defined and running versions. - if (RunningEngineMajor != HAPI_VERSION_HOUDINI_ENGINE_MAJOR - || RunningEngineMinor != HAPI_VERSION_HOUDINI_ENGINE_MINOR) - { - // Major or minor HAPI version differs, stop here - HOUDINI_LOG_ERROR( - TEXT("Starting up the Houdini Engine module failed: built and running versions do not match.")); - HOUDINI_LOG_ERROR( - TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), - HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, - RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - - // Display an error message - - // - return false; - - } - else if (RunningEngineApi != HAPI_VERSION_HOUDINI_ENGINE_API) - { - // Major/minor HAPIversions match, but only the API version differs, - // Allow the user to continue but warn him of possible instabilities - HOUDINI_LOG_WARNING( - TEXT("Starting up the Houdini Engine module: built and running versions do not match.")); - HOUDINI_LOG_WARNING( - TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), - HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, - RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - HOUDINI_LOG_WARNING( - TEXT("This could cause instabilities and crashes when using the Houdini Engine plugin")); - } - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Default CookOptions - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - - bool bUseCookingThread = true; - HAPI_Result Result = FHoudiniApi::Initialize( - &Session, - &CookOptions, - bUseCookingThread, - HoudiniRuntimeSettings->CookingThreadStackSize, - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->HoudiniEnvironmentFiles), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->OtlSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->DsoSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->ImageDsoSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->AudioDsoSearchPath)); - - if (Result == HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module.")); - } - else if (Result == HAPI_RESULT_ALREADY_INITIALIZED) - { - // Reused session? just notify the user - HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module - HAPI was already initialzed.")); - } - else - { - HOUDINI_LOG_ERROR( - TEXT("Starting up the Houdini Engine module failed: %s"), - *FHoudiniEngineUtils::GetErrorDescription(Result)); - - return false; - } - - // Let HAPI know we are running inside UE4 - FHoudiniApi::SetServerEnvString(&Session, HAPI_ENV_CLIENT_NAME, HAPI_UNREAL_CLIENT_NAME); - - if (bEnableSessionSync) - { - // Set the session sync infos if needed - UploadSessionSyncInfoToHoudini(); - - // Indicate that Session Sync is enabled - FString Notification = TEXT("Houdini Engine Session Sync enabled."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Session Sync enabled.")); - } - - return true; -} - - -void -FHoudiniEngine::OnSessionLost() -{ - // Mark the session as invalid - Session.id = -1; - Session.type = HAPI_SESSION_MAX; - bEnableSessionSync = false; - HoudiniEngineManager->StopHoudiniTicking(); - - // This indicates that we likely have lost the session due to a crash in HARS/Houdini - FString Notification = TEXT("Houdini Engine Session lost!"); - FHoudiniEngineUtils::CreateSlateNotification(Notification, 2.0, 4.0); - - HOUDINI_LOG_ERROR(TEXT("Houdini Engine Session lost! This could be caused by a crash in HARS.")); -} - -bool -FHoudiniEngine::StopSession() -{ - HAPI_Session* SessionPtr = &Session; - return StopSession(SessionPtr); -} - -bool -FHoudiniEngine::StopSession(HAPI_Session*& SessionPtr) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) - { - // SessionPtr is valid, clean up and close the session - FHoudiniApi::Cleanup(SessionPtr); - FHoudiniApi::CloseSession(SessionPtr); - } - - Session.id = -1; - Session.type = HAPI_SESSION_MAX; - bEnableSessionSync = false; - - HoudiniEngineManager->StopHoudiniTicking(); - - return true; -} - -bool -FHoudiniEngine::RestartSession() -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Starting the Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - if (!StopSession(SessionPtr)) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); - } - else - { - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - HoudiniRuntimeSettings->bStartAutomaticServer, - HoudiniRuntimeSettings->AutomaticServerTimeout, - HoudiniRuntimeSettings->SessionType, - HoudiniRuntimeSettings->ServerPipeName, - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to start the new Session")); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); - } - else - { - bSuccess = true; - } - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -bool -FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName) -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Create the Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - true, - HoudiniRuntimeSettings->AutomaticServerTimeout, - SessionType, - OverrideServerPipeName == NAME_None ? HoudiniRuntimeSettings->ServerPipeName : OverrideServerPipeName.ToString(), - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine Session")); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine session - Failed to initialize HAPI")); - } - else - { - bSuccess = true; - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -bool -FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType) -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Connecting to a Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - false, - HoudiniRuntimeSettings->AutomaticServerTimeout, - SessionType, - HoudiniRuntimeSettings->ServerPipeName, - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine Session")); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine session - Failed to initialize HAPI")); - } - else - { - bSuccess = true; - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -void -FHoudiniEngine::StartTicking() -{ - // Finish the notification and display the results - FString StatusText = TEXT("Houdini Engine session connected."); - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - - HoudiniEngineManager->StartHoudiniTicking(); -} - -void -FHoudiniEngine::StopTicking() -{ - // Finish the notification and display the results - FString StatusText = TEXT("Failed to start the Houdini Engine session..."); - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - - HoudiniEngineManager->StopHoudiniTicking(); - - HAPI_Session* SessionPtr = &Session; - StopSession(SessionPtr); -} - -bool -FHoudiniEngine::IsCookingEnabled() const -{ - return bEnableCookingGlobal; -} - -void -FHoudiniEngine::SetCookingEnabled(const bool& bInEnableCooking) -{ - bEnableCookingGlobal = bInEnableCooking; -} - -bool -FHoudiniEngine::GetFirstSessionCreated() const -{ - return bFirstSessionCreated; -} - -bool -FHoudiniEngine::CreateTaskSlateNotification( - const FText& InText, const bool& bForceNow, const float& NotificationExpire, const float& NotificationFadeOut) -{ -#if WITH_EDITOR - static double NotificationUpdateFrequency = 2.0f; - - // Check whether we want to display Slate cooking and instantiation notifications. - bool bDisplaySlateCookingNotifications = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (!bDisplaySlateCookingNotifications) - return false; - - if (!bForceNow) - { - if ((FPlatformTime::Seconds() - HapiNotificationStarted) < NotificationUpdateFrequency) - return false; - } - - if (!NotificationPtr.IsValid()) - { - FNotificationInfo Info(InText); - Info.bFireAndForget = false; - Info.FadeOutDuration = NotificationFadeOut; - Info.ExpireDuration = NotificationExpire; - TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); - if (HoudiniBrush.IsValid()) - Info.Image = HoudiniBrush.Get(); - - /* - if (!IsPIEActive()) - */ - - NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); - } -#endif - - return true; -} - -bool -FHoudiniEngine::UpdateTaskSlateNotification(const FText& InText) -{ -#if WITH_EDITOR - // task is till running - // Just update the slate notification - TSharedPtr NotificationItem = NotificationPtr.Pin(); - if (NotificationItem.IsValid()) - NotificationItem->SetText(InText); -#endif - - return true; -} - -bool -FHoudiniEngine::FinishTaskSlateNotification(const FText& InText) -{ -#if WITH_EDITOR - if (NotificationPtr.IsValid()) - { - TSharedPtr NotificationItem = NotificationPtr.Pin(); - if (NotificationItem.IsValid()) - { - NotificationItem->SetText(InText); - NotificationItem->ExpireAndFadeout(); - - NotificationPtr.Reset(); - } - } -#endif - - return true; -} - -void -FHoudiniEngine::UpdateSessionSyncInfoFromHoudini() -{ - if (!bEnableSessionSync) - return; - - // Set the Session Sync settings to Houdini - HAPI_SessionSyncInfo SessionSyncInfo; - //FHoudiniApi::SessionSyncInfo_Create(&SessionSyncInfo); - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetSessionSyncInfo(&Session, &SessionSyncInfo)) - { - bCookUsingHoudiniTime = SessionSyncInfo.cookUsingHoudiniTime; - bSyncViewport = SessionSyncInfo.syncViewport; - } -} - -void -FHoudiniEngine::UploadSessionSyncInfoToHoudini() -{ - // No need to set sessionsync info if we're not using session sync - if (!bEnableSessionSync) - return; - - // Set the Session Sync settings to Houdini - HAPI_SessionSyncInfo SessionSyncInfo; - SessionSyncInfo.cookUsingHoudiniTime = bCookUsingHoudiniTime; - SessionSyncInfo.syncViewport = bSyncViewport; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetSessionSyncInfo(&Session, &SessionSyncInfo)) - HOUDINI_LOG_WARNING(TEXT("Failed to set the SessionSync Infos.")); -} - -void -FHoudiniEngine::StartPDGCommandlet() -{ - if (HoudiniEngineManager) - HoudiniEngineManager->StartPDGCommandlet(); -} - -void -FHoudiniEngine::StopPDGCommandlet() -{ - if (HoudiniEngineManager) - HoudiniEngineManager->StopPDGCommandlet(); -} - -bool -FHoudiniEngine::IsPDGCommandletRunningOrConnected() -{ - if (HoudiniEngineManager) - return HoudiniEngineManager->IsPDGCommandletRunningOrConnected(); - return false; -} - -EHoudiniBGEOCommandletStatus -FHoudiniEngine::GetPDGCommandletStatus() -{ - if (HoudiniEngineManager) - return HoudiniEngineManager->GetPDGCommandletStatus(); - return EHoudiniBGEOCommandletStatus::NotStarted; -} - -void -FHoudiniEngine::UnregisterPostEngineInitCallback() -{ - if (PostEngineInitCallback.IsValid()) - FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitCallback); -} - -bool FHoudiniEngine::IsSyncWithHoudiniCookEnabled() const -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - return HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bSyncWithHoudiniCook : false; -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngine.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineScheduler.h" +#include "HoudiniEngineManager.h" +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniAssetComponent.h" +#include "HAPI/HAPI_Version.h" + +#include "Modules/ModuleManager.h" +#include "Misc/ScopeLock.h" +#include "Engine/StaticMesh.h" +#include "Materials/Material.h" +#include "ISettingsModule.h" +#include "HAL/PlatformFilemanager.h" +#include "Async/Async.h" +#include "Logging/LogMacros.h" + +#if WITH_EDITOR + #include "Widgets/Notifications/SNotificationList.h" + #include "Framework/Notifications/NotificationManager.h" +#endif + +#define LOCTEXT_NAMESPACE "HoudiniEngine" + +IMPLEMENT_MODULE(FHoudiniEngine, HoudiniEngine) +DEFINE_LOG_CATEGORY( LogHoudiniEngine ); + +FHoudiniEngine * +FHoudiniEngine::HoudiniEngineInstance = nullptr; + +FHoudiniEngine::FHoudiniEngine() + : LicenseType(HAPI_LICENSE_NONE) + , HoudiniEngineSchedulerThread(nullptr) + , HoudiniEngineScheduler(nullptr) + , HoudiniEngineManagerThread(nullptr) + , HoudiniEngineManager(nullptr) + //, bHAPIVersionMismatch(false) + , bEnableCookingGlobal(true) + , UIRefreshCountWhenPauseCooking(0) + , bFirstSessionCreated(false) + , bEnableSessionSync(false) + , bCookUsingHoudiniTime(true) + , bSyncViewport(false) + , bSyncHoudiniViewport(true) + , bSyncUnrealViewport(false) + , HoudiniLogoStaticMesh(nullptr) + , HoudiniDefaultMaterial(nullptr) + , HoudiniTemplateMaterial(nullptr) + , HoudiniLogoBrush(nullptr) + , HoudiniDefaultReferenceMesh(nullptr) + , HoudiniDefaultReferenceMeshMaterial(nullptr) +{ + Session.type = HAPI_SESSION_MAX; + Session.id = -1; + + +#if WITH_EDITOR + HapiNotificationStarted = 0.0; +#endif +} + +FHoudiniEngine& +FHoudiniEngine::Get() +{ + check(FHoudiniEngine::HoudiniEngineInstance); + return *FHoudiniEngine::HoudiniEngineInstance; +} + +bool +FHoudiniEngine::IsInitialized() +{ + return FHoudiniEngine::HoudiniEngineInstance != nullptr && FHoudiniEngineUtils::IsInitialized(); +} + +void +FHoudiniEngine::StartupModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine module...")); + +#if WITH_EDITOR + // Register settings. + if (ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->RegisterSettings( + "Project", "Plugins", "HoudiniEngine", + LOCTEXT("RuntimeSettingsName", "Houdini Engine"), + LOCTEXT("RuntimeSettingsDescription", "Configure the HoudiniEngine plugin"), + GetMutableDefault< UHoudiniRuntimeSettings >()); + } +#endif + + // Before starting the module, we need to locate and load HAPI library. + { + void * HAPILibraryHandle = FHoudiniEngineUtils::LoadLibHAPI(LibHAPILocation); + if ( HAPILibraryHandle ) + { + FHoudiniApi::InitializeHAPI( HAPILibraryHandle ); + } + else + { + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + HOUDINI_LOG_MESSAGE(TEXT("Failed locating or loading %s"), *LibHAPIName); + } + } + + // Create static mesh Houdini logo. + HoudiniLogoStaticMesh = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_LOGO, nullptr, LOAD_None, nullptr); + if (HoudiniLogoStaticMesh.IsValid()) + HoudiniLogoStaticMesh->AddToRoot(); + + // Create default material. + HoudiniDefaultMaterial = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultMaterial.IsValid()) + HoudiniDefaultMaterial->AddToRoot(); + + HoudiniTemplateMaterial = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniTemplateMaterial.IsValid()) + HoudiniTemplateMaterial->AddToRoot(); + + // Houdini Logo Brush + FString Icon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icon128.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) + { + const FName BrushName(*Icon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Logo Brush + FString HEIcon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_logo_128.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) + { + const FName BrushName(*HEIcon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Create Houdini default reference mesh + HoudiniDefaultReferenceMesh = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultReferenceMesh.IsValid()) + HoudiniDefaultReferenceMesh->AddToRoot(); + + // Create Houdini default reference mesh material + HoudiniDefaultReferenceMeshMaterial = LoadObject + (nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultReferenceMeshMaterial.IsValid()) + HoudiniDefaultReferenceMeshMaterial->AddToRoot(); + + // We do not automatically try to start a session when starting up the module now. + bFirstSessionCreated = false; + + // Create HAPI scheduler and processing thread. + HoudiniEngineScheduler = new FHoudiniEngineScheduler(); + HoudiniEngineSchedulerThread = FRunnableThread::Create( + HoudiniEngineScheduler, TEXT("HoudiniSchedulerThread"), 0, TPri_Normal); + + // Create Houdini Asset Manager + HoudiniEngineManager = new FHoudiniEngineManager(); + + // Set the default value for pausing houdini engine cooking + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bEnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; + + // Check if a null session is set + bool bNoneSession = (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None); + + // Initialize the singleton with this instance + FHoudiniEngine::HoudiniEngineInstance = this; + + // See if we need to start the manager ticking if needed + // Don tick if we failed to load HAPI, if cooking is disabled or if we're using a null session + if (FHoudiniApi::IsHAPIInitialized()) + { + if (bEnableCookingGlobal && !bNoneSession) + { + PostEngineInitCallback = FCoreDelegates::OnPostEngineInit.AddLambda([]() + { + FHoudiniEngine& HEngine = FHoudiniEngine::Get(); + HEngine.UnregisterPostEngineInitCallback(); + FHoudiniEngineManager* const Manager = HEngine.GetHoudiniEngineManager(); + if (Manager) + Manager->StartHoudiniTicking(); + }); + } + } +} + +void +FHoudiniEngine::ShutdownModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine module.")); + + // We no longer need the Houdini logo static mesh. + if (HoudiniLogoStaticMesh.IsValid()) + { + HoudiniLogoStaticMesh->RemoveFromRoot(); + HoudiniLogoStaticMesh = nullptr; + } + + // We no longer need the Houdini default material. + if (HoudiniDefaultMaterial.IsValid()) + { + HoudiniDefaultMaterial->RemoveFromRoot(); + HoudiniDefaultMaterial = nullptr; + } + + // We no longer need the Houdini default material. + if (HoudiniTemplateMaterial.IsValid()) + { + HoudiniTemplateMaterial->RemoveFromRoot(); + HoudiniTemplateMaterial = nullptr; + } + + // We no longer need the Houdini default reference mesh + if (HoudiniDefaultReferenceMesh.IsValid()) + { + HoudiniDefaultReferenceMesh->RemoveFromRoot(); + HoudiniDefaultReferenceMesh = nullptr; + } + + // We no longer need the Houdini default reference mesh material + if (HoudiniDefaultReferenceMeshMaterial.IsValid()) + { + HoudiniDefaultReferenceMeshMaterial->RemoveFromRoot(); + HoudiniDefaultReferenceMeshMaterial = nullptr; + } + /* + // We no longer need Houdini digital asset used for loading bgeo files. + if (HoudiniBgeoAsset.IsValid()) + { + HoudiniBgeoAsset->RemoveFromRoot(); + HoudiniBgeoAsset = nullptr; + } + */ + +#if WITH_EDITOR + // Unregister settings. + ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings"); + if (SettingsModule) + SettingsModule->UnregisterSettings("Project", "Plugins", "HoudiniEngine"); +#endif + + // Do scheduler and thread clean up. + if (HoudiniEngineScheduler) + HoudiniEngineScheduler->Stop(); + + if (HoudiniEngineSchedulerThread) + { + //HoudiniEngineSchedulerThread->Kill( true ); + HoudiniEngineSchedulerThread->WaitForCompletion(); + + delete HoudiniEngineSchedulerThread; + HoudiniEngineSchedulerThread = nullptr; + } + + if ( HoudiniEngineScheduler ) + { + delete HoudiniEngineScheduler; + HoudiniEngineScheduler = nullptr; + } + + // Do manager clean up. + if (HoudiniEngineManager) + HoudiniEngineManager->StopHoudiniTicking(); + + if (HoudiniEngineManager) + { + delete HoudiniEngineManager; + HoudiniEngineManager = nullptr; + } + + // Perform HAPI finalization. + if ( FHoudiniApi::IsHAPIInitialized() ) + { + FHoudiniApi::Cleanup(GetSession()); + FHoudiniApi::CloseSession(GetSession()); + } + + FHoudiniApi::FinalizeHAPI(); + + FHoudiniEngine::HoudiniEngineInstance = nullptr; +} + +void +FHoudiniEngine::AddTask(const FHoudiniEngineTask & InTask) +{ + if ( HoudiniEngineScheduler ) + HoudiniEngineScheduler->AddTask(InTask); + + FScopeLock ScopeLock(&CriticalSection); + FHoudiniEngineTaskInfo TaskInfo; + TaskInfo.TaskType = InTask.TaskType; + TaskInfo.TaskState = EHoudiniEngineTaskState::Working; + + TaskInfos.Add(InTask.HapiGUID, TaskInfo); +} + +void +FHoudiniEngine::AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo) +{ + FScopeLock ScopeLock(&CriticalSection); + TaskInfos.Add(InHapiGUID, InTaskInfo); +} + +void +FHoudiniEngine::RemoveTaskInfo(const FGuid& InHapiGUID) +{ + FScopeLock ScopeLock(&CriticalSection); + TaskInfos.Remove(InHapiGUID); +} + +bool +FHoudiniEngine::RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo) +{ + FScopeLock ScopeLock(&CriticalSection); + + if (TaskInfos.Contains(InHapiGUID)) + { + OutTaskInfo = TaskInfos[InHapiGUID]; + return true; + } + + return false; +} + +/* +void +FHoudiniEngine::AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + if (HoudiniEngineManager) + HoudiniEngineManager->AddComponent(HAC); +} +*/ + +const FString & +FHoudiniEngine::GetLibHAPILocation() const +{ + return LibHAPILocation; +} + +const HAPI_Session * +FHoudiniEngine::GetSession() const +{ + return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; +} + +HAPI_CookOptions +FHoudiniEngine::GetDefaultCookOptions() +{ + // Default CookOptions + HAPI_CookOptions CookOptions; + FHoudiniApi::CookOptions_Init(&CookOptions); + + CookOptions.curveRefineLOD = 8.0f; + CookOptions.clearErrorsAndWarnings = false; + CookOptions.maxVerticesPerPrimitive = 3; + CookOptions.splitGeosByGroup = false; + CookOptions.splitGeosByAttribute = false; + CookOptions.splitAttrSH = 0; + CookOptions.refineCurveToLinear = true; + CookOptions.handleBoxPartTypes = false; + CookOptions.handleSpherePartTypes = false; + CookOptions.splitPointsByVertexAttributes = false; + CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; + CookOptions.cookTemplatedGeos = true; + + return CookOptions; +} + +bool +FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, + const bool& StartAutomaticServer, + const float& AutomaticServerTimeout, + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const int32& ServerPort, + const FString& ServerHost) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + // Only start a new Session if we dont already have a valid one + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) + return true; + + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; + + HAPI_ThriftServerOptions ServerOptions; + FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); + ServerOptions.autoClose = true; + ServerOptions.timeoutMs = AutomaticServerTimeout; + + // Unless we automatically start the server, + // consider we're in SessionSync mode + bEnableSessionSync = true; + + auto UpdatePathForServer = [&] + { + // Modify our PATH so that HARC will find HARS.exe + const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); + + FString OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); + + FString ModifiedPath = +#if PLATFORM_MAC + // On Mac our binaries are split between two folders + LibHAPILocation + TEXT("/../Resources/bin") + PathDelimiter + +#endif + LibHAPILocation + PathDelimiter + OrigPathVar; + + FPlatformMisc::SetEnvironmentVar(TEXT("PATH"), *ModifiedPath); + }; + + switch ( SessionType ) + { + case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: + { + // Try to connect to an existing socket session first + SessionResult = FHoudiniApi::CreateThriftSocketSession( + SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort ); + + // Start a session and try to connect to it if we failed + if ( StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS ) + { + UpdatePathForServer(); + FHoudiniApi::StartThriftSocketServer( + &ServerOptions, ServerPort, nullptr); + + // We've started the server manually, disable session sync + bEnableSessionSync = false; + + SessionResult = FHoudiniApi::CreateThriftSocketSession( + SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort); + } + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: + { + // Try to connect to an existing pipe session first + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8(*ServerPipeName) ); + + // Start a session and try to connect to it if we failed + if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) + { + UpdatePathForServer(); + FHoudiniApi::StartThriftNamedPipeServer( + &ServerOptions, TCHAR_TO_UTF8(*ServerPipeName), nullptr); + + // We've started the server manually, disable session sync + bEnableSessionSync = false; + + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8(*ServerPipeName)); + } + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_None: + { + HOUDINI_LOG_MESSAGE(TEXT("Session type set to None, Cooking is disabled.")); + // Disable session sync + bEnableSessionSync = false; + break; + } + + // As of Unreal 4.19, InProcess sessions are not supported anymore + case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + default: + HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine session type")); + // Disable session sync + bEnableSessionSync = false; + break; + } + + if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) + { + // Disable session sync as well? + bEnableSessionSync = false; + return false; + } + + // Update this session's license type + HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( + SessionPtr, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); + + return true; +} + +bool +FHoudiniEngine::SessionSyncConnect( + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const FString& ServerHost, + const int32& ServerPort) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + // Only start a new Session if we dont already have a valid one + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(&Session)) + return true; + + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + + HAPI_ThriftServerOptions ServerOptions; + FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); + ServerOptions.autoClose = true; + ServerOptions.timeoutMs = HoudiniRuntimeSettings->AutomaticServerTimeout; + + switch (SessionType) + { + case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: + { + // Try to connect to an existing socket session first + SessionResult = FHoudiniApi::CreateThriftSocketSession( + &Session, TCHAR_TO_UTF8(*ServerHost), ServerPort); + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: + { + // Try to connect to an existing pipe session first + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + &Session, TCHAR_TO_UTF8(*ServerPipeName)); + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_None: + case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + default: + HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine Session Sync Type!!")); + bEnableSessionSync = false; + break; + } + + if (SessionResult != HAPI_RESULT_SUCCESS) + return false; + + // Enable session sync + bEnableSessionSync = true; + + // Update this session's license type + HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( + &Session, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); + + // Update the default viewport sync settings + bSyncViewport = HoudiniRuntimeSettings->bSyncViewport; + bSyncHoudiniViewport = HoudiniRuntimeSettings->bSyncHoudiniViewport; + bSyncUnrealViewport = HoudiniRuntimeSettings->bSyncUnrealViewport; + + return true; +} + +bool +FHoudiniEngine::InitializeHAPISession() +{ + // The HAPI stubs needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The Houdini API stubs have not been properly initialized.")); + return false; + } + + // We need a Valid Session + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(GetSession())) + { + HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The session is invalid.")); + return false; + } + + // Now, initialize HAPI with the new session + // We need to make sure HAPI version is correct. + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + // 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); + + // Compare defined and running versions. + if (RunningEngineMajor != HAPI_VERSION_HOUDINI_ENGINE_MAJOR + || RunningEngineMinor != HAPI_VERSION_HOUDINI_ENGINE_MINOR) + { + // Major or minor HAPI version differs, stop here + HOUDINI_LOG_ERROR( + TEXT("Starting up the Houdini Engine module failed: built and running versions do not match.")); + HOUDINI_LOG_ERROR( + TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), + HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, + RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + + // Display an error message + + // + return false; + + } + else if (RunningEngineApi != HAPI_VERSION_HOUDINI_ENGINE_API) + { + // Major/minor HAPIversions match, but only the API version differs, + // Allow the user to continue but warn him of possible instabilities + HOUDINI_LOG_WARNING( + TEXT("Starting up the Houdini Engine module: built and running versions do not match.")); + HOUDINI_LOG_WARNING( + TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), + HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, + RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + HOUDINI_LOG_WARNING( + TEXT("This could cause instabilities and crashes when using the Houdini Engine plugin")); + } + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Default CookOptions + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + + bool bUseCookingThread = true; + HAPI_Result Result = FHoudiniApi::Initialize( + &Session, + &CookOptions, + bUseCookingThread, + HoudiniRuntimeSettings->CookingThreadStackSize, + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->HoudiniEnvironmentFiles), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->OtlSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->DsoSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->ImageDsoSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->AudioDsoSearchPath)); + + if (Result == HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module.")); + } + else if (Result == HAPI_RESULT_ALREADY_INITIALIZED) + { + // Reused session? just notify the user + HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module - HAPI was already initialzed.")); + } + else + { + HOUDINI_LOG_ERROR( + TEXT("Starting up the Houdini Engine module failed: %s"), + *FHoudiniEngineUtils::GetErrorDescription(Result)); + + return false; + } + + // Let HAPI know we are running inside UE4 + FHoudiniApi::SetServerEnvString(&Session, HAPI_ENV_CLIENT_NAME, HAPI_UNREAL_CLIENT_NAME); + + if (bEnableSessionSync) + { + // Set the session sync infos if needed + UploadSessionSyncInfoToHoudini(); + + // Indicate that Session Sync is enabled + FString Notification = TEXT("Houdini Engine Session Sync enabled."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Session Sync enabled.")); + } + + return true; +} + + +void +FHoudiniEngine::OnSessionLost() +{ + // Mark the session as invalid + Session.id = -1; + Session.type = HAPI_SESSION_MAX; + bEnableSessionSync = false; + HoudiniEngineManager->StopHoudiniTicking(); + + // This indicates that we likely have lost the session due to a crash in HARS/Houdini + FString Notification = TEXT("Houdini Engine Session lost!"); + FHoudiniEngineUtils::CreateSlateNotification(Notification, 2.0, 4.0); + + HOUDINI_LOG_ERROR(TEXT("Houdini Engine Session lost! This could be caused by a crash in HARS.")); +} + +bool +FHoudiniEngine::StopSession() +{ + HAPI_Session* SessionPtr = &Session; + return StopSession(SessionPtr); +} + +bool +FHoudiniEngine::StopSession(HAPI_Session*& SessionPtr) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) + { + // SessionPtr is valid, clean up and close the session + FHoudiniApi::Cleanup(SessionPtr); + FHoudiniApi::CloseSession(SessionPtr); + } + + Session.id = -1; + Session.type = HAPI_SESSION_MAX; + bEnableSessionSync = false; + + HoudiniEngineManager->StopHoudiniTicking(); + + return true; +} + +bool +FHoudiniEngine::RestartSession() +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Starting the Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + if (!StopSession(SessionPtr)) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); + } + else + { + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + HoudiniRuntimeSettings->bStartAutomaticServer, + HoudiniRuntimeSettings->AutomaticServerTimeout, + HoudiniRuntimeSettings->SessionType, + HoudiniRuntimeSettings->ServerPipeName, + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to start the new Session")); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); + } + else + { + bSuccess = true; + } + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +bool +FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName) +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Create the Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + true, + HoudiniRuntimeSettings->AutomaticServerTimeout, + SessionType, + OverrideServerPipeName == NAME_None ? HoudiniRuntimeSettings->ServerPipeName : OverrideServerPipeName.ToString(), + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine Session")); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine session - Failed to initialize HAPI")); + } + else + { + bSuccess = true; + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +bool +FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType) +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Connecting to a Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + false, + HoudiniRuntimeSettings->AutomaticServerTimeout, + SessionType, + HoudiniRuntimeSettings->ServerPipeName, + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine Session")); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine session - Failed to initialize HAPI")); + } + else + { + bSuccess = true; + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +void +FHoudiniEngine::StartTicking() +{ + // Finish the notification and display the results + FString StatusText = TEXT("Houdini Engine session connected."); + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + + HoudiniEngineManager->StartHoudiniTicking(); +} + +void +FHoudiniEngine::StopTicking() +{ + // Finish the notification and display the results + FString StatusText = TEXT("Failed to start the Houdini Engine session..."); + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + + HoudiniEngineManager->StopHoudiniTicking(); + + HAPI_Session* SessionPtr = &Session; + StopSession(SessionPtr); +} + +bool +FHoudiniEngine::IsCookingEnabled() const +{ + return bEnableCookingGlobal; +} + +void +FHoudiniEngine::SetCookingEnabled(const bool& bInEnableCooking) +{ + bEnableCookingGlobal = bInEnableCooking; +} + +bool +FHoudiniEngine::GetFirstSessionCreated() const +{ + return bFirstSessionCreated; +} + +bool +FHoudiniEngine::CreateTaskSlateNotification( + const FText& InText, const bool& bForceNow, const float& NotificationExpire, const float& NotificationFadeOut) +{ +#if WITH_EDITOR + static double NotificationUpdateFrequency = 2.0f; + + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return false; + + if (!bForceNow) + { + if ((FPlatformTime::Seconds() - HapiNotificationStarted) < NotificationUpdateFrequency) + return false; + } + + if (!NotificationPtr.IsValid()) + { + FNotificationInfo Info(InText); + Info.bFireAndForget = false; + Info.FadeOutDuration = NotificationFadeOut; + Info.ExpireDuration = NotificationExpire; + TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + /* + if (!IsPIEActive()) + */ + + NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + } +#endif + + return true; +} + +bool +FHoudiniEngine::UpdateTaskSlateNotification(const FText& InText) +{ +#if WITH_EDITOR + // task is till running + // Just update the slate notification + TSharedPtr NotificationItem = NotificationPtr.Pin(); + if (NotificationItem.IsValid()) + NotificationItem->SetText(InText); +#endif + + return true; +} + +bool +FHoudiniEngine::FinishTaskSlateNotification(const FText& InText) +{ +#if WITH_EDITOR + if (NotificationPtr.IsValid()) + { + TSharedPtr NotificationItem = NotificationPtr.Pin(); + if (NotificationItem.IsValid()) + { + NotificationItem->SetText(InText); + NotificationItem->ExpireAndFadeout(); + + NotificationPtr.Reset(); + } + } +#endif + + return true; +} + +void +FHoudiniEngine::UpdateSessionSyncInfoFromHoudini() +{ + if (!bEnableSessionSync) + return; + + // Set the Session Sync settings to Houdini + HAPI_SessionSyncInfo SessionSyncInfo; + //FHoudiniApi::SessionSyncInfo_Create(&SessionSyncInfo); + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetSessionSyncInfo(&Session, &SessionSyncInfo)) + { + bCookUsingHoudiniTime = SessionSyncInfo.cookUsingHoudiniTime; + bSyncViewport = SessionSyncInfo.syncViewport; + } +} + +void +FHoudiniEngine::UploadSessionSyncInfoToHoudini() +{ + // No need to set sessionsync info if we're not using session sync + if (!bEnableSessionSync) + return; + + // Set the Session Sync settings to Houdini + HAPI_SessionSyncInfo SessionSyncInfo; + SessionSyncInfo.cookUsingHoudiniTime = bCookUsingHoudiniTime; + SessionSyncInfo.syncViewport = bSyncViewport; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetSessionSyncInfo(&Session, &SessionSyncInfo)) + HOUDINI_LOG_WARNING(TEXT("Failed to set the SessionSync Infos.")); +} + +void +FHoudiniEngine::StartPDGCommandlet() +{ + if (HoudiniEngineManager) + HoudiniEngineManager->StartPDGCommandlet(); +} + +void +FHoudiniEngine::StopPDGCommandlet() +{ + if (HoudiniEngineManager) + HoudiniEngineManager->StopPDGCommandlet(); +} + +bool +FHoudiniEngine::IsPDGCommandletRunningOrConnected() +{ + if (HoudiniEngineManager) + return HoudiniEngineManager->IsPDGCommandletRunningOrConnected(); + return false; +} + +EHoudiniBGEOCommandletStatus +FHoudiniEngine::GetPDGCommandletStatus() +{ + if (HoudiniEngineManager) + return HoudiniEngineManager->GetPDGCommandletStatus(); + return EHoudiniBGEOCommandletStatus::NotStarted; +} + +void +FHoudiniEngine::UnregisterPostEngineInitCallback() +{ + if (PostEngineInitCallback.IsValid()) + FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitCallback); +} + +bool FHoudiniEngine::IsSyncWithHoudiniCookEnabled() const +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + return HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bSyncWithHoudiniCook : false; +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.h b/Source/HoudiniEngine/Private/HoudiniEngine.h index 6fef65818..e5755b0d4 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.h +++ b/Source/HoudiniEngine/Private/HoudiniEngine.h @@ -1,308 +1,308 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineTaskInfo.h" -#include "HoudiniRuntimeSettings.h" - -#include "Modules/ModuleInterface.h" - -class FRunnableThread; -class FHoudiniEngineScheduler; -class FHoudiniEngineManager; -class UHoudiniAssetComponent; -class UStaticMesh; -class UMaterial; - -struct FSlateDynamicImageBrush; - -enum class EHoudiniBGEOCommandletStatus : uint8; - -// Not using the IHoudiniEngine interface for now -class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface -{ - public: - - FHoudiniEngine(); - - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // Return singleton instance of Houdini Engine, used internally. - static FHoudiniEngine & Get(); - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // Return the location of the currently loaded LibHAPI - virtual const FString & GetLibHAPILocation() const; - - // Session accessor - virtual const HAPI_Session* GetSession() const; - - // Default cook options - static HAPI_CookOptions GetDefaultCookOptions(); - - // Creates a new session - bool StartSession( - HAPI_Session*& SessionPtr, - const bool& StartAutomaticServer, - const float& AutomaticServerTimeout, - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const int32& ServerPort, - const FString& ServerHost); - - // Stop the current session if it is valid - bool StopSession(HAPI_Session*& SessionPtr); - - // Creates a session sync session - bool SessionSyncConnect( - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const FString& ServerHost, - const int32& ServerPort); - - // Stops the current session - bool StopSession(); - // Stops, then creates a new session - bool RestartSession(); - // Creates a session, start HARS - bool CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName=NAME_None); - // Connect to an existing HE session - bool ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType); - - // Starts the HoudiniEngineManager ticking - void StartTicking(); - // Stops the HoudiniEngineManager ticking and invalidate the session - void StopTicking(); - - // Initialize HAPI - bool InitializeHAPISession(); - - // Indicate to the plugin that the session is now invalid (HAPI has likely crashed...) - void OnSessionLost(); - - bool CreateTaskSlateNotification( - const FText& InText, - const bool& bForceNow = false, - const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, - const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); - - bool UpdateTaskSlateNotification(const FText& InText); - bool FinishTaskSlateNotification(const FText& InText); - - void SetHapiNotificationStartedTime(const double& InTime) { HapiNotificationStarted = InTime; }; - - // Register task for execution. - virtual void AddTask(const FHoudiniEngineTask & InTask); - // Register task info. - virtual void AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo); - // Remove task info. - virtual void RemoveTaskInfo(const FGuid& InHapiGUID); - // Remove task info. - virtual bool RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo); - // Register asset to the manager - //virtual void AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC); - - // Indicates whether or not cooking is currently enabled - bool IsCookingEnabled() const; - // Sets whether or not cooking is currently enabled - void SetCookingEnabled(const bool& bInEnableCooking); - - // Check if we need to refresh UI when cooking is paused - bool HasUIFinishRefreshingWhenPausingCooking() const { return UIRefreshCountWhenPauseCooking <= 0; }; - - // Reset number of registered HACs when cooking is paused - void SetUIRefreshCountWhenPauseCooking(const int32& bInCount) { UIRefreshCountWhenPauseCooking = bInCount; }; - // Reduce the count by 1 when an HAC UI is refreshed when cooking is paused - void RefreshUIDisplayedWhenPauseCooking() { UIRefreshCountWhenPauseCooking -= 1; }; - - // Indicates whether or not the first attempt to create a Houdini session was made - bool GetFirstSessionCreated() const; - // Sets whether or not the first attempt to create a Houdini session was made - void SetFirstSessionCreated(const bool& bInStarted) { bFirstSessionCreated = bInStarted; }; - - bool IsSessionSyncEnabled() const { return bEnableSessionSync; }; - - bool IsSyncWithHoudiniCookEnabled() const; - - bool IsCookUsingHoudiniTimeEnabled() const { return bCookUsingHoudiniTime; }; - - bool IsSyncViewportEnabled() const { return bSyncViewport; }; - - bool IsSyncHoudiniViewportEnabled() const { return bSyncHoudiniViewport; }; - - bool IsSyncUnrealViewportEnabled() const { return bSyncUnrealViewport; }; - - // Helper function to update our session sync infos from Houdini's - void UpdateSessionSyncInfoFromHoudini(); - - // Helper function to update Houdini's Session sync infos from ours - void UploadSessionSyncInfoToHoudini(); - - // Sets whether or not viewport sync is enabled - void SetSyncViewportEnabled(const bool& bInSync) { bSyncViewport = bInSync; }; - // Sets whether or not we want to sync the houdini viewport to unreal's - void SetSyncHoudiniViewportEnabled(const bool& bInSync) { bSyncHoudiniViewport = bInSync; }; - // Sets whether or not we want to sync unreal's viewport to Houdini's - void SetSyncUnrealViewportEnabled(const bool& bInSync) { bSyncUnrealViewport = bInSync; }; - - // Returns the default Houdini Logo Static Mesh - virtual TWeakObjectPtr GetHoudiniLogoStaticMesh() const { return HoudiniLogoStaticMesh; }; - - // Returns either the default Houdini material or the default template material - virtual TWeakObjectPtr GetHoudiniDefaultMaterial(const bool& bIsTemplate) const { return bIsTemplate ? HoudiniTemplateMaterial : HoudiniDefaultMaterial; }; - - // Returns the default Houdini material - virtual TWeakObjectPtr GetHoudiniDefaultMaterial() const { return HoudiniDefaultMaterial; }; - // Returns the default template Houdini material - virtual TWeakObjectPtr GetHoudiniTemplatedMaterial() const { return HoudiniTemplateMaterial; }; - // Returns a shared Ptr to the houdini logo - TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; - // Returns a shared Ptr to the houdini engine logo - TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; - - // Returns the default Houdini reference mesh - virtual TWeakObjectPtr GetHoudiniDefaultReferenceMesh() const { return HoudiniDefaultReferenceMesh; }; - // Returns the default Houdini reference mesh material - virtual TWeakObjectPtr GetHoudiniDefaultReferenceMeshMaterial() const { return HoudiniDefaultReferenceMeshMaterial; }; - - const HAPI_License GetLicenseType() const { return LicenseType; }; - - const bool IsLicenseIndie() const { return (LicenseType == HAPI_LICENSE_HOUDINI_ENGINE_INDIE || LicenseType == HAPI_LICENSE_HOUDINI_INDIE); }; - - // Session Sync ProcHandle accessor - FProcHandle GetHESSProcHandle() const { return HESS_ProcHandle; }; - void SetHESSProcHandle(const FProcHandle& InProcHandle) { HESS_ProcHandle = InProcHandle; }; - - void StartPDGCommandlet(); - - void StopPDGCommandlet(); - - bool IsPDGCommandletRunningOrConnected(); - - EHoudiniBGEOCommandletStatus GetPDGCommandletStatus(); - - FHoudiniEngineManager* GetHoudiniEngineManager() { return HoudiniEngineManager; } - - const FHoudiniEngineManager* GetHoudiniEngineManager() const { return HoudiniEngineManager; } - - void UnregisterPostEngineInitCallback(); - - private: - - // Singleton instance of Houdini Engine. - static FHoudiniEngine * HoudiniEngineInstance; - - // Location of libHAPI binary. - FString LibHAPILocation; - - // The Houdini Engine session. - HAPI_Session Session; - - // The type of HE license used by the current session - HAPI_License LicenseType; - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // Map of task statuses. - TMap TaskInfos; - - // Thread used to execute the scheduler. - FRunnableThread * HoudiniEngineSchedulerThread; - // Scheduler used to schedule HAPI instantiation and cook tasks. - FHoudiniEngineScheduler * HoudiniEngineScheduler; - - // Thread used to execute the manager. - FRunnableThread * HoudiniEngineManagerThread; - // Scheduler used to monitor and process Houdini Asset Components - FHoudiniEngineManager * HoudiniEngineManager; - - // Process Handle for session sync - FProcHandle HESS_ProcHandle; - - // Is set to true when mismatch between defined and running HAPI versions is detected. - //bool bHAPIVersionMismatch; - - // Global cooking flag, used to pause HEngine while using the editor - bool bEnableCookingGlobal; - // Counter of HACs that need to be refreshed when pause cooking - int32 UIRefreshCountWhenPauseCooking; - - // Indicates that the first attempt to create a session has been done - // This is to delay the first "automatic" session creation for the first cook - // or instantiation rather than when the module started. - bool bFirstSessionCreated; - - // Indicates if the current session is a SessionSync one - bool bEnableSessionSync; - - // If true and we're in SessionSync, keeps the assets on the plugin side synchronized with changes on the Houdini side. - //bool bSyncWithHoudiniCook; - - // If true and we're in SessionSync, use the Houdini Timeline time to cook assets. - bool bCookUsingHoudiniTime; - - // If true and we're in Session Sync, the Houdini and Unreal viewport will be synchronized. - bool bSyncViewport; - // If true and we're in Session Sync, the Houdini viewport will be synchronized to Unreal's. - bool bSyncHoudiniViewport; - // If true and we're in Session Sync, the Unreal viewport will be synchronized to Houdini's. - bool bSyncUnrealViewport; - - // Static mesh used for Houdini logo rendering. - TWeakObjectPtr HoudiniLogoStaticMesh; - - // Material used as default material. - TWeakObjectPtr HoudiniDefaultMaterial; - - // Material used as default template material. - TWeakObjectPtr HoudiniTemplateMaterial; - - // Houdini logo brush. - TSharedPtr HoudiniLogoBrush; - // Houdini logo brush. - TSharedPtr HoudiniEngineLogoBrush; - - // Static mesh used for default mesh reference - TWeakObjectPtr HoudiniDefaultReferenceMesh; - - // Material used for default mesh reference - TWeakObjectPtr HoudiniDefaultReferenceMeshMaterial; - - FDelegateHandle PostEngineInitCallback; - -#if WITH_EDITOR - /** Notification used by this component. **/ - TWeakPtr NotificationPtr; - /** Used to delay notification updates for HAPI asynchronous work. **/ - double HapiNotificationStarted; -#endif +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniRuntimeSettings.h" + +#include "Modules/ModuleInterface.h" + +class FRunnableThread; +class FHoudiniEngineScheduler; +class FHoudiniEngineManager; +class UHoudiniAssetComponent; +class UStaticMesh; +class UMaterial; + +struct FSlateDynamicImageBrush; + +enum class EHoudiniBGEOCommandletStatus : uint8; + +// Not using the IHoudiniEngine interface for now +class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface +{ + public: + + FHoudiniEngine(); + + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // Return singleton instance of Houdini Engine, used internally. + static FHoudiniEngine & Get(); + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // Return the location of the currently loaded LibHAPI + virtual const FString & GetLibHAPILocation() const; + + // Session accessor + virtual const HAPI_Session* GetSession() const; + + // Default cook options + static HAPI_CookOptions GetDefaultCookOptions(); + + // Creates a new session + bool StartSession( + HAPI_Session*& SessionPtr, + const bool& StartAutomaticServer, + const float& AutomaticServerTimeout, + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const int32& ServerPort, + const FString& ServerHost); + + // Stop the current session if it is valid + bool StopSession(HAPI_Session*& SessionPtr); + + // Creates a session sync session + bool SessionSyncConnect( + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const FString& ServerHost, + const int32& ServerPort); + + // Stops the current session + bool StopSession(); + // Stops, then creates a new session + bool RestartSession(); + // Creates a session, start HARS + bool CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName=NAME_None); + // Connect to an existing HE session + bool ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType); + + // Starts the HoudiniEngineManager ticking + void StartTicking(); + // Stops the HoudiniEngineManager ticking and invalidate the session + void StopTicking(); + + // Initialize HAPI + bool InitializeHAPISession(); + + // Indicate to the plugin that the session is now invalid (HAPI has likely crashed...) + void OnSessionLost(); + + bool CreateTaskSlateNotification( + const FText& InText, + const bool& bForceNow = false, + const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, + const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); + + bool UpdateTaskSlateNotification(const FText& InText); + bool FinishTaskSlateNotification(const FText& InText); + + void SetHapiNotificationStartedTime(const double& InTime) { HapiNotificationStarted = InTime; }; + + // Register task for execution. + virtual void AddTask(const FHoudiniEngineTask & InTask); + // Register task info. + virtual void AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo); + // Remove task info. + virtual void RemoveTaskInfo(const FGuid& InHapiGUID); + // Remove task info. + virtual bool RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo); + // Register asset to the manager + //virtual void AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC); + + // Indicates whether or not cooking is currently enabled + bool IsCookingEnabled() const; + // Sets whether or not cooking is currently enabled + void SetCookingEnabled(const bool& bInEnableCooking); + + // Check if we need to refresh UI when cooking is paused + bool HasUIFinishRefreshingWhenPausingCooking() const { return UIRefreshCountWhenPauseCooking <= 0; }; + + // Reset number of registered HACs when cooking is paused + void SetUIRefreshCountWhenPauseCooking(const int32& bInCount) { UIRefreshCountWhenPauseCooking = bInCount; }; + // Reduce the count by 1 when an HAC UI is refreshed when cooking is paused + void RefreshUIDisplayedWhenPauseCooking() { UIRefreshCountWhenPauseCooking -= 1; }; + + // Indicates whether or not the first attempt to create a Houdini session was made + bool GetFirstSessionCreated() const; + // Sets whether or not the first attempt to create a Houdini session was made + void SetFirstSessionCreated(const bool& bInStarted) { bFirstSessionCreated = bInStarted; }; + + bool IsSessionSyncEnabled() const { return bEnableSessionSync; }; + + bool IsSyncWithHoudiniCookEnabled() const; + + bool IsCookUsingHoudiniTimeEnabled() const { return bCookUsingHoudiniTime; }; + + bool IsSyncViewportEnabled() const { return bSyncViewport; }; + + bool IsSyncHoudiniViewportEnabled() const { return bSyncHoudiniViewport; }; + + bool IsSyncUnrealViewportEnabled() const { return bSyncUnrealViewport; }; + + // Helper function to update our session sync infos from Houdini's + void UpdateSessionSyncInfoFromHoudini(); + + // Helper function to update Houdini's Session sync infos from ours + void UploadSessionSyncInfoToHoudini(); + + // Sets whether or not viewport sync is enabled + void SetSyncViewportEnabled(const bool& bInSync) { bSyncViewport = bInSync; }; + // Sets whether or not we want to sync the houdini viewport to unreal's + void SetSyncHoudiniViewportEnabled(const bool& bInSync) { bSyncHoudiniViewport = bInSync; }; + // Sets whether or not we want to sync unreal's viewport to Houdini's + void SetSyncUnrealViewportEnabled(const bool& bInSync) { bSyncUnrealViewport = bInSync; }; + + // Returns the default Houdini Logo Static Mesh + virtual TWeakObjectPtr GetHoudiniLogoStaticMesh() const { return HoudiniLogoStaticMesh; }; + + // Returns either the default Houdini material or the default template material + virtual TWeakObjectPtr GetHoudiniDefaultMaterial(const bool& bIsTemplate) const { return bIsTemplate ? HoudiniTemplateMaterial : HoudiniDefaultMaterial; }; + + // Returns the default Houdini material + virtual TWeakObjectPtr GetHoudiniDefaultMaterial() const { return HoudiniDefaultMaterial; }; + // Returns the default template Houdini material + virtual TWeakObjectPtr GetHoudiniTemplatedMaterial() const { return HoudiniTemplateMaterial; }; + // Returns a shared Ptr to the houdini logo + TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; + // Returns a shared Ptr to the houdini engine logo + TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; + + // Returns the default Houdini reference mesh + virtual TWeakObjectPtr GetHoudiniDefaultReferenceMesh() const { return HoudiniDefaultReferenceMesh; }; + // Returns the default Houdini reference mesh material + virtual TWeakObjectPtr GetHoudiniDefaultReferenceMeshMaterial() const { return HoudiniDefaultReferenceMeshMaterial; }; + + const HAPI_License GetLicenseType() const { return LicenseType; }; + + const bool IsLicenseIndie() const { return (LicenseType == HAPI_LICENSE_HOUDINI_ENGINE_INDIE || LicenseType == HAPI_LICENSE_HOUDINI_INDIE); }; + + // Session Sync ProcHandle accessor + FProcHandle GetHESSProcHandle() const { return HESS_ProcHandle; }; + void SetHESSProcHandle(const FProcHandle& InProcHandle) { HESS_ProcHandle = InProcHandle; }; + + void StartPDGCommandlet(); + + void StopPDGCommandlet(); + + bool IsPDGCommandletRunningOrConnected(); + + EHoudiniBGEOCommandletStatus GetPDGCommandletStatus(); + + FHoudiniEngineManager* GetHoudiniEngineManager() { return HoudiniEngineManager; } + + const FHoudiniEngineManager* GetHoudiniEngineManager() const { return HoudiniEngineManager; } + + void UnregisterPostEngineInitCallback(); + + private: + + // Singleton instance of Houdini Engine. + static FHoudiniEngine * HoudiniEngineInstance; + + // Location of libHAPI binary. + FString LibHAPILocation; + + // The Houdini Engine session. + HAPI_Session Session; + + // The type of HE license used by the current session + HAPI_License LicenseType; + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // Map of task statuses. + TMap TaskInfos; + + // Thread used to execute the scheduler. + FRunnableThread * HoudiniEngineSchedulerThread; + // Scheduler used to schedule HAPI instantiation and cook tasks. + FHoudiniEngineScheduler * HoudiniEngineScheduler; + + // Thread used to execute the manager. + FRunnableThread * HoudiniEngineManagerThread; + // Scheduler used to monitor and process Houdini Asset Components + FHoudiniEngineManager * HoudiniEngineManager; + + // Process Handle for session sync + FProcHandle HESS_ProcHandle; + + // Is set to true when mismatch between defined and running HAPI versions is detected. + //bool bHAPIVersionMismatch; + + // Global cooking flag, used to pause HEngine while using the editor + bool bEnableCookingGlobal; + // Counter of HACs that need to be refreshed when pause cooking + int32 UIRefreshCountWhenPauseCooking; + + // Indicates that the first attempt to create a session has been done + // This is to delay the first "automatic" session creation for the first cook + // or instantiation rather than when the module started. + bool bFirstSessionCreated; + + // Indicates if the current session is a SessionSync one + bool bEnableSessionSync; + + // If true and we're in SessionSync, keeps the assets on the plugin side synchronized with changes on the Houdini side. + //bool bSyncWithHoudiniCook; + + // If true and we're in SessionSync, use the Houdini Timeline time to cook assets. + bool bCookUsingHoudiniTime; + + // If true and we're in Session Sync, the Houdini and Unreal viewport will be synchronized. + bool bSyncViewport; + // If true and we're in Session Sync, the Houdini viewport will be synchronized to Unreal's. + bool bSyncHoudiniViewport; + // If true and we're in Session Sync, the Unreal viewport will be synchronized to Houdini's. + bool bSyncUnrealViewport; + + // Static mesh used for Houdini logo rendering. + TWeakObjectPtr HoudiniLogoStaticMesh; + + // Material used as default material. + TWeakObjectPtr HoudiniDefaultMaterial; + + // Material used as default template material. + TWeakObjectPtr HoudiniTemplateMaterial; + + // Houdini logo brush. + TSharedPtr HoudiniLogoBrush; + // Houdini logo brush. + TSharedPtr HoudiniEngineLogoBrush; + + // Static mesh used for default mesh reference + TWeakObjectPtr HoudiniDefaultReferenceMesh; + + // Material used for default mesh reference + TWeakObjectPtr HoudiniDefaultReferenceMeshMaterial; + + FDelegateHandle PostEngineInitCallback; + +#if WITH_EDITOR + /** Notification used by this component. **/ + TWeakPtr NotificationPtr; + /** Used to delay notification updates for HAPI asynchronous work. **/ + double HapiNotificationStarted; +#endif }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp b/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp deleted file mode 100644 index 8a63839cc..000000000 --- a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h b/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h deleted file mode 100644 index bdac5a8a6..000000000 --- a/Source/HoudiniEngine/Private/HoudiniEngineCommandlet.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp index 7a76a16e4..3fc8db540 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp @@ -1,1550 +1,1556 @@ - -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineManager.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniParameterTranslator.h" -#include "HoudiniPDGManager.h" -#include "HoudiniInputTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniHandleTranslator.h" -#include "HoudiniSplineTranslator.h" -#include "Misc/MessageDialog.h" -#include "Misc/ScopedSlowTask.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "EditorViewportClient.h" - #include "Kismet/KismetMathLibrary.h" - - //#include "UnrealEd.h" - #include "UnrealEdGlobals.h" - #include "Editor/UnrealEdEngine.h" - #include "IPackageAutoSaver.h" -#endif - -const float -FHoudiniEngineManager::TickTimerDelay = 0.01f; - -FHoudiniEngineManager::FHoudiniEngineManager() - : CurrentIndex(0) - , ComponentCount(0) - , bMustStopTicking(false) - , SyncedHoudiniViewportPivotPosition(FVector::ZeroVector) - , SyncedHoudiniViewportQuat(FQuat::Identity) - , SyncedHoudiniViewportOffset(0.0f) - , SyncedUnrealViewportPosition(FVector::ZeroVector) - , SyncedUnrealViewportRotation(FRotator::ZeroRotator) - , SyncedUnrealViewportLookatPosition(FVector::ZeroVector) - , ZeroOffsetValue(0.f) - , bOffsetZeroed(false) -{ - -} - -FHoudiniEngineManager::~FHoudiniEngineManager() -{ - PDGManager.StopBGEOCommandletAndEndpoint(); -} - -void -FHoudiniEngineManager::StartHoudiniTicking() -{ - // If we have no timer delegate spawned, spawn one. - if (!TimerDelegateProcess.IsBound() && GEditor) - { - TimerDelegateProcess = FTimerDelegate::CreateRaw(this, &FHoudiniEngineManager::Tick); - GEditor->GetTimerManager()->SetTimer(TimerHandleProcess, TimerDelegateProcess, TickTimerDelay, true); - - // Grab current time for delayed notification. - FHoudiniEngine::Get().SetHapiNotificationStartedTime(FPlatformTime::Seconds()); - } -} - -void -FHoudiniEngineManager::StopHoudiniTicking() -{ - if (TimerDelegateProcess.IsBound() && GEditor) - { - if (IsInGameThread()) - { - GEditor->GetTimerManager()->ClearTimer(TimerHandleProcess); - TimerDelegateProcess.Unbind(); - - // Reset time for delayed notification. - FHoudiniEngine::Get().SetHapiNotificationStartedTime(0.0); - - bMustStopTicking = false; - } - else - { - // We can't stop ticking now as we're not in the game Thread, - // and accessing the timer would crash, indicate that we want to stop ticking asap - // This can happen when loosing a session due to a Houdini crash - bMustStopTicking = true; - } - } -} - -void -FHoudiniEngineManager::Tick() -{ - EnableEditorAutoSave(nullptr); - - if (bMustStopTicking) - { - // Ticking should be stopped immediately - StopHoudiniTicking(); - return; - } - - // Process the current component if possible - while (true) - { - UHoudiniAssetComponent * CurrentComponent = nullptr; - if (FHoudiniEngineRuntime::IsInitialized()) - { - FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); - - //FScopeLock ScopeLock(&CriticalSection); - ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); - - // No work to be done - if (ComponentCount <= 0) - break; - - // Wrap around if needed - if (CurrentIndex >= ComponentCount) - CurrentIndex = 0; - - CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(CurrentIndex); - CurrentIndex++; - } - - if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) - { - // Invalid component, do not process - break; - } - else if (CurrentComponent->IsPendingKill() - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) - { - // Component being deleted, do not process - break; - } - - if (!CurrentComponent->IsFullyLoaded()) - { - // Let the component figure out whether it's fully loaded or not. - CurrentComponent->HoudiniEngineTick(); - if (!CurrentComponent->IsFullyLoaded()) - continue; // We need to wait some more. - } - - if (!CurrentComponent->IsValidComponent()) - { - // This component is no longer valid. Prevent it from being processed, and remove it. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); - continue; - } - - // We don't want to the template component processing to trigger session creation - if (CurrentComponent->GetAssetState() == EHoudiniAssetState::ProcessTemplate) - { - if (CurrentComponent->IsTemplate() && !CurrentComponent->HasOpenEditor()) - { - // This component template no longer has an open editor and can be deregistered. - // TODO: Replace this polling mechanism with an "On Asset Closed" event if we - // can find one that actually works. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); - continue; - } - - if (CurrentComponent->NeedBlueprintStructureUpdate()) - { - CurrentComponent->OnBlueprintStructureModified(); - } - - if (CurrentComponent->NeedBlueprintUpdate()) - { - CurrentComponent->OnBlueprintModified(); - } - - if (FHoudiniEngine::Get().IsCookingEnabled()) - { - // Only process component template parameter updates when cooking is enabled. - if (CurrentComponent->NeedUpdateParameters() || CurrentComponent->NeedUpdateInputs()) - { - CurrentComponent->OnTemplateParametersChanged(); - } - } - - if (CurrentComponent->NeedOutputUpdate()) - { - // TODO: Transfer template output changes over to the preview instance. - } - - break; - } - - // See if we should start the default "first" session - if(!FHoudiniEngine::Get().GetSession() && !FHoudiniEngine::Get().GetFirstSessionCreated()) - { - // Only try to start the default session if we have an "active" HAC - if (CurrentComponent->GetAssetState() == EHoudiniAssetState::PreInstantiation - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Instantiating - || CurrentComponent->GetAssetState() == EHoudiniAssetState::PreCook - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Cooking) - { - FString StatusText = TEXT("Initializing Houdini Engine..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // We want to yield for a bit. - //FPlatformProcess::Sleep(0.5f); - - // Indicates that we've tried to start the session once no matter if it failed or succeed - FHoudiniEngine::Get().SetFirstSessionCreated(true); - - // Attempt to restart the session - if (!FHoudiniEngine::Get().RestartSession()) - { - // We failed to start the session - // Stop ticking until it's manually restarted - StopHoudiniTicking(); - - StatusText = TEXT("Houdini Engine failed to initialize."); - } - else - { - StatusText = TEXT("Houdini Engine successfully initialized."); - } - - // Finish the notification and display the results - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - } - } - - // Process the component - // try to catch (apache::thrift::transport::TTransportException * e) for session loss? - ProcessComponent(CurrentComponent); - break; - } - - // Handle Asset delete - if (FHoudiniEngineRuntime::IsInitialized()) - { - int32 PendingDeleteCount = FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteCount(); - for (int32 DeleteIdx = PendingDeleteCount - 1; DeleteIdx >= 0; DeleteIdx--) - { - HAPI_NodeId NodeIdToDelete = (HAPI_NodeId)FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteAt(DeleteIdx); - FGuid HapiDeletionGUID; - bool bShouldDeleteParent = FHoudiniEngineRuntime::Get().IsParentNodePendingDelete(NodeIdToDelete); - if (StartTaskAssetDelete(NodeIdToDelete, HapiDeletionGUID, bShouldDeleteParent)) - { - FHoudiniEngineRuntime::Get().RemoveNodeIdPendingDeleteAt(DeleteIdx); - if (bShouldDeleteParent) - FHoudiniEngineRuntime::Get().RemoveParentNodePendingDelete(NodeIdToDelete); - } - } - } - - // Update PDG Contexts and asset link if needed - PDGManager.Update(); - - // Session Sync Updates - if (FHoudiniEngine::Get().IsSessionSyncEnabled()) - { - // See if the session sync settings have changed on the houdini side, update ours if they did - FHoudiniEngine::Get().UpdateSessionSyncInfoFromHoudini(); -#if WITH_EDITOR - // Update the Houdini viewport from unreal if needed - if (FHoudiniEngine::Get().IsSyncViewportEnabled()) - { - // Sync the Houdini viewport to Unreal - if (!SyncHoudiniViewportToUnreal()) - { - // If the unreal viewport hasnt changed, - // See if we need to sync the Unreal viewport from Houdini's - SyncUnrealViewportToHoudini(); - } - } -#endif - } - else - { - // reset zero offset variables when session sync is off - if (ZeroOffsetValue != 0.f) - ZeroOffsetValue = 0.f; - - if (bOffsetZeroed) - bOffsetZeroed = false; - } -} - -void -FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - // No need to process component not tied to an asset - if (!HAC->GetHoudiniAsset()) - return; - - // If cooking is paused, stay in the current state until cooking's resumed - if (!FHoudiniEngine::Get().IsCookingEnabled()) - { - // We can only handle output updates - if (HAC->GetAssetState() == EHoudiniAssetState::None && HAC->NeedOutputUpdate()) - { - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Refresh UI when pause cooking - if (!FHoudiniEngine::Get().HasUIFinishRefreshingWhenPausingCooking()) - { - // Trigger a details panel update if the Houdini asset actor is selected - if (HAC->IsOwnerSelected()) - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // Finished refreshing UI of one HDA. - FHoudiniEngine::Get().RefreshUIDisplayedWhenPauseCooking(); - } - - // Prevent any other state change to happen - return; - } - - switch (HAC->GetAssetState()) - { - case EHoudiniAssetState::NeedInstantiation: - { - // Do nothing unless the HAC has been updated - if (HAC->NeedUpdate()) - { - HAC->OnPrePreInstantiation(); - HAC->bForceNeedUpdate = false; - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::PreInstantiation; - } - else if (HAC->NeedOutputUpdate()) - { - // Output updates do not recquire the HDA to be instantiated - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Update world input if we have any - FHoudiniInputTranslator::UpdateWorldInputs(HAC); - - break; - } - - case EHoudiniAssetState::PreInstantiation: - { - // Only proceed forward if we don't need to wait for our input HoudiniAssets to finish cooking/instantiating - if (HAC->NeedsToWaitForInputHoudiniAssets()) - break; - - FGuid TaskGuid; - UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (StartTaskAssetInstantiation(HoudiniAsset, HAC->GetDisplayName(), TaskGuid)) - { - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::Instantiating; - //HAC->AssetStateResult = EHoudiniAssetStateResult::None; - - // Update the Task GUID - HAC->HapiGUID = TaskGuid; - } - else - { - // If we couldnt instantiate the asset - // Change the state to NeedInstantiating - HAC->AssetState = EHoudiniAssetState::NeedInstantiation; - //HAC->AssetStateResult = EHoudiniAssetStateResult::None; - } - break; - } - - case EHoudiniAssetState::Instantiating: - { - EHoudiniAssetState NewState = EHoudiniAssetState::Instantiating; - if (UpdateInstantiating(HAC, NewState)) - { - // We need to update the HAC's state - HAC->AssetState = NewState; - EnableEditorAutoSave(HAC); - } - else - { - DisableEditorAutoSave(HAC); - } - break; - } - - case EHoudiniAssetState::PreCook: - { - // Only proceed forward if we don't need to wait for our input - // HoudiniAssets to finish cooking/instantiating - if (HAC->NeedsToWaitForInputHoudiniAssets()) - break; - - HAC->OnPrePreCook(); - // Update all the HAPI nodes, parameters, inputs etc... - PreCook(HAC); - HAC->OnPostPreCook(); - - // Create a Cooking task only if necessary - bool bCookStarted = false; - if (IsCookingEnabledForHoudiniAsset(HAC)) - { - FGuid TaskGUID = HAC->GetHapiGUID(); - if ( StartTaskAssetCooking(HAC->GetAssetId(), HAC->GetDisplayName(), TaskGUID) ) - { - // Updates the HAC's state - HAC->AssetState = EHoudiniAssetState::Cooking; - HAC->HapiGUID = TaskGUID; - bCookStarted = true; - } - } - - if(!bCookStarted) - { - // Just refresh editor properties? - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // TODO: Check! update state? - HAC->AssetState = EHoudiniAssetState::None; - } - break; - } - - case EHoudiniAssetState::Cooking: - { - EHoudiniAssetState NewState = EHoudiniAssetState::Cooking; - bool state = UpdateCooking(HAC, NewState); - if (state) - { - // We need to update the HAC's state - HAC->AssetState = NewState; - EnableEditorAutoSave(HAC); - } - else - { - DisableEditorAutoSave(HAC); - } - break; - } - - case EHoudiniAssetState::PostCook: - { - // Handle PostCook - EHoudiniAssetState NewState = EHoudiniAssetState::None; - bool bSuccess = HAC->bLastCookSuccess; - HAC->OnPreOutputProcessing(); - if (PostCook(HAC, bSuccess, HAC->GetAssetId())) - { - // Cook was successful, process the results - NewState = EHoudiniAssetState::PreProcess; - } - else - { - // Cook failed, skip output processing - NewState = EHoudiniAssetState::None; - } - HAC->AssetState = NewState; - break; - } - - case EHoudiniAssetState::PreProcess: - { - StartTaskAssetProcess(HAC); - break; - } - - case EHoudiniAssetState::Processing: - { - UpdateProcess(HAC); - - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - - HAC->OnPostOutputProcessing(); - FHoudiniEngineUtils::UpdateBlueprintEditor(HAC); - break; - } - - case EHoudiniAssetState::None: - { - // Do nothing unless the HAC has been updated - if (HAC->NeedUpdate()) - { - HAC->bForceNeedUpdate = false; - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::PreCook; - } - else if (HAC->NeedTransformUpdate()) - { - FHoudiniEngineUtils::UploadHACTransform(HAC); - } - else if (HAC->NeedOutputUpdate()) - { - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Update world inputs if we have any - FHoudiniInputTranslator::UpdateWorldInputs(HAC); - - // See if we need to get an update from Session Sync - if(FHoudiniEngine::Get().IsSessionSyncEnabled() - && FHoudiniEngine::Get().IsSyncWithHoudiniCookEnabled() - && HAC->AssetState == EHoudiniAssetState::None) - { - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - if (CookCount >= 0 && CookCount != HAC->GetAssetCookCount()) - { - // The cook count has changed on the Houdini side, - // this indicates that the user has changed something in Houdini so we need to trigger an update - HAC->AssetState = EHoudiniAssetState::PreCook; - } - } - break; - } - - case EHoudiniAssetState::NeedRebuild: - { - StartTaskAssetRebuild(HAC->AssetId, HAC->HapiGUID); - - HAC->MarkAsNeedCook(); - HAC->AssetState = EHoudiniAssetState::PreInstantiation; - break; - } - - case EHoudiniAssetState::NeedDelete: - { - FGuid HapiDeletionGUID; - StartTaskAssetDelete(HAC->GetAssetId(), HapiDeletionGUID, true); - //HAC->AssetId = -1; - - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::Deleting; - break; - } - - case EHoudiniAssetState::Deleting: - { - break; - } - } -} - - - -bool -FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID) -{ - // Make sure we have a valid session before attempting anything - if (!FHoudiniEngine::Get().GetSession()) - return false; - - OutTaskGUID.Invalidate(); - - // Load the HDA file - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - null or invalid Houdini Asset.")); - return false; - } - - HAPI_AssetLibraryId AssetLibraryId = -1; - if (!FHoudiniEngineUtils::LoadHoudiniAsset(HoudiniAsset, AssetLibraryId) ) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - could not load Houdini Asset.")); - return false; - } - - // Handle hda files that contain multiple assets - TArray< HAPI_StringHandle > AssetNames; - if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - unable to retrieve asset names.")); - return false; - } - - // By default, assume we want to load the first Asset - HAPI_StringHandle PickedAssetName = AssetNames[0]; - -#if WITH_EDITOR - // Should we show the multi asset dialog? - bool bShowMultiAssetDialog = false; - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && AssetNames.Num() > 1) - bShowMultiAssetDialog = HoudiniRuntimeSettings->bShowMultiAssetDialog; - - // TODO: Add multi selection dialog - if (bShowMultiAssetDialog ) - { - // TODO: Implement - FHoudiniEngineUtils::OpenSubassetSelectionWindow(AssetNames, PickedAssetName); - } -#endif - - // Give the HAC a new GUID to identify this request. - OutTaskGUID = FGuid::NewGuid(); - - // Create a new instantiation task - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetInstantiation, OutTaskGUID); - Task.Asset = HoudiniAsset; - Task.ActorName = DisplayName; - //Task.bLoadedComponent = bLocalLoadedComponent; - Task.AssetLibraryId = AssetLibraryId; - Task.AssetHapiName = PickedAssetName; - - // Add the task to the stack - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState ) -{ - check(HAC); - - // Will return true if the asset's state need to be updated - NewState = HAC->GetAssetState(); - bool bUpdateState = false; - - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - // Get the current task's progress - FHoudiniEngineTaskInfo TaskInfo; - if ( !UpdateTaskStatus(HAC->HapiGUID, TaskInfo) - || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetInstantiation) - { - // Couldnt get a valid task info - HOUDINI_LOG_ERROR(TEXT(" %s Failed to instantiate - invalid task"), *DisplayName); - NewState = EHoudiniAssetState::NeedInstantiation; - bUpdateState = true; - return bUpdateState; - } - - bool bSuccess = false; - bool bFinished = false; - switch (TaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Success: - { - bSuccess = true; - bFinished = true; - break; - } - - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::FinishedWithError: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - bSuccess = false; - bFinished = true; - break; - } - - case EHoudiniEngineTaskState::None: - case EHoudiniEngineTaskState::Working: - { - bFinished = false; - break; - } - } - - if ( !bFinished ) - { - // Task is still in progress, nothing to do for now - return false; - } - - if ( bSuccess && (TaskInfo.AssetId < 0) ) - { - // Task finished successfully but we received an invalid asset ID, error out - HOUDINI_LOG_ERROR(TEXT(" %s Finished Instantiation but received invalid asset id."), *DisplayName); - bSuccess = false; - } - - if ( bSuccess ) - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedInstantiation."), *DisplayName); - - // Set the new Asset ID - HAC->AssetId = TaskInfo.AssetId; - - // Assign a unique name to the actor if needed - FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(HAC); - - // TODO: Create default preset buffer. - /*TArray< char > DefaultPresetBuffer; - if (!FHoudiniEngineUtils::GetAssetPreset(TaskInfo.AssetId, DefaultPresetBuffer)) - DefaultPresetBuffer.Empty();*/ - - // Reset the cook counter. - HAC->SetAssetCookCount(0); - - // If necessary, set asset transform. - if (HAC->bUploadTransformsToHoudiniEngine) - { - // Retrieve the current component-to-world transform for this component. - if (!FHoudiniEngineUtils::HapiSetAssetTransform(TaskInfo.AssetId, HAC->GetComponentTransform())) - HOUDINI_LOG_MESSAGE(TEXT("Failed to upload the initial Transform back to HAPI.")); - } - - // Only initalize the PDG Asset Link if this Asset is a PDG Asset - // InitializePDGAssetLink may take a while to execute on non PDG HDA, - // So we want to avoid calling it if possible - if (FHoudiniPDGManager::IsPDGAsset(HAC->AssetId)) - { - PDGManager.InitializePDGAssetLink(HAC); - } - - // Update the HAC's state - NewState = EHoudiniAssetState::PreCook; - return true; - } - else - { - 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); - } - - // Reset the cook counter. - HAC->SetAssetCookCount(0); - - // Make sure the asset ID is invalid - HAC->AssetId = -1; - - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::NeedInstantiation; - //HAC->AssetStateResult = EHoudiniAssetStateResult::Success; - - return true; - } -} - -bool -FHoudiniEngineManager::StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID) -{ - // Make sure we have a valid session before attempting anything - if (!FHoudiniEngine::Get().GetSession()) - return false; - - // Check we have a valid AssetId - if (AssetId < 0) - return false; - - // Check this HAC doesn't already have a running task - if (OutTaskGUID.IsValid()) - return false; - - // Generate a GUID for our new task. - OutTaskGUID = FGuid::NewGuid(); - - // Add a new cook task - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetCooking, OutTaskGUID); - Task.ActorName = DisplayName; - Task.AssetId = AssetId; - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState) -{ - check(HAC); - - // Will return true if the asset's state need to be updated - NewState = HAC->GetAssetState(); - bool bUpdateState = false; - - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - // Get the current task's progress - FHoudiniEngineTaskInfo TaskInfo; - if (!UpdateTaskStatus(HAC->HapiGUID, TaskInfo) - || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetCooking) - { - // Couldnt get a valid task info - HOUDINI_LOG_ERROR(TEXT(" %s Failed to cook - invalid task"), *DisplayName); - NewState = EHoudiniAssetState::None; - bUpdateState = true; - return bUpdateState; - } - - bool bSuccess = false; - switch (TaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Success: - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking."), *DisplayName); - bSuccess = true; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::FinishedWithError: - { - // We finished with cook error, will still try to process the results - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with errors - will try to process the available results."), *DisplayName); - bSuccess = true; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with fatal errors - aborting."), *DisplayName); - bSuccess = false; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::None: - case EHoudiniEngineTaskState::Working: - { - // Task is still in progress, nothing to do for now - // return false so we do not update the state - bUpdateState = false; - } - break; - } - - // If the task is still in progress, return now - if (!bUpdateState) - return false; - - // Handle PostCook - NewState = EHoudiniAssetState::PostCook; - HAC->bLastCookSuccess = bSuccess; - - //if (PostCook(HAC, bSuccess, TaskInfo.AssetId)) - //{ - // // Cook was successfull, process the results - // NewState = EHoudiniAssetState::PreProcess; - // HAC->BroadcastCookFinished(); - //} - //else - //{ - // // Cook failed, skip output processing - // NewState = EHoudiniAssetState::None; - //} - - return true; -} - -bool -FHoudiniEngineManager::PreCook(UHoudiniAssetComponent* HAC) -{ - // Handle duplicated HAC - // We need to clean/duplicate some of the HAC's output data manually here - if (HAC->HasBeenDuplicated()) - { - HAC->UpdatePostDuplicate(); - } - - FHoudiniParameterTranslator::OnPreCookParameters(HAC); - - // Upload the changed/parameters back to HAPI - // If cooking is disabled, we still try to upload parameters - if (HAC->HasBeenLoaded()) - { - // Handle loaded parameters - FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); - - // Handle loaded inputs - FHoudiniInputTranslator::UpdateLoadedInputs(HAC); - - // Handle loaded outputs - FHoudiniOutputTranslator::UpdateLoadedOutputs(HAC); - - // TODO: Handle loaded curve - // TODO: Handle editable node - // TODO: Restore parameter preset data - } - - // Try to upload changed parameters - FHoudiniParameterTranslator::UploadChangedParameters(HAC); - - // Try to upload changed inputs - FHoudiniInputTranslator::UploadChangedInputs(HAC); - - // Try to upload changed editable nodes - FHoudiniOutputTranslator::UploadChangedEditableOutput(HAC, false); - - // Upload the asset's transform if needed - if (HAC->NeedTransformUpdate()) - FHoudiniEngineUtils::UploadHACTransform(HAC); - - HAC->ClearRefineMeshesTimer(); - - return true; -} - -bool -FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId) -{ - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - bool bCookSuccess = bSuccess; - if (bCookSuccess && (TaskAssetId < 0)) - { - // Task finished successfully but we received an invalid asset ID, error out - HOUDINI_LOG_ERROR(TEXT(" %s received an invalid asset id - aborting."), *DisplayName); - bCookSuccess = false; - } - - // Update the asset cook count using the node infos - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - HAC->SetAssetCookCount(CookCount); - /* - if(CookCount >= 0 ) - HAC->SetAssetCookCount(CookCount); - else - HAC->SetAssetCookCount(HAC->GetAssetCookCount()+1); - */ - - bool bNeedsToTriggerViewportUpdate = false; - if (bCookSuccess) - { - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Processing outputs...")); - - // Set new asset id. - HAC->AssetId = TaskAssetId; - - FHoudiniParameterTranslator::UpdateParameters(HAC); - - FHoudiniInputTranslator::UpdateInputs(HAC); - - bool bHasHoudiniStaticMeshOutput = false; - bool ForceUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); - FHoudiniOutputTranslator::UpdateOutputs(HAC, ForceUpdate, bHasHoudiniStaticMeshOutput); - HAC->SetNoProxyMeshNextCookRequested(false); - - // Handles have to be updated after parameters - FHoudiniHandleTranslator::UpdateHandles(HAC); - - // Clear the HasBeenLoaded flag - if (HAC->HasBeenLoaded()) - { - HAC->SetHasBeenLoaded(false); - } - - // Clear the HasBeenDuplicated flag - if (HAC->HasBeenDuplicated()) - { - HAC->SetHasBeenDuplicated(false); - } - - // TODO: Need to update rendering information. - // UpdateRenderingInformation(); - HAC->UpdateBounds(); - - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString("Finished processing outputs")); - - // Trigger a details panel update - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // If any outputs have HoudiniStaticMeshes, and if timer based refinement is enabled on the HAC, - // set the RefineMeshesTimer and ensure BuildStaticMeshesForAllHoudiniStaticMeshes is bound to - // the RefineMeshesTimerFired delegate of the HAC - if (bHasHoudiniStaticMeshOutput && HAC->IsProxyStaticMeshRefinementByTimerEnabled()) - { - if (!HAC->GetOnRefineMeshesTimerDelegate().IsBoundToObject(this)) - HAC->GetOnRefineMeshesTimerDelegate().AddRaw(this, &FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes); - HAC->SetRefineMeshesTimer(); - } - - if (bHasHoudiniStaticMeshOutput) - bNeedsToTriggerViewportUpdate = true; - - UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); - if (OnPostCookBakeDelegate.IsBound()) - { - OnPostCookBakeDelegate.Execute(HAC); - if (!HAC->IsBakeAfterNextCookEnabled()) - OnPostCookBakeDelegate.Unbind(); - } - } - else - { - // TODO: Create parameters inputs and handles inputs. - //CreateParameters(); - //CreateInputs(); - //CreateHandles(); - - // Clear the bake after cook delegate if - UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); - if (OnPostCookBakeDelegate.IsBound() && !HAC->IsBakeAfterNextCookEnabled()) - { - OnPostCookBakeDelegate.Unbind(); - // Notify the user that the bake failed since the cook failed. - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Cook failed, therefore the bake also failed...")); - } - } - - if (HAC->InputPresets.Num() > 0) - { - HAC->ApplyInputPresets(); - } - - // If we have downstream HDAs, we need to tell them we're done cooking - HAC->NotifyCookedToDownstreamAssets(); - - // Notify the PDG manager that the HDA is done cooking - FHoudiniPDGManager::NotifyAssetCooked(HAC->PDGAssetLink, bSuccess); - - if (bNeedsToTriggerViewportUpdate && GEditor) - { - // We need to manually update the vieport with HoudiniMeshProxies - // if not, modification made in H with the two way debugger wont be visible in Unreal until the vieports gets focus - GEditor->RedrawAllViewports(false); - } - - // Clear the rebuild/recook flags - HAC->SetRecookRequested(false); - HAC->SetRebuildRequested(false); - - //HAC->SyncToBlueprintGeneratedClass(); - - return bCookSuccess; -} - -bool -FHoudiniEngineManager::StartTaskAssetProcess(UHoudiniAssetComponent* HAC) -{ - HAC->AssetState = EHoudiniAssetState::Processing; - - return true; -} - -bool -FHoudiniEngineManager::UpdateProcess(UHoudiniAssetComponent* HAC) -{ - HAC->AssetState = EHoudiniAssetState::None; - - return true; -} - -bool -FHoudiniEngineManager::StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID) -{ - // Check this HAC doesn't already have a running task - if (OutTaskGUID.IsValid()) - return false; - - if (InAssetId >= 0) - { - /* TODO: Handle Asset Preset - if (!FHoudiniEngineUtils::GetAssetPreset(AssetId, PresetBuffer)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to get the asset's preset, rebuilt asset may have lost its parameters.")); - } - */ - // Delete the asset - if (!StartTaskAssetDelete(InAssetId, OutTaskGUID, true)) - { - return false; - } - } - - // Create a new task GUID for this asset - OutTaskGUID = FGuid::NewGuid(); - - return true; -} - -bool -FHoudiniEngineManager::StartTaskAssetDelete(const HAPI_NodeId& InNodeId, FGuid& OutTaskGUID, bool bShouldDeleteParent) -{ - if (InNodeId < 0) - return false; - - // Get the Asset's NodeInfo - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, &AssetNodeInfo)); - - HAPI_NodeId OBJNodeToDelete = InNodeId; - if (AssetNodeInfo.type == HAPI_NODETYPE_SOP) - { - // For SOP Asset, we want to delete their parent's OBJ node - if (bShouldDeleteParent) - { - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(OBJNodeToDelete); - OBJNodeToDelete = ParentId != -1 ? ParentId : OBJNodeToDelete; - } - } - - // Generate GUID for our new task. - OutTaskGUID = FGuid::NewGuid(); - - // Create asset deletion task object and submit it for processing. - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetDeletion, OutTaskGUID); - Task.AssetId = OBJNodeToDelete; - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo) -{ - if (!OutTaskGUID.IsValid()) - return false; - - if (!FHoudiniEngine::Get().RetrieveTaskInfo(OutTaskGUID, OutTaskInfo)) - { - // Task information does not exist - OutTaskGUID.Invalidate(); - return false; - } - - // Check whether we want to display Slate cooking and instantiation notifications. - bool bDisplaySlateCookingNotifications = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (EHoudiniEngineTaskState::None != OutTaskInfo.TaskState && bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().CreateTaskSlateNotification(OutTaskInfo.StatusText); - } - - switch (OutTaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::Success: - case EHoudiniEngineTaskState::FinishedWithError: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - // If the current task is finished - // Terminate the slate notification if they exist and delete/invalidate the task - if (bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().FinishTaskSlateNotification(OutTaskInfo.StatusText); - } - - FHoudiniEngine::Get().RemoveTaskInfo(OutTaskGUID); - OutTaskGUID.Invalidate(); - } - break; - - case EHoudiniEngineTaskState::Working: - { - // The current task is still running, simply update the current notification - if (bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().UpdateTaskSlateNotification(OutTaskInfo.StatusText); - } - } - break; - - case EHoudiniEngineTaskState::None: - default: - { - break; - } - } - - return true; -} - -bool -FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC) -{ - bool bManualRecook = false; - bool bComponentEnable = false; - if (HAC && !HAC->IsPendingKill()) - { - bManualRecook = HAC->HasRecookBeenRequested(); - bComponentEnable = HAC->IsCookingEnabled(); - } - - if (bManualRecook) - return true; - - if (bComponentEnable && FHoudiniEngine::Get().IsCookingEnabled()) - return true; - - return false; -} - -void -FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes called with HAC=nullptr")); - return; - } - -#if WITH_EDITOR - AActor *Owner = HAC->GetOwner(); - FString Name = Owner ? Owner->GetName() : HAC->GetName(); - - FScopedSlowTask Progress(2.0f, FText::FromString(FString::Printf(TEXT("Refining Proxy Mesh to Static Mesh on %s"), *Name))); - Progress.MakeDialog(); - Progress.EnterProgressFrame(1.0f); -#endif - - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); - -#if WITH_EDITOR - Progress.EnterProgressFrame(1.0f); -#endif -} - - -/* Unreal's viewport representation rules: - Viewport location is the actual camera location; - Lookat position is always right in front of the camera, which means the camera is looking at; - The rotator rotates the forward vector to a direction & orientation, and this dir and orientation is the camera's; - The identity direction and orientation of the camera is facing positive X-axis. -*/ - -/* Hapi's viewport representation rules: - The camera is located at a point on the sphere, which the center is the pivot position and the radius is offset; - Quat determines the location on the sphere and which direction the camera is facing towards, as well as the camera orientation; - The identity location, direction and orientation of the camera is facing positive Z-axis (in Hapi coords); -*/ - - -bool -FHoudiniEngineManager::SyncHoudiniViewportToUnreal() -{ - if (!FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled()) - return false; - -#if WITH_EDITOR - // Get the editor viewport LookAt position to spawn the new objects - if (!GEditor || !GEditor->GetActiveViewport()) - return false; - - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (!ViewportClient) - return false; - - // Get the current UE viewport location, lookat position, and rotation - FVector UnrealViewportPosition = ViewportClient->GetViewLocation(); - FRotator UnrealViewportRotation = ViewportClient->GetViewRotation(); - FVector UnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - /* Check if the Unreal viewport has changed */ - if (UnrealViewportPosition.Equals(SyncedUnrealViewportPosition) && - UnrealViewportRotation.Equals(SyncedUnrealViewportRotation) && - UnrealViewportLookatPosition.Equals(SyncedUnrealViewportLookatPosition)) - { - // No need to sync if the viewport camera hasn't changed - return false; - } - - /* Calculate Hapi Quaternion */ - // Initialize Hapi Quat with Unreal Quat. - // Note that rotations are in general non-commutative *** - FQuat HapiQuat = UnrealViewportRotation.Quaternion(); - - // We're in orbit mode, forward vector is Y-axis - if (ViewportClient->bUsingOrbitCamera) - { - // The forward vector is Y-negative direction when on orbiting mode - HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 180.f)); - - // rotations around X and Y axis are reversed - float TempX = HapiQuat.X; - HapiQuat.X = HapiQuat.Y; - HapiQuat.Y = TempX; - HapiQuat.W = -HapiQuat.W; - - } - // We're not in orbiting mode, forward vector is X-axis - else - { - // Rotate the Quat arount Z-axis by 90 degree. - HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 90.f)); - } - - - /* Update Hapi H_View */ - // Note: There are infinte number of H_View representation for current viewport - // Each choice of pivot point determines an equivalent representation. - // We just find an equivalent when the pivot position is the view position, and offset is 0 - - HAPI_Viewport H_View; - H_View.position[0] = UnrealViewportPosition.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - H_View.position[1] = UnrealViewportPosition.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - H_View.position[2] = UnrealViewportPosition.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Set HAPI_Offset always 0 when syncing Houdini to UE viewport - H_View.offset = 0.f; - - H_View.rotationQuaternion[0] = -HapiQuat.X; - H_View.rotationQuaternion[1] = -HapiQuat.Z; - H_View.rotationQuaternion[2] = -HapiQuat.Y; - H_View.rotationQuaternion[3] = HapiQuat.W; - - FHoudiniApi::SetViewport(FHoudiniEngine::Get().GetSession(), &H_View); - - /* Update the Synced viewport values - We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. - Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. */ - - // We need to get the H_Viewport again, since it is possible the value is a different equivalence of what we set. - HAPI_Viewport Cur_H_View; - FHoudiniApi::GetViewport( - FHoudiniEngine::Get().GetSession(), &Cur_H_View); - - // Hapi values are in Houdini coordinate and scale - SyncedHoudiniViewportPivotPosition = FVector(Cur_H_View.position[0], Cur_H_View.position[1], Cur_H_View.position[2]); - SyncedHoudiniViewportQuat = FQuat(Cur_H_View.rotationQuaternion[0], Cur_H_View.rotationQuaternion[1], Cur_H_View.rotationQuaternion[2], Cur_H_View.rotationQuaternion[3]); - SyncedHoudiniViewportOffset = Cur_H_View.offset; - - SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); - SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); - SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - // When sync Houdini to UE, we set offset to be 0. - // So we need to zero out offset for the next time syncing UE to Houdini - bOffsetZeroed = true; - - return true; -#endif - - return false; -} - - -bool -FHoudiniEngineManager::SyncUnrealViewportToHoudini() -{ - if (!FHoudiniEngine::Get().IsSyncUnrealViewportEnabled()) - return false; - -#if WITH_EDITOR - // Get the editor viewport LookAt position to spawn the new objects - if (!GEditor || !GEditor->GetActiveViewport()) - return false; - - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (!ViewportClient) - return false; - - // Get the current HAPI_Viewport - HAPI_Viewport H_View; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetViewport( - FHoudiniEngine::Get().GetSession(), &H_View)) - { - return false; - } - - - // Get Hapi viewport's PivotPosition, Offset and Quat, w.r.t Houdini's coordinate and scale. - FVector HapiViewportPivotPosition = FVector(H_View.position[0], H_View.position[1], H_View.position[2]); - float HapiViewportOffset = H_View.offset; - FQuat HapiViewportQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[1], H_View.rotationQuaternion[2], H_View.rotationQuaternion[3]); - - /* Check if the Houdini viewport has changed */ - if (SyncedHoudiniViewportPivotPosition.Equals(HapiViewportPivotPosition) && - SyncedHoudiniViewportQuat.Equals(HapiViewportQuat) && - SyncedHoudiniViewportOffset == HapiViewportOffset) - { - // Houdini viewport hasn't changed, nothing to do - return false; - } - - // Set zero value of offset when needed - if (bOffsetZeroed) - { - ZeroOffsetValue = H_View.offset; - bOffsetZeroed = false; - } - - - /* Translate the hapi camera transfrom to Unreal's representation system */ - - // Get pivot point in UE's coordinate and scale - FVector UnrealViewportPivotPosition = FVector(H_View.position[0], H_View.position[2], H_View.position[1]) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // HAPI bug? After we set the H_View, offset becomes a lot bigger when move the viewport just a little bit in Houdini. - // But the pivot point doesn't change. Which caused UE viewport jumping far suddenly. - // So we get rid of this problem by setting the first HAPI_offset value after syncing Houdini viewport as the base. - - // Get offset in UE's scale. The actual offset after 'zero out' - float UnrealOffset = (H_View.offset - ZeroOffsetValue) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - /* Calculate Quaternion in UE */ - // Rotate the resulting Quat around Z-axis by -90 degree. - // Note that rotation is in general non-commutative *** - FQuat UnrealQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[2], H_View.rotationQuaternion[1], -H_View.rotationQuaternion[3]); - UnrealQuat = UnrealQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)); - - FVector UnrealBaseVector(1.f, 0.f, 0.f); // Forward vector in Unreal viewport - - /* Get UE viewport location*/ - FVector UnrealViewPosition = - UnrealQuat.RotateVector(UnrealBaseVector) * UnrealOffset + UnrealViewportPivotPosition; - - /* Set the viewport's value */ - ViewportClient->SetViewLocation(UnrealViewPosition); - ViewportClient->SetViewRotation(UnrealQuat.Rotator()); - - // Invalidate the viewport - ViewportClient->Invalidate(); - - /* Update the synced viewport values */ - // We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. - // Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. - - // Hapi values are in Houdini coordinate and scale - SyncedHoudiniViewportPivotPosition = HapiViewportPivotPosition; - SyncedHoudiniViewportQuat = HapiViewportQuat; - SyncedHoudiniViewportOffset = HapiViewportOffset; - - SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); - SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); - SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - return true; -#endif - - return false; -} - - -void -FHoudiniEngineManager::DisableEditorAutoSave(const UHoudiniAssetComponent* HAC) -{ -#if WITH_EDITOR - if (!HAC || HAC->IsPendingKill()) - return; - - if (!GUnrealEd) - return; - - if (DisableAutoSavingHACs.Contains(HAC)) - return; - // Add the HAC to the set - DisableAutoSavingHACs.Add(HAC); - - // Return if auto-saving has been disabled by some other HACs. - if (DisableAutoSavingHACs.Num() > 1) - return; - - // Disable auto-saving by setting min time till auto-save to max float value - IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); - AutoSaver.ForceMinimumTimeTillAutoSave(TNumericLimits::Max()); -#endif -} - - -void -FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = nullptr) -{ -#if WITH_EDITOR - if (!GUnrealEd) - return; - - if (!HAC) - { - // When HAC is nullptr, go through all HACs in the set, - // remove it if the HAC has been deleted. - if (DisableAutoSavingHACs.Num() <= 0) - return; - - for (auto& CurHAC : DisableAutoSavingHACs) - { - if (!CurHAC || CurHAC->IsPendingKill()) - DisableAutoSavingHACs.Remove(CurHAC); - } - } - else - { - // Otherwise, remove the HAC from the set - if (DisableAutoSavingHACs.Contains(HAC)) - DisableAutoSavingHACs.Remove(HAC); - } - - if (DisableAutoSavingHACs.Num() > 0) - return; - - // When no HAC disables cooking, reset min time till auto-save to default value, then reset the timer - IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); - AutoSaver.ForceMinimumTimeTillAutoSave(); // use default value - AutoSaver.ResetAutoSaveTimer(); -#endif -} \ No newline at end of file +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineManager.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniParameterTranslator.h" +#include "HoudiniPDGManager.h" +#include "HoudiniInputTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniHandleTranslator.h" +#include "HoudiniSplineTranslator.h" +#include "Misc/MessageDialog.h" +#include "Misc/ScopedSlowTask.h" +#include "Containers/Ticker.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "EditorViewportClient.h" + #include "Kismet/KismetMathLibrary.h" + + //#include "UnrealEd.h" + #include "UnrealEdGlobals.h" + #include "Editor/UnrealEdEngine.h" + #include "IPackageAutoSaver.h" +#endif + +FHoudiniEngineManager::FHoudiniEngineManager() + : CurrentIndex(0) + , ComponentCount(0) + , bMustStopTicking(false) + , SyncedHoudiniViewportPivotPosition(FVector::ZeroVector) + , SyncedHoudiniViewportQuat(FQuat::Identity) + , SyncedHoudiniViewportOffset(0.0f) + , SyncedUnrealViewportPosition(FVector::ZeroVector) + , SyncedUnrealViewportRotation(FRotator::ZeroRotator) + , SyncedUnrealViewportLookatPosition(FVector::ZeroVector) + , ZeroOffsetValue(0.f) + , bOffsetZeroed(false) +{ + +} + +FHoudiniEngineManager::~FHoudiniEngineManager() +{ + PDGManager.StopBGEOCommandletAndEndpoint(); +} + +void +FHoudiniEngineManager::StartHoudiniTicking() +{ + // If we have no timer delegate spawned, spawn one. + if (!TickerHandle.IsValid() && GEditor) + { + // We use the ticker manager so we get ticked once per frame, no more. + TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FHoudiniEngineManager::Tick)); + + // Grab current time for delayed notification. + FHoudiniEngine::Get().SetHapiNotificationStartedTime(FPlatformTime::Seconds()); + } +} + +void +FHoudiniEngineManager::StopHoudiniTicking() +{ + if (TickerHandle.IsValid() && GEditor) + { + if (IsInGameThread()) + { + FTicker::GetCoreTicker().RemoveTicker(TickerHandle); + TickerHandle.Reset(); + + // Reset time for delayed notification. + FHoudiniEngine::Get().SetHapiNotificationStartedTime(0.0); + + bMustStopTicking = false; + } + else + { + // We can't stop ticking now as we're not in the game Thread, + // and accessing the timer would crash, indicate that we want to stop ticking asap + // This can happen when loosing a session due to a Houdini crash + bMustStopTicking = true; + } + } +} + +bool +FHoudiniEngineManager::Tick(float DeltaTime) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::Tick); + + EnableEditorAutoSave(nullptr); + + if (bMustStopTicking) + { + // Ticking should be stopped immediately + StopHoudiniTicking(); + return true; + } + + // Process the current component if possible + while (true) + { + UHoudiniAssetComponent * CurrentComponent = nullptr; + if (FHoudiniEngineRuntime::IsInitialized()) + { + FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); + + //FScopeLock ScopeLock(&CriticalSection); + ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); + + // No work to be done + if (ComponentCount <= 0) + break; + + // Wrap around if needed + if (CurrentIndex >= ComponentCount) + CurrentIndex = 0; + + CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(CurrentIndex); + CurrentIndex++; + } + + if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) + { + // Invalid component, do not process + break; + } + else if (CurrentComponent->IsPendingKill() + || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) + { + // Component being deleted, do not process + break; + } + + if (!CurrentComponent->IsFullyLoaded()) + { + // Let the component figure out whether it's fully loaded or not. + CurrentComponent->HoudiniEngineTick(); + if (!CurrentComponent->IsFullyLoaded()) + continue; // We need to wait some more. + } + + if (!CurrentComponent->IsValidComponent()) + { + // This component is no longer valid. Prevent it from being processed, and remove it. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); + continue; + } + + // We don't want to the template component processing to trigger session creation + if (CurrentComponent->GetAssetState() == EHoudiniAssetState::ProcessTemplate) + { + if (CurrentComponent->IsTemplate() && !CurrentComponent->HasOpenEditor()) + { + // This component template no longer has an open editor and can be deregistered. + // TODO: Replace this polling mechanism with an "On Asset Closed" event if we + // can find one that actually works. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); + continue; + } + + if (CurrentComponent->NeedBlueprintStructureUpdate()) + { + CurrentComponent->OnBlueprintStructureModified(); + } + + if (CurrentComponent->NeedBlueprintUpdate()) + { + CurrentComponent->OnBlueprintModified(); + } + + if (FHoudiniEngine::Get().IsCookingEnabled()) + { + // Only process component template parameter updates when cooking is enabled. + if (CurrentComponent->NeedUpdateParameters() || CurrentComponent->NeedUpdateInputs()) + { + CurrentComponent->OnTemplateParametersChanged(); + } + } + + if (CurrentComponent->NeedOutputUpdate()) + { + // TODO: Transfer template output changes over to the preview instance. + } + + break; + } + + // See if we should start the default "first" session + if(!FHoudiniEngine::Get().GetSession() && !FHoudiniEngine::Get().GetFirstSessionCreated()) + { + // Only try to start the default session if we have an "active" HAC + if (CurrentComponent->GetAssetState() == EHoudiniAssetState::PreInstantiation + || CurrentComponent->GetAssetState() == EHoudiniAssetState::Instantiating + || CurrentComponent->GetAssetState() == EHoudiniAssetState::PreCook + || CurrentComponent->GetAssetState() == EHoudiniAssetState::Cooking) + { + FString StatusText = TEXT("Initializing Houdini Engine..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // We want to yield for a bit. + //FPlatformProcess::Sleep(0.5f); + + // Indicates that we've tried to start the session once no matter if it failed or succeed + FHoudiniEngine::Get().SetFirstSessionCreated(true); + + // Attempt to restart the session + if (!FHoudiniEngine::Get().RestartSession()) + { + // We failed to start the session + // Stop ticking until it's manually restarted + StopHoudiniTicking(); + + StatusText = TEXT("Houdini Engine failed to initialize."); + } + else + { + StatusText = TEXT("Houdini Engine successfully initialized."); + } + + // Finish the notification and display the results + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + } + } + + // Process the component + // try to catch (apache::thrift::transport::TTransportException * e) for session loss? + ProcessComponent(CurrentComponent); + break; + } + + // Handle Asset delete + if (FHoudiniEngineRuntime::IsInitialized()) + { + int32 PendingDeleteCount = FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteCount(); + for (int32 DeleteIdx = PendingDeleteCount - 1; DeleteIdx >= 0; DeleteIdx--) + { + HAPI_NodeId NodeIdToDelete = (HAPI_NodeId)FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteAt(DeleteIdx); + FGuid HapiDeletionGUID; + bool bShouldDeleteParent = FHoudiniEngineRuntime::Get().IsParentNodePendingDelete(NodeIdToDelete); + if (StartTaskAssetDelete(NodeIdToDelete, HapiDeletionGUID, bShouldDeleteParent)) + { + FHoudiniEngineRuntime::Get().RemoveNodeIdPendingDeleteAt(DeleteIdx); + if (bShouldDeleteParent) + FHoudiniEngineRuntime::Get().RemoveParentNodePendingDelete(NodeIdToDelete); + } + } + } + + // Update PDG Contexts and asset link if needed + PDGManager.Update(); + + // Session Sync Updates + if (FHoudiniEngine::Get().IsSessionSyncEnabled()) + { + // See if the session sync settings have changed on the houdini side, update ours if they did + FHoudiniEngine::Get().UpdateSessionSyncInfoFromHoudini(); +#if WITH_EDITOR + // Update the Houdini viewport from unreal if needed + if (FHoudiniEngine::Get().IsSyncViewportEnabled()) + { + // Sync the Houdini viewport to Unreal + if (!SyncHoudiniViewportToUnreal()) + { + // If the unreal viewport hasnt changed, + // See if we need to sync the Unreal viewport from Houdini's + SyncUnrealViewportToHoudini(); + } + } +#endif + } + else + { + // reset zero offset variables when session sync is off + if (ZeroOffsetValue != 0.f) + ZeroOffsetValue = 0.f; + + if (bOffsetZeroed) + bOffsetZeroed = false; + } + + return true; +} + +void +FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::ProcessComponent); + + if (!HAC || HAC->IsPendingKill()) + return; + + // No need to process component not tied to an asset + if (!HAC->GetHoudiniAsset()) + return; + + // If cooking is paused, stay in the current state until cooking's resumed + if (!FHoudiniEngine::Get().IsCookingEnabled()) + { + // We can only handle output updates + if (HAC->GetAssetState() == EHoudiniAssetState::None && HAC->NeedOutputUpdate()) + { + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Refresh UI when pause cooking + if (!FHoudiniEngine::Get().HasUIFinishRefreshingWhenPausingCooking()) + { + // Trigger a details panel update if the Houdini asset actor is selected + if (HAC->IsOwnerSelected()) + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // Finished refreshing UI of one HDA. + FHoudiniEngine::Get().RefreshUIDisplayedWhenPauseCooking(); + } + + // Prevent any other state change to happen + return; + } + + switch (HAC->GetAssetState()) + { + case EHoudiniAssetState::NeedInstantiation: + { + // Do nothing unless the HAC has been updated + if (HAC->NeedUpdate()) + { + HAC->OnPrePreInstantiation(); + HAC->bForceNeedUpdate = false; + // Update the HAC's state + HAC->AssetState = EHoudiniAssetState::PreInstantiation; + } + else if (HAC->NeedOutputUpdate()) + { + // Output updates do not recquire the HDA to be instantiated + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Update world input if we have any + FHoudiniInputTranslator::UpdateWorldInputs(HAC); + + break; + } + + case EHoudiniAssetState::PreInstantiation: + { + // Only proceed forward if we don't need to wait for our input HoudiniAssets to finish cooking/instantiating + if (HAC->NeedsToWaitForInputHoudiniAssets()) + break; + + FGuid TaskGuid; + UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); + if (StartTaskAssetInstantiation(HoudiniAsset, HAC->GetDisplayName(), TaskGuid)) + { + // Update the HAC's state + HAC->AssetState = EHoudiniAssetState::Instantiating; + //HAC->AssetStateResult = EHoudiniAssetStateResult::None; + + // Update the Task GUID + HAC->HapiGUID = TaskGuid; + } + else + { + // If we couldnt instantiate the asset + // Change the state to NeedInstantiating + HAC->AssetState = EHoudiniAssetState::NeedInstantiation; + //HAC->AssetStateResult = EHoudiniAssetStateResult::None; + } + break; + } + + case EHoudiniAssetState::Instantiating: + { + EHoudiniAssetState NewState = EHoudiniAssetState::Instantiating; + if (UpdateInstantiating(HAC, NewState)) + { + // We need to update the HAC's state + HAC->AssetState = NewState; + EnableEditorAutoSave(HAC); + } + else + { + DisableEditorAutoSave(HAC); + } + break; + } + + case EHoudiniAssetState::PreCook: + { + // Only proceed forward if we don't need to wait for our input + // HoudiniAssets to finish cooking/instantiating + if (HAC->NeedsToWaitForInputHoudiniAssets()) + break; + + HAC->OnPrePreCook(); + // Update all the HAPI nodes, parameters, inputs etc... + PreCook(HAC); + HAC->OnPostPreCook(); + + // Create a Cooking task only if necessary + bool bCookStarted = false; + if (IsCookingEnabledForHoudiniAsset(HAC)) + { + FGuid TaskGUID = HAC->GetHapiGUID(); + if ( StartTaskAssetCooking(HAC->GetAssetId(), HAC->GetDisplayName(), TaskGUID) ) + { + // Updates the HAC's state + HAC->AssetState = EHoudiniAssetState::Cooking; + HAC->HapiGUID = TaskGUID; + bCookStarted = true; + } + } + + if(!bCookStarted) + { + // Just refresh editor properties? + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // TODO: Check! update state? + HAC->AssetState = EHoudiniAssetState::None; + } + break; + } + + case EHoudiniAssetState::Cooking: + { + EHoudiniAssetState NewState = EHoudiniAssetState::Cooking; + bool state = UpdateCooking(HAC, NewState); + if (state) + { + // We need to update the HAC's state + HAC->AssetState = NewState; + EnableEditorAutoSave(HAC); + } + else + { + DisableEditorAutoSave(HAC); + } + break; + } + + case EHoudiniAssetState::PostCook: + { + // Handle PostCook + EHoudiniAssetState NewState = EHoudiniAssetState::None; + bool bSuccess = HAC->bLastCookSuccess; + HAC->OnPreOutputProcessing(); + if (PostCook(HAC, bSuccess, HAC->GetAssetId())) + { + // Cook was successful, process the results + NewState = EHoudiniAssetState::PreProcess; + } + else + { + // Cook failed, skip output processing + NewState = EHoudiniAssetState::None; + } + HAC->AssetState = NewState; + break; + } + + case EHoudiniAssetState::PreProcess: + { + StartTaskAssetProcess(HAC); + break; + } + + case EHoudiniAssetState::Processing: + { + UpdateProcess(HAC); + + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + + HAC->OnPostOutputProcessing(); + FHoudiniEngineUtils::UpdateBlueprintEditor(HAC); + break; + } + + case EHoudiniAssetState::None: + { + // Do nothing unless the HAC has been updated + if (HAC->NeedUpdate()) + { + HAC->bForceNeedUpdate = false; + // Update the HAC's state + HAC->AssetState = EHoudiniAssetState::PreCook; + } + else if (HAC->NeedTransformUpdate()) + { + FHoudiniEngineUtils::UploadHACTransform(HAC); + } + else if (HAC->NeedOutputUpdate()) + { + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Update world inputs if we have any + FHoudiniInputTranslator::UpdateWorldInputs(HAC); + + // See if we need to get an update from Session Sync + if(FHoudiniEngine::Get().IsSessionSyncEnabled() + && FHoudiniEngine::Get().IsSyncWithHoudiniCookEnabled() + && HAC->AssetState == EHoudiniAssetState::None) + { + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + if (CookCount >= 0 && CookCount != HAC->GetAssetCookCount()) + { + // The cook count has changed on the Houdini side, + // this indicates that the user has changed something in Houdini so we need to trigger an update + HAC->AssetState = EHoudiniAssetState::PreCook; + } + } + break; + } + + case EHoudiniAssetState::NeedRebuild: + { + StartTaskAssetRebuild(HAC->AssetId, HAC->HapiGUID); + + HAC->MarkAsNeedCook(); + HAC->AssetState = EHoudiniAssetState::PreInstantiation; + break; + } + + case EHoudiniAssetState::NeedDelete: + { + FGuid HapiDeletionGUID; + StartTaskAssetDelete(HAC->GetAssetId(), HapiDeletionGUID, true); + //HAC->AssetId = -1; + + // Update the HAC's state + HAC->AssetState = EHoudiniAssetState::Deleting; + break; + } + + case EHoudiniAssetState::Deleting: + { + break; + } + } +} + + + +bool +FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID) +{ + // Make sure we have a valid session before attempting anything + if (!FHoudiniEngine::Get().GetSession()) + return false; + + OutTaskGUID.Invalidate(); + + // Load the HDA file + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - null or invalid Houdini Asset.")); + return false; + } + + HAPI_AssetLibraryId AssetLibraryId = -1; + if (!FHoudiniEngineUtils::LoadHoudiniAsset(HoudiniAsset, AssetLibraryId) ) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - could not load Houdini Asset.")); + return false; + } + + // Handle hda files that contain multiple assets + TArray< HAPI_StringHandle > AssetNames; + if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - unable to retrieve asset names.")); + return false; + } + + // By default, assume we want to load the first Asset + HAPI_StringHandle PickedAssetName = AssetNames[0]; + +#if WITH_EDITOR + // Should we show the multi asset dialog? + bool bShowMultiAssetDialog = false; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && AssetNames.Num() > 1) + bShowMultiAssetDialog = HoudiniRuntimeSettings->bShowMultiAssetDialog; + + // TODO: Add multi selection dialog + if (bShowMultiAssetDialog ) + { + // TODO: Implement + FHoudiniEngineUtils::OpenSubassetSelectionWindow(AssetNames, PickedAssetName); + } +#endif + + // Give the HAC a new GUID to identify this request. + OutTaskGUID = FGuid::NewGuid(); + + // Create a new instantiation task + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetInstantiation, OutTaskGUID); + Task.Asset = HoudiniAsset; + Task.ActorName = DisplayName; + //Task.bLoadedComponent = bLocalLoadedComponent; + Task.AssetLibraryId = AssetLibraryId; + Task.AssetHapiName = PickedAssetName; + + // Add the task to the stack + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState ) +{ + check(HAC); + + // Will return true if the asset's state need to be updated + NewState = HAC->GetAssetState(); + bool bUpdateState = false; + + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + // Get the current task's progress + FHoudiniEngineTaskInfo TaskInfo; + if ( !UpdateTaskStatus(HAC->HapiGUID, TaskInfo) + || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetInstantiation) + { + // Couldnt get a valid task info + HOUDINI_LOG_ERROR(TEXT(" %s Failed to instantiate - invalid task"), *DisplayName); + NewState = EHoudiniAssetState::NeedInstantiation; + bUpdateState = true; + return bUpdateState; + } + + bool bSuccess = false; + bool bFinished = false; + switch (TaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Success: + { + bSuccess = true; + bFinished = true; + break; + } + + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::FinishedWithError: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + bSuccess = false; + bFinished = true; + break; + } + + case EHoudiniEngineTaskState::None: + case EHoudiniEngineTaskState::Working: + { + bFinished = false; + break; + } + } + + if ( !bFinished ) + { + // Task is still in progress, nothing to do for now + return false; + } + + if ( bSuccess && (TaskInfo.AssetId < 0) ) + { + // Task finished successfully but we received an invalid asset ID, error out + HOUDINI_LOG_ERROR(TEXT(" %s Finished Instantiation but received invalid asset id."), *DisplayName); + bSuccess = false; + } + + if ( bSuccess ) + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedInstantiation."), *DisplayName); + + // Set the new Asset ID + HAC->AssetId = TaskInfo.AssetId; + + // Assign a unique name to the actor if needed + FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(HAC); + + // TODO: Create default preset buffer. + /*TArray< char > DefaultPresetBuffer; + if (!FHoudiniEngineUtils::GetAssetPreset(TaskInfo.AssetId, DefaultPresetBuffer)) + DefaultPresetBuffer.Empty();*/ + + // Reset the cook counter. + HAC->SetAssetCookCount(0); + + // If necessary, set asset transform. + if (HAC->bUploadTransformsToHoudiniEngine) + { + // Retrieve the current component-to-world transform for this component. + if (!FHoudiniEngineUtils::HapiSetAssetTransform(TaskInfo.AssetId, HAC->GetComponentTransform())) + HOUDINI_LOG_MESSAGE(TEXT("Failed to upload the initial Transform back to HAPI.")); + } + + // Only initalize the PDG Asset Link if this Asset is a PDG Asset + // InitializePDGAssetLink may take a while to execute on non PDG HDA, + // So we want to avoid calling it if possible + if (FHoudiniPDGManager::IsPDGAsset(HAC->AssetId)) + { + PDGManager.InitializePDGAssetLink(HAC); + } + + // Update the HAC's state + NewState = EHoudiniAssetState::PreCook; + return true; + } + else + { + 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); + } + + // Reset the cook counter. + HAC->SetAssetCookCount(0); + + // Make sure the asset ID is invalid + HAC->AssetId = -1; + + // Update the HAC's state + HAC->AssetState = EHoudiniAssetState::NeedInstantiation; + //HAC->AssetStateResult = EHoudiniAssetStateResult::Success; + + return true; + } +} + +bool +FHoudiniEngineManager::StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID) +{ + // Make sure we have a valid session before attempting anything + if (!FHoudiniEngine::Get().GetSession()) + return false; + + // Check we have a valid AssetId + if (AssetId < 0) + return false; + + // Check this HAC doesn't already have a running task + if (OutTaskGUID.IsValid()) + return false; + + // Generate a GUID for our new task. + OutTaskGUID = FGuid::NewGuid(); + + // Add a new cook task + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetCooking, OutTaskGUID); + Task.ActorName = DisplayName; + Task.AssetId = AssetId; + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState) +{ + check(HAC); + + // Will return true if the asset's state need to be updated + NewState = HAC->GetAssetState(); + bool bUpdateState = false; + + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + // Get the current task's progress + FHoudiniEngineTaskInfo TaskInfo; + if (!UpdateTaskStatus(HAC->HapiGUID, TaskInfo) + || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetCooking) + { + // Couldnt get a valid task info + HOUDINI_LOG_ERROR(TEXT(" %s Failed to cook - invalid task"), *DisplayName); + NewState = EHoudiniAssetState::None; + bUpdateState = true; + return bUpdateState; + } + + bool bSuccess = false; + switch (TaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Success: + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking."), *DisplayName); + bSuccess = true; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::FinishedWithError: + { + // We finished with cook error, will still try to process the results + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with errors - will try to process the available results."), *DisplayName); + bSuccess = true; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with fatal errors - aborting."), *DisplayName); + bSuccess = false; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::None: + case EHoudiniEngineTaskState::Working: + { + // Task is still in progress, nothing to do for now + // return false so we do not update the state + bUpdateState = false; + } + break; + } + + // If the task is still in progress, return now + if (!bUpdateState) + return false; + + // Handle PostCook + NewState = EHoudiniAssetState::PostCook; + HAC->bLastCookSuccess = bSuccess; + + //if (PostCook(HAC, bSuccess, TaskInfo.AssetId)) + //{ + // // Cook was successfull, process the results + // NewState = EHoudiniAssetState::PreProcess; + // HAC->BroadcastCookFinished(); + //} + //else + //{ + // // Cook failed, skip output processing + // NewState = EHoudiniAssetState::None; + //} + + return true; +} + +bool +FHoudiniEngineManager::PreCook(UHoudiniAssetComponent* HAC) +{ + // Handle duplicated HAC + // We need to clean/duplicate some of the HAC's output data manually here + if (HAC->HasBeenDuplicated()) + { + HAC->UpdatePostDuplicate(); + } + + FHoudiniParameterTranslator::OnPreCookParameters(HAC); + + // Upload the changed/parameters back to HAPI + // If cooking is disabled, we still try to upload parameters + if (HAC->HasBeenLoaded()) + { + // Handle loaded parameters + FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); + + // Handle loaded inputs + FHoudiniInputTranslator::UpdateLoadedInputs(HAC); + + // Handle loaded outputs + FHoudiniOutputTranslator::UpdateLoadedOutputs(HAC); + + // TODO: Handle loaded curve + // TODO: Handle editable node + // TODO: Restore parameter preset data + } + + // Try to upload changed parameters + FHoudiniParameterTranslator::UploadChangedParameters(HAC); + + // Try to upload changed inputs + FHoudiniInputTranslator::UploadChangedInputs(HAC); + + // Try to upload changed editable nodes + FHoudiniOutputTranslator::UploadChangedEditableOutput(HAC, false); + + // Upload the asset's transform if needed + if (HAC->NeedTransformUpdate()) + FHoudiniEngineUtils::UploadHACTransform(HAC); + + HAC->ClearRefineMeshesTimer(); + + return true; +} + +bool +FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId) +{ + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + bool bCookSuccess = bSuccess; + if (bCookSuccess && (TaskAssetId < 0)) + { + // Task finished successfully but we received an invalid asset ID, error out + HOUDINI_LOG_ERROR(TEXT(" %s received an invalid asset id - aborting."), *DisplayName); + bCookSuccess = false; + } + + // Update the asset cook count using the node infos + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + HAC->SetAssetCookCount(CookCount); + /* + if(CookCount >= 0 ) + HAC->SetAssetCookCount(CookCount); + else + HAC->SetAssetCookCount(HAC->GetAssetCookCount()+1); + */ + + bool bNeedsToTriggerViewportUpdate = false; + if (bCookSuccess) + { + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Processing outputs...")); + + // Set new asset id. + HAC->AssetId = TaskAssetId; + + FHoudiniParameterTranslator::UpdateParameters(HAC); + + FHoudiniInputTranslator::UpdateInputs(HAC); + + bool bHasHoudiniStaticMeshOutput = false; + bool ForceUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); + FHoudiniOutputTranslator::UpdateOutputs(HAC, ForceUpdate, bHasHoudiniStaticMeshOutput); + HAC->SetNoProxyMeshNextCookRequested(false); + + // Handles have to be updated after parameters + FHoudiniHandleTranslator::UpdateHandles(HAC); + + // Clear the HasBeenLoaded flag + if (HAC->HasBeenLoaded()) + { + HAC->SetHasBeenLoaded(false); + } + + // Clear the HasBeenDuplicated flag + if (HAC->HasBeenDuplicated()) + { + HAC->SetHasBeenDuplicated(false); + } + + // TODO: Need to update rendering information. + // UpdateRenderingInformation(); + HAC->UpdateBounds(); + + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString("Finished processing outputs")); + + // Trigger a details panel update + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // If any outputs have HoudiniStaticMeshes, and if timer based refinement is enabled on the HAC, + // set the RefineMeshesTimer and ensure BuildStaticMeshesForAllHoudiniStaticMeshes is bound to + // the RefineMeshesTimerFired delegate of the HAC + if (bHasHoudiniStaticMeshOutput && HAC->IsProxyStaticMeshRefinementByTimerEnabled()) + { + if (!HAC->GetOnRefineMeshesTimerDelegate().IsBoundToObject(this)) + HAC->GetOnRefineMeshesTimerDelegate().AddRaw(this, &FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes); + HAC->SetRefineMeshesTimer(); + } + + if (bHasHoudiniStaticMeshOutput) + bNeedsToTriggerViewportUpdate = true; + + UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); + if (OnPostCookBakeDelegate.IsBound()) + { + OnPostCookBakeDelegate.Execute(HAC); + if (!HAC->IsBakeAfterNextCookEnabled()) + OnPostCookBakeDelegate.Unbind(); + } + } + else + { + // TODO: Create parameters inputs and handles inputs. + //CreateParameters(); + //CreateInputs(); + //CreateHandles(); + + // Clear the bake after cook delegate if + UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); + if (OnPostCookBakeDelegate.IsBound() && !HAC->IsBakeAfterNextCookEnabled()) + { + OnPostCookBakeDelegate.Unbind(); + // Notify the user that the bake failed since the cook failed. + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Cook failed, therefore the bake also failed...")); + } + } + + if (HAC->InputPresets.Num() > 0) + { + HAC->ApplyInputPresets(); + } + + // If we have downstream HDAs, we need to tell them we're done cooking + HAC->NotifyCookedToDownstreamAssets(); + + // Notify the PDG manager that the HDA is done cooking + FHoudiniPDGManager::NotifyAssetCooked(HAC->PDGAssetLink, bSuccess); + + if (bNeedsToTriggerViewportUpdate && GEditor) + { + // We need to manually update the vieport with HoudiniMeshProxies + // if not, modification made in H with the two way debugger wont be visible in Unreal until the vieports gets focus + GEditor->RedrawAllViewports(false); + } + + // Clear the rebuild/recook flags + HAC->SetRecookRequested(false); + HAC->SetRebuildRequested(false); + + //HAC->SyncToBlueprintGeneratedClass(); + + return bCookSuccess; +} + +bool +FHoudiniEngineManager::StartTaskAssetProcess(UHoudiniAssetComponent* HAC) +{ + HAC->AssetState = EHoudiniAssetState::Processing; + + return true; +} + +bool +FHoudiniEngineManager::UpdateProcess(UHoudiniAssetComponent* HAC) +{ + HAC->AssetState = EHoudiniAssetState::None; + + return true; +} + +bool +FHoudiniEngineManager::StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID) +{ + // Check this HAC doesn't already have a running task + if (OutTaskGUID.IsValid()) + return false; + + if (InAssetId >= 0) + { + /* TODO: Handle Asset Preset + if (!FHoudiniEngineUtils::GetAssetPreset(AssetId, PresetBuffer)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get the asset's preset, rebuilt asset may have lost its parameters.")); + } + */ + // Delete the asset + if (!StartTaskAssetDelete(InAssetId, OutTaskGUID, true)) + { + return false; + } + } + + // Create a new task GUID for this asset + OutTaskGUID = FGuid::NewGuid(); + + return true; +} + +bool +FHoudiniEngineManager::StartTaskAssetDelete(const HAPI_NodeId& InNodeId, FGuid& OutTaskGUID, bool bShouldDeleteParent) +{ + if (InNodeId < 0) + return false; + + // Get the Asset's NodeInfo + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, &AssetNodeInfo)); + + HAPI_NodeId OBJNodeToDelete = InNodeId; + if (AssetNodeInfo.type == HAPI_NODETYPE_SOP) + { + // For SOP Asset, we want to delete their parent's OBJ node + if (bShouldDeleteParent) + { + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(OBJNodeToDelete); + OBJNodeToDelete = ParentId != -1 ? ParentId : OBJNodeToDelete; + } + } + + // Generate GUID for our new task. + OutTaskGUID = FGuid::NewGuid(); + + // Create asset deletion task object and submit it for processing. + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetDeletion, OutTaskGUID); + Task.AssetId = OBJNodeToDelete; + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo) +{ + if (!OutTaskGUID.IsValid()) + return false; + + if (!FHoudiniEngine::Get().RetrieveTaskInfo(OutTaskGUID, OutTaskInfo)) + { + // Task information does not exist + OutTaskGUID.Invalidate(); + return false; + } + + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (EHoudiniEngineTaskState::None != OutTaskInfo.TaskState && bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().CreateTaskSlateNotification(OutTaskInfo.StatusText); + } + + switch (OutTaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::Success: + case EHoudiniEngineTaskState::FinishedWithError: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + // If the current task is finished + // Terminate the slate notification if they exist and delete/invalidate the task + if (bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().FinishTaskSlateNotification(OutTaskInfo.StatusText); + } + + FHoudiniEngine::Get().RemoveTaskInfo(OutTaskGUID); + OutTaskGUID.Invalidate(); + } + break; + + case EHoudiniEngineTaskState::Working: + { + // The current task is still running, simply update the current notification + if (bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().UpdateTaskSlateNotification(OutTaskInfo.StatusText); + } + } + break; + + case EHoudiniEngineTaskState::None: + default: + { + break; + } + } + + return true; +} + +bool +FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC) +{ + bool bManualRecook = false; + bool bComponentEnable = false; + if (HAC && !HAC->IsPendingKill()) + { + bManualRecook = HAC->HasRecookBeenRequested(); + bComponentEnable = HAC->IsCookingEnabled(); + } + + if (bManualRecook) + return true; + + if (bComponentEnable && FHoudiniEngine::Get().IsCookingEnabled()) + return true; + + return false; +} + +void +FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes called with HAC=nullptr")); + return; + } + +#if WITH_EDITOR + AActor *Owner = HAC->GetOwner(); + FString Name = Owner ? Owner->GetName() : HAC->GetName(); + + FScopedSlowTask Progress(2.0f, FText::FromString(FString::Printf(TEXT("Refining Proxy Mesh to Static Mesh on %s"), *Name))); + Progress.MakeDialog(); + Progress.EnterProgressFrame(1.0f); +#endif + + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); + +#if WITH_EDITOR + Progress.EnterProgressFrame(1.0f); +#endif +} + + +/* Unreal's viewport representation rules: + Viewport location is the actual camera location; + Lookat position is always right in front of the camera, which means the camera is looking at; + The rotator rotates the forward vector to a direction & orientation, and this dir and orientation is the camera's; + The identity direction and orientation of the camera is facing positive X-axis. +*/ + +/* Hapi's viewport representation rules: + The camera is located at a point on the sphere, which the center is the pivot position and the radius is offset; + Quat determines the location on the sphere and which direction the camera is facing towards, as well as the camera orientation; + The identity location, direction and orientation of the camera is facing positive Z-axis (in Hapi coords); +*/ + + +bool +FHoudiniEngineManager::SyncHoudiniViewportToUnreal() +{ + if (!FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled()) + return false; + +#if WITH_EDITOR + // Get the editor viewport LookAt position to spawn the new objects + if (!GEditor || !GEditor->GetActiveViewport()) + return false; + + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (!ViewportClient) + return false; + + // Get the current UE viewport location, lookat position, and rotation + FVector UnrealViewportPosition = ViewportClient->GetViewLocation(); + FRotator UnrealViewportRotation = ViewportClient->GetViewRotation(); + FVector UnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + /* Check if the Unreal viewport has changed */ + if (UnrealViewportPosition.Equals(SyncedUnrealViewportPosition) && + UnrealViewportRotation.Equals(SyncedUnrealViewportRotation) && + UnrealViewportLookatPosition.Equals(SyncedUnrealViewportLookatPosition)) + { + // No need to sync if the viewport camera hasn't changed + return false; + } + + /* Calculate Hapi Quaternion */ + // Initialize Hapi Quat with Unreal Quat. + // Note that rotations are in general non-commutative *** + FQuat HapiQuat = UnrealViewportRotation.Quaternion(); + + // We're in orbit mode, forward vector is Y-axis + if (ViewportClient->bUsingOrbitCamera) + { + // The forward vector is Y-negative direction when on orbiting mode + HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 180.f)); + + // rotations around X and Y axis are reversed + float TempX = HapiQuat.X; + HapiQuat.X = HapiQuat.Y; + HapiQuat.Y = TempX; + HapiQuat.W = -HapiQuat.W; + + } + // We're not in orbiting mode, forward vector is X-axis + else + { + // Rotate the Quat arount Z-axis by 90 degree. + HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 90.f)); + } + + + /* Update Hapi H_View */ + // Note: There are infinte number of H_View representation for current viewport + // Each choice of pivot point determines an equivalent representation. + // We just find an equivalent when the pivot position is the view position, and offset is 0 + + HAPI_Viewport H_View; + H_View.position[0] = UnrealViewportPosition.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + H_View.position[1] = UnrealViewportPosition.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + H_View.position[2] = UnrealViewportPosition.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Set HAPI_Offset always 0 when syncing Houdini to UE viewport + H_View.offset = 0.f; + + H_View.rotationQuaternion[0] = -HapiQuat.X; + H_View.rotationQuaternion[1] = -HapiQuat.Z; + H_View.rotationQuaternion[2] = -HapiQuat.Y; + H_View.rotationQuaternion[3] = HapiQuat.W; + + FHoudiniApi::SetViewport(FHoudiniEngine::Get().GetSession(), &H_View); + + /* Update the Synced viewport values + We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. + Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. */ + + // We need to get the H_Viewport again, since it is possible the value is a different equivalence of what we set. + HAPI_Viewport Cur_H_View; + FHoudiniApi::GetViewport( + FHoudiniEngine::Get().GetSession(), &Cur_H_View); + + // Hapi values are in Houdini coordinate and scale + SyncedHoudiniViewportPivotPosition = FVector(Cur_H_View.position[0], Cur_H_View.position[1], Cur_H_View.position[2]); + SyncedHoudiniViewportQuat = FQuat(Cur_H_View.rotationQuaternion[0], Cur_H_View.rotationQuaternion[1], Cur_H_View.rotationQuaternion[2], Cur_H_View.rotationQuaternion[3]); + SyncedHoudiniViewportOffset = Cur_H_View.offset; + + SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); + SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); + SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + // When sync Houdini to UE, we set offset to be 0. + // So we need to zero out offset for the next time syncing UE to Houdini + bOffsetZeroed = true; + + return true; +#endif + + return false; +} + + +bool +FHoudiniEngineManager::SyncUnrealViewportToHoudini() +{ + if (!FHoudiniEngine::Get().IsSyncUnrealViewportEnabled()) + return false; + +#if WITH_EDITOR + // Get the editor viewport LookAt position to spawn the new objects + if (!GEditor || !GEditor->GetActiveViewport()) + return false; + + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (!ViewportClient) + return false; + + // Get the current HAPI_Viewport + HAPI_Viewport H_View; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetViewport( + FHoudiniEngine::Get().GetSession(), &H_View)) + { + return false; + } + + + // Get Hapi viewport's PivotPosition, Offset and Quat, w.r.t Houdini's coordinate and scale. + FVector HapiViewportPivotPosition = FVector(H_View.position[0], H_View.position[1], H_View.position[2]); + float HapiViewportOffset = H_View.offset; + FQuat HapiViewportQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[1], H_View.rotationQuaternion[2], H_View.rotationQuaternion[3]); + + /* Check if the Houdini viewport has changed */ + if (SyncedHoudiniViewportPivotPosition.Equals(HapiViewportPivotPosition) && + SyncedHoudiniViewportQuat.Equals(HapiViewportQuat) && + SyncedHoudiniViewportOffset == HapiViewportOffset) + { + // Houdini viewport hasn't changed, nothing to do + return false; + } + + // Set zero value of offset when needed + if (bOffsetZeroed) + { + ZeroOffsetValue = H_View.offset; + bOffsetZeroed = false; + } + + + /* Translate the hapi camera transfrom to Unreal's representation system */ + + // Get pivot point in UE's coordinate and scale + FVector UnrealViewportPivotPosition = FVector(H_View.position[0], H_View.position[2], H_View.position[1]) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // HAPI bug? After we set the H_View, offset becomes a lot bigger when move the viewport just a little bit in Houdini. + // But the pivot point doesn't change. Which caused UE viewport jumping far suddenly. + // So we get rid of this problem by setting the first HAPI_offset value after syncing Houdini viewport as the base. + + // Get offset in UE's scale. The actual offset after 'zero out' + float UnrealOffset = (H_View.offset - ZeroOffsetValue) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + /* Calculate Quaternion in UE */ + // Rotate the resulting Quat around Z-axis by -90 degree. + // Note that rotation is in general non-commutative *** + FQuat UnrealQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[2], H_View.rotationQuaternion[1], -H_View.rotationQuaternion[3]); + UnrealQuat = UnrealQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)); + + FVector UnrealBaseVector(1.f, 0.f, 0.f); // Forward vector in Unreal viewport + + /* Get UE viewport location*/ + FVector UnrealViewPosition = - UnrealQuat.RotateVector(UnrealBaseVector) * UnrealOffset + UnrealViewportPivotPosition; + + /* Set the viewport's value */ + ViewportClient->SetViewLocation(UnrealViewPosition); + ViewportClient->SetViewRotation(UnrealQuat.Rotator()); + + // Invalidate the viewport + ViewportClient->Invalidate(); + + /* Update the synced viewport values */ + // We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. + // Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. + + // Hapi values are in Houdini coordinate and scale + SyncedHoudiniViewportPivotPosition = HapiViewportPivotPosition; + SyncedHoudiniViewportQuat = HapiViewportQuat; + SyncedHoudiniViewportOffset = HapiViewportOffset; + + SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); + SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); + SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + return true; +#endif + + return false; +} + + +void +FHoudiniEngineManager::DisableEditorAutoSave(const UHoudiniAssetComponent* HAC) +{ +#if WITH_EDITOR + if (!HAC || HAC->IsPendingKill()) + return; + + if (!GUnrealEd) + return; + + if (DisableAutoSavingHACs.Contains(HAC)) + return; + // Add the HAC to the set + DisableAutoSavingHACs.Add(HAC); + + // Return if auto-saving has been disabled by some other HACs. + if (DisableAutoSavingHACs.Num() > 1) + return; + + // Disable auto-saving by setting min time till auto-save to max float value + IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); + AutoSaver.ForceMinimumTimeTillAutoSave(TNumericLimits::Max()); +#endif +} + + +void +FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = nullptr) +{ +#if WITH_EDITOR + if (!GUnrealEd) + return; + + if (!HAC) + { + // When HAC is nullptr, go through all HACs in the set, + // remove it if the HAC has been deleted. + if (DisableAutoSavingHACs.Num() <= 0) + return; + + TSet ValidComponents; + for (auto& CurHAC : DisableAutoSavingHACs) + { + if (CurHAC && !CurHAC->IsPendingKill()) + { + ValidComponents.Add(CurHAC); + } + } + DisableAutoSavingHACs = MoveTemp(ValidComponents); + } + else + { + // Otherwise, remove the HAC from the set + if (DisableAutoSavingHACs.Contains(HAC)) + DisableAutoSavingHACs.Remove(HAC); + } + + if (DisableAutoSavingHACs.Num() > 0) + return; + + // When no HAC disables cooking, reset min time till auto-save to default value, then reset the timer + IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); + AutoSaver.ResetAutoSaveTimer(); +#endif +} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.h b/Source/HoudiniEngine/Private/HoudiniEngineManager.h index 770997109..d858a224e 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.h @@ -1,185 +1,179 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "TimerManager.h" - -//#include "HAL/Runnable.h" -//#include "HAL/RunnableThread.h" -//#include "Misc/SingleThreadRunnable.h" - -#include "HoudiniPDGManager.h" - -class UHoudiniAsset; -class UHoudiniAssetComponent; - -struct FHoudiniEngineTaskInfo; -struct FGuid; - -enum class EHoudiniAssetState : uint8; - -class FHoudiniEngineManager -{ -public: - - FHoudiniEngineManager(); - virtual ~FHoudiniEngineManager(); - - void StartHoudiniTicking(); - void StopHoudiniTicking(); - void Tick(); - - // Updates / Process a component - void ProcessComponent(UHoudiniAssetComponent* HAC); - - // Build UStaticMesh for all UHoudiniStaticMesh in a HAC. - // This is fired by the OnRefinedMeshesTimerDelegate on a HAC - void BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC); - - void StartPDGCommandlet() - { - if (!IsPDGCommandletRunningOrConnected()) - PDGManager.CreateBGEOCommandletAndEndpoint(); - } - - void StopPDGCommandlet() - { - if (IsPDGCommandletRunningOrConnected()) - PDGManager.StopBGEOCommandletAndEndpoint(); - } - - bool IsPDGCommandletRunningOrConnected() - { - const EHoudiniBGEOCommandletStatus Status = PDGManager.UpdateAndGetBGEOCommandletStatus(); - return Status == EHoudiniBGEOCommandletStatus::Running || Status == EHoudiniBGEOCommandletStatus::Connected; - } - - EHoudiniBGEOCommandletStatus GetPDGCommandletStatus() { return PDGManager.UpdateAndGetBGEOCommandletStatus(); } - - -protected: - - // Updates a given task's status - // Returns true if the given task's status was properly found - bool UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo); - - // Start a task to instantiate the given HoudiniAsset - // Return true if the task was successfully created - bool StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID); - - // Updates progress of the instantiation task - // Returns true if a state change should be made - bool UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); - - // Start a task to instantiate the Houdini Asset with the given node Id - // Returns true if the task was successfully created - bool StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID); - - // Updates progress of the cooking task - // Returns true if a state change should be made - bool UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); - - // Called to update template components. - bool PreCookTemplate(UHoudiniAssetComponent* HAC); - - // Called to update all houdini nodes/params/inputs before a cook has started - bool PreCook(UHoudiniAssetComponent* HAC); - - // Called after a cook has finished - bool PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId); - - bool StartTaskAssetProcess(UHoudiniAssetComponent* HAC); - - bool UpdateProcess(UHoudiniAssetComponent* HAC); - - // Starts a rebuild task (delete then re instantiate) - // The NodeID should be invalidated after a successful call - bool StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID); - - // Starts a node delete task - // The NodeID should be invalidated after a successful call - bool StartTaskAssetDelete(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID, bool bShouldDeleteParent); - - bool IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC); - - // Syncs the houdini viewport to Unreal's viewport - // Returns true if the Houdini viewport has been modified - bool SyncHoudiniViewportToUnreal(); - - // Syncs the unreal viewport to Houdini's viewport - // Returns true if the Unreal viewport has been modified - bool SyncUnrealViewportToHoudini(); - - // Disable auto save by setting min time till auto save to the max value - void DisableEditorAutoSave(const UHoudiniAssetComponent* HAC); - - void EnableEditorAutoSave(const UHoudiniAssetComponent* HAC); - -private: - - // Delay between each update of the manager - static const float TickTimerDelay; - - // Timer handle, this timer is used for processing HAC. - FTimerHandle TimerHandleProcess; - - // Timer delegate, we use it for ticking during processing. - FTimerDelegate TimerDelegateProcess; - - // Current position in the array - uint32 CurrentIndex; - - // Current number of components in the array - uint32 ComponentCount; - - // Stopping flag. - // Indicates that we should stop ticking asap - bool bMustStopTicking; - - // The PDG Manager, handles all registered PDG Asset Links - FHoudiniPDGManager PDGManager; - - // For ViewportSync: The camera transform that Hapi and Unreal currently agree with. - FVector SyncedHoudiniViewportPivotPosition; - FQuat SyncedHoudiniViewportQuat; - float SyncedHoudiniViewportOffset; - - FVector SyncedUnrealViewportPosition; - FRotator SyncedUnrealViewportRotation; - FVector SyncedUnrealViewportLookatPosition; - - // We need these two variables to get rid of a HAPI bug - // Note: When sync Houdini to UE, we set the pivot position to be the view position, and offset to be 0.0. - // but when we switch to control viewport in Houdini, HAPI returns an H_View with a large offset, but pivot unchanged. - // so, we need these two variables to 'zero out' offset. - float ZeroOffsetValue; // in HAPI scale - bool bOffsetZeroed; - - // Indicates which HACs disable auto-saving - TSet DisableAutoSavingHACs; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "TimerManager.h" + +//#include "HAL/Runnable.h" +//#include "HAL/RunnableThread.h" +//#include "Misc/SingleThreadRunnable.h" + +#include "HoudiniPDGManager.h" + +class UHoudiniAsset; +class UHoudiniAssetComponent; + +struct FHoudiniEngineTaskInfo; +struct FGuid; + +enum class EHoudiniAssetState : uint8; + +class FHoudiniEngineManager +{ +public: + + FHoudiniEngineManager(); + virtual ~FHoudiniEngineManager(); + + void StartHoudiniTicking(); + void StopHoudiniTicking(); + bool Tick(float DeltaTime); + + // Updates / Process a component + void ProcessComponent(UHoudiniAssetComponent* HAC); + + // Build UStaticMesh for all UHoudiniStaticMesh in a HAC. + // This is fired by the OnRefinedMeshesTimerDelegate on a HAC + void BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC); + + void StartPDGCommandlet() + { + if (!IsPDGCommandletRunningOrConnected()) + PDGManager.CreateBGEOCommandletAndEndpoint(); + } + + void StopPDGCommandlet() + { + if (IsPDGCommandletRunningOrConnected()) + PDGManager.StopBGEOCommandletAndEndpoint(); + } + + bool IsPDGCommandletRunningOrConnected() + { + const EHoudiniBGEOCommandletStatus Status = PDGManager.UpdateAndGetBGEOCommandletStatus(); + return Status == EHoudiniBGEOCommandletStatus::Running || Status == EHoudiniBGEOCommandletStatus::Connected; + } + + EHoudiniBGEOCommandletStatus GetPDGCommandletStatus() { return PDGManager.UpdateAndGetBGEOCommandletStatus(); } + + +protected: + + // Updates a given task's status + // Returns true if the given task's status was properly found + bool UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo); + + // Start a task to instantiate the given HoudiniAsset + // Return true if the task was successfully created + bool StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID); + + // Updates progress of the instantiation task + // Returns true if a state change should be made + bool UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); + + // Start a task to instantiate the Houdini Asset with the given node Id + // Returns true if the task was successfully created + bool StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID); + + // Updates progress of the cooking task + // Returns true if a state change should be made + bool UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); + + // Called to update template components. + bool PreCookTemplate(UHoudiniAssetComponent* HAC); + + // Called to update all houdini nodes/params/inputs before a cook has started + bool PreCook(UHoudiniAssetComponent* HAC); + + // Called after a cook has finished + bool PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId); + + bool StartTaskAssetProcess(UHoudiniAssetComponent* HAC); + + bool UpdateProcess(UHoudiniAssetComponent* HAC); + + // Starts a rebuild task (delete then re instantiate) + // The NodeID should be invalidated after a successful call + bool StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID); + + // Starts a node delete task + // The NodeID should be invalidated after a successful call + bool StartTaskAssetDelete(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID, bool bShouldDeleteParent); + + bool IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC); + + // Syncs the houdini viewport to Unreal's viewport + // Returns true if the Houdini viewport has been modified + bool SyncHoudiniViewportToUnreal(); + + // Syncs the unreal viewport to Houdini's viewport + // Returns true if the Unreal viewport has been modified + bool SyncUnrealViewportToHoudini(); + + // Disable auto save by setting min time till auto save to the max value + void DisableEditorAutoSave(const UHoudiniAssetComponent* HAC); + + void EnableEditorAutoSave(const UHoudiniAssetComponent* HAC); + +private: + + // Ticker handle, used for processing HAC. + FDelegateHandle TickerHandle; + + // Current position in the array + uint32 CurrentIndex; + + // Current number of components in the array + uint32 ComponentCount; + + // Stopping flag. + // Indicates that we should stop ticking asap + bool bMustStopTicking; + + // The PDG Manager, handles all registered PDG Asset Links + FHoudiniPDGManager PDGManager; + + // For ViewportSync: The camera transform that Hapi and Unreal currently agree with. + FVector SyncedHoudiniViewportPivotPosition; + FQuat SyncedHoudiniViewportQuat; + float SyncedHoudiniViewportOffset; + + FVector SyncedUnrealViewportPosition; + FRotator SyncedUnrealViewportRotation; + FVector SyncedUnrealViewportLookatPosition; + + // We need these two variables to get rid of a HAPI bug + // Note: When sync Houdini to UE, we set the pivot position to be the view position, and offset to be 0.0. + // but when we switch to control viewport in Houdini, HAPI returns an H_View with a large offset, but pivot unchanged. + // so, we need these two variables to 'zero out' offset. + float ZeroOffsetValue; // in HAPI scale + bool bOffsetZeroed; + + // Indicates which HACs disable auto-saving + TSet DisableAutoSavingHACs; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp index 908540b9f..ce7231f85 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp @@ -1,35 +1,60 @@ - -#include "HoudiniEngineOutputStats.h" - -FHoudiniEngineOutputStats::FHoudiniEngineOutputStats() - : NumPackagesCreated(0) - , NumPackagesUpdated(0) -{ } - -void FHoudiniEngineOutputStats::NotifyPackageCreated(int32 NumCreated) -{ - NumPackagesCreated += NumCreated; -} - -void FHoudiniEngineOutputStats::NotifyPackageUpdated(int32 NumUpdated) -{ - NumPackagesUpdated += NumUpdated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated) -{ - const int32 Count = OutputObjectsCreated.FindOrAdd(ObjectTypeName, 0); - OutputObjectsCreated[ObjectTypeName] = Count + NumCreated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated) -{ - const int32 Count = OutputObjectsUpdated.FindOrAdd(ObjectTypeName, 0); - OutputObjectsUpdated[ObjectTypeName] = Count + NumUpdated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced) -{ - const int32 Count = OutputObjectsReplaced.FindOrAdd(ObjectTypeName, 0); - OutputObjectsReplaced[ObjectTypeName] = Count + NumReplaced; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineOutputStats.h" + +FHoudiniEngineOutputStats::FHoudiniEngineOutputStats() + : NumPackagesCreated(0) + , NumPackagesUpdated(0) +{ } + +void FHoudiniEngineOutputStats::NotifyPackageCreated(int32 NumCreated) +{ + NumPackagesCreated += NumCreated; +} + +void FHoudiniEngineOutputStats::NotifyPackageUpdated(int32 NumUpdated) +{ + NumPackagesUpdated += NumUpdated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated) +{ + const int32 Count = OutputObjectsCreated.FindOrAdd(ObjectTypeName, 0); + OutputObjectsCreated[ObjectTypeName] = Count + NumCreated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated) +{ + const int32 Count = OutputObjectsUpdated.FindOrAdd(ObjectTypeName, 0); + OutputObjectsUpdated[ObjectTypeName] = Count + NumUpdated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced) +{ + const int32 Count = OutputObjectsReplaced.FindOrAdd(ObjectTypeName, 0); + OutputObjectsReplaced[ObjectTypeName] = Count + NumReplaced; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h index 26c6f022f..6234dca1b 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Class.h" - -struct HOUDINIENGINE_API FHoudiniEngineOutputStats -{ - FHoudiniEngineOutputStats(); - - int32 NumPackagesCreated; - int32 NumPackagesUpdated; - - // These FStrings should preferably be EHoudiniOutputType enum - // Move the OUtput enums into a separate header to avoid circular dependencies. - TMap OutputObjectsCreated; - TMap OutputObjectsUpdated; - TMap OutputObjectsReplaced; - - void NotifyPackageCreated(int32 NumCreated); - void NotifyPackageUpdated(int32 NumUpdated); - - // Objects created - void NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated); - template - void NotifyObjectsCreated(EnumT EnumValue, int32 NumCreated) - { - NotifyObjectsCreated( UEnum::GetValueAsString(EnumValue), NumCreated ); - } - - // Object updated - void NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated); - template - void NotifyObjectsUpdated(EnumT EnumValue, int32 NumUpdated) - { - NotifyObjectsUpdated( UEnum::GetValueAsString(EnumValue), NumUpdated ); - } - - // Objects replaced - void NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced); - template - void NotifyObjectsReplaced(EnumT EnumValue, int32 NumReplaced) - { - NotifyObjectsReplaced( UEnum::GetValueAsString(EnumValue), NumReplaced ); - } -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Class.h" + +struct HOUDINIENGINE_API FHoudiniEngineOutputStats +{ + FHoudiniEngineOutputStats(); + + int32 NumPackagesCreated; + int32 NumPackagesUpdated; + + // These FStrings should preferably be EHoudiniOutputType enum + // Move the OUtput enums into a separate header to avoid circular dependencies. + TMap OutputObjectsCreated; + TMap OutputObjectsUpdated; + TMap OutputObjectsReplaced; + + void NotifyPackageCreated(int32 NumCreated); + void NotifyPackageUpdated(int32 NumUpdated); + + // Objects created + void NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated); + template + void NotifyObjectsCreated(EnumT EnumValue, int32 NumCreated) + { + NotifyObjectsCreated( UEnum::GetValueAsString(EnumValue), NumCreated ); + } + + // Object updated + void NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated); + template + void NotifyObjectsUpdated(EnumT EnumValue, int32 NumUpdated) + { + NotifyObjectsUpdated( UEnum::GetValueAsString(EnumValue), NumUpdated ); + } + + // Objects replaced + void NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced); + template + void NotifyObjectsReplaced(EnumT EnumValue, int32 NumReplaced) + { + NotifyObjectsReplaced( UEnum::GetValueAsString(EnumValue), NumReplaced ); + } +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h index f2cd4eee3..98b9fd867 100644 --- a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h +++ b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h @@ -1,382 +1,366 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -// Indicate we're in the HoudiniEngine module -#define HOUDINI_ENGINE -#include "HoudiniEngineRuntimePrivatePCH.h" - -// HFS path definition coming from UBT/build.cs files. -#ifndef HOUDINI_ENGINE_HFS_PATH_DEFINE - #define HOUDINI_ENGINE_HFS_PATH "" -#else - #define HOUDINI_ENGINE_STRINGIFY_HELPER(X) #X - #define HOUDINI_ENGINE_STRINGIFY(X) HOUDINI_ENGINE_STRINGIFY_HELPER(X) - #define HOUDINI_ENGINE_HFS_PATH HOUDINI_ENGINE_STRINGIFY(HOUDINI_ENGINE_HFS_PATH_DEFINE) -#endif - -// HFS subfolder containing HAPI lib. -#define HAPI_HFS_SUBFOLDER_WINDOWS TEXT( "bin" ) -#define HAPI_HFS_SUBFOLDER_MAC TEXT( "dsolib" ) -#define HAPI_HFS_SUBFOLDER_LINUX TEXT( "dsolib" ) - -// Unreal HAPI Resources. -#define HAPI_UNREAL_RESOURCE_HOUDINI_LOGO TEXT( "/HoudiniEngine/houdini_logo.houdini_logo" ) -#define HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL TEXT( "/HoudiniEngine/houdini_default_material.houdini_default_material" ) -#define HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL TEXT( "/HoudiniEngine/houdini_templated_material.houdini_templated_material") -#define HAPI_UNREAL_RESOURCE_BGEO_IMPORT TEXT( "/HoudiniEngine/houdini_bgeo_import.houdini_bgeo_import" ) - -#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH TEXT("/HoudiniEngine/default_reference_static_mesh.default_reference_static_mesh") -#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL TEXT("/HoudiniEngine/default_reference_static_mesh_material.default_reference_static_mesh_material") - -// Client name so HAPI knows we're running inside unreal -#define HAPI_UNREAL_CLIENT_NAME "unreal" - -// Error checking - this macro will check the status and return specified parameter. -#define HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HAPI_LOG_ROUTINE ) \ - do \ - { \ - HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ - if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - return HAPI_PARAM_RETURN; \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR_RETURN( HAPI_PARAM_CALL, HAPI_PARAM_RETURN ) \ - HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HOUDINI_LOG_ERROR ) - -// Simple Error checking - this macro will check the status. -#define HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ - do \ - { \ - HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ - if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR( HAPI_PARAM_CALL ) \ - HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) - -// Error checking - this macro will check the status and returns it. -#define HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ - do \ - { \ - *HAPI_PARAM_RESULT = HAPI_PARAM_CALL; \ - if ( *HAPI_PARAM_RESULT != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR_GET( HAPI_PARAM_RESULT, HAPI_PARAM_CALL ) \ - HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) - -// For Transform conversion between UE4 / Houdini -// Set to 0 to stop converting Houdini's coordinate space to unreal's -#define HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM 1 - -#define HAPI_UNREAL_SCALE_FACTOR_POSITION 100.0f -#define HAPI_UNREAL_SCALE_FACTOR_TRANSLATION 100.0f -#define HAPI_UNREAL_SCALE_FACTOR_SCALE 100.0f - -#define HAPI_UNREAL_SCALE_SMALL_VALUE KINDA_SMALL_NUMBER * 2.0f - -#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) - -// Attributes -#define HAPI_UNREAL_ATTRIB_POSITION HAPI_ATTRIB_POSITION -#define HAPI_UNREAL_ATTRIB_ROTATION "rot" -#define HAPI_UNREAL_ATTRIB_SCALE "scale" -#define HAPI_UNREAL_ATTRIB_UNIFORM_SCALE "pscale" -#define HAPI_UNREAL_ATTRIB_COLOR HAPI_ATTRIB_COLOR -#define HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR "unreal_lightmap_color" -#define HAPI_UNREAL_ATTRIB_ALPHA "Alpha" -#define HAPI_UNREAL_ATTRIB_UV HAPI_ATTRIB_UV -#define HAPI_UNREAL_ATTRIB_NORMAL HAPI_ATTRIB_NORMAL -#define HAPI_UNREAL_ATTRIB_TANGENTU HAPI_ATTRIB_TANGENT -#define HAPI_UNREAL_ATTRIB_TANGENTV HAPI_ATTRIB_TANGENT2 - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE "tile" -// Always the name of the main landscape actor. -// Names for landscape tile actors will be taken from 'unreal_output_name'. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME "unreal_landscape_shared_actor_name" -// This tile_actor_type succeeds the 'unreal_landscape_streaming_proxy' (v1) attribute. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE "unreal_landscape_tile_actor_type" -// This attribute is for backwards compatibility only. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN "unreal_landscape_layer_min" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX "unreal_landscape_layer_max" - -// Path to the level in which an actor should be generated or which contained the input data -// "." - (Default) Generate geometry in the the current persistent world -// "Junk/Background" - Path to a Map that is relative to the current persistent world's Map. -// "/Game/Maps/Level01/Junk/Background" - Absolute path to the map in which the primitive should be output -#define HAPI_UNREAL_ATTRIB_LEVEL_PATH "unreal_level_path" - -// Path to the actor that contained the input data/should be generated -#define HAPI_UNREAL_ATTRIB_ACTOR_PATH "unreal_actor_path" - -// Path to the object plugged in a geo in -#define HAPI_UNREAL_ATTRIB_OBJECT_PATH "unreal_object_path" - -// Attributes used for data exchange between UE4 and Houdini -#define HAPI_UNREAL_ATTRIB_MATERIAL "unreal_material" -#define HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK "unreal_face_material" -#define HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE "unreal_material_instance" -#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE "unreal_material_hole" -#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE "unreal_material_hole_instance" -#define HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL "unreal_physical_material" -#define HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK "unreal_face_smoothing_mask" -#define HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION "unreal_lightmap_resolution" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE "lod_screensize" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX "lod" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX "_screensize" -#define HAPI_UNREAL_ATTRIB_TAG_PREFIX "unreal_tag_" - -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX "mesh_socket" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME "mesh_socket_name" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD "unreal_mesh_socket_name" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR "mesh_socket_actor" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD "unreal_mesh_socket_actor" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG "mesh_socket_tag" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD "unreal_mesh_socket_tag" - -#define HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME "unreal_input_mesh_name" -#define HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE "unreal_input_source_file" - -#define HAPI_UNREAL_ATTRIB_INSTANCE "instance" -#define HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE "unreal_instance" -#define HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES "unreal_split_instances" -#define HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER "unreal_foliage" -#define HAPI_UNREAL_ATTRIB_INSTANCE_ROTATION "rot" -#define HAPI_UNREAL_ATTRIB_INSTANCE_SCALE "scale" -#define HAPI_UNREAL_ATTRIB_INSTANCE_POSITION HAPI_ATTRIB_POSITION -#define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" -#define HAPI_UNREAL_ATTRIB_SPLIT_ATTR "unreal_split_attr" -#define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" - - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX "unreal_vertex_index" -#define HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER "unreal_unit_landscape_layer" -#define HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS "unreal_landscape_layer_nonweightblended" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO "unreal_landscape_layer_info" - -#define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" -#define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" - -#define HAPI_UNREAL_ATTRIB_BAKE_FOLDER "unreal_bake_folder" -#define HAPI_UNREAL_ATTRIB_TEMP_FOLDER "unreal_temp_folder" -#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1 "unreal_generated_mesh_name" -#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2 "unreal_output_name" -#define HAPI_UNREAL_ATTRIB_BAKE_ACTOR "unreal_bake_actor" -#define HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER "unreal_bake_outliner_folder" - -// data tables -#define HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX "unreal_data_table_" -#define HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT "unreal_datatable_rowstruct" - -// Attributes for Curve Outputs -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE "unreal_output_curve" -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR "unreal_output_curve_linear" -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED "unreal_output_curve_closed" -// We only support Unreal spline outputs for now -//#define HAPI_UNREAL_ATTRIB_OUTPUT_HOUDINI_CURVE "houdini_output_curve" - -// Geometry Node -#define HAPI_UNREAL_PARAM_TRANSLATE "t" -#define HAPI_UNREAL_PARAM_ROTATE "r" -#define HAPI_UNREAL_PARAM_SCALE "s" -#define HAPI_UNREAL_PARAM_PIVOT "p" -#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" - -// Houdini Curve -#define HAPI_UNREAL_PARAM_CURVE_TYPE "type" -#define HAPI_UNREAL_PARAM_CURVE_METHOD "method" -#define HAPI_UNREAL_PARAM_CURVE_COORDS "coords" -#define HAPI_UNREAL_PARAM_CURVE_CLOSED "close" -#define HAPI_UNREAL_PARAM_CURVE_REVERSED "reverse" - -#define HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT "0.0, 0.0, 3.0 3.0, 0.0, 3.0" - -// String Params tags -#define HOUDINI_PARAMETER_STRING_REF_TAG TEXT("unreal_ref") -#define HOUDINI_PARAMETER_STRING_REF_CLASS_TAG TEXT("unreal_ref_class") -#define HOUDINI_PARAMETER_STRING_MULTILINE_TAG TEXT("editor") - -// Parameter tags -#define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" -#define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" -#define HAPI_PARAM_TAG_UNITS "units" - -// TODO: unused, remove! -#define HAPI_PARAM_TAG_ASSET_REF "asset_ref" - -// Groups -#define HAPI_UNREAL_GROUP_LOD_PREFIX TEXT("lod") -#define HAPI_UNREAL_GROUP_SOCKET_PREFIX TEXT("mesh_socket") -#define HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD TEXT("socket") -//#define HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX TEXT("unreal_split") - -#define HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION TEXT("main_geo") - -#define HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX TEXT("collision_geo") -#define HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX TEXT("rendered_collision_geo") - -#define HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX TEXT("collision_geo_ucx") -#define HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX TEXT("rendered_collision_geo_ucx") - -#define HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX TEXT("collision_geo_simple") -#define HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX TEXT("rendered_collision_geo_simple") - -// Default material name. -#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) - -// Various variable names used to store meta information in generated packages. -#define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE TEXT( "HoudiniGeneratedTextureType" ) -#define HAPI_UNREAL_PACKAGE_META_NODE_PATH TEXT( "HoudiniNodePath" ) -#define HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER TEXT( "HoudiniPackageBakeCounter" ) -#define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) - -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL TEXT( "N" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE TEXT( "C_A" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR TEXT( "S" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS TEXT( "R" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC TEXT( "M" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE TEXT( "E" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK TEXT( "O" ) - -// Texture planes. -#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA "C A" -#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR "C" -#define HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA "A" -#define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" - -// Materials Diffuse. -#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" - -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 "ogl_tex1" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 "basecolor_texture" - -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0 "ogl_diff" -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1 "basecolor" - -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" - -// Materials Normal. -#define HAPI_UNREAL_PARAM_MAP_NORMAL_0 "ogl_normalmap" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_1 "normalTexture" - -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" - -#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" - -// Materials Specular. -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_0 "ogl_specmap" -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_1 "reflect_texture" - -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_0 "ogl_spec" -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_1 "reflect" - -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" - -// Materials Roughness. -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0 "ogl_roughmap" -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1 "rough_texture" - -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0 "ogl_rough" -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1 "rough" - -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" - -// Materials Metallic. -#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" -#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" -#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" - -// Materials Emissive. -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" - -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0 "ogl_emit" -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1 "emitcolor" - -// Materials Opacity. -#define HAPI_UNREAL_PARAM_ALPHA_0 "ogl_alpha" -#define HAPI_UNREAL_PARAM_ALPHA_1 "opac" - -#define HAPI_UNREAL_PARAM_MAP_OPACITY_0 "ogl_opacitymap" -#define HAPI_UNREAL_PARAM_MAP_OPACITY_1 "opaccolor_texture" - -// Number of GUID characters to keep for packages -#define PACKAGE_GUID_LENGTH 8 -#define PACKAGE_GUID_COMPONENT_LENGTH 12 - -/** Ramp related defines. **/ -#define HAPI_UNREAL_RAMP_FLOAT_AXIS_X "position" -#define HAPI_UNREAL_RAMP_FLOAT_AXIS_Y "value" -#define HAPI_UNREAL_RAMP_COLOR_AXIS_X "position" -#define HAPI_UNREAL_RAMP_COLOR_AXIS_Y "color" - -/** Handle types. **/ -#define HAPI_UNREAL_HANDLE_TRANSFORM "xform" -#define HAPI_UNREAL_HANDLE_BOUNDER "bound" - -#define HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME "height" - -#define HAPI_UNREAL_NOTIFICATION_FADEOUT 2.0f -#define HAPI_UNREAL_NOTIFICATION_EXPIRE 2.0f - -// Struct to enable global silent flag - this will force dialogs to not show up. -struct FHoudiniScopedGlobalSilence -{ - FHoudiniScopedGlobalSilence() - { - bGlobalSilent = GIsSilent; - GIsSilent = true; - } - - ~FHoudiniScopedGlobalSilence() - { - GIsSilent = bGlobalSilent; - } - - bool bGlobalSilent; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +// Indicate we're in the HoudiniEngine module +#define HOUDINI_ENGINE +#include "HoudiniEngineRuntimePrivatePCH.h" + +// HFS path definition coming from UBT/build.cs files. +#ifndef HOUDINI_ENGINE_HFS_PATH_DEFINE + #define HOUDINI_ENGINE_HFS_PATH "" +#else + #define HOUDINI_ENGINE_STRINGIFY_HELPER(X) #X + #define HOUDINI_ENGINE_STRINGIFY(X) HOUDINI_ENGINE_STRINGIFY_HELPER(X) + #define HOUDINI_ENGINE_HFS_PATH HOUDINI_ENGINE_STRINGIFY(HOUDINI_ENGINE_HFS_PATH_DEFINE) +#endif + +// HFS subfolder containing HAPI lib. +#define HAPI_HFS_SUBFOLDER_WINDOWS TEXT( "bin" ) +#define HAPI_HFS_SUBFOLDER_MAC TEXT( "dsolib" ) +#define HAPI_HFS_SUBFOLDER_LINUX TEXT( "dsolib" ) + +// Unreal HAPI Resources. +#define HAPI_UNREAL_RESOURCE_HOUDINI_LOGO TEXT( "/HoudiniEngine/houdini_logo.houdini_logo" ) +#define HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL TEXT( "/HoudiniEngine/houdini_default_material.houdini_default_material" ) +#define HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL TEXT( "/HoudiniEngine/houdini_templated_material.houdini_templated_material") +#define HAPI_UNREAL_RESOURCE_BGEO_IMPORT TEXT( "/HoudiniEngine/houdini_bgeo_import.houdini_bgeo_import" ) + +#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH TEXT("/HoudiniEngine/default_reference_static_mesh.default_reference_static_mesh") +#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL TEXT("/HoudiniEngine/default_reference_static_mesh_material.default_reference_static_mesh_material") + +// Client name so HAPI knows we're running inside unreal +#define HAPI_UNREAL_CLIENT_NAME "unreal" + +// Error checking - this macro will check the status and return specified parameter. +#define HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HAPI_LOG_ROUTINE ) \ + do \ + { \ + HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ + if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + return HAPI_PARAM_RETURN; \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR_RETURN( HAPI_PARAM_CALL, HAPI_PARAM_RETURN ) \ + HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HOUDINI_LOG_ERROR ) + +// Simple Error checking - this macro will check the status. +#define HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ + do \ + { \ + HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ + if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR( HAPI_PARAM_CALL ) \ + HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) + +// Error checking - this macro will check the status and returns it. +#define HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ + do \ + { \ + *HAPI_PARAM_RESULT = HAPI_PARAM_CALL; \ + if ( *HAPI_PARAM_RESULT != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR_GET( HAPI_PARAM_RESULT, HAPI_PARAM_CALL ) \ + HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) + +// For Transform conversion between UE4 / Houdini +// Set to 0 to stop converting Houdini's coordinate space to unreal's +#define HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM 1 + +#define HAPI_UNREAL_SCALE_FACTOR_POSITION 100.0f +#define HAPI_UNREAL_SCALE_FACTOR_TRANSLATION 100.0f +#define HAPI_UNREAL_SCALE_FACTOR_SCALE 100.0f + +#define HAPI_UNREAL_SCALE_SMALL_VALUE KINDA_SMALL_NUMBER * 2.0f + +#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) + +// Attributes +#define HAPI_UNREAL_ATTRIB_POSITION HAPI_ATTRIB_POSITION +#define HAPI_UNREAL_ATTRIB_ROTATION "rot" +#define HAPI_UNREAL_ATTRIB_SCALE "scale" +#define HAPI_UNREAL_ATTRIB_UNIFORM_SCALE "pscale" +#define HAPI_UNREAL_ATTRIB_COLOR HAPI_ATTRIB_COLOR +#define HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR "unreal_lightmap_color" +#define HAPI_UNREAL_ATTRIB_ALPHA "Alpha" +#define HAPI_UNREAL_ATTRIB_UV HAPI_ATTRIB_UV +#define HAPI_UNREAL_ATTRIB_NORMAL HAPI_ATTRIB_NORMAL +#define HAPI_UNREAL_ATTRIB_TANGENTU HAPI_ATTRIB_TANGENT +#define HAPI_UNREAL_ATTRIB_TANGENTV HAPI_ATTRIB_TANGENT2 + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE "tile" +// Always the name of the main landscape actor. +// Names for landscape tile actors will be taken from 'unreal_output_name'. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME "unreal_landscape_shared_actor_name" +// This tile_actor_type succeeds the 'unreal_landscape_streaming_proxy' (v1) attribute. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE "unreal_landscape_tile_actor_type" +// This attribute is for backwards compatibility only. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN "unreal_landscape_layer_min" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX "unreal_landscape_layer_max" + +// Path to the level in which an actor should be generated or which contained the input data +// "." - (Default) Generate geometry in the the current persistent world +// "Junk/Background" - Path to a Map that is relative to the current persistent world's Map. +// "/Game/Maps/Level01/Junk/Background" - Absolute path to the map in which the primitive should be output +#define HAPI_UNREAL_ATTRIB_LEVEL_PATH "unreal_level_path" + +// Path to the actor that contained the input data/should be generated +#define HAPI_UNREAL_ATTRIB_ACTOR_PATH "unreal_actor_path" + +// Path to the object plugged in a geo in +#define HAPI_UNREAL_ATTRIB_OBJECT_PATH "unreal_object_path" + +// Attributes used for data exchange between UE4 and Houdini +#define HAPI_UNREAL_ATTRIB_MATERIAL "unreal_material" +#define HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK "unreal_face_material" +#define HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE "unreal_material_instance" +#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE "unreal_material_hole" +#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE "unreal_material_hole_instance" +#define HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL "unreal_physical_material" +#define HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK "unreal_face_smoothing_mask" +#define HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION "unreal_lightmap_resolution" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE "lod_screensize" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX "lod" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX "_screensize" +#define HAPI_UNREAL_ATTRIB_TAG_PREFIX "unreal_tag_" + +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX "mesh_socket" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME "mesh_socket_name" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD "unreal_mesh_socket_name" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR "mesh_socket_actor" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD "unreal_mesh_socket_actor" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG "mesh_socket_tag" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD "unreal_mesh_socket_tag" + +#define HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME "unreal_input_mesh_name" +#define HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE "unreal_input_source_file" + +#define HAPI_UNREAL_ATTRIB_INSTANCE "instance" +#define HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE "unreal_instance" +#define HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES "unreal_split_instances" +#define HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER "unreal_foliage" +#define HAPI_UNREAL_ATTRIB_INSTANCE_ROTATION "rot" +#define HAPI_UNREAL_ATTRIB_INSTANCE_SCALE "scale" +#define HAPI_UNREAL_ATTRIB_INSTANCE_POSITION HAPI_ATTRIB_POSITION +#define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" +#define HAPI_UNREAL_ATTRIB_SPLIT_ATTR "unreal_split_attr" +#define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" + + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX "unreal_vertex_index" +#define HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER "unreal_unit_landscape_layer" +#define HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS "unreal_landscape_layer_nonweightblended" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO "unreal_landscape_layer_info" + +#define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" +#define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" + +#define HAPI_UNREAL_ATTRIB_BAKE_FOLDER "unreal_bake_folder" +#define HAPI_UNREAL_ATTRIB_TEMP_FOLDER "unreal_temp_folder" +#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1 "unreal_generated_mesh_name" +#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2 "unreal_output_name" +#define HAPI_UNREAL_ATTRIB_BAKE_ACTOR "unreal_bake_actor" +#define HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER "unreal_bake_outliner_folder" + +// data tables +#define HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX "unreal_data_table_" +#define HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT "unreal_datatable_rowstruct" + +// Attributes for Curve Outputs +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE "unreal_output_curve" +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR "unreal_output_curve_linear" +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED "unreal_output_curve_closed" +// We only support Unreal spline outputs for now +//#define HAPI_UNREAL_ATTRIB_OUTPUT_HOUDINI_CURVE "houdini_output_curve" + +// Geometry Node +#define HAPI_UNREAL_PARAM_TRANSLATE "t" +#define HAPI_UNREAL_PARAM_ROTATE "r" +#define HAPI_UNREAL_PARAM_SCALE "s" +#define HAPI_UNREAL_PARAM_PIVOT "p" +#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" + +// Houdini Curve +#define HAPI_UNREAL_PARAM_CURVE_TYPE "type" +#define HAPI_UNREAL_PARAM_CURVE_METHOD "method" +#define HAPI_UNREAL_PARAM_CURVE_COORDS "coords" +#define HAPI_UNREAL_PARAM_CURVE_CLOSED "close" +#define HAPI_UNREAL_PARAM_CURVE_REVERSED "reverse" + +#define HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT "0.0, 0.0, 3.0 3.0, 0.0, 3.0" + +// String Params tags +#define HOUDINI_PARAMETER_STRING_REF_TAG TEXT("unreal_ref") +#define HOUDINI_PARAMETER_STRING_REF_CLASS_TAG TEXT("unreal_ref_class") +#define HOUDINI_PARAMETER_STRING_MULTILINE_TAG TEXT("editor") + +// Parameter tags +#define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" +#define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" +#define HAPI_PARAM_TAG_UNITS "units" + +// TODO: unused, remove! +#define HAPI_PARAM_TAG_ASSET_REF "asset_ref" + +// Groups +#define HAPI_UNREAL_GROUP_LOD_PREFIX TEXT("lod") +#define HAPI_UNREAL_GROUP_SOCKET_PREFIX TEXT("mesh_socket") +#define HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD TEXT("socket") +//#define HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX TEXT("unreal_split") + +#define HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION TEXT("main_geo") + +#define HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX TEXT("collision_geo") +#define HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX TEXT("rendered_collision_geo") + +#define HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX TEXT("collision_geo_ucx") +#define HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX TEXT("rendered_collision_geo_ucx") + +#define HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX TEXT("collision_geo_simple") +#define HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX TEXT("rendered_collision_geo_simple") + +// Default material name. +#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) + +// Various variable names used to store meta information in generated packages. +#define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE TEXT( "HoudiniGeneratedTextureType" ) +#define HAPI_UNREAL_PACKAGE_META_NODE_PATH TEXT( "HoudiniNodePath" ) +#define HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER TEXT( "HoudiniPackageBakeCounter" ) +#define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) + +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL TEXT( "N" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE TEXT( "C_A" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR TEXT( "S" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS TEXT( "R" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC TEXT( "M" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE TEXT( "E" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK TEXT( "O" ) + +// Texture planes. +#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA "C A" +#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR "C" +#define HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA "A" +#define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" + +// Materials Diffuse. +#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 "ogl_tex1" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 "basecolor_texture" + +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0 "ogl_diff" +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1 "basecolor" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" + +// Materials Normal. +#define HAPI_UNREAL_PARAM_MAP_NORMAL_0 "ogl_normalmap" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_1 "normalTexture" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" + +// Materials Specular. +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_0 "ogl_specmap" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_1 "reflect_texture" + +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_0 "ogl_spec" +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_1 "reflect" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" + +// Materials Roughness. +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0 "ogl_roughmap" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1 "rough_texture" + +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0 "ogl_rough" +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1 "rough" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" + +// Materials Metallic. +#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" +#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" + +// Materials Emissive. +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" + +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0 "ogl_emit" +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1 "emitcolor" + +// Materials Opacity. +#define HAPI_UNREAL_PARAM_ALPHA_0 "ogl_alpha" +#define HAPI_UNREAL_PARAM_ALPHA_1 "opac" + +#define HAPI_UNREAL_PARAM_MAP_OPACITY_0 "ogl_opacitymap" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_1 "opaccolor_texture" + +// Number of GUID characters to keep for packages +#define PACKAGE_GUID_LENGTH 8 +#define PACKAGE_GUID_COMPONENT_LENGTH 12 + +/** Ramp related defines. **/ +#define HAPI_UNREAL_RAMP_FLOAT_AXIS_X "position" +#define HAPI_UNREAL_RAMP_FLOAT_AXIS_Y "value" +#define HAPI_UNREAL_RAMP_COLOR_AXIS_X "position" +#define HAPI_UNREAL_RAMP_COLOR_AXIS_Y "color" + +/** Handle types. **/ +#define HAPI_UNREAL_HANDLE_TRANSFORM "xform" +#define HAPI_UNREAL_HANDLE_BOUNDER "bound" + +#define HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME "height" + +#define HAPI_UNREAL_NOTIFICATION_FADEOUT 2.0f +#define HAPI_UNREAL_NOTIFICATION_EXPIRE 2.0f + diff --git a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp b/Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp deleted file mode 100644 index 8a63839cc..000000000 --- a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.h b/Source/HoudiniEngine/Private/HoudiniEngineProcessor.h deleted file mode 100644 index bdac5a8a6..000000000 --- a/Source/HoudiniEngine/Private/HoudiniEngineProcessor.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp index 6eeeeddcf..57a7a760f 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp @@ -1,619 +1,619 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineScheduler.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngineString.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngine.h" - -const uint32 -FHoudiniEngineScheduler::InitialTaskSize = 256u; - -const float -FHoudiniEngineScheduler::UpdateFrequency = 0.1f; - -FHoudiniEngineScheduler::FHoudiniEngineScheduler() - : Tasks(nullptr) - , PositionWrite(0u) - , PositionRead(0u) - , bStopping(false) -{ - // Make sure size is power of two. - TaskCount = FPlatformMath::RoundUpToPowerOfTwo(FHoudiniEngineScheduler::InitialTaskSize); - - if (TaskCount) - { - // Allocate buffer to store all tasks. - Tasks = static_cast(FMemory::Malloc(TaskCount * sizeof(FHoudiniEngineTask))); - - if (Tasks) - { - // Zero memory. - FMemory::Memset(Tasks, 0x0, TaskCount * sizeof(FHoudiniEngineTask)); - } - } -} - -FHoudiniEngineScheduler::~FHoudiniEngineScheduler() -{ - if (TaskCount) - { - FMemory::Free(Tasks); - Tasks = nullptr; - } -} - -void -FHoudiniEngineScheduler::TaskDescription( - FHoudiniEngineTaskInfo & TaskInfo, - const FString & ActorName, - const FString & StatusString) -{ - FFormatNamedArguments Args; - - if (!ActorName.IsEmpty()) - { - Args.Add(TEXT("AssetName"), FText::FromString(ActorName)); - Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); - TaskInfo.StatusText = - FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetName}) : ({AssetStatus})"), Args); - } - else - { - Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); - TaskInfo.StatusText = - FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetStatus})"), Args); - } -} - -void -FHoudiniEngineScheduler::TaskInstantiateAsset(const FHoudiniEngineTask & Task) -{ - FString AssetN; - FHoudiniEngineString(Task.AssetHapiName).ToFString(AssetN); - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Instantiation Started for %s: Asset=%s, HoudiniAsset = 0x%x"), - *Task.ActorName, *AssetN, Task.Asset.Get()); - - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskInstantiateAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - if (!Task.Asset.IsValid()) - { - // Asset is no longer valid, return. - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset is no longer valid.")); - - return; - } - - if (Task.AssetHapiName < 0) - { - // Asset is no longer valid, return. - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset name is invalid.")); - - return; - } - - HAPI_Result Result = HAPI_RESULT_SUCCESS; - int32 AssetCount = 0; - HAPI_NodeId AssetId = -1; - std::string AssetNameString; - double LastUpdateTime; - - FHoudiniEngineString HoudiniEngineString(Task.AssetHapiName); - if (!HoudiniEngineString.ToStdString(AssetNameString)) - { - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Error retrieving asset name.")); - - return; - } - - // Translate asset name into Unreal string. - FString AssetName = ANSI_TO_TCHAR(AssetNameString.c_str()); - - // Initialize last update time. - LastUpdateTime = FPlatformTime::Seconds(); - - // We instantiate without cooking. - Result = FHoudiniApi::CreateNode( - FHoudiniEngine::Get().GetSession(), -1, &AssetNameString[0], nullptr, false, &AssetId); - if (Result != HAPI_RESULT_SUCCESS) - { - AddResponseMessageTaskInfo( - Result, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Error instantiating asset.")); - - return; - } - - // Add processing notification. - FHoudiniEngineTaskInfo TaskInfo( - HAPI_RESULT_SUCCESS, -1, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Working); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - TaskDescription(TaskInfo, Task.ActorName, TEXT("Started Instantiation")); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); - - // We need to spin until instantiation is finished. - while (true) - { - int Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // Cooking has been successful. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Success, AssetId, Task, - TEXT("Finished Instantiation.")); - - break; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // There was an error while instantiating. - FString CookResultString = FHoudiniEngineUtils::GetCookResult(); - int32 CookResult = static_cast(HAPI_RESULT_SUCCESS); - FHoudiniApi::GetStatus(FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_RESULT, &CookResult); - - EHoudiniEngineTaskState TaskStateResult = EHoudiniEngineTaskState::FinishedWithFatalError; - if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - TaskStateResult = EHoudiniEngineTaskState::FinishedWithError; - - AddResponseMessageTaskInfo( - static_cast(CookResult), - EHoudiniEngineTaskType::AssetInstantiation, - TaskStateResult, - AssetId, Task, - FString::Printf(TEXT("Finished Instantiation with Errors: %s"), *CookResultString)); - - break; - } - - static const double NotificationUpdateFrequency = 0.5; - if ((FPlatformTime::Seconds() - LastUpdateTime) >= NotificationUpdateFrequency) - { - // Reset update time. - LastUpdateTime = FPlatformTime::Seconds(); - const FString& CookStateMessage = FHoudiniEngineUtils::GetCookState(); - - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Working, - AssetId, Task, CookStateMessage); - } - - // We want to yield. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } -} - -void -FHoudiniEngineScheduler::TaskCookAsset(const FHoudiniEngineTask & Task) -{ - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskCookAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - // Retrieve asset id. - HAPI_NodeId AssetId = Task.AssetId; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Cooking Started for %s., AssetId = %d"), - *Task.ActorName, AssetId); - - if (AssetId == -1) - { - // We have an invalid asset id. - HOUDINI_LOG_ERROR(TEXT("TaskCookAsset failed for %s: Invalid Asset Id."), *Task.ActorName); - - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset has invalid id.")); - - return; - } - - // Default CookOptions - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - Result = FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), AssetId, &CookOptions); - if (Result != HAPI_RESULT_SUCCESS) - { - AddResponseMessageTaskInfo( - Result, EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - AssetId, Task, TEXT("Error cooking asset.")); - - return; - } - - // Add processing notification. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Working, - AssetId, Task, TEXT("Started Cooking")); - - // Initialize last update time. - double LastUpdateTime = FPlatformTime::Seconds(); - - // We need to spin until cooking is finished. - while (true) - { - int32 Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET( &Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // Cooking has been successful. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Success, - AssetId, Task, TEXT("Finished Cooking")); - - break; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - EHoudiniEngineTaskState TaskResult = EHoudiniEngineTaskState::FinishedWithFatalError; - if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - TaskResult = EHoudiniEngineTaskState::FinishedWithError; - - // There was an error while instantiating. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - TaskResult, - AssetId, Task, - TEXT("Finished Cooking with Errors")); - - break; - } - - static const double NotificationUpdateFrequency = 0.5; - if (FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency) - { - // Reset update time. - LastUpdateTime = FPlatformTime::Seconds(); - - // Retrieve status string. - const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); - - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Working, - AssetId, Task, CookStateMessage); - } - - // We want to yield. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } -} - -void -FHoudiniEngineScheduler::TaskDeleteAsset(const FHoudiniEngineTask & Task) -{ - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Destruction Started for %s. ") - TEXT("AssetId = %d"), - *Task.ActorName, Task.AssetId); - - if (FHoudiniEngineUtils::IsHoudiniNodeValid(Task.AssetId)) - FHoudiniEngineUtils::DestroyHoudiniAsset(Task.AssetId); - - // We do not insert task info as this is a fire and forget operation. - // At this point component most likely does not exist. -} - -void -FHoudiniEngineScheduler::AddResponseTaskInfo( - HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, const FHoudiniEngineTask & Task) -{ - FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); - FString StatusString = FHoudiniEngineUtils::GetErrorDescription(); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - - TaskDescription(TaskInfo, Task.ActorName, StatusString); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); -} - -void -FHoudiniEngineScheduler::AddResponseMessageTaskInfo( - HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, const FString & ErrorMessage) -{ - FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - - TaskDescription(TaskInfo, Task.ActorName, ErrorMessage); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); -} - -void -FHoudiniEngineScheduler::ProcessQueuedTasks() -{ - while (!bStopping) - { - while (true) - { - FHoudiniEngineTask Task; - - { - FScopeLock ScopeLock(&CriticalSection); - - // We have no tasks left. - if (PositionWrite == PositionRead) - break; - - // Retrieve task. - Task = Tasks[PositionRead]; - PositionRead++; - - // Wrap around if required. - PositionRead &= (TaskCount - 1); - } - - bool bTaskProcessed = true; - - switch (Task.TaskType) - { - case EHoudiniEngineTaskType::AssetInstantiation: - { - TaskInstantiateAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetCooking: - { - TaskCookAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetDeletion: - { - TaskDeleteAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetProcess: - { - TaskProccessAsset(Task); - break; - } - - default: - { - bTaskProcessed = false; - break; - } - } - - if (!bTaskProcessed) - break; - } - - if (FPlatformProcess::SupportsMultithreading()) - { - // We want to yield for a bit. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } - else - { - // If we are running in single threaded mode, return so we don't block everything else. - return; - } - } -} - -void -FHoudiniEngineScheduler::TaskProccessAsset(const FHoudiniEngineTask & Task) -{ - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskProccessAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - // Retrieve asset id. - HAPI_NodeId AssetId = Task.AssetId; - if (AssetId == -1) - { - // We have an invalid asset id. - HOUDINI_LOG_ERROR(TEXT("TaskProcessAsset failed for %s: Invalid Asset Id."), *Task.ActorName); - - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset has invalid id.")); - - return; - } - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Processing started for %s., AssetId = %d"), - *Task.ActorName, AssetId); - - // Add processing notification. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::Working, - AssetId, Task, TEXT("Started Cooking")); - - // - - - // TODO: Process results! -} - -void -FHoudiniEngineScheduler::AddTask(const FHoudiniEngineTask & Task) -{ - FScopeLock ScopeLock(&CriticalSection); - - // Check if we need to grow our circular buffer. - if (PositionWrite + 1 == PositionRead) - { - // Calculate next size (next power of two). - uint32 NextTaskCount = FPlatformMath::RoundUpToPowerOfTwo(TaskCount + 1); - - // Allocate new buffer. - FHoudiniEngineTask * Buffer = static_cast( - FMemory::Malloc(NextTaskCount * sizeof(FHoudiniEngineTask))); - - if (!Buffer) - return; - - // Zero memory. - FMemory::Memset(Buffer, 0x0, NextTaskCount * sizeof(FHoudiniEngineTask)); - - // Copy elements from old buffer to new one. - if (PositionRead < PositionWrite) - { - FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (PositionWrite - PositionRead)); - - // Update index positions. - PositionRead = 0; - PositionWrite = PositionWrite - PositionRead; - } - else - { - FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (TaskCount - PositionRead)); - FMemory::Memcpy(Buffer + TaskCount - PositionRead, Tasks, sizeof(FHoudiniEngineTask) * PositionWrite); - - // Update index positions. - PositionRead = 0; - PositionWrite = TaskCount - PositionRead + PositionWrite; - } - - // Deallocate old buffer. - FMemory::Free(Tasks); - - // Bookkeeping. - Tasks = Buffer; - TaskCount = NextTaskCount; - } - - // Store task. - Tasks[PositionWrite] = Task; - PositionWrite++; - - // Wrap around if required. - PositionWrite &= (TaskCount - 1); -} - -uint32 -FHoudiniEngineScheduler::Run() -{ - ProcessQueuedTasks(); - return 0; -} - -void -FHoudiniEngineScheduler::Stop() -{ - bStopping = true; -} - -void -FHoudiniEngineScheduler::Tick() -{ - ProcessQueuedTasks(); -} - -FSingleThreadRunnable * -FHoudiniEngineScheduler::GetSingleThreadInterface() -{ - return this; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineScheduler.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" + +const uint32 +FHoudiniEngineScheduler::InitialTaskSize = 256u; + +const float +FHoudiniEngineScheduler::UpdateFrequency = 0.1f; + +FHoudiniEngineScheduler::FHoudiniEngineScheduler() + : Tasks(nullptr) + , PositionWrite(0u) + , PositionRead(0u) + , bStopping(false) +{ + // Make sure size is power of two. + TaskCount = FPlatformMath::RoundUpToPowerOfTwo(FHoudiniEngineScheduler::InitialTaskSize); + + if (TaskCount) + { + // Allocate buffer to store all tasks. + Tasks = static_cast(FMemory::Malloc(TaskCount * sizeof(FHoudiniEngineTask))); + + if (Tasks) + { + // Zero memory. + FMemory::Memset(Tasks, 0x0, TaskCount * sizeof(FHoudiniEngineTask)); + } + } +} + +FHoudiniEngineScheduler::~FHoudiniEngineScheduler() +{ + if (TaskCount) + { + FMemory::Free(Tasks); + Tasks = nullptr; + } +} + +void +FHoudiniEngineScheduler::TaskDescription( + FHoudiniEngineTaskInfo & TaskInfo, + const FString & ActorName, + const FString & StatusString) +{ + FFormatNamedArguments Args; + + if (!ActorName.IsEmpty()) + { + Args.Add(TEXT("AssetName"), FText::FromString(ActorName)); + Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); + TaskInfo.StatusText = + FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetName}) : ({AssetStatus})"), Args); + } + else + { + Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); + TaskInfo.StatusText = + FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetStatus})"), Args); + } +} + +void +FHoudiniEngineScheduler::TaskInstantiateAsset(const FHoudiniEngineTask & Task) +{ + FString AssetN; + FHoudiniEngineString(Task.AssetHapiName).ToFString(AssetN); + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Instantiation Started for %s: Asset=%s, HoudiniAsset = 0x%x"), + *Task.ActorName, *AssetN, Task.Asset.Get()); + + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskInstantiateAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + if (!Task.Asset.IsValid()) + { + // Asset is no longer valid, return. + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset is no longer valid.")); + + return; + } + + if (Task.AssetHapiName < 0) + { + // Asset is no longer valid, return. + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset name is invalid.")); + + return; + } + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + int32 AssetCount = 0; + HAPI_NodeId AssetId = -1; + std::string AssetNameString; + double LastUpdateTime; + + FHoudiniEngineString HoudiniEngineString(Task.AssetHapiName); + if (!HoudiniEngineString.ToStdString(AssetNameString)) + { + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Error retrieving asset name.")); + + return; + } + + // Translate asset name into Unreal string. + FString AssetName = ANSI_TO_TCHAR(AssetNameString.c_str()); + + // Initialize last update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // We instantiate without cooking. + Result = FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), -1, &AssetNameString[0], nullptr, false, &AssetId); + if (Result != HAPI_RESULT_SUCCESS) + { + AddResponseMessageTaskInfo( + Result, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Error instantiating asset.")); + + return; + } + + // Add processing notification. + FHoudiniEngineTaskInfo TaskInfo( + HAPI_RESULT_SUCCESS, -1, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Working); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + TaskDescription(TaskInfo, Task.ActorName, TEXT("Started Instantiation")); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); + + // We need to spin until instantiation is finished. + while (true) + { + int Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // Cooking has been successful. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Success, AssetId, Task, + TEXT("Finished Instantiation.")); + + break; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // There was an error while instantiating. + FString CookResultString = FHoudiniEngineUtils::GetCookResult(); + int32 CookResult = static_cast(HAPI_RESULT_SUCCESS); + FHoudiniApi::GetStatus(FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_RESULT, &CookResult); + + EHoudiniEngineTaskState TaskStateResult = EHoudiniEngineTaskState::FinishedWithFatalError; + if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + TaskStateResult = EHoudiniEngineTaskState::FinishedWithError; + + AddResponseMessageTaskInfo( + static_cast(CookResult), + EHoudiniEngineTaskType::AssetInstantiation, + TaskStateResult, + AssetId, Task, + FString::Printf(TEXT("Finished Instantiation with Errors: %s"), *CookResultString)); + + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if ((FPlatformTime::Seconds() - LastUpdateTime) >= NotificationUpdateFrequency) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + const FString& CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Working, + AssetId, Task, CookStateMessage); + } + + // We want to yield. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } +} + +void +FHoudiniEngineScheduler::TaskCookAsset(const FHoudiniEngineTask & Task) +{ + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskCookAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + // Retrieve asset id. + HAPI_NodeId AssetId = Task.AssetId; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Cooking Started for %s., AssetId = %d"), + *Task.ActorName, AssetId); + + if (AssetId == -1) + { + // We have an invalid asset id. + HOUDINI_LOG_ERROR(TEXT("TaskCookAsset failed for %s: Invalid Asset Id."), *Task.ActorName); + + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset has invalid id.")); + + return; + } + + // Default CookOptions + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + Result = FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), AssetId, &CookOptions); + if (Result != HAPI_RESULT_SUCCESS) + { + AddResponseMessageTaskInfo( + Result, EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + AssetId, Task, TEXT("Error cooking asset.")); + + return; + } + + // Add processing notification. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Working, + AssetId, Task, TEXT("Started Cooking")); + + // Initialize last update time. + double LastUpdateTime = FPlatformTime::Seconds(); + + // We need to spin until cooking is finished. + while (true) + { + int32 Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET( &Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // Cooking has been successful. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Success, + AssetId, Task, TEXT("Finished Cooking")); + + break; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + EHoudiniEngineTaskState TaskResult = EHoudiniEngineTaskState::FinishedWithFatalError; + if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + TaskResult = EHoudiniEngineTaskState::FinishedWithError; + + // There was an error while instantiating. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + TaskResult, + AssetId, Task, + TEXT("Finished Cooking with Errors")); + + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if (FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // Retrieve status string. + const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Working, + AssetId, Task, CookStateMessage); + } + + // We want to yield. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } +} + +void +FHoudiniEngineScheduler::TaskDeleteAsset(const FHoudiniEngineTask & Task) +{ + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Destruction Started for %s. ") + TEXT("AssetId = %d"), + *Task.ActorName, Task.AssetId); + + if (FHoudiniEngineUtils::IsHoudiniNodeValid(Task.AssetId)) + FHoudiniEngineUtils::DestroyHoudiniAsset(Task.AssetId); + + // We do not insert task info as this is a fire and forget operation. + // At this point component most likely does not exist. +} + +void +FHoudiniEngineScheduler::AddResponseTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task) +{ + FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); + FString StatusString = FHoudiniEngineUtils::GetErrorDescription(); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + + TaskDescription(TaskInfo, Task.ActorName, StatusString); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); +} + +void +FHoudiniEngineScheduler::AddResponseMessageTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, const FString & ErrorMessage) +{ + FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + + TaskDescription(TaskInfo, Task.ActorName, ErrorMessage); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); +} + +void +FHoudiniEngineScheduler::ProcessQueuedTasks() +{ + while (!bStopping) + { + while (true) + { + FHoudiniEngineTask Task; + + { + FScopeLock ScopeLock(&CriticalSection); + + // We have no tasks left. + if (PositionWrite == PositionRead) + break; + + // Retrieve task. + Task = Tasks[PositionRead]; + PositionRead++; + + // Wrap around if required. + PositionRead &= (TaskCount - 1); + } + + bool bTaskProcessed = true; + + switch (Task.TaskType) + { + case EHoudiniEngineTaskType::AssetInstantiation: + { + TaskInstantiateAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetCooking: + { + TaskCookAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetDeletion: + { + TaskDeleteAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetProcess: + { + TaskProccessAsset(Task); + break; + } + + default: + { + bTaskProcessed = false; + break; + } + } + + if (!bTaskProcessed) + break; + } + + if (FPlatformProcess::SupportsMultithreading()) + { + // We want to yield for a bit. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } + else + { + // If we are running in single threaded mode, return so we don't block everything else. + return; + } + } +} + +void +FHoudiniEngineScheduler::TaskProccessAsset(const FHoudiniEngineTask & Task) +{ + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskProccessAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + // Retrieve asset id. + HAPI_NodeId AssetId = Task.AssetId; + if (AssetId == -1) + { + // We have an invalid asset id. + HOUDINI_LOG_ERROR(TEXT("TaskProcessAsset failed for %s: Invalid Asset Id."), *Task.ActorName); + + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset has invalid id.")); + + return; + } + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Processing started for %s., AssetId = %d"), + *Task.ActorName, AssetId); + + // Add processing notification. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::Working, + AssetId, Task, TEXT("Started Cooking")); + + // + + + // TODO: Process results! +} + +void +FHoudiniEngineScheduler::AddTask(const FHoudiniEngineTask & Task) +{ + FScopeLock ScopeLock(&CriticalSection); + + // Check if we need to grow our circular buffer. + if (PositionWrite + 1 == PositionRead) + { + // Calculate next size (next power of two). + uint32 NextTaskCount = FPlatformMath::RoundUpToPowerOfTwo(TaskCount + 1); + + // Allocate new buffer. + FHoudiniEngineTask * Buffer = static_cast( + FMemory::Malloc(NextTaskCount * sizeof(FHoudiniEngineTask))); + + if (!Buffer) + return; + + // Zero memory. + FMemory::Memset(Buffer, 0x0, NextTaskCount * sizeof(FHoudiniEngineTask)); + + // Copy elements from old buffer to new one. + if (PositionRead < PositionWrite) + { + FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (PositionWrite - PositionRead)); + + // Update index positions. + PositionRead = 0; + PositionWrite = PositionWrite - PositionRead; + } + else + { + FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (TaskCount - PositionRead)); + FMemory::Memcpy(Buffer + TaskCount - PositionRead, Tasks, sizeof(FHoudiniEngineTask) * PositionWrite); + + // Update index positions. + PositionRead = 0; + PositionWrite = TaskCount - PositionRead + PositionWrite; + } + + // Deallocate old buffer. + FMemory::Free(Tasks); + + // Bookkeeping. + Tasks = Buffer; + TaskCount = NextTaskCount; + } + + // Store task. + Tasks[PositionWrite] = Task; + PositionWrite++; + + // Wrap around if required. + PositionWrite &= (TaskCount - 1); +} + +uint32 +FHoudiniEngineScheduler::Run() +{ + ProcessQueuedTasks(); + return 0; +} + +void +FHoudiniEngineScheduler::Stop() +{ + bStopping = true; +} + +void +FHoudiniEngineScheduler::Tick() +{ + ProcessQueuedTasks(); +} + +FSingleThreadRunnable * +FHoudiniEngineScheduler::GetSingleThreadInterface() +{ + return this; +} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h index db786f178..c29098dce 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h @@ -1,117 +1,117 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineTask.h" -#include "HoudiniEngineTaskInfo.h" -#include "HAL/Runnable.h" -#include "HAL/RunnableThread.h" -#include "Misc/SingleThreadRunnable.h" - -class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable -{ -public: - - FHoudiniEngineScheduler(); - virtual ~FHoudiniEngineScheduler(); - - // FRunnable methods. - virtual uint32 Run() override; - virtual void Stop() override; - FSingleThreadRunnable * GetSingleThreadInterface() override; - - // FSingleThreadRunnable methods. - virtual void Tick() override; - - // Adds a task. - void AddTask(const FHoudiniEngineTask & Task); - - // Adds instantiation response task info. - void AddResponseTaskInfo( - HAPI_Result Result, - EHoudiniEngineTaskType TaskType, - EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, - const FHoudiniEngineTask & Task); - - void AddResponseMessageTaskInfo( - HAPI_Result Result, - EHoudiniEngineTaskType TaskType, - EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, - const FHoudiniEngineTask & Task, - const FString & ErrorMessage); - -protected: - - // Process queued tasks. - void ProcessQueuedTasks(); - - // Task : instantiate an asset. - void TaskInstantiateAsset(const FHoudiniEngineTask & Task); - - // Task : cook an asset. - void TaskCookAsset(const FHoudiniEngineTask & Task); - - // Create description of task's state. - void TaskDescription( - FHoudiniEngineTaskInfo & Task, - const FString & ActorName, - const FString & StatusString); - - // Delete an asset. - void TaskDeleteAsset(const FHoudiniEngineTask & Task); - - // Process the result of a sucesfull cook - void TaskProccessAsset(const FHoudiniEngineTask & Task); - -private: - - // Initial number of tasks in our circular queue. - static const uint32 InitialTaskSize; - - // Frequency update (sleep time between each update) - static const float UpdateFrequency; - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // List of scheduled tasks. - FHoudiniEngineTask* Tasks; - - // Head of the circular queue. - uint32 PositionWrite; - - // Tail of the circular queue. - uint32 PositionRead; - - // Size of the circular queue. - uint32 TaskCount; - - // Stopping flag. - bool bStopping; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HAL/Runnable.h" +#include "HAL/RunnableThread.h" +#include "Misc/SingleThreadRunnable.h" + +class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable +{ +public: + + FHoudiniEngineScheduler(); + virtual ~FHoudiniEngineScheduler(); + + // FRunnable methods. + virtual uint32 Run() override; + virtual void Stop() override; + FSingleThreadRunnable * GetSingleThreadInterface() override; + + // FSingleThreadRunnable methods. + virtual void Tick() override; + + // Adds a task. + void AddTask(const FHoudiniEngineTask & Task); + + // Adds instantiation response task info. + void AddResponseTaskInfo( + HAPI_Result Result, + EHoudiniEngineTaskType TaskType, + EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, + const FHoudiniEngineTask & Task); + + void AddResponseMessageTaskInfo( + HAPI_Result Result, + EHoudiniEngineTaskType TaskType, + EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, + const FHoudiniEngineTask & Task, + const FString & ErrorMessage); + +protected: + + // Process queued tasks. + void ProcessQueuedTasks(); + + // Task : instantiate an asset. + void TaskInstantiateAsset(const FHoudiniEngineTask & Task); + + // Task : cook an asset. + void TaskCookAsset(const FHoudiniEngineTask & Task); + + // Create description of task's state. + void TaskDescription( + FHoudiniEngineTaskInfo & Task, + const FString & ActorName, + const FString & StatusString); + + // Delete an asset. + void TaskDeleteAsset(const FHoudiniEngineTask & Task); + + // Process the result of a sucesfull cook + void TaskProccessAsset(const FHoudiniEngineTask & Task); + +private: + + // Initial number of tasks in our circular queue. + static const uint32 InitialTaskSize; + + // Frequency update (sleep time between each update) + static const float UpdateFrequency; + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // List of scheduled tasks. + FHoudiniEngineTask* Tasks; + + // Head of the circular queue. + uint32 PositionWrite; + + // Tail of the circular queue. + uint32 PositionRead; + + // Size of the circular queue. + uint32 TaskCount; + + // Stopping flag. + bool bStopping; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineString.cpp b/Source/HoudiniEngine/Private/HoudiniEngineString.cpp index be55a272d..74d91d273 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineString.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineString.cpp @@ -1,185 +1,215 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineString.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include - -FHoudiniEngineString::FHoudiniEngineString() - : StringId(-1) -{} - -FHoudiniEngineString::FHoudiniEngineString(int32 InStringId) - : StringId(InStringId) -{} - -FHoudiniEngineString::FHoudiniEngineString(const FHoudiniEngineString & Other) - : StringId(Other.StringId) -{} - -FHoudiniEngineString & -FHoudiniEngineString::operator=(const FHoudiniEngineString & Other) -{ - if (this != &Other) - StringId = Other.StringId; - - return *this; -} - -bool -FHoudiniEngineString::operator==(const FHoudiniEngineString & Other) const -{ - return Other.StringId == StringId; -} - -bool -FHoudiniEngineString::operator!=(const FHoudiniEngineString & Other) const -{ - return Other.StringId != StringId; -} - -int32 -FHoudiniEngineString::GetId() const -{ - return StringId; -} - -bool -FHoudiniEngineString::HasValidId() const -{ - return StringId > 0; -} - -bool -FHoudiniEngineString::ToStdString(std::string & String) const -{ - String = ""; - - // Null string ID / zero should be considered invalid - // (or we'd get the "null string, should never see this!" text) - if (StringId <= 0) - { - return false; - } - - int32 NameLength = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBufLength( - FHoudiniEngine::Get().GetSession(), StringId, &NameLength)) - { - return false; - } - - if (NameLength <= 0) - return false; - - std::vector< char > NameBuffer(NameLength, '\0'); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( - FHoudiniEngine::Get().GetSession(), - StringId, &NameBuffer[0], NameLength ) ) - { - return false; - } - - String = std::string(NameBuffer.begin(), NameBuffer.end()); - - return true; -} - -bool -FHoudiniEngineString::ToFName(FName & Name) const -{ - Name = NAME_None; - FString NameString = TEXT(""); - if (ToFString(NameString)) - { - Name = FName(*NameString); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToFString(FString & String) const -{ - String = TEXT(""); - std::string NamePlain = ""; - - if (ToStdString(NamePlain)) - { - String = UTF8_TO_TCHAR(NamePlain.c_str()); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToFText(FText & Text) const -{ - Text = FText::GetEmpty(); - FString NameString = TEXT(""); - - if (ToFString(NameString)) - { - Text = FText::FromString(NameString); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToStdString(const int32& InStringId, std::string& OutStdString) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToStdString(OutStdString); -} - -bool -FHoudiniEngineString::ToFName(const int32& InStringId, FName& OutName) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFName(OutName); -} - -bool -FHoudiniEngineString::ToFString(const int32& InStringId, FString& OutString) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFString(OutString); -} - -bool -FHoudiniEngineString::ToFText(const int32& InStringId, FText& OutText) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFText(OutText); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineString.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include + +FHoudiniEngineString::FHoudiniEngineString() + : StringId(-1) +{} + +FHoudiniEngineString::FHoudiniEngineString(int32 InStringId) + : StringId(InStringId) +{} + +FHoudiniEngineString::FHoudiniEngineString(const FHoudiniEngineString& Other) + : StringId(Other.StringId) +{} + +FHoudiniEngineString & +FHoudiniEngineString::operator=(const FHoudiniEngineString& Other) +{ + if (this != &Other) + StringId = Other.StringId; + + return *this; +} + +bool +FHoudiniEngineString::operator==(const FHoudiniEngineString& Other) const +{ + return Other.StringId == StringId; +} + +bool +FHoudiniEngineString::operator!=(const FHoudiniEngineString& Other) const +{ + return Other.StringId != StringId; +} + +int32 +FHoudiniEngineString::GetId() const +{ + return StringId; +} + +bool +FHoudiniEngineString::HasValidId() const +{ + return StringId > 0; +} + +bool +FHoudiniEngineString::ToStdString(std::string& String) const +{ + String = ""; + + // Null string ID / zero should be considered invalid + // (or we'd get the "null string, should never see this!" text) + if (StringId <= 0) + { + return false; + } + + int32 NameLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBufLength( + FHoudiniEngine::Get().GetSession(), StringId, &NameLength)) + { + return false; + } + + if (NameLength <= 0) + return false; + + std::vector< char > NameBuffer(NameLength, '\0'); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( + FHoudiniEngine::Get().GetSession(), + StringId, &NameBuffer[0], NameLength ) ) + { + return false; + } + + String = std::string(NameBuffer.begin(), NameBuffer.end()); + + return true; +} + +bool +FHoudiniEngineString::ToFName(FName& Name) const +{ + Name = NAME_None; + FString NameString = TEXT(""); + if (ToFString(NameString)) + { + Name = FName(*NameString); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToFString(FString& String) const +{ + String = TEXT(""); + std::string NamePlain = ""; + + if (ToStdString(NamePlain)) + { + String = UTF8_TO_TCHAR(NamePlain.c_str()); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToFText(FText& Text) const +{ + Text = FText::GetEmpty(); + FString NameString = TEXT(""); + + if (ToFString(NameString)) + { + Text = FText::FromString(NameString); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToStdString(const int32& InStringId, std::string& OutStdString) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToStdString(OutStdString); +} + +bool +FHoudiniEngineString::ToFName(const int32& InStringId, FName& OutName) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFName(OutName); +} + +bool +FHoudiniEngineString::ToFString(const int32& InStringId, FString& OutString) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFString(OutString); +} + +bool +FHoudiniEngineString::ToFText(const int32& InStringId, FText& OutText) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFText(OutText); +} + +bool +FHoudiniEngineString::SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray) +{ + bool bReturn = true; + OutStringArray.SetNumZeroed(InStringIdArray.Num()); + + // Avoid calling HAPI to resolve the same strings again and again + TMap ResolvedStrings; + for (int32 IdxSH = 0; IdxSH < InStringIdArray.Num(); IdxSH++) + { + const int32* ResolvedString = ResolvedStrings.Find(InStringIdArray[IdxSH]); + if (ResolvedString) + { + // Already resolved earlier, copy the string instead of calling HAPI. + OutStringArray[IdxSH] = OutStringArray[*ResolvedString]; + } + else + { + FString CurrentString = FString(); + if(!FHoudiniEngineString::ToFString(InStringIdArray[IdxSH], CurrentString)) + bReturn = false; + + OutStringArray[IdxSH] = CurrentString; + ResolvedStrings.Add(InStringIdArray[IdxSH], IdxSH); + } + } + + return bReturn; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineString.h b/Source/HoudiniEngine/Private/HoudiniEngineString.h index 47e9fba50..521248393 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineString.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineString.h @@ -1,70 +1,74 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -class FText; -class FString; -class FName; - -#include - -class HOUDINIENGINE_API FHoudiniEngineString -{ - public: - - FHoudiniEngineString(); - FHoudiniEngineString(int32 InStringId); - FHoudiniEngineString(const FHoudiniEngineString & Other); - - FHoudiniEngineString & operator=(const FHoudiniEngineString & Other); - - bool operator==(const FHoudiniEngineString & Other) const; - bool operator!=(const FHoudiniEngineString & Other) const; - - // Conversion functions - bool ToStdString(std::string & String) const; - bool ToFName(FName & Name) const; - bool ToFString(FString & String) const; - bool ToFText(FText & Text) const; - - // Static converters - static bool ToStdString(const int32& InStringId, std::string & String); - static bool ToFName(const int32& InStringId, FName & Name); - static bool ToFString(const int32& InStringId, FString & String); - static bool ToFText(const int32& InStringId, FText & Text); - - // Return id of this string. - int32 GetId() const; - - // Return true if this string has a valid id. - bool HasValidId() const; - - protected: - - // Id of the underlying Houdini Engine string. - int32 StringId; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include +#include "HoudiniApi.h" + +class FText; +class FString; +class FName; + +class HOUDINIENGINE_API FHoudiniEngineString +{ + public: + + FHoudiniEngineString(); + FHoudiniEngineString(int32 InStringId); + FHoudiniEngineString(const FHoudiniEngineString & Other); + + FHoudiniEngineString & operator=(const FHoudiniEngineString & Other); + + bool operator==(const FHoudiniEngineString & Other) const; + bool operator!=(const FHoudiniEngineString & Other) const; + + // Conversion functions + bool ToStdString(std::string & String) const; + bool ToFName(FName & Name) const; + bool ToFString(FString & String) const; + bool ToFText(FText & Text) const; + + // Static converters + static bool ToStdString(const int32& InStringId, std::string & String); + static bool ToFName(const int32& InStringId, FName & Name); + static bool ToFString(const int32& InStringId, FString & String); + static bool ToFText(const int32& InStringId, FText & Text); + + // Array converter, uses a map to avoid redudant calls to HAPI + static bool SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray); + + // Return id of this string. + int32 GetId() const; + + // Return true if this string has a valid id. + bool HasValidId() const; + + protected: + + // Id of the underlying Houdini Engine string. + int32 StringId; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp index ed8b79122..f9c98f390 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp @@ -1,48 +1,48 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineTask.h" - -#include "HoudiniApi.h" - -FHoudiniEngineTask::FHoudiniEngineTask() - : TaskType(EHoudiniEngineTaskType::None) - , ActorName(TEXT("")) - , AssetId(-1) - , AssetLibraryId(-1) - , AssetHapiName(-1) -{ - HapiGUID.Invalidate(); -} - -FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID) - : HapiGUID(InHapiGUID) - , TaskType(InTaskType) - , ActorName(TEXT("")) - , AssetId(-1) - , AssetLibraryId(-1) - , AssetHapiName(-1) -{} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineTask.h" + +#include "HoudiniApi.h" + +FHoudiniEngineTask::FHoudiniEngineTask() + : TaskType(EHoudiniEngineTaskType::None) + , ActorName(TEXT("")) + , AssetId(-1) + , AssetLibraryId(-1) + , AssetHapiName(-1) +{ + HapiGUID.Invalidate(); +} + +FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID) + : HapiGUID(InHapiGUID) + , TaskType(InTaskType) + , ActorName(TEXT("")) + , AssetId(-1) + , AssetLibraryId(-1) + , AssetHapiName(-1) +{} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.h b/Source/HoudiniEngine/Private/HoudiniEngineTask.h index ad5fa5166..2d620a3c3 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.h @@ -1,100 +1,100 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniApi.h" -#include "CoreMinimal.h" -#include "Misc/Guid.h" -#include "UObject/WeakObjectPtr.h" - -/* -namespace EHoudiniEngineTaskType -{ - enum Type - { - None, - - // This type corresponds to Houdini asset instantiation (without cooking). - AssetInstantiation, - - // This type corresponds to Houdini asset cooking request. - AssetCooking, - - // This type is used for asynchronous asset deletion. - AssetDeletion - }; -} -*/ - -UENUM() -enum class EHoudiniEngineTaskType : uint8 -{ - None, - - // This type corresponds to Houdini asset instantiation (without cooking). - AssetInstantiation, - - // This type corresponds to Houdini asset cooking request. - AssetCooking, - - // This type is used for asynchronous asset deletion. - AssetDeletion, - - // This type is used when processing the results of a sucessful cook - AssetProcess, -}; - -struct HOUDINIENGINE_API FHoudiniEngineTask -{ - // Constructors. - FHoudiniEngineTask(); - FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID); - - // GUID of this request. - FGuid HapiGUID; - - // Type of this task. - EHoudiniEngineTaskType TaskType; - - // Houdini asset for instantiation. - TWeakObjectPtr< class UHoudiniAsset > Asset; - - // Name of the actor requesting this task. - FString ActorName; - - // Asset Id. - HAPI_NodeId AssetId; - - // Library Id. - HAPI_AssetLibraryId AssetLibraryId; - - // HAPI name of the asset. - int32 AssetHapiName; - - // Is set to true if component has been loaded. - //bool bLoadedComponent; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniApi.h" +#include "CoreMinimal.h" +#include "Misc/Guid.h" +#include "UObject/WeakObjectPtr.h" + +/* +namespace EHoudiniEngineTaskType +{ + enum Type + { + None, + + // This type corresponds to Houdini asset instantiation (without cooking). + AssetInstantiation, + + // This type corresponds to Houdini asset cooking request. + AssetCooking, + + // This type is used for asynchronous asset deletion. + AssetDeletion + }; +} +*/ + +UENUM() +enum class EHoudiniEngineTaskType : uint8 +{ + None, + + // This type corresponds to Houdini asset instantiation (without cooking). + AssetInstantiation, + + // This type corresponds to Houdini asset cooking request. + AssetCooking, + + // This type is used for asynchronous asset deletion. + AssetDeletion, + + // This type is used when processing the results of a sucessful cook + AssetProcess, +}; + +struct HOUDINIENGINE_API FHoudiniEngineTask +{ + // Constructors. + FHoudiniEngineTask(); + FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID); + + // GUID of this request. + FGuid HapiGUID; + + // Type of this task. + EHoudiniEngineTaskType TaskType; + + // Houdini asset for instantiation. + TWeakObjectPtr< class UHoudiniAsset > Asset; + + // Name of the actor requesting this task. + FString ActorName; + + // Asset Id. + HAPI_NodeId AssetId; + + // Library Id. + HAPI_AssetLibraryId AssetLibraryId; + + // HAPI name of the asset. + int32 AssetHapiName; + + // Is set to true if component has been loaded. + //bool bLoadedComponent; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp index c962a952e..f104eacde 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp @@ -1,47 +1,47 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineTaskInfo.h" - -#include "HAPI/HAPI_Common.h" - -FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo() - : Result(HAPI_RESULT_SUCCESS) - , AssetId(-1) - , TaskType(EHoudiniEngineTaskType::None) - , TaskState(EHoudiniEngineTaskState::None) -{} - -FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo( - HAPI_Result InResult, - HAPI_NodeId InAssetId, - EHoudiniEngineTaskType InTaskType, - EHoudiniEngineTaskState InTaskState) - : Result(InResult) - , AssetId(InAssetId) - , TaskType(InTaskType) - , TaskState(InTaskState) +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineTaskInfo.h" + +#include "HAPI/HAPI_Common.h" + +FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo() + : Result(HAPI_RESULT_SUCCESS) + , AssetId(-1) + , TaskType(EHoudiniEngineTaskType::None) + , TaskState(EHoudiniEngineTaskState::None) +{} + +FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo( + HAPI_Result InResult, + HAPI_NodeId InAssetId, + EHoudiniEngineTaskType InTaskType, + EHoudiniEngineTaskState InTaskState) + : Result(InResult) + , AssetId(InAssetId) + , TaskType(InTaskType) + , TaskState(InTaskState) {} \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h index 8ca452972..c5831d8a6 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h @@ -1,78 +1,78 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineTask.h" - -UENUM() -enum class EHoudiniEngineTaskState : uint8 -{ - None, - - // Indicates the current task is still running - Working, - - // Indicates the task has successfully finished - Success, - - // Indicates the task has finished with non fatal errors - FinishedWithError, - - // Indicates the task has finished with fatal errors and should be terminated - FinishedWithFatalError, - - // Indicates the task has been aborted (unused) - Aborted -}; - -struct HOUDINIENGINE_API FHoudiniEngineTaskInfo -{ - // Constructors. - FHoudiniEngineTaskInfo(); - FHoudiniEngineTaskInfo( - HAPI_Result InResult, HAPI_NodeId InAssetId, - EHoudiniEngineTaskType InTaskType, - EHoudiniEngineTaskState InTaskState); - - // Current HAPI result. - HAPI_Result Result; - - // Current Asset Id. - HAPI_NodeId AssetId; - - // Type of task. - EHoudiniEngineTaskType TaskType; - - // Current status. - EHoudiniEngineTaskState TaskState; - - // String used for status / progress bar. - FText StatusText; - - // Is set to true if corresponding task was issued for loaded component. - //bool bLoadedComponent; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineTask.h" + +UENUM() +enum class EHoudiniEngineTaskState : uint8 +{ + None, + + // Indicates the current task is still running + Working, + + // Indicates the task has successfully finished + Success, + + // Indicates the task has finished with non fatal errors + FinishedWithError, + + // Indicates the task has finished with fatal errors and should be terminated + FinishedWithFatalError, + + // Indicates the task has been aborted (unused) + Aborted +}; + +struct HOUDINIENGINE_API FHoudiniEngineTaskInfo +{ + // Constructors. + FHoudiniEngineTaskInfo(); + FHoudiniEngineTaskInfo( + HAPI_Result InResult, HAPI_NodeId InAssetId, + EHoudiniEngineTaskType InTaskType, + EHoudiniEngineTaskState InTaskState); + + // Current HAPI result. + HAPI_Result Result; + + // Current Asset Id. + HAPI_NodeId AssetId; + + // Type of task. + EHoudiniEngineTaskType TaskType; + + // Current status. + EHoudiniEngineTaskState TaskState; + + // String used for status / progress bar. + FText StatusText; + + // Is set to true if corresponding task was issued for loaded component. + //bool bLoadedComponent; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp index 39d9ab726..27f4137aa 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp @@ -1,4878 +1,4871 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineUtils.h" -#include "Misc/StringFormatArg.h" - -#if PLATFORM_WINDOWS - #include "Windows/WindowsHWrapper.h" - - // Of course, Windows defines its own GetGeoInfo, - // So we need to undefine that before including HoudiniApi.h to avoid collision... - #ifdef GetGeoInfo - #undef GetGeoInfo - #endif -#endif - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniEngineString.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInput.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineRuntimeUtils.h" - -#if WITH_EDITOR - #include "SAssetSelectionWidget.h" -#endif - -#include "HAPI/HAPI_Version.h" - -#include "Misc/Paths.h" -#include "Editor/EditorEngine.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" -#include "PropertyEditorModule.h" -#include "Modules/ModuleManager.h" -#include "Engine/StaticMeshSocket.h" -#include "Async/Async.h" -#include "BlueprintEditor.h" -#include "Toolkits/AssetEditorManager.h" -#include "Engine/BlueprintGeneratedClass.h" -#include "UObject/MetaData.h" -#include "RawMesh.h" -#include "Widgets/Notifications/SNotificationList.h" -#include "Framework/Notifications/NotificationManager.h" -#include "Interfaces/IPluginManager.h" -//#include "Kismet/BlueprintEditor.h" -#include "SSCSEditor.h" -#include "Engine/WorldComposition.h" - -#if WITH_EDITOR - #include "Interfaces/IMainFrameModule.h" -#endif - -#include - -#include "AssetRegistryModule.h" -#include "FileHelpers.h" -#include "Factories/WorldFactory.h" -#include "HAL/FileManager.h" - -#if WITH_EDITOR - #include "EditorModeManager.h" - #include "EditorModes.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// HAPI_Result strings -const FString kResultStringSuccess(TEXT("Success")); -const FString kResultStringFailure(TEXT("Generic Failure")); -const FString kResultStringAlreadyInitialized(TEXT("Already Initialized")); -const FString kResultStringNotInitialized(TEXT("Not Initialized")); -const FString kResultStringCannotLoadFile(TEXT("Unable to Load File")); -const FString kResultStringParmSetFailed(TEXT("Failed Setting Parameter")); -const FString kResultStringInvalidArgument(TEXT("Invalid Argument")); -const FString kResultStringCannotLoadGeo(TEXT("Uneable to Load Geometry")); -const FString kResultStringCannotGeneratePreset(TEXT("Uneable to Generate Preset")); -const FString kResultStringCannotLoadPreset(TEXT("Uneable to Load Preset")); -const FString kResultStringAssetDefAlrealdyLoaded(TEXT("Asset definition already loaded")); -const FString kResultStringNoLicenseFound(TEXT("No License Found")); -const FString kResultStringDisallowedNCLicenseFound(TEXT("Disallowed Non Commercial License found")); -const FString kResultStringDisallowedNCAssetWithCLicense(TEXT("Disallowed Non Commercial Asset With Commercial License")); -const FString kResultStringDisallowedNCAssetWithLCLicense(TEXT("Disallowed Non Commercial Asset With Limited Commercial License")); -const FString kResultStringDisallowedLCAssetWithCLicense(TEXT("Disallowed Limited Commercial Asset With Commercial License")); -const FString kResultStringDisallowedHengineIndieWith3PartyPlugin(TEXT("Disallowed Houdini Engine Indie With 3rd Party Plugin")); -const FString kResultStringAssetInvalid(TEXT("Invalid Asset")); -const FString kResultStringNodeInvalid(TEXT("Invalid Node")); -const FString kResultStringUserInterrupted(TEXT("User Interrupt")); -const FString kResultStringInvalidSession(TEXT("Invalid Session")); -const FString kResultStringUnknowFailure(TEXT("Unknown Failure")); - -#define DebugTextLine TEXT("===================================") - -const int32 -FHoudiniEngineUtils::PackageGUIDComponentNameLength = 12; - -const int32 -FHoudiniEngineUtils::PackageGUIDItemNameLength = 8; - -const FString -FHoudiniEngineUtils::GetErrorDescription(HAPI_Result Result) -{ - if (Result == HAPI_RESULT_SUCCESS) - { - return kResultStringSuccess; - } - else - { - switch (Result) - { - case HAPI_RESULT_FAILURE: - { - return kResultStringFailure; - } - - case HAPI_RESULT_ALREADY_INITIALIZED: - { - return kResultStringAlreadyInitialized; - } - - case HAPI_RESULT_NOT_INITIALIZED: - { - return kResultStringNotInitialized; - } - - case HAPI_RESULT_CANT_LOADFILE: - { - return kResultStringCannotLoadFile; - } - - case HAPI_RESULT_PARM_SET_FAILED: - { - return kResultStringParmSetFailed; - } - - case HAPI_RESULT_INVALID_ARGUMENT: - { - return kResultStringInvalidArgument; - } - - case HAPI_RESULT_CANT_LOAD_GEO: - { - return kResultStringCannotLoadGeo; - } - - case HAPI_RESULT_CANT_GENERATE_PRESET: - { - return kResultStringCannotGeneratePreset; - } - - case HAPI_RESULT_CANT_LOAD_PRESET: - { - return kResultStringCannotLoadPreset; - } - - case HAPI_RESULT_ASSET_DEF_ALREADY_LOADED: - { - return kResultStringAssetDefAlrealdyLoaded; - } - - case HAPI_RESULT_NO_LICENSE_FOUND: - { - return kResultStringNoLicenseFound; - } - - case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: - { - return kResultStringDisallowedNCLicenseFound; - } - - case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: - { - return kResultStringDisallowedNCAssetWithCLicense; - } - - case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: - { - return kResultStringDisallowedNCAssetWithLCLicense; - } - - case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: - { - return kResultStringDisallowedLCAssetWithCLicense; - } - - case HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN: - { - return kResultStringDisallowedHengineIndieWith3PartyPlugin; - } - - case HAPI_RESULT_ASSET_INVALID: - { - return kResultStringAssetInvalid; - } - - case HAPI_RESULT_NODE_INVALID: - { - return kResultStringNodeInvalid; - } - - case HAPI_RESULT_USER_INTERRUPTED: - { - return kResultStringUserInterrupted; - } - - case HAPI_RESULT_INVALID_SESSION: - { - return kResultStringInvalidSession; - } - - default: - { - return kResultStringUnknowFailure; - } - }; - } -} - -const FString -FHoudiniEngineUtils::GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity) -{ - const HAPI_Session* SessionPtr = FHoudiniEngine::Get().GetSession(); - if (!SessionPtr) - { - // No valid session - return FString(TEXT("No valid Houdini Engine session.")); - } - - int32 StatusBufferLength = 0; - HAPI_Result Result = FHoudiniApi::GetStatusStringBufLength( - SessionPtr, status_type, verbosity, &StatusBufferLength); - - if (Result == HAPI_RESULT_INVALID_SESSION) - { - // Let FHoudiniEngine know that the sesion is now invalid to "Stop" the invalid session - // and clean things up - FHoudiniEngine::Get().OnSessionLost(); - } - - if (StatusBufferLength > 0) - { - TArray< char > StatusStringBuffer; - StatusStringBuffer.SetNumZeroed(StatusBufferLength); - FHoudiniApi::GetStatusString( - SessionPtr, status_type, &StatusStringBuffer[0], StatusBufferLength); - - return FString(UTF8_TO_TCHAR(&StatusStringBuffer[0])); - } - - return FString(TEXT("")); -} - -const FString -FHoudiniEngineUtils::GetCookResult() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_MESSAGES); -} - -const FString -FHoudiniEngineUtils::GetCookState() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); -} - -const FString -FHoudiniEngineUtils::GetErrorDescription() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS); -} - -const FString -FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId) -{ - int32 NodeErrorLength = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeNodeCookResult( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_StatusVerbosity::HAPI_STATUSVERBOSITY_ALL, &NodeErrorLength)) - { - NodeErrorLength = 0; - } - - FString NodeError; - if (NodeErrorLength > 0) - { - TArray NodeErrorBuffer; - NodeErrorBuffer.SetNumZeroed(NodeErrorLength); - FHoudiniApi::GetComposedNodeCookResult( - FHoudiniEngine::Get().GetSession(), &NodeErrorBuffer[0], NodeErrorLength); - - NodeError = FString(UTF8_TO_TCHAR(&NodeErrorBuffer[0])); - } - - return NodeError; -} - -const FString -FHoudiniEngineUtils::GetCookLog(TArray& InHACs) -{ - FString CookLog; - - // Get fetch cook status. - FString CookResult = FHoudiniEngineUtils::GetCookResult(); - if (!CookResult.IsEmpty()) - CookLog += TEXT("Cook Results:\n") + CookResult + TEXT("\n\n"); - - // Add the cook state - FString CookState = FHoudiniEngineUtils::GetCookState(); - if (!CookState.IsEmpty()) - CookLog += TEXT("Cook State:\n") + CookState + TEXT("\n\n"); - - // Error Description - FString Error = FHoudiniEngineUtils::GetErrorDescription(); - if (!Error.IsEmpty()) - CookLog += TEXT("Error Description:\n") + Error + TEXT("\n\n"); - - // Iterates on all the selected HAC and get their node errors - for (auto& HAC : InHACs) - { - if (!HAC || HAC->IsPendingKill()) - continue; - - // Get the node errors, warnings and messages - FString NodeErrors = FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(HAC->GetAssetId()); - if (NodeErrors.IsEmpty()) - continue; - - CookLog += NodeErrors; - } - - if (CookLog.IsEmpty()) - { - // See if a failed HAPI initialization / invalid session is preventing us from getting the cook log - if (!FHoudiniApi::IsHAPIInitialized()) - { - CookLog += TEXT("\n\nThe Houdini Engine API Library (HAPI) has not been initialized properly.\n\n"); - } - else - { - const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) - { - CookLog += TEXT("\n\nThe current Houdini Engine Session is not valid.\n\n"); - } - else if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) - { - CookLog += TEXT("\n\nThe current Houdini Engine Session has not been initialized properly.\n\n"); - } - } - - if (!CookLog.IsEmpty()) - { - CookLog += TEXT("Please try to restart the current Houdini Engine session via File > Restart Houdini Engine Session.\n\n"); - } - else - { - CookLog = TEXT("\n\nThe cook log is empty...\n\n"); - } - } - - return CookLog; -} - -const FString -FHoudiniEngineUtils::GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - FString HelpString = TEXT(""); - if (!HoudiniAssetComponent) - return HelpString; - - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HAPI_NodeId AssetId = HoudiniAssetComponent->GetAssetId(); - if (AssetId < 0) - return HelpString; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), HelpString); - - if (!FHoudiniEngineString::ToFString(AssetInfo.helpTextSH, HelpString)) - return HelpString; - - if (HelpString.IsEmpty()) - HelpString = TEXT("No Asset Help Found"); - - return HelpString; -} - -void -FHoudiniEngineUtils::ConvertUnrealString(const FString & UnrealString, std::string & String) -{ - String = TCHAR_TO_UTF8(*UnrealString); -} - -UWorld* -FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreateMissingPackage, bool& bOutCreatedPackage) -{ - AActor* Result = nullptr; - UWorld* PackageWorld = nullptr; - - bOutCreatedPackage = false; - - // Try to load existing UWorld from the tile package path. - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!Package) - Package = LoadPackage(nullptr, *PackagePath, LOAD_None); - if (Package) - { - // If the package is not valid (pending kill) rename it - if (Package->IsPendingKill()) - { - if (bCreateMissingPackage) - { - Package->Rename( - *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); - } - } - else - { - PackageWorld = UWorld::FindWorldInPackage(Package); - } - } - - if (!IsValid(PackageWorld) && bCreateMissingPackage) - { - // The map for this tile does not exist. Create one - UWorldFactory* Factory = NewObject(); - Factory->WorldType = EWorldType::Inactive; // World that is being loaded but not currently edited by editor. - PackageWorld = CastChecked(Factory->FactoryCreateNew(UWorld::StaticClass(), Package, NAME_None, RF_Public | RF_Standalone, NULL, GWarn)); - - if (IsValid(PackageWorld)) - { - PackageWorld->PostEditChange(); - PackageWorld->MarkPackageDirty(); - - if(FPackageName::IsValidLongPackageName(PackagePath)) - { - const FString PackageFilename = FPackageName::LongPackageNameToFilename(PackagePath); - bool bSaved = FEditorFileUtils::SaveLevel(PackageWorld->PersistentLevel, *PackageFilename); - } - - FAssetRegistryModule::AssetCreated(PackageWorld); - - bOutCreatedPackage = true; - } - } - - return PackageWorld; -} - -bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - UWorld* CurrentWorld, - const FString& PackagePath, - bool bCreateMissingPackage, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bOutPackageCreated, - bool& bPackageInWorld) -{ - UWorld* PackageWorld = FindWorldInPackage(PackagePath, bCreateMissingPackage, bOutPackageCreated); - if (!IsValid(PackageWorld)) - return false; - - if (PackageWorld->PersistentLevel == CurrentWorld->PersistentLevel) - { - // The loaded world and the package world is one and the same. - OutWorld = CurrentWorld; - OutLevel = CurrentWorld->PersistentLevel; - bPackageInWorld = true; - return true; - } - - if (CurrentWorld->GetLevels().Contains(PackageWorld->PersistentLevel)) - { - // The package level is loaded into CurrentWorld. - OutWorld = CurrentWorld; - OutLevel = PackageWorld->PersistentLevel; - bPackageInWorld = true; - return true; - } - - // The package level is not loaded at all. Send back the on-disk assets. - OutWorld = PackageWorld; - OutLevel = PackageWorld->PersistentLevel; - bPackageInWorld = false; - return true; -} - -void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) -{ - FString WorldPath = FPaths::GetPath(InWorld->GetPathName()); - IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); - TArray Packages; - Packages.Add(WorldPath); - AssetRegistry.ScanPathsSynchronous(Packages, true); -} - -AActor* FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) -{ - // AActor* NamedActor = FindObject(Outer, *InName, false); - // Find ANY actor in the world matching the given name. - AActor* NamedActor = FindActorInWorld(InWorld, FName(InName)); - OutFoundActor = NamedActor; - bool bShouldRename = false; - if (NamedActor) - { - if (NamedActor->GetClass()->IsChildOf(InClass) && !NamedActor->IsPendingKill()) - { - return NamedActor; - } - else - { - FString Suffix; - bool bShouldUpdateLabel = false; - if (NamedActor->IsPendingKill()) - Suffix = "_pendingkill"; - else - Suffix = "_0"; // A previous actor that had the same name. - const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName+Suffix); - } - } - return nullptr; -} - -void FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) -{ - LogPackageInfo( LoadPackage(nullptr, *InLongPackageName, 0) ); -} - -void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) -{ - HOUDINI_LOG_MESSAGE(DebugTextLine); - HOUDINI_LOG_MESSAGE(TEXT("= LogPackageInfo")); - if (!IsValid(InPackage)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Invalid package.")); - HOUDINI_LOG_MESSAGE(DebugTextLine); - return; - } - - HOUDINI_LOG_MESSAGE(TEXT(" = Filename: %s"), *(InPackage->FileName.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = Package Id: %d"), InPackage->GetPackageId().ValueForDebugging()); - HOUDINI_LOG_MESSAGE(TEXT(" = File size: %d"), InPackage->FileSize); - HOUDINI_LOG_MESSAGE(TEXT(" = Contains map: %d"), InPackage->ContainsMap()); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Fully Loaded: %d"), InPackage->IsFullyLoaded()); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Dirty: %d"), InPackage->IsDirty()); - - if (InPackage->WorldTileInfo.IsValid()) - { - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Position: %s"), *(InPackage->WorldTileInfo->Position.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Absolute Position: %s"), *(InPackage->WorldTileInfo->AbsolutePosition.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Bounds: %s"), *(InPackage->WorldTileInfo->Bounds.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - HidInTileView: %d"), InPackage->WorldTileInfo->bHideInTileView); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - ZOrder: %d"), InPackage->WorldTileInfo->ZOrder); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Parent tile package: %s"), *(InPackage->WorldTileInfo->ParentTilePackageName)); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo: NULL")); - } - - HOUDINI_LOG_MESSAGE(DebugTextLine); -} - -void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) -{ - UPackage* Package = LoadPackage(nullptr, *InLongPackageName, 0); - UWorld* World = nullptr; - - if (IsValid(Package)) - { - World = UWorld::FindWorldInPackage(Package); - } - - LogWorldInfo(World); -} - -void FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) -{ - - HOUDINI_LOG_MESSAGE(DebugTextLine); - HOUDINI_LOG_MESSAGE(TEXT("= LogWorldInfo")); - if (!IsValid(InWorld)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Invalid world.")); - HOUDINI_LOG_MESSAGE(DebugTextLine); - return; - } - - // UWorld lacks const-correctness on certain accessors - UWorld* NonConstWorld = const_cast(InWorld); - - HOUDINI_LOG_MESSAGE(TEXT(" = Path Name: %s"), *(InWorld->GetPathName())); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Editor World: %d"), InWorld->IsEditorWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Game World: %d"), InWorld->IsGameWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Preview World: %d"), InWorld->IsPreviewWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Actor Count: %d"), NonConstWorld->GetActorCount() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Num Levels: %d"), InWorld->GetNumLevels() ); - - if (IsValid(InWorld->WorldComposition)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Composition - Num Tiles: %d"), InWorld->WorldComposition->GetTilesList().Num() ); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT(" = World Composition NULL") ); - } - - - - HOUDINI_LOG_MESSAGE(DebugTextLine); -} - -FString -FHoudiniEngineUtils::HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType) -{ - switch (InEventType) - { - case HAPI_PDG_EVENT_NULL: - return TEXT("HAPI_PDG_EVENT_NULL"); - - case HAPI_PDG_EVENT_WORKITEM_ADD: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE"); - case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_DEP"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_PARENT"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT"); - - case HAPI_PDG_EVENT_NODE_CLEAR: - return TEXT("HAPI_PDG_EVENT_NODE_CLEAR"); - - case HAPI_PDG_EVENT_COOK_ERROR: - return TEXT("HAPI_PDG_EVENT_COOK_ERROR"); - case HAPI_PDG_EVENT_COOK_WARNING: - return TEXT("HAPI_PDG_EVENT_COOK_WARNING"); - - case HAPI_PDG_EVENT_COOK_COMPLETE: - return TEXT("HAPI_PDG_EVENT_COOK_COMPLETE"); - - case HAPI_PDG_EVENT_DIRTY_START: - return TEXT("HAPI_PDG_EVENT_DIRTY_START"); - case HAPI_PDG_EVENT_DIRTY_STOP: - return TEXT("HAPI_PDG_EVENT_DIRTY_STOP"); - - case HAPI_PDG_EVENT_DIRTY_ALL: - return TEXT("HAPI_PDG_EVENT_DIRTY_ALL"); - - case HAPI_PDG_EVENT_UI_SELECT: - return TEXT("HAPI_PDG_EVENT_UI_SELECT"); - - case HAPI_PDG_EVENT_NODE_CREATE: - return TEXT("HAPI_PDG_EVENT_NODE_CREATE"); - case HAPI_PDG_EVENT_NODE_REMOVE: - return TEXT("HAPI_PDG_EVENT_NODE_REMOVE"); - case HAPI_PDG_EVENT_NODE_RENAME: - return TEXT("HAPI_PDG_EVENT_NODE_RENAME"); - case HAPI_PDG_EVENT_NODE_CONNECT: - return TEXT("HAPI_PDG_EVENT_NODE_CONNECT"); - case HAPI_PDG_EVENT_NODE_DISCONNECT: - return TEXT("HAPI_PDG_EVENT_NODE_DISCONNECT"); - - case HAPI_PDG_EVENT_WORKITEM_SET_INT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_INT"); - case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FLOAT"); - case HAPI_PDG_EVENT_WORKITEM_SET_STRING: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_STRING"); - case HAPI_PDG_EVENT_WORKITEM_SET_FILE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FILE"); - case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT"); - case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY"); - case HAPI_PDG_EVENT_WORKITEM_MERGE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_MERGE"); - case HAPI_PDG_EVENT_WORKITEM_RESULT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_RESULT"); - - case HAPI_PDG_EVENT_WORKITEM_PRIORITY: - return TEXT("HAPI_PDG_EVENT_WORKITEM_PRIORITY"); - - case HAPI_PDG_EVENT_COOK_START: - return TEXT("HAPI_PDG_EVENT_COOK_START"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR"); - - case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: - return TEXT("HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE"); - - case HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED: - return TEXT("HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED"); - - case HAPI_PDG_EVENT_ALL: - return TEXT("HAPI_PDG_EVENT_ALL"); - case HAPI_PDG_EVENT_LOG: - return TEXT("HAPI_PDG_EVENT_LOG"); - - case HAPI_PDG_EVENT_SCHEDULER_ADDED: - return TEXT("HAPI_PDG_EVENT_SCHEDULER_ADDED"); - case HAPI_PDG_EVENT_SCHEDULER_REMOVED: - return TEXT("HAPI_PDG_EVENT_SCHEDULER_REMOVED"); - case HAPI_PDG_EVENT_SET_SCHEDULER: - return TEXT("HAPI_PDG_EVENT_SET_SCHEDULER"); - - case HAPI_PDG_EVENT_SERVICE_MANAGER_ALL: - return TEXT("HAPI_PDG_EVENT_SERVICE_MANAGER_ALL"); - - case HAPI_PDG_CONTEXT_EVENTS: - return TEXT("HAPI_PDG_CONTEXT_EVENTS"); - default: - break; - } - - return FString::Printf(TEXT("Unknown HAPI_PDG_EventType %d"), InEventType); -} - -FString -FHoudiniEngineUtils::HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState) -{ - switch (InWorkitemState) - { - case HAPI_PDG_WORKITEM_UNDEFINED: - return TEXT("HAPI_PDG_WORKITEM_UNDEFINED"); - case HAPI_PDG_WORKITEM_UNCOOKED: - return TEXT("HAPI_PDG_WORKITEM_UNCOOKED"); - case HAPI_PDG_WORKITEM_WAITING: - return TEXT("HAPI_PDG_WORKITEM_WAITING"); - case HAPI_PDG_WORKITEM_SCHEDULED: - return TEXT("HAPI_PDG_WORKITEM_SCHEDULED"); - case HAPI_PDG_WORKITEM_COOKING: - return TEXT("HAPI_PDG_WORKITEM_COOKING"); - case HAPI_PDG_WORKITEM_COOKED_SUCCESS: - return TEXT("HAPI_PDG_WORKITEM_COOKED_SUCCESS"); - case HAPI_PDG_WORKITEM_COOKED_CACHE: - return TEXT("HAPI_PDG_WORKITEM_COOKED_CACHE"); - case HAPI_PDG_WORKITEM_COOKED_FAIL: - return TEXT("HAPI_PDG_WORKITEM_COOKED_FAIL"); - case HAPI_PDG_WORKITEM_COOKED_CANCEL: - return TEXT("HAPI_PDG_WORKITEM_COOKED_CANCEL"); - case HAPI_PDG_WORKITEM_DIRTY: - return TEXT("HAPI_PDG_WORKITEM_DIRTY"); - default: - break; - } - - return FString::Printf(TEXT("Unknown HAPI_PDG_WorkitemState %d"), InWorkitemState); -} - -FName -FHoudiniEngineUtils::RenameToUniqueActor(AActor* InActor, const FString& InName) -{ - const FName NewName = MakeUniqueObjectName(InActor->GetOuter(), InActor->GetClass(), FName(InName)); - InActor->Rename( *(NewName.ToString()) ); - // TODO: Can we set actor label when actor is pending kill? - InActor->SetActorLabel(NewName.ToString()); - return NewName; -} - -UObject* FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) -{ - check(InActor); - - UObject* PrevObj = nullptr; - UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, InActor->GetOuter(), *InName, true); - if (ExistingObject && ExistingObject != InActor) - { - // Rename the existing object - const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName+TEXT("_old")) ); - ExistingObject->Rename(*(NewName.ToString())); - PrevObj = ExistingObject; - } - InActor->Rename(*InName); - if (UpdateLabel) - InActor->SetActorLabel(InName, true); - return PrevObj; -} - -void -FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - FHoudiniPackageParams& OutPackageParams, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FString &BakeFolder, - const FString &ObjectName, - const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode) -{ - OutPackageParams.GeoId = InIdentifier.GeoId; - OutPackageParams.ObjectId = InIdentifier.ObjectId; - OutPackageParams.PartId = InIdentifier.PartId; - OutPackageParams.BakeFolder = BakeFolder; - OutPackageParams.PackageMode = EPackageMode::Bake; - OutPackageParams.ReplaceMode = InReplaceMode; - OutPackageParams.HoudiniAssetName = HoudiniAssetName; - OutPackageParams.HoudiniAssetActorName = HoudiniAssetName; - OutPackageParams.ObjectName = ObjectName; -} - -bool -FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() -{ - // Update / repopulate the foliage editor mode's mesh list if the foliage editor mode is active. - // TODO: find a better way to do this, the relevant functions are in FEdModeFoliage and FFoliageEdModeToolkit are not API exported - FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); - if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) - { - EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); - EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::IsOuterHoudiniAssetComponent(UObject* Obj) -{ - if (!Obj) - return false; - return Obj->GetOuter() && Obj->GetOuter()->IsA(); -} - -UHoudiniAssetComponent* -FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(UObject* Obj) -{ - return Cast(Obj->GetOuter()); -} - -FString -FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) -{ - // Compute Houdini version string. - FString HoudiniVersionString = FString::Printf( - TEXT("%d.%d.%s%d"), HAPI_VERSION_HOUDINI_MAJOR, - HAPI_VERSION_HOUDINI_MINOR, - (ExtraDigit ? (TEXT("0.")) : TEXT("")), - HAPI_VERSION_HOUDINI_BUILD); - - // If we have a patch version, we need to append it. - if (HAPI_VERSION_HOUDINI_PATCH > 0) - HoudiniVersionString = FString::Printf(TEXT("%s.%d"), *HoudiniVersionString, HAPI_VERSION_HOUDINI_PATCH); - return HoudiniVersionString; -} - -void * -FHoudiniEngineUtils::LoadLibHAPI(FString & StoredLibHAPILocation) -{ - FString HFSPath = TEXT(""); - void * HAPILibraryHandle = nullptr; - - // Look up HAPI_PATH environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . - FString HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HAPI_PATH")); - if (!HFS_ENV_VAR.IsEmpty()) - HFSPath = HFS_ENV_VAR; - - // Look up environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . - HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HFS")); - if (!HFS_ENV_VAR.IsEmpty()) - HFSPath = HFS_ENV_VAR; - - // Get platform specific name of libHAPI. - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - - // If we have a custom location specified through settings, attempt to use that. - bool bCustomPathFound = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->bUseCustomHoudiniLocation) - { - // Create full path to libHAPI binary. - FString CustomHoudiniLocationPath = HoudiniRuntimeSettings->CustomHoudiniLocation.Path; - if (!CustomHoudiniLocationPath.IsEmpty()) - { - // Convert path to absolute if it is relative. - if (FPaths::IsRelative(CustomHoudiniLocationPath)) - CustomHoudiniLocationPath = FPaths::ConvertRelativePathToFull(CustomHoudiniLocationPath); - - FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPICustomPath)) - { - HFSPath = CustomHoudiniLocationPath; - bCustomPathFound = true; - } - } - } - - // We have HFS environment variable defined (or custom location), attempt to load libHAPI from it. - if (!HFSPath.IsEmpty()) - { - if (!bCustomPathFound) - { -#if PLATFORM_WINDOWS - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_WINDOWS); -#elif PLATFORM_MAC - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_MAC); -#elif PLATFORM_LINUX - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_LINUX); -#endif - } - - // Create full path to libHAPI binary. - FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - // libHAPI binary exists at specified location, attempt to load it. - FPlatformProcess::PushDllDirectory(*HFSPath); -#if PLATFORM_WINDOWS - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); -#elif PLATFORM_MAC || PLATFORM_LINUX - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); -#endif - FPlatformProcess::PopDllDirectory(*HFSPath); - - // If library has been loaded successfully we can stop. - if ( HAPILibraryHandle ) - { - if (bCustomPathFound) - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from custom path %s"), *LibHAPIName, *HFSPath); - else - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from HFS environment path %s"), *LibHAPIName, *HFSPath); - - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - } - - // Otherwise, we will attempt to detect Houdini installation. - FString HoudiniLocation = TEXT(HOUDINI_ENGINE_HFS_PATH); - FString LibHAPIPath; - - // Compute Houdini version string. - FString HoudiniVersionString = ComputeVersionString(false); - -#if PLATFORM_WINDOWS - - // On Windows, we have also hardcoded HFS path in plugin configuration file; attempt to load from it. - HFSPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, HAPI_HFS_SUBFOLDER_WINDOWS); - - // Create full path to libHAPI binary. - LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HFSPath); - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); - FPlatformProcess::PopDllDirectory(*HFSPath); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from Plugin defined HFS path %s"), *LibHAPIName, *HFSPath); - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - - // As a second attempt, on Windows, we try to look up location of Houdini Engine in the registry. - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini Engine"), StoredLibHAPILocation, false); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // As a third attempt, we try to look up location of Houdini installation (not Houdini Engine) in the registry. - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini"), StoredLibHAPILocation, false); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // Do similar registry lookups for the 32 bits registry - // Look for the Houdini Engine registry install path - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini Engine"), StoredLibHAPILocation, true); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // ... and for the Houdini registry install path - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini"), StoredLibHAPILocation, true); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // Finally, try to load from a hardcoded program files path. - HoudiniLocation = FString::Printf( - TEXT("C:\\Program Files\\Side Effects Software\\Houdini %s\\%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_WINDOWS); - -#else - -# if PLATFORM_MAC - - // Attempt to load from standard Mac OS X installation. - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/Current/Libraries"), *HoudiniVersionString); - - // Fallback in case the previous one doesnt exist - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/%s/Libraries"), *HoudiniVersionString, *HoudiniVersionString); - - // Fallback in case we're using the steam version - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); - - // Backup Fallback in case we're using the steam version - // (this could probably be removed as paths have changed) - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); - -# elif PLATFORM_LINUX - - // Attempt to load from standard Linux installation. - HoudiniLocation = FString::Printf( - TEXT("/opt/hfs%s/%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_LINUX); - -# endif - -#endif - - // Create full path to libHAPI binary. - LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HoudiniLocation); - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); - FPlatformProcess::PopDllDirectory(*HoudiniLocation); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from expected installation %s"), *LibHAPIName, *HoudiniLocation); - StoredLibHAPILocation = HoudiniLocation; - return HAPILibraryHandle; - } - } - - StoredLibHAPILocation = TEXT(""); - return HAPILibraryHandle; -} - -bool -FHoudiniEngineUtils::IsInitialized() -{ - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) - return false; - - return (FHoudiniApi::IsInitialized(SessionPtr) == HAPI_RESULT_SUCCESS); -} - -bool -FHoudiniEngineUtils::IsHoudiniNodeValid(const HAPI_NodeId& NodeId) -{ - if (NodeId < 0) - return false; - - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - bool ValidationAnswer = 0; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) - { - return false; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsNodeValid( - FHoudiniEngine::Get().GetSession(), NodeId, - NodeInfo.uniqueHoudiniNodeId, &ValidationAnswer)) - { - return false; - } - - return ValidationAnswer; -} - -bool -FHoudiniEngineUtils::HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex) -{ - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), HostAssetId, InputIndex), false); - - return true; -} - -bool -FHoudiniEngineUtils::DestroyHoudiniAsset(const HAPI_NodeId& AssetId) -{ - if (HAPI_RESULT_SUCCESS == FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), AssetId)) - { - return true; - } - - return false; -} - -#if PLATFORM_WINDOWS -void * -FHoudiniEngineUtils::LocateLibHAPIInRegistry( - const FString & HoudiniInstallationType, - FString & StoredLibHAPILocation, - bool LookIn32bitRegistry) -{ - auto FindDll = [&](const FString& InHoudiniInstallationPath) - { - FString HFSPath = FString::Printf(TEXT("%s/%s"), *InHoudiniInstallationPath, HAPI_HFS_SUBFOLDER_WINDOWS); - - // Create full path to libHAPI binary. - FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, HAPI_LIB_OBJECT_WINDOWS); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HFSPath); - void* HAPILibraryHandle = FPlatformProcess::GetDllHandle(HAPI_LIB_OBJECT_WINDOWS); - FPlatformProcess::PopDllDirectory(*HFSPath); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE( - TEXT("Loaded %s from Registry path %s"), HAPI_LIB_OBJECT_WINDOWS, - *HFSPath); - - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - return (void*)0; - }; - - FString HoudiniInstallationPath; - FString HoudiniVersionString = ComputeVersionString(true); - FString RegistryKey = FString::Printf( - TEXT("Software\\%sSide Effects Software\\%s"), - (LookIn32bitRegistry ? TEXT("WOW6432Node\\") : TEXT("")), *HoudiniInstallationType); - - if (FWindowsPlatformMisc::QueryRegKey( - HKEY_LOCAL_MACHINE, *RegistryKey, *HoudiniVersionString, HoudiniInstallationPath)) - { - FPaths::NormalizeDirectoryName(HoudiniInstallationPath); - return FindDll(HoudiniInstallationPath); - } - - return nullptr; -} -#endif - -bool -FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId & OutAssetLibraryId) -{ - OutAssetLibraryId = -1; - - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return false; - - if (!FHoudiniEngineUtils::IsInitialized()) - return false; - - // Get the HDA's file path - // We need to convert relative file path to absolute - FString AssetFileName = HoudiniAsset->GetAssetFileName(); - if (FPaths::IsRelative(AssetFileName)) - AssetFileName = FPaths::ConvertRelativePathToFull(AssetFileName); - - // We need to modify the file name for expanded .hdas - FString FileExtension = FPaths::GetExtension(AssetFileName); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) - { - // the .hda directory is what we should be loading - AssetFileName = FPaths::GetPath(AssetFileName); - } - - // If the hda file exists, we can simply load it directly the file - HAPI_Result Result = HAPI_RESULT_FAILURE; - if ( !AssetFileName.IsEmpty() ) - { - if ( FPaths::FileExists(AssetFileName) - || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName) ) ) - { - // Load the asset from file. - std::string AssetFileNamePlain; - FHoudiniEngineUtils::ConvertUnrealString(AssetFileName, AssetFileNamePlain); - Result = FHoudiniApi::LoadAssetLibraryFromFile( - FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); - } - } - - // Detect license issues - // HoudiniEngine aquires a license when creating/loading a node, not when creating a session - if (Result >= HAPI_RESULT_NO_LICENSE_FOUND && Result < HAPI_RESULT_ASSET_INVALID) - { - FString ErrorDesc = GetErrorDescription(Result); - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: License failed: %s."), *AssetFileName, *ErrorDesc); - - // We must stop the session to prevent further attempts at loading an HDA - // as this could lead to unreal becoming stuck and unresponsive due to license timeout - FHoudiniEngine::Get().StopSession(); - - return false; - } - - // If loading from file failed, try to load using the memory copy - if (Result != HAPI_RESULT_SUCCESS) - { - // Expanded hdas cannot be loaded from Memory - if (HoudiniAsset->IsExpandedHDA() || HoudiniAsset->GetAssetBytesCount() <= 0) - { - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); - return false; - } - else - { - // Warn the user that we are loading from memory - HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from Memory: source asset file not found."), *AssetFileName); - - // Otherwise we will try to load from buffer we've cached. - Result = FHoudiniApi::LoadAssetLibraryFromMemory( - FHoudiniEngine::Get().GetSession(), - reinterpret_cast(HoudiniAsset->GetAssetBytes()), - HoudiniAsset->GetAssetBytesCount(), true, &OutAssetLibraryId); - } - } - - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_MESSAGE(TEXT("Error loading asset library for %s: %s"), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - return true; -} - -bool -FHoudiniEngineUtils::GetSubAssetNames( - const HAPI_AssetLibraryId& AssetLibraryId, - TArray< HAPI_StringHandle >& OutAssetNames) -{ - if (AssetLibraryId < 0) - return false; - - int32 AssetCount = 0; - HAPI_Result Result = HAPI_RESULT_FAILURE; - Result = FHoudiniApi::GetAvailableAssetCount(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetCount); - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Error getting asset count: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - if (AssetCount <= 0) - { - HOUDINI_LOG_ERROR(TEXT("Could not find an asset.")); - return false; - } - - OutAssetNames.SetNumUninitialized(AssetCount); - Result = FHoudiniApi::GetAvailableAssets(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &OutAssetNames[0], AssetCount); - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Unable to retrieve sub asset names: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - if (!AssetCount) - { - HOUDINI_LOG_ERROR(TEXT("No assets found")); - return false; - } - - return true; -} - - -bool -FHoudiniEngineUtils::OpenSubassetSelectionWindow(TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ) -{ - OutPickedAssetName = -1; - - if (AssetNames.Num() <= 0) - return false; - - // Default to the first asset - OutPickedAssetName = AssetNames[0]; - -#if WITH_EDITOR - // Present the user with a dialog for choosing which asset to instantiate. - TSharedPtr ParentWindow; - if (FModuleManager::Get().IsModuleLoaded("MainFrame")) - { - // Check if the main frame is loaded. When using the old main frame it may not be. - IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); - ParentWindow = MainFrame.GetParentWindow(); - } - - if (!ParentWindow.IsValid()) - { - return false; - } - - TSharedPtr AssetSelectionWidget; - TSharedRef 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()) - { - return false; - } - - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - - int32 DialogPickedAssetName = AssetSelectionWidget->GetSelectedAssetName(); - if (DialogPickedAssetName != -1) - { - OutPickedAssetName = DialogPickedAssetName; - return true; - } - else - { - return false; - } -#endif - - return true; -} - -/* -bool -FHoudiniEngineUtils::IsValidNodeId(HAPI_NodeId NodeId) -{ - return NodeId != -1; -} -*/ - -bool -FHoudiniEngineUtils::GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString) -{ - HAPI_AssetInfo AssetInfo; - if (FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo) == HAPI_RESULT_SUCCESS) - { - FHoudiniEngineString HoudiniEngineString(AssetInfo.nameSH); - return HoudiniEngineString.ToFString(NameString); - } - - return false; -} - -bool -FHoudiniEngineUtils::GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer) -{ - PresetBuffer.Empty(); - - HAPI_NodeId NodeId; - HAPI_AssetInfo AssetInfo; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo)) - { - NodeId = AssetInfo.nodeId; - } - else - NodeId = AssetNodeId; - - int32 BufferLength = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPresetBufLength( - FHoudiniEngine::Get().GetSession(), NodeId, - HAPI_PRESETTYPE_BINARY, NULL, &BufferLength), false); - - PresetBuffer.SetNumZeroed(BufferLength); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPreset( - FHoudiniEngine::Get().GetSession(), NodeId, - &PresetBuffer[0], PresetBuffer.Num()), false); - - return true; -} - - -bool -FHoudiniEngineUtils::HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath) -{ - // Retrieve Path to the given Node, relative to the other given Node - if ((InNodeId < 0) || (InRelativeToNodeId < 0)) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InNodeId)) - return false; - - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodePath( - FHoudiniEngine::Get().GetSession(), - InNodeId, InRelativeToNodeId, &StringHandle)) - { - if(FHoudiniEngineString::ToFString(StringHandle, OutPath)) - { - return true; - } - } - return false; -} - -bool -FHoudiniEngineUtils::HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath) -{ - // Do the HAPI query only on first-use - if (!InHGPO.NodePath.IsEmpty()) - return true; - - FString NodePathTemp; - if (InHGPO.AssetId == InHGPO.GeoId) - { - // This is a SOP asset, just return the asset name in this case - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InHGPO.AssetId, &AssetInfo)) - { - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo)) - { - if (FHoudiniEngineString::ToFString(AssetNodeInfo.nameSH, NodePathTemp)) - { - OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); - } - } - } - } - else - { - // This is an OBJ asset, return the path to this geo relative to the asset - if (FHoudiniEngineUtils::HapiGetNodePath(InHGPO.GeoId, InHGPO.AssetId, NodePathTemp)) - { - OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); - } - } - - /*if (OutPath.IsEmpty()) - { - OutPath = TEXT("Empty"); - } - - return NodePath; - */ - - return !OutPath.IsEmpty(); -} - - -bool -FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), - InNodeId, &NodeInfo), false); - - int32 ObjectCount = 0; - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - ObjectCount = 1; - OutObjectInfos.SetNumUninitialized(1); - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( - FHoudiniEngine::Get().GetSession(), - NodeInfo.parentId, &OutObjectInfos[0]), false); - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( - FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); - - if (ObjectCount <= 0) - { - ObjectCount = 1; - OutObjectInfos.SetNumUninitialized(1); - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[0]), false); - } - else - { - OutObjectInfos.SetNumUninitialized(ObjectCount); - for (int32 Idx = 0; Idx < OutObjectInfos.Num(); Idx++) - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( - FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[0], 0, ObjectCount), false); - } - } - else - return false; - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId,&NodeInfo), false); - - int32 ObjectCount = 1; - OutObjectTransforms.SetNumUninitialized(1); - FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); - - OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; - OutObjectTransforms[0].scale[0] = 1.0f; - OutObjectTransforms[0].scale[1] = 1.0f; - OutObjectTransforms[0].scale[2] = 1.0f; - OutObjectTransforms[0].rstOrder = HAPI_SRT; - - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - // Do nothing. Identity transform will be used for the main parent object. - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( - FHoudiniEngine::Get().GetSession(), - InNodeId, nullptr, &ObjectCount), false); - - if (ObjectCount <= 0) - { - // Do nothing. Identity transform will be used for the main asset object. - } - else - { - OutObjectTransforms.SetNumUninitialized(ObjectCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_SRT, &OutObjectTransforms[0], 0, ObjectCount), false); - } - } - else - return false; - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, - &NodeInfo), false); - - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - NodeInfo.parentId, -1, HAPI_SRT, &HapiTransform), false); - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InNodeId, -1, HAPI_SRT, &HapiTransform), false); - } - else - return false; - - // Convert HAPI transform to Unreal one. - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, OutTransform); - - return true; -} - -void -FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform) -{ - if ( HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM ) - { - // Swap Y/Z, invert W - FQuat ObjectRotation( - HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[2], - HapiTransform.rotationQuaternion[1], -HapiTransform.rotationQuaternion[3]); - - // Swap Y/Z and scale - FVector ObjectTranslation(HapiTransform.position[0], HapiTransform.position[2], HapiTransform.position[1]); - ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[2], HapiTransform.scale[1]); - - UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); - } - else - { - FQuat ObjectRotation( - HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[1], - HapiTransform.rotationQuaternion[2], HapiTransform.rotationQuaternion[3]); - - FVector ObjectTranslation( - HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2]); - ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[1], HapiTransform.scale[2]); - - UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); - } -} - -void -FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform) -{ - float HapiMatrix[16]; - FHoudiniApi::ConvertTransformEulerToMatrix(FHoudiniEngine::Get().GetSession(), &HapiTransformEuler, HapiMatrix); - - HAPI_Transform HapiTransformQuat; - FMemory::Memzero< HAPI_Transform >(HapiTransformQuat); - FHoudiniApi::ConvertMatrixToQuat(FHoudiniEngine::Get().GetSession(), HapiMatrix, HAPI_SRT, &HapiTransformQuat); - - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransformQuat, UnrealTransform); -} - -void -FHoudiniEngineUtils::TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform) -{ - FMemory::Memzero< HAPI_Transform >(HapiTransform); - HapiTransform.rstOrder = HAPI_SRT; - - FQuat UnrealRotation = UnrealTransform.GetRotation(); - FVector UnrealTranslation = UnrealTransform.GetTranslation(); - FVector UnrealScale = UnrealTransform.GetScale3D(); - - if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) - { - // Swap Y/Z, invert XYZ - HapiTransform.rotationQuaternion[0] = -UnrealRotation.X; - HapiTransform.rotationQuaternion[1] = -UnrealRotation.Z; - HapiTransform.rotationQuaternion[2] = -UnrealRotation.Y; - HapiTransform.rotationQuaternion[3] = UnrealRotation.W; - - // Swap Y/Z, scale - HapiTransform.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransform.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransform.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - HapiTransform.scale[0] = UnrealScale.X; - HapiTransform.scale[1] = UnrealScale.Z; - HapiTransform.scale[2] = UnrealScale.Y; - } - else - { - HapiTransform.rotationQuaternion[0] = UnrealRotation.X; - HapiTransform.rotationQuaternion[1] = UnrealRotation.Y; - HapiTransform.rotationQuaternion[2] = UnrealRotation.Z; - HapiTransform.rotationQuaternion[3] = UnrealRotation.W; - - HapiTransform.position[0] = UnrealTranslation.X; - HapiTransform.position[1] = UnrealTranslation.Y; - HapiTransform.position[2] = UnrealTranslation.Z; - - HapiTransform.scale[0] = UnrealScale.X; - HapiTransform.scale[1] = UnrealScale.Y; - HapiTransform.scale[2] = UnrealScale.Z; - } -} - -void -FHoudiniEngineUtils::TranslateUnrealTransform( - const FTransform & UnrealTransform, - HAPI_TransformEuler & HapiTransformEuler) -{ - FMemory::Memzero< HAPI_TransformEuler >(HapiTransformEuler); - - HapiTransformEuler.rstOrder = HAPI_SRT; - HapiTransformEuler.rotationOrder = HAPI_XYZ; - - FQuat UnrealRotation = UnrealTransform.GetRotation(); - FVector UnrealTranslation = UnrealTransform.GetTranslation(); - FVector UnrealScale = UnrealTransform.GetScale3D(); - - if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) - { - // switch the quaternion to Y-up, LHR by Swapping Y/Z and negating W - Swap(UnrealRotation.Y, UnrealRotation.Z); - UnrealRotation.W = -UnrealRotation.W; - const FRotator Rotator = UnrealRotation.Rotator(); - - // Negate roll and pitch since they are actually RHR - HapiTransformEuler.rotationEuler[0] = -Rotator.Roll; - HapiTransformEuler.rotationEuler[1] = -Rotator.Pitch; - HapiTransformEuler.rotationEuler[2] = Rotator.Yaw; - - // Swap Y/Z, scale - HapiTransformEuler.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransformEuler.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransformEuler.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - HapiTransformEuler.scale[0] = UnrealScale.X; - HapiTransformEuler.scale[1] = UnrealScale.Z; - HapiTransformEuler.scale[2] = UnrealScale.Y; - } - else - { - const FRotator Rotator = UnrealRotation.Rotator(); - HapiTransformEuler.rotationEuler[0] = Rotator.Roll; - HapiTransformEuler.rotationEuler[1] = Rotator.Yaw; - HapiTransformEuler.rotationEuler[2] = Rotator.Pitch; - - HapiTransformEuler.position[0] = UnrealTranslation.X; - HapiTransformEuler.position[1] = UnrealTranslation.Y; - HapiTransformEuler.position[2] = UnrealTranslation.Z; - - HapiTransformEuler.scale[0] = UnrealScale.X; - HapiTransformEuler.scale[1] = UnrealScale.Y; - HapiTransformEuler.scale[2] = UnrealScale.Z; - } -} - -bool -FHoudiniEngineUtils::UploadHACTransform(UHoudiniAssetComponent* HAC) -{ - if (!HAC || !HAC->bUploadTransformsToHoudiniEngine) - return false; - - // Indicates the HAC has been fully loaded - // TODO: Check! (replaces fullyloaded) - if (!HAC->IsFullyLoaded()) - return false; - - if (HAC->GetAssetCookCount() > 0 && HAC->GetAssetId() >= 0) - { - if (!FHoudiniEngineUtils::HapiSetAssetTransform(HAC->GetAssetId(), HAC->GetComponentTransform())) - return false; - } - - HAC->SetHasComponentTransformChanged(false); - - return true; -} - -bool -FHoudiniEngineUtils::HapiSetAssetTransform(const HAPI_NodeId& AssetId, const FTransform & Transform) -{ - if (AssetId < 0) - return false; - - // Translate Unreal transform to HAPI Euler one. - HAPI_TransformEuler TransformEuler; - FMemory::Memzero< HAPI_TransformEuler >(TransformEuler); - FHoudiniEngineUtils::TranslateUnrealTransform(Transform, TransformEuler); - - // Get the NodeInfo - HAPI_NodeInfo LocalAssetNodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetId, - &LocalAssetNodeInfo), false); - - if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - LocalAssetNodeInfo.parentId, - &TransformEuler), false); - } - else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - AssetId, &TransformEuler), false); - } - else - return false; - - return true; -} - -HAPI_NodeId -FHoudiniEngineUtils::HapiGetParentNodeId(const HAPI_NodeId& NodeId) -{ - HAPI_NodeId ParentId = -1; - if (NodeId >= 0) - { - HAPI_NodeInfo NodeInfo; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) - ParentId = NodeInfo.parentId; - } - - return ParentId; -} - - -// Assign a unique Actor Label if needed -void -FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - // TODO: Necessary?? - -#if WITH_EDITOR - HAPI_NodeId AssetId = HAC->GetAssetId(); - if (AssetId < 0) - return; - - AActor* OwnerActor = HAC->GetOwner(); - if (!OwnerActor) - return; - - if (!OwnerActor->GetName().StartsWith(AHoudiniAssetActor::StaticClass()->GetName())) - return; - - // Assign unique actor label based on asset name if it seems to have not been renamed already - FString UniqueName; - if (FHoudiniEngineUtils::GetHoudiniAssetName(AssetId, UniqueName)) - FActorLabelUtilities::SetActorLabelUnique(OwnerActor, UniqueName); -#endif -} - -bool -FHoudiniEngineUtils::GetLicenseType(FString & LicenseType) -{ - LicenseType = TEXT(""); - HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetSessionEnvInt( - FHoudiniEngine::Get().GetSession(), HAPI_SESSIONENVINT_LICENSE, - (int32 *)&LicenseTypeValue), false); - - switch (LicenseTypeValue) - { - case HAPI_LICENSE_NONE: - { - LicenseType = TEXT("No License Acquired"); - break; - } - - case HAPI_LICENSE_HOUDINI_ENGINE: - { - LicenseType = TEXT("Houdini Engine"); - break; - } - - case HAPI_LICENSE_HOUDINI: - { - LicenseType = TEXT("Houdini"); - break; - } - - case HAPI_LICENSE_HOUDINI_FX: - { - LicenseType = TEXT("Houdini FX"); - break; - } - - case HAPI_LICENSE_HOUDINI_ENGINE_INDIE: - { - LicenseType = TEXT("Houdini Engine Indie"); - break; - } - - case HAPI_LICENSE_HOUDINI_INDIE: - { - LicenseType = TEXT("Houdini Indie"); - break; - } - - case HAPI_LICENSE_MAX: - default: - { - return false; - } - } - - return true; -} - -// Check if the Houdini asset component (or parent HAC of a parameter) is being cooked -bool -FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(UObject* InObj) -{ - if (!InObj) - return false; - - UHoudiniAssetComponent* HoudiniAssetComponent = nullptr; - - if (InObj->IsA()) - { - HoudiniAssetComponent = Cast(InObj); - } - else if (InObj->IsA()) - { - UHoudiniParameter* Parameter = Cast(InObj); - if (!Parameter) - return false; - - HoudiniAssetComponent = Cast(Parameter->GetOuter()); - } - - if (!HoudiniAssetComponent) - return false; - - EHoudiniAssetState AssetState = HoudiniAssetComponent->GetAssetState(); - - return AssetState >= EHoudiniAssetState::PreCook && AssetState <= EHoudiniAssetState::PostCook; -} - -void -FHoudiniEngineUtils::UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate) -{ - TArray ObjectsToUpdate; - ObjectsToUpdate.Add(InObjectToUpdate); - - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() - { - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - } -} - -void -FHoudiniEngineUtils::UpdateEditorProperties(TArray ObjectsToUpdate, const bool& InForceFullUpdate) -{ - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() - { - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - } -} - -void FHoudiniEngineUtils::UpdateBlueprintEditor(UHoudiniAssetComponent* HAC) -{ - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [HAC]() - { - FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); - } -} - -void -FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate) -{ - // TODO: Don't use this method. Prefer using IDetailLayoutBuilder::ForceRefreshDetails(). - // Example to correctly update details panel through IDetailCategoryBuilder / IDetailLayoutBuilder - // IDetailCategoryBuilder &CategoryBuilder = StructBuilder.GetParentCategory(); - // IDetailLayoutBuilder &LayoutBuilder = CategoryBuilder.GetParentLayout(); - // LayoutBuilder.ForceRefreshDetails(); - -#if WITH_EDITOR - if (!bInForceFullUpdate) - { - // bNeedFullUpdate is false only when small changes (parameters value) have been made - // We do not reselect the actor to avoid loosing the currently selected parameter - if(GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); - - return; - } - - // We now want to get all the components/actors owning the objects to update - TArray AllSceneComponents; - for (auto CurrentObject : ObjectsToUpdate) - { - if (!CurrentObject || CurrentObject->IsPendingKill()) - continue; - - // In some case, the object itself is the component - USceneComponent* SceneComp = Cast(CurrentObject); - if (!SceneComp) - { - SceneComp = Cast(CurrentObject->GetOuter()); - } - - if (SceneComp && !SceneComp->IsPendingKill()) - { - AllSceneComponents.Add(SceneComp); - continue; - } - } - - TArray AllActors; - for (auto CurrentSceneComp : AllSceneComponents) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill()) - continue; - - AActor* Actor = CurrentSceneComp->GetOwner(); - if (Actor && !Actor->IsPendingKill()) - AllActors.Add(Actor); - } - - // Updating the editor properties can be done in two ways, depending if we're in the BP editor or not - // If we have a parent actor, we're not in the BP Editor, so update via the property editor module - if (AllActors.Num() > 0) - { - // Get the property editor module - FPropertyEditorModule& PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // This will actually force a refresh of all the details view - //PropertyModule.NotifyCustomizationModuleChanged(); - - TArray SelectedActors; - for (auto Actor : AllActors) - { - if (Actor && Actor->IsSelected()) - SelectedActors.Add(Actor); - } - - if (SelectedActors.Num() > 0) - { - PropertyModule.UpdatePropertyViews(SelectedActors); - } - - // 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. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - { - // We have no details panel, nothing to update. - continue; - } - - // Get the selected actors for this details panels and check if one of ours belongs to it - const TArray>& SelectedDetailActors = DetailsView->GetSelectedActors(); - bool bFoundActor = false; - for (int32 ActorIdx = 0; ActorIdx < SelectedDetailActors.Num(); ActorIdx++) - { - TWeakObjectPtr SelectedActor = SelectedDetailActors[ActorIdx]; - if (SelectedActor.IsValid() && AllActors.Contains(SelectedActor.Get())) - { - bFoundActor = true; - break; - } - } - - // None of our actors belongs to this detail panel, no need to update it - if (!bFoundActor) - continue; - - // Refresh that details panels using its current selection - TArray Selection; - for (auto DetailsActor : SelectedDetailActors) - { - if (DetailsActor.IsValid()) - Selection.Add(DetailsActor.Get()); - } - - // Reset selected actors, force refresh and override the lock. - DetailsView->SetObjects(SelectedActors, bInForceFullUpdate, true); - - if (GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); - } - } - else - { - // TODO: Do we need to do Blueprint Editor updates here or can we confine it to "post output processing"? - - } - - /* - // Reset the full update flag - if (bNeedFullUpdate) - HAC->SetEditorPropertiesNeedFullUpdate(false); - */ - - return; -#endif -} - -void FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC) -{ - //UHoudiniAssetComponent* HACTemplate = HAC->GetCachedTemplate(); - //UBlueprintGeneratedClass* OwnerBPClass = Cast(HACTemplate->GetOuter()); - //if (!OwnerBPClass) - // return; - - ///* - //FBlueprintEditor* BlueprintEditor = static_cast(FAssetEditorManager::Get().FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); - //if (!BlueprintEditor) - // return; - //*/ - - //// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built. - //UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - //FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); - //if (!BlueprintEditor) - // return; - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(HAC); - if (!BlueprintEditor) - return; - - TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); - if (SCSEditor.IsValid()) - { - SCSEditor->UpdateTree(true); - SCSEditor->DumpTree(); - } - BlueprintEditor->RefreshMyBlueprint(); - - //BlueprintEditor->RefreshMyBlueprint(); - //BlueprintEditor->RefreshInspector(); - //BlueprintEditor->RefreshEditors(); - - // Also somehow reselect ? -} - -HAPI_Result -FHoudiniEngineUtils::SetAttributeStringData( - const FString& InString, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo) -{ - TArray StringArray; - StringArray.Add(InString); - - return SetAttributeStringData(StringArray, InNodeId, InPartId, InAttributeName, InAttributeInfo); -} - -HAPI_Result -FHoudiniEngineUtils::SetAttributeStringData( - const TArray& InStringArray, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo ) -{ - TArray StringDataArray; - for (auto CurrentString : InStringArray) - { - // Append the converted string to the string array - StringDataArray.Add(FHoudiniEngineUtils::ExtractRawString(CurrentString)); - } - - // Set the attribute's string data - HAPI_Result result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - TCHAR_TO_ANSI(*InAttributeName), &InAttributeInfo, - StringDataArray.GetData(), 0, InAttributeInfo.count); - - // ExtractRawString allocates memory using malloc, free it! - FreeRawStringMemory(StringDataArray); - - return result; -} - -char * -FHoudiniEngineUtils::ExtractRawString(const FString& InString) -{ - if (InString.IsEmpty()) - return nullptr; - - std::string ConvertedString = TCHAR_TO_UTF8(*InString); - - // Allocate space for unique string. - int32 UniqueStringBytes = ConvertedString.size() + 1; - char * UniqueString = static_cast(FMemory::Malloc(UniqueStringBytes)); - - FMemory::Memzero(UniqueString, UniqueStringBytes); - FMemory::Memcpy(UniqueString, ConvertedString.c_str(), ConvertedString.size()); - - return UniqueString; -} - -void -FHoudiniEngineUtils::FreeRawStringMemory(const char*& InRawString) -{ - if (InRawString == nullptr) - return; - - // Do not attempt to free empty strings! - if (!InRawString[0]) - return; - - FMemory::Free((void*)InRawString); - InRawString = nullptr; -} - -void -FHoudiniEngineUtils::FreeRawStringMemory(TArray& InRawStringArray) -{ - // ExtractRawString allocates memory using malloc, free it! - for (auto CurrentStrPtr : InRawStringArray) - { - FreeRawStringMemory(CurrentStrPtr); - } - InRawStringArray.Empty(); -} - -bool -FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // No need to add another component if we already show the logo - if (FHoudiniEngineUtils::HasHoudiniLogo(HAC)) - return true; - - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - UStaticMeshComponent * HoudiniLogoSMC = NewObject< UStaticMeshComponent >( - HAC, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!HoudiniLogoSMC) - return false; - - HoudiniLogoSMC->SetStaticMesh(HoudiniLogoSM); - HoudiniLogoSMC->SetVisibility(true); - HoudiniLogoSMC->SetHiddenInGame(true); - // Attach created static mesh component to our Houdini component. - HoudiniLogoSMC->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniLogoSMC->RegisterComponent(); - - return true; -} - -bool -FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the Houdini Logo SM - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - // Iterate on the HAC's component - for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) - continue; - - // Get the static mesh component - UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) - continue; - - // Check if the SMC is the Houdini Logo - if (SMC->GetStaticMesh() != HoudiniLogoSM) - continue; - - SMC->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SMC->UnregisterComponent(); - SMC->DestroyComponent(); - - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the Houdini Logo SM - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - // Iterate on the HAC's component - for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) - continue; - - // Get the static mesh component - UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) - continue; - - // Check if the SMC is the Houdini Logo - if (SMC->GetStaticMesh() == HoudiniLogoSM) - return true; - } - - return false; -} - -int32 -FHoudiniEngineUtils::HapiGetVertexListForGroup( - const HAPI_NodeId& GeoId, - const HAPI_PartInfo& PartInfo, - const FString& GroupName, - const TArray& FullVertexList, - TArray& NewVertexList, - TArray& AllVertexList, - TArray& AllFaceList, - TArray& AllGroupFaceIndices, - int32& FirstValidVertex, - int32& FirstValidPrim, - const bool& isPackedPrim) -{ - int32 ProcessedWedges = 0; - AllFaceList.Empty(); - FirstValidPrim = 0; - FirstValidVertex = 0; - NewVertexList.Init(-1, FullVertexList.Num()); - - // Get the faces membership for this group - bool bAllEquals = false; - TArray PartGroupMembership; - if (!FHoudiniEngineUtils::HapiGetGroupMembership( - GeoId, PartInfo, HAPI_GROUPTYPE_PRIM, GroupName, PartGroupMembership, bAllEquals)) - return false; - - // Go through all primitives. - for (int32 FaceIdx = 0; FaceIdx < PartGroupMembership.Num(); ++FaceIdx) - { - if (PartGroupMembership[FaceIdx] <= 0) - { - // The face is not in the group, skip - continue; - } - - // Add the face's index. - AllFaceList.Add(FaceIdx); - - // Get the index of this face's vertices - int32 FirstVertexIdx = FaceIdx * 3; - int32 SecondVertexIdx = FirstVertexIdx + 1; - int32 LastVertexIdx = FirstVertexIdx + 2; - - // This face is a member of specified group. - // Add all 3 vertices - if (FullVertexList.IsValidIndex(LastVertexIdx)) - { - NewVertexList[FirstVertexIdx] = FullVertexList[FirstVertexIdx]; - NewVertexList[SecondVertexIdx] = FullVertexList[SecondVertexIdx]; - NewVertexList[LastVertexIdx] = FullVertexList[LastVertexIdx]; - } - - // Mark these vertex indices as used. - if (AllVertexList.IsValidIndex(LastVertexIdx)) - { - AllVertexList[FirstVertexIdx] = 1; - AllVertexList[SecondVertexIdx] = 1; - AllVertexList[LastVertexIdx] = 1; - } - - // Mark this face as used. - if (AllGroupFaceIndices.IsValidIndex(FaceIdx)) - AllGroupFaceIndices[FaceIdx] = 1; - - if (ProcessedWedges == 0) - { - // Keep track of the first valid vertex/face indices for this group - // This will be useful later on when extracting attributes - FirstValidVertex = FirstVertexIdx; - FirstValidPrim = FaceIdx; - } - - ProcessedWedges += 3; - } - - return ProcessedWedges; -} - -bool -FHoudiniEngineUtils::HapiGetGroupNames( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const HAPI_GroupType& GroupType, const bool& isPackedPrim, - TArray& OutGroupNames) -{ - int32 GroupCount = 0; - if (!isPackedPrim) - { - // Get group count on the geo - HAPI_GeoInfo GeoInfo; - FHoudiniApi::GeoInfo_Init(&GeoInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo), false); - - if (GroupType == HAPI_GROUPTYPE_POINT) - GroupCount = GeoInfo.pointGroupCount; - else if (GroupType == HAPI_GROUPTYPE_PRIM) - GroupCount = GeoInfo.primitiveGroupCount; - } - else - { - // We need the group count for this packed prim - int32 PointGroupCount = 0, PrimGroupCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupCountOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PointGroupCount, &PrimGroupCount), false); - - if (GroupType == HAPI_GROUPTYPE_POINT) - GroupCount = PointGroupCount; - else if (GroupType == HAPI_GROUPTYPE_PRIM) - GroupCount = PrimGroupCount; - } - - if (GroupCount <= 0) - return true; - - TArray GroupNameStringHandles; - GroupNameStringHandles.SetNumZeroed(GroupCount); - if (!isPackedPrim) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNames( - FHoudiniEngine::Get().GetSession(), - GeoId, GroupType, &GroupNameStringHandles[0], GroupCount), false); - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNamesOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, GroupType, &GroupNameStringHandles[0], GroupCount), false); - } - - OutGroupNames.SetNum(GroupCount); - for (int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx) - { - FString CurrentGroupName = TEXT(""); - FHoudiniEngineString::ToFString(GroupNameStringHandles[NameIdx], CurrentGroupName); - OutGroupNames[NameIdx] = CurrentGroupName; - } - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetGroupMembership( - const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, - const HAPI_GroupType& GroupType, const FString & GroupName, - TArray& OutGroupMembership, bool& OutAllEquals) -{ - int32 ElementCount = (GroupType == HAPI_GROUPTYPE_POINT) ? PartInfo.pointCount : PartInfo.faceCount; - if (ElementCount < 1) - return false; - OutGroupMembership.SetNum(ElementCount); - - OutAllEquals = false; - std::string ConvertedGroupName = TCHAR_TO_UTF8(*GroupName); - if (!PartInfo.isInstanced) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembership( - FHoudiniEngine::Get().GetSession(), - GeoId, PartInfo.id, GroupType,ConvertedGroupName.c_str(), - &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembershipOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), GeoId, PartInfo.id, GroupType, - ConvertedGroupName.c_str(), &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); - } - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize, - HAPI_AttributeOwner InOwner) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniEngineUtils::HapiGetAttributeDataAsFloat")); - - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Allocate sufficient buffer for data. - OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); - - return true; - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Expected Float, found an int, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray IntData; - IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(IntData.Num()); - for (int32 Idx = 0; Idx < IntData.Num(); Idx++) - { - OutData[Idx] = (float)IntData[Idx]; - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from integer."), *FString(InAttribName)); - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - // Expected Float, found a string, try to convert the attribute - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) - { - bool bConversionError = false; - OutData.SetNum(StringData.Num()); - for (int32 Idx = 0; Idx < StringData.Num(); Idx++) - { - if (StringData[Idx].IsNumeric()) - OutData[Idx] = FCString::Atof(*StringData[Idx]); - else - bConversionError = true; - } - - if (!bConversionError) - { - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from string."), *FString(InAttribName)); - return true; - } - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a float attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - const int32& InTupleSize, - const HAPI_AttributeOwner& InOwner) -{ - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Allocate sufficient buffer for data. - OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); - - return true; - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Expected Int, found a float, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray FloatData; - FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the float values - if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(FloatData.Num()); - for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) - { - OutData[Idx] = (int32)FloatData[Idx]; - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from float."), *FString(InAttribName)); - - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - // Expected Int, found a string, try to convert the attribute - TArray StringData; - if(FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) - { - bool bConversionError = false; - OutData.SetNum(StringData.Num()); - for (int32 Idx = 0; Idx < StringData.Num(); Idx++) - { - if (StringData[Idx].IsNumeric()) - OutData[Idx] = FCString::Atoi(*StringData[Idx]); - else - bConversionError = true; - } - - if (!bConversionError) - { - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from string."), *FString(InAttribName)); - return true; - } - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be an integer attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsString( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize, - HAPI_AttributeOwner InOwner) -{ - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - return FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, OutData); - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Expected string, found a float, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray FloatData; - FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the float values - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(FloatData.Num()); - for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) - { - OutData[Idx] = FString::SanitizeFloat(FloatData[Idx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from float."), *FString(InAttribName)); - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Expected String, found an int, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray IntData; - IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(IntData.Num()); - for (int32 Idx = 0; Idx < IntData.Num(); Idx++) - { - OutData[Idx] = FString::FromInt(IntData[Idx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from integer."), *FString(InAttribName)); - return true; - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a string attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& InAttributeInfo, - TArray& OutData) -{ - if (!InAttributeInfo.exists) - return false; - - // Extract the StringHandles - TArray StringHandles; - StringHandles.Init(-1, InAttributeInfo.count * InAttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, &InAttributeInfo, - &StringHandles[0], 0, InAttributeInfo.count), false); - - // Set the output data size - OutData.SetNum(StringHandles.Num()); - - // Convert the StringHandles to FString. - // We'll use a map to minimize the number of HAPI calls - TMap StringHandleToStringMap; - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - const HAPI_StringHandle& CurrentSH = StringHandles[Idx]; - if (CurrentSH < 0) - { - OutData[Idx] = TEXT(""); - continue; - } - - FString* FoundString = StringHandleToStringMap.Find(CurrentSH); - if (FoundString) - { - OutData[Idx] = *FoundString; - } - else - { - FString HapiString = TEXT(""); - FHoudiniEngineString::ToFString(CurrentSH, HapiString); - - StringHandleToStringMap.Add(CurrentSH, HapiString); - OutData[Idx] = HapiString; - } - } - - return true; -} - -bool -FHoudiniEngineUtils::HapiCheckAttributeExists( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const char * AttribName, HAPI_AttributeOwner Owner) -{ - if (Owner == HAPI_ATTROWNER_INVALID) - { - for (int32 OwnerIdx = 0; OwnerIdx < HAPI_ATTROWNER_MAX; OwnerIdx++) - { - if (HapiCheckAttributeExists(GeoId, PartId, AttribName, (HAPI_AttributeOwner)OwnerIdx)) - { - return true; - } - } - } - else - { - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, AttribName, Owner, &AttribInfo), false); - - return AttribInfo.exists; - } - - return false; -} - -bool -FHoudiniEngineUtils::IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType) -{ - // Check for - // - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE (unreal_instance) on points/detail - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT)) - { - OutInstancerType = EHoudiniInstancerType::AttributeInstancer; - return true; - } - - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL)) - { - OutInstancerType = EHoudiniInstancerType::AttributeInstancer; - return true; - } - - // - HAPI_UNREAL_ATTRIB_INSTANCE (instance) on points - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT)) - { - OutInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsString( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const FString& DefaultValue, - FString& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParamInfo; - FHoudiniApi::ParmInfo_Init(&FoundParamInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParamInfo), false); - - // .. and value - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), NodeId, false, - &StringHandle, FoundParamInfo.stringValuesIndex, 1), false); - - // Convert the string handle to FString - return FHoudiniEngineString::ToFString(StringHandle, OutValue); -} - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const int32& DefaultValue, - int32& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParmInfo; - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), false); - - // .. and value - int32 Value = DefaultValue; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), NodeId, &Value, - FoundParmInfo.intValuesIndex, 1), false); - - OutValue = Value; - - return true; -} - - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsFloat( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const float& DefaultValue, - float& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParmInfo; - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), false); - - // .. and value - float Value = DefaultValue; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), NodeId, &Value, - FoundParmInfo.floatValuesIndex, 1), false); - - OutValue = Value; - - return true; -} - -HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo) -{ - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo); - if (NodeInfo.parmCount <= 0) - return -1; - - HAPI_ParmId ParmId = HapiFindParameterByNameOrTag(NodeInfo.id, ParmName); - if ((ParmId < 0) || (ParmId >= NodeInfo.parmCount)) - return -1; - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), -1); - - return ParmId; -} - - -HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName) -{ - // First, try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), -1); - - if (ParmId >= 0) - return ParmId; - - // Second, try to find it by its tag - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmWithTag( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), -1); - - if (ParmId >= 0) - return ParmId; - - return -1; -} - -int32 -FHoudiniEngineUtils::HapiGetAttributeOfType( - const HAPI_NodeId& GeoId, - const HAPI_NodeId& PartId, - const HAPI_AttributeOwner& AttributeOwner, - const HAPI_AttributeTypeInfo& AttributeType, - TArray< HAPI_AttributeInfo >& MatchingAttributesInfo, - TArray< FString >& MatchingAttributesName) -{ - int32 NumberOfAttributeFound = 0; - - // Get the part infos - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, &PartInfo), NumberOfAttributeFound); - - // Get All attribute names for that part - int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; - - TArray AttribNameSHArray; - AttribNameSHArray.SetNum(nAttribCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, AttributeOwner, - AttribNameSHArray.GetData(), nAttribCount), NumberOfAttributeFound); - - // Iterate on all the attributes, and get their part infos to get their type - for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) - { - // Get the name ... - FString HapiString = TEXT(""); - FHoudiniEngineString::ToFString(AttribNameSHArray[Idx], HapiString); - - // ... then the attribute info - HAPI_AttributeInfo AttrInfo; - FHoudiniApi::AttributeInfo_Init(&AttrInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, TCHAR_TO_UTF8(*HapiString), - AttributeOwner, &AttrInfo)) - continue; - - if (!AttrInfo.exists) - continue; - - // ... check the type - if (AttrInfo.typeInfo != AttributeType) - continue; - - MatchingAttributesInfo.Add(AttrInfo); - MatchingAttributesName.Add(HapiString); - - NumberOfAttributeFound++; - } - - return NumberOfAttributeFound; -} - -HAPI_PartInfo -FHoudiniEngineUtils::ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo) -{ - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - - PartInfo.id = InHPartInfo.PartId; - //PartInfo.nameSH = InHPartInfo.Name; - - switch (InHPartInfo.Type) - { - case EHoudiniPartType::Mesh: - PartInfo.type = HAPI_PARTTYPE_MESH; - break; - case EHoudiniPartType::Curve: - PartInfo.type = HAPI_PARTTYPE_CURVE; - break; - case EHoudiniPartType::Instancer: - PartInfo.type = HAPI_PARTTYPE_INSTANCER; - break; - case EHoudiniPartType::Volume: - PartInfo.type = HAPI_PARTTYPE_VOLUME; - break; - default: - case EHoudiniPartType::Invalid: - PartInfo.type = HAPI_PARTTYPE_INVALID; - break; - } - - PartInfo.faceCount = InHPartInfo.FaceCount; - PartInfo.vertexCount = InHPartInfo.VertexCount; - PartInfo.pointCount = InHPartInfo.PointCount; - - PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = InHPartInfo.PointAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX] = InHPartInfo.VertexAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_PRIM] = InHPartInfo.PrimitiveAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL] = InHPartInfo.DetailAttributeCounts; - - PartInfo.isInstanced = InHPartInfo.bIsInstanced; - - PartInfo.instancedPartCount = InHPartInfo.InstancedPartCount; - PartInfo.instanceCount = InHPartInfo.InstanceCount; - - PartInfo.hasChanged = InHPartInfo.bHasChanged; - - return PartInfo; -} - -int32 -FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - TArray< FHoudiniMeshSocket >& AllSockets, - const bool& isPackedPrim) -{ - int32 FoundSocketCount = 0; - - // Attributes we are interested in. - // Position - TArray Positions; - HAPI_AttributeInfo AttribInfoPositions; - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bool bHasRotation = false; - TArray Rotations; - HAPI_AttributeInfo AttribInfoRotations; - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bool bHasScale = false; - TArray Scales; - HAPI_AttributeInfo AttribInfoScales; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // Socket Name - bool bHasNames = false; - TArray Names; - HAPI_AttributeInfo AttribInfoNames; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bool bHasActors = false; - TArray Actors; - HAPI_AttributeInfo AttribInfoActors; - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bool bHasTags = false; - TArray Tags; - HAPI_AttributeInfo AttribInfoTags; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - - // Lambda function for creating the socket and adding it to the array - // Shared between the by Attribute / by Group methods - auto AddSocketToArray = [&](const int32& PointIdx) - { - FHoudiniMeshSocket CurrentSocket; - FVector currentPosition = FVector::ZeroVector; - if (Positions.IsValidIndex(PointIdx * 3 + 2)) - { - currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - FVector currentScale = FVector::OneVector; - if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) - { - currentScale.X = Scales[PointIdx * 3]; - currentScale.Y = Scales[PointIdx * 3 + 2]; - currentScale.Z = Scales[PointIdx * 3 + 1]; - } - - FQuat currentRotation = FQuat::Identity; - if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) - { - currentRotation.X = Rotations[PointIdx * 4]; - currentRotation.Y = Rotations[PointIdx * 4 + 2]; - currentRotation.Z = Rotations[PointIdx * 4 + 1]; - currentRotation.W = -Rotations[PointIdx * 4 + 3]; - } - - if (bHasNames && Names.IsValidIndex(PointIdx)) - CurrentSocket.Name = Names[PointIdx]; - - if (bHasActors && Actors.IsValidIndex(PointIdx)) - CurrentSocket.Actor = Actors[PointIdx]; - - if (bHasTags && Tags.IsValidIndex(PointIdx)) - CurrentSocket.Tag = Tags[PointIdx]; - - // If the scale attribute wasn't set on all socket, we might end up - // with a zero scale socket, avoid that. - if (currentScale == FVector::ZeroVector) - currentScale = FVector::OneVector; - - CurrentSocket.Transform.SetLocation(currentPosition); - CurrentSocket.Transform.SetRotation(currentRotation); - CurrentSocket.Transform.SetScale3D(currentScale); - - // We want to make sure we're not adding the same socket multiple times - AllSockets.AddUnique(CurrentSocket); - - FoundSocketCount++; - - return true; - }; - - - // Lambda function for reseting the arrays/attributes - auto ResetArraysAndAttr = [&]() - { - // Position - Positions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bHasRotation = false; - Rotations.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bHasScale = false; - Scales.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // Socket Name - bHasNames = false; - Names.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bHasActors = false; - Actors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bHasTags = false; - Tags.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - }; - - //------------------------------------------------------------------------- - // FIND SOCKETS BY DETAIL ATTRIBUTES - //------------------------------------------------------------------------- - - int32 SocketIdx = 0; - bool HasSocketAttributes = true; - while (HasSocketAttributes) - { - // Build the current socket's prefix - FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(SocketIdx); - - // Reset the arrays and attributes - ResetArraysAndAttr(); - - // Retrieve position data. - FString SocketPosAttr = SocketAttrPrefix + TEXT("_pos"); - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, TCHAR_TO_ANSI(*SocketPosAttr), - AttribInfoPositions, Positions, 0, HAPI_ATTROWNER_DETAIL)) - break; - - if (!AttribInfoPositions.exists) - { - // No need to keep looking for socket attributes - HasSocketAttributes = false; - break; - } - - // Retrieve rotation data. - FString SocketRotAttr = SocketAttrPrefix + TEXT("_rot"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketRotAttr), AttribInfoRotations, Rotations, 0, HAPI_ATTROWNER_DETAIL)) - bHasRotation = true; - - // Retrieve scale data. - FString SocketScaleAttr = SocketAttrPrefix + TEXT("_scale"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketScaleAttr), AttribInfoScales, Scales, 0, HAPI_ATTROWNER_DETAIL)) - bHasScale = true; - - // Retrieve mesh socket names. - FString SocketNameAttr = SocketAttrPrefix + TEXT("_name"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketNameAttr), AttribInfoNames, Names)) - bHasNames = true; - - // Retrieve mesh socket actor. - FString SocketActorAttr = SocketAttrPrefix + TEXT("_actor"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketActorAttr), AttribInfoActors, Actors)) - bHasActors = true; - - // Retrieve mesh socket tags. - FString SocketTagAttr = SocketAttrPrefix + TEXT("_tag"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketTagAttr), AttribInfoTags, Tags)) - bHasTags = true; - - // Add the socket to the array - AddSocketToArray(0); - - // Try to find the next socket - SocketIdx++; - } - - return FoundSocketCount; -} - - -int32 -FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - TArray& AllSockets, - const bool& isPackedPrim) -{ - // Attributes we are interested in. - // Position - TArray Positions; - HAPI_AttributeInfo AttribInfoPositions; - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bool bHasRotation = false; - TArray Rotations; - HAPI_AttributeInfo AttribInfoRotations; - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bool bHasScale = false; - TArray Scales; - HAPI_AttributeInfo AttribInfoScales; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // We can also get the sockets rotation from the normal - bool bHasNormals = false; - TArray Normals; - HAPI_AttributeInfo AttribInfoNormals; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Socket Name - bool bHasNames = false; - TArray Names; - HAPI_AttributeInfo AttribInfoNames; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bool bHasActors = false; - TArray Actors; - HAPI_AttributeInfo AttribInfoActors; - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bool bHasTags = false; - TArray Tags; - HAPI_AttributeInfo AttribInfoTags; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - - // Lambda function for creating the socket and adding it to the array - // Shared between the by Attribute / by Group methods - int32 FoundSocketCount = 0; - auto AddSocketToArray = [&](const int32& PointIdx) - { - FHoudiniMeshSocket CurrentSocket; - FVector currentPosition = FVector::ZeroVector; - if (Positions.IsValidIndex(PointIdx * 3 + 2)) - { - currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - FVector currentScale = FVector::OneVector; - if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) - { - currentScale.X = Scales[PointIdx * 3]; - currentScale.Y = Scales[PointIdx * 3 + 2]; - currentScale.Z = Scales[PointIdx * 3 + 1]; - } - - FQuat currentRotation = FQuat::Identity; - if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) - { - currentRotation.X = Rotations[PointIdx * 4]; - currentRotation.Y = Rotations[PointIdx * 4 + 2]; - currentRotation.Z = Rotations[PointIdx * 4 + 1]; - currentRotation.W = -Rotations[PointIdx * 4 + 3]; - } - else if (bHasNormals && Normals.IsValidIndex(PointIdx * 3 + 2)) - { - FVector vNormal; - vNormal.X = Normals[PointIdx * 3]; - vNormal.Y = Normals[PointIdx * 3 + 2]; - vNormal.Z = Normals[PointIdx * 3 + 1]; - - if (vNormal != FVector::ZeroVector) - currentRotation = FQuat::FindBetween(FVector::UpVector, vNormal); - } - - if (bHasNames && Names.IsValidIndex(PointIdx)) - CurrentSocket.Name = Names[PointIdx]; - - if (bHasActors && Actors.IsValidIndex(PointIdx)) - CurrentSocket.Actor = Actors[PointIdx]; - - if (bHasTags && Tags.IsValidIndex(PointIdx)) - CurrentSocket.Tag = Tags[PointIdx]; - - // If the scale attribute wasn't set on all socket, we might end up - // with a zero scale socket, avoid that. - if (currentScale == FVector::ZeroVector) - currentScale = FVector::OneVector; - - CurrentSocket.Transform.SetLocation(currentPosition); - CurrentSocket.Transform.SetRotation(currentRotation); - CurrentSocket.Transform.SetScale3D(currentScale); - - // We want to make sure we're not adding the same socket multiple times - AllSockets.AddUnique(CurrentSocket); - - FoundSocketCount++; - - return true; - }; - - - // Lambda function for reseting the arrays/attributes - auto ResetArraysAndAttr = [&]() - { - // Position - Positions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bHasRotation = false; - Rotations.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bHasScale = false; - Scales.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // When using socket groups, we can also get the sockets rotation from the normal - bHasNormals = false; - Normals.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Socket Name - bHasNames = false; - Names.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bHasActors = false; - Actors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bHasTags = false; - Tags.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - }; - - //------------------------------------------------------------------------- - // FIND SOCKETS BY POINT GROUPS - //------------------------------------------------------------------------- - - // Get object / geo group memberships for primitives. - TArray GroupNames; - if (!FHoudiniEngineUtils::HapiGetGroupNames( - GeoId, PartId, HAPI_GROUPTYPE_POINT, isPackedPrim, GroupNames)) - { - HOUDINI_LOG_MESSAGE(TEXT("GetMeshSocketList: Geo [%d] Part [%d] non-fatal error reading point group names"), GeoId, PartId); - } - - // First, we want to make sure we have at least one socket group before continuing - bool bHasSocketGroup = false; - for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) - { - const FString & GroupName = GroupNames[GeoGroupNameIdx]; - if (GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) - || GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) - { - bHasSocketGroup = true; - break; - } - } - - if (!bHasSocketGroup) - return FoundSocketCount; - - // Get the part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PartInfo)) - return false; - - // Reset the data arrays and attributes - ResetArraysAndAttr(); - - // Retrieve position data. - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, Positions)) - return false; - - // Retrieve rotation data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_ROTATION, AttribInfoRotations, Rotations)) - bHasRotation = true; - - // Retrieve normal data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, Normals)) - bHasNormals = true; - - // Retrieve scale data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_SCALE, AttribInfoScales, Scales)) - bHasScale = true; - - // Retrieve mesh socket names. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, AttribInfoNames, Names)) - bHasNames = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD, AttribInfoNames, Names)) - bHasNames = true; - - // Retrieve mesh socket actor. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR, AttribInfoActors, Actors)) - bHasActors = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD, AttribInfoActors, Actors)) - bHasActors = true; - - // Retrieve mesh socket tags. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, AttribInfoTags, Tags)) - bHasTags = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD, AttribInfoTags, Tags)) - bHasTags = true; - - // Extracting Sockets vertices - for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) - { - const FString & GroupName = GroupNames[GeoGroupNameIdx]; - if (!GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) - && !GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) - continue; - - bool AllEquals = false; - TArray< int32 > PointGroupMembership; - FHoudiniEngineUtils::HapiGetGroupMembership( - GeoId, PartInfo, HAPI_GROUPTYPE_POINT, GroupName, PointGroupMembership, AllEquals); - - // Go through all primitives. - for (int32 PointIdx = 0; PointIdx < PointGroupMembership.Num(); ++PointIdx) - { - if (PointGroupMembership[PointIdx] == 0) - { - if (AllEquals) - break; - else - continue; - } - - // Add the corresponding socket to the array - AddSocketToArray(PointIdx); - } - } - - return FoundSocketCount; -} - -bool -FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( - UStaticMesh* StaticMesh, - TArray& AllSockets, - const bool& CleanImportSockets) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Remove the sockets from the previous cook! - if (CleanImportSockets) - { - StaticMesh->Sockets.RemoveAll([=](UStaticMeshSocket* Socket) { return Socket ? Socket->bSocketCreatedAtImport : true; }); - } - - if (AllSockets.Num() <= 0) - return true; - - // Having sockets with empty names can lead to various issues, so we'll create one now - for (int32 Idx = 0; Idx < AllSockets.Num(); ++Idx) - { - // Assign the unnamed sockets with default names - if (AllSockets[Idx].Name.IsEmpty()) - AllSockets[Idx].Name = TEXT("Socket ") + FString::FromInt(Idx); - } - - // ensure the socket names are unique. (Unreal will use the first socket if multiple socket have the same name) - for (int32 Idx_i = 0; Idx_i < AllSockets.Num(); ++Idx_i) - { - int32 Count = 0; - for (int32 Idx_j = Idx_i + 1; Idx_j < AllSockets.Num(); ++Idx_j) - { - if (AllSockets[Idx_i].Name.Equals(AllSockets[Idx_j].Name)) - { - Count += 1; - AllSockets[Idx_j].Name = AllSockets[Idx_j].Name + "_" + FString::FromInt(Count); - } - } - } - - // Clear all the sockets of the output static mesh. - StaticMesh->Sockets.Empty(); - - for (int32 nSocket = 0; nSocket < AllSockets.Num(); nSocket++) - { - // Create a new Socket - UStaticMeshSocket* Socket = NewObject(StaticMesh); - if (!Socket || Socket->IsPendingKill()) - continue; - - Socket->RelativeLocation = AllSockets[nSocket].Transform.GetLocation(); - Socket->RelativeRotation = FRotator(AllSockets[nSocket].Transform.GetRotation()); - Socket->RelativeScale = AllSockets[nSocket].Transform.GetScale3D(); - Socket->SocketName = FName(*AllSockets[nSocket].Name); - - // Socket Tag - FString Tag; - if (!AllSockets[nSocket].Tag.IsEmpty()) - Tag = AllSockets[nSocket].Tag; - - // The actor will be stored temporarily in the socket's Tag as we need a StaticMeshComponent to add an actor to the socket - Tag += TEXT("|") + AllSockets[nSocket].Actor; - - Socket->Tag = Tag; - Socket->bSocketCreatedAtImport = true; - - StaticMesh->Sockets.Add(Socket); - } - - return true; -} - -bool -FHoudiniEngineUtils::CreateAttributesFromTags( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const TArray& Tags ) -{ - if (Tags.Num() <= 0) - return false; - - HAPI_Result Result = HAPI_RESULT_FAILURE; - - // Get the destination part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); - - bool NeedToCommitGeo = false; - for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) - { - FString TagString; - Tags[TagIdx].ToString(TagString); - SanitizeHAPIVariableName(TagString); - - // Create a primitive attribute for the tag - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - AttributeInfo.count = PartInfo.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; - - FString AttributeName = TEXT(HAPI_UNREAL_ATTRIB_TAG_PREFIX) + FString::FromInt(TagIdx); - AttributeName.RemoveSpacesInline(); - - Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo); - - if (Result != HAPI_RESULT_SUCCESS) - continue; - - TArray TagStr; - TagStr.Add(FHoudiniEngineUtils::ExtractRawString(TagString)); - - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo, - TagStr.GetData(), 0, AttributeInfo.count); - - if (HAPI_RESULT_SUCCESS == Result) - NeedToCommitGeo = true; - - // Free memory for allocated by ExtractRawString - FHoudiniEngineUtils::FreeRawStringMemory(TagStr); - } - - return NeedToCommitGeo; -} - -bool -FHoudiniEngineUtils::CreateGroupsFromTags( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const TArray& Tags ) -{ - if (Tags.Num() <= 0) - return true; - - HAPI_Result Result = HAPI_RESULT_FAILURE; - - // Get the destination part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); - - bool NeedToCommitGeo = false; - for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) - { - FString TagString; - Tags[TagIdx].ToString(TagString); - SanitizeHAPIVariableName(TagString); - - const char * TagStr = FHoudiniEngineUtils::ExtractRawString(TagString); - - // Create a primitive group for this tag - if ( HAPI_RESULT_SUCCESS == FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, TagStr) ) - { - // Set the group's Memberships - TArray GroupArray; - GroupArray.Init(1, PartInfo.faceCount); - - if ( HAPI_RESULT_SUCCESS == FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_GROUPTYPE_PRIM, TagStr, - GroupArray.GetData(), 0, PartInfo.faceCount) ) - { - NeedToCommitGeo = true; - } - } - - // Free memory allocated by ExtractRawString() - FHoudiniEngineUtils::FreeRawStringMemory(TagStr); - } - - return NeedToCommitGeo; -} - - -bool -FHoudiniEngineUtils::SanitizeHAPIVariableName(FString& String) -{ - // Only keep alphanumeric characters, underscores - // Also, if the first character is a digit, append an underscore at the beginning - TArray& StrArray = String.GetCharArray(); - if (StrArray.Num() <= 0) - return false; - - for (auto& CurChar : StrArray) - { - const bool bIsValid = (CurChar >= TEXT('A') && CurChar <= TEXT('Z')) - || (CurChar >= TEXT('a') && CurChar <= TEXT('z')) - || (CurChar >= TEXT('0') && CurChar <= TEXT('9')) - || (CurChar == TEXT('_')) || (CurChar == TEXT('\0')); - - if(bIsValid) - continue; - - CurChar = TEXT('_'); - } - - if (StrArray.Num() > 0) - { - TCHAR FirstChar = StrArray[0]; - if (FirstChar >= TEXT('0') && FirstChar <= TEXT('9')) - StrArray.Insert(TEXT('_'), 0); - } - - return true; -} - -bool -FHoudiniEngineUtils::GetUnrealTagAttributes( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags) -{ - FString TagAttribBase = TEXT("unreal_tag_"); - bool bAttributeFound = true; - int32 TagIdx = 0; - while (bAttributeFound) - { - FString CurrentTagAttr = TagAttribBase + FString::FromInt(TagIdx++); - bAttributeFound = HapiCheckAttributeExists(GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), HAPI_ATTROWNER_PRIM); - if (!bAttributeFound) - break; - - // found the unreal_tag_X attribute, get its value and add it to the array - FString TagValue = FString(); - - // Create an AttributeInfo - { - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) - { - TagValue = StringData[0]; - } - } - - FName NameTag = *TagValue; - OutTags.Add(NameTag); - } - - return true; -} - - -int32 -FHoudiniEngineUtils::GetPropertyAttributeList( - const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundPropertyAttributes) -{ - // Get all the detail uprop attributes on the HGPO - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive uprop attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_PRIM); - - return FoundCount; -} - - -int32 -FHoudiniEngineUtils::GetGenericAttributeList( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FString& InGenericAttributePrefix, - TArray& OutFoundAttributes, - const HAPI_AttributeOwner& AttributeOwner, - const int32& InAttribIndex) -{ - // Get the part info to get the attribute counts for the specified owner - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), InGeoNodeId, InPartId, &PartInfo), false); - - int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; - - // Get all attribute names for that part - TArray AttribNameSHArray; - AttribNameSHArray.SetNum(nAttribCount); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, AttributeOwner, - AttribNameSHArray.GetData(), nAttribCount)) - { - return 0; - } - - // For everything but detail attribute, - // if an attribute index was specified, only extract the attribute value for that specific index - // if not, extract all values for the given attribute - bool HandleSplit = false; - int32 AttribIndex = -1; - if ((AttributeOwner != HAPI_ATTROWNER_DETAIL) && (InAttribIndex != -1)) - { - // The index has already been specified so we'll use it - HandleSplit = true; - AttribIndex = InAttribIndex; - } - - int32 FoundCount = 0; - for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) - { - int32 AttribNameSH = (int32)AttribNameSHArray[Idx]; - FString AttribName = TEXT(""); - FHoudiniEngineString::ToFString(AttribNameSH, AttribName); - if (!AttribName.StartsWith(InGenericAttributePrefix, ESearchCase::IgnoreCase)) - continue; - - // Get the Attribute Info - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), AttributeOwner, &AttribInfo)) - { - // failed to get that attribute's info - continue; - } - - int32 AttribStart = 0; - int32 AttribCount = AttribInfo.count; - if (HandleSplit) - { - // For split primitives, we need to only get only one value for the proper split prim - // Make sure that the split index is valid - if (AttribIndex >= 0 && AttribIndex < AttribInfo.count) - { - AttribStart = AttribIndex; - AttribCount = 1; - } - } - - // - FHoudiniGenericAttribute CurrentGenericAttribute; - // Remove the generic attribute prefix - CurrentGenericAttribute.AttributeName = AttribName.Right(AttribName.Len() - InGenericAttributePrefix.Len()); - - CurrentGenericAttribute.AttributeOwner = (EAttribOwner)AttribInfo.owner; - - // Get the attribute type and tuple size - CurrentGenericAttribute.AttributeType = (EAttribStorageType)AttribInfo.storage; - CurrentGenericAttribute.AttributeCount = AttribInfo.count; - CurrentGenericAttribute.AttributeTupleSize = AttribInfo.tupleSize; - - if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT64) - { - // Initialize the value array - CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloat64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, 0, - CurrentGenericAttribute.DoubleValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT) - { - // Initialize the value array - TArray FloatValues; - FloatValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, FloatValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to double - CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - for (int32 n = 0; n < FloatValues.Num(); n++) - CurrentGenericAttribute.DoubleValues[n] = (double)FloatValues[n]; - - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT64) - { - // Initialize the value array - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, CurrentGenericAttribute.IntValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT) - { - // Initialize the value array - TArray IntValues; - IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, IntValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to int64 - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - for (int32 n = 0; n < IntValues.Num(); n++) - CurrentGenericAttribute.IntValues[n] = (int64)IntValues[n]; - - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - // Initialize a string handle array - TArray HapiSHArray; - HapiSHArray.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the string handle(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - HapiSHArray.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to FString - CurrentGenericAttribute.StringValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - for (int32 IdxSH = 0; IdxSH < HapiSHArray.Num(); IdxSH++) - { - FString CurrentString; - FHoudiniEngineString::ToFString(HapiSHArray[IdxSH], CurrentString); - CurrentGenericAttribute.StringValues[IdxSH] = CurrentString; - } - } - else - { - // Unsupported type, skipping! - continue; - } - - // We can add the UPropertyAttribute to the array - OutFoundAttributes.Add(CurrentGenericAttribute); - FoundCount++; - } - - return FoundCount; -} - - -void -FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject( - UObject* InObject, const FHoudiniGeoPartObject& InHGPO) -{ - if (!InObject || InObject->IsPendingKill()) - return; - - // Get the list of all the Properties to modify from the HGPO's attributes - TArray PropertiesAttributesToModify; - if (!FHoudiniEngineUtils::GetPropertyAttributeList(InHGPO, PropertiesAttributesToModify)) - return; - - // Iterate over the found Property attributes - for (auto CurrentPropAttribute : PropertiesAttributesToModify) - { - // Get the current Property Attribute - const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; - if (CurrentPropertyName.IsEmpty()) - continue; - - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropertyName, *ClassName, *ObjectName); - } -} - - -void -FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const FString& Key, const FString& Value) -{ - if (!Package || Package->IsPendingKill()) - return; - - UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) - MetaData->SetValue(Object, *Key, *Value); -} - - -bool -FHoudiniEngineUtils::AddLevelPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - ULevel* InLevel, - const int32& InCount) -{ - if (InNodeId < 0 || InCount <= 0) - return false; - - if (!InLevel || InLevel->IsPendingKill()) - return false; - - // Extract the level path from the level - FString LevelPath = InLevel->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = InCount; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(InCount); - for (int32 Idx = 0; Idx < InCount; ++Idx) - { - PrimitiveAttrs[Idx] = LevelPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} - - -bool -FHoudiniEngineUtils::AddActorPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - AActor* InActor, - const int32& InCount) -{ - if (InNodeId < 0 || InCount <= 0) - return false; - - if (!InActor || InActor->IsPendingKill()) - return false; - - // Extract the actor path - FString ActorPath = InActor->GetPathName(); - - // Get name of attribute used for Actor path - std::string MarshallingAttributeActorPath = HAPI_UNREAL_ATTRIB_ACTOR_PATH; - - // Marshall in Actor path. - HAPI_AttributeInfo AttributeInfoActorPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoActorPath); - AttributeInfoActorPath.count = InCount; - AttributeInfoActorPath.tupleSize = 1; - AttributeInfoActorPath.exists = true; - AttributeInfoActorPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoActorPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoActorPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string ActorPathCStr = TCHAR_TO_ANSI(*ActorPath); - const char* ActorPathCStrRaw = ActorPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(InCount); - for (int32 Idx = 0; Idx < InCount; ++Idx) - { - PrimitiveAttrs[Idx] = ActorPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, - MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoActorPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_actor_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} - - -bool -FHoudiniEngineUtils::ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx) -{ - const TArray< FVector2D > & LightmapUVs = RawMesh.WedgeTexCoords[LightmapSourceIdx]; - const TArray< uint32 > & Indices = RawMesh.WedgeIndices; - - if (LightmapUVs.Num() != Indices.Num()) - { - // This is invalid raw mesh; by design we consider that it contains invalid lightmap faces. - return true; - } - - for (int32 Idx = 0; Idx < Indices.Num(); Idx += 3) - { - const FVector2D & uv0 = LightmapUVs[Idx + 0]; - const FVector2D & uv1 = LightmapUVs[Idx + 1]; - const FVector2D & uv2 = LightmapUVs[Idx + 2]; - - if (uv0 == uv1 && uv1 == uv2) - { - // Detect invalid lightmap face, can stop. - return true; - } - } - - // Otherwise there are no invalid lightmap faces. - return false; -} - -void -FHoudiniEngineUtils::CreateSlateNotification( - const FString& NotificationString, const float& NotificationExpire, const float& NotificationFadeOut ) -{ -#if WITH_EDITOR - // Trying to display SlateNotifications while in a background thread will crash UE - if (!IsInGameThread() && !IsInSlateThread() && !IsInAsyncLoadingThread()) - return; - - // Check whether we want to display Slate notifications. - bool bDisplaySlateCookingNotifications = true; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (!bDisplaySlateCookingNotifications) - return; - - FText NotificationText = FText::FromString(NotificationString); - FNotificationInfo Info(NotificationText); - - Info.bFireAndForget = true; - Info.FadeOutDuration = NotificationFadeOut; - Info.ExpireDuration = NotificationExpire; - - TSharedPtr HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); - if (HoudiniBrush.IsValid()) - Info.Image = HoudiniBrush.Get(); - - FSlateNotificationManager::Get().AddNotification(Info); -#endif - - return; -} - -FString -FHoudiniEngineUtils::GetHoudiniEnginePluginDir() -{ - FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(EnginePluginDir)) - return EnginePluginDir; - - FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(ProjectPluginDir)) - return ProjectPluginDir; - - TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); - FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; - if (FPaths::DirectoryExists(PluginBaseDir)) - return PluginBaseDir; - - HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); - - return EnginePluginDir; -} - - -HAPI_Result -FHoudiniEngineUtils::CreateNode( - const HAPI_NodeId& InParentNodeId, - const FString& InOperatorName, - const FString& InNodeLabel, - const HAPI_Bool& bInCookOnCreation, - HAPI_NodeId* OutNewNodeId) -{ - // Call HAPI::CreateNode - HAPI_Result Result = FHoudiniApi::CreateNode( - FHoudiniEngine::Get().GetSession(), - InParentNodeId, TCHAR_TO_UTF8(*InOperatorName), TCHAR_TO_UTF8(*InNodeLabel), bInCookOnCreation, OutNewNodeId); - - // Return now if CreateNode fialed - if (Result != HAPI_RESULT_SUCCESS) - return Result; - - // Loop on the cook_state status until it's ready - int CurrentStatus = HAPI_State::HAPI_STATE_STARTING_LOAD; - while (CurrentStatus > HAPI_State::HAPI_STATE_MAX_READY_STATE) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), - HAPI_StatusType::HAPI_STATUS_COOK_STATE, &CurrentStatus)) - { - // Exit the loop if GetStatus somehow fails - break; - } - } - - if (CurrentStatus == HAPI_STATE_READY_WITH_FATAL_ERRORS) - { - // Fatal errors - failed - HOUDINI_LOG_ERROR(TEXT("Failed to create node %s - %s"), *InOperatorName, *InNodeLabel); - return HAPI_RESULT_FAILURE; - } - else if (CurrentStatus == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // Mention the errors - still return success - HOUDINI_LOG_WARNING(TEXT("Cook errors when creating node %s - %s"), *InOperatorName, *InNodeLabel); - } - - return HAPI_RESULT_SUCCESS; -} - - -int32 -FHoudiniEngineUtils::HapiGetCookCount(const HAPI_NodeId& InNodeId) -{ - int32 CookCount = -1; - - FHoudiniApi::GetTotalCookCount( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_ANY, true, &CookCount); - - /* - // TODO: - // Use HAPI_GetCookingTotalCount() when available - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - - int32 CookCount = -1; - HAPI_Result Result = FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), InNodeId, &NodeInfo); - - if (Result != HAPI_RESULT_FAILURE) - { - if (NodeInfo.type != HAPI_NODETYPE_OBJ) - { - // For SOP assets, get the cook count straight from the Asset Node - CookCount = NodeInfo.totalCookCount; - } - else - { - // For OBJ nodes, get the cook count from the display geos - // Retrieve information about each object contained within our asset. - TArray< HAPI_ObjectInfo > ObjectInfos; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(InNodeId, ObjectInfos)) - return false; - - for (auto CurrentHapiObjectInfo : ObjectInfos) - { - // Get the Display Geo's info - HAPI_GeoInfo DisplayHapiGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) - { - continue; - } - - HAPI_NodeInfo DisplayNodeInfo; - FHoudiniApi::NodeInfo_Init(&DisplayNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), DisplayHapiGeoInfo.nodeId, &DisplayNodeInfo)) - { - continue; - } - - CookCount += DisplayNodeInfo.totalCookCount; - } - } - } - */ - - return CookCount; -} - -bool -FHoudiniEngineUtils::GetLevelPathAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutLevelPaths, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_level_path - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_LEVEL_PATH, AttributeInfo, OutLevelPaths, 1, InAttributeOwner)) - { - if (OutLevelPaths.Num() > 0) - return true; - } - - OutLevelPaths.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetOutputNameAttribute(const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutOutputNames) -{ - // --------------------------------------------- - // Attribute: unreal_output_name - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, AttributeInfo, OutOutputNames, 1)) - { - if (OutOutputNames.Num() > 0) - return true; - } - - OutOutputNames.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, AttributeInfo, OutOutputNames, 1)) - { - if (OutOutputNames.Num() > 0) - return true; - } - - OutOutputNames.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetTileAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutTileValues, - const HAPI_AttributeOwner& InAttribOwner) -{ - // --------------------------------------------- - // Attribute: tile - // --------------------------------------------- - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, - AttribInfoTile, - OutTileValues, - 0, - InAttribOwner)) - { - if (OutTileValues.Num() > 0) - return true; - } - - OutTileValues.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - TArray& OutBakeFolder, - HAPI_PartId InPartId) -{ - OutBakeFolder.Empty(); - - HAPI_AttributeInfo BakeFolderAttribInfo; - FHoudiniApi::AttributeInfo_Init(&BakeFolderAttribInfo); - if (HapiGetAttributeDataAsString( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_DETAIL)) - { - if (OutBakeFolder.Num() > 0) - return true; - } - - if (HapiGetAttributeDataAsString( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_PRIM)) - { - if (OutBakeFolder.Num() > 0) - return true; - } - - OutBakeFolder.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeActorAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeActorNames, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_bake_actor - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_BAKE_ACTOR, AttributeInfo, OutBakeActorNames, 1, InAttributeOwner)) - { - if (OutBakeActorNames.Num() > 0) - return true; - } - - OutBakeActorNames.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeOutlinerFolders, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_bake_outliner_folder - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, AttributeInfo, OutBakeOutlinerFolders, 1, InAttributeOwner)) - { - if (OutBakeOutlinerFolders.Num() > 0) - return true; - } - - OutBakeOutlinerFolders.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeFolderOverridePath( - const HAPI_NodeId& InGeoId, - FString& OutBakeFolder, - HAPI_PartId InPartId) -{ - FString BakeFolderOverride; - - TArray StringData; - if (GetBakeFolderAttribute(InGeoId, StringData, InPartId)) - { - BakeFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - - if (BakeFolderOverride.StartsWith("Game/")) - { - BakeFolderOverride = "/" + BakeFolderOverride; - } - - FString AbsoluteOverridePath; - if (BakeFolderOverride.StartsWith("/Game/")) - { - const FString RelativePath = FPaths::ProjectContentDir() + BakeFolderOverride.Mid(6, BakeFolderOverride.Len() - 6); - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - if (!BakeFolderOverride.IsEmpty()) - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolderOverride); - } - - // Check Validity of the path - if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) - { - // Only display a warning if the path is invalid, empty is fine - if (!AbsoluteOverridePath.IsEmpty()) - HOUDINI_LOG_WARNING(TEXT("Invalid override bake path: %s"), *BakeFolderOverride); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - OutBakeFolder = HoudiniRuntimeSettings->DefaultBakeFolder; - - return false; - } - - OutBakeFolder = BakeFolderOverride; - return true; -} - -bool -FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) -{ - if (!InActor || !InDesiredLevel) - return false; - - ULevel* PreviousLevel = InActor->GetLevel(); - if (PreviousLevel == InDesiredLevel) - return true; - - UWorld* CurrentWorld = InActor->GetWorld(); - if(CurrentWorld) - CurrentWorld->RemoveActor(InActor, true); - - //Set the outer of Actor to NewLevel - InActor->Rename((const TCHAR *)0, InDesiredLevel); - InDesiredLevel->Actors.Add(InActor); - - return true; -} - -bool -FHoudiniEngineUtils::HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions, const bool& bWaitForCompletion) -{ - // Check for an invalid node id - if (InNodeId < 0) - return false; - - // No Cook Options were specified, use the default one - if (InCookOptions == nullptr) - { - // Use the default cook options - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); - } - else - { - // Use the provided CookOptions - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, InCookOptions), false); - } - - // If we don't need to wait for completion, return now - if (!bWaitForCompletion) - return true; - - // Wait for the cook to finish - HAPI_Result Result = HAPI_RESULT_SUCCESS; - while (true) - { - // Get the current cook status - int Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // The cook has been successful. - return true; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // There was an error while cooking the node. - //FString CookResultString = FHoudiniEngineUtils::GetCookResult(); - //HOUDINI_LOG_ERROR(); - return false; - } - - // We want to yield a bit. - FPlatformProcess::Sleep(0.1f); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineUtils.h" +#include "Misc/StringFormatArg.h" + +#if PLATFORM_WINDOWS + #include "Windows/WindowsHWrapper.h" + + // Of course, Windows defines its own GetGeoInfo, + // So we need to undefine that before including HoudiniApi.h to avoid collision... + #ifdef GetGeoInfo + #undef GetGeoInfo + #endif +#endif + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniEngineString.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInput.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineRuntimeUtils.h" + +#if WITH_EDITOR + #include "SAssetSelectionWidget.h" +#endif + +#include "HAPI/HAPI_Version.h" + +#include "Misc/Paths.h" +#include "Editor/EditorEngine.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "PropertyEditorModule.h" +#include "Modules/ModuleManager.h" +#include "Engine/StaticMeshSocket.h" +#include "Async/Async.h" +#include "BlueprintEditor.h" +#include "Toolkits/AssetEditorManager.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "UObject/MetaData.h" +#include "RawMesh.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Interfaces/IPluginManager.h" +//#include "Kismet/BlueprintEditor.h" +#include "SSCSEditor.h" +#include "Engine/WorldComposition.h" + +#if WITH_EDITOR + #include "Interfaces/IMainFrameModule.h" +#endif + +#include + +#include "AssetRegistryModule.h" +#include "FileHelpers.h" +#include "Factories/WorldFactory.h" +#include "HAL/FileManager.h" + +#if WITH_EDITOR + #include "EditorModeManager.h" + #include "EditorModes.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// HAPI_Result strings +const FString kResultStringSuccess(TEXT("Success")); +const FString kResultStringFailure(TEXT("Generic Failure")); +const FString kResultStringAlreadyInitialized(TEXT("Already Initialized")); +const FString kResultStringNotInitialized(TEXT("Not Initialized")); +const FString kResultStringCannotLoadFile(TEXT("Unable to Load File")); +const FString kResultStringParmSetFailed(TEXT("Failed Setting Parameter")); +const FString kResultStringInvalidArgument(TEXT("Invalid Argument")); +const FString kResultStringCannotLoadGeo(TEXT("Uneable to Load Geometry")); +const FString kResultStringCannotGeneratePreset(TEXT("Uneable to Generate Preset")); +const FString kResultStringCannotLoadPreset(TEXT("Uneable to Load Preset")); +const FString kResultStringAssetDefAlrealdyLoaded(TEXT("Asset definition already loaded")); +const FString kResultStringNoLicenseFound(TEXT("No License Found")); +const FString kResultStringDisallowedNCLicenseFound(TEXT("Disallowed Non Commercial License found")); +const FString kResultStringDisallowedNCAssetWithCLicense(TEXT("Disallowed Non Commercial Asset With Commercial License")); +const FString kResultStringDisallowedNCAssetWithLCLicense(TEXT("Disallowed Non Commercial Asset With Limited Commercial License")); +const FString kResultStringDisallowedLCAssetWithCLicense(TEXT("Disallowed Limited Commercial Asset With Commercial License")); +const FString kResultStringDisallowedHengineIndieWith3PartyPlugin(TEXT("Disallowed Houdini Engine Indie With 3rd Party Plugin")); +const FString kResultStringAssetInvalid(TEXT("Invalid Asset")); +const FString kResultStringNodeInvalid(TEXT("Invalid Node")); +const FString kResultStringUserInterrupted(TEXT("User Interrupt")); +const FString kResultStringInvalidSession(TEXT("Invalid Session")); +const FString kResultStringUnknowFailure(TEXT("Unknown Failure")); + +#define DebugTextLine TEXT("===================================") + +const int32 +FHoudiniEngineUtils::PackageGUIDComponentNameLength = 12; + +const int32 +FHoudiniEngineUtils::PackageGUIDItemNameLength = 8; + +const FString +FHoudiniEngineUtils::GetErrorDescription(HAPI_Result Result) +{ + if (Result == HAPI_RESULT_SUCCESS) + { + return kResultStringSuccess; + } + else + { + switch (Result) + { + case HAPI_RESULT_FAILURE: + { + return kResultStringFailure; + } + + case HAPI_RESULT_ALREADY_INITIALIZED: + { + return kResultStringAlreadyInitialized; + } + + case HAPI_RESULT_NOT_INITIALIZED: + { + return kResultStringNotInitialized; + } + + case HAPI_RESULT_CANT_LOADFILE: + { + return kResultStringCannotLoadFile; + } + + case HAPI_RESULT_PARM_SET_FAILED: + { + return kResultStringParmSetFailed; + } + + case HAPI_RESULT_INVALID_ARGUMENT: + { + return kResultStringInvalidArgument; + } + + case HAPI_RESULT_CANT_LOAD_GEO: + { + return kResultStringCannotLoadGeo; + } + + case HAPI_RESULT_CANT_GENERATE_PRESET: + { + return kResultStringCannotGeneratePreset; + } + + case HAPI_RESULT_CANT_LOAD_PRESET: + { + return kResultStringCannotLoadPreset; + } + + case HAPI_RESULT_ASSET_DEF_ALREADY_LOADED: + { + return kResultStringAssetDefAlrealdyLoaded; + } + + case HAPI_RESULT_NO_LICENSE_FOUND: + { + return kResultStringNoLicenseFound; + } + + case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: + { + return kResultStringDisallowedNCLicenseFound; + } + + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: + { + return kResultStringDisallowedNCAssetWithCLicense; + } + + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: + { + return kResultStringDisallowedNCAssetWithLCLicense; + } + + case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: + { + return kResultStringDisallowedLCAssetWithCLicense; + } + + case HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN: + { + return kResultStringDisallowedHengineIndieWith3PartyPlugin; + } + + case HAPI_RESULT_ASSET_INVALID: + { + return kResultStringAssetInvalid; + } + + case HAPI_RESULT_NODE_INVALID: + { + return kResultStringNodeInvalid; + } + + case HAPI_RESULT_USER_INTERRUPTED: + { + return kResultStringUserInterrupted; + } + + case HAPI_RESULT_INVALID_SESSION: + { + return kResultStringInvalidSession; + } + + default: + { + return kResultStringUnknowFailure; + } + }; + } +} + +const FString +FHoudiniEngineUtils::GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity) +{ + const HAPI_Session* SessionPtr = FHoudiniEngine::Get().GetSession(); + if (!SessionPtr) + { + // No valid session + return FString(TEXT("No valid Houdini Engine session.")); + } + + int32 StatusBufferLength = 0; + HAPI_Result Result = FHoudiniApi::GetStatusStringBufLength( + SessionPtr, status_type, verbosity, &StatusBufferLength); + + if (Result == HAPI_RESULT_INVALID_SESSION) + { + // Let FHoudiniEngine know that the sesion is now invalid to "Stop" the invalid session + // and clean things up + FHoudiniEngine::Get().OnSessionLost(); + } + + if (StatusBufferLength > 0) + { + TArray< char > StatusStringBuffer; + StatusStringBuffer.SetNumZeroed(StatusBufferLength); + FHoudiniApi::GetStatusString( + SessionPtr, status_type, &StatusStringBuffer[0], StatusBufferLength); + + return FString(UTF8_TO_TCHAR(&StatusStringBuffer[0])); + } + + return FString(TEXT("")); +} + +const FString +FHoudiniEngineUtils::GetCookResult() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_MESSAGES); +} + +const FString +FHoudiniEngineUtils::GetCookState() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); +} + +const FString +FHoudiniEngineUtils::GetErrorDescription() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS); +} + +const FString +FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId) +{ + int32 NodeErrorLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeNodeCookResult( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_StatusVerbosity::HAPI_STATUSVERBOSITY_ALL, &NodeErrorLength)) + { + NodeErrorLength = 0; + } + + FString NodeError; + if (NodeErrorLength > 0) + { + TArray NodeErrorBuffer; + NodeErrorBuffer.SetNumZeroed(NodeErrorLength); + FHoudiniApi::GetComposedNodeCookResult( + FHoudiniEngine::Get().GetSession(), &NodeErrorBuffer[0], NodeErrorLength); + + NodeError = FString(UTF8_TO_TCHAR(&NodeErrorBuffer[0])); + } + + return NodeError; +} + +const FString +FHoudiniEngineUtils::GetCookLog(TArray& InHACs) +{ + FString CookLog; + + // Get fetch cook status. + FString CookResult = FHoudiniEngineUtils::GetCookResult(); + if (!CookResult.IsEmpty()) + CookLog += TEXT("Cook Results:\n") + CookResult + TEXT("\n\n"); + + // Add the cook state + FString CookState = FHoudiniEngineUtils::GetCookState(); + if (!CookState.IsEmpty()) + CookLog += TEXT("Cook State:\n") + CookState + TEXT("\n\n"); + + // Error Description + FString Error = FHoudiniEngineUtils::GetErrorDescription(); + if (!Error.IsEmpty()) + CookLog += TEXT("Error Description:\n") + Error + TEXT("\n\n"); + + // Iterates on all the selected HAC and get their node errors + for (auto& HAC : InHACs) + { + if (!HAC || HAC->IsPendingKill()) + continue; + + // Get the node errors, warnings and messages + FString NodeErrors = FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(HAC->GetAssetId()); + if (NodeErrors.IsEmpty()) + continue; + + CookLog += NodeErrors; + } + + if (CookLog.IsEmpty()) + { + // See if a failed HAPI initialization / invalid session is preventing us from getting the cook log + if (!FHoudiniApi::IsHAPIInitialized()) + { + CookLog += TEXT("\n\nThe Houdini Engine API Library (HAPI) has not been initialized properly.\n\n"); + } + else + { + const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) + { + CookLog += TEXT("\n\nThe current Houdini Engine Session is not valid.\n\n"); + } + else if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) + { + CookLog += TEXT("\n\nThe current Houdini Engine Session has not been initialized properly.\n\n"); + } + } + + if (!CookLog.IsEmpty()) + { + CookLog += TEXT("Please try to restart the current Houdini Engine session via File > Restart Houdini Engine Session.\n\n"); + } + else + { + CookLog = TEXT("\n\nThe cook log is empty...\n\n"); + } + } + + return CookLog; +} + +const FString +FHoudiniEngineUtils::GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + FString HelpString = TEXT(""); + if (!HoudiniAssetComponent) + return HelpString; + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HAPI_NodeId AssetId = HoudiniAssetComponent->GetAssetId(); + if (AssetId < 0) + return HelpString; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), HelpString); + + if (!FHoudiniEngineString::ToFString(AssetInfo.helpTextSH, HelpString)) + return HelpString; + + if (HelpString.IsEmpty()) + HelpString = TEXT("No Asset Help Found"); + + return HelpString; +} + +void +FHoudiniEngineUtils::ConvertUnrealString(const FString & UnrealString, std::string & String) +{ + String = TCHAR_TO_UTF8(*UnrealString); +} + +UWorld* +FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreateMissingPackage, bool& bOutCreatedPackage) +{ + AActor* Result = nullptr; + UWorld* PackageWorld = nullptr; + + bOutCreatedPackage = false; + + // Try to load existing UWorld from the tile package path. + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!Package) + Package = LoadPackage(nullptr, *PackagePath, LOAD_None); + if (Package) + { + // If the package is not valid (pending kill) rename it + if (Package->IsPendingKill()) + { + if (bCreateMissingPackage) + { + Package->Rename( + *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); + } + } + else + { + PackageWorld = UWorld::FindWorldInPackage(Package); + } + } + + if (!IsValid(PackageWorld) && bCreateMissingPackage) + { + // The map for this tile does not exist. Create one + UWorldFactory* Factory = NewObject(); + Factory->WorldType = EWorldType::Inactive; // World that is being loaded but not currently edited by editor. + PackageWorld = CastChecked(Factory->FactoryCreateNew(UWorld::StaticClass(), Package, NAME_None, RF_Public | RF_Standalone, NULL, GWarn)); + + if (IsValid(PackageWorld)) + { + PackageWorld->PostEditChange(); + PackageWorld->MarkPackageDirty(); + + if(FPackageName::IsValidLongPackageName(PackagePath)) + { + const FString PackageFilename = FPackageName::LongPackageNameToFilename(PackagePath); + bool bSaved = FEditorFileUtils::SaveLevel(PackageWorld->PersistentLevel, *PackageFilename); + } + + FAssetRegistryModule::AssetCreated(PackageWorld); + + bOutCreatedPackage = true; + } + } + + return PackageWorld; +} + +bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + UWorld* CurrentWorld, + const FString& PackagePath, + bool bCreateMissingPackage, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bOutPackageCreated, + bool& bPackageInWorld) +{ + UWorld* PackageWorld = FindWorldInPackage(PackagePath, bCreateMissingPackage, bOutPackageCreated); + if (!IsValid(PackageWorld)) + return false; + + if (PackageWorld->PersistentLevel == CurrentWorld->PersistentLevel) + { + // The loaded world and the package world is one and the same. + OutWorld = CurrentWorld; + OutLevel = CurrentWorld->PersistentLevel; + bPackageInWorld = true; + return true; + } + + if (CurrentWorld->GetLevels().Contains(PackageWorld->PersistentLevel)) + { + // The package level is loaded into CurrentWorld. + OutWorld = CurrentWorld; + OutLevel = PackageWorld->PersistentLevel; + bPackageInWorld = true; + return true; + } + + // The package level is not loaded at all. Send back the on-disk assets. + OutWorld = PackageWorld; + OutLevel = PackageWorld->PersistentLevel; + bPackageInWorld = false; + return true; +} + +void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) +{ + FString WorldPath = FPaths::GetPath(InWorld->GetPathName()); + IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); + TArray Packages; + Packages.Add(WorldPath); + AssetRegistry.ScanPathsSynchronous(Packages, true); +} + +AActor* FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) +{ + // AActor* NamedActor = FindObject(Outer, *InName, false); + // Find ANY actor in the world matching the given name. + AActor* NamedActor = FindActorInWorld(InWorld, FName(InName)); + OutFoundActor = NamedActor; + bool bShouldRename = false; + if (NamedActor) + { + if (NamedActor->GetClass()->IsChildOf(InClass) && !NamedActor->IsPendingKill()) + { + return NamedActor; + } + else + { + FString Suffix; + bool bShouldUpdateLabel = false; + if (NamedActor->IsPendingKill()) + Suffix = "_pendingkill"; + else + Suffix = "_0"; // A previous actor that had the same name. + const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName+Suffix); + } + } + return nullptr; +} + +void FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) +{ + LogPackageInfo( LoadPackage(nullptr, *InLongPackageName, 0) ); +} + +void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) +{ + HOUDINI_LOG_MESSAGE(DebugTextLine); + HOUDINI_LOG_MESSAGE(TEXT("= LogPackageInfo")); + if (!IsValid(InPackage)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Invalid package.")); + HOUDINI_LOG_MESSAGE(DebugTextLine); + return; + } + + HOUDINI_LOG_MESSAGE(TEXT(" = Filename: %s"), *(InPackage->FileName.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = Package Id: %d"), InPackage->GetPackageId().ValueForDebugging()); + HOUDINI_LOG_MESSAGE(TEXT(" = File size: %d"), InPackage->FileSize); + HOUDINI_LOG_MESSAGE(TEXT(" = Contains map: %d"), InPackage->ContainsMap()); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Fully Loaded: %d"), InPackage->IsFullyLoaded()); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Dirty: %d"), InPackage->IsDirty()); + + if (InPackage->WorldTileInfo.IsValid()) + { + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Position: %s"), *(InPackage->WorldTileInfo->Position.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Absolute Position: %s"), *(InPackage->WorldTileInfo->AbsolutePosition.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Bounds: %s"), *(InPackage->WorldTileInfo->Bounds.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - HidInTileView: %d"), InPackage->WorldTileInfo->bHideInTileView); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - ZOrder: %d"), InPackage->WorldTileInfo->ZOrder); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Parent tile package: %s"), *(InPackage->WorldTileInfo->ParentTilePackageName)); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo: NULL")); + } + + HOUDINI_LOG_MESSAGE(DebugTextLine); +} + +void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) +{ + UPackage* Package = LoadPackage(nullptr, *InLongPackageName, 0); + UWorld* World = nullptr; + + if (IsValid(Package)) + { + World = UWorld::FindWorldInPackage(Package); + } + + LogWorldInfo(World); +} + +void FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) +{ + + HOUDINI_LOG_MESSAGE(DebugTextLine); + HOUDINI_LOG_MESSAGE(TEXT("= LogWorldInfo")); + if (!IsValid(InWorld)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Invalid world.")); + HOUDINI_LOG_MESSAGE(DebugTextLine); + return; + } + + // UWorld lacks const-correctness on certain accessors + UWorld* NonConstWorld = const_cast(InWorld); + + HOUDINI_LOG_MESSAGE(TEXT(" = Path Name: %s"), *(InWorld->GetPathName())); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Editor World: %d"), InWorld->IsEditorWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Game World: %d"), InWorld->IsGameWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Preview World: %d"), InWorld->IsPreviewWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Actor Count: %d"), NonConstWorld->GetActorCount() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Num Levels: %d"), InWorld->GetNumLevels() ); + + if (IsValid(InWorld->WorldComposition)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Composition - Num Tiles: %d"), InWorld->WorldComposition->GetTilesList().Num() ); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT(" = World Composition NULL") ); + } + + + + HOUDINI_LOG_MESSAGE(DebugTextLine); +} + +FString +FHoudiniEngineUtils::HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType) +{ + switch (InEventType) + { + case HAPI_PDG_EVENT_NULL: + return TEXT("HAPI_PDG_EVENT_NULL"); + + case HAPI_PDG_EVENT_WORKITEM_ADD: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE"); + case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_DEP"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_PARENT"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT"); + + case HAPI_PDG_EVENT_NODE_CLEAR: + return TEXT("HAPI_PDG_EVENT_NODE_CLEAR"); + + case HAPI_PDG_EVENT_COOK_ERROR: + return TEXT("HAPI_PDG_EVENT_COOK_ERROR"); + case HAPI_PDG_EVENT_COOK_WARNING: + return TEXT("HAPI_PDG_EVENT_COOK_WARNING"); + + case HAPI_PDG_EVENT_COOK_COMPLETE: + return TEXT("HAPI_PDG_EVENT_COOK_COMPLETE"); + + case HAPI_PDG_EVENT_DIRTY_START: + return TEXT("HAPI_PDG_EVENT_DIRTY_START"); + case HAPI_PDG_EVENT_DIRTY_STOP: + return TEXT("HAPI_PDG_EVENT_DIRTY_STOP"); + + case HAPI_PDG_EVENT_DIRTY_ALL: + return TEXT("HAPI_PDG_EVENT_DIRTY_ALL"); + + case HAPI_PDG_EVENT_UI_SELECT: + return TEXT("HAPI_PDG_EVENT_UI_SELECT"); + + case HAPI_PDG_EVENT_NODE_CREATE: + return TEXT("HAPI_PDG_EVENT_NODE_CREATE"); + case HAPI_PDG_EVENT_NODE_REMOVE: + return TEXT("HAPI_PDG_EVENT_NODE_REMOVE"); + case HAPI_PDG_EVENT_NODE_RENAME: + return TEXT("HAPI_PDG_EVENT_NODE_RENAME"); + case HAPI_PDG_EVENT_NODE_CONNECT: + return TEXT("HAPI_PDG_EVENT_NODE_CONNECT"); + case HAPI_PDG_EVENT_NODE_DISCONNECT: + return TEXT("HAPI_PDG_EVENT_NODE_DISCONNECT"); + + case HAPI_PDG_EVENT_WORKITEM_SET_INT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_INT"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FLOAT"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_STRING: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_STRING"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FILE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FILE"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_MERGE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_MERGE"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_RESULT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_RESULT"); + + case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED + return TEXT("HAPI_PDG_EVENT_WORKITEM_PRIORITY"); + + case HAPI_PDG_EVENT_COOK_START: + return TEXT("HAPI_PDG_EVENT_COOK_START"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR"); + + case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: + return TEXT("HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE"); + + case HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED: + return TEXT("HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED"); + + case HAPI_PDG_EVENT_ALL: + return TEXT("HAPI_PDG_EVENT_ALL"); + case HAPI_PDG_EVENT_LOG: + return TEXT("HAPI_PDG_EVENT_LOG"); + + case HAPI_PDG_EVENT_SCHEDULER_ADDED: + return TEXT("HAPI_PDG_EVENT_SCHEDULER_ADDED"); + case HAPI_PDG_EVENT_SCHEDULER_REMOVED: + return TEXT("HAPI_PDG_EVENT_SCHEDULER_REMOVED"); + case HAPI_PDG_EVENT_SET_SCHEDULER: + return TEXT("HAPI_PDG_EVENT_SET_SCHEDULER"); + + case HAPI_PDG_EVENT_SERVICE_MANAGER_ALL: + return TEXT("HAPI_PDG_EVENT_SERVICE_MANAGER_ALL"); + + case HAPI_PDG_CONTEXT_EVENTS: + return TEXT("HAPI_PDG_CONTEXT_EVENTS"); + default: + break; + } + + return FString::Printf(TEXT("Unknown HAPI_PDG_EventType %d"), InEventType); +} + +FString +FHoudiniEngineUtils::HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState) +{ + switch (InWorkitemState) + { + case HAPI_PDG_WORKITEM_UNDEFINED: + return TEXT("HAPI_PDG_WORKITEM_UNDEFINED"); + case HAPI_PDG_WORKITEM_UNCOOKED: + return TEXT("HAPI_PDG_WORKITEM_UNCOOKED"); + case HAPI_PDG_WORKITEM_WAITING: + return TEXT("HAPI_PDG_WORKITEM_WAITING"); + case HAPI_PDG_WORKITEM_SCHEDULED: + return TEXT("HAPI_PDG_WORKITEM_SCHEDULED"); + case HAPI_PDG_WORKITEM_COOKING: + return TEXT("HAPI_PDG_WORKITEM_COOKING"); + case HAPI_PDG_WORKITEM_COOKED_SUCCESS: + return TEXT("HAPI_PDG_WORKITEM_COOKED_SUCCESS"); + case HAPI_PDG_WORKITEM_COOKED_CACHE: + return TEXT("HAPI_PDG_WORKITEM_COOKED_CACHE"); + case HAPI_PDG_WORKITEM_COOKED_FAIL: + return TEXT("HAPI_PDG_WORKITEM_COOKED_FAIL"); + case HAPI_PDG_WORKITEM_COOKED_CANCEL: + return TEXT("HAPI_PDG_WORKITEM_COOKED_CANCEL"); + case HAPI_PDG_WORKITEM_DIRTY: + return TEXT("HAPI_PDG_WORKITEM_DIRTY"); + default: + break; + } + + return FString::Printf(TEXT("Unknown HAPI_PDG_WorkitemState %d"), InWorkitemState); +} + +FName +FHoudiniEngineUtils::RenameToUniqueActor(AActor* InActor, const FString& InName) +{ + const FName NewName = MakeUniqueObjectName(InActor->GetOuter(), InActor->GetClass(), FName(InName)); + InActor->Rename( *(NewName.ToString()) ); + // TODO: Can we set actor label when actor is pending kill? + InActor->SetActorLabel(NewName.ToString()); + return NewName; +} + +UObject* FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) +{ + check(InActor); + + UObject* PrevObj = nullptr; + UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, InActor->GetOuter(), *InName, true); + if (ExistingObject && ExistingObject != InActor) + { + // Rename the existing object + const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName+TEXT("_old")) ); + ExistingObject->Rename(*(NewName.ToString())); + PrevObj = ExistingObject; + } + InActor->Rename(*InName); + if (UpdateLabel) + InActor->SetActorLabel(InName, true); + return PrevObj; +} + +void +FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + FHoudiniPackageParams& OutPackageParams, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FString &BakeFolder, + const FString &ObjectName, + const FString &HoudiniAssetName, + EPackageReplaceMode InReplaceMode) +{ + OutPackageParams.GeoId = InIdentifier.GeoId; + OutPackageParams.ObjectId = InIdentifier.ObjectId; + OutPackageParams.PartId = InIdentifier.PartId; + OutPackageParams.BakeFolder = BakeFolder; + OutPackageParams.PackageMode = EPackageMode::Bake; + OutPackageParams.ReplaceMode = InReplaceMode; + OutPackageParams.HoudiniAssetName = HoudiniAssetName; + OutPackageParams.HoudiniAssetActorName = HoudiniAssetName; + OutPackageParams.ObjectName = ObjectName; +} + +bool +FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() +{ + // Update / repopulate the foliage editor mode's mesh list if the foliage editor mode is active. + // TODO: find a better way to do this, the relevant functions are in FEdModeFoliage and FFoliageEdModeToolkit are not API exported + FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); + if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) + { + EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); + EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::IsOuterHoudiniAssetComponent(UObject* Obj) +{ + if (!Obj) + return false; + return Obj->GetOuter() && Obj->GetOuter()->IsA(); +} + +UHoudiniAssetComponent* +FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(UObject* Obj) +{ + return Cast(Obj->GetOuter()); +} + +FString +FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) +{ + // Compute Houdini version string. + FString HoudiniVersionString = FString::Printf( + TEXT("%d.%d.%s%d"), HAPI_VERSION_HOUDINI_MAJOR, + HAPI_VERSION_HOUDINI_MINOR, + (ExtraDigit ? (TEXT("0.")) : TEXT("")), + HAPI_VERSION_HOUDINI_BUILD); + + // If we have a patch version, we need to append it. + if (HAPI_VERSION_HOUDINI_PATCH > 0) + HoudiniVersionString = FString::Printf(TEXT("%s.%d"), *HoudiniVersionString, HAPI_VERSION_HOUDINI_PATCH); + return HoudiniVersionString; +} + +void * +FHoudiniEngineUtils::LoadLibHAPI(FString & StoredLibHAPILocation) +{ + FString HFSPath = TEXT(""); + void * HAPILibraryHandle = nullptr; + + // Look up HAPI_PATH environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . + FString HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HAPI_PATH")); + if (!HFS_ENV_VAR.IsEmpty()) + HFSPath = HFS_ENV_VAR; + + // Look up environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . + HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HFS")); + if (!HFS_ENV_VAR.IsEmpty()) + HFSPath = HFS_ENV_VAR; + + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + + // If we have a custom location specified through settings, attempt to use that. + bool bCustomPathFound = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->bUseCustomHoudiniLocation) + { + // Create full path to libHAPI binary. + FString CustomHoudiniLocationPath = HoudiniRuntimeSettings->CustomHoudiniLocation.Path; + if (!CustomHoudiniLocationPath.IsEmpty()) + { + // Convert path to absolute if it is relative. + if (FPaths::IsRelative(CustomHoudiniLocationPath)) + CustomHoudiniLocationPath = FPaths::ConvertRelativePathToFull(CustomHoudiniLocationPath); + + FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPICustomPath)) + { + HFSPath = CustomHoudiniLocationPath; + bCustomPathFound = true; + } + } + } + + // We have HFS environment variable defined (or custom location), attempt to load libHAPI from it. + if (!HFSPath.IsEmpty()) + { + if (!bCustomPathFound) + { +#if PLATFORM_WINDOWS + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_WINDOWS); +#elif PLATFORM_MAC + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_MAC); +#elif PLATFORM_LINUX + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_LINUX); +#endif + } + + // Create full path to libHAPI binary. + FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + // libHAPI binary exists at specified location, attempt to load it. + FPlatformProcess::PushDllDirectory(*HFSPath); +#if PLATFORM_WINDOWS + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); +#elif PLATFORM_MAC || PLATFORM_LINUX + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); +#endif + FPlatformProcess::PopDllDirectory(*HFSPath); + + // If library has been loaded successfully we can stop. + if ( HAPILibraryHandle ) + { + if (bCustomPathFound) + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from custom path %s"), *LibHAPIName, *HFSPath); + else + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from HFS environment path %s"), *LibHAPIName, *HFSPath); + + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + } + + // Otherwise, we will attempt to detect Houdini installation. + FString HoudiniLocation = TEXT(HOUDINI_ENGINE_HFS_PATH); + FString LibHAPIPath; + + // Compute Houdini version string. + FString HoudiniVersionString = ComputeVersionString(false); + +#if PLATFORM_WINDOWS + + // On Windows, we have also hardcoded HFS path in plugin configuration file; attempt to load from it. + HFSPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, HAPI_HFS_SUBFOLDER_WINDOWS); + + // Create full path to libHAPI binary. + LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HFSPath); + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); + FPlatformProcess::PopDllDirectory(*HFSPath); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from Plugin defined HFS path %s"), *LibHAPIName, *HFSPath); + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + + // As a second attempt, on Windows, we try to look up location of Houdini Engine in the registry. + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini Engine"), StoredLibHAPILocation, false); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // As a third attempt, we try to look up location of Houdini installation (not Houdini Engine) in the registry. + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini"), StoredLibHAPILocation, false); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // Do similar registry lookups for the 32 bits registry + // Look for the Houdini Engine registry install path + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini Engine"), StoredLibHAPILocation, true); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // ... and for the Houdini registry install path + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini"), StoredLibHAPILocation, true); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // Finally, try to load from a hardcoded program files path. + HoudiniLocation = FString::Printf( + TEXT("C:\\Program Files\\Side Effects Software\\Houdini %s\\%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_WINDOWS); + +#else + +# if PLATFORM_MAC + + // Attempt to load from standard Mac OS X installation. + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/Current/Libraries"), *HoudiniVersionString); + + // Fallback in case the previous one doesnt exist + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/%s/Libraries"), *HoudiniVersionString, *HoudiniVersionString); + + // Fallback in case we're using the steam version + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); + + // Backup Fallback in case we're using the steam version + // (this could probably be removed as paths have changed) + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); + +# elif PLATFORM_LINUX + + // Attempt to load from standard Linux installation. + HoudiniLocation = FString::Printf( + TEXT("/opt/hfs%s/%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_LINUX); + +# endif + +#endif + + // Create full path to libHAPI binary. + LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HoudiniLocation); + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); + FPlatformProcess::PopDllDirectory(*HoudiniLocation); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from expected installation %s"), *LibHAPIName, *HoudiniLocation); + StoredLibHAPILocation = HoudiniLocation; + return HAPILibraryHandle; + } + } + + StoredLibHAPILocation = TEXT(""); + return HAPILibraryHandle; +} + +bool +FHoudiniEngineUtils::IsInitialized() +{ + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) + return false; + + return (FHoudiniApi::IsInitialized(SessionPtr) == HAPI_RESULT_SUCCESS); +} + +bool +FHoudiniEngineUtils::IsHoudiniNodeValid(const HAPI_NodeId& NodeId) +{ + if (NodeId < 0) + return false; + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + bool ValidationAnswer = 0; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) + { + return false; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsNodeValid( + FHoudiniEngine::Get().GetSession(), NodeId, + NodeInfo.uniqueHoudiniNodeId, &ValidationAnswer)) + { + return false; + } + + return ValidationAnswer; +} + +bool +FHoudiniEngineUtils::HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex) +{ + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), HostAssetId, InputIndex), false); + + return true; +} + +bool +FHoudiniEngineUtils::DestroyHoudiniAsset(const HAPI_NodeId& AssetId) +{ + if (HAPI_RESULT_SUCCESS == FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), AssetId)) + { + return true; + } + + return false; +} + +#if PLATFORM_WINDOWS +void * +FHoudiniEngineUtils::LocateLibHAPIInRegistry( + const FString & HoudiniInstallationType, + FString & StoredLibHAPILocation, + bool LookIn32bitRegistry) +{ + auto FindDll = [&](const FString& InHoudiniInstallationPath) + { + FString HFSPath = FString::Printf(TEXT("%s/%s"), *InHoudiniInstallationPath, HAPI_HFS_SUBFOLDER_WINDOWS); + + // Create full path to libHAPI binary. + FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, HAPI_LIB_OBJECT_WINDOWS); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HFSPath); + void* HAPILibraryHandle = FPlatformProcess::GetDllHandle(HAPI_LIB_OBJECT_WINDOWS); + FPlatformProcess::PopDllDirectory(*HFSPath); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE( + TEXT("Loaded %s from Registry path %s"), HAPI_LIB_OBJECT_WINDOWS, + *HFSPath); + + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + return (void*)0; + }; + + FString HoudiniInstallationPath; + FString HoudiniVersionString = ComputeVersionString(true); + FString RegistryKey = FString::Printf( + TEXT("Software\\%sSide Effects Software\\%s"), + (LookIn32bitRegistry ? TEXT("WOW6432Node\\") : TEXT("")), *HoudiniInstallationType); + + if (FWindowsPlatformMisc::QueryRegKey( + HKEY_LOCAL_MACHINE, *RegistryKey, *HoudiniVersionString, HoudiniInstallationPath)) + { + FPaths::NormalizeDirectoryName(HoudiniInstallationPath); + return FindDll(HoudiniInstallationPath); + } + + return nullptr; +} +#endif + +bool +FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId& OutAssetLibraryId) +{ + OutAssetLibraryId = -1; + + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return false; + + if (!FHoudiniEngineUtils::IsInitialized()) + return false; + + // Get the HDA's file path + // We need to convert relative file path to absolute + FString AssetFileName = HoudiniAsset->GetAssetFileName(); + if (FPaths::IsRelative(AssetFileName)) + AssetFileName = FPaths::ConvertRelativePathToFull(AssetFileName); + + // We need to modify the file name for expanded .hdas + FString FileExtension = FPaths::GetExtension(AssetFileName); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) + { + // the .hda directory is what we should be loading + AssetFileName = FPaths::GetPath(AssetFileName); + } + + // If the hda file exists, we can simply load it directly + HAPI_Result Result = HAPI_RESULT_FAILURE; + if ( !AssetFileName.IsEmpty() ) + { + if ( FPaths::FileExists(AssetFileName) + || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName) ) ) + { + // Load the asset from file. + std::string AssetFileNamePlain; + FHoudiniEngineUtils::ConvertUnrealString(AssetFileName, AssetFileNamePlain); + Result = FHoudiniApi::LoadAssetLibraryFromFile( + FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); + } + } + + // Lambda to detect license issues + auto CheckLicenseValid = [&AssetFileName](const HAPI_Result& Result) + { + // HoudiniEngine acquires a license when creating/loading a node, not when creating a session + if (Result >= HAPI_RESULT_NO_LICENSE_FOUND && Result < HAPI_RESULT_ASSET_INVALID) + { + FString ErrorDesc = GetErrorDescription(Result); + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: License failed: %s."), *AssetFileName, *ErrorDesc); + + // We must stop the session to prevent further attempts at loading an HDA + // as this could lead to unreal becoming stuck and unresponsive due to license timeout + FHoudiniEngine::Get().StopSession(); + + return false; + } + else + { + return true; + } + }; + + // Detect license issues when loading the source HDA + if (!CheckLicenseValid(Result)) + return false; + + // If loading from file failed, try to load using the memory copy + if (Result != HAPI_RESULT_SUCCESS) + { + // Expanded hdas cannot be loaded from Memory + if (HoudiniAsset->IsExpandedHDA() || HoudiniAsset->GetAssetBytesCount() <= 0) + { + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); + return false; + } + else + { + // Warn the user that we are loading from memory + HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from Memory: source asset file not found."), *AssetFileName); + + // Load the asset from the cached memory buffer + Result = FHoudiniApi::LoadAssetLibraryFromMemory( + FHoudiniEngine::Get().GetSession(), + reinterpret_cast(HoudiniAsset->GetAssetBytes()), + HoudiniAsset->GetAssetBytesCount(), true, &OutAssetLibraryId); + } + } + + // Detect license issues when loading the memory copy of the HDA + if (!CheckLicenseValid(Result)) + return false; + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_MESSAGE(TEXT("Error loading asset library for %s: %s"), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + return true; +} + +bool +FHoudiniEngineUtils::GetSubAssetNames( + const HAPI_AssetLibraryId& AssetLibraryId, + TArray< HAPI_StringHandle >& OutAssetNames) +{ + if (AssetLibraryId < 0) + return false; + + int32 AssetCount = 0; + HAPI_Result Result = HAPI_RESULT_FAILURE; + Result = FHoudiniApi::GetAvailableAssetCount(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetCount); + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Error getting asset count: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + if (AssetCount <= 0) + { + HOUDINI_LOG_ERROR(TEXT("Could not find an asset.")); + return false; + } + + OutAssetNames.SetNumUninitialized(AssetCount); + Result = FHoudiniApi::GetAvailableAssets(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &OutAssetNames[0], AssetCount); + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Unable to retrieve sub asset names: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + if (!AssetCount) + { + HOUDINI_LOG_ERROR(TEXT("No assets found")); + return false; + } + + return true; +} + + +bool +FHoudiniEngineUtils::OpenSubassetSelectionWindow(TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ) +{ + OutPickedAssetName = -1; + + if (AssetNames.Num() <= 0) + return false; + + // Default to the first asset + OutPickedAssetName = AssetNames[0]; + +#if WITH_EDITOR + // Present the user with a dialog for choosing which asset to instantiate. + TSharedPtr ParentWindow; + if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + // Check if the main frame is loaded. When using the old main frame it may not be. + IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (!ParentWindow.IsValid()) + { + return false; + } + + TSharedPtr AssetSelectionWidget; + TSharedRef 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()) + { + return false; + } + + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + + int32 DialogPickedAssetName = AssetSelectionWidget->GetSelectedAssetName(); + if (DialogPickedAssetName != -1) + { + OutPickedAssetName = DialogPickedAssetName; + return true; + } + else + { + return false; + } +#endif + + return true; +} + +/* +bool +FHoudiniEngineUtils::IsValidNodeId(HAPI_NodeId NodeId) +{ + return NodeId != -1; +} +*/ + +bool +FHoudiniEngineUtils::GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString) +{ + HAPI_AssetInfo AssetInfo; + if (FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo) == HAPI_RESULT_SUCCESS) + { + FHoudiniEngineString HoudiniEngineString(AssetInfo.nameSH); + return HoudiniEngineString.ToFString(NameString); + } + + return false; +} + +bool +FHoudiniEngineUtils::GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer) +{ + PresetBuffer.Empty(); + + HAPI_NodeId NodeId; + HAPI_AssetInfo AssetInfo; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo)) + { + NodeId = AssetInfo.nodeId; + } + else + NodeId = AssetNodeId; + + int32 BufferLength = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPresetBufLength( + FHoudiniEngine::Get().GetSession(), NodeId, + HAPI_PRESETTYPE_BINARY, NULL, &BufferLength), false); + + PresetBuffer.SetNumZeroed(BufferLength); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPreset( + FHoudiniEngine::Get().GetSession(), NodeId, + &PresetBuffer[0], PresetBuffer.Num()), false); + + return true; +} + + +bool +FHoudiniEngineUtils::HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath) +{ + // Retrieve Path to the given Node, relative to the other given Node + if ((InNodeId < 0) || (InRelativeToNodeId < 0)) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InNodeId)) + return false; + + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodePath( + FHoudiniEngine::Get().GetSession(), + InNodeId, InRelativeToNodeId, &StringHandle)) + { + if(FHoudiniEngineString::ToFString(StringHandle, OutPath)) + { + return true; + } + } + return false; +} + +bool +FHoudiniEngineUtils::HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath) +{ + // Do the HAPI query only on first-use + if (!InHGPO.NodePath.IsEmpty()) + return true; + + FString NodePathTemp; + if (InHGPO.AssetId == InHGPO.GeoId) + { + // This is a SOP asset, just return the asset name in this case + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), InHGPO.AssetId, &AssetInfo)) + { + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo)) + { + if (FHoudiniEngineString::ToFString(AssetNodeInfo.nameSH, NodePathTemp)) + { + OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); + } + } + } + } + else + { + // This is an OBJ asset, return the path to this geo relative to the asset + if (FHoudiniEngineUtils::HapiGetNodePath(InHGPO.GeoId, InHGPO.AssetId, NodePathTemp)) + { + OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); + } + } + + /*if (OutPath.IsEmpty()) + { + OutPath = TEXT("Empty"); + } + + return NodePath; + */ + + return !OutPath.IsEmpty(); +} + + +bool +FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + InNodeId, &NodeInfo), false); + + int32 ObjectCount = 0; + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + ObjectCount = 1; + OutObjectInfos.SetNumUninitialized(1); + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), + NodeInfo.parentId, &OutObjectInfos[0]), false); + } + else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); + + if (ObjectCount <= 0) + { + ObjectCount = 1; + OutObjectInfos.SetNumUninitialized(1); + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[0]), false); + } + else + { + OutObjectInfos.SetNumUninitialized(ObjectCount); + for (int32 Idx = 0; Idx < OutObjectInfos.Num(); Idx++) + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[0], 0, ObjectCount), false); + } + } + else + return false; + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InNodeId,&NodeInfo), false); + + int32 ObjectCount = 1; + OutObjectTransforms.SetNumUninitialized(1); + FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); + + OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; + OutObjectTransforms[0].scale[0] = 1.0f; + OutObjectTransforms[0].scale[1] = 1.0f; + OutObjectTransforms[0].scale[2] = 1.0f; + OutObjectTransforms[0].rstOrder = HAPI_SRT; + + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + // Do nothing. Identity transform will be used for the main parent object. + } + else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), + InNodeId, nullptr, &ObjectCount), false); + + if (ObjectCount <= 0) + { + // Do nothing. Identity transform will be used for the main asset object. + } + else + { + OutObjectTransforms.SetNumUninitialized(ObjectCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_SRT, &OutObjectTransforms[0], 0, ObjectCount), false); + } + } + else + return false; + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &NodeInfo), false); + + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + NodeInfo.parentId, -1, HAPI_SRT, &HapiTransform), false); + } + else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InNodeId, -1, HAPI_SRT, &HapiTransform), false); + } + else + return false; + + // Convert HAPI transform to Unreal one. + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, OutTransform); + + return true; +} + +void +FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform) +{ + if ( HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM ) + { + // Swap Y/Z, invert W + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[2], + HapiTransform.rotationQuaternion[1], -HapiTransform.rotationQuaternion[3]); + + // Swap Y/Z and scale + FVector ObjectTranslation(HapiTransform.position[0], HapiTransform.position[2], HapiTransform.position[1]); + ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[2], HapiTransform.scale[1]); + + UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); + } + else + { + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[1], + HapiTransform.rotationQuaternion[2], HapiTransform.rotationQuaternion[3]); + + FVector ObjectTranslation( + HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2]); + ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[1], HapiTransform.scale[2]); + + UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); + } +} + +void +FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform) +{ + float HapiMatrix[16]; + FHoudiniApi::ConvertTransformEulerToMatrix(FHoudiniEngine::Get().GetSession(), &HapiTransformEuler, HapiMatrix); + + HAPI_Transform HapiTransformQuat; + FMemory::Memzero< HAPI_Transform >(HapiTransformQuat); + FHoudiniApi::ConvertMatrixToQuat(FHoudiniEngine::Get().GetSession(), HapiMatrix, HAPI_SRT, &HapiTransformQuat); + + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransformQuat, UnrealTransform); +} + +void +FHoudiniEngineUtils::TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform) +{ + FMemory::Memzero< HAPI_Transform >(HapiTransform); + HapiTransform.rstOrder = HAPI_SRT; + + FQuat UnrealRotation = UnrealTransform.GetRotation(); + FVector UnrealTranslation = UnrealTransform.GetTranslation(); + FVector UnrealScale = UnrealTransform.GetScale3D(); + + if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) + { + // Swap Y/Z, invert XYZ + HapiTransform.rotationQuaternion[0] = -UnrealRotation.X; + HapiTransform.rotationQuaternion[1] = -UnrealRotation.Z; + HapiTransform.rotationQuaternion[2] = -UnrealRotation.Y; + HapiTransform.rotationQuaternion[3] = UnrealRotation.W; + + // Swap Y/Z, scale + HapiTransform.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransform.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransform.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + HapiTransform.scale[0] = UnrealScale.X; + HapiTransform.scale[1] = UnrealScale.Z; + HapiTransform.scale[2] = UnrealScale.Y; + } + else + { + HapiTransform.rotationQuaternion[0] = UnrealRotation.X; + HapiTransform.rotationQuaternion[1] = UnrealRotation.Y; + HapiTransform.rotationQuaternion[2] = UnrealRotation.Z; + HapiTransform.rotationQuaternion[3] = UnrealRotation.W; + + HapiTransform.position[0] = UnrealTranslation.X; + HapiTransform.position[1] = UnrealTranslation.Y; + HapiTransform.position[2] = UnrealTranslation.Z; + + HapiTransform.scale[0] = UnrealScale.X; + HapiTransform.scale[1] = UnrealScale.Y; + HapiTransform.scale[2] = UnrealScale.Z; + } +} + +void +FHoudiniEngineUtils::TranslateUnrealTransform( + const FTransform & UnrealTransform, + HAPI_TransformEuler & HapiTransformEuler) +{ + FMemory::Memzero< HAPI_TransformEuler >(HapiTransformEuler); + + HapiTransformEuler.rstOrder = HAPI_SRT; + HapiTransformEuler.rotationOrder = HAPI_XYZ; + + FQuat UnrealRotation = UnrealTransform.GetRotation(); + FVector UnrealTranslation = UnrealTransform.GetTranslation(); + FVector UnrealScale = UnrealTransform.GetScale3D(); + + if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) + { + // switch the quaternion to Y-up, LHR by Swapping Y/Z and negating W + Swap(UnrealRotation.Y, UnrealRotation.Z); + UnrealRotation.W = -UnrealRotation.W; + const FRotator Rotator = UnrealRotation.Rotator(); + + // Negate roll and pitch since they are actually RHR + HapiTransformEuler.rotationEuler[0] = -Rotator.Roll; + HapiTransformEuler.rotationEuler[1] = -Rotator.Pitch; + HapiTransformEuler.rotationEuler[2] = Rotator.Yaw; + + // Swap Y/Z, scale + HapiTransformEuler.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransformEuler.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransformEuler.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + HapiTransformEuler.scale[0] = UnrealScale.X; + HapiTransformEuler.scale[1] = UnrealScale.Z; + HapiTransformEuler.scale[2] = UnrealScale.Y; + } + else + { + const FRotator Rotator = UnrealRotation.Rotator(); + HapiTransformEuler.rotationEuler[0] = Rotator.Roll; + HapiTransformEuler.rotationEuler[1] = Rotator.Yaw; + HapiTransformEuler.rotationEuler[2] = Rotator.Pitch; + + HapiTransformEuler.position[0] = UnrealTranslation.X; + HapiTransformEuler.position[1] = UnrealTranslation.Y; + HapiTransformEuler.position[2] = UnrealTranslation.Z; + + HapiTransformEuler.scale[0] = UnrealScale.X; + HapiTransformEuler.scale[1] = UnrealScale.Y; + HapiTransformEuler.scale[2] = UnrealScale.Z; + } +} + +bool +FHoudiniEngineUtils::UploadHACTransform(UHoudiniAssetComponent* HAC) +{ + if (!HAC || !HAC->bUploadTransformsToHoudiniEngine) + return false; + + // Indicates the HAC has been fully loaded + // TODO: Check! (replaces fullyloaded) + if (!HAC->IsFullyLoaded()) + return false; + + if (HAC->GetAssetCookCount() > 0 && HAC->GetAssetId() >= 0) + { + if (!FHoudiniEngineUtils::HapiSetAssetTransform(HAC->GetAssetId(), HAC->GetComponentTransform())) + return false; + } + + HAC->SetHasComponentTransformChanged(false); + + return true; +} + +bool +FHoudiniEngineUtils::HapiSetAssetTransform(const HAPI_NodeId& AssetId, const FTransform & Transform) +{ + if (AssetId < 0) + return false; + + // Translate Unreal transform to HAPI Euler one. + HAPI_TransformEuler TransformEuler; + FMemory::Memzero< HAPI_TransformEuler >(TransformEuler); + FHoudiniEngineUtils::TranslateUnrealTransform(Transform, TransformEuler); + + // Get the NodeInfo + HAPI_NodeInfo LocalAssetNodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, + &LocalAssetNodeInfo), false); + + if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + LocalAssetNodeInfo.parentId, + &TransformEuler), false); + } + else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + AssetId, &TransformEuler), false); + } + else + return false; + + return true; +} + +HAPI_NodeId +FHoudiniEngineUtils::HapiGetParentNodeId(const HAPI_NodeId& NodeId) +{ + HAPI_NodeId ParentId = -1; + if (NodeId >= 0) + { + HAPI_NodeInfo NodeInfo; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) + ParentId = NodeInfo.parentId; + } + + return ParentId; +} + + +// Assign a unique Actor Label if needed +void +FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + // TODO: Necessary?? + +#if WITH_EDITOR + HAPI_NodeId AssetId = HAC->GetAssetId(); + if (AssetId < 0) + return; + + AActor* OwnerActor = HAC->GetOwner(); + if (!OwnerActor) + return; + + if (!OwnerActor->GetName().StartsWith(AHoudiniAssetActor::StaticClass()->GetName())) + return; + + // Assign unique actor label based on asset name if it seems to have not been renamed already + FString UniqueName; + if (FHoudiniEngineUtils::GetHoudiniAssetName(AssetId, UniqueName)) + FActorLabelUtilities::SetActorLabelUnique(OwnerActor, UniqueName); +#endif +} + +bool +FHoudiniEngineUtils::GetLicenseType(FString & LicenseType) +{ + LicenseType = TEXT(""); + HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetSessionEnvInt( + FHoudiniEngine::Get().GetSession(), HAPI_SESSIONENVINT_LICENSE, + (int32 *)&LicenseTypeValue), false); + + switch (LicenseTypeValue) + { + case HAPI_LICENSE_NONE: + { + LicenseType = TEXT("No License Acquired"); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE: + { + LicenseType = TEXT("Houdini Engine"); + break; + } + + case HAPI_LICENSE_HOUDINI: + { + LicenseType = TEXT("Houdini"); + break; + } + + case HAPI_LICENSE_HOUDINI_FX: + { + LicenseType = TEXT("Houdini FX"); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE_INDIE: + { + LicenseType = TEXT("Houdini Engine Indie"); + break; + } + + case HAPI_LICENSE_HOUDINI_INDIE: + { + LicenseType = TEXT("Houdini Indie"); + break; + } + + case HAPI_LICENSE_MAX: + default: + { + return false; + } + } + + return true; +} + +// Check if the Houdini asset component (or parent HAC of a parameter) is being cooked +bool +FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(UObject* InObj) +{ + if (!InObj) + return false; + + UHoudiniAssetComponent* HoudiniAssetComponent = nullptr; + + if (InObj->IsA()) + { + HoudiniAssetComponent = Cast(InObj); + } + else if (InObj->IsA()) + { + UHoudiniParameter* Parameter = Cast(InObj); + if (!Parameter) + return false; + + HoudiniAssetComponent = Cast(Parameter->GetOuter()); + } + + if (!HoudiniAssetComponent) + return false; + + EHoudiniAssetState AssetState = HoudiniAssetComponent->GetAssetState(); + + return AssetState >= EHoudiniAssetState::PreCook && AssetState <= EHoudiniAssetState::PostCook; +} + +void +FHoudiniEngineUtils::UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate) +{ + TArray ObjectsToUpdate; + ObjectsToUpdate.Add(InObjectToUpdate); + + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() + { + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + } +} + +void +FHoudiniEngineUtils::UpdateEditorProperties(TArray ObjectsToUpdate, const bool& InForceFullUpdate) +{ + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() + { + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + } +} + +void FHoudiniEngineUtils::UpdateBlueprintEditor(UHoudiniAssetComponent* HAC) +{ + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [HAC]() + { + FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); + } +} + +void +FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate) +{ + // TODO: Don't use this method. Prefer using IDetailLayoutBuilder::ForceRefreshDetails(). + // Example to correctly update details panel through IDetailCategoryBuilder / IDetailLayoutBuilder + // IDetailCategoryBuilder &CategoryBuilder = StructBuilder.GetParentCategory(); + // IDetailLayoutBuilder &LayoutBuilder = CategoryBuilder.GetParentLayout(); + // LayoutBuilder.ForceRefreshDetails(); + +#if WITH_EDITOR + if (!bInForceFullUpdate) + { + // bNeedFullUpdate is false only when small changes (parameters value) have been made + // We do not reselect the actor to avoid loosing the currently selected parameter + if(GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); + + return; + } + + // We now want to get all the components/actors owning the objects to update + TArray AllSceneComponents; + for (auto CurrentObject : ObjectsToUpdate) + { + if (!CurrentObject || CurrentObject->IsPendingKill()) + continue; + + // In some case, the object itself is the component + USceneComponent* SceneComp = Cast(CurrentObject); + if (!SceneComp) + { + SceneComp = Cast(CurrentObject->GetOuter()); + } + + if (SceneComp && !SceneComp->IsPendingKill()) + { + AllSceneComponents.Add(SceneComp); + continue; + } + } + + TArray AllActors; + for (auto CurrentSceneComp : AllSceneComponents) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill()) + continue; + + AActor* Actor = CurrentSceneComp->GetOwner(); + if (Actor && !Actor->IsPendingKill()) + AllActors.Add(Actor); + } + + // Updating the editor properties can be done in two ways, depending if we're in the BP editor or not + // If we have a parent actor, we're not in the BP Editor, so update via the property editor module + if (AllActors.Num() > 0) + { + // Get the property editor module + FPropertyEditorModule& PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // This will actually force a refresh of all the details view + //PropertyModule.NotifyCustomizationModuleChanged(); + + TArray SelectedActors; + for (auto Actor : AllActors) + { + if (Actor && Actor->IsSelected()) + SelectedActors.Add(Actor); + } + + if (SelectedActors.Num() > 0) + { + PropertyModule.UpdatePropertyViews(SelectedActors); + } + + // 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. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + { + // We have no details panel, nothing to update. + continue; + } + + // Get the selected actors for this details panels and check if one of ours belongs to it + const TArray>& SelectedDetailActors = DetailsView->GetSelectedActors(); + bool bFoundActor = false; + for (int32 ActorIdx = 0; ActorIdx < SelectedDetailActors.Num(); ActorIdx++) + { + TWeakObjectPtr SelectedActor = SelectedDetailActors[ActorIdx]; + if (SelectedActor.IsValid() && AllActors.Contains(SelectedActor.Get())) + { + bFoundActor = true; + break; + } + } + + // None of our actors belongs to this detail panel, no need to update it + if (!bFoundActor) + continue; + + // Refresh that details panels using its current selection + TArray Selection; + for (auto DetailsActor : SelectedDetailActors) + { + if (DetailsActor.IsValid()) + Selection.Add(DetailsActor.Get()); + } + + // Reset selected actors, force refresh and override the lock. + DetailsView->SetObjects(SelectedActors, bInForceFullUpdate, true); + + if (GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); + } + } + else + { + // TODO: Do we need to do Blueprint Editor updates here or can we confine it to "post output processing"? + + } + + /* + // Reset the full update flag + if (bNeedFullUpdate) + HAC->SetEditorPropertiesNeedFullUpdate(false); + */ + + return; +#endif +} + +void FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC) +{ + //UHoudiniAssetComponent* HACTemplate = HAC->GetCachedTemplate(); + //UBlueprintGeneratedClass* OwnerBPClass = Cast(HACTemplate->GetOuter()); + //if (!OwnerBPClass) + // return; + + ///* + //FBlueprintEditor* BlueprintEditor = static_cast(FAssetEditorManager::Get().FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); + //if (!BlueprintEditor) + // return; + //*/ + + //// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built. + //UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + //FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); + //if (!BlueprintEditor) + // return; + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(HAC); + if (!BlueprintEditor) + return; + + TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); + if (SCSEditor.IsValid()) + { + SCSEditor->UpdateTree(true); + SCSEditor->DumpTree(); + } + BlueprintEditor->RefreshMyBlueprint(); + + //BlueprintEditor->RefreshMyBlueprint(); + //BlueprintEditor->RefreshInspector(); + //BlueprintEditor->RefreshEditors(); + + // Also somehow reselect ? +} + +HAPI_Result +FHoudiniEngineUtils::SetAttributeStringData( + const FString& InString, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo) +{ + TArray StringArray; + StringArray.Add(InString); + + return SetAttributeStringData(StringArray, InNodeId, InPartId, InAttributeName, InAttributeInfo); +} + +HAPI_Result +FHoudiniEngineUtils::SetAttributeStringData( + const TArray& InStringArray, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo ) +{ + TArray StringDataArray; + for (auto CurrentString : InStringArray) + { + // Append the converted string to the string array + StringDataArray.Add(FHoudiniEngineUtils::ExtractRawString(CurrentString)); + } + + // Set the attribute's string data + HAPI_Result result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + TCHAR_TO_ANSI(*InAttributeName), &InAttributeInfo, + StringDataArray.GetData(), 0, InAttributeInfo.count); + + // ExtractRawString allocates memory using malloc, free it! + FreeRawStringMemory(StringDataArray); + + return result; +} + +char * +FHoudiniEngineUtils::ExtractRawString(const FString& InString) +{ + if (InString.IsEmpty()) + return nullptr; + + std::string ConvertedString = TCHAR_TO_UTF8(*InString); + + // Allocate space for unique string. + int32 UniqueStringBytes = ConvertedString.size() + 1; + char * UniqueString = static_cast(FMemory::Malloc(UniqueStringBytes)); + + FMemory::Memzero(UniqueString, UniqueStringBytes); + FMemory::Memcpy(UniqueString, ConvertedString.c_str(), ConvertedString.size()); + + return UniqueString; +} + +void +FHoudiniEngineUtils::FreeRawStringMemory(const char*& InRawString) +{ + if (InRawString == nullptr) + return; + + // Do not attempt to free empty strings! + if (!InRawString[0]) + return; + + FMemory::Free((void*)InRawString); + InRawString = nullptr; +} + +void +FHoudiniEngineUtils::FreeRawStringMemory(TArray& InRawStringArray) +{ + // ExtractRawString allocates memory using malloc, free it! + for (auto CurrentStrPtr : InRawStringArray) + { + FreeRawStringMemory(CurrentStrPtr); + } + InRawStringArray.Empty(); +} + +bool +FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // No need to add another component if we already show the logo + if (FHoudiniEngineUtils::HasHoudiniLogo(HAC)) + return true; + + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + UStaticMeshComponent * HoudiniLogoSMC = NewObject< UStaticMeshComponent >( + HAC, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!HoudiniLogoSMC) + return false; + + HoudiniLogoSMC->SetStaticMesh(HoudiniLogoSM); + HoudiniLogoSMC->SetVisibility(true); + HoudiniLogoSMC->SetHiddenInGame(true); + // Attach created static mesh component to our Houdini component. + HoudiniLogoSMC->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniLogoSMC->RegisterComponent(); + + return true; +} + +bool +FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the Houdini Logo SM + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + // Iterate on the HAC's component + for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + continue; + + // Get the static mesh component + UStaticMeshComponent* SMC = Cast(CurrentSceneComp); + if (!SMC || SMC->IsPendingKill()) + continue; + + // Check if the SMC is the Houdini Logo + if (SMC->GetStaticMesh() != HoudiniLogoSM) + continue; + + SMC->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SMC->UnregisterComponent(); + SMC->DestroyComponent(); + + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the Houdini Logo SM + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + // Iterate on the HAC's component + for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + continue; + + // Get the static mesh component + UStaticMeshComponent* SMC = Cast(CurrentSceneComp); + if (!SMC || SMC->IsPendingKill()) + continue; + + // Check if the SMC is the Houdini Logo + if (SMC->GetStaticMesh() == HoudiniLogoSM) + return true; + } + + return false; +} + +int32 +FHoudiniEngineUtils::HapiGetVertexListForGroup( + const HAPI_NodeId& GeoId, + const HAPI_PartInfo& PartInfo, + const FString& GroupName, + const TArray& FullVertexList, + TArray& NewVertexList, + TArray& AllVertexList, + TArray& AllFaceList, + TArray& AllGroupFaceIndices, + int32& FirstValidVertex, + int32& FirstValidPrim, + const bool& isPackedPrim) +{ + int32 ProcessedWedges = 0; + AllFaceList.Empty(); + FirstValidPrim = 0; + FirstValidVertex = 0; + NewVertexList.Init(-1, FullVertexList.Num()); + + // Get the faces membership for this group + bool bAllEquals = false; + TArray PartGroupMembership; + if (!FHoudiniEngineUtils::HapiGetGroupMembership( + GeoId, PartInfo, HAPI_GROUPTYPE_PRIM, GroupName, PartGroupMembership, bAllEquals)) + return false; + + // Go through all primitives. + for (int32 FaceIdx = 0; FaceIdx < PartGroupMembership.Num(); ++FaceIdx) + { + if (PartGroupMembership[FaceIdx] <= 0) + { + // The face is not in the group, skip + continue; + } + + // Add the face's index. + AllFaceList.Add(FaceIdx); + + // Get the index of this face's vertices + int32 FirstVertexIdx = FaceIdx * 3; + int32 SecondVertexIdx = FirstVertexIdx + 1; + int32 LastVertexIdx = FirstVertexIdx + 2; + + // This face is a member of specified group. + // Add all 3 vertices + if (FullVertexList.IsValidIndex(LastVertexIdx)) + { + NewVertexList[FirstVertexIdx] = FullVertexList[FirstVertexIdx]; + NewVertexList[SecondVertexIdx] = FullVertexList[SecondVertexIdx]; + NewVertexList[LastVertexIdx] = FullVertexList[LastVertexIdx]; + } + + // Mark these vertex indices as used. + if (AllVertexList.IsValidIndex(LastVertexIdx)) + { + AllVertexList[FirstVertexIdx] = 1; + AllVertexList[SecondVertexIdx] = 1; + AllVertexList[LastVertexIdx] = 1; + } + + // Mark this face as used. + if (AllGroupFaceIndices.IsValidIndex(FaceIdx)) + AllGroupFaceIndices[FaceIdx] = 1; + + if (ProcessedWedges == 0) + { + // Keep track of the first valid vertex/face indices for this group + // This will be useful later on when extracting attributes + FirstValidVertex = FirstVertexIdx; + FirstValidPrim = FaceIdx; + } + + ProcessedWedges += 3; + } + + return ProcessedWedges; +} + +bool +FHoudiniEngineUtils::HapiGetGroupNames( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const HAPI_GroupType& GroupType, const bool& isPackedPrim, + TArray& OutGroupNames) +{ + int32 GroupCount = 0; + if (!isPackedPrim) + { + // Get group count on the geo + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo), false); + + if (GroupType == HAPI_GROUPTYPE_POINT) + GroupCount = GeoInfo.pointGroupCount; + else if (GroupType == HAPI_GROUPTYPE_PRIM) + GroupCount = GeoInfo.primitiveGroupCount; + } + else + { + // We need the group count for this packed prim + int32 PointGroupCount = 0, PrimGroupCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupCountOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PointGroupCount, &PrimGroupCount), false); + + if (GroupType == HAPI_GROUPTYPE_POINT) + GroupCount = PointGroupCount; + else if (GroupType == HAPI_GROUPTYPE_PRIM) + GroupCount = PrimGroupCount; + } + + if (GroupCount <= 0) + return true; + + TArray GroupNameStringHandles; + GroupNameStringHandles.SetNumZeroed(GroupCount); + if (!isPackedPrim) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNames( + FHoudiniEngine::Get().GetSession(), + GeoId, GroupType, &GroupNameStringHandles[0], GroupCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNamesOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, GroupType, &GroupNameStringHandles[0], GroupCount), false); + } + + /* + OutGroupNames.SetNum(GroupCount); + for (int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx) + { + FString CurrentGroupName = TEXT(""); + FHoudiniEngineString::ToFString(GroupNameStringHandles[NameIdx], CurrentGroupName); + OutGroupNames[NameIdx] = CurrentGroupName; + } + */ + + FHoudiniEngineString::SHArrayToFStringArray(GroupNameStringHandles, OutGroupNames); + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetGroupMembership( + const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, + const HAPI_GroupType& GroupType, const FString & GroupName, + TArray& OutGroupMembership, bool& OutAllEquals) +{ + int32 ElementCount = (GroupType == HAPI_GROUPTYPE_POINT) ? PartInfo.pointCount : PartInfo.faceCount; + if (ElementCount < 1) + return false; + OutGroupMembership.SetNum(ElementCount); + + OutAllEquals = false; + std::string ConvertedGroupName = TCHAR_TO_UTF8(*GroupName); + if (!PartInfo.isInstanced) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembership( + FHoudiniEngine::Get().GetSession(), + GeoId, PartInfo.id, GroupType,ConvertedGroupName.c_str(), + &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembershipOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), GeoId, PartInfo.id, GroupType, + ConvertedGroupName.c_str(), &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); + } + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize, + HAPI_AttributeOwner InOwner) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniEngineUtils::HapiGetAttributeDataAsFloat")); + + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Allocate sufficient buffer for data. + OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); + + return true; + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Expected Float, found an int, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray IntData; + IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(IntData.Num()); + for (int32 Idx = 0; Idx < IntData.Num(); Idx++) + { + OutData[Idx] = (float)IntData[Idx]; + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from integer."), *FString(InAttribName)); + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + // Expected Float, found a string, try to convert the attribute + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) + { + bool bConversionError = false; + OutData.SetNum(StringData.Num()); + for (int32 Idx = 0; Idx < StringData.Num(); Idx++) + { + if (StringData[Idx].IsNumeric()) + OutData[Idx] = FCString::Atof(*StringData[Idx]); + else + bConversionError = true; + } + + if (!bConversionError) + { + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from string."), *FString(InAttribName)); + return true; + } + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a float attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + const int32& InTupleSize, + const HAPI_AttributeOwner& InOwner) +{ + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Allocate sufficient buffer for data. + OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); + + return true; + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Expected Int, found a float, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray FloatData; + FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the float values + if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(FloatData.Num()); + for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) + { + OutData[Idx] = (int32)FloatData[Idx]; + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from float."), *FString(InAttribName)); + + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + // Expected Int, found a string, try to convert the attribute + TArray StringData; + if(FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) + { + bool bConversionError = false; + OutData.SetNum(StringData.Num()); + for (int32 Idx = 0; Idx < StringData.Num(); Idx++) + { + if (StringData[Idx].IsNumeric()) + OutData[Idx] = FCString::Atoi(*StringData[Idx]); + else + bConversionError = true; + } + + if (!bConversionError) + { + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from string."), *FString(InAttribName)); + return true; + } + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be an integer attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsString( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize, + HAPI_AttributeOwner InOwner) +{ + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + return FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, OutData); + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Expected string, found a float, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray FloatData; + FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the float values + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(FloatData.Num()); + for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) + { + OutData[Idx] = FString::SanitizeFloat(FloatData[Idx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from float."), *FString(InAttribName)); + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Expected String, found an int, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray IntData; + IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(IntData.Num()); + for (int32 Idx = 0; Idx < IntData.Num(); Idx++) + { + OutData[Idx] = FString::FromInt(IntData[Idx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from integer."), *FString(InAttribName)); + return true; + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a string attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& InAttributeInfo, + TArray& OutData) +{ + if (!InAttributeInfo.exists) + return false; + + // Extract the StringHandles + TArray StringHandles; + StringHandles.Init(-1, InAttributeInfo.count * InAttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, &InAttributeInfo, + &StringHandles[0], 0, InAttributeInfo.count), false); + + // Set the output data size + OutData.SetNum(StringHandles.Num()); + + // Convert the StringHandles to FString. + // using a map to minimize the number of HAPI calls + FHoudiniEngineString::SHArrayToFStringArray(StringHandles, OutData); + + return true; +} + +bool +FHoudiniEngineUtils::HapiCheckAttributeExists( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const char * AttribName, HAPI_AttributeOwner Owner) +{ + if (Owner == HAPI_ATTROWNER_INVALID) + { + for (int32 OwnerIdx = 0; OwnerIdx < HAPI_ATTROWNER_MAX; OwnerIdx++) + { + if (HapiCheckAttributeExists(GeoId, PartId, AttribName, (HAPI_AttributeOwner)OwnerIdx)) + { + return true; + } + } + } + else + { + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, AttribName, Owner, &AttribInfo), false); + + return AttribInfo.exists; + } + + return false; +} + +bool +FHoudiniEngineUtils::IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType) +{ + // Check for + // - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE (unreal_instance) on points/detail + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT)) + { + OutInstancerType = EHoudiniInstancerType::AttributeInstancer; + return true; + } + + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL)) + { + OutInstancerType = EHoudiniInstancerType::AttributeInstancer; + return true; + } + + // - HAPI_UNREAL_ATTRIB_INSTANCE (instance) on points + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT)) + { + OutInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsString( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const FString& DefaultValue, + FString& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParamInfo), false); + + // .. and value + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), NodeId, false, + &StringHandle, FoundParamInfo.stringValuesIndex, 1), false); + + // Convert the string handle to FString + return FHoudiniEngineString::ToFString(StringHandle, OutValue); +} + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const int32& DefaultValue, + int32& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParmInfo; + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo), false); + + // .. and value + int32 Value = DefaultValue; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Value, + FoundParmInfo.intValuesIndex, 1), false); + + OutValue = Value; + + return true; +} + + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsFloat( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const float& DefaultValue, + float& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParmInfo; + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo), false); + + // .. and value + float Value = DefaultValue; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Value, + FoundParmInfo.floatValuesIndex, 1), false); + + OutValue = Value; + + return true; +} + +HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo) +{ + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo); + if (NodeInfo.parmCount <= 0) + return -1; + + HAPI_ParmId ParmId = HapiFindParameterByNameOrTag(NodeInfo.id, ParmName); + if ((ParmId < 0) || (ParmId >= NodeInfo.parmCount)) + return -1; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo), -1); + + return ParmId; +} + + +HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName) +{ + // First, try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), -1); + + if (ParmId >= 0) + return ParmId; + + // Second, try to find it by its tag + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmWithTag( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), -1); + + if (ParmId >= 0) + return ParmId; + + return -1; +} + +int32 +FHoudiniEngineUtils::HapiGetAttributeOfType( + const HAPI_NodeId& GeoId, + const HAPI_NodeId& PartId, + const HAPI_AttributeOwner& AttributeOwner, + const HAPI_AttributeTypeInfo& AttributeType, + TArray& MatchingAttributesInfo, + TArray& MatchingAttributesName) +{ + int32 NumberOfAttributeFound = 0; + + // Get the part infos + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, &PartInfo), NumberOfAttributeFound); + + // Get All attribute names for that part + int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; + + TArray AttribNameSHArray; + AttribNameSHArray.SetNum(nAttribCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, AttributeOwner, + AttribNameSHArray.GetData(), nAttribCount), NumberOfAttributeFound); + + TArray AttribNameArray; + FHoudiniEngineString::SHArrayToFStringArray(AttribNameSHArray, AttribNameArray); + + // Iterate on all the attributes, and get their part infos to get their type + for (int32 Idx = 0; Idx < AttribNameArray.Num(); Idx++) + { + FString HapiString = AttribNameArray[Idx]; + + // ... then the attribute info + HAPI_AttributeInfo AttrInfo; + FHoudiniApi::AttributeInfo_Init(&AttrInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, TCHAR_TO_UTF8(*HapiString), + AttributeOwner, &AttrInfo)) + continue; + + if (!AttrInfo.exists) + continue; + + // ... check the type + if (AttrInfo.typeInfo != AttributeType) + continue; + + MatchingAttributesInfo.Add(AttrInfo); + MatchingAttributesName.Add(HapiString); + + NumberOfAttributeFound++; + } + + return NumberOfAttributeFound; +} + +HAPI_PartInfo +FHoudiniEngineUtils::ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo) +{ + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + + PartInfo.id = InHPartInfo.PartId; + //PartInfo.nameSH = InHPartInfo.Name; + + switch (InHPartInfo.Type) + { + case EHoudiniPartType::Mesh: + PartInfo.type = HAPI_PARTTYPE_MESH; + break; + case EHoudiniPartType::Curve: + PartInfo.type = HAPI_PARTTYPE_CURVE; + break; + case EHoudiniPartType::Instancer: + PartInfo.type = HAPI_PARTTYPE_INSTANCER; + break; + case EHoudiniPartType::Volume: + PartInfo.type = HAPI_PARTTYPE_VOLUME; + break; + default: + case EHoudiniPartType::Invalid: + PartInfo.type = HAPI_PARTTYPE_INVALID; + break; + } + + PartInfo.faceCount = InHPartInfo.FaceCount; + PartInfo.vertexCount = InHPartInfo.VertexCount; + PartInfo.pointCount = InHPartInfo.PointCount; + + PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = InHPartInfo.PointAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX] = InHPartInfo.VertexAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_PRIM] = InHPartInfo.PrimitiveAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL] = InHPartInfo.DetailAttributeCounts; + + PartInfo.isInstanced = InHPartInfo.bIsInstanced; + + PartInfo.instancedPartCount = InHPartInfo.InstancedPartCount; + PartInfo.instanceCount = InHPartInfo.InstanceCount; + + PartInfo.hasChanged = InHPartInfo.bHasChanged; + + return PartInfo; +} + +int32 +FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + TArray< FHoudiniMeshSocket >& AllSockets, + const bool& isPackedPrim) +{ + int32 FoundSocketCount = 0; + + // Attributes we are interested in. + // Position + TArray Positions; + HAPI_AttributeInfo AttribInfoPositions; + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bool bHasRotation = false; + TArray Rotations; + HAPI_AttributeInfo AttribInfoRotations; + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bool bHasScale = false; + TArray Scales; + HAPI_AttributeInfo AttribInfoScales; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // Socket Name + bool bHasNames = false; + TArray Names; + HAPI_AttributeInfo AttribInfoNames; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bool bHasActors = false; + TArray Actors; + HAPI_AttributeInfo AttribInfoActors; + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bool bHasTags = false; + TArray Tags; + HAPI_AttributeInfo AttribInfoTags; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + + // Lambda function for creating the socket and adding it to the array + // Shared between the by Attribute / by Group methods + auto AddSocketToArray = [&](const int32& PointIdx) + { + FHoudiniMeshSocket CurrentSocket; + FVector currentPosition = FVector::ZeroVector; + if (Positions.IsValidIndex(PointIdx * 3 + 2)) + { + currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + FVector currentScale = FVector::OneVector; + if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) + { + currentScale.X = Scales[PointIdx * 3]; + currentScale.Y = Scales[PointIdx * 3 + 2]; + currentScale.Z = Scales[PointIdx * 3 + 1]; + } + + FQuat currentRotation = FQuat::Identity; + if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) + { + currentRotation.X = Rotations[PointIdx * 4]; + currentRotation.Y = Rotations[PointIdx * 4 + 2]; + currentRotation.Z = Rotations[PointIdx * 4 + 1]; + currentRotation.W = -Rotations[PointIdx * 4 + 3]; + } + + if (bHasNames && Names.IsValidIndex(PointIdx)) + CurrentSocket.Name = Names[PointIdx]; + + if (bHasActors && Actors.IsValidIndex(PointIdx)) + CurrentSocket.Actor = Actors[PointIdx]; + + if (bHasTags && Tags.IsValidIndex(PointIdx)) + CurrentSocket.Tag = Tags[PointIdx]; + + // If the scale attribute wasn't set on all socket, we might end up + // with a zero scale socket, avoid that. + if (currentScale == FVector::ZeroVector) + currentScale = FVector::OneVector; + + CurrentSocket.Transform.SetLocation(currentPosition); + CurrentSocket.Transform.SetRotation(currentRotation); + CurrentSocket.Transform.SetScale3D(currentScale); + + // We want to make sure we're not adding the same socket multiple times + AllSockets.AddUnique(CurrentSocket); + + FoundSocketCount++; + + return true; + }; + + + // Lambda function for reseting the arrays/attributes + auto ResetArraysAndAttr = [&]() + { + // Position + Positions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bHasRotation = false; + Rotations.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bHasScale = false; + Scales.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // Socket Name + bHasNames = false; + Names.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bHasActors = false; + Actors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bHasTags = false; + Tags.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + }; + + //------------------------------------------------------------------------- + // FIND SOCKETS BY DETAIL ATTRIBUTES + //------------------------------------------------------------------------- + + int32 SocketIdx = 0; + bool HasSocketAttributes = true; + while (HasSocketAttributes) + { + // Build the current socket's prefix + FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(SocketIdx); + + // Reset the arrays and attributes + ResetArraysAndAttr(); + + // Retrieve position data. + FString SocketPosAttr = SocketAttrPrefix + TEXT("_pos"); + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, TCHAR_TO_ANSI(*SocketPosAttr), + AttribInfoPositions, Positions, 0, HAPI_ATTROWNER_DETAIL)) + break; + + if (!AttribInfoPositions.exists) + { + // No need to keep looking for socket attributes + HasSocketAttributes = false; + break; + } + + // Retrieve rotation data. + FString SocketRotAttr = SocketAttrPrefix + TEXT("_rot"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketRotAttr), AttribInfoRotations, Rotations, 0, HAPI_ATTROWNER_DETAIL)) + bHasRotation = true; + + // Retrieve scale data. + FString SocketScaleAttr = SocketAttrPrefix + TEXT("_scale"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketScaleAttr), AttribInfoScales, Scales, 0, HAPI_ATTROWNER_DETAIL)) + bHasScale = true; + + // Retrieve mesh socket names. + FString SocketNameAttr = SocketAttrPrefix + TEXT("_name"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketNameAttr), AttribInfoNames, Names)) + bHasNames = true; + + // Retrieve mesh socket actor. + FString SocketActorAttr = SocketAttrPrefix + TEXT("_actor"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketActorAttr), AttribInfoActors, Actors)) + bHasActors = true; + + // Retrieve mesh socket tags. + FString SocketTagAttr = SocketAttrPrefix + TEXT("_tag"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketTagAttr), AttribInfoTags, Tags)) + bHasTags = true; + + // Add the socket to the array + AddSocketToArray(0); + + // Try to find the next socket + SocketIdx++; + } + + return FoundSocketCount; +} + + +int32 +FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + TArray& AllSockets, + const bool& isPackedPrim) +{ + // Attributes we are interested in. + // Position + TArray Positions; + HAPI_AttributeInfo AttribInfoPositions; + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bool bHasRotation = false; + TArray Rotations; + HAPI_AttributeInfo AttribInfoRotations; + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bool bHasScale = false; + TArray Scales; + HAPI_AttributeInfo AttribInfoScales; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // We can also get the sockets rotation from the normal + bool bHasNormals = false; + TArray Normals; + HAPI_AttributeInfo AttribInfoNormals; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Socket Name + bool bHasNames = false; + TArray Names; + HAPI_AttributeInfo AttribInfoNames; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bool bHasActors = false; + TArray Actors; + HAPI_AttributeInfo AttribInfoActors; + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bool bHasTags = false; + TArray Tags; + HAPI_AttributeInfo AttribInfoTags; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + + // Lambda function for creating the socket and adding it to the array + // Shared between the by Attribute / by Group methods + int32 FoundSocketCount = 0; + auto AddSocketToArray = [&](const int32& PointIdx) + { + FHoudiniMeshSocket CurrentSocket; + FVector currentPosition = FVector::ZeroVector; + if (Positions.IsValidIndex(PointIdx * 3 + 2)) + { + currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + FVector currentScale = FVector::OneVector; + if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) + { + currentScale.X = Scales[PointIdx * 3]; + currentScale.Y = Scales[PointIdx * 3 + 2]; + currentScale.Z = Scales[PointIdx * 3 + 1]; + } + + FQuat currentRotation = FQuat::Identity; + if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) + { + currentRotation.X = Rotations[PointIdx * 4]; + currentRotation.Y = Rotations[PointIdx * 4 + 2]; + currentRotation.Z = Rotations[PointIdx * 4 + 1]; + currentRotation.W = -Rotations[PointIdx * 4 + 3]; + } + else if (bHasNormals && Normals.IsValidIndex(PointIdx * 3 + 2)) + { + FVector vNormal; + vNormal.X = Normals[PointIdx * 3]; + vNormal.Y = Normals[PointIdx * 3 + 2]; + vNormal.Z = Normals[PointIdx * 3 + 1]; + + if (vNormal != FVector::ZeroVector) + currentRotation = FQuat::FindBetween(FVector::UpVector, vNormal); + } + + if (bHasNames && Names.IsValidIndex(PointIdx)) + CurrentSocket.Name = Names[PointIdx]; + + if (bHasActors && Actors.IsValidIndex(PointIdx)) + CurrentSocket.Actor = Actors[PointIdx]; + + if (bHasTags && Tags.IsValidIndex(PointIdx)) + CurrentSocket.Tag = Tags[PointIdx]; + + // If the scale attribute wasn't set on all socket, we might end up + // with a zero scale socket, avoid that. + if (currentScale == FVector::ZeroVector) + currentScale = FVector::OneVector; + + CurrentSocket.Transform.SetLocation(currentPosition); + CurrentSocket.Transform.SetRotation(currentRotation); + CurrentSocket.Transform.SetScale3D(currentScale); + + // We want to make sure we're not adding the same socket multiple times + AllSockets.AddUnique(CurrentSocket); + + FoundSocketCount++; + + return true; + }; + + + // Lambda function for reseting the arrays/attributes + auto ResetArraysAndAttr = [&]() + { + // Position + Positions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bHasRotation = false; + Rotations.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bHasScale = false; + Scales.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // When using socket groups, we can also get the sockets rotation from the normal + bHasNormals = false; + Normals.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Socket Name + bHasNames = false; + Names.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bHasActors = false; + Actors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bHasTags = false; + Tags.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + }; + + //------------------------------------------------------------------------- + // FIND SOCKETS BY POINT GROUPS + //------------------------------------------------------------------------- + + // Get object / geo group memberships for primitives. + TArray GroupNames; + if (!FHoudiniEngineUtils::HapiGetGroupNames( + GeoId, PartId, HAPI_GROUPTYPE_POINT, isPackedPrim, GroupNames)) + { + HOUDINI_LOG_MESSAGE(TEXT("GetMeshSocketList: Geo [%d] Part [%d] non-fatal error reading point group names"), GeoId, PartId); + } + + // First, we want to make sure we have at least one socket group before continuing + bool bHasSocketGroup = false; + for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) + { + const FString & GroupName = GroupNames[GeoGroupNameIdx]; + if (GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) + || GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) + { + bHasSocketGroup = true; + break; + } + } + + if (!bHasSocketGroup) + return FoundSocketCount; + + // Get the part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PartInfo)) + return false; + + // Reset the data arrays and attributes + ResetArraysAndAttr(); + + // Retrieve position data. + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, Positions)) + return false; + + // Retrieve rotation data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_ROTATION, AttribInfoRotations, Rotations)) + bHasRotation = true; + + // Retrieve normal data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, Normals)) + bHasNormals = true; + + // Retrieve scale data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_SCALE, AttribInfoScales, Scales)) + bHasScale = true; + + // Retrieve mesh socket names. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, AttribInfoNames, Names)) + bHasNames = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD, AttribInfoNames, Names)) + bHasNames = true; + + // Retrieve mesh socket actor. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR, AttribInfoActors, Actors)) + bHasActors = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD, AttribInfoActors, Actors)) + bHasActors = true; + + // Retrieve mesh socket tags. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, AttribInfoTags, Tags)) + bHasTags = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD, AttribInfoTags, Tags)) + bHasTags = true; + + // Extracting Sockets vertices + for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) + { + const FString & GroupName = GroupNames[GeoGroupNameIdx]; + if (!GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) + && !GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) + continue; + + bool AllEquals = false; + TArray< int32 > PointGroupMembership; + FHoudiniEngineUtils::HapiGetGroupMembership( + GeoId, PartInfo, HAPI_GROUPTYPE_POINT, GroupName, PointGroupMembership, AllEquals); + + // Go through all primitives. + for (int32 PointIdx = 0; PointIdx < PointGroupMembership.Num(); ++PointIdx) + { + if (PointGroupMembership[PointIdx] == 0) + { + if (AllEquals) + break; + else + continue; + } + + // Add the corresponding socket to the array + AddSocketToArray(PointIdx); + } + } + + return FoundSocketCount; +} + +bool +FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( + UStaticMesh* StaticMesh, + TArray& AllSockets, + const bool& CleanImportSockets) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Remove the sockets from the previous cook! + if (CleanImportSockets) + { + StaticMesh->Sockets.RemoveAll([=](UStaticMeshSocket* Socket) { return Socket ? Socket->bSocketCreatedAtImport : true; }); + } + + if (AllSockets.Num() <= 0) + return true; + + // Having sockets with empty names can lead to various issues, so we'll create one now + for (int32 Idx = 0; Idx < AllSockets.Num(); ++Idx) + { + // Assign the unnamed sockets with default names + if (AllSockets[Idx].Name.IsEmpty()) + AllSockets[Idx].Name = TEXT("Socket ") + FString::FromInt(Idx); + } + + // ensure the socket names are unique. (Unreal will use the first socket if multiple socket have the same name) + for (int32 Idx_i = 0; Idx_i < AllSockets.Num(); ++Idx_i) + { + int32 Count = 0; + for (int32 Idx_j = Idx_i + 1; Idx_j < AllSockets.Num(); ++Idx_j) + { + if (AllSockets[Idx_i].Name.Equals(AllSockets[Idx_j].Name)) + { + Count += 1; + AllSockets[Idx_j].Name = AllSockets[Idx_j].Name + "_" + FString::FromInt(Count); + } + } + } + + // Clear all the sockets of the output static mesh. + StaticMesh->Sockets.Empty(); + + for (int32 nSocket = 0; nSocket < AllSockets.Num(); nSocket++) + { + // Create a new Socket + UStaticMeshSocket* Socket = NewObject(StaticMesh); + if (!Socket || Socket->IsPendingKill()) + continue; + + Socket->RelativeLocation = AllSockets[nSocket].Transform.GetLocation(); + Socket->RelativeRotation = FRotator(AllSockets[nSocket].Transform.GetRotation()); + Socket->RelativeScale = AllSockets[nSocket].Transform.GetScale3D(); + Socket->SocketName = FName(*AllSockets[nSocket].Name); + + // Socket Tag + FString Tag; + if (!AllSockets[nSocket].Tag.IsEmpty()) + Tag = AllSockets[nSocket].Tag; + + // The actor will be stored temporarily in the socket's Tag as we need a StaticMeshComponent to add an actor to the socket + Tag += TEXT("|") + AllSockets[nSocket].Actor; + + Socket->Tag = Tag; + Socket->bSocketCreatedAtImport = true; + + StaticMesh->Sockets.Add(Socket); + } + + return true; +} + +bool +FHoudiniEngineUtils::CreateAttributesFromTags( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const TArray& Tags ) +{ + if (Tags.Num() <= 0) + return false; + + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Get the destination part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); + + bool NeedToCommitGeo = false; + for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) + { + FString TagString; + Tags[TagIdx].ToString(TagString); + SanitizeHAPIVariableName(TagString); + + // Create a primitive attribute for the tag + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + AttributeInfo.count = PartInfo.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; + + FString AttributeName = TEXT(HAPI_UNREAL_ATTRIB_TAG_PREFIX) + FString::FromInt(TagIdx); + AttributeName.RemoveSpacesInline(); + + Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo); + + if (Result != HAPI_RESULT_SUCCESS) + continue; + + TArray TagStr; + TagStr.Add(FHoudiniEngineUtils::ExtractRawString(TagString)); + + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo, + TagStr.GetData(), 0, AttributeInfo.count); + + if (HAPI_RESULT_SUCCESS == Result) + NeedToCommitGeo = true; + + // Free memory for allocated by ExtractRawString + FHoudiniEngineUtils::FreeRawStringMemory(TagStr); + } + + return NeedToCommitGeo; +} + +bool +FHoudiniEngineUtils::CreateGroupsFromTags( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const TArray& Tags ) +{ + if (Tags.Num() <= 0) + return true; + + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Get the destination part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); + + bool NeedToCommitGeo = false; + for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) + { + FString TagString; + Tags[TagIdx].ToString(TagString); + SanitizeHAPIVariableName(TagString); + + const char * TagStr = FHoudiniEngineUtils::ExtractRawString(TagString); + + // Create a primitive group for this tag + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, TagStr) ) + { + // Set the group's Memberships + TArray GroupArray; + GroupArray.Init(1, PartInfo.faceCount); + + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_GROUPTYPE_PRIM, TagStr, + GroupArray.GetData(), 0, PartInfo.faceCount) ) + { + NeedToCommitGeo = true; + } + } + + // Free memory allocated by ExtractRawString() + FHoudiniEngineUtils::FreeRawStringMemory(TagStr); + } + + return NeedToCommitGeo; +} + + +bool +FHoudiniEngineUtils::SanitizeHAPIVariableName(FString& String) +{ + // Only keep alphanumeric characters, underscores + // Also, if the first character is a digit, append an underscore at the beginning + TArray& StrArray = String.GetCharArray(); + if (StrArray.Num() <= 0) + return false; + + for (auto& CurChar : StrArray) + { + const bool bIsValid = (CurChar >= TEXT('A') && CurChar <= TEXT('Z')) + || (CurChar >= TEXT('a') && CurChar <= TEXT('z')) + || (CurChar >= TEXT('0') && CurChar <= TEXT('9')) + || (CurChar == TEXT('_')) || (CurChar == TEXT('\0')); + + if(bIsValid) + continue; + + CurChar = TEXT('_'); + } + + if (StrArray.Num() > 0) + { + TCHAR FirstChar = StrArray[0]; + if (FirstChar >= TEXT('0') && FirstChar <= TEXT('9')) + StrArray.Insert(TEXT('_'), 0); + } + + return true; +} + +bool +FHoudiniEngineUtils::GetUnrealTagAttributes( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags) +{ + FString TagAttribBase = TEXT("unreal_tag_"); + bool bAttributeFound = true; + int32 TagIdx = 0; + while (bAttributeFound) + { + FString CurrentTagAttr = TagAttribBase + FString::FromInt(TagIdx++); + bAttributeFound = HapiCheckAttributeExists(GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), HAPI_ATTROWNER_PRIM); + if (!bAttributeFound) + break; + + // found the unreal_tag_X attribute, get its value and add it to the array + FString TagValue = FString(); + + // Create an AttributeInfo + { + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) + { + TagValue = StringData[0]; + } + } + + FName NameTag = *TagValue; + OutTags.Add(NameTag); + } + + return true; +} + + +int32 +FHoudiniEngineUtils::GetPropertyAttributeList( + const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundPropertyAttributes) +{ + // Get all the detail uprop attributes on the HGPO + int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_DETAIL); + + // .. then the primitive uprop attributes + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_PRIM); + + return FoundCount; +} + + +int32 +FHoudiniEngineUtils::GetGenericAttributeList( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FString& InGenericAttributePrefix, + TArray& OutFoundAttributes, + const HAPI_AttributeOwner& AttributeOwner, + const int32& InAttribIndex) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineUtils::GetGenericAttributeList); + + // Get the part info to get the attribute counts for the specified owner + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), InGeoNodeId, InPartId, &PartInfo), false); + + int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; + + // Get all attribute names for that part + TArray AttribNameSHArray; + AttribNameSHArray.SetNum(nAttribCount); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, AttributeOwner, + AttribNameSHArray.GetData(), nAttribCount)) + { + return 0; + } + + // For everything but detail attribute, + // if an attribute index was specified, only extract the attribute value for that specific index + // if not, extract all values for the given attribute + bool HandleSplit = false; + int32 AttribIndex = -1; + if ((AttributeOwner != HAPI_ATTROWNER_DETAIL) && (InAttribIndex != -1)) + { + // The index has already been specified so we'll use it + HandleSplit = true; + AttribIndex = InAttribIndex; + } + + int32 FoundCount = 0; + for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) + { + int32 AttribNameSH = (int32)AttribNameSHArray[Idx]; + FString AttribName = TEXT(""); + FHoudiniEngineString::ToFString(AttribNameSH, AttribName); + if (!AttribName.StartsWith(InGenericAttributePrefix, ESearchCase::IgnoreCase)) + continue; + + // Get the Attribute Info + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), AttributeOwner, &AttribInfo)) + { + // failed to get that attribute's info + continue; + } + + int32 AttribStart = 0; + int32 AttribCount = AttribInfo.count; + if (HandleSplit) + { + // For split primitives, we need to only get only one value for the proper split prim + // Make sure that the split index is valid + if (AttribIndex >= 0 && AttribIndex < AttribInfo.count) + { + AttribStart = AttribIndex; + AttribCount = 1; + } + } + + // + FHoudiniGenericAttribute CurrentGenericAttribute; + // Remove the generic attribute prefix + CurrentGenericAttribute.AttributeName = AttribName.Right(AttribName.Len() - InGenericAttributePrefix.Len()); + + CurrentGenericAttribute.AttributeOwner = (EAttribOwner)AttribInfo.owner; + + // Get the attribute type and tuple size + CurrentGenericAttribute.AttributeType = (EAttribStorageType)AttribInfo.storage; + CurrentGenericAttribute.AttributeCount = AttribInfo.count; + CurrentGenericAttribute.AttributeTupleSize = AttribInfo.tupleSize; + + if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT64) + { + // Initialize the value array + CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloat64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, 0, + CurrentGenericAttribute.DoubleValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT) + { + // Initialize the value array + TArray FloatValues; + FloatValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, FloatValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to double + CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < FloatValues.Num(); n++) + CurrentGenericAttribute.DoubleValues[n] = (double)FloatValues[n]; + + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT64) + { + // Initialize the value array + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, CurrentGenericAttribute.IntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT) + { + // Initialize the value array + TArray IntValues; + IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, IntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to int64 + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < IntValues.Num(); n++) + CurrentGenericAttribute.IntValues[n] = (int64)IntValues[n]; + + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + // Initialize a string handle array + TArray HapiSHArray; + HapiSHArray.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the string handle(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + HapiSHArray.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert the String Handles to FStrings + // using a map to minimize the number of HAPI calls + FHoudiniEngineString::SHArrayToFStringArray(HapiSHArray, CurrentGenericAttribute.StringValues); + } + else + { + // Unsupported type, skipping! + continue; + } + + // We can add the UPropertyAttribute to the array + OutFoundAttributes.Add(CurrentGenericAttribute); + FoundCount++; + } + + return FoundCount; +} + + +void +FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject( + UObject* InObject, const FHoudiniGeoPartObject& InHGPO) +{ + if (!InObject || InObject->IsPendingKill()) + return; + + // Get the list of all the Properties to modify from the HGPO's attributes + TArray PropertiesAttributesToModify; + if (!FHoudiniEngineUtils::GetPropertyAttributeList(InHGPO, PropertiesAttributesToModify)) + return; + + // Iterate over the found Property attributes + for (const auto& CurrentPropAttribute : PropertiesAttributesToModify) + { + // Get the current Property Attribute + const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; + if (CurrentPropertyName.IsEmpty()) + continue; + + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) + continue; + + // Success! + FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropertyName, *ClassName, *ObjectName); + } +} + + +void +FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const FString& Key, const FString& Value) +{ + if (!Package || Package->IsPendingKill()) + return; + + UMetaData * MetaData = Package->GetMetaData(); + if (MetaData && !MetaData->IsPendingKill()) + MetaData->SetValue(Object, *Key, *Value); +} + + +bool +FHoudiniEngineUtils::AddLevelPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + ULevel* InLevel, + const int32& InCount) +{ + if (InNodeId < 0 || InCount <= 0) + return false; + + if (!InLevel || InLevel->IsPendingKill()) + return false; + + // Extract the level path from the level + FString LevelPath = InLevel->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = InCount; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(InCount); + for (int32 Idx = 0; Idx < InCount; ++Idx) + { + PrimitiveAttrs[Idx] = LevelPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} + + +bool +FHoudiniEngineUtils::AddActorPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + AActor* InActor, + const int32& InCount) +{ + if (InNodeId < 0 || InCount <= 0) + return false; + + if (!InActor || InActor->IsPendingKill()) + return false; + + // Extract the actor path + FString ActorPath = InActor->GetPathName(); + + // Get name of attribute used for Actor path + std::string MarshallingAttributeActorPath = HAPI_UNREAL_ATTRIB_ACTOR_PATH; + + // Marshall in Actor path. + HAPI_AttributeInfo AttributeInfoActorPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoActorPath); + AttributeInfoActorPath.count = InCount; + AttributeInfoActorPath.tupleSize = 1; + AttributeInfoActorPath.exists = true; + AttributeInfoActorPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoActorPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoActorPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string ActorPathCStr = TCHAR_TO_ANSI(*ActorPath); + const char* ActorPathCStrRaw = ActorPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(InCount); + for (int32 Idx = 0; Idx < InCount; ++Idx) + { + PrimitiveAttrs[Idx] = ActorPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, + MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoActorPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_actor_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} + + +bool +FHoudiniEngineUtils::ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx) +{ + const TArray< FVector2D > & LightmapUVs = RawMesh.WedgeTexCoords[LightmapSourceIdx]; + const TArray< uint32 > & Indices = RawMesh.WedgeIndices; + + if (LightmapUVs.Num() != Indices.Num()) + { + // This is invalid raw mesh; by design we consider that it contains invalid lightmap faces. + return true; + } + + for (int32 Idx = 0; Idx < Indices.Num(); Idx += 3) + { + const FVector2D & uv0 = LightmapUVs[Idx + 0]; + const FVector2D & uv1 = LightmapUVs[Idx + 1]; + const FVector2D & uv2 = LightmapUVs[Idx + 2]; + + if (uv0 == uv1 && uv1 == uv2) + { + // Detect invalid lightmap face, can stop. + return true; + } + } + + // Otherwise there are no invalid lightmap faces. + return false; +} + +void +FHoudiniEngineUtils::CreateSlateNotification( + const FString& NotificationString, const float& NotificationExpire, const float& NotificationFadeOut ) +{ +#if WITH_EDITOR + // Trying to display SlateNotifications while in a background thread will crash UE + if (!IsInGameThread() && !IsInSlateThread() && !IsInAsyncLoadingThread()) + return; + + // Check whether we want to display Slate notifications. + bool bDisplaySlateCookingNotifications = true; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return; + + FText NotificationText = FText::FromString(NotificationString); + FNotificationInfo Info(NotificationText); + + Info.bFireAndForget = true; + Info.FadeOutDuration = NotificationFadeOut; + Info.ExpireDuration = NotificationExpire; + + TSharedPtr HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + FSlateNotificationManager::Get().AddNotification(Info); +#endif + + return; +} + +FString +FHoudiniEngineUtils::GetHoudiniEnginePluginDir() +{ + FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(EnginePluginDir)) + return EnginePluginDir; + + FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(ProjectPluginDir)) + return ProjectPluginDir; + + TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); + FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; + if (FPaths::DirectoryExists(PluginBaseDir)) + return PluginBaseDir; + + HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); + + return EnginePluginDir; +} + + +HAPI_Result +FHoudiniEngineUtils::CreateNode( + const HAPI_NodeId& InParentNodeId, + const FString& InOperatorName, + const FString& InNodeLabel, + const HAPI_Bool& bInCookOnCreation, + HAPI_NodeId* OutNewNodeId) +{ + // Call HAPI::CreateNode + HAPI_Result Result = FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), + InParentNodeId, TCHAR_TO_UTF8(*InOperatorName), TCHAR_TO_UTF8(*InNodeLabel), bInCookOnCreation, OutNewNodeId); + + // Return now if CreateNode fialed + if (Result != HAPI_RESULT_SUCCESS) + return Result; + + // Loop on the cook_state status until it's ready + int CurrentStatus = HAPI_State::HAPI_STATE_STARTING_LOAD; + while (CurrentStatus > HAPI_State::HAPI_STATE_MAX_READY_STATE) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), + HAPI_StatusType::HAPI_STATUS_COOK_STATE, &CurrentStatus)) + { + // Exit the loop if GetStatus somehow fails + break; + } + } + + if (CurrentStatus == HAPI_STATE_READY_WITH_FATAL_ERRORS) + { + // Fatal errors - failed + HOUDINI_LOG_ERROR(TEXT("Failed to create node %s - %s"), *InOperatorName, *InNodeLabel); + return HAPI_RESULT_FAILURE; + } + else if (CurrentStatus == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // Mention the errors - still return success + HOUDINI_LOG_WARNING(TEXT("Cook errors when creating node %s - %s"), *InOperatorName, *InNodeLabel); + } + + return HAPI_RESULT_SUCCESS; +} + + +int32 +FHoudiniEngineUtils::HapiGetCookCount(const HAPI_NodeId& InNodeId) +{ + int32 CookCount = -1; + + FHoudiniApi::GetTotalCookCount( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_ANY, true, &CookCount); + + /* + // TODO: + // Use HAPI_GetCookingTotalCount() when available + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + + int32 CookCount = -1; + HAPI_Result Result = FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), InNodeId, &NodeInfo); + + if (Result != HAPI_RESULT_FAILURE) + { + if (NodeInfo.type != HAPI_NODETYPE_OBJ) + { + // For SOP assets, get the cook count straight from the Asset Node + CookCount = NodeInfo.totalCookCount; + } + else + { + // For OBJ nodes, get the cook count from the display geos + // Retrieve information about each object contained within our asset. + TArray< HAPI_ObjectInfo > ObjectInfos; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(InNodeId, ObjectInfos)) + return false; + + for (auto CurrentHapiObjectInfo : ObjectInfos) + { + // Get the Display Geo's info + HAPI_GeoInfo DisplayHapiGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) + { + continue; + } + + HAPI_NodeInfo DisplayNodeInfo; + FHoudiniApi::NodeInfo_Init(&DisplayNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), DisplayHapiGeoInfo.nodeId, &DisplayNodeInfo)) + { + continue; + } + + CookCount += DisplayNodeInfo.totalCookCount; + } + } + } + */ + + return CookCount; +} + +bool +FHoudiniEngineUtils::GetLevelPathAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutLevelPaths, + HAPI_AttributeOwner InAttributeOwner) +{ + // --------------------------------------------- + // Attribute: unreal_level_path + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_LEVEL_PATH, AttributeInfo, OutLevelPaths, 1, InAttributeOwner)) + { + if (OutLevelPaths.Num() > 0) + return true; + } + + OutLevelPaths.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetOutputNameAttribute(const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutOutputNames) +{ + // --------------------------------------------- + // Attribute: unreal_output_name + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, AttributeInfo, OutOutputNames, 1)) + { + if (OutOutputNames.Num() > 0) + return true; + } + + OutOutputNames.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, AttributeInfo, OutOutputNames, 1)) + { + if (OutOutputNames.Num() > 0) + return true; + } + + OutOutputNames.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetTileAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutTileValues, + const HAPI_AttributeOwner& InAttribOwner) +{ + // --------------------------------------------- + // Attribute: tile + // --------------------------------------------- + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, + AttribInfoTile, + OutTileValues, + 0, + InAttribOwner)) + { + if (OutTileValues.Num() > 0) + return true; + } + + OutTileValues.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + TArray& OutBakeFolder, + HAPI_PartId InPartId) +{ + OutBakeFolder.Empty(); + + HAPI_AttributeInfo BakeFolderAttribInfo; + FHoudiniApi::AttributeInfo_Init(&BakeFolderAttribInfo); + if (HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_DETAIL)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + + if (HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_PRIM)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + + OutBakeFolder.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeActorAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeActorNames, + HAPI_AttributeOwner InAttributeOwner) +{ + // --------------------------------------------- + // Attribute: unreal_bake_actor + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_BAKE_ACTOR, AttributeInfo, OutBakeActorNames, 1, InAttributeOwner)) + { + if (OutBakeActorNames.Num() > 0) + return true; + } + + OutBakeActorNames.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeOutlinerFolders, + HAPI_AttributeOwner InAttributeOwner) +{ + // --------------------------------------------- + // Attribute: unreal_bake_outliner_folder + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, AttributeInfo, OutBakeOutlinerFolders, 1, InAttributeOwner)) + { + if (OutBakeOutlinerFolders.Num() > 0) + return true; + } + + OutBakeOutlinerFolders.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeFolderOverridePath( + const HAPI_NodeId& InGeoId, + FString& OutBakeFolder, + HAPI_PartId InPartId) +{ + FString BakeFolderOverride; + + TArray StringData; + if (GetBakeFolderAttribute(InGeoId, StringData, InPartId)) + { + BakeFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); + } + + if (BakeFolderOverride.StartsWith("Game/")) + { + BakeFolderOverride = "/" + BakeFolderOverride; + } + + FString AbsoluteOverridePath; + if (BakeFolderOverride.StartsWith("/Game/")) + { + const FString RelativePath = FPaths::ProjectContentDir() + BakeFolderOverride.Mid(6, BakeFolderOverride.Len() - 6); + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + if (!BakeFolderOverride.IsEmpty()) + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolderOverride); + } + + // Check Validity of the path + if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) + { + // Only display a warning if the path is invalid, empty is fine + if (!AbsoluteOverridePath.IsEmpty()) + HOUDINI_LOG_WARNING(TEXT("Invalid override bake path: %s"), *BakeFolderOverride); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + OutBakeFolder = HoudiniRuntimeSettings->DefaultBakeFolder; + + return false; + } + + OutBakeFolder = BakeFolderOverride; + return true; +} + +bool +FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) +{ + if (!InActor || !InDesiredLevel) + return false; + + ULevel* PreviousLevel = InActor->GetLevel(); + if (PreviousLevel == InDesiredLevel) + return true; + + UWorld* CurrentWorld = InActor->GetWorld(); + if(CurrentWorld) + CurrentWorld->RemoveActor(InActor, true); + + //Set the outer of Actor to NewLevel + InActor->Rename((const TCHAR *)0, InDesiredLevel); + InDesiredLevel->Actors.Add(InActor); + + return true; +} + +bool +FHoudiniEngineUtils::HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions, const bool& bWaitForCompletion) +{ + // Check for an invalid node id + if (InNodeId < 0) + return false; + + // No Cook Options were specified, use the default one + if (InCookOptions == nullptr) + { + // Use the default cook options + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); + } + else + { + // Use the provided CookOptions + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, InCookOptions), false); + } + + // If we don't need to wait for completion, return now + if (!bWaitForCompletion) + return true; + + // Wait for the cook to finish + HAPI_Result Result = HAPI_RESULT_SUCCESS; + while (true) + { + // Get the current cook status + int Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // The cook has been successful. + return true; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // There was an error while cooking the node. + //FString CookResultString = FHoudiniEngineUtils::GetCookResult(); + //HOUDINI_LOG_ERROR(); + return false; + } + + // We want to yield a bit. + FPlatformProcess::Sleep(0.1f); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h index caa5fd392..9f40a517b 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h @@ -1,624 +1,624 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniEnginePrivatePCH.h" -#include "EngineUtils.h" -#include - -#include "HoudiniOutput.h" -#include "HoudiniPackageParams.h" -#include "Containers/UnrealString.h" - -#include "SSCSEditor.h" - - -class FString; -class UStaticMesh; -class UHoudiniAsset; -class UHoudiniAssetComponent; - -struct FHoudiniPartInfo; -struct FHoudiniMeshSocket; -struct FHoudiniGeoPartObject; -struct FHoudiniGenericAttribute; - -struct FRawMesh; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniInstancerType : uint8; - -struct HOUDINIENGINE_API FHoudiniEngineUtils -{ - friend struct FUnrealMeshTranslator; - - public: - // Load libHAPI and return handle to it, also store location of loaded libHAPI in passed argument. - static void* LoadLibHAPI(FString& StoredLibHAPILocation); - - // Return true if module has been properly initialized. - static bool IsInitialized(); - - // Return type of license used. - static bool GetLicenseType(FString & LicenseType); - - // Cook the specified node id - // if the cook options are null, the defualt one will be used - // if bWaitForCompletion is true, this call will be blocking until the cook is finished - static bool HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions = nullptr, const bool& bWaitForCompletion = false); - - // Return a specified HAPI status string. - static const FString GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity); - - // Return a string representing cooking result. - static const FString GetCookResult(); - - // Return a string indicating cook state. - static const FString GetCookState(); - - // Return a string error description. - static const FString GetErrorDescription(); - - // Return a string description of error from a given error code. - static const FString GetErrorDescription(HAPI_Result Result); - - // Return the errors, warning and messages on a specified node - static const FString GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId); - - static const FString GetCookLog(TArray& InHACs); - - static const FString GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent); - - // Updates the Object transform of a Houdini Asset Component - static bool UploadHACTransform(UHoudiniAssetComponent* HAC); - - // Convert FString to std::string - static void ConvertUnrealString(const FString & UnrealString, std::string& String); - - // Wrapper for the CreateNode function - // As HAPI_CreateNode is an async call, this function actually waits for the node creation to be done before returning - static HAPI_Result CreateNode( - const HAPI_NodeId& InParentNodeId, - const FString& InOperatorName, - const FString& InNodeLabel, - const HAPI_Bool& bInCookOnCreation, - HAPI_NodeId* OutNewNodeId); - - static int32 HapiGetCookCount(const HAPI_NodeId& InNodeId); - - // HAPI : Retrieve the asset node's object transform. **/ - static bool HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform); - - // HAPI : Retrieve object transforms from given asset node id. - static bool HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms); - - // HAPI : Translate HAPI transform to Unreal one. - static void TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform); - - // HAPI : Translate HAPI Euler transform to Unreal one. - static void TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform); - - // HAPI : Translate Unreal transform to HAPI one. - static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform); - - // HAPI : Translate Unreal transform to HAPI Euler one. - static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_TransformEuler & HapiTransformEuler); - - // Return true if asset is valid. - static bool IsHoudiniNodeValid(const HAPI_NodeId& AssetId); - - // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. - static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos); - - // HAPI: Retrieve Path to the given Node, relative to the given Node - static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); - - // HAPI: Retrieve the relative for the given HGPO Node - static bool HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath); - - // HAPI : Return all group names for a given Geo. - static bool HapiGetGroupNames( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const HAPI_GroupType& GroupType, const bool& isPackedPrim, - TArray& OutGroupNames ); - - // HAPI : Retrieve group membership. - static bool HapiGetGroupMembership( - const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, - const HAPI_GroupType& GroupType, const FString & GroupName, - TArray& OutGroupMembership, bool& OutAllEquals); - - // HAPI : Given vertex list, retrieve new vertex list for a specified group. - // Return number of processed valid index vertices for this split. - static int32 HapiGetVertexListForGroup( - const HAPI_NodeId& GeoId, - const HAPI_PartInfo& PartInfo, - const FString& GroupName, - const TArray& FullVertexList, - TArray& NewVertexList, - TArray& AllVertexList, - TArray& AllFaceList, - TArray& AllGroupFaceIndices, - int32& FirstValidVertex, - int32& FirstValidPrim, - const bool& isPackedPrim); - - // HAPI : Get attribute data as float. - static bool HapiGetAttributeDataAsFloat( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize = 0, - HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as Integer. - static bool HapiGetAttributeDataAsInteger( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - const int32& InTupleSize = 0, - const HAPI_AttributeOwner& InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as strings. - static bool HapiGetAttributeDataAsString( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize = 0, - HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as strings. - static bool HapiGetAttributeDataAsStringFromInfo( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& InAttributeInfo, - TArray& OutData); - - // HAPI : Check if given attribute exists. - static bool HapiCheckAttributeExists( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - const char * AttribName, - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID); - - // HAPI: Returns all the attributes of a given type for a given owner - static int32 HapiGetAttributeOfType( - const HAPI_NodeId& GeoId, - const HAPI_NodeId& PartId, - const HAPI_AttributeOwner& AttributeOwner, - const HAPI_AttributeTypeInfo& AttributeType, - TArray& MatchingAttributesInfo, - TArray& MatchingAttributesName); - - // HAPI : Look for a parameter by name or tag and returns its index. Returns -1 if not found. - static HAPI_ParmId HapiFindParameterByNameOrTag( - const HAPI_NodeId& NodeId, const std::string& ParmName); - static HAPI_ParmId HapiFindParameterByNameOrTag( - const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo); - - // Returns true is the given Geo-Part is an attribute instancer - static bool IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); - - // Return true if given asset id is valid. - //static bool IsValidNodeId(HAPI_NodeId AssetId); - - // HAPI : Return a give node's parent ID, -1 if none - static HAPI_NodeId HapiGetParentNodeId(const HAPI_NodeId& NodeId); - - /** HAPI : Marshaling, disconnect input asset from a given slot. **/ - static bool HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex); - - // Destroy asset, returns the status. - static bool DestroyHoudiniAsset(const HAPI_NodeId& AssetId); - - // Loads an HDA file and returns its AssetLibraryId - static bool LoadHoudiniAsset( - UHoudiniAsset * HoudiniAsset, - HAPI_AssetLibraryId & OutAssetLibraryId); - - // Returns the name of the available subassets in a loaded HDA - static bool GetSubAssetNames( - const HAPI_AssetLibraryId& AssetLibraryId, - TArray< HAPI_StringHandle > & OutAssetNames); - - static bool OpenSubassetSelectionWindow( - TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ); - - // Returns the name of a Houdini asset. - static bool GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString); - - // Gets preset data for a given asset. - static bool GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer); - - // HAPI : Set asset transform. - static bool HapiSetAssetTransform(const HAPI_NodeId& AssetNodeId, const FTransform & Transform); - - // TODO: Move me somewhere else - static void AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC); - - // Triggers an update the details panel - // Will use an AsyncTask if we're not in the game thread - // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. - static void UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate); - - // Triggers an update the details panel - // Will use an AsyncTask if we're not in the game thread - // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. - static void UpdateEditorProperties(TArray InObjectsToUpdate, const bool& InForceFullUpdate); - - // Triggers an update the details panel - static void UpdateBlueprintEditor(UHoudiniAssetComponent* HAC); - - // Check if the Houdini asset component is being cooked - static bool IsHoudiniAssetComponentCooking(UObject* InObj); - - // Helper function to set attribute string data for a single FString - static HAPI_Result SetAttributeStringData( - const FString& InString, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo); - - // Helper function to set attribute string data for a FString array - static HAPI_Result SetAttributeStringData( - const TArray& InStringArray, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo); - - static bool HapiGetParameterDataAsString( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const FString& DefaultValue, - FString& OutValue); - - static bool HapiGetParameterDataAsInteger( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const int32& DefaultValue, - int32 & OutValue); - - static bool HapiGetParameterDataAsFloat( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const float& DefaultValue, - float& OutValue); - - // Returns a list with all the Property attributes found on a HGPO - static int32 GetPropertyAttributeList( - const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundUProps); - - // Updates all FProperty attributes found on a given object - static void UpdateAllPropertyAttributesOnObject( - UObject* InObject, const FHoudiniGeoPartObject& InHGPO); - - // Returns a list of all the generic attributes for a given attribute owner - static int32 GetGenericAttributeList( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FString& InGenericAttributePrefix, - TArray& OutFoundAttributes, - const HAPI_AttributeOwner& AttributeOwner, - const int32& InAttribIndex = -1); - - /* - // Tries to update values for all the UProperty attributes to apply on the object. - static void ApplyUPropertyAttributesOnObject( - UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify ); - */ - /* - static bool TryToFindInStructProperty( - UObject* Object, FString UPropertyNameToFind, UStructProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); - */ - /* - static bool TryToFindInArrayProperty( - UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); - */ - - static void AddHoudiniMetaInformationToPackage( - UPackage* Package, UObject* Object, const FString& Key, const FString& Value); - - // Adds the HoudiniLogo mesh to a Houdini Asset Component - static bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); - - // Removes the default Houdini logo mesh from a HAC - static bool RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC); - - // Indicates if a HAC has the Houdini logo mesh - static bool HasHoudiniLogo(UHoudiniAssetComponent* HAC); - - // - static HAPI_PartInfo ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo); - - // - static int32 AddMeshSocketsToArray_Group( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - TArray& AllSockets, const bool& isPackedPrim); - - // - static int32 AddMeshSocketsToArray_DetailAttribute( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - TArray& AllSockets, const bool& isPackedPrim); - - static bool AddMeshSocketsToStaticMesh( - UStaticMesh* StaticMesh, - TArray& AllSockets, - const bool& CleanImportSockets); - - // - static bool CreateGroupsFromTags( - const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); - - // - static bool CreateAttributesFromTags( - const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); - - static bool GetUnrealTagAttributes(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags); - - // Helper function to access the "unreal_level_path" attribute - static bool GetLevelPathAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutLevelPath, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to access the custom output name attribute - static bool GetOutputNameAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutOutputName); - - // Helper function to access the "tile" attribute - static bool GetTileAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutTileValue, - const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); - - // Helper function to access the "unreal_bake_folder" attribute - static bool GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - TArray& OutBakeFolder, - HAPI_PartId InPartId=0); - - // Helper function to access the bake output actor attribute (unreal_bake_actor) - static bool GetBakeActorAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeActorNames, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to access the bake output actor attribute (unreal_bake_outliner_folder) - static bool GetBakeOutlinerFolderAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeOutlinerFolders, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to get the bake folder override path. This is the "unreal_bake_folder" attribute, or if this - // does not exist or is invalid, the default bake folder path configured in the settings. - static bool GetBakeFolderOverridePath( - const HAPI_NodeId& InGeoId, - FString& OutBakeFolder, - HAPI_PartId InPartId=0); - - // Adds the "unreal_level_path" primitive attribute - static bool AddLevelPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - ULevel* InLevel, - const int32& InCount); - - // Adds the "unreal_actor_path" primitive attribute - static bool AddActorPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - AActor* InActor, - const int32& InCount); - - // Helper function used to extract a const char* from a FString - // !! Allocates memory using malloc that will need to be freed afterwards! - static char * ExtractRawString(const FString& Name); - - // Frees memory allocated by ExtractRawString() - static void FreeRawStringMemory(const char*& InRawString); - - // Frees memory allocated by ExtractRawString() - static void FreeRawStringMemory(TArray& InRawStringArray); - - // Make sure a string complies with Houdini's internal variable naming convention (group, attr etc..) - static bool SanitizeHAPIVariableName(FString& String); - - /** How many GUID symbols are used for package component name generation. **/ - static const int32 PackageGUIDComponentNameLength; - - /** How many GUID symbols are used for package item name generation. **/ - static const int32 PackageGUIDItemNameLength; - - /** Helper routine to check invalid lightmap faces. **/ - static bool ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx); - - // Helper function for creating a temporary Slate notification. - static void CreateSlateNotification( - const FString& NotificationString, - const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, - const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); - - static FString GetHoudiniEnginePluginDir(); - - // ------------------------------------------------- - // UWorld and UPackage utilities - // ------------------------------------------------- - - // Find actor in a given world by name - // Note that by default this will return all actors - template - static T* FindActorInWorld(UWorld* InWorld, FName ActorName, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) - { - T* OutActor = nullptr; - for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) - { - OutActor = *ActorIt; - if (!OutActor) - continue; - if (OutActor->GetFName().Compare(ActorName)==0) - return OutActor; - } - return nullptr; - } - - // Find an actor by name - static UWorld* FindWorldInPackage(const FString& PackagePath, bool bCreatedMissingPackage, bool& bOutPackageCreated); - - // Determine the appropriate world and level in which to spawn a new actor. - static bool FindWorldAndLevelForSpawning( - UWorld* CurrentWorld, - const FString& PackagePath, - bool bCreateMissingPackage, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bOutPackageCreated, - bool& bPackageInWorld); - - template - static T* SpawnActorInLevel(UWorld* InWorld, ULevel* InLevel) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = InLevel; - return InWorld->SpawnActor(SpawnParams); - } - - // Force the AssetRegistry to recursively rescan a path for - // any new packages that it may not know about, starting at the directory - // in which the given world package is located. This is typically useful - // for WorldComposition to detect new packages immediately after they - // were created. - static void RescanWorldPath(UWorld* InWorld); - - // ------------------------------------------------- - // Actor Utilities - // ------------------------------------------------- - - // Find in actor that belongs to the given outer matching the specified name. - // If the actor doesn't match the type, or is in a PendingKill state, rename it - // so that a new actor can be created with the given name. - // Note that if an actor with the give name was found, it will be returned via `OutFoundActor`. - static AActor* FindOrRenameInvalidActorGeneric(UClass* Class, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor); - - template - static T* FindOrRenameInvalidActor(UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) - { - return Cast( FindOrRenameInvalidActorGeneric(T::StaticClass(), InWorld, InName, OutFoundActor) ); - } - - // Moves an actor to the specified level - static bool MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel); - - // ------------------------------------------------- - // Debug Utilities - // ------------------------------------------------- - - // Log debug info for the given package - static void LogPackageInfo(const FString& InLongPackageName); - static void LogPackageInfo(const UPackage* InPackage); - - static void LogWorldInfo(const FString& InLongPackageName); - static void LogWorldInfo(const UWorld* InWorld); - - static FString HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType); - static FString HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState); - - // ------------------------------------------------- - // Generic naming / pathing utilities - // ------------------------------------------------- - - // Rename the actor to a unique / generated name. - static FName RenameToUniqueActor(AActor* InActor, const FString& InName); - - // Safely rename the actor by ensuring that there aren't any existing objects left - // in the actor's outer with the same name. If an existing object was found, rename it and return it. - static UObject* SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel=true); - - // ------------------------------------------------- - // PackageParam utilities - // ------------------------------------------------- - - static void FillInPackageParamsForBakingOutput( - FHoudiniPackageParams& OutPackageParams, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FString &BakeFolder, - const FString &ObjectName, - const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets); - - // ------------------------------------------------- - // Foliage utilities - // ------------------------------------------------- - - // If the foliage editor mode is active, repopulate the list of foliage types in the UI. - // NOTE: this is a currently a bit of a hack: we deactive and reactive the foliage mode (if it was active), - // since the relevant functions are not API exported. - // Returns true if the list was repopulated. - static bool RepopulateFoliageTypeListInUI(); - - public: - - static bool IsOuterHoudiniAssetComponent(UObject* Obj); - static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(UObject* Obj); - - protected: - - // Computes the XX.YY.ZZZ version string using HAPI_Version - static FString ComputeVersionString(bool ExtraDigit); - -#if PLATFORM_WINDOWS - // Attempt to locate libHAPI on Windows in the registry. Return handle if located and return location. - static void* LocateLibHAPIInRegistry( - const FString& HoudiniInstallationType, FString& StoredLibHAPILocation, bool LookIn32bitRegistry); -#endif - - // Triggers an update the details panel - //static void UpdateEditorProperties_Internal(UObject* ObjectToUpdate, const bool& bInForceFullUpdate); - - // Triggers an update the details panel - static void UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate); - - // Trigger an update of the Blueprint Editor on the game thread - static void UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniEnginePrivatePCH.h" +#include "EngineUtils.h" +#include + +#include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" +#include "Containers/UnrealString.h" + +#include "SSCSEditor.h" + + +class FString; +class UStaticMesh; +class UHoudiniAsset; +class UHoudiniAssetComponent; + +struct FHoudiniPartInfo; +struct FHoudiniMeshSocket; +struct FHoudiniGeoPartObject; +struct FHoudiniGenericAttribute; + +struct FRawMesh; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniInstancerType : uint8; + +struct HOUDINIENGINE_API FHoudiniEngineUtils +{ + friend struct FUnrealMeshTranslator; + + public: + // Load libHAPI and return handle to it, also store location of loaded libHAPI in passed argument. + static void* LoadLibHAPI(FString& StoredLibHAPILocation); + + // Return true if module has been properly initialized. + static bool IsInitialized(); + + // Return type of license used. + static bool GetLicenseType(FString & LicenseType); + + // Cook the specified node id + // if the cook options are null, the defualt one will be used + // if bWaitForCompletion is true, this call will be blocking until the cook is finished + static bool HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions = nullptr, const bool& bWaitForCompletion = false); + + // Return a specified HAPI status string. + static const FString GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity); + + // Return a string representing cooking result. + static const FString GetCookResult(); + + // Return a string indicating cook state. + static const FString GetCookState(); + + // Return a string error description. + static const FString GetErrorDescription(); + + // Return a string description of error from a given error code. + static const FString GetErrorDescription(HAPI_Result Result); + + // Return the errors, warning and messages on a specified node + static const FString GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId); + + static const FString GetCookLog(TArray& InHACs); + + static const FString GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent); + + // Updates the Object transform of a Houdini Asset Component + static bool UploadHACTransform(UHoudiniAssetComponent* HAC); + + // Convert FString to std::string + static void ConvertUnrealString(const FString & UnrealString, std::string& String); + + // Wrapper for the CreateNode function + // As HAPI_CreateNode is an async call, this function actually waits for the node creation to be done before returning + static HAPI_Result CreateNode( + const HAPI_NodeId& InParentNodeId, + const FString& InOperatorName, + const FString& InNodeLabel, + const HAPI_Bool& bInCookOnCreation, + HAPI_NodeId* OutNewNodeId); + + static int32 HapiGetCookCount(const HAPI_NodeId& InNodeId); + + // HAPI : Retrieve the asset node's object transform. **/ + static bool HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform); + + // HAPI : Retrieve object transforms from given asset node id. + static bool HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms); + + // HAPI : Translate HAPI transform to Unreal one. + static void TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform); + + // HAPI : Translate HAPI Euler transform to Unreal one. + static void TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform); + + // HAPI : Translate Unreal transform to HAPI one. + static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform); + + // HAPI : Translate Unreal transform to HAPI Euler one. + static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_TransformEuler & HapiTransformEuler); + + // Return true if asset is valid. + static bool IsHoudiniNodeValid(const HAPI_NodeId& AssetId); + + // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. + static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos); + + // HAPI: Retrieve Path to the given Node, relative to the given Node + static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); + + // HAPI: Retrieve the relative for the given HGPO Node + static bool HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath); + + // HAPI : Return all group names for a given Geo. + static bool HapiGetGroupNames( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const HAPI_GroupType& GroupType, const bool& isPackedPrim, + TArray& OutGroupNames ); + + // HAPI : Retrieve group membership. + static bool HapiGetGroupMembership( + const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, + const HAPI_GroupType& GroupType, const FString & GroupName, + TArray& OutGroupMembership, bool& OutAllEquals); + + // HAPI : Given vertex list, retrieve new vertex list for a specified group. + // Return number of processed valid index vertices for this split. + static int32 HapiGetVertexListForGroup( + const HAPI_NodeId& GeoId, + const HAPI_PartInfo& PartInfo, + const FString& GroupName, + const TArray& FullVertexList, + TArray& NewVertexList, + TArray& AllVertexList, + TArray& AllFaceList, + TArray& AllGroupFaceIndices, + int32& FirstValidVertex, + int32& FirstValidPrim, + const bool& isPackedPrim); + + // HAPI : Get attribute data as float. + static bool HapiGetAttributeDataAsFloat( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize = 0, + HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); + + // HAPI : Get attribute data as Integer. + static bool HapiGetAttributeDataAsInteger( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + const int32& InTupleSize = 0, + const HAPI_AttributeOwner& InOwner = HAPI_ATTROWNER_INVALID); + + // HAPI : Get attribute data as strings. + static bool HapiGetAttributeDataAsString( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize = 0, + HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); + + // HAPI : Get attribute data as strings. + static bool HapiGetAttributeDataAsStringFromInfo( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& InAttributeInfo, + TArray& OutData); + + // HAPI : Check if given attribute exists. + static bool HapiCheckAttributeExists( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + const char * AttribName, + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID); + + // HAPI: Returns all the attributes of a given type for a given owner + static int32 HapiGetAttributeOfType( + const HAPI_NodeId& GeoId, + const HAPI_NodeId& PartId, + const HAPI_AttributeOwner& AttributeOwner, + const HAPI_AttributeTypeInfo& AttributeType, + TArray& MatchingAttributesInfo, + TArray& MatchingAttributesName); + + // HAPI : Look for a parameter by name or tag and returns its index. Returns -1 if not found. + static HAPI_ParmId HapiFindParameterByNameOrTag( + const HAPI_NodeId& NodeId, const std::string& ParmName); + static HAPI_ParmId HapiFindParameterByNameOrTag( + const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo); + + // Returns true is the given Geo-Part is an attribute instancer + static bool IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); + + // Return true if given asset id is valid. + //static bool IsValidNodeId(HAPI_NodeId AssetId); + + // HAPI : Return a give node's parent ID, -1 if none + static HAPI_NodeId HapiGetParentNodeId(const HAPI_NodeId& NodeId); + + /** HAPI : Marshaling, disconnect input asset from a given slot. **/ + static bool HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex); + + // Destroy asset, returns the status. + static bool DestroyHoudiniAsset(const HAPI_NodeId& AssetId); + + // Loads an HDA file and returns its AssetLibraryId + static bool LoadHoudiniAsset( + UHoudiniAsset * HoudiniAsset, + HAPI_AssetLibraryId & OutAssetLibraryId); + + // Returns the name of the available subassets in a loaded HDA + static bool GetSubAssetNames( + const HAPI_AssetLibraryId& AssetLibraryId, + TArray< HAPI_StringHandle > & OutAssetNames); + + static bool OpenSubassetSelectionWindow( + TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ); + + // Returns the name of a Houdini asset. + static bool GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString); + + // Gets preset data for a given asset. + static bool GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer); + + // HAPI : Set asset transform. + static bool HapiSetAssetTransform(const HAPI_NodeId& AssetNodeId, const FTransform & Transform); + + // TODO: Move me somewhere else + static void AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC); + + // Triggers an update the details panel + // Will use an AsyncTask if we're not in the game thread + // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. + static void UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate); + + // Triggers an update the details panel + // Will use an AsyncTask if we're not in the game thread + // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. + static void UpdateEditorProperties(TArray InObjectsToUpdate, const bool& InForceFullUpdate); + + // Triggers an update the details panel + static void UpdateBlueprintEditor(UHoudiniAssetComponent* HAC); + + // Check if the Houdini asset component is being cooked + static bool IsHoudiniAssetComponentCooking(UObject* InObj); + + // Helper function to set attribute string data for a single FString + static HAPI_Result SetAttributeStringData( + const FString& InString, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo); + + // Helper function to set attribute string data for a FString array + static HAPI_Result SetAttributeStringData( + const TArray& InStringArray, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo); + + static bool HapiGetParameterDataAsString( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const FString& DefaultValue, + FString& OutValue); + + static bool HapiGetParameterDataAsInteger( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const int32& DefaultValue, + int32 & OutValue); + + static bool HapiGetParameterDataAsFloat( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const float& DefaultValue, + float& OutValue); + + // Returns a list with all the Property attributes found on a HGPO + static int32 GetPropertyAttributeList( + const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundUProps); + + // Updates all FProperty attributes found on a given object + static void UpdateAllPropertyAttributesOnObject( + UObject* InObject, const FHoudiniGeoPartObject& InHGPO); + + // Returns a list of all the generic attributes for a given attribute owner + static int32 GetGenericAttributeList( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FString& InGenericAttributePrefix, + TArray& OutFoundAttributes, + const HAPI_AttributeOwner& AttributeOwner, + const int32& InAttribIndex = -1); + + /* + // Tries to update values for all the UProperty attributes to apply on the object. + static void ApplyUPropertyAttributesOnObject( + UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify ); + */ + /* + static bool TryToFindInStructProperty( + UObject* Object, FString UPropertyNameToFind, UStructProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); + */ + /* + static bool TryToFindInArrayProperty( + UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); + */ + + static void AddHoudiniMetaInformationToPackage( + UPackage* Package, UObject* Object, const FString& Key, const FString& Value); + + // Adds the HoudiniLogo mesh to a Houdini Asset Component + static bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); + + // Removes the default Houdini logo mesh from a HAC + static bool RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC); + + // Indicates if a HAC has the Houdini logo mesh + static bool HasHoudiniLogo(UHoudiniAssetComponent* HAC); + + // + static HAPI_PartInfo ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo); + + // + static int32 AddMeshSocketsToArray_Group( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + TArray& AllSockets, const bool& isPackedPrim); + + // + static int32 AddMeshSocketsToArray_DetailAttribute( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + TArray& AllSockets, const bool& isPackedPrim); + + static bool AddMeshSocketsToStaticMesh( + UStaticMesh* StaticMesh, + TArray& AllSockets, + const bool& CleanImportSockets); + + // + static bool CreateGroupsFromTags( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); + + // + static bool CreateAttributesFromTags( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); + + static bool GetUnrealTagAttributes(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags); + + // Helper function to access the "unreal_level_path" attribute + static bool GetLevelPathAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutLevelPath, + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + + // Helper function to access the custom output name attribute + static bool GetOutputNameAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutOutputName); + + // Helper function to access the "tile" attribute + static bool GetTileAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutTileValue, + const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); + + // Helper function to access the "unreal_bake_folder" attribute + static bool GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + TArray& OutBakeFolder, + HAPI_PartId InPartId=0); + + // Helper function to access the bake output actor attribute (unreal_bake_actor) + static bool GetBakeActorAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeActorNames, + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + + // Helper function to access the bake output actor attribute (unreal_bake_outliner_folder) + static bool GetBakeOutlinerFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeOutlinerFolders, + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + + // Helper function to get the bake folder override path. This is the "unreal_bake_folder" attribute, or if this + // does not exist or is invalid, the default bake folder path configured in the settings. + static bool GetBakeFolderOverridePath( + const HAPI_NodeId& InGeoId, + FString& OutBakeFolder, + HAPI_PartId InPartId=0); + + // Adds the "unreal_level_path" primitive attribute + static bool AddLevelPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + ULevel* InLevel, + const int32& InCount); + + // Adds the "unreal_actor_path" primitive attribute + static bool AddActorPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + AActor* InActor, + const int32& InCount); + + // Helper function used to extract a const char* from a FString + // !! Allocates memory using malloc that will need to be freed afterwards! + static char * ExtractRawString(const FString& Name); + + // Frees memory allocated by ExtractRawString() + static void FreeRawStringMemory(const char*& InRawString); + + // Frees memory allocated by ExtractRawString() + static void FreeRawStringMemory(TArray& InRawStringArray); + + // Make sure a string complies with Houdini's internal variable naming convention (group, attr etc..) + static bool SanitizeHAPIVariableName(FString& String); + + /** How many GUID symbols are used for package component name generation. **/ + static const int32 PackageGUIDComponentNameLength; + + /** How many GUID symbols are used for package item name generation. **/ + static const int32 PackageGUIDItemNameLength; + + /** Helper routine to check invalid lightmap faces. **/ + static bool ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx); + + // Helper function for creating a temporary Slate notification. + static void CreateSlateNotification( + const FString& NotificationString, + const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, + const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); + + static FString GetHoudiniEnginePluginDir(); + + // ------------------------------------------------- + // UWorld and UPackage utilities + // ------------------------------------------------- + + // Find actor in a given world by name + // Note that by default this will return all actors + template + static T* FindActorInWorld(UWorld* InWorld, FName ActorName, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) + { + T* OutActor = nullptr; + for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) + { + OutActor = *ActorIt; + if (!OutActor) + continue; + if (OutActor->GetFName().Compare(ActorName)==0) + return OutActor; + } + return nullptr; + } + + // Find an actor by name + static UWorld* FindWorldInPackage(const FString& PackagePath, bool bCreatedMissingPackage, bool& bOutPackageCreated); + + // Determine the appropriate world and level in which to spawn a new actor. + static bool FindWorldAndLevelForSpawning( + UWorld* CurrentWorld, + const FString& PackagePath, + bool bCreateMissingPackage, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bOutPackageCreated, + bool& bPackageInWorld); + + template + static T* SpawnActorInLevel(UWorld* InWorld, ULevel* InLevel) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = InLevel; + return InWorld->SpawnActor(SpawnParams); + } + + // Force the AssetRegistry to recursively rescan a path for + // any new packages that it may not know about, starting at the directory + // in which the given world package is located. This is typically useful + // for WorldComposition to detect new packages immediately after they + // were created. + static void RescanWorldPath(UWorld* InWorld); + + // ------------------------------------------------- + // Actor Utilities + // ------------------------------------------------- + + // Find in actor that belongs to the given outer matching the specified name. + // If the actor doesn't match the type, or is in a PendingKill state, rename it + // so that a new actor can be created with the given name. + // Note that if an actor with the give name was found, it will be returned via `OutFoundActor`. + static AActor* FindOrRenameInvalidActorGeneric(UClass* Class, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor); + + template + static T* FindOrRenameInvalidActor(UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) + { + return Cast( FindOrRenameInvalidActorGeneric(T::StaticClass(), InWorld, InName, OutFoundActor) ); + } + + // Moves an actor to the specified level + static bool MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel); + + // ------------------------------------------------- + // Debug Utilities + // ------------------------------------------------- + + // Log debug info for the given package + static void LogPackageInfo(const FString& InLongPackageName); + static void LogPackageInfo(const UPackage* InPackage); + + static void LogWorldInfo(const FString& InLongPackageName); + static void LogWorldInfo(const UWorld* InWorld); + + static FString HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType); + static FString HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState); + + // ------------------------------------------------- + // Generic naming / pathing utilities + // ------------------------------------------------- + + // Rename the actor to a unique / generated name. + static FName RenameToUniqueActor(AActor* InActor, const FString& InName); + + // Safely rename the actor by ensuring that there aren't any existing objects left + // in the actor's outer with the same name. If an existing object was found, rename it and return it. + static UObject* SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel=true); + + // ------------------------------------------------- + // PackageParam utilities + // ------------------------------------------------- + + static void FillInPackageParamsForBakingOutput( + FHoudiniPackageParams& OutPackageParams, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FString &BakeFolder, + const FString &ObjectName, + const FString &HoudiniAssetName, + EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets); + + // ------------------------------------------------- + // Foliage utilities + // ------------------------------------------------- + + // If the foliage editor mode is active, repopulate the list of foliage types in the UI. + // NOTE: this is a currently a bit of a hack: we deactive and reactive the foliage mode (if it was active), + // since the relevant functions are not API exported. + // Returns true if the list was repopulated. + static bool RepopulateFoliageTypeListInUI(); + + public: + + static bool IsOuterHoudiniAssetComponent(UObject* Obj); + static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(UObject* Obj); + + protected: + + // Computes the XX.YY.ZZZ version string using HAPI_Version + static FString ComputeVersionString(bool ExtraDigit); + +#if PLATFORM_WINDOWS + // Attempt to locate libHAPI on Windows in the registry. Return handle if located and return location. + static void* LocateLibHAPIInRegistry( + const FString& HoudiniInstallationType, FString& StoredLibHAPILocation, bool LookIn32bitRegistry); +#endif + + // Triggers an update the details panel + //static void UpdateEditorProperties_Internal(UObject* ObjectToUpdate, const bool& bInForceFullUpdate); + + // Triggers an update the details panel + static void UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate); + + // Trigger an update of the Blueprint Editor on the game thread + static void UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp index aadcda693..bb9e6db58 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp @@ -1,777 +1,777 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoImportCommandlet.h" - -#include "DirectoryWatcherModule.h" -#include "Modules/ModuleManager.h" -#include "Misc/Guid.h" -#include "EditorFramework/AssetImportData.h" - -#include "Editor.h" -#include "FileHelpers.h" - -#include "MessageEndpointBuilder.h" - -#include "PackageTools.h" - -#include "IDirectoryWatcher.h" - -#include "Internationalization/Regex.h" - -#include "Interfaces/ISlateNullRendererModule.h" -#include "Rendering/SlateRenderer.h" -#include "Framework/Application/SlateApplication.h" - -#include "HoudiniPackageParams.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniOutput.h" -#include "HoudiniPDGImporterMessages.h" -#include "HoudiniMeshTranslator.h" -#include "HAL/ThreadManager.h" - - -UHoudiniGeoImportCommandlet::UHoudiniGeoImportCommandlet() -{ - HelpDescription = TEXT("Import BGEOs as UAssets. Includes an option to watch a directories and include new .bgeos created there."); - - HelpUsage = TEXT("HoudiniGeoImport Usage: HoudiniGeoImport {options} [filename.bgeo]"); - // "Options:\n" - // "\t-help or -?\n" - // "\t\tDisplays this help.\n\n" - // "\t-listen=manager_messaging_address\n\n" - // "\t\tListen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager.\n\n" - // "\t-watch=directory\n\n" - // "\t\tA directory to watch for new .bgeo files to import.\n\n" - // "\t-managerpid=owner_pid\n\n" - // "\t\tThe PID of the owner/manager process. If the manager process dies the commandlet also quits.\n\n" - // "\t-bake\n\n" - // "\t\tBake generated assets. Instancers are baked to blueprints. Not supported in -listen mode.\n\n" - // "\t[filename.bgeo]\n" - // "\t\tWhen not using -listen or -watch, the path to a .bgeo file must be specified for import.\n" - //); - - HelpParamNames = { - "help", - "listen", - "guid", - "watch", - "managerpid", - "bake" - }; - - HelpParamDescriptions = { - "Displays this help.", - "Listen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager. Expects the owning process' PID.", - "Specify a GUID for the commandlet. Useful to identify the commandlet when the messaging system is used.", - "A directory to watch for new .bgeo files to import.", - "The PID of the owner/manager process. If the manager process dies the commandlet also quits.", - "Bake generated assets. Instancers are baked to blueprints. Not supported in -listen mode." - }; - - IsClient = false; - IsEditor = true; - IsServer = false; - LogToConsole = true; - ShowProgress = false; - ShowErrorCount = false; - - // LogToConsole = false; - - Mode = EHoudiniGeoImportCommandletMode::None; - bBakeOutputs = false; -} - -void UHoudiniGeoImportCommandlet::PrintUsage() const -{ - HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpDescription); - HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpUsage); - const int32 NumOptions = HelpParamNames.Num(); - for (int32 Idx = 0; Idx < NumOptions; ++Idx) - { - HOUDINI_LOG_DISPLAY(TEXT("-%s\t%s"), *HelpParamNames[Idx], *HelpParamDescriptions[Idx]); - } -} - -void UHoudiniGeoImportCommandlet::PopulatePackageParams(const FString &InBGEOFilename, FHoudiniPackageParams& OutPackageParams) -{ - UObject* InParent = this; - - if (bBakeOutputs) - { - OutPackageParams.PackageMode = EPackageMode::Bake; - } - else - { - OutPackageParams.PackageMode = EPackageMode::CookToTemp; - } - OutPackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - OutPackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - OutPackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - OutPackageParams.HoudiniAssetName = FPaths::GetBaseFilename(InBGEOFilename); - OutPackageParams.HoudiniAssetActorName = FString(); - OutPackageParams.ObjectName = FPaths::GetBaseFilename(InBGEOFilename); - - if (!OutPackageParams.OuterPackage) - { - OutPackageParams.OuterPackage = InParent; - } - - if (!OutPackageParams.ComponentGUID.IsValid()) - { - // TODO: will need to reuse the GUID when reimporting? - OutPackageParams.ComponentGUID = FGuid::NewGuid(); - } - - OutPackageParams.bAttemptToLoadMissingPackages = true; -} - -void UHoudiniGeoImportCommandlet::TickDiscoveredFiles() -{ - for (auto &FileDataEntry : DiscoveredFiles) - { - FDiscoveredFileData &FileData = FileDataEntry.Value; - if (FileData.bImportNextTick && !FileData.bImported) - { - FileData.bImportNextTick = false; - FileData.ImportAttempts++; - - FHoudiniPackageParams PackageParams; - PopulatePackageParams(FileData.FileName, PackageParams); - TArray Outputs; - int32 Error = ImportBGEO(FileData.FileName, PackageParams, Outputs); - if (Error == 0) - { - FileData.bImported = true; - HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Done"), *FileData.FileName); - } - else - { - FileData.bImported = false; - HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Failed (%d)"), *FileData.FileName, Error); - } - } - } -} - -int32 UHoudiniGeoImportCommandlet::MainLoop() -{ - GIsRunning = true; - - IDirectoryWatcher* DirectoryWatcher = nullptr; - - if (Mode == EHoudiniGeoImportCommandletMode::Listen) - { - PDGEndpoint = FMessageEndpoint::Builder("PDG/BGEO Commandlet") - .Handling(this, &UHoudiniGeoImportCommandlet::HandleImportBGEOMessage) - .ReceivingOnThread(ENamedThreads::GameThread); - if (!PDGEndpoint.IsValid()) - { - GIsRunning = false; - return 3; - } - // Notify the manager that we are running - HOUDINI_LOG_DISPLAY(TEXT("Notifying the manager (%s) that we are running"), *ManagerAddress.ToString()); - // Try to send directly to the manager - // TODO: this initially direct message does not work, the address looks to be correct, perhaps there is some - // additional set up needed to connect / discover the endpoints? - PDGEndpoint->Send(new FHoudiniPDGImportBGEODiscoverMessage(Guid), ManagerAddress); - } - else if (Mode == EHoudiniGeoImportCommandletMode::Watch) - { - FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::Get().LoadModuleChecked(TEXT("DirectoryWatcher")); - DirectoryWatcher = DirectoryWatcherModule.Get(); - } - - // In UnrealEngine 4.25 and older we cannot tick the editor engine without slate being initialized. - if (!FSlateApplication::IsInitialized()) - { - FSlateApplication::InitHighDPI(false); - FSlateApplication::Create(); - } - - // If slate is initialized, make sure it has a renderer. If we have to create a renderer, create the null renderer. - if (FSlateApplication::IsInitialized() && !FSlateApplication::Get().GetRenderer()) - { - const TSharedPtr SlateRenderer = FModuleManager::Get().LoadModuleChecked("SlateNullRenderer").CreateSlateNullRenderer(); - const TSharedRef SlateRendererSharedRef = SlateRenderer.ToSharedRef(); - FSlateApplication::Get().InitializeRenderer(SlateRendererSharedRef); - } - - // in listen mode broadcast our presence every 60 seconds - // This is an attempt to test if it solves a rare issue where the endpoints appear to get - // "disconnected" and sending a message to a previously valid message address stops working, even though - // both processes are still running (happens especially when debugging with breakpoints) - const float BroadcastIntervalSeconds = 60.0f; - float LastbroadcastTimeSeconds = 0.0f; - - // main loop - while (GIsRunning && !IsEngineExitRequested()) - { - GEngine->UpdateTimeAndHandleMaxTickRate(); - GEngine->Tick(FApp::GetDeltaTime(), false); - - if (FSlateApplication::IsInitialized()) - { - FSlateApplication::Get().PumpMessages(); - FSlateApplication::Get().Tick(); - } - - // Required for FTimerManager to function - as it blocks ticks, if the frame counter doesn't change - GFrameCounter++; - - // update task graph - FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); - - FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()); - FThreadManager::Get().Tick(); - GEngine->TickDeferredCommands(); - - if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) - { - // DirectoryWatcher->Tick(FApp::GetDeltaTime()); - - // Process the discovered files - TickDiscoveredFiles(); - } - - if (OwnerProcHandle.IsValid() && !FPlatformProcess::IsProcRunning(OwnerProcHandle)) - { - // Our once valid owner has disappeared, so quit. - RequestEngineExit(TEXT("OwnerDisappeared")); - } - - if (Mode == EHoudiniGeoImportCommandletMode::Listen && PDGEndpoint.IsValid()) - { - const float TimeSeconds = FPlatformTime::Seconds(); - if (TimeSeconds - LastbroadcastTimeSeconds >= BroadcastIntervalSeconds) - { - LastbroadcastTimeSeconds = TimeSeconds; - // Broadcast a discover message to notify that we are still available - PDGEndpoint->Publish(new FHoudiniPDGImportBGEODiscoverMessage(Guid)); - - HOUDINI_LOG_MESSAGE(TEXT("Publishing FHoudiniPDGImportBGEODiscoverMessage(%s)"), *Guid.ToString()); - } - } - - FPlatformProcess::Sleep(0); - } - - PDGEndpoint.Reset(); - if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) - { - DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(DirectoryToWatch, DirectoryWatcherHandle); - } - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Shutdown(); - - GIsRunning = false; - - return 0; -} - -void UHoudiniGeoImportCommandlet::HandleImportBGEOMessage( - const FHoudiniPDGImportBGEOMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_DISPLAY(TEXT("Received BGEO import request from %s"), *InContext->GetSender().ToString()); - - FHoudiniPackageParams PackageParams; - InMessage.PopulatePackageParams(PackageParams); - - // The commandlet must try to load packages if FindPackage fails, since we unload packages when done - PackageParams.bAttemptToLoadMissingPackages = true; - - TArray Outputs; - TMap> OutputObjectAttributes; - TMap InstancedOutputPartData; - if (ImportBGEO(InMessage.FilePath, PackageParams, Outputs, &OutputObjectAttributes, &InstancedOutputPartData) == 0) - { - FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); - (*Reply) = InMessage; - // Reply->PopulateFromPackageParams(PackageParams); - Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Success; - - const int32 NumOutputs = Outputs.Num(); - Reply->Outputs.Init(FHoudiniPDGImportNodeOutput(), NumOutputs); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - FHoudiniPDGImportNodeOutput &MessageOutput = Reply->Outputs[Index]; - UHoudiniOutput* Output = Outputs[Index]; - for (const FHoudiniGeoPartObject& HGPO : Output->GetHoudiniGeoPartObjects()) - { - HOUDINI_LOG_WARNING(TEXT("HGPO %d %d %d"), HGPO.ObjectId, HGPO.GeoId, HGPO.PartId); - MessageOutput.HoudiniGeoPartObjects.Add(HGPO); - - // Get instancer data if this is an instancer output - if (Output->GetType() == EHoudiniOutputType::Instancer) - { - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = HGPO.ObjectId; - OutputIdentifier.GeoId = HGPO.GeoId; - OutputIdentifier.PartId = HGPO.PartId; - OutputIdentifier.PartName = HGPO.PartName; - - FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = InstancedOutputPartData.Find(OutputIdentifier); - if (InstancedOutputPartDataPtr) - { - InstancedOutputPartDataPtr->BuildFlatInstancedTransformsAndObjectPaths(); - MessageOutput.InstancedOutputPartData.Add(*InstancedOutputPartDataPtr); - } - else - { - MessageOutput.InstancedOutputPartData.Add(FHoudiniInstancedOutputPartData()); - } - } - } - for (const auto& Entry : Output->GetOutputObjects()) - { - HOUDINI_LOG_WARNING(TEXT("Identifier %d %d %d"), Entry.Key.ObjectId, Entry.Key.GeoId, Entry.Key.PartId); - - MessageOutput.OutputObjects.AddDefaulted(); - FHoudiniPDGImportNodeOutputObject& MessageOutputObject = MessageOutput.OutputObjects.Last(); - - FString PackagePath = IsValid(Entry.Value.OutputObject) ? Entry.Value.OutputObject->GetPathName() : ""; - MessageOutputObject.Identifier = Entry.Key; - MessageOutputObject.PackagePath = PackagePath; - const TArray* PropertyAttributes = OutputObjectAttributes.Find(Entry.Key); - if (PropertyAttributes) - MessageOutputObject.GenericAttributes = *PropertyAttributes; - MessageOutputObject.CachedAttributes = Entry.Value.CachedAttributes; - } - } - - PDGEndpoint->Send(Reply, InContext->GetSender()); - } - else - { - HOUDINI_LOG_WARNING(TEXT("BGEO import failed.")); - FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); - Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Failed; - PDGEndpoint->Send(Reply, InContext->GetSender()); - } - - // Cleanup the outputs (remove from root) - TArray PackagesToUnload; - for (UHoudiniOutput *CurOutput : Outputs) - { - if (!IsValid(CurOutput)) - continue; - - for (const auto& Entry : CurOutput->GetOutputObjects()) - { - if (IsValid(Entry.Value.OutputObject)) - { - UPackage *Outermost = Entry.Value.OutputObject->GetOutermost(); - if (IsValid(Outermost)) - { - PackagesToUnload.Add(Outermost); - } - - Entry.Value.OutputObject->RemoveFromRoot(); - } - } - - CurOutput->RemoveFromRoot(); - } - Outputs.Empty(); - OutputObjectAttributes.Empty(); - - if (PackagesToUnload.Num() > 0) - { - HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ..."), PackagesToUnload.Num()); - FText ErrorMessage; - if (!UPackageTools::UnloadPackages(PackagesToUnload, ErrorMessage)) - { - HOUDINI_LOG_WARNING(TEXT("Unload packages failed: %s"), *ErrorMessage.ToString()); - } - else - { - HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ... Success"), PackagesToUnload.Num()); - } - PackagesToUnload.Empty(); - } - - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); -} - -bool UHoudiniGeoImportCommandlet::StartHoudiniEngineSession() -{ - // Start Houdini Engine session - HOUDINI_LOG_DISPLAY(TEXT("Starting Houdini Engine session...")); - FHoudiniEngine& HoudiniEngine = FHoudiniEngine::Get(); - if (!HoudiniEngine.CreateSession( - EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe, - "hapi_bgeo_cmdlet")) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start Houdini Engine session.")); - return false; - } - - return true; -} - -int32 UHoudiniGeoImportCommandlet::ImportBGEO( - const FString &InFilename, - const FHoudiniPackageParams &InPackageParams, - TArray& OutOutputs, - TMap>* OutGenericAttributes, - TMap* OutInstancedOutputPartData) -{ - if (!IsHoudiniEngineSessionRunning() && !StartHoudiniEngineSession()) - { - return 2; - } - - FHoudiniPackageParams PackageParams = InPackageParams; - UHoudiniGeoImporter* GeoImporter = NewObject(this); - - TArray OldOutputs; - OutOutputs.Empty(); - - // 2. Update the file paths - HOUDINI_LOG_DISPLAY(TEXT("SetFilePath %s"), *InFilename); - if (!GeoImporter->SetFilePath(InFilename)) - return 1; - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - HOUDINI_LOG_DISPLAY(TEXT("LoadBGEOFileInHAPI")); - if (!GeoImporter->LoadBGEOFileInHAPI(NodeId)) - return 1; - - // Look for a bake folder override in the BGEO file - if (PackageParams.PackageMode == EPackageMode::Bake) - { - HOUDINI_LOG_DISPLAY(TEXT("Looking for bake folder override attribute...")); - // Get the geo id for the node id - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), NodeId, &DisplayGeoInfo)) - { - FString BakeFolderOverride; - const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); - if (bFoundOverride && !BakeFolderOverride.IsEmpty()) - { - PackageParams.BakeFolder = BakeFolderOverride; - HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override: %s"), *PackageParams.BakeFolder); - } - else - { - HOUDINI_LOG_DISPLAY(TEXT("No bake folder override, using: %s"), *PackageParams.BakeFolder); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could not find display geo node id (when looking for bake folder override).")); - } - } - - auto CleanUpAndExit = [&OutOutputs, GeoImporter, NodeId](int32 InExitCode) - { - GeoImporter->GetOutputObjects().Empty(); - for (UHoudiniOutput* Output : OutOutputs) - { - Output->RemoveFromRoot(); - } - OutOutputs.Empty(); - - if (NodeId >= 0) - GeoImporter->DeleteCreatedNode(NodeId); - - return InExitCode; - }; - - // 4. Get the output from the file node - HOUDINI_LOG_DISPLAY(TEXT("BuildOutputsForNode %d"), NodeId); - if (!GeoImporter->BuildOutputsForNode(NodeId, OldOutputs, OutOutputs)) - return CleanUpAndExit(1); - - // Create uniquely named packages, commandlet runs in conjunction - // with a main editor instance, so we cannot modify existing files - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - // FString PackageName; - // UPackage* Outer = PackageParams.CreatePackageForObject(PackageName); - UObject* Outer = this; - - // 5. Create the static meshes in the outputs - HOUDINI_LOG_DISPLAY(TEXT("Create Static Meshes")); - if (!GeoImporter->CreateStaticMeshes(OutOutputs, Outer, PackageParams)) - return CleanUpAndExit(1); - - //// 6. Create the landscape in the outputs - //if (!GeoImporter->CreateLandscapes(NewOutputs, Outer, PackageParams)) - // return CleanUpAndExit(1); - - // 7. Create the instancers in the outputs - if (OutInstancedOutputPartData) - { - if (!GeoImporter->CreateInstancerOutputPartData(OutOutputs, *OutInstancedOutputPartData)) - return CleanUpAndExit(1); - } - else - { - if (!GeoImporter->CreateInstancers(OutOutputs, Outer, PackageParams)) - return CleanUpAndExit(1); - } - - if (OutGenericAttributes) - { - // Collect all generic properties from Houdini, we need to pass these - // through to PDG manager - HOUDINI_LOG_DISPLAY(TEXT("Get Generic Attributes for static meshes")); - for (UHoudiniOutput* CurOutput : OutOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (const auto& Entry : CurOutput->GetOutputObjects()) - { - const FHoudiniOutputObjectIdentifier OutputIdentifier = Entry.Key; - TArray PropertyAttributes; - FHoudiniMeshTranslator::GetGenericPropertiesAttributes( - OutputIdentifier.GeoId, OutputIdentifier.PartId, - OutputIdentifier.PointIndex, OutputIdentifier.PrimitiveIndex, - PropertyAttributes); - OutGenericAttributes->Add(OutputIdentifier, PropertyAttributes); - } - } - } - - // 8. Delete the created node in Houdini - HOUDINI_LOG_DISPLAY(TEXT("DeleteCreatedNode %d"), NodeId); - if (!GeoImporter->DeleteCreatedNode(NodeId)) - { - // Not good, but not fatal.. - //return false; - } - - TArray PackagesToSave; - TArray& OutputObjects = GeoImporter->GetOutputObjects(); - for (UObject* Object : OutputObjects) - { - if (!IsValid(Object)) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("Created object: %s"), *Object->GetFullName()); - - UAssetImportData* AssetImportData = nullptr; - if (Object->IsA()) - { - UStaticMesh* SM = Cast(Object); - AssetImportData = SM->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); - SM->AssetImportData = AssetImportData; - } - } - - if (AssetImportData) - AssetImportData->Update(InFilename); - - Object->MarkPackageDirty(); - Object->PostEditChange(); - - UPackage* Package = Object->GetOutermost(); - if (IsValid(Package)) - { - PackagesToSave.AddUnique(Package); - } - } - - if (PackagesToSave.Num() > 0) - { - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); - } - - PackagesToSave.Empty(); - OutputObjects.Empty(); - - return 0; -} - -void UHoudiniGeoImportCommandlet::HandleDirectoryChanged(const TArray& InFileChangeDatas) -{ - const FRegexPattern BGEOPattern(TEXT(R"((.*)\.(bgeo(\.[^\.]*)?)$)")); - - for (const FFileChangeData& FileChangeData : InFileChangeDatas) - { - HOUDINI_LOG_MESSAGE(TEXT("HandleDirectoryChanged %d %s"), FileChangeData.Action, *FileChangeData.Filename); - - FRegexMatcher BGEOMatcher(BGEOPattern, FileChangeData.Filename.ToLower()); - if (BGEOMatcher.FindNext() && BGEOMatcher.GetCaptureGroup(2).StartsWith(TEXT("bgeo"))) - { - HOUDINI_LOG_DISPLAY(TEXT("Updating entry for %s..."), *FileChangeData.Filename); - const uint32 MaxImportAttempts = 3; - switch(FileChangeData.Action) - { - case FFileChangeData::FCA_Added: - case FFileChangeData::FCA_Modified: - if (DiscoveredFiles.Contains(FileChangeData.Filename)) - { - FDiscoveredFileData &FileData = DiscoveredFiles[FileChangeData.Filename]; - if (!FileData.bImported && FileData.ImportAttempts < MaxImportAttempts) - FileData.bImportNextTick = true; - else if (FileData.ImportAttempts >= MaxImportAttempts) - HOUDINI_LOG_WARNING(TEXT("Not importing %s, max attempts exceeded %d"), *FileData.FileName, FileData.ImportAttempts); - } - else - { - DiscoveredFiles.Add(FileChangeData.Filename, FDiscoveredFileData(FileChangeData.Filename, true)); - } - break; - case FFileChangeData::FCA_Removed: - DiscoveredFiles.Remove(FileChangeData.Filename); - break; - default: - HOUDINI_LOG_WARNING(TEXT("Unknown file change event %d for %s"), FileChangeData.Action, *FileChangeData.Filename); - } - } - } -} - -int32 UHoudiniGeoImportCommandlet::Main(const FString& InParams) -{ - TArray Tokens; - TArray Switches; - TMap Params; - ParseCommandLine(*InParams, Tokens, Switches, Params); - - if (Switches.Contains(TEXT("help")) || Switches.Contains(TEXT("?"))) - { - PrintUsage(); - return 0; - } - - if (Params.Contains(TEXT("guid"))) - { - const FString GuidStr = Params.FindChecked(TEXT("guid")); - FGuid::Parse(GuidStr, Guid); - - HOUDINI_LOG_DISPLAY(TEXT("GUID received: %s"), *Guid.ToString()); - } - else - { - Guid = FGuid::NewGuid(); - } - - // Set bake mode - if (Switches.Contains(TEXT("bake"))) - bBakeOutputs = true; - else - bBakeOutputs = false; - - if (Params.Contains(TEXT("listen"))) - { - Mode = EHoudiniGeoImportCommandletMode::Listen; - - if (!Params.Contains(TEXT("managerpid"))) - { - HOUDINI_LOG_ERROR(TEXT("'managerpid' is required when in -listen mode.")); - return 1; - } - - if (bBakeOutputs) - { - HOUDINI_LOG_ERROR(TEXT("'listen' mode does not support baking outputs (-bake).")); - return 1; - } - - // Get the manager's messaging address from the -listen param - const FString ManagerAddressStr = Params.FindChecked(TEXT("listen")); - if (!FMessageAddress::Parse(ManagerAddressStr, ManagerAddress)) - { - HOUDINI_LOG_ERROR(TEXT("The manager messaging address passed to -listen=%s is invalid."), *ManagerAddressStr); - return 1; - } - - // Get the manager pid and proc handle - uint32 OwnerProcessId = FCString::Strtoi(*Params.FindChecked(TEXT("managerpid")), nullptr, 10); - HOUDINI_LOG_DISPLAY(TEXT("Owner process Id: %d"), OwnerProcessId); - OwnerProcHandle = FPlatformProcess::OpenProcess(OwnerProcessId); - - return MainLoop(); - } - else if (Params.Contains(TEXT("watch"))) - { - Mode = EHoudiniGeoImportCommandletMode::Watch; - - HOUDINI_LOG_DISPLAY(TEXT("directory watch mode")); - FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); - IDirectoryWatcher* DirectoryWatcher = DirectoryWatcherModule.Get(); - if (DirectoryWatcher) - { - DirectoryToWatch = Params.FindChecked(TEXT("watch")); - if (FPaths::IsRelative(DirectoryToWatch)) - DirectoryToWatch = FPaths::ConvertRelativePathToFull(DirectoryToWatch); - - HOUDINI_LOG_DISPLAY(TEXT("Watching %s"), *DirectoryToWatch); - - DirectoryWatcher->RegisterDirectoryChangedCallback_Handle( - DirectoryToWatch, - IDirectoryWatcher::FDirectoryChanged::CreateUObject(this, &UHoudiniGeoImportCommandlet::HandleDirectoryChanged), - DirectoryWatcherHandle); - - return MainLoop(); - } - else - { - return 10; - } - } - else if (Tokens.Num() > 0) - { - Mode = EHoudiniGeoImportCommandletMode::SpecifiedFiles; - - if (!StartHoudiniEngineSession()) - return 2; - - const FString Filename = FPaths::IsRelative(Tokens[0]) ? FPaths::ConvertRelativePathToFull(Tokens[0]) : Tokens[0]; - FHoudiniPackageParams PackageParams; - PopulatePackageParams(Filename, PackageParams); - - TArray Outputs; - const int32 Result = ImportBGEO(Tokens[0], PackageParams, Outputs); - - for (UHoudiniOutput* Output : Outputs) - { - Output->RemoveFromRoot(); - } - Outputs.Empty(); - - return Result; - } - - return 0; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoImportCommandlet.h" + +#include "DirectoryWatcherModule.h" +#include "Modules/ModuleManager.h" +#include "Misc/Guid.h" +#include "EditorFramework/AssetImportData.h" + +#include "Editor.h" +#include "FileHelpers.h" + +#include "MessageEndpointBuilder.h" + +#include "PackageTools.h" + +#include "IDirectoryWatcher.h" + +#include "Internationalization/Regex.h" + +#include "Interfaces/ISlateNullRendererModule.h" +#include "Rendering/SlateRenderer.h" +#include "Framework/Application/SlateApplication.h" + +#include "HoudiniPackageParams.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniOutput.h" +#include "HoudiniPDGImporterMessages.h" +#include "HoudiniMeshTranslator.h" +#include "HAL/ThreadManager.h" + + +UHoudiniGeoImportCommandlet::UHoudiniGeoImportCommandlet() +{ + HelpDescription = TEXT("Import BGEOs as UAssets. Includes an option to watch a directories and include new .bgeos created there."); + + HelpUsage = TEXT("HoudiniGeoImport Usage: HoudiniGeoImport {options} [filename.bgeo]"); + // "Options:\n" + // "\t-help or -?\n" + // "\t\tDisplays this help.\n\n" + // "\t-listen=manager_messaging_address\n\n" + // "\t\tListen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager.\n\n" + // "\t-watch=directory\n\n" + // "\t\tA directory to watch for new .bgeo files to import.\n\n" + // "\t-managerpid=owner_pid\n\n" + // "\t\tThe PID of the owner/manager process. If the manager process dies the commandlet also quits.\n\n" + // "\t-bake\n\n" + // "\t\tBake generated assets. Instancers are baked to blueprints. Not supported in -listen mode.\n\n" + // "\t[filename.bgeo]\n" + // "\t\tWhen not using -listen or -watch, the path to a .bgeo file must be specified for import.\n" + //); + + HelpParamNames = { + "help", + "listen", + "guid", + "watch", + "managerpid", + "bake" + }; + + HelpParamDescriptions = { + "Displays this help.", + "Listen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager. Expects the owning process' PID.", + "Specify a GUID for the commandlet. Useful to identify the commandlet when the messaging system is used.", + "A directory to watch for new .bgeo files to import.", + "The PID of the owner/manager process. If the manager process dies the commandlet also quits.", + "Bake generated assets. Instancers are baked to blueprints. Not supported in -listen mode." + }; + + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = true; + ShowProgress = false; + ShowErrorCount = false; + + // LogToConsole = false; + + Mode = EHoudiniGeoImportCommandletMode::None; + bBakeOutputs = false; +} + +void UHoudiniGeoImportCommandlet::PrintUsage() const +{ + HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpDescription); + HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpUsage); + const int32 NumOptions = HelpParamNames.Num(); + for (int32 Idx = 0; Idx < NumOptions; ++Idx) + { + HOUDINI_LOG_DISPLAY(TEXT("-%s\t%s"), *HelpParamNames[Idx], *HelpParamDescriptions[Idx]); + } +} + +void UHoudiniGeoImportCommandlet::PopulatePackageParams(const FString &InBGEOFilename, FHoudiniPackageParams& OutPackageParams) +{ + UObject* InParent = this; + + if (bBakeOutputs) + { + OutPackageParams.PackageMode = EPackageMode::Bake; + } + else + { + OutPackageParams.PackageMode = EPackageMode::CookToTemp; + } + OutPackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + OutPackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + OutPackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + OutPackageParams.HoudiniAssetName = FPaths::GetBaseFilename(InBGEOFilename); + OutPackageParams.HoudiniAssetActorName = FString(); + OutPackageParams.ObjectName = FPaths::GetBaseFilename(InBGEOFilename); + + if (!OutPackageParams.OuterPackage) + { + OutPackageParams.OuterPackage = InParent; + } + + if (!OutPackageParams.ComponentGUID.IsValid()) + { + // TODO: will need to reuse the GUID when reimporting? + OutPackageParams.ComponentGUID = FGuid::NewGuid(); + } + + OutPackageParams.bAttemptToLoadMissingPackages = true; +} + +void UHoudiniGeoImportCommandlet::TickDiscoveredFiles() +{ + for (auto &FileDataEntry : DiscoveredFiles) + { + FDiscoveredFileData &FileData = FileDataEntry.Value; + if (FileData.bImportNextTick && !FileData.bImported) + { + FileData.bImportNextTick = false; + FileData.ImportAttempts++; + + FHoudiniPackageParams PackageParams; + PopulatePackageParams(FileData.FileName, PackageParams); + TArray Outputs; + int32 Error = ImportBGEO(FileData.FileName, PackageParams, Outputs); + if (Error == 0) + { + FileData.bImported = true; + HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Done"), *FileData.FileName); + } + else + { + FileData.bImported = false; + HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Failed (%d)"), *FileData.FileName, Error); + } + } + } +} + +int32 UHoudiniGeoImportCommandlet::MainLoop() +{ + GIsRunning = true; + + IDirectoryWatcher* DirectoryWatcher = nullptr; + + if (Mode == EHoudiniGeoImportCommandletMode::Listen) + { + PDGEndpoint = FMessageEndpoint::Builder("PDG/BGEO Commandlet") + .Handling(this, &UHoudiniGeoImportCommandlet::HandleImportBGEOMessage) + .ReceivingOnThread(ENamedThreads::GameThread); + if (!PDGEndpoint.IsValid()) + { + GIsRunning = false; + return 3; + } + // Notify the manager that we are running + HOUDINI_LOG_DISPLAY(TEXT("Notifying the manager (%s) that we are running"), *ManagerAddress.ToString()); + // Try to send directly to the manager + // TODO: this initially direct message does not work, the address looks to be correct, perhaps there is some + // additional set up needed to connect / discover the endpoints? + PDGEndpoint->Send(new FHoudiniPDGImportBGEODiscoverMessage(Guid), ManagerAddress); + } + else if (Mode == EHoudiniGeoImportCommandletMode::Watch) + { + FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::Get().LoadModuleChecked(TEXT("DirectoryWatcher")); + DirectoryWatcher = DirectoryWatcherModule.Get(); + } + + // In UnrealEngine 4.25 and older we cannot tick the editor engine without slate being initialized. + if (!FSlateApplication::IsInitialized()) + { + FSlateApplication::InitHighDPI(false); + FSlateApplication::Create(); + } + + // If slate is initialized, make sure it has a renderer. If we have to create a renderer, create the null renderer. + if (FSlateApplication::IsInitialized() && !FSlateApplication::Get().GetRenderer()) + { + const TSharedPtr SlateRenderer = FModuleManager::Get().LoadModuleChecked("SlateNullRenderer").CreateSlateNullRenderer(); + const TSharedRef SlateRendererSharedRef = SlateRenderer.ToSharedRef(); + FSlateApplication::Get().InitializeRenderer(SlateRendererSharedRef); + } + + // in listen mode broadcast our presence every 60 seconds + // This is an attempt to test if it solves a rare issue where the endpoints appear to get + // "disconnected" and sending a message to a previously valid message address stops working, even though + // both processes are still running (happens especially when debugging with breakpoints) + const float BroadcastIntervalSeconds = 60.0f; + float LastbroadcastTimeSeconds = 0.0f; + + // main loop + while (GIsRunning && !IsEngineExitRequested()) + { + GEngine->UpdateTimeAndHandleMaxTickRate(); + GEngine->Tick(FApp::GetDeltaTime(), false); + + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().PumpMessages(); + FSlateApplication::Get().Tick(); + } + + // Required for FTimerManager to function - as it blocks ticks, if the frame counter doesn't change + GFrameCounter++; + + // update task graph + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + + FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()); + FThreadManager::Get().Tick(); + GEngine->TickDeferredCommands(); + + if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) + { + // DirectoryWatcher->Tick(FApp::GetDeltaTime()); + + // Process the discovered files + TickDiscoveredFiles(); + } + + if (OwnerProcHandle.IsValid() && !FPlatformProcess::IsProcRunning(OwnerProcHandle)) + { + // Our once valid owner has disappeared, so quit. + RequestEngineExit(TEXT("OwnerDisappeared")); + } + + if (Mode == EHoudiniGeoImportCommandletMode::Listen && PDGEndpoint.IsValid()) + { + const float TimeSeconds = FPlatformTime::Seconds(); + if (TimeSeconds - LastbroadcastTimeSeconds >= BroadcastIntervalSeconds) + { + LastbroadcastTimeSeconds = TimeSeconds; + // Broadcast a discover message to notify that we are still available + PDGEndpoint->Publish(new FHoudiniPDGImportBGEODiscoverMessage(Guid)); + + HOUDINI_LOG_MESSAGE(TEXT("Publishing FHoudiniPDGImportBGEODiscoverMessage(%s)"), *Guid.ToString()); + } + } + + FPlatformProcess::Sleep(0); + } + + PDGEndpoint.Reset(); + if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) + { + DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(DirectoryToWatch, DirectoryWatcherHandle); + } + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Shutdown(); + + GIsRunning = false; + + return 0; +} + +void UHoudiniGeoImportCommandlet::HandleImportBGEOMessage( + const FHoudiniPDGImportBGEOMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_DISPLAY(TEXT("Received BGEO import request from %s"), *InContext->GetSender().ToString()); + + FHoudiniPackageParams PackageParams; + InMessage.PopulatePackageParams(PackageParams); + + // The commandlet must try to load packages if FindPackage fails, since we unload packages when done + PackageParams.bAttemptToLoadMissingPackages = true; + + TArray Outputs; + TMap> OutputObjectAttributes; + TMap InstancedOutputPartData; + if (ImportBGEO(InMessage.FilePath, PackageParams, Outputs, &OutputObjectAttributes, &InstancedOutputPartData) == 0) + { + FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); + (*Reply) = InMessage; + // Reply->PopulateFromPackageParams(PackageParams); + Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Success; + + const int32 NumOutputs = Outputs.Num(); + Reply->Outputs.Init(FHoudiniPDGImportNodeOutput(), NumOutputs); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + FHoudiniPDGImportNodeOutput &MessageOutput = Reply->Outputs[Index]; + UHoudiniOutput* Output = Outputs[Index]; + for (const FHoudiniGeoPartObject& HGPO : Output->GetHoudiniGeoPartObjects()) + { + HOUDINI_LOG_WARNING(TEXT("HGPO %d %d %d"), HGPO.ObjectId, HGPO.GeoId, HGPO.PartId); + MessageOutput.HoudiniGeoPartObjects.Add(HGPO); + + // Get instancer data if this is an instancer output + if (Output->GetType() == EHoudiniOutputType::Instancer) + { + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = HGPO.ObjectId; + OutputIdentifier.GeoId = HGPO.GeoId; + OutputIdentifier.PartId = HGPO.PartId; + OutputIdentifier.PartName = HGPO.PartName; + + FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = InstancedOutputPartData.Find(OutputIdentifier); + if (InstancedOutputPartDataPtr) + { + InstancedOutputPartDataPtr->BuildFlatInstancedTransformsAndObjectPaths(); + MessageOutput.InstancedOutputPartData.Add(*InstancedOutputPartDataPtr); + } + else + { + MessageOutput.InstancedOutputPartData.Add(FHoudiniInstancedOutputPartData()); + } + } + } + for (const auto& Entry : Output->GetOutputObjects()) + { + HOUDINI_LOG_WARNING(TEXT("Identifier %d %d %d"), Entry.Key.ObjectId, Entry.Key.GeoId, Entry.Key.PartId); + + MessageOutput.OutputObjects.AddDefaulted(); + FHoudiniPDGImportNodeOutputObject& MessageOutputObject = MessageOutput.OutputObjects.Last(); + + FString PackagePath = IsValid(Entry.Value.OutputObject) ? Entry.Value.OutputObject->GetPathName() : ""; + MessageOutputObject.Identifier = Entry.Key; + MessageOutputObject.PackagePath = PackagePath; + const TArray* PropertyAttributes = OutputObjectAttributes.Find(Entry.Key); + if (PropertyAttributes) + MessageOutputObject.GenericAttributes = *PropertyAttributes; + MessageOutputObject.CachedAttributes = Entry.Value.CachedAttributes; + } + } + + PDGEndpoint->Send(Reply, InContext->GetSender()); + } + else + { + HOUDINI_LOG_WARNING(TEXT("BGEO import failed.")); + FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); + Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Failed; + PDGEndpoint->Send(Reply, InContext->GetSender()); + } + + // Cleanup the outputs (remove from root) + TArray PackagesToUnload; + for (UHoudiniOutput *CurOutput : Outputs) + { + if (!IsValid(CurOutput)) + continue; + + for (const auto& Entry : CurOutput->GetOutputObjects()) + { + if (IsValid(Entry.Value.OutputObject)) + { + UPackage *Outermost = Entry.Value.OutputObject->GetOutermost(); + if (IsValid(Outermost)) + { + PackagesToUnload.Add(Outermost); + } + + Entry.Value.OutputObject->RemoveFromRoot(); + } + } + + CurOutput->RemoveFromRoot(); + } + Outputs.Empty(); + OutputObjectAttributes.Empty(); + + if (PackagesToUnload.Num() > 0) + { + HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ..."), PackagesToUnload.Num()); + FText ErrorMessage; + if (!UPackageTools::UnloadPackages(PackagesToUnload, ErrorMessage)) + { + HOUDINI_LOG_WARNING(TEXT("Unload packages failed: %s"), *ErrorMessage.ToString()); + } + else + { + HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ... Success"), PackagesToUnload.Num()); + } + PackagesToUnload.Empty(); + } + + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); +} + +bool UHoudiniGeoImportCommandlet::StartHoudiniEngineSession() +{ + // Start Houdini Engine session + HOUDINI_LOG_DISPLAY(TEXT("Starting Houdini Engine session...")); + FHoudiniEngine& HoudiniEngine = FHoudiniEngine::Get(); + if (!HoudiniEngine.CreateSession( + EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe, + "hapi_bgeo_cmdlet")) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start Houdini Engine session.")); + return false; + } + + return true; +} + +int32 UHoudiniGeoImportCommandlet::ImportBGEO( + const FString &InFilename, + const FHoudiniPackageParams &InPackageParams, + TArray& OutOutputs, + TMap>* OutGenericAttributes, + TMap* OutInstancedOutputPartData) +{ + if (!IsHoudiniEngineSessionRunning() && !StartHoudiniEngineSession()) + { + return 2; + } + + FHoudiniPackageParams PackageParams = InPackageParams; + UHoudiniGeoImporter* GeoImporter = NewObject(this); + + TArray OldOutputs; + OutOutputs.Empty(); + + // 2. Update the file paths + HOUDINI_LOG_DISPLAY(TEXT("SetFilePath %s"), *InFilename); + if (!GeoImporter->SetFilePath(InFilename)) + return 1; + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + HOUDINI_LOG_DISPLAY(TEXT("LoadBGEOFileInHAPI")); + if (!GeoImporter->LoadBGEOFileInHAPI(NodeId)) + return 1; + + // Look for a bake folder override in the BGEO file + if (PackageParams.PackageMode == EPackageMode::Bake) + { + HOUDINI_LOG_DISPLAY(TEXT("Looking for bake folder override attribute...")); + // Get the geo id for the node id + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), NodeId, &DisplayGeoInfo)) + { + FString BakeFolderOverride; + const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); + if (bFoundOverride && !BakeFolderOverride.IsEmpty()) + { + PackageParams.BakeFolder = BakeFolderOverride; + HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override: %s"), *PackageParams.BakeFolder); + } + else + { + HOUDINI_LOG_DISPLAY(TEXT("No bake folder override, using: %s"), *PackageParams.BakeFolder); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could not find display geo node id (when looking for bake folder override).")); + } + } + + auto CleanUpAndExit = [&OutOutputs, GeoImporter, NodeId](int32 InExitCode) + { + GeoImporter->GetOutputObjects().Empty(); + for (UHoudiniOutput* Output : OutOutputs) + { + Output->RemoveFromRoot(); + } + OutOutputs.Empty(); + + if (NodeId >= 0) + GeoImporter->DeleteCreatedNode(NodeId); + + return InExitCode; + }; + + // 4. Get the output from the file node + HOUDINI_LOG_DISPLAY(TEXT("BuildOutputsForNode %d"), NodeId); + if (!GeoImporter->BuildOutputsForNode(NodeId, OldOutputs, OutOutputs)) + return CleanUpAndExit(1); + + // Create uniquely named packages, commandlet runs in conjunction + // with a main editor instance, so we cannot modify existing files + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + // FString PackageName; + // UPackage* Outer = PackageParams.CreatePackageForObject(PackageName); + UObject* Outer = this; + + // 5. Create the static meshes in the outputs + HOUDINI_LOG_DISPLAY(TEXT("Create Static Meshes")); + if (!GeoImporter->CreateStaticMeshes(OutOutputs, Outer, PackageParams)) + return CleanUpAndExit(1); + + //// 6. Create the landscape in the outputs + //if (!GeoImporter->CreateLandscapes(NewOutputs, Outer, PackageParams)) + // return CleanUpAndExit(1); + + // 7. Create the instancers in the outputs + if (OutInstancedOutputPartData) + { + if (!GeoImporter->CreateInstancerOutputPartData(OutOutputs, *OutInstancedOutputPartData)) + return CleanUpAndExit(1); + } + else + { + if (!GeoImporter->CreateInstancers(OutOutputs, Outer, PackageParams)) + return CleanUpAndExit(1); + } + + if (OutGenericAttributes) + { + // Collect all generic properties from Houdini, we need to pass these + // through to PDG manager + HOUDINI_LOG_DISPLAY(TEXT("Get Generic Attributes for static meshes")); + for (UHoudiniOutput* CurOutput : OutOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (const auto& Entry : CurOutput->GetOutputObjects()) + { + const FHoudiniOutputObjectIdentifier OutputIdentifier = Entry.Key; + TArray PropertyAttributes; + FHoudiniMeshTranslator::GetGenericPropertiesAttributes( + OutputIdentifier.GeoId, OutputIdentifier.PartId, + OutputIdentifier.PointIndex, OutputIdentifier.PrimitiveIndex, + PropertyAttributes); + OutGenericAttributes->Add(OutputIdentifier, PropertyAttributes); + } + } + } + + // 8. Delete the created node in Houdini + HOUDINI_LOG_DISPLAY(TEXT("DeleteCreatedNode %d"), NodeId); + if (!GeoImporter->DeleteCreatedNode(NodeId)) + { + // Not good, but not fatal.. + //return false; + } + + TArray PackagesToSave; + TArray& OutputObjects = GeoImporter->GetOutputObjects(); + for (UObject* Object : OutputObjects) + { + if (!IsValid(Object)) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("Created object: %s"), *Object->GetFullName()); + + UAssetImportData* AssetImportData = nullptr; + if (Object->IsA()) + { + UStaticMesh* SM = Cast(Object); + AssetImportData = SM->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); + SM->AssetImportData = AssetImportData; + } + } + + if (AssetImportData) + AssetImportData->Update(InFilename); + + Object->MarkPackageDirty(); + Object->PostEditChange(); + + UPackage* Package = Object->GetOutermost(); + if (IsValid(Package)) + { + PackagesToSave.AddUnique(Package); + } + } + + if (PackagesToSave.Num() > 0) + { + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); + } + + PackagesToSave.Empty(); + OutputObjects.Empty(); + + return 0; +} + +void UHoudiniGeoImportCommandlet::HandleDirectoryChanged(const TArray& InFileChangeDatas) +{ + const FRegexPattern BGEOPattern(TEXT(R"((.*)\.(bgeo(\.[^\.]*)?)$)")); + + for (const FFileChangeData& FileChangeData : InFileChangeDatas) + { + HOUDINI_LOG_MESSAGE(TEXT("HandleDirectoryChanged %d %s"), FileChangeData.Action, *FileChangeData.Filename); + + FRegexMatcher BGEOMatcher(BGEOPattern, FileChangeData.Filename.ToLower()); + if (BGEOMatcher.FindNext() && BGEOMatcher.GetCaptureGroup(2).StartsWith(TEXT("bgeo"))) + { + HOUDINI_LOG_DISPLAY(TEXT("Updating entry for %s..."), *FileChangeData.Filename); + const uint32 MaxImportAttempts = 3; + switch(FileChangeData.Action) + { + case FFileChangeData::FCA_Added: + case FFileChangeData::FCA_Modified: + if (DiscoveredFiles.Contains(FileChangeData.Filename)) + { + FDiscoveredFileData &FileData = DiscoveredFiles[FileChangeData.Filename]; + if (!FileData.bImported && FileData.ImportAttempts < MaxImportAttempts) + FileData.bImportNextTick = true; + else if (FileData.ImportAttempts >= MaxImportAttempts) + HOUDINI_LOG_WARNING(TEXT("Not importing %s, max attempts exceeded %d"), *FileData.FileName, FileData.ImportAttempts); + } + else + { + DiscoveredFiles.Add(FileChangeData.Filename, FDiscoveredFileData(FileChangeData.Filename, true)); + } + break; + case FFileChangeData::FCA_Removed: + DiscoveredFiles.Remove(FileChangeData.Filename); + break; + default: + HOUDINI_LOG_WARNING(TEXT("Unknown file change event %d for %s"), FileChangeData.Action, *FileChangeData.Filename); + } + } + } +} + +int32 UHoudiniGeoImportCommandlet::Main(const FString& InParams) +{ + TArray Tokens; + TArray Switches; + TMap Params; + ParseCommandLine(*InParams, Tokens, Switches, Params); + + if (Switches.Contains(TEXT("help")) || Switches.Contains(TEXT("?"))) + { + PrintUsage(); + return 0; + } + + if (Params.Contains(TEXT("guid"))) + { + const FString GuidStr = Params.FindChecked(TEXT("guid")); + FGuid::Parse(GuidStr, Guid); + + HOUDINI_LOG_DISPLAY(TEXT("GUID received: %s"), *Guid.ToString()); + } + else + { + Guid = FGuid::NewGuid(); + } + + // Set bake mode + if (Switches.Contains(TEXT("bake"))) + bBakeOutputs = true; + else + bBakeOutputs = false; + + if (Params.Contains(TEXT("listen"))) + { + Mode = EHoudiniGeoImportCommandletMode::Listen; + + if (!Params.Contains(TEXT("managerpid"))) + { + HOUDINI_LOG_ERROR(TEXT("'managerpid' is required when in -listen mode.")); + return 1; + } + + if (bBakeOutputs) + { + HOUDINI_LOG_ERROR(TEXT("'listen' mode does not support baking outputs (-bake).")); + return 1; + } + + // Get the manager's messaging address from the -listen param + const FString ManagerAddressStr = Params.FindChecked(TEXT("listen")); + if (!FMessageAddress::Parse(ManagerAddressStr, ManagerAddress)) + { + HOUDINI_LOG_ERROR(TEXT("The manager messaging address passed to -listen=%s is invalid."), *ManagerAddressStr); + return 1; + } + + // Get the manager pid and proc handle + uint32 OwnerProcessId = FCString::Strtoi(*Params.FindChecked(TEXT("managerpid")), nullptr, 10); + HOUDINI_LOG_DISPLAY(TEXT("Owner process Id: %d"), OwnerProcessId); + OwnerProcHandle = FPlatformProcess::OpenProcess(OwnerProcessId); + + return MainLoop(); + } + else if (Params.Contains(TEXT("watch"))) + { + Mode = EHoudiniGeoImportCommandletMode::Watch; + + HOUDINI_LOG_DISPLAY(TEXT("directory watch mode")); + FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); + IDirectoryWatcher* DirectoryWatcher = DirectoryWatcherModule.Get(); + if (DirectoryWatcher) + { + DirectoryToWatch = Params.FindChecked(TEXT("watch")); + if (FPaths::IsRelative(DirectoryToWatch)) + DirectoryToWatch = FPaths::ConvertRelativePathToFull(DirectoryToWatch); + + HOUDINI_LOG_DISPLAY(TEXT("Watching %s"), *DirectoryToWatch); + + DirectoryWatcher->RegisterDirectoryChangedCallback_Handle( + DirectoryToWatch, + IDirectoryWatcher::FDirectoryChanged::CreateUObject(this, &UHoudiniGeoImportCommandlet::HandleDirectoryChanged), + DirectoryWatcherHandle); + + return MainLoop(); + } + else + { + return 10; + } + } + else if (Tokens.Num() > 0) + { + Mode = EHoudiniGeoImportCommandletMode::SpecifiedFiles; + + if (!StartHoudiniEngineSession()) + return 2; + + const FString Filename = FPaths::IsRelative(Tokens[0]) ? FPaths::ConvertRelativePathToFull(Tokens[0]) : Tokens[0]; + FHoudiniPackageParams PackageParams; + PopulatePackageParams(Filename, PackageParams); + + TArray Outputs; + const int32 Result = ImportBGEO(Tokens[0], PackageParams, Outputs); + + for (UHoudiniOutput* Output : Outputs) + { + Output->RemoveFromRoot(); + } + Outputs.Empty(); + + return Result; + } + + return 0; +} diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h index 7ddae2e48..b6bc33ced 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h @@ -1,153 +1,153 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "Commandlets/Commandlet.h" -#include "MessageEndpoint.h" - -#include "HoudiniEngine.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPDGImporterMessages.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniGeoImportCommandlet.generated.h" - -class FSocket; - -class UHoudiniGeoImporter; -class UHoudiniOutput; - -struct FHoudiniPackageParams; - -enum class EHoudiniGeoImportCommandletMode : uint8 -{ - // Unspecified - None, - // Import of specified file - SpecifiedFiles, - // Directory watch mode - Watch, - // Listen mode (via PDGManager) - Listen -}; - -struct FDiscoveredFileData -{ -public: - FDiscoveredFileData() : FileName(), bImportNextTick(false), ImportAttempts(0), bImported(false) {} - - FDiscoveredFileData(const FString& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} - - FDiscoveredFileData(FString&& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} - - // Full/absolute file path - FString FileName; - - // Try to import this file on the next tick - bool bImportNextTick; - - // Number of attempts at importing this file - uint32 ImportAttempts; - - // The file has been imported successfully - bool bImported; -}; - -UCLASS() -class HOUDINIENGINE_API UHoudiniGeoImportCommandlet : public UCommandlet -{ - GENERATED_BODY() - -public: - - UHoudiniGeoImportCommandlet(); - - void PrintUsage() const; - - /** - * Entry point for your commandlet - * - * @param Params the string containing the parameters for the commandlet - */ - virtual int32 Main(const FString& Params) override; - - void HandleImportBGEOMessage( - const struct FHoudiniPDGImportBGEOMessage& InMessage, - const TSharedRef& InContext); - - void HandleDirectoryChanged(const TArray& InFileChangeDatas); - -protected: - - void PopulatePackageParams(const FString& InBGEOFilename, FHoudiniPackageParams& OutPackageParams); - - bool StartHoudiniEngineSession(); - - bool IsHoudiniEngineSessionRunning() { return FHoudiniEngine::Get().GetSession() != nullptr; }; - - int32 MainLoop(); - - int32 ImportBGEO( - const FString& InFilename, - const FHoudiniPackageParams& InPackageParams, - TArray& OutOutputs, - TMap>* OutGenericAttributes=nullptr, - TMap* OutInstancedOutputPartData=nullptr); - - void TickDiscoveredFiles(); - -private: - - // Messaging end point for receiving messages from PDG manager - TSharedPtr PDGEndpoint; - - // The messaging address of the manager - FMessageAddress ManagerAddress; - - // Unique ID of the commandlet. - FGuid Guid; - - // The proc handle of our owner (if in listen mode, quit when the owner stops running). - FProcHandle OwnerProcHandle; - - // TODO: Map so that we can watch multiple directories? - // Directory to watch - FString DirectoryToWatch; - // Handle if we are watching a directory for changes. - FDelegateHandle DirectoryWatcherHandle; - - // Keep track of files discovered by the watcher, and their state - TMap DiscoveredFiles; - - // Mode in which commandlet is running - EHoudiniGeoImportCommandletMode Mode; - - // Bake outputs via FHoudiniEngineBakeUtils - bool bBakeOutputs; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Commandlets/Commandlet.h" +#include "MessageEndpoint.h" + +#include "HoudiniEngine.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPDGImporterMessages.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniGeoImportCommandlet.generated.h" + +class FSocket; + +class UHoudiniGeoImporter; +class UHoudiniOutput; + +struct FHoudiniPackageParams; + +enum class EHoudiniGeoImportCommandletMode : uint8 +{ + // Unspecified + None, + // Import of specified file + SpecifiedFiles, + // Directory watch mode + Watch, + // Listen mode (via PDGManager) + Listen +}; + +struct FDiscoveredFileData +{ +public: + FDiscoveredFileData() : FileName(), bImportNextTick(false), ImportAttempts(0), bImported(false) {} + + FDiscoveredFileData(const FString& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} + + FDiscoveredFileData(FString&& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} + + // Full/absolute file path + FString FileName; + + // Try to import this file on the next tick + bool bImportNextTick; + + // Number of attempts at importing this file + uint32 ImportAttempts; + + // The file has been imported successfully + bool bImported; +}; + +UCLASS() +class HOUDINIENGINE_API UHoudiniGeoImportCommandlet : public UCommandlet +{ + GENERATED_BODY() + +public: + + UHoudiniGeoImportCommandlet(); + + void PrintUsage() const; + + /** + * Entry point for your commandlet + * + * @param Params the string containing the parameters for the commandlet + */ + virtual int32 Main(const FString& Params) override; + + void HandleImportBGEOMessage( + const struct FHoudiniPDGImportBGEOMessage& InMessage, + const TSharedRef& InContext); + + void HandleDirectoryChanged(const TArray& InFileChangeDatas); + +protected: + + void PopulatePackageParams(const FString& InBGEOFilename, FHoudiniPackageParams& OutPackageParams); + + bool StartHoudiniEngineSession(); + + bool IsHoudiniEngineSessionRunning() { return FHoudiniEngine::Get().GetSession() != nullptr; }; + + int32 MainLoop(); + + int32 ImportBGEO( + const FString& InFilename, + const FHoudiniPackageParams& InPackageParams, + TArray& OutOutputs, + TMap>* OutGenericAttributes=nullptr, + TMap* OutInstancedOutputPartData=nullptr); + + void TickDiscoveredFiles(); + +private: + + // Messaging end point for receiving messages from PDG manager + TSharedPtr PDGEndpoint; + + // The messaging address of the manager + FMessageAddress ManagerAddress; + + // Unique ID of the commandlet. + FGuid Guid; + + // The proc handle of our owner (if in listen mode, quit when the owner stops running). + FProcHandle OwnerProcHandle; + + // TODO: Map so that we can watch multiple directories? + // Directory to watch + FString DirectoryToWatch; + // Handle if we are watching a directory for changes. + FDelegateHandle DirectoryWatcherHandle; + + // Keep track of files discovered by the watcher, and their state + TMap DiscoveredFiles; + + // Mode in which commandlet is running + EHoudiniGeoImportCommandletMode Mode; + + // Bake outputs via FHoudiniEngineBakeUtils + bool bBakeOutputs; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp index 0452b892d..6f8c173ae 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp @@ -1,842 +1,844 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoImporter.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniPackageParams.h" -#include "HoudiniOutput.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniSplineComponent.h" - -#include "CoreMinimal.h" -#include "Misc/Paths.h" -#include "Misc/PackageName.h" -#include "UObject/Package.h" -#include "PackageTools.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "Editor.h" - -#include "Materials/MaterialInterface.h" -#include "Materials/Material.h" - - -UHoudiniGeoImporter::UHoudiniGeoImporter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , SourceFilePath() - , AbsoluteFilePath() - , AbsoluteFileDirectory() - , FileName() - , FileExtension() - , BakeRootFolder(TEXT("/Game/HoudiniEngine/Bake/")) -{ - /* - SourceFilePath = FString(); - - AbsoluteFilePath = FString(); - AbsoluteFileDirectory = FString(); - FileName = FString(); - FileExtension = FString(); - - OutputFilename = FString(); - BakeRootFolder = TEXT("/Game/HoudiniEngine/Bake/"); - */ -} - -bool -UHoudiniGeoImporter::SetFilePath(const FString& InFilePath) -{ - SourceFilePath = InFilePath; - if (!FPaths::FileExists(SourceFilePath)) - { - // Cant find BGEO file - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InFilePath); - return false; - } - - // Make sure we're using absolute path! - AbsoluteFilePath = FPaths::ConvertRelativePathToFull(SourceFilePath); - - // Split the file path - FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); - - // Handle .bgeo.sc correctly - if (FileExtension.Equals(TEXT("sc"))) - { - // append the bgeo to .sc - FileExtension = FPaths::GetExtension(FileName) +TEXT(".") + FileExtension; - // update the filename - FileName = FPaths::GetBaseFilename(FileName); - } - - if (FileExtension.IsEmpty()) - FileExtension = TEXT("bgeo"); - - if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) - { - // Not a bgeo file! - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *SourceFilePath); - return false; - } - - //BGEOFilePath = BGEOPath + TEXT("/") + BGEOFileName + TEXT(".") + BGEOExtension; - - // Only use "/" for the output file path - BakeRootFolder.ReplaceInline(TEXT("\\"), TEXT("/")); - // Make sure the output folder ends with a "/" - if (!BakeRootFolder.EndsWith("/")) - BakeRootFolder += TEXT("/"); - - // If we have't specified an outpout file name yet, use the input file name - if (OutputFilename.IsEmpty()) - OutputFilename = FileName; - - return true; -} - -bool -UHoudiniGeoImporter::AutoStartHoudiniEngineSessionIfNeeded() -{ - if (FHoudiniEngine::Get().GetSession()) - return true; - - // Default first session already attempted to be created ? stop here? - /* - if (FHoudiniEngine::Get().GetFirstSessionCreated()) - return false; - */ - - // Indicates that we've tried to start a session once no matter if it failed or succeed - FHoudiniEngine::Get().SetFirstSessionCreated(true); - if (!FHoudiniEngine::Get().RestartSession()) - { - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Couldn't start the default HoudiniEngine session!")); - return false; - } - - return true; -} - -bool -UHoudiniGeoImporter::BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs) -{ - FString Notification = TEXT("BGEO Importer: Getting output geos..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - const bool bInAddOutputsToRootSet = true; - return BuildAllOutputsForNode(InNodeId, this, InOldOutputs, OutNewOutputs, bInAddOutputsToRootSet); -} - -bool -UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - FString Notification = TEXT("BGEO Importer: Creating Static Meshes..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - //FHoudiniMeshTranslator::CreateAllMeshesFromHoudiniOutput(CurOutput, OuterPackage, OuterComponent, OuterAsset); - - TMap NewOutputObjects; - TMap OldOutputObjects = CurOutput->GetOutputObjects(); - TMap& AssignementMaterials = CurOutput->GetAssignementMaterials(); - TMap& ReplacementMaterials = CurOutput->GetReplacementMaterials(); - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - // Not a mesh, skip - if (CurHGPO.Type != EHoudiniPartType::Mesh) - continue; - - // Check for a unreal_output_name if we are in bake mode - FHoudiniPackageParams PackageParams(InPackageParams); - if (PackageParams.PackageMode == EPackageMode::Bake) - { - TArray OutputNames; - if (FHoudiniEngineUtils::GetOutputNameAttribute(CurHGPO.GeoId, CurHGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - PackageParams.ObjectName = OutputNames[0]; - } - } - } - - FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( - CurHGPO, - PackageParams, - OldOutputObjects, - NewOutputObjects, - AssignementMaterials, - ReplacementMaterials, - true, - EHoudiniStaticMeshMethod::RawMesh); - } - - // Add all output objects and materials - for (auto CurOutputPair : NewOutputObjects) - { - UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - - // Do the same for materials - for (auto CurAssignmentMatPair : AssignementMaterials) - { - UObject* CurObj = CurAssignmentMatPair.Value; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - - // Also assign to the output objects map as we may need the meshes to create instancers later - CurOutput->SetOutputObjects(NewOutputObjects); - } - - return true; -} - - -bool -UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - TArray CurveOutputs; - CurveOutputs.Reserve(InOutputs.Num()); - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Curve) - continue; - - CurveOutputs.Add(CurOutput); - break; - } - - FString Notification = TEXT("BGEO Importer: Creating Curves..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - // Look for the first unreal_output_name attribute on the curve outputs and use that - // for ObjectName - FHoudiniPackageParams PackageParams(InPackageParams); - for (auto& CurOutput : CurveOutputs) - { - bool bFoundOutputName = false; - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Curve) - continue; - - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - break; - } - } - } - - if (bFoundOutputName) - break; - } - - // Create a Package for the BP - PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - FString PackageName; - UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); - check(BPPackage); - - // Create and init a new Blueprint Actor - UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); - if (!Blueprint) - return false; - - // Create a fake outer component that we'll use as a temporary outer for our curves - UWorld* TempWorld = UWorld::CreateWorld(EWorldType::Inactive, false, TEXT("BGEOImporterTemp"), GetTransientPackage(), false); - const FActorSpawnParameters ActorSpawnParameters; - AActor* OuterActor = TempWorld->SpawnActor(ActorSpawnParameters); - USceneComponent* OuterComponent = - NewObject(OuterActor, USceneComponent::GetDefaultSceneRootVariableName()); - - for (auto& CurOutput : CurveOutputs) - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); - - // Prepare an ActorComponent array for AddComponentsToBlueprint() - TArray OutputComp; - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputComp.Add(CurObj); - } - - // Transfer all the instancer components to the BP - if (OutputComp.Num() > 0) - { - FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; - Params.OptionalNewRootNode = nullptr; - Params.bKeepMobility = false; - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); - } - } - - // Compile the blueprint - FKismetEditorUtilities::CompileBlueprint(Blueprint); - - // Add it to our output objects - OutputObjects.Add(Blueprint); - - return true; -} - - -bool -UHoudiniGeoImporter::CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - HOUDINI_LOG_WARNING(TEXT("Importing a landscape directly from BGEOs is not currently supported.")); - return false; - } - - return true; - - /* - // Before processing any of the output, - // we need to get the min/max value for all Height volumes in this output (if any) - float HoudiniHeightfieldOutputsGlobalMin = 0.f; - float HoudiniHeightfieldOutputsGlobalMax = 0.f; - FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax(InOutputs, HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax); - - UWorld* PersistentWorld = InParent->GetWorld(); - if(!PersistentWorld) - PersistentWorld = GEditor ? GEditor->GetEditorWorldContext(false).World() : nullptr; - - if (!PersistentWorld) - return false; - - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - FString Notification = TEXT("BGEO Importer: Creating Landscapes..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - TArray EmptyInputLandscapes; - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(CurOutput); - - bool bCreatedNewMaps = false; - ERuntimePackageMode RuntimePackageMode = ERuntimePackageMode::CookToTemp; - switch(InPackageParams.PackageMode) - { - - case EPackageMode::Bake: - RuntimePackageMode = ERuntimePackageMode::Bake; - break; - case EPackageMode::CookToLevel: - case EPackageMode::CookToTemp: - default: - RuntimePackageMode = ERuntimePackageMode::CookToTemp; - break; - } - TArray> CreatedUntrackedOutputs; - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - CreatedUntrackedOutputs, - EmptyInputLandscapes, - EmptyInputLandscapes, - HAC, - TEXT("{object_name}_"), - PersistentWorld, - HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax, - InPackageParams, bCreatedNewMaps, - RuntimePackageMode); - - // Add all output objects - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - } - - return true; - */ -} - - -bool -UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - bool HasInstancer = false; - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - HasInstancer = true; - break; - } - - if (!HasInstancer) - return true; - - FString Notification = TEXT("BGEO Importer: Creating Instancers..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - // Look for the first unreal_output_name attribute on the instancer outputs and use that - // for ObjectName - FHoudiniPackageParams PackageParams(InPackageParams); - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - bool bFoundOutputName = false; - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Instancer) - continue; - - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - break; - } - } - } - - if (bFoundOutputName) - break; - } - - // Create a Package for the BP - PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - FString PackageName; - UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); - check(BPPackage); - - // Create and init a new Blueprint Actor - UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); - if (!Blueprint) - return false; - - // Create a fake outer component that we'll use as a temporary outer for our instancers - USceneComponent* OuterComponent = NewObject(); - - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - // Create all the instancers and attach them to a fake outer component - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - CurOutput, InOutputs, OuterComponent); - - // Prepare an ActorComponent array for AddComponentsToBlueprint() - TArray OutputComp; - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputComp.Add(CurObj); - } - - // Transfer all the instancer components to the BP - if (OutputComp.Num() > 0) - { - FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; - Params.OptionalNewRootNode = nullptr; - Params.bKeepMobility = false; - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); - } - } - - // Compile the blueprint - FKismetEditorUtilities::CompileBlueprint(Blueprint); - - // Add it to our output objects - OutputObjects.Add(Blueprint); - - return true; -} - -bool -UHoudiniGeoImporter::CreateInstancerOutputPartData( - TArray& InOutputs, - TMap& OutInstancedOutputPartData) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = HGPO.ObjectId; - OutputIdentifier.GeoId = HGPO.GeoId; - OutputIdentifier.PartId = HGPO.PartId; - OutputIdentifier.PartName = HGPO.PartName; - - OutInstancedOutputPartData.Add(OutputIdentifier, FHoudiniInstancedOutputPartData()); - FHoudiniInstancedOutputPartData *InstancedOutputData = OutInstancedOutputPartData.Find(OutputIdentifier); - // Create all the instancers and attach them to a fake outer component - if (!FHoudiniInstanceTranslator::PopulateInstancedOutputPartData(HGPO, InOutputs, *InstancedOutputData)) - return false; - } - } - - return true; -} - -bool -UHoudiniGeoImporter::DeleteCreatedNode(const HAPI_NodeId& InNodeId) -{ - if (InNodeId < 0) - return false; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), InNodeId)) - { - // Could not delete the bgeo's file sop ! - HOUDINI_LOG_WARNING(TEXT("Houdini GEO Importer: Could not delete HAPI File SOP.")); - return false; - } - - return true; -} - -bool -UHoudiniGeoImporter::ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams) -{ - if (InBGEOFile.IsEmpty()) - return false; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!AutoStartHoudiniEngineSessionIfNeeded()) - return false; - - // 2. Update the file paths - if (!SetFilePath(InBGEOFile)) - return false; - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - if (!LoadBGEOFileInHAPI(NodeId)) - return false; - - // 4. Get the output from the file node - TArray NewOutputs; - TArray OldOutputs; - if (!BuildOutputsForNode(NodeId, OldOutputs, NewOutputs)) - return false; - - // Failure lambda - auto CleanUpAndReturn = [&NewOutputs](const bool& bReturnValue) - { - // Remove the output objects from the root set before returning false - for (auto Out : NewOutputs) - Out->RemoveFromRoot(); - - return bReturnValue; - }; - - // Prepare the package used for creating the mesh, landscape and instancer pacakges - FHoudiniPackageParams PackageParams; - if (InPackageParams) - { - PackageParams = *InPackageParams; - } - else - { - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.HoudiniAssetName = FString(); - PackageParams.HoudiniAssetActorName = FString(); - PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); - } - - if (!PackageParams.OuterPackage) - { - PackageParams.OuterPackage = InParent; - } - - if (!PackageParams.ComponentGUID.IsValid()) - { - // TODO: will need to reuse the GUID when reimporting? - PackageParams.ComponentGUID = FGuid::NewGuid(); - } - - // 5. Create the static meshes in the outputs - if (!CreateStaticMeshes(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 6. Create the static meshes in the outputs - if (!CreateCurves(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 7. Create the landscape in the outputs - if (!CreateLandscapes(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 8. Create the instancers in the outputs - if (!CreateInstancers(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 9. Delete the created node in Houdini - if (!DeleteCreatedNode(NodeId)) - return CleanUpAndReturn(false); - - // Clean up and return true - return CleanUpAndReturn(true); -} - -bool -UHoudiniGeoImporter::OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition) -{ - if (InBGEOFile.IsEmpty()) - return false; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!AutoStartHoudiniEngineSessionIfNeeded()) - return false; - - if (!FPaths::FileExists(InBGEOFile)) - { - // Cant find BGEO file - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InBGEOFile); - return false; - } - - // Make sure we're using absolute path! - const FString AbsoluteFilePath = FPaths::ConvertRelativePathToFull(InBGEOFile); - - if (AbsoluteFilePath.IsEmpty()) - return false; - - FString AbsoluteFileDirectory; - FString FileName; - FString FileExtension; - - // Split the file path - FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); - - // Handle .bgeo.sc correctly - if (FileExtension.Equals(TEXT("sc"))) - { - // append the bgeo to .sc - FileExtension = FPaths::GetExtension(FileName) + TEXT(".") + FileExtension; - // update the filename - FileName = FPaths::GetBaseFilename(FileName); - } - - if (FileExtension.IsEmpty()) - FileExtension = TEXT("bgeo"); - - if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) - { - // Not a bgeo file! - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *InBGEOFile); - return false; - } - - OutNodeId = -1; - - // Check HoudiniEngine / HAPI init? - if (!FHoudiniEngine::IsInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); - return false; - } - - FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); - - // Create a file SOP - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, "SOP/file", "bgeo", true, &OutNodeId), false); - - // Set the file path parameter - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - OutNodeId, "file", &ParmId), false); - - const std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str(), ParmId, 0), false); - - return true; -} - -bool -UHoudiniGeoImporter::CloseBGEOFile(const HAPI_NodeId& InNodeId) -{ - // 8. Delete the created node in Houdini - if (!DeleteCreatedNode(InNodeId)) - return false; - - return true; -} - -bool -UHoudiniGeoImporter::LoadBGEOFileInHAPI(HAPI_NodeId& NodeId) -{ - NodeId = -1; - - if (AbsoluteFilePath.IsEmpty()) - return false; - - // Check HoudiniEngine / HAPI init? - if (!FHoudiniEngine::IsInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); - return false; - } - - FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); - - // Create a file SOP - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, "SOP/file", "bgeo", true, &NodeId), false); - - // Set the file path parameter - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, "file", &ParmId), false); - - std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, 0), false); - - return CookFileNode(NodeId); -} - -bool -UHoudiniGeoImporter::CookFileNode(const HAPI_NodeId& InNodeId) -{ - // Cook the node - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); - - // Wait for the cook to finish - int32 status = HAPI_STATE_MAX_READY_STATE + 1; - while (status > HAPI_STATE_MAX_READY_STATE) - { - // Retrieve the status - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), - HAPI_STATUS_COOK_STATE, &status), false); - - FString StatusString = FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); - HOUDINI_LOG_MESSAGE(TEXT("Still Cooking, current status: %s."), *StatusString); - - // Go to bed.. - if (status > HAPI_STATE_MAX_READY_STATE) - FPlatformProcess::Sleep(0.5f); - } - - if (status != HAPI_STATE_READY) - { - // There was some cook errors - HOUDINI_LOG_ERROR(TEXT("Finished Cooking with errors!")); - return false; - } - - HOUDINI_LOG_MESSAGE(TEXT("Finished Cooking!")); - - return true; -} - -bool -UHoudiniGeoImporter::BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet) -{ - // TArray OldOutputs; - if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, InOldOutputs, OutNewOutputs, false)) - { - // Couldn't create the package - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Failed to process the File SOP's outputs!")); - return false; - } - - if (bInAddOutputsToRootSet) - { - // Add the output objects to the RootSet to prevent them from being GCed - for (auto& Out : OutNewOutputs) - Out->AddToRoot(); - } - - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoImporter.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniPackageParams.h" +#include "HoudiniOutput.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniSplineComponent.h" + +#include "CoreMinimal.h" +#include "Misc/Paths.h" +#include "Misc/PackageName.h" +#include "UObject/Package.h" +#include "PackageTools.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "Editor.h" + +#include "Materials/MaterialInterface.h" +#include "Materials/Material.h" + + +UHoudiniGeoImporter::UHoudiniGeoImporter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , SourceFilePath() + , AbsoluteFilePath() + , AbsoluteFileDirectory() + , FileName() + , FileExtension() + , BakeRootFolder(TEXT("/Game/HoudiniEngine/Bake/")) +{ + /* + SourceFilePath = FString(); + + AbsoluteFilePath = FString(); + AbsoluteFileDirectory = FString(); + FileName = FString(); + FileExtension = FString(); + + OutputFilename = FString(); + BakeRootFolder = TEXT("/Game/HoudiniEngine/Bake/"); + */ +} + +bool +UHoudiniGeoImporter::SetFilePath(const FString& InFilePath) +{ + SourceFilePath = InFilePath; + if (!FPaths::FileExists(SourceFilePath)) + { + // Cant find BGEO file + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InFilePath); + return false; + } + + // Make sure we're using absolute path! + AbsoluteFilePath = FPaths::ConvertRelativePathToFull(SourceFilePath); + + // Split the file path + FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); + + // Handle .bgeo.sc correctly + if (FileExtension.Equals(TEXT("sc"))) + { + // append the bgeo to .sc + FileExtension = FPaths::GetExtension(FileName) +TEXT(".") + FileExtension; + // update the filename + FileName = FPaths::GetBaseFilename(FileName); + } + + if (FileExtension.IsEmpty()) + FileExtension = TEXT("bgeo"); + + if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) + { + // Not a bgeo file! + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *SourceFilePath); + return false; + } + + //BGEOFilePath = BGEOPath + TEXT("/") + BGEOFileName + TEXT(".") + BGEOExtension; + + // Only use "/" for the output file path + BakeRootFolder.ReplaceInline(TEXT("\\"), TEXT("/")); + // Make sure the output folder ends with a "/" + if (!BakeRootFolder.EndsWith("/")) + BakeRootFolder += TEXT("/"); + + // If we have't specified an outpout file name yet, use the input file name + if (OutputFilename.IsEmpty()) + OutputFilename = FileName; + + return true; +} + +bool +UHoudiniGeoImporter::AutoStartHoudiniEngineSessionIfNeeded() +{ + if (FHoudiniEngine::Get().GetSession()) + return true; + + // Default first session already attempted to be created ? stop here? + /* + if (FHoudiniEngine::Get().GetFirstSessionCreated()) + return false; + */ + + // Indicates that we've tried to start a session once no matter if it failed or succeed + FHoudiniEngine::Get().SetFirstSessionCreated(true); + if (!FHoudiniEngine::Get().RestartSession()) + { + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Couldn't start the default HoudiniEngine session!")); + return false; + } + + return true; +} + +bool +UHoudiniGeoImporter::BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs) +{ + FString Notification = TEXT("BGEO Importer: Getting output geos..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + const bool bInAddOutputsToRootSet = true; + return BuildAllOutputsForNode(InNodeId, this, InOldOutputs, OutNewOutputs, bInAddOutputsToRootSet); +} + +bool +UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + FString Notification = TEXT("BGEO Importer: Creating Static Meshes..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + //FHoudiniMeshTranslator::CreateAllMeshesFromHoudiniOutput(CurOutput, OuterPackage, OuterComponent, OuterAsset); + + TMap NewOutputObjects; + TMap OldOutputObjects = CurOutput->GetOutputObjects(); + TMap& AssignementMaterials = CurOutput->GetAssignementMaterials(); + TMap& ReplacementMaterials = CurOutput->GetReplacementMaterials(); + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + // Not a mesh, skip + if (CurHGPO.Type != EHoudiniPartType::Mesh) + continue; + + // Check for a unreal_output_name if we are in bake mode + FHoudiniPackageParams PackageParams(InPackageParams); + if (PackageParams.PackageMode == EPackageMode::Bake) + { + TArray OutputNames; + if (FHoudiniEngineUtils::GetOutputNameAttribute(CurHGPO.GeoId, CurHGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + PackageParams.ObjectName = OutputNames[0]; + } + } + } + + FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); + FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( + CurHGPO, + PackageParams, + OldOutputObjects, + NewOutputObjects, + AssignementMaterials, + ReplacementMaterials, + true, + EHoudiniStaticMeshMethod::RawMesh, + SMGP); + } + + // Add all output objects and materials + for (auto CurOutputPair : NewOutputObjects) + { + UObject* CurObj = CurOutputPair.Value.OutputObject; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + + // Do the same for materials + for (auto CurAssignmentMatPair : AssignementMaterials) + { + UObject* CurObj = CurAssignmentMatPair.Value; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + + // Also assign to the output objects map as we may need the meshes to create instancers later + CurOutput->SetOutputObjects(NewOutputObjects); + } + + return true; +} + + +bool +UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + TArray CurveOutputs; + CurveOutputs.Reserve(InOutputs.Num()); + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Curve) + continue; + + CurveOutputs.Add(CurOutput); + break; + } + + FString Notification = TEXT("BGEO Importer: Creating Curves..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + // Look for the first unreal_output_name attribute on the curve outputs and use that + // for ObjectName + FHoudiniPackageParams PackageParams(InPackageParams); + for (auto& CurOutput : CurveOutputs) + { + bool bFoundOutputName = false; + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Curve) + continue; + + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + break; + } + } + } + + if (bFoundOutputName) + break; + } + + // Create a Package for the BP + PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + FString PackageName; + UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); + check(BPPackage); + + // Create and init a new Blueprint Actor + UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); + if (!Blueprint) + return false; + + // Create a fake outer component that we'll use as a temporary outer for our curves + UWorld* TempWorld = UWorld::CreateWorld(EWorldType::Inactive, false, TEXT("BGEOImporterTemp"), GetTransientPackage(), false); + const FActorSpawnParameters ActorSpawnParameters; + AActor* OuterActor = TempWorld->SpawnActor(ActorSpawnParameters); + USceneComponent* OuterComponent = + NewObject(OuterActor, USceneComponent::GetDefaultSceneRootVariableName()); + + for (auto& CurOutput : CurveOutputs) + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); + + // Prepare an ActorComponent array for AddComponentsToBlueprint() + TArray OutputComp; + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputComp.Add(CurObj); + } + + // Transfer all the instancer components to the BP + if (OutputComp.Num() > 0) + { + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; + Params.OptionalNewRootNode = nullptr; + Params.bKeepMobility = false; + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); + } + } + + // Compile the blueprint + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + // Add it to our output objects + OutputObjects.Add(Blueprint); + + return true; +} + + +bool +UHoudiniGeoImporter::CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + HOUDINI_LOG_WARNING(TEXT("Importing a landscape directly from BGEOs is not currently supported.")); + return false; + } + + return true; + + /* + // Before processing any of the output, + // we need to get the min/max value for all Height volumes in this output (if any) + float HoudiniHeightfieldOutputsGlobalMin = 0.f; + float HoudiniHeightfieldOutputsGlobalMax = 0.f; + FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax(InOutputs, HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax); + + UWorld* PersistentWorld = InParent->GetWorld(); + if(!PersistentWorld) + PersistentWorld = GEditor ? GEditor->GetEditorWorldContext(false).World() : nullptr; + + if (!PersistentWorld) + return false; + + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + FString Notification = TEXT("BGEO Importer: Creating Landscapes..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + TArray EmptyInputLandscapes; + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(CurOutput); + + bool bCreatedNewMaps = false; + ERuntimePackageMode RuntimePackageMode = ERuntimePackageMode::CookToTemp; + switch(InPackageParams.PackageMode) + { + + case EPackageMode::Bake: + RuntimePackageMode = ERuntimePackageMode::Bake; + break; + case EPackageMode::CookToLevel: + case EPackageMode::CookToTemp: + default: + RuntimePackageMode = ERuntimePackageMode::CookToTemp; + break; + } + TArray> CreatedUntrackedOutputs; + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + CreatedUntrackedOutputs, + EmptyInputLandscapes, + EmptyInputLandscapes, + HAC, + TEXT("{object_name}_"), + PersistentWorld, + HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax, + InPackageParams, bCreatedNewMaps, + RuntimePackageMode); + + // Add all output objects + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UObject* CurObj = CurOutputPair.Value.OutputObject; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + } + + return true; + */ +} + + +bool +UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + bool HasInstancer = false; + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + HasInstancer = true; + break; + } + + if (!HasInstancer) + return true; + + FString Notification = TEXT("BGEO Importer: Creating Instancers..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + // Look for the first unreal_output_name attribute on the instancer outputs and use that + // for ObjectName + FHoudiniPackageParams PackageParams(InPackageParams); + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + bool bFoundOutputName = false; + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Instancer) + continue; + + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + break; + } + } + } + + if (bFoundOutputName) + break; + } + + // Create a Package for the BP + PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + FString PackageName; + UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); + check(BPPackage); + + // Create and init a new Blueprint Actor + UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); + if (!Blueprint) + return false; + + // Create a fake outer component that we'll use as a temporary outer for our instancers + USceneComponent* OuterComponent = NewObject(); + + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + // Create all the instancers and attach them to a fake outer component + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + CurOutput, InOutputs, OuterComponent); + + // Prepare an ActorComponent array for AddComponentsToBlueprint() + TArray OutputComp; + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputComp.Add(CurObj); + } + + // Transfer all the instancer components to the BP + if (OutputComp.Num() > 0) + { + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; + Params.OptionalNewRootNode = nullptr; + Params.bKeepMobility = false; + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); + } + } + + // Compile the blueprint + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + // Add it to our output objects + OutputObjects.Add(Blueprint); + + return true; +} + +bool +UHoudiniGeoImporter::CreateInstancerOutputPartData( + TArray& InOutputs, + TMap& OutInstancedOutputPartData) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = HGPO.ObjectId; + OutputIdentifier.GeoId = HGPO.GeoId; + OutputIdentifier.PartId = HGPO.PartId; + OutputIdentifier.PartName = HGPO.PartName; + + OutInstancedOutputPartData.Add(OutputIdentifier, FHoudiniInstancedOutputPartData()); + FHoudiniInstancedOutputPartData *InstancedOutputData = OutInstancedOutputPartData.Find(OutputIdentifier); + // Create all the instancers and attach them to a fake outer component + if (!FHoudiniInstanceTranslator::PopulateInstancedOutputPartData(HGPO, InOutputs, *InstancedOutputData)) + return false; + } + } + + return true; +} + +bool +UHoudiniGeoImporter::DeleteCreatedNode(const HAPI_NodeId& InNodeId) +{ + if (InNodeId < 0) + return false; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), InNodeId)) + { + // Could not delete the bgeo's file sop ! + HOUDINI_LOG_WARNING(TEXT("Houdini GEO Importer: Could not delete HAPI File SOP.")); + return false; + } + + return true; +} + +bool +UHoudiniGeoImporter::ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams) +{ + if (InBGEOFile.IsEmpty()) + return false; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!AutoStartHoudiniEngineSessionIfNeeded()) + return false; + + // 2. Update the file paths + if (!SetFilePath(InBGEOFile)) + return false; + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + if (!LoadBGEOFileInHAPI(NodeId)) + return false; + + // 4. Get the output from the file node + TArray NewOutputs; + TArray OldOutputs; + if (!BuildOutputsForNode(NodeId, OldOutputs, NewOutputs)) + return false; + + // Failure lambda + auto CleanUpAndReturn = [&NewOutputs](const bool& bReturnValue) + { + // Remove the output objects from the root set before returning false + for (auto Out : NewOutputs) + Out->RemoveFromRoot(); + + return bReturnValue; + }; + + // Prepare the package used for creating the mesh, landscape and instancer pacakges + FHoudiniPackageParams PackageParams; + if (InPackageParams) + { + PackageParams = *InPackageParams; + } + else + { + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.HoudiniAssetName = FString(); + PackageParams.HoudiniAssetActorName = FString(); + PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); + } + + if (!PackageParams.OuterPackage) + { + PackageParams.OuterPackage = InParent; + } + + if (!PackageParams.ComponentGUID.IsValid()) + { + // TODO: will need to reuse the GUID when reimporting? + PackageParams.ComponentGUID = FGuid::NewGuid(); + } + + // 5. Create the static meshes in the outputs + if (!CreateStaticMeshes(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 6. Create the static meshes in the outputs + if (!CreateCurves(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 7. Create the landscape in the outputs + if (!CreateLandscapes(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 8. Create the instancers in the outputs + if (!CreateInstancers(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 9. Delete the created node in Houdini + if (!DeleteCreatedNode(NodeId)) + return CleanUpAndReturn(false); + + // Clean up and return true + return CleanUpAndReturn(true); +} + +bool +UHoudiniGeoImporter::OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition) +{ + if (InBGEOFile.IsEmpty()) + return false; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!AutoStartHoudiniEngineSessionIfNeeded()) + return false; + + if (!FPaths::FileExists(InBGEOFile)) + { + // Cant find BGEO file + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InBGEOFile); + return false; + } + + // Make sure we're using absolute path! + const FString AbsoluteFilePath = FPaths::ConvertRelativePathToFull(InBGEOFile); + + if (AbsoluteFilePath.IsEmpty()) + return false; + + FString AbsoluteFileDirectory; + FString FileName; + FString FileExtension; + + // Split the file path + FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); + + // Handle .bgeo.sc correctly + if (FileExtension.Equals(TEXT("sc"))) + { + // append the bgeo to .sc + FileExtension = FPaths::GetExtension(FileName) + TEXT(".") + FileExtension; + // update the filename + FileName = FPaths::GetBaseFilename(FileName); + } + + if (FileExtension.IsEmpty()) + FileExtension = TEXT("bgeo"); + + if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) + { + // Not a bgeo file! + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *InBGEOFile); + return false; + } + + OutNodeId = -1; + + // Check HoudiniEngine / HAPI init? + if (!FHoudiniEngine::IsInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); + return false; + } + + FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); + + // Create a file SOP + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, "SOP/file", "bgeo", true, &OutNodeId), false); + + // Set the file path parameter + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + OutNodeId, "file", &ParmId), false); + + const std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str(), ParmId, 0), false); + + return true; +} + +bool +UHoudiniGeoImporter::CloseBGEOFile(const HAPI_NodeId& InNodeId) +{ + // 8. Delete the created node in Houdini + if (!DeleteCreatedNode(InNodeId)) + return false; + + return true; +} + +bool +UHoudiniGeoImporter::LoadBGEOFileInHAPI(HAPI_NodeId& NodeId) +{ + NodeId = -1; + + if (AbsoluteFilePath.IsEmpty()) + return false; + + // Check HoudiniEngine / HAPI init? + if (!FHoudiniEngine::IsInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); + return false; + } + + FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); + + // Create a file SOP + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, "SOP/file", "bgeo", true, &NodeId), false); + + // Set the file path parameter + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, "file", &ParmId), false); + + std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, 0), false); + + return CookFileNode(NodeId); +} + +bool +UHoudiniGeoImporter::CookFileNode(const HAPI_NodeId& InNodeId) +{ + // Cook the node + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); + + // Wait for the cook to finish + int32 status = HAPI_STATE_MAX_READY_STATE + 1; + while (status > HAPI_STATE_MAX_READY_STATE) + { + // Retrieve the status + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), + HAPI_STATUS_COOK_STATE, &status), false); + + FString StatusString = FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); + HOUDINI_LOG_MESSAGE(TEXT("Still Cooking, current status: %s."), *StatusString); + + // Go to bed.. + if (status > HAPI_STATE_MAX_READY_STATE) + FPlatformProcess::Sleep(0.5f); + } + + if (status != HAPI_STATE_READY) + { + // There was some cook errors + HOUDINI_LOG_ERROR(TEXT("Finished Cooking with errors!")); + return false; + } + + HOUDINI_LOG_MESSAGE(TEXT("Finished Cooking!")); + + return true; +} + +bool +UHoudiniGeoImporter::BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet) +{ + // TArray OldOutputs; + if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, InOldOutputs, OutNewOutputs, false)) + { + // Couldn't create the package + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Failed to process the File SOP's outputs!")); + return false; + } + + if (bInAddOutputsToRootSet) + { + // Add the output objects to the RootSet to prevent them from being GCed + for (auto& Out : OutNewOutputs) + Out->AddToRoot(); + } + + return true; +} diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.h b/Source/HoudiniEngine/Private/HoudiniGeoImporter.h index 4ba3217d3..0ca0a5965 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.h +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.h @@ -1,122 +1,122 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniGeoImporter.generated.h" - -class UHoudiniOutput; - -struct FHoudiniPackageParams; - -UCLASS() -class HOUDINIENGINE_API UHoudiniGeoImporter : public UObject -{ -public: - - GENERATED_UCLASS_BODY() - - public: - //UHoudiniGeoImporter(); - //~UHoudiniGeoImporter(); - - void SetBakeRootFolder(const FString& InFolder) { BakeRootFolder = InFolder; }; - void SetOutputFilename(const FString& InOutFilename) { OutputFilename = InOutFilename; }; - - TArray& GetOutputObjects() { return OutputObjects; }; - - // BEGIN: Static API - // Open a BGEO file: create a file node in HAPI and cook it - static bool OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition=false); - // Cook the file node specified by the valid NodeId. - static bool CookFileNode(const HAPI_NodeId& InNodeId); - // Extract the outputs for a given node ID - static bool BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet=false); - // Delete the HAPI node and remove InOutputs from the root set. - static bool CloseBGEOFile(const HAPI_NodeId& InNodeId); - // END: Static API - - // Import the BGEO file - bool ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams=nullptr); - - // 1. Start a HE session if needed - static bool AutoStartHoudiniEngineSessionIfNeeded(); - // 2. Update our file members fromn the input file path - bool SetFilePath(const FString& InFilePath); - // 3. Creates a new file node and loads the bgeo file in HAPI - bool LoadBGEOFileInHAPI(HAPI_NodeId& NodeId); - // 4. Extract the outputs for a given node ID - bool BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs); - // 5. Creates the static meshes object found in the output - bool CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 6. Create the output curves - bool CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 7. Create the output landscapes - bool CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 8. Create the output instancers - bool CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 9. Clean up the created node - static bool DeleteCreatedNode(const HAPI_NodeId& InNodeId); - - static bool CreateInstancerOutputPartData( - TArray& InOutputs, - TMap& OutInstancedOutputPartData); - - private: - - // - // Input file - // - // Path how the file we're currently loading - FString SourceFilePath; - // Absolute Path to the file - FString AbsoluteFilePath; - FString AbsoluteFileDirectory; - // File Name / Extension - FString FileName; - FString FileExtension; - - - // - // Output file - // - // Output filename, if empty, will be set to the input filename - FString OutputFilename; - // Root Folder for storing the created files - FString BakeRootFolder; - - // - // Output Objects - // - TArray OutputObjects; - - //TArray OutputStaticMeshes; - //TArray OutputLandscapes; - //TArray OutputInstancers; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniGeoImporter.generated.h" + +class UHoudiniOutput; + +struct FHoudiniPackageParams; + +UCLASS() +class HOUDINIENGINE_API UHoudiniGeoImporter : public UObject +{ +public: + + GENERATED_UCLASS_BODY() + + public: + //UHoudiniGeoImporter(); + //~UHoudiniGeoImporter(); + + void SetBakeRootFolder(const FString& InFolder) { BakeRootFolder = InFolder; }; + void SetOutputFilename(const FString& InOutFilename) { OutputFilename = InOutFilename; }; + + TArray& GetOutputObjects() { return OutputObjects; }; + + // BEGIN: Static API + // Open a BGEO file: create a file node in HAPI and cook it + static bool OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition=false); + // Cook the file node specified by the valid NodeId. + static bool CookFileNode(const HAPI_NodeId& InNodeId); + // Extract the outputs for a given node ID + static bool BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet=false); + // Delete the HAPI node and remove InOutputs from the root set. + static bool CloseBGEOFile(const HAPI_NodeId& InNodeId); + // END: Static API + + // Import the BGEO file + bool ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams=nullptr); + + // 1. Start a HE session if needed + static bool AutoStartHoudiniEngineSessionIfNeeded(); + // 2. Update our file members fromn the input file path + bool SetFilePath(const FString& InFilePath); + // 3. Creates a new file node and loads the bgeo file in HAPI + bool LoadBGEOFileInHAPI(HAPI_NodeId& NodeId); + // 4. Extract the outputs for a given node ID + bool BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs); + // 5. Creates the static meshes object found in the output + bool CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 6. Create the output curves + bool CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 7. Create the output landscapes + bool CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 8. Create the output instancers + bool CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 9. Clean up the created node + static bool DeleteCreatedNode(const HAPI_NodeId& InNodeId); + + static bool CreateInstancerOutputPartData( + TArray& InOutputs, + TMap& OutInstancedOutputPartData); + + private: + + // + // Input file + // + // Path how the file we're currently loading + FString SourceFilePath; + // Absolute Path to the file + FString AbsoluteFilePath; + FString AbsoluteFileDirectory; + // File Name / Extension + FString FileName; + FString FileExtension; + + + // + // Output file + // + // Output filename, if empty, will be set to the input filename + FString OutputFilename; + // Root Folder for storing the created files + FString BakeRootFolder; + + // + // Output Objects + // + TArray OutputObjects; + + //TArray OutputStaticMeshes; + //TArray OutputLandscapes; + //TArray OutputInstancers; + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp index 51b11a814..8dca013bf 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp @@ -1,371 +1,370 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniHandleTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniHandleComponent.h" - - -bool -FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray NewHandles; - - if (FHoudiniHandleTranslator::BuildAllHandles(HAC->GetAssetId(), HAC, HAC->HandleComponents, NewHandles)) - { - - HAC->HandleComponents = NewHandles; - - } - - return true; -} - -bool -FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAssetComponent* OuterObject, - TArray& CurrentHandles, - TArray& NewHandles) -{ - if (AssetId < 0) - return false; - - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo)) - return false; - - TMap CurrentHandlesByName; - - for (auto& Handle : CurrentHandles) - { - if (!Handle) - continue; - - CurrentHandlesByName.Add(Handle->GetHandleName(), Handle); - } - - // If there are handles - if (AssetInfo.handleCount > 0) - { - TArray 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) - { - 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"), - OuterObject ? *(OuterObject->GetName()) : *(OuterObject->GetName()), *TypeName, *HandleName); - continue; - } - - UHoudiniHandleComponent* HandleComponent = nullptr; - UHoudiniHandleComponent** FoundHandleComponent = CurrentHandlesByName.Find(HandleName); - - if (FoundHandleComponent) - { - HandleComponent = *FoundHandleComponent; - - CurrentHandles.Remove(*FoundHandleComponent); - CurrentHandlesByName.Remove(HandleName); - } - else - { - HandleComponent = NewObject(OuterObject, - UHoudiniHandleComponent::StaticClass(), - NAME_None, RF_Public | RF_Transactional); - - HandleComponent->SetHandleName(HandleName); - HandleComponent->SetHandleType(HandleType); - - // Change the creation method so the component is listed in the details panels - HandleComponent->CreationMethod = EComponentCreationMethod::Instance; - - } - - if (!HandleComponent) - continue; - - // If we have no parent, we need to re-attach. - if (!HandleComponent->GetAttachParent()) - { - HandleComponent->AttachToComponent(OuterObject, FAttachmentTransformRules::KeepRelativeTransform); - } - - HandleComponent->SetVisibility(true); - - // If component is not registered, register it - if (!HandleComponent->IsRegistered()) - HandleComponent->RegisterComponent(); - - // Get handle's bindings - TArray BindingInfos; - BindingInfos.SetNumZeroed(HandleInfo.bindingsCount); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetHandleBindingInfo(FHoudiniEngine::Get().GetSession(), - AssetId, HandleIdx, &BindingInfos[0], 0, HandleInfo.bindingsCount)) - continue; - - HAPI_TransformEuler HapiEulerXform; - FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); - HapiEulerXform.position[0] = HapiEulerXform.position[1] = HapiEulerXform.position[2] = 0.0f; - HapiEulerXform.rotationEuler[0] = HapiEulerXform.rotationEuler[1] = HapiEulerXform.rotationEuler[2] = 0.0f; - HapiEulerXform.scale[0] = HapiEulerXform.scale[1] = HapiEulerXform.scale[2] = 1.0f; - - TSharedPtr< FString > RSTOrderStrPtr, XYZOrderStrPtr; - - for (const auto& BindingInfo : BindingInfos) - { - FString HandleParmName = TEXT(""); - FHoudiniEngineString HoudiniEngineString(BindingInfo.handleParmNameSH); - HoudiniEngineString.ToFString(HandleParmName); - - const HAPI_ParmId ParamId = BindingInfo.assetParmId; - - TArray &XformParms = HandleComponent->XformParms; - - UHoudiniParameter* FoundParam = nullptr; - - for (auto Param : OuterObject->Parameters) - { - if (Param->GetParmId() == ParamId) - { - FoundParam = Param; - break; - } - } - - HandleComponent->InitializeHandleParameters(); - - if (!HandleComponent->CheckHandleValid()) - continue; - - (void)(XformParms[int32(EXformParameter::TX)]->Bind(HapiEulerXform.position[0], "tx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::TY)]->Bind(HapiEulerXform.position[1], "ty", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::TZ)]->Bind(HapiEulerXform.position[2], "tz", 2, HandleParmName, FoundParam) - - || XformParms[int32(EXformParameter::RX)]->Bind(HapiEulerXform.rotationEuler[0], "rx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::RY)]->Bind(HapiEulerXform.rotationEuler[1], "ry", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::RZ)]->Bind(HapiEulerXform.rotationEuler[2], "rz", 2, HandleParmName, FoundParam) - - || XformParms[int32(EXformParameter::SX)]->Bind(HapiEulerXform.scale[0], "sx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::SY)]->Bind(HapiEulerXform.scale[1], "sy", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::SZ)]->Bind(HapiEulerXform.scale[2], "sz", 2, HandleParmName, FoundParam) - - || HandleComponent->RSTParm->Bind(RSTOrderStrPtr, "trs_order", 0, HandleParmName, FoundParam) - || HandleComponent->RotOrderParm->Bind(XYZOrderStrPtr, "xyz_order", 0, HandleParmName, FoundParam) - ); - } - - - HapiEulerXform.rstOrder = GetHapiRSTOrder(RSTOrderStrPtr); - HapiEulerXform.rotationOrder = GetHapiXYZOrder(XYZOrderStrPtr); - - constexpr float MaxFloat = TNumericLimits::Max(); - constexpr float MinFloat = TNumericLimits::Min(); - - HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); - HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); - HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); - - FTransform UnrealXform; - FHoudiniEngineUtils::TranslateHapiTransform(HapiEulerXform, UnrealXform); - - //HandleComponent->SetRelativeTransform(UnrealXform); - - NewHandles.Add(HandleComponent); - - } - } - - return true; -} - - -void -FHoudiniHandleTranslator::ClearHandles(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - for (auto& HandleComponent : HAC->HandleComponents) - { - if (!HandleComponent) - continue; - - HandleComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - HandleComponent->UnregisterComponent(); - HandleComponent->DestroyComponent(); - } - - HAC->HandleComponents.Empty(); -} - -HAPI_RSTOrder -FHoudiniHandleTranslator::GetHapiRSTOrder(const TSharedPtr & StrPtr) -{ - if (StrPtr.Get()) - { - FString & Str = *StrPtr; - - if (Str == "trs") return HAPI_TRS; - else if (Str == "tsr") return HAPI_TSR; - else if (Str == "rts") return HAPI_RTS; - else if (Str == "rst") return HAPI_RST; - else if (Str == "str") return HAPI_STR; - else if (Str == "srt") return HAPI_SRT; - } - - return HAPI_SRT; -} - -HAPI_XYZOrder -FHoudiniHandleTranslator::GetHapiXYZOrder(const TSharedPtr & StrPtr) -{ - if (StrPtr.Get()) - { - FString & Str = *StrPtr; - - if (Str == "xyz") return HAPI_XYZ; - else if (Str == "xzy") return HAPI_XZY; - else if (Str == "yxz") return HAPI_YXZ; - else if (Str == "yzx") return HAPI_YZX; - else if (Str == "zxy") return HAPI_ZXY; - else if (Str == "zyx") return HAPI_ZYX; - } - - return HAPI_XYZ; -} - - -void -FHoudiniHandleTranslator::UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent) -{ - if (!HandleComponent || HandleComponent->IsPendingKill()) - return; - - if (!HandleComponent->CheckHandleValid()) - return; - - TArray &XformParms = HandleComponent->XformParms; - - if (XformParms.Num() < (int32)EXformParameter::COUNT) - return; - - HAPI_Transform HapiXform; - FMemory::Memzero< HAPI_Transform >(HapiXform); - FHoudiniEngineUtils::TranslateUnrealTransform(HandleComponent->GetRelativeTransform(), HapiXform); - - const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); - - float HapiMatrix[16]; - FHoudiniApi::ConvertTransformQuatToMatrix(Session, &HapiXform, HapiMatrix); - - HAPI_TransformEuler HapiEulerXform; - FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); - FHoudiniApi::ConvertMatrixToEuler( - Session, - HapiMatrix, - GetHapiRSTOrder(HandleComponent->RSTParm->Get(TSharedPtr< FString >())), - GetHapiXYZOrder(HandleComponent->RotOrderParm->Get(TSharedPtr< FString >())), - &HapiEulerXform - ); - - *XformParms[int32(EXformParameter::TX)] = HapiEulerXform.position[0]; - *XformParms[int32(EXformParameter::TY)] = HapiEulerXform.position[1]; - *XformParms[int32(EXformParameter::TZ)] = HapiEulerXform.position[2]; - - *XformParms[int32(EXformParameter::RX)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[0]); - *XformParms[int32(EXformParameter::RY)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[1]); - *XformParms[int32(EXformParameter::RZ)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[2]); - - constexpr float MaxFloat = TNumericLimits::Max(); - constexpr float MinFloat = TNumericLimits::Min(); - HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); - HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); - HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); - - *XformParms[int32(EXformParameter::SX)] = HapiEulerXform.scale[0]; - *XformParms[int32(EXformParameter::SY)] = HapiEulerXform.scale[1]; - *XformParms[int32(EXformParameter::SZ)] = HapiEulerXform.scale[2]; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniHandleComponent.h" + + +bool +FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray NewHandles; + + if (FHoudiniHandleTranslator::BuildAllHandles(HAC->GetAssetId(), HAC, HAC->HandleComponents, NewHandles)) + { + + HAC->HandleComponents = NewHandles; + + } + + return true; +} + +bool +FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAssetComponent* OuterObject, + TArray& CurrentHandles, + TArray& NewHandles) +{ + if (AssetId < 0) + return false; + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo)) + return false; + + TMap CurrentHandlesByName; + + for (auto& Handle : CurrentHandles) + { + if (!Handle) + continue; + + CurrentHandlesByName.Add(Handle->GetHandleName(), Handle); + } + + // If there are handles + if (AssetInfo.handleCount > 0) + { + TArray 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) + { + 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"), + OuterObject ? *(OuterObject->GetName()) : *(OuterObject->GetName()), *TypeName, *HandleName); + continue; + } + + UHoudiniHandleComponent* HandleComponent = nullptr; + UHoudiniHandleComponent** FoundHandleComponent = CurrentHandlesByName.Find(HandleName); + + if (FoundHandleComponent) + { + HandleComponent = *FoundHandleComponent; + + CurrentHandles.Remove(*FoundHandleComponent); + CurrentHandlesByName.Remove(HandleName); + } + else + { + HandleComponent = NewObject(OuterObject, + UHoudiniHandleComponent::StaticClass(), + NAME_None, RF_Public | RF_Transactional); + + HandleComponent->SetHandleName(HandleName); + HandleComponent->SetHandleType(HandleType); + + // Change the creation method so the component is listed in the details panels + HandleComponent->CreationMethod = EComponentCreationMethod::Instance; + + } + + if (!HandleComponent) + continue; + + // If we have no parent, we need to re-attach. + if (!HandleComponent->GetAttachParent()) + { + HandleComponent->AttachToComponent(OuterObject, FAttachmentTransformRules::KeepRelativeTransform); + } + + HandleComponent->SetVisibility(true); + + // If component is not registered, register it + if (!HandleComponent->IsRegistered()) + HandleComponent->RegisterComponent(); + + // Get handle's bindings + TArray BindingInfos; + BindingInfos.SetNumZeroed(HandleInfo.bindingsCount); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetHandleBindingInfo(FHoudiniEngine::Get().GetSession(), + AssetId, HandleIdx, &BindingInfos[0], 0, HandleInfo.bindingsCount)) + continue; + + HAPI_TransformEuler HapiEulerXform; + FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); + HapiEulerXform.position[0] = HapiEulerXform.position[1] = HapiEulerXform.position[2] = 0.0f; + HapiEulerXform.rotationEuler[0] = HapiEulerXform.rotationEuler[1] = HapiEulerXform.rotationEuler[2] = 0.0f; + HapiEulerXform.scale[0] = HapiEulerXform.scale[1] = HapiEulerXform.scale[2] = 1.0f; + + TSharedPtr< FString > RSTOrderStrPtr, XYZOrderStrPtr; + + for (const auto& BindingInfo : BindingInfos) + { + FString HandleParmName = TEXT(""); + FHoudiniEngineString HoudiniEngineString(BindingInfo.handleParmNameSH); + HoudiniEngineString.ToFString(HandleParmName); + + const HAPI_ParmId ParamId = BindingInfo.assetParmId; + + TArray &XformParms = HandleComponent->XformParms; + + UHoudiniParameter* FoundParam = nullptr; + + for (auto Param : OuterObject->Parameters) + { + if (Param->GetParmId() == ParamId) + { + FoundParam = Param; + break; + } + } + + HandleComponent->InitializeHandleParameters(); + + if (!HandleComponent->CheckHandleValid()) + continue; + + (void)(XformParms[int32(EXformParameter::TX)]->Bind(HapiEulerXform.position[0], "tx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::TY)]->Bind(HapiEulerXform.position[1], "ty", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::TZ)]->Bind(HapiEulerXform.position[2], "tz", 2, HandleParmName, FoundParam) + + || XformParms[int32(EXformParameter::RX)]->Bind(HapiEulerXform.rotationEuler[0], "rx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::RY)]->Bind(HapiEulerXform.rotationEuler[1], "ry", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::RZ)]->Bind(HapiEulerXform.rotationEuler[2], "rz", 2, HandleParmName, FoundParam) + + || XformParms[int32(EXformParameter::SX)]->Bind(HapiEulerXform.scale[0], "sx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::SY)]->Bind(HapiEulerXform.scale[1], "sy", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::SZ)]->Bind(HapiEulerXform.scale[2], "sz", 2, HandleParmName, FoundParam) + + || HandleComponent->RSTParm->Bind(RSTOrderStrPtr, "trs_order", 0, HandleParmName, FoundParam) + || HandleComponent->RotOrderParm->Bind(XYZOrderStrPtr, "xyz_order", 0, HandleParmName, FoundParam) + ); + } + + + HapiEulerXform.rstOrder = GetHapiRSTOrder(RSTOrderStrPtr); + HapiEulerXform.rotationOrder = GetHapiXYZOrder(XYZOrderStrPtr); + + constexpr float MaxFloat = TNumericLimits::Max(); + constexpr float MinFloat = TNumericLimits::Min(); + + HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); + HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); + HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); + + FTransform UnrealXform; + FHoudiniEngineUtils::TranslateHapiTransform(HapiEulerXform, UnrealXform); + + //HandleComponent->SetRelativeTransform(UnrealXform); + + NewHandles.Add(HandleComponent); + + } + } + + return true; +} + + +void +FHoudiniHandleTranslator::ClearHandles(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + for (auto& HandleComponent : HAC->HandleComponents) + { + if (!HandleComponent) + continue; + + HandleComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + HandleComponent->UnregisterComponent(); + HandleComponent->DestroyComponent(); + } + + HAC->HandleComponents.Empty(); +} + +HAPI_RSTOrder +FHoudiniHandleTranslator::GetHapiRSTOrder(const TSharedPtr & StrPtr) +{ + if (StrPtr.Get()) + { + FString & Str = *StrPtr; + + if (Str == "trs") return HAPI_TRS; + else if (Str == "tsr") return HAPI_TSR; + else if (Str == "rts") return HAPI_RTS; + else if (Str == "rst") return HAPI_RST; + else if (Str == "str") return HAPI_STR; + else if (Str == "srt") return HAPI_SRT; + } + + return HAPI_SRT; +} + +HAPI_XYZOrder +FHoudiniHandleTranslator::GetHapiXYZOrder(const TSharedPtr & StrPtr) +{ + if (StrPtr.Get()) + { + FString & Str = *StrPtr; + + if (Str == "xyz") return HAPI_XYZ; + else if (Str == "xzy") return HAPI_XZY; + else if (Str == "yxz") return HAPI_YXZ; + else if (Str == "yzx") return HAPI_YZX; + else if (Str == "zxy") return HAPI_ZXY; + else if (Str == "zyx") return HAPI_ZYX; + } + + return HAPI_XYZ; +} + + +void +FHoudiniHandleTranslator::UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent) +{ + if (!HandleComponent || HandleComponent->IsPendingKill()) + return; + + if (!HandleComponent->CheckHandleValid()) + return; + + TArray &XformParms = HandleComponent->XformParms; + + if (XformParms.Num() < (int32)EXformParameter::COUNT) + return; + + HAPI_Transform HapiXform; + FMemory::Memzero< HAPI_Transform >(HapiXform); + FHoudiniEngineUtils::TranslateUnrealTransform(HandleComponent->GetRelativeTransform(), HapiXform); + + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + + float HapiMatrix[16]; + FHoudiniApi::ConvertTransformQuatToMatrix(Session, &HapiXform, HapiMatrix); + + HAPI_TransformEuler HapiEulerXform; + FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); + FHoudiniApi::ConvertMatrixToEuler( + Session, + HapiMatrix, + GetHapiRSTOrder(HandleComponent->RSTParm->Get(TSharedPtr< FString >())), + GetHapiXYZOrder(HandleComponent->RotOrderParm->Get(TSharedPtr< FString >())), + &HapiEulerXform + ); + + *XformParms[int32(EXformParameter::TX)] = HapiEulerXform.position[0]; + *XformParms[int32(EXformParameter::TY)] = HapiEulerXform.position[1]; + *XformParms[int32(EXformParameter::TZ)] = HapiEulerXform.position[2]; + + *XformParms[int32(EXformParameter::RX)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[0]); + *XformParms[int32(EXformParameter::RY)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[1]); + *XformParms[int32(EXformParameter::RZ)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[2]); + + constexpr float MaxFloat = TNumericLimits::Max(); + constexpr float MinFloat = TNumericLimits::Min(); + HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); + HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); + HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); + + *XformParms[int32(EXformParameter::SX)] = HapiEulerXform.scale[0]; + *XformParms[int32(EXformParameter::SY)] = HapiEulerXform.scale[1]; + *XformParms[int32(EXformParameter::SZ)] = HapiEulerXform.scale[2]; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h index 2c4827ef8..55e60ae4f 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h @@ -1,52 +1,55 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -class UHoudiniAssetComponent; -class UHoudiniHandleComponent; - -struct HOUDINIENGINE_API FHoudiniHandleTranslator -{ - static bool UpdateHandles(UHoudiniAssetComponent* HAC); - - - static bool BuildAllHandles(const HAPI_NodeId& AssetId, - UHoudiniAssetComponent* OuterObject, - TArray& CurrentHandles, - TArray& NewHandles); - - static void ClearHandles(UHoudiniAssetComponent* HAC); - - static HAPI_RSTOrder GetHapiRSTOrder(const TSharedPtr & StrPtr); - - static HAPI_XYZOrder GetHapiXYZOrder(const TSharedPtr & StrPtr); - - static void UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniAPI.h" + +#include "Templates/SharedPointer.h" + +class UHoudiniAssetComponent; +class UHoudiniHandleComponent; + +struct HOUDINIENGINE_API FHoudiniHandleTranslator +{ + static bool UpdateHandles(UHoudiniAssetComponent* HAC); + + + static bool BuildAllHandles(const HAPI_NodeId& AssetId, + UHoudiniAssetComponent* OuterObject, + TArray& CurrentHandles, + TArray& NewHandles); + + static void ClearHandles(UHoudiniAssetComponent* HAC); + + static HAPI_RSTOrder GetHapiRSTOrder(const TSharedPtr & StrPtr); + + static HAPI_XYZOrder GetHapiXYZOrder(const TSharedPtr & StrPtr); + + static void UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp index b5bd82583..108204faa 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp @@ -1,2890 +1,2890 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInputTranslator.h" - -#include "HoudiniInput.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInputObject.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniAssetActor.h" -#include "HoudiniOutputTranslator.h" -#include "UnrealBrushTranslator.h" -#include "UnrealSplineTranslator.h" -#include "UnrealMeshTranslator.h" -#include "UnrealInstanceTranslator.h" -#include "UnrealLandscapeTranslator.h" - -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "Engine/Brush.h" -#include "Engine/DataTable.h" -#include "Camera/CameraComponent.h" - -#include "Engine/SimpleConstructionScript.h" -#include "Engine/SCS_Node.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Editor/UnrealEdEngine.h" - #include "UnrealEdGlobals.h" -#endif - -#include "HCsgUtils.h" - -#include "Async/Async.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#if WITH_EDITOR -// Allows checking of objects currently being dragged around -struct FHoudiniMoveTracker -{ - FHoudiniMoveTracker() : IsObjectMoving(false) - { - GEditor->OnBeginObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = true; }); - GEditor->OnEndObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - - GEditor->OnBeginCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - GEditor->OnEndCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - } - static FHoudiniMoveTracker& Get() { static FHoudiniMoveTracker Instance; return Instance; } - - bool IsObjectMoving; -}; -#endif - -// -bool -FHoudiniInputTranslator::UpdateInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) - { - // Failed to create the inputs - return false; - } - - return true; -} - -bool -FHoudiniInputTranslator::BuildAllInputs( - const HAPI_NodeId& AssetId, - class UObject* InOuterObject, - TArray& Inputs, - TArray& Parameters) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Start by getting the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - // Get the number of geo (SOP) inputs - int32 InputCount = AssetInfo.geoInputCount; - /* - // It's best to update the input count even if the hda hasnt cooked - // as it can cause loaded geo inputs to disappear upon loading the level - if ( AssetInfo.hasEverCooked ) - { - InputCount = AssetInfo.geoInputCount; - } - */ - // Also look for object path parameters inputs - TArray> InputParameters; - for (auto Param : Parameters) - { - if (Param->GetParameterType() == EHoudiniParameterType::Input) - InputParameters.Add(Param); - } - - InputCount += InputParameters.Num(); - - // Append new inputs as needed - if (InputCount > Inputs.Num()) - { - int32 NumNewInputs = InputCount - Inputs.Num(); - for (int32 InputIdx = Inputs.Num(); InputIdx < InputCount; ++InputIdx) - { - FString InputObjectName = TEXT("Input") + FString::FromInt(InputIdx + 1); - UHoudiniInput * NewInput = NewObject< UHoudiniInput >( - InOuterObject, - UHoudiniInput::StaticClass(), - FName(*InputObjectName), - RF_Transactional); - - if (!NewInput || NewInput->IsPendingKill()) - { - //HOUDINI_LOG_WARNING("Failed to create asset input"); - continue; - } - // Create a default curve object here to avoid Transaction issue - //NewInput->CreateDefaultCurveInputObject(); - - Inputs.Add(NewInput); - } - } - else if (InputCount < Inputs.Num()) - { - // TODO: Properly clean up the input object + created nodes? - for (int32 InputIdx = Inputs.Num() - 1; InputIdx >= InputCount; InputIdx--) - { - UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - FHoudiniInputTranslator::DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); - - // DO NOT MANUALLY DESTROY THE OLD/DANGLING INPUTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - //CurrentInput->ConditionalBeginDestroy(); - //CurrentInput = nullptr; - } - - Inputs.SetNum(InputCount); - } - - // Now, check the inputs in the array match the geo inputs - //for (int32 GeoInIdx = 0; GeoInIdx < AssetInfo.geoInputCount; GeoInIdx++) - bool bBlueprintStructureChanged = false; - for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) - { - UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // Create default Name/Label/Help - FString CurrentInputName = TEXT("Input") + FString::FromInt(InputIdx + 1); - FString CurrentInputLabel = CurrentInputName; - FString CurrentInputHelp; - - // Set the nodeId - CurrentInput->SetAssetNodeId(AssetId); - - // Is this an object path parameter input? - bool bIsObjectPath = InputIdx >= AssetInfo.geoInputCount; - if (!bIsObjectPath) - { - // Mark this input as a SOP input - CurrentInput->SetSOPInput(InputIdx); - - // Get and set the name - HAPI_StringHandle InputStringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInputName( - FHoudiniEngine::Get().GetSession(), - AssetId, InputIdx, &InputStringHandle)) - { - FHoudiniEngineString HoudiniEngineString(InputStringHandle); - HoudiniEngineString.ToFString(CurrentInputLabel); - } - } - else - { - // Get this input's parameter index in the objpath param array - int32 CurrentParmIdx = InputIdx - AssetInfo.geoInputCount; - - UHoudiniParameter* CurrentParm = nullptr; - if (InputParameters.IsValidIndex(CurrentParmIdx)) - { - if (InputParameters[CurrentParmIdx].IsValid()) - CurrentParm = InputParameters[CurrentParmIdx].Get(); - } - - int32 ParmId = -1; - if (CurrentParm && !CurrentParm->IsPendingKill()) - { - ParmId = CurrentParm->GetParmId(); - CurrentInputName = CurrentParm->GetParameterName(); - CurrentInputLabel = CurrentParm->GetParameterLabel(); - CurrentInputHelp = CurrentParm->GetParameterHelp(); - } - - UHoudiniParameterOperatorPath* CurrentObjPathParm = Cast(CurrentParm); - if (CurrentObjPathParm && !CurrentObjPathParm->IsPendingKill()) - { - CurrentObjPathParm->HoudiniInput = CurrentInput; - } - - // Mark this input as an object path parameter input - CurrentInput->SetObjectPathParameter(ParmId); - } - - CurrentInput->SetName(CurrentInputName); - CurrentInput->SetLabel(CurrentInputLabel); - - if ( CurrentInputHelp.IsEmpty() ) - { - CurrentInputHelp = CurrentInputLabel + TEXT("(") + CurrentInputName + TEXT(")"); - } - CurrentInput->SetHelp(CurrentInputHelp); - - // If the input type is invalid, - // We need to initialize its default - if (CurrentInput->GetInputType() == EHoudiniInputType::Invalid) - { - // Initialize it to the default corresponding to its name - CurrentInput->SetInputType(GetDefaultInputTypeFromLabel(CurrentInputLabel), bBlueprintStructureChanged); - - // Preset the default HDA for objpath input - SetDefaultAssetFromHDA(CurrentInput); - } - - // Update input objects data on UE side for all types of inputs. - switch (CurrentInput->GetInputType()) - { - case EHoudiniInputType::Curve: - FHoudiniSplineTranslator::UpdateHoudiniInputCurves(CurrentInput); - break; - case EHoudiniInputType::Landscape: - //FUnrealLandscapeTranslator::UpdateHoudiniInputLandscapes(CurrentInput); - break; - case EHoudiniInputType::Asset: - break; - case EHoudiniInputType::Geometry: - break; - case EHoudiniInputType::Skeletal: - break; - case EHoudiniInputType::World: - break; - default: - break; - } - } - - return true; -} - -bool -FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - if (!InputToDestroy || InputToDestroy->IsPendingKill()) - return false; - - // Start by disconnecting the input / nullifying the object path parameter - if (InputToDestroy->IsObjectPathParameter()) - { - // Just set the objpath parameter to null - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - InputToDestroy->GetAssetNodeId(), "", - InputToDestroy->GetParameterId(), 0); - } - else - { - // Get the asset / created input node ID - HAPI_NodeId HostAssetId = InputToDestroy->GetAssetNodeId(); - HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); - - // Only disconnect if both are valid - if (HostAssetId >= 0 && CreatedInputId >= 0) - { - FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), - HostAssetId, InputToDestroy->GetInputIndex()); - } - } - - if (InputType == EHoudiniInputType::Asset) - { - // TODO: - // If we're an asset input, just remove us from the downstream connection on the input HDA - // then reset this input's flag - - // TODO: Check this? Clean our DS assets?? why?? likely uneeded - UHoudiniAssetComponent* OuterHAC = Cast(InputToDestroy->GetOuter()); - if (OuterHAC) - OuterHAC->ClearDownstreamHoudiniAsset(); - - InputToDestroy->SetInputNodeId(-1); - } - - return true; -} - -bool -FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - if (!InputToDestroy || InputToDestroy->IsPendingKill()) - return false; - - if (!InputToDestroy->CanDeleteHoudiniNodes()) - return false; - - // If we're destroying an asset input, don't destroy anything as we don't want to destroy the input HDA - // a simple disconnect is sufficient - if (InputType == EHoudiniInputType::Asset) - return true; - - // Destroy the nodes created by all the input objects - TArray CreatedInputDataAssetIds = InputToDestroy->GetCreatedDataNodeIds(); - TArray* InputObjectNodes = InputToDestroy->GetHoudiniInputObjectArray(InputType); - if (InputObjectNodes) - { - for (auto CurInputObject : *InputObjectNodes) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - if (CurInputObject->Type == EHoudiniInputObjectType::HoudiniAssetComponent) - { - // Remove this input object's node Id from the - // CreatedInputDataAssetIds array to avoid its deletion further down - CreatedInputDataAssetIds.Remove(CurInputObject->InputNodeId); - CurInputObject->InputNodeId = -1; - CurInputObject->InputObjectNodeId = -1; - continue; - } - - // For Actor input objects, set the input node id for all component objects to -1, - if (CurInputObject->Type == EHoudiniInputObjectType::Actor) - { - UHoudiniInputActor* CurActorInputObject = Cast(CurInputObject); - if (CurActorInputObject) - { - for (auto & CurActorComponent : CurActorInputObject->ActorComponents) - { - if (!CurActorComponent || CurActorComponent->IsPendingKill()) - continue; - - // No need to delete the nodes created for an asset component manually here, - // As they will be deleted when we clean up the CreateNodeIds array - CurActorComponent->InputNodeId = -1; - } - } - } - // No need to delete the nodes created for an asset component manually here, - // As they will be deleted when we clean up the CreateNodeIds array - - if (CurInputObject->InputNodeId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputNodeId); - CurInputObject->InputNodeId = -1; - } - - if(CurInputObject->InputObjectNodeId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputObjectNodeId); - CurInputObject->InputObjectNodeId = -1; - - // TODO: CHECK ME! - HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CurInputObject->InputObjectNodeId); - - // Delete its parent node as well - if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentNodeId)) - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentNodeId); - } - - // Also directly invalidate HoudiniSplineComponent's node IDs. - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast(CurInputObject); - if (IsValid(HoudiniSplineInputObject) && !IsGarbageCollecting()) - { - UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (SplineComponent && !SplineComponent->IsPendingKill()) - { - SplineComponent->SetNodeId(-1); - } - } - - CurInputObject->MarkChanged(true); - } - } - - // Destroy all the input assets - for (HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds) - { - if (AssetNodeId < 0) - continue; - - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), AssetNodeId); - } - CreatedInputDataAssetIds.Empty(); - - // Then simply destroy the input's parent OBJ node - if (InputToDestroy->GetInputNodeId() >= 0) - { - HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedInputId); - - if (CreatedInputId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CreatedInputId); - InputToDestroy->SetInputNodeId(-1); - } - - if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentId)) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentId); - } - } - - return true; -} - -bool -FHoudiniInputTranslator::DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - // Start by disconnecting the input/object merge - bool bSuccess = DisconnectInput(InputToDestroy, InputType); - - // Then destroy the created input nodes - bSuccess &= DestroyInputNodes(InputToDestroy, InputType); - - return bSuccess; -} - - -EHoudiniInputType -FHoudiniInputTranslator::GetDefaultInputTypeFromLabel(const FString& InputName) -{ - // We'll try to find these magic words to try to detect the default input type - //FString geoPrefix = TEXT("geo"); - FString curvePrefix = TEXT("curve"); - - FString landscapePrefix = TEXT("landscape"); - FString landscapePrefix2 = TEXT("terrain"); - FString landscapePrefix3 = TEXT("heightfield"); - - FString worldPrefix = TEXT("world"); - FString worldPrefix2 = TEXT("outliner"); - - FString assetPrefix = TEXT("asset"); - FString assetPrefix2 = TEXT("hda"); - - // By default, geometry input is chosen. - EHoudiniInputType InputType = EHoudiniInputType::Geometry; - - if (InputName.Contains(curvePrefix, ESearchCase::IgnoreCase)) - InputType = EHoudiniInputType::Curve; - - else if ((InputName.Contains(landscapePrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(landscapePrefix2, ESearchCase::IgnoreCase)) - || (InputName.Contains(landscapePrefix3, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::Landscape; - - else if ((InputName.Contains(worldPrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(worldPrefix2, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::World; - - else if ((InputName.Contains(assetPrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(assetPrefix2, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::Asset; - - return InputType; -} - -bool -FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bForce) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (!InInput->HasInputTypeChanged() && !bForce) - return true; - - // - Handle switching AWAY from an input type - DisconnectAndDestroyInput(InInput, InInput->GetPreviousInputType()); - - // Invalidate the previous input type now that we've actually changed - //InInput->SetPreviousInputType(EHoudiniInputType::Invalid); - - //ChangeInputType(InInput, NewType); - - // TODO: - // - Handle updating to the new input type - // downstream asset connection, static mesh update, curve creation... - - // Mark all the objects from this input has changed so they upload themselves - InInput->MarkAllInputObjectsChanged(true); - - return true; -} - -bool -FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input) -{ - // - if (!Input || Input->IsPendingKill()) - return false; - - // We just handle geo inputs - if (EHoudiniInputType::Geometry != Input->GetInputType()) - return false; - - // Make sure we're linked to a valid object path parameter - if (Input->GetParameterId() < 0) - return false; - - // There is a default slot, don't add if slot is already filled - //if (InputObjects.Num() > 1) - // return false; - - // Get our ParmInfo - HAPI_ParmInfo FoundParamInfo; - FHoudiniApi::ParmInfo_Init(&FoundParamInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - Input->GetAssetNodeId(), Input->GetParameterId(), &FoundParamInfo)) - { - return false; - } - - // TODO: FINISH ME! - - /* - // Get our string value - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - Input->GetInputNodeId(), false, - &StringHandle, FoundParamInfo.stringValuesIndex, 1) ) - { - FString OutValue; - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(OutValue)) - { - // Set default object on the HDA instance - will override the parameter string - // and apply the object input local-path thing for the HDA cook. - if (OutValue.Len() > 0) - { - UObject * pObject = LoadObject(nullptr, *OutValue); - if (pObject) - { - return AddInputObject(pObject); - } - } - } - } - */ - - return false; -} - -bool -FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - //for (auto CurrentInput : HAC->Inputs) - for(int32 InputIdx = 0; InputIdx < HAC->GetNumInputs(); InputIdx++) - { - UHoudiniInput*& CurrentInput = HAC->Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill() || !CurrentInput->HasChanged()) - continue; - - // First thing, see if we need to change the input type - if (CurrentInput->HasInputTypeChanged()) - { - ChangeInputType(CurrentInput, false); - } - - if (CurrentInput->GetInputType() == EHoudiniInputType::Landscape && CurrentInput->HasLandscapeExportTypeChanged()) - { - DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); - CurrentInput->MarkAllInputObjectsChanged(true); - CurrentInput->SetHasLandscapeExportTypeChanged(false); - } - - bool bSuccess = true; - if (CurrentInput->IsDataUploadNeeded()) - { - bSuccess &= UploadInputData(CurrentInput); - CurrentInput->MarkDataUploadNeeded(!bSuccess); - } - - if (CurrentInput->IsTransformUploadNeeded()) - { - bSuccess &= UploadInputTransform(CurrentInput); - } - - // Update the input properties AFTER eventually uploading it - bSuccess = UpdateInputProperties(CurrentInput); - - if (bSuccess) - { - CurrentInput->MarkChanged(false); - CurrentInput->MarkAllInputObjectsChanged(false); - } - - if (CurrentInput->HasInputTypeChanged()) - CurrentInput->SetPreviousInputType(EHoudiniInputType::Invalid); - - // Even if we failed, no need to try updating again. - CurrentInput->SetNeedsToTriggerUpdate(false); - } - - return true; -} - -bool -FHoudiniInputTranslator::UpdateInputProperties(UHoudiniInput* InInput) -{ - bool bSucess = UpdateTransformType(InInput); - - bSucess &= UpdatePackBeforeMerge(InInput); - - bSucess &= UpdateTransformOffset(InInput); - - return bSucess; -} - -bool -FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - bool nTransformType = InInput->GetKeepWorldTransform(); - - // Geometry inputs are always set to none - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType == EHoudiniInputType::Geometry) - nTransformType = 0; - - // Get the Input node ID from the host ID - HAPI_NodeId InputNodeId = -1; - HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); - - bool bSuccess = true; - const std::string sXformType = "xformtype"; - if (InInput->IsObjectPathParameter()) - { - // Directly change the Parameter xformtype - // (This will only work if the object merge is editable/unlocked) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), - HostAssetId, sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - else - { - // Query the object merge's node ID via the input - if (HAPI_RESULT_SUCCESS == FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - HostAssetId, InInput->GetInputIndex(), &InputNodeId)) - { - // Change its Parameter xformtype - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), - InputNodeId, sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - } - - // Since our input objects are all plugged into a merge node - // We want to also update the transform type on the object merge plugged into the merge node - HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); - if ((ParentNodeId >= 0) && (InputType != EHoudiniInputType::Geometry) && (InputType != EHoudiniInputType::Asset)) - { - HAPI_NodeId InputObjectNodeId = -1; - int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); - for (int n = 0; n < NumberOfInputMeshes; n++) - { - // Get the Input node ID from the host ID - InputObjectNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ParentNodeId, n, &InputObjectNodeId)) - continue; - - if (InputObjectNodeId == -1) - continue; - - // Change the xformtype parameter on the object merge - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), InputObjectNodeId, - sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - // Pack before merge is only available for Geo/World input - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType != EHoudiniInputType::World - && InputType != EHoudiniInputType::Geometry) - { - // Nothing to change - return true; - } - - uint32 nPackValue = InInput->GetPackBeforeMerge() ? 1 : 0; - - // Get the Input node ID from the host ID - HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); - - bool bSuccess = true; - const std::string sPack = "pack"; - - // We'll be going through each input object plugged in the input's merge node - // and change the pack parameter there - HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); - if (ParentNodeId >= 0) - { - HAPI_NodeId InputObjectNodeId = -1; - int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); - for (int n = 0; n < NumberOfInputMeshes; n++) - { - // Get the Input node ID from the host ID - InputObjectNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ParentNodeId, n, &InputObjectNodeId)) - continue; - - if (InputObjectNodeId == -1) - continue; - - // Change the pack parameter on the object merge - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), InputObjectNodeId, - sPack.c_str(), 0, nPackValue)) - bSuccess = false; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - // Transform offsets are only for geometry inputs - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType != EHoudiniInputType::Geometry) - { - // Nothing to change - return true; - } - - // Get the input objects - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Update each object's transform offset - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - // If the Input mesh has a Transform offset - FTransform TransformOffset = CurrentInputObject->Transform; - if (!TransformOffset.Equals(FTransform::Identity)) - { - // Updating the Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CurrentInputObject->InputObjectNodeId, &HapiTransform), false); - } - } - - return true; -} - -bool -FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - EHoudiniInputType InputType = InInput->GetInputType(); - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Iterate on all the input objects and see if they need to be uploaded - bool bSuccess = true; - TArray CreatedNodeIds; - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; - if (!CurrentInputObject->HasChanged() && CurrentInputObjectNodeId >= 0) - { - // If this object hasn't changed, no need to upload it - // but we need to keep its created input node - if (CurrentInputObject->Type == EHoudiniInputObjectType::Actor) - { - // If this input object is an actor, it actually contains other input - // objects for each of his components, keep them as well - UHoudiniInputActor* InputActor = Cast(CurrentInputObject); - if (InputActor && !InputActor->IsPendingKill()) - { - for (auto CurrentComp : InputActor->ActorComponents) - { - if (!CurrentComp || CurrentComp->IsPendingKill()) - continue; - - int32& CurrentCompNodeId = CurrentComp->InputObjectNodeId; - if (!CurrentComp->HasChanged() && CurrentCompNodeId >= 0) - { - // If the component hasnt changed and is valid, keep it - CreatedNodeIds.Add(CurrentCompNodeId); - } - else - { - // Upload the component input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentComp, CreatedNodeIds)) - bSuccess = false; - } - } - } - } - else - { - // No changes, keep it - CreatedNodeIds.Add(CurrentInputObjectNodeId); - } - } - else - { - // Upload the current input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentInputObject, CreatedNodeIds)) - bSuccess = false; - } - } - - // If we haven't created any input, invalidate our input node id - if (CreatedNodeIds.Num() == 0) - { - if (!InInput->HasInputTypeChanged()) - { - int32 InputNodeId = InInput->GetInputNodeId(); - TArray PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); - - if (InInput->GetInputType() == EHoudiniInputType::Asset) - { - UHoudiniAssetComponent * OuterHAC = Cast(InInput->GetOuter()); - HAPI_NodeId AssetId = OuterHAC->GetAssetId(); - - // Disconnect the asset input - if (InputNodeId >= 0 && InInput->GetInputIndex() >= 0) - { - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), AssetId, InInput->GetInputIndex())); - } - } - else if (InInput->GetInputType() == EHoudiniInputType::World) - { - // World nodes are handled by InputObjects () (with FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete) - } - else - { - if (InputNodeId >= 0) - { - for (int32 Idx = 0; Idx < PreviousInputObjectNodeIds.Num(); Idx++) - { - - // Get the object merge connected to the merge node - HAPI_NodeId InputObjectMergeId = -1; - HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); - - // Disconnect the two nodes - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); - - // Destroy the object merge node, do not delete other HDA (Asset input type) - HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); - } - } - } - } - InInput->GetCreatedDataNodeIds().Empty(); - InInput->SetInputNodeId(-1); - return bSuccess; - } - - // Get the current input's NodeId - HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); - // Check that the current input's node ID is still valid - if (InputNodeId < 0 || !FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - { - // This input doesn't have a valid NodeId yet, - // we need to create this input's merge node and update this input's node ID - FString MergeName = InInput->GetNodeBaseName() + TEXT("_Merge"); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/merge"), MergeName, true, &InputNodeId), false); - - InInput->SetInputNodeId(InputNodeId); - } - - //TODO: - // Do we want to update the input's transform? - if (false) - { - FTransform ComponentTransform = FTransform::Identity; - USceneComponent* OuterComp = Cast(InInput->GetOuter()); - if (OuterComp && !OuterComp->IsPendingKill()) - ComponentTransform = OuterComp->GetComponentTransform(); - - FHoudiniEngineUtils::HapiSetAssetTransform(InputNodeId, ComponentTransform); - //HapiUpdateInputNodeTransform(InputNodeId, ComponentTransform); - } - - // Connect all the input objects to the merge node now - int32 InputIndex = 0; - for (auto CurrentNodeId : CreatedNodeIds) - { - if (CurrentNodeId < 0) - continue; - - if (InputNodeId == CurrentNodeId) - continue; - - // Connect the current input object to the merge node - HOUDINI_CHECK_ERROR(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - InputNodeId, InputIndex++, CurrentNodeId, 0)); - } - - // Check if we need to disconnect extra input objects nodes from the merge - // This can be needed when the input had more input objects on the previous cook - TArray& PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); - if (!InInput->HasInputTypeChanged()) - { - for (int32 Idx = CreatedNodeIds.Num(); Idx < PreviousInputObjectNodeIds.Num(); Idx++) - { - // Get the object merge connected to the merge node - HAPI_NodeId InputObjectMergeId = -1; - if (InInput->GetInputType() != EHoudiniInputType::Asset) - HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); - - // Disconnect the two nodes - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); - - // Destroy the object merge node, do not destroy other HDA (Asset input type) - if (InInput->GetInputType() != EHoudiniInputType::Asset) - { - HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); - } - } - } - - // Keep track of all the nodes plugged into our input's merge - PreviousInputObjectNodeIds = CreatedNodeIds; - - // Finally, connect our main input node to the asset - bSuccess = ConnectInputNode(InInput); - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - EHoudiniInputType InputType = InInput->GetInputType(); - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Iterate on all the input objects and see if their transform needs to be uploaded - bool bSuccess = true; - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; - if (!CurrentInputObject->HasTransformChanged()) - continue; - - // Upload the current input object's transform to Houdini - if (!UploadHoudiniInputTransform(InInput, CurrentInputObject)) - { - bSuccess = false; - continue; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::ConnectInputNode(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - HAPI_NodeId AssetNodeId = InInput->GetAssetNodeId(); - if (AssetNodeId < 0) - return false; - - HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); - if (InputNodeId < 0) - return false; - - // Helper for connecting our input or setting the object path parameter - if (InInput->IsObjectPathParameter()) - { - // Now we can assign the input node path to the parameter - std::string ParamNameString = TCHAR_TO_UTF8(*(InInput->GetName())); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue( - FHoudiniEngine::Get().GetSession(), AssetNodeId, - ParamNameString.c_str(), InputNodeId), false); - } - else - { - // TODO: CHECK ME! - //if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - // return false; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), AssetNodeId, - InInput->GetInputIndex(), InputNodeId, 0), false); - } - - return true; -} - -bool -FHoudiniInputTranslator::UploadHoudiniInputObject( - UHoudiniInput* InInput, - UHoudiniInputObject* InInputObject, - TArray& OutCreatedNodeIds) -{ - if (!InInput || !InInputObject) - return false; - - FString ObjBaseName = InInput->GetNodeBaseName(); - - bool bSuccess = true; - switch (InInputObject->Type) - { - case EHoudiniInputObjectType::Object: - { - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForObject(ObjBaseName, InInputObject); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::StaticMesh: - { - UHoudiniInputStaticMesh* InputSM = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( - ObjBaseName, InputSM, InInput->GetExportLODs(), InInput->GetExportSockets(), - InInput->GetExportColliders(), InInput->GetImportAsReference()); - - if (bSuccess) - { - // If this SM input object takes in a BP, add all its BP StaticMesh components input object node id to the created id list. - if (InputSM->bIsBlueprint()) - { - for (auto & CurSMObj : InputSM->BlueprintStaticMeshes) - OutCreatedNodeIds.Add(CurSMObj->InputObjectNodeId); - } - else - { - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - } - } - - break; - } - - case EHoudiniInputObjectType::SkeletalMesh: - { - UHoudiniInputSkeletalMesh* InputSkelMes = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(ObjBaseName, InputSkelMes); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::SceneComponent: - { - UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(ObjBaseName, InputSceneComp); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::StaticMeshComponent: - { - UHoudiniInputMeshComponent* InputSMC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( - ObjBaseName, InputSMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders(), InInput->GetImportAsReference()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - { - UHoudiniInputInstancedMeshComponent* InputISMC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( - ObjBaseName, InputISMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::SplineComponent: - { - UHoudiniInputSplineComponent* InputSpline = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(ObjBaseName, InputSpline, InInput->GetUnrealSplineResolution()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::HoudiniSplineComponent: - { - UHoudiniInputHoudiniSplineComponent* InputCurve = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::HoudiniAssetActor: - case EHoudiniInputObjectType::HoudiniAssetComponent: - { - UHoudiniInputHoudiniAsset* InputHAC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetImportAsReference()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Actor: - { - UHoudiniInputActor* InputActor = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, OutCreatedNodeIds); - break; - } - - case EHoudiniInputObjectType::Landscape: - { - UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForLandscape(ObjBaseName, InputLandscape, InInput); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Brush: - { - UHoudiniInputBrush* InputBrush = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForBrush(ObjBaseName, InputBrush, InInput->GetBoundSelectorObjectArray()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::CameraComponent: - { - UHoudiniInputCameraComponent* InputCamera = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForCamera(ObjBaseName, InputCamera); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::DataTable: - { - UHoudiniInputDataTable* InputDT = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(ObjBaseName, InputDT); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Invalid: - //default: - break; - } - - // Mark that input object as not changed - if (bSuccess) - { - InInputObject->MarkChanged(false); - InInputObject->SetNeedsToTriggerUpdate(false); - } - else - { - // We couldn't update/create that input object, keep it changed but prevent it from trigger updates - InInputObject->SetNeedsToTriggerUpdate(false); - } - - return bSuccess; -} - - -// Upload transform for an input's InputObject -bool -FHoudiniInputTranslator::UploadHoudiniInputTransform( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject) -{ - if (!InInput || !InInputObject) - return false; - - auto UpdateTransform = [](const FTransform& InTransform, const HAPI_NodeId& InNodeId) - { - // Translate the Transform to HAPI - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(InTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InNodeId, &HapiTransform), false); - - return true; - }; - - bool bSuccess = true; - switch (InInputObject->Type) - { - case EHoudiniInputObjectType::StaticMesh: - { - // Simply update the Input mesh's Transform offset - if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) - bSuccess = false; - - break; - } - - case EHoudiniInputObjectType::StaticMeshComponent: - { - // Update using the static mesh component's transform - UHoudiniInputMeshComponent* InSMC = Cast(InInputObject); - if (!InSMC || InSMC->IsPendingKill()) - { - bSuccess = false; - break; - } - - FTransform NewTransform = InSMC->GetStaticMeshComponent() ? InSMC->GetStaticMeshComponent()->GetComponentTransform() : InInputObject->Transform; - if(!UpdateTransform(NewTransform, InInputObject->InputObjectNodeId)) - bSuccess = false; - - // Update the InputObject's transform - InInputObject->Transform = NewTransform; - - break; - } - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - { - // TODO: Only update the instances transform - break; - } - - case EHoudiniInputObjectType::HoudiniSplineComponent: - { - // TODO: Simply update the curve's transform? - break; - } - - case EHoudiniInputObjectType::HoudiniAssetActor: - case EHoudiniInputObjectType::HoudiniAssetComponent: - { - // TODO: Check, nothing to do? - break; - } - - case EHoudiniInputObjectType::Actor: - { - UHoudiniInputActor* InputActor = Cast(InInputObject); - if (!InputActor || InputActor->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Update the actor's transform - // To avoid further updates - if (InputActor->GetActor()) - InputActor->Transform = InputActor->GetActor()->GetTransform(); - - // Iterate on all the actor input objects and see if their transform needs to be uploaded - // TODO? Also update the component's actor transform?? - for (auto& CurrentComponent : InputActor->ActorComponents) - { - if (!CurrentComponent || CurrentComponent->IsPendingKill()) - continue; - - if (!CurrentComponent->HasTransformChanged()) - continue; - - // Upload the current input object's transform to Houdini - if (!UploadHoudiniInputTransform(InInput, CurrentComponent)) - { - bSuccess = false; - continue; - } - } - break; - } - - case EHoudiniInputObjectType::SceneComponent: - { - UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); - if (!InputSceneComp || InputSceneComp->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Update the component transform to avoid further updates - if (InputSceneComp->GetSceneComponent()) - InputSceneComp->Transform = InputSceneComp->GetSceneComponent()->GetComponentTransform(); - - break; - } - - case EHoudiniInputObjectType::Landscape: - { - // - UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) - { - bSuccess = false; - break; - } - - // - ALandscapeProxy* Landscape = InputLandscape->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Only apply diff for landscape since the HF's transform is used for value conversion as well - FTransform CurrentTransform = InputLandscape->Transform; - FTransform NewTransform = Landscape->ActorToWorld(); - - // Only handle position/rotation differences - FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); - FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); - - // Now get the HF's current transform - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - - if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) - { - bSuccess = false; - break; - } - - // Convert it to unreal - FTransform HFTransform; - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); - - // Apply the position offset if needed - if (!PosDiff.IsZero()) - HFTransform.AddToTranslation(PosDiff); - - // Apply the rotation offset if needed - if (!RotDiff.IsIdentity()) - HFTransform.ConcatenateRotation(RotDiff); - - // Convert back to a HAPI Transform and update the HF's transform - HAPI_TransformEuler NewHAPITransform; - FHoudiniApi::TransformEuler_Init(&NewHAPITransform); - FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); - NewHAPITransform.position[1] = 0.0f; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InputLandscape->InputObjectNodeId, &NewHAPITransform)) - { - bSuccess = false; - break; - } - - // Update the cached transform - InputLandscape->Transform = NewTransform; - } - - case EHoudiniInputObjectType::Brush: - { - // TODO: Update the Brush's transform - break; - } - - // Unsupported - case EHoudiniInputObjectType::Object: - case EHoudiniInputObjectType::SkeletalMesh: - case EHoudiniInputObjectType::SplineComponent: - { - break; - } - - case EHoudiniInputObjectType::Invalid: - default: - break; - } - - // Mark that input object as not changed - if (bSuccess) - { - InInputObject->MarkTransformChanged(false); - InInputObject->SetNeedsToTriggerUpdate(false); - } - else - { - // We couldn't update/create that input object, keep it changed but prevent it from trigger updates - InInputObject->SetNeedsToTriggerUpdate(false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForObject(const FString& InObjNodeName, UHoudiniInputObject* InObject) -{ - if (!InObject) - return false; - - UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) - return true; - - FString NodeName = InObjNodeName + TEXT("_") + Object->GetName(); - - // For UObjects we can't upload much, but can still create an input node - // with a single point, with an attribute pointing to the input object's path - HAPI_NodeId InputNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); - - // Update this input object's NodeId and ObjectNodeId - InObject->InputNodeId = (int32)InputNodeId; - InObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 2; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = 1; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); - - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - FVector ObjectPosition = InObject->Transform.GetLocation(); - TArray Position = - { - ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION - }; - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Position.GetData(), 0, - AttributeInfoPoint.count), false); - } - - { - // Create point attribute info for the path. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); - - // Set the point's path attribute - FString ObjectPathName = Object->GetPathName(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - ObjectPathName, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InputNodeId), false); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( - const FString& InObjNodeName, - UHoudiniInputStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UBlueprint* BP = nullptr; - UStaticMesh* SM = nullptr; - - FString SMName = InObjNodeName + TEXT("_"); - - // Get Blueprint or StaticMesh - if (InObject->bIsBlueprint()) - { - BP = InObject->GetBlueprint(); - if (!BP || BP->IsPendingKill()) - return true; - - SMName += BP->GetName(); - } - else - { - SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - SMName += SM->GetName(); - } - - // Marshall the Static Mesh to Houdini - bool bSuccess = true; - - if (bImportAsReference) - { - // Start by getting the Object's full name - FString AssetReference; - if (SM) - AssetReference += SM->GetFullName(); - - if (BP) - AssetReference += BP->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, SMName, InObject->Transform); - } - else - { - TArray StaticMeshComponents; - - // The input object is a Blueprint, Get all its StaticMeshes - if (BP) - { - USimpleConstructionScript* SCS = BP->SimpleConstructionScript; - if (SCS && !SCS->IsPendingKill()) - { - const TArray& Nodes = SCS->GetAllNodes(); - for (auto & CurNode : Nodes) - { - if (!CurNode || CurNode->IsPendingKill()) - continue; - - UActorComponent * CurComp = CurNode->ComponentTemplate; - if (!CurComp || CurComp->IsPendingKill()) - continue; - - UStaticMeshComponent* CurSMC = Cast(CurComp); - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - UStaticMesh* CurSM = CurSMC->GetStaticMesh(); - if (CurSM && !CurSM->IsPendingKill()) - StaticMeshComponents.Add(CurSMC); - - } - } - } - - // Clear previous Blueprint Static Mesh Comps (if there is any) - InObject->BlueprintStaticMeshes.Empty(); - - // This is a BP, add all the BP SM comps to its BlueprintStaticMeshes list. - if (InObject->bIsBlueprint()) - { - for (auto & CurSMC : StaticMeshComponents) - { - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - UHoudiniInputStaticMesh* SMObject = Cast( - UHoudiniInputObject::CreateTypedInputObject(CurSMC->GetStaticMesh(), InObject, InObject->GetName() + TEXT("_") + CurSMC->GetName())); - - if (!SMObject || SMObject->IsPendingKill()) - continue; - - bSuccess &= FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - CurSMC->GetStaticMesh(), SMObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); - - InObject->SetImportAsReference(false); - - // Update this input object's OBJ NodeId - SMObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(SMObject->InputNodeId); - - // Update the component's transform - FTransform ComponentTransform = CurSMC->GetRelativeTransform(); - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), SMObject->InputObjectNodeId, &HapiTransform), false); - } - - InObject->BlueprintStaticMeshes.Add(SMObject); - } - - return true; - } - // This is a normal static mesh input, process it normally as a static mesh Input Object - else - { - bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, InObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); - } - } - - InObject->SetImportAsReference(bImportAsReference); - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - // If the Input mesh has a Transform offset - FTransform TransformOffset = InObject->Transform; - if (!TransformOffset.Equals(FTransform::Identity)) - { - // Updating the Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USkeletalMesh* SkelMesh = InObject->GetSkeletalMesh(); - if (!SkelMesh || SkelMesh->IsPendingKill()) - return true; - - // Get the SM's transform offset - FTransform TransformOffset = InObject->Transform; - - // TODO - // Support this type of input object - // FUnrealMeshTranslator::CreateInputNodeForSkeletalMesh(...) - - return HapiCreateInputNodeForObject(InObjNodeName, InObject); -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USceneComponent* SceneComp = InObject->GetSceneComponent(); - if (!SceneComp || SceneComp->IsPendingKill()) - return true; - - // Get the Scene Component's transform - FTransform TransformOffset = InObject->Transform; - - // Get the parent Actor's transform - FTransform ParentTransform = InObject->ActorTransform; - - // Dont do that! - return false; - - // TODO - // Support this type of input object - return HapiCreateInputNodeForObject(InObjNodeName, InObject); -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UStaticMeshComponent* SMC = InObject->GetStaticMeshComponent(); - if (!SMC || SMC->IsPendingKill()) - return true; - - // Get the component's Static Mesh - UStaticMesh* SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - // Marshall the Static Mesh to Houdini - FString SMCName = InObjNodeName + TEXT("_") + SMC->GetName(); - - bool bSuccess = true; - if (bImportAsReference) - { - // Start by getting the Object's full name - FString AssetReference = SM->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, SMCName, AssetReference, InObject->Transform); - - } - else - { - bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, InObject->InputNodeId, SMCName, SMC, bExportLODs, bExportSockets, bExportColliders); - } - - InObject->SetImportAsReference(bImportAsReference); - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - // Update this input object's cache data - InObject->Update(SMC); - - // Update the component's transform - FTransform ComponentTransform = InObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputInstancedMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) - return true; - - // Get the ISMC - UInstancedStaticMeshComponent* ISMC = InObject->GetInstancedStaticMeshComponent(); - if (!ISMC || ISMC->IsPendingKill()) - return true; - - HAPI_NodeId NewNodeId = -1; - if (!FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( - ISMC, InObjNodeName, NewNodeId, bExportLODs, bExportSockets, bExportColliders, false)) - return false; - - // Update this input object's node IDs - InObject->InputNodeId = NewNodeId; - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); - - // Update the component's cached instances - InObject->Update(ISMC); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USplineComponent* Spline = InObject->GetSplineComponent(); - if (!Spline || Spline->IsPendingKill()) - return true; - - - int32 NumberOfSplineControlPoints = InObject->NumberOfSplineControlPoints; - - TArray SplineControlPoints = InObject->SplineControlPoints; - - FString NodeName = InObjNodeName + TEXT("_") + InObject->GetName(); - - if (!FUnrealSplineTranslator::CreateInputNodeForSplineComponent(Spline, SplineResolution, InObject->InputNodeId, NodeName)) - return false; - - // Cache the exported curve's data to the input object - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - InObject->MarkChanged(true); - - //if (!HapiCreateInputNodeForObject(InObjNodeName, InObject)) - // return false; - - // Update the component's cached data - InObject->Update(Spline); - - // Update the component's transform - FTransform ComponentTransform = InObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UHoudiniSplineComponent* Curve = InObject->GetCurveComponent(); - if (!Curve || Curve->IsPendingKill()) - return true; - - if (!FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent(InObjNodeName, Curve)) - return false; - - // See if the component needs it node Id invalidated - //if (InObject->InputNodeId < 0) - // Curve->SetNodeId(InObject->InputNodeId); - - // Cache the exported curve's data to the input object - InObject->InputNodeId = Curve->GetNodeId(); - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - //InObject->CurveType = Curve->GetCurveType(); - //InObject->CurveMethod = Curve->GetCurveMethod(); - //InObject->Reversed = Curve->IsReversed(); - InObject->Update(Curve); - - InObject->MarkChanged(true); - - return true; -} - -bool -FHoudiniInputTranslator:: -HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UHoudiniAssetComponent* InputHAC = InObject->GetHoudiniAssetComponent(); - if (!InputHAC || InputHAC->IsPendingKill()) - return true; - - if (!InputHAC->CanDeleteHoudiniNodes()) - return true; - - UHoudiniInput* HoudiniInput = Cast(InObject->GetOuter()); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - return true; - - UHoudiniAssetComponent* OuterHAC = Cast(HoudiniInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return true; - - // Do not allow using ourself as an input, terrible things would happen - if (InputHAC->GetAssetId() == OuterHAC->GetAssetId()) - return false; - - // If previously imported as ref, delete the input node. - if (InObject->InputNodeId > -1 && InObject->GetImportAsReference()) - { - int32 PreviousInputNodeId = InObject->InputNodeId; - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InObject->GetName()); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InObject->GetName()); - } - } - - InObject->SetImportAsReference(bImportAsReference); - - // If this object is in an Asset input, we need to set the InputNodeId directl - // to avoid creating extra merge nodes. World inputs should not do that! - bool bIsAssetInput = HoudiniInput->GetInputType() == EHoudiniInputType::Asset; - - if (bImportAsReference) - { - InObject->InputNodeId = -1; - InObject->InputObjectNodeId = -1; - - if(bIsAssetInput) - HoudiniInput->SetInputNodeId(-1); - - // Start by getting the Object's full name - FString AssetReference = InputHAC->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - if (!FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform)) // do not delete previous node if it was HAC - return false; - - if (bIsAssetInput) - HoudiniInput->SetInputNodeId(InObject->InputNodeId); - } - - InputHAC->AddDownstreamHoudiniAsset(OuterHAC); - - //if (HAC->NeedsInitialization()) - // HAC->MarkAsNeedInstantiation(); - - //HoudiniInput->SetAssetNodeId(HAC->GetAssetId()); - - // TODO: This might be uneeded as this function should only be called - // after we're not wiating on the input asset... - if (InputHAC->AssetState == EHoudiniAssetState::NeedInstantiation) - { - // If the input HAC needs to be instantiated, tell it do so - InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; - // Mark this object's input as changed so we can properly update after the input HDA's done instantiating/cooking - HoudiniInput->MarkChanged(true); - } - - if (InputHAC->NeedsInitialization() || InputHAC->NeedUpdate()) - return false; - - if (!bImportAsReference) - { - if (bIsAssetInput) - HoudiniInput->SetInputNodeId(InputHAC->GetAssetId()); - - InObject->InputNodeId = InputHAC->GetAssetId(); - } - - InObject->InputObjectNodeId = InObject->InputNodeId; - - bool bReturn = InObject->InputNodeId > -1; - - if(bIsAssetInput) - bReturn = FHoudiniInputTranslator::ConnectInputNode(HoudiniInput); - - return bReturn; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (!InObject || InObject->IsPendingKill()) - return false; - - AActor* Actor = InObject->GetActor(); - if (!Actor || Actor->IsPendingKill()) - return true; - - // Check if this is a world input and if this is a HoudiniAssetActor - // If so we need to build static meshes for any proxy meshes - if (InInput->GetInputType() == EHoudiniInputType::World && Actor->IsA()) - { - AHoudiniAssetActor *HAA = Cast(Actor); - UHoudiniAssetComponent *HAC = HAA->GetHoudiniAssetComponent(); - if (HAC && !HAC->IsPendingKill()) - { - if (HAC->HasAnyCurrentProxyOutput()) - { - bool bPendingDeleteOrRebuild = false; - bool bInvalidState = false; - const bool bIsHoudiniCookedDataAvailable = HAC->IsHoudiniCookedDataAvailable(bPendingDeleteOrRebuild, bInvalidState); - if (bIsHoudiniCookedDataAvailable) - { - // Build the static mesh - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); - // Update the input object since a new StaticMeshComponent could have been created - UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) - { - InObject->Update(InputObject); - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - } - } - else if (!bPendingDeleteOrRebuild && !bInvalidState) - { - // Request a cook with no proxy output - HAC->MarkAsNeedCook(); - HAC->SetNoProxyMeshNextCookRequested(true); - } - } - else if (InObject->ActorComponents.Num() == 0 && HAC->HasAnyOutputComponent()) - { - // The HAC has non-proxy output components, but the InObject does not have any - // actor components. This can arise after a cook if previously there were only - // proxies and the input was created when there were only proxies - // Try to update the input to find new components - UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) - { - InObject->Update(InputObject); - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - } - } - } - } - - // Now, commit all of this actor's component - int32 ComponentIdx = 0; - for (UHoudiniInputSceneComponent* CurComponent : InObject->ActorComponents) - { - if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) - ComponentIdx++; - } - - // TODO: We should call Update here... - // needs to be fixed - - // Cache our transformn - InObject->Transform = Actor->GetTransform(); - - // Do something for our actor's transform? - /* - // TODO - // Support this type of input object - FString ObjNodeName = InInput->GetNodeBaseName(); - return HapiCreateInputNodeForObject(ObjNodeName, InObject); - */ - - //TODO? Check - // return true if we have at least uploaded one component - // return (ComponentIdx > 0); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( - const FString& InObjNodeName, UHoudiniInputLandscape* InObject, UHoudiniInput* InInput) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - if (!InInput || InInput->IsPendingKill()) - return false; - - ALandscapeProxy* Landscape = InObject->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) - return true; - - EHoudiniLandscapeExportType ExportType = InInput->GetLandscapeExportType(); - - bool bSucess = false; - if (ExportType == EHoudiniLandscapeExportType::Heightfield) - { - bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); - } - else - { - bool bExportLighting = InInput->bLandscapeExportLighting; - bool bExportMaterials = InInput->bLandscapeExportMaterials; - bool bExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; - bool bExportTileUVs = InInput->bLandscapeExportTileUVs; - bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; - bool bExportAsMesh = InInput->LandscapeExportType == EHoudiniLandscapeExportType::Mesh; - - bSucess = FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( - Landscape, InObject->InputNodeId, InObjNodeName, - bExportAsMesh, bExportTileUVs, bExportNormalizedUVs, bExportLighting, bExportMaterials); - } - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - InObject->Update(Landscape); - - return bSucess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForBrush(const FString& InObjNodeName, UHoudiniInputBrush* InObject, TArray* ExcludeActors) -{ - if (!IsValid(InObject)) - return false; - - ABrush* BrushActor = InObject->GetBrush(); - if (!IsValid(BrushActor)) - return true; - - if (!FUnrealBrushTranslator::CreateInputNodeForBrush(InObject, BrushActor, ExcludeActors, InObject->InputNodeId, InObjNodeName)) - return false; - - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - InObject->Update(BrushActor); - - return true; -} - - - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, UHoudiniInputCameraComponent* InInputObject) -{ - if (!InInputObject || InInputObject->IsPendingKill()) - return false; - - UCameraComponent* Camera = InInputObject->GetCameraComponent(); - if (!Camera || Camera->IsPendingKill()) - return true; - - FString NodeName = InNodeName + TEXT("_") + Camera->GetName(); - - // Create the camera OBJ. - int32 CameraNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( - -1, TEXT("Object/cam"), InNodeName, true, &CameraNodeId), false); - - // set "Pixel Aspect Ratio" (aspect) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "aspect", 0, InInputObject->AspectRatio), false); - - // set "Projection" (projection) (0 persp, 1 ortho) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "projection", 0, InInputObject->bIsOrthographic ? 1 : 0), false); - - // set Ortho Width (orthowidth) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "orthowidth", 0, InInputObject->OrthoWidth), false); - - // set Near Clippin (near) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "near", 0, InInputObject->OrthoNearClipPlane), false); - - // set far clipping (far) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "far", 0, InInputObject->OrthoFarClipPlane), false); - - // Set the transform - HAPI_TransformEuler H_Transform; - FHoudiniApi::TransformEuler_Init(&H_Transform); - FHoudiniEngineUtils::TranslateUnrealTransform(Camera->GetComponentTransform(), H_Transform); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CameraNodeId, &H_Transform), false); - - // Update the component's transform - FTransform ComponentTransform = InInputObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Camera orientation need to be adjusted - HapiTransform.rotationEuler[1] += -90.0f; - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CameraNodeId, &HapiTransform), false); - } - - // Update this input's NodeId and ObjectNodeId - InInputObject->InputNodeId = -1;// (int32)CameraNodeId; - InInputObject->InputObjectNodeId = (int32)CameraNodeId; - - // Update this input object's cache data - InInputObject->Update(Camera); - - return true; -} - -bool -FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // We need to call BuildAllInputs here to update all the inputs, - // and make sure that the object path parameter inputs' parameter ids are up to date - if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) - return false; - - // We need to update the AssetID stored on all the inputs - // and mark all the input objects for this input type as changed - int32 HACAssetId = HAC->GetAssetId(); - for (auto CurrentInput : HAC->Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // - CurrentInput->SetAssetNodeId(HACAssetId); - - // We need to delete the nodes created for the input objects if they are valid - // (since the node IDs are transients, this likely means we're handling a recook/rebuild - // and therefore expect to recreate the input nodes) - DestroyInputNodes(CurrentInput, CurrentInput->GetInputType()); - } - - return true; -} - - - -bool -FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Only tick/cook when in Editor - // This prevents PIE cooks or runtime cooks due to inputs moving - AActor* ActorOwner = HAC->GetOwner(); - if (ActorOwner) - { - if (!ActorOwner->GetWorld() || (ActorOwner->GetWorld()->WorldType != EWorldType::Editor)) - return false; - } - -#if WITH_EDITOR - // Stop outliner objects from causing recooks while input objects are dragged around - if (FHoudiniMoveTracker::Get().IsObjectMoving) - { - //HOUDINI_LOG_MESSAGE(TEXT("Object moving, not updating world inputs!")); - return false; - } -#endif - - for (auto CurrentInput : HAC->Inputs) - { - if (!CurrentInput) - continue; - if (CurrentInput->GetInputType() != EHoudiniInputType::World) - continue; - - UpdateWorldInput(CurrentInput); - } - - return true; -} - -bool -FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (InInput->GetInputType() != EHoudiniInputType::World) - return false; - - TArray* InputObjectsPtr = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjectsPtr) - return false; - - bool bHasChanged = false; - if (InInput->IsWorldInputBoundSelector() && InInput->GetWorldInputBoundSelectorAutoUpdates()) - { - // If the input is in bound selector mode, and auto-update is enabled - // update the actors selected by the bounds first - bHasChanged = InInput->UpdateWorldSelectionFromBoundSelectors(); - } - - // See if we need to update the components for this input - // look for deleted actors/components - TArray ObjectToDeleteIndices; - for(int32 InputObjIdx = 0; InputObjIdx < InputObjectsPtr->Num(); InputObjIdx++) - { - UHoudiniInputActor* ActorObject = Cast((*InputObjectsPtr)[InputObjIdx]); - if (!ActorObject || ActorObject->IsPendingKill()) - continue; - - // Make sure the actor is still valid - bool bValidActorObject = ActorObject->GetActor() && !ActorObject->GetActor()->IsPendingKill(); - - // For BrushActors, the brush and actors must be valid as well - UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); - if (bValidActorObject && BrushActorObject) - { - ABrush* BrushActor = BrushActorObject->GetBrush(); - if (!IsValid(BrushActor)) - bValidActorObject = false; - else if (!IsValid(BrushActor->Brush)) - bValidActorObject = false; - } - - // The actor is no longer valid, mark it for deletion - if (!bValidActorObject) - { - if ((ActorObject->InputNodeId > 0) || (ActorObject->InputObjectNodeId > 0)) - { - ActorObject->InvalidateData(); - // We only need to update the input if the actors nodes were created in Houdini - bHasChanged = true; - } - - // Delete the Actor object - ObjectToDeleteIndices.Add(InputObjIdx); - continue; - } - - if (ActorObject->HasActorTransformChanged()) - { - ActorObject->MarkTransformChanged(true); - bHasChanged = true; - } - - if (ActorObject->HasContentChanged()) - { - ActorObject->MarkChanged(true); - bHasChanged = true; - } - - // Iterates on all of the actor's component - TArray ComponentToDeleteIndices; - for (int32 CompIdx = 0; CompIdx < ActorObject->ActorComponents.Num(); CompIdx++) - { - UHoudiniInputSceneComponent* CurActorComp = ActorObject->ActorComponents[CompIdx]; - if (!CurActorComp || CurActorComp->IsPendingKill()) - continue; - - // Make sure the actor is still valid - if (!CurActorComp->InputObject || CurActorComp->InputObject->IsPendingKill()) - { - // If it's not, mark it for deletion - if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) - { - CurActorComp->InvalidateData(); - - // We only need to update the input if the object were created in Houdini - bHasChanged = true; - } - - // Delete the component object - ComponentToDeleteIndices.Add(CompIdx); - - continue; - } - - if (CurActorComp->HasComponentTransformChanged()) - { - CurActorComp->MarkTransformChanged(true); - bHasChanged = true; - } - - if (CurActorComp->HasComponentChanged()) - { - CurActorComp->MarkChanged(true); - bHasChanged = true; - } - } - - // Delete the components objects on the actor that were marked for deletion - for (int32 ToDeleteIdx = ComponentToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) - ActorObject->ActorComponents.RemoveAt(ComponentToDeleteIndices[ToDeleteIdx]); - } - - // Delete the actor objects that were marked for deletion - for (int32 ToDeleteIdx = ObjectToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) - InputObjectsPtr->RemoveAt(ObjectToDeleteIndices[ToDeleteIdx]); - - // Mark the input as changed if need so it will trigger an upload - if (bHasChanged) - InInput->MarkChanged(true); - - return true; -} - - -bool -FHoudiniInputTranslator::CreateInputNodeForReference( - HAPI_NodeId& InputNodeId, - const FString & InRef, - const FString & InputNodeName, - const FTransform& InTransform) -{ - HAPI_NodeId NewNodeId = -1; - - // Create a single input node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_UTF8(*InputNodeName)), false); - - /* - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, nullptr), false); - */ - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) - return false; - - // We have now created a valid new input node, delete the previous one - HAPI_NodeId PreviousInputNodeId = InputNodeId; - if (PreviousInputNodeId >= 0) - { - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); - } - } - - // Create and initialize a part containing one point with a point attribute - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - - PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = 1; - PartInfo.vertexCount = 0; - PartInfo.faceCount = 0; - PartInfo.pointCount = 1; - PartInfo.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, &PartInfo), false); - - // Point Position Attribute - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - FVector ObjectPosition = InTransform.GetLocation(); - TArray Position = - { - ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION - }; - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Position.GetData(), 0, - AttributeInfoPoint.count), false); - } - - // String Attribute - { - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint), false); - - // Set string attribute - std::string AttriString = TCHAR_TO_ANSI(*InRef); - const char* AttriStringRaw = AttriString.c_str(); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NewNodeId, 0, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint, - &AttriStringRaw, 0, 1), false); - } - - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NewNodeId), false); - - InputNodeId = NewNodeId; - return true; -} - - - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeName, UHoudiniInputDataTable* InInputObject) -{ - //TODO - if (!InInputObject || InInputObject->IsPendingKill()) - return false; - - UDataTable* DataTable = InInputObject->GetDataTable(); - if (!DataTable || DataTable->IsPendingKill()) - return true; - - // Get the DataTable data as string - TArray> TableData = DataTable->GetTableData(EDataTableExportFlags::None); - if (TableData.Num() <= 1) - return true; - - int32 NumRows = TableData.Num() - 1; - int32 NumAttributes = TableData[0].Num(); - if (NumRows <= 0 || NumAttributes <= 0) - return true; - - // Create the input node - FString NodeName = InNodeName + TEXT("_") + DataTable->GetName(); - HAPI_NodeId InputNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); - - // Update this input object's NodeId and ObjectNodeId - InInputObject->InputNodeId = (int32)InputNodeId; - InInputObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = NumAttributes; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = NumRows; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); - - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - TArray Positions; - Positions.SetNum(NumRows * 3); - for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) - { - Positions[RowIdx * 3] = 0.0f; - Positions[RowIdx * 3 + 1] = (float)RowIdx; - Positions[RowIdx * 3 + 2] = 0.0f; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Positions.GetData(), 0, - AttributeInfoPoint.count), false); - } - - { - // Create point attribute info for the path. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); - - // Get the object path - FString ObjectPathName = DataTable->GetPathName(); - - // Create an array - TArray ObjectPaths; - ObjectPaths.Init(ObjectPathName, NumRows); - - // Set the point's path attribute - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - ObjectPaths, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); - } - - { - // Create point attribute info for data table RowTable class name - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, &AttributeInfoPoint), false); - - // Get the object path - FString RowStructName = DataTable->GetRowStructName().ToString(); - - // Create an array - TArray RowStructNames; - RowStructNames.Init(RowStructName, NumRows); - - // Set the point's path attribute - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - RowStructNames, InputNodeId, 0, - HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, AttributeInfoPoint), false); - } - - // Now set the attributes values for each "point" of the data table - for (int32 ColIdx = 0; ColIdx < NumAttributes; ColIdx++) - { - // attribute name is "unreal_data_table_COL_NAME" - FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + TableData[0][ColIdx]; - - // We need to gt all values for that attribute - TArray AttributeValues; - AttributeValues.SetNum(NumRows); - for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) - { - AttributeValues[RowIdx] = TableData[RowIdx + 1][ColIdx]; - } - - // Create a point attribute info - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = NumRows; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_POINT; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - TCHAR_TO_ANSI(*CurAttrName), &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - AttributeValues, InputNodeId, 0, - CurAttrName, AttributeInfo), false); - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InputNodeId), false); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr), false); - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputTranslator.h" + +#include "HoudiniInput.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInputObject.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniAssetActor.h" +#include "HoudiniOutputTranslator.h" +#include "UnrealBrushTranslator.h" +#include "UnrealSplineTranslator.h" +#include "UnrealMeshTranslator.h" +#include "UnrealInstanceTranslator.h" +#include "UnrealLandscapeTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "Engine/Brush.h" +#include "Engine/DataTable.h" +#include "Camera/CameraComponent.h" + +#include "Engine/SimpleConstructionScript.h" +#include "Engine/SCS_Node.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Editor/UnrealEdEngine.h" + #include "UnrealEdGlobals.h" +#endif + +#include "HCsgUtils.h" + +#include "Async/Async.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#if WITH_EDITOR +// Allows checking of objects currently being dragged around +struct FHoudiniMoveTracker +{ + FHoudiniMoveTracker() : IsObjectMoving(false) + { + GEditor->OnBeginObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = true; }); + GEditor->OnEndObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + + GEditor->OnBeginCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + GEditor->OnEndCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + } + static FHoudiniMoveTracker& Get() { static FHoudiniMoveTracker Instance; return Instance; } + + bool IsObjectMoving; +}; +#endif + +// +bool +FHoudiniInputTranslator::UpdateInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) + { + // Failed to create the inputs + return false; + } + + return true; +} + +bool +FHoudiniInputTranslator::BuildAllInputs( + const HAPI_NodeId& AssetId, + class UObject* InOuterObject, + TArray& Inputs, + TArray& Parameters) +{ + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Start by getting the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // Get the number of geo (SOP) inputs + int32 InputCount = AssetInfo.geoInputCount; + /* + // It's best to update the input count even if the hda hasnt cooked + // as it can cause loaded geo inputs to disappear upon loading the level + if ( AssetInfo.hasEverCooked ) + { + InputCount = AssetInfo.geoInputCount; + } + */ + // Also look for object path parameters inputs + TArray> InputParameters; + for (auto Param : Parameters) + { + if (Param->GetParameterType() == EHoudiniParameterType::Input) + InputParameters.Add(Param); + } + + InputCount += InputParameters.Num(); + + // Append new inputs as needed + if (InputCount > Inputs.Num()) + { + int32 NumNewInputs = InputCount - Inputs.Num(); + for (int32 InputIdx = Inputs.Num(); InputIdx < InputCount; ++InputIdx) + { + FString InputObjectName = TEXT("Input") + FString::FromInt(InputIdx + 1); + UHoudiniInput * NewInput = NewObject< UHoudiniInput >( + InOuterObject, + UHoudiniInput::StaticClass(), + FName(*InputObjectName), + RF_Transactional); + + if (!NewInput || NewInput->IsPendingKill()) + { + //HOUDINI_LOG_WARNING("Failed to create asset input"); + continue; + } + // Create a default curve object here to avoid Transaction issue + //NewInput->CreateDefaultCurveInputObject(); + + Inputs.Add(NewInput); + } + } + else if (InputCount < Inputs.Num()) + { + // TODO: Properly clean up the input object + created nodes? + for (int32 InputIdx = Inputs.Num() - 1; InputIdx >= InputCount; InputIdx--) + { + UHoudiniInput* CurrentInput = Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + FHoudiniInputTranslator::DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); + + // DO NOT MANUALLY DESTROY THE OLD/DANGLING INPUTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + //CurrentInput->ConditionalBeginDestroy(); + //CurrentInput = nullptr; + } + + Inputs.SetNum(InputCount); + } + + // Now, check the inputs in the array match the geo inputs + //for (int32 GeoInIdx = 0; GeoInIdx < AssetInfo.geoInputCount; GeoInIdx++) + bool bBlueprintStructureChanged = false; + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) + { + UHoudiniInput* CurrentInput = Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // Create default Name/Label/Help + FString CurrentInputName = TEXT("Input") + FString::FromInt(InputIdx + 1); + FString CurrentInputLabel = CurrentInputName; + FString CurrentInputHelp; + + // Set the nodeId + CurrentInput->SetAssetNodeId(AssetId); + + // Is this an object path parameter input? + bool bIsObjectPath = InputIdx >= AssetInfo.geoInputCount; + if (!bIsObjectPath) + { + // Mark this input as a SOP input + CurrentInput->SetSOPInput(InputIdx); + + // Get and set the name + HAPI_StringHandle InputStringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInputName( + FHoudiniEngine::Get().GetSession(), + AssetId, InputIdx, &InputStringHandle)) + { + FHoudiniEngineString HoudiniEngineString(InputStringHandle); + HoudiniEngineString.ToFString(CurrentInputLabel); + } + } + else + { + // Get this input's parameter index in the objpath param array + int32 CurrentParmIdx = InputIdx - AssetInfo.geoInputCount; + + UHoudiniParameter* CurrentParm = nullptr; + if (InputParameters.IsValidIndex(CurrentParmIdx)) + { + if (InputParameters[CurrentParmIdx].IsValid()) + CurrentParm = InputParameters[CurrentParmIdx].Get(); + } + + int32 ParmId = -1; + if (CurrentParm && !CurrentParm->IsPendingKill()) + { + ParmId = CurrentParm->GetParmId(); + CurrentInputName = CurrentParm->GetParameterName(); + CurrentInputLabel = CurrentParm->GetParameterLabel(); + CurrentInputHelp = CurrentParm->GetParameterHelp(); + } + + UHoudiniParameterOperatorPath* CurrentObjPathParm = Cast(CurrentParm); + if (CurrentObjPathParm && !CurrentObjPathParm->IsPendingKill()) + { + CurrentObjPathParm->HoudiniInput = CurrentInput; + } + + // Mark this input as an object path parameter input + CurrentInput->SetObjectPathParameter(ParmId); + } + + CurrentInput->SetName(CurrentInputName); + CurrentInput->SetLabel(CurrentInputLabel); + + if ( CurrentInputHelp.IsEmpty() ) + { + CurrentInputHelp = CurrentInputLabel + TEXT("(") + CurrentInputName + TEXT(")"); + } + CurrentInput->SetHelp(CurrentInputHelp); + + // If the input type is invalid, + // We need to initialize its default + if (CurrentInput->GetInputType() == EHoudiniInputType::Invalid) + { + // Initialize it to the default corresponding to its name + CurrentInput->SetInputType(GetDefaultInputTypeFromLabel(CurrentInputLabel), bBlueprintStructureChanged); + + // Preset the default HDA for objpath input + SetDefaultAssetFromHDA(CurrentInput); + } + + // Update input objects data on UE side for all types of inputs. + switch (CurrentInput->GetInputType()) + { + case EHoudiniInputType::Curve: + FHoudiniSplineTranslator::UpdateHoudiniInputCurves(CurrentInput); + break; + case EHoudiniInputType::Landscape: + //FUnrealLandscapeTranslator::UpdateHoudiniInputLandscapes(CurrentInput); + break; + case EHoudiniInputType::Asset: + break; + case EHoudiniInputType::Geometry: + break; + case EHoudiniInputType::Skeletal: + break; + case EHoudiniInputType::World: + break; + default: + break; + } + } + + return true; +} + +bool +FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + if (!InputToDestroy || InputToDestroy->IsPendingKill()) + return false; + + // Start by disconnecting the input / nullifying the object path parameter + if (InputToDestroy->IsObjectPathParameter()) + { + // Just set the objpath parameter to null + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + InputToDestroy->GetAssetNodeId(), "", + InputToDestroy->GetParameterId(), 0); + } + else + { + // Get the asset / created input node ID + HAPI_NodeId HostAssetId = InputToDestroy->GetAssetNodeId(); + HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); + + // Only disconnect if both are valid + if (HostAssetId >= 0 && CreatedInputId >= 0) + { + FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), + HostAssetId, InputToDestroy->GetInputIndex()); + } + } + + if (InputType == EHoudiniInputType::Asset) + { + // TODO: + // If we're an asset input, just remove us from the downstream connection on the input HDA + // then reset this input's flag + + // TODO: Check this? Clean our DS assets?? why?? likely uneeded + UHoudiniAssetComponent* OuterHAC = Cast(InputToDestroy->GetOuter()); + if (OuterHAC) + OuterHAC->ClearDownstreamHoudiniAsset(); + + InputToDestroy->SetInputNodeId(-1); + } + + return true; +} + +bool +FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + if (!InputToDestroy || InputToDestroy->IsPendingKill()) + return false; + + if (!InputToDestroy->CanDeleteHoudiniNodes()) + return false; + + // If we're destroying an asset input, don't destroy anything as we don't want to destroy the input HDA + // a simple disconnect is sufficient + if (InputType == EHoudiniInputType::Asset) + return true; + + // Destroy the nodes created by all the input objects + TArray CreatedInputDataAssetIds = InputToDestroy->GetCreatedDataNodeIds(); + TArray* InputObjectNodes = InputToDestroy->GetHoudiniInputObjectArray(InputType); + if (InputObjectNodes) + { + for (auto CurInputObject : *InputObjectNodes) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + if (CurInputObject->Type == EHoudiniInputObjectType::HoudiniAssetComponent) + { + // Remove this input object's node Id from the + // CreatedInputDataAssetIds array to avoid its deletion further down + CreatedInputDataAssetIds.Remove(CurInputObject->InputNodeId); + CurInputObject->InputNodeId = -1; + CurInputObject->InputObjectNodeId = -1; + continue; + } + + // For Actor input objects, set the input node id for all component objects to -1, + if (CurInputObject->Type == EHoudiniInputObjectType::Actor) + { + UHoudiniInputActor* CurActorInputObject = Cast(CurInputObject); + if (CurActorInputObject) + { + for (auto & CurActorComponent : CurActorInputObject->ActorComponents) + { + if (!CurActorComponent || CurActorComponent->IsPendingKill()) + continue; + + // No need to delete the nodes created for an asset component manually here, + // As they will be deleted when we clean up the CreateNodeIds array + CurActorComponent->InputNodeId = -1; + } + } + } + // No need to delete the nodes created for an asset component manually here, + // As they will be deleted when we clean up the CreateNodeIds array + + if (CurInputObject->InputNodeId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputNodeId); + CurInputObject->InputNodeId = -1; + } + + if(CurInputObject->InputObjectNodeId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputObjectNodeId); + CurInputObject->InputObjectNodeId = -1; + + // TODO: CHECK ME! + HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CurInputObject->InputObjectNodeId); + + // Delete its parent node as well + if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentNodeId)) + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentNodeId); + } + + // Also directly invalidate HoudiniSplineComponent's node IDs. + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast(CurInputObject); + if (IsValid(HoudiniSplineInputObject) && !IsGarbageCollecting()) + { + UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (SplineComponent && !SplineComponent->IsPendingKill()) + { + SplineComponent->SetNodeId(-1); + } + } + + CurInputObject->MarkChanged(true); + } + } + + // Destroy all the input assets + for (HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds) + { + if (AssetNodeId < 0) + continue; + + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), AssetNodeId); + } + CreatedInputDataAssetIds.Empty(); + + // Then simply destroy the input's parent OBJ node + if (InputToDestroy->GetInputNodeId() >= 0) + { + HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedInputId); + + if (CreatedInputId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CreatedInputId); + InputToDestroy->SetInputNodeId(-1); + } + + if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentId)) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentId); + } + } + + return true; +} + +bool +FHoudiniInputTranslator::DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + // Start by disconnecting the input/object merge + bool bSuccess = DisconnectInput(InputToDestroy, InputType); + + // Then destroy the created input nodes + bSuccess &= DestroyInputNodes(InputToDestroy, InputType); + + return bSuccess; +} + + +EHoudiniInputType +FHoudiniInputTranslator::GetDefaultInputTypeFromLabel(const FString& InputName) +{ + // We'll try to find these magic words to try to detect the default input type + //FString geoPrefix = TEXT("geo"); + FString curvePrefix = TEXT("curve"); + + FString landscapePrefix = TEXT("landscape"); + FString landscapePrefix2 = TEXT("terrain"); + FString landscapePrefix3 = TEXT("heightfield"); + + FString worldPrefix = TEXT("world"); + FString worldPrefix2 = TEXT("outliner"); + + FString assetPrefix = TEXT("asset"); + FString assetPrefix2 = TEXT("hda"); + + // By default, geometry input is chosen. + EHoudiniInputType InputType = EHoudiniInputType::Geometry; + + if (InputName.Contains(curvePrefix, ESearchCase::IgnoreCase)) + InputType = EHoudiniInputType::Curve; + + else if ((InputName.Contains(landscapePrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(landscapePrefix2, ESearchCase::IgnoreCase)) + || (InputName.Contains(landscapePrefix3, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::Landscape; + + else if ((InputName.Contains(worldPrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(worldPrefix2, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::World; + + else if ((InputName.Contains(assetPrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(assetPrefix2, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::Asset; + + return InputType; +} + +bool +FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bForce) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (!InInput->HasInputTypeChanged() && !bForce) + return true; + + // - Handle switching AWAY from an input type + DisconnectAndDestroyInput(InInput, InInput->GetPreviousInputType()); + + // Invalidate the previous input type now that we've actually changed + //InInput->SetPreviousInputType(EHoudiniInputType::Invalid); + + //ChangeInputType(InInput, NewType); + + // TODO: + // - Handle updating to the new input type + // downstream asset connection, static mesh update, curve creation... + + // Mark all the objects from this input has changed so they upload themselves + InInput->MarkAllInputObjectsChanged(true); + + return true; +} + +bool +FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input) +{ + // + if (!Input || Input->IsPendingKill()) + return false; + + // We just handle geo inputs + if (EHoudiniInputType::Geometry != Input->GetInputType()) + return false; + + // Make sure we're linked to a valid object path parameter + if (Input->GetParameterId() < 0) + return false; + + // There is a default slot, don't add if slot is already filled + //if (InputObjects.Num() > 1) + // return false; + + // Get our ParmInfo + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + Input->GetAssetNodeId(), Input->GetParameterId(), &FoundParamInfo)) + { + return false; + } + + // TODO: FINISH ME! + + /* + // Get our string value + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + Input->GetInputNodeId(), false, + &StringHandle, FoundParamInfo.stringValuesIndex, 1) ) + { + FString OutValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(OutValue)) + { + // Set default object on the HDA instance - will override the parameter string + // and apply the object input local-path thing for the HDA cook. + if (OutValue.Len() > 0) + { + UObject * pObject = LoadObject(nullptr, *OutValue); + if (pObject) + { + return AddInputObject(pObject); + } + } + } + } + */ + + return false; +} + +bool +FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + //for (auto CurrentInput : HAC->Inputs) + for(int32 InputIdx = 0; InputIdx < HAC->GetNumInputs(); InputIdx++) + { + UHoudiniInput*& CurrentInput = HAC->Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill() || !CurrentInput->HasChanged()) + continue; + + // First thing, see if we need to change the input type + if (CurrentInput->HasInputTypeChanged()) + { + ChangeInputType(CurrentInput, false); + } + + if (CurrentInput->GetInputType() == EHoudiniInputType::Landscape && CurrentInput->HasLandscapeExportTypeChanged()) + { + DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); + CurrentInput->MarkAllInputObjectsChanged(true); + CurrentInput->SetHasLandscapeExportTypeChanged(false); + } + + bool bSuccess = true; + if (CurrentInput->IsDataUploadNeeded()) + { + bSuccess &= UploadInputData(CurrentInput); + CurrentInput->MarkDataUploadNeeded(!bSuccess); + } + + if (CurrentInput->IsTransformUploadNeeded()) + { + bSuccess &= UploadInputTransform(CurrentInput); + } + + // Update the input properties AFTER eventually uploading it + bSuccess = UpdateInputProperties(CurrentInput); + + if (bSuccess) + { + CurrentInput->MarkChanged(false); + CurrentInput->MarkAllInputObjectsChanged(false); + } + + if (CurrentInput->HasInputTypeChanged()) + CurrentInput->SetPreviousInputType(EHoudiniInputType::Invalid); + + // Even if we failed, no need to try updating again. + CurrentInput->SetNeedsToTriggerUpdate(false); + } + + return true; +} + +bool +FHoudiniInputTranslator::UpdateInputProperties(UHoudiniInput* InInput) +{ + bool bSucess = UpdateTransformType(InInput); + + bSucess &= UpdatePackBeforeMerge(InInput); + + bSucess &= UpdateTransformOffset(InInput); + + return bSucess; +} + +bool +FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + bool nTransformType = InInput->GetKeepWorldTransform(); + + // Geometry inputs are always set to none + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType == EHoudiniInputType::Geometry) + nTransformType = 0; + + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); + + bool bSuccess = true; + const std::string sXformType = "xformtype"; + if (InInput->IsObjectPathParameter()) + { + // Directly change the Parameter xformtype + // (This will only work if the object merge is editable/unlocked) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + HostAssetId, sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + else + { + // Query the object merge's node ID via the input + if (HAPI_RESULT_SUCCESS == FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + HostAssetId, InInput->GetInputIndex(), &InputNodeId)) + { + // Change its Parameter xformtype + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + InputNodeId, sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + } + + // Since our input objects are all plugged into a merge node + // We want to also update the transform type on the object merge plugged into the merge node + HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); + if ((ParentNodeId >= 0) && (InputType != EHoudiniInputType::Geometry) && (InputType != EHoudiniInputType::Asset)) + { + HAPI_NodeId InputObjectNodeId = -1; + int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); + for (int n = 0; n < NumberOfInputMeshes; n++) + { + // Get the Input node ID from the host ID + InputObjectNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ParentNodeId, n, &InputObjectNodeId)) + continue; + + if (InputObjectNodeId == -1) + continue; + + // Change the xformtype parameter on the object merge + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId, + sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + // Pack before merge is only available for Geo/World input + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType != EHoudiniInputType::World + && InputType != EHoudiniInputType::Geometry) + { + // Nothing to change + return true; + } + + uint32 nPackValue = InInput->GetPackBeforeMerge() ? 1 : 0; + + // Get the Input node ID from the host ID + HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); + + bool bSuccess = true; + const std::string sPack = "pack"; + + // We'll be going through each input object plugged in the input's merge node + // and change the pack parameter there + HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); + if (ParentNodeId >= 0) + { + HAPI_NodeId InputObjectNodeId = -1; + int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); + for (int n = 0; n < NumberOfInputMeshes; n++) + { + // Get the Input node ID from the host ID + InputObjectNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ParentNodeId, n, &InputObjectNodeId)) + continue; + + if (InputObjectNodeId == -1) + continue; + + // Change the pack parameter on the object merge + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId, + sPack.c_str(), 0, nPackValue)) + bSuccess = false; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + // Transform offsets are only for geometry inputs + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType != EHoudiniInputType::Geometry) + { + // Nothing to change + return true; + } + + // Get the input objects + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Update each object's transform offset + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + // If the Input mesh has a Transform offset + FTransform TransformOffset = CurrentInputObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CurrentInputObject->InputObjectNodeId, &HapiTransform), false); + } + } + + return true; +} + +bool +FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + EHoudiniInputType InputType = InInput->GetInputType(); + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Iterate on all the input objects and see if they need to be uploaded + bool bSuccess = true; + TArray CreatedNodeIds; + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; + if (!CurrentInputObject->HasChanged() && CurrentInputObjectNodeId >= 0) + { + // If this object hasn't changed, no need to upload it + // but we need to keep its created input node + if (CurrentInputObject->Type == EHoudiniInputObjectType::Actor) + { + // If this input object is an actor, it actually contains other input + // objects for each of his components, keep them as well + UHoudiniInputActor* InputActor = Cast(CurrentInputObject); + if (InputActor && !InputActor->IsPendingKill()) + { + for (auto CurrentComp : InputActor->ActorComponents) + { + if (!CurrentComp || CurrentComp->IsPendingKill()) + continue; + + int32& CurrentCompNodeId = CurrentComp->InputObjectNodeId; + if (!CurrentComp->HasChanged() && CurrentCompNodeId >= 0) + { + // If the component hasnt changed and is valid, keep it + CreatedNodeIds.Add(CurrentCompNodeId); + } + else + { + // Upload the component input object to Houdini + if (!UploadHoudiniInputObject(InInput, CurrentComp, CreatedNodeIds)) + bSuccess = false; + } + } + } + } + else + { + // No changes, keep it + CreatedNodeIds.Add(CurrentInputObjectNodeId); + } + } + else + { + // Upload the current input object to Houdini + if (!UploadHoudiniInputObject(InInput, CurrentInputObject, CreatedNodeIds)) + bSuccess = false; + } + } + + // If we haven't created any input, invalidate our input node id + if (CreatedNodeIds.Num() == 0) + { + if (!InInput->HasInputTypeChanged()) + { + int32 InputNodeId = InInput->GetInputNodeId(); + TArray PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); + + if (InInput->GetInputType() == EHoudiniInputType::Asset) + { + UHoudiniAssetComponent * OuterHAC = Cast(InInput->GetOuter()); + HAPI_NodeId AssetId = OuterHAC->GetAssetId(); + + // Disconnect the asset input + if (InputNodeId >= 0 && InInput->GetInputIndex() >= 0) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), AssetId, InInput->GetInputIndex())); + } + } + else if (InInput->GetInputType() == EHoudiniInputType::World) + { + // World nodes are handled by InputObjects () (with FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete) + } + else + { + if (InputNodeId >= 0) + { + for (int32 Idx = 0; Idx < PreviousInputObjectNodeIds.Num(); Idx++) + { + + // Get the object merge connected to the merge node + HAPI_NodeId InputObjectMergeId = -1; + HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); + + // Disconnect the two nodes + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); + + // Destroy the object merge node, do not delete other HDA (Asset input type) + HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); + } + } + } + } + InInput->GetCreatedDataNodeIds().Empty(); + InInput->SetInputNodeId(-1); + return bSuccess; + } + + // Get the current input's NodeId + HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); + // Check that the current input's node ID is still valid + if (InputNodeId < 0 || !FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + { + // This input doesn't have a valid NodeId yet, + // we need to create this input's merge node and update this input's node ID + FString MergeName = InInput->GetNodeBaseName() + TEXT("_Merge"); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/merge"), MergeName, true, &InputNodeId), false); + + InInput->SetInputNodeId(InputNodeId); + } + + //TODO: + // Do we want to update the input's transform? + if (false) + { + FTransform ComponentTransform = FTransform::Identity; + USceneComponent* OuterComp = Cast(InInput->GetOuter()); + if (OuterComp && !OuterComp->IsPendingKill()) + ComponentTransform = OuterComp->GetComponentTransform(); + + FHoudiniEngineUtils::HapiSetAssetTransform(InputNodeId, ComponentTransform); + //HapiUpdateInputNodeTransform(InputNodeId, ComponentTransform); + } + + // Connect all the input objects to the merge node now + int32 InputIndex = 0; + for (auto CurrentNodeId : CreatedNodeIds) + { + if (CurrentNodeId < 0) + continue; + + if (InputNodeId == CurrentNodeId) + continue; + + // Connect the current input object to the merge node + HOUDINI_CHECK_ERROR(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + InputNodeId, InputIndex++, CurrentNodeId, 0)); + } + + // Check if we need to disconnect extra input objects nodes from the merge + // This can be needed when the input had more input objects on the previous cook + TArray& PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); + if (!InInput->HasInputTypeChanged()) + { + for (int32 Idx = CreatedNodeIds.Num(); Idx < PreviousInputObjectNodeIds.Num(); Idx++) + { + // Get the object merge connected to the merge node + HAPI_NodeId InputObjectMergeId = -1; + if (InInput->GetInputType() != EHoudiniInputType::Asset) + HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); + + // Disconnect the two nodes + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); + + // Destroy the object merge node, do not destroy other HDA (Asset input type) + if (InInput->GetInputType() != EHoudiniInputType::Asset) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); + } + } + } + + // Keep track of all the nodes plugged into our input's merge + PreviousInputObjectNodeIds = CreatedNodeIds; + + // Finally, connect our main input node to the asset + bSuccess = ConnectInputNode(InInput); + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + EHoudiniInputType InputType = InInput->GetInputType(); + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Iterate on all the input objects and see if their transform needs to be uploaded + bool bSuccess = true; + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; + if (!CurrentInputObject->HasTransformChanged()) + continue; + + // Upload the current input object's transform to Houdini + if (!UploadHoudiniInputTransform(InInput, CurrentInputObject)) + { + bSuccess = false; + continue; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::ConnectInputNode(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + HAPI_NodeId AssetNodeId = InInput->GetAssetNodeId(); + if (AssetNodeId < 0) + return false; + + HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); + if (InputNodeId < 0) + return false; + + // Helper for connecting our input or setting the object path parameter + if (InInput->IsObjectPathParameter()) + { + // Now we can assign the input node path to the parameter + std::string ParamNameString = TCHAR_TO_UTF8(*(InInput->GetName())); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue( + FHoudiniEngine::Get().GetSession(), AssetNodeId, + ParamNameString.c_str(), InputNodeId), false); + } + else + { + // TODO: CHECK ME! + //if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + // return false; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), AssetNodeId, + InInput->GetInputIndex(), InputNodeId, 0), false); + } + + return true; +} + +bool +FHoudiniInputTranslator::UploadHoudiniInputObject( + UHoudiniInput* InInput, + UHoudiniInputObject* InInputObject, + TArray& OutCreatedNodeIds) +{ + if (!InInput || !InInputObject) + return false; + + FString ObjBaseName = InInput->GetNodeBaseName(); + + bool bSuccess = true; + switch (InInputObject->Type) + { + case EHoudiniInputObjectType::Object: + { + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForObject(ObjBaseName, InInputObject); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::StaticMesh: + { + UHoudiniInputStaticMesh* InputSM = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( + ObjBaseName, InputSM, InInput->GetExportLODs(), InInput->GetExportSockets(), + InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + { + // If this SM input object takes in a BP, add all its BP StaticMesh components input object node id to the created id list. + if (InputSM->bIsBlueprint()) + { + for (auto & CurSMObj : InputSM->BlueprintStaticMeshes) + OutCreatedNodeIds.Add(CurSMObj->InputObjectNodeId); + } + else + { + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + } + } + + break; + } + + case EHoudiniInputObjectType::SkeletalMesh: + { + UHoudiniInputSkeletalMesh* InputSkelMes = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(ObjBaseName, InputSkelMes); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::SceneComponent: + { + UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(ObjBaseName, InputSceneComp); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::StaticMeshComponent: + { + UHoudiniInputMeshComponent* InputSMC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( + ObjBaseName, InputSMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + { + UHoudiniInputInstancedMeshComponent* InputISMC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( + ObjBaseName, InputISMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::SplineComponent: + { + UHoudiniInputSplineComponent* InputSpline = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(ObjBaseName, InputSpline, InInput->GetUnrealSplineResolution()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::HoudiniSplineComponent: + { + UHoudiniInputHoudiniSplineComponent* InputCurve = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::HoudiniAssetActor: + case EHoudiniInputObjectType::HoudiniAssetComponent: + { + UHoudiniInputHoudiniAsset* InputHAC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Actor: + { + UHoudiniInputActor* InputActor = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, OutCreatedNodeIds); + break; + } + + case EHoudiniInputObjectType::Landscape: + { + UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForLandscape(ObjBaseName, InputLandscape, InInput); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Brush: + { + UHoudiniInputBrush* InputBrush = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForBrush(ObjBaseName, InputBrush, InInput->GetBoundSelectorObjectArray()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::CameraComponent: + { + UHoudiniInputCameraComponent* InputCamera = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForCamera(ObjBaseName, InputCamera); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::DataTable: + { + UHoudiniInputDataTable* InputDT = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(ObjBaseName, InputDT); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Invalid: + //default: + break; + } + + // Mark that input object as not changed + if (bSuccess) + { + InInputObject->MarkChanged(false); + InInputObject->SetNeedsToTriggerUpdate(false); + } + else + { + // We couldn't update/create that input object, keep it changed but prevent it from trigger updates + InInputObject->SetNeedsToTriggerUpdate(false); + } + + return bSuccess; +} + + +// Upload transform for an input's InputObject +bool +FHoudiniInputTranslator::UploadHoudiniInputTransform( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject) +{ + if (!InInput || !InInputObject) + return false; + + auto UpdateTransform = [](const FTransform& InTransform, const HAPI_NodeId& InNodeId) + { + // Translate the Transform to HAPI + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(InTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InNodeId, &HapiTransform), false); + + return true; + }; + + bool bSuccess = true; + switch (InInputObject->Type) + { + case EHoudiniInputObjectType::StaticMesh: + { + // Simply update the Input mesh's Transform offset + if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + break; + } + + case EHoudiniInputObjectType::StaticMeshComponent: + { + // Update using the static mesh component's transform + UHoudiniInputMeshComponent* InSMC = Cast(InInputObject); + if (!InSMC || InSMC->IsPendingKill()) + { + bSuccess = false; + break; + } + + FTransform NewTransform = InSMC->GetStaticMeshComponent() ? InSMC->GetStaticMeshComponent()->GetComponentTransform() : InInputObject->Transform; + if(!UpdateTransform(NewTransform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + // Update the InputObject's transform + InInputObject->Transform = NewTransform; + + break; + } + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + { + // TODO: Only update the instances transform + break; + } + + case EHoudiniInputObjectType::HoudiniSplineComponent: + { + // TODO: Simply update the curve's transform? + break; + } + + case EHoudiniInputObjectType::HoudiniAssetActor: + case EHoudiniInputObjectType::HoudiniAssetComponent: + { + // TODO: Check, nothing to do? + break; + } + + case EHoudiniInputObjectType::Actor: + { + UHoudiniInputActor* InputActor = Cast(InInputObject); + if (!InputActor || InputActor->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Update the actor's transform + // To avoid further updates + if (InputActor->GetActor()) + InputActor->Transform = InputActor->GetActor()->GetTransform(); + + // Iterate on all the actor input objects and see if their transform needs to be uploaded + // TODO? Also update the component's actor transform?? + for (auto& CurrentComponent : InputActor->ActorComponents) + { + if (!CurrentComponent || CurrentComponent->IsPendingKill()) + continue; + + if (!CurrentComponent->HasTransformChanged()) + continue; + + // Upload the current input object's transform to Houdini + if (!UploadHoudiniInputTransform(InInput, CurrentComponent)) + { + bSuccess = false; + continue; + } + } + break; + } + + case EHoudiniInputObjectType::SceneComponent: + { + UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); + if (!InputSceneComp || InputSceneComp->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Update the component transform to avoid further updates + if (InputSceneComp->GetSceneComponent()) + InputSceneComp->Transform = InputSceneComp->GetSceneComponent()->GetComponentTransform(); + + break; + } + + case EHoudiniInputObjectType::Landscape: + { + // + UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); + if (!InputLandscape || InputLandscape->IsPendingKill()) + { + bSuccess = false; + break; + } + + // + ALandscapeProxy* Landscape = InputLandscape->GetLandscapeProxy(); + if (!Landscape || Landscape->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Only apply diff for landscape since the HF's transform is used for value conversion as well + FTransform CurrentTransform = InputLandscape->Transform; + FTransform NewTransform = Landscape->ActorToWorld(); + + // Only handle position/rotation differences + FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); + FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); + + // Now get the HF's current transform + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) + { + bSuccess = false; + break; + } + + // Convert it to unreal + FTransform HFTransform; + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); + + // Apply the position offset if needed + if (!PosDiff.IsZero()) + HFTransform.AddToTranslation(PosDiff); + + // Apply the rotation offset if needed + if (!RotDiff.IsIdentity()) + HFTransform.ConcatenateRotation(RotDiff); + + // Convert back to a HAPI Transform and update the HF's transform + HAPI_TransformEuler NewHAPITransform; + FHoudiniApi::TransformEuler_Init(&NewHAPITransform); + FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); + NewHAPITransform.position[1] = 0.0f; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InputLandscape->InputObjectNodeId, &NewHAPITransform)) + { + bSuccess = false; + break; + } + + // Update the cached transform + InputLandscape->Transform = NewTransform; + } + + case EHoudiniInputObjectType::Brush: + { + // TODO: Update the Brush's transform + break; + } + + // Unsupported + case EHoudiniInputObjectType::Object: + case EHoudiniInputObjectType::SkeletalMesh: + case EHoudiniInputObjectType::SplineComponent: + { + break; + } + + case EHoudiniInputObjectType::Invalid: + default: + break; + } + + // Mark that input object as not changed + if (bSuccess) + { + InInputObject->MarkTransformChanged(false); + InInputObject->SetNeedsToTriggerUpdate(false); + } + else + { + // We couldn't update/create that input object, keep it changed but prevent it from trigger updates + InInputObject->SetNeedsToTriggerUpdate(false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForObject(const FString& InObjNodeName, UHoudiniInputObject* InObject) +{ + if (!InObject) + return false; + + UObject* Object = InObject->GetObject(); + if (!Object || Object->IsPendingKill()) + return true; + + FString NodeName = InObjNodeName + TEXT("_") + Object->GetName(); + + // For UObjects we can't upload much, but can still create an input node + // with a single point, with an attribute pointing to the input object's path + HAPI_NodeId InputNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); + + // Update this input object's NodeId and ObjectNodeId + InObject->InputNodeId = (int32)InputNodeId; + InObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 2; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = 1; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); + + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + FVector ObjectPosition = InObject->Transform.GetLocation(); + TArray Position = + { + ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION + }; + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Position.GetData(), 0, + AttributeInfoPoint.count), false); + } + + { + // Create point attribute info for the path. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); + + // Set the point's path attribute + FString ObjectPathName = Object->GetPathName(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + ObjectPathName, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputNodeId), false); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( + const FString& InObjNodeName, + UHoudiniInputStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UBlueprint* BP = nullptr; + UStaticMesh* SM = nullptr; + + FString SMName = InObjNodeName + TEXT("_"); + + // Get Blueprint or StaticMesh + if (InObject->bIsBlueprint()) + { + BP = InObject->GetBlueprint(); + if (!BP || BP->IsPendingKill()) + return true; + + SMName += BP->GetName(); + } + else + { + SM = InObject->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + SMName += SM->GetName(); + } + + // Marshall the Static Mesh to Houdini + bool bSuccess = true; + + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference; + if (SM) + AssetReference += SM->GetFullName(); + + if (BP) + AssetReference += BP->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference( + InObject->InputNodeId, AssetReference, SMName, InObject->Transform); + } + else + { + TArray StaticMeshComponents; + + // The input object is a Blueprint, Get all its StaticMeshes + if (BP) + { + USimpleConstructionScript* SCS = BP->SimpleConstructionScript; + if (SCS && !SCS->IsPendingKill()) + { + const TArray& Nodes = SCS->GetAllNodes(); + for (auto & CurNode : Nodes) + { + if (!CurNode || CurNode->IsPendingKill()) + continue; + + UActorComponent * CurComp = CurNode->ComponentTemplate; + if (!CurComp || CurComp->IsPendingKill()) + continue; + + UStaticMeshComponent* CurSMC = Cast(CurComp); + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + UStaticMesh* CurSM = CurSMC->GetStaticMesh(); + if (CurSM && !CurSM->IsPendingKill()) + StaticMeshComponents.Add(CurSMC); + + } + } + } + + // Clear previous Blueprint Static Mesh Comps (if there is any) + InObject->BlueprintStaticMeshes.Empty(); + + // This is a BP, add all the BP SM comps to its BlueprintStaticMeshes list. + if (InObject->bIsBlueprint()) + { + for (auto & CurSMC : StaticMeshComponents) + { + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + UHoudiniInputStaticMesh* SMObject = Cast( + UHoudiniInputObject::CreateTypedInputObject(CurSMC->GetStaticMesh(), InObject, InObject->GetName() + TEXT("_") + CurSMC->GetName())); + + if (!SMObject || SMObject->IsPendingKill()) + continue; + + bSuccess &= FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + CurSMC->GetStaticMesh(), SMObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); + + InObject->SetImportAsReference(false); + + // Update this input object's OBJ NodeId + SMObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(SMObject->InputNodeId); + + // Update the component's transform + FTransform ComponentTransform = CurSMC->GetRelativeTransform(); + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), SMObject->InputObjectNodeId, &HapiTransform), false); + } + + InObject->BlueprintStaticMeshes.Add(SMObject); + } + + return true; + } + // This is a normal static mesh input, process it normally as a static mesh Input Object + else + { + bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, InObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); + } + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // If the Input mesh has a Transform offset + FTransform TransformOffset = InObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USkeletalMesh* SkelMesh = InObject->GetSkeletalMesh(); + if (!SkelMesh || SkelMesh->IsPendingKill()) + return true; + + // Get the SM's transform offset + FTransform TransformOffset = InObject->Transform; + + // TODO + // Support this type of input object + // FUnrealMeshTranslator::CreateInputNodeForSkeletalMesh(...) + + return HapiCreateInputNodeForObject(InObjNodeName, InObject); +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USceneComponent* SceneComp = InObject->GetSceneComponent(); + if (!SceneComp || SceneComp->IsPendingKill()) + return true; + + // Get the Scene Component's transform + FTransform TransformOffset = InObject->Transform; + + // Get the parent Actor's transform + FTransform ParentTransform = InObject->ActorTransform; + + // Dont do that! + return false; + + // TODO + // Support this type of input object + return HapiCreateInputNodeForObject(InObjNodeName, InObject); +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UStaticMeshComponent* SMC = InObject->GetStaticMeshComponent(); + if (!SMC || SMC->IsPendingKill()) + return true; + + // Get the component's Static Mesh + UStaticMesh* SM = InObject->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + // Marshall the Static Mesh to Houdini + FString SMCName = InObjNodeName + TEXT("_") + SMC->GetName(); + + bool bSuccess = true; + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference = SM->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, SMCName, AssetReference, InObject->Transform); + + } + else + { + bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, InObject->InputNodeId, SMCName, SMC, bExportLODs, bExportSockets, bExportColliders); + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // Update this input object's cache data + InObject->Update(SMC); + + // Update the component's transform + FTransform ComponentTransform = InObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputInstancedMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UObject* Object = InObject->GetObject(); + if (!Object || Object->IsPendingKill()) + return true; + + // Get the ISMC + UInstancedStaticMeshComponent* ISMC = InObject->GetInstancedStaticMeshComponent(); + if (!ISMC || ISMC->IsPendingKill()) + return true; + + HAPI_NodeId NewNodeId = -1; + if (!FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( + ISMC, InObjNodeName, NewNodeId, bExportLODs, bExportSockets, bExportColliders, false)) + return false; + + // Update this input object's node IDs + InObject->InputNodeId = NewNodeId; + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); + + // Update the component's cached instances + InObject->Update(ISMC); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USplineComponent* Spline = InObject->GetSplineComponent(); + if (!Spline || Spline->IsPendingKill()) + return true; + + + int32 NumberOfSplineControlPoints = InObject->NumberOfSplineControlPoints; + + TArray SplineControlPoints = InObject->SplineControlPoints; + + FString NodeName = InObjNodeName + TEXT("_") + InObject->GetName(); + + if (!FUnrealSplineTranslator::CreateInputNodeForSplineComponent(Spline, SplineResolution, InObject->InputNodeId, NodeName)) + return false; + + // Cache the exported curve's data to the input object + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + InObject->MarkChanged(true); + + //if (!HapiCreateInputNodeForObject(InObjNodeName, InObject)) + // return false; + + // Update the component's cached data + InObject->Update(Spline); + + // Update the component's transform + FTransform ComponentTransform = InObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UHoudiniSplineComponent* Curve = InObject->GetCurveComponent(); + if (!Curve || Curve->IsPendingKill()) + return true; + + if (!FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent(InObjNodeName, Curve)) + return false; + + // See if the component needs it node Id invalidated + //if (InObject->InputNodeId < 0) + // Curve->SetNodeId(InObject->InputNodeId); + + // Cache the exported curve's data to the input object + InObject->InputNodeId = Curve->GetNodeId(); + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + //InObject->CurveType = Curve->GetCurveType(); + //InObject->CurveMethod = Curve->GetCurveMethod(); + //InObject->Reversed = Curve->IsReversed(); + InObject->Update(Curve); + + InObject->MarkChanged(true); + + return true; +} + +bool +FHoudiniInputTranslator:: +HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UHoudiniAssetComponent* InputHAC = InObject->GetHoudiniAssetComponent(); + if (!InputHAC || InputHAC->IsPendingKill()) + return true; + + if (!InputHAC->CanDeleteHoudiniNodes()) + return true; + + UHoudiniInput* HoudiniInput = Cast(InObject->GetOuter()); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + return true; + + UHoudiniAssetComponent* OuterHAC = Cast(HoudiniInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return true; + + // Do not allow using ourself as an input, terrible things would happen + if (InputHAC->GetAssetId() == OuterHAC->GetAssetId()) + return false; + + // If previously imported as ref, delete the input node. + if (InObject->InputNodeId > -1 && InObject->GetImportAsReference()) + { + int32 PreviousInputNodeId = InObject->InputNodeId; + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InObject->GetName()); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InObject->GetName()); + } + } + + InObject->SetImportAsReference(bImportAsReference); + + // If this object is in an Asset input, we need to set the InputNodeId directl + // to avoid creating extra merge nodes. World inputs should not do that! + bool bIsAssetInput = HoudiniInput->GetInputType() == EHoudiniInputType::Asset; + + if (bImportAsReference) + { + InObject->InputNodeId = -1; + InObject->InputObjectNodeId = -1; + + if(bIsAssetInput) + HoudiniInput->SetInputNodeId(-1); + + // Start by getting the Object's full name + FString AssetReference = InputHAC->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + if (!FHoudiniInputTranslator::CreateInputNodeForReference( + InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform)) // do not delete previous node if it was HAC + return false; + + if (bIsAssetInput) + HoudiniInput->SetInputNodeId(InObject->InputNodeId); + } + + InputHAC->AddDownstreamHoudiniAsset(OuterHAC); + + //if (HAC->NeedsInitialization()) + // HAC->MarkAsNeedInstantiation(); + + //HoudiniInput->SetAssetNodeId(HAC->GetAssetId()); + + // TODO: This might be uneeded as this function should only be called + // after we're not wiating on the input asset... + if (InputHAC->AssetState == EHoudiniAssetState::NeedInstantiation) + { + // If the input HAC needs to be instantiated, tell it do so + InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; + // Mark this object's input as changed so we can properly update after the input HDA's done instantiating/cooking + HoudiniInput->MarkChanged(true); + } + + if (InputHAC->NeedsInitialization() || InputHAC->NeedUpdate()) + return false; + + if (!bImportAsReference) + { + if (bIsAssetInput) + HoudiniInput->SetInputNodeId(InputHAC->GetAssetId()); + + InObject->InputNodeId = InputHAC->GetAssetId(); + } + + InObject->InputObjectNodeId = InObject->InputNodeId; + + bool bReturn = InObject->InputNodeId > -1; + + if(bIsAssetInput) + bReturn = FHoudiniInputTranslator::ConnectInputNode(HoudiniInput); + + return bReturn; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForActor( + UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (!InObject || InObject->IsPendingKill()) + return false; + + AActor* Actor = InObject->GetActor(); + if (!Actor || Actor->IsPendingKill()) + return true; + + // Check if this is a world input and if this is a HoudiniAssetActor + // If so we need to build static meshes for any proxy meshes + if (InInput->GetInputType() == EHoudiniInputType::World && Actor->IsA()) + { + AHoudiniAssetActor *HAA = Cast(Actor); + UHoudiniAssetComponent *HAC = HAA->GetHoudiniAssetComponent(); + if (HAC && !HAC->IsPendingKill()) + { + if (HAC->HasAnyCurrentProxyOutput()) + { + bool bPendingDeleteOrRebuild = false; + bool bInvalidState = false; + const bool bIsHoudiniCookedDataAvailable = HAC->IsHoudiniCookedDataAvailable(bPendingDeleteOrRebuild, bInvalidState); + if (bIsHoudiniCookedDataAvailable) + { + // Build the static mesh + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); + // Update the input object since a new StaticMeshComponent could have been created + UObject *InputObject = InObject->GetObject(); + if (InputObject && !InputObject->IsPendingKill()) + { + InObject->Update(InputObject); + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } + } + else if (!bPendingDeleteOrRebuild && !bInvalidState) + { + // Request a cook with no proxy output + HAC->MarkAsNeedCook(); + HAC->SetNoProxyMeshNextCookRequested(true); + } + } + else if (InObject->ActorComponents.Num() == 0 && HAC->HasAnyOutputComponent()) + { + // The HAC has non-proxy output components, but the InObject does not have any + // actor components. This can arise after a cook if previously there were only + // proxies and the input was created when there were only proxies + // Try to update the input to find new components + UObject *InputObject = InObject->GetObject(); + if (InputObject && !InputObject->IsPendingKill()) + { + InObject->Update(InputObject); + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } + } + } + } + + // Now, commit all of this actor's component + int32 ComponentIdx = 0; + for (UHoudiniInputSceneComponent* CurComponent : InObject->ActorComponents) + { + if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) + ComponentIdx++; + } + + // TODO: We should call Update here... + // needs to be fixed + + // Cache our transformn + InObject->Transform = Actor->GetTransform(); + + // Do something for our actor's transform? + /* + // TODO + // Support this type of input object + FString ObjNodeName = InInput->GetNodeBaseName(); + return HapiCreateInputNodeForObject(ObjNodeName, InObject); + */ + + //TODO? Check + // return true if we have at least uploaded one component + // return (ComponentIdx > 0); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( + const FString& InObjNodeName, UHoudiniInputLandscape* InObject, UHoudiniInput* InInput) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + if (!InInput || InInput->IsPendingKill()) + return false; + + ALandscapeProxy* Landscape = InObject->GetLandscapeProxy(); + if (!Landscape || Landscape->IsPendingKill()) + return true; + + EHoudiniLandscapeExportType ExportType = InInput->GetLandscapeExportType(); + + bool bSucess = false; + if (ExportType == EHoudiniLandscapeExportType::Heightfield) + { + bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); + } + else + { + bool bExportLighting = InInput->bLandscapeExportLighting; + bool bExportMaterials = InInput->bLandscapeExportMaterials; + bool bExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; + bool bExportTileUVs = InInput->bLandscapeExportTileUVs; + bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; + bool bExportAsMesh = InInput->LandscapeExportType == EHoudiniLandscapeExportType::Mesh; + + bSucess = FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( + Landscape, InObject->InputNodeId, InObjNodeName, + bExportAsMesh, bExportTileUVs, bExportNormalizedUVs, bExportLighting, bExportMaterials); + } + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + InObject->Update(Landscape); + + return bSucess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForBrush(const FString& InObjNodeName, UHoudiniInputBrush* InObject, TArray* ExcludeActors) +{ + if (!IsValid(InObject)) + return false; + + ABrush* BrushActor = InObject->GetBrush(); + if (!IsValid(BrushActor)) + return true; + + if (!FUnrealBrushTranslator::CreateInputNodeForBrush(InObject, BrushActor, ExcludeActors, InObject->InputNodeId, InObjNodeName)) + return false; + + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + InObject->Update(BrushActor); + + return true; +} + + + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, UHoudiniInputCameraComponent* InInputObject) +{ + if (!InInputObject || InInputObject->IsPendingKill()) + return false; + + UCameraComponent* Camera = InInputObject->GetCameraComponent(); + if (!Camera || Camera->IsPendingKill()) + return true; + + FString NodeName = InNodeName + TEXT("_") + Camera->GetName(); + + // Create the camera OBJ. + int32 CameraNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( + -1, TEXT("Object/cam"), InNodeName, true, &CameraNodeId), false); + + // set "Pixel Aspect Ratio" (aspect) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "aspect", 0, InInputObject->AspectRatio), false); + + // set "Projection" (projection) (0 persp, 1 ortho) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "projection", 0, InInputObject->bIsOrthographic ? 1 : 0), false); + + // set Ortho Width (orthowidth) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "orthowidth", 0, InInputObject->OrthoWidth), false); + + // set Near Clippin (near) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "near", 0, InInputObject->OrthoNearClipPlane), false); + + // set far clipping (far) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "far", 0, InInputObject->OrthoFarClipPlane), false); + + // Set the transform + HAPI_TransformEuler H_Transform; + FHoudiniApi::TransformEuler_Init(&H_Transform); + FHoudiniEngineUtils::TranslateUnrealTransform(Camera->GetComponentTransform(), H_Transform); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CameraNodeId, &H_Transform), false); + + // Update the component's transform + FTransform ComponentTransform = InInputObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Camera orientation need to be adjusted + HapiTransform.rotationEuler[1] += -90.0f; + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CameraNodeId, &HapiTransform), false); + } + + // Update this input's NodeId and ObjectNodeId + InInputObject->InputNodeId = -1;// (int32)CameraNodeId; + InInputObject->InputObjectNodeId = (int32)CameraNodeId; + + // Update this input object's cache data + InInputObject->Update(Camera); + + return true; +} + +bool +FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // We need to call BuildAllInputs here to update all the inputs, + // and make sure that the object path parameter inputs' parameter ids are up to date + if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) + return false; + + // We need to update the AssetID stored on all the inputs + // and mark all the input objects for this input type as changed + int32 HACAssetId = HAC->GetAssetId(); + for (auto CurrentInput : HAC->Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // + CurrentInput->SetAssetNodeId(HACAssetId); + + // We need to delete the nodes created for the input objects if they are valid + // (since the node IDs are transients, this likely means we're handling a recook/rebuild + // and therefore expect to recreate the input nodes) + DestroyInputNodes(CurrentInput, CurrentInput->GetInputType()); + } + + return true; +} + + + +bool +FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Only tick/cook when in Editor + // This prevents PIE cooks or runtime cooks due to inputs moving + AActor* ActorOwner = HAC->GetOwner(); + if (ActorOwner) + { + if (!ActorOwner->GetWorld() || (ActorOwner->GetWorld()->WorldType != EWorldType::Editor)) + return false; + } + +#if WITH_EDITOR + // Stop outliner objects from causing recooks while input objects are dragged around + if (FHoudiniMoveTracker::Get().IsObjectMoving) + { + //HOUDINI_LOG_MESSAGE(TEXT("Object moving, not updating world inputs!")); + return false; + } +#endif + + for (auto CurrentInput : HAC->Inputs) + { + if (!CurrentInput) + continue; + if (CurrentInput->GetInputType() != EHoudiniInputType::World) + continue; + + UpdateWorldInput(CurrentInput); + } + + return true; +} + +bool +FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (InInput->GetInputType() != EHoudiniInputType::World) + return false; + + TArray* InputObjectsPtr = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjectsPtr) + return false; + + bool bHasChanged = false; + if (InInput->IsWorldInputBoundSelector() && InInput->GetWorldInputBoundSelectorAutoUpdates()) + { + // If the input is in bound selector mode, and auto-update is enabled + // update the actors selected by the bounds first + bHasChanged = InInput->UpdateWorldSelectionFromBoundSelectors(); + } + + // See if we need to update the components for this input + // look for deleted actors/components + TArray ObjectToDeleteIndices; + for(int32 InputObjIdx = 0; InputObjIdx < InputObjectsPtr->Num(); InputObjIdx++) + { + UHoudiniInputActor* ActorObject = Cast((*InputObjectsPtr)[InputObjIdx]); + if (!ActorObject || ActorObject->IsPendingKill()) + continue; + + // Make sure the actor is still valid + bool bValidActorObject = ActorObject->GetActor() && !ActorObject->GetActor()->IsPendingKill(); + + // For BrushActors, the brush and actors must be valid as well + UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); + if (bValidActorObject && BrushActorObject) + { + ABrush* BrushActor = BrushActorObject->GetBrush(); + if (!IsValid(BrushActor)) + bValidActorObject = false; + else if (!IsValid(BrushActor->Brush)) + bValidActorObject = false; + } + + // The actor is no longer valid, mark it for deletion + if (!bValidActorObject) + { + if ((ActorObject->InputNodeId > 0) || (ActorObject->InputObjectNodeId > 0)) + { + ActorObject->InvalidateData(); + // We only need to update the input if the actors nodes were created in Houdini + bHasChanged = true; + } + + // Delete the Actor object + ObjectToDeleteIndices.Add(InputObjIdx); + continue; + } + + if (ActorObject->HasActorTransformChanged()) + { + ActorObject->MarkTransformChanged(true); + bHasChanged = true; + } + + if (ActorObject->HasContentChanged()) + { + ActorObject->MarkChanged(true); + bHasChanged = true; + } + + // Iterates on all of the actor's component + TArray ComponentToDeleteIndices; + for (int32 CompIdx = 0; CompIdx < ActorObject->ActorComponents.Num(); CompIdx++) + { + UHoudiniInputSceneComponent* CurActorComp = ActorObject->ActorComponents[CompIdx]; + if (!CurActorComp || CurActorComp->IsPendingKill()) + continue; + + // Make sure the actor is still valid + if (!CurActorComp->InputObject || CurActorComp->InputObject->IsPendingKill()) + { + // If it's not, mark it for deletion + if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) + { + CurActorComp->InvalidateData(); + + // We only need to update the input if the object were created in Houdini + bHasChanged = true; + } + + // Delete the component object + ComponentToDeleteIndices.Add(CompIdx); + + continue; + } + + if (CurActorComp->HasComponentTransformChanged()) + { + CurActorComp->MarkTransformChanged(true); + bHasChanged = true; + } + + if (CurActorComp->HasComponentChanged()) + { + CurActorComp->MarkChanged(true); + bHasChanged = true; + } + } + + // Delete the components objects on the actor that were marked for deletion + for (int32 ToDeleteIdx = ComponentToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) + ActorObject->ActorComponents.RemoveAt(ComponentToDeleteIndices[ToDeleteIdx]); + } + + // Delete the actor objects that were marked for deletion + for (int32 ToDeleteIdx = ObjectToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) + InputObjectsPtr->RemoveAt(ObjectToDeleteIndices[ToDeleteIdx]); + + // Mark the input as changed if need so it will trigger an upload + if (bHasChanged) + InInput->MarkChanged(true); + + return true; +} + + +bool +FHoudiniInputTranslator::CreateInputNodeForReference( + HAPI_NodeId& InputNodeId, + const FString & InRef, + const FString & InputNodeName, + const FTransform& InTransform) +{ + HAPI_NodeId NewNodeId = -1; + + // Create a single input node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_UTF8(*InputNodeName)), false); + + /* + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, nullptr), false); + */ + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) + return false; + + // We have now created a valid new input node, delete the previous one + HAPI_NodeId PreviousInputNodeId = InputNodeId; + if (PreviousInputNodeId >= 0) + { + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); + } + } + + // Create and initialize a part containing one point with a point attribute + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + + PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = 1; + PartInfo.vertexCount = 0; + PartInfo.faceCount = 0; + PartInfo.pointCount = 1; + PartInfo.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, &PartInfo), false); + + // Point Position Attribute + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + FVector ObjectPosition = InTransform.GetLocation(); + TArray Position = + { + ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION + }; + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Position.GetData(), 0, + AttributeInfoPoint.count), false); + } + + // String Attribute + { + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint), false); + + // Set string attribute + std::string AttriString = TCHAR_TO_ANSI(*InRef); + const char* AttriStringRaw = AttriString.c_str(); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint, + &AttriStringRaw, 0, 1), false); + } + + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NewNodeId), false); + + InputNodeId = NewNodeId; + return true; +} + + + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeName, UHoudiniInputDataTable* InInputObject) +{ + //TODO + if (!InInputObject || InInputObject->IsPendingKill()) + return false; + + UDataTable* DataTable = InInputObject->GetDataTable(); + if (!DataTable || DataTable->IsPendingKill()) + return true; + + // Get the DataTable data as string + TArray> TableData = DataTable->GetTableData(EDataTableExportFlags::None); + if (TableData.Num() <= 1) + return true; + + int32 NumRows = TableData.Num() - 1; + int32 NumAttributes = TableData[0].Num(); + if (NumRows <= 0 || NumAttributes <= 0) + return true; + + // Create the input node + FString NodeName = InNodeName + TEXT("_") + DataTable->GetName(); + HAPI_NodeId InputNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); + + // Update this input object's NodeId and ObjectNodeId + InInputObject->InputNodeId = (int32)InputNodeId; + InInputObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = NumAttributes; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = NumRows; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); + + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + TArray Positions; + Positions.SetNum(NumRows * 3); + for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) + { + Positions[RowIdx * 3] = 0.0f; + Positions[RowIdx * 3 + 1] = (float)RowIdx; + Positions[RowIdx * 3 + 2] = 0.0f; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Positions.GetData(), 0, + AttributeInfoPoint.count), false); + } + + { + // Create point attribute info for the path. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); + + // Get the object path + FString ObjectPathName = DataTable->GetPathName(); + + // Create an array + TArray ObjectPaths; + ObjectPaths.Init(ObjectPathName, NumRows); + + // Set the point's path attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + ObjectPaths, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); + } + + { + // Create point attribute info for data table RowTable class name + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, &AttributeInfoPoint), false); + + // Get the object path + FString RowStructName = DataTable->GetRowStructName().ToString(); + + // Create an array + TArray RowStructNames; + RowStructNames.Init(RowStructName, NumRows); + + // Set the point's path attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + RowStructNames, InputNodeId, 0, + HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, AttributeInfoPoint), false); + } + + // Now set the attributes values for each "point" of the data table + for (int32 ColIdx = 0; ColIdx < NumAttributes; ColIdx++) + { + // attribute name is "unreal_data_table_COL_NAME" + FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + TableData[0][ColIdx]; + + // We need to gt all values for that attribute + TArray AttributeValues; + AttributeValues.SetNum(NumRows); + for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) + { + AttributeValues[RowIdx] = TableData[RowIdx + 1][ColIdx]; + } + + // Create a point attribute info + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = NumRows; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_POINT; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + TCHAR_TO_ANSI(*CurAttrName), &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + AttributeValues, InputNodeId, 0, + CurAttrName, AttributeInfo), false); + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputNodeId), false); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr), false); + + return true; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h index 1598c6b6f..b877f0fe9 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h @@ -1,202 +1,204 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -class AActor; - -class UHoudiniInput; -class UHoudiniParameter; -class UHoudiniAssetComponent; - -class UHoudiniInputObject; -class UHoudiniInputStaticMesh; -class UHoudiniInputSkeletalMesh; -class UHoudiniInputSceneComponent; -class UHoudiniInputMeshComponent; -class UHoudiniInputInstancedMeshComponent; -class UHoudiniInputSplineComponent; -class UHoudiniInputHoudiniSplineComponent; -class UHoudiniInputHoudiniAsset; -class UHoudiniInputActor; -class UHoudiniInputLandscape; -class UHoudiniInputBrush; -class UHoudiniSplineComponent; -class UHoudiniInputCameraComponent; -class UHoudiniInputDataTable; - -class AActor; - -enum class EHoudiniInputType : uint8; -enum class EHoudiniLandscapeExportType : uint8; - -struct HOUDINIENGINE_API FHoudiniInputTranslator -{ - // - static bool UpdateInputs(UHoudiniAssetComponent* HAC); - - // Update inputs from the asset - // @AssetId: NodeId of the digital asset - // @OuterObject: Object to use for transactions and as Outer for new inputs - // @CurrentInputs: pre: current & post: invalid inputs - // @NewParameters: pre: empty & post: new inputs - // On Return: CurrentInputs are the old inputs that are no longer valid, - // NewInputs are new and re-used inputs. - static bool BuildAllInputs( - const HAPI_NodeId& AssetId, - class UObject* OuterObject, - TArray& Inputs, - TArray& Parameters); - - // Update loaded inputs and their input objects so they can be uploaded properly - static bool UpdateLoadedInputs(UHoudiniAssetComponent * HAC); - - // Update all the inputs that have been marked as change - static bool UploadChangedInputs(UHoudiniAssetComponent * HAC); - - // Only update simple input properties - static bool UpdateInputProperties(UHoudiniInput* InInput); - - // Update the KeepWorldTransform / Object merge transform type property - static bool UpdateTransformType(UHoudiniInput* InInput); - - // Update the pack before merge parameter for World/Geometry inputs - static bool UpdatePackBeforeMerge(UHoudiniInput* InInput); - - // Update the transform offset for geometry inputs - static bool UpdateTransformOffset(UHoudiniInput* InInput); - - // Upload all the input's data to Houdini - static bool UploadInputData(UHoudiniInput* InInput); - - // Upload all the input's transforms to Houdini - static bool UploadInputTransform(UHoudiniInput* InInput); - - // Upload data for an input's InputObject - static bool UploadHoudiniInputObject( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, TArray& OutCreatedNodeIds); - - // Upload transform for an input's InputObject - static bool UploadHoudiniInputTransform( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject); - - // Updates/ticks world inputs in the given HAC - static bool UpdateWorldInputs(UHoudiniAssetComponent* HAC); - - // Updates/ticks the given world input - static bool UpdateWorldInput(UHoudiniInput* InInput); - - // Connect an input's nodes to its linked HDA node - static bool ConnectInputNode(UHoudiniInput* InInput); - - // Destroys an input - static bool DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - static bool DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - static bool DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - - static EHoudiniInputType GetDefaultInputTypeFromLabel(const FString& InputName); - - static bool SetDefaultAssetFromHDA(UHoudiniInput* Input); - - static bool ChangeInputType(UHoudiniInput* Input, const bool& bForce); - - static bool HapiCreateInputNodeForObject( - const FString& InObjNodeName, UHoudiniInputObject* InObject); - - static bool HapiCreateInputNodeForStaticMesh( - const FString& InObjNodeName, - UHoudiniInputStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference = false); - - static bool HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, - UHoudiniInputHoudiniSplineComponent* InObject); - - static bool HapiCreateInputNodeForLandscape( - const FString& InObjNodeName, - UHoudiniInputLandscape* InObject, - UHoudiniInput* InInput); - - static bool HapiCreateInputNodeForSkeletalMesh( - const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject); - - static bool HapiCreateInputNodeForSceneComponent( - const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject); - - static bool HapiCreateInputNodeForStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference); - - static bool HapiCreateInputNodeForInstancedStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputInstancedMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders); - - static bool HapiCreateInputNodeForSplineComponent( - const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution); - - static bool HapiCreateInputNodeForHoudiniAssetComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference); - - static bool HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds); - - static bool HapiCreateInputNodeForCamera( - const FString& InObjNodeName, UHoudiniInputCameraComponent* InObject); - - // Create input node for Brush. Optionally exclude actors when combining - // brush with other intersecting brushes. This is typically used to - // exclude Selector objects. - static bool HapiCreateInputNodeForBrush( - const FString& InObjNodeName, - UHoudiniInputBrush* InObject, - TArray* ExcludeActors - ); - - static bool HapiCreateInputNodeForDataTable( - const FString& InNodeName, UHoudiniInputDataTable* InInputObject); - - // HAPI: Create an input node for reference - static bool CreateInputNodeForReference( - HAPI_NodeId& InputNodeId, - const FString & InRef, - const FString & InputNodeName, - const FTransform & InTransform); - - //static bool HapiUpdateInputNodeTransform(const HAPI_NodeId InputNodeId, const FTransform& Transform); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +class AActor; + +class UHoudiniInput; +class UHoudiniParameter; +class UHoudiniAssetComponent; + +class UHoudiniInputObject; +class UHoudiniInputStaticMesh; +class UHoudiniInputSkeletalMesh; +class UHoudiniInputSceneComponent; +class UHoudiniInputMeshComponent; +class UHoudiniInputInstancedMeshComponent; +class UHoudiniInputSplineComponent; +class UHoudiniInputHoudiniSplineComponent; +class UHoudiniInputHoudiniAsset; +class UHoudiniInputActor; +class UHoudiniInputLandscape; +class UHoudiniInputBrush; +class UHoudiniSplineComponent; +class UHoudiniInputCameraComponent; +class UHoudiniInputDataTable; + +class AActor; + +enum class EHoudiniInputType : uint8; +enum class EHoudiniLandscapeExportType : uint8; + +struct HOUDINIENGINE_API FHoudiniInputTranslator +{ + // + static bool UpdateInputs(UHoudiniAssetComponent* HAC); + + // Update inputs from the asset + // @AssetId: NodeId of the digital asset + // @OuterObject: Object to use for transactions and as Outer for new inputs + // @CurrentInputs: pre: current & post: invalid inputs + // @NewParameters: pre: empty & post: new inputs + // On Return: CurrentInputs are the old inputs that are no longer valid, + // NewInputs are new and re-used inputs. + static bool BuildAllInputs( + const HAPI_NodeId& AssetId, + class UObject* OuterObject, + TArray& Inputs, + TArray& Parameters); + + // Update loaded inputs and their input objects so they can be uploaded properly + static bool UpdateLoadedInputs(UHoudiniAssetComponent * HAC); + + // Update all the inputs that have been marked as change + static bool UploadChangedInputs(UHoudiniAssetComponent * HAC); + + // Only update simple input properties + static bool UpdateInputProperties(UHoudiniInput* InInput); + + // Update the KeepWorldTransform / Object merge transform type property + static bool UpdateTransformType(UHoudiniInput* InInput); + + // Update the pack before merge parameter for World/Geometry inputs + static bool UpdatePackBeforeMerge(UHoudiniInput* InInput); + + // Update the transform offset for geometry inputs + static bool UpdateTransformOffset(UHoudiniInput* InInput); + + // Upload all the input's data to Houdini + static bool UploadInputData(UHoudiniInput* InInput); + + // Upload all the input's transforms to Houdini + static bool UploadInputTransform(UHoudiniInput* InInput); + + // Upload data for an input's InputObject + static bool UploadHoudiniInputObject( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, TArray& OutCreatedNodeIds); + + // Upload transform for an input's InputObject + static bool UploadHoudiniInputTransform( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject); + + // Updates/ticks world inputs in the given HAC + static bool UpdateWorldInputs(UHoudiniAssetComponent* HAC); + + // Updates/ticks the given world input + static bool UpdateWorldInput(UHoudiniInput* InInput); + + // Connect an input's nodes to its linked HDA node + static bool ConnectInputNode(UHoudiniInput* InInput); + + // Destroys an input + static bool DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + static bool DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + static bool DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + + static EHoudiniInputType GetDefaultInputTypeFromLabel(const FString& InputName); + + static bool SetDefaultAssetFromHDA(UHoudiniInput* Input); + + static bool ChangeInputType(UHoudiniInput* Input, const bool& bForce); + + static bool HapiCreateInputNodeForObject( + const FString& InObjNodeName, UHoudiniInputObject* InObject); + + static bool HapiCreateInputNodeForStaticMesh( + const FString& InObjNodeName, + UHoudiniInputStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference = false); + + static bool HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, + UHoudiniInputHoudiniSplineComponent* InObject); + + static bool HapiCreateInputNodeForLandscape( + const FString& InObjNodeName, + UHoudiniInputLandscape* InObject, + UHoudiniInput* InInput); + + static bool HapiCreateInputNodeForSkeletalMesh( + const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject); + + static bool HapiCreateInputNodeForSceneComponent( + const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject); + + static bool HapiCreateInputNodeForStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference); + + static bool HapiCreateInputNodeForInstancedStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputInstancedMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders); + + static bool HapiCreateInputNodeForSplineComponent( + const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution); + + static bool HapiCreateInputNodeForHoudiniAssetComponent( + const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference); + + static bool HapiCreateInputNodeForActor( + UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds); + + static bool HapiCreateInputNodeForCamera( + const FString& InObjNodeName, UHoudiniInputCameraComponent* InObject); + + // Create input node for Brush. Optionally exclude actors when combining + // brush with other intersecting brushes. This is typically used to + // exclude Selector objects. + static bool HapiCreateInputNodeForBrush( + const FString& InObjNodeName, + UHoudiniInputBrush* InObject, + TArray* ExcludeActors + ); + + static bool HapiCreateInputNodeForDataTable( + const FString& InNodeName, UHoudiniInputDataTable* InInputObject); + + // HAPI: Create an input node for reference + static bool CreateInputNodeForReference( + HAPI_NodeId& InputNodeId, + const FString & InRef, + const FString & InputNodeName, + const FTransform & InTransform); + + //static bool HapiUpdateInputNodeTransform(const HAPI_NodeId InputNodeId, const FTransform& Transform); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp index 9cd38e2a3..04880b326 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp @@ -1,3049 +1,3056 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInstanceTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniStaticMesh.h" - -//#include "HAPI/HAPI_Common.h" - -#include "Engine/StaticMesh.h" -#include "ComponentReregisterContext.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#if WITH_EDITOR - //#include "ScopedTransaction.h" - #include "LevelEditorViewport.h" - #include "MeshPaintHelpers.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// Fastrand is a faster alternative to std::rand() -// and doesn't oscillate when looking for 2 values like Unreal's. -inline int fastrand(int& nSeed) -{ - nSeed = (214013 * nSeed + 2531011); - return (nSeed >> 16) & 0x7FFF; -} - -// -bool -FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) -{ - // Get if force to use HISM from attribute - OutInstancedOutputPartData.bForceHISM = HasHISMAttribute(InHGPO.GeoId, InHGPO.PartId); - - // Extract the object and transforms for this instancer - if (!GetInstancerObjectsAndTransforms( - InHGPO, - InAllOutputs, - OutInstancedOutputPartData.OriginalInstancedObjects, - OutInstancedOutputPartData.OriginalInstancedTransforms, - OutInstancedOutputPartData.SplitAttributeName, - OutInstancedOutputPartData.SplitAttributeValues, - OutInstancedOutputPartData.PerSplitAttributes)) - return false; - - // Check if this is a No-Instancers ( unreal_split_instances ) - OutInstancedOutputPartData.bSplitMeshInstancer = IsSplitInstancer(InHGPO.GeoId, InHGPO.PartId); - - OutInstancedOutputPartData.bIsFoliageInstancer = IsFoliageInstancer(InHGPO.GeoId, InHGPO.PartId); - - // Extract the generic attributes - GetGenericPropertiesAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllPropertyAttributes); - - //Get the level path attribute on the instancer - if (!FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllLevelPaths)) - { - // No attribute specified - OutInstancedOutputPartData.AllLevelPaths.Empty(); - } - - // Get the output name attribute - if (!FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.OutputNames)) - { - // No attribute specified - OutInstancedOutputPartData.OutputNames.Empty(); - } - - // See if we have a tile attribute - if (!FHoudiniEngineUtils::GetTileAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.TileValues)) - { - // No attribute specified - OutInstancedOutputPartData.TileValues.Empty(); - } - - // Get the bake actor attribute - if (!FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeActorNames)) - { - // No attribute specified - OutInstancedOutputPartData.AllBakeActorNames.Empty(); - } - - // Get the bake outliner folder attribute - if (!FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeOutlinerFolders)) - { - // No attribute specified - OutInstancedOutputPartData.AllBakeOutlinerFolders.Empty(); - } - - // See if we have instancer material overrides - if (!GetMaterialOverridesFromAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.MaterialAttributes)) - OutInstancedOutputPartData.MaterialAttributes.Empty(); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - UHoudiniOutput* InOutput, - const TArray& InAllOutputs, - UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - // Keep track of the previous cook's component to clean them up after - TMap NewOutputObjects; - TMap OldOutputObjects = InOutput->GetOutputObjects(); - - TMap& InstancedOutputs = InOutput->GetInstancedOutputs(); - // Mark all the current instanced output as stale - for (auto& InstOut : InstancedOutputs) - InstOut.Value.bStale = true; - - USceneComponent* ParentComponent = Cast(InOuterComponent); - if (!ParentComponent) - return false; - - // Keep track of if we remove, create or update any foliage, so that we can repopulate the foliage type list in - // the UI (foliage mode) at the end - bool bHaveAnyFoliageInstancers = false; - - // We also need to cleanup the previous foliages instances (if we have any) - for (auto& CurrentPair : OldOutputObjects) - { - // Foliage instancers store a HISMC in the components - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentPair.Value.OutputComponent); - if (!FoliageHISMC || FoliageHISMC->IsPendingKill()) - continue; - - CleanupFoliageInstances(FoliageHISMC, ParentComponent); - bHaveAnyFoliageInstancers = true; - } - - // The default SM to be used if the instanced object has not been found (when using attribute instancers) - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) - { - // Not an instancer, skip - if (CurHGPO.Type != EHoudiniPartType::Instancer) - continue; - - // Prepare this output object's output identifier - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = CurHGPO.ObjectId; - OutputIdentifier.GeoId = CurHGPO.GeoId; - OutputIdentifier.PartId = CurHGPO.PartId; - OutputIdentifier.PartName = CurHGPO.PartName; - - FHoudiniInstancedOutputPartData InstancedOutputPartDataTmp; - const FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = nullptr; - if (InPreBuiltInstancedOutputPartData) - { - InstancedOutputPartDataPtr = InPreBuiltInstancedOutputPartData->Find(OutputIdentifier); - } - if (!InstancedOutputPartDataPtr) - { - if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs,InstancedOutputPartDataTmp)) - continue; - InstancedOutputPartDataPtr = &InstancedOutputPartDataTmp; - } - - const FHoudiniInstancedOutputPartData& InstancedOutputPartData = *InstancedOutputPartDataPtr; - - TArray InstancerMaterials; - if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes,InstancerMaterials)) - InstancerMaterials.Empty(); - - if (InstancedOutputPartData.bIsFoliageInstancer) - bHaveAnyFoliageInstancers = true; - - // - // TODO: REFACTOR THIS! - // - // We create an instanced output per original object - // These original object can then potentially be replaced by variations - // Each variations will create a instance component / OutputObject - // Currently we process all original objects AND their variations at the same time - // we should instead loop on the original objects - // - get their variations objects/transform - // - create the appropriate instancer - // This means modifying UpdateInstanceVariationsObjects so that it works using - // a single OriginalObject instead of using an array - // Also, apply the same logic to UpdateChangedInstanceOutput - // - - // Array containing all the variations objects for all the original objects - TArray> VariationInstancedObjects; - // Array containing all the variations transforms - TArray> VariationInstancedTransforms; - // Array indicate the original object index for each variation - TArray VariationOriginalObjectIndices; - // Array indicate the variation number for each variation - TArray VariationIndices; - // Update our variations using the instanced outputs - UpdateInstanceVariationObjects( - OutputIdentifier, - InstancedOutputPartData.OriginalInstancedObjects, - InstancedOutputPartData.OriginalInstancedTransforms, InOutput->GetInstancedOutputs(), - VariationInstancedObjects, VariationInstancedTransforms, - VariationOriginalObjectIndices, VariationIndices); - - // Create the instancer components now - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) - { - UObject* InstancedObject = VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - continue; - - if (!VariationInstancedTransforms.IsValidIndex(InstanceObjectIdx)) - continue; - - const TArray& InstancedObjectTransforms = VariationInstancedTransforms[InstanceObjectIdx]; - if (InstancedObjectTransforms.Num() <= 0) - continue; - - // Find the matching instance output now - FHoudiniInstancedOutput* FoundInstancedOutput = nullptr; - { - // Instanced output only use the original object index for their split identifier - FHoudiniOutputObjectIdentifier InstancedOutputIdentifier = OutputIdentifier; - InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]); - FoundInstancedOutput = InstancedOutputs.Find(InstancedOutputIdentifier); - } - - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - OutputIdentifier.SplitIdentifier = - FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]) - + TEXT("_") - + FString::FromInt(VariationIndices[InstanceObjectIdx]); - - // Get the OutputObj for this variation - FHoudiniOutputObject* FoundOutputObject = OldOutputObjects.Find(OutputIdentifier); - // See if we can find an preexisting component for this obj to try to reuse it - USceneComponent* OldInstancerComponent = nullptr; - const bool bIsProxyMesh = InstancedObject->IsA(); - if (FoundOutputObject) - { - if (bIsProxyMesh) - { - OldInstancerComponent = Cast(FoundOutputObject->ProxyComponent); - } - else - { - OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); - } - } - - // Extract the material for this variation - TArray VariationMaterials; - if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) - VariationMaterials.Empty(); - - USceneComponent* NewInstancerComponent = nullptr; - if (!CreateOrUpdateInstanceComponent( - InstancedObject, InstancedObjectTransforms, - InstancedOutputPartData.AllPropertyAttributes, CurHGPO, - ParentComponent, OldInstancerComponent, NewInstancerComponent, - InstancedOutputPartData.bSplitMeshInstancer, - InstancedOutputPartData.bIsFoliageInstancer, - VariationMaterials, - InstancedOutputPartData.bForceHISM)) - { - // TODO?? - continue; - } - - if (!NewInstancerComponent) - continue; - - // If the instanced object (by ref) wasn't found, hide the component - if(InstancedObject == DefaultReferenceSM) - NewInstancerComponent->SetHiddenInGame(true); - else - NewInstancerComponent->SetHiddenInGame(false); - - FHoudiniOutputObject& NewOutputObject = NewOutputObjects.FindOrAdd(OutputIdentifier); - if (bIsProxyMesh) - { - NewOutputObject.ProxyComponent = NewInstancerComponent; - } - else - { - NewOutputObject.OutputComponent = NewInstancerComponent; - } - - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - NewOutputObject.CachedAttributes.Empty(); - NewOutputObject.CachedTokens.Empty(); - - // Todo: get the proper attribute value per variation... - // Cache the level path, output name and tile attributes on the output object - // So they can be reused for baking - if(InstancedOutputPartData.AllLevelPaths.Num() > 0 && !InstancedOutputPartData.AllLevelPaths[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[0]); - - if(InstancedOutputPartData.OutputNames.Num() > 0 && !InstancedOutputPartData.OutputNames[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[0]); - - if(InstancedOutputPartData.TileValues.Num() > 0 && InstancedOutputPartData.TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[0])); - } - - if (InstancedOutputPartData.AllBakeActorNames.Num() > 0 && !InstancedOutputPartData.AllBakeActorNames[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[0]); - - if (InstancedOutputPartData.AllBakeOutlinerFolders.Num() > 0 && !InstancedOutputPartData.AllBakeOutlinerFolders[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[0]); - - if (InstancedOutputPartData.SplitAttributeValues.Num() > 0 - && !InstancedOutputPartData.SplitAttributeName.IsEmpty() - && InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalObjectIndices[InstanceObjectIdx])) - { - FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalObjectIndices[InstanceObjectIdx]]; - - // Cache the split attribute both as attribute and token - NewOutputObject.CachedAttributes.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); - NewOutputObject.CachedTokens.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); - - // If we have a split name that is non-empty, override attributes that can differ by split based - // on the split name - if (!SplitValue.IsEmpty()) - { - const FHoudiniInstancedOutputPerSplitAttributes* PerSplitAttributes = InstancedOutputPartData.PerSplitAttributes.Find(SplitValue); - if (PerSplitAttributes) - { - if (!PerSplitAttributes->LevelPath.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, PerSplitAttributes->LevelPath); - if (!PerSplitAttributes->BakeActorName.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, PerSplitAttributes->BakeActorName); - if (!PerSplitAttributes->BakeOutlinerFolder.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, PerSplitAttributes->BakeOutlinerFolder); - } - } - } - } - } - - // Remove reused components from the old map to avoid their deletion - for (const auto& CurNewPair : NewOutputObjects) - { - // Get the new Identifier / StaticMesh - const FHoudiniOutputObjectIdentifier& OutputIdentifier = CurNewPair.Key; - - // See if we already had that pair in the old map - FHoudiniOutputObject* FoundOldOutputObject = OldOutputObjects.Find(OutputIdentifier); - if (!FoundOldOutputObject) - continue; - - bool bKeep = false; - - UObject* NewComponent = CurNewPair.Value.OutputComponent; - if (NewComponent) - { - UObject* FoundOldComponent = FoundOldOutputObject->OutputComponent; - if (FoundOldComponent && !FoundOldComponent->IsPendingKill()) - { - bKeep = (FoundOldComponent == NewComponent); - } - } - - UObject* NewProxyComponent = CurNewPair.Value.ProxyComponent; - if (NewProxyComponent) - { - UObject* FoundOldProxyComponent = FoundOldOutputObject->ProxyComponent; - if (FoundOldProxyComponent && !FoundOldProxyComponent->IsPendingKill()) - { - bKeep = (FoundOldProxyComponent == NewProxyComponent); - } - } - - if (bKeep) - { - // Remove the reused component from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - - // The Old map now only contains unused/stale components, delete them - for (auto& OldPair : OldOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; - UObject* OldComponent = OldPair.Value.OutputComponent; - if (OldComponent) - { - bool bDestroy = true; - if (OldComponent->IsA()) - { - // When destroying a component, we have to be sure it's not an HISMC owned by an InstanceFoliageActor - UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OldComponent); - if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) - bDestroy = false; - } - - if(bDestroy) - RemoveAndDestroyComponent(OldComponent); - - OldPair.Value.OutputComponent = nullptr; - } - - UObject* OldProxyComponent = OldPair.Value.ProxyComponent; - if (OldProxyComponent) - { - RemoveAndDestroyComponent(OldProxyComponent); - OldPair.Value.ProxyComponent = nullptr; - } - } - OldOutputObjects.Empty(); - - // Update the output's object map - // Instancer do not create objects, clean the map - InOutput->SetOutputObjects(NewOutputObjects); - - // If we removed, created or updated any foliage instancers, repopulate the list of foliage types in the UI (foliage - // mode) - if (bHaveAnyFoliageInstancers) - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - - return true; -} - - -bool -FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( - FHoudiniInstancedOutput& InInstancedOutput, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - UHoudiniOutput* InParentOutput, - USceneComponent* InParentComponent) -{ - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = InOutputIdentifier.ObjectId; - OutputIdentifier.GeoId = InOutputIdentifier.GeoId; - OutputIdentifier.PartId = InOutputIdentifier.PartId; - OutputIdentifier.SplitIdentifier = InOutputIdentifier.SplitIdentifier; - OutputIdentifier.PartName = InOutputIdentifier.PartName; - - // Get if force using HISM from attribute - bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); - - TArray OriginalInstancedObjects; - OriginalInstancedObjects.Add(InInstancedOutput.OriginalObject.LoadSynchronous()); - - TArray> OriginalInstancedTransforms; - OriginalInstancedTransforms.Add(InInstancedOutput.OriginalTransforms); - - // Update our variations using the changed instancedoutputs objects - TArray> InstancedObjects; - TArray> InstancedTransforms; - TArray VariationOriginalObjectIndices; - TArray VariationIndices; - UpdateInstanceVariationObjects( - OutputIdentifier, - OriginalInstancedObjects, OriginalInstancedTransforms, - InParentOutput->GetInstancedOutputs(), - InstancedObjects, InstancedTransforms, - VariationOriginalObjectIndices, VariationIndices); - - // Find the HGPO for this instanced output - bool FoundHGPO = false; - FHoudiniGeoPartObject HGPO; - for (const auto& curHGPO : InParentOutput->GetHoudiniGeoPartObjects()) - { - if (OutputIdentifier.Matches(curHGPO)) - { - HGPO = curHGPO; - FoundHGPO = true; - break; - } - } - - if (!FoundHGPO) - { - // TODO check failure - ensure(FoundHGPO); - } - - // Extract the generic attributes for that HGPO - TArray AllPropertyAttributes; - GetGenericPropertiesAttributes(OutputIdentifier.GeoId, OutputIdentifier.PartId, AllPropertyAttributes); - - // Check if this is a No-Instancers ( unreal_split_instances ) - bool bSplitMeshInstancer = IsSplitInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); - - bool bIsFoliageInstancer = IsFoliageInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); - - // See if we have instancer material overrides - TArray InstancerMaterials; - if (!GetInstancerMaterials(OutputIdentifier.GeoId, OutputIdentifier.PartId, InstancerMaterials)) - InstancerMaterials.Empty(); - - // Keep track of the new instancer component in order to be able to clean up the unused/stale ones after. - TMap& OutputObjects = InParentOutput->GetOutputObjects(); - TMap ToDeleteOutputObjects = InParentOutput->GetOutputObjects(); - - // Create the instancer components now - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) - { - UObject* InstancedObject = InstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - continue; - - if (!InstancedTransforms.IsValidIndex(InstanceObjectIdx)) - continue; - - const TArray& InstancedObjectTransforms = InstancedTransforms[InstanceObjectIdx]; - if (InstancedObjectTransforms.Num() <= 0) - continue; - - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - // the original object index is used for the instanced outputs split identifier - OutputIdentifier.SplitIdentifier = - InOutputIdentifier.SplitIdentifier - + TEXT("_") - + FString::FromInt(VariationIndices[InstanceObjectIdx]); - - // See if we can find an preexisting component for this obj to try to reuse it - USceneComponent* OldInstancerComponent = nullptr; - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(OutputIdentifier); - if (FoundOutputObject) - { - OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); - } - - // Extract the material for this variation -// FHoudiniInstancedOutput* FoundInstancedOutput = InstancedOutputs.Find(OutputIdentifier); - TArray VariationMaterials; - if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) - VariationMaterials.Empty(); - - USceneComponent* NewInstancerComponent = nullptr; - if (!CreateOrUpdateInstanceComponent( - InstancedObject, InstancedObjectTransforms, - AllPropertyAttributes, HGPO, - InParentComponent, OldInstancerComponent, NewInstancerComponent, - bSplitMeshInstancer, bIsFoliageInstancer, InstancerMaterials, bForceHISM)) - { - // TODO?? - continue; - } - - if (!NewInstancerComponent) - continue; - - if (OldInstancerComponent != NewInstancerComponent) - { - // Previous component wasn't reused, detach and delete it - RemoveAndDestroyComponent(OldInstancerComponent); - - // Replace it with the new component - if (FoundOutputObject) - { - FoundOutputObject->OutputComponent = NewInstancerComponent; - } - else - { - FHoudiniOutputObject& NewOutputObject = OutputObjects.Add(OutputIdentifier); - NewOutputObject.OutputComponent = NewInstancerComponent; - } - } - - // Remove this output object from the todelete map - ToDeleteOutputObjects.Remove(OutputIdentifier); - } - - // Clean up the output objects that are not "reused" by the instanced outs - // The ToDelete map now only contains unused/stale components, delete them - for (auto& ToDeletePair : ToDeleteOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& ToDeleteIdentifier = ToDeletePair.Key; - UObject* OldComponent = ToDeletePair.Value.OutputComponent; - if (OldComponent) - { - RemoveAndDestroyComponent(OldComponent); - ToDeletePair.Value.OutputComponent = nullptr; - } - - UObject* OldProxyComponent = ToDeletePair.Value.ProxyComponent; - if (OldProxyComponent) - { - RemoveAndDestroyComponent(OldProxyComponent); - ToDeletePair.Value.ProxyComponent = nullptr; - } - - // Make sure the stale output object is not in the output map anymore - OutputObjects.Remove(ToDeleteIdentifier); - } - ToDeleteOutputObjects.Empty(); - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValues, - TMap& OutPerSplitAttributes) -{ - TArray InstancedObjects; - TArray> InstancedTransforms; - - TArray InstancedHGPOs; - TArray> InstancedHGPOTransforms; - - bool bSuccess = false; - switch (InHGPO.InstancerType) - { - case EHoudiniInstancerType::PackedPrimitive: - { - // Packed primitives instances - bSuccess = GetPackedPrimitiveInstancerHGPOsAndTransforms( - InHGPO, - InstancedHGPOs, - InstancedHGPOTransforms, - OutSplitAttributeName, - OutSplitAttributeValues, - OutPerSplitAttributes); - } - break; - - case EHoudiniInstancerType::AttributeInstancer: - { - // "Modern" attribute instancer - "unreal_instance" - bSuccess = GetAttributeInstancerObjectsAndTransforms( - InHGPO, - InstancedObjects, - InstancedTransforms, - OutSplitAttributeName, - OutSplitAttributeValues, - OutPerSplitAttributes); - } - break; - - case EHoudiniInstancerType::OldSchoolAttributeInstancer: - { - // Old school attribute override instancer - instance attribute w/ a HoudiniPath - bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); - } - break; - - case EHoudiniInstancerType::ObjectInstancer: - { - // Old School object instancer - bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); - } - break; - } - - if (!bSuccess) - return false; - - // Fetch the UOBject that correspond to the instanced parts - // Attribute instancers don't need to do this since they refer UObjects directly - if (InstancedHGPOs.Num() > 0) - { - for (int32 HGPOIdx = 0; HGPOIdx < InstancedHGPOs.Num(); HGPOIdx++) - { - const FHoudiniGeoPartObject& CurrentHGPO = InstancedHGPOs[HGPOIdx]; - - // Get the UObject that was generated for that HGPO - TArray ObjectsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - if (Output->OutputObjects.Num() <= 0) - continue; - - for (const auto& OutObjPair : Output->OutputObjects) - { - if (!OutObjPair.Key.Matches(CurrentHGPO)) - continue; - - const FHoudiniOutputObject& CurrentOutputObject = OutObjPair.Value; - - // In the case of a single-instance we can use the proxy (if it is current) - // FHoudiniOutputTranslator::UpdateOutputs doesn't allow proxies if there is more than one instance in an output - if (InstancedHGPOTransforms[HGPOIdx].Num() <= 1 && CurrentOutputObject.bProxyIsCurrent - && CurrentOutputObject.ProxyObject && !CurrentOutputObject.ProxyObject->IsPendingKill()) - { - ObjectsToInstance.Add(CurrentOutputObject.ProxyObject); - } - else if (CurrentOutputObject.OutputObject && !CurrentOutputObject.OutputObject->IsPendingKill()) - { - ObjectsToInstance.Add(CurrentOutputObject.OutputObject); - } - } - } - - // Add the UObject and the HGPO transforms to the output arrays - for (const auto& MatchingOutputObj : ObjectsToInstance) - { - InstancedObjects.Add(MatchingOutputObj); - InstancedTransforms.Add(InstancedHGPOTransforms[HGPOIdx]); - } - } - } - - // - if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() ) - { - // TODO - // Error / warning - return false; - } - - OutInstancedObjects = InstancedObjects; - OutInstancedTransforms = InstancedTransforms; - - return true; -} - - -void -FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TArray& InOriginalObjects, - const TArray>& InOriginalTransforms, - TMap& InstancedOutputs, - TArray>& OutVariationsInstancedObjects, - TArray>& OutVariationsInstancedTransforms, - TArray& OutVariationOriginalObjectIdx, - TArray& OutVariationIndices) -{ - FHoudiniOutputObjectIdentifier Identifier = InOutputIdentifier; - for (int32 InstObjIdx = 0; InstObjIdx < InOriginalObjects.Num(); InstObjIdx++) - { - UObject* OriginalObj = InOriginalObjects[InstObjIdx]; - if (!OriginalObj || OriginalObj->IsPendingKill()) - continue; - - // Build this output object's split identifier - Identifier.SplitIdentifier = FString::FromInt(InstObjIdx); - - // Do we have an instanced output object for this one? - FHoudiniInstancedOutput * FoundInstancedOutput = nullptr; - for (auto& Iter : InstancedOutputs) - { - FHoudiniOutputObjectIdentifier& FoundIdentifier = Iter.Key; - if (!(FoundIdentifier == Identifier)) - continue; - - // We found an existing instanced output for this identifier - FoundInstancedOutput = &(Iter.Value); - - if (FoundIdentifier.bLoaded) - { - // The output object identifier we found is marked as loaded, - // so uses old node IDs, we must update them, or the next cook - // will fail to locate the output back - FoundIdentifier.ObjectId = Identifier.ObjectId; - FoundIdentifier.GeoId = Identifier.GeoId; - FoundIdentifier.PartId = Identifier.PartId; - } - } - - if (!FoundInstancedOutput) - { - // Create a new one - FHoudiniInstancedOutput CurInstancedOutput; - CurInstancedOutput.OriginalObject = OriginalObj; - CurInstancedOutput.OriginalObjectIndex = InstObjIdx; - CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; - - CurInstancedOutput.VariationObjects.Add(OriginalObj); - CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); - CurInstancedOutput.TransformVariationIndices.SetNumZeroed(InOriginalTransforms[InstObjIdx].Num()); - CurInstancedOutput.MarkChanged(false); - CurInstancedOutput.bStale = false; - - // No variations, simply assign the object/transforms - OutVariationsInstancedObjects.Add(OriginalObj); - OutVariationsInstancedTransforms.Add(InOriginalTransforms[InstObjIdx]); - OutVariationOriginalObjectIdx.Add(InstObjIdx); - OutVariationIndices.Add(0); - - InstancedOutputs.Add(Identifier, CurInstancedOutput); - } - else - { - // Process the potential variations - FHoudiniInstancedOutput& CurInstancedOutput = *FoundInstancedOutput; - UObject *ReplacedOriginalObject = nullptr; - if (CurInstancedOutput.OriginalObject != OriginalObj) - { - ReplacedOriginalObject = CurInstancedOutput.OriginalObject.LoadSynchronous(); - CurInstancedOutput.OriginalObject = OriginalObj; - } - - CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; - - // Shouldnt be needed... - CurInstancedOutput.OriginalObjectIndex = InstObjIdx; - - // Remove any null or deleted variation objects - TArray ObjsToRemove; - for (int32 VarIdx = CurInstancedOutput.VariationObjects.Num() - 1; VarIdx >= 0; --VarIdx) - { - UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill() || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) - { - ObjsToRemove.Add(VarIdx); - } - } - if (ObjsToRemove.Num() > 0) - { - for (const int32 &VarIdx : ObjsToRemove) - { - CurInstancedOutput.VariationObjects.RemoveAt(VarIdx); - CurInstancedOutput.VariationTransformOffsets.RemoveAt(VarIdx); - } - // Force a recompute of variation assignments - CurInstancedOutput.TransformVariationIndices.SetNum(0); - } - - // If we don't have variations, simply use the original object - if (CurInstancedOutput.VariationObjects.Num() <= 0) - { - // No variations? add the original one - CurInstancedOutput.VariationObjects.Add(OriginalObj); - CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); - CurInstancedOutput.TransformVariationIndices.SetNum(0); - } - - // If the number of transforms has changed since the previous cook, - // we need to recompute the variation assignments - if (CurInstancedOutput.TransformVariationIndices.Num() != CurInstancedOutput.OriginalTransforms.Num()) - UpdateVariationAssignements(CurInstancedOutput); - - // Assign variations and their transforms - for (int32 VarIdx = 0; VarIdx < CurInstancedOutput.VariationObjects.Num(); VarIdx++) - { - UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill()) - continue; - - // Get the transforms assigned to that variation - TArray ProcessedTransforms; - ProcessInstanceTransforms(CurInstancedOutput, VarIdx, ProcessedTransforms); - if (ProcessedTransforms.Num() > 0) - { - OutVariationsInstancedObjects.Add(CurrentVariationObject); - OutVariationsInstancedTransforms.Add(ProcessedTransforms); - OutVariationOriginalObjectIdx.Add(InstObjIdx); - OutVariationIndices.Add(VarIdx); - } - } - - CurInstancedOutput.MarkChanged(false); - CurInstancedOutput.bStale = false; - } - } -} - - -void -FHoudiniInstanceTranslator::UpdateVariationAssignements(FHoudiniInstancedOutput& InstancedOutput) -{ - int32 TransformCount = InstancedOutput.OriginalTransforms.Num(); - InstancedOutput.TransformVariationIndices.SetNumZeroed(TransformCount); - - int32 VariationCount = InstancedOutput.VariationObjects.Num(); - if (VariationCount <= 1) - return; - - int nSeed = 1234; - for (int32 Idx = 0; Idx < TransformCount; Idx++) - { - InstancedOutput.TransformVariationIndices[Idx] = fastrand(nSeed) % VariationCount; - } -} - -void -FHoudiniInstanceTranslator::ProcessInstanceTransforms( - FHoudiniInstancedOutput& InstancedOutput, const int32& VariationIdx, TArray& OutProcessedTransforms) -{ - if (!InstancedOutput.VariationObjects.IsValidIndex(VariationIdx)) - return; - - if (!InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx)) - return; - - bool bHasVariations = InstancedOutput.VariationObjects.Num() > 1; - bool bHasTransformOffset = InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx) - ? !InstancedOutput.VariationTransformOffsets[VariationIdx].Equals(FTransform::Identity) - : false; - - if (!bHasVariations && !bHasTransformOffset) - { - // We dont have variations or transform offset, so we can reuse the original transforms as is - OutProcessedTransforms = InstancedOutput.OriginalTransforms; - return; - } - - if (bHasVariations) - { - // We simply need to extract the transforms for this variation - for (int32 TransformIndex = 0; TransformIndex < InstancedOutput.TransformVariationIndices.Num(); TransformIndex++) - { - if (InstancedOutput.TransformVariationIndices[TransformIndex] != VariationIdx) - continue; - - OutProcessedTransforms.Add(InstancedOutput.OriginalTransforms[TransformIndex]); - } - } - else - { - // No variations, we can reuse the original transforms - OutProcessedTransforms = InstancedOutput.OriginalTransforms; - } - - if (bHasTransformOffset) - { - // Get the transform offset for this variation - FVector PositionOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetLocation(); - FQuat RotationOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetRotation(); - FVector ScaleOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetScale3D(); - - FTransform CurrentTransform = FTransform::Identity; - for (int32 TransformIndex = 0; TransformIndex < OutProcessedTransforms.Num(); TransformIndex++) - { - CurrentTransform = OutProcessedTransforms[TransformIndex]; - - // Compute new rotation and scale. - FVector Position = CurrentTransform.GetLocation() + PositionOffset; - FQuat TransformRotation = CurrentTransform.GetRotation() * RotationOffset; - FVector TransformScale3D = CurrentTransform.GetScale3D() * ScaleOffset; - - // Make sure inverse matrix exists - seems to be a bug in Unreal when submitting instances. - // Happens in blueprint as well. - // We want to make sure the scale is not too small, but keep negative values! (Bug 90876) - if (FMath::Abs(TransformScale3D.X) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.X = (TransformScale3D.X > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - if (FMath::Abs(TransformScale3D.Y) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.Y = (TransformScale3D.Y > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - if (FMath::Abs(TransformScale3D.Z) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.Z = (TransformScale3D.Z > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - CurrentTransform.SetLocation(Position); - CurrentTransform.SetRotation(TransformRotation); - CurrentTransform.SetScale3D(TransformScale3D); - - if (CurrentTransform.IsValid()) - OutProcessedTransforms[TransformIndex] = CurrentTransform; - } - } -} - -bool -FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::PackedPrimitive) - return false; - - // Get transforms for each instance - TArray InstancerPartTransforms; - InstancerPartTransforms.SetNumZeroed(InHGPO.PartInfo.InstanceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancerPartTransforms( - FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, - HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, InHGPO.PartInfo.InstanceCount), false); - - // Convert the transform to Unreal's coordinate system - TArray InstancerUnrealTransforms; - InstancerUnrealTransforms.SetNumUninitialized(InstancerPartTransforms.Num()); - for (int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); InstanceIdx++) - { - const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; - FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, InstancerUnrealTransforms[InstanceIdx]); - } - - // Get the part ids for parts being instanced - TArray InstancedPartIds; - InstancedPartIds.SetNumZeroed(InHGPO.PartInfo.InstancedPartCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancedPartIds( - FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, - InstancedPartIds.GetData(), 0, InHGPO.PartInfo.InstancedPartCount), false); - - // See if the user has specified an attribute for splitting the instances - // and get the values - FString SplitAttribName = FString(); - TArray AllSplitAttributeValues; - bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( - InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_PRIM, SplitAttribName, AllSplitAttributeValues); - - // Get the level path attribute on the instancer - TArray AllLevelPaths; - const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( - InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_PRIM); - - // Get the bake actor attribute - TArray AllBakeActorNames; - const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( - InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_PRIM); - - // Get the bake outliner folder attribute - TArray AllBakeOutlinerFolders; - const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_PRIM); - - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; - - for (const auto& InstancedPartId : InstancedPartIds) - { - // Create a GeoPartObject corresponding to the instanced part - FHoudiniGeoPartObject InstancedHGPO; - InstancedHGPO.AssetId = InHGPO.AssetId; - InstancedHGPO.AssetName = InHGPO.AssetName; - InstancedHGPO.ObjectId = InHGPO.ObjectId; - InstancedHGPO.ObjectName = InHGPO.ObjectName; - InstancedHGPO.GeoId = InHGPO.GeoId; - InstancedHGPO.PartId = InstancedPartId; - InstancedHGPO.PartName = InHGPO.PartName; - InstancedHGPO.TransformMatrix = InHGPO.TransformMatrix; - - // TODO: Copy more cached data? - - OutInstancedHGPO.Add(InstancedHGPO); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - } - - // If we don't need to split the instances, we're done - if (!bHasSplitAttribute) - return true; - - // TODO: Optimize this! - // Split the instances using the split attribute's values - - // Move the output arrays to temp arrays - TArray UnsplitInstancedHGPOs = OutInstancedHGPO; - TArray> UnsplitInstancedTransforms = OutInstancedTransforms; - - // Empty the output arrays - OutInstancedHGPO.Empty(); - OutInstancedTransforms.Empty(); - OutSplitAttributeValue.Empty(); - for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedHGPOs.Num(); ObjIdx++) - { - // Map of split values to transform arrays - TMap> SplitTransformMap; - - TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; - - int32 NumInstances = CurrentTransforms.Num(); - if (AllSplitAttributeValues.Num() != NumInstances) - continue; - - // Split the transforms using the split values - for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) - { - const FString& SplitAttrValue = AllSplitAttributeValues[InstIdx]; - SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); - - // Record attributes for any split value we have not yet seen - if (bHasAnyPerSplitAttributes) - { - FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); - if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) - { - PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; - } - if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; - } - if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; - } - } - } - - // Add the objects, transform, split values to the final arrays - for (auto& Iterator : SplitTransformMap) - { - OutSplitAttributeValue.Add(Iterator.Key); - OutInstancedHGPO.Add(UnsplitInstancedHGPOs[ObjIdx]); - OutInstancedTransforms.Add(Iterator.Value); - } - } - - OutSplitAttributeName = SplitAttribName; - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::AttributeInstancer) - return false; - - // Look for the unreal instance attribute - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - - // instance attribute on points - bool is_override_attr = false; - HAPI_Result Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT, &AttribInfo); - - // unreal_instance attribute on points - if (Result != HAPI_RESULT_SUCCESS || AttribInfo.exists == false) - { - is_override_attr = true; - Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT, &AttribInfo); - } - - // unreal_instance attribute on detail - if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) - { - is_override_attr = true; - Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL, &AttribInfo); - } - - // Attribute does not exist. - if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the settings indicating if we want to use a default object when the referenced mesh is invalid - bool bDefaultObjectEnabled = true; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bDefaultObjectEnabled = HoudiniRuntimeSettings->bShowDefaultMesh; - } - - // See if the user has specified an attribute for splitting the instances, and get the values - FString SplitAttribName = FString(); - TArray AllSplitAttributeValues; - bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( - InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_POINT, SplitAttribName, AllSplitAttributeValues); - - // Get the level path attribute on the instancer - TArray AllLevelPaths; - const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( - InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_POINT); - - // Get the bake actor attribute - TArray AllBakeActorNames; - const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( - InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_POINT); - - // Get the bake outliner folder attribute - TArray AllBakeOutlinerFolders; - const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_POINT); - - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; - - // Array used to store the split values per objects - // Will only be used if we have a split attribute - TArray> SplitAttributeValuesPerObject; - - if (AttribInfo.owner == HAPI_ATTROWNER_DETAIL) - { - // If the attribute is on the detail, then its value is applied to all points - TArray DetailInstanceValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InHGPO.GeoId, - InHGPO.PartId, - is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, - AttribInfo, - DetailInstanceValues)) - { - // This should not happen - attribute exists, but there was an error retrieving it. - return false; - } - - if (DetailInstanceValues.Num() <= 0) - { - // No values specified. - return false; - } - - // Attempt to load specified asset. - const FString & AssetName = DetailInstanceValues[0]; - UObject * AttributeObject = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetName, nullptr, LOAD_None, nullptr); - - if (!AttributeObject) - { - // See if the ref is a class that we can instantiate - UClass * FoundClass = FindObject(ANY_PACKAGE, *AssetName); - if (FoundClass != nullptr) - { - // TODO: ensure we'll be able to create an actor from this class! - AttributeObject = FoundClass; - } - } - - if (!AttributeObject && bDefaultObjectEnabled) - { - HOUDINI_LOG_WARNING(TEXT("Failed to load instanced object '%s', using default instance mesh (hidden in game)."), *(AssetName)); - - // Couldn't load the referenced object, use the default reference mesh - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (!DefaultReferenceSM || DefaultReferenceSM->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to load the default instance mesh.")); - return false; - } - AttributeObject = DefaultReferenceSM; - } - - // Attach the objectPtr/transforms/bHiddenInGame if the attributeObject is created successfully - // (with either the actual referenced object or the default placeholder object) - if (AttributeObject) - { - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - - if(bHasSplitAttribute) - SplitAttributeValuesPerObject.Add(AllSplitAttributeValues); - } - } - else - { - // Attribute is on points, so we may have different values for each of them - TArray PointInstanceValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InHGPO.GeoId, - InHGPO.PartId, - is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, - AttribInfo, - PointInstanceValues)) - { - // This should not happen - attribute exists, but there was an error retrieving it. - return false; - } - - // The attribute is on points, so the number of points must match number of transforms. - if (!ensure(PointInstanceValues.Num() == InstancerUnrealTransforms.Num())) - { - // This should not happen, we have mismatch between number of instance values and transforms. - return false; - } - - // If instance attribute exists on points, we need to get all the unique values. - // This will give us all the unique object we want to instance - TMap ObjectsToInstance; - for (const auto& Iter : PointInstanceValues) - { - if (!ObjectsToInstance.Contains(Iter)) - { - // To avoid trying to load an object that fails multiple times, - // still add it to the array if null so we can still skip further attempts - UObject * AttributeObject = StaticLoadObject( - UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); - - if (!AttributeObject) - { - // See if the ref is a class that we can instantiate - UClass * FoundClass = FindObject(ANY_PACKAGE, *Iter); - if (FoundClass != nullptr) - { - // TODO: ensure we'll be able to create an actor from this class! - AttributeObject = FoundClass; - } - } - - ObjectsToInstance.Add(Iter, AttributeObject); - } - } - - // Iterates through all the unique objects and get their corresponding transforms - bool Success = false; - for (auto Iter : ObjectsToInstance) - { - bool bHiddenInGame = false; - // Check that we managed to load this object - UObject * AttributeObject = Iter.Value; - if (!AttributeObject && bDefaultObjectEnabled) - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load instanced object '%s', use default mesh (hidden in game)."), *(Iter.Key)); - - // If failed to load this object, add default reference mesh - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) - { - AttributeObject = DefaultReferenceSM; - bHiddenInGame = true; - } - else// Failed to load default reference mesh object - { - HOUDINI_LOG_WARNING(TEXT("Failed to load default mesh.")); - continue; - } - } - - if (!AttributeObject) - continue; - - if (!bHasSplitAttribute) - { - // No Split attribute: - // Extract the transform values that correspond to this object, and add them to the output arrays - const FString & InstancePath = Iter.Key; - TArray ObjectTransforms; - for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) - { - if (InstancePath.Equals(PointInstanceValues[Idx])) - ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); - } - - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(ObjectTransforms); - Success = true; - } - else - { - // We have a split attribute: - // Extract the transform values and split attribute values for this object, - // add them to the output arrays, and we will process the splits after - const FString & InstancePath = Iter.Key; - TArray ObjectTransforms; - TArray ObjectSplitValues; - for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) - { - if (InstancePath.Equals(PointInstanceValues[Idx])) - { - ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); - ObjectSplitValues.Add(AllSplitAttributeValues[Idx]); - } - } - - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(ObjectTransforms); - SplitAttributeValuesPerObject.Add(ObjectSplitValues); - Success = true; - } - } - - if (!Success) - return false; - } - - // If we don't need to split the instances, we're done - if (!bHasSplitAttribute) - return true; - - // Split the instances one more time, this time using the split values - - // Move the output arrays to temp arrays - TArray UnsplitInstancedObjects = OutInstancedObjects; - TArray> UnsplitInstancedTransforms = OutInstancedTransforms; - - // Empty the output arrays - OutInstancedObjects.Empty(); - OutInstancedTransforms.Empty(); - - // TODO: Output the split values as well! - OutSplitAttributeValue.Empty(); - for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedObjects.Num(); ObjIdx++) - { - UObject* InstancedObject = UnsplitInstancedObjects[ObjIdx]; - - // Map of split values to transform arrays - TMap> SplitTransformMap; - - TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; - TArray& CurrentSplits = SplitAttributeValuesPerObject[ObjIdx]; - - int32 NumInstances = CurrentTransforms.Num(); - if (CurrentSplits.Num() != NumInstances) - continue; - - // Split the transforms using the split values - for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) - { - const FString& SplitAttrValue = CurrentSplits[InstIdx]; - SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); - - // Record attributes for any split value we have not yet seen - if (bHasAnyPerSplitAttributes) - { - FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); - if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) - { - PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; - } - if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; - } - if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; - } - } - } - - // Add the objects, transform, split values to the final arrays - for (auto& Iterator : SplitTransformMap) - { - OutSplitAttributeValue.Add(Iterator.Key); - OutInstancedObjects.Add(InstancedObject); - OutInstancedTransforms.Add(Iterator.Value); - } - } - - OutSplitAttributeName = SplitAttribName; - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::OldSchoolAttributeInstancer) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the objects IDs to instanciate - int32 NumPoints = InHGPO.PartInfo.PointCount; - TArray InstancedObjectIds; - InstancedObjectIds.SetNumUninitialized(NumPoints); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedObjectIds( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InstancedObjectIds.GetData(), 0, NumPoints), false); - - // Find the set of instanced object ids and locate the corresponding parts - TSet UniqueInstancedObjectIds(InstancedObjectIds); - - // Locate all the HoudiniGeoPartObject that corresponds to the instanced object IDs - for (int32 InstancedObjectId : UniqueInstancedObjectIds) - { - // Get the parts that correspond to that object Id - TArray PartsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) - { - if (OutHGPO.Type != EHoudiniPartType::Mesh) - continue; - - if (OutHGPO.bIsInstanced) - continue; - - if (InstancedObjectId != OutHGPO.ObjectId) - continue; - - PartsToInstance.Add(OutHGPO); - } - } - - // Extract only the transforms that correspond to that specific object ID - TArray InstanceTransforms; - for (int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix) - { - if ((InstancedObjectIds[Ix] == InstancedObjectId) && (InstancerUnrealTransforms.IsValidIndex(Ix))) - { - InstanceTransforms.Add(InstancerUnrealTransforms[Ix]); - } - } - - // Add the instanced parts and their transforms to the output arrays - for (const auto& PartToInstance : PartsToInstance) - { - OutInstancedHGPO.Add(PartToInstance); - OutInstancedTransforms.Add(InstanceTransforms); - } - } - - if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0) - return true; - - return false; -} - - -bool -FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::ObjectInstancer) - return false; - - if (InHGPO.ObjectInfo.ObjectToInstanceID < 0) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the parts that correspond to that Object Id - TArray PartsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) - { - if (OutHGPO.Type != EHoudiniPartType::Mesh) - continue; - - /* - // But the instanced geo is actually not marked as instanced - if (!OutHGPO.bIsInstanced) - continue; - */ - - if (InHGPO.ObjectInfo.ObjectToInstanceID != OutHGPO.ObjectId) - continue; - - PartsToInstance.Add(OutHGPO); - } - } - - // Add found HGPO and transforms to the output arrays - for (auto& InstanceHGPO : PartsToInstance) - { - InstanceHGPO.TransformMatrix = InHGPO.TransformMatrix; - - // TODO: - //InstanceHGPO.UpdateCustomName(); - - OutInstancedHGPO.Add(InstanceHGPO); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent* OldComponent, - USceneComponent*& NewComponent, - const bool& InIsSplitMeshInstancer, - const bool& InIsFoliageInstancer, - const TArray& InstancerMaterials, - const int32& InstancerObjectIdx, - const bool& bForceHISM) -{ - enum InstancerComponentType - { - Invalid = -1, - InstancedStaticMeshComponent = 0, - HierarchicalInstancedStaticMeshComponent = 1, - MeshSplitInstancerComponent = 2, - HoudiniInstancedActorComponent = 3, - StaticMeshComponent = 4, - HoudiniStaticMeshComponent = 5, - Foliage = 6 - }; - - // See if we can reuse the old component - InstancerComponentType OldType = InstancerComponentType::Invalid; - if (OldComponent && !OldComponent->IsPendingKill()) - { - if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) - OldType = Foliage; - else if (OldComponent->IsA()) - OldType = HierarchicalInstancedStaticMeshComponent; - else if (OldComponent->IsA()) - OldType = InstancedStaticMeshComponent; - else if (OldComponent->IsA()) - OldType = MeshSplitInstancerComponent; - else if (OldComponent->IsA()) - OldType = HoudiniInstancedActorComponent; - else if (OldComponent->IsA()) - OldType = StaticMeshComponent; - else if (OldComponent->IsA()) - OldType = HoudiniStaticMeshComponent; - } - - // See what type of component we want to create - InstancerComponentType NewType = InstancerComponentType::Invalid; - - UStaticMesh * StaticMesh = Cast(InstancedObject); - UFoliageType * FoliageType = Cast(InstancedObject); - - UHoudiniStaticMesh * HSM = nullptr; - if (!StaticMesh && !FoliageType) - HSM = Cast(InstancedObject); - - if (StaticMesh) - { - if (InstancedObjectTransforms.Num() == 1) - NewType = StaticMeshComponent; - else if (InIsFoliageInstancer) - NewType = Foliage; - else if (InIsSplitMeshInstancer) - NewType = MeshSplitInstancerComponent; - else if(StaticMesh->GetNumLODs() > 1 || bForceHISM) - NewType = HierarchicalInstancedStaticMeshComponent; - else - NewType = InstancedStaticMeshComponent; - } - else if (HSM) - { - if (InstancedObjectTransforms.Num() == 1) - NewType = HoudiniStaticMeshComponent; - else - { - HOUDINI_LOG_ERROR(TEXT("More than one instance transform encountered for UHoudiniStaticMesh: %s"), *(HSM->GetPathName())); - NewType = Invalid; - return false; - } - } - else if (FoliageType) - { - NewType = Foliage; - } - else - { - NewType = HoudiniInstancedActorComponent; - } - - if (OldType == NewType) - NewComponent = OldComponent; - - UMaterialInterface* InstancerMaterial = nullptr; - if (InstancerMaterials.Num() > 0) - { - if (InstancerMaterials.IsValidIndex(InstancerObjectIdx)) - InstancerMaterial = InstancerMaterials[InstancerObjectIdx]; - else - InstancerMaterial = InstancerMaterials[0]; - } - - bool bSuccess = false; - switch (NewType) - { - case InstancedStaticMeshComponent: - case HierarchicalInstancedStaticMeshComponent: - { - // Create an Instanced Static Mesh Component - bSuccess = CreateOrUpdateInstancedStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM); - } - break; - - case MeshSplitInstancerComponent: - { - bSuccess = CreateOrUpdateMeshSplitInstancerComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterials); - } - break; - - case HoudiniInstancedActorComponent: - { - bSuccess = CreateOrUpdateInstancedActorComponent( - InstancedObject, InstancedObjectTransforms, AllPropertyAttributes, ParentComponent, NewComponent); - } - break; - - case StaticMeshComponent: - { - // Create a Static Mesh Component - bSuccess = CreateOrUpdateStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - break; - - case HoudiniStaticMeshComponent: - { - // Create a Houdini Static Mesh Component - bSuccess = CreateOrUpdateHoudiniStaticMeshComponent( - HSM, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - break; - - case Foliage: - { - bSuccess = CreateOrUpdateFoliageInstances( - StaticMesh, FoliageType, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - } - - if (!NewComponent) - return false; - - NewComponent->SetMobility(ParentComponent->Mobility); - NewComponent->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepRelativeTransform); - - // For single instance, that generates a SMC, the transform is already set on the component - // TODO: Should cumulate transform in that case? - if(NewType != StaticMeshComponent && NewType != HoudiniStaticMeshComponent) - NewComponent->SetRelativeTransform(InstancerGeoPartObject.TransformMatrix); - - // Only register if we have a valid component - if (NewComponent->GetOwner() && NewComponent->GetWorld()) - NewComponent->RegisterComponent(); - - // If the old component couldn't be reused, dettach/ destroy it - if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) - { - RemoveAndDestroyComponent(OldComponent); - } - - return bSuccess; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial, /*=nullptr*/ - const bool & bForceHISM) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast(CreatedInstancedComponent); - if (!InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill()) - { - if (InstancedStaticMesh->GetNumLODs() > 1 || bForceHISM) - { - // If the mesh has LODs, use Hierarchical ISMC - InstancedStaticMeshComponent = NewObject( - ComponentOuter, UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - } - else - { - // If the mesh doesnt have LOD, we can use a regular ISMC - InstancedStaticMeshComponent = NewObject( - ComponentOuter, UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - } - - // Change the creation method so the component is listed in the details panels - InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!InstancedStaticMeshComponent) - return false; - - InstancedStaticMeshComponent->SetStaticMesh(InstancedStaticMesh); - InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; - - InstancedStaticMeshComponent->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances themselves - // TODO: We should be calling UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( ... ) - InstancedStaticMeshComponent->ClearInstances(); - InstancedStaticMeshComponent->PreAllocateInstancesMemory(InstancedObjectTransforms.Num()); - for (const auto& Transform : InstancedObjectTransforms) - { - InstancedStaticMeshComponent->AddInstance(Transform); - } - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, 0); - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if(bCreatedNewComponent) - CreatedInstancedComponent = InstancedStaticMeshComponent; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent) -{ - if (!InstancedObject) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniInstancedActorComponent* InstancedActorComponent = Cast(CreatedInstancedComponent); - if (!InstancedActorComponent || InstancedActorComponent->IsPendingKill()) - { - // If the mesh doesnt have LOD, we can use a regular ISMC - InstancedActorComponent = NewObject( - ComponentOuter, UHoudiniInstancedActorComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - InstancedActorComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!InstancedActorComponent) - return false; - - // See if the instanced object has changed - bool bInstancedObjectHasChanged = (InstancedObject != InstancedActorComponent->GetInstancedObject()); - if (bInstancedObjectHasChanged) - { - // All actors will need to be respawned, invalidate all of them - InstancedActorComponent->ClearAllInstances(); - - // Update the HIAC's instanced asset - InstancedActorComponent->SetInstancedObject(InstancedObject); - } - - // Get the level where we want to spawn the actors - ULevel* SpawnLevel = ParentComponent->GetOwner() ? ParentComponent->GetOwner()->GetLevel() : nullptr; - if (!SpawnLevel) - return false; - - // Set the number of needed instances - InstancedActorComponent->SetNumberOfInstances(InstancedObjectTransforms.Num()); - for (int32 Idx = 0; Idx < InstancedObjectTransforms.Num(); Idx++) - { - // if we already have an actor, we can reuse it - const FTransform& CurTransform = InstancedObjectTransforms[Idx]; - - // Get the current instance - // If null, we need to create a new one, else we can reuse the actor - AActor* CurInstance = InstancedActorComponent->GetInstancedActorAt(Idx); - if (!CurInstance || CurInstance->IsPendingKill()) - { - CurInstance = SpawnInstanceActor(CurTransform, SpawnLevel, InstancedActorComponent); - InstancedActorComponent->SetInstanceAt(Idx, CurTransform, CurInstance); - } - else - { - // We can simply update the actor's transform - InstancedActorComponent->SetInstanceTransformAt(Idx, CurTransform); - } - - // Update the generic properties for that instance if any - // TODO: Handle instance variations w/ Idx - UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, Idx); - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - { - CreatedInstancedComponent = InstancedActorComponent; - } - - return true; -} - -// Create or update a MSIC -bool -FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - const TArray& InInstancerMaterials) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniMeshSplitInstancerComponent* MeshSplitComponent = Cast(CreatedInstancedComponent); - if (!MeshSplitComponent || MeshSplitComponent->IsPendingKill()) - { - // If the mesh doesn't have LOD, we can use a regular ISMC - MeshSplitComponent = NewObject( - ComponentOuter, UHoudiniMeshSplitInstancerComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - MeshSplitComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!MeshSplitComponent) - return false; - - MeshSplitComponent->SetStaticMesh(InstancedStaticMesh); - MeshSplitComponent->SetOverrideMaterials(InInstancerMaterials); - - // Now add the instances - MeshSplitComponent->SetInstanceTransforms(InstancedObjectTransforms); - - // Check for instance colors - TArray InstanceColorOverrides; - bool ColorOverrideAttributeFound = false; - - // Look for the unreal_instance_color attribute on points - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_POINT, &AttributeInfo)) - { - ColorOverrideAttributeFound = AttributeInfo.exists; - } - - // Look for the unreal_instance_color attribute on prims? (why? original code) - if (!ColorOverrideAttributeFound) - { - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_PRIM, &AttributeInfo)) - { - ColorOverrideAttributeFound = AttributeInfo.exists; - } - } - - if (ColorOverrideAttributeFound) - { - if (AttributeInfo.tupleSize == 4) - { - // Allocate sufficient buffer for data. - InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)InstanceColorOverrides.GetData(), 0, AttributeInfo.count)) - { - InstanceColorOverrides.Empty(); - } - } - else if (AttributeInfo.tupleSize == 3) - { - // Allocate sufficient buffer for data. - TArray FloatValues; - FloatValues.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)FloatValues.GetData(), 0, AttributeInfo.count)) - { - - // Allocate sufficient buffer for data. - InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); - - // Convert float to FLinearColors - for (int32 ColorIdx = 0; ColorIdx < InstanceColorOverrides.Num(); ColorIdx++) - { - InstanceColorOverrides[ColorIdx].R = FloatValues[ColorIdx * AttributeInfo.tupleSize + 0]; - InstanceColorOverrides[ColorIdx].G = FloatValues[ColorIdx * AttributeInfo.tupleSize + 1]; - InstanceColorOverrides[ColorIdx].B = FloatValues[ColorIdx * AttributeInfo.tupleSize + 2]; - InstanceColorOverrides[ColorIdx].A = 1.0; - } - FloatValues.Empty(); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_COLOR " must be a float[4] or float[3] prim/point attribute")); - } - } - - // if we have vertex color overrides, apply them now -#if WITH_EDITOR - if (InstanceColorOverrides.Num() > 0) - { - // Convert the color attribute to FColor - TArray InstanceColors; - InstanceColors.SetNumUninitialized(InstanceColorOverrides.Num()); - for (int32 ix = 0; ix < InstanceColors.Num(); ++ix) - { - InstanceColors[ix] = InstanceColorOverrides[ix].GetClamped().ToFColor(false); - } - - // Apply them to the instances - TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); - for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) - { - UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - if (!InstanceColors.IsValidIndex(InstIndex)) - continue; - - MeshPaintHelpers::FillStaticMeshVertexColors(CurSMC, -1, InstanceColors[InstIndex], FColor::White); - - //CurSMC->UnregisterComponent(); - //CurSMC->ReregisterComponent(); - - { - // We're only changing instanced vertices on this specific mesh component, so we - // only need to detach our mesh component - FComponentReregisterContext ComponentReregisterContext(CurSMC); - for (auto& CurLODData : CurSMC->LODData) - { - BeginInitResource(CurLODData.OverrideVertexColors); - } - } - - //FIXME: How to get rid of the warning about fixup vertex colors on load? - //SMC->FixupOverrideColorsIfNecessary(); - } - } -#endif - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - // TODO: Optimize - // Loop on attributes first, then components, - // if failing to find the attrib on a component, skip the rest - if (AllPropertyAttributes.Num() > 0) - { - TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); - for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) - { - UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); - } - } - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = MeshSplitComponent; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UStaticMeshComponent* SMC = Cast(CreatedInstancedComponent); - if (!SMC || SMC->IsPendingKill()) - { - // Create a new StaticMeshComponent - SMC = NewObject( - ComponentOuter, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - SMC->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!SMC) - return false; - - SMC->SetStaticMesh(InstancedStaticMesh); - SMC->GetBodyInstance()->bAutoWeld = false; - - SMC->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - SMC->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances Transform - SMC->SetRelativeTransform(InstancedObjectTransforms[0]); - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, 0); - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = SMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( - UHoudiniStaticMesh* InstancedProxyStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - if (!InstancedProxyStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniStaticMeshComponent* HSMC = Cast(CreatedInstancedComponent); - if (!HSMC || HSMC->IsPendingKill()) - { - // Create a new StaticMeshComponent - HSMC = NewObject( - ComponentOuter, UHoudiniStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - HSMC->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!HSMC) - return false; - - HSMC->SetMesh(InstancedProxyStaticMesh); - - HSMC->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedProxyStaticMesh->GetNumStaticMaterials(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - HSMC->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances Transform - HSMC->SetRelativeTransform(InstancedObjectTransforms[0]); - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, 0); - } - - // Assign the new HSMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = HSMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - - -bool -FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( - UStaticMesh* InstancedStaticMesh, - UFoliageType* InFoliageType, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - // We need either a valid SM or a valid Foliage Type - if ((!InstancedStaticMesh || InstancedStaticMesh->IsPendingKill()) - && (!InFoliageType || InFoliageType->IsPendingKill())) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - AActor* OwnerActor = ParentComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return false; - - // See if we already have a FoliageType for that static mesh - bool bCreatedNew = false; - UFoliageType *FoliageType = InFoliageType; - if (!FoliageType || FoliageType->IsPendingKill()) - { - // Foliage Type wasnt specified, only the mesh, try to find an existing foliage for that SM - FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InstancedStaticMesh); - } - else - { - // Foliage Type was specified, see if we can get its static mesh - UFoliageType_InstancedStaticMesh* FoliageISM = Cast(InFoliageType); - if (FoliageISM) - { - InstancedStaticMesh = FoliageISM->GetStaticMesh(); - } - - // See a component already exist on the actor - // If we cant find Foliage info for that foliage type, a new one will be created. - // when we call FindOrAddMesh - bCreatedNew = InstancedFoliageActor->FindInfo(FoliageType) == nullptr; - } - - if (!FoliageType || FoliageType->IsPendingKill()) - { - // We need to create a new FoliageType for this Static Mesh - // TODO: Add foliage default settings - InstancedFoliageActor->AddMesh(InstancedStaticMesh, &FoliageType); - bCreatedNew = true; - } - - if (!bCreatedNew && CreatedInstancedComponent) - { - // TODO: Shouldnt be needed anymore - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); - } - - // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it - FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); - if (!FoliageInfo) - return false; - - FTransform HoudiniAssetTransform = ParentComponent->GetComponentTransform(); - FFoliageInstance FoliageInstance; - int32 CurrentInstanceCount = 0; - for (auto CurrentTransform : InstancedObjectTransforms) - { - // Use our parent component for the base component of the instances, - // this will allow us to clean the instances by component - FoliageInstance.BaseComponent = ParentComponent; - - // TODO: FIX ME! - // Somehow, the first time when we create the Foliage type, instances need to be added with relative transform - // On subsequent cooks, they are actually expecting world transform - if (bCreatedNew) - { - FoliageInstance.Location = CurrentTransform.GetLocation(); - FoliageInstance.Rotation = CurrentTransform.GetRotation().Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D(); - } - else - { - FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); - FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); - } - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - CurrentInstanceCount++; - } - - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); - // TODO: This was due to a bug in UE4.22-20, check if still needed! - if (FoliageHISMC) - FoliageHISMC->BuildTreeIfOutdated(true, true); - - if (InstancerMaterial) - { - FoliageHISMC->OverrideMaterials.Empty(); - int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->StaticMaterials.Num() : 1; - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - FoliageHISMC->SetMaterial(Idx, InstancerMaterial); - } - - // Apply generic attributes if we have any - /* - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); - } - */ - - // Try to aplly generic properties attributes - // either on the instancer, mesh or foliage type - // TODO: Use proper atIndex!! - UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); - UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, 0); - UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, 0); - - if (bCreatedNew && FoliageHISMC) - CreatedInstancedComponent = FoliageHISMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::HapiGetInstanceTransforms( - const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancerUnrealTransforms) -{ - // Get the instance transforms - int32 PointCount = InHGPO.PartInfo.PointCount; - if (PointCount <= 0) - return false; - - TArray InstanceTransforms; - InstanceTransforms.SetNum(PointCount); - for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++) - FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx])); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetInstanceTransformsOnPart( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, HAPI_SRT, - &InstanceTransforms[0], 0, PointCount)) - { - InstanceTransforms.SetNum(0); - - // TODO: Warning? error? - return false; - } - - // Convert the transform to Unreal's coordinate system - OutInstancerUnrealTransforms.SetNumZeroed(InstanceTransforms.Num()); - for (int32 InstanceIdx = 0; InstanceIdx < InstanceTransforms.Num(); InstanceIdx++) - { - const auto& InstanceTransform = InstanceTransforms[InstanceIdx]; - FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, OutInstancerUnrealTransforms[InstanceIdx]); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::GetGenericPropertiesAttributes( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then get all the values for the primitive property attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, -1); - - // .. then finally, all values for point uproperty attributes - // TODO: !! get the correct Index here? - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, -1); - - return FoundCount > 0; -} - -bool -FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes, const int32& AtIndex) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (auto CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current property for the given instance index - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute, AtIndex)) - continue; - - // Success! - NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); - } - - return (NumSuccess > 0); -} - -bool -FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent) -{ - if (!InComponent || InComponent->IsPendingKill()) - return false; - - UFoliageInstancedStaticMeshComponent* FISMC = Cast(InComponent); - if (FISMC && !FISMC->IsPendingKill()) - { - // Make sure foliage our foliage instances have been removed - USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) - CleanupFoliageInstances(FISMC, ParentComponent); - - // do not delete FISMC that still have instances left - // as we have cleaned up our instances before, these have been hand-placed - if (FISMC->GetInstanceCount() > 0) - return false; - } - - USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - return true; - } - - return false; -} - - -bool -FHoudiniInstanceTranslator::GetMaterialOverridesFromAttributes( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutMaterialAttributes) -{ - HAPI_AttributeInfo MaterialAttributeInfo; - FHoudiniApi::AttributeInfo_Init(&MaterialAttributeInfo); - - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL, MaterialAttributeInfo, OutMaterialAttributes); - - /* - // TODO: Support material instances on instancers... - // see FHoudiniMaterialTranslator::CreateMaterialInstances() - // If material attribute and fallbacks were not found, check the material instance attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, MaterialAttributeInfo, OutMaterialAttributes); - } - */ - - if (!MaterialAttributeInfo.exists - /*&& MaterialAttributeInfo.owner != HAPI_ATTROWNER_PRIM - && MaterialAttributeInfo.owner != HAPI_ATTROWNER_DETAIL*/) - { - //HOUDINI_LOG_WARNING(TEXT("Instancer: the unreal_material attribute must be a primitive or detail attribute, ignoring the attribute.")); - OutMaterialAttributes.Empty(); - return false; - } - - return true; -} - -bool -FHoudiniInstanceTranslator::GetInstancerMaterials( - const TArray& MaterialAttributes, TArray& OutInstancerMaterials) -{ - // Use a map to avoid attempting to load the object for each instance - TMap MaterialMap; - - bool bHasValidMaterial = false; - for (auto& CurrentMatString : MaterialAttributes) - { - UMaterialInterface* CurrentMaterialInterface = nullptr; - UMaterialInterface** FoundMaterial = MaterialMap.Find(CurrentMatString); - if (!FoundMaterial) - { - // See if we can find a material interface that matches the attribute - CurrentMaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentMatString, nullptr, LOAD_NoWarn, nullptr)); - - // Check validity - if (!CurrentMaterialInterface || CurrentMaterialInterface->IsPendingKill()) - CurrentMaterialInterface = nullptr; - else - bHasValidMaterial = true; - - // Add what we found to the material map to avoid unnecessary loads - MaterialMap.Add(CurrentMatString, CurrentMaterialInterface); - } - else - { - // Reuse what we previously found - CurrentMaterialInterface = *FoundMaterial; - } - - OutInstancerMaterials.Add(CurrentMaterialInterface); - } - - // IF we couldn't find at least one valid material interface, empty the array - if (!bHasValidMaterial) - OutInstancerMaterials.Empty(); - - return true; -} - -bool -FHoudiniInstanceTranslator::GetInstancerMaterials( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutInstancerMaterials) -{ - TArray MaterialAttributes; - if (!GetMaterialOverridesFromAttributes(InGeoNodeId, InPartId, MaterialAttributes)) - MaterialAttributes.Empty(); - - return GetInstancerMaterials(MaterialAttributes, OutInstancerMaterials); -} - -bool -FHoudiniInstanceTranslator::GetVariationMaterials( - FHoudiniInstancedOutput* InInstancedOutput , const int32& InVariationIndex, - const TArray& InInstancerMaterials, TArray& OutVariationMaterials) -{ - if (!InInstancedOutput || InInstancerMaterials.Num() <= 0) - return false; - - // TODO: This also need to be improved and wont work 100%!! - // Use the instancedoutputs original object index? - if(!InInstancedOutput->VariationObjects.IsValidIndex(InVariationIndex)) - return false; - /* - // No variations, reuse the array - if (InInstancedOutput->VariationObjects.Num() == 1) - { - OutVariationMaterials = InInstancerMaterials; - return true; - } - */ - - if (InInstancedOutput->TransformVariationIndices.Num() == InInstancerMaterials.Num()) - { - for (int32 Idx = 0; Idx < InInstancedOutput->TransformVariationIndices.Num(); Idx++) - { - int32 VariationAssignment = InInstancedOutput->TransformVariationIndices[Idx]; - if (VariationAssignment != InVariationIndex) - continue; - - OutVariationMaterials.Add(InInstancerMaterials[Idx]); - } - } - else - { - if (InInstancerMaterials.IsValidIndex(InVariationIndex)) - OutVariationMaterials.Add(InInstancerMaterials[InVariationIndex]); - else - OutVariationMaterials.Add(InInstancerMaterials[0]); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::IsSplitInstancer(const int32& InGeoId, const int32& InPartId) -{ - bool bSplitMeshInstancer = false; - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; - bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); - - if (!bSplitMeshInstancer) - { - // Try on primitive - Owner = HAPI_ATTROWNER_PRIM; - bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); - } - - if (!bSplitMeshInstancer) - return false; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, - Owner, &AttributeInfo), false); - - if (!AttributeInfo.exists || AttributeInfo.count <= 0) - return false; - - TArray IntData; - // Allocate sufficient buffer for data. - IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, - &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); - - return (IntData[0] != 0); -} - -bool -FHoudiniInstanceTranslator::IsFoliageInstancer(const int32& InGeoId, const int32& InPartId) -{ - bool bIsFoliageInstancer = false; - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - - if (!bIsFoliageInstancer) - { - // Try on primitive - Owner = HAPI_ATTROWNER_PRIM; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - } - - if (!bIsFoliageInstancer) - { - // Finally, try on points - Owner = HAPI_ATTROWNER_POINT; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - } - - if (!bIsFoliageInstancer) - return false; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - Owner, &AttributeInfo), false); - - if (!AttributeInfo.exists || AttributeInfo.count <= 0) - return false; - - // We only support int/float attributes - if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - TArray IntData; - // Allocate sufficient buffer for data. - IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); - - return (IntData[0] != 0); - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - TArray FloatData; - // Allocate sufficient buffer for data. - FloatData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - &AttributeInfo, 0, &FloatData[0], 0, AttributeInfo.count), false); - - return (FloatData[0] != 0); - } - - return false; -} - - -AActor* -FHoudiniInstanceTranslator::SpawnInstanceActor(const FTransform& InTransform, ULevel* InSpawnLevel, UHoudiniInstancedActorComponent* InIAC) -{ - if (!InIAC || InIAC->IsPendingKill()) - return nullptr; - - UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - return nullptr; - - AActor* NewActor = nullptr; - -#if WITH_EDITOR - // Try to spawn a new actor for the given transform - GEditor->ClickLocation = InTransform.GetTranslation(); - GEditor->ClickPlane = FPlane(GEditor->ClickLocation, FVector::UpVector); - - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(InSpawnLevel, InstancedObject, false, RF_Transactional, nullptr); - if (NewActors.Num() > 0) - { - if (NewActors[0] && !NewActors[0]->IsPendingKill()) - { - NewActor = NewActors[0]; - } - } -#endif - - // Make sure that the actor was spawned in the proper level - FHoudiniEngineUtils::MoveActorToLevel(NewActor, InSpawnLevel); - - return NewActor; -} - - -void -FHoudiniInstanceTranslator::CleanupFoliageInstances(/*const FHoudiniInstancedOutput& InInstancedOutput,*/ UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, USceneComponent* InParentComponent) -{ - if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) - return; - - UStaticMesh* FoliageSM = InFoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - return; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(InFoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return; - - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - return; - - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(InParentComponent, FoliageType); - - // Remove the foliage type if it doesn't have any more instances - if(InFoliageHISMC->GetInstanceCount() == 0) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - return; -} - - -FString -FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(UObject* InObject) -{ - USceneComponent* InComponent = Cast(InObject); - - FString InstancerType = TEXT("Instancer"); - if (InComponent && !InComponent->IsPendingKill()) - { - if (InComponent->IsA()) - { - InstancerType = TEXT("(Split Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Actor Instancer)"); - } - else if (InComponent->IsA()) - { - if (InComponent->GetOwner() && InComponent->GetOwner()->IsA()) - InstancerType = TEXT("(Foliage Instancer)"); - else - InstancerType = TEXT("(Hierarchical Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Mesh Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Static Mesh Component)"); - } - } - - return InstancerType; -} - -bool -FHoudiniInstanceTranslator::GetInstancerSplitAttributesAndValues( - const int32& InGeoId, - const int32& InPartId, - const HAPI_AttributeOwner& InSplitAttributeOwner, - FString& OutSplitAttributeName, - TArray& OutAllSplitAttributeValues) -{ - // See if the user has specified an attribute to split the instancers. - bool bHasSplitAttribute = false; - //FString SplitAttribName = FString(); - OutSplitAttributeName = FString(); - - // Look for the unreal_split_attr attribute - // This attribute indicates the name of the point attribute that we'll use to split the instances further - HAPI_AttributeInfo SplitAttribInfo; - FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); - - TArray StringData; - bHasSplitAttribute = FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_SPLIT_ATTR, SplitAttribInfo, StringData, 1); - - if (!bHasSplitAttribute || !SplitAttribInfo.exists || StringData.Num() <= 0) - return false; - - OutSplitAttributeName = StringData[0]; - - // We have specified a split attribute, try to get its values. - OutAllSplitAttributeValues.Empty(); - if (!OutSplitAttributeName.IsEmpty()) - { - //HAPI_AttributeInfo SplitAttribInfo; - FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); - bool bSplitAttrFound = FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoId, - InPartId, - TCHAR_TO_ANSI(*OutSplitAttributeName), - SplitAttribInfo, - OutAllSplitAttributeValues, - 1, - InSplitAttributeOwner); - - if (!bSplitAttrFound || OutAllSplitAttributeValues.Num() <= 0) - { - // We couldn't properly get the point values, clean up everything - // to ensure that we'll ignore the split attribute - bHasSplitAttribute = false; - OutAllSplitAttributeValues.Empty(); - OutSplitAttributeName = FString(); - } - } - - return bHasSplitAttribute; -} - -bool -FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId) -{ - bool bHISM = false; - HAPI_AttributeInfo AttriInfo; - FHoudiniApi::AttributeInfo_Init(&AttriInfo); - TArray IntData; - IntData.Empty(); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM, AttriInfo, IntData, 1)) - { - if (IntData.Num() > 0) - bHISM = IntData[0] == 1; - } - - return bHISM; -} - -void FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() -{ - NumInstancedTransformsPerObject.Empty(); - OriginalInstancedTransformsFlat.Empty(); - for (const TArray& Transforms : OriginalInstancedTransforms) - { - NumInstancedTransformsPerObject.Add(Transforms.Num()); - OriginalInstancedTransformsFlat.Append(Transforms); - } - - OriginalInstanceObjectPackagePaths.Empty(); - for (const UObject* Obj : OriginalInstancedObjects) - { - if (IsValid(Obj)) - { - OriginalInstanceObjectPackagePaths.Add(Obj->GetPathName()); - } - else - { - OriginalInstanceObjectPackagePaths.Add(FString()); - } - } -} - -void FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() -{ - const int32 NumObjects = NumInstancedTransformsPerObject.Num(); - OriginalInstancedTransforms.Init(TArray(), NumObjects); - int32 ObjectIndexOffset = 0; - for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) - { - TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; - const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; - for (int32 Index = 0; Index < NumInstances; ++Index) - { - Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); - } - ObjectIndexOffset += NumInstances; - } - - OriginalInstancedObjects.Empty(); - for (const FString& PackageFullPath : OriginalInstanceObjectPackagePaths) - { - FString PackagePath; - FString PackageName; - const bool bDidSplit = PackageFullPath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); - if (!bDidSplit) - PackagePath = PackageFullPath; - - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!IsValid(Package)) - { - // Editor might have picked up the package yet, try to load it - Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); - } - if (IsValid(Package)) - { - OriginalInstancedObjects.Add(FindObject(Package, *PackageName)); - } - else - { - OriginalInstancedObjects.Add(nullptr); - } - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInstanceTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniStaticMesh.h" + +//#include "HAPI/HAPI_Common.h" + +#include "Engine/StaticMesh.h" +#include "ComponentReregisterContext.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#if WITH_EDITOR + //#include "ScopedTransaction.h" + #include "LevelEditorViewport.h" + #include "MeshPaintHelpers.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// Fastrand is a faster alternative to std::rand() +// and doesn't oscillate when looking for 2 values like Unreal's. +inline int fastrand(int& nSeed) +{ + nSeed = (214013 * nSeed + 2531011); + return (nSeed >> 16) & 0x7FFF; +} + +// +bool +FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) +{ + // Get if force to use HISM from attribute + OutInstancedOutputPartData.bForceHISM = HasHISMAttribute(InHGPO.GeoId, InHGPO.PartId); + + // Extract the object and transforms for this instancer + if (!GetInstancerObjectsAndTransforms( + InHGPO, + InAllOutputs, + OutInstancedOutputPartData.OriginalInstancedObjects, + OutInstancedOutputPartData.OriginalInstancedTransforms, + OutInstancedOutputPartData.SplitAttributeName, + OutInstancedOutputPartData.SplitAttributeValues, + OutInstancedOutputPartData.PerSplitAttributes)) + return false; + + // Check if this is a No-Instancers ( unreal_split_instances ) + OutInstancedOutputPartData.bSplitMeshInstancer = IsSplitInstancer(InHGPO.GeoId, InHGPO.PartId); + + OutInstancedOutputPartData.bIsFoliageInstancer = IsFoliageInstancer(InHGPO.GeoId, InHGPO.PartId); + + // Extract the generic attributes + GetGenericPropertiesAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllPropertyAttributes); + + //Get the level path attribute on the instancer + if (!FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllLevelPaths)) + { + // No attribute specified + OutInstancedOutputPartData.AllLevelPaths.Empty(); + } + + // Get the output name attribute + if (!FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.OutputNames)) + { + // No attribute specified + OutInstancedOutputPartData.OutputNames.Empty(); + } + + // See if we have a tile attribute + if (!FHoudiniEngineUtils::GetTileAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.TileValues)) + { + // No attribute specified + OutInstancedOutputPartData.TileValues.Empty(); + } + + // Get the bake actor attribute + if (!FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeActorNames)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeActorNames.Empty(); + } + + // Get the bake outliner folder attribute + if (!FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeOutlinerFolders)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeOutlinerFolders.Empty(); + } + + // See if we have instancer material overrides + if (!GetMaterialOverridesFromAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.MaterialAttributes)) + OutInstancedOutputPartData.MaterialAttributes.Empty(); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + UHoudiniOutput* InOutput, + const TArray& InAllOutputs, + UObject* InOuterComponent, + const TMap* InPreBuiltInstancedOutputPartData) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + // Keep track of the previous cook's component to clean them up after + TMap NewOutputObjects; + TMap OldOutputObjects = InOutput->GetOutputObjects(); + + TMap& InstancedOutputs = InOutput->GetInstancedOutputs(); + // Mark all the current instanced output as stale + for (auto& InstOut : InstancedOutputs) + InstOut.Value.bStale = true; + + USceneComponent* ParentComponent = Cast(InOuterComponent); + if (!ParentComponent) + return false; + + // Keep track of if we remove, create or update any foliage, so that we can repopulate the foliage type list in + // the UI (foliage mode) at the end + bool bHaveAnyFoliageInstancers = false; + + // We also need to cleanup the previous foliages instances (if we have any) + for (auto& CurrentPair : OldOutputObjects) + { + // Foliage instancers store a HISMC in the components + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentPair.Value.OutputComponent); + if (!FoliageHISMC || FoliageHISMC->IsPendingKill()) + continue; + + CleanupFoliageInstances(FoliageHISMC, ParentComponent); + bHaveAnyFoliageInstancers = true; + } + + // The default SM to be used if the instanced object has not been found (when using attribute instancers) + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) + { + // Not an instancer, skip + if (CurHGPO.Type != EHoudiniPartType::Instancer) + continue; + + // Prepare this output object's output identifier + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = CurHGPO.ObjectId; + OutputIdentifier.GeoId = CurHGPO.GeoId; + OutputIdentifier.PartId = CurHGPO.PartId; + OutputIdentifier.PartName = CurHGPO.PartName; + + FHoudiniInstancedOutputPartData InstancedOutputPartDataTmp; + const FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = nullptr; + if (InPreBuiltInstancedOutputPartData) + { + InstancedOutputPartDataPtr = InPreBuiltInstancedOutputPartData->Find(OutputIdentifier); + } + if (!InstancedOutputPartDataPtr) + { + if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs,InstancedOutputPartDataTmp)) + continue; + InstancedOutputPartDataPtr = &InstancedOutputPartDataTmp; + } + + const FHoudiniInstancedOutputPartData& InstancedOutputPartData = *InstancedOutputPartDataPtr; + + TArray InstancerMaterials; + if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes,InstancerMaterials)) + InstancerMaterials.Empty(); + + if (InstancedOutputPartData.bIsFoliageInstancer) + bHaveAnyFoliageInstancers = true; + + // + // TODO: REFACTOR THIS! + // + // We create an instanced output per original object + // These original object can then potentially be replaced by variations + // Each variations will create a instance component / OutputObject + // Currently we process all original objects AND their variations at the same time + // we should instead loop on the original objects + // - get their variations objects/transform + // - create the appropriate instancer + // This means modifying UpdateInstanceVariationsObjects so that it works using + // a single OriginalObject instead of using an array + // Also, apply the same logic to UpdateChangedInstanceOutput + // + + // Array containing all the variations objects for all the original objects + TArray> VariationInstancedObjects; + // Array containing all the variations transforms + TArray> VariationInstancedTransforms; + // Array indicate the original object index for each variation + TArray VariationOriginalObjectIndices; + // Array indicate the variation number for each variation + TArray VariationIndices; + // Update our variations using the instanced outputs + UpdateInstanceVariationObjects( + OutputIdentifier, + InstancedOutputPartData.OriginalInstancedObjects, + InstancedOutputPartData.OriginalInstancedTransforms, InOutput->GetInstancedOutputs(), + VariationInstancedObjects, VariationInstancedTransforms, + VariationOriginalObjectIndices, VariationIndices); + + // Create the instancer components now + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) + { + UObject* InstancedObject = VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + continue; + + if (!VariationInstancedTransforms.IsValidIndex(InstanceObjectIdx)) + continue; + + const TArray& InstancedObjectTransforms = VariationInstancedTransforms[InstanceObjectIdx]; + if (InstancedObjectTransforms.Num() <= 0) + continue; + + // Find the matching instance output now + FHoudiniInstancedOutput* FoundInstancedOutput = nullptr; + { + // Instanced output only use the original object index for their split identifier + FHoudiniOutputObjectIdentifier InstancedOutputIdentifier = OutputIdentifier; + InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]); + FoundInstancedOutput = InstancedOutputs.Find(InstancedOutputIdentifier); + } + + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + OutputIdentifier.SplitIdentifier = + FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]) + + TEXT("_") + + FString::FromInt(VariationIndices[InstanceObjectIdx]); + + // Get the OutputObj for this variation + FHoudiniOutputObject* FoundOutputObject = OldOutputObjects.Find(OutputIdentifier); + // See if we can find an preexisting component for this obj to try to reuse it + USceneComponent* OldInstancerComponent = nullptr; + const bool bIsProxyMesh = InstancedObject->IsA(); + if (FoundOutputObject) + { + if (bIsProxyMesh) + { + OldInstancerComponent = Cast(FoundOutputObject->ProxyComponent); + } + else + { + OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); + } + } + + // Extract the material for this variation + TArray VariationMaterials; + if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) + VariationMaterials.Empty(); + + USceneComponent* NewInstancerComponent = nullptr; + if (!CreateOrUpdateInstanceComponent( + InstancedObject, InstancedObjectTransforms, + InstancedOutputPartData.AllPropertyAttributes, CurHGPO, + ParentComponent, OldInstancerComponent, NewInstancerComponent, + InstancedOutputPartData.bSplitMeshInstancer, + InstancedOutputPartData.bIsFoliageInstancer, + VariationMaterials, + InstancedOutputPartData.bForceHISM)) + { + // TODO?? + continue; + } + + if (!NewInstancerComponent) + continue; + + // If the instanced object (by ref) wasn't found, hide the component + if(InstancedObject == DefaultReferenceSM) + NewInstancerComponent->SetHiddenInGame(true); + else + NewInstancerComponent->SetHiddenInGame(false); + + FHoudiniOutputObject& NewOutputObject = NewOutputObjects.FindOrAdd(OutputIdentifier); + if (bIsProxyMesh) + { + NewOutputObject.ProxyComponent = NewInstancerComponent; + } + else + { + NewOutputObject.OutputComponent = NewInstancerComponent; + } + + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + NewOutputObject.CachedAttributes.Empty(); + NewOutputObject.CachedTokens.Empty(); + + // Todo: get the proper attribute value per variation... + // Cache the level path, output name and tile attributes on the output object + // So they can be reused for baking + if(InstancedOutputPartData.AllLevelPaths.Num() > 0 && !InstancedOutputPartData.AllLevelPaths[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[0]); + + if(InstancedOutputPartData.OutputNames.Num() > 0 && !InstancedOutputPartData.OutputNames[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[0]); + + if(InstancedOutputPartData.TileValues.Num() > 0 && InstancedOutputPartData.TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[0])); + } + + if (InstancedOutputPartData.AllBakeActorNames.Num() > 0 && !InstancedOutputPartData.AllBakeActorNames[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[0]); + + if (InstancedOutputPartData.AllBakeOutlinerFolders.Num() > 0 && !InstancedOutputPartData.AllBakeOutlinerFolders[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[0]); + + if (InstancedOutputPartData.SplitAttributeValues.Num() > 0 + && !InstancedOutputPartData.SplitAttributeName.IsEmpty() + && InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalObjectIndices[InstanceObjectIdx])) + { + FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalObjectIndices[InstanceObjectIdx]]; + + // Cache the split attribute both as attribute and token + NewOutputObject.CachedAttributes.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); + NewOutputObject.CachedTokens.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); + + // If we have a split name that is non-empty, override attributes that can differ by split based + // on the split name + if (!SplitValue.IsEmpty()) + { + const FHoudiniInstancedOutputPerSplitAttributes* PerSplitAttributes = InstancedOutputPartData.PerSplitAttributes.Find(SplitValue); + if (PerSplitAttributes) + { + if (!PerSplitAttributes->LevelPath.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, PerSplitAttributes->LevelPath); + if (!PerSplitAttributes->BakeActorName.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, PerSplitAttributes->BakeActorName); + if (!PerSplitAttributes->BakeOutlinerFolder.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, PerSplitAttributes->BakeOutlinerFolder); + } + } + } + } + } + + // Remove reused components from the old map to avoid their deletion + for (const auto& CurNewPair : NewOutputObjects) + { + // Get the new Identifier / StaticMesh + const FHoudiniOutputObjectIdentifier& OutputIdentifier = CurNewPair.Key; + + // See if we already had that pair in the old map + FHoudiniOutputObject* FoundOldOutputObject = OldOutputObjects.Find(OutputIdentifier); + if (!FoundOldOutputObject) + continue; + + bool bKeep = false; + + UObject* NewComponent = CurNewPair.Value.OutputComponent; + if (NewComponent) + { + UObject* FoundOldComponent = FoundOldOutputObject->OutputComponent; + if (FoundOldComponent && !FoundOldComponent->IsPendingKill()) + { + bKeep = (FoundOldComponent == NewComponent); + } + } + + UObject* NewProxyComponent = CurNewPair.Value.ProxyComponent; + if (NewProxyComponent) + { + UObject* FoundOldProxyComponent = FoundOldOutputObject->ProxyComponent; + if (FoundOldProxyComponent && !FoundOldProxyComponent->IsPendingKill()) + { + bKeep = (FoundOldProxyComponent == NewProxyComponent); + } + } + + if (bKeep) + { + // Remove the reused component from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + + // The Old map now only contains unused/stale components, delete them + for (auto& OldPair : OldOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; + UObject* OldComponent = OldPair.Value.OutputComponent; + if (OldComponent) + { + bool bDestroy = true; + if (OldComponent->IsA()) + { + // When destroying a component, we have to be sure it's not an HISMC owned by an InstanceFoliageActor + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OldComponent); + if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) + bDestroy = false; + } + + if(bDestroy) + RemoveAndDestroyComponent(OldComponent); + + OldPair.Value.OutputComponent = nullptr; + } + + UObject* OldProxyComponent = OldPair.Value.ProxyComponent; + if (OldProxyComponent) + { + RemoveAndDestroyComponent(OldProxyComponent); + OldPair.Value.ProxyComponent = nullptr; + } + } + OldOutputObjects.Empty(); + + // Update the output's object map + // Instancer do not create objects, clean the map + InOutput->SetOutputObjects(NewOutputObjects); + + // If we removed, created or updated any foliage instancers, repopulate the list of foliage types in the UI (foliage + // mode) + if (bHaveAnyFoliageInstancers) + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + + return true; +} + + +bool +FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( + FHoudiniInstancedOutput& InInstancedOutput, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + UHoudiniOutput* InParentOutput, + USceneComponent* InParentComponent) +{ + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = InOutputIdentifier.ObjectId; + OutputIdentifier.GeoId = InOutputIdentifier.GeoId; + OutputIdentifier.PartId = InOutputIdentifier.PartId; + OutputIdentifier.SplitIdentifier = InOutputIdentifier.SplitIdentifier; + OutputIdentifier.PartName = InOutputIdentifier.PartName; + + // Get if force using HISM from attribute + bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); + + TArray OriginalInstancedObjects; + OriginalInstancedObjects.Add(InInstancedOutput.OriginalObject.LoadSynchronous()); + + TArray> OriginalInstancedTransforms; + OriginalInstancedTransforms.Add(InInstancedOutput.OriginalTransforms); + + // Update our variations using the changed instancedoutputs objects + TArray> InstancedObjects; + TArray> InstancedTransforms; + TArray VariationOriginalObjectIndices; + TArray VariationIndices; + UpdateInstanceVariationObjects( + OutputIdentifier, + OriginalInstancedObjects, + OriginalInstancedTransforms, + InParentOutput->GetInstancedOutputs(), + InstancedObjects, + InstancedTransforms, + VariationOriginalObjectIndices, + VariationIndices); + + // Find the HGPO for this instanced output + bool FoundHGPO = false; + FHoudiniGeoPartObject HGPO; + for (const auto& curHGPO : InParentOutput->GetHoudiniGeoPartObjects()) + { + if (OutputIdentifier.Matches(curHGPO)) + { + HGPO = curHGPO; + FoundHGPO = true; + break; + } + } + + if (!FoundHGPO) + { + // TODO check failure + ensure(FoundHGPO); + } + + // Extract the generic attributes for that HGPO + TArray AllPropertyAttributes; + GetGenericPropertiesAttributes(OutputIdentifier.GeoId, OutputIdentifier.PartId, AllPropertyAttributes); + + // Check if this is a No-Instancers ( unreal_split_instances ) + bool bSplitMeshInstancer = IsSplitInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); + + bool bIsFoliageInstancer = IsFoliageInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); + + // See if we have instancer material overrides + TArray InstancerMaterials; + if (!GetInstancerMaterials(OutputIdentifier.GeoId, OutputIdentifier.PartId, InstancerMaterials)) + InstancerMaterials.Empty(); + + // Preload objects so we can benefit from async compilation as much as possible + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) + { + InstancedObjects[InstanceObjectIdx].LoadSynchronous(); + } + + // Keep track of the new instancer component in order to be able to clean up the unused/stale ones after. + TMap& OutputObjects = InParentOutput->GetOutputObjects(); + TMap ToDeleteOutputObjects = InParentOutput->GetOutputObjects(); + + // Create the instancer components now + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) + { + UObject* InstancedObject = InstancedObjects[InstanceObjectIdx].LoadSynchronous(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + continue; + + if (!InstancedTransforms.IsValidIndex(InstanceObjectIdx)) + continue; + + const TArray& InstancedObjectTransforms = InstancedTransforms[InstanceObjectIdx]; + if (InstancedObjectTransforms.Num() <= 0) + continue; + + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + // the original object index is used for the instanced outputs split identifier + OutputIdentifier.SplitIdentifier = + InOutputIdentifier.SplitIdentifier + + TEXT("_") + + FString::FromInt(VariationIndices[InstanceObjectIdx]); + + // See if we can find an preexisting component for this obj to try to reuse it + USceneComponent* OldInstancerComponent = nullptr; + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(OutputIdentifier); + if (FoundOutputObject) + { + OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); + } + + // Extract the material for this variation +// FHoudiniInstancedOutput* FoundInstancedOutput = InstancedOutputs.Find(OutputIdentifier); + TArray VariationMaterials; + if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) + VariationMaterials.Empty(); + + USceneComponent* NewInstancerComponent = nullptr; + if (!CreateOrUpdateInstanceComponent( + InstancedObject, InstancedObjectTransforms, + AllPropertyAttributes, HGPO, + InParentComponent, OldInstancerComponent, NewInstancerComponent, + bSplitMeshInstancer, bIsFoliageInstancer, InstancerMaterials, bForceHISM)) + { + // TODO?? + continue; + } + + if (!NewInstancerComponent) + continue; + + if (OldInstancerComponent != NewInstancerComponent) + { + // Previous component wasn't reused, detach and delete it + RemoveAndDestroyComponent(OldInstancerComponent); + + // Replace it with the new component + if (FoundOutputObject) + { + FoundOutputObject->OutputComponent = NewInstancerComponent; + } + else + { + FHoudiniOutputObject& NewOutputObject = OutputObjects.Add(OutputIdentifier); + NewOutputObject.OutputComponent = NewInstancerComponent; + } + } + + // Remove this output object from the todelete map + ToDeleteOutputObjects.Remove(OutputIdentifier); + } + + // Clean up the output objects that are not "reused" by the instanced outs + // The ToDelete map now only contains unused/stale components, delete them + for (auto& ToDeletePair : ToDeleteOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& ToDeleteIdentifier = ToDeletePair.Key; + UObject* OldComponent = ToDeletePair.Value.OutputComponent; + if (OldComponent) + { + RemoveAndDestroyComponent(OldComponent); + ToDeletePair.Value.OutputComponent = nullptr; + } + + UObject* OldProxyComponent = ToDeletePair.Value.ProxyComponent; + if (OldProxyComponent) + { + RemoveAndDestroyComponent(OldProxyComponent); + ToDeletePair.Value.ProxyComponent = nullptr; + } + + // Make sure the stale output object is not in the output map anymore + OutputObjects.Remove(ToDeleteIdentifier); + } + ToDeleteOutputObjects.Empty(); + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValues, + TMap& OutPerSplitAttributes) +{ + TArray InstancedObjects; + TArray> InstancedTransforms; + + TArray InstancedHGPOs; + TArray> InstancedHGPOTransforms; + + bool bSuccess = false; + switch (InHGPO.InstancerType) + { + case EHoudiniInstancerType::PackedPrimitive: + { + // Packed primitives instances + bSuccess = GetPackedPrimitiveInstancerHGPOsAndTransforms( + InHGPO, + InstancedHGPOs, + InstancedHGPOTransforms, + OutSplitAttributeName, + OutSplitAttributeValues, + OutPerSplitAttributes); + } + break; + + case EHoudiniInstancerType::AttributeInstancer: + { + // "Modern" attribute instancer - "unreal_instance" + bSuccess = GetAttributeInstancerObjectsAndTransforms( + InHGPO, + InstancedObjects, + InstancedTransforms, + OutSplitAttributeName, + OutSplitAttributeValues, + OutPerSplitAttributes); + } + break; + + case EHoudiniInstancerType::OldSchoolAttributeInstancer: + { + // Old school attribute override instancer - instance attribute w/ a HoudiniPath + bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); + } + break; + + case EHoudiniInstancerType::ObjectInstancer: + { + // Old School object instancer + bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); + } + break; + } + + if (!bSuccess) + return false; + + // Fetch the UOBject that correspond to the instanced parts + // Attribute instancers don't need to do this since they refer UObjects directly + if (InstancedHGPOs.Num() > 0) + { + for (int32 HGPOIdx = 0; HGPOIdx < InstancedHGPOs.Num(); HGPOIdx++) + { + const FHoudiniGeoPartObject& CurrentHGPO = InstancedHGPOs[HGPOIdx]; + + // Get the UObject that was generated for that HGPO + TArray ObjectsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + if (Output->OutputObjects.Num() <= 0) + continue; + + for (const auto& OutObjPair : Output->OutputObjects) + { + if (!OutObjPair.Key.Matches(CurrentHGPO)) + continue; + + const FHoudiniOutputObject& CurrentOutputObject = OutObjPair.Value; + + // In the case of a single-instance we can use the proxy (if it is current) + // FHoudiniOutputTranslator::UpdateOutputs doesn't allow proxies if there is more than one instance in an output + if (InstancedHGPOTransforms[HGPOIdx].Num() <= 1 && CurrentOutputObject.bProxyIsCurrent + && CurrentOutputObject.ProxyObject && !CurrentOutputObject.ProxyObject->IsPendingKill()) + { + ObjectsToInstance.Add(CurrentOutputObject.ProxyObject); + } + else if (CurrentOutputObject.OutputObject && !CurrentOutputObject.OutputObject->IsPendingKill()) + { + ObjectsToInstance.Add(CurrentOutputObject.OutputObject); + } + } + } + + // Add the UObject and the HGPO transforms to the output arrays + for (const auto& MatchingOutputObj : ObjectsToInstance) + { + InstancedObjects.Add(MatchingOutputObj); + InstancedTransforms.Add(InstancedHGPOTransforms[HGPOIdx]); + } + } + } + + // + if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() ) + { + // TODO + // Error / warning + return false; + } + + OutInstancedObjects = InstancedObjects; + OutInstancedTransforms = InstancedTransforms; + + return true; +} + + +void +FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TArray& InOriginalObjects, + const TArray>& InOriginalTransforms, + TMap& InstancedOutputs, + TArray>& OutVariationsInstancedObjects, + TArray>& OutVariationsInstancedTransforms, + TArray& OutVariationOriginalObjectIdx, + TArray& OutVariationIndices) +{ + FHoudiniOutputObjectIdentifier Identifier = InOutputIdentifier; + for (int32 InstObjIdx = 0; InstObjIdx < InOriginalObjects.Num(); InstObjIdx++) + { + UObject* OriginalObj = InOriginalObjects[InstObjIdx]; + if (!OriginalObj || OriginalObj->IsPendingKill()) + continue; + + // Build this output object's split identifier + Identifier.SplitIdentifier = FString::FromInt(InstObjIdx); + + // Do we have an instanced output object for this one? + FHoudiniInstancedOutput * FoundInstancedOutput = nullptr; + for (auto& Iter : InstancedOutputs) + { + FHoudiniOutputObjectIdentifier& FoundIdentifier = Iter.Key; + if (!(FoundIdentifier == Identifier)) + continue; + + // We found an existing instanced output for this identifier + FoundInstancedOutput = &(Iter.Value); + + if (FoundIdentifier.bLoaded) + { + // The output object identifier we found is marked as loaded, + // so uses old node IDs, we must update them, or the next cook + // will fail to locate the output back + FoundIdentifier.ObjectId = Identifier.ObjectId; + FoundIdentifier.GeoId = Identifier.GeoId; + FoundIdentifier.PartId = Identifier.PartId; + } + } + + if (!FoundInstancedOutput) + { + // Create a new one + FHoudiniInstancedOutput CurInstancedOutput; + CurInstancedOutput.OriginalObject = OriginalObj; + CurInstancedOutput.OriginalObjectIndex = InstObjIdx; + CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + + CurInstancedOutput.VariationObjects.Add(OriginalObj); + CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); + CurInstancedOutput.TransformVariationIndices.SetNumZeroed(InOriginalTransforms[InstObjIdx].Num()); + CurInstancedOutput.MarkChanged(false); + CurInstancedOutput.bStale = false; + + // No variations, simply assign the object/transforms + OutVariationsInstancedObjects.Add(OriginalObj); + OutVariationsInstancedTransforms.Add(InOriginalTransforms[InstObjIdx]); + OutVariationOriginalObjectIdx.Add(InstObjIdx); + OutVariationIndices.Add(0); + + InstancedOutputs.Add(Identifier, CurInstancedOutput); + } + else + { + // Process the potential variations + FHoudiniInstancedOutput& CurInstancedOutput = *FoundInstancedOutput; + UObject *ReplacedOriginalObject = nullptr; + if (CurInstancedOutput.OriginalObject != OriginalObj) + { + ReplacedOriginalObject = CurInstancedOutput.OriginalObject.LoadSynchronous(); + CurInstancedOutput.OriginalObject = OriginalObj; + } + + CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + + // Shouldnt be needed... + CurInstancedOutput.OriginalObjectIndex = InstObjIdx; + + // Remove any null or deleted variation objects + TArray ObjsToRemove; + for (int32 VarIdx = CurInstancedOutput.VariationObjects.Num() - 1; VarIdx >= 0; --VarIdx) + { + UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); + if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill() || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) + { + ObjsToRemove.Add(VarIdx); + } + } + if (ObjsToRemove.Num() > 0) + { + for (const int32 &VarIdx : ObjsToRemove) + { + CurInstancedOutput.VariationObjects.RemoveAt(VarIdx); + CurInstancedOutput.VariationTransformOffsets.RemoveAt(VarIdx); + } + // Force a recompute of variation assignments + CurInstancedOutput.TransformVariationIndices.SetNum(0); + } + + // If we don't have variations, simply use the original object + if (CurInstancedOutput.VariationObjects.Num() <= 0) + { + // No variations? add the original one + CurInstancedOutput.VariationObjects.Add(OriginalObj); + CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); + CurInstancedOutput.TransformVariationIndices.SetNum(0); + } + + // If the number of transforms has changed since the previous cook, + // we need to recompute the variation assignments + if (CurInstancedOutput.TransformVariationIndices.Num() != CurInstancedOutput.OriginalTransforms.Num()) + UpdateVariationAssignements(CurInstancedOutput); + + // Assign variations and their transforms + for (int32 VarIdx = 0; VarIdx < CurInstancedOutput.VariationObjects.Num(); VarIdx++) + { + UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); + if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill()) + continue; + + // Get the transforms assigned to that variation + TArray ProcessedTransforms; + ProcessInstanceTransforms(CurInstancedOutput, VarIdx, ProcessedTransforms); + if (ProcessedTransforms.Num() > 0) + { + OutVariationsInstancedObjects.Add(CurrentVariationObject); + OutVariationsInstancedTransforms.Add(ProcessedTransforms); + OutVariationOriginalObjectIdx.Add(InstObjIdx); + OutVariationIndices.Add(VarIdx); + } + } + + CurInstancedOutput.MarkChanged(false); + CurInstancedOutput.bStale = false; + } + } +} + + +void +FHoudiniInstanceTranslator::UpdateVariationAssignements(FHoudiniInstancedOutput& InstancedOutput) +{ + int32 TransformCount = InstancedOutput.OriginalTransforms.Num(); + InstancedOutput.TransformVariationIndices.SetNumZeroed(TransformCount); + + int32 VariationCount = InstancedOutput.VariationObjects.Num(); + if (VariationCount <= 1) + return; + + int nSeed = 1234; + for (int32 Idx = 0; Idx < TransformCount; Idx++) + { + InstancedOutput.TransformVariationIndices[Idx] = fastrand(nSeed) % VariationCount; + } +} + +void +FHoudiniInstanceTranslator::ProcessInstanceTransforms( + FHoudiniInstancedOutput& InstancedOutput, const int32& VariationIdx, TArray& OutProcessedTransforms) +{ + if (!InstancedOutput.VariationObjects.IsValidIndex(VariationIdx)) + return; + + if (!InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx)) + return; + + bool bHasVariations = InstancedOutput.VariationObjects.Num() > 1; + bool bHasTransformOffset = InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx) + ? !InstancedOutput.VariationTransformOffsets[VariationIdx].Equals(FTransform::Identity) + : false; + + if (!bHasVariations && !bHasTransformOffset) + { + // We dont have variations or transform offset, so we can reuse the original transforms as is + OutProcessedTransforms = InstancedOutput.OriginalTransforms; + return; + } + + if (bHasVariations) + { + // We simply need to extract the transforms for this variation + for (int32 TransformIndex = 0; TransformIndex < InstancedOutput.TransformVariationIndices.Num(); TransformIndex++) + { + if (InstancedOutput.TransformVariationIndices[TransformIndex] != VariationIdx) + continue; + + OutProcessedTransforms.Add(InstancedOutput.OriginalTransforms[TransformIndex]); + } + } + else + { + // No variations, we can reuse the original transforms + OutProcessedTransforms = InstancedOutput.OriginalTransforms; + } + + if (bHasTransformOffset) + { + // Get the transform offset for this variation + FVector PositionOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetLocation(); + FQuat RotationOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetRotation(); + FVector ScaleOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetScale3D(); + + FTransform CurrentTransform = FTransform::Identity; + for (int32 TransformIndex = 0; TransformIndex < OutProcessedTransforms.Num(); TransformIndex++) + { + CurrentTransform = OutProcessedTransforms[TransformIndex]; + + // Compute new rotation and scale. + FVector Position = CurrentTransform.GetLocation() + PositionOffset; + FQuat TransformRotation = CurrentTransform.GetRotation() * RotationOffset; + FVector TransformScale3D = CurrentTransform.GetScale3D() * ScaleOffset; + + // Make sure inverse matrix exists - seems to be a bug in Unreal when submitting instances. + // Happens in blueprint as well. + // We want to make sure the scale is not too small, but keep negative values! (Bug 90876) + if (FMath::Abs(TransformScale3D.X) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.X = (TransformScale3D.X > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + if (FMath::Abs(TransformScale3D.Y) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.Y = (TransformScale3D.Y > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + if (FMath::Abs(TransformScale3D.Z) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.Z = (TransformScale3D.Z > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + CurrentTransform.SetLocation(Position); + CurrentTransform.SetRotation(TransformRotation); + CurrentTransform.SetScale3D(TransformScale3D); + + if (CurrentTransform.IsValid()) + OutProcessedTransforms[TransformIndex] = CurrentTransform; + } + } +} + +bool +FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::PackedPrimitive) + return false; + + // Get transforms for each instance + TArray InstancerPartTransforms; + InstancerPartTransforms.SetNumZeroed(InHGPO.PartInfo.InstanceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancerPartTransforms( + FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, + HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, InHGPO.PartInfo.InstanceCount), false); + + // Convert the transform to Unreal's coordinate system + TArray InstancerUnrealTransforms; + InstancerUnrealTransforms.SetNumUninitialized(InstancerPartTransforms.Num()); + for (int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); InstanceIdx++) + { + const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, InstancerUnrealTransforms[InstanceIdx]); + } + + // Get the part ids for parts being instanced + TArray InstancedPartIds; + InstancedPartIds.SetNumZeroed(InHGPO.PartInfo.InstancedPartCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancedPartIds( + FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, + InstancedPartIds.GetData(), 0, InHGPO.PartInfo.InstancedPartCount), false); + + // See if the user has specified an attribute for splitting the instances + // and get the values + FString SplitAttribName = FString(); + TArray AllSplitAttributeValues; + bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( + InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_PRIM, SplitAttribName, AllSplitAttributeValues); + + // Get the level path attribute on the instancer + TArray AllLevelPaths; + const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( + InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_PRIM); + + // Get the bake actor attribute + TArray AllBakeActorNames; + const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( + InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_PRIM); + + // Get the bake outliner folder attribute + TArray AllBakeOutlinerFolders; + const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_PRIM); + + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; + + for (const auto& InstancedPartId : InstancedPartIds) + { + // Create a GeoPartObject corresponding to the instanced part + FHoudiniGeoPartObject InstancedHGPO; + InstancedHGPO.AssetId = InHGPO.AssetId; + InstancedHGPO.AssetName = InHGPO.AssetName; + InstancedHGPO.ObjectId = InHGPO.ObjectId; + InstancedHGPO.ObjectName = InHGPO.ObjectName; + InstancedHGPO.GeoId = InHGPO.GeoId; + InstancedHGPO.PartId = InstancedPartId; + InstancedHGPO.PartName = InHGPO.PartName; + InstancedHGPO.TransformMatrix = InHGPO.TransformMatrix; + + // TODO: Copy more cached data? + + OutInstancedHGPO.Add(InstancedHGPO); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + } + + // If we don't need to split the instances, we're done + if (!bHasSplitAttribute) + return true; + + // TODO: Optimize this! + // Split the instances using the split attribute's values + + // Move the output arrays to temp arrays + TArray UnsplitInstancedHGPOs = OutInstancedHGPO; + TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + + // Empty the output arrays + OutInstancedHGPO.Empty(); + OutInstancedTransforms.Empty(); + OutSplitAttributeValue.Empty(); + for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedHGPOs.Num(); ObjIdx++) + { + // Map of split values to transform arrays + TMap> SplitTransformMap; + + TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + + int32 NumInstances = CurrentTransforms.Num(); + if (AllSplitAttributeValues.Num() != NumInstances) + continue; + + // Split the transforms using the split values + for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) + { + const FString& SplitAttrValue = AllSplitAttributeValues[InstIdx]; + SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + + // Record attributes for any split value we have not yet seen + if (bHasAnyPerSplitAttributes) + { + FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); + if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) + { + PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; + } + if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; + } + if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; + } + } + } + + // Add the objects, transform, split values to the final arrays + for (auto& Iterator : SplitTransformMap) + { + OutSplitAttributeValue.Add(Iterator.Key); + OutInstancedHGPO.Add(UnsplitInstancedHGPOs[ObjIdx]); + OutInstancedTransforms.Add(Iterator.Value); + } + } + + OutSplitAttributeName = SplitAttribName; + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::AttributeInstancer) + return false; + + // Look for the unreal instance attribute + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + // instance attribute on points + bool is_override_attr = false; + HAPI_Result Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT, &AttribInfo); + + // unreal_instance attribute on points + if (Result != HAPI_RESULT_SUCCESS || AttribInfo.exists == false) + { + is_override_attr = true; + Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT, &AttribInfo); + } + + // unreal_instance attribute on detail + if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) + { + is_override_attr = true; + Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL, &AttribInfo); + } + + // Attribute does not exist. + if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the settings indicating if we want to use a default object when the referenced mesh is invalid + bool bDefaultObjectEnabled = true; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bDefaultObjectEnabled = HoudiniRuntimeSettings->bShowDefaultMesh; + } + + // See if the user has specified an attribute for splitting the instances, and get the values + FString SplitAttribName = FString(); + TArray AllSplitAttributeValues; + bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( + InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_POINT, SplitAttribName, AllSplitAttributeValues); + + // Get the level path attribute on the instancer + TArray AllLevelPaths; + const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( + InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_POINT); + + // Get the bake actor attribute + TArray AllBakeActorNames; + const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( + InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_POINT); + + // Get the bake outliner folder attribute + TArray AllBakeOutlinerFolders; + const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_POINT); + + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; + + // Array used to store the split values per objects + // Will only be used if we have a split attribute + TArray> SplitAttributeValuesPerObject; + + if (AttribInfo.owner == HAPI_ATTROWNER_DETAIL) + { + // If the attribute is on the detail, then its value is applied to all points + TArray DetailInstanceValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InHGPO.GeoId, + InHGPO.PartId, + is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, + AttribInfo, + DetailInstanceValues)) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + if (DetailInstanceValues.Num() <= 0) + { + // No values specified. + return false; + } + + // Attempt to load specified asset. + const FString & AssetName = DetailInstanceValues[0]; + UObject * AttributeObject = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetName, nullptr, LOAD_None, nullptr); + + if (!AttributeObject) + { + // See if the ref is a class that we can instantiate + UClass * FoundClass = FindObject(ANY_PACKAGE, *AssetName); + if (FoundClass != nullptr) + { + // TODO: ensure we'll be able to create an actor from this class! + AttributeObject = FoundClass; + } + } + + if (!AttributeObject && bDefaultObjectEnabled) + { + HOUDINI_LOG_WARNING(TEXT("Failed to load instanced object '%s', using default instance mesh (hidden in game)."), *(AssetName)); + + // Couldn't load the referenced object, use the default reference mesh + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (!DefaultReferenceSM || DefaultReferenceSM->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to load the default instance mesh.")); + return false; + } + AttributeObject = DefaultReferenceSM; + } + + // Attach the objectPtr/transforms/bHiddenInGame if the attributeObject is created successfully + // (with either the actual referenced object or the default placeholder object) + if (AttributeObject) + { + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + + if(bHasSplitAttribute) + SplitAttributeValuesPerObject.Add(AllSplitAttributeValues); + } + } + else + { + // Attribute is on points, so we may have different values for each of them + TArray PointInstanceValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InHGPO.GeoId, + InHGPO.PartId, + is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, + AttribInfo, + PointInstanceValues)) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + // The attribute is on points, so the number of points must match number of transforms. + if (!ensure(PointInstanceValues.Num() == InstancerUnrealTransforms.Num())) + { + // This should not happen, we have mismatch between number of instance values and transforms. + return false; + } + + // If instance attribute exists on points, we need to get all the unique values. + // This will give us all the unique object we want to instance + TMap ObjectsToInstance; + for (const auto& Iter : PointInstanceValues) + { + if (!ObjectsToInstance.Contains(Iter)) + { + // To avoid trying to load an object that fails multiple times, + // still add it to the array if null so we can still skip further attempts + UObject * AttributeObject = StaticLoadObject( + UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); + + if (!AttributeObject) + { + // See if the ref is a class that we can instantiate + UClass * FoundClass = FindObject(ANY_PACKAGE, *Iter); + if (FoundClass != nullptr) + { + // TODO: ensure we'll be able to create an actor from this class! + AttributeObject = FoundClass; + } + } + + ObjectsToInstance.Add(Iter, AttributeObject); + } + } + + // Iterates through all the unique objects and get their corresponding transforms + bool Success = false; + for (auto Iter : ObjectsToInstance) + { + bool bHiddenInGame = false; + // Check that we managed to load this object + UObject * AttributeObject = Iter.Value; + if (!AttributeObject && bDefaultObjectEnabled) + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load instanced object '%s', use default mesh (hidden in game)."), *(Iter.Key)); + + // If failed to load this object, add default reference mesh + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + { + AttributeObject = DefaultReferenceSM; + bHiddenInGame = true; + } + else// Failed to load default reference mesh object + { + HOUDINI_LOG_WARNING(TEXT("Failed to load default mesh.")); + continue; + } + } + + if (!AttributeObject) + continue; + + if (!bHasSplitAttribute) + { + // No Split attribute: + // Extract the transform values that correspond to this object, and add them to the output arrays + const FString & InstancePath = Iter.Key; + TArray ObjectTransforms; + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) + { + if (InstancePath.Equals(PointInstanceValues[Idx])) + ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + } + + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(ObjectTransforms); + Success = true; + } + else + { + // We have a split attribute: + // Extract the transform values and split attribute values for this object, + // add them to the output arrays, and we will process the splits after + const FString & InstancePath = Iter.Key; + TArray ObjectTransforms; + TArray ObjectSplitValues; + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) + { + if (InstancePath.Equals(PointInstanceValues[Idx])) + { + ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + ObjectSplitValues.Add(AllSplitAttributeValues[Idx]); + } + } + + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(ObjectTransforms); + SplitAttributeValuesPerObject.Add(ObjectSplitValues); + Success = true; + } + } + + if (!Success) + return false; + } + + // If we don't need to split the instances, we're done + if (!bHasSplitAttribute) + return true; + + // Split the instances one more time, this time using the split values + + // Move the output arrays to temp arrays + TArray UnsplitInstancedObjects = OutInstancedObjects; + TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + + // Empty the output arrays + OutInstancedObjects.Empty(); + OutInstancedTransforms.Empty(); + + // TODO: Output the split values as well! + OutSplitAttributeValue.Empty(); + for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedObjects.Num(); ObjIdx++) + { + UObject* InstancedObject = UnsplitInstancedObjects[ObjIdx]; + + // Map of split values to transform arrays + TMap> SplitTransformMap; + + TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + TArray& CurrentSplits = SplitAttributeValuesPerObject[ObjIdx]; + + int32 NumInstances = CurrentTransforms.Num(); + if (CurrentSplits.Num() != NumInstances) + continue; + + // Split the transforms using the split values + for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) + { + const FString& SplitAttrValue = CurrentSplits[InstIdx]; + SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + + // Record attributes for any split value we have not yet seen + if (bHasAnyPerSplitAttributes) + { + FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); + if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) + { + PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; + } + if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; + } + if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; + } + } + } + + // Add the objects, transform, split values to the final arrays + for (auto& Iterator : SplitTransformMap) + { + OutSplitAttributeValue.Add(Iterator.Key); + OutInstancedObjects.Add(InstancedObject); + OutInstancedTransforms.Add(Iterator.Value); + } + } + + OutSplitAttributeName = SplitAttribName; + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::OldSchoolAttributeInstancer) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the objects IDs to instanciate + int32 NumPoints = InHGPO.PartInfo.PointCount; + TArray InstancedObjectIds; + InstancedObjectIds.SetNumUninitialized(NumPoints); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedObjectIds( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InstancedObjectIds.GetData(), 0, NumPoints), false); + + // Find the set of instanced object ids and locate the corresponding parts + TSet UniqueInstancedObjectIds(InstancedObjectIds); + + // Locate all the HoudiniGeoPartObject that corresponds to the instanced object IDs + for (int32 InstancedObjectId : UniqueInstancedObjectIds) + { + // Get the parts that correspond to that object Id + TArray PartsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) + { + if (OutHGPO.Type != EHoudiniPartType::Mesh) + continue; + + if (OutHGPO.bIsInstanced) + continue; + + if (InstancedObjectId != OutHGPO.ObjectId) + continue; + + PartsToInstance.Add(OutHGPO); + } + } + + // Extract only the transforms that correspond to that specific object ID + TArray InstanceTransforms; + for (int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix) + { + if ((InstancedObjectIds[Ix] == InstancedObjectId) && (InstancerUnrealTransforms.IsValidIndex(Ix))) + { + InstanceTransforms.Add(InstancerUnrealTransforms[Ix]); + } + } + + // Add the instanced parts and their transforms to the output arrays + for (const auto& PartToInstance : PartsToInstance) + { + OutInstancedHGPO.Add(PartToInstance); + OutInstancedTransforms.Add(InstanceTransforms); + } + } + + if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0) + return true; + + return false; +} + + +bool +FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::ObjectInstancer) + return false; + + if (InHGPO.ObjectInfo.ObjectToInstanceID < 0) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the parts that correspond to that Object Id + TArray PartsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) + { + if (OutHGPO.Type != EHoudiniPartType::Mesh) + continue; + + /* + // But the instanced geo is actually not marked as instanced + if (!OutHGPO.bIsInstanced) + continue; + */ + + if (InHGPO.ObjectInfo.ObjectToInstanceID != OutHGPO.ObjectId) + continue; + + PartsToInstance.Add(OutHGPO); + } + } + + // Add found HGPO and transforms to the output arrays + for (auto& InstanceHGPO : PartsToInstance) + { + InstanceHGPO.TransformMatrix = InHGPO.TransformMatrix; + + // TODO: + //InstanceHGPO.UpdateCustomName(); + + OutInstancedHGPO.Add(InstanceHGPO); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent* OldComponent, + USceneComponent*& NewComponent, + const bool& InIsSplitMeshInstancer, + const bool& InIsFoliageInstancer, + const TArray& InstancerMaterials, + const int32& InstancerObjectIdx, + const bool& bForceHISM) +{ + enum InstancerComponentType + { + Invalid = -1, + InstancedStaticMeshComponent = 0, + HierarchicalInstancedStaticMeshComponent = 1, + MeshSplitInstancerComponent = 2, + HoudiniInstancedActorComponent = 3, + StaticMeshComponent = 4, + HoudiniStaticMeshComponent = 5, + Foliage = 6 + }; + + // See if we can reuse the old component + InstancerComponentType OldType = InstancerComponentType::Invalid; + if (OldComponent && !OldComponent->IsPendingKill()) + { + if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) + OldType = Foliage; + else if (OldComponent->IsA()) + OldType = HierarchicalInstancedStaticMeshComponent; + else if (OldComponent->IsA()) + OldType = InstancedStaticMeshComponent; + else if (OldComponent->IsA()) + OldType = MeshSplitInstancerComponent; + else if (OldComponent->IsA()) + OldType = HoudiniInstancedActorComponent; + else if (OldComponent->IsA()) + OldType = StaticMeshComponent; + else if (OldComponent->IsA()) + OldType = HoudiniStaticMeshComponent; + } + + // See what type of component we want to create + InstancerComponentType NewType = InstancerComponentType::Invalid; + + UStaticMesh * StaticMesh = Cast(InstancedObject); + UFoliageType * FoliageType = Cast(InstancedObject); + + UHoudiniStaticMesh * HSM = nullptr; + if (!StaticMesh && !FoliageType) + HSM = Cast(InstancedObject); + + if (StaticMesh) + { + if (InstancedObjectTransforms.Num() == 1) + NewType = StaticMeshComponent; + else if (InIsFoliageInstancer) + NewType = Foliage; + else if (InIsSplitMeshInstancer) + NewType = MeshSplitInstancerComponent; + else if(StaticMesh->GetNumLODs() > 1 || bForceHISM) + NewType = HierarchicalInstancedStaticMeshComponent; + else + NewType = InstancedStaticMeshComponent; + } + else if (HSM) + { + if (InstancedObjectTransforms.Num() == 1) + NewType = HoudiniStaticMeshComponent; + else + { + HOUDINI_LOG_ERROR(TEXT("More than one instance transform encountered for UHoudiniStaticMesh: %s"), *(HSM->GetPathName())); + NewType = Invalid; + return false; + } + } + else if (FoliageType) + { + NewType = Foliage; + } + else + { + NewType = HoudiniInstancedActorComponent; + } + + if (OldType == NewType) + NewComponent = OldComponent; + + UMaterialInterface* InstancerMaterial = nullptr; + if (InstancerMaterials.Num() > 0) + { + if (InstancerMaterials.IsValidIndex(InstancerObjectIdx)) + InstancerMaterial = InstancerMaterials[InstancerObjectIdx]; + else + InstancerMaterial = InstancerMaterials[0]; + } + + bool bSuccess = false; + switch (NewType) + { + case InstancedStaticMeshComponent: + case HierarchicalInstancedStaticMeshComponent: + { + // Create an Instanced Static Mesh Component + bSuccess = CreateOrUpdateInstancedStaticMeshComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM); + } + break; + + case MeshSplitInstancerComponent: + { + bSuccess = CreateOrUpdateMeshSplitInstancerComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterials); + } + break; + + case HoudiniInstancedActorComponent: + { + bSuccess = CreateOrUpdateInstancedActorComponent( + InstancedObject, InstancedObjectTransforms, AllPropertyAttributes, ParentComponent, NewComponent); + } + break; + + case StaticMeshComponent: + { + // Create a Static Mesh Component + bSuccess = CreateOrUpdateStaticMeshComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + break; + + case HoudiniStaticMeshComponent: + { + // Create a Houdini Static Mesh Component + bSuccess = CreateOrUpdateHoudiniStaticMeshComponent( + HSM, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + break; + + case Foliage: + { + bSuccess = CreateOrUpdateFoliageInstances( + StaticMesh, FoliageType, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + } + + if (!NewComponent) + return false; + + NewComponent->SetMobility(ParentComponent->Mobility); + NewComponent->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepRelativeTransform); + + // For single instance, that generates a SMC, the transform is already set on the component + // TODO: Should cumulate transform in that case? + if(NewType != StaticMeshComponent && NewType != HoudiniStaticMeshComponent) + NewComponent->SetRelativeTransform(InstancerGeoPartObject.TransformMatrix); + + // Only register if we have a valid component + if (NewComponent->GetOwner() && NewComponent->GetWorld()) + NewComponent->RegisterComponent(); + + // If the old component couldn't be reused, dettach/ destroy it + if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) + { + RemoveAndDestroyComponent(OldComponent); + } + + return bSuccess; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial, /*=nullptr*/ + const bool & bForceHISM) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast(CreatedInstancedComponent); + if (!InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill()) + { + if (InstancedStaticMesh->GetNumLODs() > 1 || bForceHISM) + { + // If the mesh has LODs, use Hierarchical ISMC + InstancedStaticMeshComponent = NewObject( + ComponentOuter, UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + else + { + // If the mesh doesnt have LOD, we can use a regular ISMC + InstancedStaticMeshComponent = NewObject( + ComponentOuter, UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + + // Change the creation method so the component is listed in the details panels + InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!InstancedStaticMeshComponent) + return false; + + InstancedStaticMeshComponent->SetStaticMesh(InstancedStaticMesh); + InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; + + InstancedStaticMeshComponent->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances themselves + // TODO: We should be calling UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( ... ) + InstancedStaticMeshComponent->ClearInstances(); + InstancedStaticMeshComponent->PreAllocateInstancesMemory(InstancedObjectTransforms.Num()); + for (const auto& Transform : InstancedObjectTransforms) + { + InstancedStaticMeshComponent->AddInstance(Transform); + } + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + for (const auto& CurrentAttrib : AllPropertyAttributes) + { + UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, 0); + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if(bCreatedNewComponent) + CreatedInstancedComponent = InstancedStaticMeshComponent; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent) +{ + if (!InstancedObject) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniInstancedActorComponent* InstancedActorComponent = Cast(CreatedInstancedComponent); + if (!InstancedActorComponent || InstancedActorComponent->IsPendingKill()) + { + // If the mesh doesnt have LOD, we can use a regular ISMC + InstancedActorComponent = NewObject( + ComponentOuter, UHoudiniInstancedActorComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + InstancedActorComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!InstancedActorComponent) + return false; + + // See if the instanced object has changed + bool bInstancedObjectHasChanged = (InstancedObject != InstancedActorComponent->GetInstancedObject()); + if (bInstancedObjectHasChanged) + { + // All actors will need to be respawned, invalidate all of them + InstancedActorComponent->ClearAllInstances(); + + // Update the HIAC's instanced asset + InstancedActorComponent->SetInstancedObject(InstancedObject); + } + + // Get the level where we want to spawn the actors + ULevel* SpawnLevel = ParentComponent->GetOwner() ? ParentComponent->GetOwner()->GetLevel() : nullptr; + if (!SpawnLevel) + return false; + + // Set the number of needed instances + InstancedActorComponent->SetNumberOfInstances(InstancedObjectTransforms.Num()); + for (int32 Idx = 0; Idx < InstancedObjectTransforms.Num(); Idx++) + { + // if we already have an actor, we can reuse it + const FTransform& CurTransform = InstancedObjectTransforms[Idx]; + + // Get the current instance + // If null, we need to create a new one, else we can reuse the actor + AActor* CurInstance = InstancedActorComponent->GetInstancedActorAt(Idx); + if (!CurInstance || CurInstance->IsPendingKill()) + { + CurInstance = SpawnInstanceActor(CurTransform, SpawnLevel, InstancedActorComponent); + InstancedActorComponent->SetInstanceAt(Idx, CurTransform, CurInstance); + } + else + { + // We can simply update the actor's transform + InstancedActorComponent->SetInstanceTransformAt(Idx, CurTransform); + } + + // Update the generic properties for that instance if any + // TODO: Handle instance variations w/ Idx + UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, Idx); + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + { + CreatedInstancedComponent = InstancedActorComponent; + } + + return true; +} + +// Create or update a MSIC +bool +FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + const TArray& InInstancerMaterials) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniMeshSplitInstancerComponent* MeshSplitComponent = Cast(CreatedInstancedComponent); + if (!MeshSplitComponent || MeshSplitComponent->IsPendingKill()) + { + // If the mesh doesn't have LOD, we can use a regular ISMC + MeshSplitComponent = NewObject( + ComponentOuter, UHoudiniMeshSplitInstancerComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + MeshSplitComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!MeshSplitComponent) + return false; + + MeshSplitComponent->SetStaticMesh(InstancedStaticMesh); + MeshSplitComponent->SetOverrideMaterials(InInstancerMaterials); + + // Now add the instances + MeshSplitComponent->SetInstanceTransforms(InstancedObjectTransforms); + + // Check for instance colors + TArray InstanceColorOverrides; + bool ColorOverrideAttributeFound = false; + + // Look for the unreal_instance_color attribute on points + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_POINT, &AttributeInfo)) + { + ColorOverrideAttributeFound = AttributeInfo.exists; + } + + // Look for the unreal_instance_color attribute on prims? (why? original code) + if (!ColorOverrideAttributeFound) + { + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_PRIM, &AttributeInfo)) + { + ColorOverrideAttributeFound = AttributeInfo.exists; + } + } + + if (ColorOverrideAttributeFound) + { + if (AttributeInfo.tupleSize == 4) + { + // Allocate sufficient buffer for data. + InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)InstanceColorOverrides.GetData(), 0, AttributeInfo.count)) + { + InstanceColorOverrides.Empty(); + } + } + else if (AttributeInfo.tupleSize == 3) + { + // Allocate sufficient buffer for data. + TArray FloatValues; + FloatValues.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)FloatValues.GetData(), 0, AttributeInfo.count)) + { + + // Allocate sufficient buffer for data. + InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); + + // Convert float to FLinearColors + for (int32 ColorIdx = 0; ColorIdx < InstanceColorOverrides.Num(); ColorIdx++) + { + InstanceColorOverrides[ColorIdx].R = FloatValues[ColorIdx * AttributeInfo.tupleSize + 0]; + InstanceColorOverrides[ColorIdx].G = FloatValues[ColorIdx * AttributeInfo.tupleSize + 1]; + InstanceColorOverrides[ColorIdx].B = FloatValues[ColorIdx * AttributeInfo.tupleSize + 2]; + InstanceColorOverrides[ColorIdx].A = 1.0; + } + FloatValues.Empty(); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_COLOR " must be a float[4] or float[3] prim/point attribute")); + } + } + + // if we have vertex color overrides, apply them now +#if WITH_EDITOR + if (InstanceColorOverrides.Num() > 0) + { + // Convert the color attribute to FColor + TArray InstanceColors; + InstanceColors.SetNumUninitialized(InstanceColorOverrides.Num()); + for (int32 ix = 0; ix < InstanceColors.Num(); ++ix) + { + InstanceColors[ix] = InstanceColorOverrides[ix].GetClamped().ToFColor(false); + } + + // Apply them to the instances + TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); + for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) + { + UStaticMeshComponent* CurSMC = Instances[InstIndex]; + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + if (!InstanceColors.IsValidIndex(InstIndex)) + continue; + + MeshPaintHelpers::FillStaticMeshVertexColors(CurSMC, -1, InstanceColors[InstIndex], FColor::White); + + //CurSMC->UnregisterComponent(); + //CurSMC->ReregisterComponent(); + + { + // We're only changing instanced vertices on this specific mesh component, so we + // only need to detach our mesh component + FComponentReregisterContext ComponentReregisterContext(CurSMC); + for (auto& CurLODData : CurSMC->LODData) + { + BeginInitResource(CurLODData.OverrideVertexColors); + } + } + + //FIXME: How to get rid of the warning about fixup vertex colors on load? + //SMC->FixupOverrideColorsIfNecessary(); + } + } +#endif + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + // TODO: Optimize + // Loop on attributes first, then components, + // if failing to find the attrib on a component, skip the rest + if (AllPropertyAttributes.Num() > 0) + { + TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); + for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) + { + UStaticMeshComponent* CurSMC = Instances[InstIndex]; + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + for (const auto& CurrentAttrib : AllPropertyAttributes) + { + UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); + } + } + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = MeshSplitComponent; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UStaticMeshComponent* SMC = Cast(CreatedInstancedComponent); + if (!SMC || SMC->IsPendingKill()) + { + // Create a new StaticMeshComponent + SMC = NewObject( + ComponentOuter, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + SMC->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!SMC) + return false; + + SMC->SetStaticMesh(InstancedStaticMesh); + SMC->GetBodyInstance()->bAutoWeld = false; + + SMC->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + SMC->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances Transform + SMC->SetRelativeTransform(InstancedObjectTransforms[0]); + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + for (const auto& CurrentAttrib : AllPropertyAttributes) + { + UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, 0); + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = SMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( + UHoudiniStaticMesh* InstancedProxyStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + if (!InstancedProxyStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniStaticMeshComponent* HSMC = Cast(CreatedInstancedComponent); + if (!HSMC || HSMC->IsPendingKill()) + { + // Create a new StaticMeshComponent + HSMC = NewObject( + ComponentOuter, UHoudiniStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + HSMC->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!HSMC) + return false; + + HSMC->SetMesh(InstancedProxyStaticMesh); + + HSMC->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedProxyStaticMesh->GetNumStaticMaterials(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + HSMC->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances Transform + HSMC->SetRelativeTransform(InstancedObjectTransforms[0]); + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + for (const auto& CurrentAttrib : AllPropertyAttributes) + { + UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, 0); + } + + // Assign the new HSMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = HSMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + + +bool +FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( + UStaticMesh* InstancedStaticMesh, + UFoliageType* InFoliageType, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + // We need either a valid SM or a valid Foliage Type + if ((!InstancedStaticMesh || InstancedStaticMesh->IsPendingKill()) + && (!InFoliageType || InFoliageType->IsPendingKill())) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + AActor* OwnerActor = ParentComponent->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return false; + + // See if we already have a FoliageType for that static mesh + bool bCreatedNew = false; + UFoliageType *FoliageType = InFoliageType; + if (!FoliageType || FoliageType->IsPendingKill()) + { + // Foliage Type wasnt specified, only the mesh, try to find an existing foliage for that SM + FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InstancedStaticMesh); + } + else + { + // Foliage Type was specified, see if we can get its static mesh + UFoliageType_InstancedStaticMesh* FoliageISM = Cast(InFoliageType); + if (FoliageISM) + { + InstancedStaticMesh = FoliageISM->GetStaticMesh(); + } + + // See a component already exist on the actor + // If we cant find Foliage info for that foliage type, a new one will be created. + // when we call FindOrAddMesh + bCreatedNew = InstancedFoliageActor->FindInfo(FoliageType) == nullptr; + } + + if (!FoliageType || FoliageType->IsPendingKill()) + { + // We need to create a new FoliageType for this Static Mesh + // TODO: Add foliage default settings + InstancedFoliageActor->AddMesh(InstancedStaticMesh, &FoliageType); + bCreatedNew = true; + } + + if (!bCreatedNew && CreatedInstancedComponent) + { + // TODO: Shouldnt be needed anymore + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); + } + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if (!FoliageInfo) + return false; + + FTransform HoudiniAssetTransform = ParentComponent->GetComponentTransform(); + FFoliageInstance FoliageInstance; + int32 CurrentInstanceCount = 0; + for (auto CurrentTransform : InstancedObjectTransforms) + { + // Use our parent component for the base component of the instances, + // this will allow us to clean the instances by component + FoliageInstance.BaseComponent = ParentComponent; + + // TODO: FIX ME! + // Somehow, the first time when we create the Foliage type, instances need to be added with relative transform + // On subsequent cooks, they are actually expecting world transform + if (bCreatedNew) + { + FoliageInstance.Location = CurrentTransform.GetLocation(); + FoliageInstance.Rotation = CurrentTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D(); + } + else + { + FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); + FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); + } + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + CurrentInstanceCount++; + } + + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); + // TODO: This was due to a bug in UE4.22-20, check if still needed! + if (FoliageHISMC) + FoliageHISMC->BuildTreeIfOutdated(true, true); + + if (InstancerMaterial) + { + FoliageHISMC->OverrideMaterials.Empty(); + int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->StaticMaterials.Num() : 1; + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + FoliageHISMC->SetMaterial(Idx, InstancerMaterial); + } + + // Apply generic attributes if we have any + /* + // TODO: Handle variations w/ index + for (const auto& CurrentAttrib : AllPropertyAttributes) + { + UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); + } + */ + + // Try to aplly generic properties attributes + // either on the instancer, mesh or foliage type + // TODO: Use proper atIndex!! + UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, 0); + + if (bCreatedNew && FoliageHISMC) + CreatedInstancedComponent = FoliageHISMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::HapiGetInstanceTransforms( + const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancerUnrealTransforms) +{ + // Get the instance transforms + int32 PointCount = InHGPO.PartInfo.PointCount; + if (PointCount <= 0) + return false; + + TArray InstanceTransforms; + InstanceTransforms.SetNum(PointCount); + for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++) + FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx])); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetInstanceTransformsOnPart( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, HAPI_SRT, + &InstanceTransforms[0], 0, PointCount)) + { + InstanceTransforms.SetNum(0); + + // TODO: Warning? error? + return false; + } + + // Convert the transform to Unreal's coordinate system + OutInstancerUnrealTransforms.SetNumZeroed(InstanceTransforms.Num()); + for (int32 InstanceIdx = 0; InstanceIdx < InstanceTransforms.Num(); InstanceIdx++) + { + const auto& InstanceTransform = InstanceTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, OutInstancerUnrealTransforms[InstanceIdx]); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::GetGenericPropertiesAttributes( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutPropertyAttributes) +{ + // List all the generic property detail attributes ... + int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + + // .. then get all the values for the primitive property attributes + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, -1); + + // .. then finally, all values for point uproperty attributes + // TODO: !! get the correct Index here? + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, -1); + + return FoundCount > 0; +} + +bool +FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes, const int32& AtIndex) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current property for the given instance index + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute, AtIndex)) + continue; + + // Success! + NumSuccess++; + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, InObject->GetClass() ? *InObject->GetClass()->GetName() : TEXT("Object"), *InObject->GetName()); + } + + return (NumSuccess > 0); +} + +bool +FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent) +{ + if (!InComponent || InComponent->IsPendingKill()) + return false; + + UFoliageInstancedStaticMeshComponent* FISMC = Cast(InComponent); + if (FISMC && !FISMC->IsPendingKill()) + { + // Make sure foliage our foliage instances have been removed + USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); + if (ParentComponent && !ParentComponent->IsPendingKill()) + CleanupFoliageInstances(FISMC, ParentComponent); + + // do not delete FISMC that still have instances left + // as we have cleaned up our instances before, these have been hand-placed + if (FISMC->GetInstanceCount() > 0) + return false; + } + + USceneComponent* SceneComponent = Cast(InComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + return true; + } + + return false; +} + + +bool +FHoudiniInstanceTranslator::GetMaterialOverridesFromAttributes( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutMaterialAttributes) +{ + HAPI_AttributeInfo MaterialAttributeInfo; + FHoudiniApi::AttributeInfo_Init(&MaterialAttributeInfo); + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL, MaterialAttributeInfo, OutMaterialAttributes); + + /* + // TODO: Support material instances on instancers... + // see FHoudiniMaterialTranslator::CreateMaterialInstances() + // If material attribute and fallbacks were not found, check the material instance attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, MaterialAttributeInfo, OutMaterialAttributes); + } + */ + + if (!MaterialAttributeInfo.exists + /*&& MaterialAttributeInfo.owner != HAPI_ATTROWNER_PRIM + && MaterialAttributeInfo.owner != HAPI_ATTROWNER_DETAIL*/) + { + //HOUDINI_LOG_WARNING(TEXT("Instancer: the unreal_material attribute must be a primitive or detail attribute, ignoring the attribute.")); + OutMaterialAttributes.Empty(); + return false; + } + + return true; +} + +bool +FHoudiniInstanceTranslator::GetInstancerMaterials( + const TArray& MaterialAttributes, TArray& OutInstancerMaterials) +{ + // Use a map to avoid attempting to load the object for each instance + TMap MaterialMap; + + bool bHasValidMaterial = false; + for (auto& CurrentMatString : MaterialAttributes) + { + UMaterialInterface* CurrentMaterialInterface = nullptr; + UMaterialInterface** FoundMaterial = MaterialMap.Find(CurrentMatString); + if (!FoundMaterial) + { + // See if we can find a material interface that matches the attribute + CurrentMaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentMatString, nullptr, LOAD_NoWarn, nullptr)); + + // Check validity + if (!CurrentMaterialInterface || CurrentMaterialInterface->IsPendingKill()) + CurrentMaterialInterface = nullptr; + else + bHasValidMaterial = true; + + // Add what we found to the material map to avoid unnecessary loads + MaterialMap.Add(CurrentMatString, CurrentMaterialInterface); + } + else + { + // Reuse what we previously found + CurrentMaterialInterface = *FoundMaterial; + } + + OutInstancerMaterials.Add(CurrentMaterialInterface); + } + + // IF we couldn't find at least one valid material interface, empty the array + if (!bHasValidMaterial) + OutInstancerMaterials.Empty(); + + return true; +} + +bool +FHoudiniInstanceTranslator::GetInstancerMaterials( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutInstancerMaterials) +{ + TArray MaterialAttributes; + if (!GetMaterialOverridesFromAttributes(InGeoNodeId, InPartId, MaterialAttributes)) + MaterialAttributes.Empty(); + + return GetInstancerMaterials(MaterialAttributes, OutInstancerMaterials); +} + +bool +FHoudiniInstanceTranslator::GetVariationMaterials( + FHoudiniInstancedOutput* InInstancedOutput , const int32& InVariationIndex, + const TArray& InInstancerMaterials, TArray& OutVariationMaterials) +{ + if (!InInstancedOutput || InInstancerMaterials.Num() <= 0) + return false; + + // TODO: This also need to be improved and wont work 100%!! + // Use the instancedoutputs original object index? + if(!InInstancedOutput->VariationObjects.IsValidIndex(InVariationIndex)) + return false; + /* + // No variations, reuse the array + if (InInstancedOutput->VariationObjects.Num() == 1) + { + OutVariationMaterials = InInstancerMaterials; + return true; + } + */ + + if (InInstancedOutput->TransformVariationIndices.Num() == InInstancerMaterials.Num()) + { + for (int32 Idx = 0; Idx < InInstancedOutput->TransformVariationIndices.Num(); Idx++) + { + int32 VariationAssignment = InInstancedOutput->TransformVariationIndices[Idx]; + if (VariationAssignment != InVariationIndex) + continue; + + OutVariationMaterials.Add(InInstancerMaterials[Idx]); + } + } + else + { + if (InInstancerMaterials.IsValidIndex(InVariationIndex)) + OutVariationMaterials.Add(InInstancerMaterials[InVariationIndex]); + else + OutVariationMaterials.Add(InInstancerMaterials[0]); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::IsSplitInstancer(const int32& InGeoId, const int32& InPartId) +{ + bool bSplitMeshInstancer = false; + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; + bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); + + if (!bSplitMeshInstancer) + { + // Try on primitive + Owner = HAPI_ATTROWNER_PRIM; + bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); + } + + if (!bSplitMeshInstancer) + return false; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, + Owner, &AttributeInfo), false); + + if (!AttributeInfo.exists || AttributeInfo.count <= 0) + return false; + + TArray IntData; + // Allocate sufficient buffer for data. + IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, + &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); + + return (IntData[0] != 0); +} + +bool +FHoudiniInstanceTranslator::IsFoliageInstancer(const int32& InGeoId, const int32& InPartId) +{ + bool bIsFoliageInstancer = false; + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + + if (!bIsFoliageInstancer) + { + // Try on primitive + Owner = HAPI_ATTROWNER_PRIM; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + } + + if (!bIsFoliageInstancer) + { + // Finally, try on points + Owner = HAPI_ATTROWNER_POINT; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + } + + if (!bIsFoliageInstancer) + return false; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, + Owner, &AttributeInfo), false); + + if (!AttributeInfo.exists || AttributeInfo.count <= 0) + return false; + + // We only support int/float attributes + if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + TArray IntData; + // Allocate sufficient buffer for data. + IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, + &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); + + return (IntData[0] != 0); + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + TArray FloatData; + // Allocate sufficient buffer for data. + FloatData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, + &AttributeInfo, 0, &FloatData[0], 0, AttributeInfo.count), false); + + return (FloatData[0] != 0); + } + + return false; +} + + +AActor* +FHoudiniInstanceTranslator::SpawnInstanceActor(const FTransform& InTransform, ULevel* InSpawnLevel, UHoudiniInstancedActorComponent* InIAC) +{ + if (!InIAC || InIAC->IsPendingKill()) + return nullptr; + + UObject* InstancedObject = InIAC->GetInstancedObject(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + return nullptr; + + AActor* NewActor = nullptr; + +#if WITH_EDITOR + // Try to spawn a new actor for the given transform + GEditor->ClickLocation = InTransform.GetTranslation(); + GEditor->ClickPlane = FPlane(GEditor->ClickLocation, FVector::UpVector); + + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(InSpawnLevel, InstancedObject, false, RF_Transactional, nullptr); + if (NewActors.Num() > 0) + { + if (NewActors[0] && !NewActors[0]->IsPendingKill()) + { + NewActor = NewActors[0]; + } + } +#endif + + // Make sure that the actor was spawned in the proper level + FHoudiniEngineUtils::MoveActorToLevel(NewActor, InSpawnLevel); + + return NewActor; +} + + +void +FHoudiniInstanceTranslator::CleanupFoliageInstances(/*const FHoudiniInstancedOutput& InInstancedOutput,*/ UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, USceneComponent* InParentComponent) +{ + if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) + return; + + UStaticMesh* FoliageSM = InFoliageHISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + return; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(InFoliageHISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return; + + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + if (!FoliageType || FoliageType->IsPendingKill()) + return; + + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(InParentComponent, FoliageType); + + // Remove the foliage type if it doesn't have any more instances + if(InFoliageHISMC->GetInstanceCount() == 0) + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + + return; +} + + +FString +FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(UObject* InObject) +{ + USceneComponent* InComponent = Cast(InObject); + + FString InstancerType = TEXT("Instancer"); + if (InComponent && !InComponent->IsPendingKill()) + { + if (InComponent->IsA()) + { + InstancerType = TEXT("(Split Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Actor Instancer)"); + } + else if (InComponent->IsA()) + { + if (InComponent->GetOwner() && InComponent->GetOwner()->IsA()) + InstancerType = TEXT("(Foliage Instancer)"); + else + InstancerType = TEXT("(Hierarchical Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Mesh Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Static Mesh Component)"); + } + } + + return InstancerType; +} + +bool +FHoudiniInstanceTranslator::GetInstancerSplitAttributesAndValues( + const int32& InGeoId, + const int32& InPartId, + const HAPI_AttributeOwner& InSplitAttributeOwner, + FString& OutSplitAttributeName, + TArray& OutAllSplitAttributeValues) +{ + // See if the user has specified an attribute to split the instancers. + bool bHasSplitAttribute = false; + //FString SplitAttribName = FString(); + OutSplitAttributeName = FString(); + + // Look for the unreal_split_attr attribute + // This attribute indicates the name of the point attribute that we'll use to split the instances further + HAPI_AttributeInfo SplitAttribInfo; + FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); + + TArray StringData; + bHasSplitAttribute = FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_SPLIT_ATTR, SplitAttribInfo, StringData, 1); + + if (!bHasSplitAttribute || !SplitAttribInfo.exists || StringData.Num() <= 0) + return false; + + OutSplitAttributeName = StringData[0]; + + // We have specified a split attribute, try to get its values. + OutAllSplitAttributeValues.Empty(); + if (!OutSplitAttributeName.IsEmpty()) + { + //HAPI_AttributeInfo SplitAttribInfo; + FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); + bool bSplitAttrFound = FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, + InPartId, + TCHAR_TO_ANSI(*OutSplitAttributeName), + SplitAttribInfo, + OutAllSplitAttributeValues, + 1, + InSplitAttributeOwner); + + if (!bSplitAttrFound || OutAllSplitAttributeValues.Num() <= 0) + { + // We couldn't properly get the point values, clean up everything + // to ensure that we'll ignore the split attribute + bHasSplitAttribute = false; + OutAllSplitAttributeValues.Empty(); + OutSplitAttributeName = FString(); + } + } + + return bHasSplitAttribute; +} + +bool +FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId) +{ + bool bHISM = false; + HAPI_AttributeInfo AttriInfo; + FHoudiniApi::AttributeInfo_Init(&AttriInfo); + TArray IntData; + IntData.Empty(); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM, AttriInfo, IntData, 1)) + { + if (IntData.Num() > 0) + bHISM = IntData[0] == 1; + } + + return bHISM; +} + +void FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() +{ + NumInstancedTransformsPerObject.Empty(); + OriginalInstancedTransformsFlat.Empty(); + for (const TArray& Transforms : OriginalInstancedTransforms) + { + NumInstancedTransformsPerObject.Add(Transforms.Num()); + OriginalInstancedTransformsFlat.Append(Transforms); + } + + OriginalInstanceObjectPackagePaths.Empty(); + for (const UObject* Obj : OriginalInstancedObjects) + { + if (IsValid(Obj)) + { + OriginalInstanceObjectPackagePaths.Add(Obj->GetPathName()); + } + else + { + OriginalInstanceObjectPackagePaths.Add(FString()); + } + } +} + +void FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() +{ + const int32 NumObjects = NumInstancedTransformsPerObject.Num(); + OriginalInstancedTransforms.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) + { + TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; + const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; + for (int32 Index = 0; Index < NumInstances; ++Index) + { + Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumInstances; + } + + OriginalInstancedObjects.Empty(); + for (const FString& PackageFullPath : OriginalInstanceObjectPackagePaths) + { + FString PackagePath; + FString PackageName; + const bool bDidSplit = PackageFullPath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bDidSplit) + PackagePath = PackageFullPath; + + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!IsValid(Package)) + { + // Editor might have picked up the package yet, try to load it + Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); + } + if (IsValid(Package)) + { + OriginalInstancedObjects.Add(FindObject(Package, *PackageName)); + } + else + { + OriginalInstancedObjects.Add(nullptr); + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h index 91d70f84b..92af07f8d 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h @@ -1,363 +1,363 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniOutput.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniGenericAttribute.h" - -#include "HoudiniInstanceTranslator.generated.h" - -class UStaticMesh; -class UFoliageType; -class UHoudiniStaticMesh; -class UHoudiniInstancedActorComponent; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes -{ -public: - - GENERATED_BODY() - - // level path attribute value - UPROPERTY() - FString LevelPath; - - // Bake actor name attribute value - UPROPERTY() - FString BakeActorName; - - // bake outliner folder attribute value - UPROPERTY() - FString BakeOutlinerFolder; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData -{ -public: - - GENERATED_BODY() - - UPROPERTY() - bool bForceHISM; - - UPROPERTY() - TArray OriginalInstancedObjects; - - UPROPERTY() - TArray OriginalInstanceObjectPackagePaths; - - TArray> OriginalInstancedTransforms; - - UPROPERTY() - TArray NumInstancedTransformsPerObject; - - UPROPERTY() - TArray OriginalInstancedTransformsFlat; - - UPROPERTY() - FString SplitAttributeName; - - UPROPERTY() - TArray SplitAttributeValues; - - UPROPERTY() - bool bSplitMeshInstancer; - - UPROPERTY() - bool bIsFoliageInstancer; - - UPROPERTY() - TArray AllPropertyAttributes; - - // All level path attributes from the first attribute owner we could find - UPROPERTY() - TArray AllLevelPaths; - - // All bake actor name attributes from the first attribute owner we could find - UPROPERTY() - TArray AllBakeActorNames; - - // All bake outliner folder attributes from the first attribute owner we could find - UPROPERTY() - TArray AllBakeOutlinerFolders; - - // A map of split value to attribute values that are valid per split (unreal_bake_actor, unreal_level_path, - // unreal_bake_outliner_folder) - UPROPERTY() - TMap PerSplitAttributes; - - UPROPERTY() - TArray OutputNames; - - UPROPERTY() - TArray TileValues; - - UPROPERTY() - TArray MaterialAttributes; - - void BuildFlatInstancedTransformsAndObjectPaths(); - - void BuildOriginalInstancedTransformsAndObjectArrays(); -}; - -struct HOUDINIENGINE_API FHoudiniInstanceTranslator -{ - public: - - static bool PopulateInstancedOutputPartData( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); - - static bool CreateAllInstancersFromHoudiniOutput( - UHoudiniOutput* InOutput, - const TArray& InAllOutputs, - UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); - - static bool GetInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValues, - TMap& OutPerSplitAttributes); - - static bool GetPackedPrimitiveInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes); - - static bool GetAttributeInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes); - - static bool GetOldSchoolAttributeInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms); - - static bool GetObjectInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms); - - // Updates the variations array using the instanced outputs - static void UpdateInstanceVariationObjects( - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TArray& InOriginalObjects, - const TArray>& InOriginalTransforms, - TMap& InstancedOutputs, - TArray>& OutVariationsInstancedObjects, - TArray>& OutVariationsInstancedTransforms, - TArray& OutVariationOriginalObjectIdx, - TArray& OutVariationIndices); - - // Recreates the components after an instanced outputs has been changed - static bool UpdateChangedInstancedOutput( - FHoudiniInstancedOutput& InInstancedOutput, - const FHoudiniOutputObjectIdentifier& OutputIdentifier, - UHoudiniOutput* InParentOutput, - USceneComponent* InParentComponent); - - // Recomputes the variation assignements for a given instanced output - static void UpdateVariationAssignements( - FHoudiniInstancedOutput& InstancedOutput); - - // Extracts the final transforms (with the transform offset applied) for a given variation - static void ProcessInstanceTransforms( - FHoudiniInstancedOutput& InstancedOutput, - const int32& VariationIdx, - TArray& OutProcessedTransforms); - - // Creates a new component or updates the previous one if possible - static bool CreateOrUpdateInstanceComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent* OldComponent, - USceneComponent*& NewComponent, - const bool& InIsSplitMeshInstancer, - const bool& InIsFoliageInstancer, - const TArray& InstancerMaterials, - const int32& InstancerObjectIdx = 0, - const bool& bForceHISM = false); - - // Create or update an ISMC / HISMC - static bool CreateOrUpdateInstancedStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr, - const bool& bForceHISM = false); - - // Create or update an IAC - static bool CreateOrUpdateInstancedActorComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent); - - // Create or update a MeshSplitInstancer - static bool CreateOrUpdateMeshSplitInstancerComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - const TArray& InstancerMaterials); - - // Create or update a StaticMeshComponent (when we have only one instance) - static bool CreateOrUpdateStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr); - - // Create or update a HoudiniStaticMeshComponent (when we have only one instance) - static bool CreateOrUpdateHoudiniStaticMeshComponent( - UHoudiniStaticMesh* InstancedProxyStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr); - - // Create or update a Foliage instances - static bool CreateOrUpdateFoliageInstances( - UStaticMesh* InstancedStaticMesh, - UFoliageType* InFoliageType, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/); - - // Helper fumction to properly remove/destroy a component - static bool RemoveAndDestroyComponent( - UObject* InComponent); - - // Utility function - // Fetches instance transforms and convert them to ue4 coordinates - static bool HapiGetInstanceTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancerUnrealTransforms); - - // Helper function used to spawn a new Actor for UHoudiniInstancedActorComponent - // Relies on editor-only functionalities, so this function is not on the IAC itself - static AActor* SpawnInstanceActor( - const FTransform& InTransform, - ULevel* InSpawnLevel, - UHoudiniInstancedActorComponent* InIAC); - - // Helper functions for generic property attributes - static bool GetGenericPropertiesAttributes( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, - const TArray& InAllPropertyAttributes, - const int32& AtIndex); - - static bool GetMaterialOverridesFromAttributes( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutMaterialAttributes); - - static bool GetInstancerMaterials( - const TArray& MaterialAttributes, - TArray& OutInstancerMaterials); - - static bool GetInstancerMaterials( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutInstancerMaterials); - - static bool GetVariationMaterials( - FHoudiniInstancedOutput* InInstancedOutput, - const int32& InVariationIndex, - const TArray& InInstancerMaterials, - TArray& OutVariationMaterials); - - static bool IsSplitInstancer( - const int32& InGeoId, - const int32& InPartId); - - static bool IsFoliageInstancer( - const int32& InGeoId, - const int32& InPartId); - - static void CleanupFoliageInstances( - UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, - USceneComponent* InParentComponent); - - static FString GetInstancerTypeFromComponent( - UObject* InComponent); - - // Returns the name and values of the attribute that has been specified to split the instances - // returns false if the attribute is invalid or hasn't been specified - static bool GetInstancerSplitAttributesAndValues( - const int32& InGeoId, - const int32& InPartId, - const HAPI_AttributeOwner& InSplitAttributeOwner, - FString& OutSplitAttributeName, - TArray& OutAllSplitAttributeValues); - - // Get if force using HISM from attribute - static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniOutput.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniGenericAttribute.h" + +#include "HoudiniInstanceTranslator.generated.h" + +class UStaticMesh; +class UFoliageType; +class UHoudiniStaticMesh; +class UHoudiniInstancedActorComponent; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes +{ +public: + + GENERATED_BODY() + + // level path attribute value + UPROPERTY() + FString LevelPath; + + // Bake actor name attribute value + UPROPERTY() + FString BakeActorName; + + // bake outliner folder attribute value + UPROPERTY() + FString BakeOutlinerFolder; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData +{ +public: + + GENERATED_BODY() + + UPROPERTY() + bool bForceHISM; + + UPROPERTY() + TArray OriginalInstancedObjects; + + UPROPERTY() + TArray OriginalInstanceObjectPackagePaths; + + TArray> OriginalInstancedTransforms; + + UPROPERTY() + TArray NumInstancedTransformsPerObject; + + UPROPERTY() + TArray OriginalInstancedTransformsFlat; + + UPROPERTY() + FString SplitAttributeName; + + UPROPERTY() + TArray SplitAttributeValues; + + UPROPERTY() + bool bSplitMeshInstancer; + + UPROPERTY() + bool bIsFoliageInstancer; + + UPROPERTY() + TArray AllPropertyAttributes; + + // All level path attributes from the first attribute owner we could find + UPROPERTY() + TArray AllLevelPaths; + + // All bake actor name attributes from the first attribute owner we could find + UPROPERTY() + TArray AllBakeActorNames; + + // All bake outliner folder attributes from the first attribute owner we could find + UPROPERTY() + TArray AllBakeOutlinerFolders; + + // A map of split value to attribute values that are valid per split (unreal_bake_actor, unreal_level_path, + // unreal_bake_outliner_folder) + UPROPERTY() + TMap PerSplitAttributes; + + UPROPERTY() + TArray OutputNames; + + UPROPERTY() + TArray TileValues; + + UPROPERTY() + TArray MaterialAttributes; + + void BuildFlatInstancedTransformsAndObjectPaths(); + + void BuildOriginalInstancedTransformsAndObjectArrays(); +}; + +struct HOUDINIENGINE_API FHoudiniInstanceTranslator +{ + public: + + static bool PopulateInstancedOutputPartData( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); + + static bool CreateAllInstancersFromHoudiniOutput( + UHoudiniOutput* InOutput, + const TArray& InAllOutputs, + UObject* InOuterComponent, + const TMap* InPreBuiltInstancedOutputPartData=nullptr); + + static bool GetInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValues, + TMap& OutPerSplitAttributes); + + static bool GetPackedPrimitiveInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes); + + static bool GetAttributeInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes); + + static bool GetOldSchoolAttributeInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms); + + static bool GetObjectInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms); + + // Updates the variations array using the instanced outputs + static void UpdateInstanceVariationObjects( + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TArray& InOriginalObjects, + const TArray>& InOriginalTransforms, + TMap& InstancedOutputs, + TArray>& OutVariationsInstancedObjects, + TArray>& OutVariationsInstancedTransforms, + TArray& OutVariationOriginalObjectIdx, + TArray& OutVariationIndices); + + // Recreates the components after an instanced outputs has been changed + static bool UpdateChangedInstancedOutput( + FHoudiniInstancedOutput& InInstancedOutput, + const FHoudiniOutputObjectIdentifier& OutputIdentifier, + UHoudiniOutput* InParentOutput, + USceneComponent* InParentComponent); + + // Recomputes the variation assignements for a given instanced output + static void UpdateVariationAssignements( + FHoudiniInstancedOutput& InstancedOutput); + + // Extracts the final transforms (with the transform offset applied) for a given variation + static void ProcessInstanceTransforms( + FHoudiniInstancedOutput& InstancedOutput, + const int32& VariationIdx, + TArray& OutProcessedTransforms); + + // Creates a new component or updates the previous one if possible + static bool CreateOrUpdateInstanceComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent* OldComponent, + USceneComponent*& NewComponent, + const bool& InIsSplitMeshInstancer, + const bool& InIsFoliageInstancer, + const TArray& InstancerMaterials, + const int32& InstancerObjectIdx = 0, + const bool& bForceHISM = false); + + // Create or update an ISMC / HISMC + static bool CreateOrUpdateInstancedStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr, + const bool& bForceHISM = false); + + // Create or update an IAC + static bool CreateOrUpdateInstancedActorComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent); + + // Create or update a MeshSplitInstancer + static bool CreateOrUpdateMeshSplitInstancerComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + const TArray& InstancerMaterials); + + // Create or update a StaticMeshComponent (when we have only one instance) + static bool CreateOrUpdateStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr); + + // Create or update a HoudiniStaticMeshComponent (when we have only one instance) + static bool CreateOrUpdateHoudiniStaticMeshComponent( + UHoudiniStaticMesh* InstancedProxyStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr); + + // Create or update a Foliage instances + static bool CreateOrUpdateFoliageInstances( + UStaticMesh* InstancedStaticMesh, + UFoliageType* InFoliageType, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/); + + // Helper fumction to properly remove/destroy a component + static bool RemoveAndDestroyComponent( + UObject* InComponent); + + // Utility function + // Fetches instance transforms and convert them to ue4 coordinates + static bool HapiGetInstanceTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancerUnrealTransforms); + + // Helper function used to spawn a new Actor for UHoudiniInstancedActorComponent + // Relies on editor-only functionalities, so this function is not on the IAC itself + static AActor* SpawnInstanceActor( + const FTransform& InTransform, + ULevel* InSpawnLevel, + UHoudiniInstancedActorComponent* InIAC); + + // Helper functions for generic property attributes + static bool GetGenericPropertiesAttributes( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, + const TArray& InAllPropertyAttributes, + const int32& AtIndex); + + static bool GetMaterialOverridesFromAttributes( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutMaterialAttributes); + + static bool GetInstancerMaterials( + const TArray& MaterialAttributes, + TArray& OutInstancerMaterials); + + static bool GetInstancerMaterials( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutInstancerMaterials); + + static bool GetVariationMaterials( + FHoudiniInstancedOutput* InInstancedOutput, + const int32& InVariationIndex, + const TArray& InInstancerMaterials, + TArray& OutVariationMaterials); + + static bool IsSplitInstancer( + const int32& InGeoId, + const int32& InPartId); + + static bool IsFoliageInstancer( + const int32& InGeoId, + const int32& InPartId); + + static void CleanupFoliageInstances( + UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, + USceneComponent* InParentComponent); + + static FString GetInstancerTypeFromComponent( + UObject* InComponent); + + // Returns the name and values of the attribute that has been specified to split the instances + // returns false if the attribute is invalid or hasn't been specified + static bool GetInstancerSplitAttributesAndValues( + const int32& InGeoId, + const int32& InPartId, + const HAPI_AttributeOwner& InSplitAttributeOwner, + FString& OutSplitAttributeName, + TArray& OutAllSplitAttributeValues); + + // Get if force using HISM from attribute + static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp index 3333e2c0f..c991fae05 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp @@ -1,3672 +1,3673 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#include "HoudiniLandscapeTranslator.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniEngineString.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPackageParams.h" -#include "HoudiniStringResolver.h" -#include "HoudiniInput.h" - -#include "ObjectTools.h" -#include "FileHelpers.h" -#include "Editor.h" -#include "LandscapeLayerInfoObject.h" -#include "LandscapeStreamingProxy.h" -#include "LandscapeInfo.h" -#include "LandscapeEdit.h" -#include "AssetRegistryModule.h" -#include "PackageTools.h" -#include "PhysicalMaterials/PhysicalMaterial.h" -#include "UObject/UnrealType.h" - -#include "GameFramework/WorldSettings.h" -#include "HAL/PlatformFilemanager.h" -#include "Misc/Paths.h" -#include "Engine/LevelStreamingDynamic.h" -#include "Modules/ModuleManager.h" -#include "AssetToolsModule.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "LevelUtils.h" -#include "Factories/WorldFactory.h" -#include "Misc/Guid.h" -#include "Engine/LevelBounds.h" - -#include "HAL/IConsoleManager.h" -#include "Engine/AssetManager.h" -#include "Engine/LevelStreamingAlwaysLoaded.h" -#include "LandscapeEditor/Private/LandscapeEdMode.h" -#include "Misc/AssetRegistryInterface.h" -#include "Misc/StringFormatArg.h" -#include "Engine/WorldComposition.h" - -#if WITH_EDITOR - #include "LandscapeEditorModule.h" - #include "LandscapeFileFormatInterface.h" - #include "EditorLevelUtils.h" - #include "WorldBrowserModule.h" - #include "EditorLevelUtils.h" - #include "Misc/WorldCompositionUtility.h" -#endif - -static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( - TEXT("HoudiniEngine.ExportLandscapeTextures"), - 0, - TEXT("If enabled, landscape layers and heightmap will be exported as textures in the temp directory when converting a Heightfield to a Landscape.\n") - TEXT("0: Disabled\n") - TEXT("1: Enabled\n") -); - -typedef FHoudiniEngineUtils FHUtils; - -bool -FHoudiniLandscapeTranslator::CreateLandscape( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedOutputs, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* InWorld, // Persistent / root world for the landscape - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniPackageParams InPackageParams, - TArray& OutCreatedPackages -) -{ - check(LayerMinimums.Contains(TEXT("height"))); - check(LayerMaximums.Contains(TEXT("height"))); - - float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); - float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); - - if (!InOutput || InOutput->IsPendingKill()) - return false; - - // Get the height map. - const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); - if (!Heightfield) - return false; - - if (Heightfield->Type != EHoudiniPartType::Volume) - return false; - - const HAPI_NodeId GeoId = Heightfield->GeoId; - const HAPI_PartId PartId = Heightfield->PartId; - - // Construct the identifier of the Heightfield geo part. - FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); - HeightfieldIdentifier.PartName = Heightfield->PartName; - - FString NodeNameSuffix = GetActorNameSuffix(InPackageParams.PackageMode); - - TArray IntData; - TArray StrData; - // Output attributes will be stored on the Output object and will be used again during baking to determine - // where content should be baked to and what they should be named, etc. - // At the end of this function, the output attributes and tokens will be copied to the output object. - TMap OutputAttributes; - TMap OutputTokens; - FHoudiniAttributeResolver Resolver; - InPackageParams.UpdateTokensFromParams(InWorld, OutputTokens); - - bool bHasTile = Heightfield->VolumeTileIndex >= 0; - - // --------------------------------------------- - // Attribute: unreal_landscape_tile_actor_type, unreal_landscape_streaming_proxy (v1) - // --------------------------------------------- - // Determine the actor type for the tile - bool bCreateLandscapeStreamingProxy = false; - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - LandscapeActorType TileActorType = LandscapeActorType::LandscapeActor; - IntData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, AttributeInfo, IntData, 1)) - { - if (IntData.Num() > 0) - { - TileActorType = static_cast(IntData[0]); - } - } - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY, AttributeInfo, IntData, 1)) - { - if (IntData.Num() > 0 && IntData[0] != 0) - TileActorType = LandscapeActorType::LandscapeStreamingProxy; - } - - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, FString::FromInt(static_cast(TileActorType))); - - // --------------------------------------------- - // Attribute: unreal_landscape_actor_name - // --------------------------------------------- - // Retrieve the name of the main Landscape actor to look for - FString SharedLandscapeActorName = DefaultLandscapeActorPrefix + "SharedLandscape"; // If this is an empty string, don't affirm a root landscape actor? - StrData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, AttributeInfo, StrData, 1)) - { - if (StrData.Num() > 0 && !StrData[0].IsEmpty()) - SharedLandscapeActorName = StrData[0]; - } - - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); - - // --------------------------------------------- - // Attribute: unreal_level_path - // --------------------------------------------- - // FString LevelPath = bHasTile ? "{world}/Landscape/Tile{tile}" : "{world}/Landscape"; - FString LevelPath; - TArray LevelPaths; - if (FHoudiniEngineUtils::GetLevelPathAttribute(GeoId, PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - LevelPath = LevelPaths[0]; - } - if (!LevelPath.IsEmpty()) - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); - - // --------------------------------------------- - // Attribute: unreal_output_name - // --------------------------------------------- - FString LandscapeTileActorName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; - TArray AllOutputNames; - if (!FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames)) - { - if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) - LandscapeTileActorName = AllOutputNames[0]; - } - OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); - - // Streaming proxy actors/tiles requires a "main" landscape actor - // that contains the shared landscape state. - bool bRequiresSharedLandscape = false; - if (TileActorType == LandscapeActorType::LandscapeStreamingProxy) - bRequiresSharedLandscape = true; - - // ---------------------------------- - // Inject landscape specific tokens - // ---------------------------------- - if (bHasTile) - { - const FString TileValue = FString::FromInt(Heightfield->VolumeTileIndex); - // Tile value needs to go into Output arguments to be available during the bake. - OutputTokens.Add(TEXT("tile"), TileValue); - } - - // ---------------------------------- - // Expand string arguments for various landscape naming aspects. - // ---------------------------------- - - // Update resolver attributes and tokens before we start resolving attributes. - Resolver.SetCachedAttributes(OutputAttributes); - Resolver.SetTokensFromStringMap(OutputTokens); - - SharedLandscapeActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); - SharedLandscapeActorName += NodeNameSuffix; - - LandscapeTileActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, LandscapeTileActorName); - LandscapeTileActorName += NodeNameSuffix; - - LevelPath = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); - - FString TileName = LandscapeTileActorName; - - // Note that relative level paths are always interpreted as relative to the default output directory (temp / bake). - // FString TilePackagePath = FPaths::Combine(DefaultOutputPath, LevelPath); - FString TilePackagePath = Resolver.ResolveFullLevelPath(); - - // This crashes UE if the package name does not resolve - //FString TileMapFileName = FPackageName::LongPackageNameToFilename(TilePackagePath, FPackageName::GetMapPackageExtension()); - - FText NotValidReason; - bool bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); - if (!bIsValidLongName) - { - // Try a more naive approach - TilePackagePath = FPaths::Combine(InPackageParams.BakeFolder, LevelPath); - bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); - } - - if (!bIsValidLongName) - { - HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] TilePackagePath is not a valid long name. Reason: %s"), *(NotValidReason.ToString())); - return false; - } - - FString TileMapFileName; - if (!FPackageName::TryConvertLongPackageNameToFilename(TilePackagePath, TileMapFileName, FPackageName::GetMapPackageExtension())) - { - // Rather stop here than crash! - HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] Failed to resolve the TilePackagePath: %s"), *(TilePackagePath)); - return false; - } - - // Find the package for both the world and the tile. - // The world should contain the main landscape actor while - // the tile will contain a Landscape, LandscapeProxy or LandscapeStreamingProxy depending on user settings. - - bool bTileisStreamingProxy = (TileActorType == LandscapeActorType::LandscapeStreamingProxy); - UWorld* TileWorld = nullptr; // World from which to spawn tile actor - ULevel* TileLevel = nullptr; // Level in which to spawn tile actor - ALandscapeProxy* TileActor = nullptr; // Spawned tile actor. - - // ---------------------------------- - // Update package parameters for this tile - // ---------------------------------- - - // NOTE: we don't manually inject a tile number in the object name. This should - // already be encoded in the TileName string. - FHoudiniPackageParams TilePackageParams = InPackageParams; - TilePackageParams.ObjectName = TileName; - - FHoudiniPackageParams LayerPackageParams = InPackageParams; - if (bRequiresSharedLandscape) - { - // Note that layers are shared amongst all the tiles for a given landscape. - LayerPackageParams.ObjectName = SharedLandscapeActorName; - } - else - { - // This landscape tile is a standalone landscape and should have its own material layers. - LayerPackageParams.ObjectName = TileName; - } - - // See if the current heightfield has an unreal_material or unreal_hole_material assigned to it - UMaterialInterface* LandscapeMaterial = nullptr; - UMaterialInterface* LandscapeHoleMaterial = nullptr; - UPhysicalMaterial* LandscapePhysicalMaterial = nullptr; - FHoudiniLandscapeTranslator::GetLandscapeMaterials(*Heightfield, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial); - - // Extract the float data from the Heightfield. - const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; - TArray FloatValues; - float FloatMin, FloatMax; - if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) - return false; - - // Heightfield conversions should always use the global float min/max - // since they need to be calculated externally, potentially across multiple tiles. - FloatMin = fGlobalMin; - FloatMax = fGlobalMax; - - // Get the Unreal landscape size - int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; - int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; - int32 UnrealTileSizeX = -1; - int32 UnrealTileSizeY = -1; - int32 NumSectionPerLandscapeComponent = -1; - int32 NumQuadsPerLandscapeSection = -1; - - if (!FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - HoudiniHeightfieldXSize, HoudiniHeightfieldYSize, - UnrealTileSizeX, UnrealTileSizeY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection)) - { - return false; - } - - // ---------------------------------------------------- - // Export of layer textures - // ---------------------------------------------------- - // Export textures, if enabled. Mostly used for debugging at the moment. - bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; - if (bExportTexture) - { - // Export raw height data to texture - FString TextureName = TilePackageParams.ObjectName + TEXT("_height_raw"); - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - HoudiniHeightfieldXSize, - HoudiniHeightfieldYSize, - FloatValues, - FloatMin, - FloatMax); - } - - // Look for all the layers/masks corresponding to the current heightfield. - TArray< const FHoudiniGeoPartObject* > FoundLayers; - FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); - - // Get the updated layers. - TArray LayerInfos; - - if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, - LayerMinimums, LayerMaximums, LayerInfos, false, - TilePackageParams, - LayerPackageParams, - OutCreatedPackages)) - return false; - - // Convert Houdini's heightfield data to Unreal's landscape data - TArray IntHeightData; - FTransform TileTransform; - if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( - FloatValues, VolumeInfo, - UnrealTileSizeX, UnrealTileSizeY, - FloatMin, FloatMax, - IntHeightData, TileTransform)) - return false; - - // ---------------------------------------------------- - // Property changes that we want to track - // ---------------------------------------------------- - - bool bModifiedLandscapeActor = false; - bool bModifiedSharedLandscapeActor = false; - bool bSharedLandscapeMaterialChanged = false; - bool bSharedLandscapeHoleMaterialChanged = false; - bool bSharedPhysicalMaterialChanged = false; - bool bTileLandscapeMaterialChanged = false; - bool bTileLandscapeHoleMaterialChanged = false; - bool bTilePhysicalMaterialChanged = false; - bool bCreatedMap = false; - bool bCreatedTileActor = false; - bool bHeightLayerDataChanged = false; - bool bCustomLayerDataChanged = false; - - // ---------------------------------------------------- - // Calculate Tile location and landscape offset - // ---------------------------------------------------- - FTransform LandscapeTransform, NewTileTransform; - FIntPoint TileLoc; - - // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate - // for any landscape shifts due to section base aligment offsets. - CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeTransform, NewTileTransform, TileLoc); - - // ---------------------------------------------------- - // Find or create *shared* landscape - // ---------------------------------------------------- - - ALandscape* SharedLandscapeActor = nullptr; - bool bCreatedSharedLandscape = false; - - if (bRequiresSharedLandscape) - { - // Streaming proxy tiles always require a "shared landscape" that contains the - // various landscape properties to be shared amongst all the tiles. - AActor* FoundActor = nullptr; - SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, SharedLandscapeActorName, FoundActor); - - bool bIsValidSharedLandscape = IsValid(SharedLandscapeActor); - - if (bIsValidSharedLandscape) - { - // We have a possible valid shared landscape. Check whether it is compatible with the Houdini volume. - bool bIsCompatible = IsLandscapeInfoCompatible( - SharedLandscapeActor->GetLandscapeInfo(), - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection); - bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); - if (!bIsCompatible) - { - // Current landscape actor is not compatible. Destroy it. - SharedLandscapeActor->Destroy(); - SharedLandscapeActor = nullptr; - bIsValidSharedLandscape = false; - } - } - - if (!bIsValidSharedLandscape) - { - // Create and configure the main landscape actor. - // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos - SharedLandscapeActor = InWorld->SpawnActor(); - if (SharedLandscapeActor) - { - CreatedUntrackedOutputs.Add( SharedLandscapeActor ); - - // NOTE that share landscape is always located at the origin, but not the tile actors. The - // tiles are properly transformed. - SharedLandscapeActor->SetActorTransform(LandscapeTransform); - // If we working with landscape tiles, this actor will become the "Main landscape" actor but - // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. - SharedLandscapeActor->bCanHaveLayersContent = false; - SharedLandscapeActor->ComponentSizeQuads = NumQuadsPerLandscapeSection*NumSectionPerLandscapeComponent; - SharedLandscapeActor->NumSubsections = NumSectionPerLandscapeComponent; - SharedLandscapeActor->SubsectionSizeQuads = NumQuadsPerLandscapeSection; - SharedLandscapeActor->SetLandscapeGuid( FGuid::NewGuid() ); - SharedLandscapeActor->bCastStaticShadow = false; - for (const auto& ImportLayerInfo : LayerInfos) - { - SharedLandscapeActor->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(ImportLayerInfo.LayerInfo)); - } - SharedLandscapeActor->CreateLandscapeInfo(); - bCreatedSharedLandscape = true; - - // Ensure the landscape actor name and label matches `LandscapeActorName`. - FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, SharedLandscapeActorName); - SharedLandscapeActor->MarkPackageDirty(); - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(InWorld->GetPathName()) ); - return false; - } - } - else - { - // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Reusing existing shared landscape...")); - } - } - - if (SharedLandscapeActor) - { - // Ensure the existing landscape actor transform is correct. - SharedLandscapeActor->SetActorTransform(LandscapeTransform); - - bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; - if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) - { - DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - } - - if (bSharedLandscapeMaterialChanged) - { - SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; - - } - if (bSharedLandscapeHoleMaterialChanged) - { - SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - - } - - bSharedPhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? (SharedLandscapeActor->DefaultPhysMaterial != LandscapePhysicalMaterial) : false; - if (bSharedPhysicalMaterialChanged) - { - DoPreEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); - SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - //SharedLandscapeActor->ChangedPhysMaterial(); - } - } - - // ---------------------------------------------------- - // Find Landscape actor / tile - // ---------------------------------------------------- - - // Find an actor with the given name. The TileWorld and TileLevel returned should be - // used to spawn the new actor, if the actor itself could not be found. - //bool bCreatedPackage = false; - // TileActor = FindExistingLandscapeActor( - // InWorld, InOutput, ValidLandscapes, - // UnrealLandscapeSizeX, UnrealLandscapeSizeY, LandscapeTileActorName, - // LevelPath, TileWorld, TileLevel, bCreatedPackage); - - // Currently the Temp Cook mode is not concerned with creating packages. This will, at the time of writing, - // exclusively be dealt with during Bake mode so don't bother with searching / creating other packages. - - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (IsValid(HAC)) - { - TileWorld = HAC->GetWorld(); - TileLevel = HAC->GetComponentLevel(); - } - else - { - TileWorld = InWorld; - TileLevel = InWorld->PersistentLevel; - } - - check(TileWorld); - check(TileLevel); - - AActor* FoundActor = nullptr; - if (InPackageParams.PackageMode == EPackageMode::Bake) - { - // When baking, See if we can find any landscape / proxy actors for this tile in the TileLevel. - // If we find any actors that match the name but not the type, or the actors are pending kill, then - // rename them so that we can spawn new actors. - switch (TileActorType) - { - case LandscapeActorType::LandscapeActor: - TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); - break; - case LandscapeActorType::LandscapeStreamingProxy: - TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); - break; - default: - TileActor = nullptr; - } - } - else - { - // In temp mode, only consider our previous output landscapes, - // or our input landscapes that have the "update input landscape" option enabled - ALandscapeProxy* FoundLandscapeProxy = nullptr; - - // Try to see if we have an input landscape that matches the size of the current HGPO - for (int nIdx = 0; nIdx < InputLandscapesToUpdate.Num(); nIdx++) - { - ALandscapeProxy* CurrentInputLandscape = InputLandscapesToUpdate[nIdx]; - if (!CurrentInputLandscape) - continue; - - ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); - if (!CurrentInfo) - continue; - - int32 InputMinX = 0; - int32 InputMinY = 0; - int32 InputMaxX = 0; - int32 InputMaxY = 0; - CurrentInfo->GetLandscapeExtent(InputMinX, InputMinY, InputMaxX, InputMaxY); - - // If the full size matches, we'll update that input landscape - bool SizeMatch = false; - if ((InputMaxX - InputMinX + 1) == UnrealTileSizeX && (InputMaxY - InputMinY + 1) == UnrealTileSizeY) - SizeMatch = true; - - // HF and landscape don't match, try another one - if (!SizeMatch) - continue; - - // Replace FoundLandscape by that input landscape - FoundLandscapeProxy = CurrentInputLandscape; - - // We've found a valid input landscape, remove it from the input array so we don't try to update it multiple times - InputLandscapesToUpdate.RemoveAt(nIdx); - break; - } - - if (!FoundLandscapeProxy) - { - // Try to see if we can reuse one of our previous output landscape. - // Keep track of the previous cook's landscapes - TMap& OldOutputObjects = InOutput->GetOutputObjects(); - for (auto& CurrentLandscape : OldOutputObjects) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentLandscape.Value.OutputObject); - if (!LandscapePtr) - continue; - - FoundLandscapeProxy = LandscapePtr->GetRawPtr(); - if (!FoundLandscapeProxy) - { - // We may need to manually load the object - //OldLandscapeProxy = LandscapePtr->GetSoftPtr().LoadSynchronous(); - FoundLandscapeProxy = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); - } - - if (!IsValid(FoundLandscapeProxy)) - continue; - - // We need to make sure that this landscape is not one of our input landscape - // This would happen if we were previously updating it, but just turned the option off - // In that case, the landscape would be in both our inputs and outputs, - // but with the "Update Input Data" option off - if (InAllInputLandscapes.Contains(FoundLandscapeProxy)) - { - FoundLandscapeProxy = nullptr; - continue; - } - - // If we found a possible candidate, make sure that its size matches ours as we can only update a landscape tile of the same size - if (!IsLandscapeTileCompatible( - FoundLandscapeProxy, - UnrealTileSizeX-1, - UnrealTileSizeY-1, - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection)) - { - FoundLandscapeProxy = nullptr; - continue; - } - - if (SharedLandscapeActor) - { - if (FoundLandscapeProxy->GetLandscapeGuid() != SharedLandscapeActor->GetLandscapeGuid()) - { - FoundLandscapeProxy = nullptr; - continue; - } - } - - // TODO: we probably need to do some more checks with tiled landscapes as well? - - // We found a valid Candidate! - if (FoundLandscapeProxy) - break; - } - } - - if (IsValid(FoundLandscapeProxy)) - { - TileActor = FoundLandscapeProxy; - if (TileActor->GetName() != LandscapeTileActorName) - { - // Ensure the TileActor is named correctly - FHoudiniEngineUtils::SafeRenameActor(TileActor, LandscapeTileActorName); - } - } - } - - // NOTE: We don't need to delete old landscape tiles (FoundActor != TileActor) here. That is an old - // output that should get cleaned up automatically. - - if (IsValid(TileActor)) - { - check(!(TileActor->IsPendingKill())); - - // ---------------------------------------------------- - // Check landscape compatibility - // ---------------------------------------------------- - - bool bIsCompatible = IsLandscapeTileCompatible( - TileActor, - UnrealTileSizeX-1, - UnrealTileSizeY-1, - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection); - - bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(TileActor, TileActorType); - - if (!bIsCompatible) - { - // Can't reuse this tile actor since the landscape dimensions doesn't match or the actor type has changed. - if (TileActor->IsA()) - { - // This landscape tile needs to be unregistered from the landscape info. - ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); - if (IsValid(LandscapeInfo)) - { - LandscapeInfo->UnregisterActor(TileActor); - } - } - TileActor->Destroy(); - TileActor = nullptr; - } - } - - // ---------------------------------------------------- - // Create or update landscape / tile. - // ---------------------------------------------------- - // Note that a single heightfield generated in Houdini can be treated - // as either a landscape tile (LandscapeStreamingProxy) or a standalone - // landscape (ALandscape). This determination is made purely from user specified - // attributes. No "clever logic" in here, please! - - ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; - ALandscape* CachedLandscapeActor = nullptr; - ULandscapeInfo *LandscapeInfo; - - - if (!TileActor) - { - // Create a new Landscape tile in the TileWorld - TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( - IntHeightData, LayerInfos, TileTransform, - UnrealTileSizeX, UnrealTileSizeY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, - LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, - LandscapeTileActorName, - TileActorType, - SharedLandscapeActor, - TileWorld, - TileLevel, - InPackageParams); - - if (!TileActor || !TileActor->IsValidLowLevel()) - return false; - - // Update the visibility mask / layer if we have any - for (auto CurrLayerInfo : LayerInfos) - { - if (CurrLayerInfo.LayerInfo && CurrLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - TileActor->VisibilityLayer = CurrLayerInfo.LayerInfo; - TileActor->VisibilityLayer->bNoWeightBlend = true; - TileActor->VisibilityLayer->AddToRoot(); - } - } - - LandscapeInfo = TileActor->GetLandscapeInfo(); - - bCreatedTileActor = true; - bTileLandscapeMaterialChanged = true; - bTileLandscapeHoleMaterialChanged = true; - bTilePhysicalMaterialChanged = true; - bHeightLayerDataChanged = true; - bCustomLayerDataChanged = true; - } - else - { - LandscapeInfo = TileActor->GetLandscapeInfo(); - - // Always update the transform, even if the HGPO transform hasn't changed, - // If we change the number of tiles, or switch from outputting single tile to multiple, - // then its fairly likely that the unreal transform has changed even if the - // Houdini Transform remained the same - if (!TileActor->GetTransform().Equals(TileTransform)) - { - // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Updating tile transform: %s"), *(TileTransform.ToString())); - TileActor->SetActorTransform(TileTransform); - TileActor->SetAbsoluteSectionBase(TileLoc); -#if WITH_EDITOR - GEngine->BroadcastOnActorMoved(TileActor); -#endif - LandscapeInfo->RecreateLandscapeInfo(InWorld,true); - } - - // Update existing landscape / tile - if (SharedLandscapeActor && TileActorType == LandscapeActorType::LandscapeStreamingProxy) - { - TileActor->FixupSharedData(SharedLandscapeActor); - - // This is a tile with a shared landscape. - // Check whether the LandscapeActor changed. If so, update the proxy's shared properties. - CachedStreamingProxyActor = Cast(TileActor); - if (SharedLandscapeActor) - { - if (CachedStreamingProxyActor) - bModifiedLandscapeActor = CachedStreamingProxyActor->LandscapeActor != SharedLandscapeActor; - else - bModifiedLandscapeActor = true; - - if (bModifiedLandscapeActor) - { - CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; - // We need to force a state update through PostEditChangeProperty here in order to initialize - // since we're about to perform additional data updates on this tile. - DoPostEditChangeProperty(CachedStreamingProxyActor, "LandscapeActor"); - } - } - else - { - CachedStreamingProxyActor->LandscapeActor = nullptr; - } - - } - else - { - // This is a standalone tile / landscape actor. - CachedLandscapeActor = Cast(TileActor); - } - - ULandscapeInfo* PreviousInfo = TileActor->GetLandscapeInfo(); - if (!PreviousInfo) - return false; - - FIntRect BoundingRect = TileActor->GetBoundingRect(); - FIntPoint SectionOffset = TileActor->GetSectionBaseOffset(); - - // Landscape region to update - const int32 MinX = TileLoc.X; - const int32 MaxX = TileLoc.X + UnrealTileSizeX - 1; - const int32 MinY = TileLoc.Y; - const int32 MaxY = TileLoc.Y + UnrealTileSizeY - 1; - - // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. - // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools - // though the *Accessors do additional things like update normals and foliage. - - // Update height if it has been changed. - if (Heightfield->bHasGeoChanged) - { - // It is important to update the heightmap through the this since it will properly - // update normals and foliage. - FHeightmapAccessor HeightmapAccessor(LandscapeInfo); - HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); - - bHeightLayerDataChanged = true; - } - - // Update the layers on the landscape. - for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) - { - FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); - AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - - if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - TileActor->VisibilityLayer = NextUpdatedLayerInfo.LayerInfo; - TileActor->VisibilityLayer->bNoWeightBlend = true; - TileActor->VisibilityLayer->AddToRoot(); - } - - bCustomLayerDataChanged = true; - } - - bModifiedLandscapeActor = true; - } - - // ---------------------------------------------------- - // Update tile materials - // ---------------------------------------------------- - // TODO: These material updates can possibly be skipped if we have already performed this - // check on a SharedLandscape. - bTileLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (TileActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bTileLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (TileActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); - - if (bTileLandscapeMaterialChanged) - TileActor->LandscapeMaterial = LandscapeMaterial; - - if (bTileLandscapeHoleMaterialChanged) - TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - - bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; - if (bTilePhysicalMaterialChanged) - { - DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); - TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - //TileActor->ChangedPhysMaterial(); - } - - // ---------------------------------------------------- - // Apply actor tags - // ---------------------------------------------------- - - // See if we have unreal_tag_ attribute - TArray Tags; - if (TileActor && FHoudiniEngineUtils::GetUnrealTagAttributes(GeoId, PartId, Tags)) - { - TileActor->Tags = Tags; - } - - // ---------------------------------------------------- - // Update actor states based on data updates - // ---------------------------------------------------- - // Based on ALandscape and ALandscapeStreamingProxy PostEditChangeProperty() implementations, - // effect appropriate state updates based on the property updates that was performed in - // the above code. - - if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) - { - check(SharedLandscapeActor); - DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - } - - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - { - check(TileActor); - // Tile material changes are only processed if it wasn't already done for a shared - // landscape since the shared landscape should have already propagated the changes to associated proxies. - DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); - } - - if (bSharedPhysicalMaterialChanged) - { - check(SharedLandscapeActor); - DoPostEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); - } - - if (bTilePhysicalMaterialChanged) - { - check(TileActor); - DoPostEditChangeProperty(TileActor, "DefaultPhysMaterial"); - } - - if (bModifiedSharedLandscapeActor) - { - SharedLandscapeActor->PostEditChange(); - } - - if (bModifiedLandscapeActor) - { - TileActor->PostEditChange(); - } - - { - FLandscapeEditDataInterface LandscapeEdit(TileActor->GetLandscapeInfo()); - LandscapeEdit.RecalculateNormals(); - } - - if (LandscapeInfo) - { - LandscapeInfo->RecreateLandscapeInfo(InWorld, true); - } - - // Add objects to the HAC output. - SetLandscapeActorAsOutput( - InOutput, - InAllInputLandscapes, - OutputAttributes, - OutputTokens, - SharedLandscapeActor, - SharedLandscapeActorParent, - bCreatedSharedLandscape, - HeightfieldIdentifier, - TileActor, - InPackageParams.PackageMode); - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( - const ULandscapeInfo* LandscapeInfo, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection - ) -{ - if (!IsValid(LandscapeInfo)) - return false; - - - if (LandscapeInfo->ComponentNumSubsections != InNumSectionsPerComponent) - return false; - - if (LandscapeInfo->SubsectionSizeQuads != InNumQuadsPerSection) - return false; - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsLandscapeTileCompatible( - const ALandscapeProxy* TileActor, - const int32 InTileSizeX, - const int32 InTileSizeY, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection -) -{ - check(TileActor); - - // NOTE: We can't compare landscape extents here since the Houdini only knows about the size for single tile. - // and LandscapeInfo will only return extents for the *loaded* landscape tiles. - - // TODO: Add more robust checks to determine landscape compatibility. - - if (!IsLandscapeInfoCompatible(TileActor->GetLandscapeInfo(), InNumSectionsPerComponent, InNumQuadsPerSection)) - return false; - - const FIntRect Bounds = TileActor->GetBoundingRect(); - const FIntPoint Size = Bounds.Size(); - if (Size.X != InTileSizeX && Size.Y != InTileSizeY) - return false; - - return true; -} - - -bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, LandscapeActorType ActorType) -{ - if (!IsValid(Actor)) - return false; - - switch (ActorType) - { - case LandscapeActorType::LandscapeActor: - return Actor->IsA(); - break; - case LandscapeActorType::LandscapeStreamingProxy: - return Actor->IsA(); - break; - default: - break; - } - - return false; -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage, - const EPackageMode& InPackageMode) -{ - if (InPackageMode == EPackageMode::Bake) - return FindExistingLandscapeActor_Bake(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); - else - return FindExistingLandscapeActor_Temp(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - bCreatedPackage = false; - - // // Locate landscape proxy actor when running in baked mode - // AActor* FoundActor = nullptr; - ALandscapeProxy* OutActor = nullptr; - // OutActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); - // // OutActor = FHoudiniEngineUtils::FindActorInWorld(InWorld, FName(InActorName)); - // if (FoundActor && FoundActor != OutActor) - // FoundActor->Destroy(); // nuke it! - // - // if (OutActor) - // { - // // TODO: make sure that the found is actor is actually assigned to the level defined by package path. - // // If the found actor is not from that level, it should be moved there. - // - // OutWorld = OutActor->GetWorld(); - // OutLevel = OutActor->GetLevel(); - // } - // else - { - // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. - bool bActorInWorld = false; - const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - InWorld, - InPackagePath, - true, - OutWorld, - OutLevel, - bCreatedPackage, - bActorInWorld); - - if (!bResult) - { - return nullptr; - } - - // if (!bActorInWorld) - // { - // // The OutLevel is not present in the current world which means we might - // // still find the tile actor in OutWorld. - OutActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); - // } - } - - return OutActor; -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - ALandscapeProxy* OutActor = nullptr; - FString ActorName = InActorName + TEXT("_Temp"); - TMap& PrevCookObjects = InOutput->GetOutputObjects(); - - OutWorld = InWorld; - OutLevel = InWorld->PersistentLevel; - - bCreatedPackage = false; - - // Find Landscape proxy for output when running in Temp mode - for(auto& PrevObject : PrevCookObjects) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(PrevObject.Value.OutputObject); - if (!LandscapePtr) - continue; - - OutActor = LandscapePtr->GetRawPtr(); - if (!OutActor) - { - // We may need to manually load the object - OutActor = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); - } - - if (!OutActor) - continue; - - // If we were updating the input landscape before, but arent anymore, - // we could still find it here in the output, ignore them now as we're only looking for previous output - if (ValidLandscapes.Contains(OutActor)) - continue; - - if (OutActor->GetName() != ActorName) - // This is not the droid we're looking for - continue; - - if (OutActor->IsPendingKill()) - { - FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + "_pendingkill"); - continue; - } - - // If we found a possible candidate, make sure that its size matches ours - // as we can only update a landscape of the same size - ULandscapeInfo* PreviousInfo = OutActor->GetLandscapeInfo(); - if (PreviousInfo) - { - int32 PrevMinX = 0; - int32 PrevMinY = 0; - int32 PrevMaxX = 0; - int32 PrevMaxY = 0; - PreviousInfo->GetLandscapeExtent(PrevMinX, PrevMinY, PrevMaxX, PrevMaxY); - - if ((PrevMaxX - PrevMinX + 1) == UnrealLandscapeSizeX && (PrevMaxY - PrevMinY + 1) == UnrealLandscapeSizeY) - { - // The size matches, we can reuse the old landscape. - break; - } - else - { - // We can't reuse this actor. The dimensions does not match. - // We need to rename this actor in order to create a new one with the specified name. - FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + TEXT("_old") ); - OutActor = nullptr; - } - } - } - - return OutActor; -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor, - const EPackageMode InPackageMode) -{ - if (InPackageMode == EPackageMode::Bake) - return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); - else - return SetLandscapeActorAsOutput_Temp(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor) -{ - // We are in bake mode. No outputs to register / add here. - // Do nothing, for now. -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedSharedLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor) -{ - // The main landscape is a special case here. It cannot be registered with the - // output object here, since it is possibly shared by *multiple* outputs so - // we have to deal with the attached and cleanup of the actor manually. - if (bCreatedSharedLandscape && IsValid(SharedLandscapeActorParent)) - { - AttachActorToHAC(InOutput, SharedLandscapeActor); - } - - // TODO: The OutputObject cleanup being performed here should really be part of - // the output object itself (or at the very least be encapsulated in a reusable - // static function somewhere) so that individual output objects can be cleaned - // when necessary without having to duplicate code when its needed. - - // Cleanup any stale output objects - TMap& Outputs = InOutput->GetOutputObjects(); - TArray StaleOutputs; - for (auto& Elem : Outputs) - { - UHoudiniLandscapePtr* LandscapePtr = nullptr; - bool bIsStale = false; - - if (!(Elem.Key == Identifier)) - { - // Identifiers doesn't match so this is definitely a stale output. - StaleOutputs.Add(Elem.Key); - bIsStale = true; - } - - LandscapePtr = Cast(Elem.Value.OutputObject); - if (LandscapePtr) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - - if (LandscapeProxy) - { - // We shouldn't destroy any input landscape, - // or the landscape that we are currently trying to set as output.. - if (!InAllInputLandscapes.Contains(LandscapeProxy) && (LandscapeProxy != LandscapeActor)) - { - // This landscape proxy either doesn't match the landscape identifier - // or it doesn't match the actor we're about to output. Either way, - // get rid of it. - LandscapeProxy->Destroy(); - } - } - } - - if (bIsStale) - { - Elem.Value.OutputObject = nullptr; - } - } - - for (FHoudiniOutputObjectIdentifier& StaleOutput : StaleOutputs) - { - Outputs.Remove(StaleOutput); - } - - - // Send a landscape pointer back to the Output Object for this landscape tile. - FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); - UHoudiniLandscapePtr* LandscapePtr = NewObject(InOutput); - LandscapePtr->SetSoftPtr(LandscapeActor); - OutputObj.OutputObject = LandscapePtr; - OutputObj.CachedAttributes = OutputAttributes; - OutputObj.CachedTokens = OutputTokens; -} - - -bool -FHoudiniLandscapeTranslator::AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor) -{ - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (IsValid(HAC)) - { - InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); - return true; - } - return false; -} - -FString -FHoudiniLandscapeTranslator::GetActorNameSuffix(const EPackageMode& InPackageMode) -{ - if(InPackageMode == EPackageMode::CookToTemp) - return "_Temp"; - else - return FString(); -} - -void -FHoudiniLandscapeTranslator::DoPreEditChangeProperty(UObject* Obj, FName PropertyName) -{ - Obj->PreEditChange(FindFProperty(Obj->GetClass(), PropertyName)); -} - -void -FHoudiniLandscapeTranslator::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) -{ - FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); - Obj->PostEditChangeProperty(Evt); -} - -bool -FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( - const TArray< float >& HeightfieldFloatValues, - const FHoudiniVolumeInfo& HeightfieldVolumeInfo, - const int32& FinalXSize, const int32& FinalYSize, - float FloatMin, float FloatMax, - TArray< uint16 >& IntHeightData, - FTransform& LandscapeTransform, - const bool& NoResize) -{ - IntHeightData.Empty(); - LandscapeTransform.SetIdentity(); - - // HF sizes needs an X/Y swap - // NOPE.. not anymore - int32 HoudiniXSize = HeightfieldVolumeInfo.YLength; - int32 HoudiniYSize = HeightfieldVolumeInfo.XLength; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - // Test for potential special cases... - // Just print a warning for now - if (HeightfieldVolumeInfo.MinX != 0) - HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min X is not zero.")); - - if (HeightfieldVolumeInfo.MinY != 0) - HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min Y is not zero.")); - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to uint16 using doubles to get the maximum precision during the conversion - //-------------------------------------------------------------------------------------------------- - - FTransform CurrentVolumeTransform = HeightfieldVolumeInfo.Transform; - - // The ZRange in Houdini (in m) - double MeterZRange = (double)(FloatMax - FloatMin); - - // The corresponding unreal digit range (as unreal uses uint16, max is 65535) - // We may want to not use the full range in order to be able to sculpt the landscape past the min/max values after. - const double dUINT16_MAX = (double)UINT16_MAX; - double DigitZRange = 49152.0; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution) - DigitZRange = dUINT16_MAX - 1.0; - - // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down - double DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); - - // The factor used to convert from Houdini's ZRange to the desired digit range - double ZSpacing = (MeterZRange != 0.0) ? (DigitZRange / MeterZRange) : 0.0; - - // Changes these values if the user wants to loose a lot of precision - // just to keep the same transform as the landscape input - bool bUseDefaultUE4Scaling = false; - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) - bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; - - if (bUseDefaultUE4Scaling) - { - //Check that our values are compatible with UE4's default scale values - if (FloatMin < -256.0f || FloatMin > 256.0f || FloatMax < -256.0f || FloatMax > 256.0f) - { - // Warn the user that the landscape conversion will have issues - // invite him to change that setting - HOUDINI_LOG_WARNING( - TEXT("The heightfield's min and max height values are too large for being used with the \"Use Default UE4 scaling\" option.\n \ - The generated Heightfield will likely be incorrectly converted to landscape unless you disable that option in the project settings and recook the asset.")); - } - - DigitZRange = dUINT16_MAX - 1.0; - DigitCenterOffset = 0; - - // Default unreal landscape scaling is -256m:256m at Scale = 100 - // We need to apply the scale back to - FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; - FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; - MeterZRange = (double)(FloatMax - FloatMin); - - ZSpacing = ((double)DigitZRange) / MeterZRange; - } - - // Converting the data from Houdini to Unreal - // For correct orientation in unreal, the point matrix has to be transposed. - IntHeightData.SetNumUninitialized(SizeInPoints); - - int32 nUnreal = 0; - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y - int32 nHoudini = nY + nX * HoudiniYSize; - - // Get the double values in [0 - ZRange] - double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; - - // Then convert it to [0 - DesiredRange] and center it - DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; - IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); - } - } - - //-------------------------------------------------------------------------------------------------- - // 2. Resample / Pad the int data so that if fits unreal size requirements - //-------------------------------------------------------------------------------------------------- - - // UE has specific size requirements for landscape, - // so we might need to pad/resample the heightfield data - FVector LandscapeResizeFactor = FVector::OneVector; - FVector LandscapePositionOffsetInPixels = FVector::ZeroVector; - if (!NoResize) - { - // Try to resize the data - if (!FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( - IntHeightData, - HoudiniXSize, HoudiniYSize, FinalXSize, FinalYSize, - LandscapeResizeFactor, LandscapePositionOffsetInPixels)) - return false; - } - - //-------------------------------------------------------------------------------------------------- - // 3. Calculating the proper transform for the landscape to be sized and positionned properly - //-------------------------------------------------------------------------------------------------- - - // Scale: - // Calculating the equivalent scale to match Houdini's Terrain Size in Unreal - FVector LandscapeScale; - - // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing - LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; - LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Y * 2.0f; - - // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini - // Unreal has a default Z range is 512m for a scale of a 100% - LandscapeScale.Z = (float)((double)(dUINT16_MAX / DigitZRange) * MeterZRange / 512.0); - if (bUseDefaultUE4Scaling) - LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Z * 2.0f; - LandscapeScale *= 100.f; - - // If the data was resized and not expanded, we need to modify the landscape's scale - LandscapeScale *= LandscapeResizeFactor; - - // Don't allow a zero scale, as this results in divide by 0 operations in FMatrix::InverseFast in the landscape component. - if (FMath::IsNearlyZero(LandscapeScale.Z)) - LandscapeScale.Z = 1.0f; - - // We'll use the position from Houdini, but we will need to offset the Z Position to center the - // values properly as the data has been offset by the conversion to uint16 - FVector LandscapePosition = CurrentVolumeTransform.GetLocation(); - //LandscapePosition.Z = 0.0f; - - // We need to calculate the position offset so that Houdini and Unreal have the same Zero position - // In Unreal, zero has a height value of 32768. - // These values are then divided by 128 internally, and then multiplied by the Landscape's Z scale - // ( DIGIT - 32768 ) / 128 * ZScale = ZOffset - - // We need the Digit (Unreal) value of Houdini's zero for the scale calculation - // ( float and int32 are used for this because 0 might be out of the landscape Z range! - // when using the full range, this would cause an overflow for a uint16!! ) - float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); - float ZOffset = -(HoudiniZeroValueInDigit - 32768.0f) / 128.0f * LandscapeScale.Z; - - LandscapePosition.Z += ZOffset; - - // If we have padded the data when resizing the landscape, we need to offset the position because of - // the added values on the topLeft Corner of the Landscape - if (LandscapePositionOffsetInPixels != FVector::ZeroVector) - { - FVector LandscapeOffset = LandscapePositionOffsetInPixels * LandscapeScale; - LandscapeOffset.Z = 0.0f; - - LandscapePosition += LandscapeOffset; - } - - /* - FTransform TempTransform; - TempTransform.SetIdentity(); - { - // Houdini Pivot (center of the Landscape) - FVector HoudiniPivot = FVector((FinalXSize-1) * 100.0f / 2.0f, (FinalYSize-1) * 100.0f / 2.0f, 0.0f); - - // Center the landscape - FVector CenterLocation = LandscapePosition - HoudiniPivot; - - // Rotate the vector using the H rotation - // We need to compensate for the "default" HF Transform - FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); - Rotator.Yaw -= 90.0f; - Rotator.Roll += 90.0f; - FVector RotatedLocation = Rotator.RotateVector(CenterLocation); - - FQuat LandscapeRotation = FQuat(Rotator) * FQuat::Identity; - - // Return to previous origin - FVector Uncentered = RotatedLocation + HoudiniPivot; - TempTransform = FTransform(LandscapeRotation, Uncentered, LandscapeScale); - } - - LandscapeTransform = TempTransform; - */ - - // We can now set the Landscape position - LandscapeTransform.SetLocation(LandscapePosition); - LandscapeTransform.SetScale3D(LandscapeScale); - - // Rotate the vector using the H rotation - FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); - // We need to compensate for the "default" HF Transform - Rotator.Yaw -= 90.0f; - Rotator.Roll += 90.0f; - - // Only rotate if the rotator is far from zero - if(!Rotator.IsNearlyZero()) - LandscapeTransform.SetRotation(FQuat(Rotator)); - - return true; -} - -template -TArray ResampleData(const TArray& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight) -{ - TArray Result; - Result.Empty(NewWidth * NewHeight); - Result.AddUninitialized(NewWidth * NewHeight); - - const float XScale = (float)(OldWidth - 1) / (NewWidth - 1); - const float YScale = (float)(OldHeight - 1) / (NewHeight - 1); - for (int32 Y = 0; Y < NewHeight; ++Y) - { - for (int32 X = 0; X < NewWidth; ++X) - { - const float OldY = Y * YScale; - const float OldX = X * XScale; - const int32 X0 = FMath::FloorToInt(OldX); - const int32 X1 = FMath::Min(FMath::FloorToInt(OldX) + 1, OldWidth - 1); - const int32 Y0 = FMath::FloorToInt(OldY); - const int32 Y1 = FMath::Min(FMath::FloorToInt(OldY) + 1, OldHeight - 1); - const T& Original00 = Data[Y0 * OldWidth + X0]; - const T& Original10 = Data[Y0 * OldWidth + X1]; - const T& Original01 = Data[Y1 * OldWidth + X0]; - const T& Original11 = Data[Y1 * OldWidth + X1]; - Result[Y * NewWidth + X] = FMath::BiLerp(Original00, Original10, Original01, Original11, FMath::Fractional(OldX), FMath::Fractional(OldY)); - } - } - - return Result; -} - -template -void ExpandData(T* OutData, const T* InData, - int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, - int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY) -{ - const int32 OldWidth = OldMaxX - OldMinX + 1; - const int32 OldHeight = OldMaxY - OldMinY + 1; - const int32 NewWidth = NewMaxX - NewMinX + 1; - const int32 NewHeight = NewMaxY - NewMinY + 1; - const int32 OffsetX = NewMinX - OldMinX; - const int32 OffsetY = NewMinY - OldMinY; - - for (int32 Y = 0; Y < NewHeight; ++Y) - { - const int32 OldY = FMath::Clamp(Y + OffsetY, 0, OldHeight - 1); - - // Pad anything to the left - const T PadLeft = InData[OldY * OldWidth + 0]; - for (int32 X = 0; X < -OffsetX; ++X) - { - OutData[Y * NewWidth + X] = PadLeft; - } - - // Copy one row of the old data - { - const int32 X = FMath::Max(0, -OffsetX); - const int32 OldX = FMath::Clamp(X + OffsetX, 0, OldWidth - 1); - FMemory::Memcpy(&OutData[Y * NewWidth + X], &InData[OldY * OldWidth + OldX], FMath::Min(OldWidth, NewWidth) * sizeof(T)); - } - - const T PadRight = InData[OldY * OldWidth + OldWidth - 1]; - for (int32 X = -OffsetX + OldWidth; X < NewWidth; ++X) - { - OutData[Y * NewWidth + X] = PadRight; - } - } -} - -template -TArray ExpandData(const TArray& Data, - int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, - int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY, - int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr) -{ - const int32 NewWidth = NewMaxX - NewMinX + 1; - const int32 NewHeight = NewMaxY - NewMinY + 1; - - TArray Result; - Result.Empty(NewWidth * NewHeight); - Result.AddUninitialized(NewWidth * NewHeight); - - ExpandData(Result.GetData(), Data.GetData(), - OldMinX, OldMinY, OldMaxX, OldMaxY, - NewMinX, NewMinY, NewMaxX, NewMaxY); - - // Return the padding so we can offset the terrain position after - if (PadOffsetX) - *PadOffsetX = NewMinX; - - if (PadOffsetY) - *PadOffsetY = NewMinY; - - return Result; -} - -bool -FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( - TArray& HeightData, - const int32& SizeX, const int32& SizeY, - const int32& NewSizeX, const int32& NewSizeY, - FVector& LandscapeResizeFactor, - FVector& LandscapePositionOffset) -{ - LandscapeResizeFactor = FVector::OneVector; - LandscapePositionOffset = FVector::ZeroVector; - - if (HeightData.Num() <= 4) - return false; - - if ((SizeX < 2) || (SizeY < 2)) - return false; - - // No need to resize anything - if (SizeX == NewSizeX && SizeY == NewSizeY) - return true; - - // Always resample, for now. We may enable padding functionality again at some point via - // a plugin setting. - bool bForceResample = true; - bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); - - TArray NewData; - if (!bResample) - { - // Expanding the data by padding - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - - const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; - const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; - - // Store the offset in pixel due to the padding - int32 PadOffsetX = 0; - int32 PadOffsetY = 0; - - // Expanding the Data - NewData = ExpandData( - HeightData, 0, 0, SizeX - 1, SizeY - 1, - -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1, - &PadOffsetX, &PadOffsetY); - - // We will need to offset the landscape position due to the value added by the padding - LandscapePositionOffset.X = (float)PadOffsetX; - LandscapePositionOffset.Y = (float)PadOffsetY; - - // Notify the user that the data was padded - HOUDINI_LOG_WARNING( - TEXT("Landscape data was padded from ( %d x %d ) to ( %d x %d )."), - SizeX, SizeY, NewSizeX, NewSizeY); - } - else - { - // Resampling the data - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - NewData = ResampleData(HeightData, SizeX, SizeY, NewSizeX, NewSizeY); - - // The landscape has been resized, we'll need to take that into account when sizing it - LandscapeResizeFactor.X = (float)SizeX / (float)NewSizeX; - LandscapeResizeFactor.Y = (float)SizeY / (float)NewSizeY; - LandscapeResizeFactor.Z = 1.0f; - - // Notify the user if the heightfield data was resized - HOUDINI_LOG_WARNING( - TEXT("Landscape data was resized from ( %d x %d ) to ( %d x %d )."), - SizeX, SizeY, NewSizeX, NewSizeY); - } - - // Replaces Old data with the new one - HeightData = NewData; - - return true; -} - - -bool -FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - const int32& HoudiniSizeX, const int32& HoudiniSizeY, - int32& UnrealSizeX, int32& UnrealSizeY, - int32& NumSectionsPerComponent, int32& NumQuadsPerSection) -{ - if ((HoudiniSizeX < 2) || (HoudiniSizeY < 2)) - return false; - - NumSectionsPerComponent = 1; - NumQuadsPerSection = 1; - UnrealSizeX = -1; - UnrealSizeY = -1; - - // Unreal's default sizes - int32 SectionSizes[] = { 7, 15, 31, 63, 127, 255 }; - int32 NumSections[] = { 1, 2 }; - - // Component count used to calculate the final size of the landscape - int32 ComponentsCountX = 1; - int32 ComponentsCountY = 1; - - // Lambda for clamping the number of component in X/Y - auto ClampLandscapeSize = [&]() - { - // Max size is either whole components below 8192 verts, or 32 components - ComponentsCountX = FMath::Clamp(ComponentsCountX, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); - ComponentsCountY = FMath::Clamp(ComponentsCountY, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); - }; - - // Try to find a section size and number of sections that exactly matches the dimensions of the heightfield - bool bFoundMatch = false; - for (int32 SectionSizesIdx = UE_ARRAY_COUNT(SectionSizes) - 1; SectionSizesIdx >= 0; SectionSizesIdx--) - { - for (int32 NumSectionsIdx = UE_ARRAY_COUNT(NumSections) - 1; NumSectionsIdx >= 0; NumSectionsIdx--) - { - int32 ss = SectionSizes[SectionSizesIdx]; - int32 ns = NumSections[NumSectionsIdx]; - - if (((HoudiniSizeX - 1) % (ss * ns)) == 0 && ((HoudiniSizeX - 1) / (ss * ns)) <= 32 && - ((HoudiniSizeY - 1) % (ss * ns)) == 0 && ((HoudiniSizeY - 1) / (ss * ns)) <= 32) - { - bFoundMatch = true; - NumQuadsPerSection = ss; - NumSectionsPerComponent = ns; - ComponentsCountX = (HoudiniSizeX - 1) / (ss * ns); - ComponentsCountY = (HoudiniSizeY - 1) / (ss * ns); - ClampLandscapeSize(); - break; - } - } - if (bFoundMatch) - { - break; - } - } - - if (!bFoundMatch) - { - // if there was no exact match, try increasing the section size until we encompass the whole heightmap - const int32 CurrentSectionSize = NumQuadsPerSection; - const int32 CurrentNumSections = NumSectionsPerComponent; - for (int32 SectionSizesIdx = 0; SectionSizesIdx < UE_ARRAY_COUNT(SectionSizes); SectionSizesIdx++) - { - if (SectionSizes[SectionSizesIdx] < CurrentSectionSize) - { - continue; - } - - const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); - const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); - if (ComponentsX <= 32 && ComponentsY <= 32) - { - bFoundMatch = true; - NumQuadsPerSection = SectionSizes[SectionSizesIdx]; - ComponentsCountX = ComponentsX; - ComponentsCountY = ComponentsY; - ClampLandscapeSize(); - break; - } - } - } - - if (!bFoundMatch) - { - // if the heightmap is very large, fall back to using the largest values we support - const int32 MaxSectionSize = SectionSizes[UE_ARRAY_COUNT(SectionSizes) - 1]; - const int32 MaxNumSubSections = NumSections[UE_ARRAY_COUNT(NumSections) - 1]; - const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), MaxSectionSize * MaxNumSubSections); - const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), MaxSectionSize * MaxNumSubSections); - - bFoundMatch = true; - NumQuadsPerSection = MaxSectionSize; - NumSectionsPerComponent = MaxNumSubSections; - ComponentsCountX = ComponentsX; - ComponentsCountY = ComponentsY; - ClampLandscapeSize(); - } - - if (!bFoundMatch) - { - // Using default size just to not crash.. - UnrealSizeX = 512; - UnrealSizeY = 512; - NumSectionsPerComponent = 1; - NumQuadsPerSection = 511; - ComponentsCountX = 1; - ComponentsCountY = 1; - } - else - { - // Calculating the desired size - int32 QuadsPerComponent = NumSectionsPerComponent * NumQuadsPerSection; - - UnrealSizeX = ComponentsCountX * QuadsPerComponent + 1; - UnrealSizeY = ComponentsCountY * QuadsPerComponent + 1; - } - - return bFoundMatch; -} - -const FHoudiniGeoPartObject* -FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return nullptr; - - if (InOutput->GetHoudiniGeoPartObjects().Num() < 1) - return nullptr; - - for (const FHoudiniGeoPartObject& HGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Volume) - continue; - - FHoudiniVolumeInfo CurVolumeInfo = HGPO.VolumeInfo; - if (!CurVolumeInfo.Name.Contains("height")) - continue; - - // We're only handling single values for now - if (CurVolumeInfo.TupleSize != 1) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume has an invalide tuple size!")); - return nullptr; - } - - // Terrains always have a ZSize of 1. - if (CurVolumeInfo.ZLength != 1) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume's z length is not 1!")); - return nullptr; - } - - // Values should be float - if (!CurVolumeInfo.bIsFloat) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output, the height volume's data is not stored as floats!")); - return nullptr; - } - - return &HGPO; - } - - return nullptr; -} - -void -FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers) -{ - FoundLayers.Empty(); - - // Get node id - HAPI_NodeId HeightFieldNodeId = Heightfield.GeoId; - - // We need the tile attribute if the height has it - bool bParentHeightfieldHasTile = false; - int32 HeightFieldTile = -1; - { - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - TArray< int32 > TileValues; - - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HeightFieldNodeId, Heightfield.PartId, "tile", AttribInfoTile, TileValues); - - if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) - { - HeightFieldTile = TileValues[0]; - bParentHeightfieldHasTile = true; - } - } - - for (TArray< FHoudiniGeoPartObject >::TConstIterator IterLayers(InOutput->GetHoudiniGeoPartObjects()); IterLayers; ++IterLayers) - { - const FHoudiniGeoPartObject & HoudiniGeoPartObject = *IterLayers; - - HAPI_NodeId NodeId = HoudiniGeoPartObject.GeoId; - if (NodeId == -1 || NodeId != HeightFieldNodeId) - continue; - - if (bParentHeightfieldHasTile) - { - int32 CurrentTile = -1; - - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - TArray TileValues; - - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "tile", AttribInfoTile, TileValues); - - - if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) - { - CurrentTile = TileValues[0]; - } - - // Does this layer come from the same tile as the height? - if ((CurrentTile != HeightFieldTile) || (CurrentTile == -1)) - continue; - } - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, HoudiniGeoPartObject.PartId, - &CurrentVolumeInfo)) - continue; - - // We're interesting in anything but height data - FString CurrentVolumeName; - FHoudiniEngineString(CurrentVolumeInfo.nameSH).ToFString(CurrentVolumeName); - if (CurrentVolumeName.Contains("height")) - continue; - - // We're only handling single values for now - if (CurrentVolumeInfo.tupleSize != 1) - continue; - - // Terrains always have a ZSize of 1. - if (CurrentVolumeInfo.zLength != 1) - continue; - - // Values should be float - if (CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - continue; - - FoundLayers.Add(&HoudiniGeoPartObject); - } -} - -bool -FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) -{ - OutFloatArr.Empty(); - OutFloatMin = 0.f; - OutFloatMax = 0.f; - - if (HGPO->Type != EHoudiniPartType::Volume) - return false; - - HAPI_VolumeInfo VolumeInfo; - FHoudiniApi::VolumeInfo_Init(&VolumeInfo); - - HAPI_Result Result = FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - HGPO->GeoId, HGPO->PartId, &VolumeInfo); - - // We're only handling single values for now - if (VolumeInfo.tupleSize != 1) - return false; - - // Terrains always have a ZSize of 1. - if (VolumeInfo.zLength != 1) - return false; - - // Values must be float - if (VolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - return false; - - if ((VolumeInfo.xLength < 2) || (VolumeInfo.yLength < 2)) - return false; - - const int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; - - OutFloatArr.SetNum(SizeInPoints); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetHeightFieldData( - FHoudiniEngine::Get().GetSession(), - HGPO->GeoId, HGPO->PartId, - OutFloatArr.GetData(), - 0, SizeInPoints), false); - - OutFloatMin = OutFloatArr[0]; - OutFloatMax = OutFloatMin; - - for (float NextFloatVal : OutFloatArr) - { - if (NextFloatVal > OutFloatMax) - { - OutFloatMax = NextFloatVal; - } - else if (NextFloatVal < OutFloatMin) - OutFloatMin = NextFloatVal; - } - - return true; -} - -bool -FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(const FHoudiniGeoPartObject& InHGPO, TArray& NonWeightBlendedLayerNames) -{ - // Check the attribute exists on primitive or detail - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; - if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_PRIM)) - Owner = HAPI_ATTROWNER_PRIM; - else if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_DETAIL)) - Owner = HAPI_ATTROWNER_DETAIL; - else - return false; - - // Get the values - HAPI_AttributeInfo AttribInfoNonWBLayer; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNonWBLayer); - TArray AttribValues; - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, AttribInfoNonWBLayer, AttribValues, 1, Owner); - - if (AttribValues.Num() <= 0) - return false; - - // Convert them to FString - for (int32 Idx = 0; Idx < AttribValues.Num(); Idx++) - { - TArray Tokens; - AttribValues[Idx].ParseIntoArray(Tokens, TEXT(" "), true); - - for (int32 n = 0; n < Tokens.Num(); n++) - NonWeightBlendedLayerNames.AddUnique(Tokens[n]); - } - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) -{ - // Check the attribute exists on primitive or detail - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; - if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_PRIM)) - Owner = HAPI_ATTROWNER_PRIM; - else if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_DETAIL)) - Owner = HAPI_ATTROWNER_DETAIL; - else - return false; - - // Check the value - HAPI_AttributeInfo AttribInfoUnitLayer; - FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer); - TArray< int32 > AttribValues; - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, AttribInfoUnitLayer, AttribValues, 1, Owner); - - if (AttribValues.Num() > 0 && AttribValues[0] == 1) - return true; - - return false; -} - -bool -FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( - const TArray& FoundLayers, - const FHoudiniGeoPartObject& Heightfield, - const int32& LandscapeXSize, const int32& LandscapeYSize, - const TMap& GlobalMinimums, - const TMap& GlobalMaximums, - TArray& OutLayerInfos, - bool bIsUpdate, - const FHoudiniPackageParams& InTilePackageParams, - const FHoudiniPackageParams& InLayerPackageParams, - TArray& OutCreatedPackages - ) -{ - OutLayerInfos.Empty(); - - // Get the names of all non weight blended layers - TArray NonWeightBlendedLayerNames; - FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(Heightfield, NonWeightBlendedLayerNames); - - // Used for exporting layer info objects (per landscape layer) - FHoudiniPackageParams LayerPackageParams = InLayerPackageParams; - // Used for exporting textures (per landscape tile) - FHoudiniPackageParams TilePackageParams = InTilePackageParams; - - // For Debugging, do we want to export layers as textures? - bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; - - // Try to create all the layers - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - for (TArray::TConstIterator IterLayers(FoundLayers); IterLayers; ++IterLayers) - { - const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers; - if (!LayerGeoPartObject) - continue; - - if (!LayerGeoPartObject->IsValid()) - continue; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerGeoPartObject->AssetId)) - continue; - - if (bIsUpdate && !LayerGeoPartObject->bHasGeoChanged) - { - continue; - } - - TArray FloatLayerData; - float LayerMin = 0; - float LayerMax = 0; - if (!FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(LayerGeoPartObject, FloatLayerData, LayerMin, LayerMax)) - continue; - - // No need to create flat layers as Unreal will remove them afterwards.. - if (LayerMin == LayerMax) - continue; - - const FHoudiniVolumeInfo& LayerVolumeInfo = LayerGeoPartObject->VolumeInfo; - - // Get the layer's name - FString LayerName = LayerVolumeInfo.Name; - const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerName); - - TilePackageParams.ObjectName = InTilePackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; - LayerPackageParams.ObjectName = InLayerPackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; - - if (bExportTexture) - { - // Create a raw texture export of the layer on this tile - FString TextureName = TilePackageParams.ObjectName + "_raw"; - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - LayerVolumeInfo.YLength, // Y and X inverted?? why? - LayerVolumeInfo.XLength, - FloatLayerData, - LayerMin, - LayerMax); - } - - // Check if that landscape layer has been marked as unit (range in [0-1] - if (IsUnitLandscapeLayer(*LayerGeoPartObject)) - { - LayerMin = 0.0f; - LayerMax = 1.0f; - } - else - { - // We want to convert the layer using the global Min/Max - if (GlobalMaximums.Contains(LayerName)) - LayerMax = GlobalMaximums[LayerName]; - - if (GlobalMinimums.Contains(LayerName)) - LayerMin = GlobalMinimums[LayerName]; - } - - // Get the layer package path - // FString LayerNameString = FString::Printf(TEXT("%s_%d"), LayerString.GetCharArray().GetData(), (int32)LayerGeoPartObject->PartId); - // LayerNameString = UPackageTools::SanitizePackageName(LayerNameString); - - // Build an object name for the current layer - LayerPackageParams.SplitStr = SanitizedLayerName; - - // Creating the ImportLayerInfo and LayerInfo objects - FLandscapeImportLayerInfo ImportLayerInfo(*LayerName); - - // See if the user has assigned a layer info object via attribute - UPackage * Package = nullptr; - ULandscapeLayerInfoObject* LayerInfo = GetLandscapeLayerInfoForLayer(*LayerGeoPartObject, *LayerName); - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - // No assignment, try to find or create a landscape layer info object for that layer - LayerInfo = FindOrCreateLandscapeLayerInfoObject(LayerName, LayerPackageParams.GetPackagePath(), LayerPackageParams.GetPackageName(), Package); - } - - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - continue; - } - - // Convert the float data to uint8 - // HF masks need their X/Y sizes swapped - if (!FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( - FloatLayerData, LayerVolumeInfo.YLength, LayerVolumeInfo.XLength, - LayerMin, LayerMax, - LandscapeXSize, LandscapeYSize, - ImportLayerInfo.LayerData)) - continue; - - // We will store the data used to convert from Houdini values to int in the DebugColor - // This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards... - // R = Min, G = Max, B = Spacing, A = ? - LayerInfo->LayerUsageDebugColor.R = LayerMin; - LayerInfo->LayerUsageDebugColor.G = LayerMax; - LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; - LayerInfo->LayerUsageDebugColor.A = PI; - - // Visibility are by default non weight blended - if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) - LayerInfo->bNoWeightBlend = true; - else - LayerInfo->bNoWeightBlend = false; - - if (!bIsUpdate && Package && !Package->IsPendingKill()) - { - // Mark the package dirty... - Package->MarkPackageDirty(); - OutCreatedPackages.Add(Package); - } - - if (bExportTexture) - { - // Create an export of the converted data to texture - // FString TextureName = LayerString; - // if (LayerGeoPartObject->VolumeTileIndex >= 0) - // TextureName = TEXT("Tile") + FString::FromInt(LayerGeoPartObject->VolumeTileIndex) + TEXT("_") + LayerString; - // TextureName += TEXT("_conv"); - - const FString TextureName = TilePackageParams.ObjectName + TEXT("_conv"); - - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - LandscapeXSize, LandscapeYSize, - ImportLayerInfo.LayerData); - } - - // See if there is a physical material assigned via attribute for that landscape layer - UPhysicalMaterial* PhysMaterial = FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(*LayerGeoPartObject); - if (PhysMaterial && !PhysMaterial->IsPendingKill()) - { - LayerInfo->PhysMaterial = PhysMaterial; - } - - // Assign the layer info object to the import layer infos - ImportLayerInfo.LayerInfo = LayerInfo; - OutLayerInfos.Add(ImportLayerInfo); - } - - // Autosaving the layers prevents them for being deleted with the Asset - // Save the packages created for the LayerInfos - // Do this only for when creating layers. - /* - if (!bIsUpdate) - FEditorFileUtils::PromptForCheckoutAndSave(CreatedLandscapeLayerPackage, true, false); - */ - - return true; -} - -void -FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums, - bool bShouldEmptyMaps) -{ - if (bShouldEmptyMaps) - { - GlobalMinimums.Empty(); - GlobalMaximums.Empty(); - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray FloatData; - - for (const FHoudiniGeoPartObject& CurrentHeightfield: InHeightfieldArray) - { - // Get the current Heightfield GeoPartObject - if ( CurrentHeightfield.VolumeInfo.TupleSize != 1) - continue; - - // Retrieve node id from geo part. - HAPI_NodeId NodeId = CurrentHeightfield.GeoId; - if (NodeId == -1) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - &CurrentVolumeInfo)) - continue; - - // Retrieve the volume name. - FString VolumeName; - FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); - HoudiniEngineStringPartName.ToFString(VolumeName); - - bool bHasMinAttr = false; - bool bHasMaxAttr = false; - - // If this volume has an attribute defining a minimum value use it as is. - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - GlobalMinimums.Add(VolumeName, FloatData[0]); - bHasMinAttr = true; - } - } - - // If this volume has an attribute defining maximum value use it as is. - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - GlobalMaximums.Add(VolumeName, FloatData[0]); - bHasMaxAttr = true; - } - } - - if (!(bHasMinAttr && bHasMaxAttr)) - { - // Unreal's Z values are Y in Houdini - float ymin, ymax; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - nullptr, &ymin, nullptr, - nullptr, &ymax, nullptr, - nullptr, nullptr, nullptr)) - continue; - - - if (!bHasMinAttr) - { - // Read the global min value for this volume - if (!GlobalMinimums.Contains(VolumeName)) - { - GlobalMinimums.Add(VolumeName, ymin); - } - else - { - // Update the min if necessary - if (ymin < GlobalMinimums[VolumeName]) - GlobalMinimums[VolumeName] = ymin; - } - } - - if (!bHasMaxAttr) - { - // Read the global max value for this volume - if (!GlobalMaximums.Contains(VolumeName)) - { - GlobalMaximums.Add(VolumeName, ymax); - } - else - { - // Update the max if necessary - if (ymax > GlobalMaximums[VolumeName]) - GlobalMaximums[VolumeName] = ymax; - } - } - } - } -} - -void -FHoudiniLandscapeTranslator::GetLayersZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums) - -{ - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray FloatData; - - for (const FHoudiniGeoPartObject& CurrentHeightfield : InHeightfieldArray) - { - // Get the current Heightfield GeoPartObject - if (CurrentHeightfield.VolumeInfo.TupleSize != 1) - continue; - - // Retrieve node id from geo part. - HAPI_NodeId NodeId = CurrentHeightfield.GeoId; - if (NodeId == -1) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - &CurrentVolumeInfo)) - continue; - - // Retrieve the volume name. - FString VolumeName; - FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); - HoudiniEngineStringPartName.ToFString(VolumeName); - - // Read the global min value for this volume - - float MinValue; - float MaxValue; - bool bHasMin = false; - bool bHasMax = false; - - if (!GlobalMinimums.Contains(VolumeName)) - { - // Extract min value - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - MinValue = FloatData[0]; - bHasMin = true; - } - } - if (!bHasMin) - { - if (VolumeName == TEXT("height")) - { - MinValue = -1000.f; - } - else - { - MinValue = 0.f; - } - } - GlobalMinimums.Add(VolumeName, MinValue); - } - - if (!GlobalMaximums.Contains(VolumeName)) - { - // Extract max value - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - MaxValue = FloatData[0]; - bHasMax = true; - } - } - if (!bHasMax) - { - if (VolumeName == TEXT("height")) - { - MaxValue = 1000.f; - } - else - { - MaxValue = 1.f; - } - } - GlobalMaximums.Add(VolumeName, MaxValue); - } - - - - } -} - -bool -FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( - const TArray& FloatLayerData, - const int32& HoudiniXSize, const int32& HoudiniYSize, - const float& LayerMin, const float& LayerMax, - const int32& LandscapeXSize, const int32& LandscapeYSize, - TArray& LayerData, const bool& NoResize) -{ - // Convert the float data to uint8 - LayerData.SetNumUninitialized(HoudiniXSize * HoudiniYSize); - - // Calculating the factor used to convert from Houdini's ZRange to [0 255] - double LayerZRange = (LayerMax - LayerMin); - double LayerZSpacing = (LayerZRange != 0.0) ? (255.0 / (double)(LayerZRange)) : 0.0; - - int32 nUnrealIndex = 0; - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y - int32 nHoudini = nY + nX * HoudiniYSize; - - // Get the double values in [0 - ZRange] - double DoubleValue = (double)FMath::Clamp(FloatLayerData[nHoudini], LayerMin, LayerMax) - (double)LayerMin; - - // Then convert it to [0 - 255] - DoubleValue *= LayerZSpacing; - - LayerData[nUnrealIndex++] = FMath::RoundToInt(DoubleValue); - } - } - - // Finally, resize the data to fit with the new landscape size if needed - if (NoResize) - return true; - - return FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( - LayerData, HoudiniXSize, HoudiniYSize, - LandscapeXSize, LandscapeYSize); -} - -bool -FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( - TArray< uint8 >& LayerData, - const int32& SizeX, const int32& SizeY, - const int32& NewSizeX, const int32& NewSizeY) -{ - if ((NewSizeX == SizeX) && (NewSizeY == SizeY)) - return true; - - bool bForceResample = true; - bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); - - TArray NewData; - if (!bResample) - { - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - - const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; - const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; - - // Expanding the Data - NewData = ExpandData( - LayerData, - 0, 0, SizeX - 1, SizeY - 1, - -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1); - } - else - { - // Resampling the data - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - NewData = ResampleData(LayerData, SizeX, SizeY, NewSizeX, NewSizeY); - } - - LayerData = NewData; - - return true; -} - -ALandscapeProxy * -FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( - const TArray< uint16 >& IntHeightData, - const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - const FTransform& TileTransform, - const int32& XSize, - const int32& YSize, - const int32& NumSectionPerLandscapeComponent, - const int32& NumQuadsPerLandscapeSection, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhsyicalMaterial, - const FString& LandscapeTileActorName, - LandscapeActorType ActorType, - ALandscape* SharedLandscapeActor, - UWorld* InWorld, - ULevel* InLevel, - FHoudiniPackageParams InPackageParams) -{ - if (!IsValid(InWorld)) - return nullptr; - - // if (!IsValid(MainLandscapeActor)) - // return nullptr; - - if ((XSize < 2) || (YSize < 2)) - return nullptr; - - if (IntHeightData.Num() != (XSize * YSize)) - return nullptr; - - if (!GEditor) - return nullptr; - - ALandscapeProxy* LandscapeTile = nullptr; - UPackage *CreatedPackage = nullptr; - - ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; - ALandscape* CachedLandscapeActor = nullptr; - - UWorld* NewWorld = nullptr; - FString MapFileName; - bool bBroadcastMaterialUpdate = false; - //... Create landscape tile ...// - { - // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos - if (ActorType == LandscapeActorType::LandscapeStreamingProxy) - { - CachedStreamingProxyActor = InWorld->SpawnActor(); - if (CachedStreamingProxyActor) - { - check(SharedLandscapeActor); - CachedStreamingProxyActor->PreEditChange(nullptr); - - // Update landscape tile properties from the main landscape actor. - CachedStreamingProxyActor->GetSharedProperties(SharedLandscapeActor); - CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; - CachedStreamingProxyActor->bCastStaticShadow = false; - - LandscapeTile = CachedStreamingProxyActor; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not spawn ALandscapeStreamingProxy with name: %s"), *(LandscapeTileActorName) ); - return nullptr; - } - } - else - { - // Create a normal landscape actor - CachedLandscapeActor = InWorld->SpawnActor(); - if (CachedLandscapeActor) - { - CachedLandscapeActor->PreEditChange(nullptr); - CachedLandscapeActor->SetLandscapeGuid(FGuid::NewGuid()); - CachedLandscapeActor->LandscapeMaterial = LandscapeMaterial; - CachedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - CachedLandscapeActor->bCastStaticShadow = false; - bBroadcastMaterialUpdate = true; - LandscapeTile = CachedLandscapeActor; - } - } - } - - - if (!LandscapeTile) - return nullptr; - - // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. - - // Autosaving the layers prevents them for being deleted with the Asset - // Save the packages created for the LayerInfos - //if ( CreatedLayerInfoPackage.Num() > 0 ) - // FEditorFileUtils::PromptForCheckoutAndSave( CreatedLayerInfoPackage, true, false ); - - // Import the landscape data - - // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue - LandscapeTile->bCastStaticShadow = false; - - // TODO: Check me? - //if (LandscapePhsyicalMaterial) - // LandscapeTile->DefaultPhysMaterial = LandscapePhsyicalMaterial; - - // Setting the layer type here. - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - - TMap> HeightmapDataPerLayers; - TMap> MaterialLayerDataPerLayer; - HeightmapDataPerLayers.Add(FGuid(), IntHeightData); - MaterialLayerDataPerLayer.Add(FGuid(), ImportLayerInfos); - - FTransform NewTileTransform, LandscapeOffset; - FIntPoint TileLoc; - - // NOTE: The following Import call will reregister all components, causing the actor to lose its transform. - // So we'll be importing the tile data as if the actor was located at the origin and fix up transforms afterward. - CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeOffset, NewTileTransform, TileLoc); - - // Before we import new tile data, remove previous tile data that will be overlapped by the new tile. - TSet OverlappingComponents; - const int32 DestMinX = TileLoc.X; - const int32 DestMinY = TileLoc.Y; - const int32 DestMaxX = TileLoc.X + XSize - 1; - const int32 DestMaxY = TileLoc.Y + YSize - 1; - - ULandscapeInfo* LandscapeInfo = LandscapeTile->GetLandscapeInfo(); - - if (LandscapeInfo) - { - // If there is a preexisting LandscapeInfo object, check for overlapping components. - - // Shrink bounds by 1,1 to avoid GetComponentsInRegion picking up extra components on all sides due to the overlap between components - LandscapeInfo->GetComponentsInRegion(DestMinX+1, DestMinY+1, DestMaxX-1, DestMaxY-1, OverlappingComponents); - TSet StaleActors; - - for (ULandscapeComponent* Component : OverlappingComponents) - { - // Remove the overlapped component from the LandscapeInfo and then from - LandscapeInfo->Modify(); - - ALandscapeProxy* Proxy = Component->GetLandscapeProxy(); - if (!IsValid(Proxy)) - continue; - check(Proxy); - FIntRect Bounds = Proxy->GetBoundingRect(); - // If this landscape proxy has no more components left, remove it from the LandscapeInfo. - LandscapeInfo->UnregisterActor(Proxy); - Proxy->Destroy(); - } - - ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); - } - - // Import tile data - // The Import function will correctly compute the tile section locations. No need to set it explicitly. - // TODO: Verify this with world composition!! - - bool bIsStandalone = LandscapeTile->GetLandscapeActor() == LandscapeTile; - - // We set the actor transform and absolute section base before importing heighfield data. This allows us to - // use the correct (quad-space) blitting region without causing overlaps during import. - - // NOTE: The following SetAbsoluteSectionBase() calls are very important. This tells the Landscape system - // where on the landscape, in quad space, a specific tile is located. This influences is used by various - // mechanisms and tools that tie into the Landscape system such as World Composition and Landscape editor tools. - // If the Section Offset for a landscape tile is wrong, it will appear in the wrong place in the World Composition - // viewer. Worse than that, none of Landscape Editor tools will work properly because it won't be able to - // locate the correct Landscape component when calculating the "Landscape Component Key" for the given word position. - // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blit functions use the - // same underlying Landscape Component Key calculation to find the correct landscape component to draw to. So if the - // Section Offsets are wrong ... all manner of chaos will follow. - // It is also super important to call ULandscapeInfo::RecreateLandscapeInfo() after change a LandscapeProxy's - // section offset in order to update the landscape's internal caches otherwise component key calculations - // won't work correctly. - - LandscapeTile->SetActorTransform(TileTransform); - LandscapeTile->SetAbsoluteSectionBase(TileLoc); - - LandscapeTile->Import( - LandscapeTile->GetLandscapeGuid(), - DestMinX, DestMinY, DestMaxX, DestMaxY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, - HeightmapDataPerLayers, NULL, - MaterialLayerDataPerLayer, ImportLayerType); - - LandscapeTile->RecreateComponentsState(); - - if (!LandscapeInfo) - { - LandscapeInfo = LandscapeTile->GetLandscapeInfo(); - } - - // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so - // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo, - // and only then are we able to "blit" the new alpha data into the correct place on the landscape. - - ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); - - // TODO: Verify whether we still need to manually set the LandscapeLightingLOD setting or whether - // calling PostEditChange() will properly fix the state. - - // Copied straight from UE source code to avoid crash after importing the landscape: - // automatically calculate a lighting LOD that won't crash lightmass (hopefully) - // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 - LandscapeTile->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); - - // ---------------------------------------------------- - // Rename the actor - // ---------------------------------------------------- - - // NOTE: The LandscapeProxy needs to be properly initialized before renaming (which is why the rename is taking - // place at the end) since the rename will trigger PostEditChange and can crash if the actor has not been - // correctly setup. - FHoudiniEngineUtils::SafeRenameActor(LandscapeTile, LandscapeTileActorName); - - if (!LandscapeTile->MarkPackageDirty()) - { - HOUDINI_LOG_WARNING(TEXT("Editor suppressed MarkPackageDirty() for landscape tile: %s"), *(LandscapeTile->GetPathName())); - } - - return LandscapeTile; -} - - -void -FHoudiniLandscapeTranslator::CalculateTileLocation( - int32 NumSectionsPerComponent, - int32 NumQuadsPerSection, - const FTransform& TileTransform, - FTransform& OutLandscapeOffset, - FTransform& OutTileTransform, - FIntPoint& OutTileLocation) -{ - // IMPORTANT: Tile Location (Section Base) needs to be in multiples of the landscape component's quad size - const int32 ComponentSize = NumSectionsPerComponent * NumQuadsPerSection; - - OutLandscapeOffset = FTransform(FRotator::ZeroRotator, FVector::ZeroVector, TileTransform.GetScale3D()); - OutTileTransform = TileTransform; - - // Sometimes the calculated tile coordinate falls on a half-unit so we would need to remove that offset first - // before calculating integer (quad space) tile location. - // For example, 123.5, should become 123. -456.5 should become -456. - const FVector TileScale = TileTransform.GetScale3D(); - const float TileCoordX = TileTransform.GetLocation().X / TileScale.X; - const float TileCoordY = TileTransform.GetLocation().Y / TileScale.Y; - - float NearestMultipleX = FMath::RoundHalfFromZero(TileCoordX / ComponentSize) * ComponentSize; - float NearestMultipleY = FMath::RoundHalfFromZero(TileCoordY / ComponentSize) * ComponentSize; - - // If the multiples are too close to the middle, offset by 0.5 to avoid some tiles snapping up and others snapping down. - if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleX), 0.5f, 0.1f)) - { - NearestMultipleX += 0.5f; - } - if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleY), 0.5f, 0.1f)) - { - NearestMultipleY += 0.5f; - } - - const float TileOffsetX = NearestMultipleX - TileCoordX; - const float TileOffsetY = NearestMultipleY - TileCoordY; - - OutTileLocation.X = FMath::RoundHalfFromZero(NearestMultipleX); - OutTileLocation.Y = FMath::RoundHalfFromZero(NearestMultipleY); - - // Adjust landscape offset to compensate for tile location / section base shifting. - OutLandscapeOffset.SetLocation( FVector(-TileOffsetX * TileScale.X, -TileOffsetY * TileScale.Y, 0.f) ); -} - - -void -FHoudiniLandscapeTranslator::GetLandscapeMaterials( - const FHoudiniGeoPartObject& InHeightHGPO, - UMaterialInterface*& OutLandscapeMaterial, - UMaterialInterface*& OutLandscapeHoleMaterial, - UPhysicalMaterial*& OutLandscapePhysicalMaterial) -{ - OutLandscapeMaterial = nullptr; - OutLandscapeHoleMaterial = nullptr; - OutLandscapePhysicalMaterial = nullptr; - - if (InHeightHGPO.Type != EHoudiniPartType::Volume) - return; - - TArray Materials; - HAPI_AttributeInfo AttribMaterials; - FHoudiniApi::AttributeInfo_Init(&AttribMaterials); - - // First, look for landscape material - { - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL, - AttribMaterials, Materials); - - // If the material attribute was not found, check the material instance attribute. - if (!AttribMaterials.exists) - { - Materials.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, - AttribMaterials, Materials); - } - - // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. - //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) - if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) - { - HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a point, primitive or detail attribute, ignoring attribute.")); - AttribMaterials.exists = false; - Materials.Empty(); - } - - if (AttribMaterials.exists && Materials.Num() > 0) - { - // Load the material - OutLandscapeMaterial = Cast(StaticLoadObject( - UMaterialInterface::StaticClass(), - nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); - } - } - - Materials.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribMaterials); - - // Then, for the hole_material - { - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - AttribMaterials, Materials); - - // If the material attribute was not found, check the material instance attribute. - if (!AttribMaterials.exists) - { - Materials.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE, - AttribMaterials, Materials); - } - - // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. - //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) - if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) - { - HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute.")); - AttribMaterials.exists = false; - Materials.Empty(); - } - - if (AttribMaterials.exists && Materials.Num() > 0) - { - // Load the material - OutLandscapeHoleMaterial = Cast< UMaterialInterface >(StaticLoadObject( - UMaterialInterface::StaticClass(), - nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); - } - } - - // Then for the physical material - OutLandscapePhysicalMaterial = GetLandscapePhysicalMaterial(InHeightHGPO); -} - -// Read the landscape component extent attribute from a heightfield -bool -FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( - const FHoudiniGeoPartObject& HoudiniGeoPartObject, - int32& MinX, int32& MaxX, - int32& MinY, int32& MaxY) -{ - // If we dont have minX, we likely dont have the others too - if (!FHoudiniEngineUtils::HapiCheckAttributeExists( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", HAPI_ATTROWNER_PRIM)) - return false; - - // Create an AttributeInfo - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); - - // Get MinX - TArray IntData; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MinX = IntData[0]; - - // Get MaxX - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MaxX = IntData[0]; - - // Get MinY - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MinY = IntData[0]; - - // Get MaxX - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MaxY = IntData[0]; - - return true; -} - -ULandscapeLayerInfoObject * -FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& InLayerName, const FString& InPackagePath, const FString& InPackageName, UPackage*& OutPackage) -{ - FString PackageFullName = InPackagePath + TEXT("/") + InPackageName; - - // See if package exists, if it does, reuse it - bool bCreatedPackage = false; - OutPackage = FindPackage(nullptr, *PackageFullName); - if (!OutPackage || OutPackage->IsPendingKill()) - { - // We need to create a new package - OutPackage = CreatePackage(*PackageFullName); - bCreatedPackage = true; - } - - if (!OutPackage || OutPackage->IsPendingKill()) - return nullptr; - - if (!OutPackage->IsFullyLoaded()) - OutPackage->FullyLoad(); - - ULandscapeLayerInfoObject* LayerInfo = nullptr; - if (!bCreatedPackage) - { - // See if we can load the layer info instead of creating a new one - LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), OutPackage, FName(*InPackageName)); - } - - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - // Create a new LandscapeLayerInfoObject in the package - LayerInfo = NewObject(OutPackage, FName(*InPackageName), RF_Public | RF_Standalone /*| RF_Transactional*/); - - // Notify the asset registry - FAssetRegistryModule::AssetCreated(LayerInfo); - } - - if (LayerInfo && !LayerInfo->IsPendingKill()) - { - LayerInfo->LayerName = FName(*InLayerName); - - // Trigger update of the Layer Info - LayerInfo->PreEditChange(nullptr); - LayerInfo->PostEditChange(); - LayerInfo->MarkPackageDirty(); - - // Mark the package dirty... - OutPackage->MarkPackageDirty(); - } - - return LayerInfo; -} - -bool -FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax( - const TArray& AllOutputs, float& OutGlobalMin, float& OutGlobalMax) -{ - OutGlobalMin = 0.f; - OutGlobalMax = 0.f; - - for (const auto& CurrentOutput : AllOutputs) - { - if (!CurrentOutput) - continue; - - if (CurrentOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - const TArray& HGPOs = CurrentOutput->GetHoudiniGeoPartObjects(); - for (const FHoudiniGeoPartObject& CurrentHGPO : HGPOs) - { - if (CurrentHGPO.Type != EHoudiniPartType::Volume) - continue; - - if (!CurrentHGPO.VolumeInfo.Name.Contains("height")) - continue; - - // We're only handling single values for now - if (CurrentHGPO.VolumeInfo.TupleSize != 1) - continue; - - // Terrains always have a ZSize of 1. - if (CurrentHGPO.VolumeInfo.ZLength != 1) - continue; - - // Values should be float - if (!CurrentHGPO.VolumeInfo.bIsFloat) - continue; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CurrentHGPO.GeoId)) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHGPO.GeoId, CurrentHGPO.PartId, &CurrentVolumeInfo)) - continue; - - // Unreal's Z values are Y in Houdini - float yMin = OutGlobalMin, yMax = OutGlobalMax; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), - CurrentHGPO.GeoId, CurrentHGPO.PartId, - nullptr, &yMin, nullptr, - nullptr, &yMax, nullptr, - nullptr, nullptr, nullptr)) - continue; - - if (yMin < OutGlobalMin) - OutGlobalMin = yMin; - - if (yMax > OutGlobalMax) - OutGlobalMax = yMax; - } - - if (OutGlobalMin > OutGlobalMax) - { - OutGlobalMin = 0.f; - OutGlobalMax = 0.f; - } - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::EnableWorldComposition() -{ - HOUDINI_LOG_WARNING(TEXT("[FHoudiniLandscapeTranslator::EnableWorldComposition] We should never enable world composition from within the plugin.")); - // Get the world - UWorld* MyWorld = nullptr; - { - // We want to create the landscape in the landscape editor mode's world - FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); - MyWorld = EditorWorldContext.World(); - } - - if (!MyWorld) - return false; - - ULevel* CurrentLevel = MyWorld->GetCurrentLevel(); - - if (!CurrentLevel) - return false; - - AWorldSettings* WorldSettings = CurrentLevel->GetWorldSettings(); - if (!WorldSettings) - return false; - - // Enable world composition in WorldSettings - WorldSettings->bEnableWorldComposition = true; - - CurrentLevel->PostEditChange(); - - return true; -} - - -bool -FHoudiniLandscapeTranslator::GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InPrimIndex, TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive property attributes - // Volumes apparently dont have prim attributes because they're converted to pointmeshes somehow... - //FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - // InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InPrimIndex); - - // .. then the point property attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InPrimIndex); - - return FoundCount > 0; -} - - -bool -FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (auto CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current Property Attribute - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); - } - - return (NumSuccess > 0); -} - - -bool -FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(const FString& BaseName, ALandscapeProxy* Landscape) -{ - // We need to cache the input landscape to a file - if (!Landscape) - return false; - - ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Save Height data to file - //FString HeightSave = TEXT("/Game/HoudiniEngine/Temp/HeightCache.png"); - FString HeightSave = BaseName + TEXT("_height.png"); - LandscapeInfo->ExportHeightmap(HeightSave); - Landscape->ReimportHeightmapFilePath = HeightSave; - - // Save each layer to a file - for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) - { - FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); - //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) - continue; - - FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); - LandscapeInfo->ExportLayer(CurrentLayerInfo, LayerSave); - - // Update the file reimport path on the input landscape for this layer - LandscapeInfo->GetLayerEditorSettings(CurrentLayerInfo).ReimportLayerFilePath = LayerSave; - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy) -{ - if (!LandscapeProxy) - return false; - - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Restore Height data from the backup file - FString ReimportFile = LandscapeProxy->ReimportHeightmapFilePath; - if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, TEXT("height"))) - HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); - - // Restore each layer from the backup file - TArray< ULandscapeLayerInfoObject* > SourceLayers; - for (int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++) - { - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) - continue; - - FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); - ReimportFile = LandscapeProxy->EditorLayerSettings[LayerIndex].ReimportLayerFilePath; - - if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, CurrentLayerName, CurrentLayerInfo)) - HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); - - SourceLayers.Add(CurrentLayerInfo); - } - - // Iterate on the landscape info's layer to remove any layer that could have been added by Houdini - for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) - { - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; - if (SourceLayers.Contains(CurrentLayerInfo)) - continue; - - // Delete the added layer - FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName; - LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName); - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::ImportLandscapeData( - ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject) -{ - // - // Code copied/edited from FEdModeLandscape::ImportData as we cannot access that function - // - if (!LandscapeInfo) - return false; - - bool IsHeight = LayerName.Equals(TEXT("height"), ESearchCase::IgnoreCase); - - int32 MinX, MinY, MaxX, MaxY; - if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - { - const FLandscapeFileResolution LandscapeResolution = { (uint32)(1 + MaxX - MinX), (uint32)(1 + MaxY - MinY) }; - - ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); - - if (IsHeight) - { - const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); - - if (!HeightmapFormat) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); - return false; - } - - FLandscapeFileResolution ImportResolution = { 0, 0 }; - - const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename); - - // display error message if there is one, and abort the import - if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); - return false; - } - - // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape - if (HeightmapInfo.PossibleResolutions.Num() > 1) - { - if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined")); - return false; - } - else - { - ImportResolution = LandscapeResolution; - } - } - - // display warning message if there is one and allow user to cancel - if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); - - // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape - // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is - if (HeightmapInfo.PossibleResolutions.Num() == 1) - { - ImportResolution = HeightmapInfo.PossibleResolutions[0]; - if (ImportResolution != LandscapeResolution) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); - } - - FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution); - if (ImportData.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); - return false; - } - - TArray Data; - if (ImportResolution != LandscapeResolution) - { - // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked - // so that reimports behave the same as the initial import :) - - const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; - const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; - - Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16)); - - ExpandData(Data.GetData(), ImportData.Data.GetData(), - 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, - -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); - } - else - { - Data = MoveTemp(ImportData.Data); - } - - //FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap")); - - FHeightmapAccessor HeightmapAccessor(LandscapeInfo); - HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData()); - } - else - { - // We're importing a Landscape layer - if (!LayerInfoObject || LayerInfoObject->IsPendingKill()) - return false; - - const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); - if (!WeightmapFormat) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); - return false; - } - - FLandscapeFileResolution ImportResolution = { 0, 0 }; - - const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName)); - - // display error message if there is one, and abort the import - if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); - return false; - } - - // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape - if (WeightmapInfo.PossibleResolutions.Num() > 1) - { - if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined")); - return false; - } - else - { - ImportResolution = LandscapeResolution; - } - } - - // display warning message if there is one and allow user to cancel - if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); - - // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape - // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is - if (WeightmapInfo.PossibleResolutions.Num() == 1) - { - ImportResolution = WeightmapInfo.PossibleResolutions[0]; - if (ImportResolution != LandscapeResolution) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); - } - - FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution); - - if (ImportData.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); - return false; - } - - TArray Data; - if (ImportResolution != LandscapeResolution) - { - // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked - // so that reimports behave the same as the initial import :) - const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; - const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; - - Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8)); - - ExpandData(Data.GetData(), ImportData.Data.GetData(), - 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, - -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); - } - else - { - Data = MoveTemp(ImportData.Data); - } - - //FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer")); - FAlphamapAccessor AlphamapAccessor(LandscapeInfo, LayerInfoObject); - AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None); - } - } - - return true; -} - -UTexture2D* -FHoudiniLandscapeTranslator::CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& InFloatBuffer, - const float& InMin, - const float& InMax) -{ - - // Convert the float values to uint8 - double Range = (double)InMax - (double)InMin; - TArray IntBuffer; - IntBuffer.SetNum(InFloatBuffer.Num()); - for(int32 i = 0; i < InFloatBuffer.Num(); i++) - { - double dNormalizedValue = ((double)InFloatBuffer[i] - (double)InMin) / (double)Range; - IntBuffer[i] = (uint8)(dNormalizedValue * 255.0); - } - - return FHoudiniLandscapeTranslator::CreateUnrealTexture( - InPackageParams, LayerName, InXSize, InYSize, IntBuffer); -} - -UTexture2D* -FHoudiniLandscapeTranslator::CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& IntBuffer) -{ - FHoudiniPackageParams MyPackageParams = InPackageParams; - MyPackageParams.ObjectName = LayerName; - MyPackageParams.PackageMode = EPackageMode::CookToTemp; - MyPackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - FString CreatedPackageName; - UPackage* Package = MyPackageParams.CreatePackageForObject(CreatedPackageName); - if (!Package || Package->IsPendingKill()) - return nullptr; - - // Create new texture object. - UTexture2D * Texture = NewObject(Package, UTexture2D::StaticClass(), *LayerName, RF_Public | RF_Standalone); - - // Add/Update meta information to package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *LayerName); - - /*// Texture Settings - Texture->PlatformData = new FTexturePlatformData(); - Texture->PlatformData->SizeX = InXSize; - Texture->PlatformData->SizeY = InYSize; - Texture->PlatformData->PixelFormat = PF_R8G8B8A8;*/ - - // Initialize texture source. - Texture->Source.Init(InXSize, InYSize, 1, 1, TSF_BGRA8); - - // Lock the texture. - uint8 * MipData = Texture->Source.LockMip(0); - - // Create base map. - uint8* DestPtr = nullptr; - uint32 SrcWidth = InXSize; - uint32 SrcHeight = InYSize; - const uint8 * SrcData = &IntBuffer[0]; - - for (uint32 y = 0; y < SrcHeight; y++) - { - DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; - - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth + x; - - *DestPtr++ = *(SrcData + DataOffset); // B greyscale, same value 3 times - *DestPtr++ = *(SrcData + DataOffset); // G - *DestPtr++ = *(SrcData + DataOffset); // R - - *DestPtr++ = 0xFF; // A to 1 - } - } - - // Unlock the texture. - Texture->Source.UnlockMip(0); - - // Texture creation parameters. - //Texture->SRGB = TextureParameters.bSRGB; - Texture->CompressionSettings = TC_Grayscale; - Texture->CompressionNoAlpha = true; - Texture->MipGenSettings = TMGS_NoMipmaps; - - // Set the Source Guid/Hash if specified. - /* - if ( TextureParameters.SourceGuidHash.IsValid() ) - { - Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); - } - */ - - // Updating Texture & mark it as unsaved - //Texture->AddToRoot(); - //Texture->UpdateResource(); - Package->MarkPackageDirty(); - - Texture->PostEditChange(); - - FString PathName = Texture->GetPathName(); - HOUDINI_LOG_MESSAGE(TEXT("Created texture when for %s in %s"), *LayerName, *PathName); - - return Texture; -} - -UPhysicalMaterial* -FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO) -{ - // See if we have assigned a physical material to this layer via attribute - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - TArray AttributeValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InLayerHGPO.GeoId, InLayerHGPO.PartId, - HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL, - AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) - return nullptr; - - if (AttributeValues.Num() > 0) - { - return LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); - } - - return nullptr; -} - -ULandscapeLayerInfoObject* -FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName) -{ - // See if we have assigned a landscape layer info object to this layer via attribute - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - TArray AttributeValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InLayerHGPO.GeoId, InLayerHGPO.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO, - AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) - return nullptr; - - if (AttributeValues.Num() > 0) - { - ULandscapeLayerInfoObject* FoundLayerInfo = LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); - if (!FoundLayerInfo || FoundLayerInfo->IsPendingKill()) - return nullptr; - - // The layer info's name must match this layer's name or Unreal will not like this! - if (!FoundLayerInfo->LayerName.IsEqual(InLayerName)) - { - FString NameStr = InLayerName.ToString(); - HOUDINI_LOG_WARNING(TEXT("Failed to use the assigned layer info object for %s by the unreal_landscape_layer_info attribute as the found layer info object's layer name does not match."), *NameStr); - } - - return FoundLayerInfo; - } - - return nullptr; -} - - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniLandscapeTranslator.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniEngineString.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPackageParams.h" +#include "HoudiniStringResolver.h" +#include "HoudiniInput.h" + +#include "ObjectTools.h" +#include "FileHelpers.h" +#include "Editor.h" +#include "LandscapeLayerInfoObject.h" +#include "LandscapeStreamingProxy.h" +#include "LandscapeInfo.h" +#include "LandscapeEdit.h" +#include "AssetRegistryModule.h" +#include "PackageTools.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "UObject/UnrealType.h" + +#include "GameFramework/WorldSettings.h" +#include "HAL/PlatformFilemanager.h" +#include "Misc/Paths.h" +#include "Engine/LevelStreamingDynamic.h" +#include "Modules/ModuleManager.h" +#include "AssetToolsModule.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "LevelUtils.h" +#include "Factories/WorldFactory.h" +#include "Misc/Guid.h" +#include "Engine/LevelBounds.h" + +#include "HAL/IConsoleManager.h" +#include "Engine/AssetManager.h" +#include "Engine/LevelStreamingAlwaysLoaded.h" +#include "LandscapeEditor/Private/LandscapeEdMode.h" +#include "Misc/AssetRegistryInterface.h" +#include "Misc/StringFormatArg.h" +#include "Engine/WorldComposition.h" + +#if WITH_EDITOR + #include "LandscapeEditorModule.h" + #include "LandscapeFileFormatInterface.h" + #include "EditorLevelUtils.h" + #include "WorldBrowserModule.h" + #include "EditorLevelUtils.h" + #include "Misc/WorldCompositionUtility.h" +#endif + +static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( + TEXT("HoudiniEngine.ExportLandscapeTextures"), + 0, + TEXT("If enabled, landscape layers and heightmap will be exported as textures in the temp directory when converting a Heightfield to a Landscape.\n") + TEXT("0: Disabled\n") + TEXT("1: Enabled\n") +); + +typedef FHoudiniEngineUtils FHUtils; + +bool +FHoudiniLandscapeTranslator::CreateLandscape( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedOutputs, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* InWorld, // Persistent / root world for the landscape + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniPackageParams InPackageParams, + TArray& OutCreatedPackages +) +{ + check(LayerMinimums.Contains(TEXT("height"))); + check(LayerMaximums.Contains(TEXT("height"))); + + float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); + float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); + + if (!InOutput || InOutput->IsPendingKill()) + return false; + + // Get the height map. + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + if (!Heightfield) + return false; + + if (Heightfield->Type != EHoudiniPartType::Volume) + return false; + + const HAPI_NodeId GeoId = Heightfield->GeoId; + const HAPI_PartId PartId = Heightfield->PartId; + + // Construct the identifier of the Heightfield geo part. + FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); + HeightfieldIdentifier.PartName = Heightfield->PartName; + + FString NodeNameSuffix = GetActorNameSuffix(InPackageParams.PackageMode); + + TArray IntData; + TArray StrData; + // Output attributes will be stored on the Output object and will be used again during baking to determine + // where content should be baked to and what they should be named, etc. + // At the end of this function, the output attributes and tokens will be copied to the output object. + TMap OutputAttributes; + TMap OutputTokens; + FHoudiniAttributeResolver Resolver; + InPackageParams.UpdateTokensFromParams(InWorld, OutputTokens); + + bool bHasTile = Heightfield->VolumeTileIndex >= 0; + + // --------------------------------------------- + // Attribute: unreal_landscape_tile_actor_type, unreal_landscape_streaming_proxy (v1) + // --------------------------------------------- + // Determine the actor type for the tile + bool bCreateLandscapeStreamingProxy = false; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + LandscapeActorType TileActorType = LandscapeActorType::LandscapeActor; + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0) + { + TileActorType = static_cast(IntData[0]); + } + } + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0 && IntData[0] != 0) + TileActorType = LandscapeActorType::LandscapeStreamingProxy; + } + + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, FString::FromInt(static_cast(TileActorType))); + + // --------------------------------------------- + // Attribute: unreal_landscape_actor_name + // --------------------------------------------- + // Retrieve the name of the main Landscape actor to look for + FString SharedLandscapeActorName = DefaultLandscapeActorPrefix + "SharedLandscape"; // If this is an empty string, don't affirm a root landscape actor? + StrData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, AttributeInfo, StrData, 1)) + { + if (StrData.Num() > 0 && !StrData[0].IsEmpty()) + SharedLandscapeActorName = StrData[0]; + } + + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); + + // --------------------------------------------- + // Attribute: unreal_level_path + // --------------------------------------------- + // FString LevelPath = bHasTile ? "{world}/Landscape/Tile{tile}" : "{world}/Landscape"; + FString LevelPath; + TArray LevelPaths; + if (FHoudiniEngineUtils::GetLevelPathAttribute(GeoId, PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + LevelPath = LevelPaths[0]; + } + if (!LevelPath.IsEmpty()) + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); + + // --------------------------------------------- + // Attribute: unreal_output_name + // --------------------------------------------- + FString LandscapeTileActorName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; + TArray AllOutputNames; + if (!FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames)) + { + if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) + LandscapeTileActorName = AllOutputNames[0]; + } + OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); + + // Streaming proxy actors/tiles requires a "main" landscape actor + // that contains the shared landscape state. + bool bRequiresSharedLandscape = false; + if (TileActorType == LandscapeActorType::LandscapeStreamingProxy) + bRequiresSharedLandscape = true; + + // ---------------------------------- + // Inject landscape specific tokens + // ---------------------------------- + if (bHasTile) + { + const FString TileValue = FString::FromInt(Heightfield->VolumeTileIndex); + // Tile value needs to go into Output arguments to be available during the bake. + OutputTokens.Add(TEXT("tile"), TileValue); + } + + // ---------------------------------- + // Expand string arguments for various landscape naming aspects. + // ---------------------------------- + + // Update resolver attributes and tokens before we start resolving attributes. + Resolver.SetCachedAttributes(OutputAttributes); + Resolver.SetTokensFromStringMap(OutputTokens); + + SharedLandscapeActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); + SharedLandscapeActorName += NodeNameSuffix; + + LandscapeTileActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, LandscapeTileActorName); + LandscapeTileActorName += NodeNameSuffix; + + LevelPath = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); + + FString TileName = LandscapeTileActorName; + + // Note that relative level paths are always interpreted as relative to the default output directory (temp / bake). + // FString TilePackagePath = FPaths::Combine(DefaultOutputPath, LevelPath); + FString TilePackagePath = Resolver.ResolveFullLevelPath(); + + // This crashes UE if the package name does not resolve + //FString TileMapFileName = FPackageName::LongPackageNameToFilename(TilePackagePath, FPackageName::GetMapPackageExtension()); + + FText NotValidReason; + bool bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); + if (!bIsValidLongName) + { + // Try a more naive approach + TilePackagePath = FPaths::Combine(InPackageParams.BakeFolder, LevelPath); + bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); + } + + if (!bIsValidLongName) + { + HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] TilePackagePath is not a valid long name. Reason: %s"), *(NotValidReason.ToString())); + return false; + } + + FString TileMapFileName; + if (!FPackageName::TryConvertLongPackageNameToFilename(TilePackagePath, TileMapFileName, FPackageName::GetMapPackageExtension())) + { + // Rather stop here than crash! + HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] Failed to resolve the TilePackagePath: %s"), *(TilePackagePath)); + return false; + } + + // Find the package for both the world and the tile. + // The world should contain the main landscape actor while + // the tile will contain a Landscape, LandscapeProxy or LandscapeStreamingProxy depending on user settings. + + bool bTileisStreamingProxy = (TileActorType == LandscapeActorType::LandscapeStreamingProxy); + UWorld* TileWorld = nullptr; // World from which to spawn tile actor + ULevel* TileLevel = nullptr; // Level in which to spawn tile actor + ALandscapeProxy* TileActor = nullptr; // Spawned tile actor. + + // ---------------------------------- + // Update package parameters for this tile + // ---------------------------------- + + // NOTE: we don't manually inject a tile number in the object name. This should + // already be encoded in the TileName string. + FHoudiniPackageParams TilePackageParams = InPackageParams; + TilePackageParams.ObjectName = TileName; + + FHoudiniPackageParams LayerPackageParams = InPackageParams; + if (bRequiresSharedLandscape) + { + // Note that layers are shared amongst all the tiles for a given landscape. + LayerPackageParams.ObjectName = SharedLandscapeActorName; + } + else + { + // This landscape tile is a standalone landscape and should have its own material layers. + LayerPackageParams.ObjectName = TileName; + } + + // See if the current heightfield has an unreal_material or unreal_hole_material assigned to it + UMaterialInterface* LandscapeMaterial = nullptr; + UMaterialInterface* LandscapeHoleMaterial = nullptr; + UPhysicalMaterial* LandscapePhysicalMaterial = nullptr; + FHoudiniLandscapeTranslator::GetLandscapeMaterials(*Heightfield, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial); + + // Extract the float data from the Heightfield. + const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; + TArray FloatValues; + float FloatMin, FloatMax; + if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) + return false; + + // Heightfield conversions should always use the global float min/max + // since they need to be calculated externally, potentially across multiple tiles. + FloatMin = fGlobalMin; + FloatMax = fGlobalMax; + + // Get the Unreal landscape size + int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; + int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; + int32 UnrealTileSizeX = -1; + int32 UnrealTileSizeY = -1; + int32 NumSectionPerLandscapeComponent = -1; + int32 NumQuadsPerLandscapeSection = -1; + + if (!FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + HoudiniHeightfieldXSize, HoudiniHeightfieldYSize, + UnrealTileSizeX, UnrealTileSizeY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection)) + { + return false; + } + + // ---------------------------------------------------- + // Export of layer textures + // ---------------------------------------------------- + // Export textures, if enabled. Mostly used for debugging at the moment. + bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; + if (bExportTexture) + { + // Export raw height data to texture + FString TextureName = TilePackageParams.ObjectName + TEXT("_height_raw"); + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + FloatValues, + FloatMin, + FloatMax); + } + + // Look for all the layers/masks corresponding to the current heightfield. + TArray< const FHoudiniGeoPartObject* > FoundLayers; + FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); + + // Get the updated layers. + TArray LayerInfos; + + if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, + LayerMinimums, LayerMaximums, LayerInfos, false, + TilePackageParams, + LayerPackageParams, + OutCreatedPackages)) + return false; + + // Convert Houdini's heightfield data to Unreal's landscape data + TArray IntHeightData; + FTransform TileTransform; + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + FloatValues, VolumeInfo, + UnrealTileSizeX, UnrealTileSizeY, + FloatMin, FloatMax, + IntHeightData, TileTransform)) + return false; + + // ---------------------------------------------------- + // Property changes that we want to track + // ---------------------------------------------------- + + bool bModifiedLandscapeActor = false; + bool bModifiedSharedLandscapeActor = false; + bool bSharedLandscapeMaterialChanged = false; + bool bSharedLandscapeHoleMaterialChanged = false; + bool bSharedPhysicalMaterialChanged = false; + bool bTileLandscapeMaterialChanged = false; + bool bTileLandscapeHoleMaterialChanged = false; + bool bTilePhysicalMaterialChanged = false; + bool bCreatedMap = false; + bool bCreatedTileActor = false; + bool bHeightLayerDataChanged = false; + bool bCustomLayerDataChanged = false; + + // ---------------------------------------------------- + // Calculate Tile location and landscape offset + // ---------------------------------------------------- + FTransform LandscapeTransform, NewTileTransform; + FIntPoint TileLoc; + + // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate + // for any landscape shifts due to section base aligment offsets. + CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeTransform, NewTileTransform, TileLoc); + + // ---------------------------------------------------- + // Find or create *shared* landscape + // ---------------------------------------------------- + + ALandscape* SharedLandscapeActor = nullptr; + bool bCreatedSharedLandscape = false; + + if (bRequiresSharedLandscape) + { + // Streaming proxy tiles always require a "shared landscape" that contains the + // various landscape properties to be shared amongst all the tiles. + AActor* FoundActor = nullptr; + SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, SharedLandscapeActorName, FoundActor); + + bool bIsValidSharedLandscape = IsValid(SharedLandscapeActor); + + if (bIsValidSharedLandscape) + { + // We have a possible valid shared landscape. Check whether it is compatible with the Houdini volume. + bool bIsCompatible = IsLandscapeInfoCompatible( + SharedLandscapeActor->GetLandscapeInfo(), + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection); + bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); + if (!bIsCompatible) + { + // Current landscape actor is not compatible. Destroy it. + SharedLandscapeActor->Destroy(); + SharedLandscapeActor = nullptr; + bIsValidSharedLandscape = false; + } + } + + if (!bIsValidSharedLandscape) + { + // Create and configure the main landscape actor. + // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos + SharedLandscapeActor = InWorld->SpawnActor(); + if (SharedLandscapeActor) + { + CreatedUntrackedOutputs.Add( SharedLandscapeActor ); + + // NOTE that share landscape is always located at the origin, but not the tile actors. The + // tiles are properly transformed. + SharedLandscapeActor->SetActorTransform(LandscapeTransform); + // If we working with landscape tiles, this actor will become the "Main landscape" actor but + // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. + SharedLandscapeActor->bCanHaveLayersContent = false; + SharedLandscapeActor->ComponentSizeQuads = NumQuadsPerLandscapeSection*NumSectionPerLandscapeComponent; + SharedLandscapeActor->NumSubsections = NumSectionPerLandscapeComponent; + SharedLandscapeActor->SubsectionSizeQuads = NumQuadsPerLandscapeSection; + SharedLandscapeActor->SetLandscapeGuid( FGuid::NewGuid() ); + SharedLandscapeActor->bCastStaticShadow = false; + for (const auto& ImportLayerInfo : LayerInfos) + { + SharedLandscapeActor->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(ImportLayerInfo.LayerInfo)); + } + SharedLandscapeActor->CreateLandscapeInfo(); + bCreatedSharedLandscape = true; + + // Ensure the landscape actor name and label matches `LandscapeActorName`. + FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, SharedLandscapeActorName); + SharedLandscapeActor->MarkPackageDirty(); + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(InWorld->GetPathName()) ); + return false; + } + } + else + { + // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Reusing existing shared landscape...")); + } + } + + if (SharedLandscapeActor) + { + // Ensure the existing landscape actor transform is correct. + SharedLandscapeActor->SetActorTransform(LandscapeTransform); + + bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; + bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; + if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + { + DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + } + + if (bSharedLandscapeMaterialChanged) + { + SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + + } + if (bSharedLandscapeHoleMaterialChanged) + { + SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + + } + + bSharedPhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? (SharedLandscapeActor->DefaultPhysMaterial != LandscapePhysicalMaterial) : false; + if (bSharedPhysicalMaterialChanged) + { + DoPreEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); + SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; + //SharedLandscapeActor->ChangedPhysMaterial(); + } + } + + // ---------------------------------------------------- + // Find Landscape actor / tile + // ---------------------------------------------------- + + // Find an actor with the given name. The TileWorld and TileLevel returned should be + // used to spawn the new actor, if the actor itself could not be found. + //bool bCreatedPackage = false; + // TileActor = FindExistingLandscapeActor( + // InWorld, InOutput, ValidLandscapes, + // UnrealLandscapeSizeX, UnrealLandscapeSizeY, LandscapeTileActorName, + // LevelPath, TileWorld, TileLevel, bCreatedPackage); + + // Currently the Temp Cook mode is not concerned with creating packages. This will, at the time of writing, + // exclusively be dealt with during Bake mode so don't bother with searching / creating other packages. + + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (IsValid(HAC)) + { + TileWorld = HAC->GetWorld(); + TileLevel = HAC->GetComponentLevel(); + } + else + { + TileWorld = InWorld; + TileLevel = InWorld->PersistentLevel; + } + + check(TileWorld); + check(TileLevel); + + AActor* FoundActor = nullptr; + if (InPackageParams.PackageMode == EPackageMode::Bake) + { + // When baking, See if we can find any landscape / proxy actors for this tile in the TileLevel. + // If we find any actors that match the name but not the type, or the actors are pending kill, then + // rename them so that we can spawn new actors. + switch (TileActorType) + { + case LandscapeActorType::LandscapeActor: + TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); + break; + case LandscapeActorType::LandscapeStreamingProxy: + TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); + break; + default: + TileActor = nullptr; + } + } + else + { + // In temp mode, only consider our previous output landscapes, + // or our input landscapes that have the "update input landscape" option enabled + ALandscapeProxy* FoundLandscapeProxy = nullptr; + + // Try to see if we have an input landscape that matches the size of the current HGPO + for (int nIdx = 0; nIdx < InputLandscapesToUpdate.Num(); nIdx++) + { + ALandscapeProxy* CurrentInputLandscape = InputLandscapesToUpdate[nIdx]; + if (!CurrentInputLandscape) + continue; + + ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); + if (!CurrentInfo) + continue; + + int32 InputMinX = 0; + int32 InputMinY = 0; + int32 InputMaxX = 0; + int32 InputMaxY = 0; + CurrentInfo->GetLandscapeExtent(InputMinX, InputMinY, InputMaxX, InputMaxY); + + // If the full size matches, we'll update that input landscape + bool SizeMatch = false; + if ((InputMaxX - InputMinX + 1) == UnrealTileSizeX && (InputMaxY - InputMinY + 1) == UnrealTileSizeY) + SizeMatch = true; + + // HF and landscape don't match, try another one + if (!SizeMatch) + continue; + + // Replace FoundLandscape by that input landscape + FoundLandscapeProxy = CurrentInputLandscape; + + // We've found a valid input landscape, remove it from the input array so we don't try to update it multiple times + InputLandscapesToUpdate.RemoveAt(nIdx); + break; + } + + if (!FoundLandscapeProxy) + { + // Try to see if we can reuse one of our previous output landscape. + // Keep track of the previous cook's landscapes + TMap& OldOutputObjects = InOutput->GetOutputObjects(); + for (auto& CurrentLandscape : OldOutputObjects) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentLandscape.Value.OutputObject); + if (!LandscapePtr) + continue; + + FoundLandscapeProxy = LandscapePtr->GetRawPtr(); + if (!FoundLandscapeProxy) + { + // We may need to manually load the object + //OldLandscapeProxy = LandscapePtr->GetSoftPtr().LoadSynchronous(); + FoundLandscapeProxy = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); + } + + if (!IsValid(FoundLandscapeProxy)) + continue; + + // We need to make sure that this landscape is not one of our input landscape + // This would happen if we were previously updating it, but just turned the option off + // In that case, the landscape would be in both our inputs and outputs, + // but with the "Update Input Data" option off + if (InAllInputLandscapes.Contains(FoundLandscapeProxy)) + { + FoundLandscapeProxy = nullptr; + continue; + } + + // If we found a possible candidate, make sure that its size matches ours as we can only update a landscape tile of the same size + if (!IsLandscapeTileCompatible( + FoundLandscapeProxy, + UnrealTileSizeX-1, + UnrealTileSizeY-1, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection)) + { + FoundLandscapeProxy = nullptr; + continue; + } + + if (SharedLandscapeActor) + { + if (FoundLandscapeProxy->GetLandscapeGuid() != SharedLandscapeActor->GetLandscapeGuid()) + { + FoundLandscapeProxy = nullptr; + continue; + } + } + + // TODO: we probably need to do some more checks with tiled landscapes as well? + + // We found a valid Candidate! + if (FoundLandscapeProxy) + break; + } + } + + if (IsValid(FoundLandscapeProxy)) + { + TileActor = FoundLandscapeProxy; + if (TileActor->GetName() != LandscapeTileActorName) + { + // Ensure the TileActor is named correctly + FHoudiniEngineUtils::SafeRenameActor(TileActor, LandscapeTileActorName); + } + } + } + + // NOTE: We don't need to delete old landscape tiles (FoundActor != TileActor) here. That is an old + // output that should get cleaned up automatically. + + if (IsValid(TileActor)) + { + check(!(TileActor->IsPendingKill())); + + // ---------------------------------------------------- + // Check landscape compatibility + // ---------------------------------------------------- + + bool bIsCompatible = IsLandscapeTileCompatible( + TileActor, + UnrealTileSizeX-1, + UnrealTileSizeY-1, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection); + + bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(TileActor, TileActorType); + + if (!bIsCompatible) + { + // Can't reuse this tile actor since the landscape dimensions doesn't match or the actor type has changed. + if (TileActor->IsA()) + { + // This landscape tile needs to be unregistered from the landscape info. + ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); + if (IsValid(LandscapeInfo)) + { + LandscapeInfo->UnregisterActor(TileActor); + } + } + TileActor->Destroy(); + TileActor = nullptr; + } + } + + // ---------------------------------------------------- + // Create or update landscape / tile. + // ---------------------------------------------------- + // Note that a single heightfield generated in Houdini can be treated + // as either a landscape tile (LandscapeStreamingProxy) or a standalone + // landscape (ALandscape). This determination is made purely from user specified + // attributes. No "clever logic" in here, please! + + ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; + ALandscape* CachedLandscapeActor = nullptr; + ULandscapeInfo *LandscapeInfo; + + + if (!TileActor) + { + // Create a new Landscape tile in the TileWorld + TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( + IntHeightData, LayerInfos, TileTransform, + UnrealTileSizeX, UnrealTileSizeY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, + LandscapeTileActorName, + TileActorType, + SharedLandscapeActor, + TileWorld, + TileLevel, + InPackageParams); + + if (!TileActor || !TileActor->IsValidLowLevel()) + return false; + + // Update the visibility mask / layer if we have any + for (auto CurrLayerInfo : LayerInfos) + { + if (CurrLayerInfo.LayerInfo && CurrLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + TileActor->VisibilityLayer = CurrLayerInfo.LayerInfo; + TileActor->VisibilityLayer->bNoWeightBlend = true; + TileActor->VisibilityLayer->AddToRoot(); + } + } + + LandscapeInfo = TileActor->GetLandscapeInfo(); + + bCreatedTileActor = true; + bTileLandscapeMaterialChanged = true; + bTileLandscapeHoleMaterialChanged = true; + bTilePhysicalMaterialChanged = true; + bHeightLayerDataChanged = true; + bCustomLayerDataChanged = true; + } + else + { + LandscapeInfo = TileActor->GetLandscapeInfo(); + + // Always update the transform, even if the HGPO transform hasn't changed, + // If we change the number of tiles, or switch from outputting single tile to multiple, + // then its fairly likely that the unreal transform has changed even if the + // Houdini Transform remained the same + if (!TileActor->GetTransform().Equals(TileTransform)) + { + // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Updating tile transform: %s"), *(TileTransform.ToString())); + TileActor->SetActorTransform(TileTransform); + TileActor->SetAbsoluteSectionBase(TileLoc); +#if WITH_EDITOR + GEngine->BroadcastOnActorMoved(TileActor); +#endif + LandscapeInfo->RecreateLandscapeInfo(InWorld,true); + } + + // Update existing landscape / tile + if (SharedLandscapeActor && TileActorType == LandscapeActorType::LandscapeStreamingProxy) + { + TileActor->FixupSharedData(SharedLandscapeActor); + + // This is a tile with a shared landscape. + // Check whether the LandscapeActor changed. If so, update the proxy's shared properties. + CachedStreamingProxyActor = Cast(TileActor); + if (SharedLandscapeActor) + { + if (CachedStreamingProxyActor) + bModifiedLandscapeActor = CachedStreamingProxyActor->LandscapeActor != SharedLandscapeActor; + else + bModifiedLandscapeActor = true; + + if (bModifiedLandscapeActor) + { + CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; + // We need to force a state update through PostEditChangeProperty here in order to initialize + // since we're about to perform additional data updates on this tile. + DoPostEditChangeProperty(CachedStreamingProxyActor, "LandscapeActor"); + } + } + else + { + CachedStreamingProxyActor->LandscapeActor = nullptr; + } + + } + else + { + // This is a standalone tile / landscape actor. + CachedLandscapeActor = Cast(TileActor); + } + + ULandscapeInfo* PreviousInfo = TileActor->GetLandscapeInfo(); + if (!PreviousInfo) + return false; + + FIntRect BoundingRect = TileActor->GetBoundingRect(); + FIntPoint SectionOffset = TileActor->GetSectionBaseOffset(); + + // Landscape region to update + const int32 MinX = TileLoc.X; + const int32 MaxX = TileLoc.X + UnrealTileSizeX - 1; + const int32 MinY = TileLoc.Y; + const int32 MaxY = TileLoc.Y + UnrealTileSizeY - 1; + + // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. + // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools + // though the *Accessors do additional things like update normals and foliage. + + // Update height if it has been changed. + if (Heightfield->bHasGeoChanged) + { + // It is important to update the heightmap through the this since it will properly + // update normals and foliage. + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); + + bHeightLayerDataChanged = true; + } + + // Update the layers on the landscape. + for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) + { + FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + + if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + TileActor->VisibilityLayer = NextUpdatedLayerInfo.LayerInfo; + TileActor->VisibilityLayer->bNoWeightBlend = true; + TileActor->VisibilityLayer->AddToRoot(); + } + + bCustomLayerDataChanged = true; + } + + bModifiedLandscapeActor = true; + } + + // ---------------------------------------------------- + // Update tile materials + // ---------------------------------------------------- + // TODO: These material updates can possibly be skipped if we have already performed this + // check on a SharedLandscape. + bTileLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (TileActor->GetLandscapeMaterial() != LandscapeMaterial) : false; + bTileLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (TileActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); + + if (bTileLandscapeMaterialChanged) + TileActor->LandscapeMaterial = LandscapeMaterial; + + if (bTileLandscapeHoleMaterialChanged) + TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + + bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; + if (bTilePhysicalMaterialChanged) + { + DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); + TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; + //TileActor->ChangedPhysMaterial(); + } + + // ---------------------------------------------------- + // Apply actor tags + // ---------------------------------------------------- + + // See if we have unreal_tag_ attribute + TArray Tags; + if (TileActor && FHoudiniEngineUtils::GetUnrealTagAttributes(GeoId, PartId, Tags)) + { + TileActor->Tags = Tags; + } + + // ---------------------------------------------------- + // Update actor states based on data updates + // ---------------------------------------------------- + // Based on ALandscape and ALandscapeStreamingProxy PostEditChangeProperty() implementations, + // effect appropriate state updates based on the property updates that was performed in + // the above code. + + if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + { + check(SharedLandscapeActor); + DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + } + + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + { + check(TileActor); + // Tile material changes are only processed if it wasn't already done for a shared + // landscape since the shared landscape should have already propagated the changes to associated proxies. + DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); + } + + if (bSharedPhysicalMaterialChanged) + { + check(SharedLandscapeActor); + DoPostEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); + } + + if (bTilePhysicalMaterialChanged) + { + check(TileActor); + DoPostEditChangeProperty(TileActor, "DefaultPhysMaterial"); + } + + if (bModifiedSharedLandscapeActor) + { + SharedLandscapeActor->PostEditChange(); + } + + if (bModifiedLandscapeActor) + { + TileActor->PostEditChange(); + } + + { + FLandscapeEditDataInterface LandscapeEdit(TileActor->GetLandscapeInfo()); + LandscapeEdit.RecalculateNormals(); + } + + if (LandscapeInfo) + { + LandscapeInfo->RecreateLandscapeInfo(InWorld, true); + } + + // Add objects to the HAC output. + SetLandscapeActorAsOutput( + InOutput, + InAllInputLandscapes, + OutputAttributes, + OutputTokens, + SharedLandscapeActor, + SharedLandscapeActorParent, + bCreatedSharedLandscape, + HeightfieldIdentifier, + TileActor, + InPackageParams.PackageMode); + + return true; +} + +bool +FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( + const ULandscapeInfo* LandscapeInfo, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection + ) +{ + if (!IsValid(LandscapeInfo)) + return false; + + + if (LandscapeInfo->ComponentNumSubsections != InNumSectionsPerComponent) + return false; + + if (LandscapeInfo->SubsectionSizeQuads != InNumQuadsPerSection) + return false; + + return true; +} + +bool +FHoudiniLandscapeTranslator::IsLandscapeTileCompatible( + const ALandscapeProxy* TileActor, + const int32 InTileSizeX, + const int32 InTileSizeY, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection +) +{ + check(TileActor); + + // NOTE: We can't compare landscape extents here since the Houdini only knows about the size for single tile. + // and LandscapeInfo will only return extents for the *loaded* landscape tiles. + + // TODO: Add more robust checks to determine landscape compatibility. + + if (!IsLandscapeInfoCompatible(TileActor->GetLandscapeInfo(), InNumSectionsPerComponent, InNumQuadsPerSection)) + return false; + + const FIntRect Bounds = TileActor->GetBoundingRect(); + const FIntPoint Size = Bounds.Size(); + if (Size.X != InTileSizeX && Size.Y != InTileSizeY) + return false; + + return true; +} + + +bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, LandscapeActorType ActorType) +{ + if (!IsValid(Actor)) + return false; + + switch (ActorType) + { + case LandscapeActorType::LandscapeActor: + return Actor->IsA(); + break; + case LandscapeActorType::LandscapeStreamingProxy: + return Actor->IsA(); + break; + default: + break; + } + + return false; +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage, + const EPackageMode& InPackageMode) +{ + if (InPackageMode == EPackageMode::Bake) + return FindExistingLandscapeActor_Bake(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); + else + return FindExistingLandscapeActor_Temp(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + bCreatedPackage = false; + + // // Locate landscape proxy actor when running in baked mode + // AActor* FoundActor = nullptr; + ALandscapeProxy* OutActor = nullptr; + // OutActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); + // // OutActor = FHoudiniEngineUtils::FindActorInWorld(InWorld, FName(InActorName)); + // if (FoundActor && FoundActor != OutActor) + // FoundActor->Destroy(); // nuke it! + // + // if (OutActor) + // { + // // TODO: make sure that the found is actor is actually assigned to the level defined by package path. + // // If the found actor is not from that level, it should be moved there. + // + // OutWorld = OutActor->GetWorld(); + // OutLevel = OutActor->GetLevel(); + // } + // else + { + // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. + bool bActorInWorld = false; + const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + InWorld, + InPackagePath, + true, + OutWorld, + OutLevel, + bCreatedPackage, + bActorInWorld); + + if (!bResult) + { + return nullptr; + } + + // if (!bActorInWorld) + // { + // // The OutLevel is not present in the current world which means we might + // // still find the tile actor in OutWorld. + OutActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); + // } + } + + return OutActor; +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + ALandscapeProxy* OutActor = nullptr; + FString ActorName = InActorName + TEXT("_Temp"); + TMap& PrevCookObjects = InOutput->GetOutputObjects(); + + OutWorld = InWorld; + OutLevel = InWorld->PersistentLevel; + + bCreatedPackage = false; + + // Find Landscape proxy for output when running in Temp mode + for(auto& PrevObject : PrevCookObjects) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(PrevObject.Value.OutputObject); + if (!LandscapePtr) + continue; + + OutActor = LandscapePtr->GetRawPtr(); + if (!OutActor) + { + // We may need to manually load the object + OutActor = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); + } + + if (!OutActor) + continue; + + // If we were updating the input landscape before, but arent anymore, + // we could still find it here in the output, ignore them now as we're only looking for previous output + if (ValidLandscapes.Contains(OutActor)) + continue; + + if (OutActor->GetName() != ActorName) + // This is not the droid we're looking for + continue; + + if (OutActor->IsPendingKill()) + { + FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + "_pendingkill"); + continue; + } + + // If we found a possible candidate, make sure that its size matches ours + // as we can only update a landscape of the same size + ULandscapeInfo* PreviousInfo = OutActor->GetLandscapeInfo(); + if (PreviousInfo) + { + int32 PrevMinX = 0; + int32 PrevMinY = 0; + int32 PrevMaxX = 0; + int32 PrevMaxY = 0; + PreviousInfo->GetLandscapeExtent(PrevMinX, PrevMinY, PrevMaxX, PrevMaxY); + + if ((PrevMaxX - PrevMinX + 1) == UnrealLandscapeSizeX && (PrevMaxY - PrevMinY + 1) == UnrealLandscapeSizeY) + { + // The size matches, we can reuse the old landscape. + break; + } + else + { + // We can't reuse this actor. The dimensions does not match. + // We need to rename this actor in order to create a new one with the specified name. + FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + TEXT("_old") ); + OutActor = nullptr; + } + } + } + + return OutActor; +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor, + const EPackageMode InPackageMode) +{ + if (InPackageMode == EPackageMode::Bake) + return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); + else + return SetLandscapeActorAsOutput_Temp(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor) +{ + // We are in bake mode. No outputs to register / add here. + // Do nothing, for now. +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedSharedLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor) +{ + // The main landscape is a special case here. It cannot be registered with the + // output object here, since it is possibly shared by *multiple* outputs so + // we have to deal with the attached and cleanup of the actor manually. + if (bCreatedSharedLandscape && IsValid(SharedLandscapeActorParent)) + { + AttachActorToHAC(InOutput, SharedLandscapeActor); + } + + // TODO: The OutputObject cleanup being performed here should really be part of + // the output object itself (or at the very least be encapsulated in a reusable + // static function somewhere) so that individual output objects can be cleaned + // when necessary without having to duplicate code when its needed. + + // Cleanup any stale output objects + TMap& Outputs = InOutput->GetOutputObjects(); + TArray StaleOutputs; + for (auto& Elem : Outputs) + { + UHoudiniLandscapePtr* LandscapePtr = nullptr; + bool bIsStale = false; + + if (!(Elem.Key == Identifier)) + { + // Identifiers doesn't match so this is definitely a stale output. + StaleOutputs.Add(Elem.Key); + bIsStale = true; + } + + LandscapePtr = Cast(Elem.Value.OutputObject); + if (LandscapePtr) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + + if (LandscapeProxy) + { + // We shouldn't destroy any input landscape, + // or the landscape that we are currently trying to set as output.. + if (!InAllInputLandscapes.Contains(LandscapeProxy) && (LandscapeProxy != LandscapeActor)) + { + // This landscape proxy either doesn't match the landscape identifier + // or it doesn't match the actor we're about to output. Either way, + // get rid of it. + LandscapeProxy->Destroy(); + } + } + } + + if (bIsStale) + { + Elem.Value.OutputObject = nullptr; + } + } + + for (FHoudiniOutputObjectIdentifier& StaleOutput : StaleOutputs) + { + Outputs.Remove(StaleOutput); + } + + + // Send a landscape pointer back to the Output Object for this landscape tile. + FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); + UHoudiniLandscapePtr* LandscapePtr = NewObject(InOutput); + LandscapePtr->SetSoftPtr(LandscapeActor); + OutputObj.OutputObject = LandscapePtr; + OutputObj.CachedAttributes = OutputAttributes; + OutputObj.CachedTokens = OutputTokens; +} + + +bool +FHoudiniLandscapeTranslator::AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor) +{ + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (IsValid(HAC)) + { + InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + return true; + } + return false; +} + +FString +FHoudiniLandscapeTranslator::GetActorNameSuffix(const EPackageMode& InPackageMode) +{ + if(InPackageMode == EPackageMode::CookToTemp) + return "_Temp"; + else + return FString(); +} + +void +FHoudiniLandscapeTranslator::DoPreEditChangeProperty(UObject* Obj, FName PropertyName) +{ + Obj->PreEditChange(FindFProperty(Obj->GetClass(), PropertyName)); +} + +void +FHoudiniLandscapeTranslator::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) +{ + FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); + Obj->PostEditChangeProperty(Evt); +} + +bool +FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + const TArray< float >& HeightfieldFloatValues, + const FHoudiniVolumeInfo& HeightfieldVolumeInfo, + const int32& FinalXSize, const int32& FinalYSize, + float FloatMin, float FloatMax, + TArray< uint16 >& IntHeightData, + FTransform& LandscapeTransform, + const bool& NoResize) +{ + IntHeightData.Empty(); + LandscapeTransform.SetIdentity(); + + // HF sizes needs an X/Y swap + // NOPE.. not anymore + int32 HoudiniXSize = HeightfieldVolumeInfo.YLength; + int32 HoudiniYSize = HeightfieldVolumeInfo.XLength; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + // Test for potential special cases... + // Just print a warning for now + if (HeightfieldVolumeInfo.MinX != 0) + HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min X is not zero.")); + + if (HeightfieldVolumeInfo.MinY != 0) + HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min Y is not zero.")); + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to uint16 using doubles to get the maximum precision during the conversion + //-------------------------------------------------------------------------------------------------- + + FTransform CurrentVolumeTransform = HeightfieldVolumeInfo.Transform; + + // The ZRange in Houdini (in m) + double MeterZRange = (double)(FloatMax - FloatMin); + + // The corresponding unreal digit range (as unreal uses uint16, max is 65535) + // We may want to not use the full range in order to be able to sculpt the landscape past the min/max values after. + const double dUINT16_MAX = (double)UINT16_MAX; + double DigitZRange = 49152.0; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution) + DigitZRange = dUINT16_MAX - 1.0; + + // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down + double DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); + + // The factor used to convert from Houdini's ZRange to the desired digit range + double ZSpacing = (MeterZRange != 0.0) ? (DigitZRange / MeterZRange) : 0.0; + + // Changes these values if the user wants to loose a lot of precision + // just to keep the same transform as the landscape input + bool bUseDefaultUE4Scaling = false; + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) + bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + + if (bUseDefaultUE4Scaling) + { + //Check that our values are compatible with UE4's default scale values + if (FloatMin < -256.0f || FloatMin > 256.0f || FloatMax < -256.0f || FloatMax > 256.0f) + { + // Warn the user that the landscape conversion will have issues + // invite him to change that setting + HOUDINI_LOG_WARNING( + TEXT("The heightfield's min and max height values are too large for being used with the \"Use Default UE4 scaling\" option.\n \ + The generated Heightfield will likely be incorrectly converted to landscape unless you disable that option in the project settings and recook the asset.")); + } + + DigitZRange = dUINT16_MAX - 1.0; + DigitCenterOffset = 0; + + // Default unreal landscape scaling is -256m:256m at Scale = 100 + // We need to apply the scale back to + FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; + FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; + MeterZRange = (double)(FloatMax - FloatMin); + + ZSpacing = ((double)DigitZRange) / MeterZRange; + } + + // Converting the data from Houdini to Unreal + // For correct orientation in unreal, the point matrix has to be transposed. + IntHeightData.SetNumUninitialized(SizeInPoints); + + int32 nUnreal = 0; + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y + int32 nHoudini = nY + nX * HoudiniYSize; + + // Get the double values in [0 - ZRange] + double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; + + // Then convert it to [0 - DesiredRange] and center it + DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; + IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); + } + } + + //-------------------------------------------------------------------------------------------------- + // 2. Resample / Pad the int data so that if fits unreal size requirements + //-------------------------------------------------------------------------------------------------- + + // UE has specific size requirements for landscape, + // so we might need to pad/resample the heightfield data + FVector LandscapeResizeFactor = FVector::OneVector; + FVector LandscapePositionOffsetInPixels = FVector::ZeroVector; + if (!NoResize) + { + // Try to resize the data + if (!FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( + IntHeightData, + HoudiniXSize, HoudiniYSize, FinalXSize, FinalYSize, + LandscapeResizeFactor, LandscapePositionOffsetInPixels)) + return false; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Calculating the proper transform for the landscape to be sized and positionned properly + //-------------------------------------------------------------------------------------------------- + + // Scale: + // Calculating the equivalent scale to match Houdini's Terrain Size in Unreal + FVector LandscapeScale; + + // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing + LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; + LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Y * 2.0f; + + // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini + // Unreal has a default Z range is 512m for a scale of a 100% + LandscapeScale.Z = (float)((double)(dUINT16_MAX / DigitZRange) * MeterZRange / 512.0); + if (bUseDefaultUE4Scaling) + LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Z * 2.0f; + LandscapeScale *= 100.f; + + // If the data was resized and not expanded, we need to modify the landscape's scale + LandscapeScale *= LandscapeResizeFactor; + + // Don't allow a zero scale, as this results in divide by 0 operations in FMatrix::InverseFast in the landscape component. + if (FMath::IsNearlyZero(LandscapeScale.Z)) + LandscapeScale.Z = 1.0f; + + // We'll use the position from Houdini, but we will need to offset the Z Position to center the + // values properly as the data has been offset by the conversion to uint16 + FVector LandscapePosition = CurrentVolumeTransform.GetLocation(); + //LandscapePosition.Z = 0.0f; + + // We need to calculate the position offset so that Houdini and Unreal have the same Zero position + // In Unreal, zero has a height value of 32768. + // These values are then divided by 128 internally, and then multiplied by the Landscape's Z scale + // ( DIGIT - 32768 ) / 128 * ZScale = ZOffset + + // We need the Digit (Unreal) value of Houdini's zero for the scale calculation + // ( float and int32 are used for this because 0 might be out of the landscape Z range! + // when using the full range, this would cause an overflow for a uint16!! ) + float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); + float ZOffset = -(HoudiniZeroValueInDigit - 32768.0f) / 128.0f * LandscapeScale.Z; + + LandscapePosition.Z += ZOffset; + + // If we have padded the data when resizing the landscape, we need to offset the position because of + // the added values on the topLeft Corner of the Landscape + if (LandscapePositionOffsetInPixels != FVector::ZeroVector) + { + FVector LandscapeOffset = LandscapePositionOffsetInPixels * LandscapeScale; + LandscapeOffset.Z = 0.0f; + + LandscapePosition += LandscapeOffset; + } + + /* + FTransform TempTransform; + TempTransform.SetIdentity(); + { + // Houdini Pivot (center of the Landscape) + FVector HoudiniPivot = FVector((FinalXSize-1) * 100.0f / 2.0f, (FinalYSize-1) * 100.0f / 2.0f, 0.0f); + + // Center the landscape + FVector CenterLocation = LandscapePosition - HoudiniPivot; + + // Rotate the vector using the H rotation + // We need to compensate for the "default" HF Transform + FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); + Rotator.Yaw -= 90.0f; + Rotator.Roll += 90.0f; + FVector RotatedLocation = Rotator.RotateVector(CenterLocation); + + FQuat LandscapeRotation = FQuat(Rotator) * FQuat::Identity; + + // Return to previous origin + FVector Uncentered = RotatedLocation + HoudiniPivot; + TempTransform = FTransform(LandscapeRotation, Uncentered, LandscapeScale); + } + + LandscapeTransform = TempTransform; + */ + + // We can now set the Landscape position + LandscapeTransform.SetLocation(LandscapePosition); + LandscapeTransform.SetScale3D(LandscapeScale); + + // Rotate the vector using the H rotation + FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); + // We need to compensate for the "default" HF Transform + Rotator.Yaw -= 90.0f; + Rotator.Roll += 90.0f; + + // Only rotate if the rotator is far from zero + if(!Rotator.IsNearlyZero()) + LandscapeTransform.SetRotation(FQuat(Rotator)); + + return true; +} + +template +TArray ResampleData(const TArray& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight) +{ + TArray Result; + Result.Empty(NewWidth * NewHeight); + Result.AddUninitialized(NewWidth * NewHeight); + + const float XScale = (float)(OldWidth - 1) / (NewWidth - 1); + const float YScale = (float)(OldHeight - 1) / (NewHeight - 1); + for (int32 Y = 0; Y < NewHeight; ++Y) + { + for (int32 X = 0; X < NewWidth; ++X) + { + const float OldY = Y * YScale; + const float OldX = X * XScale; + const int32 X0 = FMath::FloorToInt(OldX); + const int32 X1 = FMath::Min(FMath::FloorToInt(OldX) + 1, OldWidth - 1); + const int32 Y0 = FMath::FloorToInt(OldY); + const int32 Y1 = FMath::Min(FMath::FloorToInt(OldY) + 1, OldHeight - 1); + const T& Original00 = Data[Y0 * OldWidth + X0]; + const T& Original10 = Data[Y0 * OldWidth + X1]; + const T& Original01 = Data[Y1 * OldWidth + X0]; + const T& Original11 = Data[Y1 * OldWidth + X1]; + Result[Y * NewWidth + X] = FMath::BiLerp(Original00, Original10, Original01, Original11, FMath::Fractional(OldX), FMath::Fractional(OldY)); + } + } + + return Result; +} + +template +void ExpandData(T* OutData, const T* InData, + int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, + int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY) +{ + const int32 OldWidth = OldMaxX - OldMinX + 1; + const int32 OldHeight = OldMaxY - OldMinY + 1; + const int32 NewWidth = NewMaxX - NewMinX + 1; + const int32 NewHeight = NewMaxY - NewMinY + 1; + const int32 OffsetX = NewMinX - OldMinX; + const int32 OffsetY = NewMinY - OldMinY; + + for (int32 Y = 0; Y < NewHeight; ++Y) + { + const int32 OldY = FMath::Clamp(Y + OffsetY, 0, OldHeight - 1); + + // Pad anything to the left + const T PadLeft = InData[OldY * OldWidth + 0]; + for (int32 X = 0; X < -OffsetX; ++X) + { + OutData[Y * NewWidth + X] = PadLeft; + } + + // Copy one row of the old data + { + const int32 X = FMath::Max(0, -OffsetX); + const int32 OldX = FMath::Clamp(X + OffsetX, 0, OldWidth - 1); + FMemory::Memcpy(&OutData[Y * NewWidth + X], &InData[OldY * OldWidth + OldX], FMath::Min(OldWidth, NewWidth) * sizeof(T)); + } + + const T PadRight = InData[OldY * OldWidth + OldWidth - 1]; + for (int32 X = -OffsetX + OldWidth; X < NewWidth; ++X) + { + OutData[Y * NewWidth + X] = PadRight; + } + } +} + +template +TArray ExpandData(const TArray& Data, + int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, + int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY, + int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr) +{ + const int32 NewWidth = NewMaxX - NewMinX + 1; + const int32 NewHeight = NewMaxY - NewMinY + 1; + + TArray Result; + Result.Empty(NewWidth * NewHeight); + Result.AddUninitialized(NewWidth * NewHeight); + + ExpandData(Result.GetData(), Data.GetData(), + OldMinX, OldMinY, OldMaxX, OldMaxY, + NewMinX, NewMinY, NewMaxX, NewMaxY); + + // Return the padding so we can offset the terrain position after + if (PadOffsetX) + *PadOffsetX = NewMinX; + + if (PadOffsetY) + *PadOffsetY = NewMinY; + + return Result; +} + +bool +FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( + TArray& HeightData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY, + FVector& LandscapeResizeFactor, + FVector& LandscapePositionOffset) +{ + LandscapeResizeFactor = FVector::OneVector; + LandscapePositionOffset = FVector::ZeroVector; + + if (HeightData.Num() <= 4) + return false; + + if ((SizeX < 2) || (SizeY < 2)) + return false; + + // No need to resize anything + if (SizeX == NewSizeX && SizeY == NewSizeY) + return true; + + // Always resample, for now. We may enable padding functionality again at some point via + // a plugin setting. + bool bForceResample = true; + bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); + + TArray NewData; + if (!bResample) + { + // Expanding the data by padding + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + + const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; + const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; + + // Store the offset in pixel due to the padding + int32 PadOffsetX = 0; + int32 PadOffsetY = 0; + + // Expanding the Data + NewData = ExpandData( + HeightData, 0, 0, SizeX - 1, SizeY - 1, + -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1, + &PadOffsetX, &PadOffsetY); + + // We will need to offset the landscape position due to the value added by the padding + LandscapePositionOffset.X = (float)PadOffsetX; + LandscapePositionOffset.Y = (float)PadOffsetY; + + // Notify the user that the data was padded + HOUDINI_LOG_WARNING( + TEXT("Landscape data was padded from ( %d x %d ) to ( %d x %d )."), + SizeX, SizeY, NewSizeX, NewSizeY); + } + else + { + // Resampling the data + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + NewData = ResampleData(HeightData, SizeX, SizeY, NewSizeX, NewSizeY); + + // The landscape has been resized, we'll need to take that into account when sizing it + LandscapeResizeFactor.X = (float)SizeX / (float)NewSizeX; + LandscapeResizeFactor.Y = (float)SizeY / (float)NewSizeY; + LandscapeResizeFactor.Z = 1.0f; + + // Notify the user if the heightfield data was resized + HOUDINI_LOG_WARNING( + TEXT("Landscape data was resized from ( %d x %d ) to ( %d x %d )."), + SizeX, SizeY, NewSizeX, NewSizeY); + } + + // Replaces Old data with the new one + HeightData = NewData; + + return true; +} + + +bool +FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + const int32& HoudiniSizeX, const int32& HoudiniSizeY, + int32& UnrealSizeX, int32& UnrealSizeY, + int32& NumSectionsPerComponent, int32& NumQuadsPerSection) +{ + if ((HoudiniSizeX < 2) || (HoudiniSizeY < 2)) + return false; + + NumSectionsPerComponent = 1; + NumQuadsPerSection = 1; + UnrealSizeX = -1; + UnrealSizeY = -1; + + // Unreal's default sizes + int32 SectionSizes[] = { 7, 15, 31, 63, 127, 255 }; + int32 NumSections[] = { 1, 2 }; + + // Component count used to calculate the final size of the landscape + int32 ComponentsCountX = 1; + int32 ComponentsCountY = 1; + + // Lambda for clamping the number of component in X/Y + auto ClampLandscapeSize = [&]() + { + // Max size is either whole components below 8192 verts, or 32 components + ComponentsCountX = FMath::Clamp(ComponentsCountX, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); + ComponentsCountY = FMath::Clamp(ComponentsCountY, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); + }; + + // Try to find a section size and number of sections that exactly matches the dimensions of the heightfield + bool bFoundMatch = false; + for (int32 SectionSizesIdx = UE_ARRAY_COUNT(SectionSizes) - 1; SectionSizesIdx >= 0; SectionSizesIdx--) + { + for (int32 NumSectionsIdx = UE_ARRAY_COUNT(NumSections) - 1; NumSectionsIdx >= 0; NumSectionsIdx--) + { + int32 ss = SectionSizes[SectionSizesIdx]; + int32 ns = NumSections[NumSectionsIdx]; + + if (((HoudiniSizeX - 1) % (ss * ns)) == 0 && ((HoudiniSizeX - 1) / (ss * ns)) <= 32 && + ((HoudiniSizeY - 1) % (ss * ns)) == 0 && ((HoudiniSizeY - 1) / (ss * ns)) <= 32) + { + bFoundMatch = true; + NumQuadsPerSection = ss; + NumSectionsPerComponent = ns; + ComponentsCountX = (HoudiniSizeX - 1) / (ss * ns); + ComponentsCountY = (HoudiniSizeY - 1) / (ss * ns); + ClampLandscapeSize(); + break; + } + } + if (bFoundMatch) + { + break; + } + } + + if (!bFoundMatch) + { + // if there was no exact match, try increasing the section size until we encompass the whole heightmap + const int32 CurrentSectionSize = NumQuadsPerSection; + const int32 CurrentNumSections = NumSectionsPerComponent; + for (int32 SectionSizesIdx = 0; SectionSizesIdx < UE_ARRAY_COUNT(SectionSizes); SectionSizesIdx++) + { + if (SectionSizes[SectionSizesIdx] < CurrentSectionSize) + { + continue; + } + + const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); + const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); + if (ComponentsX <= 32 && ComponentsY <= 32) + { + bFoundMatch = true; + NumQuadsPerSection = SectionSizes[SectionSizesIdx]; + ComponentsCountX = ComponentsX; + ComponentsCountY = ComponentsY; + ClampLandscapeSize(); + break; + } + } + } + + if (!bFoundMatch) + { + // if the heightmap is very large, fall back to using the largest values we support + const int32 MaxSectionSize = SectionSizes[UE_ARRAY_COUNT(SectionSizes) - 1]; + const int32 MaxNumSubSections = NumSections[UE_ARRAY_COUNT(NumSections) - 1]; + const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), MaxSectionSize * MaxNumSubSections); + const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), MaxSectionSize * MaxNumSubSections); + + bFoundMatch = true; + NumQuadsPerSection = MaxSectionSize; + NumSectionsPerComponent = MaxNumSubSections; + ComponentsCountX = ComponentsX; + ComponentsCountY = ComponentsY; + ClampLandscapeSize(); + } + + if (!bFoundMatch) + { + // Using default size just to not crash.. + UnrealSizeX = 512; + UnrealSizeY = 512; + NumSectionsPerComponent = 1; + NumQuadsPerSection = 511; + ComponentsCountX = 1; + ComponentsCountY = 1; + } + else + { + // Calculating the desired size + int32 QuadsPerComponent = NumSectionsPerComponent * NumQuadsPerSection; + + UnrealSizeX = ComponentsCountX * QuadsPerComponent + 1; + UnrealSizeY = ComponentsCountY * QuadsPerComponent + 1; + } + + return bFoundMatch; +} + +const FHoudiniGeoPartObject* +FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return nullptr; + + if (InOutput->GetHoudiniGeoPartObjects().Num() < 1) + return nullptr; + + for (const FHoudiniGeoPartObject& HGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Volume) + continue; + + FHoudiniVolumeInfo CurVolumeInfo = HGPO.VolumeInfo; + if (!CurVolumeInfo.Name.Contains("height")) + continue; + + // We're only handling single values for now + if (CurVolumeInfo.TupleSize != 1) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume has an invalide tuple size!")); + return nullptr; + } + + // Terrains always have a ZSize of 1. + if (CurVolumeInfo.ZLength != 1) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume's z length is not 1!")); + return nullptr; + } + + // Values should be float + if (!CurVolumeInfo.bIsFloat) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output, the height volume's data is not stored as floats!")); + return nullptr; + } + + return &HGPO; + } + + return nullptr; +} + +void +FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers) +{ + FoundLayers.Empty(); + + // Get node id + HAPI_NodeId HeightFieldNodeId = Heightfield.GeoId; + + // We need the tile attribute if the height has it + bool bParentHeightfieldHasTile = false; + int32 HeightFieldTile = -1; + { + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + TArray< int32 > TileValues; + + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HeightFieldNodeId, Heightfield.PartId, "tile", AttribInfoTile, TileValues); + + if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) + { + HeightFieldTile = TileValues[0]; + bParentHeightfieldHasTile = true; + } + } + + for (TArray< FHoudiniGeoPartObject >::TConstIterator IterLayers(InOutput->GetHoudiniGeoPartObjects()); IterLayers; ++IterLayers) + { + const FHoudiniGeoPartObject & HoudiniGeoPartObject = *IterLayers; + + HAPI_NodeId NodeId = HoudiniGeoPartObject.GeoId; + if (NodeId == -1 || NodeId != HeightFieldNodeId) + continue; + + if (bParentHeightfieldHasTile) + { + int32 CurrentTile = -1; + + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + TArray TileValues; + + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "tile", AttribInfoTile, TileValues); + + + if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) + { + CurrentTile = TileValues[0]; + } + + // Does this layer come from the same tile as the height? + if ((CurrentTile != HeightFieldTile) || (CurrentTile == -1)) + continue; + } + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, HoudiniGeoPartObject.PartId, + &CurrentVolumeInfo)) + continue; + + // We're interesting in anything but height data + FString CurrentVolumeName; + FHoudiniEngineString(CurrentVolumeInfo.nameSH).ToFString(CurrentVolumeName); + if (CurrentVolumeName.Contains("height")) + continue; + + // We're only handling single values for now + if (CurrentVolumeInfo.tupleSize != 1) + continue; + + // Terrains always have a ZSize of 1. + if (CurrentVolumeInfo.zLength != 1) + continue; + + // Values should be float + if (CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + continue; + + FoundLayers.Add(&HoudiniGeoPartObject); + } +} + +bool +FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) +{ + OutFloatArr.Empty(); + OutFloatMin = 0.f; + OutFloatMax = 0.f; + + if (HGPO->Type != EHoudiniPartType::Volume) + return false; + + HAPI_VolumeInfo VolumeInfo; + FHoudiniApi::VolumeInfo_Init(&VolumeInfo); + + HAPI_Result Result = FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + HGPO->GeoId, HGPO->PartId, &VolumeInfo); + + // We're only handling single values for now + if (VolumeInfo.tupleSize != 1) + return false; + + // Terrains always have a ZSize of 1. + if (VolumeInfo.zLength != 1) + return false; + + // Values must be float + if (VolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + return false; + + if ((VolumeInfo.xLength < 2) || (VolumeInfo.yLength < 2)) + return false; + + const int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; + + OutFloatArr.SetNum(SizeInPoints); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetHeightFieldData( + FHoudiniEngine::Get().GetSession(), + HGPO->GeoId, HGPO->PartId, + OutFloatArr.GetData(), + 0, SizeInPoints), false); + + OutFloatMin = OutFloatArr[0]; + OutFloatMax = OutFloatMin; + + for (float NextFloatVal : OutFloatArr) + { + if (NextFloatVal > OutFloatMax) + { + OutFloatMax = NextFloatVal; + } + else if (NextFloatVal < OutFloatMin) + OutFloatMin = NextFloatVal; + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(const FHoudiniGeoPartObject& InHGPO, TArray& NonWeightBlendedLayerNames) +{ + // Check the attribute exists on primitive or detail + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; + if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_PRIM)) + Owner = HAPI_ATTROWNER_PRIM; + else if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_DETAIL)) + Owner = HAPI_ATTROWNER_DETAIL; + else + return false; + + // Get the values + HAPI_AttributeInfo AttribInfoNonWBLayer; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNonWBLayer); + TArray AttribValues; + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, AttribInfoNonWBLayer, AttribValues, 1, Owner); + + if (AttribValues.Num() <= 0) + return false; + + // Convert them to FString + for (int32 Idx = 0; Idx < AttribValues.Num(); Idx++) + { + TArray Tokens; + AttribValues[Idx].ParseIntoArray(Tokens, TEXT(" "), true); + + for (int32 n = 0; n < Tokens.Num(); n++) + NonWeightBlendedLayerNames.AddUnique(Tokens[n]); + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) +{ + // Check the attribute exists on primitive or detail + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; + if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_PRIM)) + Owner = HAPI_ATTROWNER_PRIM; + else if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_DETAIL)) + Owner = HAPI_ATTROWNER_DETAIL; + else + return false; + + // Check the value + HAPI_AttributeInfo AttribInfoUnitLayer; + FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer); + TArray< int32 > AttribValues; + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, AttribInfoUnitLayer, AttribValues, 1, Owner); + + if (AttribValues.Num() > 0 && AttribValues[0] == 1) + return true; + + return false; +} + +bool +FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( + const TArray& FoundLayers, + const FHoudiniGeoPartObject& Heightfield, + const int32& LandscapeXSize, const int32& LandscapeYSize, + const TMap& GlobalMinimums, + const TMap& GlobalMaximums, + TArray& OutLayerInfos, + bool bIsUpdate, + const FHoudiniPackageParams& InTilePackageParams, + const FHoudiniPackageParams& InLayerPackageParams, + TArray& OutCreatedPackages + ) +{ + OutLayerInfos.Empty(); + + // Get the names of all non weight blended layers + TArray NonWeightBlendedLayerNames; + FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(Heightfield, NonWeightBlendedLayerNames); + + // Used for exporting layer info objects (per landscape layer) + FHoudiniPackageParams LayerPackageParams = InLayerPackageParams; + // Used for exporting textures (per landscape tile) + FHoudiniPackageParams TilePackageParams = InTilePackageParams; + + // For Debugging, do we want to export layers as textures? + bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; + + // Try to create all the layers + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + for (TArray::TConstIterator IterLayers(FoundLayers); IterLayers; ++IterLayers) + { + const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers; + if (!LayerGeoPartObject) + continue; + + if (!LayerGeoPartObject->IsValid()) + continue; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerGeoPartObject->AssetId)) + continue; + + if (bIsUpdate && !LayerGeoPartObject->bHasGeoChanged) + { + continue; + } + + TArray FloatLayerData; + float LayerMin = 0; + float LayerMax = 0; + if (!FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(LayerGeoPartObject, FloatLayerData, LayerMin, LayerMax)) + continue; + + // No need to create flat layers as Unreal will remove them afterwards.. + if (LayerMin == LayerMax) + continue; + + const FHoudiniVolumeInfo& LayerVolumeInfo = LayerGeoPartObject->VolumeInfo; + + // Get the layer's name + FString LayerName = LayerVolumeInfo.Name; + const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerName); + + TilePackageParams.ObjectName = InTilePackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; + LayerPackageParams.ObjectName = InLayerPackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; + + if (bExportTexture) + { + // Create a raw texture export of the layer on this tile + FString TextureName = TilePackageParams.ObjectName + "_raw"; + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + LayerVolumeInfo.YLength, // Y and X inverted?? why? + LayerVolumeInfo.XLength, + FloatLayerData, + LayerMin, + LayerMax); + } + + // Check if that landscape layer has been marked as unit (range in [0-1] + if (IsUnitLandscapeLayer(*LayerGeoPartObject)) + { + LayerMin = 0.0f; + LayerMax = 1.0f; + } + else + { + // We want to convert the layer using the global Min/Max + if (GlobalMaximums.Contains(LayerName)) + LayerMax = GlobalMaximums[LayerName]; + + if (GlobalMinimums.Contains(LayerName)) + LayerMin = GlobalMinimums[LayerName]; + } + + // Get the layer package path + // FString LayerNameString = FString::Printf(TEXT("%s_%d"), LayerString.GetCharArray().GetData(), (int32)LayerGeoPartObject->PartId); + // LayerNameString = UPackageTools::SanitizePackageName(LayerNameString); + + // Build an object name for the current layer + LayerPackageParams.SplitStr = SanitizedLayerName; + + // Creating the ImportLayerInfo and LayerInfo objects + FLandscapeImportLayerInfo ImportLayerInfo(*LayerName); + + // See if the user has assigned a layer info object via attribute + UPackage * Package = nullptr; + ULandscapeLayerInfoObject* LayerInfo = GetLandscapeLayerInfoForLayer(*LayerGeoPartObject, *LayerName); + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + // No assignment, try to find or create a landscape layer info object for that layer + LayerInfo = FindOrCreateLandscapeLayerInfoObject(LayerName, LayerPackageParams.GetPackagePath(), LayerPackageParams.GetPackageName(), Package); + } + + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + continue; + } + + // Convert the float data to uint8 + // HF masks need their X/Y sizes swapped + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( + FloatLayerData, LayerVolumeInfo.YLength, LayerVolumeInfo.XLength, + LayerMin, LayerMax, + LandscapeXSize, LandscapeYSize, + ImportLayerInfo.LayerData)) + continue; + + // We will store the data used to convert from Houdini values to int in the DebugColor + // This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards... + // R = Min, G = Max, B = Spacing, A = ? + LayerInfo->LayerUsageDebugColor.R = LayerMin; + LayerInfo->LayerUsageDebugColor.G = LayerMax; + LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; + LayerInfo->LayerUsageDebugColor.A = PI; + + // Visibility are by default non weight blended + if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) + LayerInfo->bNoWeightBlend = true; + else + LayerInfo->bNoWeightBlend = false; + + if (!bIsUpdate && Package && !Package->IsPendingKill()) + { + // Mark the package dirty... + Package->MarkPackageDirty(); + OutCreatedPackages.Add(Package); + } + + if (bExportTexture) + { + // Create an export of the converted data to texture + // FString TextureName = LayerString; + // if (LayerGeoPartObject->VolumeTileIndex >= 0) + // TextureName = TEXT("Tile") + FString::FromInt(LayerGeoPartObject->VolumeTileIndex) + TEXT("_") + LayerString; + // TextureName += TEXT("_conv"); + + const FString TextureName = TilePackageParams.ObjectName + TEXT("_conv"); + + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + LandscapeXSize, LandscapeYSize, + ImportLayerInfo.LayerData); + } + + // See if there is a physical material assigned via attribute for that landscape layer + UPhysicalMaterial* PhysMaterial = FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(*LayerGeoPartObject); + if (PhysMaterial && !PhysMaterial->IsPendingKill()) + { + LayerInfo->PhysMaterial = PhysMaterial; + } + + // Assign the layer info object to the import layer infos + ImportLayerInfo.LayerInfo = LayerInfo; + OutLayerInfos.Add(ImportLayerInfo); + } + + // Autosaving the layers prevents them for being deleted with the Asset + // Save the packages created for the LayerInfos + // Do this only for when creating layers. + /* + if (!bIsUpdate) + FEditorFileUtils::PromptForCheckoutAndSave(CreatedLandscapeLayerPackage, true, false); + */ + + return true; +} + +void +FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums, + bool bShouldEmptyMaps) +{ + if (bShouldEmptyMaps) + { + GlobalMinimums.Empty(); + GlobalMaximums.Empty(); + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray FloatData; + + for (const FHoudiniGeoPartObject& CurrentHeightfield: InHeightfieldArray) + { + // Get the current Heightfield GeoPartObject + if ( CurrentHeightfield.VolumeInfo.TupleSize != 1) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = CurrentHeightfield.GeoId; + if (NodeId == -1) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + &CurrentVolumeInfo)) + continue; + + // Retrieve the volume name. + FString VolumeName; + FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); + HoudiniEngineStringPartName.ToFString(VolumeName); + + bool bHasMinAttr = false; + bool bHasMaxAttr = false; + + // If this volume has an attribute defining a minimum value use it as is. + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + GlobalMinimums.Add(VolumeName, FloatData[0]); + bHasMinAttr = true; + } + } + + // If this volume has an attribute defining maximum value use it as is. + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + GlobalMaximums.Add(VolumeName, FloatData[0]); + bHasMaxAttr = true; + } + } + + if (!(bHasMinAttr && bHasMaxAttr)) + { + // Unreal's Z values are Y in Houdini + float ymin, ymax; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + nullptr, &ymin, nullptr, + nullptr, &ymax, nullptr, + nullptr, nullptr, nullptr)) + continue; + + + if (!bHasMinAttr) + { + // Read the global min value for this volume + if (!GlobalMinimums.Contains(VolumeName)) + { + GlobalMinimums.Add(VolumeName, ymin); + } + else + { + // Update the min if necessary + if (ymin < GlobalMinimums[VolumeName]) + GlobalMinimums[VolumeName] = ymin; + } + } + + if (!bHasMaxAttr) + { + // Read the global max value for this volume + if (!GlobalMaximums.Contains(VolumeName)) + { + GlobalMaximums.Add(VolumeName, ymax); + } + else + { + // Update the max if necessary + if (ymax > GlobalMaximums[VolumeName]) + GlobalMaximums[VolumeName] = ymax; + } + } + } + } +} + +void +FHoudiniLandscapeTranslator::GetLayersZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums) + +{ + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray FloatData; + + for (const FHoudiniGeoPartObject& CurrentHeightfield : InHeightfieldArray) + { + // Get the current Heightfield GeoPartObject + if (CurrentHeightfield.VolumeInfo.TupleSize != 1) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = CurrentHeightfield.GeoId; + if (NodeId == -1) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + &CurrentVolumeInfo)) + continue; + + // Retrieve the volume name. + FString VolumeName; + FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); + HoudiniEngineStringPartName.ToFString(VolumeName); + + // Read the global min value for this volume + + float MinValue; + float MaxValue; + bool bHasMin = false; + bool bHasMax = false; + + if (!GlobalMinimums.Contains(VolumeName)) + { + // Extract min value + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + MinValue = FloatData[0]; + bHasMin = true; + } + } + if (!bHasMin) + { + if (VolumeName == TEXT("height")) + { + MinValue = -1000.f; + } + else + { + MinValue = 0.f; + } + } + GlobalMinimums.Add(VolumeName, MinValue); + } + + if (!GlobalMaximums.Contains(VolumeName)) + { + // Extract max value + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + MaxValue = FloatData[0]; + bHasMax = true; + } + } + if (!bHasMax) + { + if (VolumeName == TEXT("height")) + { + MaxValue = 1000.f; + } + else + { + MaxValue = 1.f; + } + } + GlobalMaximums.Add(VolumeName, MaxValue); + } + + + + } +} + +bool +FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( + const TArray& FloatLayerData, + const int32& HoudiniXSize, const int32& HoudiniYSize, + const float& LayerMin, const float& LayerMax, + const int32& LandscapeXSize, const int32& LandscapeYSize, + TArray& LayerData, const bool& NoResize) +{ + // Convert the float data to uint8 + LayerData.SetNumUninitialized(HoudiniXSize * HoudiniYSize); + + // Calculating the factor used to convert from Houdini's ZRange to [0 255] + double LayerZRange = (LayerMax - LayerMin); + double LayerZSpacing = (LayerZRange != 0.0) ? (255.0 / (double)(LayerZRange)) : 0.0; + + int32 nUnrealIndex = 0; + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y + int32 nHoudini = nY + nX * HoudiniYSize; + + // Get the double values in [0 - ZRange] + double DoubleValue = (double)FMath::Clamp(FloatLayerData[nHoudini], LayerMin, LayerMax) - (double)LayerMin; + + // Then convert it to [0 - 255] + DoubleValue *= LayerZSpacing; + + LayerData[nUnrealIndex++] = FMath::RoundToInt(DoubleValue); + } + } + + // Finally, resize the data to fit with the new landscape size if needed + if (NoResize) + return true; + + return FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( + LayerData, HoudiniXSize, HoudiniYSize, + LandscapeXSize, LandscapeYSize); +} + +bool +FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( + TArray< uint8 >& LayerData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY) +{ + if ((NewSizeX == SizeX) && (NewSizeY == SizeY)) + return true; + + bool bForceResample = true; + bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); + + TArray NewData; + if (!bResample) + { + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + + const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; + const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; + + // Expanding the Data + NewData = ExpandData( + LayerData, + 0, 0, SizeX - 1, SizeY - 1, + -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1); + } + else + { + // Resampling the data + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + NewData = ResampleData(LayerData, SizeX, SizeY, NewSizeX, NewSizeY); + } + + LayerData = NewData; + + return true; +} + +ALandscapeProxy * +FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( + const TArray< uint16 >& IntHeightData, + const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + const FTransform& TileTransform, + const int32& XSize, + const int32& YSize, + const int32& NumSectionPerLandscapeComponent, + const int32& NumQuadsPerLandscapeSection, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* LandscapePhsyicalMaterial, + const FString& LandscapeTileActorName, + LandscapeActorType ActorType, + ALandscape* SharedLandscapeActor, + UWorld* InWorld, + ULevel* InLevel, + FHoudiniPackageParams InPackageParams) +{ + if (!IsValid(InWorld)) + return nullptr; + + // if (!IsValid(MainLandscapeActor)) + // return nullptr; + + if ((XSize < 2) || (YSize < 2)) + return nullptr; + + if (IntHeightData.Num() != (XSize * YSize)) + return nullptr; + + if (!GEditor) + return nullptr; + + ALandscapeProxy* LandscapeTile = nullptr; + UPackage *CreatedPackage = nullptr; + + ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; + ALandscape* CachedLandscapeActor = nullptr; + + UWorld* NewWorld = nullptr; + FString MapFileName; + bool bBroadcastMaterialUpdate = false; + //... Create landscape tile ...// + { + // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos + if (ActorType == LandscapeActorType::LandscapeStreamingProxy) + { + CachedStreamingProxyActor = InWorld->SpawnActor(); + if (CachedStreamingProxyActor) + { + check(SharedLandscapeActor); + CachedStreamingProxyActor->PreEditChange(nullptr); + + // Update landscape tile properties from the main landscape actor. + CachedStreamingProxyActor->GetSharedProperties(SharedLandscapeActor); + CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; + CachedStreamingProxyActor->bCastStaticShadow = false; + + LandscapeTile = CachedStreamingProxyActor; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not spawn ALandscapeStreamingProxy with name: %s"), *(LandscapeTileActorName) ); + return nullptr; + } + } + else + { + // Create a normal landscape actor + CachedLandscapeActor = InWorld->SpawnActor(); + if (CachedLandscapeActor) + { + CachedLandscapeActor->PreEditChange(nullptr); + CachedLandscapeActor->SetLandscapeGuid(FGuid::NewGuid()); + CachedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + CachedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + CachedLandscapeActor->bCastStaticShadow = false; + bBroadcastMaterialUpdate = true; + LandscapeTile = CachedLandscapeActor; + } + } + } + + + if (!LandscapeTile) + return nullptr; + + // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. + + // Autosaving the layers prevents them for being deleted with the Asset + // Save the packages created for the LayerInfos + //if ( CreatedLayerInfoPackage.Num() > 0 ) + // FEditorFileUtils::PromptForCheckoutAndSave( CreatedLayerInfoPackage, true, false ); + + // Import the landscape data + + // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue + LandscapeTile->bCastStaticShadow = false; + + // TODO: Check me? + //if (LandscapePhsyicalMaterial) + // LandscapeTile->DefaultPhysMaterial = LandscapePhsyicalMaterial; + + // Setting the layer type here. + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + + TMap> HeightmapDataPerLayers; + TMap> MaterialLayerDataPerLayer; + HeightmapDataPerLayers.Add(FGuid(), IntHeightData); + MaterialLayerDataPerLayer.Add(FGuid(), ImportLayerInfos); + + FTransform NewTileTransform, LandscapeOffset; + FIntPoint TileLoc; + + // NOTE: The following Import call will reregister all components, causing the actor to lose its transform. + // So we'll be importing the tile data as if the actor was located at the origin and fix up transforms afterward. + CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeOffset, NewTileTransform, TileLoc); + + // Before we import new tile data, remove previous tile data that will be overlapped by the new tile. + TSet OverlappingComponents; + const int32 DestMinX = TileLoc.X; + const int32 DestMinY = TileLoc.Y; + const int32 DestMaxX = TileLoc.X + XSize - 1; + const int32 DestMaxY = TileLoc.Y + YSize - 1; + + ULandscapeInfo* LandscapeInfo = LandscapeTile->GetLandscapeInfo(); + + if (LandscapeInfo) + { + // If there is a preexisting LandscapeInfo object, check for overlapping components. + + // Shrink bounds by 1,1 to avoid GetComponentsInRegion picking up extra components on all sides due to the overlap between components + LandscapeInfo->GetComponentsInRegion(DestMinX+1, DestMinY+1, DestMaxX-1, DestMaxY-1, OverlappingComponents); + TSet StaleActors; + + for (ULandscapeComponent* Component : OverlappingComponents) + { + // Remove the overlapped component from the LandscapeInfo and then from + LandscapeInfo->Modify(); + + ALandscapeProxy* Proxy = Component->GetLandscapeProxy(); + if (!IsValid(Proxy)) + continue; + check(Proxy); + FIntRect Bounds = Proxy->GetBoundingRect(); + // If this landscape proxy has no more components left, remove it from the LandscapeInfo. + LandscapeInfo->UnregisterActor(Proxy); + Proxy->Destroy(); + } + + ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); + } + + // Import tile data + // The Import function will correctly compute the tile section locations. No need to set it explicitly. + // TODO: Verify this with world composition!! + + bool bIsStandalone = LandscapeTile->GetLandscapeActor() == LandscapeTile; + + // We set the actor transform and absolute section base before importing heighfield data. This allows us to + // use the correct (quad-space) blitting region without causing overlaps during import. + + // NOTE: The following SetAbsoluteSectionBase() calls are very important. This tells the Landscape system + // where on the landscape, in quad space, a specific tile is located. This influences is used by various + // mechanisms and tools that tie into the Landscape system such as World Composition and Landscape editor tools. + // If the Section Offset for a landscape tile is wrong, it will appear in the wrong place in the World Composition + // viewer. Worse than that, none of Landscape Editor tools will work properly because it won't be able to + // locate the correct Landscape component when calculating the "Landscape Component Key" for the given word position. + // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blit functions use the + // same underlying Landscape Component Key calculation to find the correct landscape component to draw to. So if the + // Section Offsets are wrong ... all manner of chaos will follow. + // It is also super important to call ULandscapeInfo::RecreateLandscapeInfo() after change a LandscapeProxy's + // section offset in order to update the landscape's internal caches otherwise component key calculations + // won't work correctly. + + LandscapeTile->SetActorTransform(TileTransform); + LandscapeTile->SetAbsoluteSectionBase(TileLoc); + + LandscapeTile->Import( + LandscapeTile->GetLandscapeGuid(), + DestMinX, DestMinY, DestMaxX, DestMaxY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + HeightmapDataPerLayers, NULL, + MaterialLayerDataPerLayer, ImportLayerType); + + LandscapeTile->RecreateComponentsState(); + + if (!LandscapeInfo) + { + LandscapeInfo = LandscapeTile->GetLandscapeInfo(); + } + + // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so + // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo, + // and only then are we able to "blit" the new alpha data into the correct place on the landscape. + + ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); + + // TODO: Verify whether we still need to manually set the LandscapeLightingLOD setting or whether + // calling PostEditChange() will properly fix the state. + + // Copied straight from UE source code to avoid crash after importing the landscape: + // automatically calculate a lighting LOD that won't crash lightmass (hopefully) + // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 + LandscapeTile->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + + // ---------------------------------------------------- + // Rename the actor + // ---------------------------------------------------- + + // NOTE: The LandscapeProxy needs to be properly initialized before renaming (which is why the rename is taking + // place at the end) since the rename will trigger PostEditChange and can crash if the actor has not been + // correctly setup. + FHoudiniEngineUtils::SafeRenameActor(LandscapeTile, LandscapeTileActorName); + + if (!LandscapeTile->MarkPackageDirty()) + { + HOUDINI_LOG_WARNING(TEXT("Editor suppressed MarkPackageDirty() for landscape tile: %s"), *(LandscapeTile->GetPathName())); + } + + return LandscapeTile; +} + + +void +FHoudiniLandscapeTranslator::CalculateTileLocation( + int32 NumSectionsPerComponent, + int32 NumQuadsPerSection, + const FTransform& TileTransform, + FTransform& OutLandscapeOffset, + FTransform& OutTileTransform, + FIntPoint& OutTileLocation) +{ + // IMPORTANT: Tile Location (Section Base) needs to be in multiples of the landscape component's quad size + const int32 ComponentSize = NumSectionsPerComponent * NumQuadsPerSection; + + OutLandscapeOffset = FTransform(FRotator::ZeroRotator, FVector::ZeroVector, TileTransform.GetScale3D()); + OutTileTransform = TileTransform; + + // Sometimes the calculated tile coordinate falls on a half-unit so we would need to remove that offset first + // before calculating integer (quad space) tile location. + // For example, 123.5, should become 123. -456.5 should become -456. + const FVector TileScale = TileTransform.GetScale3D(); + const float TileCoordX = TileTransform.GetLocation().X / TileScale.X; + const float TileCoordY = TileTransform.GetLocation().Y / TileScale.Y; + + float NearestMultipleX = FMath::RoundHalfFromZero(TileCoordX / ComponentSize) * ComponentSize; + float NearestMultipleY = FMath::RoundHalfFromZero(TileCoordY / ComponentSize) * ComponentSize; + + // If the multiples are too close to the middle, offset by 0.5 to avoid some tiles snapping up and others snapping down. + if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleX), 0.5f, 0.1f)) + { + NearestMultipleX += 0.5f; + } + if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleY), 0.5f, 0.1f)) + { + NearestMultipleY += 0.5f; + } + + const float TileOffsetX = NearestMultipleX - TileCoordX; + const float TileOffsetY = NearestMultipleY - TileCoordY; + + OutTileLocation.X = FMath::RoundHalfFromZero(NearestMultipleX); + OutTileLocation.Y = FMath::RoundHalfFromZero(NearestMultipleY); + + // Adjust landscape offset to compensate for tile location / section base shifting. + OutLandscapeOffset.SetLocation( FVector(-TileOffsetX * TileScale.X, -TileOffsetY * TileScale.Y, 0.f) ); +} + + +void +FHoudiniLandscapeTranslator::GetLandscapeMaterials( + const FHoudiniGeoPartObject& InHeightHGPO, + UMaterialInterface*& OutLandscapeMaterial, + UMaterialInterface*& OutLandscapeHoleMaterial, + UPhysicalMaterial*& OutLandscapePhysicalMaterial) +{ + OutLandscapeMaterial = nullptr; + OutLandscapeHoleMaterial = nullptr; + OutLandscapePhysicalMaterial = nullptr; + + if (InHeightHGPO.Type != EHoudiniPartType::Volume) + return; + + TArray Materials; + HAPI_AttributeInfo AttribMaterials; + FHoudiniApi::AttributeInfo_Init(&AttribMaterials); + + // First, look for landscape material + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL, + AttribMaterials, Materials); + + // If the material attribute was not found, check the material instance attribute. + if (!AttribMaterials.exists) + { + Materials.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, + AttribMaterials, Materials); + } + + // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. + //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) + if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) + { + HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a point, primitive or detail attribute, ignoring attribute.")); + AttribMaterials.exists = false; + Materials.Empty(); + } + + if (AttribMaterials.exists && Materials.Num() > 0) + { + // Load the material + OutLandscapeMaterial = Cast(StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); + } + } + + Materials.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribMaterials); + + // Then, for the hole_material + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + AttribMaterials, Materials); + + // If the material attribute was not found, check the material instance attribute. + if (!AttribMaterials.exists) + { + Materials.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE, + AttribMaterials, Materials); + } + + // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. + //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) + if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) + { + HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute.")); + AttribMaterials.exists = false; + Materials.Empty(); + } + + if (AttribMaterials.exists && Materials.Num() > 0) + { + // Load the material + OutLandscapeHoleMaterial = Cast< UMaterialInterface >(StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); + } + } + + // Then for the physical material + OutLandscapePhysicalMaterial = GetLandscapePhysicalMaterial(InHeightHGPO); +} + +// Read the landscape component extent attribute from a heightfield +bool +FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + int32& MinX, int32& MaxX, + int32& MinY, int32& MaxY) +{ + // If we dont have minX, we likely dont have the others too + if (!FHoudiniEngineUtils::HapiCheckAttributeExists( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", HAPI_ATTROWNER_PRIM)) + return false; + + // Create an AttributeInfo + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); + + // Get MinX + TArray IntData; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MinX = IntData[0]; + + // Get MaxX + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MaxX = IntData[0]; + + // Get MinY + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MinY = IntData[0]; + + // Get MaxX + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MaxY = IntData[0]; + + return true; +} + +ULandscapeLayerInfoObject * +FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& InLayerName, const FString& InPackagePath, const FString& InPackageName, UPackage*& OutPackage) +{ + FString PackageFullName = InPackagePath + TEXT("/") + InPackageName; + + // See if package exists, if it does, reuse it + bool bCreatedPackage = false; + OutPackage = FindPackage(nullptr, *PackageFullName); + if (!OutPackage || OutPackage->IsPendingKill()) + { + // We need to create a new package + OutPackage = CreatePackage(*PackageFullName); + bCreatedPackage = true; + } + + if (!OutPackage || OutPackage->IsPendingKill()) + return nullptr; + + if (!OutPackage->IsFullyLoaded()) + OutPackage->FullyLoad(); + + ULandscapeLayerInfoObject* LayerInfo = nullptr; + if (!bCreatedPackage) + { + // See if we can load the layer info instead of creating a new one + LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), OutPackage, FName(*InPackageName)); + } + + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + // Create a new LandscapeLayerInfoObject in the package + LayerInfo = NewObject(OutPackage, FName(*InPackageName), RF_Public | RF_Standalone /*| RF_Transactional*/); + + // Notify the asset registry + FAssetRegistryModule::AssetCreated(LayerInfo); + } + + if (LayerInfo && !LayerInfo->IsPendingKill()) + { + LayerInfo->LayerName = FName(*InLayerName); + + // Trigger update of the Layer Info + LayerInfo->PreEditChange(nullptr); + LayerInfo->PostEditChange(); + LayerInfo->MarkPackageDirty(); + + // Mark the package dirty... + OutPackage->MarkPackageDirty(); + } + + return LayerInfo; +} + +bool +FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax( + const TArray& AllOutputs, float& OutGlobalMin, float& OutGlobalMax) +{ + OutGlobalMin = 0.f; + OutGlobalMax = 0.f; + + for (const auto& CurrentOutput : AllOutputs) + { + if (!CurrentOutput) + continue; + + if (CurrentOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + const TArray& HGPOs = CurrentOutput->GetHoudiniGeoPartObjects(); + for (const FHoudiniGeoPartObject& CurrentHGPO : HGPOs) + { + if (CurrentHGPO.Type != EHoudiniPartType::Volume) + continue; + + if (!CurrentHGPO.VolumeInfo.Name.Contains("height")) + continue; + + // We're only handling single values for now + if (CurrentHGPO.VolumeInfo.TupleSize != 1) + continue; + + // Terrains always have a ZSize of 1. + if (CurrentHGPO.VolumeInfo.ZLength != 1) + continue; + + // Values should be float + if (!CurrentHGPO.VolumeInfo.bIsFloat) + continue; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CurrentHGPO.GeoId)) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHGPO.GeoId, CurrentHGPO.PartId, &CurrentVolumeInfo)) + continue; + + // Unreal's Z values are Y in Houdini + float yMin = OutGlobalMin, yMax = OutGlobalMax; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), + CurrentHGPO.GeoId, CurrentHGPO.PartId, + nullptr, &yMin, nullptr, + nullptr, &yMax, nullptr, + nullptr, nullptr, nullptr)) + continue; + + if (yMin < OutGlobalMin) + OutGlobalMin = yMin; + + if (yMax > OutGlobalMax) + OutGlobalMax = yMax; + } + + if (OutGlobalMin > OutGlobalMax) + { + OutGlobalMin = 0.f; + OutGlobalMax = 0.f; + } + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::EnableWorldComposition() +{ + HOUDINI_LOG_WARNING(TEXT("[FHoudiniLandscapeTranslator::EnableWorldComposition] We should never enable world composition from within the plugin.")); + // Get the world + UWorld* MyWorld = nullptr; + { + // We want to create the landscape in the landscape editor mode's world + FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); + MyWorld = EditorWorldContext.World(); + } + + if (!MyWorld) + return false; + + ULevel* CurrentLevel = MyWorld->GetCurrentLevel(); + + if (!CurrentLevel) + return false; + + AWorldSettings* WorldSettings = CurrentLevel->GetWorldSettings(); + if (!WorldSettings) + return false; + + // Enable world composition in WorldSettings + WorldSettings->bEnableWorldComposition = true; + + CurrentLevel->PostEditChange(); + + return true; +} + + +bool +FHoudiniLandscapeTranslator::GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, + const int32& InPrimIndex, TArray& OutPropertyAttributes) +{ + // List all the generic property detail attributes ... + int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + + // .. then the primitive property attributes + // Volumes apparently dont have prim attributes because they're converted to pointmeshes somehow... + //FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + // InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InPrimIndex); + + // .. then the point property attributes + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InPrimIndex); + + return FoundCount > 0; +} + + +bool +FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current Property Attribute + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) + continue; + + // Success! + NumSuccess++; + FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); + } + + return (NumSuccess > 0); +} + + +bool +FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(const FString& BaseName, ALandscapeProxy* Landscape) +{ + // We need to cache the input landscape to a file + if (!Landscape) + return false; + + ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Save Height data to file + //FString HeightSave = TEXT("/Game/HoudiniEngine/Temp/HeightCache.png"); + FString HeightSave = BaseName + TEXT("_height.png"); + LandscapeInfo->ExportHeightmap(HeightSave); + Landscape->ReimportHeightmapFilePath = HeightSave; + + // Save each layer to a file + for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) + { + FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); + //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; + if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + continue; + + FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); + LandscapeInfo->ExportLayer(CurrentLayerInfo, LayerSave); + + // Update the file reimport path on the input landscape for this layer + LandscapeInfo->GetLayerEditorSettings(CurrentLayerInfo).ReimportLayerFilePath = LayerSave; + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy) +{ + if (!LandscapeProxy) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Restore Height data from the backup file + FString ReimportFile = LandscapeProxy->ReimportHeightmapFilePath; + if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, TEXT("height"))) + HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); + + // Restore each layer from the backup file + TArray< ULandscapeLayerInfoObject* > SourceLayers; + for (int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++) + { + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; + if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + continue; + + FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); + ReimportFile = LandscapeProxy->EditorLayerSettings[LayerIndex].ReimportLayerFilePath; + + if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, CurrentLayerName, CurrentLayerInfo)) + HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); + + SourceLayers.Add(CurrentLayerInfo); + } + + // Iterate on the landscape info's layer to remove any layer that could have been added by Houdini + for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) + { + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; + if (SourceLayers.Contains(CurrentLayerInfo)) + continue; + + // Delete the added layer + FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName; + LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName); + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::ImportLandscapeData( + ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject) +{ + // + // Code copied/edited from FEdModeLandscape::ImportData as we cannot access that function + // + if (!LandscapeInfo) + return false; + + bool IsHeight = LayerName.Equals(TEXT("height"), ESearchCase::IgnoreCase); + + int32 MinX, MinY, MaxX, MaxY; + if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + { + const FLandscapeFileResolution LandscapeResolution = { (uint32)(1 + MaxX - MinX), (uint32)(1 + MaxY - MinY) }; + + ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); + + if (IsHeight) + { + const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); + + if (!HeightmapFormat) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); + return false; + } + + FLandscapeFileResolution ImportResolution = { 0, 0 }; + + const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename); + + // display error message if there is one, and abort the import + if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); + return false; + } + + // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape + if (HeightmapInfo.PossibleResolutions.Num() > 1) + { + if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined")); + return false; + } + else + { + ImportResolution = LandscapeResolution; + } + } + + // display warning message if there is one and allow user to cancel + if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); + + // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape + // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is + if (HeightmapInfo.PossibleResolutions.Num() == 1) + { + ImportResolution = HeightmapInfo.PossibleResolutions[0]; + if (ImportResolution != LandscapeResolution) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); + } + + FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution); + if (ImportData.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); + return false; + } + + TArray Data; + if (ImportResolution != LandscapeResolution) + { + // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked + // so that reimports behave the same as the initial import :) + + const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; + const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; + + Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16)); + + ExpandData(Data.GetData(), ImportData.Data.GetData(), + 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, + -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); + } + else + { + Data = MoveTemp(ImportData.Data); + } + + //FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap")); + + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData()); + } + else + { + // We're importing a Landscape layer + if (!LayerInfoObject || LayerInfoObject->IsPendingKill()) + return false; + + const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); + if (!WeightmapFormat) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); + return false; + } + + FLandscapeFileResolution ImportResolution = { 0, 0 }; + + const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName)); + + // display error message if there is one, and abort the import + if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); + return false; + } + + // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape + if (WeightmapInfo.PossibleResolutions.Num() > 1) + { + if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined")); + return false; + } + else + { + ImportResolution = LandscapeResolution; + } + } + + // display warning message if there is one and allow user to cancel + if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); + + // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape + // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is + if (WeightmapInfo.PossibleResolutions.Num() == 1) + { + ImportResolution = WeightmapInfo.PossibleResolutions[0]; + if (ImportResolution != LandscapeResolution) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); + } + + FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution); + + if (ImportData.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); + return false; + } + + TArray Data; + if (ImportResolution != LandscapeResolution) + { + // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked + // so that reimports behave the same as the initial import :) + const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; + const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; + + Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8)); + + ExpandData(Data.GetData(), ImportData.Data.GetData(), + 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, + -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); + } + else + { + Data = MoveTemp(ImportData.Data); + } + + //FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer")); + FAlphamapAccessor AlphamapAccessor(LandscapeInfo, LayerInfoObject); + AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + + return true; +} + +UTexture2D* +FHoudiniLandscapeTranslator::CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& InFloatBuffer, + const float& InMin, + const float& InMax) +{ + + // Convert the float values to uint8 + double Range = (double)InMax - (double)InMin; + TArray IntBuffer; + IntBuffer.SetNum(InFloatBuffer.Num()); + for(int32 i = 0; i < InFloatBuffer.Num(); i++) + { + double dNormalizedValue = ((double)InFloatBuffer[i] - (double)InMin) / (double)Range; + IntBuffer[i] = (uint8)(dNormalizedValue * 255.0); + } + + return FHoudiniLandscapeTranslator::CreateUnrealTexture( + InPackageParams, LayerName, InXSize, InYSize, IntBuffer); +} + +UTexture2D* +FHoudiniLandscapeTranslator::CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& IntBuffer) +{ + FHoudiniPackageParams MyPackageParams = InPackageParams; + MyPackageParams.ObjectName = LayerName; + MyPackageParams.PackageMode = EPackageMode::CookToTemp; + MyPackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + FString CreatedPackageName; + UPackage* Package = MyPackageParams.CreatePackageForObject(CreatedPackageName); + if (!Package || Package->IsPendingKill()) + return nullptr; + + // Create new texture object. + UTexture2D * Texture = NewObject(Package, UTexture2D::StaticClass(), *LayerName, RF_Public | RF_Standalone); + + // Add/Update meta information to package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *LayerName); + + /*// Texture Settings + Texture->PlatformData = new FTexturePlatformData(); + Texture->PlatformData->SizeX = InXSize; + Texture->PlatformData->SizeY = InYSize; + Texture->PlatformData->PixelFormat = PF_R8G8B8A8;*/ + + // Initialize texture source. + Texture->Source.Init(InXSize, InYSize, 1, 1, TSF_BGRA8); + + // Lock the texture. + uint8 * MipData = Texture->Source.LockMip(0); + + // Create base map. + uint8* DestPtr = nullptr; + uint32 SrcWidth = InXSize; + uint32 SrcHeight = InYSize; + const uint8 * SrcData = &IntBuffer[0]; + + for (uint32 y = 0; y < SrcHeight; y++) + { + DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; + + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth + x; + + *DestPtr++ = *(SrcData + DataOffset); // B greyscale, same value 3 times + *DestPtr++ = *(SrcData + DataOffset); // G + *DestPtr++ = *(SrcData + DataOffset); // R + + *DestPtr++ = 0xFF; // A to 1 + } + } + + // Unlock the texture. + Texture->Source.UnlockMip(0); + + // Texture creation parameters. + //Texture->SRGB = TextureParameters.bSRGB; + Texture->CompressionSettings = TC_Grayscale; + Texture->CompressionNoAlpha = true; + Texture->MipGenSettings = TMGS_NoMipmaps; + + // Set the Source Guid/Hash if specified. + /* + if ( TextureParameters.SourceGuidHash.IsValid() ) + { + Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); + } + */ + + // Updating Texture & mark it as unsaved + //Texture->AddToRoot(); + //Texture->UpdateResource(); + Package->MarkPackageDirty(); + + Texture->PostEditChange(); + + FString PathName = Texture->GetPathName(); + HOUDINI_LOG_MESSAGE(TEXT("Created texture when for %s in %s"), *LayerName, *PathName); + + return Texture; +} + +UPhysicalMaterial* +FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO) +{ + // See if we have assigned a physical material to this layer via attribute + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + TArray AttributeValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InLayerHGPO.GeoId, InLayerHGPO.PartId, + HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL, + AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) + return nullptr; + + if (AttributeValues.Num() > 0) + { + return LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); + } + + return nullptr; +} + +ULandscapeLayerInfoObject* +FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName) +{ + // See if we have assigned a landscape layer info object to this layer via attribute + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + TArray AttributeValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InLayerHGPO.GeoId, InLayerHGPO.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO, + AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) + return nullptr; + + if (AttributeValues.Num() > 0) + { + ULandscapeLayerInfoObject* FoundLayerInfo = LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); + if (!FoundLayerInfo || FoundLayerInfo->IsPendingKill()) + return nullptr; + + // The layer info's name must match this layer's name or Unreal will not like this! + if (!FoundLayerInfo->LayerName.IsEqual(InLayerName)) + { + FString NameStr = InLayerName.ToString(); + HOUDINI_LOG_WARNING(TEXT("Failed to use the assigned layer info object for %s by the unreal_landscape_layer_info attribute as the found layer info object's layer name does not match."), *NameStr); + } + + return FoundLayerInfo; + } + + return nullptr; +} + + + diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h index 5c7235d63..668e1c8a8 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h @@ -1,401 +1,401 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "Landscape.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "Engine/World.h" -#include "EngineUtils.h" -#include "HoudiniEngineOutputStats.h" -#include "HoudiniPackageParams.h" - -class UHoudiniAssetComponent; -class ULandscapeLayerInfoObject; -struct FHoudiniGenericAttribute; -struct FHoudiniPackageParams; - -struct HOUDINIENGINE_API FHoudiniLandscapeTranslator -{ - public: - enum class LandscapeActorType : uint8 - { - LandscapeActor = 0, - LandscapeStreamingProxy = 1, - }; - - static bool CreateLandscape( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedActors, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* World, - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniPackageParams InPackageParams, - TArray& OutCreatedPackages); - - static ALandscapeProxy* FindExistingLandscapeActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - protected: - - static bool IsLandscapeInfoCompatible( - const ULandscapeInfo* LandscapeInfo, - const int32 NumSectionsX, - const int32 NumSectionsY); - - static bool IsLandscapeTileCompatible( - const ALandscapeProxy* TileActor, - const int32 InTileSizeX, - const int32 InTileSizeY, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection); - - static bool IsLandscapeTypeCompatible( - const AActor* Actor, - LandscapeActorType ActorType); - - - /** - * Find a ALandscapeProxy actor that can be reused. It is important - * to note that the request landscape actor could not be found, - * `OutWorld` and `OutLevel` should be used to spawn the - * new landscape actor. - * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. - */ - static ALandscapeProxy* FindExistingLandscapeActor( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage, - const EPackageMode& InPackageMode); - - static ALandscapeProxy* FindExistingLandscapeActor_Temp( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - /** - * Attempt the given ALandscapeActor to the outer HAC. Note - * that certain package modes (such as Bake) may choose not to do so. - * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. - */ - static void SetLandscapeActorAsOutput( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor, - const EPackageMode InPackageMode); - - static void SetLandscapeActorAsOutput_Bake( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor); - - static void SetLandscapeActorAsOutput_Temp( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor); - - /** - * Attach the given actor the HoudiniAssetComponent that - * owns `InOutput`, if any. - * @returns True if the actor was attached. Otherwise, return false. - */ - static bool AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor); - - /** - * Get the actor name suffix to be used in the specific packaging mode. - * @returns Suffix for actor names, return as an FString. - */ - static FString GetActorNameSuffix(const EPackageMode& InPackageMode); - - - // Helpers to get rid of repetitive boilerplate. - static void DoPreEditChangeProperty(UObject* Obj, FName PropertyName); - static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); - - public: - - - static const FHoudiniGeoPartObject* GetHoudiniHeightFieldFromOutput( - UHoudiniOutput* InOutput); - - static void GetHeightfieldsLayersFromOutput( - const UHoudiniOutput* InOutput, - const FHoudiniGeoPartObject& Heightfield, - TArray< const FHoudiniGeoPartObject* >& FoundLayers); - - static bool GetHoudiniHeightfieldFloatData( - const FHoudiniGeoPartObject* HGPO, - TArray &OutFloatArr, - float &OutFloatMin, - float &OutFloatMax); - - static bool CalcLandscapeSizeFromHeightfieldSize( - const int32& HoudiniSizeX, - const int32& HoudiniSizeY, - int32& UnrealSizeX, - int32& UnrealSizeY, - int32& NumSectionsPerComponent, - int32& NumQuadsPerSection); - - static bool ConvertHeightfieldDataToLandscapeData( - const TArray< float >& HeightfieldFloatValues, - const FHoudiniVolumeInfo& HeightfieldVolumeInfo, - const int32& FinalXSize, - const int32& FinalYSize, - float FloatMin, - float FloatMax, - TArray< uint16 >& IntHeightData, - FTransform& LandscapeTransform, - const bool& NoResize = false); - - static bool ResizeHeightDataForLandscape( - TArray& HeightData, - const int32& SizeX, - const int32& SizeY, - const int32& NewSizeX, - const int32& NewSizeY, - FVector& LandscapeResizeFactor, - FVector& LandscapePositionOffset); - - static bool CreateOrUpdateLandscapeLayers( - const TArray& FoundLayers, - const FHoudiniGeoPartObject& HeightField, - const int32& LandscapeXSize, - const int32& LandscapeYSize, - const TMap &GlobalMinimums, - const TMap &GlobalMaximums, - TArray& OutLayerInfos, - bool bIsUpdate, - const FHoudiniPackageParams& InTilePackageParams, - const FHoudiniPackageParams& InLayerPackageParams, - TArray& OutCreatedPackages); - - static bool GetNonWeightBlendedLayerNames( - const FHoudiniGeoPartObject& HeightfieldGeoPartObject, - TArray& NonWeightBlendedLayerNames); - - static bool IsUnitLandscapeLayer( - const FHoudiniGeoPartObject& LayerGeoPartObject); - - // Return the height min/max values for all - static bool CalcHeightGlobalZminZMax( - const TArray& AllOutputs, - float& OutGlobalMin, - float& OutGlobalMax); - - // Returns the min/max values per layer/volume for an array of volumes/heightfields - static void CalcHeightfieldsArrayGlobalZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums, - bool bShouldEmptyMaps=true); - - // Iterate over layers for the heightfields and retrieve min/max values - // from attributes, otherwise return default values. - static void GetLayersZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums); - - static bool ConvertHeightfieldLayerToLandscapeLayer( - const TArray& FloatLayerData, - const int32& HoudiniXSize, - const int32& HoudiniYSize, - const float& LayerMin, - const float& LayerMax, - const int32& LandscapeXSize, - const int32& LandscapeYSize, - TArray& LayerData, - const bool& NoResize = false); - - static bool ResizeLayerDataForLandscape( - TArray< uint8 >& LayerData, - const int32& SizeX, - const int32& SizeY, - const int32& NewSizeX, - const int32& NewSizeY); - - // static ALandscapeProxy * CreateLandscape( - // const TArray< uint16 >& IntHeightData, - // const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - // const FTransform& LandscapeTransform, - // const int32& XSize, - // const int32& YSize, - // const int32& NumSectionPerLandscapeComponent, - // const int32& NumQuadsPerLandscapeSection, - // UMaterialInterface* LandscapeMaterial, - // UMaterialInterface* LandscapeHoleMaterial, - // const bool& CreateLandscapeStreamingProxy, - // bool bNeedCreateNewWorld, - // UWorld* SpawnWorld, - // FHoudiniPackageParams InPackageParams, - // bool& bOutCreatedNewMap); - - static ALandscapeProxy* CreateLandscapeTileInWorld( - const TArray< uint16 >& IntHeightData, - const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - const FTransform& TileTransform, - const int32& XSize, - const int32& YSize, - const int32& NumSectionPerLandscapeComponent, - const int32& NumQuadsPerLandscapeSection, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhsyicalMaterial, - const FString& LandscapeTileActorName, - LandscapeActorType ActorType, - ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies - UWorld* InWorld, // World in which to spawn - ULevel* InLevel, // Level, contained in World, in which to spawn. - FHoudiniPackageParams InPackageParams); - -protected: - - /** - * Calculate the location of a landscape tile. - * This location is typically used in conjunction with ALandscapeProxy::SetAbsoluteSectionBase(). - */ - static void CalculateTileLocation( - int32 NumSectionsPerComponent, - int32 NumQuadsPerSection, - const FTransform& InTileTransform, - FTransform& OutLandscapeOffset, - FTransform& OutTileTransform, - FIntPoint& OutTileLocation); - -public: - - static void GetLandscapeMaterials( - const FHoudiniGeoPartObject& InHeightHGPO, - UMaterialInterface*& OutLandscapeMaterial, - UMaterialInterface*& OutLandscapeHoleMaterial, - UPhysicalMaterial*& OutLandscapePhysicalMaterial); - - static bool GetLandscapeComponentExtentAttributes( - const FHoudiniGeoPartObject& HoudiniGeoPartObject, - int32& MinX, - int32& MaxX, - int32& MinY, - int32& MaxY); - - static ULandscapeLayerInfoObject* FindOrCreateLandscapeLayerInfoObject( - const FString& InLayerName, - const FString& InPackagePath, - const FString& InPackageName, - UPackage*& OutPackage); - - static bool EnableWorldComposition(); - - static bool GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const int32& InPrimIndex, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, - const TArray& InAllPropertyAttributes); - - static bool BackupLandscapeToImageFiles( - const FString& BaseName, ALandscapeProxy* Landscape); - - static bool RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy); - - static UPhysicalMaterial* GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO); - - static ULandscapeLayerInfoObject* GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName); - - private: - - static bool ImportLandscapeData( - ULandscapeInfo* LandscapeInfo, - const FString& Filename, - const FString& LayerName, - ULandscapeLayerInfoObject* LayerInfoObject = nullptr); - - static UTexture2D* CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& InFloatBuffer, - const float& InMin, - const float& InMax); - - static UTexture2D* CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& IntBuffer); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "Landscape.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "Engine/World.h" +#include "EngineUtils.h" +#include "HoudiniEngineOutputStats.h" +#include "HoudiniPackageParams.h" + +class UHoudiniAssetComponent; +class ULandscapeLayerInfoObject; +struct FHoudiniGenericAttribute; +struct FHoudiniPackageParams; + +struct HOUDINIENGINE_API FHoudiniLandscapeTranslator +{ + public: + enum class LandscapeActorType : uint8 + { + LandscapeActor = 0, + LandscapeStreamingProxy = 1, + }; + + static bool CreateLandscape( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniPackageParams InPackageParams, + TArray& OutCreatedPackages); + + static ALandscapeProxy* FindExistingLandscapeActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + protected: + + static bool IsLandscapeInfoCompatible( + const ULandscapeInfo* LandscapeInfo, + const int32 NumSectionsX, + const int32 NumSectionsY); + + static bool IsLandscapeTileCompatible( + const ALandscapeProxy* TileActor, + const int32 InTileSizeX, + const int32 InTileSizeY, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection); + + static bool IsLandscapeTypeCompatible( + const AActor* Actor, + LandscapeActorType ActorType); + + + /** + * Find a ALandscapeProxy actor that can be reused. It is important + * to note that the request landscape actor could not be found, + * `OutWorld` and `OutLevel` should be used to spawn the + * new landscape actor. + * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. + */ + static ALandscapeProxy* FindExistingLandscapeActor( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage, + const EPackageMode& InPackageMode); + + static ALandscapeProxy* FindExistingLandscapeActor_Temp( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + /** + * Attempt the given ALandscapeActor to the outer HAC. Note + * that certain package modes (such as Bake) may choose not to do so. + * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. + */ + static void SetLandscapeActorAsOutput( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor, + const EPackageMode InPackageMode); + + static void SetLandscapeActorAsOutput_Bake( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor); + + static void SetLandscapeActorAsOutput_Temp( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor); + + /** + * Attach the given actor the HoudiniAssetComponent that + * owns `InOutput`, if any. + * @returns True if the actor was attached. Otherwise, return false. + */ + static bool AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor); + + /** + * Get the actor name suffix to be used in the specific packaging mode. + * @returns Suffix for actor names, return as an FString. + */ + static FString GetActorNameSuffix(const EPackageMode& InPackageMode); + + + // Helpers to get rid of repetitive boilerplate. + static void DoPreEditChangeProperty(UObject* Obj, FName PropertyName); + static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); + + public: + + + static const FHoudiniGeoPartObject* GetHoudiniHeightFieldFromOutput( + UHoudiniOutput* InOutput); + + static void GetHeightfieldsLayersFromOutput( + const UHoudiniOutput* InOutput, + const FHoudiniGeoPartObject& Heightfield, + TArray< const FHoudiniGeoPartObject* >& FoundLayers); + + static bool GetHoudiniHeightfieldFloatData( + const FHoudiniGeoPartObject* HGPO, + TArray &OutFloatArr, + float &OutFloatMin, + float &OutFloatMax); + + static bool CalcLandscapeSizeFromHeightfieldSize( + const int32& HoudiniSizeX, + const int32& HoudiniSizeY, + int32& UnrealSizeX, + int32& UnrealSizeY, + int32& NumSectionsPerComponent, + int32& NumQuadsPerSection); + + static bool ConvertHeightfieldDataToLandscapeData( + const TArray< float >& HeightfieldFloatValues, + const FHoudiniVolumeInfo& HeightfieldVolumeInfo, + const int32& FinalXSize, + const int32& FinalYSize, + float FloatMin, + float FloatMax, + TArray< uint16 >& IntHeightData, + FTransform& LandscapeTransform, + const bool& NoResize = false); + + static bool ResizeHeightDataForLandscape( + TArray& HeightData, + const int32& SizeX, + const int32& SizeY, + const int32& NewSizeX, + const int32& NewSizeY, + FVector& LandscapeResizeFactor, + FVector& LandscapePositionOffset); + + static bool CreateOrUpdateLandscapeLayers( + const TArray& FoundLayers, + const FHoudiniGeoPartObject& HeightField, + const int32& LandscapeXSize, + const int32& LandscapeYSize, + const TMap &GlobalMinimums, + const TMap &GlobalMaximums, + TArray& OutLayerInfos, + bool bIsUpdate, + const FHoudiniPackageParams& InTilePackageParams, + const FHoudiniPackageParams& InLayerPackageParams, + TArray& OutCreatedPackages); + + static bool GetNonWeightBlendedLayerNames( + const FHoudiniGeoPartObject& HeightfieldGeoPartObject, + TArray& NonWeightBlendedLayerNames); + + static bool IsUnitLandscapeLayer( + const FHoudiniGeoPartObject& LayerGeoPartObject); + + // Return the height min/max values for all + static bool CalcHeightGlobalZminZMax( + const TArray& AllOutputs, + float& OutGlobalMin, + float& OutGlobalMax); + + // Returns the min/max values per layer/volume for an array of volumes/heightfields + static void CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums, + bool bShouldEmptyMaps=true); + + // Iterate over layers for the heightfields and retrieve min/max values + // from attributes, otherwise return default values. + static void GetLayersZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums); + + static bool ConvertHeightfieldLayerToLandscapeLayer( + const TArray& FloatLayerData, + const int32& HoudiniXSize, + const int32& HoudiniYSize, + const float& LayerMin, + const float& LayerMax, + const int32& LandscapeXSize, + const int32& LandscapeYSize, + TArray& LayerData, + const bool& NoResize = false); + + static bool ResizeLayerDataForLandscape( + TArray< uint8 >& LayerData, + const int32& SizeX, + const int32& SizeY, + const int32& NewSizeX, + const int32& NewSizeY); + + // static ALandscapeProxy * CreateLandscape( + // const TArray< uint16 >& IntHeightData, + // const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + // const FTransform& LandscapeTransform, + // const int32& XSize, + // const int32& YSize, + // const int32& NumSectionPerLandscapeComponent, + // const int32& NumQuadsPerLandscapeSection, + // UMaterialInterface* LandscapeMaterial, + // UMaterialInterface* LandscapeHoleMaterial, + // const bool& CreateLandscapeStreamingProxy, + // bool bNeedCreateNewWorld, + // UWorld* SpawnWorld, + // FHoudiniPackageParams InPackageParams, + // bool& bOutCreatedNewMap); + + static ALandscapeProxy* CreateLandscapeTileInWorld( + const TArray< uint16 >& IntHeightData, + const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + const FTransform& TileTransform, + const int32& XSize, + const int32& YSize, + const int32& NumSectionPerLandscapeComponent, + const int32& NumQuadsPerLandscapeSection, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* LandscapePhsyicalMaterial, + const FString& LandscapeTileActorName, + LandscapeActorType ActorType, + ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies + UWorld* InWorld, // World in which to spawn + ULevel* InLevel, // Level, contained in World, in which to spawn. + FHoudiniPackageParams InPackageParams); + +protected: + + /** + * Calculate the location of a landscape tile. + * This location is typically used in conjunction with ALandscapeProxy::SetAbsoluteSectionBase(). + */ + static void CalculateTileLocation( + int32 NumSectionsPerComponent, + int32 NumQuadsPerSection, + const FTransform& InTileTransform, + FTransform& OutLandscapeOffset, + FTransform& OutTileTransform, + FIntPoint& OutTileLocation); + +public: + + static void GetLandscapeMaterials( + const FHoudiniGeoPartObject& InHeightHGPO, + UMaterialInterface*& OutLandscapeMaterial, + UMaterialInterface*& OutLandscapeHoleMaterial, + UPhysicalMaterial*& OutLandscapePhysicalMaterial); + + static bool GetLandscapeComponentExtentAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + int32& MinX, + int32& MaxX, + int32& MinY, + int32& MaxY); + + static ULandscapeLayerInfoObject* FindOrCreateLandscapeLayerInfoObject( + const FString& InLayerName, + const FString& InPackagePath, + const FString& InPackageName, + UPackage*& OutPackage); + + static bool EnableWorldComposition(); + + static bool GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const int32& InPrimIndex, + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, + const TArray& InAllPropertyAttributes); + + static bool BackupLandscapeToImageFiles( + const FString& BaseName, ALandscapeProxy* Landscape); + + static bool RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy); + + static UPhysicalMaterial* GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO); + + static ULandscapeLayerInfoObject* GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName); + + private: + + static bool ImportLandscapeData( + ULandscapeInfo* LandscapeInfo, + const FString& Filename, + const FString& LayerName, + ULandscapeLayerInfoObject* LayerInfoObject = nullptr); + + static UTexture2D* CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& InFloatBuffer, + const float& InMin, + const float& InMax); + + static UTexture2D* CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& IntBuffer); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp index 99a17368b..97523fae4 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp @@ -1,3237 +1,3231 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniMaterialTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPackageParams.h" - -#include "Materials/Material.h" -#include "Materials/MaterialInstance.h" -#include "Materials/MaterialInstanceConstant.h" -#include "PhysicalMaterials/PhysicalMaterial.h" - -#include "Materials/MaterialExpressionTextureSample.h" -#include "Materials/MaterialExpressionTextureCoordinate.h" -#include "Materials/MaterialExpressionConstant4Vector.h" -#include "Materials/MaterialExpressionConstant.h" -#include "Materials/MaterialExpressionMultiply.h" -#include "Materials/MaterialExpressionVertexColor.h" -#include "Materials/MaterialExpressionTextureSampleParameter2D.h" -#include "Materials/MaterialExpressionVectorParameter.h" -#include "Materials/MaterialExpressionScalarParameter.h" -#include "ImageUtils.h" -#include "PackageTools.h" -#include "AssetRegistryModule.h" -#include "UObject/MetaData.h" - -#if WITH_EDITOR - #include "Factories/MaterialFactoryNew.h" - #include "Factories/MaterialInstanceConstantFactoryNew.h" -#endif - -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeX = -400; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeY = -150; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepX = 220; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepY = 220; - -bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( - const HAPI_NodeId& InAssetId, - const FHoudiniPackageParams& InPackageParams, - const TArray& InUniqueMaterialIds, - const TArray& InUniqueMaterialInfos, - const TMap& InMaterials, - TMap& OutMaterials, - TArray& OutPackages, - const bool& bForceRecookAll, - bool bInTreatExistingMaterialsAsUpToDate) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMaterialTranslator::CreateHoudiniMaterials")); - - if (InUniqueMaterialIds.Num() <= 0) - return false; - - if (InUniqueMaterialInfos.Num() != InUniqueMaterialIds.Num()) - return false; - - // Empty returned materials. - OutMaterials.Empty(); - - // Update context for generated materials (will trigger when object goes out of scope). - FMaterialUpdateContext MaterialUpdateContext; - - // Default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - OutMaterials.Add(HAPI_UNREAL_DEFAULT_MATERIAL_NAME, DefaultMaterial); - - // Factory to create materials. - UMaterialFactoryNew * MaterialFactory = NewObject(); - MaterialFactory->AddToRoot(); - - for (int32 MaterialIdx = 0; MaterialIdx < InUniqueMaterialIds.Num(); MaterialIdx++) - { - HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; - - HAPI_MaterialInfo MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; - if (!MaterialInfo.exists) - { - // The material does not exist, - // we will use the default Houdini material in this case. - continue; - } - - // Get the material node's node information. - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &NodeInfo)) - { - continue; - } - - FString MaterialName = TEXT(""); - if (!FHoudiniEngineString::ToFString(NodeInfo.nameSH, MaterialName)) - { - // shouldnt happen, give a generic name - HOUDINI_LOG_WARNING(TEXT("Failed to retrieve material name!")); - MaterialName = TEXT("Material_") + FString::FromInt(MaterialInfo.nodeId); - } - - FString MaterialPathName = TEXT(""); - if (!FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, MaterialInfo, MaterialPathName)) - continue; - - // TODO: GetAssetName! - FString AssetName = TEXT("HoudiniAsset"); - - bool bCreatedNewMaterial = false; - - // TODO: Check existing material map!! - //UMaterial * Material = HoudiniCookParams.HoudiniCookManager ? Cast< UMaterial >(HoudiniCookParams.HoudiniCookManager->GetAssignmentMaterial(MaterialShopName)) : nullptr; - UMaterial * Material = nullptr; - UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); - if (FoundMaterial) - { - Material = Cast(*FoundMaterial); - } - - if (Material && !Material->IsPendingKill()) - { - // If cached material exists and has not changed, we can reuse it. - if ((bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll) - { - // We found cached material, we can reuse it. - OutMaterials.Add(MaterialPathName, Material); - continue; - } - } - else - { - // Previous Material was not found, we need to create a new one. - // TODO: Handle this! - //EObjectFlags ObjFlags = (HoudiniCookParams.MaterialAndTextureBakeMode == EBakeMode::Intermediate) ? RF_Transactional : RF_Public | RF_Standalone; - EObjectFlags ObjFlags = RF_Public | RF_Standalone; - - // Create material package and get material name. - FString MaterialPackageName; - UPackage * MaterialPackage = FHoudiniMaterialTranslator::CreatePackageForMaterial( - MaterialInfo.nodeId, MaterialName, InPackageParams, MaterialPackageName); - - Material = (UMaterial *)MaterialFactory->FactoryCreateNew( - UMaterial::StaticClass(), MaterialPackage, *MaterialPackageName, ObjFlags, NULL, GWarn); - - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialName); - - bCreatedNewMaterial = true; - } - - if (!Material || Material->IsPendingKill()) - continue; - - // Get the package and add it to our list - UPackage* Package = Material->GetOutermost(); - OutPackages.AddUnique(Package); - - /* - // TODO: This should be handled in the mesh/instance translator - // If this is an instancer material, enable the instancing flag. - if (UniqueInstancerMaterialIds.Contains(MaterialId)) - Material->bUsedWithInstancedStaticMeshes = true; - */ - - // Reset material expressions. - Material->Expressions.Empty(); - - // Generate various components for this material. - bool bMaterialComponentCreated = false; - int32 MaterialNodeY = FHoudiniMaterialTranslator::MaterialExpressionNodeY; - - // By default we mark material as opaque. Some of component creators can change this. - Material->BlendMode = BLEND_Opaque; - - // Extract diffuse plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract opacity plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract opacity mask plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract normal plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentNormal( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract specular plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract roughness plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract metallic plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract emissive plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Set other material properties. - Material->TwoSided = true; - Material->SetShadingModel(MSM_DefaultLit); - - // Schedule this material for update. - MaterialUpdateContext.AddMaterial(Material); - - // Cache material. - OutMaterials.Add(MaterialPathName, Material); - - // Propagate and trigger material updates. - if (bCreatedNewMaterial) - FAssetRegistryModule::AssetCreated(Material); - - Material->PreEditChange(nullptr); - Material->PostEditChange(); - Material->MarkPackageDirty(); - } - - MaterialFactory->RemoveFromRoot(); - - return true; -} - -// -bool -FHoudiniMaterialTranslator::CreateMaterialInstances( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& UniqueMaterialInstanceOverrides, - const TArray& InPackages, - const TMap& InMaterials, - TMap& OutMaterials, - const bool& bForceRecookAll) -{ - // Check the node ID is valid - if (InHGPO.AssetId < 0) - return false; - - // No material instance attributes - if (UniqueMaterialInstanceOverrides.Num() <= 0) - return false; - - // TODO: Improve! - // Get the material name from the material_instance attribute - // Since the material instance attribute can be set per primitive, it's going to be very difficult to know - // exactly where to look for the nth material instance. In order for the material slot to be created, - // we used the fact that the material instance attribute had to be different - // This is pretty hacky and we should probably require an extra material_instance_index attribute instead. - // as we can only create one instance of the same material, and cant get two slots for the same "source" material. - int32 MaterialIndex = 0; - for (TMap::TConstIterator Iter(UniqueMaterialInstanceOverrides); Iter; ++Iter) - { - FString CurrentSourceMaterial = Iter->Key; - if (CurrentSourceMaterial.IsEmpty()) - continue; - - // Try to find the material we want to create an instance of - UMaterialInterface* CurrentSourceMaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentSourceMaterial, nullptr, LOAD_NoWarn, nullptr)); - - if (!CurrentSourceMaterialInterface || CurrentSourceMaterialInterface->IsPendingKill()) - { - // Couldn't find the source material - HOUDINI_LOG_WARNING(TEXT("Couldn't find the source material %s to create a material instance."), *CurrentSourceMaterial); - continue; - } - - // Create/Retrieve the package for the MI - FString MaterialInstanceName; - FString MaterialInstanceNamePrefix = UPackageTools::SanitizePackageName( - CurrentSourceMaterialInterface->GetName() + TEXT("_instance_") + FString::FromInt(MaterialIndex)); - - // Increase the material index - MaterialIndex++; - - // See if we can find an existing package for that instance - UPackage * MaterialInstancePackage = nullptr; - UMaterialInterface * const * FoundMatPtr = InMaterials.Find(MaterialInstanceNamePrefix); - if (FoundMatPtr && *FoundMatPtr) - { - // We found an already existing MI, get its package - MaterialInstancePackage = Cast((*FoundMatPtr)->GetOuter()); - } - - if (MaterialInstancePackage) - { - MaterialInstanceName = MaterialInstancePackage->GetName(); - } - else - { - // We couldnt find the corresponding M_I package, so create a new one - MaterialInstancePackage = CreatePackageForMaterial(InHGPO.AssetId, MaterialInstanceNamePrefix, InPackageParams, MaterialInstanceName); - } - - // Couldn't create a package for that Material Instance - if (!MaterialInstancePackage) - continue; - - bool bNewMaterialCreated = false; - UMaterialInstanceConstant* NewMaterialInstance = LoadObject(MaterialInstancePackage, *MaterialInstanceName, nullptr, LOAD_None, nullptr); - if (!NewMaterialInstance) - { - // Factory to create materials. - UMaterialInstanceConstantFactoryNew* MaterialInstanceFactory = NewObject< UMaterialInstanceConstantFactoryNew >(); - if (!MaterialInstanceFactory) - continue; - - // Create the new material instance - MaterialInstanceFactory->AddToRoot(); - MaterialInstanceFactory->InitialParent = CurrentSourceMaterialInterface; - NewMaterialInstance = (UMaterialInstanceConstant*)MaterialInstanceFactory->FactoryCreateNew( - UMaterialInstanceConstant::StaticClass(), MaterialInstancePackage, FName(*MaterialInstanceName), - RF_Public | RF_Standalone, NULL, GWarn); - - if (NewMaterialInstance) - bNewMaterialCreated = true; - - MaterialInstanceFactory->RemoveFromRoot(); - } - - if (!NewMaterialInstance) - { - HOUDINI_LOG_WARNING(TEXT("Couldn't access the material instance for %s"), *CurrentSourceMaterial); - continue; - } - - // Update context for generated materials (will trigger when the object goes out of scope). - FMaterialUpdateContext MaterialUpdateContext; - - bool bModifiedMaterialParameters = false; - // See if we need to override some of the material instance's parameters - TArray AllMatParams; - // Get the detail material parameters - int32 ParamCount = FHoudiniEngineUtils::GetGenericAttributeList( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, - AllMatParams, HAPI_ATTROWNER_DETAIL, -1); - - // Then the primitive material parameters - int32 MaterialIndexToAttributeIndex = Iter->Value; - ParamCount += FHoudiniEngineUtils::GetGenericAttributeList( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, - AllMatParams, HAPI_ATTROWNER_PRIM, MaterialIndexToAttributeIndex); - - for (int32 ParamIdx = 0; ParamIdx < AllMatParams.Num(); ParamIdx++) - { - // Try to update the material instance parameter corresponding to the attribute - if (UpdateMaterialInstanceParameter(AllMatParams[ParamIdx], NewMaterialInstance, InPackages)) - bModifiedMaterialParameters = true; - } - - // Schedule this material for update if needed. - if (bNewMaterialCreated || bModifiedMaterialParameters) - MaterialUpdateContext.AddMaterialInstance(NewMaterialInstance); - - if (bNewMaterialCreated) - { - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialInstanceName); - // Notify registry that we have created a new material. - FAssetRegistryModule::AssetCreated(NewMaterialInstance); - } - - if (bNewMaterialCreated || bModifiedMaterialParameters) - { - // Dirty the material - NewMaterialInstance->MarkPackageDirty(); - - // Update the material instance - NewMaterialInstance->InitStaticPermutation(); - NewMaterialInstance->PreEditChange(nullptr); - NewMaterialInstance->PostEditChange(); - /* - // Automatically save the package to avoid further issue - MaterialInstancePackage->SetDirtyFlag( true ); - MaterialInstancePackage->FullyLoad(); - UPackage::SavePackage( - MaterialInstancePackage, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, - *FPackageName::LongPackageNameToFilename( MaterialInstancePackage->GetName(), FPackageName::GetAssetPackageExtension() ) ); - */ - } - - // Add the created material to the output assignement map - // Use the "source" material name as we want the instance to replace it - OutMaterials.Add(CurrentSourceMaterial, NewMaterialInstance); - } - - return true; -} - -bool -FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath) -{ - HAPI_MaterialInfo MaterialInfo; - FHoudiniApi::MaterialInfo_Init(&MaterialInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetMaterialInfo( - FHoudiniEngine::Get().GetSession(), InMaterialNodeId, - &MaterialInfo), false); - - return GetMaterialRelativePath(InAssetId, MaterialInfo, OutRelativePath); -} -bool -FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialInfo, FString& OutRelativePath) -{ - if (InAssetId < 0 || !InMaterialInfo.exists) - return false; - - // We want to get the asset node path so we can remove it from the material name - FString AssetNodeName = TEXT(""); - { - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); - - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo), false); - - FHoudiniEngineString::ToFString(AssetNodeInfo.internalNodePathSH, AssetNodeName); - } - - // Get the material name from the info - FString MaterialNodeName = TEXT(""); - { - HAPI_NodeInfo MaterialNodeInfo; - FHoudiniApi::NodeInfo_Init(&MaterialNodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &MaterialNodeInfo), false); - - FHoudiniEngineString::ToFString(MaterialNodeInfo.internalNodePathSH, MaterialNodeName); - } - - if (AssetNodeName.Len() > 0 && MaterialNodeName.Len() > 0) - { - // Remove AssetNodeName part from MaterialNodeName. Extra position is for separator. - OutRelativePath = MaterialNodeName.Mid(AssetNodeName.Len() + 1); - return true; - } - - return false; -} - - -UPackage* -FHoudiniMaterialTranslator::CreatePackageForMaterial( - const HAPI_NodeId& InMaterialNodeId, - const FString& InMaterialName, - const FHoudiniPackageParams& InPackageParams, - FString& OutMaterialName) -{ - FString MaterialDescriptor = TEXT("_material_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InMaterialName; - - FHoudiniPackageParams MyPackageParams = InPackageParams; - if (!MyPackageParams.ObjectName.IsEmpty()) - { - MyPackageParams.ObjectName += MaterialDescriptor; - } - else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) - { - MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + MaterialDescriptor; - } - else - { - MyPackageParams.ObjectName = MaterialDescriptor; - } - MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); - - return MyPackageParams.CreatePackageForObject(OutMaterialName); -} - - -UPackage* -FHoudiniMaterialTranslator::CreatePackageForTexture( - const HAPI_NodeId& InMaterialNodeId, - const FString& InTextureType, - const FHoudiniPackageParams& InPackageParams, - FString& OutTextureName) -{ - FString TextureInfoDescriptor = TEXT("_texture_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InTextureType; - FHoudiniPackageParams MyPackageParams = InPackageParams; - if (!MyPackageParams.ObjectName.IsEmpty()) - { - MyPackageParams.ObjectName += TextureInfoDescriptor; - } - else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) - { - MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + TextureInfoDescriptor; - } - else - { - MyPackageParams.ObjectName = TextureInfoDescriptor; - } - MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); - - return MyPackageParams.CreatePackageForObject(OutTextureName); -} - - -UTexture2D * -FHoudiniMaterialTranslator::CreateUnrealTexture( - UTexture2D* ExistingTexture, - const HAPI_ImageInfo& ImageInfo, - UPackage* Package, - const FString& TextureName, - const TArray& ImageBuffer, - const FCreateTexture2DParameters& TextureParameters, - const TextureGroup& LODGroup, - const FString& TextureType, - const FString& NodePath) -{ - if (!Package || Package->IsPendingKill()) - return nullptr; - - UTexture2D * Texture = nullptr; - if (ExistingTexture) - { - Texture = ExistingTexture; - } - else - { - // Create new texture object. - Texture = NewObject< UTexture2D >( - Package, UTexture2D::StaticClass(), *TextureName, - RF_Transactional); - - // Assign texture group. - Texture->LODGroup = LODGroup; - } - - // Add/Update meta information to package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *TextureName); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_NODE_PATH, *NodePath); - - // Initialize texture source. - Texture->Source.Init(ImageInfo.xRes, ImageInfo.yRes, 1, 1, TSF_BGRA8); - - // Lock the texture. - uint8 * MipData = Texture->Source.LockMip(0); - - // Create base map. - uint8* DestPtr = nullptr; - uint32 SrcWidth = ImageInfo.xRes; - uint32 SrcHeight = ImageInfo.yRes; - const char * SrcData = &ImageBuffer[0]; - - for (uint32 y = 0; y < SrcHeight; y++) - { - DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; - - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth * 4 + x * 4; - - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 2); // B - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 1); // G - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 0); // R - - if (TextureParameters.bUseAlpha) - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 3); // A - else - *DestPtr++ = 0xFF; - } - } - - bool bHasAlphaValue = false; - if (TextureParameters.bUseAlpha) - { - // See if there is an actual alpha value in the texture or if we can ignore the texture alpha - for (uint32 y = 0; y < SrcHeight; y++) - { - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth * 4 + x * 4; - if (*(uint8*)(SrcData + DataOffset + 3) != 0xFF) - { - bHasAlphaValue = true; - break; - } - } - - if (bHasAlphaValue) - break; - } - } - - // Unlock the texture. - Texture->Source.UnlockMip(0); - - // Texture creation parameters. - Texture->SRGB = TextureParameters.bSRGB; - Texture->CompressionSettings = TextureParameters.CompressionSettings; - Texture->CompressionNoAlpha = !bHasAlphaValue; - Texture->DeferCompression = TextureParameters.bDeferCompression; - - // Set the Source Guid/Hash if specified. - /* - if ( TextureParameters.SourceGuidHash.IsValid() ) - { - Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); - } - */ - - Texture->PostEditChange(); - - return Texture; -} - - - -bool -FHoudiniMaterialTranslator::HapiExtractImage( - const HAPI_ParmId& NodeParmId, - const HAPI_MaterialInfo& MaterialInfo, - const char * PlaneType, - const HAPI_ImageDataFormat& ImageDataFormat, - HAPI_ImagePacking ImagePacking, - bool bRenderToImage, - TArray& OutImageBuffer ) -{ - if (bRenderToImage) - { - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, NodeParmId), false); - } - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImageInfo), false); - - ImageInfo.dataFormat = ImageDataFormat; - ImageInfo.interleaved = true; - ImageInfo.packing = ImagePacking; - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetImageInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImageInfo), false); - - int32 ImageBufferSize = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ExtractImageToMemory( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, HAPI_RAW_FORMAT_NAME, - PlaneType, &ImageBufferSize), false); - - if (ImageBufferSize <= 0) - return false; - - OutImageBuffer.SetNumUninitialized(ImageBufferSize); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImageMemoryBuffer( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &OutImageBuffer[0], - ImageBufferSize), false); - - return true; -} - -bool -FHoudiniMaterialTranslator::HapiGetImagePlanes( - const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes) -{ - OutImagePlanes.Empty(); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, NodeParmId), false); - - int32 ImagePlaneCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlaneCount( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImagePlaneCount), false); - - if (ImagePlaneCount <= 0) - return true; - - TArray ImagePlaneStringHandles; - ImagePlaneStringHandles.SetNumZeroed(ImagePlaneCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlanes( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImagePlaneStringHandles[0], ImagePlaneCount), false); - - for (int32 IdxPlane = 0; IdxPlane < ImagePlaneStringHandles.Num(); IdxPlane++) - { - FString ValueString; - if(FHoudiniEngineString::ToFString(ImagePlaneStringHandles[IdxPlane], ValueString)) - OutImagePlanes.Add(ValueString); - } - - return true; -} - - -UMaterialExpression * -FHoudiniMaterialTranslator::MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass) -{ - if (!Expression) - return nullptr; - -#if WITH_EDITOR - if (ExpressionClass == Expression->GetClass()) - return Expression; - - // If this is a channel multiply expression, we can recurse. - UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast< UMaterialExpressionMultiply >(Expression); - if (MaterialExpressionMultiply) - { - { - UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->A.Expression; - if (MaterialExpression) - { - if (MaterialExpression->GetClass() == ExpressionClass) - return MaterialExpression; - - MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( - Cast(MaterialExpression), ExpressionClass); - - if (MaterialExpression) - return MaterialExpression; - } - } - - { - UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->B.Expression; - if (MaterialExpression) - { - if (MaterialExpression->GetClass() == ExpressionClass) - return MaterialExpression; - - MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( - Cast(MaterialExpression), ExpressionClass); - - if (MaterialExpression) - return MaterialExpression; - } - } - } -#endif - - return nullptr; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Names of generating Houdini parameters. - FString GeneratingParameterNameDiffuseTexture = TEXT(""); - FString GeneratingParameterNameUniformColor = TEXT(""); - FString GeneratingParameterNameVertexColor = TEXT(HAPI_UNREAL_ATTRIB_COLOR); - - // Diffuse texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Default; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // Attempt to look up previously created expressions. - UMaterialExpression * MaterialExpression = Material->BaseColor.Expression; - - // Locate sampling expression. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureSample = - Cast< UMaterialExpressionTextureSampleParameter2D >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // If texture sampling expression does exist, attempt to look up corresponding texture. - UTexture2D * TextureDiffuse = nullptr; - if (ExpressionTextureSample && !ExpressionTextureSample->IsPendingKill()) - TextureDiffuse = Cast< UTexture2D >(ExpressionTextureSample->Texture); - - // Locate uniform color expression. - UMaterialExpressionVectorParameter * ExpressionConstant4Vector = - Cast< UMaterialExpressionVectorParameter >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionVectorParameter::StaticClass())); - - // If uniform color expression does not exist, create it. - if (!ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill()) - { - ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( - Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); - ExpressionConstant4Vector->DefaultValue = FLinearColor::White; - } - - // Add expression. - Material->Expressions.Add(ExpressionConstant4Vector); - - // Locate vertex color expression. - UMaterialExpressionVertexColor * ExpressionVertexColor = - Cast< UMaterialExpressionVertexColor >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionVertexColor::StaticClass())); - - // If vertex color expression does not exist, create it. - if (!ExpressionVertexColor || ExpressionVertexColor->IsPendingKill()) - { - ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( - Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag); - ExpressionVertexColor->Desc = GeneratingParameterNameVertexColor; - } - - // Add expression. - Material->Expressions.Add(ExpressionVertexColor); - - // Material should have at least one multiply expression. - UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast(MaterialExpression); - if (!MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill()) - MaterialExpressionMultiply = NewObject( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - // Add expression. - Material->Expressions.Add(MaterialExpressionMultiply); - - // See if primary multiplication has secondary multiplication as A input. - UMaterialExpressionMultiply * MaterialExpressionMultiplySecondary = nullptr; - if (MaterialExpressionMultiply->A.Expression) - MaterialExpressionMultiplySecondary = - Cast(MaterialExpressionMultiply->A.Expression); - - // See if a diffuse texture is available. - HAPI_ParmId ParmDiffuseTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - - if (ParmDiffuseTextureId >= 0) - { - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - } - else - { - ParmDiffuseTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - - if (ParmDiffuseTextureId >= 0) - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - } - - // See if uniform color is available. - HAPI_ParmInfo ParmInfoDiffuseColor; - FHoudiniApi::ParmInfo_Init(&ParmInfoDiffuseColor); - HAPI_ParmId ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0, ParmInfoDiffuseColor); - - if (ParmDiffuseColorId >= 0) - { - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0); - } - else - { - ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1, ParmInfoDiffuseColor); - - if (ParmDiffuseColorId >= 0) - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1); - } - - // If we have diffuse texture parameter. - if (ParmDiffuseTextureId >= 0) - { - TArray< char > ImageBuffer; - - // Get image planes of diffuse map. - TArray< FString > DiffuseImagePlanes; - bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( - ParmDiffuseTextureId, InMaterialInfo, DiffuseImagePlanes); - - HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; - const char * PlaneType = ""; - - if (bFoundImagePlanes && DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))) - { - if (DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA))) - { - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - - // Material does use alpha. - CreateTexture2DParameters.bUseAlpha = true; - } - else - { - // We still need to have the Alpha plane, just not the CreateTexture2DParameters - // alpha option. This is because all texture data from Houdini Engine contains - // the alpha plane by default. - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - } - } - else - { - bFoundImagePlanes = false; - } - - // Retrieve color plane. - if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( - ParmDiffuseTextureId, InMaterialInfo, PlaneType, - HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) - { - UPackage * TextureDiffusePackage = nullptr; - if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) - TextureDiffusePackage = Cast(TextureDiffuse->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureDiffuseName; - bool bCreatedNewTextureDiffuse = false; - - // Create diffuse texture package, if this is a new diffuse texture. - if (!TextureDiffusePackage) - { - TextureDiffusePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, - InPackageParams, - TextureDiffuseName); - } - else if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureDiffuseName = TextureDiffuse->GetName(); - } - else - { - TextureDiffuseName = FPaths::GetBaseFilename(TextureDiffusePackage->GetName(), true); - } - - // Create diffuse texture, if we need to create one. - if (!TextureDiffuse || TextureDiffuse->IsPendingKill()) - bCreatedNewTextureDiffuse = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing diffuse texture, or create new one. - TextureDiffuse = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureDiffuse, - ImageInfo, - TextureDiffusePackage, - TextureDiffuseName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureDiffuse->SetFlags(RF_Public | RF_Standalone); - - // Create diffuse sampling expression, if needed. - if (!ExpressionTextureSample) - { - ExpressionTextureSample = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionTextureSample->Desc = GeneratingParameterNameDiffuseTexture; - ExpressionTextureSample->ParameterName = *GeneratingParameterNameDiffuseTexture; - ExpressionTextureSample->Texture = TextureDiffuse; - ExpressionTextureSample->SamplerType = SAMPLERTYPE_Color; - - // Add expression. - Material->Expressions.Add(ExpressionTextureSample); - - // Propagate and trigger diffuse texture updates. - if (bCreatedNewTextureDiffuse) - FAssetRegistryModule::AssetCreated(TextureDiffuse); - - TextureDiffuse->PreEditChange(nullptr); - TextureDiffuse->PostEditChange(); - TextureDiffuse->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureDiffusePackage); - } - } - - // If we have uniform color parameter. - if (ParmDiffuseColorId >= 0) - { - FLinearColor Color = FLinearColor::White; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&Color.R, - ParmInfoDiffuseColor.floatValuesIndex, ParmInfoDiffuseColor.size) == HAPI_RESULT_SUCCESS) - { - if (ParmInfoDiffuseColor.size == 3) - Color.A = 1.0f; - - // Record generating parameter. - ExpressionConstant4Vector->Desc = GeneratingParameterNameUniformColor; - ExpressionConstant4Vector->ParameterName = *GeneratingParameterNameUniformColor; - ExpressionConstant4Vector->DefaultValue = Color; - } - } - - // If we have have texture sample expression present, we need a secondary multiplication expression. - if (ExpressionTextureSample) - { - if (!MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary = NewObject< UMaterialExpressionMultiply >( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - // Add expression. - Material->Expressions.Add(MaterialExpressionMultiplySecondary); - } - } - else - { - // If secondary multiplication exists, but we have no sampling, we can free it. - if (MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary->A.Expression = nullptr; - MaterialExpressionMultiplySecondary->B.Expression = nullptr; - MaterialExpressionMultiplySecondary->ConditionalBeginDestroy(); - } - } - - float SecondaryExpressionScale = 1.0f; - if (MaterialExpressionMultiplySecondary) - SecondaryExpressionScale = 1.5f; - - // Create multiplication expression which has uniform color and vertex color. - MaterialExpressionMultiply->A.Expression = ExpressionConstant4Vector; - MaterialExpressionMultiply->B.Expression = ExpressionVertexColor; - - ExpressionConstant4Vector->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionConstant4Vector->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - ExpressionVertexColor->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionVertexColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiply->MaterialExpressionEditorY = - (ExpressionVertexColor->MaterialExpressionEditorY - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; - - // Hook up secondary multiplication expression to first one. - if (MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; - MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; - - ExpressionTextureSample->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiplySecondary->MaterialExpressionEditorY = - MaterialExpressionMultiply->MaterialExpressionEditorY + FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression. - Material->BaseColor.Expression = MaterialExpressionMultiplySecondary; - } - else - { - // Assign expression. - Material->BaseColor.Expression = MaterialExpressionMultiply; - - MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiply->MaterialExpressionEditorY = - (ExpressionVertexColor->MaterialExpressionEditorY - - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; - } - - return true; -} - - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - // Name of generating Houdini parameters. - FString GeneratingParameterNameTexture = TEXT(""); - - UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Opacity expressions. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; - UTexture2D * TextureOpacity = nullptr; - - // Opacity texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // See if opacity texture is available. - HAPI_ParmId ParmOpacityTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_0); - - if (ParmOpacityTextureId >= 0) - { - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_0); - } - else - { - ParmOpacityTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_1); - - if (ParmOpacityTextureId >= 0) - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_1); - } - - // If we have opacity texture parameter. - if (ParmOpacityTextureId >= 0) - { - TArray< char > ImageBuffer; - - // Get image planes of opacity map. - TArray< FString > OpacityImagePlanes; - bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( - ParmOpacityTextureId, InMaterialInfo, OpacityImagePlanes); - - HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; - const char * PlaneType = ""; - - bool bColorAlphaFound = (OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA)) && OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))); - - if (bFoundImagePlanes && bColorAlphaFound) - { - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - CreateTexture2DParameters.bUseAlpha = true; - } - else - { - bFoundImagePlanes = false; - } - - if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( - ParmOpacityTextureId, InMaterialInfo, PlaneType, - HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) - { - // Locate sampling expression. - ExpressionTextureOpacitySample = Cast< UMaterialExpressionTextureSampleParameter2D >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // Locate opacity texture, if valid. - if (ExpressionTextureOpacitySample) - TextureOpacity = Cast< UTexture2D >(ExpressionTextureOpacitySample->Texture); - - UPackage * TextureOpacityPackage = nullptr; - if (TextureOpacity) - TextureOpacityPackage = Cast< UPackage >(TextureOpacity->GetOuter()); - - HAPI_ImageInfo ImageInfo; - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureOpacityName; - bool bCreatedNewTextureOpacity = false; - - // Create opacity texture package, if this is a new opacity texture. - if (!TextureOpacityPackage) - { - TextureOpacityPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, - InPackageParams, - TextureOpacityName); - } - else if (TextureOpacity && !TextureOpacity->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureOpacityName = TextureOpacity->GetName(); - } - else - { - TextureOpacityName = FPaths::GetBaseFilename(TextureOpacityPackage->GetName(), true); - } - - // Create opacity texture, if we need to create one. - if (!TextureOpacity) - bCreatedNewTextureOpacity = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing opacity texture, or create new one. - TextureOpacity = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureOpacity, - ImageInfo, - TextureOpacityPackage, - TextureOpacityName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, - NodePath); - - // if (BakeMode == EBakeMode::CookToTemp) - TextureOpacity->SetFlags(RF_Public | RF_Standalone); - - // Create opacity sampling expression, if needed. - if (!ExpressionTextureOpacitySample) - { - ExpressionTextureOpacitySample = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionTextureOpacitySample->Desc = GeneratingParameterNameTexture; - ExpressionTextureOpacitySample->ParameterName = *GeneratingParameterNameTexture; - ExpressionTextureOpacitySample->Texture = TextureOpacity; - ExpressionTextureOpacitySample->SamplerType = SAMPLERTYPE_Grayscale; - - // Offset node placement. - ExpressionTextureOpacitySample->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionTextureOpacitySample->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Add expression. - Material->Expressions.Add(ExpressionTextureOpacitySample); - - // We need to set material type to masked. - TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput* ExpressionOutput = ExpressionOutputs.GetData(); - - Material->OpacityMask.Expression = ExpressionTextureOpacitySample; - Material->BlendMode = BLEND_Masked; - - Material->OpacityMask.Mask = ExpressionOutput->Mask; - Material->OpacityMask.MaskR = 1; - Material->OpacityMask.MaskG = 0; - Material->OpacityMask.MaskB = 0; - Material->OpacityMask.MaskA = 0; - - // Propagate and trigger opacity texture updates. - if (bCreatedNewTextureOpacity) - FAssetRegistryModule::AssetCreated(TextureOpacity); - - TextureOpacity->PreEditChange(nullptr); - TextureOpacity->PostEditChange(); - TextureOpacity->MarkPackageDirty(); - - bExpressionCreated = true; - } - - // Cache the texture package - OutPackages.AddUnique(TextureOpacityPackage); - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - float OpacityValue = 1.0f; - bool bNeedsTranslucency = false; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameters. - FString GeneratingParameterNameScalar = TEXT(""); - FString GeneratingParameterNameTexture = TEXT(""); - - UMaterialExpression * MaterialExpression = Material->Opacity.Expression; - - // Opacity expressions. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; - UMaterialExpressionScalarParameter * ExpressionScalarOpacity = nullptr; - UTexture2D * TextureOpacity = nullptr; - - // Opacity texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // If opacity sampling expression was not created, check if diffuse contains an alpha plane. - if (!ExpressionTextureOpacitySample) - { - UMaterialExpression * MaterialExpressionDiffuse = Material->BaseColor.Expression; - if (MaterialExpressionDiffuse) - { - // Locate diffuse sampling expression. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureDiffuseSample = - Cast< UMaterialExpressionTextureSampleParameter2D >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpressionDiffuse, - UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // See if there's an alpha plane in this expression's texture. - if (ExpressionTextureDiffuseSample) - { - UTexture2D * DiffuseTexture = Cast< UTexture2D >(ExpressionTextureDiffuseSample->Texture); - if (DiffuseTexture && !DiffuseTexture->CompressionNoAlpha) - { - // The diffuse texture has an alpha channel (that wasn't discarded), so we can use it - ExpressionTextureOpacitySample = ExpressionTextureDiffuseSample; - bNeedsTranslucency = true; - } - } - } - } - - // Retrieve opacity uniform parameter. - HAPI_ParmInfo ParmInfoOpacityValue; - FHoudiniApi::ParmInfo_Init(&ParmInfoOpacityValue); - HAPI_ParmId ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_0, ParmInfoOpacityValue); - - if (ParmOpacityValueId >= 0) - { - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_0); - } - else - { - ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_1, ParmInfoOpacityValue); - - if (ParmOpacityValueId >= 0) - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_1); - } - - if (ParmOpacityValueId >= 0) - { - if (ParmInfoOpacityValue.size > 0 && ParmInfoOpacityValue.floatValuesIndex >= 0) - { - float OpacityValueRetrieved = 1.0f; - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, - (float *)&OpacityValue, ParmInfoOpacityValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - if (!ExpressionScalarOpacity) - { - ExpressionScalarOpacity = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Clamp retrieved value. - OpacityValueRetrieved = FMath::Clamp< float >(OpacityValueRetrieved, 0.0f, 1.0f); - OpacityValue = OpacityValueRetrieved; - - // Set expression fields. - ExpressionScalarOpacity->DefaultValue = OpacityValue; - ExpressionScalarOpacity->SliderMin = 0.0f; - ExpressionScalarOpacity->SliderMax = 1.0f; - ExpressionScalarOpacity->Desc = GeneratingParameterNameScalar; - ExpressionScalarOpacity->ParameterName = *GeneratingParameterNameScalar; - - // Add expression. - Material->Expressions.Add(ExpressionScalarOpacity); - - // If alpha is less than 1, we need translucency. - bNeedsTranslucency |= (OpacityValue != 1.0f); - } - } - } - - if (bNeedsTranslucency) - Material->BlendMode = BLEND_Translucent; - - if (ExpressionScalarOpacity && ExpressionTextureOpacitySample) - { - // We have both alpha and alpha uniform, attempt to locate multiply expression. - UMaterialExpressionMultiply * ExpressionMultiply = - Cast< UMaterialExpressionMultiply >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, - UMaterialExpressionMultiply::StaticClass())); - - if (!ExpressionMultiply) - ExpressionMultiply = NewObject< UMaterialExpressionMultiply >( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - Material->Expressions.Add(ExpressionMultiply); - - TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); - - ExpressionMultiply->A.Expression = ExpressionTextureOpacitySample; - ExpressionMultiply->B.Expression = ExpressionScalarOpacity; - - Material->Opacity.Expression = ExpressionMultiply; - Material->Opacity.Mask = ExpressionOutput->Mask; - Material->Opacity.MaskR = 0; - Material->Opacity.MaskG = 0; - Material->Opacity.MaskB = 0; - Material->Opacity.MaskA = 1; - - ExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMultiply->MaterialExpressionEditorY = MaterialNodeY; - - ExpressionScalarOpacity->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX; - ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - bExpressionCreated = true; - } - else if (ExpressionScalarOpacity) - { - Material->Opacity.Expression = ExpressionScalarOpacity; - - ExpressionScalarOpacity->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - bExpressionCreated = true; - } - else if (ExpressionTextureOpacitySample) - { - TArray ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); - - Material->Opacity.Expression = ExpressionTextureOpacitySample; - Material->Opacity.Mask = ExpressionOutput->Mask; - Material->Opacity.MaskR = 0; - Material->Opacity.MaskG = 0; - Material->Opacity.MaskB = 0; - Material->Opacity.MaskA = 1; - - bExpressionCreated = true; - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentNormal( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - bool bTangentSpaceNormal = true; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Normal texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Normalmap; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if separate normal texture is available. - HAPI_ParmId ParmNameNormalId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_0); - - if (ParmNameNormalId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_0); - } - else - { - ParmNameNormalId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_1); - - if (ParmNameNormalId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_1); - } - - if (ParmNameNormalId >= 0) - { - // Retrieve space for this normal texture. - HAPI_ParmInfo ParmInfoNormalType; - FHoudiniApi::ParmInfo_Init(&ParmInfoNormalType); - int32 ParmNormalTypeId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); - - // Retrieve value for normal type choice list (if exists). - - if (ParmNormalTypeId >= 0) - { - FString NormalType = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT); - - if (ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0) - { - HAPI_StringHandle StringHandle; - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, false, &StringHandle, ParmInfoNormalType.stringValuesIndex, ParmInfoNormalType.size) == HAPI_RESULT_SUCCESS) - { - // Get the actual string value. - FString NormalTypeString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(NormalTypeString)) - NormalType = NormalTypeString; - } - } - - // Check if we require world space normals. - if (NormalType.Equals(TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD), ESearchCase::IgnoreCase)) - bTangentSpaceNormal = false; - } - - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameNormalId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); - - UTexture2D * TextureNormal = nullptr; - if (ExpressionNormal) - { - TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Normal.Expression) - { - Material->Normal.Expression->ConditionalBeginDestroy(); - Material->Normal.Expression = nullptr; - } - } - - UPackage * TextureNormalPackage = nullptr; - if (TextureNormal) - TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureNormalName; - bool bCreatedNewTextureNormal = false; - - // Create normal texture package, if this is a new normal texture. - if (!TextureNormalPackage) - { - TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - InPackageParams, - TextureNormalName); - } - else if (TextureNormal && !TextureNormal->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureNormalName = TextureNormal->GetName(); - } - else - { - TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); - } - - // Create normal texture, if we need to create one. - if (!TextureNormal) - bCreatedNewTextureNormal = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing normal texture, or create new one. - TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureNormal, - ImageInfo, - TextureNormalPackage, - TextureNormalName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_WorldNormalMap, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureNormal->SetFlags(RF_Public | RF_Standalone); - - // Create normal sampling expression, if needed. - if (!ExpressionNormal) - ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionNormal->Desc = GeneratingParameterName; - ExpressionNormal->ParameterName = *GeneratingParameterName; - - ExpressionNormal->Texture = TextureNormal; - ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; - - // Offset node placement. - ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Set normal space. - Material->bTangentSpaceNormal = bTangentSpaceNormal; - - // Assign expression to material. - Material->Expressions.Add(ExpressionNormal); - Material->Normal.Expression = ExpressionNormal; - - bExpressionCreated = true; - - // Propagate and trigger normal texture updates. - if (bCreatedNewTextureNormal) - FAssetRegistryModule::AssetCreated(TextureNormal); - - TextureNormal->PreEditChange(nullptr); - TextureNormal->PostEditChange(); - TextureNormal->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureNormalPackage); - } - } - - // If separate normal map was not found, see if normal plane exists in diffuse map. - if (!bExpressionCreated) - { - // See if diffuse texture is available. - HAPI_ParmId ParmNameBaseId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - - if (ParmNameBaseId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - } - else - { - ParmNameBaseId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - - if (ParmNameBaseId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - } - - if (ParmNameBaseId >= 0) - { - // Normal plane is available in diffuse map. - - TArray< char > ImageBuffer; - - // Retrieve color plane - this will contain normal data. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameBaseId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); - - UTexture2D * TextureNormal = nullptr; - if (ExpressionNormal) - { - TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Normal.Expression) - { - Material->Normal.Expression->ConditionalBeginDestroy(); - Material->Normal.Expression = nullptr; - } - } - - UPackage * TextureNormalPackage = nullptr; - if (TextureNormal) - TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureNormalName; - bool bCreatedNewTextureNormal = false; - - // Create normal texture package, if this is a new normal texture. - if (!TextureNormalPackage) - { - TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - InPackageParams, - TextureNormalName); - } - else if (TextureNormal && !TextureNormal->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureNormalName = TextureNormal->GetName(); - } - else - { - TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); - } - - // Create normal texture, if we need to create one. - if (!TextureNormal) - bCreatedNewTextureNormal = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing normal texture, or create new one. - TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureNormal, - ImageInfo, - TextureNormalPackage, - TextureNormalName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_WorldNormalMap, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureNormal->SetFlags(RF_Public | RF_Standalone); - - // Create normal sampling expression, if needed. - if (!ExpressionNormal) - ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionNormal->Desc = GeneratingParameterName; - ExpressionNormal->ParameterName = *GeneratingParameterName; - - ExpressionNormal->Texture = TextureNormal; - ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; - - // Offset node placement. - ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Set normal space. - Material->bTangentSpaceNormal = bTangentSpaceNormal; - - // Assign expression to material. - Material->Expressions.Add(ExpressionNormal); - Material->Normal.Expression = ExpressionNormal; - - // Propagate and trigger diffuse texture updates. - if (bCreatedNewTextureNormal) - FAssetRegistryModule::AssetCreated(TextureNormal); - - TextureNormal->PreEditChange(nullptr); - TextureNormal->PostEditChange(); - TextureNormal->MarkPackageDirty(); - - bExpressionCreated = true; - } - - // Cache the texture package - OutPackages.AddUnique(TextureNormalPackage); - } - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Specular texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if specular texture is available. - HAPI_ParmId ParmNameSpecularId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_0); - - if (ParmNameSpecularId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_0); - } - else - { - ParmNameSpecularId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_1); - - if (ParmNameSpecularId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_1); - } - - if (ParmNameSpecularId >= 0) - { - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameSpecularId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Specular.Expression); - - UTexture2D * TextureSpecular = nullptr; - if (ExpressionSpecular) - { - TextureSpecular = Cast< UTexture2D >(ExpressionSpecular->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Specular.Expression) - { - Material->Specular.Expression->ConditionalBeginDestroy(); - Material->Specular.Expression = nullptr; - } - } - - UPackage * TextureSpecularPackage = nullptr; - if (TextureSpecular) - TextureSpecularPackage = Cast< UPackage >(TextureSpecular->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureSpecularName; - bool bCreatedNewTextureSpecular = false; - - // Create specular texture package, if this is a new specular texture. - if (!TextureSpecularPackage) - { - TextureSpecularPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, - InPackageParams, - TextureSpecularName); - } - else if (TextureSpecular && !TextureSpecular->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureSpecularName = TextureSpecular->GetName(); - } - else - { - TextureSpecularName = FPaths::GetBaseFilename(TextureSpecularPackage->GetName(), true); - } - - // Create specular texture, if we need to create one. - if (!TextureSpecular) - bCreatedNewTextureSpecular = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing specular texture, or create new one. - TextureSpecular = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureSpecular, - ImageInfo, - TextureSpecularPackage, - TextureSpecularName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureSpecular->SetFlags(RF_Public | RF_Standalone); - - // Create specular sampling expression, if needed. - if (!ExpressionSpecular) - { - ExpressionSpecular = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionSpecular->Desc = GeneratingParameterName; - ExpressionSpecular->ParameterName = *GeneratingParameterName; - - ExpressionSpecular->Texture = TextureSpecular; - ExpressionSpecular->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionSpecular->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionSpecular->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionSpecular); - Material->Specular.Expression = ExpressionSpecular; - - bExpressionCreated = true; - - // Propagate and trigger specular texture updates. - if (bCreatedNewTextureSpecular) - FAssetRegistryModule::AssetCreated(TextureSpecular); - - TextureSpecular->PreEditChange(nullptr); - TextureSpecular->PostEditChange(); - TextureSpecular->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureSpecularPackage); - } - } - - HAPI_ParmInfo ParmInfoSpecularColor; - FHoudiniApi::ParmInfo_Init(&ParmInfoSpecularColor); - HAPI_ParmId ParmNameSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_0, ParmInfoSpecularColor); - - if (ParmNameSpecularColorId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_0); - } - else - { - ParmNameSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_1, ParmInfoSpecularColor); - - if (ParmNameSpecularColorId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_1); - } - - if (!bExpressionCreated && ParmNameSpecularColorId >= 0) - { - // Specular color is available. - - FLinearColor Color = FLinearColor::White; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmInfoSpecularColor.floatValuesIndex, ParmInfoSpecularColor.size) == HAPI_RESULT_SUCCESS) - { - if (ParmInfoSpecularColor.size == 3) - Color.A = 1.0f; - - UMaterialExpressionVectorParameter * ExpressionSpecularColor = - Cast< UMaterialExpressionVectorParameter >(Material->Specular.Expression); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionSpecularColor) - { - // Otherwise new expression is of a different type. - if (Material->Specular.Expression) - { - Material->Specular.Expression->ConditionalBeginDestroy(); - Material->Specular.Expression = nullptr; - } - - ExpressionSpecularColor = NewObject< UMaterialExpressionVectorParameter >( - Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionSpecularColor->Desc = GeneratingParameterName; - ExpressionSpecularColor->ParameterName = *GeneratingParameterName; - - ExpressionSpecularColor->DefaultValue = Color; - - // Offset node placement. - ExpressionSpecularColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionSpecularColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionSpecularColor); - Material->Specular.Expression = ExpressionSpecularColor; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Roughness texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if roughness texture is available. - HAPI_ParmId ParmNameRoughnessId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); - - if (ParmNameRoughnessId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); - } - else - { - ParmNameRoughnessId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); - - if (ParmNameRoughnessId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); - } - - if (ParmNameRoughnessId >= 0) - { - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameRoughnessId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer ) ) - { - UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Roughness.Expression); - - UTexture2D* TextureRoughness = nullptr; - if (ExpressionRoughness) - { - TextureRoughness = Cast< UTexture2D >(ExpressionRoughness->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Roughness.Expression) - { - Material->Roughness.Expression->ConditionalBeginDestroy(); - Material->Roughness.Expression = nullptr; - } - } - - UPackage * TextureRoughnessPackage = nullptr; - if (TextureRoughness) - TextureRoughnessPackage = Cast< UPackage >(TextureRoughness->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureRoughnessName; - bool bCreatedNewTextureRoughness = false; - - // Create roughness texture package, if this is a new roughness texture. - if (!TextureRoughnessPackage) - { - TextureRoughnessPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, - InPackageParams, - TextureRoughnessName); - } - else if (TextureRoughness && !TextureRoughness->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureRoughnessName = TextureRoughness->GetName(); - } - else - { - TextureRoughnessName = FPaths::GetBaseFilename(TextureRoughnessPackage->GetName(), true); - } - - // Create roughness texture, if we need to create one. - if (!TextureRoughness) - bCreatedNewTextureRoughness = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing roughness texture, or create new one. - TextureRoughness = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureRoughness, - ImageInfo, - TextureRoughnessPackage, - TextureRoughnessName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureRoughness->SetFlags(RF_Public | RF_Standalone); - - // Create roughness sampling expression, if needed. - if (!ExpressionRoughness) - ExpressionRoughness = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionRoughness->Desc = GeneratingParameterName; - ExpressionRoughness->ParameterName = *GeneratingParameterName; - - ExpressionRoughness->Texture = TextureRoughness; - ExpressionRoughness->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionRoughness->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionRoughness->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionRoughness); - Material->Roughness.Expression = ExpressionRoughness; - - bExpressionCreated = true; - - // Propagate and trigger roughness texture updates. - if (bCreatedNewTextureRoughness) - FAssetRegistryModule::AssetCreated(TextureRoughness); - - TextureRoughness->PreEditChange(nullptr); - TextureRoughness->PostEditChange(); - TextureRoughness->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureRoughnessPackage); - } - } - - HAPI_ParmInfo ParmInfoRoughnessValue; - FHoudiniApi::ParmInfo_Init(&ParmInfoRoughnessValue); - HAPI_ParmId ParmNameRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0, ParmInfoRoughnessValue); - - if (ParmNameRoughnessValueId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0); - } - else - { - ParmNameRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1, ParmInfoRoughnessValue); - - if (ParmNameRoughnessValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1); - } - - if (!bExpressionCreated && ParmNameRoughnessValueId >= 0) - { - // Roughness value is available. - - float RoughnessValue = 0.0f; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&RoughnessValue, - ParmInfoRoughnessValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - UMaterialExpressionScalarParameter * ExpressionRoughnessValue = - Cast< UMaterialExpressionScalarParameter >(Material->Roughness.Expression); - - // Clamp retrieved value. - RoughnessValue = FMath::Clamp< float >(RoughnessValue, 0.0f, 1.0f); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionRoughnessValue) - { - // Otherwise new expression is of a different type. - if (Material->Roughness.Expression) - { - Material->Roughness.Expression->ConditionalBeginDestroy(); - Material->Roughness.Expression = nullptr; - } - - ExpressionRoughnessValue = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionRoughnessValue->Desc = GeneratingParameterName; - ExpressionRoughnessValue->ParameterName = *GeneratingParameterName; - - ExpressionRoughnessValue->DefaultValue = RoughnessValue; - ExpressionRoughnessValue->SliderMin = 0.0f; - ExpressionRoughnessValue->SliderMax = 1.0f; - - // Offset node placement. - ExpressionRoughnessValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionRoughnessValue->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionRoughnessValue); - Material->Roughness.Expression = ExpressionRoughnessValue; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Metallic texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if metallic texture is available. - HAPI_ParmId ParmNameMetallicId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_METALLIC); - - if (ParmNameMetallicId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC); - } - - if (ParmNameMetallicId >= 0) - { - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameMetallicId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Metallic.Expression); - - UTexture2D * TextureMetallic = nullptr; - if (ExpressionMetallic) - { - TextureMetallic = Cast< UTexture2D >(ExpressionMetallic->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Metallic.Expression) - { - Material->Metallic.Expression->ConditionalBeginDestroy(); - Material->Metallic.Expression = nullptr; - } - } - - UPackage * TextureMetallicPackage = nullptr; - if (TextureMetallic) - TextureMetallicPackage = Cast< UPackage >(TextureMetallic->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureMetallicName; - bool bCreatedNewTextureMetallic = false; - - // Create metallic texture package, if this is a new metallic texture. - if (!TextureMetallicPackage) - { - TextureMetallicPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, - InPackageParams, - TextureMetallicName); - } - else if (TextureMetallic && !TextureMetallic->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureMetallicName = TextureMetallic->GetName(); - } - else - { - TextureMetallicName = FPaths::GetBaseFilename(TextureMetallicPackage->GetName(), true); - } - - // Create metallic texture, if we need to create one. - if (!TextureMetallic) - bCreatedNewTextureMetallic = true; - - // Get the node path to add it to the meta data - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing metallic texture, or create new one. - TextureMetallic = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureMetallic, - ImageInfo, - TextureMetallicPackage, - TextureMetallicName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureMetallic->SetFlags(RF_Public | RF_Standalone); - - // Create metallic sampling expression, if needed. - if (!ExpressionMetallic) - ExpressionMetallic = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionMetallic->Desc = GeneratingParameterName; - ExpressionMetallic->ParameterName = *GeneratingParameterName; - - ExpressionMetallic->Texture = TextureMetallic; - ExpressionMetallic->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionMetallic->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMetallic->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionMetallic); - Material->Metallic.Expression = ExpressionMetallic; - - bExpressionCreated = true; - - // Propagate and trigger metallic texture updates. - if (bCreatedNewTextureMetallic) - FAssetRegistryModule::AssetCreated(TextureMetallic); - - TextureMetallic->PreEditChange(nullptr); - TextureMetallic->PostEditChange(); - TextureMetallic->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureMetallicPackage); - } - } - - HAPI_ParmInfo ParmInfoMetallic; - FHoudiniApi::ParmInfo_Init(&ParmInfoMetallic); - HAPI_ParmId ParmNameMetallicValueIdx = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmInfoMetallic); - - if (ParmNameMetallicValueIdx >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); - - if (!bExpressionCreated && ParmNameMetallicValueIdx >= 0) - { - // Metallic value is available. - - float MetallicValue = 0.0f; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&MetallicValue, - ParmInfoMetallic.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - UMaterialExpressionScalarParameter * ExpressionMetallicValue = - Cast< UMaterialExpressionScalarParameter >(Material->Metallic.Expression); - - // Clamp retrieved value. - MetallicValue = FMath::Clamp< float >(MetallicValue, 0.0f, 1.0f); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionMetallicValue) - { - // Otherwise new expression is of a different type. - if (Material->Metallic.Expression) - { - Material->Metallic.Expression->ConditionalBeginDestroy(); - Material->Metallic.Expression = nullptr; - } - - ExpressionMetallicValue = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionMetallicValue->Desc = GeneratingParameterName; - ExpressionMetallicValue->ParameterName = *GeneratingParameterName; - - ExpressionMetallicValue->DefaultValue = MetallicValue; - ExpressionMetallicValue->SliderMin = 0.0f; - ExpressionMetallicValue->SliderMax = 1.0f; - - // Offset node placement. - ExpressionMetallicValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMetallicValue->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionMetallicValue); - Material->Metallic.Expression = ExpressionMetallicValue; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Emissive texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if emissive texture is available. - HAPI_ParmId ParmNameEmissiveId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_EMISSIVE); - - if (ParmNameEmissiveId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE); - } - - if (ParmNameEmissiveId >= 0) - { - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameEmissiveId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->EmissiveColor.Expression); - - UTexture2D * TextureEmissive = nullptr; - if (ExpressionEmissive) - { - TextureEmissive = Cast< UTexture2D >(ExpressionEmissive->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->EmissiveColor.Expression) - { - Material->EmissiveColor.Expression->ConditionalBeginDestroy(); - Material->EmissiveColor.Expression = nullptr; - } - } - - UPackage * TextureEmissivePackage = nullptr; - if (TextureEmissive) - TextureEmissivePackage = Cast< UPackage >(TextureEmissive->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureEmissiveName; - bool bCreatedNewTextureEmissive = false; - - // Create emissive texture package, if this is a new emissive texture. - if (!TextureEmissivePackage) - { - TextureEmissivePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, - InPackageParams, - TextureEmissiveName); - } - else if (TextureEmissive && !TextureEmissive->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureEmissiveName = TextureEmissive->GetName(); - } - else - { - TextureEmissiveName = FPaths::GetBaseFilename(TextureEmissivePackage->GetName(), true); - } - - // Create emissive texture, if we need to create one. - if (!TextureEmissive) - bCreatedNewTextureEmissive = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing emissive texture, or create new one. - TextureEmissive = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureEmissive, - ImageInfo, - TextureEmissivePackage, - TextureEmissiveName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureEmissive->SetFlags(RF_Public | RF_Standalone); - - // Create emissive sampling expression, if needed. - if (!ExpressionEmissive) - ExpressionEmissive = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionEmissive->Desc = GeneratingParameterName; - ExpressionEmissive->ParameterName = *GeneratingParameterName; - - ExpressionEmissive->Texture = TextureEmissive; - ExpressionEmissive->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionEmissive->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionEmissive->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionEmissive); - Material->EmissiveColor.Expression = ExpressionEmissive; - - bExpressionCreated = true; - - // Propagate and trigger metallic texture updates. - if (bCreatedNewTextureEmissive) - FAssetRegistryModule::AssetCreated(TextureEmissive); - - TextureEmissive->PreEditChange(nullptr); - TextureEmissive->PostEditChange(); - TextureEmissive->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureEmissivePackage); - } - } - - HAPI_ParmInfo ParmInfoEmissive; - FHoudiniApi::ParmInfo_Init(&ParmInfoEmissive); - HAPI_ParmId ParmNameEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0, ParmInfoEmissive); - - if (ParmNameEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0); - else - { - ParmNameEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1, ParmInfoEmissive); - - if (ParmNameEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1); - } - - if (!bExpressionCreated && ParmNameEmissiveValueId >= 0) - { - // Emissive color is available. - - FLinearColor Color = FLinearColor::White; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmInfoEmissive.floatValuesIndex, ParmInfoEmissive.size) == HAPI_RESULT_SUCCESS) - { - if (ParmInfoEmissive.size == 3) - Color.A = 1.0f; - - UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = - Cast< UMaterialExpressionConstant4Vector >(Material->EmissiveColor.Expression); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionEmissiveColor) - { - // Otherwise new expression is of a different type. - if (Material->EmissiveColor.Expression) - { - Material->EmissiveColor.Expression->ConditionalBeginDestroy(); - Material->EmissiveColor.Expression = nullptr; - } - - ExpressionEmissiveColor = NewObject< UMaterialExpressionConstant4Vector >( - Material, UMaterialExpressionConstant4Vector::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionEmissiveColor->Desc = GeneratingParameterName; - if (ExpressionEmissiveColor->CanRenameNode()) - ExpressionEmissiveColor->SetEditableName(*GeneratingParameterName); - - ExpressionEmissiveColor->Constant = Color; - - // Offset node placement. - ExpressionEmissiveColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionEmissiveColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionEmissiveColor); - Material->EmissiveColor.Expression = ExpressionEmissiveColor; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - - -bool -FHoudiniMaterialTranslator::UpdateMaterialInstanceParameter( - FHoudiniGenericAttribute MaterialParameter, - UMaterialInstanceConstant* MaterialInstance, - const TArray& InPackages) -{ - bool bParameterUpdated = false; - -#if WITH_EDITOR - if (!MaterialInstance) - return false; - - if (MaterialParameter.AttributeName.IsEmpty()) - return false; - - // The default material instance parameters needs to be handled manually as they cant be changed via generic SetParameters functions - if (MaterialParameter.AttributeName.Compare("CastShadowAsMasked", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideCastShadowAsMasked() && (MaterialInstance->GetCastShadowAsMasked() == Value)) - return false; - - MaterialInstance->SetOverrideCastShadowAsMasked(true); - MaterialInstance->SetCastShadowAsMasked(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("EmissiveBoost", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideEmissiveBoost() && (MaterialInstance->GetEmissiveBoost() == Value)) - return false; - - MaterialInstance->SetOverrideEmissiveBoost(true); - MaterialInstance->SetEmissiveBoost(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("DiffuseBoost", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideDiffuseBoost() && (MaterialInstance->GetDiffuseBoost() == Value)) - return false; - - MaterialInstance->SetOverrideDiffuseBoost(true); - MaterialInstance->SetDiffuseBoost(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("ExportResolutionScale", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideExportResolutionScale() && (MaterialInstance->GetExportResolutionScale() == Value)) - return false; - - MaterialInstance->SetOverrideExportResolutionScale(true); - MaterialInstance->SetExportResolutionScale(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("OpacityMaskClipValue", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue && (MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue = true; - MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("BlendMode", ESearchCase::IgnoreCase) == 0) - { - EBlendMode EnumValue = (EBlendMode)MaterialParameter.GetIntValue(); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - FString StringValue = MaterialParameter.GetStringValue(); - if (StringValue.Compare("Opaque", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Opaque; - else if (StringValue.Compare("Masked", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Masked; - else if (StringValue.Compare("Translucent", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Translucent; - else if (StringValue.Compare("Additive", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Additive; - else if (StringValue.Compare("Modulate", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Modulate; - else if (StringValue.StartsWith("Alpha", ESearchCase::IgnoreCase)) - EnumValue = EBlendMode::BLEND_AlphaComposite; - } - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_BlendMode && (MaterialInstance->BasePropertyOverrides.BlendMode == EnumValue)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = true; - MaterialInstance->BasePropertyOverrides.BlendMode = EnumValue; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("ShadingModel", ESearchCase::IgnoreCase) == 0) - { - EMaterialShadingModel EnumValue = (EMaterialShadingModel)MaterialParameter.GetIntValue(); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - FString StringValue = MaterialParameter.GetStringValue(); - if (StringValue.Compare("Unlit", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Unlit; - else if (StringValue.StartsWith("Default", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_DefaultLit; - else if (StringValue.Compare("Subsurface", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Subsurface; - else if (StringValue.StartsWith("Preintegrated", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_PreintegratedSkin; - else if (StringValue.StartsWith("Clear", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_ClearCoat; - else if (StringValue.Compare("SubsurfaceProfile", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_SubsurfaceProfile; - else if (StringValue.Compare("TwoSidedFoliage", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_TwoSidedFoliage; - else if (StringValue.Compare("Hair", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Hair; - else if (StringValue.Compare("Cloth", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Cloth; - else if (StringValue.Compare("Eye", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Eye; - } - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel && (MaterialInstance->BasePropertyOverrides.ShadingModel == EnumValue)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel = true; - MaterialInstance->BasePropertyOverrides.ShadingModel = EnumValue; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("TwoSided", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_TwoSided && (MaterialInstance->BasePropertyOverrides.TwoSided == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_TwoSided = true; - MaterialInstance->BasePropertyOverrides.TwoSided = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("DitheredLODTransition", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition && (MaterialInstance->BasePropertyOverrides.DitheredLODTransition == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition = true; - MaterialInstance->BasePropertyOverrides.DitheredLODTransition = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("PhysMaterial", ESearchCase::IgnoreCase) == 0) - { - // Try to load a Material corresponding to the parameter value - FString ParamValue = MaterialParameter.GetStringValue(); - UPhysicalMaterial* FoundPhysMaterial = Cast< UPhysicalMaterial >( - StaticLoadObject(UPhysicalMaterial::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); - - // Update the parameter value if necessary - if (!FoundPhysMaterial || (MaterialInstance->PhysMaterial == FoundPhysMaterial)) - return false; - - MaterialInstance->PhysMaterial = FoundPhysMaterial; - bParameterUpdated = true; - } - - if (bParameterUpdated) - return true; - - // Handling custom parameters - FName CurrentMatParamName = FName(*MaterialParameter.AttributeName); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - // String attributes are used for textures parameters - // We need to find the texture corresponding to the param - UTexture* FoundTexture = nullptr; - FString ParamValue = MaterialParameter.GetStringValue(); - - // Texture can either be already existing texture assets in UE4, or a newly generated textures by this asset - // Try to find the texture corresponding to the param value in the existing assets first. - FoundTexture = Cast( - StaticLoadObject(UTexture::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); - - if (!FoundTexture) - { - // We couldn't find a texture corresponding to the parameter in the existing UE4 assets - // Try to find the corresponding texture in the cooked temporary package we just generated - FoundTexture = FHoudiniMaterialTranslator::FindGeneratedTexture(ParamValue, InPackages); - } - - // Do not update if unnecessary - if (FoundTexture) - { - // Do not update if unnecessary - UTexture* OldTexture = nullptr; - bool FoundOldParam = MaterialInstance->GetTextureParameterValue(CurrentMatParamName, OldTexture); - if (FoundOldParam && (OldTexture == FoundTexture)) - return false; - - MaterialInstance->SetTextureParameterValueEditorOnly(CurrentMatParamName, FoundTexture); - bParameterUpdated = true; - } - } - else if (MaterialParameter.AttributeTupleSize == 1) - { - // Single attributes are either for scalar parameters or static switches - float OldValue; - bool FoundOldScalarParam = MaterialInstance->GetScalarParameterValue(CurrentMatParamName, OldValue); - if (FoundOldScalarParam) - { - // The material parameter is a scalar - float NewValue = (float)MaterialParameter.GetDoubleValue(); - - // Do not update if unnecessary - if (OldValue == NewValue) - return false; - - MaterialInstance->SetScalarParameterValueEditorOnly(CurrentMatParamName, NewValue); - bParameterUpdated = true; - } - else - { - // See if the underlying parameter is a static switch - bool NewBoolValue = MaterialParameter.GetBoolValue(); - - // We need to iterate over the material's static parameter set - FStaticParameterSet StaticParameters; - MaterialInstance->GetStaticParameterValues(StaticParameters); - - for (int32 SwitchParameterIdx = 0; SwitchParameterIdx < StaticParameters.StaticSwitchParameters.Num(); ++SwitchParameterIdx) - { - FStaticSwitchParameter& SwitchParameter = StaticParameters.StaticSwitchParameters[SwitchParameterIdx]; - if (SwitchParameter.ParameterInfo.Name != CurrentMatParamName) - continue; - - if (SwitchParameter.Value == NewBoolValue) - return false; - - SwitchParameter.Value = NewBoolValue; - SwitchParameter.bOverride = true; - - MaterialInstance->UpdateStaticPermutation(StaticParameters); - bParameterUpdated = true; - break; - } - } - } - else - { - // Tuple attributes are for vector parameters - FLinearColor NewLinearColor; - // if the attribute is stored in an int, we'll have to convert a color to a linear color - if (MaterialParameter.AttributeType == EAttribStorageType::INT || MaterialParameter.AttributeType == EAttribStorageType::INT64) - { - FColor IntColor; - IntColor.R = (int8)MaterialParameter.GetIntValue(0); - IntColor.G = (int8)MaterialParameter.GetIntValue(1); - IntColor.B = (int8)MaterialParameter.GetIntValue(2); - if (MaterialParameter.AttributeTupleSize >= 4) - IntColor.A = (int8)MaterialParameter.GetIntValue(3); - else - IntColor.A = 1; - - NewLinearColor = FLinearColor(IntColor); - } - else - { - NewLinearColor.R = (float)MaterialParameter.GetDoubleValue(0); - NewLinearColor.G = (float)MaterialParameter.GetDoubleValue(1); - NewLinearColor.B = (float)MaterialParameter.GetDoubleValue(2); - if (MaterialParameter.AttributeTupleSize >= 4) - NewLinearColor.A = (float)MaterialParameter.GetDoubleValue(3); - } - - // Do not update if unnecessary - FLinearColor OldValue; - bool FoundOldParam = MaterialInstance->GetVectorParameterValue(CurrentMatParamName, OldValue); - if (FoundOldParam && (OldValue == NewLinearColor)) - return false; - - MaterialInstance->SetVectorParameterValueEditorOnly(CurrentMatParamName, NewLinearColor); - bParameterUpdated = true; - } -#endif - - return bParameterUpdated; -} - - -UTexture* -FHoudiniMaterialTranslator::FindGeneratedTexture(const FString& TextureString, const TArray& InPackages) -{ - if (TextureString.IsEmpty()) - return nullptr; - - // Try to find the corresponding texture in the cooked temporary package generated by an HDA - UTexture* FoundTexture = nullptr; - for(const auto& CurrentPackage : InPackages) - { - // Iterate through the cooked packages - if (!CurrentPackage || CurrentPackage->IsPendingKill()) - continue; - - // First, check if the package contains a texture - FString CurrentPackageName = CurrentPackage->GetName(); - UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); - if (!PackageTexture) - continue; - - // Then check if the package's metadata match what we're looking for - // Make sure this texture was generated by Houdini Engine - UMetaData* MetaData = CurrentPackage->GetMetaData(); - if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - continue; - - // Get the texture type from the meta data - // Texture type store has meta data will be C_A, N, S, R etc.. - const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); - if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // Convert the texture type to a "friendly" version - // C_A to diffuse, N to Normal, S to Specular etc... - FString TextureTypeFriendlyString = TextureTypeString; - FString TextureTypeFriendlyAlternateString = TEXT(""); - if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) - { - TextureTypeFriendlyString = TEXT("diffuse"); - TextureTypeFriendlyAlternateString = TEXT("basecolor"); - } - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("normal"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("emissive"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("specular"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("roughness"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("metallic"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("opacity"); - - // See if we have a match between the texture string and the friendly name - if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) - { - FoundTexture = PackageTexture; - break; - } - - // Get the node path from the meta data - const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); - if (NodePath.IsEmpty()) - continue; - - // See if we have a match with the path and texture type - FString PathAndType = NodePath + TEXT("/") + TextureTypeString; - if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // See if we have a match with the friendly path and texture type - FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; - if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // Try the alternate friendly string - if (!TextureTypeFriendlyAlternateString.IsEmpty()) - { - PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; - if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - } - } - - return FoundTexture; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniMaterialTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPackageParams.h" + +#include "Materials/Material.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceConstant.h" +#include "PhysicalMaterials/PhysicalMaterial.h" + +#include "Materials/MaterialExpressionTextureSample.h" +#include "Materials/MaterialExpressionTextureCoordinate.h" +#include "Materials/MaterialExpressionConstant4Vector.h" +#include "Materials/MaterialExpressionConstant.h" +#include "Materials/MaterialExpressionMultiply.h" +#include "Materials/MaterialExpressionVertexColor.h" +#include "Materials/MaterialExpressionTextureSampleParameter2D.h" +#include "Materials/MaterialExpressionVectorParameter.h" +#include "Materials/MaterialExpressionScalarParameter.h" +#include "ImageUtils.h" +#include "PackageTools.h" +#include "AssetRegistryModule.h" +#include "UObject/MetaData.h" + +#if WITH_EDITOR + #include "Factories/MaterialFactoryNew.h" + #include "Factories/MaterialInstanceConstantFactoryNew.h" +#endif + +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeX = -400; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeY = -150; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepX = 220; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepY = 220; + +bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( + const HAPI_NodeId& InAssetId, + const FHoudiniPackageParams& InPackageParams, + const TArray& InUniqueMaterialIds, + const TArray& InUniqueMaterialInfos, + const TMap& InMaterials, + TMap& OutMaterials, + TArray& OutPackages, + const bool& bForceRecookAll, + bool bInTreatExistingMaterialsAsUpToDate) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMaterialTranslator::CreateHoudiniMaterials")); + + if (InUniqueMaterialIds.Num() <= 0) + return false; + + if (InUniqueMaterialInfos.Num() != InUniqueMaterialIds.Num()) + return false; + + // Empty returned materials. + OutMaterials.Empty(); + + // Update context for generated materials (will trigger when object goes out of scope). + FMaterialUpdateContext MaterialUpdateContext; + + // Default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + OutMaterials.Add(HAPI_UNREAL_DEFAULT_MATERIAL_NAME, DefaultMaterial); + + // Factory to create materials. + UMaterialFactoryNew * MaterialFactory = NewObject(); + MaterialFactory->AddToRoot(); + + for (int32 MaterialIdx = 0; MaterialIdx < InUniqueMaterialIds.Num(); MaterialIdx++) + { + HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; + + HAPI_MaterialInfo MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; + if (!MaterialInfo.exists) + { + // The material does not exist, + // we will use the default Houdini material in this case. + continue; + } + + // Get the material node's node information. + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &NodeInfo)) + { + continue; + } + + FString MaterialName = TEXT(""); + if (!FHoudiniEngineString::ToFString(NodeInfo.nameSH, MaterialName)) + { + // shouldnt happen, give a generic name + HOUDINI_LOG_WARNING(TEXT("Failed to retrieve material name!")); + MaterialName = TEXT("Material_") + FString::FromInt(MaterialInfo.nodeId); + } + + FString MaterialPathName = TEXT(""); + if (!FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, MaterialInfo, MaterialPathName)) + continue; + + // TODO: GetAssetName! + FString AssetName = TEXT("HoudiniAsset"); + + bool bCreatedNewMaterial = false; + + // TODO: Check existing material map!! + //UMaterial * Material = HoudiniCookParams.HoudiniCookManager ? Cast< UMaterial >(HoudiniCookParams.HoudiniCookManager->GetAssignmentMaterial(MaterialShopName)) : nullptr; + UMaterial * Material = nullptr; + UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); + if (FoundMaterial) + { + Material = Cast(*FoundMaterial); + } + + if (Material && !Material->IsPendingKill()) + { + // If cached material exists and has not changed, we can reuse it. + if ((bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll) + { + // We found cached material, we can reuse it. + OutMaterials.Add(MaterialPathName, Material); + continue; + } + } + else + { + // Previous Material was not found, we need to create a new one. + // TODO: Handle this! + //EObjectFlags ObjFlags = (HoudiniCookParams.MaterialAndTextureBakeMode == EBakeMode::Intermediate) ? RF_Transactional : RF_Public | RF_Standalone; + EObjectFlags ObjFlags = RF_Public | RF_Standalone; + + // Create material package and get material name. + FString MaterialPackageName; + UPackage * MaterialPackage = FHoudiniMaterialTranslator::CreatePackageForMaterial( + MaterialInfo.nodeId, MaterialName, InPackageParams, MaterialPackageName); + + Material = (UMaterial *)MaterialFactory->FactoryCreateNew( + UMaterial::StaticClass(), MaterialPackage, *MaterialPackageName, ObjFlags, NULL, GWarn); + + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialName); + + bCreatedNewMaterial = true; + } + + if (!Material || Material->IsPendingKill()) + continue; + + // Get the package and add it to our list + UPackage* Package = Material->GetOutermost(); + OutPackages.AddUnique(Package); + + /* + // TODO: This should be handled in the mesh/instance translator + // If this is an instancer material, enable the instancing flag. + if (UniqueInstancerMaterialIds.Contains(MaterialId)) + Material->bUsedWithInstancedStaticMeshes = true; + */ + + // Reset material expressions. + Material->Expressions.Empty(); + + // Generate various components for this material. + bool bMaterialComponentCreated = false; + int32 MaterialNodeY = FHoudiniMaterialTranslator::MaterialExpressionNodeY; + + // By default we mark material as opaque. Some of component creators can change this. + Material->BlendMode = BLEND_Opaque; + + // Extract diffuse plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract opacity plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract opacity mask plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract normal plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentNormal( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract specular plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract roughness plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract metallic plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract emissive plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Set other material properties. + Material->TwoSided = true; + Material->SetShadingModel(MSM_DefaultLit); + + // Schedule this material for update. + MaterialUpdateContext.AddMaterial(Material); + + // Cache material. + OutMaterials.Add(MaterialPathName, Material); + + // Propagate and trigger material updates. + if (bCreatedNewMaterial) + FAssetRegistryModule::AssetCreated(Material); + + Material->PreEditChange(nullptr); + Material->PostEditChange(); + Material->MarkPackageDirty(); + } + + MaterialFactory->RemoveFromRoot(); + + return true; +} + +// +bool +FHoudiniMaterialTranslator::CreateMaterialInstances( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& UniqueMaterialInstanceOverrides, + const TArray& InPackages, + const TMap& InMaterials, + TMap& OutMaterials, + const bool& bForceRecookAll) +{ + // Check the node ID is valid + if (InHGPO.AssetId < 0) + return false; + + // No material instance attributes + if (UniqueMaterialInstanceOverrides.Num() <= 0) + return false; + + // TODO: Improve! + // Get the material name from the material_instance attribute + // Since the material instance attribute can be set per primitive, it's going to be very difficult to know + // exactly where to look for the nth material instance. In order for the material slot to be created, + // we used the fact that the material instance attribute had to be different + // This is pretty hacky and we should probably require an extra material_instance_index attribute instead. + // as we can only create one instance of the same material, and cant get two slots for the same "source" material. + int32 MaterialIndex = 0; + for (TMap::TConstIterator Iter(UniqueMaterialInstanceOverrides); Iter; ++Iter) + { + FString CurrentSourceMaterial = Iter->Key; + if (CurrentSourceMaterial.IsEmpty()) + continue; + + // Try to find the material we want to create an instance of + UMaterialInterface* CurrentSourceMaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentSourceMaterial, nullptr, LOAD_NoWarn, nullptr)); + + if (!CurrentSourceMaterialInterface || CurrentSourceMaterialInterface->IsPendingKill()) + { + // Couldn't find the source material + HOUDINI_LOG_WARNING(TEXT("Couldn't find the source material %s to create a material instance."), *CurrentSourceMaterial); + continue; + } + + // Create/Retrieve the package for the MI + FString MaterialInstanceName; + FString MaterialInstanceNamePrefix = UPackageTools::SanitizePackageName( + CurrentSourceMaterialInterface->GetName() + TEXT("_instance_") + FString::FromInt(MaterialIndex)); + + // Increase the material index + MaterialIndex++; + + // See if we can find an existing package for that instance + UPackage * MaterialInstancePackage = nullptr; + UMaterialInterface * const * FoundMatPtr = InMaterials.Find(MaterialInstanceNamePrefix); + if (FoundMatPtr && *FoundMatPtr) + { + // We found an already existing MI, get its package + MaterialInstancePackage = Cast((*FoundMatPtr)->GetOuter()); + } + + if (MaterialInstancePackage) + { + MaterialInstanceName = MaterialInstancePackage->GetName(); + } + else + { + // We couldnt find the corresponding M_I package, so create a new one + MaterialInstancePackage = CreatePackageForMaterial(InHGPO.AssetId, MaterialInstanceNamePrefix, InPackageParams, MaterialInstanceName); + } + + // Couldn't create a package for that Material Instance + if (!MaterialInstancePackage) + continue; + + bool bNewMaterialCreated = false; + UMaterialInstanceConstant* NewMaterialInstance = LoadObject(MaterialInstancePackage, *MaterialInstanceName, nullptr, LOAD_None, nullptr); + if (!NewMaterialInstance) + { + // Factory to create materials. + UMaterialInstanceConstantFactoryNew* MaterialInstanceFactory = NewObject< UMaterialInstanceConstantFactoryNew >(); + if (!MaterialInstanceFactory) + continue; + + // Create the new material instance + MaterialInstanceFactory->AddToRoot(); + MaterialInstanceFactory->InitialParent = CurrentSourceMaterialInterface; + NewMaterialInstance = (UMaterialInstanceConstant*)MaterialInstanceFactory->FactoryCreateNew( + UMaterialInstanceConstant::StaticClass(), MaterialInstancePackage, FName(*MaterialInstanceName), + RF_Public | RF_Standalone, NULL, GWarn); + + if (NewMaterialInstance) + bNewMaterialCreated = true; + + MaterialInstanceFactory->RemoveFromRoot(); + } + + if (!NewMaterialInstance) + { + HOUDINI_LOG_WARNING(TEXT("Couldn't access the material instance for %s"), *CurrentSourceMaterial); + continue; + } + + // Update context for generated materials (will trigger when the object goes out of scope). + FMaterialUpdateContext MaterialUpdateContext; + + bool bModifiedMaterialParameters = false; + // See if we need to override some of the material instance's parameters + TArray AllMatParams; + // Get the detail material parameters + int32 ParamCount = FHoudiniEngineUtils::GetGenericAttributeList( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, + AllMatParams, HAPI_ATTROWNER_DETAIL, -1); + + // Then the primitive material parameters + int32 MaterialIndexToAttributeIndex = Iter->Value; + ParamCount += FHoudiniEngineUtils::GetGenericAttributeList( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, + AllMatParams, HAPI_ATTROWNER_PRIM, MaterialIndexToAttributeIndex); + + for (int32 ParamIdx = 0; ParamIdx < AllMatParams.Num(); ParamIdx++) + { + // Try to update the material instance parameter corresponding to the attribute + if (UpdateMaterialInstanceParameter(AllMatParams[ParamIdx], NewMaterialInstance, InPackages)) + bModifiedMaterialParameters = true; + } + + // Schedule this material for update if needed. + if (bNewMaterialCreated || bModifiedMaterialParameters) + MaterialUpdateContext.AddMaterialInstance(NewMaterialInstance); + + if (bNewMaterialCreated) + { + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialInstanceName); + // Notify registry that we have created a new material. + FAssetRegistryModule::AssetCreated(NewMaterialInstance); + } + + if (bNewMaterialCreated || bModifiedMaterialParameters) + { + // Dirty the material + NewMaterialInstance->MarkPackageDirty(); + + // Update the material instance + NewMaterialInstance->InitStaticPermutation(); + NewMaterialInstance->PreEditChange(nullptr); + NewMaterialInstance->PostEditChange(); + /* + // Automatically save the package to avoid further issue + MaterialInstancePackage->SetDirtyFlag( true ); + MaterialInstancePackage->FullyLoad(); + UPackage::SavePackage( + MaterialInstancePackage, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, + *FPackageName::LongPackageNameToFilename( MaterialInstancePackage->GetName(), FPackageName::GetAssetPackageExtension() ) ); + */ + } + + // Add the created material to the output assignement map + // Use the "source" material name as we want the instance to replace it + OutMaterials.Add(CurrentSourceMaterial, NewMaterialInstance); + } + + return true; +} + +bool +FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath) +{ + HAPI_MaterialInfo MaterialInfo; + FHoudiniApi::MaterialInfo_Init(&MaterialInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), InMaterialNodeId, + &MaterialInfo), false); + + return GetMaterialRelativePath(InAssetId, MaterialInfo, OutRelativePath); +} +bool +FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialInfo, FString& OutRelativePath) +{ + if (InAssetId < 0 || !InMaterialInfo.exists) + return false; + + // We want to get the asset node path so we can remove it from the material name + FString AssetNodeName = TEXT(""); + { + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); + + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo), false); + + FHoudiniEngineString::ToFString(AssetNodeInfo.internalNodePathSH, AssetNodeName); + } + + // Get the material name from the info + FString MaterialNodeName = TEXT(""); + { + HAPI_NodeInfo MaterialNodeInfo; + FHoudiniApi::NodeInfo_Init(&MaterialNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &MaterialNodeInfo), false); + + FHoudiniEngineString::ToFString(MaterialNodeInfo.internalNodePathSH, MaterialNodeName); + } + + if (AssetNodeName.Len() > 0 && MaterialNodeName.Len() > 0) + { + // Remove AssetNodeName part from MaterialNodeName. Extra position is for separator. + OutRelativePath = MaterialNodeName.Mid(AssetNodeName.Len() + 1); + return true; + } + + return false; +} + + +UPackage* +FHoudiniMaterialTranslator::CreatePackageForMaterial( + const HAPI_NodeId& InMaterialNodeId, + const FString& InMaterialName, + const FHoudiniPackageParams& InPackageParams, + FString& OutMaterialName) +{ + FString MaterialDescriptor = TEXT("_material_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InMaterialName; + + FHoudiniPackageParams MyPackageParams = InPackageParams; + if (!MyPackageParams.ObjectName.IsEmpty()) + { + MyPackageParams.ObjectName += MaterialDescriptor; + } + else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) + { + MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + MaterialDescriptor; + } + else + { + MyPackageParams.ObjectName = MaterialDescriptor; + } + MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); + + return MyPackageParams.CreatePackageForObject(OutMaterialName); +} + + +UPackage* +FHoudiniMaterialTranslator::CreatePackageForTexture( + const HAPI_NodeId& InMaterialNodeId, + const FString& InTextureType, + const FHoudiniPackageParams& InPackageParams, + FString& OutTextureName) +{ + FString TextureInfoDescriptor = TEXT("_texture_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InTextureType; + FHoudiniPackageParams MyPackageParams = InPackageParams; + if (!MyPackageParams.ObjectName.IsEmpty()) + { + MyPackageParams.ObjectName += TextureInfoDescriptor; + } + else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) + { + MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + TextureInfoDescriptor; + } + else + { + MyPackageParams.ObjectName = TextureInfoDescriptor; + } + MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); + + return MyPackageParams.CreatePackageForObject(OutTextureName); +} + + +UTexture2D * +FHoudiniMaterialTranslator::CreateUnrealTexture( + UTexture2D* ExistingTexture, + const HAPI_ImageInfo& ImageInfo, + UPackage* Package, + const FString& TextureName, + const TArray& ImageBuffer, + const FCreateTexture2DParameters& TextureParameters, + const TextureGroup& LODGroup, + const FString& TextureType, + const FString& NodePath) +{ + if (!Package || Package->IsPendingKill()) + return nullptr; + + UTexture2D * Texture = nullptr; + if (ExistingTexture) + { + Texture = ExistingTexture; + } + else + { + // Create new texture object. + Texture = NewObject< UTexture2D >( + Package, UTexture2D::StaticClass(), *TextureName, + RF_Transactional); + + // Assign texture group. + Texture->LODGroup = LODGroup; + } + + // Add/Update meta information to package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *TextureName); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_NODE_PATH, *NodePath); + + // Initialize texture source. + Texture->Source.Init(ImageInfo.xRes, ImageInfo.yRes, 1, 1, TSF_BGRA8); + + // Lock the texture. + uint8 * MipData = Texture->Source.LockMip(0); + + // Create base map. + uint8* DestPtr = nullptr; + uint32 SrcWidth = ImageInfo.xRes; + uint32 SrcHeight = ImageInfo.yRes; + const char * SrcData = &ImageBuffer[0]; + + for (uint32 y = 0; y < SrcHeight; y++) + { + DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; + + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth * 4 + x * 4; + + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 2); // B + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 1); // G + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 0); // R + + if (TextureParameters.bUseAlpha) + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 3); // A + else + *DestPtr++ = 0xFF; + } + } + + bool bHasAlphaValue = false; + if (TextureParameters.bUseAlpha) + { + // See if there is an actual alpha value in the texture or if we can ignore the texture alpha + for (uint32 y = 0; y < SrcHeight; y++) + { + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth * 4 + x * 4; + if (*(uint8*)(SrcData + DataOffset + 3) != 0xFF) + { + bHasAlphaValue = true; + break; + } + } + + if (bHasAlphaValue) + break; + } + } + + // Unlock the texture. + Texture->Source.UnlockMip(0); + + // Texture creation parameters. + Texture->SRGB = TextureParameters.bSRGB; + Texture->CompressionSettings = TextureParameters.CompressionSettings; + Texture->CompressionNoAlpha = !bHasAlphaValue; + Texture->DeferCompression = TextureParameters.bDeferCompression; + + // Set the Source Guid/Hash if specified. + /* + if ( TextureParameters.SourceGuidHash.IsValid() ) + { + Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); + } + */ + + Texture->PostEditChange(); + + return Texture; +} + + + +bool +FHoudiniMaterialTranslator::HapiExtractImage( + const HAPI_ParmId& NodeParmId, + const HAPI_MaterialInfo& MaterialInfo, + const char * PlaneType, + const HAPI_ImageDataFormat& ImageDataFormat, + HAPI_ImagePacking ImagePacking, + bool bRenderToImage, + TArray& OutImageBuffer ) +{ + if (bRenderToImage) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, NodeParmId), false); + } + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo), false); + + ImageInfo.dataFormat = ImageDataFormat; + ImageInfo.interleaved = true; + ImageInfo.packing = ImagePacking; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo), false); + + int32 ImageBufferSize = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ExtractImageToMemory( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, HAPI_RAW_FORMAT_NAME, + PlaneType, &ImageBufferSize), false); + + if (ImageBufferSize <= 0) + return false; + + OutImageBuffer.SetNumUninitialized(ImageBufferSize); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImageMemoryBuffer( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &OutImageBuffer[0], + ImageBufferSize), false); + + return true; +} + +bool +FHoudiniMaterialTranslator::HapiGetImagePlanes( + const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes) +{ + OutImagePlanes.Empty(); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, NodeParmId), false); + + int32 ImagePlaneCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlaneCount( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImagePlaneCount), false); + + if (ImagePlaneCount <= 0) + return true; + + TArray ImagePlaneStringHandles; + ImagePlaneStringHandles.SetNumZeroed(ImagePlaneCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlanes( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImagePlaneStringHandles[0], ImagePlaneCount), false); + + FHoudiniEngineString::SHArrayToFStringArray(ImagePlaneStringHandles, OutImagePlanes); + + return true; +} + + +UMaterialExpression * +FHoudiniMaterialTranslator::MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass) +{ + if (!Expression) + return nullptr; + +#if WITH_EDITOR + if (ExpressionClass == Expression->GetClass()) + return Expression; + + // If this is a channel multiply expression, we can recurse. + UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast< UMaterialExpressionMultiply >(Expression); + if (MaterialExpressionMultiply) + { + { + UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->A.Expression; + if (MaterialExpression) + { + if (MaterialExpression->GetClass() == ExpressionClass) + return MaterialExpression; + + MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( + Cast(MaterialExpression), ExpressionClass); + + if (MaterialExpression) + return MaterialExpression; + } + } + + { + UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->B.Expression; + if (MaterialExpression) + { + if (MaterialExpression->GetClass() == ExpressionClass) + return MaterialExpression; + + MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( + Cast(MaterialExpression), ExpressionClass); + + if (MaterialExpression) + return MaterialExpression; + } + } + } +#endif + + return nullptr; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Names of generating Houdini parameters. + FString GeneratingParameterNameDiffuseTexture = TEXT(""); + FString GeneratingParameterNameUniformColor = TEXT(""); + FString GeneratingParameterNameVertexColor = TEXT(HAPI_UNREAL_ATTRIB_COLOR); + + // Diffuse texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Default; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // Attempt to look up previously created expressions. + UMaterialExpression * MaterialExpression = Material->BaseColor.Expression; + + // Locate sampling expression. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureSample = + Cast< UMaterialExpressionTextureSampleParameter2D >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // If texture sampling expression does exist, attempt to look up corresponding texture. + UTexture2D * TextureDiffuse = nullptr; + if (ExpressionTextureSample && !ExpressionTextureSample->IsPendingKill()) + TextureDiffuse = Cast< UTexture2D >(ExpressionTextureSample->Texture); + + // Locate uniform color expression. + UMaterialExpressionVectorParameter * ExpressionConstant4Vector = + Cast< UMaterialExpressionVectorParameter >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionVectorParameter::StaticClass())); + + // If uniform color expression does not exist, create it. + if (!ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill()) + { + ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( + Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); + ExpressionConstant4Vector->DefaultValue = FLinearColor::White; + } + + // Add expression. + Material->Expressions.Add(ExpressionConstant4Vector); + + // Locate vertex color expression. + UMaterialExpressionVertexColor * ExpressionVertexColor = + Cast< UMaterialExpressionVertexColor >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionVertexColor::StaticClass())); + + // If vertex color expression does not exist, create it. + if (!ExpressionVertexColor || ExpressionVertexColor->IsPendingKill()) + { + ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( + Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag); + ExpressionVertexColor->Desc = GeneratingParameterNameVertexColor; + } + + // Add expression. + Material->Expressions.Add(ExpressionVertexColor); + + // Material should have at least one multiply expression. + UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast(MaterialExpression); + if (!MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill()) + MaterialExpressionMultiply = NewObject( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + // Add expression. + Material->Expressions.Add(MaterialExpressionMultiply); + + // See if primary multiplication has secondary multiplication as A input. + UMaterialExpressionMultiply * MaterialExpressionMultiplySecondary = nullptr; + if (MaterialExpressionMultiply->A.Expression) + MaterialExpressionMultiplySecondary = + Cast(MaterialExpressionMultiply->A.Expression); + + // See if a diffuse texture is available. + HAPI_ParmId ParmDiffuseTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + + if (ParmDiffuseTextureId >= 0) + { + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + } + else + { + ParmDiffuseTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); + + if (ParmDiffuseTextureId >= 0) + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); + } + + // See if uniform color is available. + HAPI_ParmInfo ParmInfoDiffuseColor; + FHoudiniApi::ParmInfo_Init(&ParmInfoDiffuseColor); + HAPI_ParmId ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0, ParmInfoDiffuseColor); + + if (ParmDiffuseColorId >= 0) + { + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0); + } + else + { + ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1, ParmInfoDiffuseColor); + + if (ParmDiffuseColorId >= 0) + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1); + } + + // If we have diffuse texture parameter. + if (ParmDiffuseTextureId >= 0) + { + TArray< char > ImageBuffer; + + // Get image planes of diffuse map. + TArray< FString > DiffuseImagePlanes; + bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( + ParmDiffuseTextureId, InMaterialInfo, DiffuseImagePlanes); + + HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; + const char * PlaneType = ""; + + if (bFoundImagePlanes && DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))) + { + if (DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA))) + { + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + + // Material does use alpha. + CreateTexture2DParameters.bUseAlpha = true; + } + else + { + // We still need to have the Alpha plane, just not the CreateTexture2DParameters + // alpha option. This is because all texture data from Houdini Engine contains + // the alpha plane by default. + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + } + } + else + { + bFoundImagePlanes = false; + } + + // Retrieve color plane. + if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( + ParmDiffuseTextureId, InMaterialInfo, PlaneType, + HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) + { + UPackage * TextureDiffusePackage = nullptr; + if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + TextureDiffusePackage = Cast(TextureDiffuse->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureDiffuseName; + bool bCreatedNewTextureDiffuse = false; + + // Create diffuse texture package, if this is a new diffuse texture. + if (!TextureDiffusePackage) + { + TextureDiffusePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, + InPackageParams, + TextureDiffuseName); + } + else if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureDiffuseName = TextureDiffuse->GetName(); + } + else + { + TextureDiffuseName = FPaths::GetBaseFilename(TextureDiffusePackage->GetName(), true); + } + + // Create diffuse texture, if we need to create one. + if (!TextureDiffuse || TextureDiffuse->IsPendingKill()) + bCreatedNewTextureDiffuse = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing diffuse texture, or create new one. + TextureDiffuse = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureDiffuse, + ImageInfo, + TextureDiffusePackage, + TextureDiffuseName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureDiffuse->SetFlags(RF_Public | RF_Standalone); + + // Create diffuse sampling expression, if needed. + if (!ExpressionTextureSample) + { + ExpressionTextureSample = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionTextureSample->Desc = GeneratingParameterNameDiffuseTexture; + ExpressionTextureSample->ParameterName = *GeneratingParameterNameDiffuseTexture; + ExpressionTextureSample->Texture = TextureDiffuse; + ExpressionTextureSample->SamplerType = SAMPLERTYPE_Color; + + // Add expression. + Material->Expressions.Add(ExpressionTextureSample); + + // Propagate and trigger diffuse texture updates. + if (bCreatedNewTextureDiffuse) + FAssetRegistryModule::AssetCreated(TextureDiffuse); + + TextureDiffuse->PreEditChange(nullptr); + TextureDiffuse->PostEditChange(); + TextureDiffuse->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureDiffusePackage); + } + } + + // If we have uniform color parameter. + if (ParmDiffuseColorId >= 0) + { + FLinearColor Color = FLinearColor::White; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&Color.R, + ParmInfoDiffuseColor.floatValuesIndex, ParmInfoDiffuseColor.size) == HAPI_RESULT_SUCCESS) + { + if (ParmInfoDiffuseColor.size == 3) + Color.A = 1.0f; + + // Record generating parameter. + ExpressionConstant4Vector->Desc = GeneratingParameterNameUniformColor; + ExpressionConstant4Vector->ParameterName = *GeneratingParameterNameUniformColor; + ExpressionConstant4Vector->DefaultValue = Color; + } + } + + // If we have have texture sample expression present, we need a secondary multiplication expression. + if (ExpressionTextureSample) + { + if (!MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary = NewObject< UMaterialExpressionMultiply >( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + // Add expression. + Material->Expressions.Add(MaterialExpressionMultiplySecondary); + } + } + else + { + // If secondary multiplication exists, but we have no sampling, we can free it. + if (MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary->A.Expression = nullptr; + MaterialExpressionMultiplySecondary->B.Expression = nullptr; + MaterialExpressionMultiplySecondary->ConditionalBeginDestroy(); + } + } + + float SecondaryExpressionScale = 1.0f; + if (MaterialExpressionMultiplySecondary) + SecondaryExpressionScale = 1.5f; + + // Create multiplication expression which has uniform color and vertex color. + MaterialExpressionMultiply->A.Expression = ExpressionConstant4Vector; + MaterialExpressionMultiply->B.Expression = ExpressionVertexColor; + + ExpressionConstant4Vector->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionConstant4Vector->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + ExpressionVertexColor->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionVertexColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiply->MaterialExpressionEditorY = + (ExpressionVertexColor->MaterialExpressionEditorY - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; + + // Hook up secondary multiplication expression to first one. + if (MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; + MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; + + ExpressionTextureSample->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiplySecondary->MaterialExpressionEditorY = + MaterialExpressionMultiply->MaterialExpressionEditorY + FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression. + Material->BaseColor.Expression = MaterialExpressionMultiplySecondary; + } + else + { + // Assign expression. + Material->BaseColor.Expression = MaterialExpressionMultiply; + + MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiply->MaterialExpressionEditorY = + (ExpressionVertexColor->MaterialExpressionEditorY - + ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; + } + + return true; +} + + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + // Name of generating Houdini parameters. + FString GeneratingParameterNameTexture = TEXT(""); + + UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Opacity expressions. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; + UTexture2D * TextureOpacity = nullptr; + + // Opacity texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // See if opacity texture is available. + HAPI_ParmId ParmOpacityTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_0); + + if (ParmOpacityTextureId >= 0) + { + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_0); + } + else + { + ParmOpacityTextureId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_1); + + if (ParmOpacityTextureId >= 0) + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_1); + } + + // If we have opacity texture parameter. + if (ParmOpacityTextureId >= 0) + { + TArray< char > ImageBuffer; + + // Get image planes of opacity map. + TArray< FString > OpacityImagePlanes; + bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( + ParmOpacityTextureId, InMaterialInfo, OpacityImagePlanes); + + HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; + const char * PlaneType = ""; + + bool bColorAlphaFound = (OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA)) && OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))); + + if (bFoundImagePlanes && bColorAlphaFound) + { + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + CreateTexture2DParameters.bUseAlpha = true; + } + else + { + bFoundImagePlanes = false; + } + + if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( + ParmOpacityTextureId, InMaterialInfo, PlaneType, + HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) + { + // Locate sampling expression. + ExpressionTextureOpacitySample = Cast< UMaterialExpressionTextureSampleParameter2D >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // Locate opacity texture, if valid. + if (ExpressionTextureOpacitySample) + TextureOpacity = Cast< UTexture2D >(ExpressionTextureOpacitySample->Texture); + + UPackage * TextureOpacityPackage = nullptr; + if (TextureOpacity) + TextureOpacityPackage = Cast< UPackage >(TextureOpacity->GetOuter()); + + HAPI_ImageInfo ImageInfo; + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureOpacityName; + bool bCreatedNewTextureOpacity = false; + + // Create opacity texture package, if this is a new opacity texture. + if (!TextureOpacityPackage) + { + TextureOpacityPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, + InPackageParams, + TextureOpacityName); + } + else if (TextureOpacity && !TextureOpacity->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureOpacityName = TextureOpacity->GetName(); + } + else + { + TextureOpacityName = FPaths::GetBaseFilename(TextureOpacityPackage->GetName(), true); + } + + // Create opacity texture, if we need to create one. + if (!TextureOpacity) + bCreatedNewTextureOpacity = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing opacity texture, or create new one. + TextureOpacity = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureOpacity, + ImageInfo, + TextureOpacityPackage, + TextureOpacityName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, + NodePath); + + // if (BakeMode == EBakeMode::CookToTemp) + TextureOpacity->SetFlags(RF_Public | RF_Standalone); + + // Create opacity sampling expression, if needed. + if (!ExpressionTextureOpacitySample) + { + ExpressionTextureOpacitySample = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionTextureOpacitySample->Desc = GeneratingParameterNameTexture; + ExpressionTextureOpacitySample->ParameterName = *GeneratingParameterNameTexture; + ExpressionTextureOpacitySample->Texture = TextureOpacity; + ExpressionTextureOpacitySample->SamplerType = SAMPLERTYPE_Grayscale; + + // Offset node placement. + ExpressionTextureOpacitySample->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionTextureOpacitySample->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Add expression. + Material->Expressions.Add(ExpressionTextureOpacitySample); + + // We need to set material type to masked. + TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput* ExpressionOutput = ExpressionOutputs.GetData(); + + Material->OpacityMask.Expression = ExpressionTextureOpacitySample; + Material->BlendMode = BLEND_Masked; + + Material->OpacityMask.Mask = ExpressionOutput->Mask; + Material->OpacityMask.MaskR = 1; + Material->OpacityMask.MaskG = 0; + Material->OpacityMask.MaskB = 0; + Material->OpacityMask.MaskA = 0; + + // Propagate and trigger opacity texture updates. + if (bCreatedNewTextureOpacity) + FAssetRegistryModule::AssetCreated(TextureOpacity); + + TextureOpacity->PreEditChange(nullptr); + TextureOpacity->PostEditChange(); + TextureOpacity->MarkPackageDirty(); + + bExpressionCreated = true; + } + + // Cache the texture package + OutPackages.AddUnique(TextureOpacityPackage); + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + float OpacityValue = 1.0f; + bool bNeedsTranslucency = false; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameters. + FString GeneratingParameterNameScalar = TEXT(""); + FString GeneratingParameterNameTexture = TEXT(""); + + UMaterialExpression * MaterialExpression = Material->Opacity.Expression; + + // Opacity expressions. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; + UMaterialExpressionScalarParameter * ExpressionScalarOpacity = nullptr; + UTexture2D * TextureOpacity = nullptr; + + // Opacity texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // If opacity sampling expression was not created, check if diffuse contains an alpha plane. + if (!ExpressionTextureOpacitySample) + { + UMaterialExpression * MaterialExpressionDiffuse = Material->BaseColor.Expression; + if (MaterialExpressionDiffuse) + { + // Locate diffuse sampling expression. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureDiffuseSample = + Cast< UMaterialExpressionTextureSampleParameter2D >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpressionDiffuse, + UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // See if there's an alpha plane in this expression's texture. + if (ExpressionTextureDiffuseSample) + { + UTexture2D * DiffuseTexture = Cast< UTexture2D >(ExpressionTextureDiffuseSample->Texture); + if (DiffuseTexture && !DiffuseTexture->CompressionNoAlpha) + { + // The diffuse texture has an alpha channel (that wasn't discarded), so we can use it + ExpressionTextureOpacitySample = ExpressionTextureDiffuseSample; + bNeedsTranslucency = true; + } + } + } + } + + // Retrieve opacity uniform parameter. + HAPI_ParmInfo ParmInfoOpacityValue; + FHoudiniApi::ParmInfo_Init(&ParmInfoOpacityValue); + HAPI_ParmId ParmOpacityValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_0, ParmInfoOpacityValue); + + if (ParmOpacityValueId >= 0) + { + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_0); + } + else + { + ParmOpacityValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_1, ParmInfoOpacityValue); + + if (ParmOpacityValueId >= 0) + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_1); + } + + if (ParmOpacityValueId >= 0) + { + if (ParmInfoOpacityValue.size > 0 && ParmInfoOpacityValue.floatValuesIndex >= 0) + { + float OpacityValueRetrieved = 1.0f; + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, + (float *)&OpacityValue, ParmInfoOpacityValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + if (!ExpressionScalarOpacity) + { + ExpressionScalarOpacity = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Clamp retrieved value. + OpacityValueRetrieved = FMath::Clamp< float >(OpacityValueRetrieved, 0.0f, 1.0f); + OpacityValue = OpacityValueRetrieved; + + // Set expression fields. + ExpressionScalarOpacity->DefaultValue = OpacityValue; + ExpressionScalarOpacity->SliderMin = 0.0f; + ExpressionScalarOpacity->SliderMax = 1.0f; + ExpressionScalarOpacity->Desc = GeneratingParameterNameScalar; + ExpressionScalarOpacity->ParameterName = *GeneratingParameterNameScalar; + + // Add expression. + Material->Expressions.Add(ExpressionScalarOpacity); + + // If alpha is less than 1, we need translucency. + bNeedsTranslucency |= (OpacityValue != 1.0f); + } + } + } + + if (bNeedsTranslucency) + Material->BlendMode = BLEND_Translucent; + + if (ExpressionScalarOpacity && ExpressionTextureOpacitySample) + { + // We have both alpha and alpha uniform, attempt to locate multiply expression. + UMaterialExpressionMultiply * ExpressionMultiply = + Cast< UMaterialExpressionMultiply >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, + UMaterialExpressionMultiply::StaticClass())); + + if (!ExpressionMultiply) + ExpressionMultiply = NewObject< UMaterialExpressionMultiply >( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + Material->Expressions.Add(ExpressionMultiply); + + TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); + + ExpressionMultiply->A.Expression = ExpressionTextureOpacitySample; + ExpressionMultiply->B.Expression = ExpressionScalarOpacity; + + Material->Opacity.Expression = ExpressionMultiply; + Material->Opacity.Mask = ExpressionOutput->Mask; + Material->Opacity.MaskR = 0; + Material->Opacity.MaskG = 0; + Material->Opacity.MaskB = 0; + Material->Opacity.MaskA = 1; + + ExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMultiply->MaterialExpressionEditorY = MaterialNodeY; + + ExpressionScalarOpacity->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX; + ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + bExpressionCreated = true; + } + else if (ExpressionScalarOpacity) + { + Material->Opacity.Expression = ExpressionScalarOpacity; + + ExpressionScalarOpacity->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + bExpressionCreated = true; + } + else if (ExpressionTextureOpacitySample) + { + TArray ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); + + Material->Opacity.Expression = ExpressionTextureOpacitySample; + Material->Opacity.Mask = ExpressionOutput->Mask; + Material->Opacity.MaskR = 0; + Material->Opacity.MaskG = 0; + Material->Opacity.MaskB = 0; + Material->Opacity.MaskA = 1; + + bExpressionCreated = true; + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentNormal( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + bool bTangentSpaceNormal = true; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Normal texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Normalmap; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if separate normal texture is available. + HAPI_ParmId ParmNameNormalId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_0); + + if (ParmNameNormalId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_0); + } + else + { + ParmNameNormalId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_1); + + if (ParmNameNormalId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_1); + } + + if (ParmNameNormalId >= 0) + { + // Retrieve space for this normal texture. + HAPI_ParmInfo ParmInfoNormalType; + FHoudiniApi::ParmInfo_Init(&ParmInfoNormalType); + int32 ParmNormalTypeId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); + + // Retrieve value for normal type choice list (if exists). + + if (ParmNormalTypeId >= 0) + { + FString NormalType = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT); + + if (ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0) + { + HAPI_StringHandle StringHandle; + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, false, &StringHandle, ParmInfoNormalType.stringValuesIndex, ParmInfoNormalType.size) == HAPI_RESULT_SUCCESS) + { + // Get the actual string value. + FString NormalTypeString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(NormalTypeString)) + NormalType = NormalTypeString; + } + } + + // Check if we require world space normals. + if (NormalType.Equals(TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD), ESearchCase::IgnoreCase)) + bTangentSpaceNormal = false; + } + + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameNormalId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); + + UTexture2D * TextureNormal = nullptr; + if (ExpressionNormal) + { + TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Normal.Expression) + { + Material->Normal.Expression->ConditionalBeginDestroy(); + Material->Normal.Expression = nullptr; + } + } + + UPackage * TextureNormalPackage = nullptr; + if (TextureNormal) + TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureNormalName; + bool bCreatedNewTextureNormal = false; + + // Create normal texture package, if this is a new normal texture. + if (!TextureNormalPackage) + { + TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + InPackageParams, + TextureNormalName); + } + else if (TextureNormal && !TextureNormal->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureNormalName = TextureNormal->GetName(); + } + else + { + TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); + } + + // Create normal texture, if we need to create one. + if (!TextureNormal) + bCreatedNewTextureNormal = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing normal texture, or create new one. + TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureNormal, + ImageInfo, + TextureNormalPackage, + TextureNormalName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_WorldNormalMap, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureNormal->SetFlags(RF_Public | RF_Standalone); + + // Create normal sampling expression, if needed. + if (!ExpressionNormal) + ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionNormal->Desc = GeneratingParameterName; + ExpressionNormal->ParameterName = *GeneratingParameterName; + + ExpressionNormal->Texture = TextureNormal; + ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; + + // Offset node placement. + ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Set normal space. + Material->bTangentSpaceNormal = bTangentSpaceNormal; + + // Assign expression to material. + Material->Expressions.Add(ExpressionNormal); + Material->Normal.Expression = ExpressionNormal; + + bExpressionCreated = true; + + // Propagate and trigger normal texture updates. + if (bCreatedNewTextureNormal) + FAssetRegistryModule::AssetCreated(TextureNormal); + + TextureNormal->PreEditChange(nullptr); + TextureNormal->PostEditChange(); + TextureNormal->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureNormalPackage); + } + } + + // If separate normal map was not found, see if normal plane exists in diffuse map. + if (!bExpressionCreated) + { + // See if diffuse texture is available. + HAPI_ParmId ParmNameBaseId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + + if (ParmNameBaseId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + } + else + { + ParmNameBaseId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); + + if (ParmNameBaseId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); + } + + if (ParmNameBaseId >= 0) + { + // Normal plane is available in diffuse map. + + TArray< char > ImageBuffer; + + // Retrieve color plane - this will contain normal data. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameBaseId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); + + UTexture2D * TextureNormal = nullptr; + if (ExpressionNormal) + { + TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Normal.Expression) + { + Material->Normal.Expression->ConditionalBeginDestroy(); + Material->Normal.Expression = nullptr; + } + } + + UPackage * TextureNormalPackage = nullptr; + if (TextureNormal) + TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureNormalName; + bool bCreatedNewTextureNormal = false; + + // Create normal texture package, if this is a new normal texture. + if (!TextureNormalPackage) + { + TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + InPackageParams, + TextureNormalName); + } + else if (TextureNormal && !TextureNormal->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureNormalName = TextureNormal->GetName(); + } + else + { + TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); + } + + // Create normal texture, if we need to create one. + if (!TextureNormal) + bCreatedNewTextureNormal = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing normal texture, or create new one. + TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureNormal, + ImageInfo, + TextureNormalPackage, + TextureNormalName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_WorldNormalMap, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureNormal->SetFlags(RF_Public | RF_Standalone); + + // Create normal sampling expression, if needed. + if (!ExpressionNormal) + ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionNormal->Desc = GeneratingParameterName; + ExpressionNormal->ParameterName = *GeneratingParameterName; + + ExpressionNormal->Texture = TextureNormal; + ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; + + // Offset node placement. + ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Set normal space. + Material->bTangentSpaceNormal = bTangentSpaceNormal; + + // Assign expression to material. + Material->Expressions.Add(ExpressionNormal); + Material->Normal.Expression = ExpressionNormal; + + // Propagate and trigger diffuse texture updates. + if (bCreatedNewTextureNormal) + FAssetRegistryModule::AssetCreated(TextureNormal); + + TextureNormal->PreEditChange(nullptr); + TextureNormal->PostEditChange(); + TextureNormal->MarkPackageDirty(); + + bExpressionCreated = true; + } + + // Cache the texture package + OutPackages.AddUnique(TextureNormalPackage); + } + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Specular texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if specular texture is available. + HAPI_ParmId ParmNameSpecularId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_0); + + if (ParmNameSpecularId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_0); + } + else + { + ParmNameSpecularId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_1); + + if (ParmNameSpecularId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_1); + } + + if (ParmNameSpecularId >= 0) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameSpecularId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Specular.Expression); + + UTexture2D * TextureSpecular = nullptr; + if (ExpressionSpecular) + { + TextureSpecular = Cast< UTexture2D >(ExpressionSpecular->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Specular.Expression) + { + Material->Specular.Expression->ConditionalBeginDestroy(); + Material->Specular.Expression = nullptr; + } + } + + UPackage * TextureSpecularPackage = nullptr; + if (TextureSpecular) + TextureSpecularPackage = Cast< UPackage >(TextureSpecular->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureSpecularName; + bool bCreatedNewTextureSpecular = false; + + // Create specular texture package, if this is a new specular texture. + if (!TextureSpecularPackage) + { + TextureSpecularPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, + InPackageParams, + TextureSpecularName); + } + else if (TextureSpecular && !TextureSpecular->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureSpecularName = TextureSpecular->GetName(); + } + else + { + TextureSpecularName = FPaths::GetBaseFilename(TextureSpecularPackage->GetName(), true); + } + + // Create specular texture, if we need to create one. + if (!TextureSpecular) + bCreatedNewTextureSpecular = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing specular texture, or create new one. + TextureSpecular = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureSpecular, + ImageInfo, + TextureSpecularPackage, + TextureSpecularName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureSpecular->SetFlags(RF_Public | RF_Standalone); + + // Create specular sampling expression, if needed. + if (!ExpressionSpecular) + { + ExpressionSpecular = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionSpecular->Desc = GeneratingParameterName; + ExpressionSpecular->ParameterName = *GeneratingParameterName; + + ExpressionSpecular->Texture = TextureSpecular; + ExpressionSpecular->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionSpecular->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionSpecular->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionSpecular); + Material->Specular.Expression = ExpressionSpecular; + + bExpressionCreated = true; + + // Propagate and trigger specular texture updates. + if (bCreatedNewTextureSpecular) + FAssetRegistryModule::AssetCreated(TextureSpecular); + + TextureSpecular->PreEditChange(nullptr); + TextureSpecular->PostEditChange(); + TextureSpecular->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureSpecularPackage); + } + } + + HAPI_ParmInfo ParmInfoSpecularColor; + FHoudiniApi::ParmInfo_Init(&ParmInfoSpecularColor); + HAPI_ParmId ParmNameSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_0, ParmInfoSpecularColor); + + if (ParmNameSpecularColorId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_0); + } + else + { + ParmNameSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_1, ParmInfoSpecularColor); + + if (ParmNameSpecularColorId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_1); + } + + if (!bExpressionCreated && ParmNameSpecularColorId >= 0) + { + // Specular color is available. + + FLinearColor Color = FLinearColor::White; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, + ParmInfoSpecularColor.floatValuesIndex, ParmInfoSpecularColor.size) == HAPI_RESULT_SUCCESS) + { + if (ParmInfoSpecularColor.size == 3) + Color.A = 1.0f; + + UMaterialExpressionVectorParameter * ExpressionSpecularColor = + Cast< UMaterialExpressionVectorParameter >(Material->Specular.Expression); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionSpecularColor) + { + // Otherwise new expression is of a different type. + if (Material->Specular.Expression) + { + Material->Specular.Expression->ConditionalBeginDestroy(); + Material->Specular.Expression = nullptr; + } + + ExpressionSpecularColor = NewObject< UMaterialExpressionVectorParameter >( + Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionSpecularColor->Desc = GeneratingParameterName; + ExpressionSpecularColor->ParameterName = *GeneratingParameterName; + + ExpressionSpecularColor->DefaultValue = Color; + + // Offset node placement. + ExpressionSpecularColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionSpecularColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionSpecularColor); + Material->Specular.Expression = ExpressionSpecularColor; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Roughness texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if roughness texture is available. + HAPI_ParmId ParmNameRoughnessId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); + + if (ParmNameRoughnessId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); + } + else + { + ParmNameRoughnessId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); + + if (ParmNameRoughnessId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); + } + + if (ParmNameRoughnessId >= 0) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameRoughnessId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer ) ) + { + UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Roughness.Expression); + + UTexture2D* TextureRoughness = nullptr; + if (ExpressionRoughness) + { + TextureRoughness = Cast< UTexture2D >(ExpressionRoughness->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Roughness.Expression) + { + Material->Roughness.Expression->ConditionalBeginDestroy(); + Material->Roughness.Expression = nullptr; + } + } + + UPackage * TextureRoughnessPackage = nullptr; + if (TextureRoughness) + TextureRoughnessPackage = Cast< UPackage >(TextureRoughness->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureRoughnessName; + bool bCreatedNewTextureRoughness = false; + + // Create roughness texture package, if this is a new roughness texture. + if (!TextureRoughnessPackage) + { + TextureRoughnessPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, + InPackageParams, + TextureRoughnessName); + } + else if (TextureRoughness && !TextureRoughness->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureRoughnessName = TextureRoughness->GetName(); + } + else + { + TextureRoughnessName = FPaths::GetBaseFilename(TextureRoughnessPackage->GetName(), true); + } + + // Create roughness texture, if we need to create one. + if (!TextureRoughness) + bCreatedNewTextureRoughness = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing roughness texture, or create new one. + TextureRoughness = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureRoughness, + ImageInfo, + TextureRoughnessPackage, + TextureRoughnessName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureRoughness->SetFlags(RF_Public | RF_Standalone); + + // Create roughness sampling expression, if needed. + if (!ExpressionRoughness) + ExpressionRoughness = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionRoughness->Desc = GeneratingParameterName; + ExpressionRoughness->ParameterName = *GeneratingParameterName; + + ExpressionRoughness->Texture = TextureRoughness; + ExpressionRoughness->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionRoughness->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionRoughness->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionRoughness); + Material->Roughness.Expression = ExpressionRoughness; + + bExpressionCreated = true; + + // Propagate and trigger roughness texture updates. + if (bCreatedNewTextureRoughness) + FAssetRegistryModule::AssetCreated(TextureRoughness); + + TextureRoughness->PreEditChange(nullptr); + TextureRoughness->PostEditChange(); + TextureRoughness->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureRoughnessPackage); + } + } + + HAPI_ParmInfo ParmInfoRoughnessValue; + FHoudiniApi::ParmInfo_Init(&ParmInfoRoughnessValue); + HAPI_ParmId ParmNameRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0, ParmInfoRoughnessValue); + + if (ParmNameRoughnessValueId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0); + } + else + { + ParmNameRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1, ParmInfoRoughnessValue); + + if (ParmNameRoughnessValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1); + } + + if (!bExpressionCreated && ParmNameRoughnessValueId >= 0) + { + // Roughness value is available. + + float RoughnessValue = 0.0f; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&RoughnessValue, + ParmInfoRoughnessValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + UMaterialExpressionScalarParameter * ExpressionRoughnessValue = + Cast< UMaterialExpressionScalarParameter >(Material->Roughness.Expression); + + // Clamp retrieved value. + RoughnessValue = FMath::Clamp< float >(RoughnessValue, 0.0f, 1.0f); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionRoughnessValue) + { + // Otherwise new expression is of a different type. + if (Material->Roughness.Expression) + { + Material->Roughness.Expression->ConditionalBeginDestroy(); + Material->Roughness.Expression = nullptr; + } + + ExpressionRoughnessValue = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionRoughnessValue->Desc = GeneratingParameterName; + ExpressionRoughnessValue->ParameterName = *GeneratingParameterName; + + ExpressionRoughnessValue->DefaultValue = RoughnessValue; + ExpressionRoughnessValue->SliderMin = 0.0f; + ExpressionRoughnessValue->SliderMax = 1.0f; + + // Offset node placement. + ExpressionRoughnessValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionRoughnessValue->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionRoughnessValue); + Material->Roughness.Expression = ExpressionRoughnessValue; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Metallic texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if metallic texture is available. + HAPI_ParmId ParmNameMetallicId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_METALLIC); + + if (ParmNameMetallicId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC); + } + + if (ParmNameMetallicId >= 0) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameMetallicId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Metallic.Expression); + + UTexture2D * TextureMetallic = nullptr; + if (ExpressionMetallic) + { + TextureMetallic = Cast< UTexture2D >(ExpressionMetallic->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Metallic.Expression) + { + Material->Metallic.Expression->ConditionalBeginDestroy(); + Material->Metallic.Expression = nullptr; + } + } + + UPackage * TextureMetallicPackage = nullptr; + if (TextureMetallic) + TextureMetallicPackage = Cast< UPackage >(TextureMetallic->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureMetallicName; + bool bCreatedNewTextureMetallic = false; + + // Create metallic texture package, if this is a new metallic texture. + if (!TextureMetallicPackage) + { + TextureMetallicPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, + InPackageParams, + TextureMetallicName); + } + else if (TextureMetallic && !TextureMetallic->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureMetallicName = TextureMetallic->GetName(); + } + else + { + TextureMetallicName = FPaths::GetBaseFilename(TextureMetallicPackage->GetName(), true); + } + + // Create metallic texture, if we need to create one. + if (!TextureMetallic) + bCreatedNewTextureMetallic = true; + + // Get the node path to add it to the meta data + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing metallic texture, or create new one. + TextureMetallic = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureMetallic, + ImageInfo, + TextureMetallicPackage, + TextureMetallicName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureMetallic->SetFlags(RF_Public | RF_Standalone); + + // Create metallic sampling expression, if needed. + if (!ExpressionMetallic) + ExpressionMetallic = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionMetallic->Desc = GeneratingParameterName; + ExpressionMetallic->ParameterName = *GeneratingParameterName; + + ExpressionMetallic->Texture = TextureMetallic; + ExpressionMetallic->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionMetallic->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMetallic->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionMetallic); + Material->Metallic.Expression = ExpressionMetallic; + + bExpressionCreated = true; + + // Propagate and trigger metallic texture updates. + if (bCreatedNewTextureMetallic) + FAssetRegistryModule::AssetCreated(TextureMetallic); + + TextureMetallic->PreEditChange(nullptr); + TextureMetallic->PostEditChange(); + TextureMetallic->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureMetallicPackage); + } + } + + HAPI_ParmInfo ParmInfoMetallic; + FHoudiniApi::ParmInfo_Init(&ParmInfoMetallic); + HAPI_ParmId ParmNameMetallicValueIdx = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmInfoMetallic); + + if (ParmNameMetallicValueIdx >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); + + if (!bExpressionCreated && ParmNameMetallicValueIdx >= 0) + { + // Metallic value is available. + + float MetallicValue = 0.0f; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&MetallicValue, + ParmInfoMetallic.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + UMaterialExpressionScalarParameter * ExpressionMetallicValue = + Cast< UMaterialExpressionScalarParameter >(Material->Metallic.Expression); + + // Clamp retrieved value. + MetallicValue = FMath::Clamp< float >(MetallicValue, 0.0f, 1.0f); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionMetallicValue) + { + // Otherwise new expression is of a different type. + if (Material->Metallic.Expression) + { + Material->Metallic.Expression->ConditionalBeginDestroy(); + Material->Metallic.Expression = nullptr; + } + + ExpressionMetallicValue = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionMetallicValue->Desc = GeneratingParameterName; + ExpressionMetallicValue->ParameterName = *GeneratingParameterName; + + ExpressionMetallicValue->DefaultValue = MetallicValue; + ExpressionMetallicValue->SliderMin = 0.0f; + ExpressionMetallicValue->SliderMax = 1.0f; + + // Offset node placement. + ExpressionMetallicValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMetallicValue->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionMetallicValue); + Material->Metallic.Expression = ExpressionMetallicValue; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; + //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; + EObjectFlags ObjectFlag = RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Emissive texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if emissive texture is available. + HAPI_ParmId ParmNameEmissiveId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_EMISSIVE); + + if (ParmNameEmissiveId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE); + } + + if (ParmNameEmissiveId >= 0) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNameEmissiveId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->EmissiveColor.Expression); + + UTexture2D * TextureEmissive = nullptr; + if (ExpressionEmissive) + { + TextureEmissive = Cast< UTexture2D >(ExpressionEmissive->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->EmissiveColor.Expression) + { + Material->EmissiveColor.Expression->ConditionalBeginDestroy(); + Material->EmissiveColor.Expression = nullptr; + } + } + + UPackage * TextureEmissivePackage = nullptr; + if (TextureEmissive) + TextureEmissivePackage = Cast< UPackage >(TextureEmissive->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureEmissiveName; + bool bCreatedNewTextureEmissive = false; + + // Create emissive texture package, if this is a new emissive texture. + if (!TextureEmissivePackage) + { + TextureEmissivePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, + InPackageParams, + TextureEmissiveName); + } + else if (TextureEmissive && !TextureEmissive->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureEmissiveName = TextureEmissive->GetName(); + } + else + { + TextureEmissiveName = FPaths::GetBaseFilename(TextureEmissivePackage->GetName(), true); + } + + // Create emissive texture, if we need to create one. + if (!TextureEmissive) + bCreatedNewTextureEmissive = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing emissive texture, or create new one. + TextureEmissive = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureEmissive, + ImageInfo, + TextureEmissivePackage, + TextureEmissiveName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureEmissive->SetFlags(RF_Public | RF_Standalone); + + // Create emissive sampling expression, if needed. + if (!ExpressionEmissive) + ExpressionEmissive = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionEmissive->Desc = GeneratingParameterName; + ExpressionEmissive->ParameterName = *GeneratingParameterName; + + ExpressionEmissive->Texture = TextureEmissive; + ExpressionEmissive->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionEmissive->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionEmissive->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionEmissive); + Material->EmissiveColor.Expression = ExpressionEmissive; + + bExpressionCreated = true; + + // Propagate and trigger metallic texture updates. + if (bCreatedNewTextureEmissive) + FAssetRegistryModule::AssetCreated(TextureEmissive); + + TextureEmissive->PreEditChange(nullptr); + TextureEmissive->PostEditChange(); + TextureEmissive->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureEmissivePackage); + } + } + + HAPI_ParmInfo ParmInfoEmissive; + FHoudiniApi::ParmInfo_Init(&ParmInfoEmissive); + HAPI_ParmId ParmNameEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0, ParmInfoEmissive); + + if (ParmNameEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0); + else + { + ParmNameEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1, ParmInfoEmissive); + + if (ParmNameEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1); + } + + if (!bExpressionCreated && ParmNameEmissiveValueId >= 0) + { + // Emissive color is available. + + FLinearColor Color = FLinearColor::White; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, + ParmInfoEmissive.floatValuesIndex, ParmInfoEmissive.size) == HAPI_RESULT_SUCCESS) + { + if (ParmInfoEmissive.size == 3) + Color.A = 1.0f; + + UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = + Cast< UMaterialExpressionConstant4Vector >(Material->EmissiveColor.Expression); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionEmissiveColor) + { + // Otherwise new expression is of a different type. + if (Material->EmissiveColor.Expression) + { + Material->EmissiveColor.Expression->ConditionalBeginDestroy(); + Material->EmissiveColor.Expression = nullptr; + } + + ExpressionEmissiveColor = NewObject< UMaterialExpressionConstant4Vector >( + Material, UMaterialExpressionConstant4Vector::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionEmissiveColor->Desc = GeneratingParameterName; + if (ExpressionEmissiveColor->CanRenameNode()) + ExpressionEmissiveColor->SetEditableName(*GeneratingParameterName); + + ExpressionEmissiveColor->Constant = Color; + + // Offset node placement. + ExpressionEmissiveColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionEmissiveColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionEmissiveColor); + Material->EmissiveColor.Expression = ExpressionEmissiveColor; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + + +bool +FHoudiniMaterialTranslator::UpdateMaterialInstanceParameter( + FHoudiniGenericAttribute MaterialParameter, + UMaterialInstanceConstant* MaterialInstance, + const TArray& InPackages) +{ + bool bParameterUpdated = false; + +#if WITH_EDITOR + if (!MaterialInstance) + return false; + + if (MaterialParameter.AttributeName.IsEmpty()) + return false; + + // The default material instance parameters needs to be handled manually as they cant be changed via generic SetParameters functions + if (MaterialParameter.AttributeName.Compare("CastShadowAsMasked", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideCastShadowAsMasked() && (MaterialInstance->GetCastShadowAsMasked() == Value)) + return false; + + MaterialInstance->SetOverrideCastShadowAsMasked(true); + MaterialInstance->SetCastShadowAsMasked(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("EmissiveBoost", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideEmissiveBoost() && (MaterialInstance->GetEmissiveBoost() == Value)) + return false; + + MaterialInstance->SetOverrideEmissiveBoost(true); + MaterialInstance->SetEmissiveBoost(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("DiffuseBoost", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideDiffuseBoost() && (MaterialInstance->GetDiffuseBoost() == Value)) + return false; + + MaterialInstance->SetOverrideDiffuseBoost(true); + MaterialInstance->SetDiffuseBoost(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("ExportResolutionScale", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideExportResolutionScale() && (MaterialInstance->GetExportResolutionScale() == Value)) + return false; + + MaterialInstance->SetOverrideExportResolutionScale(true); + MaterialInstance->SetExportResolutionScale(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("OpacityMaskClipValue", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue && (MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue = true; + MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("BlendMode", ESearchCase::IgnoreCase) == 0) + { + EBlendMode EnumValue = (EBlendMode)MaterialParameter.GetIntValue(); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + FString StringValue = MaterialParameter.GetStringValue(); + if (StringValue.Compare("Opaque", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Opaque; + else if (StringValue.Compare("Masked", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Masked; + else if (StringValue.Compare("Translucent", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Translucent; + else if (StringValue.Compare("Additive", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Additive; + else if (StringValue.Compare("Modulate", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Modulate; + else if (StringValue.StartsWith("Alpha", ESearchCase::IgnoreCase)) + EnumValue = EBlendMode::BLEND_AlphaComposite; + } + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_BlendMode && (MaterialInstance->BasePropertyOverrides.BlendMode == EnumValue)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = true; + MaterialInstance->BasePropertyOverrides.BlendMode = EnumValue; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("ShadingModel", ESearchCase::IgnoreCase) == 0) + { + EMaterialShadingModel EnumValue = (EMaterialShadingModel)MaterialParameter.GetIntValue(); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + FString StringValue = MaterialParameter.GetStringValue(); + if (StringValue.Compare("Unlit", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Unlit; + else if (StringValue.StartsWith("Default", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_DefaultLit; + else if (StringValue.Compare("Subsurface", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Subsurface; + else if (StringValue.StartsWith("Preintegrated", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_PreintegratedSkin; + else if (StringValue.StartsWith("Clear", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_ClearCoat; + else if (StringValue.Compare("SubsurfaceProfile", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_SubsurfaceProfile; + else if (StringValue.Compare("TwoSidedFoliage", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_TwoSidedFoliage; + else if (StringValue.Compare("Hair", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Hair; + else if (StringValue.Compare("Cloth", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Cloth; + else if (StringValue.Compare("Eye", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Eye; + } + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel && (MaterialInstance->BasePropertyOverrides.ShadingModel == EnumValue)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel = true; + MaterialInstance->BasePropertyOverrides.ShadingModel = EnumValue; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("TwoSided", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_TwoSided && (MaterialInstance->BasePropertyOverrides.TwoSided == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_TwoSided = true; + MaterialInstance->BasePropertyOverrides.TwoSided = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("DitheredLODTransition", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition && (MaterialInstance->BasePropertyOverrides.DitheredLODTransition == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition = true; + MaterialInstance->BasePropertyOverrides.DitheredLODTransition = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("PhysMaterial", ESearchCase::IgnoreCase) == 0) + { + // Try to load a Material corresponding to the parameter value + FString ParamValue = MaterialParameter.GetStringValue(); + UPhysicalMaterial* FoundPhysMaterial = Cast< UPhysicalMaterial >( + StaticLoadObject(UPhysicalMaterial::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); + + // Update the parameter value if necessary + if (!FoundPhysMaterial || (MaterialInstance->PhysMaterial == FoundPhysMaterial)) + return false; + + MaterialInstance->PhysMaterial = FoundPhysMaterial; + bParameterUpdated = true; + } + + if (bParameterUpdated) + return true; + + // Handling custom parameters + FName CurrentMatParamName = FName(*MaterialParameter.AttributeName); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + // String attributes are used for textures parameters + // We need to find the texture corresponding to the param + UTexture* FoundTexture = nullptr; + FString ParamValue = MaterialParameter.GetStringValue(); + + // Texture can either be already existing texture assets in UE4, or a newly generated textures by this asset + // Try to find the texture corresponding to the param value in the existing assets first. + FoundTexture = Cast( + StaticLoadObject(UTexture::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); + + if (!FoundTexture) + { + // We couldn't find a texture corresponding to the parameter in the existing UE4 assets + // Try to find the corresponding texture in the cooked temporary package we just generated + FoundTexture = FHoudiniMaterialTranslator::FindGeneratedTexture(ParamValue, InPackages); + } + + // Do not update if unnecessary + if (FoundTexture) + { + // Do not update if unnecessary + UTexture* OldTexture = nullptr; + bool FoundOldParam = MaterialInstance->GetTextureParameterValue(CurrentMatParamName, OldTexture); + if (FoundOldParam && (OldTexture == FoundTexture)) + return false; + + MaterialInstance->SetTextureParameterValueEditorOnly(CurrentMatParamName, FoundTexture); + bParameterUpdated = true; + } + } + else if (MaterialParameter.AttributeTupleSize == 1) + { + // Single attributes are either for scalar parameters or static switches + float OldValue; + bool FoundOldScalarParam = MaterialInstance->GetScalarParameterValue(CurrentMatParamName, OldValue); + if (FoundOldScalarParam) + { + // The material parameter is a scalar + float NewValue = (float)MaterialParameter.GetDoubleValue(); + + // Do not update if unnecessary + if (OldValue == NewValue) + return false; + + MaterialInstance->SetScalarParameterValueEditorOnly(CurrentMatParamName, NewValue); + bParameterUpdated = true; + } + else + { + // See if the underlying parameter is a static switch + bool NewBoolValue = MaterialParameter.GetBoolValue(); + + // We need to iterate over the material's static parameter set + FStaticParameterSet StaticParameters; + MaterialInstance->GetStaticParameterValues(StaticParameters); + + for (int32 SwitchParameterIdx = 0; SwitchParameterIdx < StaticParameters.StaticSwitchParameters.Num(); ++SwitchParameterIdx) + { + FStaticSwitchParameter& SwitchParameter = StaticParameters.StaticSwitchParameters[SwitchParameterIdx]; + if (SwitchParameter.ParameterInfo.Name != CurrentMatParamName) + continue; + + if (SwitchParameter.Value == NewBoolValue) + return false; + + SwitchParameter.Value = NewBoolValue; + SwitchParameter.bOverride = true; + + MaterialInstance->UpdateStaticPermutation(StaticParameters); + bParameterUpdated = true; + break; + } + } + } + else + { + // Tuple attributes are for vector parameters + FLinearColor NewLinearColor; + // if the attribute is stored in an int, we'll have to convert a color to a linear color + if (MaterialParameter.AttributeType == EAttribStorageType::INT || MaterialParameter.AttributeType == EAttribStorageType::INT64) + { + FColor IntColor; + IntColor.R = (int8)MaterialParameter.GetIntValue(0); + IntColor.G = (int8)MaterialParameter.GetIntValue(1); + IntColor.B = (int8)MaterialParameter.GetIntValue(2); + if (MaterialParameter.AttributeTupleSize >= 4) + IntColor.A = (int8)MaterialParameter.GetIntValue(3); + else + IntColor.A = 1; + + NewLinearColor = FLinearColor(IntColor); + } + else + { + NewLinearColor.R = (float)MaterialParameter.GetDoubleValue(0); + NewLinearColor.G = (float)MaterialParameter.GetDoubleValue(1); + NewLinearColor.B = (float)MaterialParameter.GetDoubleValue(2); + if (MaterialParameter.AttributeTupleSize >= 4) + NewLinearColor.A = (float)MaterialParameter.GetDoubleValue(3); + } + + // Do not update if unnecessary + FLinearColor OldValue; + bool FoundOldParam = MaterialInstance->GetVectorParameterValue(CurrentMatParamName, OldValue); + if (FoundOldParam && (OldValue == NewLinearColor)) + return false; + + MaterialInstance->SetVectorParameterValueEditorOnly(CurrentMatParamName, NewLinearColor); + bParameterUpdated = true; + } +#endif + + return bParameterUpdated; +} + + +UTexture* +FHoudiniMaterialTranslator::FindGeneratedTexture(const FString& TextureString, const TArray& InPackages) +{ + if (TextureString.IsEmpty()) + return nullptr; + + // Try to find the corresponding texture in the cooked temporary package generated by an HDA + UTexture* FoundTexture = nullptr; + for(const auto& CurrentPackage : InPackages) + { + // Iterate through the cooked packages + if (!CurrentPackage || CurrentPackage->IsPendingKill()) + continue; + + // First, check if the package contains a texture + FString CurrentPackageName = CurrentPackage->GetName(); + UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); + if (!PackageTexture) + continue; + + // Then check if the package's metadata match what we're looking for + // Make sure this texture was generated by Houdini Engine + UMetaData* MetaData = CurrentPackage->GetMetaData(); + if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + continue; + + // Get the texture type from the meta data + // Texture type store has meta data will be C_A, N, S, R etc.. + const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); + if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // Convert the texture type to a "friendly" version + // C_A to diffuse, N to Normal, S to Specular etc... + FString TextureTypeFriendlyString = TextureTypeString; + FString TextureTypeFriendlyAlternateString = TEXT(""); + if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) + { + TextureTypeFriendlyString = TEXT("diffuse"); + TextureTypeFriendlyAlternateString = TEXT("basecolor"); + } + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("normal"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("emissive"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("specular"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("roughness"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("metallic"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("opacity"); + + // See if we have a match between the texture string and the friendly name + if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) + { + FoundTexture = PackageTexture; + break; + } + + // Get the node path from the meta data + const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); + if (NodePath.IsEmpty()) + continue; + + // See if we have a match with the path and texture type + FString PathAndType = NodePath + TEXT("/") + TextureTypeString; + if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // See if we have a match with the friendly path and texture type + FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; + if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // Try the alternate friendly string + if (!TextureTypeFriendlyAlternateString.IsEmpty()) + { + PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; + if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + } + } + + return FoundTexture; +} diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h index 6a7727eb1..678171f3e 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h @@ -1,220 +1,220 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniGeoPartObject.h" -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "Engine/TextureDefines.h" - -//#include "HoudiniMaterialTranslator.generated.h" - -class UMaterial; -class UMaterialInterface; -class UMaterialExpression; -class UMaterialInstanceConstant; -class UTexture2D; -class UTexture; -class UPackage; - -struct FHoudiniPackageParams; -struct FCreateTexture2DParameters; -struct FHoudiniGenericAttribute; - -// Forward declared enums do not work with 4.24 builds on Linux with the Clang 8.0.1 toolchain: ISO C++ forbids forward references to 'enum' types -// enum TextureGroup; - -struct HOUDINIENGINE_API FHoudiniMaterialTranslator -{ -public: - - // - static bool CreateHoudiniMaterials( - const HAPI_NodeId& InNodeId, - const FHoudiniPackageParams& InPackageParams, - const TArray& InUniqueMaterialIds, - const TArray& InUniqueMaterialInfos, - const TMap& InMaterials, - TMap& OutMaterials, - TArray& OutPackages, - const bool& bForceRecookAll, - bool bInTreatExistingMaterialsAsUpToDate=false); - - // - static bool CreateMaterialInstances( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& UniqueMaterialInstanceOverrides, - const TArray& InPackages, - const TMap& InMaterials, - TMap& OutMaterials, - const bool& bForceRecookAll); - - // - static bool UpdateMaterialInstanceParameter( - FHoudiniGenericAttribute MaterialParameter, - UMaterialInstanceConstant* MaterialInstance, - const TArray& InPackages); - - static UTexture* FindGeneratedTexture( - const FString& TextureString, - const TArray& InPackages); - - // - static UPackage* CreatePackageForTexture( - const HAPI_NodeId& InMaterialNodeId, - const FString& InTextureType, - const FHoudiniPackageParams& InPackageParams, - FString& OutTextureName); - - // - static UPackage* CreatePackageForMaterial( - const HAPI_NodeId& InMaterialNodeId, - const FString& InMaterialName, - const FHoudiniPackageParams& InPackageParams, - FString& OutMaterialName); - - - // Create a texture from given information. - static UTexture2D* CreateUnrealTexture( - UTexture2D* ExistingTexture, - const HAPI_ImageInfo& ImageInfo, - UPackage* Package, - const FString& TextureName, - const TArray& ImageBuffer, - const FCreateTexture2DParameters& TextureParameters, - const TextureGroup& LODGroup, - const FString& TextureType, - const FString& NodePath); - - // HAPI : Retrieve a list of image planes. - static bool HapiExtractImage( - const HAPI_ParmId& NodeParmId, - const HAPI_MaterialInfo& MaterialInfo, - const char * PlaneType, - const HAPI_ImageDataFormat& ImageDataFormat, - HAPI_ImagePacking ImagePacking, - bool bRenderToImage, - TArray& OutImageBuffer); - - // HAPI : Extract image data. - static bool HapiGetImagePlanes( - const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes); - - // Returns a unique name for a given material, its relative path (to the asset) - static bool GetMaterialRelativePath( - const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialNodeInfo, FString& OutRelativePath); - static bool GetMaterialRelativePath( - const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath); - -protected: - - // Helper function to locate first Material expression of given class within given expression subgraph. - static UMaterialExpression * MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass); - - // Create various material components. - static bool CreateMaterialComponentDiffuse( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentNormal( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentSpecular( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentRoughness( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentMetallic( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentEmissive( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentOpacity( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentOpacityMask( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - -public: - - // Material node construction offsets. - static const int32 MaterialExpressionNodeX; - static const int32 MaterialExpressionNodeY; - static const int32 MaterialExpressionNodeStepX; - static const int32 MaterialExpressionNodeStepY; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniGeoPartObject.h" +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Engine/TextureDefines.h" + +//#include "HoudiniMaterialTranslator.generated.h" + +class UMaterial; +class UMaterialInterface; +class UMaterialExpression; +class UMaterialInstanceConstant; +class UTexture2D; +class UTexture; +class UPackage; + +struct FHoudiniPackageParams; +struct FCreateTexture2DParameters; +struct FHoudiniGenericAttribute; + +// Forward declared enums do not work with 4.24 builds on Linux with the Clang 8.0.1 toolchain: ISO C++ forbids forward references to 'enum' types +// enum TextureGroup; + +struct HOUDINIENGINE_API FHoudiniMaterialTranslator +{ +public: + + // + static bool CreateHoudiniMaterials( + const HAPI_NodeId& InNodeId, + const FHoudiniPackageParams& InPackageParams, + const TArray& InUniqueMaterialIds, + const TArray& InUniqueMaterialInfos, + const TMap& InMaterials, + TMap& OutMaterials, + TArray& OutPackages, + const bool& bForceRecookAll, + bool bInTreatExistingMaterialsAsUpToDate=false); + + // + static bool CreateMaterialInstances( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& UniqueMaterialInstanceOverrides, + const TArray& InPackages, + const TMap& InMaterials, + TMap& OutMaterials, + const bool& bForceRecookAll); + + // + static bool UpdateMaterialInstanceParameter( + FHoudiniGenericAttribute MaterialParameter, + UMaterialInstanceConstant* MaterialInstance, + const TArray& InPackages); + + static UTexture* FindGeneratedTexture( + const FString& TextureString, + const TArray& InPackages); + + // + static UPackage* CreatePackageForTexture( + const HAPI_NodeId& InMaterialNodeId, + const FString& InTextureType, + const FHoudiniPackageParams& InPackageParams, + FString& OutTextureName); + + // + static UPackage* CreatePackageForMaterial( + const HAPI_NodeId& InMaterialNodeId, + const FString& InMaterialName, + const FHoudiniPackageParams& InPackageParams, + FString& OutMaterialName); + + + // Create a texture from given information. + static UTexture2D* CreateUnrealTexture( + UTexture2D* ExistingTexture, + const HAPI_ImageInfo& ImageInfo, + UPackage* Package, + const FString& TextureName, + const TArray& ImageBuffer, + const FCreateTexture2DParameters& TextureParameters, + const TextureGroup& LODGroup, + const FString& TextureType, + const FString& NodePath); + + // HAPI : Retrieve a list of image planes. + static bool HapiExtractImage( + const HAPI_ParmId& NodeParmId, + const HAPI_MaterialInfo& MaterialInfo, + const char * PlaneType, + const HAPI_ImageDataFormat& ImageDataFormat, + HAPI_ImagePacking ImagePacking, + bool bRenderToImage, + TArray& OutImageBuffer); + + // HAPI : Extract image data. + static bool HapiGetImagePlanes( + const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes); + + // Returns a unique name for a given material, its relative path (to the asset) + static bool GetMaterialRelativePath( + const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialNodeInfo, FString& OutRelativePath); + static bool GetMaterialRelativePath( + const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath); + +protected: + + // Helper function to locate first Material expression of given class within given expression subgraph. + static UMaterialExpression * MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass); + + // Create various material components. + static bool CreateMaterialComponentDiffuse( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentNormal( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentSpecular( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentRoughness( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentMetallic( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentEmissive( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentOpacity( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentOpacityMask( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + +public: + + // Material node construction offsets. + static const int32 MaterialExpressionNodeX; + static const int32 MaterialExpressionNodeY; + static const int32 MaterialExpressionNodeStepX; + static const int32 MaterialExpressionNodeStepY; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp index f781b2cff..5a29919bb 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp @@ -1,6329 +1,6397 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniMeshTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniOutput.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniMaterialTranslator.h" -#include "HoudiniAssetActor.h" - -#include "HoudiniStaticMesh.h" -#include "HoudiniStaticMeshComponent.h" -#include "Engine/StaticMeshSocket.h" - -#include "PhysicsEngine/BodySetup.h" -#include "Engine/StaticMesh.h" -#include "PackageTools.h" -#include "RawMesh.h" -#include "Materials/MaterialInterface.h" -#include "Materials/Material.h" -#include "MeshDescription.h" -#include "StaticMeshAttributes.h" -#include "MeshDescriptionOperations.h" - -#include "BSPOps.h" -#include "Model.h" -#include "Engine/Polys.h" -#include "AssetRegistryModule.h" -#include "Interfaces/ITargetPlatform.h" -#include "Interfaces/ITargetPlatformManagerModule.h" -#include "AI/Navigation/NavCollisionBase.h" -#include "ObjectTools.h" - -// #include "Async/ParallelFor.h" - -#include "ProfilingDebugging/CpuProfilerTrace.h" - -#include "EditorSupportDelegates.h" - -#if WITH_EDITOR - #include "UnrealEd/Private/ConvexDecompTool.h" - #include "Editor/UnrealEd/Private/GeomFitUtils.h" - #include "LevelEditorViewport.h" - #include "FileHelpers.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// -bool -FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - UHoudiniOutput* InOutput, - const FHoudiniPackageParams& InPackageParams, - EHoudiniStaticMeshMethod InStaticMeshMethod, - UObject* InOuterComponent, - bool bInTreatExistingMaterialsAsUpToDate, - bool bInDestroyProxies) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InPackageParams.OuterPackage || InPackageParams.OuterPackage->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - TMap NewOutputObjects; - TMap OldOutputObjects = InOutput->GetOutputObjects(); - TMap& AssignementMaterials = InOutput->GetAssignementMaterials(); - TMap& ReplacementMaterials = InOutput->GetReplacementMaterials(); - - bool InForceRebuild = false; - if (InOutput->HasAnyCurrentProxy() && InStaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh) - { - // Make sure we're not preventing refinement - InForceRebuild = true; - } - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) - { - // Not a mesh, skip - if (CurHGPO.Type != EHoudiniPartType::Mesh) - continue; - - CreateStaticMeshFromHoudiniGeoPartObject( - CurHGPO, - InPackageParams, - OldOutputObjects, - NewOutputObjects, - AssignementMaterials, - ReplacementMaterials, - InForceRebuild, - InStaticMeshMethod, - bInTreatExistingMaterialsAsUpToDate); - } - - return FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - InOutput, - InOuterComponent, - NewOutputObjects, - bInDestroyProxies); -} - -bool -FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - UHoudiniOutput* InOutput, - UObject* InOuterComponent, - TMap& InNewOutputObjects, - bool bInDestroyProxies, - bool bInApplyGenericProperties) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - TMap OldOutputObjects = InOutput->GetOutputObjects(); - - // Remove Static Meshes and their components from the old map - // to avoid their deletion if new proxies were created for them - for (auto& NewOutputObj : InNewOutputObjects) - { - FHoudiniOutputObjectIdentifier OutputIdentifier = NewOutputObj.Key; - - // See if we already had that pair in the old map of static mesh - FHoudiniOutputObject* FoundOldOutputObj = OldOutputObjects.Find(NewOutputObj.Key); - if (!FoundOldOutputObj) - continue; - - UObject* NewStaticMesh = NewOutputObj.Value.OutputObject; - UObject* NewProxyMesh = NewOutputObj.Value.ProxyObject; - - UObject* OldStaticMesh = FoundOldOutputObj->OutputObject; - if (OldStaticMesh && !OldStaticMesh->IsPendingKill()) - { - // If a proxy was created for an existing static mesh, keep the existing static - // mesh (will be hidden) - if (NewProxyMesh && NewOutputObj.Value.bProxyIsCurrent) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - else if (NewStaticMesh && NewStaticMesh == OldStaticMesh) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - - UObject* OldProxyMesh = FoundOldOutputObj->ProxyObject; - if (OldProxyMesh && !OldProxyMesh->IsPendingKill()) - { - // If a new static mesh was created for a proxy, keep the proxy (will be hidden) - // ... unless we want to explicitly destroy proxies - if (NewStaticMesh && !bInDestroyProxies) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - else if (NewProxyMesh && (NewProxyMesh == OldProxyMesh)) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - } - - // The old map now only contains unused/stale Meshes/Components, delete them - for (auto& OldPair : OldOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; - FHoudiniOutputObject& OldOutputObject = OldPair.Value; - - // Remove the old component from the map - RemoveAndDestroyComponent(OldOutputObject.OutputComponent); - OldOutputObject.OutputComponent = nullptr; - // Remove the old proxy component from the map - RemoveAndDestroyComponent(OldOutputObject.ProxyComponent); - OldOutputObject.ProxyComponent = nullptr; - - if (OldOutputObject.OutputObject && !OldOutputObject.OutputObject->IsPendingKill()) - { - OldOutputObject.OutputObject->MarkPendingKill(); - } - - if (OldOutputObject.ProxyObject && !OldOutputObject.ProxyObject->IsPendingKill()) - { - OldOutputObject.ProxyObject->MarkPendingKill(); - } - } - OldOutputObjects.Empty(); - - /* - // Remove any stale components, these are components with OutputIdentifiers that are not - // in NewOutputObjects. This seems to happen mostly with the first or second cook after a - // "Rebuild Asset" - if (OutputComponents.Num() > 0 || OutputProxyComponents.Num() > 0) - { - TArray> StaleComponents; - const uint32 MaxNumStale = FMath::Max(OutputComponents.Num(), OutputProxyComponents.Num()); - StaleComponents.Reserve(MaxNumStale); - for (auto& ComponentPair : OutputComponents) - { - if (!NewOutputObjects.Contains(ComponentPair.Key) && !OldOutputObjectsReplacedByProxy.Contains(ComponentPair.Key)) - { - StaleComponents.Add(ComponentPair); - } - } - for (auto& ComponentPair : StaleComponents) - { - RemoveAndDestroyComponent(ComponentPair.Key, OutputComponents); - } - StaleComponents.Empty(MaxNumStale); - - for (auto& ComponentPair : OutputProxyComponents) - { - if (!NewOutputProxyObjects.Contains(ComponentPair.Key) && !OldOutputProxyObjectsReplacedByStaticMesh.Contains(ComponentPair.Key)) - { - StaleComponents.Add(ComponentPair); - } - } - for (auto& ComponentPair : StaleComponents) - { - RemoveAndDestroyComponent(ComponentPair.Key, OutputProxyComponents); - } - StaleComponents.Empty(); - } - */ - - // Now create/update the new static mesh components - for (auto& NewPair : InNewOutputObjects) - { - // Get the old Identifier / StaticMesh - const FHoudiniOutputObjectIdentifier& OutputIdentifier = NewPair.Key; - FHoudiniOutputObject& OutputObject = NewPair.Value; - - // Check if we should create a Proxy/SMC - if (OutputObject.bProxyIsCurrent) - { - UObject *Mesh = OutputObject.ProxyObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) - { - HOUDINI_LOG_ERROR(TEXT("Proxy Mesh is invalid (wrong type or pending kill)...")); - continue; - } - - // Create or update a new proxy component - TSubclassOf ComponentType = UHoudiniStaticMeshComponent::StaticClass(); - const FHoudiniGeoPartObject *FoundHGPO = nullptr; - bool bCreated = false; - UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); - if (MeshComponent) - { - UHoudiniStaticMeshComponent *HSMC = Cast(MeshComponent); - - if (bCreated) - { - PostCreateHoudiniStaticMeshComponent(HSMC, Mesh); - } - else if (HSMC && !HSMC->IsPendingKill() && HSMC->GetMesh() != Mesh) - { - // We need to reassign the HSM to the component - UHoudiniStaticMesh* HSM = Cast(Mesh); - HSMC->SetMesh(HSM); - } - - UpdateMeshComponent( - MeshComponent, - OutputIdentifier, - FoundHGPO, - InOutput->HoudiniCreatedSocketActors, - InOutput->HoudiniAttachedSocketActors, - bInApplyGenericProperties); - - if (!bCreated) - { - // For proxy meshes: notify that the mesh has been updated - HSMC->NotifyMeshUpdated(); - HSMC->SetHoudiniIconVisible(true); - } - } - - // Now, ensure that meshes replaced by proxies are still kept but hidden - USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent) - { - SceneComponent->SetVisibility(false); - SceneComponent->SetHiddenInGame(true); - } - - // If the proxy mesh we just created is templated, hide it in game - if (FoundHGPO->bIsTemplated) - { - MeshComponent->SetHiddenInGame(true); - } - } - else - { - // Create a new SMC if needed - UObject* Mesh = OutputObject.OutputObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) - { - HOUDINI_LOG_ERROR(TEXT("Mesh is invalid (wrong type or pending kill)...")); - continue; - } - - TSubclassOf ComponentType = UStaticMeshComponent::StaticClass(); - const FHoudiniGeoPartObject *FoundHGPO = nullptr; - bool bCreated = false; - UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); - if (MeshComponent) - { - if (bCreated) - { - PostCreateStaticMeshComponent(Cast(MeshComponent), Mesh); - } - UpdateMeshComponent( - MeshComponent, - OutputIdentifier, - FoundHGPO, - InOutput->HoudiniCreatedSocketActors, - InOutput->HoudiniAttachedSocketActors, - bInApplyGenericProperties); - } - - // Now, ensure that proxies replaced by meshes are still kept but hidden - UHoudiniStaticMeshComponent *HSMC = Cast(OutputObject.ProxyComponent); - if (HSMC) - { - HSMC->SetVisibility(false); - HSMC->SetHiddenInGame(true); - HSMC->SetHoudiniIconVisible(false); - } - - // If the mesh we just created is templated, hide it in game - if (FoundHGPO->bIsTemplated) - { - MeshComponent->SetHiddenInGame(true); - } - } - } - - // Assign the new output objects to the output - InOutput->SetOutputObjects(InNewOutputObjects); - - return true; -} - -void -FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, - const FHoudiniGeoPartObject *InHGPO, TArray &HoudiniCreatedSocketActors, TArray &HoudiniAttachedSocketActors, - bool bInApplyGenericProperties) -{ - // Update collision/visibility - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(InOutputIdentifier.SplitIdentifier); - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider) - { - // Invisible complex collider should not be seen - InMeshComponent->SetVisibility(false); - InMeshComponent->SetHiddenInGame(true); - InMeshComponent->SetCollisionProfileName(FName(TEXT("InvisibleWall"))); - InMeshComponent->SetCastShadow(false); - } - else - { - // Update visiblity - bool bVisible = InHGPO ? InHGPO->bIsVisible : true; - InMeshComponent->SetVisibility(bVisible); - InMeshComponent->SetHiddenInGame(!bVisible); - } - - // TODO: - // Update navmesh? - - // Transform the component by transformation provided by HAPI. - InMeshComponent->SetRelativeTransform(InHGPO->TransformMatrix); - - // If the static mesh had sockets, we can assign the desired actor to them now - UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); - UStaticMesh * StaticMesh = nullptr; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - StaticMesh = StaticMeshComponent->GetStaticMesh(); - - if (StaticMesh && !StaticMesh->IsPendingKill()) - { - 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; - - AddActorsToMeshSocket(StaticMesh->Sockets[nSocket], StaticMeshComponent, HoudiniCreatedSocketActors, HoudiniAttachedSocketActors); - } - - // Iterate all remaining created socket actors, destroy the ones that are not assigned to socket after re-cook - { - for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - continue; - } - - bool bFoundSocket = false; - for (auto & CurSocket : StaticMesh->Sockets) - { - if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) - { - bFoundSocket = true; - break; - } - } - // cur actor's attaching socket is found, skip - if (bFoundSocket) - continue; - - // Destroy the previous created socket actor if not found - HoudiniCreatedSocketActors.RemoveAt(Idx); - CurActor->Destroy(); - } - } - - // Detach the in level actors which is not attached to any socket now - { - for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor* CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniAttachedSocketActors.RemoveAt(Idx); - continue; - } - - bool bFoundSocket = false; - for (auto & CurSocket : StaticMesh->Sockets) - { - if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) - { - bFoundSocket = true; - break; - } - } - - if (bFoundSocket) - continue; - - // If the attached socket name is not found in current socket, detach it and remove from the array - CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - HoudiniAttachedSocketActors.RemoveAt(Idx); - } - } - - } - - if (bInApplyGenericProperties) - { - // Clear the component tags as generic properties only add them - InMeshComponent->ComponentTags.Empty(); - // Update the property attributes on the component - TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( - InOutputIdentifier.GeoId, InOutputIdentifier.PartId, - InOutputIdentifier.PointIndex, InOutputIdentifier.PrimitiveIndex, - PropertyAttributes)) - { - UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); - } - } -} - -bool -FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& InOutputObjects, - TMap& OutOutputObjects, - TMap& AssignmentMaterialMap, - TMap& ReplacementMaterialMap, - const bool& InForceRebuild, - EHoudiniStaticMeshMethod InStaticMeshMethod, - bool bInTreatExistingMaterialsAsUpToDate) -{ - // If we're not forcing the rebuild - // No need to recreate something that hasn't changed - if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged) && InOutputObjects.Num() > 0) - { - // Simply reuse the existing meshes - OutOutputObjects = InOutputObjects; - return true; - } - - FHoudiniMeshTranslator CurrentTranslator; - CurrentTranslator.ForceRebuild = InForceRebuild; - CurrentTranslator.SetHoudiniGeoPartObject(InHGPO); - CurrentTranslator.SetInputObjects(InOutputObjects); - CurrentTranslator.SetOutputObjects(OutOutputObjects); - CurrentTranslator.SetInputAssignmentMaterials(AssignmentMaterialMap); - CurrentTranslator.SetReplacementMaterials(ReplacementMaterialMap); - CurrentTranslator.SetPackageParams(InPackageParams, true); - CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); - - // TODO: Fetch from settings/HAC - CurrentTranslator.DefaultMeshSmoothing = 1; - if (false) - CurrentTranslator.DefaultMeshSmoothing = 0; - - // TODO: mechanism to determine when to use dynamic mesh for fast updates, and when to switch to - // baking the full static mesh - switch (InStaticMeshMethod) - { - case EHoudiniStaticMeshMethod::RawMesh: - CurrentTranslator.CreateStaticMesh_RawMesh(); - break; - case EHoudiniStaticMeshMethod::FMeshDescription: - CurrentTranslator.CreateStaticMesh_MeshDescription(); - break; - case EHoudiniStaticMeshMethod::UHoudiniStaticMesh: - CurrentTranslator.CreateHoudiniStaticMesh(); - break; - } - - // Copy the output objects/materials - OutOutputObjects = CurrentTranslator.OutputObjects; - AssignmentMaterialMap = CurrentTranslator.OutputAssignmentMaterials; - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartVertexList() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartVertexList")); - - if (HGPO.PartInfo.VertexCount <= 0) - return false; - - // Get the vertex List - PartVertexList.SetNumUninitialized(HGPO.PartInfo.VertexCount); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVertexList( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, &PartVertexList[0], 0, HGPO.PartInfo.VertexCount)) - { - // Error getting the vertex list. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - - return false; - } - - return true; -} - -void -FHoudiniMeshTranslator::SortSplitGroups() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::SortSplitGroups")); - - // Sort the splits in the order that we want to process them: - // Simple/Convex invisible colliders should be treated first as they will need to be attached to the visible meshes - TArray First; - - // The main geo and its LODs should be created after. - TArray Main; - TArray LODs; - - // Finally, visible colliders and invisible complex colliders as they need their own static mesh - TArray Last; - - for (auto& curSplit : HGPO.SplitGroups) - { - EHoudiniSplitType curSplitType = GetSplitTypeFromSplitName(curSplit); - switch (curSplitType) - { - case EHoudiniSplitType::InvisibleSimpleCollider: - case EHoudiniSplitType::InvisibleUCXCollider: - First.Add(curSplit); - break; - - case EHoudiniSplitType::Normal: - Main.Add(curSplit); - break; - - case EHoudiniSplitType::LOD: - LODs.Add(curSplit); - break; - - case EHoudiniSplitType::RenderedSimpleCollider: - case EHoudiniSplitType::RenderedUCXCollider: - case EHoudiniSplitType::RenderedComplexCollider: - case EHoudiniSplitType::InvisibleComplexCollider: - Last.Add(curSplit); - break; - } - } - - // Make sure LODs are order by name - LODs.Sort(); - - // Copy the split names in order - AllSplitGroups.Empty(); - for (auto& splitName : First) - AllSplitGroups.Add(splitName); - - for (auto& splitName : Main) - AllSplitGroups.Add(splitName); - - for (auto& splitName : LODs) - AllSplitGroups.Add(splitName); - - for (auto& splitName : Last) - AllSplitGroups.Add(splitName); -} - -bool -FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices")); - - // Reset the splits faces/indices arrays - AllSplitVertexLists.Empty(); - AllSplitVertexCounts.Empty(); - AllSplitFaceIndices.Empty(); - AllSplitFirstValidVertexIndex.Empty(); - AllSplitFirstValidPrimIndex.Empty(); - - bool bHasSplit = AllSplitGroups.Num() > 0; - if (bHasSplit) - { - HAPI_PartInfo PartInfo = FHoudiniEngineUtils::ToHAPIPartInfo(HGPO.PartInfo); - - // Buffer for all vertex indices used for split groups. - // We need this to figure out all vertex indices that are not part of them. - TArray AllVertexList; - AllVertexList.SetNumZeroed(PartVertexList.Num()); - - // Buffer for all face indices used for split groups. - // We need this to figure out all face indices that are not part of them. - TArray AllGroupFaceIndices; - AllGroupFaceIndices.SetNumZeroed(HGPO.PartInfo.FaceCount); - - // Some of the groups may contain invalid geometry - // Store them here so we can remove them afterwards - TArray InvalidGroupNameIndices; - - // Extract the vertices/faces for each of the split groups - for (int32 SplitIdx = 0; SplitIdx < AllSplitGroups.Num(); SplitIdx++) - { - const FString& GroupName = AllSplitGroups[SplitIdx]; - - // New vertex list just for this group. - TArray< int32 > GroupVertexList; - TArray< int32 > AllFaceList; - - int32 FirstValidPrimIndex = 0; - int32 FirstValidVertexIndex = 0; - // Extract vertex indices for this split. - int32 GroupVertexListCount = FHoudiniEngineUtils::HapiGetVertexListForGroup( - HGPO.GeoId, PartInfo, GroupName, - PartVertexList, GroupVertexList, - AllVertexList, AllFaceList, AllGroupFaceIndices, - FirstValidVertexIndex, FirstValidPrimIndex, - HGPO.PartInfo.bIsInstanced); - - if (GroupVertexListCount <= 0) - { - // This group doesn't have vertices/faces, mark it as invalid - InvalidGroupNameIndices.Add(SplitIdx); - - // Error getting the vertex list. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list for group %s - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, *GroupName); - - continue; - } - - // If list is not empty, we store it for this group - this will define new mesh. - AllSplitVertexLists.Add(GroupName, GroupVertexList); - AllSplitVertexCounts.Add(GroupName, GroupVertexListCount); - AllSplitFaceIndices.Add(GroupName, AllFaceList); - AllSplitFirstValidVertexIndex.Add(GroupName, FirstValidPrimIndex); - AllSplitFirstValidPrimIndex.Add(GroupName, FirstValidVertexIndex); - } - - if (InvalidGroupNameIndices.Num() > 0) - { - // Remove all invalid split groups - for (int32 InvalIdx = InvalidGroupNameIndices.Num() - 1; InvalIdx >= 0; InvalIdx--) - { - int32 Index = InvalidGroupNameIndices[InvalIdx]; - AllSplitGroups.RemoveAt(Index); - } - } - - // We also need to figure out / construct the vertex list for everything that's not in a split group - TArray GroupSplitFacesRemaining; - GroupSplitFacesRemaining.Init(-1, PartVertexList.Num()); - - int32 GroupVertexListCount = 0; - bool bHasMainSplitGroup = false; - TArray< int32 > GroupSplitFaceIndicesRemaining; - int32 FistUnusedVertexIndex = -1; - for (int32 SplitVertexIdx = 0; SplitVertexIdx < AllVertexList.Num(); SplitVertexIdx++) - { - if (AllVertexList[SplitVertexIdx] == 0) - { - // This is an unused index, we need to add it to unused vertex list. - FistUnusedVertexIndex = SplitVertexIdx; - GroupSplitFacesRemaining[SplitVertexIdx] = PartVertexList[SplitVertexIdx]; - bHasMainSplitGroup = true; - GroupVertexListCount++; - } - } - - int32 FistUnusedPrimIndex = -1; - for (int32 SplitFaceIdx = 0; SplitFaceIdx < AllGroupFaceIndices.Num(); SplitFaceIdx++) - { - if (AllGroupFaceIndices[SplitFaceIdx] == 0) - { - // This is unused face, we need to add it to unused faces list. - GroupSplitFaceIndicesRemaining.Add(SplitFaceIdx); - FistUnusedPrimIndex = SplitFaceIdx; - } - } - - // We store the remaining geo vertex list as a special split named "main geo" - // and make sure its treated before the collider meshes - if (bHasMainSplitGroup) - { - static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - AllSplitGroups.Add(RemainingGroupName); - AllSplitVertexLists.Add(RemainingGroupName, GroupSplitFacesRemaining); - AllSplitVertexCounts.Add(RemainingGroupName, GroupVertexListCount); - AllSplitFaceIndices.Add(RemainingGroupName, GroupSplitFaceIndicesRemaining); - AllSplitFirstValidPrimIndex.Add(RemainingGroupName, FistUnusedPrimIndex); - AllSplitFirstValidVertexIndex.Add(RemainingGroupName, FistUnusedVertexIndex); - } - } - else - { - // No splitting required - // Mark everything as the main geo group - static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - AllSplitGroups.Add(RemainingGroupName); - AllSplitVertexLists.Add(RemainingGroupName, PartVertexList); - AllSplitVertexCounts.Add(RemainingGroupName, PartVertexList.Num()); - AllSplitFirstValidPrimIndex.Add(RemainingGroupName, 0); - AllSplitFirstValidVertexIndex.Add(RemainingGroupName, 0); - - TArray AllFaces; - for (int32 FaceIdx = 0; FaceIdx < HGPO.PartInfo.FaceCount; ++FaceIdx) - AllFaces.Add(FaceIdx); - - AllSplitFaceIndices.Add(RemainingGroupName, AllFaces); - } - - return true; -} - -void -FHoudiniMeshTranslator::ResetPartCache() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::ResetPartCache")); - - // Vertex Positions - PartPositions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Vertex Normals - PartNormals.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Vertex TangentU - PartTangentU.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentU); - - // Vertex TangentV - PartTangentV.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentV); - - // Vertex Colors - PartColors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoColors); - - // Vertex Alpha values - PartAlphas.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoAlpha); - - // FaceSmoothing values - PartFaceSmoothingMasks.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceSmoothingMasks); - - // UVs - PartUVSets.Empty(); - AttribInfoUVSets.Empty(); - - // UVs - PartLightMapResolutions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoLightmapResolution); - - // Material IDs per face - PartFaceMaterialIds.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialIds); - // Unique material IDs - PartUniqueMaterialIds.Empty(); - // Material infos for each unique Material - PartUniqueMaterialInfos.Empty(); - // - bOnlyOneFaceMaterial = false; - - // Face Materials override - PartFaceMaterialOverrides.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialOverrides); - bMaterialOverrideNeedsCreateInstance = false; - - // LOD Screensize - PartLODScreensize.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoLODScreensize); -} - -bool -FHoudiniMeshTranslator::UpdatePartPositionIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartPositionIfNeeded")); - - // Only Retrieve the vertices positions if necessary - if (PartPositions.Num() > 0) - return true; - - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve position data") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded")); - - // No need to read the normals if we want unreal to recompute them after - bool bReadNormals = true; - // TODO: Add runtime setting check! - //bool bReadNormals = HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; - if (!bReadNormals) - return true; - - // Only Retrieve the normals if we haven't already - if (PartNormals.Num() > 0) - return true; - - // Retrieve normal data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, PartNormals); - - // There is no normals to fetch - if (!AttribInfoNormals.exists) - return true; - - if (!Success && AttribInfoNormals.exists) - { - // Error retrieving normals. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve normal data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded")) - - bool bReturn = true; - if (PartTangentU.Num() <= 0) - { - // Retrieve TangentU data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_TANGENTU, AttribInfoTangentU, PartTangentU); - - if (!Success && AttribInfoTangentU.exists) - { - // Error retrieving tangent. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentU data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - bReturn = false; - } - } - - if (PartTangentV.Num() <= 0) - { - // Retrieve TangentV data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_TANGENTV, AttribInfoTangentV, PartTangentV); - - if (!Success && AttribInfoTangentV.exists) - { - // Error retrieving tangent. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentV data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - bReturn = false; - } - } - - return bReturn; -} - -bool -FHoudiniMeshTranslator::UpdatePartColorsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartColorsIfNeeded")); - - // Only Retrieve the vertices colors if necessary - if (PartColors.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_COLOR, AttribInfoColors, PartColors); - - if (!Success && AttribInfoColors.exists) - { - // Error retrieving colors. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve color data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded")); - - // Only Retrieve the vertices alphas if necessary - if (PartAlphas.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_ALPHA, AttribInfoAlpha, PartAlphas); - - if (!Success && AttribInfoAlpha.exists) - { - // Error retrieving alpha values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve alpha data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceSmoothingIfNeeded() -{ - // Only Retrieve the vertices FaceSmoothing if necessary - if (PartFaceSmoothingMasks.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, - AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks); - - if (!Success && AttribInfoFaceSmoothingMasks.exists) - { - // Error retrieving FaceSmoothing values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve FaceSmoothing data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded")); - - // Only Retrieve uvs if necessary - if (PartUVSets.Num() > 0) - return true; - - PartUVSets.SetNum(MAX_STATIC_TEXCOORDS); - AttribInfoUVSets.SetNum(MAX_STATIC_TEXCOORDS); - - // The second UV set should be called uv2, but we will still check if need to look for a uv1 set. - // If uv1 exists, we'll look for uv, uv1, uv2 etc.. if not we'll look for uv, uv2, uv3 etc.. - bool bUV1Exists = FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, "uv1"); - - // Retrieve UVs. - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (TexCoordIdx > 0) - UVAttributeName += FString::Printf(TEXT("%d"), bUV1Exists ? TexCoordIdx : TexCoordIdx + 1); - - FHoudiniApi::AttributeInfo_Init(&AttribInfoUVSets[TexCoordIdx]); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*UVAttributeName), - AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], 2); - } - - // Also look for 16.5 uvs (attributes with a Texture type) - // For that, we'll have to iterate through ALL the attributes and check their types - TArray< FString > FoundAttributeNames; - TArray< HAPI_AttributeInfo > FoundAttributeInfos; - - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - FHoudiniEngineUtils::HapiGetAttributeOfType( - HGPO.GeoId, HGPO.PartId, (HAPI_AttributeOwner)AttrIdx, - HAPI_ATTRIBUTE_TYPE_TEXTURE, FoundAttributeInfos, FoundAttributeNames); - } - - if (FoundAttributeInfos.Num() <= 0) - return true; - - // We found some additionnal uv attributes - int32 AvailableIdx = 0; - for (int32 attrIdx = 0; attrIdx < FoundAttributeInfos.Num(); attrIdx++) - { - // Ignore the old uvs - if (FoundAttributeNames[attrIdx] == TEXT("uv") - || FoundAttributeNames[attrIdx] == TEXT("uv1") - || FoundAttributeNames[attrIdx] == TEXT("uv2") - || FoundAttributeNames[attrIdx] == TEXT("uv3") - || FoundAttributeNames[attrIdx] == TEXT("uv4") - || FoundAttributeNames[attrIdx] == TEXT("uv5") - || FoundAttributeNames[attrIdx] == TEXT("uv6") - || FoundAttributeNames[attrIdx] == TEXT("uv7") - || FoundAttributeNames[attrIdx] == TEXT("uv8")) - continue; - - HAPI_AttributeInfo CurrentAttrInfo = FoundAttributeInfos[attrIdx]; - if (!CurrentAttrInfo.exists) - continue; - - // Look for the next available index in the return arrays - for (; AvailableIdx < AttribInfoUVSets.Num(); AvailableIdx++) - { - if (!AttribInfoUVSets[AvailableIdx].exists) - break; - } - - // We are limited to MAX_STATIC_TEXCOORDS uv sets! - // If we already have too many uv sets, skip the rest - if ((AvailableIdx >= MAX_STATIC_TEXCOORDS) || (AvailableIdx >= AttribInfoUVSets.Num())) - { - HOUDINI_LOG_WARNING(TEXT("Too many UV sets found. Unreal only supports %d , skipping the remaining uv sets."), (int32)MAX_STATIC_TEXCOORDS); - break; - } - - // Force the tuple size to 2 ? - CurrentAttrInfo.tupleSize = 2; - - // Add the attribute infos we found - AttribInfoUVSets[AvailableIdx] = CurrentAttrInfo; - - // Allocate sufficient buffer for the attribute's data. - PartUVSets[AvailableIdx].SetNumUninitialized(CurrentAttrInfo.count * CurrentAttrInfo.tupleSize); - - // Get the texture coordinates - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, TCHAR_TO_UTF8(*(FoundAttributeNames[attrIdx])), - &AttribInfoUVSets[AvailableIdx], -1, - &PartUVSets[AvailableIdx][0], 0, CurrentAttrInfo.count)) - { - // Something went wrong when trying to access the uv values, invalidate this set - AttribInfoUVSets[AvailableIdx].exists = false; - } - } - - // Remove unused UV sets - if (bRemoveUnused) - { - for (int32 Idx = PartUVSets.Num() - 1; Idx >= 0; Idx--) - { - if (PartUVSets[Idx].Num() > 0) - continue; - - PartUVSets.RemoveAt(Idx); - } - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartLightmapResolutionsIfNeeded() -{ - // Only Retrieve the vertices lightmap resolution if necessary - if (PartLightMapResolutions.Num() > 0) - return true; - - // Get lightmap resolution (if present). - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, - AttribInfoLightmapResolution, PartLightMapResolutions); - - if (!Success && AttribInfoLightmapResolution.exists) - { - // Error retrieving lightmap resolution values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve lightmap resolution data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded")); - - // Only Retrieve the material IDs if necessary - if (PartFaceMaterialIds.Num() > 0) - return true; - - int32 NumFaces = HGPO.PartInfo.FaceCount; - if (NumFaces <= 0) - return true; - - PartFaceMaterialIds.SetNum(NumFaces); - - // Get the materials IDs per face - HAPI_Bool bSingleFaceMaterial = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, &bSingleFaceMaterial, - &PartFaceMaterialIds[0], 0, NumFaces)) - { - // Error retrieving material face assignments. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material face assignments"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - bOnlyOneFaceMaterial = bSingleFaceMaterial; - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded")); - - // Only Retrieve the material overrides if necessary - if (PartFaceMaterialOverrides.Num() > 0) - return true; - - bMaterialOverrideNeedsCreateInstance = false; - - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - - // If material attribute was not found, check fallback compatibility attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - } - - // If material attribute and fallbacks were not found, check the material instance attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - - // We will we need to create material instances from the override attributes - bMaterialOverrideNeedsCreateInstance = AttribInfoFaceMaterialOverrides.exists; - } - - if (AttribInfoFaceMaterialOverrides.exists - && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_PRIM - && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_DETAIL) - { - HOUDINI_LOG_WARNING(TEXT("Static Mesh [%d %s], Geo [%d], Part [%d %s]: unreal_material must be a primitive or detail attribute, ignoring attribute."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - AttribInfoFaceMaterialOverrides.exists = false; - bMaterialOverrideNeedsCreateInstance = false; - PartFaceMaterialOverrides.Empty(); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartNeededMaterials() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials")); - - // Update the per face material IDs - UpdatePartFaceMaterialIDsIfNeeded(); - - // See if we have some material overides - UpdatePartFaceMaterialOverridesIfNeeded(); - - // If we have houdini materials AND overrides: - // We want to only create the Houdini materials that are not "covered" by overrides - // If we have material instance attributes, create all the houdini material anyway - // as their textures could be referenced by the material instance parameters - if (PartFaceMaterialOverrides.Num() > 0 && !bMaterialOverrideNeedsCreateInstance) - { - // If the material override was set on the detail, no need to look for houdini material IDs, as only the override will be used - if (AttribInfoFaceMaterialOverrides.exists && AttribInfoFaceMaterialOverrides.owner == HAPI_ATTROWNER_PRIM) - { - for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) - { - // Add a material ID to the unique array only if that face is not using the override - if (PartFaceMaterialOverrides[MaterialIdx].IsEmpty()) - PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); - } - } - } - else - { - // No material overrides, simply update the unique material array - for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) - PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); - } - - // Remove the invalid material ID from the unique array - PartUniqueMaterialIds.RemoveSingle(-1); - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials - Get the unique material infos")); - // Get the unique material infos - PartUniqueMaterialInfos.SetNum(PartUniqueMaterialIds.Num()); - for (int32 MaterialIdx = 0; MaterialIdx < PartUniqueMaterialIds.Num(); MaterialIdx++) - { - - FHoudiniApi::MaterialInfo_Init(&PartUniqueMaterialInfos[MaterialIdx]); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialInfo( - FHoudiniEngine::Get().GetSession(), - PartUniqueMaterialIds[MaterialIdx], - &PartUniqueMaterialInfos[MaterialIdx])) - { - // Error retrieving material face assignments. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material info for material %d"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, PartUniqueMaterialIds[MaterialIdx]); - continue; - } - } - } - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartLODScreensizeIfNeeded() -{ - // Only retrieve LOD screensizes if necessary - if (PartLODScreensize.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE, - AttribInfoLODScreensize, PartLODScreensize); - - if (!Success && AttribInfoLODScreensize.exists) - { - // Error retrieving FaceSmoothing values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve LOD screensizes"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - - -UStaticMesh* -FHoudiniMeshTranslator::CreateNewStaticMesh(const FString& InSplitIdentifier) -{ - // Update the current Obj/Geo/Part/Split IDs - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.GeoId; - PackageParams.PartId = HGPO.PartId; - PackageParams.SplitStr = InSplitIdentifier; - - UStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) - return nullptr; - - return NewStaticMesh; -} - -UHoudiniStaticMesh* -FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentifier) -{ - // Update the current Obj/Geo/Part/Split IDs - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.GeoId; - PackageParams.PartId = HGPO.PartId; - // Add _HSM suffix to the split str, to distinguish the temporary HoudiniStaticMesh - // from the UStaticMesh - PackageParams.SplitStr = InSplitIdentifier + "_HSM"; - - UHoudiniStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) - return nullptr; - - return NewStaticMesh; -} - -bool -FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() -{ - double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Prepare the object that will store UCX and simple colliders - AllAggregateCollisions.Empty(); - - // We need to know the number of LODs that will be needed for this part - int32 NumberOfLODs = 0; - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) - NumberOfLODs++; - else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - bHasMainGeo = true; - } - - // Update the part's material's IDS and info now - CreateNeededMaterials(); - - // Check now if they were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Get the current target platform for default lod policies - ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); - check(CurrentPlatform); - - // New mesh list - TMap StaticMeshToBuild; - - // Map of Houdini Material IDs to Unreal Material Indices - TMap MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap MapHoudiniMatAttributesToUnrealIndex; - - bool MeshMaterialsHaveBeenReset = false; - - // Mesh Socket array - TArray AllSockets; - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - - // Iterate through all detected split groups we care about and split geometry. - // The split are ordered in the following way: - // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - - // Get/Create the Aggregate Collisions for this mesh identifier - FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); - - // Handle UCX / Convex Hull colliders - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) - { - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the convex hull colliders and add them to the Aggregate - if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) - continue; - } - else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) - { - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the simple colliders and add them to the aggregate - if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) - continue; - } - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing SM from a previous cook - UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - // Prepare LOD Group data for this static mesh - FStaticMeshLODGroup LODGroup; - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing static mesh, create a new one - FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - - // Use the platform's default LODGroup policy - // TODO? Add setting for default LOD Group? - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); - } - else - { - // Try to reuse the existing SM's LOD group instead of the default one - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); - } - - if (!FoundOutputObject) - { - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - InputObjects.Remove(OutputObjectIdentifier); - } - else - { - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - FoundOutputObject->CachedAttributes.Empty(); - FoundOutputObject->CachedTokens.Empty(); - } - FoundOutputObject->bProxyIsCurrent = false; - - // TODO: Needed? - // Free any RHI resources for existing mesh before we re-create in place. - FoundStaticMesh->PreEditChange(NULL); - - // Check that the Static Mesh we found has the appropriate number of Source models/LODs - int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); - - // LODs are only for the "main" mesh, not for complex colliders! - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); - - if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) - { - while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) - FoundStaticMesh->AddSourceModel(); - - // We may have to remove excessive LOD levels - if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) - FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); - - // Initialize their default reduction setting - for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) - { - FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); - } - FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); - } - - // By default, always work on the first source model, unless we're a LOD - int32 SrcModelIndex = 0; - int32 LODIndex = 0; - if (SplitType == EHoudiniSplitType::LOD) - { - for (auto& curSplit : AllSplitGroups) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); - if (CurrentSplitType == EHoudiniSplitType::LOD - || CurrentSplitType == EHoudiniSplitType::Normal) - { - LODIndex++; - } - - if (curSplit == SplitGroupName) - break; - } - - // Fix for the case where we don't have a main geo - if(!bHasMainGeo) - LODIndex--; - } - - // Grab the appropriate SourceModel - FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; - if (!SrcModel) - { - HOUDINI_LOG_ERROR( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); - continue; - } - - // Load existing raw model. This will be empty as we are constructing a new mesh. - FRawMesh RawMesh; - if (!bRebuildStaticMesh) - { - // We dont need to rebuild the mesh itself: - // the geometry hasn't changed, but the materials have. - // We can just load the old data into the Raw mesh and reuse it. - SrcModel->LoadRawMesh(RawMesh); - } - else - { - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's normal if needed - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - // Check that the number of normal we retrieved is correct - int32 WedgeNormalCount = SplitNormals.Num() / 3; - if (SplitNormals.Num() < 0 || !SplitNormals.IsValidIndex((WedgeNormalCount - 1) * 3 + 2)) - { - // Ignore normals - WedgeNormalCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); - } - - // Transfer the normals to the raw mesh - RawMesh.WedgeTangentZ.SetNumZeroed(WedgeNormalCount); - for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) - { - // Swap Y/Z for Coordinates conversion - RawMesh.WedgeTangentZ[WedgeTangentZIdx].X = SplitNormals[WedgeTangentZIdx * 3 + 0]; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].Y = SplitNormals[WedgeTangentZIdx * 3 + 2]; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].Z = SplitNormals[WedgeTangentZIdx * 3 + 1]; - } - - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENTS - //--------------------------------------------------------------------------------------------------------------------- - - // No need to read the tangents if we want unreal to recompute them after - bool bReadTangents = true; - // TODO: Add runtime setting check! - //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - TArray< float > SplitTangentU; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - TArray< float > SplitTangentV; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - bool bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - - // Check that the number of tangents read matches the number of normals - int32 WedgeTangentUCount = SplitTangentU.Num() / 3; - int32 WedgeTangentVCount = SplitTangentV.Num() / 3; - if (WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount) - bGenerateTangents = true; - - /* - // TODO: Add settings check! - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - */ - - // Generate the tangents if needed - if (bGenerateTangents) - { - RawMesh.WedgeTangentX.SetNumZeroed(WedgeNormalCount); - RawMesh.WedgeTangentY.SetNumZeroed(WedgeNormalCount); - for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) - { - FVector TangentX, TangentY; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].FindBestAxisVectors(TangentX, TangentY); - - RawMesh.WedgeTangentX[WedgeTangentZIdx] = TangentX; - RawMesh.WedgeTangentY[WedgeTangentZIdx] = TangentY; - } - } - else - { - // Transfer the tangents we have read them and they're valid - RawMesh.WedgeTangentX.SetNumZeroed(WedgeTangentUCount); - for (int32 WedgeTangentUIdx = 0; WedgeTangentUIdx < WedgeTangentUCount; ++WedgeTangentUIdx) - { - // We need to flip Z and Y - RawMesh.WedgeTangentX[WedgeTangentUIdx].X = SplitTangentU[WedgeTangentUIdx * 3 + 0]; - RawMesh.WedgeTangentX[WedgeTangentUIdx].Y = SplitTangentU[WedgeTangentUIdx * 3 + 2]; - RawMesh.WedgeTangentX[WedgeTangentUIdx].Z = SplitTangentU[WedgeTangentUIdx * 3 + 1]; - } - - RawMesh.WedgeTangentY.SetNumZeroed(WedgeTangentVCount); - for (int32 WedgeTangentVIdx = 0; WedgeTangentVIdx < WedgeTangentVCount; ++WedgeTangentVIdx) - { - // We need to flip Z and Y - RawMesh.WedgeTangentY[WedgeTangentVIdx].X = SplitTangentV[WedgeTangentVIdx * 3 + 0]; - RawMesh.WedgeTangentY[WedgeTangentVIdx].Y = SplitTangentV[WedgeTangentVIdx * 3 + 2]; - RawMesh.WedgeTangentY[WedgeTangentVIdx].Z = SplitTangentV[WedgeTangentVIdx * 3 + 1]; - } - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // VERTEX COLORS AND ALPHAS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's colors if needed - UpdatePartColorsIfNeeded(); - - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract this part's alpha values if needed - UpdatePartAlphasIfNeeded(); - - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - - // Transfer colors and alphas if possible - int32 WedgeColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; - bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && WedgeColorsCount > 0; - bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == WedgeColorsCount); - if (bSplitColorValid) - { - RawMesh.WedgeColors.SetNumZeroed(WedgeColorsCount); - for (int32 WedgeColorIdx = 0; WedgeColorIdx < WedgeColorsCount; WedgeColorIdx++) - { - FLinearColor WedgeColor; - WedgeColor.R = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); - WedgeColor.G = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); - WedgeColor.B = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); - - if (bSplitAlphaValid) - { - // Use the Alpha attribute value - WedgeColor.A = FMath::Clamp(SplitAlphas[WedgeColorIdx], 0.0f, 1.0f); - } - else if (AttribInfoColors.tupleSize >= 4) - { - // Use the alpha value from the color attribute - WedgeColor.A = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); - } - else - { - WedgeColor.A = 1.0f; - } - - // Convert linear color to fixed color. - RawMesh.WedgeColors[WedgeColorIdx] = WedgeColor.ToFColor(false); - } - } - else - { - // TODO? Needed? New meshes wont have WedgeIndices yet!? - // No Colors or Alphas, init colors to White - FColor DefaultWedgeColor = FLinearColor::White.ToFColor(false); - WedgeColorsCount = RawMesh.WedgeIndices.Num(); - if (WedgeColorsCount > 0) - RawMesh.WedgeColors.Init(DefaultWedgeColor, WedgeColorsCount); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's FaceSmoothing values if needed - UpdatePartFaceSmoothingIfNeeded(); - - // Get the FaceSmoothing values for this split - TArray SplitFaceSmoothingMasks; - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); - - // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! - RawMesh.FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); - - // Check that the number of face smoothing values we retrieved is correct - int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; - if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) - { - // Ignore our face smoothing values - WedgeFaceSmoothCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); - } - - // Transfer the face smoothing masks to the raw mesh if we have any - for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) - { - RawMesh.FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; - } - - //--------------------------------------------------------------------------------------------------------------------- - // UVS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's UV sets if needed - UpdatePartUVSetsIfNeeded(); - - // See if we need to transfer uv point attributes to vertex attributes. - TArray> SplitUVSets; - SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - } - - // Transfer UVs to the Raw Mesh - int32 UVChannelCount = 0; - int32 LightMapUVChannel = 0; - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; - - int32 WedgeUVCount = SplitUVs.Num() / 2; - if (SplitUVs.Num() > 0 && SplitUVs.IsValidIndex((WedgeUVCount - 1) * 2 + 1)) - { - RawMesh.WedgeTexCoords[TexCoordIdx].SetNumZeroed(WedgeUVCount); - for (int32 WedgeUVIdx = 0; WedgeUVIdx < WedgeUVCount; ++WedgeUVIdx) - { - // We need to flip V coordinate when it's coming from HAPI. - RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].X = SplitUVs[WedgeUVIdx * 2 + 0]; - RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].Y = 1.0f - SplitUVs[WedgeUVIdx * 2 + 1]; - } - - UVChannelCount++; - if (UVChannelCount <= 2) - LightMapUVChannel = TexCoordIdx; - } - else - { - RawMesh.WedgeTexCoords[TexCoordIdx].Empty(); - } - } - - // We must have at least one UV channel. If there's none, create one filled with zero data. - if (UVChannelCount == 0) - RawMesh.WedgeTexCoords[0].SetNumZeroed(SplitVertexCount); - - // Set the lightmap Coordinate Index - // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention - // If not, the first UV set will be used - FoundStaticMesh->LightMapCoordinateIndex = LightMapUVChannel; - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's LightmapResolution values if needed - UpdatePartLightmapResolutionsIfNeeded(); - - // make sure the mesh has a new lighting guid - FoundStaticMesh->LightingGuid = FGuid::NewGuid(); - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" - // So that IndicesMapper[ oldIndex ] => newIndex - TArray IndicesMapper; - IndicesMapper.Init(-1, SplitVertexList.Num()); - int32 CurrentMapperIndex = 0; - - // NeededVertices: - // Array containing the old index of the needed vertices for the current split - // NeededVertices[ newIndex ] => oldIndex - TArray< int32 > NeededVertices; - RawMesh.WedgeIndices.SetNumZeroed(SplitVertexCount); - - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) - || !IndicesMapper.IsValidIndex(WedgeIndices[1]) - || !IndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (IndicesMapper[WedgeIndices[i]] < 0) - { - // This old index has not yet been "converted" to a new index - NeededVertices.Add(WedgeIndices[i]); - IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; - CurrentMapperIndex++; - } - - // Replace the old index with the new one - WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; - } - - if (!RawMesh.WedgeIndices.IsValidIndex(ValidVertexId + 2)) - break; - - // Flip wedge indices to fix the winding order. - RawMesh.WedgeIndices[ValidVertexId + 0] = WedgeIndices[0]; - RawMesh.WedgeIndices[ValidVertexId + 1] = WedgeIndices[2]; - RawMesh.WedgeIndices[ValidVertexId + 2] = WedgeIndices[1]; - - // Check if we need to patch UVs. - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - if (RawMesh.WedgeTexCoords[TexCoordIdx].IsValidIndex(ValidVertexId + 2)) - { - Swap(RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 1], - RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 2]); - } - } - - // Check if we need to patch colors. - if (RawMesh.WedgeColors.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeColors[ValidVertexId + 1], RawMesh.WedgeColors[ValidVertexId + 2]); - - // Check if we need to patch Normals and tangents. - if (RawMesh.WedgeTangentZ.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentZ[ValidVertexId + 1], RawMesh.WedgeTangentZ[ValidVertexId + 2]); - - if (RawMesh.WedgeTangentX.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentX[ValidVertexId + 1], RawMesh.WedgeTangentX[ValidVertexId + 2]); - - if (RawMesh.WedgeTangentY.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentY[ValidVertexId + 1], RawMesh.WedgeTangentY[ValidVertexId + 2]); - - ValidVertexId += 3; - } - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - UpdatePartPositionIfNeeded(); - - // - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - int32 VertexPositionsCount = NeededVertices.Num(); - RawMesh.VertexPositions.SetNumZeroed(VertexPositionsCount); - - for (int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx) - { - int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; - if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // We need to swap Z and Y coordinate here, and convert from m to cm. - RawMesh.VertexPositions[VertexPositionIdx].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - RawMesh.VertexPositions[VertexPositionIdx].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - RawMesh.VertexPositions[VertexPositionIdx].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - /* - // TODO: - // Check if this mesh contains only degenerate triangles. - if (FHoudiniEngineUtils::CountDegenerateTriangles(RawMesh) == SplitGroupFaceCount) - { - // This mesh contains only degenerate triangles, there's nothing we can do. - if (bStaticMeshCreated) - StaticMesh->MarkPendingKill(); - - continue; - } - */ - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL ATTRIBUTE OVERRIDES - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: These are actually per faces, not per vertices... - // Need to update!! - UpdatePartFaceMaterialOverridesIfNeeded(); - - //--------------------------------------------------------------------------------------------------------------------- - // FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Handle Materials!!!! - - // Get face indices for this split. - TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; - - // We need to reset the Static Mesh's materials once per SM: - // so, for the first lod, or the main geo... - if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) - { - FoundStaticMesh->StaticMaterials.Empty(); - MeshMaterialsHaveBeenReset = true; - } - - // .. or for each visible complex collider - if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - FoundStaticMesh->StaticMaterials.Empty(); - - // Process material overrides first - if (PartFaceMaterialOverrides.Num() > 0) - { - // If the part has material overrides - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - continue; - - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); - int32 CurrentFaceMaterialIdx = 0; - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; - - // Start by looking in our assignment map - auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface && !MaterialName.IsEmpty()) - { - // Only try to load a material if has a chance to be valid! - MaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); - } - else - { - // The Attribute Material and its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else - { - // If everything fails, we'll use the default material - MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); - } - } - } - - // Update the Face Material on the mesh - RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; - } - } - else if (PartUniqueMaterialIds.Num() > 0) - { - // The part has houdini materials - if (bOnlyOneFaceMaterial) - { - // We have only one material. - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - else - { - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - // Reset Rawmesh material face assignments. - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; - } - - UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Add the material to the Static mesh - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); - - // Update the face index - RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; - } - } - } - else - { - // No materials were found, we need to use default Houdini material. - int32 SplitFaceCount = SplitFaceIndices.Num(); - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceCount); - - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - - // TODO: - // BUILD SETTINGS - // (Using default for now) - SrcModel->BuildSettings.bRemoveDegenerates = true; - SrcModel->BuildSettings.bUseMikkTSpace = true; - SrcModel->BuildSettings.bBuildAdjacencyBuffer = false; - SrcModel->BuildSettings.MinLightmapResolution = 64; - SrcModel->BuildSettings.bUseFullPrecisionUVs = false; - SrcModel->BuildSettings.SrcLightmapIndex = 0; - SrcModel->BuildSettings.DstLightmapIndex = 1; - SrcModel->BuildSettings.bRecomputeNormals = (0 == RawMesh.WedgeTangentZ.Num()); - SrcModel->BuildSettings.bRecomputeTangents = (0 == RawMesh.WedgeTangentX.Num() || 0 == RawMesh.WedgeTangentY.Num()); - SrcModel->BuildSettings.bGenerateLightmapUVs = RawMesh.WedgeTexCoords->Num() <= 0; - - // Check for a lightmap resolution override - int32 LightMapResolutionOverride = -1; - if (PartLightMapResolutions.Num() > 0) - LightMapResolutionOverride = PartLightMapResolutions[0]; - - if (LightMapResolutionOverride > 0) - FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; - else - FoundStaticMesh->LightMapResolution = 64; - - // TODO: - // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - - // By default the distance field resolution should be set to 2.0 - // TODO should come from the HAC - //SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniCookParams.GeneratedDistanceFieldResolutionScale; - SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; - - // This is required due to the impeding deprecation of FRawMesh - // If we dont update this UE4 will crash upon deleting an asset. - SrcModel->StaticMeshOwner = FoundStaticMesh; - // Store the new raw mesh. - SrcModel->SaveRawMesh(RawMesh); - - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = GetLODSCreensizeForSplit(SplitGroupName); - if (screensize >= 0.0f) - { - // Only apply the LOD screensize if it's valid - SrcModel->ScreenSize = screensize; - //FoundStaticMesh->GetSourceModel(LODIndex).ScreenSize = screensize; - FoundStaticMesh->bAutoComputeLODScreenSize = false; - } - - // TODO: - // SET STATIC MESH GENERATION PARAM - // HANDLE COLLIDERS - // REMOVE OLD COLLIDERS - // CUSTOM BAKE NAME OVERRIDE - - // Update property attributes on the SM - TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( - HGPO.GeoId, HGPO.PartId, - AllSplitFirstValidVertexIndex[SplitGroupName], - AllSplitFirstValidPrimIndex[SplitGroupName], - PropertyAttributes)) - { - UpdateGenericPropertiesAttributes( - FoundStaticMesh, PropertyAttributes); - } - - TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - { - // cache the level path attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); - } - } - - TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - // cache the output name attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); - } - } - - TArray TileValues; - if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); - } - } - - TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) - { - if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); - } - } - - TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) - { - if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); - } - } - - // Notify that we created a new Static Mesh if needed - if (bNewStaticMeshCreated) - FAssetRegistryModule::AssetCreated(FoundStaticMesh); - - // Add the Static mesh to the output maps and the build map if we haven't already - if (FoundOutputObject) - { - FoundOutputObject->OutputObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = false; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - - StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); - } - - // Look if we only have colliders - // If we do, we'll allow attaching sockets to the collider meshes - bool bCollidersOnly = true; - for (auto& Current : StaticMeshToBuild) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) - { - bCollidersOnly = false; - break; - } - } - - FHoudiniScopedGlobalSilence ScopedGlobalSilence; - for (auto& Current : StaticMeshToBuild) - { - UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) - continue; - - UBodySetup * BodySetup = SM->BodySetup; - if (!BodySetup) - { - SM->CreateBodySetup(); - BodySetup = SM->BodySetup; - } - - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - - // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) - { - // Make sure rendering is done - so we are not changing data being used by collision drawing. - FlushRenderingCommands(); - - // Clean up old colliders from a previous cook - BodySetup->Modify(); - BodySetup->RemoveSimpleCollision(); - // Create new GUID - BodySetup->InvalidatePhysicsData(); - - FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; - FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); - if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) - { - BodySetup->AddCollisionFrom(*CurrentAggColl); - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; - } - - RefreshCollisionChange(*SM); - SM->bCustomizedCollision = true; - - // See if we need to enable collisions on the whole mesh - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - // Complex collider, enable collisions for this static mesh. - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - else - { - // TODO - // if the LODForCollision uproperty attribute is set, we need to activate complex collision - // on the static mesh for that lod to be picked up properly as a collider - if (FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, - "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) - { - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - } - } - - // Add the Sockets to the StaticMesh - // We only add them to the main geo, or to the colliders if we only generate colliders - bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; - if (bAddSocket) - { - if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); - } - } - - // BUILD the Static Mesh - // bSilent doesnt add the Build Errors... - double build_start = FPlatformTime::Seconds(); - TArray SMBuildErrors; - SM->Build(true, &SMBuildErrors); - double build_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); - - SM->GetOnMeshChanged().Broadcast(); - - /* - // Try to find the outer package so we can dirty it up - if (SM->GetOuter()) - { - SM->GetOuter()->MarkPackageDirty(); - } - else - { - SM->MarkPackageDirty(); - } - */ - - - UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - TArray PackageToSave; - PackageToSave.Add(MeshPackage); - - // Save the created package - FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); - */ - } - } - - // TODO: Still necessary ? SM->Build should actually update the navmesh... - // Now that all the meshes are built and their collisions meshes and primitives updated, - // we need to update their pre-built navigation collision used by the navmesh - for (auto& Iter : OutputObjects) - { - UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) - continue; - - UBodySetup * BodySetup = StaticMesh->BodySetup; - if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) - { - // Unreal caches the Navigation Collision and never updates it for StaticMeshes, - // so we need to manually flush and recreate the data to have proper navigation collision - BodySetup->InvalidatePhysicsData(); - BodySetup->CreatePhysicsMeshes(); - StaticMesh->NavCollision->Setup(BodySetup); - } - } - - double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() executed in %f seconds."), time_end - time_start); - - return true; -} - -bool -FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() -{ - double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - // Simple colliders first, lods and finally, invisible colliders (that are separate Static Mesh) - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Prepare the object that will store UCX and simple colliders - AllAggregateCollisions.Empty(); - - // We need to know the number of LODs that will be needed for this part - int32 NumberOfLODs = 0; - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) - NumberOfLODs++; - else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - bHasMainGeo = true; - } - - // Update the part's material's IDS and info now - CreateNeededMaterials(); - - // Check if the materials were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Get the current target platform for default lod policies - ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); - check(CurrentPlatform); - - // New mesh list - TMap StaticMeshToBuild; - - // Map of Houdini Material IDs to Unreal Material Indices - TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; - - bool MeshMaterialsHaveBeenReset = false; - - // Mesh Socket array - TArray AllSockets; - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - - double tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); - - // Iterate through all detected split groups we care about and split geometry. - // The split are ordered in the following way: - // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], - OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - - // Get/Create the Aggregate Collisions for this mesh identifier - FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); - - // Handle UCX / Convex Hull colliders - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) - { - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the convex hull colliders and add them to the Aggregate - if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) - continue; - } - else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) - { - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the simple colliders and add them to the aggregate - if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) - continue; - } - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing SM from a previous cook - UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - // Prepare LOD Group data for this static mesh - FStaticMeshLODGroup LODGroup; - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing static mesh, create a new one - FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - - // Use the platform's default LODGroup policy - // TODO? Add setting for default LOD Group? - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); - } - else - { - // Try to reuse the existing SM's LOD group instead of the default one - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); - } - - if (!FoundOutputObject) - { - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - } - else - { - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - FoundOutputObject->CachedAttributes.Empty(); - FoundOutputObject->CachedTokens.Empty(); - } - FoundOutputObject->bProxyIsCurrent = false; - - // TODO: Needed? - // Free any RHI resources for existing mesh before we re-create in place. - FoundStaticMesh->PreEditChange(NULL); - - // Check that the Static Mesh we found has the appropriate number of Source models/LODs - int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); - - // LODs are only for the "main" mesh, not for complex colliders! - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); - - if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) - { - while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) - FoundStaticMesh->AddSourceModel(); - - // We may have to remove excessive LOD levels - if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) - FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); - - // Initialize their default reduction setting - for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) - { - FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); - } - FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); - } - - // By default, always work on the first source model, unless we're a LOD - int32 SrcModelIndex = 0; - int32 LODIndex = 0; - if (SplitType == EHoudiniSplitType::LOD) - { - for (auto& curSplit : AllSplitGroups) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); - if (CurrentSplitType == EHoudiniSplitType::LOD - || CurrentSplitType == EHoudiniSplitType::Normal) - { - LODIndex++; - } - - if (curSplit == SplitGroupName) - break; - } - - // Fix for the case where we don't have a main geo - if(!bHasMainGeo) - LODIndex--; - } - - // Grab the appropriate SourceModel - FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; - if (!SrcModel) - { - HOUDINI_LOG_ERROR( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); - continue; - } - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - bool bRecomputeNormal = false; - bool bRecomputeTangent = false; - - // Load the existing mesh description if we don't need to rebuild the mesh - //FRawMesh RawMesh; - FMeshDescription* MeshDescription; - if (!bRebuildStaticMesh) - { - // We dont need to rebuild the mesh itself: - // the geometry hasn't changed, but the materials have. - // We can just reuse the old MeshDescription and reuse it. - MeshDescription = FoundStaticMesh->GetMeshDescription(LODIndex); - } - else - { - // Extract all the data needed for this split - // Start by initializing the MeshDescription for this LOD - MeshDescription = FoundStaticMesh->CreateMeshDescription(LODIndex); - FStaticMeshAttributes(*MeshDescription).Register(); - - // Mesh description uses material to create its PolygonGroups, - // so we first need to know how many different materials we have for this split - // and what vertices/indices belong to each material for remapping - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // SplitNeededVertices - // Array containing the (unique) part indices for the vertices that are needed for this split - // SplitNeededVertices[splitIndex] = PartIndex - TArray SplitNeededVertices; - //SplitNeededVertices.SetNumZeroed(SplitVertexCount); - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" so that IndicesMapper[ partIndex ] => splitIndex - TArray PartToSplitIndicesMapper; - PartToSplitIndicesMapper.Init(-1, SplitVertexList.Num()); - //TMap SplitToPartIndicesMapper; - - // SplitIndices - // Array of SplitIndices used to describe this split's polygons - TArray SplitIndices; - SplitIndices.SetNumZeroed(SplitVertexCount); - - int32 CurrentSplitIndex = 0; - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[0]) - || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[1]) - || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (PartToSplitIndicesMapper[WedgeIndices[i]] < 0) - { - // This part index has not yet been "converted" to a new split index - SplitNeededVertices.Add(WedgeIndices[i]); - PartToSplitIndicesMapper[WedgeIndices[i]] = CurrentSplitIndex; - //SplitToPartIndicesMapper.Add(CurrentSplitIndex, WedgeIndices[i]); - CurrentSplitIndex++; - } - - // Replace the old part index with the new split index - WedgeIndices[i] = PartToSplitIndicesMapper[WedgeIndices[i]]; - } - - if (!SplitIndices.IsValidIndex(ValidVertexId + 2)) - break; - - // Flip wedge indices to fix the winding order. - SplitIndices[ValidVertexId + 0] = WedgeIndices[0]; - SplitIndices[ValidVertexId + 1] = WedgeIndices[2]; - SplitIndices[ValidVertexId + 2] = WedgeIndices[1]; - - ValidVertexId += 3; - } - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract position for this part - UpdatePartPositionIfNeeded(); - - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - TVertexAttributesRef VertexPositions = - MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - - MeshDescription->ReserveNewVertices(SplitNeededVertices.Num()); - for ( const int32& NeededVertexIndex : SplitNeededVertices) - { - // Create a new Vertex - FVertexID VertexID = MeshDescription->CreateVertex(); - if (PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexPositions[VertexID].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexPositions[VertexID].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexPositions[VertexID].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - else - { - // Error when retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - } - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: Check if still needed for MeshDescription - // We need to reset the Static Mesh's materials once per SM: - // so, for the first lod, or the main geo... - if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) - { - FoundStaticMesh->StaticMaterials.Empty(); - MeshMaterialsHaveBeenReset = true; - } - - // .. or for each visible complex collider - if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - FoundStaticMesh->StaticMaterials.Empty(); - - // Get this split's faces - TArray& SplitGroupFaceIndices = AllSplitFaceIndices[SplitGroupName]; - // Array holding the materials needed for this split - //TArray SplitMaterials; - // Split Material indices per face, by default all faces are set to use the first Material - TArray SplitFaceMaterialIndices; - SplitFaceMaterialIndices.SetNumZeroed(SplitGroupFaceIndices.Num()); - - bool HasHoudiniMaterials = PartUniqueMaterialIds.Num() > 0; - bool HasMaterialOverrides = PartFaceMaterialOverrides.Num() > 0; - if (!HasHoudiniMaterials && !HasMaterialOverrides) - { - // We don't have any material override or houdini material - // we just need one polygon group using the default Houdini material. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // TODO: ? Add default mat to the assignement map? - } - else if (HasHoudiniMaterials && !HasMaterialOverrides) - { - // We have Houdini Material but no overrides - if (bOnlyOneFaceMaterial || PartUniqueMaterialIds.Num() == 1) - { - // We have only one Houdini material. - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // TODO: ? Add the mat to the assignement map? - } - else - { - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * MaterialDefault = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - // Reset Rawmesh material face assignments. - for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just use its material index - SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; - } - - UMaterialInterface * MaterialInterface = Cast(MaterialDefault); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Add the material to the Static mesh - //int32 UnrealMatIndex = SplitMaterials.Add(Material); - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); - - // Update the face index - SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; - } - } - } - else - { - // If we have material overrides - for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; - - int32 CurrentFaceMaterialIdx = -1; - if (PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - { - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); - if (FoundFaceMaterialIdx) - { - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; - if (!MaterialName.IsEmpty()) - { - // Only try to load a material if has a chance to be valid! - MaterialInterface = Cast< UMaterialInterface >( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); - } - } - - if (CurrentFaceMaterialIdx < 0) - { - // The attribute Material or its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else - { - // If everything else fails, we'll use the default material - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); - } - } - } - - // Update the Face Material on the mesh - SplitFaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; - } - } - - // Create a Polygon Group for each material slot - TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = - MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - - // We must use the number of assignment materials found to reserve the number of material slots - // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials - int32 NumberOfMaterials = OutputAssignmentMaterials.Num(); - if (NumberOfMaterials <= 0) - { - // No materials, create a polygon group for the default one - const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - } - else - { - MeshDescription->ReserveNewPolygonGroups(NumberOfMaterials); - //for (int32 MatIndex = 0; MatIndex < NumberOfMaterials; ++MatIndex) - for (auto& CurrentMatAssignement : OutputAssignmentMaterials) - { - const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = - FName(CurrentMatAssignement.Value ? *(CurrentMatAssignement.Value->GetName()) : *(CurrentMatAssignement.Key)); - } - } - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - // - // VERTEX INSTANCE ATTRIBUTES - // NORMALS, TANGENTS, COLORS, UVS, Alpha - // - - // Extract the normals - UpdatePartNormalsIfNeeded(); - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - - // Extract the tangents - // No need to read the tangents if we want unreal to recompute them after - TArray SplitTangentU; - TArray SplitTangentV; - bool bReadTangents = true; - // TODO: Add runtime setting check! - //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - int32 NormalCount = SplitNormals.Num(); - bool bGenerateTangents = (NormalCount > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - // Check that the number of tangents read matches the number of normals - if (SplitTangentU.Num() != NormalCount || SplitTangentV.Num() != NormalCount) - bGenerateTangents = true; - - /* - // TODO: Add settings check! - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - */ - - // Generate the tangents if needed - if (bGenerateTangents) - { - SplitTangentU.SetNumZeroed(NormalCount); - SplitTangentV.SetNumZeroed(NormalCount); - for (int32 Idx = 0; Idx + 2 < NormalCount; Idx += 3) - { - FVector TangentZ; - TangentZ.X = SplitNormals[Idx + 0]; - TangentZ.Y = SplitNormals[Idx + 2]; - TangentZ.Z = SplitNormals[Idx + 1]; - - FVector TangentX, TangentY; - TangentZ.FindBestAxisVectors(TangentX, TangentY); - - SplitTangentU[Idx + 0] = TangentX.X; - SplitTangentU[Idx + 2] = TangentX.Y; - SplitTangentU[Idx + 1] = TangentX.Z; - - SplitTangentV[Idx + 0] = TangentY.X; - SplitTangentV[Idx + 2] = TangentY.Y; - SplitTangentV[Idx + 1] = TangentY.Z; - } - } - } - TVertexInstanceAttributesRef VertexInstanceTangents = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - - // Extract the color values - UpdatePartColorsIfNeeded(); - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract the alpha values - UpdatePartAlphasIfNeeded(); - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - TVertexInstanceAttributesRef VertexInstanceColors = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - - // Extract UVs - UpdatePartUVSetsIfNeeded(true); - // See if we need to transfer uv point attributes to vertex attributes. - int32 UVSetCount = PartUVSets.Num(); - TArray> SplitUVSets; - SplitUVSets.SetNum(UVSetCount); - for (int32 TexCoordIdx = 0; TexCoordIdx < UVSetCount; TexCoordIdx++) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - } - TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - VertexInstanceUVs.SetNumIndices(UVSetCount); - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - // Allocate space for the vertex instances and polygons - MeshDescription->ReserveNewVertexInstances(SplitIndices.Num()); - MeshDescription->ReserveNewPolygons(SplitIndices.Num() / 3); - //Approximately 2.5 edges per polygons - MeshDescription->ReserveNewEdges(SplitIndices.Num() * 2.5f / 3); - - bool bHasNormal = SplitNormals.Num() > 0; - bool bHasTangents = SplitTangentU.Num() > 0 && SplitTangentV.Num() > 0; - bool bHasRGB = SplitColors.Num() > 0; - bool bHasRGBA = bHasRGB && AttribInfoColors.tupleSize == 4; - bool bHasAlpha = SplitAlphas.Num() > 0; - - bRecomputeNormal = !bHasNormal; - bRecomputeTangent = !bHasTangents; - - TArray HasUVSets; - HasUVSets.SetNumZeroed(PartUVSets.Num()); - for (int32 Idx = 0; Idx < PartUVSets.Num(); Idx++) - HasUVSets[Idx] = PartUVSets[Idx].Num() > 0; - - uint32 FaceCount = SplitIndices.Num() / 3; - for (uint32 FaceIndex = 0; FaceIndex < FaceCount; FaceIndex++) - { - TArray FaceVertexInstanceIDs; - FaceVertexInstanceIDs.SetNum(3); - - // Ignore degenerate triangles - FVertexID VertexIDs[3]; - for (int32 Corner = 0; Corner < 3; ++Corner) - { - VertexIDs[Corner] = FVertexID(SplitIndices[(FaceIndex * 3) + Corner]); - } - if (VertexIDs[0] == VertexIDs[1] || VertexIDs[0] == VertexIDs[2] || VertexIDs[1] == VertexIDs[2]) - continue; - - //FVertexID FaceVertexIDs[3]; - for (int32 Corner = 0; Corner < 3; Corner++) - { - uint32 SplitIndex = (FaceIndex * 3) + Corner; - uint32 SplitVertexIndex = SplitIndices[SplitIndex]; - const FVertexInstanceID& VertexInstanceID = MeshDescription->CreateVertexInstance(FVertexID(SplitVertexIndex)); - - // Fix the winding order by updating the SplitIndex (invert corner 1 and 2) - // instead of going 0 1 2 go 0 2 1 - // TODO; this slows down StaticMesh->Build() considerably! - Corner == 1 ? SplitIndex++ : Corner == 2 ? SplitIndex-- : SplitIndex; - - const uint32 SplitVertexIndex_X = SplitIndex * 3 + 0; - const uint32 SplitVertexIndex_Y = SplitIndex * 3 + 2; - const uint32 SplitVertexIndex_Z = SplitIndex * 3 + 1; - // Normals - if (bHasNormal) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexInstanceNormals[VertexInstanceID].X = SplitNormals[SplitVertexIndex_X]; - VertexInstanceNormals[VertexInstanceID].Y = SplitNormals[SplitVertexIndex_Y]; - VertexInstanceNormals[VertexInstanceID].Z = SplitNormals[SplitVertexIndex_Z]; - } - - // Tangents and binormals - if (bHasTangents) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexInstanceTangents[VertexInstanceID].X = SplitTangentU[SplitVertexIndex_X]; - VertexInstanceTangents[VertexInstanceID].Y = SplitTangentU[SplitVertexIndex_Y]; - VertexInstanceTangents[VertexInstanceID].Z = SplitTangentU[SplitVertexIndex_Z]; - - FVector TangentY; - TangentY.X = SplitTangentV[SplitVertexIndex_X]; - TangentY.Y = SplitTangentV[SplitVertexIndex_Y]; - TangentY.Z = SplitTangentV[SplitVertexIndex_Z]; - - VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign( - VertexInstanceTangents[VertexInstanceID].GetSafeNormal(), - TangentY.GetSafeNormal(), - VertexInstanceNormals[VertexInstanceID].GetSafeNormal()); - } - - // Color - FLinearColor Color = FLinearColor::White; - if (bHasRGB) - { - Color.R = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); - Color.G = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); - Color.B = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); - } - // Alpha - if (bHasAlpha) - { - Color.A = FMath::Clamp(SplitAlphas[SplitIndex], 0.0f, 1.0f); - } - else if (bHasRGBA) - { - Color.A = FMath::Clamp(SplitColors[SplitIndex * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); - } - VertexInstanceColors[VertexInstanceID] = FVector4(Color); - - // UVs - for (int32 UVIndex = 0; UVIndex < SplitUVSets.Num(); UVIndex++) - { - if (HasUVSets[UVIndex]) - { - // We need to flip V coordinate when it's coming from HAPI. - FVector2D CurrentUV; - CurrentUV.X = SplitUVSets[UVIndex][SplitIndex * 2 + 0]; - CurrentUV.Y = 1.0f - SplitUVSets[UVIndex][SplitIndex * 2 + 1]; - - VertexInstanceUVs.Set(VertexInstanceID, UVIndex, CurrentUV); - } - } - - FaceVertexInstanceIDs[Corner] = VertexInstanceID; - } - - const FPolygonGroupID PolygonGroupID(SplitFaceMaterialIndices[FaceIndex]); - - // Insert a triangle into the mesh - MeshDescription->CreateTriangle(PolygonGroupID, FaceVertexInstanceIDs); - } - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's FaceSmoothing values if needed - UpdatePartFaceSmoothingIfNeeded(); - - // Get the FaceSmoothing values for this split - TArray SplitFaceSmoothingMasks; - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); - - // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! - // TODO: Expose the default FaceSmoothing value - // 0 will make hard face - TArray FaceSmoothingMasks; - FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); - - // Check that the number of face smoothing values we retrieved is correct - int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; - if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) - { - // Ignore our face smoothing values - WedgeFaceSmoothCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); - } - - // Transfer the face smoothing masks to the raw mesh if we have any - for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) - { - FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; - } - - // TODO - // Check - FStaticMeshOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, *MeshDescription); - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - // Extract this part's LightmapResolution values if needed - UpdatePartLightmapResolutionsIfNeeded(); - - // make sure the mesh has a new lighting guid - FoundStaticMesh->LightingGuid = FGuid::NewGuid(); - } - - // TODO: - // BUILD SETTINGS - // (Using default for now) - SrcModel->BuildSettings.bRemoveDegenerates = true; - SrcModel->BuildSettings.bUseMikkTSpace = true; - SrcModel->BuildSettings.bBuildAdjacencyBuffer = false; - SrcModel->BuildSettings.MinLightmapResolution = 64; - SrcModel->BuildSettings.bUseFullPrecisionUVs = false; - SrcModel->BuildSettings.SrcLightmapIndex = 0; - SrcModel->BuildSettings.DstLightmapIndex = 1; - SrcModel->BuildSettings.bRecomputeNormals = bRecomputeNormal; - SrcModel->BuildSettings.bRecomputeTangents = bRecomputeNormal || bRecomputeTangent; - SrcModel->BuildSettings.bGenerateLightmapUVs = PartUVSets.Num() <= 0; - - // Set the lightmap Coordinate Index - // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention - FoundStaticMesh->LightMapCoordinateIndex = PartUVSets.Num() > 1 ? 1 : 0; - - // Check for a lightmapa resolution override - int32 LightMapResolutionOverride = -1; - if ( PartLightMapResolutions.Num() > 0) - LightMapResolutionOverride = PartLightMapResolutions[0]; - - if (LightMapResolutionOverride > 0) - FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; - else - FoundStaticMesh->LightMapResolution = 64; - - // TODO: - // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - - // By default the distance field resolution should be set to 2.0 - // TODO should come from the HAC - //SrcModel->BuildSettings.DistanceFieldResolutionScale = HoudiniCookParams.GeneratedDistanceFieldResolutionScale; - SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; - - // RAW MESH CHECKS - - // TODO: Check not needed w/ FMeshDesc - // This is required due to the impeding deprecation of FRawMesh - // If we dont update this UE4 will crash upon deleting an asset. - //SrcModel->StaticMeshOwner = FoundStaticMesh; - - // Store the new MeshDescription - FoundStaticMesh->CommitMeshDescription(LODIndex); - //Set the Imported version before calling the build - FoundStaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; - - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = GetLODSCreensizeForSplit(SplitGroupName); - if (screensize >= 0.0f) - { - // Only apply the LOD screensize if it's valid - SrcModel->ScreenSize = screensize; - //FoundStaticMesh->GetSourceModel(LodIndex).ScreenSize = screensize; - FoundStaticMesh->bAutoComputeLODScreenSize = false; - } - - // SET STATIC MESH GENERATION PARAM - // HANDLE COLLIDERS - // REMOVE OLD COLLIDERS - // CUSTOM BAKE NAME OVERRIDE - - // UPDATE UPROPERTY ATTRIBS - // Update property attributes on the SM - TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( - HGPO.GeoId, HGPO.PartId, - AllSplitFirstValidVertexIndex[SplitGroupName], - AllSplitFirstValidPrimIndex[SplitGroupName], - PropertyAttributes)) - { - UpdateGenericPropertiesAttributes( - FoundStaticMesh, PropertyAttributes); - } - - TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - { - // cache the level path attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); - } - } - - TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - // cache the output name attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); - } - } - - TArray TileValues; - if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); - } - } - - TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) - { - if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); - } - } - - TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) - { - if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); - } - } - - // Notify that we created a new Static Mesh if needed - if(bNewStaticMeshCreated) - FAssetRegistryModule::AssetCreated(FoundStaticMesh); - - // Add the Static mesh to the output maps and the build map if we haven't already - if (FoundOutputObject) - { - FoundOutputObject->OutputObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = false; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - - StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); - - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished MD in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Look if we only have colliders - // If we do, we'll allow attaching sockets to the collider meshes - bool bCollidersOnly = true; - for (auto& Current : StaticMeshToBuild) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) - { - bCollidersOnly = false; - break; - } - } - - FHoudiniScopedGlobalSilence ScopedGlobalSilence; - for (auto& Current : StaticMeshToBuild) - { - UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) - continue; - - UBodySetup * BodySetup = SM->BodySetup; - if (!BodySetup) - { - SM->CreateBodySetup(); - BodySetup = SM->BodySetup; - } - - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - - // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) - { - // Make sure rendering is done - so we are not changing data being used by collision drawing. - FlushRenderingCommands(); - - // Clean up old colliders from a previous cook - BodySetup->Modify(); - BodySetup->RemoveSimpleCollision(); - // Create new GUID - BodySetup->InvalidatePhysicsData(); - - FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; - FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); - if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) - { - BodySetup->AddCollisionFrom(*CurrentAggColl); - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; - } - - // Moved RefreshCollisionChange to after the SM->Build call - // RefreshCollisionChange(*SM); - SM->bCustomizedCollision = true; - - // See if we need to enable collisions on the whole mesh - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - // Complex collider, enable collisions for this static mesh. - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - else - { - // TODO - // if the LODForCollision uproperty attribute is set, we need to activate complex collision - // on the static mesh for that lod to be picked up properly as a collider - if ( FHoudiniEngineUtils::HapiCheckAttributeExists( HGPO.GeoId, HGPO.PartId, - "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) - { - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - } - } - - // Add the Sockets to the StaticMesh - // We only add them to the main geo, or to the colliders if we only generate colliders - bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; - if (bAddSocket) - { - if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); - } - } - - // BUILD the Static Mesh - // bSilent doesnt add the Build Errors... - double build_start = FPlatformTime::Seconds(); - TArray SMBuildErrors; - SM->Build(true, &SMBuildErrors); - double build_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); - - // TODO: copied the content of RefreshCollision below and commented out CreateNavCollision - // it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, - // and can be expensive depending on the vert/poly count of the mesh - // TODO: also moved this to after the call to Build, since Build updates the mesh's - // physics state (calling this before Build when rebuilding an existing high poly mesh as - // low poly mesh, incurs quite a performance hit. This is likely due to processing of physics - // meshes with high vert/poly count before the Build - // RefreshCollisionChange(*SM); - { - // SM->CreateNavCollision(/*bIsUpdate=*/true); - - for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) - { - UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); - if (StaticMeshComponent->GetStaticMesh() == SM) - { - // it needs to recreate IF it already has been created - if (StaticMeshComponent->IsPhysicsStateCreated()) - { - StaticMeshComponent->RecreatePhysicsState(); - } - } - } - - FEditorSupportDelegates::RedrawAllViewports.Broadcast(); - } - - SM->GetOnMeshChanged().Broadcast(); - /* - // Try to find the outer package so we can dirty it up - if (SM->GetOuter()) - { - SM->GetOuter()->MarkPackageDirty(); - } - else - { - SM->MarkPackageDirty(); - } - */ - - UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - TArray PackageToSave; - PackageToSave.Add(MeshPackage); - - // Save the created package - FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); - */ - } - } - - // TODO: Still necessary ? SM->Build should actually update the navmesh... - // TODO: Commented out for now, since it appears that the content of the loop is - // already called in UStaticMesh::BuildInternal and UStaticMesh::PostBuildInternal - //// Now that all the meshes are built and their collisions meshes and primitives updated, - //// we need to update their pre-built navigation collision used by the navmesh - //for (auto& Iter : OutputObjects) - //{ - // UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); - // if (!StaticMesh || StaticMesh->IsPendingKill()) - // continue; - - // UBodySetup * BodySetup = StaticMesh->BodySetup; - // if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) - // { - // // Unreal caches the Navigation Collision and never updates it for StaticMeshes, - // // so we need to manually flush and recreate the data to have proper navigation collision - // // TODO: Is this still required? These two functions are called by - // // UStaticMesh::BuildInternal, which is called by UStaticMesh::Build/BatchBuild - // // BodySetup->InvalidatePhysicsData(); - // // BodySetup->CreatePhysicsMeshes(); - - // // TODO: Is this still required? This function is called by UStaticMesh::CreateNavCollision - // // which is called by the UStaticMesh::PostBuildInternal function, which is called at the - // // end of the build. - // // StaticMesh->NavCollision->Setup(BodySetup); - // } - //} - - double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() executed in %f seconds."), time_end - time_start); - - return true; -} - -bool -FHoudiniMeshTranslator::CreateHoudiniStaticMesh() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh")); - - const double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Determine if there is "main" geo, if not we'll use the first LOD - // as main geo - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - { - bHasMainGeo = true; - break; - } - } - - // Update the part's material's IDS and info now - //UpdatePartFaceMaterialsIfNeeded(); - CreateNeededMaterials(); - - // Check if the materials were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Map of Houdini Material IDs to Unreal Material Indices - TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; - - bool MeshMaterialsHaveBeenReset = false; - - double tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); - - // Iterate through all detected split groups we care about and split geometry. - bool bMainGeoOrFirstLODFound = false; - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Per Split")); - - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // We are only interested in the Normal/main geo and visible colliders - if (SplitType != EHoudiniSplitType::Normal && - SplitType != EHoudiniSplitType::LOD && - SplitType != EHoudiniSplitType::RenderedComplexCollider && - SplitType != EHoudiniSplitType::RenderedSimpleCollider && - SplitType != EHoudiniSplitType::RenderedUCXCollider) - { - continue; - } - - // We only use LOD if there is no Normal geo - if (SplitType == EHoudiniSplitType::Normal) - { - bMainGeoOrFirstLODFound = true; - HOUDINI_LOG_MESSAGE(TEXT("Found Normal geo for mesh.")); - } - else if (SplitType == EHoudiniSplitType::LOD) - { - if (bHasMainGeo) - { - HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since the mesh has Normal geo.")); - continue; - } - else if (bMainGeoOrFirstLODFound) - { - HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since we have already processed the first LOD.")); - continue; - } - else - { - bMainGeoOrFirstLODFound = true; - HOUDINI_LOG_MESSAGE(TEXT("Mesh does not have Normal geo, found first LOD.")); - } - } - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], - OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing DM from a previous cook - UHoudiniStaticMesh* FoundStaticMesh = FindExistingHoudiniStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing dynamic mesh, create a new one - FoundStaticMesh = CreateNewHoudiniStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - } - - if (!FoundOutputObject) - { - // If we couldnt find a previous output object, create a new one - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - } - FoundOutputObject->bProxyIsCurrent = true; - - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - - if (bRebuildStaticMesh) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build/Rebuild UHoudiniStaticMesh")); - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" - // So that IndicesMapper[ oldIndex ] => newIndex - TArray IndicesMapper; - IndicesMapper.Init(-1, SplitVertexList.Num()); - int32 CurrentMapperIndex = 0; - - // NeededVertices: - // Array containing the old index of the needed vertices for the current split - // NeededVertices[ newIndex ] => oldIndex - TArray< int32 > NeededVertices; - NeededVertices.Reserve(SplitVertexList.Num() / 3); - TArray< int32 > TriangleIndices; - TriangleIndices.Reserve(SplitVertexList.Num()); - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build IndicesMapper and NeededVertices")); - - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) - || !IndicesMapper.IsValidIndex(WedgeIndices[1]) - || !IndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (IndicesMapper[WedgeIndices[i]] < 0) - { - // This old index has not yet been "converted" to a new index - NeededVertices.Add(WedgeIndices[i]); - IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; - CurrentMapperIndex++; - } - - // Replace the old index with the new one - WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; - } - - // Flip wedge indices to fix the winding order. - TriangleIndices.Add(WedgeIndices[0]); - TriangleIndices.Add(WedgeIndices[2]); - TriangleIndices.Add(WedgeIndices[1]); - - ValidVertexId += 3; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's normal if needed - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - // Check that the number of normal we retrieved is correct - int32 NormalCount = SplitNormals.Num() / 3; - if (NormalCount < 0 || NormalCount < NeededVertices.Num()) - { - // Ignore normals - NormalCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENTS - //--------------------------------------------------------------------------------------------------------------------- - - TArray< float > SplitTangentU; - TArray< float > SplitTangentV; - int32 TangentUCount = 0; - int32 TangentVCount = 0; - // No need to read the tangents if we want unreal to recompute them after - // TODO: Add runtime setting check! - //bool bReadTangents = HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always; - bool bReadTangents = true; - bool bGenerateTangents = false; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - - // Check that the number of tangents read matches the number of normals - TangentUCount = SplitTangentU.Num() / 3; - TangentVCount = SplitTangentV.Num() / 3; - if (TangentUCount != NormalCount || TangentVCount != NormalCount) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh: Generate tangents due to count mismatch (# U Tangents = %d; # V Tangents = %d; # Normals = %d)"), TangentUCount, TangentVCount, NormalCount); - bGenerateTangents = true; - } - - /* - // TODO: Add settings check! - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - */ - } - - //--------------------------------------------------------------------------------------------------------------------- - // VERTEX COLORS AND ALPHAS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's colors if needed - UpdatePartColorsIfNeeded(); - - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract this part's alpha values if needed - UpdatePartAlphasIfNeeded(); - - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - - const int32 ColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; - const bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && ColorsCount > 0; - const bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == ColorsCount); - - //--------------------------------------------------------------------------------------------------------------------- - // UVS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's UV sets if needed - UpdatePartUVSetsIfNeeded(); - - // See if we need to transfer uv point attributes to vertex attributes. - int32 NumUVLayers = 0; - TArray> SplitUVSets; - SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - if (SplitUVSets[TexCoordIdx].Num() > 0) - { - NumUVLayers++; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL ATTRIBUTE OVERRIDES - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: These are actually per faces, not per vertices... - // Need to update!! - UpdatePartFaceMaterialOverridesIfNeeded(); - - // - // Initialize mesh - // - const int32 NumVertexPositions = NeededVertices.Num(); - const int32 NumTriangles = TriangleIndices.Num() / 3; - const bool bHasPerFaceMaterials = PartFaceMaterialOverrides.Num() > 0 || (PartUniqueMaterialIds.Num() > 0 && !bOnlyOneFaceMaterial); - - FoundStaticMesh->Initialize( - NumVertexPositions, - NumTriangles, - NumUVLayers, // NumUVLayers - 0, // InitialNumStaticMaterials - NormalCount > 0, // HasNormals - NormalCount > 0 && bReadTangents, // HasTangents - bSplitColorValid, // HasColors - bHasPerFaceMaterials // HasPerFaceMaterials - ); - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - UpdatePartPositionIfNeeded(); - - // - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Vertex Positions")); - - for (int32 VertexPositionIdx = 0; VertexPositionIdx < NumVertexPositions; ++VertexPositionIdx) - //ParallelFor(NumVertexPositions, [&](uint32 VertexPositionIdx) - { - int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; - if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // We need to swap Z and Y coordinate here, and convert from m to cm. - FoundStaticMesh->SetVertexPosition(VertexPositionIdx, FVector( - PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION, - PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION, - PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION - )); - }//); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACES / TRIS - // Now set Normals, UVs and Colors on mesh points and AttributeSet - //--------------------------------------------------------------------------------------------------------------------- - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Triangle Indices & Per Vertex Instance Attribute Values")); - - // Now add the triangles to the mesh - for (int32 TriangleIdx = 0; TriangleIdx < NumTriangles; ++TriangleIdx) - // ParallelFor(NumTriangles, [&](uint32 TriangleIdx) - { - - const int32 TriVertIdx0 = TriangleIdx * 3; - FoundStaticMesh->SetTriangleVertexIndices(TriangleIdx, FIntVector( - TriangleIndices[TriVertIdx0 + 0], - TriangleIndices[TriVertIdx0 + 1], - TriangleIndices[TriVertIdx0 + 2] - )); - - const int32 TriWindingIndex[3] = { 0, 2, 1 }; - if (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)) - { - // Flip Z and Y coordinate for normal, but don't scale - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - const FVector Normal( - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] - ); - - FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); - - if (bReadTangents) - { - FVector TangentU, TangentV; - if (bGenerateTangents) - { - // Generate the tangents if needed - Normal.FindBestAxisVectors(TangentU, TangentV); - } - else - { - // Transfer the tangents from Houdini - TangentU.X = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; - TangentU.Y = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; - TangentU.Z = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; - - TangentU.X = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; - TangentU.Y = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; - TangentU.Z = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; - } - - FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); - FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); - } - } - } - - if (bSplitColorValid && SplitColors.IsValidIndex(TriVertIdx0 * AttribInfoColors.tupleSize + 3 * AttribInfoColors.tupleSize - 1)) - { - FLinearColor VertexLinearColor; - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - VertexLinearColor.R = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 0], 0.0f, 1.0f); - VertexLinearColor.G = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 1], 0.0f, 1.0f); - VertexLinearColor.B = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 2], 0.0f, 1.0f); - - if (bSplitAlphaValid) - { - VertexLinearColor.A = FMath::Clamp(SplitAlphas[TriVertIdx0 + ElementIdx], 0.0f, 1.0f); - } - else if (AttribInfoColors.tupleSize >= 4) - { - VertexLinearColor.A = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 3], 0.0f, 1.0f); - } - else - { - VertexLinearColor.A = 1.0f; - } - const FColor VertexColor = VertexLinearColor.ToFColor(false); - FoundStaticMesh->SetTriangleVertexColor(TriangleIdx, TriWindingIndex[ElementIdx], VertexColor); - } - } - - if (NumUVLayers > 0) - { - // Dynamic mesh supports only 1 UV layer on the mesh it self. So we set the first layer - // on the mesh itself only, and we set all layers on the AttributeSet - for (int32 TexCoordIdx = 0; TexCoordIdx < NumUVLayers; ++TexCoordIdx) - { - const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; - if (SplitUVs.IsValidIndex(TriVertIdx0 * 2 + 3 * 2 - 1)) - { - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - const int32 UVIdx = TriVertIdx0 * 2 + ElementIdx * 2; - // We need to flip V coordinate when it's coming from HAPI. - const FVector2D UV(SplitUVs[UVIdx + 0], 1.0f - SplitUVs[UVIdx + 1]); - // Set the UV on the vertex instance in the UVLayer - FoundStaticMesh->SetTriangleVertexUV(TriangleIdx, TriWindingIndex[ElementIdx], TexCoordIdx, UV); - } - } - } - } - }//); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIALS / FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // Get face indices for this split. - TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; - - // Process material overrides first - if (PartFaceMaterialOverrides.Num() > 0) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Material Overrides")); - - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - continue; - - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); - int32 CurrentFaceMaterialIdx = 0; - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; - - // Start by looking in our assignment map - auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface && !MaterialName.IsEmpty()) - { - // Only try to load a material if has a chance to be valid! - MaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); - } - else - { - // The Attribute Material and its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else - { - // If everything fails, we'll use the default material - MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Add the material to the mesh - CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); - } - } - } - - // Update the Face Material on the mesh - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); - } - } - else if (PartUniqueMaterialIds.Num() > 0) - { - // The part has houdini materials - if (bOnlyOneFaceMaterial) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Single Material")); - - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - } - else - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Materials")); - - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); - continue; - } - - UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Add the material to the mesh - int32 UnrealMatIndex = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); - - // Update the face index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); - } - } - } - else - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Default Material")); - - // No materials were found, we need to use default Houdini material. - int32 SplitFaceCount = SplitFaceIndices.Num(); - - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - } - - //// Update property attributes on the mesh - //TArray PropertyAttributes; - //if (GetGenericPropertiesAttributes( - // HGPO.GeoId, HGPO.PartId, - // AllSplitFirstValidVertexIndex[SplitGroupName], - // AllSplitFirstValidPrimIndex[SplitGroupName], - // PropertyAttributes)) - //{ - // UpdateGenericPropertiesAttributes( - // FoundStaticMesh, PropertyAttributes); - //} - - FoundStaticMesh->Optimize(); - - //// Try to find the outer package so we can dirty it up - //if (FoundStaticMesh->GetOuter()) - //{ - // FoundStaticMesh->GetOuter()->MarkPackageDirty(); - //} - //else - //{ - // FoundStaticMesh->MarkPackageDirty(); - //} - UPackage *MeshPackage = FoundStaticMesh->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - // Save the created/updated package - FEditorFileUtils::PromptForCheckoutAndSave({ MeshPackage }, false, false); - */ - } - - // Add the Proxy mesh to the output maps - if (FoundOutputObject) - { - FoundOutputObject->ProxyObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = true; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - } - - const double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() executed in %f seconds."), time_end - time_start); - - return true; -} - -bool -FHoudiniMeshTranslator::CreateNeededMaterials() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateNeededMaterials")); - - UpdatePartNeededMaterials(); - - TArray MaterialAndTexturePackages; - FHoudiniMaterialTranslator::CreateHoudiniMaterials( - HGPO.AssetId, PackageParams, - PartUniqueMaterialIds, PartUniqueMaterialInfos, - InputAssignmentMaterials, OutputAssignmentMaterials, - MaterialAndTexturePackages, false, bTreatExistingMaterialsAsUpToDate); - - /* - // Save the created packages if needed - // DPT: deactivated, only dirty for now, as we'll save them when saving the world. - if (MaterialAndTexturePackages.Num() > 0) - FEditorFileUtils::PromptForCheckoutAndSave(MaterialAndTexturePackages, true, false); - */ - - if (bMaterialOverrideNeedsCreateInstance && PartFaceMaterialOverrides.Num() > 0) - { - // Map containing unique face materials override attribute - // and their first valid prim index - // We create only one material instance per attribute - TMap UniqueFaceMaterialOverrides; - for (int FaceIdx = 0; FaceIdx < PartFaceMaterialOverrides.Num(); FaceIdx++) - { - FString MatOverrideAttr = PartFaceMaterialOverrides[FaceIdx]; - if (UniqueFaceMaterialOverrides.Contains(MatOverrideAttr)) - continue; - - // Add the material override and face index to the map - UniqueFaceMaterialOverrides.Add(MatOverrideAttr, FaceIdx); - } - - FHoudiniMaterialTranslator::CreateMaterialInstances( - HGPO, PackageParams, - UniqueFaceMaterialOverrides, MaterialAndTexturePackages, - InputAssignmentMaterials, OutputAssignmentMaterials, - false); - } - - return true; -} - -FString -FHoudiniMeshTranslator::GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType) -{ - FString MeshIdentifier = TEXT(""); - switch (InSplitType) - { - case EHoudiniSplitType::Normal: - case EHoudiniSplitType::LOD: - case EHoudiniSplitType::InvisibleUCXCollider: - case EHoudiniSplitType::InvisibleSimpleCollider: - // LODs and Invisible simple colliders use the main mesh - MeshIdentifier = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - break; - - case EHoudiniSplitType::InvisibleComplexCollider: - case EHoudiniSplitType::RenderedComplexCollider: - case EHoudiniSplitType::RenderedUCXCollider: - case EHoudiniSplitType::RenderedSimpleCollider: - // Rendered colliders or invisible complex colliders have their own static mesh - MeshIdentifier = InSplitName; - break; - - default: - break; - } - - return MeshIdentifier; -} - -UStaticMesh* -FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - // See if we already have an input object for that output identifier - FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); - UStaticMesh* FoundStaticMesh = nullptr; - if (FoundOutputObjectPtr) - { - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - FoundStaticMesh = nullptr; - } - - if (!FoundStaticMesh) - { - // No input object matching this identifier, see if we have created an output object that matches - FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); - if (!FoundOutputObjectPtr) - return nullptr; - - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - return nullptr; - } - - if (FoundStaticMesh) - { - UObject* OuterMost = FoundStaticMesh->GetOutermostObject(); - if (OuterMost->IsA()) - { - // The Outermost for this static mesh is a level - // This is likely a SM created by V1, and we should not reuse it. - // This will force the plugin to recreate a "proper" SM in the temp folder. - FoundStaticMesh->MarkPendingKill(); - FoundStaticMesh = nullptr; - } - } - - return FoundStaticMesh; -} - -UHoudiniStaticMesh* -FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - // See if we already have an input object for that output identifier - FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); - UHoudiniStaticMesh* FoundStaticMesh = nullptr; - if (FoundOutputObjectPtr) - { - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - FoundStaticMesh = nullptr; - } - - if (!FoundStaticMesh) - { - // No input object matching this identifier, see if we have created an output object that matches - FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); - if (!FoundOutputObjectPtr) - return nullptr; - - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - return nullptr; - } - - return FoundStaticMesh; -} - -EHoudiniSplitType -FHoudiniMeshTranslator::GetSplitTypeFromSplitName(const FString& InSplitName) -{ - const FString MainGroup = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - if (InSplitName.StartsWith(MainGroup, ESearchCase::IgnoreCase)) - return EHoudiniSplitType::Normal; - - const FString LODGroupPrefix = HAPI_UNREAL_GROUP_LOD_PREFIX; - if (InSplitName.StartsWith(LODGroupPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::LOD; - } - - const FString RenderedCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; - if (InSplitName.StartsWith(RenderedCollisionPrefix, ESearchCase::IgnoreCase)) - { - // Rendered colliders - // See if it is a simple/ucx/complex - const FString RenderedUCXCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX; - const FString RenderedSimpleCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(RenderedUCXCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::RenderedUCXCollider; - } - else if (InSplitName.StartsWith(RenderedSimpleCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::RenderedSimpleCollider; - } - else - { - return EHoudiniSplitType::RenderedComplexCollider; - } - } - - const FString InvisibleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(InvisibleCollisionPrefix, ESearchCase::IgnoreCase)) - { - // Invisible colliders - // See if it is a simple/ucx/complex - const FString InvisibleUCXCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX; - const FString InvisibleSimpleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(InvisibleUCXCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::InvisibleUCXCollider; - } - else if (InSplitName.StartsWith(InvisibleSimpleCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::InvisibleSimpleCollider; - } - else - { - return EHoudiniSplitType::InvisibleComplexCollider; - } - } - - // ? - return EHoudiniSplitType::Invalid; - //return EHoudiniSplitType::Normal; -} - -bool -FHoudiniMeshTranslator::AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) -{ - // Get the vertex indices for the split group - TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; - - // We're only interested in unique vertices - TArray UniqueVertexIndexes; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - UniqueVertexIndexes.AddUnique(Index); - } - - // Extract the collision geo's vertices - TArray< FVector > VertexArray; - VertexArray.SetNum(UniqueVertexIndexes.Num()); - for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) - { - int32 VertexIndex = UniqueVertexIndexes[Idx]; - if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) - continue; - - VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - -#if WITH_EDITOR - // Do we want to create multiple convex hulls? - bool bDoMultiHullDecomp = false; - if (SplitGroupName.Contains(TEXT("ucx_multi"), ESearchCase::IgnoreCase)) - bDoMultiHullDecomp = true; - - uint32 HullCount = 8; - int32 MaxHullVerts = 16; - if (bDoMultiHullDecomp) - { - // TODO: - // Look for extra attributes for the decomposition parameters? (HullCount/MaxHullVerts) - } - - if (bDoMultiHullDecomp && (VertexArray.Num() >= 3 || UniqueVertexIndexes.Num() >= 3)) - { - // creating multiple convex hull collision - // ... this might take a while - - // We're only interested in the valid indices! - TArray Indices; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - Indices.Add(Index); - } - - // But we need all the positions as vertex - TArray< FVector > Vertices; - Vertices.SetNum(PartPositions.Num() / 3); - - for (int32 Idx = 0; Idx < Vertices.Num(); Idx++) - { - Vertices[Idx].X = PartPositions[Idx * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[Idx].Y = PartPositions[Idx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[Idx].Z = PartPositions[Idx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - // We are using Unreal's DecomposeMeshToHulls() - // We need a BodySetup so create a fake/transient one - UBodySetup* BodySetup = NewObject(); - - // Run actual util to do the work (if we have some valid input) - DecomposeMeshToHulls(BodySetup, Vertices, Indices, HullCount, MaxHullVerts); - - // If we succeed, return here - // If not, keep going and we'll try to do a single hull decomposition - if (BodySetup->AggGeom.ConvexElems.Num() > 0) - { - // Copy the convex elem to our aggregate - for (int32 n = 0; n < BodySetup->AggGeom.ConvexElems.Num(); n++) - AggCollisions.ConvexElems.Add(BodySetup->AggGeom.ConvexElems[n]); - - return true; - } - } -#endif - - // Creating a single Convex collision - FKConvexElem ConvexCollision; - ConvexCollision.VertexData = VertexArray; - ConvexCollision.UpdateElemBox(); - - AggCollisions.ConvexElems.Add(ConvexCollision); - - return true; -} - -bool -FHoudiniMeshTranslator::AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) -{ - // Get the vertex indices for the split group - TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; - - // We're only interested in unique vertices - TArray UniqueVertexIndexes; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - UniqueVertexIndexes.AddUnique(Index); - } - - // Extract the collision geo's vertices - TArray< FVector > VertexArray; - VertexArray.SetNum(UniqueVertexIndexes.Num()); - for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) - { - int32 VertexIndex = UniqueVertexIndexes[Idx]; - if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) - continue; - - VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - int32 NewColliders = 0; - if (SplitGroupName.Contains("Box")) - { - NewColliders = FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(VertexArray, AggCollisions); - } - else if (SplitGroupName.Contains("Sphere")) - { - NewColliders = FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(VertexArray, AggCollisions); - } - else if (SplitGroupName.Contains("Capsule")) - { - NewColliders = FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(VertexArray, AggCollisions); - } - else - { - // We need to see what type of collision the user wants - // by default, a kdop26 will be created - uint32 NumDirections = 26; - const FVector* Directions = KDopDir26; - if (SplitGroupName.Contains("kdop10X")) - { - NumDirections = 10; - Directions = KDopDir10X; - } - else if (SplitGroupName.Contains("kdop10Y")) - { - NumDirections = 10; - Directions = KDopDir10Y; - } - else if (SplitGroupName.Contains("kdop10Z")) - { - NumDirections = 10; - Directions = KDopDir10Z; - } - else if (SplitGroupName.Contains("kdop18")) - { - NumDirections = 18; - Directions = KDopDir18; - } - - // Converting the directions to a TArray - TArray DirArray; - DirArray.SetNum(NumDirections); - for (uint32 DirectionIndex = 0; DirectionIndex < NumDirections; DirectionIndex++) - { - DirArray[DirectionIndex] = Directions[DirectionIndex]; - } - - NewColliders = FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(VertexArray, DirArray, AggCollisions); - } - - return (NewColliders > 0); -} - -int32 -FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData) -{ - return FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, OutVertexData); -} - -/* -int32 -FHoudiniMeshTranslator::GetSplitNormals( - const TArray& InSplitVertexList, TArray& OutNormals) -{ - // Extract the normals - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutNormals.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; - OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; - OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; - - OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; - OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; - OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; - - OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; - OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; - OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; - } - - return WedgeCount; -} - -int32 -FHoudiniMeshTranslator::GetSplitUVs( - const TArray& InSplitVertexList, TArray& OutUVs) -{ - // Extract the normals - UpdatePartUVSetsIfNeeded(); - - // Get the normals for this split - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutNormals.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; - OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; - OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; - - OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; - OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; - OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; - - OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; - OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; - OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; - } - - return WedgeCount; -} - - -int32 -FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutData, - const float& ScaleFactor) -{ - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutData.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; - OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; - - OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; - OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; - - OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; - OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; - } - - return WedgeCount; -} - -int32 -FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutData, - const float& ScaleFactor) -{ - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutData.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; - OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; - - OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; - OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; - - OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; - OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; - } - - return WedgeCount; -} -*/ - - -template -int32 FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::TransferPartAttributesToSplit")); - - if (!InAttribInfo.exists || InAttribInfo.tupleSize <= 0) - return 0; - - if (InData.Num() <= 0) - return 0; - - int32 ValidWedgeCount = 0; - - // Future optimization - see if we can do direct vertex transfer. - int32 WedgeCount = InVertexList.Num(); - int32 LastValidWedgeIdx = 0; - if (InAttribInfo.owner == HAPI_ATTROWNER_POINT) - { - // Point attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - int32 VertexIdx = InVertexList[WedgeIdx]; - if (VertexIdx < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[VertexIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_VERTEX) - { - // Vertex attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[WedgeIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_PRIM) - { - // Primitive attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 PrimIdx = WedgeIdx / 3; - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[PrimIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_DETAIL) - { - // Detail attribute transfer - // We have one value to copy for all output split vertices - // if the attribute is a single value (not a tuple) - // then we can simply use the array init function instead of looping - if (InAttribInfo.tupleSize == 1) - { - OutVertexData.Init(InData[0], WedgeCount); - } - else - { - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - } - else - { - // Invalid attribute owner, shouldn't happen - check(false); - } - - OutVertexData.SetNumZeroed(ValidWedgeCount * InAttribInfo.tupleSize); - - return ValidWedgeCount; -} - -float -FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) -{ - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = -1.0f; - - // Start by looking at the lod_screensize primitive attribute - bool bAttribValid = false; - UpdatePartLODScreensizeIfNeeded(); - - if (PartLODScreensize.Num() > 0) - { - // use the "lod_screensize" primitive attribute - int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - if (PartLODScreensize.IsValidIndex(FirstValidPrimIndex)) - screensize = PartLODScreensize[FirstValidPrimIndex]; - } - - if (screensize >= 0.0f) - { - // We couldn't find the primitive attribute, look for a "lodX_screensize" detail attribute - FString LODAttributeName = SplitGroupName + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX; - - TArray LODScreenSizes; - HAPI_AttributeInfo AttribInfoScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); - - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*LODAttributeName), - AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_DETAIL); - - if (AttribInfoScreenSize.exists && LODScreenSizes.Num() > 0) - { - screensize = LODScreenSizes[0]; - } - } - - if (screensize >= 0.0f) - { - // finally, look for a potential uproperty style attribute - // aka, "unreal_uproperty_screensize" - TArray LODScreenSizes; - HAPI_AttributeInfo AttribInfoScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); - - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, "unreal_uproperty_screensize", - AttribInfoScreenSize, LODScreenSizes); - - if (AttribInfoScreenSize.exists) - { - if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_DETAIL && LODScreenSizes.Num() > 0) - { - screensize = LODScreenSizes[0]; - } - else if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_PRIM) - { - int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - if (LODScreenSizes.IsValidIndex(FirstValidPrimIndex)) - screensize = LODScreenSizes[FirstValidPrimIndex]; - } - } - } - - // Make sure the screensize is in percent, so if its above 1, divide by 100 - if (screensize > 1.0f) - screensize /= 100.0f; - - return screensize; -} - -int32 -FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - // Calculate bounding Box. - FVector Center, Extents; - FVector unitVec = FVector::OneVector;// bs->BuildScale3D; - CalcBoundingBox(InPositionArray, Center, Extents, unitVec); - - FKBoxElem BoxElem; - BoxElem.Center = Center; - BoxElem.X = Extents.X * 2.0f; - BoxElem.Y = Extents.Y * 2.0f; - BoxElem.Z = Extents.Z * 2.0f; - OutAggregateCollisions.BoxElems.Add(BoxElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FBox Box(ForceInit); - for (const FVector& CurPos : PositionArray) - { - Box += CurPos; - } - Box.GetCenterAndExtents(Center, Extents); -} - -int32 -FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FSphere bSphere, bSphere2, bestSphere; - FVector unitVec = FVector::OneVector; - - // Calculate bounding sphere. - CalcBoundingSphere(InPositionArray, bSphere, unitVec); - CalcBoundingSphere2(InPositionArray, bSphere2, unitVec); - - if (bSphere.W < bSphere2.W) - bestSphere = bSphere; - else - bestSphere = bSphere2; - - // Don't use if radius is zero. - if (bestSphere.W <= 0.f) - { - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Sphere collider.")); - return 0; - } - - FKSphereElem SphereElem; - SphereElem.Center = bestSphere.Center; - SphereElem.Radius = bestSphere.W; - OutAggregateCollisions.SphereElems.Add(SphereElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - if (PositionArray.Num() == 0) - return; - - FBox Box; - FVector MinIx[3]; - FVector MaxIx[3]; - - bool bFirstVertex = true; - for (const FVector& CurPosition : PositionArray) - { - FVector p = CurPosition * LimitVec; - if (bFirstVertex) - { - // First, find AABB, remembering furthest points in each dir. - Box.Min = p; - Box.Max = Box.Min; - - MinIx[0] = CurPosition; - MinIx[1] = CurPosition; - MinIx[2] = CurPosition; - - MaxIx[0] = CurPosition; - MaxIx[1] = CurPosition; - MaxIx[2] = CurPosition; - bFirstVertex = false; - continue; - } - - // X // - if (p.X < Box.Min.X) - { - Box.Min.X = p.X; - MinIx[0] = CurPosition; - } - else if (p.X > Box.Max.X) - { - Box.Max.X = p.X; - MaxIx[0] = CurPosition; - } - - // Y // - if (p.Y < Box.Min.Y) - { - Box.Min.Y = p.Y; - MinIx[1] = CurPosition; - } - else if (p.Y > Box.Max.Y) - { - Box.Max.Y = p.Y; - MaxIx[1] = CurPosition; - } - - // Z // - if (p.Z < Box.Min.Z) - { - Box.Min.Z = p.Z; - MinIx[2] = CurPosition; - } - else if (p.Z > Box.Max.Z) - { - Box.Max.Z = p.Z; - MaxIx[2] = CurPosition; - } - } - - const FVector Extremes[3] = { (MaxIx[0] - MinIx[0]) * LimitVec, - (MaxIx[1] - MinIx[1]) * LimitVec, - (MaxIx[2] - MinIx[2]) * LimitVec }; - - // Now find extreme points furthest apart, and initial center and radius of sphere. - float d2 = 0.f; - for (int32 i = 0; i < 3; i++) - { - const float tmpd2 = Extremes[i].SizeSquared(); - if (tmpd2 > d2) - { - d2 = tmpd2; - sphere.Center = (MinIx[i] + (0.5f * Extremes[i])) * LimitVec; - sphere.W = 0.f; - } - } - - const FVector Extents = FVector(Extremes[0].X, Extremes[1].Y, Extremes[2].Z); - - // radius and radius squared - float r = 0.5f * Extents.GetMax(); - float r2 = FMath::Square(r); - - // Now check each point lies within this sphere. If not - expand it a bit. - for (const FVector& curPos : PositionArray) - { - const FVector cToP = (curPos * LimitVec) - sphere.Center; - - const float pr2 = cToP.SizeSquared(); - - // If this point is outside our current bounding sphere's radius - if (pr2 > r2) - { - // ..expand radius just enough to include this point. - const float pr = FMath::Sqrt(pr2); - r = 0.5f * (r + pr); - r2 = FMath::Square(r); - - sphere.Center += ((pr - r) / pr * cToP); - } - } - - sphere.W = r; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FVector Center, Extents; - CalcBoundingBox(PositionArray, Center, Extents, LimitVec); - - sphere.Center = Center; - sphere.W = 0.0f; - - for (const FVector& curPos : PositionArray) - { - float Dist = FVector::DistSquared(curPos * LimitVec, sphere.Center); - if (Dist > sphere.W) - sphere.W = Dist; - } - sphere.W = FMath::Sqrt(sphere.W); -} - -int32 -FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FSphere sphere; - float length; - FRotator rotation; - FVector unitVec = FVector::OneVector; - - // Calculate bounding sphyl. - CalcBoundingSphyl(InPositionArray, sphere, length, rotation, unitVec); - - // Dont use if radius is zero. - if (sphere.W <= 0.f) - { - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Capsule collider.")); - return 0; - } - - // If height is zero, then a sphere would be better (should we just create one instead?) - if (length <= 0.f) - { - length = SMALL_NUMBER; - } - - FKSphylElem SphylElem; - SphylElem.Center = sphere.Center; - SphylElem.Rotation = rotation; - SphylElem.Radius = sphere.W; - SphylElem.Length = length; - OutAggregateCollisions.SphylElems.Add(SphylElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - if (PositionArray.Num() == 0) - return; - - FVector Center, Extents; - CalcBoundingBox(PositionArray, Center, Extents, LimitVec); - - // @todo sphere.Center could perhaps be adjusted to best fit if model is non-symmetric on it's longest axis - sphere.Center = Center; - - // Work out best axis aligned orientation (longest side) - float Extent = Extents.GetMax(); - if (Extent == Extents.X) - { - rotation = FRotator(90.f, 0.f, 0.f); - Extents.X = 0.0f; - } - else if (Extent == Extents.Y) - { - rotation = FRotator(0.f, 0.f, 90.f); - Extents.Y = 0.0f; - } - else - { - rotation = FRotator(0.f, 0.f, 0.f); - Extents.Z = 0.0f; - } - - // Cleared the largest axis above, remaining determines the radius - float r = Extents.GetMax(); - float r2 = FMath::Square(r); - - // Now check each point lies within this the radius. If not - expand it a bit. - for (const FVector& CurPos : PositionArray) - { - FVector cToP = (CurPos * LimitVec) - sphere.Center; - cToP = rotation.UnrotateVector(cToP); - - const float pr2 = cToP.SizeSquared2D(); // Ignore Z here... - - // If this point is outside our current bounding sphere's radius - if (pr2 > r2) - { - // ..expand radius just enough to include this point. - const float pr = FMath::Sqrt(pr2); - r = 0.5f * (r + pr); - r2 = FMath::Square(r); - } - } - - // The length is the longest side minus the radius. - float hl = FMath::Max(0.0f, Extent - r); - - // Now check each point lies within the length. If not - expand it a bit. - for (const FVector& CurPos : PositionArray) - { - FVector cToP = (CurPos * LimitVec) - sphere.Center; - cToP = rotation.UnrotateVector(cToP); - - // If this point is outside our current bounding sphyl's length - if (FMath::Abs(cToP.Z) > hl) - { - const bool bFlip = (cToP.Z < 0.f ? true : false); - const FVector cOrigin(0.f, 0.f, (bFlip ? -hl : hl)); - - const float pr2 = (cOrigin - cToP).SizeSquared(); - - // If this point is outside our current bounding sphyl's radius - if (pr2 > r2) - { - FVector cPoint; - FMath::SphereDistToLine(cOrigin, r, cToP, (bFlip ? FVector(0.f, 0.f, 1.f) : FVector(0.f, 0.f, -1.f)), cPoint); - - // Don't accept zero as a valid diff when we know it's outside the sphere (saves needless retest on further iterations of like points) - hl += FMath::Max(FMath::Abs(cToP.Z - cPoint.Z), 1.e-6f); - } - } - } - - sphere.W = r; - length = hl * 2.0f; -} - -int32 -FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - const float my_flt_max = 3.402823466e+38F; - - // Do k- specific stuff. - int32 kCount = Dirs.Num(); - - TArray maxDist; - maxDist.Init(-my_flt_max, kCount); - /* - for (int32 i = 0; i < kCount; i++) - maxDist.Add(my_flt_max); - */ - - // Construct temporary UModel for kdop creation. We keep no refs to it, so it can be GC'd. - auto TempModel = NewObject(); - TempModel->Initialize(nullptr, 1); - - // For each vertex, project along each kdop direction, to find the max in that direction. - for (int32 i = 0; i < InPositionArray.Num(); i++) - { - for (int32 j = 0; j < kCount; j++) - { - float dist = InPositionArray[i] | Dirs[j]; - maxDist[j] = FMath::Max(dist, maxDist[j]); - } - } - - // Inflate kdop to ensure it is no degenerate - const float MinSize = 0.1f; - for (int32 i = 0; i < kCount; i++) - { - maxDist[i] += MinSize; - } - - // Now we have the planes of the kdop, we work out the face polygons. - TArray planes; - for (int32 i = 0; i < kCount; i++) - planes.Add(FPlane(Dirs[i], maxDist[i])); - - for (int32 i = 0; i < planes.Num(); i++) - { - FPoly* Polygon = new(TempModel->Polys->Element) FPoly(); - FVector Base, AxisX, AxisY; - - Polygon->Init(); - Polygon->Normal = planes[i]; - Polygon->Normal.FindBestAxisVectors(AxisX, AxisY); - - Base = planes[i] * planes[i].W; - - new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); - - for (int32 j = 0; j < planes.Num(); j++) - { - if (i != j) - { - if (!Polygon->Split(-FVector(planes[j]), planes[j] * planes[j].W)) - { - Polygon->Vertices.Empty(); - break; - } - } - } - - if (Polygon->Vertices.Num() < 3) - { - // If poly resulted in no verts, remove from array - TempModel->Polys->Element.RemoveAt(TempModel->Polys->Element.Num() - 1); - } - else - { - // Other stuff... - Polygon->iLink = i; - Polygon->CalcNormal(1); - } - } - - if (TempModel->Polys->Element.Num() < 4) - { - TempModel = NULL; - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple KDOP collider.")); - return 0; - } - - // Build bounding box. - TempModel->BuildBound(); - - // Build BSP for the brush. - FBSPOps::bspBuild(TempModel, FBSPOps::BSP_Good, 15, 70, 1, 0); - FBSPOps::bspRefresh(TempModel, 1); - FBSPOps::bspBuildBounds(TempModel); - - // Now, create a temporary BodySetup to build the colliders - UBodySetup* TempBS = NewObject(); - TempBS->CreateFromModel(TempModel, false); - - // Copy the convex elements back to our aggregate - int32 NewConvexElems = 0; - if (TempBS && TempBS->AggGeom.ConvexElems.Num() > 0) - { - for (const auto& CurConvexElem : TempBS->AggGeom.ConvexElems) - { - OutAggregateCollisions.ConvexElems.Add(CurConvexElem); - NewConvexElems++; - } - } - - return NewConvexElems; -} - - -bool -FHoudiniMeshTranslator::GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, - TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive property attributes for the given prim - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); - - // .. then finally, point uprop attributes for the given vert - // TODO: !! get the correct Index here? - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidVertexIndex); - - return FoundCount > 0; -} - -bool -FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (auto CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current Property Attribute - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); - } - - return (NumSuccess > 0); -} - - -void -FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO) -{ - PackageParams = InPackageParams; - - if (bUpdateHGPO) - { - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.ObjectId; - PackageParams.PartId = HGPO.ObjectId; - } -} - -bool -FHoudiniMeshTranslator::RemoveAndDestroyComponent(UObject* InComponent) -{ - if (!InComponent || InComponent->IsPendingKill()) - return false; - - USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - return true; - } - - return false; -} - -UMeshComponent* -FHoudiniMeshTranslator::CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf &InComponentType) -{ - // Create a new SMC as we couldn't find an existing one - USceneComponent* OuterSceneComponent = Cast(InOuterComponent); - UObject * Outer = nullptr; - if (OuterSceneComponent && !OuterSceneComponent->IsPendingKill()) - Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); - - UMeshComponent *MeshComponent = NewObject(Outer, InComponentType, NAME_None, RF_Transactional); - - // Initialize it - MeshComponent->SetVisibility(true); - //MeshComponent->SetMobility(Mobility); - - // TODO: - // Property propagation: set the new SMC's properties to the HAC's current settings - //CopyComponentPropertiesTo(MeshComponent); - - // Change the creation method so the component is listed in the details panels - MeshComponent->CreationMethod = EComponentCreationMethod::Instance; - - // Attach created static mesh component to our Houdini component. - MeshComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - MeshComponent->OnComponentCreated(); - MeshComponent->RegisterComponent(); - - return MeshComponent; -} - -bool -FHoudiniMeshTranslator::PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh) -{ - UStaticMesh *Mesh = Cast(InMesh); - if (Mesh) - { - InComponent->SetStaticMesh(Mesh); - - return true; - } - - return false; -} - -bool -FHoudiniMeshTranslator::PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh) -{ - UHoudiniStaticMesh *Mesh = Cast(InMesh); - if (Mesh) - { - InComponent->SetMesh(Mesh); - - return true; - } - - return false; -} - -UMeshComponent* -FHoudiniMeshTranslator::CreateOrUpdateMeshComponent( - const UHoudiniOutput *InOutput, - UObject *InOuterComponent, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TSubclassOf& InComponentType, - FHoudiniOutputObject& OutputObject, - FHoudiniGeoPartObject const *& OutFoundHGPO, - bool& bCreated) -{ - bCreated = false; - OutFoundHGPO = nullptr; - - // Find the HGPO that matches this mesh - for (auto& curHGPO : InOutput->HoudiniGeoPartObjects) - { - if (curHGPO.ObjectId != InOutputIdentifier.ObjectId - || curHGPO.GeoId != InOutputIdentifier.GeoId - || curHGPO.PartId != InOutputIdentifier.PartId) - { - continue; - } - - if (InOutputIdentifier.SplitIdentifier.Equals(HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION) - || curHGPO.SplitGroups.Contains(InOutputIdentifier.SplitIdentifier)) - { - OutFoundHGPO = &curHGPO; - } - } - - // No need to create a component for instanced meshes! - if (OutFoundHGPO && OutFoundHGPO->bIsInstanced) - return nullptr; - - bool bIsProxyComponent = InComponentType == UHoudiniStaticMeshComponent::StaticClass(); - - // See if we already have a component for that mesh - UMeshComponent* MeshComponent = nullptr; - if (bIsProxyComponent) - MeshComponent = Cast(OutputObject.ProxyComponent); - else - MeshComponent = Cast(OutputObject.OutputComponent); - - // If there is an existing component, but it is pending kill, then it was likely - // deleted by some other process, such as by the user in the editor, so don't use it - if (!MeshComponent || MeshComponent->IsPendingKill() || !MeshComponent->IsA(InComponentType)) - { - // If the component is not of type InComponentType, or the found component is pending kill, destroy - // the existing component (a new one is then created below) - RemoveAndDestroyComponent(MeshComponent); - MeshComponent = nullptr; - } - - if (!MeshComponent) - { - // Create a new SMC/HSMC as we couldn't find an existing one - MeshComponent = CreateMeshComponent(InOuterComponent, InComponentType); - - if (MeshComponent) - { - // Add to the output object - if (bIsProxyComponent) - OutputObject.ProxyComponent = MeshComponent; - else - OutputObject.OutputComponent = MeshComponent; - - bCreated = true; - } - } - - return MeshComponent; -} - -bool -FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, - TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors) -{ - if (!Socket || Socket->IsPendingKill() || !StaticMeshComponent || StaticMeshComponent->IsPendingKill()) - return false; - - // The actor to assign is stored is the socket's tag - FString ActorString = Socket->Tag; - if (ActorString.IsEmpty()) - return false; - - // The actor to assign are listed after a | - TArray ActorStringArray; - ActorString.ParseIntoArray(ActorStringArray, TEXT("|"), false); - - // The "real" Tag is the first - if (ActorStringArray.Num() > 0) - Socket->Tag = ActorStringArray[0]; - - // We just add a Tag, no Actor - if (ActorStringArray.Num() == 1) - return false; - - // Extract the parsed actor string to split it further - ActorString = ActorStringArray[1]; - - // Converting the string to a string array using delimiters - const TCHAR* Delims[] = { TEXT(","), TEXT(";") }; - ActorString.ParseIntoArray(ActorStringArray, Delims, 2); - - // And try to find the corresponding HoudiniAssetActor in the editor world - // to avoid finding "deleted" assets with the same name - //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); -#if WITH_EDITOR - UWorld* EditorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; - if (!EditorWorld || EditorWorld->IsPendingKill()) - return false; - - // Remove the previous created actors which were attached to this socket - { - for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - continue; - } - - if (CurActor->GetAttachParentSocketName() == Socket->SocketName) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - CurActor->Destroy(); - } - } - } - - // Detach the previous in level actors which was attached to this socket - { - for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniAttachedSocketActors.RemoveAt(Idx); - continue; - } - - if (CurActor->GetAttachParentSocketName() == Socket->SocketName) - { - CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - HoudiniAttachedSocketActors.RemoveAt(Idx); - } - } - } - - auto CreateDefaultActor = [EditorWorld, StaticMeshComponent, Socket, HoudiniCreatedSocketActors]() - { - AActor * CreatedDefaultActor = nullptr; - - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) - { - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( - EditorWorld->GetCurrentLevel(), DefaultReferenceSM, false, RF_Transactional, nullptr); - - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load default mesh.")); - } - else - { - - // Set the default mesh actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : NewActors[0]->GetComponents()) - { - UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) - CurSMC->SetMobility(OutputSMCMobility); - } - - // Set the default mesh actor hidden in game. - NewActors[0]->SetActorHiddenInGame(true); - - Socket->AttachActor(NewActors[0], StaticMeshComponent); - CreatedDefaultActor = NewActors[0]; - //HoudiniCreatedSocketActors.Add(NewActors[0]); - } - } - else - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load default mesh.")); - } - - return CreatedDefaultActor; - }; - - bool bUseDefaultActor = true; - // Get from the Houdini runtime setting if use default object when the reference is invalid - // true by default if fail to access HoudiniRuntimeSettings - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bUseDefaultActor = HoudiniRuntimeSettings->bShowDefaultMesh; - } - - if (ActorStringArray.Num() <= 0) - { - if (!bUseDefaultActor) - return true; - - HOUDINI_LOG_WARNING( - TEXT("Output static mesh: Socket '%s' actor is not specified. Spawn a default mesh (hidden in game)."), *(Socket->GetName())); - - AActor * DefaultActor = CreateDefaultActor(); - if (DefaultActor && !DefaultActor->IsPendingKill()) - HoudiniCreatedSocketActors.Add(DefaultActor); - - return true; - } - - // try to find the actor in level first - for (TActorIterator ActorItr(EditorWorld); ActorItr; ++ActorItr) - { - // Same as with the Object Iterator, access the subclass instance with the * or -> operators. - AActor *Actor = *ActorItr; - if (!Actor || Actor->IsPendingKillOrUnreachable()) - continue; - - for (int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++) - { - if (Actor->GetName() != ActorStringArray[StringIdx] - && Actor->GetActorLabel() != ActorStringArray[StringIdx]) - continue; - - // Set the actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : Actor->GetComponents()) - { - UStaticMeshComponent * SMC = Cast(CurComp); - if (SMC && !SMC->IsPendingKill()) - SMC->SetMobility(OutputSMCMobility); - } - - Socket->AttachActor(Actor, StaticMeshComponent); - HoudiniAttachedSocketActors.Add(Actor); - - // Remove the string if the actor is found in the editor level - ActorStringArray.RemoveAt(StringIdx); - break; - } - } - - bool bSuccess = true; - // If some of the actors are not found in the level, try to find them in the content browser. Spawn one if existed - for (int32 Idx = ActorStringArray.Num() - 1; Idx>= 0; --Idx) - { - UObject * Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *ActorStringArray[Idx]); - if (!Obj || Obj->IsPendingKill()) - { - bSuccess = false; - continue; - } - - // Spawn a new actor with the found object - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( - EditorWorld->GetCurrentLevel(), Obj, false, RF_Transactional, nullptr); - - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) - { - bSuccess = false; - continue; - } - - // Set the new actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : NewActors[0]->GetComponents()) - { - UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) - CurSMC->SetMobility(OutputSMCMobility); - } - - Socket->AttachActor(NewActors[0], StaticMeshComponent); - HoudiniCreatedSocketActors.Add(NewActors[0]); - - ActorStringArray.RemoveAt(Idx); - } - - // Failed to find actors in both level and content browser - // Spawn default actors if enabled - if (bUseDefaultActor) - { - for (int32 Idx = ActorStringArray.Num() - 1; Idx >= 0; --Idx) - { - HOUDINI_LOG_WARNING( - TEXT("Output static mesh: Failed to attach '%s' to socket '%s', spawn a default mesh (hidden in game)."), *(ActorStringArray[Idx]), *(Socket->GetName())); - - // If failed to load this object, spawn a default mesh - AActor * CurDefaultActor = CreateDefaultActor(); - if (CurDefaultActor && !CurDefaultActor->IsPendingKill()) - HoudiniCreatedSocketActors.Add(CurDefaultActor); - } - } - - if (ActorStringArray.Num() > 0) - return false; -#endif - - return bSuccess; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniMeshTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniOutput.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniMaterialTranslator.h" +#include "HoudiniAssetActor.h" + +#include "HoudiniStaticMesh.h" +#include "HoudiniStaticMeshComponent.h" +#include "Engine/StaticMeshSocket.h" + +#include "PhysicsEngine/BodySetup.h" +#include "Engine/StaticMesh.h" +#include "PackageTools.h" +#include "RawMesh.h" +#include "Materials/MaterialInterface.h" +#include "Materials/Material.h" +#include "MeshDescription.h" +#include "StaticMeshAttributes.h" +#include "MeshDescriptionOperations.h" + +#include "BSPOps.h" +#include "Model.h" +#include "Engine/Polys.h" +#include "AssetRegistryModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "AI/Navigation/NavCollisionBase.h" +#include "ObjectTools.h" + +// #include "Async/ParallelFor.h" + +#include "ProfilingDebugging/CpuProfilerTrace.h" + +#include "EditorSupportDelegates.h" + +#if WITH_EDITOR + #include "UnrealEd/Private/ConvexDecompTool.h" + #include "Editor/UnrealEd/Private/GeomFitUtils.h" + #include "LevelEditorViewport.h" + #include "FileHelpers.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// +bool +FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + UHoudiniOutput* InOutput, + const FHoudiniPackageParams& InPackageParams, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + UObject* InOuterComponent, + bool bInTreatExistingMaterialsAsUpToDate, + bool bInDestroyProxies) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InPackageParams.OuterPackage || InPackageParams.OuterPackage->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + TMap NewOutputObjects; + TMap OldOutputObjects = InOutput->GetOutputObjects(); + TMap& AssignementMaterials = InOutput->GetAssignementMaterials(); + TMap& ReplacementMaterials = InOutput->GetReplacementMaterials(); + + bool InForceRebuild = false; + if (InOutput->HasAnyCurrentProxy() && InStaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh) + { + // Make sure we're not preventing refinement + InForceRebuild = true; + } + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) + { + // Not a mesh, skip + if (CurHGPO.Type != EHoudiniPartType::Mesh) + continue; + + CreateStaticMeshFromHoudiniGeoPartObject( + CurHGPO, + InPackageParams, + OldOutputObjects, + NewOutputObjects, + AssignementMaterials, + ReplacementMaterials, + InForceRebuild, + InStaticMeshMethod, + InSMGenerationProperties, + bInTreatExistingMaterialsAsUpToDate); + } + + return FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + InOutput, + InOuterComponent, + NewOutputObjects, + bInDestroyProxies); +} + +bool +FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + UHoudiniOutput* InOutput, + UObject* InOuterComponent, + TMap& InNewOutputObjects, + bool bInDestroyProxies, + bool bInApplyGenericProperties) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + TMap OldOutputObjects = InOutput->GetOutputObjects(); + + // Remove Static Meshes and their components from the old map + // to avoid their deletion if new proxies were created for them + for (auto& NewOutputObj : InNewOutputObjects) + { + FHoudiniOutputObjectIdentifier OutputIdentifier = NewOutputObj.Key; + + // See if we already had that pair in the old map of static mesh + FHoudiniOutputObject* FoundOldOutputObj = OldOutputObjects.Find(NewOutputObj.Key); + if (!FoundOldOutputObj) + continue; + + UObject* NewStaticMesh = NewOutputObj.Value.OutputObject; + UObject* NewProxyMesh = NewOutputObj.Value.ProxyObject; + + UObject* OldStaticMesh = FoundOldOutputObj->OutputObject; + if (OldStaticMesh && !OldStaticMesh->IsPendingKill()) + { + // If a proxy was created for an existing static mesh, keep the existing static + // mesh (will be hidden) + if (NewProxyMesh && NewOutputObj.Value.bProxyIsCurrent) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + else if (NewStaticMesh && NewStaticMesh == OldStaticMesh) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + + UObject* OldProxyMesh = FoundOldOutputObj->ProxyObject; + if (OldProxyMesh && !OldProxyMesh->IsPendingKill()) + { + // If a new static mesh was created for a proxy, keep the proxy (will be hidden) + // ... unless we want to explicitly destroy proxies + if (NewStaticMesh && !bInDestroyProxies) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + else if (NewProxyMesh && (NewProxyMesh == OldProxyMesh)) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + } + + // The old map now only contains unused/stale Meshes/Components, delete them + for (auto& OldPair : OldOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; + FHoudiniOutputObject& OldOutputObject = OldPair.Value; + + // Remove the old component from the map + RemoveAndDestroyComponent(OldOutputObject.OutputComponent); + OldOutputObject.OutputComponent = nullptr; + // Remove the old proxy component from the map + RemoveAndDestroyComponent(OldOutputObject.ProxyComponent); + OldOutputObject.ProxyComponent = nullptr; + + if (OldOutputObject.OutputObject && !OldOutputObject.OutputObject->IsPendingKill()) + { + OldOutputObject.OutputObject->MarkPendingKill(); + } + + if (OldOutputObject.ProxyObject && !OldOutputObject.ProxyObject->IsPendingKill()) + { + OldOutputObject.ProxyObject->MarkPendingKill(); + } + } + OldOutputObjects.Empty(); + + /* + // Remove any stale components, these are components with OutputIdentifiers that are not + // in NewOutputObjects. This seems to happen mostly with the first or second cook after a + // "Rebuild Asset" + if (OutputComponents.Num() > 0 || OutputProxyComponents.Num() > 0) + { + TArray> StaleComponents; + const uint32 MaxNumStale = FMath::Max(OutputComponents.Num(), OutputProxyComponents.Num()); + StaleComponents.Reserve(MaxNumStale); + for (auto& ComponentPair : OutputComponents) + { + if (!NewOutputObjects.Contains(ComponentPair.Key) && !OldOutputObjectsReplacedByProxy.Contains(ComponentPair.Key)) + { + StaleComponents.Add(ComponentPair); + } + } + for (auto& ComponentPair : StaleComponents) + { + RemoveAndDestroyComponent(ComponentPair.Key, OutputComponents); + } + StaleComponents.Empty(MaxNumStale); + + for (auto& ComponentPair : OutputProxyComponents) + { + if (!NewOutputProxyObjects.Contains(ComponentPair.Key) && !OldOutputProxyObjectsReplacedByStaticMesh.Contains(ComponentPair.Key)) + { + StaleComponents.Add(ComponentPair); + } + } + for (auto& ComponentPair : StaleComponents) + { + RemoveAndDestroyComponent(ComponentPair.Key, OutputProxyComponents); + } + StaleComponents.Empty(); + } + */ + + // Now create/update the new static mesh components + for (auto& NewPair : InNewOutputObjects) + { + // Get the old Identifier / StaticMesh + const FHoudiniOutputObjectIdentifier& OutputIdentifier = NewPair.Key; + FHoudiniOutputObject& OutputObject = NewPair.Value; + + // Check if we should create a Proxy/SMC + if (OutputObject.bProxyIsCurrent) + { + UObject *Mesh = OutputObject.ProxyObject; + if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + { + HOUDINI_LOG_ERROR(TEXT("Proxy Mesh is invalid (wrong type or pending kill)...")); + continue; + } + + // Create or update a new proxy component + TSubclassOf ComponentType = UHoudiniStaticMeshComponent::StaticClass(); + const FHoudiniGeoPartObject *FoundHGPO = nullptr; + bool bCreated = false; + UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); + if (MeshComponent) + { + UHoudiniStaticMeshComponent *HSMC = Cast(MeshComponent); + + if (bCreated) + { + PostCreateHoudiniStaticMeshComponent(HSMC, Mesh); + } + else if (HSMC && !HSMC->IsPendingKill() && HSMC->GetMesh() != Mesh) + { + // We need to reassign the HSM to the component + UHoudiniStaticMesh* HSM = Cast(Mesh); + HSMC->SetMesh(HSM); + } + + UpdateMeshComponent( + MeshComponent, + OutputIdentifier, + FoundHGPO, + InOutput->HoudiniCreatedSocketActors, + InOutput->HoudiniAttachedSocketActors, + bInApplyGenericProperties); + + if (!bCreated) + { + // For proxy meshes: notify that the mesh has been updated + HSMC->NotifyMeshUpdated(); + HSMC->SetHoudiniIconVisible(true); + } + } + + // Now, ensure that meshes replaced by proxies are still kept but hidden + USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); + if (SceneComponent) + { + SceneComponent->SetVisibility(false); + SceneComponent->SetHiddenInGame(true); + } + + // If the proxy mesh we just created is templated, hide it in game + if (FoundHGPO->bIsTemplated) + { + MeshComponent->SetHiddenInGame(true); + } + } + else + { + // Create a new SMC if needed + UObject* Mesh = OutputObject.OutputObject; + if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + { + HOUDINI_LOG_ERROR(TEXT("Mesh is invalid (wrong type or pending kill)...")); + continue; + } + + TSubclassOf ComponentType = UStaticMeshComponent::StaticClass(); + const FHoudiniGeoPartObject *FoundHGPO = nullptr; + bool bCreated = false; + UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); + if (MeshComponent) + { + if (bCreated) + { + PostCreateStaticMeshComponent(Cast(MeshComponent), Mesh); + } + UpdateMeshComponent( + MeshComponent, + OutputIdentifier, + FoundHGPO, + InOutput->HoudiniCreatedSocketActors, + InOutput->HoudiniAttachedSocketActors, + bInApplyGenericProperties); + } + + // Now, ensure that proxies replaced by meshes are still kept but hidden + UHoudiniStaticMeshComponent *HSMC = Cast(OutputObject.ProxyComponent); + if (HSMC) + { + HSMC->SetVisibility(false); + HSMC->SetHiddenInGame(true); + HSMC->SetHoudiniIconVisible(false); + } + + // If the mesh we just created is templated, hide it in game + if (FoundHGPO->bIsTemplated) + { + MeshComponent->SetHiddenInGame(true); + } + } + } + + // Assign the new output objects to the output + InOutput->SetOutputObjects(InNewOutputObjects); + + return true; +} + +void +FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, + const FHoudiniGeoPartObject *InHGPO, TArray &HoudiniCreatedSocketActors, TArray &HoudiniAttachedSocketActors, + bool bInApplyGenericProperties) +{ + // Update collision/visibility + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(InOutputIdentifier.SplitIdentifier); + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider) + { + // Invisible complex collider should not be seen + InMeshComponent->SetVisibility(false); + InMeshComponent->SetHiddenInGame(true); + InMeshComponent->SetCollisionProfileName(FName(TEXT("InvisibleWall"))); + InMeshComponent->SetCastShadow(false); + } + else + { + // Update visiblity + bool bVisible = InHGPO ? InHGPO->bIsVisible : true; + InMeshComponent->SetVisibility(bVisible); + InMeshComponent->SetHiddenInGame(!bVisible); + } + + // TODO: + // Update navmesh? + + // Transform the component by transformation provided by HAPI. + InMeshComponent->SetRelativeTransform(InHGPO->TransformMatrix); + + // If the static mesh had sockets, we can assign the desired actor to them now + UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); + UStaticMesh * StaticMesh = nullptr; + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + StaticMesh = StaticMeshComponent->GetStaticMesh(); + + if (StaticMesh && !StaticMesh->IsPendingKill()) + { + 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; + + AddActorsToMeshSocket(StaticMesh->Sockets[nSocket], StaticMeshComponent, HoudiniCreatedSocketActors, HoudiniAttachedSocketActors); + } + + // Iterate all remaining created socket actors, destroy the ones that are not assigned to socket after re-cook + { + for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniCreatedSocketActors[Idx]; + + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + continue; + } + + bool bFoundSocket = false; + for (auto & CurSocket : StaticMesh->Sockets) + { + if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) + { + bFoundSocket = true; + break; + } + } + // cur actor's attaching socket is found, skip + if (bFoundSocket) + continue; + + // Destroy the previous created socket actor if not found + HoudiniCreatedSocketActors.RemoveAt(Idx); + CurActor->Destroy(); + } + } + + // Detach the in level actors which is not attached to any socket now + { + for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor* CurActor = HoudiniAttachedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniAttachedSocketActors.RemoveAt(Idx); + continue; + } + + bool bFoundSocket = false; + for (auto & CurSocket : StaticMesh->Sockets) + { + if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) + { + bFoundSocket = true; + break; + } + } + + if (bFoundSocket) + continue; + + // If the attached socket name is not found in current socket, detach it and remove from the array + CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + HoudiniAttachedSocketActors.RemoveAt(Idx); + } + } + + } + + if (bInApplyGenericProperties) + { + // Clear the component tags as generic properties only add them + InMeshComponent->ComponentTags.Empty(); + // Update the property attributes on the component + TArray PropertyAttributes; + if (GetGenericPropertiesAttributes( + InOutputIdentifier.GeoId, InOutputIdentifier.PartId, + InOutputIdentifier.PointIndex, InOutputIdentifier.PrimitiveIndex, + PropertyAttributes)) + { + UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); + } + } +} + +bool +FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& InOutputObjects, + TMap& OutOutputObjects, + TMap& AssignmentMaterialMap, + TMap& ReplacementMaterialMap, + const bool& InForceRebuild, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + bool bInTreatExistingMaterialsAsUpToDate) +{ + // If we're not forcing the rebuild + // No need to recreate something that hasn't changed + if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged) && InOutputObjects.Num() > 0) + { + // Simply reuse the existing meshes + OutOutputObjects = InOutputObjects; + return true; + } + + FHoudiniMeshTranslator CurrentTranslator; + CurrentTranslator.ForceRebuild = InForceRebuild; + CurrentTranslator.SetHoudiniGeoPartObject(InHGPO); + CurrentTranslator.SetInputObjects(InOutputObjects); + CurrentTranslator.SetOutputObjects(OutOutputObjects); + CurrentTranslator.SetInputAssignmentMaterials(AssignmentMaterialMap); + CurrentTranslator.SetReplacementMaterials(ReplacementMaterialMap); + CurrentTranslator.SetPackageParams(InPackageParams, true); + CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); + CurrentTranslator.SetStaticMeshGenerationProperties(InSMGenerationProperties); + + // TODO: Fetch from settings/HAC + CurrentTranslator.DefaultMeshSmoothing = 1; + if (false) + CurrentTranslator.DefaultMeshSmoothing = 0; + + // TODO: mechanism to determine when to use dynamic mesh for fast updates, and when to switch to + // baking the full static mesh + switch (InStaticMeshMethod) + { + case EHoudiniStaticMeshMethod::RawMesh: + CurrentTranslator.CreateStaticMesh_RawMesh(); + break; + case EHoudiniStaticMeshMethod::FMeshDescription: + CurrentTranslator.CreateStaticMesh_MeshDescription(); + break; + case EHoudiniStaticMeshMethod::UHoudiniStaticMesh: + CurrentTranslator.CreateHoudiniStaticMesh(); + break; + } + + // Copy the output objects/materials + OutOutputObjects = CurrentTranslator.OutputObjects; + AssignmentMaterialMap = CurrentTranslator.OutputAssignmentMaterials; + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartVertexList() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartVertexList")); + + if (HGPO.PartInfo.VertexCount <= 0) + return false; + + // Get the vertex List + PartVertexList.SetNumUninitialized(HGPO.PartInfo.VertexCount); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, &PartVertexList[0], 0, HGPO.PartInfo.VertexCount)) + { + // Error getting the vertex list. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + + return false; + } + + return true; +} + +void +FHoudiniMeshTranslator::SortSplitGroups() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::SortSplitGroups")); + + // Sort the splits in the order that we want to process them: + // Simple/Convex invisible colliders should be treated first as they will need to be attached to the visible meshes + TArray First; + + // The main geo and its LODs should be created after. + TArray Main; + TArray LODs; + + // Finally, visible colliders and invisible complex colliders as they need their own static mesh + TArray Last; + + for (auto& curSplit : HGPO.SplitGroups) + { + EHoudiniSplitType curSplitType = GetSplitTypeFromSplitName(curSplit); + switch (curSplitType) + { + case EHoudiniSplitType::InvisibleSimpleCollider: + case EHoudiniSplitType::InvisibleUCXCollider: + First.Add(curSplit); + break; + + case EHoudiniSplitType::Normal: + Main.Add(curSplit); + break; + + case EHoudiniSplitType::LOD: + LODs.Add(curSplit); + break; + + case EHoudiniSplitType::RenderedSimpleCollider: + case EHoudiniSplitType::RenderedUCXCollider: + case EHoudiniSplitType::RenderedComplexCollider: + case EHoudiniSplitType::InvisibleComplexCollider: + Last.Add(curSplit); + break; + } + } + + // Make sure LODs are order by name + LODs.Sort(); + + // Copy the split names in order + AllSplitGroups.Empty(); + for (auto& splitName : First) + AllSplitGroups.Add(splitName); + + for (auto& splitName : Main) + AllSplitGroups.Add(splitName); + + for (auto& splitName : LODs) + AllSplitGroups.Add(splitName); + + for (auto& splitName : Last) + AllSplitGroups.Add(splitName); +} + +bool +FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices")); + + // Reset the splits faces/indices arrays + AllSplitVertexLists.Empty(); + AllSplitVertexCounts.Empty(); + AllSplitFaceIndices.Empty(); + AllSplitFirstValidVertexIndex.Empty(); + AllSplitFirstValidPrimIndex.Empty(); + + bool bHasSplit = AllSplitGroups.Num() > 0; + if (bHasSplit) + { + HAPI_PartInfo PartInfo = FHoudiniEngineUtils::ToHAPIPartInfo(HGPO.PartInfo); + + // Buffer for all vertex indices used for split groups. + // We need this to figure out all vertex indices that are not part of them. + TArray AllVertexList; + AllVertexList.SetNumZeroed(PartVertexList.Num()); + + // Buffer for all face indices used for split groups. + // We need this to figure out all face indices that are not part of them. + TArray AllGroupFaceIndices; + AllGroupFaceIndices.SetNumZeroed(HGPO.PartInfo.FaceCount); + + // Some of the groups may contain invalid geometry + // Store them here so we can remove them afterwards + TArray InvalidGroupNameIndices; + + // Extract the vertices/faces for each of the split groups + for (int32 SplitIdx = 0; SplitIdx < AllSplitGroups.Num(); SplitIdx++) + { + const FString& GroupName = AllSplitGroups[SplitIdx]; + + // New vertex list just for this group. + TArray< int32 > GroupVertexList; + TArray< int32 > AllFaceList; + + int32 FirstValidPrimIndex = 0; + int32 FirstValidVertexIndex = 0; + // Extract vertex indices for this split. + int32 GroupVertexListCount = FHoudiniEngineUtils::HapiGetVertexListForGroup( + HGPO.GeoId, PartInfo, GroupName, + PartVertexList, GroupVertexList, + AllVertexList, AllFaceList, AllGroupFaceIndices, + FirstValidVertexIndex, FirstValidPrimIndex, + HGPO.PartInfo.bIsInstanced); + + if (GroupVertexListCount <= 0) + { + // This group doesn't have vertices/faces, mark it as invalid + InvalidGroupNameIndices.Add(SplitIdx); + + // Error getting the vertex list. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list for group %s - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, *GroupName); + + continue; + } + + // If list is not empty, we store it for this group - this will define new mesh. + AllSplitVertexLists.Add(GroupName, GroupVertexList); + AllSplitVertexCounts.Add(GroupName, GroupVertexListCount); + AllSplitFaceIndices.Add(GroupName, AllFaceList); + AllSplitFirstValidVertexIndex.Add(GroupName, FirstValidVertexIndex); + AllSplitFirstValidPrimIndex.Add(GroupName, FirstValidPrimIndex); + } + + if (InvalidGroupNameIndices.Num() > 0) + { + // Remove all invalid split groups + for (int32 InvalIdx = InvalidGroupNameIndices.Num() - 1; InvalIdx >= 0; InvalIdx--) + { + int32 Index = InvalidGroupNameIndices[InvalIdx]; + AllSplitGroups.RemoveAt(Index); + } + } + + // We also need to figure out / construct the vertex list for everything that's not in a split group + TArray GroupSplitFacesRemaining; + GroupSplitFacesRemaining.Init(-1, PartVertexList.Num()); + + int32 GroupVertexListCount = 0; + bool bHasMainSplitGroup = false; + TArray< int32 > GroupSplitFaceIndicesRemaining; + int32 FistUnusedVertexIndex = -1; + for (int32 SplitVertexIdx = 0; SplitVertexIdx < AllVertexList.Num(); SplitVertexIdx++) + { + if (AllVertexList[SplitVertexIdx] == 0) + { + // This is an unused index, we need to add it to unused vertex list. + FistUnusedVertexIndex = SplitVertexIdx; + GroupSplitFacesRemaining[SplitVertexIdx] = PartVertexList[SplitVertexIdx]; + bHasMainSplitGroup = true; + GroupVertexListCount++; + } + } + + int32 FistUnusedPrimIndex = -1; + for (int32 SplitFaceIdx = 0; SplitFaceIdx < AllGroupFaceIndices.Num(); SplitFaceIdx++) + { + if (AllGroupFaceIndices[SplitFaceIdx] == 0) + { + // This is unused face, we need to add it to unused faces list. + GroupSplitFaceIndicesRemaining.Add(SplitFaceIdx); + FistUnusedPrimIndex = SplitFaceIdx; + } + } + + // We store the remaining geo vertex list as a special split named "main geo" + // and make sure its treated before the collider meshes + if (bHasMainSplitGroup) + { + static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + AllSplitGroups.Add(RemainingGroupName); + AllSplitVertexLists.Add(RemainingGroupName, GroupSplitFacesRemaining); + AllSplitVertexCounts.Add(RemainingGroupName, GroupVertexListCount); + AllSplitFaceIndices.Add(RemainingGroupName, GroupSplitFaceIndicesRemaining); + AllSplitFirstValidPrimIndex.Add(RemainingGroupName, FistUnusedPrimIndex); + AllSplitFirstValidVertexIndex.Add(RemainingGroupName, FistUnusedVertexIndex); + } + } + else + { + // No splitting required + // Mark everything as the main geo group + static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + AllSplitGroups.Add(RemainingGroupName); + AllSplitVertexLists.Add(RemainingGroupName, PartVertexList); + AllSplitVertexCounts.Add(RemainingGroupName, PartVertexList.Num()); + AllSplitFirstValidPrimIndex.Add(RemainingGroupName, 0); + AllSplitFirstValidVertexIndex.Add(RemainingGroupName, 0); + + TArray AllFaces; + for (int32 FaceIdx = 0; FaceIdx < HGPO.PartInfo.FaceCount; ++FaceIdx) + AllFaces.Add(FaceIdx); + + AllSplitFaceIndices.Add(RemainingGroupName, AllFaces); + } + + return true; +} + +void +FHoudiniMeshTranslator::ResetPartCache() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::ResetPartCache")); + + // Vertex Positions + PartPositions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Vertex Normals + PartNormals.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Vertex TangentU + PartTangentU.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentU); + + // Vertex TangentV + PartTangentV.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentV); + + // Vertex Colors + PartColors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoColors); + + // Vertex Alpha values + PartAlphas.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoAlpha); + + // FaceSmoothing values + PartFaceSmoothingMasks.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceSmoothingMasks); + + // UVs + PartUVSets.Empty(); + AttribInfoUVSets.Empty(); + + // UVs + PartLightMapResolutions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoLightmapResolution); + + // Material IDs per face + PartFaceMaterialIds.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialIds); + // Unique material IDs + PartUniqueMaterialIds.Empty(); + // Material infos for each unique Material + PartUniqueMaterialInfos.Empty(); + // + bOnlyOneFaceMaterial = false; + + // Face Materials override + PartFaceMaterialOverrides.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialOverrides); + bMaterialOverrideNeedsCreateInstance = false; + + // LOD Screensize + PartLODScreensize.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoLODScreensize); +} + +bool +FHoudiniMeshTranslator::UpdatePartPositionIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartPositionIfNeeded")); + + // Only Retrieve the vertices positions if necessary + if (PartPositions.Num() > 0) + return true; + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve position data") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded")); + + // No need to read the normals if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadNormals = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + if (!bReadNormals) + return true; + + // Only Retrieve the normals if we haven't already + if (PartNormals.Num() > 0) + return true; + + // Retrieve normal data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, PartNormals); + + // There is no normals to fetch + if (!AttribInfoNormals.exists) + return true; + + if (!Success && AttribInfoNormals.exists) + { + // Error retrieving normals. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve normal data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded")) + + bool bReturn = true; + if (PartTangentU.Num() <= 0) + { + // Retrieve TangentU data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_TANGENTU, AttribInfoTangentU, PartTangentU); + + if (!Success && AttribInfoTangentU.exists) + { + // Error retrieving tangent. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentU data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + bReturn = false; + } + } + + if (PartTangentV.Num() <= 0) + { + // Retrieve TangentV data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_TANGENTV, AttribInfoTangentV, PartTangentV); + + if (!Success && AttribInfoTangentV.exists) + { + // Error retrieving tangent. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentV data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + bReturn = false; + } + } + + return bReturn; +} + +bool +FHoudiniMeshTranslator::UpdatePartColorsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartColorsIfNeeded")); + + // Only Retrieve the vertices colors if necessary + if (PartColors.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_COLOR, AttribInfoColors, PartColors); + + if (!Success && AttribInfoColors.exists) + { + // Error retrieving colors. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve color data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded")); + + // Only Retrieve the vertices alphas if necessary + if (PartAlphas.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_ALPHA, AttribInfoAlpha, PartAlphas); + + if (!Success && AttribInfoAlpha.exists) + { + // Error retrieving alpha values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve alpha data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceSmoothingIfNeeded() +{ + // Only Retrieve the vertices FaceSmoothing if necessary + if (PartFaceSmoothingMasks.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, + AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks); + + if (!Success && AttribInfoFaceSmoothingMasks.exists) + { + // Error retrieving FaceSmoothing values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve FaceSmoothing data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded")); + + // Only Retrieve uvs if necessary + if (PartUVSets.Num() > 0) + return true; + + PartUVSets.SetNum(MAX_STATIC_TEXCOORDS); + AttribInfoUVSets.SetNum(MAX_STATIC_TEXCOORDS); + + // The second UV set should be called uv2, but we will still check if need to look for a uv1 set. + // If uv1 exists, we'll look for uv, uv1, uv2 etc.. if not we'll look for uv, uv2, uv3 etc.. + bool bUV1Exists = FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, "uv1"); + + // Retrieve UVs. + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (TexCoordIdx > 0) + UVAttributeName += FString::Printf(TEXT("%d"), bUV1Exists ? TexCoordIdx : TexCoordIdx + 1); + + FHoudiniApi::AttributeInfo_Init(&AttribInfoUVSets[TexCoordIdx]); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*UVAttributeName), + AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], 2); + } + + // Also look for 16.5 uvs (attributes with a Texture type) + // For that, we'll have to iterate through ALL the attributes and check their types + TArray< FString > FoundAttributeNames; + TArray< HAPI_AttributeInfo > FoundAttributeInfos; + + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + FHoudiniEngineUtils::HapiGetAttributeOfType( + HGPO.GeoId, HGPO.PartId, (HAPI_AttributeOwner)AttrIdx, + HAPI_ATTRIBUTE_TYPE_TEXTURE, FoundAttributeInfos, FoundAttributeNames); + } + + if (FoundAttributeInfos.Num() <= 0) + return true; + + // We found some additionnal uv attributes + int32 AvailableIdx = 0; + for (int32 attrIdx = 0; attrIdx < FoundAttributeInfos.Num(); attrIdx++) + { + // Ignore the old uvs + if (FoundAttributeNames[attrIdx] == TEXT("uv") + || FoundAttributeNames[attrIdx] == TEXT("uv1") + || FoundAttributeNames[attrIdx] == TEXT("uv2") + || FoundAttributeNames[attrIdx] == TEXT("uv3") + || FoundAttributeNames[attrIdx] == TEXT("uv4") + || FoundAttributeNames[attrIdx] == TEXT("uv5") + || FoundAttributeNames[attrIdx] == TEXT("uv6") + || FoundAttributeNames[attrIdx] == TEXT("uv7") + || FoundAttributeNames[attrIdx] == TEXT("uv8")) + continue; + + HAPI_AttributeInfo CurrentAttrInfo = FoundAttributeInfos[attrIdx]; + if (!CurrentAttrInfo.exists) + continue; + + // Look for the next available index in the return arrays + for (; AvailableIdx < AttribInfoUVSets.Num(); AvailableIdx++) + { + if (!AttribInfoUVSets[AvailableIdx].exists) + break; + } + + // We are limited to MAX_STATIC_TEXCOORDS uv sets! + // If we already have too many uv sets, skip the rest + if ((AvailableIdx >= MAX_STATIC_TEXCOORDS) || (AvailableIdx >= AttribInfoUVSets.Num())) + { + HOUDINI_LOG_WARNING(TEXT("Too many UV sets found. Unreal only supports %d , skipping the remaining uv sets."), (int32)MAX_STATIC_TEXCOORDS); + break; + } + + // Force the tuple size to 2 ? + CurrentAttrInfo.tupleSize = 2; + + // Add the attribute infos we found + AttribInfoUVSets[AvailableIdx] = CurrentAttrInfo; + + // Allocate sufficient buffer for the attribute's data. + PartUVSets[AvailableIdx].SetNumUninitialized(CurrentAttrInfo.count * CurrentAttrInfo.tupleSize); + + // Get the texture coordinates + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, TCHAR_TO_UTF8(*(FoundAttributeNames[attrIdx])), + &AttribInfoUVSets[AvailableIdx], -1, + &PartUVSets[AvailableIdx][0], 0, CurrentAttrInfo.count)) + { + // Something went wrong when trying to access the uv values, invalidate this set + AttribInfoUVSets[AvailableIdx].exists = false; + } + } + + // Remove unused UV sets + if (bRemoveUnused) + { + for (int32 Idx = PartUVSets.Num() - 1; Idx >= 0; Idx--) + { + if (PartUVSets[Idx].Num() > 0) + continue; + + PartUVSets.RemoveAt(Idx); + } + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartLightmapResolutionsIfNeeded() +{ + // Only Retrieve the vertices lightmap resolution if necessary + if (PartLightMapResolutions.Num() > 0) + return true; + + // Get lightmap resolution (if present). + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, + AttribInfoLightmapResolution, PartLightMapResolutions); + + if (!Success && AttribInfoLightmapResolution.exists) + { + // Error retrieving lightmap resolution values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve lightmap resolution data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded")); + + // Only Retrieve the material IDs if necessary + if (PartFaceMaterialIds.Num() > 0) + return true; + + int32 NumFaces = HGPO.PartInfo.FaceCount; + if (NumFaces <= 0) + return true; + + PartFaceMaterialIds.SetNum(NumFaces); + + // Get the materials IDs per face + HAPI_Bool bSingleFaceMaterial = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, &bSingleFaceMaterial, + &PartFaceMaterialIds[0], 0, NumFaces)) + { + // Error retrieving material face assignments. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material face assignments"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + bOnlyOneFaceMaterial = bSingleFaceMaterial; + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded")); + + // Only Retrieve the material overrides if necessary + if (PartFaceMaterialOverrides.Num() > 0) + return true; + + bMaterialOverrideNeedsCreateInstance = false; + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + + // If material attribute was not found, check fallback compatibility attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + } + + // If material attribute and fallbacks were not found, check the material instance attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + + // We will we need to create material instances from the override attributes + bMaterialOverrideNeedsCreateInstance = AttribInfoFaceMaterialOverrides.exists; + } + + if (AttribInfoFaceMaterialOverrides.exists + && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_PRIM + && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_DETAIL) + { + HOUDINI_LOG_WARNING(TEXT("Static Mesh [%d %s], Geo [%d], Part [%d %s]: unreal_material must be a primitive or detail attribute, ignoring attribute."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + AttribInfoFaceMaterialOverrides.exists = false; + bMaterialOverrideNeedsCreateInstance = false; + PartFaceMaterialOverrides.Empty(); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartNeededMaterials() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials")); + + // Update the per face material IDs + UpdatePartFaceMaterialIDsIfNeeded(); + + // See if we have some material overides + UpdatePartFaceMaterialOverridesIfNeeded(); + + // If we have houdini materials AND overrides: + // We want to only create the Houdini materials that are not "covered" by overrides + // If we have material instance attributes, create all the houdini material anyway + // as their textures could be referenced by the material instance parameters + if (PartFaceMaterialOverrides.Num() > 0 && !bMaterialOverrideNeedsCreateInstance) + { + // If the material override was set on the detail, no need to look for houdini material IDs, as only the override will be used + if (AttribInfoFaceMaterialOverrides.exists && AttribInfoFaceMaterialOverrides.owner == HAPI_ATTROWNER_PRIM) + { + for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) + { + // Add a material ID to the unique array only if that face is not using the override + if (PartFaceMaterialOverrides[MaterialIdx].IsEmpty()) + PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); + } + } + } + else + { + // No material overrides, simply update the unique material array + for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) + PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); + } + + // Remove the invalid material ID from the unique array + PartUniqueMaterialIds.RemoveSingle(-1); + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials - Get the unique material infos")); + // Get the unique material infos + PartUniqueMaterialInfos.SetNum(PartUniqueMaterialIds.Num()); + for (int32 MaterialIdx = 0; MaterialIdx < PartUniqueMaterialIds.Num(); MaterialIdx++) + { + + FHoudiniApi::MaterialInfo_Init(&PartUniqueMaterialInfos[MaterialIdx]); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), + PartUniqueMaterialIds[MaterialIdx], + &PartUniqueMaterialInfos[MaterialIdx])) + { + // Error retrieving material face assignments. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material info for material %d"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, PartUniqueMaterialIds[MaterialIdx]); + continue; + } + } + } + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartLODScreensizeIfNeeded() +{ + // Only retrieve LOD screensizes if necessary + if (PartLODScreensize.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE, + AttribInfoLODScreensize, PartLODScreensize); + + if (!Success && AttribInfoLODScreensize.exists) + { + // Error retrieving FaceSmoothing values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve LOD screensizes"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + + +UStaticMesh* +FHoudiniMeshTranslator::CreateNewStaticMesh(const FString& InSplitIdentifier) +{ + // Update the current Obj/Geo/Part/Split IDs + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.GeoId; + PackageParams.PartId = HGPO.PartId; + PackageParams.SplitStr = InSplitIdentifier; + + UStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); + if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + return nullptr; + + return NewStaticMesh; +} + +UHoudiniStaticMesh* +FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentifier) +{ + // Update the current Obj/Geo/Part/Split IDs + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.GeoId; + PackageParams.PartId = HGPO.PartId; + // Add _HSM suffix to the split str, to distinguish the temporary HoudiniStaticMesh + // from the UStaticMesh + PackageParams.SplitStr = InSplitIdentifier + "_HSM"; + + UHoudiniStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); + if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + return nullptr; + + return NewStaticMesh; +} + +bool +FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() +{ + double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Prepare the object that will store UCX and simple colliders + AllAggregateCollisions.Empty(); + + // We need to know the number of LODs that will be needed for this part + int32 NumberOfLODs = 0; + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) + NumberOfLODs++; + else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + bHasMainGeo = true; + } + + // Update the part's material's IDS and info now + CreateNeededMaterials(); + + // Check now if they were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Get the current target platform for default lod policies + ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + check(CurrentPlatform); + + // New mesh list + TMap StaticMeshToBuild; + + // Map of Houdini Material IDs to Unreal Material Indices + TMap MapHoudiniMatIdToUnrealIndex; + // Map of Houdini Material Attributes to Unreal Material Indices + TMap MapHoudiniMatAttributesToUnrealIndex; + + bool MeshMaterialsHaveBeenReset = false; + + // Mesh Socket array + TArray AllSockets; + FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + + // Iterate through all detected split groups we care about and split geometry. + // The split are ordered in the following way: + // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + + // Get/Create the Aggregate Collisions for this mesh identifier + FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); + + // Handle UCX / Convex Hull colliders + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) + { + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the convex hull colliders and add them to the Aggregate + if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) + continue; + } + else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) + { + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the simple colliders and add them to the aggregate + if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) + continue; + } + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing SM from a previous cook + UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + // Prepare LOD Group data for this static mesh + FStaticMeshLODGroup LODGroup; + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing static mesh, create a new one + FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + + // Use the platform's default LODGroup policy + // TODO? Add setting for default LOD Group? + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); + } + else + { + // Try to reuse the existing SM's LOD group instead of the default one + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); + } + + if (!FoundOutputObject) + { + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + InputObjects.Remove(OutputObjectIdentifier); + } + else + { + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + FoundOutputObject->CachedAttributes.Empty(); + FoundOutputObject->CachedTokens.Empty(); + } + FoundOutputObject->bProxyIsCurrent = false; + + // TODO: Needed? + // Free any RHI resources for existing mesh before we re-create in place. + FoundStaticMesh->PreEditChange(NULL); + + // Check that the Static Mesh we found has the appropriate number of Source models/LODs + int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); + + // LODs are only for the "main" mesh, not for complex colliders! + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); + + if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) + { + while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) + FoundStaticMesh->AddSourceModel(); + + // We may have to remove excessive LOD levels + if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) + FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); + + // Initialize their default reduction setting + for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) + { + FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); + } + FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); + } + + // By default, always work on the first source model, unless we're a LOD + int32 SrcModelIndex = 0; + int32 LODIndex = 0; + if (SplitType == EHoudiniSplitType::LOD) + { + for (auto& curSplit : AllSplitGroups) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); + if (CurrentSplitType == EHoudiniSplitType::LOD + || CurrentSplitType == EHoudiniSplitType::Normal) + { + LODIndex++; + } + + if (curSplit == SplitGroupName) + break; + } + + // Fix for the case where we don't have a main geo + if(!bHasMainGeo) + LODIndex--; + } + + // Grab the appropriate SourceModel + FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; + if (!SrcModel) + { + HOUDINI_LOG_ERROR( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); + continue; + } + + // Load existing raw model. This will be empty as we are constructing a new mesh. + FRawMesh RawMesh; + if (!bRebuildStaticMesh) + { + // We dont need to rebuild the mesh itself: + // the geometry hasn't changed, but the materials have. + // We can just load the old data into the Raw mesh and reuse it. + SrcModel->LoadRawMesh(RawMesh); + } + else + { + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's normal if needed + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + // Check that the number of normal we retrieved is correct + int32 WedgeNormalCount = SplitNormals.Num() / 3; + if (SplitNormals.Num() < 0 || !SplitNormals.IsValidIndex((WedgeNormalCount - 1) * 3 + 2)) + { + // Ignore normals + WedgeNormalCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); + } + + // Transfer the normals to the raw mesh + RawMesh.WedgeTangentZ.SetNumZeroed(WedgeNormalCount); + for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) + { + // Swap Y/Z for Coordinates conversion + RawMesh.WedgeTangentZ[WedgeTangentZIdx].X = SplitNormals[WedgeTangentZIdx * 3 + 0]; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].Y = SplitNormals[WedgeTangentZIdx * 3 + 2]; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].Z = SplitNormals[WedgeTangentZIdx * 3 + 1]; + } + + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENTS + //--------------------------------------------------------------------------------------------------------------------- + + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + TArray< float > SplitTangentU; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + TArray< float > SplitTangentV; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + bool bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + + // Check that the number of tangents read matches the number of normals + int32 WedgeTangentUCount = SplitTangentU.Num() / 3; + int32 WedgeTangentVCount = SplitTangentV.Num() / 3; + if (WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount) + bGenerateTangents = true; + + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + + // Generate the tangents if needed + if (bGenerateTangents) + { + RawMesh.WedgeTangentX.SetNumZeroed(WedgeNormalCount); + RawMesh.WedgeTangentY.SetNumZeroed(WedgeNormalCount); + for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) + { + FVector TangentX, TangentY; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].FindBestAxisVectors(TangentX, TangentY); + + RawMesh.WedgeTangentX[WedgeTangentZIdx] = TangentX; + RawMesh.WedgeTangentY[WedgeTangentZIdx] = TangentY; + } + } + else + { + // Transfer the tangents we have read them and they're valid + RawMesh.WedgeTangentX.SetNumZeroed(WedgeTangentUCount); + for (int32 WedgeTangentUIdx = 0; WedgeTangentUIdx < WedgeTangentUCount; ++WedgeTangentUIdx) + { + // We need to flip Z and Y + RawMesh.WedgeTangentX[WedgeTangentUIdx].X = SplitTangentU[WedgeTangentUIdx * 3 + 0]; + RawMesh.WedgeTangentX[WedgeTangentUIdx].Y = SplitTangentU[WedgeTangentUIdx * 3 + 2]; + RawMesh.WedgeTangentX[WedgeTangentUIdx].Z = SplitTangentU[WedgeTangentUIdx * 3 + 1]; + } + + RawMesh.WedgeTangentY.SetNumZeroed(WedgeTangentVCount); + for (int32 WedgeTangentVIdx = 0; WedgeTangentVIdx < WedgeTangentVCount; ++WedgeTangentVIdx) + { + // We need to flip Z and Y + RawMesh.WedgeTangentY[WedgeTangentVIdx].X = SplitTangentV[WedgeTangentVIdx * 3 + 0]; + RawMesh.WedgeTangentY[WedgeTangentVIdx].Y = SplitTangentV[WedgeTangentVIdx * 3 + 2]; + RawMesh.WedgeTangentY[WedgeTangentVIdx].Z = SplitTangentV[WedgeTangentVIdx * 3 + 1]; + } + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // VERTEX COLORS AND ALPHAS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's colors if needed + UpdatePartColorsIfNeeded(); + + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract this part's alpha values if needed + UpdatePartAlphasIfNeeded(); + + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + + // Transfer colors and alphas if possible + int32 WedgeColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; + bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && WedgeColorsCount > 0; + bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == WedgeColorsCount); + if (bSplitColorValid) + { + RawMesh.WedgeColors.SetNumZeroed(WedgeColorsCount); + for (int32 WedgeColorIdx = 0; WedgeColorIdx < WedgeColorsCount; WedgeColorIdx++) + { + FLinearColor WedgeColor; + WedgeColor.R = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); + WedgeColor.G = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); + WedgeColor.B = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); + + if (bSplitAlphaValid) + { + // Use the Alpha attribute value + WedgeColor.A = FMath::Clamp(SplitAlphas[WedgeColorIdx], 0.0f, 1.0f); + } + else if (AttribInfoColors.tupleSize >= 4) + { + // Use the alpha value from the color attribute + WedgeColor.A = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); + } + else + { + WedgeColor.A = 1.0f; + } + + // Convert linear color to fixed color. + RawMesh.WedgeColors[WedgeColorIdx] = WedgeColor.ToFColor(false); + } + } + else + { + // TODO? Needed? New meshes wont have WedgeIndices yet!? + // No Colors or Alphas, init colors to White + FColor DefaultWedgeColor = FLinearColor::White.ToFColor(false); + WedgeColorsCount = RawMesh.WedgeIndices.Num(); + if (WedgeColorsCount > 0) + RawMesh.WedgeColors.Init(DefaultWedgeColor, WedgeColorsCount); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's FaceSmoothing values if needed + UpdatePartFaceSmoothingIfNeeded(); + + // Get the FaceSmoothing values for this split + TArray SplitFaceSmoothingMasks; + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); + + // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! + RawMesh.FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); + + // Check that the number of face smoothing values we retrieved is correct + int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; + if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) + { + // Ignore our face smoothing values + WedgeFaceSmoothCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); + } + + // Transfer the face smoothing masks to the raw mesh if we have any + for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) + { + RawMesh.FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; + } + + //--------------------------------------------------------------------------------------------------------------------- + // UVS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's UV sets if needed + UpdatePartUVSetsIfNeeded(); + + // See if we need to transfer uv point attributes to vertex attributes. + TArray> SplitUVSets; + SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + } + + // Transfer UVs to the Raw Mesh + int32 UVChannelCount = 0; + int32 LightMapUVChannel = 0; + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; + + int32 WedgeUVCount = SplitUVs.Num() / 2; + if (SplitUVs.Num() > 0 && SplitUVs.IsValidIndex((WedgeUVCount - 1) * 2 + 1)) + { + RawMesh.WedgeTexCoords[TexCoordIdx].SetNumZeroed(WedgeUVCount); + for (int32 WedgeUVIdx = 0; WedgeUVIdx < WedgeUVCount; ++WedgeUVIdx) + { + // We need to flip V coordinate when it's coming from HAPI. + RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].X = SplitUVs[WedgeUVIdx * 2 + 0]; + RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].Y = 1.0f - SplitUVs[WedgeUVIdx * 2 + 1]; + } + + UVChannelCount++; + if (UVChannelCount <= 2) + LightMapUVChannel = TexCoordIdx; + } + else + { + RawMesh.WedgeTexCoords[TexCoordIdx].Empty(); + } + } + + // We must have at least one UV channel. If there's none, create one filled with zero data. + if (UVChannelCount == 0) + RawMesh.WedgeTexCoords[0].SetNumZeroed(SplitVertexCount); + + // Set the lightmap Coordinate Index + // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention + // If not, the first UV set will be used + FoundStaticMesh->LightMapCoordinateIndex = LightMapUVChannel; + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's LightmapResolution values if needed + UpdatePartLightmapResolutionsIfNeeded(); + + // make sure the mesh has a new lighting guid + FoundStaticMesh->LightingGuid = FGuid::NewGuid(); + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" + // So that IndicesMapper[ oldIndex ] => newIndex + TArray IndicesMapper; + IndicesMapper.Init(-1, SplitVertexList.Num()); + int32 CurrentMapperIndex = 0; + + // NeededVertices: + // Array containing the old index of the needed vertices for the current split + // NeededVertices[ newIndex ] => oldIndex + TArray< int32 > NeededVertices; + RawMesh.WedgeIndices.SetNumZeroed(SplitVertexCount); + + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) + || !IndicesMapper.IsValidIndex(WedgeIndices[1]) + || !IndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (IndicesMapper[WedgeIndices[i]] < 0) + { + // This old index has not yet been "converted" to a new index + NeededVertices.Add(WedgeIndices[i]); + IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; + CurrentMapperIndex++; + } + + // Replace the old index with the new one + WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; + } + + if (!RawMesh.WedgeIndices.IsValidIndex(ValidVertexId + 2)) + break; + + // Flip wedge indices to fix the winding order. + RawMesh.WedgeIndices[ValidVertexId + 0] = WedgeIndices[0]; + RawMesh.WedgeIndices[ValidVertexId + 1] = WedgeIndices[2]; + RawMesh.WedgeIndices[ValidVertexId + 2] = WedgeIndices[1]; + + // Check if we need to patch UVs. + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + if (RawMesh.WedgeTexCoords[TexCoordIdx].IsValidIndex(ValidVertexId + 2)) + { + Swap(RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 1], + RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 2]); + } + } + + // Check if we need to patch colors. + if (RawMesh.WedgeColors.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeColors[ValidVertexId + 1], RawMesh.WedgeColors[ValidVertexId + 2]); + + // Check if we need to patch Normals and tangents. + if (RawMesh.WedgeTangentZ.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentZ[ValidVertexId + 1], RawMesh.WedgeTangentZ[ValidVertexId + 2]); + + if (RawMesh.WedgeTangentX.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentX[ValidVertexId + 1], RawMesh.WedgeTangentX[ValidVertexId + 2]); + + if (RawMesh.WedgeTangentY.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentY[ValidVertexId + 1], RawMesh.WedgeTangentY[ValidVertexId + 2]); + + ValidVertexId += 3; + } + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + UpdatePartPositionIfNeeded(); + + // + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + int32 VertexPositionsCount = NeededVertices.Num(); + RawMesh.VertexPositions.SetNumZeroed(VertexPositionsCount); + + for (int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx) + { + int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; + if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // We need to swap Z and Y coordinate here, and convert from m to cm. + RawMesh.VertexPositions[VertexPositionIdx].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + RawMesh.VertexPositions[VertexPositionIdx].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + RawMesh.VertexPositions[VertexPositionIdx].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + /* + // TODO: + // Check if this mesh contains only degenerate triangles. + if (FHoudiniEngineUtils::CountDegenerateTriangles(RawMesh) == SplitGroupFaceCount) + { + // This mesh contains only degenerate triangles, there's nothing we can do. + if (bStaticMeshCreated) + StaticMesh->MarkPendingKill(); + + continue; + } + */ + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL ATTRIBUTE OVERRIDES + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: These are actually per faces, not per vertices... + // Need to update!! + UpdatePartFaceMaterialOverridesIfNeeded(); + + //--------------------------------------------------------------------------------------------------------------------- + // FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Handle Materials!!!! + + // Get face indices for this split. + TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; + + // We need to reset the Static Mesh's materials once per SM: + // so, for the first lod, or the main geo... + if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + { + FoundStaticMesh->StaticMaterials.Empty(); + MeshMaterialsHaveBeenReset = true; + } + + // .. or for each visible complex collider + if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + FoundStaticMesh->StaticMaterials.Empty(); + + // Process material overrides first + if (PartFaceMaterialOverrides.Num() > 0) + { + // If the part has material overrides + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + continue; + + const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); + int32 CurrentFaceMaterialIdx = 0; + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Try to locate the corresponding material interface + UMaterialInterface * MaterialInterface = nullptr; + + // Start by looking in our assignment map + auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface && !MaterialName.IsEmpty()) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add this material to the map + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + } + else + { + // The Attribute Material and its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + CurrentFaceMaterialIdx = *FoundUnrealMatIndex; + } + else + { + // If everything fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + } + } + } + + // Update the Face Material on the mesh + RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + } + } + else if (PartUniqueMaterialIds.Num() > 0) + { + // The part has houdini materials + if (bOnlyOneFaceMaterial) + { + // We have only one material. + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Empty(); + FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + else + { + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + // Reset Rawmesh material face assignments. + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } + + UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Add the material to the Static mesh + int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + + // Update the face index + RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } + } + } + else + { + // No materials were found, we need to use default Houdini material. + int32 SplitFaceCount = SplitFaceIndices.Num(); + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceCount); + + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Empty(); + FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + + // Update the Build Settings using the default setting values + SetMeshBuildSettings( + SrcModel->BuildSettings, + RawMesh.WedgeTangentZ.Num() > 0, + (RawMesh.WedgeTangentX.Num() > 0 && RawMesh.WedgeTangentY.Num() > 0), + RawMesh.WedgeTexCoords->Num() > 0); + + // Check for a lightmap resolution override + int32 LightMapResolutionOverride = -1; + if (PartLightMapResolutions.Num() > 0) + LightMapResolutionOverride = PartLightMapResolutions[0]; + + if (LightMapResolutionOverride > 0) + FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; + else + FoundStaticMesh->LightMapResolution = 64; + + // TODO: + // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? + + // By default the distance field resolution should be set to 2.0 + // TODO should come from the HAC + //SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; + + // This is required due to the impeding deprecation of FRawMesh + // If we dont update this UE4 will crash upon deleting an asset. + SrcModel->StaticMeshOwner = FoundStaticMesh; + // Store the new raw mesh. + SrcModel->SaveRawMesh(RawMesh); + + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = GetLODSCreensizeForSplit(SplitGroupName); + if (screensize >= 0.0f) + { + // Only apply the LOD screensize if it's valid + SrcModel->ScreenSize = screensize; + //FoundStaticMesh->GetSourceModel(LODIndex).ScreenSize = screensize; + FoundStaticMesh->bAutoComputeLODScreenSize = false; + } + + // TODO: + // SET STATIC MESH GENERATION PARAM + // HANDLE COLLIDERS + // REMOVE OLD COLLIDERS + // CUSTOM BAKE NAME OVERRIDE + + // Update property attributes on the SM + TArray PropertyAttributes; + if (GetGenericPropertiesAttributes( + HGPO.GeoId, HGPO.PartId, + AllSplitFirstValidVertexIndex[SplitGroupName], + AllSplitFirstValidPrimIndex[SplitGroupName], + PropertyAttributes)) + { + UpdateGenericPropertiesAttributes( + FoundStaticMesh, PropertyAttributes); + } + + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray TileValues; + if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + // Notify that we created a new Static Mesh if needed + if (bNewStaticMeshCreated) + FAssetRegistryModule::AssetCreated(FoundStaticMesh); + + // Add the Static mesh to the output maps and the build map if we haven't already + if (FoundOutputObject) + { + FoundOutputObject->OutputObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = false; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + + StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); + } + + // Look if we only have colliders + // If we do, we'll allow attaching sockets to the collider meshes + bool bCollidersOnly = true; + for (auto& Current : StaticMeshToBuild) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) + { + bCollidersOnly = false; + break; + } + } + + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + for (auto& Current : StaticMeshToBuild) + { + UStaticMesh* SM = Current.Value; + if (!SM || SM->IsPendingKill()) + continue; + + UBodySetup * BodySetup = SM->BodySetup; + if (!BodySetup) + { + SM->CreateBodySetup(); + BodySetup = SM->BodySetup; + } + + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + + // Handle the Static Mesh's colliders + if (BodySetup && !BodySetup->IsPendingKill()) + { + // Make sure rendering is done - so we are not changing data being used by collision drawing. + FlushRenderingCommands(); + + // Clean up old colliders from a previous cook + BodySetup->Modify(); + BodySetup->RemoveSimpleCollision(); + // Create new GUID + BodySetup->InvalidatePhysicsData(); + + FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; + FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); + if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) + { + BodySetup->AddCollisionFrom(*CurrentAggColl); + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; + } + + RefreshCollisionChange(*SM); + SM->bCustomizedCollision = true; + + // See if we need to enable collisions on the whole mesh + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + // Complex collider, enable collisions for this static mesh. + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + else + { + // TODO + // if the LODForCollision uproperty attribute is set, we need to activate complex collision + // on the static mesh for that lod to be picked up properly as a collider + if (FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, + "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) + { + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + } + } + + // Add the Sockets to the StaticMesh + // We only add them to the main geo, or to the colliders if we only generate colliders + bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; + if (bAddSocket) + { + if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); + } + } + + // BUILD the Static Mesh + // bSilent doesnt add the Build Errors... + double build_start = FPlatformTime::Seconds(); + TArray SMBuildErrors; + SM->Build(true, &SMBuildErrors); + double build_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); + + SM->GetOnMeshChanged().Broadcast(); + + /* + // Try to find the outer package so we can dirty it up + if (SM->GetOuter()) + { + SM->GetOuter()->MarkPackageDirty(); + } + else + { + SM->MarkPackageDirty(); + } + */ + + + UPackage* MeshPackage = SM->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + + /* + // DPT: deactivated auto saving mesh/material package + // only dirty for now, as we'll save them when saving the world. + TArray PackageToSave; + PackageToSave.Add(MeshPackage); + + // Save the created package + FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); + */ + } + } + + // TODO: Still necessary ? SM->Build should actually update the navmesh... + // Now that all the meshes are built and their collisions meshes and primitives updated, + // we need to update their pre-built navigation collision used by the navmesh + for (auto& Iter : OutputObjects) + { + UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); + if (!StaticMesh || StaticMesh->IsPendingKill()) + continue; + + UBodySetup * BodySetup = StaticMesh->BodySetup; + if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) + { + // Unreal caches the Navigation Collision and never updates it for StaticMeshes, + // so we need to manually flush and recreate the data to have proper navigation collision + BodySetup->InvalidatePhysicsData(); + BodySetup->CreatePhysicsMeshes(); + StaticMesh->NavCollision->Setup(BodySetup); + } + } + + double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() executed in %f seconds."), time_end - time_start); + + return true; +} + +bool +FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() +{ + double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + // Simple colliders first, lods and finally, invisible colliders (that are separate Static Mesh) + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Prepare the object that will store UCX and simple colliders + AllAggregateCollisions.Empty(); + + // We need to know the number of LODs that will be needed for this part + int32 NumberOfLODs = 0; + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) + NumberOfLODs++; + else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + bHasMainGeo = true; + } + + // Update the part's material's IDS and info now + CreateNeededMaterials(); + + // Check if the materials were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Get the current target platform for default lod policies + ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + check(CurrentPlatform); + + // New mesh list + TMap StaticMeshToBuild; + + // Map of Houdini Material IDs to Unreal Material Indices + TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; + // Map of Houdini Material Attributes to Unreal Material Indices + TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; + + bool MeshMaterialsHaveBeenReset = false; + + // Mesh Socket array + TArray AllSockets; + FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + + double tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); + + // Iterate through all detected split groups we care about and split geometry. + // The split are ordered in the following way: + // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + + // Get/Create the Aggregate Collisions for this mesh identifier + FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); + + // Handle UCX / Convex Hull colliders + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) + { + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the convex hull colliders and add them to the Aggregate + if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) + continue; + } + else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) + { + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the simple colliders and add them to the aggregate + if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) + continue; + } + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing SM from a previous cook + UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + // Prepare LOD Group data for this static mesh + FStaticMeshLODGroup LODGroup; + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing static mesh, create a new one + FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + + // Use the platform's default LODGroup policy + // TODO? Add setting for default LOD Group? + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); + } + else + { + // Try to reuse the existing SM's LOD group instead of the default one + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); + } + + if (!FoundOutputObject) + { + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + } + else + { + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + FoundOutputObject->CachedAttributes.Empty(); + FoundOutputObject->CachedTokens.Empty(); + } + FoundOutputObject->bProxyIsCurrent = false; + + // TODO: Needed? + // Free any RHI resources for existing mesh before we re-create in place. + FoundStaticMesh->PreEditChange(NULL); + + // Check that the Static Mesh we found has the appropriate number of Source models/LODs + int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); + + // LODs are only for the "main" mesh, not for complex colliders! + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); + + if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) + { + while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) + FoundStaticMesh->AddSourceModel(); + + // We may have to remove excessive LOD levels + if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) + FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); + + // Initialize their default reduction setting + for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) + { + FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); + } + FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); + } + + // By default, always work on the first source model, unless we're a LOD + int32 SrcModelIndex = 0; + int32 LODIndex = 0; + if (SplitType == EHoudiniSplitType::LOD) + { + for (auto& curSplit : AllSplitGroups) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); + if (CurrentSplitType == EHoudiniSplitType::LOD + || CurrentSplitType == EHoudiniSplitType::Normal) + { + LODIndex++; + } + + if (curSplit == SplitGroupName) + break; + } + + // Fix for the case where we don't have a main geo + if(!bHasMainGeo) + LODIndex--; + } + + // Grab the appropriate SourceModel + FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; + if (!SrcModel) + { + HOUDINI_LOG_ERROR( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); + continue; + } + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + bool bHasNormal = false; + bool bHasTangents = false; + + // Load the existing mesh description if we don't need to rebuild the mesh + FMeshDescription* MeshDescription; + if (!bRebuildStaticMesh) + { + // We dont need to rebuild the mesh itself: + // the geometry hasn't changed, but the materials have. + // We can just reuse the old MeshDescription and reuse it. + MeshDescription = FoundStaticMesh->GetMeshDescription(LODIndex); + } + else + { + // Extract all the data needed for this split + // Start by initializing the MeshDescription for this LOD + MeshDescription = FoundStaticMesh->CreateMeshDescription(LODIndex); + FStaticMeshAttributes(*MeshDescription).Register(); + + // Mesh description uses material to create its PolygonGroups, + // so we first need to know how many different materials we have for this split + // and what vertices/indices belong to each material for remapping + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // SplitNeededVertices + // Array containing the (unique) part indices for the vertices that are needed for this split + // SplitNeededVertices[splitIndex] = PartIndex + TArray SplitNeededVertices; + //SplitNeededVertices.SetNumZeroed(SplitVertexCount); + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" so that IndicesMapper[ partIndex ] => splitIndex + TArray PartToSplitIndicesMapper; + PartToSplitIndicesMapper.Init(-1, SplitVertexList.Num()); + //TMap SplitToPartIndicesMapper; + + // SplitIndices + // Array of SplitIndices used to describe this split's polygons + TArray SplitIndices; + SplitIndices.SetNumZeroed(SplitVertexCount); + + int32 CurrentSplitIndex = 0; + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[0]) + || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[1]) + || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (PartToSplitIndicesMapper[WedgeIndices[i]] < 0) + { + // This part index has not yet been "converted" to a new split index + SplitNeededVertices.Add(WedgeIndices[i]); + PartToSplitIndicesMapper[WedgeIndices[i]] = CurrentSplitIndex; + //SplitToPartIndicesMapper.Add(CurrentSplitIndex, WedgeIndices[i]); + CurrentSplitIndex++; + } + + // Replace the old part index with the new split index + WedgeIndices[i] = PartToSplitIndicesMapper[WedgeIndices[i]]; + } + + if (!SplitIndices.IsValidIndex(ValidVertexId + 2)) + break; + + // Flip wedge indices to fix the winding order. + SplitIndices[ValidVertexId + 0] = WedgeIndices[0]; + SplitIndices[ValidVertexId + 1] = WedgeIndices[2]; + SplitIndices[ValidVertexId + 2] = WedgeIndices[1]; + + ValidVertexId += 3; + } + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract position for this part + UpdatePartPositionIfNeeded(); + + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + TVertexAttributesRef VertexPositions = + MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); + + MeshDescription->ReserveNewVertices(SplitNeededVertices.Num()); + for ( const int32& NeededVertexIndex : SplitNeededVertices) + { + // Create a new Vertex + FVertexID VertexID = MeshDescription->CreateVertex(); + if (PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexPositions[VertexID].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexPositions[VertexID].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexPositions[VertexID].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + else + { + // Error when retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + } + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: Check if still needed for MeshDescription + // We need to reset the Static Mesh's materials once per SM: + // so, for the first lod, or the main geo... + if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + { + FoundStaticMesh->StaticMaterials.Empty(); + MeshMaterialsHaveBeenReset = true; + } + + // .. or for each visible complex collider + if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + FoundStaticMesh->StaticMaterials.Empty(); + + // Get this split's faces + TArray& SplitGroupFaceIndices = AllSplitFaceIndices[SplitGroupName]; + // Array holding the materials needed for this split + //TArray SplitMaterials; + // Split Material indices per face, by default all faces are set to use the first Material + TArray SplitFaceMaterialIndices; + SplitFaceMaterialIndices.SetNumZeroed(SplitGroupFaceIndices.Num()); + + bool HasHoudiniMaterials = PartUniqueMaterialIds.Num() > 0; + bool HasMaterialOverrides = PartFaceMaterialOverrides.Num() > 0; + if (!HasHoudiniMaterials && !HasMaterialOverrides) + { + // We don't have any material override or houdini material + // we just need one polygon group using the default Houdini material. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + + // TODO: ? Add default mat to the assignement map? + } + else if (HasHoudiniMaterials && !HasMaterialOverrides) + { + // We have Houdini Material but no overrides + if (bOnlyOneFaceMaterial || PartUniqueMaterialIds.Num() == 1) + { + // We have only one Houdini material. + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + + // TODO: ? Add the mat to the assignement map? + } + else + { + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * MaterialDefault = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + // Reset Rawmesh material face assignments. + for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just use its material index + SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } + + UMaterialInterface * MaterialInterface = Cast(MaterialDefault); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Add the material to the Static mesh + //int32 UnrealMatIndex = SplitMaterials.Add(Material); + int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + + // Update the face index + SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } + } + } + else + { + // If we have material overrides + for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; + + int32 CurrentFaceMaterialIdx = -1; + if (PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + { + const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); + if (FoundFaceMaterialIdx) + { + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Try to locate the corresponding material interface + UMaterialInterface * MaterialInterface = nullptr; + if (!MaterialName.IsEmpty()) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast< UMaterialInterface >( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add this material to the map + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + } + } + + if (CurrentFaceMaterialIdx < 0) + { + // The attribute Material or its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + CurrentFaceMaterialIdx = *FoundUnrealMatIndex; + } + else + { + // If everything else fails, we'll use the default material + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + } + } + } + + // Update the Face Material on the mesh + SplitFaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + } + } + + // Create a Polygon Group for each material slot + TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = + MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); + + // We must use the number of assignment materials found to reserve the number of material slots + // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials + int32 NumberOfMaterials = OutputAssignmentMaterials.Num(); + if (NumberOfMaterials <= 0) + { + // No materials, create a polygon group for the default one + const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + } + else + { + MeshDescription->ReserveNewPolygonGroups(NumberOfMaterials); + //for (int32 MatIndex = 0; MatIndex < NumberOfMaterials; ++MatIndex) + for (auto& CurrentMatAssignement : OutputAssignmentMaterials) + { + const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = + FName(CurrentMatAssignement.Value ? *(CurrentMatAssignement.Value->GetName()) : *(CurrentMatAssignement.Key)); + } + } + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + // + // VERTEX INSTANCE ATTRIBUTES + // NORMALS, TANGENTS, COLORS, UVS, Alpha + // + + // Extract the normals + UpdatePartNormalsIfNeeded(); + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); + + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + + // Extract the tangents + TArray SplitTangentU; + TArray SplitTangentV; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + int32 NormalCount = SplitNormals.Num(); + bool bGenerateTangents = (NormalCount > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + // Check that the number of tangents read matches the number of normals + if (SplitTangentU.Num() != NormalCount || SplitTangentV.Num() != NormalCount) + bGenerateTangents = true; + + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + + // Generate the tangents if needed + if (bGenerateTangents) + { + SplitTangentU.SetNumZeroed(NormalCount); + SplitTangentV.SetNumZeroed(NormalCount); + for (int32 Idx = 0; Idx + 2 < NormalCount; Idx += 3) + { + FVector TangentZ; + TangentZ.X = SplitNormals[Idx + 0]; + TangentZ.Y = SplitNormals[Idx + 2]; + TangentZ.Z = SplitNormals[Idx + 1]; + + FVector TangentX, TangentY; + TangentZ.FindBestAxisVectors(TangentX, TangentY); + + SplitTangentU[Idx + 0] = TangentX.X; + SplitTangentU[Idx + 2] = TangentX.Y; + SplitTangentU[Idx + 1] = TangentX.Z; + + SplitTangentV[Idx + 0] = TangentY.X; + SplitTangentV[Idx + 2] = TangentY.Y; + SplitTangentV[Idx + 1] = TangentY.Z; + } + } + } + TVertexInstanceAttributesRef VertexInstanceTangents = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); + TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); + + // Extract the color values + UpdatePartColorsIfNeeded(); + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract the alpha values + UpdatePartAlphasIfNeeded(); + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + TVertexInstanceAttributesRef VertexInstanceColors = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); + + // Extract UVs + UpdatePartUVSetsIfNeeded(true); + // See if we need to transfer uv point attributes to vertex attributes. + int32 UVSetCount = PartUVSets.Num(); + TArray> SplitUVSets; + SplitUVSets.SetNum(UVSetCount); + for (int32 TexCoordIdx = 0; TexCoordIdx < UVSetCount; TexCoordIdx++) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + } + TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); + VertexInstanceUVs.SetNumIndices(UVSetCount); + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + // Allocate space for the vertex instances and polygons + MeshDescription->ReserveNewVertexInstances(SplitIndices.Num()); + MeshDescription->ReserveNewPolygons(SplitIndices.Num() / 3); + //Approximately 2.5 edges per polygons + MeshDescription->ReserveNewEdges(SplitIndices.Num() * 2.5f / 3); + + bHasNormal = SplitNormals.Num() > 0; + bHasTangents = SplitTangentU.Num() > 0 && SplitTangentV.Num() > 0; + bool bHasRGB = SplitColors.Num() > 0; + bool bHasRGBA = bHasRGB && AttribInfoColors.tupleSize == 4; + bool bHasAlpha = SplitAlphas.Num() > 0; + + TArray HasUVSets; + HasUVSets.SetNumZeroed(PartUVSets.Num()); + for (int32 Idx = 0; Idx < PartUVSets.Num(); Idx++) + HasUVSets[Idx] = PartUVSets[Idx].Num() > 0; + + uint32 FaceCount = SplitIndices.Num() / 3; + for (uint32 FaceIndex = 0; FaceIndex < FaceCount; FaceIndex++) + { + TArray FaceVertexInstanceIDs; + FaceVertexInstanceIDs.SetNum(3); + + // Ignore degenerate triangles + FVertexID VertexIDs[3]; + for (int32 Corner = 0; Corner < 3; ++Corner) + { + VertexIDs[Corner] = FVertexID(SplitIndices[(FaceIndex * 3) + Corner]); + } + if (VertexIDs[0] == VertexIDs[1] || VertexIDs[0] == VertexIDs[2] || VertexIDs[1] == VertexIDs[2]) + continue; + + //FVertexID FaceVertexIDs[3]; + for (int32 Corner = 0; Corner < 3; Corner++) + { + uint32 SplitIndex = (FaceIndex * 3) + Corner; + uint32 SplitVertexIndex = SplitIndices[SplitIndex]; + const FVertexInstanceID& VertexInstanceID = MeshDescription->CreateVertexInstance(FVertexID(SplitVertexIndex)); + + // Fix the winding order by updating the SplitIndex (invert corner 1 and 2) + // instead of going 0 1 2 go 0 2 1 + // TODO; this slows down StaticMesh->Build() considerably! + Corner == 1 ? SplitIndex++ : Corner == 2 ? SplitIndex-- : SplitIndex; + + const uint32 SplitVertexIndex_X = SplitIndex * 3 + 0; + const uint32 SplitVertexIndex_Y = SplitIndex * 3 + 2; + const uint32 SplitVertexIndex_Z = SplitIndex * 3 + 1; + // Normals + if (bHasNormal) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexInstanceNormals[VertexInstanceID].X = SplitNormals[SplitVertexIndex_X]; + VertexInstanceNormals[VertexInstanceID].Y = SplitNormals[SplitVertexIndex_Y]; + VertexInstanceNormals[VertexInstanceID].Z = SplitNormals[SplitVertexIndex_Z]; + } + + // Tangents and binormals + if (bHasTangents) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexInstanceTangents[VertexInstanceID].X = SplitTangentU[SplitVertexIndex_X]; + VertexInstanceTangents[VertexInstanceID].Y = SplitTangentU[SplitVertexIndex_Y]; + VertexInstanceTangents[VertexInstanceID].Z = SplitTangentU[SplitVertexIndex_Z]; + + FVector TangentY; + TangentY.X = SplitTangentV[SplitVertexIndex_X]; + TangentY.Y = SplitTangentV[SplitVertexIndex_Y]; + TangentY.Z = SplitTangentV[SplitVertexIndex_Z]; + + VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign( + VertexInstanceTangents[VertexInstanceID].GetSafeNormal(), + TangentY.GetSafeNormal(), + VertexInstanceNormals[VertexInstanceID].GetSafeNormal()); + } + + // Color + FLinearColor Color = FLinearColor::White; + if (bHasRGB) + { + Color.R = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); + Color.G = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); + Color.B = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); + } + // Alpha + if (bHasAlpha) + { + Color.A = FMath::Clamp(SplitAlphas[SplitIndex], 0.0f, 1.0f); + } + else if (bHasRGBA) + { + Color.A = FMath::Clamp(SplitColors[SplitIndex * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); + } + VertexInstanceColors[VertexInstanceID] = FVector4(Color); + + // UVs + for (int32 UVIndex = 0; UVIndex < SplitUVSets.Num(); UVIndex++) + { + if (HasUVSets[UVIndex]) + { + // We need to flip V coordinate when it's coming from HAPI. + FVector2D CurrentUV; + CurrentUV.X = SplitUVSets[UVIndex][SplitIndex * 2 + 0]; + CurrentUV.Y = 1.0f - SplitUVSets[UVIndex][SplitIndex * 2 + 1]; + + VertexInstanceUVs.Set(VertexInstanceID, UVIndex, CurrentUV); + } + } + + FaceVertexInstanceIDs[Corner] = VertexInstanceID; + } + + const FPolygonGroupID PolygonGroupID(SplitFaceMaterialIndices[FaceIndex]); + + // Insert a triangle into the mesh + MeshDescription->CreateTriangle(PolygonGroupID, FaceVertexInstanceIDs); + } + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's FaceSmoothing values if needed + UpdatePartFaceSmoothingIfNeeded(); + + // Get the FaceSmoothing values for this split + TArray SplitFaceSmoothingMasks; + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); + + // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! + // TODO: Expose the default FaceSmoothing value + // 0 will make hard face + TArray FaceSmoothingMasks; + FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); + + // Check that the number of face smoothing values we retrieved is correct + int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; + if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) + { + // Ignore our face smoothing values + WedgeFaceSmoothCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); + } + + // Transfer the face smoothing masks to the raw mesh if we have any + for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) + { + FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; + } + + // TODO + // Check + FStaticMeshOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, *MeshDescription); + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + // Extract this part's LightmapResolution values if needed + UpdatePartLightmapResolutionsIfNeeded(); + + // make sure the mesh has a new lighting guid + FoundStaticMesh->LightingGuid = FGuid::NewGuid(); + } + + // Update the Build Settings using the default setting values + SetMeshBuildSettings( + SrcModel->BuildSettings, + bHasNormal, + bHasTangents, + PartUVSets.Num() > 0); + + // Set the lightmap Coordinate Index + // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention + FoundStaticMesh->LightMapCoordinateIndex = PartUVSets.Num() > 1 ? 1 : 0; + + // Check for a lightmapa resolution override + int32 LightMapResolutionOverride = -1; + if ( PartLightMapResolutions.Num() > 0) + LightMapResolutionOverride = PartLightMapResolutions[0]; + + if (LightMapResolutionOverride > 0) + FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; + else + FoundStaticMesh->LightMapResolution = 64; + + // TODO: + // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? + + // By default the distance field resolution should be set to 2.0 + // TODO should come from the HAC + //SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; + + // RAW MESH CHECKS + + // TODO: Check not needed w/ FMeshDesc + // This is required due to the impeding deprecation of FRawMesh + // If we dont update this UE4 will crash upon deleting an asset. + //SrcModel->StaticMeshOwner = FoundStaticMesh; + + // Store the new MeshDescription + FoundStaticMesh->CommitMeshDescription(LODIndex); + //Set the Imported version before calling the build + FoundStaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; + + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = GetLODSCreensizeForSplit(SplitGroupName); + if (screensize >= 0.0f) + { + // Only apply the LOD screensize if it's valid + SrcModel->ScreenSize = screensize; + //FoundStaticMesh->GetSourceModel(LodIndex).ScreenSize = screensize; + FoundStaticMesh->bAutoComputeLODScreenSize = false; + } + + // SET STATIC MESH GENERATION PARAM + // HANDLE COLLIDERS + // REMOVE OLD COLLIDERS + // CUSTOM BAKE NAME OVERRIDE + + // UPDATE UPROPERTY ATTRIBS + // Update property attributes on the SM + TArray PropertyAttributes; + if (GetGenericPropertiesAttributes( + HGPO.GeoId, HGPO.PartId, + AllSplitFirstValidVertexIndex[SplitGroupName], + AllSplitFirstValidPrimIndex[SplitGroupName], + PropertyAttributes)) + { + UpdateGenericPropertiesAttributes( + FoundStaticMesh, PropertyAttributes); + } + + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray TileValues; + if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + // Notify that we created a new Static Mesh if needed + if(bNewStaticMeshCreated) + FAssetRegistryModule::AssetCreated(FoundStaticMesh); + + // Add the Static mesh to the output maps and the build map if we haven't already + if (FoundOutputObject) + { + FoundOutputObject->OutputObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = false; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + + StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); + + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished MD in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Look if we only have colliders + // If we do, we'll allow attaching sockets to the collider meshes + bool bCollidersOnly = true; + for (auto& Current : StaticMeshToBuild) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) + { + bCollidersOnly = false; + break; + } + } + + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + for (auto& Current : StaticMeshToBuild) + { + UStaticMesh* SM = Current.Value; + if (!SM || SM->IsPendingKill()) + continue; + + UBodySetup * BodySetup = SM->BodySetup; + if (!BodySetup) + { + SM->CreateBodySetup(); + BodySetup = SM->BodySetup; + } + + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + + // Handle the Static Mesh's colliders + if (BodySetup && !BodySetup->IsPendingKill()) + { + // Make sure rendering is done - so we are not changing data being used by collision drawing. + FlushRenderingCommands(); + + // Clean up old colliders from a previous cook + BodySetup->Modify(); + BodySetup->RemoveSimpleCollision(); + // Create new GUID + BodySetup->InvalidatePhysicsData(); + + FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; + FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); + if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) + { + BodySetup->AddCollisionFrom(*CurrentAggColl); + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; + } + + // Moved RefreshCollisionChange to after the SM->Build call + // RefreshCollisionChange(*SM); + SM->bCustomizedCollision = true; + + // See if we need to enable collisions on the whole mesh + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + // Complex collider, enable collisions for this static mesh. + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + else + { + // TODO + // if the LODForCollision uproperty attribute is set, we need to activate complex collision + // on the static mesh for that lod to be picked up properly as a collider + if ( FHoudiniEngineUtils::HapiCheckAttributeExists( HGPO.GeoId, HGPO.PartId, + "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) + { + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + } + } + + // Add the Sockets to the StaticMesh + // We only add them to the main geo, or to the colliders if we only generate colliders + bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; + if (bAddSocket) + { + if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); + } + } + + // BUILD the Static Mesh + // bSilent doesnt add the Build Errors... + double build_start = FPlatformTime::Seconds(); + TArray SMBuildErrors; + SM->Build(true, &SMBuildErrors); + double build_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); + + // TODO: copied the content of RefreshCollision below and commented out CreateNavCollision + // it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, + // and can be expensive depending on the vert/poly count of the mesh + // TODO: also moved this to after the call to Build, since Build updates the mesh's + // physics state (calling this before Build when rebuilding an existing high poly mesh as + // low poly mesh, incurs quite a performance hit. This is likely due to processing of physics + // meshes with high vert/poly count before the Build + // RefreshCollisionChange(*SM); + { + // SM->CreateNavCollision(/*bIsUpdate=*/true); + + for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + { + UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); + if (StaticMeshComponent->GetStaticMesh() == SM) + { + // it needs to recreate IF it already has been created + if (StaticMeshComponent->IsPhysicsStateCreated()) + { + StaticMeshComponent->RecreatePhysicsState(); + } + } + } + + FEditorSupportDelegates::RedrawAllViewports.Broadcast(); + } + + SM->GetOnMeshChanged().Broadcast(); + /* + // Try to find the outer package so we can dirty it up + if (SM->GetOuter()) + { + SM->GetOuter()->MarkPackageDirty(); + } + else + { + SM->MarkPackageDirty(); + } + */ + + UPackage* MeshPackage = SM->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + /* + // DPT: deactivated auto saving mesh/material package + // only dirty for now, as we'll save them when saving the world. + TArray PackageToSave; + PackageToSave.Add(MeshPackage); + + // Save the created package + FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); + */ + } + } + + // TODO: Still necessary ? SM->Build should actually update the navmesh... + // TODO: Commented out for now, since it appears that the content of the loop is + // already called in UStaticMesh::BuildInternal and UStaticMesh::PostBuildInternal + //// Now that all the meshes are built and their collisions meshes and primitives updated, + //// we need to update their pre-built navigation collision used by the navmesh + //for (auto& Iter : OutputObjects) + //{ + // UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); + // if (!StaticMesh || StaticMesh->IsPendingKill()) + // continue; + + // UBodySetup * BodySetup = StaticMesh->BodySetup; + // if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) + // { + // // Unreal caches the Navigation Collision and never updates it for StaticMeshes, + // // so we need to manually flush and recreate the data to have proper navigation collision + // // TODO: Is this still required? These two functions are called by + // // UStaticMesh::BuildInternal, which is called by UStaticMesh::Build/BatchBuild + // // BodySetup->InvalidatePhysicsData(); + // // BodySetup->CreatePhysicsMeshes(); + + // // TODO: Is this still required? This function is called by UStaticMesh::CreateNavCollision + // // which is called by the UStaticMesh::PostBuildInternal function, which is called at the + // // end of the build. + // // StaticMesh->NavCollision->Setup(BodySetup); + // } + //} + + double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() executed in %f seconds."), time_end - time_start); + + return true; +} + +bool +FHoudiniMeshTranslator::CreateHoudiniStaticMesh() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh")); + + const double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Determine if there is "main" geo, if not we'll use the first LOD + // as main geo + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + { + bHasMainGeo = true; + break; + } + } + + // Update the part's material's IDS and info now + //UpdatePartFaceMaterialsIfNeeded(); + CreateNeededMaterials(); + + // Check if the materials were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Map of Houdini Material IDs to Unreal Material Indices + TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; + // Map of Houdini Material Attributes to Unreal Material Indices + TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; + + bool MeshMaterialsHaveBeenReset = false; + + double tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); + + // Iterate through all detected split groups we care about and split geometry. + bool bMainGeoOrFirstLODFound = false; + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Per Split")); + + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // We are only interested in the Normal/main geo and visible colliders + if (SplitType != EHoudiniSplitType::Normal && + SplitType != EHoudiniSplitType::LOD && + SplitType != EHoudiniSplitType::RenderedComplexCollider && + SplitType != EHoudiniSplitType::RenderedSimpleCollider && + SplitType != EHoudiniSplitType::RenderedUCXCollider) + { + continue; + } + + // We only use LOD if there is no Normal geo + if (SplitType == EHoudiniSplitType::Normal) + { + bMainGeoOrFirstLODFound = true; + HOUDINI_LOG_MESSAGE(TEXT("Found Normal geo for mesh.")); + } + else if (SplitType == EHoudiniSplitType::LOD) + { + if (bHasMainGeo) + { + HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since the mesh has Normal geo.")); + continue; + } + else if (bMainGeoOrFirstLODFound) + { + HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since we have already processed the first LOD.")); + continue; + } + else + { + bMainGeoOrFirstLODFound = true; + HOUDINI_LOG_MESSAGE(TEXT("Mesh does not have Normal geo, found first LOD.")); + } + } + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing DM from a previous cook + UHoudiniStaticMesh* FoundStaticMesh = FindExistingHoudiniStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing dynamic mesh, create a new one + FoundStaticMesh = CreateNewHoudiniStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + } + + if (!FoundOutputObject) + { + // If we couldnt find a previous output object, create a new one + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + } + FoundOutputObject->bProxyIsCurrent = true; + + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + + if (bRebuildStaticMesh) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build/Rebuild UHoudiniStaticMesh")); + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" + // So that IndicesMapper[ oldIndex ] => newIndex + TArray IndicesMapper; + IndicesMapper.Init(-1, SplitVertexList.Num()); + int32 CurrentMapperIndex = 0; + + // NeededVertices: + // Array containing the old index of the needed vertices for the current split + // NeededVertices[ newIndex ] => oldIndex + TArray< int32 > NeededVertices; + NeededVertices.Reserve(SplitVertexList.Num() / 3); + TArray< int32 > TriangleIndices; + TriangleIndices.Reserve(SplitVertexList.Num()); + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build IndicesMapper and NeededVertices")); + + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) + || !IndicesMapper.IsValidIndex(WedgeIndices[1]) + || !IndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (IndicesMapper[WedgeIndices[i]] < 0) + { + // This old index has not yet been "converted" to a new index + NeededVertices.Add(WedgeIndices[i]); + IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; + CurrentMapperIndex++; + } + + // Replace the old index with the new one + WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; + } + + // Flip wedge indices to fix the winding order. + TriangleIndices.Add(WedgeIndices[0]); + TriangleIndices.Add(WedgeIndices[2]); + TriangleIndices.Add(WedgeIndices[1]); + + ValidVertexId += 3; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's normal if needed + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + // Check that the number of normal we retrieved is correct + int32 NormalCount = SplitNormals.Num() / 3; + if (NormalCount < 0 || NormalCount < NeededVertices.Num()) + { + // Ignore normals + NormalCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENTS + //--------------------------------------------------------------------------------------------------------------------- + + TArray SplitTangentU; + TArray SplitTangentV; + int32 TangentUCount = 0; + int32 TangentVCount = 0; + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + + bool bGenerateTangents = bReadTangents; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + + // Check that the number of tangents read matches the number of normals + TangentUCount = SplitTangentU.Num() / 3; + TangentVCount = SplitTangentV.Num() / 3; + if (TangentUCount != NormalCount || TangentVCount != NormalCount) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh: Generate tangents due to count mismatch (# U Tangents = %d; # V Tangents = %d; # Normals = %d)"), TangentUCount, TangentVCount, NormalCount); + bGenerateTangents = true; + } + + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // VERTEX COLORS AND ALPHAS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's colors if needed + UpdatePartColorsIfNeeded(); + + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract this part's alpha values if needed + UpdatePartAlphasIfNeeded(); + + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + + const int32 ColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; + const bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && ColorsCount > 0; + const bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == ColorsCount); + + //--------------------------------------------------------------------------------------------------------------------- + // UVS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's UV sets if needed + UpdatePartUVSetsIfNeeded(); + + // See if we need to transfer uv point attributes to vertex attributes. + int32 NumUVLayers = 0; + TArray> SplitUVSets; + SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + if (SplitUVSets[TexCoordIdx].Num() > 0) + { + NumUVLayers++; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL ATTRIBUTE OVERRIDES + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: These are actually per faces, not per vertices... + // Need to update!! + UpdatePartFaceMaterialOverridesIfNeeded(); + + // + // Initialize mesh + // + const int32 NumVertexPositions = NeededVertices.Num(); + const int32 NumTriangles = TriangleIndices.Num() / 3; + const bool bHasPerFaceMaterials = PartFaceMaterialOverrides.Num() > 0 || (PartUniqueMaterialIds.Num() > 0 && !bOnlyOneFaceMaterial); + + FoundStaticMesh->Initialize( + NumVertexPositions, + NumTriangles, + NumUVLayers, // NumUVLayers + 0, // InitialNumStaticMaterials + NormalCount > 0, // HasNormals + NormalCount > 0 && bReadTangents, // HasTangents + bSplitColorValid, // HasColors + bHasPerFaceMaterials // HasPerFaceMaterials + ); + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + UpdatePartPositionIfNeeded(); + + // + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Vertex Positions")); + + for (int32 VertexPositionIdx = 0; VertexPositionIdx < NumVertexPositions; ++VertexPositionIdx) + //ParallelFor(NumVertexPositions, [&](uint32 VertexPositionIdx) + { + int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; + if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // We need to swap Z and Y coordinate here, and convert from m to cm. + FoundStaticMesh->SetVertexPosition(VertexPositionIdx, FVector( + PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION, + PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION, + PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION + )); + }//); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACES / TRIS + // Now set Normals, UVs and Colors on mesh points and AttributeSet + //--------------------------------------------------------------------------------------------------------------------- + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Triangle Indices & Per Vertex Instance Attribute Values")); + + // Now add the triangles to the mesh + for (int32 TriangleIdx = 0; TriangleIdx < NumTriangles; ++TriangleIdx) + // ParallelFor(NumTriangles, [&](uint32 TriangleIdx) + { + + const int32 TriVertIdx0 = TriangleIdx * 3; + FoundStaticMesh->SetTriangleVertexIndices(TriangleIdx, FIntVector( + TriangleIndices[TriVertIdx0 + 0], + TriangleIndices[TriVertIdx0 + 1], + TriangleIndices[TriVertIdx0 + 2] + )); + + const int32 TriWindingIndex[3] = { 0, 2, 1 }; + if (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)) + { + // Flip Z and Y coordinate for normal, but don't scale + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + const FVector Normal( + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] + ); + + FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); + + if (bReadTangents) + { + FVector TangentU, TangentV; + if (bGenerateTangents) + { + // Generate the tangents if needed + Normal.FindBestAxisVectors(TangentU, TangentV); + } + else + { + // Transfer the tangents from Houdini + TangentU.X = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; + TangentU.Y = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; + TangentU.Z = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; + + TangentU.X = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; + TangentU.Y = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; + TangentU.Z = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; + } + + FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); + FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + } + } + } + + if (bSplitColorValid && SplitColors.IsValidIndex(TriVertIdx0 * AttribInfoColors.tupleSize + 3 * AttribInfoColors.tupleSize - 1)) + { + FLinearColor VertexLinearColor; + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + VertexLinearColor.R = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 0], 0.0f, 1.0f); + VertexLinearColor.G = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 1], 0.0f, 1.0f); + VertexLinearColor.B = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 2], 0.0f, 1.0f); + + if (bSplitAlphaValid) + { + VertexLinearColor.A = FMath::Clamp(SplitAlphas[TriVertIdx0 + ElementIdx], 0.0f, 1.0f); + } + else if (AttribInfoColors.tupleSize >= 4) + { + VertexLinearColor.A = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 3], 0.0f, 1.0f); + } + else + { + VertexLinearColor.A = 1.0f; + } + const FColor VertexColor = VertexLinearColor.ToFColor(false); + FoundStaticMesh->SetTriangleVertexColor(TriangleIdx, TriWindingIndex[ElementIdx], VertexColor); + } + } + + if (NumUVLayers > 0) + { + // Dynamic mesh supports only 1 UV layer on the mesh it self. So we set the first layer + // on the mesh itself only, and we set all layers on the AttributeSet + for (int32 TexCoordIdx = 0; TexCoordIdx < NumUVLayers; ++TexCoordIdx) + { + const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; + if (SplitUVs.IsValidIndex(TriVertIdx0 * 2 + 3 * 2 - 1)) + { + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + const int32 UVIdx = TriVertIdx0 * 2 + ElementIdx * 2; + // We need to flip V coordinate when it's coming from HAPI. + const FVector2D UV(SplitUVs[UVIdx + 0], 1.0f - SplitUVs[UVIdx + 1]); + // Set the UV on the vertex instance in the UVLayer + FoundStaticMesh->SetTriangleVertexUV(TriangleIdx, TriWindingIndex[ElementIdx], TexCoordIdx, UV); + } + } + } + } + } + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIALS / FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // Get face indices for this split. + TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; + + // Process material overrides first + if (PartFaceMaterialOverrides.Num() > 0) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Material Overrides")); + + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + continue; + + const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); + int32 CurrentFaceMaterialIdx = 0; + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Try to locate the corresponding material interface + UMaterialInterface * MaterialInterface = nullptr; + + // Start by looking in our assignment map + auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface && !MaterialName.IsEmpty()) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add this material to the map + CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + } + else + { + // The Attribute Material and its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + CurrentFaceMaterialIdx = *FoundUnrealMatIndex; + } + else + { + // If everything fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Add the material to the mesh + CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + } + } + } + + // Update the Face Material on the mesh + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); + } + } + else if (PartUniqueMaterialIds.Num() > 0) + { + // The part has houdini materials + if (bOnlyOneFaceMaterial) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Single Material")); + + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + } + else + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Materials")); + + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); + continue; + } + + UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Add the material to the mesh + int32 UnrealMatIndex = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + + // Update the face index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); + } + } + } + else + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Default Material")); + + // No materials were found, we need to use default Houdini material. + int32 SplitFaceCount = SplitFaceIndices.Num(); + + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + } + + //// Update property attributes on the mesh + //TArray PropertyAttributes; + //if (GetGenericPropertiesAttributes( + // HGPO.GeoId, HGPO.PartId, + // AllSplitFirstValidVertexIndex[SplitGroupName], + // AllSplitFirstValidPrimIndex[SplitGroupName], + // PropertyAttributes)) + //{ + // UpdateGenericPropertiesAttributes( + // FoundStaticMesh, PropertyAttributes); + //} + + FoundStaticMesh->Optimize(); + + //// Try to find the outer package so we can dirty it up + //if (FoundStaticMesh->GetOuter()) + //{ + // FoundStaticMesh->GetOuter()->MarkPackageDirty(); + //} + //else + //{ + // FoundStaticMesh->MarkPackageDirty(); + //} + UPackage *MeshPackage = FoundStaticMesh->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + + /* + // DPT: deactivated auto saving mesh/material package + // only dirty for now, as we'll save them when saving the world. + // Save the created/updated package + FEditorFileUtils::PromptForCheckoutAndSave({ MeshPackage }, false, false); + */ + } + + // Add the Proxy mesh to the output maps + if (FoundOutputObject) + { + FoundOutputObject->ProxyObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = true; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + } + + const double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() executed in %f seconds."), time_end - time_start); + + return true; +} + +bool +FHoudiniMeshTranslator::CreateNeededMaterials() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateNeededMaterials")); + + UpdatePartNeededMaterials(); + + TArray MaterialAndTexturePackages; + FHoudiniMaterialTranslator::CreateHoudiniMaterials( + HGPO.AssetId, PackageParams, + PartUniqueMaterialIds, PartUniqueMaterialInfos, + InputAssignmentMaterials, OutputAssignmentMaterials, + MaterialAndTexturePackages, false, bTreatExistingMaterialsAsUpToDate); + + /* + // Save the created packages if needed + // DPT: deactivated, only dirty for now, as we'll save them when saving the world. + if (MaterialAndTexturePackages.Num() > 0) + FEditorFileUtils::PromptForCheckoutAndSave(MaterialAndTexturePackages, true, false); + */ + + if (bMaterialOverrideNeedsCreateInstance && PartFaceMaterialOverrides.Num() > 0) + { + // Map containing unique face materials override attribute + // and their first valid prim index + // We create only one material instance per attribute + TMap UniqueFaceMaterialOverrides; + for (int FaceIdx = 0; FaceIdx < PartFaceMaterialOverrides.Num(); FaceIdx++) + { + FString MatOverrideAttr = PartFaceMaterialOverrides[FaceIdx]; + if (UniqueFaceMaterialOverrides.Contains(MatOverrideAttr)) + continue; + + // Add the material override and face index to the map + UniqueFaceMaterialOverrides.Add(MatOverrideAttr, FaceIdx); + } + + FHoudiniMaterialTranslator::CreateMaterialInstances( + HGPO, PackageParams, + UniqueFaceMaterialOverrides, MaterialAndTexturePackages, + InputAssignmentMaterials, OutputAssignmentMaterials, + false); + } + + return true; +} + +FString +FHoudiniMeshTranslator::GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType) +{ + FString MeshIdentifier = TEXT(""); + switch (InSplitType) + { + case EHoudiniSplitType::Normal: + case EHoudiniSplitType::LOD: + case EHoudiniSplitType::InvisibleUCXCollider: + case EHoudiniSplitType::InvisibleSimpleCollider: + // LODs and Invisible simple colliders use the main mesh + MeshIdentifier = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + break; + + case EHoudiniSplitType::InvisibleComplexCollider: + case EHoudiniSplitType::RenderedComplexCollider: + case EHoudiniSplitType::RenderedUCXCollider: + case EHoudiniSplitType::RenderedSimpleCollider: + // Rendered colliders or invisible complex colliders have their own static mesh + MeshIdentifier = InSplitName; + break; + + default: + break; + } + + return MeshIdentifier; +} + +UStaticMesh* +FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + // See if we already have an input object for that output identifier + FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); + UStaticMesh* FoundStaticMesh = nullptr; + if (FoundOutputObjectPtr) + { + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + FoundStaticMesh = nullptr; + } + + if (!FoundStaticMesh) + { + // No input object matching this identifier, see if we have created an output object that matches + FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); + if (!FoundOutputObjectPtr) + return nullptr; + + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + return nullptr; + } + + if (FoundStaticMesh) + { + UObject* OuterMost = FoundStaticMesh->GetOutermostObject(); + if (OuterMost->IsA()) + { + // The Outermost for this static mesh is a level + // This is likely a SM created by V1, and we should not reuse it. + // This will force the plugin to recreate a "proper" SM in the temp folder. + FoundStaticMesh->MarkPendingKill(); + FoundStaticMesh = nullptr; + } + } + + return FoundStaticMesh; +} + +UHoudiniStaticMesh* +FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + // See if we already have an input object for that output identifier + FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); + UHoudiniStaticMesh* FoundStaticMesh = nullptr; + if (FoundOutputObjectPtr) + { + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + FoundStaticMesh = nullptr; + } + + if (!FoundStaticMesh) + { + // No input object matching this identifier, see if we have created an output object that matches + FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); + if (!FoundOutputObjectPtr) + return nullptr; + + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + return nullptr; + } + + return FoundStaticMesh; +} + +EHoudiniSplitType +FHoudiniMeshTranslator::GetSplitTypeFromSplitName(const FString& InSplitName) +{ + const FString MainGroup = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + if (InSplitName.StartsWith(MainGroup, ESearchCase::IgnoreCase)) + return EHoudiniSplitType::Normal; + + const FString LODGroupPrefix = HAPI_UNREAL_GROUP_LOD_PREFIX; + if (InSplitName.StartsWith(LODGroupPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::LOD; + } + + const FString RenderedCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; + if (InSplitName.StartsWith(RenderedCollisionPrefix, ESearchCase::IgnoreCase)) + { + // Rendered colliders + // See if it is a simple/ucx/complex + const FString RenderedUCXCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX; + const FString RenderedSimpleCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(RenderedUCXCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::RenderedUCXCollider; + } + else if (InSplitName.StartsWith(RenderedSimpleCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::RenderedSimpleCollider; + } + else + { + return EHoudiniSplitType::RenderedComplexCollider; + } + } + + const FString InvisibleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(InvisibleCollisionPrefix, ESearchCase::IgnoreCase)) + { + // Invisible colliders + // See if it is a simple/ucx/complex + const FString InvisibleUCXCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX; + const FString InvisibleSimpleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(InvisibleUCXCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::InvisibleUCXCollider; + } + else if (InSplitName.StartsWith(InvisibleSimpleCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::InvisibleSimpleCollider; + } + else + { + return EHoudiniSplitType::InvisibleComplexCollider; + } + } + + // ? + return EHoudiniSplitType::Invalid; + //return EHoudiniSplitType::Normal; +} + +bool +FHoudiniMeshTranslator::AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) +{ + // Get the vertex indices for the split group + TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; + + // We're only interested in unique vertices + TArray UniqueVertexIndexes; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + UniqueVertexIndexes.AddUnique(Index); + } + + // Extract the collision geo's vertices + TArray< FVector > VertexArray; + VertexArray.SetNum(UniqueVertexIndexes.Num()); + for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) + { + int32 VertexIndex = UniqueVertexIndexes[Idx]; + if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) + continue; + + VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + +#if WITH_EDITOR + // Do we want to create multiple convex hulls? + bool bDoMultiHullDecomp = false; + if (SplitGroupName.Contains(TEXT("ucx_multi"), ESearchCase::IgnoreCase)) + bDoMultiHullDecomp = true; + + uint32 HullCount = 8; + int32 MaxHullVerts = 16; + if (bDoMultiHullDecomp) + { + // TODO: + // Look for extra attributes for the decomposition parameters? (HullCount/MaxHullVerts) + } + + if (bDoMultiHullDecomp && (VertexArray.Num() >= 3 || UniqueVertexIndexes.Num() >= 3)) + { + // creating multiple convex hull collision + // ... this might take a while + + // We're only interested in the valid indices! + TArray Indices; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + Indices.Add(Index); + } + + // But we need all the positions as vertex + TArray< FVector > Vertices; + Vertices.SetNum(PartPositions.Num() / 3); + + for (int32 Idx = 0; Idx < Vertices.Num(); Idx++) + { + Vertices[Idx].X = PartPositions[Idx * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[Idx].Y = PartPositions[Idx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[Idx].Z = PartPositions[Idx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + // We are using Unreal's DecomposeMeshToHulls() + // We need a BodySetup so create a fake/transient one + UBodySetup* BodySetup = NewObject(); + + // Run actual util to do the work (if we have some valid input) + DecomposeMeshToHulls(BodySetup, Vertices, Indices, HullCount, MaxHullVerts); + + // If we succeed, return here + // If not, keep going and we'll try to do a single hull decomposition + if (BodySetup->AggGeom.ConvexElems.Num() > 0) + { + // Copy the convex elem to our aggregate + for (int32 n = 0; n < BodySetup->AggGeom.ConvexElems.Num(); n++) + AggCollisions.ConvexElems.Add(BodySetup->AggGeom.ConvexElems[n]); + + return true; + } + } +#endif + + // Creating a single Convex collision + FKConvexElem ConvexCollision; + ConvexCollision.VertexData = VertexArray; + ConvexCollision.UpdateElemBox(); + + AggCollisions.ConvexElems.Add(ConvexCollision); + + return true; +} + +bool +FHoudiniMeshTranslator::AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) +{ + // Get the vertex indices for the split group + TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; + + // We're only interested in unique vertices + TArray UniqueVertexIndexes; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + UniqueVertexIndexes.AddUnique(Index); + } + + // Extract the collision geo's vertices + TArray< FVector > VertexArray; + VertexArray.SetNum(UniqueVertexIndexes.Num()); + for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) + { + int32 VertexIndex = UniqueVertexIndexes[Idx]; + if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) + continue; + + VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + int32 NewColliders = 0; + if (SplitGroupName.Contains("Box")) + { + NewColliders = FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(VertexArray, AggCollisions); + } + else if (SplitGroupName.Contains("Sphere")) + { + NewColliders = FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(VertexArray, AggCollisions); + } + else if (SplitGroupName.Contains("Capsule")) + { + NewColliders = FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(VertexArray, AggCollisions); + } + else + { + // We need to see what type of collision the user wants + // by default, a kdop26 will be created + uint32 NumDirections = 26; + const FVector* Directions = KDopDir26; + if (SplitGroupName.Contains("kdop10X")) + { + NumDirections = 10; + Directions = KDopDir10X; + } + else if (SplitGroupName.Contains("kdop10Y")) + { + NumDirections = 10; + Directions = KDopDir10Y; + } + else if (SplitGroupName.Contains("kdop10Z")) + { + NumDirections = 10; + Directions = KDopDir10Z; + } + else if (SplitGroupName.Contains("kdop18")) + { + NumDirections = 18; + Directions = KDopDir18; + } + + // Converting the directions to a TArray + TArray DirArray; + DirArray.SetNum(NumDirections); + for (uint32 DirectionIndex = 0; DirectionIndex < NumDirections; DirectionIndex++) + { + DirArray[DirectionIndex] = Directions[DirectionIndex]; + } + + NewColliders = FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(VertexArray, DirArray, AggCollisions); + } + + return (NewColliders > 0); +} + +int32 +FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData) +{ + return FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, OutVertexData); +} + +/* +int32 +FHoudiniMeshTranslator::GetSplitNormals( + const TArray& InSplitVertexList, TArray& OutNormals) +{ + // Extract the normals + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutNormals.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; + OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; + OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; + + OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; + OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; + OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; + + OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; + OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; + OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; + } + + return WedgeCount; +} + +int32 +FHoudiniMeshTranslator::GetSplitUVs( + const TArray& InSplitVertexList, TArray& OutUVs) +{ + // Extract the normals + UpdatePartUVSetsIfNeeded(); + + // Get the normals for this split + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutNormals.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; + OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; + OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; + + OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; + OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; + OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; + + OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; + OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; + OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; + } + + return WedgeCount; +} + + +int32 +FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutData, + const float& ScaleFactor) +{ + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutData.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; + OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; + + OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; + OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; + + OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; + OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; + } + + return WedgeCount; +} + +int32 +FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutData, + const float& ScaleFactor) +{ + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutData.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; + OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; + + OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; + OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; + + OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; + OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; + } + + return WedgeCount; +} +*/ + + +template +int32 FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::TransferPartAttributesToSplit")); + + if (!InAttribInfo.exists || InAttribInfo.tupleSize <= 0) + return 0; + + if (InData.Num() <= 0) + return 0; + + int32 ValidWedgeCount = 0; + + // Future optimization - see if we can do direct vertex transfer. + int32 WedgeCount = InVertexList.Num(); + int32 LastValidWedgeIdx = 0; + if (InAttribInfo.owner == HAPI_ATTROWNER_POINT) + { + // Point attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + int32 VertexIdx = InVertexList[WedgeIdx]; + if (VertexIdx < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[VertexIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_VERTEX) + { + // Vertex attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[WedgeIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_PRIM) + { + // Primitive attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 PrimIdx = WedgeIdx / 3; + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[PrimIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_DETAIL) + { + // Detail attribute transfer + // We have one value to copy for all output split vertices + // if the attribute is a single value (not a tuple) + // then we can simply use the array init function instead of looping + if (InAttribInfo.tupleSize == 1) + { + OutVertexData.Init(InData[0], WedgeCount); + } + else + { + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + } + else + { + // Invalid attribute owner, shouldn't happen + check(false); + } + + OutVertexData.SetNumZeroed(ValidWedgeCount * InAttribInfo.tupleSize); + + return ValidWedgeCount; +} + +float +FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) +{ + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = -1.0f; + + // Start by looking at the lod_screensize primitive attribute + bool bAttribValid = false; + UpdatePartLODScreensizeIfNeeded(); + + if (PartLODScreensize.Num() > 0) + { + // use the "lod_screensize" primitive attribute + int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + if (PartLODScreensize.IsValidIndex(FirstValidPrimIndex)) + screensize = PartLODScreensize[FirstValidPrimIndex]; + } + + if (screensize < 0.0f) + { + // We couldn't find the primitive attribute, look for a "lodX_screensize" detail attribute + FString LODAttributeName = SplitGroupName + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX; + + TArray LODScreenSizes; + HAPI_AttributeInfo AttribInfoScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); + + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*LODAttributeName), + AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_DETAIL); + + if (AttribInfoScreenSize.exists && LODScreenSizes.Num() > 0) + { + screensize = LODScreenSizes[0]; + } + } + + if (screensize < 0.0f) + { + // finally, look for a potential uproperty style attribute + // aka, "unreal_uproperty_screensize" + TArray LODScreenSizes; + HAPI_AttributeInfo AttribInfoScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); + + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, "unreal_uproperty_screensize", + AttribInfoScreenSize, LODScreenSizes); + + if (AttribInfoScreenSize.exists) + { + if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_DETAIL && LODScreenSizes.Num() > 0) + { + screensize = LODScreenSizes[0]; + } + else if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_PRIM) + { + int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + if (LODScreenSizes.IsValidIndex(FirstValidPrimIndex)) + screensize = LODScreenSizes[FirstValidPrimIndex]; + } + } + } + + // Make sure the screensize is in percent, so if its above 1, divide by 100 + if (screensize > 1.0f) + screensize /= 100.0f; + + return screensize; +} + +int32 +FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + // Calculate bounding Box. + FVector Center, Extents; + FVector unitVec = FVector::OneVector;// bs->BuildScale3D; + CalcBoundingBox(InPositionArray, Center, Extents, unitVec); + + FKBoxElem BoxElem; + BoxElem.Center = Center; + BoxElem.X = Extents.X * 2.0f; + BoxElem.Y = Extents.Y * 2.0f; + BoxElem.Z = Extents.Z * 2.0f; + OutAggregateCollisions.BoxElems.Add(BoxElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FBox Box(ForceInit); + for (const FVector& CurPos : PositionArray) + { + Box += CurPos; + } + Box.GetCenterAndExtents(Center, Extents); +} + +int32 +FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FSphere bSphere, bSphere2, bestSphere; + FVector unitVec = FVector::OneVector; + + // Calculate bounding sphere. + CalcBoundingSphere(InPositionArray, bSphere, unitVec); + CalcBoundingSphere2(InPositionArray, bSphere2, unitVec); + + if (bSphere.W < bSphere2.W) + bestSphere = bSphere; + else + bestSphere = bSphere2; + + // Don't use if radius is zero. + if (bestSphere.W <= 0.f) + { + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Sphere collider.")); + return 0; + } + + FKSphereElem SphereElem; + SphereElem.Center = bestSphere.Center; + SphereElem.Radius = bestSphere.W; + OutAggregateCollisions.SphereElems.Add(SphereElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + if (PositionArray.Num() == 0) + return; + + FBox Box; + FVector MinIx[3]; + FVector MaxIx[3]; + + bool bFirstVertex = true; + for (const FVector& CurPosition : PositionArray) + { + FVector p = CurPosition * LimitVec; + if (bFirstVertex) + { + // First, find AABB, remembering furthest points in each dir. + Box.Min = p; + Box.Max = Box.Min; + + MinIx[0] = CurPosition; + MinIx[1] = CurPosition; + MinIx[2] = CurPosition; + + MaxIx[0] = CurPosition; + MaxIx[1] = CurPosition; + MaxIx[2] = CurPosition; + bFirstVertex = false; + continue; + } + + // X // + if (p.X < Box.Min.X) + { + Box.Min.X = p.X; + MinIx[0] = CurPosition; + } + else if (p.X > Box.Max.X) + { + Box.Max.X = p.X; + MaxIx[0] = CurPosition; + } + + // Y // + if (p.Y < Box.Min.Y) + { + Box.Min.Y = p.Y; + MinIx[1] = CurPosition; + } + else if (p.Y > Box.Max.Y) + { + Box.Max.Y = p.Y; + MaxIx[1] = CurPosition; + } + + // Z // + if (p.Z < Box.Min.Z) + { + Box.Min.Z = p.Z; + MinIx[2] = CurPosition; + } + else if (p.Z > Box.Max.Z) + { + Box.Max.Z = p.Z; + MaxIx[2] = CurPosition; + } + } + + const FVector Extremes[3] = { (MaxIx[0] - MinIx[0]) * LimitVec, + (MaxIx[1] - MinIx[1]) * LimitVec, + (MaxIx[2] - MinIx[2]) * LimitVec }; + + // Now find extreme points furthest apart, and initial center and radius of sphere. + float d2 = 0.f; + for (int32 i = 0; i < 3; i++) + { + const float tmpd2 = Extremes[i].SizeSquared(); + if (tmpd2 > d2) + { + d2 = tmpd2; + sphere.Center = (MinIx[i] + (0.5f * Extremes[i])) * LimitVec; + sphere.W = 0.f; + } + } + + const FVector Extents = FVector(Extremes[0].X, Extremes[1].Y, Extremes[2].Z); + + // radius and radius squared + float r = 0.5f * Extents.GetMax(); + float r2 = FMath::Square(r); + + // Now check each point lies within this sphere. If not - expand it a bit. + for (const FVector& curPos : PositionArray) + { + const FVector cToP = (curPos * LimitVec) - sphere.Center; + + const float pr2 = cToP.SizeSquared(); + + // If this point is outside our current bounding sphere's radius + if (pr2 > r2) + { + // ..expand radius just enough to include this point. + const float pr = FMath::Sqrt(pr2); + r = 0.5f * (r + pr); + r2 = FMath::Square(r); + + sphere.Center += ((pr - r) / pr * cToP); + } + } + + sphere.W = r; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FVector Center, Extents; + CalcBoundingBox(PositionArray, Center, Extents, LimitVec); + + sphere.Center = Center; + sphere.W = 0.0f; + + for (const FVector& curPos : PositionArray) + { + float Dist = FVector::DistSquared(curPos * LimitVec, sphere.Center); + if (Dist > sphere.W) + sphere.W = Dist; + } + sphere.W = FMath::Sqrt(sphere.W); +} + +int32 +FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FSphere sphere; + float length; + FRotator rotation; + FVector unitVec = FVector::OneVector; + + // Calculate bounding sphyl. + CalcBoundingSphyl(InPositionArray, sphere, length, rotation, unitVec); + + // Dont use if radius is zero. + if (sphere.W <= 0.f) + { + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Capsule collider.")); + return 0; + } + + // If height is zero, then a sphere would be better (should we just create one instead?) + if (length <= 0.f) + { + length = SMALL_NUMBER; + } + + FKSphylElem SphylElem; + SphylElem.Center = sphere.Center; + SphylElem.Rotation = rotation; + SphylElem.Radius = sphere.W; + SphylElem.Length = length; + OutAggregateCollisions.SphylElems.Add(SphylElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + if (PositionArray.Num() == 0) + return; + + FVector Center, Extents; + CalcBoundingBox(PositionArray, Center, Extents, LimitVec); + + // @todo sphere.Center could perhaps be adjusted to best fit if model is non-symmetric on it's longest axis + sphere.Center = Center; + + // Work out best axis aligned orientation (longest side) + float Extent = Extents.GetMax(); + if (Extent == Extents.X) + { + rotation = FRotator(90.f, 0.f, 0.f); + Extents.X = 0.0f; + } + else if (Extent == Extents.Y) + { + rotation = FRotator(0.f, 0.f, 90.f); + Extents.Y = 0.0f; + } + else + { + rotation = FRotator(0.f, 0.f, 0.f); + Extents.Z = 0.0f; + } + + // Cleared the largest axis above, remaining determines the radius + float r = Extents.GetMax(); + float r2 = FMath::Square(r); + + // Now check each point lies within this the radius. If not - expand it a bit. + for (const FVector& CurPos : PositionArray) + { + FVector cToP = (CurPos * LimitVec) - sphere.Center; + cToP = rotation.UnrotateVector(cToP); + + const float pr2 = cToP.SizeSquared2D(); // Ignore Z here... + + // If this point is outside our current bounding sphere's radius + if (pr2 > r2) + { + // ..expand radius just enough to include this point. + const float pr = FMath::Sqrt(pr2); + r = 0.5f * (r + pr); + r2 = FMath::Square(r); + } + } + + // The length is the longest side minus the radius. + float hl = FMath::Max(0.0f, Extent - r); + + // Now check each point lies within the length. If not - expand it a bit. + for (const FVector& CurPos : PositionArray) + { + FVector cToP = (CurPos * LimitVec) - sphere.Center; + cToP = rotation.UnrotateVector(cToP); + + // If this point is outside our current bounding sphyl's length + if (FMath::Abs(cToP.Z) > hl) + { + const bool bFlip = (cToP.Z < 0.f ? true : false); + const FVector cOrigin(0.f, 0.f, (bFlip ? -hl : hl)); + + const float pr2 = (cOrigin - cToP).SizeSquared(); + + // If this point is outside our current bounding sphyl's radius + if (pr2 > r2) + { + FVector cPoint; + FMath::SphereDistToLine(cOrigin, r, cToP, (bFlip ? FVector(0.f, 0.f, 1.f) : FVector(0.f, 0.f, -1.f)), cPoint); + + // Don't accept zero as a valid diff when we know it's outside the sphere (saves needless retest on further iterations of like points) + hl += FMath::Max(FMath::Abs(cToP.Z - cPoint.Z), 1.e-6f); + } + } + } + + sphere.W = r; + length = hl * 2.0f; +} + +int32 +FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + const float my_flt_max = 3.402823466e+38F; + + // Do k- specific stuff. + int32 kCount = Dirs.Num(); + + TArray maxDist; + maxDist.Init(-my_flt_max, kCount); + /* + for (int32 i = 0; i < kCount; i++) + maxDist.Add(my_flt_max); + */ + + // Construct temporary UModel for kdop creation. We keep no refs to it, so it can be GC'd. + auto TempModel = NewObject(); + TempModel->Initialize(nullptr, 1); + + // For each vertex, project along each kdop direction, to find the max in that direction. + for (int32 i = 0; i < InPositionArray.Num(); i++) + { + for (int32 j = 0; j < kCount; j++) + { + float dist = InPositionArray[i] | Dirs[j]; + maxDist[j] = FMath::Max(dist, maxDist[j]); + } + } + + // Inflate kdop to ensure it is no degenerate + const float MinSize = 0.1f; + for (int32 i = 0; i < kCount; i++) + { + maxDist[i] += MinSize; + } + + // Now we have the planes of the kdop, we work out the face polygons. + TArray planes; + for (int32 i = 0; i < kCount; i++) + planes.Add(FPlane(Dirs[i], maxDist[i])); + + for (int32 i = 0; i < planes.Num(); i++) + { + FPoly* Polygon = new(TempModel->Polys->Element) FPoly(); + FVector Base, AxisX, AxisY; + + Polygon->Init(); + Polygon->Normal = planes[i]; + Polygon->Normal.FindBestAxisVectors(AxisX, AxisY); + + Base = planes[i] * planes[i].W; + + new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); + + for (int32 j = 0; j < planes.Num(); j++) + { + if (i != j) + { + if (!Polygon->Split(-FVector(planes[j]), planes[j] * planes[j].W)) + { + Polygon->Vertices.Empty(); + break; + } + } + } + + if (Polygon->Vertices.Num() < 3) + { + // If poly resulted in no verts, remove from array + TempModel->Polys->Element.RemoveAt(TempModel->Polys->Element.Num() - 1); + } + else + { + // Other stuff... + Polygon->iLink = i; + Polygon->CalcNormal(1); + } + } + + if (TempModel->Polys->Element.Num() < 4) + { + TempModel = NULL; + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple KDOP collider.")); + return 0; + } + + // Build bounding box. + TempModel->BuildBound(); + + // Build BSP for the brush. + FBSPOps::bspBuild(TempModel, FBSPOps::BSP_Good, 15, 70, 1, 0); + FBSPOps::bspRefresh(TempModel, 1); + FBSPOps::bspBuildBounds(TempModel); + + // Now, create a temporary BodySetup to build the colliders + UBodySetup* TempBS = NewObject(); + TempBS->CreateFromModel(TempModel, false); + + // Copy the convex elements back to our aggregate + int32 NewConvexElems = 0; + if (TempBS && TempBS->AggGeom.ConvexElems.Num() > 0) + { + for (const auto& CurConvexElem : TempBS->AggGeom.ConvexElems) + { + OutAggregateCollisions.ConvexElems.Add(CurConvexElem); + NewConvexElems++; + } + } + + return NewConvexElems; +} + + +bool +FHoudiniMeshTranslator::GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, + const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, + TArray& OutPropertyAttributes) +{ + // List all the generic property detail attributes ... + int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + + // .. then the primitive property attributes for the given prim + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); + + // .. then finally, point uprop attributes for the given vert + // TODO: !! get the correct Index here? + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidVertexIndex); + + return FoundCount > 0; +} + +bool +FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current Property Attribute + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) + continue; + + // Success! + NumSuccess++; + FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); + } + + return (NumSuccess > 0); +} + + +void +FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO) +{ + PackageParams = InPackageParams; + + if (bUpdateHGPO) + { + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.ObjectId; + PackageParams.PartId = HGPO.ObjectId; + } +} + +bool +FHoudiniMeshTranslator::RemoveAndDestroyComponent(UObject* InComponent) +{ + if (!InComponent || InComponent->IsPendingKill()) + return false; + + USceneComponent* SceneComponent = Cast(InComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + return true; + } + + return false; +} + +UMeshComponent* +FHoudiniMeshTranslator::CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf &InComponentType) +{ + // Create a new SMC as we couldn't find an existing one + USceneComponent* OuterSceneComponent = Cast(InOuterComponent); + UObject * Outer = nullptr; + if (OuterSceneComponent && !OuterSceneComponent->IsPendingKill()) + Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); + + UMeshComponent *MeshComponent = NewObject(Outer, InComponentType, NAME_None, RF_Transactional); + + // Initialize it + MeshComponent->SetVisibility(true); + //MeshComponent->SetMobility(Mobility); + + // TODO: + // Property propagation: set the new SMC's properties to the HAC's current settings + //CopyComponentPropertiesTo(MeshComponent); + + // Change the creation method so the component is listed in the details panels + MeshComponent->CreationMethod = EComponentCreationMethod::Instance; + + // Attach created static mesh component to our Houdini component. + MeshComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + MeshComponent->OnComponentCreated(); + MeshComponent->RegisterComponent(); + + return MeshComponent; +} + +bool +FHoudiniMeshTranslator::PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh) +{ + UStaticMesh *Mesh = Cast(InMesh); + if (Mesh) + { + InComponent->SetStaticMesh(Mesh); + + return true; + } + + return false; +} + +bool +FHoudiniMeshTranslator::PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh) +{ + UHoudiniStaticMesh *Mesh = Cast(InMesh); + if (Mesh) + { + InComponent->SetMesh(Mesh); + + return true; + } + + return false; +} + +UMeshComponent* +FHoudiniMeshTranslator::CreateOrUpdateMeshComponent( + const UHoudiniOutput *InOutput, + UObject *InOuterComponent, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TSubclassOf& InComponentType, + FHoudiniOutputObject& OutputObject, + FHoudiniGeoPartObject const *& OutFoundHGPO, + bool& bCreated) +{ + bCreated = false; + OutFoundHGPO = nullptr; + + // Find the HGPO that matches this mesh + for (auto& curHGPO : InOutput->HoudiniGeoPartObjects) + { + if (curHGPO.ObjectId != InOutputIdentifier.ObjectId + || curHGPO.GeoId != InOutputIdentifier.GeoId + || curHGPO.PartId != InOutputIdentifier.PartId) + { + continue; + } + + if (InOutputIdentifier.SplitIdentifier.Equals(HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION) + || curHGPO.SplitGroups.Contains(InOutputIdentifier.SplitIdentifier)) + { + OutFoundHGPO = &curHGPO; + } + } + + // No need to create a component for instanced meshes! + if (OutFoundHGPO && OutFoundHGPO->bIsInstanced) + return nullptr; + + bool bIsProxyComponent = InComponentType == UHoudiniStaticMeshComponent::StaticClass(); + + // See if we already have a component for that mesh + UMeshComponent* MeshComponent = nullptr; + if (bIsProxyComponent) + MeshComponent = Cast(OutputObject.ProxyComponent); + else + MeshComponent = Cast(OutputObject.OutputComponent); + + // If there is an existing component, but it is pending kill, then it was likely + // deleted by some other process, such as by the user in the editor, so don't use it + if (!MeshComponent || MeshComponent->IsPendingKill() || !MeshComponent->IsA(InComponentType)) + { + // If the component is not of type InComponentType, or the found component is pending kill, destroy + // the existing component (a new one is then created below) + RemoveAndDestroyComponent(MeshComponent); + MeshComponent = nullptr; + } + + if (!MeshComponent) + { + // Create a new SMC/HSMC as we couldn't find an existing one + MeshComponent = CreateMeshComponent(InOuterComponent, InComponentType); + + if (MeshComponent) + { + // Add to the output object + if (bIsProxyComponent) + OutputObject.ProxyComponent = MeshComponent; + else + OutputObject.OutputComponent = MeshComponent; + + bCreated = true; + } + } + + return MeshComponent; +} + +bool +FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, + TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors) +{ + if (!Socket || Socket->IsPendingKill() || !StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + return false; + + // The actor to assign is stored is the socket's tag + FString ActorString = Socket->Tag; + if (ActorString.IsEmpty()) + return false; + + // The actor to assign are listed after a | + TArray ActorStringArray; + ActorString.ParseIntoArray(ActorStringArray, TEXT("|"), false); + + // The "real" Tag is the first + if (ActorStringArray.Num() > 0) + Socket->Tag = ActorStringArray[0]; + + // We just add a Tag, no Actor + if (ActorStringArray.Num() == 1) + return false; + + // Extract the parsed actor string to split it further + ActorString = ActorStringArray[1]; + + // Converting the string to a string array using delimiters + const TCHAR* Delims[] = { TEXT(","), TEXT(";") }; + ActorString.ParseIntoArray(ActorStringArray, Delims, 2); + + // And try to find the corresponding HoudiniAssetActor in the editor world + // to avoid finding "deleted" assets with the same name + //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); +#if WITH_EDITOR + UWorld* EditorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; + if (!EditorWorld || EditorWorld->IsPendingKill()) + return false; + + // Remove the previous created actors which were attached to this socket + { + for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniCreatedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + continue; + } + + if (CurActor->GetAttachParentSocketName() == Socket->SocketName) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + CurActor->Destroy(); + } + } + } + + // Detach the previous in level actors which was attached to this socket + { + for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniAttachedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniAttachedSocketActors.RemoveAt(Idx); + continue; + } + + if (CurActor->GetAttachParentSocketName() == Socket->SocketName) + { + CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + HoudiniAttachedSocketActors.RemoveAt(Idx); + } + } + } + + auto CreateDefaultActor = [EditorWorld, StaticMeshComponent, Socket, HoudiniCreatedSocketActors]() + { + AActor * CreatedDefaultActor = nullptr; + + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + { + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( + EditorWorld->GetCurrentLevel(), DefaultReferenceSM, false, RF_Transactional, nullptr); + + if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load default mesh.")); + } + else + { + + // Set the default mesh actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : NewActors[0]->GetComponents()) + { + UStaticMeshComponent * CurSMC = Cast(CurComp); + if (CurSMC && !CurSMC->IsPendingKill()) + CurSMC->SetMobility(OutputSMCMobility); + } + + // Set the default mesh actor hidden in game. + NewActors[0]->SetActorHiddenInGame(true); + + Socket->AttachActor(NewActors[0], StaticMeshComponent); + CreatedDefaultActor = NewActors[0]; + //HoudiniCreatedSocketActors.Add(NewActors[0]); + } + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load default mesh.")); + } + + return CreatedDefaultActor; + }; + + bool bUseDefaultActor = true; + // Get from the Houdini runtime setting if use default object when the reference is invalid + // true by default if fail to access HoudiniRuntimeSettings + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bUseDefaultActor = HoudiniRuntimeSettings->bShowDefaultMesh; + } + + if (ActorStringArray.Num() <= 0) + { + if (!bUseDefaultActor) + return true; + + HOUDINI_LOG_WARNING( + TEXT("Output static mesh: Socket '%s' actor is not specified. Spawn a default mesh (hidden in game)."), *(Socket->GetName())); + + AActor * DefaultActor = CreateDefaultActor(); + if (DefaultActor && !DefaultActor->IsPendingKill()) + HoudiniCreatedSocketActors.Add(DefaultActor); + + return true; + } + + // try to find the actor in level first + for (TActorIterator ActorItr(EditorWorld); ActorItr; ++ActorItr) + { + // Same as with the Object Iterator, access the subclass instance with the * or -> operators. + AActor *Actor = *ActorItr; + if (!Actor || Actor->IsPendingKillOrUnreachable()) + continue; + + for (int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++) + { + if (Actor->GetName() != ActorStringArray[StringIdx] + && Actor->GetActorLabel() != ActorStringArray[StringIdx]) + continue; + + // Set the actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : Actor->GetComponents()) + { + UStaticMeshComponent * SMC = Cast(CurComp); + if (SMC && !SMC->IsPendingKill()) + SMC->SetMobility(OutputSMCMobility); + } + + Socket->AttachActor(Actor, StaticMeshComponent); + HoudiniAttachedSocketActors.Add(Actor); + + // Remove the string if the actor is found in the editor level + ActorStringArray.RemoveAt(StringIdx); + break; + } + } + + bool bSuccess = true; + // If some of the actors are not found in the level, try to find them in the content browser. Spawn one if existed + for (int32 Idx = ActorStringArray.Num() - 1; Idx>= 0; --Idx) + { + UObject * Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *ActorStringArray[Idx]); + if (!Obj || Obj->IsPendingKill()) + { + bSuccess = false; + continue; + } + + // Spawn a new actor with the found object + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( + EditorWorld->GetCurrentLevel(), Obj, false, RF_Transactional, nullptr); + + if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + { + bSuccess = false; + continue; + } + + // Set the new actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : NewActors[0]->GetComponents()) + { + UStaticMeshComponent * CurSMC = Cast(CurComp); + if (CurSMC && !CurSMC->IsPendingKill()) + CurSMC->SetMobility(OutputSMCMobility); + } + + Socket->AttachActor(NewActors[0], StaticMeshComponent); + HoudiniCreatedSocketActors.Add(NewActors[0]); + + ActorStringArray.RemoveAt(Idx); + } + + // Failed to find actors in both level and content browser + // Spawn default actors if enabled + if (bUseDefaultActor) + { + for (int32 Idx = ActorStringArray.Num() - 1; Idx >= 0; --Idx) + { + HOUDINI_LOG_WARNING( + TEXT("Output static mesh: Failed to attach '%s' to socket '%s', spawn a default mesh (hidden in game)."), *(ActorStringArray[Idx]), *(Socket->GetName())); + + // If failed to load this object, spawn a default mesh + AActor * CurDefaultActor = CreateDefaultActor(); + if (CurDefaultActor && !CurDefaultActor->IsPendingKill()) + HoudiniCreatedSocketActors.Add(CurDefaultActor); + } + } + + if (ActorStringArray.Num() > 0) + return false; +#endif + + return bSuccess; +} + +void +FHoudiniMeshTranslator::SetMeshBuildSettings( + FMeshBuildSettings& OutMeshBuildSettings, + const bool& bHasNormals, + const bool& bHasTangents, + const bool& bHasLightmapUVSet) +{ + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + OutMeshBuildSettings.bRemoveDegenerates = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bRemoveDegenerates : true; + OutMeshBuildSettings.bUseMikkTSpace = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bUseMikkTSpace : true; + OutMeshBuildSettings.bBuildAdjacencyBuffer = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bBuildAdjacencyBuffer : false; + OutMeshBuildSettings.MinLightmapResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MinLightmapResolution : 64; + OutMeshBuildSettings.bUseFullPrecisionUVs = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bUseFullPrecisionUVs : false; + OutMeshBuildSettings.SrcLightmapIndex = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->SrcLightmapIndex : 0; + OutMeshBuildSettings.DstLightmapIndex = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->DstLightmapIndex : 1; + + OutMeshBuildSettings.bComputeWeightedNormals = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bComputeWeightedNormals : false; + OutMeshBuildSettings.bBuildReversedIndexBuffer = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bBuildReversedIndexBuffer : true; + OutMeshBuildSettings.bUseHighPrecisionTangentBasis = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bUseHighPrecisionTangentBasis : false; + OutMeshBuildSettings.bGenerateDistanceFieldAsIfTwoSided = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bGenerateDistanceFieldAsIfTwoSided : false; + OutMeshBuildSettings.bSupportFaceRemap = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bSupportFaceRemap : false; + OutMeshBuildSettings.DistanceFieldResolutionScale = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->DistanceFieldResolutionScale : 2.0f; + + // Recomputing normals. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag : HRSRF_OnlyIfMissing; + switch (RecomputeNormalFlag) + { + case HRSRF_Always: + { + OutMeshBuildSettings.bRecomputeNormals = true; + break; + } + + case HRSRF_OnlyIfMissing: + { + OutMeshBuildSettings.bRecomputeNormals = !bHasNormals; + break; + } + + case HRSRF_Never: + default: + { + OutMeshBuildSettings.bRecomputeNormals = false; + break; + } + } + + // Recomputing tangents. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; + switch (RecomputeTangentFlag) + { + case HRSRF_Always: + { + OutMeshBuildSettings.bRecomputeTangents = true; + break; + } + + case HRSRF_OnlyIfMissing: + { + OutMeshBuildSettings.bRecomputeTangents = !bHasTangents; + break; + } + + case HRSRF_Never: + default: + { + OutMeshBuildSettings.bRecomputeTangents = false; + break; + } + } + + // Lightmap UV generation. + EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; + switch (GenerateLightmapUVFlag) + { + case HRSRF_Always: + { + OutMeshBuildSettings.bGenerateLightmapUVs = true; + break; + } + + case HRSRF_OnlyIfMissing: + { + OutMeshBuildSettings.bGenerateLightmapUVs = !bHasLightmapUVSet; + break; + } + + case HRSRF_Never: + default: + { + OutMeshBuildSettings.bGenerateLightmapUVs = false; + break; + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h index d6a2e2fc7..e4fb420db 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h @@ -1,404 +1,418 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "HoudiniPackageParams.h" -#include "HoudiniAssetComponent.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "PhysicsEngine/AggregateGeom.h" - -//#include "HoudiniMeshTranslator.generated.h" - -class UStaticMesh; -class UStaticMeshSocket; -class UMaterialInterface; -class UMeshComponent; -class UStaticMeshComponent; -class UHoudiniStaticMesh; -class UHoudiniStaticMeshComponent; - -struct FKAggregateGeom; -struct FHoudiniGenericAttribute; - - -UENUM() -enum class EHoudiniSplitType : uint8 -{ - Invalid, - - Normal, - - LOD, - - RenderedComplexCollider, - InvisibleComplexCollider, - - RenderedUCXCollider, - InvisibleUCXCollider, - - RenderedSimpleCollider, - InvisibleSimpleCollider -}; - -struct HOUDINIENGINE_API FHoudiniMeshTranslator -{ - public: - - //----------------------------------------------------------------------------------------------------------------------------- - // HOUDINI TO UNREAL - //----------------------------------------------------------------------------------------------------------------------------- - - // - static bool CreateAllMeshesAndComponentsFromHoudiniOutput( - UHoudiniOutput* InOutput, - const FHoudiniPackageParams& InPackageParams, - EHoudiniStaticMeshMethod InStaticMeshMethod, - UObject* InOuterComponent, - bool bInTreatExistingMaterialsAsUpToDate=false, - bool bInDestroyProxies=false); - - static bool CreateStaticMeshFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& InOutputObjects, - TMap& OutOutputObjects, - TMap& InAssignmentMaterialMap, - TMap& InReplacementMaterialMap, - const bool& InForceRebuild, - EHoudiniStaticMeshMethod InStaticMeshMethod, - bool bInTreatExistingMaterialsAsUpToDate = false); - - static bool CreateOrUpdateAllComponents( - UHoudiniOutput* InOutput, - UObject* InOuterComponent, - TMap& InNewOutputObjects, - bool bInDestroyProxies=false, - bool bInApplyGenericProperties=true); - - - //----------------------------------------------------------------------------------------------------------------------------- - // HELPERS - //----------------------------------------------------------------------------------------------------------------------------- - static EHoudiniSplitType GetSplitTypeFromSplitName(const FString& InSplitName); - - static FString GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType); - - // TODO: Rename me! and template me! float/int/string ? - // TransferPartAttributesToSplitVertices - static int32 TransferRegularPointAttributesToVertices( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData); - - template - static int32 TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutSplitData); - - - //----------------------------------------------------------------------------------------------------------------------------- - // ACCESSORS - //----------------------------------------------------------------------------------------------------------------------------- - - //----------------------------------------------------------------------------------------------------------------------------- - // MUTATORS - //----------------------------------------------------------------------------------------------------------------------------- - void SetHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) { HGPO = InHGPO; }; - void SetOuterComponent(UObject* InOuter) { OuterComponent = InOuter; }; - void SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO = false); - - void SetInputObjects(const TMap& InInputObjects) { InputObjects = InInputObjects; }; - void SetOutputObjects(TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; - - void SetInputAssignmentMaterials(const TMap& InInputMaterials) { InputAssignmentMaterials = InInputMaterials; }; - void SetReplacementMaterials(const TMap& InReplacementMaterials) { ReplacementMaterials = InReplacementMaterials; }; - - //void SetInputObjectProperties(const TMap& InInputObjectProperties) { InputObjectProperties = InInputObjectProperties; }; - //void SetOutputObjectProperties(TMap& InOutputObjectProperties) { OutputObjectProperties = InOutputObjectProperties; }; - - void SetTreatExistingMaterialsAsUpToDate(bool bInTreatExistingMaterialsAsUpToDate) { bTreatExistingMaterialsAsUpToDate = bInTreatExistingMaterialsAsUpToDate; } - - //----------------------------------------------------------------------------------------------------------------------------- - // Helpers - //----------------------------------------------------------------------------------------------------------------------------- - - // Helper functions for generic property attributes - static bool GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes); - - protected: - - // Create a StaticMesh using the MeshDescription format - bool CreateStaticMesh_MeshDescription(); - - // Legacy function using RawMesh for static Mesh creation - bool CreateStaticMesh_RawMesh(); - - // Create a UHoudiniStaticMesh - bool CreateHoudiniStaticMesh(); - - void ResetPartCache(); - - bool UpdatePartVertexList(); - - void SortSplitGroups(); - - bool UpdateSplitsFacesAndIndices(); - - // Update this part's position cache if we haven't already - bool UpdatePartPositionIfNeeded(); - - // Update this part's normal cache if we haven't already - bool UpdatePartNormalsIfNeeded(); - - // Update this part's tangent and binormal caches if we haven't already - bool UpdatePartTangentsIfNeeded(); - - // Update this part's color cache if we haven't already - bool UpdatePartColorsIfNeeded(); - - // Update this part's alpha if we haven't already - bool UpdatePartAlphasIfNeeded(); - - // Update this part's face smoothing values if we haven't already - bool UpdatePartFaceSmoothingIfNeeded(); - - // Update this part's UV sets if we haven't already - bool UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused = false); - - // Update this part;s lightmap resolution cache if we haven't already - bool UpdatePartLightmapResolutionsIfNeeded(); - - // Update this part's lod screensize attribute cache if we haven't already - bool UpdatePartLODScreensizeIfNeeded(); - - // Update th unique materials ids and infos needed for this part using the face materials and overrides - bool UpdatePartNeededMaterials(); - - // Update this part's face material IDs, unique material IDs and material Infos caches if we haven't already - bool UpdatePartFaceMaterialIDsIfNeeded(); - - // Update this part's material overrides cache if we haven't already - bool UpdatePartFaceMaterialOverridesIfNeeded(); - - // Updates and create the material that are needed for this part - bool CreateNeededMaterials(); - - UStaticMesh* CreateNewStaticMesh(const FString& InMeshIdentifierString); - - UStaticMesh* FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); - - UHoudiniStaticMesh* CreateNewHoudiniStaticMesh(const FString& InMeshIdentifierString); - - UHoudiniStaticMesh* FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); - - float GetLODSCreensizeForSplit(const FString& SplitGroupName); - - // Create convex/UCX collider for a split and add to the aggregate - bool AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); - // Create simple colliders for a split and add to the aggregate - bool AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); - - // Helper functions to generate the simple colliders and add them to the aggregate - static int32 GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions); - - // Helper functions for the simple colliders generation - static void CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec); - static void CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); - static void CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); - static void CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec); - - // Helper functions to remove unused/stale components - static bool RemoveAndDestroyComponent(UObject* InComponent); - - // Helper to create a new mesh component - static UMeshComponent* CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf& InComponentType); - - // Helper to update an existing mesh component - static void UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, - const FHoudiniGeoPartObject *InHGPO, TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors, - bool bInApplyGenericProperties=true); - - // Helper to create or update a mesh component for a UStaticMesh or proxy mesh output - static UMeshComponent* CreateOrUpdateMeshComponent( - const UHoudiniOutput* InOutput, - UObject* InOuterComponent, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TSubclassOf& InComponentType, - FHoudiniOutputObject& OutOutputObject, - FHoudiniGeoPartObject const *& OutFoundHGPO, - bool &bCreated); - - // Helper to initialize a UStaticMeshComponent after it was created. - static bool PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh); - - // Helper to initialize a UHoudiniStaticMeshComponent after it was created. - static bool PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh); - - static bool AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, - TArray& HoudiniCreatedSocketActors, TArray& HoudiniAttachedSocketActors); - - protected: - - // Data cache for this translator - - // The HoudiniGeoPartObject we're working on - FHoudiniGeoPartObject HGPO; - - // Outer object for attaching components to - UObject* OuterComponent; - - // Structure that handles cooking/baking package creation parameters - FHoudiniPackageParams PackageParams; - - - // Previous output objects - TMap InputObjects; - - // New Output objects - TMap OutputObjects; - - - // Input Material Map - TMap InputAssignmentMaterials; - // Output Material Map - TMap OutputAssignmentMaterials; - // Input Replacement Materials maps - TMap ReplacementMaterials; - - // Input mesh properties - //TMap InputObjectProperties; - // Output mesh properties - //TMap OutputObjectProperties; - - // Indicates the update is forced - bool ForceRebuild; - - // The generated simple/UCX colliders - TMap AllAggregateCollisions; - - // Names of the groups used for splitting the geometry - TArray AllSplitGroups; - - // Per-split lists of faces - TMap> AllSplitVertexLists; - - // Per-split number of faces - TMap AllSplitVertexCounts; - - // Per-split indices arrays - TMap> AllSplitFaceIndices; - - // Per-split first valid vertex index - TMap AllSplitFirstValidVertexIndex; - - // Per-split first valid prim index - TMap AllSplitFirstValidPrimIndex; - - // Vertex Indices for the part - TArray PartVertexList; - - // Positions - TArray PartPositions; - HAPI_AttributeInfo AttribInfoPositions; - - // Vertex Normals - TArray PartNormals; - HAPI_AttributeInfo AttribInfoNormals; - - // Vertex TangentU - TArray PartTangentU; - HAPI_AttributeInfo AttribInfoTangentU; - - // Vertex TangentV - TArray PartTangentV; - HAPI_AttributeInfo AttribInfoTangentV; - - // Vertex Colors - TArray PartColors; - HAPI_AttributeInfo AttribInfoColors; - - // Vertex Alpha values - TArray PartAlphas; - HAPI_AttributeInfo AttribInfoAlpha; - - // Face Smoothing masks - TArray PartFaceSmoothingMasks; - HAPI_AttributeInfo AttribInfoFaceSmoothingMasks; - - // UVs - TArray> PartUVSets; - TArray AttribInfoUVSets; - - // Lightmap resolution - TArray PartLightMapResolutions; - HAPI_AttributeInfo AttribInfoLightmapResolution; - - // Material IDs per face - TArray PartFaceMaterialIds; - HAPI_AttributeInfo AttribInfoFaceMaterialIds; - // Unique material IDs - TArray PartUniqueMaterialIds; - //TSet PartUniqueMaterialIds; - // Material infos for each unique Material - TArray PartUniqueMaterialInfos; - //TSet PartUniqueMaterialInfos; - // Indicates we only have a single face material - bool bOnlyOneFaceMaterial; - - // Material Overrides per face - TArray PartFaceMaterialOverrides; - HAPI_AttributeInfo AttribInfoFaceMaterialOverrides; - // Indicates that material overides attributes need an instance to be created - bool bMaterialOverrideNeedsCreateInstance; - - // LOD Screensize - TArray PartLODScreensize; - HAPI_AttributeInfo AttribInfoLODScreensize; - - int32 DefaultMeshSmoothing; - - // When building a mesh, if an associated material already exists, treat - // it as up to date, regardless of the MaterialInfo.bHasChanged flag - bool bTreatExistingMaterialsAsUpToDate; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" +#include "HoudiniAssetComponent.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "PhysicsEngine/AggregateGeom.h" + +//#include "HoudiniMeshTranslator.generated.h" + +class UStaticMesh; +class UStaticMeshSocket; +class UMaterialInterface; +class UMeshComponent; +class UStaticMeshComponent; +class UHoudiniStaticMesh; +class UHoudiniStaticMeshComponent; + +struct FKAggregateGeom; +struct FHoudiniGenericAttribute; + + +UENUM() +enum class EHoudiniSplitType : uint8 +{ + Invalid, + + Normal, + + LOD, + + RenderedComplexCollider, + InvisibleComplexCollider, + + RenderedUCXCollider, + InvisibleUCXCollider, + + RenderedSimpleCollider, + InvisibleSimpleCollider +}; + +struct HOUDINIENGINE_API FHoudiniMeshTranslator +{ + public: + + //----------------------------------------------------------------------------------------------------------------------------- + // HOUDINI TO UNREAL + //----------------------------------------------------------------------------------------------------------------------------- + + // + static bool CreateAllMeshesAndComponentsFromHoudiniOutput( + UHoudiniOutput* InOutput, + const FHoudiniPackageParams& InPackageParams, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + UObject* InOuterComponent, + bool bInTreatExistingMaterialsAsUpToDate=false, + bool bInDestroyProxies=false); + + static bool CreateStaticMeshFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& InOutputObjects, + TMap& OutOutputObjects, + TMap& InAssignmentMaterialMap, + TMap& InReplacementMaterialMap, + const bool& InForceRebuild, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + bool bInTreatExistingMaterialsAsUpToDate = false); + + static bool CreateOrUpdateAllComponents( + UHoudiniOutput* InOutput, + UObject* InOuterComponent, + TMap& InNewOutputObjects, + bool bInDestroyProxies=false, + bool bInApplyGenericProperties=true); + + + //----------------------------------------------------------------------------------------------------------------------------- + // HELPERS + //----------------------------------------------------------------------------------------------------------------------------- + static EHoudiniSplitType GetSplitTypeFromSplitName(const FString& InSplitName); + + static FString GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType); + + // TODO: Rename me! and template me! float/int/string ? + // TransferPartAttributesToSplitVertices + static int32 TransferRegularPointAttributesToVertices( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData); + + template + static int32 TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutSplitData); + + // Update the MeshBuild Settings using the Houdini runtime settings + static void SetMeshBuildSettings( + FMeshBuildSettings& OutMeshBuildSettings, + const bool& bHasNormals, + const bool& bHasTangents, + const bool& bHasLightmapUVSet); + + + //----------------------------------------------------------------------------------------------------------------------------- + // ACCESSORS + //----------------------------------------------------------------------------------------------------------------------------- + + //----------------------------------------------------------------------------------------------------------------------------- + // MUTATORS + //----------------------------------------------------------------------------------------------------------------------------- + void SetHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) { HGPO = InHGPO; }; + void SetOuterComponent(UObject* InOuter) { OuterComponent = InOuter; }; + void SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO = false); + + void SetInputObjects(const TMap& InInputObjects) { InputObjects = InInputObjects; }; + void SetOutputObjects(TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; + + void SetInputAssignmentMaterials(const TMap& InInputMaterials) { InputAssignmentMaterials = InInputMaterials; }; + void SetReplacementMaterials(const TMap& InReplacementMaterials) { ReplacementMaterials = InReplacementMaterials; }; + + //void SetInputObjectProperties(const TMap& InInputObjectProperties) { InputObjectProperties = InInputObjectProperties; }; + //void SetOutputObjectProperties(TMap& InOutputObjectProperties) { OutputObjectProperties = InOutputObjectProperties; }; + + void SetTreatExistingMaterialsAsUpToDate(bool bInTreatExistingMaterialsAsUpToDate) { bTreatExistingMaterialsAsUpToDate = bInTreatExistingMaterialsAsUpToDate; } + + void SetStaticMeshGenerationProperties(const FHoudiniStaticMeshGenerationProperties& InStaticMeshGenerationProperties) { StaticMeshGenerationProperties = InStaticMeshGenerationProperties; }; + + //----------------------------------------------------------------------------------------------------------------------------- + // Helpers + //----------------------------------------------------------------------------------------------------------------------------- + + // Helper functions for generic property attributes + static bool GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, + const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes); + + protected: + + // Create a StaticMesh using the MeshDescription format + bool CreateStaticMesh_MeshDescription(); + + // Legacy function using RawMesh for static Mesh creation + bool CreateStaticMesh_RawMesh(); + + // Create a UHoudiniStaticMesh + bool CreateHoudiniStaticMesh(); + + void ResetPartCache(); + + bool UpdatePartVertexList(); + + void SortSplitGroups(); + + bool UpdateSplitsFacesAndIndices(); + + // Update this part's position cache if we haven't already + bool UpdatePartPositionIfNeeded(); + + // Update this part's normal cache if we haven't already + bool UpdatePartNormalsIfNeeded(); + + // Update this part's tangent and binormal caches if we haven't already + bool UpdatePartTangentsIfNeeded(); + + // Update this part's color cache if we haven't already + bool UpdatePartColorsIfNeeded(); + + // Update this part's alpha if we haven't already + bool UpdatePartAlphasIfNeeded(); + + // Update this part's face smoothing values if we haven't already + bool UpdatePartFaceSmoothingIfNeeded(); + + // Update this part's UV sets if we haven't already + bool UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused = false); + + // Update this part;s lightmap resolution cache if we haven't already + bool UpdatePartLightmapResolutionsIfNeeded(); + + // Update this part's lod screensize attribute cache if we haven't already + bool UpdatePartLODScreensizeIfNeeded(); + + // Update th unique materials ids and infos needed for this part using the face materials and overrides + bool UpdatePartNeededMaterials(); + + // Update this part's face material IDs, unique material IDs and material Infos caches if we haven't already + bool UpdatePartFaceMaterialIDsIfNeeded(); + + // Update this part's material overrides cache if we haven't already + bool UpdatePartFaceMaterialOverridesIfNeeded(); + + // Updates and create the material that are needed for this part + bool CreateNeededMaterials(); + + UStaticMesh* CreateNewStaticMesh(const FString& InMeshIdentifierString); + + UStaticMesh* FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); + + UHoudiniStaticMesh* CreateNewHoudiniStaticMesh(const FString& InMeshIdentifierString); + + UHoudiniStaticMesh* FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); + + float GetLODSCreensizeForSplit(const FString& SplitGroupName); + + // Create convex/UCX collider for a split and add to the aggregate + bool AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); + // Create simple colliders for a split and add to the aggregate + bool AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); + + // Helper functions to generate the simple colliders and add them to the aggregate + static int32 GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions); + + // Helper functions for the simple colliders generation + static void CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec); + static void CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); + static void CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); + static void CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec); + + // Helper functions to remove unused/stale components + static bool RemoveAndDestroyComponent(UObject* InComponent); + + // Helper to create a new mesh component + static UMeshComponent* CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf& InComponentType); + + // Helper to update an existing mesh component + static void UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, + const FHoudiniGeoPartObject *InHGPO, TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors, + bool bInApplyGenericProperties=true); + + // Helper to create or update a mesh component for a UStaticMesh or proxy mesh output + static UMeshComponent* CreateOrUpdateMeshComponent( + const UHoudiniOutput* InOutput, + UObject* InOuterComponent, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TSubclassOf& InComponentType, + FHoudiniOutputObject& OutOutputObject, + FHoudiniGeoPartObject const *& OutFoundHGPO, + bool &bCreated); + + // Helper to initialize a UStaticMeshComponent after it was created. + static bool PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh); + + // Helper to initialize a UHoudiniStaticMeshComponent after it was created. + static bool PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh); + + static bool AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, + TArray& HoudiniCreatedSocketActors, TArray& HoudiniAttachedSocketActors); + + protected: + + // Data cache for this translator + + // The HoudiniGeoPartObject we're working on + FHoudiniGeoPartObject HGPO; + + // Outer object for attaching components to + UObject* OuterComponent; + + // Structure that handles cooking/baking package creation parameters + FHoudiniPackageParams PackageParams; + + + // Previous output objects + TMap InputObjects; + + // New Output objects + TMap OutputObjects; + + + // Input Material Map + TMap InputAssignmentMaterials; + // Output Material Map + TMap OutputAssignmentMaterials; + // Input Replacement Materials maps + TMap ReplacementMaterials; + + // Input mesh properties + //TMap InputObjectProperties; + // Output mesh properties + //TMap OutputObjectProperties; + + // Indicates the update is forced + bool ForceRebuild; + + // The generated simple/UCX colliders + TMap AllAggregateCollisions; + + // Names of the groups used for splitting the geometry + TArray AllSplitGroups; + + // Per-split lists of faces + TMap> AllSplitVertexLists; + + // Per-split number of faces + TMap AllSplitVertexCounts; + + // Per-split indices arrays + TMap> AllSplitFaceIndices; + + // Per-split first valid vertex index + TMap AllSplitFirstValidVertexIndex; + + // Per-split first valid prim index + TMap AllSplitFirstValidPrimIndex; + + // Vertex Indices for the part + TArray PartVertexList; + + // Positions + TArray PartPositions; + HAPI_AttributeInfo AttribInfoPositions; + + // Vertex Normals + TArray PartNormals; + HAPI_AttributeInfo AttribInfoNormals; + + // Vertex TangentU + TArray PartTangentU; + HAPI_AttributeInfo AttribInfoTangentU; + + // Vertex TangentV + TArray PartTangentV; + HAPI_AttributeInfo AttribInfoTangentV; + + // Vertex Colors + TArray PartColors; + HAPI_AttributeInfo AttribInfoColors; + + // Vertex Alpha values + TArray PartAlphas; + HAPI_AttributeInfo AttribInfoAlpha; + + // Face Smoothing masks + TArray PartFaceSmoothingMasks; + HAPI_AttributeInfo AttribInfoFaceSmoothingMasks; + + // UVs + TArray> PartUVSets; + TArray AttribInfoUVSets; + + // Lightmap resolution + TArray PartLightMapResolutions; + HAPI_AttributeInfo AttribInfoLightmapResolution; + + // Material IDs per face + TArray PartFaceMaterialIds; + HAPI_AttributeInfo AttribInfoFaceMaterialIds; + // Unique material IDs + TArray PartUniqueMaterialIds; + //TSet PartUniqueMaterialIds; + // Material infos for each unique Material + TArray PartUniqueMaterialInfos; + //TSet PartUniqueMaterialInfos; + // Indicates we only have a single face material + bool bOnlyOneFaceMaterial; + + // Material Overrides per face + TArray PartFaceMaterialOverrides; + HAPI_AttributeInfo AttribInfoFaceMaterialOverrides; + // Indicates that material overides attributes need an instance to be created + bool bMaterialOverrideNeedsCreateInstance; + + // LOD Screensize + TArray PartLODScreensize; + HAPI_AttributeInfo AttribInfoLODScreensize; + + int32 DefaultMeshSmoothing; + + // When building a mesh, if an associated material already exists, treat + // it as up to date, regardless of the MaterialInfo.bHasChanged flag + bool bTreatExistingMaterialsAsUpToDate; + + // Default properties to be used when generating Static Meshes + FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp index 825ad66f4..9160fa8e9 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp @@ -1,2045 +1,2049 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniOutputTranslator.h" - -#include "HoudiniOutput.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" - -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniInput.h" -#include "HoudiniStaticMesh.h" - -#include "HoudiniMeshTranslator.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" - -#include "Editor.h" -#include "EditorSupportDelegates.h" -#include "FileHelpers.h" -#include "LandscapeInfo.h" -#include "HAL/PlatformFilemanager.h" -#include "HAL/FileManager.h" -#include "Engine/WorldComposition.h" -#include "Modules/ModuleManager.h" -#include "WorldBrowserModule.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// -bool -FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& bInForceUpdate, bool& bOutHasHoudiniStaticMeshOutput) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the bake folder override - FHoudiniOutputTranslator::GetBakeFolderFromAttribute(HAC); - - // Get the temp folder override - FHoudiniOutputTranslator::GetTempFolderFromAttribute(HAC); - - // Check if the HDA has been marked as not producing outputs - if (!HAC->bOutputless) - { - // Check if we want to convert legacy v1 data - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility && HAC->Version1CompatibilityHAC) - { - // Do not reuse legacy outputs! - for (auto& OldOutput : HAC->Outputs) - { - ClearOutput(OldOutput); - } - } - - TArray NewOutputs; - if (FHoudiniOutputTranslator::BuildAllOutputs(HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos)) - { - ClearAndRemoveOutputs(HAC); - // Replace with the new parameters - HAC->Outputs = NewOutputs; - } - } - else - { - // This HDA is marked as not supposed to produce any output - ClearAndRemoveOutputs(HAC); - } - - // NOTE: PersistentWorld can be NULL when, for example, working with - // HoudiniAssetComponents in Blueprints. - UWorld* PersistentWorld = HAC->GetWorld(); - UWorldComposition* WorldComposition = nullptr; - if (PersistentWorld) - { - WorldComposition = PersistentWorld->WorldComposition; - } - - if (IsValid(WorldComposition)) - { - // We don't want the origin to shift as we're potentially updating levels. - WorldComposition->bTemporarilyDisableOriginTracking = true; - } - - // "Process" the mesh. - // TODO: Move this to the actual processing stage, - // And see if some of this could be threaded - UObject* OuterComponent = HAC; - - FString HoudiniAssetPath = FPaths::GetPath(HAC->GetPathName()); - FString ComponentGUIDString = HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); - FString HoudiniAssetNameString = HAC->GetDisplayName(); - - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - PackageParams.ObjectName = FString(); - - TArray CreatedWorldCompositionPackages; - bool bCreatedNewMaps = false; - //... for heightfield outputs ...// - - // Collect all the landscape layers' global min/max values. - TMap LandscapeLayerGlobalMinimums; - TMap LandscapeLayerGlobalMaximums; - - // Store the instancer outputs separately so we can process them later, after all mesh output are processed. - // Determine the total number of instances, if we have more than 1 then mesh parts with instanced geo we will not create proxy meshes - // Also if we have object instancer (or oldschool attribute instancers), we won't be creating any proxy at all - TArray InstancerOutputs; - int32 NumInstances = 0; - bool bHasObjectInstancer = false; - - for (auto& CurOutput : HAC->Outputs) - { - if (CurOutput->GetType() == EHoudiniOutputType::Instancer) - { - // InstancerOutputs.Add(CurOutput); - for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type == EHoudiniPartType::Instancer) - { - if (HGPO.InstancerType == EHoudiniInstancerType::PackedPrimitive) - { - NumInstances += HGPO.PartInfo.InstanceCount; - } - else - { - NumInstances += HGPO.PartInfo.PointCount; - } - - if ((HGPO.InstancerType == EHoudiniInstancerType::ObjectInstancer) - || (HGPO.InstancerType == EHoudiniInstancerType::OldSchoolAttributeInstancer)) - { - bHasObjectInstancer = true; - } - } - } - } - else if (CurOutput->GetType() == EHoudiniOutputType::Landscape) - { - FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, false); - } - } - - bOutHasHoudiniStaticMeshOutput = false; - int32 NumVisibleOutputs = 0; - int32 NumOutputs = HAC->Outputs.Num(); - bool bHasLandscape = false; - - // Before processing all the outputs, - // See if we have any landscape input that have "Update Input Landscape" enabled - // And make an array of all our input landscapes - TArray AllInputLandscapes; - TArray InputLandscapesToUpdate; - - for (auto CurrentInput : HAC->Inputs) - { - if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) - continue; - - // Get the landscape input's landscape - ALandscapeProxy* InputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); - if (!InputLandscape) - continue; - - AllInputLandscapes.Add(InputLandscape); - - if (CurrentInput->GetUpdateInputLandscape()) - InputLandscapesToUpdate.Add(InputLandscape); - } - - // ---------------------------------------------------- - // Process outputs - // ---------------------------------------------------- - TArray CreatedPackages; - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* CurOutput = HAC->GetOutputAt(OutputIdx); - if (!CurOutput || CurOutput->IsPendingKill()) - continue; - - FString Notification = FString::Format(TEXT("Processing output {0} / {1}..."), {FString::FromInt(OutputIdx + 1), FString::FromInt(NumOutputs)}); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - if (!HAC->IsOutputTypeSupported(CurOutput->GetType())) - continue; - - switch (CurOutput->GetType()) - { - case EHoudiniOutputType::Mesh: - { - bool bIsProxyStaticMeshEnabled = ( - HAC->IsProxyStaticMeshEnabled() && - !HAC->HasNoProxyMeshNextCookBeenRequested() && - !HAC->IsBakeAfterNextCookEnabled()); - if (bIsProxyStaticMeshEnabled && NumInstances > 1) - { - if (bHasObjectInstancer) - { - // Completely disable proxies if we have object instancers/old school attribute instancers - // as they rely on having a static mesh created (and the instanced mesh HGPO is not marked as instanced...) - bIsProxyStaticMeshEnabled = false; - } - else - { - // If we dont have proxy instancer, enable proxy only for non-instanced mesh - for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.bIsInstanced && HGPO.Type == EHoudiniPartType::Mesh) - { - bIsProxyStaticMeshEnabled = false; - break; - } - } - } - } - - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - PackageParams, - bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, - OuterComponent); - - NumVisibleOutputs++; - - // Look for UHoudiniStaticMesh in the output, and set bOutHasHoudiniStaticMeshOutput accordingly - if (bIsProxyStaticMeshEnabled && !bOutHasHoudiniStaticMeshOutput) - { - bOutHasHoudiniStaticMeshOutput &= CurOutput->HasAnyCurrentProxy(); - } - - break; - } - - case EHoudiniOutputType::Curve: - { - const TArray &GeoPartObjects = CurOutput->GetHoudiniGeoPartObjects(); - - if (GeoPartObjects.Num() <= 0) - continue; - - const FHoudiniGeoPartObject & CurHGPO = GeoPartObjects[0]; - - if (CurOutput->IsEditableNode()) - { - if (!CurOutput->HasEditableNodeBuilt()) - { - // Editable curve, only need to be built once. - UHoudiniSplineComponent* HoudiniSplineComponent = FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode( - CurHGPO.GeoId, - CurHGPO.PartName, - HAC); - - HoudiniSplineComponent->SetIsEditableOutputCurve(true); - - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; - EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; - EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; - EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; - - TMap& OutputObjects = CurOutput->GetOutputObjects(); - FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - FoundOutputObject.OutputComponent = HoudiniSplineComponent; - - CurOutput->SetHasEditableNodeBuilt(true); - } - } - else - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); - NumVisibleOutputs += CurOutput->GetOutputObjects().Num(); - break; - } - } - break; - - case EHoudiniOutputType::Instancer: - InstancerOutputs.Add(CurOutput); - break; - - case EHoudiniOutputType::Landscape: - { - NumVisibleOutputs++; - - // This gets called for each heightfield primitive from Houdini, i.e., each "tile". - bool bNewMapCreated = false; - // Registering of untracked actors is not currently used in the HDA - // workflow. HDA cleanup will manually search for shared landscapes - // and remove them. That aforementioned behaviour should really be updated to - // make use of untracked actors on the HAC (similar to PDG Asset Link). - TArray> UntrackedActors; - - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - UntrackedActors, - InputLandscapesToUpdate, - AllInputLandscapes, - HAC, - TEXT("{hda_actor_name}_"), - PersistentWorld, - LandscapeLayerGlobalMinimums, - LandscapeLayerGlobalMaximums, - PackageParams, - CreatedPackages); - - bHasLandscape = true; - - // Attach the created landscape to the parent HAC. - ALandscapeProxy* OutputLandscape = nullptr; - for (auto& Pair : CurOutput->GetOutputObjects()) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(Pair.Value.OutputObject); - OutputLandscape = LandscapePtr->GetRawPtr(); - break; - } - - if (OutputLandscape) - { - // Attach the created landscapes to HAC - // Output Transforms are always relative to the HDA - HAC->SetMobility(EComponentMobility::Static); - OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); - // Note that the above attach will cause the collision components to crap out. This manifests - // itself via the Landscape editor tools not being able to trace Landscape collision components. - // By recreating collision components here, it appears to put things back into working order. - OutputLandscape->RecreateCollisionComponents(); - } - - bCreatedNewMaps |= bNewMapCreated; - - break; - } - default: - // Do Nothing for now - break; - } - } - - // Now that all meshes have been created, process the instancers - for (auto& CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); - NumVisibleOutputs++; - } - - if (NumVisibleOutputs > 0) - { - // If we have valid outputs, we don't need to display the houdini logo anymore... - FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(HAC); - } - else - { - // ... if we don't have any valid outputs however, we should - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HAC); - } - - if (bHasLandscape) - { - // ---------------------------------------------------- - // Cleanup untracked shared landscape actors - // ---------------------------------------------------- - // This is a nasty hack to clean up SharedLandscape actors generated by the - // Landscape translator but aren't tracked by an HoudiniOutputObject, since the - // translators can't dynamically create outputs. - - { - // First collect all the landscapes that is being tracked by the HAC. - TSet TrackedLandscapes; - for(UHoudiniOutput* Output : HAC->Outputs) - { - if (Output->GetType() == EHoudiniOutputType::Landscape) - { - for(auto& Elem : Output->GetOutputObjects()) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(Elem.Value.OutputObject); - if (!IsValid(LandscapePtr)) - continue; - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - if (IsValid(LandscapeProxy)) - { - TrackedLandscapes.Add(LandscapeProxy); - - // We need to recreate component states for landscapes if a tile was created, moved, or resized - // otherwise the landscape will exhibit render artifacts (such as only rendering every other - // component.) - LandscapeProxy->RecreateComponentsState(); - } - } - } - } - - // Iterate over Houdini asset child assets in order to find dangling Landscape actors - TArray AttachedComponents = HAC->GetAttachChildren(); - for(USceneComponent* Component : AttachedComponents) - { - if (!IsValid(Component)) - continue; - AActor* Actor = Component->GetOwner(); - ALandscape* Landscape = Cast(Actor); - if (!Landscape) - continue; - if (TrackedLandscapes.Contains(Landscape)) - continue; - - ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); - if (!Info || Info->Proxies.Num() == 0) - { - Landscape->Destroy(); - } - } - } - - // Recreate Landscape Info calls WorldChange, so no need to do it manually. - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - } - - if (IsValid(WorldComposition)) - { - // Disable the flag that we set before starting the import process. - WorldComposition->bTemporarilyDisableOriginTracking = false; - } - - // If the owner component was marked as loaded, unmark all outputs - if (HAC->HasBeenLoaded()) - { - for (auto& CurrentOutput : HAC->Outputs) - { - CurrentOutput->MarkAsLoaded(false); - } - } - - if (bCreatedNewMaps) - { - // Force the asset registry to update its cache of packages paths - // recursively for this world, otherwise world composition won't - // pick them up during the WorldComposition::Rescan(). - FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); - - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - - FHoudiniEngineUtils::LogWorldInfo(PersistentWorld); - if (WorldComposition) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); - } - - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); - } - - if (CreatedPackages.Num() > 0) - { - // Save created packages. For example, we don't want landscape layers deleted - // along with the HDA. - FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); - } - - return true; -} - -bool -FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - UObject* OuterComponent = HAC; - - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - PackageParams.ObjectName = FString(); - - bool bFoundProxies = false; - TArray InstancerOutputs; - for (auto& CurOutput : HAC->Outputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType == EHoudiniOutputType::Mesh) - { - if (CurOutput->HasAnyCurrentProxy()) - { - bFoundProxies = true; - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - PackageParams, - HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, - OuterComponent, - true, // bInTreatExistingMaterialsAsUpToDate - bInDestroyProxies - ); - } - } - else if (OutputType == EHoudiniOutputType::Instancer) - { - InstancerOutputs.Add(CurOutput); - } - } - - // Rebuild instancers if we built any static meshes from proxies - if (bFoundProxies) - { - for (auto& CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); - } - } - - return true; -} - -// -bool -FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) -{ - HAPI_NodeId & AssetId = HAC->AssetId; - // Get the AssetInfo - 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; - - TArray EditableCurveObjIds; - TArray EditableCurveGeoIds; - TArray EditableCurvePartIds; - TArray EditableCurvePartNames; - - // Iterate through all objects to get all editable curve's object geo and part Ids. - - for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) - { - // Retrieve the object info - const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; - - // Cache/convert them - FHoudiniObjectInfo CurrentObjectInfo; - CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); - - // Start by getting the number of editable nodes - int32 EditableNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, - true, &EditableNodeCount)); - - if (EditableNodeCount > 0) - { - TArray< HAPI_NodeId > EditableNodeIds; - EditableNodeIds.SetNumUninitialized(EditableNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - - for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) - { - HAPI_GeoInfo CurrentEditableGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentEditableGeoInfo.isDisplayGeo) - continue; - - // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) - continue; - - // Only catch editable curves - if (CurrentEditableGeoInfo.type != HAPI_GeoType::HAPI_GEOTYPE_CURVE) - continue; - - // Cook the editable node to get its parts - if (CurrentEditableGeoInfo.partCount <= 0) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentEditableGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentEditableGeoInfo.nodeId, - &CurrentEditableGeoInfo)); - } - - // Iterate on this geo's parts - for (int32 PartId = 0; PartId < CurrentEditableGeoInfo.partCount; ++PartId) - { - // Get part information. - HAPI_PartInfo CurrentHapiPartInfo; - FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - continue; - - if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE) - continue; - - // Get the editable curve's part name - FHoudiniEngineString hapiSTR(CurrentHapiPartInfo.nameSH); - FString PartName; - hapiSTR.ToFString(PartName); - - EditableCurveObjIds.Add(CurrentHapiObjectInfo.nodeId); - EditableCurveGeoIds.Add(CurrentEditableGeoInfo.nodeId); - EditableCurvePartIds.Add(CurrentHapiPartInfo.id); - EditableCurvePartNames.Add(PartName); - } - } - } - - } - - int32 Idx = 0; - for (auto& CurrentOutput : HAC->Outputs) - { - if (CurrentOutput->IsEditableNode()) - { - // The HAC is Loaded, re-assign node id to its editable curves - if (CurrentOutput->HasEditableNodeBuilt()) - { - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - for (auto& Pair : OutputObjects) - { - if (Idx >= EditableCurvePartIds.Num()) - break; - - UHoudiniSplineComponent * HoudiniSplineComponent = Cast(Pair.Value.OutputComponent); - if (HoudiniSplineComponent && !HoudiniSplineComponent->IsPendingKill()) - { - HoudiniSplineComponent->SetNodeId(EditableCurveGeoIds[Idx]); - - Pair.Key.ObjectId = EditableCurveObjIds[Idx]; - Pair.Key.GeoId = EditableCurveGeoIds[Idx]; - Pair.Key.PartId = EditableCurvePartIds[Idx]; - Pair.Key.PartName = EditableCurvePartNames[Idx]; - - Idx += 1; - } - } - } - // The HAC is a Duplication, re-construct output objects with attached duplicated editable curves, matching by part name - else - { - const TArray &Children = HAC->GetAttachChildren(); - for (auto & CurAttachedComp : Children) - { - if (!CurAttachedComp || CurAttachedComp->IsPendingKill()) - continue; - - if (!CurAttachedComp->IsA()) - continue; - - UHoudiniSplineComponent * CurAttachedSplineComp = Cast(CurAttachedComp); - if (!CurAttachedSplineComp) - continue; - - if (!CurAttachedSplineComp->IsEditableOutputCurve()) - continue; - - if (Idx >= EditableCurvePartIds.Num()) - break; - - // Found a match - if (CurAttachedSplineComp->GetGeoPartName().Equals(EditableCurvePartNames[Idx])) - { - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = EditableCurveObjIds[Idx]; - EditableSplineComponentIdentifier.GeoId = EditableCurveGeoIds[Idx]; - EditableSplineComponentIdentifier.PartId = EditableCurvePartIds[Idx]; - EditableSplineComponentIdentifier.PartName = EditableCurvePartNames[Idx]; - - CurAttachedSplineComp->SetNodeId(EditableSplineComponentIdentifier.GeoId); - - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - FHoudiniOutputObject& NewOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - NewOutputObject.OutputComponent = CurAttachedSplineComp; - - CurrentOutput->SetHasEditableNodeBuilt(true); - FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(CurAttachedSplineComp); - - Idx += 1; - break; - } - } - } - } - else - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); - } - - // Mark our outputs as loaded so they can be matched for potential reuse - // This indicates that the HGPO's ids are invalid and that HGPO should be matched using partnames instead - CurrentOutput->MarkAsLoaded(true); - } - - return true; -} - -// -bool -FHoudiniOutputTranslator::UploadChangedEditableOutput( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray &Outputs = HAC->Outputs; - - // Iterate through the outputs array of HAC. - for (auto& CurrentOutput : HAC->Outputs) - { - if (!CurrentOutput) - continue; - - // Only update the editable nodes that have been built before. - if (!CurrentOutput->IsEditableNode() || !CurrentOutput->HasEditableNodeBuilt()) - continue; - - for (auto& CurrentOutputObj : CurrentOutput->GetOutputObjects()) - { - UHoudiniSplineComponent* HoudiniSplineComponent = Cast(CurrentOutputObj.Value.OutputComponent); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (!HoudiniSplineComponent->HasChanged()) - continue; - - if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(HoudiniSplineComponent)) - HoudiniSplineComponent->MarkChanged(false); - else - HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); - } - } - - return true; -} - - -bool -FHoudiniOutputTranslator::BuildAllOutputs( - const HAPI_NodeId& AssetId, - UObject* InOuterObject, - TArray& InOldOutputs, - TArray& OutNewOutputs, - const bool& InOutputTemplatedGeos) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Get the AssetInfo - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - FString CurrentAssetName; - { - FHoudiniEngineString hapiSTR(AssetInfo.nameSH); - hapiSTR.ToFString(CurrentAssetName); - } - - // Retrieve the asset's transform. - // TODO: Unused?! - //FTransform AssetUnrealTransform; - //if (!FHoudiniEngineUtils::HapiGetAssetTransform(AssetId, AssetUnrealTransform)) - // return false; - - // Retrieve information about each object contained within our asset. - TArray ObjectInfos; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos)) - return false; - - //const int32 ObjectCount = ObjectInfos.Num(); - - // Retrieve transforms for each object in this asset. - TArray ObjectTransforms; - if (!FHoudiniEngineUtils::HapiGetObjectTransforms(AssetId, ObjectTransforms)) - return false; - - // Mark all the previous HGPOs on the outputs as stale - // This indicates that they were from a previous cook and should then be deleted - for (auto& CurOutput : InOldOutputs) - { - if (CurOutput) - CurOutput->MarkAllHGPOsAsStale(true); - } - - // For HF / Volumes, we only create new Outputs for height volume - // Store all the other volumes (masks etc) on the side and we will - // match them with theit corresponding height volume after - TArray UnassignedVolumeParts; - - TArray AllSockets; - - // Iterate through all objects. - int32 OutputIdx = 1; - for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) - { - // Retrieve the object info - const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; - - // Cache/convert them - FHoudiniObjectInfo CurrentObjectInfo; - CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); - - // Retrieve object name. - FString CurrentObjectName = CurrentObjectInfo.Name; - - // Get transformation for this object. - const HAPI_Transform & ObjectTransform = ObjectTransforms[ObjectId]; - FTransform TransformMatrix; - FHoudiniEngineUtils::TranslateHapiTransform(ObjectTransform, TransformMatrix); - - // TODO: Check transforms?? - - // Build an array of the geos we'll need to process - // In most case, it will only be the display geo, - // but we may also want to process editable geos as well - TArray GeoInfos; - - // Get the Display Geo's info - HAPI_GeoInfo DisplayHapiGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName); - } - else - { - // Add the display geo info to the array - GeoInfos.Add(DisplayHapiGeoInfo); - } - - // Handle the editable nodes for this geo - // Start by getting the number of editable nodes - int32 EditableNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, - true, &EditableNodeCount)); - - if (EditableNodeCount > 0) - { - TArray< HAPI_NodeId > EditableNodeIds; - EditableNodeIds.SetNumUninitialized(EditableNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - - for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) - { - HAPI_GeoInfo CurrentEditableGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentEditableGeoInfo.isDisplayGeo) - continue; - - // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) - continue; - - // Add this geo to the geo info array - GeoInfos.Add(CurrentEditableGeoInfo); - } - } - - // Handle the templated nodes if desired - if (InOutputTemplatedGeos) - { - // Start by getting the number of templated nodes - int32 TemplatedNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, - HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, - true, &TemplatedNodeCount)); - - if (TemplatedNodeCount > 0) - { - TArray TemplatedNodeIds; - TemplatedNodeIds.SetNumUninitialized(TemplatedNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, TemplatedNodeIds.GetData(), TemplatedNodeCount)); - - for (int32 nTemplated = 0; nTemplated < TemplatedNodeCount; nTemplated++) - { - HAPI_GeoInfo CurrentTemplatedGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentTemplatedGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - TemplatedNodeIds[nTemplated], &CurrentTemplatedGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentTemplatedGeoInfo.isDisplayGeo) - continue; - - // We don't want all the nested template node IDs, - // as our HDA could potentially be using other HDAs with nested template flags - // Make sure the parent of the templated node is either the HDA, the current OBJ or the Display SOP - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurrentTemplatedGeoInfo.nodeId); - if (ParentId != CurrentHapiObjectInfo.nodeId - && ParentId != DisplayHapiGeoInfo.nodeId - && ParentId != AssetId) - { - continue; - } - - // Add this geo to the geo info array - GeoInfos.Add(CurrentTemplatedGeoInfo); - } - } - } - - // Iterates through the geos we want to process - for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) - { - // Cook editable/templated nodes to get their parts. - const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; - if ((CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) - || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0)) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, - &GeoInfos[GeoIdx])); - } - - // Cache/convert the display geo's info - FHoudiniGeoInfo CurrentGeoInfo; - CacheGeoInfo(CurrentHapiGeoInfo, CurrentGeoInfo); - - // Simply create an empty array for this geo's group names - // We might need it later for splitting - TArray GeoGroupNames; - bool HasSocketGroups = false; - - // Iterate on this geo's parts - for (int32 PartId = 0; PartId < CurrentGeoInfo.PartCount; ++PartId) - { - // Get part information. - HAPI_PartInfo CurrentHapiPartInfo; - FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); - - // If the geo is templated, cook it manually - if (CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - } - - bool bPartInfoFailed = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - { - bPartInfoFailed = true; - - // If the geo is templated, attempt to cook it manually - if(CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, nullptr); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, - &GeoInfos[GeoIdx])); - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - { - // We managed to get the templated part infos after cooking - bPartInfoFailed = false; - } - } - } - - if (bPartInfoFailed) - { - // Error retrieving part info. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d] unable to retrieve PartInfo - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId); - continue; - } - - // Convert/cache the part info - FHoudiniPartInfo CurrentPartInfo; - CachePartInfo(CurrentHapiPartInfo, CurrentPartInfo); - - // Retrieve part name. - FString CurrentPartName = CurrentPartInfo.Name; - - // Unsupported/Invalid part - if (CurrentPartInfo.Type == EHoudiniPartType::Invalid) - continue; - - // Update part/instancer type from the part infos - EHoudiniPartType CurrentPartType = EHoudiniPartType::Invalid; - EHoudiniInstancerType CurrentInstancerType = EHoudiniInstancerType::Invalid; - switch (CurrentHapiPartInfo.type) - { - case HAPI_PARTTYPE_BOX: - case HAPI_PARTTYPE_SPHERE: - case HAPI_PARTTYPE_MESH: - { - if (CurrentHapiGeoInfo.type == HAPI_GEOTYPE_CURVE) - { - // Closed curve will be seen as mesh - CurrentPartType = EHoudiniPartType::Curve; - } - else - { - CurrentPartType = EHoudiniPartType::Mesh; - - if (CurrentHapiObjectInfo.isInstancer) - { - if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // That part is actually an attribute instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - } - else - { - // That part is actually an instancer - CurrentPartType = EHoudiniPartType::Instancer; - CurrentInstancerType = EHoudiniInstancerType::ObjectInstancer; - } - - } - else if (CurrentHapiPartInfo.vertexCount <= 0 && CurrentHapiPartInfo.pointCount <= 0) - { - // No points, no vertices, we're likely invalid - CurrentPartType = EHoudiniPartType::Invalid; - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a mesh with no points or vertices - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - } - else if (CurrentHapiPartInfo.vertexCount <= 0) - { - // This is not an instancer, we do not have vertices, but we have points - // Maybe this is a point cloud with attribute override instancing - if(FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // Mark it as an instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - } - else - { - // No vertices, not an instancer, just a point cloud, consider ourself as invalid - CurrentPartType = EHoudiniPartType::Invalid; - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a point cloud mesh - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - } - } - } - } - break; - - case HAPI_PARTTYPE_CURVE: - { - // Make sure that this curve is not an an attribute instancer! - if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // Mark the part as an instancer it as an instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - } - else - { - // The curve is a curve! - CurrentPartType = EHoudiniPartType::Curve; - } - } - break; - - case HAPI_PARTTYPE_INSTANCER: - // This is a packed primitive instancer - CurrentPartType = EHoudiniPartType::Instancer; - CurrentInstancerType = EHoudiniInstancerType::PackedPrimitive; - break; - - case HAPI_PARTTYPE_VOLUME: - // Volume data, likely a Heightfield height / mask - CurrentPartType = EHoudiniPartType::Volume; - break; - - default: - // Unsupported Part Type - break; - } - - // There are no vertices AND no points and this part is not a packed prim instancer - if ((CurrentPartInfo.VertexCount <= 0 && CurrentPartInfo.PointCount <= 0) - && (CurrentPartType != EHoudiniPartType::Instancer || CurrentInstancerType != EHoudiniInstancerType::PackedPrimitive)) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] no points or vertices found - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - continue; - } - - // This is an instancer with no points. - if (CurrentHapiObjectInfo.isInstancer && CurrentHapiPartInfo.pointCount <= 0) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is instancer but has 0 points - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - continue; - } - - // Ignore invalid parts - if (CurrentPartType == EHoudiniPartType::Invalid) - continue; - - // Build the HGPO corresponding to this part - FHoudiniGeoPartObject currentHGPO; - currentHGPO.AssetId = AssetId; - currentHGPO.AssetName = CurrentAssetName; - - currentHGPO.ObjectId = CurrentHapiObjectInfo.nodeId; - currentHGPO.ObjectName = CurrentObjectName; - - currentHGPO.GeoId = CurrentHapiGeoInfo.nodeId; - - currentHGPO.PartId = CurrentHapiPartInfo.id; - - currentHGPO.Type = CurrentPartType; - currentHGPO.InstancerType = CurrentInstancerType; - - currentHGPO.TransformMatrix = TransformMatrix; - - currentHGPO.NodePath = TEXT(""); - - currentHGPO.bIsVisible = CurrentHapiObjectInfo.isVisible && !CurrentHapiPartInfo.isInstanced; - currentHGPO.bIsEditable = CurrentHapiGeoInfo.isEditable; - currentHGPO.bIsInstanced = CurrentHapiPartInfo.isInstanced; - // Never consider a display geo as templated! - currentHGPO.bIsTemplated = CurrentHapiGeoInfo.isDisplayGeo ? false : CurrentHapiGeoInfo.isTemplated; - - currentHGPO.bHasGeoChanged = CurrentHapiGeoInfo.hasGeoChanged; - currentHGPO.bHasPartChanged = CurrentHapiPartInfo.hasChanged; - currentHGPO.bHasMaterialsChanged = CurrentHapiGeoInfo.hasMaterialChanged; - currentHGPO.bHasTransformChanged = CurrentHapiObjectInfo.hasTransformChanged; - - // Copy the HAPI info caches - currentHGPO.ObjectInfo = CurrentObjectInfo; - currentHGPO.GeoInfo = CurrentGeoInfo; - currentHGPO.PartInfo = CurrentPartInfo; - - // We only support meshes for templated geos - if (currentHGPO.bIsTemplated && (CurrentPartType != EHoudiniPartType::Mesh)) - continue; - - // Update the HGPO's node path - FHoudiniEngineUtils::HapiGetNodePath(currentHGPO, currentHGPO.NodePath); - - // Try to get the custom part name from attribute - FString CustomPartName; - if (FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CustomPartName)) - currentHGPO.SetCustomPartName(CustomPartName); - else - currentHGPO.PartName = CurrentPartName; - - // - // Mesh Only - Extract split groups - // - // Extract the group names used by this part to see if it will require splitting - // Only meshes can be split, via their primitive groups - TArray< FString > SplitGroupNames; - if (CurrentPartType == EHoudiniPartType::Mesh) - { - if (!CurrentHapiPartInfo.isInstanced && GeoGroupNames.Num() > 0) - { - // We are not instanced and already have extracted the geo's group names - // We can simply reuse the Geo group names / socket groups - currentHGPO.SplitGroups = GeoGroupNames; - } - else - { - // We need to get the primitive group names from HAPI - int32 GroupCount = 0; - TArray GroupNames; - if (!FHoudiniEngineUtils::HapiGetGroupNames( - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - HAPI_GROUPTYPE_PRIM, CurrentHapiPartInfo.isInstanced, - GroupNames)) - { - GroupCount = 0; - GroupNames.Empty(); - } - - // Convert the string handles to FStrings - for (const FString& GroupName : GroupNames) - { - FString LodGroup = HAPI_UNREAL_GROUP_LOD_PREFIX; - FString CollisionGroup = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; - FString RenderedCollisionGroup = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; - if (GroupName.StartsWith(LodGroup, ESearchCase::IgnoreCase) - || GroupName.StartsWith(CollisionGroup, ESearchCase::IgnoreCase) - || GroupName.StartsWith(RenderedCollisionGroup, ESearchCase::IgnoreCase)) - //|| GroupName.StartsWith(HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX, ESearchCase::IgnoreCase)) - { - // Split by collisions / lods - currentHGPO.SplitGroups.Add(GroupName); - } - } - - // Sort the Group name array by name, - // this will order the LODs and other incremental group names - currentHGPO.SplitGroups.Sort(); - - // If this part is not instanced, we can copy the geo - // group names so we can reuse them for another part - if (!CurrentHapiPartInfo.isInstanced) - { - GeoGroupNames = currentHGPO.SplitGroups; - } - } - } - - // - // Volume Only - Extract volume name/tile index - // - // Extract the volume's name, and see if a tile attribute is present - FHoudiniVolumeInfo CurrentVolumeInfo; - if (CurrentPartType == EHoudiniPartType::Volume) - { - // Get this volume's info - HAPI_VolumeInfo CurrentHapiVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentHapiVolumeInfo); - - bool bVolumeValid = true; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - &CurrentHapiVolumeInfo)) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.tupleSize != 1) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.zLength != 1) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - { - bVolumeValid = false; - } - - // Only cache valid volumes - if (bVolumeValid) - { - // Convert/Cache the volume info - CacheVolumeInfo(CurrentHapiVolumeInfo, CurrentVolumeInfo); - - // Get the volume's name - currentHGPO.VolumeName = CurrentVolumeInfo.Name; - - // Now see if this volume has a tile attribute - TArray TileValues; - if (FHoudiniEngineUtils::GetTileAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, TileValues, HAPI_ATTROWNER_PRIM)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - currentHGPO.VolumeTileIndex = TileValues[0]; - else - currentHGPO.VolumeTileIndex = -1; - } - } - } - currentHGPO.VolumeInfo = CurrentVolumeInfo; - - // Cache the curve info as well - FHoudiniCurveInfo CurrentCurveInfo; - if (CurrentPartType == EHoudiniPartType::Curve) - { - HAPI_CurveInfo CurrentHapiCurveInfo; - FHoudiniApi::CurveInfo_Init(&CurrentHapiCurveInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - &CurrentHapiCurveInfo)) - { - // Cache/Convert this part's curve info - CacheCurveInfo(CurrentHapiCurveInfo, CurrentCurveInfo); - } - } - currentHGPO.CurveInfo = CurrentCurveInfo; - - - // TODO: - // DONE? bake folders are handled out of this loop? - // See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute - //TArray BakeFolderOverrides; - - // Extract socket points - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); - - // See if we have an existing output that matches this HGPO or if we need to create a new one - bool IsFoundOutputValid = false; - UHoudiniOutput ** FoundHoudiniOutput = nullptr; - // We handle volumes differently than other outputs types, as a single HF output has multiple HGPOs - if (currentHGPO.Type != EHoudiniPartType::Volume) - { - // Look in the previous output if we have a match - FoundHoudiniOutput = InOldOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(currentHGPO) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - - } - else - { - // Look in the previous outputs if we have a match - FoundHoudiniOutput = InOldOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, true) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - - // If we dont have a match in the old maps, also look in the newly created outputs - if (!IsFoundOutputValid) - { - FoundHoudiniOutput = OutNewOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, false) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - } - } - - UHoudiniOutput * HoudiniOutput = nullptr; - if (IsFoundOutputValid) - { - // We can reuse the existing output - HoudiniOutput = *FoundHoudiniOutput; - HoudiniOutput->SetIsUpdating(true); - // Transfer this output from the old array to the new one - InOldOutputs.Remove(HoudiniOutput); - } - else - { - // We couldn't find a valid output object, so create a new one - - // If the current part is a volume, only create a new output object - // if the volume's name is "height", if not store the HGPO aside - if (currentHGPO.Type == EHoudiniPartType::Volume - && !currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) - { - UnassignedVolumeParts.Add(currentHGPO); - continue; - } - - // Create a new output object - //FString OutputName = TEXT("Output") + FString::FromInt(OutputIdx++); - HoudiniOutput = NewObject( - InOuterObject, - UHoudiniOutput::StaticClass(), - NAME_None,//FName(*OutputName), - RF_NoFlags); - - // Make sure the created object is valid - if (!HoudiniOutput || HoudiniOutput->IsPendingKill()) - { - //HOUDINI_LOG_WARNING("Failed to create asset output"); - continue; - } - - // Mark if the HoudiniOutput is editable - HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); - } - - // Add the HGPO to the output - HoudiniOutput->AddNewHGPO(currentHGPO); - // Add this output object to the new ouput array - OutNewOutputs.AddUnique(HoudiniOutput); - } - } - } - - // Update the output/HGPO associations from the map - // Clear the old HGPO since we don't need them anymore - for (auto& CurrentOuput : OutNewOutputs) - { - if (!CurrentOuput || CurrentOuput->IsPendingKill()) - continue; - - CurrentOuput->DeleteAllStaleHGPOs(); - } - - // If we have unassigned volumes, - // try to find their corresponding output - if (UnassignedVolumeParts.Num() > 0) - { - for (auto& currentVolumeHGPO : UnassignedVolumeParts) - { - UHoudiniOutput ** FoundHoudiniOutput = OutNewOutputs.FindByPredicate( - [currentVolumeHGPO](UHoudiniOutput* Output) - { - return Output ? Output->HeightfieldMatch(currentVolumeHGPO, false) : false; - }); - - if (!FoundHoudiniOutput || !(*FoundHoudiniOutput) || (*FoundHoudiniOutput)->IsPendingKill()) - { - // Skip - consider this volume as invalid - continue; - } - - // Add this HGPO to the output - (*FoundHoudiniOutput)->AddNewHGPO(currentVolumeHGPO); - } - } - - // All our output objects now have their HGPO assigned - // We can now parse them to update the output type - for (auto& Output : OutNewOutputs) - { - Output->UpdateOutputType(); - } - - return true; -} - -bool -FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray& Outputs = HAC->Outputs; - - // Iterate through the outputs array of HAC. - for (int32 Index = 0; Index < HAC->GetNumOutputs(); ++Index) - { - UHoudiniOutput* CurrentOutput = HAC->GetOutputAt(Index); - if (!CurrentOutput) - continue; - - if (!HAC->IsOutputTypeSupported(CurrentOutput->GetType())) - continue; - - switch (CurrentOutput->GetType()) - { - case EHoudiniOutputType::Instancer: - { - bool bNeedToRecreateInstancers = false; - for (auto& Iter : CurrentOutput->GetInstancedOutputs()) - { - FHoudiniInstancedOutput& InstOutput = Iter.Value; - if (!InstOutput.bChanged) - continue; - - /* - FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( - InstOutput, Iter.Key, CurrentOutput, HAC); - */ - - // TODO: - // UpdateChangedInstancedOutput needs some improvements - // as it currently destroy too many components. - // For now, we'll update all the instancers - bNeedToRecreateInstancers = true; - - InstOutput.MarkChanged(false); - } - - if (bNeedToRecreateInstancers) - { - if (HAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation || HAC->HasBeenLoaded()) - { - // Instantiate the HDA if it's not been - // This is because CreateAllInstancersFromHoudiniOutput() actually reads the transform from HAPI - // Calling it on a HDA not yet instantiated causes a crash... - HAC->AssetState = EHoudiniAssetState::PreInstantiation; - } - else - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurrentOutput, Outputs, HAC); - } - } - } - break; - - case EHoudiniOutputType::Curve: - { - //FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); - } - break; - - default: - break; - } - } - - return true; -} - -void -FHoudiniOutputTranslator::CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache) -{ - FHoudiniEngineString hapiSTR(InObjInfo.nameSH); - hapiSTR.ToFString(OutObjInfoCache.Name); - //OutObjInfoCache.Name = InObjInfo.nameSH; - - OutObjInfoCache.NodeId = InObjInfo.nodeId; - OutObjInfoCache.ObjectToInstanceID = InObjInfo.objectToInstanceId; - - OutObjInfoCache.bHasTransformChanged = InObjInfo.hasTransformChanged; - OutObjInfoCache.bHaveGeosChanged = InObjInfo.haveGeosChanged; - - OutObjInfoCache.bIsVisible = InObjInfo.isVisible; - OutObjInfoCache.bIsInstancer = InObjInfo.isInstancer; - OutObjInfoCache.bIsInstanced = InObjInfo.isInstanced; - - OutObjInfoCache.GeoCount = InObjInfo.geoCount; -}; - -EHoudiniGeoType -FHoudiniOutputTranslator::ConvertHapiGeoType(const HAPI_GeoType& InType) -{ - EHoudiniGeoType OutType = EHoudiniGeoType::Invalid; - switch (InType) - { - case HAPI_GEOTYPE_DEFAULT: - OutType = EHoudiniGeoType::Default; - break; - - case HAPI_GEOTYPE_INTERMEDIATE: - OutType = EHoudiniGeoType::Intermediate; - break; - - case HAPI_GEOTYPE_INPUT: - OutType = EHoudiniGeoType::Input; - break; - - case HAPI_GEOTYPE_CURVE: - OutType = EHoudiniGeoType::Curve; - break; - - default: - OutType = EHoudiniGeoType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache) -{ - OutGeoInfoCache.Type = ConvertHapiGeoType(InGeoInfo.type); - - FHoudiniEngineString hapiSTR(InGeoInfo.nameSH); - hapiSTR.ToFString(OutGeoInfoCache.Name); - - OutGeoInfoCache.NodeId = InGeoInfo.nodeId; - - OutGeoInfoCache.bIsEditable = InGeoInfo.isEditable; - OutGeoInfoCache.bIsTemplated = InGeoInfo.isTemplated; - OutGeoInfoCache.bIsDisplayGeo = InGeoInfo.isDisplayGeo; - OutGeoInfoCache.bHasGeoChanged = InGeoInfo.hasGeoChanged; - OutGeoInfoCache.bHasMaterialChanged = InGeoInfo.hasMaterialChanged; - - OutGeoInfoCache.PartCount = InGeoInfo.partCount; - OutGeoInfoCache.PointGroupCount = InGeoInfo.pointGroupCount; - OutGeoInfoCache.PrimitiveGroupCount = InGeoInfo.primitiveGroupCount; -}; - - -EHoudiniPartType -FHoudiniOutputTranslator::ConvertHapiPartType(const HAPI_PartType& InType) -{ - EHoudiniPartType OutType = EHoudiniPartType::Invalid; - switch (InType) - { - case HAPI_PARTTYPE_BOX: - case HAPI_PARTTYPE_SPHERE: - case HAPI_PARTTYPE_MESH: - OutType = EHoudiniPartType::Mesh; - break; - - case HAPI_PARTTYPE_CURVE: - OutType = EHoudiniPartType::Curve; - break; - - case HAPI_PARTTYPE_INSTANCER: - OutType = EHoudiniPartType::Instancer; - break; - - case HAPI_PARTTYPE_VOLUME: - OutType = EHoudiniPartType::Volume; - break; - - default: - OutType = EHoudiniPartType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache) -{ - OutPartInfoCache.PartId = InPartInfo.id; - - FHoudiniEngineString hapiSTR(InPartInfo.nameSH); - hapiSTR.ToFString(OutPartInfoCache.Name); - - OutPartInfoCache.Type = ConvertHapiPartType(InPartInfo.type); - - OutPartInfoCache.FaceCount = InPartInfo.faceCount; - OutPartInfoCache.VertexCount = InPartInfo.vertexCount; - OutPartInfoCache.PointCount = InPartInfo.pointCount; - - OutPartInfoCache.PointAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_POINT]; - OutPartInfoCache.VertexAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX]; - OutPartInfoCache.PrimitiveAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_PRIM]; - OutPartInfoCache.DetailAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL]; - - OutPartInfoCache.bIsInstanced = InPartInfo.isInstanced; - - OutPartInfoCache.InstancedPartCount = InPartInfo.instancedPartCount; - OutPartInfoCache.InstanceCount = InPartInfo.instanceCount; - - OutPartInfoCache.bHasChanged = InPartInfo.hasChanged; -}; - -void -FHoudiniOutputTranslator::CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache) -{ - FHoudiniEngineString hapiSTR(InVolumeInfo.nameSH); - hapiSTR.ToFString(OutVolumeInfoCache.Name); - - OutVolumeInfoCache.bIsVDB = (InVolumeInfo.type == HAPI_VOLUMETYPE_VDB); // replaces VolumeType Type; - - OutVolumeInfoCache.TupleSize = InVolumeInfo.tupleSize; - OutVolumeInfoCache.bIsFloat = (InVolumeInfo.storage == HAPI_STORAGETYPE_FLOAT); // replaces StorageType StorageType; - OutVolumeInfoCache.TileSize = InVolumeInfo.tileSize; - - FHoudiniEngineUtils::TranslateHapiTransform(InVolumeInfo.transform, OutVolumeInfoCache.Transform); - OutVolumeInfoCache.bHasTaper = InVolumeInfo.hasTaper; - - OutVolumeInfoCache.XLength = InVolumeInfo.xLength; - OutVolumeInfoCache.YLength = InVolumeInfo.yLength; - OutVolumeInfoCache.ZLength = InVolumeInfo.zLength; - - OutVolumeInfoCache.MinX = InVolumeInfo.minX; - OutVolumeInfoCache.MinY = InVolumeInfo.minY; - OutVolumeInfoCache.MinZ = InVolumeInfo.minZ; - - OutVolumeInfoCache.XTaper = InVolumeInfo.xTaper; - OutVolumeInfoCache.YTaper = InVolumeInfo.yTaper; -}; - -EHoudiniCurveType -FHoudiniOutputTranslator::ConvertHapiCurveType(const HAPI_CurveType& InType) -{ - EHoudiniCurveType OutType = EHoudiniCurveType::Invalid; - switch (InType) - { - case HAPI_CURVETYPE_LINEAR: - OutType = EHoudiniCurveType::Polygon; - break; - - case HAPI_CURVETYPE_NURBS: - OutType = EHoudiniCurveType::Nurbs; - break; - - case HAPI_CURVETYPE_BEZIER: - OutType = EHoudiniCurveType::Bezier; - break; - - case HAPI_CURVETYPE_MAX: - OutType = EHoudiniCurveType::Points; - break; - - default: - OutType = EHoudiniCurveType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache) -{ - OutCurveInfoCache.Type = ConvertHapiCurveType(InCurveInfo.curveType); - - OutCurveInfoCache.CurveCount = InCurveInfo.curveCount; - OutCurveInfoCache.VertexCount = InCurveInfo.vertexCount; - OutCurveInfoCache.KnotCount = InCurveInfo.knotCount; - - OutCurveInfoCache.bIsPeriodic = InCurveInfo.isPeriodic; - OutCurveInfoCache.bIsRational = InCurveInfo.isRational; - - OutCurveInfoCache.Order = InCurveInfo.order; - - OutCurveInfoCache.bHasKnots = InCurveInfo.hasKnots; -}; - - -void -FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC) -{ - if (!IsValid(InHAC)) - return; - - // DO NOT MANUALLY DESTROY THE OLD/DANGLING OUTPUTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - // Simply clearing the array is enough - for (auto& OldOutput : InHAC->Outputs) - { - ClearOutput(OldOutput); - } - - InHAC->Outputs.Empty(); -} - -void -FHoudiniOutputTranslator::ClearOutput(UHoudiniOutput* Output) -{ - switch (Output->GetType()) - { - case EHoudiniOutputType::Landscape: - { - for (auto& OutputObject : Output->GetOutputObjects()) - { - // Currently, any Landscape managed by an HDA is always present in the current level. - // Only when it gets baked will Landscapes be serialized to other levels so for now - // assume that a landscape should be available, unless explicitly deleted the user. - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.Value.OutputObject); - if (!LandscapePtr) - continue; - - ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); - - if (!IsValid(Landscape) || !Landscape->IsValidLowLevel()) - continue; - - Landscape->UnregisterAllComponents(); - Landscape->Destroy(); - - // if (Output->IsLandscapeWorldComposition()) - // { - // TSoftObjectPtr LandscapeSoftPtr = LandscapePtr->GetSoftPtr(); - // - // FString SoftPtrPath = LandscapeSoftPtr.ToSoftObjectPath().ToString(); - // - // FString FileName = FPaths::GetBaseFilename(SoftPtrPath); - // FString FileDirectory = FPaths::GetPath(SoftPtrPath); - // - // FString ContentPath = FPaths::ProjectContentDir(); - // FString ContentFullPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*ContentPath); - // - // FString AbsoluteFilePath = ContentFullPath + FileDirectory.Mid(5, FileDirectory.Len() - 5) + "/" + FPaths::GetBaseFilename(FileName) + ".umap"; - // - // FPlatformFileManager::Get().GetPlatformFile().FileExists(*(AbsoluteFilePath)); - // - // FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*(AbsoluteFilePath)); - // } - // else - // { - - // } - } - } - break; - - case EHoudiniOutputType::Instancer: - { - for (auto& OutputObject : Output->GetOutputObjects()) - { - // Is this a foliage instancer? Check if the component is owned by an AInstancedFoliageActor - UActorComponent* const Component = Cast(OutputObject.Value.OutputComponent); - if (!IsValid(Component)) - continue; - AActor* const OwnerActor = Component->GetOwner(); - if (!IsValid(OwnerActor) || !OwnerActor->IsA()) - continue; - - UHierarchicalInstancedStaticMeshComponent* const FoliageHISMC = Cast(Component); - if (IsValid(FoliageHISMC)) - { - // Find the parent component: the foliage component outer, otherwise, if a houdini asset actor, the - // houdini asset component, otherwise for a normal actor its root component, finally try and see - // if the outer itself is a component. - USceneComponent* ParentComponent = Cast(FoliageHISMC->GetOuter()); - if (!IsValid(ParentComponent)) - { - UObject* const OutputOuter = Output->GetOuter(); - if (IsValid(OutputOuter)) - { - if (OutputOuter->IsA()) - { - ParentComponent = Cast(OutputOuter)->GetHoudiniAssetComponent(); - } - else if (OutputOuter->IsA()) - { - ParentComponent = Cast(OutputOuter)->GetRootComponent(); - } - else - { - ParentComponent = Cast(OutputOuter); - } - } - } - if (IsValid(ParentComponent)) - { - FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, ParentComponent); - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - } - } - } - } - break; - // ... Other output types ...// - - default: - break; - - } - - Output->Clear(); -} - - -bool -FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName) -{ - HAPI_AttributeInfo CustomPartNameInfo; - FHoudiniApi::AttributeInfo_Init(&CustomPartNameInfo); - - bool bHasCustomName = false; - TArray CustomNames; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, CustomPartNameInfo, CustomNames)) - { - // Look for the v2 attribute (unreal_output_name) - bHasCustomName = true; - } - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, CustomPartNameInfo, CustomNames)) - { - // If we couldnt find the new attribute, use the legacy v1 attribute (unreal_generated_mesh_name) - bHasCustomName = true; - } - - if (!bHasCustomName) - return false; - - if (CustomNames.Num() <= 0) - return false; - - OutCustomPartName = CustomNames[0]; - - if (OutCustomPartName.IsEmpty()) - return false; - - return true; -} - -void -FHoudiniOutputTranslator::GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) - return; - - FString BakeFolderOverride = FString(); - - FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); - - // If the TempCookFolder of the HAC is non-empty and is different from the override path. - // do not override it if the current temp cook path is valid. (it was user specified) - if (!HAC->BakeFolder.Path.IsEmpty() && !HAC->BakeFolder.Path.Equals(BakeFolderOverride)) - return; - - HAC->BakeFolder.Path = BakeFolderOverride; -} - -void -FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) - return; - - FString TempFolderOverride = FString(); - - HAPI_AttributeInfo TempFolderAttriInfo; - FHoudiniApi::AttributeInfo_Init(&TempFolderAttriInfo); - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_DETAIL)) - { - TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - else - { - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) - { - TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - } - - if (TempFolderOverride.StartsWith("Game/")) - { - TempFolderOverride = "/" + TempFolderOverride; - } - - FString AbsoluteOverridePath; - if (TempFolderOverride.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + TempFolderOverride.Mid(6, TempFolderOverride.Len() - 6); - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - if (!TempFolderOverride.IsEmpty()) - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*TempFolderOverride); - } - - // Check Validity of the path - if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) - { - // Only display a warning if the path is invalid, empty is fine - if(!AbsoluteOverridePath.IsEmpty()) - HOUDINI_LOG_WARNING(TEXT("Invalid override temporary cook path: %s"), *TempFolderOverride); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - TempFolderOverride = HoudiniRuntimeSettings->DefaultTemporaryCookFolder; - } - - // If the TempCookFolder of the HAC is non-empty and is different from the override path. - // do not override it if the current temp cook path is valid. (it was user specified) - if (!HAC->TemporaryCookFolder.Path.IsEmpty() && !HAC->TemporaryCookFolder.Path.Equals(TempFolderOverride)) - return; - - HAC->TemporaryCookFolder.Path = TempFolderOverride; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniOutputTranslator.h" + +#include "HoudiniOutput.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" + +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniInput.h" +#include "HoudiniStaticMesh.h" + +#include "HoudiniMeshTranslator.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" + +#include "Editor.h" +#include "EditorSupportDelegates.h" +#include "FileHelpers.h" +#include "LandscapeInfo.h" +#include "HAL/PlatformFilemanager.h" +#include "HAL/FileManager.h" +#include "Engine/WorldComposition.h" +#include "Modules/ModuleManager.h" +#include "WorldBrowserModule.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// +bool +FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& bInForceUpdate, bool& bOutHasHoudiniStaticMeshOutput) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the bake folder override + FHoudiniOutputTranslator::GetBakeFolderFromAttribute(HAC); + + // Get the temp folder override + FHoudiniOutputTranslator::GetTempFolderFromAttribute(HAC); + + // Check if the HDA has been marked as not producing outputs + if (!HAC->bOutputless) + { + // Check if we want to convert legacy v1 data + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility && HAC->Version1CompatibilityHAC) + { + // Do not reuse legacy outputs! + for (auto& OldOutput : HAC->Outputs) + { + ClearOutput(OldOutput); + } + } + + TArray NewOutputs; + if (FHoudiniOutputTranslator::BuildAllOutputs(HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos)) + { + ClearAndRemoveOutputs(HAC); + // Replace with the new parameters + HAC->Outputs = NewOutputs; + } + } + else + { + // This HDA is marked as not supposed to produce any output + ClearAndRemoveOutputs(HAC); + } + + // NOTE: PersistentWorld can be NULL when, for example, working with + // HoudiniAssetComponents in Blueprints. + UWorld* PersistentWorld = HAC->GetWorld(); + UWorldComposition* WorldComposition = nullptr; + if (PersistentWorld) + { + WorldComposition = PersistentWorld->WorldComposition; + } + + if (IsValid(WorldComposition)) + { + // We don't want the origin to shift as we're potentially updating levels. + WorldComposition->bTemporarilyDisableOriginTracking = true; + } + + // "Process" the mesh. + // TODO: Move this to the actual processing stage, + // And see if some of this could be threaded + UObject* OuterComponent = HAC; + + FString HoudiniAssetPath = FPaths::GetPath(HAC->GetPathName()); + FString ComponentGUIDString = HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); + FString HoudiniAssetNameString = HAC->GetDisplayName(); + + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + PackageParams.ObjectName = FString(); + + TArray CreatedWorldCompositionPackages; + bool bCreatedNewMaps = false; + //... for heightfield outputs ...// + + // Collect all the landscape layers' global min/max values. + TMap LandscapeLayerGlobalMinimums; + TMap LandscapeLayerGlobalMaximums; + + // Store the instancer outputs separately so we can process them later, after all mesh output are processed. + // Determine the total number of instances, if we have more than 1 then mesh parts with instanced geo we will not create proxy meshes + // Also if we have object instancer (or oldschool attribute instancers), we won't be creating any proxy at all + TArray InstancerOutputs; + int32 NumInstances = 0; + bool bHasObjectInstancer = false; + + for (auto& CurOutput : HAC->Outputs) + { + if (CurOutput->GetType() == EHoudiniOutputType::Instancer) + { + // InstancerOutputs.Add(CurOutput); + for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type == EHoudiniPartType::Instancer) + { + if (HGPO.InstancerType == EHoudiniInstancerType::PackedPrimitive) + { + NumInstances += HGPO.PartInfo.InstanceCount; + } + else + { + NumInstances += HGPO.PartInfo.PointCount; + } + + if ((HGPO.InstancerType == EHoudiniInstancerType::ObjectInstancer) + || (HGPO.InstancerType == EHoudiniInstancerType::OldSchoolAttributeInstancer)) + { + bHasObjectInstancer = true; + } + } + } + } + else if (CurOutput->GetType() == EHoudiniOutputType::Landscape) + { + FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, false); + } + } + + bOutHasHoudiniStaticMeshOutput = false; + int32 NumVisibleOutputs = 0; + int32 NumOutputs = HAC->Outputs.Num(); + bool bHasLandscape = false; + + // Before processing all the outputs, + // See if we have any landscape input that have "Update Input Landscape" enabled + // And make an array of all our input landscapes + TArray AllInputLandscapes; + TArray InputLandscapesToUpdate; + + for (auto CurrentInput : HAC->Inputs) + { + if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) + continue; + + // Get the landscape input's landscape + ALandscapeProxy* InputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); + if (!InputLandscape) + continue; + + AllInputLandscapes.Add(InputLandscape); + + if (CurrentInput->GetUpdateInputLandscape()) + InputLandscapesToUpdate.Add(InputLandscape); + } + + // ---------------------------------------------------- + // Process outputs + // ---------------------------------------------------- + TArray CreatedPackages; + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) + { + UHoudiniOutput* CurOutput = HAC->GetOutputAt(OutputIdx); + if (!CurOutput || CurOutput->IsPendingKill()) + continue; + + FString Notification = FString::Format(TEXT("Processing output {0} / {1}..."), {FString::FromInt(OutputIdx + 1), FString::FromInt(NumOutputs)}); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + if (!HAC->IsOutputTypeSupported(CurOutput->GetType())) + continue; + + switch (CurOutput->GetType()) + { + case EHoudiniOutputType::Mesh: + { + bool bIsProxyStaticMeshEnabled = ( + HAC->IsProxyStaticMeshEnabled() && + !HAC->HasNoProxyMeshNextCookBeenRequested() && + !HAC->IsBakeAfterNextCookEnabled()); + if (bIsProxyStaticMeshEnabled && NumInstances > 1) + { + if (bHasObjectInstancer) + { + // Completely disable proxies if we have object instancers/old school attribute instancers + // as they rely on having a static mesh created (and the instanced mesh HGPO is not marked as instanced...) + bIsProxyStaticMeshEnabled = false; + } + else + { + // If we dont have proxy instancer, enable proxy only for non-instanced mesh + for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.bIsInstanced && HGPO.Type == EHoudiniPartType::Mesh) + { + bIsProxyStaticMeshEnabled = false; + break; + } + } + } + } + + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + PackageParams, + bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, + HAC->StaticMeshGenerationProperties, + OuterComponent); + + NumVisibleOutputs++; + + // Look for UHoudiniStaticMesh in the output, and set bOutHasHoudiniStaticMeshOutput accordingly + if (bIsProxyStaticMeshEnabled && !bOutHasHoudiniStaticMeshOutput) + { + bOutHasHoudiniStaticMeshOutput &= CurOutput->HasAnyCurrentProxy(); + } + + break; + } + + case EHoudiniOutputType::Curve: + { + const TArray &GeoPartObjects = CurOutput->GetHoudiniGeoPartObjects(); + + if (GeoPartObjects.Num() <= 0) + continue; + + const FHoudiniGeoPartObject & CurHGPO = GeoPartObjects[0]; + + if (CurOutput->IsEditableNode()) + { + if (!CurOutput->HasEditableNodeBuilt()) + { + // Editable curve, only need to be built once. + UHoudiniSplineComponent* HoudiniSplineComponent = FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode( + CurHGPO.GeoId, + CurHGPO.PartName, + HAC); + + HoudiniSplineComponent->SetIsEditableOutputCurve(true); + + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; + EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; + EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; + EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; + + TMap& OutputObjects = CurOutput->GetOutputObjects(); + FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + FoundOutputObject.OutputComponent = HoudiniSplineComponent; + + CurOutput->SetHasEditableNodeBuilt(true); + } + } + else + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); + NumVisibleOutputs += CurOutput->GetOutputObjects().Num(); + break; + } + } + break; + + case EHoudiniOutputType::Instancer: + InstancerOutputs.Add(CurOutput); + break; + + case EHoudiniOutputType::Landscape: + { + NumVisibleOutputs++; + + // This gets called for each heightfield primitive from Houdini, i.e., each "tile". + bool bNewMapCreated = false; + // Registering of untracked actors is not currently used in the HDA + // workflow. HDA cleanup will manually search for shared landscapes + // and remove them. That aforementioned behaviour should really be updated to + // make use of untracked actors on the HAC (similar to PDG Asset Link). + TArray> UntrackedActors; + + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + UntrackedActors, + InputLandscapesToUpdate, + AllInputLandscapes, + HAC, + TEXT("{hda_actor_name}_"), + PersistentWorld, + LandscapeLayerGlobalMinimums, + LandscapeLayerGlobalMaximums, + PackageParams, + CreatedPackages); + + bHasLandscape = true; + + // Attach the created landscape to the parent HAC. + ALandscapeProxy* OutputLandscape = nullptr; + for (auto& Pair : CurOutput->GetOutputObjects()) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(Pair.Value.OutputObject); + OutputLandscape = LandscapePtr->GetRawPtr(); + break; + } + + if (OutputLandscape) + { + // Attach the created landscapes to HAC + // Output Transforms are always relative to the HDA + HAC->SetMobility(EComponentMobility::Static); + OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + // Note that the above attach will cause the collision components to crap out. This manifests + // itself via the Landscape editor tools not being able to trace Landscape collision components. + // By recreating collision components here, it appears to put things back into working order. + OutputLandscape->RecreateCollisionComponents(); + } + + bCreatedNewMaps |= bNewMapCreated; + + break; + } + default: + // Do Nothing for now + break; + } + } + + // Now that all meshes have been created, process the instancers + for (auto& CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); + NumVisibleOutputs++; + } + + if (NumVisibleOutputs > 0) + { + // If we have valid outputs, we don't need to display the houdini logo anymore... + FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(HAC); + } + else + { + // ... if we don't have any valid outputs however, we should + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HAC); + } + + if (bHasLandscape) + { + // ---------------------------------------------------- + // Cleanup untracked shared landscape actors + // ---------------------------------------------------- + // This is a nasty hack to clean up SharedLandscape actors generated by the + // Landscape translator but aren't tracked by an HoudiniOutputObject, since the + // translators can't dynamically create outputs. + + { + // First collect all the landscapes that is being tracked by the HAC. + TSet TrackedLandscapes; + for(UHoudiniOutput* Output : HAC->Outputs) + { + if (Output->GetType() == EHoudiniOutputType::Landscape) + { + for(auto& Elem : Output->GetOutputObjects()) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(Elem.Value.OutputObject); + if (!IsValid(LandscapePtr)) + continue; + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapeProxy)) + { + TrackedLandscapes.Add(LandscapeProxy); + + // We need to recreate component states for landscapes if a tile was created, moved, or resized + // otherwise the landscape will exhibit render artifacts (such as only rendering every other + // component.) + LandscapeProxy->RecreateComponentsState(); + } + } + } + } + + // Iterate over Houdini asset child assets in order to find dangling Landscape actors + TArray AttachedComponents = HAC->GetAttachChildren(); + for(USceneComponent* Component : AttachedComponents) + { + if (!IsValid(Component)) + continue; + AActor* Actor = Component->GetOwner(); + ALandscape* Landscape = Cast(Actor); + if (!Landscape) + continue; + if (TrackedLandscapes.Contains(Landscape)) + continue; + + ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); + if (!Info || Info->Proxies.Num() == 0) + { + Landscape->Destroy(); + } + } + } + + // Recreate Landscape Info calls WorldChange, so no need to do it manually. + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + } + + if (IsValid(WorldComposition)) + { + // Disable the flag that we set before starting the import process. + WorldComposition->bTemporarilyDisableOriginTracking = false; + } + + // If the owner component was marked as loaded, unmark all outputs + if (HAC->HasBeenLoaded()) + { + for (auto& CurrentOutput : HAC->Outputs) + { + CurrentOutput->MarkAsLoaded(false); + } + } + + if (bCreatedNewMaps) + { + // Force the asset registry to update its cache of packages paths + // recursively for this world, otherwise world composition won't + // pick them up during the WorldComposition::Rescan(). + FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); + + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + + FHoudiniEngineUtils::LogWorldInfo(PersistentWorld); + if (WorldComposition) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); + } + + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); + } + + if (CreatedPackages.Num() > 0) + { + // Save created packages. For example, we don't want landscape layers deleted + // along with the HDA. + FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); + } + + return true; +} + +bool +FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + UObject* OuterComponent = HAC; + + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + PackageParams.ObjectName = FString(); + + bool bFoundProxies = false; + TArray InstancerOutputs; + for (auto& CurOutput : HAC->Outputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType == EHoudiniOutputType::Mesh) + { + if (CurOutput->HasAnyCurrentProxy()) + { + bFoundProxies = true; + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + PackageParams, + HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, + HAC->StaticMeshGenerationProperties, + OuterComponent, + true, // bInTreatExistingMaterialsAsUpToDate + bInDestroyProxies + ); + } + } + else if (OutputType == EHoudiniOutputType::Instancer) + { + InstancerOutputs.Add(CurOutput); + } + } + + // Rebuild instancers if we built any static meshes from proxies + if (bFoundProxies) + { + for (auto& CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); + } + } + + return true; +} + +// +bool +FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) +{ + HAPI_NodeId & AssetId = HAC->AssetId; + // Get the AssetInfo + 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; + + TArray EditableCurveObjIds; + TArray EditableCurveGeoIds; + TArray EditableCurvePartIds; + TArray EditableCurvePartNames; + + // Iterate through all objects to get all editable curve's object geo and part Ids. + + for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; + + // Cache/convert them + FHoudiniObjectInfo CurrentObjectInfo; + CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); + + // Start by getting the number of editable nodes + int32 EditableNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + + if (EditableNodeCount > 0) + { + TArray< HAPI_NodeId > EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) + continue; + + // Only catch editable curves + if (CurrentEditableGeoInfo.type != HAPI_GeoType::HAPI_GEOTYPE_CURVE) + continue; + + // Cook the editable node to get its parts + if (CurrentEditableGeoInfo.partCount <= 0) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentEditableGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentEditableGeoInfo.nodeId, + &CurrentEditableGeoInfo)); + } + + // Iterate on this geo's parts + for (int32 PartId = 0; PartId < CurrentEditableGeoInfo.partCount; ++PartId) + { + // Get part information. + HAPI_PartInfo CurrentHapiPartInfo; + FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + continue; + + if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE) + continue; + + // Get the editable curve's part name + FHoudiniEngineString hapiSTR(CurrentHapiPartInfo.nameSH); + FString PartName; + hapiSTR.ToFString(PartName); + + EditableCurveObjIds.Add(CurrentHapiObjectInfo.nodeId); + EditableCurveGeoIds.Add(CurrentEditableGeoInfo.nodeId); + EditableCurvePartIds.Add(CurrentHapiPartInfo.id); + EditableCurvePartNames.Add(PartName); + } + } + } + + } + + int32 Idx = 0; + for (auto& CurrentOutput : HAC->Outputs) + { + if (CurrentOutput->IsEditableNode()) + { + // The HAC is Loaded, re-assign node id to its editable curves + if (CurrentOutput->HasEditableNodeBuilt()) + { + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + for (auto& Pair : OutputObjects) + { + if (Idx >= EditableCurvePartIds.Num()) + break; + + UHoudiniSplineComponent * HoudiniSplineComponent = Cast(Pair.Value.OutputComponent); + if (HoudiniSplineComponent && !HoudiniSplineComponent->IsPendingKill()) + { + HoudiniSplineComponent->SetNodeId(EditableCurveGeoIds[Idx]); + + Pair.Key.ObjectId = EditableCurveObjIds[Idx]; + Pair.Key.GeoId = EditableCurveGeoIds[Idx]; + Pair.Key.PartId = EditableCurvePartIds[Idx]; + Pair.Key.PartName = EditableCurvePartNames[Idx]; + + Idx += 1; + } + } + } + // The HAC is a Duplication, re-construct output objects with attached duplicated editable curves, matching by part name + else + { + const TArray &Children = HAC->GetAttachChildren(); + for (auto & CurAttachedComp : Children) + { + if (!CurAttachedComp || CurAttachedComp->IsPendingKill()) + continue; + + if (!CurAttachedComp->IsA()) + continue; + + UHoudiniSplineComponent * CurAttachedSplineComp = Cast(CurAttachedComp); + if (!CurAttachedSplineComp) + continue; + + if (!CurAttachedSplineComp->IsEditableOutputCurve()) + continue; + + if (Idx >= EditableCurvePartIds.Num()) + break; + + // Found a match + if (CurAttachedSplineComp->GetGeoPartName().Equals(EditableCurvePartNames[Idx])) + { + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = EditableCurveObjIds[Idx]; + EditableSplineComponentIdentifier.GeoId = EditableCurveGeoIds[Idx]; + EditableSplineComponentIdentifier.PartId = EditableCurvePartIds[Idx]; + EditableSplineComponentIdentifier.PartName = EditableCurvePartNames[Idx]; + + CurAttachedSplineComp->SetNodeId(EditableSplineComponentIdentifier.GeoId); + + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + FHoudiniOutputObject& NewOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + NewOutputObject.OutputComponent = CurAttachedSplineComp; + + CurrentOutput->SetHasEditableNodeBuilt(true); + FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(CurAttachedSplineComp); + + Idx += 1; + break; + } + } + } + } + else + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); + } + + // Mark our outputs as loaded so they can be matched for potential reuse + // This indicates that the HGPO's ids are invalid and that HGPO should be matched using partnames instead + CurrentOutput->MarkAsLoaded(true); + } + + return true; +} + +// +bool +FHoudiniOutputTranslator::UploadChangedEditableOutput( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray &Outputs = HAC->Outputs; + + // Iterate through the outputs array of HAC. + for (auto& CurrentOutput : HAC->Outputs) + { + if (!CurrentOutput) + continue; + + // Only update the editable nodes that have been built before. + if (!CurrentOutput->IsEditableNode() || !CurrentOutput->HasEditableNodeBuilt()) + continue; + + for (auto& CurrentOutputObj : CurrentOutput->GetOutputObjects()) + { + UHoudiniSplineComponent* HoudiniSplineComponent = Cast(CurrentOutputObj.Value.OutputComponent); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (!HoudiniSplineComponent->HasChanged()) + continue; + + if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(HoudiniSplineComponent)) + HoudiniSplineComponent->MarkChanged(false); + else + HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); + } + } + + return true; +} + + +bool +FHoudiniOutputTranslator::BuildAllOutputs( + const HAPI_NodeId& AssetId, + UObject* InOuterObject, + TArray& InOldOutputs, + TArray& OutNewOutputs, + const bool& InOutputTemplatedGeos) +{ + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Get the AssetInfo + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + FString CurrentAssetName; + { + FHoudiniEngineString hapiSTR(AssetInfo.nameSH); + hapiSTR.ToFString(CurrentAssetName); + } + + // Retrieve the asset's transform. + // TODO: Unused?! + //FTransform AssetUnrealTransform; + //if (!FHoudiniEngineUtils::HapiGetAssetTransform(AssetId, AssetUnrealTransform)) + // return false; + + // Retrieve information about each object contained within our asset. + TArray ObjectInfos; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos)) + return false; + + //const int32 ObjectCount = ObjectInfos.Num(); + + // Retrieve transforms for each object in this asset. + TArray ObjectTransforms; + if (!FHoudiniEngineUtils::HapiGetObjectTransforms(AssetId, ObjectTransforms)) + return false; + + // Mark all the previous HGPOs on the outputs as stale + // This indicates that they were from a previous cook and should then be deleted + for (auto& CurOutput : InOldOutputs) + { + if (CurOutput) + CurOutput->MarkAllHGPOsAsStale(true); + } + + // For HF / Volumes, we only create new Outputs for height volume + // Store all the other volumes (masks etc) on the side and we will + // match them with theit corresponding height volume after + TArray UnassignedVolumeParts; + + TArray AllSockets; + + // Iterate through all objects. + int32 OutputIdx = 1; + for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; + + // Cache/convert them + FHoudiniObjectInfo CurrentObjectInfo; + CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); + + // Retrieve object name. + FString CurrentObjectName = CurrentObjectInfo.Name; + + // Get transformation for this object. + const HAPI_Transform & ObjectTransform = ObjectTransforms[ObjectId]; + FTransform TransformMatrix; + FHoudiniEngineUtils::TranslateHapiTransform(ObjectTransform, TransformMatrix); + + // TODO: Check transforms?? + + // Build an array of the geos we'll need to process + // In most case, it will only be the display geo, + // but we may also want to process editable geos as well + TArray GeoInfos; + + // Get the Display Geo's info + HAPI_GeoInfo DisplayHapiGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName); + } + else + { + // Add the display geo info to the array + GeoInfos.Add(DisplayHapiGeoInfo); + } + + // Handle the editable nodes for this geo + // Start by getting the number of editable nodes + int32 EditableNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + + if (EditableNodeCount > 0) + { + TArray< HAPI_NodeId > EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) + continue; + + // Add this geo to the geo info array + GeoInfos.Add(CurrentEditableGeoInfo); + } + } + + // Handle the templated nodes if desired + if (InOutputTemplatedGeos) + { + // Start by getting the number of templated nodes + int32 TemplatedNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, + HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, + true, &TemplatedNodeCount)); + + if (TemplatedNodeCount > 0) + { + TArray TemplatedNodeIds; + TemplatedNodeIds.SetNumUninitialized(TemplatedNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, TemplatedNodeIds.GetData(), TemplatedNodeCount)); + + for (int32 nTemplated = 0; nTemplated < TemplatedNodeCount; nTemplated++) + { + HAPI_GeoInfo CurrentTemplatedGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentTemplatedGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + TemplatedNodeIds[nTemplated], &CurrentTemplatedGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentTemplatedGeoInfo.isDisplayGeo) + continue; + + // We don't want all the nested template node IDs, + // as our HDA could potentially be using other HDAs with nested template flags + // Make sure the parent of the templated node is either the HDA, the current OBJ or the Display SOP + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurrentTemplatedGeoInfo.nodeId); + if (ParentId != CurrentHapiObjectInfo.nodeId + && ParentId != DisplayHapiGeoInfo.nodeId + && ParentId != AssetId) + { + continue; + } + + // Add this geo to the geo info array + GeoInfos.Add(CurrentTemplatedGeoInfo); + } + } + } + + // Iterates through the geos we want to process + for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) + { + // Cook editable/templated nodes to get their parts. + const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; + if ((CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) + || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0)) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, + &GeoInfos[GeoIdx])); + } + + // Cache/convert the display geo's info + FHoudiniGeoInfo CurrentGeoInfo; + CacheGeoInfo(CurrentHapiGeoInfo, CurrentGeoInfo); + + // Simply create an empty array for this geo's group names + // We might need it later for splitting + TArray GeoGroupNames; + bool HasSocketGroups = false; + + // Iterate on this geo's parts + for (int32 PartId = 0; PartId < CurrentGeoInfo.PartCount; ++PartId) + { + // Get part information. + HAPI_PartInfo CurrentHapiPartInfo; + FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); + + // If the geo is templated, cook it manually + if (CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + } + + bool bPartInfoFailed = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + { + bPartInfoFailed = true; + + // If the geo is templated, attempt to cook it manually + if(CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, nullptr); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, + &GeoInfos[GeoIdx])); + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + { + // We managed to get the templated part infos after cooking + bPartInfoFailed = false; + } + } + } + + if (bPartInfoFailed) + { + // Error retrieving part info. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d] unable to retrieve PartInfo - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId); + continue; + } + + // Convert/cache the part info + FHoudiniPartInfo CurrentPartInfo; + CachePartInfo(CurrentHapiPartInfo, CurrentPartInfo); + + // Retrieve part name. + FString CurrentPartName = CurrentPartInfo.Name; + + // Unsupported/Invalid part + if (CurrentPartInfo.Type == EHoudiniPartType::Invalid) + continue; + + // Update part/instancer type from the part infos + EHoudiniPartType CurrentPartType = EHoudiniPartType::Invalid; + EHoudiniInstancerType CurrentInstancerType = EHoudiniInstancerType::Invalid; + switch (CurrentHapiPartInfo.type) + { + case HAPI_PARTTYPE_BOX: + case HAPI_PARTTYPE_SPHERE: + case HAPI_PARTTYPE_MESH: + { + if (CurrentHapiGeoInfo.type == HAPI_GEOTYPE_CURVE) + { + // Closed curve will be seen as mesh + CurrentPartType = EHoudiniPartType::Curve; + } + else + { + CurrentPartType = EHoudiniPartType::Mesh; + + if (CurrentHapiObjectInfo.isInstancer) + { + if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // That part is actually an attribute instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + } + else + { + // That part is actually an instancer + CurrentPartType = EHoudiniPartType::Instancer; + CurrentInstancerType = EHoudiniInstancerType::ObjectInstancer; + } + + } + else if (CurrentHapiPartInfo.vertexCount <= 0 && CurrentHapiPartInfo.pointCount <= 0) + { + // No points, no vertices, we're likely invalid + CurrentPartType = EHoudiniPartType::Invalid; + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a mesh with no points or vertices - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + } + else if (CurrentHapiPartInfo.vertexCount <= 0) + { + // This is not an instancer, we do not have vertices, but we have points + // Maybe this is a point cloud with attribute override instancing + if(FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // Mark it as an instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + } + else + { + // No vertices, not an instancer, just a point cloud, consider ourself as invalid + CurrentPartType = EHoudiniPartType::Invalid; + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a point cloud mesh - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + } + } + } + } + break; + + case HAPI_PARTTYPE_CURVE: + { + // Make sure that this curve is not an an attribute instancer! + if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // Mark the part as an instancer it as an instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + } + else + { + // The curve is a curve! + CurrentPartType = EHoudiniPartType::Curve; + } + } + break; + + case HAPI_PARTTYPE_INSTANCER: + // This is a packed primitive instancer + CurrentPartType = EHoudiniPartType::Instancer; + CurrentInstancerType = EHoudiniInstancerType::PackedPrimitive; + break; + + case HAPI_PARTTYPE_VOLUME: + // Volume data, likely a Heightfield height / mask + CurrentPartType = EHoudiniPartType::Volume; + break; + + default: + // Unsupported Part Type + break; + } + + // There are no vertices AND no points and this part is not a packed prim instancer + if ((CurrentPartInfo.VertexCount <= 0 && CurrentPartInfo.PointCount <= 0) + && (CurrentPartType != EHoudiniPartType::Instancer || CurrentInstancerType != EHoudiniInstancerType::PackedPrimitive)) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] no points or vertices found - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + continue; + } + + // This is an instancer with no points. + if (CurrentHapiObjectInfo.isInstancer && CurrentHapiPartInfo.pointCount <= 0) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is instancer but has 0 points - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + continue; + } + + // Ignore invalid parts + if (CurrentPartType == EHoudiniPartType::Invalid) + continue; + + // Build the HGPO corresponding to this part + FHoudiniGeoPartObject currentHGPO; + currentHGPO.AssetId = AssetId; + currentHGPO.AssetName = CurrentAssetName; + + currentHGPO.ObjectId = CurrentHapiObjectInfo.nodeId; + currentHGPO.ObjectName = CurrentObjectName; + + currentHGPO.GeoId = CurrentHapiGeoInfo.nodeId; + + currentHGPO.PartId = CurrentHapiPartInfo.id; + + currentHGPO.Type = CurrentPartType; + currentHGPO.InstancerType = CurrentInstancerType; + + currentHGPO.TransformMatrix = TransformMatrix; + + currentHGPO.NodePath = TEXT(""); + + currentHGPO.bIsVisible = CurrentHapiObjectInfo.isVisible && !CurrentHapiPartInfo.isInstanced; + currentHGPO.bIsEditable = CurrentHapiGeoInfo.isEditable; + currentHGPO.bIsInstanced = CurrentHapiPartInfo.isInstanced; + // Never consider a display geo as templated! + currentHGPO.bIsTemplated = CurrentHapiGeoInfo.isDisplayGeo ? false : CurrentHapiGeoInfo.isTemplated; + + currentHGPO.bHasGeoChanged = CurrentHapiGeoInfo.hasGeoChanged; + currentHGPO.bHasPartChanged = CurrentHapiPartInfo.hasChanged; + currentHGPO.bHasMaterialsChanged = CurrentHapiGeoInfo.hasMaterialChanged; + currentHGPO.bHasTransformChanged = CurrentHapiObjectInfo.hasTransformChanged; + + // Copy the HAPI info caches + currentHGPO.ObjectInfo = CurrentObjectInfo; + currentHGPO.GeoInfo = CurrentGeoInfo; + currentHGPO.PartInfo = CurrentPartInfo; + + // We only support meshes for templated geos + if (currentHGPO.bIsTemplated && (CurrentPartType != EHoudiniPartType::Mesh)) + continue; + + // Update the HGPO's node path + FHoudiniEngineUtils::HapiGetNodePath(currentHGPO, currentHGPO.NodePath); + + // Try to get the custom part name from attribute + FString CustomPartName; + if (FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CustomPartName)) + currentHGPO.SetCustomPartName(CustomPartName); + else + currentHGPO.PartName = CurrentPartName; + + // + // Mesh Only - Extract split groups + // + // Extract the group names used by this part to see if it will require splitting + // Only meshes can be split, via their primitive groups + TArray< FString > SplitGroupNames; + if (CurrentPartType == EHoudiniPartType::Mesh) + { + if (!CurrentHapiPartInfo.isInstanced && GeoGroupNames.Num() > 0) + { + // We are not instanced and already have extracted the geo's group names + // We can simply reuse the Geo group names / socket groups + currentHGPO.SplitGroups = GeoGroupNames; + } + else + { + // We need to get the primitive group names from HAPI + int32 GroupCount = 0; + TArray GroupNames; + if (!FHoudiniEngineUtils::HapiGetGroupNames( + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + HAPI_GROUPTYPE_PRIM, CurrentHapiPartInfo.isInstanced, + GroupNames)) + { + GroupCount = 0; + GroupNames.Empty(); + } + + // Convert the string handles to FStrings + for (const FString& GroupName : GroupNames) + { + FString LodGroup = HAPI_UNREAL_GROUP_LOD_PREFIX; + FString CollisionGroup = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; + FString RenderedCollisionGroup = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; + if (GroupName.StartsWith(LodGroup, ESearchCase::IgnoreCase) + || GroupName.StartsWith(CollisionGroup, ESearchCase::IgnoreCase) + || GroupName.StartsWith(RenderedCollisionGroup, ESearchCase::IgnoreCase)) + //|| GroupName.StartsWith(HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX, ESearchCase::IgnoreCase)) + { + // Split by collisions / lods + currentHGPO.SplitGroups.Add(GroupName); + } + } + + // Sort the Group name array by name, + // this will order the LODs and other incremental group names + currentHGPO.SplitGroups.Sort(); + + // If this part is not instanced, we can copy the geo + // group names so we can reuse them for another part + if (!CurrentHapiPartInfo.isInstanced) + { + GeoGroupNames = currentHGPO.SplitGroups; + } + } + } + + // + // Volume Only - Extract volume name/tile index + // + // Extract the volume's name, and see if a tile attribute is present + FHoudiniVolumeInfo CurrentVolumeInfo; + if (CurrentPartType == EHoudiniPartType::Volume) + { + // Get this volume's info + HAPI_VolumeInfo CurrentHapiVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentHapiVolumeInfo); + + bool bVolumeValid = true; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + &CurrentHapiVolumeInfo)) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.tupleSize != 1) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.zLength != 1) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + { + bVolumeValid = false; + } + + // Only cache valid volumes + if (bVolumeValid) + { + // Convert/Cache the volume info + CacheVolumeInfo(CurrentHapiVolumeInfo, CurrentVolumeInfo); + + // Get the volume's name + currentHGPO.VolumeName = CurrentVolumeInfo.Name; + + // Now see if this volume has a tile attribute + TArray TileValues; + if (FHoudiniEngineUtils::GetTileAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, TileValues, HAPI_ATTROWNER_PRIM)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + currentHGPO.VolumeTileIndex = TileValues[0]; + else + currentHGPO.VolumeTileIndex = -1; + } + } + } + currentHGPO.VolumeInfo = CurrentVolumeInfo; + + // Cache the curve info as well + // !!! Only call GetCurveInfo if the PartType is Curve + // !!! Closed curves are actually Meshes, and calling GetCurveInfo on a Mesh will crash HAPI! + FHoudiniCurveInfo CurrentCurveInfo; + if (CurrentPartType == EHoudiniPartType::Curve && CurrentPartInfo.Type == EHoudiniPartType::Curve) + { + HAPI_CurveInfo CurrentHapiCurveInfo; + FHoudiniApi::CurveInfo_Init(&CurrentHapiCurveInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + &CurrentHapiCurveInfo)) + { + // Cache/Convert this part's curve info + CacheCurveInfo(CurrentHapiCurveInfo, CurrentCurveInfo); + } + } + currentHGPO.CurveInfo = CurrentCurveInfo; + + + // TODO: + // DONE? bake folders are handled out of this loop? + // See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute + //TArray BakeFolderOverrides; + + // Extract socket points + FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); + FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); + + // See if we have an existing output that matches this HGPO or if we need to create a new one + bool IsFoundOutputValid = false; + UHoudiniOutput ** FoundHoudiniOutput = nullptr; + // We handle volumes differently than other outputs types, as a single HF output has multiple HGPOs + if (currentHGPO.Type != EHoudiniPartType::Volume) + { + // Look in the previous output if we have a match + FoundHoudiniOutput = InOldOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(currentHGPO) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + + } + else + { + // Look in the previous outputs if we have a match + FoundHoudiniOutput = InOldOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, true) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + + // If we dont have a match in the old maps, also look in the newly created outputs + if (!IsFoundOutputValid) + { + FoundHoudiniOutput = OutNewOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, false) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + } + } + + UHoudiniOutput * HoudiniOutput = nullptr; + if (IsFoundOutputValid) + { + // We can reuse the existing output + HoudiniOutput = *FoundHoudiniOutput; + HoudiniOutput->SetIsUpdating(true); + // Transfer this output from the old array to the new one + InOldOutputs.Remove(HoudiniOutput); + } + else + { + // We couldn't find a valid output object, so create a new one + + // If the current part is a volume, only create a new output object + // if the volume's name is "height", if not store the HGPO aside + if (currentHGPO.Type == EHoudiniPartType::Volume + && !currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) + { + UnassignedVolumeParts.Add(currentHGPO); + continue; + } + + // Create a new output object + //FString OutputName = TEXT("Output") + FString::FromInt(OutputIdx++); + HoudiniOutput = NewObject( + InOuterObject, + UHoudiniOutput::StaticClass(), + NAME_None,//FName(*OutputName), + RF_NoFlags); + + // Make sure the created object is valid + if (!HoudiniOutput || HoudiniOutput->IsPendingKill()) + { + //HOUDINI_LOG_WARNING("Failed to create asset output"); + continue; + } + + // Mark if the HoudiniOutput is editable + HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); + } + + // Add the HGPO to the output + HoudiniOutput->AddNewHGPO(currentHGPO); + // Add this output object to the new ouput array + OutNewOutputs.AddUnique(HoudiniOutput); + } + } + } + + // Update the output/HGPO associations from the map + // Clear the old HGPO since we don't need them anymore + for (auto& CurrentOuput : OutNewOutputs) + { + if (!CurrentOuput || CurrentOuput->IsPendingKill()) + continue; + + CurrentOuput->DeleteAllStaleHGPOs(); + } + + // If we have unassigned volumes, + // try to find their corresponding output + if (UnassignedVolumeParts.Num() > 0) + { + for (auto& currentVolumeHGPO : UnassignedVolumeParts) + { + UHoudiniOutput ** FoundHoudiniOutput = OutNewOutputs.FindByPredicate( + [currentVolumeHGPO](UHoudiniOutput* Output) + { + return Output ? Output->HeightfieldMatch(currentVolumeHGPO, false) : false; + }); + + if (!FoundHoudiniOutput || !(*FoundHoudiniOutput) || (*FoundHoudiniOutput)->IsPendingKill()) + { + // Skip - consider this volume as invalid + continue; + } + + // Add this HGPO to the output + (*FoundHoudiniOutput)->AddNewHGPO(currentVolumeHGPO); + } + } + + // All our output objects now have their HGPO assigned + // We can now parse them to update the output type + for (auto& Output : OutNewOutputs) + { + Output->UpdateOutputType(); + } + + return true; +} + +bool +FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray& Outputs = HAC->Outputs; + + // Iterate through the outputs array of HAC. + for (int32 Index = 0; Index < HAC->GetNumOutputs(); ++Index) + { + UHoudiniOutput* CurrentOutput = HAC->GetOutputAt(Index); + if (!CurrentOutput) + continue; + + if (!HAC->IsOutputTypeSupported(CurrentOutput->GetType())) + continue; + + switch (CurrentOutput->GetType()) + { + case EHoudiniOutputType::Instancer: + { + bool bNeedToRecreateInstancers = false; + for (auto& Iter : CurrentOutput->GetInstancedOutputs()) + { + FHoudiniInstancedOutput& InstOutput = Iter.Value; + if (!InstOutput.bChanged) + continue; + + /* + FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( + InstOutput, Iter.Key, CurrentOutput, HAC); + */ + + // TODO: + // UpdateChangedInstancedOutput needs some improvements + // as it currently destroy too many components. + // For now, we'll update all the instancers + bNeedToRecreateInstancers = true; + + InstOutput.MarkChanged(false); + } + + if (bNeedToRecreateInstancers) + { + if (HAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation || HAC->HasBeenLoaded()) + { + // Instantiate the HDA if it's not been + // This is because CreateAllInstancersFromHoudiniOutput() actually reads the transform from HAPI + // Calling it on a HDA not yet instantiated causes a crash... + HAC->AssetState = EHoudiniAssetState::PreInstantiation; + } + else + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurrentOutput, Outputs, HAC); + } + } + } + break; + + case EHoudiniOutputType::Curve: + { + //FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); + } + break; + + default: + break; + } + } + + return true; +} + +void +FHoudiniOutputTranslator::CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache) +{ + FHoudiniEngineString hapiSTR(InObjInfo.nameSH); + hapiSTR.ToFString(OutObjInfoCache.Name); + //OutObjInfoCache.Name = InObjInfo.nameSH; + + OutObjInfoCache.NodeId = InObjInfo.nodeId; + OutObjInfoCache.ObjectToInstanceID = InObjInfo.objectToInstanceId; + + OutObjInfoCache.bHasTransformChanged = InObjInfo.hasTransformChanged; + OutObjInfoCache.bHaveGeosChanged = InObjInfo.haveGeosChanged; + + OutObjInfoCache.bIsVisible = InObjInfo.isVisible; + OutObjInfoCache.bIsInstancer = InObjInfo.isInstancer; + OutObjInfoCache.bIsInstanced = InObjInfo.isInstanced; + + OutObjInfoCache.GeoCount = InObjInfo.geoCount; +}; + +EHoudiniGeoType +FHoudiniOutputTranslator::ConvertHapiGeoType(const HAPI_GeoType& InType) +{ + EHoudiniGeoType OutType = EHoudiniGeoType::Invalid; + switch (InType) + { + case HAPI_GEOTYPE_DEFAULT: + OutType = EHoudiniGeoType::Default; + break; + + case HAPI_GEOTYPE_INTERMEDIATE: + OutType = EHoudiniGeoType::Intermediate; + break; + + case HAPI_GEOTYPE_INPUT: + OutType = EHoudiniGeoType::Input; + break; + + case HAPI_GEOTYPE_CURVE: + OutType = EHoudiniGeoType::Curve; + break; + + default: + OutType = EHoudiniGeoType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache) +{ + OutGeoInfoCache.Type = ConvertHapiGeoType(InGeoInfo.type); + + FHoudiniEngineString hapiSTR(InGeoInfo.nameSH); + hapiSTR.ToFString(OutGeoInfoCache.Name); + + OutGeoInfoCache.NodeId = InGeoInfo.nodeId; + + OutGeoInfoCache.bIsEditable = InGeoInfo.isEditable; + OutGeoInfoCache.bIsTemplated = InGeoInfo.isTemplated; + OutGeoInfoCache.bIsDisplayGeo = InGeoInfo.isDisplayGeo; + OutGeoInfoCache.bHasGeoChanged = InGeoInfo.hasGeoChanged; + OutGeoInfoCache.bHasMaterialChanged = InGeoInfo.hasMaterialChanged; + + OutGeoInfoCache.PartCount = InGeoInfo.partCount; + OutGeoInfoCache.PointGroupCount = InGeoInfo.pointGroupCount; + OutGeoInfoCache.PrimitiveGroupCount = InGeoInfo.primitiveGroupCount; +}; + + +EHoudiniPartType +FHoudiniOutputTranslator::ConvertHapiPartType(const HAPI_PartType& InType) +{ + EHoudiniPartType OutType = EHoudiniPartType::Invalid; + switch (InType) + { + case HAPI_PARTTYPE_BOX: + case HAPI_PARTTYPE_SPHERE: + case HAPI_PARTTYPE_MESH: + OutType = EHoudiniPartType::Mesh; + break; + + case HAPI_PARTTYPE_CURVE: + OutType = EHoudiniPartType::Curve; + break; + + case HAPI_PARTTYPE_INSTANCER: + OutType = EHoudiniPartType::Instancer; + break; + + case HAPI_PARTTYPE_VOLUME: + OutType = EHoudiniPartType::Volume; + break; + + default: + OutType = EHoudiniPartType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache) +{ + OutPartInfoCache.PartId = InPartInfo.id; + + FHoudiniEngineString hapiSTR(InPartInfo.nameSH); + hapiSTR.ToFString(OutPartInfoCache.Name); + + OutPartInfoCache.Type = ConvertHapiPartType(InPartInfo.type); + + OutPartInfoCache.FaceCount = InPartInfo.faceCount; + OutPartInfoCache.VertexCount = InPartInfo.vertexCount; + OutPartInfoCache.PointCount = InPartInfo.pointCount; + + OutPartInfoCache.PointAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_POINT]; + OutPartInfoCache.VertexAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX]; + OutPartInfoCache.PrimitiveAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_PRIM]; + OutPartInfoCache.DetailAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL]; + + OutPartInfoCache.bIsInstanced = InPartInfo.isInstanced; + + OutPartInfoCache.InstancedPartCount = InPartInfo.instancedPartCount; + OutPartInfoCache.InstanceCount = InPartInfo.instanceCount; + + OutPartInfoCache.bHasChanged = InPartInfo.hasChanged; +}; + +void +FHoudiniOutputTranslator::CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache) +{ + FHoudiniEngineString hapiSTR(InVolumeInfo.nameSH); + hapiSTR.ToFString(OutVolumeInfoCache.Name); + + OutVolumeInfoCache.bIsVDB = (InVolumeInfo.type == HAPI_VOLUMETYPE_VDB); // replaces VolumeType Type; + + OutVolumeInfoCache.TupleSize = InVolumeInfo.tupleSize; + OutVolumeInfoCache.bIsFloat = (InVolumeInfo.storage == HAPI_STORAGETYPE_FLOAT); // replaces StorageType StorageType; + OutVolumeInfoCache.TileSize = InVolumeInfo.tileSize; + + FHoudiniEngineUtils::TranslateHapiTransform(InVolumeInfo.transform, OutVolumeInfoCache.Transform); + OutVolumeInfoCache.bHasTaper = InVolumeInfo.hasTaper; + + OutVolumeInfoCache.XLength = InVolumeInfo.xLength; + OutVolumeInfoCache.YLength = InVolumeInfo.yLength; + OutVolumeInfoCache.ZLength = InVolumeInfo.zLength; + + OutVolumeInfoCache.MinX = InVolumeInfo.minX; + OutVolumeInfoCache.MinY = InVolumeInfo.minY; + OutVolumeInfoCache.MinZ = InVolumeInfo.minZ; + + OutVolumeInfoCache.XTaper = InVolumeInfo.xTaper; + OutVolumeInfoCache.YTaper = InVolumeInfo.yTaper; +}; + +EHoudiniCurveType +FHoudiniOutputTranslator::ConvertHapiCurveType(const HAPI_CurveType& InType) +{ + EHoudiniCurveType OutType = EHoudiniCurveType::Invalid; + switch (InType) + { + case HAPI_CURVETYPE_LINEAR: + OutType = EHoudiniCurveType::Polygon; + break; + + case HAPI_CURVETYPE_NURBS: + OutType = EHoudiniCurveType::Nurbs; + break; + + case HAPI_CURVETYPE_BEZIER: + OutType = EHoudiniCurveType::Bezier; + break; + + case HAPI_CURVETYPE_MAX: + OutType = EHoudiniCurveType::Points; + break; + + default: + OutType = EHoudiniCurveType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache) +{ + OutCurveInfoCache.Type = ConvertHapiCurveType(InCurveInfo.curveType); + + OutCurveInfoCache.CurveCount = InCurveInfo.curveCount; + OutCurveInfoCache.VertexCount = InCurveInfo.vertexCount; + OutCurveInfoCache.KnotCount = InCurveInfo.knotCount; + + OutCurveInfoCache.bIsPeriodic = InCurveInfo.isPeriodic; + OutCurveInfoCache.bIsRational = InCurveInfo.isRational; + + OutCurveInfoCache.Order = InCurveInfo.order; + + OutCurveInfoCache.bHasKnots = InCurveInfo.hasKnots; +}; + + +void +FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC) +{ + if (!IsValid(InHAC)) + return; + + // DO NOT MANUALLY DESTROY THE OLD/DANGLING OUTPUTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + // Simply clearing the array is enough + for (auto& OldOutput : InHAC->Outputs) + { + ClearOutput(OldOutput); + } + + InHAC->Outputs.Empty(); +} + +void +FHoudiniOutputTranslator::ClearOutput(UHoudiniOutput* Output) +{ + switch (Output->GetType()) + { + case EHoudiniOutputType::Landscape: + { + for (auto& OutputObject : Output->GetOutputObjects()) + { + // Currently, any Landscape managed by an HDA is always present in the current level. + // Only when it gets baked will Landscapes be serialized to other levels so for now + // assume that a landscape should be available, unless explicitly deleted the user. + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.Value.OutputObject); + if (!LandscapePtr) + continue; + + ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); + + if (!IsValid(Landscape) || !Landscape->IsValidLowLevel()) + continue; + + Landscape->UnregisterAllComponents(); + Landscape->Destroy(); + + // if (Output->IsLandscapeWorldComposition()) + // { + // TSoftObjectPtr LandscapeSoftPtr = LandscapePtr->GetSoftPtr(); + // + // FString SoftPtrPath = LandscapeSoftPtr.ToSoftObjectPath().ToString(); + // + // FString FileName = FPaths::GetBaseFilename(SoftPtrPath); + // FString FileDirectory = FPaths::GetPath(SoftPtrPath); + // + // FString ContentPath = FPaths::ProjectContentDir(); + // FString ContentFullPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*ContentPath); + // + // FString AbsoluteFilePath = ContentFullPath + FileDirectory.Mid(5, FileDirectory.Len() - 5) + "/" + FPaths::GetBaseFilename(FileName) + ".umap"; + // + // FPlatformFileManager::Get().GetPlatformFile().FileExists(*(AbsoluteFilePath)); + // + // FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*(AbsoluteFilePath)); + // } + // else + // { + + // } + } + } + break; + + case EHoudiniOutputType::Instancer: + { + for (auto& OutputObject : Output->GetOutputObjects()) + { + // Is this a foliage instancer? Check if the component is owned by an AInstancedFoliageActor + UActorComponent* const Component = Cast(OutputObject.Value.OutputComponent); + if (!IsValid(Component)) + continue; + AActor* const OwnerActor = Component->GetOwner(); + if (!IsValid(OwnerActor) || !OwnerActor->IsA()) + continue; + + UHierarchicalInstancedStaticMeshComponent* const FoliageHISMC = Cast(Component); + if (IsValid(FoliageHISMC)) + { + // Find the parent component: the foliage component outer, otherwise, if a houdini asset actor, the + // houdini asset component, otherwise for a normal actor its root component, finally try and see + // if the outer itself is a component. + USceneComponent* ParentComponent = Cast(FoliageHISMC->GetOuter()); + if (!IsValid(ParentComponent)) + { + UObject* const OutputOuter = Output->GetOuter(); + if (IsValid(OutputOuter)) + { + if (OutputOuter->IsA()) + { + ParentComponent = Cast(OutputOuter)->GetHoudiniAssetComponent(); + } + else if (OutputOuter->IsA()) + { + ParentComponent = Cast(OutputOuter)->GetRootComponent(); + } + else + { + ParentComponent = Cast(OutputOuter); + } + } + } + if (IsValid(ParentComponent)) + { + FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, ParentComponent); + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + } + } + } + } + break; + // ... Other output types ...// + + default: + break; + + } + + Output->Clear(); +} + + +bool +FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName) +{ + HAPI_AttributeInfo CustomPartNameInfo; + FHoudiniApi::AttributeInfo_Init(&CustomPartNameInfo); + + bool bHasCustomName = false; + TArray CustomNames; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, CustomPartNameInfo, CustomNames)) + { + // Look for the v2 attribute (unreal_output_name) + bHasCustomName = true; + } + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, CustomPartNameInfo, CustomNames)) + { + // If we couldnt find the new attribute, use the legacy v1 attribute (unreal_generated_mesh_name) + bHasCustomName = true; + } + + if (!bHasCustomName) + return false; + + if (CustomNames.Num() <= 0) + return false; + + OutCustomPartName = CustomNames[0]; + + if (OutCustomPartName.IsEmpty()) + return false; + + return true; +} + +void +FHoudiniOutputTranslator::GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) + return; + + FString BakeFolderOverride = FString(); + + FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); + + // If the BakeFolder of the HAC is non-empty and is different from the override path. + // do not override it if the current temp cook path is valid. (it was user specified) + if (!HAC->BakeFolder.Path.IsEmpty() && !HAC->BakeFolder.Path.Equals(BakeFolderOverride)) + return; + + HAC->BakeFolder.Path = BakeFolderOverride; +} + +void +FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) + return; + + FString TempFolderOverride = FString(); + + HAPI_AttributeInfo TempFolderAttriInfo; + FHoudiniApi::AttributeInfo_Init(&TempFolderAttriInfo); + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_DETAIL)) + { + TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); + } + else + { + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) + { + TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); + } + } + + if (TempFolderOverride.StartsWith("Game/")) + { + TempFolderOverride = "/" + TempFolderOverride; + } + + FString AbsoluteOverridePath; + if (TempFolderOverride.StartsWith("/Game/")) + { + FString RelativePath = FPaths::ProjectContentDir() + TempFolderOverride.Mid(6, TempFolderOverride.Len() - 6); + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + if (!TempFolderOverride.IsEmpty()) + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*TempFolderOverride); + } + + // Check Validity of the path + if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) + { + // Only display a warning if the path is invalid, empty is fine + if(!AbsoluteOverridePath.IsEmpty()) + HOUDINI_LOG_WARNING(TEXT("Invalid override temporary cook path: %s"), *TempFolderOverride); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + TempFolderOverride = HoudiniRuntimeSettings->DefaultTemporaryCookFolder; + } + + // If the TempCookFolder of the HAC is non-empty and is different from the override path. + // do not override it if the current temp cook path is valid. (it was user specified) + if (!HAC->TemporaryCookFolder.Path.IsEmpty() && !HAC->TemporaryCookFolder.Path.Equals(TempFolderOverride)) + return; + + HAC->TemporaryCookFolder.Path = TempFolderOverride; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h index 371e0113f..ffc33156a 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h @@ -1,94 +1,96 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -class UHoudiniOutput; -class UHoudiniAssetComponent; - -struct FHoudiniObjectInfo; -struct FHoudiniGeoInfo; -struct FHoudiniPartInfo; -struct FHoudiniVolumeInfo; -struct FHoudiniCurveInfo; - -enum class EHoudiniOutputType : uint8; -enum class EHoudiniGeoType : uint8; -enum class EHoudiniPartType : uint8; -enum class EHoudiniCurveType : int8; - -struct HOUDINIENGINE_API FHoudiniOutputTranslator -{ - // - static bool UpdateOutputs( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate, - bool& bOutHasHoudiniStaticMeshOutput); - - // - static bool BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies=false); - - // - static bool UpdateLoadedOutputs(UHoudiniAssetComponent* HAC); - - // - static bool UploadChangedEditableOutput( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate); - // - static bool BuildAllOutputs( - const HAPI_NodeId& AssetId, - UObject* InOuterObject, - TArray& InOldOutputs, - TArray& OutNewOutputs, - const bool& InOutputTemplatedGeos); - - static bool UpdateChangedOutputs( - UHoudiniAssetComponent* HAC); - - // Helpers functions used to convert HAPI types - static EHoudiniGeoType ConvertHapiGeoType(const HAPI_GeoType& InType); - static EHoudiniPartType ConvertHapiPartType(const HAPI_PartType& InType); - static EHoudiniCurveType ConvertHapiCurveType(const HAPI_CurveType& InType); - - // Helper functions used to cache HAPI infos - static void CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache); - static void CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache); - static void CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache); - static void CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache); - static void CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache); - - // Helper to clear the outputs of the houdini asset component - static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC); - // Helper to clear an individual UHoudiniOutput - static void ClearOutput(UHoudiniOutput* Output); - - static bool GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName); - static void GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC); - static void GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" + +class UHoudiniOutput; +class UHoudiniAssetComponent; + +struct FHoudiniObjectInfo; +struct FHoudiniGeoInfo; +struct FHoudiniPartInfo; +struct FHoudiniVolumeInfo; +struct FHoudiniCurveInfo; + +enum class EHoudiniOutputType : uint8; +enum class EHoudiniGeoType : uint8; +enum class EHoudiniPartType : uint8; +enum class EHoudiniCurveType : int8; + +struct HOUDINIENGINE_API FHoudiniOutputTranslator +{ + // + static bool UpdateOutputs( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate, + bool& bOutHasHoudiniStaticMeshOutput); + + // + static bool BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies=false); + + // + static bool UpdateLoadedOutputs(UHoudiniAssetComponent* HAC); + + // + static bool UploadChangedEditableOutput( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate); + // + static bool BuildAllOutputs( + const HAPI_NodeId& AssetId, + UObject* InOuterObject, + TArray& InOldOutputs, + TArray& OutNewOutputs, + const bool& InOutputTemplatedGeos); + + static bool UpdateChangedOutputs( + UHoudiniAssetComponent* HAC); + + // Helpers functions used to convert HAPI types + static EHoudiniGeoType ConvertHapiGeoType(const HAPI_GeoType& InType); + static EHoudiniPartType ConvertHapiPartType(const HAPI_PartType& InType); + static EHoudiniCurveType ConvertHapiCurveType(const HAPI_CurveType& InType); + + // Helper functions used to cache HAPI infos + static void CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache); + static void CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache); + static void CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache); + static void CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache); + static void CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache); + + // Helper to clear the outputs of the houdini asset component + static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC); + // Helper to clear an individual UHoudiniOutput + static void ClearOutput(UHoudiniOutput* Output); + + static bool GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName); + static void GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC); + static void GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp index 4111d45ae..c3c416a4b 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp @@ -1,81 +1,106 @@ -#include "HoudiniPDGImporterMessages.h" - - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage() - : FilePath() - , Name() - , TOPNodeId(-1) - , WorkItemId(-1) -{ - -} - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams -) - : FilePath(InFilePath) - , Name(InName) - , TOPNodeId(-1) - , WorkItemId(-1) -{ - SetPackageParams(InPackageParams); -} - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - HAPI_NodeId InTOPNodeId, - HAPI_PDG_WorkitemId InWorkItemId -) - : FilePath(InFilePath) - , Name(InName) - , TOPNodeId(InTOPNodeId) - , WorkItemId(InWorkItemId) -{ - SetPackageParams(InPackageParams); -} - -void FHoudiniPDGImportBGEOMessage::SetPackageParams(const FHoudiniPackageParams& InPackageParams) -{ - PackageParams = InPackageParams; - PackageParams.OuterPackage = nullptr; -} - -void FHoudiniPDGImportBGEOMessage::PopulatePackageParams(FHoudiniPackageParams& OutPackageParams) const -{ - UObject* KeepOuter = OutPackageParams.OuterPackage; - OutPackageParams = PackageParams; - OutPackageParams.OuterPackage = KeepOuter; -} - -FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage() - : ImportResult(EHoudiniPDGImportBGEOResult::HPIBR_Failed) -{ - -} - -FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - const EHoudiniPDGImportBGEOResult& InImportResult -) - : FHoudiniPDGImportBGEOMessage(InFilePath, InName, InPackageParams) - , ImportResult(InImportResult) -{ -} - -FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage() - : CommandletGuid() -{ - -} - -FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid) - : CommandletGuid(InCommandletGuid) -{ - -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGImporterMessages.h" + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage() + : FilePath() + , Name() + , TOPNodeId(-1) + , WorkItemId(-1) +{ + +} + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams +) + : FilePath(InFilePath) + , Name(InName) + , TOPNodeId(-1) + , WorkItemId(-1) +{ + SetPackageParams(InPackageParams); +} + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + HAPI_NodeId InTOPNodeId, + HAPI_PDG_WorkitemId InWorkItemId +) + : FilePath(InFilePath) + , Name(InName) + , TOPNodeId(InTOPNodeId) + , WorkItemId(InWorkItemId) +{ + SetPackageParams(InPackageParams); +} + +void FHoudiniPDGImportBGEOMessage::SetPackageParams(const FHoudiniPackageParams& InPackageParams) +{ + PackageParams = InPackageParams; + PackageParams.OuterPackage = nullptr; +} + +void FHoudiniPDGImportBGEOMessage::PopulatePackageParams(FHoudiniPackageParams& OutPackageParams) const +{ + UObject* KeepOuter = OutPackageParams.OuterPackage; + OutPackageParams = PackageParams; + OutPackageParams.OuterPackage = KeepOuter; +} + +FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage() + : ImportResult(EHoudiniPDGImportBGEOResult::HPIBR_Failed) +{ + +} + +FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + const EHoudiniPDGImportBGEOResult& InImportResult +) + : FHoudiniPDGImportBGEOMessage(InFilePath, InName, InPackageParams) + , ImportResult(InImportResult) +{ +} + +FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage() + : CommandletGuid() +{ + +} + +FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid) + : CommandletGuid(InCommandletGuid) +{ + +} diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h index 0bcb0717f..df3b58f73 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h @@ -1,161 +1,187 @@ -#pragma once - -#include "CoreMinimal.h" -#include "Misc/Guid.h" - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniPackageParams.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInstanceTranslator.h" - -#include "HoudiniPDGImporterMessages.generated.h" - -// Message used to find/discover running commandlets -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEODiscoverMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEODiscoverMessage(); - - FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid); - - // The GUID of the commandlet we are looking for - UPROPERTY() - FGuid CommandletGuid; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEOMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEOMessage(); - - FHoudiniPDGImportBGEOMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams); - - FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - HAPI_NodeId InTOPNodeId, - HAPI_PDG_WorkitemId InWorkItemId); - - void SetPackageParams(const FHoudiniPackageParams& InPackageParams); - - void PopulatePackageParams(FHoudiniPackageParams &OutPackageParams) const; - - // BGEO file path - UPROPERTY() - FString FilePath; - - // PDG work item name - UPROPERTY() - FString Name; - - // TOP/PDG info - // TOP node ID - UPROPERTY() - // HAPI_NodeId TOPNodeId; - int32 TOPNodeId; - - // Work item id - UPROPERTY() - // HAPI_PDG_WorkitemId WorkItemId; - int32 WorkItemId; - - // Package params for the asset - UPROPERTY() - FHoudiniPackageParams PackageParams; -}; - - -UENUM() -enum class EHoudiniPDGImportBGEOResult : uint8 -{ - // Create uassets from the bgeo completely failed. - HPIBR_Failed UMETA(DisplayName="Failed"), - - // Successfully created uassets for all content in the bgeo - HPIBR_Success UMETA(DisplayName = "Success"), - - // Some uassets were created, but there were unsupported objects in the bgeo as well - HPIBR_PartialSuccess UMETA(DisplayName = "Partial Success"), - - HIBPR_MAX -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniGenericAttributes -{ -public: - GENERATED_BODY() - - FHoudiniGenericAttributes() {}; - FHoudiniGenericAttributes(const TArray& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; - FHoudiniGenericAttributes(TArray&& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; - - UPROPERTY() - TArray PropertyAttributes; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutputObject -{ -public: - GENERATED_BODY(); - - UPROPERTY() - FHoudiniOutputObjectIdentifier Identifier; - - UPROPERTY() - FString PackagePath; - - UPROPERTY() - FHoudiniGenericAttributes GenericAttributes; - - UPROPERTY() - TMap CachedAttributes; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutput -{ -public: - GENERATED_BODY(); - - UPROPERTY() - TArray HoudiniGeoPartObjects; - - UPROPERTY() - TArray OutputObjects; - - UPROPERTY() - TArray InstancedOutputPartData; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEOResultMessage : public FHoudiniPDGImportBGEOMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEOResultMessage(); - - FHoudiniPDGImportBGEOResultMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams, const EHoudiniPDGImportBGEOResult& InImportResult); - - void operator=(const FHoudiniPDGImportBGEOMessage& InRHS) { (*static_cast(this)) = InRHS; } - - // Result of the bgeo import -> uassets - UPROPERTY() - EHoudiniPDGImportBGEOResult ImportResult; - - UPROPERTY() - TArray Outputs; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Guid.h" + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniPackageParams.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInstanceTranslator.h" + +#include "HoudiniPDGImporterMessages.generated.h" + +// Message used to find/discover running commandlets +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEODiscoverMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEODiscoverMessage(); + + FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid); + + // The GUID of the commandlet we are looking for + UPROPERTY() + FGuid CommandletGuid; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEOMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEOMessage(); + + FHoudiniPDGImportBGEOMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams); + + FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + HAPI_NodeId InTOPNodeId, + HAPI_PDG_WorkitemId InWorkItemId); + + void SetPackageParams(const FHoudiniPackageParams& InPackageParams); + + void PopulatePackageParams(FHoudiniPackageParams &OutPackageParams) const; + + // BGEO file path + UPROPERTY() + FString FilePath; + + // PDG work item name + UPROPERTY() + FString Name; + + // TOP/PDG info + // TOP node ID + UPROPERTY() + // HAPI_NodeId TOPNodeId; + int32 TOPNodeId; + + // Work item id + UPROPERTY() + // HAPI_PDG_WorkitemId WorkItemId; + int32 WorkItemId; + + // Package params for the asset + UPROPERTY() + FHoudiniPackageParams PackageParams; +}; + + +UENUM() +enum class EHoudiniPDGImportBGEOResult : uint8 +{ + // Create uassets from the bgeo completely failed. + HPIBR_Failed UMETA(DisplayName="Failed"), + + // Successfully created uassets for all content in the bgeo + HPIBR_Success UMETA(DisplayName = "Success"), + + // Some uassets were created, but there were unsupported objects in the bgeo as well + HPIBR_PartialSuccess UMETA(DisplayName = "Partial Success"), + + HIBPR_MAX +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniGenericAttributes +{ +public: + GENERATED_BODY() + + FHoudiniGenericAttributes() {}; + FHoudiniGenericAttributes(const TArray& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; + FHoudiniGenericAttributes(TArray&& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; + + UPROPERTY() + TArray PropertyAttributes; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutputObject +{ +public: + GENERATED_BODY(); + + UPROPERTY() + FHoudiniOutputObjectIdentifier Identifier; + + UPROPERTY() + FString PackagePath; + + UPROPERTY() + FHoudiniGenericAttributes GenericAttributes; + + UPROPERTY() + TMap CachedAttributes; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutput +{ +public: + GENERATED_BODY(); + + UPROPERTY() + TArray HoudiniGeoPartObjects; + + UPROPERTY() + TArray OutputObjects; + + UPROPERTY() + TArray InstancedOutputPartData; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEOResultMessage : public FHoudiniPDGImportBGEOMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEOResultMessage(); + + FHoudiniPDGImportBGEOResultMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams, const EHoudiniPDGImportBGEOResult& InImportResult); + + void operator=(const FHoudiniPDGImportBGEOMessage& InRHS) { (*static_cast(this)) = InRHS; } + + // Result of the bgeo import -> uassets + UPROPERTY() + EHoudiniPDGImportBGEOResult ImportResult; + + UPROPERTY() + TArray Outputs; + +}; diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp index 89af09131..01ad03181 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp @@ -1,2057 +1,2061 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGManager.h" - -#include "Modules/ModuleManager.h" -#include "MessageEndpointBuilder.h" -#include "HAL/FileManager.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPackageParams.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniPDGTranslator.h" -#include "HoudiniPDGImporterMessages.h" - -#include "HAPI/HAPI_Common.h" - -HOUDINI_PDG_DEFINE_LOG_CATEGORY(); - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniPDGManager::FHoudiniPDGManager() -{ -} - -FHoudiniPDGManager::~FHoudiniPDGManager() -{ -} - -bool -FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) -{ - if (!InHAC || InHAC->IsPendingKill()) - return false; - - int32 AssetId = InHAC->GetAssetId(); - if (AssetId < 0) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) - return false; - - // Create a new PDG Asset Link Object - bool bRegisterPDGAssetLink = false; - UHoudiniPDGAssetLink* PDGAssetLink = InHAC->GetPDGAssetLink(); - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - { - PDGAssetLink = NewObject(InHAC, UHoudiniPDGAssetLink::StaticClass(), NAME_None, RF_Transactional); - bRegisterPDGAssetLink = true; - } - - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - PDGAssetLink->AssetID = AssetId; - - // Get the HDA's info - HAPI_NodeInfo AssetInfo; - FHoudiniApi::NodeInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, &AssetInfo), false); - - // Get the node path - FString AssetNodePath; - FHoudiniEngineString::ToFString(AssetInfo.internalNodePathSH, PDGAssetLink->AssetNodePath); - - // Get the node name - FString AssetName; - FHoudiniEngineString::ToFString(AssetInfo.nameSH, PDGAssetLink->AssetName); - - const bool bZeroWorkItemTallys = true; - if (!FHoudiniPDGManager::PopulateTOPNetworks(PDGAssetLink, bZeroWorkItemTallys)) - { - // We couldn't find any valid TOPNet/TOPNode, this is not a PDG Asset - // Make sure the HDA doesn't have a PDGAssetLink - InHAC->SetPDGAssetLink(nullptr); - return false; - } - - // If the PDG asset link comes from a loaded asset, we also need to register it - if (InHAC->HasBeenLoaded()) - { - bRegisterPDGAssetLink = true; - } - - // We have found valid TOPNetworks and TOPNodes, - // This is a PDG HDA, so add the AssetLink to it - PDGAssetLink->LinkState = EPDGLinkState::Linked; - - if (PDGAssetLink->SelectedTOPNetworkIndex < 0) - PDGAssetLink->SelectedTOPNetworkIndex = 0; - - InHAC->SetPDGAssetLink(PDGAssetLink); - - if (bRegisterPDGAssetLink) - { - // Register this PDG Asset Link to the PDG Manager - TWeakObjectPtr AssetLinkPtr(PDGAssetLink); - PDGAssetLinks.Add(AssetLinkPtr); - } - - // If the commandlet is enabled, check if we have started and established communication with the commandlet yet - // if not, try to start the commandlet - bool bCommandletIsEnabled = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (IsValid(HoudiniRuntimeSettings)) - { - bCommandletIsEnabled = HoudiniRuntimeSettings->bPDGAsyncCommandletImportEnabled; - } - - if (bCommandletIsEnabled) - { - const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); - if (CommandletStatus == EHoudiniBGEOCommandletStatus::NotStarted && bCommandletIsEnabled) - { - CreateBGEOCommandletAndEndpoint(); - } - } - - return true; -} - -bool -FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) -{ - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - // If the PDG Asset link is inactive, indicate that our HDA must be instantiated - if (PDGAssetLink->LinkState == EPDGLinkState::Inactive) - { - UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); - if(!ParentHAC) - { - // No valid parent HAC, error! - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("No valid Houdini Asset Component parent for PDG Asset Link!")); - } - else if (ParentHAC && ParentHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) - { - PDGAssetLink->LinkState = EPDGLinkState::Linking; - ParentHAC->AssetState = EHoudiniAssetState::PreInstantiation; - } - else - { - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("Unable to link the PDG Asset link! Try to rebuild the HDA.")); - } - } - - if (PDGAssetLink->LinkState == EPDGLinkState::Linking) - { - return true; - } - - if (PDGAssetLink->LinkState != EPDGLinkState::Linked) - { - UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); - int32 AssetId = ParentHAC->GetAssetId(); - if (AssetId < 0) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) - return false; - - PDGAssetLink->AssetID = AssetId; - } - - if(!PopulateTOPNetworks(PDGAssetLink)) - { - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("Failed to populte the PDG Asset Link.")); - return false; - } - - return true; -} - - -bool -FHoudiniPDGManager::PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys) -{ - // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - // Get all the network nodes within the asset, recursively. - // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type - int32 NetworkNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); - - if (NetworkNodeCount <= 0) - return false; - - TArray AllNetworkNodeIDs; - AllNetworkNodeIDs.SetNum(NetworkNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); - - // There is currently no way to only get non bypassed nodes via HAPI - // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... - TArray AllBypassedTOPNetNodeIDs; - { - int32 BypassedTOPNetNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); - - if (BypassedTOPNetNodeCount > 0) - { - // Get the list of all bypassed TOP Net... - AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); - - // ... and remove them from the network list - for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) - AllNetworkNodeIDs.RemoveAt(Idx); - } - } - } - - - // For each Network we found earlier, only add those with TOP child nodes - // Therefore guaranteeing that we only add TOP networks - TArray AllTOPNetworks; - for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) - { - if (CurrentNodeId < 0) - { - continue; - } - - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) - { - continue; - } - - // Skip non TOP or SOP networks - if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP - && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) - { - continue; - } - - // Check that this TOP Net is not nested in another TOP Net... - // This will happen with ROP Geometry TOPs for example... - bool bIsNestedInTOPNet = false; - HAPI_NodeId CurrentParentId = CurrentNodeInfo.parentId; - while (CurrentParentId > 0) - { - if (AllNetworkNodeIDs.Contains(CurrentParentId)) - { - bIsNestedInTOPNet = true; - break; - } - - if(AllBypassedTOPNetNodeIDs.Contains(CurrentParentId)) - { - bIsNestedInTOPNet = true; - break; - } - - HAPI_NodeInfo ParentNodeInfo; - FHoudiniApi::NodeInfo_Init(&ParentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentParentId, &ParentNodeInfo)) - { - break; - } - - // Get our parent's parent - CurrentParentId = ParentNodeInfo.parentId; - } - - // Skip nested TOP nets - if (bIsNestedInTOPNet) - continue; - - // Get the list of all TOP nodes within the current network (ignoring schedulers) - int32 TOPNodeCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) - { - continue; - } - - TArray AllTOPNodeIDs; - AllTOPNodeIDs.SetNum(TOPNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, AllTOPNodeIDs.GetData(), TOPNodeCount), false); - - // Skip networks without TOP nodes - if (AllTOPNodeIDs.Num() <= 0) - { - continue; - } - - // Since there is currently no way to get only non-bypassed nodes via HAPI - // we need to get a list of all the bypassed top nodes to remove them from the previous list - { - int32 BypassedTOPNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_TOP_NONSCHEDULER | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNodeCount), false); - - if (BypassedTOPNodeCount > 0) - { - // Get the list of all bypassed TOP Nodes... - TArray AllBypassedTOPNodes; - AllBypassedTOPNodes.SetNum(BypassedTOPNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - AllBypassedTOPNodes.GetData(), BypassedTOPNodeCount), false); - - // ... and remove them from the top node list - for (int32 Idx = AllTOPNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNodes.Contains(AllTOPNodeIDs[Idx])) - AllTOPNodeIDs.RemoveAt(Idx); - } - } - } - - // TODO: - // Apply the show and output filter on that node - bool bShow = true; - - // Get the node path - FString CurrentNodePath; - FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, PDGAssetLink->AssetID, CurrentNodePath); - - // Get the node name - FString CurrentNodeName; - FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, CurrentNodeName); - - UTOPNetwork* CurrentTOPNetwork = nullptr; - int32 FoundTOPNetIndex = INDEX_NONE; - UTOPNetwork* FoundTOPNet = PDGAssetLink->GetTOPNetworkByNodePath(CurrentNodeName, PDGAssetLink->AllTOPNetworks, FoundTOPNetIndex); - if (IsValid(FoundTOPNet)) - { - // Reuse the existing corresponding TOP NET - CurrentTOPNetwork = FoundTOPNet; - PDGAssetLink->AllTOPNetworks.RemoveAt(FoundTOPNetIndex); - } - else - { - // Create a new instance for the TOP NET - CurrentTOPNetwork = NewObject(PDGAssetLink, UTOPNetwork::StaticClass(), NAME_None, RF_Transactional); - } - - // Update the TOP NET - CurrentTOPNetwork->NodeId = CurrentNodeId; - CurrentTOPNetwork->NodeName = CurrentNodeName; - CurrentTOPNetwork->NodePath = CurrentNodePath; - CurrentTOPNetwork->ParentName = PDGAssetLink->AssetName; - CurrentTOPNetwork->bShowResults = bShow; - - // Only add network that have valid TOP Nodes - if (PopulateTOPNodes(AllTOPNodeIDs, CurrentTOPNetwork, PDGAssetLink, bInZeroWorkItemTallys)) - { - // See if we need to select a new TOP node - bool bReselectValidTOP = false; - if (CurrentTOPNetwork->SelectedTOPIndex < 0) - bReselectValidTOP = true; - else if (!CurrentTOPNetwork->AllTOPNodes.IsValidIndex(CurrentTOPNetwork->SelectedTOPIndex)) - bReselectValidTOP = true; - else if (!IsValid(CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]) || - CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]->bHidden) - bReselectValidTOP = true; - - if (bReselectValidTOP) - { - // Select the first valid TOP node (not hidden) - for (int Idx = 0; Idx < CurrentTOPNetwork->AllTOPNodes.Num(); Idx++) - { - UTOPNode *TOPNode = CurrentTOPNetwork->AllTOPNodes[Idx]; - if (!IsValid(TOPNode) || TOPNode->bHidden) - continue; - - CurrentTOPNetwork->SelectedTOPIndex = Idx; - break; - } - } - - AllTOPNetworks.Add(CurrentTOPNetwork); - } - } - - // Clear previous TOP networks, nodes and generated data - for (UTOPNetwork* CurrentTOPNet : PDGAssetLink->AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - PDGAssetLink->ClearTOPNetworkWorkItemResults(CurrentTOPNet); - } - //PDGAssetLink->ClearAllTOPData(); - PDGAssetLink->AllTOPNetworks = AllTOPNetworks; - - return (AllTOPNetworks.Num() > 0); -} - - -bool -FHoudiniPDGManager::PopulateTOPNodes( - const TArray& InTopNodeIDs, UTOPNetwork* InTOPNetwork, UHoudiniPDGAssetLink* InPDGAssetLink, bool bInZeroWorkItemTallys) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InTOPNetwork)) - return false; - - // - int32 TOPNodeCount = 0; - - // Holds list of found TOP nodes - TArray AllTOPNodes; - for(const HAPI_NodeId& CurrentTOPNodeID : InTopNodeIDs) - { - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentTOPNodeID, &CurrentNodeInfo)) - { - continue; - } - - // Increase the number of valid TOP Node - // (before applying the node filter) - TOPNodeCount++; - - // Get the node path - FString NodePath; - FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, InTOPNetwork->NodeId, NodePath); - - // Get the node name - FString NodeName; - FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, NodeName); - - // See if we can find an existing version of this TOPNOde - UTOPNode* CurrentTOPNode = nullptr; - int32 FoundNodeIndex = INDEX_NONE; - UTOPNode* FoundNode = InPDGAssetLink->GetTOPNodeByNodePath(NodePath, InTOPNetwork->AllTOPNodes, FoundNodeIndex); - if (IsValid(FoundNode)) - { - CurrentTOPNode = FoundNode; - InTOPNetwork->AllTOPNodes.RemoveAt(FoundNodeIndex); - - if (bInZeroWorkItemTallys) - { - CurrentTOPNode->WorkItemTally.ZeroAll(); - CurrentTOPNode->NodeState = EPDGNodeState::None; - } - } - else - { - // Didn't find an existing UTOPNode for this node, create a new UTOPNode instance - CurrentTOPNode = NewObject(InPDGAssetLink, UTOPNode::StaticClass(), NAME_None, RF_Transactional); - } - - CurrentTOPNode->NodeId = CurrentTOPNodeID; - CurrentTOPNode->NodeName = NodeName; - CurrentTOPNode->NodePath = NodePath; - CurrentTOPNode->ParentName = InTOPNetwork->ParentName + TEXT("_") + InTOPNetwork->NodeName; - CurrentTOPNode->bHasChildNodes = CurrentNodeInfo.childNodeCount > 0; - - // Filter display/autoload using name - CurrentTOPNode->bHidden = false; - if (InPDGAssetLink->bUseTOPNodeFilter && !InPDGAssetLink->TOPNodeFilter.IsEmpty()) - { - // Only display nodes that matches the filter - if (!NodeName.StartsWith(InPDGAssetLink->TOPNodeFilter)) - CurrentTOPNode->bHidden = true; - } - - // Automatically load results for nodes that match the filter - if (InPDGAssetLink->bUseTOPOutputFilter) - { - bool bAutoLoad = false; - if (InPDGAssetLink->TOPOutputFilter.IsEmpty()) - bAutoLoad = true; - else if (NodeName.StartsWith(InPDGAssetLink->TOPOutputFilter)) - bAutoLoad = true; - - CurrentTOPNode->bAutoLoad = bAutoLoad; - - // Show autoloaded results by default - CurrentTOPNode->SetVisibleInLevel(bAutoLoad); - } - - AllTOPNodes.Add(CurrentTOPNode); - } - - for (UTOPNode* CurTOPNode : InTOPNetwork->AllTOPNodes) - { - if (!IsValid(CurTOPNode)) - continue; - - InPDGAssetLink->ClearTOPNodeWorkItemResults(CurTOPNode); - } - - InTOPNetwork->AllTOPNodes = AllTOPNodes; - - return (TOPNodeCount > 0); -} - - -void -FHoudiniPDGManager::DirtyTOPNode(UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - // Dirty the specified TOP node... - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( - FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, true)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *(InTOPNode->NodeName)); - } - - // ... and clear its work item results. - UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(InTOPNode); -} - -// void -// FHoudiniPDGManager::DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode) -// { -// // Dirty the specified TOP node... -// if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( -// FHoudiniEngine::Get().GetSession(), InTOPNode.NodeId, true)) -// { -// HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *InTOPNode.NodeName); -// } -// } - -void -FHoudiniPDGManager::CookTOPNode(UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - if (InTOPNode->NodeState == EPDGNodeState::Cooking || InTOPNode->AnyWorkItemsPending()) - { - HOUDINI_LOG_WARNING(TEXT("PDG Cook TOP Node - %s is already/still cooking, ignoring 'Cook TOP Node' request."), *(InTOPNode->NodePath)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( - FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, 0, 0)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook TOP Node - Failed to cook %s!"), *(InTOPNode->NodeName)); - } -} - - -void -FHoudiniPDGManager::DirtyAll(UTOPNetwork* InTOPNet) -{ - if (!IsValid(InTOPNet)) - return; - - // Dirty the specified TOP network... - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, true)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Dirty All - Failed to dirty all of %s's TOP nodes!"), *(InTOPNet->NodeName)); - return; - } - - // ... and clear its work item results. - UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(InTOPNet); -} - - -void -FHoudiniPDGManager::CookOutput(UTOPNetwork* InTOPNet) -{ - // Cook the output TOP node of the currently selected TOP network. - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - bool bAlreadyCooking = InTOPNet->AnyWorkItemsPending(); - - if (!bAlreadyCooking) - { - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - int32 PDGState = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( - FHoudiniEngine::Get().GetSession(), GraphContextId, &PDGState)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's PDG state."), *(InTOPNet->NodeName)); - return; - } - bAlreadyCooking = ((HAPI_PDG_State) PDGState == HAPI_PDG_STATE_COOKING); - } - - if (bAlreadyCooking) - { - HOUDINI_LOG_WARNING(TEXT("PDG Cook Output - %s is already/still cooking, ignoring 'Cook Output' request."), *(InTOPNet->NodeName)); - return; - } - - // TODO: ??? - // Cancel all cooks. This is required as otherwise the graph gets into an infinite cook state (bug?) - if(HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, 0, 0)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to cook %s's output!"), *(InTOPNet->NodeName)); - } -} - - -void -FHoudiniPDGManager::PauseCook(UTOPNetwork* InTOPNet) -{ - // Pause the PDG cook of the currently selected TOP network - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::PausePDGCook( - FHoudiniEngine::Get().GetSession(), GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to pause %s!"), *(InTOPNet->NodeName)); - return; - } -} - - -void -FHoudiniPDGManager::CancelCook(UTOPNetwork* InTOPNet) -{ - // Cancel the PDG cook of the currently selected TOP network - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CancelPDGCook( - FHoudiniEngine::Get().GetSession(), GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to cancel cook for %s!"), *(InTOPNet->NodeName)); - return; - } -} - -void -FHoudiniPDGManager::Update() -{ - // Clean up registered PDG Asset Links - for(int32 Idx = PDGAssetLinks.Num() - 1; Idx >= 0; Idx--) - { - TWeakObjectPtr Ptr = PDGAssetLinks[Idx]; - if (!Ptr.IsValid() || Ptr.IsStale()) - { - PDGAssetLinks.RemoveAt(Idx); - continue; - } - - UHoudiniPDGAssetLink* CurPDGAssetLink = PDGAssetLinks[Idx].Get(); - if (!CurPDGAssetLink || CurPDGAssetLink->IsPendingKill()) - { - PDGAssetLinks.RemoveAt(Idx); - continue; - } - } - - // Do nothing if we dont have any valid PDG asset Link - if (PDGAssetLinks.Num() <= 0) - return; - - // Update the PDG contexts and handle all pdg events and work item status updates - UpdatePDGContexts(); - - // Prcoess any workitem result if we have any - ProcessWorkItemResults(); -} - -// Query all the PDG graph context in the current Houdini Engine session. -// Handle PDG events, work item status updates. -// Forward relevant events to PDGAssetLink objects. -void -FHoudiniPDGManager::UpdatePDGContexts() -{ - // Get current PDG graph contexts - ReinitializePDGContext(); - - // Process next set of events for each graph context - if (PDGContextIDs.Num() > 0) - { - // Only initialize event array if not valid, or user resized max size - if(PDGEventInfos.Num() != MaxNumberOfPDGEvents) - PDGEventInfos.SetNum(MaxNumberOfPDGEvents); - - // TODO: member? - //HAPI_PDG_State PDGState; - for(const HAPI_PDG_GraphContextId& CurrentContextID : PDGContextIDs) - { - /* - // TODO: No need to reset events at each tick - int32 PDGStateInt; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( - FHoudiniEngine::Get().GetSession(), CurrentContextID, &PDGStateInt)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get PDG state")); - continue; - } - - PDGState = (HAPI_PDG_State)PDGStateInt; - - for (int32 Idx = 0; Idx < PDGEventInfos.Num(); Idx++) - { - ResetPDGEventInfo(PDGEventInfos[Idx]); - } - */ - - int32 PDGEventCount = 0; - int32 RemainingPDGEventCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGEvents( - FHoudiniEngine::Get().GetSession(), CurrentContextID, PDGEventInfos.GetData(), - MaxNumberOfPDGEvents, &PDGEventCount, &RemainingPDGEventCount)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get PDG events")); - continue; - } - - if (PDGEventCount < 1) - continue; - - for (int32 EventIdx = 0; EventIdx < PDGEventCount; EventIdx++) - { - ProcessPDGEvent(CurrentContextID, PDGEventInfos[EventIdx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("PDG: Tick processed %d events, %d remaining."), PDGEventCount, RemainingPDGEventCount); - } - } - - // Refresh UI if necessary - for (auto CurAssetLink : PDGAssetLinks) - { - UHoudiniPDGAssetLink* AssetLink = CurAssetLink.Get(); - if (AssetLink) - { - if (AssetLink->bNeedsUIRefresh) - { - FHoudiniPDGManager::RefreshPDGAssetLinkUI(AssetLink); - AssetLink->bNeedsUIRefresh = false; - } - else - { - AssetLink->UpdateWorkItemTally(); - } - } - } -} - -// Query the currently active PDG graph contexts in the Houdini Engine session. -// Should be done each time to get latest set of graph contexts. -void -FHoudiniPDGManager::ReinitializePDGContext() -{ - int32 NumContexts = 0; - - PDGContextNames.SetNum(MaxNumberOPDGContexts); - PDGContextIDs.SetNum(MaxNumberOPDGContexts); - - if(HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContexts( - FHoudiniEngine::Get().GetSession(), - &NumContexts, PDGContextNames.GetData(), PDGContextIDs.GetData(), MaxNumberOPDGContexts) || NumContexts <= 0) - { - PDGContextNames.SetNum(0); - PDGContextIDs.SetNum(0); - return; - } - - if(PDGContextIDs.Num() != NumContexts) - PDGContextIDs.SetNum(NumContexts); - - if (PDGContextNames.Num() != NumContexts) - PDGContextNames.SetNum(NumContexts); -} - -// Process a PDG event. Notify the relevant PDGAssetLink object. -void -FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo) -{ - UHoudiniPDGAssetLink* PDGAssetLink = nullptr; - UTOPNode* TOPNode = nullptr; - - HAPI_PDG_EventType EventType = (HAPI_PDG_EventType)EventInfo.eventType; - HAPI_PDG_WorkitemState CurrentWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.currentState; - HAPI_PDG_WorkitemState LastWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.lastState; - - // Debug: get the HAPI_PDG_EventType as a string - const FString EventName = FHoudiniEngineUtils::HapiGetEventTypeAsString(EventType); - const FString CurrentWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(CurrentWorkItemState); - const FString LastWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(LastWorkItemState); - - if(!GetTOPAssetLinkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNode) - || PDGAssetLink == nullptr || PDGAssetLink->IsPendingKill() - || TOPNode == nullptr || TOPNode->IsPendingKill() - || TOPNode->NodeId != EventInfo.nodeId) - { - - HOUDINI_LOG_WARNING(TEXT("[ProcessPDGEvent]: Could not find matching TOPNode for event %s, workitem id %d, node id %d"), *EventName, EventInfo.workitemId, EventInfo.nodeId); - return; - } - - HOUDINI_PDG_MESSAGE( - TEXT("[ProcessPDGEvent]: TOPNode: %s, WorkItem ID: %d, Event Type: %s, Current State: %s, Last State %s"), - *(TOPNode->NodePath), EventInfo.workitemId, *EventName, *CurrentWorkitemStateName, *LastWorkitemStateName); - - FLinearColor MsgColor = FLinearColor::White; - - bool bUpdatePDGNodeState = false; - switch (EventType) - { - case HAPI_PDG_EVENT_NULL: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::None); - break; - - case HAPI_PDG_EVENT_NODE_CLEAR: - NotifyTOPNodePDGStateClear(PDGAssetLink, TOPNode); - break; - - case HAPI_PDG_EVENT_WORKITEM_ADD: - bUpdatePDGNodeState = true; - NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, 1); - break; - - case HAPI_PDG_EVENT_WORKITEM_REMOVE: - RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, TOPNode); - bUpdatePDGNodeState = true; - NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, -1); - break; - - case HAPI_PDG_EVENT_COOK_WARNING: - MsgColor = FLinearColor::Yellow; - break; - - case HAPI_PDG_EVENT_COOK_ERROR: - MsgColor = FLinearColor::Red; - break; - - case HAPI_PDG_EVENT_COOK_COMPLETE: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); - break; - - case HAPI_PDG_EVENT_DIRTY_START: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtying); - break; - - case HAPI_PDG_EVENT_DIRTY_STOP: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtied); - break; - - case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: - { - // Last states - bUpdatePDGNodeState = true; - if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) - { - NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, -1); - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) - { - NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, -1); - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) - { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); - } - else if ( (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE || LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) - && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) - { - // Handled previously cooked WI - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, -1); - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) - { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); - } - else - { - // TODO: - // unhandled state change - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); - } - - if (LastWorkItemState == CurrentWorkItemState) - { - // TODO: - // Not a change!! shouldnt happen! - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); - } - - // New states - if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) - { - NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, 1); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNCOOKED) - { - - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_DIRTY) - { - // ClearWorkItemResult(InContextID, EventInfo, *TOPNode); - ClearWorkItemResult(PDGAssetLink, EventInfo.workitemId, TOPNode); - // RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, *TOPNode); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) - { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, 1); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) - { - NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, 1); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS - || CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE) - { - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 1); - - // On cook success, handle results - CreateWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) - { - // TODO: on cook failure, get log path? - NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, 1); - MsgColor = FLinearColor::Red; - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CANCEL) - { - // Ignore it because in-progress cooks can be cancelled when automatically recooking graph - } - } - break; - - // Unhandled events - case HAPI_PDG_EVENT_DIRTY_ALL: - case HAPI_PDG_EVENT_COOK_START: - case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: - case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: - case HAPI_PDG_EVENT_UI_SELECT: - case HAPI_PDG_EVENT_NODE_CREATE: - case HAPI_PDG_EVENT_NODE_REMOVE: - case HAPI_PDG_EVENT_NODE_RENAME: - case HAPI_PDG_EVENT_NODE_CONNECT: - case HAPI_PDG_EVENT_NODE_DISCONNECT: - case HAPI_PDG_EVENT_WORKITEM_SET_INT: - case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: - case HAPI_PDG_EVENT_WORKITEM_SET_STRING: - case HAPI_PDG_EVENT_WORKITEM_SET_FILE: - case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: - case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: - case HAPI_PDG_EVENT_WORKITEM_RESULT: - case HAPI_PDG_EVENT_WORKITEM_PRIORITY: - case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: - case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: - case HAPI_PDG_EVENT_ALL: - case HAPI_PDG_EVENT_LOG: - case HAPI_PDG_CONTEXT_EVENTS: - break; - } - - if (bUpdatePDGNodeState) - { - // Work item events - EPDGNodeState CurrentTOPNodeState = TOPNode->NodeState; - if (CurrentTOPNodeState == EPDGNodeState::Cooking) - { - if (TOPNode->AreAllWorkItemsComplete()) - { - if (TOPNode->AnyWorkItemsFailed()) - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); - } - else - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); - } - } - } - else if (TOPNode->AnyWorkItemsPending()) - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cooking); - } - } - - if (EventInfo.msgSH >= 0) - { - FString EventMsg; - FHoudiniEngineString::ToFString(EventInfo.msgSH, EventMsg); - if (!EventMsg.IsEmpty()) - { - // TODO: Event MSG? - // Somehow update the PDG event msg UI ?? - // Simply log for now... - if (MsgColor == FLinearColor::Red) - { - HOUDINI_LOG_ERROR(TEXT("%s"), *EventMsg); - } - else if (MsgColor == FLinearColor::Yellow) - { - HOUDINI_LOG_WARNING(TEXT("%s"), *EventMsg); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT("%s"), *EventMsg); - } - } - } -} - -void -FHoudiniPDGManager::ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo) -{ - InEventInfo.nodeId = -1; - InEventInfo.workitemId = -1; - InEventInfo.dependencyId = -1; - InEventInfo.currentState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; - InEventInfo.lastState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; - InEventInfo.eventType = HAPI_PDG_EventType::HAPI_PDG_EVENT_NULL; -} - - -bool -FHoudiniPDGManager::GetTOPAssetLinkAndNode( - const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode) -{ - // Returns the PDGAssetLink and FTOPNode data associated with this TOP node ID - OutAssetLink = nullptr; - OutTOPNode = nullptr; - for (TWeakObjectPtr& CurAssetLinkPtr : PDGAssetLinks) - { - if (!CurAssetLinkPtr.IsValid() || CurAssetLinkPtr.IsStale()) - continue; - - UHoudiniPDGAssetLink* CurAssetLink = CurAssetLinkPtr.Get(); - if (!CurAssetLink || CurAssetLink->IsPendingKill()) - continue; - - OutTOPNode = CurAssetLink->GetTOPNode((int32)InNodeID); - - if (OutTOPNode != nullptr) - { - OutAssetLink = CurAssetLink; - return true; - } - } - - return false; -} - -void -FHoudiniPDGManager::SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->NodeState = InPDGState; - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - //Debug.LogFormat("NotifyTOPNodePDGStateClear:: {0}", topNode._nodeName); - InTOPNode->NodeState = EPDGNodeState::None; - InTOPNode->WorkItemTally.ZeroAll(); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ZeroAll"), *(InTOPNode->NodePath)); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); - -} - -void -FHoudiniPDGManager::NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.TotalWorkItems = FMath::Max(InTOPNode->WorkItemTally.TotalWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally TotalWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.CookedWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookedWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.ErroredWorkItems = FMath::Max(InTOPNode->WorkItemTally.ErroredWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.WaitingWorkItems = FMath::Max(InTOPNode->WorkItemTally.WaitingWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.ScheduledWorkItems = FMath::Max(InTOPNode->WorkItemTally.ScheduledWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->WorkItemTally.CookingWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookingWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // TODO!!! - // Clear all work items' results for the specified TOP node. - // This destroys any loaded results (geometry etc). - //session.LogErrorOverride = false; - InAssetLink->ClearWorkItemResultByID(InWorkItemID, InTOPNode); - // session.LogErrorOverride = true; -} - -void -FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // Clear all of the work item's results for the specified TOP node and also remove the work item itself from - // the TOP node. - InAssetLink->DestroyWorkItemByID(InWorkItemID, InTOPNode); -} - -void -FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // Only update the editor properties if the PDG asset link's Actor is selected - // else, just update the workitemtally - InAssetLink->UpdateWorkItemTally(); - - UHoudiniAssetComponent* HAC = Cast(InAssetLink->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor* ActorOwner = HAC->GetOwner(); - if (ActorOwner != nullptr && ActorOwner->IsSelected()) - { - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - } -} - -void -FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - if (bSuccess) - { - if (InAssetLink->LinkState == EPDGLinkState::Linked) - { - if (InAssetLink->bAutoCook) - { - FHoudiniPDGManager::CookOutput(InAssetLink->GetSelectedTOPNetwork()); - } - } - else - { - UpdatePDGAssetLink(InAssetLink); - } - } - else - { - InAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - } -} - -bool -FHoudiniPDGManager::CreateWorkItemResult( - UTOPNode* InTOPNode, - const HAPI_PDG_GraphContextId& InContextID, - HAPI_PDG_WorkitemId InWorkItemID, - bool bInLoadResultObjects) -{ - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); - return false; - } - - HAPI_PDG_WorkitemInfo WorkItemInfo; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( - FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); - // TODO? continue? - return false; - } - - if (WorkItemInfo.numResults > 0) - { - TArray ResultInfos; - ResultInfos.SetNum(WorkItemInfo.numResults); - const int32 resultCount = WorkItemInfo.numResults; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemResultInfo( - FHoudiniEngine::Get().GetSession(), - InTOPNode->NodeId, InWorkItemID, ResultInfos.GetData(), resultCount)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d result info for %s"), InWorkItemID, *(InTOPNode->NodeName)); - // TODO? continue? - return false; - } - - FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); - if (!WorkResult) - { - FTOPWorkResult LocalWorkResult; - LocalWorkResult.WorkItemID = InWorkItemID; - LocalWorkResult.WorkItemIndex = WorkItemInfo.index; - const int32 Idx = InTOPNode->WorkResult.Add(LocalWorkResult); - WorkResult = &(InTOPNode->WorkResult[Idx]); - } - - FString WorkItemName; - FHoudiniEngineString::ToFString(WorkItemInfo.nameSH, WorkItemName); - - // Load each result geometry - const int32 NumResults = ResultInfos.Num(); - for (int32 Idx = 0; Idx < NumResults; Idx++) - { - const HAPI_PDG_WorkitemResultInfo& ResultInfo = ResultInfos[Idx]; - if (ResultInfo.resultTagSH <= 0 || ResultInfo.resultSH <= 0) - continue; - - FString CurrentTag; - FHoudiniEngineString::ToFString(ResultInfo.resultTagSH, CurrentTag); - if(CurrentTag.IsEmpty() || !CurrentTag.StartsWith(TEXT("file"))) - continue; - - FString CurrentPath = FString(); - FHoudiniEngineString::ToFString(ResultInfo.resultSH, CurrentPath); - - // Construct the name and look for an existing work result, re-use it if found, otherwise create a new one - const FString WorkResultName = FString::Printf( - TEXT("%s_%s_%d"), - *(InTOPNode->ParentName), - *WorkItemName, - WorkItemInfo.index); - - FTOPWorkResultObject* ExistingResultObject = WorkResult->ResultObjects.FindByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) - { - return InResultObject.Name == WorkResultName; - }); - if (ExistingResultObject) - { - ExistingResultObject->FilePath = CurrentPath; - if (ExistingResultObject->State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) - { - ExistingResultObject->State = EPDGWorkResultState::ToDelete; - } - else if ((ExistingResultObject->State == EPDGWorkResultState::Loaded || - ExistingResultObject->State == EPDGWorkResultState::ToDelete || - ExistingResultObject->State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) - { - // When the commandlet is not being used, we could leave the outputs in place and have - // translators try to re-use objects/components. When the commandlet is being used, the packages - // are always saved and standalone, so if we want to automatically clean up old results then we - // need to destroy the existing outputs - if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - ExistingResultObject->DestroyResultOutputs(); - ExistingResultObject->State = EPDGWorkResultState::ToLoad; - } - else - { - ExistingResultObject->State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; - } - } - else - { - FTOPWorkResultObject ResultObj; - ResultObj.Name = WorkResultName; - ResultObj.FilePath = CurrentPath; - ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; - - WorkResult->ResultObjects.Add(ResultObj); - } - } - } - - return true; -} - -void -FHoudiniPDGManager::ProcessWorkItemResults() -{ - const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); - for (auto& CurrentPDGAssetLink : PDGAssetLinks) - { - // Iterate through all PDG Asset Link - UHoudiniPDGAssetLink* AssetLink = CurrentPDGAssetLink.Get(); - if (!AssetLink) - continue; - - // Set up package parameters to: - // Cook to temp houdini engine directory - // and if the PDG asset link is associated with a Houdini Asset Component (HAC): - // set the outer package to the HAC - // set the HoudiniAssetName according to the HAC - // set the ComponentGUID according to the HAC - // otherwise we set the outer to the asset link's parent and leave naming and GUID blank - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - // AActor* ParentActor = nullptr; - UObject* AssetLinkParent = AssetLink->GetOuter(); - UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; - if (HAC) - { - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - - // ParentActor = HAC->GetOwner(); - } - else - { - PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); - PackageParams.HoudiniAssetName = FString(); - PackageParams.HoudiniAssetActorName = FString(); - // PackageParams.ComponentGUID = HAC->GetComponentGUID(); - - // // Try to find a parent actor - // UObject* Parent = AssetLinkParent; - // while (Parent && !ParentActor) - // { - // ParentActor = Cast(Parent); - // if (!ParentActor) - // Parent = ParentActor->GetOuter(); - // } - } - PackageParams.ObjectName = FString(); - - // UWorld *World = ParentActor ? ParentActor->GetWorld() : AssetLink->GetWorld(); - UWorld *World = AssetLink->GetWorld(); - - // .. All TOP Nets - for (UTOPNetwork* CurrentTOPNet : AssetLink->AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - // .. All TOP Nodes - for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - // ... All WorkResult - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; - CurrentTOPNode->bCachedHaveLoadedWorkResults = false; - for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) - { - // ... All WorkResultObjects - for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) - { - if (CurrentWorkResultObj.State == EPDGWorkResultState::ToLoad) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Loading; - - // Load this WRObj - PackageParams.PDGTOPNetworkName = CurrentTOPNet->NodeName; - PackageParams.PDGTOPNodeName = CurrentTOPNode->NodeName; - PackageParams.PDGWorkItemIndex = CurrentWorkResult.WorkItemIndex; - - if (CommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - { - BGEOCommandletEndpoint->Send(new FHoudiniPDGImportBGEOMessage( - CurrentWorkResultObj.FilePath, - CurrentWorkResultObj.Name, - PackageParams, - CurrentTOPNode->NodeId, - CurrentWorkResult.WorkItemID - ), BGEOCommandletAddress); - } - else - { - if (FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - AssetLink, - CurrentTOPNode, - CurrentWorkResultObj, - PackageParams)) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Loaded; - CurrentTOPNode->bCachedHaveLoadedWorkResults = true; - - // Broadcast that we have loaded the work result object to those interested - AssetLink->OnWorkResultObjectLoaded.Broadcast( - AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemID, CurrentWorkResultObj.Name); - } - else - { - CurrentWorkResultObj.State = EPDGWorkResultState::None; - } - } - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::Loaded) - { - // If the work item result obj is in the "Loaded" state, confirm that the output actor - // is still valid (the user could have manually deleted the output - if (!IsValid(CurrentWorkResultObj.GetOutputActorOwner().GetOutputActor())) - { - // If the output actor is invalid, set the state to ToDelete to complete the - // unload/deletion process - CurrentWorkResultObj.State = EPDGWorkResultState::ToDelete; - } - else - { - CurrentTOPNode->bCachedHaveLoadedWorkResults = true; - } - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::ToDelete) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Deleting; - - // Delete and clean up that WRObj - CurrentWorkResultObj.DestroyResultOutputs(); - CurrentWorkResultObj.GetOutputActorOwner().DestroyOutputActor(); - CurrentWorkResultObj.State = EPDGWorkResultState::Deleted; - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::Deleted) - { - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::NotLoaded) - { - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - } - } - } - } - } -} - -void FHoudiniPDGManager::HandleImportBGEODiscoverMessage( - const FHoudiniPDGImportBGEODiscoverMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_DISPLAY(TEXT("Received Discover from %s"), *InContext->GetSender().ToString()); - // Ignore any discover acks received if we already have a valid local address - // for the commandlet - if (BGEOCommandletAddress.IsValid()) - return; - - if (BGEOCommandletProcHandle.IsValid() && InMessage.CommandletGuid.IsValid() && BGEOCommandletGuid == InMessage.CommandletGuid) - { - BGEOCommandletAddress = InContext->GetSender(); - } -} - -void FHoudiniPDGManager::HandleImportBGEOResultMessage( - const FHoudiniPDGImportBGEOResultMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_MESSAGE(TEXT("Received BGEO import result message")); - if (InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_Success || InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_PartialSuccess) - { - FHoudiniPackageParams PackageParams; - InMessage.PopulatePackageParams(PackageParams); - - // Find asset link and work result object - UHoudiniPDGAssetLink *AssetLink = nullptr; - UTOPNode *TOPNode = nullptr; - if (!GetTOPAssetLinkAndNode(InMessage.TOPNodeId, AssetLink, TOPNode) || - !IsValid(AssetLink) || !IsValid(TOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP node with id %d, aborting output object creation."), InMessage.TOPNodeId); - return; - } - - FTOPWorkResult* WorkResult = AssetLink->GetWorkResultByID(InMessage.WorkItemId, TOPNode); - if (WorkResult == nullptr) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result with id %d, aborting output object creation."), InMessage.WorkItemId); - return; - } - const FString& WorkResultObjectName = InMessage.Name; - FTOPWorkResultObject* WorkResultObject = WorkResult->ResultObjects.FindByPredicate( - [&WorkResultObjectName](const FTOPWorkResultObject& WorkResultObject) - { - return WorkResultObject.Name == WorkResultObjectName; - } - ); - if (WorkResultObject == nullptr) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result object with name %s, aborting output object creation."), *InMessage.Name); - return; - } - - if (WorkResultObject->State != EPDGWorkResultState::Loading) - { - HOUDINI_LOG_WARNING(TEXT("TOP work result object (%s) not in Loading state, aborting output object creation."), *InMessage.Name); - return; - } - - // Set package params outer - UObject* AssetLinkParent = AssetLink->GetOuter(); - UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; - if (HAC) - { - PackageParams.OuterPackage = HAC->GetComponentLevel(); - } - else - { - PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); - } - - // Construct UHoudiniOutputs - bool bHasUnsupportedOutputs = false; - TArray NewOutputs; - TMap InstancedOutputPartData; - NewOutputs.Reserve(InMessage.Outputs.Num()); - for (const FHoudiniPDGImportNodeOutput& Output : InMessage.Outputs) - { - UHoudiniOutput* NewOutput = NewObject( - AssetLink, - UHoudiniOutput::StaticClass(), - NAME_None,//FName(*OutputName), - RF_NoFlags); - NewOutputs.Add(NewOutput); - const int32 NumHGPO = Output.HoudiniGeoPartObjects.Num(); - for (int32 Index = 0; Index < NumHGPO; ++Index) - { - const FHoudiniGeoPartObject& HGPO = Output.HoudiniGeoPartObjects[Index]; - NewOutput->AddNewHGPO(HGPO); - - if (Output.InstancedOutputPartData.IsValidIndex(Index)) - { - FHoudiniOutputObjectIdentifier Identifier; - Identifier.ObjectId = HGPO.ObjectId; - Identifier.GeoId = HGPO.GeoId; - Identifier.PartId = HGPO.PartId; - Identifier.PartName = HGPO.PartName; - FHoudiniInstancedOutputPartData InstancedPartData = Output.InstancedOutputPartData[Index]; - InstancedPartData.BuildOriginalInstancedTransformsAndObjectArrays(); - InstancedOutputPartData.Add(Identifier, InstancedPartData); - } - } - const int32 NumObjects = Output.OutputObjects.Num(); - for (int32 Index = 0; Index < NumObjects; ++Index) - { - const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; - FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; - - const FString& FullPackagePath = ImportOutputObject.PackagePath; - FString PackagePath; - FString PackageName; - const bool bDidSplit = FullPackagePath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); - if (!bDidSplit) - PackagePath = FullPackagePath; - - FHoudiniOutputObject OutputObject; - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!IsValid(Package)) - { - // Editor might have picked up the package yet, try to load it - Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); - } - if (IsValid(Package)) - { - OutputObject.OutputObject = FindObject(Package, *PackageName); - } - Identifier.bLoaded = true; - NewOutput->GetOutputObjects().Add(Identifier, OutputObject); - } - NewOutput->UpdateOutputType(); - const EHoudiniOutputType OutputType = NewOutput->GetType(); - if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) - { - bHasUnsupportedOutputs = true; - } - } - - bool bSuccess = true; - if (bHasUnsupportedOutputs) - { - HOUDINI_LOG_MESSAGE(TEXT("Processing output types not supported by commandlet for %s"), *InMessage.Name); - bSuccess = FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - AssetLink, TOPNode, *WorkResultObject, PackageParams, - { - EHoudiniOutputType::Landscape, - EHoudiniOutputType::Curve, - EHoudiniOutputType::Skeletal - } - ); - - if (bSuccess) - { - // Clear/remove the outputs on WorkResultObject that are supported by the commandlet, since we - // are going to replace them with NewOutputs now - TArray& CurrentOutputs = WorkResultObject->GetResultOutputs(); - const int32 NumCurrentOutputs = CurrentOutputs.Num(); - for (int32 Index = 0; Index < NumCurrentOutputs; ++Index) - { - UHoudiniOutput* CurOutput = CurrentOutputs[Index]; - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) - { - // Was created in editor, override the dummy one in NewOutputs with CurOutput - if (NewOutputs.IsValidIndex(Index)) - { - if (OutputType == NewOutputs[Index]->GetType()) - { - UHoudiniOutput* TempOutput = NewOutputs[Index]; - FHoudiniOutputTranslator::ClearOutput(TempOutput); - NewOutputs[Index] = CurOutput; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Unexpected commandlet output type at index %d!"), Index); - } - } - else - { - HOUDINI_LOG_ERROR(TEXT("Expected output index %d from commandlet to be exist!"), Index); - } - } - } - } - } - - if (bSuccess && FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( - AssetLink, - TOPNode, - *WorkResultObject, - PackageParams, - NewOutputs, - {EHoudiniOutputType::Mesh, EHoudiniOutputType::Instancer}, - &InstancedOutputPartData)) - { - const int32 NumOutputs = NewOutputs.Num(); - for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex) - { - UHoudiniOutput *NewOutput = NewOutputs[OutputIndex]; - - if (NewOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - const FHoudiniPDGImportNodeOutput& Output = InMessage.Outputs[OutputIndex]; - int32 NumObjects = Output.OutputObjects.Num(); - for (int32 Index = 0; Index < NumObjects; ++Index) - { - const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; - FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; - FHoudiniOutputObject *OutputObject = NewOutput->GetOutputObjects().Find(Identifier); - if (OutputObject && IsValid(OutputObject->OutputComponent)) - { - // Update generic property attributes - FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( - OutputObject->OutputComponent, - ImportOutputObject.GenericAttributes.PropertyAttributes); - - // Copy cached attributes - OutputObject->CachedAttributes.Append(ImportOutputObject.CachedAttributes); - } - } - } - } - else - { - bSuccess = false; - } - - if (bSuccess) - { - WorkResultObject->State = EPDGWorkResultState::Loaded; - HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); - // Broadcast that we have loaded the work result object to those interested - AssetLink->OnWorkResultObjectLoaded.Broadcast(AssetLink, TOPNode, WorkResult->WorkItemID, WorkResultObject->Name); - } - else - { - WorkResultObject->State = EPDGWorkResultState::None; - HOUDINI_LOG_WARNING(TEXT("Failed to process loaded assets for %s"), *InMessage.Name); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Commandlet failed to import bgeo for %s"), *InMessage.Name); - } -} - -bool FHoudiniPDGManager::CreateBGEOCommandletAndEndpoint() -{ - if (!BGEOCommandletEndpoint.IsValid()) - { - BGEOCommandletAddress.Invalidate(); - BGEOCommandletEndpoint = FMessageEndpoint::Builder(TEXT("Houdini BGEO Commandlet")) - .Handling(this, &FHoudiniPDGManager::HandleImportBGEOResultMessage) - .Handling(this, &FHoudiniPDGManager::HandleImportBGEODiscoverMessage) - .ReceivingOnThread(ENamedThreads::GameThread); - - if (!BGEOCommandletEndpoint.IsValid()) - { - HOUDINI_LOG_WARNING(TEXT("Could not set up messaging end point for BGEO commandlet")); - return false; - } - - BGEOCommandletEndpoint->Subscribe(); - } - - if (!BGEOCommandletProcHandle.IsValid() || !FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - { - // Start the bgeo commandlet - static const FString BGEOCommandletName = TEXT("HoudiniGeoImport"); - BGEOCommandletGuid = FGuid::NewGuid(); - BGEOCommandletAddress.Invalidate(); - - // Get the absolute path to the project file, if known, otherwise get - // the project name. For the path: quote it for the command line. - IFileManager& FileManager = IFileManager::Get(); - FString ProjectPathOrName = FApp::GetProjectName(); - if (FPaths::IsProjectFilePathSet()) - { - const FString ProjectPath = FPaths::GetProjectFilePath(); - if (!ProjectPath.IsEmpty()) - { - ProjectPathOrName = FString::Printf( - TEXT("\"%s\""), - *FileManager.ConvertToAbsolutePathForExternalAppForRead(*ProjectPath) - ); - } - } - - if (ProjectPathOrName.IsEmpty()) - return false; - - // Get the executable path for the app/editor - FString ExePath = FPlatformProcess::GenerateApplicationPath(FApp::GetName(), FApp::GetBuildConfiguration()); - if (!ExePath.IsEmpty()) - ExePath = FileManager.ConvertToAbsolutePathForExternalAppForRead(*ExePath); - - if (ExePath.IsEmpty()) - return false; - - const FString CommandLineParameters = FString::Printf( - TEXT("%s -messaging -run=%s -guid=%s -listen=%s -managerpid=%d"), - *ProjectPathOrName, - *BGEOCommandletName, - *BGEOCommandletGuid.ToString(), - *BGEOCommandletEndpoint->GetAddress().ToString(), - FPlatformProcess::GetCurrentProcessId()); - - BGEOCommandletProcHandle = FPlatformProcess::CreateProc( - *ExePath, - *CommandLineParameters, - false, - true, - false, - &BGEOCommandletProcessId, - 0, - NULL, - NULL); - if (!BGEOCommandletProcHandle.IsValid()) - { - return false; - } - } - - return true; -} - -void FHoudiniPDGManager::StopBGEOCommandletAndEndpoint() -{ - BGEOCommandletEndpoint.Reset(); - BGEOCommandletAddress.Invalidate(); - BGEOCommandletGuid.Invalidate(); - - if (BGEOCommandletProcHandle.IsValid() && FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - { - FPlatformProcess::TerminateProc(BGEOCommandletProcHandle, true); - if (BGEOCommandletProcHandle.IsValid()) - { - FPlatformProcess::WaitForProc(BGEOCommandletProcHandle); - FPlatformProcess::CloseProc(BGEOCommandletProcHandle); - } - } -} - -EHoudiniBGEOCommandletStatus FHoudiniPDGManager::UpdateAndGetBGEOCommandletStatus() -{ - if (BGEOCommandletProcHandle.IsValid()) - { - if (!FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Crashed; - else if (BGEOCommandletAddress.IsValid()) - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Connected; - else - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Running; - } - else - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::NotStarted; - - return BGEOCommandletStatus; -} - - -bool -FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) -{ - if (InAssetId < 0) - return false; - - // Get all the network nodes within the asset, recursively. - // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type - int32 NetworkNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); - - if (NetworkNodeCount <= 0) - return false; - - TArray AllNetworkNodeIDs; - AllNetworkNodeIDs.SetNum(NetworkNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); - - // There is currently no way to only get non bypassed nodes via HAPI - // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... - TArray AllBypassedTOPNetNodeIDs; - { - int32 BypassedTOPNetNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); - - if (BypassedTOPNetNodeCount > 0) - { - // Get the list of all bypassed TOP Net... - AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); - - // ... and remove them from the network list - for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) - AllNetworkNodeIDs.RemoveAt(Idx); - } - } - } - - // For each Network we found earlier, only consider those with TOP child nodes - // If we find TOP nodes in a valid network, then consider this HDA a PDG HDA - for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) - { - if (CurrentNodeId < 0) - { - continue; - } - - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) - { - continue; - } - - // Skip non TOP or SOP networks - if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP - && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) - { - continue; - } - - // Get the list of all TOP nodes within the current network (ignoring schedulers) - int32 TOPNodeCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) - { - continue; - } - - // We found valid TOP Nodes, this is a PDG HDA - if (TOPNodeCount > 0) - return true; - } - - // No valid TOP node found in SOP/TOP Networks, this is not a PDG HDA - return false; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGManager.h" + +#include "Modules/ModuleManager.h" +#include "MessageEndpointBuilder.h" +#include "HAL/FileManager.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPackageParams.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniPDGTranslator.h" +#include "HoudiniPDGImporterMessages.h" + +#include "HAPI/HAPI_Common.h" + +HOUDINI_PDG_DEFINE_LOG_CATEGORY(); + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniPDGManager::FHoudiniPDGManager() +{ +} + +FHoudiniPDGManager::~FHoudiniPDGManager() +{ +} + +bool +FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) +{ + if (!InHAC || InHAC->IsPendingKill()) + return false; + + int32 AssetId = InHAC->GetAssetId(); + if (AssetId < 0) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) + return false; + + // Create a new PDG Asset Link Object + bool bRegisterPDGAssetLink = false; + UHoudiniPDGAssetLink* PDGAssetLink = InHAC->GetPDGAssetLink(); + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + { + PDGAssetLink = NewObject(InHAC, UHoudiniPDGAssetLink::StaticClass(), NAME_None, RF_Transactional); + bRegisterPDGAssetLink = true; + } + + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + PDGAssetLink->AssetID = AssetId; + + // Get the HDA's info + HAPI_NodeInfo AssetInfo; + FHoudiniApi::NodeInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, &AssetInfo), false); + + // Get the node path + FString AssetNodePath; + FHoudiniEngineString::ToFString(AssetInfo.internalNodePathSH, PDGAssetLink->AssetNodePath); + + // Get the node name + FString AssetName; + FHoudiniEngineString::ToFString(AssetInfo.nameSH, PDGAssetLink->AssetName); + + const bool bZeroWorkItemTallys = true; + if (!FHoudiniPDGManager::PopulateTOPNetworks(PDGAssetLink, bZeroWorkItemTallys)) + { + // We couldn't find any valid TOPNet/TOPNode, this is not a PDG Asset + // Make sure the HDA doesn't have a PDGAssetLink + InHAC->SetPDGAssetLink(nullptr); + return false; + } + + // If the PDG asset link comes from a loaded asset, we also need to register it + if (InHAC->HasBeenLoaded()) + { + bRegisterPDGAssetLink = true; + } + + // We have found valid TOPNetworks and TOPNodes, + // This is a PDG HDA, so add the AssetLink to it + PDGAssetLink->LinkState = EPDGLinkState::Linked; + + if (PDGAssetLink->SelectedTOPNetworkIndex < 0) + PDGAssetLink->SelectedTOPNetworkIndex = 0; + + InHAC->SetPDGAssetLink(PDGAssetLink); + + if (bRegisterPDGAssetLink) + { + // Register this PDG Asset Link to the PDG Manager + TWeakObjectPtr AssetLinkPtr(PDGAssetLink); + PDGAssetLinks.Add(AssetLinkPtr); + } + + // If the commandlet is enabled, check if we have started and established communication with the commandlet yet + // if not, try to start the commandlet + bool bCommandletIsEnabled = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (IsValid(HoudiniRuntimeSettings)) + { + bCommandletIsEnabled = HoudiniRuntimeSettings->bPDGAsyncCommandletImportEnabled; + } + + if (bCommandletIsEnabled) + { + const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); + if (CommandletStatus == EHoudiniBGEOCommandletStatus::NotStarted && bCommandletIsEnabled) + { + CreateBGEOCommandletAndEndpoint(); + } + } + + return true; +} + +bool +FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) +{ + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + // If the PDG Asset link is inactive, indicate that our HDA must be instantiated + if (PDGAssetLink->LinkState == EPDGLinkState::Inactive) + { + UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); + if(!ParentHAC) + { + // No valid parent HAC, error! + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("No valid Houdini Asset Component parent for PDG Asset Link!")); + } + else if (ParentHAC && ParentHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) + { + PDGAssetLink->LinkState = EPDGLinkState::Linking; + ParentHAC->AssetState = EHoudiniAssetState::PreInstantiation; + } + else + { + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("Unable to link the PDG Asset link! Try to rebuild the HDA.")); + } + } + + if (PDGAssetLink->LinkState == EPDGLinkState::Linking) + { + return true; + } + + if (PDGAssetLink->LinkState != EPDGLinkState::Linked) + { + UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); + int32 AssetId = ParentHAC->GetAssetId(); + if (AssetId < 0) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) + return false; + + PDGAssetLink->AssetID = AssetId; + } + + if(!PopulateTOPNetworks(PDGAssetLink)) + { + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("Failed to populte the PDG Asset Link.")); + return false; + } + + return true; +} + + +bool +FHoudiniPDGManager::PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys) +{ + // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + // Get all the network nodes within the asset, recursively. + // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type + int32 NetworkNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); + + if (NetworkNodeCount <= 0) + return false; + + TArray AllNetworkNodeIDs; + AllNetworkNodeIDs.SetNum(NetworkNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); + + // There is currently no way to only get non bypassed nodes via HAPI + // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... + TArray AllBypassedTOPNetNodeIDs; + { + int32 BypassedTOPNetNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); + + if (BypassedTOPNetNodeCount > 0) + { + // Get the list of all bypassed TOP Net... + AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); + + // ... and remove them from the network list + for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) + AllNetworkNodeIDs.RemoveAt(Idx); + } + } + } + + + // For each Network we found earlier, only add those with TOP child nodes + // Therefore guaranteeing that we only add TOP networks + TArray AllTOPNetworks; + for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) + { + if (CurrentNodeId < 0) + { + continue; + } + + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) + { + continue; + } + + // Skip non TOP or SOP networks + if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP + && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) + { + continue; + } + + // Check that this TOP Net is not nested in another TOP Net... + // This will happen with ROP Geometry TOPs for example... + bool bIsNestedInTOPNet = false; + HAPI_NodeId CurrentParentId = CurrentNodeInfo.parentId; + while (CurrentParentId > 0) + { + if (AllNetworkNodeIDs.Contains(CurrentParentId)) + { + bIsNestedInTOPNet = true; + break; + } + + if(AllBypassedTOPNetNodeIDs.Contains(CurrentParentId)) + { + bIsNestedInTOPNet = true; + break; + } + + HAPI_NodeInfo ParentNodeInfo; + FHoudiniApi::NodeInfo_Init(&ParentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentParentId, &ParentNodeInfo)) + { + break; + } + + // Get our parent's parent + CurrentParentId = ParentNodeInfo.parentId; + } + + // Skip nested TOP nets + if (bIsNestedInTOPNet) + continue; + + // Get the list of all TOP nodes within the current network (ignoring schedulers) + int32 TOPNodeCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) + { + continue; + } + + TArray AllTOPNodeIDs; + AllTOPNodeIDs.SetNum(TOPNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, AllTOPNodeIDs.GetData(), TOPNodeCount), false); + + // Skip networks without TOP nodes + if (AllTOPNodeIDs.Num() <= 0) + { + continue; + } + + // Since there is currently no way to get only non-bypassed nodes via HAPI + // we need to get a list of all the bypassed top nodes to remove them from the previous list + { + int32 BypassedTOPNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_TOP_NONSCHEDULER | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNodeCount), false); + + if (BypassedTOPNodeCount > 0) + { + // Get the list of all bypassed TOP Nodes... + TArray AllBypassedTOPNodes; + AllBypassedTOPNodes.SetNum(BypassedTOPNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + AllBypassedTOPNodes.GetData(), BypassedTOPNodeCount), false); + + // ... and remove them from the top node list + for (int32 Idx = AllTOPNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNodes.Contains(AllTOPNodeIDs[Idx])) + AllTOPNodeIDs.RemoveAt(Idx); + } + } + } + + // TODO: + // Apply the show and output filter on that node + bool bShow = true; + + // Get the node path + FString CurrentNodePath; + FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, PDGAssetLink->AssetID, CurrentNodePath); + + // Get the node name + FString CurrentNodeName; + FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, CurrentNodeName); + + UTOPNetwork* CurrentTOPNetwork = nullptr; + int32 FoundTOPNetIndex = INDEX_NONE; + UTOPNetwork* FoundTOPNet = PDGAssetLink->GetTOPNetworkByNodePath(CurrentNodeName, PDGAssetLink->AllTOPNetworks, FoundTOPNetIndex); + if (IsValid(FoundTOPNet)) + { + // Reuse the existing corresponding TOP NET + CurrentTOPNetwork = FoundTOPNet; + PDGAssetLink->AllTOPNetworks.RemoveAt(FoundTOPNetIndex); + } + else + { + // Create a new instance for the TOP NET + CurrentTOPNetwork = NewObject(PDGAssetLink, UTOPNetwork::StaticClass(), NAME_None, RF_Transactional); + } + + // Update the TOP NET + CurrentTOPNetwork->NodeId = CurrentNodeId; + CurrentTOPNetwork->NodeName = CurrentNodeName; + CurrentTOPNetwork->NodePath = CurrentNodePath; + CurrentTOPNetwork->ParentName = PDGAssetLink->AssetName; + CurrentTOPNetwork->bShowResults = bShow; + + // Only add network that have valid TOP Nodes + if (PopulateTOPNodes(AllTOPNodeIDs, CurrentTOPNetwork, PDGAssetLink, bInZeroWorkItemTallys)) + { + // See if we need to select a new TOP node + bool bReselectValidTOP = false; + if (CurrentTOPNetwork->SelectedTOPIndex < 0) + bReselectValidTOP = true; + else if (!CurrentTOPNetwork->AllTOPNodes.IsValidIndex(CurrentTOPNetwork->SelectedTOPIndex)) + bReselectValidTOP = true; + else if (!IsValid(CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]) || + CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]->bHidden) + bReselectValidTOP = true; + + if (bReselectValidTOP) + { + // Select the first valid TOP node (not hidden) + for (int Idx = 0; Idx < CurrentTOPNetwork->AllTOPNodes.Num(); Idx++) + { + UTOPNode *TOPNode = CurrentTOPNetwork->AllTOPNodes[Idx]; + if (!IsValid(TOPNode) || TOPNode->bHidden) + continue; + + CurrentTOPNetwork->SelectedTOPIndex = Idx; + break; + } + } + + AllTOPNetworks.Add(CurrentTOPNetwork); + } + } + + // Clear previous TOP networks, nodes and generated data + for (UTOPNetwork* CurrentTOPNet : PDGAssetLink->AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + PDGAssetLink->ClearTOPNetworkWorkItemResults(CurrentTOPNet); + } + //PDGAssetLink->ClearAllTOPData(); + PDGAssetLink->AllTOPNetworks = AllTOPNetworks; + + return (AllTOPNetworks.Num() > 0); +} + + +bool +FHoudiniPDGManager::PopulateTOPNodes( + const TArray& InTopNodeIDs, UTOPNetwork* InTOPNetwork, UHoudiniPDGAssetLink* InPDGAssetLink, bool bInZeroWorkItemTallys) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InTOPNetwork)) + return false; + + // + int32 TOPNodeCount = 0; + + // Holds list of found TOP nodes + TArray AllTOPNodes; + for(const HAPI_NodeId& CurrentTOPNodeID : InTopNodeIDs) + { + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentTOPNodeID, &CurrentNodeInfo)) + { + continue; + } + + // Increase the number of valid TOP Node + // (before applying the node filter) + TOPNodeCount++; + + // Get the node path + FString NodePath; + FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, InTOPNetwork->NodeId, NodePath); + + // Get the node name + FString NodeName; + FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, NodeName); + + // See if we can find an existing version of this TOPNOde + UTOPNode* CurrentTOPNode = nullptr; + int32 FoundNodeIndex = INDEX_NONE; + UTOPNode* FoundNode = InPDGAssetLink->GetTOPNodeByNodePath(NodePath, InTOPNetwork->AllTOPNodes, FoundNodeIndex); + if (IsValid(FoundNode)) + { + CurrentTOPNode = FoundNode; + InTOPNetwork->AllTOPNodes.RemoveAt(FoundNodeIndex); + + if (bInZeroWorkItemTallys) + { + CurrentTOPNode->WorkItemTally.ZeroAll(); + CurrentTOPNode->NodeState = EPDGNodeState::None; + } + } + else + { + // Didn't find an existing UTOPNode for this node, create a new UTOPNode instance + CurrentTOPNode = NewObject(InPDGAssetLink, UTOPNode::StaticClass(), NAME_None, RF_Transactional); + } + + CurrentTOPNode->NodeId = CurrentTOPNodeID; + CurrentTOPNode->NodeName = NodeName; + CurrentTOPNode->NodePath = NodePath; + CurrentTOPNode->ParentName = InTOPNetwork->ParentName + TEXT("_") + InTOPNetwork->NodeName; + CurrentTOPNode->bHasChildNodes = CurrentNodeInfo.childNodeCount > 0; + + // Filter display/autoload using name + CurrentTOPNode->bHidden = false; + if (InPDGAssetLink->bUseTOPNodeFilter && !InPDGAssetLink->TOPNodeFilter.IsEmpty()) + { + // Only display nodes that matches the filter + if (!NodeName.StartsWith(InPDGAssetLink->TOPNodeFilter)) + CurrentTOPNode->bHidden = true; + } + + // Automatically load results for nodes that match the filter + if (InPDGAssetLink->bUseTOPOutputFilter) + { + bool bAutoLoad = false; + if (InPDGAssetLink->TOPOutputFilter.IsEmpty()) + bAutoLoad = true; + else if (NodeName.StartsWith(InPDGAssetLink->TOPOutputFilter)) + bAutoLoad = true; + + CurrentTOPNode->bAutoLoad = bAutoLoad; + + // Show autoloaded results by default + CurrentTOPNode->SetVisibleInLevel(bAutoLoad); + } + + AllTOPNodes.Add(CurrentTOPNode); + } + + for (UTOPNode* CurTOPNode : InTOPNetwork->AllTOPNodes) + { + if (!IsValid(CurTOPNode)) + continue; + + InPDGAssetLink->ClearTOPNodeWorkItemResults(CurTOPNode); + } + + InTOPNetwork->AllTOPNodes = AllTOPNodes; + + return (TOPNodeCount > 0); +} + + +void +FHoudiniPDGManager::DirtyTOPNode(UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + // Dirty the specified TOP node... + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( + FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, true)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *(InTOPNode->NodeName)); + } + + // ... and clear its work item results. + UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(InTOPNode); +} + +// void +// FHoudiniPDGManager::DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode) +// { +// // Dirty the specified TOP node... +// if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( +// FHoudiniEngine::Get().GetSession(), InTOPNode.NodeId, true)) +// { +// HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *InTOPNode.NodeName); +// } +// } + +void +FHoudiniPDGManager::CookTOPNode(UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + if (InTOPNode->NodeState == EPDGNodeState::Cooking || InTOPNode->AnyWorkItemsPending()) + { + HOUDINI_LOG_WARNING(TEXT("PDG Cook TOP Node - %s is already/still cooking, ignoring 'Cook TOP Node' request."), *(InTOPNode->NodePath)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( + FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, 0, 0)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook TOP Node - Failed to cook %s!"), *(InTOPNode->NodeName)); + } +} + + +void +FHoudiniPDGManager::DirtyAll(UTOPNetwork* InTOPNet) +{ + if (!IsValid(InTOPNet)) + return; + + // Dirty the specified TOP network... + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, true)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Dirty All - Failed to dirty all of %s's TOP nodes!"), *(InTOPNet->NodeName)); + return; + } + + // ... and clear its work item results. + UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(InTOPNet); +} + + +void +FHoudiniPDGManager::CookOutput(UTOPNetwork* InTOPNet) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::CookOutput); + + // Cook the output TOP node of the currently selected TOP network. + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + bool bAlreadyCooking = InTOPNet->AnyWorkItemsPending(); + + if (!bAlreadyCooking) + { + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + int32 PDGState = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( + FHoudiniEngine::Get().GetSession(), GraphContextId, &PDGState)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's PDG state."), *(InTOPNet->NodeName)); + return; + } + bAlreadyCooking = ((HAPI_PDG_State) PDGState == HAPI_PDG_STATE_COOKING); + } + + if (bAlreadyCooking) + { + HOUDINI_LOG_WARNING(TEXT("PDG Cook Output - %s is already/still cooking, ignoring 'Cook Output' request."), *(InTOPNet->NodeName)); + return; + } + + // TODO: ??? + // Cancel all cooks. This is required as otherwise the graph gets into an infinite cook state (bug?) + if(HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, 0, 0)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to cook %s's output!"), *(InTOPNet->NodeName)); + } +} + + +void +FHoudiniPDGManager::PauseCook(UTOPNetwork* InTOPNet) +{ + // Pause the PDG cook of the currently selected TOP network + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::PausePDGCook( + FHoudiniEngine::Get().GetSession(), GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to pause %s!"), *(InTOPNet->NodeName)); + return; + } +} + + +void +FHoudiniPDGManager::CancelCook(UTOPNetwork* InTOPNet) +{ + // Cancel the PDG cook of the currently selected TOP network + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CancelPDGCook( + FHoudiniEngine::Get().GetSession(), GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to cancel cook for %s!"), *(InTOPNet->NodeName)); + return; + } +} + +void +FHoudiniPDGManager::Update() +{ + // Clean up registered PDG Asset Links + for(int32 Idx = PDGAssetLinks.Num() - 1; Idx >= 0; Idx--) + { + TWeakObjectPtr Ptr = PDGAssetLinks[Idx]; + if (!Ptr.IsValid() || Ptr.IsStale()) + { + PDGAssetLinks.RemoveAt(Idx); + continue; + } + + UHoudiniPDGAssetLink* CurPDGAssetLink = PDGAssetLinks[Idx].Get(); + if (!CurPDGAssetLink || CurPDGAssetLink->IsPendingKill()) + { + PDGAssetLinks.RemoveAt(Idx); + continue; + } + } + + // Do nothing if we dont have any valid PDG asset Link + if (PDGAssetLinks.Num() <= 0) + return; + + // Update the PDG contexts and handle all pdg events and work item status updates + UpdatePDGContexts(); + + // Prcoess any workitem result if we have any + ProcessWorkItemResults(); +} + +// Query all the PDG graph context in the current Houdini Engine session. +// Handle PDG events, work item status updates. +// Forward relevant events to PDGAssetLink objects. +void +FHoudiniPDGManager::UpdatePDGContexts() +{ + // Get current PDG graph contexts + ReinitializePDGContext(); + + // Process next set of events for each graph context + if (PDGContextIDs.Num() > 0) + { + // Only initialize event array if not valid, or user resized max size + if(PDGEventInfos.Num() != MaxNumberOfPDGEvents) + PDGEventInfos.SetNum(MaxNumberOfPDGEvents); + + // TODO: member? + //HAPI_PDG_State PDGState; + for(const HAPI_PDG_GraphContextId& CurrentContextID : PDGContextIDs) + { + /* + // TODO: No need to reset events at each tick + int32 PDGStateInt; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( + FHoudiniEngine::Get().GetSession(), CurrentContextID, &PDGStateInt)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get PDG state")); + continue; + } + + PDGState = (HAPI_PDG_State)PDGStateInt; + + for (int32 Idx = 0; Idx < PDGEventInfos.Num(); Idx++) + { + ResetPDGEventInfo(PDGEventInfos[Idx]); + } + */ + + int32 PDGEventCount = 0; + int32 RemainingPDGEventCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGEvents( + FHoudiniEngine::Get().GetSession(), CurrentContextID, PDGEventInfos.GetData(), + MaxNumberOfPDGEvents, &PDGEventCount, &RemainingPDGEventCount)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get PDG events")); + continue; + } + + if (PDGEventCount < 1) + continue; + + for (int32 EventIdx = 0; EventIdx < PDGEventCount; EventIdx++) + { + ProcessPDGEvent(CurrentContextID, PDGEventInfos[EventIdx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("PDG: Tick processed %d events, %d remaining."), PDGEventCount, RemainingPDGEventCount); + } + } + + // Refresh UI if necessary + for (auto CurAssetLink : PDGAssetLinks) + { + UHoudiniPDGAssetLink* AssetLink = CurAssetLink.Get(); + if (AssetLink) + { + if (AssetLink->bNeedsUIRefresh) + { + FHoudiniPDGManager::RefreshPDGAssetLinkUI(AssetLink); + AssetLink->bNeedsUIRefresh = false; + } + else + { + AssetLink->UpdateWorkItemTally(); + } + } + } +} + +// Query the currently active PDG graph contexts in the Houdini Engine session. +// Should be done each time to get latest set of graph contexts. +void +FHoudiniPDGManager::ReinitializePDGContext() +{ + int32 NumContexts = 0; + + PDGContextNames.SetNum(MaxNumberOPDGContexts); + PDGContextIDs.SetNum(MaxNumberOPDGContexts); + + if(HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContexts( + FHoudiniEngine::Get().GetSession(), + &NumContexts, PDGContextNames.GetData(), PDGContextIDs.GetData(), MaxNumberOPDGContexts) || NumContexts <= 0) + { + PDGContextNames.SetNum(0); + PDGContextIDs.SetNum(0); + return; + } + + if(PDGContextIDs.Num() != NumContexts) + PDGContextIDs.SetNum(NumContexts); + + if (PDGContextNames.Num() != NumContexts) + PDGContextNames.SetNum(NumContexts); +} + +// Process a PDG event. Notify the relevant PDGAssetLink object. +void +FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo) +{ + UHoudiniPDGAssetLink* PDGAssetLink = nullptr; + UTOPNode* TOPNode = nullptr; + + HAPI_PDG_EventType EventType = (HAPI_PDG_EventType)EventInfo.eventType; + HAPI_PDG_WorkitemState CurrentWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.currentState; + HAPI_PDG_WorkitemState LastWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.lastState; + + // Debug: get the HAPI_PDG_EventType as a string + const FString EventName = FHoudiniEngineUtils::HapiGetEventTypeAsString(EventType); + const FString CurrentWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(CurrentWorkItemState); + const FString LastWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(LastWorkItemState); + + if(!GetTOPAssetLinkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNode) + || PDGAssetLink == nullptr || PDGAssetLink->IsPendingKill() + || TOPNode == nullptr || TOPNode->IsPendingKill() + || TOPNode->NodeId != EventInfo.nodeId) + { + + HOUDINI_LOG_WARNING(TEXT("[ProcessPDGEvent]: Could not find matching TOPNode for event %s, workitem id %d, node id %d"), *EventName, EventInfo.workitemId, EventInfo.nodeId); + return; + } + + HOUDINI_PDG_MESSAGE( + TEXT("[ProcessPDGEvent]: TOPNode: %s, WorkItem ID: %d, Event Type: %s, Current State: %s, Last State %s"), + *(TOPNode->NodePath), EventInfo.workitemId, *EventName, *CurrentWorkitemStateName, *LastWorkitemStateName); + + FLinearColor MsgColor = FLinearColor::White; + + bool bUpdatePDGNodeState = false; + switch (EventType) + { + case HAPI_PDG_EVENT_NULL: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::None); + break; + + case HAPI_PDG_EVENT_NODE_CLEAR: + NotifyTOPNodePDGStateClear(PDGAssetLink, TOPNode); + break; + + case HAPI_PDG_EVENT_WORKITEM_ADD: + bUpdatePDGNodeState = true; + NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, 1); + break; + + case HAPI_PDG_EVENT_WORKITEM_REMOVE: + RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, TOPNode); + bUpdatePDGNodeState = true; + NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, -1); + break; + + case HAPI_PDG_EVENT_COOK_WARNING: + MsgColor = FLinearColor::Yellow; + break; + + case HAPI_PDG_EVENT_COOK_ERROR: + MsgColor = FLinearColor::Red; + break; + + case HAPI_PDG_EVENT_COOK_COMPLETE: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + break; + + case HAPI_PDG_EVENT_DIRTY_START: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtying); + break; + + case HAPI_PDG_EVENT_DIRTY_STOP: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtied); + break; + + case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: + { + // Last states + bUpdatePDGNodeState = true; + if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) + { + NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, -1); + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) + { + NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, -1); + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) + { + NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); + } + else if ( (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE || LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) + && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) + { + // Handled previously cooked WI + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, -1); + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) + { + NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); + } + else + { + // TODO: + // unhandled state change + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); + } + + if (LastWorkItemState == CurrentWorkItemState) + { + // TODO: + // Not a change!! shouldnt happen! + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); + } + + // New states + if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) + { + NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, 1); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNCOOKED) + { + + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_DIRTY) + { + // ClearWorkItemResult(InContextID, EventInfo, *TOPNode); + ClearWorkItemResult(PDGAssetLink, EventInfo.workitemId, TOPNode); + // RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, *TOPNode); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) + { + NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, 1); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) + { + NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, 1); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS + || CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE) + { + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 1); + + // On cook success, handle results + CreateWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) + { + // TODO: on cook failure, get log path? + NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, 1); + MsgColor = FLinearColor::Red; + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CANCEL) + { + // Ignore it because in-progress cooks can be cancelled when automatically recooking graph + } + } + break; + + // Unhandled events + case HAPI_PDG_EVENT_DIRTY_ALL: + case HAPI_PDG_EVENT_COOK_START: + case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: + case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: + case HAPI_PDG_EVENT_UI_SELECT: + case HAPI_PDG_EVENT_NODE_CREATE: + case HAPI_PDG_EVENT_NODE_REMOVE: + case HAPI_PDG_EVENT_NODE_RENAME: + case HAPI_PDG_EVENT_NODE_CONNECT: + case HAPI_PDG_EVENT_NODE_DISCONNECT: + case HAPI_PDG_EVENT_WORKITEM_SET_INT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_STRING: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FILE: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_RESULT: + case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: + case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: + case HAPI_PDG_EVENT_ALL: + case HAPI_PDG_EVENT_LOG: + case HAPI_PDG_CONTEXT_EVENTS: + break; + } + + if (bUpdatePDGNodeState) + { + // Work item events + EPDGNodeState CurrentTOPNodeState = TOPNode->NodeState; + if (CurrentTOPNodeState == EPDGNodeState::Cooking) + { + if (TOPNode->AreAllWorkItemsComplete()) + { + if (TOPNode->AnyWorkItemsFailed()) + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); + } + else + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + } + } + } + else if (TOPNode->AnyWorkItemsPending()) + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cooking); + } + } + + if (EventInfo.msgSH >= 0) + { + FString EventMsg; + FHoudiniEngineString::ToFString(EventInfo.msgSH, EventMsg); + if (!EventMsg.IsEmpty()) + { + // TODO: Event MSG? + // Somehow update the PDG event msg UI ?? + // Simply log for now... + if (MsgColor == FLinearColor::Red) + { + HOUDINI_LOG_ERROR(TEXT("%s"), *EventMsg); + } + else if (MsgColor == FLinearColor::Yellow) + { + HOUDINI_LOG_WARNING(TEXT("%s"), *EventMsg); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT("%s"), *EventMsg); + } + } + } +} + +void +FHoudiniPDGManager::ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo) +{ + InEventInfo.nodeId = -1; + InEventInfo.workitemId = -1; + InEventInfo.dependencyId = -1; + InEventInfo.currentState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; + InEventInfo.lastState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; + InEventInfo.eventType = HAPI_PDG_EventType::HAPI_PDG_EVENT_NULL; +} + + +bool +FHoudiniPDGManager::GetTOPAssetLinkAndNode( + const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode) +{ + // Returns the PDGAssetLink and FTOPNode data associated with this TOP node ID + OutAssetLink = nullptr; + OutTOPNode = nullptr; + for (TWeakObjectPtr& CurAssetLinkPtr : PDGAssetLinks) + { + if (!CurAssetLinkPtr.IsValid() || CurAssetLinkPtr.IsStale()) + continue; + + UHoudiniPDGAssetLink* CurAssetLink = CurAssetLinkPtr.Get(); + if (!CurAssetLink || CurAssetLink->IsPendingKill()) + continue; + + OutTOPNode = CurAssetLink->GetTOPNode((int32)InNodeID); + + if (OutTOPNode != nullptr) + { + OutAssetLink = CurAssetLink; + return true; + } + } + + return false; +} + +void +FHoudiniPDGManager::SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->NodeState = InPDGState; + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + //Debug.LogFormat("NotifyTOPNodePDGStateClear:: {0}", topNode._nodeName); + InTOPNode->NodeState = EPDGNodeState::None; + InTOPNode->WorkItemTally.ZeroAll(); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ZeroAll"), *(InTOPNode->NodePath)); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); + +} + +void +FHoudiniPDGManager::NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.TotalWorkItems = FMath::Max(InTOPNode->WorkItemTally.TotalWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally TotalWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.CookedWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookedWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.ErroredWorkItems = FMath::Max(InTOPNode->WorkItemTally.ErroredWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.WaitingWorkItems = FMath::Max(InTOPNode->WorkItemTally.WaitingWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.ScheduledWorkItems = FMath::Max(InTOPNode->WorkItemTally.ScheduledWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->WorkItemTally.CookingWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookingWorkItems + Increment, 0); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // TODO!!! + // Clear all work items' results for the specified TOP node. + // This destroys any loaded results (geometry etc). + //session.LogErrorOverride = false; + InAssetLink->ClearWorkItemResultByID(InWorkItemID, InTOPNode); + // session.LogErrorOverride = true; +} + +void +FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // Clear all of the work item's results for the specified TOP node and also remove the work item itself from + // the TOP node. + InAssetLink->DestroyWorkItemByID(InWorkItemID, InTOPNode); +} + +void +FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // Only update the editor properties if the PDG asset link's Actor is selected + // else, just update the workitemtally + InAssetLink->UpdateWorkItemTally(); + + UHoudiniAssetComponent* HAC = Cast(InAssetLink->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor* ActorOwner = HAC->GetOwner(); + if (ActorOwner != nullptr && ActorOwner->IsSelected()) + { + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + } +} + +void +FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + if (bSuccess) + { + if (InAssetLink->LinkState == EPDGLinkState::Linked) + { + if (InAssetLink->bAutoCook) + { + FHoudiniPDGManager::CookOutput(InAssetLink->GetSelectedTOPNetwork()); + } + } + else + { + UpdatePDGAssetLink(InAssetLink); + } + } + else + { + InAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + } +} + +bool +FHoudiniPDGManager::CreateWorkItemResult( + UTOPNode* InTOPNode, + const HAPI_PDG_GraphContextId& InContextID, + HAPI_PDG_WorkitemId InWorkItemID, + bool bInLoadResultObjects) +{ + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); + return false; + } + + HAPI_PDG_WorkitemInfo WorkItemInfo; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( + FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return false; + } + + if (WorkItemInfo.numResults > 0) + { + TArray ResultInfos; + ResultInfos.SetNum(WorkItemInfo.numResults); + const int32 resultCount = WorkItemInfo.numResults; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemResultInfo( + FHoudiniEngine::Get().GetSession(), + InTOPNode->NodeId, InWorkItemID, ResultInfos.GetData(), resultCount)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d result info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return false; + } + + FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); + if (!WorkResult) + { + FTOPWorkResult LocalWorkResult; + LocalWorkResult.WorkItemID = InWorkItemID; + LocalWorkResult.WorkItemIndex = WorkItemInfo.index; + const int32 Idx = InTOPNode->WorkResult.Add(LocalWorkResult); + WorkResult = &(InTOPNode->WorkResult[Idx]); + } + + FString WorkItemName; + FHoudiniEngineString::ToFString(WorkItemInfo.nameSH, WorkItemName); + + // Load each result geometry + const int32 NumResults = ResultInfos.Num(); + for (int32 Idx = 0; Idx < NumResults; Idx++) + { + const HAPI_PDG_WorkitemResultInfo& ResultInfo = ResultInfos[Idx]; + if (ResultInfo.resultTagSH <= 0 || ResultInfo.resultSH <= 0) + continue; + + FString CurrentTag; + FHoudiniEngineString::ToFString(ResultInfo.resultTagSH, CurrentTag); + if(CurrentTag.IsEmpty() || !CurrentTag.StartsWith(TEXT("file"))) + continue; + + FString CurrentPath = FString(); + FHoudiniEngineString::ToFString(ResultInfo.resultSH, CurrentPath); + + // Construct the name and look for an existing work result, re-use it if found, otherwise create a new one + const FString WorkResultName = FString::Printf( + TEXT("%s_%s_%d"), + *(InTOPNode->ParentName), + *WorkItemName, + WorkItemInfo.index); + + FTOPWorkResultObject* ExistingResultObject = WorkResult->ResultObjects.FindByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) + { + return InResultObject.Name == WorkResultName; + }); + if (ExistingResultObject) + { + ExistingResultObject->FilePath = CurrentPath; + if (ExistingResultObject->State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) + { + ExistingResultObject->State = EPDGWorkResultState::ToDelete; + } + else if ((ExistingResultObject->State == EPDGWorkResultState::Loaded || + ExistingResultObject->State == EPDGWorkResultState::ToDelete || + ExistingResultObject->State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) + { + // When the commandlet is not being used, we could leave the outputs in place and have + // translators try to re-use objects/components. When the commandlet is being used, the packages + // are always saved and standalone, so if we want to automatically clean up old results then we + // need to destroy the existing outputs + if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) + ExistingResultObject->DestroyResultOutputs(); + ExistingResultObject->State = EPDGWorkResultState::ToLoad; + } + else + { + ExistingResultObject->State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + } + } + else + { + FTOPWorkResultObject ResultObj; + ResultObj.Name = WorkResultName; + ResultObj.FilePath = CurrentPath; + ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + + WorkResult->ResultObjects.Add(ResultObj); + } + } + } + + return true; +} + +void +FHoudiniPDGManager::ProcessWorkItemResults() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::ProcessWorkItemResults); + + const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); + for (auto& CurrentPDGAssetLink : PDGAssetLinks) + { + // Iterate through all PDG Asset Link + UHoudiniPDGAssetLink* AssetLink = CurrentPDGAssetLink.Get(); + if (!AssetLink) + continue; + + // Set up package parameters to: + // Cook to temp houdini engine directory + // and if the PDG asset link is associated with a Houdini Asset Component (HAC): + // set the outer package to the HAC + // set the HoudiniAssetName according to the HAC + // set the ComponentGUID according to the HAC + // otherwise we set the outer to the asset link's parent and leave naming and GUID blank + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + // AActor* ParentActor = nullptr; + UObject* AssetLinkParent = AssetLink->GetOuter(); + UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; + if (HAC) + { + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + + // ParentActor = HAC->GetOwner(); + } + else + { + PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); + PackageParams.HoudiniAssetName = FString(); + PackageParams.HoudiniAssetActorName = FString(); + // PackageParams.ComponentGUID = HAC->GetComponentGUID(); + + // // Try to find a parent actor + // UObject* Parent = AssetLinkParent; + // while (Parent && !ParentActor) + // { + // ParentActor = Cast(Parent); + // if (!ParentActor) + // Parent = ParentActor->GetOuter(); + // } + } + PackageParams.ObjectName = FString(); + + // UWorld *World = ParentActor ? ParentActor->GetWorld() : AssetLink->GetWorld(); + UWorld *World = AssetLink->GetWorld(); + + // .. All TOP Nets + for (UTOPNetwork* CurrentTOPNet : AssetLink->AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + // .. All TOP Nodes + for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + // ... All WorkResult + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; + CurrentTOPNode->bCachedHaveLoadedWorkResults = false; + for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) + { + // ... All WorkResultObjects + for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) + { + if (CurrentWorkResultObj.State == EPDGWorkResultState::ToLoad) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Loading; + + // Load this WRObj + PackageParams.PDGTOPNetworkName = CurrentTOPNet->NodeName; + PackageParams.PDGTOPNodeName = CurrentTOPNode->NodeName; + PackageParams.PDGWorkItemIndex = CurrentWorkResult.WorkItemIndex; + + if (CommandletStatus == EHoudiniBGEOCommandletStatus::Connected) + { + BGEOCommandletEndpoint->Send(new FHoudiniPDGImportBGEOMessage( + CurrentWorkResultObj.FilePath, + CurrentWorkResultObj.Name, + PackageParams, + CurrentTOPNode->NodeId, + CurrentWorkResult.WorkItemID + ), BGEOCommandletAddress); + } + else + { + if (FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + AssetLink, + CurrentTOPNode, + CurrentWorkResultObj, + PackageParams)) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Loaded; + CurrentTOPNode->bCachedHaveLoadedWorkResults = true; + + // Broadcast that we have loaded the work result object to those interested + AssetLink->OnWorkResultObjectLoaded.Broadcast( + AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemID, CurrentWorkResultObj.Name); + } + else + { + CurrentWorkResultObj.State = EPDGWorkResultState::None; + } + } + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::Loaded) + { + // If the work item result obj is in the "Loaded" state, confirm that the output actor + // is still valid (the user could have manually deleted the output + if (!IsValid(CurrentWorkResultObj.GetOutputActorOwner().GetOutputActor())) + { + // If the output actor is invalid, set the state to ToDelete to complete the + // unload/deletion process + CurrentWorkResultObj.State = EPDGWorkResultState::ToDelete; + } + else + { + CurrentTOPNode->bCachedHaveLoadedWorkResults = true; + } + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::ToDelete) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Deleting; + + // Delete and clean up that WRObj + CurrentWorkResultObj.DestroyResultOutputs(); + CurrentWorkResultObj.GetOutputActorOwner().DestroyOutputActor(); + CurrentWorkResultObj.State = EPDGWorkResultState::Deleted; + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::Deleted) + { + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::NotLoaded) + { + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + } + } + } + } + } +} + +void FHoudiniPDGManager::HandleImportBGEODiscoverMessage( + const FHoudiniPDGImportBGEODiscoverMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_DISPLAY(TEXT("Received Discover from %s"), *InContext->GetSender().ToString()); + // Ignore any discover acks received if we already have a valid local address + // for the commandlet + if (BGEOCommandletAddress.IsValid()) + return; + + if (BGEOCommandletProcHandle.IsValid() && InMessage.CommandletGuid.IsValid() && BGEOCommandletGuid == InMessage.CommandletGuid) + { + BGEOCommandletAddress = InContext->GetSender(); + } +} + +void FHoudiniPDGManager::HandleImportBGEOResultMessage( + const FHoudiniPDGImportBGEOResultMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_MESSAGE(TEXT("Received BGEO import result message")); + if (InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_Success || InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_PartialSuccess) + { + FHoudiniPackageParams PackageParams; + InMessage.PopulatePackageParams(PackageParams); + + // Find asset link and work result object + UHoudiniPDGAssetLink *AssetLink = nullptr; + UTOPNode *TOPNode = nullptr; + if (!GetTOPAssetLinkAndNode(InMessage.TOPNodeId, AssetLink, TOPNode) || + !IsValid(AssetLink) || !IsValid(TOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP node with id %d, aborting output object creation."), InMessage.TOPNodeId); + return; + } + + FTOPWorkResult* WorkResult = AssetLink->GetWorkResultByID(InMessage.WorkItemId, TOPNode); + if (WorkResult == nullptr) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result with id %d, aborting output object creation."), InMessage.WorkItemId); + return; + } + const FString& WorkResultObjectName = InMessage.Name; + FTOPWorkResultObject* WorkResultObject = WorkResult->ResultObjects.FindByPredicate( + [&WorkResultObjectName](const FTOPWorkResultObject& WorkResultObject) + { + return WorkResultObject.Name == WorkResultObjectName; + } + ); + if (WorkResultObject == nullptr) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result object with name %s, aborting output object creation."), *InMessage.Name); + return; + } + + if (WorkResultObject->State != EPDGWorkResultState::Loading) + { + HOUDINI_LOG_WARNING(TEXT("TOP work result object (%s) not in Loading state, aborting output object creation."), *InMessage.Name); + return; + } + + // Set package params outer + UObject* AssetLinkParent = AssetLink->GetOuter(); + UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; + if (HAC) + { + PackageParams.OuterPackage = HAC->GetComponentLevel(); + } + else + { + PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); + } + + // Construct UHoudiniOutputs + bool bHasUnsupportedOutputs = false; + TArray NewOutputs; + TMap InstancedOutputPartData; + NewOutputs.Reserve(InMessage.Outputs.Num()); + for (const FHoudiniPDGImportNodeOutput& Output : InMessage.Outputs) + { + UHoudiniOutput* NewOutput = NewObject( + AssetLink, + UHoudiniOutput::StaticClass(), + NAME_None,//FName(*OutputName), + RF_NoFlags); + NewOutputs.Add(NewOutput); + const int32 NumHGPO = Output.HoudiniGeoPartObjects.Num(); + for (int32 Index = 0; Index < NumHGPO; ++Index) + { + const FHoudiniGeoPartObject& HGPO = Output.HoudiniGeoPartObjects[Index]; + NewOutput->AddNewHGPO(HGPO); + + if (Output.InstancedOutputPartData.IsValidIndex(Index)) + { + FHoudiniOutputObjectIdentifier Identifier; + Identifier.ObjectId = HGPO.ObjectId; + Identifier.GeoId = HGPO.GeoId; + Identifier.PartId = HGPO.PartId; + Identifier.PartName = HGPO.PartName; + FHoudiniInstancedOutputPartData InstancedPartData = Output.InstancedOutputPartData[Index]; + InstancedPartData.BuildOriginalInstancedTransformsAndObjectArrays(); + InstancedOutputPartData.Add(Identifier, InstancedPartData); + } + } + const int32 NumObjects = Output.OutputObjects.Num(); + for (int32 Index = 0; Index < NumObjects; ++Index) + { + const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; + FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; + + const FString& FullPackagePath = ImportOutputObject.PackagePath; + FString PackagePath; + FString PackageName; + const bool bDidSplit = FullPackagePath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bDidSplit) + PackagePath = FullPackagePath; + + FHoudiniOutputObject OutputObject; + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!IsValid(Package)) + { + // Editor might have picked up the package yet, try to load it + Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); + } + if (IsValid(Package)) + { + OutputObject.OutputObject = FindObject(Package, *PackageName); + } + Identifier.bLoaded = true; + NewOutput->GetOutputObjects().Add(Identifier, OutputObject); + } + NewOutput->UpdateOutputType(); + const EHoudiniOutputType OutputType = NewOutput->GetType(); + if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) + { + bHasUnsupportedOutputs = true; + } + } + + bool bSuccess = true; + if (bHasUnsupportedOutputs) + { + HOUDINI_LOG_MESSAGE(TEXT("Processing output types not supported by commandlet for %s"), *InMessage.Name); + bSuccess = FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + AssetLink, TOPNode, *WorkResultObject, PackageParams, + { + EHoudiniOutputType::Landscape, + EHoudiniOutputType::Curve, + EHoudiniOutputType::Skeletal + } + ); + + if (bSuccess) + { + // Clear/remove the outputs on WorkResultObject that are supported by the commandlet, since we + // are going to replace them with NewOutputs now + TArray& CurrentOutputs = WorkResultObject->GetResultOutputs(); + const int32 NumCurrentOutputs = CurrentOutputs.Num(); + for (int32 Index = 0; Index < NumCurrentOutputs; ++Index) + { + UHoudiniOutput* CurOutput = CurrentOutputs[Index]; + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) + { + // Was created in editor, override the dummy one in NewOutputs with CurOutput + if (NewOutputs.IsValidIndex(Index)) + { + if (OutputType == NewOutputs[Index]->GetType()) + { + UHoudiniOutput* TempOutput = NewOutputs[Index]; + FHoudiniOutputTranslator::ClearOutput(TempOutput); + NewOutputs[Index] = CurOutput; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Unexpected commandlet output type at index %d!"), Index); + } + } + else + { + HOUDINI_LOG_ERROR(TEXT("Expected output index %d from commandlet to be exist!"), Index); + } + } + } + } + } + + if (bSuccess && FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( + AssetLink, + TOPNode, + *WorkResultObject, + PackageParams, + NewOutputs, + {EHoudiniOutputType::Mesh, EHoudiniOutputType::Instancer}, + &InstancedOutputPartData)) + { + const int32 NumOutputs = NewOutputs.Num(); + for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex) + { + UHoudiniOutput *NewOutput = NewOutputs[OutputIndex]; + + if (NewOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + const FHoudiniPDGImportNodeOutput& Output = InMessage.Outputs[OutputIndex]; + int32 NumObjects = Output.OutputObjects.Num(); + for (int32 Index = 0; Index < NumObjects; ++Index) + { + const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; + FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; + FHoudiniOutputObject *OutputObject = NewOutput->GetOutputObjects().Find(Identifier); + if (OutputObject && IsValid(OutputObject->OutputComponent)) + { + // Update generic property attributes + FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( + OutputObject->OutputComponent, + ImportOutputObject.GenericAttributes.PropertyAttributes); + + // Copy cached attributes + OutputObject->CachedAttributes.Append(ImportOutputObject.CachedAttributes); + } + } + } + } + else + { + bSuccess = false; + } + + if (bSuccess) + { + WorkResultObject->State = EPDGWorkResultState::Loaded; + HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); + // Broadcast that we have loaded the work result object to those interested + AssetLink->OnWorkResultObjectLoaded.Broadcast(AssetLink, TOPNode, WorkResult->WorkItemID, WorkResultObject->Name); + } + else + { + WorkResultObject->State = EPDGWorkResultState::None; + HOUDINI_LOG_WARNING(TEXT("Failed to process loaded assets for %s"), *InMessage.Name); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Commandlet failed to import bgeo for %s"), *InMessage.Name); + } +} + +bool FHoudiniPDGManager::CreateBGEOCommandletAndEndpoint() +{ + if (!BGEOCommandletEndpoint.IsValid()) + { + BGEOCommandletAddress.Invalidate(); + BGEOCommandletEndpoint = FMessageEndpoint::Builder(TEXT("Houdini BGEO Commandlet")) + .Handling(this, &FHoudiniPDGManager::HandleImportBGEOResultMessage) + .Handling(this, &FHoudiniPDGManager::HandleImportBGEODiscoverMessage) + .ReceivingOnThread(ENamedThreads::GameThread); + + if (!BGEOCommandletEndpoint.IsValid()) + { + HOUDINI_LOG_WARNING(TEXT("Could not set up messaging end point for BGEO commandlet")); + return false; + } + + BGEOCommandletEndpoint->Subscribe(); + } + + if (!BGEOCommandletProcHandle.IsValid() || !FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + { + // Start the bgeo commandlet + static const FString BGEOCommandletName = TEXT("HoudiniGeoImport"); + BGEOCommandletGuid = FGuid::NewGuid(); + BGEOCommandletAddress.Invalidate(); + + // Get the absolute path to the project file, if known, otherwise get + // the project name. For the path: quote it for the command line. + IFileManager& FileManager = IFileManager::Get(); + FString ProjectPathOrName = FApp::GetProjectName(); + if (FPaths::IsProjectFilePathSet()) + { + const FString ProjectPath = FPaths::GetProjectFilePath(); + if (!ProjectPath.IsEmpty()) + { + ProjectPathOrName = FString::Printf( + TEXT("\"%s\""), + *FileManager.ConvertToAbsolutePathForExternalAppForRead(*ProjectPath) + ); + } + } + + if (ProjectPathOrName.IsEmpty()) + return false; + + // Get the executable path for the app/editor + FString ExePath = FPlatformProcess::GenerateApplicationPath(FApp::GetName(), FApp::GetBuildConfiguration()); + if (!ExePath.IsEmpty()) + ExePath = FileManager.ConvertToAbsolutePathForExternalAppForRead(*ExePath); + + if (ExePath.IsEmpty()) + return false; + + const FString CommandLineParameters = FString::Printf( + TEXT("%s -messaging -run=%s -guid=%s -listen=%s -managerpid=%d"), + *ProjectPathOrName, + *BGEOCommandletName, + *BGEOCommandletGuid.ToString(), + *BGEOCommandletEndpoint->GetAddress().ToString(), + FPlatformProcess::GetCurrentProcessId()); + + BGEOCommandletProcHandle = FPlatformProcess::CreateProc( + *ExePath, + *CommandLineParameters, + false, + true, + false, + &BGEOCommandletProcessId, + 0, + NULL, + NULL); + if (!BGEOCommandletProcHandle.IsValid()) + { + return false; + } + } + + return true; +} + +void FHoudiniPDGManager::StopBGEOCommandletAndEndpoint() +{ + BGEOCommandletEndpoint.Reset(); + BGEOCommandletAddress.Invalidate(); + BGEOCommandletGuid.Invalidate(); + + if (BGEOCommandletProcHandle.IsValid() && FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + { + FPlatformProcess::TerminateProc(BGEOCommandletProcHandle, true); + if (BGEOCommandletProcHandle.IsValid()) + { + FPlatformProcess::WaitForProc(BGEOCommandletProcHandle); + FPlatformProcess::CloseProc(BGEOCommandletProcHandle); + } + } +} + +EHoudiniBGEOCommandletStatus FHoudiniPDGManager::UpdateAndGetBGEOCommandletStatus() +{ + if (BGEOCommandletProcHandle.IsValid()) + { + if (!FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Crashed; + else if (BGEOCommandletAddress.IsValid()) + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Connected; + else + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Running; + } + else + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::NotStarted; + + return BGEOCommandletStatus; +} + + +bool +FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) +{ + if (InAssetId < 0) + return false; + + // Get all the network nodes within the asset, recursively. + // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type + int32 NetworkNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); + + if (NetworkNodeCount <= 0) + return false; + + TArray AllNetworkNodeIDs; + AllNetworkNodeIDs.SetNum(NetworkNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); + + // There is currently no way to only get non bypassed nodes via HAPI + // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... + TArray AllBypassedTOPNetNodeIDs; + { + int32 BypassedTOPNetNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); + + if (BypassedTOPNetNodeCount > 0) + { + // Get the list of all bypassed TOP Net... + AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); + + // ... and remove them from the network list + for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) + AllNetworkNodeIDs.RemoveAt(Idx); + } + } + } + + // For each Network we found earlier, only consider those with TOP child nodes + // If we find TOP nodes in a valid network, then consider this HDA a PDG HDA + for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) + { + if (CurrentNodeId < 0) + { + continue; + } + + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) + { + continue; + } + + // Skip non TOP or SOP networks + if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP + && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) + { + continue; + } + + // Get the list of all TOP nodes within the current network (ignoring schedulers) + int32 TOPNodeCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) + { + continue; + } + + // We found valid TOP Nodes, this is a PDG HDA + if (TOPNodeCount > 0) + return true; + } + + // No valid TOP node found in SOP/TOP Networks, this is not a PDG HDA + return false; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.h b/Source/HoudiniEngine/Private/HoudiniPDGManager.h index 234a35ee9..83c87030a 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.h @@ -1,200 +1,199 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HAL/PlatformProcess.h" - -#include "MessageEndpoint.h" - -class UHoudiniAssetComponent; -class UHoudiniPDGAssetLink; -class UTOPNetwork; -class UTOPNode; -class FSocket; - -enum class EPDGNodeState : uint8; - -// BGEO commandlet status -enum class HOUDINIENGINE_API EHoudiniBGEOCommandletStatus : uint8 -{ - // PDG manager has not tried to start the commandlet - NotStarted, - // PDG manager has PID for the commandlet and the host OS indicates it is running, but no message has been - // received from it yet - Running, - // PDG manager has PID for the commandlet, the host OS indicates it is running, and a discover message has been - // received - Connected, - // After being in the Connected state, the commandlet stopped running (host OS indicates PID is not valid) - Crashed -}; - -struct HOUDINIENGINE_API FHoudiniPDGManager -{ - -public: - - FHoudiniPDGManager(); - - virtual ~FHoudiniPDGManager(); - - // Initialize the PDG Asset Link for a HoudiniAssetComponent - // returns true if the HAC uses a PDG asset, and a PDGAssetLink was successfully created - bool InitializePDGAssetLink(UHoudiniAssetComponent* InHAC); - - // Updates an existing PDG AssetLink - static bool UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink); - - // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. - static bool PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys=false); - - static void RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink); - - // Indicates if the Asset is a PDG Asset - // This will look for TOP nodes in all SOP/TOP net in the HDA. - static bool IsPDGAsset(const HAPI_NodeId& InAssetId); - - // Given TOP nodes from a TOP network, populate internal state from each TOP node. - static bool PopulateTOPNodes( - const TArray& InTopNodeIDs, - UTOPNetwork* InTOPNetwork, - UHoudiniPDGAssetLink* InPDGAssetLink, - bool bInZeroWorkItemTallys=false); - - // Cook the specified TOP node. - static void CookTOPNode(UTOPNode* InTOPNode); - - // Dirty the specified TOP node and clear its work item results. - static void DirtyTOPNode(UTOPNode* InTOPNode); - - // // Dirty all the tasks/work items of the specified TOP node. Does not - // // clear its work item results. - // static void DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode); - - // Dirty the TOP network and clear all work item results. - static void DirtyAll(UTOPNetwork* InTOPNet); - - // Cook the output TOP node of the currently selected TOP network. - static void CookOutput(UTOPNetwork* InTOPNet); - - // Pause the PDG cook of the currently selected TOP network - static void PauseCook(UTOPNetwork* InTOPNet); - - // Cancel the PDG cook of the currently selected TOP network - static void CancelCook(UTOPNetwork* InTOPNet); - - static void NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess); - - // Update all registered PDG Asset links - void Update(); - - void ReinitializePDGContext(); - - // Clear all of the specified work item's results from the specified TOP node. This destroys any loaded results - // (geometry etc), but keeps the work item struct. - //void ClearWorkItemResult(const HAPI_PDG_GraphContextId& InContextID, const HAPI_PDG_EventInfo& InEventInfo, FTOPNode& TOPNode); - void ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); - - // Clear the specified work item's results from the specified TOP node and remove the work item struct from the TOP - // node. This destroys any loaded results (geometry etc), and the work item struct. - void RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); - - // Create FTOPWorkResult for a given TOP node, and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. - // Geometry is not directly loaded by this function, the FTOPWorkResultObjects' states will be set to ToLoad and - // the ProcessWorkItemResults function will take care of loading the geo. - // Results must be tagged with 'file', and must have a file path, otherwise will not included. - bool CreateWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); - - // Handles replies from commandlets in response to a FHoudiniPDGImportBGEODiscoverMessage - void HandleImportBGEODiscoverMessage( - const struct FHoudiniPDGImportBGEODiscoverMessage& InMessage, - const TSharedRef& InContext); - - // Handles messages sent by the commandlet once an import of a bgeo is complete, and uassets have been created. - void HandleImportBGEOResultMessage( - const struct FHoudiniPDGImportBGEOResultMessage& InMessage, - const TSharedRef& InContext); - - // Create the bgeo commandlet endpoint and start the commandlet (if not already running). - bool CreateBGEOCommandletAndEndpoint(); - - void StopBGEOCommandletAndEndpoint(); - - // Updates and returns the BGEO commandlet status - EHoudiniBGEOCommandletStatus UpdateAndGetBGEOCommandletStatus(); - -private: - - void UpdatePDGContexts(); - - void ProcessWorkItemResults(); - - void ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo); - - static void ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo); - - // Returns the PDGAssetLink and FTOPNode associated with this TOP node ID - bool GetTOPAssetLinkAndNode(const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode); - - void SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState); - - void NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode); - - void NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - - void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - - void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - - void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - - void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - - void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); - -private: - - TArray PDGContextNames; - TArray PDGContextIDs; - TArray PDGEventInfos; - - TArray> PDGAssetLinks; - - int32 MaxNumberOfPDGEvents = 20; - int32 MaxNumberOPDGContexts = 20; - - TSharedPtr BGEOCommandletEndpoint; - FMessageAddress BGEOCommandletAddress; - FProcHandle BGEOCommandletProcHandle; - FGuid BGEOCommandletGuid; - uint32 BGEOCommandletProcessId; - // Keep track of the BGEO commandlet status - EHoudiniBGEOCommandletStatus BGEOCommandletStatus; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HAL/PlatformProcess.h" + +#include "MessageEndpoint.h" + +class UHoudiniAssetComponent; +class UHoudiniPDGAssetLink; +class UTOPNetwork; +class UTOPNode; +class FSocket; + +enum class EPDGNodeState : uint8; + +// BGEO commandlet status +enum class HOUDINIENGINE_API EHoudiniBGEOCommandletStatus : uint8 +{ + // PDG manager has not tried to start the commandlet + NotStarted, + // PDG manager has PID for the commandlet and the host OS indicates it is running, but no message has been + // received from it yet + Running, + // PDG manager has PID for the commandlet, the host OS indicates it is running, and a discover message has been + // received + Connected, + // After being in the Connected state, the commandlet stopped running (host OS indicates PID is not valid) + Crashed +}; + +struct HOUDINIENGINE_API FHoudiniPDGManager +{ + +public: + + FHoudiniPDGManager(); + + virtual ~FHoudiniPDGManager(); + + // Initialize the PDG Asset Link for a HoudiniAssetComponent + // returns true if the HAC uses a PDG asset, and a PDGAssetLink was successfully created + bool InitializePDGAssetLink(UHoudiniAssetComponent* InHAC); + + // Updates an existing PDG AssetLink + static bool UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink); + + // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. + static bool PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys=false); + + static void RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink); + + // Indicates if the Asset is a PDG Asset + // This will look for TOP nodes in all SOP/TOP net in the HDA. + static bool IsPDGAsset(const HAPI_NodeId& InAssetId); + + // Given TOP nodes from a TOP network, populate internal state from each TOP node. + static bool PopulateTOPNodes( + const TArray& InTopNodeIDs, + UTOPNetwork* InTOPNetwork, + UHoudiniPDGAssetLink* InPDGAssetLink, + bool bInZeroWorkItemTallys=false); + + // Cook the specified TOP node. + static void CookTOPNode(UTOPNode* InTOPNode); + + // Dirty the specified TOP node and clear its work item results. + static void DirtyTOPNode(UTOPNode* InTOPNode); + + // // Dirty all the tasks/work items of the specified TOP node. Does not + // // clear its work item results. + // static void DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode); + + // Dirty the TOP network and clear all work item results. + static void DirtyAll(UTOPNetwork* InTOPNet); + + // Cook the output TOP node of the currently selected TOP network. + static void CookOutput(UTOPNetwork* InTOPNet); + + // Pause the PDG cook of the currently selected TOP network + static void PauseCook(UTOPNetwork* InTOPNet); + + // Cancel the PDG cook of the currently selected TOP network + static void CancelCook(UTOPNetwork* InTOPNet); + + static void NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess); + + // Update all registered PDG Asset links + void Update(); + + void ReinitializePDGContext(); + + // Clear all of the specified work item's results from the specified TOP node. This destroys any loaded results + // (geometry etc), but keeps the work item struct. + //void ClearWorkItemResult(const HAPI_PDG_GraphContextId& InContextID, const HAPI_PDG_EventInfo& InEventInfo, FTOPNode& TOPNode); + void ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); + + // Clear the specified work item's results from the specified TOP node and remove the work item struct from the TOP + // node. This destroys any loaded results (geometry etc), and the work item struct. + void RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); + + // Create FTOPWorkResult for a given TOP node, and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. + // Geometry is not directly loaded by this function, the FTOPWorkResultObjects' states will be set to ToLoad and + // the ProcessWorkItemResults function will take care of loading the geo. + // Results must be tagged with 'file', and must have a file path, otherwise will not included. + bool CreateWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); + + // Handles replies from commandlets in response to a FHoudiniPDGImportBGEODiscoverMessage + void HandleImportBGEODiscoverMessage( + const struct FHoudiniPDGImportBGEODiscoverMessage& InMessage, + const TSharedRef& InContext); + + // Handles messages sent by the commandlet once an import of a bgeo is complete, and uassets have been created. + void HandleImportBGEOResultMessage( + const struct FHoudiniPDGImportBGEOResultMessage& InMessage, + const TSharedRef& InContext); + + // Create the bgeo commandlet endpoint and start the commandlet (if not already running). + bool CreateBGEOCommandletAndEndpoint(); + + void StopBGEOCommandletAndEndpoint(); + + // Updates and returns the BGEO commandlet status + EHoudiniBGEOCommandletStatus UpdateAndGetBGEOCommandletStatus(); + +private: + + void UpdatePDGContexts(); + + void ProcessWorkItemResults(); + + void ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo); + + static void ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo); + + // Returns the PDGAssetLink and FTOPNode associated with this TOP node ID + bool GetTOPAssetLinkAndNode(const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode); + + void SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState); + + void NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode); + + void NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + + void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + + void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + + void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + + void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + + void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + +private: + + TArray PDGContextNames; + TArray PDGContextIDs; + TArray PDGEventInfos; + + TArray> PDGAssetLinks; + + int32 MaxNumberOfPDGEvents = 20; + int32 MaxNumberOPDGContexts = 20; + + TSharedPtr BGEOCommandletEndpoint; + FMessageAddress BGEOCommandletAddress; + FProcHandle BGEOCommandletProcHandle; + FGuid BGEOCommandletGuid; + uint32 BGEOCommandletProcessId; + // Keep track of the BGEO commandlet status + EHoudiniBGEOCommandletStatus BGEOCommandletStatus; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp index 29733597c..d80ebc009 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp @@ -1,490 +1,493 @@ - -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGTranslator.h" - - -#include "Editor.h" -#include "Containers/Array.h" -#include "FileHelpers.h" -#include "LandscapeInfo.h" -// #include "Engine/WorldComposition.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniPackageParams.h" -#include "HoudiniOutput.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniSplineTranslator.h" - -#define LOCTEXT_NAMESPACE "HoudiniEngine" - -bool -FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray InOutputTypesToProcess, - bool bInTreatExistingMaterialsAsUpToDate) -{ - if (!IsValid(InAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InAssetLink is null.")); - return false; - } - - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InTOPNode is null.")); - return false; - } - - TArray OldTOPOutputs = InWorkResultObject.GetResultOutputs(); - TArray NewTOPOutputs; - - FHoudiniEngine::Get().CreateTaskSlateNotification(LOCTEXT("LoadPDGBGEO", "Loading PDG Output BGEO File...")); - - bool bResult = false; - // Create a new file node in HAPI for the bgeo and cook it - HAPI_NodeId FileNodeId = -1; - bResult = UHoudiniGeoImporter::OpenBGEOFile(InWorkResultObject.FilePath, FileNodeId); - if (bResult) - bResult = UHoudiniGeoImporter::CookFileNode(FileNodeId); - - // If the cook was successful, build outputs - if (bResult) - { - FHoudiniEngine::Get().UpdateTaskSlateNotification( - LOCTEXT("BuildPDGBGEOOutputs", "Building Ouputs from BGEO File...")); - - const bool bAddOutputsToRootSet = false; - bResult = UHoudiniGeoImporter::BuildAllOutputsForNode( - FileNodeId, - InAssetLink, - OldTOPOutputs, - NewTOPOutputs, - bAddOutputsToRootSet); - } - - if (bResult) - { - FHoudiniEngine::Get().UpdateTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); - - // If we successfully received outputs from the BGEO file, process the outputs - FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); - AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - if (!IsValid(WorkItemOutputActor)) - { - UWorld* World = InAssetLink->GetWorld(); - FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); - AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - if (!IsValid(TOPNodeOutputActor)) - { - if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) - TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - } - if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) - WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - } - - for (auto& OldOutput : OldTOPOutputs) - { - FHoudiniOutputTranslator::ClearOutput(OldOutput); - } - OldTOPOutputs.Empty(); - InWorkResultObject.GetResultOutputs().Empty(); - InWorkResultObject.SetResultOutputs(NewTOPOutputs); - - bResult = CreateAllResultObjectsFromPDGOutputs( - NewTOPOutputs, - InPackageParams, - WorkItemOutputActor->GetRootComponent(), - InOutputTypesToProcess, - bInTreatExistingMaterialsAsUpToDate); - - if (!bResult) - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); - else - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); - - InTOPNode->UpdateOutputVisibilityInLevel(); - } - else - { - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("BuildPDGBGEOOutputsFail", "Failed building outputs from BGEO file...")); - } - - // Delete the file node used to load the BGEO via HAPI - if (FileNodeId >= 0) - { - UHoudiniGeoImporter::CloseBGEOFile(FileNodeId); - FileNodeId = -1; - } - - return bResult; -} - -bool -FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray& InOutputs, - TArray InOutputTypesToProcess, - const TMap* InPreBuiltInstancedOutputPartData) -{ - if (!IsValid(InAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InAssetLink is null.")); - return false; - } - - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InTOPNode is null.")); - return false; - } - - FHoudiniEngine::Get().CreateTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); - - // If we successfully received outputs from the BGEO file, process the outputs - FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); - AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - if (!IsValid(WorkItemOutputActor)) - { - UWorld* World = InAssetLink->GetWorld(); - FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); - AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - if (!IsValid(TOPNodeOutputActor)) - { - if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) - TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - } - if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) - WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - } - - InWorkResultObject.SetResultOutputs(InOutputs); - - const bool bInTreatExistingMaterialsAsUpToDate = true; - const bool bOnlyUseExistingAssets = true; - bool bResult = CreateAllResultObjectsFromPDGOutputs( - InOutputs, - InPackageParams, - WorkItemOutputActor->GetRootComponent(), - InOutputTypesToProcess, - bInTreatExistingMaterialsAsUpToDate, - bOnlyUseExistingAssets, - InPreBuiltInstancedOutputPartData); - - if (!bResult) - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); - else - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); - - InTOPNode->UpdateOutputVisibilityInLevel(); - - return bResult; -} - -bool -FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( - TArray& InOutputs, - const FHoudiniPackageParams& InPackageParams, - UObject* InOuterComponent, - TArray InOutputTypesToProcess, - bool bInTreatExistingMaterialsAsUpToDate, - bool bInOnlyUseExistingAssets, - const TMap* InPreBuiltInstancedOutputPartData) -{ - // Process the new/updated outputs via the various translators - // We try to maintain as much parity with the existing HoudiniAssetComponent workflow - // as possible. - - // // For world composition landscapes - // FString WorldCompositionPath = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - // if (bInUseWorldComposition) - // { - // FHoudiniLandscapeTranslator::EnableWorldComposition(); - // - // // Save the current map as well if world composition is enabled. - // FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); - // UWorld* CurrentWorld = EditorWorldContext.World(); - // - // if (CurrentWorld) - // { - // // Save the current map - // FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); - // UPackage* CurrentWorldPackage = CreatePackage(*CurrentWorldPath); - // if (CurrentWorldPackage) - // { - // CurrentWorldPackage->MarkPackageDirty(); - // - // TArray CurrentWorldToSave; - // CurrentWorldToSave.Add(CurrentWorldPackage); - // - // FEditorFileUtils::PromptForCheckoutAndSave(CurrentWorldToSave, true, false); - // - // WorldCompositionPath = FPackageName::GetLongPackagePath(CurrentWorldPackage->GetName()); - // } - // } - // } - - // // Before processing any of the output, - // // we need to get the min/max value for all Height volumes in this output (if any) - TMap LandscapeLayerGlobalMinimums; - TMap LandscapeLayerGlobalMaximums; - - for (UHoudiniOutput* CurOutput : InOutputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType == EHoudiniOutputType::Landscape) - { - // Populate all layer minimums/maximums with default values since, in PDG mode, - // we can't collect the values across all tiles. The user would have to do this - // manually in the Topnet. - FHoudiniLandscapeTranslator::GetLayersZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums); - } - } - - TArray InstancerOutputs; - TArray LandscapeOutputs; - TArray CreatedPackages; - - //bool bCreatedNewMaps = false; - UWorld* PersistentWorld = InOuterComponent->GetTypedOuter(); - check(PersistentWorld); - - for (UHoudiniOutput* CurOutput : InOutputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (InOutputTypesToProcess.Num() > 0 && !InOutputTypesToProcess.Contains(OutputType)) - { - continue; - } - switch (OutputType) - { - case EHoudiniOutputType::Mesh: - { - const bool bInDestroyProxies = false; - if (bInOnlyUseExistingAssets) - { - const bool bInApplyGenericProperties = false; - TMap NewOutputObjects(CurOutput->GetOutputObjects()); - FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - CurOutput, - InOuterComponent, - NewOutputObjects, - bInDestroyProxies, - bInApplyGenericProperties - ); - } - else - { - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - InPackageParams, - EHoudiniStaticMeshMethod::RawMesh, - InOuterComponent, - bInTreatExistingMaterialsAsUpToDate, - bInDestroyProxies - ); - } - } - break; - - case EHoudiniOutputType::Curve: - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, InOuterComponent); - break; - } - - case EHoudiniOutputType::Instancer: - { - InstancerOutputs.Add(CurOutput); - } - break; - - case EHoudiniOutputType::Landscape: - { - TArray EmptyInputLandscapes; - // Retrieve the topnet parent to which Sharedlandscapes will be attached. - AActor* WorkItemActor = InOuterComponent->GetTypedOuter(); - USceneComponent* TopnetParent = nullptr; - if (WorkItemActor) - { - AActor* TopnetParentActor = WorkItemActor->GetAttachParentActor(); - if (TopnetParentActor) - { - TopnetParent = TopnetParentActor->GetRootComponent(); - } - } - TArray> CreatedUntrackedOutputs; - - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - CreatedUntrackedOutputs, - EmptyInputLandscapes, - EmptyInputLandscapes, - TopnetParent, - TEXT("{hda_actor_name}_{pdg_topnet_name}_"), - PersistentWorld, - LandscapeLayerGlobalMinimums, - LandscapeLayerGlobalMaximums, - InPackageParams, - //bCreatedNewMaps, - CreatedPackages); - // Attach any landscape actors to InOuterComponent - LandscapeOutputs.Add(CurOutput); - } - break; - - default: - { - HOUDINI_LOG_WARNING(TEXT("[FTOPWorkResultObject::UpdateResultOutputs]: Unsupported output type: %s"), *UHoudiniOutput::OutputTypeToString(OutputType)); - } - break; - } - } - - // Process instancer outputs after all other outputs have been processed, since it - // might depend on meshes etc from other outputs - if (InstancerOutputs.Num() > 0) - { - for (UHoudiniOutput* CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - CurOutput, - InOutputs, - InOuterComponent, - InPreBuiltInstancedOutputPartData - ); - } - } - - - USceneComponent* ParentComponent = Cast(InOuterComponent); - - if (ParentComponent) - { - AActor* ParentActor = ParentComponent->GetOwner(); - for (UHoudiniOutput* LandscapeOutput : LandscapeOutputs) - { - for (auto& Pair : LandscapeOutput->GetOutputObjects()) - { - FHoudiniOutputObject &OutputObject = Pair.Value; - - // If the OutputObject has an OutputComponent, try to attach it to ParentComponent - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) - { - USceneComponent* Component = Cast(OutputObject.OutputComponent); - if (IsValid(Component) && !Component->IsAttachedTo(ParentComponent)) - { - Component->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepWorldTransform); - continue; - } - } - - // If OutputObject does not have an OutputComponent, check if OutputObject is an AActor and attach - // it to ParentComponent - if (IsValid(OutputObject.OutputObject)) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - if (IsValid(LandscapePtr)) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - if (IsValid(LandscapeProxy)) - { - if (!LandscapeProxy->IsAttachedTo(ParentActor)) - { - LandscapeProxy->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform); - LandscapeProxy->RecreateCollisionComponents(); - } - - if (LandscapeProxy) - { - // We need to recreate component states for landscapes if a tile was created, moved, or resized - // otherwise the landscape will exhibit render artifacts (such as only rendering every other - // component.) - LandscapeProxy->RecreateComponentsState(); - } - } - - } - } - } - } - } - - /* - if (bCreatedNewMaps && PersistentWorld) - { - // Force the asset registry to update its cache of packages paths - // recursively for this world, otherwise world composition won't - // pick them up during the WorldComposition::Rescan(). - FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); - - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - - if (IsValid(PersistentWorld->WorldComposition)) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); - } - - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); - } - */ - - if (CreatedPackages.Num() > 0) - { - // Save created packages. For example, we don't want landscape layers deleted - // along with the HDA. - FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); - } - - return true; -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGTranslator.h" + + +#include "Editor.h" +#include "Containers/Array.h" +#include "FileHelpers.h" +#include "LandscapeInfo.h" +// #include "Engine/WorldComposition.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniPackageParams.h" +#include "HoudiniOutput.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniSplineTranslator.h" + +#define LOCTEXT_NAMESPACE "HoudiniEngine" + +bool +FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray InOutputTypesToProcess, + bool bInTreatExistingMaterialsAsUpToDate) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem); + + if (!IsValid(InAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InAssetLink is null.")); + return false; + } + + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InTOPNode is null.")); + return false; + } + + TArray OldTOPOutputs = InWorkResultObject.GetResultOutputs(); + TArray NewTOPOutputs; + + FHoudiniEngine::Get().CreateTaskSlateNotification(LOCTEXT("LoadPDGBGEO", "Loading PDG Output BGEO File...")); + + bool bResult = false; + // Create a new file node in HAPI for the bgeo and cook it + HAPI_NodeId FileNodeId = -1; + bResult = UHoudiniGeoImporter::OpenBGEOFile(InWorkResultObject.FilePath, FileNodeId); + if (bResult) + bResult = UHoudiniGeoImporter::CookFileNode(FileNodeId); + + // If the cook was successful, build outputs + if (bResult) + { + FHoudiniEngine::Get().UpdateTaskSlateNotification( + LOCTEXT("BuildPDGBGEOOutputs", "Building Ouputs from BGEO File...")); + + const bool bAddOutputsToRootSet = false; + bResult = UHoudiniGeoImporter::BuildAllOutputsForNode( + FileNodeId, + InAssetLink, + OldTOPOutputs, + NewTOPOutputs, + bAddOutputsToRootSet); + } + + if (bResult) + { + FHoudiniEngine::Get().UpdateTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); + + // If we successfully received outputs from the BGEO file, process the outputs + FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); + AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + if (!IsValid(WorkItemOutputActor)) + { + UWorld* World = InAssetLink->GetWorld(); + FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); + AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + if (!IsValid(TOPNodeOutputActor)) + { + if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) + TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + } + if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) + WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + } + + for (auto& OldOutput : OldTOPOutputs) + { + FHoudiniOutputTranslator::ClearOutput(OldOutput); + } + OldTOPOutputs.Empty(); + InWorkResultObject.GetResultOutputs().Empty(); + InWorkResultObject.SetResultOutputs(NewTOPOutputs); + + bResult = CreateAllResultObjectsFromPDGOutputs( + NewTOPOutputs, + InPackageParams, + WorkItemOutputActor->GetRootComponent(), + InOutputTypesToProcess, + bInTreatExistingMaterialsAsUpToDate); + + if (!bResult) + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); + else + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); + + InTOPNode->UpdateOutputVisibilityInLevel(); + } + else + { + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("BuildPDGBGEOOutputsFail", "Failed building outputs from BGEO file...")); + } + + // Delete the file node used to load the BGEO via HAPI + if (FileNodeId >= 0) + { + UHoudiniGeoImporter::CloseBGEOFile(FileNodeId); + FileNodeId = -1; + } + + return bResult; +} + +bool +FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray& InOutputs, + TArray InOutputTypesToProcess, + const TMap* InPreBuiltInstancedOutputPartData) +{ + if (!IsValid(InAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InAssetLink is null.")); + return false; + } + + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InTOPNode is null.")); + return false; + } + + FHoudiniEngine::Get().CreateTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); + + // If we successfully received outputs from the BGEO file, process the outputs + FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); + AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + if (!IsValid(WorkItemOutputActor)) + { + UWorld* World = InAssetLink->GetWorld(); + FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); + AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + if (!IsValid(TOPNodeOutputActor)) + { + if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) + TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + } + if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) + WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + } + + InWorkResultObject.SetResultOutputs(InOutputs); + + const bool bInTreatExistingMaterialsAsUpToDate = true; + const bool bOnlyUseExistingAssets = true; + bool bResult = CreateAllResultObjectsFromPDGOutputs( + InOutputs, + InPackageParams, + WorkItemOutputActor->GetRootComponent(), + InOutputTypesToProcess, + bInTreatExistingMaterialsAsUpToDate, + bOnlyUseExistingAssets, + InPreBuiltInstancedOutputPartData); + + if (!bResult) + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); + else + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); + + InTOPNode->UpdateOutputVisibilityInLevel(); + + return bResult; +} + +bool +FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( + TArray& InOutputs, + const FHoudiniPackageParams& InPackageParams, + UObject* InOuterComponent, + TArray InOutputTypesToProcess, + bool bInTreatExistingMaterialsAsUpToDate, + bool bInOnlyUseExistingAssets, + const TMap* InPreBuiltInstancedOutputPartData) +{ + // Process the new/updated outputs via the various translators + // We try to maintain as much parity with the existing HoudiniAssetComponent workflow + // as possible. + + // // For world composition landscapes + // FString WorldCompositionPath = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + // if (bInUseWorldComposition) + // { + // FHoudiniLandscapeTranslator::EnableWorldComposition(); + // + // // Save the current map as well if world composition is enabled. + // FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); + // UWorld* CurrentWorld = EditorWorldContext.World(); + // + // if (CurrentWorld) + // { + // // Save the current map + // FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); + // UPackage* CurrentWorldPackage = CreatePackage(nullptr, *CurrentWorldPath); + // if (CurrentWorldPackage) + // { + // CurrentWorldPackage->MarkPackageDirty(); + // + // TArray CurrentWorldToSave; + // CurrentWorldToSave.Add(CurrentWorldPackage); + // + // FEditorFileUtils::PromptForCheckoutAndSave(CurrentWorldToSave, true, false); + // + // WorldCompositionPath = FPackageName::GetLongPackagePath(CurrentWorldPackage->GetName()); + // } + // } + // } + + // // Before processing any of the output, + // // we need to get the min/max value for all Height volumes in this output (if any) + TMap LandscapeLayerGlobalMinimums; + TMap LandscapeLayerGlobalMaximums; + + for (UHoudiniOutput* CurOutput : InOutputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType == EHoudiniOutputType::Landscape) + { + // Populate all layer minimums/maximums with default values since, in PDG mode, + // we can't collect the values across all tiles. The user would have to do this + // manually in the Topnet. + FHoudiniLandscapeTranslator::GetLayersZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums); + } + } + + TArray InstancerOutputs; + TArray LandscapeOutputs; + TArray CreatedPackages; + + //bool bCreatedNewMaps = false; + UWorld* PersistentWorld = InOuterComponent->GetTypedOuter(); + check(PersistentWorld); + + for (UHoudiniOutput* CurOutput : InOutputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (InOutputTypesToProcess.Num() > 0 && !InOutputTypesToProcess.Contains(OutputType)) + { + continue; + } + switch (OutputType) + { + case EHoudiniOutputType::Mesh: + { + const bool bInDestroyProxies = false; + if (bInOnlyUseExistingAssets) + { + const bool bInApplyGenericProperties = false; + TMap NewOutputObjects(CurOutput->GetOutputObjects()); + FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + CurOutput, + InOuterComponent, + NewOutputObjects, + bInDestroyProxies, + bInApplyGenericProperties + ); + } + else + { + FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + InPackageParams, + EHoudiniStaticMeshMethod::RawMesh, + SMGP, + InOuterComponent, + bInTreatExistingMaterialsAsUpToDate, + bInDestroyProxies + ); + } + } + break; + + case EHoudiniOutputType::Curve: + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, InOuterComponent); + break; + } + + case EHoudiniOutputType::Instancer: + { + InstancerOutputs.Add(CurOutput); + } + break; + + case EHoudiniOutputType::Landscape: + { + TArray EmptyInputLandscapes; + // Retrieve the topnet parent to which Sharedlandscapes will be attached. + AActor* WorkItemActor = InOuterComponent->GetTypedOuter(); + USceneComponent* TopnetParent = nullptr; + if (WorkItemActor) + { + AActor* TopnetParentActor = WorkItemActor->GetAttachParentActor(); + if (TopnetParentActor) + { + TopnetParent = TopnetParentActor->GetRootComponent(); + } + } + TArray> CreatedUntrackedOutputs; + + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + CreatedUntrackedOutputs, + EmptyInputLandscapes, + EmptyInputLandscapes, + TopnetParent, + TEXT("{hda_actor_name}_{pdg_topnet_name}_"), + PersistentWorld, + LandscapeLayerGlobalMinimums, + LandscapeLayerGlobalMaximums, + InPackageParams, + //bCreatedNewMaps, + CreatedPackages); + // Attach any landscape actors to InOuterComponent + LandscapeOutputs.Add(CurOutput); + } + break; + + default: + { + HOUDINI_LOG_WARNING(TEXT("[FTOPWorkResultObject::UpdateResultOutputs]: Unsupported output type: %s"), *UHoudiniOutput::OutputTypeToString(OutputType)); + } + break; + } + } + + // Process instancer outputs after all other outputs have been processed, since it + // might depend on meshes etc from other outputs + if (InstancerOutputs.Num() > 0) + { + for (UHoudiniOutput* CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + CurOutput, + InOutputs, + InOuterComponent, + InPreBuiltInstancedOutputPartData + ); + } + } + + + USceneComponent* ParentComponent = Cast(InOuterComponent); + + if (ParentComponent) + { + AActor* ParentActor = ParentComponent->GetOwner(); + for (UHoudiniOutput* LandscapeOutput : LandscapeOutputs) + { + for (auto& Pair : LandscapeOutput->GetOutputObjects()) + { + FHoudiniOutputObject &OutputObject = Pair.Value; + + // If the OutputObject has an OutputComponent, try to attach it to ParentComponent + if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) + { + USceneComponent* Component = Cast(OutputObject.OutputComponent); + if (IsValid(Component) && !Component->IsAttachedTo(ParentComponent)) + { + Component->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepWorldTransform); + continue; + } + } + + // If OutputObject does not have an OutputComponent, check if OutputObject is an AActor and attach + // it to ParentComponent + if (IsValid(OutputObject.OutputObject)) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + if (IsValid(LandscapePtr)) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapeProxy)) + { + if (!LandscapeProxy->IsAttachedTo(ParentActor)) + { + LandscapeProxy->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform); + LandscapeProxy->RecreateCollisionComponents(); + } + + if (LandscapeProxy) + { + // We need to recreate component states for landscapes if a tile was created, moved, or resized + // otherwise the landscape will exhibit render artifacts (such as only rendering every other + // component.) + LandscapeProxy->RecreateComponentsState(); + } + } + + } + } + } + } + } + + /* + if (bCreatedNewMaps && PersistentWorld) + { + // Force the asset registry to update its cache of packages paths + // recursively for this world, otherwise world composition won't + // pick them up during the WorldComposition::Rescan(). + FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); + + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + + if (IsValid(PersistentWorld->WorldComposition)) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); + } + + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); + } + */ + + if (CreatedPackages.Num() > 0) + { + // Save created packages. For example, we don't want landscape layers deleted + // along with the HDA. + FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); + } + + return true; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h index 2419b3c81..07d466767 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h @@ -1,75 +1,75 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -class UHoudiniPDGAssetLink; -class UHoudiniOutput; -class AActor; -class UTOPNode; - -enum class EHoudiniOutputType : uint8; - -struct FHoudiniPackageParams; -struct FTOPWorkResultObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniInstancedOutputPartData; - -struct HOUDINIENGINE_API FHoudiniPDGTranslator -{ - public: - // Create/update assets/geometry for all PDG outputs of InWorkResultObject. This will use - // InWorkResultObject.FilePath to load the BGEO file and update/build outputs. - static bool CreateAllResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray InOutputTypesToProcess={}, - bool bInTreatExistingMaterialsAsUpToDate=false); - - static bool LoadExistingAssetsAsResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray& InOutputs, - TArray InOutputTypesToProcess={}, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); - - // Use the relevant translators to create assets/geometry for all PDG outputs (InOutputs). - // InOuterComponent is the component to attach the created output objects/components to. - static bool CreateAllResultObjectsFromPDGOutputs( - TArray& InOutputs, - const FHoudiniPackageParams& InPackageParams, - UObject* InOuterComponent, - TArray InOutputTypesToProcess={}, - bool bInTreatExistingMaterialsAsUpToDate=false, - bool bInOnlyUseExistingAssets=false, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +class UHoudiniPDGAssetLink; +class UHoudiniOutput; +class AActor; +class UTOPNode; + +enum class EHoudiniOutputType : uint8; + +struct FHoudiniPackageParams; +struct FTOPWorkResultObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniInstancedOutputPartData; + +struct HOUDINIENGINE_API FHoudiniPDGTranslator +{ + public: + // Create/update assets/geometry for all PDG outputs of InWorkResultObject. This will use + // InWorkResultObject.FilePath to load the BGEO file and update/build outputs. + static bool CreateAllResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray InOutputTypesToProcess={}, + bool bInTreatExistingMaterialsAsUpToDate=false); + + static bool LoadExistingAssetsAsResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray& InOutputs, + TArray InOutputTypesToProcess={}, + const TMap* InPreBuiltInstancedOutputPartData=nullptr); + + // Use the relevant translators to create assets/geometry for all PDG outputs (InOutputs). + // InOuterComponent is the component to attach the created output objects/components to. + static bool CreateAllResultObjectsFromPDGOutputs( + TArray& InOutputs, + const FHoudiniPackageParams& InPackageParams, + UObject* InOuterComponent, + TArray InOutputTypesToProcess={}, + bool bInTreatExistingMaterialsAsUpToDate=false, + bool bInOnlyUseExistingAssets=false, + const TMap* InPreBuiltInstancedOutputPartData=nullptr); +}; diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp index c320de333..4cd3bd031 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp @@ -1,435 +1,435 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPackageParams.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniStringResolver.h" - -#include "PackageTools.h" -#include "ObjectTools.h" -#include "Engine/StaticMesh.h" -#include "UObject/MetaData.h" - -// -FHoudiniPackageParams::FHoudiniPackageParams() -{ - PackageMode = EPackageMode::CookToTemp; - ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - OuterPackage = nullptr; - ObjectName = FString(); - HoudiniAssetName = FString(); - HoudiniAssetActorName = FString(); - - ObjectId = 0; - GeoId = 0; - PartId = 0; - SplitStr = 0; - - ComponentGUID.Invalidate(); - - PDGTOPNetworkName.Empty(); - PDGTOPNodeName.Empty(); - PDGWorkItemIndex = INDEX_NONE; - - bAttemptToLoadMissingPackages = false; -} - - -// -FHoudiniPackageParams::~FHoudiniPackageParams() -{ - - -} - - -// Returns the object flags corresponding to the current package mode -EObjectFlags -FHoudiniPackageParams::GetObjectFlags() const -{ - if (PackageMode == EPackageMode::CookToTemp) - return RF_Public | RF_Standalone; - else if (PackageMode == EPackageMode::Bake) - return RF_Public | RF_Standalone; - else - return RF_NoFlags; -} - -FString -FHoudiniPackageParams::GetPackageName() const -{ - if (!ObjectName.IsEmpty()) - return ObjectName; - - // If we have PDG infos, generate a name including them - if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkItemIndex >= 0) - { - return FString::Printf( - TEXT("%s_%s_%s_%d_%d_%s"), - *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkItemIndex, PartId, *SplitStr); - } - else - { - // Generate an object name using the HGPO IDs and the HDA name - return FString::Printf(TEXT("%s_%d_%d_%d_%s"), *HoudiniAssetName, ObjectId, GeoId, PartId, *SplitStr); - } -} - -FString -FHoudiniPackageParams::GetPackagePath() const -{ - FString PackagePath = FString(); - switch (PackageMode) - { - case EPackageMode::CookToLevel: - { - // Path to the persistent level - //PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()); - - // In this mode, we'll use the persistent level as our package's outer - // simply use the hda + component guid for the path - // Add a subdir for the HDA - if (!HoudiniAssetName.IsEmpty()) - PackagePath += TEXT("/") + HoudiniAssetName; - // Add a subdir using the owner component GUID if possible - if(ComponentGUID.IsValid()) - PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); - - // TODO: FIX ME!!! - // Old version - // Build the package name - PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()) + - TEXT("/") + - HoudiniAssetName; - } - break; - - case EPackageMode::CookToTemp: - { - // Temporary Folder - PackagePath = TempCookFolder; - // Add a subdir for the HDA - if (!HoudiniAssetName.IsEmpty()) - PackagePath += TEXT("/") + HoudiniAssetName; - // Add a subdir using the owner component GUID if possible - if (ComponentGUID.IsValid()) - PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); - } - break; - - case EPackageMode::Bake: - { - PackagePath = BakeFolder; - } - break; - } - - return PackagePath; -} - -bool -FHoudiniPackageParams::GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter) -{ - OutBakeCounter = 0; - - if (!IsValid(InAsset)) - return false; - - UPackage* Package = InAsset->GetPackage(); - // const FString PackagePathName = Package->GetPathName(); - // FString PackagePathNamePrefix; - // FString BakeCountOrGUID; - // if (!GetPackageNameWithoutBakeCounterOrGUIDSuffix(PackagePathName, PackagePathNamePrefix, BakeCountOrGUID)) - // PackagePathNamePrefix = PackagePathName; - // - // const FString ThisPackageNameBase = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); - // if (!PackagePathNamePrefix.Equals(ThisPackageNameBase)) - // return false; - // - // // Not a valid counter suffix, could be a GUID suffix. Return true since the prefixes match. - // if (BakeCountOrGUID.IsNumeric()) - // OutBakeCounter = FCString::Atoi(*BakeCountOrGUID); - // - // return true; - - if (!IsValid(Package)) - return false; - - UMetaData* MetaData = Package->GetMetaData(); - if (!IsValid(MetaData)) - return false; - - if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER)) - { - FString BakeCounterStr = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER); - BakeCounterStr.TrimStartAndEndInline(); - if (BakeCounterStr.IsNumeric()) - { - OutBakeCounter = FCString::Atoi(*BakeCounterStr); - return true; - } - } - - return false; -} - -bool -FHoudiniPackageParams::GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID) -{ - if (!InAsset) - return false; - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return false; - - UMetaData* MetaData = Package->GetMetaData(); - if (!IsValid(MetaData)) - return false; - - if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) - { - OutGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); - OutGUID.TrimStartAndEndInline(); - if (!OutGUID.IsEmpty()) - return true; - } - - return false; -} - -FString -FHoudiniPackageParams::GetPackageNameExcludingBakeCounter(const UObject* InAsset) -{ - if (!IsValid(InAsset)) - return FString(); - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return FString(); - - FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); - int32 BakeCounter = 0; - if (GetBakeCounterFromBakedAsset(InAsset, BakeCounter)) - { - const FString BakeCounterSuffix = FString::Printf(TEXT("_%d"), BakeCounter); - if (PackageName.EndsWith(BakeCounterSuffix)) - PackageName = PackageName.Mid(0, PackageName.Len() - BakeCounterSuffix.Len()); - } - - return PackageName; -} - -bool -FHoudiniPackageParams::MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const -{ - if (!IsValid(InAsset)) - return false; - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return false; - - const FString InAssetPackagePathName = FPaths::GetPath(Package->GetPathName()) + TEXT("/") + GetPackageNameExcludingBakeCounter(InAsset); - const FString ThisPackagePathName = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); - return InAssetPackagePathName.Equals(ThisPackagePathName); -} - -FString -FHoudiniPackageParams::GetPackageNameExcludingGUID(const UObject* InAsset) -{ - if (!IsValid(InAsset)) - return FString(); - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return FString(); - - FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); - FString GUIDStr; - if (GetGUIDFromTempAsset(InAsset, GUIDStr)) - { - if (PackageName.EndsWith(TEXT("_") + GUIDStr)) - PackageName = PackageName.Mid(0, PackageName.Len() - GUIDStr.Len() - 1); - } - - return PackageName; -} - -UPackage* -FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart) const -{ - // GUID/counter used to differentiate with existing package - int32 BakeCounter = InBakeCounterStart; - FGuid CurrentGuid = FGuid::NewGuid(); - - // Get the appropriate package path/name for this object - FString PackageName = GetPackageName(); - FString PackagePath = GetPackagePath(); - - // Iterate until we find a suitable name for the package - UPackage * NewPackage = nullptr; - while (true) - { - OutPackageName = PackageName; - - // Append the Bake guid/counter to the object name if needed - if (BakeCounter > 0) - { - OutPackageName += (PackageMode == EPackageMode::Bake) - ? TEXT("_") + FString::FromInt(BakeCounter) - : TEXT("_") + CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); - } - - // Build the final package name - FString FinalPackageName = PackagePath + TEXT("/") + OutPackageName; - // Sanitize package name. - FinalPackageName = UPackageTools::SanitizePackageName(FinalPackageName); - - UObject * PackageOuter = nullptr; - /* - // As of UE4.26, it is not possible anymore to create package with a non null outer - // CookToLevel is, anyway, no logner supported in v2. - if (PackageMode == EPackageMode::CookToLevel) - { - // If we are not baking, then use outermost package, since objects within our package - // need to be visible to external operations, such as copy paste. - PackageOuter = OuterPackage; - } - */ - - // See if a package named similarly already exists - UPackage* FoundPackage = FindPackage(PackageOuter, *FinalPackageName); - if (FoundPackage == nullptr && bAttemptToLoadMissingPackages) - { - FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_NoWarn); - } - if (ReplaceMode == EPackageReplaceMode::CreateNewAssets - && FoundPackage && !FoundPackage->IsPendingKill()) - { - // we need to generate a new name for it - CurrentGuid = FGuid::NewGuid(); - BakeCounter++; - continue; - } - - // Create actual package. - NewPackage = CreatePackage(*FinalPackageName); - if (IsValid(NewPackage)) - { - // Record bake counter / temp GUID in package metadata - UMetaData* MetaData = NewPackage->GetMetaData(); - if (IsValid(MetaData)) - { - if (PackageMode == EPackageMode::Bake) - { - // HOUDINI_LOG_MESSAGE(TEXT("Recording bake counter in package metadata: %d"), BakeCounter); - MetaData->RootMetaDataMap.Add( - HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER, FString::FromInt(BakeCounter)); - } - else if (CurrentGuid.IsValid()) - { - const FString GuidStr = CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); - // HOUDINI_LOG_MESSAGE(TEXT("Recording temp guid in package metadata: %s"), *GuidStr); - MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_TEMP_GUID, GuidStr); - } - } - } - - break; - } - - return NewPackage; -} - - -// Fixes link error with the template function under -void TemplateFixer() -{ - FHoudiniPackageParams PP; - UStaticMesh* SM = PP.CreateObjectAndPackage(); - UHoudiniStaticMesh* HSM = PP.CreateObjectAndPackage(); - //UMaterial* Mat = PP.CreateObjectAndPackage(); - //UTexture2D* Text = PP.CreateObjectAndPackage(); -} - -template -T* FHoudiniPackageParams::CreateObjectAndPackage() -{ - // Create the package for the object - FString NewObjectName; - UPackage* Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) - return nullptr; - - const FString SanitizedObjectName = ObjectTools::SanitizeObjectName(NewObjectName); - - T* ExistingTypedObject = FindObject(Package, *NewObjectName); - UObject* ExistingObject = FindObject(Package, *NewObjectName); - - if (ExistingTypedObject != nullptr && !ExistingTypedObject->IsPendingKill()) - { - // An object of the appropriate type already exists, update it! - ExistingTypedObject->PreEditChange(nullptr); - } - else if (ExistingObject != nullptr) - { - // Replacing an object of a different type, Delete it first. - const bool bDeleteSucceeded = ObjectTools::DeleteSingleObject(ExistingObject); - if (bDeleteSucceeded) - { - // Force GC so we can cleanly create a new asset (and not do an 'in place' replacement) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - // Create a package for each mesh - Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) - return nullptr; - } - else - { - // failed to delete - return nullptr; - } - } - - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); - - return NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPackageParams.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniStringResolver.h" + +#include "PackageTools.h" +#include "ObjectTools.h" +#include "Engine/StaticMesh.h" +#include "UObject/MetaData.h" + +// +FHoudiniPackageParams::FHoudiniPackageParams() +{ + PackageMode = EPackageMode::CookToTemp; + ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + OuterPackage = nullptr; + ObjectName = FString(); + HoudiniAssetName = FString(); + HoudiniAssetActorName = FString(); + + ObjectId = 0; + GeoId = 0; + PartId = 0; + SplitStr = 0; + + ComponentGUID.Invalidate(); + + PDGTOPNetworkName.Empty(); + PDGTOPNodeName.Empty(); + PDGWorkItemIndex = INDEX_NONE; + + bAttemptToLoadMissingPackages = false; +} + + +// +FHoudiniPackageParams::~FHoudiniPackageParams() +{ + + +} + + +// Returns the object flags corresponding to the current package mode +EObjectFlags +FHoudiniPackageParams::GetObjectFlags() const +{ + if (PackageMode == EPackageMode::CookToTemp) + return RF_Public | RF_Standalone; + else if (PackageMode == EPackageMode::Bake) + return RF_Public | RF_Standalone; + else + return RF_NoFlags; +} + +FString +FHoudiniPackageParams::GetPackageName() const +{ + if (!ObjectName.IsEmpty()) + return ObjectName; + + // If we have PDG infos, generate a name including them + if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkItemIndex >= 0) + { + return FString::Printf( + TEXT("%s_%s_%s_%d_%d_%s"), + *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkItemIndex, PartId, *SplitStr); + } + else + { + // Generate an object name using the HGPO IDs and the HDA name + return FString::Printf(TEXT("%s_%d_%d_%d_%s"), *HoudiniAssetName, ObjectId, GeoId, PartId, *SplitStr); + } +} + +FString +FHoudiniPackageParams::GetPackagePath() const +{ + FString PackagePath = FString(); + switch (PackageMode) + { + case EPackageMode::CookToLevel: + { + // Path to the persistent level + //PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()); + + // In this mode, we'll use the persistent level as our package's outer + // simply use the hda + component guid for the path + // Add a subdir for the HDA + if (!HoudiniAssetName.IsEmpty()) + PackagePath += TEXT("/") + HoudiniAssetName; + // Add a subdir using the owner component GUID if possible + if(ComponentGUID.IsValid()) + PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); + + // TODO: FIX ME!!! + // Old version + // Build the package name + PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()) + + TEXT("/") + + HoudiniAssetName; + } + break; + + case EPackageMode::CookToTemp: + { + // Temporary Folder + PackagePath = TempCookFolder; + // Add a subdir for the HDA + if (!HoudiniAssetName.IsEmpty()) + PackagePath += TEXT("/") + HoudiniAssetName; + // Add a subdir using the owner component GUID if possible + if (ComponentGUID.IsValid()) + PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); + } + break; + + case EPackageMode::Bake: + { + PackagePath = BakeFolder; + } + break; + } + + return PackagePath; +} + +bool +FHoudiniPackageParams::GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter) +{ + OutBakeCounter = 0; + + if (!IsValid(InAsset)) + return false; + + UPackage* Package = InAsset->GetPackage(); + // const FString PackagePathName = Package->GetPathName(); + // FString PackagePathNamePrefix; + // FString BakeCountOrGUID; + // if (!GetPackageNameWithoutBakeCounterOrGUIDSuffix(PackagePathName, PackagePathNamePrefix, BakeCountOrGUID)) + // PackagePathNamePrefix = PackagePathName; + // + // const FString ThisPackageNameBase = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); + // if (!PackagePathNamePrefix.Equals(ThisPackageNameBase)) + // return false; + // + // // Not a valid counter suffix, could be a GUID suffix. Return true since the prefixes match. + // if (BakeCountOrGUID.IsNumeric()) + // OutBakeCounter = FCString::Atoi(*BakeCountOrGUID); + // + // return true; + + if (!IsValid(Package)) + return false; + + UMetaData* MetaData = Package->GetMetaData(); + if (!IsValid(MetaData)) + return false; + + if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER)) + { + FString BakeCounterStr = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER); + BakeCounterStr.TrimStartAndEndInline(); + if (BakeCounterStr.IsNumeric()) + { + OutBakeCounter = FCString::Atoi(*BakeCounterStr); + return true; + } + } + + return false; +} + +bool +FHoudiniPackageParams::GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID) +{ + if (!InAsset) + return false; + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return false; + + UMetaData* MetaData = Package->GetMetaData(); + if (!IsValid(MetaData)) + return false; + + if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) + { + OutGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); + OutGUID.TrimStartAndEndInline(); + if (!OutGUID.IsEmpty()) + return true; + } + + return false; +} + +FString +FHoudiniPackageParams::GetPackageNameExcludingBakeCounter(const UObject* InAsset) +{ + if (!IsValid(InAsset)) + return FString(); + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return FString(); + + FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); + int32 BakeCounter = 0; + if (GetBakeCounterFromBakedAsset(InAsset, BakeCounter)) + { + const FString BakeCounterSuffix = FString::Printf(TEXT("_%d"), BakeCounter); + if (PackageName.EndsWith(BakeCounterSuffix)) + PackageName = PackageName.Mid(0, PackageName.Len() - BakeCounterSuffix.Len()); + } + + return PackageName; +} + +bool +FHoudiniPackageParams::MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const +{ + if (!IsValid(InAsset)) + return false; + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return false; + + const FString InAssetPackagePathName = FPaths::GetPath(Package->GetPathName()) + TEXT("/") + GetPackageNameExcludingBakeCounter(InAsset); + const FString ThisPackagePathName = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); + return InAssetPackagePathName.Equals(ThisPackagePathName); +} + +FString +FHoudiniPackageParams::GetPackageNameExcludingGUID(const UObject* InAsset) +{ + if (!IsValid(InAsset)) + return FString(); + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return FString(); + + FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); + FString GUIDStr; + if (GetGUIDFromTempAsset(InAsset, GUIDStr)) + { + if (PackageName.EndsWith(TEXT("_") + GUIDStr)) + PackageName = PackageName.Mid(0, PackageName.Len() - GUIDStr.Len() - 1); + } + + return PackageName; +} + +UPackage* +FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart) const +{ + // GUID/counter used to differentiate with existing package + int32 BakeCounter = InBakeCounterStart; + FGuid CurrentGuid = FGuid::NewGuid(); + + // Get the appropriate package path/name for this object + FString PackageName = GetPackageName(); + FString PackagePath = GetPackagePath(); + + // Iterate until we find a suitable name for the package + UPackage * NewPackage = nullptr; + while (true) + { + OutPackageName = PackageName; + + // Append the Bake guid/counter to the object name if needed + if (BakeCounter > 0) + { + OutPackageName += (PackageMode == EPackageMode::Bake) + ? TEXT("_") + FString::FromInt(BakeCounter) + : TEXT("_") + CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); + } + + // Build the final package name + FString FinalPackageName = PackagePath + TEXT("/") + OutPackageName; + // Sanitize package name. + FinalPackageName = UPackageTools::SanitizePackageName(FinalPackageName); + + UObject * PackageOuter = nullptr; + /* + // As of UE4.26, it is not possible anymore to create package with a non null outer + // CookToLevel is, anyway, no logner supported in v2. + if (PackageMode == EPackageMode::CookToLevel) + { + // If we are not baking, then use outermost package, since objects within our package + // need to be visible to external operations, such as copy paste. + PackageOuter = OuterPackage; + } + */ + + // See if a package named similarly already exists + UPackage* FoundPackage = FindPackage(PackageOuter, *FinalPackageName); + if (FoundPackage == nullptr && bAttemptToLoadMissingPackages) + { + FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_NoWarn); + } + if (ReplaceMode == EPackageReplaceMode::CreateNewAssets + && FoundPackage && !FoundPackage->IsPendingKill()) + { + // we need to generate a new name for it + CurrentGuid = FGuid::NewGuid(); + BakeCounter++; + continue; + } + + // Create actual package. + NewPackage = CreatePackage(*FinalPackageName); + if (IsValid(NewPackage)) + { + // Record bake counter / temp GUID in package metadata + UMetaData* MetaData = NewPackage->GetMetaData(); + if (IsValid(MetaData)) + { + if (PackageMode == EPackageMode::Bake) + { + // HOUDINI_LOG_MESSAGE(TEXT("Recording bake counter in package metadata: %d"), BakeCounter); + MetaData->RootMetaDataMap.Add( + HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER, FString::FromInt(BakeCounter)); + } + else if (CurrentGuid.IsValid()) + { + const FString GuidStr = CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); + // HOUDINI_LOG_MESSAGE(TEXT("Recording temp guid in package metadata: %s"), *GuidStr); + MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_TEMP_GUID, GuidStr); + } + } + } + + break; + } + + return NewPackage; +} + + +// Fixes link error with the template function under +void TemplateFixer() +{ + FHoudiniPackageParams PP; + UStaticMesh* SM = PP.CreateObjectAndPackage(); + UHoudiniStaticMesh* HSM = PP.CreateObjectAndPackage(); + //UMaterial* Mat = PP.CreateObjectAndPackage(); + //UTexture2D* Text = PP.CreateObjectAndPackage(); +} + +template +T* FHoudiniPackageParams::CreateObjectAndPackage() +{ + // Create the package for the object + FString NewObjectName; + UPackage* Package = CreatePackageForObject(NewObjectName); + if (!Package || Package->IsPendingKill()) + return nullptr; + + const FString SanitizedObjectName = ObjectTools::SanitizeObjectName(NewObjectName); + + T* ExistingTypedObject = FindObject(Package, *NewObjectName); + UObject* ExistingObject = FindObject(Package, *NewObjectName); + + if (ExistingTypedObject != nullptr && !ExistingTypedObject->IsPendingKill()) + { + // An object of the appropriate type already exists, update it! + ExistingTypedObject->PreEditChange(nullptr); + } + else if (ExistingObject != nullptr) + { + // Replacing an object of a different type, Delete it first. + const bool bDeleteSucceeded = ObjectTools::DeleteSingleObject(ExistingObject); + if (bDeleteSucceeded) + { + // Force GC so we can cleanly create a new asset (and not do an 'in place' replacement) + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Create a package for each mesh + Package = CreatePackageForObject(NewObjectName); + if (!Package || Package->IsPendingKill()) + return nullptr; + } + else + { + // failed to delete + return nullptr; + } + } + + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); + + return NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); +} + diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.h b/Source/HoudiniEngine/Private/HoudiniPackageParams.h index ac963bfb7..87939cac4 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.h +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.h @@ -1,239 +1,239 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/ObjectMacros.h" -#include "Engine/World.h" -#include "Misc/Paths.h" - -#include "HoudiniStringResolver.h" - -#include "HoudiniPackageParams.generated.h" - -class UStaticMesh; - -UENUM() -enum class EPackageMode : int8 -{ - CookToLevel, - CookToTemp, - Bake -}; - -UENUM() -enum class EPackageReplaceMode : int8 -{ - CreateNewAssets, - ReplaceExistingAssets -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPackageParams -{ -public: - GENERATED_BODY(); - - // - FHoudiniPackageParams(); - // - ~FHoudiniPackageParams(); - - // Helper functions returning the default behavior expected when cooking mesh - static EPackageMode GetDefaultStaticMeshesCookMode() { return EPackageMode::CookToTemp; }; - // Helper functions returning the default behavior expected when cooking materials or textures - static EPackageMode GetDefaultMaterialAndTextureCookMode() { return EPackageMode::CookToTemp; }; - // Helper functions returning the default behavior for replacing existing package - static EPackageReplaceMode GetDefaultReplaceMode() { return EPackageReplaceMode::ReplaceExistingAssets; }; - - // Returns the name for the package depending on the mode - FString GetPackageName() const; - // Returns the package's path depending on the mode - FString GetPackagePath() const; - // Returns the object flags corresponding to the current package mode - EObjectFlags GetObjectFlags() const; - - // Get the bake counter for InAsset's package metadata. Return true if the counter was found, false otherwise. - static bool GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter); - - // Get the GUID for a temp asset. - static bool GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID); - - // Get package name without bake counter - static FString GetPackageNameExcludingBakeCounter(const UObject* InAsset); - - // Get package name without temp GUID suffix - static FString GetPackageNameExcludingGUID(const UObject* InAsset); - - // Returns true if these package params generate the same package path and name as InAsset's package path name (with - // any potential bake counters stripped during comparison) - bool MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const; - - // Helper function to create a Package for a given object - UPackage* CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart=0) const; - - // Helper function to create an object and its package - template T* CreateObjectAndPackage(); - - - // The current cook/baking mode - UPROPERTY() - EPackageMode PackageMode; - // How to handle existing assets? replace or rename? - UPROPERTY() - EPackageReplaceMode ReplaceMode; - - // When cooking in bake mode - folder to create assets in - UPROPERTY() - FString BakeFolder; - // When cooking in temp mode - folder to create assets in - UPROPERTY() - FString TempCookFolder; - - // Package to save to - UPROPERTY() - UObject* OuterPackage; - - // Name of the package we want to create - // If null, we'll generate one from: - // (without PDG) ASSET_OBJ_GEO_PART_SPLIT, - // (with PDG) ASSET_TOPNET_TOPNODE_WORKITEMINDEX_PART_SPLIT - UPROPERTY() - FString ObjectName; - - // Name of the HDA - UPROPERTY() - FString HoudiniAssetName; - - // Name of actor that is managing an instance of the HDA - UPROPERTY() - FString HoudiniAssetActorName; - - // - UPROPERTY() - int32 ObjectId; - // - UPROPERTY() - int32 GeoId; - // - UPROPERTY() - int32 PartId; - // - UPROPERTY() - FString SplitStr; - - // GUID used for the owner - UPROPERTY() - FGuid ComponentGUID; - - // For PDG temporary outputs: the TOP network name - UPROPERTY() - FString PDGTOPNetworkName; - // For PDG temporary outputs: the TOP node name - UPROPERTY() - FString PDGTOPNodeName; - // For PDG temporary outputs: the work item index of the TOP node - UPROPERTY() - int32 PDGWorkItemIndex; - - // If FindPackage returns null, if this flag is true then a LoadPackage will also be attempted - // This is for use cases, such as commandlets, that might unload packages once done with them, but that must - // reliably be able to determine if a package exists later - UPROPERTY() - bool bAttemptToLoadMissingPackages; - - ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. - //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; - //FString GetBakeFolderArgument(ERuntimePackageMode PackageMode) const; - - //// Return the output path as either the temp or bake path, depending on the package mode. - //FString GetOutputFolderForPackageMode(ERuntimePackageMode PackageMode) const; - - /* - * Build a "standard" set of string formatting arguments that - * is typically used across HoudiniEngine path naming outputs. - * Note that each output type may contain additional named arguments - * that are not listed here. - * {out} - The output directory (varies depending on the package mode). - * {pkg} - The path to the destination package (varies depending on the package mode). - * {world} - Path the directory that contains the world. - * {hda_name} - Name of the HDA - * {guid} - guid of the HDA component - * @param PackageParams The output path for the current build mode (Temp / Bake). - * @param HACWorld The world in which the HDA component lives (typically Editor world). - * @param OutArgs The generated named arguments to be used for string formatting. - */ - - // Populate a map of named arguments from this FHoudiniPackageParams. - template - void UpdateTokensFromParams( - const UWorld* WorldContext, - TMap& OutTokens) const - { - UpdateOutputPathTokens(PackageMode, OutTokens); - - OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); - OutTokens.Add("object_name", ValueT( ObjectName )); - OutTokens.Add("object_id", ValueT( FString::FromInt(ObjectId) )); - OutTokens.Add("geo_id", ValueT( FString::FromInt(GeoId) )); - OutTokens.Add("part_id", ValueT( FString::FromInt(PartId) )); - OutTokens.Add("split_str", ValueT( SplitStr)); - OutTokens.Add("hda_name", ValueT( HoudiniAssetName )); - OutTokens.Add("hda_actor_name", ValueT( HoudiniAssetActorName )); - OutTokens.Add("pdg_topnet_name", ValueT( PDGTOPNetworkName )); - OutTokens.Add("pdg_topnode_name", ValueT( PDGTOPNodeName )); - OutTokens.Add("pdg_workitem_index", ValueT( FString::FromInt(PDGWorkItemIndex) )); - OutTokens.Add("guid", ValueT( ComponentGUID.ToString() )); - } - - template - void UpdateOutputPathTokens(EPackageMode InPackageMode, TMap& OutTokens) const - { - const FString PackagePath = GetPackagePath(); - - OutTokens.Add("temp", ValueT(FPaths::GetPath(TempCookFolder))); - OutTokens.Add("bake", ValueT(FPaths::GetPath(BakeFolder))); - - // `out_basepath` is useful if users want to organize their cook/bake assets - // different to the convention defined by GetPackagePath(). This would typically - // be combined with `unreal_level_path` during level path resolves. - switch (InPackageMode) - { - case EPackageMode::CookToTemp: - case EPackageMode::CookToLevel: - OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(TempCookFolder))); - break; - case EPackageMode::Bake: - OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(BakeFolder))); - break; - } - - OutTokens.Add("out", ValueT( FPaths::GetPath(PackagePath) )); - } - -}; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/ObjectMacros.h" +#include "Engine/World.h" +#include "Misc/Paths.h" + +#include "HoudiniStringResolver.h" + +#include "HoudiniPackageParams.generated.h" + +class UStaticMesh; + +UENUM() +enum class EPackageMode : int8 +{ + CookToLevel, + CookToTemp, + Bake +}; + +UENUM() +enum class EPackageReplaceMode : int8 +{ + CreateNewAssets, + ReplaceExistingAssets +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPackageParams +{ +public: + GENERATED_BODY(); + + // + FHoudiniPackageParams(); + // + ~FHoudiniPackageParams(); + + // Helper functions returning the default behavior expected when cooking mesh + static EPackageMode GetDefaultStaticMeshesCookMode() { return EPackageMode::CookToTemp; }; + // Helper functions returning the default behavior expected when cooking materials or textures + static EPackageMode GetDefaultMaterialAndTextureCookMode() { return EPackageMode::CookToTemp; }; + // Helper functions returning the default behavior for replacing existing package + static EPackageReplaceMode GetDefaultReplaceMode() { return EPackageReplaceMode::ReplaceExistingAssets; }; + + // Returns the name for the package depending on the mode + FString GetPackageName() const; + // Returns the package's path depending on the mode + FString GetPackagePath() const; + // Returns the object flags corresponding to the current package mode + EObjectFlags GetObjectFlags() const; + + // Get the bake counter for InAsset's package metadata. Return true if the counter was found, false otherwise. + static bool GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter); + + // Get the GUID for a temp asset. + static bool GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID); + + // Get package name without bake counter + static FString GetPackageNameExcludingBakeCounter(const UObject* InAsset); + + // Get package name without temp GUID suffix + static FString GetPackageNameExcludingGUID(const UObject* InAsset); + + // Returns true if these package params generate the same package path and name as InAsset's package path name (with + // any potential bake counters stripped during comparison) + bool MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const; + + // Helper function to create a Package for a given object + UPackage* CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart=0) const; + + // Helper function to create an object and its package + template T* CreateObjectAndPackage(); + + + // The current cook/baking mode + UPROPERTY() + EPackageMode PackageMode; + // How to handle existing assets? replace or rename? + UPROPERTY() + EPackageReplaceMode ReplaceMode; + + // When cooking in bake mode - folder to create assets in + UPROPERTY() + FString BakeFolder; + // When cooking in temp mode - folder to create assets in + UPROPERTY() + FString TempCookFolder; + + // Package to save to + UPROPERTY() + UObject* OuterPackage; + + // Name of the package we want to create + // If null, we'll generate one from: + // (without PDG) ASSET_OBJ_GEO_PART_SPLIT, + // (with PDG) ASSET_TOPNET_TOPNODE_WORKITEMINDEX_PART_SPLIT + UPROPERTY() + FString ObjectName; + + // Name of the HDA + UPROPERTY() + FString HoudiniAssetName; + + // Name of actor that is managing an instance of the HDA + UPROPERTY() + FString HoudiniAssetActorName; + + // + UPROPERTY() + int32 ObjectId; + // + UPROPERTY() + int32 GeoId; + // + UPROPERTY() + int32 PartId; + // + UPROPERTY() + FString SplitStr; + + // GUID used for the owner + UPROPERTY() + FGuid ComponentGUID; + + // For PDG temporary outputs: the TOP network name + UPROPERTY() + FString PDGTOPNetworkName; + // For PDG temporary outputs: the TOP node name + UPROPERTY() + FString PDGTOPNodeName; + // For PDG temporary outputs: the work item index of the TOP node + UPROPERTY() + int32 PDGWorkItemIndex; + + // If FindPackage returns null, if this flag is true then a LoadPackage will also be attempted + // This is for use cases, such as commandlets, that might unload packages once done with them, but that must + // reliably be able to determine if a package exists later + UPROPERTY() + bool bAttemptToLoadMissingPackages; + + ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. + //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; + //FString GetBakeFolderArgument(ERuntimePackageMode PackageMode) const; + + //// Return the output path as either the temp or bake path, depending on the package mode. + //FString GetOutputFolderForPackageMode(ERuntimePackageMode PackageMode) const; + + /* + * Build a "standard" set of string formatting arguments that + * is typically used across HoudiniEngine path naming outputs. + * Note that each output type may contain additional named arguments + * that are not listed here. + * {out} - The output directory (varies depending on the package mode). + * {pkg} - The path to the destination package (varies depending on the package mode). + * {world} - Path the directory that contains the world. + * {hda_name} - Name of the HDA + * {guid} - guid of the HDA component + * @param PackageParams The output path for the current build mode (Temp / Bake). + * @param HACWorld The world in which the HDA component lives (typically Editor world). + * @param OutArgs The generated named arguments to be used for string formatting. + */ + + // Populate a map of named arguments from this FHoudiniPackageParams. + template + void UpdateTokensFromParams( + const UWorld* WorldContext, + TMap& OutTokens) const + { + UpdateOutputPathTokens(PackageMode, OutTokens); + + OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); + OutTokens.Add("object_name", ValueT( ObjectName )); + OutTokens.Add("object_id", ValueT( FString::FromInt(ObjectId) )); + OutTokens.Add("geo_id", ValueT( FString::FromInt(GeoId) )); + OutTokens.Add("part_id", ValueT( FString::FromInt(PartId) )); + OutTokens.Add("split_str", ValueT( SplitStr)); + OutTokens.Add("hda_name", ValueT( HoudiniAssetName )); + OutTokens.Add("hda_actor_name", ValueT( HoudiniAssetActorName )); + OutTokens.Add("pdg_topnet_name", ValueT( PDGTOPNetworkName )); + OutTokens.Add("pdg_topnode_name", ValueT( PDGTOPNodeName )); + OutTokens.Add("pdg_workitem_index", ValueT( FString::FromInt(PDGWorkItemIndex) )); + OutTokens.Add("guid", ValueT( ComponentGUID.ToString() )); + } + + template + void UpdateOutputPathTokens(EPackageMode InPackageMode, TMap& OutTokens) const + { + const FString PackagePath = GetPackagePath(); + + OutTokens.Add("temp", ValueT(FPaths::GetPath(TempCookFolder))); + OutTokens.Add("bake", ValueT(FPaths::GetPath(BakeFolder))); + + // `out_basepath` is useful if users want to organize their cook/bake assets + // different to the convention defined by GetPackagePath(). This would typically + // be combined with `unreal_level_path` during level path resolves. + switch (InPackageMode) + { + case EPackageMode::CookToTemp: + case EPackageMode::CookToLevel: + OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(TempCookFolder))); + break; + case EPackageMode::Bake: + OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(BakeFolder))); + break; + } + + OutTokens.Add("out", ValueT( FPaths::GetPath(PackagePath) )); + } + +}; + + diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp index 60f636cc7..967a9701e 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp @@ -1,2933 +1,2943 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniInput.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniParameter.h" -#include "HoudiniAssetComponent.h" - - -// Default values for certain UI min and max parameter values -#define HAPI_UNREAL_PARAM_INT_UI_MIN 0 -#define HAPI_UNREAL_PARAM_INT_UI_MAX 10 -#define HAPI_UNREAL_PARAM_FLOAT_UI_MIN 0.0f -#define HAPI_UNREAL_PARAM_FLOAT_UI_MAX 10.0f - -// Some default parameter name -#define HAPI_UNREAL_PARAM_TRANSLATE "t" -#define HAPI_UNREAL_PARAM_ROTATE "r" -#define HAPI_UNREAL_PARAM_SCALE "s" -#define HAPI_UNREAL_PARAM_PIVOT "p" -#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" - -// -bool -FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // When recooking/rebuilding the HDA, force a full update of all params - bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); - - TArray NewParameters; - if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, true, bForceFullUpdate)) - { - /* - // DO NOT MANUALLY DESTROY THE OLD/DANGLING PARAMETERS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - - // Destroy old/dangling parameters - for (auto& OldParm : HAC->Parameters) - { - if (!OldParm || OldParm->IsPendingKill()) - continue; - - OldParm->ConditionalBeginDestroy(); - OldParm = nullptr; - } - */ - - // Replace with the new parameters - HAC->Parameters = NewParameters; - } - - - return true; -} - -bool -FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) -{ - // Call OnPreCook for all parameters. - // Parameters can use this to ensure that any cached / non-cooking state is properly - // synced before the cook starts (Looking at you, ramp parameters!) - for (UHoudiniParameter* Param : HAC->Parameters) - { - if (!Param || Param->IsPendingKill()) - continue; - - Param->OnPreCook(); - } - - return true; -} - -// -bool -FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Update all the parameters using the loaded parameter object - // We set "UpdateValues" to false because we do not want to "read" the parameter value from Houdini - // but keep the loaded value - - // This is the first cook on loading after a save or duplication, - // We need to sync the Ramp parameters first, so that their child parameters can be kept - // TODO: Simplify this, should be handled in BuildAllParameters, - for (int32 Idx = 0; Idx < HAC->Parameters.Num(); ++Idx) - { - UHoudiniParameter* Param = HAC->Parameters[Idx]; - - if (!Param || Param->IsPendingKill()) - continue; - - switch(Param->GetParameterType()) - { - case EHoudiniParameterType::ColorRamp: - case EHoudiniParameterType::FloatRamp: - case EHoudiniParameterType::MultiParm: - { - SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, Idx); - } - break; - - default: - break; - } - } - - // When recooking/rebuilding the HDA, force a full update of all params - bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); - - // This call to BuildAllParameters will keep all the loaded parameters (in the HAC's Parameters array) - // that are still present in the HDA, and keep their loaded value. - TArray NewParameters; - if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, false, bForceFullUpdate)) - { - /* - // DO NOT DESTROY OLD PARAMS MANUALLY HERE - // This causes crashes upon duplication due to uncollected zombie objects... - // GC is supposed to handle this by itself - // Destroy old/dangling parameters - for (auto& OldParm : HAC->Parameters) - { - if (!OldParm || OldParm->IsPendingKill()) - continue; - - OldParm->ConditionalBeginDestroy(); - OldParm = nullptr; - } - */ - - // Simply replace with the new parameters - HAC->Parameters = NewParameters; - } - - return true; -} - -bool -FHoudiniParameterTranslator::BuildAllParameters( - const HAPI_NodeId& AssetId, - class UObject* Outer, - TArray& CurrentParameters, - TArray& NewParameters, - const bool& bUpdateValues, - const bool& InForceFullUpdate) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); - - NewParameters.Empty(); - if (NodeInfo.parmCount == 0) - { - // The asset doesnt have any parameter, we're done. - return true; - } - else if (NodeInfo.parmCount < 0) - { - // Invalid parm count - return false; - } - - TArray AllMultiParams; - - // Retrieve all the parameter infos. - TArray< HAPI_ParmInfo > ParmInfos; - ParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); - - // Create a name lookup cache for the current parameters - TMap CurrentParametersByName; - CurrentParametersByName.Reserve(CurrentParameters.Num()); - for (auto& Parm : CurrentParameters) - { - if (!Parm) - continue; - CurrentParametersByName.Add(Parm->GetParameterName(), Parm); - } - - // Create properties for parameters. - TArray NewParmIds; - for (int32 ParamIdx = 0; ParamIdx < NodeInfo.parmCount; ++ParamIdx) - { - - // Retrieve param info at this index. - const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; - - // If the parameter is corrupt, skip it - if (ParmInfo.id < 0 || ParmInfo.childIndex < 0) - { - HOUDINI_LOG_WARNING(TEXT("Corrupt parameter %d detected, skipping."), ParamIdx); - continue; - } - - // If the parameter is invisible, skip it. - //if (ParmInfo.invisible) - // continue; - - // Check if any parent folder of this parameter is invisible - bool SkipParm = false; - HAPI_ParmId ParentId = ParmInfo.parentId; - while (ParentId > 0 && !SkipParm) - { - if (const HAPI_ParmInfo* ParentInfoPtr = ParmInfos.FindByPredicate([=](const HAPI_ParmInfo& Info) { - return Info.id == ParentId; - })) - { - if (ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER) - SkipParm = true; - ParentId = ParentInfoPtr->parentId; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not find parent of parameter %d"), ParmInfo.id); - SkipParm = true; - } - } - - if (SkipParm) - continue; - - // See if this parameter has already been created. - // We can't use the HAPI_ParmId because it is not unique to parameter instances, - // so instead, try to find the existing parameter by name using the lookup table - FString NewParmName; - FHoudiniEngineString(ParmInfo.nameSH).ToFString(NewParmName); - - EHoudiniParameterType ParmType = EHoudiniParameterType::Invalid; - FHoudiniParameterTranslator::GetParmTypeFromParmInfo(ParmInfo, ParmType); - - UHoudiniParameter ** FoundHoudiniParameter = CurrentParametersByName.Find(NewParmName); - - // If that parameter exists, we might be able to simply reuse it. - bool IsFoundParameterValid = false; - if (FoundHoudiniParameter && *FoundHoudiniParameter && !(*FoundHoudiniParameter)->IsPendingKill()) - { - // First, we can simply check that the tuple size hasn't changed - if ((*FoundHoudiniParameter)->GetTupleSize() != ParmInfo.size) - { - IsFoundParameterValid = false; - } - else if (ParmType == EHoudiniParameterType::Invalid ) - { - IsFoundParameterValid = false; - } - else if (ParmType != (*FoundHoudiniParameter)->GetParameterType() ) - { - // Types do not match - IsFoundParameterValid = false; - } - else if ( !CheckParameterTypeAndClassMatch( *FoundHoudiniParameter, ParmType) ) - { - // Found parameter class does not match - IsFoundParameterValid = false; - } - else - { - // We can reuse the parameter - IsFoundParameterValid = true; - } - } - - UHoudiniParameter * HoudiniAssetParameter = nullptr; - - if (IsFoundParameterValid) - { - // We can reuse the parameter we found - HoudiniAssetParameter = *FoundHoudiniParameter; - - // Transfer param object from current map to new map - CurrentParameters.Remove(HoudiniAssetParameter); - CurrentParametersByName.Remove(NewParmName); - - // Do a fast update of this parameter - if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, InForceFullUpdate, bUpdateValues)) - continue; - - // Reset the states of ramp parameters. - switch (HoudiniAssetParameter->GetParameterType()) - { - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); - if (FloatRampParam) - { - UHoudiniAssetComponent* ParentHAC = Cast(FloatRampParam->GetOuter()); - if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) - FloatRampParam->bCaching = false; - } - - break; - } - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); - if (ColorRampParam) - { - UHoudiniAssetComponent* ParentHAC = Cast(ColorRampParam->GetOuter()); - if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) - ColorRampParam->bCaching = false; - } - - break; - } - } - - } - else - { - // Create a new parameter object of the appropriate type - HoudiniAssetParameter = CreateTypedParameter(Outer, ParmType, NewParmName); - // Fully update this parameter - if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, true, true)) - continue; - - } - - // Add the new parameters - NewParameters.Add(HoudiniAssetParameter); - NewParmIds.Add(ParmInfo.id); - - - // Check if the parameter is a direct child of a multiparam. - if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::MultiParm) - AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); - - if (AllMultiParams.Contains(HoudiniAssetParameter->GetParentParmId())) - { - HoudiniAssetParameter->SetIsDirectChildOfMultiParm(true); - - // Treat the folderlist whose direct parent is a multi param as a multi param too. - if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::FolderList) - AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); - } - - } - - // Assign folder type to all folderlists, - // if the first child of the folderlist is Tab or Radio button, set the bIsTabMenu of the folderlistParam to be true, otherwise false - for (int32 Idx = 0; Idx < NewParameters.Num(); ++Idx) - { - UHoudiniParameter * CurParam = NewParameters[Idx]; - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (CurParam->GetParameterType() == EHoudiniParameterType::FolderList) - { - UHoudiniParameterFolderList* CurFolderList = Cast(CurParam); - if (!CurFolderList || CurFolderList->IsPendingKill()) - continue; - - int32 FirstChildIdx = Idx + 1; - if (!NewParameters.IsValidIndex(FirstChildIdx)) - continue; - - UHoudiniParameterFolder* FirstChildFolder = Cast(NewParameters[FirstChildIdx]); - if (!FirstChildFolder || FirstChildFolder->IsPendingKill()) - continue; - - if (FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Radio || - FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Tabs) - { - // If this is the first time build - if (!CurFolderList->IsTabMenu()) - { - // Set the folderlist to be tabs - CurFolderList->SetIsTabMenu(true); - // Select the first child tab folder by default. - FirstChildFolder->SetChosen(true); - } - } - else - CurFolderList->SetIsTabMenu(false); - } - } - - FHoudiniEngineUtils::UpdateEditorProperties(Outer, true); - - return true; -} - - -void -FHoudiniParameterTranslator::GetParmTypeFromParmInfo( - const HAPI_ParmInfo& ParmInfo, - EHoudiniParameterType& ParmType) -{ - ParmType = EHoudiniParameterType::Invalid; - //ParmValueType = EHoudiniParameterValueType::Invalid; - - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_BUTTON: - ParmType = EHoudiniParameterType::Button; - //ParmValueType = EHoudiniParameterValueType::Int; - break; - - case HAPI_PARMTYPE_STRING: - { - if (ParmInfo.choiceCount > 0) - { - ParmType = EHoudiniParameterType::StringChoice; - //ParmValueType = EHoudiniParameterValueType::String; - } - else - { - ParmType = EHoudiniParameterType::String; - //ParmValueType = EHoudiniParameterValueType::String; - } - break; - } - - case HAPI_PARMTYPE_INT: - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_BUTTONSTRIP) - { - ParmType = EHoudiniParameterType::ButtonStrip; - break; - } - - if (ParmInfo.choiceCount > 0) - { - ParmType = EHoudiniParameterType::IntChoice; - //ParmValueType = EHoudiniParameterValueType::Int; - } - else - { - ParmType = EHoudiniParameterType::Int; - //ParmValueType = EHoudiniParameterValueType::Int; - } - break; - } - - case HAPI_PARMTYPE_FLOAT: - { - ParmType = EHoudiniParameterType::Float; - //ParmValueType = EHoudiniParameterValueType::Float; - break; - } - - case HAPI_PARMTYPE_TOGGLE: - { - ParmType = EHoudiniParameterType::Toggle; - //ParmValueType = EHoudiniParameterValueType::Int; - break; - } - - case HAPI_PARMTYPE_COLOR: - { - ParmType = EHoudiniParameterType::Color; - //ParmValueType = EHoudiniParameterValueType::Float; - break; - } - - case HAPI_PARMTYPE_LABEL: - { - ParmType = EHoudiniParameterType::Label; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_SEPARATOR: - { - ParmType = EHoudiniParameterType::Separator; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - case HAPI_PARMTYPE_FOLDERLIST: - { - ParmType = EHoudiniParameterType::FolderList; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - // Treat radio folders as tab folders for now - case HAPI_PARMTYPE_FOLDERLIST_RADIO: - { - ParmType = EHoudiniParameterType::FolderList; - break; - } - - case HAPI_PARMTYPE_FOLDER: - { - ParmType = EHoudiniParameterType::Folder; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - case HAPI_PARMTYPE_MULTIPARMLIST: - { - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType) - { - ParmType = EHoudiniParameterType::FloatRamp; - //ParmValueType = EHoudiniParameterValueType::Float; - } - else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - { - ParmType = EHoudiniParameterType::ColorRamp; - //ParmValueType = EHoudiniParameterValueType::Float; - } - else - { - ParmType = EHoudiniParameterType::MultiParm; - //ParmValueType = EHoudiniParameterValueType::Int; - } - break; - } - - case HAPI_PARMTYPE_PATH_FILE: - { - ParmType = EHoudiniParameterType::File; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_DIR: - { - ParmType = EHoudiniParameterType::FileDir; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_GEO: - { - ParmType = EHoudiniParameterType::FileGeo; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - { - ParmType = EHoudiniParameterType::FileImage; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_NODE: - { - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - ParmType = EHoudiniParameterType::Input; - } - else - { - ParmType = EHoudiniParameterType::String; - } - break; - } - - default: - { - // Just ignore unsupported types for now. - HOUDINI_LOG_WARNING(TEXT("Parameter Type (%d) is unsupported"), static_cast(ParmInfo.type)); - break; - } - } -} - -UClass* -FHoudiniParameterTranslator::GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo) -{ - UClass* FoundClass = nullptr; - - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_STRING: - if (!ParmInfo.choiceCount) - FoundClass = UHoudiniParameterString::StaticClass(); - else - FoundClass = UHoudiniParameterChoice ::StaticClass(); - break; - - case HAPI_PARMTYPE_INT: - if (!ParmInfo.choiceCount) - FoundClass = UHoudiniParameterInt::StaticClass(); - else - FoundClass = UHoudiniParameterChoice::StaticClass(); - break; - - case HAPI_PARMTYPE_FLOAT: - FoundClass = UHoudiniParameterFloat::StaticClass(); - break; - - case HAPI_PARMTYPE_TOGGLE: - FoundClass = UHoudiniParameterToggle::StaticClass(); - break; - - case HAPI_PARMTYPE_COLOR: - FoundClass = UHoudiniParameterColor::StaticClass(); - break; - - case HAPI_PARMTYPE_LABEL: - FoundClass = UHoudiniParameterLabel::StaticClass(); - break; - - case HAPI_PARMTYPE_BUTTON: - FoundClass = UHoudiniParameterButton::StaticClass(); - break; - - case HAPI_PARMTYPE_SEPARATOR: - FoundClass = UHoudiniParameterSeparator::StaticClass(); - break; - - case HAPI_PARMTYPE_FOLDERLIST: - FoundClass = UHoudiniParameterFolderList::StaticClass(); - break; - - case HAPI_PARMTYPE_FOLDER: - FoundClass = UHoudiniParameterFolder::StaticClass(); - break; - - case HAPI_PARMTYPE_MULTIPARMLIST: - { - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - FoundClass = UHoudiniParameterRampFloat::StaticClass(); - else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - FoundClass = UHoudiniParameterRampColor::StaticClass(); - } - break; - - case HAPI_PARMTYPE_PATH_FILE: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_DIR: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_GEO: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - - case HAPI_PARMTYPE_NODE: - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - FoundClass = UHoudiniParameter::StaticClass(); - } - else - { - FoundClass = UHoudiniParameterString::StaticClass(); - } - break; - } - - if (FoundClass == nullptr) - return UHoudiniParameter::StaticClass(); - - return FoundClass; -} - -bool -FHoudiniParameterTranslator::CheckParameterTypeAndClassMatch(UHoudiniParameter* Parameter, const EHoudiniParameterType& ParmType) -{ - UClass* FoundClass = Parameter->GetClass(); - bool FailedTypeCheck = true; - - switch (ParmType) - { - case EHoudiniParameterType::Invalid: - { - FailedTypeCheck = true; - break; - } - - case EHoudiniParameterType::Button: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterButton >(); - break; - } - - case EHoudiniParameterType::Color: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterColor >(); - break; - } - - case EHoudiniParameterType::ColorRamp: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampColor >(); - break; - } - case EHoudiniParameterType::FloatRamp: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampFloat >(); - break; - } - - case EHoudiniParameterType::File: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileDir: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileGeo: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileImage: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - - case EHoudiniParameterType::Float: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFloat >(); - break; - } - - case EHoudiniParameterType::Folder: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolder >(); - break; - } - - case EHoudiniParameterType::FolderList: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolderList >(); - break; - } - - case EHoudiniParameterType::Input: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterOperatorPath >(); - break; - } - - case EHoudiniParameterType::Int: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterInt >(); - break; - } - - case EHoudiniParameterType::IntChoice: - case EHoudiniParameterType::StringChoice: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); - break; - } - - case EHoudiniParameterType::Label: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterLabel >(); - break; - } - - case EHoudiniParameterType::MultiParm: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterMultiParm >(); - break; - } - - case EHoudiniParameterType::Separator: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterSeparator >(); - break; - } - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); - break; - } - - case EHoudiniParameterType::Toggle: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterToggle >(); - break; - } - }; - - return !FailedTypeCheck; -} -/* -bool -FHoudiniParameterTranslator::CheckParameterClassAndInfoMatch(UHoudiniParameter* Parameter, const HAPI_ParmInfo& ParmInfo) -{ - if (!Parameter || Parameter->IsPendingKill()) - return false; - - UClass* FoundClass = Parameter->GetClass(); - bool FailedTypeCheck = true; - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_STRING: - if (!ParmInfo.choiceCount) - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); - else - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); - break; - - case HAPI_PARMTYPE_INT: - if (!ParmInfo.choiceCount) - FailedTypeCheck &= !FoundClass->IsChildOf(); - else - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FLOAT: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_TOGGLE: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_COLOR: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_LABEL: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_BUTTON: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_SEPARATOR: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FOLDERLIST: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FOLDER: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_MULTIPARMLIST: - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - break; - - case HAPI_PARMTYPE_PATH_FILE: - case HAPI_PARMTYPE_PATH_FILE_DIR: - case HAPI_PARMTYPE_PATH_FILE_GEO: - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_NODE: - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - else - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - break; - } - - return FailedTypeCheck; -} -*/ - -UHoudiniParameter * -FHoudiniParameterTranslator::CreateTypedParameter(UObject * Outer, const EHoudiniParameterType& ParmType, const FString& ParmName) -{ - UHoudiniParameter* HoudiniParameter = nullptr; - // Create a parameter of the desired type - switch (ParmType) - { - case EHoudiniParameterType::Button: - HoudiniParameter = UHoudiniParameterButton::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::ButtonStrip: - HoudiniParameter = UHoudiniParameterButtonStrip::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Color: - HoudiniParameter = UHoudiniParameterColor::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::ColorRamp: - HoudiniParameter = UHoudiniParameterRampColor::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FloatRamp: - HoudiniParameter = UHoudiniParameterRampFloat::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::File: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FileDir: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileDir); - break; - - case EHoudiniParameterType::FileGeo: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileGeo); - break; - - case EHoudiniParameterType::FileImage: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileImage); - break; - - case EHoudiniParameterType::Float: - HoudiniParameter = UHoudiniParameterFloat::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Folder: - HoudiniParameter = UHoudiniParameterFolder::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FolderList: - HoudiniParameter = UHoudiniParameterFolderList::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Input: - // Input parameter simply use the base class as all the processingsince is handled by UHoudiniInput - HoudiniParameter = UHoudiniParameterOperatorPath::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(ParmType); - break; - - case EHoudiniParameterType::Int: - HoudiniParameter = UHoudiniParameterInt::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::IntChoice: - HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::IntChoice); - break; - - case EHoudiniParameterType::StringChoice: - HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::StringChoice); - break; - - case EHoudiniParameterType::Label: - HoudiniParameter = UHoudiniParameterLabel::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::MultiParm: - HoudiniParameter = UHoudiniParameterMultiParm::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Separator: - HoudiniParameter = UHoudiniParameterSeparator::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - HoudiniParameter = UHoudiniParameterString::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Toggle: - HoudiniParameter = UHoudiniParameterToggle::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Invalid: - // TODO handle invalid params - HoudiniParameter = UHoudiniParameter::Create(Outer, ParmName); - break; - } - - return HoudiniParameter; -} - -bool -FHoudiniParameterTranslator::UpdateParameterFromInfo( - UHoudiniParameter * HoudiniParameter, const HAPI_NodeId& InNodeId, const HAPI_ParmInfo& ParmInfo, - const bool& bFullUpdate, const bool& bUpdateValue) -{ - if (!HoudiniParameter || HoudiniParameter->IsPendingKill()) - return false; - - // Copy values from the ParmInfos - HoudiniParameter->SetNodeId(InNodeId); - HoudiniParameter->SetParmId(ParmInfo.id); - HoudiniParameter->SetParentParmId(ParmInfo.parentId); - - HoudiniParameter->SetChildIndex(ParmInfo.childIndex); - HoudiniParameter->SetTagCount(ParmInfo.tagCount); - HoudiniParameter->SetTupleSize(ParmInfo.size); - - HoudiniParameter->SetVisible(!ParmInfo.invisible); - HoudiniParameter->SetDisabled(ParmInfo.disabled); - HoudiniParameter->SetSpare(ParmInfo.spare); - HoudiniParameter->SetJoinNext(ParmInfo.joinNext); - - HoudiniParameter->SetTagCount(ParmInfo.tagCount); - HoudiniParameter->SetIsChildOfMultiParm(ParmInfo.isChildOfMultiParm); - - UHoudiniParameterMultiParm* MultiParm = Cast(HoudiniParameter); - if(MultiParm) - MultiParm->InstanceStartOffset = ParmInfo.instanceStartOffset; - - - - // Get the parameter type - EHoudiniParameterType ParmType = HoudiniParameter->GetParameterType(); - - // We need to set string values from the parmInfo - if (bFullUpdate) - { - FString Name; - { - // Name - FHoudiniEngineString HoudiniEngineStringName(ParmInfo.nameSH); - if (HoudiniEngineStringName.ToFString(Name)) - HoudiniParameter->SetParameterName(Name); - } - - { - // Label - FString Label; - FHoudiniEngineString HoudiniEngineStringLabel(ParmInfo.labelSH); - if (HoudiniEngineStringLabel.ToFString(Label)) - HoudiniParameter->SetParameterLabel(Label); - } - - { - // Help - FString Help; - FHoudiniEngineString HoudiniEngineStringHelp(ParmInfo.helpSH); - if (HoudiniEngineStringHelp.ToFString(Help)) - HoudiniParameter->SetParameterHelp(Help); - } - - if (ParmType == EHoudiniParameterType::String - || ParmType == EHoudiniParameterType::Int - || ParmType == EHoudiniParameterType::Float - || ParmType == EHoudiniParameterType::Toggle - || ParmType == EHoudiniParameterType::Color) - { - // See if the parm has an expression - int32 TupleIdx = ParmInfo.intValuesIndex; - bool bHasExpression = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ParmHasExpression( - FHoudiniEngine::Get().GetSession(), InNodeId, - TCHAR_TO_UTF8(*Name), TupleIdx, &bHasExpression)) - { - // ? - } - - FString ParmExprString = TEXT(""); - if (bHasExpression) - { - // Try to get the expression's value - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmExpression( - FHoudiniEngine::Get().GetSession(), InNodeId, - TCHAR_TO_UTF8(*Name), TupleIdx, &StringHandle)) - { - FHoudiniEngineString HoudiniEngineString(StringHandle); - HoudiniEngineString.ToFString(ParmExprString); - } - - // Check if we actually have an expression - // String parameters return true even if they do not have one - bHasExpression = ParmExprString.Len() > 0; - - } - - HoudiniParameter->SetHasExpression(bHasExpression); - HoudiniParameter->SetExpression(ParmExprString); - } - else - { - HoudiniParameter->SetHasExpression(false); - HoudiniParameter->SetExpression(FString()); - } - - // Get parameter tags. - int32 TagCount = HoudiniParameter->GetTagCount(); - for (int32 Idx = 0; Idx < TagCount; ++Idx) - { - HAPI_StringHandle TagNameSH; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagName( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmInfo.id, Idx, &TagNameSH)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); - continue; - } - - FString NameString = TEXT(""); - FHoudiniEngineString::ToFString(TagNameSH, NameString); - if (NameString.IsEmpty()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); - continue; - } - - HAPI_StringHandle TagValueSH; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagValue( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmInfo.id, TCHAR_TO_ANSI(*NameString), &TagValueSH)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag value: parmId: %d, tag: %s"), ParmInfo.id, *NameString); - } - - FString ValueString = TEXT(""); - FHoudiniEngineString::ToFString(TagValueSH, ValueString); - - HoudiniParameter->GetTags().Add(NameString, ValueString); - } - } - - // - // Update properties specific to parameter classes - switch (ParmType) - { - case EHoudiniParameterType::Button: - { - UHoudiniParameterButton* HoudiniParameterButton = Cast(HoudiniParameter); - if (HoudiniParameterButton && !HoudiniParameterButton->IsPendingKill()) - { - HoudiniParameterButton->SetValueIndex(ParmInfo.intValuesIndex); - } - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - UHoudiniParameterButtonStrip* HoudiniParameterButtonStrip = Cast(HoudiniParameter); - if (HoudiniParameterButtonStrip && !HoudiniParameterButtonStrip->IsPendingKill()) - { - HoudiniParameterButtonStrip->SetValueIndex(ParmInfo.intValuesIndex); - HoudiniParameterButtonStrip->Count = ParmInfo.choiceCount; - } - - if (bFullUpdate) - { - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNum(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - - HoudiniParameterButtonStrip->InitializeLabels(ParmInfo.choiceCount); - - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ButtonLabel = HoudiniParameterButtonStrip->GetStringLabelAt(ChoiceIdx); - if (ButtonLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ButtonLabel)) - return false; - } - } - - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterButtonStrip->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.choiceCount) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - } - break; - - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* HoudiniParameterColor = Cast(HoudiniParameter); - if (HoudiniParameterColor && !HoudiniParameterColor->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterColor->SetValueIndex(ParmInfo.floatValuesIndex); - - // Update the Parameter value if we want to - if (bUpdateValue) - { - // Get the actual value for this property. - FLinearColor Color = FLinearColor::White; - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - (float *)&Color.R, ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - - HoudiniParameterColor->SetColorValue(Color); - } - - if (bFullUpdate) - { - // Set the default value at parameter created. - HoudiniParameterColor->SetDefaultValue(); - } - } - } - break; - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* HoudiniParameterRampColor = Cast(HoudiniParameter); - if (HoudiniParameterRampColor && !HoudiniParameterRampColor->IsPendingKill()) - { - HoudiniParameterRampColor->SetInstanceCount(ParmInfo.instanceCount); - HoudiniParameterRampColor->MultiParmInstanceLength = ParmInfo.instanceLength; - } - } - break; - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* HoudiniParameterRampFloat = Cast(HoudiniParameter); - if (HoudiniParameterRampFloat && !HoudiniParameterRampFloat->IsPendingKill()) - { - HoudiniParameterRampFloat->SetInstanceCount(ParmInfo.instanceCount); - HoudiniParameterRampFloat->MultiParmInstanceLength = ParmInfo.instanceLength; - } - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - UHoudiniParameterFile* HoudiniParameterFile = Cast(HoudiniParameter); - if (HoudiniParameterFile && !HoudiniParameterFile->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFile->SetValueIndex(ParmInfo.stringValuesIndex); - - // Update the file filter and read only tag only for full updates - if (bFullUpdate) - { - // Check if we are read-only - bool bIsReadOnly = false; - FString FileChooserTag; - if (FHoudiniParameterTranslator::HapiGetParameterTagValue(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_FILE_READONLY, FileChooserTag)) - { - if (FileChooserTag.Equals(TEXT("read"), ESearchCase::IgnoreCase)) - bIsReadOnly = true; - } - HoudiniParameterFile->SetReadOnly(bIsReadOnly); - - // Update the file type using the typeInfo string. - if (ParmInfo.typeInfoSH > 0) - { - FString Filters; - FHoudiniEngineString HoudiniEngineString(ParmInfo.typeInfoSH); - if (HoudiniEngineString.ToFString(Filters)) - { - if (!Filters.IsEmpty()) - HoudiniParameterFile->SetFileFilters(Filters); - } - } - } - - if (bUpdateValue) - { - // Get the actual values for this property. - TArray< HAPI_StringHandle > StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), InNodeId, false, - &StringHandles[0], ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - - // Convert HAPI string handles to Unreal strings. - HoudiniParameterFile->SetNumberOfValues(ParmInfo.size); - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - - // Update the parameter value - HoudiniParameterFile->SetValueAt(ValueString, Idx); - } - } - - if (bFullUpdate) - { - HoudiniParameterFile->SetDefaultValues(); - } - } - } - break; - - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* HoudiniParameterFloat = Cast(HoudiniParameter); - if (HoudiniParameterFloat && !HoudiniParameterFloat->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFloat->SetValueIndex(ParmInfo.floatValuesIndex); - - if (bUpdateValue) - { - // Update the parameter's value - HoudiniParameterFloat->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterFloat->GetValuesPtr(), - ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - - if (bFullUpdate) - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_FLOAT_LOG) - { - HoudiniParameterFloat->SetIsLogarithmic(true); - } - - // set the default float values. - HoudiniParameterFloat->SetDefaultValues(); - - // Only update Unit, no swap, and Min/Max values when doing a full update - - // Get the parameter's unit from the "unit" tag - FString ParamUnit; - FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); - HoudiniParameterFloat->SetUnit(ParamUnit); - - // Get the parameter's no swap tag (hengine_noswap) - HoudiniParameterFloat->SetNoSwap(HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_NOSWAP)); - - // Set the min and max for this parameter - if (ParmInfo.hasMin) - { - HoudiniParameterFloat->SetHasMin(true); - HoudiniParameterFloat->SetMin(ParmInfo.min); - } - else - { - HoudiniParameterFloat->SetHasMin(false); - HoudiniParameterFloat->SetMin(TNumericLimits::Lowest()); - } - - if (ParmInfo.hasMax) - { - HoudiniParameterFloat->SetHasMax(true); - HoudiniParameterFloat->SetMax(ParmInfo.max); - } - else - { - HoudiniParameterFloat->SetHasMax(false); - HoudiniParameterFloat->SetMax(TNumericLimits::Max()); - } - - // Set min and max for UI for this property. - bool bUsesDefaultMin = false; - if (ParmInfo.hasUIMin) - { - HoudiniParameterFloat->SetHasUIMin(true); - HoudiniParameterFloat->SetUIMin(ParmInfo.UIMin); - } - else if (ParmInfo.hasMin) - { - // If it is not set, use supplied min. - HoudiniParameterFloat->SetUIMin(ParmInfo.min); - } - else - { - // Min value Houdini uses by default. - HoudiniParameterFloat->SetUIMin(HAPI_UNREAL_PARAM_FLOAT_UI_MIN); - bUsesDefaultMin = true; - } - - bool bUsesDefaultMax = false; - if (ParmInfo.hasUIMax) - { - HoudiniParameterFloat->SetHasUIMax(true); - HoudiniParameterFloat->SetUIMax(ParmInfo.UIMax); - } - else if (ParmInfo.hasMax) - { - // If it is not set, use supplied max. - HoudiniParameterFloat->SetUIMax(ParmInfo.max); - } - else - { - // Max value Houdini uses by default. - HoudiniParameterFloat->SetUIMax(HAPI_UNREAL_PARAM_FLOAT_UI_MAX); - bUsesDefaultMax = true; - } - - if (bUsesDefaultMin || bUsesDefaultMax) - { - // If we are using defaults, we can detect some most common parameter names and alter defaults. - FString LocalParameterName = HoudiniParameterFloat->GetParameterName(); - FHoudiniEngineString HoudiniEngineString(ParmInfo.nameSH); - HoudiniEngineString.ToFString(LocalParameterName); - - static const FString ParameterNameTranslate(TEXT(HAPI_UNREAL_PARAM_TRANSLATE)); - static const FString ParameterNameRotate(TEXT(HAPI_UNREAL_PARAM_ROTATE)); - static const FString ParameterNameScale(TEXT(HAPI_UNREAL_PARAM_SCALE)); - static const FString ParameterNamePivot(TEXT(HAPI_UNREAL_PARAM_PIVOT)); - - if (!LocalParameterName.IsEmpty()) - { - if (LocalParameterName.Equals(ParameterNameTranslate) - || LocalParameterName.Equals(ParameterNameScale) - || LocalParameterName.Equals(ParameterNamePivot)) - { - if (bUsesDefaultMin) - { - HoudiniParameterFloat->SetUIMin(-1.0f); - } - if (bUsesDefaultMax) - { - HoudiniParameterFloat->SetUIMax(1.0f); - } - } - else if (LocalParameterName.Equals(ParameterNameRotate)) - { - if (bUsesDefaultMin) - { - HoudiniParameterFloat->SetUIMin(0.0f); - } - if (bUsesDefaultMax) - { - HoudiniParameterFloat->SetUIMax(360.0f); - } - } - } - } - } - } - } - break; - - case EHoudiniParameterType::Folder: - { - UHoudiniParameterFolder* HoudiniParameterFolder = Cast(HoudiniParameter); - if (HoudiniParameterFolder && !HoudiniParameterFolder->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFolder->SetValueIndex(ParmInfo.intValuesIndex); - HoudiniParameterFolder->SetFolderType(GetFolderTypeFromParamInfo(&ParmInfo)); - } - } - break; - - case EHoudiniParameterType::FolderList: - { - UHoudiniParameterFolderList* HoudiniParameterFolderList = Cast(HoudiniParameter); - if (HoudiniParameterFolderList && !HoudiniParameterFolderList->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFolderList->SetValueIndex(ParmInfo.intValuesIndex); - } - } - break; - - case EHoudiniParameterType::Input: - { - // Inputs parameters are just stored, and handled separately by UHoudiniInputs - UHoudiniParameterOperatorPath* HoudiniParameterOperatorPath = Cast(HoudiniParameter); - if (HoudiniParameterOperatorPath && !HoudiniParameterOperatorPath->IsPendingKill()) - { - /* - // DO NOT CREATE A DUPLICATE INPUT HERE! - // Inputs are created by the input translator, and will be tied to this parameter there - UHoudiniInput * NewInput = NewObject< UHoudiniInput >( - HoudiniParameterOperatorPath, - UHoudiniInput::StaticClass()); - - UHoudiniAssetComponent *ParentHAC = Cast(HoudiniParameterOperatorPath->GetOuter()); - - if (!ParentHAC) - return false; - - if (!NewInput || NewInput->IsPendingKill()) - return false; - - // Set the nodeId - NewInput->SetAssetNodeId(ParentHAC->GetAssetId()); - NewInput->SetInputType(EHoudiniInputType::Geometry); - HoudiniParameterOperatorPath->HoudiniInputs.Add(NewInput); - */ - // Set the valueIndex - HoudiniParameterOperatorPath->SetValueIndex(ParmInfo.stringValuesIndex); - } - } - break; - - case EHoudiniParameterType::Int: - { - UHoudiniParameterInt* HoudiniParameterInt = Cast(HoudiniParameter); - if (HoudiniParameterInt && !HoudiniParameterInt->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterInt->SetValueIndex(ParmInfo.intValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - HoudiniParameterInt->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterInt->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - - if (bFullUpdate) - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_INT_LOG) - { - HoudiniParameterInt->SetIsLogarithmic(true); - } - - // Set the default int values at created - HoudiniParameterInt->SetDefaultValues(); - // Only update unit and Min/Max values for a full update - - // Get the parameter's unit from the "unit" tag - FString ParamUnit; - FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); - HoudiniParameterInt->SetUnit(ParamUnit); - - // Set the min and max for this parameter - if (ParmInfo.hasMin) - { - HoudiniParameterInt->SetHasMin(true); - HoudiniParameterInt->SetMin((int32)ParmInfo.min); - } - else - { - HoudiniParameterInt->SetHasMin(false); - HoudiniParameterInt->SetMin(TNumericLimits::Lowest()); - } - - if (ParmInfo.hasMax) - { - HoudiniParameterInt->SetHasMax(true); - HoudiniParameterInt->SetMax((int32)ParmInfo.max); - } - else - { - HoudiniParameterInt->SetHasMax(false); - HoudiniParameterInt->SetMax(TNumericLimits::Max()); - } - - // Set min and max for UI for this property. - if (ParmInfo.hasUIMin) - { - HoudiniParameterInt->SetHasUIMin(true); - HoudiniParameterInt->SetUIMin((int32)ParmInfo.UIMin); - } - else if (ParmInfo.hasMin) - { - // If it is not set, use supplied min. - HoudiniParameterInt->SetUIMin((int32)ParmInfo.min); - } - else - { - // Min value Houdini uses by default. - HoudiniParameterInt->SetUIMin(HAPI_UNREAL_PARAM_INT_UI_MIN); - } - - if (ParmInfo.hasUIMax) - { - HoudiniParameterInt->SetHasUIMax(true); - HoudiniParameterInt->SetUIMax((int32)ParmInfo.UIMax); - } - else if (ParmInfo.hasMax) - { - // If it is not set, use supplied max. - HoudiniParameterInt->SetUIMax((int32)ParmInfo.max); - } - else - { - // Max value Houdini uses by default. - HoudiniParameterInt->SetUIMax(HAPI_UNREAL_PARAM_INT_UI_MAX); - } - } - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* HoudiniParameterIntChoice = Cast(HoudiniParameter); - if (HoudiniParameterIntChoice && !HoudiniParameterIntChoice->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterIntChoice->SetValueIndex(ParmInfo.intValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - int32 CurrentIntValue = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &CurrentIntValue, - ParmInfo.intValuesIndex, ParmInfo.size), false); - - // Check the value is valid - if (CurrentIntValue >= ParmInfo.choiceCount) - { - HOUDINI_LOG_WARNING(TEXT("parm '%s' has an invalid value %d, menu tokens are not supported for choice menus"), - *HoudiniParameterIntChoice->GetParameterName(), CurrentIntValue); - CurrentIntValue = 0; - } - - HoudiniParameterIntChoice->SetIntValue(CurrentIntValue); - } - - // Get the choice descriptors - if (bFullUpdate) - { - // Set the default value at created - HoudiniParameterIntChoice->SetDefaultIntValue(); - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - - // Set the array sizes - HoudiniParameterIntChoice->SetNumChoices(ParmInfo.choiceCount); - - bool bMatchedSelectionLabel = false; - int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValue(); - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ChoiceLabel = HoudiniParameterIntChoice->GetStringChoiceLabelAt(ChoiceIdx); - if (ChoiceLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ChoiceLabel)) - return false; - //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); - } - - // Match our string value to the corresponding selection label. - if (ChoiceIdx == CurrentIntValue) - { - HoudiniParameterIntChoice->SetStringValue(*ChoiceLabel); - } - } - } - else if (bUpdateValue) - { - // We still need to match the string value to the label - HoudiniParameterIntChoice->UpdateStringValueFromInt(); - } - } - } - break; - - case EHoudiniParameterType::StringChoice: - { - UHoudiniParameterChoice* HoudiniParameterStringChoice = Cast(HoudiniParameter); - if (HoudiniParameterStringChoice && !HoudiniParameterStringChoice->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterStringChoice->SetValueIndex(ParmInfo.stringValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandle, - ParmInfo.stringValuesIndex, ParmInfo.size), false); - - // Get the string value - FString StringValue; - FHoudiniEngineString HoudiniEngineString(StringHandle); - HoudiniEngineString.ToFString(StringValue); - - HoudiniParameterStringChoice->SetStringValue(StringValue); - } - - // Get the choice descriptors - if (bFullUpdate) - { - // Set default value at created. - HoudiniParameterStringChoice->SetDefaultStringValue(); - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - - // Set the array sizes - HoudiniParameterStringChoice->SetNumChoices(ParmInfo.choiceCount); - - bool bMatchedSelectionLabel = false; - FString CurrentStringValue = HoudiniParameterStringChoice->GetStringValue(); - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ChoiceValue = HoudiniParameterStringChoice->GetStringChoiceValueAt(ChoiceIdx); - if (ChoiceValue) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].valueSH); - if (!HoudiniEngineString.ToFString(*ChoiceValue)) - return false; - //StringChoiceValues.Add(TSharedPtr< FString >(ChoiceValue)); - } - - FString * ChoiceLabel = HoudiniParameterStringChoice->GetStringChoiceLabelAt(ChoiceIdx); - if (ChoiceLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ChoiceLabel)) - return false; - //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); - } - - // If this is a string choice list, we need to match name with corresponding selection label. - if (!bMatchedSelectionLabel && ChoiceValue->Equals(CurrentStringValue)) - { - bMatchedSelectionLabel = true; - HoudiniParameterStringChoice->SetIntValue(ChoiceIdx); - } - } - } - else if (bUpdateValue) - { - // We still need to match the string value to the label - HoudiniParameterStringChoice->UpdateIntValueFromString(); - } - } - } - break; - - case EHoudiniParameterType::Label: - { - UHoudiniParameterLabel* HoudiniParameterLabel = Cast(HoudiniParameter); - if (HoudiniParameterLabel && !HoudiniParameterLabel->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_LABEL) - return false; - - // Set the valueIndex - HoudiniParameterLabel->SetValueIndex(ParmInfo.stringValuesIndex); - - // Get the actual value for this property. - TArray StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandles[0], - ParmInfo.stringValuesIndex, ParmInfo.size); - - HoudiniParameterLabel->EmptyLabelString(); - - // Convert HAPI string handles to Unreal strings. - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - HoudiniParameterLabel->AddLabelString(ValueString); - } - } - } - break; - - case EHoudiniParameterType::MultiParm: - { - UHoudiniParameterMultiParm* HoudiniParameterMulti = Cast(HoudiniParameter); - if (HoudiniParameterMulti && !HoudiniParameterMulti->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST) - return false; - - // Set the valueIndex - HoudiniParameterMulti->SetValueIndex(ParmInfo.intValuesIndex); - - // Set the multiparm value - int32 MultiParmValue = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &MultiParmValue, ParmInfo.intValuesIndex, 1), false); - - HoudiniParameterMulti->SetValue(MultiParmValue); - HoudiniParameterMulti->MultiParmInstanceCount = ParmInfo.instanceCount; - HoudiniParameterMulti->MultiParmInstanceLength = ParmInfo.instanceLength; - - } - - if (bFullUpdate) - { - HoudiniParameterMulti->SetDefaultInstanceCount(ParmInfo.instanceCount); - } - } - break; - - case EHoudiniParameterType::Separator: - { - UHoudiniParameterSeparator* HoudiniParameterSeparator = Cast(HoudiniParameter); - if (HoudiniParameterSeparator && !HoudiniParameterSeparator->IsPendingKill()) - { - // We can only handle separator type. - if (ParmInfo.type != HAPI_PARMTYPE_SEPARATOR) - return false; - - // Set the valueIndex - HoudiniParameterSeparator->SetValueIndex(ParmInfo.stringValuesIndex); - } - } - break; - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - { - UHoudiniParameterString* HoudiniParameterString = Cast(HoudiniParameter); - if (HoudiniParameterString && !HoudiniParameterString->IsPendingKill()) - { - // We can only handle string type. - if (ParmInfo.type != HAPI_PARMTYPE_STRING && ParmInfo.type != HAPI_PARMTYPE_NODE) - return false; - - // Set the valueIndex - HoudiniParameterString->SetValueIndex(ParmInfo.stringValuesIndex); - - // Stop if we don't want to update the value - if (bUpdateValue) - { - // Get the actual value for this property. - TArray< HAPI_StringHandle > StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandles[0], - ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - - // Convert HAPI string handles to Unreal strings. - HoudiniParameterString->SetNumberOfValues(ParmInfo.size); - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - HoudiniParameterString->SetValueAt(ValueString, Idx); - } - } - - if (bFullUpdate) - { - // Set default string values on created - HoudiniParameterString->SetDefaultValues(); - // Check if the parameter has the "asset_ref" tag - HoudiniParameterString->SetIsAssetRef( - FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_ASSET_REF)); - } - } - } - break; - - case EHoudiniParameterType::Toggle: - { - UHoudiniParameterToggle* HoudiniParameterToggle = Cast(HoudiniParameter); - if (HoudiniParameterToggle && !HoudiniParameterToggle->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_TOGGLE) - return false; - - // Set the valueIndex - HoudiniParameterToggle->SetValueIndex(ParmInfo.intValuesIndex); - - // Stop if we don't want to update the value - if (bUpdateValue) - { - // Get the actual values for this property. - HoudiniParameterToggle->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterToggle->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - } - - if (bFullUpdate) - { - HoudiniParameterToggle->SetDefaultValues(); - } - } - break; - - case EHoudiniParameterType::Invalid: - { - // TODO - } - break; - } - - return true; -} - -bool -FHoudiniParameterTranslator::HapiGetParameterTagValue(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag, FString& TagValue) -{ - // Default - TagValue = FString(); - - // Does the parameter has the tag? - bool HasTag = false; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ParmHasTag( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &HasTag), false); - - if (!HasTag) - return false; - - // Get the tag string value - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmTagValue( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &StringHandle), false); - - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(TagValue)) - { - return true; - } - - return false; -} - - -bool -FHoudiniParameterTranslator::HapiGetParameterUnit(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString) -{ - // - OutUnitString = TEXT(""); - - // We're looking for the parameter unit tag - //FString UnitTag = "units"; - - // Get the actual string value. - FString UnitString = TEXT(""); - if (!FHoudiniParameterTranslator::HapiGetParameterTagValue(NodeId, ParmId, "units", UnitString)) - return false; - - // We need to do some replacement in the string here in order to be able to get the - // proper unit type when calling FUnitConversion::UnitFromString(...) after. - - // Per second and per hour are the only "per" unit that unreal recognize - UnitString.ReplaceInline(TEXT("s-1"), TEXT("/s")); - UnitString.ReplaceInline(TEXT("h-1"), TEXT("/h")); - - // Houdini likes to add '1' on all the unit, so we'll remove all of them - // except the '-1' that still needs to remain. - UnitString.ReplaceInline(TEXT("-1"), TEXT("--")); - UnitString.ReplaceInline(TEXT("1"), TEXT("")); - UnitString.ReplaceInline(TEXT("--"), TEXT("-1")); - - OutUnitString = UnitString; - - return true; -} - -bool -FHoudiniParameterTranslator::HapiGetParameterHasTag(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag) -{ - // Does the parameter has the tag we're looking for? - bool HasTag = false; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &HasTag), false); - - return HasTag; -} - - -bool -FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * HAC ) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TMap RampsToRevert; - - for (int32 ParmIdx = 0; ParmIdx < HAC->GetNumParameters(); ParmIdx++) - { - UHoudiniParameter*& CurrentParm = HAC->Parameters[ParmIdx]; - if (!CurrentParm || CurrentParm->IsPendingKill() || !CurrentParm->HasChanged()) - continue; - - bool bSuccess = false; - - if (CurrentParm->IsPendingRevertToDefault()) - { - bSuccess = RevertParameterToDefault(CurrentParm); - - if (CurrentParm->GetParameterType() == EHoudiniParameterType::FloatRamp || - CurrentParm->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - RampsToRevert.Add(CurrentParm->GetParameterName(), CurrentParm); - } - } - else - { - bSuccess = UploadParameterValue(CurrentParm); - } - - - if (bSuccess) - { - CurrentParm->MarkChanged(false); - //CurrentParm->SetNeedsToTriggerUpdate(false); - } - else - { - // Keep this param marked as changed but prevent it from generating updates - CurrentParm->SetNeedsToTriggerUpdate(false); - } - } - - FHoudiniParameterTranslator::RevertRampParameters(RampsToRevert, HAC->GetAssetId()); - - return true; -} - -bool -FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return false; - - switch (InParam->GetParameterType()) - { - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParam = Cast(InParam); - if (!FloatParam || FloatParam->IsPendingKill()) - { - return false; - } - - float* DataPtr = FloatParam->GetValuesPtr(); - if (!DataPtr) - { - return false; - } - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - FloatParam->GetNodeId(), DataPtr, FloatParam->GetValueIndex(), FloatParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::Int: - { - UHoudiniParameterInt* IntParam = Cast(InParam); - if (!IntParam || IntParam->IsPendingKill()) - { - return false; - } - - int32* DataPtr = IntParam->GetValuesPtr(); - if (!DataPtr) - { - return false; - } - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - IntParam->GetNodeId(), DataPtr, IntParam->GetValueIndex(), IntParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::String: - { - UHoudiniParameterString* StringParam = Cast(InParam); - if (!StringParam || StringParam->IsPendingKill()) - { - return false; - } - - int32 NumValues = StringParam->GetNumberOfValues(); - if (NumValues <= 0) - { - return false; - } - - for (int32 Idx = 0; Idx < NumValues; Idx++) - { - std::string ConvertedString = TCHAR_TO_UTF8(*(StringParam->GetValueAt(Idx))); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - StringParam->GetNodeId(), ConvertedString.c_str(), StringParam->GetParmId(), Idx), false); - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) - return false; - - // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValue(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); - } - break; - case EHoudiniParameterType::StringChoice: - { - UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) - { - return false; - } - - if (ChoiceParam->IsStringChoice()) - { - // Set the parameter's string value. - std::string ConvertedString = TCHAR_TO_UTF8(*(ChoiceParam->GetStringValue())); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), ConvertedString.c_str(), ChoiceParam->GetParmId(), 0), false); - } - else - { - // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValue(); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); - } - } - break; - - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* ColorParam = Cast(InParam); - if (!ColorParam || ColorParam->IsPendingKill()) - return false; - - bool bHasAlpha = ColorParam->GetTupleSize() == 4 ? true : false; - FLinearColor Color = ColorParam->GetColorValue(); - - // Set the color value - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - ColorParam->GetNodeId(), - (float*)(&Color.R), ColorParam->GetValueIndex(), bHasAlpha ? 4 : 3), false); - - } - break; - - case EHoudiniParameterType::Button: - { - UHoudiniParameterButton* ButtonParam = Cast(InParam); - if (!ButtonParam) - return false; - - TArray DataArray; - DataArray.Add(1); - - // Set the button parameter value to 1, (setting button param to any value will call the callback function.) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ButtonParam->GetNodeId(), - DataArray.GetData(), - ButtonParam->GetValueIndex(), 1), false); - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - UHoudiniParameterButtonStrip* ButtonStripParam = Cast(InParam); - if (!ButtonStripParam) - return false; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ButtonStripParam->GetNodeId(), - ButtonStripParam->Values.GetData(), - ButtonStripParam->GetValueIndex(), ButtonStripParam->Count), false); - } - break; - - case EHoudiniParameterType::Toggle: - { - UHoudiniParameterToggle* ToggleParam = Cast(InParam); - if (!ToggleParam) - return false; - - // Set the toggle parameter values. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ToggleParam->GetNodeId(), - ToggleParam->GetValuesPtr(), - ToggleParam->GetValueIndex(), ToggleParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - UHoudiniParameterFile* FileParam = Cast(InParam); - - if (!UploadDirectoryPath(FileParam)) - return false; - } - break; - - case EHoudiniParameterType::MultiParm: - { - if (!UploadMultiParmValues(InParam)) - return false; - } - - break; - - case EHoudiniParameterType::FloatRamp: - { - if (!UploadRampParameter(InParam)) - return false; - } - break; - - case EHoudiniParameterType::ColorRamp: - { - if (!UploadRampParameter(InParam)) - return false; - } - break; - - default: - { - // TODO: implement other parameter types! - return false; - } - break; - } - - // The parameter is no longer considered as changed - InParam->MarkChanged(false); - - return true; -} - -bool -FHoudiniParameterTranslator::RevertParameterToDefault(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return false; - - if (!InParam->IsPendingRevertToDefault()) - return false; - - TArray TupleToRevert; - InParam->GetTuplePendingRevertToDefault(TupleToRevert); - if (TupleToRevert.Num() <= 0) - return false; - - FString ParameterName = InParam->GetParameterName(); - - bool bReverted = true; - for (auto CurrentIdx : TupleToRevert ) - { - if (!TupleToRevert.IsValidIndex(CurrentIdx)) - { - // revert the whole parameter to its default value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefaults( - FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName))) - { - HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s to its default value."), *ParameterName); - bReverted = false; - } - } - else - { - // revert a tuple to its default value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefault( - FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName), CurrentIdx)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s - %d to its default value."), *ParameterName, CurrentIdx); - bReverted = false; - } - } - } - - if (!bReverted) - return false; - - // The parameter no longer needs to be reverted - InParam->MarkDefault(true); - - return true; -} - -EHoudiniFolderParameterType -FHoudiniParameterTranslator::GetFolderTypeFromParamInfo(const HAPI_ParmInfo* ParamInfo) -{ - EHoudiniFolderParameterType Type = EHoudiniFolderParameterType::Invalid; - - switch (ParamInfo->scriptType) - { - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPSIMPLE: - Type = EHoudiniFolderParameterType::Simple; - break; - - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPCOLLAPSIBLE: - Type = EHoudiniFolderParameterType::Collapsible; - break; - - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUP: - Type = EHoudiniFolderParameterType::Tabs; - break; - - // Treat Radio folders as tabs for now - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPRADIO: - Type = EHoudiniFolderParameterType::Radio; - break; - - default: - Type = EHoudiniFolderParameterType::Other; - break; - - } - - return Type; - -} - -bool -FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InParam, TArray &OldParams, const int32& InAssetId, const int32 CurrentIndex) -{ - - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - - if (!MultiParam || MultiParam->IsPendingKill()) - return false; - - UHoudiniParameterRampFloat* FloatRampParameter = nullptr; - UHoudiniParameterRampColor* ColorRampParameter = nullptr; - - if (MultiParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - FloatRampParameter = Cast(MultiParam); - - else if (MultiParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - ColorRampParameter = Cast(MultiParam); - - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); - - HAPI_NodeId NodeId = AssetInfo.nodeId; - - int32 Idx = 0; - int32 InstanceCount = -1; - HAPI_ParmId ParmId = -1; - TArray ParmInfos; - if (!GetMultiParmInstanceStartIdx(AssetInfo, MultiParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - - for (int n = 0; n < InstanceCount - MultiParam->GetInstanceCount(); ++n) - { - FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset); - } - - for (int n = 0; n < MultiParam->GetInstanceCount() - InstanceCount; ++n) - { - FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset); - } - - - // Sync nested multi-params recursively - for (int32 ParamIdx = CurrentIndex; ParamIdx < OldParams.Num(); ++ParamIdx) - { - UHoudiniParameter* NextParm = OldParams[ParamIdx]; - if (NextParm->GetParentParmId() == ParmId) - { - if (NextParm->GetParameterType() == EHoudiniParameterType::MultiParm) - { - SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, ParamIdx); - } - } - } - - - // The multiparm is a ramp, Get the param infos again, since the number of param instances is changed - if (!GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - // Step 3: Set values of the inserted points - if (FloatRampParameter) - { - for (auto & Point : FloatRampParameter->Points) - { - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); - - // 2: update float value at param Idx + 1 - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Value), ParmInfos[Idx + 1].floatValuesIndex, 1); - - // 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Point->Interpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - else if (ColorRampParameter) - { - for (auto& Point : ColorRampParameter->Points) - { - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); - - // 2: update color value at param Idx + 1 - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, (float*)(&Point->Value.R), ParmInfos[Idx + 1].floatValuesIndex, 3); - - // 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Point->Interpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - - - return true; -} - - -bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam) -{ - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam || MultiParam->IsPendingKill()) - return false; - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InParam->GetOuter()); - if (!HoudiniAssetComponent) - return false; - - int32 InsertIndexStart = -1; - UHoudiniParameterRampFloat* RampFloatParam = Cast(InParam); - UHoudiniParameterRampColor* RampColorParam = Cast(InParam); - - TArray *Events = nullptr; - if (RampFloatParam) - { - Events = &(RampFloatParam->ModificationEvents); - InsertIndexStart = RampFloatParam->GetInstanceCount(); - } - else if (RampColorParam) - { - Events = &(RampColorParam->ModificationEvents); - InsertIndexStart = RampColorParam->GetInstanceCount(); - } - else - return false; - - // Handle All Events - Events->Sort([](const UHoudiniParameterRampModificationEvent& A, const UHoudiniParameterRampModificationEvent& B) - { - return A.DeleteInstanceIndex > B.DeleteInstanceIndex; - }); - - - // Step 1: Handle all delete events first - for (auto& Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsDeleteEvent()) - continue; - - FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Event->DeleteInstanceIndex + MultiParam->InstanceStartOffset); - - InsertIndexStart -= 1; - } - - int32 InsertIndex = InsertIndexStart; - - - // Step 2: Handle all insert events - for (auto& Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsInsertEvent()) - continue; - - FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), InsertIndex + MultiParam->InstanceStartOffset); - - InsertIndex += 1; - } - - // Step 3: Set inserted parameter values (only if there are instances inserted) - if (InsertIndex > InsertIndexStart) - { - if (HoudiniAssetComponent) - { - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), HoudiniAssetComponent->AssetId, &AssetInfo), false); - - int32 Idx = 0; - int32 InstanceCount = -1; - HAPI_ParmId ParmId = -1; - TArray< HAPI_ParmInfo > ParmInfos; - - if (!FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), - Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - if (InstanceCount < 0) - return false; - - // Instance count doesn't match, - if (InsertIndex != InstanceCount) - return false; - - - // Starting index of parameters which just inserted - Idx += 3 * InsertIndexStart; - - - for (auto & Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsInsertEvent()) - continue; - - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &(Event->InsertPosition), ParmInfos[Idx].floatValuesIndex, 1); - - // step 2: update value at param Idx + 1 - if (Event->IsFloatRampEvent()) - { - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &(Event->InsertFloat), ParmInfos[Idx + 1].floatValuesIndex, 1); - } - else - { - // color value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, (float*)(&Event->InsertColor.R), ParmInfos[Idx + 1].floatValuesIndex, 3); - } - - // step 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Event->InsertInterpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - } - - // Step 4: clear all events - Events->Empty(); - - return true; -} - -bool FHoudiniParameterTranslator::UploadMultiParmValues(UHoudiniParameter* InParam) -{ - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam) - return false; - - TArray &LastModificationArray = MultiParam->MultiParmInstanceLastModifyArray; - - int32 Size = MultiParam->MultiParmInstanceLastModifyArray.Num(); - - for (int32 Index = 0; Index < Size; ++Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Inserted) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) - return false; - - } - } - - for (int32 Index = Size - 1; Index >= 0; --Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) - return false; - } - } - - // Remove all removal events. - for (int32 Index = Size - 1; Index >= 0; --Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) - LastModificationArray.RemoveAt(Index); - } - - // The last modification array is resized. - Size = LastModificationArray.Num(); - - // Reset the last modification array - for (int32 Itr =Size - 1; Itr >= 0; --Itr) - { - LastModificationArray[Itr] = EHoudiniMultiParmModificationType::None; - } - - MultiParam->MultiParmInstanceCount = Size; - - return true; -} - -bool -FHoudiniParameterTranslator::UploadDirectoryPath(UHoudiniParameterFile* InParam) -{ - if(!InParam) - return false; - - for (int32 Index = 0; Index < InParam->GetNumValues(); ++Index) - { - std::string ConvertedString = TCHAR_TO_UTF8(*(InParam->GetValueAt(Index))); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue(FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), ConvertedString.c_str(), InParam->GetParmId(), Index), false); - } - - return true; -} - -bool -FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, - int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos) -{ - // Reset outputs - OutStartIdx = 0; - OutInstanceCount = -1; - OutParmId = -1; - OutParmInfos.Empty(); - - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &NodeInfo), false); - - OutParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &OutParmInfos[0], 0, NodeInfo.parmCount), false); - - - while (OutStartIdx < OutParmInfos.Num()) - { - FString ParmNameBuffer; - FHoudiniEngineString(OutParmInfos[OutStartIdx].nameSH).ToFString(ParmNameBuffer); - - if (ParmNameBuffer == InParmName) - { - OutParmId = OutParmInfos[OutStartIdx].id; - OutInstanceCount = OutParmInfos[OutStartIdx].instanceCount; - break; - } - - OutStartIdx += 1; - } - - // Start index of the ramp children parameters - OutStartIdx += 1; - - return true; -} - -bool -FHoudiniParameterTranslator::RevertRampParameters(TMap & InRampParams, const int32 & AssetId) -{ - if (InRampParams.Num() <= 0) - return true; - - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); - - // Retrieve all the parameter infos. - TArray< HAPI_ParmInfo > ParmInfos; - ParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); - - int32 ParamIdx = 0; - while (ParamIdx < ParmInfos.Num()) - { - const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; - FString ParmName; - FHoudiniEngineString(ParmInfo.nameSH).ToFString(ParmName); - - if (InRampParams.Contains(ParmName)) - { - if (!InRampParams[ParmName]) - { - ParamIdx += 1; - continue; - } - - if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat * FloatRamp = Cast(InRampParams[ParmName]); - if (!FloatRamp) - { - ParamIdx += 1; - continue; - } - - if (ParmInfo.instanceCount != FloatRamp->NumDefaultPoints) - { - ParamIdx += 1; - continue; - } - - for (int32 PtIdx = 0; PtIdx < FloatRamp->NumDefaultPoints; ++PtIdx) - { - const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; - const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; - const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultValues.GetData() + PtIdx, ValueParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); - - ParamIdx += 3; - } - } - - if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor * ColorRamp = Cast(InRampParams[ParmName]); - if (!ColorRamp) - { - ParamIdx += 1; - continue; - } - - if (ParmInfo.instanceCount != ColorRamp->NumDefaultPoints) - { - ParamIdx += 1; - continue; - } - - for (int32 PtIdx = 0; PtIdx < ColorRamp->NumDefaultPoints; ++PtIdx) - { - const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; - const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; - const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, ColorRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, (float*)(&ColorRamp->DefaultValues[PtIdx].R), ValueParmInfo.floatValuesIndex, 3); - - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, ColorRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); - - ParamIdx += 3; - } - } - } - - ParamIdx += 1; - } - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniInput.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniParameter.h" +#include "HoudiniAssetComponent.h" + + +// Default values for certain UI min and max parameter values +#define HAPI_UNREAL_PARAM_INT_UI_MIN 0 +#define HAPI_UNREAL_PARAM_INT_UI_MAX 10 +#define HAPI_UNREAL_PARAM_FLOAT_UI_MIN 0.0f +#define HAPI_UNREAL_PARAM_FLOAT_UI_MAX 10.0f + +// Some default parameter name +#define HAPI_UNREAL_PARAM_TRANSLATE "t" +#define HAPI_UNREAL_PARAM_ROTATE "r" +#define HAPI_UNREAL_PARAM_SCALE "s" +#define HAPI_UNREAL_PARAM_PIVOT "p" +#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" + +// +bool +FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // When recooking/rebuilding the HDA, force a full update of all params + bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); + + TArray NewParameters; + if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, true, bForceFullUpdate)) + { + /* + // DO NOT MANUALLY DESTROY THE OLD/DANGLING PARAMETERS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + + // Destroy old/dangling parameters + for (auto& OldParm : HAC->Parameters) + { + if (!OldParm || OldParm->IsPendingKill()) + continue; + + OldParm->ConditionalBeginDestroy(); + OldParm = nullptr; + } + */ + + // Replace with the new parameters + HAC->Parameters = NewParameters; + } + + + return true; +} + +bool +FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) +{ + // Call OnPreCook for all parameters. + // Parameters can use this to ensure that any cached / non-cooking state is properly + // synced before the cook starts (Looking at you, ramp parameters!) + for (UHoudiniParameter* Param : HAC->Parameters) + { + if (!Param || Param->IsPendingKill()) + continue; + + Param->OnPreCook(); + } + + return true; +} + +// +bool +FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Update all the parameters using the loaded parameter object + // We set "UpdateValues" to false because we do not want to "read" the parameter value + // from Houdini but keep the loaded value + + // This is the first cook on loading after a save or duplication, + for (int32 Idx = 0; Idx < HAC->Parameters.Num(); ++Idx) + { + UHoudiniParameter* Param = HAC->Parameters[Idx]; + + if (!Param || Param->IsPendingKill()) + continue; + + switch(Param->GetParameterType()) + { + case EHoudiniParameterType::ColorRamp: + case EHoudiniParameterType::FloatRamp: + case EHoudiniParameterType::MultiParm: + { + // We need to sync the Ramp parameters first, so that their child parameters can be kept + // TODO: Simplify this, should be handled in BuildAllParameters + SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, Idx); + } + break; + + case EHoudiniParameterType::Button: + case EHoudiniParameterType::ButtonStrip: + { + // Do not trigger buttons upon loading + Param->MarkChanged(false); + } + break; + + default: + break; + } + } + + // When recooking/rebuilding the HDA, force a full update of all params + bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); + + // This call to BuildAllParameters will keep all the loaded parameters (in the HAC's Parameters array) + // that are still present in the HDA, and keep their loaded value. + TArray NewParameters; + if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, false, bForceFullUpdate)) + { + /* + // DO NOT DESTROY OLD PARAMS MANUALLY HERE + // This causes crashes upon duplication due to uncollected zombie objects... + // GC is supposed to handle this by itself + // Destroy old/dangling parameters + for (auto& OldParm : HAC->Parameters) + { + if (!OldParm || OldParm->IsPendingKill()) + continue; + + OldParm->ConditionalBeginDestroy(); + OldParm = nullptr; + } + */ + + // Simply replace with the new parameters + HAC->Parameters = NewParameters; + } + + return true; +} + +bool +FHoudiniParameterTranslator::BuildAllParameters( + const HAPI_NodeId& AssetId, + class UObject* Outer, + TArray& CurrentParameters, + TArray& NewParameters, + const bool& bUpdateValues, + const bool& InForceFullUpdate) +{ + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); + + NewParameters.Empty(); + if (NodeInfo.parmCount == 0) + { + // The asset doesnt have any parameter, we're done. + return true; + } + else if (NodeInfo.parmCount < 0) + { + // Invalid parm count + return false; + } + + TArray AllMultiParams; + + // Retrieve all the parameter infos. + TArray< HAPI_ParmInfo > ParmInfos; + ParmInfos.SetNumUninitialized(NodeInfo.parmCount); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); + + // Create a name lookup cache for the current parameters + TMap CurrentParametersByName; + CurrentParametersByName.Reserve(CurrentParameters.Num()); + for (auto& Parm : CurrentParameters) + { + if (!Parm) + continue; + CurrentParametersByName.Add(Parm->GetParameterName(), Parm); + } + + // Create properties for parameters. + TArray NewParmIds; + for (int32 ParamIdx = 0; ParamIdx < NodeInfo.parmCount; ++ParamIdx) + { + + // Retrieve param info at this index. + const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; + + // If the parameter is corrupt, skip it + if (ParmInfo.id < 0 || ParmInfo.childIndex < 0) + { + HOUDINI_LOG_WARNING(TEXT("Corrupt parameter %d detected, skipping."), ParamIdx); + continue; + } + + // If the parameter is invisible, skip it. + //if (ParmInfo.invisible) + // continue; + + // Check if any parent folder of this parameter is invisible + bool SkipParm = false; + HAPI_ParmId ParentId = ParmInfo.parentId; + while (ParentId > 0 && !SkipParm) + { + if (const HAPI_ParmInfo* ParentInfoPtr = ParmInfos.FindByPredicate([=](const HAPI_ParmInfo& Info) { + return Info.id == ParentId; + })) + { + if (ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER) + SkipParm = true; + ParentId = ParentInfoPtr->parentId; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not find parent of parameter %d"), ParmInfo.id); + SkipParm = true; + } + } + + if (SkipParm) + continue; + + // See if this parameter has already been created. + // We can't use the HAPI_ParmId because it is not unique to parameter instances, + // so instead, try to find the existing parameter by name using the lookup table + FString NewParmName; + FHoudiniEngineString(ParmInfo.nameSH).ToFString(NewParmName); + + EHoudiniParameterType ParmType = EHoudiniParameterType::Invalid; + FHoudiniParameterTranslator::GetParmTypeFromParmInfo(ParmInfo, ParmType); + + UHoudiniParameter ** FoundHoudiniParameter = CurrentParametersByName.Find(NewParmName); + + // If that parameter exists, we might be able to simply reuse it. + bool IsFoundParameterValid = false; + if (FoundHoudiniParameter && *FoundHoudiniParameter && !(*FoundHoudiniParameter)->IsPendingKill()) + { + // First, we can simply check that the tuple size hasn't changed + if ((*FoundHoudiniParameter)->GetTupleSize() != ParmInfo.size) + { + IsFoundParameterValid = false; + } + else if (ParmType == EHoudiniParameterType::Invalid ) + { + IsFoundParameterValid = false; + } + else if (ParmType != (*FoundHoudiniParameter)->GetParameterType() ) + { + // Types do not match + IsFoundParameterValid = false; + } + else if ( !CheckParameterTypeAndClassMatch( *FoundHoudiniParameter, ParmType) ) + { + // Found parameter class does not match + IsFoundParameterValid = false; + } + else + { + // We can reuse the parameter + IsFoundParameterValid = true; + } + } + + UHoudiniParameter * HoudiniAssetParameter = nullptr; + + if (IsFoundParameterValid) + { + // We can reuse the parameter we found + HoudiniAssetParameter = *FoundHoudiniParameter; + + // Transfer param object from current map to new map + CurrentParameters.Remove(HoudiniAssetParameter); + CurrentParametersByName.Remove(NewParmName); + + // Do a fast update of this parameter + if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, InForceFullUpdate, bUpdateValues)) + continue; + + // Reset the states of ramp parameters. + switch (HoudiniAssetParameter->GetParameterType()) + { + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); + if (FloatRampParam) + { + UHoudiniAssetComponent* ParentHAC = Cast(FloatRampParam->GetOuter()); + if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) + FloatRampParam->bCaching = false; + } + + break; + } + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); + if (ColorRampParam) + { + UHoudiniAssetComponent* ParentHAC = Cast(ColorRampParam->GetOuter()); + if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) + ColorRampParam->bCaching = false; + } + + break; + } + } + + } + else + { + // Create a new parameter object of the appropriate type + HoudiniAssetParameter = CreateTypedParameter(Outer, ParmType, NewParmName); + // Fully update this parameter + if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, true, true)) + continue; + + } + + // Add the new parameters + NewParameters.Add(HoudiniAssetParameter); + NewParmIds.Add(ParmInfo.id); + + + // Check if the parameter is a direct child of a multiparam. + if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::MultiParm) + AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); + + if (AllMultiParams.Contains(HoudiniAssetParameter->GetParentParmId())) + { + HoudiniAssetParameter->SetIsDirectChildOfMultiParm(true); + + // Treat the folderlist whose direct parent is a multi param as a multi param too. + if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::FolderList) + AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); + } + + } + + // Assign folder type to all folderlists, + // if the first child of the folderlist is Tab or Radio button, set the bIsTabMenu of the folderlistParam to be true, otherwise false + for (int32 Idx = 0; Idx < NewParameters.Num(); ++Idx) + { + UHoudiniParameter * CurParam = NewParameters[Idx]; + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (CurParam->GetParameterType() == EHoudiniParameterType::FolderList) + { + UHoudiniParameterFolderList* CurFolderList = Cast(CurParam); + if (!CurFolderList || CurFolderList->IsPendingKill()) + continue; + + int32 FirstChildIdx = Idx + 1; + if (!NewParameters.IsValidIndex(FirstChildIdx)) + continue; + + UHoudiniParameterFolder* FirstChildFolder = Cast(NewParameters[FirstChildIdx]); + if (!FirstChildFolder || FirstChildFolder->IsPendingKill()) + continue; + + if (FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Radio || + FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Tabs) + { + // If this is the first time build + if (!CurFolderList->IsTabMenu()) + { + // Set the folderlist to be tabs + CurFolderList->SetIsTabMenu(true); + // Select the first child tab folder by default. + FirstChildFolder->SetChosen(true); + } + } + else + CurFolderList->SetIsTabMenu(false); + } + } + + FHoudiniEngineUtils::UpdateEditorProperties(Outer, true); + + return true; +} + + +void +FHoudiniParameterTranslator::GetParmTypeFromParmInfo( + const HAPI_ParmInfo& ParmInfo, + EHoudiniParameterType& ParmType) +{ + ParmType = EHoudiniParameterType::Invalid; + //ParmValueType = EHoudiniParameterValueType::Invalid; + + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_BUTTON: + ParmType = EHoudiniParameterType::Button; + //ParmValueType = EHoudiniParameterValueType::Int; + break; + + case HAPI_PARMTYPE_STRING: + { + if (ParmInfo.choiceCount > 0) + { + ParmType = EHoudiniParameterType::StringChoice; + //ParmValueType = EHoudiniParameterValueType::String; + } + else + { + ParmType = EHoudiniParameterType::String; + //ParmValueType = EHoudiniParameterValueType::String; + } + break; + } + + case HAPI_PARMTYPE_INT: + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_BUTTONSTRIP) + { + ParmType = EHoudiniParameterType::ButtonStrip; + break; + } + + if (ParmInfo.choiceCount > 0) + { + ParmType = EHoudiniParameterType::IntChoice; + //ParmValueType = EHoudiniParameterValueType::Int; + } + else + { + ParmType = EHoudiniParameterType::Int; + //ParmValueType = EHoudiniParameterValueType::Int; + } + break; + } + + case HAPI_PARMTYPE_FLOAT: + { + ParmType = EHoudiniParameterType::Float; + //ParmValueType = EHoudiniParameterValueType::Float; + break; + } + + case HAPI_PARMTYPE_TOGGLE: + { + ParmType = EHoudiniParameterType::Toggle; + //ParmValueType = EHoudiniParameterValueType::Int; + break; + } + + case HAPI_PARMTYPE_COLOR: + { + ParmType = EHoudiniParameterType::Color; + //ParmValueType = EHoudiniParameterValueType::Float; + break; + } + + case HAPI_PARMTYPE_LABEL: + { + ParmType = EHoudiniParameterType::Label; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_SEPARATOR: + { + ParmType = EHoudiniParameterType::Separator; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + case HAPI_PARMTYPE_FOLDERLIST: + { + ParmType = EHoudiniParameterType::FolderList; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + // Treat radio folders as tab folders for now + case HAPI_PARMTYPE_FOLDERLIST_RADIO: + { + ParmType = EHoudiniParameterType::FolderList; + break; + } + + case HAPI_PARMTYPE_FOLDER: + { + ParmType = EHoudiniParameterType::Folder; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + case HAPI_PARMTYPE_MULTIPARMLIST: + { + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType) + { + ParmType = EHoudiniParameterType::FloatRamp; + //ParmValueType = EHoudiniParameterValueType::Float; + } + else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + { + ParmType = EHoudiniParameterType::ColorRamp; + //ParmValueType = EHoudiniParameterValueType::Float; + } + else + { + ParmType = EHoudiniParameterType::MultiParm; + //ParmValueType = EHoudiniParameterValueType::Int; + } + break; + } + + case HAPI_PARMTYPE_PATH_FILE: + { + ParmType = EHoudiniParameterType::File; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_DIR: + { + ParmType = EHoudiniParameterType::FileDir; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_GEO: + { + ParmType = EHoudiniParameterType::FileGeo; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + { + ParmType = EHoudiniParameterType::FileImage; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_NODE: + { + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + ParmType = EHoudiniParameterType::Input; + } + else + { + ParmType = EHoudiniParameterType::String; + } + break; + } + + default: + { + // Just ignore unsupported types for now. + HOUDINI_LOG_WARNING(TEXT("Parameter Type (%d) is unsupported"), static_cast(ParmInfo.type)); + break; + } + } +} + +UClass* +FHoudiniParameterTranslator::GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo) +{ + UClass* FoundClass = nullptr; + + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_STRING: + if (!ParmInfo.choiceCount) + FoundClass = UHoudiniParameterString::StaticClass(); + else + FoundClass = UHoudiniParameterChoice ::StaticClass(); + break; + + case HAPI_PARMTYPE_INT: + if (!ParmInfo.choiceCount) + FoundClass = UHoudiniParameterInt::StaticClass(); + else + FoundClass = UHoudiniParameterChoice::StaticClass(); + break; + + case HAPI_PARMTYPE_FLOAT: + FoundClass = UHoudiniParameterFloat::StaticClass(); + break; + + case HAPI_PARMTYPE_TOGGLE: + FoundClass = UHoudiniParameterToggle::StaticClass(); + break; + + case HAPI_PARMTYPE_COLOR: + FoundClass = UHoudiniParameterColor::StaticClass(); + break; + + case HAPI_PARMTYPE_LABEL: + FoundClass = UHoudiniParameterLabel::StaticClass(); + break; + + case HAPI_PARMTYPE_BUTTON: + FoundClass = UHoudiniParameterButton::StaticClass(); + break; + + case HAPI_PARMTYPE_SEPARATOR: + FoundClass = UHoudiniParameterSeparator::StaticClass(); + break; + + case HAPI_PARMTYPE_FOLDERLIST: + FoundClass = UHoudiniParameterFolderList::StaticClass(); + break; + + case HAPI_PARMTYPE_FOLDER: + FoundClass = UHoudiniParameterFolder::StaticClass(); + break; + + case HAPI_PARMTYPE_MULTIPARMLIST: + { + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + FoundClass = UHoudiniParameterRampFloat::StaticClass(); + else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + FoundClass = UHoudiniParameterRampColor::StaticClass(); + } + break; + + case HAPI_PARMTYPE_PATH_FILE: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_DIR: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_GEO: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + + case HAPI_PARMTYPE_NODE: + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + FoundClass = UHoudiniParameter::StaticClass(); + } + else + { + FoundClass = UHoudiniParameterString::StaticClass(); + } + break; + } + + if (FoundClass == nullptr) + return UHoudiniParameter::StaticClass(); + + return FoundClass; +} + +bool +FHoudiniParameterTranslator::CheckParameterTypeAndClassMatch(UHoudiniParameter* Parameter, const EHoudiniParameterType& ParmType) +{ + UClass* FoundClass = Parameter->GetClass(); + bool FailedTypeCheck = true; + + switch (ParmType) + { + case EHoudiniParameterType::Invalid: + { + FailedTypeCheck = true; + break; + } + + case EHoudiniParameterType::Button: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterButton >(); + break; + } + + case EHoudiniParameterType::Color: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterColor >(); + break; + } + + case EHoudiniParameterType::ColorRamp: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampColor >(); + break; + } + case EHoudiniParameterType::FloatRamp: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampFloat >(); + break; + } + + case EHoudiniParameterType::File: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileDir: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileGeo: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileImage: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + + case EHoudiniParameterType::Float: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFloat >(); + break; + } + + case EHoudiniParameterType::Folder: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolder >(); + break; + } + + case EHoudiniParameterType::FolderList: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolderList >(); + break; + } + + case EHoudiniParameterType::Input: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterOperatorPath >(); + break; + } + + case EHoudiniParameterType::Int: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterInt >(); + break; + } + + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::StringChoice: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); + break; + } + + case EHoudiniParameterType::Label: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterLabel >(); + break; + } + + case EHoudiniParameterType::MultiParm: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterMultiParm >(); + break; + } + + case EHoudiniParameterType::Separator: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterSeparator >(); + break; + } + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); + break; + } + + case EHoudiniParameterType::Toggle: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterToggle >(); + break; + } + }; + + return !FailedTypeCheck; +} +/* +bool +FHoudiniParameterTranslator::CheckParameterClassAndInfoMatch(UHoudiniParameter* Parameter, const HAPI_ParmInfo& ParmInfo) +{ + if (!Parameter || Parameter->IsPendingKill()) + return false; + + UClass* FoundClass = Parameter->GetClass(); + bool FailedTypeCheck = true; + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_STRING: + if (!ParmInfo.choiceCount) + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); + else + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); + break; + + case HAPI_PARMTYPE_INT: + if (!ParmInfo.choiceCount) + FailedTypeCheck &= !FoundClass->IsChildOf(); + else + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FLOAT: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_TOGGLE: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_COLOR: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_LABEL: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_BUTTON: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_SEPARATOR: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FOLDERLIST: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FOLDER: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_MULTIPARMLIST: + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + break; + + case HAPI_PARMTYPE_PATH_FILE: + case HAPI_PARMTYPE_PATH_FILE_DIR: + case HAPI_PARMTYPE_PATH_FILE_GEO: + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_NODE: + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + else + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + break; + } + + return FailedTypeCheck; +} +*/ + +UHoudiniParameter * +FHoudiniParameterTranslator::CreateTypedParameter(UObject * Outer, const EHoudiniParameterType& ParmType, const FString& ParmName) +{ + UHoudiniParameter* HoudiniParameter = nullptr; + // Create a parameter of the desired type + switch (ParmType) + { + case EHoudiniParameterType::Button: + HoudiniParameter = UHoudiniParameterButton::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::ButtonStrip: + HoudiniParameter = UHoudiniParameterButtonStrip::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Color: + HoudiniParameter = UHoudiniParameterColor::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::ColorRamp: + HoudiniParameter = UHoudiniParameterRampColor::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FloatRamp: + HoudiniParameter = UHoudiniParameterRampFloat::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::File: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FileDir: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileDir); + break; + + case EHoudiniParameterType::FileGeo: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileGeo); + break; + + case EHoudiniParameterType::FileImage: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileImage); + break; + + case EHoudiniParameterType::Float: + HoudiniParameter = UHoudiniParameterFloat::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Folder: + HoudiniParameter = UHoudiniParameterFolder::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FolderList: + HoudiniParameter = UHoudiniParameterFolderList::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Input: + // Input parameter simply use the base class as all the processingsince is handled by UHoudiniInput + HoudiniParameter = UHoudiniParameterOperatorPath::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(ParmType); + break; + + case EHoudiniParameterType::Int: + HoudiniParameter = UHoudiniParameterInt::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::IntChoice: + HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::IntChoice); + break; + + case EHoudiniParameterType::StringChoice: + HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::StringChoice); + break; + + case EHoudiniParameterType::Label: + HoudiniParameter = UHoudiniParameterLabel::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::MultiParm: + HoudiniParameter = UHoudiniParameterMultiParm::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Separator: + HoudiniParameter = UHoudiniParameterSeparator::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + HoudiniParameter = UHoudiniParameterString::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Toggle: + HoudiniParameter = UHoudiniParameterToggle::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Invalid: + // TODO handle invalid params + HoudiniParameter = UHoudiniParameter::Create(Outer, ParmName); + break; + } + + return HoudiniParameter; +} + +bool +FHoudiniParameterTranslator::UpdateParameterFromInfo( + UHoudiniParameter * HoudiniParameter, const HAPI_NodeId& InNodeId, const HAPI_ParmInfo& ParmInfo, + const bool& bFullUpdate, const bool& bUpdateValue) +{ + if (!HoudiniParameter || HoudiniParameter->IsPendingKill()) + return false; + + // Copy values from the ParmInfos + HoudiniParameter->SetNodeId(InNodeId); + HoudiniParameter->SetParmId(ParmInfo.id); + HoudiniParameter->SetParentParmId(ParmInfo.parentId); + + HoudiniParameter->SetChildIndex(ParmInfo.childIndex); + HoudiniParameter->SetTagCount(ParmInfo.tagCount); + HoudiniParameter->SetTupleSize(ParmInfo.size); + + HoudiniParameter->SetVisible(!ParmInfo.invisible); + HoudiniParameter->SetDisabled(ParmInfo.disabled); + HoudiniParameter->SetSpare(ParmInfo.spare); + HoudiniParameter->SetJoinNext(ParmInfo.joinNext); + + HoudiniParameter->SetTagCount(ParmInfo.tagCount); + HoudiniParameter->SetIsChildOfMultiParm(ParmInfo.isChildOfMultiParm); + + UHoudiniParameterMultiParm* MultiParm = Cast(HoudiniParameter); + if(MultiParm) + MultiParm->InstanceStartOffset = ParmInfo.instanceStartOffset; + + + + // Get the parameter type + EHoudiniParameterType ParmType = HoudiniParameter->GetParameterType(); + + // We need to set string values from the parmInfo + if (bFullUpdate) + { + FString Name; + { + // Name + FHoudiniEngineString HoudiniEngineStringName(ParmInfo.nameSH); + if (HoudiniEngineStringName.ToFString(Name)) + HoudiniParameter->SetParameterName(Name); + } + + { + // Label + FString Label; + FHoudiniEngineString HoudiniEngineStringLabel(ParmInfo.labelSH); + if (HoudiniEngineStringLabel.ToFString(Label)) + HoudiniParameter->SetParameterLabel(Label); + } + + { + // Help + FString Help; + FHoudiniEngineString HoudiniEngineStringHelp(ParmInfo.helpSH); + if (HoudiniEngineStringHelp.ToFString(Help)) + HoudiniParameter->SetParameterHelp(Help); + } + + if (ParmType == EHoudiniParameterType::String + || ParmType == EHoudiniParameterType::Int + || ParmType == EHoudiniParameterType::Float + || ParmType == EHoudiniParameterType::Toggle + || ParmType == EHoudiniParameterType::Color) + { + // See if the parm has an expression + int32 TupleIdx = ParmInfo.intValuesIndex; + bool bHasExpression = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ParmHasExpression( + FHoudiniEngine::Get().GetSession(), InNodeId, + TCHAR_TO_UTF8(*Name), TupleIdx, &bHasExpression)) + { + // ? + } + + FString ParmExprString = TEXT(""); + if (bHasExpression) + { + // Try to get the expression's value + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmExpression( + FHoudiniEngine::Get().GetSession(), InNodeId, + TCHAR_TO_UTF8(*Name), TupleIdx, &StringHandle)) + { + FHoudiniEngineString HoudiniEngineString(StringHandle); + HoudiniEngineString.ToFString(ParmExprString); + } + + // Check if we actually have an expression + // String parameters return true even if they do not have one + bHasExpression = ParmExprString.Len() > 0; + + } + + HoudiniParameter->SetHasExpression(bHasExpression); + HoudiniParameter->SetExpression(ParmExprString); + } + else + { + HoudiniParameter->SetHasExpression(false); + HoudiniParameter->SetExpression(FString()); + } + + // Get parameter tags. + int32 TagCount = HoudiniParameter->GetTagCount(); + for (int32 Idx = 0; Idx < TagCount; ++Idx) + { + HAPI_StringHandle TagNameSH; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagName( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmInfo.id, Idx, &TagNameSH)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); + continue; + } + + FString NameString = TEXT(""); + FHoudiniEngineString::ToFString(TagNameSH, NameString); + if (NameString.IsEmpty()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); + continue; + } + + HAPI_StringHandle TagValueSH; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmInfo.id, TCHAR_TO_ANSI(*NameString), &TagValueSH)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag value: parmId: %d, tag: %s"), ParmInfo.id, *NameString); + } + + FString ValueString = TEXT(""); + FHoudiniEngineString::ToFString(TagValueSH, ValueString); + + HoudiniParameter->GetTags().Add(NameString, ValueString); + } + } + + // + // Update properties specific to parameter classes + switch (ParmType) + { + case EHoudiniParameterType::Button: + { + UHoudiniParameterButton* HoudiniParameterButton = Cast(HoudiniParameter); + if (HoudiniParameterButton && !HoudiniParameterButton->IsPendingKill()) + { + HoudiniParameterButton->SetValueIndex(ParmInfo.intValuesIndex); + } + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + UHoudiniParameterButtonStrip* HoudiniParameterButtonStrip = Cast(HoudiniParameter); + if (HoudiniParameterButtonStrip && !HoudiniParameterButtonStrip->IsPendingKill()) + { + HoudiniParameterButtonStrip->SetValueIndex(ParmInfo.intValuesIndex); + HoudiniParameterButtonStrip->Count = ParmInfo.choiceCount; + } + + if (bFullUpdate) + { + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + ParmChoices.SetNum(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + + HoudiniParameterButtonStrip->InitializeLabels(ParmInfo.choiceCount); + + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ButtonLabel = HoudiniParameterButtonStrip->GetStringLabelAt(ChoiceIdx); + if (ButtonLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ButtonLabel)) + return false; + } + } + + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterButtonStrip->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.choiceCount) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + } + break; + + case EHoudiniParameterType::Color: + { + UHoudiniParameterColor* HoudiniParameterColor = Cast(HoudiniParameter); + if (HoudiniParameterColor && !HoudiniParameterColor->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterColor->SetValueIndex(ParmInfo.floatValuesIndex); + + // Update the Parameter value if we want to + if (bUpdateValue) + { + // Get the actual value for this property. + FLinearColor Color = FLinearColor::White; + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + (float *)&Color.R, ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + + HoudiniParameterColor->SetColorValue(Color); + } + + if (bFullUpdate) + { + // Set the default value at parameter created. + HoudiniParameterColor->SetDefaultValue(); + } + } + } + break; + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* HoudiniParameterRampColor = Cast(HoudiniParameter); + if (HoudiniParameterRampColor && !HoudiniParameterRampColor->IsPendingKill()) + { + HoudiniParameterRampColor->SetInstanceCount(ParmInfo.instanceCount); + HoudiniParameterRampColor->MultiParmInstanceLength = ParmInfo.instanceLength; + } + } + break; + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* HoudiniParameterRampFloat = Cast(HoudiniParameter); + if (HoudiniParameterRampFloat && !HoudiniParameterRampFloat->IsPendingKill()) + { + HoudiniParameterRampFloat->SetInstanceCount(ParmInfo.instanceCount); + HoudiniParameterRampFloat->MultiParmInstanceLength = ParmInfo.instanceLength; + } + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + UHoudiniParameterFile* HoudiniParameterFile = Cast(HoudiniParameter); + if (HoudiniParameterFile && !HoudiniParameterFile->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFile->SetValueIndex(ParmInfo.stringValuesIndex); + + // Update the file filter and read only tag only for full updates + if (bFullUpdate) + { + // Check if we are read-only + bool bIsReadOnly = false; + FString FileChooserTag; + if (FHoudiniParameterTranslator::HapiGetParameterTagValue(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_FILE_READONLY, FileChooserTag)) + { + if (FileChooserTag.Equals(TEXT("read"), ESearchCase::IgnoreCase)) + bIsReadOnly = true; + } + HoudiniParameterFile->SetReadOnly(bIsReadOnly); + + // Update the file type using the typeInfo string. + if (ParmInfo.typeInfoSH > 0) + { + FString Filters; + FHoudiniEngineString HoudiniEngineString(ParmInfo.typeInfoSH); + if (HoudiniEngineString.ToFString(Filters)) + { + if (!Filters.IsEmpty()) + HoudiniParameterFile->SetFileFilters(Filters); + } + } + } + + if (bUpdateValue) + { + // Get the actual values for this property. + TArray< HAPI_StringHandle > StringHandles; + StringHandles.SetNumZeroed(ParmInfo.size); + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), InNodeId, false, + &StringHandles[0], ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + + // Convert HAPI string handles to Unreal strings. + HoudiniParameterFile->SetNumberOfValues(ParmInfo.size); + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + + // Update the parameter value + HoudiniParameterFile->SetValueAt(ValueString, Idx); + } + } + + if (bFullUpdate) + { + HoudiniParameterFile->SetDefaultValues(); + } + } + } + break; + + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* HoudiniParameterFloat = Cast(HoudiniParameter); + if (HoudiniParameterFloat && !HoudiniParameterFloat->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFloat->SetValueIndex(ParmInfo.floatValuesIndex); + + if (bUpdateValue) + { + // Update the parameter's value + HoudiniParameterFloat->SetNumberOfValues(ParmInfo.size); + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterFloat->GetValuesPtr(), + ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + + if (bFullUpdate) + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_FLOAT_LOG) + { + HoudiniParameterFloat->SetIsLogarithmic(true); + } + + // set the default float values. + HoudiniParameterFloat->SetDefaultValues(); + + // Only update Unit, no swap, and Min/Max values when doing a full update + + // Get the parameter's unit from the "unit" tag + FString ParamUnit; + FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); + HoudiniParameterFloat->SetUnit(ParamUnit); + + // Get the parameter's no swap tag (hengine_noswap) + HoudiniParameterFloat->SetNoSwap(HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_NOSWAP)); + + // Set the min and max for this parameter + if (ParmInfo.hasMin) + { + HoudiniParameterFloat->SetHasMin(true); + HoudiniParameterFloat->SetMin(ParmInfo.min); + } + else + { + HoudiniParameterFloat->SetHasMin(false); + HoudiniParameterFloat->SetMin(TNumericLimits::Lowest()); + } + + if (ParmInfo.hasMax) + { + HoudiniParameterFloat->SetHasMax(true); + HoudiniParameterFloat->SetMax(ParmInfo.max); + } + else + { + HoudiniParameterFloat->SetHasMax(false); + HoudiniParameterFloat->SetMax(TNumericLimits::Max()); + } + + // Set min and max for UI for this property. + bool bUsesDefaultMin = false; + if (ParmInfo.hasUIMin) + { + HoudiniParameterFloat->SetHasUIMin(true); + HoudiniParameterFloat->SetUIMin(ParmInfo.UIMin); + } + else if (ParmInfo.hasMin) + { + // If it is not set, use supplied min. + HoudiniParameterFloat->SetUIMin(ParmInfo.min); + } + else + { + // Min value Houdini uses by default. + HoudiniParameterFloat->SetUIMin(HAPI_UNREAL_PARAM_FLOAT_UI_MIN); + bUsesDefaultMin = true; + } + + bool bUsesDefaultMax = false; + if (ParmInfo.hasUIMax) + { + HoudiniParameterFloat->SetHasUIMax(true); + HoudiniParameterFloat->SetUIMax(ParmInfo.UIMax); + } + else if (ParmInfo.hasMax) + { + // If it is not set, use supplied max. + HoudiniParameterFloat->SetUIMax(ParmInfo.max); + } + else + { + // Max value Houdini uses by default. + HoudiniParameterFloat->SetUIMax(HAPI_UNREAL_PARAM_FLOAT_UI_MAX); + bUsesDefaultMax = true; + } + + if (bUsesDefaultMin || bUsesDefaultMax) + { + // If we are using defaults, we can detect some most common parameter names and alter defaults. + FString LocalParameterName = HoudiniParameterFloat->GetParameterName(); + FHoudiniEngineString HoudiniEngineString(ParmInfo.nameSH); + HoudiniEngineString.ToFString(LocalParameterName); + + static const FString ParameterNameTranslate(TEXT(HAPI_UNREAL_PARAM_TRANSLATE)); + static const FString ParameterNameRotate(TEXT(HAPI_UNREAL_PARAM_ROTATE)); + static const FString ParameterNameScale(TEXT(HAPI_UNREAL_PARAM_SCALE)); + static const FString ParameterNamePivot(TEXT(HAPI_UNREAL_PARAM_PIVOT)); + + if (!LocalParameterName.IsEmpty()) + { + if (LocalParameterName.Equals(ParameterNameTranslate) + || LocalParameterName.Equals(ParameterNameScale) + || LocalParameterName.Equals(ParameterNamePivot)) + { + if (bUsesDefaultMin) + { + HoudiniParameterFloat->SetUIMin(-1.0f); + } + if (bUsesDefaultMax) + { + HoudiniParameterFloat->SetUIMax(1.0f); + } + } + else if (LocalParameterName.Equals(ParameterNameRotate)) + { + if (bUsesDefaultMin) + { + HoudiniParameterFloat->SetUIMin(0.0f); + } + if (bUsesDefaultMax) + { + HoudiniParameterFloat->SetUIMax(360.0f); + } + } + } + } + } + } + } + break; + + case EHoudiniParameterType::Folder: + { + UHoudiniParameterFolder* HoudiniParameterFolder = Cast(HoudiniParameter); + if (HoudiniParameterFolder && !HoudiniParameterFolder->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFolder->SetValueIndex(ParmInfo.intValuesIndex); + HoudiniParameterFolder->SetFolderType(GetFolderTypeFromParamInfo(&ParmInfo)); + } + } + break; + + case EHoudiniParameterType::FolderList: + { + UHoudiniParameterFolderList* HoudiniParameterFolderList = Cast(HoudiniParameter); + if (HoudiniParameterFolderList && !HoudiniParameterFolderList->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFolderList->SetValueIndex(ParmInfo.intValuesIndex); + } + } + break; + + case EHoudiniParameterType::Input: + { + // Inputs parameters are just stored, and handled separately by UHoudiniInputs + UHoudiniParameterOperatorPath* HoudiniParameterOperatorPath = Cast(HoudiniParameter); + if (HoudiniParameterOperatorPath && !HoudiniParameterOperatorPath->IsPendingKill()) + { + /* + // DO NOT CREATE A DUPLICATE INPUT HERE! + // Inputs are created by the input translator, and will be tied to this parameter there + UHoudiniInput * NewInput = NewObject< UHoudiniInput >( + HoudiniParameterOperatorPath, + UHoudiniInput::StaticClass()); + + UHoudiniAssetComponent *ParentHAC = Cast(HoudiniParameterOperatorPath->GetOuter()); + + if (!ParentHAC) + return false; + + if (!NewInput || NewInput->IsPendingKill()) + return false; + + // Set the nodeId + NewInput->SetAssetNodeId(ParentHAC->GetAssetId()); + NewInput->SetInputType(EHoudiniInputType::Geometry); + HoudiniParameterOperatorPath->HoudiniInputs.Add(NewInput); + */ + // Set the valueIndex + HoudiniParameterOperatorPath->SetValueIndex(ParmInfo.stringValuesIndex); + } + } + break; + + case EHoudiniParameterType::Int: + { + UHoudiniParameterInt* HoudiniParameterInt = Cast(HoudiniParameter); + if (HoudiniParameterInt && !HoudiniParameterInt->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterInt->SetValueIndex(ParmInfo.intValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + HoudiniParameterInt->SetNumberOfValues(ParmInfo.size); + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterInt->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + + if (bFullUpdate) + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_INT_LOG) + { + HoudiniParameterInt->SetIsLogarithmic(true); + } + + // Set the default int values at created + HoudiniParameterInt->SetDefaultValues(); + // Only update unit and Min/Max values for a full update + + // Get the parameter's unit from the "unit" tag + FString ParamUnit; + FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); + HoudiniParameterInt->SetUnit(ParamUnit); + + // Set the min and max for this parameter + if (ParmInfo.hasMin) + { + HoudiniParameterInt->SetHasMin(true); + HoudiniParameterInt->SetMin((int32)ParmInfo.min); + } + else + { + HoudiniParameterInt->SetHasMin(false); + HoudiniParameterInt->SetMin(TNumericLimits::Lowest()); + } + + if (ParmInfo.hasMax) + { + HoudiniParameterInt->SetHasMax(true); + HoudiniParameterInt->SetMax((int32)ParmInfo.max); + } + else + { + HoudiniParameterInt->SetHasMax(false); + HoudiniParameterInt->SetMax(TNumericLimits::Max()); + } + + // Set min and max for UI for this property. + if (ParmInfo.hasUIMin) + { + HoudiniParameterInt->SetHasUIMin(true); + HoudiniParameterInt->SetUIMin((int32)ParmInfo.UIMin); + } + else if (ParmInfo.hasMin) + { + // If it is not set, use supplied min. + HoudiniParameterInt->SetUIMin((int32)ParmInfo.min); + } + else + { + // Min value Houdini uses by default. + HoudiniParameterInt->SetUIMin(HAPI_UNREAL_PARAM_INT_UI_MIN); + } + + if (ParmInfo.hasUIMax) + { + HoudiniParameterInt->SetHasUIMax(true); + HoudiniParameterInt->SetUIMax((int32)ParmInfo.UIMax); + } + else if (ParmInfo.hasMax) + { + // If it is not set, use supplied max. + HoudiniParameterInt->SetUIMax((int32)ParmInfo.max); + } + else + { + // Max value Houdini uses by default. + HoudiniParameterInt->SetUIMax(HAPI_UNREAL_PARAM_INT_UI_MAX); + } + } + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* HoudiniParameterIntChoice = Cast(HoudiniParameter); + if (HoudiniParameterIntChoice && !HoudiniParameterIntChoice->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterIntChoice->SetValueIndex(ParmInfo.intValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + int32 CurrentIntValue = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &CurrentIntValue, + ParmInfo.intValuesIndex, ParmInfo.size), false); + + // Check the value is valid + if (CurrentIntValue >= ParmInfo.choiceCount) + { + HOUDINI_LOG_WARNING(TEXT("parm '%s' has an invalid value %d, menu tokens are not supported for choice menus"), + *HoudiniParameterIntChoice->GetParameterName(), CurrentIntValue); + CurrentIntValue = 0; + } + + HoudiniParameterIntChoice->SetIntValue(CurrentIntValue); + } + + // Get the choice descriptors + if (bFullUpdate) + { + // Set the default value at created + HoudiniParameterIntChoice->SetDefaultIntValue(); + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + + // Set the array sizes + HoudiniParameterIntChoice->SetNumChoices(ParmInfo.choiceCount); + + bool bMatchedSelectionLabel = false; + int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValue(); + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ChoiceLabel = HoudiniParameterIntChoice->GetStringChoiceLabelAt(ChoiceIdx); + if (ChoiceLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ChoiceLabel)) + return false; + //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); + } + + // Match our string value to the corresponding selection label. + if (ChoiceIdx == CurrentIntValue) + { + HoudiniParameterIntChoice->SetStringValue(*ChoiceLabel); + } + } + } + else if (bUpdateValue) + { + // We still need to match the string value to the label + HoudiniParameterIntChoice->UpdateStringValueFromInt(); + } + } + } + break; + + case EHoudiniParameterType::StringChoice: + { + UHoudiniParameterChoice* HoudiniParameterStringChoice = Cast(HoudiniParameter); + if (HoudiniParameterStringChoice && !HoudiniParameterStringChoice->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterStringChoice->SetValueIndex(ParmInfo.stringValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandle, + ParmInfo.stringValuesIndex, ParmInfo.size), false); + + // Get the string value + FString StringValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + HoudiniEngineString.ToFString(StringValue); + + HoudiniParameterStringChoice->SetStringValue(StringValue); + } + + // Get the choice descriptors + if (bFullUpdate) + { + // Set default value at created. + HoudiniParameterStringChoice->SetDefaultStringValue(); + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + + // Set the array sizes + HoudiniParameterStringChoice->SetNumChoices(ParmInfo.choiceCount); + + bool bMatchedSelectionLabel = false; + FString CurrentStringValue = HoudiniParameterStringChoice->GetStringValue(); + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ChoiceValue = HoudiniParameterStringChoice->GetStringChoiceValueAt(ChoiceIdx); + if (ChoiceValue) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].valueSH); + if (!HoudiniEngineString.ToFString(*ChoiceValue)) + return false; + //StringChoiceValues.Add(TSharedPtr< FString >(ChoiceValue)); + } + + FString * ChoiceLabel = HoudiniParameterStringChoice->GetStringChoiceLabelAt(ChoiceIdx); + if (ChoiceLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ChoiceLabel)) + return false; + //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); + } + + // If this is a string choice list, we need to match name with corresponding selection label. + if (!bMatchedSelectionLabel && ChoiceValue->Equals(CurrentStringValue)) + { + bMatchedSelectionLabel = true; + HoudiniParameterStringChoice->SetIntValue(ChoiceIdx); + } + } + } + else if (bUpdateValue) + { + // We still need to match the string value to the label + HoudiniParameterStringChoice->UpdateIntValueFromString(); + } + } + } + break; + + case EHoudiniParameterType::Label: + { + UHoudiniParameterLabel* HoudiniParameterLabel = Cast(HoudiniParameter); + if (HoudiniParameterLabel && !HoudiniParameterLabel->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_LABEL) + return false; + + // Set the valueIndex + HoudiniParameterLabel->SetValueIndex(ParmInfo.stringValuesIndex); + + // Get the actual value for this property. + TArray StringHandles; + StringHandles.SetNumZeroed(ParmInfo.size); + FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandles[0], + ParmInfo.stringValuesIndex, ParmInfo.size); + + HoudiniParameterLabel->EmptyLabelString(); + + // Convert HAPI string handles to Unreal strings. + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + HoudiniParameterLabel->AddLabelString(ValueString); + } + } + } + break; + + case EHoudiniParameterType::MultiParm: + { + UHoudiniParameterMultiParm* HoudiniParameterMulti = Cast(HoudiniParameter); + if (HoudiniParameterMulti && !HoudiniParameterMulti->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST) + return false; + + // Set the valueIndex + HoudiniParameterMulti->SetValueIndex(ParmInfo.intValuesIndex); + + // Set the multiparm value + int32 MultiParmValue = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &MultiParmValue, ParmInfo.intValuesIndex, 1), false); + + HoudiniParameterMulti->SetValue(MultiParmValue); + HoudiniParameterMulti->MultiParmInstanceCount = ParmInfo.instanceCount; + HoudiniParameterMulti->MultiParmInstanceLength = ParmInfo.instanceLength; + + } + + if (bFullUpdate) + { + HoudiniParameterMulti->SetDefaultInstanceCount(ParmInfo.instanceCount); + } + } + break; + + case EHoudiniParameterType::Separator: + { + UHoudiniParameterSeparator* HoudiniParameterSeparator = Cast(HoudiniParameter); + if (HoudiniParameterSeparator && !HoudiniParameterSeparator->IsPendingKill()) + { + // We can only handle separator type. + if (ParmInfo.type != HAPI_PARMTYPE_SEPARATOR) + return false; + + // Set the valueIndex + HoudiniParameterSeparator->SetValueIndex(ParmInfo.stringValuesIndex); + } + } + break; + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + { + UHoudiniParameterString* HoudiniParameterString = Cast(HoudiniParameter); + if (HoudiniParameterString && !HoudiniParameterString->IsPendingKill()) + { + // We can only handle string type. + if (ParmInfo.type != HAPI_PARMTYPE_STRING && ParmInfo.type != HAPI_PARMTYPE_NODE) + return false; + + // Set the valueIndex + HoudiniParameterString->SetValueIndex(ParmInfo.stringValuesIndex); + + // Stop if we don't want to update the value + if (bUpdateValue) + { + // Get the actual value for this property. + TArray< HAPI_StringHandle > StringHandles; + StringHandles.SetNumZeroed(ParmInfo.size); + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandles[0], + ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + + // Convert HAPI string handles to Unreal strings. + HoudiniParameterString->SetNumberOfValues(ParmInfo.size); + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + HoudiniParameterString->SetValueAt(ValueString, Idx); + } + } + + if (bFullUpdate) + { + // Set default string values on created + HoudiniParameterString->SetDefaultValues(); + // Check if the parameter has the "asset_ref" tag + HoudiniParameterString->SetIsAssetRef( + FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_ASSET_REF)); + } + } + } + break; + + case EHoudiniParameterType::Toggle: + { + UHoudiniParameterToggle* HoudiniParameterToggle = Cast(HoudiniParameter); + if (HoudiniParameterToggle && !HoudiniParameterToggle->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_TOGGLE) + return false; + + // Set the valueIndex + HoudiniParameterToggle->SetValueIndex(ParmInfo.intValuesIndex); + + // Stop if we don't want to update the value + if (bUpdateValue) + { + // Get the actual values for this property. + HoudiniParameterToggle->SetNumberOfValues(ParmInfo.size); + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterToggle->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + } + + if (bFullUpdate) + { + HoudiniParameterToggle->SetDefaultValues(); + } + } + break; + + case EHoudiniParameterType::Invalid: + { + // TODO + } + break; + } + + return true; +} + +bool +FHoudiniParameterTranslator::HapiGetParameterTagValue(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag, FString& TagValue) +{ + // Default + TagValue = FString(); + + // Does the parameter has the tag? + bool HasTag = false; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &HasTag), false); + + if (!HasTag) + return false; + + // Get the tag string value + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &StringHandle), false); + + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(TagValue)) + { + return true; + } + + return false; +} + + +bool +FHoudiniParameterTranslator::HapiGetParameterUnit(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString) +{ + // + OutUnitString = TEXT(""); + + // We're looking for the parameter unit tag + //FString UnitTag = "units"; + + // Get the actual string value. + FString UnitString = TEXT(""); + if (!FHoudiniParameterTranslator::HapiGetParameterTagValue(NodeId, ParmId, "units", UnitString)) + return false; + + // We need to do some replacement in the string here in order to be able to get the + // proper unit type when calling FUnitConversion::UnitFromString(...) after. + + // Per second and per hour are the only "per" unit that unreal recognize + UnitString.ReplaceInline(TEXT("s-1"), TEXT("/s")); + UnitString.ReplaceInline(TEXT("h-1"), TEXT("/h")); + + // Houdini likes to add '1' on all the unit, so we'll remove all of them + // except the '-1' that still needs to remain. + UnitString.ReplaceInline(TEXT("-1"), TEXT("--")); + UnitString.ReplaceInline(TEXT("1"), TEXT("")); + UnitString.ReplaceInline(TEXT("--"), TEXT("-1")); + + OutUnitString = UnitString; + + return true; +} + +bool +FHoudiniParameterTranslator::HapiGetParameterHasTag(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag) +{ + // Does the parameter has the tag we're looking for? + bool HasTag = false; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &HasTag), false); + + return HasTag; +} + + +bool +FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * HAC ) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniParameterTranslator::UploadChangedParameters); + + if (!HAC || HAC->IsPendingKill()) + return false; + + TMap RampsToRevert; + + for (int32 ParmIdx = 0; ParmIdx < HAC->GetNumParameters(); ParmIdx++) + { + UHoudiniParameter*& CurrentParm = HAC->Parameters[ParmIdx]; + if (!CurrentParm || CurrentParm->IsPendingKill() || !CurrentParm->HasChanged()) + continue; + + bool bSuccess = false; + + if (CurrentParm->IsPendingRevertToDefault()) + { + bSuccess = RevertParameterToDefault(CurrentParm); + + if (CurrentParm->GetParameterType() == EHoudiniParameterType::FloatRamp || + CurrentParm->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + RampsToRevert.Add(CurrentParm->GetParameterName(), CurrentParm); + } + } + else + { + bSuccess = UploadParameterValue(CurrentParm); + } + + + if (bSuccess) + { + CurrentParm->MarkChanged(false); + //CurrentParm->SetNeedsToTriggerUpdate(false); + } + else + { + // Keep this param marked as changed but prevent it from generating updates + CurrentParm->SetNeedsToTriggerUpdate(false); + } + } + + FHoudiniParameterTranslator::RevertRampParameters(RampsToRevert, HAC->GetAssetId()); + + return true; +} + +bool +FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return false; + + switch (InParam->GetParameterType()) + { + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* FloatParam = Cast(InParam); + if (!FloatParam || FloatParam->IsPendingKill()) + { + return false; + } + + float* DataPtr = FloatParam->GetValuesPtr(); + if (!DataPtr) + { + return false; + } + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + FloatParam->GetNodeId(), DataPtr, FloatParam->GetValueIndex(), FloatParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::Int: + { + UHoudiniParameterInt* IntParam = Cast(InParam); + if (!IntParam || IntParam->IsPendingKill()) + { + return false; + } + + int32* DataPtr = IntParam->GetValuesPtr(); + if (!DataPtr) + { + return false; + } + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + IntParam->GetNodeId(), DataPtr, IntParam->GetValueIndex(), IntParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::String: + { + UHoudiniParameterString* StringParam = Cast(InParam); + if (!StringParam || StringParam->IsPendingKill()) + { + return false; + } + + int32 NumValues = StringParam->GetNumberOfValues(); + if (NumValues <= 0) + { + return false; + } + + for (int32 Idx = 0; Idx < NumValues; Idx++) + { + std::string ConvertedString = TCHAR_TO_UTF8(*(StringParam->GetValueAt(Idx))); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + StringParam->GetNodeId(), ConvertedString.c_str(), StringParam->GetParmId(), Idx), false); + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* ChoiceParam = Cast(InParam); + if (!ChoiceParam || ChoiceParam->IsPendingKill()) + return false; + + // Set the parameter's int value. + int32 IntValue = ChoiceParam->GetIntValue(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); + } + break; + case EHoudiniParameterType::StringChoice: + { + UHoudiniParameterChoice* ChoiceParam = Cast(InParam); + if (!ChoiceParam || ChoiceParam->IsPendingKill()) + { + return false; + } + + if (ChoiceParam->IsStringChoice()) + { + // Set the parameter's string value. + std::string ConvertedString = TCHAR_TO_UTF8(*(ChoiceParam->GetStringValue())); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), ConvertedString.c_str(), ChoiceParam->GetParmId(), 0), false); + } + else + { + // Set the parameter's int value. + int32 IntValue = ChoiceParam->GetIntValue(); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); + } + } + break; + + case EHoudiniParameterType::Color: + { + UHoudiniParameterColor* ColorParam = Cast(InParam); + if (!ColorParam || ColorParam->IsPendingKill()) + return false; + + bool bHasAlpha = ColorParam->GetTupleSize() == 4 ? true : false; + FLinearColor Color = ColorParam->GetColorValue(); + + // Set the color value + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + ColorParam->GetNodeId(), + (float*)(&Color.R), ColorParam->GetValueIndex(), bHasAlpha ? 4 : 3), false); + + } + break; + + case EHoudiniParameterType::Button: + { + UHoudiniParameterButton* ButtonParam = Cast(InParam); + if (!ButtonParam) + return false; + + TArray DataArray; + DataArray.Add(1); + + // Set the button parameter value to 1, (setting button param to any value will call the callback function.) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ButtonParam->GetNodeId(), + DataArray.GetData(), + ButtonParam->GetValueIndex(), 1), false); + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + UHoudiniParameterButtonStrip* ButtonStripParam = Cast(InParam); + if (!ButtonStripParam) + return false; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ButtonStripParam->GetNodeId(), + ButtonStripParam->Values.GetData(), + ButtonStripParam->GetValueIndex(), ButtonStripParam->Count), false); + } + break; + + case EHoudiniParameterType::Toggle: + { + UHoudiniParameterToggle* ToggleParam = Cast(InParam); + if (!ToggleParam) + return false; + + // Set the toggle parameter values. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ToggleParam->GetNodeId(), + ToggleParam->GetValuesPtr(), + ToggleParam->GetValueIndex(), ToggleParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + UHoudiniParameterFile* FileParam = Cast(InParam); + + if (!UploadDirectoryPath(FileParam)) + return false; + } + break; + + case EHoudiniParameterType::MultiParm: + { + if (!UploadMultiParmValues(InParam)) + return false; + } + + break; + + case EHoudiniParameterType::FloatRamp: + { + if (!UploadRampParameter(InParam)) + return false; + } + break; + + case EHoudiniParameterType::ColorRamp: + { + if (!UploadRampParameter(InParam)) + return false; + } + break; + + default: + { + // TODO: implement other parameter types! + return false; + } + break; + } + + // The parameter is no longer considered as changed + InParam->MarkChanged(false); + + return true; +} + +bool +FHoudiniParameterTranslator::RevertParameterToDefault(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return false; + + if (!InParam->IsPendingRevertToDefault()) + return false; + + TArray TupleToRevert; + InParam->GetTuplePendingRevertToDefault(TupleToRevert); + if (TupleToRevert.Num() <= 0) + return false; + + FString ParameterName = InParam->GetParameterName(); + + bool bReverted = true; + for (auto CurrentIdx : TupleToRevert ) + { + if (!TupleToRevert.IsValidIndex(CurrentIdx)) + { + // revert the whole parameter to its default value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefaults( + FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName))) + { + HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s to its default value."), *ParameterName); + bReverted = false; + } + } + else + { + // revert a tuple to its default value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefault( + FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName), CurrentIdx)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s - %d to its default value."), *ParameterName, CurrentIdx); + bReverted = false; + } + } + } + + if (!bReverted) + return false; + + // The parameter no longer needs to be reverted + InParam->MarkDefault(true); + + return true; +} + +EHoudiniFolderParameterType +FHoudiniParameterTranslator::GetFolderTypeFromParamInfo(const HAPI_ParmInfo* ParamInfo) +{ + EHoudiniFolderParameterType Type = EHoudiniFolderParameterType::Invalid; + + switch (ParamInfo->scriptType) + { + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPSIMPLE: + Type = EHoudiniFolderParameterType::Simple; + break; + + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPCOLLAPSIBLE: + Type = EHoudiniFolderParameterType::Collapsible; + break; + + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUP: + Type = EHoudiniFolderParameterType::Tabs; + break; + + // Treat Radio folders as tabs for now + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPRADIO: + Type = EHoudiniFolderParameterType::Radio; + break; + + default: + Type = EHoudiniFolderParameterType::Other; + break; + + } + + return Type; + +} + +bool +FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InParam, TArray &OldParams, const int32& InAssetId, const int32 CurrentIndex) +{ + + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + + if (!MultiParam || MultiParam->IsPendingKill()) + return false; + + UHoudiniParameterRampFloat* FloatRampParameter = nullptr; + UHoudiniParameterRampColor* ColorRampParameter = nullptr; + + if (MultiParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + FloatRampParameter = Cast(MultiParam); + + else if (MultiParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + ColorRampParameter = Cast(MultiParam); + + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); + + HAPI_NodeId NodeId = AssetInfo.nodeId; + + int32 Idx = 0; + int32 InstanceCount = -1; + HAPI_ParmId ParmId = -1; + TArray ParmInfos; + if (!GetMultiParmInstanceStartIdx(AssetInfo, MultiParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + + for (int n = 0; n < InstanceCount - MultiParam->GetInstanceCount(); ++n) + { + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset); + } + + for (int n = 0; n < MultiParam->GetInstanceCount() - InstanceCount; ++n) + { + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset); + } + + + // Sync nested multi-params recursively + for (int32 ParamIdx = CurrentIndex; ParamIdx < OldParams.Num(); ++ParamIdx) + { + UHoudiniParameter* NextParm = OldParams[ParamIdx]; + if (NextParm->GetParentParmId() == ParmId) + { + if (NextParm->GetParameterType() == EHoudiniParameterType::MultiParm) + { + SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, ParamIdx); + } + } + } + + + // The multiparm is a ramp, Get the param infos again, since the number of param instances is changed + if (!GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + // Step 3: Set values of the inserted points + if (FloatRampParameter) + { + for (auto & Point : FloatRampParameter->Points) + { + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); + + // 2: update float value at param Idx + 1 + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Value), ParmInfos[Idx + 1].floatValuesIndex, 1); + + // 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Point->Interpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + else if (ColorRampParameter) + { + for (auto& Point : ColorRampParameter->Points) + { + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); + + // 2: update color value at param Idx + 1 + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, (float*)(&Point->Value.R), ParmInfos[Idx + 1].floatValuesIndex, 3); + + // 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Point->Interpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + + + return true; +} + + +bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam) +{ + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + if (!MultiParam || MultiParam->IsPendingKill()) + return false; + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InParam->GetOuter()); + if (!HoudiniAssetComponent) + return false; + + int32 InsertIndexStart = -1; + UHoudiniParameterRampFloat* RampFloatParam = Cast(InParam); + UHoudiniParameterRampColor* RampColorParam = Cast(InParam); + + TArray *Events = nullptr; + if (RampFloatParam) + { + Events = &(RampFloatParam->ModificationEvents); + InsertIndexStart = RampFloatParam->GetInstanceCount(); + } + else if (RampColorParam) + { + Events = &(RampColorParam->ModificationEvents); + InsertIndexStart = RampColorParam->GetInstanceCount(); + } + else + return false; + + // Handle All Events + Events->Sort([](const UHoudiniParameterRampModificationEvent& A, const UHoudiniParameterRampModificationEvent& B) + { + return A.DeleteInstanceIndex > B.DeleteInstanceIndex; + }); + + + // Step 1: Handle all delete events first + for (auto& Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsDeleteEvent()) + continue; + + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Event->DeleteInstanceIndex + MultiParam->InstanceStartOffset); + + InsertIndexStart -= 1; + } + + int32 InsertIndex = InsertIndexStart; + + + // Step 2: Handle all insert events + for (auto& Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsInsertEvent()) + continue; + + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), InsertIndex + MultiParam->InstanceStartOffset); + + InsertIndex += 1; + } + + // Step 3: Set inserted parameter values (only if there are instances inserted) + if (InsertIndex > InsertIndexStart) + { + if (HoudiniAssetComponent) + { + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), HoudiniAssetComponent->AssetId, &AssetInfo), false); + + int32 Idx = 0; + int32 InstanceCount = -1; + HAPI_ParmId ParmId = -1; + TArray< HAPI_ParmInfo > ParmInfos; + + if (!FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), + Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + if (InstanceCount < 0) + return false; + + // Instance count doesn't match, + if (InsertIndex != InstanceCount) + return false; + + + // Starting index of parameters which just inserted + Idx += 3 * InsertIndexStart; + + + for (auto & Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsInsertEvent()) + continue; + + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &(Event->InsertPosition), ParmInfos[Idx].floatValuesIndex, 1); + + // step 2: update value at param Idx + 1 + if (Event->IsFloatRampEvent()) + { + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &(Event->InsertFloat), ParmInfos[Idx + 1].floatValuesIndex, 1); + } + else + { + // color value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, (float*)(&Event->InsertColor.R), ParmInfos[Idx + 1].floatValuesIndex, 3); + } + + // step 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Event->InsertInterpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + } + + // Step 4: clear all events + Events->Empty(); + + return true; +} + +bool FHoudiniParameterTranslator::UploadMultiParmValues(UHoudiniParameter* InParam) +{ + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + if (!MultiParam) + return false; + + TArray &LastModificationArray = MultiParam->MultiParmInstanceLastModifyArray; + + int32 Size = MultiParam->MultiParmInstanceLastModifyArray.Num(); + + for (int32 Index = 0; Index < Size; ++Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Inserted) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) + return false; + + } + } + + for (int32 Index = Size - 1; Index >= 0; --Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) + return false; + } + } + + // Remove all removal events. + for (int32 Index = Size - 1; Index >= 0; --Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) + LastModificationArray.RemoveAt(Index); + } + + // The last modification array is resized. + Size = LastModificationArray.Num(); + + // Reset the last modification array + for (int32 Itr =Size - 1; Itr >= 0; --Itr) + { + LastModificationArray[Itr] = EHoudiniMultiParmModificationType::None; + } + + MultiParam->MultiParmInstanceCount = Size; + + return true; +} + +bool +FHoudiniParameterTranslator::UploadDirectoryPath(UHoudiniParameterFile* InParam) +{ + if(!InParam) + return false; + + for (int32 Index = 0; Index < InParam->GetNumValues(); ++Index) + { + std::string ConvertedString = TCHAR_TO_UTF8(*(InParam->GetValueAt(Index))); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue(FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), ConvertedString.c_str(), InParam->GetParmId(), Index), false); + } + + return true; +} + +bool +FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, + int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos) +{ + // Reset outputs + OutStartIdx = 0; + OutInstanceCount = -1; + OutParmId = -1; + OutParmInfos.Empty(); + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &NodeInfo), false); + + OutParmInfos.SetNumUninitialized(NodeInfo.parmCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &OutParmInfos[0], 0, NodeInfo.parmCount), false); + + + while (OutStartIdx < OutParmInfos.Num()) + { + FString ParmNameBuffer; + FHoudiniEngineString(OutParmInfos[OutStartIdx].nameSH).ToFString(ParmNameBuffer); + + if (ParmNameBuffer == InParmName) + { + OutParmId = OutParmInfos[OutStartIdx].id; + OutInstanceCount = OutParmInfos[OutStartIdx].instanceCount; + break; + } + + OutStartIdx += 1; + } + + // Start index of the ramp children parameters + OutStartIdx += 1; + + return true; +} + +bool +FHoudiniParameterTranslator::RevertRampParameters(TMap & InRampParams, const int32 & AssetId) +{ + if (InRampParams.Num() <= 0) + return true; + + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); + + // Retrieve all the parameter infos. + TArray< HAPI_ParmInfo > ParmInfos; + ParmInfos.SetNumUninitialized(NodeInfo.parmCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); + + int32 ParamIdx = 0; + while (ParamIdx < ParmInfos.Num()) + { + const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; + FString ParmName; + FHoudiniEngineString(ParmInfo.nameSH).ToFString(ParmName); + + if (InRampParams.Contains(ParmName)) + { + if (!InRampParams[ParmName]) + { + ParamIdx += 1; + continue; + } + + if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat * FloatRamp = Cast(InRampParams[ParmName]); + if (!FloatRamp) + { + ParamIdx += 1; + continue; + } + + if (ParmInfo.instanceCount != FloatRamp->NumDefaultPoints) + { + ParamIdx += 1; + continue; + } + + for (int32 PtIdx = 0; PtIdx < FloatRamp->NumDefaultPoints; ++PtIdx) + { + const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; + const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; + const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultValues.GetData() + PtIdx, ValueParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); + + ParamIdx += 3; + } + } + + if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor * ColorRamp = Cast(InRampParams[ParmName]); + if (!ColorRamp) + { + ParamIdx += 1; + continue; + } + + if (ParmInfo.instanceCount != ColorRamp->NumDefaultPoints) + { + ParamIdx += 1; + continue; + } + + for (int32 PtIdx = 0; PtIdx < ColorRamp->NumDefaultPoints; ++PtIdx) + { + const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; + const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; + const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, ColorRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, (float*)(&ColorRamp->DefaultValues[PtIdx].R), ValueParmInfo.floatValuesIndex, 3); + + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, ColorRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); + + ParamIdx += 3; + } + } + } + + ParamIdx += 1; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h index 534421fc6..46ef25b34 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h @@ -1,148 +1,150 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -class UHoudiniAssetComponent; -class UHoudiniParameter; -class UHoudiniParameterFile; - -enum class EHoudiniFolderParameterType : uint8; -enum class EHoudiniParameterType : uint8; - -struct HOUDINIENGINE_API FHoudiniParameterTranslator -{ - // - static bool UpdateParameters(UHoudiniAssetComponent* HAC); - - static bool OnPreCookParameters(UHoudiniAssetComponent* HAC); - - // - static bool UpdateLoadedParameters(UHoudiniAssetComponent* HAC); - - // - static bool UploadChangedParameters(UHoudiniAssetComponent* HAC); - - // - static bool UploadParameterValue(UHoudiniParameter* InParam); - - // - static bool UploadMultiParmValues(UHoudiniParameter* InParam); - - // - static bool UploadRampParameter(UHoudiniParameter* InParam); - - // - static bool UploadDirectoryPath(UHoudiniParameterFile* InParam); - - // - static bool RevertParameterToDefault(UHoudiniParameter* InParam); - - // - static bool SyncMultiParmValuesAtLoad(UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const int32 Idx); - - // - static bool GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, - int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos); - - /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. - @AssetId: Id of the digital asset - @PrimaryObject: Object to use for transactions and as Outer for new top-level parameters - @CurrentParameters: pre: current & post: invalid parameters - @NewParameters: new params added to this - - On Return: CurrentParameters are the old parameters that are no longer valid, - NewParameters are new and re-used parameters. - */ - static bool BuildAllParameters( - const HAPI_NodeId& AssetId, - class UObject* OuterObject, - TArray& CurrentParameters, - TArray& NewParameters, - const bool& bUpdateValues, - const bool& InForceFullUpdate); - - // Parameter creation - static UHoudiniParameter * CreateTypedParameter( - class UObject * Outer, - const EHoudiniParameterType& ParmType, - const FString& ParmName ); - - // Parameter update - // bFullUpdate should be set to false after a minor update (change/recook) of the parameter - // and set to true when creating a new parameter - // bUpdateValue should be set to false when updating loaded parameters - // as the internal parameter's value from HAPI - static bool UpdateParameterFromInfo( - UHoudiniParameter * HoudiniParameter, - const HAPI_NodeId& InNodeId, - const HAPI_ParmInfo& ParmInfo, - const bool& bFullUpdate = true, - const bool& bUpdateValue = true); - - static UClass* GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo); - - static void GetParmTypeFromParmInfo( - const HAPI_ParmInfo& ParmInfo, - EHoudiniParameterType& ParmType); - - static bool CheckParameterTypeAndClassMatch( - UHoudiniParameter* Parameter, - const EHoudiniParameterType& ParmType); - - /* - static bool CheckParameterClassAndInfoMatch( - UHoudiniParameter* Parameter, - const HAPI_ParmInfo& ParmInfo ); - */ - - // HAPI: Get a parameter's tag value. - static bool HapiGetParameterTagValue( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - const FString& Tag, - FString& TagValue); - - // HAPI: Get a parameter's unit. - static bool HapiGetParameterUnit( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - FString& OutUnitString ); - - // HAPI: Indicates if a parameter has a given tag - static bool HapiGetParameterHasTag( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - const FString& Tag); - - // Get folder parameter type from HAPI_ParmInfo struct - static EHoudiniFolderParameterType GetFolderTypeFromParamInfo( - const HAPI_ParmInfo* ParamInfo); - - static bool RevertRampParameters(TMap & InRampParams, const int32 & AssetId); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +class UHoudiniAssetComponent; +class UHoudiniParameter; +class UHoudiniParameterFile; + +enum class EHoudiniFolderParameterType : uint8; +enum class EHoudiniParameterType : uint8; + +struct HOUDINIENGINE_API FHoudiniParameterTranslator +{ + // + static bool UpdateParameters(UHoudiniAssetComponent* HAC); + + static bool OnPreCookParameters(UHoudiniAssetComponent* HAC); + + // + static bool UpdateLoadedParameters(UHoudiniAssetComponent* HAC); + + // + static bool UploadChangedParameters(UHoudiniAssetComponent* HAC); + + // + static bool UploadParameterValue(UHoudiniParameter* InParam); + + // + static bool UploadMultiParmValues(UHoudiniParameter* InParam); + + // + static bool UploadRampParameter(UHoudiniParameter* InParam); + + // + static bool UploadDirectoryPath(UHoudiniParameterFile* InParam); + + // + static bool RevertParameterToDefault(UHoudiniParameter* InParam); + + // + static bool SyncMultiParmValuesAtLoad(UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const int32 Idx); + + // + static bool GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, + int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos); + + /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. + @AssetId: Id of the digital asset + @PrimaryObject: Object to use for transactions and as Outer for new top-level parameters + @CurrentParameters: pre: current & post: invalid parameters + @NewParameters: new params added to this + + On Return: CurrentParameters are the old parameters that are no longer valid, + NewParameters are new and re-used parameters. + */ + static bool BuildAllParameters( + const HAPI_NodeId& AssetId, + class UObject* OuterObject, + TArray& CurrentParameters, + TArray& NewParameters, + const bool& bUpdateValues, + const bool& InForceFullUpdate); + + // Parameter creation + static UHoudiniParameter * CreateTypedParameter( + class UObject * Outer, + const EHoudiniParameterType& ParmType, + const FString& ParmName ); + + // Parameter update + // bFullUpdate should be set to false after a minor update (change/recook) of the parameter + // and set to true when creating a new parameter + // bUpdateValue should be set to false when updating loaded parameters + // as the internal parameter's value from HAPI + static bool UpdateParameterFromInfo( + UHoudiniParameter * HoudiniParameter, + const HAPI_NodeId& InNodeId, + const HAPI_ParmInfo& ParmInfo, + const bool& bFullUpdate = true, + const bool& bUpdateValue = true); + + static UClass* GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo); + + static void GetParmTypeFromParmInfo( + const HAPI_ParmInfo& ParmInfo, + EHoudiniParameterType& ParmType); + + static bool CheckParameterTypeAndClassMatch( + UHoudiniParameter* Parameter, + const EHoudiniParameterType& ParmType); + + /* + static bool CheckParameterClassAndInfoMatch( + UHoudiniParameter* Parameter, + const HAPI_ParmInfo& ParmInfo ); + */ + + // HAPI: Get a parameter's tag value. + static bool HapiGetParameterTagValue( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + const FString& Tag, + FString& TagValue); + + // HAPI: Get a parameter's unit. + static bool HapiGetParameterUnit( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + FString& OutUnitString ); + + // HAPI: Indicates if a parameter has a given tag + static bool HapiGetParameterHasTag( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + const FString& Tag); + + // Get folder parameter type from HAPI_ParmInfo struct + static EHoudiniFolderParameterType GetFolderTypeFromParamInfo( + const HAPI_ParmInfo* ParamInfo); + + static bool RevertRampParameters(TMap & InRampParams, const int32 & AssetId); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp index 9a70ac1a2..40b10d0f5 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp @@ -1,1610 +1,1610 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" - -#include "HoudiniGeoPartObject.h" -#include "Components/SplineComponent.h" - -#include "EditorViewportClient.h" -#include "Engine/Selection.h" - -#include "HoudiniEnginePrivatePCH.h" - -void -FHoudiniSplineTranslator::ExtractStringPositions(const FString& Positions, TArray& OutPositions) -{ - TArray< FString > PointStrings; - static const TCHAR * PositionSeparators[] = - { - TEXT(" "), - TEXT(","), - }; - - int32 NumCoords = Positions.ParseIntoArray(PointStrings, PositionSeparators, 2); - OutPositions.SetNum(NumCoords / 3); - for (int32 OutIndex = 0; OutIndex < OutPositions.Num(); OutIndex++) - { - const int32& CoordIndex = OutIndex * 3; - OutPositions[OutIndex].X = FCString::Atof(*(PointStrings[CoordIndex + 0])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutPositions[OutIndex].Y = FCString::Atof(*(PointStrings[CoordIndex + 2])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutPositions[OutIndex].Z = FCString::Atof(*(PointStrings[CoordIndex + 1])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } -} - -void -FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData) -{ - OutVectorData.SetNum(InRawData.Num() / 3); - - for (int32 OutIndex = 0; OutIndex < OutVectorData.Num(); OutIndex++) - { - const int32& InIndex = OutIndex * 3; - OutVectorData[OutIndex].X = InRawData[InIndex + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutVectorData[OutIndex].Y = InRawData[InIndex + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutVectorData[OutIndex].Z = InRawData[InIndex + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } -} - -void -FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts) -{ - OutVectorData.SetNum(CurveCounts.Num()); - - int32 TotalNumPoints = 0; - for (const int32 & NextCount : CurveCounts) - TotalNumPoints += NextCount; - - // Do not fill the output array, if the total number of points does not match - if (InRawData.Num() < TotalNumPoints * 3) - return; - - - int32 Itr = 0; - - for (int32 n = 0; n < CurveCounts.Num(); ++n) - { - TArray & NextVectorDataArray = OutVectorData[n]; - NextVectorDataArray.SetNumZeroed(CurveCounts[n]); - - for (int32 PtIdx = 0; PtIdx < CurveCounts[n]; ++PtIdx) - { - if (Itr + 2 >= InRawData.Num()) - return; - - NextVectorDataArray[PtIdx].X = InRawData[Itr] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - NextVectorDataArray[PtIdx].Y = InRawData[Itr + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - NextVectorDataArray[PtIdx].Z = InRawData[Itr + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - - Itr += 3; - } - } -} -void -FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC) -{ - for (UHoudiniInput * NextInput : HAC->Inputs) - UpdateHoudiniInputCurves(NextInput); -} - -void -FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniInput* Input) -{ - if (!Input || Input->GetInputType() != EHoudiniInputType::Curve) - return; - - TArray *InputObjectArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!InputObjectArray) - return; - - for (UHoudiniInputObject * NextInputObject : *InputObjectArray) - { - UHoudiniInputHoudiniSplineComponent * HoudiniSplineInput = Cast(NextInputObject); - if (!HoudiniSplineInput) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInput->GetCurveComponent(); - FHoudiniSplineTranslator::UpdateHoudiniCurve(HoudiniSplineComponent); - } -} - -bool -FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent) -{ - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return false; - - int32 CurveNode_id = HoudiniSplineComponent->GetNodeId(); - if (CurveNode_id < 0) - return false; - - bool Success = true; - FString CurvePointsString = FString(); - int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; - int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; - int32 CurveClosed = 0; - int32 CurveReversed = 0; - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsString( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); - - - HoudiniSplineComponent->SetCurveType((EHoudiniCurveType)CurveTypeValue); - HoudiniSplineComponent->SetCurveMethod((EHoudiniCurveMethod)CurveMethodValue); - HoudiniSplineComponent->SetClosedCurve(CurveClosed == 1); - HoudiniSplineComponent->SetReversed(CurveReversed == 1); - - // We need to get the NodeInfo to get the parent id - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurveNode_id, &NodeInfo), false); - - TArray< float > RefinedCurvePositions; - HAPI_AttributeInfo AttributeRefinedCurvePositions; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); - Success &= FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); - - // Process coords string and extract positions. - TArray CurvePoints; - FHoudiniSplineTranslator::ExtractStringPositions(CurvePointsString, CurvePoints); - - TArray CurveDisplayPoints; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurveDisplayPoints); - - // build curve points for editable curves. - if (HoudiniSplineComponent->CurvePoints.Num() < CurvePoints.Num()) - { - HoudiniSplineComponent->CurvePoints.Empty(); - for (FVector NextPos : CurvePoints) - { - FTransform NextTrans = FTransform::Identity; - NextTrans.SetLocation(NextPos); - HoudiniSplineComponent->CurvePoints.Add(NextTrans); - } - } - - // Update the display point on the curve - HoudiniSplineComponent->Construct(CurveDisplayPoints); - - HoudiniSplineComponent->MarkChanged(false); - - return Success; -} - - -bool -FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent) -{ - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return true; - - TArray PositionArray; - TArray RotationArray; - TArray Scales3dArray; - TArray UniformScaleArray; - for (FTransform & NextTransform : HoudiniSplineComponent->CurvePoints) - { - PositionArray.Add(NextTransform.GetLocation()); - RotationArray.Add(NextTransform.GetRotation()); - Scales3dArray.Add(NextTransform.GetScale3D()); - UniformScaleArray.Add(1.f); - } - - HAPI_NodeId CurveNode_id = HoudiniSplineComponent->GetNodeId(); - FTransform ParentTransform = HoudiniSplineComponent->GetComponentTransform(); - - FString InputNodeNameString = HoudiniSplineComponent->GetName(); - UHoudiniInputHoudiniSplineComponent* InputObject = Cast(HoudiniSplineComponent->GetOuter()); - if (InputObject) - { - UHoudiniInput* Input = Cast(InputObject->GetOuter()); - if (Input) - { - InputNodeNameString = Input->GetNodeBaseName(); - } - } - InputNodeNameString += TEXT("_curve"); - - bool Success = FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( - CurveNode_id, - InputNodeNameString, - &PositionArray, - &RotationArray, - &Scales3dArray, - HoudiniSplineComponent->GetCurveType(), - HoudiniSplineComponent->GetCurveMethod(), - HoudiniSplineComponent->IsClosedCurve(), - HoudiniSplineComponent->IsReversed(), - false, - ParentTransform); - - HoudiniSplineComponent->SetNodeId(CurveNode_id); - Success &= UpdateHoudiniCurve(HoudiniSplineComponent); - - return Success; -} - -bool -FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent) -{ - if (!SplineComponent || SplineComponent->IsPendingKill()) - return true; - - bool Success = HapiUpdateNodeForHoudiniSplineComponent(SplineComponent); - - return Success; -} - -bool -FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( - HAPI_NodeId& CurveNodeId, - const FString& InputNodeName, - TArray* Positions, - TArray* Rotations, - TArray* Scales3d, - EHoudiniCurveType InCurveType, - EHoudiniCurveMethod InCurveMethod, - const bool& InClosed, - const bool& InReversed, - const bool& InForceClose, - const FTransform& ParentTransform ) -{ -#if WITH_EDITOR - // Positions are required - if (!Positions) - return false; - - // We also need a valid host asset and 2 points to make a curve - int32 NumberOfCVs = Positions->Num(); - if (NumberOfCVs < 2) - return false; - - // Check if connected asset id is valid, if it is not, we need to create an input asset. - if (CurveNodeId < 0) - { - HAPI_NodeId NodeId = -1; - // Create the curve SOP Node - if (!FHoudiniSplineTranslator::HapiCreateCurveInputNode(NodeId, InputNodeName)) - return false; - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NodeId)) - return false; - - // We now have a valid id. - CurveNodeId = NodeId; - } - else - { - // We have to revert the Geo to its original state so we can use the Curve SOP: - // adding parameters to the Curve SOP locked it, preventing its parameters (type, method, isClosed) from working - FHoudiniApi::RevertGeo(FHoudiniEngine::Get().GetSession(), CurveNodeId); - } - - // - // In order to be able to add rotations and scale attributes to the curve SOP, we need to cook it twice: - // - // - First, we send the positions string to it, and cook it without refinement. - // this will allow us to get the proper curve CVs, part attributes and curve info to create the desired curve. - // - // - We then need to send back all the info extracted from the curve SOP to it, and add the rotation - // and scale attributes to it. This will lock the curve SOP, and prevent the curve type and method - // parameters from functioning properly (hence why we needed the first cook to set that up) - // - - // Set the curve type and curve method parameters for the curve node - int32 CurveTypeValue = (int32)InCurveType; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - - int32 CurveMethodValue = (int32)InCurveMethod; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - - int32 CurveClosed = InClosed ? 1 : 0; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - - int32 CurveReversed = InReversed ? 1 : 0; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); - - // Reading the curve parameters - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); - - if (InForceClose) - { - // We need to update the closed parameter - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); - - CurveClosed = 1; - } - - // For closed NURBS (CVs and Breakpoints), we have to close the curve manually, by duplicating its last point - // in order to be able to set the rotations and scales attributes properly. - bool bCloseCurveManually = false; - if (CurveClosed && (CurveTypeValue == HAPI_CURVETYPE_NURBS) && (CurveMethodValue != 2)) - { - // The curve is not closed anymore - if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 0)) - { - bCloseCurveManually = true; - - // Duplicating the first point to the end of the curve - // This needs to be done before sending the position string - FVector pos = (*Positions)[0]; - Positions->Add(pos); - - CurveClosed = false; - } - } - - // Creating the position string - FString PositionString = TEXT(""); - FHoudiniSplineTranslator::CreatePositionsString(*Positions, PositionString); - - // Get param id for the PositionString and modify it - HAPI_ParmId ParmId = -1; - if (FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId) != HAPI_RESULT_SUCCESS) - { - return false; - } - - std::string ConvertedString = TCHAR_TO_UTF8(*PositionString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - ConvertedString.c_str(), ParmId, 0), false); - - // If we don't want to add rotations or scale attributes to the curve, - // we can just cook the node normally and stop here. - bool bAddRotations = (Rotations != nullptr); - bool bAddScales3d = (Scales3d != nullptr); - if (!bAddRotations && !bAddScales3d) - { - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); - */ - - // Cook the node, no need to wait for completion - return FHoudiniEngineUtils::HapiCookNode(CurveNodeId, nullptr, false); - } - - // Setting up the first cook, without the curve refinement - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - CookOptions.maxVerticesPerPrimitive = -1; - CookOptions.refineCurveToLinear = false; - if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, true)) - return false; - - // We can now read back the Part infos from the cooked curve. - HAPI_PartInfo PartInfos; - FHoudiniApi::PartInfo_Init(&PartInfos); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurveNodeId, 0, &PartInfos), false); - - // - // Depending on the curve type and method, additionnal control points might have been created. - // We now have to interpolate the rotations and scale attributes for these. - // - - // Lambda function that interpolates rotation, scale and uniform scales values - // between two points using fCoeff as a weight, and insert the interpolated value at nInsertIndex - auto InterpolateRotScaleUScale = [&](const int32& nIndex1, const int32& nIndex2, const float& fCoeff, const int32& nInsertIndex) - { - if (Rotations && Rotations->IsValidIndex(nIndex1) && Rotations->IsValidIndex(nIndex2)) - { - FQuat interpolation = FQuat::Slerp((*Rotations)[nIndex1], (*Rotations)[nIndex2], fCoeff); - if (Rotations->IsValidIndex(nInsertIndex)) - Rotations->Insert(interpolation, nInsertIndex); - else - Rotations->Add(interpolation); - } - - if (Scales3d && Scales3d->IsValidIndex(nIndex1) && Scales3d->IsValidIndex(nIndex2)) - { - FVector interpolation = fCoeff * (*Scales3d)[nIndex1] + (1.0f - fCoeff) * (*Scales3d)[nIndex2]; - if (Scales3d->IsValidIndex(nInsertIndex)) - Scales3d->Insert(interpolation, nInsertIndex); - else - Scales3d->Add(interpolation); - } - }; - - // Lambda function that duplicates rotation and scale values - // at nIndex and insert/adds it at nInsertIndex - auto DuplicateRotScale = [&](const int32& nIndex, const int32& nInsertIndex) - { - if (Rotations && Rotations->IsValidIndex(nIndex)) - { - FQuat value = (*Rotations)[nIndex]; - if (Rotations->IsValidIndex(nInsertIndex)) - Rotations->Insert(value, nInsertIndex); - else - Rotations->Add(value); - } - - if (Scales3d && Scales3d->IsValidIndex(nIndex)) - { - FVector value = (*Scales3d)[nIndex]; - if (Scales3d->IsValidIndex(nInsertIndex)) - Scales3d->Insert(value, nInsertIndex); - else - Scales3d->Add(value); - } - }; - - // Do we want to close the curve by ourselves? - if (bCloseCurveManually) - { - // We need to duplicate the info of the first point to the last - DuplicateRotScale(0, NumberOfCVs++); - - // We need to update the closed parameter - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); - } - - // INTERPOLATION - if (CurveTypeValue == HAPI_CURVETYPE_NURBS) - { - // Closed NURBS have additional points reproducing the first ones - if (InClosed) - { - // Only the first one if the method is freehand ... - DuplicateRotScale(0, NumberOfCVs++); - - if (CurveMethodValue != 2) - { - // ... but also the 2nd and 3rd if the method is CVs or Breakpoints. - DuplicateRotScale(1, NumberOfCVs++); - DuplicateRotScale(2, NumberOfCVs++); - } - } - else if (CurveMethodValue == 1) - { - // Open NURBS have 2 new points if the method is breakpoint: - // One between the 1st and 2nd ... - InterpolateRotScaleUScale(0, 1, 0.5f, 1); - - // ... and one before the last one. - InterpolateRotScaleUScale(NumberOfCVs, NumberOfCVs - 1, 0.5f, NumberOfCVs); - NumberOfCVs += 2; - } - } - else if (CurveTypeValue == HAPI_CURVETYPE_BEZIER) - { - // Bezier curves requires additional point if the method is Breakpoints - if (CurveMethodValue == 1) - { - // 2 interpolated control points are added per points (except the last one) - int32 nOffset = 0; - for (int32 n = 0; n < NumberOfCVs - 1; n++) - { - int nIndex1 = n + nOffset; - int nIndex2 = n + nOffset + 1; - - InterpolateRotScaleUScale(nIndex1, nIndex2, 0.33f, nIndex2); - nIndex2++; - InterpolateRotScaleUScale(nIndex1, nIndex2, 0.66f, nIndex2); - - nOffset += 2; - } - NumberOfCVs += nOffset; - - if (CurveClosed) - { - // If the curve is closed, we need to add 2 points after the last, - // interpolated between the last and the first one - int nIndex = NumberOfCVs - 1; - InterpolateRotScaleUScale(nIndex, 0, 0.33f, NumberOfCVs++); - InterpolateRotScaleUScale(nIndex, 0, 0.66f, NumberOfCVs++); - - // and finally, the last point is the first.. - DuplicateRotScale(0, NumberOfCVs++); - } - } - else if (CurveClosed) - { - // For the other methods, if the bezier curve is closed, the last point is the 1st - DuplicateRotScale(0, NumberOfCVs++); - } - } - - // Even after interpolation, additional points might still be missing: - // Bezier curves require a certain number of points regarding their order, - // if points are lacking then HAPI duplicates the last one. - if (NumberOfCVs < PartInfos.pointCount) - { - int nToAdd = PartInfos.pointCount - NumberOfCVs; - for (int n = 0; n < nToAdd; n++) - { - DuplicateRotScale(NumberOfCVs - 1, NumberOfCVs); - NumberOfCVs++; - } - } - - // To avoid crashes, attributes will only be added if we now have the correct number of them - bAddRotations = bAddRotations && (Rotations->Num() == PartInfos.pointCount); - bAddScales3d = bAddScales3d && (Scales3d->Num() == PartInfos.pointCount); - - // We need to increase the point attributes count for points in the Part Infos - HAPI_AttributeOwner NewAttributesOwner = HAPI_ATTROWNER_POINT; - HAPI_AttributeOwner OriginalAttributesOwner = HAPI_ATTROWNER_POINT; - - int OriginalPointParametersCount = PartInfos.attributeCounts[NewAttributesOwner]; - if (bAddRotations) - PartInfos.attributeCounts[NewAttributesOwner] += 1; - if (bAddScales3d) - PartInfos.attributeCounts[NewAttributesOwner] += 1; - - // Sending the updated PartInfos - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, &PartInfos), false); - - // We need now to reproduce ALL the curves attributes for ALL the Owners.. - for (int nOwner = 0; nOwner < HAPI_ATTROWNER_MAX; nOwner++) - { - int nOwnerAttributeCount = nOwner == NewAttributesOwner ? OriginalPointParametersCount : PartInfos.attributeCounts[nOwner]; - if (nOwnerAttributeCount == 0) - continue; - - TArray AttributeNamesSH; - AttributeNamesSH.SetNum(nOwnerAttributeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, (HAPI_AttributeOwner)nOwner, - AttributeNamesSH.GetData(), AttributeNamesSH.Num()), false); - - for (int nAttribute = 0; nAttribute < AttributeNamesSH.Num(); nAttribute++) - { - const HAPI_StringHandle sh = AttributeNamesSH[nAttribute]; - if (sh == 0) - continue; - - // Get the attribute name - std::string attr_name; - FHoudiniEngineString::ToStdString(sh, attr_name); - if (strcmp(attr_name.c_str(), "__topology") == 0) - continue; - - // and the attribute infos - HAPI_AttributeInfo attr_info; - FHoudiniApi::AttributeInfo_Init(&attr_info); - //FMemory::Memzero< HAPI_AttributeInfo >( attr_info ); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, attr_name.c_str(), - (HAPI_AttributeOwner)nOwner, &attr_info), false); - - switch (attr_info.storage) - { - case HAPI_STORAGETYPE_INT: - { - // Storing IntData - TArray< int > IntData; - IntData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, -1, - IntData.GetData(), 0, attr_info.count), false); - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info, IntData.GetData(), - 0, attr_info.count), false); - } - break; - - case HAPI_STORAGETYPE_FLOAT: - { - // Storing Float Data - TArray< float > FloatData; - FloatData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, -1, - FloatData.GetData(), - 0, attr_info.count), false); - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - FloatData.GetData(), - 0, attr_info.count), false); - } - break; - - case HAPI_STORAGETYPE_STRING: - { - // Storing String Data - TArray StringHandleData; - StringHandleData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - StringHandleData.GetData(), - 0, attr_info.count), false); - - // Convert the SH to const char * - TArray StringData; - StringData.SetNumUninitialized(attr_info.count); - for (int n = 0; n < StringHandleData.Num(); n++) - { - // Converting the string - std::string strSTD; - FHoudiniEngineString::ToStdString(sh, strSTD); - - StringData[n] = strSTD.c_str(); - } - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - StringData.GetData(), - 0, attr_info.count), false); - } - break; - - default: - continue; - } - } - } - - // Only GET/SET curve infos if the part is a curve... - // (Closed linear curves are actually not considered as curves...) - if (PartInfos.type == HAPI_PARTTYPE_CURVE) - { - // We need to read the curve infos ... - HAPI_CurveInfo CurveInfo; - FHoudiniApi::CurveInfo_Init(&CurveInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - &CurveInfo), false); - - // ... the curve counts - TArray< int > CurveCounts; - CurveCounts.SetNumUninitialized(CurveInfo.curveCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveCounts.GetData(), - 0, CurveInfo.curveCount), false); - - // .. the curve orders - TArray< int > CurveOrders; - CurveOrders.SetNumUninitialized(CurveInfo.curveCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveOrders( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveOrders.GetData(), - 0, CurveInfo.curveCount), false); - - // .. And the Knots if they exist. - TArray< float > KnotsArray; - if (CurveInfo.hasKnots) - { - KnotsArray.SetNumUninitialized(CurveInfo.knotCount); - HOUDINI_CHECK_ERROR_RETURN( - FHoudiniApi::GetCurveKnots( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - KnotsArray.GetData(), - 0, CurveInfo.knotCount), false); - } - - // To set them back in HAPI - // CurveInfo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - &CurveInfo), false); - - // CurveCounts - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveCounts.GetData(), - 0, CurveInfo.curveCount), false); - - // CurveOrders - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveOrders( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveOrders.GetData(), - 0, CurveInfo.curveCount), false); - - // And Knots if they exist - if (CurveInfo.hasKnots) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveKnots( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - KnotsArray.GetData(), - 0, CurveInfo.knotCount), false); - } - } - - if (PartInfos.faceCount > 0) - { - // getting the face counts - TArray< int > FaceCounts; - FaceCounts.SetNumUninitialized(PartInfos.faceCount); - - if (FHoudiniApi::GetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - FaceCounts.GetData(), 0, - PartInfos.faceCount) == HAPI_RESULT_SUCCESS) - { - // Set the face count - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - FaceCounts.GetData(), - 0, PartInfos.faceCount), false); - } - } - - if (PartInfos.vertexCount > 0) - { - // the vertex list - TArray< int > VertexList; - VertexList.SetNumUninitialized(PartInfos.vertexCount); - - if (FHoudiniApi::GetVertexList( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - VertexList.GetData(), - 0, PartInfos.vertexCount) == HAPI_RESULT_SUCCESS) - { - // setting the vertex list - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - VertexList.GetData(), - 0, PartInfos.vertexCount), false); - } - } - - // We can add attributes to the curve now that all the curves attributes - // and properties have been reset. - if (bAddRotations) - { - // Create ROTATION attribute info - HAPI_AttributeInfo AttributeInfoRotation; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); - AttributeInfoRotation.count = NumberOfCVs; - AttributeInfoRotation.tupleSize = 4; - AttributeInfoRotation.exists = true; - AttributeInfoRotation.owner = NewAttributesOwner; - AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRotation.originalOwner = OriginalAttributesOwner; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_ROTATION, - &AttributeInfoRotation), false); - - // Convert the rotation infos - TArray< float > CurveRotations; - CurveRotations.SetNumZeroed(NumberOfCVs * 4); - for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) - { - // Get current quaternion - const FQuat& RotationQuaternion = (*Rotations)[Idx]; - - CurveRotations[Idx * 4 + 0] = RotationQuaternion.X; - CurveRotations[Idx * 4 + 1] = RotationQuaternion.Z; - CurveRotations[Idx * 4 + 2] = RotationQuaternion.Y; - CurveRotations[Idx * 4 + 3] = -RotationQuaternion.W; - } - - //we can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_ROTATION, - &AttributeInfoRotation, - CurveRotations.GetData(), - 0, AttributeInfoRotation.count), false); - } - - // Create SCALE attribute info. - if (bAddScales3d) - { - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = NumberOfCVs; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = NewAttributesOwner; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = OriginalAttributesOwner; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_SCALE, - &AttributeInfoScale), false); - - // Convert the scale - TArray< float > CurveScales; - CurveScales.SetNumZeroed(NumberOfCVs * 3); - for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) - { - // Get current scale - FVector ScaleVector = (*Scales3d)[Idx]; - CurveScales[Idx * 3 + 0] = ScaleVector.X; - CurveScales[Idx * 3 + 1] = ScaleVector.Z; - CurveScales[Idx * 3 + 2] = ScaleVector.Y; - } - - // We can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_SCALE, - &AttributeInfoScale, - CurveScales.GetData(), - 0, AttributeInfoScale.count), false); - } - - // Finally, commit the geo ... - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), CurveNodeId), false); - - // And cook it with refinement enabled - CookOptions.refineCurveToLinear = true; - //HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - // FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); - if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, false)) - return false; -#endif - - return true; -} - -void -FHoudiniSplineTranslator::CreatePositionsString(const TArray& InPositions, FString& OutPositionString) -{ - OutPositionString = TEXT(""); - for (int32 Idx = 0; Idx < InPositions.Num(); ++Idx) - { - FVector Position = InPositions[Idx]; - // Convert to meters - Position /= HAPI_UNREAL_SCALE_FACTOR_POSITION; - // Swap Y/Z - OutPositionString += FString::Printf(TEXT("%f, %f, %f "), Position.X, Position.Z, Position.Y); - } -} - -bool -FHoudiniSplineTranslator::HapiCreateCurveInputNode(HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName) -{ - // Create the curve SOP Node - HAPI_NodeId NewNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/curve"), InputNodeName, false, &NewNodeId), false); - - OutCurveNodeId = NewNodeId; - - // Submit default points to curve. - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), NewNodeId, - HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), NewNodeId, - HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT, ParmId, 0), false); - - // Cook the newly created node - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); - */ - - return FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true); -} - -UHoudiniSplineComponent* -FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent) -{ - if (GeoId < 0) - return nullptr; - - if (!OuterComponent || OuterComponent->IsPendingKill()) - return nullptr; - - USceneComponent* const SceneComponent = Cast(OuterComponent); - if (!IsValid(SceneComponent)) - return nullptr; - - // Create a HoudiniSplineComponent for the editable curve. - UHoudiniSplineComponent* HoudiniSplineComponent = NewObject( - OuterComponent, - UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Public | RF_Transactional); - - HoudiniSplineComponent->SetNodeId(GeoId); - HoudiniSplineComponent->SetGeoPartName(PartName); - - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(SceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - - UpdateHoudiniCurve(HoudiniSplineComponent); - - ReselectSelectedActors(); - - return HoudiniSplineComponent; - -} - -UHoudiniSplineComponent* -FHoudiniSplineTranslator::CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC) -{ - if (!OuterHAC || OuterHAC->IsPendingKill()) - return nullptr; - - UObject* Outer = nullptr; - if (OuterHAC && !OuterHAC->IsPendingKill()) - Outer = OuterHAC->GetOwner() ? OuterHAC->GetOwner() : OuterHAC->GetOuter(); - - UHoudiniSplineComponent *NewHoudiniSplineComponent = NewObject(Outer, UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!NewHoudiniSplineComponent) - return nullptr; - - NewHoudiniSplineComponent->Construct(CurvePoints); - - bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); - bool bHasScales = CurveScales.Num() == CurvePoints.Num(); - - TArray Transforms; - for (int32 n = 0; n < CurvePoints.Num(); ++n) - { - FTransform NextTransform = FTransform::Identity; - NextTransform.SetLocation(CurvePoints[n]); - - if (bHasRotations) - NextTransform.SetRotation(CurveRotations[n].Rotation().Quaternion()); - - if (bHasScales) - NextTransform.SetScale3D(CurveScales[n]); - - Transforms.Add(NextTransform); - } - - NewHoudiniSplineComponent->CurveType = EHoudiniCurveType::Polygon; - NewHoudiniSplineComponent->bIsOutputCurve = true; - - NewHoudiniSplineComponent->AttachToComponent(OuterHAC, FAttachmentTransformRules::KeepRelativeTransform); - NewHoudiniSplineComponent->RegisterComponent(); - - ReselectSelectedActors(); - - return NewHoudiniSplineComponent; -} - -USplineComponent* -FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, - UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed) -{ - if (!OuterComponent || OuterComponent->IsPendingKill()) - return nullptr; - - USceneComponent* OuterSceneComponent = Cast(OuterComponent); - if (!IsValid(OuterSceneComponent)) - return nullptr; - - UObject* Outer = nullptr; - Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); - - USplineComponent* NewSplineComponent = NewObject(Outer, USplineComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!NewSplineComponent) - return nullptr; - - // Clear default USplineComponent's points - NewSplineComponent->ClearSplinePoints(); - NewSplineComponent->bEditableWhenInherited = false; - - //bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); - //bool bHasScales = CurveScales.Num() == CurvePoints.Num(); - - for (int32 n = 0; n < CurvePoints.Num(); ++n) - { - NewSplineComponent->AddSplinePoint(CurvePoints[n], ESplineCoordinateSpace::Local); - - //FSplinePoint NewSplinePoint; - //NewSplinePoint.Position = CurvePoints[n]; - //if (bHasRotations) - // NewSplinePoint.Rotation = CurveRotations[n].Rotation(); - - //if (bHasScales) - // NewSplinePoint.Scale = CurveScales[n]; - //NewSplineComponent->AddPoint(NewSplinePoint, false); - } - - if (bIsLinear) - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - else - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); - } - - - NewSplineComponent->SetClosedLoop(bIsClosed); - - /* - NewSplineComponent->SetClosedLoop(bClosed); - - if (Type == int32(EHoudiniCurveType::Linear)) - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - else - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); - } - */ - - NewSplineComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - NewSplineComponent->RegisterComponent(); - AActor *OwnerActor = Cast(Outer); - if (IsValid(OwnerActor)) - OwnerActor->AddInstanceComponent(NewSplineComponent); - - ReselectSelectedActors(); - - return NewSplineComponent; -} - -bool -FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed) -{ - if (!EditedSplineComponent || EditedSplineComponent->IsPendingKill()) - return false; - - if (CurvePoints.Num() < 2) - return false; - - int MinCount = FMath::Min(CurvePoints.Num(), EditedSplineComponent->GetNumberOfSplinePoints()); - - for (int32 Idx = EditedSplineComponent->GetNumberOfSplinePoints() - 1; Idx >= 0; --Idx) - { - EditedSplineComponent->RemoveSplinePoint(Idx, false); - } - - for (int32 Idx = 0; Idx < CurvePoints.Num(); ++Idx) - { - EditedSplineComponent->AddSplinePoint(CurvePoints[Idx], ESplineCoordinateSpace::Local, false); - - if (CurveType == EHoudiniCurveType::Polygon) - EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Linear, false); - else - EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Curve, false); - } - - EditedSplineComponent->SetClosedLoop(bClosed, true); - - return true; -} - -bool -FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent) -{ - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - if (CurvePoints.Num() < 2) - return false; - - int MinCount = FMath::Min(CurvePoints.Num(), EditedHoudiniSplineComponent->CurvePoints.Num()); - - int Idx = 0; - // modify existing points - for (; Idx < MinCount; ++Idx) - { - FTransform CurTrans = EditedHoudiniSplineComponent->CurvePoints[Idx]; - if (CurTrans.GetLocation() == CurvePoints[Idx]) - continue; - - CurTrans.SetLocation(CurvePoints[Idx]); - } - - // remove extra points - if (Idx < EditedHoudiniSplineComponent->CurvePoints.Num()-1) - { - for (int32 n = EditedHoudiniSplineComponent->CurvePoints.Num() - 1; n >= Idx; --n) - { - EditedHoudiniSplineComponent->RemovePointAtIndex(n); - } - } - - - // append extra points - for (; Idx < CurvePoints.Num(); ++Idx) - { - FTransform NewPoint = FTransform::Identity; - NewPoint.SetLocation(CurvePoints[Idx]); - EditedHoudiniSplineComponent->CurvePoints.Add(NewPoint); - } - - return true; -} - - -bool -FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - UObject* InOuterComponent, - TMap& InSplines, - TMap& OutSplines, - const bool& InForceRebuild, - const EHoudiniCurveOutputType& OutputCurveType, - const bool& bIsLinear, - const bool& bIsClosed) -{ - // If we're not forcing the rebuild - // No need to recreate something that hasn't changed - if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged)) - { - // Simply reuse the existing meshes - OutSplines = InSplines; - return true; - } - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - int32 CurveNodeId = InHGPO.GeoId; - int32 CurvePartId = InHGPO.PartId; - if (CurveNodeId < 0 || CurvePartId < 0) - return false; - - // Extract all curve points from this HGPO - TArray RefinedCurvePositions; - HAPI_AttributeInfo AttributeRefinedCurvePositions; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); - - TArray RefinedCurveRotations; - HAPI_AttributeInfo AttributeRefinedCurveRotations; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveRotations); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_ROTATION, AttributeRefinedCurveRotations, RefinedCurveRotations); - - TArray RefinedCurveScales; - HAPI_AttributeInfo AttributeRefinedCurveScales; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveScales); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_SCALE, AttributeRefinedCurveScales, RefinedCurveScales); - - HAPI_CurveInfo CurveInfo; - FHoudiniApi::CurveInfo_Init(&CurveInfo); - FHoudiniApi::GetCurveInfo(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, &CurveInfo); - - int32 NumOfCurves = CurveInfo.curveCount; - TArray CurvePointsCounts; - CurvePointsCounts.SetNumZeroed(NumOfCurves); - FHoudiniApi::GetCurveCounts(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, CurvePointsCounts.GetData(), 0, NumOfCurves); - - TArray> CurvesDisplayPoints; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurvesDisplayPoints, CurvePointsCounts); - - TArray> CurvesRotations; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveRotations, CurvesRotations, CurvePointsCounts); - - TArray> CurvesScales; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveScales, CurvesScales, CurvePointsCounts); - - // Extract all curve points from this HGPO - FString GeoName = InHGPO.PartName; - int32 CurveIdx = 1; - - // Iterate through all curves found in this HGPO - for (int32 n = 0; n < CurvesDisplayPoints.Num(); ++n) - { - FString CurveName = FString::Printf(TEXT("%s curve %d"), *GeoName, CurveIdx); - CurveIdx += 1; - - if (CurvePointsCounts[n] < 2) - { - // Invalid vertex count, skip this curve. - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d] invalid vertex count.") - TEXT("- skipping."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx); - continue; - } - - FHoudiniOutputObjectIdentifier CurveIdentifier(InHGPO.ObjectId, InHGPO.GeoId, InHGPO.PartId, CurveName); - FHoudiniOutputObject* FoundOutputObject = InSplines.Find(CurveIdentifier); - - bool bNeedToRebuildSpline = false; - if (!FoundOutputObject) - bNeedToRebuildSpline = true; - - USceneComponent* FoundComponent = Cast(FoundOutputObject ? FoundOutputObject->OutputComponent : nullptr); - if (FoundComponent && !FoundComponent->IsPendingKill()) - { - // Only support output to Unreal Spline for now... - //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::UnrealSpline) - // bNeedToRebuildSpline = true; - - //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) - // bNeedToRebuildSpline = true; - - if (InHGPO.bHasGeoChanged || InHGPO.PartInfo.bHasChanged || InForceRebuild) - bNeedToRebuildSpline = true; - } - else - { - bNeedToRebuildSpline = true; - } - - // The curve has not changed, no need to go through the rest - if (!bNeedToRebuildSpline) - { - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - continue; - } - - bool bReusedPreviousOutput = false; - if (!FoundOutputObject) - { - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline (default): Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - // If not found (at initialize), create an Unreal spline - // We only support unreal spline for now.. - // May support Houdini spline too later - USplineComponent* CreatedSplineComponent = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); - if (!CreatedSplineComponent) - continue; - - // Create a new output object - FHoudiniOutputObject NewOutputObject; - NewOutputObject.OutputComponent = CreatedSplineComponent; - - NewOutputObject.CurveOutputProperty.CurveOutputType = OutputCurveType; - NewOutputObject.CurveOutputProperty.NumPoints = CurvePointsCounts[n]; - - // TODO: Need a way to access info of the output curve - NewOutputObject.CurveOutputProperty.CurveMethod = EHoudiniCurveMethod::Breakpoints; - NewOutputObject.CurveOutputProperty.CurveType = bIsLinear ? EHoudiniCurveType::Polygon : EHoudiniCurveType::Bezier; - NewOutputObject.CurveOutputProperty.bClosed = false; - // Fill in the rest of output curve properties - - OutSplines.Add(CurveIdentifier, NewOutputObject); - } - else - { - // - if (FoundOutputObject->CurveOutputProperty.CurveOutputType == EHoudiniCurveOutputType::UnrealSpline) - { - // See if we can simply update the previous Spline Component - bool bCanUpdateUnrealSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); - if (bCanUpdateUnrealSpline) - { - // Update the existing unreal spline component - bReusedPreviousOutput = true; - HOUDINI_LOG_WARNING( - TEXT("Updating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - USplineComponent* FoundUnrealSpline = Cast(FoundOutputObject->OutputComponent); - if (!FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(CurvesDisplayPoints[n], FoundUnrealSpline, FoundOutputObject->CurveOutputProperty.CurveType, FoundOutputObject->CurveOutputProperty.bClosed)) - continue; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - else - { - // Create a new Unreal spline component - // We support unreal spline only for now... - bReusedPreviousOutput = false; - FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::UnrealSpline; - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - USplineComponent* NewUnrealSpline = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); - if (!NewUnrealSpline) - continue; - - FoundOutputObject->OutputComponent = NewUnrealSpline; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - } - // We current support Unreal Spline output only... - /* - else - { - // We want to output a Houdini Spline Component - // See if we can simply update the previous Houdini Spline Component - bool bCanUpdateHoudiniSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); - if (bCanUpdateHoudiniSpline) - { - // Update the existing houdini spline component - bReusedPreviousOutput = true; - HOUDINI_LOG_WARNING( - TEXT("Changing Houdini Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - UHoudiniSplineComponent* FoundHoudiniSpline = Cast(FoundOutputObject->OutputComponent); - if (!FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], FoundHoudiniSpline)) - continue; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - else - { - // Create a new Houdini spline component - bReusedPreviousOutput = false; - FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - UHoudiniSplineComponent* NewHoudiniSpline = CreateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuter); - if (!NewHoudiniSpline) - continue; - - FoundOutputObject->OutputComponent = NewHoudiniSpline; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - } - */ - } - - if (bReusedPreviousOutput) - { - // Remove the reused output unreal spline from the old map to avoid its deletion - InSplines.Remove(CurveIdentifier); - } - - HOUDINI_LOG_WARNING( - TEXT("Finished Generating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - } - - return true; -} - - -bool -FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - // ONLY DO THIS ON CURVES!!!! - if (InOutput->GetType() != EHoudiniOutputType::Curve) - return false; - - // UHoudiniAssetComponent* OuterHAC = Cast(InOuterComponent); - // - // if (!OuterHAC || OuterHAC->IsPendingKill()) - // return false; - - TMap NewOutputObjects; - TMap& OldOutputObjects = InOutput->GetOutputObjects(); - - // Iterate on all the output's HGPO - for (const FHoudiniGeoPartObject & CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - // not a curve, skip - if (CurHGPO.Type != EHoudiniPartType::Curve) - continue; - - // Check if we want to create a houdini output curve from corresponding attribute - HAPI_AttributeInfo CurveOutputAttriInfo; - FHoudiniApi::AttributeInfo_Init(&CurveOutputAttriInfo); - TArray IntData; - IntData.Empty(); - - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE, CurveOutputAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - continue; - - if (IntData.Num() <= 0) - continue; - else - { - if (IntData[0] == 0) - continue; - } - - HAPI_AttributeInfo LinearAttriInfo; - FHoudiniApi::AttributeInfo_Init(&LinearAttriInfo); - IntData.Empty(); - - bool bIsLinear = false; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR, LinearAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - { - if (IntData.Num() > 0) - bIsLinear = IntData[0] == 1; - } - - HAPI_AttributeInfo ClosedAttriInfo; - FHoudiniApi::AttributeInfo_Init(&ClosedAttriInfo); - IntData.Empty(); - - bool bIsClosed = false; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED, ClosedAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - { - if (IntData.Num() > 0) - bIsClosed = IntData[0] == 1; - } - - // We output curve to Unreal Spline only for now - // May support output to Houdini Spline later - CreateOutputSplinesFromHoudiniGeoPartObject(CurHGPO, InOuterComponent, OldOutputObjects, NewOutputObjects, false, EHoudiniCurveOutputType::UnrealSpline, bIsLinear, bIsClosed); - } - - // TODO: FIX ME!!! This literally nukes all the output objects, even if they are not curves! - - // The old map now only contains unused/stale output curves destroy them - for (auto& OldPair : OldOutputObjects) - { - USceneComponent* OldSplineSceneComponent = Cast(OldPair.Value.OutputComponent); - - if (!OldSplineSceneComponent || OldSplineSceneComponent->IsPendingKill()) - continue; - - // The output object is supposed to be a spline - if (!OldSplineSceneComponent->IsA() && !OldSplineSceneComponent->IsA()) - continue; - - OldSplineSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - OldSplineSceneComponent->UnregisterComponent(); - OldSplineSceneComponent->DestroyComponent(); - } - OldOutputObjects.Empty(); - - InOutput->SetOutputObjects(NewOutputObjects); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - - return true; -} - -void -FHoudiniSplineTranslator::ReselectSelectedActors() -{ - // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? - USelection* Selection = GEditor->GetSelectedActors(); - TArray SelectedActors; - SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); - Selection->GetSelectedObjects(SelectedActors); - - GEditor->SelectNone(false, false, false); - - for (AActor* NextSelected : SelectedActors) - { - GEditor->SelectActor(NextSelected, true, true, true, true); - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "HoudiniGeoPartObject.h" +#include "Components/SplineComponent.h" + +#include "EditorViewportClient.h" +#include "Engine/Selection.h" + +#include "HoudiniEnginePrivatePCH.h" + +void +FHoudiniSplineTranslator::ExtractStringPositions(const FString& Positions, TArray& OutPositions) +{ + TArray< FString > PointStrings; + static const TCHAR * PositionSeparators[] = + { + TEXT(" "), + TEXT(","), + }; + + int32 NumCoords = Positions.ParseIntoArray(PointStrings, PositionSeparators, 2); + OutPositions.SetNum(NumCoords / 3); + for (int32 OutIndex = 0; OutIndex < OutPositions.Num(); OutIndex++) + { + const int32& CoordIndex = OutIndex * 3; + OutPositions[OutIndex].X = FCString::Atof(*(PointStrings[CoordIndex + 0])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutPositions[OutIndex].Y = FCString::Atof(*(PointStrings[CoordIndex + 2])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutPositions[OutIndex].Z = FCString::Atof(*(PointStrings[CoordIndex + 1])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } +} + +void +FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData) +{ + OutVectorData.SetNum(InRawData.Num() / 3); + + for (int32 OutIndex = 0; OutIndex < OutVectorData.Num(); OutIndex++) + { + const int32& InIndex = OutIndex * 3; + OutVectorData[OutIndex].X = InRawData[InIndex + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutVectorData[OutIndex].Y = InRawData[InIndex + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutVectorData[OutIndex].Z = InRawData[InIndex + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } +} + +void +FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts) +{ + OutVectorData.SetNum(CurveCounts.Num()); + + int32 TotalNumPoints = 0; + for (const int32 & NextCount : CurveCounts) + TotalNumPoints += NextCount; + + // Do not fill the output array, if the total number of points does not match + if (InRawData.Num() < TotalNumPoints * 3) + return; + + + int32 Itr = 0; + + for (int32 n = 0; n < CurveCounts.Num(); ++n) + { + TArray & NextVectorDataArray = OutVectorData[n]; + NextVectorDataArray.SetNumZeroed(CurveCounts[n]); + + for (int32 PtIdx = 0; PtIdx < CurveCounts[n]; ++PtIdx) + { + if (Itr + 2 >= InRawData.Num()) + return; + + NextVectorDataArray[PtIdx].X = InRawData[Itr] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + NextVectorDataArray[PtIdx].Y = InRawData[Itr + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + NextVectorDataArray[PtIdx].Z = InRawData[Itr + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + + Itr += 3; + } + } +} +void +FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC) +{ + for (UHoudiniInput * NextInput : HAC->Inputs) + UpdateHoudiniInputCurves(NextInput); +} + +void +FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniInput* Input) +{ + if (!Input || Input->GetInputType() != EHoudiniInputType::Curve) + return; + + TArray *InputObjectArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!InputObjectArray) + return; + + for (UHoudiniInputObject * NextInputObject : *InputObjectArray) + { + UHoudiniInputHoudiniSplineComponent * HoudiniSplineInput = Cast(NextInputObject); + if (!HoudiniSplineInput) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInput->GetCurveComponent(); + FHoudiniSplineTranslator::UpdateHoudiniCurve(HoudiniSplineComponent); + } +} + +bool +FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent) +{ + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return false; + + int32 CurveNode_id = HoudiniSplineComponent->GetNodeId(); + if (CurveNode_id < 0) + return false; + + bool Success = true; + FString CurvePointsString = FString(); + int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; + int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; + int32 CurveClosed = 0; + int32 CurveReversed = 0; + + Success &= FHoudiniEngineUtils::HapiGetParameterDataAsString( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString); + + Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); + + Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); + + Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); + + Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + + + HoudiniSplineComponent->SetCurveType((EHoudiniCurveType)CurveTypeValue); + HoudiniSplineComponent->SetCurveMethod((EHoudiniCurveMethod)CurveMethodValue); + HoudiniSplineComponent->SetClosedCurve(CurveClosed == 1); + HoudiniSplineComponent->SetReversed(CurveReversed == 1); + + // We need to get the NodeInfo to get the parent id + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurveNode_id, &NodeInfo), false); + + TArray< float > RefinedCurvePositions; + HAPI_AttributeInfo AttributeRefinedCurvePositions; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); + Success &= FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); + + // Process coords string and extract positions. + TArray CurvePoints; + FHoudiniSplineTranslator::ExtractStringPositions(CurvePointsString, CurvePoints); + + TArray CurveDisplayPoints; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurveDisplayPoints); + + // build curve points for editable curves. + if (HoudiniSplineComponent->CurvePoints.Num() < CurvePoints.Num()) + { + HoudiniSplineComponent->CurvePoints.Empty(); + for (FVector NextPos : CurvePoints) + { + FTransform NextTrans = FTransform::Identity; + NextTrans.SetLocation(NextPos); + HoudiniSplineComponent->CurvePoints.Add(NextTrans); + } + } + + // Update the display point on the curve + HoudiniSplineComponent->Construct(CurveDisplayPoints); + + HoudiniSplineComponent->MarkChanged(false); + + return Success; +} + + +bool +FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent) +{ + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return true; + + TArray PositionArray; + TArray RotationArray; + TArray Scales3dArray; + TArray UniformScaleArray; + for (FTransform & NextTransform : HoudiniSplineComponent->CurvePoints) + { + PositionArray.Add(NextTransform.GetLocation()); + RotationArray.Add(NextTransform.GetRotation()); + Scales3dArray.Add(NextTransform.GetScale3D()); + UniformScaleArray.Add(1.f); + } + + HAPI_NodeId CurveNode_id = HoudiniSplineComponent->GetNodeId(); + FTransform ParentTransform = HoudiniSplineComponent->GetComponentTransform(); + + FString InputNodeNameString = HoudiniSplineComponent->GetName(); + UHoudiniInputHoudiniSplineComponent* InputObject = Cast(HoudiniSplineComponent->GetOuter()); + if (InputObject) + { + UHoudiniInput* Input = Cast(InputObject->GetOuter()); + if (Input) + { + InputNodeNameString = Input->GetNodeBaseName(); + } + } + InputNodeNameString += TEXT("_curve"); + + bool Success = FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( + CurveNode_id, + InputNodeNameString, + &PositionArray, + &RotationArray, + &Scales3dArray, + HoudiniSplineComponent->GetCurveType(), + HoudiniSplineComponent->GetCurveMethod(), + HoudiniSplineComponent->IsClosedCurve(), + HoudiniSplineComponent->IsReversed(), + false, + ParentTransform); + + HoudiniSplineComponent->SetNodeId(CurveNode_id); + Success &= UpdateHoudiniCurve(HoudiniSplineComponent); + + return Success; +} + +bool +FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent) +{ + if (!SplineComponent || SplineComponent->IsPendingKill()) + return true; + + bool Success = HapiUpdateNodeForHoudiniSplineComponent(SplineComponent); + + return Success; +} + +bool +FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( + HAPI_NodeId& CurveNodeId, + const FString& InputNodeName, + TArray* Positions, + TArray* Rotations, + TArray* Scales3d, + EHoudiniCurveType InCurveType, + EHoudiniCurveMethod InCurveMethod, + const bool& InClosed, + const bool& InReversed, + const bool& InForceClose, + const FTransform& ParentTransform ) +{ +#if WITH_EDITOR + // Positions are required + if (!Positions) + return false; + + // We also need a valid host asset and 2 points to make a curve + int32 NumberOfCVs = Positions->Num(); + if (NumberOfCVs < 2) + return false; + + // Check if connected asset id is valid, if it is not, we need to create an input asset. + if (CurveNodeId < 0) + { + HAPI_NodeId NodeId = -1; + // Create the curve SOP Node + if (!FHoudiniSplineTranslator::HapiCreateCurveInputNode(NodeId, InputNodeName)) + return false; + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NodeId)) + return false; + + // We now have a valid id. + CurveNodeId = NodeId; + } + else + { + // We have to revert the Geo to its original state so we can use the Curve SOP: + // adding parameters to the Curve SOP locked it, preventing its parameters (type, method, isClosed) from working + FHoudiniApi::RevertGeo(FHoudiniEngine::Get().GetSession(), CurveNodeId); + } + + // + // In order to be able to add rotations and scale attributes to the curve SOP, we need to cook it twice: + // + // - First, we send the positions string to it, and cook it without refinement. + // this will allow us to get the proper curve CVs, part attributes and curve info to create the desired curve. + // + // - We then need to send back all the info extracted from the curve SOP to it, and add the rotation + // and scale attributes to it. This will lock the curve SOP, and prevent the curve type and method + // parameters from functioning properly (hence why we needed the first cook to set that up) + // + + // Set the curve type and curve method parameters for the curve node + int32 CurveTypeValue = (int32)InCurveType; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); + + int32 CurveMethodValue = (int32)InCurveMethod; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); + + int32 CurveClosed = InClosed ? 1 : 0; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); + + int32 CurveReversed = InReversed ? 1 : 0; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + + // Reading the curve parameters + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + + if (InForceClose) + { + // We need to update the closed parameter + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); + + CurveClosed = 1; + } + + // For closed NURBS (CVs and Breakpoints), we have to close the curve manually, by duplicating its last point + // in order to be able to set the rotations and scales attributes properly. + bool bCloseCurveManually = false; + if (CurveClosed && (CurveTypeValue == HAPI_CURVETYPE_NURBS) && (CurveMethodValue != 2)) + { + // The curve is not closed anymore + if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 0)) + { + bCloseCurveManually = true; + + // Duplicating the first point to the end of the curve + // This needs to be done before sending the position string + FVector pos = (*Positions)[0]; + Positions->Add(pos); + + CurveClosed = false; + } + } + + // Creating the position string + FString PositionString = TEXT(""); + FHoudiniSplineTranslator::CreatePositionsString(*Positions, PositionString); + + // Get param id for the PositionString and modify it + HAPI_ParmId ParmId = -1; + if (FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId) != HAPI_RESULT_SUCCESS) + { + return false; + } + + std::string ConvertedString = TCHAR_TO_UTF8(*PositionString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + ConvertedString.c_str(), ParmId, 0), false); + + // If we don't want to add rotations or scale attributes to the curve, + // we can just cook the node normally and stop here. + bool bAddRotations = (Rotations != nullptr); + bool bAddScales3d = (Scales3d != nullptr); + if (!bAddRotations && !bAddScales3d) + { + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); + */ + + // Cook the node, no need to wait for completion + return FHoudiniEngineUtils::HapiCookNode(CurveNodeId, nullptr, false); + } + + // Setting up the first cook, without the curve refinement + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + CookOptions.maxVerticesPerPrimitive = -1; + CookOptions.refineCurveToLinear = false; + if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, true)) + return false; + + // We can now read back the Part infos from the cooked curve. + HAPI_PartInfo PartInfos; + FHoudiniApi::PartInfo_Init(&PartInfos); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurveNodeId, 0, &PartInfos), false); + + // + // Depending on the curve type and method, additionnal control points might have been created. + // We now have to interpolate the rotations and scale attributes for these. + // + + // Lambda function that interpolates rotation, scale and uniform scales values + // between two points using fCoeff as a weight, and insert the interpolated value at nInsertIndex + auto InterpolateRotScaleUScale = [&](const int32& nIndex1, const int32& nIndex2, const float& fCoeff, const int32& nInsertIndex) + { + if (Rotations && Rotations->IsValidIndex(nIndex1) && Rotations->IsValidIndex(nIndex2)) + { + FQuat interpolation = FQuat::Slerp((*Rotations)[nIndex1], (*Rotations)[nIndex2], fCoeff); + if (Rotations->IsValidIndex(nInsertIndex)) + Rotations->Insert(interpolation, nInsertIndex); + else + Rotations->Add(interpolation); + } + + if (Scales3d && Scales3d->IsValidIndex(nIndex1) && Scales3d->IsValidIndex(nIndex2)) + { + FVector interpolation = fCoeff * (*Scales3d)[nIndex1] + (1.0f - fCoeff) * (*Scales3d)[nIndex2]; + if (Scales3d->IsValidIndex(nInsertIndex)) + Scales3d->Insert(interpolation, nInsertIndex); + else + Scales3d->Add(interpolation); + } + }; + + // Lambda function that duplicates rotation and scale values + // at nIndex and insert/adds it at nInsertIndex + auto DuplicateRotScale = [&](const int32& nIndex, const int32& nInsertIndex) + { + if (Rotations && Rotations->IsValidIndex(nIndex)) + { + FQuat value = (*Rotations)[nIndex]; + if (Rotations->IsValidIndex(nInsertIndex)) + Rotations->Insert(value, nInsertIndex); + else + Rotations->Add(value); + } + + if (Scales3d && Scales3d->IsValidIndex(nIndex)) + { + FVector value = (*Scales3d)[nIndex]; + if (Scales3d->IsValidIndex(nInsertIndex)) + Scales3d->Insert(value, nInsertIndex); + else + Scales3d->Add(value); + } + }; + + // Do we want to close the curve by ourselves? + if (bCloseCurveManually) + { + // We need to duplicate the info of the first point to the last + DuplicateRotScale(0, NumberOfCVs++); + + // We need to update the closed parameter + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); + } + + // INTERPOLATION + if (CurveTypeValue == HAPI_CURVETYPE_NURBS) + { + // Closed NURBS have additional points reproducing the first ones + if (InClosed) + { + // Only the first one if the method is freehand ... + DuplicateRotScale(0, NumberOfCVs++); + + if (CurveMethodValue != 2) + { + // ... but also the 2nd and 3rd if the method is CVs or Breakpoints. + DuplicateRotScale(1, NumberOfCVs++); + DuplicateRotScale(2, NumberOfCVs++); + } + } + else if (CurveMethodValue == 1) + { + // Open NURBS have 2 new points if the method is breakpoint: + // One between the 1st and 2nd ... + InterpolateRotScaleUScale(0, 1, 0.5f, 1); + + // ... and one before the last one. + InterpolateRotScaleUScale(NumberOfCVs, NumberOfCVs - 1, 0.5f, NumberOfCVs); + NumberOfCVs += 2; + } + } + else if (CurveTypeValue == HAPI_CURVETYPE_BEZIER) + { + // Bezier curves requires additional point if the method is Breakpoints + if (CurveMethodValue == 1) + { + // 2 interpolated control points are added per points (except the last one) + int32 nOffset = 0; + for (int32 n = 0; n < NumberOfCVs - 1; n++) + { + int nIndex1 = n + nOffset; + int nIndex2 = n + nOffset + 1; + + InterpolateRotScaleUScale(nIndex1, nIndex2, 0.33f, nIndex2); + nIndex2++; + InterpolateRotScaleUScale(nIndex1, nIndex2, 0.66f, nIndex2); + + nOffset += 2; + } + NumberOfCVs += nOffset; + + if (CurveClosed) + { + // If the curve is closed, we need to add 2 points after the last, + // interpolated between the last and the first one + int nIndex = NumberOfCVs - 1; + InterpolateRotScaleUScale(nIndex, 0, 0.33f, NumberOfCVs++); + InterpolateRotScaleUScale(nIndex, 0, 0.66f, NumberOfCVs++); + + // and finally, the last point is the first.. + DuplicateRotScale(0, NumberOfCVs++); + } + } + else if (CurveClosed) + { + // For the other methods, if the bezier curve is closed, the last point is the 1st + DuplicateRotScale(0, NumberOfCVs++); + } + } + + // Even after interpolation, additional points might still be missing: + // Bezier curves require a certain number of points regarding their order, + // if points are lacking then HAPI duplicates the last one. + if (NumberOfCVs < PartInfos.pointCount) + { + int nToAdd = PartInfos.pointCount - NumberOfCVs; + for (int n = 0; n < nToAdd; n++) + { + DuplicateRotScale(NumberOfCVs - 1, NumberOfCVs); + NumberOfCVs++; + } + } + + // To avoid crashes, attributes will only be added if we now have the correct number of them + bAddRotations = bAddRotations && (Rotations->Num() == PartInfos.pointCount); + bAddScales3d = bAddScales3d && (Scales3d->Num() == PartInfos.pointCount); + + // We need to increase the point attributes count for points in the Part Infos + HAPI_AttributeOwner NewAttributesOwner = HAPI_ATTROWNER_POINT; + HAPI_AttributeOwner OriginalAttributesOwner = HAPI_ATTROWNER_POINT; + + int OriginalPointParametersCount = PartInfos.attributeCounts[NewAttributesOwner]; + if (bAddRotations) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + if (bAddScales3d) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + + // Sending the updated PartInfos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, &PartInfos), false); + + // We need now to reproduce ALL the curves attributes for ALL the Owners.. + for (int nOwner = 0; nOwner < HAPI_ATTROWNER_MAX; nOwner++) + { + int nOwnerAttributeCount = nOwner == NewAttributesOwner ? OriginalPointParametersCount : PartInfos.attributeCounts[nOwner]; + if (nOwnerAttributeCount == 0) + continue; + + TArray AttributeNamesSH; + AttributeNamesSH.SetNum(nOwnerAttributeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, (HAPI_AttributeOwner)nOwner, + AttributeNamesSH.GetData(), AttributeNamesSH.Num()), false); + + for (int nAttribute = 0; nAttribute < AttributeNamesSH.Num(); nAttribute++) + { + const HAPI_StringHandle sh = AttributeNamesSH[nAttribute]; + if (sh == 0) + continue; + + // Get the attribute name + std::string attr_name; + FHoudiniEngineString::ToStdString(sh, attr_name); + if (strcmp(attr_name.c_str(), "__topology") == 0) + continue; + + // and the attribute infos + HAPI_AttributeInfo attr_info; + FHoudiniApi::AttributeInfo_Init(&attr_info); + //FMemory::Memzero< HAPI_AttributeInfo >( attr_info ); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, attr_name.c_str(), + (HAPI_AttributeOwner)nOwner, &attr_info), false); + + switch (attr_info.storage) + { + case HAPI_STORAGETYPE_INT: + { + // Storing IntData + TArray< int > IntData; + IntData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + IntData.GetData(), 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info, IntData.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_FLOAT: + { + // Storing Float Data + TArray< float > FloatData; + FloatData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + FloatData.GetData(), + 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + FloatData.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_STRING: + { + // Storing String Data + TArray StringHandleData; + StringHandleData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + StringHandleData.GetData(), + 0, attr_info.count), false); + + // Convert the SH to const char * + TArray StringData; + StringData.SetNumUninitialized(attr_info.count); + for (int n = 0; n < StringHandleData.Num(); n++) + { + // Converting the string + std::string strSTD; + FHoudiniEngineString::ToStdString(sh, strSTD); + + StringData[n] = strSTD.c_str(); + } + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + StringData.GetData(), + 0, attr_info.count), false); + } + break; + + default: + continue; + } + } + } + + // Only GET/SET curve infos if the part is a curve... + // (Closed linear curves are actually not considered as curves...) + if (PartInfos.type == HAPI_PARTTYPE_CURVE) + { + // We need to read the curve infos ... + HAPI_CurveInfo CurveInfo; + FHoudiniApi::CurveInfo_Init(&CurveInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + &CurveInfo), false); + + // ... the curve counts + TArray< int > CurveCounts; + CurveCounts.SetNumUninitialized(CurveInfo.curveCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveCounts.GetData(), + 0, CurveInfo.curveCount), false); + + // .. the curve orders + TArray< int > CurveOrders; + CurveOrders.SetNumUninitialized(CurveInfo.curveCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveOrders( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveOrders.GetData(), + 0, CurveInfo.curveCount), false); + + // .. And the Knots if they exist. + TArray< float > KnotsArray; + if (CurveInfo.hasKnots) + { + KnotsArray.SetNumUninitialized(CurveInfo.knotCount); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetCurveKnots( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + KnotsArray.GetData(), + 0, CurveInfo.knotCount), false); + } + + // To set them back in HAPI + // CurveInfo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + &CurveInfo), false); + + // CurveCounts + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveCounts.GetData(), + 0, CurveInfo.curveCount), false); + + // CurveOrders + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveOrders( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveOrders.GetData(), + 0, CurveInfo.curveCount), false); + + // And Knots if they exist + if (CurveInfo.hasKnots) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveKnots( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + KnotsArray.GetData(), + 0, CurveInfo.knotCount), false); + } + } + + if (PartInfos.faceCount > 0) + { + // getting the face counts + TArray< int > FaceCounts; + FaceCounts.SetNumUninitialized(PartInfos.faceCount); + + if (FHoudiniApi::GetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + FaceCounts.GetData(), 0, + PartInfos.faceCount) == HAPI_RESULT_SUCCESS) + { + // Set the face count + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + FaceCounts.GetData(), + 0, PartInfos.faceCount), false); + } + } + + if (PartInfos.vertexCount > 0) + { + // the vertex list + TArray< int > VertexList; + VertexList.SetNumUninitialized(PartInfos.vertexCount); + + if (FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + VertexList.GetData(), + 0, PartInfos.vertexCount) == HAPI_RESULT_SUCCESS) + { + // setting the vertex list + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + VertexList.GetData(), + 0, PartInfos.vertexCount), false); + } + } + + // We can add attributes to the curve now that all the curves attributes + // and properties have been reset. + if (bAddRotations) + { + // Create ROTATION attribute info + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + AttributeInfoRotation.count = NumberOfCVs; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = NewAttributesOwner; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation), false); + + // Convert the rotation infos + TArray< float > CurveRotations; + CurveRotations.SetNumZeroed(NumberOfCVs * 4); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + // Get current quaternion + const FQuat& RotationQuaternion = (*Rotations)[Idx]; + + CurveRotations[Idx * 4 + 0] = RotationQuaternion.X; + CurveRotations[Idx * 4 + 1] = RotationQuaternion.Z; + CurveRotations[Idx * 4 + 2] = RotationQuaternion.Y; + CurveRotations[Idx * 4 + 3] = -RotationQuaternion.W; + } + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation, + CurveRotations.GetData(), + 0, AttributeInfoRotation.count), false); + } + + // Create SCALE attribute info. + if (bAddScales3d) + { + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = NumberOfCVs; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = NewAttributesOwner; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale), false); + + // Convert the scale + TArray< float > CurveScales; + CurveScales.SetNumZeroed(NumberOfCVs * 3); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + // Get current scale + FVector ScaleVector = (*Scales3d)[Idx]; + CurveScales[Idx * 3 + 0] = ScaleVector.X; + CurveScales[Idx * 3 + 1] = ScaleVector.Z; + CurveScales[Idx * 3 + 2] = ScaleVector.Y; + } + + // We can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale, + CurveScales.GetData(), + 0, AttributeInfoScale.count), false); + } + + // Finally, commit the geo ... + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), CurveNodeId), false); + + // And cook it with refinement enabled + CookOptions.refineCurveToLinear = true; + //HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + // FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); + if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, false)) + return false; +#endif + + return true; +} + +void +FHoudiniSplineTranslator::CreatePositionsString(const TArray& InPositions, FString& OutPositionString) +{ + OutPositionString = TEXT(""); + for (int32 Idx = 0; Idx < InPositions.Num(); ++Idx) + { + FVector Position = InPositions[Idx]; + // Convert to meters + Position /= HAPI_UNREAL_SCALE_FACTOR_POSITION; + // Swap Y/Z + OutPositionString += FString::Printf(TEXT("%f, %f, %f "), Position.X, Position.Z, Position.Y); + } +} + +bool +FHoudiniSplineTranslator::HapiCreateCurveInputNode(HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName) +{ + // Create the curve SOP Node + HAPI_NodeId NewNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/curve"), InputNodeName, false, &NewNodeId), false); + + OutCurveNodeId = NewNodeId; + + // Submit default points to curve. + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), NewNodeId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NewNodeId, + HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT, ParmId, 0), false); + + // Cook the newly created node + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); + */ + + return FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true); +} + +UHoudiniSplineComponent* +FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent) +{ + if (GeoId < 0) + return nullptr; + + if (!OuterComponent || OuterComponent->IsPendingKill()) + return nullptr; + + USceneComponent* const SceneComponent = Cast(OuterComponent); + if (!IsValid(SceneComponent)) + return nullptr; + + // Create a HoudiniSplineComponent for the editable curve. + UHoudiniSplineComponent* HoudiniSplineComponent = NewObject( + OuterComponent, + UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Public | RF_Transactional); + + HoudiniSplineComponent->SetNodeId(GeoId); + HoudiniSplineComponent->SetGeoPartName(PartName); + + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(SceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + + UpdateHoudiniCurve(HoudiniSplineComponent); + + ReselectSelectedActors(); + + return HoudiniSplineComponent; + +} + +UHoudiniSplineComponent* +FHoudiniSplineTranslator::CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC) +{ + if (!OuterHAC || OuterHAC->IsPendingKill()) + return nullptr; + + UObject* Outer = nullptr; + if (OuterHAC && !OuterHAC->IsPendingKill()) + Outer = OuterHAC->GetOwner() ? OuterHAC->GetOwner() : OuterHAC->GetOuter(); + + UHoudiniSplineComponent *NewHoudiniSplineComponent = NewObject(Outer, UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!NewHoudiniSplineComponent) + return nullptr; + + NewHoudiniSplineComponent->Construct(CurvePoints); + + bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); + bool bHasScales = CurveScales.Num() == CurvePoints.Num(); + + TArray Transforms; + for (int32 n = 0; n < CurvePoints.Num(); ++n) + { + FTransform NextTransform = FTransform::Identity; + NextTransform.SetLocation(CurvePoints[n]); + + if (bHasRotations) + NextTransform.SetRotation(CurveRotations[n].Rotation().Quaternion()); + + if (bHasScales) + NextTransform.SetScale3D(CurveScales[n]); + + Transforms.Add(NextTransform); + } + + NewHoudiniSplineComponent->CurveType = EHoudiniCurveType::Polygon; + NewHoudiniSplineComponent->bIsOutputCurve = true; + + NewHoudiniSplineComponent->AttachToComponent(OuterHAC, FAttachmentTransformRules::KeepRelativeTransform); + NewHoudiniSplineComponent->RegisterComponent(); + + ReselectSelectedActors(); + + return NewHoudiniSplineComponent; +} + +USplineComponent* +FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, + UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed) +{ + if (!OuterComponent || OuterComponent->IsPendingKill()) + return nullptr; + + USceneComponent* OuterSceneComponent = Cast(OuterComponent); + if (!IsValid(OuterSceneComponent)) + return nullptr; + + UObject* Outer = nullptr; + Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); + + USplineComponent* NewSplineComponent = NewObject(Outer, USplineComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!NewSplineComponent) + return nullptr; + + // Clear default USplineComponent's points + NewSplineComponent->ClearSplinePoints(); + NewSplineComponent->bEditableWhenInherited = false; + + //bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); + //bool bHasScales = CurveScales.Num() == CurvePoints.Num(); + + for (int32 n = 0; n < CurvePoints.Num(); ++n) + { + NewSplineComponent->AddSplinePoint(CurvePoints[n], ESplineCoordinateSpace::Local); + + //FSplinePoint NewSplinePoint; + //NewSplinePoint.Position = CurvePoints[n]; + //if (bHasRotations) + // NewSplinePoint.Rotation = CurveRotations[n].Rotation(); + + //if (bHasScales) + // NewSplinePoint.Scale = CurveScales[n]; + //NewSplineComponent->AddPoint(NewSplinePoint, false); + } + + if (bIsLinear) + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + else + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); + } + + + NewSplineComponent->SetClosedLoop(bIsClosed); + + /* + NewSplineComponent->SetClosedLoop(bClosed); + + if (Type == int32(EHoudiniCurveType::Linear)) + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + else + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); + } + */ + + NewSplineComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + NewSplineComponent->RegisterComponent(); + AActor *OwnerActor = Cast(Outer); + if (IsValid(OwnerActor)) + OwnerActor->AddInstanceComponent(NewSplineComponent); + + ReselectSelectedActors(); + + return NewSplineComponent; +} + +bool +FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed) +{ + if (!EditedSplineComponent || EditedSplineComponent->IsPendingKill()) + return false; + + if (CurvePoints.Num() < 2) + return false; + + int MinCount = FMath::Min(CurvePoints.Num(), EditedSplineComponent->GetNumberOfSplinePoints()); + + for (int32 Idx = EditedSplineComponent->GetNumberOfSplinePoints() - 1; Idx >= 0; --Idx) + { + EditedSplineComponent->RemoveSplinePoint(Idx, false); + } + + for (int32 Idx = 0; Idx < CurvePoints.Num(); ++Idx) + { + EditedSplineComponent->AddSplinePoint(CurvePoints[Idx], ESplineCoordinateSpace::Local, false); + + if (CurveType == EHoudiniCurveType::Polygon) + EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Linear, false); + else + EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Curve, false); + } + + EditedSplineComponent->SetClosedLoop(bClosed, true); + + return true; +} + +bool +FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent) +{ + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + if (CurvePoints.Num() < 2) + return false; + + int MinCount = FMath::Min(CurvePoints.Num(), EditedHoudiniSplineComponent->CurvePoints.Num()); + + int Idx = 0; + // modify existing points + for (; Idx < MinCount; ++Idx) + { + FTransform CurTrans = EditedHoudiniSplineComponent->CurvePoints[Idx]; + if (CurTrans.GetLocation() == CurvePoints[Idx]) + continue; + + CurTrans.SetLocation(CurvePoints[Idx]); + } + + // remove extra points + if (Idx < EditedHoudiniSplineComponent->CurvePoints.Num()-1) + { + for (int32 n = EditedHoudiniSplineComponent->CurvePoints.Num() - 1; n >= Idx; --n) + { + EditedHoudiniSplineComponent->RemovePointAtIndex(n); + } + } + + + // append extra points + for (; Idx < CurvePoints.Num(); ++Idx) + { + FTransform NewPoint = FTransform::Identity; + NewPoint.SetLocation(CurvePoints[Idx]); + EditedHoudiniSplineComponent->CurvePoints.Add(NewPoint); + } + + return true; +} + + +bool +FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + UObject* InOuterComponent, + TMap& InSplines, + TMap& OutSplines, + const bool& InForceRebuild, + const EHoudiniCurveOutputType& OutputCurveType, + const bool& bIsLinear, + const bool& bIsClosed) +{ + // If we're not forcing the rebuild + // No need to recreate something that hasn't changed + if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged)) + { + // Simply reuse the existing meshes + OutSplines = InSplines; + return true; + } + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + int32 CurveNodeId = InHGPO.GeoId; + int32 CurvePartId = InHGPO.PartId; + if (CurveNodeId < 0 || CurvePartId < 0) + return false; + + // Extract all curve points from this HGPO + TArray RefinedCurvePositions; + HAPI_AttributeInfo AttributeRefinedCurvePositions; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); + + TArray RefinedCurveRotations; + HAPI_AttributeInfo AttributeRefinedCurveRotations; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveRotations); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_ROTATION, AttributeRefinedCurveRotations, RefinedCurveRotations); + + TArray RefinedCurveScales; + HAPI_AttributeInfo AttributeRefinedCurveScales; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveScales); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_SCALE, AttributeRefinedCurveScales, RefinedCurveScales); + + HAPI_CurveInfo CurveInfo; + FHoudiniApi::CurveInfo_Init(&CurveInfo); + FHoudiniApi::GetCurveInfo(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, &CurveInfo); + + int32 NumOfCurves = CurveInfo.curveCount; + TArray CurvePointsCounts; + CurvePointsCounts.SetNumZeroed(NumOfCurves); + FHoudiniApi::GetCurveCounts(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, CurvePointsCounts.GetData(), 0, NumOfCurves); + + TArray> CurvesDisplayPoints; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurvesDisplayPoints, CurvePointsCounts); + + TArray> CurvesRotations; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveRotations, CurvesRotations, CurvePointsCounts); + + TArray> CurvesScales; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveScales, CurvesScales, CurvePointsCounts); + + // Extract all curve points from this HGPO + FString GeoName = InHGPO.PartName; + int32 CurveIdx = 1; + + // Iterate through all curves found in this HGPO + for (int32 n = 0; n < CurvesDisplayPoints.Num(); ++n) + { + FString CurveName = FString::Printf(TEXT("%s curve %d"), *GeoName, CurveIdx); + CurveIdx += 1; + + if (CurvePointsCounts[n] < 2) + { + // Invalid vertex count, skip this curve. + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d] invalid vertex count.") + TEXT("- skipping."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx); + continue; + } + + FHoudiniOutputObjectIdentifier CurveIdentifier(InHGPO.ObjectId, InHGPO.GeoId, InHGPO.PartId, CurveName); + FHoudiniOutputObject* FoundOutputObject = InSplines.Find(CurveIdentifier); + + bool bNeedToRebuildSpline = false; + if (!FoundOutputObject) + bNeedToRebuildSpline = true; + + USceneComponent* FoundComponent = Cast(FoundOutputObject ? FoundOutputObject->OutputComponent : nullptr); + if (FoundComponent && !FoundComponent->IsPendingKill()) + { + // Only support output to Unreal Spline for now... + //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::UnrealSpline) + // bNeedToRebuildSpline = true; + + //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + // bNeedToRebuildSpline = true; + + if (InHGPO.bHasGeoChanged || InHGPO.PartInfo.bHasChanged || InForceRebuild) + bNeedToRebuildSpline = true; + } + else + { + bNeedToRebuildSpline = true; + } + + // The curve has not changed, no need to go through the rest + if (!bNeedToRebuildSpline) + { + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + continue; + } + + bool bReusedPreviousOutput = false; + if (!FoundOutputObject) + { + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline (default): Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + // If not found (at initialize), create an Unreal spline + // We only support unreal spline for now.. + // May support Houdini spline too later + USplineComponent* CreatedSplineComponent = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); + if (!CreatedSplineComponent) + continue; + + // Create a new output object + FHoudiniOutputObject NewOutputObject; + NewOutputObject.OutputComponent = CreatedSplineComponent; + + NewOutputObject.CurveOutputProperty.CurveOutputType = OutputCurveType; + NewOutputObject.CurveOutputProperty.NumPoints = CurvePointsCounts[n]; + + // TODO: Need a way to access info of the output curve + NewOutputObject.CurveOutputProperty.CurveMethod = EHoudiniCurveMethod::Breakpoints; + NewOutputObject.CurveOutputProperty.CurveType = bIsLinear ? EHoudiniCurveType::Polygon : EHoudiniCurveType::Bezier; + NewOutputObject.CurveOutputProperty.bClosed = false; + // Fill in the rest of output curve properties + + OutSplines.Add(CurveIdentifier, NewOutputObject); + } + else + { + // + if (FoundOutputObject->CurveOutputProperty.CurveOutputType == EHoudiniCurveOutputType::UnrealSpline) + { + // See if we can simply update the previous Spline Component + bool bCanUpdateUnrealSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); + if (bCanUpdateUnrealSpline) + { + // Update the existing unreal spline component + bReusedPreviousOutput = true; + HOUDINI_LOG_WARNING( + TEXT("Updating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + USplineComponent* FoundUnrealSpline = Cast(FoundOutputObject->OutputComponent); + if (!FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(CurvesDisplayPoints[n], FoundUnrealSpline, FoundOutputObject->CurveOutputProperty.CurveType, FoundOutputObject->CurveOutputProperty.bClosed)) + continue; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + else + { + // Create a new Unreal spline component + // We support unreal spline only for now... + bReusedPreviousOutput = false; + FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::UnrealSpline; + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + USplineComponent* NewUnrealSpline = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); + if (!NewUnrealSpline) + continue; + + FoundOutputObject->OutputComponent = NewUnrealSpline; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + } + // We current support Unreal Spline output only... + /* + else + { + // We want to output a Houdini Spline Component + // See if we can simply update the previous Houdini Spline Component + bool bCanUpdateHoudiniSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); + if (bCanUpdateHoudiniSpline) + { + // Update the existing houdini spline component + bReusedPreviousOutput = true; + HOUDINI_LOG_WARNING( + TEXT("Changing Houdini Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + UHoudiniSplineComponent* FoundHoudiniSpline = Cast(FoundOutputObject->OutputComponent); + if (!FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], FoundHoudiniSpline)) + continue; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + else + { + // Create a new Houdini spline component + bReusedPreviousOutput = false; + FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + UHoudiniSplineComponent* NewHoudiniSpline = CreateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuter); + if (!NewHoudiniSpline) + continue; + + FoundOutputObject->OutputComponent = NewHoudiniSpline; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + } + */ + } + + if (bReusedPreviousOutput) + { + // Remove the reused output unreal spline from the old map to avoid its deletion + InSplines.Remove(CurveIdentifier); + } + + HOUDINI_LOG_WARNING( + TEXT("Finished Generating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + } + + return true; +} + + +bool +FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + // ONLY DO THIS ON CURVES!!!! + if (InOutput->GetType() != EHoudiniOutputType::Curve) + return false; + + // UHoudiniAssetComponent* OuterHAC = Cast(InOuterComponent); + // + // if (!OuterHAC || OuterHAC->IsPendingKill()) + // return false; + + TMap NewOutputObjects; + TMap& OldOutputObjects = InOutput->GetOutputObjects(); + + // Iterate on all the output's HGPO + for (const FHoudiniGeoPartObject & CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + // not a curve, skip + if (CurHGPO.Type != EHoudiniPartType::Curve) + continue; + + // Check if we want to create a houdini output curve from corresponding attribute + HAPI_AttributeInfo CurveOutputAttriInfo; + FHoudiniApi::AttributeInfo_Init(&CurveOutputAttriInfo); + TArray IntData; + IntData.Empty(); + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE, CurveOutputAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + continue; + + if (IntData.Num() <= 0) + continue; + else + { + if (IntData[0] == 0) + continue; + } + + HAPI_AttributeInfo LinearAttriInfo; + FHoudiniApi::AttributeInfo_Init(&LinearAttriInfo); + IntData.Empty(); + + bool bIsLinear = false; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR, LinearAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + { + if (IntData.Num() > 0) + bIsLinear = IntData[0] == 1; + } + + HAPI_AttributeInfo ClosedAttriInfo; + FHoudiniApi::AttributeInfo_Init(&ClosedAttriInfo); + IntData.Empty(); + + bool bIsClosed = false; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED, ClosedAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + { + if (IntData.Num() > 0) + bIsClosed = IntData[0] == 1; + } + + // We output curve to Unreal Spline only for now + // May support output to Houdini Spline later + CreateOutputSplinesFromHoudiniGeoPartObject(CurHGPO, InOuterComponent, OldOutputObjects, NewOutputObjects, false, EHoudiniCurveOutputType::UnrealSpline, bIsLinear, bIsClosed); + } + + // TODO: FIX ME!!! This literally nukes all the output objects, even if they are not curves! + + // The old map now only contains unused/stale output curves destroy them + for (auto& OldPair : OldOutputObjects) + { + USceneComponent* OldSplineSceneComponent = Cast(OldPair.Value.OutputComponent); + + if (!OldSplineSceneComponent || OldSplineSceneComponent->IsPendingKill()) + continue; + + // The output object is supposed to be a spline + if (!OldSplineSceneComponent->IsA() && !OldSplineSceneComponent->IsA()) + continue; + + OldSplineSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + OldSplineSceneComponent->UnregisterComponent(); + OldSplineSceneComponent->DestroyComponent(); + } + OldOutputObjects.Empty(); + + InOutput->SetOutputObjects(NewOutputObjects); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + + return true; +} + +void +FHoudiniSplineTranslator::ReselectSelectedActors() +{ + // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? + USelection* Selection = GEditor->GetSelectedActors(); + TArray SelectedActors; + SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); + Selection->GetSelectedObjects(SelectedActors); + + GEditor->SelectNone(false, false, false); + + for (AActor* NextSelected : SelectedActors) + { + GEditor->SelectActor(NextSelected, true, true, true, true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h index 2d6df4eb6..078696156 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h @@ -1,116 +1,116 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" - -class UHoudiniInput; -class UHoudiniOutput; -class UHoudiniAssetComponent; -class UHoudiniSplineComponent; -class USceneComponent; -class USplineComponent; - -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniOutputObject; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniCurveOutputType : uint8; - -struct HOUDINIENGINE_API FHoudiniSplineTranslator -{ - // Get the cooked Houdini curve. - static bool UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent); - - // Get all cooked Houdini curves of an input. - static void UpdateHoudiniInputCurves(UHoudiniInput* Input); - - // Get all cooked Houdini curves of inputs in an HAC. - static void UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC); - - // Upload Houdini spline component data to the curve node, and then sync the Houdini Spline Component with the curve node. - static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent); - - // Create a new curve node. - static bool HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent); - - // Update the curve node data, or create a new curve node if the CurveNodeId is valid. - static bool HapiCreateCurveInputNodeForData( - HAPI_NodeId& CurveNodeId, - const FString& InputNodeName, - TArray* Positions, - TArray* Rotations, - TArray* Scales3d, - EHoudiniCurveType InCurveType, - EHoudiniCurveMethod InCurveMethod, - const bool& InClosed, - const bool& InReversed, - const bool& InForceClose = false, - const FTransform& ParentTransform = FTransform::Identity); - - // Create a default curve node. - static bool HapiCreateCurveInputNode( - HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName); - - // Create a Houdini spline component from a given editable node. (Only called once when first build the editable node.) - static UHoudiniSplineComponent* CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent); - - // Helper functions. - static void ExtractStringPositions(const FString& Positions, TArray& OutPositions); - - static void ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData); - - static void ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts); - - static void CreatePositionsString(const TArray& InPositions, FString& OutPositionString); - - static bool CreateOutputSplinesFromHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO, UObject* InOuterComponent, - TMap& InSplines, - TMap& OutSplines, - const bool& InForceRebuild, - const EHoudiniCurveOutputType& OutputCurveType, - const bool& bIsLinear, - const bool& bIsclosed); - - static bool CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent); - - static USplineComponent* CreateOutputUnrealSplineComponent(const TArray& CurvePoints, - const TArray& CurveRotations, const TArray& CurveScales, UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed); - - static UHoudiniSplineComponent* CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC); - - static bool UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed); - - static bool UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent); - - static void ReselectSelectedActors(); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" + +class UHoudiniInput; +class UHoudiniOutput; +class UHoudiniAssetComponent; +class UHoudiniSplineComponent; +class USceneComponent; +class USplineComponent; + +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniOutputObject; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniCurveOutputType : uint8; + +struct HOUDINIENGINE_API FHoudiniSplineTranslator +{ + // Get the cooked Houdini curve. + static bool UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent); + + // Get all cooked Houdini curves of an input. + static void UpdateHoudiniInputCurves(UHoudiniInput* Input); + + // Get all cooked Houdini curves of inputs in an HAC. + static void UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC); + + // Upload Houdini spline component data to the curve node, and then sync the Houdini Spline Component with the curve node. + static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent); + + // Create a new curve node. + static bool HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent); + + // Update the curve node data, or create a new curve node if the CurveNodeId is valid. + static bool HapiCreateCurveInputNodeForData( + HAPI_NodeId& CurveNodeId, + const FString& InputNodeName, + TArray* Positions, + TArray* Rotations, + TArray* Scales3d, + EHoudiniCurveType InCurveType, + EHoudiniCurveMethod InCurveMethod, + const bool& InClosed, + const bool& InReversed, + const bool& InForceClose = false, + const FTransform& ParentTransform = FTransform::Identity); + + // Create a default curve node. + static bool HapiCreateCurveInputNode( + HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName); + + // Create a Houdini spline component from a given editable node. (Only called once when first build the editable node.) + static UHoudiniSplineComponent* CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent); + + // Helper functions. + static void ExtractStringPositions(const FString& Positions, TArray& OutPositions); + + static void ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData); + + static void ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts); + + static void CreatePositionsString(const TArray& InPositions, FString& OutPositionString); + + static bool CreateOutputSplinesFromHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO, UObject* InOuterComponent, + TMap& InSplines, + TMap& OutSplines, + const bool& InForceRebuild, + const EHoudiniCurveOutputType& OutputCurveType, + const bool& bIsLinear, + const bool& bIsclosed); + + static bool CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent); + + static USplineComponent* CreateOutputUnrealSplineComponent(const TArray& CurvePoints, + const TArray& CurveRotations, const TArray& CurveScales, UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed); + + static UHoudiniSplineComponent* CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC); + + static bool UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed); + + static bool UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent); + + static void ReselectSelectedActors(); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp index 3a490d3e8..21c57a62a 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp @@ -1,125 +1,152 @@ - -#include "HoudiniStringResolver.h" -#include "HoudiniEngineRuntimeUtils.h" - -void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutTokens) const -{ - for (auto& Elem : CachedTokens) - { - OutTokens.Add(Elem.Key, Elem.Value.StringValue); - } -} - -void FHoudiniStringResolver::SetToken(const FString& InName, const FString& InValue) -{ - CachedTokens.Add(InName, InValue); -} - -void FHoudiniStringResolver::SetTokensFromStringMap(const TMap& InTokens, bool bClearTokens) -{ - if (bClearTokens) - { - CachedTokens.Empty(); - } - - for (auto& Elem : InTokens) - { - CachedTokens.Add(Elem.Key, Elem.Value); - } -} - - - -FString FHoudiniStringResolver::ResolveString( - const FString& InString) const -{ - const FString Result = FString::Format(*InString, CachedTokens); - return Result; -} - -//void FHoudiniStringResolver::SetCurrentWorld(UWorld* InWorld) -//{ -// SetAttribute("world", InWorld->GetPathName()); -//} - -FString FHoudiniAttributeResolver::ResolveAttribute( - const FString& InAttrName, - const FString& InDefaultValue) const -{ - if (!CachedAttributes.Contains(InAttrName)) - { - return ResolveString(InDefaultValue); - } - FString AttrStr = CachedAttributes.FindChecked(InAttrName); - return ResolveString(AttrStr); -} - -//FString FHoudiniStringResolver::GetTempFolderArgument() const -//{ -// // The actual temp directory should have been supplied externally -// if (Tokens.Contains(TEXT("temp"))) -// return Tokens.FindChecked(TEXT("temp")); -// -// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'temp' argument. Using fallback value.")); -// return TEXT("/Game/Content/HoudiniEngine/Temp"); // Fallback value -//} -// -//FString FHoudiniStringResolver::GetBakeFolderArgument() const -//{ -// // The actual bake directory should have been supplied externally -// if (Tokens.Contains(TEXT("bake"))) -// return Tokens.FindChecked(TEXT("bake")); -// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'bake' argument. Using fallback value.")); -// return TEXT("/Game/Content/HoudiniEngine/Bake"); // Fallback value -//} -// -//FString FHoudiniStringResolver::GetOutputFolderForPackageMode(EPackageMode PackageMode) const -//{ -// switch (PackageMode) -// { -// case EPackageMode::Bake: -// return GetBakeFolderArgument(); -// case EPackageMode::CookToLevel: -// case EPackageMode::CookToTemp: -// return GetTempFolderArgument(); -// } -// return ""; -//} - - void FHoudiniAttributeResolver::SetCachedAttributes(const TMap& Attributes) - { - CachedAttributes = Attributes; - } - -void FHoudiniAttributeResolver::SetAttribute(const FString& InName, const FString& InValue) -{ - CachedAttributes.Add(InName, InValue); -} - -FString FHoudiniAttributeResolver::ResolveFullLevelPath() const -{ - FString OutputFolder = TEXT("/Game/Content/HoudiniEngine/Temp"); - - const FStringFormatArg* BaseDir = CachedTokens.Find(TEXT("out_basedir")); - if (BaseDir) - OutputFolder = BaseDir->StringValue; - - FString LevelPathAttr = ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, TEXT("{out}")); - if (LevelPathAttr.IsEmpty()) - return OutputFolder; - - return FHoudiniEngineRuntimeUtils::JoinPaths(OutputFolder, LevelPathAttr); -} - -FString FHoudiniAttributeResolver::ResolveOutputName() const -{ - FString OutputAttribName; - - if (CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2)) - OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2; - else - OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1; - - return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStringResolver.h" +#include "HoudiniEngineRuntimeUtils.h" + +#include "HoudiniEnginePrivatePCH.h" + +void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutTokens) const +{ + for (auto& Elem : CachedTokens) + { + OutTokens.Add(Elem.Key, Elem.Value.StringValue); + } +} + +void FHoudiniStringResolver::SetToken(const FString& InName, const FString& InValue) +{ + CachedTokens.Add(InName, InValue); +} + +void FHoudiniStringResolver::SetTokensFromStringMap(const TMap& InTokens, bool bClearTokens) +{ + if (bClearTokens) + { + CachedTokens.Empty(); + } + + for (auto& Elem : InTokens) + { + CachedTokens.Add(Elem.Key, Elem.Value); + } +} + + + +FString FHoudiniStringResolver::ResolveString( + const FString& InString) const +{ + const FString Result = FString::Format(*InString, CachedTokens); + return Result; +} + +//void FHoudiniStringResolver::SetCurrentWorld(UWorld* InWorld) +//{ +// SetAttribute("world", InWorld->GetPathName()); +//} + +FString FHoudiniAttributeResolver::ResolveAttribute( + const FString& InAttrName, + const FString& InDefaultValue) const +{ + if (!CachedAttributes.Contains(InAttrName)) + { + return ResolveString(InDefaultValue); + } + FString AttrStr = CachedAttributes.FindChecked(InAttrName); + return ResolveString(AttrStr); +} + +//FString FHoudiniStringResolver::GetTempFolderArgument() const +//{ +// // The actual temp directory should have been supplied externally +// if (Tokens.Contains(TEXT("temp"))) +// return Tokens.FindChecked(TEXT("temp")); +// +// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'temp' argument. Using fallback value.")); +// return TEXT("/Game/Content/HoudiniEngine/Temp"); // Fallback value +//} +// +//FString FHoudiniStringResolver::GetBakeFolderArgument() const +//{ +// // The actual bake directory should have been supplied externally +// if (Tokens.Contains(TEXT("bake"))) +// return Tokens.FindChecked(TEXT("bake")); +// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'bake' argument. Using fallback value.")); +// return TEXT("/Game/Content/HoudiniEngine/Bake"); // Fallback value +//} +// +//FString FHoudiniStringResolver::GetOutputFolderForPackageMode(EPackageMode PackageMode) const +//{ +// switch (PackageMode) +// { +// case EPackageMode::Bake: +// return GetBakeFolderArgument(); +// case EPackageMode::CookToLevel: +// case EPackageMode::CookToTemp: +// return GetTempFolderArgument(); +// } +// return ""; +//} + + void FHoudiniAttributeResolver::SetCachedAttributes(const TMap& Attributes) + { + CachedAttributes = Attributes; + } + +void FHoudiniAttributeResolver::SetAttribute(const FString& InName, const FString& InValue) +{ + CachedAttributes.Add(InName, InValue); +} + +FString FHoudiniAttributeResolver::ResolveFullLevelPath() const +{ + FString OutputFolder = TEXT("/Game/Content/HoudiniEngine/Temp"); + + const FStringFormatArg* BaseDir = CachedTokens.Find(TEXT("out_basedir")); + if (BaseDir) + OutputFolder = BaseDir->StringValue; + + FString LevelPathAttr = ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, TEXT("{out}")); + if (LevelPathAttr.IsEmpty()) + return OutputFolder; + + return FHoudiniEngineRuntimeUtils::JoinPaths(OutputFolder, LevelPathAttr); +} + +FString FHoudiniAttributeResolver::ResolveOutputName() const +{ + FString OutputAttribName; + + if (CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2)) + OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2; + else + OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1; + + return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); +} + diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.h b/Source/HoudiniEngine/Private/HoudiniStringResolver.h index dab936184..dae37b1e3 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.h +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.h @@ -1,76 +1,101 @@ - -#pragma once - -#include "CoreMinimal.h" - -#include "HoudiniStringResolver.generated.h" - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniStringResolver -{ - - GENERATED_USTRUCT_BODY(); - -protected: - - // Named arguments that will be substituted into attribute values upon retrieval. - TMap CachedTokens; - - -public: - // ---------------------------------- - // Named argument accessors - // ---------------------------------- - - TMap& GetCachedTokens() { return CachedTokens; } - - - // Set a named argument that will be used for argument replacement during GetAttribute calls. - void SetToken(const FString& InName, const FString& InValue); - - void GetTokensAsStringMap(TMap& OutTokens) const; - - void SetTokensFromStringMap(const TMap& InValue, bool bClearTokens=true); - - // Resolve a string by substituting `Tokens` as named arguments during string formatting. - FString ResolveString(const FString& InStr) const; - -}; - - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolver -{ - GENERATED_USTRUCT_BODY(); - -protected: - TMap CachedAttributes; - -public: - - void SetCachedAttributes(const TMap& Attributes); - - // Return a mutable reference to the cached attributes. - TMap& GetCachedAttributes() { return CachedAttributes; } - - // Return immutable reference to cached attributes. - const TMap& GetCachedAttributes() const { return CachedAttributes; } - - // Set an attribute with the given name and value in the attribute cache. - void SetAttribute(const FString& InName, const FString& InValue); - - // Try to resolve an attribute with the given name. If the attribute could not be - // found, use DefaultValue as a fallback. - FString ResolveAttribute(const FString& InAttrName, const FString& InDefaultValue) const; - - // ---------------------------------- - // Helpers - // ---------------------------------- - - // Helper for resolving the `unreal_level_path` attribute. - FString ResolveFullLevelPath() const; - - // Helper for resolver custom output name attributes. - FString ResolveOutputName() const; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniStringResolver.generated.h" + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniStringResolver +{ + + GENERATED_USTRUCT_BODY(); + +protected: + + // Named arguments that will be substituted into attribute values upon retrieval. + TMap CachedTokens; + + +public: + // ---------------------------------- + // Named argument accessors + // ---------------------------------- + + TMap& GetCachedTokens() { return CachedTokens; } + + + // Set a named argument that will be used for argument replacement during GetAttribute calls. + void SetToken(const FString& InName, const FString& InValue); + + void GetTokensAsStringMap(TMap& OutTokens) const; + + void SetTokensFromStringMap(const TMap& InValue, bool bClearTokens=true); + + // Resolve a string by substituting `Tokens` as named arguments during string formatting. + FString ResolveString(const FString& InStr) const; + +}; + + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolver +{ + GENERATED_USTRUCT_BODY(); + +protected: + TMap CachedAttributes; + +public: + + void SetCachedAttributes(const TMap& Attributes); + + // Return a mutable reference to the cached attributes. + TMap& GetCachedAttributes() { return CachedAttributes; } + + // Return immutable reference to cached attributes. + const TMap& GetCachedAttributes() const { return CachedAttributes; } + + // Set an attribute with the given name and value in the attribute cache. + void SetAttribute(const FString& InName, const FString& InValue); + + // Try to resolve an attribute with the given name. If the attribute could not be + // found, use DefaultValue as a fallback. + FString ResolveAttribute(const FString& InAttrName, const FString& InDefaultValue) const; + + // ---------------------------------- + // Helpers + // ---------------------------------- + + // Helper for resolving the `unreal_level_path` attribute. + FString ResolveFullLevelPath() const; + + // Helper for resolver custom output name attributes. + FString ResolveOutputName() const; + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp b/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp index 214f00254..1fd5162eb 100644 --- a/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp +++ b/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp @@ -1,159 +1,159 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "SAssetSelectionWidget.h" - -#include "HoudiniEngineString.h" - -#if WITH_EDITOR - -#include "EditorStyleSet.h" -#include "Widgets/Layout/SBorder.h" -#include "Widgets/Input/SButton.h" - - -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 WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - -FReply -SAssetSelectionWidget::OnButtonOk() -{ - if (TSharedPtr WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - -FReply -SAssetSelectionWidget::OnButtonCancel() -{ - bIsCancelled = true; - - if (TSharedPtr WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "SAssetSelectionWidget.h" + +#include "HoudiniEngineString.h" + +#if WITH_EDITOR + +#include "EditorStyleSet.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Input/SButton.h" + + +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 WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply +SAssetSelectionWidget::OnButtonOk() +{ + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply +SAssetSelectionWidget::OnButtonCancel() +{ + bIsCancelled = true; + + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + #endif \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/SAssetSelectionWidget.h b/Source/HoudiniEngine/Private/SAssetSelectionWidget.h index 520f1ccd3..66f2c39e6 100644 --- a/Source/HoudiniEngine/Private/SAssetSelectionWidget.h +++ b/Source/HoudiniEngine/Private/SAssetSelectionWidget.h @@ -1,100 +1,100 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#if WITH_EDITOR - -#include "CoreMinimal.h" -//#include "Misc/Attribute.h" - -#include "Widgets/SWidget.h" -#include "Widgets/SCompoundWidget.h" -#include "Widgets/SWindow.h" -#include "Widgets/DeclarativeSyntaxSupport.h" - -#include "HAPI/HAPI_Common.h" - -/** 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, 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; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +//#include "Misc/Attribute.h" + +#include "Widgets/SWidget.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWindow.h" +#include "Widgets/DeclarativeSyntaxSupport.h" + +#include "HAPI/HAPI_Common.h" + +/** 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, 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; +}; + #endif \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp index 1485c5126..75d4f1dc2 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp @@ -1,422 +1,448 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#include "UnrealBrushTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniInputObject.h" - -#include "HoudiniGeoPartObject.h" -#include "Model.h" -#include "Engine/Polys.h" - -#include "HoudiniEngineRuntimeUtils.h" - -// Includes for Brush building code. Remove when the code is in the correct place. -#include "HCsgUtils.h" -#include "ActorEditorUtils.h" -#include "Misc/ScopedSlowTask.h" - -#include "Engine/Level.h" - -// TODO: Fix this -// This is currently being included to get access to the CreateFaceMaterialArray / DeleteFaceMaterialArray methods. -#include "UnrealMeshTranslator.h" - -DEFINE_LOG_CATEGORY_STATIC(LogBrushTranslator, Log, All); - -bool FUnrealBrushTranslator::CreateInputNodeForBrush( - UHoudiniInputBrush* InputBrushObject, - ABrush* BrushActor, - const TArray* ExcludeActors, - HAPI_NodeId &CreatedNodeId, - const FString& NodeName -) -{ - if (!IsValid(BrushActor)) - return false; - - if (!IsValid(BrushActor->Brush)) - return false; - - if (InputBrushObject->ShouldIgnoreThisInput()) - return true; - - //-------------------------------------------------------------------------------------------------- - // Create an input node - //-------------------------------------------------------------------------------------------------- - - HAPI_NodeId ParentNodeId = -1; - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CreatedNodeId)) - { - HAPI_NodeId InputNodeId = -1; - //FString BrushNodeName = NodeName + TEXT("_") + BrushActor->GetName(); - FString BrushNodeName = BrushActor->GetName(); - // Create Brush SOP node - std::string NodeNameRawString; - FHoudiniEngineUtils::ConvertUnrealString(BrushNodeName, NodeNameRawString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - return false; - - // We now have a valid id. - CreatedNodeId = InputNodeId; - ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); - - // Add a clean node - HAPI_NodeId CleanNodeId; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - ParentNodeId, TEXT("clean"), TEXT("clean"), true, &CleanNodeId), false); - - // Connect input node to the clean node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CleanNodeId, 0, CreatedNodeId, 0), false); - - // Set display flag on the clean node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetNodeDisplay( - FHoudiniEngine::Get().GetSession(), CleanNodeId, 1), false); - } - else - { - ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); - } - - - // Transform for positions - const FTransform ActorTransform = BrushActor->GetActorTransform(); - const FTransform ActorTransformInverse = ActorTransform.Inverse(); - // Precompute matrices for Normal transformations (see FPlane::TransformBy) - const FMatrix NmlInvXform = ActorTransformInverse.ToMatrixWithScale().TransposeAdjoint(); - float NScale = ActorTransformInverse.GetDeterminant() < 0 ? -1.f : 1.f; - - //-------------------------------------------------------------------------------------------------- - // Find actors that intersect with the given brush. - //-------------------------------------------------------------------------------------------------- - TArray BrushActors; - UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(InputBrushObject, BrushActors); - - UModel* BrushModel = UHCsgUtils::BuildModelFromBrushes(BrushActors); - InputBrushObject->UpdateCachedData(BrushModel, BrushActors); - - // DEBUG: Upload the level model (baked by UE) to Houdini - // ULevel* Level = BrushActor->GetTypedOuter(); - // BrushModel = Level->Model; - - - int NumPoints = BrushModel->Points.Num(); - if (NumPoints == 0) - { - // The content has changed and now we don't have geo to output. - // Be sure to clean up existing nodes in Houdini. - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), ParentNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *(BrushActor->GetName())); - } - CreatedNodeId = -1; - return true; - } - - //-------------------------------------------------------------------------------------------------- - // Construct the face count buffer. Also count the vertex indices in required to define the Part. - //-------------------------------------------------------------------------------------------------- - - int NumIndices = 0; - TArray FaceCountBuffer; - - { - // Calculate the size of the vertex buffer and the base vertex index of each node. - TArray& Nodes = BrushModel->Nodes; - //TArray& Surfs = BrushModel->Surfs; - TArray& Verts = BrushModel->Verts; - - int32 NumNodes = Nodes.Num(); - - FaceCountBuffer.SetNumUninitialized(NumNodes); - // Build the face counts buffer by iterating over the BSP nodes. - for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) - { - FBspNode& Node = Nodes[NodeIndex]; - FaceCountBuffer[NodeIndex] = Node.NumVertices; - NumIndices += Node.NumVertices; - } - } - - //-------------------------------------------------------------------------------------------------- - // Apply actor transform - //-------------------------------------------------------------------------------------------------- - - if (!ActorTransform.Equals(FTransform::Identity)) - { - - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ActorTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), ParentNodeId, &HapiTransform), false); - } - - //-------------------------------------------------------------------------------------------------- - // Start processing the geo and add it to the input node - //-------------------------------------------------------------------------------------------------- - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumIndices; - Part.faceCount = FaceCountBuffer.Num(); - Part.pointCount = NumPoints; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, &Part), false); - - // ----------------------------- - // Vector - Point Attribute Info - // ----------------------------- - HAPI_AttributeInfo AttributeInfoPointVector; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointVector); - AttributeInfoPointVector.count = NumPoints; - AttributeInfoPointVector.tupleSize = 3; - AttributeInfoPointVector.exists = true; - AttributeInfoPointVector.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointVector.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointVector.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector), false); - - // ----------------------------- - // Vector - Vertex Attribute Info - // ----------------------------- - HAPI_AttributeInfo AttributeInfoVertexVector; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertexVector); - AttributeInfoVertexVector.count = NumIndices; - AttributeInfoVertexVector.tupleSize = 3; - AttributeInfoVertexVector.exists = true; - AttributeInfoVertexVector.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertexVector.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertexVector.originalOwner = HAPI_ATTROWNER_INVALID; - - // ----------------------------- - // POSITION (P) - // ----------------------------- - - { - TArray< FVector > OutPosition; - FVector Scale = FVector(1.f, 1.f, 1.f); // TODO: Extract from actor transform. - OutPosition.SetNumUninitialized(NumPoints); - - for (int32 PosIndex = 0; PosIndex < NumPoints; ++PosIndex) - { - FVector Point = BrushModel->Points[PosIndex]; - Point = ActorTransformInverse.TransformPosition(Point); - FVector Pos(Point.X, Point.Z, Point.Y); - OutPosition[PosIndex] = Pos/HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - // Upload point positions. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector, - (const float *)OutPosition.GetData(), 0, AttributeInfoPointVector.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES (VertexList), NORMALS, UVS - //--------------------------------------------------------------------------------------------------------------------- - // Vertex buffer processing logic based on UModel::BuildVertexBuffers(). - { - // Calculate the size of the vertex buffer and the base vertex index of each node. - TArray& Positions = BrushModel->Points; - TArray& Nodes = BrushModel->Nodes; - TArray& Surfs = BrushModel->Surfs; - TArray& Verts = BrushModel->Verts; - TArray& Vectors = BrushModel->Vectors; - - int32 NumNodes = Nodes.Num(); - TArray Indices; - TArray OutNormals; - TArray OutUV; - TArray MaterialIndices; - TMap MaterialMap; - - Indices.SetNumUninitialized(NumIndices); - OutNormals.SetNumUninitialized(NumIndices); - OutUV.SetNumUninitialized(NumIndices); - - MaterialIndices.SetNumUninitialized(NumNodes); - - // Populate the vertex index buffer. - int32 iVertex = 0; - for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) - { - FBspNode& Node = Nodes[NodeIndex]; - FBspSurf& Surf = Surfs[Node.iSurf]; - for (int32 NodeVertexIndex = 0; NodeVertexIndex < Node.NumVertices; ++NodeVertexIndex) - { - // Vertex Index - Indices[iVertex] = Verts[Node.iVertPool + NodeVertexIndex].pVertex; - // Normal - FVector N = Vectors[Surf.vNormal]; - N = NmlInvXform.TransformVector(N).GetSafeNormal(); - - OutNormals[iVertex] = FVector(N.X, N.Z, N.Y); - // UVs - FVector& vU = Vectors[Surf.vTextureU]; - FVector& vV = Vectors[Surf.vTextureV]; - FVector deltaVtx = (Positions[Indices[iVertex]] - Positions[Surf.pBase]); - float U = FVector::DotProduct(deltaVtx, vU) / UModel::GetGlobalBSPTexelScale(); - float V = -FVector::DotProduct(deltaVtx, vV) / UModel::GetGlobalBSPTexelScale(); - OutUV[iVertex] = FVector(U, V, 0.f); - ++iVertex; - } - // Face Material - // Construct a material index array for the faces - int32 MaterialIndex = MaterialMap.FindOrAdd(Surf.Material, MaterialMap.Num()); - MaterialIndices[NodeIndex] = MaterialIndex; - } - - // Set the vertex index buffer - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, Indices.GetData(), 0, NumIndices), false); - - // Set the face counts as per the BSP nodes. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, FaceCountBuffer.GetData(), 0, FaceCountBuffer.Num()), false); - - // ----------------------------- - // Normal attribute - // ----------------------------- - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertexVector), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertexVector, (const float *)OutNormals.GetData(), - 0, AttributeInfoVertexVector.count), false); - - // ----------------------------- - // UV attribute - // ----------------------------- - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_UV, &AttributeInfoVertexVector), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_UV, - &AttributeInfoVertexVector, (const float *)OutUV.GetData(), - 0, AttributeInfoVertexVector.count), false); - - // ----------------------------- - // Material attribute - // ----------------------------- - - TArray Materials; - - if (MaterialMap.Num() > 0) - { - // Sort the output material in the correct order (by material index) - MaterialMap.ValueSort([](int32 A, int32 B) { return A < B; }); - - // Set the value in the correct order - // Do not reduce the array of materials, this could cause crahses in some weird cases.. - /*if (MaterialMap.Num() > MaterialInterfaces.Num()) - MaterialInterfaces.SetNumZeroed(MaterialMap.Num());*/ - Materials.SetNumUninitialized(MaterialMap.Num()); - - int32 MaterialIndex = 0; - for (auto Kvp : MaterialMap) - { - Materials[MaterialIndex++] = Kvp.Key; - } - } - - // Create list of materials, one for each face. - TArray< char * > OutMaterials; - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - Materials, MaterialIndices, OutMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - CreatedNodeId, - 0, - NumNodes, - OutMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(OutMaterials); - - // Delete texture material parameter names. - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedNodeId), false ); - - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealBrushTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniInputObject.h" + +#include "HoudiniGeoPartObject.h" +#include "Model.h" +#include "Engine/Polys.h" + +#include "HoudiniEngineRuntimeUtils.h" + +// Includes for Brush building code. Remove when the code is in the correct place. +#include "HCsgUtils.h" +#include "ActorEditorUtils.h" +#include "Misc/ScopedSlowTask.h" + +#include "Engine/Level.h" + +// TODO: Fix this +// This is currently being included to get access to the CreateFaceMaterialArray / DeleteFaceMaterialArray methods. +#include "UnrealMeshTranslator.h" + +DEFINE_LOG_CATEGORY_STATIC(LogBrushTranslator, Log, All); + +bool FUnrealBrushTranslator::CreateInputNodeForBrush( + UHoudiniInputBrush* InputBrushObject, + ABrush* BrushActor, + const TArray* ExcludeActors, + HAPI_NodeId &CreatedNodeId, + const FString& NodeName +) +{ + if (!IsValid(BrushActor)) + return false; + + if (!IsValid(BrushActor->Brush)) + return false; + + if (InputBrushObject->ShouldIgnoreThisInput()) + return true; + + //-------------------------------------------------------------------------------------------------- + // Create an input node + //-------------------------------------------------------------------------------------------------- + + HAPI_NodeId ParentNodeId = -1; + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CreatedNodeId)) + { + HAPI_NodeId InputNodeId = -1; + //FString BrushNodeName = NodeName + TEXT("_") + BrushActor->GetName(); + FString BrushNodeName = BrushActor->GetName(); + // Create Brush SOP node + std::string NodeNameRawString; + FHoudiniEngineUtils::ConvertUnrealString(BrushNodeName, NodeNameRawString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + return false; + + // We now have a valid id. + CreatedNodeId = InputNodeId; + ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); + + // Add a clean node + HAPI_NodeId CleanNodeId; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + ParentNodeId, TEXT("clean"), TEXT("clean"), true, &CleanNodeId), false); + + // Connect input node to the clean node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CleanNodeId, 0, CreatedNodeId, 0), false); + + // Set display flag on the clean node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetNodeDisplay( + FHoudiniEngine::Get().GetSession(), CleanNodeId, 1), false); + } + else + { + ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); + } + + + // Transform for positions + const FTransform ActorTransform = BrushActor->GetActorTransform(); + const FTransform ActorTransformInverse = ActorTransform.Inverse(); + // Precompute matrices for Normal transformations (see FPlane::TransformBy) + const FMatrix NmlInvXform = ActorTransformInverse.ToMatrixWithScale().TransposeAdjoint(); + float NScale = ActorTransformInverse.GetDeterminant() < 0 ? -1.f : 1.f; + + //-------------------------------------------------------------------------------------------------- + // Find actors that intersect with the given brush. + //-------------------------------------------------------------------------------------------------- + TArray BrushActors; + UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(InputBrushObject, BrushActors); + + UModel* BrushModel = UHCsgUtils::BuildModelFromBrushes(BrushActors); + InputBrushObject->UpdateCachedData(BrushModel, BrushActors); + + // DEBUG: Upload the level model (baked by UE) to Houdini + // ULevel* Level = BrushActor->GetTypedOuter(); + // BrushModel = Level->Model; + + + int NumPoints = BrushModel->Points.Num(); + if (NumPoints == 0) + { + // The content has changed and now we don't have geo to output. + // Be sure to clean up existing nodes in Houdini. + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), ParentNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *(BrushActor->GetName())); + } + CreatedNodeId = -1; + return true; + } + + //-------------------------------------------------------------------------------------------------- + // Construct the face count buffer. Also count the vertex indices in required to define the Part. + //-------------------------------------------------------------------------------------------------- + + int NumIndices = 0; + TArray FaceCountBuffer; + + { + // Calculate the size of the vertex buffer and the base vertex index of each node. + TArray& Nodes = BrushModel->Nodes; + //TArray& Surfs = BrushModel->Surfs; + TArray& Verts = BrushModel->Verts; + + int32 NumNodes = Nodes.Num(); + + FaceCountBuffer.SetNumUninitialized(NumNodes); + // Build the face counts buffer by iterating over the BSP nodes. + for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) + { + FBspNode& Node = Nodes[NodeIndex]; + FaceCountBuffer[NodeIndex] = Node.NumVertices; + NumIndices += Node.NumVertices; + } + } + + //-------------------------------------------------------------------------------------------------- + // Apply actor transform + //-------------------------------------------------------------------------------------------------- + + if (!ActorTransform.Equals(FTransform::Identity)) + { + + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ActorTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), ParentNodeId, &HapiTransform), false); + } + + //-------------------------------------------------------------------------------------------------- + // Start processing the geo and add it to the input node + //-------------------------------------------------------------------------------------------------- + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumIndices; + Part.faceCount = FaceCountBuffer.Num(); + Part.pointCount = NumPoints; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, &Part), false); + + // ----------------------------- + // Vector - Point Attribute Info + // ----------------------------- + HAPI_AttributeInfo AttributeInfoPointVector; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointVector); + AttributeInfoPointVector.count = NumPoints; + AttributeInfoPointVector.tupleSize = 3; + AttributeInfoPointVector.exists = true; + AttributeInfoPointVector.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointVector.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointVector.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector), false); + + // ----------------------------- + // Vector - Vertex Attribute Info + // ----------------------------- + HAPI_AttributeInfo AttributeInfoVertexVector; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertexVector); + AttributeInfoVertexVector.count = NumIndices; + AttributeInfoVertexVector.tupleSize = 3; + AttributeInfoVertexVector.exists = true; + AttributeInfoVertexVector.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertexVector.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertexVector.originalOwner = HAPI_ATTROWNER_INVALID; + + // ----------------------------- + // POSITION (P) + // ----------------------------- + + { + TArray< FVector > OutPosition; + FVector Scale = FVector(1.f, 1.f, 1.f); // TODO: Extract from actor transform. + OutPosition.SetNumUninitialized(NumPoints); + + for (int32 PosIndex = 0; PosIndex < NumPoints; ++PosIndex) + { + FVector Point = BrushModel->Points[PosIndex]; + Point = ActorTransformInverse.TransformPosition(Point); + FVector Pos(Point.X, Point.Z, Point.Y); + OutPosition[PosIndex] = Pos/HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + // Upload point positions. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector, + (const float *)OutPosition.GetData(), 0, AttributeInfoPointVector.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES (VertexList), NORMALS, UVS + //--------------------------------------------------------------------------------------------------------------------- + // Vertex buffer processing logic based on UModel::BuildVertexBuffers(). + { + // Calculate the size of the vertex buffer and the base vertex index of each node. + TArray& Positions = BrushModel->Points; + TArray& Nodes = BrushModel->Nodes; + TArray& Surfs = BrushModel->Surfs; + TArray& Verts = BrushModel->Verts; + TArray& Vectors = BrushModel->Vectors; + + int32 NumNodes = Nodes.Num(); + TArray Indices; + TArray OutNormals; + TArray OutUV; + TArray MaterialIndices; + TMap MaterialMap; + + Indices.SetNumUninitialized(NumIndices); + OutNormals.SetNumUninitialized(NumIndices); + OutUV.SetNumUninitialized(NumIndices); + + MaterialIndices.SetNumUninitialized(NumNodes); + + // Populate the vertex index buffer. + int32 iVertex = 0; + for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) + { + FBspNode& Node = Nodes[NodeIndex]; + FBspSurf& Surf = Surfs[Node.iSurf]; + for (int32 NodeVertexIndex = 0; NodeVertexIndex < Node.NumVertices; ++NodeVertexIndex) + { + // Vertex Index + Indices[iVertex] = Verts[Node.iVertPool + NodeVertexIndex].pVertex; + // Normal + FVector N = Vectors[Surf.vNormal]; + N = NmlInvXform.TransformVector(N).GetSafeNormal(); + + OutNormals[iVertex] = FVector(N.X, N.Z, N.Y); + // UVs + FVector& vU = Vectors[Surf.vTextureU]; + FVector& vV = Vectors[Surf.vTextureV]; + FVector deltaVtx = (Positions[Indices[iVertex]] - Positions[Surf.pBase]); + float U = FVector::DotProduct(deltaVtx, vU) / UModel::GetGlobalBSPTexelScale(); + float V = -FVector::DotProduct(deltaVtx, vV) / UModel::GetGlobalBSPTexelScale(); + OutUV[iVertex] = FVector(U, V, 0.f); + ++iVertex; + } + // Face Material + // Construct a material index array for the faces + int32 MaterialIndex = MaterialMap.FindOrAdd(Surf.Material, MaterialMap.Num()); + MaterialIndices[NodeIndex] = MaterialIndex; + } + + // Set the vertex index buffer + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, Indices.GetData(), 0, NumIndices), false); + + // Set the face counts as per the BSP nodes. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, FaceCountBuffer.GetData(), 0, FaceCountBuffer.Num()), false); + + // ----------------------------- + // Normal attribute + // ----------------------------- + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertexVector), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertexVector, (const float *)OutNormals.GetData(), + 0, AttributeInfoVertexVector.count), false); + + // ----------------------------- + // UV attribute + // ----------------------------- + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_UV, &AttributeInfoVertexVector), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_UV, + &AttributeInfoVertexVector, (const float *)OutUV.GetData(), + 0, AttributeInfoVertexVector.count), false); + + // ----------------------------- + // Material attribute + // ----------------------------- + + TArray Materials; + + if (MaterialMap.Num() > 0) + { + // Sort the output material in the correct order (by material index) + MaterialMap.ValueSort([](int32 A, int32 B) { return A < B; }); + + // Set the value in the correct order + // Do not reduce the array of materials, this could cause crahses in some weird cases.. + /*if (MaterialMap.Num() > MaterialInterfaces.Num()) + MaterialInterfaces.SetNumZeroed(MaterialMap.Num());*/ + Materials.SetNumUninitialized(MaterialMap.Num()); + + int32 MaterialIndex = 0; + for (auto Kvp : MaterialMap) + { + Materials[MaterialIndex++] = Kvp.Key; + } + } + + // List of materials, one for each face. + TArray OutMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + Materials, MaterialIndices, OutMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + CreatedNodeId, + 0, + NumNodes, + OutMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + Materials, MaterialIndices, OutMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + CreatedNodeId, + 0, + NumNodes, + OutMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(OutMaterials); + + // Delete texture material parameter names. + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedNodeId), false ); + + + return true; +} + diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.h b/Source/HoudiniEngine/Private/UnrealBrushTranslator.h index b47f27d28..1d285570f 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.h @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "UObject/NameTypes.h" - -class ABrush; -class AActor; -class UModel; -class UHoudiniInputBrush; -class ABrush; -class AActor; - -struct HOUDINIENGINE_API FUnrealBrushTranslator -{ -public: - static bool CreateInputNodeForBrush( - UHoudiniInputBrush* InputBrushObject, - ABrush* BrushActor, - const TArray* ExcludeActors, - HAPI_NodeId &CreatedNodeId, - const FString& NodeName - ); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "UObject/NameTypes.h" + +class ABrush; +class AActor; +class UModel; +class UHoudiniInputBrush; +class ABrush; +class AActor; + +struct HOUDINIENGINE_API FUnrealBrushTranslator +{ +public: + static bool CreateInputNodeForBrush( + UHoudiniInputBrush* InputBrushObject, + ABrush* BrushActor, + const TArray* ExcludeActors, + HAPI_NodeId &CreatedNodeId, + const FString& NodeName + ); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp index 8a06f1276..3857d0416 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp @@ -1,212 +1,212 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealInstanceTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "UnrealMeshTranslator.h" - -#include "Engine/StaticMesh.h" -#include "Components/InstancedStaticMeshComponent.h" - -bool -FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( - UInstancedStaticMeshComponent* ISMC, - const FString& InNodeName, - HAPI_NodeId& OutCreatedNodeId, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bExportAsAttributeInstancer) -{ - int32 InstanceCount = ISMC->GetInstanceCount(); - if (InstanceCount < 1) - return true; - - // Get the Static Mesh instanced by the component - UStaticMesh* SM = ISMC->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - // Marshall the Static Mesh to Houdini - int32 SMNodeId = -1; - FString ISMCName = InNodeName + TEXT("_") + ISMC->GetName(); - bool bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, SMNodeId, InNodeName, ISMC, bExportLODs, bExportSockets, bExportColliders); - - if (!bSuccess) - return false; - - // To create the instance properly (via packed prim), we need to: - // - create a copytopoints (with pack and instance enable - // - an inputnode containing all of the instances transform as points - // - plug the input node and the static mesh node in the copytopoints - - // Create the copytopoints SOP. - int32 CopyNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/copytopoints"), InNodeName, true, &CopyNodeId), false); - - // set "Pack And Instance" (pack) to true - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CopyNodeId, "pack", 0, 1), false); - - // Get the copytopoints parent OBJ NodeID - HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CopyNodeId); - - // Now create an input node for the instance transforms - int32 InstancesNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - ParentNodeId, TEXT("null"), "instances", false, &InstancesNodeId), false); - - // MARSHALL THE INSTANCE TRANSFORM - { - // Get the instance transform and convert them to Position/Rotation/Scale array - TArray Positions; - Positions.SetNumZeroed(InstanceCount * 3); - TArray Rotations; - Rotations.SetNumZeroed(InstanceCount * 4); - TArray Scales; - Scales.SetNumZeroed(InstanceCount * 3); - for (int32 InstanceIdx = 0; InstanceIdx < InstanceCount; InstanceIdx++) - { - FTransform CurTransform; - ISMC->GetInstanceTransform(InstanceIdx, CurTransform); - - // Convert Unreal Position to Houdini - FVector PositionVector = CurTransform.GetLocation(); - Positions[InstanceIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Positions[InstanceIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Positions[InstanceIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - - // Convert Unreal Rotation to Houdini - FQuat RotationQuaternion = CurTransform.GetRotation(); - Rotations[InstanceIdx * 4 + 0] = RotationQuaternion.X; - Rotations[InstanceIdx * 4 + 1] = RotationQuaternion.Z; - Rotations[InstanceIdx * 4 + 2] = RotationQuaternion.Y; - Rotations[InstanceIdx * 4 + 3] = -RotationQuaternion.W; - - // Convert Unreal Scale to Houdini - FVector ScaleVector = CurTransform.GetScale3D(); - Scales[InstanceIdx * 3 + 0] = ScaleVector.X; - Scales[InstanceIdx * 3 + 1] = ScaleVector.Z; - Scales[InstanceIdx * 3 + 2] = ScaleVector.Y; - } - - // Create a part for the instance points. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = InstanceCount; - Part.type = HAPI_PARTTYPE_MESH; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InstancesNodeId, 0, &Part), false); - - // Create position (P) attribute - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = InstanceCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Positions.GetData(), 0, AttributeInfoPoint.count), false); - - // Create Rotation (rot) attribute - HAPI_AttributeInfo AttributeInfoRotation; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); - AttributeInfoRotation.count = InstanceCount; - AttributeInfoRotation.tupleSize = 4; - AttributeInfoRotation.exists = true; - AttributeInfoRotation.owner = HAPI_ATTROWNER_POINT; - AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRotation.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation, - Rotations.GetData(), 0, AttributeInfoRotation.count), false); - - // Create scale attribute - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = InstanceCount; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, - Scales.GetData(), 0, AttributeInfoScale.count), false); - - // Commit the instance point geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InstancesNodeId), false); - } - - // Connect the mesh to the copytopoints node's second input - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CopyNodeId, 0, SMNodeId, 0), false); - - // Connect the instances to the copytopoints node's second input - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CopyNodeId, 1, InstancesNodeId, 0), false); - - // Update this input object's node IDs - OutCreatedNodeId = CopyNodeId; - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealInstanceTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "UnrealMeshTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Components/InstancedStaticMeshComponent.h" + +bool +FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( + UInstancedStaticMeshComponent* ISMC, + const FString& InNodeName, + HAPI_NodeId& OutCreatedNodeId, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bExportAsAttributeInstancer) +{ + int32 InstanceCount = ISMC->GetInstanceCount(); + if (InstanceCount < 1) + return true; + + // Get the Static Mesh instanced by the component + UStaticMesh* SM = ISMC->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + // Marshall the Static Mesh to Houdini + int32 SMNodeId = -1; + FString ISMCName = InNodeName + TEXT("_") + ISMC->GetName(); + bool bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, SMNodeId, InNodeName, ISMC, bExportLODs, bExportSockets, bExportColliders); + + if (!bSuccess) + return false; + + // To create the instance properly (via packed prim), we need to: + // - create a copytopoints (with pack and instance enable + // - an inputnode containing all of the instances transform as points + // - plug the input node and the static mesh node in the copytopoints + + // Create the copytopoints SOP. + int32 CopyNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/copytopoints"), InNodeName, true, &CopyNodeId), false); + + // set "Pack And Instance" (pack) to true + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CopyNodeId, "pack", 0, 1), false); + + // Get the copytopoints parent OBJ NodeID + HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CopyNodeId); + + // Now create an input node for the instance transforms + int32 InstancesNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + ParentNodeId, TEXT("null"), "instances", false, &InstancesNodeId), false); + + // MARSHALL THE INSTANCE TRANSFORM + { + // Get the instance transform and convert them to Position/Rotation/Scale array + TArray Positions; + Positions.SetNumZeroed(InstanceCount * 3); + TArray Rotations; + Rotations.SetNumZeroed(InstanceCount * 4); + TArray Scales; + Scales.SetNumZeroed(InstanceCount * 3); + for (int32 InstanceIdx = 0; InstanceIdx < InstanceCount; InstanceIdx++) + { + FTransform CurTransform; + ISMC->GetInstanceTransform(InstanceIdx, CurTransform); + + // Convert Unreal Position to Houdini + FVector PositionVector = CurTransform.GetLocation(); + Positions[InstanceIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Positions[InstanceIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Positions[InstanceIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + + // Convert Unreal Rotation to Houdini + FQuat RotationQuaternion = CurTransform.GetRotation(); + Rotations[InstanceIdx * 4 + 0] = RotationQuaternion.X; + Rotations[InstanceIdx * 4 + 1] = RotationQuaternion.Z; + Rotations[InstanceIdx * 4 + 2] = RotationQuaternion.Y; + Rotations[InstanceIdx * 4 + 3] = -RotationQuaternion.W; + + // Convert Unreal Scale to Houdini + FVector ScaleVector = CurTransform.GetScale3D(); + Scales[InstanceIdx * 3 + 0] = ScaleVector.X; + Scales[InstanceIdx * 3 + 1] = ScaleVector.Z; + Scales[InstanceIdx * 3 + 2] = ScaleVector.Y; + } + + // Create a part for the instance points. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = InstanceCount; + Part.type = HAPI_PARTTYPE_MESH; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InstancesNodeId, 0, &Part), false); + + // Create position (P) attribute + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = InstanceCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Positions.GetData(), 0, AttributeInfoPoint.count), false); + + // Create Rotation (rot) attribute + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + AttributeInfoRotation.count = InstanceCount; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation, + Rotations.GetData(), 0, AttributeInfoRotation.count), false); + + // Create scale attribute + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = InstanceCount; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, + Scales.GetData(), 0, AttributeInfoScale.count), false); + + // Commit the instance point geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InstancesNodeId), false); + } + + // Connect the mesh to the copytopoints node's second input + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CopyNodeId, 0, SMNodeId, 0), false); + + // Connect the instances to the copytopoints node's second input + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CopyNodeId, 1, InstancesNodeId, 0), false); + + // Update this input object's node IDs + OutCreatedNodeId = CopyNodeId; + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h index 9ef5cbb1a..c7a6a5ef9 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h @@ -1,49 +1,49 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -class UInstancedStaticMeshComponent; - -struct HOUDINIENGINE_API FUnrealInstanceTranslator -{ - public: - - // HAPI : Marshaling, extract geometry and create input asset for it - return true on success - static bool HapiCreateInputNodeForInstancer( - UInstancedStaticMeshComponent* ISMC, - const FString& InNodeName, - HAPI_NodeId& OutCreatedNodeId, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bExportAsAttributeInstancer); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +class UInstancedStaticMeshComponent; + +struct HOUDINIENGINE_API FUnrealInstanceTranslator +{ + public: + + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForInstancer( + UInstancedStaticMeshComponent* ISMC, + const FString& InNodeName, + HAPI_NodeId& OutCreatedNodeId, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bExportAsAttributeInstancer); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp index f14c42665..791526a38 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp @@ -1,1990 +1,1991 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#include "HoudiniApi.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" - -#include "UnrealLandscapeTranslator.h" -#include "HoudiniGeoPartObject.h" - -#include "Landscape.h" -#include "LandscapeDataAccess.h" -#include "LandscapeEdit.h" -#include "LightMap.h" -#include "Engine/MapBuildDataRegistry.h" -#include "PhysicalMaterials/PhysicalMaterial.h" - - -bool -FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( - ALandscapeProxy* LandscapeProxy, - HAPI_NodeId& CreatedNodeId, - const FString& InputNodeNameString, - const bool& bExportGeometryAsMesh, - const bool& bExportTileUVs, - const bool bExportNormalizedUVs, - const bool bExportLighting, - const bool bExportMaterials ) -{ - //-------------------------------------------------------------------------------------------------- - // 1. Create an input node - //-------------------------------------------------------------------------------------------------- - HAPI_NodeId InputNodeId = -1; - // Create the curve SOP Node - std::string NodeNameRawString; - FHoudiniEngineUtils::ConvertUnrealString(InputNodeNameString, NodeNameRawString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - return false; - - // We now have a valid id. - CreatedNodeId = InputNodeId; - - if(!FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true)) - return false; - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); - */ - //-------------------------------------------------------------------------------------------------- - // 2. Set the part info - //-------------------------------------------------------------------------------------------------- - int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; - float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; - - //int32 NumComponents = bExportOnlySelected ? SelectedComponents.Num() : LandscapeProxy->LandscapeComponents.Num(); - int32 NumComponents = LandscapeProxy->LandscapeComponents.Num(); - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - int32 VertexCount = NumComponents * VertexCountPerComponent; - if (!VertexCount) - return false; - - int32 TriangleCount = NumComponents * FMath::Square(ComponentSizeQuads) * 2; - int32 QuadCount = NumComponents * FMath::Square(ComponentSizeQuads); - int32 IndexCount = QuadCount * 4; - - // Create part info - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - //FMemory::Memzero< HAPI_PartInfo >(Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = VertexCount; - Part.type = HAPI_PARTTYPE_MESH; - - // If we are exporting to a mesh, we need vertices and faces - if (bExportGeometryAsMesh) - { - Part.vertexCount = IndexCount; - Part.faceCount = QuadCount; - } - - // Set the part infos - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, &DisplayGeoInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part), false); - - //-------------------------------------------------------------------------------------------------- - // 3. Extract the landscape data - //-------------------------------------------------------------------------------------------------- - // Array for the position data - TArray LandscapePositionArray; - // Array for the normals - TArray LandscapeNormalArray; - // Array for the UVs - TArray LandscapeUVArray; - // Array for the vertex index of each point in its component - TArray LandscapeComponentVertexIndicesArray; - // Array for the tile names per point - TArray LandscapeComponentNameArray; - // Array for the lightmap values - TArray LandscapeLightmapValues; - // Selected components set to all components in current landscape proxy - TSet SelectedComponents; - SelectedComponents.Append(LandscapeProxy->LandscapeComponents); - - // Extract all the data from the landscape to the arrays - if (!ExtractLandscapeData( - LandscapeProxy, SelectedComponents, - bExportLighting, bExportTileUVs, bExportNormalizedUVs, - LandscapePositionArray, LandscapeNormalArray, - LandscapeUVArray, LandscapeComponentVertexIndicesArray, - LandscapeComponentNameArray, LandscapeLightmapValues)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 3. Set the corresponding attributes in Houdini - //-------------------------------------------------------------------------------------------------- - - // Create point attribute info containing positions. - if (!AddLandscapePositionAttribute(DisplayGeoInfo.nodeId, LandscapePositionArray)) - return false; - - // Create point attribute info containing normals. - if (!AddLandscapeNormalAttribute(DisplayGeoInfo.nodeId, LandscapeNormalArray)) - return false; - - // Create point attribute info containing UVs. - if (!AddLandscapeUVAttribute(DisplayGeoInfo.nodeId, LandscapeUVArray)) - return false; - - // Create point attribute containing landscape component vertex indices (indices of vertices within the grid - x,y). - if (!AddLandscapeComponentVertexIndicesAttribute(DisplayGeoInfo.nodeId, LandscapeComponentVertexIndicesArray)) - return false; - - // Create point attribute containing landscape component name. - if (!AddLandscapeComponentNameAttribute(DisplayGeoInfo.nodeId, LandscapeComponentNameArray)) - return false; - - // Create point attribute info containing lightmap information. - if (bExportLighting) - { - if (!AddLandscapeLightmapColorAttribute(DisplayGeoInfo.nodeId, LandscapeLightmapValues)) - return false; - } - - // Set indices if we are exporting full geometry. - if (bExportGeometryAsMesh) - { - if (!AddLandscapeMeshIndicesAndMaterialsAttribute( - DisplayGeoInfo.nodeId, - bExportMaterials, - ComponentSizeQuads, - QuadCount, - LandscapeProxy, - SelectedComponents)) - return false; - } - - // If we are marshalling material information. - if (bExportMaterials) - { - if (!AddLandscapeGlobalMaterialAttribute(DisplayGeoInfo.nodeId, LandscapeProxy)) - return false; - } - - /* - // TODO: Move this to ExtractLandscapeData() - //-------------------------------------------------------------------------------------------------- - // 4. Extract and convert all the layers - //-------------------------------------------------------------------------------------------------- - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Get the landscape X/Y Size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - return false; - - // Calc the X/Y size in points - int32 XSize = (MaxX - MinX + 1); - int32 YSize = (MaxY - MinY + 1); - if ((XSize < 2) || (YSize < 2)) - return false; - - bool MaskInitialized = false; - int32 NumLayers = LandscapeInfo->Layers.Num(); - for (int32 n = 0; n < NumLayers; n++) - { - // 1. Extract the uint8 values from the layer - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!GetLandscapeLayerData( - LandscapeInfo, n, - MinX, MinY, MaxX, MaxY, - CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - // 2. Convert unreal uint8 values to floats - // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float - HAPI_VolumeInfo CurrentLayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); - TArray CurrentLayerFloatData; - if (!ConvertLandscapeLayerDataToHeightfieldData( - CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, - CurrentLayerFloatData, CurrentLayerVolumeInfo)) - continue; - - if (!AddLandscapeLayerAttribute( - DisplayGeoInfo.nodeId, CurrentLayerFloatData, LayerName)) - continue; - } - */ - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId), false); - - // TODO: Remove me! - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); - */ - - return FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true); -} - -bool -FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( - ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId, const FString& InputNodeNameStr) -{ - if (!LandscapeProxy) - return false; - - // Export the whole landscape and its layer as a single heightfield. - - //-------------------------------------------------------------------------------------------------- - // 1. Extracting the height data - //-------------------------------------------------------------------------------------------------- - TArray HeightData; - int32 XSize, YSize; - FVector Min, Max; - if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 2. Convert the height uint16 data to float - //-------------------------------------------------------------------------------------------------- - TArray HeightfieldFloatValues; - HAPI_VolumeInfo HeightfieldVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); - FTransform LandscapeTransform = LandscapeProxy->ActorToWorld(); - FVector CenterOffset = FVector::ZeroVector; - if (!ConvertLandscapeDataToHeightfieldData( - HeightData, XSize, YSize, Min, Max, LandscapeTransform, - HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 3. Create the Heightfield Input Node - //-------------------------------------------------------------------------------------------------- - HAPI_NodeId HeightFieldId = -1; - HAPI_NodeId HeightId = -1; - HAPI_NodeId MaskId = -1; - HAPI_NodeId MergeId = -1; - if (!CreateHeightfieldInputNode(InputNodeNameStr, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 4. Set the HeightfieldData in Houdini - //-------------------------------------------------------------------------------------------------- - // Set the Height volume's data - HAPI_PartId PartId = 0; - if (!SetHeighfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) - return false; - - // Add the materials used - UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); - UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); - UPhysicalMaterial* LandscapePhysMat = LandscapeProxy->DefaultPhysMaterial; - AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(HeightId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(HeightId, PartId, LandscapeProxy, 1); - - // Add the unreal_level_path attribute - ULevel* Level = LandscapeProxy->GetLevel(); - if (Level) - { - FHoudiniEngineUtils::AddLevelPathAttribute(HeightId, PartId, Level, 1); - /* - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToVolume(HeightId, PartId, LevelPath); - */ - } - - // Commit the height volume - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), HeightId), false); - - //-------------------------------------------------------------------------------------------------- - // 5. Extract and convert all the layers - //-------------------------------------------------------------------------------------------------- - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - bool MaskInitialized = false; - int32 MergeInputIndex = 2; - int32 NumLayers = LandscapeInfo->Layers.Num(); - for (int32 n = 0; n < NumLayers; n++) - { - // 1. Extract the uint8 values from the layer - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!GetLandscapeLayerData(LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - // 2. Convert unreal uint8 values to floats - // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float - HAPI_VolumeInfo CurrentLayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); - TArray CurrentLayerFloatData; - if (!ConvertLandscapeLayerDataToHeightfieldData( - CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, - CurrentLayerFloatData, CurrentLayerVolumeInfo)) - continue; - - // We reuse the height layer's transform - CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; - - // 3. See if we need to create an input volume, or can reuse the HF's default mask volume - bool IsMask = false; - if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) - IsMask = true; - - HAPI_NodeId LayerVolumeNodeId = -1; - if (!IsMask) - { - // Current layer is not mask, so we need to create a new input volume - std::string LayerNameStr; - FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); - - FHoudiniApi::CreateHeightfieldInputVolumeNode( - FHoudiniEngine::Get().GetSession(), - HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); - } - else - { - // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node - LayerVolumeNodeId = MaskId; - } - - // Check if we have a valid id for the input volume. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) - continue; - - // 4. Set the layer/mask heighfield data in Houdini - HAPI_PartId CurrentPartId = 0; - if (!SetHeighfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) - continue; - - // Get the physical material used by that layer - UPhysicalMaterial* LayerPhysicalMat = LandscapePhysMat; - { - FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; - ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; - if (LayerInfo) - LayerPhysicalMat = LayerInfo->PhysMaterial; - } - - // Also add the material attributes to the layer volumes - AddLandscapeMaterialAttributesToVolume(LayerVolumeNodeId, PartId, LandscapeMat, LandscapeHoleMat, LayerPhysicalMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(LayerVolumeNodeId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(LayerVolumeNodeId, PartId, LandscapeProxy, 1); - - // Also add the level path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(LayerVolumeNodeId, PartId, Level, 1); - //AddLevelPathAttributeToVolume(LayerVolumeNodeId, PartId, LevelPath); - - // Commit the volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); - - if (!IsMask) - { - // We had to create a new volume for this layer, so we need to connect it to the HF's merge node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - MergeId, MergeInputIndex, LayerVolumeNodeId, 0), false); - - MergeInputIndex++; - } - else - { - MaskInitialized = true; - } - } - - // We need to have a mask layer as it is required for proper heightfield functionalities - // Setting the volume info on the mask is needed for the HF to have proper transform in H! - // If we didn't create a mask volume before, send a default one now - if (!MaskInitialized) - { - MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); - - // Add the materials used - AddLandscapeMaterialAttributesToVolume(MaskId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(MaskId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(MaskId, PartId, LandscapeProxy, 1); - - // Also add the level path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(MaskId, PartId, Level, 1); - //AddLevelPathAttributeToVolume(MaskId, PartId, LevelPath); - - // Commit the mask volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), MaskId), false); - } - - HAPI_TransformEuler HAPIObjectTransform; - FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); - //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); - LandscapeTransform.SetScale3D(FVector::OneVector); - FHoudiniEngineUtils::TranslateUnrealTransform(LandscapeTransform, HAPIObjectTransform); - HAPIObjectTransform.position[1] = 0.0f; - - HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(HeightFieldId); - FHoudiniApi::SetObjectTransform(FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform); - - // Since HF are centered but landscape aren't, we need to set the HF's center parameter - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); - - // Finally, cook the Heightfield node - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), HeightFieldId, &CookOptions), false); - */ - if(!FHoudiniEngineUtils::HapiCookNode(HeightFieldId, nullptr, true)) - return false; - - CreatedHeightfieldNodeId = HeightFieldId; - - return true; -} - -// Converts Unreal uint16 values to Houdini Float -bool -FUnrealLandscapeTranslator::ConvertLandscapeLayerDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - const FLinearColor& LayerUsageDebugColor, - TArray& LayerFloatValues, - HAPI_VolumeInfo& LayerVolumeInfo) -{ - LayerFloatValues.Empty(); - - int32 HoudiniXSize = YSize; - int32 HoudiniYSize = XSize; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - if (IntHeightData.Num() != SizeInPoints) - return false; - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to float - //-------------------------------------------------------------------------------------------------- - - // By default, values are converted from unreal [0 255] uint8 to Houdini [0 1] float - // uint8 min/max - uint8 IntMin = 0; - uint8 IntMax = UINT8_MAX; - // The range in Digits - double DigitRange = (double)UINT8_MAX; - - // By default, the values will be converted to [0, 1] - float LayerMin = 0.0f; - float LayerMax = 1.0f; - float LayerSpacing = 1.0f / DigitRange; - - // If this layer came from Houdini, its alpha value should be PI - // This indicates that we can extract additional infos stored its debug usage color - // so we can reconstruct the original source values (float) more accurately - if (LayerUsageDebugColor.A == PI) - { - // We need the ZMin / ZMax uint8 values - IntMin = IntHeightData[0]; - IntMax = IntMin; - for (int n = 0; n < IntHeightData.Num(); n++) - { - if (IntHeightData[n] < IntMin) - IntMin = IntHeightData[n]; - if (IntHeightData[n] > IntMax) - IntMax = IntHeightData[n]; - } - - DigitRange = (double)IntMax - (double)IntMin; - - // Read the original min/max and spacing stored in the debug color - LayerMin = LayerUsageDebugColor.R; - LayerMax = LayerUsageDebugColor.G; - LayerSpacing = LayerUsageDebugColor.B; - } - - // Convert the Int data to Float - LayerFloatValues.SetNumUninitialized(SizeInPoints); - - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // We need to invert X/Y when reading the value from Unreal - int32 nHoudini = nX + nY * HoudiniXSize; - int32 nUnreal = nY + nX * XSize; - - // Convert the int values to meter - // Unreal's digit value have a zero value of 32768 - double DoubleValue = ((double)IntHeightData[nUnreal] - (double)IntMin) * LayerSpacing + LayerMin; - LayerFloatValues[nHoudini] = (float)DoubleValue; - } - } - - /* - // Verifying the converted ZMin / ZMax - float FloatMin = LayerFloatValues[0]; - float FloatMax = FloatMin; - for (int32 n = 0; n < LayerFloatValues.Num(); n++) - { - if (LayerFloatValues[n] < FloatMin) - FloatMin = LayerFloatValues[n]; - if (LayerFloatValues[n] > FloatMax) - FloatMax = LayerFloatValues[n]; - } - */ - - //-------------------------------------------------------------------------------------------------- - // 2. Fill the volume info - //-------------------------------------------------------------------------------------------------- - LayerVolumeInfo.xLength = HoudiniXSize; - LayerVolumeInfo.yLength = HoudiniYSize; - LayerVolumeInfo.zLength = 1; - - LayerVolumeInfo.minX = 0; - LayerVolumeInfo.minY = 0; - LayerVolumeInfo.minZ = 0; - - LayerVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; - LayerVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; - LayerVolumeInfo.tupleSize = 1; - LayerVolumeInfo.tileSize = 1; - - LayerVolumeInfo.hasTaper = false; - LayerVolumeInfo.xTaper = 0.0; - LayerVolumeInfo.yTaper = 0.0; - - // The layer transform will have to be copied from the main heightfield's transform - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeData( - ALandscapeProxy* LandscapeProxy, - TArray& HeightData, - int32& XSize, int32& YSize, - FVector& Min, FVector& Max) -{ - if (!LandscapeProxy) - return false; - - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Get the landscape extents to get its size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - - // To handle streaming proxies correctly, get the extents via all the components, - // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. - for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) - { - Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); - } - - if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize)) - return false; - - // Get the landscape Min/Max values - // Do not use Landscape->GetActorBounds() here as instanced geo - // (due to grass layers for example) can cause it to return incorrect bounds! - FVector Origin, Extent; - GetLandscapeProxyBounds(LandscapeProxy, Origin, Extent); - - // Get the landscape Min/Max values - Min = Origin - Extent; - Max = Origin + Extent; - - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeData( - ULandscapeInfo* LandscapeInfo, - const int32& MinX, const int32& MinY, - const int32& MaxX, const int32& MaxY, - TArray& HeightData, - int32& XSize, int32& YSize) -{ - if (!LandscapeInfo) - return false; - - // Get the X/Y size in points - XSize = (MaxX - MinX + 1); - YSize = (MaxY - MinY + 1); - - if ((XSize < 2) || (YSize < 2)) - return false; - - // Extracting the uint16 values from the landscape - FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); - HeightData.AddZeroed(XSize * YSize); - LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0); - - return true; -} - - -void -FUnrealLandscapeTranslator::GetLandscapeProxyBounds( - ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents) -{ - // Iterate only on the landscape components - FBox Bounds(ForceInit); - for (const UActorComponent* ActorComponent : LandscapeProxy->GetComponents()) - { - const ULandscapeComponent* LandscapeComp = Cast(ActorComponent); - if (LandscapeComp && LandscapeComp->IsRegistered()) - Bounds += LandscapeComp->Bounds.GetBox(); - } - - // Convert the bounds to origin/offset vectors - Bounds.GetCenterAndExtents(Origin, Extents); -} - -bool -FUnrealLandscapeTranslator::ConvertLandscapeDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - FVector Min, FVector Max, - const FTransform& LandscapeTransform, - TArray& HeightfieldFloatValues, - HAPI_VolumeInfo& HeightfieldVolumeInfo, - FVector& CenterOffset) -{ - HeightfieldFloatValues.Empty(); - - int32 HoudiniXSize = YSize; - int32 HoudiniYSize = XSize; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - if (IntHeightData.Num() != SizeInPoints) - return false; - - // Use default unreal scaling for marshalling landscapes - // A lot of precision will be lost in order to keep the same transform as the landscape input - bool bUseDefaultUE4Scaling = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) - bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to float - //-------------------------------------------------------------------------------------------------- - - - // Convert the min/max values from cm to meters - Min /= 100.0; - Max /= 100.0; - - // Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0 - // To convert the uint16 values to float "metric" values, offset the int by 32768 to center it, - // then scale it - - // Spacing used to convert from uint16 to meters - double ZSpacing = 512.0 / ((double)UINT16_MAX); - ZSpacing *= ((double)LandscapeTransform.GetScale3D().Z / 100.0); - - // Center value in meters (Landscape ranges from [-255:257] meters at default scale - double ZCenterOffset = 32767; - double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f; - // Convert the Int data to Float - HeightfieldFloatValues.SetNumUninitialized(SizeInPoints); - - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // We need to invert X/Y when reading the value from Unreal - int32 nHoudini = nX + nY * HoudiniXSize; - int32 nUnreal = nY + nX * XSize; - - // Convert the int values to meter - // Unreal's digit value have a zero value of 32768 - double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset; - HeightfieldFloatValues[nHoudini] = (float)DoubleValue; - } - } - - //-------------------------------------------------------------------------------------------------- - // 2. Convert the Unreal Transform to a HAPI_transform - //-------------------------------------------------------------------------------------------------- - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - //FMemory::Memzero< HAPI_Transform >( HapiTransform ); - { - FQuat Rotation = LandscapeTransform.GetRotation(); - if (Rotation != FQuat::Identity) - { - //Swap(ObjectRotation.Y, ObjectRotation.Z); - HapiTransform.rotationQuaternion[0] = Rotation.X; - HapiTransform.rotationQuaternion[1] = Rotation.Z; - HapiTransform.rotationQuaternion[2] = Rotation.Y; - HapiTransform.rotationQuaternion[3] = -Rotation.W; - } - else - { - HapiTransform.rotationQuaternion[0] = 0; - HapiTransform.rotationQuaternion[1] = 0; - HapiTransform.rotationQuaternion[2] = 0; - HapiTransform.rotationQuaternion[3] = 1; - } - - // Heightfield are centered, landscapes are not - CenterOffset = (Max - Min) * 0.5f; - - // Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform) - //FVector Position = LandscapeTransform.GetLocation() / 100.0f; - HapiTransform.position[1] = 0.0f;//Position.X + CenterOffset.X; - HapiTransform.position[0] = 0.0f;//Position.Y + CenterOffset.Y; - HapiTransform.position[2] = 0.0f; - - FVector Scale = LandscapeTransform.GetScale3D() / 100.0f; - HapiTransform.scale[0] = Scale.X * 0.5f * HoudiniXSize; - HapiTransform.scale[1] = Scale.Y * 0.5f * HoudiniYSize; - HapiTransform.scale[2] = 0.5f; - if (bUseDefaultUE4Scaling) - HapiTransform.scale[2] *= Scale.Z; - - HapiTransform.shear[0] = 0.0f; - HapiTransform.shear[1] = 0.0f; - HapiTransform.shear[2] = 0.0f; - } - - //-------------------------------------------------------------------------------------------------- - // 3. Fill the volume info - //-------------------------------------------------------------------------------------------------- - HeightfieldVolumeInfo.xLength = HoudiniXSize; - HeightfieldVolumeInfo.yLength = HoudiniYSize; - HeightfieldVolumeInfo.zLength = 1; - - HeightfieldVolumeInfo.minX = 0; - HeightfieldVolumeInfo.minY = 0; - HeightfieldVolumeInfo.minZ = 0; - - HeightfieldVolumeInfo.transform = HapiTransform; - - HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; - HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; - HeightfieldVolumeInfo.tupleSize = 1; - HeightfieldVolumeInfo.tileSize = 1; - - HeightfieldVolumeInfo.hasTaper = false; - HeightfieldVolumeInfo.xTaper = 0.0; - HeightfieldVolumeInfo.yTaper = 0.0; - - return true; -} - -bool -FUnrealLandscapeTranslator::CreateHeightfieldInputNode( - const FString& NodeName, - const int32& XSize, - const int32& YSize, - HAPI_NodeId& HeightfieldNodeId, - HAPI_NodeId& HeightNodeId, - HAPI_NodeId& MaskNodeId, - HAPI_NodeId& MergeNodeId) -{ - // Make sure the Heightfield node doesnt already exists - if (HeightfieldNodeId != -1) - return false; - - // Convert the node's name - std::string NameStr; - FHoudiniEngineUtils::ConvertUnrealString(NodeName, NameStr); - - // Create the heigthfield node via HAPI - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightFieldInput( - FHoudiniEngine::Get().GetSession(), - -1, NameStr.c_str(), XSize, YSize, 1.0f, HAPI_HeightFieldSampling::HAPI_HEIGHTFIELD_SAMPLING_CORNER, - &HeightfieldNodeId, &HeightNodeId, &MaskNodeId, &MergeNodeId), false); - - // Cook it - return FHoudiniEngineUtils::HapiCookNode(HeightfieldNodeId, nullptr, true); - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), HeightfieldNodeId, &CookOptions), false); - - return true; - */ -} - -bool -FUnrealLandscapeTranslator::SetHeighfieldData( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - TArray& FloatValues, - const HAPI_VolumeInfo& VolumeInfo, - const FString& HeightfieldName) -{ - // Cook the node to get proper infos on it - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, &CookOptions), false); - */ - if(!FHoudiniEngineUtils::HapiCookNode(VolumeNodeId, nullptr, true)) - return false; - - // Read the geo/part/volume info from the volume node - HAPI_GeoInfo GeoInfo; - FHoudiniApi::GeoInfo_Init(&GeoInfo); - //FMemory::Memset< HAPI_GeoInfo >(GeoInfo, 0); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, &GeoInfo), false); - - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - //FMemory::Memset< HAPI_PartInfo >(PartInfo, 0); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), - GeoInfo.nodeId, PartId, &PartInfo), false); - - // Update the volume infos - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, PartInfo.id, &VolumeInfo), false); - - // Volume name - std::string NameStr; - FHoudiniEngineUtils::ConvertUnrealString(HeightfieldName, NameStr); - - // Set the Heighfield data on the volume - float * HeightData = FloatValues.GetData(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetHeightFieldData( - FHoudiniEngine::Get().GetSession(), - GeoInfo.nodeId, PartInfo.id, NameStr.c_str(), HeightData, 0, FloatValues.Num()), false); - - return true; -} - -bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - UMaterialInterface* InLandscapeMaterial, - UMaterialInterface* InLandscapeHoleMaterial, - UPhysicalMaterial* InPhysicalMaterial) -{ - if (VolumeNodeId == -1) - return false; - - // LANDSCAPE MATERIAL - if (InLandscapeMaterial && !InLandscapeMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InLandscapeMaterialString = InLandscapeMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - // HOLE MATERIAL - if (InLandscapeHoleMaterial && !InLandscapeHoleMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InLandscapeMaterialString = InLandscapeHoleMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (Result == HAPI_RESULT_SUCCESS) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_hole_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - // PHYSICAL MATERIAL - if (InPhysicalMaterial && !InPhysicalMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InPhysMatlString = InPhysicalMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (Result == HAPI_RESULT_SUCCESS) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InPhysMatlString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_physical_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - return true; -} - -/* -bool -FUnrealLandscapeTranslator::AddLevelPathAttributeToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - const FString& LevelPath) -{ - if (VolumeNodeId == -1) - return false; - - // LANDSCAPE MATERIAL - if (LevelPath.IsEmpty()) - return false; - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = 1; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to cont char * - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray LevelPathArr; - LevelPathArr.Add(LevelPathCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - LevelPathArr.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} -*/ - -bool -FUnrealLandscapeTranslator::GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, const int32& LayerIndex, - TArray& LayerData, FLinearColor& LayerUsageDebugColor, - FString& LayerName) -{ - if (!LandscapeInfo) - return false; - - // Get the landscape X/Y Size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - return false; - - if (!GetLandscapeLayerData( - LandscapeInfo, LayerIndex, - MinX, MinY, MaxX, MaxY, - LayerData, LayerUsageDebugColor, LayerName)) - return false; - - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - const int32& MinX, const int32& MinY, - const int32& MaxX, const int32& MaxY, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName) -{ - if (!LandscapeInfo) - return false; - - if (!LandscapeInfo->Layers.IsValidIndex(LayerIndex)) - return false; - - FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[LayerIndex]; - ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; - if (!LayerInfo) - return false; - - // Calc the X/Y size in points - int32 XSize = (MaxX - MinX + 1); - int32 YSize = (MaxY - MinY + 1); - if ((XSize < 2) || (YSize < 2)) - return false; - - // extracting the uint8 values from the layer - FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); - LayerData.AddZeroed(XSize * YSize); - LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, LayerData.GetData(), 0); - - LayerUsageDebugColor = LayerInfo->LayerUsageDebugColor; - - LayerName = LayersSetting.GetLayerName().ToString(); - - return true; -} - -bool -FUnrealLandscapeTranslator::InitDefaultHeightfieldMask( - const HAPI_VolumeInfo& HeightVolumeInfo, - const HAPI_NodeId& MaskVolumeNodeId) -{ - // We need to have a mask layer as it is required for proper heightfield functionalities - - // Creating an array filled with 0.0 - TArray< float > MaskFloatData; - MaskFloatData.Init(0.0f, HeightVolumeInfo.xLength * HeightVolumeInfo.yLength); - - // Creating the volume infos - HAPI_VolumeInfo MaskVolumeInfo = HeightVolumeInfo; - - // Set the heighfield data in Houdini - FString MaskName = TEXT("mask"); - HAPI_PartId PartId = 0; - if (!SetHeighfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) - return false; - - return true; -} - -bool -FUnrealLandscapeTranslator::DestroyLandscapeAssetNode(HAPI_NodeId& ConnectedAssetId, TArray& CreatedInputAssetIds) -{ - HAPI_AssetInfo NodeAssetInfo; - FHoudiniApi::AssetInfo_Init(&NodeAssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeAssetInfo), false); - - FHoudiniEngineString AssetOpName(NodeAssetInfo.fullOpNameSH); - FString OpName; - if (!AssetOpName.ToFString(OpName)) - return false; - - if (!OpName.Contains(TEXT("xform"))) - { - // Not a transform node, so not a Heightfield - // We just need to destroy the landscape asset node - return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); - } - - // The landscape was marshalled as a heightfield, so we need to destroy and disconnect - // the volvis nodes, all the merge node's input (each merge input is a volume for one - // of the layer/mask of the landscape ) - - // Query the volvis node id - // The volvis node is the fist input of the xform node - HAPI_NodeId VolvisNodeId = -1; - FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ConnectedAssetId, 0, &VolvisNodeId); - - // First, destroy the merge node and its inputs - // The merge node is in the first input of the volvis node - HAPI_NodeId MergeNodeId = -1; - FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - VolvisNodeId, 0, &MergeNodeId); - - if (MergeNodeId != -1) - { - // Get the merge node info - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), MergeNodeId, &NodeInfo), false); - - for (int32 n = 0; n < NodeInfo.inputCount; n++) - { - // Get the Input node ID from the host ID - HAPI_NodeId InputNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - MergeNodeId, n, &InputNodeId)) - break; - - if (InputNodeId == -1) - break; - - // Disconnect and Destroy that input - FHoudiniEngineUtils::HapiDisconnectAsset(MergeNodeId, n); - FHoudiniEngineUtils::DestroyHoudiniAsset(InputNodeId); - } - } - - // Second step, destroy all the volumes GEO assets - for (HAPI_NodeId AssetNodeId : CreatedInputAssetIds) - { - FHoudiniEngineUtils::DestroyHoudiniAsset(AssetNodeId); - } - CreatedInputAssetIds.Empty(); - - // Finally disconnect and destroy the xform, volvis and merge nodes, then destroy them - FHoudiniEngineUtils::HapiDisconnectAsset(ConnectedAssetId, 0); - FHoudiniEngineUtils::HapiDisconnectAsset(VolvisNodeId, 0); - FHoudiniEngineUtils::DestroyHoudiniAsset(MergeNodeId); - FHoudiniEngineUtils::DestroyHoudiniAsset(VolvisNodeId); - - return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); -} - - -bool -FUnrealLandscapeTranslator::ExtractLandscapeData( - ALandscapeProxy * LandscapeProxy, TSet& SelectedComponents, - const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, - TArray& LandscapePositionArray, - TArray& LandscapeNormalArray, - TArray& LandscapeUVArray, - TArray& LandscapeComponentVertexIndicesArray, - TArray& LandscapeComponentNameArray, - TArray& LandscapeLightmapValues) -{ - if (!LandscapeProxy) - return false; - - if (SelectedComponents.Num() < 1) - return false; - - // Get runtime settings. - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Calc all the needed sizes - int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; - float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; - - int32 NumComponents = SelectedComponents.Num(); - bool bExportOnlySelected = NumComponents != LandscapeProxy->LandscapeComponents.Num(); - - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - int32 VertexCount = NumComponents * VertexCountPerComponent; - if (!VertexCount) - return false; - - // Initialize the data arrays - LandscapePositionArray.SetNumUninitialized(VertexCount); - LandscapeNormalArray.SetNumUninitialized(VertexCount); - LandscapeUVArray.SetNumUninitialized(VertexCount); - LandscapeComponentNameArray.SetNumUninitialized(VertexCount); - LandscapeComponentVertexIndicesArray.SetNumUninitialized(VertexCount); - if (bExportLighting) - LandscapeLightmapValues.SetNumUninitialized(VertexCount); - - //----------------------------------------------------------------------------------------------------------------- - // EXTRACT THE LANDSCAPE DATA - //----------------------------------------------------------------------------------------------------------------- - FIntPoint IntPointMax = FIntPoint::ZeroValue; - - int32 AllPositionsIdx = 0; - for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) - { - ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; - if (bExportOnlySelected && !SelectedComponents.Contains(LandscapeComponent)) - continue; - - TArray64< uint8 > LightmapMipData; - int32 LightmapMipSizeX = 0; - int32 LightmapMipSizeY = 0; - - // See if we need to export lighting information. - if (bExportLighting) - { - const FMeshMapBuildData* MapBuildData = LandscapeComponent->GetMeshMapBuildData(); - FLightMap2D* LightMap2D = MapBuildData && MapBuildData->LightMap ? MapBuildData->LightMap->GetLightMap2D() : nullptr; - if (LightMap2D && LightMap2D->IsValid(0)) - { - UTexture2D * TextureLightmap = LightMap2D->GetTexture(0); - if (TextureLightmap) - { - if (TextureLightmap->Source.GetMipData(LightmapMipData, 0, 0, 0, nullptr)) - { - LightmapMipSizeX = TextureLightmap->Source.GetSizeX(); - LightmapMipSizeY = TextureLightmap->Source.GetSizeY(); - } - else - { - LightmapMipData.Empty(); - } - } - } - } - - // Construct landscape component data interface to access raw data. - FLandscapeComponentDataInterface CDI(LandscapeComponent, LandscapeProxy->ExportLOD); - - // Get name of this landscape component. - const char * LandscapeComponentNameStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->GetName()); - for (int32 VertexIdx = 0; VertexIdx < VertexCountPerComponent; VertexIdx++) - { - int32 VertX = 0; - int32 VertY = 0; - CDI.VertexIndexToXY(VertexIdx, VertX, VertY); - - // Get position. - FVector PositionVector = CDI.GetWorldVertex(VertX, VertY); - - // Get normal / tangent / binormal. - FVector Normal = FVector::ZeroVector; - FVector TangentX = FVector::ZeroVector; - FVector TangentY = FVector::ZeroVector; - CDI.GetLocalTangentVectors(VertX, VertY, TangentX, TangentY, Normal); - - // Export UVs. - FVector TextureUV = FVector::ZeroVector; - if (bExportTileUVs) - { - // We want to export uvs per tile. - TextureUV = FVector(VertX, VertY, 0.0f); - - // If we need to normalize UV space. - if (bExportNormalizedUVs) - TextureUV /= ComponentSizeQuads; - } - else - { - // We want to export global uvs (default). - FIntPoint IntPoint = LandscapeComponent->GetSectionBase(); - TextureUV = FVector(VertX * ScaleFactor + IntPoint.X, VertY * ScaleFactor + IntPoint.Y, 0.0f); - - // Keep track of max offset. - IntPointMax = IntPointMax.ComponentMax(IntPoint); - } - - if (bExportLighting) - { - FLinearColor VertexLightmapColor(0.0f, 0.0f, 0.0f, 1.0f); - if (LightmapMipData.Num() > 0) - { - FVector2D UVCoord(VertX, VertY); - UVCoord /= (ComponentSizeQuads + 1); - - FColor LightmapColorRaw = PickVertexColorFromTextureMip( - LightmapMipData.GetData(), UVCoord, LightmapMipSizeX, LightmapMipSizeY); - - VertexLightmapColor = LightmapColorRaw.ReinterpretAsLinear(); - } - - LandscapeLightmapValues[AllPositionsIdx] = VertexLightmapColor; - } - - // Retrieve component transform. - const FTransform & ComponentTransform = LandscapeComponent->GetComponentTransform(); - - // Retrieve component scale. - const FVector & ScaleVector = ComponentTransform.GetScale3D(); - - // Perform normalization. - Normal /= ScaleVector; - Normal.Normalize(); - - TangentX /= ScaleVector; - TangentX.Normalize(); - - TangentY /= ScaleVector; - TangentY.Normalize(); - - // Perform position scaling. - FVector PositionTransformed = PositionVector / HAPI_UNREAL_SCALE_FACTOR_POSITION; - LandscapePositionArray[AllPositionsIdx].X = PositionTransformed.X; - LandscapePositionArray[AllPositionsIdx].Y = PositionTransformed.Z; - LandscapePositionArray[AllPositionsIdx].Z = PositionTransformed.Y; - - Swap(Normal.Y, Normal.Z); - - // Store landscape component name for this point. - LandscapeComponentNameArray[AllPositionsIdx] = LandscapeComponentNameStr; - - // Store vertex index (x,y) for this point. - LandscapeComponentVertexIndicesArray[AllPositionsIdx].X = VertX; - LandscapeComponentVertexIndicesArray[AllPositionsIdx].Y = VertY; - - // Store point normal. - LandscapeNormalArray[AllPositionsIdx] = Normal; - - // Store uv. - LandscapeUVArray[AllPositionsIdx] = TextureUV; - - AllPositionsIdx++; - } - - // Free the memory allocated for LandscapeComponentNameStr - FHoudiniEngineUtils::FreeRawStringMemory(LandscapeComponentNameStr); - } - - // If we need to normalize UV space and we are doing global UVs. - if (!bExportTileUVs && bExportNormalizedUVs) - { - IntPointMax += FIntPoint(ComponentSizeQuads, ComponentSizeQuads); - IntPointMax = IntPointMax.ComponentMax(FIntPoint(1, 1)); - - for (int32 UVIdx = 0; UVIdx < VertexCount; ++UVIdx) - { - FVector & PositionUV = LandscapeUVArray[UVIdx]; - PositionUV.X /= IntPointMax.X; - PositionUV.Y /= IntPointMax.Y; - } - } - - return true; -} - -FColor -FUnrealLandscapeTranslator::PickVertexColorFromTextureMip( - const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight) -{ - check(MipBytes); - - FColor ResultColor(0, 0, 0, 255); - - if (UVCoord.X >= 0.0f && UVCoord.X < 1.0f && UVCoord.Y >= 0.0f && UVCoord.Y < 1.0f) - { - const int32 X = MipWidth * UVCoord.X; - const int32 Y = MipHeight * UVCoord.Y; - - const int32 Index = ((Y * MipWidth) + X) * 4; - - ResultColor.B = MipBytes[Index + 0]; - ResultColor.G = MipBytes[Index + 1]; - ResultColor.R = MipBytes[Index + 2]; - ResultColor.A = MipBytes[Index + 3]; - } - - return ResultColor; -} - -bool -FUnrealLandscapeTranslator::AddLandscapePositionAttribute(const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray) -{ - int32 VertexCount = LandscapePositionArray.Num(); - if (VertexCount < 3) - return false; - - // Create point attribute info containing positions. - HAPI_AttributeInfo AttributeInfoPointPosition; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointPosition); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointPosition ); - AttributeInfoPointPosition.count = VertexCount; - AttributeInfoPointPosition.tupleSize = 3; - AttributeInfoPointPosition.exists = true; - AttributeInfoPointPosition.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointPosition.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointPosition.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition), false); - - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition, - (const float *)LandscapePositionArray.GetData(), - 0, AttributeInfoPointPosition.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeNormalAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeNormalArray) -{ - int32 VertexCount = LandscapeNormalArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointNormal; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointNormal); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointNormal ); - AttributeInfoPointNormal.count = VertexCount; - AttributeInfoPointNormal.tupleSize = 3; - AttributeInfoPointNormal.exists = true; - AttributeInfoPointNormal.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointNormal.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointNormal.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal, - (const float *)LandscapeNormalArray.GetData(), 0, VertexCount), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeUVAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeUVArray) -{ - int32 VertexCount = LandscapeUVArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointUV; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointUV); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointUV ); - AttributeInfoPointUV.count = VertexCount; - AttributeInfoPointUV.tupleSize = 3; - AttributeInfoPointUV.exists = true; - AttributeInfoPointUV.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointUV.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointUV.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV, - (const float *)LandscapeUVArray.GetData(), 0, AttributeInfoPointUV.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeComponentVertexIndicesAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentVertexIndicesArray) -{ - int32 VertexCount = LandscapeComponentVertexIndicesArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointLandscapeComponentVertexIndices; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentVertexIndices); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentVertexIndices ); - AttributeInfoPointLandscapeComponentVertexIndices.count = VertexCount; - AttributeInfoPointLandscapeComponentVertexIndices.tupleSize = 2; - AttributeInfoPointLandscapeComponentVertexIndices.exists = true; - AttributeInfoPointLandscapeComponentVertexIndices.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLandscapeComponentVertexIndices.storage = HAPI_STORAGETYPE_INT; - AttributeInfoPointLandscapeComponentVertexIndices.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, - &AttributeInfoPointLandscapeComponentVertexIndices), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, - &AttributeInfoPointLandscapeComponentVertexIndices, - (const int *)LandscapeComponentVertexIndicesArray.GetData(), 0, - AttributeInfoPointLandscapeComponentVertexIndices.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeComponentNameAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray) -{ - int32 VertexCount = LandscapeComponentNameArray.Num(); - if (VertexCount < 3) - return false; - - // Create point attribute containing landscape component name. - HAPI_AttributeInfo AttributeInfoPointLandscapeComponentNames; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentNames); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentNames ); - AttributeInfoPointLandscapeComponentNames.count = VertexCount; - AttributeInfoPointLandscapeComponentNames.tupleSize = 1; - AttributeInfoPointLandscapeComponentNames.exists = true; - AttributeInfoPointLandscapeComponentNames.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLandscapeComponentNames.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPointLandscapeComponentNames.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, - &AttributeInfoPointLandscapeComponentNames), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, - &AttributeInfoPointLandscapeComponentNames, - (const char **)LandscapeComponentNameArray.GetData(), - 0, AttributeInfoPointLandscapeComponentNames.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeLightmapColorAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues) -{ - int32 VertexCount = LandscapeLightmapValues.Num(); - - HAPI_AttributeInfo AttributeInfoPointLightmapColor; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLightmapColor); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLightmapColor ); - AttributeInfoPointLightmapColor.count = VertexCount; - AttributeInfoPointLightmapColor.tupleSize = 4; - AttributeInfoPointLightmapColor.exists = true; - AttributeInfoPointLightmapColor.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLightmapColor.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointLightmapColor.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor, - (const float *)LandscapeLightmapValues.GetData(), 0, - AttributeInfoPointLightmapColor.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeMeshIndicesAndMaterialsAttribute( - const HAPI_NodeId& NodeId, const bool& bExportMaterials, - const int32& ComponentSizeQuads, const int32& QuadCount, - ALandscapeProxy * LandscapeProxy, - const TSet< ULandscapeComponent * >& SelectedComponents) -{ - if (!LandscapeProxy) - return false; - - // Compute number of necessary indices. - int32 IndexCount = QuadCount * 4; - if (IndexCount < 0) - return false; - - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - - // Array holding indices data. - TArray LandscapeIndices; - LandscapeIndices.SetNumUninitialized(IndexCount); - - // Allocate space for face names. - // The LandscapeMaterial and HoleMaterial per point - TArray FaceMaterials; - TArray FaceHoleMaterials; - FaceMaterials.SetNumUninitialized(QuadCount); - FaceHoleMaterials.SetNumUninitialized(QuadCount); - - int32 VertIdx = 0; - int32 QuadIdx = 0; - - const char * MaterialRawStr = nullptr; - const char * MaterialHoleRawStr = nullptr; - - // Lambda for freeing the memory allocated by ExtractRawString and returning - auto FreeMemoryReturn = [&MaterialRawStr, &MaterialHoleRawStr](const bool& bReturn) - { - FHoudiniEngineUtils::FreeRawStringMemory(MaterialRawStr); - FHoudiniEngineUtils::FreeRawStringMemory(MaterialHoleRawStr); - - return bReturn; - }; - - const int32 QuadComponentCount = ComponentSizeQuads + 1; - for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) - { - ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; - if (!SelectedComponents.Contains(LandscapeComponent)) - continue; - - if (bExportMaterials) - { - // If component has an override material, we need to get the raw name (if exporting materials). - if (LandscapeComponent->OverrideMaterial) - { - MaterialRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideMaterial->GetName()); - } - - // If component has an override hole material, we need to get the raw name (if exporting materials). - if (LandscapeComponent->OverrideHoleMaterial) - { - MaterialHoleRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideHoleMaterial->GetName()); - } - } - - int32 BaseVertIndex = ComponentIdx * VertexCountPerComponent; - for (int32 YIdx = 0; YIdx < ComponentSizeQuads; YIdx++) - { - for (int32 XIdx = 0; XIdx < ComponentSizeQuads; XIdx++) - { - LandscapeIndices[VertIdx + 0] = BaseVertIndex + (XIdx + 0) + (YIdx + 0) * QuadComponentCount; - LandscapeIndices[VertIdx + 1] = BaseVertIndex + (XIdx + 1) + (YIdx + 0) * QuadComponentCount; - LandscapeIndices[VertIdx + 2] = BaseVertIndex + (XIdx + 1) + (YIdx + 1) * QuadComponentCount; - LandscapeIndices[VertIdx + 3] = BaseVertIndex + (XIdx + 0) + (YIdx + 1) * QuadComponentCount; - - // Store override materials (if exporting materials). - if (bExportMaterials) - { - FaceMaterials[QuadIdx] = MaterialRawStr; - FaceHoleMaterials[QuadIdx] = MaterialHoleRawStr; - } - - VertIdx += 4; - QuadIdx++; - } - } - } - - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, LandscapeIndices.GetData(), 0, LandscapeIndices.Num()), - FreeMemoryReturn(false)); - - // We need to generate array of face counts. - TArray LandscapeFaces; - LandscapeFaces.Init(4, QuadCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, LandscapeFaces.GetData(), 0, LandscapeFaces.Num()), - FreeMemoryReturn(false)); - - if (bExportMaterials) - { - if (!FaceMaterials.Contains(nullptr)) - { - // Marshall in override primitive material names. - HAPI_AttributeInfo AttributeInfoPrimitiveMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterial ); - AttributeInfoPrimitiveMaterial.count = FaceMaterials.Num(); - AttributeInfoPrimitiveMaterial.tupleSize = 1; - AttributeInfoPrimitiveMaterial.exists = true; - AttributeInfoPrimitiveMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoPrimitiveMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPrimitiveMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial, - (const char **)FaceMaterials.GetData(), 0, AttributeInfoPrimitiveMaterial.count), - FreeMemoryReturn(false)); - } - - if (!FaceHoleMaterials.Contains(nullptr)) - { - // Marshall in override primitive material hole names. - HAPI_AttributeInfo AttributeInfoPrimitiveMaterialHole; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterialHole); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterialHole ); - AttributeInfoPrimitiveMaterialHole.count = FaceHoleMaterials.Num(); - AttributeInfoPrimitiveMaterialHole.tupleSize = 1; - AttributeInfoPrimitiveMaterialHole.exists = true; - AttributeInfoPrimitiveMaterialHole.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoPrimitiveMaterialHole.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPrimitiveMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoPrimitiveMaterialHole), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoPrimitiveMaterialHole, (const char **)FaceHoleMaterials.GetData(), 0, - AttributeInfoPrimitiveMaterialHole.count), - FreeMemoryReturn(false)); - } - } - - // Free the memory and return true - return FreeMemoryReturn(true); -} - -bool -FUnrealLandscapeTranslator::AddLandscapeGlobalMaterialAttribute( - const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy) -{ - if (!LandscapeProxy) - return false; - - // If there's a global landscape material, we marshall it as detail. - UMaterialInterface * MaterialInterface = LandscapeProxy->GetLandscapeMaterial(); - const char * MaterialNameStr = ""; - if (MaterialInterface) - { - FString FullMaterialName = MaterialInterface->GetPathName(); - MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); - } - - HAPI_AttributeInfo AttributeInfoDetailMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterial ); - AttributeInfoDetailMaterial.count = 1; - AttributeInfoDetailMaterial.tupleSize = 1; - AttributeInfoDetailMaterial.exists = true; - AttributeInfoDetailMaterial.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoDetailMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoDetailMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial, - (const char**)&MaterialNameStr, 0, AttributeInfoDetailMaterial.count), false); - - // If there's a global landscape hole material, we marshall it as detail. - UMaterialInterface * HoleMaterialInterface = LandscapeProxy->GetLandscapeHoleMaterial(); - const char * HoleMaterialNameStr = ""; - if (HoleMaterialInterface) - { - FString FullMaterialName = HoleMaterialInterface->GetPathName(); - MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); - } - - HAPI_AttributeInfo AttributeInfoDetailMaterialHole; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterialHole); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterialHole ); - AttributeInfoDetailMaterialHole.count = 1; - AttributeInfoDetailMaterialHole.tupleSize = 1; - AttributeInfoDetailMaterialHole.exists = true; - AttributeInfoDetailMaterialHole.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoDetailMaterialHole.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoDetailMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoDetailMaterialHole), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoDetailMaterialHole, (const char **)&HoleMaterialNameStr, 0, - AttributeInfoDetailMaterialHole.count), false); - - return true; -} - - -bool -FUnrealLandscapeTranslator::AddLandscapeLayerAttribute( - const HAPI_NodeId& NodeId, const TArray& LandscapeLayerArray, const FString& LayerName) -{ - int32 VertexCount = LandscapeLayerArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoLayer; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLayer); - AttributeInfoLayer.count = VertexCount; - AttributeInfoLayer.tupleSize = 1; - AttributeInfoLayer.exists = true; - AttributeInfoLayer.owner = HAPI_ATTROWNER_POINT; - AttributeInfoLayer.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLayer.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, - TCHAR_TO_ANSI(*LayerName), - &AttributeInfoLayer), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, - TCHAR_TO_ANSI(*LayerName), - &AttributeInfoLayer, - (const float *)LandscapeLayerArray.GetData(), - 0, AttributeInfoLayer.count), false); - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "UnrealLandscapeTranslator.h" +#include "HoudiniGeoPartObject.h" + +#include "Landscape.h" +#include "LandscapeDataAccess.h" +#include "LandscapeEdit.h" +#include "LightMap.h" +#include "Engine/MapBuildDataRegistry.h" +#include "PhysicalMaterials/PhysicalMaterial.h" + + +bool +FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( + ALandscapeProxy* LandscapeProxy, + HAPI_NodeId& CreatedNodeId, + const FString& InputNodeNameString, + const bool& bExportGeometryAsMesh, + const bool& bExportTileUVs, + const bool bExportNormalizedUVs, + const bool bExportLighting, + const bool bExportMaterials ) +{ + //-------------------------------------------------------------------------------------------------- + // 1. Create an input node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId InputNodeId = -1; + // Create the curve SOP Node + std::string NodeNameRawString; + FHoudiniEngineUtils::ConvertUnrealString(InputNodeNameString, NodeNameRawString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + return false; + + // We now have a valid id. + CreatedNodeId = InputNodeId; + + if(!FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true)) + return false; + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); + */ + //-------------------------------------------------------------------------------------------------- + // 2. Set the part info + //-------------------------------------------------------------------------------------------------- + int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; + float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; + + //int32 NumComponents = bExportOnlySelected ? SelectedComponents.Num() : LandscapeProxy->LandscapeComponents.Num(); + int32 NumComponents = LandscapeProxy->LandscapeComponents.Num(); + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + int32 VertexCount = NumComponents * VertexCountPerComponent; + if (!VertexCount) + return false; + + int32 TriangleCount = NumComponents * FMath::Square(ComponentSizeQuads) * 2; + int32 QuadCount = NumComponents * FMath::Square(ComponentSizeQuads); + int32 IndexCount = QuadCount * 4; + + // Create part info + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + //FMemory::Memzero< HAPI_PartInfo >(Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = VertexCount; + Part.type = HAPI_PARTTYPE_MESH; + + // If we are exporting to a mesh, we need vertices and faces + if (bExportGeometryAsMesh) + { + Part.vertexCount = IndexCount; + Part.faceCount = QuadCount; + } + + // Set the part infos + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, &DisplayGeoInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part), false); + + //-------------------------------------------------------------------------------------------------- + // 3. Extract the landscape data + //-------------------------------------------------------------------------------------------------- + // Array for the position data + TArray LandscapePositionArray; + // Array for the normals + TArray LandscapeNormalArray; + // Array for the UVs + TArray LandscapeUVArray; + // Array for the vertex index of each point in its component + TArray LandscapeComponentVertexIndicesArray; + // Array for the tile names per point + TArray LandscapeComponentNameArray; + // Array for the lightmap values + TArray LandscapeLightmapValues; + // Selected components set to all components in current landscape proxy + TSet SelectedComponents; + SelectedComponents.Append(LandscapeProxy->LandscapeComponents); + + // Extract all the data from the landscape to the arrays + if (!ExtractLandscapeData( + LandscapeProxy, SelectedComponents, + bExportLighting, bExportTileUVs, bExportNormalizedUVs, + LandscapePositionArray, LandscapeNormalArray, + LandscapeUVArray, LandscapeComponentVertexIndicesArray, + LandscapeComponentNameArray, LandscapeLightmapValues)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Set the corresponding attributes in Houdini + //-------------------------------------------------------------------------------------------------- + + // Create point attribute info containing positions. + if (!AddLandscapePositionAttribute(DisplayGeoInfo.nodeId, LandscapePositionArray)) + return false; + + // Create point attribute info containing normals. + if (!AddLandscapeNormalAttribute(DisplayGeoInfo.nodeId, LandscapeNormalArray)) + return false; + + // Create point attribute info containing UVs. + if (!AddLandscapeUVAttribute(DisplayGeoInfo.nodeId, LandscapeUVArray)) + return false; + + // Create point attribute containing landscape component vertex indices (indices of vertices within the grid - x,y). + if (!AddLandscapeComponentVertexIndicesAttribute(DisplayGeoInfo.nodeId, LandscapeComponentVertexIndicesArray)) + return false; + + // Create point attribute containing landscape component name. + if (!AddLandscapeComponentNameAttribute(DisplayGeoInfo.nodeId, LandscapeComponentNameArray)) + return false; + + // Create point attribute info containing lightmap information. + if (bExportLighting) + { + if (!AddLandscapeLightmapColorAttribute(DisplayGeoInfo.nodeId, LandscapeLightmapValues)) + return false; + } + + // Set indices if we are exporting full geometry. + if (bExportGeometryAsMesh) + { + if (!AddLandscapeMeshIndicesAndMaterialsAttribute( + DisplayGeoInfo.nodeId, + bExportMaterials, + ComponentSizeQuads, + QuadCount, + LandscapeProxy, + SelectedComponents)) + return false; + } + + // If we are marshalling material information. + if (bExportMaterials) + { + if (!AddLandscapeGlobalMaterialAttribute(DisplayGeoInfo.nodeId, LandscapeProxy)) + return false; + } + + /* + // TODO: Move this to ExtractLandscapeData() + //-------------------------------------------------------------------------------------------------- + // 4. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Get the landscape X/Y Size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + return false; + + // Calc the X/Y size in points + int32 XSize = (MaxX - MinX + 1); + int32 YSize = (MaxY - MinY + 1); + if ((XSize < 2) || (YSize < 2)) + return false; + + bool MaskInitialized = false; + int32 NumLayers = LandscapeInfo->Layers.Num(); + for (int32 n = 0; n < NumLayers; n++) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!GetLandscapeLayerData( + LandscapeInfo, n, + MinX, MinY, MaxX, MaxY, + CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray CurrentLayerFloatData; + if (!ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo)) + continue; + + if (!AddLandscapeLayerAttribute( + DisplayGeoInfo.nodeId, CurrentLayerFloatData, LayerName)) + continue; + } + */ + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId), false); + + // TODO: Remove me! + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); + */ + + return FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true); +} + +bool +FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( + ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId, const FString& InputNodeNameStr) +{ + if (!LandscapeProxy) + return false; + + // Export the whole landscape and its layer as a single heightfield. + + //-------------------------------------------------------------------------------------------------- + // 1. Extracting the height data + //-------------------------------------------------------------------------------------------------- + TArray HeightData; + int32 XSize, YSize; + FVector Min, Max; + if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + HAPI_VolumeInfo HeightfieldVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + FTransform LandscapeTransform = LandscapeProxy->ActorToWorld(); + FVector CenterOffset = FVector::ZeroVector; + if (!ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId HeightFieldId = -1; + HAPI_NodeId HeightId = -1; + HAPI_NodeId MaskId = -1; + HAPI_NodeId MergeId = -1; + if (!CreateHeightfieldInputNode(InputNodeNameStr, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + HAPI_PartId PartId = 0; + if (!SetHeighfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) + return false; + + // Add the materials used + UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); + UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); + UPhysicalMaterial* LandscapePhysMat = LandscapeProxy->DefaultPhysMaterial; + AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateAttributesFromTags(HeightId, PartId, LandscapeProxy->Tags); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(HeightId, PartId, LandscapeProxy, 1); + + // Add the unreal_level_path attribute + ULevel* Level = LandscapeProxy->GetLevel(); + if (Level) + { + FHoudiniEngineUtils::AddLevelPathAttribute(HeightId, PartId, Level, 1); + /* + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToVolume(HeightId, PartId, LevelPath); + */ + } + + // Commit the height volume + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), HeightId), false); + + //-------------------------------------------------------------------------------------------------- + // 5. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + bool MaskInitialized = false; + int32 MergeInputIndex = 2; + int32 NumLayers = LandscapeInfo->Layers.Num(); + for (int32 n = 0; n < NumLayers; n++) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!GetLandscapeLayerData(LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray CurrentLayerFloatData; + if (!ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo)) + continue; + + // We reuse the height layer's transform + CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; + + // 3. See if we need to create an input volume, or can reuse the HF's default mask volume + bool IsMask = false; + if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) + IsMask = true; + + HAPI_NodeId LayerVolumeNodeId = -1; + if (!IsMask) + { + // Current layer is not mask, so we need to create a new input volume + std::string LayerNameStr; + FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); + } + else + { + // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node + LayerVolumeNodeId = MaskId; + } + + // Check if we have a valid id for the input volume. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) + continue; + + // 4. Set the layer/mask heighfield data in Houdini + HAPI_PartId CurrentPartId = 0; + if (!SetHeighfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) + continue; + + // Get the physical material used by that layer + UPhysicalMaterial* LayerPhysicalMat = LandscapePhysMat; + { + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (LayerInfo) + LayerPhysicalMat = LayerInfo->PhysMaterial; + } + + // Also add the material attributes to the layer volumes + AddLandscapeMaterialAttributesToVolume(LayerVolumeNodeId, PartId, LandscapeMat, LandscapeHoleMat, LayerPhysicalMat); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateAttributesFromTags(LayerVolumeNodeId, PartId, LandscapeProxy->Tags); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(LayerVolumeNodeId, PartId, LandscapeProxy, 1); + + // Also add the level path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(LayerVolumeNodeId, PartId, Level, 1); + //AddLevelPathAttributeToVolume(LayerVolumeNodeId, PartId, LevelPath); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); + + if (!IsMask) + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex, LayerVolumeNodeId, 0), false); + + MergeInputIndex++; + } + else + { + MaskInitialized = true; + } + } + + // We need to have a mask layer as it is required for proper heightfield functionalities + // Setting the volume info on the mask is needed for the HF to have proper transform in H! + // If we didn't create a mask volume before, send a default one now + if (!MaskInitialized) + { + MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); + + // Add the materials used + AddLandscapeMaterialAttributesToVolume(MaskId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateAttributesFromTags(MaskId, PartId, LandscapeProxy->Tags); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(MaskId, PartId, LandscapeProxy, 1); + + // Also add the level path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(MaskId, PartId, Level, 1); + //AddLevelPathAttributeToVolume(MaskId, PartId, LevelPath); + + // Commit the mask volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), MaskId), false); + } + + HAPI_TransformEuler HAPIObjectTransform; + FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); + LandscapeTransform.SetScale3D(FVector::OneVector); + FHoudiniEngineUtils::TranslateUnrealTransform(LandscapeTransform, HAPIObjectTransform); + HAPIObjectTransform.position[1] = 0.0f; + + HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(HeightFieldId); + FHoudiniApi::SetObjectTransform(FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform); + + // Since HF are centered but landscape aren't, we need to set the HF's center parameter + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); + + // Finally, cook the Heightfield node + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightFieldId, &CookOptions), false); + */ + if(!FHoudiniEngineUtils::HapiCookNode(HeightFieldId, nullptr, true)) + return false; + + CreatedHeightfieldNodeId = HeightFieldId; + + return true; +} + +// Converts Unreal uint16 values to Houdini Float +bool +FUnrealLandscapeTranslator::ConvertLandscapeLayerDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + const FLinearColor& LayerUsageDebugColor, + TArray& LayerFloatValues, + HAPI_VolumeInfo& LayerVolumeInfo) +{ + LayerFloatValues.Empty(); + + int32 HoudiniXSize = YSize; + int32 HoudiniYSize = XSize; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + if (IntHeightData.Num() != SizeInPoints) + return false; + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to float + //-------------------------------------------------------------------------------------------------- + + // By default, values are converted from unreal [0 255] uint8 to Houdini [0 1] float + // uint8 min/max + uint8 IntMin = 0; + uint8 IntMax = UINT8_MAX; + // The range in Digits + double DigitRange = (double)UINT8_MAX; + + // By default, the values will be converted to [0, 1] + float LayerMin = 0.0f; + float LayerMax = 1.0f; + float LayerSpacing = 1.0f / DigitRange; + + // If this layer came from Houdini, its alpha value should be PI + // This indicates that we can extract additional infos stored its debug usage color + // so we can reconstruct the original source values (float) more accurately + if (LayerUsageDebugColor.A == PI) + { + // We need the ZMin / ZMax uint8 values + IntMin = IntHeightData[0]; + IntMax = IntMin; + for (int n = 0; n < IntHeightData.Num(); n++) + { + if (IntHeightData[n] < IntMin) + IntMin = IntHeightData[n]; + if (IntHeightData[n] > IntMax) + IntMax = IntHeightData[n]; + } + + DigitRange = (double)IntMax - (double)IntMin; + + // Read the original min/max and spacing stored in the debug color + LayerMin = LayerUsageDebugColor.R; + LayerMax = LayerUsageDebugColor.G; + LayerSpacing = LayerUsageDebugColor.B; + } + + // Convert the Int data to Float + LayerFloatValues.SetNumUninitialized(SizeInPoints); + + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // We need to invert X/Y when reading the value from Unreal + int32 nHoudini = nX + nY * HoudiniXSize; + int32 nUnreal = nY + nX * XSize; + + // Convert the int values to meter + // Unreal's digit value have a zero value of 32768 + double DoubleValue = ((double)IntHeightData[nUnreal] - (double)IntMin) * LayerSpacing + LayerMin; + LayerFloatValues[nHoudini] = (float)DoubleValue; + } + } + + /* + // Verifying the converted ZMin / ZMax + float FloatMin = LayerFloatValues[0]; + float FloatMax = FloatMin; + for (int32 n = 0; n < LayerFloatValues.Num(); n++) + { + if (LayerFloatValues[n] < FloatMin) + FloatMin = LayerFloatValues[n]; + if (LayerFloatValues[n] > FloatMax) + FloatMax = LayerFloatValues[n]; + } + */ + + //-------------------------------------------------------------------------------------------------- + // 2. Fill the volume info + //-------------------------------------------------------------------------------------------------- + LayerVolumeInfo.xLength = HoudiniXSize; + LayerVolumeInfo.yLength = HoudiniYSize; + LayerVolumeInfo.zLength = 1; + + LayerVolumeInfo.minX = 0; + LayerVolumeInfo.minY = 0; + LayerVolumeInfo.minZ = 0; + + LayerVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; + LayerVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; + LayerVolumeInfo.tupleSize = 1; + LayerVolumeInfo.tileSize = 1; + + LayerVolumeInfo.hasTaper = false; + LayerVolumeInfo.xTaper = 0.0; + LayerVolumeInfo.yTaper = 0.0; + + // The layer transform will have to be copied from the main heightfield's transform + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeData( + ALandscapeProxy* LandscapeProxy, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max) +{ + if (!LandscapeProxy) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Get the landscape extents to get its size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + + // To handle streaming proxies correctly, get the extents via all the components, + // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. + for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) + { + Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); + } + + if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize)) + return false; + + // Get the landscape Min/Max values + // Do not use Landscape->GetActorBounds() here as instanced geo + // (due to grass layers for example) can cause it to return incorrect bounds! + FVector Origin, Extent; + GetLandscapeProxyBounds(LandscapeProxy, Origin, Extent); + + // Get the landscape Min/Max values + Min = Origin - Extent; + Max = Origin + Extent; + + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeData( + ULandscapeInfo* LandscapeInfo, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& HeightData, + int32& XSize, int32& YSize) +{ + if (!LandscapeInfo) + return false; + + // Get the X/Y size in points + XSize = (MaxX - MinX + 1); + YSize = (MaxY - MinY + 1); + + if ((XSize < 2) || (YSize < 2)) + return false; + + // Extracting the uint16 values from the landscape + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + HeightData.AddZeroed(XSize * YSize); + LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0); + + return true; +} + + +void +FUnrealLandscapeTranslator::GetLandscapeProxyBounds( + ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents) +{ + // Iterate only on the landscape components + FBox Bounds(ForceInit); + for (const UActorComponent* ActorComponent : LandscapeProxy->GetComponents()) + { + const ULandscapeComponent* LandscapeComp = Cast(ActorComponent); + if (LandscapeComp && LandscapeComp->IsRegistered()) + Bounds += LandscapeComp->Bounds.GetBox(); + } + + // Convert the bounds to origin/offset vectors + Bounds.GetCenterAndExtents(Origin, Extents); +} + +bool +FUnrealLandscapeTranslator::ConvertLandscapeDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + FVector Min, FVector Max, + const FTransform& LandscapeTransform, + TArray& HeightfieldFloatValues, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + FVector& CenterOffset) +{ + HeightfieldFloatValues.Empty(); + + int32 HoudiniXSize = YSize; + int32 HoudiniYSize = XSize; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + if (IntHeightData.Num() != SizeInPoints) + return false; + + // Use default unreal scaling for marshalling landscapes + // A lot of precision will be lost in order to keep the same transform as the landscape input + bool bUseDefaultUE4Scaling = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) + bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to float + //-------------------------------------------------------------------------------------------------- + + + // Convert the min/max values from cm to meters + Min /= 100.0; + Max /= 100.0; + + // Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0 + // To convert the uint16 values to float "metric" values, offset the int by 32768 to center it, + // then scale it + + // Spacing used to convert from uint16 to meters + double ZSpacing = 512.0 / ((double)UINT16_MAX); + ZSpacing *= ((double)LandscapeTransform.GetScale3D().Z / 100.0); + + // Center value in meters (Landscape ranges from [-255:257] meters at default scale + double ZCenterOffset = 32767; + double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f; + // Convert the Int data to Float + HeightfieldFloatValues.SetNumUninitialized(SizeInPoints); + + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // We need to invert X/Y when reading the value from Unreal + int32 nHoudini = nX + nY * HoudiniXSize; + int32 nUnreal = nY + nX * XSize; + + // Convert the int values to meter + // Unreal's digit value have a zero value of 32768 + double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset; + HeightfieldFloatValues[nHoudini] = (float)DoubleValue; + } + } + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the Unreal Transform to a HAPI_transform + //-------------------------------------------------------------------------------------------------- + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + //FMemory::Memzero< HAPI_Transform >( HapiTransform ); + { + FQuat Rotation = LandscapeTransform.GetRotation(); + if (Rotation != FQuat::Identity) + { + //Swap(ObjectRotation.Y, ObjectRotation.Z); + HapiTransform.rotationQuaternion[0] = Rotation.X; + HapiTransform.rotationQuaternion[1] = Rotation.Z; + HapiTransform.rotationQuaternion[2] = Rotation.Y; + HapiTransform.rotationQuaternion[3] = -Rotation.W; + } + else + { + HapiTransform.rotationQuaternion[0] = 0; + HapiTransform.rotationQuaternion[1] = 0; + HapiTransform.rotationQuaternion[2] = 0; + HapiTransform.rotationQuaternion[3] = 1; + } + + // Heightfield are centered, landscapes are not + CenterOffset = (Max - Min) * 0.5f; + + // Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform) + //FVector Position = LandscapeTransform.GetLocation() / 100.0f; + HapiTransform.position[1] = 0.0f;//Position.X + CenterOffset.X; + HapiTransform.position[0] = 0.0f;//Position.Y + CenterOffset.Y; + HapiTransform.position[2] = 0.0f; + + FVector Scale = LandscapeTransform.GetScale3D() / 100.0f; + HapiTransform.scale[0] = Scale.X * 0.5f * HoudiniXSize; + HapiTransform.scale[1] = Scale.Y * 0.5f * HoudiniYSize; + HapiTransform.scale[2] = 0.5f; + if (bUseDefaultUE4Scaling) + HapiTransform.scale[2] *= Scale.Z; + + HapiTransform.shear[0] = 0.0f; + HapiTransform.shear[1] = 0.0f; + HapiTransform.shear[2] = 0.0f; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Fill the volume info + //-------------------------------------------------------------------------------------------------- + HeightfieldVolumeInfo.xLength = HoudiniXSize; + HeightfieldVolumeInfo.yLength = HoudiniYSize; + HeightfieldVolumeInfo.zLength = 1; + + HeightfieldVolumeInfo.minX = 0; + HeightfieldVolumeInfo.minY = 0; + HeightfieldVolumeInfo.minZ = 0; + + HeightfieldVolumeInfo.transform = HapiTransform; + + HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; + HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; + HeightfieldVolumeInfo.tupleSize = 1; + HeightfieldVolumeInfo.tileSize = 1; + + HeightfieldVolumeInfo.hasTaper = false; + HeightfieldVolumeInfo.xTaper = 0.0; + HeightfieldVolumeInfo.yTaper = 0.0; + + return true; +} + +bool +FUnrealLandscapeTranslator::CreateHeightfieldInputNode( + const FString& NodeName, + const int32& XSize, + const int32& YSize, + HAPI_NodeId& HeightfieldNodeId, + HAPI_NodeId& HeightNodeId, + HAPI_NodeId& MaskNodeId, + HAPI_NodeId& MergeNodeId) +{ + // Make sure the Heightfield node doesnt already exists + if (HeightfieldNodeId != -1) + return false; + + // Convert the node's name + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString(NodeName, NameStr); + + // Create the heigthfield node via HAPI + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightFieldInput( + FHoudiniEngine::Get().GetSession(), + -1, NameStr.c_str(), XSize, YSize, 1.0f, HAPI_HeightFieldSampling::HAPI_HEIGHTFIELD_SAMPLING_CORNER, + &HeightfieldNodeId, &HeightNodeId, &MaskNodeId, &MergeNodeId), false); + + // Cook it + return FHoudiniEngineUtils::HapiCookNode(HeightfieldNodeId, nullptr, true); + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightfieldNodeId, &CookOptions), false); + + return true; + */ +} + +bool +FUnrealLandscapeTranslator::SetHeighfieldData( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + TArray& FloatValues, + const HAPI_VolumeInfo& VolumeInfo, + const FString& HeightfieldName) +{ + // Cook the node to get proper infos on it + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, &CookOptions), false); + */ + if(!FHoudiniEngineUtils::HapiCookNode(VolumeNodeId, nullptr, true)) + return false; + + // Read the geo/part/volume info from the volume node + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + //FMemory::Memset< HAPI_GeoInfo >(GeoInfo, 0); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, &GeoInfo), false); + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + //FMemory::Memset< HAPI_PartInfo >(PartInfo, 0); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoInfo.nodeId, PartId, &PartInfo), false); + + // Update the volume infos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, PartInfo.id, &VolumeInfo), false); + + // Volume name + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString(HeightfieldName, NameStr); + + // Set the Heighfield data on the volume + float * HeightData = FloatValues.GetData(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetHeightFieldData( + FHoudiniEngine::Get().GetSession(), + GeoInfo.nodeId, PartInfo.id, NameStr.c_str(), HeightData, 0, FloatValues.Num()), false); + + return true; +} + +bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + UMaterialInterface* InLandscapeMaterial, + UMaterialInterface* InLandscapeHoleMaterial, + UPhysicalMaterial* InPhysicalMaterial) +{ + if (VolumeNodeId == -1) + return false; + + // LANDSCAPE MATERIAL + if (InLandscapeMaterial && !InLandscapeMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InLandscapeMaterialString = InLandscapeMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + // HOLE MATERIAL + if (InLandscapeHoleMaterial && !InLandscapeHoleMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InLandscapeMaterialString = InLandscapeHoleMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (Result == HAPI_RESULT_SUCCESS) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_hole_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + // PHYSICAL MATERIAL + if (InPhysicalMaterial && !InPhysicalMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InPhysMatlString = InPhysicalMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (Result == HAPI_RESULT_SUCCESS) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InPhysMatlString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_physical_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + return true; +} + +/* +bool +FUnrealLandscapeTranslator::AddLevelPathAttributeToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + const FString& LevelPath) +{ + if (VolumeNodeId == -1) + return false; + + // LANDSCAPE MATERIAL + if (LevelPath.IsEmpty()) + return false; + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = 1; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to cont char * + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray LevelPathArr; + LevelPathArr.Add(LevelPathCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + LevelPathArr.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} +*/ + +bool +FUnrealLandscapeTranslator::GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, const int32& LayerIndex, + TArray& LayerData, FLinearColor& LayerUsageDebugColor, + FString& LayerName) +{ + if (!LandscapeInfo) + return false; + + // Get the landscape X/Y Size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + return false; + + if (!GetLandscapeLayerData( + LandscapeInfo, LayerIndex, + MinX, MinY, MaxX, MaxY, + LayerData, LayerUsageDebugColor, LayerName)) + return false; + + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName) +{ + if (!LandscapeInfo) + return false; + + if (!LandscapeInfo->Layers.IsValidIndex(LayerIndex)) + return false; + + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[LayerIndex]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (!LayerInfo) + return false; + + // Calc the X/Y size in points + int32 XSize = (MaxX - MinX + 1); + int32 YSize = (MaxY - MinY + 1); + if ((XSize < 2) || (YSize < 2)) + return false; + + // extracting the uint8 values from the layer + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + LayerData.AddZeroed(XSize * YSize); + LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, LayerData.GetData(), 0); + + LayerUsageDebugColor = LayerInfo->LayerUsageDebugColor; + + LayerName = LayersSetting.GetLayerName().ToString(); + + return true; +} + +bool +FUnrealLandscapeTranslator::InitDefaultHeightfieldMask( + const HAPI_VolumeInfo& HeightVolumeInfo, + const HAPI_NodeId& MaskVolumeNodeId) +{ + // We need to have a mask layer as it is required for proper heightfield functionalities + + // Creating an array filled with 0.0 + TArray< float > MaskFloatData; + MaskFloatData.Init(0.0f, HeightVolumeInfo.xLength * HeightVolumeInfo.yLength); + + // Creating the volume infos + HAPI_VolumeInfo MaskVolumeInfo = HeightVolumeInfo; + + // Set the heighfield data in Houdini + FString MaskName = TEXT("mask"); + HAPI_PartId PartId = 0; + if (!SetHeighfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) + return false; + + return true; +} + +bool +FUnrealLandscapeTranslator::DestroyLandscapeAssetNode(HAPI_NodeId& ConnectedAssetId, TArray& CreatedInputAssetIds) +{ + HAPI_AssetInfo NodeAssetInfo; + FHoudiniApi::AssetInfo_Init(&NodeAssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeAssetInfo), false); + + FHoudiniEngineString AssetOpName(NodeAssetInfo.fullOpNameSH); + FString OpName; + if (!AssetOpName.ToFString(OpName)) + return false; + + if (!OpName.Contains(TEXT("xform"))) + { + // Not a transform node, so not a Heightfield + // We just need to destroy the landscape asset node + return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); + } + + // The landscape was marshalled as a heightfield, so we need to destroy and disconnect + // the volvis nodes, all the merge node's input (each merge input is a volume for one + // of the layer/mask of the landscape ) + + // Query the volvis node id + // The volvis node is the fist input of the xform node + HAPI_NodeId VolvisNodeId = -1; + FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, &VolvisNodeId); + + // First, destroy the merge node and its inputs + // The merge node is in the first input of the volvis node + HAPI_NodeId MergeNodeId = -1; + FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + VolvisNodeId, 0, &MergeNodeId); + + if (MergeNodeId != -1) + { + // Get the merge node info + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), MergeNodeId, &NodeInfo), false); + + for (int32 n = 0; n < NodeInfo.inputCount; n++) + { + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeNodeId, n, &InputNodeId)) + break; + + if (InputNodeId == -1) + break; + + // Disconnect and Destroy that input + FHoudiniEngineUtils::HapiDisconnectAsset(MergeNodeId, n); + FHoudiniEngineUtils::DestroyHoudiniAsset(InputNodeId); + } + } + + // Second step, destroy all the volumes GEO assets + for (HAPI_NodeId AssetNodeId : CreatedInputAssetIds) + { + FHoudiniEngineUtils::DestroyHoudiniAsset(AssetNodeId); + } + CreatedInputAssetIds.Empty(); + + // Finally disconnect and destroy the xform, volvis and merge nodes, then destroy them + FHoudiniEngineUtils::HapiDisconnectAsset(ConnectedAssetId, 0); + FHoudiniEngineUtils::HapiDisconnectAsset(VolvisNodeId, 0); + FHoudiniEngineUtils::DestroyHoudiniAsset(MergeNodeId); + FHoudiniEngineUtils::DestroyHoudiniAsset(VolvisNodeId); + + return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); +} + + +bool +FUnrealLandscapeTranslator::ExtractLandscapeData( + ALandscapeProxy * LandscapeProxy, TSet& SelectedComponents, + const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, + TArray& LandscapePositionArray, + TArray& LandscapeNormalArray, + TArray& LandscapeUVArray, + TArray& LandscapeComponentVertexIndicesArray, + TArray& LandscapeComponentNameArray, + TArray& LandscapeLightmapValues) +{ + if (!LandscapeProxy) + return false; + + if (SelectedComponents.Num() < 1) + return false; + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Calc all the needed sizes + int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; + float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; + + int32 NumComponents = SelectedComponents.Num(); + bool bExportOnlySelected = NumComponents != LandscapeProxy->LandscapeComponents.Num(); + + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + int32 VertexCount = NumComponents * VertexCountPerComponent; + if (!VertexCount) + return false; + + // Initialize the data arrays + LandscapePositionArray.SetNumUninitialized(VertexCount); + LandscapeNormalArray.SetNumUninitialized(VertexCount); + LandscapeUVArray.SetNumUninitialized(VertexCount); + LandscapeComponentNameArray.SetNumUninitialized(VertexCount); + LandscapeComponentVertexIndicesArray.SetNumUninitialized(VertexCount); + if (bExportLighting) + LandscapeLightmapValues.SetNumUninitialized(VertexCount); + + //----------------------------------------------------------------------------------------------------------------- + // EXTRACT THE LANDSCAPE DATA + //----------------------------------------------------------------------------------------------------------------- + FIntPoint IntPointMax = FIntPoint::ZeroValue; + + int32 AllPositionsIdx = 0; + for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; + if (bExportOnlySelected && !SelectedComponents.Contains(LandscapeComponent)) + continue; + + TArray64< uint8 > LightmapMipData; + int32 LightmapMipSizeX = 0; + int32 LightmapMipSizeY = 0; + + // See if we need to export lighting information. + if (bExportLighting) + { + const FMeshMapBuildData* MapBuildData = LandscapeComponent->GetMeshMapBuildData(); + FLightMap2D* LightMap2D = MapBuildData && MapBuildData->LightMap ? MapBuildData->LightMap->GetLightMap2D() : nullptr; + if (LightMap2D && LightMap2D->IsValid(0)) + { + UTexture2D * TextureLightmap = LightMap2D->GetTexture(0); + if (TextureLightmap) + { + if (TextureLightmap->Source.GetMipData(LightmapMipData, 0, 0, 0, nullptr)) + { + LightmapMipSizeX = TextureLightmap->Source.GetSizeX(); + LightmapMipSizeY = TextureLightmap->Source.GetSizeY(); + } + else + { + LightmapMipData.Empty(); + } + } + } + } + + // Construct landscape component data interface to access raw data. + FLandscapeComponentDataInterface CDI(LandscapeComponent, LandscapeProxy->ExportLOD); + + // Get name of this landscape component. + const char * LandscapeComponentNameStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->GetName()); + for (int32 VertexIdx = 0; VertexIdx < VertexCountPerComponent; VertexIdx++) + { + int32 VertX = 0; + int32 VertY = 0; + CDI.VertexIndexToXY(VertexIdx, VertX, VertY); + + // Get position. + FVector PositionVector = CDI.GetWorldVertex(VertX, VertY); + + // Get normal / tangent / binormal. + FVector Normal = FVector::ZeroVector; + FVector TangentX = FVector::ZeroVector; + FVector TangentY = FVector::ZeroVector; + CDI.GetLocalTangentVectors(VertX, VertY, TangentX, TangentY, Normal); + + // Export UVs. + FVector TextureUV = FVector::ZeroVector; + if (bExportTileUVs) + { + // We want to export uvs per tile. + TextureUV = FVector(VertX, VertY, 0.0f); + + // If we need to normalize UV space. + if (bExportNormalizedUVs) + TextureUV /= ComponentSizeQuads; + } + else + { + // We want to export global uvs (default). + FIntPoint IntPoint = LandscapeComponent->GetSectionBase(); + TextureUV = FVector(VertX * ScaleFactor + IntPoint.X, VertY * ScaleFactor + IntPoint.Y, 0.0f); + + // Keep track of max offset. + IntPointMax = IntPointMax.ComponentMax(IntPoint); + } + + if (bExportLighting) + { + FLinearColor VertexLightmapColor(0.0f, 0.0f, 0.0f, 1.0f); + if (LightmapMipData.Num() > 0) + { + FVector2D UVCoord(VertX, VertY); + UVCoord /= (ComponentSizeQuads + 1); + + FColor LightmapColorRaw = PickVertexColorFromTextureMip( + LightmapMipData.GetData(), UVCoord, LightmapMipSizeX, LightmapMipSizeY); + + VertexLightmapColor = LightmapColorRaw.ReinterpretAsLinear(); + } + + LandscapeLightmapValues[AllPositionsIdx] = VertexLightmapColor; + } + + // Retrieve component transform. + const FTransform & ComponentTransform = LandscapeComponent->GetComponentTransform(); + + // Retrieve component scale. + const FVector & ScaleVector = ComponentTransform.GetScale3D(); + + // Perform normalization. + Normal /= ScaleVector; + Normal.Normalize(); + + TangentX /= ScaleVector; + TangentX.Normalize(); + + TangentY /= ScaleVector; + TangentY.Normalize(); + + // Perform position scaling. + FVector PositionTransformed = PositionVector / HAPI_UNREAL_SCALE_FACTOR_POSITION; + LandscapePositionArray[AllPositionsIdx].X = PositionTransformed.X; + LandscapePositionArray[AllPositionsIdx].Y = PositionTransformed.Z; + LandscapePositionArray[AllPositionsIdx].Z = PositionTransformed.Y; + + Swap(Normal.Y, Normal.Z); + + // Store landscape component name for this point. + LandscapeComponentNameArray[AllPositionsIdx] = LandscapeComponentNameStr; + + // Store vertex index (x,y) for this point. + LandscapeComponentVertexIndicesArray[AllPositionsIdx].X = VertX; + LandscapeComponentVertexIndicesArray[AllPositionsIdx].Y = VertY; + + // Store point normal. + LandscapeNormalArray[AllPositionsIdx] = Normal; + + // Store uv. + LandscapeUVArray[AllPositionsIdx] = TextureUV; + + AllPositionsIdx++; + } + + // Free the memory allocated for LandscapeComponentNameStr + FHoudiniEngineUtils::FreeRawStringMemory(LandscapeComponentNameStr); + } + + // If we need to normalize UV space and we are doing global UVs. + if (!bExportTileUVs && bExportNormalizedUVs) + { + IntPointMax += FIntPoint(ComponentSizeQuads, ComponentSizeQuads); + IntPointMax = IntPointMax.ComponentMax(FIntPoint(1, 1)); + + for (int32 UVIdx = 0; UVIdx < VertexCount; ++UVIdx) + { + FVector & PositionUV = LandscapeUVArray[UVIdx]; + PositionUV.X /= IntPointMax.X; + PositionUV.Y /= IntPointMax.Y; + } + } + + return true; +} + +FColor +FUnrealLandscapeTranslator::PickVertexColorFromTextureMip( + const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight) +{ + check(MipBytes); + + FColor ResultColor(0, 0, 0, 255); + + if (UVCoord.X >= 0.0f && UVCoord.X < 1.0f && UVCoord.Y >= 0.0f && UVCoord.Y < 1.0f) + { + const int32 X = MipWidth * UVCoord.X; + const int32 Y = MipHeight * UVCoord.Y; + + const int32 Index = ((Y * MipWidth) + X) * 4; + + ResultColor.B = MipBytes[Index + 0]; + ResultColor.G = MipBytes[Index + 1]; + ResultColor.R = MipBytes[Index + 2]; + ResultColor.A = MipBytes[Index + 3]; + } + + return ResultColor; +} + +bool +FUnrealLandscapeTranslator::AddLandscapePositionAttribute(const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray) +{ + int32 VertexCount = LandscapePositionArray.Num(); + if (VertexCount < 3) + return false; + + // Create point attribute info containing positions. + HAPI_AttributeInfo AttributeInfoPointPosition; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointPosition); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointPosition ); + AttributeInfoPointPosition.count = VertexCount; + AttributeInfoPointPosition.tupleSize = 3; + AttributeInfoPointPosition.exists = true; + AttributeInfoPointPosition.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointPosition.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointPosition.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition), false); + + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition, + (const float *)LandscapePositionArray.GetData(), + 0, AttributeInfoPointPosition.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeNormalAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeNormalArray) +{ + int32 VertexCount = LandscapeNormalArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointNormal; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointNormal); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointNormal ); + AttributeInfoPointNormal.count = VertexCount; + AttributeInfoPointNormal.tupleSize = 3; + AttributeInfoPointNormal.exists = true; + AttributeInfoPointNormal.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointNormal.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointNormal.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal, + (const float *)LandscapeNormalArray.GetData(), 0, VertexCount), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeUVAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeUVArray) +{ + int32 VertexCount = LandscapeUVArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointUV; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointUV); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointUV ); + AttributeInfoPointUV.count = VertexCount; + AttributeInfoPointUV.tupleSize = 3; + AttributeInfoPointUV.exists = true; + AttributeInfoPointUV.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointUV.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointUV.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV, + (const float *)LandscapeUVArray.GetData(), 0, AttributeInfoPointUV.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeComponentVertexIndicesAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentVertexIndicesArray) +{ + int32 VertexCount = LandscapeComponentVertexIndicesArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointLandscapeComponentVertexIndices; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentVertexIndices); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentVertexIndices ); + AttributeInfoPointLandscapeComponentVertexIndices.count = VertexCount; + AttributeInfoPointLandscapeComponentVertexIndices.tupleSize = 2; + AttributeInfoPointLandscapeComponentVertexIndices.exists = true; + AttributeInfoPointLandscapeComponentVertexIndices.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLandscapeComponentVertexIndices.storage = HAPI_STORAGETYPE_INT; + AttributeInfoPointLandscapeComponentVertexIndices.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, + &AttributeInfoPointLandscapeComponentVertexIndices), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, + &AttributeInfoPointLandscapeComponentVertexIndices, + (const int *)LandscapeComponentVertexIndicesArray.GetData(), 0, + AttributeInfoPointLandscapeComponentVertexIndices.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeComponentNameAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray) +{ + int32 VertexCount = LandscapeComponentNameArray.Num(); + if (VertexCount < 3) + return false; + + // Create point attribute containing landscape component name. + HAPI_AttributeInfo AttributeInfoPointLandscapeComponentNames; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentNames); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentNames ); + AttributeInfoPointLandscapeComponentNames.count = VertexCount; + AttributeInfoPointLandscapeComponentNames.tupleSize = 1; + AttributeInfoPointLandscapeComponentNames.exists = true; + AttributeInfoPointLandscapeComponentNames.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLandscapeComponentNames.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPointLandscapeComponentNames.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, + &AttributeInfoPointLandscapeComponentNames), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, + &AttributeInfoPointLandscapeComponentNames, + (const char **)LandscapeComponentNameArray.GetData(), + 0, AttributeInfoPointLandscapeComponentNames.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeLightmapColorAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues) +{ + int32 VertexCount = LandscapeLightmapValues.Num(); + + HAPI_AttributeInfo AttributeInfoPointLightmapColor; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLightmapColor); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLightmapColor ); + AttributeInfoPointLightmapColor.count = VertexCount; + AttributeInfoPointLightmapColor.tupleSize = 4; + AttributeInfoPointLightmapColor.exists = true; + AttributeInfoPointLightmapColor.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLightmapColor.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointLightmapColor.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor, + (const float *)LandscapeLightmapValues.GetData(), 0, + AttributeInfoPointLightmapColor.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeMeshIndicesAndMaterialsAttribute( + const HAPI_NodeId& NodeId, const bool& bExportMaterials, + const int32& ComponentSizeQuads, const int32& QuadCount, + ALandscapeProxy * LandscapeProxy, + const TSet< ULandscapeComponent * >& SelectedComponents) +{ + if (!LandscapeProxy) + return false; + + // Compute number of necessary indices. + int32 IndexCount = QuadCount * 4; + if (IndexCount < 0) + return false; + + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + + // Array holding indices data. + TArray LandscapeIndices; + LandscapeIndices.SetNumUninitialized(IndexCount); + + // Allocate space for face names. + // The LandscapeMaterial and HoleMaterial per point + TArray FaceMaterials; + TArray FaceHoleMaterials; + FaceMaterials.SetNumUninitialized(QuadCount); + FaceHoleMaterials.SetNumUninitialized(QuadCount); + + int32 VertIdx = 0; + int32 QuadIdx = 0; + + const char * MaterialRawStr = nullptr; + const char * MaterialHoleRawStr = nullptr; + + // Lambda for freeing the memory allocated by ExtractRawString and returning + auto FreeMemoryReturn = [&MaterialRawStr, &MaterialHoleRawStr](const bool& bReturn) + { + FHoudiniEngineUtils::FreeRawStringMemory(MaterialRawStr); + FHoudiniEngineUtils::FreeRawStringMemory(MaterialHoleRawStr); + + return bReturn; + }; + + const int32 QuadComponentCount = ComponentSizeQuads + 1; + for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; + if (!SelectedComponents.Contains(LandscapeComponent)) + continue; + + if (bExportMaterials) + { + // If component has an override material, we need to get the raw name (if exporting materials). + if (LandscapeComponent->OverrideMaterial) + { + MaterialRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideMaterial->GetName()); + } + + // If component has an override hole material, we need to get the raw name (if exporting materials). + if (LandscapeComponent->OverrideHoleMaterial) + { + MaterialHoleRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideHoleMaterial->GetName()); + } + } + + int32 BaseVertIndex = ComponentIdx * VertexCountPerComponent; + for (int32 YIdx = 0; YIdx < ComponentSizeQuads; YIdx++) + { + for (int32 XIdx = 0; XIdx < ComponentSizeQuads; XIdx++) + { + LandscapeIndices[VertIdx + 0] = BaseVertIndex + (XIdx + 0) + (YIdx + 0) * QuadComponentCount; + LandscapeIndices[VertIdx + 1] = BaseVertIndex + (XIdx + 1) + (YIdx + 0) * QuadComponentCount; + LandscapeIndices[VertIdx + 2] = BaseVertIndex + (XIdx + 1) + (YIdx + 1) * QuadComponentCount; + LandscapeIndices[VertIdx + 3] = BaseVertIndex + (XIdx + 0) + (YIdx + 1) * QuadComponentCount; + + // Store override materials (if exporting materials). + if (bExportMaterials) + { + FaceMaterials[QuadIdx] = MaterialRawStr; + FaceHoleMaterials[QuadIdx] = MaterialHoleRawStr; + } + + VertIdx += 4; + QuadIdx++; + } + } + } + + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, LandscapeIndices.GetData(), 0, LandscapeIndices.Num()), + FreeMemoryReturn(false)); + + // We need to generate array of face counts. + TArray LandscapeFaces; + LandscapeFaces.Init(4, QuadCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, LandscapeFaces.GetData(), 0, LandscapeFaces.Num()), + FreeMemoryReturn(false)); + + if (bExportMaterials) + { + if (!FaceMaterials.Contains(nullptr)) + { + // Marshall in override primitive material names. + HAPI_AttributeInfo AttributeInfoPrimitiveMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterial ); + AttributeInfoPrimitiveMaterial.count = FaceMaterials.Num(); + AttributeInfoPrimitiveMaterial.tupleSize = 1; + AttributeInfoPrimitiveMaterial.exists = true; + AttributeInfoPrimitiveMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoPrimitiveMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPrimitiveMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial, + (const char **)FaceMaterials.GetData(), 0, AttributeInfoPrimitiveMaterial.count), + FreeMemoryReturn(false)); + } + + if (!FaceHoleMaterials.Contains(nullptr)) + { + // Marshall in override primitive material hole names. + HAPI_AttributeInfo AttributeInfoPrimitiveMaterialHole; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterialHole); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterialHole ); + AttributeInfoPrimitiveMaterialHole.count = FaceHoleMaterials.Num(); + AttributeInfoPrimitiveMaterialHole.tupleSize = 1; + AttributeInfoPrimitiveMaterialHole.exists = true; + AttributeInfoPrimitiveMaterialHole.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoPrimitiveMaterialHole.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPrimitiveMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoPrimitiveMaterialHole), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoPrimitiveMaterialHole, (const char **)FaceHoleMaterials.GetData(), 0, + AttributeInfoPrimitiveMaterialHole.count), + FreeMemoryReturn(false)); + } + } + + // Free the memory and return true + return FreeMemoryReturn(true); +} + +bool +FUnrealLandscapeTranslator::AddLandscapeGlobalMaterialAttribute( + const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy) +{ + if (!LandscapeProxy) + return false; + + // If there's a global landscape material, we marshall it as detail. + UMaterialInterface * MaterialInterface = LandscapeProxy->GetLandscapeMaterial(); + const char * MaterialNameStr = ""; + if (MaterialInterface) + { + FString FullMaterialName = MaterialInterface->GetPathName(); + MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); + } + + HAPI_AttributeInfo AttributeInfoDetailMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterial ); + AttributeInfoDetailMaterial.count = 1; + AttributeInfoDetailMaterial.tupleSize = 1; + AttributeInfoDetailMaterial.exists = true; + AttributeInfoDetailMaterial.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoDetailMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoDetailMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial, + (const char**)&MaterialNameStr, 0, AttributeInfoDetailMaterial.count), false); + + // If there's a global landscape hole material, we marshall it as detail. + UMaterialInterface * HoleMaterialInterface = LandscapeProxy->GetLandscapeHoleMaterial(); + const char * HoleMaterialNameStr = ""; + if (HoleMaterialInterface) + { + FString FullMaterialName = HoleMaterialInterface->GetPathName(); + MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); + } + + HAPI_AttributeInfo AttributeInfoDetailMaterialHole; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterialHole); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterialHole ); + AttributeInfoDetailMaterialHole.count = 1; + AttributeInfoDetailMaterialHole.tupleSize = 1; + AttributeInfoDetailMaterialHole.exists = true; + AttributeInfoDetailMaterialHole.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoDetailMaterialHole.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoDetailMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoDetailMaterialHole), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoDetailMaterialHole, (const char **)&HoleMaterialNameStr, 0, + AttributeInfoDetailMaterialHole.count), false); + + return true; +} + + +bool +FUnrealLandscapeTranslator::AddLandscapeLayerAttribute( + const HAPI_NodeId& NodeId, const TArray& LandscapeLayerArray, const FString& LayerName) +{ + int32 VertexCount = LandscapeLayerArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoLayer; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLayer); + AttributeInfoLayer.count = VertexCount; + AttributeInfoLayer.tupleSize = 1; + AttributeInfoLayer.exists = true; + AttributeInfoLayer.owner = HAPI_ATTROWNER_POINT; + AttributeInfoLayer.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLayer.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, + TCHAR_TO_ANSI(*LayerName), + &AttributeInfoLayer), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, + TCHAR_TO_ANSI(*LayerName), + &AttributeInfoLayer, + (const float *)LandscapeLayerArray.GetData(), + 0, AttributeInfoLayer.count), false); + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h index b6dafe507..ee33bf81e 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h @@ -1,234 +1,234 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Landscape.h" -#include "HAPI/HAPI_Common.h" - -class ALandscapeProxy; -class UHoudiniInputLandscape; - -struct HOUDINIENGINE_API FUnrealLandscapeTranslator -{ - public: - - // ------------------------------------------------------------------------------------------ - // Unreal Landscape to Houdini Heightfield - // ------------------------------------------------------------------------------------------ - static bool CreateHeightfieldFromLandscape( - ALandscapeProxy* LandcapeProxy, - HAPI_NodeId& CreatedHeightfieldNodeId, - const FString &InputNodeNameStr); - - // Extracts the uint16 values of a given landscape - static bool GetLandscapeData( - ALandscapeProxy* LandscapeProxy, - TArray& HeightData, - int32& XSize, int32& YSize, - FVector& Min, FVector& Max); - - static bool GetLandscapeData( - ULandscapeInfo* LandscapeInfo, - const int32& MinX, - const int32& MinY, - const int32& MaxX, - const int32& MaxY, - TArray& HeightData, - int32& XSize, int32& YSize); - - static void GetLandscapeProxyBounds( - ALandscapeProxy* LandscapeProxy, - FVector& Origin, FVector& Extents); - - // Converts Unreal uint16 values to Houdini Float - static bool ConvertLandscapeDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, - const int32& YSize, - FVector Min, - FVector Max, - const FTransform& LandscapeTransform, - TArray& HeightfieldFloatValues, - HAPI_VolumeInfo& HeightfieldVolumeInfo, - FVector& CenterOffset); - - // Converts Unreal uint8 values to Houdini Float - static bool ConvertLandscapeLayerDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - const FLinearColor& LayerUsageDebugColor, - TArray& LayerFloatValues, - HAPI_VolumeInfo& LayerVolumeInfo); - - // Creates an unlocked heightfield input node - static bool CreateHeightfieldInputNode( - const FString& NodeName, - const int32& XSize, - const int32& YSize, - HAPI_NodeId& HeightfieldNodeId, - HAPI_NodeId& HeightNodeId, - HAPI_NodeId& MaskNodeId, - HAPI_NodeId& MergeNodeId ); - - // Set the volume float value for a heightfield - static bool SetHeighfieldData( - const HAPI_NodeId& AssetId, - const HAPI_PartId& PartId, - TArray< float >& FloatValues, - const HAPI_VolumeInfo& VolumeInfo, - const FString& HeightfieldName); - - static bool AddLandscapeMaterialAttributesToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* InPhysicalMaterial); - - /* - static bool AddLevelPathAttributeToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - const FString& LevelPath); - */ - - // Extracts the uint8 values of a given landscape - static bool GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName); - - static bool GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - const int32& MinX, - const int32& MinY, - const int32& MaxX, - const int32& MaxY, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName); - - // Initialise the Heightfield Mask with default values - static bool InitDefaultHeightfieldMask( - const HAPI_VolumeInfo& HeightVolumeInfo, - const HAPI_NodeId& MaskVolumeNodeId); - - // Landscape nodes clean up - static bool DestroyLandscapeAssetNode( - HAPI_NodeId& ConnectedAssetId, - TArray& CreatedInputAssetIds); - - - //-------------------------------------------------------------------------------------------------- - // Unreal to Houdini - MESH / POINTS - //-------------------------------------------------------------------------------------------------- - - static bool CreateMeshOrPointsFromLandscape( - ALandscapeProxy* InLandscape, - HAPI_NodeId& InputNodeId, - const FString& InInputNodeNameString, - const bool& bExportGeometryAsMesh, - const bool& bExportTileUVs, - const bool bExportNormalizedUVs, - const bool bExportLighting, - const bool bExportMaterials); - - // Extract data from the landscape - static bool ExtractLandscapeData( - ALandscapeProxy * LandscapeProxy, - TSet& SelectedComponents, - const bool& bExportLighting, - const bool& bExportTileUVs, - const bool& bExportNormalizedUVs, - TArray& LandscapePositionArray, - TArray& LandscapeNormalArray, - TArray& LandscapeUVArray, - TArray& LandscapeComponentVertexIndicesArray, - TArray& LandscapeComponentNameArray, - TArray& LandscapeLightmapValues); - - // Helper functions to extract color from a texture - static FColor PickVertexColorFromTextureMip( - const uint8 * MipBytes, - FVector2D & UVCoord, - int32 MipWidth, - int32 MipHeight); - - // Add the Position attribute extracted from a landscape - static bool AddLandscapePositionAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapePositionArray); - - // Add the Normal attribute extracted from a landscape - static bool AddLandscapeNormalAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapeNormalArray); - - // Add the UV attribute extracted from a landscape - static bool AddLandscapeUVAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapeUVArray); - - // Add the Component Vertex Index attribute extracted from a landscape - static bool AddLandscapeComponentVertexIndicesAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeComponentVertexIndicesArray); - - // Add the Component Name attribute extracted from a landscape - static bool AddLandscapeComponentNameAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeComponentNameArray); - - // Add the lightmap color attribute extracted from a landscape - static bool AddLandscapeLightmapColorAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeLightmapValues); - - // Creates and add the vertex indices and face materials attribute from a landscape - static bool AddLandscapeMeshIndicesAndMaterialsAttribute( - const HAPI_NodeId& NodeId, - const bool& bExportMaterials, - const int32& ComponentSizeQuads, - const int32& QuadCount, - ALandscapeProxy * LandscapeProxy, - const TSet& SelectedComponents); - - // Add the global (detail) material and hole material attribute from a landscape - static bool AddLandscapeGlobalMaterialAttribute( - const HAPI_NodeId& NodeId, - ALandscapeProxy * LandscapeProxy); - - // Add landscape layer values as point attributes - static bool AddLandscapeLayerAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeLayerArray, - const FString& LayerName); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Landscape.h" +#include "HAPI/HAPI_Common.h" + +class ALandscapeProxy; +class UHoudiniInputLandscape; + +struct HOUDINIENGINE_API FUnrealLandscapeTranslator +{ + public: + + // ------------------------------------------------------------------------------------------ + // Unreal Landscape to Houdini Heightfield + // ------------------------------------------------------------------------------------------ + static bool CreateHeightfieldFromLandscape( + ALandscapeProxy* LandcapeProxy, + HAPI_NodeId& CreatedHeightfieldNodeId, + const FString &InputNodeNameStr); + + // Extracts the uint16 values of a given landscape + static bool GetLandscapeData( + ALandscapeProxy* LandscapeProxy, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max); + + static bool GetLandscapeData( + ULandscapeInfo* LandscapeInfo, + const int32& MinX, + const int32& MinY, + const int32& MaxX, + const int32& MaxY, + TArray& HeightData, + int32& XSize, int32& YSize); + + static void GetLandscapeProxyBounds( + ALandscapeProxy* LandscapeProxy, + FVector& Origin, FVector& Extents); + + // Converts Unreal uint16 values to Houdini Float + static bool ConvertLandscapeDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, + const int32& YSize, + FVector Min, + FVector Max, + const FTransform& LandscapeTransform, + TArray& HeightfieldFloatValues, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + FVector& CenterOffset); + + // Converts Unreal uint8 values to Houdini Float + static bool ConvertLandscapeLayerDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + const FLinearColor& LayerUsageDebugColor, + TArray& LayerFloatValues, + HAPI_VolumeInfo& LayerVolumeInfo); + + // Creates an unlocked heightfield input node + static bool CreateHeightfieldInputNode( + const FString& NodeName, + const int32& XSize, + const int32& YSize, + HAPI_NodeId& HeightfieldNodeId, + HAPI_NodeId& HeightNodeId, + HAPI_NodeId& MaskNodeId, + HAPI_NodeId& MergeNodeId ); + + // Set the volume float value for a heightfield + static bool SetHeighfieldData( + const HAPI_NodeId& AssetId, + const HAPI_PartId& PartId, + TArray< float >& FloatValues, + const HAPI_VolumeInfo& VolumeInfo, + const FString& HeightfieldName); + + static bool AddLandscapeMaterialAttributesToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* InPhysicalMaterial); + + /* + static bool AddLevelPathAttributeToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + const FString& LevelPath); + */ + + // Extracts the uint8 values of a given landscape + static bool GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName); + + static bool GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + const int32& MinX, + const int32& MinY, + const int32& MaxX, + const int32& MaxY, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName); + + // Initialise the Heightfield Mask with default values + static bool InitDefaultHeightfieldMask( + const HAPI_VolumeInfo& HeightVolumeInfo, + const HAPI_NodeId& MaskVolumeNodeId); + + // Landscape nodes clean up + static bool DestroyLandscapeAssetNode( + HAPI_NodeId& ConnectedAssetId, + TArray& CreatedInputAssetIds); + + + //-------------------------------------------------------------------------------------------------- + // Unreal to Houdini - MESH / POINTS + //-------------------------------------------------------------------------------------------------- + + static bool CreateMeshOrPointsFromLandscape( + ALandscapeProxy* InLandscape, + HAPI_NodeId& InputNodeId, + const FString& InInputNodeNameString, + const bool& bExportGeometryAsMesh, + const bool& bExportTileUVs, + const bool bExportNormalizedUVs, + const bool bExportLighting, + const bool bExportMaterials); + + // Extract data from the landscape + static bool ExtractLandscapeData( + ALandscapeProxy * LandscapeProxy, + TSet& SelectedComponents, + const bool& bExportLighting, + const bool& bExportTileUVs, + const bool& bExportNormalizedUVs, + TArray& LandscapePositionArray, + TArray& LandscapeNormalArray, + TArray& LandscapeUVArray, + TArray& LandscapeComponentVertexIndicesArray, + TArray& LandscapeComponentNameArray, + TArray& LandscapeLightmapValues); + + // Helper functions to extract color from a texture + static FColor PickVertexColorFromTextureMip( + const uint8 * MipBytes, + FVector2D & UVCoord, + int32 MipWidth, + int32 MipHeight); + + // Add the Position attribute extracted from a landscape + static bool AddLandscapePositionAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapePositionArray); + + // Add the Normal attribute extracted from a landscape + static bool AddLandscapeNormalAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapeNormalArray); + + // Add the UV attribute extracted from a landscape + static bool AddLandscapeUVAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapeUVArray); + + // Add the Component Vertex Index attribute extracted from a landscape + static bool AddLandscapeComponentVertexIndicesAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeComponentVertexIndicesArray); + + // Add the Component Name attribute extracted from a landscape + static bool AddLandscapeComponentNameAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeComponentNameArray); + + // Add the lightmap color attribute extracted from a landscape + static bool AddLandscapeLightmapColorAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeLightmapValues); + + // Creates and add the vertex indices and face materials attribute from a landscape + static bool AddLandscapeMeshIndicesAndMaterialsAttribute( + const HAPI_NodeId& NodeId, + const bool& bExportMaterials, + const int32& ComponentSizeQuads, + const int32& QuadCount, + ALandscapeProxy * LandscapeProxy, + const TSet& SelectedComponents); + + // Add the global (detail) material and hole material attribute from a landscape + static bool AddLandscapeGlobalMaterialAttribute( + const HAPI_NodeId& NodeId, + ALandscapeProxy * LandscapeProxy); + + // Add landscape layer values as point attributes + static bool AddLandscapeLayerAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeLayerArray, + const FString& LayerName); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp index 63593286f..ee0fcf6c7 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp @@ -1,4120 +1,4255 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealMeshTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "RawMesh.h" -#include "MeshDescription.h" -#include "MeshDescriptionOperations.h" -#include "Engine/StaticMesh.h" -#include "PhysicsEngine/BodySetup.h" -#include "Engine/StaticMeshSocket.h" -#include "Components/StaticMeshComponent.h" -#include "Materials/Material.h" -#include "Materials/MaterialInterface.h" -#include "MeshAttributes.h" -#include "StaticMeshAttributes.h" - -#if WITH_EDITOR - #include "EditorFramework/AssetImportData.h" -#endif - -bool -FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - UStaticMesh* StaticMesh, - HAPI_NodeId& InputNodeId, - const FString& InputNodeName, - UStaticMeshComponent* StaticMeshComponent /* = nullptr */, - const bool& ExportAllLODs /* = false */, - const bool& ExportSockets /* = false */, - const bool& ExportColliders /* = false */) -{ - // If we don't have a static mesh there's nothing to do. - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Node ID for the newly created node - HAPI_NodeId NewNodeId = -1; - - // Export sockets if there are some - bool DoExportSockets = ExportSockets && (StaticMesh->Sockets.Num() > 0); - - // Export LODs if there are some - bool DoExportLODs = ExportAllLODs && (StaticMesh->GetNumLODs() > 1); - - // Export colliders if there are some - bool DoExportColliders = ExportColliders && StaticMesh->BodySetup != nullptr; - if (DoExportColliders) - { - if (!StaticMesh->BodySetup) - { - DoExportColliders = false; - } - else - { - if (StaticMesh->BodySetup->AggGeom.GetElementCount() <= 0) - DoExportColliders = false; - } - } - - // We need to use a merge node if we export lods OR sockets - bool UseMergeNode = DoExportLODs || DoExportSockets || DoExportColliders; - if (UseMergeNode) - { - // TODO: - // What if OutInputNodeId already exists? - // Delete previous merge?/input? - - // Create a merge SOP asset. This will be our "InputNodeId" - // as all the different LOD meshes and sockets will be plugged into it - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/merge"), InputNodeName, true, &NewNodeId), false); - } - else - { - // No LODs/Sockets, we just need a single input node - // If InputNodeId is invalid, we need to create an input node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_ANSI(*InputNodeName)), false); - - if (!FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true)) - return false; - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); - */ - } - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) - return false; - - HAPI_NodeId PreviousInputNodeId = InputNodeId; - - // Update our input NodeId - InputNodeId = NewNodeId; - // Get our parent OBJ NodeID - HAPI_NodeId InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); - - // We have now created a valid new input node, delete the previous one - if (PreviousInputNodeId >= 0) - { - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); - } - } - - // TODO: - // Setting for lightmap resolution? - //const uint8 ExportMethod = 0; // Raw mesh - //const uint8 ExportMethod = 1; // Mesh description - const uint8 ExportMethod = 2; // LODResources (render mesh) - //bool bExportViaRawMesh = false; - - int32 NumLODsToExport = DoExportLODs ? StaticMesh->GetNumLODs() : 1; - for (int32 LODIndex = 0; LODIndex < NumLODsToExport; LODIndex++) - { - // Grab the LOD level. - FStaticMeshSourceModel & SrcModel = StaticMesh->GetSourceModel(LODIndex); - - // If we're using a merge node, we need to create a new input null - HAPI_NodeId CurrentLODNodeId = -1; - if (UseMergeNode) - { - // Create a new input node for the current LOD - const char * LODName = ""; - { - FString LOD = TEXT("lod") + FString::FromInt(LODIndex); - LODName = TCHAR_TO_UTF8(*LOD); - } - - // Create the node in this input object's OBJ node - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InputObjectNodeId, TEXT("null"), LODName, false, &CurrentLODNodeId), false); - } - else - { - // No merge node, just use the input node we created before - CurrentLODNodeId = NewNodeId; - } - - // Either export the current LOD Mesh by using RawMEsh or MeshDescription (legacy) - FMeshDescription* MeshDesc = nullptr; - // if (!bExportViaRawMesh) - if (ExportMethod == 1) - { - // This will either fetch the mesh description that is cached on the SrcModel - // or load it from bulk data / DDC once - if (SrcModel.MeshDescription.IsValid()) - { - MeshDesc = SrcModel.MeshDescription.Get(); - } - else - { - const double StartTime = FPlatformTime::Seconds(); - MeshDesc = StaticMesh->GetMeshDescription(LODIndex); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->GetMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - } - - bool bMeshSuccess = false; - if (ExportMethod == 1 && MeshDesc) - { - // Convert the Mesh using FMeshDescription - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForMeshDescription( - CurrentLODNodeId, - *MeshDesc, - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - else if (ExportMethod == 2) - { - // Convert the LOD Mesh using FStaticMeshLODResources - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( - CurrentLODNodeId, - StaticMesh->GetLODForExport(LODIndex), - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - else - { - // Convert the LOD Mesh using FRawMesh - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForRawMesh( - CurrentLODNodeId, - SrcModel, - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForRawMesh completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - - if (!bMeshSuccess) - continue; - - if (UseMergeNode) - { - // Connect the LOD node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, LODIndex, CurrentLODNodeId, 0), false); - } - } - - // next Index for adding nodes to the merge - int32 NextMergeIndex = NumLODsToExport; - if (DoExportColliders) - { - FKAggregateGeom SimpleColliders = StaticMesh->BodySetup->AggGeom; - - // Export BOX colliders - for (auto& CurBox : SimpleColliders.BoxElems) - { - FVector BoxCenter = CurBox.Center; - FVector BoxExtent = FVector(CurBox.X, CurBox.Y, CurBox.Z); - FRotator BoxRotation = CurBox.Rotation; - - HAPI_NodeId BoxNodeId = -1; - if (!CreateInputNodeForBox( - BoxNodeId, InputObjectNodeId, NextMergeIndex, - BoxCenter, BoxExtent, BoxRotation)) - continue; - - if (BoxNodeId < 0) - continue; - - // Connect the Box node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, BoxNodeId, 0), false); - - NextMergeIndex++; - } - - // Export SPHERE colliders - for (auto& CurSphere : SimpleColliders.SphereElems) - { - HAPI_NodeId SphereNodeId = -1; - if (!CreateInputNodeForSphere( - SphereNodeId, InputObjectNodeId, NextMergeIndex, - CurSphere.Center, CurSphere.Radius)) - continue; - - if (SphereNodeId < 0) - continue; - - // Connect the Sphere node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, SphereNodeId, 0), false); - - NextMergeIndex++; - } - - // Export CAPSULE colliders - for (auto& CurSphyl : SimpleColliders.SphylElems) - { - HAPI_NodeId SphylNodeId = -1; - if (!CreateInputNodeForSphyl( - SphylNodeId, InputObjectNodeId, NextMergeIndex, - CurSphyl.Center, CurSphyl.Rotation, CurSphyl.Radius, CurSphyl.Length)) - continue; - - if (SphylNodeId < 0) - continue; - - // Connect the capsule node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, SphylNodeId, 0), false); - - NextMergeIndex++; - } - - // Export CONVEX colliders - for (auto& CurConvex : SimpleColliders.ConvexElems) - { - HAPI_NodeId ConvexNodeId = -1; - if (!CreateInputNodeForConvex( - ConvexNodeId, InputObjectNodeId, NextMergeIndex, CurConvex)) - continue; - - if (ConvexNodeId < 0) - continue; - - // Connect the capsule node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, ConvexNodeId, 0), false); - - NextMergeIndex++; - } - } - - if (DoExportSockets && StaticMesh->Sockets.Num() > 0) - { - // Create an input node for the mesh sockets - HAPI_NodeId SocketsNodeId = -1; - if (CreateInputNodeForMeshSockets(StaticMesh->Sockets, InputObjectNodeId, SocketsNodeId)) - { - // We can connect the socket node to the merge node's last input. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), NewNodeId, NextMergeIndex, SocketsNodeId, 0), false); - - NextMergeIndex++; - } - else if (SocketsNodeId != -1) - { - // If we failed to properly export the sockets, clean up the created node - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), SocketsNodeId); - } - } - - // - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForMeshSockets( - const TArray& InMeshSocket, const HAPI_NodeId& InParentNodeId, HAPI_NodeId& OutSocketsNodeId) -{ - int32 NumSockets = InMeshSocket.Num(); - if (NumSockets <= 0) - return false; - - // Create a new input node for the sockets - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeId, TEXT("null"), "sockets", false, &OutSocketsNodeId), false); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.pointCount = NumSockets; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, &Part), false); - - // Create POS point attribute info. - HAPI_AttributeInfo AttributeInfoPos; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); - AttributeInfoPos.count = NumSockets; - AttributeInfoPos.tupleSize = 3; - AttributeInfoPos.exists = true; - AttributeInfoPos.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos), false); - - // Create Rot point attribute Info - HAPI_AttributeInfo AttributeInfoRot; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); - AttributeInfoRot.count = NumSockets; - AttributeInfoRot.tupleSize = 4; - AttributeInfoRot.exists = true; - AttributeInfoRot.owner = HAPI_ATTROWNER_POINT; - AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot), false); - - // Create scale point attribute Info - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = NumSockets; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); - - // Create the name attrib info - HAPI_AttributeInfo AttributeInfoName; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); - AttributeInfoName.count = NumSockets; - AttributeInfoName.tupleSize = 1; - AttributeInfoName.exists = true; - AttributeInfoName.owner = HAPI_ATTROWNER_POINT; - AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName), false); - - // Create the tag attrib info - HAPI_AttributeInfo AttributeInfoTag; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); - AttributeInfoTag.count = NumSockets; - AttributeInfoTag.tupleSize = 1; - AttributeInfoTag.exists = true; - AttributeInfoTag.owner = HAPI_ATTROWNER_POINT; - AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag), false); - - // Extract the sockets transform values - TArray SocketPos; - SocketPos.SetNumZeroed(NumSockets * 3); - TArray SocketRot; - SocketRot.SetNumZeroed(NumSockets * 4); - TArray SocketScale; - SocketScale.SetNumZeroed(NumSockets * 3); - - // raw string array for names and tag, will need to be free before returning - TArray SocketNames; - TArray SocketTags; - - // Lambda for freeing the const char array's memory and returning - auto FreeMemoryReturn = [&SocketNames, &SocketTags](const bool& bReturn) - { - // Frees the memory allocated by ExtractRawString for the names and tags - FHoudiniEngineUtils::FreeRawStringMemory(SocketNames); - FHoudiniEngineUtils::FreeRawStringMemory(SocketTags); - - return bReturn; - }; - - for (int32 Idx = 0; Idx < NumSockets; ++Idx) - { - UStaticMeshSocket* CurrentSocket = InMeshSocket[Idx]; - if (!CurrentSocket || CurrentSocket->IsPendingKill()) - continue; - - // Get the socket's transform and convert it to HapiTransform - FTransform SocketTransform(CurrentSocket->RelativeRotation, CurrentSocket->RelativeLocation, CurrentSocket->RelativeScale); - HAPI_Transform HapiSocketTransform; - FHoudiniApi::Transform_Init(&HapiSocketTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(SocketTransform, HapiSocketTransform); - - // Fill the attribute values - SocketPos[3 * Idx + 0] = HapiSocketTransform.position[0]; - SocketPos[3 * Idx + 1] = HapiSocketTransform.position[1]; - SocketPos[3 * Idx + 2] = HapiSocketTransform.position[2]; - - SocketRot[4 * Idx + 0] = HapiSocketTransform.rotationQuaternion[0]; - SocketRot[4 * Idx + 1] = HapiSocketTransform.rotationQuaternion[1]; - SocketRot[4 * Idx + 2] = HapiSocketTransform.rotationQuaternion[2]; - SocketRot[4 * Idx + 3] = HapiSocketTransform.rotationQuaternion[3]; - - SocketScale[3 * Idx + 0] = HapiSocketTransform.scale[0]; - SocketScale[3 * Idx + 1] = HapiSocketTransform.scale[1]; - SocketScale[3 * Idx + 2] = HapiSocketTransform.scale[2]; - - FString CurrentSocketName; - if (!CurrentSocket->SocketName.IsNone()) - CurrentSocketName = CurrentSocket->SocketName.ToString(); - else - CurrentSocketName = TEXT("Socket") + FString::FromInt(Idx); - SocketNames.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocketName)); - - if (!CurrentSocket->Tag.IsEmpty()) - SocketTags.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocket->Tag)); - else - SocketTags.Add(""); - } - - //we can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos, - SocketPos.GetData(), 0, AttributeInfoPos.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot, - SocketRot.GetData(), 0, AttributeInfoRot.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, - SocketScale.GetData(), 0, AttributeInfoScale.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName, - SocketNames.GetData(), 0, AttributeInfoName.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag, - SocketTags.GetData(), 0, AttributeInfoTag.count), - FreeMemoryReturn(false)); - - // We will also create the socket_details attributes - for (int32 Idx = 0; Idx < NumSockets; ++Idx) - { - // Build the current socket's prefix - FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(Idx); - - // Create mesh_socketX_pos attribute info. - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); - AttributeInfoPos.count = 1; - AttributeInfoPos.tupleSize = 3; - AttributeInfoPos.exists = true; - AttributeInfoPos.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; - - FString PosAttr = SocketAttrPrefix + TEXT("_pos"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos, - &(SocketPos[3 * Idx]), 0, AttributeInfoPos.count), - FreeMemoryReturn(false)); - - // Create mesh_socketX_rot point attribute Info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); - AttributeInfoRot.count = 1; - AttributeInfoRot.tupleSize = 4; - AttributeInfoRot.exists = true; - AttributeInfoRot.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; - - FString RotAttr = SocketAttrPrefix + TEXT("_rot"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot, - &(SocketRot[4 * Idx]), 0, AttributeInfoRot.count), - FreeMemoryReturn(false)); - - // Create mesh_socketX_scale point attribute Info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = 1; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - FString ScaleAttr = SocketAttrPrefix + TEXT("_scale"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale, - &(SocketScale[3 * Idx]), 0, AttributeInfoScale.count), - FreeMemoryReturn(false)); - - // Create the mesh_socketX_name attrib info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); - AttributeInfoName.count = 1; - AttributeInfoName.tupleSize = 1; - AttributeInfoName.exists = true; - AttributeInfoName.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; - - FString NameAttr = SocketAttrPrefix + TEXT("_name"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName, - &(SocketNames[Idx]), 0, AttributeInfoName.count), - FreeMemoryReturn(false)); - - // Create the mesh_socketX_tag attrib info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); - AttributeInfoTag.count = 1; - AttributeInfoTag.tupleSize = 1; - AttributeInfoTag.exists = true; - AttributeInfoTag.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; - - FString TagAttr = SocketAttrPrefix + TEXT("_tag"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag, - &(SocketTags[Idx]), 0, AttributeInfoTag.count), - FreeMemoryReturn(false)); - } - - // Now add the sockets group - const char * SocketGroupStr = "socket_imported"; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr), - FreeMemoryReturn(false)); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, NumSockets); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr, GroupArray.GetData(), 0, NumSockets), - FreeMemoryReturn(false)); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), OutSocketsNodeId), - FreeMemoryReturn(false)); - - return FreeMemoryReturn(true); -} - -bool -FUnrealMeshTranslator::CreateInputNodeForRawMesh( - const HAPI_NodeId& NodeId, - const FStaticMeshSourceModel& SourceModel, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent ) -{ - // Convert the Mesh using FRawMesh - FRawMesh RawMesh; - SourceModel.LoadRawMesh(RawMesh); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = RawMesh.WedgeIndices.Num(); - Part.faceCount = RawMesh.WedgeIndices.Num() / 3; - Part.pointCount = RawMesh.VertexPositions.Num(); - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = RawMesh.VertexPositions.Num(); - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Grab the build scale - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.VertexPositions.Num() > 3) - { - TArray StaticMeshVertices; - StaticMeshVertices.SetNumZeroed(RawMesh.VertexPositions.Num() * 3); - for (int32 VertexIdx = 0; VertexIdx < RawMesh.VertexPositions.Num(); ++VertexIdx) - { - // Convert Unreal to Houdini - const FVector & PositionVector = RawMesh.VertexPositions[VertexIdx]; - StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; - StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; - StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - for (int32 MeshTexCoordIdx = 0; MeshTexCoordIdx < MAX_STATIC_TEXCOORDS; MeshTexCoordIdx++) - { - int32 StaticMeshUVCount = RawMesh.WedgeTexCoords[MeshTexCoordIdx].Num(); - if (StaticMeshUVCount > 0) - { - const TArray & RawMeshUVs = RawMesh.WedgeTexCoords[MeshTexCoordIdx]; - TArray StaticMeshUVs; - StaticMeshUVs.Reserve(StaticMeshUVCount); - - // Transfer UV data. - for (int32 UVIdx = 0; UVIdx < StaticMeshUVCount; UVIdx++) - StaticMeshUVs.Emplace(RawMeshUVs[UVIdx].X, 1.0 - RawMeshUVs[UVIdx].Y, 0); - - // Convert Unreal to Houdini - // We need to re-index UVs for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - // We do not touch wedge 0 of this triangle, Swap 2 and 3 to reverse the winding order. - StaticMeshUVs.SwapMemory(WedgeIdx + 1, WedgeIdx + 2); - } - - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (MeshTexCoordIdx > 0) - UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = StaticMeshUVCount; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, (const float *)StaticMeshUVs.GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentZ.Num() > 0) - { - TArray ChangedNormals(RawMesh.WedgeTangentZ); - - // We need to re-index normals for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentZ1 = ChangedNormals[WedgeIdx + 1]; - FVector TangentZ2 = ChangedNormals[WedgeIdx + 2]; - - ChangedNormals[WedgeIdx + 1] = TangentZ2; - ChangedNormals[WedgeIdx + 2] = TangentZ1; - } - - // We also need to swap the vector's Y and Z components - for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedNormals[WedgeIdx].Y, ChangedNormals[WedgeIdx].Z); - - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedNormals.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, (const float *)ChangedNormals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentX.Num() > 0) - { - TArray ChangedTangentU(RawMesh.WedgeTangentX); - - // We need to re-index tangents for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentU1 = ChangedTangentU[WedgeIdx + 1]; - FVector TangentU2 = ChangedTangentU[WedgeIdx + 2]; - - ChangedTangentU[WedgeIdx + 1] = TangentU2; - ChangedTangentU[WedgeIdx + 2] = TangentU1; - } - - // We also need to swap the vector's Y and Z components - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedTangentU[WedgeIdx].Y, ChangedTangentU[WedgeIdx].Z); - - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedTangentU.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - (const float *)ChangedTangentU.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentY.Num() > 0) - { - TArray ChangedTangentV(RawMesh.WedgeTangentY); - // We need to re-index normals for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentV1 = ChangedTangentV[WedgeIdx + 1]; - FVector TangentV2 = ChangedTangentV[WedgeIdx + 2]; - - ChangedTangentV[WedgeIdx + 1] = TangentV2; - ChangedTangentV[WedgeIdx + 2] = TangentV1; - } - - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedTangentV[WedgeIdx].Y, ChangedTangentV[WedgeIdx].Z); - - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedTangentV.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - (const float *)ChangedTangentV.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - { - // If we have instance override vertex colors on the StaticMeshComponent, - // we first need to propagate them to our copy of the RawMesh Vert Colors - TArray ChangedColors; - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && - StaticMesh->RenderData && - StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; - FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) - { - // Use the wedge map if it is available as it is lossless. - int32 NumWedges = RawMesh.WedgeIndices.Num(); - if (RenderModel.WedgeMap.Num() == NumWedges) - { - int32 NumExistingColors = RawMesh.WedgeColors.Num(); - if (NumExistingColors < NumWedges) - { - RawMesh.WedgeColors.AddUninitialized(NumWedges - NumExistingColors); - } - - // Replace mesh colors with override colors - for (int32 i = 0; i < NumWedges; i++) - { - FColor WedgeColor = FColor::White; - int32 Index = RenderModel.WedgeMap[i]; - if (Index != INDEX_NONE) - { - WedgeColor = ColorVertexBuffer.VertexColor(Index); - } - RawMesh.WedgeColors[i] = WedgeColor; - } - } - } - } - - // See if we have colors to upload. - if (RawMesh.WedgeColors.Num() > 0) - { - ChangedColors.SetNumUninitialized(RawMesh.WedgeColors.Num()); - - // Convert Unreal to Houdini - // We need to re-index colors for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - ChangedColors[WedgeIdx + 0] = RawMesh.WedgeColors[WedgeIdx + 0].ReinterpretAsLinear(); - ChangedColors[WedgeIdx + 1] = RawMesh.WedgeColors[WedgeIdx + 2].ReinterpretAsLinear(); - ChangedColors[WedgeIdx + 2] = RawMesh.WedgeColors[WedgeIdx + 1].ReinterpretAsLinear(); - } - } - - if (ChangedColors.Num() > 0) - { - // Extract the RGB colors - TArray ColorValues; - ColorValues.SetNum(ChangedColors.Num() * 3); - for (int32 colorIndex = 0; colorIndex < ChangedColors.Num(); colorIndex++) - { - ColorValues[colorIndex * 3] = ChangedColors[colorIndex].R; - ColorValues[colorIndex * 3 + 1] = ChangedColors[colorIndex].G; - ColorValues[colorIndex * 3 + 2] = ChangedColors[colorIndex].B; - } - - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedColors.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - ColorValues.GetData(), 0, AttributeInfoVertex.count), false); - - // Create the attribute for Alpha - TArray AlphaValues; - AlphaValues.SetNum(ChangedColors.Num()); - for (int32 alphaIndex = 0; alphaIndex < ChangedColors.Num(); alphaIndex++) - AlphaValues[alphaIndex] = ChangedColors[alphaIndex].A; - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.count = AlphaValues.Num(); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - AlphaValues.GetData(), 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES (VertexList) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeIndices.Num() > 0) - { - TArray StaticMeshIndices; - StaticMeshIndices.SetNumUninitialized(RawMesh.WedgeIndices.Num()); - - // Convert Unreal to Houdini - for (int32 IndexIdx = 0; IndexIdx < RawMesh.WedgeIndices.Num(); IndexIdx += 3) - { - // Swap indices to fix winding order. - StaticMeshIndices[IndexIdx + 0] = RawMesh.WedgeIndices[IndexIdx + 0]; - StaticMeshIndices[IndexIdx + 1] = RawMesh.WedgeIndices[IndexIdx + 2]; - StaticMeshIndices[IndexIdx + 2] = RawMesh.WedgeIndices[IndexIdx + 1]; - } - - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, StaticMeshIndices.GetData(), 0, StaticMeshIndices.Num()), false); - - // We need to generate array of face counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, StaticMeshFaceCounts.GetData(), 0, StaticMeshFaceCounts.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - // Marshall face material indices. - if (RawMesh.FaceMaterialIndices.Num() > 0) - { - // Create an array of Material Interfaces - TArray MaterialInterfaces; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()) - { - // StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); - - int32 NumMeshBasedMaterials = StaticMeshComponent->GetNumMaterials(); - TArray MeshBasedMaterialInterfaces; - MeshBasedMaterialInterfaces.SetNumUninitialized(NumMeshBasedMaterials); - for (int32 i = 0; i < NumMeshBasedMaterials; i++) - MeshBasedMaterialInterfaces[i] = StaticMeshComponent->GetMaterial(i); - - int32 NumSections = StaticMesh->GetNumSections(InLODIndex); - MaterialInterfaces.SetNumUninitialized(NumSections); - for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) - { - FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex); - check(Info.MaterialIndex < NumMeshBasedMaterials); - MaterialInterfaces[SectionIndex] = MeshBasedMaterialInterfaces[Info.MaterialIndex]; - } - } - else - { - // Query the Static mesh's materials - for (int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++) - { - MaterialInterfaces.Add(StaticMesh->GetMaterial(MatIdx)); - } - - // Try to fix up inconsistencies between the RawMesh / StaticMesh material indexes - // by using the meshes sections... - // TODO: Fix me properly! - // Proper fix would be to export the meshes via the FStaticMeshLODResources obtained - // by GetLODForExport(), and then export the mesh by sections. - if (StaticMesh->RenderData && StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - TMap MapOfMaterials; - FStaticMeshLODResources& LODResources = StaticMesh->RenderData->LODResources[InLODIndex]; - for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) - { - // Get the material for each element at the current lod index - int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; - if (!MapOfMaterials.Contains(MaterialIndex)) - { - MapOfMaterials.Add(MaterialIndex, StaticMesh->GetMaterial(MaterialIndex)); - } - } - - if (MapOfMaterials.Num() > 0) - { - // Sort the output material in the correct order (by material index) - MapOfMaterials.KeySort([](int32 A, int32 B) { return A < B; }); - - // Set the value in the correct order - // Do not reduce the array of materials, this could cause crahses in some weird cases.. - if (MapOfMaterials.Num() > MaterialInterfaces.Num()) - MaterialInterfaces.SetNumZeroed(MapOfMaterials.Num()); - - int32 MaterialIndex = 0; - for (auto Kvp : MapOfMaterials) - { - MaterialInterfaces[MaterialIndex++] = Kvp.Value; - } - } - } - } - - // Create list of materials, one for each face. - TArray StaticMeshFaceMaterials; - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - RawMesh.FaceMaterialIndices.Num(), - StaticMeshFaceMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(StaticMeshFaceMaterials); - - // Delete texture material parameter names - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING MASKS - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.FaceSmoothingMasks.Num() > 0) - { - HAPI_AttributeInfo AttributeInfoSmoothingMasks; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - AttributeInfoSmoothingMasks.count = RawMesh.FaceSmoothingMasks.Num(); - AttributeInfoSmoothingMasks.tupleSize = 1; - AttributeInfoSmoothingMasks.exists = true; - AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - (const int32 *)RawMesh.FaceSmoothingMasks.GetData(), 0, RawMesh.FaceSmoothingMasks.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - TArray AllTags; - for (auto& ComponentTag : StaticMeshComponent->ComponentTags) - AllTags.AddUnique(ComponentTag); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - for (auto& ActorTag : ParentActor->Tags) - AllTags.AddUnique(ActorTag); - } - - // Try to create groups for the tags - if (!FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, AllTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups for the Static Mesh Component and Actor tags!")); - - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - /* - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - -bool -FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( - const HAPI_NodeId& NodeId, - const FStaticMeshLODResources& LODResources, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent) -{ - // Convert the Mesh using FStaticMeshLODResources - - // Check that the mesh is not empty - if (LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0) - { - HOUDINI_LOG_ERROR(TEXT("No vertices in mesh!")); - return false; - } - - if (LODResources.Sections.Num() == 0) - { - HOUDINI_LOG_ERROR(TEXT("No triangles in mesh!")); - return false; - } - - // Vertex instance and triangle counts - const uint32 OrigNumVertexInstances = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); - const uint32 NumTriangles = LODResources.GetNumTriangles(); - const uint32 NumVertexInstances = NumTriangles * 3; - const uint32 NumSections = LODResources.Sections.Num(); - - // Grab the build scale - const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - // In FStaticMeshLODResources each vertex instances stores its position, even if the positions are not unique (in other - // words, in Houdini terminology, the number of points and vertices are the same. We'll do the same thing that Epic - // does in FBX export: we'll run through all vertex instances and use a hash to determine which instances share a - // position, so that we can a smaller number of points than vertices, and vertices share point positions - TArray UEVertexInstanceIdxToPointIdx; - UEVertexInstanceIdxToPointIdx.Reserve(OrigNumVertexInstances); - - TMap PositionToPointIndexMap; - PositionToPointIndexMap.Reserve(OrigNumVertexInstances); - - TArray StaticMeshVertices; - StaticMeshVertices.Reserve(OrigNumVertexInstances * 3); - for (uint32 VertexInstanceIndex = 0; VertexInstanceIndex < OrigNumVertexInstances; ++VertexInstanceIndex) - { - // Convert Unreal to Houdini - const FVector &PositionVector = LODResources.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexInstanceIndex); - const int32 *FoundPointIndexPtr = PositionToPointIndexMap.Find(PositionVector); - if (!FoundPointIndexPtr) - { - const int32 NewPointIndex = StaticMeshVertices.Add(PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X) / 3; - StaticMeshVertices.Add(PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z); - StaticMeshVertices.Add(PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y); - - PositionToPointIndexMap.Add(PositionVector, NewPointIndex); - UEVertexInstanceIdxToPointIdx.Add(NewPointIndex); - } - else - { - UEVertexInstanceIdxToPointIdx.Add(*FoundPointIndexPtr); - } - } - - StaticMeshVertices.Shrink(); - const uint32 NumVertices = StaticMeshVertices.Num() / 3; - - // Now that we know how many vertices (points), vertex instances (vertices) and triagnles we have, - // we can create the part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumVertexInstances; - Part.faceCount = NumTriangles; - Part.pointCount = NumVertices; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = Part.pointCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - - // Determine which attributes we have - const bool bIsVertexInstanceNormalsValid = true; - const bool bIsVertexInstanceTangentsValid = true; - const bool bIsVertexInstanceBinormalsValid = true; - const bool bIsVertexInstanceColorsValid = LODResources.bHasColorVertexData; - const uint32 NumUVLayers = FMath::Min(LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(), MAX_STATIC_TEXCOORDS); - const bool bIsVertexInstanceUVsValid = NumUVLayers > 0; - - bool bUseComponentOverrideColors = false; - // Determine if have override colors on the static mesh component, if so prefer to use those - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (ColorVertexBuffer.GetNumVertices() == LODResources.GetNumVertices()) - { - bUseComponentOverrideColors = true; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL INDEX -> MATERIAL INTERFACE - //--------------------------------------------------------------------------------------------------------------------- - TArray MaterialInterfaces; - TArray TriangleMaterialIndices; - - // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); - const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); - // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, - // then we will set UEDefaultMaterial at the invalid index - int32 UEDefaultMaterialIndex = INDEX_NONE; - UMaterialInterface *UEDefaultMaterial = nullptr; - if (NumStaticMaterials > 0) - { - MaterialInterfaces.Reserve(NumStaticMaterials); - for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) - { - const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; - UMaterialInterface *Material = nullptr; - if (bIsStaticMeshComponentValid) - { - Material = StaticMeshComponent->GetMaterial(MaterialIndex); - } - else - { - Material = MaterialInfo.MaterialInterface; - } - // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - UEDefaultMaterialIndex = MaterialIndex; - } - Material = UEDefaultMaterial; - HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); - } - // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); - MaterialInterfaces.Add(Material); - } - - TriangleMaterialIndices.Reserve(NumTriangles); - } - - // If we haven't created UEDefaultMaterial yet, check that all the sections' MaterialIndex - // is valid, if not, create UEDefaultMaterial and add to MaterialInterfaces to get UEDefaultMaterialIndex - if (!UEDefaultMaterial || UEDefaultMaterialIndex == INDEX_NONE) - { - for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) - { - // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial - // up to and including MaterialIndex and log a warning - const int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; - if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - // Add the UEDefaultMaterial to MaterialInterfaces - UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); - } - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); - } - } - } - - // Determine the final number of materials we have, with default for missing/invalid indices - const int32 NumMaterials = MaterialInterfaces.Num(); - - // Now we deal with vertex instance attributes. - if (NumTriangles > 0) - { - // UV layer array. Each layer has an array of floats, 3 floats per vertex instance - TArray> UVs; - // Normals: 3 floats per vertex instance - TArray Normals; - // Tangents: 3 floats per vertex instance - TArray Tangents; - // Binormals: 3 floats per vertex instance - TArray Binormals; - // RGBColors: 3 floats per vertex instance - TArray RGBColors; - // Alphas: 1 float per vertex instance - TArray Alphas; - - // Initialize the arrays for the attributes that are valid - if (bIsVertexInstanceUVsValid) - { - UVs.SetNum(NumUVLayers); - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); - } - } - - if (bIsVertexInstanceNormalsValid) - { - Normals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceTangentsValid) - { - Tangents.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceBinormalsValid) - { - Binormals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - RGBColors.SetNumUninitialized(NumVertexInstances * 3); - Alphas.SetNumUninitialized(NumVertexInstances); - } - - // Array of vertex (point position) indices per triangle - TArray MeshTriangleVertexIndices; - MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); - // Array of vertex counts per triangle/face - TArray MeshTriangleVertexCounts; - MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); - - int32 TriangleIdx = 0; - int32 HoudiniVertexIdx = 0; - FIndexArrayView TriangleVertexIndices = LODResources.IndexBuffer.GetArrayView(); - for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) - { - const FStaticMeshSection& Section = LODResources.Sections[SectionIndex]; - for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < Section.NumTriangles; ++SectionTriangleIndex) - { - MeshTriangleVertexCounts[TriangleIdx] = 3; - for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) - { - // Reverse the winding order for Houdini (but still start at 0) - const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; - const uint32 UEVertexIndex = TriangleVertexIndices[Section.FirstIndex + SectionTriangleIndex * 3 + WindingIdx]; - - // Calculate the index of the first component of a vertex instance's value in an inline float array - // representing vectors (3 float) per vertex instance - const int32 Float3Index = HoudiniVertexIdx * 3; - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - const FVector2D &UV = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(UEVertexIndex, UVLayerIndex); - UVs[UVLayerIndex][Float3Index + 0] = UV.X; - UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; - UVs[UVLayerIndex][Float3Index + 2] = 0; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - const FVector &Normal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(UEVertexIndex); - Normals[Float3Index + 0] = Normal.X; - Normals[Float3Index + 1] = Normal.Z; - Normals[Float3Index + 2] = Normal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - const FVector &Tangent = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(UEVertexIndex); - Tangents[Float3Index + 0] = Tangent.X; - Tangents[Float3Index + 1] = Tangent.Z; - Tangents[Float3Index + 2] = Tangent.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - // In order to calculate the binormal we also need the tangent and normal - if (bIsVertexInstanceBinormalsValid) - { - FVector Binormal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(UEVertexIndex); - Binormals[Float3Index + 0] = Binormal.X; - Binormals[Float3Index + 1] = Binormal.Z; - Binormals[Float3Index + 2] = Binormal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - FVector4 Color = FLinearColor::White; - if (bUseComponentOverrideColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - Color = ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); - } - else - { - Color = LODResources.VertexBuffers.ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); - } - RGBColors[Float3Index + 0] = Color[0]; - RGBColors[Float3Index + 1] = Color[1]; - RGBColors[Float3Index + 2] = Color[2]; - Alphas[HoudiniVertexIdx] = Color[3]; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - if (UEVertexInstanceIdxToPointIdx.IsValidIndex(UEVertexIndex)) - { - MeshTriangleVertexIndices[HoudiniVertexIdx] = UEVertexInstanceIdxToPointIdx[UEVertexIndex]; - } - - HoudiniVertexIdx++; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - if (MaterialInterfaces.IsValidIndex(Section.MaterialIndex)) - { - TriangleMaterialIndices.Add(Section.MaterialIndex); - } - else - { - TriangleMaterialIndices.Add(UEDefaultMaterialIndex); - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, Section.MaterialIndex, *(UEDefaultMaterial->GetPathName())); - } - - TriangleIdx++; - } - } - - // Now transfer valid vertex instance attributes to Houdini vertex attributes - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) - { - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (UVLayerIndex > 0) - UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = NumVertexInstances; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, Normals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - Tangents.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceBinormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - Binormals.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - RGBColors.GetData(), 0, AttributeInfoVertex.count), false); - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.count = Alphas.Num(); - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - Alphas.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); - - // Send the array of face vertex counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); - - // Send material assignments to Houdini - if (NumMaterials > 0) - { - // Create list of materials, one for each face. - TArray TriangleMaterials; - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); - - // Delete texture parameter attribute names. - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - - } - - // TODO: The render mesh (LODResources) does not have face smoothing information, and the raw mesh triangle order is - // potentially different (see also line 4152 TODO_FBX in Engine\Source\Editor\UnrealEd\Private\Fbx\FbxMainExport.cpp - ////--------------------------------------------------------------------------------------------------------------------- - //// TRIANGLE SMOOTHING MASKS - ////--------------------------------------------------------------------------------------------------------------------- - //TArray TriangleSmoothingMasks; - //TriangleSmoothingMasks.SetNumZeroed(NumTriangles); - //FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); - //if (TriangleSmoothingMasks.Num() > 0) - //{ - // HAPI_AttributeInfo AttributeInfoSmoothingMasks; - // FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - // AttributeInfoSmoothingMasks.tupleSize = 1; - // AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); - // AttributeInfoSmoothingMasks.exists = true; - // AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - // AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - // AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - // FHoudiniEngine::Get().GetSession(), - // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - // FHoudiniEngine::Get().GetSession(), - // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - // (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); - //} - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - // Try to create groups for the static mesh component's tags - if (StaticMeshComponent->ComponentTags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Try to create groups for the parent Actor's tags - if (ParentActor->Tags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - - /* - // Add the unreal_level_path attribute - FString LevelPath = ActorPath;// FString(); - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - -bool -FUnrealMeshTranslator::CreateInputNodeForMeshDescription( - const HAPI_NodeId& NodeId, - const FMeshDescription& MeshDescription, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent) -{ - // Convert the Mesh using FMeshDescription - // Get references to the attributes we are interested in - // before sending to Houdini we'll check if each attribute is valid - FStaticMeshConstAttributes MeshDescriptionAttributes(MeshDescription); - - TVertexAttributesConstRef VertexPositions = MeshDescriptionAttributes.GetVertexPositions(); - TVertexInstanceAttributesConstRef VertexInstanceNormals = MeshDescriptionAttributes.GetVertexInstanceNormals(); - TVertexInstanceAttributesConstRef VertexInstanceTangents = MeshDescriptionAttributes.GetVertexInstanceTangents(); - TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = MeshDescriptionAttributes.GetVertexInstanceBinormalSigns(); - TVertexInstanceAttributesConstRef VertexInstanceColors = MeshDescriptionAttributes.GetVertexInstanceColors(); - TVertexInstanceAttributesConstRef VertexInstanceUVs = MeshDescriptionAttributes.GetVertexInstanceUVs(); - //TPolygonGroupAttributesConstRef PolygonGroupMaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames(); - - // Get the vertex and triangle arrays - const FVertexArray &MDVertices = MeshDescription.Vertices(); - const FPolygonGroupArray &MDPolygonGroups = MeshDescription.PolygonGroups(); - const FPolygonArray &MDPolygons = MeshDescription.Polygons(); - const FTriangleArray &MDTriangles = MeshDescription.Triangles(); - - // Determine point, vertex and polygon counts - const uint32 NumVertices = MDVertices.Num(); - const uint32 NumTriangles = MDTriangles.Num(); - const uint32 NumVertexInstances = NumTriangles * 3; - - // Some checks: we expect triangulated meshes - if (MeshDescription.VertexInstances().Num() != NumTriangles * 3) - { - HOUDINI_LOG_ERROR(TEXT("Expected a triangulated mesh, but # VertexInstances (%d) != # Triangles * 3 (%d)"), MeshDescription.VertexInstances().Num(), NumTriangles * 3); - return false; - } - - // Determine which attributes we have - const bool bIsVertexPositionsValid = VertexPositions.IsValid(); - const bool bIsVertexInstanceNormalsValid = VertexInstanceNormals.IsValid(); - const bool bIsVertexInstanceTangentsValid = VertexInstanceTangents.IsValid(); - const bool bIsVertexInstanceBinormalSignsValid = VertexInstanceBinormalSigns.IsValid(); - const bool bIsVertexInstanceColorsValid = VertexInstanceColors.IsValid(); - const bool bIsVertexInstanceUVsValid = VertexInstanceUVs.IsValid(); - //const bool bIsPolygonGroupImportedMaterialSlotNamesValid = PolygonGroupMaterialSlotNames.IsValid(); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumVertexInstances; - Part.faceCount = NumTriangles; - Part.pointCount = NumVertices; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = Part.pointCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Grab the build scale - const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - // The mesh element arrays are sparse: the max index/ID value can be larger than the number of elements - 1 - // so we have to maintain a lookup of VertexID (UE) to PointIndex (Houdini) - TArray VertexIDToHIndex; - if (bIsVertexPositionsValid && VertexPositions.GetNumElements() >= 3) - { - TArray StaticMeshVertices; - StaticMeshVertices.SetNumUninitialized(NumVertices * 3); - - int32 VertexIdx = 0; - VertexIDToHIndex.Init(INDEX_NONE, MDVertices.GetArraySize()); - - for (const FVertexID& VertexID : MDVertices.GetElementIDs()) - { - // Convert Unreal to Houdini - const FVector &PositionVector = VertexPositions.Get(VertexID); - StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; - StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; - StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; - - // Record the UE Vertex ID to Houdini Point Index lookup - VertexIDToHIndex[VertexID.GetValue()] = VertexIdx; - VertexIdx++; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - } - - bool bUseComponentOverrideColors = false; - // Determine if have override colors on the static mesh component, if so prefer to use those - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && - StaticMesh->RenderData && - StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; - FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) - { - // Use the wedge map if it is available as it is lossless. - if (RenderModel.WedgeMap.Num() == NumVertexInstances) - { - bUseComponentOverrideColors = true; - } - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL SLOT -> MATERIAL INTERFACE - //--------------------------------------------------------------------------------------------------------------------- - // In theory the ImportedMaterialSlotName attribute on PolygonGroups should tell us which material slot is used by - // that group, and thus which MaterialIndex we should assign to triangles in that group. Unfortunately we have - // encountered cases where the ImportedMaterialSlotName name attribute does not match any of the MaterialSlotName or - // ImportedMaterialSlotNames in the StaticMesh->StaticMaterials array. Therefore we have no choice but to rely - // on the PolygonGroup order vs Section order to determine the MaterialIndex for a group. We do what Epic does - // when building a static mesh: Sections are created in the same order as iterating over PolygonGroups, but importantly, - // empty PolygonGroups are skipped - - // // Get material slot name to material index - // and the UMaterialInterface array - // TMap MaterialSlotToInterface; - TArray MaterialInterfaces; - TArray TriangleMaterialIndices; - - // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); - const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); - // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, - // then we will set UEDefaultMaterial at the invalid index - int32 UEDefaultMaterialIndex = INDEX_NONE; - UMaterialInterface *UEDefaultMaterial = nullptr; - if (NumStaticMaterials > 0) - { - MaterialInterfaces.Reserve(NumStaticMaterials); - for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) - { - const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; - UMaterialInterface *Material = nullptr; - if (bIsStaticMeshComponentValid) - { - Material = StaticMeshComponent->GetMaterial(MaterialIndex); - } - else - { - Material = MaterialInfo.MaterialInterface; - } - // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - UEDefaultMaterialIndex = MaterialIndex; - } - Material = UEDefaultMaterial; - HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); - } - // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); - MaterialInterfaces.Add(Material); - } - - TriangleMaterialIndices.Reserve(NumTriangles); - } - // SectionIndex: Looking at Epic's StaticMesh build code, Sections are created in the same - // order as iterating over PolygonGroups, but skipping empty PolygonGroups - TMap PolygonGroupToMaterialIndex; - PolygonGroupToMaterialIndex.Reserve(MeshDescription.PolygonGroups().Num()); - int32 SectionIndex = 0; - for (const FPolygonGroupID &PolygonGroupID : MDPolygonGroups.GetElementIDs()) - { - // Skip empty polygon groups - if (MeshDescription.GetNumPolygonGroupPolygons(PolygonGroupID) == 0) - { - continue; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - // // Get the material index for the material slot for this polygon group - //int32 MaterialIndex = INDEX_NONE; - //if (bIsPolygonGroupImportedMaterialSlotNamesValid) - //{ - // const FName &MaterialSlotName = PolygonGroupMaterialSlotNames.Get(PolygonGroupID); - // const int32 *MaterialIndexPtr = MaterialSlotToInterface.Find(MaterialSlotName); - // if (MaterialIndexPtr != nullptr) - // { - // MaterialIndex = *MaterialIndexPtr; - // } - //} - - // Get the material for the LOD and section via the section info map - if (StaticMesh->GetNumSections(InLODIndex) <= SectionIndex) - { - HOUDINI_LOG_ERROR(TEXT("Found more non-empty polygon groups in the mesh description for LOD %d than sections in the static mesh..."), InLODIndex); - return false; - } - - // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial - // up to and including MaterialIndex and log a warning - int32 MaterialIndex = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex).MaterialIndex; - if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - // Add the UEDefaultMaterial to MaterialInterfaces - UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); - } - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); - MaterialIndex = UEDefaultMaterialIndex; - } - - PolygonGroupToMaterialIndex.Add(PolygonGroupID, MaterialIndex); - SectionIndex++; - } - - // Determine the final number of materials we have, with defaults for missing/invalid indices - const int32 NumMaterials = MaterialInterfaces.Num(); - - // Now we deal with vertex instance attributes. - // // First we must also record a UE VertexInstanceID to Houdini Vertex Index lookup, - // // and then get and convert all valid and supported vertex instance attributes from UE - // TArray VertexInstanceIDToHIndex; - - if (NumTriangles > 0) - { - // UV layer array. Each layer has an array of floats, 3 floats per vertex instance - TArray> UVs; - const int32 NumUVLayers = bIsVertexInstanceUVsValid ? FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_STATIC_TEXCOORDS) : 0; - // Normals: 3 floats per vertex instance - TArray Normals; - // Tangents: 3 floats per vertex instance - TArray Tangents; - // Binormals: 3 floats per vertex instance - TArray Binormals; - // RGBColors: 3 floats per vertex instance - TArray RGBColors; - // Alphas: 1 float per vertex instance - TArray Alphas; - - // Initialize the arrays for the attributes that are valid - if (bIsVertexInstanceUVsValid) - { - UVs.SetNum(NumUVLayers); - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); - } - } - - if (bIsVertexInstanceNormalsValid) - { - Normals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceTangentsValid) - { - Tangents.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceBinormalSignsValid) - { - Binormals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - RGBColors.SetNumUninitialized(NumVertexInstances * 3); - Alphas.SetNumUninitialized(NumVertexInstances); - } - - // Array of material index per triangle/face - TArray MeshTriangleVertexIndices; - MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); - // Array of vertex counts per triangle/face - TArray MeshTriangleVertexCounts; - MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); - - int32 TriangleIdx = 0; - int32 VertexInstanceIdx = 0; - // VertexInstanceIDToHIndex.Init(-1, MDVertexInstances.GetArraySize()); - - for (const FPolygonID &PolygonID : MDPolygons.GetElementIDs()) - { - for (const FTriangleID &TriangleID : MeshDescription.GetPolygonTriangleIDs(PolygonID)) - { - MeshTriangleVertexCounts[TriangleIdx] = 3; - for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) - { - // Reverse the winding order for Houdini (but still start at 0) - const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; - const FVertexInstanceID &VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, WindingIdx); - - // // UE Vertex Instance ID to Houdini Vertex Index look up - // VertexInstanceIDToHIndex[VertexInstanceID.GetValue()] = VertexInstanceIdx; - - // Calculate the index of the first component of a vertex instance's value in an inline float array - // representing vectors (3 float) per vertex instance - const int32 Float3Index = VertexInstanceIdx * 3; - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - const FVector2D &UV = VertexInstanceUVs.Get(VertexInstanceID, UVLayerIndex); - UVs[UVLayerIndex][Float3Index + 0] = UV.X; - UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; - UVs[UVLayerIndex][Float3Index + 2] = 0; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - const FVector &Normal = VertexInstanceNormals.Get(VertexInstanceID); - Normals[Float3Index + 0] = Normal.X; - Normals[Float3Index + 1] = Normal.Z; - Normals[Float3Index + 2] = Normal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - const FVector &Tangent = VertexInstanceTangents.Get(VertexInstanceID); - Tangents[Float3Index + 0] = Tangent.X; - Tangents[Float3Index + 1] = Tangent.Z; - Tangents[Float3Index + 2] = Tangent.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - // In order to calculate the binormal we also need the tangent and normal - if (bIsVertexInstanceBinormalSignsValid && bIsVertexInstanceTangentsValid && bIsVertexInstanceNormalsValid) - { - const float &BinormalSign = VertexInstanceBinormalSigns.Get(VertexInstanceID); - FVector Binormal = FVector::CrossProduct( - FVector(Tangents[Float3Index + 0], Tangents[Float3Index + 1], Tangents[Float3Index + 2]), - FVector(Normals[Float3Index + 0], Normals[Float3Index + 1], Normals[Float3Index + 2]) - ) * BinormalSign; - Binormals[Float3Index + 0] = Binormal.X; - Binormals[Float3Index + 1] = Binormal.Y; - Binormals[Float3Index + 2] = Binormal.Z; - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - FVector4 Color = FLinearColor::White; - if (bUseComponentOverrideColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshLODResources& RenderModel = StaticMesh->RenderData->LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - int32 Index = RenderModel.WedgeMap[VertexInstanceIdx]; - if (Index != INDEX_NONE) - { - Color = ColorVertexBuffer.VertexColor(Index).ReinterpretAsLinear(); - } - } - else - { - Color = VertexInstanceColors.Get(VertexInstanceID); - } - RGBColors[Float3Index + 0] = Color[0]; - RGBColors[Float3Index + 1] = Color[1]; - RGBColors[Float3Index + 2] = Color[2]; - Alphas[VertexInstanceIdx] = Color[3]; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - const FVertexID& VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID); - const int32 UEVertexIdx = VertexID.GetValue(); - if (VertexIDToHIndex.IsValidIndex(UEVertexIdx)) - { - MeshTriangleVertexIndices[VertexInstanceIdx] = VertexIDToHIndex[UEVertexIdx]; - } - - VertexInstanceIdx++; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - const FPolygonGroupID &PolygonGroupID = MeshDescription.GetPolygonPolygonGroup(PolygonID); - const int32 MaterialIndex = PolygonGroupToMaterialIndex.FindChecked(PolygonGroupID); - TriangleMaterialIndices.Add(MaterialIndex); - - TriangleIdx++; - } - } - - // Now transfer valid vertex instance attributes to Houdini vertex attributes - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) - { - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (UVLayerIndex > 0) - UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = NumVertexInstances; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, Normals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - Tangents.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceBinormalSignsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - Binormals.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - RGBColors.GetData(), 0, AttributeInfoVertex.count), false); - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.count = Alphas.Num(); - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - Alphas.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); - - // Send the array of face vertex counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); - - // Send material assignments to Houdini - if (NumMaterials > 0) - { - // Create list of materials, one for each face. - TArray TriangleMaterials; - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bool bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterialIndices.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); - - // Delete texture material parameter names. - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (bAttributeSuccess) - { - check(0); - return false; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE SMOOTHING MASKS - //--------------------------------------------------------------------------------------------------------------------- - TArray TriangleSmoothingMasks; - TriangleSmoothingMasks.SetNumZeroed(NumTriangles); - FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); - if (TriangleSmoothingMasks.Num() > 0) - { - HAPI_AttributeInfo AttributeInfoSmoothingMasks; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - AttributeInfoSmoothingMasks.tupleSize = 1; - AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); - AttributeInfoSmoothingMasks.exists = true; - AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - // Try to create groups for the static mesh component's tags - if (StaticMeshComponent->ComponentTags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Try to create groups for the parent Actor's tags - if (ParentActor->Tags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - - /* - FString LevelPath = FString(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - - -void -FUnrealMeshTranslator::CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray& OutStaticMeshFaceMaterials, - TMap> & OutScalarMaterialParameters, - TMap> & OutVectorMaterialParameters, - TMap> & OutTextureMaterialParameters) -{ - // We need to create list of unique materials. - TArray< char * > UniqueMaterialList; - - UMaterialInterface * MaterialInterface = nullptr; - char* UniqueName = nullptr; - - UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); - - // Initialize material parameter arrays - TMap> ScalarParams; - TMap> VectorParams; - TMap> TextureParams; - - if (Materials.Num()) - { - // We have materials. - for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) - { - UniqueName = nullptr; - MaterialInterface = Materials[MaterialIdx]; - if (!MaterialInterface) - { - // Null material interface found, add default instead. - UniqueMaterialList.Add(DefaultMaterialName); - - // No need to collect material parameters on the default material - continue; - } - - // We found a material, get its name and material parameters - FString FullMaterialName = MaterialInterface->GetPathName(); - UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); - UniqueMaterialList.Add(UniqueName); - - // Collect all scalar parameters in all materials - { - TArray MaterialScalarParamInfos; - TArray MaterialScalarParamGuids; - MaterialInterface->GetAllScalarParameterInfo(MaterialScalarParamInfos, MaterialScalarParamGuids); - - for (auto & CurScalarParam : MaterialScalarParamInfos) - { - FString CurScalarParamName = CurScalarParam.Name.ToString(); - float CurScalarVal; - MaterialInterface->GetScalarParameterValue(CurScalarParam, CurScalarVal); - if (!ScalarParams.Contains(CurScalarParamName)) - { - TArray CurArray; - CurArray.SetNumUninitialized(Materials.Num()); - // Initialize the array with the Min float value - for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) - CurArray[ArrIdx] = FLT_MIN; - - ScalarParams.Add(CurScalarParamName, CurArray); - OutScalarMaterialParameters.Add(CurScalarParamName); - } - - ScalarParams[CurScalarParamName][MaterialIdx] = CurScalarVal; - } - } - - // Collect all vector parameters in all materials - { - TArray MaterialVectorParamInfos; - TArray MaterialVectorParamGuids; - MaterialInterface->GetAllVectorParameterInfo(MaterialVectorParamInfos, MaterialVectorParamGuids); - - for (auto & CurVectorParam : MaterialVectorParamInfos) - { - FString CurVectorParamName = CurVectorParam.Name.ToString(); - FLinearColor CurVectorValue; - MaterialInterface->GetVectorParameterValue(CurVectorParam, CurVectorValue); - if (!VectorParams.Contains(CurVectorParamName)) - { - TArray CurArray; - CurArray.SetNumUninitialized(Materials.Num()); - FLinearColor MinColor(FLT_MIN, FLT_MIN, FLT_MIN, FLT_MIN); - for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) - CurArray[ArrIdx] = MinColor; - - VectorParams.Add(CurVectorParamName, CurArray); - OutVectorMaterialParameters.Add(CurVectorParamName); - } - - VectorParams[CurVectorParamName][MaterialIdx] = CurVectorValue; - } - } - - // Collect all texture parameters in all materials - { - TArray MaterialTextureParamInfos; - TArray MaterialTextureParamGuids; - MaterialInterface->GetAllTextureParameterInfo(MaterialTextureParamInfos, MaterialTextureParamGuids); - - for (auto & CurTextureParam : MaterialTextureParamInfos) - { - FString CurTextureParamName = CurTextureParam.Name.ToString(); - UTexture * CurTexture = nullptr; - MaterialInterface->GetTextureParameterValue(CurTextureParam, CurTexture); - - if (!CurTexture || CurTexture->IsPendingKill()) - continue; - - FString TexturePath = CurTexture->GetPathName(); - if (!TextureParams.Contains(CurTextureParamName)) - { - TArray CurArray; - CurArray.SetNumZeroed(Materials.Num()); - - TextureParams.Add(CurTextureParamName, CurArray); - OutTextureMaterialParameters.Add(CurTextureParamName); - } - - char * TexturePathRawStr = UniqueName = FHoudiniEngineUtils::ExtractRawString(TexturePath); - TextureParams[CurTextureParamName][MaterialIdx] = TexturePathRawStr; - } - } - - } - } - else - { - // We do not have any materials, add default. - UniqueMaterialList.Add(DefaultMaterialName); - } - - // TODO: Needs to be improved! - // We shouldnt be testing for each face, but only for each unique facematerial value... - for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) - { - int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; - if(UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) - { - OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); - - for (auto & Pair : ScalarParams) - { - OutScalarMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); - } - - for (auto & Pair : VectorParams) - { - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].R); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].G); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].B); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].A); - } - - for (auto & Pair : TextureParams) - { - OutTextureMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); - } - } - else - { - OutStaticMeshFaceMaterials.Add(DefaultMaterialName); - } - } -} - - -void -FUnrealMeshTranslator::DeleteFaceMaterialArray(TArray& OutStaticMeshFaceMaterials) -{ - // Clean up the memory allocated by CreateFaceMaterialArray() - TSet UniqueMaterials(OutStaticMeshFaceMaterials); - for (TSet::TIterator Iter = UniqueMaterials.CreateIterator(); Iter; ++Iter) - { - char* MaterialName = *Iter; - FMemory::Free(MaterialName); - } - - OutStaticMeshFaceMaterials.Empty(); -} - -bool -FUnrealMeshTranslator::CreateInputNodeForBox( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& BoxCenter, - const FVector& BoxExtent, - const FRotator& BoxRotation) -{ - // Create a new input node for the box collider - FString BoxName = TEXT("box") + FString::FromInt(ColliderIndex); - - // Create the node in this input object's OBJ node - HAPI_NodeId BoxNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("box"), BoxName, false, &BoxNodeId), false); - - // Set the box parameters - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 0, BoxExtent.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 1, BoxExtent.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 2, BoxExtent.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 0, BoxCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 1, BoxCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 2, BoxCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 0, BoxRotation.Roll); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 2, BoxRotation.Pitch); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 1, BoxRotation.Yaw); - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), BoxNodeId, &CookOptions); - */ - if (!FHoudiniEngineUtils::HapiCookNode(BoxNodeId, nullptr, true)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_box") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, BoxNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForSphere( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphereCenter, - const float& SphereRadius) -{ - // Create a new input node for the sphere collider - const char * SphereName = ""; - { - FString SPH = TEXT("Sphere") + FString::FromInt(ColliderIndex); - SphereName = TCHAR_TO_UTF8(*SPH); - } - - // Create the node in this input object's OBJ node - HAPI_NodeId SphereNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "sphere", SphereName, false, &SphereNodeId), false); - - // Set the box parameters - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 1, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 2, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 0, SphereCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 1, SphereCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 2, SphereCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "type", 0, 1); - /* - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "scale", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - */ - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), SphereNodeId, &CookOptions); - */ - if (!FHoudiniEngineUtils::HapiCookNode(SphereNodeId, nullptr, true)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_sphere") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphereNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForSphyl( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphylCenter, - const FRotator& SphylRotation, - const float& SphylRadius, - const float& SphereLength) -{ - // - // Get the Sphyl's vertices and indices - // (code drived from FKSphylElem::GetElemSolid) - // - - // TODO: - // Rotation? - - const int32 NumSides = 6; - const int32 NumRings = (NumSides / 2) + 1; - - // The first/last arc are on top of each other. - const int32 NumVerts = (NumSides + 1) * (NumRings + 1); - - // Calculate the vertices for one arc - TArray ArcVertices; - ArcVertices.SetNum(NumRings + 1); - for (int32 RingIdx = 0; RingIdx < NumRings + 1; RingIdx++) - { - float Angle; - float ZOffset; - if (RingIdx <= NumSides / 4) - { - Angle = ((float)RingIdx / (NumRings - 1)) * PI; - ZOffset = 0.5 * SphereLength; - } - else - { - Angle = ((float)(RingIdx - 1) / (NumRings - 1)) * PI; - ZOffset = -0.5 * SphereLength; - } - - // Note- unit sphere, so position always has mag of one. We can just use it for normal! - FVector SpherePos; - SpherePos.X = 0.0f; - SpherePos.Y = SphylRadius * FMath::Sin(Angle); - SpherePos.Z = SphylRadius * FMath::Cos(Angle); - - ArcVertices[RingIdx] = SpherePos + FVector(0, 0, ZOffset); - } - - // Get the transform matrix for the rotation - FRotationMatrix SphylRotMatrix(SphylRotation); - - // Get the Sphyl's vertices by rotating the arc NumSides+1 times. - TArray Vertices; - Vertices.SetNum(NumVerts * 3); - for (int32 SideIdx = 0; SideIdx < NumSides + 1; SideIdx++) - { - const FRotator ArcRotator(0, 360.f * ((float)SideIdx / NumSides), 0); - const FRotationMatrix ArcRot(ArcRotator); - const float XTexCoord = ((float)SideIdx / NumSides); - - for (int32 VertIdx = 0; VertIdx < NumRings + 1; VertIdx++) - { - int32 VIx = (NumRings + 1)*SideIdx + VertIdx; - - FVector CurPosition = SphylCenter + ArcRot.TransformPosition(ArcVertices[VertIdx]); - CurPosition = SphylRotMatrix.TransformPosition(CurPosition); - - // Convert the UE4 position to Houdini - Vertices[VIx * 3 + 0] = CurPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[VIx * 3 + 1] = CurPosition.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[VIx * 3 + 2] = CurPosition.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - } - - // Add all of the indices to the mesh. - int32 NumIndices = NumSides * NumRings * 6; - TArray Indices; - Indices.SetNum(NumIndices); - - int32 CurIndex = 0; - for (int32 SideIdx = 0; SideIdx < NumSides; SideIdx++) - { - const int32 a0start = (SideIdx + 0) * (NumRings + 1); - const int32 a1start = (SideIdx + 1) * (NumRings + 1); - for (int32 RingIdx = 0; RingIdx < NumRings; RingIdx++) - { - // First Tri (reverse winding) - Indices[CurIndex+0] = a0start + RingIdx + 0; - Indices[CurIndex+2] = a1start + RingIdx + 0; - Indices[CurIndex+1] = a0start + RingIdx + 1; - CurIndex += 3; - // Second Tri (reverse winding) - Indices[CurIndex+0] = a1start + RingIdx + 0; - Indices[CurIndex+2] = a1start + RingIdx + 1; - Indices[CurIndex+1] = a0start + RingIdx + 1; - CurIndex += 3; - } - } - - // - // Create the Sphyl Mesh in houdini - // - HAPI_NodeId SphylNodeId = -1; - FString SphylName = TEXT("Sphyl") + FString::FromInt(ColliderIndex); - if(!CreateInputNodeForCollider(SphylNodeId, InParentNodeID, ColliderIndex, SphylName, Vertices, Indices)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_capsule") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphylNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForConvex( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FKConvexElem& ConvexCollider) -{ - TArray Vertices; - TArray Indices; - -#if PHYSICS_INTERFACE_PHYSX - if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) -#elif WITH_CHAOS - //if (ConvexCollider.GetChaosConvexMesh().IsValid()) - if (ConvexCollider.IndexData.Num() > 0 && ConvexCollider.IndexData.Num() % 3 == 0) -#else - if(false) -#endif - { - // Get the convex colliders vertices and indices from the mesh - TArray VertexBuffer; - TArray IndexBuffer; - ConvexCollider.AddCachedSolidConvexGeom(VertexBuffer, IndexBuffer, FColor::White); - - Vertices.SetNum(VertexBuffer.Num() * 3); - int32 CurIndex = 0; - for (auto& CurVert : VertexBuffer) - { - Vertices[CurIndex * 3 + 0] = CurVert.Position.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 1] = CurVert.Position.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 2] = CurVert.Position.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - CurIndex++; - } - - Indices.SetNum(IndexBuffer.Num()); - for (int Idx = 0; (Idx + 2) < IndexBuffer.Num(); Idx += 3) - { - // Reverse winding - Indices[Idx + 0] = Indices[Idx + 0]; - Indices[Idx + 2] = Indices[Idx + 1]; - Indices[Idx + 1] = Indices[Idx + 2]; - } - } - else - { - int32 NumVert = ConvexCollider.VertexData.Num(); - Vertices.SetNum(NumVert * 3); - //Indices.SetNum(NumVert); - int32 CurIndex = 0; - for (auto& CurVert : ConvexCollider.VertexData) - { - Vertices[CurIndex * 3 + 0] = CurVert.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 1] = CurVert.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 2] = CurVert.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - - /* - // TODO: Get proper polygons... - Indices[CurIndex] = CurIndex; - */ - CurIndex++; - } - - // TODO: Get Proper polygons - for (int32 Idx = 0; Idx + 2 < NumVert; Idx++) - { - Indices.Add(Idx + 0); - Indices.Add(Idx + 1); - Indices.Add(Idx + 2); - } - - /* - for (int32 Idx = 0; Idx + 3 < NumVert; Idx+= 4) - { - Indices.Add(Idx + 0); - Indices.Add(Idx + 1); - Indices.Add(Idx + 2); - - Indices.Add(Idx + 2); - Indices.Add(Idx + 1); - Indices.Add(Idx + 3); - } - */ - } - - // - // Create the Convex Mesh in houdini - // - HAPI_NodeId ConvexNodeId = -1; - FString ConvexName = TEXT("Convex") + FString::FromInt(ColliderIndex); - if (!CreateInputNodeForCollider(ConvexNodeId, InParentNodeID, ColliderIndex, ConvexName, Vertices, Indices)) - return false; - - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), ColliderNodeId, &CookOptions); - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "groupcreate", GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_ucx - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_ucx") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Create a convex hull (shrinkwrap::2.0) node to fix the lack of proper indices - HAPI_NodeId ConvexHullNodeId = -1; - FString ConvexHullName = TEXT("ConvexHull") + FString::FromInt(ColliderIndex); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "shrinkwrap::2.0", ConvexHullName, false, &ConvexHullNodeId), false); - - if (ConvexHullNodeId > 0) - { - // Connect the collider to the convex hull - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), ConvexHullNodeId, 0, ConvexNodeId, 0), false); - - // Connect the convex hull to the group - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexHullNodeId, 0), false); - } - else - { - // Connect the collider to the group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexNodeId, 0), false); - - } - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForCollider( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FString& ColliderName, - const TArray& ColliderVertices, - const TArray& ColliderIndices) -{ - // Create a new input node for the collider - const char * ColliderNameStr = TCHAR_TO_UTF8(*ColliderName); - - // Create the node in this input object's OBJ node - HAPI_NodeId ColliderNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "null", ColliderNameStr, false, &ColliderNodeId), false); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = ColliderIndices.Num(); - Part.faceCount = ColliderIndices.Num() / 3; - Part.pointCount = ColliderVertices.Num() / 3; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), ColliderNodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = ColliderVertices.Num() / 3; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Upload the positions - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - ColliderVertices.GetData(), 0, AttributeInfoPoint.count), false); - - // Upload the indices - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, ColliderIndices.GetData(), 0, ColliderIndices.Num()), false); - - // Generate the array of face counts. - TArray ColldierFaceCounts; - ColldierFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, ColldierFaceCounts.GetData(), 0, ColldierFaceCounts.Num()), false); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), ColliderNodeId), false); - - OutNodeId = ColliderNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - const int32 & NodeId, - const int32 & PartId, - const int32 & Count, - const TArray & TriangleMaterials, - const TMap> & ScalarMaterialParameters, - const TMap> & VectorMaterialParameters, - const TMap> & TextureMaterialParameters) -{ - if (NodeId < 0) - return false; - - bool bSuccess = true; - - // Create attribute for materials. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.count = Count; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - // Create the new attribute - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial, - (const char **)TriangleMaterials.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - - // Add scalar material parameter attributes - for (auto & Pair : ScalarMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 1; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - // Create the new attribute - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - Pair.Value.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - // Add vector material parameters - for (auto & Pair : VectorMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 4; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - Pair.Value.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - // Add texture material parameter attributes - for (auto & Pair : TextureMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 1; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // Replace null strings by empty strings to prevent crashes when setting the attribute. - char* EmptyString = nullptr; - TArray StringData = Pair.Value; - for (auto& CurValue : StringData) - { - if (CurValue != nullptr) - continue; - - if (!EmptyString) - { - // Allocate the empty string the first time it is needed. This is free'd along with - // the other strings in the arrays in TextureMaterialParameters by calls to DeleteFaceMaterialArray - EmptyString = FHoudiniEngineUtils::ExtractRawString(FString(TEXT(""))); - } - CurValue = EmptyString; - } - - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - (const char **)StringData.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - return bSuccess; -} - -/* -bool -FUnrealMeshTranslator::AddLevelPathAttributeToMesh( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const FString& LevelPath, - const int32& Count) -{ - if (NodeId == -1 || LevelPath.IsEmpty() || Count <= 0) - return false; - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = Count; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Count); - for (int32 Idx = 0; Idx < Count; ++Idx) - { - PrimitiveAttrs[Idx] = LevelPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealMeshTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "RawMesh.h" +#include "MeshDescription.h" +#include "MeshDescriptionOperations.h" +#include "Engine/StaticMesh.h" +#include "PhysicsEngine/BodySetup.h" +#include "Engine/StaticMeshSocket.h" +#include "Components/StaticMeshComponent.h" +#include "Materials/Material.h" +#include "Materials/MaterialInterface.h" +#include "MeshAttributes.h" +#include "StaticMeshAttributes.h" + +#if WITH_EDITOR + #include "EditorFramework/AssetImportData.h" +#endif + +bool +FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + UStaticMesh* StaticMesh, + HAPI_NodeId& InputNodeId, + const FString& InputNodeName, + UStaticMeshComponent* StaticMeshComponent /* = nullptr */, + const bool& ExportAllLODs /* = false */, + const bool& ExportSockets /* = false */, + const bool& ExportColliders /* = false */) +{ + // If we don't have a static mesh there's nothing to do. + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Node ID for the newly created node + HAPI_NodeId NewNodeId = -1; + + // Export sockets if there are some + bool DoExportSockets = ExportSockets && (StaticMesh->Sockets.Num() > 0); + + // Export LODs if there are some + bool DoExportLODs = ExportAllLODs && (StaticMesh->GetNumLODs() > 1); + + // Export colliders if there are some + bool DoExportColliders = ExportColliders && StaticMesh->BodySetup != nullptr; + if (DoExportColliders) + { + if (!StaticMesh->BodySetup) + { + DoExportColliders = false; + } + else + { + if (StaticMesh->BodySetup->AggGeom.GetElementCount() <= 0) + DoExportColliders = false; + } + } + + // We need to use a merge node if we export lods OR sockets + bool UseMergeNode = DoExportLODs || DoExportSockets || DoExportColliders; + if (UseMergeNode) + { + // TODO: + // What if OutInputNodeId already exists? + // Delete previous merge?/input? + + // Create a merge SOP asset. This will be our "InputNodeId" + // as all the different LOD meshes and sockets will be plugged into it + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/merge"), InputNodeName, true, &NewNodeId), false); + } + else + { + // No LODs/Sockets, we just need a single input node + // If InputNodeId is invalid, we need to create an input node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_ANSI(*InputNodeName)), false); + + if (!FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true)) + return false; + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); + */ + } + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) + return false; + + HAPI_NodeId PreviousInputNodeId = InputNodeId; + + // Update our input NodeId + InputNodeId = NewNodeId; + // Get our parent OBJ NodeID + HAPI_NodeId InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); + + // We have now created a valid new input node, delete the previous one + if (PreviousInputNodeId >= 0) + { + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); + } + } + + // TODO: + // Setting for lightmap resolution? + //const uint8 ExportMethod = 0; // Raw mesh + //const uint8 ExportMethod = 1; // Mesh description + const uint8 ExportMethod = 2; // LODResources (render mesh) + //bool bExportViaRawMesh = false; + + int32 NumLODsToExport = DoExportLODs ? StaticMesh->GetNumLODs() : 1; + for (int32 LODIndex = 0; LODIndex < NumLODsToExport; LODIndex++) + { + // Grab the LOD level. + FStaticMeshSourceModel & SrcModel = StaticMesh->GetSourceModel(LODIndex); + + // If we're using a merge node, we need to create a new input null + HAPI_NodeId CurrentLODNodeId = -1; + if (UseMergeNode) + { + // Create a new input node for the current LOD + const char * LODName = ""; + { + FString LOD = TEXT("lod") + FString::FromInt(LODIndex); + LODName = TCHAR_TO_UTF8(*LOD); + } + + // Create the node in this input object's OBJ node + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InputObjectNodeId, TEXT("null"), LODName, false, &CurrentLODNodeId), false); + } + else + { + // No merge node, just use the input node we created before + CurrentLODNodeId = NewNodeId; + } + + // Either export the current LOD Mesh by using RawMEsh or MeshDescription (legacy) + FMeshDescription* MeshDesc = nullptr; + // if (!bExportViaRawMesh) + if (ExportMethod == 1) + { + // This will either fetch the mesh description that is cached on the SrcModel + // or load it from bulk data / DDC once + if (SrcModel.MeshDescription.IsValid()) + { + MeshDesc = SrcModel.MeshDescription.Get(); + } + else + { + const double StartTime = FPlatformTime::Seconds(); + MeshDesc = StaticMesh->GetMeshDescription(LODIndex); + HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->GetMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + } + + bool bMeshSuccess = false; + if (ExportMethod == 1 && MeshDesc) + { + // Convert the Mesh using FMeshDescription + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForMeshDescription( + CurrentLODNodeId, + *MeshDesc, + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + else if (ExportMethod == 2) + { + // Convert the LOD Mesh using FStaticMeshLODResources + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( + CurrentLODNodeId, + StaticMesh->GetLODForExport(LODIndex), + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + else + { + // Convert the LOD Mesh using FRawMesh + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForRawMesh( + CurrentLODNodeId, + SrcModel, + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForRawMesh completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + + if (!bMeshSuccess) + continue; + + if (UseMergeNode) + { + // Connect the LOD node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, LODIndex, CurrentLODNodeId, 0), false); + } + } + + // next Index for adding nodes to the merge + int32 NextMergeIndex = NumLODsToExport; + if (DoExportColliders) + { + FKAggregateGeom SimpleColliders = StaticMesh->BodySetup->AggGeom; + + // Export BOX colliders + for (auto& CurBox : SimpleColliders.BoxElems) + { + FVector BoxCenter = CurBox.Center; + FVector BoxExtent = FVector(CurBox.X, CurBox.Y, CurBox.Z); + FRotator BoxRotation = CurBox.Rotation; + + HAPI_NodeId BoxNodeId = -1; + if (!CreateInputNodeForBox( + BoxNodeId, InputObjectNodeId, NextMergeIndex, + BoxCenter, BoxExtent, BoxRotation)) + continue; + + if (BoxNodeId < 0) + continue; + + // Connect the Box node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, BoxNodeId, 0), false); + + NextMergeIndex++; + } + + // Export SPHERE colliders + for (auto& CurSphere : SimpleColliders.SphereElems) + { + HAPI_NodeId SphereNodeId = -1; + if (!CreateInputNodeForSphere( + SphereNodeId, InputObjectNodeId, NextMergeIndex, + CurSphere.Center, CurSphere.Radius)) + continue; + + if (SphereNodeId < 0) + continue; + + // Connect the Sphere node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, SphereNodeId, 0), false); + + NextMergeIndex++; + } + + // Export CAPSULE colliders + for (auto& CurSphyl : SimpleColliders.SphylElems) + { + HAPI_NodeId SphylNodeId = -1; + if (!CreateInputNodeForSphyl( + SphylNodeId, InputObjectNodeId, NextMergeIndex, + CurSphyl.Center, CurSphyl.Rotation, CurSphyl.Radius, CurSphyl.Length)) + continue; + + if (SphylNodeId < 0) + continue; + + // Connect the capsule node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, SphylNodeId, 0), false); + + NextMergeIndex++; + } + + // Export CONVEX colliders + for (auto& CurConvex : SimpleColliders.ConvexElems) + { + HAPI_NodeId ConvexNodeId = -1; + if (!CreateInputNodeForConvex( + ConvexNodeId, InputObjectNodeId, NextMergeIndex, CurConvex)) + continue; + + if (ConvexNodeId < 0) + continue; + + // Connect the capsule node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, ConvexNodeId, 0), false); + + NextMergeIndex++; + } + } + + if (DoExportSockets && StaticMesh->Sockets.Num() > 0) + { + // Create an input node for the mesh sockets + HAPI_NodeId SocketsNodeId = -1; + if (CreateInputNodeForMeshSockets(StaticMesh->Sockets, InputObjectNodeId, SocketsNodeId)) + { + // We can connect the socket node to the merge node's last input. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), NewNodeId, NextMergeIndex, SocketsNodeId, 0), false); + + NextMergeIndex++; + } + else if (SocketsNodeId != -1) + { + // If we failed to properly export the sockets, clean up the created node + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), SocketsNodeId); + } + } + + // + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForMeshSockets( + const TArray& InMeshSocket, const HAPI_NodeId& InParentNodeId, HAPI_NodeId& OutSocketsNodeId) +{ + int32 NumSockets = InMeshSocket.Num(); + if (NumSockets <= 0) + return false; + + // Create a new input node for the sockets + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeId, TEXT("null"), "sockets", false, &OutSocketsNodeId), false); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.pointCount = NumSockets; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, &Part), false); + + // Create POS point attribute info. + HAPI_AttributeInfo AttributeInfoPos; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); + AttributeInfoPos.count = NumSockets; + AttributeInfoPos.tupleSize = 3; + AttributeInfoPos.exists = true; + AttributeInfoPos.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos), false); + + // Create Rot point attribute Info + HAPI_AttributeInfo AttributeInfoRot; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); + AttributeInfoRot.count = NumSockets; + AttributeInfoRot.tupleSize = 4; + AttributeInfoRot.exists = true; + AttributeInfoRot.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot), false); + + // Create scale point attribute Info + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = NumSockets; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); + + // Create the name attrib info + HAPI_AttributeInfo AttributeInfoName; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); + AttributeInfoName.count = NumSockets; + AttributeInfoName.tupleSize = 1; + AttributeInfoName.exists = true; + AttributeInfoName.owner = HAPI_ATTROWNER_POINT; + AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName), false); + + // Create the tag attrib info + HAPI_AttributeInfo AttributeInfoTag; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); + AttributeInfoTag.count = NumSockets; + AttributeInfoTag.tupleSize = 1; + AttributeInfoTag.exists = true; + AttributeInfoTag.owner = HAPI_ATTROWNER_POINT; + AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag), false); + + // Extract the sockets transform values + TArray SocketPos; + SocketPos.SetNumZeroed(NumSockets * 3); + TArray SocketRot; + SocketRot.SetNumZeroed(NumSockets * 4); + TArray SocketScale; + SocketScale.SetNumZeroed(NumSockets * 3); + + // raw string array for names and tag, will need to be free before returning + TArray SocketNames; + TArray SocketTags; + + // Lambda for freeing the const char array's memory and returning + auto FreeMemoryReturn = [&SocketNames, &SocketTags](const bool& bReturn) + { + // Frees the memory allocated by ExtractRawString for the names and tags + FHoudiniEngineUtils::FreeRawStringMemory(SocketNames); + FHoudiniEngineUtils::FreeRawStringMemory(SocketTags); + + return bReturn; + }; + + for (int32 Idx = 0; Idx < NumSockets; ++Idx) + { + UStaticMeshSocket* CurrentSocket = InMeshSocket[Idx]; + if (!CurrentSocket || CurrentSocket->IsPendingKill()) + continue; + + // Get the socket's transform and convert it to HapiTransform + FTransform SocketTransform(CurrentSocket->RelativeRotation, CurrentSocket->RelativeLocation, CurrentSocket->RelativeScale); + HAPI_Transform HapiSocketTransform; + FHoudiniApi::Transform_Init(&HapiSocketTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(SocketTransform, HapiSocketTransform); + + // Fill the attribute values + SocketPos[3 * Idx + 0] = HapiSocketTransform.position[0]; + SocketPos[3 * Idx + 1] = HapiSocketTransform.position[1]; + SocketPos[3 * Idx + 2] = HapiSocketTransform.position[2]; + + SocketRot[4 * Idx + 0] = HapiSocketTransform.rotationQuaternion[0]; + SocketRot[4 * Idx + 1] = HapiSocketTransform.rotationQuaternion[1]; + SocketRot[4 * Idx + 2] = HapiSocketTransform.rotationQuaternion[2]; + SocketRot[4 * Idx + 3] = HapiSocketTransform.rotationQuaternion[3]; + + SocketScale[3 * Idx + 0] = HapiSocketTransform.scale[0]; + SocketScale[3 * Idx + 1] = HapiSocketTransform.scale[1]; + SocketScale[3 * Idx + 2] = HapiSocketTransform.scale[2]; + + FString CurrentSocketName; + if (!CurrentSocket->SocketName.IsNone()) + CurrentSocketName = CurrentSocket->SocketName.ToString(); + else + CurrentSocketName = TEXT("Socket") + FString::FromInt(Idx); + SocketNames.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocketName)); + + if (!CurrentSocket->Tag.IsEmpty()) + SocketTags.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocket->Tag)); + else + SocketTags.Add(""); + } + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos, + SocketPos.GetData(), 0, AttributeInfoPos.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot, + SocketRot.GetData(), 0, AttributeInfoRot.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, + SocketScale.GetData(), 0, AttributeInfoScale.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName, + SocketNames.GetData(), 0, AttributeInfoName.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag, + SocketTags.GetData(), 0, AttributeInfoTag.count), + FreeMemoryReturn(false)); + + // We will also create the socket_details attributes + for (int32 Idx = 0; Idx < NumSockets; ++Idx) + { + // Build the current socket's prefix + FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(Idx); + + // Create mesh_socketX_pos attribute info. + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); + AttributeInfoPos.count = 1; + AttributeInfoPos.tupleSize = 3; + AttributeInfoPos.exists = true; + AttributeInfoPos.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; + + FString PosAttr = SocketAttrPrefix + TEXT("_pos"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos, + &(SocketPos[3 * Idx]), 0, AttributeInfoPos.count), + FreeMemoryReturn(false)); + + // Create mesh_socketX_rot point attribute Info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); + AttributeInfoRot.count = 1; + AttributeInfoRot.tupleSize = 4; + AttributeInfoRot.exists = true; + AttributeInfoRot.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; + + FString RotAttr = SocketAttrPrefix + TEXT("_rot"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot, + &(SocketRot[4 * Idx]), 0, AttributeInfoRot.count), + FreeMemoryReturn(false)); + + // Create mesh_socketX_scale point attribute Info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = 1; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + FString ScaleAttr = SocketAttrPrefix + TEXT("_scale"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale, + &(SocketScale[3 * Idx]), 0, AttributeInfoScale.count), + FreeMemoryReturn(false)); + + // Create the mesh_socketX_name attrib info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); + AttributeInfoName.count = 1; + AttributeInfoName.tupleSize = 1; + AttributeInfoName.exists = true; + AttributeInfoName.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; + + FString NameAttr = SocketAttrPrefix + TEXT("_name"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName, + &(SocketNames[Idx]), 0, AttributeInfoName.count), + FreeMemoryReturn(false)); + + // Create the mesh_socketX_tag attrib info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); + AttributeInfoTag.count = 1; + AttributeInfoTag.tupleSize = 1; + AttributeInfoTag.exists = true; + AttributeInfoTag.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; + + FString TagAttr = SocketAttrPrefix + TEXT("_tag"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag, + &(SocketTags[Idx]), 0, AttributeInfoTag.count), + FreeMemoryReturn(false)); + } + + // Now add the sockets group + const char * SocketGroupStr = "socket_imported"; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr), + FreeMemoryReturn(false)); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, NumSockets); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr, GroupArray.GetData(), 0, NumSockets), + FreeMemoryReturn(false)); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), OutSocketsNodeId), + FreeMemoryReturn(false)); + + return FreeMemoryReturn(true); +} + +bool +FUnrealMeshTranslator::CreateInputNodeForRawMesh( + const HAPI_NodeId& NodeId, + const FStaticMeshSourceModel& SourceModel, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent ) +{ + // Convert the Mesh using FRawMesh + FRawMesh RawMesh; + SourceModel.LoadRawMesh(RawMesh); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = RawMesh.WedgeIndices.Num(); + Part.faceCount = RawMesh.WedgeIndices.Num() / 3; + Part.pointCount = RawMesh.VertexPositions.Num(); + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = RawMesh.VertexPositions.Num(); + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Grab the build scale + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.VertexPositions.Num() > 3) + { + TArray StaticMeshVertices; + StaticMeshVertices.SetNumZeroed(RawMesh.VertexPositions.Num() * 3); + for (int32 VertexIdx = 0; VertexIdx < RawMesh.VertexPositions.Num(); ++VertexIdx) + { + // Convert Unreal to Houdini + const FVector & PositionVector = RawMesh.VertexPositions[VertexIdx]; + StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; + StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; + StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + for (int32 MeshTexCoordIdx = 0; MeshTexCoordIdx < MAX_STATIC_TEXCOORDS; MeshTexCoordIdx++) + { + int32 StaticMeshUVCount = RawMesh.WedgeTexCoords[MeshTexCoordIdx].Num(); + if (StaticMeshUVCount > 0) + { + const TArray & RawMeshUVs = RawMesh.WedgeTexCoords[MeshTexCoordIdx]; + TArray StaticMeshUVs; + StaticMeshUVs.Reserve(StaticMeshUVCount); + + // Transfer UV data. + for (int32 UVIdx = 0; UVIdx < StaticMeshUVCount; UVIdx++) + StaticMeshUVs.Emplace(RawMeshUVs[UVIdx].X, 1.0 - RawMeshUVs[UVIdx].Y, 0); + + // Convert Unreal to Houdini + // We need to re-index UVs for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + // We do not touch wedge 0 of this triangle, Swap 2 and 3 to reverse the winding order. + StaticMeshUVs.SwapMemory(WedgeIdx + 1, WedgeIdx + 2); + } + + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (MeshTexCoordIdx > 0) + UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = StaticMeshUVCount; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, (const float *)StaticMeshUVs.GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentZ.Num() > 0) + { + TArray ChangedNormals(RawMesh.WedgeTangentZ); + + // We need to re-index normals for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentZ1 = ChangedNormals[WedgeIdx + 1]; + FVector TangentZ2 = ChangedNormals[WedgeIdx + 2]; + + ChangedNormals[WedgeIdx + 1] = TangentZ2; + ChangedNormals[WedgeIdx + 2] = TangentZ1; + } + + // We also need to swap the vector's Y and Z components + for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedNormals[WedgeIdx].Y, ChangedNormals[WedgeIdx].Z); + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedNormals.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, (const float *)ChangedNormals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentX.Num() > 0) + { + TArray ChangedTangentU(RawMesh.WedgeTangentX); + + // We need to re-index tangents for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentU1 = ChangedTangentU[WedgeIdx + 1]; + FVector TangentU2 = ChangedTangentU[WedgeIdx + 2]; + + ChangedTangentU[WedgeIdx + 1] = TangentU2; + ChangedTangentU[WedgeIdx + 2] = TangentU1; + } + + // We also need to swap the vector's Y and Z components + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedTangentU[WedgeIdx].Y, ChangedTangentU[WedgeIdx].Z); + + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedTangentU.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + (const float *)ChangedTangentU.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentY.Num() > 0) + { + TArray ChangedTangentV(RawMesh.WedgeTangentY); + // We need to re-index normals for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentV1 = ChangedTangentV[WedgeIdx + 1]; + FVector TangentV2 = ChangedTangentV[WedgeIdx + 2]; + + ChangedTangentV[WedgeIdx + 1] = TangentV2; + ChangedTangentV[WedgeIdx + 2] = TangentV1; + } + + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedTangentV[WedgeIdx].Y, ChangedTangentV[WedgeIdx].Z); + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedTangentV.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + (const float *)ChangedTangentV.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + { + // If we have instance override vertex colors on the StaticMeshComponent, + // we first need to propagate them to our copy of the RawMesh Vert Colors + TArray ChangedColors; + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && + StaticMesh->RenderData && + StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; + FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) + { + // Use the wedge map if it is available as it is lossless. + int32 NumWedges = RawMesh.WedgeIndices.Num(); + if (RenderModel.WedgeMap.Num() == NumWedges) + { + int32 NumExistingColors = RawMesh.WedgeColors.Num(); + if (NumExistingColors < NumWedges) + { + RawMesh.WedgeColors.AddUninitialized(NumWedges - NumExistingColors); + } + + // Replace mesh colors with override colors + for (int32 i = 0; i < NumWedges; i++) + { + FColor WedgeColor = FColor::White; + int32 Index = RenderModel.WedgeMap[i]; + if (Index != INDEX_NONE) + { + WedgeColor = ColorVertexBuffer.VertexColor(Index); + } + RawMesh.WedgeColors[i] = WedgeColor; + } + } + } + } + + // See if we have colors to upload. + if (RawMesh.WedgeColors.Num() > 0) + { + ChangedColors.SetNumUninitialized(RawMesh.WedgeColors.Num()); + + // Convert Unreal to Houdini + // We need to re-index colors for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + ChangedColors[WedgeIdx + 0] = RawMesh.WedgeColors[WedgeIdx + 0].ReinterpretAsLinear(); + ChangedColors[WedgeIdx + 1] = RawMesh.WedgeColors[WedgeIdx + 2].ReinterpretAsLinear(); + ChangedColors[WedgeIdx + 2] = RawMesh.WedgeColors[WedgeIdx + 1].ReinterpretAsLinear(); + } + } + + if (ChangedColors.Num() > 0) + { + // Extract the RGB colors + TArray ColorValues; + ColorValues.SetNum(ChangedColors.Num() * 3); + for (int32 colorIndex = 0; colorIndex < ChangedColors.Num(); colorIndex++) + { + ColorValues[colorIndex * 3] = ChangedColors[colorIndex].R; + ColorValues[colorIndex * 3 + 1] = ChangedColors[colorIndex].G; + ColorValues[colorIndex * 3 + 2] = ChangedColors[colorIndex].B; + } + + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedColors.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + ColorValues.GetData(), 0, AttributeInfoVertex.count), false); + + // Create the attribute for Alpha + TArray AlphaValues; + AlphaValues.SetNum(ChangedColors.Num()); + for (int32 alphaIndex = 0; alphaIndex < ChangedColors.Num(); alphaIndex++) + AlphaValues[alphaIndex] = ChangedColors[alphaIndex].A; + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.count = AlphaValues.Num(); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + AlphaValues.GetData(), 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES (VertexList) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeIndices.Num() > 0) + { + TArray StaticMeshIndices; + StaticMeshIndices.SetNumUninitialized(RawMesh.WedgeIndices.Num()); + + // Convert Unreal to Houdini + for (int32 IndexIdx = 0; IndexIdx < RawMesh.WedgeIndices.Num(); IndexIdx += 3) + { + // Swap indices to fix winding order. + StaticMeshIndices[IndexIdx + 0] = RawMesh.WedgeIndices[IndexIdx + 0]; + StaticMeshIndices[IndexIdx + 1] = RawMesh.WedgeIndices[IndexIdx + 2]; + StaticMeshIndices[IndexIdx + 2] = RawMesh.WedgeIndices[IndexIdx + 1]; + } + + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, StaticMeshIndices.GetData(), 0, StaticMeshIndices.Num()), false); + + // We need to generate array of face counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, StaticMeshFaceCounts.GetData(), 0, StaticMeshFaceCounts.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + // Marshall face material indices. + if (RawMesh.FaceMaterialIndices.Num() > 0) + { + // Create an array of Material Interfaces + TArray MaterialInterfaces; + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()) + { + // StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); + + int32 NumMeshBasedMaterials = StaticMeshComponent->GetNumMaterials(); + TArray MeshBasedMaterialInterfaces; + MeshBasedMaterialInterfaces.SetNumUninitialized(NumMeshBasedMaterials); + for (int32 i = 0; i < NumMeshBasedMaterials; i++) + MeshBasedMaterialInterfaces[i] = StaticMeshComponent->GetMaterial(i); + + int32 NumSections = StaticMesh->GetNumSections(InLODIndex); + MaterialInterfaces.SetNumUninitialized(NumSections); + for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) + { + FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex); + check(Info.MaterialIndex < NumMeshBasedMaterials); + MaterialInterfaces[SectionIndex] = MeshBasedMaterialInterfaces[Info.MaterialIndex]; + } + } + else + { + // Query the Static mesh's materials + for (int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++) + { + MaterialInterfaces.Add(StaticMesh->GetMaterial(MatIdx)); + } + + // Try to fix up inconsistencies between the RawMesh / StaticMesh material indexes + // by using the meshes sections... + // TODO: Fix me properly! + // Proper fix would be to export the meshes via the FStaticMeshLODResources obtained + // by GetLODForExport(), and then export the mesh by sections. + if (StaticMesh->RenderData && StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + { + TMap MapOfMaterials; + FStaticMeshLODResources& LODResources = StaticMesh->RenderData->LODResources[InLODIndex]; + for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) + { + // Get the material for each element at the current lod index + int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; + if (!MapOfMaterials.Contains(MaterialIndex)) + { + MapOfMaterials.Add(MaterialIndex, StaticMesh->GetMaterial(MaterialIndex)); + } + } + + if (MapOfMaterials.Num() > 0) + { + // Sort the output material in the correct order (by material index) + MapOfMaterials.KeySort([](int32 A, int32 B) { return A < B; }); + + // Set the value in the correct order + // Do not reduce the array of materials, this could cause crahses in some weird cases.. + if (MapOfMaterials.Num() > MaterialInterfaces.Num()) + MaterialInterfaces.SetNumZeroed(MapOfMaterials.Num()); + + int32 MaterialIndex = 0; + for (auto Kvp : MapOfMaterials) + { + MaterialInterfaces[MaterialIndex++] = Kvp.Value; + } + } + } + } + + // List of materials, one for each face. + TArray StaticMeshFaceMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + RawMesh.FaceMaterialIndices.Num(), + StaticMeshFaceMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + RawMesh.FaceMaterialIndices.Num(), + StaticMeshFaceMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(StaticMeshFaceMaterials); + + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING MASKS + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.FaceSmoothingMasks.Num() > 0) + { + HAPI_AttributeInfo AttributeInfoSmoothingMasks; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + AttributeInfoSmoothingMasks.count = RawMesh.FaceSmoothingMasks.Num(); + AttributeInfoSmoothingMasks.tupleSize = 1; + AttributeInfoSmoothingMasks.exists = true; + AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + (const int32 *)RawMesh.FaceSmoothingMasks.GetData(), 0, RawMesh.FaceSmoothingMasks.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->LightMapResolution); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + TArray AllTags; + for (auto& ComponentTag : StaticMeshComponent->ComponentTags) + AllTags.AddUnique(ComponentTag); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + for (auto& ActorTag : ParentActor->Tags) + AllTags.AddUnique(ActorTag); + } + + // Try to create groups for the tags + if (!FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, AllTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups for the Static Mesh Component and Actor tags!")); + + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + /* + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +bool +FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( + const HAPI_NodeId& NodeId, + const FStaticMeshLODResources& LODResources, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent) +{ + // Convert the Mesh using FStaticMeshLODResources + + // Check that the mesh is not empty + if (LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0) + { + HOUDINI_LOG_ERROR(TEXT("No vertices in mesh!")); + return false; + } + + if (LODResources.Sections.Num() == 0) + { + HOUDINI_LOG_ERROR(TEXT("No triangles in mesh!")); + return false; + } + + // Vertex instance and triangle counts + const uint32 OrigNumVertexInstances = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); + const uint32 NumTriangles = LODResources.GetNumTriangles(); + const uint32 NumVertexInstances = NumTriangles * 3; + const uint32 NumSections = LODResources.Sections.Num(); + + // Grab the build scale + const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + // In FStaticMeshLODResources each vertex instances stores its position, even if the positions are not unique (in other + // words, in Houdini terminology, the number of points and vertices are the same. We'll do the same thing that Epic + // does in FBX export: we'll run through all vertex instances and use a hash to determine which instances share a + // position, so that we can a smaller number of points than vertices, and vertices share point positions + TArray UEVertexInstanceIdxToPointIdx; + UEVertexInstanceIdxToPointIdx.Reserve(OrigNumVertexInstances); + + TMap PositionToPointIndexMap; + PositionToPointIndexMap.Reserve(OrigNumVertexInstances); + + TArray StaticMeshVertices; + StaticMeshVertices.Reserve(OrigNumVertexInstances * 3); + for (uint32 VertexInstanceIndex = 0; VertexInstanceIndex < OrigNumVertexInstances; ++VertexInstanceIndex) + { + // Convert Unreal to Houdini + const FVector &PositionVector = LODResources.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexInstanceIndex); + const int32 *FoundPointIndexPtr = PositionToPointIndexMap.Find(PositionVector); + if (!FoundPointIndexPtr) + { + const int32 NewPointIndex = StaticMeshVertices.Add(PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X) / 3; + StaticMeshVertices.Add(PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z); + StaticMeshVertices.Add(PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y); + + PositionToPointIndexMap.Add(PositionVector, NewPointIndex); + UEVertexInstanceIdxToPointIdx.Add(NewPointIndex); + } + else + { + UEVertexInstanceIdxToPointIdx.Add(*FoundPointIndexPtr); + } + } + + StaticMeshVertices.Shrink(); + const uint32 NumVertices = StaticMeshVertices.Num() / 3; + + // Now that we know how many vertices (points), vertex instances (vertices) and triagnles we have, + // we can create the part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumVertexInstances; + Part.faceCount = NumTriangles; + Part.pointCount = NumVertices; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = Part.pointCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + + // Determine which attributes we have + const bool bIsVertexInstanceNormalsValid = true; + const bool bIsVertexInstanceTangentsValid = true; + const bool bIsVertexInstanceBinormalsValid = true; + const bool bIsVertexInstanceColorsValid = LODResources.bHasColorVertexData; + const uint32 NumUVLayers = FMath::Min(LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(), MAX_STATIC_TEXCOORDS); + const bool bIsVertexInstanceUVsValid = NumUVLayers > 0; + + bool bUseComponentOverrideColors = false; + // Determine if have override colors on the static mesh component, if so prefer to use those + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (ColorVertexBuffer.GetNumVertices() == LODResources.GetNumVertices()) + { + bUseComponentOverrideColors = true; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL INDEX -> MATERIAL INTERFACE + //--------------------------------------------------------------------------------------------------------------------- + TArray MaterialInterfaces; + TArray TriangleMaterialIndices; + + // If the static mesh component is valid, get the materials via the component to account for overrides + const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); + const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); + // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, + // then we will set UEDefaultMaterial at the invalid index + int32 UEDefaultMaterialIndex = INDEX_NONE; + UMaterialInterface *UEDefaultMaterial = nullptr; + if (NumStaticMaterials > 0) + { + MaterialInterfaces.Reserve(NumStaticMaterials); + for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) + { + const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; + UMaterialInterface *Material = nullptr; + if (bIsStaticMeshComponentValid) + { + Material = StaticMeshComponent->GetMaterial(MaterialIndex); + } + else + { + Material = MaterialInfo.MaterialInterface; + } + // If the Material is NULL or invalid, fallback to the default material + if (!Material || Material->IsPendingKill()) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + UEDefaultMaterialIndex = MaterialIndex; + } + Material = UEDefaultMaterial; + HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); + } + // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); + MaterialInterfaces.Add(Material); + } + + TriangleMaterialIndices.Reserve(NumTriangles); + } + + // If we haven't created UEDefaultMaterial yet, check that all the sections' MaterialIndex + // is valid, if not, create UEDefaultMaterial and add to MaterialInterfaces to get UEDefaultMaterialIndex + if (!UEDefaultMaterial || UEDefaultMaterialIndex == INDEX_NONE) + { + for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + { + // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial + // up to and including MaterialIndex and log a warning + const int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; + if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + // Add the UEDefaultMaterial to MaterialInterfaces + UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); + } + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); + } + } + } + + // Determine the final number of materials we have, with default for missing/invalid indices + const int32 NumMaterials = MaterialInterfaces.Num(); + + // Now we deal with vertex instance attributes. + if (NumTriangles > 0) + { + // UV layer array. Each layer has an array of floats, 3 floats per vertex instance + TArray> UVs; + // Normals: 3 floats per vertex instance + TArray Normals; + // Tangents: 3 floats per vertex instance + TArray Tangents; + // Binormals: 3 floats per vertex instance + TArray Binormals; + // RGBColors: 3 floats per vertex instance + TArray RGBColors; + // Alphas: 1 float per vertex instance + TArray Alphas; + + // Initialize the arrays for the attributes that are valid + if (bIsVertexInstanceUVsValid) + { + UVs.SetNum(NumUVLayers); + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); + } + } + + if (bIsVertexInstanceNormalsValid) + { + Normals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceTangentsValid) + { + Tangents.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceBinormalsValid) + { + Binormals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + RGBColors.SetNumUninitialized(NumVertexInstances * 3); + Alphas.SetNumUninitialized(NumVertexInstances); + } + + // Array of vertex (point position) indices per triangle + TArray MeshTriangleVertexIndices; + MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); + // Array of vertex counts per triangle/face + TArray MeshTriangleVertexCounts; + MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); + + int32 TriangleIdx = 0; + int32 HoudiniVertexIdx = 0; + FIndexArrayView TriangleVertexIndices = LODResources.IndexBuffer.GetArrayView(); + for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + { + const FStaticMeshSection& Section = LODResources.Sections[SectionIndex]; + for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < Section.NumTriangles; ++SectionTriangleIndex) + { + MeshTriangleVertexCounts[TriangleIdx] = 3; + for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) + { + // Reverse the winding order for Houdini (but still start at 0) + const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; + const uint32 UEVertexIndex = TriangleVertexIndices[Section.FirstIndex + SectionTriangleIndex * 3 + WindingIdx]; + + // Calculate the index of the first component of a vertex instance's value in an inline float array + // representing vectors (3 float) per vertex instance + const int32 Float3Index = HoudiniVertexIdx * 3; + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + const FVector2D &UV = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(UEVertexIndex, UVLayerIndex); + UVs[UVLayerIndex][Float3Index + 0] = UV.X; + UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; + UVs[UVLayerIndex][Float3Index + 2] = 0; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + const FVector &Normal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(UEVertexIndex); + Normals[Float3Index + 0] = Normal.X; + Normals[Float3Index + 1] = Normal.Z; + Normals[Float3Index + 2] = Normal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + const FVector &Tangent = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(UEVertexIndex); + Tangents[Float3Index + 0] = Tangent.X; + Tangents[Float3Index + 1] = Tangent.Z; + Tangents[Float3Index + 2] = Tangent.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + // In order to calculate the binormal we also need the tangent and normal + if (bIsVertexInstanceBinormalsValid) + { + FVector Binormal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(UEVertexIndex); + Binormals[Float3Index + 0] = Binormal.X; + Binormals[Float3Index + 1] = Binormal.Z; + Binormals[Float3Index + 2] = Binormal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + FVector4 Color = FLinearColor::White; + if (bUseComponentOverrideColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + Color = ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); + } + else + { + Color = LODResources.VertexBuffers.ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); + } + RGBColors[Float3Index + 0] = Color[0]; + RGBColors[Float3Index + 1] = Color[1]; + RGBColors[Float3Index + 2] = Color[2]; + Alphas[HoudiniVertexIdx] = Color[3]; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + if (UEVertexInstanceIdxToPointIdx.IsValidIndex(UEVertexIndex)) + { + MeshTriangleVertexIndices[HoudiniVertexIdx] = UEVertexInstanceIdxToPointIdx[UEVertexIndex]; + } + + HoudiniVertexIdx++; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + if (MaterialInterfaces.IsValidIndex(Section.MaterialIndex)) + { + TriangleMaterialIndices.Add(Section.MaterialIndex); + } + else + { + TriangleMaterialIndices.Add(UEDefaultMaterialIndex); + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, Section.MaterialIndex, *(UEDefaultMaterial->GetPathName())); + } + + TriangleIdx++; + } + } + + // Now transfer valid vertex instance attributes to Houdini vertex attributes + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) + { + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (UVLayerIndex > 0) + UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = NumVertexInstances; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, Normals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + Tangents.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceBinormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + Binormals.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + RGBColors.GetData(), 0, AttributeInfoVertex.count), false); + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.count = Alphas.Num(); + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + Alphas.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); + + // Send the array of face vertex counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); + + // Send material assignments to Houdini + if (NumMaterials > 0) + { + // List of materials, one for each face. + TArray TriangleMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); + + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + } + + // TODO: The render mesh (LODResources) does not have face smoothing information, and the raw mesh triangle order is + // potentially different (see also line 4152 TODO_FBX in Engine\Source\Editor\UnrealEd\Private\Fbx\FbxMainExport.cpp + ////--------------------------------------------------------------------------------------------------------------------- + //// TRIANGLE SMOOTHING MASKS + ////--------------------------------------------------------------------------------------------------------------------- + //TArray TriangleSmoothingMasks; + //TriangleSmoothingMasks.SetNumZeroed(NumTriangles); + //FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); + //if (TriangleSmoothingMasks.Num() > 0) + //{ + // HAPI_AttributeInfo AttributeInfoSmoothingMasks; + // FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + // AttributeInfoSmoothingMasks.tupleSize = 1; + // AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); + // AttributeInfoSmoothingMasks.exists = true; + // AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + // AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + // AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + // FHoudiniEngine::Get().GetSession(), + // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + // FHoudiniEngine::Get().GetSession(), + // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + // (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); + //} + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->LightMapResolution); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + // Try to create groups for the static mesh component's tags + if (StaticMeshComponent->ComponentTags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Try to create groups for the parent Actor's tags + if (ParentActor->Tags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + + /* + // Add the unreal_level_path attribute + FString LevelPath = ActorPath;// FString(); + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +bool +FUnrealMeshTranslator::CreateInputNodeForMeshDescription( + const HAPI_NodeId& NodeId, + const FMeshDescription& MeshDescription, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent) +{ + // Convert the Mesh using FMeshDescription + // Get references to the attributes we are interested in + // before sending to Houdini we'll check if each attribute is valid + FStaticMeshConstAttributes MeshDescriptionAttributes(MeshDescription); + + TVertexAttributesConstRef VertexPositions = MeshDescriptionAttributes.GetVertexPositions(); + TVertexInstanceAttributesConstRef VertexInstanceNormals = MeshDescriptionAttributes.GetVertexInstanceNormals(); + TVertexInstanceAttributesConstRef VertexInstanceTangents = MeshDescriptionAttributes.GetVertexInstanceTangents(); + TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = MeshDescriptionAttributes.GetVertexInstanceBinormalSigns(); + TVertexInstanceAttributesConstRef VertexInstanceColors = MeshDescriptionAttributes.GetVertexInstanceColors(); + TVertexInstanceAttributesConstRef VertexInstanceUVs = MeshDescriptionAttributes.GetVertexInstanceUVs(); + //TPolygonGroupAttributesConstRef PolygonGroupMaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames(); + + // Get the vertex and triangle arrays + const FVertexArray &MDVertices = MeshDescription.Vertices(); + const FPolygonGroupArray &MDPolygonGroups = MeshDescription.PolygonGroups(); + const FPolygonArray &MDPolygons = MeshDescription.Polygons(); + const FTriangleArray &MDTriangles = MeshDescription.Triangles(); + + // Determine point, vertex and polygon counts + const uint32 NumVertices = MDVertices.Num(); + const uint32 NumTriangles = MDTriangles.Num(); + const uint32 NumVertexInstances = NumTriangles * 3; + + // Some checks: we expect triangulated meshes + if (MeshDescription.VertexInstances().Num() != NumTriangles * 3) + { + HOUDINI_LOG_ERROR(TEXT("Expected a triangulated mesh, but # VertexInstances (%d) != # Triangles * 3 (%d)"), MeshDescription.VertexInstances().Num(), NumTriangles * 3); + return false; + } + + // Determine which attributes we have + const bool bIsVertexPositionsValid = VertexPositions.IsValid(); + const bool bIsVertexInstanceNormalsValid = VertexInstanceNormals.IsValid(); + const bool bIsVertexInstanceTangentsValid = VertexInstanceTangents.IsValid(); + const bool bIsVertexInstanceBinormalSignsValid = VertexInstanceBinormalSigns.IsValid(); + const bool bIsVertexInstanceColorsValid = VertexInstanceColors.IsValid(); + const bool bIsVertexInstanceUVsValid = VertexInstanceUVs.IsValid(); + //const bool bIsPolygonGroupImportedMaterialSlotNamesValid = PolygonGroupMaterialSlotNames.IsValid(); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumVertexInstances; + Part.faceCount = NumTriangles; + Part.pointCount = NumVertices; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = Part.pointCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Grab the build scale + const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + // The mesh element arrays are sparse: the max index/ID value can be larger than the number of elements - 1 + // so we have to maintain a lookup of VertexID (UE) to PointIndex (Houdini) + TArray VertexIDToHIndex; + if (bIsVertexPositionsValid && VertexPositions.GetNumElements() >= 3) + { + TArray StaticMeshVertices; + StaticMeshVertices.SetNumUninitialized(NumVertices * 3); + + int32 VertexIdx = 0; + VertexIDToHIndex.Init(INDEX_NONE, MDVertices.GetArraySize()); + + for (const FVertexID& VertexID : MDVertices.GetElementIDs()) + { + // Convert Unreal to Houdini + const FVector &PositionVector = VertexPositions.Get(VertexID); + StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; + StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; + StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; + + // Record the UE Vertex ID to Houdini Point Index lookup + VertexIDToHIndex[VertexID.GetValue()] = VertexIdx; + VertexIdx++; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + } + + bool bUseComponentOverrideColors = false; + // Determine if have override colors on the static mesh component, if so prefer to use those + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && + StaticMesh->RenderData && + StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; + FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) + { + // Use the wedge map if it is available as it is lossless. + if (RenderModel.WedgeMap.Num() == NumVertexInstances) + { + bUseComponentOverrideColors = true; + } + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL SLOT -> MATERIAL INTERFACE + //--------------------------------------------------------------------------------------------------------------------- + // In theory the ImportedMaterialSlotName attribute on PolygonGroups should tell us which material slot is used by + // that group, and thus which MaterialIndex we should assign to triangles in that group. Unfortunately we have + // encountered cases where the ImportedMaterialSlotName name attribute does not match any of the MaterialSlotName or + // ImportedMaterialSlotNames in the StaticMesh->StaticMaterials array. Therefore we have no choice but to rely + // on the PolygonGroup order vs Section order to determine the MaterialIndex for a group. We do what Epic does + // when building a static mesh: Sections are created in the same order as iterating over PolygonGroups, but importantly, + // empty PolygonGroups are skipped + + // // Get material slot name to material index + // and the UMaterialInterface array + // TMap MaterialSlotToInterface; + TArray MaterialInterfaces; + TArray TriangleMaterialIndices; + + // If the static mesh component is valid, get the materials via the component to account for overrides + const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); + const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); + // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, + // then we will set UEDefaultMaterial at the invalid index + int32 UEDefaultMaterialIndex = INDEX_NONE; + UMaterialInterface *UEDefaultMaterial = nullptr; + if (NumStaticMaterials > 0) + { + MaterialInterfaces.Reserve(NumStaticMaterials); + for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) + { + const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; + UMaterialInterface *Material = nullptr; + if (bIsStaticMeshComponentValid) + { + Material = StaticMeshComponent->GetMaterial(MaterialIndex); + } + else + { + Material = MaterialInfo.MaterialInterface; + } + // If the Material is NULL or invalid, fallback to the default material + if (!Material || Material->IsPendingKill()) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + UEDefaultMaterialIndex = MaterialIndex; + } + Material = UEDefaultMaterial; + HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); + } + // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); + MaterialInterfaces.Add(Material); + } + + TriangleMaterialIndices.Reserve(NumTriangles); + } + // SectionIndex: Looking at Epic's StaticMesh build code, Sections are created in the same + // order as iterating over PolygonGroups, but skipping empty PolygonGroups + TMap PolygonGroupToMaterialIndex; + PolygonGroupToMaterialIndex.Reserve(MeshDescription.PolygonGroups().Num()); + int32 SectionIndex = 0; + for (const FPolygonGroupID &PolygonGroupID : MDPolygonGroups.GetElementIDs()) + { + // Skip empty polygon groups + if (MeshDescription.GetNumPolygonGroupPolygons(PolygonGroupID) == 0) + { + continue; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + // // Get the material index for the material slot for this polygon group + //int32 MaterialIndex = INDEX_NONE; + //if (bIsPolygonGroupImportedMaterialSlotNamesValid) + //{ + // const FName &MaterialSlotName = PolygonGroupMaterialSlotNames.Get(PolygonGroupID); + // const int32 *MaterialIndexPtr = MaterialSlotToInterface.Find(MaterialSlotName); + // if (MaterialIndexPtr != nullptr) + // { + // MaterialIndex = *MaterialIndexPtr; + // } + //} + + // Get the material for the LOD and section via the section info map + if (StaticMesh->GetNumSections(InLODIndex) <= SectionIndex) + { + HOUDINI_LOG_ERROR(TEXT("Found more non-empty polygon groups in the mesh description for LOD %d than sections in the static mesh..."), InLODIndex); + return false; + } + + // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial + // up to and including MaterialIndex and log a warning + int32 MaterialIndex = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex).MaterialIndex; + if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + // Add the UEDefaultMaterial to MaterialInterfaces + UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); + } + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); + MaterialIndex = UEDefaultMaterialIndex; + } + + PolygonGroupToMaterialIndex.Add(PolygonGroupID, MaterialIndex); + SectionIndex++; + } + + // Determine the final number of materials we have, with defaults for missing/invalid indices + const int32 NumMaterials = MaterialInterfaces.Num(); + + // Now we deal with vertex instance attributes. + // // First we must also record a UE VertexInstanceID to Houdini Vertex Index lookup, + // // and then get and convert all valid and supported vertex instance attributes from UE + // TArray VertexInstanceIDToHIndex; + + if (NumTriangles > 0) + { + // UV layer array. Each layer has an array of floats, 3 floats per vertex instance + TArray> UVs; + const int32 NumUVLayers = bIsVertexInstanceUVsValid ? FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_STATIC_TEXCOORDS) : 0; + // Normals: 3 floats per vertex instance + TArray Normals; + // Tangents: 3 floats per vertex instance + TArray Tangents; + // Binormals: 3 floats per vertex instance + TArray Binormals; + // RGBColors: 3 floats per vertex instance + TArray RGBColors; + // Alphas: 1 float per vertex instance + TArray Alphas; + + // Initialize the arrays for the attributes that are valid + if (bIsVertexInstanceUVsValid) + { + UVs.SetNum(NumUVLayers); + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); + } + } + + if (bIsVertexInstanceNormalsValid) + { + Normals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceTangentsValid) + { + Tangents.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceBinormalSignsValid) + { + Binormals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + RGBColors.SetNumUninitialized(NumVertexInstances * 3); + Alphas.SetNumUninitialized(NumVertexInstances); + } + + // Array of material index per triangle/face + TArray MeshTriangleVertexIndices; + MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); + // Array of vertex counts per triangle/face + TArray MeshTriangleVertexCounts; + MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); + + int32 TriangleIdx = 0; + int32 VertexInstanceIdx = 0; + // VertexInstanceIDToHIndex.Init(-1, MDVertexInstances.GetArraySize()); + + for (const FPolygonID &PolygonID : MDPolygons.GetElementIDs()) + { + for (const FTriangleID &TriangleID : MeshDescription.GetPolygonTriangleIDs(PolygonID)) + { + MeshTriangleVertexCounts[TriangleIdx] = 3; + for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) + { + // Reverse the winding order for Houdini (but still start at 0) + const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; + const FVertexInstanceID &VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, WindingIdx); + + // // UE Vertex Instance ID to Houdini Vertex Index look up + // VertexInstanceIDToHIndex[VertexInstanceID.GetValue()] = VertexInstanceIdx; + + // Calculate the index of the first component of a vertex instance's value in an inline float array + // representing vectors (3 float) per vertex instance + const int32 Float3Index = VertexInstanceIdx * 3; + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + const FVector2D &UV = VertexInstanceUVs.Get(VertexInstanceID, UVLayerIndex); + UVs[UVLayerIndex][Float3Index + 0] = UV.X; + UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; + UVs[UVLayerIndex][Float3Index + 2] = 0; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + const FVector &Normal = VertexInstanceNormals.Get(VertexInstanceID); + Normals[Float3Index + 0] = Normal.X; + Normals[Float3Index + 1] = Normal.Z; + Normals[Float3Index + 2] = Normal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + const FVector &Tangent = VertexInstanceTangents.Get(VertexInstanceID); + Tangents[Float3Index + 0] = Tangent.X; + Tangents[Float3Index + 1] = Tangent.Z; + Tangents[Float3Index + 2] = Tangent.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + // In order to calculate the binormal we also need the tangent and normal + if (bIsVertexInstanceBinormalSignsValid && bIsVertexInstanceTangentsValid && bIsVertexInstanceNormalsValid) + { + const float &BinormalSign = VertexInstanceBinormalSigns.Get(VertexInstanceID); + FVector Binormal = FVector::CrossProduct( + FVector(Tangents[Float3Index + 0], Tangents[Float3Index + 1], Tangents[Float3Index + 2]), + FVector(Normals[Float3Index + 0], Normals[Float3Index + 1], Normals[Float3Index + 2]) + ) * BinormalSign; + Binormals[Float3Index + 0] = Binormal.X; + Binormals[Float3Index + 1] = Binormal.Y; + Binormals[Float3Index + 2] = Binormal.Z; + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + FVector4 Color = FLinearColor::White; + if (bUseComponentOverrideColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshLODResources& RenderModel = StaticMesh->RenderData->LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + int32 Index = RenderModel.WedgeMap[VertexInstanceIdx]; + if (Index != INDEX_NONE) + { + Color = ColorVertexBuffer.VertexColor(Index).ReinterpretAsLinear(); + } + } + else + { + Color = VertexInstanceColors.Get(VertexInstanceID); + } + RGBColors[Float3Index + 0] = Color[0]; + RGBColors[Float3Index + 1] = Color[1]; + RGBColors[Float3Index + 2] = Color[2]; + Alphas[VertexInstanceIdx] = Color[3]; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + const FVertexID& VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID); + const int32 UEVertexIdx = VertexID.GetValue(); + if (VertexIDToHIndex.IsValidIndex(UEVertexIdx)) + { + MeshTriangleVertexIndices[VertexInstanceIdx] = VertexIDToHIndex[UEVertexIdx]; + } + + VertexInstanceIdx++; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + const FPolygonGroupID &PolygonGroupID = MeshDescription.GetPolygonPolygonGroup(PolygonID); + const int32 MaterialIndex = PolygonGroupToMaterialIndex.FindChecked(PolygonGroupID); + TriangleMaterialIndices.Add(MaterialIndex); + + TriangleIdx++; + } + } + + // Now transfer valid vertex instance attributes to Houdini vertex attributes + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) + { + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (UVLayerIndex > 0) + UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = NumVertexInstances; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, Normals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + Tangents.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceBinormalSignsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + Binormals.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + RGBColors.GetData(), 0, AttributeInfoVertex.count), false); + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.count = Alphas.Num(); + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + Alphas.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); + + // Send the array of face vertex counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); + + // Send material assignments to Houdini + if (NumMaterials > 0) + { + // List of materials, one for each face. + TArray TriangleMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); + + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (bAttributeSuccess) + { + check(0); + return false; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE SMOOTHING MASKS + //--------------------------------------------------------------------------------------------------------------------- + TArray TriangleSmoothingMasks; + TriangleSmoothingMasks.SetNumZeroed(NumTriangles); + FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); + if (TriangleSmoothingMasks.Num() > 0) + { + HAPI_AttributeInfo AttributeInfoSmoothingMasks; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + AttributeInfoSmoothingMasks.tupleSize = 1; + AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); + AttributeInfoSmoothingMasks.exists = true; + AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->LightMapResolution); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + // Try to create groups for the static mesh component's tags + if (StaticMeshComponent->ComponentTags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Try to create groups for the parent Actor's tags + if (ParentActor->Tags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + + /* + FString LevelPath = FString(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +void +FUnrealMeshTranslator::CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials) +{ + // We need to create list of unique materials. + TArray UniqueMaterialList; + + UMaterialInterface * MaterialInterface = nullptr; + char* UniqueName = nullptr; + + UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); + + if (Materials.Num()) + { + // We have materials. + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) + { + UniqueName = nullptr; + MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface) + { + // Null material interface found, add default instead. + UniqueMaterialList.Add(DefaultMaterialName); + + // No need to collect material parameters on the default material + continue; + } + + // We found a material, get its name and material parameters + FString FullMaterialName = MaterialInterface->GetPathName(); + UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); + UniqueMaterialList.Add(UniqueName); + } + } + else + { + // We do not have any materials, add default. + UniqueMaterialList.Add(DefaultMaterialName); + } + + // TODO: Needs to be improved! + // We shouldnt be testing for each face, but only for each unique facematerial value... + for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) + { + int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; + if (UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) + { + OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); + } + else + { + OutStaticMeshFaceMaterials.Add(DefaultMaterialName); + } + } +} + + +void +FUnrealMeshTranslator::CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials, + TMap> & OutScalarMaterialParameters, + TMap> & OutVectorMaterialParameters, + TMap> & OutTextureMaterialParameters) +{ + // We need to create list of unique materials. + TArray UniqueMaterialList; + + UMaterialInterface * MaterialInterface = nullptr; + char* UniqueName = nullptr; + + UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); + + // Initialize material parameter arrays + TMap> ScalarParams; + TMap> VectorParams; + TMap> TextureParams; + + if (Materials.Num()) + { + // We have materials. + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) + { + UniqueName = nullptr; + MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface) + { + // Null material interface found, add default instead. + UniqueMaterialList.Add(DefaultMaterialName); + + // No need to collect material parameters on the default material + continue; + } + + // We found a material, get its name and material parameters + FString FullMaterialName = MaterialInterface->GetPathName(); + UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); + UniqueMaterialList.Add(UniqueName); + + // Collect all scalar parameters in all materials + { + TArray MaterialScalarParamInfos; + TArray MaterialScalarParamGuids; + MaterialInterface->GetAllScalarParameterInfo(MaterialScalarParamInfos, MaterialScalarParamGuids); + + for (auto & CurScalarParam : MaterialScalarParamInfos) + { + FString CurScalarParamName = CurScalarParam.Name.ToString(); + float CurScalarVal; + MaterialInterface->GetScalarParameterValue(CurScalarParam, CurScalarVal); + if (!ScalarParams.Contains(CurScalarParamName)) + { + TArray CurArray; + CurArray.SetNumUninitialized(Materials.Num()); + // Initialize the array with the Min float value + for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) + CurArray[ArrIdx] = FLT_MIN; + + ScalarParams.Add(CurScalarParamName, CurArray); + OutScalarMaterialParameters.Add(CurScalarParamName); + } + + ScalarParams[CurScalarParamName][MaterialIdx] = CurScalarVal; + } + } + + // Collect all vector parameters in all materials + { + TArray MaterialVectorParamInfos; + TArray MaterialVectorParamGuids; + MaterialInterface->GetAllVectorParameterInfo(MaterialVectorParamInfos, MaterialVectorParamGuids); + + for (auto & CurVectorParam : MaterialVectorParamInfos) + { + FString CurVectorParamName = CurVectorParam.Name.ToString(); + FLinearColor CurVectorValue; + MaterialInterface->GetVectorParameterValue(CurVectorParam, CurVectorValue); + if (!VectorParams.Contains(CurVectorParamName)) + { + TArray CurArray; + CurArray.SetNumUninitialized(Materials.Num()); + FLinearColor MinColor(FLT_MIN, FLT_MIN, FLT_MIN, FLT_MIN); + for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) + CurArray[ArrIdx] = MinColor; + + VectorParams.Add(CurVectorParamName, CurArray); + OutVectorMaterialParameters.Add(CurVectorParamName); + } + + VectorParams[CurVectorParamName][MaterialIdx] = CurVectorValue; + } + } + + // Collect all texture parameters in all materials + { + TArray MaterialTextureParamInfos; + TArray MaterialTextureParamGuids; + MaterialInterface->GetAllTextureParameterInfo(MaterialTextureParamInfos, MaterialTextureParamGuids); + + for (auto & CurTextureParam : MaterialTextureParamInfos) + { + FString CurTextureParamName = CurTextureParam.Name.ToString(); + UTexture * CurTexture = nullptr; + MaterialInterface->GetTextureParameterValue(CurTextureParam, CurTexture); + + if (!CurTexture || CurTexture->IsPendingKill()) + continue; + + FString TexturePath = CurTexture->GetPathName(); + if (!TextureParams.Contains(CurTextureParamName)) + { + TArray CurArray; + CurArray.SetNumZeroed(Materials.Num()); + + TextureParams.Add(CurTextureParamName, CurArray); + OutTextureMaterialParameters.Add(CurTextureParamName); + } + + char * TexturePathRawStr = UniqueName = FHoudiniEngineUtils::ExtractRawString(TexturePath); + TextureParams[CurTextureParamName][MaterialIdx] = TexturePathRawStr; + } + } + + } + } + else + { + // We do not have any materials, add default. + UniqueMaterialList.Add(DefaultMaterialName); + } + + // TODO: Needs to be improved! + // We shouldnt be testing for each face, but only for each unique facematerial value... + for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) + { + int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; + if(UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) + { + OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); + + for (auto & Pair : ScalarParams) + { + OutScalarMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); + } + + for (auto & Pair : VectorParams) + { + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].R); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].G); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].B); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].A); + } + + for (auto & Pair : TextureParams) + { + OutTextureMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); + } + } + else + { + OutStaticMeshFaceMaterials.Add(DefaultMaterialName); + } + } +} + + +void +FUnrealMeshTranslator::DeleteFaceMaterialArray(TArray& OutStaticMeshFaceMaterials) +{ + // Clean up the memory allocated by CreateFaceMaterialArray() + TSet UniqueMaterials(OutStaticMeshFaceMaterials); + for (TSet::TIterator Iter = UniqueMaterials.CreateIterator(); Iter; ++Iter) + { + char* MaterialName = *Iter; + FMemory::Free(MaterialName); + } + + OutStaticMeshFaceMaterials.Empty(); +} + +bool +FUnrealMeshTranslator::CreateInputNodeForBox( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& BoxCenter, + const FVector& BoxExtent, + const FRotator& BoxRotation) +{ + // Create a new input node for the box collider + FString BoxName = TEXT("box") + FString::FromInt(ColliderIndex); + + // Create the node in this input object's OBJ node + HAPI_NodeId BoxNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("box"), BoxName, false, &BoxNodeId), false); + + // Set the box parameters + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 0, BoxExtent.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 1, BoxExtent.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 2, BoxExtent.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 0, BoxCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 1, BoxCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 2, BoxCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 0, BoxRotation.Roll); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 2, BoxRotation.Pitch); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 1, BoxRotation.Yaw); + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), BoxNodeId, &CookOptions); + */ + if (!FHoudiniEngineUtils::HapiCookNode(BoxNodeId, nullptr, true)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_box") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, BoxNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForSphere( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphereCenter, + const float& SphereRadius) +{ + // Create a new input node for the sphere collider + const char * SphereName = ""; + { + FString SPH = TEXT("Sphere") + FString::FromInt(ColliderIndex); + SphereName = TCHAR_TO_UTF8(*SPH); + } + + // Create the node in this input object's OBJ node + HAPI_NodeId SphereNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "sphere", SphereName, false, &SphereNodeId), false); + + // Set the box parameters + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 1, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 2, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 0, SphereCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 1, SphereCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 2, SphereCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "type", 0, 1); + /* + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "scale", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + */ + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), SphereNodeId, &CookOptions); + */ + if (!FHoudiniEngineUtils::HapiCookNode(SphereNodeId, nullptr, true)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_sphere") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphereNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForSphyl( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphylCenter, + const FRotator& SphylRotation, + const float& SphylRadius, + const float& SphereLength) +{ + // + // Get the Sphyl's vertices and indices + // (code drived from FKSphylElem::GetElemSolid) + // + + // TODO: + // Rotation? + + const int32 NumSides = 6; + const int32 NumRings = (NumSides / 2) + 1; + + // The first/last arc are on top of each other. + const int32 NumVerts = (NumSides + 1) * (NumRings + 1); + + // Calculate the vertices for one arc + TArray ArcVertices; + ArcVertices.SetNum(NumRings + 1); + for (int32 RingIdx = 0; RingIdx < NumRings + 1; RingIdx++) + { + float Angle; + float ZOffset; + if (RingIdx <= NumSides / 4) + { + Angle = ((float)RingIdx / (NumRings - 1)) * PI; + ZOffset = 0.5 * SphereLength; + } + else + { + Angle = ((float)(RingIdx - 1) / (NumRings - 1)) * PI; + ZOffset = -0.5 * SphereLength; + } + + // Note- unit sphere, so position always has mag of one. We can just use it for normal! + FVector SpherePos; + SpherePos.X = 0.0f; + SpherePos.Y = SphylRadius * FMath::Sin(Angle); + SpherePos.Z = SphylRadius * FMath::Cos(Angle); + + ArcVertices[RingIdx] = SpherePos + FVector(0, 0, ZOffset); + } + + // Get the transform matrix for the rotation + FRotationMatrix SphylRotMatrix(SphylRotation); + + // Get the Sphyl's vertices by rotating the arc NumSides+1 times. + TArray Vertices; + Vertices.SetNum(NumVerts * 3); + for (int32 SideIdx = 0; SideIdx < NumSides + 1; SideIdx++) + { + const FRotator ArcRotator(0, 360.f * ((float)SideIdx / NumSides), 0); + const FRotationMatrix ArcRot(ArcRotator); + const float XTexCoord = ((float)SideIdx / NumSides); + + for (int32 VertIdx = 0; VertIdx < NumRings + 1; VertIdx++) + { + int32 VIx = (NumRings + 1)*SideIdx + VertIdx; + + FVector CurPosition = SphylCenter + ArcRot.TransformPosition(ArcVertices[VertIdx]); + CurPosition = SphylRotMatrix.TransformPosition(CurPosition); + + // Convert the UE4 position to Houdini + Vertices[VIx * 3 + 0] = CurPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[VIx * 3 + 1] = CurPosition.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[VIx * 3 + 2] = CurPosition.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + } + + // Add all of the indices to the mesh. + int32 NumIndices = NumSides * NumRings * 6; + TArray Indices; + Indices.SetNum(NumIndices); + + int32 CurIndex = 0; + for (int32 SideIdx = 0; SideIdx < NumSides; SideIdx++) + { + const int32 a0start = (SideIdx + 0) * (NumRings + 1); + const int32 a1start = (SideIdx + 1) * (NumRings + 1); + for (int32 RingIdx = 0; RingIdx < NumRings; RingIdx++) + { + // First Tri (reverse winding) + Indices[CurIndex+0] = a0start + RingIdx + 0; + Indices[CurIndex+2] = a1start + RingIdx + 0; + Indices[CurIndex+1] = a0start + RingIdx + 1; + CurIndex += 3; + // Second Tri (reverse winding) + Indices[CurIndex+0] = a1start + RingIdx + 0; + Indices[CurIndex+2] = a1start + RingIdx + 1; + Indices[CurIndex+1] = a0start + RingIdx + 1; + CurIndex += 3; + } + } + + // + // Create the Sphyl Mesh in houdini + // + HAPI_NodeId SphylNodeId = -1; + FString SphylName = TEXT("Sphyl") + FString::FromInt(ColliderIndex); + if(!CreateInputNodeForCollider(SphylNodeId, InParentNodeID, ColliderIndex, SphylName, Vertices, Indices)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_capsule") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphylNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForConvex( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FKConvexElem& ConvexCollider) +{ + TArray Vertices; + TArray Indices; + +#if PHYSICS_INTERFACE_PHYSX + if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) +#elif WITH_CHAOS + //if (ConvexCollider.GetChaosConvexMesh().IsValid()) + if (ConvexCollider.IndexData.Num() > 0 && ConvexCollider.IndexData.Num() % 3 == 0) +#else + if(false) +#endif + { + // Get the convex colliders vertices and indices from the mesh + TArray VertexBuffer; + TArray IndexBuffer; + ConvexCollider.AddCachedSolidConvexGeom(VertexBuffer, IndexBuffer, FColor::White); + + Vertices.SetNum(VertexBuffer.Num() * 3); + int32 CurIndex = 0; + for (auto& CurVert : VertexBuffer) + { + Vertices[CurIndex * 3 + 0] = CurVert.Position.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 1] = CurVert.Position.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 2] = CurVert.Position.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + CurIndex++; + } + + Indices.SetNum(IndexBuffer.Num()); + for (int Idx = 0; (Idx + 2) < IndexBuffer.Num(); Idx += 3) + { + // Reverse winding + Indices[Idx + 0] = Indices[Idx + 0]; + Indices[Idx + 2] = Indices[Idx + 1]; + Indices[Idx + 1] = Indices[Idx + 2]; + } + } + else + { + int32 NumVert = ConvexCollider.VertexData.Num(); + Vertices.SetNum(NumVert * 3); + //Indices.SetNum(NumVert); + int32 CurIndex = 0; + for (auto& CurVert : ConvexCollider.VertexData) + { + Vertices[CurIndex * 3 + 0] = CurVert.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 1] = CurVert.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 2] = CurVert.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + + /* + // TODO: Get proper polygons... + Indices[CurIndex] = CurIndex; + */ + CurIndex++; + } + + // TODO: Get Proper polygons + for (int32 Idx = 0; Idx + 2 < NumVert; Idx++) + { + Indices.Add(Idx + 0); + Indices.Add(Idx + 1); + Indices.Add(Idx + 2); + } + + /* + for (int32 Idx = 0; Idx + 3 < NumVert; Idx+= 4) + { + Indices.Add(Idx + 0); + Indices.Add(Idx + 1); + Indices.Add(Idx + 2); + + Indices.Add(Idx + 2); + Indices.Add(Idx + 1); + Indices.Add(Idx + 3); + } + */ + } + + // + // Create the Convex Mesh in houdini + // + HAPI_NodeId ConvexNodeId = -1; + FString ConvexName = TEXT("Convex") + FString::FromInt(ColliderIndex); + if (!CreateInputNodeForCollider(ConvexNodeId, InParentNodeID, ColliderIndex, ConvexName, Vertices, Indices)) + return false; + + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), ColliderNodeId, &CookOptions); + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "groupcreate", GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_ucx + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_ucx") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Create a convex hull (shrinkwrap::2.0) node to fix the lack of proper indices + HAPI_NodeId ConvexHullNodeId = -1; + FString ConvexHullName = TEXT("ConvexHull") + FString::FromInt(ColliderIndex); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "shrinkwrap::2.0", ConvexHullName, false, &ConvexHullNodeId), false); + + if (ConvexHullNodeId > 0) + { + // Connect the collider to the convex hull + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), ConvexHullNodeId, 0, ConvexNodeId, 0), false); + + // Connect the convex hull to the group + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexHullNodeId, 0), false); + } + else + { + // Connect the collider to the group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexNodeId, 0), false); + + } + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForCollider( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FString& ColliderName, + const TArray& ColliderVertices, + const TArray& ColliderIndices) +{ + // Create a new input node for the collider + const char * ColliderNameStr = TCHAR_TO_UTF8(*ColliderName); + + // Create the node in this input object's OBJ node + HAPI_NodeId ColliderNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "null", ColliderNameStr, false, &ColliderNodeId), false); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = ColliderIndices.Num(); + Part.faceCount = ColliderIndices.Num() / 3; + Part.pointCount = ColliderVertices.Num() / 3; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), ColliderNodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = ColliderVertices.Num() / 3; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Upload the positions + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + ColliderVertices.GetData(), 0, AttributeInfoPoint.count), false); + + // Upload the indices + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, ColliderIndices.GetData(), 0, ColliderIndices.Num()), false); + + // Generate the array of face counts. + TArray ColldierFaceCounts; + ColldierFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, ColldierFaceCounts.GetData(), 0, ColldierFaceCounts.Num()), false); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), ColliderNodeId), false); + + OutNodeId = ColliderNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + const int32 & NodeId, + const int32 & PartId, + const int32 & Count, + const TArray & TriangleMaterials, + const TMap> & ScalarMaterialParameters, + const TMap> & VectorMaterialParameters, + const TMap> & TextureMaterialParameters) +{ + if (NodeId < 0) + return false; + + bool bSuccess = true; + + // Create attribute for materials. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.count = Count; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial, + (const char **)TriangleMaterials.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + + // Add scalar material parameter attributes + for (auto & Pair : ScalarMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 1; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + Pair.Value.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + // Add vector material parameters + for (auto & Pair : VectorMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 4; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + Pair.Value.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + // Add texture material parameter attributes + for (auto & Pair : TextureMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 1; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // Replace null strings by empty strings to prevent crashes when setting the attribute. + char* EmptyString = nullptr; + TArray StringData = Pair.Value; + for (auto& CurValue : StringData) + { + if (CurValue != nullptr) + continue; + + if (!EmptyString) + { + // Allocate the empty string the first time it is needed. This is free'd along with + // the other strings in the arrays in TextureMaterialParameters by calls to DeleteFaceMaterialArray + EmptyString = FHoudiniEngineUtils::ExtractRawString(FString(TEXT(""))); + } + CurValue = EmptyString; + } + + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + (const char **)StringData.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + return bSuccess; +} + +/* +bool +FUnrealMeshTranslator::AddLevelPathAttributeToMesh( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const FString& LevelPath, + const int32& Count) +{ + if (NodeId == -1 || LevelPath.IsEmpty() || Count <= 0) + return false; + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = Count; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Count); + for (int32 Idx = 0; Idx < Count; ++Idx) + { + PrimitiveAttrs[Idx] = LevelPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} */ \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.h b/Source/HoudiniEngine/Private/UnrealMeshTranslator.h index 42a4c7243..b524ea308 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.h @@ -1,164 +1,176 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -class UStaticMesh; -class UStaticMeshComponent; -class UMaterialInterface; -class UStaticMeshSocket; - -struct FStaticMeshSourceModel; -struct FStaticMeshLODResources; -struct FMeshDescription; -struct FKConvexElem; - -struct HOUDINIENGINE_API FUnrealMeshTranslator -{ - public: - - // HAPI : Marshaling, extract geometry and create input asset for it - return true on success - static bool HapiCreateInputNodeForStaticMesh( - UStaticMesh * Mesh, - HAPI_NodeId& InputObjectNodeId, - const FString& InputNodeName, - class UStaticMeshComponent* StaticMeshComponent = nullptr, - const bool& ExportAllLODs = false, - const bool& ExportSockets = false, - const bool& ExportColliders = false); - - // Convert the Mesh using FStaticMeshLODResources - static bool CreateInputNodeForStaticMeshLODResources( - const HAPI_NodeId& NodeId, - const FStaticMeshLODResources& LODResources, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - // Convert the Mesh using FMeshDescription - static bool CreateInputNodeForMeshDescription( - const HAPI_NodeId& NodeId, - const FMeshDescription& MeshDescription, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - // Convert the Mesh using FRawMesh - - static bool CreateInputNodeForRawMesh( - const HAPI_NodeId& NodeId, - const FStaticMeshSourceModel& SourceModel, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - static bool CreateInputNodeForBox( - HAPI_NodeId& OutBoxNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& BoxCenter, - const FVector& BoxExtent, - const FRotator& BoxRotation); - - static bool CreateInputNodeForSphere( - HAPI_NodeId& OutSphereNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphereCenter, - const float& SphereRadius); - - static bool CreateInputNodeForSphyl( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphylCenter, - const FRotator& SphylRotation, - const float& SphylRadius, - const float& SphereLength); - - static bool CreateInputNodeForConvex( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FKConvexElem& ConvexCollider); - - static bool CreateInputNodeForCollider( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FString& ColliderName, - const TArray& ColliderVertices, - const TArray& ColliderIndices); - - static bool CreateInputNodeForMeshSockets( - const TArray& InMeshSocket, - const HAPI_NodeId& InParentNodeId, - HAPI_NodeId& OutSocketsNodeId); - - // Create helper array of material names, used for marshalling static mesh's materials. - // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() - static void CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray & OutStaticMeshFaceMaterials, - TMap> & OutScalarMaterialParameters, - TMap> & OutVectorMaterialParameters, - TMap> & OutTextureMaterialParameters); - - // Delete helper array of material names. - // Clean up the memory allocated by CreateFaceMaterialArray() - static void DeleteFaceMaterialArray(TArray & OutStaticMeshFaceMaterials); - - // Create and set mesh material attribute and material (scalar, vector and texture) parameters attributes - static bool CreateHoudiniMeshAttributes( - const int32 & NodeId, - const int32 & PartId, - const int32 & Count, - const TArray & TriangleMaterials, - const TMap> & ScalarMaterialParameters, - const TMap> & VectorMaterialParameters, - const TMap> & TextureMaterialParameters); - - /* - // Creates the unreal_level_path attribute on the input mesh - static bool AddLevelPathAttributeToMesh( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const FString& LevelPath, - const int32& Count); - */ - - private: - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +class UStaticMesh; +class UStaticMeshComponent; +class UMaterialInterface; +class UStaticMeshSocket; + +struct FStaticMeshSourceModel; +struct FStaticMeshLODResources; +struct FMeshDescription; +struct FKConvexElem; + +struct HOUDINIENGINE_API FUnrealMeshTranslator +{ + public: + + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForStaticMesh( + UStaticMesh * Mesh, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + class UStaticMeshComponent* StaticMeshComponent = nullptr, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false, + const bool& ExportColliders = false); + + // Convert the Mesh using FStaticMeshLODResources + static bool CreateInputNodeForStaticMeshLODResources( + const HAPI_NodeId& NodeId, + const FStaticMeshLODResources& LODResources, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + // Convert the Mesh using FMeshDescription + static bool CreateInputNodeForMeshDescription( + const HAPI_NodeId& NodeId, + const FMeshDescription& MeshDescription, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + // Convert the Mesh using FRawMesh + + static bool CreateInputNodeForRawMesh( + const HAPI_NodeId& NodeId, + const FStaticMeshSourceModel& SourceModel, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + static bool CreateInputNodeForBox( + HAPI_NodeId& OutBoxNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& BoxCenter, + const FVector& BoxExtent, + const FRotator& BoxRotation); + + static bool CreateInputNodeForSphere( + HAPI_NodeId& OutSphereNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphereCenter, + const float& SphereRadius); + + static bool CreateInputNodeForSphyl( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphylCenter, + const FRotator& SphylRotation, + const float& SphylRadius, + const float& SphereLength); + + static bool CreateInputNodeForConvex( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FKConvexElem& ConvexCollider); + + static bool CreateInputNodeForCollider( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FString& ColliderName, + const TArray& ColliderVertices, + const TArray& ColliderIndices); + + static bool CreateInputNodeForMeshSockets( + const TArray& InMeshSocket, + const HAPI_NodeId& InParentNodeId, + HAPI_NodeId& OutSocketsNodeId); + + + // Helper function to extract the array of material names used by a given mesh + // This is used for marshalling static mesh's materials. + // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() + static void CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials); + + // Helper function to extract the array of material names used by a given mesh + // Also extracts all scalar/vector/texture parameter in the materials + // This is used for marshalling static mesh's materials. + // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() + // The texture parameter array also needs to be cleared. + static void CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray & OutStaticMeshFaceMaterials, + TMap> & OutScalarMaterialParameters, + TMap> & OutVectorMaterialParameters, + TMap> & OutTextureMaterialParameters); + + // Delete helper array of material names. + // Clean up the memory allocated by CreateFaceMaterialArray() + static void DeleteFaceMaterialArray(TArray & OutStaticMeshFaceMaterials); + + // Create and set mesh material attribute and material (scalar, vector and texture) parameters attributes + static bool CreateHoudiniMeshAttributes( + const int32 & NodeId, + const int32 & PartId, + const int32 & Count, + const TArray & TriangleMaterials, + const TMap> & ScalarMaterialParameters, + const TMap> & VectorMaterialParameters, + const TMap> & TextureMaterialParameters); + + /* + // Creates the unreal_level_path attribute on the input mesh + static bool AddLevelPathAttributeToMesh( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const FString& LevelPath, + const int32& Count); + */ + + private: + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp index 0cc61dd0d..268476eba 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp @@ -1,123 +1,124 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#include "UnrealSplineTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "Components/SplineComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniSplineTranslator.h" - -bool -FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId& CreatedInputNodeId, const FString& NodeName) -{ - if (!SplineComponent || SplineComponent->IsPendingKill()) - return false; - - int32 NumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); - float SplineLength = SplineComponent->GetSplineLength(); - - // Calculate the number of refined point we want - int32 NumberOfRefinedSplinePoints = SplineResolution > 0.0f ? ceil(SplineLength / SplineResolution) + 1 : NumberOfControlPoints; - - TArray RefinedSplinePositions; - TArray RefinedSplineRotations; - TArray RefinedSplineScales; - - if (NumberOfRefinedSplinePoints <= NumberOfControlPoints) - { - // There's not enough refined points, so we'll use the control points instead - RefinedSplinePositions.SetNumZeroed(NumberOfControlPoints); - RefinedSplineRotations.SetNumZeroed(NumberOfControlPoints); - RefinedSplineScales.SetNumZeroed(NumberOfControlPoints); - - for (int32 n = 0; n < NumberOfControlPoints; ++n) - { - RefinedSplinePositions[n] = SplineComponent->GetLocationAtSplinePoint(n, ESplineCoordinateSpace::Local); - RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World); - RefinedSplineScales[n] = SplineComponent->GetScaleAtSplinePoint(n); - } - } - else - { - // Calculate the refined spline component - RefinedSplinePositions.SetNumZeroed(NumberOfRefinedSplinePoints); - RefinedSplineRotations.SetNumZeroed(NumberOfRefinedSplinePoints); - RefinedSplineScales.SetNumZeroed(NumberOfRefinedSplinePoints); - - float CurrentDistance = 0.0f; - for (int32 n = 0; n < NumberOfRefinedSplinePoints; ++n) - { - RefinedSplinePositions[n] = SplineComponent->GetLocationAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::Local); - RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::World); - RefinedSplineScales[n] = SplineComponent->GetScaleAtDistanceAlongSpline(CurrentDistance); - - CurrentDistance += SplineResolution; - } - } - - - if (!FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData(CreatedInputNodeId, NodeName, - &RefinedSplinePositions, &RefinedSplineRotations, &RefinedSplineScales, - EHoudiniCurveType::Polygon, EHoudiniCurveMethod::Breakpoints, false, SplineComponent->IsClosedLoop())) - return false; - - // Add spline component tags if it has any - bool NeedToCommit = FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, SplineComponent->ComponentTags); - - // Add the parent actor's tag if it has any - AActor* ParentActor = SplineComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - if (FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, ParentActor->Tags)) - NeedToCommit = true; - - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - FHoudiniApi::GetPartInfo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId, 0, &PartInfo); - - // Add the unreal_actor_path attribute - if(FHoudiniEngineUtils::AddActorPathAttribute(CreatedInputNodeId, 0, ParentActor, PartInfo.faceCount)) - NeedToCommit = true; - - // Add the unreal_level_path attribute - if(FHoudiniEngineUtils::AddLevelPathAttribute(CreatedInputNodeId, 0, ParentActor->GetLevel(), PartInfo.faceCount)) - NeedToCommit = true; - } - - if (NeedToCommit) - { - // We successfully added tags to the geo, so we need to commit the changes - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups for the spline input's tags!")); - } - - - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealSplineTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "Components/SplineComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniSplineTranslator.h" + +bool +FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId& CreatedInputNodeId, const FString& NodeName) +{ + if (!SplineComponent || SplineComponent->IsPendingKill()) + return false; + + int32 NumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); + float SplineLength = SplineComponent->GetSplineLength(); + + // Calculate the number of refined point we want + int32 NumberOfRefinedSplinePoints = SplineResolution > 0.0f ? ceil(SplineLength / SplineResolution) + 1 : NumberOfControlPoints; + + TArray RefinedSplinePositions; + TArray RefinedSplineRotations; + TArray RefinedSplineScales; + + if (NumberOfRefinedSplinePoints <= NumberOfControlPoints) + { + // There's not enough refined points, so we'll use the control points instead + RefinedSplinePositions.SetNumZeroed(NumberOfControlPoints); + RefinedSplineRotations.SetNumZeroed(NumberOfControlPoints); + RefinedSplineScales.SetNumZeroed(NumberOfControlPoints); + + for (int32 n = 0; n < NumberOfControlPoints; ++n) + { + RefinedSplinePositions[n] = SplineComponent->GetLocationAtSplinePoint(n, ESplineCoordinateSpace::Local); + RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World); + RefinedSplineScales[n] = SplineComponent->GetScaleAtSplinePoint(n); + } + } + else + { + // Calculate the refined spline component + RefinedSplinePositions.SetNumZeroed(NumberOfRefinedSplinePoints); + RefinedSplineRotations.SetNumZeroed(NumberOfRefinedSplinePoints); + RefinedSplineScales.SetNumZeroed(NumberOfRefinedSplinePoints); + + float CurrentDistance = 0.0f; + for (int32 n = 0; n < NumberOfRefinedSplinePoints; ++n) + { + RefinedSplinePositions[n] = SplineComponent->GetLocationAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::Local); + RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::World); + RefinedSplineScales[n] = SplineComponent->GetScaleAtDistanceAlongSpline(CurrentDistance); + + CurrentDistance += SplineResolution; + } + } + + + if (!FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData(CreatedInputNodeId, NodeName, + &RefinedSplinePositions, &RefinedSplineRotations, &RefinedSplineScales, + EHoudiniCurveType::Polygon, EHoudiniCurveMethod::Breakpoints, false, SplineComponent->IsClosedLoop())) + return false; + + // Add spline component tags if it has any + bool NeedToCommit = FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, SplineComponent->ComponentTags); + + // Add the parent actor's tag if it has any + AActor* ParentActor = SplineComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + if (FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, ParentActor->Tags)) + NeedToCommit = true; + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + FHoudiniApi::GetPartInfo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId, 0, &PartInfo); + + // Add the unreal_actor_path attribute + if(FHoudiniEngineUtils::AddActorPathAttribute(CreatedInputNodeId, 0, ParentActor, PartInfo.faceCount)) + NeedToCommit = true; + + // Add the unreal_level_path attribute + if(FHoudiniEngineUtils::AddLevelPathAttribute(CreatedInputNodeId, 0, ParentActor->GetLevel(), PartInfo.faceCount)) + NeedToCommit = true; + } + + if (NeedToCommit) + { + // We successfully added tags to the geo, so we need to commit the changes + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups for the spline input's tags!")); + } + + + return true; +} diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.h b/Source/HoudiniEngine/Private/UnrealSplineTranslator.h index aef0858d9..e6d302233 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.h @@ -1,39 +1,39 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "UObject/NameTypes.h" - -class USplineComponent; - -struct HOUDINIENGINE_API FUnrealSplineTranslator -{ -public: - static bool CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId &CreatedInputNodeId, const FString& NodeName); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "UObject/NameTypes.h" + +class USplineComponent; + +struct HOUDINIENGINE_API FUnrealSplineTranslator +{ +public: + static bool CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId &CreatedInputNodeId, const FString& NodeName); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI.h b/Source/HoudiniEngine/Public/HAPI/HAPI.h index 7391fae93..8ea30ffdd 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI.h @@ -4372,7 +4372,7 @@ HAPI_DECL HAPI_GetVertexList( const HAPI_Session * session, /// @brief Get the attribute info struct for the attribute specified by name. /// -/// @ingroup GeometryGetters +/// @ingroup Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4408,7 +4408,7 @@ HAPI_DECL HAPI_GetAttributeInfo( const HAPI_Session * session, /// name string handles are only valid until the next time this /// function is called. /// -/// @ingroup GeometryGetters +/// @ingroup Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4445,7 +4445,7 @@ HAPI_DECL HAPI_GetAttributeNames( const HAPI_Session * session, /// @brief Get attribute integer data. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4505,7 +4505,7 @@ HAPI_DECL HAPI_GetAttributeIntData( const HAPI_Session * session, /// Therefore the array values are returned as a flat array, with /// another sizes array containing the lengths of each array entry. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4565,7 +4565,7 @@ HAPI_DECL HAPI_GetAttributeIntArrayData( const HAPI_Session * session, /// @brief Get attribute 64-bit integer data. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4625,7 +4625,7 @@ HAPI_DECL HAPI_GetAttributeInt64Data( const HAPI_Session * session, /// Therefore the array values are returned as a flat array, with /// another sizes array containing the lengths of each array entry. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4684,7 +4684,7 @@ HAPI_DECL HAPI_GetAttributeInt64ArrayData( const HAPI_Session * session, /// @brief Get attribute float data. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4744,7 +4744,7 @@ HAPI_DECL HAPI_GetAttributeFloatData( const HAPI_Session * session, /// Therefore the array values are returned as a flat array, with /// another sizes array containing the lengths of each array entry. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4803,7 +4803,7 @@ HAPI_DECL HAPI_GetAttributeFloatArrayData( const HAPI_Session * session, /// @brief Get 64-bit attribute float data. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4863,7 +4863,7 @@ HAPI_DECL HAPI_GetAttributeFloat64Data( const HAPI_Session * session, /// Therefore the array values are returned as a flat array, with /// another sizes array containing the lengths of each array entry. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4924,7 +4924,7 @@ HAPI_DECL HAPI_GetAttributeFloat64ArrayData( const HAPI_Session * session, /// returned are only valid until the next time this function /// is called. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -4978,7 +4978,7 @@ HAPI_DECL HAPI_GetAttributeStringData( const HAPI_Session * session, /// Note that the string handles returned are only valid until /// the next time this function is called. /// -/// @ingroup GeometryGetters +/// @ingroup GeometryGetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5433,7 +5433,7 @@ HAPI_DECL HAPI_SetVertexList( const HAPI_Session * session, /// @brief Add an attribute. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5458,9 +5458,10 @@ HAPI_DECL HAPI_AddAttribute( const HAPI_Session * session, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info ); + /// @brief Delete an attribute from an input geo /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5488,7 +5489,7 @@ HAPI_DECL HAPI_DeleteAttribute( const HAPI_Session * session, /// @brief Set attribute integer data. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5535,7 +5536,7 @@ HAPI_DECL HAPI_SetAttributeIntData( const HAPI_Session * session, /// @brief Set 64-bit attribute integer data. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5582,7 +5583,7 @@ HAPI_DECL HAPI_SetAttributeInt64Data( const HAPI_Session * session, /// @brief Set attribute float data. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5629,7 +5630,7 @@ HAPI_DECL HAPI_SetAttributeFloatData( const HAPI_Session * session, /// @brief Set 64-bit attribute float data. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. @@ -5676,7 +5677,7 @@ HAPI_DECL HAPI_SetAttributeFloat64Data( const HAPI_Session * session, /// @brief Set attribute string data. /// -/// @ingroup GeometrySetters +/// @ingroup GeometrySetters Attributes /// /// @param[in] session /// The session of Houdini you are interacting with. diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h index 4869521bf..7273fd16b 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h @@ -839,65 +839,101 @@ HAPI_C_ENUM_TYPEDEF( HAPI_PDG_State ) /// Used with PDG functions enum HAPI_PDG_EventType { + /// An empty, undefined event. Should be ignored. HAPI_PDG_EVENT_NULL, + /// Sent when a new work item is added by a node HAPI_PDG_EVENT_WORKITEM_ADD, + /// Sent when a work item is deleted from a node HAPI_PDG_EVENT_WORKITEM_REMOVE, + /// Sent when a work item's state changes HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE, + /// Sent when a work item has a dependency added HAPI_PDG_EVENT_WORKITEM_ADD_DEP, + /// Sent when a dependency is removed from a work item HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP, + /// Sent from dynamic work items that generate from a cooked item HAPI_PDG_EVENT_WORKITEM_ADD_PARENT, + /// Sent when the parent item for a work item is deleted HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT, + /// A node event that indicates that node is about to have all its work items cleared HAPI_PDG_EVENT_NODE_CLEAR, + /// Sent when an error is issued by the node HAPI_PDG_EVENT_COOK_ERROR, + /// Sent when an warning is issued by the node HAPI_PDG_EVENT_COOK_WARNING, + /// Sent for each node in the graph, when a cook completes HAPI_PDG_EVENT_COOK_COMPLETE, + /// A node event indicating that one more items in the node will be dirtied HAPI_PDG_EVENT_DIRTY_START, + /// A node event indicating that the node has finished dirtying items HAPI_PDG_EVENT_DIRTY_STOP, + /// A event indicating that the entire graph is about to be dirtied HAPI_PDG_EVENT_DIRTY_ALL, + /// A work item event that indicates the item has been selected in the TOPs UI HAPI_PDG_EVENT_UI_SELECT, + /// Sent when a new node is created HAPI_PDG_EVENT_NODE_CREATE, + /// Sent when a node was removed from the graph HAPI_PDG_EVENT_NODE_REMOVE, + /// Sent when a node was renamed HAPI_PDG_EVENT_NODE_RENAME, + /// Sent when a node was connected to another node HAPI_PDG_EVENT_NODE_CONNECT, + /// Sent when a node is disconnected from another node HAPI_PDG_EVENT_NODE_DISCONNECT, + /// Sent when an int attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_INT, + /// Sent when a float attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_FLOAT, + /// Sent when a string attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_STRING, + /// Sent when a file attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_FILE, + /// Sent when a Python object attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT, + /// Sent when a geometry attribute value is modified on a work item HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY, + /// Deprecated HAPI_PDG_EVENT_WORKITEM_MERGE, + /// Sent when an output file is added to a work item HAPI_PDG_EVENT_WORKITEM_RESULT, + /// Sent when a work items priority is changed HAPI_PDG_EVENT_WORKITEM_PRIORITY, - + /// Sent for each node in the graph, when a cook starts HAPI_PDG_EVENT_COOK_START, - + /// Deprecated HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR, + /// Deprecated HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR, + /// Deprecated HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE, - + /// Deprecated HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED, - + /// A special enum that represents the OR of all event types HAPI_PDG_EVENT_ALL, + /// A special enum that represents the OR of both the `CookError` and `CookWarning` events HAPI_PDG_EVENT_LOG, + /// Sent when a new scheduler is added to the graph HAPI_PDG_EVENT_SCHEDULER_ADDED, + /// Sent when a scheduler is removed from the graph HAPI_PDG_EVENT_SCHEDULER_REMOVED, + /// Sent when the scheduler assigned to a node is changed HAPI_PDG_EVENT_SET_SCHEDULER, - + /// Deprecated HAPI_PDG_EVENT_SERVICE_MANAGER_ALL, HAPI_PDG_CONTEXT_EVENTS diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h index 36f73c1cd..be6f3a1ac 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h @@ -1,5 +1,5 @@ /* -* Copyright (c) <2020> Side Effects Software Inc.* +* Copyright (c) <2021> 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. * * Produced by: * Side Effects Software Inc @@ -27,7 +27,7 @@ // expecting to compile against. #define HAPI_VERSION_HOUDINI_MAJOR 18 #define HAPI_VERSION_HOUDINI_MINOR 5 -#define HAPI_VERSION_HOUDINI_BUILD 408 +#define HAPI_VERSION_HOUDINI_BUILD 462 #define HAPI_VERSION_HOUDINI_PATCH 0 // The two components of the Houdini Engine (marketed) version. diff --git a/Source/HoudiniEngine/Public/HoudiniApi.h b/Source/HoudiniEngine/Public/HoudiniApi.h index 46383cfa7..00b066cd6 100644 --- a/Source/HoudiniEngine/Public/HoudiniApi.h +++ b/Source/HoudiniEngine/Public/HoudiniApi.h @@ -1,977 +1,977 @@ -/* - * Copyright (c) <2020> 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. - * - * - * COMMENTS: - * This file is generated. Do not modify directly. - */ - -#pragma once -#include "HAPI/HAPI.h" -#include "HAL/PlatformProcess.h" - - -struct HOUDINIENGINE_API FHoudiniApi -{ -public: - - static void InitializeHAPI(void* LibraryHandle); - static void FinalizeHAPI(); - static bool IsHAPIInitialized(); - -public: - - typedef HAPI_Result (*AddAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*AddGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - typedef HAPI_AssetInfo (*AssetInfo_CreateFuncPtr)(); - typedef void (*AssetInfo_InitFuncPtr)(HAPI_AssetInfo * in); - typedef HAPI_AttributeInfo (*AttributeInfo_CreateFuncPtr)(); - typedef void (*AttributeInfo_InitFuncPtr)(HAPI_AttributeInfo * in); - typedef HAPI_Result (*BindCustomImplementationFuncPtr)(HAPI_SessionType session_type, const char * dll_path); - typedef HAPI_Result (*CancelPDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - typedef HAPI_Result (*CheckForSpecificErrorsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); - typedef HAPI_Result (*CleanupFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*ClearConnectionErrorFuncPtr)(); - typedef HAPI_Result (*CloseSessionFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*CommitGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*CommitWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*ComposeChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - typedef HAPI_Result (*ComposeNodeCookResultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); - typedef HAPI_Result (*ComposeObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); - typedef HAPI_Result (*ConnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); - typedef HAPI_Result (*ConvertMatrixToEulerFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - typedef HAPI_Result (*ConvertMatrixToQuatFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); - typedef HAPI_Result (*ConvertTransformFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - typedef HAPI_Result (*ConvertTransformEulerToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); - typedef HAPI_Result (*ConvertTransformQuatToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); - typedef HAPI_Result (*CookNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); - typedef HAPI_Bool (*CookOptions_AreEqualFuncPtr)(const HAPI_CookOptions * left, const HAPI_CookOptions * right); - typedef HAPI_CookOptions (*CookOptions_CreateFuncPtr)(); - typedef void (*CookOptions_InitFuncPtr)(HAPI_CookOptions * in); - typedef HAPI_Result (*CookPDGFuncPtr)(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); - typedef HAPI_Result (*CreateCustomSessionFuncPtr)(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); - typedef HAPI_Result (*CreateHeightFieldInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); - typedef HAPI_Result (*CreateHeightfieldInputVolumeNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); - typedef HAPI_Result (*CreateInProcessSessionFuncPtr)(HAPI_Session * session); - typedef HAPI_Result (*CreateInputNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); - typedef HAPI_Result (*CreateNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); - typedef HAPI_Result (*CreateThriftNamedPipeSessionFuncPtr)(HAPI_Session * session, const char * pipe_name); - typedef HAPI_Result (*CreateThriftSocketSessionFuncPtr)(HAPI_Session * session, const char * host_name, int port); - typedef HAPI_Result (*CreateWorkitemFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); - typedef HAPI_CurveInfo (*CurveInfo_CreateFuncPtr)(); - typedef void (*CurveInfo_InitFuncPtr)(HAPI_CurveInfo * in); - typedef HAPI_Result (*DeleteAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*DeleteGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - typedef HAPI_Result (*DeleteNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*DirtyPDGNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); - typedef HAPI_Result (*DisconnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); - typedef HAPI_Result (*DisconnectNodeOutputsAtFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); - typedef HAPI_Result (*ExtractImageToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); - typedef HAPI_Result (*ExtractImageToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); - typedef HAPI_GeoInfo (*GeoInfo_CreateFuncPtr)(); - typedef int (*GeoInfo_GetGroupCountByTypeFuncPtr)(HAPI_GeoInfo * in, HAPI_GroupType type); - typedef void (*GeoInfo_InitFuncPtr)(HAPI_GeoInfo * in); - typedef HAPI_Result (*GetActiveCacheCountFuncPtr)(const HAPI_Session * session, int * active_cache_count); - typedef HAPI_Result (*GetActiveCacheNamesFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); - typedef HAPI_Result (*GetAssetDefinitionParmCountsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); - typedef HAPI_Result (*GetAssetDefinitionParmInfosFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); - typedef HAPI_Result (*GetAssetDefinitionParmValuesFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); - typedef HAPI_Result (*GetAssetInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); - typedef HAPI_Result (*GetAttributeFloat64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeFloatArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*GetAttributeInt64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeIntArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); - typedef HAPI_Result (*GetAttributeStringArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); - typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); - typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); - typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); - typedef HAPI_Result (*GetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); - typedef HAPI_Result (*GetComposedChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); - typedef HAPI_Result (*GetComposedNodeCookResultFuncPtr)(const HAPI_Session * session, char * string_value, int length); - typedef HAPI_Result (*GetComposedObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); - typedef HAPI_Result (*GetComposedObjectTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); - typedef HAPI_Result (*GetConnectionErrorFuncPtr)(char * string_value, int length, HAPI_Bool clear); - typedef HAPI_Result (*GetConnectionErrorLengthFuncPtr)(int * buffer_length); - typedef HAPI_Result (*GetCookingCurrentCountFuncPtr)(const HAPI_Session * session, int * count); - typedef HAPI_Result (*GetCookingTotalCountFuncPtr)(const HAPI_Session * session, int * count); - typedef HAPI_Result (*GetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); - typedef HAPI_Result (*GetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); - typedef HAPI_Result (*GetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); - typedef HAPI_Result (*GetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); - typedef HAPI_Result (*GetDisplayGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); - typedef HAPI_Result (*GetEnvIntFuncPtr)(HAPI_EnvIntType int_type, int * value); - typedef HAPI_Result (*GetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); - typedef HAPI_Result (*GetFirstVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - typedef HAPI_Result (*GetGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); - typedef HAPI_Result (*GetGeoSizeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); - typedef HAPI_Result (*GetGroupCountOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); - typedef HAPI_Result (*GetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - typedef HAPI_Result (*GetGroupMembershipOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - typedef HAPI_Result (*GetGroupNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - typedef HAPI_Result (*GetGroupNamesOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - typedef HAPI_Result (*GetHIPFileNodeCountFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, int * count); - typedef HAPI_Result (*GetHIPFileNodeIdsFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); - typedef HAPI_Result (*GetHandleBindingInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); - typedef HAPI_Result (*GetHandleInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); - typedef HAPI_Result (*GetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); - typedef HAPI_Result (*GetImageFilePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); - typedef HAPI_Result (*GetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); - typedef HAPI_Result (*GetImageMemoryBufferFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); - typedef HAPI_Result (*GetImagePlaneCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); - typedef HAPI_Result (*GetImagePlanesFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); - typedef HAPI_Result (*GetInstanceTransformsOnPartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - typedef HAPI_Result (*GetInstancedObjectIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); - typedef HAPI_Result (*GetInstancedPartIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); - typedef HAPI_Result (*GetInstancerPartTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - typedef HAPI_Result (*GetManagerNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); - typedef HAPI_Result (*GetMaterialInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); - typedef HAPI_Result (*GetMaterialNodeIdsOnFacesFuncPtr)(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); - typedef HAPI_Result (*GetNextVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - typedef HAPI_Result (*GetNodeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); - typedef HAPI_Result (*GetNodeInputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); - typedef HAPI_Result (*GetNodeOutputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); - typedef HAPI_Result (*GetNodePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); - typedef HAPI_Result (*GetNumWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * num); - typedef HAPI_Result (*GetObjectInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); - typedef HAPI_Result (*GetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); - typedef HAPI_Result (*GetOutputNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); - typedef HAPI_Result (*GetPDGEventsFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); - typedef HAPI_Result (*GetPDGGraphContextIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); - typedef HAPI_Result (*GetPDGGraphContextsFuncPtr)(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); - typedef HAPI_Result (*GetPDGStateFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); - typedef HAPI_Result (*GetParametersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); - typedef HAPI_Result (*GetParmChoiceListsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); - typedef HAPI_Result (*GetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); - typedef HAPI_Result (*GetParmFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); - typedef HAPI_Result (*GetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); - typedef HAPI_Result (*GetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); - typedef HAPI_Result (*GetParmIdFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); - typedef HAPI_Result (*GetParmInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); - typedef HAPI_Result (*GetParmInfoFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); - typedef HAPI_Result (*GetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); - typedef HAPI_Result (*GetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); - typedef HAPI_Result (*GetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); - typedef HAPI_Result (*GetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); - typedef HAPI_Result (*GetParmStringValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); - typedef HAPI_Result (*GetParmTagNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); - typedef HAPI_Result (*GetParmTagValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); - typedef HAPI_Result (*GetParmWithTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); - typedef HAPI_Result (*GetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); - typedef HAPI_Result (*GetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); - typedef HAPI_Result (*GetPresetBufLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); - typedef HAPI_Result (*GetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int * value); - typedef HAPI_Result (*GetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); - typedef HAPI_Result (*GetServerEnvVarCountFuncPtr)(const HAPI_Session * session, int * env_count); - typedef HAPI_Result (*GetServerEnvVarListFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); - typedef HAPI_Result (*GetSessionEnvIntFuncPtr)(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); - typedef HAPI_Result (*GetSessionSyncInfoFuncPtr)(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); - typedef HAPI_Result (*GetSphereInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); - typedef HAPI_Result (*GetStatusFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, int * status); - typedef HAPI_Result (*GetStatusStringFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); - typedef HAPI_Result (*GetStatusStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); - typedef HAPI_Result (*GetStringFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); - typedef HAPI_Result (*GetStringBatchFuncPtr)(const HAPI_Session * session, char * char_buffer, int char_array_length); - typedef HAPI_Result (*GetStringBatchSizeFuncPtr)(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); - typedef HAPI_Result (*GetStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); - typedef HAPI_Result (*GetSupportedImageFileFormatCountFuncPtr)(const HAPI_Session * session, int * file_format_count); - typedef HAPI_Result (*GetSupportedImageFileFormatsFuncPtr)(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); - typedef HAPI_Result (*GetTimeFuncPtr)(const HAPI_Session * session, float * time); - typedef HAPI_Result (*GetTimelineOptionsFuncPtr)(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); - typedef HAPI_Result (*GetTotalCookCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - typedef HAPI_Result (*GetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool * enabled); - typedef HAPI_Result (*GetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); - typedef HAPI_Result (*GetViewportFuncPtr)(const HAPI_Session * session, HAPI_Viewport * viewport); - typedef HAPI_Result (*GetVolumeBoundsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); - typedef HAPI_Result (*GetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); - typedef HAPI_Result (*GetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); - typedef HAPI_Result (*GetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); - typedef HAPI_Result (*GetVolumeVisualInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); - typedef HAPI_Result (*GetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); - typedef HAPI_Result (*GetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); - typedef HAPI_Result (*GetWorkitemDataLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); - typedef HAPI_Result (*GetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); - typedef HAPI_Result (*GetWorkitemInfoFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); - typedef HAPI_Result (*GetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); - typedef HAPI_Result (*GetWorkitemResultInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); - typedef HAPI_Result (*GetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); - typedef HAPI_Result (*GetWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); - typedef HAPI_HandleBindingInfo (*HandleBindingInfo_CreateFuncPtr)(); - typedef void (*HandleBindingInfo_InitFuncPtr)(HAPI_HandleBindingInfo * in); - typedef HAPI_HandleInfo (*HandleInfo_CreateFuncPtr)(); - typedef void (*HandleInfo_InitFuncPtr)(HAPI_HandleInfo * in); - typedef HAPI_ImageFileFormat (*ImageFileFormat_CreateFuncPtr)(); - typedef void (*ImageFileFormat_InitFuncPtr)(HAPI_ImageFileFormat *in); - typedef HAPI_ImageInfo (*ImageInfo_CreateFuncPtr)(); - typedef void (*ImageInfo_InitFuncPtr)(HAPI_ImageInfo * in); - typedef HAPI_Result (*InitializeFuncPtr)(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); - typedef HAPI_Result (*InsertMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - typedef HAPI_Result (*InterruptFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*IsInitializedFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*IsNodeValidFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); - typedef HAPI_Result (*IsSessionValidFuncPtr)(const HAPI_Session * session); - typedef HAPI_Keyframe (*Keyframe_CreateFuncPtr)(); - typedef void (*Keyframe_InitFuncPtr)(HAPI_Keyframe * in); - typedef HAPI_Result (*LoadAssetLibraryFromFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - typedef HAPI_Result (*LoadAssetLibraryFromMemoryFuncPtr)(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - typedef HAPI_Result (*LoadGeoFromFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_Result (*LoadGeoFromMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); - typedef HAPI_Result (*LoadHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); - typedef HAPI_Result (*LoadNodeFromFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); - typedef HAPI_MaterialInfo (*MaterialInfo_CreateFuncPtr)(); - typedef void (*MaterialInfo_InitFuncPtr)(HAPI_MaterialInfo * in); - typedef HAPI_Result (*MergeHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); - typedef HAPI_NodeInfo (*NodeInfo_CreateFuncPtr)(); - typedef void (*NodeInfo_InitFuncPtr)(HAPI_NodeInfo * in); - typedef HAPI_ObjectInfo (*ObjectInfo_CreateFuncPtr)(); - typedef void (*ObjectInfo_InitFuncPtr)(HAPI_ObjectInfo * in); - typedef HAPI_ParmChoiceInfo (*ParmChoiceInfo_CreateFuncPtr)(); - typedef void (*ParmChoiceInfo_InitFuncPtr)(HAPI_ParmChoiceInfo * in); - typedef HAPI_Result (*ParmHasExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); - typedef HAPI_Result (*ParmHasTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); - typedef HAPI_ParmInfo (*ParmInfo_CreateFuncPtr)(); - typedef int (*ParmInfo_GetFloatValueCountFuncPtr)(const HAPI_ParmInfo * in); - typedef int (*ParmInfo_GetIntValueCountFuncPtr)(const HAPI_ParmInfo * in); - typedef int (*ParmInfo_GetStringValueCountFuncPtr)(const HAPI_ParmInfo* in); - typedef void (*ParmInfo_InitFuncPtr)(HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsFloatFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsIntFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsNodeFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsNonValueFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsPathFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsStringFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_PartInfo (*PartInfo_CreateFuncPtr)(); - typedef int (*PartInfo_GetAttributeCountByOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - typedef int (*PartInfo_GetElementCountByAttributeOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - typedef int (*PartInfo_GetElementCountByGroupTypeFuncPtr)(HAPI_PartInfo * in, HAPI_GroupType type); - typedef void (*PartInfo_InitFuncPtr)(HAPI_PartInfo * in); - typedef HAPI_Result (*PausePDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - typedef HAPI_Result (*PythonThreadInterpreterLockFuncPtr)(const HAPI_Session * session, HAPI_Bool locked); - typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); - typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); - typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const int string_handle); - typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); - typedef HAPI_Result (*RenderCOPToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId cop_node_id); - typedef HAPI_Result (*RenderTextureToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); - typedef HAPI_Result (*ResetSimulationFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*RevertGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*RevertParmToDefaultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); - typedef HAPI_Result (*RevertParmToDefaultsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); - typedef HAPI_Result (*SaveGeoToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_Result (*SaveGeoToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); - typedef HAPI_Result (*SaveHIPFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); - typedef HAPI_Result (*SaveNodeToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_SessionSyncInfo (*SessionSyncInfo_CreateFuncPtr)(); - typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); - typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); - typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); - typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); - typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); - typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, int * handle_value); - typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); - typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); - typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); - typedef HAPI_Result (*SetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); - typedef HAPI_Result (*SetNodeDisplayFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); - typedef HAPI_Result (*SetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); - typedef HAPI_Result (*SetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*SetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); - typedef HAPI_Result (*SetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); - typedef HAPI_Result (*SetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); - typedef HAPI_Result (*SetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); - typedef HAPI_Result (*SetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); - typedef HAPI_Result (*SetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*SetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); - typedef HAPI_Result (*SetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); - typedef HAPI_Result (*SetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int value); - typedef HAPI_Result (*SetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, const char * value); - typedef HAPI_Result (*SetSessionSyncFuncPtr)(const HAPI_Session * session, HAPI_Bool enable); - typedef HAPI_Result (*SetSessionSyncInfoFuncPtr)(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); - typedef HAPI_Result (*SetTimeFuncPtr)(const HAPI_Session * session, float time); - typedef HAPI_Result (*SetTimelineOptionsFuncPtr)(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); - typedef HAPI_Result (*SetTransformAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - typedef HAPI_Result (*SetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool enabled); - typedef HAPI_Result (*SetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); - typedef HAPI_Result (*SetViewportFuncPtr)(const HAPI_Session * session, const HAPI_Viewport * viewport); - typedef HAPI_Result (*SetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); - typedef HAPI_Result (*SetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); - typedef HAPI_Result (*SetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); - typedef HAPI_Result (*SetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); - typedef HAPI_Result (*SetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); - typedef HAPI_Result (*SetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); - typedef HAPI_Result (*SetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); - typedef HAPI_Result (*SetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); - typedef HAPI_Result (*StartThriftNamedPipeServerFuncPtr)(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); - typedef HAPI_Result (*StartThriftSocketServerFuncPtr)(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); - typedef HAPI_ThriftServerOptions (*ThriftServerOptions_CreateFuncPtr)(); - typedef void (*ThriftServerOptions_InitFuncPtr)(HAPI_ThriftServerOptions * in); - typedef HAPI_TimelineOptions (*TimelineOptions_CreateFuncPtr)(); - typedef void (*TimelineOptions_InitFuncPtr)(HAPI_TimelineOptions * in); - typedef HAPI_TransformEuler (*TransformEuler_CreateFuncPtr)(); - typedef void (*TransformEuler_InitFuncPtr)(HAPI_TransformEuler * in); - typedef HAPI_Transform (*Transform_CreateFuncPtr)(); - typedef void (*Transform_InitFuncPtr)(HAPI_Transform * in); - typedef HAPI_Viewport (*Viewport_CreateFuncPtr)(); - typedef HAPI_VolumeInfo (*VolumeInfo_CreateFuncPtr)(); - typedef void (*VolumeInfo_InitFuncPtr)(HAPI_VolumeInfo * in); - typedef HAPI_VolumeTileInfo (*VolumeTileInfo_CreateFuncPtr)(); - typedef void (*VolumeTileInfo_InitFuncPtr)(HAPI_VolumeTileInfo * in); - -public: - - static AddAttributeFuncPtr AddAttribute; - static AddGroupFuncPtr AddGroup; - static AssetInfo_CreateFuncPtr AssetInfo_Create; - static AssetInfo_InitFuncPtr AssetInfo_Init; - static AttributeInfo_CreateFuncPtr AttributeInfo_Create; - static AttributeInfo_InitFuncPtr AttributeInfo_Init; - static BindCustomImplementationFuncPtr BindCustomImplementation; - static CancelPDGCookFuncPtr CancelPDGCook; - static CheckForSpecificErrorsFuncPtr CheckForSpecificErrors; - static CleanupFuncPtr Cleanup; - static ClearConnectionErrorFuncPtr ClearConnectionError; - static CloseSessionFuncPtr CloseSession; - static CommitGeoFuncPtr CommitGeo; - static CommitWorkitemsFuncPtr CommitWorkitems; - static ComposeChildNodeListFuncPtr ComposeChildNodeList; - static ComposeNodeCookResultFuncPtr ComposeNodeCookResult; - static ComposeObjectListFuncPtr ComposeObjectList; - static ConnectNodeInputFuncPtr ConnectNodeInput; - static ConvertMatrixToEulerFuncPtr ConvertMatrixToEuler; - static ConvertMatrixToQuatFuncPtr ConvertMatrixToQuat; - static ConvertTransformFuncPtr ConvertTransform; - static ConvertTransformEulerToMatrixFuncPtr ConvertTransformEulerToMatrix; - static ConvertTransformQuatToMatrixFuncPtr ConvertTransformQuatToMatrix; - static CookNodeFuncPtr CookNode; - static CookOptions_AreEqualFuncPtr CookOptions_AreEqual; - static CookOptions_CreateFuncPtr CookOptions_Create; - static CookOptions_InitFuncPtr CookOptions_Init; - static CookPDGFuncPtr CookPDG; - static CreateCustomSessionFuncPtr CreateCustomSession; - static CreateHeightFieldInputFuncPtr CreateHeightFieldInput; - static CreateHeightfieldInputVolumeNodeFuncPtr CreateHeightfieldInputVolumeNode; - static CreateInProcessSessionFuncPtr CreateInProcessSession; - static CreateInputNodeFuncPtr CreateInputNode; - static CreateNodeFuncPtr CreateNode; - static CreateThriftNamedPipeSessionFuncPtr CreateThriftNamedPipeSession; - static CreateThriftSocketSessionFuncPtr CreateThriftSocketSession; - static CreateWorkitemFuncPtr CreateWorkitem; - static CurveInfo_CreateFuncPtr CurveInfo_Create; - static CurveInfo_InitFuncPtr CurveInfo_Init; - static DeleteAttributeFuncPtr DeleteAttribute; - static DeleteGroupFuncPtr DeleteGroup; - static DeleteNodeFuncPtr DeleteNode; - static DirtyPDGNodeFuncPtr DirtyPDGNode; - static DisconnectNodeInputFuncPtr DisconnectNodeInput; - static DisconnectNodeOutputsAtFuncPtr DisconnectNodeOutputsAt; - static ExtractImageToFileFuncPtr ExtractImageToFile; - static ExtractImageToMemoryFuncPtr ExtractImageToMemory; - static GeoInfo_CreateFuncPtr GeoInfo_Create; - static GeoInfo_GetGroupCountByTypeFuncPtr GeoInfo_GetGroupCountByType; - static GeoInfo_InitFuncPtr GeoInfo_Init; - static GetActiveCacheCountFuncPtr GetActiveCacheCount; - static GetActiveCacheNamesFuncPtr GetActiveCacheNames; - static GetAssetDefinitionParmCountsFuncPtr GetAssetDefinitionParmCounts; - static GetAssetDefinitionParmInfosFuncPtr GetAssetDefinitionParmInfos; - static GetAssetDefinitionParmValuesFuncPtr GetAssetDefinitionParmValues; - static GetAssetInfoFuncPtr GetAssetInfo; - static GetAttributeFloat64ArrayDataFuncPtr GetAttributeFloat64ArrayData; - static GetAttributeFloat64DataFuncPtr GetAttributeFloat64Data; - static GetAttributeFloatArrayDataFuncPtr GetAttributeFloatArrayData; - static GetAttributeFloatDataFuncPtr GetAttributeFloatData; - static GetAttributeInfoFuncPtr GetAttributeInfo; - static GetAttributeInt64ArrayDataFuncPtr GetAttributeInt64ArrayData; - static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; - static GetAttributeIntArrayDataFuncPtr GetAttributeIntArrayData; - static GetAttributeIntDataFuncPtr GetAttributeIntData; - static GetAttributeNamesFuncPtr GetAttributeNames; - static GetAttributeStringArrayDataFuncPtr GetAttributeStringArrayData; - static GetAttributeStringDataFuncPtr GetAttributeStringData; - static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; - static GetAvailableAssetsFuncPtr GetAvailableAssets; - static GetBoxInfoFuncPtr GetBoxInfo; - static GetCachePropertyFuncPtr GetCacheProperty; - static GetComposedChildNodeListFuncPtr GetComposedChildNodeList; - static GetComposedNodeCookResultFuncPtr GetComposedNodeCookResult; - static GetComposedObjectListFuncPtr GetComposedObjectList; - static GetComposedObjectTransformsFuncPtr GetComposedObjectTransforms; - static GetConnectionErrorFuncPtr GetConnectionError; - static GetConnectionErrorLengthFuncPtr GetConnectionErrorLength; - static GetCookingCurrentCountFuncPtr GetCookingCurrentCount; - static GetCookingTotalCountFuncPtr GetCookingTotalCount; - static GetCurveCountsFuncPtr GetCurveCounts; - static GetCurveInfoFuncPtr GetCurveInfo; - static GetCurveKnotsFuncPtr GetCurveKnots; - static GetCurveOrdersFuncPtr GetCurveOrders; - static GetDisplayGeoInfoFuncPtr GetDisplayGeoInfo; - static GetEnvIntFuncPtr GetEnvInt; - static GetFaceCountsFuncPtr GetFaceCounts; - static GetFirstVolumeTileFuncPtr GetFirstVolumeTile; - static GetGeoInfoFuncPtr GetGeoInfo; - static GetGeoSizeFuncPtr GetGeoSize; - static GetGroupCountOnPackedInstancePartFuncPtr GetGroupCountOnPackedInstancePart; - static GetGroupMembershipFuncPtr GetGroupMembership; - static GetGroupMembershipOnPackedInstancePartFuncPtr GetGroupMembershipOnPackedInstancePart; - static GetGroupNamesFuncPtr GetGroupNames; - static GetGroupNamesOnPackedInstancePartFuncPtr GetGroupNamesOnPackedInstancePart; - static GetHIPFileNodeCountFuncPtr GetHIPFileNodeCount; - static GetHIPFileNodeIdsFuncPtr GetHIPFileNodeIds; - static GetHandleBindingInfoFuncPtr GetHandleBindingInfo; - static GetHandleInfoFuncPtr GetHandleInfo; - static GetHeightFieldDataFuncPtr GetHeightFieldData; - static GetImageFilePathFuncPtr GetImageFilePath; - static GetImageInfoFuncPtr GetImageInfo; - static GetImageMemoryBufferFuncPtr GetImageMemoryBuffer; - static GetImagePlaneCountFuncPtr GetImagePlaneCount; - static GetImagePlanesFuncPtr GetImagePlanes; - static GetInstanceTransformsOnPartFuncPtr GetInstanceTransformsOnPart; - static GetInstancedObjectIdsFuncPtr GetInstancedObjectIds; - static GetInstancedPartIdsFuncPtr GetInstancedPartIds; - static GetInstancerPartTransformsFuncPtr GetInstancerPartTransforms; - static GetManagerNodeIdFuncPtr GetManagerNodeId; - static GetMaterialInfoFuncPtr GetMaterialInfo; - static GetMaterialNodeIdsOnFacesFuncPtr GetMaterialNodeIdsOnFaces; - static GetNextVolumeTileFuncPtr GetNextVolumeTile; - static GetNodeInfoFuncPtr GetNodeInfo; - static GetNodeInputNameFuncPtr GetNodeInputName; - static GetNodeOutputNameFuncPtr GetNodeOutputName; - static GetNodePathFuncPtr GetNodePath; - static GetNumWorkitemsFuncPtr GetNumWorkitems; - static GetObjectInfoFuncPtr GetObjectInfo; - static GetObjectTransformFuncPtr GetObjectTransform; - static GetOutputNodeIdFuncPtr GetOutputNodeId; - static GetPDGEventsFuncPtr GetPDGEvents; - static GetPDGGraphContextIdFuncPtr GetPDGGraphContextId; - static GetPDGGraphContextsFuncPtr GetPDGGraphContexts; - static GetPDGStateFuncPtr GetPDGState; - static GetParametersFuncPtr GetParameters; - static GetParmChoiceListsFuncPtr GetParmChoiceLists; - static GetParmExpressionFuncPtr GetParmExpression; - static GetParmFileFuncPtr GetParmFile; - static GetParmFloatValueFuncPtr GetParmFloatValue; - static GetParmFloatValuesFuncPtr GetParmFloatValues; - static GetParmIdFromNameFuncPtr GetParmIdFromName; - static GetParmInfoFuncPtr GetParmInfo; - static GetParmInfoFromNameFuncPtr GetParmInfoFromName; - static GetParmIntValueFuncPtr GetParmIntValue; - static GetParmIntValuesFuncPtr GetParmIntValues; - static GetParmNodeValueFuncPtr GetParmNodeValue; - static GetParmStringValueFuncPtr GetParmStringValue; - static GetParmStringValuesFuncPtr GetParmStringValues; - static GetParmTagNameFuncPtr GetParmTagName; - static GetParmTagValueFuncPtr GetParmTagValue; - static GetParmWithTagFuncPtr GetParmWithTag; - static GetPartInfoFuncPtr GetPartInfo; - static GetPresetFuncPtr GetPreset; - static GetPresetBufLengthFuncPtr GetPresetBufLength; - static GetServerEnvIntFuncPtr GetServerEnvInt; - static GetServerEnvStringFuncPtr GetServerEnvString; - static GetServerEnvVarCountFuncPtr GetServerEnvVarCount; - static GetServerEnvVarListFuncPtr GetServerEnvVarList; - static GetSessionEnvIntFuncPtr GetSessionEnvInt; - static GetSessionSyncInfoFuncPtr GetSessionSyncInfo; - static GetSphereInfoFuncPtr GetSphereInfo; - static GetStatusFuncPtr GetStatus; - static GetStatusStringFuncPtr GetStatusString; - static GetStatusStringBufLengthFuncPtr GetStatusStringBufLength; - static GetStringFuncPtr GetString; - static GetStringBatchFuncPtr GetStringBatch; - static GetStringBatchSizeFuncPtr GetStringBatchSize; - static GetStringBufLengthFuncPtr GetStringBufLength; - static GetSupportedImageFileFormatCountFuncPtr GetSupportedImageFileFormatCount; - static GetSupportedImageFileFormatsFuncPtr GetSupportedImageFileFormats; - static GetTimeFuncPtr GetTime; - static GetTimelineOptionsFuncPtr GetTimelineOptions; - static GetTotalCookCountFuncPtr GetTotalCookCount; - static GetUseHoudiniTimeFuncPtr GetUseHoudiniTime; - static GetVertexListFuncPtr GetVertexList; - static GetViewportFuncPtr GetViewport; - static GetVolumeBoundsFuncPtr GetVolumeBounds; - static GetVolumeInfoFuncPtr GetVolumeInfo; - static GetVolumeTileFloatDataFuncPtr GetVolumeTileFloatData; - static GetVolumeTileIntDataFuncPtr GetVolumeTileIntData; - static GetVolumeVisualInfoFuncPtr GetVolumeVisualInfo; - static GetVolumeVoxelFloatDataFuncPtr GetVolumeVoxelFloatData; - static GetVolumeVoxelIntDataFuncPtr GetVolumeVoxelIntData; - static GetWorkitemDataLengthFuncPtr GetWorkitemDataLength; - static GetWorkitemFloatDataFuncPtr GetWorkitemFloatData; - static GetWorkitemInfoFuncPtr GetWorkitemInfo; - static GetWorkitemIntDataFuncPtr GetWorkitemIntData; - static GetWorkitemResultInfoFuncPtr GetWorkitemResultInfo; - static GetWorkitemStringDataFuncPtr GetWorkitemStringData; - static GetWorkitemsFuncPtr GetWorkitems; - static HandleBindingInfo_CreateFuncPtr HandleBindingInfo_Create; - static HandleBindingInfo_InitFuncPtr HandleBindingInfo_Init; - static HandleInfo_CreateFuncPtr HandleInfo_Create; - static HandleInfo_InitFuncPtr HandleInfo_Init; - static ImageFileFormat_CreateFuncPtr ImageFileFormat_Create; - static ImageFileFormat_InitFuncPtr ImageFileFormat_Init; - static ImageInfo_CreateFuncPtr ImageInfo_Create; - static ImageInfo_InitFuncPtr ImageInfo_Init; - static InitializeFuncPtr Initialize; - static InsertMultiparmInstanceFuncPtr InsertMultiparmInstance; - static InterruptFuncPtr Interrupt; - static IsInitializedFuncPtr IsInitialized; - static IsNodeValidFuncPtr IsNodeValid; - static IsSessionValidFuncPtr IsSessionValid; - static Keyframe_CreateFuncPtr Keyframe_Create; - static Keyframe_InitFuncPtr Keyframe_Init; - static LoadAssetLibraryFromFileFuncPtr LoadAssetLibraryFromFile; - static LoadAssetLibraryFromMemoryFuncPtr LoadAssetLibraryFromMemory; - static LoadGeoFromFileFuncPtr LoadGeoFromFile; - static LoadGeoFromMemoryFuncPtr LoadGeoFromMemory; - static LoadHIPFileFuncPtr LoadHIPFile; - static LoadNodeFromFileFuncPtr LoadNodeFromFile; - static MaterialInfo_CreateFuncPtr MaterialInfo_Create; - static MaterialInfo_InitFuncPtr MaterialInfo_Init; - static MergeHIPFileFuncPtr MergeHIPFile; - static NodeInfo_CreateFuncPtr NodeInfo_Create; - static NodeInfo_InitFuncPtr NodeInfo_Init; - static ObjectInfo_CreateFuncPtr ObjectInfo_Create; - static ObjectInfo_InitFuncPtr ObjectInfo_Init; - static ParmChoiceInfo_CreateFuncPtr ParmChoiceInfo_Create; - static ParmChoiceInfo_InitFuncPtr ParmChoiceInfo_Init; - static ParmHasExpressionFuncPtr ParmHasExpression; - static ParmHasTagFuncPtr ParmHasTag; - static ParmInfo_CreateFuncPtr ParmInfo_Create; - static ParmInfo_GetFloatValueCountFuncPtr ParmInfo_GetFloatValueCount; - static ParmInfo_GetIntValueCountFuncPtr ParmInfo_GetIntValueCount; - static ParmInfo_GetStringValueCountFuncPtr ParmInfo_GetStringValueCount; - static ParmInfo_InitFuncPtr ParmInfo_Init; - static ParmInfo_IsFloatFuncPtr ParmInfo_IsFloat; - static ParmInfo_IsIntFuncPtr ParmInfo_IsInt; - static ParmInfo_IsNodeFuncPtr ParmInfo_IsNode; - static ParmInfo_IsNonValueFuncPtr ParmInfo_IsNonValue; - static ParmInfo_IsPathFuncPtr ParmInfo_IsPath; - static ParmInfo_IsStringFuncPtr ParmInfo_IsString; - static PartInfo_CreateFuncPtr PartInfo_Create; - static PartInfo_GetAttributeCountByOwnerFuncPtr PartInfo_GetAttributeCountByOwner; - static PartInfo_GetElementCountByAttributeOwnerFuncPtr PartInfo_GetElementCountByAttributeOwner; - static PartInfo_GetElementCountByGroupTypeFuncPtr PartInfo_GetElementCountByGroupType; - static PartInfo_InitFuncPtr PartInfo_Init; - static PausePDGCookFuncPtr PausePDGCook; - static PythonThreadInterpreterLockFuncPtr PythonThreadInterpreterLock; - static QueryNodeInputFuncPtr QueryNodeInput; - static QueryNodeOutputConnectedCountFuncPtr QueryNodeOutputConnectedCount; - static QueryNodeOutputConnectedNodesFuncPtr QueryNodeOutputConnectedNodes; - static RemoveCustomStringFuncPtr RemoveCustomString; - static RemoveMultiparmInstanceFuncPtr RemoveMultiparmInstance; - static RemoveParmExpressionFuncPtr RemoveParmExpression; - static RenameNodeFuncPtr RenameNode; - static RenderCOPToImageFuncPtr RenderCOPToImage; - static RenderTextureToImageFuncPtr RenderTextureToImage; - static ResetSimulationFuncPtr ResetSimulation; - static RevertGeoFuncPtr RevertGeo; - static RevertParmToDefaultFuncPtr RevertParmToDefault; - static RevertParmToDefaultsFuncPtr RevertParmToDefaults; - static SaveGeoToFileFuncPtr SaveGeoToFile; - static SaveGeoToMemoryFuncPtr SaveGeoToMemory; - static SaveHIPFileFuncPtr SaveHIPFile; - static SaveNodeToFileFuncPtr SaveNodeToFile; - static SessionSyncInfo_CreateFuncPtr SessionSyncInfo_Create; - static SetAnimCurveFuncPtr SetAnimCurve; - static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; - static SetAttributeFloatDataFuncPtr SetAttributeFloatData; - static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; - static SetAttributeIntDataFuncPtr SetAttributeIntData; - static SetAttributeStringDataFuncPtr SetAttributeStringData; - static SetCachePropertyFuncPtr SetCacheProperty; - static SetCurveCountsFuncPtr SetCurveCounts; - static SetCurveInfoFuncPtr SetCurveInfo; - static SetCurveKnotsFuncPtr SetCurveKnots; - static SetCurveOrdersFuncPtr SetCurveOrders; - static SetCustomStringFuncPtr SetCustomString; - static SetFaceCountsFuncPtr SetFaceCounts; - static SetGroupMembershipFuncPtr SetGroupMembership; - static SetHeightFieldDataFuncPtr SetHeightFieldData; - static SetImageInfoFuncPtr SetImageInfo; - static SetNodeDisplayFuncPtr SetNodeDisplay; - static SetObjectTransformFuncPtr SetObjectTransform; - static SetParmExpressionFuncPtr SetParmExpression; - static SetParmFloatValueFuncPtr SetParmFloatValue; - static SetParmFloatValuesFuncPtr SetParmFloatValues; - static SetParmIntValueFuncPtr SetParmIntValue; - static SetParmIntValuesFuncPtr SetParmIntValues; - static SetParmNodeValueFuncPtr SetParmNodeValue; - static SetParmStringValueFuncPtr SetParmStringValue; - static SetPartInfoFuncPtr SetPartInfo; - static SetPresetFuncPtr SetPreset; - static SetServerEnvIntFuncPtr SetServerEnvInt; - static SetServerEnvStringFuncPtr SetServerEnvString; - static SetSessionSyncFuncPtr SetSessionSync; - static SetSessionSyncInfoFuncPtr SetSessionSyncInfo; - static SetTimeFuncPtr SetTime; - static SetTimelineOptionsFuncPtr SetTimelineOptions; - static SetTransformAnimCurveFuncPtr SetTransformAnimCurve; - static SetUseHoudiniTimeFuncPtr SetUseHoudiniTime; - static SetVertexListFuncPtr SetVertexList; - static SetViewportFuncPtr SetViewport; - static SetVolumeInfoFuncPtr SetVolumeInfo; - static SetVolumeTileFloatDataFuncPtr SetVolumeTileFloatData; - static SetVolumeTileIntDataFuncPtr SetVolumeTileIntData; - static SetVolumeVoxelFloatDataFuncPtr SetVolumeVoxelFloatData; - static SetVolumeVoxelIntDataFuncPtr SetVolumeVoxelIntData; - static SetWorkitemFloatDataFuncPtr SetWorkitemFloatData; - static SetWorkitemIntDataFuncPtr SetWorkitemIntData; - static SetWorkitemStringDataFuncPtr SetWorkitemStringData; - static StartThriftNamedPipeServerFuncPtr StartThriftNamedPipeServer; - static StartThriftSocketServerFuncPtr StartThriftSocketServer; - static ThriftServerOptions_CreateFuncPtr ThriftServerOptions_Create; - static ThriftServerOptions_InitFuncPtr ThriftServerOptions_Init; - static TimelineOptions_CreateFuncPtr TimelineOptions_Create; - static TimelineOptions_InitFuncPtr TimelineOptions_Init; - static TransformEuler_CreateFuncPtr TransformEuler_Create; - static TransformEuler_InitFuncPtr TransformEuler_Init; - static Transform_CreateFuncPtr Transform_Create; - static Transform_InitFuncPtr Transform_Init; - static Viewport_CreateFuncPtr Viewport_Create; - static VolumeInfo_CreateFuncPtr VolumeInfo_Create; - static VolumeInfo_InitFuncPtr VolumeInfo_Init; - static VolumeTileInfo_CreateFuncPtr VolumeTileInfo_Create; - static VolumeTileInfo_InitFuncPtr VolumeTileInfo_Init; - -public: - - static HAPI_Result AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - static HAPI_Result AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - static HAPI_AssetInfo AssetInfo_CreateEmptyStub(); - static void AssetInfo_InitEmptyStub(HAPI_AssetInfo * in); - static HAPI_AttributeInfo AttributeInfo_CreateEmptyStub(); - static void AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in); - static HAPI_Result BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path); - static HAPI_Result CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - static HAPI_Result CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); - static HAPI_Result CleanupEmptyStub(const HAPI_Session * session); - static HAPI_Result ClearConnectionErrorEmptyStub(); - static HAPI_Result CloseSessionEmptyStub(const HAPI_Session * session); - static HAPI_Result CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - static HAPI_Result ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); - static HAPI_Result ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); - static HAPI_Result ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); - static HAPI_Result ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - static HAPI_Result ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); - static HAPI_Result ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - static HAPI_Result ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); - static HAPI_Result ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); - static HAPI_Result CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); - static HAPI_Bool CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right); - static HAPI_CookOptions CookOptions_CreateEmptyStub(); - static void CookOptions_InitEmptyStub(HAPI_CookOptions * in); - static HAPI_Result CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); - static HAPI_Result CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); - static HAPI_Result CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); - static HAPI_Result CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); - static HAPI_Result CreateInProcessSessionEmptyStub(HAPI_Session * session); - static HAPI_Result CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); - static HAPI_Result CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); - static HAPI_Result CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name); - static HAPI_Result CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port); - static HAPI_Result CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); - static HAPI_CurveInfo CurveInfo_CreateEmptyStub(); - static void CurveInfo_InitEmptyStub(HAPI_CurveInfo * in); - static HAPI_Result DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - static HAPI_Result DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - static HAPI_Result DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); - static HAPI_Result DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); - static HAPI_Result DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); - static HAPI_Result ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); - static HAPI_Result ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); - static HAPI_GeoInfo GeoInfo_CreateEmptyStub(); - static int GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type); - static void GeoInfo_InitEmptyStub(HAPI_GeoInfo * in); - static HAPI_Result GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count); - static HAPI_Result GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); - static HAPI_Result GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); - static HAPI_Result GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); - static HAPI_Result GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); - static HAPI_Result GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); - static HAPI_Result GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); - static HAPI_Result GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); - static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); - static HAPI_Result GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); - static HAPI_Result GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); - static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); - static HAPI_Result GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); - static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); - static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); - static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); - static HAPI_Result GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); - static HAPI_Result GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); - static HAPI_Result GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length); - static HAPI_Result GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); - static HAPI_Result GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); - static HAPI_Result GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear); - static HAPI_Result GetConnectionErrorLengthEmptyStub(int * buffer_length); - static HAPI_Result GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count); - static HAPI_Result GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count); - static HAPI_Result GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); - static HAPI_Result GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); - static HAPI_Result GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); - static HAPI_Result GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); - static HAPI_Result GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); - static HAPI_Result GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value); - static HAPI_Result GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); - static HAPI_Result GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - static HAPI_Result GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); - static HAPI_Result GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); - static HAPI_Result GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); - static HAPI_Result GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - static HAPI_Result GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - static HAPI_Result GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - static HAPI_Result GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - static HAPI_Result GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count); - static HAPI_Result GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); - static HAPI_Result GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); - static HAPI_Result GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); - static HAPI_Result GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); - static HAPI_Result GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); - static HAPI_Result GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); - static HAPI_Result GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); - static HAPI_Result GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); - static HAPI_Result GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); - static HAPI_Result GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - static HAPI_Result GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); - static HAPI_Result GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); - static HAPI_Result GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - static HAPI_Result GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); - static HAPI_Result GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); - static HAPI_Result GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); - static HAPI_Result GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - static HAPI_Result GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); - static HAPI_Result GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); - static HAPI_Result GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); - static HAPI_Result GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); - static HAPI_Result GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num); - static HAPI_Result GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); - static HAPI_Result GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); - static HAPI_Result GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); - static HAPI_Result GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); - static HAPI_Result GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); - static HAPI_Result GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); - static HAPI_Result GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); - static HAPI_Result GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); - static HAPI_Result GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); - static HAPI_Result GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); - static HAPI_Result GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); - static HAPI_Result GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); - static HAPI_Result GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); - static HAPI_Result GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); - static HAPI_Result GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); - static HAPI_Result GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); - static HAPI_Result GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); - static HAPI_Result GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); - static HAPI_Result GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); - static HAPI_Result GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); - static HAPI_Result GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); - static HAPI_Result GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); - static HAPI_Result GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); - static HAPI_Result GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); - static HAPI_Result GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); - static HAPI_Result GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); - static HAPI_Result GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); - static HAPI_Result GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value); - static HAPI_Result GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); - static HAPI_Result GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count); - static HAPI_Result GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); - static HAPI_Result GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); - static HAPI_Result GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); - static HAPI_Result GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); - static HAPI_Result GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status); - static HAPI_Result GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); - static HAPI_Result GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); - static HAPI_Result GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); - static HAPI_Result GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length); - static HAPI_Result GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); - static HAPI_Result GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); - static HAPI_Result GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count); - static HAPI_Result GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); - static HAPI_Result GetTimeEmptyStub(const HAPI_Session * session, float * time); - static HAPI_Result GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); - static HAPI_Result GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - static HAPI_Result GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled); - static HAPI_Result GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); - static HAPI_Result GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport); - static HAPI_Result GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); - static HAPI_Result GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); - static HAPI_Result GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); - static HAPI_Result GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); - static HAPI_Result GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); - static HAPI_Result GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); - static HAPI_Result GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); - static HAPI_Result GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); - static HAPI_Result GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); - static HAPI_Result GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); - static HAPI_Result GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); - static HAPI_Result GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); - static HAPI_Result GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); - static HAPI_Result GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); - static HAPI_HandleBindingInfo HandleBindingInfo_CreateEmptyStub(); - static void HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in); - static HAPI_HandleInfo HandleInfo_CreateEmptyStub(); - static void HandleInfo_InitEmptyStub(HAPI_HandleInfo * in); - static HAPI_ImageFileFormat ImageFileFormat_CreateEmptyStub(); - static void ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in); - static HAPI_ImageInfo ImageInfo_CreateEmptyStub(); - static void ImageInfo_InitEmptyStub(HAPI_ImageInfo * in); - static HAPI_Result InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); - static HAPI_Result InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - static HAPI_Result InterruptEmptyStub(const HAPI_Session * session); - static HAPI_Result IsInitializedEmptyStub(const HAPI_Session * session); - static HAPI_Result IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); - static HAPI_Result IsSessionValidEmptyStub(const HAPI_Session * session); - static HAPI_Keyframe Keyframe_CreateEmptyStub(); - static void Keyframe_InitEmptyStub(HAPI_Keyframe * in); - static HAPI_Result LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - static HAPI_Result LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - static HAPI_Result LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_Result LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); - static HAPI_Result LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); - static HAPI_Result LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); - static HAPI_MaterialInfo MaterialInfo_CreateEmptyStub(); - static void MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in); - static HAPI_Result MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); - static HAPI_NodeInfo NodeInfo_CreateEmptyStub(); - static void NodeInfo_InitEmptyStub(HAPI_NodeInfo * in); - static HAPI_ObjectInfo ObjectInfo_CreateEmptyStub(); - static void ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in); - static HAPI_ParmChoiceInfo ParmChoiceInfo_CreateEmptyStub(); - static void ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in); - static HAPI_Result ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); - static HAPI_Result ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); - static HAPI_ParmInfo ParmInfo_CreateEmptyStub(); - static int ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in); - static int ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in); - static int ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in); - static void ParmInfo_InitEmptyStub(HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in); - static HAPI_PartInfo PartInfo_CreateEmptyStub(); - static int PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - static int PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - static int PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type); - static void PartInfo_InitEmptyStub(HAPI_PartInfo * in); - static HAPI_Result PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - static HAPI_Result PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked); - static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); - static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); - static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle); - static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); - static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); - static HAPI_Result RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id); - static HAPI_Result RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); - static HAPI_Result ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); - static HAPI_Result RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); - static HAPI_Result SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_Result SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); - static HAPI_Result SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); - static HAPI_Result SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_SessionSyncInfo SessionSyncInfo_CreateEmptyStub(); - static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); - static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); - static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); - static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); - static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); - static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); - static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); - static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); - static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); - static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value); - static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); - static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); - static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); - static HAPI_Result SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); - static HAPI_Result SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); - static HAPI_Result SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); - static HAPI_Result SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - static HAPI_Result SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); - static HAPI_Result SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); - static HAPI_Result SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); - static HAPI_Result SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); - static HAPI_Result SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); - static HAPI_Result SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - static HAPI_Result SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); - static HAPI_Result SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); - static HAPI_Result SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value); - static HAPI_Result SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value); - static HAPI_Result SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable); - static HAPI_Result SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); - static HAPI_Result SetTimeEmptyStub(const HAPI_Session * session, float time); - static HAPI_Result SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); - static HAPI_Result SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - static HAPI_Result SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled); - static HAPI_Result SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); - static HAPI_Result SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport); - static HAPI_Result SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); - static HAPI_Result SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); - static HAPI_Result SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); - static HAPI_Result SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); - static HAPI_Result SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); - static HAPI_Result SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); - static HAPI_Result SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); - static HAPI_Result SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); - static HAPI_Result StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); - static HAPI_Result StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); - static HAPI_ThriftServerOptions ThriftServerOptions_CreateEmptyStub(); - static void ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in); - static HAPI_TimelineOptions TimelineOptions_CreateEmptyStub(); - static void TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in); - static HAPI_TransformEuler TransformEuler_CreateEmptyStub(); - static void TransformEuler_InitEmptyStub(HAPI_TransformEuler * in); - static HAPI_Transform Transform_CreateEmptyStub(); - static void Transform_InitEmptyStub(HAPI_Transform * in); - static HAPI_Viewport Viewport_CreateEmptyStub(); - static HAPI_VolumeInfo VolumeInfo_CreateEmptyStub(); - static void VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in); - static HAPI_VolumeTileInfo VolumeTileInfo_CreateEmptyStub(); - static void VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in); -}; +/* + * Copyright (c) <2021> 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. + * + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +#pragma once +#include "HAPI/HAPI.h" +#include "HAL/PlatformProcess.h" + + +struct HOUDINIENGINE_API FHoudiniApi +{ +public: + + static void InitializeHAPI(void* LibraryHandle); + static void FinalizeHAPI(); + static bool IsHAPIInitialized(); + +public: + + typedef HAPI_Result (*AddAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*AddGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + typedef HAPI_AssetInfo (*AssetInfo_CreateFuncPtr)(); + typedef void (*AssetInfo_InitFuncPtr)(HAPI_AssetInfo * in); + typedef HAPI_AttributeInfo (*AttributeInfo_CreateFuncPtr)(); + typedef void (*AttributeInfo_InitFuncPtr)(HAPI_AttributeInfo * in); + typedef HAPI_Result (*BindCustomImplementationFuncPtr)(HAPI_SessionType session_type, const char * dll_path); + typedef HAPI_Result (*CancelPDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + typedef HAPI_Result (*CheckForSpecificErrorsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); + typedef HAPI_Result (*CleanupFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*ClearConnectionErrorFuncPtr)(); + typedef HAPI_Result (*CloseSessionFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*CommitGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*CommitWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*ComposeChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + typedef HAPI_Result (*ComposeNodeCookResultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); + typedef HAPI_Result (*ComposeObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + typedef HAPI_Result (*ConnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); + typedef HAPI_Result (*ConvertMatrixToEulerFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + typedef HAPI_Result (*ConvertMatrixToQuatFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); + typedef HAPI_Result (*ConvertTransformFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + typedef HAPI_Result (*ConvertTransformEulerToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); + typedef HAPI_Result (*ConvertTransformQuatToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); + typedef HAPI_Result (*CookNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); + typedef HAPI_Bool (*CookOptions_AreEqualFuncPtr)(const HAPI_CookOptions * left, const HAPI_CookOptions * right); + typedef HAPI_CookOptions (*CookOptions_CreateFuncPtr)(); + typedef void (*CookOptions_InitFuncPtr)(HAPI_CookOptions * in); + typedef HAPI_Result (*CookPDGFuncPtr)(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); + typedef HAPI_Result (*CreateCustomSessionFuncPtr)(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); + typedef HAPI_Result (*CreateHeightFieldInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); + typedef HAPI_Result (*CreateHeightfieldInputVolumeNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); + typedef HAPI_Result (*CreateInProcessSessionFuncPtr)(HAPI_Session * session); + typedef HAPI_Result (*CreateInputNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); + typedef HAPI_Result (*CreateNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); + typedef HAPI_Result (*CreateThriftNamedPipeSessionFuncPtr)(HAPI_Session * session, const char * pipe_name); + typedef HAPI_Result (*CreateThriftSocketSessionFuncPtr)(HAPI_Session * session, const char * host_name, int port); + typedef HAPI_Result (*CreateWorkitemFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); + typedef HAPI_CurveInfo (*CurveInfo_CreateFuncPtr)(); + typedef void (*CurveInfo_InitFuncPtr)(HAPI_CurveInfo * in); + typedef HAPI_Result (*DeleteAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*DeleteGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + typedef HAPI_Result (*DeleteNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*DirtyPDGNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); + typedef HAPI_Result (*DisconnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); + typedef HAPI_Result (*DisconnectNodeOutputsAtFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); + typedef HAPI_Result (*ExtractImageToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); + typedef HAPI_Result (*ExtractImageToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); + typedef HAPI_GeoInfo (*GeoInfo_CreateFuncPtr)(); + typedef int (*GeoInfo_GetGroupCountByTypeFuncPtr)(HAPI_GeoInfo * in, HAPI_GroupType type); + typedef void (*GeoInfo_InitFuncPtr)(HAPI_GeoInfo * in); + typedef HAPI_Result (*GetActiveCacheCountFuncPtr)(const HAPI_Session * session, int * active_cache_count); + typedef HAPI_Result (*GetActiveCacheNamesFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); + typedef HAPI_Result (*GetAssetDefinitionParmCountsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); + typedef HAPI_Result (*GetAssetDefinitionParmInfosFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); + typedef HAPI_Result (*GetAssetDefinitionParmValuesFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); + typedef HAPI_Result (*GetAssetInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); + typedef HAPI_Result (*GetAttributeFloat64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeFloatArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*GetAttributeInt64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeIntArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); + typedef HAPI_Result (*GetAttributeStringArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); + typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); + typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); + typedef HAPI_Result (*GetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); + typedef HAPI_Result (*GetComposedChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); + typedef HAPI_Result (*GetComposedNodeCookResultFuncPtr)(const HAPI_Session * session, char * string_value, int length); + typedef HAPI_Result (*GetComposedObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); + typedef HAPI_Result (*GetComposedObjectTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + typedef HAPI_Result (*GetConnectionErrorFuncPtr)(char * string_value, int length, HAPI_Bool clear); + typedef HAPI_Result (*GetConnectionErrorLengthFuncPtr)(int * buffer_length); + typedef HAPI_Result (*GetCookingCurrentCountFuncPtr)(const HAPI_Session * session, int * count); + typedef HAPI_Result (*GetCookingTotalCountFuncPtr)(const HAPI_Session * session, int * count); + typedef HAPI_Result (*GetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); + typedef HAPI_Result (*GetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); + typedef HAPI_Result (*GetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); + typedef HAPI_Result (*GetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); + typedef HAPI_Result (*GetDisplayGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetEnvIntFuncPtr)(HAPI_EnvIntType int_type, int * value); + typedef HAPI_Result (*GetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); + typedef HAPI_Result (*GetFirstVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + typedef HAPI_Result (*GetGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetGeoSizeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); + typedef HAPI_Result (*GetGroupCountOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); + typedef HAPI_Result (*GetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + typedef HAPI_Result (*GetGroupMembershipOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + typedef HAPI_Result (*GetGroupNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + typedef HAPI_Result (*GetGroupNamesOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + typedef HAPI_Result (*GetHIPFileNodeCountFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, int * count); + typedef HAPI_Result (*GetHIPFileNodeIdsFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); + typedef HAPI_Result (*GetHandleBindingInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); + typedef HAPI_Result (*GetHandleInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); + typedef HAPI_Result (*GetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); + typedef HAPI_Result (*GetImageFilePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); + typedef HAPI_Result (*GetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); + typedef HAPI_Result (*GetImageMemoryBufferFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); + typedef HAPI_Result (*GetImagePlaneCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); + typedef HAPI_Result (*GetImagePlanesFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); + typedef HAPI_Result (*GetInstanceTransformsOnPartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + typedef HAPI_Result (*GetInstancedObjectIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); + typedef HAPI_Result (*GetInstancedPartIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); + typedef HAPI_Result (*GetInstancerPartTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + typedef HAPI_Result (*GetManagerNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); + typedef HAPI_Result (*GetMaterialInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); + typedef HAPI_Result (*GetMaterialNodeIdsOnFacesFuncPtr)(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); + typedef HAPI_Result (*GetNextVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + typedef HAPI_Result (*GetNodeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); + typedef HAPI_Result (*GetNodeInputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); + typedef HAPI_Result (*GetNodeOutputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); + typedef HAPI_Result (*GetNodePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); + typedef HAPI_Result (*GetNumWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * num); + typedef HAPI_Result (*GetObjectInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); + typedef HAPI_Result (*GetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + typedef HAPI_Result (*GetOutputNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); + typedef HAPI_Result (*GetPDGEventsFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); + typedef HAPI_Result (*GetPDGGraphContextIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); + typedef HAPI_Result (*GetPDGGraphContextsFuncPtr)(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); + typedef HAPI_Result (*GetPDGStateFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); + typedef HAPI_Result (*GetParametersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); + typedef HAPI_Result (*GetParmChoiceListsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); + typedef HAPI_Result (*GetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); + typedef HAPI_Result (*GetParmFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); + typedef HAPI_Result (*GetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); + typedef HAPI_Result (*GetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); + typedef HAPI_Result (*GetParmIdFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); + typedef HAPI_Result (*GetParmInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); + typedef HAPI_Result (*GetParmInfoFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); + typedef HAPI_Result (*GetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); + typedef HAPI_Result (*GetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); + typedef HAPI_Result (*GetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); + typedef HAPI_Result (*GetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); + typedef HAPI_Result (*GetParmStringValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); + typedef HAPI_Result (*GetParmTagNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); + typedef HAPI_Result (*GetParmTagValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); + typedef HAPI_Result (*GetParmWithTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); + typedef HAPI_Result (*GetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); + typedef HAPI_Result (*GetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); + typedef HAPI_Result (*GetPresetBufLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); + typedef HAPI_Result (*GetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int * value); + typedef HAPI_Result (*GetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); + typedef HAPI_Result (*GetServerEnvVarCountFuncPtr)(const HAPI_Session * session, int * env_count); + typedef HAPI_Result (*GetServerEnvVarListFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); + typedef HAPI_Result (*GetSessionEnvIntFuncPtr)(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); + typedef HAPI_Result (*GetSessionSyncInfoFuncPtr)(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); + typedef HAPI_Result (*GetSphereInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); + typedef HAPI_Result (*GetStatusFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, int * status); + typedef HAPI_Result (*GetStatusStringFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); + typedef HAPI_Result (*GetStatusStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); + typedef HAPI_Result (*GetStringFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); + typedef HAPI_Result (*GetStringBatchFuncPtr)(const HAPI_Session * session, char * char_buffer, int char_array_length); + typedef HAPI_Result (*GetStringBatchSizeFuncPtr)(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); + typedef HAPI_Result (*GetStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); + typedef HAPI_Result (*GetSupportedImageFileFormatCountFuncPtr)(const HAPI_Session * session, int * file_format_count); + typedef HAPI_Result (*GetSupportedImageFileFormatsFuncPtr)(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); + typedef HAPI_Result (*GetTimeFuncPtr)(const HAPI_Session * session, float * time); + typedef HAPI_Result (*GetTimelineOptionsFuncPtr)(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); + typedef HAPI_Result (*GetTotalCookCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + typedef HAPI_Result (*GetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool * enabled); + typedef HAPI_Result (*GetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); + typedef HAPI_Result (*GetViewportFuncPtr)(const HAPI_Session * session, HAPI_Viewport * viewport); + typedef HAPI_Result (*GetVolumeBoundsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); + typedef HAPI_Result (*GetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); + typedef HAPI_Result (*GetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); + typedef HAPI_Result (*GetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); + typedef HAPI_Result (*GetVolumeVisualInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); + typedef HAPI_Result (*GetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); + typedef HAPI_Result (*GetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); + typedef HAPI_Result (*GetWorkitemDataLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); + typedef HAPI_Result (*GetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); + typedef HAPI_Result (*GetWorkitemInfoFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); + typedef HAPI_Result (*GetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); + typedef HAPI_Result (*GetWorkitemResultInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); + typedef HAPI_Result (*GetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); + typedef HAPI_Result (*GetWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); + typedef HAPI_HandleBindingInfo (*HandleBindingInfo_CreateFuncPtr)(); + typedef void (*HandleBindingInfo_InitFuncPtr)(HAPI_HandleBindingInfo * in); + typedef HAPI_HandleInfo (*HandleInfo_CreateFuncPtr)(); + typedef void (*HandleInfo_InitFuncPtr)(HAPI_HandleInfo * in); + typedef HAPI_ImageFileFormat (*ImageFileFormat_CreateFuncPtr)(); + typedef void (*ImageFileFormat_InitFuncPtr)(HAPI_ImageFileFormat *in); + typedef HAPI_ImageInfo (*ImageInfo_CreateFuncPtr)(); + typedef void (*ImageInfo_InitFuncPtr)(HAPI_ImageInfo * in); + typedef HAPI_Result (*InitializeFuncPtr)(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); + typedef HAPI_Result (*InsertMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + typedef HAPI_Result (*InterruptFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*IsInitializedFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*IsNodeValidFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); + typedef HAPI_Result (*IsSessionValidFuncPtr)(const HAPI_Session * session); + typedef HAPI_Keyframe (*Keyframe_CreateFuncPtr)(); + typedef void (*Keyframe_InitFuncPtr)(HAPI_Keyframe * in); + typedef HAPI_Result (*LoadAssetLibraryFromFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + typedef HAPI_Result (*LoadAssetLibraryFromMemoryFuncPtr)(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + typedef HAPI_Result (*LoadGeoFromFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_Result (*LoadGeoFromMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); + typedef HAPI_Result (*LoadHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); + typedef HAPI_Result (*LoadNodeFromFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); + typedef HAPI_MaterialInfo (*MaterialInfo_CreateFuncPtr)(); + typedef void (*MaterialInfo_InitFuncPtr)(HAPI_MaterialInfo * in); + typedef HAPI_Result (*MergeHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); + typedef HAPI_NodeInfo (*NodeInfo_CreateFuncPtr)(); + typedef void (*NodeInfo_InitFuncPtr)(HAPI_NodeInfo * in); + typedef HAPI_ObjectInfo (*ObjectInfo_CreateFuncPtr)(); + typedef void (*ObjectInfo_InitFuncPtr)(HAPI_ObjectInfo * in); + typedef HAPI_ParmChoiceInfo (*ParmChoiceInfo_CreateFuncPtr)(); + typedef void (*ParmChoiceInfo_InitFuncPtr)(HAPI_ParmChoiceInfo * in); + typedef HAPI_Result (*ParmHasExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); + typedef HAPI_Result (*ParmHasTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); + typedef HAPI_ParmInfo (*ParmInfo_CreateFuncPtr)(); + typedef int (*ParmInfo_GetFloatValueCountFuncPtr)(const HAPI_ParmInfo * in); + typedef int (*ParmInfo_GetIntValueCountFuncPtr)(const HAPI_ParmInfo * in); + typedef int (*ParmInfo_GetStringValueCountFuncPtr)(const HAPI_ParmInfo* in); + typedef void (*ParmInfo_InitFuncPtr)(HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsFloatFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsIntFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsNodeFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsNonValueFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsPathFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsStringFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_PartInfo (*PartInfo_CreateFuncPtr)(); + typedef int (*PartInfo_GetAttributeCountByOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + typedef int (*PartInfo_GetElementCountByAttributeOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + typedef int (*PartInfo_GetElementCountByGroupTypeFuncPtr)(HAPI_PartInfo * in, HAPI_GroupType type); + typedef void (*PartInfo_InitFuncPtr)(HAPI_PartInfo * in); + typedef HAPI_Result (*PausePDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + typedef HAPI_Result (*PythonThreadInterpreterLockFuncPtr)(const HAPI_Session * session, HAPI_Bool locked); + typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); + typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); + typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); + typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const int string_handle); + typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); + typedef HAPI_Result (*RenderCOPToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId cop_node_id); + typedef HAPI_Result (*RenderTextureToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); + typedef HAPI_Result (*ResetSimulationFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*RevertGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*RevertParmToDefaultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); + typedef HAPI_Result (*RevertParmToDefaultsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); + typedef HAPI_Result (*SaveGeoToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_Result (*SaveGeoToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); + typedef HAPI_Result (*SaveHIPFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); + typedef HAPI_Result (*SaveNodeToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_SessionSyncInfo (*SessionSyncInfo_CreateFuncPtr)(); + typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); + typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); + typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); + typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); + typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, int * handle_value); + typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); + typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); + typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); + typedef HAPI_Result (*SetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); + typedef HAPI_Result (*SetNodeDisplayFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); + typedef HAPI_Result (*SetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); + typedef HAPI_Result (*SetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*SetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); + typedef HAPI_Result (*SetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); + typedef HAPI_Result (*SetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); + typedef HAPI_Result (*SetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); + typedef HAPI_Result (*SetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); + typedef HAPI_Result (*SetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*SetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); + typedef HAPI_Result (*SetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); + typedef HAPI_Result (*SetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int value); + typedef HAPI_Result (*SetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, const char * value); + typedef HAPI_Result (*SetSessionSyncFuncPtr)(const HAPI_Session * session, HAPI_Bool enable); + typedef HAPI_Result (*SetSessionSyncInfoFuncPtr)(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); + typedef HAPI_Result (*SetTimeFuncPtr)(const HAPI_Session * session, float time); + typedef HAPI_Result (*SetTimelineOptionsFuncPtr)(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); + typedef HAPI_Result (*SetTransformAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + typedef HAPI_Result (*SetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool enabled); + typedef HAPI_Result (*SetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); + typedef HAPI_Result (*SetViewportFuncPtr)(const HAPI_Session * session, const HAPI_Viewport * viewport); + typedef HAPI_Result (*SetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); + typedef HAPI_Result (*SetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); + typedef HAPI_Result (*SetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); + typedef HAPI_Result (*SetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); + typedef HAPI_Result (*SetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); + typedef HAPI_Result (*SetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); + typedef HAPI_Result (*SetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); + typedef HAPI_Result (*SetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); + typedef HAPI_Result (*StartThriftNamedPipeServerFuncPtr)(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); + typedef HAPI_Result (*StartThriftSocketServerFuncPtr)(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); + typedef HAPI_ThriftServerOptions (*ThriftServerOptions_CreateFuncPtr)(); + typedef void (*ThriftServerOptions_InitFuncPtr)(HAPI_ThriftServerOptions * in); + typedef HAPI_TimelineOptions (*TimelineOptions_CreateFuncPtr)(); + typedef void (*TimelineOptions_InitFuncPtr)(HAPI_TimelineOptions * in); + typedef HAPI_TransformEuler (*TransformEuler_CreateFuncPtr)(); + typedef void (*TransformEuler_InitFuncPtr)(HAPI_TransformEuler * in); + typedef HAPI_Transform (*Transform_CreateFuncPtr)(); + typedef void (*Transform_InitFuncPtr)(HAPI_Transform * in); + typedef HAPI_Viewport (*Viewport_CreateFuncPtr)(); + typedef HAPI_VolumeInfo (*VolumeInfo_CreateFuncPtr)(); + typedef void (*VolumeInfo_InitFuncPtr)(HAPI_VolumeInfo * in); + typedef HAPI_VolumeTileInfo (*VolumeTileInfo_CreateFuncPtr)(); + typedef void (*VolumeTileInfo_InitFuncPtr)(HAPI_VolumeTileInfo * in); + +public: + + static AddAttributeFuncPtr AddAttribute; + static AddGroupFuncPtr AddGroup; + static AssetInfo_CreateFuncPtr AssetInfo_Create; + static AssetInfo_InitFuncPtr AssetInfo_Init; + static AttributeInfo_CreateFuncPtr AttributeInfo_Create; + static AttributeInfo_InitFuncPtr AttributeInfo_Init; + static BindCustomImplementationFuncPtr BindCustomImplementation; + static CancelPDGCookFuncPtr CancelPDGCook; + static CheckForSpecificErrorsFuncPtr CheckForSpecificErrors; + static CleanupFuncPtr Cleanup; + static ClearConnectionErrorFuncPtr ClearConnectionError; + static CloseSessionFuncPtr CloseSession; + static CommitGeoFuncPtr CommitGeo; + static CommitWorkitemsFuncPtr CommitWorkitems; + static ComposeChildNodeListFuncPtr ComposeChildNodeList; + static ComposeNodeCookResultFuncPtr ComposeNodeCookResult; + static ComposeObjectListFuncPtr ComposeObjectList; + static ConnectNodeInputFuncPtr ConnectNodeInput; + static ConvertMatrixToEulerFuncPtr ConvertMatrixToEuler; + static ConvertMatrixToQuatFuncPtr ConvertMatrixToQuat; + static ConvertTransformFuncPtr ConvertTransform; + static ConvertTransformEulerToMatrixFuncPtr ConvertTransformEulerToMatrix; + static ConvertTransformQuatToMatrixFuncPtr ConvertTransformQuatToMatrix; + static CookNodeFuncPtr CookNode; + static CookOptions_AreEqualFuncPtr CookOptions_AreEqual; + static CookOptions_CreateFuncPtr CookOptions_Create; + static CookOptions_InitFuncPtr CookOptions_Init; + static CookPDGFuncPtr CookPDG; + static CreateCustomSessionFuncPtr CreateCustomSession; + static CreateHeightFieldInputFuncPtr CreateHeightFieldInput; + static CreateHeightfieldInputVolumeNodeFuncPtr CreateHeightfieldInputVolumeNode; + static CreateInProcessSessionFuncPtr CreateInProcessSession; + static CreateInputNodeFuncPtr CreateInputNode; + static CreateNodeFuncPtr CreateNode; + static CreateThriftNamedPipeSessionFuncPtr CreateThriftNamedPipeSession; + static CreateThriftSocketSessionFuncPtr CreateThriftSocketSession; + static CreateWorkitemFuncPtr CreateWorkitem; + static CurveInfo_CreateFuncPtr CurveInfo_Create; + static CurveInfo_InitFuncPtr CurveInfo_Init; + static DeleteAttributeFuncPtr DeleteAttribute; + static DeleteGroupFuncPtr DeleteGroup; + static DeleteNodeFuncPtr DeleteNode; + static DirtyPDGNodeFuncPtr DirtyPDGNode; + static DisconnectNodeInputFuncPtr DisconnectNodeInput; + static DisconnectNodeOutputsAtFuncPtr DisconnectNodeOutputsAt; + static ExtractImageToFileFuncPtr ExtractImageToFile; + static ExtractImageToMemoryFuncPtr ExtractImageToMemory; + static GeoInfo_CreateFuncPtr GeoInfo_Create; + static GeoInfo_GetGroupCountByTypeFuncPtr GeoInfo_GetGroupCountByType; + static GeoInfo_InitFuncPtr GeoInfo_Init; + static GetActiveCacheCountFuncPtr GetActiveCacheCount; + static GetActiveCacheNamesFuncPtr GetActiveCacheNames; + static GetAssetDefinitionParmCountsFuncPtr GetAssetDefinitionParmCounts; + static GetAssetDefinitionParmInfosFuncPtr GetAssetDefinitionParmInfos; + static GetAssetDefinitionParmValuesFuncPtr GetAssetDefinitionParmValues; + static GetAssetInfoFuncPtr GetAssetInfo; + static GetAttributeFloat64ArrayDataFuncPtr GetAttributeFloat64ArrayData; + static GetAttributeFloat64DataFuncPtr GetAttributeFloat64Data; + static GetAttributeFloatArrayDataFuncPtr GetAttributeFloatArrayData; + static GetAttributeFloatDataFuncPtr GetAttributeFloatData; + static GetAttributeInfoFuncPtr GetAttributeInfo; + static GetAttributeInt64ArrayDataFuncPtr GetAttributeInt64ArrayData; + static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; + static GetAttributeIntArrayDataFuncPtr GetAttributeIntArrayData; + static GetAttributeIntDataFuncPtr GetAttributeIntData; + static GetAttributeNamesFuncPtr GetAttributeNames; + static GetAttributeStringArrayDataFuncPtr GetAttributeStringArrayData; + static GetAttributeStringDataFuncPtr GetAttributeStringData; + static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; + static GetAvailableAssetsFuncPtr GetAvailableAssets; + static GetBoxInfoFuncPtr GetBoxInfo; + static GetCachePropertyFuncPtr GetCacheProperty; + static GetComposedChildNodeListFuncPtr GetComposedChildNodeList; + static GetComposedNodeCookResultFuncPtr GetComposedNodeCookResult; + static GetComposedObjectListFuncPtr GetComposedObjectList; + static GetComposedObjectTransformsFuncPtr GetComposedObjectTransforms; + static GetConnectionErrorFuncPtr GetConnectionError; + static GetConnectionErrorLengthFuncPtr GetConnectionErrorLength; + static GetCookingCurrentCountFuncPtr GetCookingCurrentCount; + static GetCookingTotalCountFuncPtr GetCookingTotalCount; + static GetCurveCountsFuncPtr GetCurveCounts; + static GetCurveInfoFuncPtr GetCurveInfo; + static GetCurveKnotsFuncPtr GetCurveKnots; + static GetCurveOrdersFuncPtr GetCurveOrders; + static GetDisplayGeoInfoFuncPtr GetDisplayGeoInfo; + static GetEnvIntFuncPtr GetEnvInt; + static GetFaceCountsFuncPtr GetFaceCounts; + static GetFirstVolumeTileFuncPtr GetFirstVolumeTile; + static GetGeoInfoFuncPtr GetGeoInfo; + static GetGeoSizeFuncPtr GetGeoSize; + static GetGroupCountOnPackedInstancePartFuncPtr GetGroupCountOnPackedInstancePart; + static GetGroupMembershipFuncPtr GetGroupMembership; + static GetGroupMembershipOnPackedInstancePartFuncPtr GetGroupMembershipOnPackedInstancePart; + static GetGroupNamesFuncPtr GetGroupNames; + static GetGroupNamesOnPackedInstancePartFuncPtr GetGroupNamesOnPackedInstancePart; + static GetHIPFileNodeCountFuncPtr GetHIPFileNodeCount; + static GetHIPFileNodeIdsFuncPtr GetHIPFileNodeIds; + static GetHandleBindingInfoFuncPtr GetHandleBindingInfo; + static GetHandleInfoFuncPtr GetHandleInfo; + static GetHeightFieldDataFuncPtr GetHeightFieldData; + static GetImageFilePathFuncPtr GetImageFilePath; + static GetImageInfoFuncPtr GetImageInfo; + static GetImageMemoryBufferFuncPtr GetImageMemoryBuffer; + static GetImagePlaneCountFuncPtr GetImagePlaneCount; + static GetImagePlanesFuncPtr GetImagePlanes; + static GetInstanceTransformsOnPartFuncPtr GetInstanceTransformsOnPart; + static GetInstancedObjectIdsFuncPtr GetInstancedObjectIds; + static GetInstancedPartIdsFuncPtr GetInstancedPartIds; + static GetInstancerPartTransformsFuncPtr GetInstancerPartTransforms; + static GetManagerNodeIdFuncPtr GetManagerNodeId; + static GetMaterialInfoFuncPtr GetMaterialInfo; + static GetMaterialNodeIdsOnFacesFuncPtr GetMaterialNodeIdsOnFaces; + static GetNextVolumeTileFuncPtr GetNextVolumeTile; + static GetNodeInfoFuncPtr GetNodeInfo; + static GetNodeInputNameFuncPtr GetNodeInputName; + static GetNodeOutputNameFuncPtr GetNodeOutputName; + static GetNodePathFuncPtr GetNodePath; + static GetNumWorkitemsFuncPtr GetNumWorkitems; + static GetObjectInfoFuncPtr GetObjectInfo; + static GetObjectTransformFuncPtr GetObjectTransform; + static GetOutputNodeIdFuncPtr GetOutputNodeId; + static GetPDGEventsFuncPtr GetPDGEvents; + static GetPDGGraphContextIdFuncPtr GetPDGGraphContextId; + static GetPDGGraphContextsFuncPtr GetPDGGraphContexts; + static GetPDGStateFuncPtr GetPDGState; + static GetParametersFuncPtr GetParameters; + static GetParmChoiceListsFuncPtr GetParmChoiceLists; + static GetParmExpressionFuncPtr GetParmExpression; + static GetParmFileFuncPtr GetParmFile; + static GetParmFloatValueFuncPtr GetParmFloatValue; + static GetParmFloatValuesFuncPtr GetParmFloatValues; + static GetParmIdFromNameFuncPtr GetParmIdFromName; + static GetParmInfoFuncPtr GetParmInfo; + static GetParmInfoFromNameFuncPtr GetParmInfoFromName; + static GetParmIntValueFuncPtr GetParmIntValue; + static GetParmIntValuesFuncPtr GetParmIntValues; + static GetParmNodeValueFuncPtr GetParmNodeValue; + static GetParmStringValueFuncPtr GetParmStringValue; + static GetParmStringValuesFuncPtr GetParmStringValues; + static GetParmTagNameFuncPtr GetParmTagName; + static GetParmTagValueFuncPtr GetParmTagValue; + static GetParmWithTagFuncPtr GetParmWithTag; + static GetPartInfoFuncPtr GetPartInfo; + static GetPresetFuncPtr GetPreset; + static GetPresetBufLengthFuncPtr GetPresetBufLength; + static GetServerEnvIntFuncPtr GetServerEnvInt; + static GetServerEnvStringFuncPtr GetServerEnvString; + static GetServerEnvVarCountFuncPtr GetServerEnvVarCount; + static GetServerEnvVarListFuncPtr GetServerEnvVarList; + static GetSessionEnvIntFuncPtr GetSessionEnvInt; + static GetSessionSyncInfoFuncPtr GetSessionSyncInfo; + static GetSphereInfoFuncPtr GetSphereInfo; + static GetStatusFuncPtr GetStatus; + static GetStatusStringFuncPtr GetStatusString; + static GetStatusStringBufLengthFuncPtr GetStatusStringBufLength; + static GetStringFuncPtr GetString; + static GetStringBatchFuncPtr GetStringBatch; + static GetStringBatchSizeFuncPtr GetStringBatchSize; + static GetStringBufLengthFuncPtr GetStringBufLength; + static GetSupportedImageFileFormatCountFuncPtr GetSupportedImageFileFormatCount; + static GetSupportedImageFileFormatsFuncPtr GetSupportedImageFileFormats; + static GetTimeFuncPtr GetTime; + static GetTimelineOptionsFuncPtr GetTimelineOptions; + static GetTotalCookCountFuncPtr GetTotalCookCount; + static GetUseHoudiniTimeFuncPtr GetUseHoudiniTime; + static GetVertexListFuncPtr GetVertexList; + static GetViewportFuncPtr GetViewport; + static GetVolumeBoundsFuncPtr GetVolumeBounds; + static GetVolumeInfoFuncPtr GetVolumeInfo; + static GetVolumeTileFloatDataFuncPtr GetVolumeTileFloatData; + static GetVolumeTileIntDataFuncPtr GetVolumeTileIntData; + static GetVolumeVisualInfoFuncPtr GetVolumeVisualInfo; + static GetVolumeVoxelFloatDataFuncPtr GetVolumeVoxelFloatData; + static GetVolumeVoxelIntDataFuncPtr GetVolumeVoxelIntData; + static GetWorkitemDataLengthFuncPtr GetWorkitemDataLength; + static GetWorkitemFloatDataFuncPtr GetWorkitemFloatData; + static GetWorkitemInfoFuncPtr GetWorkitemInfo; + static GetWorkitemIntDataFuncPtr GetWorkitemIntData; + static GetWorkitemResultInfoFuncPtr GetWorkitemResultInfo; + static GetWorkitemStringDataFuncPtr GetWorkitemStringData; + static GetWorkitemsFuncPtr GetWorkitems; + static HandleBindingInfo_CreateFuncPtr HandleBindingInfo_Create; + static HandleBindingInfo_InitFuncPtr HandleBindingInfo_Init; + static HandleInfo_CreateFuncPtr HandleInfo_Create; + static HandleInfo_InitFuncPtr HandleInfo_Init; + static ImageFileFormat_CreateFuncPtr ImageFileFormat_Create; + static ImageFileFormat_InitFuncPtr ImageFileFormat_Init; + static ImageInfo_CreateFuncPtr ImageInfo_Create; + static ImageInfo_InitFuncPtr ImageInfo_Init; + static InitializeFuncPtr Initialize; + static InsertMultiparmInstanceFuncPtr InsertMultiparmInstance; + static InterruptFuncPtr Interrupt; + static IsInitializedFuncPtr IsInitialized; + static IsNodeValidFuncPtr IsNodeValid; + static IsSessionValidFuncPtr IsSessionValid; + static Keyframe_CreateFuncPtr Keyframe_Create; + static Keyframe_InitFuncPtr Keyframe_Init; + static LoadAssetLibraryFromFileFuncPtr LoadAssetLibraryFromFile; + static LoadAssetLibraryFromMemoryFuncPtr LoadAssetLibraryFromMemory; + static LoadGeoFromFileFuncPtr LoadGeoFromFile; + static LoadGeoFromMemoryFuncPtr LoadGeoFromMemory; + static LoadHIPFileFuncPtr LoadHIPFile; + static LoadNodeFromFileFuncPtr LoadNodeFromFile; + static MaterialInfo_CreateFuncPtr MaterialInfo_Create; + static MaterialInfo_InitFuncPtr MaterialInfo_Init; + static MergeHIPFileFuncPtr MergeHIPFile; + static NodeInfo_CreateFuncPtr NodeInfo_Create; + static NodeInfo_InitFuncPtr NodeInfo_Init; + static ObjectInfo_CreateFuncPtr ObjectInfo_Create; + static ObjectInfo_InitFuncPtr ObjectInfo_Init; + static ParmChoiceInfo_CreateFuncPtr ParmChoiceInfo_Create; + static ParmChoiceInfo_InitFuncPtr ParmChoiceInfo_Init; + static ParmHasExpressionFuncPtr ParmHasExpression; + static ParmHasTagFuncPtr ParmHasTag; + static ParmInfo_CreateFuncPtr ParmInfo_Create; + static ParmInfo_GetFloatValueCountFuncPtr ParmInfo_GetFloatValueCount; + static ParmInfo_GetIntValueCountFuncPtr ParmInfo_GetIntValueCount; + static ParmInfo_GetStringValueCountFuncPtr ParmInfo_GetStringValueCount; + static ParmInfo_InitFuncPtr ParmInfo_Init; + static ParmInfo_IsFloatFuncPtr ParmInfo_IsFloat; + static ParmInfo_IsIntFuncPtr ParmInfo_IsInt; + static ParmInfo_IsNodeFuncPtr ParmInfo_IsNode; + static ParmInfo_IsNonValueFuncPtr ParmInfo_IsNonValue; + static ParmInfo_IsPathFuncPtr ParmInfo_IsPath; + static ParmInfo_IsStringFuncPtr ParmInfo_IsString; + static PartInfo_CreateFuncPtr PartInfo_Create; + static PartInfo_GetAttributeCountByOwnerFuncPtr PartInfo_GetAttributeCountByOwner; + static PartInfo_GetElementCountByAttributeOwnerFuncPtr PartInfo_GetElementCountByAttributeOwner; + static PartInfo_GetElementCountByGroupTypeFuncPtr PartInfo_GetElementCountByGroupType; + static PartInfo_InitFuncPtr PartInfo_Init; + static PausePDGCookFuncPtr PausePDGCook; + static PythonThreadInterpreterLockFuncPtr PythonThreadInterpreterLock; + static QueryNodeInputFuncPtr QueryNodeInput; + static QueryNodeOutputConnectedCountFuncPtr QueryNodeOutputConnectedCount; + static QueryNodeOutputConnectedNodesFuncPtr QueryNodeOutputConnectedNodes; + static RemoveCustomStringFuncPtr RemoveCustomString; + static RemoveMultiparmInstanceFuncPtr RemoveMultiparmInstance; + static RemoveParmExpressionFuncPtr RemoveParmExpression; + static RenameNodeFuncPtr RenameNode; + static RenderCOPToImageFuncPtr RenderCOPToImage; + static RenderTextureToImageFuncPtr RenderTextureToImage; + static ResetSimulationFuncPtr ResetSimulation; + static RevertGeoFuncPtr RevertGeo; + static RevertParmToDefaultFuncPtr RevertParmToDefault; + static RevertParmToDefaultsFuncPtr RevertParmToDefaults; + static SaveGeoToFileFuncPtr SaveGeoToFile; + static SaveGeoToMemoryFuncPtr SaveGeoToMemory; + static SaveHIPFileFuncPtr SaveHIPFile; + static SaveNodeToFileFuncPtr SaveNodeToFile; + static SessionSyncInfo_CreateFuncPtr SessionSyncInfo_Create; + static SetAnimCurveFuncPtr SetAnimCurve; + static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; + static SetAttributeFloatDataFuncPtr SetAttributeFloatData; + static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; + static SetAttributeIntDataFuncPtr SetAttributeIntData; + static SetAttributeStringDataFuncPtr SetAttributeStringData; + static SetCachePropertyFuncPtr SetCacheProperty; + static SetCurveCountsFuncPtr SetCurveCounts; + static SetCurveInfoFuncPtr SetCurveInfo; + static SetCurveKnotsFuncPtr SetCurveKnots; + static SetCurveOrdersFuncPtr SetCurveOrders; + static SetCustomStringFuncPtr SetCustomString; + static SetFaceCountsFuncPtr SetFaceCounts; + static SetGroupMembershipFuncPtr SetGroupMembership; + static SetHeightFieldDataFuncPtr SetHeightFieldData; + static SetImageInfoFuncPtr SetImageInfo; + static SetNodeDisplayFuncPtr SetNodeDisplay; + static SetObjectTransformFuncPtr SetObjectTransform; + static SetParmExpressionFuncPtr SetParmExpression; + static SetParmFloatValueFuncPtr SetParmFloatValue; + static SetParmFloatValuesFuncPtr SetParmFloatValues; + static SetParmIntValueFuncPtr SetParmIntValue; + static SetParmIntValuesFuncPtr SetParmIntValues; + static SetParmNodeValueFuncPtr SetParmNodeValue; + static SetParmStringValueFuncPtr SetParmStringValue; + static SetPartInfoFuncPtr SetPartInfo; + static SetPresetFuncPtr SetPreset; + static SetServerEnvIntFuncPtr SetServerEnvInt; + static SetServerEnvStringFuncPtr SetServerEnvString; + static SetSessionSyncFuncPtr SetSessionSync; + static SetSessionSyncInfoFuncPtr SetSessionSyncInfo; + static SetTimeFuncPtr SetTime; + static SetTimelineOptionsFuncPtr SetTimelineOptions; + static SetTransformAnimCurveFuncPtr SetTransformAnimCurve; + static SetUseHoudiniTimeFuncPtr SetUseHoudiniTime; + static SetVertexListFuncPtr SetVertexList; + static SetViewportFuncPtr SetViewport; + static SetVolumeInfoFuncPtr SetVolumeInfo; + static SetVolumeTileFloatDataFuncPtr SetVolumeTileFloatData; + static SetVolumeTileIntDataFuncPtr SetVolumeTileIntData; + static SetVolumeVoxelFloatDataFuncPtr SetVolumeVoxelFloatData; + static SetVolumeVoxelIntDataFuncPtr SetVolumeVoxelIntData; + static SetWorkitemFloatDataFuncPtr SetWorkitemFloatData; + static SetWorkitemIntDataFuncPtr SetWorkitemIntData; + static SetWorkitemStringDataFuncPtr SetWorkitemStringData; + static StartThriftNamedPipeServerFuncPtr StartThriftNamedPipeServer; + static StartThriftSocketServerFuncPtr StartThriftSocketServer; + static ThriftServerOptions_CreateFuncPtr ThriftServerOptions_Create; + static ThriftServerOptions_InitFuncPtr ThriftServerOptions_Init; + static TimelineOptions_CreateFuncPtr TimelineOptions_Create; + static TimelineOptions_InitFuncPtr TimelineOptions_Init; + static TransformEuler_CreateFuncPtr TransformEuler_Create; + static TransformEuler_InitFuncPtr TransformEuler_Init; + static Transform_CreateFuncPtr Transform_Create; + static Transform_InitFuncPtr Transform_Init; + static Viewport_CreateFuncPtr Viewport_Create; + static VolumeInfo_CreateFuncPtr VolumeInfo_Create; + static VolumeInfo_InitFuncPtr VolumeInfo_Init; + static VolumeTileInfo_CreateFuncPtr VolumeTileInfo_Create; + static VolumeTileInfo_InitFuncPtr VolumeTileInfo_Init; + +public: + + static HAPI_Result AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + static HAPI_Result AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + static HAPI_AssetInfo AssetInfo_CreateEmptyStub(); + static void AssetInfo_InitEmptyStub(HAPI_AssetInfo * in); + static HAPI_AttributeInfo AttributeInfo_CreateEmptyStub(); + static void AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in); + static HAPI_Result BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path); + static HAPI_Result CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + static HAPI_Result CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); + static HAPI_Result CleanupEmptyStub(const HAPI_Session * session); + static HAPI_Result ClearConnectionErrorEmptyStub(); + static HAPI_Result CloseSessionEmptyStub(const HAPI_Session * session); + static HAPI_Result CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + static HAPI_Result ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); + static HAPI_Result ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + static HAPI_Result ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); + static HAPI_Result ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + static HAPI_Result ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); + static HAPI_Result ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + static HAPI_Result ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); + static HAPI_Result ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); + static HAPI_Result CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); + static HAPI_Bool CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right); + static HAPI_CookOptions CookOptions_CreateEmptyStub(); + static void CookOptions_InitEmptyStub(HAPI_CookOptions * in); + static HAPI_Result CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); + static HAPI_Result CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); + static HAPI_Result CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); + static HAPI_Result CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); + static HAPI_Result CreateInProcessSessionEmptyStub(HAPI_Session * session); + static HAPI_Result CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); + static HAPI_Result CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); + static HAPI_Result CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name); + static HAPI_Result CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port); + static HAPI_Result CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); + static HAPI_CurveInfo CurveInfo_CreateEmptyStub(); + static void CurveInfo_InitEmptyStub(HAPI_CurveInfo * in); + static HAPI_Result DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + static HAPI_Result DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + static HAPI_Result DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); + static HAPI_Result DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); + static HAPI_Result DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); + static HAPI_Result ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); + static HAPI_Result ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); + static HAPI_GeoInfo GeoInfo_CreateEmptyStub(); + static int GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type); + static void GeoInfo_InitEmptyStub(HAPI_GeoInfo * in); + static HAPI_Result GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count); + static HAPI_Result GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); + static HAPI_Result GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); + static HAPI_Result GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); + static HAPI_Result GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); + static HAPI_Result GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); + static HAPI_Result GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); + static HAPI_Result GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); + static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + static HAPI_Result GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + static HAPI_Result GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); + static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); + static HAPI_Result GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); + static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); + static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); + static HAPI_Result GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); + static HAPI_Result GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); + static HAPI_Result GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length); + static HAPI_Result GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); + static HAPI_Result GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + static HAPI_Result GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear); + static HAPI_Result GetConnectionErrorLengthEmptyStub(int * buffer_length); + static HAPI_Result GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count); + static HAPI_Result GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count); + static HAPI_Result GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); + static HAPI_Result GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); + static HAPI_Result GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); + static HAPI_Result GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); + static HAPI_Result GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value); + static HAPI_Result GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); + static HAPI_Result GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + static HAPI_Result GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); + static HAPI_Result GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); + static HAPI_Result GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + static HAPI_Result GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + static HAPI_Result GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + static HAPI_Result GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + static HAPI_Result GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count); + static HAPI_Result GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); + static HAPI_Result GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); + static HAPI_Result GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); + static HAPI_Result GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); + static HAPI_Result GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); + static HAPI_Result GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); + static HAPI_Result GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); + static HAPI_Result GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); + static HAPI_Result GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); + static HAPI_Result GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + static HAPI_Result GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); + static HAPI_Result GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); + static HAPI_Result GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + static HAPI_Result GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); + static HAPI_Result GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); + static HAPI_Result GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); + static HAPI_Result GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + static HAPI_Result GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); + static HAPI_Result GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); + static HAPI_Result GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); + static HAPI_Result GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); + static HAPI_Result GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num); + static HAPI_Result GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); + static HAPI_Result GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + static HAPI_Result GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); + static HAPI_Result GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); + static HAPI_Result GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); + static HAPI_Result GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); + static HAPI_Result GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); + static HAPI_Result GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); + static HAPI_Result GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); + static HAPI_Result GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); + static HAPI_Result GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); + static HAPI_Result GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); + static HAPI_Result GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); + static HAPI_Result GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); + static HAPI_Result GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); + static HAPI_Result GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); + static HAPI_Result GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); + static HAPI_Result GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); + static HAPI_Result GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); + static HAPI_Result GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); + static HAPI_Result GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); + static HAPI_Result GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); + static HAPI_Result GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); + static HAPI_Result GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); + static HAPI_Result GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); + static HAPI_Result GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); + static HAPI_Result GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); + static HAPI_Result GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value); + static HAPI_Result GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); + static HAPI_Result GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count); + static HAPI_Result GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); + static HAPI_Result GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); + static HAPI_Result GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); + static HAPI_Result GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); + static HAPI_Result GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status); + static HAPI_Result GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); + static HAPI_Result GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); + static HAPI_Result GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); + static HAPI_Result GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length); + static HAPI_Result GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); + static HAPI_Result GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); + static HAPI_Result GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count); + static HAPI_Result GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); + static HAPI_Result GetTimeEmptyStub(const HAPI_Session * session, float * time); + static HAPI_Result GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); + static HAPI_Result GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + static HAPI_Result GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled); + static HAPI_Result GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); + static HAPI_Result GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport); + static HAPI_Result GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); + static HAPI_Result GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); + static HAPI_Result GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); + static HAPI_Result GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); + static HAPI_Result GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); + static HAPI_Result GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); + static HAPI_Result GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); + static HAPI_Result GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); + static HAPI_Result GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); + static HAPI_Result GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); + static HAPI_Result GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); + static HAPI_Result GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); + static HAPI_Result GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); + static HAPI_Result GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); + static HAPI_HandleBindingInfo HandleBindingInfo_CreateEmptyStub(); + static void HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in); + static HAPI_HandleInfo HandleInfo_CreateEmptyStub(); + static void HandleInfo_InitEmptyStub(HAPI_HandleInfo * in); + static HAPI_ImageFileFormat ImageFileFormat_CreateEmptyStub(); + static void ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in); + static HAPI_ImageInfo ImageInfo_CreateEmptyStub(); + static void ImageInfo_InitEmptyStub(HAPI_ImageInfo * in); + static HAPI_Result InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); + static HAPI_Result InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + static HAPI_Result InterruptEmptyStub(const HAPI_Session * session); + static HAPI_Result IsInitializedEmptyStub(const HAPI_Session * session); + static HAPI_Result IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); + static HAPI_Result IsSessionValidEmptyStub(const HAPI_Session * session); + static HAPI_Keyframe Keyframe_CreateEmptyStub(); + static void Keyframe_InitEmptyStub(HAPI_Keyframe * in); + static HAPI_Result LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + static HAPI_Result LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + static HAPI_Result LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_Result LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); + static HAPI_Result LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); + static HAPI_Result LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); + static HAPI_MaterialInfo MaterialInfo_CreateEmptyStub(); + static void MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in); + static HAPI_Result MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); + static HAPI_NodeInfo NodeInfo_CreateEmptyStub(); + static void NodeInfo_InitEmptyStub(HAPI_NodeInfo * in); + static HAPI_ObjectInfo ObjectInfo_CreateEmptyStub(); + static void ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in); + static HAPI_ParmChoiceInfo ParmChoiceInfo_CreateEmptyStub(); + static void ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in); + static HAPI_Result ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); + static HAPI_Result ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); + static HAPI_ParmInfo ParmInfo_CreateEmptyStub(); + static int ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in); + static int ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in); + static int ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in); + static void ParmInfo_InitEmptyStub(HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in); + static HAPI_PartInfo PartInfo_CreateEmptyStub(); + static int PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + static int PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + static int PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type); + static void PartInfo_InitEmptyStub(HAPI_PartInfo * in); + static HAPI_Result PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + static HAPI_Result PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked); + static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); + static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); + static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); + static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle); + static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); + static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); + static HAPI_Result RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id); + static HAPI_Result RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); + static HAPI_Result ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); + static HAPI_Result RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); + static HAPI_Result SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_Result SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); + static HAPI_Result SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); + static HAPI_Result SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_SessionSyncInfo SessionSyncInfo_CreateEmptyStub(); + static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); + static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); + static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); + static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); + static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); + static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); + static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value); + static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); + static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); + static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); + static HAPI_Result SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); + static HAPI_Result SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); + static HAPI_Result SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); + static HAPI_Result SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + static HAPI_Result SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); + static HAPI_Result SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); + static HAPI_Result SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); + static HAPI_Result SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); + static HAPI_Result SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); + static HAPI_Result SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + static HAPI_Result SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); + static HAPI_Result SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); + static HAPI_Result SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value); + static HAPI_Result SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value); + static HAPI_Result SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable); + static HAPI_Result SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); + static HAPI_Result SetTimeEmptyStub(const HAPI_Session * session, float time); + static HAPI_Result SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); + static HAPI_Result SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + static HAPI_Result SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled); + static HAPI_Result SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); + static HAPI_Result SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport); + static HAPI_Result SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); + static HAPI_Result SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); + static HAPI_Result SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); + static HAPI_Result SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); + static HAPI_Result SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); + static HAPI_Result SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); + static HAPI_Result SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); + static HAPI_Result SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); + static HAPI_Result StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); + static HAPI_Result StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); + static HAPI_ThriftServerOptions ThriftServerOptions_CreateEmptyStub(); + static void ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in); + static HAPI_TimelineOptions TimelineOptions_CreateEmptyStub(); + static void TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in); + static HAPI_TransformEuler TransformEuler_CreateEmptyStub(); + static void TransformEuler_InitEmptyStub(HAPI_TransformEuler * in); + static HAPI_Transform Transform_CreateEmptyStub(); + static void Transform_InitEmptyStub(HAPI_Transform * in); + static HAPI_Viewport Viewport_CreateEmptyStub(); + static HAPI_VolumeInfo VolumeInfo_CreateEmptyStub(); + static void VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in); + static HAPI_VolumeTileInfo VolumeTileInfo_CreateEmptyStub(); + static void VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in); +}; diff --git a/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs b/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs index d51f6ac40..91c671201 100644 --- a/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs +++ b/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs @@ -1,123 +1,119 @@ -/* - * Copyright (c) <2020> 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. - * - * Produced by: - * Side Effects Software Inc - * 123 Front Street West, Suite 1401 - * Toronto, Ontario - * Canada M5J 2M2 - * 416-504-9876 - * - */ - -using UnrealBuildTool; -using System; -using System.IO; - -public class HoudiniEngineEditor : ModuleRules -{ - public HoudiniEngineEditor( ReadOnlyTargetRules Target ) : base( Target ) - { - bPrecompile = true; - PCHUsage = PCHUsageMode.NoSharedPCHs; - PrivatePCHHeaderFile = "Private/HoudiniEngineEditorPrivatePCH.h"; - - // Check if we are compiling on unsupported platforms. - if ( Target.Platform != UnrealTargetPlatform.Win64 && - Target.Platform != UnrealTargetPlatform.Mac && - Target.Platform != UnrealTargetPlatform.Linux ) - { - string Err = string.Format( "Houdini Engine Editor: Compiling for unsupported platform." ); - System.Console.WriteLine( Err ); - throw new BuildException( Err ); - } - - PublicIncludePaths.AddRange( - new string[] { - Path.Combine(ModuleDirectory, "Public") - } - ); - - PrivateIncludePaths.AddRange( - new string[] { - "HoudiniEngine/Private", - "HoudiniEngineRuntime/Private" - } - ); - - PrivateIncludePathModuleNames.AddRange( - new string[] { - "PlacementMode" - } - ); - - // Add common dependencies. - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "HoudiniEngine", - "HoudiniEngineRuntime", - "Slate", - "SlateCore", - "Landscape", - "Foliage" - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "AppFramework", - "AssetTools", - "ContentBrowser", - "DesktopWidgets", - "EditorStyle", - "EditorWidgets", - "Engine", - "InputCore", - "LevelEditor", - "MainFrame", - "Projects", - "PropertyEditor", - "RHI", - "RawMesh", - "RenderCore", - "TargetPlatform", - "UnrealEd", - "ApplicationCore", - "CurveEditor", - "Json", - "SceneOutliner", - "PropertyPath", - "MaterialEditor" - } - ); - - DynamicallyLoadedModuleNames.AddRange( - new string[] - { - "PlacementMode", - } - ); - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using UnrealBuildTool; +using System; +using System.IO; + +public class HoudiniEngineEditor : ModuleRules +{ + public HoudiniEngineEditor( ReadOnlyTargetRules Target ) : base( Target ) + { + bPrecompile = true; + PCHUsage = PCHUsageMode.NoSharedPCHs; + PrivatePCHHeaderFile = "Private/HoudiniEngineEditorPrivatePCH.h"; + + // Check if we are compiling on unsupported platforms. + if ( Target.Platform != UnrealTargetPlatform.Win64 && + Target.Platform != UnrealTargetPlatform.Mac && + Target.Platform != UnrealTargetPlatform.Linux ) + { + string Err = string.Format( "Houdini Engine Editor: Compiling for unsupported platform." ); + System.Console.WriteLine( Err ); + throw new BuildException( Err ); + } + + PublicIncludePaths.AddRange( + new string[] { + Path.Combine(ModuleDirectory, "Public") + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "HoudiniEngine/Private", + "HoudiniEngineRuntime/Private" + } + ); + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "PlacementMode" + } + ); + + // Add common dependencies. + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "HoudiniEngine", + "HoudiniEngineRuntime", + "Slate", + "SlateCore", + "Landscape", + "Foliage" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "AppFramework", + "AssetTools", + "ContentBrowser", + "DesktopWidgets", + "EditorStyle", + "EditorWidgets", + "Engine", + "InputCore", + "LevelEditor", + "MainFrame", + "Projects", + "PropertyEditor", + "RHI", + "RawMesh", + "RenderCore", + "TargetPlatform", + "UnrealEd", + "ApplicationCore", + "CurveEditor", + "Json", + "SceneOutliner", + "PropertyPath", + "MaterialEditor" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + "PlacementMode", + } + ); + } +} diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp index 5ac0ad6f4..36dd0f577 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp @@ -1,459 +1,459 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "AssetTypeActions_HoudiniAsset.h" -#include "HoudiniAsset.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniTool.h" -#include "HoudiniEngineEditorUtils.h" - -#include "EditorReimportHandler.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "HAL/FileManager.h" -#include "EditorFramework/AssetImportData.h" -#include "LevelEditor.h" -#include "Modules/ModuleManager.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FText -FAssetTypeActions_HoudiniAsset::GetName() const -{ - return LOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset"); -} - -FColor -FAssetTypeActions_HoudiniAsset::GetTypeColor() const -{ - return FColor(255, 165, 0); -} - -UClass * -FAssetTypeActions_HoudiniAsset::GetSupportedClass() const -{ - return UHoudiniAsset::StaticClass(); -} - -uint32 -FAssetTypeActions_HoudiniAsset::GetCategories() -{ - return EAssetTypeCategories::Misc; -} - -/* -UThumbnailInfo * -FAssetTypeActions_HoudiniAsset::GetThumbnailInfo(UObject * Asset) const -{ - if (!Asset || Asset->IsPendingKill()) - return nullptr; - - UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >(Asset); - UThumbnailInfo * ThumbnailInfo = HoudiniAsset->ThumbnailInfo; - if (!ThumbnailInfo) - { - // If we have no thumbnail information, construct it. - ThumbnailInfo = NewObject< USceneThumbnailInfo >(HoudiniAsset, USceneThumbnailInfo::StaticClass()); - HoudiniAsset->ThumbnailInfo = ThumbnailInfo; - } - - return ThumbnailInfo; -} -*/ - -bool -FAssetTypeActions_HoudiniAsset::HasActions(const TArray< UObject * > & InObjects) const -{ - return true; -} - -void -FAssetTypeActions_HoudiniAsset::GetActions(const TArray & InObjects, class FMenuBuilder & MenuBuilder) -{ - bool ValidObjects = false; - TArray> HoudiniAssets; - if (InObjects.Num() > 0) - { - HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); - ValidObjects = true; - } - - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Reimport", "Reimport"), - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ReimportTooltip", "Reimport selected Houdini Assets."), - FSlateIcon(StyleSetName, "HoudiniEngine._Reset"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteReimport, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAll", "Rebuild All Instances"), - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAllTooltip", "Reimports and rebuild all instances of the selected Houdini Assets."), - FSlateIcon(StyleSetName, "HoudiniEngine._RebuildAll"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorer", "Find Source"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorerTooltip", - "Opens explorer at the location of this asset."), - FSlateIcon(StyleSetName, "HoudiniEngine.Hou_OpenInHoudinidiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudini", "Open in Houdini"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudiniTooltip", - "Opens the selected asset in Houdini."), - FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuSeparator(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Instantiate", "Instantiate"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateTooltip", - "Instantiate the selected asset in the current world."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiate, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOrigin", "Instantiate at the origin"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOriginTooltip", - "Instantiate the selected asset in the current world. The Houdini Asset Actor will be created at the origin of the level (0,0,0)."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuSeparator(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingle", "Apply to the current selection (single input)"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingleTooltip", - "Applies the selected asset to the current world selection. All the selected object will be assigned to the first input."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMulti", "Apply to the current selection (multiple inputs )"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMultiTooltip", - "Applies the selected asset to the current world selection. Each selected object will be assigned to its own input (one object per input)."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatch", "Batch Apply to the current selection"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatchTooltip", - "Batch apply the selected asset to the current world selection. An instance of the selected Houdini asset will be created for each selected object."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); -} - - -bool -FAssetTypeActions_HoudiniAsset::AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) -{ - if (ActivationType == EAssetTypeActivationMethod::DoubleClicked) - { - bool ValidObjects = false; - TArray> HoudiniAssets; - if (InObjects.Num() > 0) - { - HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); - ValidObjects = true; - } - - if (ValidObjects) - { - ExecuteInstantiate(HoudiniAssets); - return true; - } - } - - return false; -} - - -TSharedRef -FAssetTypeActions_HoudiniAsset::AddLevelEditorMenuExtenders(TArray> HoudiniAssets) -{ - FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); - TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); - - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - TSharedRef Extender = MakeShareable(new FExtender); - Extender->AddMenuExtension( - "ActorAsset", - EExtensionHook::After, - LevelEditorCommandBindings, - FMenuExtensionDelegate::CreateLambda([this, HoudiniAssets, StyleSetName](FMenuBuilder& MenuBuilder) - { - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorer", "Find Source HDA"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorerTooltip", "Opens an explorer at the location of this actor's source HDA file."), - FSlateIcon(StyleSetName, "HoudiniEngine.DigitalAsset"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudini", "Open HDA in Houdini"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudiniTooltip", "Opens the selected asset's source HDA file in Houdini."), - FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - }) - ); - - return Extender; -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteReimport(TArray> InHoudiniAssetPtrs) -{ - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (HoudiniAsset) - FReimportManager::Instance()->Reimport(HoudiniAsset, true); - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs) -{ - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (HoudiniAsset && HoudiniAsset->AssetImportData) - { - const FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); - if (SourceFilePath.Len() && IFileManager::Get().FileSize(*SourceFilePath) != INDEX_NONE) - return FPlatformProcess::ExploreFolder(*SourceFilePath); - } - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs) -{ - if (!FHoudiniEngine::IsInitialized()) - return; - - if (InHoudiniAssetPtrs.Num() != 1) - return; - - UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - return; - - FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); - if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) - return; - - if (!FPaths::FileExists(SourceFilePath)) - return; - - // We'll need to modify the file name for expanded .hda - FString FileExtension = FPaths::GetExtension(SourceFilePath); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) - { - // the .hda directory is what we're actually interested in loading - SourceFilePath = FPaths::GetPath(SourceFilePath); - } - - if (FPaths::IsRelative(SourceFilePath)) - FPaths::ConvertRelativePathToFull(SourceFilePath); - - // Then open the HDA file in Houdini - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + "/hview"; - - FPlatformProcess::CreateProc( - HoudiniLocation.GetCharArray().GetData(), - SourceFilePath.GetCharArray().GetData(), - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); -} - - -void -FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs) -{ - // Reimports and then rebuild all instances of the asset - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (!HoudiniAsset) - continue; - - // Reimports the asset - FReimportManager::Instance()->Reimport(HoudiniAsset, true); - - // Rebuilds all instances of that asset in the scene - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * Component = *Itr; - if (Component && (Component->GetHoudiniAsset() == HoudiniAsset)) - { - Component->MarkAsNeedRebuild(); - } - } - } -} - - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE); -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI); -} -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH); -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType) -{ - if (InHoudiniAssetPtrs.Num() != 1) - return; - - UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - return; - - - FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(HoudiniAsset, InType, EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY); - /* - // Creating a temporary tool for the selected asset - TSoftObjectPtr HoudiniAssetPtr(HoudiniAsset); - FHoudiniTool HoudiniTool( - HoudiniAssetPtr, - FText::FromString(HoudiniAsset->GetName()), - Type, - EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY, - FText(), - NULL, - FString(), - false, - FFilePath(), - FHoudiniToolDirectory(), - FString()); - - SHoudiniToolPalette::InstantiateHoudiniTool(&HoudiniTool); - */ -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs) -{ - for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) - { - UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - continue; - - FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, FTransform::Identity); - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteInstantiate(TArray> InHoudiniAssetPtrs) -{ - FTransform DefaultTransform = FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform(); - for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) - { - UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - continue; - - FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, DefaultTransform); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "AssetTypeActions_HoudiniAsset.h" +#include "HoudiniAsset.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniTool.h" +#include "HoudiniEngineEditorUtils.h" + +#include "EditorReimportHandler.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "HAL/FileManager.h" +#include "EditorFramework/AssetImportData.h" +#include "LevelEditor.h" +#include "Modules/ModuleManager.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FText +FAssetTypeActions_HoudiniAsset::GetName() const +{ + return LOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset"); +} + +FColor +FAssetTypeActions_HoudiniAsset::GetTypeColor() const +{ + return FColor(255, 165, 0); +} + +UClass * +FAssetTypeActions_HoudiniAsset::GetSupportedClass() const +{ + return UHoudiniAsset::StaticClass(); +} + +uint32 +FAssetTypeActions_HoudiniAsset::GetCategories() +{ + return EAssetTypeCategories::Misc; +} + +/* +UThumbnailInfo * +FAssetTypeActions_HoudiniAsset::GetThumbnailInfo(UObject * Asset) const +{ + if (!Asset || Asset->IsPendingKill()) + return nullptr; + + UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >(Asset); + UThumbnailInfo * ThumbnailInfo = HoudiniAsset->ThumbnailInfo; + if (!ThumbnailInfo) + { + // If we have no thumbnail information, construct it. + ThumbnailInfo = NewObject< USceneThumbnailInfo >(HoudiniAsset, USceneThumbnailInfo::StaticClass()); + HoudiniAsset->ThumbnailInfo = ThumbnailInfo; + } + + return ThumbnailInfo; +} +*/ + +bool +FAssetTypeActions_HoudiniAsset::HasActions(const TArray< UObject * > & InObjects) const +{ + return true; +} + +void +FAssetTypeActions_HoudiniAsset::GetActions(const TArray & InObjects, class FMenuBuilder & MenuBuilder) +{ + bool ValidObjects = false; + TArray> HoudiniAssets; + if (InObjects.Num() > 0) + { + HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); + ValidObjects = true; + } + + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Reimport", "Reimport"), + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ReimportTooltip", "Reimport selected Houdini Assets."), + FSlateIcon(StyleSetName, "HoudiniEngine._Reset"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteReimport, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAll", "Rebuild All Instances"), + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAllTooltip", "Reimports and rebuild all instances of the selected Houdini Assets."), + FSlateIcon(StyleSetName, "HoudiniEngine._RebuildAll"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorer", "Find Source"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorerTooltip", + "Opens explorer at the location of this asset."), + FSlateIcon(StyleSetName, "HoudiniEngine.Hou_OpenInHoudinidiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudini", "Open in Houdini"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudiniTooltip", + "Opens the selected asset in Houdini."), + FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuSeparator(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Instantiate", "Instantiate"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateTooltip", + "Instantiate the selected asset in the current world."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiate, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOrigin", "Instantiate at the origin"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOriginTooltip", + "Instantiate the selected asset in the current world. The Houdini Asset Actor will be created at the origin of the level (0,0,0)."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuSeparator(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingle", "Apply to the current selection (single input)"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingleTooltip", + "Applies the selected asset to the current world selection. All the selected object will be assigned to the first input."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMulti", "Apply to the current selection (multiple inputs )"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMultiTooltip", + "Applies the selected asset to the current world selection. Each selected object will be assigned to its own input (one object per input)."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatch", "Batch Apply to the current selection"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatchTooltip", + "Batch apply the selected asset to the current world selection. An instance of the selected Houdini asset will be created for each selected object."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); +} + + +bool +FAssetTypeActions_HoudiniAsset::AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) +{ + if (ActivationType == EAssetTypeActivationMethod::DoubleClicked) + { + bool ValidObjects = false; + TArray> HoudiniAssets; + if (InObjects.Num() > 0) + { + HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); + ValidObjects = true; + } + + if (ValidObjects) + { + ExecuteInstantiate(HoudiniAssets); + return true; + } + } + + return false; +} + + +TSharedRef +FAssetTypeActions_HoudiniAsset::AddLevelEditorMenuExtenders(TArray> HoudiniAssets) +{ + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); + + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + TSharedRef Extender = MakeShareable(new FExtender); + Extender->AddMenuExtension( + "ActorAsset", + EExtensionHook::After, + LevelEditorCommandBindings, + FMenuExtensionDelegate::CreateLambda([this, HoudiniAssets, StyleSetName](FMenuBuilder& MenuBuilder) + { + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorer", "Find Source HDA"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorerTooltip", "Opens an explorer at the location of this actor's source HDA file."), + FSlateIcon(StyleSetName, "HoudiniEngine.DigitalAsset"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudini", "Open HDA in Houdini"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudiniTooltip", "Opens the selected asset's source HDA file in Houdini."), + FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + }) + ); + + return Extender; +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteReimport(TArray> InHoudiniAssetPtrs) +{ + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (HoudiniAsset) + FReimportManager::Instance()->Reimport(HoudiniAsset, true); + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs) +{ + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (HoudiniAsset && HoudiniAsset->AssetImportData) + { + const FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); + if (SourceFilePath.Len() && IFileManager::Get().FileSize(*SourceFilePath) != INDEX_NONE) + return FPlatformProcess::ExploreFolder(*SourceFilePath); + } + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs) +{ + if (!FHoudiniEngine::IsInitialized()) + return; + + if (InHoudiniAssetPtrs.Num() != 1) + return; + + UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + return; + + FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); + if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) + return; + + if (!FPaths::FileExists(SourceFilePath)) + return; + + // We'll need to modify the file name for expanded .hda + FString FileExtension = FPaths::GetExtension(SourceFilePath); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) + { + // the .hda directory is what we're actually interested in loading + SourceFilePath = FPaths::GetPath(SourceFilePath); + } + + if (FPaths::IsRelative(SourceFilePath)) + FPaths::ConvertRelativePathToFull(SourceFilePath); + + // Then open the HDA file in Houdini + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + "/hview"; + + FPlatformProcess::CreateProc( + HoudiniLocation.GetCharArray().GetData(), + SourceFilePath.GetCharArray().GetData(), + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); +} + + +void +FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs) +{ + // Reimports and then rebuild all instances of the asset + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (!HoudiniAsset) + continue; + + // Reimports the asset + FReimportManager::Instance()->Reimport(HoudiniAsset, true); + + // Rebuilds all instances of that asset in the scene + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * Component = *Itr; + if (Component && (Component->GetHoudiniAsset() == HoudiniAsset)) + { + Component->MarkAsNeedRebuild(); + } + } + } +} + + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE); +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI); +} +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH); +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType) +{ + if (InHoudiniAssetPtrs.Num() != 1) + return; + + UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + return; + + + FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(HoudiniAsset, InType, EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY); + /* + // Creating a temporary tool for the selected asset + TSoftObjectPtr HoudiniAssetPtr(HoudiniAsset); + FHoudiniTool HoudiniTool( + HoudiniAssetPtr, + FText::FromString(HoudiniAsset->GetName()), + Type, + EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY, + FText(), + NULL, + FString(), + false, + FFilePath(), + FHoudiniToolDirectory(), + FString()); + + SHoudiniToolPalette::InstantiateHoudiniTool(&HoudiniTool); + */ +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs) +{ + for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) + { + UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + continue; + + FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, FTransform::Identity); + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteInstantiate(TArray> InHoudiniAssetPtrs) +{ + FTransform DefaultTransform = FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform(); + for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) + { + UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + continue; + + FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, DefaultTransform); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h index f29ee8874..fe798135a 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h @@ -1,84 +1,84 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "AssetTypeActions_Base.h" - -class UClass; -class UObject; -class UHoudiniAsset; - -enum class EHoudiniToolType : uint8; - -class FAssetTypeActions_HoudiniAsset : public FAssetTypeActions_Base -{ - public: - - // FAssetTypeActions_Base methods. - virtual FText GetName() const override; - virtual FColor GetTypeColor() const override; - virtual UClass* GetSupportedClass() const override; - virtual uint32 GetCategories() override; - //virtual UThumbnailInfo * GetThumbnailInfo(UObject * Asset) const override; - virtual bool HasActions(const TArray< UObject * > & InObjects) const override; - virtual void GetActions(const TArray< UObject * > & InObjects, class FMenuBuilder & MenuBuilder) override; - - virtual bool AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) override; - - TSharedRef AddLevelEditorMenuExtenders(TArray> InHoudiniAssetPtrs); - - protected: - - // Handler for reimport option. - void ExecuteReimport(TArray> InHoudiniAssetPtrs); - - // Handler for rebuild all option - void ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs); - - // Handler for find in explorer option - void ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs); - - // Handler for the open in Houdini option - void ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs); - - // Handler to apply the current hda to the current world selection (single input) - void ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs); - - // Handler to apply the current hda to the current world selection (multi input) - void ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs); - - // Handler to batch apply the current hda to the current world selection - void ExecuteApplyBatch(TArray> InHoudiniAssetPtrs ); - - // Handler to instantiate the HDA in the world - void ExecuteInstantiate(TArray> InHoudiniAssetPtrs); - - // Handler to instantiate the HDA in the world, actor is placed at the origin - void ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs); - - void ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "AssetTypeActions_Base.h" + +class UClass; +class UObject; +class UHoudiniAsset; + +enum class EHoudiniToolType : uint8; + +class FAssetTypeActions_HoudiniAsset : public FAssetTypeActions_Base +{ + public: + + // FAssetTypeActions_Base methods. + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + //virtual UThumbnailInfo * GetThumbnailInfo(UObject * Asset) const override; + virtual bool HasActions(const TArray< UObject * > & InObjects) const override; + virtual void GetActions(const TArray< UObject * > & InObjects, class FMenuBuilder & MenuBuilder) override; + + virtual bool AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) override; + + TSharedRef AddLevelEditorMenuExtenders(TArray> InHoudiniAssetPtrs); + + protected: + + // Handler for reimport option. + void ExecuteReimport(TArray> InHoudiniAssetPtrs); + + // Handler for rebuild all option + void ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs); + + // Handler for find in explorer option + void ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs); + + // Handler for the open in Houdini option + void ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs); + + // Handler to apply the current hda to the current world selection (single input) + void ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs); + + // Handler to apply the current hda to the current world selection (multi input) + void ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs); + + // Handler to batch apply the current hda to the current world selection + void ExecuteApplyBatch(TArray> InHoudiniAssetPtrs ); + + // Handler to instantiate the HDA in the world + void ExecuteInstantiate(TArray> InHoudiniAssetPtrs); + + // Handler to instantiate the HDA in the world, actor is placed at the origin + void ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs); + + void ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp index 58797cb32..8f7e86ba7 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp @@ -1,117 +1,117 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetActorFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineUtils.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniAssetActorFactory::UHoudiniAssetActorFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - DisplayName = LOCTEXT("HoudiniAssetDisplayName", "Houdini Engine Asset"); - NewActorClass = AHoudiniAssetActor::StaticClass(); -} - -bool -UHoudiniAssetActorFactory::CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) -{ - if (!AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UHoudiniAsset::StaticClass())) - { - OutErrorMsg = NSLOCTEXT("CanCreateActor", "NoHoudiniAsset", "A valid Houdini Engine asset must be specified."); - return false; - } - - return true; -} - -UObject * -UHoudiniAssetActorFactory::GetAssetFromActorInstance(AActor * Instance) -{ - check(Instance->IsA(NewActorClass)); - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(Instance); - - check(HoudiniAssetActor->GetHoudiniAssetComponent()); - return HoudiniAssetActor->GetHoudiniAssetComponent()->HoudiniAsset; -} - -void -UHoudiniAssetActorFactory::PostSpawnActor(UObject * Asset, AActor * NewActor) -{ - HOUDINI_LOG_MESSAGE(TEXT("PostSpawnActor %s, supplied Asset = 0x%0.8p"), *NewActor->GetName(), Asset); - - UHoudiniAsset * HoudiniAsset = CastChecked(Asset); - if (HoudiniAsset) - { - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(NewActor); - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - check(HoudiniAssetComponent); - - //HoudiniAssetComponent->UnregisterComponent(); - //HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - //HoudiniAssetComponent->RegisterComponent(); - - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); - - if (!HoudiniAssetActor->IsUsedForPreview()) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); - } - } -} - -void -UHoudiniAssetActorFactory::PostCreateBlueprint(UObject * Asset, AActor * CDO) -{ - HOUDINI_LOG_MESSAGE(TEXT("PostCreateBlueprint, supplied Asset = 0x%0.8p"), Asset); - - UHoudiniAsset * HoudiniAsset = CastChecked(Asset); - if (HoudiniAsset) - { - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(CDO); - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - check(HoudiniAssetComponent); - - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); - - if (!HoudiniAssetActor->IsUsedForPreview()) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); - } - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetActorFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineUtils.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetActorFactory::UHoudiniAssetActorFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + DisplayName = LOCTEXT("HoudiniAssetDisplayName", "Houdini Engine Asset"); + NewActorClass = AHoudiniAssetActor::StaticClass(); +} + +bool +UHoudiniAssetActorFactory::CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) +{ + if (!AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UHoudiniAsset::StaticClass())) + { + OutErrorMsg = NSLOCTEXT("CanCreateActor", "NoHoudiniAsset", "A valid Houdini Engine asset must be specified."); + return false; + } + + return true; +} + +UObject * +UHoudiniAssetActorFactory::GetAssetFromActorInstance(AActor * Instance) +{ + check(Instance->IsA(NewActorClass)); + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(Instance); + + check(HoudiniAssetActor->GetHoudiniAssetComponent()); + return HoudiniAssetActor->GetHoudiniAssetComponent()->HoudiniAsset; +} + +void +UHoudiniAssetActorFactory::PostSpawnActor(UObject * Asset, AActor * NewActor) +{ + HOUDINI_LOG_MESSAGE(TEXT("PostSpawnActor %s, supplied Asset = 0x%0.8p"), *NewActor->GetName(), Asset); + + UHoudiniAsset * HoudiniAsset = CastChecked(Asset); + if (HoudiniAsset) + { + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(NewActor); + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + check(HoudiniAssetComponent); + + //HoudiniAssetComponent->UnregisterComponent(); + //HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + //HoudiniAssetComponent->RegisterComponent(); + + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); + + if (!HoudiniAssetActor->IsUsedForPreview()) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); + } + } +} + +void +UHoudiniAssetActorFactory::PostCreateBlueprint(UObject * Asset, AActor * CDO) +{ + HOUDINI_LOG_MESSAGE(TEXT("PostCreateBlueprint, supplied Asset = 0x%0.8p"), Asset); + + UHoudiniAsset * HoudiniAsset = CastChecked(Asset); + if (HoudiniAsset) + { + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(CDO); + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + check(HoudiniAssetComponent); + + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); + + if (!HoudiniAssetActor->IsUsedForPreview()) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h index b3a99dc97..286b7586a 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h @@ -1,58 +1,58 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ActorFactories/ActorFactory.h" -#include "HoudiniAssetActorFactory.generated.h" - -class FText; -class AActor; -class UObject; -class UHoudiniAssetComponent; - -struct FAssetData; - -UCLASS(config = Editor) -class UHoudiniAssetActorFactory : public UActorFactory -{ - GENERATED_UCLASS_BODY() - -public: - // UActorFactory methods: - // Return true if Actor can be created from a given asset. - virtual bool CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) override; - // Given an instance of an actor pertaining to this factory, find the asset that should be used to create a new actor. - virtual UObject * GetAssetFromActorInstance(AActor * Instance) override; - // Modify the actor after it has been spawned. - virtual void PostSpawnActor(UObject * Asset, AActor * NewActor) override; - // Called after a blueprint is created by this factory to update the blueprint's CDO properties - // with state from the asset for this factory. - virtual void PostCreateBlueprint(UObject * Asset, AActor * CDO) override; - -protected: - bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ActorFactories/ActorFactory.h" +#include "HoudiniAssetActorFactory.generated.h" + +class FText; +class AActor; +class UObject; +class UHoudiniAssetComponent; + +struct FAssetData; + +UCLASS(config = Editor) +class UHoudiniAssetActorFactory : public UActorFactory +{ + GENERATED_UCLASS_BODY() + +public: + // UActorFactory methods: + // Return true if Actor can be created from a given asset. + virtual bool CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) override; + // Given an instance of an actor pertaining to this factory, find the asset that should be used to create a new actor. + virtual UObject * GetAssetFromActorInstance(AActor * Instance) override; + // Modify the actor after it has been spawned. + virtual void PostSpawnActor(UObject * Asset, AActor * NewActor) override; + // Called after a blueprint is created by this factory to update the blueprint's CDO properties + // with state from the asset for this factory. + virtual void PostCreateBlueprint(UObject * Asset, AActor * CDO) override; + +protected: + bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp index 83b5a7e59..895b17c06 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp @@ -1,72 +1,72 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetBroker.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAsset.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniAssetBroker::~FHoudiniAssetBroker() -{ - -} - -UClass * -FHoudiniAssetBroker::GetSupportedAssetClass() -{ - return UHoudiniAsset::StaticClass(); -} - -bool -FHoudiniAssetBroker::AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) -{ - if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) - { - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(InAsset); - if (HoudiniAsset || !InAsset) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - return true; - } - } - - return false; -} - -UObject * -FHoudiniAssetBroker::GetAssetFromComponent(UActorComponent * InComponent) -{ - if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) - { - return HoudiniAssetComponent->GetHoudiniAsset(); - } - - return nullptr; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetBroker.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAsset.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniAssetBroker::~FHoudiniAssetBroker() +{ + +} + +UClass * +FHoudiniAssetBroker::GetSupportedAssetClass() +{ + return UHoudiniAsset::StaticClass(); +} + +bool +FHoudiniAssetBroker::AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) +{ + if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) + { + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(InAsset); + if (HoudiniAsset || !InAsset) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + return true; + } + } + + return false; +} + +UObject * +FHoudiniAssetBroker::GetAssetFromComponent(UActorComponent * InComponent) +{ + if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) + { + return HoudiniAssetComponent->GetHoudiniAsset(); + } + + return nullptr; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h index 01d6f3da8..3357d841f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h @@ -1,49 +1,49 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ComponentAssetBroker.h" - -class UObject; -class UActorComponent; - -class FHoudiniAssetBroker : public IComponentAssetBroker -{ -public: - - virtual ~FHoudiniAssetBroker(); - - // IComponentAssetBroker methods. - // Reports the asset class this broker knows how to handle. - UClass * GetSupportedAssetClass() override; - - // Assign the assigned asset to the supplied component. - bool AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) override; - - // Get the currently assigned asset from the component. - UObject * GetAssetFromComponent(UActorComponent * InComponent) override; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ComponentAssetBroker.h" + +class UObject; +class UActorComponent; + +class FHoudiniAssetBroker : public IComponentAssetBroker +{ +public: + + virtual ~FHoudiniAssetBroker(); + + // IComponentAssetBroker methods. + // Reports the asset class this broker knows how to handle. + UClass * GetSupportedAssetClass() override; + + // Assign the assigned asset to the supplied component. + bool AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) override; + + // Get the currently assigned asset from the component. + UObject * GetAssetFromComponent(UActorComponent * InComponent) override; +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp index b1362ed0e..8ab4cdb45 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp @@ -1,466 +1,466 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetComponentDetails.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAsset.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniParameter.h" -#include "HoudiniHandleComponent.h" -#include "HoudiniParameterDetails.h" -#include "HoudiniInput.h" -#include "HoudiniInputDetails.h" -#include "HoudiniHandleDetails.h" -#include "HoudiniOutput.h" -#include "HoudiniOutputDetails.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Layout/SSeparator.h" -#include "Widgets/Layout/SBox.h" -#include "Widgets/Images/SImage.h" - -#include "PropertyCustomizationHelpers.h" -#include "DetailLayoutBuilder.h" -#include "DetailCategoryBuilder.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -TSharedRef< IDetailCustomization > -FHoudiniAssetComponentDetails::MakeInstance() -{ - return MakeShareable(new FHoudiniAssetComponentDetails); -} - -FHoudiniAssetComponentDetails::FHoudiniAssetComponentDetails() -{ - OutputDetails = MakeShared(); - ParameterDetails = MakeShared(); - PDGDetails = MakeShared(); - HoudiniEngineDetails = MakeShared(); -} - - -FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() -{ - // The ramp param's curves are added to root to avoid garbage collection - // We need to remove those curves from the root when the details classes are destroyed. - if (ParameterDetails.IsValid()) - { - FHoudiniParameterDetails* ParamDetailsPtr = ParameterDetails.Get(); - - for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) - { - if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) - continue; - - CurFloatRampCurve->RemoveFromRoot(); - } - - for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) - { - if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) - continue; - - CurColorRampCurve->RemoveFromRoot(); - } - - ParamDetailsPtr->CreatedFloatRampCurves.Empty(); - ParamDetailsPtr->CreatedColorRampCurves.Empty(); - } -} - -void -FHoudiniAssetComponentDetails::AddIndieLicenseRow(IDetailCategoryBuilder& InCategory) -{ - FText IndieText = - FText::FromString(TEXT("Houdini Engine Indie - For Limited Commercial Use Only")); - - FSlateFontInfo LargeDetailsFont = IDetailLayoutBuilder::GetDetailFontBold(); - LargeDetailsFont.Size += 2; - - FSlateColor LabelColor = FLinearColor(1.0f, 1.0f, 0.0f, 1.0f); - - InCategory.AddCustomRow(FText::GetEmpty()) - [ - SNew(STextBlock) - .Text(IndieText) - .ToolTipText(IndieText) - .Font(LargeDetailsFont) - .Justification(ETextJustify::Center) - .ColorAndOpacity(LabelColor) - ]; - - InCategory.AddCustomRow(FText::GetEmpty()) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .Padding(0, 0, 5, 0) - [ - SNew(SSeparator) - .Thickness(2.0f) - ] - ]; -} - -void -FHoudiniAssetComponentDetails::AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC) -{ - FString CategoryName = "Bake"; - InCategory.AddGroup(FName(*CategoryName), FText::FromString(CategoryName), false, false); - -} - -void -FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) -{ - // Get all components which are being customized. - TArray< TWeakObjectPtr< UObject > > ObjectsCustomized; - DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); - - // Extract the Houdini Asset Component to detail - for (int32 i = 0; i < ObjectsCustomized.Num(); ++i) - { - if (ObjectsCustomized[i].IsValid()) - { - UObject * Object = ObjectsCustomized[i].Get(); - if (Object) - { - UHoudiniAssetComponent * HAC = Cast< UHoudiniAssetComponent >(Object); - if (HAC && !HAC->IsPendingKill()) - HoudiniAssetComponents.Add(HAC); - } - } - } - - // Check if we'll need to add indie license labels - bool bIsIndieLicense = FHoudiniEngine::Get().IsLicenseIndie(); - - // To handle multiselection parameter edit, we try to group the selected components by their houdini assets - // TODO? ignore multiselection if all are not the same HDA? - // TODO do the same for inputs - TMap, TArray>> HoudiniAssetToHACs; - for (auto HAC : HoudiniAssetComponents) - { - TWeakObjectPtr HoudiniAsset = HAC->GetHoudiniAsset(); - if (!HoudiniAsset.IsValid()) - continue; - - TArray>& ValueRef = HoudiniAssetToHACs.FindOrAdd(HoudiniAsset); - ValueRef.Add(HAC); - } - - for (auto Iter : HoudiniAssetToHACs) - { - TArray> HACs = Iter.Value; - if (HACs.Num() < 1) - continue; - - TWeakObjectPtr MainComponent = HACs[0]; - if (!MainComponent.IsValid()) - continue; - - // If we have selected more than one component that have different HDAs, - // we'll want to separate the param/input/output category for each HDA - FString MultiSelectionIdentifier = FString(); - if (HoudiniAssetToHACs.Num() > 1) - { - MultiSelectionIdentifier = TEXT("("); - if (MainComponent->GetHoudiniAsset()) - MultiSelectionIdentifier += MainComponent->GetHoudiniAsset()->GetName(); - MultiSelectionIdentifier += TEXT(")"); - } - - /* - // Handled by the UPROPERTIES on the component in v2! - // Edit the Houdini details category - IDetailCategoryBuilder & HoudiniAssetCategory = - DetailBuilder.EditCategory("HoudiniAsset", FText::GetEmpty(), ECategoryPriority::Important); - */ - - // - // 0. HOUDINI ASSET DETAILS - // - - { - FString HoudiniEngineCategoryName = "Houdini Engine"; - HoudiniEngineCategoryName += MultiSelectionIdentifier; - - // Create Houdini Engine details category - IDetailCategoryBuilder & HouEngineCategory = - DetailBuilder.EditCategory(*HoudiniEngineCategoryName, FText::FromString("Houdini Engine"), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouEngineCategory); - - TArray MultiSelectedHACs; - for (auto& NextHACWeakPtr : HACs) - { - if (NextHACWeakPtr.IsValid()) - MultiSelectedHACs.Add(NextHACWeakPtr.Get()); - } - - HoudiniEngineDetails->CreateWidget(HouEngineCategory, MultiSelectedHACs); - } - - // - // 1. PDG ASSET LINK (if available) - // - if (MainComponent->GetPDGAssetLink()) - { - FString PDGCatName = "HoudiniPDGAssetLink"; - PDGCatName += MultiSelectionIdentifier; - - // Create the PDG Asset Link details category - IDetailCategoryBuilder & HouPDGCategory = - DetailBuilder.EditCategory(*PDGCatName, FText::FromString("Houdini - PDG Asset Link"), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouPDGCategory); - - // TODO: Handle multi selection of outputs like params/inputs? - - - PDGDetails->CreateWidget(HouPDGCategory, MainComponent->GetPDGAssetLink()/*, MainComponent*/); - } - - - // - // 2. PARAMETER DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString ParamCatName = "HoudiniParameters"; - ParamCatName += MultiSelectionIdentifier; - - // Create the Parameters details category - IDetailCategoryBuilder & HouParameterCategory = - DetailBuilder.EditCategory(*ParamCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if(bIsIndieLicense) - AddIndieLicenseRow(HouParameterCategory); - - // Iterate through the component's parameters - for (int32 ParamIdx = 0; ParamIdx < MainComponent->GetNumParameters(); ParamIdx++) - { - // We only want to create root parameters here, they will recursively create child parameters. - UHoudiniParameter* CurrentParam = MainComponent->GetParameterAt(ParamIdx); - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - // TODO: remove ? unneeded? - // ensure the parameter is actually owned by a HAC - /*const TWeakObjectPtr Owner = Cast(CurrentParam->GetOuter()); - if (!Owner.IsValid()) - continue;*/ - - // Build an array of edited parameter for multi edit - TArray EditedParams; - EditedParams.Add(CurrentParam); - - // Add the corresponding params in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniParameter* LinkedParam = HACs[LinkedIdx]->GetParameterAt(ParamIdx); - if (!LinkedParam || LinkedParam->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if ( !LinkedParam->Matches(*CurrentParam) ) - { - LinkedParam = MainComponent->FindMatchingParameter(CurrentParam); - if (!LinkedParam || LinkedParam->IsPendingKill() || LinkedParam->IsChildParameter()) - continue; - } - - EditedParams.Add(LinkedParam); - } - - ParameterDetails->CreateWidget(HouParameterCategory, EditedParams); - } - - /*** HOUDINI HANDLE DETAILS ***/ - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString HandleCatName = "HoudiniHandles"; - HandleCatName += MultiSelectionIdentifier; - - // Create the Parameters details category - IDetailCategoryBuilder & HouHandleCategory = - DetailBuilder.EditCategory(*HandleCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouHandleCategory); - - // Iterate through the component's Houdini handles - for (int32 HandleIdx = 0; HandleIdx < MainComponent->GetNumHandles(); ++HandleIdx) - { - UHoudiniHandleComponent* CurrentHandleComponent = MainComponent->GetHandleComponentAt(HandleIdx); - - if (!CurrentHandleComponent || CurrentHandleComponent->IsPendingKill()) - continue; - - TArray EditedHandles; - EditedHandles.Add(CurrentHandleComponent); - - // Add the corresponding params in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); ++LinkedIdx) - { - UHoudiniHandleComponent* LinkedHandle = HACs[LinkedIdx]->GetHandleComponentAt(HandleIdx); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) - continue; - - // Linked handles should match the main param, if not try to find one that matches - if (!LinkedHandle->Matches(*CurrentHandleComponent)) - { - LinkedHandle = MainComponent->FindMatchingHandle(CurrentHandleComponent); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) - continue; - } - - EditedHandles.Add(LinkedHandle); - } - - FHoudiniHandleDetails::CreateWidget(HouHandleCategory, EditedHandles); - } - - - // - // 3. INPUT DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString InputCatName = "HoudiniInputs"; - InputCatName += MultiSelectionIdentifier; - - // Create the input details category - IDetailCategoryBuilder & HouInputCategory = - DetailBuilder.EditCategory(*InputCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouInputCategory); - - // Iterate through the component's inputs - for (int32 InputIdx = 0; InputIdx < MainComponent->GetNumInputs(); InputIdx++) - { - UHoudiniInput* CurrentInput = MainComponent->GetInputAt(InputIdx); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (!MainComponent->IsInputTypeSupported(CurrentInput->GetInputType())) - continue; - - // Object path parameter inputs are displayed by the ParameterDetails - skip them - if (CurrentInput->IsObjectPathParameter()) - continue; - - // Build an array of edited inputs for multi edit - TArray EditedInputs; - EditedInputs.Add(CurrentInput); - - // Add the corresponding inputs in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniInput* LinkedInput = HACs[LinkedIdx]->GetInputAt(InputIdx); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if (!LinkedInput->Matches(*CurrentInput)) - { - LinkedInput = MainComponent->FindMatchingInput(CurrentInput); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - } - - EditedInputs.Add(LinkedInput); - } - - FHoudiniInputDetails::CreateWidget(HouInputCategory, EditedInputs); - } - - // - // 4. OUTPUT DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString OutputCatName = "HoudiniOutputs"; - OutputCatName += MultiSelectionIdentifier; - - // Create the output details category - IDetailCategoryBuilder & HouOutputCategory = - DetailBuilder.EditCategory(*OutputCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // Iterate through the component's outputs - for (int32 OutputIdx = 0; OutputIdx < MainComponent->GetNumOutputs(); OutputIdx++) - { - UHoudiniOutput* CurrentOutput = MainComponent->GetOutputAt(OutputIdx); - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - // Build an array of edited inpoutputs for multi edit - TArray EditedOutputs; - EditedOutputs.Add(CurrentOutput); - - // Add the corresponding outputs in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniOutput* LinkedOutput = HACs[LinkedIdx]->GetOutputAt(OutputIdx); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) - continue; - - /* - // Linked output should match the main output! If not try to find one that matches - if (!LinkedOutput->Matches(*CurrentOutput)) - { - LinkedOutput = MainComponent->FindMatchingInput(CurrentOutput); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) - continue; - } - */ - - EditedOutputs.Add(LinkedOutput); - } - - // TODO: Handle multi selection of outputs like params/inputs? - OutputDetails->CreateWidget(HouOutputCategory, EditedOutputs); - } - } -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetComponentDetails.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAsset.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniParameter.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniParameterDetails.h" +#include "HoudiniInput.h" +#include "HoudiniInputDetails.h" +#include "HoudiniHandleDetails.h" +#include "HoudiniOutput.h" +#include "HoudiniOutputDetails.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Images/SImage.h" + +#include "PropertyCustomizationHelpers.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +TSharedRef< IDetailCustomization > +FHoudiniAssetComponentDetails::MakeInstance() +{ + return MakeShareable(new FHoudiniAssetComponentDetails); +} + +FHoudiniAssetComponentDetails::FHoudiniAssetComponentDetails() +{ + OutputDetails = MakeShared(); + ParameterDetails = MakeShared(); + PDGDetails = MakeShared(); + HoudiniEngineDetails = MakeShared(); +} + + +FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() +{ + // The ramp param's curves are added to root to avoid garbage collection + // We need to remove those curves from the root when the details classes are destroyed. + if (ParameterDetails.IsValid()) + { + FHoudiniParameterDetails* ParamDetailsPtr = ParameterDetails.Get(); + + for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) + { + if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) + continue; + + CurFloatRampCurve->RemoveFromRoot(); + } + + for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) + { + if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) + continue; + + CurColorRampCurve->RemoveFromRoot(); + } + + ParamDetailsPtr->CreatedFloatRampCurves.Empty(); + ParamDetailsPtr->CreatedColorRampCurves.Empty(); + } +} + +void +FHoudiniAssetComponentDetails::AddIndieLicenseRow(IDetailCategoryBuilder& InCategory) +{ + FText IndieText = + FText::FromString(TEXT("Houdini Engine Indie - For Limited Commercial Use Only")); + + FSlateFontInfo LargeDetailsFont = IDetailLayoutBuilder::GetDetailFontBold(); + LargeDetailsFont.Size += 2; + + FSlateColor LabelColor = FLinearColor(1.0f, 1.0f, 0.0f, 1.0f); + + InCategory.AddCustomRow(FText::GetEmpty()) + [ + SNew(STextBlock) + .Text(IndieText) + .ToolTipText(IndieText) + .Font(LargeDetailsFont) + .Justification(ETextJustify::Center) + .ColorAndOpacity(LabelColor) + ]; + + InCategory.AddCustomRow(FText::GetEmpty()) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(0, 0, 5, 0) + [ + SNew(SSeparator) + .Thickness(2.0f) + ] + ]; +} + +void +FHoudiniAssetComponentDetails::AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC) +{ + FString CategoryName = "Bake"; + InCategory.AddGroup(FName(*CategoryName), FText::FromString(CategoryName), false, false); + +} + +void +FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + // Get all components which are being customized. + TArray< TWeakObjectPtr< UObject > > ObjectsCustomized; + DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); + + // Extract the Houdini Asset Component to detail + for (int32 i = 0; i < ObjectsCustomized.Num(); ++i) + { + if (ObjectsCustomized[i].IsValid()) + { + UObject * Object = ObjectsCustomized[i].Get(); + if (Object) + { + UHoudiniAssetComponent * HAC = Cast< UHoudiniAssetComponent >(Object); + if (HAC && !HAC->IsPendingKill()) + HoudiniAssetComponents.Add(HAC); + } + } + } + + // Check if we'll need to add indie license labels + bool bIsIndieLicense = FHoudiniEngine::Get().IsLicenseIndie(); + + // To handle multiselection parameter edit, we try to group the selected components by their houdini assets + // TODO? ignore multiselection if all are not the same HDA? + // TODO do the same for inputs + TMap, TArray>> HoudiniAssetToHACs; + for (auto HAC : HoudiniAssetComponents) + { + TWeakObjectPtr HoudiniAsset = HAC->GetHoudiniAsset(); + if (!HoudiniAsset.IsValid()) + continue; + + TArray>& ValueRef = HoudiniAssetToHACs.FindOrAdd(HoudiniAsset); + ValueRef.Add(HAC); + } + + for (auto Iter : HoudiniAssetToHACs) + { + TArray> HACs = Iter.Value; + if (HACs.Num() < 1) + continue; + + TWeakObjectPtr MainComponent = HACs[0]; + if (!MainComponent.IsValid()) + continue; + + // If we have selected more than one component that have different HDAs, + // we'll want to separate the param/input/output category for each HDA + FString MultiSelectionIdentifier = FString(); + if (HoudiniAssetToHACs.Num() > 1) + { + MultiSelectionIdentifier = TEXT("("); + if (MainComponent->GetHoudiniAsset()) + MultiSelectionIdentifier += MainComponent->GetHoudiniAsset()->GetName(); + MultiSelectionIdentifier += TEXT(")"); + } + + /* + // Handled by the UPROPERTIES on the component in v2! + // Edit the Houdini details category + IDetailCategoryBuilder & HoudiniAssetCategory = + DetailBuilder.EditCategory("HoudiniAsset", FText::GetEmpty(), ECategoryPriority::Important); + */ + + // + // 0. HOUDINI ASSET DETAILS + // + + { + FString HoudiniEngineCategoryName = "Houdini Engine"; + HoudiniEngineCategoryName += MultiSelectionIdentifier; + + // Create Houdini Engine details category + IDetailCategoryBuilder & HouEngineCategory = + DetailBuilder.EditCategory(*HoudiniEngineCategoryName, FText::FromString("Houdini Engine"), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouEngineCategory); + + TArray MultiSelectedHACs; + for (auto& NextHACWeakPtr : HACs) + { + if (NextHACWeakPtr.IsValid()) + MultiSelectedHACs.Add(NextHACWeakPtr.Get()); + } + + HoudiniEngineDetails->CreateWidget(HouEngineCategory, MultiSelectedHACs); + } + + // + // 1. PDG ASSET LINK (if available) + // + if (MainComponent->GetPDGAssetLink()) + { + FString PDGCatName = "HoudiniPDGAssetLink"; + PDGCatName += MultiSelectionIdentifier; + + // Create the PDG Asset Link details category + IDetailCategoryBuilder & HouPDGCategory = + DetailBuilder.EditCategory(*PDGCatName, FText::FromString("Houdini - PDG Asset Link"), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouPDGCategory); + + // TODO: Handle multi selection of outputs like params/inputs? + + + PDGDetails->CreateWidget(HouPDGCategory, MainComponent->GetPDGAssetLink()/*, MainComponent*/); + } + + + // + // 2. PARAMETER DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString ParamCatName = "HoudiniParameters"; + ParamCatName += MultiSelectionIdentifier; + + // Create the Parameters details category + IDetailCategoryBuilder & HouParameterCategory = + DetailBuilder.EditCategory(*ParamCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if(bIsIndieLicense) + AddIndieLicenseRow(HouParameterCategory); + + // Iterate through the component's parameters + for (int32 ParamIdx = 0; ParamIdx < MainComponent->GetNumParameters(); ParamIdx++) + { + // We only want to create root parameters here, they will recursively create child parameters. + UHoudiniParameter* CurrentParam = MainComponent->GetParameterAt(ParamIdx); + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + // TODO: remove ? unneeded? + // ensure the parameter is actually owned by a HAC + /*const TWeakObjectPtr Owner = Cast(CurrentParam->GetOuter()); + if (!Owner.IsValid()) + continue;*/ + + // Build an array of edited parameter for multi edit + TArray EditedParams; + EditedParams.Add(CurrentParam); + + // Add the corresponding params in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniParameter* LinkedParam = HACs[LinkedIdx]->GetParameterAt(ParamIdx); + if (!LinkedParam || LinkedParam->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if ( !LinkedParam->Matches(*CurrentParam) ) + { + LinkedParam = MainComponent->FindMatchingParameter(CurrentParam); + if (!LinkedParam || LinkedParam->IsPendingKill() || LinkedParam->IsChildParameter()) + continue; + } + + EditedParams.Add(LinkedParam); + } + + ParameterDetails->CreateWidget(HouParameterCategory, EditedParams); + } + + /*** HOUDINI HANDLE DETAILS ***/ + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString HandleCatName = "HoudiniHandles"; + HandleCatName += MultiSelectionIdentifier; + + // Create the Parameters details category + IDetailCategoryBuilder & HouHandleCategory = + DetailBuilder.EditCategory(*HandleCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouHandleCategory); + + // Iterate through the component's Houdini handles + for (int32 HandleIdx = 0; HandleIdx < MainComponent->GetNumHandles(); ++HandleIdx) + { + UHoudiniHandleComponent* CurrentHandleComponent = MainComponent->GetHandleComponentAt(HandleIdx); + + if (!CurrentHandleComponent || CurrentHandleComponent->IsPendingKill()) + continue; + + TArray EditedHandles; + EditedHandles.Add(CurrentHandleComponent); + + // Add the corresponding params in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); ++LinkedIdx) + { + UHoudiniHandleComponent* LinkedHandle = HACs[LinkedIdx]->GetHandleComponentAt(HandleIdx); + if (!LinkedHandle || LinkedHandle->IsPendingKill()) + continue; + + // Linked handles should match the main param, if not try to find one that matches + if (!LinkedHandle->Matches(*CurrentHandleComponent)) + { + LinkedHandle = MainComponent->FindMatchingHandle(CurrentHandleComponent); + if (!LinkedHandle || LinkedHandle->IsPendingKill()) + continue; + } + + EditedHandles.Add(LinkedHandle); + } + + FHoudiniHandleDetails::CreateWidget(HouHandleCategory, EditedHandles); + } + + + // + // 3. INPUT DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString InputCatName = "HoudiniInputs"; + InputCatName += MultiSelectionIdentifier; + + // Create the input details category + IDetailCategoryBuilder & HouInputCategory = + DetailBuilder.EditCategory(*InputCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouInputCategory); + + // Iterate through the component's inputs + for (int32 InputIdx = 0; InputIdx < MainComponent->GetNumInputs(); InputIdx++) + { + UHoudiniInput* CurrentInput = MainComponent->GetInputAt(InputIdx); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (!MainComponent->IsInputTypeSupported(CurrentInput->GetInputType())) + continue; + + // Object path parameter inputs are displayed by the ParameterDetails - skip them + if (CurrentInput->IsObjectPathParameter()) + continue; + + // Build an array of edited inputs for multi edit + TArray EditedInputs; + EditedInputs.Add(CurrentInput); + + // Add the corresponding inputs in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniInput* LinkedInput = HACs[LinkedIdx]->GetInputAt(InputIdx); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if (!LinkedInput->Matches(*CurrentInput)) + { + LinkedInput = MainComponent->FindMatchingInput(CurrentInput); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + } + + EditedInputs.Add(LinkedInput); + } + + FHoudiniInputDetails::CreateWidget(HouInputCategory, EditedInputs); + } + + // + // 4. OUTPUT DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString OutputCatName = "HoudiniOutputs"; + OutputCatName += MultiSelectionIdentifier; + + // Create the output details category + IDetailCategoryBuilder & HouOutputCategory = + DetailBuilder.EditCategory(*OutputCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // Iterate through the component's outputs + for (int32 OutputIdx = 0; OutputIdx < MainComponent->GetNumOutputs(); OutputIdx++) + { + UHoudiniOutput* CurrentOutput = MainComponent->GetOutputAt(OutputIdx); + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + // Build an array of edited inpoutputs for multi edit + TArray EditedOutputs; + EditedOutputs.Add(CurrentOutput); + + // Add the corresponding outputs in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniOutput* LinkedOutput = HACs[LinkedIdx]->GetOutputAt(OutputIdx); + if (!LinkedOutput || LinkedOutput->IsPendingKill()) + continue; + + /* + // Linked output should match the main output! If not try to find one that matches + if (!LinkedOutput->Matches(*CurrentOutput)) + { + LinkedOutput = MainComponent->FindMatchingInput(CurrentOutput); + if (!LinkedOutput || LinkedOutput->IsPendingKill()) + continue; + } + */ + + EditedOutputs.Add(LinkedOutput); + } + + // TODO: Handle multi selection of outputs like params/inputs? + OutputDetails->CreateWidget(HouOutputCategory, EditedOutputs); + } + } +} + + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h index 47a51e56a..c908ea27b 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h @@ -1,86 +1,86 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "IDetailCustomization.h" -#include "HoudiniPDGDetails.h" -#include "HoudiniOutputDetails.h" -#include "HoudiniParameterDetails.h" -#include "HoudiniEngineDetails.h" - -class UHoudiniAssetComponent; -class UStaticMesh; - -class FHoudiniAssetComponentDetails : public IDetailCustomization -{ -public: - - // Constructor. - FHoudiniAssetComponentDetails(); - - // Destructor. - virtual ~FHoudiniAssetComponentDetails(); - - // IDetailCustomization methods. - virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; - - // Create an instance of this detail layout class. - static TSharedRef MakeInstance(); - -private: - - // Adds a text row indicate we're using a Houdini indie license - void AddIndieLicenseRow(IDetailCategoryBuilder& InCategory); - - // Adds a category for baking options - void AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC); - - // Handler for double clicking the static mesh thumbnail, opens the editor. - FReply OnThumbnailDoubleClick( - const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent, UObject* Object); - - -private: - - // Components which are being customized. - TArray> HoudiniAssetComponents; - - // Structure holding the output's details - TSharedPtr OutputDetails; - - // Structure holding the parameter's details - TSharedPtr ParameterDetails; - - // Structure holding the PDG Asset Link's details - TSharedPtr PDGDetails; - - // Structure holding the HoudiniAsset details - TSharedPtr HoudiniEngineDetails; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" +#include "HoudiniPDGDetails.h" +#include "HoudiniOutputDetails.h" +#include "HoudiniParameterDetails.h" +#include "HoudiniEngineDetails.h" + +class UHoudiniAssetComponent; +class UStaticMesh; + +class FHoudiniAssetComponentDetails : public IDetailCustomization +{ +public: + + // Constructor. + FHoudiniAssetComponentDetails(); + + // Destructor. + virtual ~FHoudiniAssetComponentDetails(); + + // IDetailCustomization methods. + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + + // Create an instance of this detail layout class. + static TSharedRef MakeInstance(); + +private: + + // Adds a text row indicate we're using a Houdini indie license + void AddIndieLicenseRow(IDetailCategoryBuilder& InCategory); + + // Adds a category for baking options + void AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC); + + // Handler for double clicking the static mesh thumbnail, opens the editor. + FReply OnThumbnailDoubleClick( + const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent, UObject* Object); + + +private: + + // Components which are being customized. + TArray> HoudiniAssetComponents; + + // Structure holding the output's details + TSharedPtr OutputDetails; + + // Structure holding the parameter's details + TSharedPtr ParameterDetails; + + // Structure holding the PDG Asset Link's details + TSharedPtr PDGDetails; + + // Structure holding the HoudiniAsset details + TSharedPtr HoudiniEngineDetails; + + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp index 56db7a3db..4b1961337 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp @@ -1,209 +1,209 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniAsset.h" - -#include "EditorFramework/AssetImportData.h" -#include "Misc/FileHelper.h" -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniAssetFactory::UHoudiniAssetFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - // This factory is responsible for manufacturing HoudiniEngine assets. - SupportedClass = UHoudiniAsset::StaticClass(); - - // This factory does not manufacture new objects from scratch. - bCreateNew = false; - - // This factory will not open the editor for each new object. - bEditAfterNew = false; - - // This factory will import objects from files. - bEditorImport = true; - - // Factory does not import objects from text. - bText = false; - - // Add supported formats. - Formats.Add(TEXT("otl;Houdini Engine Asset")); - Formats.Add(TEXT("otllc;Houdini Engine Limited Commercial Asset")); - Formats.Add(TEXT("otlnc;Houdini Engine Non-Commercial Asset")); - Formats.Add(TEXT("hda;Houdini Engine Asset")); - Formats.Add(TEXT("hdalc;Houdini Engine Limited Commercial Asset")); - Formats.Add(TEXT("hdanc;Houdini Engine Non-Commercial Asset")); - Formats.Add(TEXT("hdalibrary;Houdini Engine Expanded Asset")); -} - -bool -UHoudiniAssetFactory::DoesSupportClass(UClass * Class) -{ - return Class == SupportedClass; -} - -FText -UHoudiniAssetFactory::GetDisplayName() const -{ - return LOCTEXT("HoudiniAssetFactoryDescription", "Houdini Engine Asset"); -} - -UObject * -UHoudiniAssetFactory::FactoryCreateBinary( - UClass * InClass, UObject* InParent, FName InName, EObjectFlags Flags, - UObject * Context, const TCHAR * Type, const uint8 *& Buffer, - const uint8 * BufferEnd, FFeedbackContext * Warn ) -{ - // Broadcast notification that a new asset is being imported. - GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type); - - // Create a new asset. - UHoudiniAsset * HoudiniAsset = NewObject< UHoudiniAsset >(InParent, InName, Flags); - HoudiniAsset->CreateAsset(Buffer, BufferEnd, UFactory::GetCurrentFilename()); - - // Create reimport information. - UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(HoudiniAsset, UAssetImportData::StaticClass()); - HoudiniAsset->AssetImportData = AssetImportData; - } - - AssetImportData->Update(UFactory::GetCurrentFilename()); - - // Broadcast notification that the new asset has been imported. - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, HoudiniAsset); - - return HoudiniAsset; -} - -UObject* -UHoudiniAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) -{ - // "houdini.hdalibrary" files (expanded hda / hda folder) need a special treatment, - // but ".hda" files can be loaded normally - FString FileExtension = FPaths::GetExtension(Filename); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) != 0) - return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); - - // Make sure the file name is sections.list - FString NameOfFile = FPaths::GetBaseFilename(Filename); - if (NameOfFile.Compare(TEXT("houdini"), ESearchCase::IgnoreCase) != 0) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); - return nullptr; - } - - // Make sure that the proper .list file is loaded - FString PathToFile = FPaths::GetPath(Filename); - if (PathToFile.Find(TEXT(".hda")) != (PathToFile.Len() - 4)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); - return nullptr; - } - - FString NewFilename = PathToFile; - FString NewFileNameNoHDA = FPaths::GetBaseFilename(PathToFile); - FName NewIname = FName(*NewFileNameNoHDA); - FString NewFileExtension = FPaths::GetExtension(NewFilename); - - // load as binary - TArray Data; - if (!FFileHelper::LoadFileToArray(Data, *Filename)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s' to array"), *Filename); - return nullptr; - } - - Data.Add(0); - ParseParms(Parms); - const uint8* Ptr = &Data[0]; - - return FactoryCreateBinary(InClass, InParent, NewIname, Flags, nullptr, *NewFileExtension, Ptr, Ptr + Data.Num() - 1, Warn); -} - -bool -UHoudiniAssetFactory::CanReimport(UObject * Obj, TArray< FString > & OutFilenames) -{ - UHoudiniAsset * HoudiniAsset = Cast(Obj); - if (HoudiniAsset) - { - UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; - if (AssetImportData) - OutFilenames.Add(AssetImportData->GetFirstFilename()); - else - OutFilenames.Add(TEXT("")); - - return true; - } - - return false; -} - -void -UHoudiniAssetFactory::SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) -{ - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); - if (HoudiniAsset && (1 == NewReimportPaths.Num())) - HoudiniAsset->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); -} - -EReimportResult::Type -UHoudiniAssetFactory::Reimport(UObject * Obj) -{ - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); - if (HoudiniAsset && HoudiniAsset->AssetImportData) - { - // Make sure file is valid and exists. - const FString & Filename = HoudiniAsset->AssetImportData->GetFirstFilename(); - - if (!Filename.Len() || IFileManager::Get().FileSize(*Filename) == INDEX_NONE) - return EReimportResult::Failed; - - if (UFactory::StaticImportObject( - HoudiniAsset->GetClass(), HoudiniAsset->GetOuter(), *HoudiniAsset->GetName(), - RF_Public | RF_Standalone, *Filename, NULL, this)) - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimported successfully.")); - - if (HoudiniAsset->GetOuter()) - HoudiniAsset->GetOuter()->MarkPackageDirty(); - else - HoudiniAsset->MarkPackageDirty(); - - return EReimportResult::Succeeded; - } - } - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); - return EReimportResult::Failed; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniAsset.h" + +#include "EditorFramework/AssetImportData.h" +#include "Misc/FileHelper.h" +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetFactory::UHoudiniAssetFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + // This factory is responsible for manufacturing HoudiniEngine assets. + SupportedClass = UHoudiniAsset::StaticClass(); + + // This factory does not manufacture new objects from scratch. + bCreateNew = false; + + // This factory will not open the editor for each new object. + bEditAfterNew = false; + + // This factory will import objects from files. + bEditorImport = true; + + // Factory does not import objects from text. + bText = false; + + // Add supported formats. + Formats.Add(TEXT("otl;Houdini Engine Asset")); + Formats.Add(TEXT("otllc;Houdini Engine Limited Commercial Asset")); + Formats.Add(TEXT("otlnc;Houdini Engine Non-Commercial Asset")); + Formats.Add(TEXT("hda;Houdini Engine Asset")); + Formats.Add(TEXT("hdalc;Houdini Engine Limited Commercial Asset")); + Formats.Add(TEXT("hdanc;Houdini Engine Non-Commercial Asset")); + Formats.Add(TEXT("hdalibrary;Houdini Engine Expanded Asset")); +} + +bool +UHoudiniAssetFactory::DoesSupportClass(UClass * Class) +{ + return Class == SupportedClass; +} + +FText +UHoudiniAssetFactory::GetDisplayName() const +{ + return LOCTEXT("HoudiniAssetFactoryDescription", "Houdini Engine Asset"); +} + +UObject * +UHoudiniAssetFactory::FactoryCreateBinary( + UClass * InClass, UObject* InParent, FName InName, EObjectFlags Flags, + UObject * Context, const TCHAR * Type, const uint8 *& Buffer, + const uint8 * BufferEnd, FFeedbackContext * Warn ) +{ + // Broadcast notification that a new asset is being imported. + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type); + + // Create a new asset. + UHoudiniAsset * HoudiniAsset = NewObject< UHoudiniAsset >(InParent, InName, Flags); + HoudiniAsset->CreateAsset(Buffer, BufferEnd, UFactory::GetCurrentFilename()); + + // Create reimport information. + UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(HoudiniAsset, UAssetImportData::StaticClass()); + HoudiniAsset->AssetImportData = AssetImportData; + } + + AssetImportData->Update(UFactory::GetCurrentFilename()); + + // Broadcast notification that the new asset has been imported. + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, HoudiniAsset); + + return HoudiniAsset; +} + +UObject* +UHoudiniAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) +{ + // "houdini.hdalibrary" files (expanded hda / hda folder) need a special treatment, + // but ".hda" files can be loaded normally + FString FileExtension = FPaths::GetExtension(Filename); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) != 0) + return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); + + // Make sure the file name is sections.list + FString NameOfFile = FPaths::GetBaseFilename(Filename); + if (NameOfFile.Compare(TEXT("houdini"), ESearchCase::IgnoreCase) != 0) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); + return nullptr; + } + + // Make sure that the proper .list file is loaded + FString PathToFile = FPaths::GetPath(Filename); + if (PathToFile.Find(TEXT(".hda")) != (PathToFile.Len() - 4)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); + return nullptr; + } + + FString NewFilename = PathToFile; + FString NewFileNameNoHDA = FPaths::GetBaseFilename(PathToFile); + FName NewIname = FName(*NewFileNameNoHDA); + FString NewFileExtension = FPaths::GetExtension(NewFilename); + + // load as binary + TArray Data; + if (!FFileHelper::LoadFileToArray(Data, *Filename)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s' to array"), *Filename); + return nullptr; + } + + Data.Add(0); + ParseParms(Parms); + const uint8* Ptr = &Data[0]; + + return FactoryCreateBinary(InClass, InParent, NewIname, Flags, nullptr, *NewFileExtension, Ptr, Ptr + Data.Num() - 1, Warn); +} + +bool +UHoudiniAssetFactory::CanReimport(UObject * Obj, TArray< FString > & OutFilenames) +{ + UHoudiniAsset * HoudiniAsset = Cast(Obj); + if (HoudiniAsset) + { + UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; + if (AssetImportData) + OutFilenames.Add(AssetImportData->GetFirstFilename()); + else + OutFilenames.Add(TEXT("")); + + return true; + } + + return false; +} + +void +UHoudiniAssetFactory::SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); + if (HoudiniAsset && (1 == NewReimportPaths.Num())) + HoudiniAsset->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); +} + +EReimportResult::Type +UHoudiniAssetFactory::Reimport(UObject * Obj) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); + if (HoudiniAsset && HoudiniAsset->AssetImportData) + { + // Make sure file is valid and exists. + const FString & Filename = HoudiniAsset->AssetImportData->GetFirstFilename(); + + if (!Filename.Len() || IFileManager::Get().FileSize(*Filename) == INDEX_NONE) + return EReimportResult::Failed; + + if (UFactory::StaticImportObject( + HoudiniAsset->GetClass(), HoudiniAsset->GetOuter(), *HoudiniAsset->GetName(), + RF_Public | RF_Standalone, *Filename, NULL, this)) + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimported successfully.")); + + if (HoudiniAsset->GetOuter()) + HoudiniAsset->GetOuter()->MarkPackageDirty(); + else + HoudiniAsset->MarkPackageDirty(); + + return EReimportResult::Succeeded; + } + } + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); + return EReimportResult::Failed; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h index aa64b17af..c0c1cf333 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "EditorReimportHandler.h" -#include "Factories/Factory.h" -#include "HoudiniAssetFactory.generated.h" - -class UClass; -class UObject; -class FFeedbackContext; - -UCLASS(config = Editor) -class UHoudiniAssetFactory : public UFactory, public FReimportHandler -{ - GENERATED_UCLASS_BODY() - - public: - - // UFactory methods. - // return true if it supports this class - virtual bool DoesSupportClass(UClass * Class) override; - - // Returns the name of the factory for menus - virtual FText GetDisplayName() const override; - - // Create a new object by importing it from a binary buffer. - virtual UObject * FactoryCreateBinary( - UClass * InClass, UObject * InParent, FName InName, EObjectFlags Flags, - UObject * Context, const TCHAR * Type, const uint8 *& Buffer, const uint8 * BufferEnd, - FFeedbackContext * Warn) override; - - // Create a new object by importing it from a file name. - virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, - EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, - FFeedbackContext* Warn, bool& bOutOperationCanceled) override; - - // FReimportHandler methods. - // Check to see if we have a handler to manage the reimporting of the object - virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; - - // Sets the reimport path(s) for the specified object - virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; - - // Attempt to reimport the specified object from its source - virtual EReimportResult::Type Reimport(UObject * Obj) override; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "EditorReimportHandler.h" +#include "Factories/Factory.h" +#include "HoudiniAssetFactory.generated.h" + +class UClass; +class UObject; +class FFeedbackContext; + +UCLASS(config = Editor) +class UHoudiniAssetFactory : public UFactory, public FReimportHandler +{ + GENERATED_UCLASS_BODY() + + public: + + // UFactory methods. + // return true if it supports this class + virtual bool DoesSupportClass(UClass * Class) override; + + // Returns the name of the factory for menus + virtual FText GetDisplayName() const override; + + // Create a new object by importing it from a binary buffer. + virtual UObject * FactoryCreateBinary( + UClass * InClass, UObject * InParent, FName InName, EObjectFlags Flags, + UObject * Context, const TCHAR * Type, const uint8 *& Buffer, const uint8 * BufferEnd, + FFeedbackContext * Warn) override; + + // Create a new object by importing it from a file name. + virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, + EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, + FFeedbackContext* Warn, bool& bOutOperationCanceled) override; + + // FReimportHandler methods. + // Check to see if we have a handler to manage the reimporting of the object + virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; + + // Sets the reimport path(s) for the specified object + virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; + + // Attempt to reimport the specified object from its source + virtual EReimportResult::Type Reimport(UObject * Obj) override; +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp index d05322a6f..3ee39bf1c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp @@ -1,5209 +1,5210 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineBakeUtils.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngineUtils.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutput.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineUtils.h" -#include "UnrealLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniStringResolver.h" -#include "HoudiniEngineCommands.h" - -#include "Engine/StaticMesh.h" -#include "Engine/World.h" -#include "RawMesh.h" -#include "UObject/Package.h" -#include "PackageTools.h" -#include "UObject/MetaData.h" -#include "AssetRegistryModule.h" -#include "Materials/Material.h" -#include "LandscapeProxy.h" -#include "LandscapeStreamingProxy.h" -#include "LandscapeInfo.h" -#include "Factories/WorldFactory.h" -#include "AssetToolsModule.h" -#include "InstancedFoliageActor.h" -#include "Components/SplineComponent.h" -#include "GameFramework/Actor.h" -#include "Engine/StaticMeshActor.h" -#include "Components/StaticMeshComponent.h" -#include "PhysicsEngine/BodySetup.h" -#include "ActorFactories/ActorFactoryStaticMesh.h" -#include "ActorFactories/ActorFactoryEmptyActor.h" -#include "BusyCursor.h" -#include "Editor.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "FileHelpers.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngine.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "Editor/EditorEngine.h" -#include "Factories/BlueprintFactory.h" -#include "Engine/SimpleConstructionScript.h" -#include "Misc/Paths.h" -#include "HAL/FileManager.h" -#include "LandscapeEdit.h" -#include "Containers/UnrealString.h" -#include "Components/AudioComponent.h" -#include "Engine/WorldComposition.h" -#include "Kismet2/BlueprintEditorUtils.h" -#include "MaterialEditor/Public/MaterialEditingLibrary.h" -#include "MaterialGraph/MaterialGraph.h" -#include "Particles/ParticleSystemComponent.h" -#include "Sound/SoundBase.h" -#include "UObject/UnrealType.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() - : Actor(nullptr) - , OutputIndex(INDEX_NONE) - , OutputObjectIdentifier() - , ActorBakeName(NAME_None) - , BakedObject(nullptr) - , SourceObject(nullptr) -{ -} - -FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( - AActor* InActor, - FName InActorBakeName, - FName InWorldOutlinerFolder, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - UObject* InBakedObject, - UObject* InSourceObject) - : Actor(InActor) - , OutputIndex(InOutputIndex) - , OutputObjectIdentifier(InOutputObjectIdentifier) - , ActorBakeName(InActorBakeName) - , WorldOutlinerFolder(InWorldOutlinerFolder) - , BakedObject(InBakedObject) - , SourceObject(InSourceObject) -{ -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - UHoudiniAssetComponent* InHACToBake, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess) -{ - if (!IsValid(InHACToBake)) - return false; - - // Handle proxies: if the output has any current proxies, first refine them - bool bHACNeedsToReCook; - if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bHACNeedsToReCook)) - { - // Either the component is invalid, or needs a recook to refine a proxy mesh - return false; - } - - bool bSuccess = false; - switch (InBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake); - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake); - } - break; - - case EHoudiniEngineBakeOption::ToFoliage: - { - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake); - } - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - { - //Todo - bSuccess = false; - } - break; - - } - - if (bSuccess && bInRemoveHACOutputOnSuccess) - FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - TArray NewActors; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - - if (!FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats)) - { - // TODO ? - HOUDINI_LOG_WARNING(TEXT("Errors when baking")); - } - - // Save the created packages - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Recenter and select the baked actors - if (GEditor && NewActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : NewActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (HoudiniAssetComponent->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && NewActors.Num() > 0) - GEditor->NoteSelectionChange(); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!IsValid(OwnerActor)) - return false; - - const FString HoudiniAssetName = OwnerActor->GetName(); - - // Get an array of the outputs - const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); - TArray Outputs; - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - Outputs.Add(HoudiniAssetComponent->GetOutputAt(OutputIdx)); - } - - // Get the previous bake objects and grow/shrink to match asset outputs - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - // Ensure we have an entry for each output - if (BakedOutputs.Num() != NumOutputs) - BakedOutputs.SetNum(NumOutputs); - - return BakeHoudiniOutputsToActors( - Outputs, - BakedOutputs, - HoudiniAssetName, - HoudiniAssetComponent->GetComponentTransform(), - HoudiniAssetComponent->BakeFolder, - HoudiniAssetComponent->TemporaryCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutNewActors, - OutPackagesToSave, - OutBakeStats, - InOutputTypesToBake, - InInstancerComponentTypesToBake, - InFallbackActor, - InFallbackWorldOutlinerFolder); -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( - const TArray& InOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FTransform& InParentTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - const int32 NumOutputs = InOutputs.Num(); - - const FString MsgTemplate = TEXT("Baking output: {0}/{1}."); - FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs }); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg)); - - TArray BakedActors; - - // First bake everything except instancers, then bake instancers. Since instancers might use meshes in - // from the other outputs. - bool bHasAnyInstancers = false; - int32 NumProcessedOutputs = 0; - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) - { - NumProcessedOutputs++; - continue; - } - - Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); - - const EHoudiniOutputType OutputType = Output->GetType(); - // Check if we should skip this output type - if (InOutputTypesToBake && InOutputTypesToBake->Find(OutputType) == INDEX_NONE) - { - NumProcessedOutputs++; - continue; - } - - switch (OutputType) - { - case EHoudiniOutputType::Mesh: - { - FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( - OutputIdx, - InOutputs, - InBakedOutputs, - InHoudiniAssetName, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - break; - - case EHoudiniOutputType::Instancer: - { - if (!bHasAnyInstancers) - bHasAnyInstancers = true; - NumProcessedOutputs--; - } - break; - - case EHoudiniOutputType::Landscape: - { - UHoudiniAssetComponent* HAC = Cast(Output->GetOuter()); - if (IsValid(HAC)) - { - // UWorld* WorldContext = Output->GetWorld(); - const bool bResult = BakeLandscape( - OutputIdx, - Output, - InBakedOutputs[OutputIdx].BakedOutputObjects, - bInReplaceActors, - bInReplaceAssets, - InBakeFolder.Path, - InHoudiniAssetName, - OutBakeStats); - } - } - break; - - case EHoudiniOutputType::Skeletal: - break; - - case EHoudiniOutputType::Curve: - { - FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - Output, - InBakedOutputs[OutputIdx].BakedOutputObjects, - InBakedOutputs, - InHoudiniAssetName, - InBakeFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - break; - - case EHoudiniOutputType::Invalid: - break; - } - - NumProcessedOutputs++; - } - - if (bHasAnyInstancers) - { - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) - { - NumProcessedOutputs++; - continue; - } - - Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); - - if (Output->GetType() == EHoudiniOutputType::Instancer) - { - FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( - OutputIdx, - InOutputs, - InBakedOutputs, - InParentTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - OutPackagesToSave, - InInstancerComponentTypesToBake, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - - NumProcessedOutputs++; - } - } - - OutNewActors.Append(BakedActors); - - return true; -} - - -bool -FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n) - { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n); - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Instancer) - continue; - - if (Output->GetInstancedOutputs().Num() > 0) - return true; - /* - // TODO: Is this needed? check we have components to bake? - for (auto& OutputObjectPair : Output->GetOutputObjects()) - { - if (OutputObjectPair.Value.OutputCompoent!= nullpt) - return true; - } - */ - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor * OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return false; - - int32 BakedCount = 0; - TArray PackagesToSave; - - FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); - - // Build an array of the outputs so that we can search for meshes/previous baked meshes - const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); - TArray Outputs; - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); - if (!Output || Output->IsPendingKill()) - continue; - - Outputs.Add(Output); - } - - // Get the previous bake outputs and match the output array size - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - if (BakedOutputs.Num() != NumOutputs) - BakedOutputs.SetNum(NumOutputs); - - // Map storing original and baked Static Meshes - TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Instancer) - continue; - - // TODO: No need to use the instanced outputs for this - // We should simply iterate on the Output Objects instead! - TMap& OutputObjects = Output->GetOutputObjects(); - TMap& InstancedOutputs = Output->GetInstancedOutputs(); - for (auto & Pair : InstancedOutputs) - { - FString InstanceName = OwnerActor->GetName(); - - // // See if we have a bake name for that output - // FHoudiniOutputObject* OutputObj = OutputObjects.Find(Pair.Key); - // if (OutputObj && OutputObj->BakeName.IsEmpty()) - // InstanceName = OutputObj->BakeName; - - FHoudiniInstancedOutput& InstancedOutput = Pair.Value; - for (int32 VariarionIdx = 0; VariarionIdx < InstancedOutput.VariationObjects.Num(); ++VariarionIdx) - { - // TODO: !!! what if the instanced object/var is not a static mesh!!!!!! - UObject* CurrentVariationObject = InstancedOutput.VariationObjects[VariarionIdx].Get(); - UStaticMesh* InstancedStaticMesh = Cast(CurrentVariationObject); - if (!InstancedStaticMesh) - { - if (CurrentVariationObject) - { - HOUDINI_LOG_ERROR(TEXT("Failed to bake the instances of %s to Foliage"), *CurrentVariationObject->GetName()); - } - continue; - } - - // Check if we have already handled this mesh (already baked it from a previous variation), if so, - // use that - UStaticMesh* OutStaticMesh = nullptr; - bool bCreateNewType = true; - if (OriginalToBakedMesh.Contains(InstancedStaticMesh)) - { - OutStaticMesh = OriginalToBakedMesh.FindChecked(InstancedStaticMesh); - bCreateNewType = false; - } - - if (!IsValid(OutStaticMesh)) - { - // Find the output object and identifier for the mesh and previous bake of the mesh (if it exists) - FString ObjectName; - int32 MeshOutputIdx = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshOutputIdentifier; - UStaticMesh* PreviousBakeMesh = nullptr; - FHoudiniBakedOutputObject* BakedOutputObject = nullptr; - if (FindOutputObject(InstancedStaticMesh, Outputs, MeshOutputIdx, MeshOutputIdentifier)) - { - GetTemporaryOutputObjectBakeName(InstancedStaticMesh, Outputs, ObjectName); - - BakedOutputObject = &BakedOutputs[MeshOutputIdx].BakedOutputObjects.FindOrAdd(MeshOutputIdentifier); - if (BakedOutputObject) - { - PreviousBakeMesh = Cast(BakedOutputObject->GetBakedObjectIfValid()); - } - } - else - { - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); - } - - // If the instanced static mesh is still a temporary Houdini created Static Mesh - // we will duplicate/bake it first before baking to foliage - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - MeshOutputIdentifier, - HoudiniAssetComponent->BakeFolder.Path, - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // DuplicateStaticMeshAndCreatePackageIfNeeded uses baked results to find a baked version of - // InstancedStaticMesh in the current bake results, but since we are already using - // OriginalToBakedMesh we don't have to populate BakedResults - const TArray BakedResults; - OutStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - InstancedStaticMesh, - PreviousBakeMesh, - PackageParams, - Outputs, - BakedResults, - HoudiniAssetComponent->TemporaryCookFolder.Path, - PackagesToSave); - OriginalToBakedMesh.Add(InstancedStaticMesh, OutStaticMesh); - - // Update our tracked baked output - if (BakedOutputObject) - BakedOutputObject->BakedObject = FSoftObjectPath(OutStaticMesh).ToString(); - - bCreateNewType = true; - } - - // See if we already have a FoliageType for that static mesh - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(OutStaticMesh); - if (!FoliageType || FoliageType->IsPendingKill()) - { - // We need to create a new FoliageType for this Static Mesh - // TODO: Add foliage default settings - InstancedFoliageActor->AddMesh(OutStaticMesh, &FoliageType); - bCreateNewType = true; - } - - // If we are baking in replace mode, remove the foliage type if it already exists - // and a create a new one - if (bInReplaceAssets && bCreateNewType && IsValid(FoliageType)) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it - FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); - if (!FoliageInfo) - continue; - - // Apply the transform offset on the transforms for this variation - TArray ProcessedTransforms; - FHoudiniInstanceTranslator::ProcessInstanceTransforms(InstancedOutput, VariarionIdx, ProcessedTransforms); - - FFoliageInstance FoliageInstance; - int32 CurrentInstanceCount = 0; - for (auto CurrentTransform : ProcessedTransforms) - { - FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); - FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - CurrentInstanceCount++; - } - - // TODO: This was due to a bug in UE4.22-20, check if still needed! - if (FoliageInfo->GetComponent()) - FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); - - // Notify the user that we succesfully bake the instances to foliage - FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + OutStaticMesh->GetName() + TEXT(" to Foliage"); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - BakedCount += ProcessedTransforms.Num(); - } - } - } - - InstancedFoliageActor->RegisterAllComponents(); - - // Update / repopulate the foliage editor mode's mesh list - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - - if (BakedCount > 0) - { - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - return true; - } - - return false; -} - - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) - return false; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - - // Ensure we have the same number of baked outputs and asset outputs - if (InBakedOutputs.Num() != InAllOutputs.Num()) - InBakedOutputs.SetNum(InAllOutputs.Num()); - - // Iterate on the output objects, baking their object/component as we go - for (auto& Pair : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = Pair.Value; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputs[InOutputIndex].BakedOutputObjects.FindOrAdd(Pair.Key); - - if (CurrentOutputObject.bProxyIsCurrent) - { - // TODO: we need to refine the SM first! - // ?? - } - - if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill()) - continue; - - if (CurrentOutputObject.OutputComponent->IsA()) - { - // TODO: Baking foliage instancer to actors it not supported currently - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent))) - { - BakeInstancerOutputToActors_ISMC( - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent))) - { - BakeInstancerOutputToActors_IAC( - InOutputIndex, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InBakeFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) - { - BakeInstancerOutputToActors_MSIC( - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) - { - BakeInstancerOutputToActors_SMC( - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else - { - // Unsupported component! - } - - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UInstancedStaticMeshComponent * InISMC = Cast(InOutputObject.OutputComponent); - if (!InISMC || InISMC->IsPendingKill()) - return false; - - AActor * OwnerActor = InISMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh * StaticMesh = InISMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) - { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - } - - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // ObjectName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, PackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); - - // Update the baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a new level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if(!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - /* - // TODO: Get the bake name! - // Bake override, the output name - // The bake name override has priority - FString InstancerName = InOutputObject.BakeName; - if (InstancerName.IsEmpty()) - { - // .. then use the output name - InstancerName = Resolver.ResolveOutputName(); - } - */ - - // Should we create one actor with an ISMC or multiple actors with one SMC? - bool bSpawnMultipleSMC = false; - if (bSpawnMultipleSMC) - { - // TODO: Double check, Has a crash here! - - // Get the StaticMesh ActorFactory - UActorFactory* SMFactory = nullptr; - - if (!FoundActor) - { - SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!SMFactory) - return false; - } - - // Split the instances to multiple StaticMeshActors - for (int32 InstanceIdx = 0; InstanceIdx < InISMC->GetInstanceCount(); InstanceIdx++) - { - FTransform InstanceTransform; - InISMC->GetInstanceTransform(InstanceIdx, InstanceTransform, true); - - if (!FoundActor) - { - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform, RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) - continue; - } - - FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName, FoundActor); - // FoundActor->Rename(*NewName.ToString()); - // FoundActor->SetActorLabel(NewName.ToString()); - RenameAndRelabelActor(FoundActor, NewName.ToString(), false); - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - continue; - - // Copy properties from the existing component - CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC); - - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh)); - } - } - else - { - bool bSpawnedActor = false; - if (!FoundActor) - { - // Only create one actor - FActorSpawnParameters SpawnInfo; - SpawnInfo.OverrideLevel = DesiredLevel; - SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); - SpawnInfo.bDeferConstruction = true; - - // Spawn the new Actor - FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - bSpawnedActor = true; - - FoundActor->SetActorLabel(FoundActor->GetName()); - FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); - } - else - { - // If there is a previously baked component, and we are in replace mode, remove it - if (bInReplaceAssets) - { - USceneComponent* InPrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(InPrevComponent) && InPrevComponent->GetOwner() == FoundActor) - RemovePreviouslyBakedComponent(InPrevComponent); - } - - const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); - } - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Get/create the actor's root component - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - if (bSpawnedActor && IsValid(RootComponent)) - RootComponent->SetWorldTransform(InTransform); - - // Duplicate the instancer component, create a Hierarchical ISMC if needed - UInstancedStaticMeshComponent* NewISMC = nullptr; - UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast(InISMC); - if (InHISMC) - { - NewISMC = DuplicateObject( - InHISMC, - FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetFName())); - } - else - { - NewISMC = DuplicateObject( - InISMC, - FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetFName())); - } - - if (!NewISMC) - { - //DesiredLevel->OwningWorld-> - return false; - } - - InBakedOutputObject.BakedComponent = FSoftObjectPath(NewISMC).ToString(); - - NewISMC->RegisterComponent(); - // NewISMC->SetupAttachment(nullptr); - NewISMC->SetStaticMesh(BakedStaticMesh); - FoundActor->AddInstanceComponent(NewISMC); - // NewActor->SetRootComponent(NewISMC); - if (IsValid(RootComponent)) - NewISMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - NewISMC->SetWorldTransform(InISMC->GetComponentTransform()); - - // TODO: do we need to copy properties here, we duplicated the component - // // Copy properties from the existing component - // CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); - - if (bSpawnedActor) - FoundActor->FinishSpawning(InTransform); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh)); - - FoundActor->InvalidateLightingCache(); - FoundActor->PostEditMove(true); - FoundActor->MarkPackageDirty(); - } - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UStaticMeshComponent* InSMC = Cast(InOutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) - return false; - - AActor* OwnerActor = InSMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh* StaticMesh = InSMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) - { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - } - - // BaseName holds the Actor / HDA name - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, - OutActors, InTempCookFolder.Path, OutPackagesToSave); - - // Update the previous baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - UStaticMeshComponent* StaticMeshComponent = nullptr; - // Create an actor if we didn't find one - if (!FoundActor) - { - // Get the StaticMesh ActorFactory - UActorFactory* SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!SMFactory) - return false; - - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - return false; - - StaticMeshComponent = SMActor->GetStaticMeshComponent(); - } - else - { - USceneComponent* RootComponent = GetActorRootComponent(FoundActor); - if (!IsValid(RootComponent)) - return false; - - if (bInReplaceAssets) - { - // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it - UStaticMeshComponent* PrevSMC = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) - { - StaticMeshComponent = PrevSMC; - } - } - - if (!IsValid(StaticMeshComponent)) - { - // Create a new static mesh component - StaticMeshComponent = NewObject(FoundActor, NAME_None, RF_Transactional); - - FoundActor->AddInstanceComponent(StaticMeshComponent); - StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - StaticMeshComponent->RegisterComponent(); - } - } - - FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName, FoundActor); - // FoundActor->Rename(*NewName.ToString()); - // FoundActor->SetActorLabel(NewName.ToString()); - RenameAndRelabelActor(FoundActor, NewName.ToString(), false); - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Update the previous baked component - InBakedOutputObject.BakedComponent = FSoftObjectPath(StaticMeshComponent).ToString(); - - if (!IsValid(StaticMeshComponent)) - return false; - - // Copy properties from the existing component - CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC); - StaticMeshComponent->SetStaticMesh(BakedStaticMesh); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh)); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave) -{ - UHoudiniInstancedActorComponent* InIAC = Cast(InOutputObject.OutputComponent); - if (!InIAC || InIAC->IsPendingKill()) - return false; - - AActor * OwnerActor = InIAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - // BaseName holds the Actor / HDA name - const FName BaseName = FName(OwnerActor->GetName()); - - // Get the object instanced by this IAC - UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - return false; - - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - BaseName.ToString(), - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // If we are baking in actor replacement mode, remove any previously baked instanced actors for this output - if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0) - { - UWorld* LevelWorld = DesiredLevel->GetWorld(); - if (IsValid(LevelWorld)) - { - for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) - { - const FSoftObjectPath ActorPath(ActorPathStr); - - if (!ActorPath.IsValid()) - continue; - - AActor* Actor = Cast(ActorPath.TryLoad()); - // Destroy Actor if it is valid and part of DesiredLevel - if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel) - { -#if WITH_EDITOR - LevelWorld->EditorDestroyActor(Actor, true); -#else - LevelWorld->DestroyActor(Actor); -#endif - } - } - } - } - - // Empty and reserve enough space for new instanced actors - InBakedOutputObject.InstancedActors.Empty(InIAC->GetInstancedActors().Num()); - - // Iterates on all the instances of the IAC - for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors()) - { - if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) - continue; - - FName NewInstanceName = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName); - FString NewNameStr = NewInstanceName.ToString(); - - FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); - AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); - if (!NewActor || NewActor->IsPendingKill()) - continue; - - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); - - NewActor->SetActorLabel(NewNameStr); - SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); - NewActor->SetActorTransform(CurrentTransform); - - InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); - - OutActors.Add(FHoudiniEngineBakedActor( - NewActor, - BaseName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - nullptr, - InstancedObject)); - } - - // TODO: - // Move Actors to DesiredLevel if needed?? - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = true; - const bool bInDestroyBakedInstancedActors = false; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UHoudiniMeshSplitInstancerComponent * InMSIC = Cast(InOutputObject.OutputComponent); - if (!InMSIC || InMSIC->IsPendingKill()) - return false; - - AActor * OwnerActor = InMSIC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh * StaticMesh = InMSIC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) - { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - } - - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, - OutActors, InTempCookFolder.Path, OutPackagesToSave); - - // Update the baked output - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Get the level specified by attribute - // Access some of the attributes that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - bool bSpawnedActor = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - if (!FoundActor) - { - // This is a split mesh instancer component - we will create a generic AActor with a bunch of SMC - FActorSpawnParameters SpawnInfo; - SpawnInfo.OverrideLevel = DesiredLevel; - SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); - SpawnInfo.bDeferConstruction = true; - - // Spawn the new Actor - FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - bSpawnedActor = true; - - FoundActor->SetActorLabel(FoundActor->GetName()); - FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); - } - else - { - // If we are baking in replacement mode, remove the previous components (if they belong to FoundActor) - for (const FString& PrevComponentPathStr : InBakedOutputObject.InstancedComponents) - { - const FSoftObjectPath PrevComponentPath(PrevComponentPathStr); - - if (!PrevComponentPath.IsValid()) - continue; - - UActorComponent* PrevComponent = Cast(PrevComponentPath.TryLoad()); - if (!IsValid(PrevComponent) || PrevComponent->GetOwner() != FoundActor) - continue; - - RemovePreviouslyBakedComponent(PrevComponent); - } - - const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); - } - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Get/create the actor's root component - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - if (bSpawnedActor && IsValid(RootComponent)) - RootComponent->SetWorldTransform(InTransform); - - // Empty and reserve enough space in the baked components array for the new components - InBakedOutputObject.InstancedComponents.Empty(InMSIC->GetInstances().Num()); - - // Now add s SMC component for each of the SMC's instance - for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances()) - { - if (!CurrentSMC || CurrentSMC->IsPendingKill()) - continue; - - UStaticMeshComponent* NewSMC = DuplicateObject( - CurrentSMC, - FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetFName())); - if (!NewSMC || NewSMC->IsPendingKill()) - continue; - - InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString()); - - NewSMC->RegisterComponent(); - // NewSMC->SetupAttachment(nullptr); - NewSMC->SetStaticMesh(BakedStaticMesh); - FoundActor->AddInstanceComponent(NewSMC); - NewSMC->SetWorldTransform(CurrentSMC->GetComponentTransform()); - if (IsValid(RootComponent)) - NewSMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); - - // TODO: Do we need to copy properties here, we duplicated the component - // // Copy properties from the existing component - // CopyPropertyToNewActorAndComponent(FoundActor, NewSMC, CurrentSMC); - } - - if (bSpawnedActor) - FoundActor->FinishSpawning(InTransform); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh)); - - FoundActor->InvalidateLightingCache(); - FoundActor->PostEditMove(true); - FoundActor->MarkPackageDirty(); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = true; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = false; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::FindHGPO( - const FHoudiniOutputObjectIdentifier& InIdentifier, - const TArray& InHGPOs, - FHoudiniGeoPartObject const*& OutHGPO) -{ - // Find the HGPO that matches this output identifier - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - for (auto & NextHGPO : InHGPOs) - { - // We use Matches() here as it handles the case where the HDA was loaded, - // which likely means that the the obj/geo/part ids dont match the output identifier - if(InIdentifier.Matches(NextHGPO)) - { - FoundHGPO = &NextHGPO; - break; - } - } - - OutHGPO = FoundHGPO; - return !OutHGPO; -} - -void -FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const FHoudiniOutputObject& InMeshOutputObject, - FString& OutBakeName) -{ - // The bake name override has priority - OutBakeName = InMeshOutputObject.BakeName; - if (OutBakeName.IsEmpty()) - { - FHoudiniAttributeResolver Resolver; - Resolver.SetCachedAttributes(InMeshOutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(InMeshOutputObject.CachedTokens); - const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InObject); - // The default output name (if not set via attributes) is {object_name}, which look for an object_name - // key-value token - if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) - Resolver.SetToken(TEXT("object_name"), DefaultObjectName); - OutBakeName = Resolver.ResolveOutputName(); - // const TArray& HGPOs = InAllOutputs[MeshOutputIdx]->GetHoudiniGeoPartObjects(); - // const FHoudiniGeoPartObject* FoundHGPO = nullptr; - // FindHGPO(MeshIdentifier, HGPOs, FoundHGPO); - // // ... finally the part name - // if (FoundHGPO && FoundHGPO->bHasCustomPartName) - // OutBakeName = FoundHGPO->PartName; - if (OutBakeName.IsEmpty()) - OutBakeName = DefaultObjectName; - } -} - -bool -FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const TArray& InAllOutputs, - FString& OutBakeName) -{ - if (!IsValid(InObject)) - return false; - - OutBakeName.Empty(); - - int32 MeshOutputIdx = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - if (FindOutputObject(InObject, InAllOutputs, MeshOutputIdx, MeshIdentifier)) - { - // Found the mesh, get its name - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier); - GetTemporaryOutputObjectBakeName(InObject, MeshOutputObject, OutBakeName); - - return true; - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) - return false; - - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!Factory) - return false; - - TMap& OutputObjects = InOutput->GetOutputObjects(); -const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); - - // Get the previous bake objects - if (InOutputIndex >= 0 && !InBakedOutputs.IsValidIndex(InOutputIndex)) - InBakedOutputs.SetNum(InOutputIndex + 1); - TMap& BakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; - - for (auto& Pair : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - const FHoudiniOutputObject& OutputObject = Pair.Value; - - // Fetch previous bake output - FHoudiniBakedOutputObject& BakedOutputObject = BakedOutputObjects.FindOrAdd(Identifier); - - UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) - continue; - - UStaticMeshComponent* InSMC = Cast(OutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) - continue; - - // Find the HGPO that matches this output identifier - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - FindHGPO(Identifier, HGPOs, FoundHGPO); - - // We do not bake templated geos - if (FoundHGPO && FoundHGPO->bIsTemplated) - continue; - - FHoudiniAttributeResolver Resolver; - Resolver.SetCachedAttributes(OutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(OutputObject.CachedTokens); - const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - // The default output name (if not set via attributes) is {object_name}, which look for an object_name - // key-value token - if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) - Resolver.SetToken(TEXT("object_name"), DefaultObjectName); - - // The bake name override has priority - FString SMName = OutputObject.BakeName; - if (SMName.IsEmpty()) - { - // // ... finally the part name - // if (FoundHGPO && FoundHGPO->bHasCustomPartName) - // SMName = FoundHGPO->PartName; - // else - SMName = Resolver.ResolveOutputName(); - if (SMName.IsEmpty()) - SMName = DefaultObjectName; - } - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, Identifier, InBakeFolder.Path, SMName, - InHoudiniAssetName, AssetPackageReplaceMode); - - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); - - UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - // See if this output object has an unreal_level_path attribute specified - // In which case, we need to create/find the desired level for baking instead of using the current one - bool bHasLevelPathAttribute = OutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Access some of the attribute that were cached on the output object - // FHoudiniAttributeResolver Resolver; - // const TMap& CachedAttributes = OutputObject.CachedAttributes; - TMap Tokens = OutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - // Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - continue; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add the level to the packages to save? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - // Bake the static mesh if it is still temporary - UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, - Cast(BakedOutputObject.GetBakedObjectIfValid()), - PackageParams, - InAllOutputs, - OutActors, - InTempCookFolder.Path, - OutPackagesToSave); - - if (!BakedSM || BakedSM->IsPendingKill()) - continue; - - // Record the baked object - BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString(); - - // Make sure we have a level to spawn to - if (!DesiredLevel || DesiredLevel->IsPendingKill()) - continue; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - UStaticMeshComponent* SMC = nullptr; - if (!FoundActor) - { - // Spawn the new actor - FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) - continue; - - // Copy properties to new actor - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - continue; - - SMC = SMActor->GetStaticMeshComponent(); - } - else - { - if (bInReplaceAssets) - { - // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it - UStaticMeshComponent* PrevSMC = Cast(BakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) - { - SMC = PrevSMC; - } - } - - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - - if (!IsValid(SMC)) - { - // Create a new static mesh component on the existing actor - SMC = NewObject(FoundActor, NAME_None, RF_Transactional); - - FoundActor->AddInstanceComponent(SMC); - if (IsValid(RootComponent)) - SMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - else - FoundActor->SetRootComponent(SMC); - SMC->RegisterComponent(); - } - } - - // We need to make a unique name for the actor, renaming an object on top of another is a fatal error - const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName, FoundActor); - const FString NewNameStr = NewName.ToString(); - // FoundActor->Rename(*NewNameStr); - // FoundActor->SetActorLabel(NewNameStr); - RenameAndRelabelActor(FoundActor, NewNameStr, false); - SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath); - - if (IsValid(SMC)) - { - CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC); - SMC->SetStaticMesh(BakedSM); - BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString(); - } - - BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh)); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - BakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - UHoudiniOutput* Output, - TMap& InBakedOutputObjects, - const TArray& InAllBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!Output || Output->IsPendingKill()) - return false; - - TArray PackagesToSave; - - TMap& OutputObjects = Output->GetOutputObjects(); - const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); - - for (auto & Pair : OutputObjects) - { - FHoudiniOutputObject& OutputObject = Pair.Value; - USplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - FHoudiniOutputObjectIdentifier & Identifier = Pair.Key; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(Identifier); - - // TODO: FIX ME!! May not work 100% - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - for (auto & NextHGPO : HGPOs) - { - if (Identifier.GeoId == NextHGPO.GeoId && - Identifier.ObjectId == NextHGPO.ObjectId && - Identifier.PartId == NextHGPO.PartId) - { - FoundHGPO = &NextHGPO; - break; - } - } - - if (!FoundHGPO) - continue; - - FString CurveName = Pair.Value.BakeName; - if (CurveName.IsEmpty()) - { - if (FoundHGPO->bHasCustomPartName) - CurveName = FoundHGPO->PartName; - else - CurveName = InHoudiniAssetName + "_" + SplineComponent->GetName(); - } - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, Identifier, InBakeFolder.Path, CurveName, - InHoudiniAssetName, AssetPackageReplaceMode); - - BakeCurve(OutputObject, BakedOutputObject, PackageParams, bInReplaceActors, bInReplaceAssets, OutActors, - PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); - } - - SaveBakedPackages(PackagesToSave); - - return true; -} - -bool -FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint) -{ - if (!InActor || InActor->IsPendingKill()) - return false; - - if (!OutBlueprint || OutBlueprint->IsPendingKill()) - return false; - - if (InActor->GetInstanceComponents().Num() > 0) - FKismetEditorUtilities::AddComponentsToBlueprint( - OutBlueprint, - InActor->GetInstanceComponents()); - - if (OutBlueprint->GeneratedClass) - { - AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject()); - if (!CDO || CDO->IsPendingKill()) - return false; - - const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) - (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); - - EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions); - - USceneComponent * Scene = CDO->GetRootComponent(); - if (Scene && !Scene->IsPendingKill()) - { - Scene->SetRelativeLocation(FVector::ZeroVector); - Scene->SetRelativeRotation(FRotator::ZeroRotator); - - // Clear out the attachment info after having copied the properties from the source actor - Scene->SetupAttachment(nullptr); - while (true) - { - const int32 ChildCount = Scene->GetAttachChildren().Num(); - if (ChildCount < 1) - break; - - USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; - if (Component && !Component->IsPendingKill()) - Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - } - check(Scene->GetAttachChildren().Num() == 0); - - // Ensure the light mass information is cleaned up - Scene->InvalidateLightingCache(); - - // Copy relative scale from source to target. - if (USceneComponent* SrcSceneRoot = InActor->GetRootComponent()) - { - Scene->SetRelativeScale3D_Direct(SrcSceneRoot->GetRelativeScale3D()); - } - } - } - - // Compile our blueprint and notify asset system about blueprint. - //FKismetEditorUtilities::CompileBlueprint(OutBlueprint); - //FAssetRegistryModule::AssetCreated(OutBlueprint); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) -{ - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, BakeStats, Blueprints, PackagesToSave); - if (!bSuccess) - { - // TODO: ? - HOUDINI_LOG_WARNING(TEXT("Errors while baking to blueprints.")); - } - - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprints( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceAssets, - FHoudiniEngineOutputStats& InBakeStats, - TArray& OutBlueprints, - TArray& OutPackagesToSave) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); - const bool bIsOwnerActorValid = IsValid(OwnerActor); - - TArray Actors; - - // Don't process outputs that are not supported in blueprints - TArray OutputsToBake = { - EHoudiniOutputType::Mesh, - EHoudiniOutputType::Instancer, - EHoudiniOutputType::Curve - }; - TArray InstancerComponentTypesToBake = { - EHoudiniInstancerComponentType::StaticMeshComponent, - EHoudiniInstancerComponentType::InstancedStaticMeshComponent, - EHoudiniInstancerComponentType::MeshSplitInstancerComponent, - }; - // When baking blueprints we always create new actors since they are deleted from the world once copied into the - // blueprint - const bool bReplaceActors = false; - bool bBakeSuccess = BakeHoudiniActorToActors( - HoudiniAssetComponent, - bReplaceActors, - bInReplaceAssets, - Actors, - OutPackagesToSave, - InBakeStats, - &OutputsToBake, - &InstancerComponentTypesToBake); - if (!bBakeSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Could not create output actors for baking to blueprint.")); - return false; - } - - // Get the previous baked outputs - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - - bBakeSuccess = BakeBlueprintsFromBakedActors( - Actors, - HoudiniAssetComponent->bRecenterBakedActors, - bInReplaceAssets, - bIsOwnerActorValid ? OwnerActor->GetName() : FString(), - HoudiniAssetComponent->BakeFolder, - &BakedOutputs, - nullptr, - OutBlueprints, - OutPackagesToSave); - - return bBakeSuccess; -} - -UStaticMesh* -FHoudiniEngineBakeUtils::BakeStaticMesh( - UStaticMesh * StaticMesh, - const FHoudiniPackageParams& PackageParams, - const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return nullptr; - - TArray PackagesToSave; - TArray Outputs; - const TArray BakedResults; - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave); - - if (BakedStaticMesh) - { - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor) - { - TArray Objects; - Objects.Add(BakedStaticMesh); - GEditor->SyncBrowserToObjects(Objects); - } - } - - return BakedStaticMesh; -} - -bool -FHoudiniEngineBakeUtils::BakeLandscape( - int32 InOutputIndex, - UHoudiniOutput* InOutput, - TMap& InBakedOutputObjects, - bool bInReplaceActors, - bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, - FHoudiniEngineOutputStats& BakeStats - ) -{ - if (!IsValid(InOutput)) - return false; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - TArray PackagesToSave; - TArray LandscapeWorldsToUpdate; - - FHoudiniPackageParams PackageParams; - - for (auto& Elem : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key; - FHoudiniOutputObject& OutputObject = Elem.Value; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(ObjectIdentifier); - - // Populate the package params for baking this output object. - if (!IsValid(OutputObject.OutputObject)) - continue; - - if (!OutputObject.OutputObject->IsA()) - continue; - - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); - if (!IsValid(Landscape)) - continue; - - FString ObjectName = Landscape->GetName(); - - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - ObjectIdentifier, - BakePath, - ObjectName, - HoudiniAssetName, - AssetPackageReplaceMode - ); - - BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, - PackageParams, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); - } - - if (PackagesToSave.Num() > 0) - { - FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); - } - - for(UWorld* LandscapeWorld : LandscapeWorldsToUpdate) - { - if (!LandscapeWorld) - continue; - FHoudiniEngineUtils::RescanWorldPath(LandscapeWorld); - ULandscapeInfo::RecreateLandscapeInfo(LandscapeWorld, true); - if (LandscapeWorld->WorldComposition) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(LandscapeWorld); - } - } - - if (PackagesToSave.Num() > 0) - { - // These packages were either created during the Bake process or they weren't - // loaded in the first place so be sure to unload them again to preserve their "state". - - TArray PackagesToUnload; - for (UPackage* Package : PackagesToSave) - { - if (!Package->IsDirty()) - PackagesToUnload.Add(Package); - } - UPackageTools::UnloadPackages(PackagesToUnload); - } - -#if WITH_EDITOR - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); -#endif - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeLandscapeObject( - FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInReplaceActors, - bool bInReplaceAssets, - FHoudiniPackageParams& PackageParams, - TArray& WorldsToUpdate, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& BakeStats) -{ - UHoudiniLandscapePtr* LandscapePointer = Cast(InOutputObject.OutputObject); - if (!LandscapePointer) - return false; - - ALandscapeProxy* TileActor = LandscapePointer->GetRawPtr(); - if (!TileActor) - return false; - - // Fetch the previous bake's pointer and proxy (if available) - ALandscapeProxy* PreviousTileActor = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - - UWorld* TileWorld = TileActor->GetWorld(); - ULevel* TileLevel = TileActor->GetLevel(); - - ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true); - - // At this point we reconstruct the resolver using cached attributes and tokens - // and just update certain tokens (output paths) for bake mode. - FHoudiniAttributeResolver Resolver; - { - TMap Tokens = InOutputObject.CachedTokens; - // PackageParams.UpdateOutputPathTokens(EPackageMode::Bake, Tokens); - PackageParams.UpdateTokensFromParams(TileWorld, Tokens); - Resolver.SetCachedAttributes(InOutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC - // and has the appropriate name. - ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor(); - check(SharedLandscapeActor); - - // Fetch the previous bake's shared landscape actor (if available) - ALandscape* PreviousSharedLandscapeActor = nullptr; - if (IsValid(PreviousTileActor)) - PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor(); - - const bool bHasSharedLandscape = SharedLandscapeActor != TileActor; - const bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; - bool bLandscapeReplaced = false; - if (bHasSharedLandscape) - { - // If we are baking in replace mode and we have a previous shared landscape actor, use the name of that - // actor - const FString DesiredSharedLandscapeName = bHasPreviousSharedLandscape && bInReplaceActors - ? PreviousSharedLandscapeActor->GetName() - : Resolver.ResolveAttribute( - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, - SharedLandscapeActor->GetName()); - - // If we are not baking in replacement mode, create a unique name if the name is already in use - const FString SharedLandscapeName = !bInReplaceActors - ? MakeUniqueObjectNameIfNeeded(SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *DesiredSharedLandscapeName).ToString() - : DesiredSharedLandscapeName; - - if (SharedLandscapeActor->GetName() != SharedLandscapeName) - { - AActor* FoundActor = nullptr; - ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); - if (ExistingLandscape && bInReplaceActors) - { - // Even though we found an existing landscape with the desired type, we're just going to destroy/replace - // it for now. - FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); - ExistingLandscape->Destroy(); - bLandscapeReplaced = true; - } - - // Fix name of shared landscape - FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); - } - - SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); - } - - // Find the world where the landscape tile should be placed. - - TArray ValidLandscapes; - - FString ActorName = Resolver.ResolveOutputName(); - - // If the unreal_level_path was not specified, then fallback to the tile world's package - FString PackagePath = TileWorld->GetPackage() ? TileWorld->GetPackage()->GetPathName() : FString(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - PackagePath = Resolver.ResolveFullLevelPath(); - - if (bInReplaceActors) - { - // If we are baking in replace mode: get the previous baked actor (if available) name, but only if it is in the - // same target level - if (IsValid(PreviousTileActor)) - { - UPackage* PreviousPackage = PreviousTileActor->GetPackage(); - if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath) - { - ActorName = PreviousTileActor->GetName(); - } - } - } - - bool bCreatedPackage = false; - UWorld* TargetWorld = nullptr; - ULevel* TargetLevel = nullptr; - ALandscapeProxy* TargetActor = FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( - TileActor->GetWorld(), - nullptr, //unused in bake mode - ValidLandscapes,//unused in bake mode - -1, //unused in bake mode - -1, //unused in bake mode - ActorName, - PackagePath, - TargetWorld, - TargetLevel, - bCreatedPackage - ); - - check(TargetLevel) - check(TargetWorld) - - if (TargetActor && TargetActor != TileActor) - { - if (bInReplaceActors && (!PreviousTileActor || PreviousTileActor == TargetActor)) - { - // We found an target matching the name that we want. For now, rename it and then nuke it, so that - // at the very least we can spawn a new actor with the desired name. At a later stage we'll implement - // a content update, if possible. - FHoudiniEngineUtils::RenameToUniqueActor(TargetActor, ActorName + TEXT("_0")); - TargetActor->Destroy(); - } - else - { - // incremental, keep existing actor and create a unique name for the new one - ActorName = MakeUniqueObjectName(TargetActor->GetOuter(), TargetActor->GetClass(), *ActorName).ToString(); - } - TargetActor = nullptr; - } - - if (TargetLevel != TileActor->GetLevel()) - { - bool bLevelInWorld = TileWorld->ContainsLevel(TargetLevel); - ALandscape* SharedLandscape = TileActor->GetLandscapeActor(); - ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); - - check(LandscapeInfo); - - // We can now move the current landscape to the new world / level - // if (TileActor->GetClass()->IsChildOf()) - { - // We can only move streaming proxies to sublevels for now. - TArray ActorsToMove = {TileActor}; - - ALandscapeProxy* NewLandscapeProxy = LandscapeInfo->MoveComponentsToLevel(TileActor->LandscapeComponents, TargetLevel); - // We have now moved the landscape components into the new level. We can (hopefully) safely delete the - // old tile actor. - TileActor->Destroy(); - - TargetLevel->MarkPackageDirty(); - - TileActor = NewLandscapeProxy; - } - } - else - { - // Ensure the landscape actor is detached. - TileActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - } - - // Ensure the tile actor has the desired name. - FHoudiniEngineUtils::SafeRenameActor(TileActor, ActorName); - - if (TileActor->GetClass()->IsChildOf(ALandscape::StaticClass())) - { - // This is not a shared landscape. Be sure to update this landscape's world when - // baking is done. - WorldsToUpdate.AddUnique(TileActor->GetWorld()); - } - - if (bCreatedPackage) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(TargetLevel->GetOutermost()); - } - - // Record the landscape in the baked output object via a new UHoudiniLandscapePtr - // UHoudiniLandscapePtr* BakedLandscapePtr = NewObject(LandscapePointer->GetOuter()); - // if (IsValid(BakedLandscapePtr)) - // { - // BakedLandscapePtr->SetSoftPtr(TileActor); - InBakedOutputObject.BakedObject = FSoftObjectPath(TileActor).ToString(); - // } - // else - // { - // InBakedOutputObject.BakedObject = nullptr; - // } - - // Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks - InOutputObject.OutputObject = nullptr; - - DestroyPreviousBakeOutput(InBakedOutputObject, true, true, true); - - // ---------------------------------------------------- - // Collect baking stats - // ---------------------------------------------------- - if (bLandscapeReplaced) - BakeStats.NotifyObjectsReplaced(EHoudiniOutputType::Landscape, 1); - else - BakeStats.NotifyObjectsCreated(EHoudiniOutputType::Landscape, 1); - - if (bCreatedPackage) - BakeStats.NotifyPackageCreated(1); - else - if (TileLevel != TargetLevel) - BakeStats.NotifyPackageUpdated(1); - - return true; -} - -UStaticMesh * -FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - UStaticMesh * InStaticMesh, - UStaticMesh * InPreviousBakeStaticMesh, - const FHoudiniPackageParams &PackageParams, - const TArray& InParentOutputs, - const TArray& InCurrentBakedActors, - const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages) -{ - if (!InStaticMesh || InStaticMesh->IsPendingKill()) - return nullptr; - - bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, InParentOutputs, InTemporaryCookFolder); - if (!bIsTemporaryStaticMesh) - { - // The Static Mesh is not a temporary one/already baked, we can simply reuse it - // instead of duplicating it - return InStaticMesh; - } - - // Look for InStaticMesh as the SourceObject in InCurrentBakedActors (it could have already been baked along with - // a previous output: instancers etc) - for (const FHoudiniEngineBakedActor& BakedActor : InCurrentBakedActors) - { - if (BakedActor.SourceObject == InStaticMesh && IsValid(BakedActor.BakedObject) - && BakedActor.BakedObject->IsA(InStaticMesh->GetClass())) - { - // We have found a bake result where InStaticMesh was the source object and we have a valid BakedObject - // of a compatible class - return Cast(BakedActor.BakedObject); - } - } - - // InStaticMesh is temporary and we didn't find a baked version of it in our current bake output, we need to bake it - - // If we have a previously baked static mesh, get the bake counter from it so that both replace and increment - // is consistent with the bake counter - int32 BakeCounter = 0; - bool bPreviousBakeStaticMeshValid = IsValid(InPreviousBakeStaticMesh); - TArray PreviousBakeMaterials; - if (bPreviousBakeStaticMeshValid) - { - bPreviousBakeStaticMeshValid = PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBakeStaticMesh); - if (bPreviousBakeStaticMeshValid) - { - PackageParams.GetBakeCounterFromBakedAsset(InPreviousBakeStaticMesh, BakeCounter); - PreviousBakeMaterials = InPreviousBakeStaticMesh->StaticMaterials;//GetStaticMaterials(); - } - } - FString CreatedPackageName; - UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter); - if (!MeshPackage || MeshPackage->IsPendingKill()) - return nullptr; - - OutCreatedPackages.Add(MeshPackage); - - // We need to be sure the package has been fully loaded before calling DuplicateObject - if (!MeshPackage->IsFullyLoaded()) - { - FlushAsyncLoading(); - if (!MeshPackage->GetOuter()) - { - MeshPackage->FullyLoad(); - } - else - { - MeshPackage->GetOutermost()->FullyLoad(); - } - } - - // If the a UStaticMesh with that name already exists then detach it from all of its components before replacing - // it so that its render resources can be safely replaced/updated, and then reattach it - UStaticMesh * DuplicatedStaticMesh = nullptr; - UStaticMesh* ExistingMesh = FindObject(MeshPackage, *CreatedPackageName); - if (IsValid(ExistingMesh)) - { - FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); - DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); - } - else - { - DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); - } - - if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MeshPackage, DuplicatedStaticMesh, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MeshPackage, DuplicatedStaticMesh, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedPackageName); - - // See if we need to duplicate materials and textures. - TArrayDuplicatedMaterials; - TArray& Materials = DuplicatedStaticMesh->StaticMaterials; - for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) - { - UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - continue; - - // Only duplicate the material if it is temporary - if (IsObjectTemporary(MaterialInterface, InParentOutputs, InTemporaryCookFolder)) - { - UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); - if (MaterialPackage && !MaterialPackage->IsPendingKill()) - { - FString MaterialName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( - MeshPackage, DuplicatedStaticMesh, MaterialName)) - { - MaterialName = MaterialName + "_Material" + FString::FromInt(MaterialIdx + 1); - - // We only deal with materials. - UMaterial * Material = Cast< UMaterial >(MaterialInterface); - if (Material && !Material->IsPendingKill()) - { - // Look for a previous bake material at this index - UMaterial* PreviousBakeMaterial = nullptr; - if (bPreviousBakeStaticMeshValid && PreviousBakeMaterials.IsValidIndex(MaterialIdx)) - { - PreviousBakeMaterial = Cast(PreviousBakeMaterials[MaterialIdx].MaterialInterface); - } - // Duplicate material resource. - UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages); - - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) - continue; - - // Store duplicated material. - FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx]; - DupeStaticMaterial.MaterialInterface = DuplicatedMaterial; - DuplicatedMaterials.Add(DupeStaticMaterial); - continue; - } - } - } - } - - // We can simply reuse the source material - DuplicatedMaterials.Add(Materials[MaterialIdx]); - } - - // Assign duplicated materials. - DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; - - // Notify registry that we have created a new duplicate mesh. - FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); - - // Dirty the static mesh package. - DuplicatedStaticMesh->MarkPackageDirty(); - - return DuplicatedStaticMesh; -} - -ALandscapeProxy* -FHoudiniEngineBakeUtils::BakeHeightfield( - ALandscapeProxy * InLandscapeProxy, - const FHoudiniPackageParams & PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType) -{ - if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill()) - return nullptr; - - const FString & BakeFolder = PackageParams.BakeFolder; - const FString & AssetName = PackageParams.HoudiniAssetName; - - switch (LandscapeOutputBakeType) - { - case EHoudiniLandscapeOutputBakeType::Detachment: - { - // Detach the landscape from the Houdini Asset Actor - InLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - } - break; - case EHoudiniLandscapeOutputBakeType::BakeToImage: - { - // Create heightmap image to the bake folder - ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) - return nullptr; - - // bake to image must use absoluate path, - // and the file name has a file extension (.png) - FString BakeFolderInFullPath = BakeFolder; - - // Figure absolute path, - if (!BakeFolderInFullPath.EndsWith("/")) - BakeFolderInFullPath += "/"; - - if (BakeFolderInFullPath.StartsWith("/Game")) - BakeFolderInFullPath = BakeFolderInFullPath.Mid(5, BakeFolderInFullPath.Len() - 5); - - if (BakeFolderInFullPath.StartsWith("/")) - BakeFolderInFullPath = BakeFolderInFullPath.Mid(1, BakeFolderInFullPath.Len() - 1); - - FString FullPath = FPaths::ProjectContentDir() + BakeFolderInFullPath + AssetName + "_" + InLandscapeProxy->GetName() + ".png"; - - InLandscapeInfo->ExportHeightmap(FullPath); - - // TODO: - // We should update this to have an asset/package.. - } - break; - case EHoudiniLandscapeOutputBakeType::BakeToWorld: - { - ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) - return nullptr; - - // 0. Get Landscape Data // - - // Extract landscape height data - TArray InLandscapeHeightData; - int32 XSize, YSize; - FVector Min, Max; - if (!FUnrealLandscapeTranslator::GetLandscapeData(InLandscapeProxy, InLandscapeHeightData, XSize, YSize, Min, Max)) - return nullptr; - - // Extract landscape Layers data - TArray InLandscapeImportLayerInfos; - for (int32 n = 0; n < InLandscapeInfo->Layers.Num(); ++n) - { - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - FLandscapeImportLayerInfo CurrentLayerInfo; - CurrentLayerInfo.LayerName = FName(LayerName); - CurrentLayerInfo.LayerInfo = InLandscapeInfo->Layers[n].LayerInfoObj; - CurrentLayerInfo.LayerData = CurrentLayerIntData; - - CurrentLayerInfo.LayerInfo->LayerUsageDebugColor = LayerUsageDebugColor; - - InLandscapeImportLayerInfos.Add(CurrentLayerInfo); - } - - // 1. Create package // - - FString PackagePath = PackageParams.GetPackagePath(); - FString PackageName = PackageParams.GetPackageName(); - - UPackage *CreatedPackage = nullptr; - FString CreatedPackageName; - - CreatedPackage = PackageParams.CreatePackageForObject(CreatedPackageName); - - if (!CreatedPackage) - return nullptr; - - // 2. Create a new world asset with dialog // - UWorldFactory* Factory = NewObject(); - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - UObject* Asset = AssetToolsModule.Get().CreateAssetWithDialog( - PackageName, PackagePath, - UWorld::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - - - UWorld* NewWorld = Cast(Asset); - if (!NewWorld) - return nullptr; - - NewWorld->SetCurrentLevel(NewWorld->PersistentLevel); - - // 4. Spawn a landscape proxy actor in the created world - ALandscapeStreamingProxy * BakedLandscapeProxy = NewWorld->SpawnActor(); - if (!BakedLandscapeProxy) - return nullptr; - - // Create a new GUID - FGuid currentGUID = FGuid::NewGuid(); - BakedLandscapeProxy->SetLandscapeGuid(currentGUID); - - // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue - BakedLandscapeProxy->bCastStaticShadow = false; - - - // 5. Import data to the created landscape proxy - TMap> HeightmapDataPerLayers; - TMap> MaterialLayerDataPerLayer; - - HeightmapDataPerLayers.Add(FGuid(), InLandscapeHeightData); - MaterialLayerDataPerLayer.Add(FGuid(), InLandscapeImportLayerInfos); - - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - - BakedLandscapeProxy->Import( - currentGUID, - 0, 0, XSize-1, YSize-1, - InLandscapeInfo->ComponentNumSubsections, InLandscapeInfo->SubsectionSizeQuads, - HeightmapDataPerLayers, NULL, - MaterialLayerDataPerLayer, ImportLayerType); - - BakedLandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); - - - if (BakedLandscapeProxy->LandscapeMaterial) - BakedLandscapeProxy->LandscapeMaterial = InLandscapeProxy->LandscapeMaterial; - - if (BakedLandscapeProxy->LandscapeHoleMaterial) - BakedLandscapeProxy->LandscapeHoleMaterial = InLandscapeProxy->LandscapeHoleMaterial; - - // 6. Register all the landscape components, and set landscape actor transform - BakedLandscapeProxy->RegisterAllComponents(); - BakedLandscapeProxy->SetActorTransform(InLandscapeProxy->GetTransform()); - - // 7. Save Package - TArray PackagesToSave; - PackagesToSave.Add(CreatedPackage); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor) - { - TArray Objects; - Objects.Add(NewWorld); - GEditor->SyncBrowserToObjects(Objects); - } - } - break; - } - - return InLandscapeProxy; -} - -bool -FHoudiniEngineBakeUtils::BakeCurve( - USplineComponent* InSplineComponent, - ULevel* InLevel, - const FHoudiniPackageParams &PackageParams, - AActor*& OutActor, - USplineComponent*& OutSplineComponent, - FName InOverrideFolderPath, - AActor* InActor) -{ - if (!IsValid(InActor)) - { - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; - if (!Factory) - return false; - - OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform(), RF_Transactional); - } - else - { - OutActor = InActor; - } - - // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FName BaseActorName(PackageParams.ObjectName); - const FName NewName = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName, OutActor); - const FString NewNameStr = NewName.ToString(); - // OutActor->Rename(*NewNameStr); - // OutActor->SetActorLabel(NewNameStr); - RenameAndRelabelActor(OutActor, NewNameStr, false); - OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); - - USplineComponent* DuplicatedSplineComponent = DuplicateObject( - InSplineComponent, - OutActor, - MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), FName(PackageParams.ObjectName))); - OutActor->AddInstanceComponent(DuplicatedSplineComponent); - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); - DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - - FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent); - DuplicatedSplineComponent->RegisterComponent(); - - OutSplineComponent = DuplicatedSplineComponent; - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeCurve( - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - // const TArray& InAllBakedOutputs, - const FHoudiniPackageParams &PackageParams, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - USplineComponent* SplineComponent = Cast(InOutputObject.OutputComponent); - if (!IsValid(SplineComponent)) - return false; - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; - - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a new level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if(!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - // If we are baking in replace mode, remove the previous bake component - if (bInReplaceAssets && !InBakedOutputObject.BakedComponent.IsEmpty()) - { - UActorComponent* PrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (PrevComponent && PrevComponent->GetOwner() == FoundActor) - { - RemovePreviouslyBakedComponent(PrevComponent); - } - } - - FHoudiniPackageParams CurvePackageParams = PackageParams; - CurvePackageParams.ObjectName = BakeActorName.ToString(); - USplineComponent* NewSplineComponent = nullptr; - const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName)); - if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor)) - return false; - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - InBakedOutputObject.BakedComponent = FSoftObjectPath(NewSplineComponent).ToString(); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - FHoudiniEngineBakedActor Result; - Result.Actor = FoundActor; - Result.ActorBakeName = BakeActorName; - OutActors.Add(Result); - - return true; -} - -AActor* -FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PackageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform) -{ - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) - return nullptr; - - TArray & DisplayPoints = InHoudiniSplineComponent->DisplayPoints; - if (DisplayPoints.Num() < 2) - return nullptr; - - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; - if (!Factory) - return nullptr; - - // Remove the actor if it exists - for (auto & Actor : DesiredLevel->Actors) - { - if (!Actor) - continue; - - if (Actor->GetName() == PackageParams.ObjectName) - { - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - Actor->RemoveFromRoot(); - Actor->ConditionalBeginDestroy(); - World->EditorDestroyActor(Actor, true); - - break; - } - } - - AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform(), RF_Transactional); - - USplineComponent* BakedUnrealSplineComponent = NewObject(NewActor); - if (!BakedUnrealSplineComponent) - return nullptr; - - // add display points to created unreal spline component - for (int32 n = 0; n < DisplayPoints.Num(); ++n) - { - FVector & NextPoint = DisplayPoints[n]; - BakedUnrealSplineComponent->AddSplinePoint(NextPoint, ESplineCoordinateSpace::Local); - // Set the curve point type to be linear, since we are using display points - BakedUnrealSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - NewActor->AddInstanceComponent(BakedUnrealSplineComponent); - - BakedUnrealSplineComponent->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); - - FAssetRegistryModule::AssetCreated(NewActor); - FAssetRegistryModule::AssetCreated(BakedUnrealSplineComponent); - BakedUnrealSplineComponent->RegisterComponent(); - - // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); - const FString NewNameStr = NewName.ToString(); - // NewActor->Rename(*NewNameStr); - // NewActor->SetActorLabel(NewNameStr); - RenameAndRelabelActor(NewActor, NewNameStr, false); - NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName)); - - return NewActor; -} - -UBlueprint* -FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PackageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform) -{ - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) - return nullptr; - - FGuid BakeGUID = FGuid::NewGuid(); - - if (!BakeGUID.IsValid()) - BakeGUID = FGuid::NewGuid(); - - // We only want half of generated guid string. - FString BakeGUIDString = BakeGUID.ToString().Left(FHoudiniEngineUtils::PackageGUIDItemNameLength); - - // Generate Blueprint name. - FString BlueprintName = PackageParams.ObjectName + "_BP"; - - // Generate unique package name. - FString PackageName = PackageParams.BakeFolder + "/" + BlueprintName; - PackageName = UPackageTools::SanitizePackageName(PackageName); - - // See if package exists, if it does, we need to regenerate the name. - UPackage * Package = FindPackage(nullptr, *PackageName); - - if (Package && !Package->IsPendingKill()) - { - // Package does exist, there's a collision, we need to generate a new name. - BakeGUID.Invalidate(); - } - else - { - // Create actual package. - Package = CreatePackage(*PackageName); - } - - AActor * CreatedHoudiniSplineActor = FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - InHoudiniSplineComponent, PackageParams, WorldToSpawn, SpawnTransform); - - TArray PackagesToSave; - - UBlueprint * Blueprint = nullptr; - if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill()) - { - - UObject* Asset = nullptr; - - Asset = StaticFindObjectFast(UObject::StaticClass(), Package, FName(*BlueprintName)); - if (!Asset) - { - UBlueprintFactory* Factory = NewObject(); - - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - Asset = AssetToolsModule.Get().CreateAsset( - BlueprintName, PackageParams.BakeFolder, - UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - } - - TArray Components; - for (auto & Next : CreatedHoudiniSplineActor->GetComponents()) - { - Components.Add(Next); - } - - Blueprint = Cast(Asset); - - // Clear old Blueprint Node tree - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - - int32 NodeSize = SCS->GetAllNodes().Num(); - for (int32 n = NodeSize - 1; n >= 0; --n) - SCS->RemoveNode(SCS->GetAllNodes()[n]); - - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components); - - CreatedHoudiniSplineActor->RemoveFromRoot(); - CreatedHoudiniSplineActor->ConditionalBeginDestroy(); - - GWorld->EditorDestroyActor(CreatedHoudiniSplineActor, true); - - Package->MarkPackageDirty(); - PackagesToSave.Add(Package); - } - - // Save the created BP package. - FHoudiniEngineBakeUtils::SaveBakedPackages - (PackagesToSave); - - return Blueprint; -} - - -void -FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const TCHAR * Key, - const TCHAR * Value) -{ - if (!Package || Package->IsPendingKill()) - return; - - UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) - MetaData->SetValue(Object, Key, Value); -} - - -bool -FHoudiniEngineBakeUtils:: -GetHoudiniGeneratedNameFromMetaInformation( - UPackage * Package, UObject * Object, FString & HoudiniName) -{ - if (!Package || Package->IsPendingKill()) - return false; - - UMetaData * MetaData = Package->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return false; - - if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - { - // Retrieve name used for package generation. - const FString NameFull = MetaData->GetValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME); - - //HoudiniName = NameFull.Left(FMath::Min(NameFull.Len(), FHoudiniEngineUtils::PackageGUIDItemNameLength)); - HoudiniName = NameFull; - return true; - } - - return false; -} - -UMaterial * -FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - UMaterial * Material, UMaterial* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutGeneratedPackages) -{ - UMaterial * DuplicatedMaterial = nullptr; - - FString CreatedMaterialName; - // Create material package. Use the same package params as static mesh, but with the material's name - FHoudiniPackageParams MaterialPackageParams = ObjectPackageParams; - MaterialPackageParams.ObjectName = MaterialName; - - // Check if there is a valid previous material. If so, get the bake counter for consistency in - // replace or iterative package naming - bool bIsPreviousBakeMaterialValid = IsValid(PreviousBakeMaterial); - int32 BakeCounter = 0; - TArray PreviousBakeMaterialExpressions; - if (bIsPreviousBakeMaterialValid) - { - bIsPreviousBakeMaterialValid = MaterialPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeMaterial); - if (bIsPreviousBakeMaterialValid) - { - MaterialPackageParams.GetBakeCounterFromBakedAsset(PreviousBakeMaterial, BakeCounter); - PreviousBakeMaterialExpressions = PreviousBakeMaterial->Expressions; - } - } - - UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter); - - if (!MaterialPackage || MaterialPackage->IsPendingKill()) - return nullptr; - - // Clone material. - DuplicatedMaterial = DuplicateObject< UMaterial >(Material, MaterialPackage, *CreatedMaterialName); - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, DuplicatedMaterial, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, DuplicatedMaterial, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedMaterialName); - - // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. - const int32 NumExpressions = DuplicatedMaterial->Expressions.Num(); - for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx) - { - UMaterialExpression* Expression = DuplicatedMaterial->Expressions[ExpressionIdx]; - UMaterialExpression* PreviousBakeExpression = nullptr; - if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx)) - { - PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; - } - FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( - Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages); - } - - // Notify registry that we have created a new duplicate material. - FAssetRegistryModule::AssetCreated(DuplicatedMaterial); - - // Dirty the material package. - DuplicatedMaterial->MarkPackageDirty(); - - // Recompile the baked material - // DuplicatedMaterial->ForceRecompileForRendering(); - // Use UMaterialEditingLibrary::RecompileMaterial since it correctly updates texture references in the material - // which ForceRecompileForRendering does not do - UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterial); - - OutGeneratedPackages.Add(MaterialPackage); - - return DuplicatedMaterial; -} - -void -FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( - UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, - const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages) -{ - UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression); - if (!TextureSample || TextureSample->IsPendingKill()) - return; - - UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture); - if (!Texture || Texture->IsPendingKill()) - return; - - UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) - return; - - // Try to get the previous bake's texture - UTexture2D* PreviousBakeTexture = nullptr; - if (IsValid(PreviousBakeMaterialExpression)) - { - UMaterialExpressionTextureSample* PreviousBakeTextureSample = Cast< UMaterialExpressionTextureSample >(PreviousBakeMaterialExpression); - if (IsValid(PreviousBakeTextureSample)) - PreviousBakeTexture = Cast< UTexture2D >(PreviousBakeTextureSample->Texture); - } - - FString GeneratedTextureName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( - TexturePackage, Texture, GeneratedTextureName)) - { - // Duplicate texture. - UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( - Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages); - - // Re-assign generated texture. - TextureSample->Texture = DuplicatedTexture; - } -} - -UTexture2D * -FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( - UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages) -{ - UTexture2D* DuplicatedTexture = nullptr; -#if WITH_EDITOR - // Retrieve original package of this texture. - UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) - return nullptr; - - FString GeneratedTextureName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName)) - { - UMetaData * MetaData = TexturePackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return nullptr; - - // Retrieve texture type. - const FString & TextureType = - MetaData->GetValue(Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); - - FString CreatedTextureName; - - // Create texture package. Use the same package params as material's, but with object name appended by generated texture's name - FHoudiniPackageParams TexturePackageParams = PackageParams; - TexturePackageParams.ObjectName = TexturePackageParams.ObjectName + "_" + GeneratedTextureName; - - // Determine the bake counter of the previous bake's texture (if exists/valid) for naming consistency when - // replacing/iterating - bool bIsPreviousBakeTextureValid = IsValid(PreviousBakeTexture); - int32 BakeCounter = 0; - if (bIsPreviousBakeTextureValid) - { - bIsPreviousBakeTextureValid = TexturePackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeTexture); - if (bIsPreviousBakeTextureValid) - { - TexturePackageParams.GetBakeCounterFromBakedAsset(PreviousBakeTexture, BakeCounter); - } - } - - UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter); - - if (!NewTexturePackage || NewTexturePackage->IsPendingKill()) - return nullptr; - - // Clone texture. - DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName); - if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedTextureName); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); - - // Notify registry that we have created a new duplicate texture. - FAssetRegistryModule::AssetCreated(DuplicatedTexture); - - // Dirty the texture package. - DuplicatedTexture->MarkPackageDirty(); - - OutCreatedPackages.Add(NewTexturePackage); - } -#endif - return DuplicatedTexture; -} - - -bool -FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); - - if (!ActorOwner || ActorOwner->IsPendingKill()) - return false; - - UWorld* World = ActorOwner->GetWorld(); - if (!World) - World = GWorld; - - World->EditorDestroyActor(ActorOwner, false); - - return true; -} - - -void -FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld) -{ - UWorld * CurrentWorld = nullptr; - if (bSaveCurrentWorld && GEditor) - CurrentWorld = GEditor->GetEditorWorldContext().World(); - - if (CurrentWorld) - { - // Save the current map - FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); - UPackage* CurrentWorldPackage = CreatePackage(*CurrentWorldPath); - - if (CurrentWorldPackage) - { - CurrentWorldPackage->MarkPackageDirty(); - PackagesToSave.Add(CurrentWorldPackage); - } - } - - FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); -} - -bool -FHoudiniEngineBakeUtils::FindOutputObject( - const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) -{ - if (!InObjectToFind || InObjectToFind->IsPendingKill()) - return false; - - const int32 NumOutputs = InOutputs.Num(); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - const UHoudiniOutput* CurOutput = InOutputs[OutputIdx]; - if (!IsValid(CurOutput)) - continue; - - for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) - { - if (CurOutputObject.Value.OutputObject == InObjectToFind - || CurOutputObject.Value.OutputComponent == InObjectToFind - || CurOutputObject.Value.ProxyObject == InObjectToFind - || CurOutputObject.Value.ProxyComponent == InObjectToFind) - { - OutOutputIndex = OutputIdx; - OutIdentifier = CurOutputObject.Key; - return true; - } - } - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - FString TempPath = FString(); - - // TODO: Get the HAC outputs in a better way? - TArray Outputs; - if (InHAC && !InHAC->IsPendingKill()) - { - const int32 NumOutputs = InHAC->GetNumOutputs(); - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - Outputs.Add(InHAC->GetOutputAt(OutputIdx)); - } - - TempPath = InHAC->TemporaryCookFolder.Path; - } - - return IsObjectTemporary(InObject, Outputs, TempPath); -} - -bool FHoudiniEngineBakeUtils::IsObjectTemporary( - UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - int32 ParentOutputIndex = -1; - FHoudiniOutputObjectIdentifier Identifier; - if (FindOutputObject(InObject, InParentOutputs, ParentOutputIndex, Identifier)) - return true; - - // Check the package path for this object - // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated - UPackage* ObjectPackage = InObject->GetOutermost(); - if (ObjectPackage && !ObjectPackage->IsPendingKill()) - { - const FString PathName = ObjectPackage->GetPathName(); - if (PathName.StartsWith(InTemporaryCookFolder)) - return true; - - // Also check the default temp folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder)) - return true; - - /* - // TODO: this just indicates that the object was generated by H - // it could as well have been baked before... - // we should probably add a "temp" metadata - // Look in the meta info as well?? - UMetaData * MetaData = ObjectPackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return false; - - if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - return true; - */ - } - - return false; -} - -void -FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC) -{ - if (!NewSMC || NewSMC->IsPendingKill()) - return; - - if (!InSMC || InSMC->IsPendingKill()) - return; - - // Copy properties to new actor - //UStaticMeshComponent* OtherSMC_NonConst = const_cast(StaticMeshComponent); - NewSMC->SetCollisionProfileName(InSMC->GetCollisionProfileName()); - NewSMC->SetCollisionEnabled(InSMC->GetCollisionEnabled()); - NewSMC->LightmassSettings = InSMC->LightmassSettings; - NewSMC->CastShadow = InSMC->CastShadow; - NewSMC->SetMobility(InSMC->Mobility); - - UBodySetup* InBodySetup = InSMC->GetBodySetup(); - UBodySetup* NewBodySetup = NewSMC->GetBodySetup(); - if (InBodySetup && NewBodySetup) - { - // Copy the BodySetup - NewBodySetup->CopyBodyPropertiesFrom(InBodySetup); - - // We need to recreate the physics mesh for the new body setup - NewBodySetup->InvalidatePhysicsData(); - NewBodySetup->CreatePhysicsMeshes(); - - // Only copy the physical material if it's different from the default one, - // As this was causing crashes on BakeToActors in some cases - if (GEngine != NULL && NewBodySetup->GetPhysMaterial() != GEngine->DefaultPhysMaterial) - NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial()); - } - - if (NewActor && !NewActor->IsPendingKill()) - NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame); - - NewSMC->SetVisibility(InSMC->IsVisible()); - - // TODO: - // // Reapply the uproperties modified by attributes on the new component - // FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO); - - // The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another - UClass* ComponentClass = InSMC->GetClass(); - if (ComponentClass != NewSMC->GetClass()) - { - HOUDINI_LOG_WARNING( - TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), - *(ComponentClass->GetName()), - *(NewSMC->GetClass()->GetName())); - - NewSMC->PostEditChange(); - return; - } - - TSet SourceUCSModifiedProperties; - InSMC->GetUCSModifiedProperties(SourceUCSModifiedProperties); - - AActor* SourceActor = InSMC->GetOwner(); - if (!IsValid(SourceActor)) - { - NewSMC->PostEditChange(); - return; - } - - TArray ModifiedObjects; - const EditorUtilities::FCopyOptions Options(EditorUtilities::ECopyOptions::CallPostEditChangeProperty); - // Copy component properties - for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) - { - const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); - const bool bIsIdentical = Property->Identical_InContainer( InSMC, NewSMC ); - const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); - const bool bIsTransform = - Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); - - // auto SourceComponentIsRoot = [&]() - // { - // USceneComponent* RootComponent = SourceActor->GetRootComponent(); - // if (InSMC == RootComponent) - // { - // return true; - // } - // return false; - // }; - - // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - // && ( !bIsTransform || !SourceComponentIsRoot() ) ) - if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - && !bIsTransform ) - { - // const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) - // && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); - const bool bIsSafeToCopy = true; - if( bIsSafeToCopy ) - { - if (!Options.CanCopyProperty(*Property, *SourceActor)) - { - continue; - } - - if( !ModifiedObjects.Contains(NewSMC) ) - { - NewSMC->SetFlags(RF_Transactional); - NewSMC->Modify(); - ModifiedObjects.Add(NewSMC); - } - - if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) - { - // @todo simulate: Should we be calling this on the component instead? - NewActor->PreEditChange( Property ); - } - - EditorUtilities::CopySingleProperty(InSMC, NewSMC, Property); - - if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) - { - FPropertyChangedEvent PropertyChangedEvent( Property ); - NewActor->PostEditChangeProperty( PropertyChangedEvent ); - } - } - } - } - - NewSMC->PostEditChange(); -}; - -bool -FHoudiniEngineBakeUtils::RemovePreviouslyBakedActor( - AActor* InNewBakedActor, - ULevel* InLevel, - const FHoudiniPackageParams& InPackageParams) -{ - // Remove a previous bake actor if it exists - for (auto & Actor : InLevel->Actors) - { - if (!Actor) - continue; - - if (Actor != InNewBakedActor && Actor->GetName() == InPackageParams.ObjectName) - { - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - Actor->RemoveFromRoot(); - Actor->ConditionalBeginDestroy(); - World->EditorDestroyActor(Actor, true); - - return true; - } - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::RemovePreviouslyBakedComponent(UActorComponent* InComponent) -{ - if (!IsValid(InComponent)) - return false; - - // Remove from its actor first - if (InComponent->GetOwner()) - InComponent->GetOwner()->RemoveOwnedComponent(InComponent); - - // Detach from its parent component if attached - USceneComponent* SceneComponent = Cast(InComponent); - if (IsValid(SceneComponent)) - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - InComponent->UnregisterComponent(); - InComponent->DestroyComponent(); - - return true; -} - -FName -FHoudiniEngineBakeUtils::GetOutputFolderPath(UObject* InOutputOwner) -{ - // Get an output folder path for PDG outputs generated by InOutputOwner. - // The folder path is: / - FString FolderName; - FName FolderDirName; - AActor* OuterActor = Cast(InOutputOwner); - if (OuterActor) - { - FolderName = OuterActor->GetActorLabel(); - FolderDirName = OuterActor->GetFolderPath(); - } - else - { - FolderName = InOutputOwner->GetName(); - } - if (!FolderDirName.IsNone()) - return FName(FString::Printf(TEXT("%s/%s"), *FolderDirName.ToString(), *FolderName)); - else - return FName(FolderName); -} - -void -FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique) -{ - FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); - - const FSoftObjectPath OldPath = FSoftObjectPath(InAsset); - - FString NewName; - if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetPackage(), InAsset->GetClass(), FName(InNewName), InAsset).ToString(); - else - NewName = InNewName; - - InAsset->Rename(*NewName); - - const FSoftObjectPath NewPath = FSoftObjectPath(InAsset); - if (OldPath != NewPath) - { - TArray RenameData; - RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); - AssetToolsModule.Get().RenameAssets(RenameData); - } -} - -void -FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique) -{ - if (!IsValid(InActor)) - return; - - FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); - - const FSoftObjectPath OldPath = FSoftObjectPath(InActor); - - FString NewName; - if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName), InActor).ToString(); - else - NewName = InNewName; - - InActor->Rename(*NewName); - InActor->SetActorLabel(NewName); - - const FSoftObjectPath NewPath = FSoftObjectPath(InActor); - if (OldPath != NewPath) - { - TArray RenameData; - RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); - AssetToolsModule.Get().RenameAssets(RenameData); - } -} - -bool -FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor( - AActor* InActor, - const FString& InNewName, - const FName& InFolderPath) -{ - if (!IsValid(InActor)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: InActor is null.")); - return false; - } - - if (InNewName.TrimStartAndEnd().IsEmpty()) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: A valid actor name must be specified.")); - return false; - } - - // Detach from parent - InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - // Rename - // InActor->Rename(*MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName)).ToString()); - // InActor->SetActorLabel(InNewName); - const bool bMakeUniqueIfNotUnique = true; - RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique); - - InActor->SetFolderPath(InFolderPath); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultIndex, - int32 InWorkResultObjectIndex, - bool bInReplaceActors, - bool bInReplaceAssets, - bool bInBakeToWorkResultActor, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!IsValid(InPDGAssetLink)) - return false; - - if (!IsValid(InNode)) - return false; - - if (!InNode->WorkResult.IsValidIndex(InWorkResultIndex)) - return false; - - FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultIndex]; - if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectIndex)) - return false; - - FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectIndex]; - TArray& Outputs = WorkResultObject.GetResultOutputs(); - if (Outputs.Num() == 0) - return true; - - AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor(); - if (!IsValid(WorkResultObjectActor)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObjectActor (%s) is null (unexpected since # Outputs > 0)"), *WorkResultObject.Name); - return false; - } - - // BakedActorsForWorkResultObject contains each actor that contains baked PDG results. Actors may - // appear in the array more than once if they have more than one baked result/component associated with - // them - TArray BakedActorsForWorkResultObject; - const FString HoudiniAssetName(WorkResultObject.Name); - - // Find the previous bake output for this work result object - FString Key; - InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key); - FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key); - - BakeHoudiniOutputsToActors( - Outputs, - BakedOutputContainer.BakedOutputs, - HoudiniAssetName, - WorkResultObjectActor->GetActorTransform(), - InPDGAssetLink->BakeFolder, - InPDGAssetLink->GetTemporaryCookFolder(), - bInReplaceActors, - bInReplaceAssets, - BakedActorsForWorkResultObject, - OutPackagesToSave, - OutBakeStats, - InOutputTypesToBake, - InInstancerComponentTypesToBake, - bInBakeToWorkResultActor ? WorkResultObjectActor : nullptr, - InFallbackWorldOutlinerFolder); - - // Set the PDG indices on the output baked actor entries - if (BakedActorsForWorkResultObject.Num() > 0) - { - for (FHoudiniEngineBakedActor& BakedActorEntry : BakedActorsForWorkResultObject) - { - BakedActorEntry.PDGWorkResultIndex = InWorkResultIndex; - BakedActorEntry.PDGWorkResultObjectIndex = InWorkResultObjectIndex; - } - } - - // If anything was baked to WorkResultObjectActor, detach it from its parent - if (bInBakeToWorkResultActor) - { - FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); - // if we re-used the temp actor as a bake actor, then remove its temp outputs - WorkResultObject.DestroyResultOutputs(); - AActor* WROActor = OutputActorOwner.GetOutputActor(); - if (WROActor) - { - const FHoudiniEngineBakedActor* BakedActorEntry = BakedActorsForWorkResultObject.FindByPredicate([WROActor](const FHoudiniEngineBakedActor& Entry) - { - return Entry.Actor == WROActor; - }); - if (BakedActorEntry) - { - OutputActorOwner.SetOutputActor(nullptr); - const FString OldActorPath = FSoftObjectPath(WROActor).ToString(); - DetachAndRenameBakedPDGOutputActor( - WROActor, BakedActorEntry->ActorBakeName.ToString(), BakedActorEntry->WorldOutlinerFolder); - const FString NewActorPath = FSoftObjectPath(WROActor).ToString(); - if (OldActorPath != NewActorPath) - { - // Fix cached string reference in baked outputs to WROActor - for (FHoudiniBakedOutput& BakedOutput : BakedOutputContainer.BakedOutputs) - { - for (auto& Entry : BakedOutput.BakedOutputObjects) - { - if (Entry.Value.Actor == OldActorPath) - Entry.Value.Actor = NewActorPath; - } - } - } - } - else - { - OutputActorOwner.DestroyOutputActor(); - } - } - } - OutBakedActors.Append(BakedActorsForWorkResultObject); - return true; -} - - -bool -FHoudiniEngineBakeUtils::BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName) -{ - if (!IsValid(InPDGAssetLink)) - return false; - - if (!IsValid(InNode)) - return false; - - // Find the work result index and work result object index - const int32 WorkResultIndex = InNode->WorkResult.IndexOfByPredicate([InWorkResultId](const FTOPWorkResult& Entry) - { - return Entry.WorkItemID == InWorkResultId; - }); - if (!InNode->WorkResult.IsValidIndex(WorkResultIndex)) - return false; - FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIndex]; - const int32 WorkResultObjectIndex = WorkResult.ResultObjects.IndexOfByPredicate([InWorkResultObjectName](const FTOPWorkResultObject& Entry) - { - return Entry.Name.Equals(InWorkResultObjectName); - }); - if (!WorkResult.ResultObjects.IsValidIndex(WorkResultObjectIndex)) - return false; - - // Determine the output world outliner folder path via the PDG asset link's - // owner's folder path and name - UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); - if (!PDGOwner) - PDGOwner = InPDGAssetLink->GetOuter(); - const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); - - // Determine the actor/package replacement settings - const bool bBakeBlueprints = InPDGAssetLink->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToBlueprint; - const bool bReplaceActors = !bBakeBlueprints && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Determine the output types to bake: don't bake landscapes in blueprint baking mode - TArray OutputTypesToBake; - TArray InstancerComponentTypesToBake; - if (bBakeBlueprints) - { - OutputTypesToBake.Add(EHoudiniOutputType::Mesh); - OutputTypesToBake.Add(EHoudiniOutputType::Instancer); - OutputTypesToBake.Add(EHoudiniOutputType::Curve); - - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); - } - - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - TArray BakedActors; - - bool bSuccess = BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - WorkResultIndex, - WorkResultObjectIndex, - bReplaceActors, - bReplaceAssets, - !bBakeBlueprints, - BakedActors, - PackagesToSave, - BakeStats, - OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, - InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, - FallbackWorldOutlinerFolderPath.ToString() - ); - - // Recenter and select the baked actors - if (GEditor && BakedActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : BakedActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (InPDGAssetLink->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && BakedActors.Num() > 0) - GEditor->NoteSelectionChange(); - - if (bBakeBlueprints && bSuccess) - { - TArray Blueprints; - bSuccess = BakeBlueprintsFromBakedActors( - BakedActors, - InPDGAssetLink->bRecenterBakedActors, - bReplaceAssets, - InPDGAssetLink->AssetName, - InPDGAssetLink->BakeFolder, - nullptr, - &InNode->GetBakedWorkResultObjectsOutputs(), - Blueprints, - PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - } - - SaveBakedPackages(PackagesToSave); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return bSuccess; -} - -void -FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName) -{ - if (!IsValid(InPDGAssetLink)) - return; - - if (!InPDGAssetLink->bBakeAfterWorkResultObjectLoaded) - return; - - BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - InWorkResultId, - InWorkResultObjectName); -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInBakeForBlueprint, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNode)) - return false; - - // Determine the output world outliner folder path via the PDG asset link's - // owner's folder path and name - UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); - if (!PDGOwner) - PDGOwner = InPDGAssetLink->GetOuter(); - const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); - - // Determine the actor/package replacement settings - const bool bReplaceActors = !bInBakeForBlueprint && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Determine the output types to bake: don't bake landscapes in blueprint baking mode - TArray OutputTypesToBake; - TArray InstancerComponentTypesToBake; - if (bInBakeForBlueprint) - { - OutputTypesToBake.Add(EHoudiniOutputType::Mesh); - OutputTypesToBake.Add(EHoudiniOutputType::Instancer); - OutputTypesToBake.Add(EHoudiniOutputType::Curve); - - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); - } - - const int32 NumWorkResults = InNode->WorkResult.Num(); - for (int32 WorkResultIdx = 0; WorkResultIdx < NumWorkResults; ++WorkResultIdx) - { - FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIdx]; - const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); - for (int32 WorkResultObjectIdx = 0; WorkResultObjectIdx < NumWorkResultObjects; ++WorkResultObjectIdx) - { - BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - WorkResultIdx, - WorkResultObjectIdx, - bReplaceActors, - bReplaceAssets, - !bInBakeForBlueprint, - OutBakedActors, - OutPackagesToSave, - OutBakeStats, - OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, - InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, - FallbackWorldOutlinerFolderPath.ToString() - ); - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - bool bInBakeForBlueprint, - TArray& BakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNetwork)) - return false; - - bool bSuccess = true; - for (UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, BakedActors, OutPackagesToSave, OutBakeStats); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - TArray BakedActors; - - const bool bBakeBlueprints = false; - - bool bSuccess = true; - switch(InPDGAssetLink->PDGBakeSelectionOption) - { - case EPDGBakeSelectionOption::All: - for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) - { - if (!IsValid(Network)) - continue; - - for (UTOPNode* Node : Network->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); - } - } - break; - case EPDGBakeSelectionOption::SelectedNetwork: - bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); - case EPDGBakeSelectionOption::SelectedNode: - bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); - } - - SaveBakedPackages(PackagesToSave); - - // Recenter and select the baked actors - if (GEditor && BakedActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : BakedActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (InPDGAssetLink->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && BakedActors.Num() > 0) - GEditor->NoteSelectionChange(); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( - const TArray& InBakedActors, - bool bInRecenterBakedActors, - bool bInReplaceAssets, - const FString& InAssetName, - const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOuputs, - TMap* const InPDGBakedOutputs, - TArray& OutBlueprints, - TArray& OutPackagesToSave) -{ - // // Clear selection - // if (GEditor) - // { - // GEditor->SelectNone(false, true); - // GEditor->NoteSelectionChange(); - // } - - // Iterate over the baked actors. An actor might appear multiple times if multiple OutputComponents were - // baked to the same actor, so keep track of actors we have already processed in BakedActorSet - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem); - TArray AssetsToReOpenEditors; - TSet BakedActorSet; - - for (const FHoudiniEngineBakedActor& Entry : InBakedActors) - { - AActor *Actor = Entry.Actor; - - if (!Actor || Actor->IsPendingKill()) - continue; - - if (BakedActorSet.Contains(Actor)) - continue; - - BakedActorSet.Add(Actor); - - UObject* Asset = nullptr; - - // Recenter the actor to its bounding box center - if (bInRecenterBakedActors) - CenterActorToBoundingBoxCenter(Actor); - - // Create package for out Blueprint - FString BlueprintName; - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - FHoudiniOutputObjectIdentifier(), - InBakeFolder.Path, - Entry.ActorBakeName.ToString() + "_BP", - InAssetName, - AssetPackageReplaceMode); - - // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment - // is consistent with the bake counter - int32 BakeCounter = 0; - UBlueprint* InPreviousBlueprint = nullptr; - FHoudiniBakedOutputObject* BakedOutputObject = nullptr; - FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; - // Get the baked output object - if (Entry.PDGWorkResultIndex >= 0 && Entry.PDGWorkResultObjectIndex >= 0 && InPDGBakedOutputs) - { - const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkResultIndex, Entry.PDGWorkResultObjectIndex); - WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); - if (WorkResultObjectBakedOutput) - { - if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } - } - else if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs) - { - if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs->IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = (*InNonPDGBakedOuputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } - if (BakedOutputObject) - { - InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); - if (IsValid(InPreviousBlueprint)) - { - if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint)) - { - PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter); - } - } - } - - UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter); - - if (!Package || Package->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName())); - continue; - } - - if (!Package->IsFullyLoaded()) - Package->FullyLoad(); - - //Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false); - // Find existing asset first first (only relevant if we are in replacement mode). If the existing asset has a - // different base class than the incoming actor, we reparent the blueprint to the new base class before - // clearing the SCS graph and repopulating it from the temp actor. - Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName)); - if (IsValid(Asset)) - { - UBlueprint* Blueprint = Cast(Asset); - if (IsValid(Blueprint)) - { - if (Blueprint->GeneratedClass && Blueprint->GeneratedClass != Actor->GetClass()) - { - // Close editors opened on existing asset if applicable - if (Asset && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Asset, false) != nullptr) - { - AssetEditorSubsystem->CloseAllEditorsForAsset(Asset); - AssetsToReOpenEditors.Add(Asset); - } - - Blueprint->ParentClass = Actor->GetClass(); - - FBlueprintEditorUtils::RefreshAllNodes(Blueprint); - FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); - FKismetEditorUtilities::CompileBlueprint(Blueprint); - } - } - } - else if (Asset && Asset->IsPendingKill()) - { - // Rename to pending kill so that we can use the desired name - const FString AssetPendingKillName(BlueprintName + "_PENDING_KILL"); - // Asset->Rename(*MakeUniqueObjectNameIfNeeded(Package, UBlueprint::StaticClass(), AssetPendingKillName).ToString()); - RenameAsset(Asset, AssetPendingKillName, true); - Asset = nullptr; - } - - if (!Asset) - { - UBlueprintFactory* Factory = NewObject(); - Factory->ParentClass = Actor->GetClass(); - - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - Asset = AssetToolsModule.Get().CreateAsset( - BlueprintName, InBakeFolder.Path, - UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - } - - UBlueprint* Blueprint = Cast(Asset); - - if (!Blueprint || Blueprint->IsPendingKill()) - { - HOUDINI_LOG_WARNING( - TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."), - *(InBakeFolder.Path), *BlueprintName); - - continue; - } - - // Close editors opened on existing asset if applicable - if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr) - { - AssetEditorSubsystem->CloseAllEditorsForAsset(Blueprint); - AssetsToReOpenEditors.Add(Blueprint); - } - - // Record the blueprint as the previous bake blueprint - if (BakedOutputObject) - BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString(); - - OutBlueprints.Add(Blueprint); - - // Clear old Blueprint Node tree - { - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - - int32 NodeSize = SCS->GetAllNodes().Num(); - for (int32 n = NodeSize - 1; n >= 0; --n) - SCS->RemoveNode(SCS->GetAllNodes()[n]); - } - - FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint); - - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - World->EditorDestroyActor(Actor, true); - - // Save the created BP package. - Package->MarkPackageDirty(); - OutPackagesToSave.Add(Package); - } - - // Re-open asset editors for updated blueprints that were open in editors - if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0) - { - for (UObject* Asset : AssetsToReOpenEditors) - { - if (IsValid(Asset)) - { - AssetEditorSubsystem->OpenEditorForAsset(Asset); - } - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - TArray BPActors; - - if (!IsValid(InPDGAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InPDGAssetLink is null")); - return false; - } - - if (!IsValid(InNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InNode is null")); - return false; - } - - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Bake PDG output to new actors - // bInBakeForBlueprint == true will skip landscapes and instanced actor components - const bool bInBakeForBlueprint = true; - TArray BakedActors; - bool bSuccess = BakePDGTOPNodeOutputsKeepActors( - InPDGAssetLink, - InNode, - bInBakeForBlueprint, - BakedActors, - OutPackagesToSave, - OutBakeStats - ); - - if (bSuccess) - { - bSuccess = BakeBlueprintsFromBakedActors( - BakedActors, - InPDGAssetLink->bRecenterBakedActors, - bReplaceAssets, - InPDGAssetLink->AssetName, - InPDGAssetLink->BakeFolder, - nullptr, - &InNode->GetBakedWorkResultObjectsOutputs(), - OutBlueprints, - OutPackagesToSave); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNetwork)) - return false; - - bool bSuccess = true; - for (UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, OutBlueprints, OutPackagesToSave, OutBakeStats); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - TArray Blueprints; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - bool bSuccess = true; - switch(InPDGAssetLink->PDGBakeSelectionOption) - { - case EPDGBakeSelectionOption::All: - for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) - { - if (!IsValid(Network)) - continue; - - for (UTOPNode* Node : Network->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, Blueprints, PackagesToSave, BakeStats); - } - } - break; - case EPDGBakeSelectionOption::SelectedNetwork: - bSuccess &= BakePDGTOPNetworkBlueprints( - InPDGAssetLink, - InPDGAssetLink->GetSelectedTOPNetwork(), - Blueprints, - PackagesToSave, - BakeStats); - case EPDGBakeSelectionOption::SelectedNode: - bSuccess &= BakePDGTOPNodeBlueprints( - InPDGAssetLink, - InPDGAssetLink->GetSelectedTOPNode(), - Blueprints, - PackagesToSave, - BakeStats); - } - - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - const FString& InLevelPath, - ULevel*& OutDesiredLevel, - UWorld*& OutDesiredWorld, - bool& OutCreatedPackage) -{ - OutDesiredLevel = nullptr; - OutDesiredWorld = nullptr; - if (InLevelPath.IsEmpty()) - { - OutDesiredWorld = GWorld; - OutDesiredLevel = GWorld->GetCurrentLevel(); - } - else - { - OutCreatedPackage = false; - - UWorld* FoundWorld = nullptr; - ULevel* FoundLevel = nullptr; - bool bActorInWorld = false; - if (FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - GWorld, - InLevelPath, - true, - FoundWorld, - FoundLevel, - OutCreatedPackage, - bActorInWorld)) - { - OutDesiredLevel = FoundLevel; - OutDesiredWorld = FoundWorld; - } - } - - return ((OutDesiredWorld != nullptr) && (OutDesiredLevel != nullptr)); -} - - -bool -FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( - const FString& InBakeActorName, - ULevel* InLevel, - AActor*& OutActor, - bool bInNoPendingKillActors, - bool bRenamePendingKillActor) -{ - OutActor = nullptr; - - if (!IsValid(InLevel)) - return false; - - UWorld* const World = InLevel->GetWorld(); - if (!IsValid(World)) - return false; - - // Look for an actor with the given name in the world - const FName BakeActorFName(InBakeActorName); - AActor* FoundActor = Cast(StaticFindObjectFast(AActor::StaticClass(), InLevel, BakeActorFName)); - // for (TActorIterator Iter(World, AActor::StaticClass(), EActorIteratorFlags::AllActors); Iter; ++Iter) - // { - // AActor* const Actor = *Iter; - // if (Actor->GetFName() == BakeActorFName && Actor->GetLevel() == InLevel) - // { - // // Found the actor - // FoundActor = Actor; - // break; - // } - // } - - // If we found an actor and it is pending kill, rename it and don't use it - if (FoundActor) - { - if (FoundActor->IsPendingKill()) - { - if (bRenamePendingKillActor) - { - // FoundActor->Rename( - // *MakeUniqueObjectNameIfNeeded( - // FoundActor->GetOuter(), - // FoundActor->GetClass(), - // FName(FoundActor->GetName() + "_Pending_Kill")).ToString()); - RenameAndRelabelActor( - FoundActor, - *MakeUniqueObjectNameIfNeeded( - FoundActor->GetOuter(), - FoundActor->GetClass(), - FName(FoundActor->GetName() + "_Pending_Kill"), - FoundActor).ToString(), - false); - } - if (bInNoPendingKillActors) - FoundActor = nullptr; - else - OutActor = FoundActor; - } - else - { - OutActor = FoundActor; - } - } - - return true; -} - -bool FHoudiniEngineBakeUtils::FindUnrealBakeActor( - const FHoudiniOutputObject& InOutputObject, - const FHoudiniBakedOutputObject& InBakedOutputObject, - const TArray& InAllBakedActors, - ULevel* InLevel, - FName InDefaultActorName, - bool bInReplaceActorBakeMode, - AActor* InFallbackActor, - AActor*& OutFoundActor, - bool& bOutHasBakeActorName, - FName& OutBakeActorName) -{ - // Determine desired actor name via unreal_output_actor, fallback to InDefaultActorName - OutBakeActorName = NAME_None; - OutFoundActor = nullptr; - bOutHasBakeActorName = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_ACTOR); - if (bOutHasBakeActorName) - { - const FString& BakeActorNameStr = InOutputObject.CachedAttributes[HAPI_UNREAL_ATTRIB_BAKE_ACTOR]; - if (BakeActorNameStr.IsEmpty()) - { - OutBakeActorName = NAME_None; - bOutHasBakeActorName = false; - } - else - { - OutBakeActorName = *BakeActorNameStr; - // We have a bake actor name, look for the actor - AActor* BakeNameActor = nullptr; - if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor)) - { - // Found an actor with that name, check that we "own" it (we created in during baking previously) - AActor* IncrementedBakedActor = nullptr; - for (const FHoudiniEngineBakedActor& BakedActor : InAllBakedActors) - { - if (!IsValid(BakedActor.Actor)) - continue; - if (BakedActor.Actor == BakeNameActor) - { - OutFoundActor = BakeNameActor; - break; - } - else if (!IncrementedBakedActor && BakedActor.ActorBakeName == OutBakeActorName) - { - // Found an actor we have baked named OutBakeActorName_# (incremental version of our desired name) - IncrementedBakedActor = BakedActor.Actor; - } - } - if (!OutFoundActor && IncrementedBakedActor) - OutFoundActor = IncrementedBakedActor; - } - } - } - - // If unreal_actor_name is not set, or is blank, fallback to InDefaultActorName - if (!bOutHasBakeActorName || (OutBakeActorName.IsNone() || OutBakeActorName.ToString().TrimStartAndEnd().IsEmpty())) - OutBakeActorName = InDefaultActorName; - - if (!OutFoundActor) - { - // If in replace mode, use previous bake actor if valid and in InLevel - if (bInReplaceActorBakeMode) - { - const FSoftObjectPath PrevActorPath(InBakedOutputObject.Actor); - const FString ActorPath = PrevActorPath.IsSubobject() - ? PrevActorPath.GetAssetPathString() + ":" + PrevActorPath.GetSubPathString() - : PrevActorPath.GetAssetPathString(); - const FString LevelPath = IsValid(InLevel) ? InLevel->GetPathName() : ""; - if (PrevActorPath.IsValid() && (LevelPath.IsEmpty() || ActorPath.StartsWith(LevelPath))) - OutFoundActor = InBakedOutputObject.GetActorIfValid(); - } - - // Fallback to InFallbackActor if valid and in InLevel - if (!OutFoundActor && IsValid(InFallbackActor) && (!InLevel || InFallbackActor->GetLevel() == InLevel)) - OutFoundActor = InFallbackActor; - } - - return true; -} - -AActor* -FHoudiniEngineBakeUtils::FindExistingActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - bCreatedPackage = false; - - // Try to Locate a previous actor - AActor* FoundActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); - if (FoundActor) - FoundActor->Destroy(); // nuke it! - - if (FoundActor) - { - // TODO: make sure that the found is actor is actually assigned to the level defined by package path. - // If the found actor is not from that level, it should be moved there. - - OutWorld = FoundActor->GetWorld(); - OutLevel = FoundActor->GetLevel(); - } - else - { - // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. - bool bActorInWorld = false; - const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - InWorld, - InPackagePath, - true, - OutWorld, - OutLevel, - bCreatedPackage, - bActorInWorld); - - if (!bResult) - { - return nullptr; - } - - if (!bActorInWorld) - { - // The OutLevel is not present in the current world which means we might - // still find the tile actor in OutWorld. - FoundActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); - } - } - - return FoundActor; -} - -bool -FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh( - UHoudiniAssetComponent* InHoudiniAssetComponent, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess, - bool& bOutNeedsReCook) -{ - if (!IsValid(InHoudiniAssetComponent)) - { - return false; - } - - // Handle proxies: if the output has any current proxies, first refine them - bOutNeedsReCook = false; - if (InHoudiniAssetComponent->HasAnyCurrentProxyOutput()) - { - bool bNeedsRebuildOrDelete; - bool bInvalidState; - const bool bCookedDataAvailable = InHoudiniAssetComponent->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bInvalidState); - - if (bCookedDataAvailable) - { - // Cook data is available, refine the mesh - AHoudiniAssetActor* HoudiniActor = Cast(InHoudiniAssetComponent->GetOwner()); - if (IsValid(HoudiniActor)) - { - FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ HoudiniActor }); - } - } - else if (!bNeedsRebuildOrDelete && !bInvalidState) - { - // A cook is needed: request the cook, but with no proxy and with a bake after cook - InHoudiniAssetComponent->SetNoProxyMeshNextCookRequested(true); - // Only - if (!InHoudiniAssetComponent->IsBakeAfterNextCookEnabled() || !InHoudiniAssetComponent->GetOnPostCookBakeDelegate().IsBound()) - { - InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess](UHoudiniAssetComponent* InHAC) { - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess); - }); - } - InHoudiniAssetComponent->MarkAsNeedCook(); - - bOutNeedsReCook = true; - - // The cook has to complete first (asynchronously) before the bake can happen - // The SetBakeAfterNextCookEnabled flag will result in a bake after cook - return false; - } - else - { - // The HAC is in an unsupported state - const EHoudiniAssetState AssetState = InHoudiniAssetComponent->GetAssetState(); - HOUDINI_LOG_ERROR(TEXT("Could not refine (in order to bake) %s, the asset is in an unsupported state: %s"), *(InHoudiniAssetComponent->GetPathName()), *(UEnum::GetValueAsString(AssetState))); - return false; - } - } - - return true; -} - -void -FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor) -{ - if (!IsValid(InActor)) - return; - - USceneComponent * const RootComponent = InActor->GetRootComponent(); - if (!IsValid(RootComponent)) - return; - - // If the root component does not have any child components, then there is nothing to recenter - if (RootComponent->GetNumChildrenComponents() <= 0) - return; - - const bool bOnlyCollidingComponents = false; - const bool bIncludeFromChildActors = true; - FVector Origin; - FVector BoxExtent; - InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); - - const FVector Delta = Origin - RootComponent->GetComponentLocation(); - // Actor->SetActorLocation(Origin); - RootComponent->SetWorldLocation(Origin); - - for (USceneComponent* SceneComponent : RootComponent->GetAttachChildren()) - { - if (!IsValid(SceneComponent)) - continue; - - SceneComponent->SetWorldLocation(SceneComponent->GetComponentLocation() - Delta); - } -} - -void -FHoudiniEngineBakeUtils::CenterActorsToBoundingBoxCenter(const TArray& InActors) -{ - for (AActor* Actor : InActors) - { - if (!IsValid(Actor)) - continue; - - CenterActorToBoundingBoxCenter(Actor); - } -} - -USceneComponent* -FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMissing, EComponentMobility::Type InMobilityIfCreated) -{ - USceneComponent* RootComponent = InActor->GetRootComponent(); - if (!IsValid(RootComponent)) - { - RootComponent = NewObject(InActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); - - // Change the creation method so the component is listed in the details panels - InActor->SetRootComponent(RootComponent); - InActor->AddInstanceComponent(RootComponent); - RootComponent->RegisterComponent(); - RootComponent->SetMobility(InMobilityIfCreated); - } - - return RootComponent; -} - -FName -FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed) -{ - if (IsValid(InObjectThatWouldBeRenamed)) - { - const FName CurrentName = InObjectThatWouldBeRenamed->GetFName(); - if (CurrentName == InName) - return InName; - - // Check if the prefix matches (without counter suffix) the new name - const FString CurrentNamePlainStr = CurrentName.GetPlainNameString(); - if (CurrentNamePlainStr == InName.ToString()) - return CurrentName; - } - - UObject* ExistingObject = nullptr; - if (InOuter == ANY_PACKAGE) - { - ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *InName.ToString()); - } - else - { - ExistingObject = StaticFindObjectFast(nullptr, InOuter, InName); - } - - if (ExistingObject) - return MakeUniqueObjectName(InOuter, InClass, InName); - return InName; -} - -FName -FHoudiniEngineBakeUtils::GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) -{ - const FString* FolderPathPtr = InOutputObject.CachedAttributes.Find(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER); - if (FolderPathPtr && !FolderPathPtr->IsEmpty()) - return FName(*FolderPathPtr); - else - return InDefaultFolder; -} - -bool -FHoudiniEngineBakeUtils::SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) -{ - if (!IsValid(InActor)) - return false; - - InActor->SetFolderPath(GetOutlinerFolderPath(InOutputObject, InDefaultFolder)); - return true; -} - -uint32 -FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInDestroyBakedComponent, - bool bInDestroyBakedInstancedActors, - bool bInDestroyBakedInstancedComponents) -{ - uint32 NumDeleted = 0; - - if (bInDestroyBakedComponent) - { - UActorComponent* Component = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (Component) - { - if (RemovePreviouslyBakedComponent(Component)) - { - InBakedOutputObject.BakedComponent = nullptr; - NumDeleted++; - } - } - } - - if (bInDestroyBakedInstancedActors) - { - for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) - { - const FSoftObjectPath ActorPath(ActorPathStr); - - if (!ActorPath.IsValid()) - continue; - - AActor* Actor = Cast(ActorPath.TryLoad()); - if (IsValid(Actor)) - { - UWorld* World = Actor->GetWorld(); - if (IsValid(World)) - { -#if WITH_EDITOR - World->EditorDestroyActor(Actor, true); -#else - World->DestroyActor(Actor); -#endif - NumDeleted++; - } - } - } - InBakedOutputObject.InstancedActors.Empty(); - } - - if (bInDestroyBakedInstancedComponents) - { - for (const FString& ComponentPathStr : InBakedOutputObject.InstancedComponents) - { - const FSoftObjectPath ComponentPath(ComponentPathStr); - - if (!ComponentPath.IsValid()) - continue; - - UActorComponent* Component = Cast(ComponentPath.TryLoad()); - if (IsValid(Component)) - { - if (RemovePreviouslyBakedComponent(Component)) - NumDeleted++; - } - } - InBakedOutputObject.InstancedComponents.Empty(); - } - - return NumDeleted; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineBakeUtils.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngineUtils.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutput.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineUtils.h" +#include "UnrealLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniStringResolver.h" +#include "HoudiniEngineCommands.h" + +#include "Engine/StaticMesh.h" +#include "Engine/World.h" +#include "RawMesh.h" +#include "UObject/Package.h" +#include "PackageTools.h" +#include "UObject/MetaData.h" +#include "AssetRegistryModule.h" +#include "Materials/Material.h" +#include "LandscapeProxy.h" +#include "LandscapeStreamingProxy.h" +#include "LandscapeInfo.h" +#include "Factories/WorldFactory.h" +#include "AssetToolsModule.h" +#include "InstancedFoliageActor.h" +#include "Components/SplineComponent.h" +#include "GameFramework/Actor.h" +#include "Engine/StaticMeshActor.h" +#include "Components/StaticMeshComponent.h" +#include "PhysicsEngine/BodySetup.h" +#include "ActorFactories/ActorFactoryStaticMesh.h" +#include "ActorFactories/ActorFactoryEmptyActor.h" +#include "BusyCursor.h" +#include "Editor.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "FileHelpers.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngine.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "Editor/EditorEngine.h" +#include "Factories/BlueprintFactory.h" +#include "Engine/SimpleConstructionScript.h" +#include "Misc/Paths.h" +#include "HAL/FileManager.h" +#include "LandscapeEdit.h" +#include "Containers/UnrealString.h" +#include "Components/AudioComponent.h" +#include "Engine/WorldComposition.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "MaterialEditor/Public/MaterialEditingLibrary.h" +#include "MaterialGraph/MaterialGraph.h" +#include "Particles/ParticleSystemComponent.h" +#include "Sound/SoundBase.h" +#include "UObject/UnrealType.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() + : Actor(nullptr) + , OutputIndex(INDEX_NONE) + , OutputObjectIdentifier() + , ActorBakeName(NAME_None) + , BakedObject(nullptr) + , SourceObject(nullptr) +{ +} + +FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( + AActor* InActor, + FName InActorBakeName, + FName InWorldOutlinerFolder, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + UObject* InBakedObject, + UObject* InSourceObject) + : Actor(InActor) + , OutputIndex(InOutputIndex) + , OutputObjectIdentifier(InOutputObjectIdentifier) + , ActorBakeName(InActorBakeName) + , WorldOutlinerFolder(InWorldOutlinerFolder) + , BakedObject(InBakedObject) + , SourceObject(InSourceObject) +{ +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + UHoudiniAssetComponent* InHACToBake, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess) +{ + if (!IsValid(InHACToBake)) + return false; + + // Handle proxies: if the output has any current proxies, first refine them + bool bHACNeedsToReCook; + if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bHACNeedsToReCook)) + { + // Either the component is invalid, or needs a recook to refine a proxy mesh + return false; + } + + bool bSuccess = false; + switch (InBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake); + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake); + } + break; + + case EHoudiniEngineBakeOption::ToFoliage: + { + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake); + } + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + { + //Todo + bSuccess = false; + } + break; + + } + + if (bSuccess && bInRemoveHACOutputOnSuccess) + FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + TArray NewActors; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + if (!FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats)) + { + // TODO ? + HOUDINI_LOG_WARNING(TEXT("Errors when baking")); + } + + // Save the created packages + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && NewActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : NewActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (HoudiniAssetComponent->bRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && NewActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); + if (!IsValid(OwnerActor)) + return false; + + const FString HoudiniAssetName = OwnerActor->GetName(); + + // Get an array of the outputs + const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); + TArray Outputs; + Outputs.Reserve(NumOutputs); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + Outputs.Add(HoudiniAssetComponent->GetOutputAt(OutputIdx)); + } + + // Get the previous bake objects and grow/shrink to match asset outputs + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + // Ensure we have an entry for each output + if (BakedOutputs.Num() != NumOutputs) + BakedOutputs.SetNum(NumOutputs); + + return BakeHoudiniOutputsToActors( + Outputs, + BakedOutputs, + HoudiniAssetName, + HoudiniAssetComponent->GetComponentTransform(), + HoudiniAssetComponent->BakeFolder, + HoudiniAssetComponent->TemporaryCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutNewActors, + OutPackagesToSave, + OutBakeStats, + InOutputTypesToBake, + InInstancerComponentTypesToBake, + InFallbackActor, + InFallbackWorldOutlinerFolder); +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( + const TArray& InOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FTransform& InParentTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + const int32 NumOutputs = InOutputs.Num(); + + const FString MsgTemplate = TEXT("Baking output: {0}/{1}."); + FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs }); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg)); + + TArray BakedActors; + + // First bake everything except instancers, then bake instancers. Since instancers might use meshes in + // from the other outputs. + bool bHasAnyInstancers = false; + int32 NumProcessedOutputs = 0; + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + UHoudiniOutput* Output = InOutputs[OutputIdx]; + if (!Output || Output->IsPendingKill()) + { + NumProcessedOutputs++; + continue; + } + + Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); + + const EHoudiniOutputType OutputType = Output->GetType(); + // Check if we should skip this output type + if (InOutputTypesToBake && InOutputTypesToBake->Find(OutputType) == INDEX_NONE) + { + NumProcessedOutputs++; + continue; + } + + switch (OutputType) + { + case EHoudiniOutputType::Mesh: + { + FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + OutputIdx, + InOutputs, + InBakedOutputs, + InHoudiniAssetName, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + break; + + case EHoudiniOutputType::Instancer: + { + if (!bHasAnyInstancers) + bHasAnyInstancers = true; + NumProcessedOutputs--; + } + break; + + case EHoudiniOutputType::Landscape: + { + const bool bResult = BakeLandscape( + OutputIdx, + Output, + InBakedOutputs[OutputIdx].BakedOutputObjects, + bInReplaceActors, + bInReplaceAssets, + InBakeFolder.Path, + InHoudiniAssetName, + OutBakeStats); + } + break; + + case EHoudiniOutputType::Skeletal: + break; + + case EHoudiniOutputType::Curve: + { + FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( + Output, + InBakedOutputs[OutputIdx].BakedOutputObjects, + InBakedOutputs, + InHoudiniAssetName, + InBakeFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + break; + + case EHoudiniOutputType::Invalid: + break; + } + + NumProcessedOutputs++; + } + + if (bHasAnyInstancers) + { + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + UHoudiniOutput* Output = InOutputs[OutputIdx]; + if (!Output || Output->IsPendingKill()) + { + NumProcessedOutputs++; + continue; + } + + Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); + + if (Output->GetType() == EHoudiniOutputType::Instancer) + { + FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + OutputIdx, + InOutputs, + InBakedOutputs, + InParentTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + OutPackagesToSave, + InInstancerComponentTypesToBake, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + + NumProcessedOutputs++; + } + } + + OutNewActors.Append(BakedActors); + + return true; +} + + +bool +FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n) + { + UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n); + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + continue; + + if (Output->GetInstancedOutputs().Num() > 0) + return true; + /* + // TODO: Is this needed? check we have components to bake? + for (auto& OutputObjectPair : Output->GetOutputObjects()) + { + if (OutputObjectPair.Value.OutputCompoent!= nullpt) + return true; + } + */ + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor * OwnerActor = HoudiniAssetComponent->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return false; + + int32 BakedCount = 0; + TArray PackagesToSave; + + FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); + + // Build an array of the outputs so that we can search for meshes/previous baked meshes + const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); + TArray Outputs; + Outputs.Reserve(NumOutputs); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) + { + UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); + if (!Output || Output->IsPendingKill()) + continue; + + Outputs.Add(Output); + } + + // Get the previous bake outputs and match the output array size + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + if (BakedOutputs.Num() != NumOutputs) + BakedOutputs.SetNum(NumOutputs); + + // Map storing original and baked Static Meshes + TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) + { + UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + continue; + + // TODO: No need to use the instanced outputs for this + // We should simply iterate on the Output Objects instead! + TMap& OutputObjects = Output->GetOutputObjects(); + TMap& InstancedOutputs = Output->GetInstancedOutputs(); + for (auto & Pair : InstancedOutputs) + { + FString InstanceName = OwnerActor->GetName(); + + // // See if we have a bake name for that output + // FHoudiniOutputObject* OutputObj = OutputObjects.Find(Pair.Key); + // if (OutputObj && OutputObj->BakeName.IsEmpty()) + // InstanceName = OutputObj->BakeName; + + FHoudiniInstancedOutput& InstancedOutput = Pair.Value; + for (int32 VariarionIdx = 0; VariarionIdx < InstancedOutput.VariationObjects.Num(); ++VariarionIdx) + { + // TODO: !!! what if the instanced object/var is not a static mesh!!!!!! + UObject* CurrentVariationObject = InstancedOutput.VariationObjects[VariarionIdx].Get(); + UStaticMesh* InstancedStaticMesh = Cast(CurrentVariationObject); + if (!InstancedStaticMesh) + { + if (CurrentVariationObject) + { + HOUDINI_LOG_ERROR(TEXT("Failed to bake the instances of %s to Foliage"), *CurrentVariationObject->GetName()); + } + continue; + } + + // Check if we have already handled this mesh (already baked it from a previous variation), if so, + // use that + UStaticMesh* OutStaticMesh = nullptr; + bool bCreateNewType = true; + if (OriginalToBakedMesh.Contains(InstancedStaticMesh)) + { + OutStaticMesh = OriginalToBakedMesh.FindChecked(InstancedStaticMesh); + bCreateNewType = false; + } + + if (!IsValid(OutStaticMesh)) + { + // Find the output object and identifier for the mesh and previous bake of the mesh (if it exists) + FString ObjectName; + int32 MeshOutputIdx = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshOutputIdentifier; + UStaticMesh* PreviousBakeMesh = nullptr; + FHoudiniBakedOutputObject* BakedOutputObject = nullptr; + if (FindOutputObject(InstancedStaticMesh, Outputs, MeshOutputIdx, MeshOutputIdentifier)) + { + GetTemporaryOutputObjectBakeName(InstancedStaticMesh, Outputs, ObjectName); + + BakedOutputObject = &BakedOutputs[MeshOutputIdx].BakedOutputObjects.FindOrAdd(MeshOutputIdentifier); + if (BakedOutputObject) + { + PreviousBakeMesh = Cast(BakedOutputObject->GetBakedObjectIfValid()); + } + } + else + { + ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); + } + + // If the instanced static mesh is still a temporary Houdini created Static Mesh + // we will duplicate/bake it first before baking to foliage + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + MeshOutputIdentifier, + HoudiniAssetComponent->BakeFolder.Path, + ObjectName, + OwnerActor->GetName(), + AssetPackageReplaceMode); + + // DuplicateStaticMeshAndCreatePackageIfNeeded uses baked results to find a baked version of + // InstancedStaticMesh in the current bake results, but since we are already using + // OriginalToBakedMesh we don't have to populate BakedResults + const TArray BakedResults; + OutStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + InstancedStaticMesh, + PreviousBakeMesh, + PackageParams, + Outputs, + BakedResults, + HoudiniAssetComponent->TemporaryCookFolder.Path, + PackagesToSave); + OriginalToBakedMesh.Add(InstancedStaticMesh, OutStaticMesh); + + // Update our tracked baked output + if (BakedOutputObject) + BakedOutputObject->BakedObject = FSoftObjectPath(OutStaticMesh).ToString(); + + bCreateNewType = true; + } + + // See if we already have a FoliageType for that static mesh + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(OutStaticMesh); + if (!FoliageType || FoliageType->IsPendingKill()) + { + // We need to create a new FoliageType for this Static Mesh + // TODO: Add foliage default settings + InstancedFoliageActor->AddMesh(OutStaticMesh, &FoliageType); + bCreateNewType = true; + } + + // If we are baking in replace mode, remove the foliage type if it already exists + // and a create a new one + if (bInReplaceAssets && bCreateNewType && IsValid(FoliageType)) + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if (!FoliageInfo) + continue; + + // Apply the transform offset on the transforms for this variation + TArray ProcessedTransforms; + FHoudiniInstanceTranslator::ProcessInstanceTransforms(InstancedOutput, VariarionIdx, ProcessedTransforms); + + FFoliageInstance FoliageInstance; + int32 CurrentInstanceCount = 0; + for (auto CurrentTransform : ProcessedTransforms) + { + FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); + FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + CurrentInstanceCount++; + } + + // TODO: This was due to a bug in UE4.22-20, check if still needed! + if (FoliageInfo->GetComponent()) + FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); + + // Notify the user that we succesfully bake the instances to foliage + FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + OutStaticMesh->GetName() + TEXT(" to Foliage"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + BakedCount += ProcessedTransforms.Num(); + } + } + } + + InstancedFoliageActor->RegisterAllComponents(); + + // Update / repopulate the foliage editor mode's mesh list + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + + if (BakedCount > 0) + { + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + return true; + } + + return false; +} + + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; + if (!InOutput || InOutput->IsPendingKill()) + return false; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + + // Ensure we have the same number of baked outputs and asset outputs + if (InBakedOutputs.Num() != InAllOutputs.Num()) + InBakedOutputs.SetNum(InAllOutputs.Num()); + + // Iterate on the output objects, baking their object/component as we go + for (auto& Pair : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = Pair.Value; + FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputs[InOutputIndex].BakedOutputObjects.FindOrAdd(Pair.Key); + + if (CurrentOutputObject.bProxyIsCurrent) + { + // TODO: we need to refine the SM first! + // ?? + } + + if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill()) + continue; + + if (CurrentOutputObject.OutputComponent->IsA()) + { + // TODO: Baking foliage instancer to actors it not supported currently + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent))) + { + BakeInstancerOutputToActors_ISMC( + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent))) + { + BakeInstancerOutputToActors_IAC( + InOutputIndex, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) + { + BakeInstancerOutputToActors_MSIC( + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) + { + BakeInstancerOutputToActors_SMC( + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else + { + // Unsupported component! + } + + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UInstancedStaticMeshComponent * InISMC = Cast(InOutputObject.OutputComponent); + if (!InISMC || InISMC->IsPendingKill()) + return false; + + AActor * OwnerActor = InISMC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh * StaticMesh = InISMC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its + // name from its package. + FString ObjectName; + if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) + { + // Not found in HDA/temp outputs, use its package name + ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + } + + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + // See if the instanced static mesh is still a temporary Houdini created Static Mesh + // If it is, we need to bake the StaticMesh first + FHoudiniPackageParams PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + InOutputObjectIdentifier, + InBakeFolder.Path, + // ObjectName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), + ObjectName, + OwnerActor->GetName(), + AssetPackageReplaceMode); + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, PackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + + // Update the baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + + // Access some of the attribute that were cached on the output object + FHoudiniAttributeResolver Resolver; + { + TMap CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if(!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + /* + // TODO: Get the bake name! + // Bake override, the output name + // The bake name override has priority + FString InstancerName = InOutputObject.BakeName; + if (InstancerName.IsEmpty()) + { + // .. then use the output name + InstancerName = Resolver.ResolveOutputName(); + } + */ + + // Should we create one actor with an ISMC or multiple actors with one SMC? + bool bSpawnMultipleSMC = false; + if (bSpawnMultipleSMC) + { + // TODO: Double check, Has a crash here! + + // Get the StaticMesh ActorFactory + UActorFactory* SMFactory = nullptr; + + if (!FoundActor) + { + SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!SMFactory) + return false; + } + + // Split the instances to multiple StaticMeshActors + for (int32 InstanceIdx = 0; InstanceIdx < InISMC->GetInstanceCount(); InstanceIdx++) + { + FTransform InstanceTransform; + InISMC->GetInstanceTransform(InstanceIdx, InstanceTransform, true); + + if (!FoundActor) + { + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform, RF_Transactional); + if (!FoundActor || FoundActor->IsPendingKill()) + continue; + } + + FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName, FoundActor); + // FoundActor->Rename(*NewName.ToString()); + // FoundActor->SetActorLabel(NewName.ToString()); + RenameAndRelabelActor(FoundActor, NewName.ToString(), false); + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + continue; + + // Copy properties from the existing component + CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC); + + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh)); + } + } + else + { + bool bSpawnedActor = false; + if (!FoundActor) + { + // Only create one actor + FActorSpawnParameters SpawnInfo; + SpawnInfo.OverrideLevel = DesiredLevel; + SpawnInfo.ObjectFlags = RF_Transactional; + SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); + SpawnInfo.bDeferConstruction = true; + + // Spawn the new Actor + FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + bSpawnedActor = true; + + FoundActor->SetActorLabel(FoundActor->GetName()); + FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); + } + else + { + // If there is a previously baked component, and we are in replace mode, remove it + if (bInReplaceAssets) + { + USceneComponent* InPrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(InPrevComponent) && InPrevComponent->GetOwner() == FoundActor) + RemovePreviouslyBakedComponent(InPrevComponent); + } + + const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); + } + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Get/create the actor's root component + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + if (bSpawnedActor && IsValid(RootComponent)) + RootComponent->SetWorldTransform(InTransform); + + // Duplicate the instancer component, create a Hierarchical ISMC if needed + UInstancedStaticMeshComponent* NewISMC = nullptr; + UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast(InISMC); + if (InHISMC) + { + NewISMC = DuplicateObject( + InHISMC, + FoundActor, + MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetFName())); + } + else + { + NewISMC = DuplicateObject( + InISMC, + FoundActor, + MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetFName())); + } + + if (!NewISMC) + { + //DesiredLevel->OwningWorld-> + return false; + } + + InBakedOutputObject.BakedComponent = FSoftObjectPath(NewISMC).ToString(); + + NewISMC->RegisterComponent(); + // NewISMC->SetupAttachment(nullptr); + NewISMC->SetStaticMesh(BakedStaticMesh); + FoundActor->AddInstanceComponent(NewISMC); + // NewActor->SetRootComponent(NewISMC); + if (IsValid(RootComponent)) + NewISMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + NewISMC->SetWorldTransform(InISMC->GetComponentTransform()); + + // TODO: do we need to copy properties here, we duplicated the component + // // Copy properties from the existing component + // CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); + + if (bSpawnedActor) + FoundActor->FinishSpawning(InTransform); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh)); + + FoundActor->InvalidateLightingCache(); + FoundActor->PostEditMove(true); + FoundActor->MarkPackageDirty(); + } + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UStaticMeshComponent* InSMC = Cast(InOutputObject.OutputComponent); + if (!InSMC || InSMC->IsPendingKill()) + return false; + + AActor* OwnerActor = InSMC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh* StaticMesh = InSMC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its + // name from its package. + FString ObjectName; + if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) + { + // Not found in HDA/temp outputs, use its package name + ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + } + + // BaseName holds the Actor / HDA name + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + // See if the instanced static mesh is still a temporary Houdini created Static Mesh + // If it is, we need to bake the StaticMesh first + FHoudiniPackageParams PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + InOutputObjectIdentifier, + InBakeFolder.Path, + // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), + ObjectName, + OwnerActor->GetName(), + AssetPackageReplaceMode); + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, + OutActors, InTempCookFolder.Path, OutPackagesToSave); + + // Update the previous baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + + // Access some of the attribute that were cached on the output object + FHoudiniAttributeResolver Resolver; + { + TMap CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + UStaticMeshComponent* StaticMeshComponent = nullptr; + // Create an actor if we didn't find one + if (!FoundActor) + { + // Get the StaticMesh ActorFactory + UActorFactory* SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!SMFactory) + return false; + + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + return false; + + StaticMeshComponent = SMActor->GetStaticMeshComponent(); + } + else + { + USceneComponent* RootComponent = GetActorRootComponent(FoundActor); + if (!IsValid(RootComponent)) + return false; + + if (bInReplaceAssets) + { + // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it + UStaticMeshComponent* PrevSMC = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) + { + StaticMeshComponent = PrevSMC; + } + } + + if (!IsValid(StaticMeshComponent)) + { + // Create a new static mesh component + StaticMeshComponent = NewObject(FoundActor, NAME_None, RF_Transactional); + + FoundActor->AddInstanceComponent(StaticMeshComponent); + StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + StaticMeshComponent->RegisterComponent(); + } + } + + FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName, FoundActor); + // FoundActor->Rename(*NewName.ToString()); + // FoundActor->SetActorLabel(NewName.ToString()); + RenameAndRelabelActor(FoundActor, NewName.ToString(), false); + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Update the previous baked component + InBakedOutputObject.BakedComponent = FSoftObjectPath(StaticMeshComponent).ToString(); + + if (!IsValid(StaticMeshComponent)) + return false; + + // Copy properties from the existing component + CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC); + StaticMeshComponent->SetStaticMesh(BakedStaticMesh); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh)); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave) +{ + UHoudiniInstancedActorComponent* InIAC = Cast(InOutputObject.OutputComponent); + if (!InIAC || InIAC->IsPendingKill()) + return false; + + AActor * OwnerActor = InIAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + // BaseName holds the Actor / HDA name + const FName BaseName = FName(OwnerActor->GetName()); + + // Get the object instanced by this IAC + UObject* InstancedObject = InIAC->GetInstancedObject(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + return false; + + FHoudiniPackageParams PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + InOutputObjectIdentifier, + InBakeFolder.Path, + BaseName.ToString(), + OwnerActor->GetName(), + AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Access some of the attribute that were cached on the output object + FHoudiniAttributeResolver Resolver; + { + TMap CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // If we are baking in actor replacement mode, remove any previously baked instanced actors for this output + if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0) + { + UWorld* LevelWorld = DesiredLevel->GetWorld(); + if (IsValid(LevelWorld)) + { + for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) + { + const FSoftObjectPath ActorPath(ActorPathStr); + + if (!ActorPath.IsValid()) + continue; + + AActor* Actor = Cast(ActorPath.TryLoad()); + // Destroy Actor if it is valid and part of DesiredLevel + if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel) + { +#if WITH_EDITOR + LevelWorld->EditorDestroyActor(Actor, true); +#else + LevelWorld->DestroyActor(Actor); +#endif + } + } + } + } + + // Empty and reserve enough space for new instanced actors + InBakedOutputObject.InstancedActors.Empty(InIAC->GetInstancedActors().Num()); + + // Iterates on all the instances of the IAC + for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors()) + { + if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) + continue; + + FName NewInstanceName = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName); + FString NewNameStr = NewInstanceName.ToString(); + + FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); + AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); + if (!NewActor || NewActor->IsPendingKill()) + continue; + + const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) + (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); + + EditorUtilities::CopyActorProperties(CurrentInstancedActor, NewActor); + + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); + + NewActor->SetActorLabel(NewNameStr); + SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); + NewActor->SetActorTransform(CurrentTransform); + + InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); + + OutActors.Add(FHoudiniEngineBakedActor( + NewActor, + BaseName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + nullptr, + InstancedObject)); + } + + // TODO: + // Move Actors to DesiredLevel if needed?? + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = true; + const bool bInDestroyBakedInstancedActors = false; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UHoudiniMeshSplitInstancerComponent * InMSIC = Cast(InOutputObject.OutputComponent); + if (!InMSIC || InMSIC->IsPendingKill()) + return false; + + AActor * OwnerActor = InMSIC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh * StaticMesh = InMSIC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its + // name from its package. + FString ObjectName; + if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) + { + // Not found in HDA/temp outputs, use its package name + ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + } + + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + // See if the instanced static mesh is still a temporary Houdini created Static Mesh + // If it is, we need to bake the StaticMesh first + FHoudiniPackageParams PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + InOutputObjectIdentifier, + InBakeFolder.Path, + // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), + ObjectName, + OwnerActor->GetName(), + AssetPackageReplaceMode); + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, + OutActors, InTempCookFolder.Path, OutPackagesToSave); + + // Update the baked output + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + + // Get the level specified by attribute + // Access some of the attributes that were cached on the output object + FHoudiniAttributeResolver Resolver; + { + TMap CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + bool bSpawnedActor = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + if (!FoundActor) + { + // This is a split mesh instancer component - we will create a generic AActor with a bunch of SMC + FActorSpawnParameters SpawnInfo; + SpawnInfo.OverrideLevel = DesiredLevel; + SpawnInfo.ObjectFlags = RF_Transactional; + SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); + SpawnInfo.bDeferConstruction = true; + + // Spawn the new Actor + FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + bSpawnedActor = true; + + FoundActor->SetActorLabel(FoundActor->GetName()); + FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); + } + else + { + // If we are baking in replacement mode, remove the previous components (if they belong to FoundActor) + for (const FString& PrevComponentPathStr : InBakedOutputObject.InstancedComponents) + { + const FSoftObjectPath PrevComponentPath(PrevComponentPathStr); + + if (!PrevComponentPath.IsValid()) + continue; + + UActorComponent* PrevComponent = Cast(PrevComponentPath.TryLoad()); + if (!IsValid(PrevComponent) || PrevComponent->GetOwner() != FoundActor) + continue; + + RemovePreviouslyBakedComponent(PrevComponent); + } + + const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); + } + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Get/create the actor's root component + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + if (bSpawnedActor && IsValid(RootComponent)) + RootComponent->SetWorldTransform(InTransform); + + // Empty and reserve enough space in the baked components array for the new components + InBakedOutputObject.InstancedComponents.Empty(InMSIC->GetInstances().Num()); + + // Now add s SMC component for each of the SMC's instance + for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances()) + { + if (!CurrentSMC || CurrentSMC->IsPendingKill()) + continue; + + UStaticMeshComponent* NewSMC = DuplicateObject( + CurrentSMC, + FoundActor, + MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetFName())); + if (!NewSMC || NewSMC->IsPendingKill()) + continue; + + InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString()); + + NewSMC->RegisterComponent(); + // NewSMC->SetupAttachment(nullptr); + NewSMC->SetStaticMesh(BakedStaticMesh); + FoundActor->AddInstanceComponent(NewSMC); + NewSMC->SetWorldTransform(CurrentSMC->GetComponentTransform()); + if (IsValid(RootComponent)) + NewSMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); + + // TODO: Do we need to copy properties here, we duplicated the component + // // Copy properties from the existing component + // CopyPropertyToNewActorAndComponent(FoundActor, NewSMC, CurrentSMC); + } + + if (bSpawnedActor) + FoundActor->FinishSpawning(InTransform); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh)); + + FoundActor->InvalidateLightingCache(); + FoundActor->PostEditMove(true); + FoundActor->MarkPackageDirty(); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = true; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = false; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::FindHGPO( + const FHoudiniOutputObjectIdentifier& InIdentifier, + const TArray& InHGPOs, + FHoudiniGeoPartObject const*& OutHGPO) +{ + // Find the HGPO that matches this output identifier + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + for (auto & NextHGPO : InHGPOs) + { + // We use Matches() here as it handles the case where the HDA was loaded, + // which likely means that the the obj/geo/part ids dont match the output identifier + if(InIdentifier.Matches(NextHGPO)) + { + FoundHGPO = &NextHGPO; + break; + } + } + + OutHGPO = FoundHGPO; + return !OutHGPO; +} + +void +FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const FHoudiniOutputObject& InMeshOutputObject, + FString& OutBakeName) +{ + // The bake name override has priority + OutBakeName = InMeshOutputObject.BakeName; + if (OutBakeName.IsEmpty()) + { + FHoudiniAttributeResolver Resolver; + Resolver.SetCachedAttributes(InMeshOutputObject.CachedAttributes); + Resolver.SetTokensFromStringMap(InMeshOutputObject.CachedTokens); + const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InObject); + // The default output name (if not set via attributes) is {object_name}, which look for an object_name + // key-value token + if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) + Resolver.SetToken(TEXT("object_name"), DefaultObjectName); + OutBakeName = Resolver.ResolveOutputName(); + // const TArray& HGPOs = InAllOutputs[MeshOutputIdx]->GetHoudiniGeoPartObjects(); + // const FHoudiniGeoPartObject* FoundHGPO = nullptr; + // FindHGPO(MeshIdentifier, HGPOs, FoundHGPO); + // // ... finally the part name + // if (FoundHGPO && FoundHGPO->bHasCustomPartName) + // OutBakeName = FoundHGPO->PartName; + if (OutBakeName.IsEmpty()) + OutBakeName = DefaultObjectName; + } +} + +bool +FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const TArray& InAllOutputs, + FString& OutBakeName) +{ + if (!IsValid(InObject)) + return false; + + OutBakeName.Empty(); + + int32 MeshOutputIdx = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + if (FindOutputObject(InObject, InAllOutputs, MeshOutputIdx, MeshIdentifier)) + { + // Found the mesh, get its name + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier); + GetTemporaryOutputObjectBakeName(InObject, MeshOutputObject, OutBakeName); + + return true; + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; + if (!InOutput || InOutput->IsPendingKill()) + return false; + + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!Factory) + return false; + + TMap& OutputObjects = InOutput->GetOutputObjects(); +const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); + + // Get the previous bake objects + if (InOutputIndex >= 0 && !InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + TMap& BakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + + for (auto& Pair : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + const FHoudiniOutputObject& OutputObject = Pair.Value; + + // Fetch previous bake output + FHoudiniBakedOutputObject& BakedOutputObject = BakedOutputObjects.FindOrAdd(Identifier); + + UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); + if (!StaticMesh || StaticMesh->IsPendingKill()) + continue; + + UStaticMeshComponent* InSMC = Cast(OutputObject.OutputComponent); + if (!InSMC || InSMC->IsPendingKill()) + continue; + + // Find the HGPO that matches this output identifier + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + FindHGPO(Identifier, HGPOs, FoundHGPO); + + // We do not bake templated geos + if (FoundHGPO && FoundHGPO->bIsTemplated) + continue; + + FHoudiniAttributeResolver Resolver; + Resolver.SetCachedAttributes(OutputObject.CachedAttributes); + Resolver.SetTokensFromStringMap(OutputObject.CachedTokens); + const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + // The default output name (if not set via attributes) is {object_name}, which look for an object_name + // key-value token + if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) + Resolver.SetToken(TEXT("object_name"), DefaultObjectName); + + // The bake name override has priority + FString SMName = OutputObject.BakeName; + if (SMName.IsEmpty()) + { + // // ... finally the part name + // if (FoundHGPO && FoundHGPO->bHasCustomPartName) + // SMName = FoundHGPO->PartName; + // else + SMName = Resolver.ResolveOutputName(); + if (SMName.IsEmpty()) + SMName = DefaultObjectName; + } + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, Identifier, InBakeFolder.Path, SMName, + InHoudiniAssetName, AssetPackageReplaceMode); + + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); + + UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + // See if this output object has an unreal_level_path attribute specified + // In which case, we need to create/find the desired level for baking instead of using the current one + bool bHasLevelPathAttribute = OutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Access some of the attribute that were cached on the output object + // FHoudiniAttributeResolver Resolver; + // const TMap& CachedAttributes = OutputObject.CachedAttributes; + TMap Tokens = OutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + // Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + continue; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add the level to the packages to save? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + // Bake the static mesh if it is still temporary + UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, + Cast(BakedOutputObject.GetBakedObjectIfValid()), + PackageParams, + InAllOutputs, + OutActors, + InTempCookFolder.Path, + OutPackagesToSave); + + if (!BakedSM || BakedSM->IsPendingKill()) + continue; + + // Record the baked object + BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString(); + + // Make sure we have a level to spawn to + if (!DesiredLevel || DesiredLevel->IsPendingKill()) + continue; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + UStaticMeshComponent* SMC = nullptr; + if (!FoundActor) + { + // Spawn the new actor + FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); + if (!FoundActor || FoundActor->IsPendingKill()) + continue; + + // Copy properties to new actor + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + continue; + + SMC = SMActor->GetStaticMeshComponent(); + } + else + { + if (bInReplaceAssets) + { + // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it + UStaticMeshComponent* PrevSMC = Cast(BakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) + { + SMC = PrevSMC; + } + } + + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + + if (!IsValid(SMC)) + { + // Create a new static mesh component on the existing actor + SMC = NewObject(FoundActor, NAME_None, RF_Transactional); + + FoundActor->AddInstanceComponent(SMC); + if (IsValid(RootComponent)) + SMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + else + FoundActor->SetRootComponent(SMC); + SMC->RegisterComponent(); + } + } + + // We need to make a unique name for the actor, renaming an object on top of another is a fatal error + const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName, FoundActor); + const FString NewNameStr = NewName.ToString(); + // FoundActor->Rename(*NewNameStr); + // FoundActor->SetActorLabel(NewNameStr); + RenameAndRelabelActor(FoundActor, NewNameStr, false); + SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath); + + if (IsValid(SMC)) + { + CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC); + SMC->SetStaticMesh(BakedSM); + BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString(); + } + + BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh)); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + BakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( + UHoudiniOutput* Output, + TMap& InBakedOutputObjects, + const TArray& InAllBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!Output || Output->IsPendingKill()) + return false; + + TArray PackagesToSave; + + TMap& OutputObjects = Output->GetOutputObjects(); + const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); + + for (auto & Pair : OutputObjects) + { + FHoudiniOutputObject& OutputObject = Pair.Value; + USplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + FHoudiniOutputObjectIdentifier & Identifier = Pair.Key; + FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(Identifier); + + // TODO: FIX ME!! May not work 100% + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + for (auto & NextHGPO : HGPOs) + { + if (Identifier.GeoId == NextHGPO.GeoId && + Identifier.ObjectId == NextHGPO.ObjectId && + Identifier.PartId == NextHGPO.PartId) + { + FoundHGPO = &NextHGPO; + break; + } + } + + if (!FoundHGPO) + continue; + + FString CurveName = Pair.Value.BakeName; + if (CurveName.IsEmpty()) + { + if (FoundHGPO->bHasCustomPartName) + CurveName = FoundHGPO->PartName; + else + CurveName = InHoudiniAssetName + "_" + SplineComponent->GetName(); + } + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, Identifier, InBakeFolder.Path, CurveName, + InHoudiniAssetName, AssetPackageReplaceMode); + + BakeCurve(OutputObject, BakedOutputObject, PackageParams, bInReplaceActors, bInReplaceAssets, OutActors, + PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); + } + + SaveBakedPackages(PackagesToSave); + + return true; +} + +bool +FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint) +{ + if (!InActor || InActor->IsPendingKill()) + return false; + + if (!OutBlueprint || OutBlueprint->IsPendingKill()) + return false; + + if (InActor->GetInstanceComponents().Num() > 0) + FKismetEditorUtilities::AddComponentsToBlueprint( + OutBlueprint, + InActor->GetInstanceComponents()); + + if (OutBlueprint->GeneratedClass) + { + AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject()); + if (!CDO || CDO->IsPendingKill()) + return false; + + const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) + (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); + + EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions); + + USceneComponent * Scene = CDO->GetRootComponent(); + if (Scene && !Scene->IsPendingKill()) + { + Scene->SetRelativeLocation(FVector::ZeroVector); + Scene->SetRelativeRotation(FRotator::ZeroRotator); + + // Clear out the attachment info after having copied the properties from the source actor + Scene->SetupAttachment(nullptr); + while (true) + { + const int32 ChildCount = Scene->GetAttachChildren().Num(); + if (ChildCount < 1) + break; + + USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; + if (Component && !Component->IsPendingKill()) + Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + } + check(Scene->GetAttachChildren().Num() == 0); + + // Ensure the light mass information is cleaned up + Scene->InvalidateLightingCache(); + + // Copy relative scale from source to target. + if (USceneComponent* SrcSceneRoot = InActor->GetRootComponent()) + { + Scene->SetRelativeScale3D_Direct(SrcSceneRoot->GetRelativeScale3D()); + } + } + } + + // Compile our blueprint and notify asset system about blueprint. + //FKismetEditorUtilities::CompileBlueprint(OutBlueprint); + //FAssetRegistryModule::AssetCreated(OutBlueprint); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) +{ + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, BakeStats, Blueprints, PackagesToSave); + if (!bSuccess) + { + // TODO: ? + HOUDINI_LOG_WARNING(TEXT("Errors while baking to blueprints.")); + } + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprints( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceAssets, + FHoudiniEngineOutputStats& InBakeStats, + TArray& OutBlueprints, + TArray& OutPackagesToSave) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); + const bool bIsOwnerActorValid = IsValid(OwnerActor); + + TArray Actors; + + // Don't process outputs that are not supported in blueprints + TArray OutputsToBake = { + EHoudiniOutputType::Mesh, + EHoudiniOutputType::Instancer, + EHoudiniOutputType::Curve + }; + TArray InstancerComponentTypesToBake = { + EHoudiniInstancerComponentType::StaticMeshComponent, + EHoudiniInstancerComponentType::InstancedStaticMeshComponent, + EHoudiniInstancerComponentType::MeshSplitInstancerComponent, + }; + // When baking blueprints we always create new actors since they are deleted from the world once copied into the + // blueprint + const bool bReplaceActors = false; + bool bBakeSuccess = BakeHoudiniActorToActors( + HoudiniAssetComponent, + bReplaceActors, + bInReplaceAssets, + Actors, + OutPackagesToSave, + InBakeStats, + &OutputsToBake, + &InstancerComponentTypesToBake); + if (!bBakeSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Could not create output actors for baking to blueprint.")); + return false; + } + + // Get the previous baked outputs + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + + bBakeSuccess = BakeBlueprintsFromBakedActors( + Actors, + HoudiniAssetComponent->bRecenterBakedActors, + bInReplaceAssets, + bIsOwnerActorValid ? OwnerActor->GetName() : FString(), + HoudiniAssetComponent->BakeFolder, + &BakedOutputs, + nullptr, + OutBlueprints, + OutPackagesToSave); + + return bBakeSuccess; +} + +UStaticMesh* +FHoudiniEngineBakeUtils::BakeStaticMesh( + UStaticMesh * StaticMesh, + const FHoudiniPackageParams& PackageParams, + const TArray& InAllOutputs, + const FDirectoryPath& InTempCookFolder) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return nullptr; + + TArray PackagesToSave; + TArray Outputs; + const TArray BakedResults; + UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave); + + if (BakedStaticMesh) + { + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor) + { + TArray Objects; + Objects.Add(BakedStaticMesh); + GEditor->SyncBrowserToObjects(Objects); + } + } + + return BakedStaticMesh; +} + +bool +FHoudiniEngineBakeUtils::BakeLandscape( + int32 InOutputIndex, + UHoudiniOutput* InOutput, + TMap& InBakedOutputObjects, + bool bInReplaceActors, + bool bInReplaceAssets, + FString BakePath, + FString HoudiniAssetName, + FHoudiniEngineOutputStats& BakeStats + ) +{ + if (!IsValid(InOutput)) + return false; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + TArray PackagesToSave; + TArray LandscapeWorldsToUpdate; + + FHoudiniPackageParams PackageParams; + + for (auto& Elem : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key; + FHoudiniOutputObject& OutputObject = Elem.Value; + FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(ObjectIdentifier); + + // Populate the package params for baking this output object. + if (!IsValid(OutputObject.OutputObject)) + continue; + + if (!OutputObject.OutputObject->IsA()) + continue; + + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); + if (!IsValid(Landscape)) + continue; + + FString ObjectName = Landscape->GetName(); + + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + ObjectIdentifier, + BakePath, + ObjectName, + HoudiniAssetName, + AssetPackageReplaceMode + ); + + BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, + PackageParams, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); + } + + if (PackagesToSave.Num() > 0) + { + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); + } + + for(UWorld* LandscapeWorld : LandscapeWorldsToUpdate) + { + if (!LandscapeWorld) + continue; + FHoudiniEngineUtils::RescanWorldPath(LandscapeWorld); + ULandscapeInfo::RecreateLandscapeInfo(LandscapeWorld, true); + if (LandscapeWorld->WorldComposition) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(LandscapeWorld); + } + } + + if (PackagesToSave.Num() > 0) + { + // These packages were either created during the Bake process or they weren't + // loaded in the first place so be sure to unload them again to preserve their "state". + + TArray PackagesToUnload; + for (UPackage* Package : PackagesToSave) + { + if (!Package->IsDirty()) + PackagesToUnload.Add(Package); + } + UPackageTools::UnloadPackages(PackagesToUnload); + } + +#if WITH_EDITOR + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); +#endif + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeLandscapeObject( + FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInReplaceActors, + bool bInReplaceAssets, + FHoudiniPackageParams& PackageParams, + TArray& WorldsToUpdate, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& BakeStats) +{ + UHoudiniLandscapePtr* LandscapePointer = Cast(InOutputObject.OutputObject); + if (!LandscapePointer) + return false; + + ALandscapeProxy* TileActor = LandscapePointer->GetRawPtr(); + if (!TileActor) + return false; + + // Fetch the previous bake's pointer and proxy (if available) + ALandscapeProxy* PreviousTileActor = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + + UWorld* TileWorld = TileActor->GetWorld(); + ULevel* TileLevel = TileActor->GetLevel(); + + ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true); + + // At this point we reconstruct the resolver using cached attributes and tokens + // and just update certain tokens (output paths) for bake mode. + FHoudiniAttributeResolver Resolver; + { + TMap Tokens = InOutputObject.CachedTokens; + // PackageParams.UpdateOutputPathTokens(EPackageMode::Bake, Tokens); + PackageParams.UpdateTokensFromParams(TileWorld, Tokens); + Resolver.SetCachedAttributes(InOutputObject.CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC + // and has the appropriate name. + ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor(); + check(SharedLandscapeActor); + + // Fetch the previous bake's shared landscape actor (if available) + ALandscape* PreviousSharedLandscapeActor = nullptr; + if (IsValid(PreviousTileActor)) + PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor(); + + const bool bHasSharedLandscape = SharedLandscapeActor != TileActor; + const bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; + bool bLandscapeReplaced = false; + if (bHasSharedLandscape) + { + // If we are baking in replace mode and we have a previous shared landscape actor, use the name of that + // actor + const FString DesiredSharedLandscapeName = bHasPreviousSharedLandscape && bInReplaceActors + ? PreviousSharedLandscapeActor->GetName() + : Resolver.ResolveAttribute( + HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + SharedLandscapeActor->GetName()); + + // If we are not baking in replacement mode, create a unique name if the name is already in use + const FString SharedLandscapeName = !bInReplaceActors + ? MakeUniqueObjectNameIfNeeded(SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *DesiredSharedLandscapeName).ToString() + : DesiredSharedLandscapeName; + + if (SharedLandscapeActor->GetName() != SharedLandscapeName) + { + AActor* FoundActor = nullptr; + ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); + if (ExistingLandscape && bInReplaceActors) + { + // Even though we found an existing landscape with the desired type, we're just going to destroy/replace + // it for now. + FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); + ExistingLandscape->Destroy(); + bLandscapeReplaced = true; + } + + // Fix name of shared landscape + FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); + } + + SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); + } + + // Find the world where the landscape tile should be placed. + + TArray ValidLandscapes; + + FString ActorName = Resolver.ResolveOutputName(); + + // If the unreal_level_path was not specified, then fallback to the tile world's package + FString PackagePath = TileWorld->GetPackage() ? TileWorld->GetPackage()->GetPathName() : FString(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + PackagePath = Resolver.ResolveFullLevelPath(); + + if (bInReplaceActors) + { + // If we are baking in replace mode: get the previous baked actor (if available) name, but only if it is in the + // same target level + if (IsValid(PreviousTileActor)) + { + UPackage* PreviousPackage = PreviousTileActor->GetPackage(); + if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath) + { + ActorName = PreviousTileActor->GetName(); + } + } + } + + bool bCreatedPackage = false; + UWorld* TargetWorld = nullptr; + ULevel* TargetLevel = nullptr; + ALandscapeProxy* TargetActor = FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( + TileActor->GetWorld(), + nullptr, //unused in bake mode + ValidLandscapes,//unused in bake mode + -1, //unused in bake mode + -1, //unused in bake mode + ActorName, + PackagePath, + TargetWorld, + TargetLevel, + bCreatedPackage + ); + + check(TargetLevel) + check(TargetWorld) + + if (TargetActor && TargetActor != TileActor) + { + if (bInReplaceActors && (!PreviousTileActor || PreviousTileActor == TargetActor)) + { + // We found an target matching the name that we want. For now, rename it and then nuke it, so that + // at the very least we can spawn a new actor with the desired name. At a later stage we'll implement + // a content update, if possible. + FHoudiniEngineUtils::RenameToUniqueActor(TargetActor, ActorName + TEXT("_0")); + TargetActor->Destroy(); + } + else + { + // incremental, keep existing actor and create a unique name for the new one + ActorName = MakeUniqueObjectName(TargetActor->GetOuter(), TargetActor->GetClass(), *ActorName).ToString(); + } + TargetActor = nullptr; + } + + if (TargetLevel != TileActor->GetLevel()) + { + bool bLevelInWorld = TileWorld->ContainsLevel(TargetLevel); + ALandscape* SharedLandscape = TileActor->GetLandscapeActor(); + ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); + + check(LandscapeInfo); + + // We can now move the current landscape to the new world / level + // if (TileActor->GetClass()->IsChildOf()) + { + // We can only move streaming proxies to sublevels for now. + TArray ActorsToMove = {TileActor}; + + ALandscapeProxy* NewLandscapeProxy = LandscapeInfo->MoveComponentsToLevel(TileActor->LandscapeComponents, TargetLevel); + // We have now moved the landscape components into the new level. We can (hopefully) safely delete the + // old tile actor. + TileActor->Destroy(); + + TargetLevel->MarkPackageDirty(); + + TileActor = NewLandscapeProxy; + } + } + else + { + // Ensure the landscape actor is detached. + TileActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + } + + // Ensure the tile actor has the desired name. + FHoudiniEngineUtils::SafeRenameActor(TileActor, ActorName); + + if (TileActor->GetClass()->IsChildOf(ALandscape::StaticClass())) + { + // This is not a shared landscape. Be sure to update this landscape's world when + // baking is done. + WorldsToUpdate.AddUnique(TileActor->GetWorld()); + } + + if (bCreatedPackage) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(TargetLevel->GetOutermost()); + } + + // Record the landscape in the baked output object via a new UHoudiniLandscapePtr + // UHoudiniLandscapePtr* BakedLandscapePtr = NewObject(LandscapePointer->GetOuter()); + // if (IsValid(BakedLandscapePtr)) + // { + // BakedLandscapePtr->SetSoftPtr(TileActor); + InBakedOutputObject.BakedObject = FSoftObjectPath(TileActor).ToString(); + // } + // else + // { + // InBakedOutputObject.BakedObject = nullptr; + // } + + // Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks + InOutputObject.OutputObject = nullptr; + + DestroyPreviousBakeOutput(InBakedOutputObject, true, true, true); + + // ---------------------------------------------------- + // Collect baking stats + // ---------------------------------------------------- + if (bLandscapeReplaced) + BakeStats.NotifyObjectsReplaced(EHoudiniOutputType::Landscape, 1); + else + BakeStats.NotifyObjectsCreated(EHoudiniOutputType::Landscape, 1); + + if (bCreatedPackage) + BakeStats.NotifyPackageCreated(1); + else + if (TileLevel != TargetLevel) + BakeStats.NotifyPackageUpdated(1); + + return true; +} + +UStaticMesh * +FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + UStaticMesh * InStaticMesh, + UStaticMesh * InPreviousBakeStaticMesh, + const FHoudiniPackageParams &PackageParams, + const TArray& InParentOutputs, + const TArray& InCurrentBakedActors, + const FString& InTemporaryCookFolder, + TArray & OutCreatedPackages) +{ + if (!InStaticMesh || InStaticMesh->IsPendingKill()) + return nullptr; + + bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, InParentOutputs, InTemporaryCookFolder); + if (!bIsTemporaryStaticMesh) + { + // The Static Mesh is not a temporary one/already baked, we can simply reuse it + // instead of duplicating it + return InStaticMesh; + } + + // Look for InStaticMesh as the SourceObject in InCurrentBakedActors (it could have already been baked along with + // a previous output: instancers etc) + for (const FHoudiniEngineBakedActor& BakedActor : InCurrentBakedActors) + { + if (BakedActor.SourceObject == InStaticMesh && IsValid(BakedActor.BakedObject) + && BakedActor.BakedObject->IsA(InStaticMesh->GetClass())) + { + // We have found a bake result where InStaticMesh was the source object and we have a valid BakedObject + // of a compatible class + return Cast(BakedActor.BakedObject); + } + } + + // InStaticMesh is temporary and we didn't find a baked version of it in our current bake output, we need to bake it + + // If we have a previously baked static mesh, get the bake counter from it so that both replace and increment + // is consistent with the bake counter + int32 BakeCounter = 0; + bool bPreviousBakeStaticMeshValid = IsValid(InPreviousBakeStaticMesh); + TArray PreviousBakeMaterials; + if (bPreviousBakeStaticMeshValid) + { + bPreviousBakeStaticMeshValid = PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBakeStaticMesh); + if (bPreviousBakeStaticMeshValid) + { + PackageParams.GetBakeCounterFromBakedAsset(InPreviousBakeStaticMesh, BakeCounter); + PreviousBakeMaterials = InPreviousBakeStaticMesh->StaticMaterials;//GetStaticMaterials(); + } + } + FString CreatedPackageName; + UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter); + if (!MeshPackage || MeshPackage->IsPendingKill()) + return nullptr; + + OutCreatedPackages.Add(MeshPackage); + + // We need to be sure the package has been fully loaded before calling DuplicateObject + if (!MeshPackage->IsFullyLoaded()) + { + FlushAsyncLoading(); + if (!MeshPackage->GetOuter()) + { + MeshPackage->FullyLoad(); + } + else + { + MeshPackage->GetOutermost()->FullyLoad(); + } + } + + // If the a UStaticMesh with that name already exists then detach it from all of its components before replacing + // it so that its render resources can be safely replaced/updated, and then reattach it + UStaticMesh * DuplicatedStaticMesh = nullptr; + UStaticMesh* ExistingMesh = FindObject(MeshPackage, *CreatedPackageName); + if (IsValid(ExistingMesh)) + { + FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); + DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + } + else + { + DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + } + + if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, DuplicatedStaticMesh, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, DuplicatedStaticMesh, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedPackageName); + + // See if we need to duplicate materials and textures. + TArrayDuplicatedMaterials; + TArray& Materials = DuplicatedStaticMesh->StaticMaterials; + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + continue; + + // Only duplicate the material if it is temporary + if (IsObjectTemporary(MaterialInterface, InParentOutputs, InTemporaryCookFolder)) + { + UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); + if (MaterialPackage && !MaterialPackage->IsPendingKill()) + { + FString MaterialName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + MeshPackage, DuplicatedStaticMesh, MaterialName)) + { + MaterialName = MaterialName + "_Material" + FString::FromInt(MaterialIdx + 1); + + // We only deal with materials. + UMaterial * Material = Cast< UMaterial >(MaterialInterface); + if (Material && !Material->IsPendingKill()) + { + // Look for a previous bake material at this index + UMaterial* PreviousBakeMaterial = nullptr; + if (bPreviousBakeStaticMeshValid && PreviousBakeMaterials.IsValidIndex(MaterialIdx)) + { + PreviousBakeMaterial = Cast(PreviousBakeMaterials[MaterialIdx].MaterialInterface); + } + // Duplicate material resource. + UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages); + + if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + continue; + + // Store duplicated material. + FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx]; + DupeStaticMaterial.MaterialInterface = DuplicatedMaterial; + DuplicatedMaterials.Add(DupeStaticMaterial); + continue; + } + } + } + } + + // We can simply reuse the source material + DuplicatedMaterials.Add(Materials[MaterialIdx]); + } + + // Assign duplicated materials. + DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; + + // Notify registry that we have created a new duplicate mesh. + FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); + + // Dirty the static mesh package. + DuplicatedStaticMesh->MarkPackageDirty(); + + return DuplicatedStaticMesh; +} + +ALandscapeProxy* +FHoudiniEngineBakeUtils::BakeHeightfield( + ALandscapeProxy * InLandscapeProxy, + const FHoudiniPackageParams & PackageParams, + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType) +{ + if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill()) + return nullptr; + + const FString & BakeFolder = PackageParams.BakeFolder; + const FString & AssetName = PackageParams.HoudiniAssetName; + + switch (LandscapeOutputBakeType) + { + case EHoudiniLandscapeOutputBakeType::Detachment: + { + // Detach the landscape from the Houdini Asset Actor + InLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + } + break; + case EHoudiniLandscapeOutputBakeType::BakeToImage: + { + // Create heightmap image to the bake folder + ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); + if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + return nullptr; + + // bake to image must use absoluate path, + // and the file name has a file extension (.png) + FString BakeFolderInFullPath = BakeFolder; + + // Figure absolute path, + if (!BakeFolderInFullPath.EndsWith("/")) + BakeFolderInFullPath += "/"; + + if (BakeFolderInFullPath.StartsWith("/Game")) + BakeFolderInFullPath = BakeFolderInFullPath.Mid(5, BakeFolderInFullPath.Len() - 5); + + if (BakeFolderInFullPath.StartsWith("/")) + BakeFolderInFullPath = BakeFolderInFullPath.Mid(1, BakeFolderInFullPath.Len() - 1); + + FString FullPath = FPaths::ProjectContentDir() + BakeFolderInFullPath + AssetName + "_" + InLandscapeProxy->GetName() + ".png"; + + InLandscapeInfo->ExportHeightmap(FullPath); + + // TODO: + // We should update this to have an asset/package.. + } + break; + case EHoudiniLandscapeOutputBakeType::BakeToWorld: + { + ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); + if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + return nullptr; + + // 0. Get Landscape Data // + + // Extract landscape height data + TArray InLandscapeHeightData; + int32 XSize, YSize; + FVector Min, Max; + if (!FUnrealLandscapeTranslator::GetLandscapeData(InLandscapeProxy, InLandscapeHeightData, XSize, YSize, Min, Max)) + return nullptr; + + // Extract landscape Layers data + TArray InLandscapeImportLayerInfos; + for (int32 n = 0; n < InLandscapeInfo->Layers.Num(); ++n) + { + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + FLandscapeImportLayerInfo CurrentLayerInfo; + CurrentLayerInfo.LayerName = FName(LayerName); + CurrentLayerInfo.LayerInfo = InLandscapeInfo->Layers[n].LayerInfoObj; + CurrentLayerInfo.LayerData = CurrentLayerIntData; + + CurrentLayerInfo.LayerInfo->LayerUsageDebugColor = LayerUsageDebugColor; + + InLandscapeImportLayerInfos.Add(CurrentLayerInfo); + } + + // 1. Create package // + + FString PackagePath = PackageParams.GetPackagePath(); + FString PackageName = PackageParams.GetPackageName(); + + UPackage *CreatedPackage = nullptr; + FString CreatedPackageName; + + CreatedPackage = PackageParams.CreatePackageForObject(CreatedPackageName); + + if (!CreatedPackage) + return nullptr; + + // 2. Create a new world asset with dialog // + UWorldFactory* Factory = NewObject(); + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + UObject* Asset = AssetToolsModule.Get().CreateAssetWithDialog( + PackageName, PackagePath, + UWorld::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + + + UWorld* NewWorld = Cast(Asset); + if (!NewWorld) + return nullptr; + + NewWorld->SetCurrentLevel(NewWorld->PersistentLevel); + + // 4. Spawn a landscape proxy actor in the created world + ALandscapeStreamingProxy * BakedLandscapeProxy = NewWorld->SpawnActor(); + if (!BakedLandscapeProxy) + return nullptr; + + // Create a new GUID + FGuid currentGUID = FGuid::NewGuid(); + BakedLandscapeProxy->SetLandscapeGuid(currentGUID); + + // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue + BakedLandscapeProxy->bCastStaticShadow = false; + + + // 5. Import data to the created landscape proxy + TMap> HeightmapDataPerLayers; + TMap> MaterialLayerDataPerLayer; + + HeightmapDataPerLayers.Add(FGuid(), InLandscapeHeightData); + MaterialLayerDataPerLayer.Add(FGuid(), InLandscapeImportLayerInfos); + + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + + BakedLandscapeProxy->Import( + currentGUID, + 0, 0, XSize-1, YSize-1, + InLandscapeInfo->ComponentNumSubsections, InLandscapeInfo->SubsectionSizeQuads, + HeightmapDataPerLayers, NULL, + MaterialLayerDataPerLayer, ImportLayerType); + + BakedLandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + + + if (BakedLandscapeProxy->LandscapeMaterial) + BakedLandscapeProxy->LandscapeMaterial = InLandscapeProxy->LandscapeMaterial; + + if (BakedLandscapeProxy->LandscapeHoleMaterial) + BakedLandscapeProxy->LandscapeHoleMaterial = InLandscapeProxy->LandscapeHoleMaterial; + + // 6. Register all the landscape components, and set landscape actor transform + BakedLandscapeProxy->RegisterAllComponents(); + BakedLandscapeProxy->SetActorTransform(InLandscapeProxy->GetTransform()); + + // 7. Save Package + TArray PackagesToSave; + PackagesToSave.Add(CreatedPackage); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor) + { + TArray Objects; + Objects.Add(NewWorld); + GEditor->SyncBrowserToObjects(Objects); + } + } + break; + } + + return InLandscapeProxy; +} + +bool +FHoudiniEngineBakeUtils::BakeCurve( + USplineComponent* InSplineComponent, + ULevel* InLevel, + const FHoudiniPackageParams &PackageParams, + AActor*& OutActor, + USplineComponent*& OutSplineComponent, + FName InOverrideFolderPath, + AActor* InActor) +{ + if (!IsValid(InActor)) + { + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; + if (!Factory) + return false; + + OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform(), RF_Transactional); + } + else + { + OutActor = InActor; + } + + // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset + const FName BaseActorName(PackageParams.ObjectName); + const FName NewName = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName, OutActor); + const FString NewNameStr = NewName.ToString(); + // OutActor->Rename(*NewNameStr); + // OutActor->SetActorLabel(NewNameStr); + RenameAndRelabelActor(OutActor, NewNameStr, false); + OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); + + USplineComponent* DuplicatedSplineComponent = DuplicateObject( + InSplineComponent, + OutActor, + MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), FName(PackageParams.ObjectName))); + OutActor->AddInstanceComponent(DuplicatedSplineComponent); + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); + DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + + FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent); + DuplicatedSplineComponent->RegisterComponent(); + + OutSplineComponent = DuplicatedSplineComponent; + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeCurve( + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + // const TArray& InAllBakedOutputs, + const FHoudiniPackageParams &PackageParams, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + USplineComponent* SplineComponent = Cast(InOutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + return false; + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; + + // Access some of the attribute that were cached on the output object + FHoudiniAttributeResolver Resolver; + { + TMap CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); + Resolver.SetCachedAttributes(CachedAttributes); + Resolver.SetTokensFromStringMap(Tokens); + } + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if(!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + // If we are baking in replace mode, remove the previous bake component + if (bInReplaceAssets && !InBakedOutputObject.BakedComponent.IsEmpty()) + { + UActorComponent* PrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (PrevComponent && PrevComponent->GetOwner() == FoundActor) + { + RemovePreviouslyBakedComponent(PrevComponent); + } + } + + FHoudiniPackageParams CurvePackageParams = PackageParams; + CurvePackageParams.ObjectName = BakeActorName.ToString(); + USplineComponent* NewSplineComponent = nullptr; + const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName)); + if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor)) + return false; + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + InBakedOutputObject.BakedComponent = FSoftObjectPath(NewSplineComponent).ToString(); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + FHoudiniEngineBakedActor Result; + Result.Actor = FoundActor; + Result.ActorBakeName = BakeActorName; + OutActors.Add(Result); + + return true; +} + +AActor* +FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PackageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform) +{ + if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + return nullptr; + + TArray & DisplayPoints = InHoudiniSplineComponent->DisplayPoints; + if (DisplayPoints.Num() < 2) + return nullptr; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; + if (!Factory) + return nullptr; + + // Remove the actor if it exists + for (auto & Actor : DesiredLevel->Actors) + { + if (!Actor) + continue; + + if (Actor->GetName() == PackageParams.ObjectName) + { + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + Actor->RemoveFromRoot(); + Actor->ConditionalBeginDestroy(); + World->EditorDestroyActor(Actor, true); + + break; + } + } + + AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform(), RF_Transactional); + + USplineComponent* BakedUnrealSplineComponent = NewObject(NewActor); + if (!BakedUnrealSplineComponent) + return nullptr; + + // add display points to created unreal spline component + for (int32 n = 0; n < DisplayPoints.Num(); ++n) + { + FVector & NextPoint = DisplayPoints[n]; + BakedUnrealSplineComponent->AddSplinePoint(NextPoint, ESplineCoordinateSpace::Local); + // Set the curve point type to be linear, since we are using display points + BakedUnrealSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + NewActor->AddInstanceComponent(BakedUnrealSplineComponent); + + BakedUnrealSplineComponent->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); + + FAssetRegistryModule::AssetCreated(NewActor); + FAssetRegistryModule::AssetCreated(BakedUnrealSplineComponent); + BakedUnrealSplineComponent->RegisterComponent(); + + // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset + const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); + const FString NewNameStr = NewName.ToString(); + // NewActor->Rename(*NewNameStr); + // NewActor->SetActorLabel(NewNameStr); + RenameAndRelabelActor(NewActor, NewNameStr, false); + NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName)); + + return NewActor; +} + +UBlueprint* +FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PackageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform) +{ + if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + return nullptr; + + FGuid BakeGUID = FGuid::NewGuid(); + + if (!BakeGUID.IsValid()) + BakeGUID = FGuid::NewGuid(); + + // We only want half of generated guid string. + FString BakeGUIDString = BakeGUID.ToString().Left(FHoudiniEngineUtils::PackageGUIDItemNameLength); + + // Generate Blueprint name. + FString BlueprintName = PackageParams.ObjectName + "_BP"; + + // Generate unique package name. + FString PackageName = PackageParams.BakeFolder + "/" + BlueprintName; + PackageName = UPackageTools::SanitizePackageName(PackageName); + + // See if package exists, if it does, we need to regenerate the name. + UPackage * Package = FindPackage(nullptr, *PackageName); + + if (Package && !Package->IsPendingKill()) + { + // Package does exist, there's a collision, we need to generate a new name. + BakeGUID.Invalidate(); + } + else + { + // Create actual package. + Package = CreatePackage(*PackageName); + } + + AActor * CreatedHoudiniSplineActor = FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + InHoudiniSplineComponent, PackageParams, WorldToSpawn, SpawnTransform); + + TArray PackagesToSave; + + UBlueprint * Blueprint = nullptr; + if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill()) + { + + UObject* Asset = nullptr; + + Asset = StaticFindObjectFast(UObject::StaticClass(), Package, FName(*BlueprintName)); + if (!Asset) + { + UBlueprintFactory* Factory = NewObject(); + + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + Asset = AssetToolsModule.Get().CreateAsset( + BlueprintName, PackageParams.BakeFolder, + UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + } + + TArray Components; + for (auto & Next : CreatedHoudiniSplineActor->GetComponents()) + { + Components.Add(Next); + } + + Blueprint = Cast(Asset); + + // Clear old Blueprint Node tree + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + + int32 NodeSize = SCS->GetAllNodes().Num(); + for (int32 n = NodeSize - 1; n >= 0; --n) + SCS->RemoveNode(SCS->GetAllNodes()[n]); + + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components); + + CreatedHoudiniSplineActor->RemoveFromRoot(); + CreatedHoudiniSplineActor->ConditionalBeginDestroy(); + + GWorld->EditorDestroyActor(CreatedHoudiniSplineActor, true); + + Package->MarkPackageDirty(); + PackagesToSave.Add(Package); + } + + // Save the created BP package. + FHoudiniEngineBakeUtils::SaveBakedPackages + (PackagesToSave); + + return Blueprint; +} + + +void +FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const TCHAR * Key, + const TCHAR * Value) +{ + if (!Package || Package->IsPendingKill()) + return; + + UMetaData * MetaData = Package->GetMetaData(); + if (MetaData && !MetaData->IsPendingKill()) + MetaData->SetValue(Object, Key, Value); +} + + +bool +FHoudiniEngineBakeUtils:: +GetHoudiniGeneratedNameFromMetaInformation( + UPackage * Package, UObject * Object, FString & HoudiniName) +{ + if (!Package || Package->IsPendingKill()) + return false; + + UMetaData * MetaData = Package->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return false; + + if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + { + // Retrieve name used for package generation. + const FString NameFull = MetaData->GetValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME); + + //HoudiniName = NameFull.Left(FMath::Min(NameFull.Len(), FHoudiniEngineUtils::PackageGUIDItemNameLength)); + HoudiniName = NameFull; + return true; + } + + return false; +} + +UMaterial * +FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + UMaterial * Material, UMaterial* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, + TArray & OutGeneratedPackages) +{ + UMaterial * DuplicatedMaterial = nullptr; + + FString CreatedMaterialName; + // Create material package. Use the same package params as static mesh, but with the material's name + FHoudiniPackageParams MaterialPackageParams = ObjectPackageParams; + MaterialPackageParams.ObjectName = MaterialName; + + // Check if there is a valid previous material. If so, get the bake counter for consistency in + // replace or iterative package naming + bool bIsPreviousBakeMaterialValid = IsValid(PreviousBakeMaterial); + int32 BakeCounter = 0; + TArray PreviousBakeMaterialExpressions; + if (bIsPreviousBakeMaterialValid) + { + bIsPreviousBakeMaterialValid = MaterialPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeMaterial); + if (bIsPreviousBakeMaterialValid) + { + MaterialPackageParams.GetBakeCounterFromBakedAsset(PreviousBakeMaterial, BakeCounter); + PreviousBakeMaterialExpressions = PreviousBakeMaterial->Expressions; + } + } + + UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter); + + if (!MaterialPackage || MaterialPackage->IsPendingKill()) + return nullptr; + + // Clone material. + DuplicatedMaterial = DuplicateObject< UMaterial >(Material, MaterialPackage, *CreatedMaterialName); + if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, DuplicatedMaterial, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, DuplicatedMaterial, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedMaterialName); + + // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. + const int32 NumExpressions = DuplicatedMaterial->Expressions.Num(); + for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx) + { + UMaterialExpression* Expression = DuplicatedMaterial->Expressions[ExpressionIdx]; + UMaterialExpression* PreviousBakeExpression = nullptr; + if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx)) + { + PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; + } + FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages); + } + + // Notify registry that we have created a new duplicate material. + FAssetRegistryModule::AssetCreated(DuplicatedMaterial); + + // Dirty the material package. + DuplicatedMaterial->MarkPackageDirty(); + + // Recompile the baked material + // DuplicatedMaterial->ForceRecompileForRendering(); + // Use UMaterialEditingLibrary::RecompileMaterial since it correctly updates texture references in the material + // which ForceRecompileForRendering does not do + UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterial); + + OutGeneratedPackages.Add(MaterialPackage); + + return DuplicatedMaterial; +} + +void +FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, + const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages) +{ + UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression); + if (!TextureSample || TextureSample->IsPendingKill()) + return; + + UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture); + if (!Texture || Texture->IsPendingKill()) + return; + + UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); + if (!TexturePackage || TexturePackage->IsPendingKill()) + return; + + // Try to get the previous bake's texture + UTexture2D* PreviousBakeTexture = nullptr; + if (IsValid(PreviousBakeMaterialExpression)) + { + UMaterialExpressionTextureSample* PreviousBakeTextureSample = Cast< UMaterialExpressionTextureSample >(PreviousBakeMaterialExpression); + if (IsValid(PreviousBakeTextureSample)) + PreviousBakeTexture = Cast< UTexture2D >(PreviousBakeTextureSample->Texture); + } + + FString GeneratedTextureName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + TexturePackage, Texture, GeneratedTextureName)) + { + // Duplicate texture. + UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( + Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages); + + // Re-assign generated texture. + TextureSample->Texture = DuplicatedTexture; + } +} + +UTexture2D * +FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( + UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages) +{ + UTexture2D* DuplicatedTexture = nullptr; +#if WITH_EDITOR + // Retrieve original package of this texture. + UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); + if (!TexturePackage || TexturePackage->IsPendingKill()) + return nullptr; + + FString GeneratedTextureName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName)) + { + UMetaData * MetaData = TexturePackage->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return nullptr; + + // Retrieve texture type. + const FString & TextureType = + MetaData->GetValue(Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); + + FString CreatedTextureName; + + // Create texture package. Use the same package params as material's, but with object name appended by generated texture's name + FHoudiniPackageParams TexturePackageParams = PackageParams; + TexturePackageParams.ObjectName = TexturePackageParams.ObjectName + "_" + GeneratedTextureName; + + // Determine the bake counter of the previous bake's texture (if exists/valid) for naming consistency when + // replacing/iterating + bool bIsPreviousBakeTextureValid = IsValid(PreviousBakeTexture); + int32 BakeCounter = 0; + if (bIsPreviousBakeTextureValid) + { + bIsPreviousBakeTextureValid = TexturePackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeTexture); + if (bIsPreviousBakeTextureValid) + { + TexturePackageParams.GetBakeCounterFromBakedAsset(PreviousBakeTexture, BakeCounter); + } + } + + UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter); + + if (!NewTexturePackage || NewTexturePackage->IsPendingKill()) + return nullptr; + + // Clone texture. + DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName); + if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedTextureName); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); + + // Notify registry that we have created a new duplicate texture. + FAssetRegistryModule::AssetCreated(DuplicatedTexture); + + // Dirty the texture package. + DuplicatedTexture->MarkPackageDirty(); + + OutCreatedPackages.Add(NewTexturePackage); + } +#endif + return DuplicatedTexture; +} + + +bool +FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); + + if (!ActorOwner || ActorOwner->IsPendingKill()) + return false; + + UWorld* World = ActorOwner->GetWorld(); + if (!World) + World = GWorld; + + World->EditorDestroyActor(ActorOwner, false); + + return true; +} + + +void +FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld) +{ + UWorld * CurrentWorld = nullptr; + if (bSaveCurrentWorld && GEditor) + CurrentWorld = GEditor->GetEditorWorldContext().World(); + + if (CurrentWorld) + { + // Save the current map + FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); + UPackage* CurrentWorldPackage = CreatePackage(*CurrentWorldPath); + + if (CurrentWorldPackage) + { + CurrentWorldPackage->MarkPackageDirty(); + PackagesToSave.Add(CurrentWorldPackage); + } + } + + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); +} + +bool +FHoudiniEngineBakeUtils::FindOutputObject( + const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) +{ + if (!InObjectToFind || InObjectToFind->IsPendingKill()) + return false; + + const int32 NumOutputs = InOutputs.Num(); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + const UHoudiniOutput* CurOutput = InOutputs[OutputIdx]; + if (!IsValid(CurOutput)) + continue; + + for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) + { + if (CurOutputObject.Value.OutputObject == InObjectToFind + || CurOutputObject.Value.OutputComponent == InObjectToFind + || CurOutputObject.Value.ProxyObject == InObjectToFind + || CurOutputObject.Value.ProxyComponent == InObjectToFind) + { + OutOutputIndex = OutputIdx; + OutIdentifier = CurOutputObject.Key; + return true; + } + } + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + FString TempPath = FString(); + + // TODO: Get the HAC outputs in a better way? + TArray Outputs; + if (InHAC && !InHAC->IsPendingKill()) + { + const int32 NumOutputs = InHAC->GetNumOutputs(); + Outputs.Reserve(NumOutputs); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + Outputs.Add(InHAC->GetOutputAt(OutputIdx)); + } + + TempPath = InHAC->TemporaryCookFolder.Path; + } + + return IsObjectTemporary(InObject, Outputs, TempPath); +} + +bool FHoudiniEngineBakeUtils::IsObjectTemporary( + UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + int32 ParentOutputIndex = -1; + FHoudiniOutputObjectIdentifier Identifier; + if (FindOutputObject(InObject, InParentOutputs, ParentOutputIndex, Identifier)) + return true; + + // Check the package path for this object + // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated + UPackage* ObjectPackage = InObject->GetOutermost(); + if (ObjectPackage && !ObjectPackage->IsPendingKill()) + { + const FString PathName = ObjectPackage->GetPathName(); + if (PathName.StartsWith(InTemporaryCookFolder)) + return true; + + // Also check the default temp folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder)) + return true; + + /* + // TODO: this just indicates that the object was generated by H + // it could as well have been baked before... + // we should probably add a "temp" metadata + // Look in the meta info as well?? + UMetaData * MetaData = ObjectPackage->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return false; + + if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + return true; + */ + } + + return false; +} + +void +FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC) +{ + if (!NewSMC || NewSMC->IsPendingKill()) + return; + + if (!InSMC || InSMC->IsPendingKill()) + return; + + // Copy properties to new actor + //UStaticMeshComponent* OtherSMC_NonConst = const_cast(StaticMeshComponent); + NewSMC->SetCollisionProfileName(InSMC->GetCollisionProfileName()); + NewSMC->SetCollisionEnabled(InSMC->GetCollisionEnabled()); + NewSMC->LightmassSettings = InSMC->LightmassSettings; + NewSMC->CastShadow = InSMC->CastShadow; + NewSMC->SetMobility(InSMC->Mobility); + + UBodySetup* InBodySetup = InSMC->GetBodySetup(); + UBodySetup* NewBodySetup = NewSMC->GetBodySetup(); + if (InBodySetup && NewBodySetup) + { + // Copy the BodySetup + NewBodySetup->CopyBodyPropertiesFrom(InBodySetup); + + // We need to recreate the physics mesh for the new body setup + NewBodySetup->InvalidatePhysicsData(); + NewBodySetup->CreatePhysicsMeshes(); + + // Only copy the physical material if it's different from the default one, + // As this was causing crashes on BakeToActors in some cases + if (GEngine != NULL && NewBodySetup->GetPhysMaterial() != GEngine->DefaultPhysMaterial) + NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial()); + } + + if (NewActor && !NewActor->IsPendingKill()) + NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame); + + NewSMC->SetVisibility(InSMC->IsVisible()); + + // TODO: + // // Reapply the uproperties modified by attributes on the new component + // FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO); + + // The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another + UClass* ComponentClass = InSMC->GetClass(); + if (ComponentClass != NewSMC->GetClass()) + { + HOUDINI_LOG_WARNING( + TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), + *(ComponentClass->GetName()), + *(NewSMC->GetClass()->GetName())); + + NewSMC->PostEditChange(); + return; + } + + TSet SourceUCSModifiedProperties; + InSMC->GetUCSModifiedProperties(SourceUCSModifiedProperties); + + AActor* SourceActor = InSMC->GetOwner(); + if (!IsValid(SourceActor)) + { + NewSMC->PostEditChange(); + return; + } + + TArray ModifiedObjects; + const EditorUtilities::FCopyOptions Options(EditorUtilities::ECopyOptions::CallPostEditChangeProperty); + // Copy component properties + for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) + { + const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); + const bool bIsIdentical = Property->Identical_InContainer( InSMC, NewSMC ); + const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); + const bool bIsTransform = + Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); + + // auto SourceComponentIsRoot = [&]() + // { + // USceneComponent* RootComponent = SourceActor->GetRootComponent(); + // if (InSMC == RootComponent) + // { + // return true; + // } + // return false; + // }; + + // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + // && ( !bIsTransform || !SourceComponentIsRoot() ) ) + if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + && !bIsTransform ) + { + // const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) + // && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); + const bool bIsSafeToCopy = true; + if( bIsSafeToCopy ) + { + if (!Options.CanCopyProperty(*Property, *SourceActor)) + { + continue; + } + + if( !ModifiedObjects.Contains(NewSMC) ) + { + NewSMC->SetFlags(RF_Transactional); + NewSMC->Modify(); + ModifiedObjects.Add(NewSMC); + } + + if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) + { + // @todo simulate: Should we be calling this on the component instead? + NewActor->PreEditChange( Property ); + } + + EditorUtilities::CopySingleProperty(InSMC, NewSMC, Property); + + if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) + { + FPropertyChangedEvent PropertyChangedEvent( Property ); + NewActor->PostEditChangeProperty( PropertyChangedEvent ); + } + } + } + } + + NewSMC->PostEditChange(); +}; + +bool +FHoudiniEngineBakeUtils::RemovePreviouslyBakedActor( + AActor* InNewBakedActor, + ULevel* InLevel, + const FHoudiniPackageParams& InPackageParams) +{ + // Remove a previous bake actor if it exists + for (auto & Actor : InLevel->Actors) + { + if (!Actor) + continue; + + if (Actor != InNewBakedActor && Actor->GetName() == InPackageParams.ObjectName) + { + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + Actor->RemoveFromRoot(); + Actor->ConditionalBeginDestroy(); + World->EditorDestroyActor(Actor, true); + + return true; + } + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::RemovePreviouslyBakedComponent(UActorComponent* InComponent) +{ + if (!IsValid(InComponent)) + return false; + + // Remove from its actor first + if (InComponent->GetOwner()) + InComponent->GetOwner()->RemoveOwnedComponent(InComponent); + + // Detach from its parent component if attached + USceneComponent* SceneComponent = Cast(InComponent); + if (IsValid(SceneComponent)) + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + InComponent->UnregisterComponent(); + InComponent->DestroyComponent(); + + return true; +} + +FName +FHoudiniEngineBakeUtils::GetOutputFolderPath(UObject* InOutputOwner) +{ + // Get an output folder path for PDG outputs generated by InOutputOwner. + // The folder path is: / + FString FolderName; + FName FolderDirName; + AActor* OuterActor = Cast(InOutputOwner); + if (OuterActor) + { + FolderName = OuterActor->GetActorLabel(); + FolderDirName = OuterActor->GetFolderPath(); + } + else + { + FolderName = InOutputOwner->GetName(); + } + if (!FolderDirName.IsNone()) + return FName(FString::Printf(TEXT("%s/%s"), *FolderDirName.ToString(), *FolderName)); + else + return FName(FolderName); +} + +void +FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique) +{ + FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); + + const FSoftObjectPath OldPath = FSoftObjectPath(InAsset); + + FString NewName; + if (bMakeUniqueIfNotUnique) + NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetPackage(), InAsset->GetClass(), FName(InNewName), InAsset).ToString(); + else + NewName = InNewName; + + InAsset->Rename(*NewName); + + const FSoftObjectPath NewPath = FSoftObjectPath(InAsset); + if (OldPath != NewPath) + { + TArray RenameData; + RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); + AssetToolsModule.Get().RenameAssets(RenameData); + } +} + +void +FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique) +{ + if (!IsValid(InActor)) + return; + + FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); + + const FSoftObjectPath OldPath = FSoftObjectPath(InActor); + + FString NewName; + if (bMakeUniqueIfNotUnique) + NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName), InActor).ToString(); + else + NewName = InNewName; + + InActor->Rename(*NewName); + InActor->SetActorLabel(NewName); + + const FSoftObjectPath NewPath = FSoftObjectPath(InActor); + if (OldPath != NewPath) + { + TArray RenameData; + RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); + AssetToolsModule.Get().RenameAssets(RenameData); + } +} + +bool +FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor( + AActor* InActor, + const FString& InNewName, + const FName& InFolderPath) +{ + if (!IsValid(InActor)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: InActor is null.")); + return false; + } + + if (InNewName.TrimStartAndEnd().IsEmpty()) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: A valid actor name must be specified.")); + return false; + } + + // Detach from parent + InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + // Rename + // InActor->Rename(*MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName)).ToString()); + // InActor->SetActorLabel(InNewName); + const bool bMakeUniqueIfNotUnique = true; + RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique); + + InActor->SetFolderPath(InFolderPath); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultIndex, + int32 InWorkResultObjectIndex, + bool bInReplaceActors, + bool bInReplaceAssets, + bool bInBakeToWorkResultActor, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!IsValid(InPDGAssetLink)) + return false; + + if (!IsValid(InNode)) + return false; + + if (!InNode->WorkResult.IsValidIndex(InWorkResultIndex)) + return false; + + FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultIndex]; + if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectIndex)) + return false; + + FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectIndex]; + TArray& Outputs = WorkResultObject.GetResultOutputs(); + if (Outputs.Num() == 0) + return true; + + AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor(); + if (!IsValid(WorkResultObjectActor)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObjectActor (%s) is null (unexpected since # Outputs > 0)"), *WorkResultObject.Name); + return false; + } + + // BakedActorsForWorkResultObject contains each actor that contains baked PDG results. Actors may + // appear in the array more than once if they have more than one baked result/component associated with + // them + TArray BakedActorsForWorkResultObject; + const FString HoudiniAssetName(WorkResultObject.Name); + + // Find the previous bake output for this work result object + FString Key; + InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key); + FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key); + + BakeHoudiniOutputsToActors( + Outputs, + BakedOutputContainer.BakedOutputs, + HoudiniAssetName, + WorkResultObjectActor->GetActorTransform(), + InPDGAssetLink->BakeFolder, + InPDGAssetLink->GetTemporaryCookFolder(), + bInReplaceActors, + bInReplaceAssets, + BakedActorsForWorkResultObject, + OutPackagesToSave, + OutBakeStats, + InOutputTypesToBake, + InInstancerComponentTypesToBake, + bInBakeToWorkResultActor ? WorkResultObjectActor : nullptr, + InFallbackWorldOutlinerFolder); + + // Set the PDG indices on the output baked actor entries + if (BakedActorsForWorkResultObject.Num() > 0) + { + for (FHoudiniEngineBakedActor& BakedActorEntry : BakedActorsForWorkResultObject) + { + BakedActorEntry.PDGWorkResultIndex = InWorkResultIndex; + BakedActorEntry.PDGWorkResultObjectIndex = InWorkResultObjectIndex; + } + } + + // If anything was baked to WorkResultObjectActor, detach it from its parent + if (bInBakeToWorkResultActor) + { + FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); + // if we re-used the temp actor as a bake actor, then remove its temp outputs + WorkResultObject.DestroyResultOutputs(); + AActor* WROActor = OutputActorOwner.GetOutputActor(); + if (WROActor) + { + const FHoudiniEngineBakedActor* BakedActorEntry = BakedActorsForWorkResultObject.FindByPredicate([WROActor](const FHoudiniEngineBakedActor& Entry) + { + return Entry.Actor == WROActor; + }); + if (BakedActorEntry) + { + OutputActorOwner.SetOutputActor(nullptr); + const FString OldActorPath = FSoftObjectPath(WROActor).ToString(); + DetachAndRenameBakedPDGOutputActor( + WROActor, BakedActorEntry->ActorBakeName.ToString(), BakedActorEntry->WorldOutlinerFolder); + const FString NewActorPath = FSoftObjectPath(WROActor).ToString(); + if (OldActorPath != NewActorPath) + { + // Fix cached string reference in baked outputs to WROActor + for (FHoudiniBakedOutput& BakedOutput : BakedOutputContainer.BakedOutputs) + { + for (auto& Entry : BakedOutput.BakedOutputObjects) + { + if (Entry.Value.Actor == OldActorPath) + Entry.Value.Actor = NewActorPath; + } + } + } + } + else + { + OutputActorOwner.DestroyOutputActor(); + } + } + } + OutBakedActors.Append(BakedActorsForWorkResultObject); + return true; +} + + +bool +FHoudiniEngineBakeUtils::BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultId, + const FString& InWorkResultObjectName) +{ + if (!IsValid(InPDGAssetLink)) + return false; + + if (!IsValid(InNode)) + return false; + + // Find the work result index and work result object index + const int32 WorkResultIndex = InNode->WorkResult.IndexOfByPredicate([InWorkResultId](const FTOPWorkResult& Entry) + { + return Entry.WorkItemID == InWorkResultId; + }); + if (!InNode->WorkResult.IsValidIndex(WorkResultIndex)) + return false; + FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIndex]; + const int32 WorkResultObjectIndex = WorkResult.ResultObjects.IndexOfByPredicate([InWorkResultObjectName](const FTOPWorkResultObject& Entry) + { + return Entry.Name.Equals(InWorkResultObjectName); + }); + if (!WorkResult.ResultObjects.IsValidIndex(WorkResultObjectIndex)) + return false; + + // Determine the output world outliner folder path via the PDG asset link's + // owner's folder path and name + UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); + if (!PDGOwner) + PDGOwner = InPDGAssetLink->GetOuter(); + const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); + + // Determine the actor/package replacement settings + const bool bBakeBlueprints = InPDGAssetLink->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToBlueprint; + const bool bReplaceActors = !bBakeBlueprints && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + // Determine the output types to bake: don't bake landscapes in blueprint baking mode + TArray OutputTypesToBake; + TArray InstancerComponentTypesToBake; + if (bBakeBlueprints) + { + OutputTypesToBake.Add(EHoudiniOutputType::Mesh); + OutputTypesToBake.Add(EHoudiniOutputType::Instancer); + OutputTypesToBake.Add(EHoudiniOutputType::Curve); + + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); + } + + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + TArray BakedActors; + + bool bSuccess = BakePDGWorkResultObject( + InPDGAssetLink, + InNode, + WorkResultIndex, + WorkResultObjectIndex, + bReplaceActors, + bReplaceAssets, + !bBakeBlueprints, + BakedActors, + PackagesToSave, + BakeStats, + OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, + InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, + FallbackWorldOutlinerFolderPath.ToString() + ); + + // Recenter and select the baked actors + if (GEditor && BakedActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : BakedActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (InPDGAssetLink->bRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && BakedActors.Num() > 0) + GEditor->NoteSelectionChange(); + + if (bBakeBlueprints && bSuccess) + { + TArray Blueprints; + bSuccess = BakeBlueprintsFromBakedActors( + BakedActors, + InPDGAssetLink->bRecenterBakedActors, + bReplaceAssets, + InPDGAssetLink->AssetName, + InPDGAssetLink->BakeFolder, + nullptr, + &InNode->GetBakedWorkResultObjectsOutputs(), + Blueprints, + PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + } + + SaveBakedPackages(PackagesToSave); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + return bSuccess; +} + +void +FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultId, + const FString& InWorkResultObjectName) +{ + if (!IsValid(InPDGAssetLink)) + return; + + if (!InPDGAssetLink->bBakeAfterWorkResultObjectLoaded) + return; + + BakePDGWorkResultObject( + InPDGAssetLink, + InNode, + InWorkResultId, + InWorkResultObjectName); +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInBakeForBlueprint, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNode)) + return false; + + // Determine the output world outliner folder path via the PDG asset link's + // owner's folder path and name + UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); + if (!PDGOwner) + PDGOwner = InPDGAssetLink->GetOuter(); + const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); + + // Determine the actor/package replacement settings + const bool bReplaceActors = !bInBakeForBlueprint && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + // Determine the output types to bake: don't bake landscapes in blueprint baking mode + TArray OutputTypesToBake; + TArray InstancerComponentTypesToBake; + if (bInBakeForBlueprint) + { + OutputTypesToBake.Add(EHoudiniOutputType::Mesh); + OutputTypesToBake.Add(EHoudiniOutputType::Instancer); + OutputTypesToBake.Add(EHoudiniOutputType::Curve); + + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); + } + + const int32 NumWorkResults = InNode->WorkResult.Num(); + for (int32 WorkResultIdx = 0; WorkResultIdx < NumWorkResults; ++WorkResultIdx) + { + FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIdx]; + const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); + for (int32 WorkResultObjectIdx = 0; WorkResultObjectIdx < NumWorkResultObjects; ++WorkResultObjectIdx) + { + BakePDGWorkResultObject( + InPDGAssetLink, + InNode, + WorkResultIdx, + WorkResultObjectIdx, + bReplaceActors, + bReplaceAssets, + !bInBakeForBlueprint, + OutBakedActors, + OutPackagesToSave, + OutBakeStats, + OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, + InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, + FallbackWorldOutlinerFolderPath.ToString() + ); + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + bool bInBakeForBlueprint, + TArray& BakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNetwork)) + return false; + + bool bSuccess = true; + for (UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, BakedActors, OutPackagesToSave, OutBakeStats); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + TArray BakedActors; + + const bool bBakeBlueprints = false; + + bool bSuccess = true; + switch(InPDGAssetLink->PDGBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) + { + if (!IsValid(Network)) + continue; + + for (UTOPNode* Node : Network->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + } + } + break; + case EPDGBakeSelectionOption::SelectedNetwork: + bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + case EPDGBakeSelectionOption::SelectedNode: + bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + } + + SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && BakedActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : BakedActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (InPDGAssetLink->bRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && BakedActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( + const TArray& InBakedActors, + bool bInRecenterBakedActors, + bool bInReplaceAssets, + const FString& InAssetName, + const FDirectoryPath& InBakeFolder, + TArray* const InNonPDGBakedOuputs, + TMap* const InPDGBakedOutputs, + TArray& OutBlueprints, + TArray& OutPackagesToSave) +{ + // // Clear selection + // if (GEditor) + // { + // GEditor->SelectNone(false, true); + // GEditor->NoteSelectionChange(); + // } + + // Iterate over the baked actors. An actor might appear multiple times if multiple OutputComponents were + // baked to the same actor, so keep track of actors we have already processed in BakedActorSet + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem); + TArray AssetsToReOpenEditors; + TSet BakedActorSet; + + for (const FHoudiniEngineBakedActor& Entry : InBakedActors) + { + AActor *Actor = Entry.Actor; + + if (!Actor || Actor->IsPendingKill()) + continue; + + if (BakedActorSet.Contains(Actor)) + continue; + + BakedActorSet.Add(Actor); + + UObject* Asset = nullptr; + + // Recenter the actor to its bounding box center + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Actor); + + // Create package for out Blueprint + FString BlueprintName; + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + FHoudiniOutputObjectIdentifier(), + InBakeFolder.Path, + Entry.ActorBakeName.ToString() + "_BP", + InAssetName, + AssetPackageReplaceMode); + + // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment + // is consistent with the bake counter + int32 BakeCounter = 0; + UBlueprint* InPreviousBlueprint = nullptr; + FHoudiniBakedOutputObject* BakedOutputObject = nullptr; + FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; + // Get the baked output object + if (Entry.PDGWorkResultIndex >= 0 && Entry.PDGWorkResultObjectIndex >= 0 && InPDGBakedOutputs) + { + const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkResultIndex, Entry.PDGWorkResultObjectIndex); + WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); + if (WorkResultObjectBakedOutput) + { + if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + } + else if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs) + { + if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs->IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = (*InNonPDGBakedOuputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + if (BakedOutputObject) + { + InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); + if (IsValid(InPreviousBlueprint)) + { + if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint)) + { + PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter); + } + } + } + + UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter); + + if (!Package || Package->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName())); + continue; + } + + if (!Package->IsFullyLoaded()) + Package->FullyLoad(); + + //Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false); + // Find existing asset first first (only relevant if we are in replacement mode). If the existing asset has a + // different base class than the incoming actor, we reparent the blueprint to the new base class before + // clearing the SCS graph and repopulating it from the temp actor. + Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName)); + if (IsValid(Asset)) + { + UBlueprint* Blueprint = Cast(Asset); + if (IsValid(Blueprint)) + { + if (Blueprint->GeneratedClass && Blueprint->GeneratedClass != Actor->GetClass()) + { + // Close editors opened on existing asset if applicable + if (Asset && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Asset, false) != nullptr) + { + AssetEditorSubsystem->CloseAllEditorsForAsset(Asset); + AssetsToReOpenEditors.Add(Asset); + } + + Blueprint->ParentClass = Actor->GetClass(); + + FBlueprintEditorUtils::RefreshAllNodes(Blueprint); + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + FKismetEditorUtilities::CompileBlueprint(Blueprint); + } + } + } + else if (Asset && Asset->IsPendingKill()) + { + // Rename to pending kill so that we can use the desired name + const FString AssetPendingKillName(BlueprintName + "_PENDING_KILL"); + // Asset->Rename(*MakeUniqueObjectNameIfNeeded(Package, UBlueprint::StaticClass(), AssetPendingKillName).ToString()); + RenameAsset(Asset, AssetPendingKillName, true); + Asset = nullptr; + } + + if (!Asset) + { + UBlueprintFactory* Factory = NewObject(); + Factory->ParentClass = Actor->GetClass(); + + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + Asset = AssetToolsModule.Get().CreateAsset( + BlueprintName, InBakeFolder.Path, + UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + } + + UBlueprint* Blueprint = Cast(Asset); + + if (!Blueprint || Blueprint->IsPendingKill()) + { + HOUDINI_LOG_WARNING( + TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."), + *(InBakeFolder.Path), *BlueprintName); + + continue; + } + + // Close editors opened on existing asset if applicable + if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr) + { + AssetEditorSubsystem->CloseAllEditorsForAsset(Blueprint); + AssetsToReOpenEditors.Add(Blueprint); + } + + // Record the blueprint as the previous bake blueprint + if (BakedOutputObject) + BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString(); + + OutBlueprints.Add(Blueprint); + + // Clear old Blueprint Node tree + { + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + + int32 NodeSize = SCS->GetAllNodes().Num(); + for (int32 n = NodeSize - 1; n >= 0; --n) + SCS->RemoveNode(SCS->GetAllNodes()[n]); + } + + FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint); + + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + World->EditorDestroyActor(Actor, true); + + // Save the created BP package. + Package->MarkPackageDirty(); + OutPackagesToSave.Add(Package); + } + + // Re-open asset editors for updated blueprints that were open in editors + if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0) + { + for (UObject* Asset : AssetsToReOpenEditors) + { + if (IsValid(Asset)) + { + AssetEditorSubsystem->OpenEditorForAsset(Asset); + } + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + TArray BPActors; + + if (!IsValid(InPDGAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InPDGAssetLink is null")); + return false; + } + + if (!IsValid(InNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InNode is null")); + return false; + } + + const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + // Bake PDG output to new actors + // bInBakeForBlueprint == true will skip landscapes and instanced actor components + const bool bInBakeForBlueprint = true; + TArray BakedActors; + bool bSuccess = BakePDGTOPNodeOutputsKeepActors( + InPDGAssetLink, + InNode, + bInBakeForBlueprint, + BakedActors, + OutPackagesToSave, + OutBakeStats + ); + + if (bSuccess) + { + bSuccess = BakeBlueprintsFromBakedActors( + BakedActors, + InPDGAssetLink->bRecenterBakedActors, + bReplaceAssets, + InPDGAssetLink->AssetName, + InPDGAssetLink->BakeFolder, + nullptr, + &InNode->GetBakedWorkResultObjectsOutputs(), + OutBlueprints, + OutPackagesToSave); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNetwork)) + return false; + + bool bSuccess = true; + for (UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, OutBlueprints, OutPackagesToSave, OutBakeStats); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + TArray Blueprints; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + bool bSuccess = true; + switch(InPDGAssetLink->PDGBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) + { + if (!IsValid(Network)) + continue; + + for (UTOPNode* Node : Network->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, Blueprints, PackagesToSave, BakeStats); + } + } + break; + case EPDGBakeSelectionOption::SelectedNetwork: + bSuccess &= BakePDGTOPNetworkBlueprints( + InPDGAssetLink, + InPDGAssetLink->GetSelectedTOPNetwork(), + Blueprints, + PackagesToSave, + BakeStats); + case EPDGBakeSelectionOption::SelectedNode: + bSuccess &= BakePDGTOPNodeBlueprints( + InPDGAssetLink, + InPDGAssetLink->GetSelectedTOPNode(), + Blueprints, + PackagesToSave, + BakeStats); + } + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + const FString& InLevelPath, + ULevel*& OutDesiredLevel, + UWorld*& OutDesiredWorld, + bool& OutCreatedPackage) +{ + OutDesiredLevel = nullptr; + OutDesiredWorld = nullptr; + if (InLevelPath.IsEmpty()) + { + OutDesiredWorld = GWorld; + OutDesiredLevel = GWorld->GetCurrentLevel(); + } + else + { + OutCreatedPackage = false; + + UWorld* FoundWorld = nullptr; + ULevel* FoundLevel = nullptr; + bool bActorInWorld = false; + if (FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + GWorld, + InLevelPath, + true, + FoundWorld, + FoundLevel, + OutCreatedPackage, + bActorInWorld)) + { + OutDesiredLevel = FoundLevel; + OutDesiredWorld = FoundWorld; + } + } + + return ((OutDesiredWorld != nullptr) && (OutDesiredLevel != nullptr)); +} + + +bool +FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( + const FString& InBakeActorName, + ULevel* InLevel, + AActor*& OutActor, + bool bInNoPendingKillActors, + bool bRenamePendingKillActor) +{ + OutActor = nullptr; + + if (!IsValid(InLevel)) + return false; + + UWorld* const World = InLevel->GetWorld(); + if (!IsValid(World)) + return false; + + // Look for an actor with the given name in the world + const FName BakeActorFName(InBakeActorName); + AActor* FoundActor = Cast(StaticFindObjectFast(AActor::StaticClass(), InLevel, BakeActorFName)); + // for (TActorIterator Iter(World, AActor::StaticClass(), EActorIteratorFlags::AllActors); Iter; ++Iter) + // { + // AActor* const Actor = *Iter; + // if (Actor->GetFName() == BakeActorFName && Actor->GetLevel() == InLevel) + // { + // // Found the actor + // FoundActor = Actor; + // break; + // } + // } + + // If we found an actor and it is pending kill, rename it and don't use it + if (FoundActor) + { + if (FoundActor->IsPendingKill()) + { + if (bRenamePendingKillActor) + { + // FoundActor->Rename( + // *MakeUniqueObjectNameIfNeeded( + // FoundActor->GetOuter(), + // FoundActor->GetClass(), + // FName(FoundActor->GetName() + "_Pending_Kill")).ToString()); + RenameAndRelabelActor( + FoundActor, + *MakeUniqueObjectNameIfNeeded( + FoundActor->GetOuter(), + FoundActor->GetClass(), + FName(FoundActor->GetName() + "_Pending_Kill"), + FoundActor).ToString(), + false); + } + if (bInNoPendingKillActors) + FoundActor = nullptr; + else + OutActor = FoundActor; + } + else + { + OutActor = FoundActor; + } + } + + return true; +} + +bool FHoudiniEngineBakeUtils::FindUnrealBakeActor( + const FHoudiniOutputObject& InOutputObject, + const FHoudiniBakedOutputObject& InBakedOutputObject, + const TArray& InAllBakedActors, + ULevel* InLevel, + FName InDefaultActorName, + bool bInReplaceActorBakeMode, + AActor* InFallbackActor, + AActor*& OutFoundActor, + bool& bOutHasBakeActorName, + FName& OutBakeActorName) +{ + // Determine desired actor name via unreal_output_actor, fallback to InDefaultActorName + OutBakeActorName = NAME_None; + OutFoundActor = nullptr; + bOutHasBakeActorName = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_ACTOR); + if (bOutHasBakeActorName) + { + const FString& BakeActorNameStr = InOutputObject.CachedAttributes[HAPI_UNREAL_ATTRIB_BAKE_ACTOR]; + if (BakeActorNameStr.IsEmpty()) + { + OutBakeActorName = NAME_None; + bOutHasBakeActorName = false; + } + else + { + OutBakeActorName = *BakeActorNameStr; + // We have a bake actor name, look for the actor + AActor* BakeNameActor = nullptr; + if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor)) + { + // Found an actor with that name, check that we "own" it (we created in during baking previously) + AActor* IncrementedBakedActor = nullptr; + for (const FHoudiniEngineBakedActor& BakedActor : InAllBakedActors) + { + if (!IsValid(BakedActor.Actor)) + continue; + if (BakedActor.Actor == BakeNameActor) + { + OutFoundActor = BakeNameActor; + break; + } + else if (!IncrementedBakedActor && BakedActor.ActorBakeName == OutBakeActorName) + { + // Found an actor we have baked named OutBakeActorName_# (incremental version of our desired name) + IncrementedBakedActor = BakedActor.Actor; + } + } + if (!OutFoundActor && IncrementedBakedActor) + OutFoundActor = IncrementedBakedActor; + } + } + } + + // If unreal_actor_name is not set, or is blank, fallback to InDefaultActorName + if (!bOutHasBakeActorName || (OutBakeActorName.IsNone() || OutBakeActorName.ToString().TrimStartAndEnd().IsEmpty())) + OutBakeActorName = InDefaultActorName; + + if (!OutFoundActor) + { + // If in replace mode, use previous bake actor if valid and in InLevel + if (bInReplaceActorBakeMode) + { + const FSoftObjectPath PrevActorPath(InBakedOutputObject.Actor); + const FString ActorPath = PrevActorPath.IsSubobject() + ? PrevActorPath.GetAssetPathString() + ":" + PrevActorPath.GetSubPathString() + : PrevActorPath.GetAssetPathString(); + const FString LevelPath = IsValid(InLevel) ? InLevel->GetPathName() : ""; + if (PrevActorPath.IsValid() && (LevelPath.IsEmpty() || ActorPath.StartsWith(LevelPath))) + OutFoundActor = InBakedOutputObject.GetActorIfValid(); + } + + // Fallback to InFallbackActor if valid and in InLevel + if (!OutFoundActor && IsValid(InFallbackActor) && (!InLevel || InFallbackActor->GetLevel() == InLevel)) + OutFoundActor = InFallbackActor; + } + + return true; +} + +AActor* +FHoudiniEngineBakeUtils::FindExistingActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + bCreatedPackage = false; + + // Try to Locate a previous actor + AActor* FoundActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); + if (FoundActor) + FoundActor->Destroy(); // nuke it! + + if (FoundActor) + { + // TODO: make sure that the found is actor is actually assigned to the level defined by package path. + // If the found actor is not from that level, it should be moved there. + + OutWorld = FoundActor->GetWorld(); + OutLevel = FoundActor->GetLevel(); + } + else + { + // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. + bool bActorInWorld = false; + const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + InWorld, + InPackagePath, + true, + OutWorld, + OutLevel, + bCreatedPackage, + bActorInWorld); + + if (!bResult) + { + return nullptr; + } + + if (!bActorInWorld) + { + // The OutLevel is not present in the current world which means we might + // still find the tile actor in OutWorld. + FoundActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); + } + } + + return FoundActor; +} + +bool +FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh( + UHoudiniAssetComponent* InHoudiniAssetComponent, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess, + bool& bOutNeedsReCook) +{ + if (!IsValid(InHoudiniAssetComponent)) + { + return false; + } + + // Handle proxies: if the output has any current proxies, first refine them + bOutNeedsReCook = false; + if (InHoudiniAssetComponent->HasAnyCurrentProxyOutput()) + { + bool bNeedsRebuildOrDelete; + bool bInvalidState; + const bool bCookedDataAvailable = InHoudiniAssetComponent->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bInvalidState); + + if (bCookedDataAvailable) + { + // Cook data is available, refine the mesh + AHoudiniAssetActor* HoudiniActor = Cast(InHoudiniAssetComponent->GetOwner()); + if (IsValid(HoudiniActor)) + { + FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ HoudiniActor }); + } + } + else if (!bNeedsRebuildOrDelete && !bInvalidState) + { + // A cook is needed: request the cook, but with no proxy and with a bake after cook + InHoudiniAssetComponent->SetNoProxyMeshNextCookRequested(true); + // Only + if (!InHoudiniAssetComponent->IsBakeAfterNextCookEnabled() || !InHoudiniAssetComponent->GetOnPostCookBakeDelegate().IsBound()) + { + InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess](UHoudiniAssetComponent* InHAC) { + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess); + }); + } + InHoudiniAssetComponent->MarkAsNeedCook(); + + bOutNeedsReCook = true; + + // The cook has to complete first (asynchronously) before the bake can happen + // The SetBakeAfterNextCookEnabled flag will result in a bake after cook + return false; + } + else + { + // The HAC is in an unsupported state + const EHoudiniAssetState AssetState = InHoudiniAssetComponent->GetAssetState(); + HOUDINI_LOG_ERROR(TEXT("Could not refine (in order to bake) %s, the asset is in an unsupported state: %s"), *(InHoudiniAssetComponent->GetPathName()), *(UEnum::GetValueAsString(AssetState))); + return false; + } + } + + return true; +} + +void +FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor) +{ + if (!IsValid(InActor)) + return; + + USceneComponent * const RootComponent = InActor->GetRootComponent(); + if (!IsValid(RootComponent)) + return; + + // If the root component does not have any child components, then there is nothing to recenter + if (RootComponent->GetNumChildrenComponents() <= 0) + return; + + const bool bOnlyCollidingComponents = false; + const bool bIncludeFromChildActors = true; + FVector Origin; + FVector BoxExtent; + InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); + + const FVector Delta = Origin - RootComponent->GetComponentLocation(); + // Actor->SetActorLocation(Origin); + RootComponent->SetWorldLocation(Origin); + + for (USceneComponent* SceneComponent : RootComponent->GetAttachChildren()) + { + if (!IsValid(SceneComponent)) + continue; + + SceneComponent->SetWorldLocation(SceneComponent->GetComponentLocation() - Delta); + } +} + +void +FHoudiniEngineBakeUtils::CenterActorsToBoundingBoxCenter(const TArray& InActors) +{ + for (AActor* Actor : InActors) + { + if (!IsValid(Actor)) + continue; + + CenterActorToBoundingBoxCenter(Actor); + } +} + +USceneComponent* +FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMissing, EComponentMobility::Type InMobilityIfCreated) +{ + USceneComponent* RootComponent = InActor->GetRootComponent(); + if (!IsValid(RootComponent)) + { + RootComponent = NewObject(InActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); + + // Change the creation method so the component is listed in the details panels + InActor->SetRootComponent(RootComponent); + InActor->AddInstanceComponent(RootComponent); + RootComponent->RegisterComponent(); + RootComponent->SetMobility(InMobilityIfCreated); + } + + return RootComponent; +} + +FName +FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed) +{ + if (IsValid(InObjectThatWouldBeRenamed)) + { + const FName CurrentName = InObjectThatWouldBeRenamed->GetFName(); + if (CurrentName == InName) + return InName; + + // Check if the prefix matches (without counter suffix) the new name + const FString CurrentNamePlainStr = CurrentName.GetPlainNameString(); + if (CurrentNamePlainStr == InName.ToString()) + return CurrentName; + } + + UObject* ExistingObject = nullptr; + if (InOuter == ANY_PACKAGE) + { + ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *InName.ToString()); + } + else + { + ExistingObject = StaticFindObjectFast(nullptr, InOuter, InName); + } + + if (ExistingObject) + return MakeUniqueObjectName(InOuter, InClass, InName); + return InName; +} + +FName +FHoudiniEngineBakeUtils::GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) +{ + const FString* FolderPathPtr = InOutputObject.CachedAttributes.Find(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER); + if (FolderPathPtr && !FolderPathPtr->IsEmpty()) + return FName(*FolderPathPtr); + else + return InDefaultFolder; +} + +bool +FHoudiniEngineBakeUtils::SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) +{ + if (!IsValid(InActor)) + return false; + + InActor->SetFolderPath(GetOutlinerFolderPath(InOutputObject, InDefaultFolder)); + return true; +} + +uint32 +FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInDestroyBakedComponent, + bool bInDestroyBakedInstancedActors, + bool bInDestroyBakedInstancedComponents) +{ + uint32 NumDeleted = 0; + + if (bInDestroyBakedComponent) + { + UActorComponent* Component = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (Component) + { + if (RemovePreviouslyBakedComponent(Component)) + { + InBakedOutputObject.BakedComponent = nullptr; + NumDeleted++; + } + } + } + + if (bInDestroyBakedInstancedActors) + { + for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) + { + const FSoftObjectPath ActorPath(ActorPathStr); + + if (!ActorPath.IsValid()) + continue; + + AActor* Actor = Cast(ActorPath.TryLoad()); + if (IsValid(Actor)) + { + UWorld* World = Actor->GetWorld(); + if (IsValid(World)) + { +#if WITH_EDITOR + World->EditorDestroyActor(Actor, true); +#else + World->DestroyActor(Actor); +#endif + NumDeleted++; + } + } + } + InBakedOutputObject.InstancedActors.Empty(); + } + + if (bInDestroyBakedInstancedComponents) + { + for (const FString& ComponentPathStr : InBakedOutputObject.InstancedComponents) + { + const FSoftObjectPath ComponentPath(ComponentPathStr); + + if (!ComponentPath.IsValid()) + continue; + + UActorComponent* Component = Cast(ComponentPath.TryLoad()); + if (IsValid(Component)) + { + if (RemovePreviouslyBakedComponent(Component)) + NumDeleted++; + } + } + InBakedOutputObject.InstancedComponents.Empty(); + } + + return NumDeleted; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h index bcfd41a5f..08e780c30 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h @@ -1,621 +1,622 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#pragma once - -#include "HoudiniPDGAssetLink.h" -#include "HoudiniOutput.h" - -class UHoudiniAssetComponent; -class UHoudiniOutput; -class ALandscapeProxy; -class UStaticMesh; -class USplineComponent; -class UPackage; -class UWorld; -class AActor; -class UHoudiniSplineComponent; -class UStaticMeshComponent; -class UHoudiniPDGAssetLink; -class UTOPNetwork; -class UTOPNode; - -struct FHoudiniPackageParams; -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniEngineOutputStats; -struct FHoudiniBakedOutputObject; - -enum class EHoudiniLandscapeOutputBakeType : uint8; - -// An enum of the different types for instancer component/bake types -UENUM() -enum class EHoudiniInstancerComponentType : uint8 -{ - StaticMeshComponent, - InstancedStaticMeshComponent, - MeshSplitInstancerComponent, - InstancedActorComponent -}; - -// Helper struct to track actors created/used when baking, with -// the intended bake name (before making it unique), and their -// output index and output object identifier. -struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor -{ - FHoudiniEngineBakedActor(); - - FHoudiniEngineBakedActor( - AActor* InActor, - FName InActorBakeName, - FName InWorldOutlinerFolder, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - UObject* InBakedObject, - UObject* InSourceObject); - - // The actor that the baked output was associated with - AActor* Actor = nullptr; - - // The output index on the HAC for the baked object - int32 OutputIndex = INDEX_NONE; - - // The output object identifier for the baked object - FHoudiniOutputObjectIdentifier OutputObjectIdentifier; - - // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. - FName ActorBakeName = NAME_None; - - // The world outliner folder the actor is placed in - FName WorldOutlinerFolder = NAME_None; - - // The index of the work item when baking PDG - int32 PDGWorkResultIndex = INDEX_NONE; - - // The index of the work result object of the work item when baking PDG - int32 PDGWorkResultObjectIndex = INDEX_NONE; - - // The baked primary asset (such as static mesh) - UObject* BakedObject = nullptr; - - // The temp asset that was baked to BakedObject - UObject* SourceObject = nullptr; -}; - -struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils -{ -public: - - /** Bake static mesh. **/ - - /*static UStaticMesh * BakeStaticMesh( - UHoudiniAssetComponent * HoudiniAssetComponent, - UStaticMesh * InStaticMesh, - const FHoudiniPackageParams &PackageParams);*/ - - static ALandscapeProxy* BakeHeightfield( - ALandscapeProxy * InLandscapeProxy, - const FHoudiniPackageParams &PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType); - - static bool BakeCurve( - USplineComponent* InSplineComponent, - ULevel* InLevel, - const FHoudiniPackageParams &PackageParams, - AActor*& OutActor, - USplineComponent*& OutSplineComponent, - FName InOverrideFolderPath=NAME_None, - AActor* InActor=nullptr); - - static bool BakeCurve( - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - // const TArray& InAllBakedOutputs, - const FHoudiniPackageParams &PackageParams, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static AActor* BakeInputHoudiniCurveToActor( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PakcageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform); - - static UBlueprint* BakeInputHoudiniCurveToBlueprint( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PakcageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform); - - static UStaticMesh* BakeStaticMesh( - UStaticMesh * StaticMesh, - const FHoudiniPackageParams & PackageParams, - const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder); - - static bool BakeLandscape( - int32 InOutputIndex, - UHoudiniOutput* InOutput, - TMap& InBakedOutputObjects, - bool bInReplaceActors, - bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, - FHoudiniEngineOutputStats& BakeStats); - - static bool BakeLandscapeObject( - FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInReplaceActors, - bool bInReplaceAssets, - FHoudiniPackageParams& PackageParams, - TArray& WorldsToUpdate, - TArray& OutPackagesToUnload, - FHoudiniEngineOutputStats& BakeStats); - - static bool BakeInstancerOutputToActors( - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_ISMC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_IAC( - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave); - - static bool BakeInstancerOutputToActors_MSIC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_SMC( - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static UStaticMesh * DuplicateStaticMeshAndCreatePackageIfNeeded( - UStaticMesh * InStaticMesh, - UStaticMesh * InPreviousBakeStaticMesh, - const FHoudiniPackageParams &PackageParams, - const TArray& InParentOutputs, - const TArray& InCurrentBakedActors, - const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages); - - static UMaterial * DuplicateMaterialAndCreatePackage( - UMaterial * Material, - UMaterial* PreviousBakeMaterial, - const FString & SubMaterialName, - const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutCreatedPackages); - - static void ReplaceDuplicatedMaterialTextureSample( - UMaterialExpression * MaterialExpression, - UMaterialExpression* PreviousBakeMaterialExpression, - const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); - - static UTexture2D * DuplicateTextureAndCreatePackage( - UTexture2D * Texture, - UTexture2D* PreviousBakeTexture, - const FString & SubTextureName, - const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); - - // Bake a Houdini asset component (InHACToBake) based on the bInReplace and BakeOption arguments. - // Returns true if the underlying bake function (for example, BakeHoudiniActorToActors, returns true (or a valid UObject*)) - static bool BakeHoudiniAssetComponent( - UHoudiniAssetComponent* InHACToBake, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess); - - static bool BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets); - - static bool BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeHoudiniOutputsToActors( - const TArray& InOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FTransform& InParentTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); - - static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); - - static bool BakeStaticMeshOutputToActors( - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeHoudiniCurveOutputToActors( - UHoudiniOutput* Output, - TMap& InBakedOutputObjects, - const TArray& InAllBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeBlueprintsFromBakedActors( - const TArray& InBakedActors, - bool bInRecenterBakedActors, - bool bInReplaceAssets, - const FString& InAssetName, - const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOuputs, - TMap* const InPDGBakedOutputs, - TArray& OutBlueprints, - TArray& OutPackagesToSave); - - static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); - - static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, FHoudiniEngineOutputStats& InBakeStats, TArray& OutBlueprints, TArray& OutPackagesToSave); - - static bool CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint); - - static void AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const TCHAR * Key, - const TCHAR * Value); - - static bool GetHoudiniGeneratedNameFromMetaInformation( - UPackage * Package, UObject * Object, FString & HoudiniName); - - static bool DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent); - - static void SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld = false); - - // Look for InObjectToFind among InOutputs. Return true if found and set OutOutputIndex and OutIdentifier. - static bool FindOutputObject( - const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); - - static bool IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC); - - static bool IsObjectTemporary( - UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); - - // Function used to copy properties from the source Static Mesh Component to the new (baked) one - static void CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC); - - // Finds the world/level indicated by the package path. - // If the level doesn't exists, it will be created. - // If InLevelPath is empty, outputs the editor world and current level - // Returns true if the world/level were found, false otherwise - static bool FindOrCreateDesiredLevelFromLevelPath( - const FString& InLevelPath, - ULevel*& OutDesiredLevel, - UWorld*& OutDesiredWorld, - bool& OutCreatedPackage); - - // Finds the actor indicated by InBakeActorName in InLevel. - // Returns false if any input was invalid (InLevel is invalid for example), true otherwise - // If an actor was found OutActor is set - // If bInNoPendingKillActors is true, then if an actor called InBakeActorName is found but is pending kill, then - // it is not set in OutActor - // If bRenamePendingKillActor is true, then if a pending kill actor call InBakeActorName is found it is renamed - // (uniquely) with a _Pending_Kill suffix (regardless of bInNoPendingKillActors). - static bool FindDesiredBakeActorFromBakeActorName( - const FString& InBakeActorName, - ULevel* InLevel, - AActor*& OutActor, - bool bInNoPendingKillActors=true, - bool bRenamePendingKillActor=true); - - // Helper that determines the desired bake actor name with unreal_bake_actor attribute, falling - // back to InDefaultActorName if the attribute is not set. - // If unreal_bake_actor is set, we look for such in InLevel, and use it *if* it is present in InAlLBakedOutputs. - // Otherwise if we are baking in replace mode, and the previous bake actor is available and in InLevel, return it - // as OutFoundActor. Otherwise return InFallbackActor as OutFoundActor. - // bOutHasBakeActorName indicates if the output has the unreal_bake_actor attribute set. - // OutFoundActor is the actor that was found (if one was found) - static bool FindUnrealBakeActor( - const FHoudiniOutputObject& InOutputObject, - const FHoudiniBakedOutputObject& InBakedOutputObject, - const TArray& InAllBakedActors, - ULevel* InLevel, - FName InDefaultActorName, - bool bInReplaceActorBakeMode, - AActor* InFallbackActor, - AActor*& OutFoundActor, - bool& bOutHasBakeActorName, - FName& OutBakeActorName); - - // Try to find an actor that we can use for baking. - // If the requested actor could not be found, then `OutWorld` and `OutLevel` - // should be used to spawn the new bake actor. - // @returns AActor* if found. Otherwise, returns nullptr. - static AActor* FindExistingActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - // Remove a previously baked actor - static bool RemovePreviouslyBakedActor( - AActor* InNewBakedActor, - ULevel* InLevel, - const FHoudiniPackageParams& InPackageParams); - - static bool RemovePreviouslyBakedComponent(UActorComponent* InComponent); - - // Get the world outliner folder path for output generated by InOutputOwner - static FName GetOutputFolderPath(UObject* InOutputOwner); - - static void RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); - - // Helper function for renaming and relabelling an actor - static void RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); - - // Start: PDG Baking - - // Detach InActor from its parent, and rename to InNewName (attaches a numeric suffix to make it unique via - // MakeUniqueObjectName). Place it in the world outliner folder InFolderPath. - static bool DetachAndRenameBakedPDGOutputActor(AActor* InActor, const FString& InNewName, const FName& InFolderPath); - - static bool BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultIndex, - int32 InWorkResultObjectIndex, - bool bInReplaceActors, - bool bInReplaceAssets, - bool bInBakeToWorkResultActor, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName); - - // Checks if auto-bake is enabled on InPDGAssetLink, and if it is, calls BakePDGWorkResultObject. - static void AutoBakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName); - - // Bake PDG output. This bakes all assets from all work items in the specified InNode (FTOPNode). - // It uses the existing output actors in the level, but breaks any links from these actors to the PDG link and - // moves the actors out of the parent Folder/ detaches from the parent PDG output actor. - static bool BakePDGTOPNodeOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInBakeForBlueprint, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes all assets from all work items in the specified TOP network. - // It uses the existing output actors in the level, but breaks any links - // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent - // PDG output actor. - static bool BakePDGTOPNetworkOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - bool bInBakeForBlueprint, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes assets from TOP networks and nodes according to - // InPDGAssetLink->PDGBakeSelectionOption. It uses the existing output actors in the level, but breaks any links - // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent - // PDG output actor. - static bool BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink); - - // Bake PDG output. This bakes all supported assets from all work items in the specified InNode (FTOPNode). - // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from - // PDG output actors. - static bool BakePDGTOPNodeBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes all supported assets from all work items in the specified TOP network. - // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from - // PDG output actors. - static bool BakePDGTOPNetworkBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes assets from TOP networks and nodes according to - // InPDGAssetLink->PDGBakeSelectionOption. It duplicates the output actors and bakes them to blueprints. Assets - // that were baked are removed from PDG output actors. - static bool BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink); - - // End: PDG Baking - -protected: - - // Find the HGPO with matching identifier. Returns true if the HGPO was found. - static bool FindHGPO( - const FHoudiniOutputObjectIdentifier& InIdentifier, - const TArray& InHGPOs, - FHoudiniGeoPartObject const*& OutHGPO); - - // Set OutBakeName to the resolved output name of InMeshOutputObject / InObject. OutBakeName is set to the object's - // BakeName (the BakeName on the InMeshOutputObject, or if that is not set, the custom part name or finally the - // package name. - static void GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const FHoudiniOutputObject& InMeshOutputObject, - FString& OutBakeName); - - // Look for InObject in InAllOutputs. If found the function returns true and OutBakeName is set to the object's - // BakeName (the BakeName on the OutputObject, or if that is not set, the custom part name or finally the package - // name. - static bool GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const TArray& InAllOutputs, - FString& OutBakeName); - - // Checks if InHoudiniAssetComponent has any current proxy mesh. Refines if it possible. Returns true - // if baking can continue, false otherwise. If the component has a proxy, but no cook data, then false is - // returned, the component is set to recook without a proxy and with bake after cook, and bOutNeedsReCook is set - // to true. - // bInReplace and BakeOption represents the baking settings to use if a delayed bake (post-cook) needs to be triggered. - static bool CheckForAndRefineHoudiniProxyMesh( - UHoudiniAssetComponent* InHoudiniAssetComponent, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption BakeOption, - bool bInRemoveHACOutputOnSuccess, - bool& bOutNeedsReCook); - - // Position InActor at its bounding box center (keep components' world location) - static void CenterActorToBoundingBoxCenter(AActor* InActor); - - // Position each of the actors in InActors at its bounding box center (keep components' world location) - static void CenterActorsToBoundingBoxCenter(const TArray& InActors); - - // Helper to get or optionally create a RootComponent for an actor - static USceneComponent* GetActorRootComponent( - AActor* InActor, bool bCreateIfMissing=true, EComponentMobility::Type InMobilityIfCreated=EComponentMobility::Static); - - // Helper function to return a unique object name if the given is already in use - static FName MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed=nullptr); - - // Helper for getting the actor folder path for the world outliner, based unreal_bake_outliner_folder - static FName GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); - - // Helper for setting the actor folder path in the world outliner - static bool SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); - - // Helper for destroying previous bake components/actors - static uint32 DestroyPreviousBakeOutput( - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInDestroyBakedComponent, - bool bInDestroyBakedInstancedActors, - bool bInDestroyBakedInstancedComponents); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniPDGAssetLink.h" +#include "HoudiniOutput.h" + +class UHoudiniAssetComponent; +class UHoudiniOutput; +class ALandscapeProxy; +class UStaticMesh; +class USplineComponent; +class UPackage; +class UWorld; +class AActor; +class UHoudiniSplineComponent; +class UStaticMeshComponent; +class UHoudiniPDGAssetLink; +class UTOPNetwork; +class UTOPNode; + +struct FHoudiniPackageParams; +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniEngineOutputStats; +struct FHoudiniBakedOutputObject; + +enum class EHoudiniLandscapeOutputBakeType : uint8; + +// An enum of the different types for instancer component/bake types +UENUM() +enum class EHoudiniInstancerComponentType : uint8 +{ + StaticMeshComponent, + InstancedStaticMeshComponent, + MeshSplitInstancerComponent, + InstancedActorComponent +}; + +// Helper struct to track actors created/used when baking, with +// the intended bake name (before making it unique), and their +// output index and output object identifier. +struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor +{ + FHoudiniEngineBakedActor(); + + FHoudiniEngineBakedActor( + AActor* InActor, + FName InActorBakeName, + FName InWorldOutlinerFolder, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + UObject* InBakedObject, + UObject* InSourceObject); + + // The actor that the baked output was associated with + AActor* Actor = nullptr; + + // The output index on the HAC for the baked object + int32 OutputIndex = INDEX_NONE; + + // The output object identifier for the baked object + FHoudiniOutputObjectIdentifier OutputObjectIdentifier; + + // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. + FName ActorBakeName = NAME_None; + + // The world outliner folder the actor is placed in + FName WorldOutlinerFolder = NAME_None; + + // The index of the work item when baking PDG + int32 PDGWorkResultIndex = INDEX_NONE; + + // The index of the work result object of the work item when baking PDG + int32 PDGWorkResultObjectIndex = INDEX_NONE; + + // The baked primary asset (such as static mesh) + UObject* BakedObject = nullptr; + + // The temp asset that was baked to BakedObject + UObject* SourceObject = nullptr; +}; + +struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils +{ +public: + + /** Bake static mesh. **/ + + /*static UStaticMesh * BakeStaticMesh( + UHoudiniAssetComponent * HoudiniAssetComponent, + UStaticMesh * InStaticMesh, + const FHoudiniPackageParams &PackageParams);*/ + + static ALandscapeProxy* BakeHeightfield( + ALandscapeProxy * InLandscapeProxy, + const FHoudiniPackageParams &PackageParams, + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType); + + static bool BakeCurve( + USplineComponent* InSplineComponent, + ULevel* InLevel, + const FHoudiniPackageParams &PackageParams, + AActor*& OutActor, + USplineComponent*& OutSplineComponent, + FName InOverrideFolderPath=NAME_None, + AActor* InActor=nullptr); + + static bool BakeCurve( + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + // const TArray& InAllBakedOutputs, + const FHoudiniPackageParams &PackageParams, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static AActor* BakeInputHoudiniCurveToActor( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PakcageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform); + + static UBlueprint* BakeInputHoudiniCurveToBlueprint( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PakcageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform); + + static UStaticMesh* BakeStaticMesh( + UStaticMesh * StaticMesh, + const FHoudiniPackageParams & PackageParams, + const TArray& InAllOutputs, + const FDirectoryPath& InTempCookFolder); + + static bool BakeLandscape( + int32 InOutputIndex, + UHoudiniOutput* InOutput, + TMap& InBakedOutputObjects, + bool bInReplaceActors, + bool bInReplaceAssets, + FString BakePath, + FString HoudiniAssetName, + FHoudiniEngineOutputStats& BakeStats); + + static bool BakeLandscapeObject( + FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInReplaceActors, + bool bInReplaceAssets, + FHoudiniPackageParams& PackageParams, + TArray& WorldsToUpdate, + TArray& OutPackagesToUnload, + FHoudiniEngineOutputStats& BakeStats); + + static bool BakeInstancerOutputToActors( + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_ISMC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_IAC( + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave); + + static bool BakeInstancerOutputToActors_MSIC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_SMC( + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static UStaticMesh * DuplicateStaticMeshAndCreatePackageIfNeeded( + UStaticMesh * InStaticMesh, + UStaticMesh * InPreviousBakeStaticMesh, + const FHoudiniPackageParams &PackageParams, + const TArray& InParentOutputs, + const TArray& InCurrentBakedActors, + const FString& InTemporaryCookFolder, + TArray & OutCreatedPackages); + + static UMaterial * DuplicateMaterialAndCreatePackage( + UMaterial * Material, + UMaterial* PreviousBakeMaterial, + const FString & SubMaterialName, + const FHoudiniPackageParams& ObjectPackageParams, + TArray & OutCreatedPackages); + + static void ReplaceDuplicatedMaterialTextureSample( + UMaterialExpression * MaterialExpression, + UMaterialExpression* PreviousBakeMaterialExpression, + const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages); + + static UTexture2D * DuplicateTextureAndCreatePackage( + UTexture2D * Texture, + UTexture2D* PreviousBakeTexture, + const FString & SubTextureName, + const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages); + + // Bake a Houdini asset component (InHACToBake) based on the bInReplace and BakeOption arguments. + // Returns true if the underlying bake function (for example, BakeHoudiniActorToActors, returns true (or a valid UObject*)) + static bool BakeHoudiniAssetComponent( + UHoudiniAssetComponent* InHACToBake, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess); + + static bool BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets); + + static bool BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeHoudiniOutputsToActors( + const TArray& InOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FTransform& InParentTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); + + static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); + + static bool BakeStaticMeshOutputToActors( + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeHoudiniCurveOutputToActors( + UHoudiniOutput* Output, + TMap& InBakedOutputObjects, + const TArray& InAllBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeBlueprintsFromBakedActors( + const TArray& InBakedActors, + bool bInRecenterBakedActors, + bool bInReplaceAssets, + const FString& InAssetName, + const FDirectoryPath& InBakeFolder, + TArray* const InNonPDGBakedOuputs, + TMap* const InPDGBakedOutputs, + TArray& OutBlueprints, + TArray& OutPackagesToSave); + + static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); + + static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, FHoudiniEngineOutputStats& InBakeStats, TArray& OutBlueprints, TArray& OutPackagesToSave); + + static bool CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint); + + static void AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const TCHAR * Key, + const TCHAR * Value); + + static bool GetHoudiniGeneratedNameFromMetaInformation( + UPackage * Package, UObject * Object, FString & HoudiniName); + + static bool DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent); + + static void SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld = false); + + // Look for InObjectToFind among InOutputs. Return true if found and set OutOutputIndex and OutIdentifier. + static bool FindOutputObject( + const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); + + static bool IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC); + + static bool IsObjectTemporary( + UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); + + // Function used to copy properties from the source Static Mesh Component to the new (baked) one + static void CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC); + + // Finds the world/level indicated by the package path. + // If the level doesn't exists, it will be created. + // If InLevelPath is empty, outputs the editor world and current level + // Returns true if the world/level were found, false otherwise + static bool FindOrCreateDesiredLevelFromLevelPath( + const FString& InLevelPath, + ULevel*& OutDesiredLevel, + UWorld*& OutDesiredWorld, + bool& OutCreatedPackage); + + // Finds the actor indicated by InBakeActorName in InLevel. + // Returns false if any input was invalid (InLevel is invalid for example), true otherwise + // If an actor was found OutActor is set + // If bInNoPendingKillActors is true, then if an actor called InBakeActorName is found but is pending kill, then + // it is not set in OutActor + // If bRenamePendingKillActor is true, then if a pending kill actor call InBakeActorName is found it is renamed + // (uniquely) with a _Pending_Kill suffix (regardless of bInNoPendingKillActors). + static bool FindDesiredBakeActorFromBakeActorName( + const FString& InBakeActorName, + ULevel* InLevel, + AActor*& OutActor, + bool bInNoPendingKillActors=true, + bool bRenamePendingKillActor=true); + + // Helper that determines the desired bake actor name with unreal_bake_actor attribute, falling + // back to InDefaultActorName if the attribute is not set. + // If unreal_bake_actor is set, we look for such in InLevel, and use it *if* it is present in InAlLBakedOutputs. + // Otherwise if we are baking in replace mode, and the previous bake actor is available and in InLevel, return it + // as OutFoundActor. Otherwise return InFallbackActor as OutFoundActor. + // bOutHasBakeActorName indicates if the output has the unreal_bake_actor attribute set. + // OutFoundActor is the actor that was found (if one was found) + static bool FindUnrealBakeActor( + const FHoudiniOutputObject& InOutputObject, + const FHoudiniBakedOutputObject& InBakedOutputObject, + const TArray& InAllBakedActors, + ULevel* InLevel, + FName InDefaultActorName, + bool bInReplaceActorBakeMode, + AActor* InFallbackActor, + AActor*& OutFoundActor, + bool& bOutHasBakeActorName, + FName& OutBakeActorName); + + // Try to find an actor that we can use for baking. + // If the requested actor could not be found, then `OutWorld` and `OutLevel` + // should be used to spawn the new bake actor. + // @returns AActor* if found. Otherwise, returns nullptr. + static AActor* FindExistingActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + // Remove a previously baked actor + static bool RemovePreviouslyBakedActor( + AActor* InNewBakedActor, + ULevel* InLevel, + const FHoudiniPackageParams& InPackageParams); + + static bool RemovePreviouslyBakedComponent(UActorComponent* InComponent); + + // Get the world outliner folder path for output generated by InOutputOwner + static FName GetOutputFolderPath(UObject* InOutputOwner); + + static void RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); + + // Helper function for renaming and relabelling an actor + static void RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); + + // Start: PDG Baking + + // Detach InActor from its parent, and rename to InNewName (attaches a numeric suffix to make it unique via + // MakeUniqueObjectName). Place it in the world outliner folder InFolderPath. + static bool DetachAndRenameBakedPDGOutputActor(AActor* InActor, const FString& InNewName, const FName& InFolderPath); + + static bool BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultIndex, + int32 InWorkResultObjectIndex, + bool bInReplaceActors, + bool bInReplaceAssets, + bool bInBakeToWorkResultActor, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultId, + const FString& InWorkResultObjectName); + + // Checks if auto-bake is enabled on InPDGAssetLink, and if it is, calls BakePDGWorkResultObject. + static void AutoBakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultId, + const FString& InWorkResultObjectName); + + // Bake PDG output. This bakes all assets from all work items in the specified InNode (FTOPNode). + // It uses the existing output actors in the level, but breaks any links from these actors to the PDG link and + // moves the actors out of the parent Folder/ detaches from the parent PDG output actor. + static bool BakePDGTOPNodeOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInBakeForBlueprint, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes all assets from all work items in the specified TOP network. + // It uses the existing output actors in the level, but breaks any links + // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent + // PDG output actor. + static bool BakePDGTOPNetworkOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + bool bInBakeForBlueprint, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes assets from TOP networks and nodes according to + // InPDGAssetLink->PDGBakeSelectionOption. It uses the existing output actors in the level, but breaks any links + // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent + // PDG output actor. + static bool BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink); + + // Bake PDG output. This bakes all supported assets from all work items in the specified InNode (FTOPNode). + // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from + // PDG output actors. + static bool BakePDGTOPNodeBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes all supported assets from all work items in the specified TOP network. + // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from + // PDG output actors. + static bool BakePDGTOPNetworkBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes assets from TOP networks and nodes according to + // InPDGAssetLink->PDGBakeSelectionOption. It duplicates the output actors and bakes them to blueprints. Assets + // that were baked are removed from PDG output actors. + static bool BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink); + + // End: PDG Baking + +protected: + + // Find the HGPO with matching identifier. Returns true if the HGPO was found. + static bool FindHGPO( + const FHoudiniOutputObjectIdentifier& InIdentifier, + const TArray& InHGPOs, + FHoudiniGeoPartObject const*& OutHGPO); + + // Set OutBakeName to the resolved output name of InMeshOutputObject / InObject. OutBakeName is set to the object's + // BakeName (the BakeName on the InMeshOutputObject, or if that is not set, the custom part name or finally the + // package name. + static void GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const FHoudiniOutputObject& InMeshOutputObject, + FString& OutBakeName); + + // Look for InObject in InAllOutputs. If found the function returns true and OutBakeName is set to the object's + // BakeName (the BakeName on the OutputObject, or if that is not set, the custom part name or finally the package + // name. + static bool GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const TArray& InAllOutputs, + FString& OutBakeName); + + // Checks if InHoudiniAssetComponent has any current proxy mesh. Refines if it possible. Returns true + // if baking can continue, false otherwise. If the component has a proxy, but no cook data, then false is + // returned, the component is set to recook without a proxy and with bake after cook, and bOutNeedsReCook is set + // to true. + // bInReplace and BakeOption represents the baking settings to use if a delayed bake (post-cook) needs to be triggered. + static bool CheckForAndRefineHoudiniProxyMesh( + UHoudiniAssetComponent* InHoudiniAssetComponent, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption BakeOption, + bool bInRemoveHACOutputOnSuccess, + bool& bOutNeedsReCook); + + // Position InActor at its bounding box center (keep components' world location) + static void CenterActorToBoundingBoxCenter(AActor* InActor); + + // Position each of the actors in InActors at its bounding box center (keep components' world location) + static void CenterActorsToBoundingBoxCenter(const TArray& InActors); + + // Helper to get or optionally create a RootComponent for an actor + static USceneComponent* GetActorRootComponent( + AActor* InActor, bool bCreateIfMissing=true, EComponentMobility::Type InMobilityIfCreated=EComponentMobility::Static); + + // Helper function to return a unique object name if the given is already in use + static FName MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed=nullptr); + + // Helper for getting the actor folder path for the world outliner, based unreal_bake_outliner_folder + static FName GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); + + // Helper for setting the actor folder path in the world outliner + static bool SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); + + // Helper for destroying previous bake components/actors + static uint32 DestroyPreviousBakeOutput( + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInDestroyBakedComponent, + bool bInDestroyBakedInstancedActors, + bool bInDestroyBakedInstancedComponents); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp index 49b31f0bb..ce29b517d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp @@ -1,1695 +1,1741 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineCommands.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniOutput.h" - -#include "DesktopPlatformModule.h" -#include "Interfaces/IMainFrameModule.h" -#include "EditorDirectories.h" -#include "Misc/ScopedSlowTask.h" -#include "Async/Async.h" -#include "FileHelpers.h" -#include "AssetRegistryModule.h" -#include "Engine/ObjectLibrary.h" -#include "ObjectTools.h" -#include "CoreGlobals.h" -#include "HoudiniEngineOutputStats.h" -#include "Misc/FeedbackContext.h" -#include "HAL/FileManager.h" -#include "Modules/ModuleManager.h" -#include "ISettingsModule.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FDelegateHandle FHoudiniEngineCommands::OnPostSaveWorldRefineProxyMeshesHandle = FDelegateHandle(); - -void -FHoudiniEngineCommands::RegisterCommands() -{ - UI_COMMAND(_CreateSession, "Create Session", "Creates a new Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_ConnectSession, "Connect Session", "Connects to an existing Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_StopSession, "Stop Session", "Stops the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RestartSession, "Restart Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_OpenSessionSync, "Open Houdini Session Sync", "Opens Houdini with Session Sync and connect to it.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_CloseSessionSync, "Close Houdini Session Sync", "Close the Session Sync Houdini.", EUserInterfaceActionType::Button, FInputChord()); - - // Viewport Sync - UI_COMMAND(_ViewportSyncNone, "Disabled", "Do not sync viewports.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncUnreal, "Sync Unreal to Houdini.", "Sync the Unreal viewport to Houdini's.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncHoudini, "Sync Houdini to Unreal", "Sync the Houdini viewport to Unreal's.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncBoth, "Both", "Sync both Unreal and Houdini's viewport.", EUserInterfaceActionType::Check, FInputChord()); - - // PDG Import Commandlet - UI_COMMAND(_StartPDGCommandlet, "Start Async Importer", "Start the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_StopPDGCommandlet, "Stop Async Importer", "Stops the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_IsPDGCommandletEnabled, "Enable Async Importer", "Enables the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Check, FInputChord()); - - UI_COMMAND(_InstallInfo, "Installation Info", "Display information on the current Houdini Engine installation", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_PluginSettings, "PluginSettings", "Displays the Houdini Engine plugin settings", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_OpenInHoudini, "Open scene in Houdini", "Opens the current Houdini scene in Houdini.", EUserInterfaceActionType::Button, FInputChord(EKeys::O, EModifierKey::Control | EModifierKey::Alt)); - UI_COMMAND(_SaveHIPFile, "Save Houdini scene (HIP)", "Saves a .hip file of the current Houdini scene.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_OnlineDoc, "Online Documentation", "Go to the plugin's online documentation.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_OnlineForum, "Online Forum", "Go to the plugin's online forum.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_ReportBug, "Report a bug", "Report a bug for Houdini Engine for Unreal plugin.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_CookAll, "Recook All", "Recooks all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_CookSelected, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::C, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_RebuildAll, "Rebuild All", "Rebuilds all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RebuildSelected, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::R, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_BakeAll, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_BakeSelected, "Bake And Replace Selection", "Bakes and replaces with blueprints selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::B, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_RefineAll, "Refine all Houdini Proxy Meshes To Static Meshes", "Builds and replaces all Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RefineSelected, "Refine selected Houdini Proxy Meshes To Static Meshes", "Builds and replaces selected Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_CleanUpTempFolder, "Clean Houdini Engine Temp Folder", "Deletes the unused temporary files in the Temporary Cook Folder.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_PauseAssetCooking, "Pause Houdini Engine Cooking", "When activated, prevents Houdini Engine from cooking assets until unpaused.", EUserInterfaceActionType::Check, FInputChord(EKeys::P, EModifierKey::Control | EModifierKey::Alt)); -} - -void -FHoudiniEngineCommands::SaveHIPFile() -{ - IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get(); - if (!DesktopPlatform || !FHoudiniEngineUtils::IsInitialized()) - return; - - TArray< FString > SaveFilenames; - bool bSaved = false; - void * ParentWindowWindowHandle = NULL; - - IMainFrameModule & MainFrameModule = FModuleManager::LoadModuleChecked< IMainFrameModule >(TEXT("MainFrame")); - const TSharedPtr< SWindow > & MainFrameParentWindow = MainFrameModule.GetParentWindow(); - if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) - ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); - - bSaved = DesktopPlatform->SaveFileDialog( - ParentWindowWindowHandle, - NSLOCTEXT("SaveHIPFile", "SaveHIPFile", "Saves a .hip file of the current Houdini scene.").ToString(), - *(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT)), - TEXT(""), - TEXT("Houdini HIP file|*.hip"), - EFileDialogFlags::None, - SaveFilenames); - - if (bSaved && SaveFilenames.Num()) - { - // Add a slate notification - FString Notification = TEXT("Saving internal Houdini scene..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Saved Houdini scene to %s"), *SaveFilenames[0]); - - // Get first path. - std::string HIPPathConverted(TCHAR_TO_UTF8(*SaveFilenames[0])); - - // Save HIP file through Engine. - FHoudiniApi::SaveHIPFile(FHoudiniEngine::Get().GetSession(), HIPPathConverted.c_str(), false); - } -} - -void -FHoudiniEngineCommands::OpenInHoudini() -{ - if (!FHoudiniEngine::IsInitialized()) - return; - - // First, saves the current scene as a hip file - // Creates a proper temporary file name - FString UserTempPath = FPaths::CreateTempFilename( - FPlatformProcess::UserTempDir(), - TEXT("HoudiniEngine"), TEXT(".hip")); - - // Save HIP file through Engine. - std::string TempPathConverted(TCHAR_TO_UTF8(*UserTempPath)); - FHoudiniApi::SaveHIPFile( - FHoudiniEngine::Get().GetSession(), - TempPathConverted.c_str(), false); - - if (!FPaths::FileExists(UserTempPath)) - return; - - // Add a slate notification - FString Notification = TEXT("Opening scene in Houdini..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini.")); - - // Add quotes to the path to avoid issues with spaces - UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); - // Then open the hip file in Houdini - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); - FPlatformProcess::CreateProc( - *HoudiniLocation, - *UserTempPath, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - // Unfortunately, LaunchFileInDefaultExternalApplication doesn't seem to be working properly - //FPlatformProcess::LaunchFileInDefaultExternalApplication( UserTempPath.GetCharArray().GetData(), nullptr, ELaunchVerb::Open ); -} - -void -FHoudiniEngineCommands::ReportBug() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::ShowInstallInfo() -{ - // TODO -} - -void -FHoudiniEngineCommands::ShowPluginSettings() -{ - FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Plugins"), FName("HoudiniEngine")); -} - -void -FHoudiniEngineCommands::OnlineDocumentation() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_DOC_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::OnlineForum() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_FORUM_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::CleanUpTempFolder() -{ - // TODO: Improve me! slow now that we also have SM saved in the temp directory - // Due to the ref, we probably iterate a little too much, and should maybe do passes following the order of refs: - // mesh first, then materials, then textures. - // have a look at UWrangleContentCommandlet as well - - // Add a slate notification - FString Notification = TEXT("Cleaning up Houdini Engine temporary folder..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - GWarn->BeginSlowTask(LOCTEXT("CleanUpTemp", "Cleaning up the Houdini Engine Temp Folder"), false, false); - - // Get the default temp cook folder - FString TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - TArray TempCookFolders; - TempCookFolders.Add(FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder()); - for (TObjectIterator It; It; ++It) - { - FString CookFolder = It->TemporaryCookFolder.Path; - if (CookFolder.IsEmpty()) - continue; - - TempCookFolders.AddUnique(CookFolder); - } - - // The Asset registry will help us finding if the content of the asset is referenced - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - int32 DeletedCount = 0; - bool bDidDeleteAsset = true; - while (bDidDeleteAsset) - { - // To correctly clean the temp folder, we need to iterate multiple times, because some of the temp assets - // might be referenced by other temp assets.. (ie Textures are referenced by Materials) - // We'll stop looking for assets to delete when no deletion occured. - bDidDeleteAsset = false; - - TArray AssetDataList; - for (auto& TempFolder : TempCookFolders) - { - // The Object library will list all UObjects found in the TempFolder - auto ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, true); - ObjectLibrary->LoadAssetDataFromPath(TempFolder); - - // Get all the assets found in the TEMP folder - TArray CurrentAssetDataList; - ObjectLibrary->GetAssetDataList(CurrentAssetDataList); - - AssetDataList.Append(CurrentAssetDataList); - } - - // All the assets we're going to delete - TArray AssetDataToDelete; - for (FAssetData Data : AssetDataList) - { - UPackage* CurrentPackage = Data.GetPackage(); - if (!CurrentPackage || CurrentPackage->IsPendingKill()) - continue; - - // Do not try to delete the package if it's referenced anywhere - TArray ReferenceNames; - AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, UE::AssetRegistry::EDependencyCategory::All); - if (ReferenceNames.Num() > 0) - continue; - - bool bAssetDataSafeToDelete = true; - TArray AssetsInPackage; - AssetRegistryModule.Get().GetAssetsByPackageName(CurrentPackage->GetFName(), AssetsInPackage); - for (const auto& AssetInfo : AssetsInPackage) - { - // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) - UObject* AssetInPackage = AssetInfo.GetAsset(); - if (!AssetInPackage || AssetInPackage->IsPendingKill()) - continue; - - FReferencerInformationList ReferencesIncludingUndo; - bool bReferencedInMemoryOrUndoStack = IsReferenced(AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo); - if (!bReferencedInMemoryOrUndoStack) - continue; - - // We do have external references, check if the external references are in our ObjectToDelete list - // If they are, we can delete the asset because its references are going to be deleted as well. - for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences) - { - UObject* Outer = ExtRef.Referencer->GetOuter(); - if (!Outer || Outer->IsPendingKill()) - continue; - - bool bOuterFound = false; - for (auto DataToDelete : AssetDataToDelete) - { - if (DataToDelete.GetPackage() == Outer) - { - bOuterFound = true; - break; - } - else if (DataToDelete.GetAsset() == Outer) - { - bOuterFound = true; - break; - } - } - - // We have at least one reference that's not going to be deleted, we have to keep the asset - if (!bOuterFound) - { - bAssetDataSafeToDelete = false; - break; - } - } - } - - if (bAssetDataSafeToDelete) - AssetDataToDelete.Add(Data); - } - - // Nothing to delete - if (AssetDataToDelete.Num() <= 0) - break; - - int32 CurrentDeleted = ObjectTools::DeleteAssets(AssetDataToDelete, false); - - if (CurrentDeleted > 0) - { - DeletedCount += CurrentDeleted; - bDidDeleteAsset = true; - } - } - - - // Now, go through all the directories in the temp directories and delete all the empty ones - IFileManager& FM = IFileManager::Get(); - // Lambda that parses a directory recursively and returns true if it is empty - auto IsEmptyFolder = [&FM](FString PathToDeleteOnDisk) - { - struct FEmptyFolderVisitor : public IPlatformFile::FDirectoryVisitor - { - bool bIsEmpty; - FEmptyFolderVisitor() - : bIsEmpty(true) - { - } - - virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override - { - if (!bIsDirectory) - { - bIsEmpty = false; - return false; // abort searching - } - - return true; // continue searching - } - }; - - // Look for files on disk in case the folder contains things not tracked by the asset registry - FEmptyFolderVisitor EmptyFolderVisitor; - IFileManager::Get().IterateDirectoryRecursively(*PathToDeleteOnDisk, EmptyFolderVisitor); - return EmptyFolderVisitor.bIsEmpty; - }; - - // Iterates on all the temporary cook directories recursively, - // And keep not of all the empty directories - FString TempCookPathOnDisk; - TArray FoldersToDelete; - if (FPackageName::TryConvertLongPackageNameToFilename(TempCookFolder, TempCookPathOnDisk)) - { - FM.IterateDirectoryRecursively(*TempCookPathOnDisk, [&FM, &FoldersToDelete, &IsEmptyFolder](const TCHAR* InFilenameOrDirectory, const bool InIsDirectory) -> bool - { - // Skip Files - if (!InIsDirectory) - return true; - - FString CurrentDirectoryPath = FString(InFilenameOrDirectory); - if (IsEmptyFolder(CurrentDirectoryPath)) - FoldersToDelete.Add(CurrentDirectoryPath); - - // keep iterating - return true; - }); - } - - int32 DeletedDirectories = 0; - for (auto& FolderPath : FoldersToDelete) - { - FString PathToDelete; - if (!FPackageName::TryConvertFilenameToLongPackageName(FolderPath, PathToDelete)) - continue; - - if (IFileManager::Get().DeleteDirectory(*FolderPath, false, true)) - { - AssetRegistryModule.Get().RemovePath(PathToDelete); - DeletedDirectories++; - } - } - - GWarn->EndSlowTask(); - - // Add a slate notification - Notification = TEXT("Deleted ") + FString::FromInt(DeletedCount) + TEXT(" temporary files and ") + FString::FromInt(DeletedDirectories) + TEXT(" directories."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Deleted %d temporary files and %d directories."), DeletedCount, DeletedDirectories); -} - -void -FHoudiniEngineCommands::BakeAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Baking all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 BakedCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component")); - continue; - } - - if (!HoudiniAssetComponent->IsComponentValid()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - if (AssetName != "Default__HoudiniAssetActor") - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is invalid"), *AssetName); - continue; - } - - // If component is not cooking or instancing, we can bake blueprint. - if (HoudiniAssetComponent->IsInstantiatingOrCooking()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is actively instantiating or cooking"), *AssetName); - continue; - } - - bool bSuccess = false; - bool BakeToBlueprints = true; - if (BakeToBlueprints) - { - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // bSuccess = true; - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - if (bSuccess) - { - // Instantiate blueprints in component's level, then remove houdini asset actor - bSuccess = false; - ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); - if (IsValid(Level)) - { - UWorld* World = Level->GetWorld(); - if (IsValid(World)) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = Level; - FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); - for (UBlueprint* Blueprint : Blueprints) - { - if (!IsValid(Blueprint)) - continue; - World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); - } - - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - bSuccess = true; - } - } - } - } - else - { - // TODO: this used to have a way to not select in v1 - // if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent)) - // bSuccess = true; - if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, true, true)) - { - bSuccess = true; - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - } - } - - if (bSuccess) - BakedCount++; - } - - // Add a slate notification - Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); -} - -void -FHoudiniEngineCommands::PauseAssetCooking() -{ - // Revert the global flag - bool bCurrentCookingEnabled = !FHoudiniEngine::Get().IsCookingEnabled(); - FHoudiniEngine::Get().SetCookingEnabled(bCurrentCookingEnabled); - - // We need to refresh UI when pause cooking. Set refresh UI counter to be the number of current registered HACs. - if (!bCurrentCookingEnabled) - FHoudiniEngine::Get().SetUIRefreshCountWhenPauseCooking( FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount() ); - - // Add a slate notification - FString Notification = TEXT("Houdini Engine cooking paused"); - if (bCurrentCookingEnabled) - Notification = TEXT("Houdini Engine cooking resumed"); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - if (bCurrentCookingEnabled) - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking resumed.")); - else - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking paused.")); - - if (!bCurrentCookingEnabled) - return; - - /* - // If we are unpausing, tick each asset component to "update" them - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!")); - continue; - } - - HoudiniAssetComponent->StartHoudiniTicking(); - } - */ -} - -bool -FHoudiniEngineCommands::IsAssetCookingPaused() -{ - return !FHoudiniEngine::Get().IsCookingEnabled(); -} - -void -FHoudiniEngineCommands::RecookSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Cooking selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and cook the assets if they're in a valid state - int32 CookedCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedCook(); - CookedCount++; - } - - // Add a slate notification - Notification = TEXT("Re-cooking ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-cooking %d selected Houdini assets."), CookedCount); -} - -void -FHoudiniEngineCommands::RecookAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Cooking all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 CookedCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedCook(); - CookedCount++; - } - - // Add a slate notification - Notification = TEXT("Re-cooked ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-cooked %d Houdini assets in the current level."), CookedCount); -} - -void -FHoudiniEngineCommands::RebuildAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Re-building all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 RebuiltCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedRebuild(); - RebuiltCount++; - } - - // Add a slate notification - Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d Houdini assets in the current level."), RebuiltCount); -} - -void -FHoudiniEngineCommands::RebuildSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Rebuilding selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and rebuilds the assets if they're in a valid state - int32 RebuiltCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid()) - continue; - - HoudiniAssetComponent->MarkAsNeedRebuild(); - RebuiltCount++; - } - - // Add a slate notification - Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d selected Houdini assets."), RebuiltCount); -} - -void -FHoudiniEngineCommands::BakeSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Baking selected Houdini Asset Actors in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and rebuilds the assets if they're in a valid state - int32 BakedCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); - continue; - } - - if (!HoudiniAssetComponent->IsComponentValid()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - HOUDINI_LOG_ERROR(TEXT("Failed to export Houdini Asset: %s in the scene!"), *AssetName); - continue; - } - - // If component is not cooking or instancing, we can bake blueprint. - if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) - { - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // BakedCount++; - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // bSuccess = true; - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - if (bSuccess) - { - // Instantiate blueprints in component's level, then remove houdini asset actor - ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); - if (IsValid(Level)) - { - UWorld* World = Level->GetWorld(); - if (IsValid(World)) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = Level; - FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); - for (UBlueprint* Blueprint : Blueprints) - { - if (!IsValid(Blueprint)) - continue; - World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); - } - - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - BakedCount++; - } - } - } - } - } - - // Add a slate notification - Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); -} - -// Recentre HoudiniAsset actors' pivots to their input / cooked static-mesh average centre. -void FHoudiniEngineCommands::RecentreSelection() -{ - /* -#if WITH_EDITOR - //Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Recentering selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and cook the assets if they're in a valid state - int32 RecentreCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || !HoudiniAssetComponent->IsComponentValid()) - continue; - - // Get the average centre of all the created Static Meshes - FVector AverageBoundsCentre = FVector::ZeroVector; - int32 NumBounds = 0; - const FVector CurrentLocation = HoudiniAssetComponent->GetComponentLocation(); - { - //Check Static Meshes - TArray StaticMeshes; - StaticMeshes.Reserve(16); - HoudiniAssetComponent->GetAllUsedStaticMeshes(StaticMeshes); - - //Get average centre of all the static meshes. - for (const UStaticMesh* pMesh : StaticMeshes) - { - if (!pMesh) - continue; - - //to world space - AverageBoundsCentre += (pMesh->GetBounds().Origin + CurrentLocation); - NumBounds++; - } - } - - //Check Inputs - if (0 == NumBounds) - { - const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs; - for (const UHoudiniInput* pInput : AssetInputs) - { - if (!pInput || pInput->IsPendingKill()) - continue; - - // to world space - FBox Bounds = pInput->GetInputBounds(CurrentLocation); - if (Bounds.IsValid) - { - AverageBoundsCentre += Bounds.GetCenter(); - NumBounds++; - } - } - } - - //if we have more than one, get the average centre - if (NumBounds > 1) - { - AverageBoundsCentre /= (float)NumBounds; - } - - //if we need to move... - float fDist = FVector::DistSquared(CurrentLocation, AverageBoundsCentre); - if (NumBounds && fDist > 1.0f) - { - // Move actor to average centre and recook - // This will refresh the static mesh under the HoudiniAssestComponent ( undoing the translation ). - HoudiniAssetActor->SetActorLocation(AverageBoundsCentre, false, nullptr, ETeleportType::TeleportPhysics); - - // Recook now the houdini-static-mesh has a new origin - HoudiniAssetComponent->StartTaskAssetCookingManual(); - RecentreCount++; - } - } - - if (RecentreCount) - { - // UE4 Editor doesn't refresh the translation-handles until they are re-selected, confusing the user, deselect the objects. - GEditor->SelectNone(true, false); - } - - // Add a slate notification - Notification = TEXT("Re-centred ") + FString::FromInt(RecentreCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-centred %d selected Houdini assets."), RecentreCount); - -#endif //WITH_EDITOR - */ -} - -void -FHoudiniEngineCommands::OpenSessionSync() -{ - //if (!FHoudiniEngine::IsInitialized()) - // return; - - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - HAPI Not initialized")); - return; - } - - // Get the runtime settings to get the session/type and settings - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - - EHoudiniRuntimeSettingsSessionType SessionType = HoudiniRuntimeSettings->SessionType; - FString ServerPipeName = HoudiniRuntimeSettings->ServerPipeName; - int32 ServerPort = HoudiniRuntimeSettings->ServerPort; - - FString SessionSyncArgs = TEXT("-hess="); - if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe) - { - // Add the -hess=pipe:hapi argument - SessionSyncArgs += TEXT("pipe:") + ServerPipeName; - } - else if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_Socket) - { - // Add the -hess=port:9090 argument - SessionSyncArgs += TEXT("port:") + FString::FromInt(ServerPort); - } - else - { - // Invalid session type - HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - Invalid session type")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Opening Houdini Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Opening Houdini Session Sync.")); - - // Only launch Houdini in Session sync if we havent started it already! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - if (!FPlatformProcess::IsProcRunning(PreviousHESS)) - { - // Start houdini with the -hess commandline args - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); - FProcHandle HESSHandle = FPlatformProcess::CreateProc( - *HoudiniLocation, - *SessionSyncArgs, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - // Keep track of the SessionSync ProcHandle - FHoudiniEngine::Get().SetHESSProcHandle(HESSHandle); - } - - // Start an Async task to connect to Session Sync - Async(EAsyncExecution::TaskGraphMainThread, [SessionType, ServerPipeName, ServerPort]() - { - // Use a timeout to avoid waiting indefinitely for H to start in session sync mode - const double Timeout = 180.0; // 3min - const double StartTimestamp = FPlatformTime::Seconds(); - - FString ServerHost = TEXT("localhost"); - while (!FHoudiniEngine::Get().SessionSyncConnect(SessionType, ServerPipeName, ServerHost, ServerPort)) - { - // Houdini might not be done loading, sleep for one second - FPlatformProcess::Sleep(1); - - // Check for the timeout - if (FPlatformTime::Seconds() - StartTimestamp > Timeout) - { - // ... and a log message - HOUDINI_LOG_ERROR(TEXT("Failed to start SessionSync - Timeout...")); - return false; - } - } - - // Initialize HAPI with this session - if (!FHoudiniEngine::Get().InitializeHAPISession()) - { - FHoudiniEngine::Get().StopTicking(); - return false; - } - - // Notify all HACs that they need to instantiate in the new session - MarkAllHACsAsNeedInstantiation(); - - // Start ticking - FHoudiniEngine::Get().StartTicking(); - - // Add a slate notification - FString Notification = TEXT("Succesfully connected to Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Succesfully connected to Session Sync...")); - - return true; - }); -} - -void -FHoudiniEngineCommands::CloseSessionSync() -{ - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to stop Session Sync - HAPI Not initialized")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Stopping Houdini Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Stopping Houdini Session Sync.")); - - // Stop Houdini Session sync if it is still running! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - if (FPlatformProcess::IsProcRunning(PreviousHESS)) - { - FPlatformProcess::TerminateProc(PreviousHESS, true); - } -} - -void -FHoudiniEngineCommands::SetViewportSync(const int32& ViewportSync) -{ - if (ViewportSync == 1) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); - } - else if (ViewportSync == 2) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); - } - else if (ViewportSync == 3) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); - } - else - { - FHoudiniEngine::Get().SetSyncViewportEnabled(false); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); - } -} - -int32 -FHoudiniEngineCommands::GetViewportSync() -{ - if(!FHoudiniEngine::Get().IsSyncViewportEnabled()) - return 0; - - bool bSyncH = FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled(); - bool bSyncU = FHoudiniEngine::Get().IsSyncUnrealViewportEnabled(); - if (bSyncH && !bSyncU) - return 1; - else if (!bSyncH && bSyncU) - return 2; - else if (bSyncH && bSyncU) - return 3; - else - return 0; -} - -void -FHoudiniEngineCommands::RestartSession() -{ - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().RestartSession()) - return; - - // We've successfully restarted the Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::CreateSession() -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().CreateSession(HoudiniRuntimeSettings->SessionType)) - return; - - // We've successfully created the Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::ConnectSession() -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().ConnectSession(HoudiniRuntimeSettings->SessionType)) - return; - - // We've successfully connected to a Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation() -{ - // Notify all the HoudiniAssetComponents that they need to re instantiate themselves in the new Houdini engine session. - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedInstantiation(); - } -} - -bool -FHoudiniEngineCommands::IsSessionValid() -{ - return FHoudiniEngine::IsInitialized(); -} - -bool -FHoudiniEngineCommands::IsSessionSyncProcessValid() -{ - // Only launch Houdini in Session sync if we havent started it already! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - return FPlatformProcess::IsProcRunning(PreviousHESS); -} - -void -FHoudiniEngineCommands::StopSession() -{ - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine session stopped.")); - } -} - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE) -{ - // Get current world selection - TArray WorldSelection; - int32 NumSelectedHoudiniAssets = 0; - if (bOnlySelectedActors) - { - NumSelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (NumSelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - } - - // Add a slate notification - FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // First find the components that have meshes that we must refine - TArray ComponentsToRefine; - TArray ComponentsToCook; - // Components that would be candidates for refinement/cooking, but have errors - TArray SkippedComponents; - if (bOnlySelectedActors) - { - for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Index]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - } - else - { - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - } - - RefineTriagedHoudiniProxyMesehesToStaticMeshes( - ComponentsToRefine, - ComponentsToCook, - SkippedComponents, - bSilent, - bRefineAll, - bOnPreSaveWorld, - OnPreSaveWorld, - bOnPreBeginPIE - ); -} - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent) -{ - const bool bRefineAll = true; - const bool bOnPreSaveWorld = false; - UWorld* OnPreSaveWorld = nullptr; - const bool bOnPreBeginPIE = false; - - // First find the components that have meshes that we must refine - TArray ComponentsToRefine; - TArray ComponentsToCook; - // Components that would be candidates for refinement/cooking, but have errors - TArray SkippedComponents; - for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine) - { - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - - RefineTriagedHoudiniProxyMesehesToStaticMeshes( - ComponentsToRefine, - ComponentsToCook, - SkippedComponents, - bSilent, - bRefineAll, - bOnPreSaveWorld, - OnPreSaveWorld, - bOnPreBeginPIE - ); -} - -void -FHoudiniEngineCommands::StartPDGCommandlet() -{ - FHoudiniEngine::Get().StartPDGCommandlet(); -} - -void -FHoudiniEngineCommands::StopPDGCommandlet() -{ - FHoudiniEngine::Get().StopPDGCommandlet(); -} - -bool -FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected() -{ - return FHoudiniEngine::Get().IsPDGCommandletRunningOrConnected(); -} - -bool -FHoudiniEngineCommands::IsPDGCommandletEnabled() -{ - const UHoudiniRuntimeSettings* const Settings = GetDefault(); - if (IsValid(Settings)) - { - return Settings->bPDGAsyncCommandletImportEnabled; - } - - return false; -} - -bool -FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled) -{ - UHoudiniRuntimeSettings* const Settings = GetMutableDefault(); - if (IsValid(Settings)) - { - Settings->bPDGAsyncCommandletImportEnabled = InEnabled; - return true; - } - - return false; -} - -void -FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped) -{ - if (!InHAC || InHAC->IsPendingKill()) - return; - - // Make sure that the component's World and Owner are valid - AActor *Owner = InHAC->GetOwner(); - if (!Owner || Owner->IsPendingKill()) - return; - - UWorld *World = InHAC->GetWorld(); - if (!World || World->IsPendingKill()) - return; - - if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World) - return; - - // Check if we should consider this component for proxy mesh refinement based on its settings and - // flags passed to the function - if (bRefineAll || - (bOnPreSaveWorld && InHAC->IsProxyStaticMeshRefinementOnPreSaveWorldEnabled()) || - (bOnPreBeginPIE && InHAC->IsProxyStaticMeshRefinementOnPreBeginPIEEnabled())) - { - TArray ProxyMeshPackagesToSave; - TArray ComponentsWithProxiesToSave; - - if (InHAC->HasAnyCurrentProxyOutput()) - { - // Get the state of the asset and check if it is cooked - // If it is not cook, request a cook. We can only build the UStaticMesh - // if the data from the cook is available - // If the state is not pre-cook, or None (cooked), then the state is invalid, - // log an error and skip the component - bool bNeedsRebuildOrDelete = false; - bool bUnsupportedState = false; - const bool bCookedDataAvailable = InHAC->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bUnsupportedState); - if (bCookedDataAvailable) - { - OutToRefine.Add(InHAC); - ComponentsWithProxiesToSave.Add(InHAC); - } - else if (!bUnsupportedState && !bNeedsRebuildOrDelete) - { - InHAC->MarkAsNeedCook(); - // Force the output of the cook to be directly created as a UStaticMesh and not a proxy - InHAC->SetNoProxyMeshNextCookRequested(true); - OutToCook.Add(InHAC); - ComponentsWithProxiesToSave.Add(InHAC); - } - else - { - OutSkipped.Add(InHAC); - const EHoudiniAssetState AssetState = InHAC->GetAssetState(); - HOUDINI_LOG_ERROR(TEXT("Could not refine %s, the asset is in an unsupported state: %s"), *(InHAC->GetPathName()), *(UEnum::GetValueAsString(AssetState))); - } - } - else if (InHAC->HasAnyProxyOutput()) - { - // If the HAC has non-current proxies, destroy them - // TODO: Make this its own command? - const uint32 NumOutputs = InHAC->GetNumOutputs(); - for (uint32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = InHAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - TMap& OutputObjects = Output->GetOutputObjects(); - for (auto& CurrentPair : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; - if (!CurrentOutputObject.bProxyIsCurrent) - { - // The proxy is not current, delete it and its component - USceneComponent* FoundProxyComponent = Cast(CurrentOutputObject.ProxyComponent); - if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (FoundProxyComponent->GetOwner()) - FoundProxyComponent->GetOwner()->RemoveOwnedComponent(FoundProxyComponent); - - FoundProxyComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - FoundProxyComponent->UnregisterComponent(); - FoundProxyComponent->DestroyComponent(); - } - - UObject* ProxyObject = CurrentOutputObject.ProxyObject; - if (!ProxyObject || ProxyObject->IsPendingKill()) - continue; - - ProxyObject->MarkPendingKill(); - ProxyObject->MarkPackageDirty(); - UPackage* const Package = ProxyObject->GetPackage(); - if (IsValid(Package)) - ProxyMeshPackagesToSave.Add(Package); - } - } - } - } - - for (UHoudiniAssetComponent* const HAC : ComponentsWithProxiesToSave) - { - const uint32 NumOutputs = HAC->GetNumOutputs(); - for (uint32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - TMap& OutputObjects = Output->GetOutputObjects(); - for (auto& CurrentPair : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; - if (CurrentOutputObject.bProxyIsCurrent && CurrentOutputObject.ProxyObject) - { - UPackage* const Package = CurrentOutputObject.ProxyObject->GetPackage(); - if (IsValid(Package) && Package->IsDirty()) - ProxyMeshPackagesToSave.Add(Package); - } - } - } - } - - if (ProxyMeshPackagesToSave.Num() > 0) - { - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - FEditorFileUtils::PromptForCheckoutAndSave(ProxyMeshPackagesToSave, true, false); - } - } -} - -void -FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( - const TArray& InComponentsToRefine, - const TArray& InComponentsToCook, - const TArray& InSkippedComponents, - bool bInSilent, - bool bInRefineAll, - bool bInOnPreSaveWorld, - UWorld* InOnPreSaveWorld, - bool bInOnPrePIEBeginPlay) -{ - // Slate notification text - FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); - - const uint32 NumComponentsToCook = InComponentsToCook.Num(); - const uint32 NumComponentsToRefine = InComponentsToRefine.Num(); - const uint32 NumComponentsToProcess = NumComponentsToCook + NumComponentsToRefine; - TArray SuccessfulComponents; - uint32 NumSkippedComponents = InSkippedComponents.Num(); - if (NumComponentsToProcess > 0) - { - // The task progress pointer is potentially going to be shared with a background thread and tasks - // on the main thread, so make it thread safe - TSharedPtr TaskProgress = MakeShareable(new FSlowTask((float)NumComponentsToProcess, FText::FromString(Notification))); - TaskProgress->Initialize(); - if (!bInSilent) - TaskProgress->MakeDialog(/*bShowCancelButton=*/true); - - // Iterate over the components for which we can build UStaticMesh, and build the meshes - bool bCancelled = false; - for (uint32 ComponentIndex = 0; ComponentIndex < NumComponentsToRefine; ++ComponentIndex) - { - UHoudiniAssetComponent* HoudiniAssetComponent = InComponentsToRefine[ComponentIndex]; - TaskProgress->EnterProgressFrame(1.0f); - const bool bDestroyProxies = true; - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HoudiniAssetComponent, bDestroyProxies); - - SuccessfulComponents.Add(HoudiniAssetComponent); - - bCancelled = TaskProgress->ShouldCancel(); - if (bCancelled) - { - NumSkippedComponents += NumComponentsToRefine - ComponentIndex - 1; - break; - } - } - - if (NumComponentsToCook > 0 && !bCancelled) - { - // Now use an async task to check on the progress of the cooking components - Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { - RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); - }); - } - else - { - RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(NumComponentsToProcess, NumSkippedComponents, 0, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); - } - } -} - - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray& InComponentsToCook, TSharedPtr InTaskProgress, uint32 InNumComponentsToProcess, uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) -{ - // Copy to a double linked list so that we can loop through - // to check progress of each component and remove it easily - // if it has completed/failed - TDoubleLinkedList CookList; - for (UHoudiniAssetComponent *HAC : InComponentsToCook) - { - CookList.AddTail(HAC); - } - - // Add the successfully cooked compoments to the incoming successful components (previously refined) - TArray SuccessfulComponents(InSuccessfulComponents); - - bool bCancelled = false; - uint32 NumFailedToCook = 0; - while (CookList.Num() > 0 && !bCancelled) - { - TDoubleLinkedList::TDoubleLinkedListNode *Node = CookList.GetHead(); - while (Node && !bCancelled) - { - TDoubleLinkedList::TDoubleLinkedListNode *Next = Node->GetNextNode(); - UHoudiniAssetComponent* HAC = Node->GetValue(); - - if (HAC && !HAC->IsPendingKill()) - { - const EHoudiniAssetState State = HAC->GetAssetState(); - const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult(); - bool bUpdateProgress = false; - if (State == EHoudiniAssetState::None) - { - // Cooked, count as success, remove node - CookList.RemoveNode(Node); - SuccessfulComponents.Add(Node->GetValue()); - bUpdateProgress = true; - } - else if (ResultState != EHoudiniAssetStateResult::None && ResultState != EHoudiniAssetStateResult::Working) - { - // Failed, remove node - HOUDINI_LOG_ERROR(TEXT("Failed to cook %s to obtain static mesh."), *(HAC->GetPathName())); - CookList.RemoveNode(Node); - bUpdateProgress = true; - NumFailedToCook++; - } - - if (bUpdateProgress && InTaskProgress.IsValid()) - { - // Update progress only on the main thread, and check for cancellation request - bCancelled = Async(EAsyncExecution::TaskGraphMainThread, [InTaskProgress]() { - InTaskProgress->EnterProgressFrame(1.0f); - return InTaskProgress->ShouldCancel(); - }).Get(); - } - } - - Node = Next; - } - FPlatformProcess::Sleep(0.01f); - } - - if (bCancelled) - { - HOUDINI_LOG_WARNING(TEXT("Mesh refinement cancelled while waiting for %d components to cook."), CookList.Num()); - } - - // Cooking is done, or failed, display the notifications on the main thread - const uint32 NumRemaining = CookList.Num(); - Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InNumSkippedComponents, NumFailedToCook, NumRemaining, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { - RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InNumSkippedComponents + NumRemaining, NumFailedToCook, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); - }); -} - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) -{ - FString Notification; - if (InNumSkippedComponents + InNumFailedToCook > 0) - { - if (bCancelled) - { - Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); - } - else - { - Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); - } - FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_ERROR(TEXT("%s"), *Notification); - } - else if (InNumTotalComponents > 0) - { - Notification = TEXT("Done: Refining Houdini proxy meshes to static meshes."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_MESSAGE(TEXT("%s"), *Notification); - } - if (InTaskProgress) - { - InTaskProgress->Destroy(); - } - if (bOnPreSaveWorld && InSuccessfulComponents.Num() > 0) - { - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - - // Save the dirty static meshes in InSuccessfulComponents OnPostSaveWorld - // TODO: Remove? This may not be necessary now as we save all dirty temporary cook data in PostSaveWorld() already (Static Meshes, Materials...) - OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([InSuccessfulComponents, bOnPreSaveWorld, InOnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) { - if (bOnPreSaveWorld && InOnPreSaveWorld && InOnPreSaveWorld != InWorld) - return; - - RefineProxyMeshesHandleOnPostSaveWorld(InSuccessfulComponents, InSaveFlags, InWorld, bInSuccess); - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - }); - } -} - -void -FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) -{ - TArray PackagesToSave; - - for (UHoudiniAssetComponent* HAC : InSuccessfulComponents) - { - if (!HAC || HAC->IsPendingKill()) - continue; - - const int32 NumOutputs = HAC->GetNumOutputs(); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (auto &OutputObjectPair : Output->GetOutputObjects()) - { - UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) - continue; - - UStaticMesh *SM = Cast(Obj); - if (!SM) - continue; - - UPackage *Package = SM->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - } - } - - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineCommands.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniOutput.h" + +#include "DesktopPlatformModule.h" +#include "Interfaces/IMainFrameModule.h" +#include "EditorDirectories.h" +#include "Misc/ScopedSlowTask.h" +#include "Async/Async.h" +#include "FileHelpers.h" +#include "AssetRegistryModule.h" +#include "Engine/ObjectLibrary.h" +#include "ObjectTools.h" +#include "CoreGlobals.h" +#include "HoudiniEngineOutputStats.h" +#include "Misc/FeedbackContext.h" +#include "HAL/FileManager.h" +#include "Modules/ModuleManager.h" +#include "ISettingsModule.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FDelegateHandle FHoudiniEngineCommands::OnPostSaveWorldRefineProxyMeshesHandle = FDelegateHandle(); + +void +FHoudiniEngineCommands::RegisterCommands() +{ + UI_COMMAND(_CreateSession, "Create Session", "Creates a new Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_ConnectSession, "Connect Session", "Connects to an existing Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_StopSession, "Stop Session", "Stops the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RestartSession, "Restart Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_OpenSessionSync, "Open Houdini Session Sync", "Opens Houdini with Session Sync and connect to it.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_CloseSessionSync, "Close Houdini Session Sync", "Close the Session Sync Houdini.", EUserInterfaceActionType::Button, FInputChord()); + + // Viewport Sync + UI_COMMAND(_ViewportSyncNone, "Disabled", "Do not sync viewports.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncUnreal, "Sync Unreal to Houdini.", "Sync the Unreal viewport to Houdini's.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncHoudini, "Sync Houdini to Unreal", "Sync the Houdini viewport to Unreal's.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncBoth, "Both", "Sync both Unreal and Houdini's viewport.", EUserInterfaceActionType::Check, FInputChord()); + + // PDG Import Commandlet + UI_COMMAND(_StartPDGCommandlet, "Start Async Importer", "Start the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_StopPDGCommandlet, "Stop Async Importer", "Stops the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_IsPDGCommandletEnabled, "Enable Async Importer", "Enables the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Check, FInputChord()); + + UI_COMMAND(_InstallInfo, "Installation Info", "Display information on the current Houdini Engine installation", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_PluginSettings, "PluginSettings", "Displays the Houdini Engine plugin settings", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_OpenInHoudini, "Open scene in Houdini", "Opens the current Houdini scene in Houdini.", EUserInterfaceActionType::Button, FInputChord(EKeys::O, EModifierKey::Control | EModifierKey::Alt)); + UI_COMMAND(_SaveHIPFile, "Save Houdini scene (HIP)", "Saves a .hip file of the current Houdini scene.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_OnlineDoc, "Online Documentation", "Go to the plugin's online documentation.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_OnlineForum, "Online Forum", "Go to the plugin's online forum.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_ReportBug, "Report a bug", "Report a bug for Houdini Engine for Unreal plugin.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_CookAll, "Recook All", "Recooks all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_CookSelected, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::C, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_RebuildAll, "Rebuild All", "Rebuilds all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RebuildSelected, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::R, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_BakeAll, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_BakeSelected, "Bake And Replace Selection", "Bakes and replaces with blueprints selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::B, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_RefineAll, "Refine all Houdini Proxy Meshes To Static Meshes", "Builds and replaces all Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RefineSelected, "Refine selected Houdini Proxy Meshes To Static Meshes", "Builds and replaces selected Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_CleanUpTempFolder, "Clean Houdini Engine Temp Folder", "Deletes the unused temporary files in the Temporary Cook Folder.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_PauseAssetCooking, "Pause Houdini Engine Cooking", "When activated, prevents Houdini Engine from cooking assets until unpaused.", EUserInterfaceActionType::Check, FInputChord(EKeys::P, EModifierKey::Control | EModifierKey::Alt)); +} + +void +FHoudiniEngineCommands::SaveHIPFile() +{ + if (!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) + { + HOUDINI_LOG_ERROR(TEXT("Cannot save the Houdini scene, the Houdini Engine session hasn't been started.")); + return; + } + + IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get(); + if (!DesktopPlatform || !FHoudiniEngineUtils::IsInitialized()) + return; + + TArray< FString > SaveFilenames; + bool bSaved = false; + void * ParentWindowWindowHandle = NULL; + + IMainFrameModule & MainFrameModule = FModuleManager::LoadModuleChecked< IMainFrameModule >(TEXT("MainFrame")); + const TSharedPtr< SWindow > & MainFrameParentWindow = MainFrameModule.GetParentWindow(); + if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) + ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); + + bSaved = DesktopPlatform->SaveFileDialog( + ParentWindowWindowHandle, + NSLOCTEXT("SaveHIPFile", "SaveHIPFile", "Saves a .hip file of the current Houdini scene.").ToString(), + *(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT)), + TEXT(""), + TEXT("Houdini HIP file|*.hip"), + EFileDialogFlags::None, + SaveFilenames); + + if (bSaved && SaveFilenames.Num()) + { + // Add a slate notification + FString Notification = TEXT("Saving internal Houdini scene..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Saved Houdini scene to %s"), *SaveFilenames[0]); + + // Get first path. + std::string HIPPathConverted(TCHAR_TO_UTF8(*SaveFilenames[0])); + + // Save HIP file through Engine. + FHoudiniApi::SaveHIPFile(FHoudiniEngine::Get().GetSession(), HIPPathConverted.c_str(), false); + } +} + +void +FHoudiniEngineCommands::OpenInHoudini() +{ + if(!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) + { + HOUDINI_LOG_ERROR(TEXT("Cannot open the scene in Houdini, the Houdini Engine session hasn't been started.")); + return; + } + + // First, saves the current scene as a hip file + // Creates a proper temporary file name + FString UserTempPath = FPaths::CreateTempFilename( + FPlatformProcess::UserTempDir(), + TEXT("HoudiniEngine"), TEXT(".hip")); + + // Save HIP file through Engine. + std::string TempPathConverted(TCHAR_TO_UTF8(*UserTempPath)); + FHoudiniApi::SaveHIPFile( + FHoudiniEngine::Get().GetSession(), + TempPathConverted.c_str(), false); + + if (!FPaths::FileExists(UserTempPath)) + return; + + // Add a slate notification + FString Notification = TEXT("Opening scene in Houdini..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Add quotes to the path to avoid issues with spaces + UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); + // Then open the hip file in Houdini + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); + + FProcHandle ProcHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *UserTempPath, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!ProcHandle.IsValid()) + { + // Try with the steam version executable instead + HoudiniLocation = LibHAPILocation + TEXT("//hindie.steam"); + + ProcHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *UserTempPath, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!ProcHandle.IsValid()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to open scene in Houdini.")); + } + } + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini.")); +} + +void +FHoudiniEngineCommands::ReportBug() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::ShowInstallInfo() +{ + // TODO +} + +void +FHoudiniEngineCommands::ShowPluginSettings() +{ + FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Plugins"), FName("HoudiniEngine")); +} + +void +FHoudiniEngineCommands::OnlineDocumentation() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_DOC_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::OnlineForum() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_FORUM_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::CleanUpTempFolder() +{ + // TODO: Improve me! slow now that we also have SM saved in the temp directory + // Due to the ref, we probably iterate a little too much, and should maybe do passes following the order of refs: + // mesh first, then materials, then textures. + // have a look at UWrangleContentCommandlet as well + + // Add a slate notification + FString Notification = TEXT("Cleaning up Houdini Engine temporary folder..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + GWarn->BeginSlowTask(LOCTEXT("CleanUpTemp", "Cleaning up the Houdini Engine Temp Folder"), false, false); + + // Get the default temp cook folder + FString TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + TArray TempCookFolders; + TempCookFolders.Add(FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder()); + for (TObjectIterator It; It; ++It) + { + FString CookFolder = It->TemporaryCookFolder.Path; + if (CookFolder.IsEmpty()) + continue; + + TempCookFolders.AddUnique(CookFolder); + } + + // The Asset registry will help us finding if the content of the asset is referenced + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + int32 DeletedCount = 0; + bool bDidDeleteAsset = true; + while (bDidDeleteAsset) + { + // To correctly clean the temp folder, we need to iterate multiple times, because some of the temp assets + // might be referenced by other temp assets.. (ie Textures are referenced by Materials) + // We'll stop looking for assets to delete when no deletion occured. + bDidDeleteAsset = false; + + TArray AssetDataList; + for (auto& TempFolder : TempCookFolders) + { + // The Object library will list all UObjects found in the TempFolder + auto ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, true); + ObjectLibrary->LoadAssetDataFromPath(TempFolder); + + // Get all the assets found in the TEMP folder + TArray CurrentAssetDataList; + ObjectLibrary->GetAssetDataList(CurrentAssetDataList); + + AssetDataList.Append(CurrentAssetDataList); + } + + // All the assets we're going to delete + TArray AssetDataToDelete; + for (FAssetData Data : AssetDataList) + { + UPackage* CurrentPackage = Data.GetPackage(); + if (!CurrentPackage || CurrentPackage->IsPendingKill()) + continue; + + // Do not try to delete the package if it's referenced anywhere + TArray ReferenceNames; + AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, UE::AssetRegistry::EDependencyCategory::All); + if (ReferenceNames.Num() > 0) + continue; + + bool bAssetDataSafeToDelete = true; + TArray AssetsInPackage; + AssetRegistryModule.Get().GetAssetsByPackageName(CurrentPackage->GetFName(), AssetsInPackage); + for (const auto& AssetInfo : AssetsInPackage) + { + // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) + UObject* AssetInPackage = AssetInfo.GetAsset(); + if (!AssetInPackage || AssetInPackage->IsPendingKill()) + continue; + + FReferencerInformationList ReferencesIncludingUndo; + bool bReferencedInMemoryOrUndoStack = IsReferenced(AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo); + if (!bReferencedInMemoryOrUndoStack) + continue; + + // We do have external references, check if the external references are in our ObjectToDelete list + // If they are, we can delete the asset because its references are going to be deleted as well. + for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences) + { + UObject* Outer = ExtRef.Referencer->GetOuter(); + if (!Outer || Outer->IsPendingKill()) + continue; + + bool bOuterFound = false; + for (auto DataToDelete : AssetDataToDelete) + { + if (DataToDelete.GetPackage() == Outer) + { + bOuterFound = true; + break; + } + else if (DataToDelete.GetAsset() == Outer) + { + bOuterFound = true; + break; + } + } + + // We have at least one reference that's not going to be deleted, we have to keep the asset + if (!bOuterFound) + { + bAssetDataSafeToDelete = false; + break; + } + } + } + + if (bAssetDataSafeToDelete) + AssetDataToDelete.Add(Data); + } + + // Nothing to delete + if (AssetDataToDelete.Num() <= 0) + break; + + int32 CurrentDeleted = ObjectTools::DeleteAssets(AssetDataToDelete, false); + + if (CurrentDeleted > 0) + { + DeletedCount += CurrentDeleted; + bDidDeleteAsset = true; + } + } + + + // Now, go through all the directories in the temp directories and delete all the empty ones + IFileManager& FM = IFileManager::Get(); + // Lambda that parses a directory recursively and returns true if it is empty + auto IsEmptyFolder = [&FM](FString PathToDeleteOnDisk) + { + struct FEmptyFolderVisitor : public IPlatformFile::FDirectoryVisitor + { + bool bIsEmpty; + FEmptyFolderVisitor() + : bIsEmpty(true) + { + } + + virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override + { + if (!bIsDirectory) + { + bIsEmpty = false; + return false; // abort searching + } + + return true; // continue searching + } + }; + + // Look for files on disk in case the folder contains things not tracked by the asset registry + FEmptyFolderVisitor EmptyFolderVisitor; + IFileManager::Get().IterateDirectoryRecursively(*PathToDeleteOnDisk, EmptyFolderVisitor); + return EmptyFolderVisitor.bIsEmpty; + }; + + // Iterates on all the temporary cook directories recursively, + // And keep not of all the empty directories + FString TempCookPathOnDisk; + TArray FoldersToDelete; + if (FPackageName::TryConvertLongPackageNameToFilename(TempCookFolder, TempCookPathOnDisk)) + { + FM.IterateDirectoryRecursively(*TempCookPathOnDisk, [&FM, &FoldersToDelete, &IsEmptyFolder](const TCHAR* InFilenameOrDirectory, const bool InIsDirectory) -> bool + { + // Skip Files + if (!InIsDirectory) + return true; + + FString CurrentDirectoryPath = FString(InFilenameOrDirectory); + if (IsEmptyFolder(CurrentDirectoryPath)) + FoldersToDelete.Add(CurrentDirectoryPath); + + // keep iterating + return true; + }); + } + + int32 DeletedDirectories = 0; + for (auto& FolderPath : FoldersToDelete) + { + FString PathToDelete; + if (!FPackageName::TryConvertFilenameToLongPackageName(FolderPath, PathToDelete)) + continue; + + if (IFileManager::Get().DeleteDirectory(*FolderPath, false, true)) + { + AssetRegistryModule.Get().RemovePath(PathToDelete); + DeletedDirectories++; + } + } + + GWarn->EndSlowTask(); + + // Add a slate notification + Notification = TEXT("Deleted ") + FString::FromInt(DeletedCount) + TEXT(" temporary files and ") + FString::FromInt(DeletedDirectories) + TEXT(" directories."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Deleted %d temporary files and %d directories."), DeletedCount, DeletedDirectories); +} + +void +FHoudiniEngineCommands::BakeAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Baking all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 BakedCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component")); + continue; + } + + if (!HoudiniAssetComponent->IsComponentValid()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + if (AssetName != "Default__HoudiniAssetActor") + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is invalid"), *AssetName); + continue; + } + + // If component is not cooking or instancing, we can bake blueprint. + if (HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is actively instantiating or cooking"), *AssetName); + continue; + } + + bool bSuccess = false; + bool BakeToBlueprints = true; + if (BakeToBlueprints) + { + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // bSuccess = true; + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + if (bSuccess) + { + // Instantiate blueprints in component's level, then remove houdini asset actor + bSuccess = false; + ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); + if (IsValid(Level)) + { + UWorld* World = Level->GetWorld(); + if (IsValid(World)) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = Level; + FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); + for (UBlueprint* Blueprint : Blueprints) + { + if (!IsValid(Blueprint)) + continue; + World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); + } + + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + bSuccess = true; + } + } + } + } + else + { + // TODO: this used to have a way to not select in v1 + // if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent)) + // bSuccess = true; + if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, true, true)) + { + bSuccess = true; + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + } + } + + if (bSuccess) + BakedCount++; + } + + // Add a slate notification + Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); +} + +void +FHoudiniEngineCommands::PauseAssetCooking() +{ + // Revert the global flag + bool bCurrentCookingEnabled = !FHoudiniEngine::Get().IsCookingEnabled(); + FHoudiniEngine::Get().SetCookingEnabled(bCurrentCookingEnabled); + + // We need to refresh UI when pause cooking. Set refresh UI counter to be the number of current registered HACs. + if (!bCurrentCookingEnabled) + FHoudiniEngine::Get().SetUIRefreshCountWhenPauseCooking( FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount() ); + + // Add a slate notification + FString Notification = TEXT("Houdini Engine cooking paused"); + if (bCurrentCookingEnabled) + Notification = TEXT("Houdini Engine cooking resumed"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + if (bCurrentCookingEnabled) + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking resumed.")); + else + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking paused.")); + + if (!bCurrentCookingEnabled) + return; + + /* + // If we are unpausing, tick each asset component to "update" them + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!")); + continue; + } + + HoudiniAssetComponent->StartHoudiniTicking(); + } + */ +} + +bool +FHoudiniEngineCommands::IsAssetCookingPaused() +{ + return !FHoudiniEngine::Get().IsCookingEnabled(); +} + +void +FHoudiniEngineCommands::RecookSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Cooking selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and cook the assets if they're in a valid state + int32 CookedCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedCook(); + CookedCount++; + } + + // Add a slate notification + Notification = TEXT("Re-cooking ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-cooking %d selected Houdini assets."), CookedCount); +} + +void +FHoudiniEngineCommands::RecookAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Cooking all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 CookedCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedCook(); + CookedCount++; + } + + // Add a slate notification + Notification = TEXT("Re-cooked ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-cooked %d Houdini assets in the current level."), CookedCount); +} + +void +FHoudiniEngineCommands::RebuildAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Re-building all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 RebuiltCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedRebuild(); + RebuiltCount++; + } + + // Add a slate notification + Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d Houdini assets in the current level."), RebuiltCount); +} + +void +FHoudiniEngineCommands::RebuildSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Rebuilding selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and rebuilds the assets if they're in a valid state + int32 RebuiltCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid()) + continue; + + HoudiniAssetComponent->MarkAsNeedRebuild(); + RebuiltCount++; + } + + // Add a slate notification + Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d selected Houdini assets."), RebuiltCount); +} + +void +FHoudiniEngineCommands::BakeSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Baking selected Houdini Asset Actors in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and rebuilds the assets if they're in a valid state + int32 BakedCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); + continue; + } + + if (!HoudiniAssetComponent->IsComponentValid()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + HOUDINI_LOG_ERROR(TEXT("Failed to export Houdini Asset: %s in the scene!"), *AssetName); + continue; + } + + // If component is not cooking or instancing, we can bake blueprint. + if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // BakedCount++; + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // bSuccess = true; + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + if (bSuccess) + { + // Instantiate blueprints in component's level, then remove houdini asset actor + ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); + if (IsValid(Level)) + { + UWorld* World = Level->GetWorld(); + if (IsValid(World)) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = Level; + FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); + for (UBlueprint* Blueprint : Blueprints) + { + if (!IsValid(Blueprint)) + continue; + World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); + } + + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + BakedCount++; + } + } + } + } + } + + // Add a slate notification + Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); +} + +// Recentre HoudiniAsset actors' pivots to their input / cooked static-mesh average centre. +void FHoudiniEngineCommands::RecentreSelection() +{ + /* +#if WITH_EDITOR + //Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Recentering selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and cook the assets if they're in a valid state + int32 RecentreCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || !HoudiniAssetComponent->IsComponentValid()) + continue; + + // Get the average centre of all the created Static Meshes + FVector AverageBoundsCentre = FVector::ZeroVector; + int32 NumBounds = 0; + const FVector CurrentLocation = HoudiniAssetComponent->GetComponentLocation(); + { + //Check Static Meshes + TArray StaticMeshes; + StaticMeshes.Reserve(16); + HoudiniAssetComponent->GetAllUsedStaticMeshes(StaticMeshes); + + //Get average centre of all the static meshes. + for (const UStaticMesh* pMesh : StaticMeshes) + { + if (!pMesh) + continue; + + //to world space + AverageBoundsCentre += (pMesh->GetBounds().Origin + CurrentLocation); + NumBounds++; + } + } + + //Check Inputs + if (0 == NumBounds) + { + const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs; + for (const UHoudiniInput* pInput : AssetInputs) + { + if (!pInput || pInput->IsPendingKill()) + continue; + + // to world space + FBox Bounds = pInput->GetInputBounds(CurrentLocation); + if (Bounds.IsValid) + { + AverageBoundsCentre += Bounds.GetCenter(); + NumBounds++; + } + } + } + + //if we have more than one, get the average centre + if (NumBounds > 1) + { + AverageBoundsCentre /= (float)NumBounds; + } + + //if we need to move... + float fDist = FVector::DistSquared(CurrentLocation, AverageBoundsCentre); + if (NumBounds && fDist > 1.0f) + { + // Move actor to average centre and recook + // This will refresh the static mesh under the HoudiniAssestComponent ( undoing the translation ). + HoudiniAssetActor->SetActorLocation(AverageBoundsCentre, false, nullptr, ETeleportType::TeleportPhysics); + + // Recook now the houdini-static-mesh has a new origin + HoudiniAssetComponent->StartTaskAssetCookingManual(); + RecentreCount++; + } + } + + if (RecentreCount) + { + // UE4 Editor doesn't refresh the translation-handles until they are re-selected, confusing the user, deselect the objects. + GEditor->SelectNone(true, false); + } + + // Add a slate notification + Notification = TEXT("Re-centred ") + FString::FromInt(RecentreCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-centred %d selected Houdini assets."), RecentreCount); + +#endif //WITH_EDITOR + */ +} + +void +FHoudiniEngineCommands::OpenSessionSync() +{ + //if (!FHoudiniEngine::IsInitialized()) + // return; + + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - HAPI Not initialized")); + return; + } + + // Get the runtime settings to get the session/type and settings + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + + EHoudiniRuntimeSettingsSessionType SessionType = HoudiniRuntimeSettings->SessionType; + FString ServerPipeName = HoudiniRuntimeSettings->ServerPipeName; + int32 ServerPort = HoudiniRuntimeSettings->ServerPort; + + FString SessionSyncArgs = TEXT("-hess="); + if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe) + { + // Add the -hess=pipe:hapi argument + SessionSyncArgs += TEXT("pipe:") + ServerPipeName; + } + else if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_Socket) + { + // Add the -hess=port:9090 argument + SessionSyncArgs += TEXT("port:") + FString::FromInt(ServerPort); + } + else + { + // Invalid session type + HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - Invalid session type")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Opening Houdini Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Opening Houdini Session Sync.")); + + // Only launch Houdini in Session sync if we havent started it already! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + if (!FPlatformProcess::IsProcRunning(PreviousHESS)) + { + // Start houdini with the -hess commandline args + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); + FProcHandle HESSHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *SessionSyncArgs, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!HESSHandle.IsValid()) + { + // Try with the steam version executable instead + HoudiniLocation = LibHAPILocation + TEXT("//hindie.steam"); + + HESSHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *SessionSyncArgs, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!HESSHandle.IsValid()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to launch Houdini in Session Sync mode.")); + return; + } + } + + // Keep track of the SessionSync ProcHandle + FHoudiniEngine::Get().SetHESSProcHandle(HESSHandle); + } + + // Start an Async task to connect to Session Sync + Async(EAsyncExecution::TaskGraphMainThread, [SessionType, ServerPipeName, ServerPort]() + { + // Use a timeout to avoid waiting indefinitely for H to start in session sync mode + const double Timeout = 180.0; // 3min + const double StartTimestamp = FPlatformTime::Seconds(); + + FString ServerHost = TEXT("localhost"); + while (!FHoudiniEngine::Get().SessionSyncConnect(SessionType, ServerPipeName, ServerHost, ServerPort)) + { + // Houdini might not be done loading, sleep for one second + FPlatformProcess::Sleep(1); + + // Check for the timeout + if (FPlatformTime::Seconds() - StartTimestamp > Timeout) + { + // ... and a log message + HOUDINI_LOG_ERROR(TEXT("Failed to start SessionSync - Timeout...")); + return false; + } + } + + // Initialize HAPI with this session + if (!FHoudiniEngine::Get().InitializeHAPISession()) + { + FHoudiniEngine::Get().StopTicking(); + return false; + } + + // Notify all HACs that they need to instantiate in the new session + MarkAllHACsAsNeedInstantiation(); + + // Start ticking + FHoudiniEngine::Get().StartTicking(); + + // Add a slate notification + FString Notification = TEXT("Succesfully connected to Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Succesfully connected to Session Sync...")); + + return true; + }); +} + +void +FHoudiniEngineCommands::CloseSessionSync() +{ + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to stop Session Sync - HAPI Not initialized")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Stopping Houdini Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Stopping Houdini Session Sync.")); + + // Stop Houdini Session sync if it is still running! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + if (FPlatformProcess::IsProcRunning(PreviousHESS)) + { + FPlatformProcess::TerminateProc(PreviousHESS, true); + } +} + +void +FHoudiniEngineCommands::SetViewportSync(const int32& ViewportSync) +{ + if (ViewportSync == 1) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); + } + else if (ViewportSync == 2) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); + } + else if (ViewportSync == 3) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); + } + else + { + FHoudiniEngine::Get().SetSyncViewportEnabled(false); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); + } +} + +int32 +FHoudiniEngineCommands::GetViewportSync() +{ + if(!FHoudiniEngine::Get().IsSyncViewportEnabled()) + return 0; + + bool bSyncH = FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled(); + bool bSyncU = FHoudiniEngine::Get().IsSyncUnrealViewportEnabled(); + if (bSyncH && !bSyncU) + return 1; + else if (!bSyncH && bSyncU) + return 2; + else if (bSyncH && bSyncU) + return 3; + else + return 0; +} + +void +FHoudiniEngineCommands::RestartSession() +{ + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().RestartSession()) + return; + + // We've successfully restarted the Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::CreateSession() +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().CreateSession(HoudiniRuntimeSettings->SessionType)) + return; + + // We've successfully created the Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::ConnectSession() +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().ConnectSession(HoudiniRuntimeSettings->SessionType)) + return; + + // We've successfully connected to a Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation() +{ + // Notify all the HoudiniAssetComponents that they need to re instantiate themselves in the new Houdini engine session. + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedInstantiation(); + } +} + +bool +FHoudiniEngineCommands::IsSessionValid() +{ + return FHoudiniEngine::IsInitialized(); +} + +bool +FHoudiniEngineCommands::IsSessionSyncProcessValid() +{ + // Only launch Houdini in Session sync if we havent started it already! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + return FPlatformProcess::IsProcRunning(PreviousHESS); +} + +void +FHoudiniEngineCommands::StopSession() +{ + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine session stopped.")); + } +} + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE) +{ + // Get current world selection + TArray WorldSelection; + int32 NumSelectedHoudiniAssets = 0; + if (bOnlySelectedActors) + { + NumSelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (NumSelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + } + + // Add a slate notification + FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // First find the components that have meshes that we must refine + TArray ComponentsToRefine; + TArray ComponentsToCook; + // Components that would be candidates for refinement/cooking, but have errors + TArray SkippedComponents; + if (bOnlySelectedActors) + { + for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Index]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + } + else + { + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + } + + RefineTriagedHoudiniProxyMesehesToStaticMeshes( + ComponentsToRefine, + ComponentsToCook, + SkippedComponents, + bSilent, + bRefineAll, + bOnPreSaveWorld, + OnPreSaveWorld, + bOnPreBeginPIE + ); +} + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent) +{ + const bool bRefineAll = true; + const bool bOnPreSaveWorld = false; + UWorld* OnPreSaveWorld = nullptr; + const bool bOnPreBeginPIE = false; + + // First find the components that have meshes that we must refine + TArray ComponentsToRefine; + TArray ComponentsToCook; + // Components that would be candidates for refinement/cooking, but have errors + TArray SkippedComponents; + for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine) + { + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + + RefineTriagedHoudiniProxyMesehesToStaticMeshes( + ComponentsToRefine, + ComponentsToCook, + SkippedComponents, + bSilent, + bRefineAll, + bOnPreSaveWorld, + OnPreSaveWorld, + bOnPreBeginPIE + ); +} + +void +FHoudiniEngineCommands::StartPDGCommandlet() +{ + FHoudiniEngine::Get().StartPDGCommandlet(); +} + +void +FHoudiniEngineCommands::StopPDGCommandlet() +{ + FHoudiniEngine::Get().StopPDGCommandlet(); +} + +bool +FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected() +{ + return FHoudiniEngine::Get().IsPDGCommandletRunningOrConnected(); +} + +bool +FHoudiniEngineCommands::IsPDGCommandletEnabled() +{ + const UHoudiniRuntimeSettings* const Settings = GetDefault(); + if (IsValid(Settings)) + { + return Settings->bPDGAsyncCommandletImportEnabled; + } + + return false; +} + +bool +FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled) +{ + UHoudiniRuntimeSettings* const Settings = GetMutableDefault(); + if (IsValid(Settings)) + { + Settings->bPDGAsyncCommandletImportEnabled = InEnabled; + return true; + } + + return false; +} + +void +FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped) +{ + if (!InHAC || InHAC->IsPendingKill()) + return; + + // Make sure that the component's World and Owner are valid + AActor *Owner = InHAC->GetOwner(); + if (!Owner || Owner->IsPendingKill()) + return; + + UWorld *World = InHAC->GetWorld(); + if (!World || World->IsPendingKill()) + return; + + if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World) + return; + + // Check if we should consider this component for proxy mesh refinement based on its settings and + // flags passed to the function + if (bRefineAll || + (bOnPreSaveWorld && InHAC->IsProxyStaticMeshRefinementOnPreSaveWorldEnabled()) || + (bOnPreBeginPIE && InHAC->IsProxyStaticMeshRefinementOnPreBeginPIEEnabled())) + { + TArray ProxyMeshPackagesToSave; + TArray ComponentsWithProxiesToSave; + + if (InHAC->HasAnyCurrentProxyOutput()) + { + // Get the state of the asset and check if it is cooked + // If it is not cook, request a cook. We can only build the UStaticMesh + // if the data from the cook is available + // If the state is not pre-cook, or None (cooked), then the state is invalid, + // log an error and skip the component + bool bNeedsRebuildOrDelete = false; + bool bUnsupportedState = false; + const bool bCookedDataAvailable = InHAC->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bUnsupportedState); + if (bCookedDataAvailable) + { + OutToRefine.Add(InHAC); + ComponentsWithProxiesToSave.Add(InHAC); + } + else if (!bUnsupportedState && !bNeedsRebuildOrDelete) + { + InHAC->MarkAsNeedCook(); + // Force the output of the cook to be directly created as a UStaticMesh and not a proxy + InHAC->SetNoProxyMeshNextCookRequested(true); + OutToCook.Add(InHAC); + ComponentsWithProxiesToSave.Add(InHAC); + } + else + { + OutSkipped.Add(InHAC); + const EHoudiniAssetState AssetState = InHAC->GetAssetState(); + HOUDINI_LOG_ERROR(TEXT("Could not refine %s, the asset is in an unsupported state: %s"), *(InHAC->GetPathName()), *(UEnum::GetValueAsString(AssetState))); + } + } + else if (InHAC->HasAnyProxyOutput()) + { + // If the HAC has non-current proxies, destroy them + // TODO: Make this its own command? + const uint32 NumOutputs = InHAC->GetNumOutputs(); + for (uint32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = InHAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + TMap& OutputObjects = Output->GetOutputObjects(); + for (auto& CurrentPair : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; + if (!CurrentOutputObject.bProxyIsCurrent) + { + // The proxy is not current, delete it and its component + USceneComponent* FoundProxyComponent = Cast(CurrentOutputObject.ProxyComponent); + if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (FoundProxyComponent->GetOwner()) + FoundProxyComponent->GetOwner()->RemoveOwnedComponent(FoundProxyComponent); + + FoundProxyComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + FoundProxyComponent->UnregisterComponent(); + FoundProxyComponent->DestroyComponent(); + } + + UObject* ProxyObject = CurrentOutputObject.ProxyObject; + if (!ProxyObject || ProxyObject->IsPendingKill()) + continue; + + ProxyObject->MarkPendingKill(); + ProxyObject->MarkPackageDirty(); + UPackage* const Package = ProxyObject->GetPackage(); + if (IsValid(Package)) + ProxyMeshPackagesToSave.Add(Package); + } + } + } + } + + for (UHoudiniAssetComponent* const HAC : ComponentsWithProxiesToSave) + { + const uint32 NumOutputs = HAC->GetNumOutputs(); + for (uint32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + TMap& OutputObjects = Output->GetOutputObjects(); + for (auto& CurrentPair : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; + if (CurrentOutputObject.bProxyIsCurrent && CurrentOutputObject.ProxyObject) + { + UPackage* const Package = CurrentOutputObject.ProxyObject->GetPackage(); + if (IsValid(Package) && Package->IsDirty()) + ProxyMeshPackagesToSave.Add(Package); + } + } + } + } + + if (ProxyMeshPackagesToSave.Num() > 0) + { + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + FEditorFileUtils::PromptForCheckoutAndSave(ProxyMeshPackagesToSave, true, false); + } + } +} + +void +FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( + const TArray& InComponentsToRefine, + const TArray& InComponentsToCook, + const TArray& InSkippedComponents, + bool bInSilent, + bool bInRefineAll, + bool bInOnPreSaveWorld, + UWorld* InOnPreSaveWorld, + bool bInOnPrePIEBeginPlay) +{ + // Slate notification text + FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); + + const uint32 NumComponentsToCook = InComponentsToCook.Num(); + const uint32 NumComponentsToRefine = InComponentsToRefine.Num(); + const uint32 NumComponentsToProcess = NumComponentsToCook + NumComponentsToRefine; + TArray SuccessfulComponents; + uint32 NumSkippedComponents = InSkippedComponents.Num(); + if (NumComponentsToProcess > 0) + { + // The task progress pointer is potentially going to be shared with a background thread and tasks + // on the main thread, so make it thread safe + TSharedPtr TaskProgress = MakeShareable(new FSlowTask((float)NumComponentsToProcess, FText::FromString(Notification))); + TaskProgress->Initialize(); + if (!bInSilent) + TaskProgress->MakeDialog(/*bShowCancelButton=*/true); + + // Iterate over the components for which we can build UStaticMesh, and build the meshes + bool bCancelled = false; + for (uint32 ComponentIndex = 0; ComponentIndex < NumComponentsToRefine; ++ComponentIndex) + { + UHoudiniAssetComponent* HoudiniAssetComponent = InComponentsToRefine[ComponentIndex]; + TaskProgress->EnterProgressFrame(1.0f); + const bool bDestroyProxies = true; + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HoudiniAssetComponent, bDestroyProxies); + + SuccessfulComponents.Add(HoudiniAssetComponent); + + bCancelled = TaskProgress->ShouldCancel(); + if (bCancelled) + { + NumSkippedComponents += NumComponentsToRefine - ComponentIndex - 1; + break; + } + } + + if (NumComponentsToCook > 0 && !bCancelled) + { + // Now use an async task to check on the progress of the cooking components + Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { + RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); + }); + } + else + { + RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(NumComponentsToProcess, NumSkippedComponents, 0, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); + } + } +} + + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray& InComponentsToCook, TSharedPtr InTaskProgress, uint32 InNumComponentsToProcess, uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) +{ + // Copy to a double linked list so that we can loop through + // to check progress of each component and remove it easily + // if it has completed/failed + TDoubleLinkedList CookList; + for (UHoudiniAssetComponent *HAC : InComponentsToCook) + { + CookList.AddTail(HAC); + } + + // Add the successfully cooked compoments to the incoming successful components (previously refined) + TArray SuccessfulComponents(InSuccessfulComponents); + + bool bCancelled = false; + uint32 NumFailedToCook = 0; + while (CookList.Num() > 0 && !bCancelled) + { + TDoubleLinkedList::TDoubleLinkedListNode *Node = CookList.GetHead(); + while (Node && !bCancelled) + { + TDoubleLinkedList::TDoubleLinkedListNode *Next = Node->GetNextNode(); + UHoudiniAssetComponent* HAC = Node->GetValue(); + + if (HAC && !HAC->IsPendingKill()) + { + const EHoudiniAssetState State = HAC->GetAssetState(); + const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult(); + bool bUpdateProgress = false; + if (State == EHoudiniAssetState::None) + { + // Cooked, count as success, remove node + CookList.RemoveNode(Node); + SuccessfulComponents.Add(Node->GetValue()); + bUpdateProgress = true; + } + else if (ResultState != EHoudiniAssetStateResult::None && ResultState != EHoudiniAssetStateResult::Working) + { + // Failed, remove node + HOUDINI_LOG_ERROR(TEXT("Failed to cook %s to obtain static mesh."), *(HAC->GetPathName())); + CookList.RemoveNode(Node); + bUpdateProgress = true; + NumFailedToCook++; + } + + if (bUpdateProgress && InTaskProgress.IsValid()) + { + // Update progress only on the main thread, and check for cancellation request + bCancelled = Async(EAsyncExecution::TaskGraphMainThread, [InTaskProgress]() { + InTaskProgress->EnterProgressFrame(1.0f); + return InTaskProgress->ShouldCancel(); + }).Get(); + } + } + + Node = Next; + } + FPlatformProcess::Sleep(0.01f); + } + + if (bCancelled) + { + HOUDINI_LOG_WARNING(TEXT("Mesh refinement cancelled while waiting for %d components to cook."), CookList.Num()); + } + + // Cooking is done, or failed, display the notifications on the main thread + const uint32 NumRemaining = CookList.Num(); + Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InNumSkippedComponents, NumFailedToCook, NumRemaining, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { + RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InNumSkippedComponents + NumRemaining, NumFailedToCook, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); + }); +} + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) +{ + FString Notification; + if (InNumSkippedComponents + InNumFailedToCook > 0) + { + if (bCancelled) + { + Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); + } + else + { + Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); + } + FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_ERROR(TEXT("%s"), *Notification); + } + else if (InNumTotalComponents > 0) + { + Notification = TEXT("Done: Refining Houdini proxy meshes to static meshes."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_MESSAGE(TEXT("%s"), *Notification); + } + if (InTaskProgress) + { + InTaskProgress->Destroy(); + } + if (bOnPreSaveWorld && InSuccessfulComponents.Num() > 0) + { + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + + // Save the dirty static meshes in InSuccessfulComponents OnPostSaveWorld + // TODO: Remove? This may not be necessary now as we save all dirty temporary cook data in PostSaveWorld() already (Static Meshes, Materials...) + OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([InSuccessfulComponents, bOnPreSaveWorld, InOnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) { + if (bOnPreSaveWorld && InOnPreSaveWorld && InOnPreSaveWorld != InWorld) + return; + + RefineProxyMeshesHandleOnPostSaveWorld(InSuccessfulComponents, InSaveFlags, InWorld, bInSuccess); + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + }); + } +} + +void +FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) +{ + TArray PackagesToSave; + + for (UHoudiniAssetComponent* HAC : InSuccessfulComponents) + { + if (!HAC || HAC->IsPendingKill()) + continue; + + const int32 NumOutputs = HAC->GetNumOutputs(); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (auto &OutputObjectPair : Output->GetOutputObjects()) + { + UObject *Obj = OutputObjectPair.Value.OutputObject; + if (!Obj || Obj->IsPendingKill()) + continue; + + UStaticMesh *SM = Cast(Obj); + if (!SM) + continue; + + UPackage *Package = SM->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + } + } + + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h index b939cf376..6cd08f21f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h @@ -1,262 +1,263 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineStyle.h" - -#include "Framework/Commands/Commands.h" -#include "Delegates/IDelegateInstance.h" - -class UHoudiniAssetComponent; -class AHoudiniAssetActor; -struct FSlowTask; - -// Class containing commands for Houdini Engine actions -class FHoudiniEngineCommands : public TCommands -{ -public: - FHoudiniEngineCommands() - : TCommands - ( - TEXT("HoudiniEngine"), // Context name for fast lookup - NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), // Localized context name for displaying - NAME_None, // Parent context name. - FHoudiniEngineStyle::GetStyleSetName() // Icon Style Set - ) - { - } - - // TCommand<> interface - virtual void RegisterCommands() override; - -public: - - // Menu action called to save a HIP file. - static void SaveHIPFile(); - - // Menu action called to report a bug. - static void ReportBug(); - - // Menu action called to open the current scene in Houdini. - static void OpenInHoudini(); - - // Menu action called to clean up all unused files in the cook temp folder - static void CleanUpTempFolder(); - - // Menu action to bake/replace all current Houdini Assets with blueprints - static void BakeAllAssets(); - - // Helper function for baking/replacing the current select Houdini Assets with blueprints - static void BakeSelection(); - - // Helper function for restarting the current Houdini Engine session. - static void RestartSession(); - - // Menu action to pause cooking for all Houdini Assets - static void PauseAssetCooking(); - - // Helper delegate used to get the current state of PauseAssetCooking. - static bool IsAssetCookingPaused(); - - // Helper function for recooking all assets in the current level - static void RecookAllAssets(); - - // Helper function for rebuilding all assets in the current level - static void RebuildAllAssets(); - - // Helper function for recooking selected assets - static void RecookSelection(); - - // Helper function for rebuilding selected assets - static void RebuildSelection(); - - // Helper function for rebuilding selected assets - static void RecentreSelection(); - - // Helper function for starting Houdini in Sesion Sync mode - static void OpenSessionSync(); - - // Helper function for closing the current Houdini Sesion Sync - static void CloseSessionSync(); - - // returns true if the current HE session is valid - static bool IsSessionValid(); - - // Returns true if the current Session Sync process is still running - static bool IsSessionSyncProcessValid(); - - static int32 GetViewportSync(); - - static void SetViewportSync(const int32& ViewportSync); - - static void CreateSession(); - - static void ConnectSession(); - - static void StopSession(); - - static void ShowInstallInfo(); - - static void ShowPluginSettings(); - - static void OnlineDocumentation(); - - static void OnlineForum(); - - // Helper function for building static meshes for all assets using HoudiniStaticMesh - // If bSilent is false, show a progress dialog. - // If bRefineAll is true, then all components with HoudiniStaticMesh meshes will be - // refined to UStaticMesh. Otherwise, bOnPreSaveWorld and bOnPrePIEBeginPlay is checked - // against the settings of the component to determine if refinement should take place. - // If bOnPreSaveWorld is true, then OnPreSaveWorld should be the World that is being saved. In - // that case, only proxy meshes attached to components from that world will be refined. - static void RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent=false, bool bRefineAll=true, bool bOnPreSaveWorld=false, UWorld *PreSaveWorld=nullptr, bool bOnPrePIEBeginPlay=false); - - // Refine all proxy meshes on UHoudiniAssetCompoments of InActorsToRefine. - static void RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent=false); - - static void StartPDGCommandlet(); - - static void StopPDGCommandlet(); - - static bool IsPDGCommandletRunningOrConnected(); - - // Returns true if the commandlet is enabled in the settings - static bool IsPDGCommandletEnabled(); - - // Set the bPDGAsyncCommandletImportEnabled in the settings - static bool SetPDGCommandletEnabled(bool InEnabled); - - static FDelegateHandle& GetOnPostSaveWorldRefineProxyMeshesHandle() { return OnPostSaveWorldRefineProxyMeshesHandle; } - -public: - - // UI Action to create a Houdini Engine Session - TSharedPtr _CreateSession; - // UI Action to connect to a Houdini Engine Session - TSharedPtr _ConnectSession; - // UI Action to stop the current Houdini Engine Session - TSharedPtr _StopSession; - // UI Action to restart the current Houdini Engine Session - TSharedPtr _RestartSession; - // UI Action to open Houdini Session Sync - TSharedPtr _OpenSessionSync; - // UI Action to open Houdini Session Sync - TSharedPtr _CloseSessionSync; - - // UI Action to disable viewport sync - TSharedPtr _ViewportSyncNone; - // UI Action to enable unreal viewport sync - TSharedPtr _ViewportSyncUnreal; - // UI Action to enable houdini viewport sync - TSharedPtr _ViewportSyncHoudini; - // UI Action to enable bidirectionnal viewport sync - TSharedPtr _ViewportSyncBoth; - - // - TSharedPtr _InstallInfo; - // - TSharedPtr _PluginSettings; - - // Menu action called to open the current scene in Houdini. - TSharedPtr _OpenInHoudini; - // Menu action called to save a HIP file. - TSharedPtr _SaveHIPFile; - // Menu action called to clean up all unused files in the cook temp folder - TSharedPtr _CleanUpTempFolder; - - // - TSharedPtr _OnlineDoc; - // - TSharedPtr _OnlineForum; - // Menu action called to report a bug. - TSharedPtr _ReportBug; - - // UI Action to recook all HDA - TSharedPtr _CookAll; - // UI Action to recook the current world selection - TSharedPtr _CookSelected; - // Menu action to bake/replace all current Houdini Assets with blueprints - TSharedPtr _BakeAll; - // UI Action to bake and replace the current world selection - TSharedPtr _BakeSelected; - // UI Action to rebuild all HDA - TSharedPtr _RebuildAll; - // UI Action to rebuild the current world selection - TSharedPtr _RebuildSelected; - // UI Action for building static meshes for all assets using HoudiniStaticMesh - TSharedPtr _RefineAll; - // UI Action for building static meshes for selected assets using HoudiniStaticMesh - TSharedPtr _RefineSelected; - // Menu action to pause cooking for all Houdini Assets - TSharedPtr _PauseAssetCooking; - - // UI Action to recentre the current selection - TSharedPtr _RecentreSelected; - - // Start PDG/BGEO commandlet - TSharedPtr _StartPDGCommandlet; - // Stop PDG/BGEO commandlet - TSharedPtr _StopPDGCommandlet; - // Is PDG/BGEO commandlet enabled - TSharedPtr _IsPDGCommandletEnabled; - -protected: - - // Triage a HoudiniAssetComponent with UHoudiniStaticMesh as needing cooking or if a UStaticMesh can be immediately built - static void TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped); - - static void RefineTriagedHoudiniProxyMesehesToStaticMeshes( - const TArray& InComponentsToRefine, - const TArray& InComponentsToCook, - const TArray& InSkippedComponents, - bool bInSilent=false, - bool bInRefineAll=true, - bool bInOnPreSaveWorld=false, - UWorld* InOnPreSaveWorld=nullptr, - bool bInOnPrePIEBeginPlay=false); - - // Called in a background thread by RefineHoudiniProxyMeshesToStaticMeshes when some components need to be cooked to generate UStaticMeshes. Checks and waits for - // cooking of each component to complete, and then calls RefineHoudiniProxyMeshesToStaticMeshesNotifyDone on the main thread. - static void RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray &InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumComponentsToProcess, const uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); - - // Display a notification / end/close progress dialog, when refining mesh proxies to static meshes is complete - static void RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); - - // Handle OnPostSaveWorld for refining proxy meshes: this saves all the dirty UPackages of the UStaticMeshes that were created during RefineHoudiniProxyMeshesToStaticMeshes - // if it was called as a result of a PreSaveWorld. - static void RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess); - - // Helper function used to indicate to all HAC that they need to be instantiated in the new HE session - // Needs to be call after starting/restarting/connecting/session syncing a HE session.. - static void MarkAllHACsAsNeedInstantiation(); - - // Delegate that is set up to refined proxy meshes post save world (it removes itself afterwards) - static FDelegateHandle OnPostSaveWorldRefineProxyMeshesHandle; - -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineStyle.h" + +#include "Framework/Commands/Commands.h" +#include "Misc/SlowTask.h" +#include "Delegates/IDelegateInstance.h" + +class UHoudiniAssetComponent; +class AHoudiniAssetActor; +struct FSlowTask; + +// Class containing commands for Houdini Engine actions +class FHoudiniEngineCommands : public TCommands +{ +public: + FHoudiniEngineCommands() + : TCommands + ( + TEXT("HoudiniEngine"), // Context name for fast lookup + NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), // Localized context name for displaying + NAME_None, // Parent context name. + FHoudiniEngineStyle::GetStyleSetName() // Icon Style Set + ) + { + } + + // TCommand<> interface + virtual void RegisterCommands() override; + +public: + + // Menu action called to save a HIP file. + static void SaveHIPFile(); + + // Menu action called to report a bug. + static void ReportBug(); + + // Menu action called to open the current scene in Houdini. + static void OpenInHoudini(); + + // Menu action called to clean up all unused files in the cook temp folder + static void CleanUpTempFolder(); + + // Menu action to bake/replace all current Houdini Assets with blueprints + static void BakeAllAssets(); + + // Helper function for baking/replacing the current select Houdini Assets with blueprints + static void BakeSelection(); + + // Helper function for restarting the current Houdini Engine session. + static void RestartSession(); + + // Menu action to pause cooking for all Houdini Assets + static void PauseAssetCooking(); + + // Helper delegate used to get the current state of PauseAssetCooking. + static bool IsAssetCookingPaused(); + + // Helper function for recooking all assets in the current level + static void RecookAllAssets(); + + // Helper function for rebuilding all assets in the current level + static void RebuildAllAssets(); + + // Helper function for recooking selected assets + static void RecookSelection(); + + // Helper function for rebuilding selected assets + static void RebuildSelection(); + + // Helper function for rebuilding selected assets + static void RecentreSelection(); + + // Helper function for starting Houdini in Sesion Sync mode + static void OpenSessionSync(); + + // Helper function for closing the current Houdini Sesion Sync + static void CloseSessionSync(); + + // returns true if the current HE session is valid + static bool IsSessionValid(); + + // Returns true if the current Session Sync process is still running + static bool IsSessionSyncProcessValid(); + + static int32 GetViewportSync(); + + static void SetViewportSync(const int32& ViewportSync); + + static void CreateSession(); + + static void ConnectSession(); + + static void StopSession(); + + static void ShowInstallInfo(); + + static void ShowPluginSettings(); + + static void OnlineDocumentation(); + + static void OnlineForum(); + + // Helper function for building static meshes for all assets using HoudiniStaticMesh + // If bSilent is false, show a progress dialog. + // If bRefineAll is true, then all components with HoudiniStaticMesh meshes will be + // refined to UStaticMesh. Otherwise, bOnPreSaveWorld and bOnPrePIEBeginPlay is checked + // against the settings of the component to determine if refinement should take place. + // If bOnPreSaveWorld is true, then OnPreSaveWorld should be the World that is being saved. In + // that case, only proxy meshes attached to components from that world will be refined. + static void RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent=false, bool bRefineAll=true, bool bOnPreSaveWorld=false, UWorld *PreSaveWorld=nullptr, bool bOnPrePIEBeginPlay=false); + + // Refine all proxy meshes on UHoudiniAssetCompoments of InActorsToRefine. + static void RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent=false); + + static void StartPDGCommandlet(); + + static void StopPDGCommandlet(); + + static bool IsPDGCommandletRunningOrConnected(); + + // Returns true if the commandlet is enabled in the settings + static bool IsPDGCommandletEnabled(); + + // Set the bPDGAsyncCommandletImportEnabled in the settings + static bool SetPDGCommandletEnabled(bool InEnabled); + + static FDelegateHandle& GetOnPostSaveWorldRefineProxyMeshesHandle() { return OnPostSaveWorldRefineProxyMeshesHandle; } + +public: + + // UI Action to create a Houdini Engine Session + TSharedPtr _CreateSession; + // UI Action to connect to a Houdini Engine Session + TSharedPtr _ConnectSession; + // UI Action to stop the current Houdini Engine Session + TSharedPtr _StopSession; + // UI Action to restart the current Houdini Engine Session + TSharedPtr _RestartSession; + // UI Action to open Houdini Session Sync + TSharedPtr _OpenSessionSync; + // UI Action to open Houdini Session Sync + TSharedPtr _CloseSessionSync; + + // UI Action to disable viewport sync + TSharedPtr _ViewportSyncNone; + // UI Action to enable unreal viewport sync + TSharedPtr _ViewportSyncUnreal; + // UI Action to enable houdini viewport sync + TSharedPtr _ViewportSyncHoudini; + // UI Action to enable bidirectionnal viewport sync + TSharedPtr _ViewportSyncBoth; + + // + TSharedPtr _InstallInfo; + // + TSharedPtr _PluginSettings; + + // Menu action called to open the current scene in Houdini. + TSharedPtr _OpenInHoudini; + // Menu action called to save a HIP file. + TSharedPtr _SaveHIPFile; + // Menu action called to clean up all unused files in the cook temp folder + TSharedPtr _CleanUpTempFolder; + + // + TSharedPtr _OnlineDoc; + // + TSharedPtr _OnlineForum; + // Menu action called to report a bug. + TSharedPtr _ReportBug; + + // UI Action to recook all HDA + TSharedPtr _CookAll; + // UI Action to recook the current world selection + TSharedPtr _CookSelected; + // Menu action to bake/replace all current Houdini Assets with blueprints + TSharedPtr _BakeAll; + // UI Action to bake and replace the current world selection + TSharedPtr _BakeSelected; + // UI Action to rebuild all HDA + TSharedPtr _RebuildAll; + // UI Action to rebuild the current world selection + TSharedPtr _RebuildSelected; + // UI Action for building static meshes for all assets using HoudiniStaticMesh + TSharedPtr _RefineAll; + // UI Action for building static meshes for selected assets using HoudiniStaticMesh + TSharedPtr _RefineSelected; + // Menu action to pause cooking for all Houdini Assets + TSharedPtr _PauseAssetCooking; + + // UI Action to recentre the current selection + TSharedPtr _RecentreSelected; + + // Start PDG/BGEO commandlet + TSharedPtr _StartPDGCommandlet; + // Stop PDG/BGEO commandlet + TSharedPtr _StopPDGCommandlet; + // Is PDG/BGEO commandlet enabled + TSharedPtr _IsPDGCommandletEnabled; + +protected: + + // Triage a HoudiniAssetComponent with UHoudiniStaticMesh as needing cooking or if a UStaticMesh can be immediately built + static void TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped); + + static void RefineTriagedHoudiniProxyMesehesToStaticMeshes( + const TArray& InComponentsToRefine, + const TArray& InComponentsToCook, + const TArray& InSkippedComponents, + bool bInSilent=false, + bool bInRefineAll=true, + bool bInOnPreSaveWorld=false, + UWorld* InOnPreSaveWorld=nullptr, + bool bInOnPrePIEBeginPlay=false); + + // Called in a background thread by RefineHoudiniProxyMeshesToStaticMeshes when some components need to be cooked to generate UStaticMeshes. Checks and waits for + // cooking of each component to complete, and then calls RefineHoudiniProxyMeshesToStaticMeshesNotifyDone on the main thread. + static void RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray &InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumComponentsToProcess, const uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); + + // Display a notification / end/close progress dialog, when refining mesh proxies to static meshes is complete + static void RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); + + // Handle OnPostSaveWorld for refining proxy meshes: this saves all the dirty UPackages of the UStaticMeshes that were created during RefineHoudiniProxyMeshesToStaticMeshes + // if it was called as a result of a PreSaveWorld. + static void RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess); + + // Helper function used to indicate to all HAC that they need to be instantiated in the new HE session + // Needs to be call after starting/restarting/connecting/session syncing a HE session.. + static void MarkAllHACsAsNeedInstantiation(); + + // Delegate that is set up to refined proxy meshes post save world (it removes itself afterwards) + static FDelegateHandle OnPostSaveWorldRefineProxyMeshesHandle; + +}; + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp index bbd8e24c4..300c2fff1 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp @@ -1,1950 +1,1951 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" - -#include "DetailCategoryBuilder.h" -#include "IDetailGroup.h" -#include "DetailWidgetRow.h" -#include "Widgets/SBoxPanel.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Layout/SScrollBox.h" -#include "Widgets/Input/SMultiLineEditableTextBox.h" -#include "Widgets/Layout/SUniformGridPanel.h" -#include "Brushes/SlateImageBrush.h" -#include "Widgets/Input/SComboBox.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "ActorPickerMode.h" -#include "SceneOutlinerModule.h" -#include "Modules/ModuleManager.h" -#include "Interfaces/IMainFrameModule.h" -#include "AssetThumbnail.h" -#include "DetailLayoutBuilder.h" -#include "SAssetDropTarget.h" -#include "PropertyCustomizationHelpers.h" -#include "ScopedTransaction.h" -#include "SEnumCombobox.h" -#include "HAL/FileManager.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define HOUDINI_ENGINE_UI_SECTION_GENERATE 1 -#define HOUDINI_ENGINE_UI_SECTION_BAKE 2 -#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS 3 -#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG 4 - -#define HOUDINI_ENGINE_UI_BUTTON_WIDTH 135.0f - -#define HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT "Generate" -#define HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT "Bake" -#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT "Asset Options" -#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT "Help and Debug" - - -void -SHoudiniAssetLogWidget::Construct(const FArguments & InArgs) -{ - this->ChildSlot - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background"))) - .Content() - [ - SNew(SScrollBox) - + SScrollBox::Slot() - [ - SNew(SMultiLineEditableTextBox) - .Text(FText::FromString(InArgs._LogText)) - .AutoWrapText(true) - .IsReadOnly(true) - ] - ] - ]; -} - - -void -FHoudiniEngineDetails::CreateWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // 0. Houdini Engine Icon - FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs); - - // 1. Create Generate Category - FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 2. Create Bake Category - FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 3. Create Asset Options Category - FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 4. Create Help and Debug Category - FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs); - -} - -void -FHoudiniEngineDetails::CreateHoudiniEngineIconWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Skip drawing the icon if the icon image is not loaded correctly. - TSharedPtr HoudiniEngineUIIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIIconBrush(); - if (!HoudiniEngineUIIconBrush.IsValid()) - return; - - FDetailWidgetRow & Row = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef Box = SNew(SHorizontalBox); - TSharedPtr Image; - - Box->AddSlot() - .AutoWidth() - .Padding(0.0f, 5.0f, 5.0f, 10.0f) - .HAlign(HAlign_Left) - [ - SNew(SBox) - .HeightOverride(30) - .WidthOverride(208) - [ - SAssignNew(Image, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - Image->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIIconBrush]() { - return HoudiniEngineUIIconBrush.Get(); - }))); - - Row.WholeRowWidget.Widget = Box; - Row.IsEnabled(false); -} - -void -FHoudiniEngineDetails::CreateGenerateWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - auto OnReBuildClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->MarkAsNeedRebuild(); - } - - return FReply::Handled(); - }; - - auto OnRecookClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->MarkAsNeedCook(); - } - - return FReply::Handled(); - }; - - auto ShouldEnableResetParametersButtonLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - // Reset parameters to default values? - for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) - { - UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); - - if (NextParm && !NextParm->IsDefault()) - return true; - } - } - - return false; - }; - - auto OnResetParametersClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - // Reset parameters to default values? - for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) - { - UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); - - if (NextParm && !NextParm->IsDefault()) - { - NextParm->RevertToDefault(); - } - } - } - - return FReply::Handled(); - }; - - auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) - { - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FString NewPathStr = Val.ToString(); - - if (NewPathStr.IsEmpty()) - return; - - if (NewPathStr.StartsWith("Game/")) - { - NewPathStr = "/" + NewPathStr; - } - - FString AbsolutePath; - if (NewPathStr.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); - } - - if (!FPaths::DirectoryExists(AbsolutePath)) - { - HOUDINI_LOG_WARNING(TEXT("Invalid path")); - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - return; - } - - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr)) - continue; - - NextHAC->TemporaryCookFolder.Path = NewPathStr; - } - }; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_GENERATE); - - // Button Row (draw only if expanded) - if (!MainHAC->bGenerateMenuExpanded) - return; - - TSharedPtr HoudiniEngineUIRebuildIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRebuildIconBrush(); - TSharedPtr HoudiniEngineUIRecookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - TSharedPtr HoudiniEngineUIResetParametersIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIResetParametersIconBrush(); - - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef ButtonHorizontalBox = SNew(SHorizontalBox); - - // Rebuild button - TSharedPtr RebuildButton; - TSharedPtr RebuildButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(15.0f, 0.0f, 0.0f, 2.0f) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(RebuildButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) - //.Text(FText::FromString("Rebuild")) - .Visibility(EVisibility::Visible) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) - ] - ] - .OnClicked_Lambda(OnReBuildClickedLambda) - ] - ]; - - if (HoudiniEngineUIRebuildIconBrush.IsValid()) - { - TSharedPtr RebuildImage; - RebuildButtonHorizontalBox->AddSlot() - //.Padding(25.0f, 0.0f, 3.0f, 0.0f) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RebuildImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - RebuildImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { - return HoudiniEngineUIRebuildIconBrush.Get(); - }))); - } - - RebuildButtonHorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .AutoWidth() - .Padding(5.0, 0.0, 0.0, 0.0) - [ - SNew(STextBlock) - .Text(FText::FromString("Rebuild")) - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; - ButtonRow.IsEnabled(false); - - // Recook button - TSharedPtr RecookButton; - TSharedPtr RecookButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(RecookButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) - //.Text(FText::FromString("Recook")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnRecookClickedLambda) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) - ] - ] - ] - ]; - - if (HoudiniEngineUIRecookIconBrush.IsValid()) - { - TSharedPtr RecookImage; - RecookButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RecookImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - RecookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { - return HoudiniEngineUIRecookIconBrush.Get(); - }))); - } - - RecookButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Recook")) - ]; - - // Reset Parameters button - TSharedPtr ResetParametersButton; - TSharedPtr ResetParametersButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(ResetParametersButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsResetParametersAssetButton", "Reset the selected Houdini Asset's parameters to their default values.")) - //.Text(FText::FromString("Reset Parameters")) - .IsEnabled_Lambda(ShouldEnableResetParametersButtonLambda) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnResetParametersClickedLambda) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(ResetParametersButtonHorizontalBox, SHorizontalBox) - ] - ] - ] - ]; - - if (HoudiniEngineUIResetParametersIconBrush.IsValid()) - { - TSharedPtr ResetParametersImage; - ResetParametersButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(0.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(ResetParametersImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - ResetParametersImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIResetParametersIconBrush]() { - return HoudiniEngineUIResetParametersIconBrush.Get(); - }))); - } - - ResetParametersButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - //.FillWidth(4.2f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - //.MinDesiredWidth(160.f) - .Text(FText::FromString("Reset Parameters")) - ]; - - - // Temp Cook Folder Row - FDetailWidgetRow & TempCookFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef TempCookFolderRowHorizontalBox = SNew(SHorizontalBox); - - TempCookFolderRowHorizontalBox->AddSlot() - //.Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineTemporaryCookFolderLabel", "Temporary Cook Folder")) - .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, MAterials, Textures..) that are generated by Houdini Assets when they cook.")) - ] - ]; - - TempCookFolderRowHorizontalBox->AddSlot() - .MaxWidth(235.0f) - [ - SNew(SBox) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, Materials, Textures..) that are generated by Houdini Assets when they cook.")) - .HintText(LOCTEXT("HoudiniEngineTempCookFolderHintText", "Input to set temporary cook folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainHAC->TemporaryCookFolder.Path)) - .OnTextCommitted_Lambda(OnCookFolderTextCommittedLambda) - ] - ]; - - TempCookFolderRow.WholeRowWidget.Widget = TempCookFolderRowHorizontalBox; -} - -void -FHoudiniEngineDetails::OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC) -{ - if (!IsValid(InHAC)) - return; - - if (!bInState) - { - if (InHAC->GetOnPostCookBakeDelegate().IsBound()) - InHAC->GetOnPostCookBakeDelegate().Unbind(); - } - else - { - InHAC->GetOnPostCookBakeDelegate().BindLambda([](UHoudiniAssetComponent* HAC) - { - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - HAC, - HAC->bReplacePreviousBake, - HAC->HoudiniEngineBakeOption, - HAC->bRemoveOutputAfterBake); - }); - } -} - -void -FHoudiniEngineDetails::CreateBakeWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE); - - if (!MainHAC->bBakeMenuExpanded) - return; - - auto OnBakeButtonClickedLambda = [InHACs, MainHAC]() - { - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - NextHAC, - MainHAC->bReplacePreviousBake, - MainHAC->HoudiniEngineBakeOption, - MainHAC->bRemoveOutputAfterBake); - } - - return FReply::Handled(); - }; - - auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) - { - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FString NewPathStr = Val.ToString(); - - if (NewPathStr.IsEmpty()) - return; - - if (NewPathStr.StartsWith("Game/")) - { - NewPathStr = "/" + NewPathStr; - } - - FString AbsolutePath; - if (NewPathStr.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); - } - - if (!FPaths::DirectoryExists(AbsolutePath)) - { - HOUDINI_LOG_WARNING(TEXT("Invalid path")); - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - return; - } - - - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - if (NextHAC->BakeFolder.Path.Equals(NewPathStr)) - continue; - - NextHAC->BakeFolder.Path = NewPathStr; - } - }; - - // Button Row - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - - // Bake Button - TSharedPtr BakeButton; - TSharedPtr BakeButtonHorizontalBox; - - ButtonRowHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(15.f, 0.0f, 0.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(BakeButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsBakeButton", "Bake the Houdini Asset Component(s).")) - //.Text(FText::FromString("Recook")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnBakeButtonClickedLambda) - .Content() - [ - SAssignNew(BakeButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); - if (BakeIconBrush.IsValid()) - { - TSharedPtr BakeImage; - BakeButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(BakeImage, SImage) - ] - ]; - - BakeImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([BakeIconBrush]() { - return BakeIconBrush.Get(); - }))); - } - - BakeButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Bake")) - ]; - - // Bake Type ComboBox - TSharedPtr>> TypeComboBox; - - TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEngineBakeTypeOptionsLabels(); - TSharedPtr IntialSelec; - if (OptionSource) - { - IntialSelec = (*OptionSource)[(int)MainHAC->HoudiniEngineBakeOption]; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - //.MaxWidth(103.f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - //.WidthOverride(103.f) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [MainHAC, InHACs](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EHoudiniEngineBakeOption NewOption = - FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - MainHAC->HoudiniEngineBakeOption = NewOption; - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - [ - SNew(STextBlock) - .Text_Lambda([MainHAC]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(MainHAC->HoudiniEngineBakeOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; - - // Clear Output After Baking Row - FDetailWidgetRow & ClearOutputAfterBakingRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); - - // Remove Output Checkbox - TSharedPtr CheckBoxRemoveOutput; - TSharedPtr CheckBoxAutoBake; - TSharedPtr CheckBoxRecenterBakedActors; - TSharedPtr CheckBoxReplacePreviousBake; - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRemoveOutput, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBox", "Remove HDA Output After Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBoxToolTip", "After baking the existing output of this Houdini Asset Actor will be removed.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bRemoveOutputAfterBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bRemoveOutputAfterBake = bNewState; - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bRecenterBakedActors = bNewState; - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - // TODO: find a better way to manage the initial binding/unbinding of the post cook bake delegate - // We do this here to ensure the delegate is bound/unbound correctly when the UI is initially drawn - // Currently we have the problem that the HoudiniEngineRuntime and HoudiniEngine modules cannot access - // the FHoudiniEngineBakeUtils code that is in HoudiniEngineEditor (this is the primary reason for the delegate and - // managing the delegate in this way). - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - const bool bState = NextHAC->IsBakeAfterNextCookEnabled(); - NextHAC->SetBakeAfterNextCookEnabled(bState); - OnBakeAfterCookChangedHelper(bState, NextHAC); - } - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxAutoBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake the next cook.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->IsBakeAfterNextCookEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->SetBakeAfterNextCookEnabled(bNewState); - OnBakeAfterCookChangedHelper(bNewState, NextHAC); - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - // Replace Checkbox - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxReplacePreviousBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBox", "Replace Previous Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBoxToolTip", "When baking replace the previous bake's output instead of creating additional output actors/components/objects.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bReplacePreviousBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - MainHAC->bReplacePreviousBake = bNewState; - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - ClearOutputAfterBakingRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; - - // Bake Folder Row - FDetailWidgetRow & BakeFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) - ] - ]; - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .MaxWidth(235.0) - [ - SNew(SBox) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) - .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainHAC->BakeFolder.Path)) - .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) - ] - ]; - - BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; - - switch (MainHAC->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToActorToolTip", - "Bake this Houdini Asset Actor and its components to native unreal actors and components, replacing the previous baked result.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToActorToolTip", - "Bake this Houdini Asset Actor and its components to native unreal actors and components.")); - } - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToBlueprintToolTip", - "Bake this Houdini Asset Actor to a blueprint.")); - } - break; - - case EHoudiniEngineBakeOption::ToFoliage: - { - if (!FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(MainHAC)) - { - // If the HAC does not have instanced output, disable Bake to Foliage - BakeButton->SetEnabled(false); - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonNoInstancedOutputToolTip", - "The Houdini Asset must be outputing at least one instancer in order to be able to bake to Foliage.")); - } - else - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToFoliageToolTip", - "Add this Houdini Asset Actor's instancers to the current level's Foliage, replacing the previously baked foliage instancers from this actor.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToFoliageToolTip", - "Add this Houdini Asset Actor's instancers to the current level's Foliage.")); - } - } - } - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToWorldOutlinerToolTip", - "Not implemented.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToWorldOutlinerToolTip", - "Not implemented.")); - } - } - break; - } - - // Todo: remove me! - if (MainHAC->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToWorldOutliner) - BakeButton->SetEnabled(false); - -} - -void -FHoudiniEngineDetails::CreateAssetOptionsWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Header Row - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS); - - if (!MainHAC->bAssetOptionMenuExpanded) - return; - - auto IsCheckedParameterChangedLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateParameterChangedLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnParameterChange = bChecked; - } - }; - - auto IsCheckedTransformChangeLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedTransformChangeLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnTransformChange = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedAssetInputCookLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedAssetInputCookLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnAssetInputCook = bChecked; - } - }; - - auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedPushTransformToHoudiniLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bUploadTransformsToHoudiniEngine = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedDoNotGenerateOutputsLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bOutputless = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedOutputTemplatedGeosLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bOutputTemplateGeos = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - // Checkboxes row - FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef CheckBoxesHorizontalBox = SNew(SHorizontalBox); - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - CheckBoxesHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - CheckBoxesHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineCookTriggersLabel", "Cook Triggers")) - ] - ]; - - // Parameter change check box - FText TooltipText = LOCTEXT("HoudiniEngineParameterChangeTooltip", "If enabled, modifying a parameter or input on this Houdini Asset will automatically trigger a cook of the HDA in Houdini."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineParameterChangeCheckBoxLabel", "On Parameter/Input Change")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateParameterChangedLambda) - .IsChecked_Lambda(IsCheckedParameterChangedLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Transform change check box - TooltipText = LOCTEXT("HoudiniEngineTransformChangeTooltip", "If enabled, changing the Houdini Asset Actor's transform in Unreal will also update its HDA's node transform in Houdini, and trigger a recook of the HDA with the updated transform."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineTransformChangeCheckBoxLabel", "On Transform Change")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedTransformChangeLambda) - .IsChecked_Lambda(IsCheckedTransformChangeLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Triggers Downstream cook checkbox - TooltipText = LOCTEXT("HoudiniEngineAssetInputCookTooltip", "When enabled, this asset will automatically re-cook after one its asset input has finished cooking."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineAssetInputCheckBoxLabel", "On Asset Input Cook")) - .ToolTipText(TooltipText) - ] - - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedAssetInputCookLambda) - .IsChecked_Lambda(IsCheckedAssetInputCookLambda) - .ToolTipText(TooltipText) - ] - ]; - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous")) - ]; - - // Push Transform to Houdini check box - TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini")) - .ToolTipText(TooltipText) - ] - - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda) - .IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Do not generate output check box - TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda) - .IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Output templated geos check box - TooltipText = LOCTEXT("HoudiniEnginOutputTemplatesTooltip", "If enabled, Geometry nodes in the asset that have the template flag will be outputed."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Output Templated Geos")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedOutputTemplatedGeosLambda) - .IsChecked_Lambda(IsCheckedOutputTemplatedGeosLambda) - .ToolTipText(TooltipText) - ] - ]; - - CheckBoxesRow.WholeRowWidget.Widget = CheckBoxesHorizontalBox; -} - -void -FHoudiniEngineDetails::CreateHelpAndDebugWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Header Row - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG); - - if (!MainHAC->bHelpAndDebugMenuExpanded) - return; - - auto OnFetchCookLogButtonClickedLambda = [InHACs]() - { - return ShowCookLog(InHACs); - }; - - auto OnHelpButtonClickedLambda = [MainHAC]() - { - return ShowAssetHelp(MainHAC); - }; - - // Button Row - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - TSharedPtr CookLogButtonHorizontalBox = SNew(SHorizontalBox); - - // Fetch Cook Log button - ButtonRowHorizontalBox->AddSlot() - //.Padding(15.0f, 0.0f, 0.0f, 0.0f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(FText::FromString("Fetch and display all cook logs available for this Houdini Asset Actor.")) - //.Text(FText::FromString("Fetch Cook Log")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnFetchCookLogButtonClickedLambda) - .Content() - [ - SAssignNew(CookLogButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr CookLogIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUICookLogIconBrush(); - if (CookLogIconBrush.IsValid()) - { - TSharedPtr CookImage; - CookLogButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookImage, SImage) - ] - ]; - - CookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookLogIconBrush]() { - return CookLogIconBrush.Get(); - }))); - } - - CookLogButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Show Cook Logs")) - ]; - - // Asset Help Button - TSharedPtr AssetHelpButtonHorizontalBox; - ButtonRowHorizontalBox->AddSlot() - //.Padding(4.0, 0.0f, 0.0f, 0.0f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(FText::FromString("Display this Houdini Asset Actor's HDA help.")) - //.Text(FText::FromString("Asset Help")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnHelpButtonClickedLambda) - .Content() - [ - SAssignNew(AssetHelpButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr AssetHelpIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIAssetHelpIconBrush(); - if (AssetHelpIconBrush.IsValid()) - { - TSharedPtr AssetHelpImage; - AssetHelpButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(AssetHelpImage, SImage) - ] - ]; - - AssetHelpImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([AssetHelpIconBrush]() { - return AssetHelpIconBrush.Get(); - }))); - } - - AssetHelpButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Asset Help")) - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; -} - -FMenuBuilder -FHoudiniEngineDetails::Helper_CreateHoudiniAssetPicker() -{ - auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - return true; - }; - - auto OnActorSelected = [](AActor* Actor) - { - UE_LOG(LogTemp, Warning, TEXT("Actor Selected")); - - return; - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterHoudiniAssetLambda); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("HoudiniEngineDetailsAssetPicker", "Asset")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -const FSlateBrush * -FHoudiniEngineDetails::GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const -{ - if (HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - -/* -TSharedRef< SWidget > -FHoudiniEngineDetails::OnGetHoudiniAssetMenuContent(TArray InHACs) -{ - TArray< const UClass * > AllowedClasses; - AllowedClasses.Add(UHoudiniAsset::StaticClass()); - - TArray< UFactory * > NewAssetFactories; - - UHoudiniAsset * HoudiniAsset = nullptr; - if (InHACs.Num() > 0) - { - UHoudiniAssetComponent * HoudiniAssetComponent = InHACs[0]; - HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; - } - - auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - return true; - }; - - // Delegate for filtering Houdini assets. - FOnShouldFilterAsset OnShouldFilterHoudiniAsset = FOnShouldFilterAsset::CreateLambda(OnShouldFilterHoudiniAssetLambda); - - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(HoudiniAsset), true, - AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset, - FOnAssetSelected::CreateLambda([](const FAssetData & AssetData) {}), - FSimpleDelegate::CreateLambda([]() { }) - ); -} -*/ - -FReply -FHoudiniEngineDetails::ShowCookLog(TArray InHACS) -{ - TSharedPtr< SWindow > ParentWindow; - FString CookLog = FHoudiniEngineUtils::GetCookLog(InHACS); - - // 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("MainFrame"); - ParentWindow = MainFrame.GetParentWindow(); - } - - if (ParentWindow.IsValid()) - { - TSharedPtr HoudiniAssetCookLog; - - TSharedRef Window = - SNew(SWindow) - .Title(LOCTEXT("WindowTitle", "Houdini Cook Log")) - .ClientSize(FVector2D(640, 480)); - - Window->SetContent( - SAssignNew(HoudiniAssetCookLog, SHoudiniAssetLogWidget) - .LogText(CookLog)); - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - } - - return FReply::Handled(); -} - -FReply -FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC) -{ - if (!InHAC) - return FReply::Handled(); - - FString AssetHelp = FHoudiniEngineUtils::GetAssetHelp(InHAC); - - 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 HoudiniAssetHelpLog; - - TSharedRef Window = - SNew(SWindow) - .Title(LOCTEXT("WindowTitle", "Houdini Asset Help")) - .ClientSize(FVector2D(640, 480)); - - Window->SetContent( - SAssignNew(HoudiniAssetHelpLog, SHoudiniAssetLogWidget) - .LogText(AssetHelp)); - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - } - return FReply::Handled(); -} - -void -FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return; - - FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - HoudiniAssetComponent->bGenerateMenuExpanded = !HoudiniAssetComponent->bGenerateMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - HoudiniAssetComponent->bBakeMenuExpanded = !HoudiniAssetComponent->bBakeMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - HoudiniAssetComponent->bAssetOptionMenuExpanded = !HoudiniAssetComponent->bAssetOptionMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - HoudiniAssetComponent->bHelpAndDebugMenuExpanded = !HoudiniAssetComponent->bHelpAndDebugMenuExpanded; - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniAssetComponent, true); - - return FReply::Handled(); - }); - - TFunction GetText = [MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT); - break; - } - return FText::FromString(""); - }; - - TFunction GetExpanderBrush = [HoudiniAssetComponent, MenuSection](SButton* InExpanderArrow) - { - FName ResourceName; - bool bMenuExpanded = false; - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - bMenuExpanded = HoudiniAssetComponent->bGenerateMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - bMenuExpanded = HoudiniAssetComponent->bBakeMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - bMenuExpanded = HoudiniAssetComponent->bAssetOptionMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - bMenuExpanded = HoudiniAssetComponent->bHelpAndDebugMenuExpanded; - } - - if (bMenuExpanded) - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - }; - - return AddHeaderRow(HoudiniEngineCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); -} - -void -FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]() - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - InPDGAssetLink->Modify(); - InPDGAssetLink->bBakeMenuExpanded = !InPDGAssetLink->bBakeMenuExpanded; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded), InPDGAssetLink); - break; - } - - //FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, true); - - return FReply::Handled(); - }); - - TFunction GetText = [MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); - break; - } - return FText::FromString(""); - }; - - TFunction GetExpanderBrush = [InPDGAssetLink, MenuSection](SButton* InExpanderArrow) - { - FName ResourceName; - bool bMenuExpanded = false; - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - bMenuExpanded = InPDGAssetLink->bBakeMenuExpanded; - break; - } - - if (bMenuExpanded) - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - }; - - return AddHeaderRow(PDGCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); -} - -void -FHoudiniEngineDetails::AddHeaderRow( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - FOnClicked& InOnExpanderClick, - TFunction& InGetText, - TFunction& InGetExpanderBrush) -{ - // Header Row - FDetailWidgetRow & HeaderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedPtr HeaderHorizontalBox; - HeaderRow.WholeRowWidget.Widget = SAssignNew(HeaderHorizontalBox, SHorizontalBox); - - TSharedPtr ExpanderImage; - TSharedPtr ExpanderArrow; - HeaderHorizontalBox->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth() - [ - SAssignNew(ExpanderArrow, SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - .OnClicked(InOnExpanderClick) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - HeaderHorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SNew(STextBlock) - .Text_Lambda([InGetText](){return InGetText(); }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - ExpanderImage->SetImage( - TAttribute::Create( - [ExpanderArrow, InGetExpanderBrush]() - { - return InGetExpanderBrush(ExpanderArrow.Get()); - })); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" + +#include "CoreMinimal.h" +#include "DetailCategoryBuilder.h" +#include "IDetailGroup.h" +#include "DetailWidgetRow.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Brushes/SlateImageBrush.h" +#include "Widgets/Input/SComboBox.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "ActorPickerMode.h" +#include "SceneOutlinerModule.h" +#include "Modules/ModuleManager.h" +#include "Interfaces/IMainFrameModule.h" +#include "AssetThumbnail.h" +#include "DetailLayoutBuilder.h" +#include "SAssetDropTarget.h" +#include "PropertyCustomizationHelpers.h" +#include "ScopedTransaction.h" +#include "SEnumCombobox.h" +#include "HAL/FileManager.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define HOUDINI_ENGINE_UI_SECTION_GENERATE 1 +#define HOUDINI_ENGINE_UI_SECTION_BAKE 2 +#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS 3 +#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG 4 + +#define HOUDINI_ENGINE_UI_BUTTON_WIDTH 135.0f + +#define HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT "Generate" +#define HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT "Bake" +#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT "Asset Options" +#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT "Help and Debug" + + +void +SHoudiniAssetLogWidget::Construct(const FArguments & InArgs) +{ + this->ChildSlot + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background"))) + .Content() + [ + SNew(SScrollBox) + + SScrollBox::Slot() + [ + SNew(SMultiLineEditableTextBox) + .Text(FText::FromString(InArgs._LogText)) + .AutoWrapText(true) + .IsReadOnly(true) + ] + ] + ]; +} + + +void +FHoudiniEngineDetails::CreateWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // 0. Houdini Engine Icon + FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs); + + // 1. Create Generate Category + FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 2. Create Bake Category + FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 3. Create Asset Options Category + FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 4. Create Help and Debug Category + FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs); + +} + +void +FHoudiniEngineDetails::CreateHoudiniEngineIconWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Skip drawing the icon if the icon image is not loaded correctly. + TSharedPtr HoudiniEngineUIIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIIconBrush(); + if (!HoudiniEngineUIIconBrush.IsValid()) + return; + + FDetailWidgetRow & Row = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef Box = SNew(SHorizontalBox); + TSharedPtr Image; + + Box->AddSlot() + .AutoWidth() + .Padding(0.0f, 5.0f, 5.0f, 10.0f) + .HAlign(HAlign_Left) + [ + SNew(SBox) + .HeightOverride(30) + .WidthOverride(208) + [ + SAssignNew(Image, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + Image->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIIconBrush]() { + return HoudiniEngineUIIconBrush.Get(); + }))); + + Row.WholeRowWidget.Widget = Box; + Row.IsEnabled(false); +} + +void +FHoudiniEngineDetails::CreateGenerateWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + auto OnReBuildClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->MarkAsNeedRebuild(); + } + + return FReply::Handled(); + }; + + auto OnRecookClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->MarkAsNeedCook(); + } + + return FReply::Handled(); + }; + + auto ShouldEnableResetParametersButtonLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + // Reset parameters to default values? + for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) + { + UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); + + if (NextParm && !NextParm->IsDefault()) + return true; + } + } + + return false; + }; + + auto OnResetParametersClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + // Reset parameters to default values? + for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) + { + UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); + + if (NextParm && !NextParm->IsDefault()) + { + NextParm->RevertToDefault(); + } + } + } + + return FReply::Handled(); + }; + + auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) + { + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FString NewPathStr = Val.ToString(); + + if (NewPathStr.IsEmpty()) + return; + + if (NewPathStr.StartsWith("Game/")) + { + NewPathStr = "/" + NewPathStr; + } + + FString AbsolutePath; + if (NewPathStr.StartsWith("/Game/")) + { + FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); + } + + if (!FPaths::DirectoryExists(AbsolutePath)) + { + HOUDINI_LOG_WARNING(TEXT("Invalid path")); + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + return; + } + + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr)) + continue; + + NextHAC->TemporaryCookFolder.Path = NewPathStr; + } + }; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_GENERATE); + + // Button Row (draw only if expanded) + if (!MainHAC->bGenerateMenuExpanded) + return; + + TSharedPtr HoudiniEngineUIRebuildIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRebuildIconBrush(); + TSharedPtr HoudiniEngineUIRecookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + TSharedPtr HoudiniEngineUIResetParametersIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIResetParametersIconBrush(); + + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef ButtonHorizontalBox = SNew(SHorizontalBox); + + // Rebuild button + TSharedPtr RebuildButton; + TSharedPtr RebuildButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(15.0f, 0.0f, 0.0f, 2.0f) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(RebuildButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) + //.Text(FText::FromString("Rebuild")) + .Visibility(EVisibility::Visible) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) + ] + ] + .OnClicked_Lambda(OnReBuildClickedLambda) + ] + ]; + + if (HoudiniEngineUIRebuildIconBrush.IsValid()) + { + TSharedPtr RebuildImage; + RebuildButtonHorizontalBox->AddSlot() + //.Padding(25.0f, 0.0f, 3.0f, 0.0f) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RebuildImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + RebuildImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { + return HoudiniEngineUIRebuildIconBrush.Get(); + }))); + } + + RebuildButtonHorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) + [ + SNew(STextBlock) + .Text(FText::FromString("Rebuild")) + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; + ButtonRow.IsEnabled(false); + + // Recook button + TSharedPtr RecookButton; + TSharedPtr RecookButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(RecookButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) + //.Text(FText::FromString("Recook")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnRecookClickedLambda) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) + ] + ] + ] + ]; + + if (HoudiniEngineUIRecookIconBrush.IsValid()) + { + TSharedPtr RecookImage; + RecookButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RecookImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + RecookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { + return HoudiniEngineUIRecookIconBrush.Get(); + }))); + } + + RecookButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Recook")) + ]; + + // Reset Parameters button + TSharedPtr ResetParametersButton; + TSharedPtr ResetParametersButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(ResetParametersButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsResetParametersAssetButton", "Reset the selected Houdini Asset's parameters to their default values.")) + //.Text(FText::FromString("Reset Parameters")) + .IsEnabled_Lambda(ShouldEnableResetParametersButtonLambda) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnResetParametersClickedLambda) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(ResetParametersButtonHorizontalBox, SHorizontalBox) + ] + ] + ] + ]; + + if (HoudiniEngineUIResetParametersIconBrush.IsValid()) + { + TSharedPtr ResetParametersImage; + ResetParametersButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(0.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(ResetParametersImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + ResetParametersImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIResetParametersIconBrush]() { + return HoudiniEngineUIResetParametersIconBrush.Get(); + }))); + } + + ResetParametersButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + //.FillWidth(4.2f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + //.MinDesiredWidth(160.f) + .Text(FText::FromString("Reset Parameters")) + ]; + + + // Temp Cook Folder Row + FDetailWidgetRow & TempCookFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef TempCookFolderRowHorizontalBox = SNew(SHorizontalBox); + + TempCookFolderRowHorizontalBox->AddSlot() + //.Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineTemporaryCookFolderLabel", "Temporary Cook Folder")) + .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, MAterials, Textures..) that are generated by Houdini Assets when they cook.")) + ] + ]; + + TempCookFolderRowHorizontalBox->AddSlot() + .MaxWidth(235.0f) + [ + SNew(SBox) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, Materials, Textures..) that are generated by Houdini Assets when they cook.")) + .HintText(LOCTEXT("HoudiniEngineTempCookFolderHintText", "Input to set temporary cook folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainHAC->TemporaryCookFolder.Path)) + .OnTextCommitted_Lambda(OnCookFolderTextCommittedLambda) + ] + ]; + + TempCookFolderRow.WholeRowWidget.Widget = TempCookFolderRowHorizontalBox; +} + +void +FHoudiniEngineDetails::OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC) +{ + if (!IsValid(InHAC)) + return; + + if (!bInState) + { + if (InHAC->GetOnPostCookBakeDelegate().IsBound()) + InHAC->GetOnPostCookBakeDelegate().Unbind(); + } + else + { + InHAC->GetOnPostCookBakeDelegate().BindLambda([](UHoudiniAssetComponent* HAC) + { + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + HAC, + HAC->bReplacePreviousBake, + HAC->HoudiniEngineBakeOption, + HAC->bRemoveOutputAfterBake); + }); + } +} + +void +FHoudiniEngineDetails::CreateBakeWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE); + + if (!MainHAC->bBakeMenuExpanded) + return; + + auto OnBakeButtonClickedLambda = [InHACs, MainHAC]() + { + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + NextHAC, + MainHAC->bReplacePreviousBake, + MainHAC->HoudiniEngineBakeOption, + MainHAC->bRemoveOutputAfterBake); + } + + return FReply::Handled(); + }; + + auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) + { + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FString NewPathStr = Val.ToString(); + + if (NewPathStr.IsEmpty()) + return; + + if (NewPathStr.StartsWith("Game/")) + { + NewPathStr = "/" + NewPathStr; + } + + FString AbsolutePath; + if (NewPathStr.StartsWith("/Game/")) + { + FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); + } + + if (!FPaths::DirectoryExists(AbsolutePath)) + { + HOUDINI_LOG_WARNING(TEXT("Invalid path")); + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + return; + } + + + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + if (NextHAC->BakeFolder.Path.Equals(NewPathStr)) + continue; + + NextHAC->BakeFolder.Path = NewPathStr; + } + }; + + // Button Row + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + + // Bake Button + TSharedPtr BakeButton; + TSharedPtr BakeButtonHorizontalBox; + + ButtonRowHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(15.f, 0.0f, 0.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(BakeButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsBakeButton", "Bake the Houdini Asset Component(s).")) + //.Text(FText::FromString("Recook")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnBakeButtonClickedLambda) + .Content() + [ + SAssignNew(BakeButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); + if (BakeIconBrush.IsValid()) + { + TSharedPtr BakeImage; + BakeButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(BakeImage, SImage) + ] + ]; + + BakeImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([BakeIconBrush]() { + return BakeIconBrush.Get(); + }))); + } + + BakeButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Bake")) + ]; + + // Bake Type ComboBox + TSharedPtr>> TypeComboBox; + + TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEngineBakeTypeOptionsLabels(); + TSharedPtr IntialSelec; + if (OptionSource) + { + IntialSelec = (*OptionSource)[(int)MainHAC->HoudiniEngineBakeOption]; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + //.MaxWidth(103.f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + //.WidthOverride(103.f) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [MainHAC, InHACs](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EHoudiniEngineBakeOption NewOption = + FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + MainHAC->HoudiniEngineBakeOption = NewOption; + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + [ + SNew(STextBlock) + .Text_Lambda([MainHAC]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(MainHAC->HoudiniEngineBakeOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; + + // Clear Output After Baking Row + FDetailWidgetRow & ClearOutputAfterBakingRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); + + // Remove Output Checkbox + TSharedPtr CheckBoxRemoveOutput; + TSharedPtr CheckBoxAutoBake; + TSharedPtr CheckBoxRecenterBakedActors; + TSharedPtr CheckBoxReplacePreviousBake; + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRemoveOutput, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBox", "Remove HDA Output After Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBoxToolTip", "After baking the existing output of this Houdini Asset Actor will be removed.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bRemoveOutputAfterBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bRemoveOutputAfterBake = bNewState; + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bRecenterBakedActors = bNewState; + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + // TODO: find a better way to manage the initial binding/unbinding of the post cook bake delegate + // We do this here to ensure the delegate is bound/unbound correctly when the UI is initially drawn + // Currently we have the problem that the HoudiniEngineRuntime and HoudiniEngine modules cannot access + // the FHoudiniEngineBakeUtils code that is in HoudiniEngineEditor (this is the primary reason for the delegate and + // managing the delegate in this way). + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + const bool bState = NextHAC->IsBakeAfterNextCookEnabled(); + NextHAC->SetBakeAfterNextCookEnabled(bState); + OnBakeAfterCookChangedHelper(bState, NextHAC); + } + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxAutoBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake the next cook.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->IsBakeAfterNextCookEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->SetBakeAfterNextCookEnabled(bNewState); + OnBakeAfterCookChangedHelper(bNewState, NextHAC); + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + // Replace Checkbox + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxReplacePreviousBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBox", "Replace Previous Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBoxToolTip", "When baking replace the previous bake's output instead of creating additional output actors/components/objects.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bReplacePreviousBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + MainHAC->bReplacePreviousBake = bNewState; + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + ClearOutputAfterBakingRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; + + // Bake Folder Row + FDetailWidgetRow & BakeFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) + .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) + ] + ]; + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .MaxWidth(235.0) + [ + SNew(SBox) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) + .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainHAC->BakeFolder.Path)) + .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) + ] + ]; + + BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; + + switch (MainHAC->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToActorToolTip", + "Bake this Houdini Asset Actor and its components to native unreal actors and components, replacing the previous baked result.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToActorToolTip", + "Bake this Houdini Asset Actor and its components to native unreal actors and components.")); + } + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToBlueprintToolTip", + "Bake this Houdini Asset Actor to a blueprint.")); + } + break; + + case EHoudiniEngineBakeOption::ToFoliage: + { + if (!FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(MainHAC)) + { + // If the HAC does not have instanced output, disable Bake to Foliage + BakeButton->SetEnabled(false); + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonNoInstancedOutputToolTip", + "The Houdini Asset must be outputing at least one instancer in order to be able to bake to Foliage.")); + } + else + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToFoliageToolTip", + "Add this Houdini Asset Actor's instancers to the current level's Foliage, replacing the previously baked foliage instancers from this actor.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToFoliageToolTip", + "Add this Houdini Asset Actor's instancers to the current level's Foliage.")); + } + } + } + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToWorldOutlinerToolTip", + "Not implemented.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToWorldOutlinerToolTip", + "Not implemented.")); + } + } + break; + } + + // Todo: remove me! + if (MainHAC->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToWorldOutliner) + BakeButton->SetEnabled(false); + +} + +void +FHoudiniEngineDetails::CreateAssetOptionsWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Header Row + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS); + + if (!MainHAC->bAssetOptionMenuExpanded) + return; + + auto IsCheckedParameterChangedLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateParameterChangedLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnParameterChange = bChecked; + } + }; + + auto IsCheckedTransformChangeLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedTransformChangeLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnTransformChange = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedAssetInputCookLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedAssetInputCookLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnAssetInputCook = bChecked; + } + }; + + auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedPushTransformToHoudiniLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bUploadTransformsToHoudiniEngine = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedDoNotGenerateOutputsLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bOutputless = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedOutputTemplatedGeosLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bOutputTemplateGeos = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + // Checkboxes row + FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef CheckBoxesHorizontalBox = SNew(SHorizontalBox); + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + CheckBoxesHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + CheckBoxesHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineCookTriggersLabel", "Cook Triggers")) + ] + ]; + + // Parameter change check box + FText TooltipText = LOCTEXT("HoudiniEngineParameterChangeTooltip", "If enabled, modifying a parameter or input on this Houdini Asset will automatically trigger a cook of the HDA in Houdini."); + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineParameterChangeCheckBoxLabel", "On Parameter/Input Change")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateParameterChangedLambda) + .IsChecked_Lambda(IsCheckedParameterChangedLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Transform change check box + TooltipText = LOCTEXT("HoudiniEngineTransformChangeTooltip", "If enabled, changing the Houdini Asset Actor's transform in Unreal will also update its HDA's node transform in Houdini, and trigger a recook of the HDA with the updated transform."); + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineTransformChangeCheckBoxLabel", "On Transform Change")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedTransformChangeLambda) + .IsChecked_Lambda(IsCheckedTransformChangeLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Triggers Downstream cook checkbox + TooltipText = LOCTEXT("HoudiniEngineAssetInputCookTooltip", "When enabled, this asset will automatically re-cook after one its asset input has finished cooking."); + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineAssetInputCheckBoxLabel", "On Asset Input Cook")) + .ToolTipText(TooltipText) + ] + + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedAssetInputCookLambda) + .IsChecked_Lambda(IsCheckedAssetInputCookLambda) + .ToolTipText(TooltipText) + ] + ]; + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous")) + ]; + + // Push Transform to Houdini check box + TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini."); + RightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini")) + .ToolTipText(TooltipText) + ] + + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda) + .IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Do not generate output check box + TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs."); + RightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda) + .IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Output templated geos check box + TooltipText = LOCTEXT("HoudiniEnginOutputTemplatesTooltip", "If enabled, Geometry nodes in the asset that have the template flag will be outputed."); + RightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Output Templated Geos")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedOutputTemplatedGeosLambda) + .IsChecked_Lambda(IsCheckedOutputTemplatedGeosLambda) + .ToolTipText(TooltipText) + ] + ]; + + CheckBoxesRow.WholeRowWidget.Widget = CheckBoxesHorizontalBox; +} + +void +FHoudiniEngineDetails::CreateHelpAndDebugWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Header Row + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG); + + if (!MainHAC->bHelpAndDebugMenuExpanded) + return; + + auto OnFetchCookLogButtonClickedLambda = [InHACs]() + { + return ShowCookLog(InHACs); + }; + + auto OnHelpButtonClickedLambda = [MainHAC]() + { + return ShowAssetHelp(MainHAC); + }; + + // Button Row + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + TSharedPtr CookLogButtonHorizontalBox = SNew(SHorizontalBox); + + // Fetch Cook Log button + ButtonRowHorizontalBox->AddSlot() + //.Padding(15.0f, 0.0f, 0.0f, 0.0f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(FText::FromString("Fetch and display all cook logs available for this Houdini Asset Actor.")) + //.Text(FText::FromString("Fetch Cook Log")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnFetchCookLogButtonClickedLambda) + .Content() + [ + SAssignNew(CookLogButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr CookLogIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUICookLogIconBrush(); + if (CookLogIconBrush.IsValid()) + { + TSharedPtr CookImage; + CookLogButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookImage, SImage) + ] + ]; + + CookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookLogIconBrush]() { + return CookLogIconBrush.Get(); + }))); + } + + CookLogButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Show Cook Logs")) + ]; + + // Asset Help Button + TSharedPtr AssetHelpButtonHorizontalBox; + ButtonRowHorizontalBox->AddSlot() + //.Padding(4.0, 0.0f, 0.0f, 0.0f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(FText::FromString("Display this Houdini Asset Actor's HDA help.")) + //.Text(FText::FromString("Asset Help")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnHelpButtonClickedLambda) + .Content() + [ + SAssignNew(AssetHelpButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr AssetHelpIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIAssetHelpIconBrush(); + if (AssetHelpIconBrush.IsValid()) + { + TSharedPtr AssetHelpImage; + AssetHelpButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(AssetHelpImage, SImage) + ] + ]; + + AssetHelpImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([AssetHelpIconBrush]() { + return AssetHelpIconBrush.Get(); + }))); + } + + AssetHelpButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Asset Help")) + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; +} + +FMenuBuilder +FHoudiniEngineDetails::Helper_CreateHoudiniAssetPicker() +{ + auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + return true; + }; + + auto OnActorSelected = [](AActor* Actor) + { + UE_LOG(LogTemp, Warning, TEXT("Actor Selected")); + + return; + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterHoudiniAssetLambda); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("HoudiniEngineDetailsAssetPicker", "Asset")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +const FSlateBrush * +FHoudiniEngineDetails::GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const +{ + if (HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + +/* +TSharedRef< SWidget > +FHoudiniEngineDetails::OnGetHoudiniAssetMenuContent(TArray InHACs) +{ + TArray< const UClass * > AllowedClasses; + AllowedClasses.Add(UHoudiniAsset::StaticClass()); + + TArray< UFactory * > NewAssetFactories; + + UHoudiniAsset * HoudiniAsset = nullptr; + if (InHACs.Num() > 0) + { + UHoudiniAssetComponent * HoudiniAssetComponent = InHACs[0]; + HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; + } + + auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + return true; + }; + + // Delegate for filtering Houdini assets. + FOnShouldFilterAsset OnShouldFilterHoudiniAsset = FOnShouldFilterAsset::CreateLambda(OnShouldFilterHoudiniAssetLambda); + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(HoudiniAsset), true, + AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset, + FOnAssetSelected::CreateLambda([](const FAssetData & AssetData) {}), + FSimpleDelegate::CreateLambda([]() { }) + ); +} +*/ + +FReply +FHoudiniEngineDetails::ShowCookLog(TArray InHACS) +{ + TSharedPtr< SWindow > ParentWindow; + FString CookLog = FHoudiniEngineUtils::GetCookLog(InHACS); + + // 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("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (ParentWindow.IsValid()) + { + TSharedPtr HoudiniAssetCookLog; + + TSharedRef Window = + SNew(SWindow) + .Title(LOCTEXT("WindowTitle", "Houdini Cook Log")) + .ClientSize(FVector2D(640, 480)); + + Window->SetContent( + SAssignNew(HoudiniAssetCookLog, SHoudiniAssetLogWidget) + .LogText(CookLog)); + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + } + + return FReply::Handled(); +} + +FReply +FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC) +{ + if (!InHAC) + return FReply::Handled(); + + FString AssetHelp = FHoudiniEngineUtils::GetAssetHelp(InHAC); + + 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 HoudiniAssetHelpLog; + + TSharedRef Window = + SNew(SWindow) + .Title(LOCTEXT("WindowTitle", "Houdini Asset Help")) + .ClientSize(FVector2D(640, 480)); + + Window->SetContent( + SAssignNew(HoudiniAssetHelpLog, SHoudiniAssetLogWidget) + .LogText(AssetHelp)); + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + } + return FReply::Handled(); +} + +void +FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return; + + FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + HoudiniAssetComponent->bGenerateMenuExpanded = !HoudiniAssetComponent->bGenerateMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + HoudiniAssetComponent->bBakeMenuExpanded = !HoudiniAssetComponent->bBakeMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + HoudiniAssetComponent->bAssetOptionMenuExpanded = !HoudiniAssetComponent->bAssetOptionMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + HoudiniAssetComponent->bHelpAndDebugMenuExpanded = !HoudiniAssetComponent->bHelpAndDebugMenuExpanded; + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniAssetComponent, true); + + return FReply::Handled(); + }); + + TFunction GetText = [MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT); + break; + } + return FText::FromString(""); + }; + + TFunction GetExpanderBrush = [HoudiniAssetComponent, MenuSection](SButton* InExpanderArrow) + { + FName ResourceName; + bool bMenuExpanded = false; + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + bMenuExpanded = HoudiniAssetComponent->bGenerateMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + bMenuExpanded = HoudiniAssetComponent->bBakeMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + bMenuExpanded = HoudiniAssetComponent->bAssetOptionMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + bMenuExpanded = HoudiniAssetComponent->bHelpAndDebugMenuExpanded; + } + + if (bMenuExpanded) + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + }; + + return AddHeaderRow(HoudiniEngineCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); +} + +void +FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]() + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + InPDGAssetLink->Modify(); + InPDGAssetLink->bBakeMenuExpanded = !InPDGAssetLink->bBakeMenuExpanded; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded), InPDGAssetLink); + break; + } + + //FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, true); + + return FReply::Handled(); + }); + + TFunction GetText = [MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); + break; + } + return FText::FromString(""); + }; + + TFunction GetExpanderBrush = [InPDGAssetLink, MenuSection](SButton* InExpanderArrow) + { + FName ResourceName; + bool bMenuExpanded = false; + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + bMenuExpanded = InPDGAssetLink->bBakeMenuExpanded; + break; + } + + if (bMenuExpanded) + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + }; + + return AddHeaderRow(PDGCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); +} + +void +FHoudiniEngineDetails::AddHeaderRow( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + FOnClicked& InOnExpanderClick, + TFunction& InGetText, + TFunction& InGetExpanderBrush) +{ + // Header Row + FDetailWidgetRow & HeaderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedPtr HeaderHorizontalBox; + HeaderRow.WholeRowWidget.Widget = SAssignNew(HeaderHorizontalBox, SHorizontalBox); + + TSharedPtr ExpanderImage; + TSharedPtr ExpanderArrow; + HeaderHorizontalBox->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth() + [ + SAssignNew(ExpanderArrow, SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + .OnClicked(InOnExpanderClick) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + HeaderHorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SNew(STextBlock) + .Text_Lambda([InGetText](){return InGetText(); }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + ExpanderImage->SetImage( + TAttribute::Create( + [ExpanderArrow, InGetExpanderBrush]() + { + return InGetExpanderBrush(ExpanderArrow.Get()); + })); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h index cc52add15..e19fe9a76 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h @@ -1,117 +1,122 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#pragma once - -#include "CoreMinimal.h" - -class IDetailCategoryBuilder; -class UHoudiniAssetComponent; -class UHoudiniPDGAssetLink; -class FMenuBuilder; -class SBorder; -class SButton; - -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SCompoundWidget.h" -#include "Framework/SlateDelegates.h" - -class SHoudiniAssetLogWidget : public SCompoundWidget -{ -public: - - SLATE_BEGIN_ARGS(SHoudiniAssetLogWidget) - : _LogText(TEXT("")) - {} - - SLATE_ARGUMENT(FString, LogText) - SLATE_END_ARGS() - - /** Widget construct. **/ - void Construct(const FArguments & InArgs); -}; - -class FHoudiniEngineDetails : public TSharedFromThis -{ -public: - static void CreateWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateHoudiniEngineIconWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateGenerateWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateBakeWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreatePDGBakeWidgets( - IDetailCategoryBuilder& InPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink); - - static void CreateAssetOptionsWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateHelpAndDebugWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static FReply ShowCookLog(TArray InHACS); - - static FReply ShowAssetHelp(UHoudiniAssetComponent * InHAC); - - static FMenuBuilder Helper_CreateHoudiniAssetPicker(); - - const FSlateBrush * GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const; - - /** Construct drop down menu content for Houdini asset. **/ - //static TSharedRef< SWidget > OnGetHoudiniAssetMenuContent(TArray InHACs); - - static void AddHeaderRowForHoudiniAssetComponent( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - UHoudiniAssetComponent* HoudiniAssetComponent, - int32 MenuSection); - - static void AddHeaderRowForHoudiniPDGAssetLink( - IDetailCategoryBuilder& PDGCategoryBuilder, - UHoudiniPDGAssetLink* InPDGAssetLink, - int32 MenuSection); - - static void AddHeaderRow( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - FOnClicked& InOnExpanderClick, - TFunction& InGetText, - TFunction& InGetExpanderBrush); - - // Helper for binding/unbinding the post cook bake delegate - static void OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC); -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Framework/SlateDelegates.h" +#include "Styling/SlateBrush.h" +#include "Widgets/Layout/SBorder.h" +#include "Framework/SlateDelegates.h" +#include "Widgets/Input/SButton.h" + +class IDetailCategoryBuilder; +class UHoudiniAssetComponent; +class UHoudiniPDGAssetLink; +class FMenuBuilder; +class SBorder; +class SButton; + +class SHoudiniAssetLogWidget : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SHoudiniAssetLogWidget) + : _LogText(TEXT("")) + {} + + SLATE_ARGUMENT(FString, LogText) + SLATE_END_ARGS() + + /** Widget construct. **/ + void Construct(const FArguments & InArgs); +}; + +class FHoudiniEngineDetails : public TSharedFromThis +{ +public: + static void CreateWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateHoudiniEngineIconWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateGenerateWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateBakeWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreatePDGBakeWidgets( + IDetailCategoryBuilder& InPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink); + + static void CreateAssetOptionsWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateHelpAndDebugWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static FReply ShowCookLog(TArray InHACS); + + static FReply ShowAssetHelp(UHoudiniAssetComponent * InHAC); + + static FMenuBuilder Helper_CreateHoudiniAssetPicker(); + + const FSlateBrush * GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const; + + /** Construct drop down menu content for Houdini asset. **/ + //static TSharedRef< SWidget > OnGetHoudiniAssetMenuContent(TArray InHACs); + + static void AddHeaderRowForHoudiniAssetComponent( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + UHoudiniAssetComponent* HoudiniAssetComponent, + int32 MenuSection); + + static void AddHeaderRowForHoudiniPDGAssetLink( + IDetailCategoryBuilder& PDGCategoryBuilder, + UHoudiniPDGAssetLink* InPDGAssetLink, + int32 MenuSection); + + static void AddHeaderRow( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + FOnClicked& InOnExpanderClick, + TFunction& InGetText, + TFunction& InGetExpanderBrush); + + // Helper for binding/unbinding the post cook bake delegate + static void OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC); +}; + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp index 64137804f..9a3fc34d6 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp @@ -1,1474 +1,1543 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineEditor.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetBroker.h" -#include "HoudiniAssetActorFactory.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetComponentDetails.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniRuntimeSettingsDetails.h" -#include "HoudiniSplineComponentVisualizer.h" -#include "HoudiniHandleComponentVisualizer.h" -#include "AssetTypeActions_HoudiniAsset.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPackageParams.h" - -#include "Modules/ModuleManager.h" -#include "Interfaces/IPluginManager.h" -#include "HAL/PlatformFilemanager.h" -#include "Misc/MessageDialog.h" -#include "Misc/Paths.h" -#include "AssetRegistryModule.h" -#include "PropertyEditorModule.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "LevelEditor.h" -#include "Templates/SharedPointer.h" -#include "Framework/Application/SlateApplication.h" -#include "HAL/ConsoleManager.h" -#include "Editor/UnrealEdEngine.h" -#include "Editor.h" -#include "UnrealEdGlobals.h" -#include "Engine/Selection.h" -#include "Widgets/Input/SCheckBox.h" -#include "Logging/LogMacros.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FHoudiniEngineEditor, HoudiniEngineEditor); -DEFINE_LOG_CATEGORY(LogHoudiniEngineEditor); - -FHoudiniEngineEditor * -FHoudiniEngineEditor::HoudiniEngineEditorInstance = nullptr; - -FHoudiniEngineEditor & -FHoudiniEngineEditor::Get() -{ - return *HoudiniEngineEditorInstance; -} - -bool -FHoudiniEngineEditor::IsInitialized() -{ - return FHoudiniEngineEditor::HoudiniEngineEditorInstance != nullptr; -} - -FHoudiniEngineEditor::FHoudiniEngineEditor() -{ -} - -void FHoudiniEngineEditor::StartupModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine Editor module.")); - - // Create style set. - FHoudiniEngineStyle::Initialize(); - - // Initilizes various resources used by our editor UI widgets - InitializeWidgetResource(); - - // Register asset type actions. - RegisterAssetTypeActions(); - - // Register asset brokers. - RegisterAssetBrokers(); - - // Register component visualizers. - RegisterComponentVisualizers(); - - // Register detail presenters. - RegisterDetails(); - - // Register actor factories. - RegisterActorFactories(); - - // Extends the file menu. - ExtendMenu(); - - // Extend the World Outliner Menu - AddLevelViewportMenuExtender(); - - // Adds the custom console commands - RegisterConsoleCommands(); - - // Register global undo / redo callbacks. - //RegisterForUndo(); - - //RegisterPlacementModeExtensions(); - - // Register for any FEditorDelegates that we are interested in, such as - // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds - RegisterEditorDelegates(); - - // Store the instance. - FHoudiniEngineEditor::HoudiniEngineEditorInstance = this; - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module startup complete.")); -} - -void FHoudiniEngineEditor::ShutdownModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine Editor module.")); - - // Deregister editor delegates - UnregisterEditorDelegates(); - - // Deregister console commands - UnregisterConsoleCommands(); - - // Remove the level viewport Menu extender - RemoveLevelViewportMenuExtender(); - - // Unregister asset type actions. - UnregisterAssetTypeActions(); - - // Unregister asset brokers. - //UnregisterAssetBrokers(); - - // Unregister detail presenters. - UnregisterDetails(); - - // Unregister our component visualizers. - //UnregisterComponentVisualizers(); - - // Unregister global undo / redo callbacks. - //UnregisterForUndo(); - - //UnregisterPlacementModeExtensions(); - - // Unregister the styleset - FHoudiniEngineStyle::Shutdown(); - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module shutdown complete.")); -} - -FString -FHoudiniEngineEditor::GetHoudiniEnginePluginDir() -{ - FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(EnginePluginDir)) - return EnginePluginDir; - - FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(ProjectPluginDir)) - return ProjectPluginDir; - - TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); - FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; - if (FPaths::DirectoryExists(PluginBaseDir)) - return PluginBaseDir; - - HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); - - return EnginePluginDir; -} - -void -FHoudiniEngineEditor::RegisterDetails() -{ - FPropertyEditorModule & PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Register details presenter for our component type and runtime settings. - PropertyModule.RegisterCustomClassLayout( - TEXT("HoudiniAssetComponent"), - FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniAssetComponentDetails::MakeInstance)); - - PropertyModule.RegisterCustomClassLayout( - TEXT("HoudiniRuntimeSettings"), - FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniRuntimeSettingsDetails::MakeInstance)); -} - -void -FHoudiniEngineEditor::UnregisterDetails() -{ - if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) - { - FPropertyEditorModule & PropertyModule = - FModuleManager::LoadModuleChecked("PropertyEditor"); - - PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniAssetComponent")); - PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniRuntimeSettings")); - } -} - -void -FHoudiniEngineEditor::RegisterComponentVisualizers() -{ - if (GUnrealEd) - { - // Register Houdini spline visualizer - SplineComponentVisualizer = MakeShareable(new FHoudiniSplineComponentVisualizer); - if (SplineComponentVisualizer.IsValid()) - { - GUnrealEd->RegisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName(), SplineComponentVisualizer); - SplineComponentVisualizer->OnRegister(); - } - - // Register Houdini handle visualizer - HandleComponentVisualizer = MakeShareable(new FHoudiniHandleComponentVisualizer); - if (HandleComponentVisualizer.IsValid()) - { - GUnrealEd->RegisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName(), HandleComponentVisualizer); - HandleComponentVisualizer->OnRegister(); - } - } -} - -void -FHoudiniEngineEditor::UnregisterComponentVisualizers() -{ - if (GUnrealEd) - { - // Unregister Houdini spline visualizer - if(SplineComponentVisualizer.IsValid()) - GUnrealEd->UnregisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); - - // Unregister Houdini handle visualizer - if (HandleComponentVisualizer.IsValid()) - GUnrealEd->UnregisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - } -} - -void -FHoudiniEngineEditor::RegisterAssetTypeAction(IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action) -{ - AssetTools.RegisterAssetTypeActions(Action); - AssetTypeActions.Add(Action); -} - -void -FHoudiniEngineEditor::RegisterAssetTypeActions() -{ - // Create and register asset type actions for Houdini asset. - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools").Get(); - RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_HoudiniAsset())); -} - -void -FHoudiniEngineEditor::UnregisterAssetTypeActions() -{ - // Unregister asset type actions we have previously registered. - if (FModuleManager::Get().IsModuleLoaded("AssetTools")) - { - IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >("AssetTools").Get(); - - for (int32 Index = 0; Index < AssetTypeActions.Num(); ++Index) - AssetTools.UnregisterAssetTypeActions(AssetTypeActions[Index].ToSharedRef()); - - AssetTypeActions.Empty(); - } -} - -void -FHoudiniEngineEditor::RegisterAssetBrokers() -{ - // Create and register broker for Houdini asset. - HoudiniAssetBroker = MakeShareable(new FHoudiniAssetBroker()); - FComponentAssetBrokerage::RegisterBroker( HoudiniAssetBroker, UHoudiniAssetComponent::StaticClass(), true, true ); -} - -void -FHoudiniEngineEditor::UnregisterAssetBrokers() -{ - if (UObjectInitialized()) - { - // Unregister broker. - FComponentAssetBrokerage::UnregisterBroker( HoudiniAssetBroker ); - } -} - -void -FHoudiniEngineEditor::RegisterActorFactories() -{ - if (GEditor) - { - UHoudiniAssetActorFactory * HoudiniAssetActorFactory = - NewObject< UHoudiniAssetActorFactory >(GetTransientPackage(), UHoudiniAssetActorFactory::StaticClass()); - - GEditor->ActorFactories.Add(HoudiniAssetActorFactory); - } -} - -void -FHoudiniEngineEditor::BindMenuCommands() -{ - HEngineCommands = MakeShareable(new FUICommandList); - - FHoudiniEngineCommands::Register(); - const FHoudiniEngineCommands& Commands = FHoudiniEngineCommands::Get(); - - // Session - - HEngineCommands->MapAction( - Commands._CreateSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CreateSession(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._ConnectSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ConnectSession(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._StopSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopSession(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RestartSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RestartSession(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OpenSessionSync, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OpenSessionSync(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); - - HEngineCommands->MapAction( - Commands._CloseSessionSync, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CloseSessionSync(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); - - HEngineCommands->MapAction( - Commands._ViewportSyncNone, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(0); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 0); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncHoudini, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(1); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 1); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncUnreal, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(2); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 2); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncBoth, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(3); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 3); }) - ); - - // PDG commandlet - HEngineCommands->MapAction( - Commands._IsPDGCommandletEnabled, - FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::SetPDGCommandletEnabled(!FHoudiniEngineCommands::IsPDGCommandletEnabled()); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletEnabled(); }) - ); - - HEngineCommands->MapAction( - Commands._StartPDGCommandlet, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StartPDGCommandlet(); }), - FCanExecuteAction::CreateLambda([]() - { - return FHoudiniEngineCommands::IsPDGCommandletEnabled() && !FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); - }) - ); - - HEngineCommands->MapAction( - Commands._StopPDGCommandlet, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopPDGCommandlet(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); })); - - // Plugin - - HEngineCommands->MapAction( - Commands._InstallInfo, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowInstallInfo(); }), - FCanExecuteAction::CreateLambda([]() { return false; })); - - HEngineCommands->MapAction( - Commands._PluginSettings, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowPluginSettings(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Files - - HEngineCommands->MapAction( - Commands._OpenInHoudini, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::OpenInHoudini(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._SaveHIPFile, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::SaveHIPFile(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._CleanUpTempFolder, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CleanUpTempFolder(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Help and support - - HEngineCommands->MapAction( - Commands._ReportBug, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::ReportBug(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OnlineDoc, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineDocumentation(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OnlineForum, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineForum(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Actions - - HEngineCommands->MapAction( - Commands._CookAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookAllAssets(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._CookSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RebuildAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildAllAssets(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RebuildSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._BakeAll, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::BakeAllAssets(); }), - FCanExecuteAction::CreateLambda([](){ return true; })); - - HEngineCommands->MapAction( - Commands._BakeSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::BakeSelection(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._RefineAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._RefineSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._PauseAssetCooking, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::PauseAssetCooking(); }), - FCanExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::IsSessionValid(); }), - FIsActionChecked::CreateLambda([](){ return FHoudiniEngineCommands::IsAssetCookingPaused(); })); - - // Non menu command (used for shortcuts only) - - // Append the command to the editor module - FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >("LevelEditor"); - LevelEditorModule.GetGlobalLevelEditorActions()->Append(HEngineCommands.ToSharedRef()); -} - -void -FHoudiniEngineEditor::ExtendMenu() -{ - if (IsRunningCommandlet()) - return; - - // We need to add/bind the UI Commands to their functions first - BindMenuCommands(); - - MainMenuExtender = MakeShareable(new FExtender); - - // Extend File menu, we will add Houdini section. - MainMenuExtender->AddMenuExtension( - "FileLoadAndSave", - EExtensionHook::After, - HEngineCommands, - FMenuExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniFileMenuExtension)); - - MainMenuExtender->AddMenuBarExtension( - "Edit", - EExtensionHook::After, - HEngineCommands, - FMenuBarExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniEditorMenu)); - - // Add our menu extender - FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); - LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); -} - -void -FHoudiniEngineEditor::AddHoudiniFileMenuExtension(FMenuBuilder & MenuBuilder) -{ - MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); - - // Icons used by the commands are defined in the HoudiniEngineStyle - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - - MenuBuilder.EndSection(); -} - -void -FHoudiniEngineEditor::AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder) -{ - // View - MenuBarBuilder.AddPullDownMenu( - LOCTEXT("HoudiniLabel", "Houdini Engine"), - LOCTEXT("HoudiniMenu_ToolTip", "Open the Houdini Engine menu"), - FNewMenuDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniMainMenuExtension), - "View"); -} - -void -FHoudiniEngineEditor::AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder) -{ - /* - MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); - // Icons used by the commands are defined in the HoudiniEngineStyle - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - MenuBuilder.EndSection(); - */ - - MenuBuilder.BeginSection("Session", LOCTEXT("SessionLabel", "Session")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CreateSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ConnectSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenSessionSync); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CloseSessionSync); - - // Viewport sync menu - struct FLocalMenuBuilder - { - static void FillViewportSyncMenu(FMenuBuilder& InSubMenuBuilder) - { - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncNone); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncHoudini); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncUnreal); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncBoth); - } - }; - - MenuBuilder.AddSubMenu( - LOCTEXT("SyncViewport", "Sync Viewport"), - LOCTEXT("SyncViewport_ToolTip", "Sync Viewport"), - FNewMenuDelegate::CreateStatic(&FLocalMenuBuilder::FillViewportSyncMenu), - false, - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._SyncViewport")); - - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("PDG", LOCTEXT("PDGLabel", "PDG")); - struct FLocalPDGMenuBuilder - { - static void FillPDGMenu(FMenuBuilder& InSubMenuBuilder) - { - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._IsPDGCommandletEnabled); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StartPDGCommandlet); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopPDGCommandlet); - } - }; - MenuBuilder.AddSubMenu( - LOCTEXT("PDGSubMenu", "Work Item Import Settings"), - LOCTEXT("PDGSubmenu_ToolTip", "PDG Work Item Import Settings"), - FNewMenuDelegate::CreateStatic(&FLocalPDGMenuBuilder::FillPDGMenu), - false, - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.PDGLink")); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Plugin", LOCTEXT("PluginLabel", "Plugin")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._InstallInfo); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PluginSettings); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("File", LOCTEXT("FileLabel", "File")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Help", LOCTEXT("HelpLabel", "Help And Support")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineDoc); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineForum); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Actions", LOCTEXT("ActionsLabel", "Actions")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - - - MenuBuilder.EndSection(); -} - -void -FHoudiniEngineEditor::RegisterForUndo() -{ - /* - if (GUnrealEd) - GUnrealEd->RegisterForUndo(this); - */ -} - -void -FHoudiniEngineEditor::UnregisterForUndo() -{ - /* - if (GUnrealEd) - GUnrealEd->UnregisterForUndo(this); - */ -} - -void -FHoudiniEngineEditor::RegisterPlacementModeExtensions() -{ - // Load custom houdini tools - /* - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - check(HoudiniRuntimeSettings); - - if (HoudiniRuntimeSettings->bHidePlacementModeHoudiniTools) - return; - - FPlacementCategoryInfo Info( - LOCTEXT("HoudiniCategoryName", "Houdini Engine"), - "HoudiniEngine", - TEXT("PMHoudiniEngine"), - 25 - ); - Info.CustomGenerator = []() -> TSharedRef { return SNew(SHoudiniToolPalette); }; - - IPlacementModeModule::Get().RegisterPlacementCategory(Info); - */ -} - -void -FHoudiniEngineEditor::UnregisterPlacementModeExtensions() -{ - /* - if (IPlacementModeModule::IsAvailable()) - { - IPlacementModeModule::Get().UnregisterPlacementCategory("HoudiniEngine"); - } - - HoudiniTools.Empty(); - */ -} - -void -FHoudiniEngineEditor::InitializeWidgetResource() -{ - // Choice labels for all the input types - //TArray> InputTypeChoiceLabels; - InputTypeChoiceLabels.Reset(); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Asset)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Landscape)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::World)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Skeletal)))); - - BlueprintInputTypeChoiceLabels.Reset(); - BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); - BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); - - // Choice labels for all Houdini curve types - HoudiniCurveTypeChoiceLabels.Reset(); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Polygon)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Nurbs)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Bezier)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Points)))); - - // Choice labels for all Houdini curve methods - HoudiniCurveMethodChoiceLabels.Reset(); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::CVs)))); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Breakpoints)))); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Freehand)))); - - // Choice labels for all Houdini ramp parameter interpolation methods - HoudiniParameterRampInterpolationLabels.Reset(); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CONSTANT)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::LINEAR)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CATMULL_ROM)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::MONOTONE_CUBIC)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BEZIER)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BSPLINE)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::HERMITE)))); - - // Choice labels for all Houdini curve output export types - HoudiniCurveOutputExportTypeLabels.Reset(); - HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Unreal Spline")))); - HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Houdini Spline")))); - - // Choice labels for all Unreal curve output curve types - //(for temporary, we need to figure out a way to access the output curve's info later) - UnrealCurveOutputCurveTypeLabels.Reset(); - UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Linear")))); - UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Curve")))); - - // Option labels for all landscape outputs bake options - HoudiniLandscapeOutputBakeOptionLabels.Reset(); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Current Level")))); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Image")))); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To New World")))); - - // Option labels for Houdini Engine PDG bake options - HoudiniEnginePDGBakeTypeOptionLabels.Reset(); - HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); - HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); - - // Option labels for Houdini Engine bake options - HoudiniEngineBakeTypeOptionLabels.Reset(); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToFoliage)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToWorldOutliner)))); - - // Option labels for Houdini Engine PDG bake options - HoudiniEnginePDGBakeSelectionOptionLabels.Reset(); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::All)))); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNetwork)))); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNode)))); - - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Reset(); - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::ReplaceExistingAssets)))); - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::CreateNewAssets)))); - - - static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); - - // Houdini Logo Brush - FString Icon128FilePath = IconsDir + TEXT("icon_houdini_logo_128"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) - { - const FName BrushName(*Icon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Logo Brush - FString HEIcon128FilePath = IconsDir + TEXT("icon_hengine_logo_128"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) - { - const FName BrushName(*HEIcon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Banner - FString HoudiniEngineUIIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_banner_d.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Rebuild Icon Brush - FString HoudiniEngineUIRebuildIconFilePath = IconsDir + TEXT("rebuild_all16x16.png"); - //FString HoudiniEngineUIRebuildIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_reload_icon.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRebuildIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIRebuildIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIRebuildIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Recook Icon Brush - //FString HoudiniEngineUIRecookIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_recook_icon.png"); - FString HoudiniEngineUIRecookIconFilePath = IconsDir + TEXT("cook_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRecookIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIRecookIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIRecookIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Reset Parameters Icon Brush - //FString HoudiniEngineUIResetParametersIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_resetparameters_icon.png"); - FString HoudiniEngineUIResetParametersIconFilePath = IconsDir + TEXT("reset_parameters16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIResetParametersIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIResetParametersIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIResetParametersIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Bake - FString BakeIconFilePath = IconsDir + TEXT("bake_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*BakeIconFilePath)) - { - const FName BrushName(*BakeIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIBakeIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // CookLog - FString CookLogIconFilePath = IconsDir + TEXT("cook_log16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*CookLogIconFilePath)) - { - const FName BrushName(*CookLogIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUICookLogIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // AssetHelp - FString AssetHelpIconFilePath = IconsDir + TEXT("asset_help16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*AssetHelpIconFilePath)) - { - const FName BrushName(*AssetHelpIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIAssetHelpIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - - // PDG Asset Link - // PDG - FString PDGIconFilePath = IconsDir + TEXT("pdg_link16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGIconFilePath)) - { - const FName BrushName(*PDGIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Cancel - // PDGCancel - FString PDGCancelIconFilePath = IconsDir + TEXT("pdg_cancel16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGCancelIconFilePath)) - { - const FName BrushName(*PDGCancelIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGCancelIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Dirty All - // PDGDirtyAll - FString PDGDirtyAllIconFilePath = IconsDir + TEXT("pdg_dirty_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyAllIconFilePath)) - { - const FName BrushName(*PDGDirtyAllIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGDirtyAllIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Dirty Node - // PDGDirtyNode - FString PDGDirtyNodeIconFilePath = IconsDir + TEXT("pdg_dirty_node16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyNodeIconFilePath)) - { - const FName BrushName(*PDGDirtyNodeIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGDirtyNodeIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Pause - // PDGReset - FString PDGPauseIconFilePath = IconsDir + TEXT("pdg_pause16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGPauseIconFilePath)) - { - const FName BrushName(*PDGPauseIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGPauseIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Reset - // PDGReset - FString PDGResetIconFilePath = IconsDir + TEXT("pdg_reset16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGResetIconFilePath)) - { - const FName BrushName(*PDGResetIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGResetIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Refresh - // PDGRefresh - FString PDGRefreshIconFilePath = IconsDir + TEXT("pdg_refresh16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGRefreshIconFilePath)) - { - const FName BrushName(*PDGRefreshIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGRefreshIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } -} - -void -FHoudiniEngineEditor::AddLevelViewportMenuExtender() -{ - FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked("LevelEditor"); - auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); - - MenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender)); - LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle(); -} - -void -FHoudiniEngineEditor::RemoveLevelViewportMenuExtender() -{ - if (LevelViewportExtenderHandle.IsValid()) - { - FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr("LevelEditor"); - if (LevelEditorModule) - { - typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType; - LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll( - [=](const DelegateType& In) { return In.GetHandle() == LevelViewportExtenderHandle; }); - } - } -} - -TSharedRef -FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef CommandList, const TArray InActors) -{ - TSharedRef Extender = MakeShareable(new FExtender); - - // Build an array of the HoudiniAssets corresponding to the selected actors - TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets; - TArray< TWeakObjectPtr< AHoudiniAssetActor > > HoudiniAssetActors; - for (auto CurrentActor : InActors) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(CurrentActor); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - HoudiniAssetActors.Add(HoudiniAssetActor); - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssets.AddUnique(HoudiniAssetComponent->GetHoudiniAsset()); - } - - if (HoudiniAssets.Num() > 0) - { - // Add the Asset menu extension - if (AssetTypeActions.Num() > 0) - { - // Add the menu extensions via our HoudiniAssetTypeActions - FAssetTypeActions_HoudiniAsset * HATA = static_cast(AssetTypeActions[0].Get()); - if (HATA) - Extender = HATA->AddLevelEditorMenuExtenders(HoudiniAssets); - } - } - - if (HoudiniAssetActors.Num() > 0) - { - // Add some actor menu extensions - FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); - TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); - Extender->AddMenuExtension( - "ActorControl", - EExtensionHook::After, - LevelEditorCommandBindings, - FMenuExtensionDelegate::CreateLambda([this, HoudiniAssetActors](FMenuBuilder& MenuBuilder) - { - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recentre", "Recentre selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecentreTooltip", "Recentres the selected Houdini Asset Actors pivots to their input/cooked static mesh average centre."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecentreSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recook", "Recook selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecookTooltip", "Forces a recook on the selected Houdini Asset Actors."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._CookSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Rebuild", "Rebuild selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RebuildTooltip", "Rebuilds selected Houdini Asset Actors in the current level."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RebuildSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshes", "Refine Houdini Proxy Meshes"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshesTooltip", "Build and replace Houdini Proxy Meshes with Static Meshes."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RefineSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - }) - ); - } - - return Extender; -} - -void -FHoudiniEngineEditor::RegisterConsoleCommands() -{ - IConsoleManager &ConsoleManager = IConsoleManager::Get(); - const TCHAR *CommandName = TEXT("HoudiniEngine.RefineHoudiniProxyMeshesToStaticMeshes"); - IConsoleCommand *Command = ConsoleManager.RegisterConsoleCommand( - CommandName, - TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), - FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); - if (Command) - { - ConsoleCommands.Add(Command); - } - else - { - HOUDINI_LOG_ERROR(TEXT("Failed to register the '%s' console command."), CommandName); - } -} - -void -FHoudiniEngineEditor::UnregisterConsoleCommands() -{ - IConsoleManager &ConsoleManager = IConsoleManager::Get(); - for (IConsoleCommand *Command : ConsoleCommands) - { - if (Command) - { - ConsoleManager.UnregisterConsoleObject(Command); - } - } - ConsoleCommands.Empty(); -} - -void -FHoudiniEngineEditor::RegisterEditorDelegates() -{ - PreSaveWorldEditorDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([](uint32 SaveFlags, UWorld* World) - { - // Skip if this is a game world or an autosave, only refine meshes when the user manually saves - if (!World->IsGameWorld() && (SaveFlags & ESaveFlags::SAVE_FromAutosave) == 0) - { - const bool bSelectedOnly = false; - const bool bSilent = false; - const bool bRefineAll = false; - const bool bOnPreSaveWorld = true; - UWorld * const OnPreSaveWorld = World; - const bool bOnPreBeginPIE = false; - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); - } - - if (!World->IsGameWorld()) - { - UWorld * const OnPreSaveWorld = World; - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - - // Save all dirty temporary cook package OnPostSaveWorld - OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([OnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) - { - if (OnPreSaveWorld && OnPreSaveWorld != InWorld) - return; - - FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(InWorld); - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - }); - } - }); - - PreBeginPIEEditorDelegateHandle = FEditorDelegates::PreBeginPIE.AddLambda([](const bool bIsSimulating) - { - const bool bSelectedOnly = false; - const bool bSilent = false; - const bool bRefineAll = false; - const bool bOnPreSaveWorld = false; - UWorld * const OnPreSaveWorld = nullptr; - const bool bOnPreBeginPIE = true; - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); - }); - - OnDeleteActorsBegin = FEditorDelegates::OnDeleteActorsBegin.AddLambda([this](){ this->HandleOnDeleteActorsBegin(); }); - OnDeleteActorsEnd = FEditorDelegates::OnDeleteActorsEnd.AddLambda([this](){ this-> HandleOnDeleteActorsEnd(); }); -} - -void -FHoudiniEngineEditor::UnregisterEditorDelegates() -{ - if (PreSaveWorldEditorDelegateHandle.IsValid()) - FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldEditorDelegateHandle); - - if (PreBeginPIEEditorDelegateHandle.IsValid()) - FEditorDelegates::PreSaveWorld.Remove(PreBeginPIEEditorDelegateHandle); - - if (OnDeleteActorsBegin.IsValid()) - FEditorDelegates::OnDeleteActorsBegin.Remove(OnDeleteActorsBegin); - - if (OnDeleteActorsEnd.IsValid()) - FEditorDelegates::OnDeleteActorsEnd.Remove(OnDeleteActorsEnd); -} - -FString -FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption) -{ - FString Str; - switch (BakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - Str = "Actor"; - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - Str = "Blueprint"; - break; - - case EHoudiniEngineBakeOption::ToFoliage: - Str = "Foliage"; - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - Str = "World Outliner"; - break; - } - - return Str; -} - -FString -FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption) -{ - FString Str; - switch (BakeOption) - { - case EPDGBakeSelectionOption::All: - Str = "All Outputs"; - break; - - case EPDGBakeSelectionOption::SelectedNetwork: - Str = "Selected Network (All Outputs)"; - break; - - case EPDGBakeSelectionOption::SelectedNode: - Str = "Selected Node (All Outputs)"; - break; - } - - return Str; -} - -FString -FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption) -{ - FString Str; - switch (InOption) - { - case EPDGBakePackageReplaceModeOption::CreateNewAssets: - Str = "Create New Assets"; - break; - case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: - Str = "Replace Existing Assets"; - break; - } - - return Str; -} - -const EHoudiniEngineBakeOption -FHoudiniEngineEditor::StringToHoudiniEngineBakeOption(const FString & InString) -{ - if (InString == "Actor") - return EHoudiniEngineBakeOption::ToActor; - - if (InString == "Blueprint") - return EHoudiniEngineBakeOption::ToBlueprint; - - if (InString == "Foliage") - return EHoudiniEngineBakeOption::ToFoliage; - - if (InString == "World Outliner") - return EHoudiniEngineBakeOption::ToWorldOutliner; - - return EHoudiniEngineBakeOption::ToActor; -} - -const EPDGBakeSelectionOption -FHoudiniEngineEditor::StringToPDGBakeSelectionOption(const FString& InString) -{ - if (InString == "All Outputs") - return EPDGBakeSelectionOption::All; - - if (InString == "Selected Network (All Outputs)") - return EPDGBakeSelectionOption::SelectedNetwork; - - if (InString == "Selected Node (All Outputs)") - return EPDGBakeSelectionOption::SelectedNode; - - return EPDGBakeSelectionOption::All; -} - -const EPDGBakePackageReplaceModeOption -FHoudiniEngineEditor::StringToPDGBakePackageReplaceModeOption(const FString & InString) -{ - if (InString == "Create New Assets") - return EPDGBakePackageReplaceModeOption::CreateNewAssets; - - if (InString == "Replace Existing Assets") - return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; -} - -const EPackageReplaceMode -FHoudiniEngineEditor::PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode) -{ - EPackageReplaceMode Mode; - switch (InReplaceMode) - { - case EPDGBakePackageReplaceModeOption::CreateNewAssets: - Mode = EPackageReplaceMode::CreateNewAssets; - break; - case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: - Mode = EPackageReplaceMode::ReplaceExistingAssets; - break; - default: - { - Mode = FHoudiniPackageParams::GetDefaultReplaceMode(); - HOUDINI_LOG_WARNING(TEXT("Unsupported value for EPDGBakePackageReplaceModeOption %d, using " - "FHoudiniPackageParams::GetDefaultReplaceMode() for resulting EPackageReplaceMode %d"), - InReplaceMode, Mode); - } - } - - return Mode; -} - -void -FHoudiniEngineEditor::HandleOnDeleteActorsBegin() -{ - if (!GEditor) - return; - - TArray AssetActorsWithTempPDGOutput; - // Iterate over all selected actors - for(FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) - { - AActor* SelectedActor = Cast(*It); - if (IsValid(SelectedActor)) - { - // If the class is a AHoudiniAssetActor check if it has temporary PDG outputs - AHoudiniAssetActor* AssetActor = Cast(SelectedActor); - if (IsValid(AssetActor)) - { - UHoudiniPDGAssetLink* AssetLink = AssetActor->GetPDGAssetLink(); - if (IsValid(AssetLink) && AssetLink->HasTemporaryOutputs()) - { - AssetActorsWithTempPDGOutput.Add(AssetActor); - } - } - } - } - - if (AssetActorsWithTempPDGOutput.Num() > 0) - { - const FText DialogTitle = LOCTEXT( - "PDGAssetLink_DeleteWithTemporaryOutputs_Title", - "Warning: PDG Asset Link(s) With Temporary Outputs"); - const EAppReturnType::Type Choice = FMessageDialog::Open( - EAppMsgType::YesNo, - EAppReturnType::No, - LOCTEXT( - "PDGAssetLink_DeleteWithTemporaryOutputs", - "One or more PDG Asset Links in the selection still have temporary outputs. Are you sure you want to " - "delete these PDG Asset Links and their actors?"), - &DialogTitle); - - const bool bKeepAssetLinkActors = (Choice == EAppReturnType::No); - for (AHoudiniAssetActor* AssetActor : AssetActorsWithTempPDGOutput) - { - if (bKeepAssetLinkActors) - { - GEditor->SelectActor(AssetActor, false, false); - ActorsToReselectOnDeleteActorsEnd.Add(AssetActor); - } - } - } -} - -void -FHoudiniEngineEditor::HandleOnDeleteActorsEnd() -{ - if (!GEditor) - return; - - for (AActor* Actor : ActorsToReselectOnDeleteActorsEnd) - { - if (IsValid(Actor)) - GEditor->SelectActor(Actor, true, false); - } - GEditor->NoteSelectionChange(); - ActorsToReselectOnDeleteActorsEnd.Empty(); -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineEditor.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetBroker.h" +#include "HoudiniAssetActorFactory.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniRuntimeSettingsDetails.h" +#include "HoudiniSplineComponentVisualizer.h" +#include "HoudiniHandleComponentVisualizer.h" +#include "AssetTypeActions_HoudiniAsset.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPackageParams.h" + +#include "Modules/ModuleManager.h" +#include "Interfaces/IPluginManager.h" +#include "HAL/PlatformFilemanager.h" +#include "Misc/MessageDialog.h" +#include "Misc/Paths.h" +#include "AssetRegistryModule.h" +#include "PropertyEditorModule.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "LevelEditor.h" +#include "Templates/SharedPointer.h" +#include "Framework/Application/SlateApplication.h" +#include "HAL/ConsoleManager.h" +#include "Editor/UnrealEdEngine.h" +#include "Editor.h" +#include "UnrealEdGlobals.h" +#include "Engine/Selection.h" +#include "Widgets/Input/SCheckBox.h" +#include "Logging/LogMacros.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHoudiniEngineEditor, HoudiniEngineEditor); +DEFINE_LOG_CATEGORY(LogHoudiniEngineEditor); + +FHoudiniEngineEditor * +FHoudiniEngineEditor::HoudiniEngineEditorInstance = nullptr; + +FHoudiniEngineEditor & +FHoudiniEngineEditor::Get() +{ + return *HoudiniEngineEditorInstance; +} + +bool +FHoudiniEngineEditor::IsInitialized() +{ + return FHoudiniEngineEditor::HoudiniEngineEditorInstance != nullptr; +} + +FHoudiniEngineEditor::FHoudiniEngineEditor() +{ +} + +void FHoudiniEngineEditor::StartupModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine Editor module.")); + + // Create style set. + FHoudiniEngineStyle::Initialize(); + + // Initilizes various resources used by our editor UI widgets + InitializeWidgetResource(); + + // Register asset type actions. + RegisterAssetTypeActions(); + + // Register asset brokers. + RegisterAssetBrokers(); + + // Register component visualizers. + RegisterComponentVisualizers(); + + // Register detail presenters. + RegisterDetails(); + + // Register actor factories. + RegisterActorFactories(); + + // Extends the file menu. + ExtendMenu(); + + // Extend the World Outliner Menu + AddLevelViewportMenuExtender(); + + // Adds the custom console commands + RegisterConsoleCommands(); + + // Register global undo / redo callbacks. + //RegisterForUndo(); + + //RegisterPlacementModeExtensions(); + + // Register for any FEditorDelegates that we are interested in, such as + // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds + RegisterEditorDelegates(); + + // Store the instance. + FHoudiniEngineEditor::HoudiniEngineEditorInstance = this; + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module startup complete.")); +} + +void FHoudiniEngineEditor::ShutdownModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine Editor module.")); + + // Deregister editor delegates + UnregisterEditorDelegates(); + + // Deregister console commands + UnregisterConsoleCommands(); + + // Remove the level viewport Menu extender + RemoveLevelViewportMenuExtender(); + + // Unregister asset type actions. + UnregisterAssetTypeActions(); + + // Unregister asset brokers. + //UnregisterAssetBrokers(); + + // Unregister detail presenters. + UnregisterDetails(); + + // Unregister our component visualizers. + //UnregisterComponentVisualizers(); + + // Unregister global undo / redo callbacks. + //UnregisterForUndo(); + + //UnregisterPlacementModeExtensions(); + + // Unregister the styleset + FHoudiniEngineStyle::Shutdown(); + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module shutdown complete.")); +} + +FString +FHoudiniEngineEditor::GetHoudiniEnginePluginDir() +{ + FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(EnginePluginDir)) + return EnginePluginDir; + + FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(ProjectPluginDir)) + return ProjectPluginDir; + + TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); + FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; + if (FPaths::DirectoryExists(PluginBaseDir)) + return PluginBaseDir; + + HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); + + return EnginePluginDir; +} + +void +FHoudiniEngineEditor::RegisterDetails() +{ + FPropertyEditorModule & PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Register details presenter for our component type and runtime settings. + PropertyModule.RegisterCustomClassLayout( + TEXT("HoudiniAssetComponent"), + FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniAssetComponentDetails::MakeInstance)); + + PropertyModule.RegisterCustomClassLayout( + TEXT("HoudiniRuntimeSettings"), + FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniRuntimeSettingsDetails::MakeInstance)); +} + +void +FHoudiniEngineEditor::UnregisterDetails() +{ + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + FPropertyEditorModule & PropertyModule = + FModuleManager::LoadModuleChecked("PropertyEditor"); + + PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniAssetComponent")); + PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniRuntimeSettings")); + } +} + +void +FHoudiniEngineEditor::RegisterComponentVisualizers() +{ + if (GUnrealEd) + { + // Register Houdini spline visualizer + SplineComponentVisualizer = MakeShareable(new FHoudiniSplineComponentVisualizer); + if (SplineComponentVisualizer.IsValid()) + { + GUnrealEd->RegisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName(), SplineComponentVisualizer); + SplineComponentVisualizer->OnRegister(); + } + + // Register Houdini handle visualizer + HandleComponentVisualizer = MakeShareable(new FHoudiniHandleComponentVisualizer); + if (HandleComponentVisualizer.IsValid()) + { + GUnrealEd->RegisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName(), HandleComponentVisualizer); + HandleComponentVisualizer->OnRegister(); + } + } +} + +void +FHoudiniEngineEditor::UnregisterComponentVisualizers() +{ + if (GUnrealEd) + { + // Unregister Houdini spline visualizer + if(SplineComponentVisualizer.IsValid()) + GUnrealEd->UnregisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); + + // Unregister Houdini handle visualizer + if (HandleComponentVisualizer.IsValid()) + GUnrealEd->UnregisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + } +} + +void +FHoudiniEngineEditor::RegisterAssetTypeAction(IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action) +{ + AssetTools.RegisterAssetTypeActions(Action); + AssetTypeActions.Add(Action); +} + +void +FHoudiniEngineEditor::RegisterAssetTypeActions() +{ + // Create and register asset type actions for Houdini asset. + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools").Get(); + RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_HoudiniAsset())); +} + +void +FHoudiniEngineEditor::UnregisterAssetTypeActions() +{ + // Unregister asset type actions we have previously registered. + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >("AssetTools").Get(); + + for (int32 Index = 0; Index < AssetTypeActions.Num(); ++Index) + AssetTools.UnregisterAssetTypeActions(AssetTypeActions[Index].ToSharedRef()); + + AssetTypeActions.Empty(); + } +} + +void +FHoudiniEngineEditor::RegisterAssetBrokers() +{ + // Create and register broker for Houdini asset. + HoudiniAssetBroker = MakeShareable(new FHoudiniAssetBroker()); + FComponentAssetBrokerage::RegisterBroker( HoudiniAssetBroker, UHoudiniAssetComponent::StaticClass(), true, true ); +} + +void +FHoudiniEngineEditor::UnregisterAssetBrokers() +{ + if (UObjectInitialized()) + { + // Unregister broker. + FComponentAssetBrokerage::UnregisterBroker( HoudiniAssetBroker ); + } +} + +void +FHoudiniEngineEditor::RegisterActorFactories() +{ + if (GEditor) + { + UHoudiniAssetActorFactory * HoudiniAssetActorFactory = + NewObject< UHoudiniAssetActorFactory >(GetTransientPackage(), UHoudiniAssetActorFactory::StaticClass()); + + GEditor->ActorFactories.Add(HoudiniAssetActorFactory); + } +} + +void +FHoudiniEngineEditor::BindMenuCommands() +{ + HEngineCommands = MakeShareable(new FUICommandList); + + FHoudiniEngineCommands::Register(); + const FHoudiniEngineCommands& Commands = FHoudiniEngineCommands::Get(); + + // Session + + HEngineCommands->MapAction( + Commands._CreateSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CreateSession(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._ConnectSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ConnectSession(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._StopSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopSession(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RestartSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RestartSession(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OpenSessionSync, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OpenSessionSync(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); + + HEngineCommands->MapAction( + Commands._CloseSessionSync, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CloseSessionSync(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); + + HEngineCommands->MapAction( + Commands._ViewportSyncNone, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(0); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 0); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncHoudini, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(1); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 1); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncUnreal, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(2); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 2); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncBoth, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(3); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 3); }) + ); + + // PDG commandlet + HEngineCommands->MapAction( + Commands._IsPDGCommandletEnabled, + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::SetPDGCommandletEnabled(!FHoudiniEngineCommands::IsPDGCommandletEnabled()); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletEnabled(); }) + ); + + HEngineCommands->MapAction( + Commands._StartPDGCommandlet, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StartPDGCommandlet(); }), + FCanExecuteAction::CreateLambda([]() + { + return FHoudiniEngineCommands::IsPDGCommandletEnabled() && !FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); + }) + ); + + HEngineCommands->MapAction( + Commands._StopPDGCommandlet, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopPDGCommandlet(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); })); + + // Plugin + + HEngineCommands->MapAction( + Commands._InstallInfo, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowInstallInfo(); }), + FCanExecuteAction::CreateLambda([]() { return false; })); + + HEngineCommands->MapAction( + Commands._PluginSettings, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowPluginSettings(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Files + + HEngineCommands->MapAction( + Commands._OpenInHoudini, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::OpenInHoudini(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._SaveHIPFile, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::SaveHIPFile(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._CleanUpTempFolder, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CleanUpTempFolder(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Help and support + + HEngineCommands->MapAction( + Commands._ReportBug, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::ReportBug(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OnlineDoc, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineDocumentation(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OnlineForum, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineForum(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Actions + + HEngineCommands->MapAction( + Commands._CookAll, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookAllAssets(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._CookSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RebuildAll, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildAllAssets(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RebuildSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._BakeAll, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::BakeAllAssets(); }), + FCanExecuteAction::CreateLambda([](){ return true; })); + + HEngineCommands->MapAction( + Commands._BakeSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::BakeSelection(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._RefineAll, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._RefineSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._PauseAssetCooking, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::PauseAssetCooking(); }), + FCanExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::IsSessionValid(); }), + FIsActionChecked::CreateLambda([](){ return FHoudiniEngineCommands::IsAssetCookingPaused(); })); + + // Non menu command (used for shortcuts only) + + // Append the command to the editor module + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >("LevelEditor"); + LevelEditorModule.GetGlobalLevelEditorActions()->Append(HEngineCommands.ToSharedRef()); +} + +void +FHoudiniEngineEditor::ExtendMenu() +{ + if (IsRunningCommandlet()) + return; + + // We need to add/bind the UI Commands to their functions first + BindMenuCommands(); + + MainMenuExtender = MakeShareable(new FExtender); + + // Extend File menu, we will add Houdini section. + MainMenuExtender->AddMenuExtension( + "FileLoadAndSave", + EExtensionHook::After, + HEngineCommands, + FMenuExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniFileMenuExtension)); + + MainMenuExtender->AddMenuBarExtension( + "Edit", + EExtensionHook::After, + HEngineCommands, + FMenuBarExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniEditorMenu)); + + // Add our menu extender + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); +} + +void +FHoudiniEngineEditor::AddHoudiniFileMenuExtension(FMenuBuilder & MenuBuilder) +{ + MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); + + // Icons used by the commands are defined in the HoudiniEngineStyle + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + + MenuBuilder.EndSection(); +} + +void +FHoudiniEngineEditor::AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder) +{ + // View + MenuBarBuilder.AddPullDownMenu( + LOCTEXT("HoudiniLabel", "Houdini Engine"), + LOCTEXT("HoudiniMenu_ToolTip", "Open the Houdini Engine menu"), + FNewMenuDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniMainMenuExtension), + "View"); +} + +void +FHoudiniEngineEditor::AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder) +{ + /* + MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); + // Icons used by the commands are defined in the HoudiniEngineStyle + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + MenuBuilder.EndSection(); + */ + + MenuBuilder.BeginSection("Session", LOCTEXT("SessionLabel", "Session")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CreateSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ConnectSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenSessionSync); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CloseSessionSync); + + // Viewport sync menu + struct FLocalMenuBuilder + { + static void FillViewportSyncMenu(FMenuBuilder& InSubMenuBuilder) + { + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncNone); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncHoudini); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncUnreal); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncBoth); + } + }; + + MenuBuilder.AddSubMenu( + LOCTEXT("SyncViewport", "Sync Viewport"), + LOCTEXT("SyncViewport_ToolTip", "Sync Viewport"), + FNewMenuDelegate::CreateStatic(&FLocalMenuBuilder::FillViewportSyncMenu), + false, + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._SyncViewport")); + + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("PDG", LOCTEXT("PDGLabel", "PDG")); + struct FLocalPDGMenuBuilder + { + static void FillPDGMenu(FMenuBuilder& InSubMenuBuilder) + { + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._IsPDGCommandletEnabled); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StartPDGCommandlet); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopPDGCommandlet); + } + }; + MenuBuilder.AddSubMenu( + LOCTEXT("PDGSubMenu", "Work Item Import Settings"), + LOCTEXT("PDGSubmenu_ToolTip", "PDG Work Item Import Settings"), + FNewMenuDelegate::CreateStatic(&FLocalPDGMenuBuilder::FillPDGMenu), + false, + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.PDGLink")); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Plugin", LOCTEXT("PluginLabel", "Plugin")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._InstallInfo); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PluginSettings); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("File", LOCTEXT("FileLabel", "File")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Help", LOCTEXT("HelpLabel", "Help And Support")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineDoc); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineForum); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Actions", LOCTEXT("ActionsLabel", "Actions")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + + + MenuBuilder.EndSection(); +} + +void +FHoudiniEngineEditor::RegisterForUndo() +{ + /* + if (GUnrealEd) + GUnrealEd->RegisterForUndo(this); + */ +} + +void +FHoudiniEngineEditor::UnregisterForUndo() +{ + /* + if (GUnrealEd) + GUnrealEd->UnregisterForUndo(this); + */ +} + +void +FHoudiniEngineEditor::RegisterPlacementModeExtensions() +{ + // Load custom houdini tools + /* + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check(HoudiniRuntimeSettings); + + if (HoudiniRuntimeSettings->bHidePlacementModeHoudiniTools) + return; + + FPlacementCategoryInfo Info( + LOCTEXT("HoudiniCategoryName", "Houdini Engine"), + "HoudiniEngine", + TEXT("PMHoudiniEngine"), + 25 + ); + Info.CustomGenerator = []() -> TSharedRef { return SNew(SHoudiniToolPalette); }; + + IPlacementModeModule::Get().RegisterPlacementCategory(Info); + */ +} + +void +FHoudiniEngineEditor::UnregisterPlacementModeExtensions() +{ + /* + if (IPlacementModeModule::IsAvailable()) + { + IPlacementModeModule::Get().UnregisterPlacementCategory("HoudiniEngine"); + } + + HoudiniTools.Empty(); + */ +} + +void +FHoudiniEngineEditor::InitializeWidgetResource() +{ + // Choice labels for all the input types + //TArray> InputTypeChoiceLabels; + InputTypeChoiceLabels.Reset(); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Asset)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Landscape)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::World)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Skeletal)))); + + BlueprintInputTypeChoiceLabels.Reset(); + BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); + BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); + + // Choice labels for all Houdini curve types + HoudiniCurveTypeChoiceLabels.Reset(); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Polygon)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Nurbs)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Bezier)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Points)))); + + // Choice labels for all Houdini curve methods + HoudiniCurveMethodChoiceLabels.Reset(); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::CVs)))); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Breakpoints)))); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Freehand)))); + + // Choice labels for all Houdini ramp parameter interpolation methods + HoudiniParameterRampInterpolationLabels.Reset(); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CONSTANT)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::LINEAR)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CATMULL_ROM)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::MONOTONE_CUBIC)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BEZIER)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BSPLINE)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::HERMITE)))); + + // Choice labels for all Houdini curve output export types + HoudiniCurveOutputExportTypeLabels.Reset(); + HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Unreal Spline")))); + HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Houdini Spline")))); + + // Choice labels for all Unreal curve output curve types + //(for temporary, we need to figure out a way to access the output curve's info later) + UnrealCurveOutputCurveTypeLabels.Reset(); + UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Linear")))); + UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Curve")))); + + // Option labels for all landscape outputs bake options + HoudiniLandscapeOutputBakeOptionLabels.Reset(); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Current Level")))); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Image")))); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To New World")))); + + // Option labels for Houdini Engine PDG bake options + HoudiniEnginePDGBakeTypeOptionLabels.Reset(); + HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); + HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); + + // Option labels for Houdini Engine bake options + HoudiniEngineBakeTypeOptionLabels.Reset(); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToFoliage)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToWorldOutliner)))); + + // Option labels for Houdini Engine PDG bake options + HoudiniEnginePDGBakeSelectionOptionLabels.Reset(); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::All)))); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNetwork)))); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNode)))); + + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Reset(); + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::ReplaceExistingAssets)))); + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::CreateNewAssets)))); + + + static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); + + // Houdini Logo Brush + FString Icon128FilePath = IconsDir + TEXT("icon_houdini_logo_128"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) + { + const FName BrushName(*Icon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Logo Brush + FString HEIcon128FilePath = IconsDir + TEXT("icon_hengine_logo_128"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) + { + const FName BrushName(*HEIcon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Banner + FString HoudiniEngineUIIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_banner_d.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Rebuild Icon Brush + FString HoudiniEngineUIRebuildIconFilePath = IconsDir + TEXT("rebuild_all16x16.png"); + //FString HoudiniEngineUIRebuildIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_reload_icon.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRebuildIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIRebuildIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIRebuildIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Recook Icon Brush + //FString HoudiniEngineUIRecookIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_recook_icon.png"); + FString HoudiniEngineUIRecookIconFilePath = IconsDir + TEXT("cook_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRecookIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIRecookIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIRecookIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Reset Parameters Icon Brush + //FString HoudiniEngineUIResetParametersIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_resetparameters_icon.png"); + FString HoudiniEngineUIResetParametersIconFilePath = IconsDir + TEXT("reset_parameters16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIResetParametersIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIResetParametersIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIResetParametersIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Bake + FString BakeIconFilePath = IconsDir + TEXT("bake_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*BakeIconFilePath)) + { + const FName BrushName(*BakeIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIBakeIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // CookLog + FString CookLogIconFilePath = IconsDir + TEXT("cook_log16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*CookLogIconFilePath)) + { + const FName BrushName(*CookLogIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUICookLogIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // AssetHelp + FString AssetHelpIconFilePath = IconsDir + TEXT("asset_help16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*AssetHelpIconFilePath)) + { + const FName BrushName(*AssetHelpIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIAssetHelpIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + + // PDG Asset Link + // PDG + FString PDGIconFilePath = IconsDir + TEXT("pdg_link16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGIconFilePath)) + { + const FName BrushName(*PDGIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Cancel + // PDGCancel + FString PDGCancelIconFilePath = IconsDir + TEXT("pdg_cancel16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGCancelIconFilePath)) + { + const FName BrushName(*PDGCancelIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGCancelIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Dirty All + // PDGDirtyAll + FString PDGDirtyAllIconFilePath = IconsDir + TEXT("pdg_dirty_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyAllIconFilePath)) + { + const FName BrushName(*PDGDirtyAllIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGDirtyAllIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Dirty Node + // PDGDirtyNode + FString PDGDirtyNodeIconFilePath = IconsDir + TEXT("pdg_dirty_node16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyNodeIconFilePath)) + { + const FName BrushName(*PDGDirtyNodeIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGDirtyNodeIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Pause + // PDGReset + FString PDGPauseIconFilePath = IconsDir + TEXT("pdg_pause16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGPauseIconFilePath)) + { + const FName BrushName(*PDGPauseIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGPauseIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Reset + // PDGReset + FString PDGResetIconFilePath = IconsDir + TEXT("pdg_reset16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGResetIconFilePath)) + { + const FName BrushName(*PDGResetIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGResetIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Refresh + // PDGRefresh + FString PDGRefreshIconFilePath = IconsDir + TEXT("pdg_refresh16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGRefreshIconFilePath)) + { + const FName BrushName(*PDGRefreshIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGRefreshIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } +} + +void +FHoudiniEngineEditor::AddLevelViewportMenuExtender() +{ + FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked("LevelEditor"); + auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); + + MenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender)); + LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle(); +} + +void +FHoudiniEngineEditor::RemoveLevelViewportMenuExtender() +{ + if (LevelViewportExtenderHandle.IsValid()) + { + FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr("LevelEditor"); + if (LevelEditorModule) + { + typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType; + LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll( + [=](const DelegateType& In) { return In.GetHandle() == LevelViewportExtenderHandle; }); + } + } +} + +TSharedRef +FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef CommandList, const TArray InActors) +{ + TSharedRef Extender = MakeShareable(new FExtender); + + // Build an array of the HoudiniAssets corresponding to the selected actors + TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets; + TArray< TWeakObjectPtr< AHoudiniAssetActor > > HoudiniAssetActors; + for (auto CurrentActor : InActors) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(CurrentActor); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + HoudiniAssetActors.Add(HoudiniAssetActor); + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssets.AddUnique(HoudiniAssetComponent->GetHoudiniAsset()); + } + + if (HoudiniAssets.Num() > 0) + { + // Add the Asset menu extension + if (AssetTypeActions.Num() > 0) + { + // Add the menu extensions via our HoudiniAssetTypeActions + FAssetTypeActions_HoudiniAsset * HATA = static_cast(AssetTypeActions[0].Get()); + if (HATA) + Extender = HATA->AddLevelEditorMenuExtenders(HoudiniAssets); + } + } + + if (HoudiniAssetActors.Num() > 0) + { + // Add some actor menu extensions + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); + Extender->AddMenuExtension( + "ActorControl", + EExtensionHook::After, + LevelEditorCommandBindings, + FMenuExtensionDelegate::CreateLambda([this, HoudiniAssetActors](FMenuBuilder& MenuBuilder) + { + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recentre", "Recentre selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecentreTooltip", "Recentres the selected Houdini Asset Actors pivots to their input/cooked static mesh average centre."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecentreSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recook", "Recook selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecookTooltip", "Forces a recook on the selected Houdini Asset Actors."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._CookSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Rebuild", "Rebuild selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RebuildTooltip", "Rebuilds selected Houdini Asset Actors in the current level."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RebuildSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshes", "Refine Houdini Proxy Meshes"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshesTooltip", "Build and replace Houdini Proxy Meshes with Static Meshes."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RefineSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + }) + ); + } + + return Extender; +} + +void +FHoudiniEngineEditor::RegisterConsoleCommands() +{ + // Register corresponding console commands + static FAutoConsoleCommand CCmdOpen = FAutoConsoleCommand( + TEXT("Houdini.Open"), + TEXT("Open the scene in Houdini."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::OpenInHoudini)); + + static FAutoConsoleCommand CCmdSave = FAutoConsoleCommand( + TEXT("Houdini.Save"), + TEXT("Save the current Houdini scene to a hip file."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::SaveHIPFile)); + + static FAutoConsoleCommand CCmdBake = FAutoConsoleCommand( + TEXT("Houdini.BakeAll"), + TEXT("Bakes and replaces with blueprints all Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::BakeAllAssets)); + + static FAutoConsoleCommand CCmdClean = FAutoConsoleCommand( + TEXT("Houdini.Clean"), + TEXT("Cleans up unused/unreferenced Houdini Engine temporary files."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::CleanUpTempFolder)); + + static FAutoConsoleCommand CCmdPause = FAutoConsoleCommand( + TEXT("Houdini.Pause"), + TEXT("Pauses Houdini Engine Asset cooking."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::PauseAssetCooking)); + + // Additional console only commands + static FAutoConsoleCommand CCmdCookAll = FAutoConsoleCommand( + TEXT("Houdini.CookAll"), + TEXT("Re-cooks all Houdini Engine Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RecookAllAssets)); + + static FAutoConsoleCommand CCmdRebuildAll = FAutoConsoleCommand( + TEXT("Houdini.RebuildAll"), + TEXT("Rebuilds all Houdini Engine Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RebuildAllAssets)); + + static FAutoConsoleCommand CCmdCookSelec = FAutoConsoleCommand( + TEXT("Houdini.Cook"), + TEXT("Re-cooks selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RecookSelection)); + + static FAutoConsoleCommand CCmdRebuildSelec = FAutoConsoleCommand( + TEXT("Houdini.Rebuild"), + TEXT("Rebuilds selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RebuildSelection)); + + static FAutoConsoleCommand CCmdBakeSelec = FAutoConsoleCommand( + TEXT("Houdini.Bake"), + TEXT("Bakes and replaces with blueprints selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::BakeSelection)); + + static FAutoConsoleCommand CCmdRestartSession = FAutoConsoleCommand( + TEXT("Houdini.RestartSession"), + TEXT("Restart the current Houdini Session."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RestartSession)); + + /* + IConsoleManager &ConsoleManager = IConsoleManager::Get(); + const TCHAR *CommandName = TEXT("HoudiniEngine.RefineHoudiniProxyMeshesToStaticMeshes"); + IConsoleCommand *Command = ConsoleManager.RegisterConsoleCommand( + CommandName, + TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), + FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); + if (Command) + { + ConsoleCommands.Add(Command); + } + else + { + HOUDINI_LOG_ERROR(TEXT("Failed to register the '%s' console command."), CommandName); + } + */ + + static FAutoConsoleCommand CCmdRefine = FAutoConsoleCommand( + TEXT("Houdini.RefineAll"), + TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), + FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); + + static FAutoConsoleCommand CCmdOpenSessionSync = FAutoConsoleCommand( + TEXT("Houdini.OpenSessionSync"), + TEXT("Stops the current session, opens Houdini and automnatically start and connect a Session Sync."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::OpenSessionSync)); +} + +void +FHoudiniEngineEditor::UnregisterConsoleCommands() +{ + IConsoleManager &ConsoleManager = IConsoleManager::Get(); + for (IConsoleCommand *Command : ConsoleCommands) + { + if (Command) + { + ConsoleManager.UnregisterConsoleObject(Command); + } + } + ConsoleCommands.Empty(); +} + +void +FHoudiniEngineEditor::RegisterEditorDelegates() +{ + PreSaveWorldEditorDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([](uint32 SaveFlags, UWorld* World) + { + // Skip if this is a game world or an autosave, only refine meshes when the user manually saves + if (!World->IsGameWorld() && (SaveFlags & ESaveFlags::SAVE_FromAutosave) == 0) + { + const bool bSelectedOnly = false; + const bool bSilent = false; + const bool bRefineAll = false; + const bool bOnPreSaveWorld = true; + UWorld * const OnPreSaveWorld = World; + const bool bOnPreBeginPIE = false; + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); + } + + if (!World->IsGameWorld()) + { + UWorld * const OnPreSaveWorld = World; + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + + // Save all dirty temporary cook package OnPostSaveWorld + OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([OnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) + { + if (OnPreSaveWorld && OnPreSaveWorld != InWorld) + return; + + FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(InWorld); + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + }); + } + }); + + PreBeginPIEEditorDelegateHandle = FEditorDelegates::PreBeginPIE.AddLambda([](const bool bIsSimulating) + { + const bool bSelectedOnly = false; + const bool bSilent = false; + const bool bRefineAll = false; + const bool bOnPreSaveWorld = false; + UWorld * const OnPreSaveWorld = nullptr; + const bool bOnPreBeginPIE = true; + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); + }); + + OnDeleteActorsBegin = FEditorDelegates::OnDeleteActorsBegin.AddLambda([this](){ this->HandleOnDeleteActorsBegin(); }); + OnDeleteActorsEnd = FEditorDelegates::OnDeleteActorsEnd.AddLambda([this](){ this-> HandleOnDeleteActorsEnd(); }); +} + +void +FHoudiniEngineEditor::UnregisterEditorDelegates() +{ + if (PreSaveWorldEditorDelegateHandle.IsValid()) + FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldEditorDelegateHandle); + + if (PreBeginPIEEditorDelegateHandle.IsValid()) + FEditorDelegates::PreSaveWorld.Remove(PreBeginPIEEditorDelegateHandle); + + if (OnDeleteActorsBegin.IsValid()) + FEditorDelegates::OnDeleteActorsBegin.Remove(OnDeleteActorsBegin); + + if (OnDeleteActorsEnd.IsValid()) + FEditorDelegates::OnDeleteActorsEnd.Remove(OnDeleteActorsEnd); +} + +FString +FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption) +{ + FString Str; + switch (BakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + Str = "Actor"; + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + Str = "Blueprint"; + break; + + case EHoudiniEngineBakeOption::ToFoliage: + Str = "Foliage"; + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + Str = "World Outliner"; + break; + } + + return Str; +} + +FString +FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption) +{ + FString Str; + switch (BakeOption) + { + case EPDGBakeSelectionOption::All: + Str = "All Outputs"; + break; + + case EPDGBakeSelectionOption::SelectedNetwork: + Str = "Selected Network (All Outputs)"; + break; + + case EPDGBakeSelectionOption::SelectedNode: + Str = "Selected Node (All Outputs)"; + break; + } + + return Str; +} + +FString +FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption) +{ + FString Str; + switch (InOption) + { + case EPDGBakePackageReplaceModeOption::CreateNewAssets: + Str = "Create New Assets"; + break; + case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: + Str = "Replace Existing Assets"; + break; + } + + return Str; +} + +const EHoudiniEngineBakeOption +FHoudiniEngineEditor::StringToHoudiniEngineBakeOption(const FString & InString) +{ + if (InString == "Actor") + return EHoudiniEngineBakeOption::ToActor; + + if (InString == "Blueprint") + return EHoudiniEngineBakeOption::ToBlueprint; + + if (InString == "Foliage") + return EHoudiniEngineBakeOption::ToFoliage; + + if (InString == "World Outliner") + return EHoudiniEngineBakeOption::ToWorldOutliner; + + return EHoudiniEngineBakeOption::ToActor; +} + +const EPDGBakeSelectionOption +FHoudiniEngineEditor::StringToPDGBakeSelectionOption(const FString& InString) +{ + if (InString == "All Outputs") + return EPDGBakeSelectionOption::All; + + if (InString == "Selected Network (All Outputs)") + return EPDGBakeSelectionOption::SelectedNetwork; + + if (InString == "Selected Node (All Outputs)") + return EPDGBakeSelectionOption::SelectedNode; + + return EPDGBakeSelectionOption::All; +} + +const EPDGBakePackageReplaceModeOption +FHoudiniEngineEditor::StringToPDGBakePackageReplaceModeOption(const FString & InString) +{ + if (InString == "Create New Assets") + return EPDGBakePackageReplaceModeOption::CreateNewAssets; + + if (InString == "Replace Existing Assets") + return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; +} + +const EPackageReplaceMode +FHoudiniEngineEditor::PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode) +{ + EPackageReplaceMode Mode; + switch (InReplaceMode) + { + case EPDGBakePackageReplaceModeOption::CreateNewAssets: + Mode = EPackageReplaceMode::CreateNewAssets; + break; + case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: + Mode = EPackageReplaceMode::ReplaceExistingAssets; + break; + default: + { + Mode = FHoudiniPackageParams::GetDefaultReplaceMode(); + HOUDINI_LOG_WARNING(TEXT("Unsupported value for EPDGBakePackageReplaceModeOption %d, using " + "FHoudiniPackageParams::GetDefaultReplaceMode() for resulting EPackageReplaceMode %d"), + InReplaceMode, Mode); + } + } + + return Mode; +} + +void +FHoudiniEngineEditor::HandleOnDeleteActorsBegin() +{ + if (!GEditor) + return; + + TArray AssetActorsWithTempPDGOutput; + // Iterate over all selected actors + for(FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) + { + AActor* SelectedActor = Cast(*It); + if (IsValid(SelectedActor)) + { + // If the class is a AHoudiniAssetActor check if it has temporary PDG outputs + AHoudiniAssetActor* AssetActor = Cast(SelectedActor); + if (IsValid(AssetActor)) + { + UHoudiniPDGAssetLink* AssetLink = AssetActor->GetPDGAssetLink(); + if (IsValid(AssetLink) && AssetLink->HasTemporaryOutputs()) + { + AssetActorsWithTempPDGOutput.Add(AssetActor); + } + } + } + } + + if (AssetActorsWithTempPDGOutput.Num() > 0) + { + const FText DialogTitle = LOCTEXT( + "PDGAssetLink_DeleteWithTemporaryOutputs_Title", + "Warning: PDG Asset Link(s) With Temporary Outputs"); + const EAppReturnType::Type Choice = FMessageDialog::Open( + EAppMsgType::YesNo, + EAppReturnType::No, + LOCTEXT( + "PDGAssetLink_DeleteWithTemporaryOutputs", + "One or more PDG Asset Links in the selection still have temporary outputs. Are you sure you want to " + "delete these PDG Asset Links and their actors?"), + &DialogTitle); + + const bool bKeepAssetLinkActors = (Choice == EAppReturnType::No); + for (AHoudiniAssetActor* AssetActor : AssetActorsWithTempPDGOutput) + { + if (bKeepAssetLinkActors) + { + GEditor->SelectActor(AssetActor, false, false); + ActorsToReselectOnDeleteActorsEnd.Add(AssetActor); + } + } + } +} + +void +FHoudiniEngineEditor::HandleOnDeleteActorsEnd() +{ + if (!GEditor) + return; + + for (AActor* Actor : ActorsToReselectOnDeleteActorsEnd) + { + if (IsValid(Actor)) + GEditor->SelectActor(Actor, true, false); + } + GEditor->NoteSelectionChange(); + ActorsToReselectOnDeleteActorsEnd.Empty(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h index ad08dc277..451d781dd 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h @@ -1,347 +1,350 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "IHoudiniEngineEditor.h" -#include "HoudiniInputTypes.h" - -#include "CoreTypes.h" -#include "Templates/SharedPointer.h" - -class FExtender; -class IAssetTools; -class IAssetTypeActions; -class IComponentAssetBroker; -class FComponentVisualizer; -class FMenuBuilder; -class FMenuBarBuilder; -class FUICommandList; -class AActor; - -struct IConsoleCommand; -struct FSlateDynamicImageBrush; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod: int8; -enum class EHoudiniLandscapeOutputBakeType: uint8; -enum class EHoudiniEngineBakeOption : uint8; -enum class EPDGBakeSelectionOption : uint8; -enum class EPDGBakePackageReplaceModeOption : uint8; -enum class EPackageReplaceMode : int8; - -class HOUDINIENGINEEDITOR_API FHoudiniEngineEditor : public IHoudiniEngineEditor -{ - public: - FHoudiniEngineEditor(); - - // IModuleInterface methods. - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // IHoudiniEngineEditor methods - virtual void RegisterComponentVisualizers() override; - virtual void UnregisterComponentVisualizers() override; - virtual void RegisterDetails() override; - virtual void UnregisterDetails() override; - virtual void RegisterAssetTypeActions() override; - virtual void UnregisterAssetTypeActions() override; - virtual void RegisterAssetBrokers() override; - virtual void UnregisterAssetBrokers() override; - virtual void RegisterActorFactories() override; - virtual void ExtendMenu() override; - virtual void RegisterForUndo() override; - virtual void UnregisterForUndo() override; - virtual void RegisterPlacementModeExtensions() override; - virtual void UnregisterPlacementModeExtensions() override; - - // Return singleton instance of Houdini Engine Editor, used internally. - static FHoudiniEngineEditor & Get(); - - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // Returns the plugin's directory - static FString GetHoudiniEnginePluginDir(); - - // Initializes Widget resources - void InitializeWidgetResource(); - - // Menu action to pause cooking for all Houdini Assets - void PauseAssetCooking(); - - // Helper delegate used to determine if PauseAssetCooking can be executed. - bool CanPauseAssetCooking(); - - // Helper delegate used to get the current state of PauseAssetCooking. - bool IsAssetCookingPaused(); - - // Returns a pointer to the input choice types - TArray>* GetInputTypeChoiceLabels() { return &InputTypeChoiceLabels; }; - TArray>* GetBlueprintInputTypeChoiceLabels() { return &BlueprintInputTypeChoiceLabels; }; - - // Returns a pointer to the Houdini curve types - TArray>* GetHoudiniCurveTypeChoiceLabels() { return &HoudiniCurveTypeChoiceLabels; }; - - // Returns a pointer to the Houdini curve methods - TArray>* GetHoudiniCurveMethodChoiceLabels() { return &HoudiniCurveMethodChoiceLabels; }; - - // Returns a pointer to the Houdini ramp parameter interpolation methods - TArray>* GetHoudiniParameterRampInterpolationMethodLabels() {return &HoudiniParameterRampInterpolationLabels;} - - // Returns a pointer to the Houdini curve output export types - TArray>* GetHoudiniCurveOutputExportTypeLabels() { return &HoudiniCurveOutputExportTypeLabels; }; - - TArray>* GetHoudiniLandscapeOutputBakeOptionsLabels() { return &HoudiniLandscapeOutputBakeOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Type labels - TArray>* GetHoudiniEnginePDGBakeTypeOptionsLabels() { return &HoudiniEnginePDGBakeTypeOptionLabels; }; - - // Returns a pointer to the Houdini Engine Bake Type labels - TArray>* GetHoudiniEngineBakeTypeOptionsLabels() { return &HoudiniEngineBakeTypeOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Target labels - TArray>* GetHoudiniEnginePDGBakeSelectionOptionsLabels() { return &HoudiniEnginePDGBakeSelectionOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Package Replace Mode labels - TArray>* GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels() { return &HoudiniEnginePDGBakePackageReplaceModeOptionLabels; }; - - // Returns a shared Ptr to the Houdini logo - TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; - TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; - - // Functions Return a shared Ptr to the Houdini Engine UI Icon - TSharedPtr GetHoudiniEngineUIIconBrush() const { return HoudiniEngineUIIconBrush; } - TSharedPtr GetHoudiniEngineUIRebuildIconBrush() const { return HoudiniEngineUIRebuildIconBrush; } - TSharedPtr GetHoudiniEngineUIRecookIconBrush() const { return HoudiniEngineUIRecookIconBrush; } - TSharedPtr GetHoudiniEngineUIResetParametersIconBrush() const { return HoudiniEngineUIResetParametersIconBrush; } - - TSharedPtr GetHoudiniEngineUIBakeIconBrush() const { return HoudiniEngineUIBakeIconBrush; } - TSharedPtr GetHoudiniEngineUICookLogIconBrush() const { return HoudiniEngineUICookLogIconBrush; } - TSharedPtr GetHoudiniEngineUIAssetHelpIconBrush() const { return HoudiniEngineUIAssetHelpIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGIconBrush() const { return HoudiniEngineUIPDGIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGCancelIconBrush() const { return HoudiniEngineUIPDGCancelIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGDirtyAllIconBrush() const { return HoudiniEngineUIPDGDirtyAllIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGDirtyNodeIconBrush() const { return HoudiniEngineUIPDGDirtyNodeIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGPauseIconBrush() const { return HoudiniEngineUIPDGPauseIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGResetIconBrush() const { return HoudiniEngineUIPDGResetIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGRefreshIconBrush() const { return HoudiniEngineUIPDGRefreshIconBrush; } - - // Returns a pointer to Unreal output curve types (for temporary) - TArray>* GetUnrealOutputCurveTypeLabels() { return &UnrealCurveOutputCurveTypeLabels; }; - - // returns string from Houdini Engine Bake Option - FString GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption); - - // returns string from Houdini Engine PDG Bake Target Option - FString GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption); - - // returns string from PDG package replace mode option - FString GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption); - - // Return HoudiniEngineBakeOption from FString - const EHoudiniEngineBakeOption StringToHoudiniEngineBakeOption(const FString & InString); - - // Return EPDGBakeSelectionOption from FString - const EPDGBakeSelectionOption StringToPDGBakeSelectionOption(const FString& InString); - - // Return EPDGBakePackageReplaceModeOption from FString - const EPDGBakePackageReplaceModeOption StringToPDGBakePackageReplaceModeOption(const FString & InString); - - // Convert EPDGBakePackageReplaceModeOption to EPackageReplaceMode - // TODO: perhaps EPackageReplaceMode can be moved to HoudiniEngineRuntime to avoid having both - // TODO: EPDGBakePackageReplaceModeOption and EPackageReplaceMode? - const EPackageReplaceMode PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode); - - // Get the reference of the radio button folder circle point arrays reference - TArray & GetHoudiniParameterRadioButtonPointsOuter() { return HoudiniParameterRadioButtonPointsOuter; }; - TArray & GetHoudiniParameterRadioButtonPointsInner() { return HoudiniParameterRadioButtonPointsInner; }; - - // Gets the PostSaveWorldOnceHandle - FDelegateHandle& GetOnPostSaveWorldOnceHandle() { return PostSaveWorldOnceHandle; } - - protected: - - // Binds the commands used by the menus - void BindMenuCommands(); - - // Register AssetType action. - void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef< IAssetTypeActions > Action); - - // Add menu extension for our module. - void AddHoudiniFileMenuExtension(FMenuBuilder& MenuBuilder); - - // Add the Houdini Engine editor menu - void AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder); - - // Add menu extension for our module. - void AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder); - - // Adds the custom Houdini Engine commands to the world outliner context menu - void AddLevelViewportMenuExtender(); - - // Removes the custom Houdini Engine commands to the world outliner context menu - void RemoveLevelViewportMenuExtender(); - - // Returns all the custom Houdini Engine commands for the world outliner context menu - TSharedRef GetLevelViewportContextMenuExtender( - const TSharedRef CommandList, const TArray InActors); - - // Register all console commands provided by this module - void RegisterConsoleCommands(); - - // Unregister all registered console commands provided by this module - void UnregisterConsoleCommands(); - - // Register for any FEditorDelegates that we are interested in, such as - // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds - void RegisterEditorDelegates(); - - // Deregister editor delegates - void UnregisterEditorDelegates(); - - // Process the OnDeleteActorsBegin call received from FEditorDelegates. - // Check if any AHoudiniAssetActors with PDG links are selected for deletion. If so, - // check if these still have temporary outputs and give the user to option to skip - // deleting the ones with temporary output. - void HandleOnDeleteActorsBegin(); - - // Re-select AHoudiniAssetActors that were deselected (to avoid deletion) by HandleOnDeleteActorsBegin - void HandleOnDeleteActorsEnd(); - - private: - - // Singleton instance of Houdini Engine Editor. - static FHoudiniEngineEditor * HoudiniEngineEditorInstance; - - // AssetType actions associated with Houdini asset. - TArray> AssetTypeActions; - - // Broker associated with Houdini asset. - TSharedPtr HoudiniAssetBroker; - - // Widget resources: Input Type combo box labels - TArray> InputTypeChoiceLabels; - TArray> BlueprintInputTypeChoiceLabels; - - // Widget resources: Houdini Curve Type combo box labels - TArray> HoudiniCurveTypeChoiceLabels; - - // Widget resources: Houdini Curve Method combo box labels - TArray> HoudiniCurveMethodChoiceLabels; - - // Widget resources: Houdini Ramp Interpolation method combo box labels - TArray> HoudiniParameterRampInterpolationLabels; - - // Widget resources: Houdini Curve Output type labels - TArray> HoudiniCurveOutputExportTypeLabels; - - // Widget resources: Unreal Curve type labels (for temporary, we need to figure out a way to access the output curve's info later) - TArray> UnrealCurveOutputCurveTypeLabels; - - // Widget resources: Landscape output Bake type labels - TArray> HoudiniLandscapeOutputBakeOptionLabels; - - // Widget resources: PDG Bake type labels - TArray> HoudiniEnginePDGBakeTypeOptionLabels; - - // Widget resources: Bake type labels - TArray> HoudiniEngineBakeTypeOptionLabels; - - // Widget resources: PDG Bake target labels - TArray> HoudiniEnginePDGBakeSelectionOptionLabels; - - // Widget resources: PDG Bake package replace mode labels - TArray> HoudiniEnginePDGBakePackageReplaceModeOptionLabels; - - // List of UI commands used by the various menus - TSharedPtr HEngineCommands; - - // Houdini logo brush. - TSharedPtr HoudiniLogoBrush; - // Houdini Engine logo brush - TSharedPtr HoudiniEngineLogoBrush; - - // houdini Engine UI Brushes - TSharedPtr HoudiniEngineUIIconBrush; - TSharedPtr HoudiniEngineUIRebuildIconBrush; - TSharedPtr HoudiniEngineUIRecookIconBrush; - TSharedPtr HoudiniEngineUIResetParametersIconBrush; - - TSharedPtr HoudiniEngineUIBakeIconBrush; - TSharedPtr HoudiniEngineUICookLogIconBrush; - TSharedPtr HoudiniEngineUIAssetHelpIconBrush; - TSharedPtr HoudiniEngineUIPDGIconBrush; - TSharedPtr HoudiniEngineUIPDGCancelIconBrush; - TSharedPtr HoudiniEngineUIPDGDirtyAllIconBrush; - TSharedPtr HoudiniEngineUIPDGDirtyNodeIconBrush; - TSharedPtr HoudiniEngineUIPDGPauseIconBrush; - TSharedPtr HoudiniEngineUIPDGResetIconBrush; - TSharedPtr HoudiniEngineUIPDGRefreshIconBrush; - - // The extender to pass to the level editor to extend it's File menu. - TSharedPtr MainMenuExtender; - - // The extender to pass to the level editor to extend it's Main menu. - //TSharedPtr FileMenuExtender; - - // DelegateHandle for the viewport's context menu extender - FDelegateHandle LevelViewportExtenderHandle; - - // SplineComponentVisualizer - TSharedPtr SplineComponentVisualizer; - - TSharedPtr HandleComponentVisualizer; - - // Array of HoudiniEngine console commands - TArray ConsoleCommands; - - // Delegate handle for the PreSaveWorld editor delegate - FDelegateHandle PreSaveWorldEditorDelegateHandle; - - // Delegate handle for the PostSaveWorld editor delegate: this - // is bound on PreSaveWorld with specific captures and then unbound - // by itself - FDelegateHandle PostSaveWorldOnceHandle; - - // Delegate handle for the PreBeginPIE editor delegate - FDelegateHandle PreBeginPIEEditorDelegateHandle; - - // Delegate handle for OnDeleteActorsBegin - FDelegateHandle OnDeleteActorsBegin; - - // Delegate handle for OnDeleteActorsEnd - FDelegateHandle OnDeleteActorsEnd; - - // List of actors that HandleOnDeleteActorsBegin marked to _not_ be deleted. This - // is used to re-select these actors in HandleOnDeleteActorsEnd. - TArray ActorsToReselectOnDeleteActorsEnd; - - // Cache the points of radio button folder circle points to avoid huge amount of repeat computation. - // (Computing points are time consuming since it uses trigonometric functions) - TArray HoudiniParameterRadioButtonPointsOuter; - TArray HoudiniParameterRadioButtonPointsInner; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "IHoudiniEngineEditor.h" +#include "HoudiniInputTypes.h" + +#include "CoreTypes.h" +#include "Templates/SharedPointer.h" +#include "Framework/Commands/UICommandList.h" +#include "Brushes/SlateDynamicImageBrush.h" + + +class FExtender; +class IAssetTools; +class IAssetTypeActions; +class IComponentAssetBroker; +class FComponentVisualizer; +class FMenuBuilder; +class FMenuBarBuilder; +class FUICommandList; +class AActor; + +struct IConsoleCommand; +struct FSlateDynamicImageBrush; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod: int8; +enum class EHoudiniLandscapeOutputBakeType: uint8; +enum class EHoudiniEngineBakeOption : uint8; +enum class EPDGBakeSelectionOption : uint8; +enum class EPDGBakePackageReplaceModeOption : uint8; +enum class EPackageReplaceMode : int8; + +class HOUDINIENGINEEDITOR_API FHoudiniEngineEditor : public IHoudiniEngineEditor +{ + public: + FHoudiniEngineEditor(); + + // IModuleInterface methods. + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // IHoudiniEngineEditor methods + virtual void RegisterComponentVisualizers() override; + virtual void UnregisterComponentVisualizers() override; + virtual void RegisterDetails() override; + virtual void UnregisterDetails() override; + virtual void RegisterAssetTypeActions() override; + virtual void UnregisterAssetTypeActions() override; + virtual void RegisterAssetBrokers() override; + virtual void UnregisterAssetBrokers() override; + virtual void RegisterActorFactories() override; + virtual void ExtendMenu() override; + virtual void RegisterForUndo() override; + virtual void UnregisterForUndo() override; + virtual void RegisterPlacementModeExtensions() override; + virtual void UnregisterPlacementModeExtensions() override; + + // Return singleton instance of Houdini Engine Editor, used internally. + static FHoudiniEngineEditor & Get(); + + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // Returns the plugin's directory + static FString GetHoudiniEnginePluginDir(); + + // Initializes Widget resources + void InitializeWidgetResource(); + + // Menu action to pause cooking for all Houdini Assets + void PauseAssetCooking(); + + // Helper delegate used to determine if PauseAssetCooking can be executed. + bool CanPauseAssetCooking(); + + // Helper delegate used to get the current state of PauseAssetCooking. + bool IsAssetCookingPaused(); + + // Returns a pointer to the input choice types + TArray>* GetInputTypeChoiceLabels() { return &InputTypeChoiceLabels; }; + TArray>* GetBlueprintInputTypeChoiceLabels() { return &BlueprintInputTypeChoiceLabels; }; + + // Returns a pointer to the Houdini curve types + TArray>* GetHoudiniCurveTypeChoiceLabels() { return &HoudiniCurveTypeChoiceLabels; }; + + // Returns a pointer to the Houdini curve methods + TArray>* GetHoudiniCurveMethodChoiceLabels() { return &HoudiniCurveMethodChoiceLabels; }; + + // Returns a pointer to the Houdini ramp parameter interpolation methods + TArray>* GetHoudiniParameterRampInterpolationMethodLabels() {return &HoudiniParameterRampInterpolationLabels;} + + // Returns a pointer to the Houdini curve output export types + TArray>* GetHoudiniCurveOutputExportTypeLabels() { return &HoudiniCurveOutputExportTypeLabels; }; + + TArray>* GetHoudiniLandscapeOutputBakeOptionsLabels() { return &HoudiniLandscapeOutputBakeOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Type labels + TArray>* GetHoudiniEnginePDGBakeTypeOptionsLabels() { return &HoudiniEnginePDGBakeTypeOptionLabels; }; + + // Returns a pointer to the Houdini Engine Bake Type labels + TArray>* GetHoudiniEngineBakeTypeOptionsLabels() { return &HoudiniEngineBakeTypeOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Target labels + TArray>* GetHoudiniEnginePDGBakeSelectionOptionsLabels() { return &HoudiniEnginePDGBakeSelectionOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Package Replace Mode labels + TArray>* GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels() { return &HoudiniEnginePDGBakePackageReplaceModeOptionLabels; }; + + // Returns a shared Ptr to the Houdini logo + TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; + TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; + + // Functions Return a shared Ptr to the Houdini Engine UI Icon + TSharedPtr GetHoudiniEngineUIIconBrush() const { return HoudiniEngineUIIconBrush; } + TSharedPtr GetHoudiniEngineUIRebuildIconBrush() const { return HoudiniEngineUIRebuildIconBrush; } + TSharedPtr GetHoudiniEngineUIRecookIconBrush() const { return HoudiniEngineUIRecookIconBrush; } + TSharedPtr GetHoudiniEngineUIResetParametersIconBrush() const { return HoudiniEngineUIResetParametersIconBrush; } + + TSharedPtr GetHoudiniEngineUIBakeIconBrush() const { return HoudiniEngineUIBakeIconBrush; } + TSharedPtr GetHoudiniEngineUICookLogIconBrush() const { return HoudiniEngineUICookLogIconBrush; } + TSharedPtr GetHoudiniEngineUIAssetHelpIconBrush() const { return HoudiniEngineUIAssetHelpIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGIconBrush() const { return HoudiniEngineUIPDGIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGCancelIconBrush() const { return HoudiniEngineUIPDGCancelIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGDirtyAllIconBrush() const { return HoudiniEngineUIPDGDirtyAllIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGDirtyNodeIconBrush() const { return HoudiniEngineUIPDGDirtyNodeIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGPauseIconBrush() const { return HoudiniEngineUIPDGPauseIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGResetIconBrush() const { return HoudiniEngineUIPDGResetIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGRefreshIconBrush() const { return HoudiniEngineUIPDGRefreshIconBrush; } + + // Returns a pointer to Unreal output curve types (for temporary) + TArray>* GetUnrealOutputCurveTypeLabels() { return &UnrealCurveOutputCurveTypeLabels; }; + + // returns string from Houdini Engine Bake Option + FString GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption); + + // returns string from Houdini Engine PDG Bake Target Option + FString GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption); + + // returns string from PDG package replace mode option + FString GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption); + + // Return HoudiniEngineBakeOption from FString + const EHoudiniEngineBakeOption StringToHoudiniEngineBakeOption(const FString & InString); + + // Return EPDGBakeSelectionOption from FString + const EPDGBakeSelectionOption StringToPDGBakeSelectionOption(const FString& InString); + + // Return EPDGBakePackageReplaceModeOption from FString + const EPDGBakePackageReplaceModeOption StringToPDGBakePackageReplaceModeOption(const FString & InString); + + // Convert EPDGBakePackageReplaceModeOption to EPackageReplaceMode + // TODO: perhaps EPackageReplaceMode can be moved to HoudiniEngineRuntime to avoid having both + // TODO: EPDGBakePackageReplaceModeOption and EPackageReplaceMode? + const EPackageReplaceMode PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode); + + // Get the reference of the radio button folder circle point arrays reference + TArray & GetHoudiniParameterRadioButtonPointsOuter() { return HoudiniParameterRadioButtonPointsOuter; }; + TArray & GetHoudiniParameterRadioButtonPointsInner() { return HoudiniParameterRadioButtonPointsInner; }; + + // Gets the PostSaveWorldOnceHandle + FDelegateHandle& GetOnPostSaveWorldOnceHandle() { return PostSaveWorldOnceHandle; } + + protected: + + // Binds the commands used by the menus + void BindMenuCommands(); + + // Register AssetType action. + void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef< IAssetTypeActions > Action); + + // Add menu extension for our module. + void AddHoudiniFileMenuExtension(FMenuBuilder& MenuBuilder); + + // Add the Houdini Engine editor menu + void AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder); + + // Add menu extension for our module. + void AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder); + + // Adds the custom Houdini Engine commands to the world outliner context menu + void AddLevelViewportMenuExtender(); + + // Removes the custom Houdini Engine commands to the world outliner context menu + void RemoveLevelViewportMenuExtender(); + + // Returns all the custom Houdini Engine commands for the world outliner context menu + TSharedRef GetLevelViewportContextMenuExtender( + const TSharedRef CommandList, const TArray InActors); + + // Register all console commands provided by this module + void RegisterConsoleCommands(); + + // Unregister all registered console commands provided by this module + void UnregisterConsoleCommands(); + + // Register for any FEditorDelegates that we are interested in, such as + // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds + void RegisterEditorDelegates(); + + // Deregister editor delegates + void UnregisterEditorDelegates(); + + // Process the OnDeleteActorsBegin call received from FEditorDelegates. + // Check if any AHoudiniAssetActors with PDG links are selected for deletion. If so, + // check if these still have temporary outputs and give the user to option to skip + // deleting the ones with temporary output. + void HandleOnDeleteActorsBegin(); + + // Re-select AHoudiniAssetActors that were deselected (to avoid deletion) by HandleOnDeleteActorsBegin + void HandleOnDeleteActorsEnd(); + + private: + + // Singleton instance of Houdini Engine Editor. + static FHoudiniEngineEditor * HoudiniEngineEditorInstance; + + // AssetType actions associated with Houdini asset. + TArray> AssetTypeActions; + + // Broker associated with Houdini asset. + TSharedPtr HoudiniAssetBroker; + + // Widget resources: Input Type combo box labels + TArray> InputTypeChoiceLabels; + TArray> BlueprintInputTypeChoiceLabels; + + // Widget resources: Houdini Curve Type combo box labels + TArray> HoudiniCurveTypeChoiceLabels; + + // Widget resources: Houdini Curve Method combo box labels + TArray> HoudiniCurveMethodChoiceLabels; + + // Widget resources: Houdini Ramp Interpolation method combo box labels + TArray> HoudiniParameterRampInterpolationLabels; + + // Widget resources: Houdini Curve Output type labels + TArray> HoudiniCurveOutputExportTypeLabels; + + // Widget resources: Unreal Curve type labels (for temporary, we need to figure out a way to access the output curve's info later) + TArray> UnrealCurveOutputCurveTypeLabels; + + // Widget resources: Landscape output Bake type labels + TArray> HoudiniLandscapeOutputBakeOptionLabels; + + // Widget resources: PDG Bake type labels + TArray> HoudiniEnginePDGBakeTypeOptionLabels; + + // Widget resources: Bake type labels + TArray> HoudiniEngineBakeTypeOptionLabels; + + // Widget resources: PDG Bake target labels + TArray> HoudiniEnginePDGBakeSelectionOptionLabels; + + // Widget resources: PDG Bake package replace mode labels + TArray> HoudiniEnginePDGBakePackageReplaceModeOptionLabels; + + // List of UI commands used by the various menus + TSharedPtr HEngineCommands; + + // Houdini logo brush. + TSharedPtr HoudiniLogoBrush; + // Houdini Engine logo brush + TSharedPtr HoudiniEngineLogoBrush; + + // houdini Engine UI Brushes + TSharedPtr HoudiniEngineUIIconBrush; + TSharedPtr HoudiniEngineUIRebuildIconBrush; + TSharedPtr HoudiniEngineUIRecookIconBrush; + TSharedPtr HoudiniEngineUIResetParametersIconBrush; + + TSharedPtr HoudiniEngineUIBakeIconBrush; + TSharedPtr HoudiniEngineUICookLogIconBrush; + TSharedPtr HoudiniEngineUIAssetHelpIconBrush; + TSharedPtr HoudiniEngineUIPDGIconBrush; + TSharedPtr HoudiniEngineUIPDGCancelIconBrush; + TSharedPtr HoudiniEngineUIPDGDirtyAllIconBrush; + TSharedPtr HoudiniEngineUIPDGDirtyNodeIconBrush; + TSharedPtr HoudiniEngineUIPDGPauseIconBrush; + TSharedPtr HoudiniEngineUIPDGResetIconBrush; + TSharedPtr HoudiniEngineUIPDGRefreshIconBrush; + + // The extender to pass to the level editor to extend it's File menu. + TSharedPtr MainMenuExtender; + + // The extender to pass to the level editor to extend it's Main menu. + //TSharedPtr FileMenuExtender; + + // DelegateHandle for the viewport's context menu extender + FDelegateHandle LevelViewportExtenderHandle; + + // SplineComponentVisualizer + TSharedPtr SplineComponentVisualizer; + + TSharedPtr HandleComponentVisualizer; + + // Array of HoudiniEngine console commands + TArray ConsoleCommands; + + // Delegate handle for the PreSaveWorld editor delegate + FDelegateHandle PreSaveWorldEditorDelegateHandle; + + // Delegate handle for the PostSaveWorld editor delegate: this + // is bound on PreSaveWorld with specific captures and then unbound + // by itself + FDelegateHandle PostSaveWorldOnceHandle; + + // Delegate handle for the PreBeginPIE editor delegate + FDelegateHandle PreBeginPIEEditorDelegateHandle; + + // Delegate handle for OnDeleteActorsBegin + FDelegateHandle OnDeleteActorsBegin; + + // Delegate handle for OnDeleteActorsEnd + FDelegateHandle OnDeleteActorsEnd; + + // List of actors that HandleOnDeleteActorsBegin marked to _not_ be deleted. This + // is used to re-select these actors in HandleOnDeleteActorsEnd. + TArray ActorsToReselectOnDeleteActorsEnd; + + // Cache the points of radio button folder circle points to avoid huge amount of repeat computation. + // (Computing points are time consuming since it uses trigonometric functions) + TArray HoudiniParameterRadioButtonPointsOuter; + TArray HoudiniParameterRadioButtonPointsInner; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp deleted file mode 100644 index 8a63839cc..000000000 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h deleted file mode 100644 index 293eeb8ed..000000000 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorLocalization.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h index d9ebe1684..b6765739d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h @@ -1,149 +1,149 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#define HOUDINI_ENGINE_EDITOR - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "Editor.h" - -// Details panel desired sizes. -#define HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH 270 -#define HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH 310 -#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH 350 -#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_FULL_WIDGET_WIDTH 400 - - // URL used for bug reporting. -#define HAPI_UNREAL_BUG_REPORT_URL TEXT("https://www.sidefx.com/bugs/submit/") -#define HAPI_UNREAL_ONLINE_DOC_URL TEXT("https://www.sidefx.com/docs/unreal/") -#define HAPI_UNREAL_ONLINE_FORUM_URL TEXT("https://www.sidefx.com/forum/51/") - - -// -// Parameter UI constants -// - -// Constants for parameter UI indentation - -// Change this constant to change the overall indentation width -#define INDENTATION_UNIT_WIDTH 20.0f -// Do not change this width unless the folder triangle arrow is customized. -#define NON_FOLDER_OFFSET_WIDTH 22.0f - - -// Houdini parameter UI row margin heights -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON 8.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP 2.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR 5.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP 57.20f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3 7.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP 51.70f -#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER 2.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST 2.05f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY 62.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE 49.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM 36.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE 41.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE 68.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET 177.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE 235.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH 275.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD 219.35f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL 18.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3 7.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE 7.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL 4.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR 1.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING 6.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF 6.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE 7.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE 10.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE 5.60f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID 0.0f - - - -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER 4.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER 2.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER 2.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_MULTIPARMHEADER 57.20f -#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE_MULTIPARMHEADER 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER 2.60f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER 3.75f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_MULTIPARMHEADER 51.70f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE_MULTIPARMHEADER 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER 1.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER 58.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER 49.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER 40.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER 37.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER 68.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER 173.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER 231.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER 266.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER 215.05f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER 14.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER 2.57f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER 4.12f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER 4.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER 0.75f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER 1.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER 2.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER 2.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER 4.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER 10.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER 2.30f - -// Radio button UI constants -#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER 18 -#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER 8 -#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER 4.5f -#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER 1.0f -#define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X 7.0f +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#define HOUDINI_ENGINE_EDITOR + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Editor.h" + +// Details panel desired sizes. +#define HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH 270 +#define HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH 310 +#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH 350 +#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_FULL_WIDGET_WIDTH 400 + + // URL used for bug reporting. +#define HAPI_UNREAL_BUG_REPORT_URL TEXT("https://www.sidefx.com/bugs/submit/") +#define HAPI_UNREAL_ONLINE_DOC_URL TEXT("https://www.sidefx.com/docs/unreal/") +#define HAPI_UNREAL_ONLINE_FORUM_URL TEXT("https://www.sidefx.com/forum/51/") + + +// +// Parameter UI constants +// + +// Constants for parameter UI indentation + +// Change this constant to change the overall indentation width +#define INDENTATION_UNIT_WIDTH 20.0f +// Do not change this width unless the folder triangle arrow is customized. +#define NON_FOLDER_OFFSET_WIDTH 22.0f + + +// Houdini parameter UI row margin heights +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON 8.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP 2.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR 5.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP 57.20f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3 7.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP 51.70f +#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER 2.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST 2.05f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY 62.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE 49.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM 36.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE 41.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE 68.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET 177.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE 235.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH 275.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD 219.35f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL 18.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3 7.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE 7.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL 4.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR 1.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING 6.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF 6.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE 7.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE 10.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE 5.60f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID 0.0f + + + +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER 4.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER 2.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER 2.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_MULTIPARMHEADER 57.20f +#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE_MULTIPARMHEADER 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER 2.60f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER 3.75f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_MULTIPARMHEADER 51.70f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE_MULTIPARMHEADER 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER 1.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER 58.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER 49.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER 40.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER 37.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER 68.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER 173.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER 231.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER 266.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER 215.05f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER 14.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER 2.57f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER 4.12f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER 4.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER 0.75f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER 1.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER 2.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER 2.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER 4.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER 10.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER 2.30f + +// Radio button UI constants +#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER 18 +#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER 8 +#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER 4.5f +#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER 1.0f +#define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X 7.0f #define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_Y 13.2f \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp index 199a361e8..b76c6797f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp @@ -1,653 +1,653 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineEditorUtils.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniAssetActor.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniAsset.h" -#include "HoudiniOutput.h" -#include "HoudiniTool.h" - -#include "ContentBrowserModule.h" -#include "IContentBrowserSingleton.h" -#include "Engine/Selection.h" -#include "AssetRegistryModule.h" -#include "EditorViewportClient.h" -#include "ActorFactories/ActorFactory.h" -#include "FileHelpers.h" -#include "PropertyPathHelpers.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -int32 -FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection) -{ - ContentBrowserSelection.Empty(); - - // Get the current Content browser selection - FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >("ContentBrowser"); - TArray SelectedAssets; - ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets); - - for (int32 n = 0; n < SelectedAssets.Num(); n++) - { - // Get the current object - UObject * Object = SelectedAssets[n].GetAsset(); - if (!Object || Object->IsPendingKill()) - continue; - - // Only static meshes are supported - if (Object->GetClass() != UStaticMesh::StaticClass()) - continue; - - ContentBrowserSelection.Add(Object); - } - - return ContentBrowserSelection.Num(); -} - -int32 -FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly) -{ - WorldSelection.Empty(); - - // Get the current editor selection - if (GEditor) - { - USelection* SelectedActors = GEditor->GetSelectedActors(); - if (SelectedActors && !SelectedActors->IsPendingKill()) - { - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor * Actor = Cast< AActor >(*It); - if (!Actor && Actor->IsPendingKill()) - continue; - - // Ignore the SkySphere? - FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); - if (ClassName == TEXT("BP_Sky_Sphere_C")) - continue; - - // We're normally only selecting actors with StaticMeshComponents and SplineComponents - // Heightfields? Filter here or later? also allow HoudiniAssets? - WorldSelection.Add(Actor); - } - } - - } - - // If we only want Houdini Actors... - if (bHoudiniAssetActorsOnly) - { - // ... remove all but them - for (int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx--) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - WorldSelection.RemoveAt(Idx); - } - } - - return WorldSelection.Num(); -} - - -FString -FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType) -{ - FString HoudiniCurveTypeStr; - switch (HoudiniCurveType) - { - case EHoudiniCurveType::Invalid: - { - HoudiniCurveTypeStr = TEXT("Invalid"); - } - break; - - case EHoudiniCurveType::Polygon: - { - HoudiniCurveTypeStr = TEXT("Polygon"); - } - break; - - case EHoudiniCurveType::Nurbs: - { - HoudiniCurveTypeStr = TEXT("Nurbs"); - } - break; - - case EHoudiniCurveType::Bezier: - { - HoudiniCurveTypeStr = TEXT("Bezier"); - } - break; - - case EHoudiniCurveType::Points: - { - HoudiniCurveTypeStr = TEXT("Points"); - } - break; - } - - return HoudiniCurveTypeStr; -} - -FString -FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(const EHoudiniCurveMethod& CurveMethod) -{ - FString HoudiniCurveMethodStr; - switch (CurveMethod) - { - case EHoudiniCurveMethod::Invalid: - { - HoudiniCurveMethodStr = TEXT("Invalid"); - } - break; - case EHoudiniCurveMethod::CVs: - { - HoudiniCurveMethodStr = TEXT("CVs"); - } - break; - case EHoudiniCurveMethod::Breakpoints: - { - HoudiniCurveMethodStr = TEXT("Breakpoints"); - } - break; - case EHoudiniCurveMethod::Freehand: - { - HoudiniCurveMethodStr = TEXT("Freehand"); - } - break; - } - - return HoudiniCurveMethodStr; -} - - -FString -FHoudiniEngineEditorUtils::HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType) -{ - // Temporary, we need to figure out a way to access the output curve's info later - FString UnrealCurveType; - switch (HoudiniCurveType) - { - case EHoudiniCurveType::Polygon: - case EHoudiniCurveType::Points: - { - UnrealCurveType = TEXT("Linear"); - } - break; - - case EHoudiniCurveType::Nurbs: - case EHoudiniCurveType::Bezier: - { - UnrealCurveType = TEXT("Curve"); - } - break; - } - - return UnrealCurveType; -} - -FString -FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType) -{ - FString LandscapeBakeTypeString; - switch (LandscapeBakeType) - { - case EHoudiniLandscapeOutputBakeType::Detachment: - LandscapeBakeTypeString = "To Current Level"; - break; - - case EHoudiniLandscapeOutputBakeType::BakeToImage: - LandscapeBakeTypeString = "To Image"; - break; - - case EHoudiniLandscapeOutputBakeType::BakeToWorld: - LandscapeBakeTypeString = "To New World"; - break; - - } - - return LandscapeBakeTypeString; -} - - -FTransform -FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform() -{ - FTransform SpawnTransform = FTransform::Identity; - - // Get the editor viewport LookAt position to spawn the new objects - if (GEditor && GEditor->GetActiveViewport()) - { - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (ViewportClient) - { - // We need to toggle the orbit camera on to get the proper LookAtLocation to spawn our asset - ViewportClient->ToggleOrbitCamera(true); - SpawnTransform.SetLocation(ViewportClient->GetLookAtLocation()); - ViewportClient->ToggleOrbitCamera(false); - } - } - - return SpawnTransform; -} - -FTransform -FHoudiniEngineEditorUtils::GetMeanWorldSelectionTransform() -{ - FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); - - if (GEditor && (GEditor->GetSelectedActorCount() > 0)) - { - // Get the current Level Editor Selection - USelection* SelectedActors = GEditor->GetSelectedActors(); - - int NumAppliedTransform = 0; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor * Actor = Cast< AActor >(*It); - if (!Actor) - continue; - - // Just Ignore the SkySphere... - FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); - if (ClassName == TEXT("BP_Sky_Sphere_C")) - continue; - - FTransform CurrentTransform = Actor->GetTransform(); - - ALandscapeProxy* Landscape = Cast< ALandscapeProxy >(Actor); - if (Landscape) - { - // We need to offset Landscape's transform in X/Y to center them properly - FVector Origin, Extent; - Actor->GetActorBounds(false, Origin, Extent); - - // Use the origin's XY Position - FVector Location = CurrentTransform.GetLocation(); - Location.X = Origin.X; - Location.Y = Origin.Y; - CurrentTransform.SetLocation(Location); - } - - // Accumulate all the actor transforms... - if (NumAppliedTransform == 0) - SpawnTransform = CurrentTransform; - else - SpawnTransform.Accumulate(CurrentTransform); - - NumAppliedTransform++; - } - - if (NumAppliedTransform > 0) - { - // "Mean" all the accumulated Transform - SpawnTransform.SetScale3D(FVector::OneVector); - SpawnTransform.NormalizeRotation(); - - if (NumAppliedTransform > 1) - SpawnTransform.SetLocation(SpawnTransform.GetLocation() / (float)NumAppliedTransform); - } - } - - return SpawnTransform; -} - -void -FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType) -{ - if (!InHoudiniAsset) - return; - - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - // Load the asset - UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); - if (!AssetObj) - return; - - // Get the asset Factory - UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); - if (!Factory) - return; - - // Get the current Level Editor Selection - TArray WorldSelection; - int32 WorldSelectionCount = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection); - - // Get the current Content browser selection - TArray ContentBrowserSelection; - int32 ContentBrowserSelectionCount = FHoudiniEngineEditorUtils::GetContentBrowserSelection(ContentBrowserSelection); - - // By default, Content browser selection has a priority over the world selection - bool UseCBSelection = ContentBrowserSelectionCount > 0; - if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY) - UseCBSelection = true; - else if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY) - UseCBSelection = false; - - // Modify the created actor's position from the current editor world selection - FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); - if (WorldSelectionCount > 0) - { - // Get the "mean" transform of all the selected actors - SpawnTransform = GetMeanWorldSelectionTransform(); - } - - // If the current tool is a batch one, we'll need to create multiple instances of the HDA - if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH) - { - // Unselect the current selection to select the created actor after - if (GEditor) - GEditor->SelectNone(true, true, false); - - // An instance of the asset will be created for each selected object - for (int32 SelecIndex = 0; SelecIndex < (UseCBSelection ? ContentBrowserSelectionCount : WorldSelectionCount); SelecIndex++) - { - // Get the current object - UObject* CurrentSelectedObject = nullptr; - if (UseCBSelection && ContentBrowserSelection.IsValidIndex(SelecIndex)) - CurrentSelectedObject = ContentBrowserSelection[SelecIndex]; - - if (!UseCBSelection && WorldSelection.IsValidIndex(SelecIndex)) - CurrentSelectedObject = WorldSelection[SelecIndex]; - - if (!CurrentSelectedObject) - continue; - - // If it's an actor, use its Transform to spawn the HDA - AActor* CurrentSelectedActor = Cast(CurrentSelectedObject); - if (CurrentSelectedActor) - SpawnTransform = CurrentSelectedActor->GetTransform(); - else - SpawnTransform = GetDefaulAssetSpawnTransform(); - - // Create the actor for the HDA - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); - if (!CreatedActor) - continue; - - // Get the HoudiniAssetActor / HoudiniAssetComponent we just created - AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; - if (!HoudiniAssetActor) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent) - continue; - - // Create and set the input preset for this HDA and selected Object - TMap InputPreset; - InputPreset.Add(CurrentSelectedObject, 0); - HoudiniAssetComponent->SetInputPresets(InputPreset); - - // Select the Actor we just created - if (GEditor && GEditor->CanSelectActor(CreatedActor, true, false)) - GEditor->SelectActor(CreatedActor, true, true, true); - } - } - else - { - // We only need to create a single instance of the asset, regarding of the selection - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); - if (!CreatedActor) - return; - - // Generator tools don't need to preset their input - if (InType != EHoudiniToolType::HTOOLTYPE_GENERATOR) - { - TMap InputPresets; - AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor ? HoudiniAssetActor->GetHoudiniAssetComponent() : nullptr; - if (HoudiniAssetComponent) - { - // Build the preset map - int InputIndex = 0; - for (auto CurrentObject : (UseCBSelection ? ContentBrowserSelection : WorldSelection)) - { - if (!CurrentObject) - continue; - - if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI) - { - // The selection will be applied individually to multiple inputs - // (first object to first input, second object to second input etc...) - InputPresets.Add(CurrentObject, InputIndex++); - } - else - { - // All the selection will be applied to the asset's first input - InputPresets.Add(CurrentObject, 0); - } - } - - // Set the input preset on the HoudiniAssetComponent - if (InputPresets.Num() > 0) - HoudiniAssetComponent->SetInputPresets(InputPresets); - } - } - - // Select the Actor we just created - if (GEditor->CanSelectActor(CreatedActor, true, true)) - { - GEditor->SelectNone(true, true, false); - GEditor->SelectActor(CreatedActor, true, true, true); - } - } -} - -void -FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform) -{ - if (!InHoudiniAsset) - return; - - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - // Load the asset - UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); - if (!AssetObj) - return; - - // Get the asset Factory - UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); - if (!Factory) - return; - - // We only need to create a single instance of the asset, regarding of the selection - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), InTransform); - if (!CreatedActor) - return; - - // Select the Actor we just created - if (GEditor->CanSelectActor(CreatedActor, true, true)) - { - GEditor->SelectNone(true, true, false); - GEditor->SelectActor(CreatedActor, true, true, true); - } -} - - -void -FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) -{ - // Add a slate notification - FString Notification = TEXT("Saving all Houdini temporary cook data..."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - - TArray PackagesToSave; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HAC = *Itr; - if (!HAC || HAC->IsPendingKill()) - continue; - - if (InSaveWorld && InSaveWorld != HAC->GetWorld()) - continue; - - const int32 NumOutputs = HAC->GetNumOutputs(); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - // TODO: Also save landscape layer info objects? - if (Output->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (auto &OutputObjectPair : Output->GetOutputObjects()) - { - UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) - continue; - - UStaticMesh *SM = Cast(Obj); - if (!SM) - continue; - - UPackage *Package = SM->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - - for (auto& MaterialAssignementPair : Output->GetAssignementMaterials()) - { - UMaterialInterface* MatInterface = MaterialAssignementPair.Value; - if (!MatInterface || MatInterface->IsPendingKill()) - continue; - - UPackage *Package = MatInterface->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - } - } - - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); -} - -void -FHoudiniEngineEditorUtils::ReselectSelectedActors() -{ - // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? - USelection* Selection = GEditor->GetSelectedActors(); - TArray SelectedActors; - SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); - Selection->GetSelectedObjects(SelectedActors); - - GEditor->SelectNone(false, false, false); - - for (AActor* NextSelected : SelectedActors) - { - GEditor->SelectActor(NextSelected, true, true, true, true); - } -} - -FString -FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding, const TCHAR PathSep) -{ - int32 Depth = 0; - for (const TCHAR Char : InNodePath) - { - if (Char == PathSep) - Depth++; - } - FString Trimmed = InNodeName; - Trimmed.TrimStartInline(); - return Trimmed.LeftPad(Trimmed.Len() + (Depth * Padding)); -} - -void -FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject) -{ - if (!IsValid(InRootObject)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty]: InRootObject is null.")); - return; - } - - const FCachedPropertyPath CachedPath(InPropertyPath.ToString()); - if (CachedPath.Resolve(InRootObject)) - { - // Notify that we have changed the property - // FPropertyChangedEvent Evt = CachedPath.ToPropertyChangedEvent(EPropertyChangeType::Unspecified); - // Construct FPropertyChangedEvent from the cached property path - const int32 NumSegments = CachedPath.GetNumSegments(); - FPropertyChangedEvent Evt( - CastFieldChecked(CachedPath.GetLastSegment().GetField().ToField()), - EPropertyChangeType::Unspecified, - { InRootObject }); - - if(NumSegments > 1) - { - Evt.SetActiveMemberProperty(CastFieldChecked(CachedPath.GetSegment(NumSegments - 2).GetField().ToField())); - } - - // Set the array of indices to the changed property - TArray> ArrayIndicesPerObject; - ArrayIndicesPerObject.AddDefaulted(1); - for (int32 SegmentIdx = 0; SegmentIdx < NumSegments; ++SegmentIdx) - { - const FPropertyPathSegment& Segment = CachedPath.GetSegment(SegmentIdx); - const int32 ArrayIndex = Segment.GetArrayIndex(); - if (ArrayIndex != INDEX_NONE) - { - ArrayIndicesPerObject[0].Add(Segment.GetName().ToString(), ArrayIndex); - } - } - Evt.SetArrayIndexPerObject(ArrayIndicesPerObject); - - FEditPropertyChain Chain; - CachedPath.ToEditPropertyChain(Chain); - FPropertyChangedChainEvent ChainEvent(Chain, Evt); - ChainEvent.ObjectIteratorIndex = 0; - InRootObject->PostEditChangeChainProperty(ChainEvent); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could not resolve property path '%s' on %s."), *InPropertyPath.ToString(), *(InRootObject->GetFullName())); - } -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineEditorUtils.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniAssetActor.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniAsset.h" +#include "HoudiniOutput.h" +#include "HoudiniTool.h" + +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" +#include "Engine/Selection.h" +#include "AssetRegistryModule.h" +#include "EditorViewportClient.h" +#include "ActorFactories/ActorFactory.h" +#include "FileHelpers.h" +#include "PropertyPathHelpers.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +int32 +FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection) +{ + ContentBrowserSelection.Empty(); + + // Get the current Content browser selection + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >("ContentBrowser"); + TArray SelectedAssets; + ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets); + + for (int32 n = 0; n < SelectedAssets.Num(); n++) + { + // Get the current object + UObject * Object = SelectedAssets[n].GetAsset(); + if (!Object || Object->IsPendingKill()) + continue; + + // Only static meshes are supported + if (Object->GetClass() != UStaticMesh::StaticClass()) + continue; + + ContentBrowserSelection.Add(Object); + } + + return ContentBrowserSelection.Num(); +} + +int32 +FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly) +{ + WorldSelection.Empty(); + + // Get the current editor selection + if (GEditor) + { + USelection* SelectedActors = GEditor->GetSelectedActors(); + if (SelectedActors && !SelectedActors->IsPendingKill()) + { + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor * Actor = Cast< AActor >(*It); + if (!Actor && Actor->IsPendingKill()) + continue; + + // Ignore the SkySphere? + FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); + if (ClassName == TEXT("BP_Sky_Sphere_C")) + continue; + + // We're normally only selecting actors with StaticMeshComponents and SplineComponents + // Heightfields? Filter here or later? also allow HoudiniAssets? + WorldSelection.Add(Actor); + } + } + + } + + // If we only want Houdini Actors... + if (bHoudiniAssetActorsOnly) + { + // ... remove all but them + for (int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx--) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + WorldSelection.RemoveAt(Idx); + } + } + + return WorldSelection.Num(); +} + + +FString +FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType) +{ + FString HoudiniCurveTypeStr; + switch (HoudiniCurveType) + { + case EHoudiniCurveType::Invalid: + { + HoudiniCurveTypeStr = TEXT("Invalid"); + } + break; + + case EHoudiniCurveType::Polygon: + { + HoudiniCurveTypeStr = TEXT("Polygon"); + } + break; + + case EHoudiniCurveType::Nurbs: + { + HoudiniCurveTypeStr = TEXT("Nurbs"); + } + break; + + case EHoudiniCurveType::Bezier: + { + HoudiniCurveTypeStr = TEXT("Bezier"); + } + break; + + case EHoudiniCurveType::Points: + { + HoudiniCurveTypeStr = TEXT("Points"); + } + break; + } + + return HoudiniCurveTypeStr; +} + +FString +FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(const EHoudiniCurveMethod& CurveMethod) +{ + FString HoudiniCurveMethodStr; + switch (CurveMethod) + { + case EHoudiniCurveMethod::Invalid: + { + HoudiniCurveMethodStr = TEXT("Invalid"); + } + break; + case EHoudiniCurveMethod::CVs: + { + HoudiniCurveMethodStr = TEXT("CVs"); + } + break; + case EHoudiniCurveMethod::Breakpoints: + { + HoudiniCurveMethodStr = TEXT("Breakpoints"); + } + break; + case EHoudiniCurveMethod::Freehand: + { + HoudiniCurveMethodStr = TEXT("Freehand"); + } + break; + } + + return HoudiniCurveMethodStr; +} + + +FString +FHoudiniEngineEditorUtils::HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType) +{ + // Temporary, we need to figure out a way to access the output curve's info later + FString UnrealCurveType; + switch (HoudiniCurveType) + { + case EHoudiniCurveType::Polygon: + case EHoudiniCurveType::Points: + { + UnrealCurveType = TEXT("Linear"); + } + break; + + case EHoudiniCurveType::Nurbs: + case EHoudiniCurveType::Bezier: + { + UnrealCurveType = TEXT("Curve"); + } + break; + } + + return UnrealCurveType; +} + +FString +FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType) +{ + FString LandscapeBakeTypeString; + switch (LandscapeBakeType) + { + case EHoudiniLandscapeOutputBakeType::Detachment: + LandscapeBakeTypeString = "To Current Level"; + break; + + case EHoudiniLandscapeOutputBakeType::BakeToImage: + LandscapeBakeTypeString = "To Image"; + break; + + case EHoudiniLandscapeOutputBakeType::BakeToWorld: + LandscapeBakeTypeString = "To New World"; + break; + + } + + return LandscapeBakeTypeString; +} + + +FTransform +FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform() +{ + FTransform SpawnTransform = FTransform::Identity; + + // Get the editor viewport LookAt position to spawn the new objects + if (GEditor && GEditor->GetActiveViewport()) + { + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (ViewportClient) + { + // We need to toggle the orbit camera on to get the proper LookAtLocation to spawn our asset + ViewportClient->ToggleOrbitCamera(true); + SpawnTransform.SetLocation(ViewportClient->GetLookAtLocation()); + ViewportClient->ToggleOrbitCamera(false); + } + } + + return SpawnTransform; +} + +FTransform +FHoudiniEngineEditorUtils::GetMeanWorldSelectionTransform() +{ + FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); + + if (GEditor && (GEditor->GetSelectedActorCount() > 0)) + { + // Get the current Level Editor Selection + USelection* SelectedActors = GEditor->GetSelectedActors(); + + int NumAppliedTransform = 0; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor * Actor = Cast< AActor >(*It); + if (!Actor) + continue; + + // Just Ignore the SkySphere... + FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); + if (ClassName == TEXT("BP_Sky_Sphere_C")) + continue; + + FTransform CurrentTransform = Actor->GetTransform(); + + ALandscapeProxy* Landscape = Cast< ALandscapeProxy >(Actor); + if (Landscape) + { + // We need to offset Landscape's transform in X/Y to center them properly + FVector Origin, Extent; + Actor->GetActorBounds(false, Origin, Extent); + + // Use the origin's XY Position + FVector Location = CurrentTransform.GetLocation(); + Location.X = Origin.X; + Location.Y = Origin.Y; + CurrentTransform.SetLocation(Location); + } + + // Accumulate all the actor transforms... + if (NumAppliedTransform == 0) + SpawnTransform = CurrentTransform; + else + SpawnTransform.Accumulate(CurrentTransform); + + NumAppliedTransform++; + } + + if (NumAppliedTransform > 0) + { + // "Mean" all the accumulated Transform + SpawnTransform.SetScale3D(FVector::OneVector); + SpawnTransform.NormalizeRotation(); + + if (NumAppliedTransform > 1) + SpawnTransform.SetLocation(SpawnTransform.GetLocation() / (float)NumAppliedTransform); + } + } + + return SpawnTransform; +} + +void +FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType) +{ + if (!InHoudiniAsset) + return; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + // Load the asset + UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); + if (!AssetObj) + return; + + // Get the asset Factory + UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); + if (!Factory) + return; + + // Get the current Level Editor Selection + TArray WorldSelection; + int32 WorldSelectionCount = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection); + + // Get the current Content browser selection + TArray ContentBrowserSelection; + int32 ContentBrowserSelectionCount = FHoudiniEngineEditorUtils::GetContentBrowserSelection(ContentBrowserSelection); + + // By default, Content browser selection has a priority over the world selection + bool UseCBSelection = ContentBrowserSelectionCount > 0; + if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY) + UseCBSelection = true; + else if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY) + UseCBSelection = false; + + // Modify the created actor's position from the current editor world selection + FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); + if (WorldSelectionCount > 0) + { + // Get the "mean" transform of all the selected actors + SpawnTransform = GetMeanWorldSelectionTransform(); + } + + // If the current tool is a batch one, we'll need to create multiple instances of the HDA + if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH) + { + // Unselect the current selection to select the created actor after + if (GEditor) + GEditor->SelectNone(true, true, false); + + // An instance of the asset will be created for each selected object + for (int32 SelecIndex = 0; SelecIndex < (UseCBSelection ? ContentBrowserSelectionCount : WorldSelectionCount); SelecIndex++) + { + // Get the current object + UObject* CurrentSelectedObject = nullptr; + if (UseCBSelection && ContentBrowserSelection.IsValidIndex(SelecIndex)) + CurrentSelectedObject = ContentBrowserSelection[SelecIndex]; + + if (!UseCBSelection && WorldSelection.IsValidIndex(SelecIndex)) + CurrentSelectedObject = WorldSelection[SelecIndex]; + + if (!CurrentSelectedObject) + continue; + + // If it's an actor, use its Transform to spawn the HDA + AActor* CurrentSelectedActor = Cast(CurrentSelectedObject); + if (CurrentSelectedActor) + SpawnTransform = CurrentSelectedActor->GetTransform(); + else + SpawnTransform = GetDefaulAssetSpawnTransform(); + + // Create the actor for the HDA + AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); + if (!CreatedActor) + continue; + + // Get the HoudiniAssetActor / HoudiniAssetComponent we just created + AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; + if (!HoudiniAssetActor) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent) + continue; + + // Create and set the input preset for this HDA and selected Object + TMap InputPreset; + InputPreset.Add(CurrentSelectedObject, 0); + HoudiniAssetComponent->SetInputPresets(InputPreset); + + // Select the Actor we just created + if (GEditor && GEditor->CanSelectActor(CreatedActor, true, false)) + GEditor->SelectActor(CreatedActor, true, true, true); + } + } + else + { + // We only need to create a single instance of the asset, regarding of the selection + AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); + if (!CreatedActor) + return; + + // Generator tools don't need to preset their input + if (InType != EHoudiniToolType::HTOOLTYPE_GENERATOR) + { + TMap InputPresets; + AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor ? HoudiniAssetActor->GetHoudiniAssetComponent() : nullptr; + if (HoudiniAssetComponent) + { + // Build the preset map + int InputIndex = 0; + for (auto CurrentObject : (UseCBSelection ? ContentBrowserSelection : WorldSelection)) + { + if (!CurrentObject) + continue; + + if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI) + { + // The selection will be applied individually to multiple inputs + // (first object to first input, second object to second input etc...) + InputPresets.Add(CurrentObject, InputIndex++); + } + else + { + // All the selection will be applied to the asset's first input + InputPresets.Add(CurrentObject, 0); + } + } + + // Set the input preset on the HoudiniAssetComponent + if (InputPresets.Num() > 0) + HoudiniAssetComponent->SetInputPresets(InputPresets); + } + } + + // Select the Actor we just created + if (GEditor->CanSelectActor(CreatedActor, true, true)) + { + GEditor->SelectNone(true, true, false); + GEditor->SelectActor(CreatedActor, true, true, true); + } + } +} + +void +FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform) +{ + if (!InHoudiniAsset) + return; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + // Load the asset + UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); + if (!AssetObj) + return; + + // Get the asset Factory + UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); + if (!Factory) + return; + + // We only need to create a single instance of the asset, regarding of the selection + AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), InTransform); + if (!CreatedActor) + return; + + // Select the Actor we just created + if (GEditor->CanSelectActor(CreatedActor, true, true)) + { + GEditor->SelectNone(true, true, false); + GEditor->SelectActor(CreatedActor, true, true, true); + } +} + + +void +FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) +{ + // Add a slate notification + FString Notification = TEXT("Saving all Houdini temporary cook data..."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + + TArray PackagesToSave; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HAC = *Itr; + if (!HAC || HAC->IsPendingKill()) + continue; + + if (InSaveWorld && InSaveWorld != HAC->GetWorld()) + continue; + + const int32 NumOutputs = HAC->GetNumOutputs(); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + // TODO: Also save landscape layer info objects? + if (Output->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (auto &OutputObjectPair : Output->GetOutputObjects()) + { + UObject *Obj = OutputObjectPair.Value.OutputObject; + if (!Obj || Obj->IsPendingKill()) + continue; + + UStaticMesh *SM = Cast(Obj); + if (!SM) + continue; + + UPackage *Package = SM->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + + for (auto& MaterialAssignementPair : Output->GetAssignementMaterials()) + { + UMaterialInterface* MatInterface = MaterialAssignementPair.Value; + if (!MatInterface || MatInterface->IsPendingKill()) + continue; + + UPackage *Package = MatInterface->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + } + } + + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); +} + +void +FHoudiniEngineEditorUtils::ReselectSelectedActors() +{ + // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? + USelection* Selection = GEditor->GetSelectedActors(); + TArray SelectedActors; + SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); + Selection->GetSelectedObjects(SelectedActors); + + GEditor->SelectNone(false, false, false); + + for (AActor* NextSelected : SelectedActors) + { + GEditor->SelectActor(NextSelected, true, true, true, true); + } +} + +FString +FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding, const TCHAR PathSep) +{ + int32 Depth = 0; + for (const TCHAR Char : InNodePath) + { + if (Char == PathSep) + Depth++; + } + FString Trimmed = InNodeName; + Trimmed.TrimStartInline(); + return Trimmed.LeftPad(Trimmed.Len() + (Depth * Padding)); +} + +void +FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject) +{ + if (!IsValid(InRootObject)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty]: InRootObject is null.")); + return; + } + + const FCachedPropertyPath CachedPath(InPropertyPath.ToString()); + if (CachedPath.Resolve(InRootObject)) + { + // Notify that we have changed the property + // FPropertyChangedEvent Evt = CachedPath.ToPropertyChangedEvent(EPropertyChangeType::Unspecified); + // Construct FPropertyChangedEvent from the cached property path + const int32 NumSegments = CachedPath.GetNumSegments(); + FPropertyChangedEvent Evt( + CastFieldChecked(CachedPath.GetLastSegment().GetField().ToField()), + EPropertyChangeType::Unspecified, + { InRootObject }); + + if(NumSegments > 1) + { + Evt.SetActiveMemberProperty(CastFieldChecked(CachedPath.GetSegment(NumSegments - 2).GetField().ToField())); + } + + // Set the array of indices to the changed property + TArray> ArrayIndicesPerObject; + ArrayIndicesPerObject.AddDefaulted(1); + for (int32 SegmentIdx = 0; SegmentIdx < NumSegments; ++SegmentIdx) + { + const FPropertyPathSegment& Segment = CachedPath.GetSegment(SegmentIdx); + const int32 ArrayIndex = Segment.GetArrayIndex(); + if (ArrayIndex != INDEX_NONE) + { + ArrayIndicesPerObject[0].Add(Segment.GetName().ToString(), ArrayIndex); + } + } + Evt.SetArrayIndexPerObject(ArrayIndicesPerObject); + + FEditPropertyChain Chain; + CachedPath.ToEditPropertyChain(Chain); + FPropertyChangedChainEvent ChainEvent(Chain, Evt); + ChainEvent.ObjectIteratorIndex = 0; + InRootObject->PostEditChangeChainProperty(ChainEvent); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could not resolve property path '%s' on %s."), *InPropertyPath.ToString(), *(InRootObject->GetFullName())); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h index dbe4f7bb4..ef51b9b2a 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h @@ -1,86 +1,86 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -class FString; -class UObject; -class UHoudiniAsset; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniLandscapeOutputBakeType : uint8; -enum class EHoudiniToolType : uint8; -enum class EHoudiniToolSelectionType : uint8; - -struct FHoudiniEngineEditorUtils -{ -public: - - // Triggers an update the details panel - //static void UpdateEditorProperties(UObject* ObjectToUpdate); - - // Helper function for accessing the current CB selection - static int32 GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection); - - // Helper function for accessing the current world selection - static int32 GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly = false); - - static FString HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType); - - static FString HoudiniCurveMethodToString(const EHoudiniCurveMethod& HoudinCurveMethod); - - static FString HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType); - - // (for temporary, we need to figure out a way to access the output curve's info later) - static FString HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType); - - static FTransform GetDefaulAssetSpawnTransform(); - - static FTransform GetMeanWorldSelectionTransform(); - - // Instantiate a HoudiniAsset at a given position - static void InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform); - - // Instantiate the given HDA, and handles the current CB/World selection - static void InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType); - - // Helper function used to save all temporary packages when the level is saved - static void SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld); - - // Deselect and reselect all selected actors to get rid of component not showing bug after create. - static void ReselectSelectedActors(); - - // Gets the node name indent from the left with the specified number of spaces based on the path depth. - static FString GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding=4, const TCHAR PathSep='/'); - - // Property change notifications - // Call PostEditChangeChainProperty on InRootObject for the property at InPropertyPath relative to - // InRootObject. - static void NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +class FString; +class UObject; +class UHoudiniAsset; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniLandscapeOutputBakeType : uint8; +enum class EHoudiniToolType : uint8; +enum class EHoudiniToolSelectionType : uint8; + +struct FHoudiniEngineEditorUtils +{ +public: + + // Triggers an update the details panel + //static void UpdateEditorProperties(UObject* ObjectToUpdate); + + // Helper function for accessing the current CB selection + static int32 GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection); + + // Helper function for accessing the current world selection + static int32 GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly = false); + + static FString HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType); + + static FString HoudiniCurveMethodToString(const EHoudiniCurveMethod& HoudinCurveMethod); + + static FString HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType); + + // (for temporary, we need to figure out a way to access the output curve's info later) + static FString HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType); + + static FTransform GetDefaulAssetSpawnTransform(); + + static FTransform GetMeanWorldSelectionTransform(); + + // Instantiate a HoudiniAsset at a given position + static void InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform); + + // Instantiate the given HDA, and handles the current CB/World selection + static void InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType); + + // Helper function used to save all temporary packages when the level is saved + static void SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld); + + // Deselect and reselect all selected actors to get rid of component not showing bug after create. + static void ReselectSelectedActors(); + + // Gets the node name indent from the left with the specified number of spaces based on the path depth. + static FString GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding=4, const TCHAR PathSep='/'); + + // Property change notifications + // Call PostEditChangeChainProperty on InRootObject for the property at InPropertyPath relative to + // InRootObject. + static void NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp index cf67c3152..a8c45c221 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp @@ -1,311 +1,311 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineStyle.h" - -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineUtils.h" - -#include "EditorStyleSet.h" -#include "Styling/SlateStyleRegistry.h" -#include "Styling/SlateTypes.h" -#include "SlateOptMacros.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) -#define TTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) -#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) -#define OTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) - -TSharedPtr FHoudiniEngineStyle::StyleSet = nullptr; - -TSharedPtr -FHoudiniEngineStyle::Get() -{ - return StyleSet; -} - -FName -FHoudiniEngineStyle::GetStyleSetName() -{ - static FName HoudiniStyleName(TEXT("HoudiniEngineStyle")); - return HoudiniStyleName; -} - -BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION - -void -FHoudiniEngineStyle::Initialize() -{ - // Only register the StyleSet once - if (StyleSet.IsValid()) - return; - - StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName())); - StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); - StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); - - // Note, these sizes are in Slate Units. - // Slate Units do NOT have to map to pixels. - const FVector2D Icon5x16(5.0f, 16.0f); - const FVector2D Icon8x4(8.0f, 4.0f); - const FVector2D Icon8x8(8.0f, 8.0f); - const FVector2D Icon10x10(10.0f, 10.0f); - const FVector2D Icon12x12(12.0f, 12.0f); - const FVector2D Icon12x16(12.0f, 16.0f); - const FVector2D Icon14x14(14.0f, 14.0f); - const FVector2D Icon16x16(16.0f, 16.0f); - const FVector2D Icon20x20(20.0f, 20.0f); - const FVector2D Icon22x22(22.0f, 22.0f); - const FVector2D Icon24x24(24.0f, 24.0f); - const FVector2D Icon25x25(25.0f, 25.0f); - const FVector2D Icon32x32(32.0f, 32.0f); - const FVector2D Icon40x40(40.0f, 40.0f); - const FVector2D Icon64x64(64.0f, 64.0f); - const FVector2D Icon36x24(36.0f, 24.0f); - const FVector2D Icon128x128(128.0f, 128.0f); - - static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); - StyleSet->Set( - "HoudiniEngine.HoudiniEngineLogo", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set( - "ClassIcon.HoudiniAssetActor", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set( - "HoudiniEngine.HoudiniEngineLogo40", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); - - StyleSet->Set( - "ClassIcon.HoudiniAsset", - new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset.png"), Icon16x16)); - - StyleSet->Set( - "ClassThumbnail.HoudiniAsset", - new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset_128.png"), Icon64x64)); - - static FString ResourcesDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/"); - - FString AssetHelpIcon = IconsDir + TEXT("asset_help16x16.png"); - FString BakeAllIcon = IconsDir + TEXT("bake_all16x16.png"); - FString BakeSelIcon = IconsDir + TEXT("bake_selected16x16.png"); - FString CleanTempIcon = IconsDir + TEXT("clean_temp16x16.png"); - FString CookAllIcon = IconsDir + TEXT("cook_all16x16.png"); - FString CookLogIcon = IconsDir + TEXT("cook_log16x16.png"); - FString CookSelIcon = IconsDir + TEXT("cook_selected16x16.png"); - FString DigitalAssetIcon = IconsDir + TEXT("digital_asset16x16.png"); - FString OnlineForumIcon = IconsDir + TEXT("online_forum16x16.png"); - FString OnlineHelpIcon = IconsDir + TEXT("online_help16x16.png"); - FString OpenInHIcon = IconsDir + TEXT("open_in_houdini16x16.png"); - FString PauseIcon = IconsDir + TEXT("pause16x16.png"); - FString PDGCancelIcon = IconsDir + TEXT("pdg_cancel16x16.png"); - FString PDGDirtyAllIcon = IconsDir + TEXT("pdg_dirty_all16x16.png"); - FString PDGDirtyNodeIcon = IconsDir + TEXT("pdg_dirty_node16x16.png"); - FString PDGLinkIcon = IconsDir + TEXT("pdg_link16x16.png"); - FString PDGPauseIcon = IconsDir + TEXT("pdg_pause16x16.png"); - FString PDGRefreshIcon = IconsDir + TEXT("pdg_refresh16x16.png"); - FString PDGResetIcon = IconsDir + TEXT("pdg_reset16x16.png"); - FString RebuildAllIcon = IconsDir + TEXT("rebuild_all16x16.png"); - FString RebuildSelIcon = IconsDir + TEXT("rebuild_selected16x16.png"); - FString RefineAllIcon = IconsDir + TEXT("refine_all16x16.png"); - FString RefineSelIcon = IconsDir + TEXT("refine_selected16x16.png"); - FString ReportBugIcon = IconsDir + TEXT("report_bug16x16.png"); - FString ResetIcon = IconsDir + TEXT("reset16x16.png"); - FString ResetParamIcon = IconsDir + TEXT("reset_parameters16x16.png"); - FString SaveToHipIcon = IconsDir + TEXT("save_to_hip16x16.png"); - FString SessionConnectIcon = IconsDir + TEXT("session_connect16x16.png"); - FString SessionCreateIcon = IconsDir + TEXT("session_create16x16.png"); - FString SessionRestartIcon = IconsDir + TEXT("session_restart16x16.png"); - FString SessionStopIcon = IconsDir + TEXT("session_stop16x16.png"); - FString SessionSyncIcon = IconsDir + TEXT("session_sync16x16.png"); - FString SessionSyncStartIcon = IconsDir + TEXT("session_sync_start16x16.png"); - FString SessionSyncStopIcon = IconsDir + TEXT("session_sync_stop16x16.png"); - FString ViewportSyncIcon = IconsDir + TEXT("viewport_sync16x16.png"); - FString ViewportSyncBothIcon = IconsDir + TEXT("viewport_sync_both16x16.png"); - FString ViewportSyncHoudiniIcon = IconsDir + TEXT("viewport_sync_houdini16x16.png"); - FString ViewportSyncOffIcon = IconsDir + TEXT("viewport_sync_off16x16.png"); - FString ViewportSyncUnrealIcon = IconsDir + TEXT("viewport_sync_unreal16x16.png"); - - FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); - FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); - - StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(SessionCreateIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(SessionConnectIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(SessionStopIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(SessionRestartIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._SessionSync", new FSlateImageBrush(SessionSyncIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(SessionSyncStartIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(SessionSyncStopIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._SyncViewport", new FSlateImageBrush(ViewportSyncIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncNone", new FSlateImageBrush(ViewportSyncOffIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncBoth", new FSlateImageBrush(ViewportSyncBothIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncUnreal", new FSlateImageBrush(ViewportSyncUnrealIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncHoudini", new FSlateImageBrush(ViewportSyncHoudiniIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(OpenInHIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(SaveToHipIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(CleanTempIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(OnlineHelpIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(OnlineForumIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(ReportBugIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(CookAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(CookSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(BakeSelIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(BakeAllIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(RebuildAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(RebuildSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(RefineAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(RefineSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(PauseIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._Reset", new FSlateImageBrush(ResetIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine.DigitalAsset", new FSlateImageBrush(DigitalAssetIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine.PDGLink", new FSlateImageBrush(PDGLinkIcon, Icon16x16)); - - /* - FString StopIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Clear")->GetResourceName().ToString(); - FString RestartIcon = FEditorStyle::GetBrush("Tutorials.Browser.RestartButton")->GetResourceName().ToString(); - FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); - FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); - FString ClearIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Delete")->GetResourceName().ToString(); - FString HelpIcon = FEditorStyle::GetBrush("Icons.Help")->GetResourceName().ToString(); - FString WarningIcon = FEditorStyle::GetBrush("Icons.Warning")->GetResourceName().ToString(); - FString BPIcon = FEditorStyle::GetBrush("PropertyWindow.Button_CreateNewBlueprint")->GetResourceName().ToString(); - FString PauseIcon = FEditorStyle::GetBrush("Profiler.Pause")->GetResourceName().ToString(); - - StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(StopIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(RestartIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(StopIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); - - - StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(HelpIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(WarningIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - */ - - // We need some colors from Editor Style & this is the only way to do this at the moment - const FSlateColor DefaultForeground = FEditorStyle::GetSlateColor("DefaultForeground"); - const FSlateColor InvertedForeground = FEditorStyle::GetSlateColor("InvertedForeground"); - const FSlateColor SelectorColor = FEditorStyle::GetSlateColor("SelectorColor"); - const FSlateColor SelectionColor = FEditorStyle::GetSlateColor("SelectionColor"); - const FSlateColor SelectionColor_Inactive = FEditorStyle::GetSlateColor("SelectionColor_Inactive"); - - const FTableRowStyle &NormalTableRowStyle = FEditorStyle::Get().GetWidgetStyle("TableView.Row"); - StyleSet->Set( - "HoudiniEngine.TableRow", FTableRowStyle(NormalTableRowStyle) - .SetEvenRowBackgroundBrush(FSlateNoResource()) - .SetEvenRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) - .SetOddRowBackgroundBrush(FSlateNoResource()) - .SetOddRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) - .SetSelectorFocusedBrush(BORDER_BRUSH("Common/Selector", FMargin(4.f / 16.f), SelectorColor)) - .SetActiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) - .SetActiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) - .SetInactiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) - .SetInactiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) - .SetTextColor(DefaultForeground) - .SetSelectedTextColor(InvertedForeground) - ); - - // Normal Text - const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle("NormalText"); - StyleSet->Set( - "HoudiniEngine.ThumbnailText", FTextBlockStyle(NormalText) - .SetFont(TTF_CORE_FONT("Fonts/Roboto-Regular", 9)) - .SetColorAndOpacity(FSlateColor::UseForeground()) - .SetShadowOffset(FVector2D::ZeroVector) - .SetShadowColorAndOpacity(FLinearColor::Black) - .SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f)) - .SetHighlightShape(BOX_BRUSH("Common/TextBlockHighlightShape", FMargin(3.f / 8.f))) - ); - - StyleSet->Set("HoudiniEngine.ThumbnailShadow", new BOX_BRUSH("ContentBrowser/ThumbnailShadow", FMargin(4.0f / 64.0f))); - StyleSet->Set("HoudiniEngine.ThumbnailBackground", new IMAGE_BRUSH("Common/ClassBackground_64x", FVector2D(64.f, 64.f), FLinearColor(0.75f, 0.75f, 0.75f, 1.0f))); - - // Register Slate style. - FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); -}; - -END_SLATE_FUNCTION_BUILD_OPTIMIZATION - -#undef IMAGE_BRUSH -#undef BOX_BRUSH -#undef BORDER_BRUSH -#undef TTF_FONT -#undef TTF_CORE_FONT -#undef OTF_FONT -#undef OTF_CORE_FONT - -void -FHoudiniEngineStyle::Shutdown() -{ - if (StyleSet.IsValid()) - { - FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); - ensure(StyleSet.IsUnique()); - StyleSet.Reset(); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineStyle.h" + +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineUtils.h" + +#include "EditorStyleSet.h" +#include "Styling/SlateStyleRegistry.h" +#include "Styling/SlateTypes.h" +#include "SlateOptMacros.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define TTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) +#define OTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +TSharedPtr FHoudiniEngineStyle::StyleSet = nullptr; + +TSharedPtr +FHoudiniEngineStyle::Get() +{ + return StyleSet; +} + +FName +FHoudiniEngineStyle::GetStyleSetName() +{ + static FName HoudiniStyleName(TEXT("HoudiniEngineStyle")); + return HoudiniStyleName; +} + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void +FHoudiniEngineStyle::Initialize() +{ + // Only register the StyleSet once + if (StyleSet.IsValid()) + return; + + StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName())); + StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); + StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); + + // Note, these sizes are in Slate Units. + // Slate Units do NOT have to map to pixels. + const FVector2D Icon5x16(5.0f, 16.0f); + const FVector2D Icon8x4(8.0f, 4.0f); + const FVector2D Icon8x8(8.0f, 8.0f); + const FVector2D Icon10x10(10.0f, 10.0f); + const FVector2D Icon12x12(12.0f, 12.0f); + const FVector2D Icon12x16(12.0f, 16.0f); + const FVector2D Icon14x14(14.0f, 14.0f); + const FVector2D Icon16x16(16.0f, 16.0f); + const FVector2D Icon20x20(20.0f, 20.0f); + const FVector2D Icon22x22(22.0f, 22.0f); + const FVector2D Icon24x24(24.0f, 24.0f); + const FVector2D Icon25x25(25.0f, 25.0f); + const FVector2D Icon32x32(32.0f, 32.0f); + const FVector2D Icon40x40(40.0f, 40.0f); + const FVector2D Icon64x64(64.0f, 64.0f); + const FVector2D Icon36x24(36.0f, 24.0f); + const FVector2D Icon128x128(128.0f, 128.0f); + + static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); + StyleSet->Set( + "HoudiniEngine.HoudiniEngineLogo", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( + "ClassIcon.HoudiniAssetActor", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( + "HoudiniEngine.HoudiniEngineLogo40", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); + + StyleSet->Set( + "ClassIcon.HoudiniAsset", + new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset.png"), Icon16x16)); + + StyleSet->Set( + "ClassThumbnail.HoudiniAsset", + new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset_128.png"), Icon64x64)); + + static FString ResourcesDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/"); + + FString AssetHelpIcon = IconsDir + TEXT("asset_help16x16.png"); + FString BakeAllIcon = IconsDir + TEXT("bake_all16x16.png"); + FString BakeSelIcon = IconsDir + TEXT("bake_selected16x16.png"); + FString CleanTempIcon = IconsDir + TEXT("clean_temp16x16.png"); + FString CookAllIcon = IconsDir + TEXT("cook_all16x16.png"); + FString CookLogIcon = IconsDir + TEXT("cook_log16x16.png"); + FString CookSelIcon = IconsDir + TEXT("cook_selected16x16.png"); + FString DigitalAssetIcon = IconsDir + TEXT("digital_asset16x16.png"); + FString OnlineForumIcon = IconsDir + TEXT("online_forum16x16.png"); + FString OnlineHelpIcon = IconsDir + TEXT("online_help16x16.png"); + FString OpenInHIcon = IconsDir + TEXT("open_in_houdini16x16.png"); + FString PauseIcon = IconsDir + TEXT("pause16x16.png"); + FString PDGCancelIcon = IconsDir + TEXT("pdg_cancel16x16.png"); + FString PDGDirtyAllIcon = IconsDir + TEXT("pdg_dirty_all16x16.png"); + FString PDGDirtyNodeIcon = IconsDir + TEXT("pdg_dirty_node16x16.png"); + FString PDGLinkIcon = IconsDir + TEXT("pdg_link16x16.png"); + FString PDGPauseIcon = IconsDir + TEXT("pdg_pause16x16.png"); + FString PDGRefreshIcon = IconsDir + TEXT("pdg_refresh16x16.png"); + FString PDGResetIcon = IconsDir + TEXT("pdg_reset16x16.png"); + FString RebuildAllIcon = IconsDir + TEXT("rebuild_all16x16.png"); + FString RebuildSelIcon = IconsDir + TEXT("rebuild_selected16x16.png"); + FString RefineAllIcon = IconsDir + TEXT("refine_all16x16.png"); + FString RefineSelIcon = IconsDir + TEXT("refine_selected16x16.png"); + FString ReportBugIcon = IconsDir + TEXT("report_bug16x16.png"); + FString ResetIcon = IconsDir + TEXT("reset16x16.png"); + FString ResetParamIcon = IconsDir + TEXT("reset_parameters16x16.png"); + FString SaveToHipIcon = IconsDir + TEXT("save_to_hip16x16.png"); + FString SessionConnectIcon = IconsDir + TEXT("session_connect16x16.png"); + FString SessionCreateIcon = IconsDir + TEXT("session_create16x16.png"); + FString SessionRestartIcon = IconsDir + TEXT("session_restart16x16.png"); + FString SessionStopIcon = IconsDir + TEXT("session_stop16x16.png"); + FString SessionSyncIcon = IconsDir + TEXT("session_sync16x16.png"); + FString SessionSyncStartIcon = IconsDir + TEXT("session_sync_start16x16.png"); + FString SessionSyncStopIcon = IconsDir + TEXT("session_sync_stop16x16.png"); + FString ViewportSyncIcon = IconsDir + TEXT("viewport_sync16x16.png"); + FString ViewportSyncBothIcon = IconsDir + TEXT("viewport_sync_both16x16.png"); + FString ViewportSyncHoudiniIcon = IconsDir + TEXT("viewport_sync_houdini16x16.png"); + FString ViewportSyncOffIcon = IconsDir + TEXT("viewport_sync_off16x16.png"); + FString ViewportSyncUnrealIcon = IconsDir + TEXT("viewport_sync_unreal16x16.png"); + + FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); + FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); + + StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(SessionCreateIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(SessionConnectIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(SessionStopIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(SessionRestartIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._SessionSync", new FSlateImageBrush(SessionSyncIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(SessionSyncStartIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(SessionSyncStopIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._SyncViewport", new FSlateImageBrush(ViewportSyncIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncNone", new FSlateImageBrush(ViewportSyncOffIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncBoth", new FSlateImageBrush(ViewportSyncBothIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncUnreal", new FSlateImageBrush(ViewportSyncUnrealIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncHoudini", new FSlateImageBrush(ViewportSyncHoudiniIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(OpenInHIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(SaveToHipIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(CleanTempIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(OnlineHelpIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(OnlineForumIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(ReportBugIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(CookAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(CookSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(BakeSelIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(BakeAllIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(RebuildAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(RebuildSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(RefineAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(RefineSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(PauseIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._Reset", new FSlateImageBrush(ResetIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine.DigitalAsset", new FSlateImageBrush(DigitalAssetIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine.PDGLink", new FSlateImageBrush(PDGLinkIcon, Icon16x16)); + + /* + FString StopIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Clear")->GetResourceName().ToString(); + FString RestartIcon = FEditorStyle::GetBrush("Tutorials.Browser.RestartButton")->GetResourceName().ToString(); + FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); + FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); + FString ClearIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Delete")->GetResourceName().ToString(); + FString HelpIcon = FEditorStyle::GetBrush("Icons.Help")->GetResourceName().ToString(); + FString WarningIcon = FEditorStyle::GetBrush("Icons.Warning")->GetResourceName().ToString(); + FString BPIcon = FEditorStyle::GetBrush("PropertyWindow.Button_CreateNewBlueprint")->GetResourceName().ToString(); + FString PauseIcon = FEditorStyle::GetBrush("Profiler.Pause")->GetResourceName().ToString(); + + StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(StopIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(RestartIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(StopIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); + + + StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(HelpIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(WarningIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + */ + + // We need some colors from Editor Style & this is the only way to do this at the moment + const FSlateColor DefaultForeground = FEditorStyle::GetSlateColor("DefaultForeground"); + const FSlateColor InvertedForeground = FEditorStyle::GetSlateColor("InvertedForeground"); + const FSlateColor SelectorColor = FEditorStyle::GetSlateColor("SelectorColor"); + const FSlateColor SelectionColor = FEditorStyle::GetSlateColor("SelectionColor"); + const FSlateColor SelectionColor_Inactive = FEditorStyle::GetSlateColor("SelectionColor_Inactive"); + + const FTableRowStyle &NormalTableRowStyle = FEditorStyle::Get().GetWidgetStyle("TableView.Row"); + StyleSet->Set( + "HoudiniEngine.TableRow", FTableRowStyle(NormalTableRowStyle) + .SetEvenRowBackgroundBrush(FSlateNoResource()) + .SetEvenRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) + .SetOddRowBackgroundBrush(FSlateNoResource()) + .SetOddRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) + .SetSelectorFocusedBrush(BORDER_BRUSH("Common/Selector", FMargin(4.f / 16.f), SelectorColor)) + .SetActiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) + .SetActiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) + .SetInactiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) + .SetInactiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) + .SetTextColor(DefaultForeground) + .SetSelectedTextColor(InvertedForeground) + ); + + // Normal Text + const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle("NormalText"); + StyleSet->Set( + "HoudiniEngine.ThumbnailText", FTextBlockStyle(NormalText) + .SetFont(TTF_CORE_FONT("Fonts/Roboto-Regular", 9)) + .SetColorAndOpacity(FSlateColor::UseForeground()) + .SetShadowOffset(FVector2D::ZeroVector) + .SetShadowColorAndOpacity(FLinearColor::Black) + .SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f)) + .SetHighlightShape(BOX_BRUSH("Common/TextBlockHighlightShape", FMargin(3.f / 8.f))) + ); + + StyleSet->Set("HoudiniEngine.ThumbnailShadow", new BOX_BRUSH("ContentBrowser/ThumbnailShadow", FMargin(4.0f / 64.0f))); + StyleSet->Set("HoudiniEngine.ThumbnailBackground", new IMAGE_BRUSH("Common/ClassBackground_64x", FVector2D(64.f, 64.f), FLinearColor(0.75f, 0.75f, 0.75f, 1.0f))); + + // Register Slate style. + FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); +}; + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef TTF_CORE_FONT +#undef OTF_FONT +#undef OTF_CORE_FONT + +void +FHoudiniEngineStyle::Shutdown() +{ + if (StyleSet.IsValid()) + { + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); + ensure(StyleSet.IsUnique()); + StyleSet.Reset(); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h index 239935d92..ac64f0faa 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h @@ -1,42 +1,42 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Styling/SlateStyle.h" - -class FHoudiniEngineStyle -{ - public: - static void Initialize(); - static void Shutdown(); - static TSharedPtr Get(); - static FName GetStyleSetName(); - - private: - //static FString InContent(const FString &RelativePath, const ANSICHAR *Extension); - static TSharedPtr StyleSet; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Styling/SlateStyle.h" + +class FHoudiniEngineStyle +{ + public: + static void Initialize(); + static void Shutdown(); + static TSharedPtr Get(); + static FName GetStyleSetName(); + + private: + //static FString InContent(const FString &RelativePath, const ANSICHAR *Extension); + static TSharedPtr StyleSet; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h deleted file mode 100644 index 293eeb8ed..000000000 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineTool.h +++ /dev/null @@ -1,27 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp index a8cfc925d..5541b05bf 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp @@ -1,382 +1,382 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniOutput.h" -#include "HoudiniEngine.h" - -#include "Engine/StaticMesh.h" -//#include "Engine/SkeletalMesh.h" - -#include "EditorFramework/AssetImportData.h" -#include "Misc/FileHelper.h" -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniGeoFactory::UHoudiniGeoFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - // This factory is responsible for manufacturing HoudiniEngine assets. - SupportedClass = nullptr;// UHoudiniAsset::StaticClass(); - - // This factory does not manufacture new objects from scratch. - bCreateNew = false; - - // This factory will not open the editor for each new object. - bEditAfterNew = false; - - // This factory will import objects from files. - bEditorImport = true; - - // Factory does not import objects from text. - bText = false; - - // Add supported formats. - Formats.Add(TEXT("bgeo;Houdini Geometry")); - Formats.Add(TEXT("bgeo.sc;Houdini Geometry (compressed)")); - Formats.Add(TEXT("sc;Houdini Geometry (compressed)")); -} - -bool -UHoudiniGeoFactory::FactoryCanImport(const FString& Filename) -{ - const FString Extension = FPaths::GetExtension(Filename); - if(FPaths::GetExtension(Filename) == TEXT("bgeo")) - return true; - if (FPaths::GetExtension(Filename) == TEXT("bgeo.sc")) - return true; - if (FPaths::GetExtension(Filename) == TEXT("sc")) - return true; - - return false; -} - -bool -UHoudiniGeoFactory::DoesSupportClass(UClass * Class) -{ - return Class == UStaticMesh::StaticClass(); //|| Class == USkeletalMesh::StaticClass()); -} - -UClass* -UHoudiniGeoFactory::ResolveSupportedClass() -{ - return UStaticMesh::StaticClass(); -} - -FText -UHoudiniGeoFactory::GetDisplayName() const -{ - return LOCTEXT("HoudiniGeoFactoryDescription", "Houdini Engine Geo"); -} - -UObject* -UHoudiniGeoFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) -{ - // Make sure we're loading bgeo / bgeo.sc files - FString FileExtension = FPaths::GetExtension(Filename); - if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) - return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); - - // - // TODO: - // Handle import settings here? - // - UObject* Success = Import(InClass, Cast(InParent), InName.ToString(), Filename, Flags, false); - if (!Success) - { - FString Notification = TEXT("BGEO Importer: Failed to load the BGEO file."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - return nullptr; - } - - // Notifiy we're done loading the bgeo - FString Notification = TEXT("BGEO Importer: BGEO file imported succesfully."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - return Success; -} - -bool -UHoudiniGeoFactory::CanReimport(UObject * Obj, TArray& OutFilenames) -{ - UAssetImportData* ImportData = nullptr; - if (Obj->GetClass() == UStaticMesh::StaticClass()) - { - UStaticMesh* Mesh = Cast(Obj); - ImportData = Mesh->AssetImportData; - } - /* - else if (Obj->GetClass() == USkeletalMesh::StaticClass()) - { - USkeletalMesh* Cache = Cast(Obj); - ImportData = Cache->AssetImportData; - } - */ - - if (ImportData) - { - if (FPaths::GetExtension(ImportData->GetFirstFilename()).Contains(TEXT("bgeo"))) - { - ImportData->ExtractFilenames(OutFilenames); - return true; - } - } - return false; -} - -void -UHoudiniGeoFactory::SetReimportPaths(UObject * Obj, const TArray& NewReimportPaths) -{ - UStaticMesh* Mesh = Cast(Obj); - if (Mesh && Mesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) - { - Mesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); - } - - /* - USkeletalMesh* SkeletalMesh = Cast(Obj); - if (SkeletalMesh && SkeletalMesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) - { - SkeletalMesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); - } - */ -} - - -UObject* -UHoudiniGeoFactory::Import(UClass* InClass, UPackage* InParent, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport) -{ - // Broadcast PreImport - GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, FName(FileName), TEXT("Houdini GEO")); - - // Create a new Geo importer - TArray DummyOldOutputs; - TArray NewOutputs; - UHoudiniGeoImporter* BGEOImporter = NewObject(this); - BGEOImporter->AddToRoot(); - - // Clean up lambda - auto CleanUp = [&NewOutputs, &BGEOImporter]() - { - // Remove the importer and output objects from the root set - BGEOImporter->RemoveFromRoot(); - for (auto Out : NewOutputs) - Out->RemoveFromRoot(); - }; - - // Failure lambda - auto FailImportAndReturnNull = [this, &CleanUp, &NewOutputs, &BGEOImporter]() - { - CleanUp(); - - // Failed to read the file info, fail the import - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, nullptr); - - return nullptr; - }; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!BGEOImporter->AutoStartHoudiniEngineSessionIfNeeded()) - return FailImportAndReturnNull(); - - // 2. Update the file paths - if (!BGEOImporter->SetFilePath(AbsoluteFilePath)) - return FailImportAndReturnNull(); - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - if (!BGEOImporter->LoadBGEOFileInHAPI(NodeId)) - return FailImportAndReturnNull(); - - // Prepare the package used for creating the mesh, landscape and instancer pacakges - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - PackageParams.HoudiniAssetName = FString(); - PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); - PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); - - if (bReimport) - { - PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - } - else - { - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - } - - // 4. Get the output from the file node - if (!BGEOImporter->BuildOutputsForNode(NodeId, DummyOldOutputs, NewOutputs)) - return FailImportAndReturnNull(); - - // 5. Create the static meshes in the outputs - if (!BGEOImporter->CreateStaticMeshes(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 6. Create the curves in the outputs - if (!BGEOImporter->CreateCurves(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 7. Create the landscape in the outputs - if (!BGEOImporter->CreateLandscapes(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 8. Create the instancers in the outputs - if (!BGEOImporter->CreateInstancers(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 9. Delete the created node in Houdini - if (!BGEOImporter->DeleteCreatedNode(NodeId)) - { - // Not good, but not fatal.. - //return false; - } - - // Get our result object and "finalize" them - TArray Results = BGEOImporter->GetOutputObjects(); - for (UObject* Object : Results) - { - if (!Object || Object->IsPendingKill()) - continue; - - Object->SetFlags(Flags); - - UAssetImportData * AssetImportData = nullptr; - if (Object->IsA()) - { - UStaticMesh* SM = Cast(Object); - AssetImportData = SM->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); - SM->AssetImportData = AssetImportData; - } - } - /* - else if (Object->IsA()) - { - USkeletalMesh * SkeletalMesh = Cast(Object); - AssetImportData = SkeletalMesh->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject(SkeletalMesh, USkeletalMesh::StaticClass()); - SkeletalMesh->AssetImportData = AssetImportData; - } - } - */ - - if (AssetImportData) - AssetImportData->Update(AbsoluteFilePath); - - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, Object); - Object->MarkPackageDirty(); - Object->PostEditChange(); - } - - CleanUp(); - - // Determine out parent according to the generated assets outer - UObject* OutParent = (Results.Num() > 0 && InParent != Results[0]->GetOutermost()) ? Results[0]->GetOutermost() : InParent; - return (Results.Num() > 0) ? OutParent : nullptr; -} - -EReimportResult::Type -UHoudiniGeoFactory::Reimport(UObject * Obj) -{ - auto FailReimport = []() - { - // Notifiy we failed to load the bgeo - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); - return EReimportResult::Failed; - }; - - if (!Obj || Obj->IsPendingKill()) - return FailReimport(); - - UPackage* Package = Cast(Obj->GetOuter()); - if (!Package || Package->IsPendingKill()) - return FailReimport(); - - UAssetImportData* ImportData = nullptr; - if (Obj->GetClass() == UStaticMesh::StaticClass()) - { - UStaticMesh* StaticMesh = Cast(Obj); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return FailReimport(); - - ImportData = StaticMesh->AssetImportData; - } - /* - else if(Obj->GetClass() == USkeletalMesh::StaticClass()) - { - USkeletalMesh* SkeletalMesh = Cast(Obj); - if (!SkeletalMesh || SkeletalMesh->IsPendingKill()) - return FailReimport(); - - ImportData = SkeletalMesh->AssetImportData; - } - */ - if (!ImportData || ImportData->IsPendingKill()) - return FailReimport(); - - if (ImportData->GetSourceFileCount() <= 0) - return FailReimport(); - - const FString RelativeFileName = ImportData->SourceData.SourceFiles[0].RelativeFilename; - const FString FileExtension = FPaths::GetExtension(RelativeFileName); - FString FileName = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativeFileName); - if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) - return FailReimport(); - - if (!Import(Obj->GetClass(), Package, Obj->GetName(), FileName, Obj->GetFlags(), true)) - return FailReimport(); - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has Succeed.")); - - // Notifiy we're done loading the bgeo - FString Notification = TEXT("BGEO Importer: BGEO file re-imported succesfully."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - return EReimportResult::Succeeded; -} - -int32 -UHoudiniGeoFactory::GetPriority() const -{ - return ImportPriority; -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniOutput.h" +#include "HoudiniEngine.h" + +#include "Engine/StaticMesh.h" +//#include "Engine/SkeletalMesh.h" + +#include "EditorFramework/AssetImportData.h" +#include "Misc/FileHelper.h" +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniGeoFactory::UHoudiniGeoFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + // This factory is responsible for manufacturing HoudiniEngine assets. + SupportedClass = nullptr;// UHoudiniAsset::StaticClass(); + + // This factory does not manufacture new objects from scratch. + bCreateNew = false; + + // This factory will not open the editor for each new object. + bEditAfterNew = false; + + // This factory will import objects from files. + bEditorImport = true; + + // Factory does not import objects from text. + bText = false; + + // Add supported formats. + Formats.Add(TEXT("bgeo;Houdini Geometry")); + Formats.Add(TEXT("bgeo.sc;Houdini Geometry (compressed)")); + Formats.Add(TEXT("sc;Houdini Geometry (compressed)")); +} + +bool +UHoudiniGeoFactory::FactoryCanImport(const FString& Filename) +{ + const FString Extension = FPaths::GetExtension(Filename); + if(FPaths::GetExtension(Filename) == TEXT("bgeo")) + return true; + if (FPaths::GetExtension(Filename) == TEXT("bgeo.sc")) + return true; + if (FPaths::GetExtension(Filename) == TEXT("sc")) + return true; + + return false; +} + +bool +UHoudiniGeoFactory::DoesSupportClass(UClass * Class) +{ + return Class == UStaticMesh::StaticClass(); //|| Class == USkeletalMesh::StaticClass()); +} + +UClass* +UHoudiniGeoFactory::ResolveSupportedClass() +{ + return UStaticMesh::StaticClass(); +} + +FText +UHoudiniGeoFactory::GetDisplayName() const +{ + return LOCTEXT("HoudiniGeoFactoryDescription", "Houdini Engine Geo"); +} + +UObject* +UHoudiniGeoFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) +{ + // Make sure we're loading bgeo / bgeo.sc files + FString FileExtension = FPaths::GetExtension(Filename); + if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) + return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); + + // + // TODO: + // Handle import settings here? + // + UObject* Success = Import(InClass, Cast(InParent), InName.ToString(), Filename, Flags, false); + if (!Success) + { + FString Notification = TEXT("BGEO Importer: Failed to load the BGEO file."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + return nullptr; + } + + // Notifiy we're done loading the bgeo + FString Notification = TEXT("BGEO Importer: BGEO file imported succesfully."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + return Success; +} + +bool +UHoudiniGeoFactory::CanReimport(UObject * Obj, TArray& OutFilenames) +{ + UAssetImportData* ImportData = nullptr; + if (Obj->GetClass() == UStaticMesh::StaticClass()) + { + UStaticMesh* Mesh = Cast(Obj); + ImportData = Mesh->AssetImportData; + } + /* + else if (Obj->GetClass() == USkeletalMesh::StaticClass()) + { + USkeletalMesh* Cache = Cast(Obj); + ImportData = Cache->AssetImportData; + } + */ + + if (ImportData) + { + if (FPaths::GetExtension(ImportData->GetFirstFilename()).Contains(TEXT("bgeo"))) + { + ImportData->ExtractFilenames(OutFilenames); + return true; + } + } + return false; +} + +void +UHoudiniGeoFactory::SetReimportPaths(UObject * Obj, const TArray& NewReimportPaths) +{ + UStaticMesh* Mesh = Cast(Obj); + if (Mesh && Mesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) + { + Mesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); + } + + /* + USkeletalMesh* SkeletalMesh = Cast(Obj); + if (SkeletalMesh && SkeletalMesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) + { + SkeletalMesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); + } + */ +} + + +UObject* +UHoudiniGeoFactory::Import(UClass* InClass, UPackage* InParent, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport) +{ + // Broadcast PreImport + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, FName(FileName), TEXT("Houdini GEO")); + + // Create a new Geo importer + TArray DummyOldOutputs; + TArray NewOutputs; + UHoudiniGeoImporter* BGEOImporter = NewObject(this); + BGEOImporter->AddToRoot(); + + // Clean up lambda + auto CleanUp = [&NewOutputs, &BGEOImporter]() + { + // Remove the importer and output objects from the root set + BGEOImporter->RemoveFromRoot(); + for (auto Out : NewOutputs) + Out->RemoveFromRoot(); + }; + + // Failure lambda + auto FailImportAndReturnNull = [this, &CleanUp, &NewOutputs, &BGEOImporter]() + { + CleanUp(); + + // Failed to read the file info, fail the import + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, nullptr); + + return nullptr; + }; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!BGEOImporter->AutoStartHoudiniEngineSessionIfNeeded()) + return FailImportAndReturnNull(); + + // 2. Update the file paths + if (!BGEOImporter->SetFilePath(AbsoluteFilePath)) + return FailImportAndReturnNull(); + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + if (!BGEOImporter->LoadBGEOFileInHAPI(NodeId)) + return FailImportAndReturnNull(); + + // Prepare the package used for creating the mesh, landscape and instancer pacakges + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + PackageParams.HoudiniAssetName = FString(); + PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); + PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); + + if (bReimport) + { + PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + } + else + { + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + } + + // 4. Get the output from the file node + if (!BGEOImporter->BuildOutputsForNode(NodeId, DummyOldOutputs, NewOutputs)) + return FailImportAndReturnNull(); + + // 5. Create the static meshes in the outputs + if (!BGEOImporter->CreateStaticMeshes(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 6. Create the curves in the outputs + if (!BGEOImporter->CreateCurves(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 7. Create the landscape in the outputs + if (!BGEOImporter->CreateLandscapes(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 8. Create the instancers in the outputs + if (!BGEOImporter->CreateInstancers(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 9. Delete the created node in Houdini + if (!BGEOImporter->DeleteCreatedNode(NodeId)) + { + // Not good, but not fatal.. + //return false; + } + + // Get our result object and "finalize" them + TArray Results = BGEOImporter->GetOutputObjects(); + for (UObject* Object : Results) + { + if (!Object || Object->IsPendingKill()) + continue; + + Object->SetFlags(Flags); + + UAssetImportData * AssetImportData = nullptr; + if (Object->IsA()) + { + UStaticMesh* SM = Cast(Object); + AssetImportData = SM->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); + SM->AssetImportData = AssetImportData; + } + } + /* + else if (Object->IsA()) + { + USkeletalMesh * SkeletalMesh = Cast(Object); + AssetImportData = SkeletalMesh->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject(SkeletalMesh, USkeletalMesh::StaticClass()); + SkeletalMesh->AssetImportData = AssetImportData; + } + } + */ + + if (AssetImportData) + AssetImportData->Update(AbsoluteFilePath); + + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, Object); + Object->MarkPackageDirty(); + Object->PostEditChange(); + } + + CleanUp(); + + // Determine out parent according to the generated assets outer + UObject* OutParent = (Results.Num() > 0 && InParent != Results[0]->GetOutermost()) ? Results[0]->GetOutermost() : InParent; + return (Results.Num() > 0) ? OutParent : nullptr; +} + +EReimportResult::Type +UHoudiniGeoFactory::Reimport(UObject * Obj) +{ + auto FailReimport = []() + { + // Notifiy we failed to load the bgeo + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); + return EReimportResult::Failed; + }; + + if (!Obj || Obj->IsPendingKill()) + return FailReimport(); + + UPackage* Package = Cast(Obj->GetOuter()); + if (!Package || Package->IsPendingKill()) + return FailReimport(); + + UAssetImportData* ImportData = nullptr; + if (Obj->GetClass() == UStaticMesh::StaticClass()) + { + UStaticMesh* StaticMesh = Cast(Obj); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return FailReimport(); + + ImportData = StaticMesh->AssetImportData; + } + /* + else if(Obj->GetClass() == USkeletalMesh::StaticClass()) + { + USkeletalMesh* SkeletalMesh = Cast(Obj); + if (!SkeletalMesh || SkeletalMesh->IsPendingKill()) + return FailReimport(); + + ImportData = SkeletalMesh->AssetImportData; + } + */ + if (!ImportData || ImportData->IsPendingKill()) + return FailReimport(); + + if (ImportData->GetSourceFileCount() <= 0) + return FailReimport(); + + const FString RelativeFileName = ImportData->SourceData.SourceFiles[0].RelativeFilename; + const FString FileExtension = FPaths::GetExtension(RelativeFileName); + FString FileName = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativeFileName); + if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) + return FailReimport(); + + if (!Import(Obj->GetClass(), Package, Obj->GetName(), FileName, Obj->GetFlags(), true)) + return FailReimport(); + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has Succeed.")); + + // Notifiy we're done loading the bgeo + FString Notification = TEXT("BGEO Importer: BGEO file re-imported succesfully."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + return EReimportResult::Succeeded; +} + +int32 +UHoudiniGeoFactory::GetPriority() const +{ + return ImportPriority; +} + + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h index a8190ba9d..8dd0a42cb 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h @@ -1,83 +1,83 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "EditorReimportHandler.h" -#include "Factories/Factory.h" -#include "HoudiniGeoFactory.generated.h" - -class UClass; -class UObject; -class FFeedbackContext; - -UCLASS(config = Editor) -class UHoudiniGeoFactory : public UFactory, public FReimportHandler -{ - GENERATED_UCLASS_BODY() - - public: - - // - // UFactory Interface - // - // Returns the name of the factory for menus - virtual FText GetDisplayName() const override; - // return true if it supports this class - virtual bool DoesSupportClass(UClass * Class) override; - // - virtual UClass* ResolveSupportedClass() override; - // - //virtual UClass* ResolveSupportedClass() override; - // Return true if we can import the file - virtual bool FactoryCanImport(const FString& Filename) override; - - // Create a new object by importing it from a file name. - virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, - EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, - FFeedbackContext* Warn, bool& bOutOperationCanceled) override; - - // - // FReimportHandler Interface - // - UObject* Import(UClass* InClass, UPackage* OuterObject, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport); - - // Check to see if we have a handler to manage the reimporting of the object - virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; - - // Sets the reimport path(s) for the specified object - virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; - // Attempt to reimport the specified object from its source - virtual EReimportResult::Type Reimport(UObject * Obj) override; - - //void ShowImportOptionsWindow(TSharedPtr& Options, FString FilePath, const UHoudiniGeoImporter& Importer); - - virtual int32 GetPriority() const override; - - // - // - // -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "EditorReimportHandler.h" +#include "Factories/Factory.h" +#include "HoudiniGeoFactory.generated.h" + +class UClass; +class UObject; +class FFeedbackContext; + +UCLASS(config = Editor) +class UHoudiniGeoFactory : public UFactory, public FReimportHandler +{ + GENERATED_UCLASS_BODY() + + public: + + // + // UFactory Interface + // + // Returns the name of the factory for menus + virtual FText GetDisplayName() const override; + // return true if it supports this class + virtual bool DoesSupportClass(UClass * Class) override; + // + virtual UClass* ResolveSupportedClass() override; + // + //virtual UClass* ResolveSupportedClass() override; + // Return true if we can import the file + virtual bool FactoryCanImport(const FString& Filename) override; + + // Create a new object by importing it from a file name. + virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, + EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, + FFeedbackContext* Warn, bool& bOutOperationCanceled) override; + + // + // FReimportHandler Interface + // + UObject* Import(UClass* InClass, UPackage* OuterObject, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport); + + // Check to see if we have a handler to manage the reimporting of the object + virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; + + // Sets the reimport path(s) for the specified object + virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; + // Attempt to reimport the specified object from its source + virtual EReimportResult::Type Reimport(UObject * Obj) override; + + //void ShowImportOptionsWindow(TSharedPtr& Options, FString FilePath, const UHoudiniGeoImporter& Importer); + + virtual int32 GetPriority() const override; + + // + // + // +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp index f6553d476..13b4057bd 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp @@ -1,260 +1,260 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleComponentVisualizer.h" - -#include "EditorViewportClient.h" - -#include "HoudiniHandleTranslator.h" -#include "HoudiniAssetComponent.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_HIT_PROXY(HHoudiniHandleVisProxy, HComponentVisProxy); - -HHoudiniHandleVisProxy::HHoudiniHandleVisProxy(const UActorComponent * InComponent) - : HComponentVisProxy(InComponent, HPP_Wireframe) -{} - - -FHoudiniHandleComponentVisualizerCommands::FHoudiniHandleComponentVisualizerCommands() - : TCommands< FHoudiniHandleComponentVisualizerCommands >( - "HoudiniHandleComponentVisualizer", - FText::FromString("Houdini handle Component Visualizer"), - NAME_None, - FEditorStyle::GetStyleSetName()) -{} - -void -FHoudiniHandleComponentVisualizerCommands::RegisterCommands() -{} - - -FHoudiniHandleComponentVisualizer::FHoudiniHandleComponentVisualizer() - : FComponentVisualizer() - , EditedComponent(nullptr) - , bEditing(false) -{ - FHoudiniHandleComponentVisualizerCommands::Register(); - VisualizerActions = MakeShareable(new FUICommandList); -} - -FHoudiniHandleComponentVisualizer::~FHoudiniHandleComponentVisualizer() -{ - FHoudiniHandleComponentVisualizerCommands::Unregister(); -} - -void -FHoudiniHandleComponentVisualizer::DrawVisualization(const UActorComponent * Component, - const FSceneView * View, FPrimitiveDrawInterface * PDI) -{ - const UHoudiniHandleComponent* HandleComponent = Cast(Component); - - if (!HandleComponent) - return; - - UHoudiniAssetComponent* HAC = Cast(HandleComponent->GetOuter()); - - if (!HAC) - return; - - - static TMap ColorMapActive; - static TMap ColorMapInactive; - - - int32 AssetId = HAC->GetAssetId(); - - if (!ColorMapActive.Contains(AssetId) || !ColorMapInactive.Contains(AssetId)) - { - FLinearColor NewActiveColor = FLinearColor::MakeRandomColor(); - FLinearColor NewInactiveColor = NewActiveColor.CopyWithNewOpacity(0.1)/2.5f; - - ColorMapActive.Add(AssetId, NewActiveColor); - ColorMapInactive.Add(AssetId, NewInactiveColor); - } - - - const FLinearColor& ActiveColor = ColorMapActive[AssetId]; - const FLinearColor& InactiveColor = ColorMapInactive[AssetId]; - - bool IsActive = EditedComponent != nullptr; - - if (IsActive) - { - UHoudiniAssetComponent* EditedComponentParent = Cast(EditedComponent->GetOuter()); - IsActive &= EditedComponentParent && EditedComponentParent->GetAssetId() == HAC->GetAssetId(); - } - - // Draw point and set hit box for it. - PDI->SetHitProxy(new HHoudiniHandleVisProxy(HandleComponent)); - { - static const float GrabHandleSizeActive = 24.0f; - static const float GrabHandleSizeInactive = 18.0f; - - PDI->DrawPoint(HandleComponent->GetComponentTransform().GetLocation(), IsActive ? ActiveColor : InactiveColor, IsActive ? GrabHandleSizeActive : GrabHandleSizeInactive, SDPG_Foreground); - } - - if (HandleComponent->HandleType == EHoudiniHandleType::Bounder) - { - // draw the scale box - FTransform BoxTransform = HandleComponent->GetComponentTransform(); - const float BoxRad = 50.f; - const FBox Box(FVector(-BoxRad, -BoxRad, -BoxRad), FVector(BoxRad, BoxRad, BoxRad)); - DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), Box, IsActive ? ActiveColor : InactiveColor, SDPG_Foreground); - } - - PDI->SetHitProxy(nullptr); -} - -bool -FHoudiniHandleComponentVisualizer::VisProxyHandleClick( - FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) -{ - bEditing = false; - - bAllowTranslate = false; - bAllowRotation = false; - bAllowScale = false; - - - if (VisProxy && VisProxy->Component.IsValid()) - { - const UHoudiniHandleComponent * Component = - CastChecked< const UHoudiniHandleComponent >(VisProxy->Component.Get()); - - const TArray &XformParms = Component->XformParms; - - if (!Component->CheckHandleValid()) - return bEditing; - - EditedComponent = const_cast(Component); - - if (Component) - { - if (VisProxy->IsA(HHoudiniHandleVisProxy::StaticGetType())) - bEditing = true; - - bAllowTranslate = - XformParms[int32(EXformParameter::TX)]->AssetParameter || - XformParms[int32(EXformParameter::TY)]->AssetParameter || - XformParms[int32(EXformParameter::TZ)]->AssetParameter; - - bAllowRotation = - XformParms[int32(EXformParameter::RX)]->AssetParameter || - XformParms[int32(EXformParameter::RY)]->AssetParameter || - XformParms[int32(EXformParameter::RZ)]->AssetParameter; - - bAllowScale = - XformParms[int32(EXformParameter::SX)]->AssetParameter || - XformParms[int32(EXformParameter::SY)]->AssetParameter || - XformParms[int32(EXformParameter::SZ)]->AssetParameter; - } - } - - return bEditing; -} - -void -FHoudiniHandleComponentVisualizer::EndEditing() -{ - EditedComponent = nullptr; -} - -bool -FHoudiniHandleComponentVisualizer::GetWidgetLocation( - const FEditorViewportClient * ViewportClient, - FVector & OutLocation) const -{ - if (EditedComponent) - { - OutLocation = EditedComponent->GetComponentTransform().GetLocation(); - return true; - } - - return false; -} - -bool -FHoudiniHandleComponentVisualizer::GetCustomInputCoordinateSystem( - const FEditorViewportClient * ViewportClient, - FMatrix & OutMatrix) const -{ - if (EditedComponent && ViewportClient->GetWidgetMode() == FWidget::WM_Scale) - { - OutMatrix = FRotationMatrix::Make(EditedComponent->GetComponentTransform().GetRotation()); - return true; - } - else - { - return false; - } -} - -bool -FHoudiniHandleComponentVisualizer::HandleInputDelta( - FEditorViewportClient * ViewportClient, FViewport * Viewport, - FVector& DeltaTranslate, FRotator & DeltaRotate, FVector & DeltaScale) -{ - if (!EditedComponent) - return false; - - if (!DeltaTranslate.IsZero() && bAllowTranslate) - { - EditedComponent->SetRelativeLocation(EditedComponent->GetRelativeTransform().GetLocation() + DeltaTranslate); - } - - if (!DeltaRotate.IsZero() && bAllowRotation) - { - EditedComponent->SetRelativeRotation(DeltaRotate.Quaternion() * EditedComponent->GetRelativeTransform().GetRotation()); - } - - if (!DeltaScale.IsZero() && bAllowScale) - { - EditedComponent->SetRelativeScale3D(EditedComponent->GetRelativeTransform().GetScale3D() + DeltaScale); - } - - return true; -} - -bool -FHoudiniHandleComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) -{ - if (EditedComponent) - { - if (Key == EKeys::LeftMouseButton && Event == IE_Released) - { - if (GEditor) - GEditor->RedrawLevelEditingViewports(true); - - FHoudiniHandleTranslator::UpdateTransformParameters(EditedComponent); - } - - } - return false; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleComponentVisualizer.h" + +#include "EditorViewportClient.h" + +#include "HoudiniHandleTranslator.h" +#include "HoudiniAssetComponent.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_HIT_PROXY(HHoudiniHandleVisProxy, HComponentVisProxy); + +HHoudiniHandleVisProxy::HHoudiniHandleVisProxy(const UActorComponent * InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) +{} + + +FHoudiniHandleComponentVisualizerCommands::FHoudiniHandleComponentVisualizerCommands() + : TCommands< FHoudiniHandleComponentVisualizerCommands >( + "HoudiniHandleComponentVisualizer", + FText::FromString("Houdini handle Component Visualizer"), + NAME_None, + FEditorStyle::GetStyleSetName()) +{} + +void +FHoudiniHandleComponentVisualizerCommands::RegisterCommands() +{} + + +FHoudiniHandleComponentVisualizer::FHoudiniHandleComponentVisualizer() + : FComponentVisualizer() + , EditedComponent(nullptr) + , bEditing(false) +{ + FHoudiniHandleComponentVisualizerCommands::Register(); + VisualizerActions = MakeShareable(new FUICommandList); +} + +FHoudiniHandleComponentVisualizer::~FHoudiniHandleComponentVisualizer() +{ + FHoudiniHandleComponentVisualizerCommands::Unregister(); +} + +void +FHoudiniHandleComponentVisualizer::DrawVisualization(const UActorComponent * Component, + const FSceneView * View, FPrimitiveDrawInterface * PDI) +{ + const UHoudiniHandleComponent* HandleComponent = Cast(Component); + + if (!HandleComponent) + return; + + UHoudiniAssetComponent* HAC = Cast(HandleComponent->GetOuter()); + + if (!HAC) + return; + + + static TMap ColorMapActive; + static TMap ColorMapInactive; + + + int32 AssetId = HAC->GetAssetId(); + + if (!ColorMapActive.Contains(AssetId) || !ColorMapInactive.Contains(AssetId)) + { + FLinearColor NewActiveColor = FLinearColor::MakeRandomColor(); + FLinearColor NewInactiveColor = NewActiveColor.CopyWithNewOpacity(0.1)/2.5f; + + ColorMapActive.Add(AssetId, NewActiveColor); + ColorMapInactive.Add(AssetId, NewInactiveColor); + } + + + const FLinearColor& ActiveColor = ColorMapActive[AssetId]; + const FLinearColor& InactiveColor = ColorMapInactive[AssetId]; + + bool IsActive = EditedComponent != nullptr; + + if (IsActive) + { + UHoudiniAssetComponent* EditedComponentParent = Cast(EditedComponent->GetOuter()); + IsActive &= EditedComponentParent && EditedComponentParent->GetAssetId() == HAC->GetAssetId(); + } + + // Draw point and set hit box for it. + PDI->SetHitProxy(new HHoudiniHandleVisProxy(HandleComponent)); + { + static const float GrabHandleSizeActive = 24.0f; + static const float GrabHandleSizeInactive = 18.0f; + + PDI->DrawPoint(HandleComponent->GetComponentTransform().GetLocation(), IsActive ? ActiveColor : InactiveColor, IsActive ? GrabHandleSizeActive : GrabHandleSizeInactive, SDPG_Foreground); + } + + if (HandleComponent->HandleType == EHoudiniHandleType::Bounder) + { + // draw the scale box + FTransform BoxTransform = HandleComponent->GetComponentTransform(); + const float BoxRad = 50.f; + const FBox Box(FVector(-BoxRad, -BoxRad, -BoxRad), FVector(BoxRad, BoxRad, BoxRad)); + DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), Box, IsActive ? ActiveColor : InactiveColor, SDPG_Foreground); + } + + PDI->SetHitProxy(nullptr); +} + +bool +FHoudiniHandleComponentVisualizer::VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) +{ + bEditing = false; + + bAllowTranslate = false; + bAllowRotation = false; + bAllowScale = false; + + + if (VisProxy && VisProxy->Component.IsValid()) + { + const UHoudiniHandleComponent * Component = + CastChecked< const UHoudiniHandleComponent >(VisProxy->Component.Get()); + + const TArray &XformParms = Component->XformParms; + + if (!Component->CheckHandleValid()) + return bEditing; + + EditedComponent = const_cast(Component); + + if (Component) + { + if (VisProxy->IsA(HHoudiniHandleVisProxy::StaticGetType())) + bEditing = true; + + bAllowTranslate = + XformParms[int32(EXformParameter::TX)]->AssetParameter || + XformParms[int32(EXformParameter::TY)]->AssetParameter || + XformParms[int32(EXformParameter::TZ)]->AssetParameter; + + bAllowRotation = + XformParms[int32(EXformParameter::RX)]->AssetParameter || + XformParms[int32(EXformParameter::RY)]->AssetParameter || + XformParms[int32(EXformParameter::RZ)]->AssetParameter; + + bAllowScale = + XformParms[int32(EXformParameter::SX)]->AssetParameter || + XformParms[int32(EXformParameter::SY)]->AssetParameter || + XformParms[int32(EXformParameter::SZ)]->AssetParameter; + } + } + + return bEditing; +} + +void +FHoudiniHandleComponentVisualizer::EndEditing() +{ + EditedComponent = nullptr; +} + +bool +FHoudiniHandleComponentVisualizer::GetWidgetLocation( + const FEditorViewportClient * ViewportClient, + FVector & OutLocation) const +{ + if (EditedComponent) + { + OutLocation = EditedComponent->GetComponentTransform().GetLocation(); + return true; + } + + return false; +} + +bool +FHoudiniHandleComponentVisualizer::GetCustomInputCoordinateSystem( + const FEditorViewportClient * ViewportClient, + FMatrix & OutMatrix) const +{ + if (EditedComponent && ViewportClient->GetWidgetMode() == FWidget::WM_Scale) + { + OutMatrix = FRotationMatrix::Make(EditedComponent->GetComponentTransform().GetRotation()); + return true; + } + else + { + return false; + } +} + +bool +FHoudiniHandleComponentVisualizer::HandleInputDelta( + FEditorViewportClient * ViewportClient, FViewport * Viewport, + FVector& DeltaTranslate, FRotator & DeltaRotate, FVector & DeltaScale) +{ + if (!EditedComponent) + return false; + + if (!DeltaTranslate.IsZero() && bAllowTranslate) + { + EditedComponent->SetRelativeLocation(EditedComponent->GetRelativeTransform().GetLocation() + DeltaTranslate); + } + + if (!DeltaRotate.IsZero() && bAllowRotation) + { + EditedComponent->SetRelativeRotation(DeltaRotate.Quaternion() * EditedComponent->GetRelativeTransform().GetRotation()); + } + + if (!DeltaScale.IsZero() && bAllowScale) + { + EditedComponent->SetRelativeScale3D(EditedComponent->GetRelativeTransform().GetScale3D() + DeltaScale); + } + + return true; +} + +bool +FHoudiniHandleComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) +{ + if (EditedComponent) + { + if (Key == EKeys::LeftMouseButton && Event == IE_Released) + { + if (GEditor) + GEditor->RedrawLevelEditingViewports(true); + + FHoudiniHandleTranslator::UpdateTransformParameters(EditedComponent); + } + + } + return false; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h index 40cc299ca..5eed6461b 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h @@ -1,114 +1,114 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ComponentVisualizer.h" -#include "Framework/Commands/Commands.h" -#include "Framework/Commands/UICommandList.h" - -#include "Components/ActorComponent.h" -#include "HoudiniHandleComponent.h" - -/** Base class for clickable editing proxies. **/ -struct HHoudiniHandleVisProxy : public HComponentVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniHandleVisProxy(const UActorComponent * InComponent); -}; - -/** Define commands for our component visualizer */ -class FHoudiniHandleComponentVisualizerCommands : public TCommands< FHoudiniHandleComponentVisualizerCommands > -{ -public: - - /** Constructor. **/ - FHoudiniHandleComponentVisualizerCommands(); - - /** Register commands. **/ - virtual void RegisterCommands() override; - -public: - - /** Command for adding a control point. **/ - TSharedPtr< FUICommandInfo > CommandAddControlPoint; - - /** Command for deleting a control point. **/ - TSharedPtr< FUICommandInfo > CommandDeleteControlPoint; -}; - - -/** Our handle visualizer. **/ -class FHoudiniHandleComponentVisualizer : public FComponentVisualizer -{ -public: - FHoudiniHandleComponentVisualizer(); - - virtual ~FHoudiniHandleComponentVisualizer(); - - /** FComponentVisualizer methods. **/ - - /** Draw visualization for the given component. **/ - virtual void DrawVisualization( - const UActorComponent * Component, const FSceneView * View, - FPrimitiveDrawInterface * PDI) override; - - /** Handle a click on a registered hit box. **/ - virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; - - virtual void EndEditing(); - - /** Returns location of a gizmo widget. **/ - virtual bool GetWidgetLocation( - const FEditorViewportClient *, FVector & OutLocation) const override; - - virtual bool GetCustomInputCoordinateSystem( - const FEditorViewportClient * ViewportClient, FMatrix & OutMatrix) const override; - - /** Handle input change. **/ - virtual bool HandleInputDelta( - FEditorViewportClient *, FViewport *, FVector & DeltaTranslate, - FRotator & DeltaRotate, FVector & DeltaScale) override; - - virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; - - void SetEditedComponent(UHoudiniHandleComponent* InComponent) { EditedComponent = InComponent; }; - void ClearEditedComponent() { EditedComponent = nullptr; }; - - -protected: - /** Visualizer actions. **/ - TSharedPtr< FUICommandList > VisualizerActions; - - /** Houdini component which is being edited. **/ - UHoudiniHandleComponent* EditedComponent; - - /** Is set to true if we are editing. **/ - uint32 bEditing : 1; - uint32 bAllowTranslate : 1; - uint32 bAllowRotation : 1; - uint32 bAllowScale : 1; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ComponentVisualizer.h" +#include "Framework/Commands/Commands.h" +#include "Framework/Commands/UICommandList.h" + +#include "Components/ActorComponent.h" +#include "HoudiniHandleComponent.h" + +/** Base class for clickable editing proxies. **/ +struct HHoudiniHandleVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniHandleVisProxy(const UActorComponent * InComponent); +}; + +/** Define commands for our component visualizer */ +class FHoudiniHandleComponentVisualizerCommands : public TCommands< FHoudiniHandleComponentVisualizerCommands > +{ +public: + + /** Constructor. **/ + FHoudiniHandleComponentVisualizerCommands(); + + /** Register commands. **/ + virtual void RegisterCommands() override; + +public: + + /** Command for adding a control point. **/ + TSharedPtr< FUICommandInfo > CommandAddControlPoint; + + /** Command for deleting a control point. **/ + TSharedPtr< FUICommandInfo > CommandDeleteControlPoint; +}; + + +/** Our handle visualizer. **/ +class FHoudiniHandleComponentVisualizer : public FComponentVisualizer +{ +public: + FHoudiniHandleComponentVisualizer(); + + virtual ~FHoudiniHandleComponentVisualizer(); + + /** FComponentVisualizer methods. **/ + + /** Draw visualization for the given component. **/ + virtual void DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI) override; + + /** Handle a click on a registered hit box. **/ + virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; + + virtual void EndEditing(); + + /** Returns location of a gizmo widget. **/ + virtual bool GetWidgetLocation( + const FEditorViewportClient *, FVector & OutLocation) const override; + + virtual bool GetCustomInputCoordinateSystem( + const FEditorViewportClient * ViewportClient, FMatrix & OutMatrix) const override; + + /** Handle input change. **/ + virtual bool HandleInputDelta( + FEditorViewportClient *, FViewport *, FVector & DeltaTranslate, + FRotator & DeltaRotate, FVector & DeltaScale) override; + + virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; + + void SetEditedComponent(UHoudiniHandleComponent* InComponent) { EditedComponent = InComponent; }; + void ClearEditedComponent() { EditedComponent = nullptr; }; + + +protected: + /** Visualizer actions. **/ + TSharedPtr< FUICommandList > VisualizerActions; + + /** Houdini component which is being edited. **/ + UHoudiniHandleComponent* EditedComponent; + + /** Is set to true if we are editing. **/ + uint32 bEditing : 1; + uint32 bAllowTranslate : 1; + uint32 bAllowRotation : 1; + uint32 bAllowScale : 1; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp index 1a162cfb1..613c1f094 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp @@ -1,400 +1,397 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniHandleDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniHandleComponent.h" -#include "HoudiniHandleTranslator.h" -#include "HoudiniHandleComponentVisualizer.h" - -#include "DetailCategoryBuilder.h" -#include "DetailWidgetRow.h" -#include "IDetailGroup.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Images/SImage.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -void -FHoudiniHandleDetails::CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles) -{ - - if (InHandles.Num() <= 0) - return; - - UHoudiniHandleComponent* MainHandle = InHandles[0]; - - if (!MainHandle || MainHandle->IsPendingKill()) - return; - - - FString HandleNameStr = MainHandle->GetHandleName() + TEXT(" (") + GetHandleTypeString(MainHandle->GetHandleType()) + TEXT(" )"); - FName HandleName = FName(*HandleNameStr); - IDetailGroup& Group = HouHandleCategory.AddGroup(HandleName, FText::FromString(HandleNameStr), false, false); - - // Create a widget row for this handle - FDetailWidgetRow& Row = Group.AddWidgetRow(); - - CreateNameWidget(Row); - - // Create value widget - - TSharedRef ValueWidgetVerticalBox = SNew(SVerticalBox); - - // Translate - auto OnLocationChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FVector Location = MainHandle->GetRelativeTransform().GetLocation(); - - if (Axis == 0) - Location.X = Val; - else if (Axis == 1) - Location.Y = Val; - else - Location.Z = Val; - - MainHandle->SetRelativeLocation(Location); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertLocationToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - FVector DefaultLocation = FVector(0.f, 0.f, 0.f); - MainHandle->SetRelativeLocation(DefaultLocation); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Z; }) - .OnXCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 0); - }) - .OnYCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 1); - }) - .OnZCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeLocation() == FVector::ZeroVector) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertLocationToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - // Rotation - auto OnRotationChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FQuat Rotation = MainHandle->GetRelativeTransform().GetRotation(); - - if (Axis == 0) - Rotation.X = Val; - else if (Axis == 1) - Rotation.Y = Val; - else - Rotation.Z = Val; - - MainHandle->SetRelativeRotation(Rotation); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertRotationToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - MainHandle->SetRelativeRotation(FQuat::Identity); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Z; }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 0); - }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 1); - }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeTransform().GetRotation() == FQuat::Identity) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertRotationToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - // Scale - auto OnScaleChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FVector Scale = MainHandle->GetRelativeTransform().GetScale3D(); - - if (Axis == 0) - Scale.X = Val; - else if (Axis == 1) - Scale.Y = Val; - else - Scale.Z = Val; - - MainHandle->SetRelativeScale3D(Scale); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertScaleToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - MainHandle->SetRelativeScale3D(FVector::OneVector); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Z; }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 0); - }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 1); - }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeTransform().GetScale3D() == FVector::OneVector) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertScaleToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - Row.ValueWidget.Widget = ValueWidgetVerticalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - - auto OnMouseEnterLambda = [MainHandle](const FGeometry& Geometry, const FPointerEvent& Event) - { - if (!MainHandle) - return; - - TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); - - if (HandleVisualizer.IsValid()) - { - HandleVisualizer->SetEditedComponent(MainHandle); - } - }; - - auto OnMouseLeaveLambda = [MainHandle](const FPointerEvent& Event) - { - if (!MainHandle) - return; - - TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); - - if (HandleVisualizer.IsValid()) - { - HandleVisualizer->SetEditedComponent(nullptr); - } - }; - - // Set on mouse leave UI widget callback functions - Row.NameWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); - Row.ValueWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); - - Row.NameWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); - Row.ValueWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); -} - -void -FHoudiniHandleDetails::CreateNameWidget(FDetailWidgetRow& Row) -{ - TSharedRef VerticalBox = SNew(SVerticalBox); - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Translate")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Rotation")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Scale")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - - Row.NameWidget.Widget = VerticalBox; -} - -FString -FHoudiniHandleDetails::GetHandleTypeString(const EHoudiniHandleType& HandleType) -{ - switch (HandleType) - { - case EHoudiniHandleType::Bounder: - return FString("Bounder"); - case EHoudiniHandleType::Xform: - return FString("Xform"); - case EHoudiniHandleType::Unsupported: - return FString("Unsupported"); - - default: - break; - } - - return FString(""); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleDetails.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniHandleTranslator.h" +#include "HoudiniHandleComponentVisualizer.h" + +#include "DetailCategoryBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailGroup.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Images/SImage.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +void +FHoudiniHandleDetails::CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles) +{ + + if (InHandles.Num() <= 0) + return; + + UHoudiniHandleComponent* MainHandle = InHandles[0]; + + if (!MainHandle || MainHandle->IsPendingKill()) + return; + + + FString HandleNameStr = MainHandle->GetHandleName() + TEXT(" (") + GetHandleTypeString(MainHandle->GetHandleType()) + TEXT(" )"); + FName HandleName = FName(*HandleNameStr); + IDetailGroup& Group = HouHandleCategory.AddGroup(HandleName, FText::FromString(HandleNameStr), false, false); + + // Create a widget row for this handle + FDetailWidgetRow& Row = Group.AddWidgetRow(); + + CreateNameWidget(Row); + + // Create value widget + + TSharedRef ValueWidgetVerticalBox = SNew(SVerticalBox); + + // Translate + auto OnLocationChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FVector Location = MainHandle->GetRelativeTransform().GetLocation(); + + if (Axis == 0) + Location.X = Val; + else if (Axis == 1) + Location.Y = Val; + else + Location.Z = Val; + + MainHandle->SetRelativeLocation(Location); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertLocationToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + FVector DefaultLocation = FVector(0.f, 0.f, 0.f); + MainHandle->SetRelativeLocation(DefaultLocation); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Z; }) + .OnXCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 0); + }) + .OnYCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 1); + }) + .OnZCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeLocation() == FVector::ZeroVector) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertLocationToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + // Rotation + auto OnRotationChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FQuat Rotation = MainHandle->GetRelativeTransform().GetRotation(); + + if (Axis == 0) + Rotation.X = Val; + else if (Axis == 1) + Rotation.Y = Val; + else + Rotation.Z = Val; + + MainHandle->SetRelativeRotation(Rotation); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertRotationToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + MainHandle->SetRelativeRotation(FQuat::Identity); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Z; }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 0); + }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 1); + }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeTransform().GetRotation() == FQuat::Identity) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertRotationToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + // Scale + auto OnScaleChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FVector Scale = MainHandle->GetRelativeTransform().GetScale3D(); + + if (Axis == 0) + Scale.X = Val; + else if (Axis == 1) + Scale.Y = Val; + else + Scale.Z = Val; + + MainHandle->SetRelativeScale3D(Scale); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertScaleToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + MainHandle->SetRelativeScale3D(FVector::OneVector); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Z; }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 0); + }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 1); + }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeTransform().GetScale3D() == FVector::OneVector) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertScaleToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + Row.ValueWidget.Widget = ValueWidgetVerticalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + + auto OnMouseEnterLambda = [MainHandle](const FGeometry& Geometry, const FPointerEvent& Event) + { + if (!MainHandle) + return; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); + + if (HandleVisualizer.IsValid()) + { + HandleVisualizer->SetEditedComponent(MainHandle); + } + }; + + auto OnMouseLeaveLambda = [MainHandle](const FPointerEvent& Event) + { + if (!MainHandle) + return; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); + + if (HandleVisualizer.IsValid()) + { + HandleVisualizer->SetEditedComponent(nullptr); + } + }; + + // Set on mouse leave UI widget callback functions + Row.NameWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); + Row.ValueWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); + + Row.NameWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); + Row.ValueWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); +} + +void +FHoudiniHandleDetails::CreateNameWidget(FDetailWidgetRow& Row) +{ + TSharedRef VerticalBox = SNew(SVerticalBox); + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Translate")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Rotation")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Scale")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + + Row.NameWidget.Widget = VerticalBox; +} + +FString +FHoudiniHandleDetails::GetHandleTypeString(const EHoudiniHandleType& HandleType) +{ + switch (HandleType) + { + case EHoudiniHandleType::Bounder: + return FString("Bounder"); + case EHoudiniHandleType::Xform: + return FString("Xform"); + case EHoudiniHandleType::Unsupported: + return FString("Unsupported"); + + default: + break; + } + + return FString(""); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h index e82898b34..14dfd7c8c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "DetailWidgetRow.h" - -class UHoudiniHandleComponent; -class IDetailCategoryBuilder; -enum class EHoudiniHandleType : uint8; - -class FHoudiniHandleDetails : public TSharedFromThis -{ -public: - static void CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles); - - static void CreateNameWidget(FDetailWidgetRow& Row); - - static FString GetHandleTypeString(const EHoudiniHandleType& HandleType); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "DetailWidgetRow.h" + +class UHoudiniHandleComponent; +class IDetailCategoryBuilder; +enum class EHoudiniHandleType : uint8; + +class FHoudiniHandleDetails : public TSharedFromThis +{ +public: + static void CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles); + + static void CreateNameWidget(FDetailWidgetRow& Row); + + static FString GetHandleTypeString(const EHoudiniHandleType& HandleType); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp index 6cd3311b2..d03008293 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp @@ -1,4974 +1,4972 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniInputDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniInput.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetBlueprintComponent.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniPackageParams.h" - -#include "Editor.h" -#include "DetailLayoutBuilder.h" -#include "DetailCategoryBuilder.h" -#include "IDetailCustomization.h" -#include "DetailWidgetRow.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SRotatorInputBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SNumericEntryBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SEditableText.h" -#include "Widgets/Layout/SUniformGridPanel.h" -#include "Editor/UnrealEd/Public/AssetThumbnail.h" -#include "Editor/PropertyEditor/Private/SDetailsViewBase.h" -#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" -#include "SAssetDropTarget.h" -#include "ScopedTransaction.h" -#include "Engine/SkeletalMesh.h" -#include "Engine/Selection.h" -#include "EngineUtils.h" -#include "AssetData.h" -#include "Framework/SlateDelegates.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "Modules/ModuleManager.h" -#include "SceneOutlinerModule.h" - -#include "Editor/UnrealEdEngine.h" -#include "HoudiniSplineComponentVisualizer.h" -#include "UnrealEdGlobals.h" -#include "Widgets/SWidget.h" - -#include "HoudiniEngineRuntimeUtils.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// Customized TextBlock to show 'editing...' text if this Houdini Spline Component is being edited -class SCurveEditingTextBlock : public STextBlock -{ -public: - UHoudiniSplineComponent* HoudiniSplineComponent; - TSharedPtr HoudiniSplineComponentVisualizer; -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override - { - if (!HoudiniSplineComponentVisualizer.IsValid() || !HoudiniSplineComponent) - return LayerId; - - if (HoudiniSplineComponentVisualizer->GetEditedHoudiniSplineComponent() != HoudiniSplineComponent) - return LayerId; - - return STextBlock::OnPaint(Args, AllottedGeometry, MyClippingRect, - OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - } -}; - -void -FHoudiniInputDetails::CreateWidget( - IDetailCategoryBuilder& HouInputCategory, - TArray InInputs, - FDetailWidgetRow* InputRow) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouInputCategory.GetParentLayout().GetThumbnailPool(); - - EHoudiniInputType MainInputType = MainInput->GetInputType(); - UHoudiniAssetComponent* HAC = MainInput->GetTypedOuter(); - - // Create a widget row, or get the given row. - FDetailWidgetRow* Row = InputRow; - Row = InputRow == nullptr ? &(HouInputCategory.AddCustomRow(FText::GetEmpty())) : InputRow; - if (!Row) - return; - - // Create the standard input name widget if this is not a operator path parameter. - // Operator path parameter's name widget is handled by HoudiniParameterDetails. - if (!InputRow) - CreateNameWidget(MainInput, *Row, true, InInputs.Num()); - - // Create a vertical Box for storing the UI - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - // ComboBox : Input Type - const IDetailsView* DetailsView = HouInputCategory.GetParentLayout().GetDetailsView(); - AddInputTypeComboBox(HouInputCategory, VerticalBox, InInputs, DetailsView); - - // Checkbox : Keep World Transform - AddKeepWorldTransformCheckBox(VerticalBox, InInputs); - - - // Checkbox : CurveInput trigger cook on curve changed - AddCurveInputCookOnChangeCheckBox(VerticalBox, InInputs); - - - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) - { - // Checkbox : Pack before merging - AddPackBeforeMergeCheckbox(VerticalBox, InInputs); - } - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World || MainInputType == EHoudiniInputType::Asset) - { - AddImportAsReferenceCheckbox(VerticalBox, InInputs); - } - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) - { - // Checkboxes : Export LODs / Sockets / Collisions - AddExportCheckboxes(VerticalBox, InInputs); - } - - switch (MainInput->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - AddGeometryInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); - } - break; - - case EHoudiniInputType::Asset: - { - AddAssetInputUI(VerticalBox, InInputs); - } - break; - - case EHoudiniInputType::Curve: - { - AddCurveInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); - } - break; - - case EHoudiniInputType::Landscape: - { - AddLandscapeInputUI(VerticalBox, InInputs); - } - break; - - case EHoudiniInputType::World: - { - AddWorldInputUI(HouInputCategory, VerticalBox, InInputs, DetailsView); - } - break; - - case EHoudiniInputType::Skeletal: - { - AddSkeletalInputUI(VerticalBox, InInputs, AssetThumbnailPool); - } - break; - } - - - Row->ValueWidget.Widget = VerticalBox; - - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - //Row.ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniInputDetails::CreateNameWidget( - UHoudiniInput* InInput, FDetailWidgetRow & Row, bool bLabel, int32 InInputCount) -{ - if (!InInput || InInput->IsPendingKill()) - return; - - FString InputLabelStr = InInput->GetLabel(); - if (InInputCount > 1) - { - InputLabelStr += TEXT(" (") + FString::FromInt(InInputCount) + TEXT(")"); - } - - const FText & FinalInputLabelText = bLabel ? FText::FromString(InputLabelStr) : FText::GetEmpty(); - FText InputTooltip = GetInputTooltip(InInput); - { - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(FinalInputLabelText) - .ToolTipText(InputTooltip) - .Font(FEditorStyle::GetFontStyle(!InInput->HasChanged() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))); - } -} - -FText -FHoudiniInputDetails::GetInputTooltip(UHoudiniInput* InParam) -{ - // TODO - return FText(); -} - -void -FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuilder, TSharedRef VerticalBox, TArray& InInputs, const IDetailsView* DetailsView) -{ - // Get the details view name and locked status - bool bDetailsLocked = false; - FName DetailsPanelName = "LevelEditorSelectionDetails"; - if (DetailsView) - { - DetailsPanelName = DetailsView->GetIdentifier(); - if (DetailsView->IsLocked()) - bDetailsLocked = true; - } - - // Lambda return a FText correpsonding to an input's current type - auto GetInputText = [](UHoudiniInput* InInput) - { - return FText::FromString(InInput->GetInputTypeAsString()); - }; - - // Lambda for changing inputs type - auto OnSelChanged = [DetailsPanelName, &CategoryBuilder](TArray InInputsToUpdate, TSharedPtr InNewChoice) - { - if (!InNewChoice.IsValid()) - return; - - EHoudiniInputType NewInputType = UHoudiniInput::StringToInputType(*InNewChoice.Get()); - if (NewInputType != EHoudiniInputType::World) - { - Helper_CancelWorldSelection(InInputsToUpdate, DetailsPanelName); - } - - if (InInputsToUpdate.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputsToUpdate[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetBlueprintComponent* HAB = MainInput->GetTypedOuter(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Input Type"), - MainInput->GetOuter()); - - bool bBlueprintStructureModified = false; - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetInputType() == NewInputType) - continue; - - /* This causes multiple issues. It does not set reset the previous type variable to Invalid sometimes - and it causes re-cook infinitely after few undo changing type. - { - CurInput->SetInputType(NewInputType); - CurInput->Modify(); - } - */ - - { - // Cache the current input type for undo type changing (since new type becomes previous type after undo) - EHoudiniInputType PrevType = CurInput->GetPreviousInputType(); - CurInput->SetPreviousInputType(NewInputType); - - CurInput->Modify(); - CurInput->SetPreviousInputType(PrevType); - CurInput->SetInputType(NewInputType, bBlueprintStructureModified); // pass in false for 2nd parameter in order to avoid creating default curve if empty - } - CurInput->MarkChanged(true); - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - - } - - if (HAB) - { - if (bBlueprintStructureModified) - HAB->MarkAsBlueprintStructureModified(); - } - - }; - - UHoudiniInput* MainInput = InInputs[0]; - TArray>* SupportedChoices = nullptr; - UHoudiniAssetBlueprintComponent* HAC = MainInput->GetTypedOuter(); - if (HAC) - { - SupportedChoices = FHoudiniEngineEditor::Get().GetBlueprintInputTypeChoiceLabels(); - } - else - { - SupportedChoices = FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels(); - } - - // ComboBox : Input Type - TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBoxInputType; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(ComboBoxInputType, SComboBox>) - .OptionsSource(SupportedChoices) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels())[((int32)MainInput->GetInputType() - 1)]) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([=](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnSelChanged(InInputs, NewChoice); - }) - [ - SNew( STextBlock ) - .Text_Lambda([=]() - { - return GetInputText(MainInput); - }) - .Font( FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; -} - -void -FHoudiniInputDetails:: AddCurveInputCookOnChangeCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - - if (!MainInput || MainInput->GetInputType() != EHoudiniInputType::Curve) - return; - - auto IsCheckedCookOnChange = [MainInput]() - { - if (!MainInput) - return ECheckBoxState::Checked; - - return MainInput->GetCookOnCurveChange() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto CheckStateChangedCookOnChange = [InInputs](ECheckBoxState NewState) - { - bool bChecked = NewState == ECheckBoxState::Checked; - for (auto & NextInput : InInputs) - { - if (!NextInput) - continue; - - NextInput->SetCookOnCurveChange(bChecked); - } - }; - - // Checkbox : Trigger cook on input curve changed - TSharedPtr< SCheckBox > CheckBoxCookOnCurveChanged; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxCookOnCurveChanged, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookOnCurveChangedCheckbox", "Auto-update")) - .ToolTipText(LOCTEXT("CookOnCurveChangeCheckboxTip", "When checked, cook is triggered automatically when the curve is modified.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedCookOnChange, MainInput]() - { - return IsCheckedCookOnChange(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedCookOnChange](ECheckBoxState NewState) - { - return CheckStateChangedCookOnChange( NewState); - }) - ]; - -} - -void -FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current KeepWorldTransform state - auto IsCheckedKeepWorldTransform = [&](UHoudiniInput* InInput) - { - return InInput->GetKeepWorldTransform() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing KeepWorldTransform state - auto CheckStateChangedKeepWorldTransform = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (MainInput->GetKeepWorldTransform() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Keep World Transform"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetKeepWorldTransform() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetKeepWorldTransform(bNewState); - CurInput->MarkChanged(true); - } - }; - - - // Checkbox : Keep World Transform - TSharedPtr< SCheckBox > CheckBoxTranformType; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxTranformType, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("KeepWorldTransformCheckbox", "Keep World Transform")) - .ToolTipText(LOCTEXT("KeepWorldTransformCheckboxTip", "Set this Input's object_merge Transform Type to INTO_THIS_OBJECT. If unchecked, it will be set to NONE.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedKeepWorldTransform(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedKeepWorldTransform(InInputs, NewState); - }) - ]; - - // the checkbox is read only for geo inputs - if (MainInput->GetInputType() == EHoudiniInputType::Geometry) - CheckBoxTranformType->SetEnabled(false); - - // Checkbox is read only if the input is an object-path parameter - //if (MainInput->IsObjectPathParameter() ) - // CheckBoxTranformType->SetEnabled(false); -} - -void -FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current PackBeforeMerge state - auto IsCheckedPackBeforeMerge = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetPackBeforeMerge() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing PackBeforeMerge state - auto CheckStateChangedPackBeforeMerge = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetPackBeforeMerge() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetPackBeforeMerge() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetPackBeforeMerge(bNewState); - CurInput->MarkChanged(true); - } - }; - - TSharedPtr< SCheckBox > CheckBoxPackBeforeMerge; - VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() - [ - SAssignNew( CheckBoxPackBeforeMerge, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "PackBeforeMergeCheckbox", "Pack Geometry before merging" ) ) - .ToolTipText( LOCTEXT( "PackBeforeMergeCheckboxTip", "Pack each separate piece of geometry before merging them into the input." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedPackBeforeMerge(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedPackBeforeMerge(InInputs, NewState); - }) - ]; -} - -void -FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current PackBeforeMerge state - auto IsCheckedImportAsReference= [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetImportAsReference() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing PackBeforeMerge state - auto CheckStateChangedImportAsReference= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetImportAsReference() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetImportAsReference() == bNewState) - continue; - - TArray * InputObjs = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (InputObjs) - { - // Mark all its input objects as changed to trigger recook. - for (auto CurInputObj : *InputObjs) - { - if (!CurInputObj || CurInputObj->IsPendingKill()) - continue; - - if (CurInputObj->GetImportAsReference() != bNewState) - CurInputObj->MarkChanged(true); - } - } - - CurInput->Modify(); - CurInput->SetImportAsReference(bNewState); - } - }; - - TSharedPtr< SCheckBox > CheckBoxImportAsReference; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxImportAsReference, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("ImportInputAsRefCheckbox", "Import input as references")) - .ToolTipText(LOCTEXT("ImportInputAsRefCheckboxTip", "Import input objects as references. (Geometry, World and Asset input types only)")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedImportAsReference(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedImportAsReference(InInputs, NewState); - }) - ]; -} -void -FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current ExportLODs state - auto IsCheckedExportLODs = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportLODs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda returning a CheckState from the input's current ExportSockets state - auto IsCheckedExportSockets = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportSockets() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda returning a CheckState from the input's current ExportColliders state - auto IsCheckedExportColliders = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportColliders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing ExportLODs state - auto CheckStateChangedExportLODs = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportLODs() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export LODs"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportLODs() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportLODs(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - // Lambda for changing ExportSockets state - auto CheckStateChangedExportSockets = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportSockets() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Sockets"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportSockets() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportSockets(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - // Lambda for changing ExportColliders state - auto CheckStateChangedExportColliders = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportColliders() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Colliders"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportColliders() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportColliders(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - TSharedPtr< SCheckBox > CheckBoxExportLODs; - TSharedPtr< SCheckBox > CheckBoxExportSockets; - TSharedPtr< SCheckBox > CheckBoxExportColliders; - VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew(CheckBoxExportLODs, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportAllLOD", "Export LODs" ) ) - .ToolTipText( LOCTEXT( "ExportAllLODCheckboxTip", "If enabled, all LOD Meshes in this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportLODs(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportLODs(InInputs, NewState); - }) - ] - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( CheckBoxExportSockets, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportSockets", "Export Sockets" ) ) - .ToolTipText( LOCTEXT( "ExportSocketsTip", "If enabled, all Mesh Sockets in this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportSockets(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportSockets(InInputs, NewState); - }) - ] - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( CheckBoxExportColliders, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportColliders", "Export Colliders" ) ) - .ToolTipText( LOCTEXT( "ExportCollidersTip", "If enabled, collision geometry for this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportColliders(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportColliders(InInputs, NewState); - }) - ] - ]; -} - -void -FHoudiniInputDetails::AddGeometryInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool ) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - - // Lambda for changing ExportColliders state - auto SetGeometryInputObjectsCount = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& NewInputCount) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing the number of Geometry Input Objects"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry) == NewInputCount) - continue; - - CurInput->Modify(); - - CurInput->SetInputObjectsNumber(EHoudiniInputType::Geometry, NewInputCount); - CurInput->MarkChanged(true); - - // - if (GEditor) - GEditor->RedrawAllViewports(); - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - }; - - InVerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs, NumInputObjects]() - { - return SetGeometryInputObjectsCount(InInputs, NumInputObjects + 1); - }), - LOCTEXT("AddInput", "Adds a Geometry Input"), true) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs]() - { - return SetGeometryInputObjectsCount(InInputs, 0); - }), - LOCTEXT("EmptyInputs", "Removes All Inputs"), true) - ] - ]; - - for (int32 GeometryObjectIdx = 0; GeometryObjectIdx < NumInputObjects; GeometryObjectIdx++) - { - //UObject* InputObject = InParam.GetInputObject(Idx); - Helper_CreateGeometryWidget(CategoryBuilder, InInputs, GeometryObjectIdx, AssetThumbnailPool, InVerticalBox); - } -} - - - -// Create a single geometry widget for the given input object -void -FHoudiniInputDetails::Helper_CreateGeometryWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InGeometryObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox ) -{ - UHoudiniInput* MainInput = InInputs[0]; - - // Access the object used in the corresponding geometry input - UHoudiniInputObject* HoudiniInputObject = MainInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - UObject* InputObject = HoudiniInputObject ? HoudiniInputObject->GetObject() : nullptr; - - // Create thumbnail for this static mesh. - TSharedPtr StaticMeshThumbnail = MakeShareable( - new FAssetThumbnail(InputObject, 64, 64, AssetThumbnailPool)); - - // Lambda for adding new geometry input objects - auto UpdateGeometryObjectAt = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& AtIndex, UObject* InObject) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - if (!InObject || InObject->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing a Geometry Input Object"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - UObject* InputObject = CurInput->GetInputObjectAt(EHoudiniInputType::Geometry, AtIndex); - if (InObject == InputObject) - continue; - - UHoudiniInputObject* CurrentInputObjectWrapper = CurInput->GetHoudiniInputObjectAt(AtIndex); - if (CurrentInputObjectWrapper) - CurrentInputObjectWrapper->Modify(); - - CurInput->Modify(); - - CurInput->SetInputObjectAt(EHoudiniInputType::Geometry, AtIndex, InObject); - CurInput->MarkChanged(true); - - // TODO: Not needed? - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - }; - - // Drop Target: Static/Skeletal Mesh - TSharedPtr HorizontalBox = NULL; - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop_Lambda([]( const UObject* InObject) - { - return UHoudiniInput::IsObjectAcceptable(EHoudiniInputType::Geometry, InObject); - }) - .OnAssetDropped_Lambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt](UObject* InObject) - { - return UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, InObject); - }) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - // Thumbnail : Static Mesh - FText ParameterLabelText = FText::FromString(MainInput->GetLabel()); - - TSharedPtr< SBorder > StaticMeshThumbnailBorder; - HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(StaticMeshThumbnailBorder, SBorder) - .Padding(5.0f) - .OnMouseDoubleClick_Lambda([MainInput, InGeometryObjectIdx](const FGeometry&, const FPointerEvent&) - { - UObject* InputObject = MainInput->GetInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - if (GEditor && InputObject) - GEditor->EditObject(InputObject); - - return FReply::Handled(); - }) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(ParameterLabelText) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - StaticMeshThumbnailBorder->SetBorderImage(TAttribute::Create( - TAttribute::FGetter::CreateLambda([StaticMeshThumbnailBorder]() - { - if (StaticMeshThumbnailBorder.IsValid() && StaticMeshThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ) ) ); - - FText MeshNameText = FText::GetEmpty(); - if (InputObject) - MeshNameText = FText::FromString(InputObject->GetName()); - - - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add Combo box : Static Mesh - TSharedPtr StaticMeshComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SAssignNew(StaticMeshComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(MeshNameText) - ] - ] - ]; - - - StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( - [MainInput, InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt]() - { - TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); - UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); - - TArray< UFactory * > NewAssetFactories; - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(DefaultObj), - true, - AllowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) - { - if (StaticMeshComboButton.IsValid()) - { - StaticMeshComboButton->SetIsOpen(false); - UObject * Object = AssetData.GetAsset(); - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); - } - }), - FSimpleDelegate::CreateLambda([]() {})); - })); - - - // Add buttons - TSharedPtr ButtonHorizontalBox; - ComboAndButtonBox->AddSlot() - .FillHeight(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ButtonHorizontalBox, SHorizontalBox) - ]; - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add( TEXT( "Asset" ), MeshNameText ); - FText StaticMeshTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", - "Browse to '{Asset}' in Content Browser" ), Args ); - - // Button : Use selected in content browser - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton(FSimpleDelegate::CreateLambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt]() - { - if (GEditor) - { - TArray CBSelections; - GEditor->GetContentBrowserSelections(CBSelections); - - // Get the first selected static mesh object - UObject* Object = nullptr; - for (auto & CurAssetData : CBSelections) - { - if (CurAssetData.AssetClass != UStaticMesh::StaticClass()->GetFName()) - continue; - - Object = CurAssetData.GetAsset(); - break; - } - - if (Object && !Object->IsPendingKill()) - { - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); - } - } - }), TAttribute< FText >(LOCTEXT("GeometryInputUseSelectedAssetFromCB", "Use Selected Asset from Content Browser"))) - ]; - - // Button : Browse Static Mesh - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateLambda([MainInput, InGeometryObjectIdx]() - { - UObject* InputObject = MainInput->GetInputObjectAt(InGeometryObjectIdx); - if (GEditor && InputObject) - { - TArray Objects; - Objects.Add(InputObject); - GEditor->SyncBrowserToObjects(Objects); - } - }), - TAttribute< FText >( StaticMeshTooltip ) - ) - ]; - - // ButtonBox : Reset - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default static mesh" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked_Lambda( [UpdateGeometryObjectAt, InInputs, InGeometryObjectIdx]() - { - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, nullptr); - return FReply::Handled(); - }) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ]; - - // Insert/Delete/Duplicate - ButtonHorizontalBox->AddSlot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( - FExecuteAction::CreateLambda( [ InInputs, InGeometryObjectIdx, MainInput ]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: insert a Geometry Input Object"), - MainInput->GetOuter()); - // Insert - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->InsertInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - } - } ), - FExecuteAction::CreateLambda([MainInput, InInputs, InGeometryObjectIdx]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: delete a Geometry Input Object"), - MainInput->GetOuter()); - - // Delete - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->DeleteInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - - if (GEditor) - GEditor->RedrawAllViewports(); - } - } ), - FExecuteAction::CreateLambda([InInputs, InGeometryObjectIdx, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), - MainInput->GetOuter()); - - // Duplicate - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->DuplicateInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - } - } ) ) - ]; - - // TRANSFORM OFFSET EXPANDER - { - TSharedPtr ExpanderArrow; - TSharedPtr ExpanderImage; - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( ExpanderArrow, SButton ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ClickMethod( EButtonClickMethod::MouseDown ) - .Visibility( EVisibility::Visible ) - .OnClicked(FOnClicked::CreateLambda([InInputs, InGeometryObjectIdx, MainInput, &CategoryBuilder]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled();; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), - MainInput->GetOuter()); - - // Expand transform - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->OnTransformUIExpand(InGeometryObjectIdx); - } - - // TODO: Not needed? - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - })) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ] - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( STextBlock ) - .Text( LOCTEXT("GeoInputTransform", "Transform Offset") ) - .ToolTipText( LOCTEXT( "GeoInputTransformTooltip", "Transform offset used for correction before sending the asset to Houdini" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - ]; - - // Set delegate for image - ExpanderImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda( [=]() { - FName ResourceName; - if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx) ) - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - return FEditorStyle::GetBrush( ResourceName ); - } ) ) ); - } - - // Lambda for changing the transform values - auto ChangeTransformOffsetAt = [&](const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex, const bool& DoChange, TArray InInputs) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputTransformChange", "Houdini Input: Changing Transform offset"), - InInputs[0]->GetOuter()); - - bool bChanged = true; - for (int Idx = 0; Idx < InInputs.Num(); Idx++) - { - if (!InInputs[Idx] || InInputs[Idx]->IsPendingKill()) - continue; - - UHoudiniInputObject* InputObject = InInputs[Idx]->GetHoudiniInputObjectAt(AtIndex); - if (InputObject) - InputObject->Modify(); - - bChanged &= InInputs[Idx]->SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); - } - - if (bChanged && DoChange) - { - // Mark the values as changed to trigger an update - for (int Idx = 0; Idx < InInputs.Num(); Idx++) - { - InInputs[Idx]->MarkChanged(true); - } - } - else - { - // Cancel the transaction - Transaction.Cancel(); - } - }; - - // Get Visibility of reset buttons - bool bResetButtonVisiblePosition = false; - bool bResetButtonVisibleRotation = false; - bool bResetButtonVisibleScale = false; - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - FTransform* CurTransform = CurInput->GetTransformOffset(InGeometryObjectIdx); - if (!CurTransform) - continue; - - if (CurTransform->GetLocation() != FVector::ZeroVector) - bResetButtonVisiblePosition = true; - - FRotator Rotator = CurTransform->Rotator(); - if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) - bResetButtonVisibleRotation = true; - - if (CurTransform->GetScale3D() != FVector::OneVector) - bResetButtonVisibleScale = true; - } - - auto ChangeTransformOffsetUniformlyAt = [InGeometryObjectIdx, InInputs, ChangeTransformOffsetAt](const float & Val, const int32& PosRotScaleIndex) - { - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 0, true, InInputs); - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 1, true, InInputs); - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 2, true, InInputs); - }; - - // TRANSFORM OFFSET - if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) - { - // Position - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew(STextBlock) - .Text( LOCTEXT("GeoInputTranslate", "T") ) - .ToolTipText( LOCTEXT( "GeoInputTranslateTooltip", "Translate" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SVectorInputBox ) - .bColorAxisLabels( true ) - .AllowSpin(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetX, InGeometryObjectIdx))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetY, InGeometryObjectIdx))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetZ, InGeometryObjectIdx))) - .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 0, true, InInputs); }) - .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 1, true, InInputs); }) - .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 2, true, InInputs); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([MainInput, ChangeTransformOffsetUniformlyAt, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 0); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - - // Rotation - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text( LOCTEXT("GeoInputRotate", "R") ) - .ToolTipText( LOCTEXT( "GeoInputRotateTooltip", "Rotate" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SRotatorInputBox ) - .AllowSpin( true ) - .bColorAxisLabels( true ) - .Roll(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetRoll, InGeometryObjectIdx))) - .Pitch(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetPitch, InGeometryObjectIdx))) - .Yaw(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetYaw, InGeometryObjectIdx))) - .OnRollCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 0, true, InInputs); }) - .OnPitchCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 1, true, InInputs); }) - .OnYawCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 2, true, InInputs); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (Not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 1); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - - // Scale - bool bLocked = false; - if (HoudiniInputObject) - bLocked = HoudiniInputObject->IsUniformScaleLocked(); - - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "GeoInputScale", "S" ) ) - .ToolTipText( LOCTEXT( "GeoInputScaleTooltip", "Scale" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SVectorInputBox ) - .bColorAxisLabels( true ) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetX, InGeometryObjectIdx))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetY, InGeometryObjectIdx))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetZ, InGeometryObjectIdx))) - .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 0, true, InInputs); - }) - .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 1, true, InInputs); - }) - .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 2, true, InInputs); - }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - SNew(SHorizontalBox) - // Lock Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ToolTipText(HoudiniInputObject ? - LOCTEXT("GeoInputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the input object maintains its shape in each direction when scaled") : - LOCTEXT("GeoInputLockButtonToolTipNoObject", "No input object selected")) - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(bLocked ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([InInputs, MainInput, InGeometryObjectIdx, HoudiniInputObject, &CategoryBuilder]() - { - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - UHoudiniInputObject*CurInputObject = CurInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - CurInputObject->SwitchUniformScaleLock(); - } - - if (HoudiniInputObject) - { - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); - }) - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(1.0f, 2); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - } -} - -void -FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Houdini Asset Picker Widget - { - FMenuBuilder MenuBuilder = Helper_CreateHoudiniAssetPickerWidget(InInputs); - - VerticalBox->AddSlot() - .Padding(2.0f, 2.0f, 5.0f, 2.0f) - .AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - // Button : Clear Selection - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - auto IsClearButtonEnabled = [MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return false; - - TArray* AssetInputObjectsArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - - if (!AssetInputObjectsArray) - return false; - - if (AssetInputObjectsArray->Num() <= 0) - return false; - - return true; - }; - - FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChangeClear", "Houdini Input: Clearing asset input selection"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - TArray* AssetInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - if (!AssetInputObjectsArray) - continue; - - CurrentInput->Modify(); - - AssetInputObjectsArray->Empty(); - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }); - - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - // Button : Clear Selection - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("ClearSelection", "Clear Selection")) - .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) - .IsEnabled_Lambda(IsClearButtonEnabled) - .OnClicked(OnClearSelect) - ] - ]; - - // Do not enable select all/clear select when selection has been started and details are locked - //HorizontalBox->SetEnabled(!bDetailsLocked); - } - - -} - -void -FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs, TSharedPtr AssetThumbnailPool) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Curve); - - // lambda for inserting an input Houdini curve. - auto InsertAnInputCurve = [MainInput, &CategoryBuilder](const int32& NewInputCount) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetComponent* OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - // Do not insert input object when the HAC does not finish cooking - EHoudiniAssetState CurrentHACState = OuterHAC->GetAssetState(); - if (CurrentHACState >= EHoudiniAssetState::PreCook && CurrentHACState<= EHoudiniAssetState::Processing) - return; - - // Clear the to be inserted object array, which records the pointers of the input objects to be inserted. - MainInput->LastInsertedInputs.Empty(); - // Record the pointer of the object to be inserted before transaction for undo the insert action. - bool bBlueprintStructureModified = false; - UHoudiniInputHoudiniSplineComponent* NewInput = MainInput->CreateHoudiniSplineInput(nullptr, true, false, bBlueprintStructureModified); - MainInput->LastInsertedInputs.Add(NewInput); - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction(FText::FromString("Modifying Houdini input: Adding curve input.")); - MainInput->Modify(); - - // Modify the MainInput. - MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType())->Add(NewInput); - - MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, NewInputCount); - - if (bBlueprintStructureModified) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([InsertAnInputCurve, NumInputObjects]() - { - return InsertAnInputCurve(NumInputObjects+1); - //return SetCurveInputObjectsCount(NumInputObjects+1); - }), - - LOCTEXT("AddInputCurve", "Adds a Curve Input"), true) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateLambda([InInputs, MainInput, &CategoryBuilder]() - { - TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - - // Detach all curves before deleting. - for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) - { - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - } - - // Clear the insert objects buffer before transaction. - MainInput->LastInsertedInputs.Empty(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction(FText::FromString("Modifying Houdini Input: Delete curve inputs.")); - MainInput->Modify(); - - bool bBlueprintStructureModified = false; - - // actual delete. - for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) - { - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - MainInput->RemoveSplineFromInputObject(HoudiniInput, bBlueprintStructureModified); - - MainInput->DeleteInputObjectAt(EHoudiniInputType::Curve, n); - } - - MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, 0); - - if (bBlueprintStructureModified) - { - UActorComponent* OuterComponent = Cast(MainInput->GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterComponent); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - }), - LOCTEXT("EmptyInputsCurve", "Removes All Curve Inputs"), true) - ] - + SHorizontalBox::Slot().FillWidth(80.f).MaxWidth(80.f) - [ - SNew(SButton) - .Text(LOCTEXT("ResetCurveOffsetStr", "Reset Offset")) - .OnClicked_Lambda([MainInput]()->FReply - { - MainInput->ResetDefaultCurveOffset(); - return FReply::Handled(); - }) - ] - ]; - - //UHoudiniSplineComponent* SplineCompBeingEdited = nullptr; - TSharedPtr HouSplineComponentVisualizer; - if (GUnrealEd) - { - TSharedPtr Visualizer = - GUnrealEd->FindComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); - - HouSplineComponentVisualizer = StaticCastSharedPtr(Visualizer); - } - - - for (int n = 0; n < NumInputObjects; n++) - { - Helper_CreateCurveWidget(CategoryBuilder, InInputs, n, AssetThumbnailPool ,VerticalBox, HouSplineComponentVisualizer); - } -} - -void -FHoudiniInputDetails::Helper_CreateCurveWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InCurveObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox, - TSharedPtr HouSplineComponentVisualizer) -{ - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetComponent * OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - auto GetHoudiniSplineComponentAtIndex = [](UHoudiniInput * Input, int32 Index) - { - UHoudiniSplineComponent* FoundHoudiniSplineComponent = nullptr; - if (!Input || Input->IsPendingKill()) - return FoundHoudiniSplineComponent; - - // Get the TArray ptr to the curve objects in this input - TArray * CurveInputComponentArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - return FoundHoudiniSplineComponent; - - if (!CurveInputComponentArray->IsValidIndex(Index)) - return FoundHoudiniSplineComponent; - - // Access the object used in the corresponding Houdini curve input - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - FoundHoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - - return FoundHoudiniSplineComponent; - }; - - - // Get the TArray ptr to the curve objects in this input - TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - return; - - if (!CurveInputComponentArray->IsValidIndex(InCurveObjectIdx)) - return; - - // Access the object used in the corresponding Houdini curve input - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[InCurveObjectIdx]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent) - return; - - FString HoudiniSplineName = HoudiniSplineComponent->GetHoudiniSplineName(); - - // Editable label for the current Houdini curve - TSharedPtr LabelHorizontalBox; - VerticalBox->AddSlot() - .Padding(0, 2) - .AutoHeight() - [ - SAssignNew(LabelHorizontalBox, SHorizontalBox) - ]; - - - TSharedPtr LabelBlock; - LabelHorizontalBox->AddSlot() - .Padding(0, 15, 0, 2) - .MaxWidth(150.f) - .FillWidth(150.f) - .VAlign(VAlign_Bottom) - .HAlign(HAlign_Left) - [ - SNew(SBox).HeightOverride(20.f).WidthOverride(200.f).VAlign(VAlign_Center) - [ - SAssignNew(LabelBlock, SEditableText).Text(FText::FromString(HoudiniSplineName)) - .OnTextCommitted_Lambda([HoudiniSplineComponent](FText NewText, ETextCommit::Type CommitType) - { - if (CommitType == ETextCommit::Type::OnEnter) - { - HoudiniSplineComponent->SetHoudiniSplineName(NewText.ToString()); - } - }) - ] - ]; - - // 'Editing...' TextBlock showing if this component is being edited - TSharedPtr EditingTextBlock; - LabelHorizontalBox->AddSlot() - .Padding(0, 15, 0, 2) - .MaxWidth(55.f) - .FillWidth(55.f) - .VAlign(VAlign_Bottom) - .HAlign(HAlign_Left) - [ - SNew(SBox).HeightOverride(20.f).WidthOverride(75.f).VAlign(VAlign_Center) - [ - SAssignNew(EditingTextBlock, SCurveEditingTextBlock).Text(LOCTEXT("HoudiniCurveInputEditingLabel", "(editing...)")) - ] - ]; - - EditingTextBlock->HoudiniSplineComponent = HoudiniSplineComponent; - EditingTextBlock->HoudiniSplineComponentVisualizer = HouSplineComponentVisualizer; - - // Lambda for deleting the current curve input - auto DeleteHoudiniCurveAtIndex = [InInputs, InCurveObjectIdx, OuterHAC, CurveInputComponentArray, &CategoryBuilder]() - { - if (!OuterHAC|| OuterHAC->IsPendingKill()) - return; - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeDeleteACurve", "Houdini Input: Deleting a curve input"), - OuterHAC); - - int MainInputCurveArraySize = CurveInputComponentArray->Num(); - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - Input->Modify(); - - TArray* InputObjectArr = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!InputObjectArr) - continue; - - if (!InputObjectArr->IsValidIndex(InCurveObjectIdx)) - continue; - - if (MainInputCurveArraySize != InputObjectArr->Num()) - continue; - - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast((*InputObjectArr)[InCurveObjectIdx]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - return; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent) - return; - - // Detach the spline component before delete. - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - - // This input is marked changed when an input component is deleted. - Input->DeleteInputObjectAt(EHoudiniInputType::Curve, InCurveObjectIdx); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - // Add delete button UI - LabelHorizontalBox->AddSlot().Padding(0, 2, 0, 2).HAlign(HAlign_Right).VAlign(VAlign_Bottom).AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([DeleteHoudiniCurveAtIndex]() - { - return DeleteHoudiniCurveAtIndex(); - })) - ]; - - - TSharedPtr HorizontalBox = NULL; - VerticalBox->AddSlot().Padding(0, 2).AutoHeight()[SAssignNew(HorizontalBox, SHorizontalBox)]; - - // Closed check box - // Lambda returning a closed state - auto IsCheckedClosedCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsClosedCurve() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing Closed state - auto CheckStateChangedClosedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeClosed", "Houdini Input: Changing Curve Closed"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->IsClosedCurve() == bNewState) - continue; - - HoudiniSplineComponent->Modify(); - - HoudiniSplineComponent->SetClosedCurve(bNewState); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Add Closed check box UI - TSharedPtr CheckBoxClosed = NULL; - HorizontalBox->AddSlot().Padding(0, 2).AutoWidth() - [ - SAssignNew(CheckBoxClosed, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("ClosedCurveCheckBox", "Closed")) - .ToolTipText(LOCTEXT("ClosedCurveCheckboxTip", "Close this input curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedClosedCurve]() - { - return IsCheckedClosedCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedClosedCurve](ECheckBoxState NewState) - { - return CheckStateChangedClosedCurve(NewState); - }) - ]; - - // Reversed check box - // Lambda returning a reversed state - auto IsCheckedReversedCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsReversed() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing reversed state - auto CheckStateChangedReversedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeReversed", "Houdini Input: Changing Curve Reversed"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->IsReversed() == bNewState) - continue; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetReversed(bNewState); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Add reversed check box UI - TSharedPtr CheckBoxReversed = NULL; - HorizontalBox->AddSlot() - .Padding(2, 2) - .AutoWidth() - [ - SAssignNew(CheckBoxReversed, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("ReversedCurveCheckBox", "Reversed")) - .ToolTipText(LOCTEXT("ReversedCurveCheckboxTip", "Reverse this input curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedReversedCurve]() - { - return IsCheckedReversedCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedReversedCurve](ECheckBoxState NewState) - { - return CheckStateChangedReversedCurve(NewState); - }) - ]; - - // Visible check box - // Lambda returning a visible state - auto IsCheckedVisibleCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsHoudiniSplineVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing visible state - auto CheckStateChangedVisibleCurve = [GetHoudiniSplineComponentAtIndex, InInputs, OuterHAC, InCurveObjectIdx](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent) - continue; - - if (HoudiniSplineComponent->IsHoudiniSplineVisible() == bNewState) - return; - - HoudiniSplineComponent->SetHoudiniSplineVisible(bNewState); - } - - if (GEditor) - GEditor->RedrawAllViewports(); - - }; - - // Add visible check box UI - TSharedPtr CheckBoxVisible = NULL; - HorizontalBox->AddSlot().Padding(2, 2).AutoWidth() - [ - SAssignNew(CheckBoxVisible, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("VisibleCurveCheckBox", "Visible")) - .ToolTipText(LOCTEXT("VisibleCurveCheckboxTip", "Set the visibility of this curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedVisibleCurve]() - { - return IsCheckedVisibleCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedVisibleCurve](ECheckBoxState NewState) - { - return CheckStateChangedVisibleCurve(NewState); - }) - ]; - - // Curve type comboBox - // Lambda for changing Houdini curve type - auto OnCurveTypeChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - if (!InNewChoice.IsValid()) - return; - - EHoudiniCurveType NewInputType = UHoudiniInput::StringToHoudiniCurveType(*InNewChoice.Get()); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeType", "Houdini Input: Changing Curve Type"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->GetCurveType() == NewInputType) - continue; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetCurveType(NewInputType); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Lambda for getting Houdini curve type - auto GetCurveTypeText = [HoudiniSplineComponent]() - { - return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(HoudiniSplineComponent->GetCurveType())); - }; - - // Add curve type combo box UI - TSharedPtr CurveTypeHorizontalBox; - VerticalBox->AddSlot() - .Padding(0, 2, 2, 0) - .AutoHeight() - [ - SAssignNew(CurveTypeHorizontalBox, SHorizontalBox) - ]; - - // Add curve type label UI - CurveTypeHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() - [ - SNew(STextBlock).Text(LOCTEXT("CurveTypeText", "Curve Type ")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveType; - CurveTypeHorizontalBox->AddSlot() - .Padding(2, 2, 5, 2) - .FillWidth(150.f) - .MaxWidth(150.f) - [ - SAssignNew(ComboBoxCurveType, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels())[(int)HoudiniSplineComponent->GetCurveType()]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnCurveTypeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnCurveTypeChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([GetCurveTypeText]() - { - return GetCurveTypeText(); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - // Houdini curve method combo box - // Lambda for changing Houdini curve method - auto OnCurveMethodChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - if (!InNewChoice.IsValid()) - return; - - EHoudiniCurveMethod NewInputMethod = UHoudiniInput::StringToHoudiniCurveMethod(*InNewChoice.Get()); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeMethod", "Houdini Input: Changing Curve Method"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->GetCurveMethod() == NewInputMethod) - return; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetCurveMethod(NewInputMethod); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Lambda for getting Houdini curve method - auto GetCurveMethodText = [HoudiniSplineComponent]() - { - return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(HoudiniSplineComponent->GetCurveMethod())); - }; - - // Add curve method combo box UI - TSharedPtr< SHorizontalBox > CurveMethodHorizontalBox; - VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(CurveMethodHorizontalBox, SHorizontalBox)]; - - // Add curve method label UI - CurveMethodHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() - [ - SNew(STextBlock).Text(LOCTEXT("CurveMethodText", "Curve Method ")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveMethod; - CurveMethodHorizontalBox->AddSlot().Padding(2, 2, 5, 2).FillWidth(150.f).MaxWidth(150.f) - [ - SAssignNew(ComboBoxCurveMethod, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels())[(int)HoudiniSplineComponent->GetCurveMethod()]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnCurveMethodChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnCurveMethodChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([GetCurveMethodText]() - { - return GetCurveMethodText(); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - auto BakeInputCurveLambda = [](TArray Inputs, int32 Index, bool bBakeToBlueprint) - { - for (auto & NextInput : Inputs) - { - if (!NextInput || NextInput->IsPendingKill()) - continue; - - UHoudiniAssetComponent * OuterHAC = Cast(NextInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - continue; - - AActor * OwnerActor = OuterHAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - continue; - - TArray * CurveInputComponentArray = NextInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - continue; - - if (!CurveInputComponentArray->IsValidIndex(Index)) - continue; - - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - FHoudiniPackageParams PackageParams; - PackageParams.BakeFolder = OuterHAC->BakeFolder.Path; - PackageParams.HoudiniAssetName = OuterHAC->GetName(); - PackageParams.GeoId = NextInput->GetAssetNodeId(); - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.ObjectId = Index; - PackageParams.ObjectName = OwnerActor->GetName() + "InputHoudiniSpline" + FString::FromInt(Index); - - if (bBakeToBlueprint) - { - FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( - HoudiniSplineComponent, - PackageParams, - OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); - } - else - { - FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - HoudiniSplineComponent, - PackageParams, - OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); - } - } - - return FReply::Handled(); - }; - - // Add input curve bake button - TSharedPtr< SHorizontalBox > InputCurveBakeHorizontalBox; - VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(InputCurveBakeHorizontalBox, SHorizontalBox)]; - VerticalBox->AddSlot() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(110.f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("HoudiniInputCurveBakeToActorButton", "Bake to Actor")) - .IsEnabled(true) - .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() - { - return BakeInputCurveLambda(InInputs, InCurveObjectIdx, false); - }) - .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToActorButtonToolTip", "Bake this input curve to Actor")) - ] - - + SHorizontalBox::Slot().MaxWidth(110.f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("HoudiniInputCurveBakeToBPButton", "Bake to Blueprint")) - .IsEnabled(true) - .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() - { - return BakeInputCurveLambda(InInputs, InCurveObjectIdx, true); - }) - .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToBPButtonToolTip", "Bake this input curve to Blueprint")) - ] - ]; - - // Do we actually need to set enable the UI components? - if (MainInput->GetInputType() == EHoudiniInputType::Curve) - { - LabelBlock->SetEnabled(true); - CheckBoxClosed->SetEnabled(true); - CheckBoxReversed->SetEnabled(true); - CheckBoxVisible->SetEnabled(true); - ComboBoxCurveType->SetEnabled(true); - ComboBoxCurveMethod->SetEnabled(true); - } - else - { - LabelBlock->SetEnabled(false); - CheckBoxClosed->SetEnabled(false); - CheckBoxReversed->SetEnabled(false); - CheckBoxVisible->SetEnabled(false); - ComboBoxCurveType->SetEnabled(false); - ComboBoxCurveMethod->SetEnabled(false); - } -} - -void -FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - // Lambda returning a CheckState from the input's current KeepWorldTransform state - auto IsCheckedUpdateInputLandscape = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetUpdateInputLandscape() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing KeepWorldTransform state - auto CheckStateChangedUpdateInputLandscape = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangedUpdate", "Houdini Input: Changing Keep World Transform"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (bNewState == CurInput->GetUpdateInputLandscape()) - continue; - - CurInput->Modify(); - - UHoudiniAssetComponent* HAC = Cast(CurInput->GetOuter()); - if (!HAC) - continue; - - TArray* LandscapeInputObjects = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (!LandscapeInputObjects) - continue; - - for (UHoudiniInputObject* NextInputObj : *LandscapeInputObjects) - { - UHoudiniInputLandscape* CurrentInputLandscape = Cast(NextInputObj); - if (!CurrentInputLandscape) - continue; - - ALandscapeProxy* CurrentInputLandscapeProxy = CurrentInputLandscape->GetLandscapeProxy(); - if (!CurrentInputLandscapeProxy) - continue; - - if (bNewState) - { - // We want to update this landscape data directly, start by backing it up to image files in the temp folder - FString BackupBaseName = HAC->TemporaryCookFolder.Path - + TEXT("/") - + CurrentInputLandscapeProxy->GetName() - + TEXT("_") - + HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); - - // We need to cache the input landscape to a file - FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(BackupBaseName, CurrentInputLandscapeProxy); - - // Cache its transform on the input - CurrentInputLandscape->CachedInputLandscapeTraqnsform = CurrentInputLandscapeProxy->ActorToWorld(); - - HAC->SetMobility(EComponentMobility::Static); - CurrentInputLandscapeProxy->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); - } - else - { - // We are not updating this input landscape anymore, detach it and restore its backed-up values - CurrentInputLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - - // Restore the input landscape's backup data - FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(CurrentInputLandscapeProxy); - - // Reapply the source Landscape's transform - CurrentInputLandscapeProxy->SetActorTransform(CurrentInputLandscape->CachedInputLandscapeTraqnsform); - - // TODO: - // Clear the input obj map? - } - } - - CurInput->bUpdateInputLandscape = (NewState == ECheckBoxState::Checked); - CurInput->MarkChanged(true); - } - }; - - // CheckBox : Update Input Landscape Data - TSharedPtr< SCheckBox > CheckBoxUpdateInput; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew( CheckBoxUpdateInput, SCheckBox).Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) - .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedUpdateInputLandscape, MainInput]() - { - return IsCheckedUpdateInputLandscape(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedUpdateInputLandscape, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedUpdateInputLandscape(InInputs, NewState); - }) - ]; - - // Actor picker: Landscape. - FMenuBuilder MenuBuilder = Helper_CreateLandscapePickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - - // Checkboxes : Export landscape as Heightfield/Mesh/Points - { - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAs", "Export Landscape As")) - .ToolTipText(LOCTEXT("LandscapeExportAsToolTip", "Choose the type of data you want the ladscape to be exported to:\n * Heightfield\n * Mesh\n * Points")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr ButtonOptionsPanel; - VerticalBox->AddSlot().Padding(5, 2, 5, 2).AutoHeight() - [ - SAssignNew(ButtonOptionsPanel, SUniformGridPanel) - ]; - - auto IsCheckedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) - { - if (!Input || Input->IsPendingKill()) - return ECheckBoxState::Unchecked; - - if (Input->GetLandscapeExportType() == LandscapeExportType) - return ECheckBoxState::Checked; - else - return ECheckBoxState::Unchecked; - }; - - auto CheckStateChangedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) - { - if (!Input || Input->IsPendingKill()) - return false; - - if (Input->GetLandscapeExportType() == LandscapeExportType) - return false; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changed Landscape export type."), - Input->GetOuter()); - Input->Modify(); - - Input->SetLandscapeExportType(LandscapeExportType); - Input->SetHasLandscapeExportTypeChanged(true); - Input->MarkChanged(true); - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return true; - - for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) - { - if (!NextInputObj) - continue; - NextInputObj->MarkChanged(true); - } - - return true; - }; - - // Heightfield - FText HeightfieldTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a heightfield."); - ButtonOptionsPanel->AddSlot(0, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Start") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Heightfield); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for(auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Heightfield); - }) - .ToolTipText(HeightfieldTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("ClassIcon.LandscapeComponent")) - ] - + SHorizontalBox::Slot() - .FillWidth(1.f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsHeightfieldCheckbox", "Heightfield")) - .ToolTipText(HeightfieldTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - - // Mesh - FText MeshTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a mesh."); - ButtonOptionsPanel->AddSlot(1, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Mesh); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for (auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Mesh); - }) - .ToolTipText(MeshTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("ClassIcon.StaticMeshComponent")) - ] - - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsMeshCheckbox", "Mesh")) - .ToolTipText(MeshTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - - // Points - FText PointsTooltip = LOCTEXT("LandscapeExportAsPointsTooltip", "If enabled, the landscape will be exported to Houdini as points."); - ButtonOptionsPanel->AddSlot(2, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.End") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Points); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for (auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Points); - }) - .ToolTipText(PointsTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("Mobility.Static")) - ] - - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsPointsCheckbox", "Points")) - .ToolTipText(PointsTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - } - - // CheckBox : Export selected components only - { - TSharedPtr< SCheckBox > CheckBoxExportSelected; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportSelected, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeSelectedCheckbox", "Export Selected Landscape Components Only")) - .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, only the selected Landscape Components will be exported.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportSelectionOnly ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportSelectionOnly", "Houdini Input: Changing Landscape export only selected component."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportSelectionOnly) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportSelectionOnly = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - } - - // Checkbox: auto select components - { - TSharedPtr< SCheckBox > CheckBoxAutoSelectComponents; - VerticalBox->AddSlot().Padding(10, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxAutoSelectComponents, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("AutoSelectComponentCheckbox", "Auto-select component in asset bounds")) - .ToolTipText(LOCTEXT("AutoSelectComponentCheckboxTooltip", "If enabled, when no Landscape components are curremtly selected, the one within the asset's bounding box will be exported.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeAutoSelectComponent ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeAutoSelectComponent", "Houdini Input: Changing Landscape input auto-selects components."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeAutoSelectComponent) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeAutoSelectComponent = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - // Enable only when exporting selection or when exporting heighfield (for now) - bool bEnable = false; - for (auto CurrentInput : InInputs) - { - if (!MainInput->bLandscapeExportSelectionOnly) - continue; - - bEnable = true; - break; - } - CheckBoxAutoSelectComponents->SetEnabled(bEnable); - } - - - // The following checkbox are only added when not in heightfield mode - if (MainInput->LandscapeExportType != EHoudiniLandscapeExportType::Heightfield) - { - // Checkbox : Export materials - { - TSharedPtr< SCheckBox > CheckBoxExportMaterials; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportMaterials, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeMaterialsCheckbox", "Export Landscape Materials")) - .ToolTipText(LOCTEXT("LandscapeMaterialsTooltip", "If enabled, the landscape materials will be exported with it.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportMaterials", "Houdini Input: Changing Landscape input export materials."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportMaterials) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportMaterials = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportMaterials->SetEnabled(false); - */ - } - - // Checkbox : Export Tile UVs - { - TSharedPtr< SCheckBox > CheckBoxExportTileUVs; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportTileUVs, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeTileUVsCheckbox", "Export Landscape Tile UVs")) - .ToolTipText(LOCTEXT("LandscapeTileUVsTooltip", "If enabled, UVs will be exported separately for each Landscape tile.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportTileUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportTileUVs", "Houdini Input: Changing Landscape export tile UVs."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportTileUVs) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportTileUVs = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportTileUVs->SetEnabled(false); - */ - } - - // Checkbox : Export normalized UVs - { - TSharedPtr< SCheckBox > CheckBoxExportNormalizedUVs; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportNormalizedUVs, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeNormalizedUVsCheckbox", "Export Landscape Normalized UVs")) - .ToolTipText(LOCTEXT("LandscapeNormalizedUVsTooltip", "If enabled, landscape UVs will be exported in [0, 1].")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportNormalizedUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Changing Landscape export normalized UVs."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportNormalizedUVs) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportNormalizedUVs = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportNormalizedUVs->SetEnabled(false); - */ - } - - // Checkbox : Export lighting - { - TSharedPtr< SCheckBox > CheckBoxExportLighting; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportLighting, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeLightingCheckbox", "Export Landscape Lighting")) - .ToolTipText(LOCTEXT("LandscapeLightingTooltip", "If enabled, lightmap information will be exported with the landscape.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportLighting", "Houdini Input: Changing Landscape export lighting."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportLighting) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportLighting = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportLighting->SetEnabled(false); - */ - } - - } - - // Button : Recommit - { - auto OnButtonRecommitClicked = [InInputs]() - { - for (auto CurrentInput : InInputs) - { - TArray* LandscapeInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); - if (!LandscapeInputObjectsArray) - continue; - - for (UHoudiniInputObject* NextLandscapeInput : *LandscapeInputObjectsArray) - { - if (!NextLandscapeInput) - continue; - - NextLandscapeInput->MarkChanged(true); - } - - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1, 2, 4, 2) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("LandscapeInputRecommit", "Recommit Landscape")) - .ToolTipText(LOCTEXT("LandscapeInputRecommitTooltip", "Recommits the Landscape to Houdini.")) - .OnClicked_Lambda(OnButtonRecommitClicked) - ] - ]; - } - - - // Button : Clear Selection - { - auto IsClearButtonEnabled = [MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return false; - - if (MainInput->GetInputType() != EHoudiniInputType::Landscape) - return false; - - TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); - if (!MainInputObjectsArray) - return false; - - if (MainInputObjectsArray->Num() <= 0) - return false; - - return true; - }; - - auto OnButtonClearClicked = [InInputs]() - { - if (InInputs.Num() <= 0) - return FReply::Handled(); - - UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - if (MainInput->GetInputType() != EHoudiniInputType::Landscape) - return FReply::Handled(); - - TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); - if (!MainInputObjectsArray) - return FReply::Handled(); - - if (MainInputObjectsArray->Num() <= 0) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Clearing landscape input."), - MainInput->GetOuter()); - - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - TArray* LandscapeInputObjectsArray = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (!LandscapeInputObjectsArray) - continue; - - if (LandscapeInputObjectsArray->Num() <= 0) - continue; - - CurInput->MarkChanged(true); - CurInput->Modify(); - - LandscapeInputObjectsArray->Empty(); - } - - return FReply::Handled(); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().Padding(1, 2, 4, 2) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("ClearSelection", "Clear Selection")) - .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) - .IsEnabled_Lambda(IsClearButtonEnabled) - .OnClicked_Lambda(OnButtonClearClicked) - ] - ]; - } -} - -/* -FMenuBuilder -FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArray& InInputs, const TAttribute& HeadingText, const bool& bShowCurrentSelectionSection) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - - // Filters are only based on the MainInput - auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - if (!Actor->IsA()) - return false; - - ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); - if (!LandscapeProxy) - return false; - - // Get the landscape's actor - AActor* OwnerActor = LandscapeProxy->GetOwner(); - - // Get our Actor - UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); - AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; - - // TODO: FIX ME! - // IF the landscape is owned by ourself, skip it! - if (OwnerActor == MyOwner) - return false; - - return true; - }; - - auto OnShouldFilterWorld = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - const TArray* InputObjects = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurInputObject : *InputObjects) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - auto OnShouldFilterHoudiniAsset = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - // But not our own Asset Actor - if (const USceneComponent* RootComp = Cast(InInput->GetOuter())) - { - if (RootComp && Cast(RootComp->GetOwner()) != Actor) - return true; - } - - return false; - }; - - auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape, OnShouldFilterWorld, OnShouldFilterHoudiniAsset](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - switch (MainInput->GetInputType()) - { - case EHoudiniInputType::Landscape: - return OnShouldFilterLandscape(Actor, MainInput); - case EHoudiniInputType::World: - return OnShouldFilterWorld(Actor, MainInput); - case EHoudiniInputType::Asset: - return OnShouldFilterHoudiniAsset(Actor, MainInput); - default: - return true; - } - - return false; - }; - - - // Selection uses the input arrays - auto OnLandscapeSelected = [](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - ALandscapeProxy* LandscapeProxy = Cast(Actor); - if (!LandscapeProxy) - return; - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return; - - LandscapeInputObjectsArray->Empty(); - - FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); - - // Create a Houdini Input Object. - UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( - LandscapeProxy, Input, LandscapeName.ToString()); - - UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); - LandscapeInput->MarkChanged(true); - - LandscapeInputObjectsArray->Add(LandscapeInput); - Input->MarkChanged(true); - }; - - auto OnHoudiniAssetActorSelected = [](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); - if (!HoudiniAssetActor) - return; - - TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!AssetInputObjectsArray) - return; - - AssetInputObjectsArray->Empty(); - - FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); - - // Create a Houdini Asset Input Object - UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); - - UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); - AssetInput->MarkChanged(true); - - AssetInputObjectsArray->Add(AssetInput); - Input->MarkChanged(true); - }; - - auto OnWorldSelected = [](AActor* Actor, UHoudiniInput* Input) - { - // Do Nothing - }; - - auto OnActorSelected = [OnLandscapeSelected, OnWorldSelected, OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) - { - for (auto& CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - return; - - switch (CurInput->GetInputType()) - { - case EHoudiniInputType::Landscape: - return OnLandscapeSelected(Actor, CurInput); - case EHoudiniInputType::World: - return OnWorldSelected(Actor, CurInput); - case EHoudiniInputType::Asset: - return OnHoudiniAssetActorSelected(Actor, CurInput); - default: - return; - } - } - - return; - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - if (bShowCurrentSelectionSection) - { - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - } - - - MenuBuilder.BeginSection(NAME_None, HeadingText); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} -*/ - - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterHoudiniAsset = [InInputs](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - // But not our selected Asset Actor - for (auto & NextSelectedInput : InInputs) - { - if (!NextSelectedInput) - continue; - - const USceneComponent* RootComp = Cast(NextSelectedInput->GetOuter()); - if (RootComp && Cast(RootComp->GetOwner()) == Actor) - return false; - - } - - return true; - }; - - // Filters are only based on the MainInput - auto OnShouldFilterActor = [MainInput, OnShouldFilterHoudiniAsset](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - return OnShouldFilterHoudiniAsset(Actor); - }; - - auto OnHoudiniAssetActorSelected = [OnShouldFilterHoudiniAsset](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || Actor->IsPendingKill() || !Input || Input->IsPendingKill()) - return; - - AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); - if (!HoudiniAssetActor) - return; - - // Make sure that the actor is valid for this input - if (!OnShouldFilterHoudiniAsset(Actor)) - return; - - TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - if (!AssetInputObjectsArray) - return; - - FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); - - // Create a Houdini Asset Input Object - UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); - - UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); - AssetInput->MarkChanged(true); - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniAssetInputChange", "Houdini Input: Selecting an asset input"), - Input->GetOuter()); - - Input->Modify(); - - AssetInputObjectsArray->Empty(); - AssetInputObjectsArray->Add(AssetInput); - Input->MarkChanged(true); - }; - - auto OnActorSelected = [OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) - { - for (auto& CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - return; - - OnHoudiniAssetActorSelected(Actor, CurInput); - } - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - // Show current selection - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("AssetInputSelectableActors", "Houdini Assets")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - if (!Actor->IsA()) - return false; - - ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); - if (!LandscapeProxy) - return false; - - // Get the landscape's parent actor - // We need to get the AttachParent on the root componet, GteOwner will not return the parent actor! - AActor* OwnerActor = nullptr; - USceneComponent* RootComponent = LandscapeProxy->GetRootComponent(); - if (RootComponent && !RootComponent->IsPendingKill()) - OwnerActor = RootComponent->GetAttachParent() ? RootComponent->GetAttachParent()->GetOwner() : LandscapeProxy->GetOwner(); - - // Get our Actor - UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); - AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; - - // IF the landscape is owned by ourself, skip it! - if (OwnerActor && OwnerActor == MyOwner) - { - // ... buuuut we dont want to filter input landscapes that have the "Update Input Landscape Data" option enabled - // (and are, therefore, outputs as well) - for (int32 Idx = 0; Idx < MyHAC->GetNumInputs(); Idx++) - { - UHoudiniInput* CurrentInput = MyHAC->GetInputAt(Idx); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) - continue; - - if (!CurrentInput->GetUpdateInputLandscape()) - continue; - - // Don't filter our input landscapes - ALandscapeProxy* UpdatedInputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); - if (LandscapeProxy == UpdatedInputLandscape) - return true; - } - - return false; - } - - return true; - }; - - // Filters are only based on the MainInput - auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - return OnShouldFilterLandscape(Actor, MainInput); - }; - - // Selection uses the input arrays - auto OnLandscapeSelected = [OnShouldFilterLandscape](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - // Make sure that the actor is valid for this input - if (!OnShouldFilterLandscape(Actor, Input)) - return; - - ALandscapeProxy* LandscapeProxy = Cast(Actor); - if (!LandscapeProxy) - return; - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return; - - LandscapeInputObjectsArray->Empty(); - - FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); - - // Create a Houdini Input Object. - UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( - LandscapeProxy, Input, LandscapeName.ToString()); - - UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); - LandscapeInput->MarkChanged(true); - - LandscapeInputObjectsArray->Add(LandscapeInput); - Input->MarkChanged(true); - }; - - auto OnActorSelected = [OnLandscapeSelected](AActor* Actor, TArray InInputs) - { - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeSelections", "Houdini Input: Selecting input landscape."), - MainInput->GetOuter()); - - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - OnLandscapeSelected(Actor, CurInput); - } - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - // Show current selection - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("LandscapeInputSelectableActors", "Landscapes")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterWorld = [MainInput](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - const TArray* InputObjects = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurInputObject : *InputObjects) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) - { - // See if the input object is a HAC, if it is, get its parent actor - UHoudiniAssetComponent* CurHAC = Cast(CurInputObject->GetObject()); - if (CurHAC && !CurHAC->IsPendingKill()) - CurActor = CurHAC->GetOwner(); - } - - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - auto OnWorldSelected = [](AActor* Actor) - { - // Do Nothing - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterWorld); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputSelectedActors", "Currently Selected Actors")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnWorldSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilter = [MainInput](const AActor* const Actor) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - const TArray* BoundObjects = MainInput->GetBoundSelectorObjectArray(); - if (!BoundObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurActor : *BoundObjects) - { - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - - auto OnSelected = [](AActor* Actor) - { - // Do Nothing - }; - - FMenuBuilder MenuBuilder(true, nullptr); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputBoundSelectors", "Bound Selectors")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(FOnShouldFilterActor::CreateLambda(OnShouldFilter)); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -void -FHoudiniInputDetails::AddWorldInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - const IDetailsView* DetailsView) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); - - // Get the details view name and locked status - bool bDetailsLocked = false; - FName DetailsPanelName = "LevelEditorSelectionDetails"; - if (DetailsView) - { - DetailsPanelName = DetailsView->GetIdentifier(); - if (DetailsView->IsLocked()) - bDetailsLocked = true; - } - - // Check of we're in bound selector mode - bool bIsBoundSelector = MainInput->IsWorldInputBoundSelector(); - - // Button : Start Selection / Use current selection + refresh - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - //auto ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); - //auto ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); - FText ButtonLabel; - FText ButtonTooltip; - if (!bIsBoundSelector) - { - // Button : Start Selection / Use current selection - if (bDetailsLocked) - { - ButtonLabel = LOCTEXT("WorldInputUseCurrentSelection", "Use Current Selection (Unlocks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputUseCurrentSelectionTooltip", "Fill the asset's input with the current selection and unlocks the Details Panel."); - } - else - { - ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); - } - /* - FOnClicked OnSelectActors = FOnClicked::CreateStatic( - &FHoudiniInputDetails::Helper_OnButtonClickSelectActors, InInputs, DetailsPanelName); - */ - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ButtonLabel) - .ToolTipText(ButtonTooltip) - //.OnClicked(OnSelectActors) - .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() - { - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName); - }) - - ] - ]; - } - else - { - // Button : Start Selection / Use current selection as Bound selector - if (bDetailsLocked) - { - ButtonLabel = LOCTEXT("WorldInputUseSelectionAsBoundSelector", "Use Selection as Bound Selector (Unlocks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputUseSelectionAsBoundSelectorTooltip", "Fill the asset's input with all the actors contains in the current's selection bound, and unlocks the Details Panel."); - } - else - { - ButtonLabel = LOCTEXT("WorldInputStartBoundSelection", "Start Bound Selection (Locks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputStartBoundSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that will be used as bounds."); - } - - /* - FOnClicked OnSelectBounds = FOnClicked::CreateStatic( - &FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector, InInputs, DetailsPanelName); - */ - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ButtonLabel) - .ToolTipText(ButtonTooltip) - //.OnClicked(OnSelectBounds) - .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() - { - return Helper_OnButtonClickUseSelectionAsBoundSelector(CategoryBuilder, InInputs, DetailsPanelName); - }) - ] - ]; - } - } - - // Button : Select All + Clear Selection - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FOnClicked OnSelectAll = FOnClicked::CreateLambda([InInputs, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputSelectedAll", "Houdini Input: Selecting all actor in the current world"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // Get the parent component/actor/world of the current input - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - UWorld* MyWorld = CurrentInput->GetWorld(); - - TArray NewSelectedActors; - for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) - { - AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - NewSelectedActors.Add(CurrentActor); - } - - CurrentInput->Modify(); - - bool bHasChanged = CurrentInput->UpdateWorldSelection(NewSelectedActors); - } - - return FReply::Handled(); - }); - - FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput, &CategoryBuilder]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - const bool bMainInputBoundSelection = MainInput->IsWorldInputBoundSelector(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputClear", "Houdini Input: Clearing world input selection"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - // Do nothing if the current input has different selector settings from the main input - if (CurrentInput->IsWorldInputBoundSelector() != bMainInputBoundSelection) - continue; - - CurrentInput->Modify(); - - if (CurrentInput->IsWorldInputBoundSelector()) - { - CurrentInput->SetBoundSelectorObjectsNumber(0); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - else - { - TArray EmptySelection; - bool bHasChanged = CurrentInput->UpdateWorldSelection(EmptySelection); - } - } - - return FReply::Handled(); - }); - - FText ClearSelectionLabel; - FText ClearSelectionTooltip; - if (bIsBoundSelector) - { - ClearSelectionLabel = LOCTEXT("ClearBoundSelection", "Clear Bound Selection"); - ClearSelectionTooltip = LOCTEXT("ClearBoundSelectionTooltip", "Clear the input's current bound selection."); - } - else - { - ClearSelectionLabel = LOCTEXT("ClearSelection", "Clear Selection"); - ClearSelectionTooltip = LOCTEXT("ClearSelectionTooltip", "Clear the input's current selection."); - } - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - // Button : SelectAll - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("WorldInputSelectAll", "Select All")) - .ToolTipText(LOCTEXT("WorldInputSelectAll", "Fill the asset's input with all actors.")) - .OnClicked(OnSelectAll) - .IsEnabled(!bIsBoundSelector) - ] - + SHorizontalBox::Slot() - [ - // Button : Clear Selection - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ClearSelectionLabel) - .ToolTipText(ClearSelectionTooltip) - .OnClicked(OnClearSelect) - ] - ]; - - // Do not enable select all/clear select when selection has been started and details are locked - HorizontalBox->SetEnabled(!bDetailsLocked); - } - - // Checkbox: Bound Selector - { - // Lambda returning a CheckState from the input's current bound selector state - auto IsCheckedBoundSelector = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->IsWorldInputBoundSelector() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing bound selector state - auto CheckStateChangedIsBoundSelector = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeBoungSelector", "Houdini Input: Changing world input to bound selector"), - MainInput->GetOuter()); - - bool bNewState = (NewState == ECheckBoxState::Checked); - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->IsWorldInputBoundSelector() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetWorldInputBoundSelector(bNewState); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - // Checkbox : Is Bound Selector - TSharedPtr< SCheckBox > CheckBoxBoundSelector; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxBoundSelector, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("BoundSelector", "Bound Selector")) - .ToolTipText(LOCTEXT("BoundSelectorTip", "When enabled, this world input works as a bound selector, sending all the objects contained in the bound selector bounding boxes.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedBoundSelector, MainInput]() - { - return IsCheckedBoundSelector(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedIsBoundSelector, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedIsBoundSelector(InInputs, NewState); - }) - ]; - } - - // Checkbox: Bound Selector Auto update - { - // Lambda returning a CheckState from the input's current auto update state - auto IsCheckedAutoUpdate = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetWorldInputBoundSelectorAutoUpdates() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing the auto update state - auto CheckStateChangedBoundAutoUpdates = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeAutoUpdate", "Houdini Input: Changing bound selector auto-update state."), - MainInput->GetOuter()); - - bool bNewState = (NewState == ECheckBoxState::Checked); - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetWorldInputBoundSelectorAutoUpdates() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetWorldInputBoundSelectorAutoUpdates(bNewState); - CurInput->MarkChanged(true); - } - }; - - // Checkbox : Is Bound Selector - TSharedPtr< SCheckBox > CheckBoxBoundAutoUpdate; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxBoundAutoUpdate, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("BoundAutoUpdate", "Update bound selection automatically")) - .ToolTipText(LOCTEXT("BoundAutoUpdateTip", "If enabled and if this world input is set as a bound selector, the objects selected by the bounds will update automatically.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedAutoUpdate, MainInput]() - { - return IsCheckedAutoUpdate(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedBoundAutoUpdates, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedBoundAutoUpdates(InInputs, NewState); - }) - ]; - - CheckBoxBoundAutoUpdate->SetEnabled(MainInput->IsWorldInputBoundSelector()); - } - - // ActorPicker : Bound Selector - if(bIsBoundSelector) - { - FMenuBuilder MenuBuilder = Helper_CreateBoundSelectorPickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - // ActorPicker : World Outliner - { - FMenuBuilder MenuBuilder = Helper_CreateWorldActorPickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - { - // Spline Resolution - TSharedPtr> NumericEntryBox; - int32 Idx = 0; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(LOCTEXT("SplineRes", "Unreal Spline Resolution")) - .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm betweem control points)\nSet this to 0 to only export the control points.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SNumericEntryBox) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .MinValue(-1.0f) - .MaxValue(1000.0f) - .MinSliderValue(0.0f) - .MaxSliderValue(1000.0f) - .Value(MainInput->GetUnrealSplineResolution()) - .OnValueChanged_Lambda([MainInput, InInputs](float Val) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeSplineResolution", "Houdini Input: Changing world input spline resolution"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetUnrealSplineResolution() == Val) - continue; - - CurrentInput->Modify(); - - CurrentInput->SetUnrealSplineResolution(Val); - CurrentInput->MarkChanged(true); - } - }) - /* - .Value(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject( - &InParam, &UHoudiniAssetInput::GetSplineResolutionValue))) - .OnValueChanged(SNumericEntryBox< float >::FOnValueChanged::CreateUObject( - &InParam, &UHoudiniAssetInput::SetSplineResolutionValue)) - .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateUObject( - &InParam, &UHoudiniAssetInput::IsSplineResolutionEnabled))) - */ - .SliderExponent(1.0f) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("SplineResToDefault", "Reset to default value.")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - // TODO: FINISH ME! - //.OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) - .OnClicked_Lambda([MainInput, InInputs]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputRevertSplineResolution", "Houdini Input: Reverting world input spline resolution to default"), - MainInput->GetOuter()); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - float DefaultSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetUnrealSplineResolution() == DefaultSplineResolution) - continue; - - CurrentInput->Modify(); - - CurrentInput->SetUnrealSplineResolution(DefaultSplineResolution); - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - } -} - -void -FHoudiniInputDetails::AddSkeletalInputUI( - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool ) -{ -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) -{ - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, false); -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) -{ - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, true); -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName, const bool& bUseWorldInAsWorldSelector) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // There's no undo operation for button. - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Locate the details panel. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - return FReply::Handled(); - - class SLocalDetailsView : public SDetailsViewBase - { - public: - void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } - void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } - }; - auto * LocalDetailsView = static_cast(DetailsView.Get()); - - if (!DetailsView->IsLocked()) - { - // - // START SELECTION - // Locks the details view and select our currently selected actors - // - LocalDetailsView->LockDetailsView(); - check(DetailsView->IsLocked()); - - // Force refresh of details view. - TArray InputOuters; - for (auto CurIn : InInputs) - InputOuters.Add(CurIn->GetOuter()); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - //ReselectSelectedActors(); - - if (bUseWorldInAsWorldSelector) - { - // Bound Selection - // Select back the previously chosen bound selectors - GEditor->SelectNone(false, true); - int32 NumBoundSelectors = MainInput->GetNumberOfBoundSelectorObjects(); - for (int32 Idx = 0; Idx < NumBoundSelectors; Idx++) - { - AActor* Actor = MainInput->GetBoundSelectorObjectAt(Idx); - if (!Actor || Actor->IsPendingKill()) - continue; - - GEditor->SelectActor(Actor, true, true); - } - } - else - { - // Regular selection - // Select the already chosen input Actors from the World Outliner. - GEditor->SelectNone(false, true); - int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); - for (int32 Idx = 0; Idx < NumInputObjects; Idx++) - { - UHoudiniInputObject* CurInputObject = MainInput->GetHoudiniInputObjectAt(Idx); - if (!CurInputObject) - continue; - - AActor* Actor = nullptr; - UHoudiniInputActor* InputActor = Cast(CurInputObject); - if (InputActor && !InputActor->IsPendingKill()) - { - // Get the input actor - Actor = InputActor->GetActor(); - } - else - { - // See if the input object is a HAC - UHoudiniInputHoudiniAsset* InputHAC = Cast(CurInputObject); - if (InputHAC && !InputHAC->IsPendingKill()) - { - Actor = InputHAC->GetHoudiniAssetComponent() ? InputHAC->GetHoudiniAssetComponent()->GetOwner() : nullptr; - } - } - - if (!Actor || Actor->IsPendingKill()) - continue; - - GEditor->SelectActor(Actor, true, true); - } - } - - return FReply::Handled(); - } - else - { - // - // UPDATE SELECTION - // Unlocks the input's selection and select the HDA back. - // - - if (!GEditor || !GEditor->GetSelectedObjects()) - return FReply::Handled(); - - USelection * SelectedActors = GEditor->GetSelectedActors(); - if (!SelectedActors) - return FReply::Handled(); - - // Create a transaction - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniWorldInputSelectionChanged", "Changing Houdini world outliner input objects"), - MainInput->GetOuter()); - - - TArray AllActors; - for (auto CurrentInput : InInputs) - { - CurrentInput->Modify(); - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - AllActors.Add(ParentActor); - - bool bHasChanged = true; - if (bUseWorldInAsWorldSelector) - { - // - // Update bound selectors - - // Clean up the selected actors - TArray ValidBoundSelectedActors; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor* CurrentBoundActor = Cast(*It); - if (!CurrentBoundActor) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentBoundActor == ParentActor)) - continue; - - ValidBoundSelectedActors.Add(CurrentBoundActor); - } - - // See if the bound selector have changed - int32 PreviousBoundSelectorCount = CurrentInput->GetNumberOfBoundSelectorObjects(); - if (PreviousBoundSelectorCount == ValidBoundSelectedActors.Num()) - { - // Same number of BoundSelectors, see if they have changed - bHasChanged = false; - for (int32 BoundIdx = 0; BoundIdx < PreviousBoundSelectorCount; BoundIdx++) - { - AActor* PreviousBound = CurrentInput->GetBoundSelectorObjectAt(BoundIdx); - if (!PreviousBound) - continue; - - if (!ValidBoundSelectedActors.Contains(PreviousBound)) - { - bHasChanged = true; - break; - } - } - } - - if (bHasChanged) - { - // Only update the bound selector objects on the input if they have changed - CurrentInput->SetBoundSelectorObjectsNumber(ValidBoundSelectedActors.Num()); - int32 InputObjectIdx = 0; - for (auto CurActor : ValidBoundSelectedActors) - { - CurrentInput->SetBoundSelectorObjectAt(InputObjectIdx++, CurActor); - } - - // Update the current selection from the BoundSelectors - CurrentInput->UpdateWorldSelectionFromBoundSelectors(); - } - } - else - { - // - // Update our selection directly with the currently selected actors - // - - TArray ValidSelectedActors; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor* CurrentActor = Cast(*It); - if (!CurrentActor) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - ValidSelectedActors.Add(CurrentActor); - } - - // Update the input objects from the valid selected actors array - // Only new/remove input objects will be marked as changed - bHasChanged = CurrentInput->UpdateWorldSelection(ValidSelectedActors); - } - - // If we didnt change the selection, cancel the transaction - if (!bHasChanged) - Transaction.Cancel(); - } - - // We can now unlock the details view... - LocalDetailsView->UnlockDetailsView(); - check(!DetailsView->IsLocked()); - - // .. reset the selected actors, force refresh and override the lock. - DetailsView->SetObjects(AllActors, true, true); - - // We now need to reselect all our Asset Actors. - // If we don't do this, our Asset parameters will stop refreshing and the user will be very confused. - // It is also resetting the state of the selection before the input actor selection process was started. - GEditor->SelectNone(false, true); - for (auto CurrentActor : AllActors) - { - AActor* ParentActor = Cast(CurrentActor); - if (!ParentActor) - continue; - - GEditor->SelectActor(ParentActor, true, true); - } - - // Update the input details layout. - // if (CategoryBuilder.IsParentLayoutValid()) - // CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); -} - - -bool -FHoudiniInputDetails::Helper_CancelWorldSelection(TArray& InInputs, const FName& DetailsPanelName) -{ - if (InInputs.Num() <= 0) - return false; - - // Get the property module to access the details view - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Locate the details panel. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - return false; - - if (!DetailsView->IsLocked()) - return false; - - class SLocalDetailsView : public SDetailsViewBase - { - public: - void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } - void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } - }; - auto * LocalDetailsView = static_cast(DetailsView.Get()); - - // Get all our parent components / actors - TArray AllComponents; - TArray AllActors; - for (auto CurrentInput : InInputs) - { - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - if (!ParentComponent) - continue; - - AllComponents.Add(ParentComponent); - - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - if (!ParentActor) - continue; - - AllActors.Add(ParentActor); - } - - // Unlock the detail view and re-select our parent actors - { - LocalDetailsView->UnlockDetailsView(); - check(!DetailsView->IsLocked()); - - // Reset selected actor to itself, force refresh and override the lock. - DetailsView->SetObjects(AllActors, true, true); - } - - // Reselect the Asset Actor. If we don't do this, our Asset parameters will stop - // refreshing and the user will be very confused. It is also resetting the state - // of the selection before the input actor selection process was started. - GEditor->SelectNone(false, true); - for (auto ParentActorObj : AllActors) - { - AActor* ParentActor = Cast(ParentActorObj); - if (!ParentActor) - continue; - - GEditor->SelectActor(ParentActor, true, true); - } - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniInput.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetBlueprintComponent.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniPackageParams.h" + +#include "Editor.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SRotatorInputBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SEditableText.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Editor/UnrealEd/Public/AssetThumbnail.h" +#include "Editor/PropertyEditor/Private/SDetailsViewBase.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" +#include "SAssetDropTarget.h" +#include "ScopedTransaction.h" +#include "Engine/SkeletalMesh.h" +#include "Engine/Selection.h" +#include "EngineUtils.h" +#include "AssetData.h" +#include "Framework/SlateDelegates.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Modules/ModuleManager.h" +#include "SceneOutlinerModule.h" + +#include "Editor/UnrealEdEngine.h" +#include "HoudiniSplineComponentVisualizer.h" +#include "UnrealEdGlobals.h" +#include "Widgets/SWidget.h" + +#include "HoudiniEngineRuntimeUtils.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// Customized TextBlock to show 'editing...' text if this Houdini Spline Component is being edited +class SCurveEditingTextBlock : public STextBlock +{ +public: + UHoudiniSplineComponent* HoudiniSplineComponent; + TSharedPtr HoudiniSplineComponentVisualizer; +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override + { + if (!HoudiniSplineComponentVisualizer.IsValid() || !HoudiniSplineComponent) + return LayerId; + + if (HoudiniSplineComponentVisualizer->GetEditedHoudiniSplineComponent() != HoudiniSplineComponent) + return LayerId; + + return STextBlock::OnPaint(Args, AllottedGeometry, MyClippingRect, + OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + } +}; + +void +FHoudiniInputDetails::CreateWidget( + IDetailCategoryBuilder& HouInputCategory, + TArray InInputs, + FDetailWidgetRow* InputRow) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouInputCategory.GetParentLayout().GetThumbnailPool(); + + EHoudiniInputType MainInputType = MainInput->GetInputType(); + UHoudiniAssetComponent* HAC = MainInput->GetTypedOuter(); + + // Create a widget row, or get the given row. + FDetailWidgetRow* Row = InputRow; + Row = InputRow == nullptr ? &(HouInputCategory.AddCustomRow(FText::GetEmpty())) : InputRow; + if (!Row) + return; + + // Create the standard input name widget if this is not a operator path parameter. + // Operator path parameter's name widget is handled by HoudiniParameterDetails. + if (!InputRow) + CreateNameWidget(MainInput, *Row, true, InInputs.Num()); + + // Create a vertical Box for storing the UI + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + // ComboBox : Input Type + const IDetailsView* DetailsView = HouInputCategory.GetParentLayout().GetDetailsView(); + AddInputTypeComboBox(HouInputCategory, VerticalBox, InInputs, DetailsView); + + // Checkbox : Keep World Transform + AddKeepWorldTransformCheckBox(VerticalBox, InInputs); + + + // Checkbox : CurveInput trigger cook on curve changed + AddCurveInputCookOnChangeCheckBox(VerticalBox, InInputs); + + + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) + { + // Checkbox : Pack before merging + AddPackBeforeMergeCheckbox(VerticalBox, InInputs); + } + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World || MainInputType == EHoudiniInputType::Asset) + { + AddImportAsReferenceCheckbox(VerticalBox, InInputs); + } + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) + { + // Checkboxes : Export LODs / Sockets / Collisions + AddExportCheckboxes(VerticalBox, InInputs); + } + + switch (MainInput->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + AddGeometryInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); + } + break; + + case EHoudiniInputType::Asset: + { + AddAssetInputUI(VerticalBox, InInputs); + } + break; + + case EHoudiniInputType::Curve: + { + AddCurveInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); + } + break; + + case EHoudiniInputType::Landscape: + { + AddLandscapeInputUI(VerticalBox, InInputs); + } + break; + + case EHoudiniInputType::World: + { + AddWorldInputUI(HouInputCategory, VerticalBox, InInputs, DetailsView); + } + break; + + case EHoudiniInputType::Skeletal: + { + AddSkeletalInputUI(VerticalBox, InInputs, AssetThumbnailPool); + } + break; + } + + + Row->ValueWidget.Widget = VerticalBox; + + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + //Row.ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniInputDetails::CreateNameWidget( + UHoudiniInput* InInput, FDetailWidgetRow & Row, bool bLabel, int32 InInputCount) +{ + if (!InInput || InInput->IsPendingKill()) + return; + + FString InputLabelStr = InInput->GetLabel(); + if (InInputCount > 1) + { + InputLabelStr += TEXT(" (") + FString::FromInt(InInputCount) + TEXT(")"); + } + + const FText & FinalInputLabelText = bLabel ? FText::FromString(InputLabelStr) : FText::GetEmpty(); + FText InputTooltip = GetInputTooltip(InInput); + { + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(FinalInputLabelText) + .ToolTipText(InputTooltip) + .Font(FEditorStyle::GetFontStyle(!InInput->HasChanged() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))); + } +} + +FText +FHoudiniInputDetails::GetInputTooltip(UHoudiniInput* InParam) +{ + // TODO + return FText(); +} + +void +FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuilder, TSharedRef VerticalBox, TArray& InInputs, const IDetailsView* DetailsView) +{ + // Get the details view name and locked status + bool bDetailsLocked = false; + FName DetailsPanelName = "LevelEditorSelectionDetails"; + if (DetailsView) + { + DetailsPanelName = DetailsView->GetIdentifier(); + if (DetailsView->IsLocked()) + bDetailsLocked = true; + } + + // Lambda return a FText correpsonding to an input's current type + auto GetInputText = [](UHoudiniInput* InInput) + { + return FText::FromString(InInput->GetInputTypeAsString()); + }; + + // Lambda for changing inputs type + auto OnSelChanged = [DetailsPanelName, &CategoryBuilder](TArray InInputsToUpdate, TSharedPtr InNewChoice) + { + if (!InNewChoice.IsValid()) + return; + + EHoudiniInputType NewInputType = UHoudiniInput::StringToInputType(*InNewChoice.Get()); + if (NewInputType != EHoudiniInputType::World) + { + Helper_CancelWorldSelection(InInputsToUpdate, DetailsPanelName); + } + + if (InInputsToUpdate.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputsToUpdate[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetBlueprintComponent* HAB = MainInput->GetTypedOuter(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Input Type"), + MainInput->GetOuter()); + + bool bBlueprintStructureModified = false; + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetInputType() == NewInputType) + continue; + + /* This causes multiple issues. It does not set reset the previous type variable to Invalid sometimes + and it causes re-cook infinitely after few undo changing type. + { + CurInput->SetInputType(NewInputType); + CurInput->Modify(); + } + */ + + { + // Cache the current input type for undo type changing (since new type becomes previous type after undo) + EHoudiniInputType PrevType = CurInput->GetPreviousInputType(); + CurInput->SetPreviousInputType(NewInputType); + + CurInput->Modify(); + CurInput->SetPreviousInputType(PrevType); + CurInput->SetInputType(NewInputType, bBlueprintStructureModified); // pass in false for 2nd parameter in order to avoid creating default curve if empty + } + CurInput->MarkChanged(true); + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + + } + + if (HAB) + { + if (bBlueprintStructureModified) + HAB->MarkAsBlueprintStructureModified(); + } + + }; + + UHoudiniInput* MainInput = InInputs[0]; + TArray>* SupportedChoices = nullptr; + UHoudiniAssetBlueprintComponent* HAC = MainInput->GetTypedOuter(); + if (HAC) + { + SupportedChoices = FHoudiniEngineEditor::Get().GetBlueprintInputTypeChoiceLabels(); + } + else + { + SupportedChoices = FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels(); + } + + // ComboBox : Input Type + TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBoxInputType; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(ComboBoxInputType, SComboBox>) + .OptionsSource(SupportedChoices) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels())[((int32)MainInput->GetInputType() - 1)]) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([=](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnSelChanged(InInputs, NewChoice); + }) + [ + SNew( STextBlock ) + .Text_Lambda([=]() + { + return GetInputText(MainInput); + }) + .Font( FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; +} + +void +FHoudiniInputDetails:: AddCurveInputCookOnChangeCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + + if (!MainInput || MainInput->GetInputType() != EHoudiniInputType::Curve) + return; + + auto IsCheckedCookOnChange = [MainInput]() + { + if (!MainInput) + return ECheckBoxState::Checked; + + return MainInput->GetCookOnCurveChange() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto CheckStateChangedCookOnChange = [InInputs](ECheckBoxState NewState) + { + bool bChecked = NewState == ECheckBoxState::Checked; + for (auto & NextInput : InInputs) + { + if (!NextInput) + continue; + + NextInput->SetCookOnCurveChange(bChecked); + } + }; + + // Checkbox : Trigger cook on input curve changed + TSharedPtr< SCheckBox > CheckBoxCookOnCurveChanged; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxCookOnCurveChanged, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookOnCurveChangedCheckbox", "Auto-update")) + .ToolTipText(LOCTEXT("CookOnCurveChangeCheckboxTip", "When checked, cook is triggered automatically when the curve is modified.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedCookOnChange, MainInput]() + { + return IsCheckedCookOnChange(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedCookOnChange](ECheckBoxState NewState) + { + return CheckStateChangedCookOnChange( NewState); + }) + ]; + +} + +void +FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current KeepWorldTransform state + auto IsCheckedKeepWorldTransform = [&](UHoudiniInput* InInput) + { + return InInput->GetKeepWorldTransform() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing KeepWorldTransform state + auto CheckStateChangedKeepWorldTransform = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (MainInput->GetKeepWorldTransform() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Keep World Transform"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetKeepWorldTransform() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetKeepWorldTransform(bNewState); + CurInput->MarkChanged(true); + } + }; + + + // Checkbox : Keep World Transform + TSharedPtr< SCheckBox > CheckBoxTranformType; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxTranformType, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("KeepWorldTransformCheckbox", "Keep World Transform")) + .ToolTipText(LOCTEXT("KeepWorldTransformCheckboxTip", "Set this Input's object_merge Transform Type to INTO_THIS_OBJECT. If unchecked, it will be set to NONE.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedKeepWorldTransform(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedKeepWorldTransform(InInputs, NewState); + }) + ]; + + // the checkbox is read only for geo inputs + if (MainInput->GetInputType() == EHoudiniInputType::Geometry) + CheckBoxTranformType->SetEnabled(false); + + // Checkbox is read only if the input is an object-path parameter + //if (MainInput->IsObjectPathParameter() ) + // CheckBoxTranformType->SetEnabled(false); +} + +void +FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current PackBeforeMerge state + auto IsCheckedPackBeforeMerge = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetPackBeforeMerge() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing PackBeforeMerge state + auto CheckStateChangedPackBeforeMerge = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetPackBeforeMerge() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetPackBeforeMerge() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetPackBeforeMerge(bNewState); + CurInput->MarkChanged(true); + } + }; + + TSharedPtr< SCheckBox > CheckBoxPackBeforeMerge; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxPackBeforeMerge, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "PackBeforeMergeCheckbox", "Pack Geometry before merging" ) ) + .ToolTipText( LOCTEXT( "PackBeforeMergeCheckboxTip", "Pack each separate piece of geometry before merging them into the input." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedPackBeforeMerge(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedPackBeforeMerge(InInputs, NewState); + }) + ]; +} + +void +FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current PackBeforeMerge state + auto IsCheckedImportAsReference= [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetImportAsReference() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing PackBeforeMerge state + auto CheckStateChangedImportAsReference= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetImportAsReference() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetImportAsReference() == bNewState) + continue; + + TArray * InputObjs = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (InputObjs) + { + // Mark all its input objects as changed to trigger recook. + for (auto CurInputObj : *InputObjs) + { + if (!CurInputObj || CurInputObj->IsPendingKill()) + continue; + + if (CurInputObj->GetImportAsReference() != bNewState) + CurInputObj->MarkChanged(true); + } + } + + CurInput->Modify(); + CurInput->SetImportAsReference(bNewState); + } + }; + + TSharedPtr< SCheckBox > CheckBoxImportAsReference; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxImportAsReference, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ImportInputAsRefCheckbox", "Import input as references")) + .ToolTipText(LOCTEXT("ImportInputAsRefCheckboxTip", "Import input objects as references. (Geometry, World and Asset input types only)")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedImportAsReference(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedImportAsReference(InInputs, NewState); + }) + ]; +} +void +FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current ExportLODs state + auto IsCheckedExportLODs = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportLODs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda returning a CheckState from the input's current ExportSockets state + auto IsCheckedExportSockets = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportSockets() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda returning a CheckState from the input's current ExportColliders state + auto IsCheckedExportColliders = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportColliders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing ExportLODs state + auto CheckStateChangedExportLODs = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportLODs() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export LODs"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportLODs() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportLODs(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + // Lambda for changing ExportSockets state + auto CheckStateChangedExportSockets = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportSockets() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Sockets"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportSockets() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportSockets(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + // Lambda for changing ExportColliders state + auto CheckStateChangedExportColliders = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportColliders() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Colliders"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportColliders() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportColliders(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + TSharedPtr< SCheckBox > CheckBoxExportLODs; + TSharedPtr< SCheckBox > CheckBoxExportSockets; + TSharedPtr< SCheckBox > CheckBoxExportColliders; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew(CheckBoxExportLODs, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportAllLOD", "Export LODs" ) ) + .ToolTipText( LOCTEXT( "ExportAllLODCheckboxTip", "If enabled, all LOD Meshes in this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportLODs(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportLODs(InInputs, NewState); + }) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( CheckBoxExportSockets, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportSockets", "Export Sockets" ) ) + .ToolTipText( LOCTEXT( "ExportSocketsTip", "If enabled, all Mesh Sockets in this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportSockets(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportSockets(InInputs, NewState); + }) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( CheckBoxExportColliders, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportColliders", "Export Colliders" ) ) + .ToolTipText( LOCTEXT( "ExportCollidersTip", "If enabled, collision geometry for this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportColliders(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportColliders(InInputs, NewState); + }) + ] + ]; +} + +void +FHoudiniInputDetails::AddGeometryInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool ) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + + // Lambda for changing ExportColliders state + auto SetGeometryInputObjectsCount = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& NewInputCount) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing the number of Geometry Input Objects"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry) == NewInputCount) + continue; + + CurInput->Modify(); + + CurInput->SetInputObjectsNumber(EHoudiniInputType::Geometry, NewInputCount); + CurInput->MarkChanged(true); + + // + if (GEditor) + GEditor->RedrawAllViewports(); + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + }; + + InVerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs, NumInputObjects]() + { + return SetGeometryInputObjectsCount(InInputs, NumInputObjects + 1); + }), + LOCTEXT("AddInput", "Adds a Geometry Input"), true) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs]() + { + return SetGeometryInputObjectsCount(InInputs, 0); + }), + LOCTEXT("EmptyInputs", "Removes All Inputs"), true) + ] + ]; + + for (int32 GeometryObjectIdx = 0; GeometryObjectIdx < NumInputObjects; GeometryObjectIdx++) + { + //UObject* InputObject = InParam.GetInputObject(Idx); + Helper_CreateGeometryWidget(CategoryBuilder, InInputs, GeometryObjectIdx, AssetThumbnailPool, InVerticalBox); + } +} + + + +// Create a single geometry widget for the given input object +void +FHoudiniInputDetails::Helper_CreateGeometryWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InGeometryObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox ) +{ + UHoudiniInput* MainInput = InInputs[0]; + + // Access the object used in the corresponding geometry input + UHoudiniInputObject* HoudiniInputObject = MainInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + UObject* InputObject = HoudiniInputObject ? HoudiniInputObject->GetObject() : nullptr; + + // Create thumbnail for this static mesh. + TSharedPtr StaticMeshThumbnail = MakeShareable( + new FAssetThumbnail(InputObject, 64, 64, AssetThumbnailPool)); + + // Lambda for adding new geometry input objects + auto UpdateGeometryObjectAt = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& AtIndex, UObject* InObject) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + if (!InObject || InObject->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing a Geometry Input Object"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + UObject* InputObject = CurInput->GetInputObjectAt(EHoudiniInputType::Geometry, AtIndex); + if (InObject == InputObject) + continue; + + UHoudiniInputObject* CurrentInputObjectWrapper = CurInput->GetHoudiniInputObjectAt(AtIndex); + if (CurrentInputObjectWrapper) + CurrentInputObjectWrapper->Modify(); + + CurInput->Modify(); + + CurInput->SetInputObjectAt(EHoudiniInputType::Geometry, AtIndex, InObject); + CurInput->MarkChanged(true); + + // TODO: Not needed? + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + }; + + // Drop Target: Static/Skeletal Mesh + TSharedPtr HorizontalBox = NULL; + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop_Lambda([]( const UObject* InObject) + { + return UHoudiniInput::IsObjectAcceptable(EHoudiniInputType::Geometry, InObject); + }) + .OnAssetDropped_Lambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt](UObject* InObject) + { + return UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, InObject); + }) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + // Thumbnail : Static Mesh + FText ParameterLabelText = FText::FromString(MainInput->GetLabel()); + + TSharedPtr< SBorder > StaticMeshThumbnailBorder; + HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(StaticMeshThumbnailBorder, SBorder) + .Padding(5.0f) + .OnMouseDoubleClick_Lambda([MainInput, InGeometryObjectIdx](const FGeometry&, const FPointerEvent&) + { + UObject* InputObject = MainInput->GetInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + if (GEditor && InputObject) + GEditor->EditObject(InputObject); + + return FReply::Handled(); + }) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(ParameterLabelText) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + StaticMeshThumbnailBorder->SetBorderImage(TAttribute::Create( + TAttribute::FGetter::CreateLambda([StaticMeshThumbnailBorder]() + { + if (StaticMeshThumbnailBorder.IsValid() && StaticMeshThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ) ) ); + + FText MeshNameText = FText::GetEmpty(); + if (InputObject) + MeshNameText = FText::FromString(InputObject->GetName()); + + + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add Combo box : Static Mesh + TSharedPtr StaticMeshComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SAssignNew(StaticMeshComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(MeshNameText) + ] + ] + ]; + + + StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( + [MainInput, InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt]() + { + TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); + UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); + + TArray< UFactory * > NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(DefaultObj), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda([InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) + { + if (StaticMeshComboButton.IsValid()) + { + StaticMeshComboButton->SetIsOpen(false); + UObject * Object = AssetData.GetAsset(); + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); + } + }), + FSimpleDelegate::CreateLambda([]() {})); + })); + + + // Add buttons + TSharedPtr ButtonHorizontalBox; + ComboAndButtonBox->AddSlot() + .FillHeight(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ButtonHorizontalBox, SHorizontalBox) + ]; + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add( TEXT( "Asset" ), MeshNameText ); + FText StaticMeshTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", + "Browse to '{Asset}' in Content Browser" ), Args ); + + // Button : Use selected in content browser + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton(FSimpleDelegate::CreateLambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt]() + { + if (GEditor) + { + TArray CBSelections; + GEditor->GetContentBrowserSelections(CBSelections); + + // Get the first selected static mesh object + UObject* Object = nullptr; + for (auto & CurAssetData : CBSelections) + { + if (CurAssetData.AssetClass != UStaticMesh::StaticClass()->GetFName()) + continue; + + Object = CurAssetData.GetAsset(); + break; + } + + if (Object && !Object->IsPendingKill()) + { + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); + } + } + }), TAttribute< FText >(LOCTEXT("GeometryInputUseSelectedAssetFromCB", "Use Selected Asset from Content Browser"))) + ]; + + // Button : Browse Static Mesh + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateLambda([MainInput, InGeometryObjectIdx]() + { + UObject* InputObject = MainInput->GetInputObjectAt(InGeometryObjectIdx); + if (GEditor && InputObject) + { + TArray Objects; + Objects.Add(InputObject); + GEditor->SyncBrowserToObjects(Objects); + } + }), + TAttribute< FText >( StaticMeshTooltip ) + ) + ]; + + // ButtonBox : Reset + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default static mesh" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked_Lambda( [UpdateGeometryObjectAt, InInputs, InGeometryObjectIdx]() + { + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, nullptr); + return FReply::Handled(); + }) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + // Insert/Delete/Duplicate + ButtonHorizontalBox->AddSlot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( + FExecuteAction::CreateLambda( [ InInputs, InGeometryObjectIdx, MainInput ]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: insert a Geometry Input Object"), + MainInput->GetOuter()); + // Insert + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->InsertInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + } + } ), + FExecuteAction::CreateLambda([MainInput, InInputs, InGeometryObjectIdx]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: delete a Geometry Input Object"), + MainInput->GetOuter()); + + // Delete + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->DeleteInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + + if (GEditor) + GEditor->RedrawAllViewports(); + } + } ), + FExecuteAction::CreateLambda([InInputs, InGeometryObjectIdx, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), + MainInput->GetOuter()); + + // Duplicate + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->DuplicateInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + } + } ) ) + ]; + + // TRANSFORM OFFSET EXPANDER + { + TSharedPtr ExpanderArrow; + TSharedPtr ExpanderImage; + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( ExpanderArrow, SButton ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ClickMethod( EButtonClickMethod::MouseDown ) + .Visibility( EVisibility::Visible ) + .OnClicked(FOnClicked::CreateLambda([InInputs, InGeometryObjectIdx, MainInput, &CategoryBuilder]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled();; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), + MainInput->GetOuter()); + + // Expand transform + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->OnTransformUIExpand(InGeometryObjectIdx); + } + + // TODO: Not needed? + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + })) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ] + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT("GeoInputTransform", "Transform Offset") ) + .ToolTipText( LOCTEXT( "GeoInputTransformTooltip", "Transform offset used for correction before sending the asset to Houdini" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + // Set delegate for image + ExpanderImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda( [=]() { + FName ResourceName; + if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx) ) + { + ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + return FEditorStyle::GetBrush( ResourceName ); + } ) ) ); + } + + // Lambda for changing the transform values + auto ChangeTransformOffsetAt = [&](const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex, const bool& DoChange, TArray InInputs) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputTransformChange", "Houdini Input: Changing Transform offset"), + InInputs[0]->GetOuter()); + + bool bChanged = true; + for (int Idx = 0; Idx < InInputs.Num(); Idx++) + { + if (!InInputs[Idx] || InInputs[Idx]->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObject = InInputs[Idx]->GetHoudiniInputObjectAt(AtIndex); + if (InputObject) + InputObject->Modify(); + + bChanged &= InInputs[Idx]->SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); + } + + if (bChanged && DoChange) + { + // Mark the values as changed to trigger an update + for (int Idx = 0; Idx < InInputs.Num(); Idx++) + { + InInputs[Idx]->MarkChanged(true); + } + } + else + { + // Cancel the transaction + Transaction.Cancel(); + } + }; + + // Get Visibility of reset buttons + bool bResetButtonVisiblePosition = false; + bool bResetButtonVisibleRotation = false; + bool bResetButtonVisibleScale = false; + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + FTransform* CurTransform = CurInput->GetTransformOffset(InGeometryObjectIdx); + if (!CurTransform) + continue; + + if (CurTransform->GetLocation() != FVector::ZeroVector) + bResetButtonVisiblePosition = true; + + FRotator Rotator = CurTransform->Rotator(); + if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) + bResetButtonVisibleRotation = true; + + if (CurTransform->GetScale3D() != FVector::OneVector) + bResetButtonVisibleScale = true; + } + + auto ChangeTransformOffsetUniformlyAt = [InGeometryObjectIdx, InInputs, ChangeTransformOffsetAt](const float & Val, const int32& PosRotScaleIndex) + { + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 0, true, InInputs); + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 1, true, InInputs); + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 2, true, InInputs); + }; + + // TRANSFORM OFFSET + if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) + { + // Position + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew(STextBlock) + .Text( LOCTEXT("GeoInputTranslate", "T") ) + .ToolTipText( LOCTEXT( "GeoInputTranslateTooltip", "Translate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .AllowSpin(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetX, InGeometryObjectIdx))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetY, InGeometryObjectIdx))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetZ, InGeometryObjectIdx))) + .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 0, true, InInputs); }) + .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 1, true, InInputs); }) + .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 2, true, InInputs); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([MainInput, ChangeTransformOffsetUniformlyAt, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 0); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + + // Rotation + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text( LOCTEXT("GeoInputRotate", "R") ) + .ToolTipText( LOCTEXT( "GeoInputRotateTooltip", "Rotate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SRotatorInputBox ) + .AllowSpin( true ) + .bColorAxisLabels( true ) + .Roll(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetRoll, InGeometryObjectIdx))) + .Pitch(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetPitch, InGeometryObjectIdx))) + .Yaw(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetYaw, InGeometryObjectIdx))) + .OnRollCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 0, true, InInputs); }) + .OnPitchCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 1, true, InInputs); }) + .OnYawCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 2, true, InInputs); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (Not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 1); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + + // Scale + bool bLocked = false; + if (HoudiniInputObject) + bLocked = HoudiniInputObject->IsUniformScaleLocked(); + + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "GeoInputScale", "S" ) ) + .ToolTipText( LOCTEXT( "GeoInputScaleTooltip", "Scale" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetX, InGeometryObjectIdx))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetY, InGeometryObjectIdx))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetZ, InGeometryObjectIdx))) + .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 0, true, InInputs); + }) + .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 1, true, InInputs); + }) + .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 2, true, InInputs); + }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + SNew(SHorizontalBox) + // Lock Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ToolTipText(HoudiniInputObject ? + LOCTEXT("GeoInputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the input object maintains its shape in each direction when scaled") : + LOCTEXT("GeoInputLockButtonToolTipNoObject", "No input object selected")) + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(bLocked ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([InInputs, MainInput, InGeometryObjectIdx, HoudiniInputObject, &CategoryBuilder]() + { + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + UHoudiniInputObject*CurInputObject = CurInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + CurInputObject->SwitchUniformScaleLock(); + } + + if (HoudiniInputObject) + { + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); + }) + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(1.0f, 2); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + } +} + +void +FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Houdini Asset Picker Widget + { + FMenuBuilder MenuBuilder = Helper_CreateHoudiniAssetPickerWidget(InInputs); + + VerticalBox->AddSlot() + .Padding(2.0f, 2.0f, 5.0f, 2.0f) + .AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + // Button : Clear Selection + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + auto IsClearButtonEnabled = [MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return false; + + TArray* AssetInputObjectsArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + + if (!AssetInputObjectsArray) + return false; + + if (AssetInputObjectsArray->Num() <= 0) + return false; + + return true; + }; + + FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChangeClear", "Houdini Input: Clearing asset input selection"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + TArray* AssetInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + if (!AssetInputObjectsArray) + continue; + + CurrentInput->Modify(); + + AssetInputObjectsArray->Empty(); + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }); + + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + // Button : Clear Selection + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("ClearSelection", "Clear Selection")) + .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) + .IsEnabled_Lambda(IsClearButtonEnabled) + .OnClicked(OnClearSelect) + ] + ]; + + // Do not enable select all/clear select when selection has been started and details are locked + //HorizontalBox->SetEnabled(!bDetailsLocked); + } + + +} + +void +FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs, TSharedPtr AssetThumbnailPool) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Curve); + + // lambda for inserting an input Houdini curve. + auto InsertAnInputCurve = [MainInput, &CategoryBuilder](const int32& NewInputCount) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetComponent* OuterHAC = Cast(MainInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + // Do not insert input object when the HAC does not finish cooking + EHoudiniAssetState CurrentHACState = OuterHAC->GetAssetState(); + if (CurrentHACState >= EHoudiniAssetState::PreCook && CurrentHACState<= EHoudiniAssetState::Processing) + return; + + // Clear the to be inserted object array, which records the pointers of the input objects to be inserted. + MainInput->LastInsertedInputs.Empty(); + // Record the pointer of the object to be inserted before transaction for undo the insert action. + bool bBlueprintStructureModified = false; + UHoudiniInputHoudiniSplineComponent* NewInput = MainInput->CreateHoudiniSplineInput(nullptr, true, false, bBlueprintStructureModified); + MainInput->LastInsertedInputs.Add(NewInput); + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction(FText::FromString("Modifying Houdini input: Adding curve input.")); + MainInput->Modify(); + + // Modify the MainInput. + MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType())->Add(NewInput); + + MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, NewInputCount); + + if (bBlueprintStructureModified) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([InsertAnInputCurve, NumInputObjects]() + { + return InsertAnInputCurve(NumInputObjects+1); + //return SetCurveInputObjectsCount(NumInputObjects+1); + }), + + LOCTEXT("AddInputCurve", "Adds a Curve Input"), true) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateLambda([InInputs, MainInput, &CategoryBuilder]() + { + TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + + // Detach all curves before deleting. + for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) + { + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast ((*CurveInputComponentArray)[n]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + } + + // Clear the insert objects buffer before transaction. + MainInput->LastInsertedInputs.Empty(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction(FText::FromString("Modifying Houdini Input: Delete curve inputs.")); + MainInput->Modify(); + + bool bBlueprintStructureModified = false; + + // actual delete. + for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) + { + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast ((*CurveInputComponentArray)[n]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + MainInput->RemoveSplineFromInputObject(HoudiniInput, bBlueprintStructureModified); + + MainInput->DeleteInputObjectAt(EHoudiniInputType::Curve, n); + } + + MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, 0); + + if (bBlueprintStructureModified) + { + UActorComponent* OuterComponent = Cast(MainInput->GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterComponent); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + }), + LOCTEXT("EmptyInputsCurve", "Removes All Curve Inputs"), true) + ] + + SHorizontalBox::Slot().FillWidth(80.f).MaxWidth(80.f) + [ + SNew(SButton) + .Text(LOCTEXT("ResetCurveOffsetStr", "Reset Offset")) + .OnClicked_Lambda([MainInput]()->FReply + { + MainInput->ResetDefaultCurveOffset(); + return FReply::Handled(); + }) + ] + ]; + + //UHoudiniSplineComponent* SplineCompBeingEdited = nullptr; + TSharedPtr HouSplineComponentVisualizer; + if (GUnrealEd) + { + TSharedPtr Visualizer = + GUnrealEd->FindComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); + + HouSplineComponentVisualizer = StaticCastSharedPtr(Visualizer); + } + + + for (int n = 0; n < NumInputObjects; n++) + { + Helper_CreateCurveWidget(CategoryBuilder, InInputs, n, AssetThumbnailPool ,VerticalBox, HouSplineComponentVisualizer); + } +} + +void +FHoudiniInputDetails::Helper_CreateCurveWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InCurveObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox, + TSharedPtr HouSplineComponentVisualizer) +{ + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetComponent * OuterHAC = Cast(MainInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + auto GetHoudiniSplineComponentAtIndex = [](UHoudiniInput * Input, int32 Index) + { + UHoudiniSplineComponent* FoundHoudiniSplineComponent = nullptr; + if (!Input || Input->IsPendingKill()) + return FoundHoudiniSplineComponent; + + // Get the TArray ptr to the curve objects in this input + TArray * CurveInputComponentArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + return FoundHoudiniSplineComponent; + + if (!CurveInputComponentArray->IsValidIndex(Index)) + return FoundHoudiniSplineComponent; + + // Access the object used in the corresponding Houdini curve input + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + FoundHoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + + return FoundHoudiniSplineComponent; + }; + + + // Get the TArray ptr to the curve objects in this input + TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + return; + + if (!CurveInputComponentArray->IsValidIndex(InCurveObjectIdx)) + return; + + // Access the object used in the corresponding Houdini curve input + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[InCurveObjectIdx]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent) + return; + + FString HoudiniSplineName = HoudiniSplineComponent->GetHoudiniSplineName(); + + // Editable label for the current Houdini curve + TSharedPtr LabelHorizontalBox; + VerticalBox->AddSlot() + .Padding(0, 2) + .AutoHeight() + [ + SAssignNew(LabelHorizontalBox, SHorizontalBox) + ]; + + + TSharedPtr LabelBlock; + LabelHorizontalBox->AddSlot() + .Padding(0, 15, 0, 2) + .MaxWidth(150.f) + .FillWidth(150.f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Left) + [ + SNew(SBox).HeightOverride(20.f).WidthOverride(200.f).VAlign(VAlign_Center) + [ + SAssignNew(LabelBlock, SEditableText).Text(FText::FromString(HoudiniSplineName)) + .OnTextCommitted_Lambda([HoudiniSplineComponent](FText NewText, ETextCommit::Type CommitType) + { + if (CommitType == ETextCommit::Type::OnEnter) + { + HoudiniSplineComponent->SetHoudiniSplineName(NewText.ToString()); + } + }) + ] + ]; + + // 'Editing...' TextBlock showing if this component is being edited + TSharedPtr EditingTextBlock; + LabelHorizontalBox->AddSlot() + .Padding(0, 15, 0, 2) + .MaxWidth(55.f) + .FillWidth(55.f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Left) + [ + SNew(SBox).HeightOverride(20.f).WidthOverride(75.f).VAlign(VAlign_Center) + [ + SAssignNew(EditingTextBlock, SCurveEditingTextBlock).Text(LOCTEXT("HoudiniCurveInputEditingLabel", "(editing...)")) + ] + ]; + + EditingTextBlock->HoudiniSplineComponent = HoudiniSplineComponent; + EditingTextBlock->HoudiniSplineComponentVisualizer = HouSplineComponentVisualizer; + + // Lambda for deleting the current curve input + auto DeleteHoudiniCurveAtIndex = [InInputs, InCurveObjectIdx, OuterHAC, CurveInputComponentArray, &CategoryBuilder]() + { + if (!OuterHAC|| OuterHAC->IsPendingKill()) + return; + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeDeleteACurve", "Houdini Input: Deleting a curve input"), + OuterHAC); + + int MainInputCurveArraySize = CurveInputComponentArray->Num(); + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + Input->Modify(); + + TArray* InputObjectArr = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!InputObjectArr) + continue; + + if (!InputObjectArr->IsValidIndex(InCurveObjectIdx)) + continue; + + if (MainInputCurveArraySize != InputObjectArr->Num()) + continue; + + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast((*InputObjectArr)[InCurveObjectIdx]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + return; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent) + return; + + // Detach the spline component before delete. + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + + // This input is marked changed when an input component is deleted. + Input->DeleteInputObjectAt(EHoudiniInputType::Curve, InCurveObjectIdx); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + // Add delete button UI + LabelHorizontalBox->AddSlot().Padding(0, 2, 0, 2).HAlign(HAlign_Right).VAlign(VAlign_Bottom).AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([DeleteHoudiniCurveAtIndex]() + { + return DeleteHoudiniCurveAtIndex(); + })) + ]; + + + TSharedPtr HorizontalBox = NULL; + VerticalBox->AddSlot().Padding(0, 2).AutoHeight()[SAssignNew(HorizontalBox, SHorizontalBox)]; + + // Closed check box + // Lambda returning a closed state + auto IsCheckedClosedCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsClosedCurve() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing Closed state + auto CheckStateChangedClosedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeClosed", "Houdini Input: Changing Curve Closed"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->IsClosedCurve() == bNewState) + continue; + + HoudiniSplineComponent->Modify(); + + HoudiniSplineComponent->SetClosedCurve(bNewState); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Add Closed check box UI + TSharedPtr CheckBoxClosed = NULL; + HorizontalBox->AddSlot().Padding(0, 2).AutoWidth() + [ + SAssignNew(CheckBoxClosed, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("ClosedCurveCheckBox", "Closed")) + .ToolTipText(LOCTEXT("ClosedCurveCheckboxTip", "Close this input curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedClosedCurve]() + { + return IsCheckedClosedCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedClosedCurve](ECheckBoxState NewState) + { + return CheckStateChangedClosedCurve(NewState); + }) + ]; + + // Reversed check box + // Lambda returning a reversed state + auto IsCheckedReversedCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsReversed() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing reversed state + auto CheckStateChangedReversedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeReversed", "Houdini Input: Changing Curve Reversed"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->IsReversed() == bNewState) + continue; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetReversed(bNewState); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Add reversed check box UI + TSharedPtr CheckBoxReversed = NULL; + HorizontalBox->AddSlot() + .Padding(2, 2) + .AutoWidth() + [ + SAssignNew(CheckBoxReversed, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("ReversedCurveCheckBox", "Reversed")) + .ToolTipText(LOCTEXT("ReversedCurveCheckboxTip", "Reverse this input curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedReversedCurve]() + { + return IsCheckedReversedCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedReversedCurve](ECheckBoxState NewState) + { + return CheckStateChangedReversedCurve(NewState); + }) + ]; + + // Visible check box + // Lambda returning a visible state + auto IsCheckedVisibleCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsHoudiniSplineVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing visible state + auto CheckStateChangedVisibleCurve = [GetHoudiniSplineComponentAtIndex, InInputs, OuterHAC, InCurveObjectIdx](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent) + continue; + + if (HoudiniSplineComponent->IsHoudiniSplineVisible() == bNewState) + return; + + HoudiniSplineComponent->SetHoudiniSplineVisible(bNewState); + } + + if (GEditor) + GEditor->RedrawAllViewports(); + + }; + + // Add visible check box UI + TSharedPtr CheckBoxVisible = NULL; + HorizontalBox->AddSlot().Padding(2, 2).AutoWidth() + [ + SAssignNew(CheckBoxVisible, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("VisibleCurveCheckBox", "Visible")) + .ToolTipText(LOCTEXT("VisibleCurveCheckboxTip", "Set the visibility of this curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedVisibleCurve]() + { + return IsCheckedVisibleCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedVisibleCurve](ECheckBoxState NewState) + { + return CheckStateChangedVisibleCurve(NewState); + }) + ]; + + // Curve type comboBox + // Lambda for changing Houdini curve type + auto OnCurveTypeChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + if (!InNewChoice.IsValid()) + return; + + EHoudiniCurveType NewInputType = UHoudiniInput::StringToHoudiniCurveType(*InNewChoice.Get()); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeType", "Houdini Input: Changing Curve Type"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->GetCurveType() == NewInputType) + continue; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetCurveType(NewInputType); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Lambda for getting Houdini curve type + auto GetCurveTypeText = [HoudiniSplineComponent]() + { + return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(HoudiniSplineComponent->GetCurveType())); + }; + + // Add curve type combo box UI + TSharedPtr CurveTypeHorizontalBox; + VerticalBox->AddSlot() + .Padding(0, 2, 2, 0) + .AutoHeight() + [ + SAssignNew(CurveTypeHorizontalBox, SHorizontalBox) + ]; + + // Add curve type label UI + CurveTypeHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() + [ + SNew(STextBlock).Text(LOCTEXT("CurveTypeText", "Curve Type ")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveType; + CurveTypeHorizontalBox->AddSlot() + .Padding(2, 2, 5, 2) + .FillWidth(150.f) + .MaxWidth(150.f) + [ + SAssignNew(ComboBoxCurveType, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels())[(int)HoudiniSplineComponent->GetCurveType()]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnCurveTypeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnCurveTypeChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([GetCurveTypeText]() + { + return GetCurveTypeText(); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + // Houdini curve method combo box + // Lambda for changing Houdini curve method + auto OnCurveMethodChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + if (!InNewChoice.IsValid()) + return; + + EHoudiniCurveMethod NewInputMethod = UHoudiniInput::StringToHoudiniCurveMethod(*InNewChoice.Get()); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeMethod", "Houdini Input: Changing Curve Method"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->GetCurveMethod() == NewInputMethod) + return; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetCurveMethod(NewInputMethod); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Lambda for getting Houdini curve method + auto GetCurveMethodText = [HoudiniSplineComponent]() + { + return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(HoudiniSplineComponent->GetCurveMethod())); + }; + + // Add curve method combo box UI + TSharedPtr< SHorizontalBox > CurveMethodHorizontalBox; + VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(CurveMethodHorizontalBox, SHorizontalBox)]; + + // Add curve method label UI + CurveMethodHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() + [ + SNew(STextBlock).Text(LOCTEXT("CurveMethodText", "Curve Method ")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveMethod; + CurveMethodHorizontalBox->AddSlot().Padding(2, 2, 5, 2).FillWidth(150.f).MaxWidth(150.f) + [ + SAssignNew(ComboBoxCurveMethod, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels())[(int)HoudiniSplineComponent->GetCurveMethod()]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnCurveMethodChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnCurveMethodChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([GetCurveMethodText]() + { + return GetCurveMethodText(); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + auto BakeInputCurveLambda = [](TArray Inputs, int32 Index, bool bBakeToBlueprint) + { + for (auto & NextInput : Inputs) + { + if (!NextInput || NextInput->IsPendingKill()) + continue; + + UHoudiniAssetComponent * OuterHAC = Cast(NextInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + continue; + + AActor * OwnerActor = OuterHAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + continue; + + TArray * CurveInputComponentArray = NextInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + continue; + + if (!CurveInputComponentArray->IsValidIndex(Index)) + continue; + + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + FHoudiniPackageParams PackageParams; + PackageParams.BakeFolder = OuterHAC->BakeFolder.Path; + PackageParams.HoudiniAssetName = OuterHAC->GetName(); + PackageParams.GeoId = NextInput->GetAssetNodeId(); + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.ObjectId = Index; + PackageParams.ObjectName = OwnerActor->GetName() + "InputHoudiniSpline" + FString::FromInt(Index); + + if (bBakeToBlueprint) + { + FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( + HoudiniSplineComponent, + PackageParams, + OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); + } + else + { + FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + HoudiniSplineComponent, + PackageParams, + OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); + } + } + + return FReply::Handled(); + }; + + // Add input curve bake button + TSharedPtr< SHorizontalBox > InputCurveBakeHorizontalBox; + VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(InputCurveBakeHorizontalBox, SHorizontalBox)]; + VerticalBox->AddSlot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(110.f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("HoudiniInputCurveBakeToActorButton", "Bake to Actor")) + .IsEnabled(true) + .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() + { + return BakeInputCurveLambda(InInputs, InCurveObjectIdx, false); + }) + .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToActorButtonToolTip", "Bake this input curve to Actor")) + ] + + + SHorizontalBox::Slot().MaxWidth(110.f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("HoudiniInputCurveBakeToBPButton", "Bake to Blueprint")) + .IsEnabled(true) + .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() + { + return BakeInputCurveLambda(InInputs, InCurveObjectIdx, true); + }) + .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToBPButtonToolTip", "Bake this input curve to Blueprint")) + ] + ]; + + // Do we actually need to set enable the UI components? + if (MainInput->GetInputType() == EHoudiniInputType::Curve) + { + LabelBlock->SetEnabled(true); + CheckBoxClosed->SetEnabled(true); + CheckBoxReversed->SetEnabled(true); + CheckBoxVisible->SetEnabled(true); + ComboBoxCurveType->SetEnabled(true); + ComboBoxCurveMethod->SetEnabled(true); + } + else + { + LabelBlock->SetEnabled(false); + CheckBoxClosed->SetEnabled(false); + CheckBoxReversed->SetEnabled(false); + CheckBoxVisible->SetEnabled(false); + ComboBoxCurveType->SetEnabled(false); + ComboBoxCurveMethod->SetEnabled(false); + } +} + +void +FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + // Lambda returning a CheckState from the input's current KeepWorldTransform state + auto IsCheckedUpdateInputLandscape = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetUpdateInputLandscape() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing KeepWorldTransform state + auto CheckStateChangedUpdateInputLandscape = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangedUpdate", "Houdini Input: Changing Keep World Transform"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (bNewState == CurInput->GetUpdateInputLandscape()) + continue; + + CurInput->Modify(); + + UHoudiniAssetComponent* HAC = Cast(CurInput->GetOuter()); + if (!HAC) + continue; + + TArray* LandscapeInputObjects = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (!LandscapeInputObjects) + continue; + + for (UHoudiniInputObject* NextInputObj : *LandscapeInputObjects) + { + UHoudiniInputLandscape* CurrentInputLandscape = Cast(NextInputObj); + if (!CurrentInputLandscape) + continue; + + ALandscapeProxy* CurrentInputLandscapeProxy = CurrentInputLandscape->GetLandscapeProxy(); + if (!CurrentInputLandscapeProxy) + continue; + + if (bNewState) + { + // We want to update this landscape data directly, start by backing it up to image files in the temp folder + FString BackupBaseName = HAC->TemporaryCookFolder.Path + + TEXT("/") + + CurrentInputLandscapeProxy->GetName() + + TEXT("_") + + HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); + + // We need to cache the input landscape to a file + FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(BackupBaseName, CurrentInputLandscapeProxy); + + // Cache its transform on the input + CurrentInputLandscape->CachedInputLandscapeTraqnsform = CurrentInputLandscapeProxy->ActorToWorld(); + + HAC->SetMobility(EComponentMobility::Static); + CurrentInputLandscapeProxy->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + } + else + { + // We are not updating this input landscape anymore, detach it and restore its backed-up values + CurrentInputLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + // Restore the input landscape's backup data + FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(CurrentInputLandscapeProxy); + + // Reapply the source Landscape's transform + CurrentInputLandscapeProxy->SetActorTransform(CurrentInputLandscape->CachedInputLandscapeTraqnsform); + + // TODO: + // Clear the input obj map? + } + } + + CurInput->bUpdateInputLandscape = (NewState == ECheckBoxState::Checked); + CurInput->MarkChanged(true); + } + }; + + // CheckBox : Update Input Landscape Data + TSharedPtr< SCheckBox > CheckBoxUpdateInput; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew( CheckBoxUpdateInput, SCheckBox).Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) + .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedUpdateInputLandscape, MainInput]() + { + return IsCheckedUpdateInputLandscape(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedUpdateInputLandscape, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedUpdateInputLandscape(InInputs, NewState); + }) + ]; + + // Actor picker: Landscape. + FMenuBuilder MenuBuilder = Helper_CreateLandscapePickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + + // Checkboxes : Export landscape as Heightfield/Mesh/Points + { + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAs", "Export Landscape As")) + .ToolTipText(LOCTEXT("LandscapeExportAsToolTip", "Choose the type of data you want the ladscape to be exported to:\n * Heightfield\n * Mesh\n * Points")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr ButtonOptionsPanel; + VerticalBox->AddSlot().Padding(5, 2, 5, 2).AutoHeight() + [ + SAssignNew(ButtonOptionsPanel, SUniformGridPanel) + ]; + + auto IsCheckedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) + { + if (!Input || Input->IsPendingKill()) + return ECheckBoxState::Unchecked; + + if (Input->GetLandscapeExportType() == LandscapeExportType) + return ECheckBoxState::Checked; + else + return ECheckBoxState::Unchecked; + }; + + auto CheckStateChangedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) + { + if (!Input || Input->IsPendingKill()) + return false; + + if (Input->GetLandscapeExportType() == LandscapeExportType) + return false; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changed Landscape export type."), + Input->GetOuter()); + Input->Modify(); + + Input->SetLandscapeExportType(LandscapeExportType); + Input->SetHasLandscapeExportTypeChanged(true); + Input->MarkChanged(true); + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return true; + + for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) + { + if (!NextInputObj) + continue; + NextInputObj->MarkChanged(true); + } + + return true; + }; + + // Heightfield + FText HeightfieldTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a heightfield."); + ButtonOptionsPanel->AddSlot(0, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Start") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Heightfield); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for(auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Heightfield); + }) + .ToolTipText(HeightfieldTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("ClassIcon.LandscapeComponent")) + ] + + SHorizontalBox::Slot() + .FillWidth(1.f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsHeightfieldCheckbox", "Heightfield")) + .ToolTipText(HeightfieldTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + + // Mesh + FText MeshTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a mesh."); + ButtonOptionsPanel->AddSlot(1, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Mesh); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for (auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Mesh); + }) + .ToolTipText(MeshTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("ClassIcon.StaticMeshComponent")) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsMeshCheckbox", "Mesh")) + .ToolTipText(MeshTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + + // Points + FText PointsTooltip = LOCTEXT("LandscapeExportAsPointsTooltip", "If enabled, the landscape will be exported to Houdini as points."); + ButtonOptionsPanel->AddSlot(2, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.End") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Points); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for (auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Points); + }) + .ToolTipText(PointsTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("Mobility.Static")) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsPointsCheckbox", "Points")) + .ToolTipText(PointsTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + } + + // CheckBox : Export selected components only + { + TSharedPtr< SCheckBox > CheckBoxExportSelected; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportSelected, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeSelectedCheckbox", "Export Selected Landscape Components Only")) + .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, only the selected Landscape Components will be exported.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportSelectionOnly ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportSelectionOnly", "Houdini Input: Changing Landscape export only selected component."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportSelectionOnly) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportSelectionOnly = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + } + + // Checkbox: auto select components + { + TSharedPtr< SCheckBox > CheckBoxAutoSelectComponents; + VerticalBox->AddSlot().Padding(10, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxAutoSelectComponents, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AutoSelectComponentCheckbox", "Auto-select component in asset bounds")) + .ToolTipText(LOCTEXT("AutoSelectComponentCheckboxTooltip", "If enabled, when no Landscape components are curremtly selected, the one within the asset's bounding box will be exported.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeAutoSelectComponent ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeAutoSelectComponent", "Houdini Input: Changing Landscape input auto-selects components."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeAutoSelectComponent) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeAutoSelectComponent = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + // Enable only when exporting selection or when exporting heighfield (for now) + bool bEnable = false; + for (auto CurrentInput : InInputs) + { + if (!MainInput->bLandscapeExportSelectionOnly) + continue; + + bEnable = true; + break; + } + CheckBoxAutoSelectComponents->SetEnabled(bEnable); + } + + + // The following checkbox are only added when not in heightfield mode + if (MainInput->LandscapeExportType != EHoudiniLandscapeExportType::Heightfield) + { + // Checkbox : Export materials + { + TSharedPtr< SCheckBox > CheckBoxExportMaterials; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportMaterials, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeMaterialsCheckbox", "Export Landscape Materials")) + .ToolTipText(LOCTEXT("LandscapeMaterialsTooltip", "If enabled, the landscape materials will be exported with it.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportMaterials", "Houdini Input: Changing Landscape input export materials."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportMaterials) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportMaterials = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportMaterials->SetEnabled(false); + */ + } + + // Checkbox : Export Tile UVs + { + TSharedPtr< SCheckBox > CheckBoxExportTileUVs; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportTileUVs, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeTileUVsCheckbox", "Export Landscape Tile UVs")) + .ToolTipText(LOCTEXT("LandscapeTileUVsTooltip", "If enabled, UVs will be exported separately for each Landscape tile.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportTileUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportTileUVs", "Houdini Input: Changing Landscape export tile UVs."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportTileUVs) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportTileUVs = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportTileUVs->SetEnabled(false); + */ + } + + // Checkbox : Export normalized UVs + { + TSharedPtr< SCheckBox > CheckBoxExportNormalizedUVs; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportNormalizedUVs, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeNormalizedUVsCheckbox", "Export Landscape Normalized UVs")) + .ToolTipText(LOCTEXT("LandscapeNormalizedUVsTooltip", "If enabled, landscape UVs will be exported in [0, 1].")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportNormalizedUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Changing Landscape export normalized UVs."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportNormalizedUVs) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportNormalizedUVs = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportNormalizedUVs->SetEnabled(false); + */ + } + + // Checkbox : Export lighting + { + TSharedPtr< SCheckBox > CheckBoxExportLighting; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportLighting, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeLightingCheckbox", "Export Landscape Lighting")) + .ToolTipText(LOCTEXT("LandscapeLightingTooltip", "If enabled, lightmap information will be exported with the landscape.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportLighting", "Houdini Input: Changing Landscape export lighting."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportLighting) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportLighting = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportLighting->SetEnabled(false); + */ + } + + } + + // Button : Recommit + { + auto OnButtonRecommitClicked = [InInputs]() + { + for (auto CurrentInput : InInputs) + { + TArray* LandscapeInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); + if (!LandscapeInputObjectsArray) + continue; + + for (UHoudiniInputObject* NextLandscapeInput : *LandscapeInputObjectsArray) + { + if (!NextLandscapeInput) + continue; + + NextLandscapeInput->MarkChanged(true); + } + + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1, 2, 4, 2) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("LandscapeInputRecommit", "Recommit Landscape")) + .ToolTipText(LOCTEXT("LandscapeInputRecommitTooltip", "Recommits the Landscape to Houdini.")) + .OnClicked_Lambda(OnButtonRecommitClicked) + ] + ]; + } + + + // Button : Clear Selection + { + auto IsClearButtonEnabled = [MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return false; + + if (MainInput->GetInputType() != EHoudiniInputType::Landscape) + return false; + + TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); + if (!MainInputObjectsArray) + return false; + + if (MainInputObjectsArray->Num() <= 0) + return false; + + return true; + }; + + auto OnButtonClearClicked = [InInputs]() + { + if (InInputs.Num() <= 0) + return FReply::Handled(); + + UHoudiniInput * MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + if (MainInput->GetInputType() != EHoudiniInputType::Landscape) + return FReply::Handled(); + + TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); + if (!MainInputObjectsArray) + return FReply::Handled(); + + if (MainInputObjectsArray->Num() <= 0) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Clearing landscape input."), + MainInput->GetOuter()); + + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + TArray* LandscapeInputObjectsArray = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (!LandscapeInputObjectsArray) + continue; + + if (LandscapeInputObjectsArray->Num() <= 0) + continue; + + CurInput->MarkChanged(true); + CurInput->Modify(); + + LandscapeInputObjectsArray->Empty(); + } + + return FReply::Handled(); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1, 2, 4, 2) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("ClearSelection", "Clear Selection")) + .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) + .IsEnabled_Lambda(IsClearButtonEnabled) + .OnClicked_Lambda(OnButtonClearClicked) + ] + ]; + } +} + +/* +FMenuBuilder +FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArray& InInputs, const TAttribute& HeadingText, const bool& bShowCurrentSelectionSection) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + + // Filters are only based on the MainInput + auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + if (!Actor->IsA()) + return false; + + ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); + if (!LandscapeProxy) + return false; + + // Get the landscape's actor + AActor* OwnerActor = LandscapeProxy->GetOwner(); + + // Get our Actor + UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); + AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; + + // TODO: FIX ME! + // IF the landscape is owned by ourself, skip it! + if (OwnerActor == MyOwner) + return false; + + return true; + }; + + auto OnShouldFilterWorld = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + const TArray* InputObjects = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurInputObject : *InputObjects) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + AActor* CurActor = Cast(CurInputObject->GetObject()); + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + auto OnShouldFilterHoudiniAsset = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + // But not our own Asset Actor + if (const USceneComponent* RootComp = Cast(InInput->GetOuter())) + { + if (RootComp && Cast(RootComp->GetOwner()) != Actor) + return true; + } + + return false; + }; + + auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape, OnShouldFilterWorld, OnShouldFilterHoudiniAsset](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + switch (MainInput->GetInputType()) + { + case EHoudiniInputType::Landscape: + return OnShouldFilterLandscape(Actor, MainInput); + case EHoudiniInputType::World: + return OnShouldFilterWorld(Actor, MainInput); + case EHoudiniInputType::Asset: + return OnShouldFilterHoudiniAsset(Actor, MainInput); + default: + return true; + } + + return false; + }; + + + // Selection uses the input arrays + auto OnLandscapeSelected = [](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + ALandscapeProxy* LandscapeProxy = Cast(Actor); + if (!LandscapeProxy) + return; + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return; + + LandscapeInputObjectsArray->Empty(); + + FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); + + // Create a Houdini Input Object. + UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( + LandscapeProxy, Input, LandscapeName.ToString()); + + UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); + LandscapeInput->MarkChanged(true); + + LandscapeInputObjectsArray->Add(LandscapeInput); + Input->MarkChanged(true); + }; + + auto OnHoudiniAssetActorSelected = [](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); + if (!HoudiniAssetActor) + return; + + TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!AssetInputObjectsArray) + return; + + AssetInputObjectsArray->Empty(); + + FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); + + // Create a Houdini Asset Input Object + UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); + + UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); + AssetInput->MarkChanged(true); + + AssetInputObjectsArray->Add(AssetInput); + Input->MarkChanged(true); + }; + + auto OnWorldSelected = [](AActor* Actor, UHoudiniInput* Input) + { + // Do Nothing + }; + + auto OnActorSelected = [OnLandscapeSelected, OnWorldSelected, OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) + { + for (auto& CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + return; + + switch (CurInput->GetInputType()) + { + case EHoudiniInputType::Landscape: + return OnLandscapeSelected(Actor, CurInput); + case EHoudiniInputType::World: + return OnWorldSelected(Actor, CurInput); + case EHoudiniInputType::Asset: + return OnHoudiniAssetActorSelected(Actor, CurInput); + default: + return; + } + } + + return; + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + if (bShowCurrentSelectionSection) + { + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + } + + + MenuBuilder.BeginSection(NAME_None, HeadingText); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} +*/ + + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterHoudiniAsset = [InInputs](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + // But not our selected Asset Actor + for (auto & NextSelectedInput : InInputs) + { + if (!NextSelectedInput) + continue; + + const USceneComponent* RootComp = Cast(NextSelectedInput->GetOuter()); + if (RootComp && Cast(RootComp->GetOwner()) == Actor) + return false; + + } + + return true; + }; + + // Filters are only based on the MainInput + auto OnShouldFilterActor = [MainInput, OnShouldFilterHoudiniAsset](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + return OnShouldFilterHoudiniAsset(Actor); + }; + + auto OnHoudiniAssetActorSelected = [OnShouldFilterHoudiniAsset](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || Actor->IsPendingKill() || !Input || Input->IsPendingKill()) + return; + + AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); + if (!HoudiniAssetActor) + return; + + // Make sure that the actor is valid for this input + if (!OnShouldFilterHoudiniAsset(Actor)) + return; + + TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + if (!AssetInputObjectsArray) + return; + + FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); + + // Create a Houdini Asset Input Object + UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); + + UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); + AssetInput->MarkChanged(true); + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniAssetInputChange", "Houdini Input: Selecting an asset input"), + Input->GetOuter()); + + Input->Modify(); + + AssetInputObjectsArray->Empty(); + AssetInputObjectsArray->Add(AssetInput); + Input->MarkChanged(true); + }; + + auto OnActorSelected = [OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) + { + for (auto& CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + return; + + OnHoudiniAssetActorSelected(Actor, CurInput); + } + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + // Show current selection + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("AssetInputSelectableActors", "Houdini Assets")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + if (!Actor->IsA()) + return false; + + ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); + if (!LandscapeProxy) + return false; + + // Get the landscape's parent actor + // We need to get the AttachParent on the root componet, GteOwner will not return the parent actor! + AActor* OwnerActor = nullptr; + USceneComponent* RootComponent = LandscapeProxy->GetRootComponent(); + if (RootComponent && !RootComponent->IsPendingKill()) + OwnerActor = RootComponent->GetAttachParent() ? RootComponent->GetAttachParent()->GetOwner() : LandscapeProxy->GetOwner(); + + // Get our Actor + UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); + AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; + + // IF the landscape is owned by ourself, skip it! + if (OwnerActor && OwnerActor == MyOwner) + { + // ... buuuut we dont want to filter input landscapes that have the "Update Input Landscape Data" option enabled + // (and are, therefore, outputs as well) + for (int32 Idx = 0; Idx < MyHAC->GetNumInputs(); Idx++) + { + UHoudiniInput* CurrentInput = MyHAC->GetInputAt(Idx); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) + continue; + + if (!CurrentInput->GetUpdateInputLandscape()) + continue; + + // Don't filter our input landscapes + ALandscapeProxy* UpdatedInputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); + if (LandscapeProxy == UpdatedInputLandscape) + return true; + } + + return false; + } + + return true; + }; + + // Filters are only based on the MainInput + auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + return OnShouldFilterLandscape(Actor, MainInput); + }; + + // Selection uses the input arrays + auto OnLandscapeSelected = [OnShouldFilterLandscape](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + // Make sure that the actor is valid for this input + if (!OnShouldFilterLandscape(Actor, Input)) + return; + + ALandscapeProxy* LandscapeProxy = Cast(Actor); + if (!LandscapeProxy) + return; + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return; + + LandscapeInputObjectsArray->Empty(); + + FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); + + // Create a Houdini Input Object. + UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( + LandscapeProxy, Input, LandscapeName.ToString()); + + UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); + LandscapeInput->MarkChanged(true); + + LandscapeInputObjectsArray->Add(LandscapeInput); + Input->MarkChanged(true); + }; + + auto OnActorSelected = [OnLandscapeSelected](AActor* Actor, TArray InInputs) + { + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeSelections", "Houdini Input: Selecting input landscape."), + MainInput->GetOuter()); + + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + OnLandscapeSelected(Actor, CurInput); + } + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + // Show current selection + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("LandscapeInputSelectableActors", "Landscapes")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterWorld = [MainInput](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + const TArray* InputObjects = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurInputObject : *InputObjects) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + AActor* CurActor = Cast(CurInputObject->GetObject()); + if (!CurActor || CurActor->IsPendingKill()) + { + // See if the input object is a HAC, if it is, get its parent actor + UHoudiniAssetComponent* CurHAC = Cast(CurInputObject->GetObject()); + if (CurHAC && !CurHAC->IsPendingKill()) + CurActor = CurHAC->GetOwner(); + } + + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + auto OnWorldSelected = [](AActor* Actor) + { + // Do Nothing + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterWorld); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputSelectedActors", "Currently Selected Actors")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnWorldSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilter = [MainInput](const AActor* const Actor) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + const TArray* BoundObjects = MainInput->GetBoundSelectorObjectArray(); + if (!BoundObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurActor : *BoundObjects) + { + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + + auto OnSelected = [](AActor* Actor) + { + // Do Nothing + }; + + FMenuBuilder MenuBuilder(true, nullptr); + MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputBoundSelectors", "Bound Selectors")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(FOnShouldFilterActor::CreateLambda(OnShouldFilter)); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +void +FHoudiniInputDetails::AddWorldInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + const IDetailsView* DetailsView) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); + + // Get the details view name and locked status + bool bDetailsLocked = false; + FName DetailsPanelName = "LevelEditorSelectionDetails"; + if (DetailsView) + { + DetailsPanelName = DetailsView->GetIdentifier(); + if (DetailsView->IsLocked()) + bDetailsLocked = true; + } + + // Check of we're in bound selector mode + bool bIsBoundSelector = MainInput->IsWorldInputBoundSelector(); + + // Button : Start Selection / Use current selection + refresh + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + //auto ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); + //auto ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); + FText ButtonLabel; + FText ButtonTooltip; + if (!bIsBoundSelector) + { + // Button : Start Selection / Use current selection + if (bDetailsLocked) + { + ButtonLabel = LOCTEXT("WorldInputUseCurrentSelection", "Use Current Selection (Unlocks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputUseCurrentSelectionTooltip", "Fill the asset's input with the current selection and unlocks the Details Panel."); + } + else + { + ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); + } + /* + FOnClicked OnSelectActors = FOnClicked::CreateStatic( + &FHoudiniInputDetails::Helper_OnButtonClickSelectActors, InInputs, DetailsPanelName); + */ + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ButtonLabel) + .ToolTipText(ButtonTooltip) + //.OnClicked(OnSelectActors) + .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() + { + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName); + }) + + ] + ]; + } + else + { + // Button : Start Selection / Use current selection as Bound selector + if (bDetailsLocked) + { + ButtonLabel = LOCTEXT("WorldInputUseSelectionAsBoundSelector", "Use Selection as Bound Selector (Unlocks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputUseSelectionAsBoundSelectorTooltip", "Fill the asset's input with all the actors contains in the current's selection bound, and unlocks the Details Panel."); + } + else + { + ButtonLabel = LOCTEXT("WorldInputStartBoundSelection", "Start Bound Selection (Locks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputStartBoundSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that will be used as bounds."); + } + + /* + FOnClicked OnSelectBounds = FOnClicked::CreateStatic( + &FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector, InInputs, DetailsPanelName); + */ + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ButtonLabel) + .ToolTipText(ButtonTooltip) + //.OnClicked(OnSelectBounds) + .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() + { + return Helper_OnButtonClickUseSelectionAsBoundSelector(CategoryBuilder, InInputs, DetailsPanelName); + }) + ] + ]; + } + } + + // Button : Select All + Clear Selection + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FOnClicked OnSelectAll = FOnClicked::CreateLambda([InInputs, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputSelectedAll", "Houdini Input: Selecting all actor in the current world"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // Get the parent component/actor/world of the current input + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + UWorld* MyWorld = CurrentInput->GetWorld(); + + TArray NewSelectedActors; + for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) + { + AActor *CurrentActor = *ActorItr; + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + NewSelectedActors.Add(CurrentActor); + } + + CurrentInput->Modify(); + + bool bHasChanged = CurrentInput->UpdateWorldSelection(NewSelectedActors); + } + + return FReply::Handled(); + }); + + FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput, &CategoryBuilder]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + const bool bMainInputBoundSelection = MainInput->IsWorldInputBoundSelector(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputClear", "Houdini Input: Clearing world input selection"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + // Do nothing if the current input has different selector settings from the main input + if (CurrentInput->IsWorldInputBoundSelector() != bMainInputBoundSelection) + continue; + + CurrentInput->Modify(); + + if (CurrentInput->IsWorldInputBoundSelector()) + { + CurrentInput->SetBoundSelectorObjectsNumber(0); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + else + { + TArray EmptySelection; + bool bHasChanged = CurrentInput->UpdateWorldSelection(EmptySelection); + } + } + + return FReply::Handled(); + }); + + FText ClearSelectionLabel; + FText ClearSelectionTooltip; + if (bIsBoundSelector) + { + ClearSelectionLabel = LOCTEXT("ClearBoundSelection", "Clear Bound Selection"); + ClearSelectionTooltip = LOCTEXT("ClearBoundSelectionTooltip", "Clear the input's current bound selection."); + } + else + { + ClearSelectionLabel = LOCTEXT("ClearSelection", "Clear Selection"); + ClearSelectionTooltip = LOCTEXT("ClearSelectionTooltip", "Clear the input's current selection."); + } + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + // Button : SelectAll + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("WorldInputSelectAll", "Select All")) + .ToolTipText(LOCTEXT("WorldInputSelectAll", "Fill the asset's input with all actors.")) + .OnClicked(OnSelectAll) + .IsEnabled(!bIsBoundSelector) + ] + + SHorizontalBox::Slot() + [ + // Button : Clear Selection + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ClearSelectionLabel) + .ToolTipText(ClearSelectionTooltip) + .OnClicked(OnClearSelect) + ] + ]; + + // Do not enable select all/clear select when selection has been started and details are locked + HorizontalBox->SetEnabled(!bDetailsLocked); + } + + // Checkbox: Bound Selector + { + // Lambda returning a CheckState from the input's current bound selector state + auto IsCheckedBoundSelector = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->IsWorldInputBoundSelector() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing bound selector state + auto CheckStateChangedIsBoundSelector = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeBoungSelector", "Houdini Input: Changing world input to bound selector"), + MainInput->GetOuter()); + + bool bNewState = (NewState == ECheckBoxState::Checked); + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->IsWorldInputBoundSelector() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetWorldInputBoundSelector(bNewState); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + // Checkbox : Is Bound Selector + TSharedPtr< SCheckBox > CheckBoxBoundSelector; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxBoundSelector, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("BoundSelector", "Bound Selector")) + .ToolTipText(LOCTEXT("BoundSelectorTip", "When enabled, this world input works as a bound selector, sending all the objects contained in the bound selector bounding boxes.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedBoundSelector, MainInput]() + { + return IsCheckedBoundSelector(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedIsBoundSelector, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedIsBoundSelector(InInputs, NewState); + }) + ]; + } + + // Checkbox: Bound Selector Auto update + { + // Lambda returning a CheckState from the input's current auto update state + auto IsCheckedAutoUpdate = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetWorldInputBoundSelectorAutoUpdates() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing the auto update state + auto CheckStateChangedBoundAutoUpdates = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeAutoUpdate", "Houdini Input: Changing bound selector auto-update state."), + MainInput->GetOuter()); + + bool bNewState = (NewState == ECheckBoxState::Checked); + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetWorldInputBoundSelectorAutoUpdates() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetWorldInputBoundSelectorAutoUpdates(bNewState); + CurInput->MarkChanged(true); + } + }; + + // Checkbox : Is Bound Selector + TSharedPtr< SCheckBox > CheckBoxBoundAutoUpdate; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxBoundAutoUpdate, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("BoundAutoUpdate", "Update bound selection automatically")) + .ToolTipText(LOCTEXT("BoundAutoUpdateTip", "If enabled and if this world input is set as a bound selector, the objects selected by the bounds will update automatically.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedAutoUpdate, MainInput]() + { + return IsCheckedAutoUpdate(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedBoundAutoUpdates, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedBoundAutoUpdates(InInputs, NewState); + }) + ]; + + CheckBoxBoundAutoUpdate->SetEnabled(MainInput->IsWorldInputBoundSelector()); + } + + // ActorPicker : Bound Selector + if(bIsBoundSelector) + { + FMenuBuilder MenuBuilder = Helper_CreateBoundSelectorPickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + // ActorPicker : World Outliner + { + FMenuBuilder MenuBuilder = Helper_CreateWorldActorPickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + { + // Spline Resolution + TSharedPtr> NumericEntryBox; + int32 Idx = 0; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("SplineRes", "Unreal Spline Resolution")) + .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm betweem control points)\nSet this to 0 to only export the control points.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .MinValue(-1.0f) + .MaxValue(1000.0f) + .MinSliderValue(0.0f) + .MaxSliderValue(1000.0f) + .Value(MainInput->GetUnrealSplineResolution()) + .OnValueChanged_Lambda([MainInput, InInputs](float Val) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeSplineResolution", "Houdini Input: Changing world input spline resolution"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetUnrealSplineResolution() == Val) + continue; + + CurrentInput->Modify(); + + CurrentInput->SetUnrealSplineResolution(Val); + CurrentInput->MarkChanged(true); + } + }) + /* + .Value(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetSplineResolutionValue))) + .OnValueChanged(SNumericEntryBox< float >::FOnValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetSplineResolutionValue)) + .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsSplineResolutionEnabled))) + */ + .SliderExponent(1.0f) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("SplineResToDefault", "Reset to default value.")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + // TODO: FINISH ME! + //.OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) + .OnClicked_Lambda([MainInput, InInputs]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputRevertSplineResolution", "Houdini Input: Reverting world input spline resolution to default"), + MainInput->GetOuter()); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + float DefaultSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetUnrealSplineResolution() == DefaultSplineResolution) + continue; + + CurrentInput->Modify(); + + CurrentInput->SetUnrealSplineResolution(DefaultSplineResolution); + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + } +} + +void +FHoudiniInputDetails::AddSkeletalInputUI( + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool ) +{ +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) +{ + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, false); +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) +{ + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, true); +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName, const bool& bUseWorldInAsWorldSelector) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // There's no undo operation for button. + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Locate the details panel. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + return FReply::Handled(); + + class SLocalDetailsView : public SDetailsViewBase + { + public: + void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } + void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } + }; + auto * LocalDetailsView = static_cast(DetailsView.Get()); + + if (!DetailsView->IsLocked()) + { + // + // START SELECTION + // Locks the details view and select our currently selected actors + // + LocalDetailsView->LockDetailsView(); + check(DetailsView->IsLocked()); + + // Force refresh of details view. + TArray InputOuters; + for (auto CurIn : InInputs) + InputOuters.Add(CurIn->GetOuter()); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + //ReselectSelectedActors(); + + if (bUseWorldInAsWorldSelector) + { + // Bound Selection + // Select back the previously chosen bound selectors + GEditor->SelectNone(false, true); + int32 NumBoundSelectors = MainInput->GetNumberOfBoundSelectorObjects(); + for (int32 Idx = 0; Idx < NumBoundSelectors; Idx++) + { + AActor* Actor = MainInput->GetBoundSelectorObjectAt(Idx); + if (!Actor || Actor->IsPendingKill()) + continue; + + GEditor->SelectActor(Actor, true, true); + } + } + else + { + // Regular selection + // Select the already chosen input Actors from the World Outliner. + GEditor->SelectNone(false, true); + int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); + for (int32 Idx = 0; Idx < NumInputObjects; Idx++) + { + UHoudiniInputObject* CurInputObject = MainInput->GetHoudiniInputObjectAt(Idx); + if (!CurInputObject) + continue; + + AActor* Actor = nullptr; + UHoudiniInputActor* InputActor = Cast(CurInputObject); + if (InputActor && !InputActor->IsPendingKill()) + { + // Get the input actor + Actor = InputActor->GetActor(); + } + else + { + // See if the input object is a HAC + UHoudiniInputHoudiniAsset* InputHAC = Cast(CurInputObject); + if (InputHAC && !InputHAC->IsPendingKill()) + { + Actor = InputHAC->GetHoudiniAssetComponent() ? InputHAC->GetHoudiniAssetComponent()->GetOwner() : nullptr; + } + } + + if (!Actor || Actor->IsPendingKill()) + continue; + + GEditor->SelectActor(Actor, true, true); + } + } + + return FReply::Handled(); + } + else + { + // + // UPDATE SELECTION + // Unlocks the input's selection and select the HDA back. + // + + if (!GEditor || !GEditor->GetSelectedObjects()) + return FReply::Handled(); + + USelection * SelectedActors = GEditor->GetSelectedActors(); + if (!SelectedActors) + return FReply::Handled(); + + // Create a transaction + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniWorldInputSelectionChanged", "Changing Houdini world outliner input objects"), + MainInput->GetOuter()); + + + TArray AllActors; + for (auto CurrentInput : InInputs) + { + CurrentInput->Modify(); + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + AllActors.Add(ParentActor); + + bool bHasChanged = true; + if (bUseWorldInAsWorldSelector) + { + // + // Update bound selectors + + // Clean up the selected actors + TArray ValidBoundSelectedActors; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor* CurrentBoundActor = Cast(*It); + if (!CurrentBoundActor) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentBoundActor == ParentActor)) + continue; + + ValidBoundSelectedActors.Add(CurrentBoundActor); + } + + // See if the bound selector have changed + int32 PreviousBoundSelectorCount = CurrentInput->GetNumberOfBoundSelectorObjects(); + if (PreviousBoundSelectorCount == ValidBoundSelectedActors.Num()) + { + // Same number of BoundSelectors, see if they have changed + bHasChanged = false; + for (int32 BoundIdx = 0; BoundIdx < PreviousBoundSelectorCount; BoundIdx++) + { + AActor* PreviousBound = CurrentInput->GetBoundSelectorObjectAt(BoundIdx); + if (!PreviousBound) + continue; + + if (!ValidBoundSelectedActors.Contains(PreviousBound)) + { + bHasChanged = true; + break; + } + } + } + + if (bHasChanged) + { + // Only update the bound selector objects on the input if they have changed + CurrentInput->SetBoundSelectorObjectsNumber(ValidBoundSelectedActors.Num()); + int32 InputObjectIdx = 0; + for (auto CurActor : ValidBoundSelectedActors) + { + CurrentInput->SetBoundSelectorObjectAt(InputObjectIdx++, CurActor); + } + + // Update the current selection from the BoundSelectors + CurrentInput->UpdateWorldSelectionFromBoundSelectors(); + } + } + else + { + // + // Update our selection directly with the currently selected actors + // + + TArray ValidSelectedActors; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor* CurrentActor = Cast(*It); + if (!CurrentActor) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + ValidSelectedActors.Add(CurrentActor); + } + + // Update the input objects from the valid selected actors array + // Only new/remove input objects will be marked as changed + bHasChanged = CurrentInput->UpdateWorldSelection(ValidSelectedActors); + } + + // If we didnt change the selection, cancel the transaction + if (!bHasChanged) + Transaction.Cancel(); + } + + // We can now unlock the details view... + LocalDetailsView->UnlockDetailsView(); + check(!DetailsView->IsLocked()); + + // .. reset the selected actors, force refresh and override the lock. + DetailsView->SetObjects(AllActors, true, true); + + // We now need to reselect all our Asset Actors. + // If we don't do this, our Asset parameters will stop refreshing and the user will be very confused. + // It is also resetting the state of the selection before the input actor selection process was started. + GEditor->SelectNone(false, true); + for (auto CurrentActor : AllActors) + { + AActor* ParentActor = Cast(CurrentActor); + if (!ParentActor) + continue; + + GEditor->SelectActor(ParentActor, true, true); + } + + // Update the input details layout. + // if (CategoryBuilder.IsParentLayoutValid()) + // CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); +} + + +bool +FHoudiniInputDetails::Helper_CancelWorldSelection(TArray& InInputs, const FName& DetailsPanelName) +{ + if (InInputs.Num() <= 0) + return false; + + // Get the property module to access the details view + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Locate the details panel. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + return false; + + if (!DetailsView->IsLocked()) + return false; + + class SLocalDetailsView : public SDetailsViewBase + { + public: + void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } + void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } + }; + auto * LocalDetailsView = static_cast(DetailsView.Get()); + + // Get all our parent components / actors + TArray AllComponents; + TArray AllActors; + for (auto CurrentInput : InInputs) + { + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + if (!ParentComponent) + continue; + + AllComponents.Add(ParentComponent); + + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + if (!ParentActor) + continue; + + AllActors.Add(ParentActor); + } + + // Unlock the detail view and re-select our parent actors + { + LocalDetailsView->UnlockDetailsView(); + check(!DetailsView->IsLocked()); + + // Reset selected actor to itself, force refresh and override the lock. + DetailsView->SetObjects(AllActors, true, true); + } + + // Reselect the Asset Actor. If we don't do this, our Asset parameters will stop + // refreshing and the user will be very confused. It is also resetting the state + // of the selection before the input actor selection process was started. + GEditor->SelectNone(false, true); + for (auto ParentActorObj : AllActors) + { + AActor* ParentActor = Cast(ParentActorObj); + if (!ParentActor) + continue; + + GEditor->SelectActor(ParentActor, true, true); + } + + return true; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h index 3382ffd1f..831dbe331 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h @@ -1,165 +1,168 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -class UHoudiniInput; -class UHoudiniSplineComponent; - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class FMenuBuilder; -class SVerticalBox; -class IDetailsView; -class FReply; -class FAssetThumbnailPool; - -class FHoudiniInputDetails : public TSharedFromThis -{ - public: - static void CreateWidget( - IDetailCategoryBuilder& HouInputCategoryBuilder, - TArray InInputs, FDetailWidgetRow* InputRow = nullptr); - - static void CreateNameWidget( - UHoudiniInput* InParam, - FDetailWidgetRow & Row, - bool bLabel, - int32 InInputCount); - - static FText GetInputTooltip( UHoudiniInput* InInput ); - - // ComboBox : Input Type - static void AddInputTypeComboBox( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - const IDetailsView* InDetailsView); - - // Checkbox : Keep World Transform - static void AddKeepWorldTransformCheckBox( - TSharedRef InVerticalBox, - TArray& InInputs); - - static void AddCurveInputCookOnChangeCheckBox( - TSharedRef< SVerticalBox > VerticalBox, - TArray& InInputs); - - // Checkbox : Pack before merging - static void AddPackBeforeMergeCheckbox( - TSharedRef InVerticalBox, - TArray& InInputs); - - static void AddImportAsReferenceCheckbox( - TSharedRef< SVerticalBox > VerticalBox, - TArray& InInputs); - - // Checkboxes : Export LODs / Sockets / Collisions - static void AddExportCheckboxes( - TSharedRef InVerticalBox, - TArray& InInputs); - - // Add Geometry Inputs UI Widgets - static void AddGeometryInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - - // Create a single geometry widget for the given input object - static void Helper_CreateGeometryWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const FPlatformTypes::int32& InGeometryObjectIdx, - TSharedPtr AssetThumbnailPool, TSharedRef VerticalBox); - - static void Helper_CreateCurveWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InCurveObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox, - TSharedPtr HouSplineComponentVisualizer); - - // Add Asset Inputs UI Widgets - static void AddAssetInputUI( - TSharedRef VerticalBox, - TArray& InInputs); - - // Add Curve Inputs UI Widgets - static void AddCurveInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - - // Add Landscape Inputs UI Widgets - static void AddLandscapeInputUI( - TSharedRef VerticalBox, - TArray& InInputs); - - // Add World Inputs UI Widgets - static void AddWorldInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - const IDetailsView* InDetailsView); - - // Add Skeletal Inputs UI Widgets - static void AddSkeletalInputUI( - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - /* - static FMenuBuilder Helper_CreateCustomActorPickerWidget( - UHoudiniInput* InParam, - const TAttribute& HeadingText, - const bool& bShowCurrentSelectionSection) - */ - - static FMenuBuilder Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateLandscapePickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateWorldActorPickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateBoundSelectorPickerWidget(TArray& InInputs); - - static FReply Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); - - static FReply Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); - - static FReply Helper_OnButtonClickSelectActors( - IDetailCategoryBuilder& CategoryBuilder, - TArray InInputs, - const FName& InDetailsPanelName, - const bool& bUseWorldInAsWorldSelector); - - static bool Helper_CancelWorldSelection( - TArray& InInputs, const FName& DetailsPanelName); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Widgets/SBoxPanel.h" +#include "IDetailsView.h" + +class UHoudiniInput; +class UHoudiniSplineComponent; + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class FMenuBuilder; +class SVerticalBox; +class IDetailsView; +class FReply; +class FAssetThumbnailPool; + +class FHoudiniInputDetails : public TSharedFromThis +{ + public: + static void CreateWidget( + IDetailCategoryBuilder& HouInputCategoryBuilder, + TArray InInputs, FDetailWidgetRow* InputRow = nullptr); + + static void CreateNameWidget( + UHoudiniInput* InParam, + FDetailWidgetRow & Row, + bool bLabel, + int32 InInputCount); + + static FText GetInputTooltip( UHoudiniInput* InInput ); + + // ComboBox : Input Type + static void AddInputTypeComboBox( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + const IDetailsView* InDetailsView); + + // Checkbox : Keep World Transform + static void AddKeepWorldTransformCheckBox( + TSharedRef InVerticalBox, + TArray& InInputs); + + static void AddCurveInputCookOnChangeCheckBox( + TSharedRef< SVerticalBox > VerticalBox, + TArray& InInputs); + + // Checkbox : Pack before merging + static void AddPackBeforeMergeCheckbox( + TSharedRef InVerticalBox, + TArray& InInputs); + + static void AddImportAsReferenceCheckbox( + TSharedRef< SVerticalBox > VerticalBox, + TArray& InInputs); + + // Checkboxes : Export LODs / Sockets / Collisions + static void AddExportCheckboxes( + TSharedRef InVerticalBox, + TArray& InInputs); + + // Add Geometry Inputs UI Widgets + static void AddGeometryInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + + // Create a single geometry widget for the given input object + static void Helper_CreateGeometryWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const FPlatformTypes::int32& InGeometryObjectIdx, + TSharedPtr AssetThumbnailPool, TSharedRef VerticalBox); + + static void Helper_CreateCurveWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InCurveObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox, + TSharedPtr HouSplineComponentVisualizer); + + // Add Asset Inputs UI Widgets + static void AddAssetInputUI( + TSharedRef VerticalBox, + TArray& InInputs); + + // Add Curve Inputs UI Widgets + static void AddCurveInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + + // Add Landscape Inputs UI Widgets + static void AddLandscapeInputUI( + TSharedRef VerticalBox, + TArray& InInputs); + + // Add World Inputs UI Widgets + static void AddWorldInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + const IDetailsView* InDetailsView); + + // Add Skeletal Inputs UI Widgets + static void AddSkeletalInputUI( + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + /* + static FMenuBuilder Helper_CreateCustomActorPickerWidget( + UHoudiniInput* InParam, + const TAttribute& HeadingText, + const bool& bShowCurrentSelectionSection) + */ + + static FMenuBuilder Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateLandscapePickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateWorldActorPickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateBoundSelectorPickerWidget(TArray& InInputs); + + static FReply Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); + + static FReply Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); + + static FReply Helper_OnButtonClickSelectActors( + IDetailCategoryBuilder& CategoryBuilder, + TArray InInputs, + const FName& InDetailsPanelName, + const bool& bUseWorldInAsWorldSelector); + + static bool Helper_CancelWorldSelection( + TArray& InInputs, const FName& DetailsPanelName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp index 0a8d315fa..5c0fe7b7f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp @@ -1,3245 +1,3243 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniOutputDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniAssetComponentDetails.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAsset.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniEngineCommands.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "DetailWidgetRow.h" -#include "IDetailGroup.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SRotatorInputBox.h" -#include "Widgets/Layout/SBox.h" -#include "Widgets/Text/STextBlock.h" -#include "Editor/UnrealEd/Public/AssetThumbnail.h" -#include "SAssetDropTarget.h" -#include "Engine/StaticMesh.h" -#include "Components/SplineComponent.h" -#include "Materials/Material.h" -#include "Materials/MaterialInstance.h" -#include "Materials/MaterialInstanceConstant.h" -#include "Sound/SoundBase.h" -#include "Engine/SkeletalMesh.h" -#include "Particles/ParticleSystem.h" -//#include "Landscape.h" -#include "LandscapeProxy.h" -#include "ScopedTransaction.h" -#include "PhysicsEngine/BodySetup.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" -#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -void -FHoudiniOutputDetails::CreateWidget( - IDetailCategoryBuilder& HouOutputCategory, - TArray InOutputs) -{ - if (InOutputs.Num() <= 0) - return; - - UHoudiniOutput* MainOutput = InOutputs[0]; - - // Don't create UI for editable curve. - if (!MainOutput || MainOutput->IsPendingKill() || MainOutput->IsEditableNode()) - return; - - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); - - // TODO - // For now we just handle Mesh Outputs - - switch (MainOutput->GetType()) - { - case EHoudiniOutputType::Mesh: - { - FHoudiniOutputDetails::CreateMeshOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Landscape: - { - FHoudiniOutputDetails::CreateLandscapeOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Instancer: - { - FHoudiniOutputDetails::CreateInstancerOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Curve: - { - FHoudiniOutputDetails::CreateCurveOutputWidget(HouOutputCategory, MainOutput); - break; - } - case EHoudiniOutputType::Skeletal: - default: - { - FHoudiniOutputDetails::CreateDefaultOutputWidget(HouOutputCategory, MainOutput); - break; - } - - } - -} - - -void -FHoudiniOutputDetails::CreateLandscapeOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // Go through this output's objects - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& CurrentOutputObj : OutputObjects) - { - UHoudiniLandscapePtr* LandscapePointer = Cast(CurrentOutputObj.Value.OutputObject); - if (!LandscapePointer) - continue; - - FHoudiniOutputObjectIdentifier& Identifier = CurrentOutputObj.Key; - const FHoudiniGeoPartObject *HGPO = nullptr; - for (const auto& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!Identifier.Matches(CurHGPO)) - continue; - - HGPO = &CurHGPO; - break; - } - - if (!HGPO) - continue; - - CreateLandscapeOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapePointer, Identifier); - } -} - -void -FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - const FHoudiniGeoPartObject& HGPO, - UHoudiniLandscapePtr* LandscapePointer, - const FHoudiniOutputObjectIdentifier & OutputIdentifier) -{ - if (!LandscapePointer || LandscapePointer->IsPendingKill() || !LandscapePointer->LandscapeSoftPtr.IsValid()) - return; - - if (!InOutput || InOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return; - - ALandscapeProxy * Landscape = LandscapePointer->LandscapeSoftPtr.Get(); - if (!Landscape || Landscape->IsPendingKill()) - return; - - // TODO: Get bake base name - FString Label = Landscape->GetName(); - - EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType = LandscapePointer->BakeType; - - // Get thumbnail pool for this builder - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - TArray>* BakeOptionString = FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels(); - - // Create bake mesh name textfield. - IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - LandscapeGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(Label)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - // Create the thumbnail for the landscape output object. - TSharedPtr< FAssetThumbnail > LandscapeThumbnail = - MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); - - TSharedPtr< SBorder > LandscapeThumbnailBorder; - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - LandscapeGrp.AddWidgetRow() - .NameContent() - [ - SNew(SSpacer) - .Size(FVector2D(250, 64)) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot().Padding(0, 2).AutoHeight() - [ - SNew(SBox).WidthOverride(175) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(LandscapeThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(Landscape->GetPathName())) - [ - LandscapeThumbnail->MakeThumbnailWidget() - ] - ] - ] - - + SHorizontalBox::Slot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SBox).WidthOverride(40.0f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("Bake", "Bake")) - .IsEnabled(true) - .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() - { - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - if (FoundOutputObject) - { - TArray AllOutputs; - AllOutputs.Reserve(HAC->GetNumOutputs()); - HAC->GetOutputs(AllOutputs); - FHoudiniOutputDetails::OnBakeOutputObject( - FoundOutputObject->BakeName, - Landscape, - OutputIdentifier, - HGPO, - OwnerActor->GetName(), - HAC->BakeFolder.Path, - HAC->TemporaryCookFolder.Path, - InOutput->GetType(), - LandscapeOutputBakeType, - AllOutputs); - } - - // TODO: Remove the output landscape if the landscape bake type is Detachment? - return FReply::Handled(); - }) - .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) - ] - ] - + SHorizontalBox::Slot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SBox).WidthOverride(120.f) - [ - SNew(SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - if (SelectType != ESelectInfo::Type::OnMouseClick) - return; - - FString *NewChoiceStr = NewChoice.Get(); - if (!NewChoiceStr) - return; - - if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); - } - else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); - } - else - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); - } - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - [ - SNew(STextBlock) - .Text_Lambda([LandscapePointer]() - { - FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); - return FText::FromString(BakeTypeString); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ] - ] - ]; - - // Store thumbnail for this landscape. - OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); - - // We need to add material box for each the landscape and landscape hole materials - for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) - { - UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); - TSharedPtr MaterialThumbnailBorder; - TSharedPtr HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if (MaterialInterface) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - - // Create thumbnail for this material. - TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = - MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(STextBlock) - .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().Padding(0, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) - .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(MaterialThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(MaterialPathName)) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this landscape and material index. - { - TPairInitializer Pair(Landscape, MaterialIdx); - MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); - } - - // Combox Box and Button Box - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Combo row - TSharedPtr< SComboButton > AssetComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) - [ - SAssignNew(AssetComboButton, SComboButton) - //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - // Buttons row - TSharedPtr ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Add use Content Browser selection arrow - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)Landscape, InOutput, MaterialIdx), - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) - ]; - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), - TAttribute< FText >(MaterialTooltip)) - ]; - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - - // Store combo button for this mesh and index. - { - TPairInitializer Pair(Landscape, MaterialIdx); - MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); - } - } - -} - -void -FHoudiniOutputDetails::CreateMeshOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent* HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - FString HoudiniAssetName; - if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) - { - HoudiniAssetName = HAC->GetOwner()->GetName(); - } - else if (HAC->GetHoudiniAsset()) - { - HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); - } - else - { - HoudiniAssetName = HAC->GetName(); - } - - // Go through this output's object - int32 OutputObjIdx = 0; - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& IterObject : OutputObjects) - { - UStaticMesh* StaticMesh = Cast(IterObject.Value.OutputObject); - UHoudiniStaticMesh* ProxyMesh = Cast(IterObject.Value.ProxyObject); - - if ((!StaticMesh || StaticMesh->IsPendingKill()) - && (!ProxyMesh || ProxyMesh->IsPendingKill())) - continue; - - FHoudiniOutputObjectIdentifier & OutputIdentifier = IterObject.Key; - - // Find the corresponding HGPO in the output - FHoudiniGeoPartObject HoudiniGeoPartObject; - for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!OutputIdentifier.Matches(curHGPO)) - continue; - - HoudiniGeoPartObject = curHGPO; - break; - } - - if (StaticMesh && !StaticMesh->IsPendingKill()) - { - bool bIsProxyMeshCurrent = IterObject.Value.bProxyIsCurrent; - - // If we have a static mesh, alway display its widget even if the proxy is more recent - CreateStaticMeshAndMaterialWidgets( - HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); - } - else - { - // If we only have a proxy mesh, then show the proxy widget - CreateProxyMeshAndMaterialWidgets( - HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject); - } - } -} - -void -FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - int32 OutputObjIdx = 0; - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& IterObject : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = IterObject.Value; - USceneComponent* SplineComponent = Cast(IterObject.Value.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - FHoudiniOutputObjectIdentifier& OutputIdentifier = IterObject.Key; - FHoudiniGeoPartObject HoudiniGeoPartObject; - for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!OutputIdentifier.Matches(curHGPO)) - continue; - - HoudiniGeoPartObject = curHGPO; - break; - } - - CreateCurveWidgets(HouOutputCategory, InOutput, SplineComponent, CurrentOutputObject, OutputIdentifier, HoudiniGeoPartObject); - } -} - -void -FHoudiniOutputDetails::CreateCurveWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - USceneComponent* SplineComponent, - FHoudiniOutputObject& OutputObject, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - FHoudiniGeoPartObject& HoudiniGeoPartObject) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // We support Unreal Spline out only for now - USplineComponent* SplineOutput = Cast(SplineComponent); - if (!SplineOutput || SplineOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return; - - FHoudiniCurveOutputProperties* OutputProperty = &(OutputObject.CurveOutputProperty); - EHoudiniCurveType OutputCurveType = OutputObject.CurveOutputProperty.CurveType; - - FString Label = SplineComponent->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - //Label += FString("_") + OutputIdentifier.SplitIdentifier; - - FString OutputCurveName = OutputObject.BakeName; - if(OutputCurveName.IsEmpty()) - OutputCurveName = OwnerActor->GetName() + "_" + Label; - - const FText& LabelText = FText::FromString("Unreal Spline"); - - IDetailGroup& CurveOutputGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label), false, false); - - // Bake name row UI - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(OutputObject.BakeName)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - ] - - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("OutputCurveSplineType", "Spline Type")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText_Lambda([SplineOutput, Label, OutputCurveType]() - { - FString ToolTipStr = FString::Printf(TEXT(" curve: %s\n Export type: Unreal Spline\n num points: %d\n curve type: %s\n closed: %s"), - *Label, - SplineOutput->GetNumberOfSplinePoints(), - *FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(OutputCurveType), - SplineOutput->IsClosedLoop() ? *(FString("yes")) : *(FString("no"))); - - return FText::FromString(ToolTipStr); - }) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(STextBlock) - // We support Unreal Spline output only for now... - .Text(LOCTEXT("OutputCurveSplineTypeUnreal", "Unreal Spline")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ]; - - //if (bIsUnrealSpline) - //{ - USplineComponent* UnrealSpline = Cast(SplineComponent); - - // Curve type combo box UI - auto InitialSelectionLambda = [OutputProperty]() - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - { - return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[0]; - } - else - { - return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[1]; - } - }; - - TSharedPtr>> UnrealCurveTypeComboBox; - - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Text(LOCTEXT("OutputCurveUnrealSplinePointType", "Spline Point Type")) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(UnrealCurveTypeComboBox, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels()) - .InitiallySelectedItem(InitialSelectionLambda()) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [OutputProperty, InOutput, SplineComponent](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - // Set the curve point type locally - USplineComponent* Spline = Cast(SplineComponent); - if (!Spline || Spline->IsPendingKill()) - return; - - FString *NewChoiceStr = NewChoice.Get(); - if (!NewChoiceStr) - return; - - if (*NewChoiceStr == "Linear") - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - return; - - OutputProperty->CurveType = EHoudiniCurveType::Polygon; - - for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) - { - Spline->SetSplinePointType(PtIdx, ESplinePointType::Linear); - } - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - } - else if (*NewChoiceStr == "Curve") - { - if (OutputProperty->CurveType != EHoudiniCurveType::Polygon) - return; - - OutputProperty->CurveType = EHoudiniCurveType::Bezier; - - for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) - { - Spline->SetSplinePointType(PtIdx, ESplinePointType::Curve); - } - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([OutputProperty]() - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - return FText::FromString(TEXT("Linear")); - else - return FText::FromString(TEXT("Curve")); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - // Add closed curve checkbox UI - TSharedPtr ClosedCheckBox; - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Text(LOCTEXT("OutputCurveUnrealSplineClosed", "Closed")) - ] - .ValueContent() - [ - SAssignNew(ClosedCheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([UnrealSpline, InOutput](ECheckBoxState NewState) - { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) - return; - - UnrealSpline->SetClosedLoop(NewState == ECheckBoxState::Checked); - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - .IsChecked_Lambda([UnrealSpline]() - { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return UnrealSpline->IsClosedLoop() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - ]; - //} - - // Add Bake Button UI - TSharedPtr BakeButton; - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - ] - .ValueContent() - [ - SAssignNew(BakeButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) - .IsEnabled(true) - .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) - .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName]() - { - TArray AllOutputs; - AllOutputs.Reserve(HAC->GetNumOutputs()); - HAC->GetOutputs(AllOutputs); - FHoudiniOutputDetails::OnBakeOutputObject( - OutputCurveName, - SplineComponent, - OutputIdentifier, - HoudiniGeoPartObject, - OwnerActor->GetName(), - HAC->BakeFolder.Path, - HAC->TemporaryCookFolder.Path, - InOutput->GetType(), - EHoudiniLandscapeOutputBakeType::InValid, - AllOutputs); - - return FReply::Handled(); - }) - ]; -} - -void -FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UStaticMesh * StaticMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject, - const bool& bIsProxyMeshCurrent) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return; - - UHoudiniAssetComponent* OwningHAC = Cast(InOutput->GetOuter()); - - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); - - // Get thumbnail pool for this builder. - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // TODO: GetBakingBaseName! - FString Label = StaticMesh->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - // Create thumbnail for this mesh. - TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = - MakeShareable(new FAssetThumbnail(StaticMesh, 64, 64, AssetThumbnailPool)); - TSharedPtr StaticMeshThumbnailBorder; - - TSharedRef VerticalBox = SNew(SVerticalBox); - - IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(BakeName)) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - }) - .ToolTipText( LOCTEXT( "BakeNameTip", "The base name of the baked asset") ) - ] - - +SHorizontalBox::Slot() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "RevertNameOverride", "Revert bake name override" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ] - ]; - - // Add details on the SM colliders - EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); - FString MeshLabel = TEXT( "Static Mesh" ); - - // If the Proxy mesh is more recent, indicate it in the details - if (bIsProxyMeshCurrent) - { - MeshLabel += TEXT("\n(unrefined)"); - } - - // Indicate that this mesh is instanced - if (HoudiniGeoPartObject.bIsInstanced) - { - MeshLabel += TEXT("\n(instanced)"); - } - - if (HoudiniGeoPartObject.bIsTemplated) - { - MeshLabel += TEXT("\n(templated)"); - } - - int32 NumSimpleColliders = 0; - if (StaticMesh->BodySetup && !StaticMesh->BodySetup->IsPendingKill()) - NumSimpleColliders = StaticMesh->BodySetup->AggGeom.GetElementCount(); - - if(NumSimpleColliders > 0) - { - MeshLabel += TEXT( "\n(") + FString::FromInt(NumSimpleColliders) + TEXT(" Simple Collider" ); - if (NumSimpleColliders > 1 ) - MeshLabel += TEXT("s"); - MeshLabel += TEXT(")"); - } - else if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - MeshLabel += TEXT( "\n(Rendered Complex Collider)" ); - } - else if(SplitType == EHoudiniSplitType::InvisibleComplexCollider ) - { - MeshLabel += TEXT( "\n(Invisible Complex Collider)" ); - } - - if ( StaticMesh->GetNumLODs() > 1 ) - MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)"); - - if ( StaticMesh->Sockets.Num() > 0 ) - MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->Sockets.Num() ) + TEXT(" sockets)"); - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew( STextBlock ) - .Text( FText::FromString(MeshLabel) ) - .Font( IDetailLayoutBuilder::GetDetailFont() ) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot() - .Padding( 0, 2 ) - .AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) - .AutoWidth() - [ - SAssignNew( StaticMeshThumbnailBorder, SBorder ) - .Padding( 5.0f ) - .BorderImage( this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)StaticMesh ) - .OnMouseDoubleClick( this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *) StaticMesh ) - [ - SNew( SBox ) - .WidthOverride( 64 ) - .HeightOverride( 64 ) - .ToolTipText( FText::FromString( StaticMesh->GetPathName() ) ) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ] - - +SHorizontalBox::Slot() - .FillWidth( 1.0f ) - .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SVerticalBox ) - +SVerticalBox::Slot() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .MaxWidth( 80.0f ) - [ - SNew( SButton ) - .VAlign( VAlign_Center ) - .HAlign( HAlign_Center ) - .Text( LOCTEXT( "Bake", "Bake" ) ) - .IsEnabled(true) - .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC]() - { - TArray AllOutputs; - FString TempCookFolder; - if (IsValid(OwningHAC)) - { - AllOutputs.Reserve(OwningHAC->GetNumOutputs()); - OwningHAC->GetOutputs(AllOutputs); - - TempCookFolder = OwningHAC->TemporaryCookFolder.Path; - } - FHoudiniOutputDetails::OnBakeOutputObject( - BakeName, - StaticMesh, - OutputIdentifier, - HoudiniGeoPartObject, - HoudiniAssetName, - BakeFolder, - TempCookFolder, - InOutput->GetType(), - EHoudiniLandscapeOutputBakeType::InValid, - AllOutputs); - - return FReply::Handled(); - }) - .ToolTipText( LOCTEXT( "HoudiniStaticMeshBakeButton", "Bake this generated static mesh" ) ) - ] - +SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)StaticMesh), - TAttribute(LOCTEXT("HoudiniStaticMeshBrowseButton", "Browse to this generated static mesh in the content browser"))) - ] - ] - ] - ]; - - // Store thumbnail for this mesh. - OutputObjectThumbnailBorders.Add((UObject*)StaticMesh, StaticMeshThumbnailBorder); - - // We need to add material box for each material present in this static mesh. - auto & StaticMeshMaterials = StaticMesh->StaticMaterials; - for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) - { - UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface; - TSharedPtr< SBorder > MaterialThumbnailBorder; - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if ( MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - else - { - MaterialInterface = nullptr; - MaterialName = TEXT("Material (invalid)") + FString::FromInt( MaterialIdx ) ; - MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - } - - // Create thumbnail for this material. - TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = - MakeShareable( new FAssetThumbnail( MaterialInterface, 64, 64, AssetThumbnailPool ) ); - - VerticalBox->AddSlot().Padding( 0, 2 ) - [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop( this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver ) - .OnAssetDropped( - this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx ) - [ - SAssignNew( HorizontalBox, SHorizontalBox ) - ] - ]; - - HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() - [ - SAssignNew( MaterialThumbnailBorder, SBorder ) - .Padding( 5.0f ) - .BorderImage( - this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject *)StaticMesh, MaterialIdx ) - .OnMouseDoubleClick( - this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface ) - [ - SNew( SBox ) - .WidthOverride( 64 ) - .HeightOverride( 64 ) - .ToolTipText( FText::FromString( MaterialPathName ) ) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this mesh and material index. - { - TPairInitializer Pair( StaticMesh, MaterialIdx ); - MaterialInterfaceThumbnailBorders.Add( Pair, MaterialThumbnailBorder ); - } - - // ComboBox and buttons - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add Combo box - TSharedPtr< SComboButton > AssetComboButton; - ComboAndButtonBox->AddSlot().VAlign(VAlign_Center).FillHeight(1.0f) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().VAlign(VAlign_Center).FillHeight(1.0f) - [ - SAssignNew(AssetComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, - MaterialInterface, (UObject*)StaticMesh, InOutput, MaterialIdx) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - - // Add buttons - TSharedPtr< SHorizontalBox > ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Use CB selection arrow button - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)StaticMesh, InOutput, MaterialIdx), - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) - ]; - - // Browse CB button - ButtonBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface ), TAttribute< FText >( MaterialTooltip ) ) - ]; - - // Reset button - ButtonBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "ResetToBaseMaterial", "Reset to base material" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked( - this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ]; - - // Store combo button for this mesh and index. - { - TPairInitializer Pair( StaticMesh, MaterialIdx ); - MaterialInterfaceComboButtons.Add( Pair, AssetComboButton ); - } - } -} - -void -FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UHoudiniStaticMesh * ProxyMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject) -{ - if (!ProxyMesh || ProxyMesh->IsPendingKill()) - return; - - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); - - // Get thumbnail pool for this builder. - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // TODO: GetBakingBaseName! - FString Label = ProxyMesh->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - // Create thumbnail for this mesh. - TSharedPtr MeshThumbnail = MakeShareable(new FAssetThumbnail(ProxyMesh, 64, 64, AssetThumbnailPool)); - TSharedPtr MeshThumbnailBorder; - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(BakeName)) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - }) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - // Add details on the Proxy Mesh - EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); - FString MeshLabel = TEXT("Proxy Mesh"); - - // Indicate that this mesh is instanced - if (HoudiniGeoPartObject.bIsInstanced) - { - MeshLabel += TEXT("\n(instanced)"); - } - - if (HoudiniGeoPartObject.bIsTemplated) - { - MeshLabel += TEXT("\n(templated)"); - } - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(FText::FromString(MeshLabel)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot() - .Padding(0, 2) - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(MeshThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)ProxyMesh) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)ProxyMesh) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(ProxyMesh->GetPathName())) - [ - MeshThumbnail->MakeThumbnailWidget() - ] - ] - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .MaxWidth(80.0f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("Refine", "Refine")) - .IsEnabled(true) - .OnClicked(this, &FHoudiniOutputDetails::OnRefineClicked, (UObject *)ProxyMesh, InOutput) - .ToolTipText(LOCTEXT("RefineTooltip", "Refine this Proxy Mesh to a Static Mesh")) - ] - ] - ] - ]; - - // Store thumbnail for this mesh. - OutputObjectThumbnailBorders.Add(ProxyMesh, MeshThumbnailBorder); - - // We need to add material box for each material present in this static mesh. - auto & ProxyMeshMaterials = ProxyMesh->GetStaticMaterials(); - for (int32 MaterialIdx = 0; MaterialIdx < ProxyMeshMaterials.Num(); ++MaterialIdx) - { - UMaterialInterface * MaterialInterface = ProxyMeshMaterials[MaterialIdx].MaterialInterface; - TSharedPtr< SBorder > MaterialThumbnailBorder; - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if (MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill()) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - else - { - MaterialInterface = nullptr; - MaterialName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - } - - // Create thumbnail for this material. - TSharedPtr MaterialInterfaceThumbnail = - MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); - - // No drop target - VerticalBox->AddSlot() - .Padding(0, 2) - [ - SNew(SAssetDropTarget) - //.OnIsAssetAcceptableForDrop(false) - //.OnAssetDropped( - // this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - HorizontalBox->AddSlot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(MaterialThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage( - this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)ProxyMesh, MaterialIdx) - .OnMouseDoubleClick( - this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(MaterialPathName)) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this mesh and material index. - { - TPairInitializer Pair((UObject*)ProxyMesh, MaterialIdx); - MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); - } - - // Combo box and buttons - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add combo box - TSharedPtr AssetComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SAssignNew(AssetComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - /*.OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, - MaterialInterface, StaticMesh, InOutput, MaterialIdx)*/ - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - - TSharedPtr ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Disable the combobutton for proxies - AssetComboButton->SetEnabled(false); - - // Add use selection form content browser array - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - /*FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)ProxyMesh, InOutput, MaterialIdx),*/ - FSimpleDelegate::CreateLambda([]() {}), // Do nothing for proxies - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser")), false) - // Disable the use CB selection button for proxies - ]; - - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP(this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), TAttribute(MaterialTooltip)) - ]; - - /* - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked( - this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - */ - - // Store combo button for this mesh and index. - { - TPairInitializer Pair(ProxyMesh, MaterialIdx); - MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); - } - } -} - -FText -FHoudiniOutputDetails::GetOutputDebugName(UHoudiniOutput* InOutput) -{ - // Get the name and type - FString OutputNameStr = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); - - // Then add the number of parts - OutputNameStr += TEXT(" (") + FString::FromInt(InOutput->GetHoudiniGeoPartObjects().Num()) + TEXT(" Part(s))\n"); - - return FText::FromString(OutputNameStr); -} -FText -FHoudiniOutputDetails::GetOutputDebugDescription(UHoudiniOutput* InOutput) -{ - const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); - - FString OutputValStr; - OutputValStr += TEXT("HGPOs:\n"); - for (auto& HGPO : HGPOs) - { - OutputValStr += TEXT(" - ") + HGPO.PartName + TEXT(" (") + FHoudiniGeoPartObject::HoudiniPartTypeToString(HGPO.Type) + TEXT(")"); - - if (HGPO.SplitGroups.Num() > 0) - { - OutputValStr += TEXT("( ") + FString::FromInt(HGPO.SplitGroups.Num()) + TEXT(" splits:"); - for (auto& split : HGPO.SplitGroups) - { - OutputValStr += TEXT(" ") + split; - } - OutputValStr += TEXT(")"); - } - - if (!HGPO.VolumeName.IsEmpty()) - { - OutputValStr += TEXT("( ") + HGPO.VolumeName; - if (HGPO.VolumeTileIndex >= 0) - OutputValStr += TEXT(" tile ") + FString::FromInt(HGPO.VolumeTileIndex); - OutputValStr += TEXT(" )"); - } - - OutputValStr += TEXT("\n"); - } - - // Add output objects if any - TMap AllOutputObj = InOutput->GetOutputObjects(); - if (AllOutputObj.Num() > 0) - { - bool TitleAdded = false; - for (const auto& Iter : AllOutputObj) - { - UObject* OutObject = Iter.Value.OutputObject; - if (OutObject) - { - OutputValStr += OutObject->GetFullName() + TEXT(" (obj)\n"); - } - - UObject* OutComp = Iter.Value.OutputComponent; - if (OutComp) - { - OutputValStr += OutObject->GetFullName() + TEXT(" (comp)\n"); - } - } - } - - return FText::FromString(OutputValStr); -} - -FText -FHoudiniOutputDetails::GetOutputTooltip(UHoudiniOutput* InOutput) -{ - // TODO - return FText(); -} - - -const FSlateBrush * -FHoudiniOutputDetails::GetThumbnailBorder(UObject* Mesh) const -{ - TSharedPtr ThumbnailBorder = OutputObjectThumbnailBorders[Mesh]; - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - - -const FSlateBrush * -FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(UObject* OutputObject, int32 MaterialIdx) const -{ - if (!OutputObject) - return nullptr; - - TPairInitializer Pair(OutputObject, MaterialIdx); - TSharedPtr ThumbnailBorder = MaterialInterfaceThumbnailBorders[Pair]; - - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - -/* -const FSlateBrush * -FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx) const -{ - if (!Landscape) - return nullptr; - - TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); - TSharedPtr< SBorder > ThumbnailBorder = LandscapeMaterialInterfaceThumbnailBorders[Pair]; - - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} -*/ - -FReply -FHoudiniOutputDetails::OnThumbnailDoubleClick( - const FGeometry & InMyGeometry, - const FPointerEvent & InMouseEvent, UObject * Object) -{ - if (Object && GEditor) - GEditor->EditObject(Object); - - return FReply::Handled(); -} - -/* -FReply -FHoudiniOutputDetails::OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject) -{ - if (HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill()) - { - FHoudiniPackageParams PackageParms; - - - FHoudiniEngineBakeUtils::BakeStaticMesh(HoudiniAssetComponent, GeoPartObject, StaticMesh, PackageParms); - // TODO: Bake the SM - - - // We need to locate corresponding geo part object in component. - const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(StaticMesh); - - // (void)FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( - // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); - - } - - return FReply::Handled(); -} -*/ - -bool -FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver(const UObject * InObject) const -{ - return (InObject && InObject->IsA(UMaterialInterface::StaticClass())); -} - - -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - UStaticMesh * StaticMesh, - UHoudiniOutput * HoudiniOutput, - int32 MaterialIdx) -{ - FReply RetValue = FReply::Handled(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return RetValue; - - if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) - return RetValue; - - // Retrieve material interface which is being replaced. - UMaterialInterface * MaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; - if (!MaterialInterface) - return RetValue; - - // Find the string corresponding to the material that is being replaced - const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); - if (!FoundString ) - { - // This material was not replaced, no need to reset it - return RetValue; - } - - // This material has been replaced previously. - FString MaterialString = *FoundString; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), HoudiniOutput); - - // Remove the replacement - HoudiniOutput->Modify(); - HoudiniOutput->GetReplacementMaterials().Remove(MaterialString); - - bool bViewportNeedsUpdate = true; - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - UMaterialInterface * const * FoundMat = HoudiniOutput->GetAssignementMaterials().Find(MaterialString); - if (FoundMat && (*FoundMat)) - AssignMaterial = *FoundMat; - - // Replace material on static mesh. - StaticMesh->Modify(); - StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = AssignMaterial; - - // Replace the material on any component (SMC/ISMC) that uses the above SM - // TODO: ?? Replace for all? - for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) - { - // Only look at MeshComponents - UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); - if (!SMC) - continue; - - if (SMC->GetStaticMesh() != StaticMesh) - continue; - - SMC->Modify(); - SMC->SetMaterial(MaterialIdx, AssignMaterial); - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - return RetValue; -} - -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - ALandscapeProxy* InLandscape, - UHoudiniOutput * InHoudiniOutput, - int32 InMaterialIdx) -{ - FReply RetValue = FReply::Handled(); - if (!InLandscape || InLandscape->IsPendingKill()) - return RetValue; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * MaterialInterface = InMaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); - UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - - // Find the string corresponding to the material that is being replaced - const FString* FoundString = InHoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); - if (!FoundString) - { - // This material was not replaced, no need to reset it - return RetValue; - } - - // This material has been replaced previously. - FString MaterialString = *FoundString; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), InHoudiniOutput); - - // Remove the replacement - InHoudiniOutput->Modify(); - InHoudiniOutput->GetReplacementMaterials().Remove(MaterialString); - - bool bViewportNeedsUpdate = true; - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - UMaterialInterface * const * FoundMat = InHoudiniOutput->GetAssignementMaterials().Find(MaterialString); - if (FoundMat && (*FoundMat)) - AssignMaterial = *FoundMat; - - // Replace material on Landscape - InLandscape->Modify(); - if (InMaterialIdx == 0) - InLandscape->LandscapeMaterial = AssignMaterial; - else - InLandscape->LandscapeHoleMaterial = AssignMaterial; - - InLandscape->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); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - */ - - FHoudiniEngineUtils::UpdateEditorProperties(InHoudiniOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - return RetValue; -} -/* -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - ALandscapeProxy * Landscape, UHoudiniOutput * InOutput, int32 MaterialIdx) -{ - bool bViewportNeedsUpdate = false; - - // TODO: Handle me! - for (TArray< UHoudiniAssetComponent * >::TIterator - IterComponents(HoudiniAssetComponents); IterComponents; ++IterComponents) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; - if (!HoudiniAssetComponent) - continue; - - TWeakObjectPtr* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find(*HoudiniGeoPartObject); - if (!FoundLandscapePtr) - continue; - - ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get(); - if (!FoundLandscape || !FoundLandscape->IsValidLowLevel()) - continue; - - if (FoundLandscape != Landscape) - continue; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); - UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - - bool bMaterialRestored = false; - FString MaterialShopName; - if (!HoudiniAssetComponent->GetReplacementMaterialShopName(*HoudiniGeoPartObject, MaterialInterface, MaterialShopName)) - { - // This material was not replaced so there's no need to reset it - continue; - } - - // Remove the replacement - HoudiniAssetComponent->RemoveReplacementMaterial(*HoudiniGeoPartObject, MaterialShopName); - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial(MaterialShopName); - if (AssignedMaterial) - MaterialInterfaceReplacement = AssignedMaterial; - - // Replace material on the landscape - Landscape->Modify(); - - if (MaterialIdx == 0) - Landscape->LandscapeMaterial = MaterialInterfaceReplacement; - else - Landscape->LandscapeHoleMaterial = MaterialInterfaceReplacement; - - //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); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - - HoudiniAssetComponent->UpdateEditorProperties(false); - bViewportNeedsUpdate = true; - } - - if (GEditor && bViewportNeedsUpdate) - { - GEditor->RedrawAllViewports(); - } - - return FReply::Handled(); -} -*/ - -void -FHoudiniOutputDetails::OnBrowseTo(UObject* InObject) -{ - if (GEditor) - { - TArray Objects; - Objects.Add(InObject); - GEditor->SyncBrowserToObjects(Objects); - } -} - -TSharedRef -FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent( - UMaterialInterface* MaterialInterface, - UObject* OutputObject, - UHoudiniOutput* InOutput, - int32 MaterialIdx) -{ - TArray AllowedClasses; - AllowedClasses.Add(UMaterialInterface::StaticClass()); - - TArray NewAssetFactories; - - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(MaterialInterface), - true, - AllowedClasses, - NewAssetFactories, - OnShouldFilterMaterialInterface, - FOnAssetSelected::CreateSP( - this, &FHoudiniOutputDetails::OnMaterialInterfaceSelected, OutputObject, InOutput, MaterialIdx), - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::CloseMaterialInterfaceComboButton)); -} - - -void -FHoudiniOutputDetails::CloseMaterialInterfaceComboButton() -{ - -} - -void -FHoudiniOutputDetails::OnMaterialInterfaceDropped( - UObject * InObject, - UStaticMesh * StaticMesh, - UHoudiniOutput * HoudiniOutput, - int32 MaterialIdx) -{ - UMaterialInterface * MaterialInterface = Cast(InObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - return; - - if (!StaticMesh || StaticMesh->IsPendingKill()) - return; - - if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) - return; - - bool bViewportNeedsUpdate = false; - - // Retrieve material interface which is being replaced. - UMaterialInterface * OldMaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; - if (OldMaterialInterface == MaterialInterface) - return; - - // Find the string corresponding to the material that is being replaced - FString MaterialString = FString(); - const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been replaced previously. - MaterialString = *FoundString; - } - else - { - // We have no previous replacement for this material, - // see if we can find it the material assignment list. - FoundString = HoudiniOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been assigned previously. - MaterialString = *FoundString; - } - else - { - UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - if (OldMaterialInterface == DefaultMaterial) - { - // This is replacement for default material. - MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - } - else - { - // External Material? - MaterialString = OldMaterialInterface->GetName(); - } - } - } - - if (MaterialString.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), HoudiniOutput); - - // Add a new material replacement entry. - HoudiniOutput->Modify(); - HoudiniOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); - - // Replace material on static mesh. - StaticMesh->Modify(); - StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = MaterialInterface; - - // Replace the material on any component (SMC/ISMC) that uses the above SM - for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) - { - // Only look at MeshComponents - UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); - if (SMC && !SMC->IsPendingKill()) - { - if (SMC->GetStaticMesh() == StaticMesh) - { - SMC->Modify(); - SMC->SetMaterial(MaterialIdx, MaterialInterface); - } - } - else - { - UStaticMesh* SM = Cast(OutputObject.Value.OutputObject); - if (SM && !SM->IsPendingKill()) - { - SM->Modify(); - SM->SetMaterial(MaterialIdx, MaterialInterface); - } - } - - - - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); - - /* - if(GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); -*/ - if (GEditor) - GEditor->RedrawAllViewports(); -} - -// Delegate used when a valid material has been drag and dropped on a landscape. -void -FHoudiniOutputDetails::OnMaterialInterfaceDropped( - UObject* InDroppedObject, - ALandscapeProxy* InLandscape, - UHoudiniOutput* InOutput, - int32 MaterialIdx) -{ - UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >(InDroppedObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - return; - - if (!InLandscape || InLandscape->IsPendingKill()) - return; - - bool bViewportNeedsUpdate = false; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * OldMaterialInterface = MaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); - if (OldMaterialInterface == MaterialInterface) - return; - - // Find the string corresponding to the material that is being replaced - FString MaterialString = FString(); - const FString* FoundString = InOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been replaced previously. - MaterialString = *FoundString; - } - else - { - // We have no previous replacement for this material, - // see if we can find it the material assignment list. - FoundString = InOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been assigned previously. - MaterialString = *FoundString; - } - else - { - UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - if (OldMaterialInterface == DefaultMaterial) - { - // This is replacement for default material. - MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - } - else - { - // External Material? - if (OldMaterialInterface && !OldMaterialInterface->IsPendingKill()) - MaterialString = OldMaterialInterface->GetName(); - } - } - } - - if (MaterialString.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), InOutput); - - // Add a new material replacement entry. - InOutput->Modify(); - InOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); - - // Replace material on the landscape - InLandscape->Modify(); - - if (MaterialIdx == 0) - InLandscape->LandscapeMaterial = MaterialInterface; - else - InLandscape->LandscapeHoleMaterial = MaterialInterface; - - // Update the landscape components Material instances - InLandscape->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 >(InLandscape->GetClass(), (MaterialIdx == 0) ? TEXT("LandscapeMaterial") : TEXT("LandscapeHoleMaterial")); - if (FoundProperty) - { - FPropertyChangedEvent PropChanged(FoundProperty, EPropertyChangeType::ValueSet); - InLandscape->PostEditChangeProperty(PropChanged); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - */ - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); -} - -void -FHoudiniOutputDetails::OnMaterialInterfaceSelected( - const FAssetData & AssetData, - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - TPairInitializer Pair(OutputObject, MaterialIdx); - TSharedPtr AssetComboButton = MaterialInterfaceComboButtons[Pair]; - if (AssetComboButton.IsValid()) - { - AssetComboButton->SetIsOpen(false); - - UObject * Object = AssetData.GetAsset(); - - UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); - } - - ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } - } -} - -void -FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - if (!OutputObject || OutputObject->IsPendingKill()) - return; - - if (!InOutput || InOutput->IsPendingKill()) - return; - - if (GEditor) - { - TArray CBSelections; - GEditor->GetContentBrowserSelections(CBSelections); - - // Get the first selected material object - UObject* Object = nullptr; - for (auto & CurAssetData : CBSelections) - { - if (CurAssetData.AssetClass != UMaterial::StaticClass()->GetFName() && - CurAssetData.AssetClass != UMaterialInstance::StaticClass()->GetFName() && - CurAssetData.AssetClass != UMaterialInstanceConstant::StaticClass()->GetFName()) - continue; - - Object = CurAssetData.GetAsset(); - break; - } - - if (Object && !Object->IsPendingKill()) - { - UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); - } - - ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } - } - } -} - -void -FHoudiniOutputDetails::CreateInstancerOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // Do not display instancer UI for one-instance instancers - bool OnlyOneInstanceInstancers = true; - for (auto& Iter : InOutput->GetInstancedOutputs()) - { - FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); - if (CurInstanceOutput.OriginalTransforms.Num() <= 1) - continue; - - OnlyOneInstanceInstancers = false; - break; - } - - // This output only has one-instance instancers (SMC), no need to display the instancer UI. - if (OnlyOneInstanceInstancers) - return; - - // Classes allowed for instance variations. - const TArray AllowedClasses = - { - UStaticMesh::StaticClass(), USkeletalMesh::StaticClass(), - AActor::StaticClass(), UBlueprint::StaticClass(), - UFXSystemAsset::StaticClass(), USoundBase::StaticClass() - }; - - // Classes not allowed for instances variations (useless?) - TArray DisallowedClasses = - { - UClass::StaticClass(), ULevel::StaticClass(), - UMaterial::StaticClass(), UTexture::StaticClass() - }; - - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // Lambda for adding new variation objects - auto AddObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) - { - // TODO: undo/redo? - InOutputToUpdate.VariationObjects.Insert(InObject, AtIndex); - InOutputToUpdate.VariationTransformOffsets.Insert(FTransform::Identity, AtIndex); - FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for adding new geometry input objects - auto RemoveObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex) - { - // Also keep one instance object - if (AtIndex < 0 || AtIndex >= InOutputToUpdate.VariationObjects.Num()) - return; - - if (InOutputToUpdate.VariationObjects.Num() == 1) - return; - - // TODO: undo/redo? - InOutputToUpdate.VariationObjects.RemoveAt(AtIndex); - InOutputToUpdate.VariationTransformOffsets.RemoveAt( AtIndex); - FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for updating a variation - auto SetObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) - { - if (!InOutputToUpdate.VariationObjects.IsValidIndex(AtIndex)) - return; - - InOutputToUpdate.VariationObjects[AtIndex] = InObject; - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for changing the transform offset values - auto ChangeTransformOffsetAt = [InOutput]( - FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, - const float& Value, const int32& PosRotScaleIndex, const int32& XYZIndex) - { - bool bChanged = InOutputToUpdate.SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); - if (!bChanged) - return; - - InOutputToUpdate.MarkChanged(true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Get this output's OutputObject - const TMap& OutputObjects = InOutput->GetOutputObjects(); - - // Iterate on all of the output's HGPO - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - // Not an instancer, skip - if (CurHGPO.Type != EHoudiniPartType::Instancer) - continue; - - // Get the label for that instancer - FString InstancerLabel = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); - if (CurHGPO.bHasCustomPartName) - InstancerLabel = CurHGPO.PartName; - - TSharedRef InstancerVerticalBox = SNew(SVerticalBox); - TSharedPtr InstancerHorizontalBox = nullptr; - - // Create a new Group for that instancer - IDetailGroup& InstancerGroup = HouOutputCategory.AddGroup(FName(*InstancerLabel), FText::FromString(InstancerLabel)); - - // Now iterate and display the instance outputs that matches this HGPO - for (auto& Iter : InOutput->GetInstancedOutputs()) - { - FHoudiniOutputObjectIdentifier& CurOutputObjectIdentifier = Iter.Key; - if (!CurOutputObjectIdentifier.Matches(CurHGPO)) - continue; - - FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); - - // Dont display instancer UI for one-instance instancers (SMC) - if (CurInstanceOutput.OriginalTransforms.Num() <= 1) - continue; - - for( int32 VariationIdx = 0; VariationIdx < CurInstanceOutput.VariationObjects.Num(); VariationIdx++ ) - { - UObject * InstancedObject = CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous(); - if ( !InstancedObject || InstancedObject->IsPendingKill() ) - { - HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); - continue; - } - - // Create thumbnail for this object. - TSharedPtr VariationThumbnail = - MakeShareable(new FAssetThumbnail(InstancedObject, 64, 64, AssetThumbnailPool)); - TSharedRef PickerVerticalBox = SNew(SVerticalBox); - TSharedPtr PickerHorizontalBox = nullptr; - TSharedPtr VariationThumbnailBorder; - - // For the variation name, reuse the instancer label and append the variation index if we have more than one variation - FString InstanceOutputLabel = InstancerLabel; - if(CurInstanceOutput.VariationObjects.Num() > 1) - InstanceOutputLabel += TEXT(" [") + FString::FromInt(VariationIdx) + TEXT("]"); - - IDetailGroup* DetailGroup = &InstancerGroup; - if (CurInstanceOutput.VariationObjects.Num() > 1) - { - // If we have more than one variation, add a new group for each variation - DetailGroup = &InstancerGroup.AddGroup(FName(*InstanceOutputLabel), FText::FromString(InstanceOutputLabel), true); - } - - // See if we can find the corresponding component to get its type - FString InstancerType = TEXT("(Instancer)"); - FHoudiniOutputObjectIdentifier CurVariationIdentifier = CurOutputObjectIdentifier; - CurVariationIdentifier.SplitIdentifier += TEXT("_") + FString::FromInt(VariationIdx); - const FHoudiniOutputObject* VariationOutputObject = OutputObjects.Find(CurVariationIdentifier); - if(VariationOutputObject) - InstancerType = FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(VariationOutputObject->OutputComponent); - - DetailGroup->AddWidgetRow() - .NameContent() - [ - //SNew(SSpacer) - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(InstancerType)) - //.Size(FVector2D(250, 64)) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - PickerVerticalBox - ]; - - // Add an asset drop target - PickerVerticalBox->AddSlot() - .Padding( 0, 2 ) - .AutoHeight() - [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop( SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( - [=]( const UObject* Obj ) { - for ( auto Klass : DisallowedClasses ) - { - if ( Obj && Obj->IsA( Klass ) ) - return false; - } - return true; - }) - ) - .OnAssetDropped_Lambda([&CurInstanceOutput, VariationIdx, SetObjectAt](UObject* InObject) - { - return SetObjectAt(CurInstanceOutput, VariationIdx, InObject); - }) - [ - SAssignNew( PickerHorizontalBox, SHorizontalBox ) - ] - ]; - - PickerHorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(VariationThumbnailBorder, SBorder) - .Padding( 5.0f ) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, InstancedObject) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(InstancedObject->GetPathName())) - [ - VariationThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - VariationThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush *>::Create( - TAttribute::FGetter::CreateLambda([=]() - { - if (VariationThumbnailBorder.IsValid() && VariationThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ) ) ); - - PickerHorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 28.0f, 0.0f, 28.0f) - [ - PropertyCustomizationHelpers::MakeAddButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, AddObjectAt]() - { - UObject* ObjToAdd = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? - CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() - : nullptr; - - return AddObjectAt(CurInstanceOutput, VariationIdx, ObjToAdd); - }), - LOCTEXT("AddAnotherInstanceToolTip", "Add Another Instance")) - ]; - - PickerHorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 28.0f, 4.0f, 28.0f ) - [ - PropertyCustomizationHelpers::MakeRemoveButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, RemoveObjectAt]() - { - return RemoveObjectAt(CurInstanceOutput, VariationIdx); - }), - LOCTEXT("RemoveLastInstanceToolTip", "Remove Last Instance")) - ]; - - TSharedPtr AssetComboButton; - TSharedPtr ButtonBox; - PickerHorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .HAlign(HAlign_Fill) - [ - SAssignNew(ButtonBox, SHorizontalBox) - +SHorizontalBox::Slot() - [ - SAssignNew(AssetComboButton, SComboButton) - //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) - /* TODO: Update UI - .OnMenuOpenChanged( FOnIsOpenChanged::CreateUObject( - &InParam, &UHoudiniAssetInstanceInput::ChangedStaticMeshComboButton, - CurInstanceOutput, InstOutIdx, VariationIdx ) ) - */ - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(InstancedObject->GetName())) - ] - ] - ] - ]; - - // Create asset picker for this combo button. - { - TArray NewAssetFactories; - TSharedRef PropertyMenuAssetPicker = PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(InstancedObject), - true, - AllowedClasses, - DisallowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([&CurInstanceOutput, VariationIdx, SetObjectAt, AssetComboButton](const FAssetData& AssetData) - { - if ( AssetComboButton.IsValid() ) - { - AssetComboButton->SetIsOpen( false ); - UObject * Object = AssetData.GetAsset(); - SetObjectAt( CurInstanceOutput, VariationIdx, Object); - } - }), - // Nothing to do on close - FSimpleDelegate::CreateLambda([](){}) - ); - - AssetComboButton->SetMenuContent(PropertyMenuAssetPicker); - } - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(InstancedObject->GetName())); - FText StaticMeshTooltip = - FText::Format(LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx]() - { - UObject* InputObject = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? - CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() - : nullptr; - - if (GEditor && InputObject) - { - TArray Objects; - Objects.Add(InputObject); - GEditor->SyncBrowserToObjects(Objects); - } - }), - TAttribute< FText >( StaticMeshTooltip ) ) - ]; - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f ) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT( "ResetToBase", "Reset to default static mesh")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([SetObjectAt, &CurInstanceOutput, VariationIdx]() - { - SetObjectAt(CurInstanceOutput, VariationIdx, CurInstanceOutput.OriginalObject.LoadSynchronous()); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - - - // Get Visibility of reset buttons - bool bResetButtonVisiblePosition = false; - bool bResetButtonVisibleRotation = false; - bool bResetButtonVisibleScale = false; - - FTransform CurTransform = CurInstanceOutput.VariationTransformOffsets[VariationIdx]; - - if (CurTransform.GetLocation() != FVector::ZeroVector) - bResetButtonVisiblePosition = true; - - FRotator Rotator = CurTransform.Rotator(); - if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) - bResetButtonVisibleRotation = true; - - if (CurTransform.GetScale3D() != FVector::OneVector) - bResetButtonVisibleScale = true; - - auto ChangeTransformOffsetUniformlyAt = [ChangeTransformOffsetAt, VariationIdx, &CurInstanceOutput](const float& Val, const int32& PosRotScaleIndex) - { - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 0); - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 1); - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 2); - }; - - TSharedRef OffsetVerticalBox = SNew(SVerticalBox); - FText LabelPositionText = LOCTEXT("HoudiniPositionOffset", "Position Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelPositionText) - .ToolTipText(LabelPositionText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .AllowSpin(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 0); } - ))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 1); } - ))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 2); } - ))) - .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 0); }) - .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 1); }) - .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 2); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("InstancerOutputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, CurInstanceOutput, InOutput]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 0); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - ]; - - FText LabelRotationText = LOCTEXT("HoudiniRotationOffset", "Rotation Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelRotationText) - .ToolTipText(LabelRotationText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SRotatorInputBox) - .AllowSpin(true) - .bColorAxisLabels(true) - .Roll(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 0); } - ))) - .Pitch(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 1); } - ))) - .Yaw(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 2); } - ))) - .OnRollCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 0); }) - .OnPitchCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 1); }) - .OnYawCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 2); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 1); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - ]; - - FText LabelScaleText = LOCTEXT("HoudiniScaleOffset", "Scale Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelScaleText) - .ToolTipText(LabelScaleText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 0); } - ))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 1); } - ))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 2); } - ))) - .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 0); - }) - .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 1); - }) - .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 2); - }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("InstancerOutputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the output object maintains its shape in each direction when scaled")) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(CurInstanceOutput.IsUnformScaleLocked() ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([&CurInstanceOutput, InOutput]() - { - CurInstanceOutput.SwitchUniformScaleLock(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() - { - ChangeTransformOffsetUniformlyAt(1.0f, 2); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - /* - // TODO: Add support for this back - + SHorizontalBox::Slot().AutoWidth() - [ - // Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "TransparentCheckBox") - .ToolTipText(LOCTEXT("PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled")) - *//* - .OnCheckStateChanged(FOnCheckStateChanged::CreateLambda([=](ECheckBoxState NewState) - { - if ( MyParam.IsValid() && InputFieldPtr.IsValid() ) - MyParam->CheckStateChanged( NewState == ECheckBoxState::Checked, InputFieldPtr.Get(), VariationIdx ); - })) - .IsChecked( TAttribute< ECheckBoxState >::Create( - TAttribute::FGetter::CreateLambda( [=]() - { - if (InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly(VariationIdx)) - return ECheckBoxState::Checked; - return ECheckBoxState::Unchecked; - } - ))) - *//* - [ - SNew(SImage) - *//*.Image(TAttribute::Create( - TAttribute::FGetter::CreateLambda( [=]() - { - if ( InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly( VariationIdx ) ) - { - return FEditorStyle::GetBrush( TEXT( "GenericLock" ) ); - } - return FEditorStyle::GetBrush( TEXT( "GenericUnlock" ) ); - } - ))) - *//* - .ColorAndOpacity( FSlateColor::UseForeground() ) - ] - ] - */ - ]; - } - } - } -} - -/* -void -FHoudiniOutputDetails::OnMaterialInterfaceSelected( - const FAssetData & AssetData, - ALandscapeProxy* Landscape, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); - TSharedPtr< SComboButton > AssetComboButton = LandscapeMaterialInterfaceComboButtons[Pair]; - if (AssetComboButton.IsValid()) - { - AssetComboButton->SetIsOpen(false); - - UObject * Object = AssetData.GetAsset(); - OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } -} -*/ - -void -FHoudiniOutputDetails::CreateDefaultOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput) - return; - - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); - - // TODO - // This is just a temporary placeholder displaying name/output type - { - FString OutputNameStr = InOutput->GetName(); - FText OutputTooltip = GetOutputTooltip(InOutput); - - // Create a new detail row - // Name - FText OutputNameTxt = GetOutputDebugName(InOutput); - FDetailWidgetRow & Row = HouOutputCategory.AddCustomRow(FText::GetEmpty()); - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(OutputNameTxt) - .ToolTipText(OutputTooltip) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - - // Value - FText OutputTypeTxt = GetOutputDebugDescription(InOutput); - Row.ValueWidget.Widget = - SNew(STextBlock) - .Text(OutputTypeTxt) - .ToolTipText(OutputTooltip) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - } -} - -void -FHoudiniOutputDetails::OnBakeOutputObject( - const FString& InBakeName, - UObject * BakedOutputObject, - const FHoudiniOutputObjectIdentifier & OutputIdentifier, - const FHoudiniGeoPartObject & HGPO, - const FString & HoudiniAssetName, - const FString & BakeFolder, - const FString & TempCookFolder, - const EHoudiniOutputType & Type, - const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, - const TArray& InAllOutputs) -{ - if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) - return; - - FString ObjectName = InBakeName; - - // Set Object name according to priority Default Name > Attrib Custom Name > UI Custom Name - if(InBakeName.IsEmpty()) - { - if (HGPO.bHasCustomPartName) - ObjectName = HGPO.PartName; - else - ObjectName = BakedOutputObject->GetName(); - } - - // Fill in the package params - FHoudiniPackageParams PackageParams; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - OutputIdentifier, - BakeFolder, - ObjectName, - HoudiniAssetName); - - switch (Type) - { - case EHoudiniOutputType::Mesh: - { - UStaticMesh* StaticMesh = Cast(BakedOutputObject); - if (StaticMesh) - { - FDirectoryPath TempCookFolderPath; - TempCookFolderPath.Path = TempCookFolder; - UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( - StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath); - } - } - break; - case EHoudiniOutputType::Curve: - { - USplineComponent* SplineComponent = Cast(BakedOutputObject); - if (SplineComponent) - { - AActor* BakedActor; - USplineComponent* BakedSplineComponent; - FHoudiniEngineBakeUtils::BakeCurve(SplineComponent, GWorld->GetCurrentLevel(), PackageParams, BakedActor, BakedSplineComponent); - } - } - break; - case EHoudiniOutputType::Landscape: - { - ALandscapeProxy* Landscape = Cast(BakedOutputObject); - if (Landscape) - { - FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType); - } - } - break; - } -} - -FReply -FHoudiniOutputDetails::OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput) -{ - // TODO: Actually refine only the selected ProxyMesh - // For now, refine all the selection - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true, true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); -} - -void -FHoudiniOutputDetails::OnBakeNameCommitted( - const FText& Val, ETextCommit::Type TextCommitType, - UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - if (!InOutput) - return; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - - if (!FoundOutputObject) - return; - - FoundOutputObject->BakeName = Val.ToString(); -} - -void -FHoudiniOutputDetails::OnRevertBakeNameToDefault(UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier & InIdentifier) -{ - if (!InOutput) - return; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - - if (!FoundOutputObject) - return; - - FoundOutputObject->BakeName = FString(); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniOutputDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAsset.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniEngineCommands.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailGroup.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SRotatorInputBox.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Text/STextBlock.h" +#include "Editor/UnrealEd/Public/AssetThumbnail.h" +#include "SAssetDropTarget.h" +#include "Engine/StaticMesh.h" +#include "Components/SplineComponent.h" +#include "Materials/Material.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceConstant.h" +#include "Sound/SoundBase.h" +#include "Engine/SkeletalMesh.h" +#include "Particles/ParticleSystem.h" +//#include "Landscape.h" +#include "LandscapeProxy.h" +#include "ScopedTransaction.h" +#include "PhysicsEngine/BodySetup.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +void +FHoudiniOutputDetails::CreateWidget( + IDetailCategoryBuilder& HouOutputCategory, + TArray InOutputs) +{ + if (InOutputs.Num() <= 0) + return; + + UHoudiniOutput* MainOutput = InOutputs[0]; + + // Don't create UI for editable curve. + if (!MainOutput || MainOutput->IsPendingKill() || MainOutput->IsEditableNode()) + return; + + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); + + // TODO + // For now we just handle Mesh Outputs + + switch (MainOutput->GetType()) + { + case EHoudiniOutputType::Mesh: + { + FHoudiniOutputDetails::CreateMeshOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Landscape: + { + FHoudiniOutputDetails::CreateLandscapeOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Instancer: + { + FHoudiniOutputDetails::CreateInstancerOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Curve: + { + FHoudiniOutputDetails::CreateCurveOutputWidget(HouOutputCategory, MainOutput); + break; + } + case EHoudiniOutputType::Skeletal: + default: + { + FHoudiniOutputDetails::CreateDefaultOutputWidget(HouOutputCategory, MainOutput); + break; + } + + } + +} + + +void +FHoudiniOutputDetails::CreateLandscapeOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // Go through this output's objects + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& CurrentOutputObj : OutputObjects) + { + UHoudiniLandscapePtr* LandscapePointer = Cast(CurrentOutputObj.Value.OutputObject); + if (!LandscapePointer) + continue; + + FHoudiniOutputObjectIdentifier& Identifier = CurrentOutputObj.Key; + const FHoudiniGeoPartObject *HGPO = nullptr; + for (const auto& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!Identifier.Matches(CurHGPO)) + continue; + + HGPO = &CurHGPO; + break; + } + + if (!HGPO) + continue; + + CreateLandscapeOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapePointer, Identifier); + } +} + +void +FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + const FHoudiniGeoPartObject& HGPO, + UHoudiniLandscapePtr* LandscapePointer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier) +{ + if (!LandscapePointer || LandscapePointer->IsPendingKill() || !LandscapePointer->LandscapeSoftPtr.IsValid()) + return; + + if (!InOutput || InOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return; + + ALandscapeProxy * Landscape = LandscapePointer->LandscapeSoftPtr.Get(); + if (!Landscape || Landscape->IsPendingKill()) + return; + + // TODO: Get bake base name + FString Label = Landscape->GetName(); + + EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType = LandscapePointer->BakeType; + + // Get thumbnail pool for this builder + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + TArray>* BakeOptionString = FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels(); + + // Create bake mesh name textfield. + IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(Label)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + // Create the thumbnail for the landscape output object. + TSharedPtr< FAssetThumbnail > LandscapeThumbnail = + MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); + + TSharedPtr< SBorder > LandscapeThumbnailBorder; + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(SSpacer) + .Size(FVector2D(250, 64)) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot().Padding(0, 2).AutoHeight() + [ + SNew(SBox).WidthOverride(175) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(LandscapeThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(Landscape->GetPathName())) + [ + LandscapeThumbnail->MakeThumbnailWidget() + ] + ] + ] + + + SHorizontalBox::Slot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SBox).WidthOverride(40.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("Bake", "Bake")) + .IsEnabled(true) + .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() + { + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + if (FoundOutputObject) + { + TArray AllOutputs; + AllOutputs.Reserve(HAC->GetNumOutputs()); + HAC->GetOutputs(AllOutputs); + FHoudiniOutputDetails::OnBakeOutputObject( + FoundOutputObject->BakeName, + Landscape, + OutputIdentifier, + HGPO, + OwnerActor->GetName(), + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + InOutput->GetType(), + LandscapeOutputBakeType, + AllOutputs); + } + + // TODO: Remove the output landscape if the landscape bake type is Detachment? + return FReply::Handled(); + }) + .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) + ] + ] + + SHorizontalBox::Slot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SBox).WidthOverride(120.f) + [ + SNew(SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + if (SelectType != ESelectInfo::Type::OnMouseClick) + return; + + FString *NewChoiceStr = NewChoice.Get(); + if (!NewChoiceStr) + return; + + if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); + } + else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); + } + else + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); + } + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + [ + SNew(STextBlock) + .Text_Lambda([LandscapePointer]() + { + FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); + return FText::FromString(BakeTypeString); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ] + ] + ]; + + // Store thumbnail for this landscape. + OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); + + // We need to add material box for each the landscape and landscape hole materials + for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) + { + UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + TSharedPtr MaterialThumbnailBorder; + TSharedPtr HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if (MaterialInterface) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + + // Create thumbnail for this material. + TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(STextBlock) + .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().Padding(0, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) + .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(MaterialThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(MaterialPathName)) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this landscape and material index. + { + TPairInitializer Pair(Landscape, MaterialIdx); + MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + } + + // Combox Box and Button Box + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Combo row + TSharedPtr< SComboButton > AssetComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) + [ + SAssignNew(AssetComboButton, SComboButton) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + // Buttons row + TSharedPtr ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Add use Content Browser selection arrow + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)Landscape, InOutput, MaterialIdx), + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + ]; + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), + TAttribute< FText >(MaterialTooltip)) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + + // Store combo button for this mesh and index. + { + TPairInitializer Pair(Landscape, MaterialIdx); + MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + } + } + +} + +void +FHoudiniOutputDetails::CreateMeshOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent* HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + FString HoudiniAssetName; + if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) + { + HoudiniAssetName = HAC->GetOwner()->GetName(); + } + else if (HAC->GetHoudiniAsset()) + { + HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); + } + else + { + HoudiniAssetName = HAC->GetName(); + } + + // Go through this output's object + int32 OutputObjIdx = 0; + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& IterObject : OutputObjects) + { + UStaticMesh* StaticMesh = Cast(IterObject.Value.OutputObject); + UHoudiniStaticMesh* ProxyMesh = Cast(IterObject.Value.ProxyObject); + + if ((!StaticMesh || StaticMesh->IsPendingKill()) + && (!ProxyMesh || ProxyMesh->IsPendingKill())) + continue; + + FHoudiniOutputObjectIdentifier & OutputIdentifier = IterObject.Key; + + // Find the corresponding HGPO in the output + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!OutputIdentifier.Matches(curHGPO)) + continue; + + HoudiniGeoPartObject = curHGPO; + break; + } + + if (StaticMesh && !StaticMesh->IsPendingKill()) + { + bool bIsProxyMeshCurrent = IterObject.Value.bProxyIsCurrent; + + // If we have a static mesh, alway display its widget even if the proxy is more recent + CreateStaticMeshAndMaterialWidgets( + HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); + } + else + { + // If we only have a proxy mesh, then show the proxy widget + CreateProxyMeshAndMaterialWidgets( + HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject); + } + } +} + +void +FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + int32 OutputObjIdx = 0; + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& IterObject : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = IterObject.Value; + USceneComponent* SplineComponent = Cast(IterObject.Value.OutputComponent); + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + FHoudiniOutputObjectIdentifier& OutputIdentifier = IterObject.Key; + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!OutputIdentifier.Matches(curHGPO)) + continue; + + HoudiniGeoPartObject = curHGPO; + break; + } + + CreateCurveWidgets(HouOutputCategory, InOutput, SplineComponent, CurrentOutputObject, OutputIdentifier, HoudiniGeoPartObject); + } +} + +void +FHoudiniOutputDetails::CreateCurveWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + USceneComponent* SplineComponent, + FHoudiniOutputObject& OutputObject, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + FHoudiniGeoPartObject& HoudiniGeoPartObject) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // We support Unreal Spline out only for now + USplineComponent* SplineOutput = Cast(SplineComponent); + if (!SplineOutput || SplineOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return; + + FHoudiniCurveOutputProperties* OutputProperty = &(OutputObject.CurveOutputProperty); + EHoudiniCurveType OutputCurveType = OutputObject.CurveOutputProperty.CurveType; + + FString Label = SplineComponent->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + //Label += FString("_") + OutputIdentifier.SplitIdentifier; + + FString OutputCurveName = OutputObject.BakeName; + if(OutputCurveName.IsEmpty()) + OutputCurveName = OwnerActor->GetName() + "_" + Label; + + const FText& LabelText = FText::FromString("Unreal Spline"); + + IDetailGroup& CurveOutputGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label), false, false); + + // Bake name row UI + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(OutputObject.BakeName)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + ] + + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("OutputCurveSplineType", "Spline Type")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText_Lambda([SplineOutput, Label, OutputCurveType]() + { + FString ToolTipStr = FString::Printf(TEXT(" curve: %s\n Export type: Unreal Spline\n num points: %d\n curve type: %s\n closed: %s"), + *Label, + SplineOutput->GetNumberOfSplinePoints(), + *FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(OutputCurveType), + SplineOutput->IsClosedLoop() ? *(FString("yes")) : *(FString("no"))); + + return FText::FromString(ToolTipStr); + }) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(STextBlock) + // We support Unreal Spline output only for now... + .Text(LOCTEXT("OutputCurveSplineTypeUnreal", "Unreal Spline")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + + //if (bIsUnrealSpline) + //{ + USplineComponent* UnrealSpline = Cast(SplineComponent); + + // Curve type combo box UI + auto InitialSelectionLambda = [OutputProperty]() + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + { + return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[0]; + } + else + { + return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[1]; + } + }; + + TSharedPtr>> UnrealCurveTypeComboBox; + + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(LOCTEXT("OutputCurveUnrealSplinePointType", "Spline Point Type")) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(UnrealCurveTypeComboBox, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels()) + .InitiallySelectedItem(InitialSelectionLambda()) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [OutputProperty, InOutput, SplineComponent](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + // Set the curve point type locally + USplineComponent* Spline = Cast(SplineComponent); + if (!Spline || Spline->IsPendingKill()) + return; + + FString *NewChoiceStr = NewChoice.Get(); + if (!NewChoiceStr) + return; + + if (*NewChoiceStr == "Linear") + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + return; + + OutputProperty->CurveType = EHoudiniCurveType::Polygon; + + for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) + { + Spline->SetSplinePointType(PtIdx, ESplinePointType::Linear); + } + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + } + else if (*NewChoiceStr == "Curve") + { + if (OutputProperty->CurveType != EHoudiniCurveType::Polygon) + return; + + OutputProperty->CurveType = EHoudiniCurveType::Bezier; + + for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) + { + Spline->SetSplinePointType(PtIdx, ESplinePointType::Curve); + } + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([OutputProperty]() + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + return FText::FromString(TEXT("Linear")); + else + return FText::FromString(TEXT("Curve")); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + // Add closed curve checkbox UI + TSharedPtr ClosedCheckBox; + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(LOCTEXT("OutputCurveUnrealSplineClosed", "Closed")) + ] + .ValueContent() + [ + SAssignNew(ClosedCheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([UnrealSpline, InOutput](ECheckBoxState NewState) + { + if (!UnrealSpline || UnrealSpline->IsPendingKill()) + return; + + UnrealSpline->SetClosedLoop(NewState == ECheckBoxState::Checked); + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + .IsChecked_Lambda([UnrealSpline]() + { + if (!UnrealSpline || UnrealSpline->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return UnrealSpline->IsClosedLoop() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + ]; + //} + + // Add Bake Button UI + TSharedPtr BakeButton; + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + ] + .ValueContent() + [ + SAssignNew(BakeButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) + .IsEnabled(true) + .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) + .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName]() + { + TArray AllOutputs; + AllOutputs.Reserve(HAC->GetNumOutputs()); + HAC->GetOutputs(AllOutputs); + FHoudiniOutputDetails::OnBakeOutputObject( + OutputCurveName, + SplineComponent, + OutputIdentifier, + HoudiniGeoPartObject, + OwnerActor->GetName(), + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + InOutput->GetType(), + EHoudiniLandscapeOutputBakeType::InValid, + AllOutputs); + + return FReply::Handled(); + }) + ]; +} + +void +FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UStaticMesh * StaticMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + const bool& bIsProxyMeshCurrent) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return; + + UHoudiniAssetComponent* OwningHAC = Cast(InOutput->GetOuter()); + + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // TODO: GetBakingBaseName! + FString Label = StaticMesh->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + // Create thumbnail for this mesh. + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = + MakeShareable(new FAssetThumbnail(StaticMesh, 64, 64, AssetThumbnailPool)); + TSharedPtr StaticMeshThumbnailBorder; + + TSharedRef VerticalBox = SNew(SVerticalBox); + + IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(BakeName)) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + }) + .ToolTipText( LOCTEXT( "BakeNameTip", "The base name of the baked asset") ) + ] + + +SHorizontalBox::Slot() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "RevertNameOverride", "Revert bake name override" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ] + ]; + + // Add details on the SM colliders + EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); + FString MeshLabel = TEXT( "Static Mesh" ); + + // If the Proxy mesh is more recent, indicate it in the details + if (bIsProxyMeshCurrent) + { + MeshLabel += TEXT("\n(unrefined)"); + } + + // Indicate that this mesh is instanced + if (HoudiniGeoPartObject.bIsInstanced) + { + MeshLabel += TEXT("\n(instanced)"); + } + + if (HoudiniGeoPartObject.bIsTemplated) + { + MeshLabel += TEXT("\n(templated)"); + } + + int32 NumSimpleColliders = 0; + if (StaticMesh->BodySetup && !StaticMesh->BodySetup->IsPendingKill()) + NumSimpleColliders = StaticMesh->BodySetup->AggGeom.GetElementCount(); + + if(NumSimpleColliders > 0) + { + MeshLabel += TEXT( "\n(") + FString::FromInt(NumSimpleColliders) + TEXT(" Simple Collider" ); + if (NumSimpleColliders > 1 ) + MeshLabel += TEXT("s"); + MeshLabel += TEXT(")"); + } + else if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + MeshLabel += TEXT( "\n(Rendered Complex Collider)" ); + } + else if(SplitType == EHoudiniSplitType::InvisibleComplexCollider ) + { + MeshLabel += TEXT( "\n(Invisible Complex Collider)" ); + } + + if ( StaticMesh->GetNumLODs() > 1 ) + MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)"); + + if ( StaticMesh->Sockets.Num() > 0 ) + MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->Sockets.Num() ) + TEXT(" sockets)"); + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew( STextBlock ) + .Text( FText::FromString(MeshLabel) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot() + .Padding( 0, 2 ) + .AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) + .AutoWidth() + [ + SAssignNew( StaticMeshThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)StaticMesh ) + .OnMouseDoubleClick( this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *) StaticMesh ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( StaticMesh->GetPathName() ) ) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ] + + +SHorizontalBox::Slot() + .FillWidth( 1.0f ) + .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SVerticalBox ) + +SVerticalBox::Slot() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .MaxWidth( 80.0f ) + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .Text( LOCTEXT( "Bake", "Bake" ) ) + .IsEnabled(true) + .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC]() + { + TArray AllOutputs; + FString TempCookFolder; + if (IsValid(OwningHAC)) + { + AllOutputs.Reserve(OwningHAC->GetNumOutputs()); + OwningHAC->GetOutputs(AllOutputs); + + TempCookFolder = OwningHAC->TemporaryCookFolder.Path; + } + FHoudiniOutputDetails::OnBakeOutputObject( + BakeName, + StaticMesh, + OutputIdentifier, + HoudiniGeoPartObject, + HoudiniAssetName, + BakeFolder, + TempCookFolder, + InOutput->GetType(), + EHoudiniLandscapeOutputBakeType::InValid, + AllOutputs); + + return FReply::Handled(); + }) + .ToolTipText( LOCTEXT( "HoudiniStaticMeshBakeButton", "Bake this generated static mesh" ) ) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)StaticMesh), + TAttribute(LOCTEXT("HoudiniStaticMeshBrowseButton", "Browse to this generated static mesh in the content browser"))) + ] + ] + ] + ]; + + // Store thumbnail for this mesh. + OutputObjectThumbnailBorders.Add((UObject*)StaticMesh, StaticMeshThumbnailBorder); + + // We need to add material box for each material present in this static mesh. + auto & StaticMeshMaterials = StaticMesh->StaticMaterials; + for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) + { + UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface; + TSharedPtr< SBorder > MaterialThumbnailBorder; + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if ( MaterialInterface && !MaterialInterface->IsPendingKill() + && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + else + { + MaterialInterface = nullptr; + MaterialName = TEXT("Material (invalid)") + FString::FromInt( MaterialIdx ) ; + MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + } + + // Create thumbnail for this material. + TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + MakeShareable( new FAssetThumbnail( MaterialInterface, 64, 64, AssetThumbnailPool ) ); + + VerticalBox->AddSlot().Padding( 0, 2 ) + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver ) + .OnAssetDropped( + this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx ) + [ + SAssignNew( HorizontalBox, SHorizontalBox ) + ] + ]; + + HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() + [ + SAssignNew( MaterialThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( + this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject *)StaticMesh, MaterialIdx ) + .OnMouseDoubleClick( + this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( MaterialPathName ) ) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this mesh and material index. + { + TPairInitializer Pair( StaticMesh, MaterialIdx ); + MaterialInterfaceThumbnailBorders.Add( Pair, MaterialThumbnailBorder ); + } + + // ComboBox and buttons + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add Combo box + TSharedPtr< SComboButton > AssetComboButton; + ComboAndButtonBox->AddSlot().VAlign(VAlign_Center).FillHeight(1.0f) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().VAlign(VAlign_Center).FillHeight(1.0f) + [ + SAssignNew(AssetComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, + MaterialInterface, (UObject*)StaticMesh, InOutput, MaterialIdx) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + + // Add buttons + TSharedPtr< SHorizontalBox > ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Use CB selection arrow button + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)StaticMesh, InOutput, MaterialIdx), + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + ]; + + // Browse CB button + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface ), TAttribute< FText >( MaterialTooltip ) ) + ]; + + // Reset button + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBaseMaterial", "Reset to base material" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked( + this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + // Store combo button for this mesh and index. + { + TPairInitializer Pair( StaticMesh, MaterialIdx ); + MaterialInterfaceComboButtons.Add( Pair, AssetComboButton ); + } + } +} + +void +FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UHoudiniStaticMesh * ProxyMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject) +{ + if (!ProxyMesh || ProxyMesh->IsPendingKill()) + return; + + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // TODO: GetBakingBaseName! + FString Label = ProxyMesh->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + // Create thumbnail for this mesh. + TSharedPtr MeshThumbnail = MakeShareable(new FAssetThumbnail(ProxyMesh, 64, 64, AssetThumbnailPool)); + TSharedPtr MeshThumbnailBorder; + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(BakeName)) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + }) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + // Add details on the Proxy Mesh + EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); + FString MeshLabel = TEXT("Proxy Mesh"); + + // Indicate that this mesh is instanced + if (HoudiniGeoPartObject.bIsInstanced) + { + MeshLabel += TEXT("\n(instanced)"); + } + + if (HoudiniGeoPartObject.bIsTemplated) + { + MeshLabel += TEXT("\n(templated)"); + } + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(FText::FromString(MeshLabel)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot() + .Padding(0, 2) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(MeshThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)ProxyMesh) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)ProxyMesh) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(ProxyMesh->GetPathName())) + [ + MeshThumbnail->MakeThumbnailWidget() + ] + ] + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .MaxWidth(80.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("Refine", "Refine")) + .IsEnabled(true) + .OnClicked(this, &FHoudiniOutputDetails::OnRefineClicked, (UObject *)ProxyMesh, InOutput) + .ToolTipText(LOCTEXT("RefineTooltip", "Refine this Proxy Mesh to a Static Mesh")) + ] + ] + ] + ]; + + // Store thumbnail for this mesh. + OutputObjectThumbnailBorders.Add(ProxyMesh, MeshThumbnailBorder); + + // We need to add material box for each material present in this static mesh. + auto & ProxyMeshMaterials = ProxyMesh->GetStaticMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < ProxyMeshMaterials.Num(); ++MaterialIdx) + { + UMaterialInterface * MaterialInterface = ProxyMeshMaterials[MaterialIdx].MaterialInterface; + TSharedPtr< SBorder > MaterialThumbnailBorder; + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if (MaterialInterface && !MaterialInterface->IsPendingKill() + && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill()) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + else + { + MaterialInterface = nullptr; + MaterialName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + } + + // Create thumbnail for this material. + TSharedPtr MaterialInterfaceThumbnail = + MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + + // No drop target + VerticalBox->AddSlot() + .Padding(0, 2) + [ + SNew(SAssetDropTarget) + //.OnIsAssetAcceptableForDrop(false) + //.OnAssetDropped( + // this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + HorizontalBox->AddSlot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(MaterialThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage( + this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)ProxyMesh, MaterialIdx) + .OnMouseDoubleClick( + this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(MaterialPathName)) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this mesh and material index. + { + TPairInitializer Pair((UObject*)ProxyMesh, MaterialIdx); + MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + } + + // Combo box and buttons + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add combo box + TSharedPtr AssetComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SAssignNew(AssetComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + /*.OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, + MaterialInterface, StaticMesh, InOutput, MaterialIdx)*/ + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + + TSharedPtr ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Disable the combobutton for proxies + AssetComboButton->SetEnabled(false); + + // Add use selection form content browser array + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + /*FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)ProxyMesh, InOutput, MaterialIdx),*/ + FSimpleDelegate::CreateLambda([]() {}), // Do nothing for proxies + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser")), false) + // Disable the use CB selection button for proxies + ]; + + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP(this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), TAttribute(MaterialTooltip)) + ]; + + /* + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked( + this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + */ + + // Store combo button for this mesh and index. + { + TPairInitializer Pair(ProxyMesh, MaterialIdx); + MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + } + } +} + +FText +FHoudiniOutputDetails::GetOutputDebugName(UHoudiniOutput* InOutput) +{ + // Get the name and type + FString OutputNameStr = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); + + // Then add the number of parts + OutputNameStr += TEXT(" (") + FString::FromInt(InOutput->GetHoudiniGeoPartObjects().Num()) + TEXT(" Part(s))\n"); + + return FText::FromString(OutputNameStr); +} +FText +FHoudiniOutputDetails::GetOutputDebugDescription(UHoudiniOutput* InOutput) +{ + const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); + + FString OutputValStr; + OutputValStr += TEXT("HGPOs:\n"); + for (auto& HGPO : HGPOs) + { + OutputValStr += TEXT(" - ") + HGPO.PartName + TEXT(" (") + FHoudiniGeoPartObject::HoudiniPartTypeToString(HGPO.Type) + TEXT(")"); + + if (HGPO.SplitGroups.Num() > 0) + { + OutputValStr += TEXT("( ") + FString::FromInt(HGPO.SplitGroups.Num()) + TEXT(" splits:"); + for (auto& split : HGPO.SplitGroups) + { + OutputValStr += TEXT(" ") + split; + } + OutputValStr += TEXT(")"); + } + + if (!HGPO.VolumeName.IsEmpty()) + { + OutputValStr += TEXT("( ") + HGPO.VolumeName; + if (HGPO.VolumeTileIndex >= 0) + OutputValStr += TEXT(" tile ") + FString::FromInt(HGPO.VolumeTileIndex); + OutputValStr += TEXT(" )"); + } + + OutputValStr += TEXT("\n"); + } + + // Add output objects if any + TMap AllOutputObj = InOutput->GetOutputObjects(); + if (AllOutputObj.Num() > 0) + { + bool TitleAdded = false; + for (const auto& Iter : AllOutputObj) + { + UObject* OutObject = Iter.Value.OutputObject; + if (OutObject) + { + OutputValStr += OutObject->GetFullName() + TEXT(" (obj)\n"); + } + + UObject* OutComp = Iter.Value.OutputComponent; + if (OutComp) + { + OutputValStr += OutObject->GetFullName() + TEXT(" (comp)\n"); + } + } + } + + return FText::FromString(OutputValStr); +} + +FText +FHoudiniOutputDetails::GetOutputTooltip(UHoudiniOutput* InOutput) +{ + // TODO + return FText(); +} + + +const FSlateBrush * +FHoudiniOutputDetails::GetThumbnailBorder(UObject* Mesh) const +{ + TSharedPtr ThumbnailBorder = OutputObjectThumbnailBorders[Mesh]; + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + + +const FSlateBrush * +FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(UObject* OutputObject, int32 MaterialIdx) const +{ + if (!OutputObject) + return nullptr; + + TPairInitializer Pair(OutputObject, MaterialIdx); + TSharedPtr ThumbnailBorder = MaterialInterfaceThumbnailBorders[Pair]; + + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + +/* +const FSlateBrush * +FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx) const +{ + if (!Landscape) + return nullptr; + + TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); + TSharedPtr< SBorder > ThumbnailBorder = LandscapeMaterialInterfaceThumbnailBorders[Pair]; + + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} +*/ + +FReply +FHoudiniOutputDetails::OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, + const FPointerEvent & InMouseEvent, UObject * Object) +{ + if (Object && GEditor) + GEditor->EditObject(Object); + + return FReply::Handled(); +} + +/* +FReply +FHoudiniOutputDetails::OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject) +{ + if (HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill()) + { + FHoudiniPackageParams PackageParms; + + + FHoudiniEngineBakeUtils::BakeStaticMesh(HoudiniAssetComponent, GeoPartObject, StaticMesh, PackageParms); + // TODO: Bake the SM + + + // We need to locate corresponding geo part object in component. + const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(StaticMesh); + + // (void)FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( + // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); + + } + + return FReply::Handled(); +} +*/ + +bool +FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver(const UObject * InObject) const +{ + return (InObject && InObject->IsA(UMaterialInterface::StaticClass())); +} + + +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + UStaticMesh * StaticMesh, + UHoudiniOutput * HoudiniOutput, + int32 MaterialIdx) +{ + FReply RetValue = FReply::Handled(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return RetValue; + + if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) + return RetValue; + + // Retrieve material interface which is being replaced. + UMaterialInterface * MaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; + if (!MaterialInterface) + return RetValue; + + // Find the string corresponding to the material that is being replaced + const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); + if (!FoundString ) + { + // This material was not replaced, no need to reset it + return RetValue; + } + + // This material has been replaced previously. + FString MaterialString = *FoundString; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), HoudiniOutput); + + // Remove the replacement + HoudiniOutput->Modify(); + HoudiniOutput->GetReplacementMaterials().Remove(MaterialString); + + bool bViewportNeedsUpdate = true; + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + UMaterialInterface * const * FoundMat = HoudiniOutput->GetAssignementMaterials().Find(MaterialString); + if (FoundMat && (*FoundMat)) + AssignMaterial = *FoundMat; + + // Replace material on static mesh. + StaticMesh->Modify(); + StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = AssignMaterial; + + // Replace the material on any component (SMC/ISMC) that uses the above SM + // TODO: ?? Replace for all? + for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) + { + // Only look at MeshComponents + UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); + if (!SMC) + continue; + + if (SMC->GetStaticMesh() != StaticMesh) + continue; + + SMC->Modify(); + SMC->SetMaterial(MaterialIdx, AssignMaterial); + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + return RetValue; +} + +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + ALandscapeProxy* InLandscape, + UHoudiniOutput * InHoudiniOutput, + int32 InMaterialIdx) +{ + FReply RetValue = FReply::Handled(); + if (!InLandscape || InLandscape->IsPendingKill()) + return RetValue; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * MaterialInterface = InMaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); + UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + // Find the string corresponding to the material that is being replaced + const FString* FoundString = InHoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); + if (!FoundString) + { + // This material was not replaced, no need to reset it + return RetValue; + } + + // This material has been replaced previously. + FString MaterialString = *FoundString; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), InHoudiniOutput); + + // Remove the replacement + InHoudiniOutput->Modify(); + InHoudiniOutput->GetReplacementMaterials().Remove(MaterialString); + + bool bViewportNeedsUpdate = true; + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + UMaterialInterface * const * FoundMat = InHoudiniOutput->GetAssignementMaterials().Find(MaterialString); + if (FoundMat && (*FoundMat)) + AssignMaterial = *FoundMat; + + // Replace material on Landscape + InLandscape->Modify(); + if (InMaterialIdx == 0) + InLandscape->LandscapeMaterial = AssignMaterial; + else + InLandscape->LandscapeHoleMaterial = AssignMaterial; + + InLandscape->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); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + */ + + FHoudiniEngineUtils::UpdateEditorProperties(InHoudiniOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + return RetValue; +} +/* +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + ALandscapeProxy * Landscape, UHoudiniOutput * InOutput, int32 MaterialIdx) +{ + bool bViewportNeedsUpdate = false; + + // TODO: Handle me! + for (TArray< UHoudiniAssetComponent * >::TIterator + IterComponents(HoudiniAssetComponents); IterComponents; ++IterComponents) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if (!HoudiniAssetComponent) + continue; + + TWeakObjectPtr* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find(*HoudiniGeoPartObject); + if (!FoundLandscapePtr) + continue; + + ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get(); + if (!FoundLandscape || !FoundLandscape->IsValidLowLevel()) + continue; + + if (FoundLandscape != Landscape) + continue; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + bool bMaterialRestored = false; + FString MaterialShopName; + if (!HoudiniAssetComponent->GetReplacementMaterialShopName(*HoudiniGeoPartObject, MaterialInterface, MaterialShopName)) + { + // This material was not replaced so there's no need to reset it + continue; + } + + // Remove the replacement + HoudiniAssetComponent->RemoveReplacementMaterial(*HoudiniGeoPartObject, MaterialShopName); + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial(MaterialShopName); + if (AssignedMaterial) + MaterialInterfaceReplacement = AssignedMaterial; + + // Replace material on the landscape + Landscape->Modify(); + + if (MaterialIdx == 0) + Landscape->LandscapeMaterial = MaterialInterfaceReplacement; + else + Landscape->LandscapeHoleMaterial = MaterialInterfaceReplacement; + + //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); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + + HoudiniAssetComponent->UpdateEditorProperties(false); + bViewportNeedsUpdate = true; + } + + if (GEditor && bViewportNeedsUpdate) + { + GEditor->RedrawAllViewports(); + } + + return FReply::Handled(); +} +*/ + +void +FHoudiniOutputDetails::OnBrowseTo(UObject* InObject) +{ + if (GEditor) + { + TArray Objects; + Objects.Add(InObject); + GEditor->SyncBrowserToObjects(Objects); + } +} + +TSharedRef +FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent( + UMaterialInterface* MaterialInterface, + UObject* OutputObject, + UHoudiniOutput* InOutput, + int32 MaterialIdx) +{ + TArray AllowedClasses; + AllowedClasses.Add(UMaterialInterface::StaticClass()); + + TArray NewAssetFactories; + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(MaterialInterface), + true, + AllowedClasses, + NewAssetFactories, + OnShouldFilterMaterialInterface, + FOnAssetSelected::CreateSP( + this, &FHoudiniOutputDetails::OnMaterialInterfaceSelected, OutputObject, InOutput, MaterialIdx), + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::CloseMaterialInterfaceComboButton)); +} + + +void +FHoudiniOutputDetails::CloseMaterialInterfaceComboButton() +{ + +} + +void +FHoudiniOutputDetails::OnMaterialInterfaceDropped( + UObject * InObject, + UStaticMesh * StaticMesh, + UHoudiniOutput * HoudiniOutput, + int32 MaterialIdx) +{ + UMaterialInterface * MaterialInterface = Cast(InObject); + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + return; + + if (!StaticMesh || StaticMesh->IsPendingKill()) + return; + + if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) + return; + + bool bViewportNeedsUpdate = false; + + // Retrieve material interface which is being replaced. + UMaterialInterface * OldMaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; + if (OldMaterialInterface == MaterialInterface) + return; + + // Find the string corresponding to the material that is being replaced + FString MaterialString = FString(); + const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been replaced previously. + MaterialString = *FoundString; + } + else + { + // We have no previous replacement for this material, + // see if we can find it the material assignment list. + FoundString = HoudiniOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been assigned previously. + MaterialString = *FoundString; + } + else + { + UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + if (OldMaterialInterface == DefaultMaterial) + { + // This is replacement for default material. + MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + } + else + { + // External Material? + MaterialString = OldMaterialInterface->GetName(); + } + } + } + + if (MaterialString.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), HoudiniOutput); + + // Add a new material replacement entry. + HoudiniOutput->Modify(); + HoudiniOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); + + // Replace material on static mesh. + StaticMesh->Modify(); + StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = MaterialInterface; + + // Replace the material on any component (SMC/ISMC) that uses the above SM + for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) + { + // Only look at MeshComponents + UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); + if (SMC && !SMC->IsPendingKill()) + { + if (SMC->GetStaticMesh() == StaticMesh) + { + SMC->Modify(); + SMC->SetMaterial(MaterialIdx, MaterialInterface); + } + } + else + { + UStaticMesh* SM = Cast(OutputObject.Value.OutputObject); + if (SM && !SM->IsPendingKill()) + { + SM->Modify(); + SM->SetMaterial(MaterialIdx, MaterialInterface); + } + } + + + + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); + + /* + if(GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); +*/ + if (GEditor) + GEditor->RedrawAllViewports(); +} + +// Delegate used when a valid material has been drag and dropped on a landscape. +void +FHoudiniOutputDetails::OnMaterialInterfaceDropped( + UObject* InDroppedObject, + ALandscapeProxy* InLandscape, + UHoudiniOutput* InOutput, + int32 MaterialIdx) +{ + UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >(InDroppedObject); + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + return; + + if (!InLandscape || InLandscape->IsPendingKill()) + return; + + bool bViewportNeedsUpdate = false; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * OldMaterialInterface = MaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); + if (OldMaterialInterface == MaterialInterface) + return; + + // Find the string corresponding to the material that is being replaced + FString MaterialString = FString(); + const FString* FoundString = InOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been replaced previously. + MaterialString = *FoundString; + } + else + { + // We have no previous replacement for this material, + // see if we can find it the material assignment list. + FoundString = InOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been assigned previously. + MaterialString = *FoundString; + } + else + { + UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + if (OldMaterialInterface == DefaultMaterial) + { + // This is replacement for default material. + MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + } + else + { + // External Material? + if (OldMaterialInterface && !OldMaterialInterface->IsPendingKill()) + MaterialString = OldMaterialInterface->GetName(); + } + } + } + + if (MaterialString.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), InOutput); + + // Add a new material replacement entry. + InOutput->Modify(); + InOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); + + // Replace material on the landscape + InLandscape->Modify(); + + if (MaterialIdx == 0) + InLandscape->LandscapeMaterial = MaterialInterface; + else + InLandscape->LandscapeHoleMaterial = MaterialInterface; + + // Update the landscape components Material instances + InLandscape->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 >(InLandscape->GetClass(), (MaterialIdx == 0) ? TEXT("LandscapeMaterial") : TEXT("LandscapeHoleMaterial")); + if (FoundProperty) + { + FPropertyChangedEvent PropChanged(FoundProperty, EPropertyChangeType::ValueSet); + InLandscape->PostEditChangeProperty(PropChanged); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + */ + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); +} + +void +FHoudiniOutputDetails::OnMaterialInterfaceSelected( + const FAssetData & AssetData, + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + TPairInitializer Pair(OutputObject, MaterialIdx); + TSharedPtr AssetComboButton = MaterialInterfaceComboButtons[Pair]; + if (AssetComboButton.IsValid()) + { + AssetComboButton->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + + UStaticMesh* SM = Cast(OutputObject); + if (SM && !SM->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); + } + + ALandscapeProxy* Landscape = Cast(OutputObject); + if (Landscape && !Landscape->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } + } +} + +void +FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + if (!OutputObject || OutputObject->IsPendingKill()) + return; + + if (!InOutput || InOutput->IsPendingKill()) + return; + + if (GEditor) + { + TArray CBSelections; + GEditor->GetContentBrowserSelections(CBSelections); + + // Get the first selected material object + UObject* Object = nullptr; + for (auto & CurAssetData : CBSelections) + { + if (CurAssetData.AssetClass != UMaterial::StaticClass()->GetFName() && + CurAssetData.AssetClass != UMaterialInstance::StaticClass()->GetFName() && + CurAssetData.AssetClass != UMaterialInstanceConstant::StaticClass()->GetFName()) + continue; + + Object = CurAssetData.GetAsset(); + break; + } + + if (Object && !Object->IsPendingKill()) + { + UStaticMesh* SM = Cast(OutputObject); + if (SM && !SM->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); + } + + ALandscapeProxy* Landscape = Cast(OutputObject); + if (Landscape && !Landscape->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } + } + } +} + +void +FHoudiniOutputDetails::CreateInstancerOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // Do not display instancer UI for one-instance instancers + bool OnlyOneInstanceInstancers = true; + for (auto& Iter : InOutput->GetInstancedOutputs()) + { + FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); + if (CurInstanceOutput.OriginalTransforms.Num() <= 1) + continue; + + OnlyOneInstanceInstancers = false; + break; + } + + // This output only has one-instance instancers (SMC), no need to display the instancer UI. + if (OnlyOneInstanceInstancers) + return; + + // Classes allowed for instance variations. + const TArray AllowedClasses = + { + UStaticMesh::StaticClass(), USkeletalMesh::StaticClass(), + AActor::StaticClass(), UBlueprint::StaticClass(), + UFXSystemAsset::StaticClass(), USoundBase::StaticClass() + }; + + // Classes not allowed for instances variations (useless?) + TArray DisallowedClasses = + { + UClass::StaticClass(), ULevel::StaticClass(), + UMaterial::StaticClass(), UTexture::StaticClass() + }; + + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // Lambda for adding new variation objects + auto AddObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) + { + // TODO: undo/redo? + InOutputToUpdate.VariationObjects.Insert(InObject, AtIndex); + InOutputToUpdate.VariationTransformOffsets.Insert(FTransform::Identity, AtIndex); + FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for adding new geometry input objects + auto RemoveObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex) + { + // Also keep one instance object + if (AtIndex < 0 || AtIndex >= InOutputToUpdate.VariationObjects.Num()) + return; + + if (InOutputToUpdate.VariationObjects.Num() == 1) + return; + + // TODO: undo/redo? + InOutputToUpdate.VariationObjects.RemoveAt(AtIndex); + InOutputToUpdate.VariationTransformOffsets.RemoveAt( AtIndex); + FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for updating a variation + auto SetObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) + { + if (!InOutputToUpdate.VariationObjects.IsValidIndex(AtIndex)) + return; + + InOutputToUpdate.VariationObjects[AtIndex] = InObject; + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for changing the transform offset values + auto ChangeTransformOffsetAt = [InOutput]( + FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, + const float& Value, const int32& PosRotScaleIndex, const int32& XYZIndex) + { + bool bChanged = InOutputToUpdate.SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); + if (!bChanged) + return; + + InOutputToUpdate.MarkChanged(true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Get this output's OutputObject + const TMap& OutputObjects = InOutput->GetOutputObjects(); + + // Iterate on all of the output's HGPO + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + // Not an instancer, skip + if (CurHGPO.Type != EHoudiniPartType::Instancer) + continue; + + // Get the label for that instancer + FString InstancerLabel = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); + if (CurHGPO.bHasCustomPartName) + InstancerLabel = CurHGPO.PartName; + + TSharedRef InstancerVerticalBox = SNew(SVerticalBox); + TSharedPtr InstancerHorizontalBox = nullptr; + + // Create a new Group for that instancer + IDetailGroup& InstancerGroup = HouOutputCategory.AddGroup(FName(*InstancerLabel), FText::FromString(InstancerLabel)); + + // Now iterate and display the instance outputs that matches this HGPO + for (auto& Iter : InOutput->GetInstancedOutputs()) + { + FHoudiniOutputObjectIdentifier& CurOutputObjectIdentifier = Iter.Key; + if (!CurOutputObjectIdentifier.Matches(CurHGPO)) + continue; + + FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); + + // Dont display instancer UI for one-instance instancers (SMC) + if (CurInstanceOutput.OriginalTransforms.Num() <= 1) + continue; + + for( int32 VariationIdx = 0; VariationIdx < CurInstanceOutput.VariationObjects.Num(); VariationIdx++ ) + { + UObject * InstancedObject = CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous(); + if ( !InstancedObject || InstancedObject->IsPendingKill() ) + { + HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); + continue; + } + + // Create thumbnail for this object. + TSharedPtr VariationThumbnail = + MakeShareable(new FAssetThumbnail(InstancedObject, 64, 64, AssetThumbnailPool)); + TSharedRef PickerVerticalBox = SNew(SVerticalBox); + TSharedPtr PickerHorizontalBox = nullptr; + TSharedPtr VariationThumbnailBorder; + + // For the variation name, reuse the instancer label and append the variation index if we have more than one variation + FString InstanceOutputLabel = InstancerLabel; + if(CurInstanceOutput.VariationObjects.Num() > 1) + InstanceOutputLabel += TEXT(" [") + FString::FromInt(VariationIdx) + TEXT("]"); + + IDetailGroup* DetailGroup = &InstancerGroup; + if (CurInstanceOutput.VariationObjects.Num() > 1) + { + // If we have more than one variation, add a new group for each variation + DetailGroup = &InstancerGroup.AddGroup(FName(*InstanceOutputLabel), FText::FromString(InstanceOutputLabel), true); + } + + // See if we can find the corresponding component to get its type + FString InstancerType = TEXT("(Instancer)"); + FHoudiniOutputObjectIdentifier CurVariationIdentifier = CurOutputObjectIdentifier; + CurVariationIdentifier.SplitIdentifier += TEXT("_") + FString::FromInt(VariationIdx); + const FHoudiniOutputObject* VariationOutputObject = OutputObjects.Find(CurVariationIdentifier); + if(VariationOutputObject) + InstancerType = FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(VariationOutputObject->OutputComponent); + + DetailGroup->AddWidgetRow() + .NameContent() + [ + //SNew(SSpacer) + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(InstancerType)) + //.Size(FVector2D(250, 64)) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + PickerVerticalBox + ]; + + // Add an asset drop target + PickerVerticalBox->AddSlot() + .Padding( 0, 2 ) + .AutoHeight() + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( + [=]( const UObject* Obj ) { + for ( auto Klass : DisallowedClasses ) + { + if ( Obj && Obj->IsA( Klass ) ) + return false; + } + return true; + }) + ) + .OnAssetDropped_Lambda([&CurInstanceOutput, VariationIdx, SetObjectAt](UObject* InObject) + { + return SetObjectAt(CurInstanceOutput, VariationIdx, InObject); + }) + [ + SAssignNew( PickerHorizontalBox, SHorizontalBox ) + ] + ]; + + PickerHorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(VariationThumbnailBorder, SBorder) + .Padding( 5.0f ) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, InstancedObject) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(InstancedObject->GetPathName())) + [ + VariationThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + VariationThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush *>::Create( + TAttribute::FGetter::CreateLambda([=]() + { + if (VariationThumbnailBorder.IsValid() && VariationThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ) ) ); + + PickerHorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 28.0f, 0.0f, 28.0f) + [ + PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, AddObjectAt]() + { + UObject* ObjToAdd = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? + CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() + : nullptr; + + return AddObjectAt(CurInstanceOutput, VariationIdx, ObjToAdd); + }), + LOCTEXT("AddAnotherInstanceToolTip", "Add Another Instance")) + ]; + + PickerHorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 28.0f, 4.0f, 28.0f ) + [ + PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, RemoveObjectAt]() + { + return RemoveObjectAt(CurInstanceOutput, VariationIdx); + }), + LOCTEXT("RemoveLastInstanceToolTip", "Remove Last Instance")) + ]; + + TSharedPtr AssetComboButton; + TSharedPtr ButtonBox; + PickerHorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .HAlign(HAlign_Fill) + [ + SAssignNew(ButtonBox, SHorizontalBox) + +SHorizontalBox::Slot() + [ + SAssignNew(AssetComboButton, SComboButton) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) + /* TODO: Update UI + .OnMenuOpenChanged( FOnIsOpenChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::ChangedStaticMeshComboButton, + CurInstanceOutput, InstOutIdx, VariationIdx ) ) + */ + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(InstancedObject->GetName())) + ] + ] + ] + ]; + + // Create asset picker for this combo button. + { + TArray NewAssetFactories; + TSharedRef PropertyMenuAssetPicker = PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(InstancedObject), + true, + AllowedClasses, + DisallowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda([&CurInstanceOutput, VariationIdx, SetObjectAt, AssetComboButton](const FAssetData& AssetData) + { + if ( AssetComboButton.IsValid() ) + { + AssetComboButton->SetIsOpen( false ); + UObject * Object = AssetData.GetAsset(); + SetObjectAt( CurInstanceOutput, VariationIdx, Object); + } + }), + // Nothing to do on close + FSimpleDelegate::CreateLambda([](){}) + ); + + AssetComboButton->SetMenuContent(PropertyMenuAssetPicker); + } + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(InstancedObject->GetName())); + FText StaticMeshTooltip = + FText::Format(LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx]() + { + UObject* InputObject = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? + CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() + : nullptr; + + if (GEditor && InputObject) + { + TArray Objects; + Objects.Add(InputObject); + GEditor->SyncBrowserToObjects(Objects); + } + }), + TAttribute< FText >( StaticMeshTooltip ) ) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f ) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT( "ResetToBase", "Reset to default static mesh")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([SetObjectAt, &CurInstanceOutput, VariationIdx]() + { + SetObjectAt(CurInstanceOutput, VariationIdx, CurInstanceOutput.OriginalObject.LoadSynchronous()); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + + + // Get Visibility of reset buttons + bool bResetButtonVisiblePosition = false; + bool bResetButtonVisibleRotation = false; + bool bResetButtonVisibleScale = false; + + FTransform CurTransform = CurInstanceOutput.VariationTransformOffsets[VariationIdx]; + + if (CurTransform.GetLocation() != FVector::ZeroVector) + bResetButtonVisiblePosition = true; + + FRotator Rotator = CurTransform.Rotator(); + if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) + bResetButtonVisibleRotation = true; + + if (CurTransform.GetScale3D() != FVector::OneVector) + bResetButtonVisibleScale = true; + + auto ChangeTransformOffsetUniformlyAt = [ChangeTransformOffsetAt, VariationIdx, &CurInstanceOutput](const float& Val, const int32& PosRotScaleIndex) + { + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 0); + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 1); + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 2); + }; + + TSharedRef OffsetVerticalBox = SNew(SVerticalBox); + FText LabelPositionText = LOCTEXT("HoudiniPositionOffset", "Position Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelPositionText) + .ToolTipText(LabelPositionText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .AllowSpin(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 0); } + ))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 1); } + ))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 2); } + ))) + .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 0); }) + .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 1); }) + .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 2); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("InstancerOutputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, CurInstanceOutput, InOutput]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 0); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + ]; + + FText LabelRotationText = LOCTEXT("HoudiniRotationOffset", "Rotation Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelRotationText) + .ToolTipText(LabelRotationText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SRotatorInputBox) + .AllowSpin(true) + .bColorAxisLabels(true) + .Roll(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 0); } + ))) + .Pitch(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 1); } + ))) + .Yaw(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 2); } + ))) + .OnRollCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 0); }) + .OnPitchCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 1); }) + .OnYawCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 2); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 1); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + ]; + + FText LabelScaleText = LOCTEXT("HoudiniScaleOffset", "Scale Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelScaleText) + .ToolTipText(LabelScaleText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 0); } + ))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 1); } + ))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 2); } + ))) + .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 0); + }) + .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 1); + }) + .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 2); + }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("InstancerOutputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the output object maintains its shape in each direction when scaled")) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(CurInstanceOutput.IsUnformScaleLocked() ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([&CurInstanceOutput, InOutput]() + { + CurInstanceOutput.SwitchUniformScaleLock(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() + { + ChangeTransformOffsetUniformlyAt(1.0f, 2); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + /* + // TODO: Add support for this back + + SHorizontalBox::Slot().AutoWidth() + [ + // Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "TransparentCheckBox") + .ToolTipText(LOCTEXT("PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled")) + *//* + .OnCheckStateChanged(FOnCheckStateChanged::CreateLambda([=](ECheckBoxState NewState) + { + if ( MyParam.IsValid() && InputFieldPtr.IsValid() ) + MyParam->CheckStateChanged( NewState == ECheckBoxState::Checked, InputFieldPtr.Get(), VariationIdx ); + })) + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute::FGetter::CreateLambda( [=]() + { + if (InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly(VariationIdx)) + return ECheckBoxState::Checked; + return ECheckBoxState::Unchecked; + } + ))) + *//* + [ + SNew(SImage) + *//*.Image(TAttribute::Create( + TAttribute::FGetter::CreateLambda( [=]() + { + if ( InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly( VariationIdx ) ) + { + return FEditorStyle::GetBrush( TEXT( "GenericLock" ) ); + } + return FEditorStyle::GetBrush( TEXT( "GenericUnlock" ) ); + } + ))) + *//* + .ColorAndOpacity( FSlateColor::UseForeground() ) + ] + ] + */ + ]; + } + } + } +} + +/* +void +FHoudiniOutputDetails::OnMaterialInterfaceSelected( + const FAssetData & AssetData, + ALandscapeProxy* Landscape, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); + TSharedPtr< SComboButton > AssetComboButton = LandscapeMaterialInterfaceComboButtons[Pair]; + if (AssetComboButton.IsValid()) + { + AssetComboButton->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } +} +*/ + +void +FHoudiniOutputDetails::CreateDefaultOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput) + return; + + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); + + // TODO + // This is just a temporary placeholder displaying name/output type + { + FString OutputNameStr = InOutput->GetName(); + FText OutputTooltip = GetOutputTooltip(InOutput); + + // Create a new detail row + // Name + FText OutputNameTxt = GetOutputDebugName(InOutput); + FDetailWidgetRow & Row = HouOutputCategory.AddCustomRow(FText::GetEmpty()); + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(OutputNameTxt) + .ToolTipText(OutputTooltip) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + + // Value + FText OutputTypeTxt = GetOutputDebugDescription(InOutput); + Row.ValueWidget.Widget = + SNew(STextBlock) + .Text(OutputTypeTxt) + .ToolTipText(OutputTooltip) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + } +} + +void +FHoudiniOutputDetails::OnBakeOutputObject( + const FString& InBakeName, + UObject * BakedOutputObject, + const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniGeoPartObject & HGPO, + const FString & HoudiniAssetName, + const FString & BakeFolder, + const FString & TempCookFolder, + const EHoudiniOutputType & Type, + const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, + const TArray& InAllOutputs) +{ + if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) + return; + + FString ObjectName = InBakeName; + + // Set Object name according to priority Default Name > Attrib Custom Name > UI Custom Name + if(InBakeName.IsEmpty()) + { + if (HGPO.bHasCustomPartName) + ObjectName = HGPO.PartName; + else + ObjectName = BakedOutputObject->GetName(); + } + + // Fill in the package params + FHoudiniPackageParams PackageParams; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + OutputIdentifier, + BakeFolder, + ObjectName, + HoudiniAssetName); + + switch (Type) + { + case EHoudiniOutputType::Mesh: + { + UStaticMesh* StaticMesh = Cast(BakedOutputObject); + if (StaticMesh) + { + FDirectoryPath TempCookFolderPath; + TempCookFolderPath.Path = TempCookFolder; + UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( + StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath); + } + } + break; + case EHoudiniOutputType::Curve: + { + USplineComponent* SplineComponent = Cast(BakedOutputObject); + if (SplineComponent) + { + AActor* BakedActor; + USplineComponent* BakedSplineComponent; + FHoudiniEngineBakeUtils::BakeCurve(SplineComponent, GWorld->GetCurrentLevel(), PackageParams, BakedActor, BakedSplineComponent); + } + } + break; + case EHoudiniOutputType::Landscape: + { + ALandscapeProxy* Landscape = Cast(BakedOutputObject); + if (Landscape) + { + FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType); + } + } + break; + } +} + +FReply +FHoudiniOutputDetails::OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput) +{ + // TODO: Actually refine only the selected ProxyMesh + // For now, refine all the selection + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true, true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); +} + +void +FHoudiniOutputDetails::OnBakeNameCommitted( + const FText& Val, ETextCommit::Type TextCommitType, + UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + if (!InOutput) + return; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + + if (!FoundOutputObject) + return; + + FoundOutputObject->BakeName = Val.ToString(); +} + +void +FHoudiniOutputDetails::OnRevertBakeNameToDefault(UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier & InIdentifier) +{ + if (!InOutput) + return; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + + if (!FoundOutputObject) + return; + + FoundOutputObject->BakeName = FString(); +} #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h index e3b9b2342..138abaab4 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h @@ -1,212 +1,216 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "ContentBrowserDelegates.h" - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class UHoudiniOutput; -class UHoudiniAssetComponent; -class FAssetThumbnailPool; -class ALandscapeProxy; -class USplineComponent; -class UHoudiniLandscapePtr; -class UHoudiniStaticMesh; -class UMaterialInterface; -class SBorder; -class SComboButton; - -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniOutputObject; - -enum class EHoudiniOutputType : uint8; -enum class EHoudiniLandscapeOutputBakeType : uint8; - -class FHoudiniOutputDetails : public TSharedFromThis -{ -public: - void CreateWidget( - IDetailCategoryBuilder& HouInputCategoryBuilder, - TArray InOutputs); - - void CreateMeshOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateCurveOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateStaticMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UStaticMesh * StaticMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject, - const bool& bIsProxyMeshCurrent); - - void CreateProxyMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UHoudiniStaticMesh * ProxyMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject); - - void CreateCurveWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - USceneComponent* SplineComponent, - FHoudiniOutputObject& OutputObject, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - FHoudiniGeoPartObject& HoudiniGeoPartObject); - - void CreateLandscapeOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateLandscapeOutputWidget_Helper( - IDetailCategoryBuilder & HouOutputCategory, - UHoudiniOutput * InOutput, - const FHoudiniGeoPartObject & HGPO, - UHoudiniLandscapePtr * LandscapePointer, - const FHoudiniOutputObjectIdentifier & OutputIdentifier); - - void CreateInstancerOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput * InOutput); - - void CreateDefaultOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - static FText GetOutputTooltip(UHoudiniOutput* MainOutput); - static FText GetOutputDebugName(UHoudiniOutput* InOutput); - static FText GetOutputDebugDescription(UHoudiniOutput* InOutput); - - static void OnBakeNameCommitted( - const FText& Val, ETextCommit::Type TextCommitType, - UHoudiniOutput * InOutput, - const FHoudiniOutputObjectIdentifier & InIdentifier); - - static void OnRevertBakeNameToDefault( - UHoudiniOutput * InOutput, - const FHoudiniOutputObjectIdentifier & InIdentifier); - - static void OnBakeOutputObject( - const FString& InBakeName, - UObject * BakedOutputObject, - const FHoudiniOutputObjectIdentifier & OutputIdentifier, - const FHoudiniGeoPartObject & HGPO, - const FString & HoudiniAssetName, - const FString & BakeFolder, - const FString & TempCookFolder, - const EHoudiniOutputType & Type, - const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, - const TArray& InAllOutputs); - - FReply OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput); - - // Gets the border brush to show around thumbnails, changes when the user hovers on it. - const FSlateBrush * GetThumbnailBorder(UObject* Mesh) const; - const FSlateBrush * GetMaterialInterfaceThumbnailBorder(UObject* Mesh, int32 MaterialIdx) const; - - // Delegate used to detect if valid object has been dragged and dropped. - bool OnMaterialInterfaceDraggedOver(const UObject * InObject) const; - - // Delegate used when a valid material has been drag and dropped on a mesh. - void OnMaterialInterfaceDropped( - UObject* InDroppedObject, - UStaticMesh* InStaticMesh, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Delegate used when a valid material has been drag and dropped on a landscape. - void OnMaterialInterfaceDropped( - UObject* InDroppedObject, - ALandscapeProxy* InLandscape, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Construct drop down menu content for material. - TSharedRef OnGetMaterialInterfaceMenuContent( - UMaterialInterface* MaterialInterface, - UObject* OutputObject, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Delegate for handling selection in content browser. - void OnMaterialInterfaceSelected( - const FAssetData & AssetData, - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx); - - // Delegate for handling Use CB selection arrow button clicked. - void OnUseContentBrowserSelectedMaterialInterface( - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx); - - // Closes the combo button. - void CloseMaterialInterfaceComboButton(); - - // Browse to material interface. - void OnBrowseTo(UObject* InObject); - - // Handler for reset material interface button. - FReply OnResetMaterialInterfaceClicked( - UStaticMesh * StaticMesh, UHoudiniOutput * InOutput, int32 MaterialIdx); - - FReply OnResetMaterialInterfaceClicked( - ALandscapeProxy* InLandscape, UHoudiniOutput * InHoudiniOutput, int32 InMaterialIdx); - - // Handler for when static mesh thumbnail is double clicked. We open editor in this case. - FReply OnThumbnailDoubleClick( - const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object); - - // Handler for bake individual static mesh action. - // static FReply OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject); - -private: - - // Map of meshes and corresponding thumbnail borders. - TMap> OutputObjectThumbnailBorders; - // Map of meshes / material indices to thumbnail borders. - TMap, TSharedPtr> MaterialInterfaceThumbnailBorders; - // Map of meshes / material indices to combo elements. - TMap, TSharedPtr> MaterialInterfaceComboButtons; - - /** Delegate for filtering material interfaces. **/ - FOnShouldFilterAsset OnShouldFilterMaterialInterface; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "ContentBrowserDelegates.h" +#include "Materials/MaterialInterface.h" +#include "Components/Border.h" +#include "Components/ComboBox.h" + + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class UHoudiniOutput; +class UHoudiniAssetComponent; +class FAssetThumbnailPool; +class ALandscapeProxy; +class USplineComponent; +class UHoudiniLandscapePtr; +class UHoudiniStaticMesh; +class UMaterialInterface; +class SBorder; +class SComboButton; + +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniOutputObject; + +enum class EHoudiniOutputType : uint8; +enum class EHoudiniLandscapeOutputBakeType : uint8; + +class FHoudiniOutputDetails : public TSharedFromThis +{ +public: + void CreateWidget( + IDetailCategoryBuilder& HouInputCategoryBuilder, + TArray InOutputs); + + void CreateMeshOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateCurveOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateStaticMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UStaticMesh * StaticMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + const bool& bIsProxyMeshCurrent); + + void CreateProxyMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UHoudiniStaticMesh * ProxyMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject); + + void CreateCurveWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + USceneComponent* SplineComponent, + FHoudiniOutputObject& OutputObject, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + FHoudiniGeoPartObject& HoudiniGeoPartObject); + + void CreateLandscapeOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateLandscapeOutputWidget_Helper( + IDetailCategoryBuilder & HouOutputCategory, + UHoudiniOutput * InOutput, + const FHoudiniGeoPartObject & HGPO, + UHoudiniLandscapePtr * LandscapePointer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier); + + void CreateInstancerOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput * InOutput); + + void CreateDefaultOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + static FText GetOutputTooltip(UHoudiniOutput* MainOutput); + static FText GetOutputDebugName(UHoudiniOutput* InOutput); + static FText GetOutputDebugDescription(UHoudiniOutput* InOutput); + + static void OnBakeNameCommitted( + const FText& Val, ETextCommit::Type TextCommitType, + UHoudiniOutput * InOutput, + const FHoudiniOutputObjectIdentifier & InIdentifier); + + static void OnRevertBakeNameToDefault( + UHoudiniOutput * InOutput, + const FHoudiniOutputObjectIdentifier & InIdentifier); + + static void OnBakeOutputObject( + const FString& InBakeName, + UObject * BakedOutputObject, + const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniGeoPartObject & HGPO, + const FString & HoudiniAssetName, + const FString & BakeFolder, + const FString & TempCookFolder, + const EHoudiniOutputType & Type, + const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, + const TArray& InAllOutputs); + + FReply OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput); + + // Gets the border brush to show around thumbnails, changes when the user hovers on it. + const FSlateBrush * GetThumbnailBorder(UObject* Mesh) const; + const FSlateBrush * GetMaterialInterfaceThumbnailBorder(UObject* Mesh, int32 MaterialIdx) const; + + // Delegate used to detect if valid object has been dragged and dropped. + bool OnMaterialInterfaceDraggedOver(const UObject * InObject) const; + + // Delegate used when a valid material has been drag and dropped on a mesh. + void OnMaterialInterfaceDropped( + UObject* InDroppedObject, + UStaticMesh* InStaticMesh, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Delegate used when a valid material has been drag and dropped on a landscape. + void OnMaterialInterfaceDropped( + UObject* InDroppedObject, + ALandscapeProxy* InLandscape, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Construct drop down menu content for material. + TSharedRef OnGetMaterialInterfaceMenuContent( + UMaterialInterface* MaterialInterface, + UObject* OutputObject, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Delegate for handling selection in content browser. + void OnMaterialInterfaceSelected( + const FAssetData & AssetData, + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx); + + // Delegate for handling Use CB selection arrow button clicked. + void OnUseContentBrowserSelectedMaterialInterface( + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx); + + // Closes the combo button. + void CloseMaterialInterfaceComboButton(); + + // Browse to material interface. + void OnBrowseTo(UObject* InObject); + + // Handler for reset material interface button. + FReply OnResetMaterialInterfaceClicked( + UStaticMesh * StaticMesh, UHoudiniOutput * InOutput, int32 MaterialIdx); + + FReply OnResetMaterialInterfaceClicked( + ALandscapeProxy* InLandscape, UHoudiniOutput * InHoudiniOutput, int32 InMaterialIdx); + + // Handler for when static mesh thumbnail is double clicked. We open editor in this case. + FReply OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object); + + // Handler for bake individual static mesh action. + // static FReply OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject); + +private: + + // Map of meshes and corresponding thumbnail borders. + TMap> OutputObjectThumbnailBorders; + // Map of meshes / material indices to thumbnail borders. + TMap, TSharedPtr> MaterialInterfaceThumbnailBorders; + // Map of meshes / material indices to combo elements. + TMap, TSharedPtr> MaterialInterfaceComboButtons; + + /** Delegate for filtering material interfaces. **/ + FOnShouldFilterAsset OnShouldFilterMaterialInterface; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp index 598a6ab9c..24e0f284c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp @@ -1,2628 +1,2628 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPDGManager.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAssetActor.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniEngineDetails.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "IDetailGroup.h" -#include "IDetailCustomization.h" -#include "PropertyCustomizationHelpers.h" -#include "DetailWidgetRow.h" -#include "ScopedTransaction.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/SBoxPanel.h" -#include "Widgets/Layout/SSpacer.h" -#include "Framework/SlateDelegates.h" -#include "Templates/SharedPointer.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define HOUDINI_ENGINE_UI_SECTION_PDG_BAKE 2 - -void -FHoudiniPDGDetails::CreateWidget( - IDetailCategoryBuilder& HouPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // PDG ASSET - FHoudiniPDGDetails::AddPDGAssetWidget(HouPDGCategory, InPDGAssetLink); - - // TOP NETWORKS - FHoudiniPDGDetails::AddTOPNetworkWidget(HouPDGCategory, InPDGAssetLink); - - // PDG EVENT MESSAGES -} - - -void -FHoudiniPDGDetails::AddPDGAssetWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // PDG STATUS ROW - AddPDGAssetStatus(InPDGCategory, InPDGAssetLink); - - // Commandlet Status row - AddPDGCommandletStatus(InPDGCategory, FHoudiniEngine::Get().GetPDGCommandletStatus()); - - // REFRESH / RESET Buttons - { - TSharedRef RefreshHBox = SNew(SHorizontalBox); - TSharedPtr ResetHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGRefreshResetRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Refresh", "Refresh")) - .ToolTipText(LOCTEXT("RefreshTooltip", "Refreshes infos displayed by the the PDG Asset Link")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .OnClicked_Lambda([InPDGAssetLink]() - { - FHoudiniPDGDetails::RefreshPDGAssetLink(InPDGAssetLink); - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(RefreshHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Reset", "Reset")) - .ToolTipText(LOCTEXT("ResetTooltip", "Resets the PDG Asset Link")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .OnClicked_Lambda([InPDGAssetLink]() - { - // TODO: RESET USELESS? - FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(ResetHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr RefreshIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGRefreshIconBrush(); - if (RefreshIconBrush.IsValid()) - { - TSharedPtr RefreshImage; - RefreshHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RefreshImage, SImage) - ] - ]; - - RefreshImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([RefreshIconBrush]() { return RefreshIconBrush.Get(); }))); - } - - RefreshHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Refresh", "Refresh")) - ]; - - TSharedPtr ResetIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGResetIconBrush(); - if (ResetIconBrush.IsValid()) - { - TSharedPtr ResetImage; - ResetHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(ResetImage, SImage) - ] - ]; - - ResetImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([ResetIconBrush]() { return ResetIconBrush.Get(); }))); - } - - ResetHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Reset", "Reset")) - ]; - } - - // TOP NODE FILTER - { - FText Tooltip = FText::FromString(TEXT("When enabled, the TOP Node Filter will only display the TOP Nodes found in the current network that start with the filter prefix. Disabling the Filter will display all of the TOP Network's TOP Nodes.")); - // Lambda for changing the filter value - auto ChangeTOPNodeFilter = [InPDGAssetLink](const FString& NewValue) - { - if (InPDGAssetLink->TOPNodeFilter.Equals(NewValue)) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->TOPNodeFilter = NewValue; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter), InPDGAssetLink); - }; - - FDetailWidgetRow& PDGFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGFilterRow, InPDGAssetLink); - PDGFilterRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox enable filter - SNew(SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bUseTOPNodeFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (InPDGAssetLink->bUseTOPNodeFilter == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bUseTOPNodeFilter = bNewState; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node Filter"))) - .ToolTipText(Tooltip) - ]; - - PDGFilterRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .ToolTipText(Tooltip) - .Text_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return FText(); - return FText::FromString(InPDGAssetLink->TOPNodeFilter); - }) - .OnTextCommitted_Lambda([ChangeTOPNodeFilter](const FText& Val, ETextCommit::Type TextCommitType) - { - ChangeTOPNodeFilter(Val.ToString()); - }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([=]() - { - FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER); - ChangeTOPNodeFilter(DefaultFilter); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - } - - // TOP OUTPUT FILTER - { - // Lambda for changing the filter value - FText Tooltip = FText::FromString(TEXT("When enabled, the Work Item Output Files created for the TOP Nodes found in the current network that start with the filter prefix will be automatically loaded int the world after being cooked.")); - auto ChangeTOPOutputFilter = [InPDGAssetLink](const FString& NewValue) - { - if (InPDGAssetLink->TOPOutputFilter.Equals(NewValue)) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->TOPOutputFilter = NewValue; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter), InPDGAssetLink); - }; - - FDetailWidgetRow& PDGOutputFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGOutputFilterRow, InPDGAssetLink); - - PDGOutputFilterRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox enable filter - SNew(SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bUseTOPOutputFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (InPDGAssetLink->bUseTOPOutputFilter == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bUseTOPOutputFilter = bNewState; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Output Filter"))) - .ToolTipText(Tooltip) - ]; - - PDGOutputFilterRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return FText(); - return FText::FromString(InPDGAssetLink->TOPOutputFilter); - }) - .OnTextCommitted_Lambda([ChangeTOPOutputFilter](const FText& Val, ETextCommit::Type TextCommitType) - { - ChangeTOPOutputFilter(Val.ToString()); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([ChangeTOPOutputFilter]() - { - FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER); - ChangeTOPOutputFilter(DefaultFilter); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - } - - // Checkbox: Autocook - { - FText Tooltip = FText::FromString(TEXT("When enabled, the selected TOP Network's output will automatically cook after succesfully cooking the PDG Asset Link HDA.")); - FDetailWidgetRow& PDGAutocookRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGAutocookRow, InPDGAssetLink); - PDGAutocookRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Auto-cook"))) - .ToolTipText(Tooltip) - ]; - - TSharedPtr AutoCookCheckBox; - PDGAutocookRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(AutoCookCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bAutoCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (!InPDGAssetLink || InPDGAssetLink->bAutoCook == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bAutoCook = bNewState; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bAutoCook), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ]; - } - // Output parent actor selector - { - IDetailPropertyRow* PDGOutputParentActorRow = InPDGCategory.AddExternalObjectProperty({ InPDGAssetLink }, "OutputParentActor"); - if (PDGOutputParentActorRow) - { - TAttribute PDGOutputParentActorRowEnabled; - BindDisableIfPDGNotLinked(PDGOutputParentActorRowEnabled, InPDGAssetLink); - PDGOutputParentActorRow->IsEnabled(PDGOutputParentActorRowEnabled); - TSharedPtr NameWidget; - TSharedPtr ValueWidget; - PDGOutputParentActorRow->GetDefaultWidgets(NameWidget, ValueWidget); - PDGOutputParentActorRow->DisplayName(FText::FromString(TEXT("Output Parent Actor"))); - PDGOutputParentActorRow->ToolTip(FText::FromString( - TEXT("The PDG Output Actors will be created under this parent actor. If not set, then the PDG Output Actors will be created under a new folder."))); - } - } - - // Add bake widgets for PDG output - CreatePDGBakeWidgets(InPDGCategory, InPDGAssetLink); - - // TODO: move this to a better place: the baking code is in HoudiniEngineEditor, the PDG manager (that knows about - // when work object results are loaded is in HoudiniEngine and the PDGAssetLink is in HoudiniEngineRuntime). So - // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? - if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) - InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); - InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject); - - // WORK ITEM STATUS - { - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGStatusRow, InPDGAssetLink); - FHoudiniPDGDetails::AddWorkItemStatusWidget( - PDGStatusRow, TEXT("Asset Work Item Status"), InPDGAssetLink, false); - } -} - -bool -FHoudiniPDGDetails::GetPDGStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor) -{ - OutPDGStatusString = FString(); - OutPDGStatusColor = FLinearColor::White; - - if (!IsValid(InPDGAssetLink)) - return false; - - switch (InPDGAssetLink->LinkState) - { - case EPDGLinkState::Linked: - OutPDGStatusString = TEXT("PDG is READY"); - OutPDGStatusColor = FLinearColor::Green; - break; - case EPDGLinkState::Linking: - OutPDGStatusString = TEXT("PDG is Linking"); - OutPDGStatusColor = FLinearColor::Yellow; - break; - case EPDGLinkState::Error_Not_Linked: - OutPDGStatusString = TEXT("PDG is ERRORED"); - OutPDGStatusColor = FLinearColor::Red; - break; - case EPDGLinkState::Inactive: - OutPDGStatusString = TEXT("PDG is INACTIVE"); - OutPDGStatusColor = FLinearColor::White; - break; - default: - return false; - } - - return true; -} - -void -FHoudiniPDGDetails::AddPDGAssetStatus( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink) -{ - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - FString PDGStatusString; - FLinearColor PDGStatusColor; - GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); - return FText::FromString(PDGStatusString); - }) - .ColorAndOpacity_Lambda([InPDGAssetLink]() - { - FString PDGStatusString; - FLinearColor PDGStatusColor; - GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); - return FSlateColor(PDGStatusColor); - }) - ] - ]; -} - -void -FHoudiniPDGDetails::GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor) -{ - OutStatusString = FString(); - OutStatusColor = FLinearColor::White; - switch (FHoudiniEngine::Get().GetPDGCommandletStatus()) - { - case EHoudiniBGEOCommandletStatus::Connected: - OutStatusString = TEXT("Async importer is CONNECTED"); - OutStatusColor = FLinearColor::Green; - break; - case EHoudiniBGEOCommandletStatus::Running: - OutStatusString = TEXT("Async importer is Running"); - OutStatusColor = FLinearColor::Yellow; - break; - case EHoudiniBGEOCommandletStatus::Crashed: - OutStatusString = TEXT("Async importer has CRASHED"); - OutStatusColor = FLinearColor::Red; - break; - case EHoudiniBGEOCommandletStatus::NotStarted: - OutStatusString = TEXT("Async importer is NOT STARTED"); - OutStatusColor = FLinearColor::White; - break; - } -} - -void -FHoudiniPDGDetails::AddPDGCommandletStatus( - IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus) -{ - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Visibility_Lambda([]() - { - const UHoudiniRuntimeSettings* Settings = GetDefault(); - if (IsValid(Settings)) - { - return FHoudiniEngineCommands::IsPDGCommandletEnabled() ? EVisibility::Visible : EVisibility::Collapsed; - } - - return EVisibility::Visible; - }) - .Text_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetPDGCommandletStatus(StatusString, StatusColor); - return FText::FromString(StatusString); - }) - .ColorAndOpacity_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetPDGCommandletStatus(StatusString, StatusColor); - return FSlateColor(StatusColor); - }) - ] - ]; -} - -bool -FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( - UHoudiniPDGAssetLink* InAssetLink, - bool bInForSelectedNode, - const FString& InTallyItemString, - int32& OutValue, - FLinearColor& OutColor) -{ - OutValue = 0; - OutColor = FLinearColor::White; - - if (!IsValid(InAssetLink)) - return false; - - bool bFound = false; - FWorkItemTally* TallyPtr = nullptr; - if (bInForSelectedNode) - { - UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); - if (TOPNode && !TOPNode->bHidden) - TallyPtr = &(TOPNode->WorkItemTally); - } - else - TallyPtr = &(InAssetLink->WorkItemTally); - - if (TallyPtr) - { - if (InTallyItemString == TEXT("WAITING")) - { - // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI - OutValue = TallyPtr->WaitingWorkItems + TallyPtr->ScheduledWorkItems; - OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("COOKING")) - { - OutValue = TallyPtr->CookingWorkItems; - OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("COOKED")) - { - OutValue = TallyPtr->CookedWorkItems; - OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("FAILED")) - { - OutValue = TallyPtr->ErroredWorkItems; - OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; - bFound = true; - } - } - - return bFound; -} - -void -FHoudiniPDGDetails::AddWorkItemStatusWidget( - FDetailWidgetRow& InRow, const FString& InTitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode) -{ - auto AddGridBox = [InAssetLink, bInForSelectedNode](const FString& Title) -> SHorizontalBox::FSlot& - { - return SHorizontalBox::Slot() - .MaxWidth(500.0f) - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .AutoHeight() - .Padding(FMargin(1.0f, 2.0f)) - [ - SNew(SBorder) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) - .BorderBackgroundColor(FSlateColor(FLinearColor(0.6, 0.6, 0.6))) - .Padding(FMargin(1.0f, 5.0f)) - [ - SNew(SBox) - .WidthOverride(95.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString(Title)) - .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FSlateColor(Color); - }) - ] - ] - ] - + SVerticalBox::Slot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .AutoHeight() - .Padding(FMargin(1.0f, 2.0f)) - [ - SNew(SBorder) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) - .BorderBackgroundColor(FSlateColor(FLinearColor(0.8, 0.8, 0.8))) - .Padding(FMargin(1.0f, 5.0f)) - [ - SNew(SBox) - .WidthOverride(95.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FText::AsNumber(Value); - }) - .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FSlateColor(Color); - }) - ] - ] - ] - ]; - }; - - InRow.WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f) - .AutoWidth() - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - [ - SNew(SSpacer) - ] - + SVerticalBox::Slot() - .AutoHeight() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(FMargin(0.0f, 2.0f)) - [ - SNew(STextBlock) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .Text(FText::FromString(InTitleString)) - - ] - + SVerticalBox::Slot() - .AutoHeight() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(FMargin(0.0f, 2.0f)) - [ - SNew(SHorizontalBox) - + AddGridBox(TEXT("WAITING")) - + AddGridBox(TEXT("COOKING")) - + AddGridBox(TEXT("COOKED")) - + AddGridBox(TEXT("FAILED")) - ] - + SVerticalBox::Slot() - [ - SNew(SSpacer) - ] - ] - ]; -} - - -void -FHoudiniPDGDetails::AddTOPNetworkWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink ) -{ - if (!InPDGAssetLink->GetSelectedTOPNetwork()) - return; - - if (InPDGAssetLink->AllTOPNetworks.Num() <= 0) - return; - - TOPNetworksPtr.Reset(); - - FString GroupLabel = TEXT("TOP Networks"); - IDetailGroup& TOPNetWorkGrp = InPDGCategory.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), false, true); - - // Combobox: TOP Network - { - FDetailWidgetRow& PDGTOPNetRow = TOPNetWorkGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGTOPNetRow, InPDGAssetLink); - PDGTOPNetRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Network"))) - ]; - - // Fill the TOP Networks SharedString array - TOPNetworksPtr.SetNum(InPDGAssetLink->AllTOPNetworks.Num()); - for(int32 Idx = 0; Idx < InPDGAssetLink->AllTOPNetworks.Num(); Idx++) - { - const UTOPNetwork* Network = InPDGAssetLink->AllTOPNetworks[Idx]; - if (!IsValid(Network)) - { - TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( - Idx, - TEXT("Invalid"), - TEXT("Invalid") - )); - } - else - { - TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( - Idx, - FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Network->NodeName, Network->NodePath), - Network->NodePath - )); - } - } - - if(TOPNetworksPtr.Num() <= 0) - TOPNetworksPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, "----"))); - - // Lambda for selecting another TOPNet - auto OnTOPNetChanged = [InPDGAssetLink](TSharedPtr InNewChoice) - { - if (!InNewChoice.IsValid()) - return; - - const int32 NewChoice = InNewChoice->Value; - int32 NewSelectedIndex = -1; - if (InPDGAssetLink->AllTOPNetworks.IsValidIndex(NewChoice)) - NewSelectedIndex = NewChoice; - - if (InPDGAssetLink->SelectedTOPNetworkIndex == NewSelectedIndex) - return; - - if (NewSelectedIndex < 0) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->SelectedTOPNetworkIndex = NewSelectedIndex; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex), InPDGAssetLink); - }; - - TSharedPtr HorizontalBoxTOPNet; - TSharedPtr>> ComboBoxTOPNet; - int32 SelectedIndex = TOPNetworksPtr.IndexOfByPredicate([InPDGAssetLink](const TSharedPtr& InEntry) - { - return InEntry.IsValid() && InEntry->Value == InPDGAssetLink->SelectedTOPNetworkIndex; - }); - if (SelectedIndex < 0) - SelectedIndex = 0; - - PDGTOPNetRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .FillWidth(300.f) - .MaxWidth(300.f) - [ - SAssignNew(ComboBoxTOPNet, SComboBox>) - .OptionsSource(&TOPNetworksPtr) - .InitiallySelectedItem(TOPNetworksPtr[SelectedIndex]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); - const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryToolTip) - .Margin(2.0f) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnTOPNetChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnTOPNetChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString(InPDGAssetLink->GetSelectedTOPNetworkName()); - }) - .ToolTipText_Lambda([InPDGAssetLink]() - { - UTOPNetwork const * const Network = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(Network)) - { - if (!Network->NodePath.IsEmpty()) - return FText::FromString(Network->NodePath); - else - return FText::FromString(Network->NodeName); - } - else - { - return FText(); - } - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - } - - // Buttons: DIRTY ALL / COOK OUTPUT - { - TSharedRef DirtyAllHBox = SNew(SHorizontalBox); - TSharedPtr CookOutHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("DirtyAll", "Dirty All")) - .ToolTipText(LOCTEXT("DirtyAllTooltip", "Dirty all TOP nodes in the selected TOP network and clears all of its work item results.")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork()); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(TOPNetwork)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - FHoudiniPDGManager::DirtyAll(TOPNetwork); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - else - { - UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(TOPNetwork); - } - } - } - - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(DirtyAllHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("CookOut", "Cook Output")) - .ToolTipText(LOCTEXT("CookOutTooltip", "Cooks the output nodes of the selected TOP network")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!IsValid(SelectedTOPNet)) - return false; - - // Disable if there any nodes in the network that are already cooking - return !SelectedTOPNet->AnyWorkItemsPending(); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::CookOutput(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CookOutHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr DirtyAllIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyAllIconBrush(); - if (DirtyAllIconBrush.IsValid()) - { - TSharedPtr DirtyAllImage; - DirtyAllHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(DirtyAllImage, SImage) - ] - ]; - - DirtyAllImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([DirtyAllIconBrush]() { return DirtyAllIconBrush.Get(); }))); - } - - DirtyAllHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("DirtyAll", "Dirty All")) - ]; - - TSharedPtr CookOutIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - if (CookOutIconBrush.IsValid()) - { - TSharedPtr CookOutImage; - CookOutHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookOutImage, SImage) - ] - ]; - - CookOutImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookOutIconBrush]() { return CookOutIconBrush.Get(); }))); - } - - CookOutHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookOut", "Cook Output")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: PAUSE COOK / CANCEL COOK - { - TSharedRef PauseHBox = SNew(SHorizontalBox); - TSharedPtr CancelHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Pause", "Pause Cook")) - .ToolTipText(LOCTEXT("PauseTooltip", "Pauses cooking for the selected TOP Network")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::PauseCook(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(PauseHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Cancel", "Cancel Cook")) - .ToolTipText(LOCTEXT("CancelTooltip", "Cancels cooking the selected TOP network")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::CancelCook(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CancelHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr PauseIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGPauseIconBrush(); - if (PauseIconBrush.IsValid()) - { - TSharedPtr PauseImage; - PauseHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(PauseImage, SImage) - ] - ]; - - PauseImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([PauseIconBrush]() { return PauseIconBrush.Get(); }))); - } - - PauseHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Pause", "Pause Cook")) - ]; - - TSharedPtr CancelIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGCancelIconBrush(); - if (CancelIconBrush.IsValid()) - { - TSharedPtr CancelImage; - CancelHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CancelImage, SImage) - ] - ]; - - CancelImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CancelIconBrush]() { return CancelIconBrush.Get(); }))); - } - - CancelHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Cancel", "Cancel Cook")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: Unload Work Item Objects - { - FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork(); }) - .WidthOverride(200.0f) - [ - SNew(SButton) - .Text(LOCTEXT("UnloadWorkItemsForNetwork", "Unload All Work Item Objects")) - .ToolTipText(LOCTEXT("UnloadWorkItemsForNetworkTooltip", "Unloads / removes loaded work item results from level for all nodes in this network. Not undoable: use the \"Load Work Item Objects\" button on the individual TOP nodes to reload work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNetwork* const SelectedNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!IsValid(SelectedNet) || - INDEX_NONE == SelectedNet->AllTOPNodes.IndexOfByPredicate([](const UTOPNode* InNode) { return IsValid(InNode) && InNode->bCachedHaveLoadedWorkResults; })) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNetwork* const TOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(TOPNet)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - // Set the state to ToDelete, PDGManager will delete it when processing work items - TOPNet->SetLoadedWorkResultsToDelete(); - } - else - { - // Delete and unload the result objects and actors now - TOPNet->DeleteWorkResultOutputObjects(); - } - } - } - - return FReply::Handled(); - }) - ] - ] - ]; - } - - // TOP NODE WIDGETS - FHoudiniPDGDetails::AddTOPNodeWidget(TOPNetWorkGrp, InPDGAssetLink); -} - -bool -FHoudiniPDGDetails::GetSelectedTOPNodeStatusAndColor(UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor) -{ - OutTOPNodeStatus = FString(); - OutTOPNodeStatusColor = FLinearColor::White; - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode) && !TOPNode->bHidden) - { - OutTOPNodeStatus = UHoudiniPDGAssetLink::GetTOPNodeStatus(TOPNode); - OutTOPNodeStatusColor = UHoudiniPDGAssetLink::GetTOPNodeStatusColor(TOPNode); - - return true; - } - } - - return false; -} - -void -FHoudiniPDGDetails::AddTOPNodeWidget( - IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink ) -{ - if (!InPDGAssetLink->GetSelectedTOPNetwork()) - return; - - FString GroupLabel = TEXT("TOP Nodes"); - IDetailGroup& TOPNodesGrp = InGroup.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), true); - - // Combobox: TOP Node - { - FDetailWidgetRow& PDGTOPNodeRow = TOPNodesGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGTOPNodeRow, InPDGAssetLink); - PDGTOPNodeRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node"))) - ]; - - // Update the TOP Node SharedString - TOPNodesPtr.Reset(); - TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, LOCTEXT("ComboBoxEntryNoSelectedTOPNode", "- Select -").ToString()))); - const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNet)) - { - const int32 NumTOPNodes = SelectedTOPNet->AllTOPNodes.Num(); - for (int32 Idx = 0; Idx < NumTOPNodes; Idx++) - { - const UTOPNode* const Node = SelectedTOPNet->AllTOPNodes[Idx]; - if (!IsValid(Node) || Node->bHidden) - continue; - - TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip( - Idx, - FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Node->NodeName, Node->NodePath), - Node->NodePath - ))); - } - } - - FString NodeErrorText = FString(); - FString NodeErrorTooltip = FString(); - FLinearColor NodeErrorColor = FLinearColor::White; - if (!IsValid(SelectedTOPNet) || SelectedTOPNet->AllTOPNodes.Num() <= 0) - { - NodeErrorText = TEXT("No valid TOP Node found!"); - NodeErrorTooltip = TEXT("There is no valid TOP Node found in the selected TOP Network!"); - NodeErrorColor = FLinearColor::Red; - } - else if(TOPNodesPtr.Num() <= 0) - { - NodeErrorText = TEXT("No visible TOP Node found!"); - NodeErrorTooltip = TEXT("No visible TOP Node found, all nodes in this network are hidden. Please update your TOP Node Filter."); - NodeErrorColor = FLinearColor::Yellow; - } - - // Lambda for selecting a TOPNode - auto OnTOPNodeChanged = [InPDGAssetLink](TSharedPtr InNewChoice) - { - UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!InNewChoice.IsValid() || !IsValid(TOPNetwork)) - return; - - const int32 NewChoice = InNewChoice->Value; - int32 NewSelectedIndex = INDEX_NONE; - if (TOPNetwork->AllTOPNodes.IsValidIndex(NewChoice)) - NewSelectedIndex = NewChoice; - - if (TOPNetwork->SelectedTOPIndex != NewSelectedIndex) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNetwork); - - TOPNetwork->Modify(); - TOPNetwork->SelectedTOPIndex = NewSelectedIndex; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UTOPNetwork, SelectedTOPIndex), TOPNetwork); - } - }; - - TSharedPtr HorizontalBoxTOPNode; - TSharedPtr>> ComboBoxTOPNode; - int32 SelectedIndex = 0; - UTOPNetwork* const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNetwork) && SelectedTOPNetwork->SelectedTOPIndex >= 0) - { - //SelectedIndex = InPDGAssetLink->GetSelectedTOPNetwork()->SelectedTOPIndex; - - // We need to match the selection by the index in the AllTopNodes array - // Because of the nodefilter, it is possible that the selected index does not match the index in TOPNodesPtr - const int32 SelectedTOPNodeIndex = SelectedTOPNetwork->SelectedTOPIndex; - // Find the matching UI index - for (int32 UIIndex = 0; UIIndex < TOPNodesPtr.Num(); UIIndex++) - { - if (TOPNodesPtr[UIIndex] && TOPNodesPtr[UIIndex]->Value == SelectedTOPNodeIndex) - { - // We found the UI Index that matches the current TOP Node! - SelectedIndex = UIIndex; - break; - } - } - } - - TSharedPtr ErrorText; - - PDGTOPNodeRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .FillWidth(300.f) - .MaxWidth(300.f) - [ - SAssignNew(ComboBoxTOPNode, SComboBox>) - .OptionsSource(&TOPNodesPtr) - .InitiallySelectedItem(TOPNodesPtr[SelectedIndex]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); - const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryToolTip) - .Margin(2.0f) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnTOPNodeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnTOPNodeChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink, ComboBoxTOPNode, Options = TOPNodesPtr]() - { - if (IsValid(InPDGAssetLink)) - return FText::FromString(InPDGAssetLink->GetSelectedTOPNodeName()); - else - return FText(); - }) - .ToolTipText_Lambda([InPDGAssetLink]() - { - UTOPNode const * const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (!TOPNode->NodePath.IsEmpty()) - return FText::FromString(TOPNode->NodePath); - else - return FText::FromString(TOPNode->NodeName); - } - else - { - return FText(); - } - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .AutoWidth() - [ - SAssignNew(ErrorText, STextBlock) - .Text(FText::FromString(NodeErrorText)) - .ToolTipText(FText::FromString(NodeErrorText)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .ColorAndOpacity(FLinearColor::Red) - //.ShadowColorAndOpacity(FLinearColor::Black) - ]; - - // Update the error text if needed - ErrorText->SetText(FText::FromString(NodeErrorText)); - ErrorText->SetToolTipText(FText::FromString(NodeErrorTooltip)); - ErrorText->SetColorAndOpacity(NodeErrorColor); - - // Hide the combobox if we have an error - ComboBoxTOPNode->SetVisibility(NodeErrorText.IsEmpty() ? EVisibility::Visible : EVisibility::Hidden); - } - - // TOP Node State - { - FDetailWidgetRow& PDGNodeStateResultRow = TOPNodesGrp.AddWidgetRow(); - DisableIfPDGNotLinked(PDGNodeStateResultRow, InPDGAssetLink); - PDGNodeStateResultRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node State"))) - ]; - - PDGNodeStateResultRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - FString TOPNodeStatus = FString(); - FLinearColor TOPNodeStatusColor = FLinearColor::White; - GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); - return FText::FromString(TOPNodeStatus); - }) - .ColorAndOpacity_Lambda([InPDGAssetLink]() - { - FString TOPNodeStatus = FString(); - FLinearColor TOPNodeStatusColor = FLinearColor::White; - GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); - return FSlateColor(TOPNodeStatusColor); - }) - ]; - } - - // Checkbox: Load Work Item Output Files - { - auto ToolTipLambda = [InPDGAssetLink]() - { - bool bDisabled = false; - if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) - { - bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; - } - - return bDisabled - ? FText::FromString(TEXT("This node has child nodes, the auto-load setting must be set on the child nodes individually.")) - : FText::FromString(TEXT("When enabled, Output files produced by this TOP Node's Work Items will automatically be loaded when cooked.")); - }; - FDetailWidgetRow& PDGNodeAutoLoadRow = TOPNodesGrp.AddWidgetRow(); - - DisableIfPDGNotLinked(PDGNodeAutoLoadRow, InPDGAssetLink); - PDGNodeAutoLoadRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) - return true; - return false; - })); - - PDGNodeAutoLoadRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Auto-Load Work Item Output Files"))) - .ToolTipText_Lambda(ToolTipLambda) - ]; - - TSharedPtr AutoLoadCheckBox; - - PDGNodeAutoLoadRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(AutoLoadCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->GetSelectedTOPNode() - ? (InPDGAssetLink->GetSelectedTOPNode()->bAutoLoad ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - UTOPNode* TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(TOPNode) || TOPNode->bAutoLoad == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNode); - - TOPNode->Modify(); - TOPNode->bAutoLoad = bNewState; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UTOPNode, bAutoLoad), TOPNode); - - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - }) - .ToolTipText_Lambda(ToolTipLambda) - ]; - } - - // Checkbox: Work Item Output Files Visible - { - auto ToolTipLambda = [InPDGAssetLink]() - { - bool bDisabled = false; - if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) - { - bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; - } - - return bDisabled - ? FText::FromString(TEXT("This node has child nodes, visibility of work item outputs must be set on the child nodes individually.")) - : FText::FromString(TEXT("Toggles the visibility of the actors created from this TOP Node's Work Item File Outputs.")); - }; - - FDetailWidgetRow& PDGNodeShowResultRow = TOPNodesGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGNodeShowResultRow, InPDGAssetLink); - PDGNodeShowResultRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) - return true; - - return false; - })); - PDGNodeShowResultRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Work Item Output Files Visible"))) - .ToolTipText_Lambda(ToolTipLambda) - ]; - - TSharedPtr ShowResCheckBox; - PDGNodeShowResultRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(ShowResCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->GetSelectedTOPNode() - ? (InPDGAssetLink->GetSelectedTOPNode()->IsVisibleInLevel() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(TOPNode) || TOPNode->IsVisibleInLevel() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNode); - - TOPNode->Modify(); - TOPNode->SetVisibleInLevel(bNewState); - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(TEXT("bShow"), TOPNode); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - }) - .ToolTipText_Lambda(ToolTipLambda) - ]; - } - - // Buttons: DIRTY NODE / COOK NODE - { - TSharedRef DirtyHBox = SNew(SHorizontalBox); - TSharedPtr CookHBox = SNew(SHorizontalBox); - - TSharedPtr DirtyButton; - TSharedPtr CookButton; - - FDetailWidgetRow& PDGDirtyCookRow = TOPNodesGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); - }) - .WidthOverride(200.0f) - [ - SAssignNew(DirtyButton, SButton) - //.Text(LOCTEXT("DirtyNode", "Dirty Node")) - .ToolTipText(LOCTEXT("DirtyNodeTooltip", "Dirties the selected TOP node and clears its work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - FHoudiniPDGManager::DirtyTOPNode(TOPNode); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - else - { - UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(TOPNode); - } - } - } - - return FReply::Handled(); - }) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNode()) && !InPDGAssetLink->GetSelectedTOPNode()->bHidden) - return true; - return false; - }) - .Content() - [ - SAssignNew(DirtyHBox, SHorizontalBox) - ] - ] - ] - // + SHorizontalBox::Slot() - // .AutoWidth() - // [ - // SNew(SBox) - // .WidthOverride(200.0f) - // [ - // SAssignNew(DirtyButton, SButton) - // .Text(LOCTEXT("DirtyAllTasks", "Dirty All Tasks")) - // .ToolTipText(LOCTEXT("DirtyAllTasksTooltip", "Dirties all tasks/work items on the selected TOP node.")) - // .ContentPadding(FMargin(5.0f, 2.0f)) - // .VAlign(VAlign_Center) - // .HAlign(HAlign_Center) - // .OnClicked_Lambda([InPDGAssetLink]() - // { - // if(InPDGAssetLink->GetSelectedTOPNode()) - // { - // FHoudiniPDGManager::DirtyAllTasksOfTOPNode(*(InPDGAssetLink->GetSelectedTOPNode())); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - // } - // - // return FReply::Handled(); - // }) - // ] - // ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(200.0f) - [ - SAssignNew(CookButton, SButton) - //.Text(LOCTEXT("CookNode", "Cook Node")) - .ToolTipText(LOCTEXT("CookNodeTooltip", "Cooks the selected TOP Node.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode)) - return false; - // Disable Cook Node button if the node is already cooking - return !SelectedNode->bHidden && SelectedNode->NodeState != EPDGNodeState::Cooking && !SelectedNode->AnyWorkItemsPending(); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node)) - { - FHoudiniPDGManager::CookTOPNode(Node); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CookHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr DirtyIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyNodeIconBrush(); - if (DirtyIconBrush.IsValid()) - { - TSharedPtr DirtyImage; - DirtyHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(DirtyImage, SImage) - ] - ]; - - DirtyImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([DirtyIconBrush]() { return DirtyIconBrush.Get(); }))); - } - - DirtyHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("DirtyNode", "Dirty Node")) - ]; - - TSharedPtr CookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - if (CookIconBrush.IsValid()) - { - TSharedPtr CookImage; - CookHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookImage, SImage) - ] - ]; - - CookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookIconBrush]() { return CookIconBrush.Get(); }))); - } - - CookHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookNode", "Cook Node")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: Load Work Item Objects / Unload Work Item Objects - { - TSharedPtr UnloadWorkItemsButton; - TSharedPtr LoadWorkItemsButton; - - FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNodesGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode()); - }) - .WidthOverride(200.0f) - [ - SAssignNew(UnloadWorkItemsButton, SButton) - .Text(LOCTEXT("UnloadWorkItemsForNode", "Unload Work Item Objects")) - .ToolTipText(LOCTEXT("UnloadWorkItemsForNodeTooltip", "Unloads / removes loaded work item results from level. Not undoable: use the \"Load Work Item Objects\" button to reload the results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveLoadedWorkResults) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - // Set the state to ToDelete, PDGManager will delete it when processing work items - TOPNode->SetLoadedWorkResultsToDelete(); - } - else - { - // Delete and unload the result objects and actors now - TOPNode->DeleteWorkResultOutputObjects(); - } - } - } - - return FReply::Handled(); - }) - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(200.0f) - [ - SAssignNew(LoadWorkItemsButton, SButton) - .Text(LOCTEXT("LoadWorkItems", "Load Work Item Objects")) - .ToolTipText(LOCTEXT("LoadWorkItemsForNodeTooltip", "Loads any available but not loaded work items objects (this could include items from a previous cook). Creates output actors. Not undoable: use the \"Unload Work Item Objects\" button to unload/remove loaded work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveNotLoadedWorkResults) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(SelectedNode)) - { - SelectedNode->SetNotLoadedWorkResultsToLoad(true); - } - } - return FReply::Handled(); - }) - ] - ] - ]; - } - - // TOP Node WorkItem Status - { - if (InPDGAssetLink->GetSelectedTOPNode()) - { - FDetailWidgetRow& PDGNodeWorkItemStatsRow = TOPNodesGrp.AddWidgetRow(); - DisableIfPDGNotLinked(PDGNodeWorkItemStatsRow, InPDGAssetLink); - FHoudiniPDGDetails::AddWorkItemStatusWidget( - PDGNodeWorkItemStatsRow, TEXT("TOP Node Work Item Status"), InPDGAssetLink, true); - } - } -} - -void -FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // Repopulate the network and nodes for the assetlink - if (!FHoudiniPDGManager::UpdatePDGAssetLink(InPDGAssetLink)) - return; - - FHoudiniPDGDetails::RefreshUI(InPDGAssetLink, true); -} - -void -FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // Update the workitem stats - InPDGAssetLink->UpdateWorkItemTally(); - - // Update the editor properties - FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, InFullUpdate); -} - -void -FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); - - if (!InPDGAssetLink->bBakeMenuExpanded) - return; - - auto OnBakeButtonClickedLambda = [InPDGAssetLink]() - { - switch (InPDGAssetLink->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(InPDGAssetLink); - // else - FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink); - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceWithBlueprint(InPDGAssetLink); - // else - FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink); - } - break; - // - // case EHoudiniEngineBakeOption::ToFoliage: - // { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(InPDGAssetLink); - // else - // FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InPDGAssetLink); - // } - // break; - // - // case EHoudiniEngineBakeOption::ToWorldOutliner: - // { - // if (InPDGAssetLink->bIsReplace) - // { - // // Todo - // } - // else - // { - // //Todo - // } - // } - // break; - } - - return FReply::Handled(); - }; - - auto OnBakeFolderTextCommittedLambda = [InPDGAssetLink](const FText& Val, ETextCommit::Type TextCommitType) - { - FString NewPathStr = Val.ToString(); - if (NewPathStr.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - //Todo? Check if the new Bake folder path is valid - InPDGAssetLink->Modify(); - InPDGAssetLink->BakeFolder.Path = NewPathStr; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, BakeFolder), InPDGAssetLink); - }; - - // Button Row - FDetailWidgetRow & ButtonRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(ButtonRow, InPDGAssetLink); - - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - - // Bake Button - TSharedRef BakeHBox = SNew(SHorizontalBox); - TSharedPtr BakeButton; - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(15.f, 0.0f, 0.0f, 0.0f) - .MaxWidth(75.0f) - [ - SNew(SBox) - .WidthOverride(75.0f) - [ - SAssignNew(BakeButton, SButton) - //.Text(FText::FromString("Bake")) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - //.ToolTipText(LOCTEXT("HoudiniPDGDetailsBakeButton", "Bake the Houdini PDG TOP Node(s)")) - .ToolTipText_Lambda([InPDGAssetLink]() - { - switch (InPDGAssetLink->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - return LOCTEXT( - "HoudiniEnginePDGBakeButtonBakeToActorToolTip", - "Bake this Houdini PDG Asset's output assets and seperate the output actors from the PDG asset link."); - } - break; - case EHoudiniEngineBakeOption::ToBlueprint: - { - return LOCTEXT( - "HoudiniEnginePDGBakeButtonBakeToBlueprintToolTip", - "Bake this Houdini PDG Asset's output assets to blueprints and remove temporary output actors that no " - "longer has output components from the PDG asset link."); - } - break; - default: - { - return FText(); - } - } - }) - .Visibility(EVisibility::Visible) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda(OnBakeButtonClickedLambda) - .Content() - [ - SAssignNew(BakeHBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); - if (BakeIconBrush.IsValid()) - { - TSharedPtr BakeImage; - BakeHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(BakeImage, SImage) - ] - ]; - - BakeImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([BakeIconBrush]() { return BakeIconBrush.Get(); }))); - } - - BakeHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Bake")) - ]; - - // bake Type ComboBox - TSharedPtr>> TypeComboBox; - - TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeTypeOptionsLabels(); - TSharedPtr IntialSelec; - if (OptionSource) - { - // IntialSelec = (*OptionSource)[(int)InPDGAssetLink->HoudiniEngineBakeOption]; - const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption); - const TSharedPtr* DefaultOption = OptionSource->FindByPredicate( - [DefaultStr](TSharedPtr InStringPtr) - { - return InStringPtr.IsValid() && *InStringPtr == DefaultStr; - } - ); - if (DefaultOption) - IntialSelec = *DefaultOption; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(93.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(93.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid() || SelectType == ESelectInfo::Type::Direct) - return; - - const EHoudiniEngineBakeOption NewOption = - FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->HoudiniEngineBakeOption) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->HoudiniEngineBakeOption = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink, TypeComboBox, OptionSource]() - { - return FText::FromString(FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - // bake selection ComboBox - TSharedPtr>> BakeSelectionComboBox; - - TArray>* PDGBakeSelectionOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeSelectionOptionsLabels(); - TSharedPtr PDGBakeSelectionIntialSelec; - if (PDGBakeSelectionOptionSource) - { - PDGBakeSelectionIntialSelec = (*PDGBakeSelectionOptionSource)[(int)InPDGAssetLink->PDGBakeSelectionOption]; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(163.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(163.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(PDGBakeSelectionOptionSource) - .InitiallySelectedItem(PDGBakeSelectionIntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EPDGBakeSelectionOption NewOption = - FHoudiniEngineEditor::Get().StringToPDGBakeSelectionOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->PDGBakeSelectionOption) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->PDGBakeSelectionOption = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromPDGBakeTargetOption(InPDGAssetLink->PDGBakeSelectionOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; - - // Bake package replacement mode row - FDetailWidgetRow & BakePackageReplaceRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(BakePackageReplaceRow, InPDGAssetLink); - - TSharedRef BakePackageReplaceRowHorizontalBox = SNew(SHorizontalBox); - - BakePackageReplaceRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeLabel", "Replace Mode")) - .ToolTipText( - LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeTooltip", "Replacement mode " - "during baking. Create new assets, using numerical suffixes in package names when necessary, or " - "replace existing assets with matching names. Also replaces the previous bake's output actors in " - "replacement mode vs creating incremental ones.")) - ] - ]; - - // bake package replace mode ComboBox - TSharedPtr>> BakePackageReplaceModeComboBox; - - TArray>* PDGBakePackageReplaceModeOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels(); - TSharedPtr PDGBakePackageReplaceModeInitialSelec; - if (PDGBakePackageReplaceModeOptionSource) - { - const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode); - const TSharedPtr* DefaultOption = PDGBakePackageReplaceModeOptionSource->FindByPredicate( - [DefaultStr](TSharedPtr InStringPtr) - { - return InStringPtr.IsValid() && *InStringPtr == DefaultStr; - } - ); - if (DefaultOption) - PDGBakePackageReplaceModeInitialSelec = *DefaultOption; - } - - BakePackageReplaceRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(163.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(163.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(PDGBakePackageReplaceModeOptionSource) - .InitiallySelectedItem(PDGBakePackageReplaceModeInitialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - const FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EPDGBakePackageReplaceModeOption NewOption = - FHoudiniEngineEditor::Get().StringToPDGBakePackageReplaceModeOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->PDGBakePackageReplaceMode) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->PDGBakePackageReplaceMode = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - BakePackageReplaceRow.WholeRowWidget.Widget = BakePackageReplaceRowHorizontalBox; - - // Bake Folder Row - FDetailWidgetRow & BakeFolderRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(BakeFolderRow, InPDGAssetLink); - - TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) - ] - ]; - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .MaxWidth(235.0) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) - .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) - .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) - ] - ]; - - BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; - - // Add additional bake options - FDetailWidgetRow & AdditionalBakeSettingsRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); - - TSharedPtr CheckBoxAutoBake; - TSharedPtr CheckBoxRecenterBakedActors; - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bRecenterBakedActors = bNewState; - - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bRecenterBakedActors), InPDGAssetLink); - }) - ] - ]; - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxAutoBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake work result object as they are loaded.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bBakeAfterWorkResultObjectLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bBakeAfterWorkResultObjectLoaded = bNewState; - - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterWorkResultObjectLoaded), InPDGAssetLink); - }) - ] - ]; - - AdditionalBakeSettingsRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText) - : Text(InText) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText, const FString &InToolTip) - : Text(InText) - , ToolTip(InToolTip) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText) - : Text(InText) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText, FString&& InToolTip) - : Text(InText) - , ToolTip(InToolTip) - , Value(InValue) -{ -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPDGManager.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetActor.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniEngineDetails.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "IDetailGroup.h" +#include "IDetailCustomization.h" +#include "PropertyCustomizationHelpers.h" +#include "DetailWidgetRow.h" +#include "ScopedTransaction.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SSpacer.h" +#include "Framework/SlateDelegates.h" +#include "Templates/SharedPointer.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define HOUDINI_ENGINE_UI_SECTION_PDG_BAKE 2 + +void +FHoudiniPDGDetails::CreateWidget( + IDetailCategoryBuilder& HouPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // PDG ASSET + FHoudiniPDGDetails::AddPDGAssetWidget(HouPDGCategory, InPDGAssetLink); + + // TOP NETWORKS + FHoudiniPDGDetails::AddTOPNetworkWidget(HouPDGCategory, InPDGAssetLink); + + // PDG EVENT MESSAGES +} + + +void +FHoudiniPDGDetails::AddPDGAssetWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // PDG STATUS ROW + AddPDGAssetStatus(InPDGCategory, InPDGAssetLink); + + // Commandlet Status row + AddPDGCommandletStatus(InPDGCategory, FHoudiniEngine::Get().GetPDGCommandletStatus()); + + // REFRESH / RESET Buttons + { + TSharedRef RefreshHBox = SNew(SHorizontalBox); + TSharedPtr ResetHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGRefreshResetRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Refresh", "Refresh")) + .ToolTipText(LOCTEXT("RefreshTooltip", "Refreshes infos displayed by the the PDG Asset Link")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked_Lambda([InPDGAssetLink]() + { + FHoudiniPDGDetails::RefreshPDGAssetLink(InPDGAssetLink); + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(RefreshHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Reset", "Reset")) + .ToolTipText(LOCTEXT("ResetTooltip", "Resets the PDG Asset Link")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked_Lambda([InPDGAssetLink]() + { + // TODO: RESET USELESS? + FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(ResetHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr RefreshIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGRefreshIconBrush(); + if (RefreshIconBrush.IsValid()) + { + TSharedPtr RefreshImage; + RefreshHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RefreshImage, SImage) + ] + ]; + + RefreshImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([RefreshIconBrush]() { return RefreshIconBrush.Get(); }))); + } + + RefreshHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Refresh", "Refresh")) + ]; + + TSharedPtr ResetIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGResetIconBrush(); + if (ResetIconBrush.IsValid()) + { + TSharedPtr ResetImage; + ResetHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(ResetImage, SImage) + ] + ]; + + ResetImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([ResetIconBrush]() { return ResetIconBrush.Get(); }))); + } + + ResetHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Reset", "Reset")) + ]; + } + + // TOP NODE FILTER + { + FText Tooltip = FText::FromString(TEXT("When enabled, the TOP Node Filter will only display the TOP Nodes found in the current network that start with the filter prefix. Disabling the Filter will display all of the TOP Network's TOP Nodes.")); + // Lambda for changing the filter value + auto ChangeTOPNodeFilter = [InPDGAssetLink](const FString& NewValue) + { + if (InPDGAssetLink->TOPNodeFilter.Equals(NewValue)) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->TOPNodeFilter = NewValue; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter), InPDGAssetLink); + }; + + FDetailWidgetRow& PDGFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGFilterRow, InPDGAssetLink); + PDGFilterRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox enable filter + SNew(SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bUseTOPNodeFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (InPDGAssetLink->bUseTOPNodeFilter == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bUseTOPNodeFilter = bNewState; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node Filter"))) + .ToolTipText(Tooltip) + ]; + + PDGFilterRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .ToolTipText(Tooltip) + .Text_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return FText(); + return FText::FromString(InPDGAssetLink->TOPNodeFilter); + }) + .OnTextCommitted_Lambda([ChangeTOPNodeFilter](const FText& Val, ETextCommit::Type TextCommitType) + { + ChangeTOPNodeFilter(Val.ToString()); + }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([=]() + { + FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER); + ChangeTOPNodeFilter(DefaultFilter); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + } + + // TOP OUTPUT FILTER + { + // Lambda for changing the filter value + FText Tooltip = FText::FromString(TEXT("When enabled, the Work Item Output Files created for the TOP Nodes found in the current network that start with the filter prefix will be automatically loaded int the world after being cooked.")); + auto ChangeTOPOutputFilter = [InPDGAssetLink](const FString& NewValue) + { + if (InPDGAssetLink->TOPOutputFilter.Equals(NewValue)) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->TOPOutputFilter = NewValue; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter), InPDGAssetLink); + }; + + FDetailWidgetRow& PDGOutputFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGOutputFilterRow, InPDGAssetLink); + + PDGOutputFilterRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox enable filter + SNew(SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bUseTOPOutputFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (InPDGAssetLink->bUseTOPOutputFilter == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bUseTOPOutputFilter = bNewState; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Output Filter"))) + .ToolTipText(Tooltip) + ]; + + PDGOutputFilterRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return FText(); + return FText::FromString(InPDGAssetLink->TOPOutputFilter); + }) + .OnTextCommitted_Lambda([ChangeTOPOutputFilter](const FText& Val, ETextCommit::Type TextCommitType) + { + ChangeTOPOutputFilter(Val.ToString()); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([ChangeTOPOutputFilter]() + { + FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER); + ChangeTOPOutputFilter(DefaultFilter); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + } + + // Checkbox: Autocook + { + FText Tooltip = FText::FromString(TEXT("When enabled, the selected TOP Network's output will automatically cook after succesfully cooking the PDG Asset Link HDA.")); + FDetailWidgetRow& PDGAutocookRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGAutocookRow, InPDGAssetLink); + PDGAutocookRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Auto-cook"))) + .ToolTipText(Tooltip) + ]; + + TSharedPtr AutoCookCheckBox; + PDGAutocookRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(AutoCookCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bAutoCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (!InPDGAssetLink || InPDGAssetLink->bAutoCook == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bAutoCook = bNewState; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bAutoCook), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ]; + } + // Output parent actor selector + { + IDetailPropertyRow* PDGOutputParentActorRow = InPDGCategory.AddExternalObjectProperty({ InPDGAssetLink }, "OutputParentActor"); + if (PDGOutputParentActorRow) + { + TAttribute PDGOutputParentActorRowEnabled; + BindDisableIfPDGNotLinked(PDGOutputParentActorRowEnabled, InPDGAssetLink); + PDGOutputParentActorRow->IsEnabled(PDGOutputParentActorRowEnabled); + TSharedPtr NameWidget; + TSharedPtr ValueWidget; + PDGOutputParentActorRow->GetDefaultWidgets(NameWidget, ValueWidget); + PDGOutputParentActorRow->DisplayName(FText::FromString(TEXT("Output Parent Actor"))); + PDGOutputParentActorRow->ToolTip(FText::FromString( + TEXT("The PDG Output Actors will be created under this parent actor. If not set, then the PDG Output Actors will be created under a new folder."))); + } + } + + // Add bake widgets for PDG output + CreatePDGBakeWidgets(InPDGCategory, InPDGAssetLink); + + // TODO: move this to a better place: the baking code is in HoudiniEngineEditor, the PDG manager (that knows about + // when work object results are loaded is in HoudiniEngine and the PDGAssetLink is in HoudiniEngineRuntime). So + // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? + if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) + InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); + InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject); + + // WORK ITEM STATUS + { + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGStatusRow, InPDGAssetLink); + FHoudiniPDGDetails::AddWorkItemStatusWidget( + PDGStatusRow, TEXT("Asset Work Item Status"), InPDGAssetLink, false); + } +} + +bool +FHoudiniPDGDetails::GetPDGStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor) +{ + OutPDGStatusString = FString(); + OutPDGStatusColor = FLinearColor::White; + + if (!IsValid(InPDGAssetLink)) + return false; + + switch (InPDGAssetLink->LinkState) + { + case EPDGLinkState::Linked: + OutPDGStatusString = TEXT("PDG is READY"); + OutPDGStatusColor = FLinearColor::Green; + break; + case EPDGLinkState::Linking: + OutPDGStatusString = TEXT("PDG is Linking"); + OutPDGStatusColor = FLinearColor::Yellow; + break; + case EPDGLinkState::Error_Not_Linked: + OutPDGStatusString = TEXT("PDG is ERRORED"); + OutPDGStatusColor = FLinearColor::Red; + break; + case EPDGLinkState::Inactive: + OutPDGStatusString = TEXT("PDG is INACTIVE"); + OutPDGStatusColor = FLinearColor::White; + break; + default: + return false; + } + + return true; +} + +void +FHoudiniPDGDetails::AddPDGAssetStatus( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink) +{ + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + FString PDGStatusString; + FLinearColor PDGStatusColor; + GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); + return FText::FromString(PDGStatusString); + }) + .ColorAndOpacity_Lambda([InPDGAssetLink]() + { + FString PDGStatusString; + FLinearColor PDGStatusColor; + GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); + return FSlateColor(PDGStatusColor); + }) + ] + ]; +} + +void +FHoudiniPDGDetails::GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor) +{ + OutStatusString = FString(); + OutStatusColor = FLinearColor::White; + switch (FHoudiniEngine::Get().GetPDGCommandletStatus()) + { + case EHoudiniBGEOCommandletStatus::Connected: + OutStatusString = TEXT("Async importer is CONNECTED"); + OutStatusColor = FLinearColor::Green; + break; + case EHoudiniBGEOCommandletStatus::Running: + OutStatusString = TEXT("Async importer is Running"); + OutStatusColor = FLinearColor::Yellow; + break; + case EHoudiniBGEOCommandletStatus::Crashed: + OutStatusString = TEXT("Async importer has CRASHED"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniBGEOCommandletStatus::NotStarted: + OutStatusString = TEXT("Async importer is NOT STARTED"); + OutStatusColor = FLinearColor::White; + break; + } +} + +void +FHoudiniPDGDetails::AddPDGCommandletStatus( + IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus) +{ + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Visibility_Lambda([]() + { + const UHoudiniRuntimeSettings* Settings = GetDefault(); + if (IsValid(Settings)) + { + return FHoudiniEngineCommands::IsPDGCommandletEnabled() ? EVisibility::Visible : EVisibility::Collapsed; + } + + return EVisibility::Visible; + }) + .Text_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetPDGCommandletStatus(StatusString, StatusColor); + return FText::FromString(StatusString); + }) + .ColorAndOpacity_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetPDGCommandletStatus(StatusString, StatusColor); + return FSlateColor(StatusColor); + }) + ] + ]; +} + +bool +FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( + UHoudiniPDGAssetLink* InAssetLink, + bool bInForSelectedNode, + const FString& InTallyItemString, + int32& OutValue, + FLinearColor& OutColor) +{ + OutValue = 0; + OutColor = FLinearColor::White; + + if (!IsValid(InAssetLink)) + return false; + + bool bFound = false; + FWorkItemTally* TallyPtr = nullptr; + if (bInForSelectedNode) + { + UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); + if (TOPNode && !TOPNode->bHidden) + TallyPtr = &(TOPNode->WorkItemTally); + } + else + TallyPtr = &(InAssetLink->WorkItemTally); + + if (TallyPtr) + { + if (InTallyItemString == TEXT("WAITING")) + { + // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI + OutValue = TallyPtr->WaitingWorkItems + TallyPtr->ScheduledWorkItems; + OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("COOKING")) + { + OutValue = TallyPtr->CookingWorkItems; + OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("COOKED")) + { + OutValue = TallyPtr->CookedWorkItems; + OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("FAILED")) + { + OutValue = TallyPtr->ErroredWorkItems; + OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; + bFound = true; + } + } + + return bFound; +} + +void +FHoudiniPDGDetails::AddWorkItemStatusWidget( + FDetailWidgetRow& InRow, const FString& InTitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode) +{ + auto AddGridBox = [InAssetLink, bInForSelectedNode](const FString& Title) -> SHorizontalBox::FSlot& + { + return SHorizontalBox::Slot() + .MaxWidth(500.0f) + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(FMargin(1.0f, 2.0f)) + [ + SNew(SBorder) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .BorderBackgroundColor(FSlateColor(FLinearColor(0.6, 0.6, 0.6))) + .Padding(FMargin(1.0f, 5.0f)) + [ + SNew(SBox) + .WidthOverride(95.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(Title)) + .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FSlateColor(Color); + }) + ] + ] + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(FMargin(1.0f, 2.0f)) + [ + SNew(SBorder) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .BorderBackgroundColor(FSlateColor(FLinearColor(0.8, 0.8, 0.8))) + .Padding(FMargin(1.0f, 5.0f)) + [ + SNew(SBox) + .WidthOverride(95.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FText::AsNumber(Value); + }) + .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FSlateColor(Color); + }) + ] + ] + ] + ]; + }; + + InRow.WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f) + .AutoWidth() + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + [ + SNew(SSpacer) + ] + + SVerticalBox::Slot() + .AutoHeight() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(FMargin(0.0f, 2.0f)) + [ + SNew(STextBlock) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .Text(FText::FromString(InTitleString)) + + ] + + SVerticalBox::Slot() + .AutoHeight() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(FMargin(0.0f, 2.0f)) + [ + SNew(SHorizontalBox) + + AddGridBox(TEXT("WAITING")) + + AddGridBox(TEXT("COOKING")) + + AddGridBox(TEXT("COOKED")) + + AddGridBox(TEXT("FAILED")) + ] + + SVerticalBox::Slot() + [ + SNew(SSpacer) + ] + ] + ]; +} + + +void +FHoudiniPDGDetails::AddTOPNetworkWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink ) +{ + if (!InPDGAssetLink->GetSelectedTOPNetwork()) + return; + + if (InPDGAssetLink->AllTOPNetworks.Num() <= 0) + return; + + TOPNetworksPtr.Reset(); + + FString GroupLabel = TEXT("TOP Networks"); + IDetailGroup& TOPNetWorkGrp = InPDGCategory.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), false, true); + + // Combobox: TOP Network + { + FDetailWidgetRow& PDGTOPNetRow = TOPNetWorkGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGTOPNetRow, InPDGAssetLink); + PDGTOPNetRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Network"))) + ]; + + // Fill the TOP Networks SharedString array + TOPNetworksPtr.SetNum(InPDGAssetLink->AllTOPNetworks.Num()); + for(int32 Idx = 0; Idx < InPDGAssetLink->AllTOPNetworks.Num(); Idx++) + { + const UTOPNetwork* Network = InPDGAssetLink->AllTOPNetworks[Idx]; + if (!IsValid(Network)) + { + TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( + Idx, + TEXT("Invalid"), + TEXT("Invalid") + )); + } + else + { + TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( + Idx, + FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Network->NodeName, Network->NodePath), + Network->NodePath + )); + } + } + + if(TOPNetworksPtr.Num() <= 0) + TOPNetworksPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, "----"))); + + // Lambda for selecting another TOPNet + auto OnTOPNetChanged = [InPDGAssetLink](TSharedPtr InNewChoice) + { + if (!InNewChoice.IsValid()) + return; + + const int32 NewChoice = InNewChoice->Value; + int32 NewSelectedIndex = -1; + if (InPDGAssetLink->AllTOPNetworks.IsValidIndex(NewChoice)) + NewSelectedIndex = NewChoice; + + if (InPDGAssetLink->SelectedTOPNetworkIndex == NewSelectedIndex) + return; + + if (NewSelectedIndex < 0) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->SelectedTOPNetworkIndex = NewSelectedIndex; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex), InPDGAssetLink); + }; + + TSharedPtr HorizontalBoxTOPNet; + TSharedPtr>> ComboBoxTOPNet; + int32 SelectedIndex = TOPNetworksPtr.IndexOfByPredicate([InPDGAssetLink](const TSharedPtr& InEntry) + { + return InEntry.IsValid() && InEntry->Value == InPDGAssetLink->SelectedTOPNetworkIndex; + }); + if (SelectedIndex < 0) + SelectedIndex = 0; + + PDGTOPNetRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .FillWidth(300.f) + .MaxWidth(300.f) + [ + SAssignNew(ComboBoxTOPNet, SComboBox>) + .OptionsSource(&TOPNetworksPtr) + .InitiallySelectedItem(TOPNetworksPtr[SelectedIndex]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); + const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryToolTip) + .Margin(2.0f) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnTOPNetChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnTOPNetChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString(InPDGAssetLink->GetSelectedTOPNetworkName()); + }) + .ToolTipText_Lambda([InPDGAssetLink]() + { + UTOPNetwork const * const Network = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(Network)) + { + if (!Network->NodePath.IsEmpty()) + return FText::FromString(Network->NodePath); + else + return FText::FromString(Network->NodeName); + } + else + { + return FText(); + } + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + // Buttons: DIRTY ALL / COOK OUTPUT + { + TSharedRef DirtyAllHBox = SNew(SHorizontalBox); + TSharedPtr CookOutHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("DirtyAll", "Dirty All")) + .ToolTipText(LOCTEXT("DirtyAllTooltip", "Dirty all TOP nodes in the selected TOP network and clears all of its work item results.")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork()); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(TOPNetwork)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + FHoudiniPDGManager::DirtyAll(TOPNetwork); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + else + { + UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(TOPNetwork); + } + } + } + + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(DirtyAllHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("CookOut", "Cook Output")) + .ToolTipText(LOCTEXT("CookOutTooltip", "Cooks the output nodes of the selected TOP network")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNet)) + return false; + + // Disable if there any nodes in the network that are already cooking + return !SelectedTOPNet->AnyWorkItemsPending(); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::CookOutput(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CookOutHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr DirtyAllIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyAllIconBrush(); + if (DirtyAllIconBrush.IsValid()) + { + TSharedPtr DirtyAllImage; + DirtyAllHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(DirtyAllImage, SImage) + ] + ]; + + DirtyAllImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([DirtyAllIconBrush]() { return DirtyAllIconBrush.Get(); }))); + } + + DirtyAllHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("DirtyAll", "Dirty All")) + ]; + + TSharedPtr CookOutIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + if (CookOutIconBrush.IsValid()) + { + TSharedPtr CookOutImage; + CookOutHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookOutImage, SImage) + ] + ]; + + CookOutImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookOutIconBrush]() { return CookOutIconBrush.Get(); }))); + } + + CookOutHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookOut", "Cook Output")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: PAUSE COOK / CANCEL COOK + { + TSharedRef PauseHBox = SNew(SHorizontalBox); + TSharedPtr CancelHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Pause", "Pause Cook")) + .ToolTipText(LOCTEXT("PauseTooltip", "Pauses cooking for the selected TOP Network")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::PauseCook(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(PauseHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Cancel", "Cancel Cook")) + .ToolTipText(LOCTEXT("CancelTooltip", "Cancels cooking the selected TOP network")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::CancelCook(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CancelHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr PauseIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGPauseIconBrush(); + if (PauseIconBrush.IsValid()) + { + TSharedPtr PauseImage; + PauseHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(PauseImage, SImage) + ] + ]; + + PauseImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([PauseIconBrush]() { return PauseIconBrush.Get(); }))); + } + + PauseHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Pause", "Pause Cook")) + ]; + + TSharedPtr CancelIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGCancelIconBrush(); + if (CancelIconBrush.IsValid()) + { + TSharedPtr CancelImage; + CancelHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CancelImage, SImage) + ] + ]; + + CancelImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CancelIconBrush]() { return CancelIconBrush.Get(); }))); + } + + CancelHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Cancel", "Cancel Cook")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: Unload Work Item Objects + { + FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork(); }) + .WidthOverride(200.0f) + [ + SNew(SButton) + .Text(LOCTEXT("UnloadWorkItemsForNetwork", "Unload All Work Item Objects")) + .ToolTipText(LOCTEXT("UnloadWorkItemsForNetworkTooltip", "Unloads / removes loaded work item results from level for all nodes in this network. Not undoable: use the \"Load Work Item Objects\" button on the individual TOP nodes to reload work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNetwork* const SelectedNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!IsValid(SelectedNet) || + INDEX_NONE == SelectedNet->AllTOPNodes.IndexOfByPredicate([](const UTOPNode* InNode) { return IsValid(InNode) && InNode->bCachedHaveLoadedWorkResults; })) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNetwork* const TOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(TOPNet)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + // Set the state to ToDelete, PDGManager will delete it when processing work items + TOPNet->SetLoadedWorkResultsToDelete(); + } + else + { + // Delete and unload the result objects and actors now + TOPNet->DeleteWorkResultOutputObjects(); + } + } + } + + return FReply::Handled(); + }) + ] + ] + ]; + } + + // TOP NODE WIDGETS + FHoudiniPDGDetails::AddTOPNodeWidget(TOPNetWorkGrp, InPDGAssetLink); +} + +bool +FHoudiniPDGDetails::GetSelectedTOPNodeStatusAndColor(UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor) +{ + OutTOPNodeStatus = FString(); + OutTOPNodeStatusColor = FLinearColor::White; + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode) && !TOPNode->bHidden) + { + OutTOPNodeStatus = UHoudiniPDGAssetLink::GetTOPNodeStatus(TOPNode); + OutTOPNodeStatusColor = UHoudiniPDGAssetLink::GetTOPNodeStatusColor(TOPNode); + + return true; + } + } + + return false; +} + +void +FHoudiniPDGDetails::AddTOPNodeWidget( + IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink ) +{ + if (!InPDGAssetLink->GetSelectedTOPNetwork()) + return; + + FString GroupLabel = TEXT("TOP Nodes"); + IDetailGroup& TOPNodesGrp = InGroup.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), true); + + // Combobox: TOP Node + { + FDetailWidgetRow& PDGTOPNodeRow = TOPNodesGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGTOPNodeRow, InPDGAssetLink); + PDGTOPNodeRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node"))) + ]; + + // Update the TOP Node SharedString + TOPNodesPtr.Reset(); + TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, LOCTEXT("ComboBoxEntryNoSelectedTOPNode", "- Select -").ToString()))); + const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNet)) + { + const int32 NumTOPNodes = SelectedTOPNet->AllTOPNodes.Num(); + for (int32 Idx = 0; Idx < NumTOPNodes; Idx++) + { + const UTOPNode* const Node = SelectedTOPNet->AllTOPNodes[Idx]; + if (!IsValid(Node) || Node->bHidden) + continue; + + TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip( + Idx, + FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Node->NodeName, Node->NodePath), + Node->NodePath + ))); + } + } + + FString NodeErrorText = FString(); + FString NodeErrorTooltip = FString(); + FLinearColor NodeErrorColor = FLinearColor::White; + if (!IsValid(SelectedTOPNet) || SelectedTOPNet->AllTOPNodes.Num() <= 0) + { + NodeErrorText = TEXT("No valid TOP Node found!"); + NodeErrorTooltip = TEXT("There is no valid TOP Node found in the selected TOP Network!"); + NodeErrorColor = FLinearColor::Red; + } + else if(TOPNodesPtr.Num() <= 0) + { + NodeErrorText = TEXT("No visible TOP Node found!"); + NodeErrorTooltip = TEXT("No visible TOP Node found, all nodes in this network are hidden. Please update your TOP Node Filter."); + NodeErrorColor = FLinearColor::Yellow; + } + + // Lambda for selecting a TOPNode + auto OnTOPNodeChanged = [InPDGAssetLink](TSharedPtr InNewChoice) + { + UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!InNewChoice.IsValid() || !IsValid(TOPNetwork)) + return; + + const int32 NewChoice = InNewChoice->Value; + int32 NewSelectedIndex = INDEX_NONE; + if (TOPNetwork->AllTOPNodes.IsValidIndex(NewChoice)) + NewSelectedIndex = NewChoice; + + if (TOPNetwork->SelectedTOPIndex != NewSelectedIndex) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNetwork); + + TOPNetwork->Modify(); + TOPNetwork->SelectedTOPIndex = NewSelectedIndex; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UTOPNetwork, SelectedTOPIndex), TOPNetwork); + } + }; + + TSharedPtr HorizontalBoxTOPNode; + TSharedPtr>> ComboBoxTOPNode; + int32 SelectedIndex = 0; + UTOPNetwork* const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNetwork) && SelectedTOPNetwork->SelectedTOPIndex >= 0) + { + //SelectedIndex = InPDGAssetLink->GetSelectedTOPNetwork()->SelectedTOPIndex; + + // We need to match the selection by the index in the AllTopNodes array + // Because of the nodefilter, it is possible that the selected index does not match the index in TOPNodesPtr + const int32 SelectedTOPNodeIndex = SelectedTOPNetwork->SelectedTOPIndex; + // Find the matching UI index + for (int32 UIIndex = 0; UIIndex < TOPNodesPtr.Num(); UIIndex++) + { + if (TOPNodesPtr[UIIndex] && TOPNodesPtr[UIIndex]->Value == SelectedTOPNodeIndex) + { + // We found the UI Index that matches the current TOP Node! + SelectedIndex = UIIndex; + break; + } + } + } + + TSharedPtr ErrorText; + + PDGTOPNodeRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .FillWidth(300.f) + .MaxWidth(300.f) + [ + SAssignNew(ComboBoxTOPNode, SComboBox>) + .OptionsSource(&TOPNodesPtr) + .InitiallySelectedItem(TOPNodesPtr[SelectedIndex]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); + const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryToolTip) + .Margin(2.0f) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnTOPNodeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnTOPNodeChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink, ComboBoxTOPNode, Options = TOPNodesPtr]() + { + if (IsValid(InPDGAssetLink)) + return FText::FromString(InPDGAssetLink->GetSelectedTOPNodeName()); + else + return FText(); + }) + .ToolTipText_Lambda([InPDGAssetLink]() + { + UTOPNode const * const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (!TOPNode->NodePath.IsEmpty()) + return FText::FromString(TOPNode->NodePath); + else + return FText::FromString(TOPNode->NodeName); + } + else + { + return FText(); + } + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .AutoWidth() + [ + SAssignNew(ErrorText, STextBlock) + .Text(FText::FromString(NodeErrorText)) + .ToolTipText(FText::FromString(NodeErrorText)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .ColorAndOpacity(FLinearColor::Red) + //.ShadowColorAndOpacity(FLinearColor::Black) + ]; + + // Update the error text if needed + ErrorText->SetText(FText::FromString(NodeErrorText)); + ErrorText->SetToolTipText(FText::FromString(NodeErrorTooltip)); + ErrorText->SetColorAndOpacity(NodeErrorColor); + + // Hide the combobox if we have an error + ComboBoxTOPNode->SetVisibility(NodeErrorText.IsEmpty() ? EVisibility::Visible : EVisibility::Hidden); + } + + // TOP Node State + { + FDetailWidgetRow& PDGNodeStateResultRow = TOPNodesGrp.AddWidgetRow(); + DisableIfPDGNotLinked(PDGNodeStateResultRow, InPDGAssetLink); + PDGNodeStateResultRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node State"))) + ]; + + PDGNodeStateResultRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + FString TOPNodeStatus = FString(); + FLinearColor TOPNodeStatusColor = FLinearColor::White; + GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); + return FText::FromString(TOPNodeStatus); + }) + .ColorAndOpacity_Lambda([InPDGAssetLink]() + { + FString TOPNodeStatus = FString(); + FLinearColor TOPNodeStatusColor = FLinearColor::White; + GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); + return FSlateColor(TOPNodeStatusColor); + }) + ]; + } + + // Checkbox: Load Work Item Output Files + { + auto ToolTipLambda = [InPDGAssetLink]() + { + bool bDisabled = false; + if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) + { + bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; + } + + return bDisabled + ? FText::FromString(TEXT("This node has child nodes, the auto-load setting must be set on the child nodes individually.")) + : FText::FromString(TEXT("When enabled, Output files produced by this TOP Node's Work Items will automatically be loaded when cooked.")); + }; + FDetailWidgetRow& PDGNodeAutoLoadRow = TOPNodesGrp.AddWidgetRow(); + + DisableIfPDGNotLinked(PDGNodeAutoLoadRow, InPDGAssetLink); + PDGNodeAutoLoadRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) + return true; + return false; + })); + + PDGNodeAutoLoadRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Auto-Load Work Item Output Files"))) + .ToolTipText_Lambda(ToolTipLambda) + ]; + + TSharedPtr AutoLoadCheckBox; + + PDGNodeAutoLoadRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(AutoLoadCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->GetSelectedTOPNode() + ? (InPDGAssetLink->GetSelectedTOPNode()->bAutoLoad ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + UTOPNode* TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(TOPNode) || TOPNode->bAutoLoad == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNode); + + TOPNode->Modify(); + TOPNode->bAutoLoad = bNewState; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UTOPNode, bAutoLoad), TOPNode); + + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + }) + .ToolTipText_Lambda(ToolTipLambda) + ]; + } + + // Checkbox: Work Item Output Files Visible + { + auto ToolTipLambda = [InPDGAssetLink]() + { + bool bDisabled = false; + if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) + { + bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; + } + + return bDisabled + ? FText::FromString(TEXT("This node has child nodes, visibility of work item outputs must be set on the child nodes individually.")) + : FText::FromString(TEXT("Toggles the visibility of the actors created from this TOP Node's Work Item File Outputs.")); + }; + + FDetailWidgetRow& PDGNodeShowResultRow = TOPNodesGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGNodeShowResultRow, InPDGAssetLink); + PDGNodeShowResultRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) + return true; + + return false; + })); + PDGNodeShowResultRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Work Item Output Files Visible"))) + .ToolTipText_Lambda(ToolTipLambda) + ]; + + TSharedPtr ShowResCheckBox; + PDGNodeShowResultRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(ShowResCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->GetSelectedTOPNode() + ? (InPDGAssetLink->GetSelectedTOPNode()->IsVisibleInLevel() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(TOPNode) || TOPNode->IsVisibleInLevel() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNode); + + TOPNode->Modify(); + TOPNode->SetVisibleInLevel(bNewState); + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(TEXT("bShow"), TOPNode); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + }) + .ToolTipText_Lambda(ToolTipLambda) + ]; + } + + // Buttons: DIRTY NODE / COOK NODE + { + TSharedRef DirtyHBox = SNew(SHorizontalBox); + TSharedPtr CookHBox = SNew(SHorizontalBox); + + TSharedPtr DirtyButton; + TSharedPtr CookButton; + + FDetailWidgetRow& PDGDirtyCookRow = TOPNodesGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); + }) + .WidthOverride(200.0f) + [ + SAssignNew(DirtyButton, SButton) + //.Text(LOCTEXT("DirtyNode", "Dirty Node")) + .ToolTipText(LOCTEXT("DirtyNodeTooltip", "Dirties the selected TOP node and clears its work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + FHoudiniPDGManager::DirtyTOPNode(TOPNode); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + else + { + UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(TOPNode); + } + } + } + + return FReply::Handled(); + }) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNode()) && !InPDGAssetLink->GetSelectedTOPNode()->bHidden) + return true; + return false; + }) + .Content() + [ + SAssignNew(DirtyHBox, SHorizontalBox) + ] + ] + ] + // + SHorizontalBox::Slot() + // .AutoWidth() + // [ + // SNew(SBox) + // .WidthOverride(200.0f) + // [ + // SAssignNew(DirtyButton, SButton) + // .Text(LOCTEXT("DirtyAllTasks", "Dirty All Tasks")) + // .ToolTipText(LOCTEXT("DirtyAllTasksTooltip", "Dirties all tasks/work items on the selected TOP node.")) + // .ContentPadding(FMargin(5.0f, 2.0f)) + // .VAlign(VAlign_Center) + // .HAlign(HAlign_Center) + // .OnClicked_Lambda([InPDGAssetLink]() + // { + // if(InPDGAssetLink->GetSelectedTOPNode()) + // { + // FHoudiniPDGManager::DirtyAllTasksOfTOPNode(*(InPDGAssetLink->GetSelectedTOPNode())); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + // } + // + // return FReply::Handled(); + // }) + // ] + // ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(200.0f) + [ + SAssignNew(CookButton, SButton) + //.Text(LOCTEXT("CookNode", "Cook Node")) + .ToolTipText(LOCTEXT("CookNodeTooltip", "Cooks the selected TOP Node.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode)) + return false; + // Disable Cook Node button if the node is already cooking + return !SelectedNode->bHidden && SelectedNode->NodeState != EPDGNodeState::Cooking && !SelectedNode->AnyWorkItemsPending(); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node)) + { + FHoudiniPDGManager::CookTOPNode(Node); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CookHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr DirtyIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyNodeIconBrush(); + if (DirtyIconBrush.IsValid()) + { + TSharedPtr DirtyImage; + DirtyHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(DirtyImage, SImage) + ] + ]; + + DirtyImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([DirtyIconBrush]() { return DirtyIconBrush.Get(); }))); + } + + DirtyHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("DirtyNode", "Dirty Node")) + ]; + + TSharedPtr CookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + if (CookIconBrush.IsValid()) + { + TSharedPtr CookImage; + CookHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookImage, SImage) + ] + ]; + + CookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookIconBrush]() { return CookIconBrush.Get(); }))); + } + + CookHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookNode", "Cook Node")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: Load Work Item Objects / Unload Work Item Objects + { + TSharedPtr UnloadWorkItemsButton; + TSharedPtr LoadWorkItemsButton; + + FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNodesGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode()); + }) + .WidthOverride(200.0f) + [ + SAssignNew(UnloadWorkItemsButton, SButton) + .Text(LOCTEXT("UnloadWorkItemsForNode", "Unload Work Item Objects")) + .ToolTipText(LOCTEXT("UnloadWorkItemsForNodeTooltip", "Unloads / removes loaded work item results from level. Not undoable: use the \"Load Work Item Objects\" button to reload the results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveLoadedWorkResults) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + // Set the state to ToDelete, PDGManager will delete it when processing work items + TOPNode->SetLoadedWorkResultsToDelete(); + } + else + { + // Delete and unload the result objects and actors now + TOPNode->DeleteWorkResultOutputObjects(); + } + } + } + + return FReply::Handled(); + }) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(200.0f) + [ + SAssignNew(LoadWorkItemsButton, SButton) + .Text(LOCTEXT("LoadWorkItems", "Load Work Item Objects")) + .ToolTipText(LOCTEXT("LoadWorkItemsForNodeTooltip", "Loads any available but not loaded work items objects (this could include items from a previous cook). Creates output actors. Not undoable: use the \"Unload Work Item Objects\" button to unload/remove loaded work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveNotLoadedWorkResults) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(SelectedNode)) + { + SelectedNode->SetNotLoadedWorkResultsToLoad(true); + } + } + return FReply::Handled(); + }) + ] + ] + ]; + } + + // TOP Node WorkItem Status + { + if (InPDGAssetLink->GetSelectedTOPNode()) + { + FDetailWidgetRow& PDGNodeWorkItemStatsRow = TOPNodesGrp.AddWidgetRow(); + DisableIfPDGNotLinked(PDGNodeWorkItemStatsRow, InPDGAssetLink); + FHoudiniPDGDetails::AddWorkItemStatusWidget( + PDGNodeWorkItemStatsRow, TEXT("TOP Node Work Item Status"), InPDGAssetLink, true); + } + } +} + +void +FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // Repopulate the network and nodes for the assetlink + if (!FHoudiniPDGManager::UpdatePDGAssetLink(InPDGAssetLink)) + return; + + FHoudiniPDGDetails::RefreshUI(InPDGAssetLink, true); +} + +void +FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // Update the workitem stats + InPDGAssetLink->UpdateWorkItemTally(); + + // Update the editor properties + FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, InFullUpdate); +} + +void +FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); + + if (!InPDGAssetLink->bBakeMenuExpanded) + return; + + auto OnBakeButtonClickedLambda = [InPDGAssetLink]() + { + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(InPDGAssetLink); + // else + FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink); + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceWithBlueprint(InPDGAssetLink); + // else + FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink); + } + break; + // + // case EHoudiniEngineBakeOption::ToFoliage: + // { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(InPDGAssetLink); + // else + // FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InPDGAssetLink); + // } + // break; + // + // case EHoudiniEngineBakeOption::ToWorldOutliner: + // { + // if (InPDGAssetLink->bIsReplace) + // { + // // Todo + // } + // else + // { + // //Todo + // } + // } + // break; + } + + return FReply::Handled(); + }; + + auto OnBakeFolderTextCommittedLambda = [InPDGAssetLink](const FText& Val, ETextCommit::Type TextCommitType) + { + FString NewPathStr = Val.ToString(); + if (NewPathStr.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + //Todo? Check if the new Bake folder path is valid + InPDGAssetLink->Modify(); + InPDGAssetLink->BakeFolder.Path = NewPathStr; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, BakeFolder), InPDGAssetLink); + }; + + // Button Row + FDetailWidgetRow & ButtonRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(ButtonRow, InPDGAssetLink); + + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + + // Bake Button + TSharedRef BakeHBox = SNew(SHorizontalBox); + TSharedPtr BakeButton; + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(15.f, 0.0f, 0.0f, 0.0f) + .MaxWidth(75.0f) + [ + SNew(SBox) + .WidthOverride(75.0f) + [ + SAssignNew(BakeButton, SButton) + //.Text(FText::FromString("Bake")) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + //.ToolTipText(LOCTEXT("HoudiniPDGDetailsBakeButton", "Bake the Houdini PDG TOP Node(s)")) + .ToolTipText_Lambda([InPDGAssetLink]() + { + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + return LOCTEXT( + "HoudiniEnginePDGBakeButtonBakeToActorToolTip", + "Bake this Houdini PDG Asset's output assets and seperate the output actors from the PDG asset link."); + } + break; + case EHoudiniEngineBakeOption::ToBlueprint: + { + return LOCTEXT( + "HoudiniEnginePDGBakeButtonBakeToBlueprintToolTip", + "Bake this Houdini PDG Asset's output assets to blueprints and remove temporary output actors that no " + "longer has output components from the PDG asset link."); + } + break; + default: + { + return FText(); + } + } + }) + .Visibility(EVisibility::Visible) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda(OnBakeButtonClickedLambda) + .Content() + [ + SAssignNew(BakeHBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); + if (BakeIconBrush.IsValid()) + { + TSharedPtr BakeImage; + BakeHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(BakeImage, SImage) + ] + ]; + + BakeImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([BakeIconBrush]() { return BakeIconBrush.Get(); }))); + } + + BakeHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Bake")) + ]; + + // bake Type ComboBox + TSharedPtr>> TypeComboBox; + + TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeTypeOptionsLabels(); + TSharedPtr IntialSelec; + if (OptionSource) + { + // IntialSelec = (*OptionSource)[(int)InPDGAssetLink->HoudiniEngineBakeOption]; + const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption); + const TSharedPtr* DefaultOption = OptionSource->FindByPredicate( + [DefaultStr](TSharedPtr InStringPtr) + { + return InStringPtr.IsValid() && *InStringPtr == DefaultStr; + } + ); + if (DefaultOption) + IntialSelec = *DefaultOption; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(93.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(93.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid() || SelectType == ESelectInfo::Type::Direct) + return; + + const EHoudiniEngineBakeOption NewOption = + FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->HoudiniEngineBakeOption) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->HoudiniEngineBakeOption = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink, TypeComboBox, OptionSource]() + { + return FText::FromString(FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + // bake selection ComboBox + TSharedPtr>> BakeSelectionComboBox; + + TArray>* PDGBakeSelectionOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeSelectionOptionsLabels(); + TSharedPtr PDGBakeSelectionIntialSelec; + if (PDGBakeSelectionOptionSource) + { + PDGBakeSelectionIntialSelec = (*PDGBakeSelectionOptionSource)[(int)InPDGAssetLink->PDGBakeSelectionOption]; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(163.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(163.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(PDGBakeSelectionOptionSource) + .InitiallySelectedItem(PDGBakeSelectionIntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EPDGBakeSelectionOption NewOption = + FHoudiniEngineEditor::Get().StringToPDGBakeSelectionOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->PDGBakeSelectionOption) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->PDGBakeSelectionOption = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromPDGBakeTargetOption(InPDGAssetLink->PDGBakeSelectionOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; + + // Bake package replacement mode row + FDetailWidgetRow & BakePackageReplaceRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(BakePackageReplaceRow, InPDGAssetLink); + + TSharedRef BakePackageReplaceRowHorizontalBox = SNew(SHorizontalBox); + + BakePackageReplaceRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeLabel", "Replace Mode")) + .ToolTipText( + LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeTooltip", "Replacement mode " + "during baking. Create new assets, using numerical suffixes in package names when necessary, or " + "replace existing assets with matching names. Also replaces the previous bake's output actors in " + "replacement mode vs creating incremental ones.")) + ] + ]; + + // bake package replace mode ComboBox + TSharedPtr>> BakePackageReplaceModeComboBox; + + TArray>* PDGBakePackageReplaceModeOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels(); + TSharedPtr PDGBakePackageReplaceModeInitialSelec; + if (PDGBakePackageReplaceModeOptionSource) + { + const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode); + const TSharedPtr* DefaultOption = PDGBakePackageReplaceModeOptionSource->FindByPredicate( + [DefaultStr](TSharedPtr InStringPtr) + { + return InStringPtr.IsValid() && *InStringPtr == DefaultStr; + } + ); + if (DefaultOption) + PDGBakePackageReplaceModeInitialSelec = *DefaultOption; + } + + BakePackageReplaceRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(163.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(163.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(PDGBakePackageReplaceModeOptionSource) + .InitiallySelectedItem(PDGBakePackageReplaceModeInitialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + const FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EPDGBakePackageReplaceModeOption NewOption = + FHoudiniEngineEditor::Get().StringToPDGBakePackageReplaceModeOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->PDGBakePackageReplaceMode) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->PDGBakePackageReplaceMode = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + BakePackageReplaceRow.WholeRowWidget.Widget = BakePackageReplaceRowHorizontalBox; + + // Bake Folder Row + FDetailWidgetRow & BakeFolderRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(BakeFolderRow, InPDGAssetLink); + + TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) + .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) + ] + ]; + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .MaxWidth(235.0) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) + .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) + .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) + ] + ]; + + BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; + + // Add additional bake options + FDetailWidgetRow & AdditionalBakeSettingsRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); + + TSharedPtr CheckBoxAutoBake; + TSharedPtr CheckBoxRecenterBakedActors; + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bRecenterBakedActors = bNewState; + + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bRecenterBakedActors), InPDGAssetLink); + }) + ] + ]; + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxAutoBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake work result object as they are loaded.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bBakeAfterWorkResultObjectLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bBakeAfterWorkResultObjectLoaded = bNewState; + + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterWorkResultObjectLoaded), InPDGAssetLink); + }) + ] + ]; + + AdditionalBakeSettingsRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText) + : Text(InText) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText, const FString &InToolTip) + : Text(InText) + , ToolTip(InToolTip) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText) + : Text(InText) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText, FString&& InToolTip) + : Text(InText) + , ToolTip(InToolTip) + , Value(InValue) +{ +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h index dd11aa32e..eb5953860 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h @@ -1,140 +1,140 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Templates/SharedPointer.h" -#include "DetailWidgetRow.h" - -#include "HoudiniPDGAssetLink.h" - -class IDetailGroup; -class IDetailCategoryBuilder; - -struct FWorkItemTally; -enum class EPDGLinkState : uint8; -enum class EHoudiniBGEOCommandletStatus : uint8; - -// Convenience struct to hold a label and tooltip for widgets. -struct FTextAndTooltip -{ -public: - FTextAndTooltip(int32 InValue, const FString& InText); - FTextAndTooltip(int32 InValue, const FString& InText, const FString &InTooltip); - FTextAndTooltip(int32 InValue, FString&& InText); - FTextAndTooltip(int32 InValue, FString&& InText, FString&& InTooltip); - - FString Text; - - FString ToolTip; - - int32 Value; -}; - -class FHoudiniPDGDetails : public TSharedFromThis -{ - public: - - void CreateWidget( - IDetailCategoryBuilder & HouPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink); - //UHoudiniAssetComponent* InHAC); - - void AddPDGAssetWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - - void AddWorkItemStatusWidget( - FDetailWidgetRow& InRow, const FString& TitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode); - - void AddPDGAssetStatus( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink); - - void AddPDGCommandletStatus( - IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus); - - void AddTOPNetworkWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - - void AddTOPNodeWidget( - IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink); - - static void RefreshPDGAssetLink( - UHoudiniPDGAssetLink* InPDGAssetLink); - - static void RefreshUI( - UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate = true); - - static void - CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - protected: - // Helper function for getting the work item tally and color - static bool GetWorkItemTallyValueAndColor( - UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode, const FString& InTallyItemString, - int32& OutValue, FLinearColor& OutColor); - - // Helper to get the status text for the selected TOP node, and the color with which to display it on the UI. - // Returns false if the InPDGAssetLink is invalid, or there is no selected TOP node. - static bool GetSelectedTOPNodeStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor); - - // Helper to get asset link status and status color for UI - static bool GetPDGStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor); - - // Helper for getting the commandlet status text and color for the UI - static void GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor); - - // Helper to check if the asset link state is Linked - static FORCEINLINE bool IsPDGLinked(UHoudiniPDGAssetLink* InPDGAssetLink) - { - return IsValid(InPDGAssetLink) && InPDGAssetLink->LinkState == EPDGLinkState::Linked; - } - - // Helper for binding IsPDGLinked to a TAttribute - static FORCEINLINE void BindDisableIfPDGNotLinked(TAttribute &InAttrToBind, UHoudiniPDGAssetLink* InPDGAssetLink) - { - InAttrToBind.Bind( - TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink); - }) - ); - } - - // Helper to disable a UI row if InPDGAssetLink is not linked - static FORCEINLINE void DisableIfPDGNotLinked(FDetailWidgetRow& InRow, UHoudiniPDGAssetLink* InPDGAssetLink) - { - BindDisableIfPDGNotLinked(InRow.IsEnabledAttr, InPDGAssetLink); - } - - private: - - TArray> TOPNetworksPtr; - - TArray> TOPNodesPtr; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Templates/SharedPointer.h" +#include "DetailWidgetRow.h" + +#include "HoudiniPDGAssetLink.h" + +class IDetailGroup; +class IDetailCategoryBuilder; + +struct FWorkItemTally; +enum class EPDGLinkState : uint8; +enum class EHoudiniBGEOCommandletStatus : uint8; + +// Convenience struct to hold a label and tooltip for widgets. +struct FTextAndTooltip +{ +public: + FTextAndTooltip(int32 InValue, const FString& InText); + FTextAndTooltip(int32 InValue, const FString& InText, const FString &InTooltip); + FTextAndTooltip(int32 InValue, FString&& InText); + FTextAndTooltip(int32 InValue, FString&& InText, FString&& InTooltip); + + FString Text; + + FString ToolTip; + + int32 Value; +}; + +class FHoudiniPDGDetails : public TSharedFromThis +{ + public: + + void CreateWidget( + IDetailCategoryBuilder & HouPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink); + //UHoudiniAssetComponent* InHAC); + + void AddPDGAssetWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + + void AddWorkItemStatusWidget( + FDetailWidgetRow& InRow, const FString& TitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode); + + void AddPDGAssetStatus( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink); + + void AddPDGCommandletStatus( + IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus); + + void AddTOPNetworkWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + + void AddTOPNodeWidget( + IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink); + + static void RefreshPDGAssetLink( + UHoudiniPDGAssetLink* InPDGAssetLink); + + static void RefreshUI( + UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate = true); + + static void + CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + protected: + // Helper function for getting the work item tally and color + static bool GetWorkItemTallyValueAndColor( + UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode, const FString& InTallyItemString, + int32& OutValue, FLinearColor& OutColor); + + // Helper to get the status text for the selected TOP node, and the color with which to display it on the UI. + // Returns false if the InPDGAssetLink is invalid, or there is no selected TOP node. + static bool GetSelectedTOPNodeStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor); + + // Helper to get asset link status and status color for UI + static bool GetPDGStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor); + + // Helper for getting the commandlet status text and color for the UI + static void GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor); + + // Helper to check if the asset link state is Linked + static FORCEINLINE bool IsPDGLinked(UHoudiniPDGAssetLink* InPDGAssetLink) + { + return IsValid(InPDGAssetLink) && InPDGAssetLink->LinkState == EPDGLinkState::Linked; + } + + // Helper for binding IsPDGLinked to a TAttribute + static FORCEINLINE void BindDisableIfPDGNotLinked(TAttribute &InAttrToBind, UHoudiniPDGAssetLink* InPDGAssetLink) + { + InAttrToBind.Bind( + TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink); + }) + ); + } + + // Helper to disable a UI row if InPDGAssetLink is not linked + static FORCEINLINE void DisableIfPDGNotLinked(FDetailWidgetRow& InRow, UHoudiniPDGAssetLink* InPDGAssetLink) + { + BindDisableIfPDGNotLinked(InRow.IsEnabledAttr, InPDGAssetLink); + } + + private: + + TArray> TOPNetworksPtr; + + TArray> TOPNodesPtr; + +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp index dfdabfce4..2dc560544 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp @@ -1,7308 +1,7338 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterDetails.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniInput.h" -#include "HoudiniAsset.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "SNewFilePathPicker.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "IDetailGroup.h" -#include "IDetailCustomization.h" -#include "PropertyCustomizationHelpers.h" -#include "DetailWidgetRow.h" -#include "Math/UnitConversion.h" -#include "ScopedTransaction.h" -#include "EditorDirectories.h" - -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SNumericEntryBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SMultiLineEditableTextBox.h" -#include "Widgets/Colors/SColorPicker.h" -#include "Widgets/Views/SExpanderArrow.h" -#include "Widgets/Layout/SExpandableArea.h" -#include "Widgets/Views/STableRow.h" -#include "Widgets/Input/NumericUnitTypeInterface.inl" -#include "Widgets/Images/SImage.h" -#include "Widgets/Layout/SSeparator.h" -#include "Widgets/Layout/SSplitter.h" -#include "SCurveEditorView.h" -#include "SAssetDropTarget.h" -#include "AssetThumbnail.h" - -#include "HoudiniInputDetails.h" - -#include "Framework/SlateDelegates.h" -#include "Templates/SharedPointer.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -int32 -SCustomizedButton::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const -{ - TSharedPtr Content = GetContent(); - - // 0. Initialize Line Buffer. - TArray Line; - Line.SetNumUninitialized(2); - - // Initialize Color buffer. - FLinearColor Color = FLinearColor::White; - - // 1. Draw the radio button. - if (bIsRadioButton) - { - // Construct the radio button circles exactly once, - // All radio buttons share the same circles then - if (FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER || - FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER) - { - ConstructRadioButtonCircles(); - } - - DrawRadioButton(AllottedGeometry, OutDrawElements, LayerId, bChosen); - } - - // 2. Draw background color (if selected) - if (bChosen) - { - Line[0].X = AllottedGeometry.Size.X - AllottedGeometry.Size.Y / 2.0f + 2.5f; - Line[0].Y = Content->GetDesiredSize().Y / 2.0f; - Line[1].X = AllottedGeometry.Size.Y / 2.0f - 2.5f; - Line[1].Y = Content->GetDesiredSize().Y / 2.0f; - - Color = FLinearColor::White; - Color.A = bIsRadioButton ? 0.05 : 0.1; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, AllottedGeometry.Size.Y); - } - - // 3. Drawing square around the text - { - // Switch the point order for each line to save few value assignment cycles - Line[0].X = 0.0f; - Line[0].Y = 0.0f; - Line[1].X = 0.0f; - Line[1].Y = Content->GetDesiredSize().Y; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); - - //Line[0].X = 0.0f; - //Line[0].Y = Content->GetDesiredSize().Y; - Line[0].X = AllottedGeometry.Size.X; - Line[0].Y = Content->GetDesiredSize().Y; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, bChosen ? FLinearColor::Gray : FLinearColor::Black, true, 1.0f); - - //Line[0].X = AllottedGeometry.Size.X; - //Line[0].Y = Content->GetDesiredSize().Y; - Line[1].X = AllottedGeometry.Size.X; - Line[1].Y = 0.0f; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); /* draw gray bottom line if this tab is selected, black otherwise*/ - - //Line[0].X = AllottedGeometry.Size.X; - //Line[0].Y = 0.0f; - Line[0].X = 0.0f; - Line[0].Y = 0.0f; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); - } - - // 4. Draw child widget - Content->Paint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - - return LayerId; -}; - -void -SCustomizedButton::ConstructRadioButtonCircles() const -{ - TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); - TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); - OuterPoints.Empty(); - InnerPoints.Empty(); - - OuterPoints.SetNumZeroed(HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER); - InnerPoints.SetNumZeroed(8); - - // Construct outer circle - int32 CurDegree = 0; - int32 DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; - - for (int32 Idx = 0; Idx < HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; ++Idx) - { - OuterPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); - OuterPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); - - CurDegree += DegStep; - } - - // Construct inner circle - CurDegree = 0; - DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER; - for (int32 Idx = 0; Idx < 8; ++Idx) - { - InnerPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); - InnerPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); - - CurDegree += DegStep; - } -} - -void -SCustomizedButton::DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const -{ - TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); - TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); - if (OuterPoints.Num() <= 1 || InnerPoints.Num() <= 1) - return; - - FLinearColor ColorNonSelected = FLinearColor::White; - FLinearColor ColorSelected = FLinearColor::Yellow; - - // initialize line buffer - TArray Line; - Line.SetNumZeroed(2); - bool alternator = false; - - // Draw outer circle - Line[0] = OuterPoints.Last(); - for (int32 Idx = 0; Idx < OuterPoints.Num(); ++Idx) - { - // alternate the points order each time to some some assignment cycles - if (alternator) - { - Line[0].X = OuterPoints[Idx].X; - Line[0].Y = OuterPoints[Idx].Y; - } - else - { - Line[1].X = OuterPoints[Idx].X; - Line[1].Y = OuterPoints[Idx].Y; - } - - alternator = !alternator; - - // Draw a line segment - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, ColorNonSelected, true, 1.0f); - } - - // Draw inner circle - alternator = false; - Line[0] = InnerPoints.Last(); - for (int32 Idx = 0; Idx < InnerPoints.Num(); ++Idx) - { - // alternate the points order each time to some some assignment cycles - if (alternator) - { - Line[0].X = InnerPoints[Idx].X; - Line[0].Y = InnerPoints[Idx].Y; - } - else - { - Line[1].X = InnerPoints[Idx].X; - Line[1].Y = InnerPoints[Idx].Y; - } - - alternator = !alternator; - - // Draw a line segment - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, bSelected ? ColorSelected : ColorNonSelected, true, 3.0f); - } -} - -void -SCustomizedBox::SetHoudiniParameter(TArray& InParams) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - - bool bIsMultiparmInstanceHeader = MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 0; - - switch (MainParam->GetParameterType()) - { - case EHoudiniParameterType::Button: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON; - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP; - } - break; - - case EHoudiniParameterType::Color: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR; - } - break; - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor * ColorRampParameter = Cast(MainParam); - if (!ColorRampParameter || ColorRampParameter->IsPendingKill()) - return; - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP; - if (ColorRampParameter->CachedPoints.Num() > 0) - MarginHeight = MarginHeight + HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE * (float)(ColorRampParameter->CachedPoints.Num() - 1); - } - break; - - case EHoudiniParameterType::File: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE; - } - break; - - case EHoudiniParameterType::FileDir: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR; - } - break; - - case EHoudiniParameterType::FileGeo: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO; - } - break; - - case EHoudiniParameterType::FileImage: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE; - } - break; - - case EHoudiniParameterType::Float: - { - if (MainParam->GetTupleSize() == 3) - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3; - } - else - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT - + (MainParam->GetTupleSize() - 1)* HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE; - } - } - } - break; - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat * FloatRampParameter = Cast(MainParam); - if (!FloatRampParameter || FloatRampParameter->IsPendingKill()) - return; - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP; - - if (FloatRampParameter->CachedPoints.Num() > 0) - MarginHeight = MarginHeight + Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE * (float)(FloatRampParameter->CachedPoints.Num() - 1); - } - break; - - case EHoudiniParameterType::Folder: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER; - } - break; - - case EHoudiniParameterType::FolderList: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST; - } - break; - - case EHoudiniParameterType::Input: - { - UHoudiniParameterOperatorPath* InputParam = Cast(MainParam); - - if (!InputParam || InputParam->IsPendingKill() || !InputParam->HoudiniInput.IsValid()) - break; - - UHoudiniInput* Input = InputParam->HoudiniInput.Get(); - - if (!Input || Input->IsPendingKill()) - break; - - - if (bIsMultiparmInstanceHeader) - { - switch (Input->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - int32 ExpandedTransformUIs = 0; - for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) - { - if (Input->IsTransformUIExpanded(Idx)) - ExpandedTransformUIs += 1; - } - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER - + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Curve: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Asset: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Landscape: - { - if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::World: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Skeletal: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER; - } - break; - default: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER; - break; - } - } - else - { - switch (Input->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - int32 ExpandedTransformUIs = 0; - for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) - { - if (Input->IsTransformUIExpanded(Idx)) - ExpandedTransformUIs += 1; - } - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE - + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM; - } - break; - case EHoudiniInputType::Curve: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE; - } - break; - case EHoudiniInputType::Asset: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET; - } - break; - case EHoudiniInputType::Landscape: - { - if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH; - } - break; - case EHoudiniInputType::World: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD; - } - break; - case EHoudiniInputType::Skeletal: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL; - } - break; - default: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT; - break; - - } - } - } - break; - - case EHoudiniParameterType::Int: - { - if (MainParam->GetTupleSize() == 3) - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3; - } - else - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER + - (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE; - } - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE; - } - break; - - case EHoudiniParameterType::Label: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL; - } - break; - - case EHoudiniParameterType::MultiParm: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM; - } - break; - - case EHoudiniParameterType::Separator: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR; - bIsSeparator = true; - } - break; - - case EHoudiniParameterType::String: - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE; - } - } - break; - - case EHoudiniParameterType::StringAssetRef: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF; - } - break; - - case EHoudiniParameterType::StringChoice: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE; - } - break; - - case EHoudiniParameterType::Toggle: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE; - } - break; - - case EHoudiniParameterType::Invalid: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID; - break; - - default: - MarginHeight = 0.0f; - break; - } -} - -float -SCustomizedBox::AddIndentation(UHoudiniParameter* InParam, - TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists) -{ - if (!InParam || InParam->IsPendingKill()) - return 0.0f; - - bool bIsMainParmSimpleFolder = false; - // Get if this Parameter is a simple / collapsible folder - if (InParam->GetParameterType() == EHoudiniParameterType::Folder) - { - UHoudiniParameterFolder* FolderParm = Cast(InParam); - if (FolderParm) - bIsMainParmSimpleFolder = !FolderParm->IsTab(); - } - - int32 ParentId = InParam->GetParentParmId(); - UHoudiniParameter* CurParm = InParam; - float Indentation = 0.0f; - - while (ParentId >= 0) - { - UHoudiniParameter* ParentFolder = nullptr; - UHoudiniParameterMultiParm* ParentMultiParm = nullptr; - - if (InAllFoldersAndFolderLists.Contains(ParentId)) - ParentFolder = InAllFoldersAndFolderLists[ParentId]; - - if (InAllMultiParms.Contains(ParentId)) - ParentMultiParm = InAllMultiParms[ParentId]; - - // The parent is a folder, add one unit of indentation - if (ParentFolder) - { - // Update the parent parm id - ParentId = ParentFolder->GetParentParmId(); - - if (ParentFolder->GetParameterType() == EHoudiniParameterType::FolderList) - continue; - - UHoudiniParameterFolder* Folder = Cast(ParentFolder); - - if (!Folder) - continue; - - // update the current parm, find the parent of new cur param in the next round - CurParm = Folder; - Indentation += 1.0f; - } - // The parent is a multiparm - else if (ParentMultiParm) - { - // Update the parent parm id - ParentId = ParentMultiParm->GetParentParmId(); - - if (CurParm->GetChildIndex() == 0) - { - Indentation += 0.0f; - } - else - { - Indentation += 2.0f; - } - - // update the current parm, find the parent of new cur param in the next round - CurParm = ParentMultiParm; - } - else - { - // no folder/multiparm parent, end the loop - ParentId = -1; - } - } - - - float IndentationWidth = INDENTATION_UNIT_WIDTH * Indentation; - - // Add a base indentation to non simple/collapsible param - // Since it needs more space to offset the arrow width - if (!bIsMainParmSimpleFolder) - IndentationWidth += NON_FOLDER_OFFSET_WIDTH; - - this->AddSlot().AutoWidth() - [ - SNew(SBox).WidthOverride(IndentationWidth) - ]; - - - return IndentationWidth; -}; - -int32 -SCustomizedBox::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const -{ - - SHorizontalBox::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - - // Initialize line buffer - TArray Line; - Line.SetNumZeroed(2); - // Initialize color buffer - FLinearColor Color = FLinearColor::White; - Color.A = 0.3; - - // draw the bottom line if this row is the tab folder list - if (bIsTabFolderListRow) - { - // Get the start position of the tabs bottom line (right bottom pt of the right most child widget) - float VerticalLineStartPosX = 0.0f; - float VerticalLineStartPosY = 0.0f; - float BottomLineStartPosX = 0.0f; - float BottomLineStartPosY = -1.0f; - - for (int32 Idx = 0; Idx < Children.Num(); ++Idx) - { - TSharedPtr CurChild = Children.GetChildAt(Idx); - if (!CurChild.IsValid()) - continue; - - if (Idx == 0) - { - VerticalLineStartPosX = CurChild->GetDesiredSize().X; - VerticalLineStartPosY = CurChild->GetDesiredSize().Y; - } - - BottomLineStartPosX += CurChild->GetDesiredSize().X; - - if (BottomLineStartPosY < 0.0f) - BottomLineStartPosY= CurChild->GetDesiredSize().Y; - } - - // Draw bottom line - Line[0].X = BottomLineStartPosX; - Line[0].Y = BottomLineStartPosY; - Line[1].X = AllottedGeometry.Size.X; - Line[1].Y = BottomLineStartPosY; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - - // Draw divider lines - { - Line[0].Y = -MarginHeight; - Line[1].Y = AllottedGeometry.Size.Y + MarginHeight; - - int32 NumOfLinesToDraw = bIsTabFolderListRow ? DividerLinePositions.Num() - 1 : DividerLinePositions.Num(); - for (int32 Idx = 0; Idx < NumOfLinesToDraw; ++Idx) - { - const float& CurDivider = DividerLinePositions[Idx]; - Line[0].X = CurDivider; - Line[1].X = CurDivider; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - - // Draw the last inner most divider line differently when this the tabs' row. - if (bIsTabFolderListRow && DividerLinePositions.Num() > 0) - { - const float& TabDivider = DividerLinePositions.Last(); - Line[0].X = TabDivider; - Line[1].X = TabDivider; - Line[0].Y = 0.f; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - } - - // Draw tab ending lines - { - float YPos = 0.0f; - - for (const float & CurEndingDivider : EndingDividerLinePositions) - { - // Draw cur ending line (vertical) - - Line[0].X = CurEndingDivider; - Line[0].Y = -2.3f; - Line[1].X = CurEndingDivider; - Line[1].Y = YPos; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - - // Draw cur ending line (horizontal) - - // Line[0].X = CurEndingDivider; - Line[0].Y = YPos; - Line[1].X = AllottedGeometry.Size.X; - // Line[1].Y = YPos; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - - YPos += 2.0f; - } - } - - // Draw the separator line if this is the row of a separator parameter - { - if (bIsSeparator) - { - Line[0].X = 25.f; - if (DividerLinePositions.Num() > 0) - Line[0].X += DividerLinePositions.Last(); - - Line[0].Y = AllottedGeometry.Size.Y / 2.f; - Line[1].X = AllottedGeometry.Size.X - 20.f; - Line[1].Y = Line[0].Y; - - Color.A = 0.7; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.5f); - } - } - - return LayerId; -}; - -void -SHoudiniFloatRampCurveEditor::Construct(const FArguments & InArgs) -{ - SCurveEditor::Construct(SCurveEditor::FArguments() - .ViewMinInput(InArgs._ViewMinInput) - .ViewMaxInput(InArgs._ViewMaxInput) - .ViewMinOutput(InArgs._ViewMinOutput) - .ViewMaxOutput(InArgs._ViewMaxOutput) - .XAxisName(InArgs._XAxisName) - .YAxisName(InArgs._YAxisName) - .HideUI(InArgs._HideUI) - .DrawCurve(InArgs._DrawCurve) - .TimelineLength(InArgs._TimelineLength) - .AllowZoomOutput(InArgs._AllowZoomOutput) - .ShowInputGridNumbers(InArgs._ShowInputGridNumbers) - .ShowOutputGridNumbers(InArgs._ShowOutputGridNumbers) - .ShowZoomButtons(InArgs._ShowZoomButtons) - .ZoomToFitHorizontal(InArgs._ZoomToFitHorizontal) - .ZoomToFitVertical(InArgs._ZoomToFitVertical) - ); - - - UCurveEditorSettings * CurveEditorSettings = GetSettings(); - if (CurveEditorSettings) - { - CurveEditorSettings->SetTangentVisibility(ECurveEditorTangentVisibility::NoTangents); - } -} - -void -SHoudiniColorRampCurveEditor::Construct(const FArguments & InArgs) -{ - SColorGradientEditor::Construct(SColorGradientEditor::FArguments() - .ViewMinInput(InArgs._ViewMinInput) - .ViewMaxInput(InArgs._ViewMaxInput) - ); -} - - -FReply -SHoudiniFloatRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - FReply Reply = SCurveEditor::OnMouseButtonUp(MyGeometry, MouseEvent); - - if (!HoudiniFloatRampCurve.IsValid()) - return Reply; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - FRichCurve& FloatCurve = HoudiniFloatRampCurve.Get()->FloatCurve; - - TArray>& FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; - - if (FloatRampParameters.Num() < 1) - return Reply; - - if (!FloatRampParameters[0].IsValid()) - return Reply; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - // Do not allow modification when the parent HDA of the main param is being cooked. - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points of the main float ramp param to other parameters - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - // Modification is based on the main parameter, use synced points if the main param is on auto update mode, use cached points otherwise. - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - // On mouse button up handler handles point modification only - if (FloatCurve.GetNumKeys() != NumMainPoints) - return Reply; - - bool bNeedToRefreshEditor= false; - - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float& CurvePosition = FloatCurve.Keys[Idx].Time; - float& CurveValue = FloatCurve.Keys[Idx].Value; - - // This point is modified - if (MainPoint->GetPosition() != CurvePosition || MainPoint->GetValue() != CurveValue) - { - - // The editor needs refresh only if the main parameter is on manual mode, and has been modified - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedToRefreshEditor = true; - - // Iterate through the float ramp parameter of all selected HDAs. - for (auto & NextRampFloat : FloatRampParameters) - { - if (!NextRampFloat.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedRampFloat = NextRampFloat.Get(); - - if (!SelectedRampFloat) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedRampFloat)) - continue; - - if (SelectedRampFloat->IsAutoUpdate() && bCookingEnabled) - { - // The selected float ramp parameter is on auto update mode, use its synced points. - TArray &SelectedRampPoints = SelectedRampFloat->Points; - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // Synced points in the selected ramp is more than or the same number as that in the main parameter, - // modify the position and value of the synced point and mark them as changed. - - UHoudiniParameterRampFloatPoint*& ModifiedPoint = SelectedRampPoints[Idx]; - - if (!ModifiedPoint) - continue; - - if (ModifiedPoint->GetPosition() != CurvePosition && ModifiedPoint->PositionParentParm) - { - ModifiedPoint->SetPosition(CurvePosition); - ModifiedPoint->PositionParentParm->MarkChanged(true); - } - - if (ModifiedPoint->GetValue() != CurveValue && ModifiedPoint->ValueParentParm) - { - ModifiedPoint->SetValue(CurveValue); - ModifiedPoint->ValueParentParm->MarkChanged(true); - } - } - else - { - // Synced points in the selected ramp is less than that in the main parameter - // Since we have pushed the points of the main param to all of the selected ramps, - // We need to modify the insert event. - - int32 IndexInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedRampFloat->ModificationEvents.IsValidIndex(Idx)) - { - UHoudiniParameterRampModificationEvent*& ModEvent = SelectedRampFloat->ModificationEvents[Idx]; - if (!ModEvent) - continue; - - if (ModEvent->InsertPosition != CurvePosition) - ModEvent->InsertPosition = CurvePosition; - - if (ModEvent->InsertFloat != CurveValue) - ModEvent->InsertFloat = CurveValue; - } - - } - } - else - { - // The selected float ramp is on manual update mode, use the cached points. - TArray &FloatRampCachedPoints = SelectedRampFloat->CachedPoints; - - // Since we have pushed the points in main param to all the selected float ramp, - // we need to modify the corresponding cached point in the selected float ramp. - - if (FloatRampCachedPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampFloatPoint*& ModifiedCachedPoint = FloatRampCachedPoints[Idx]; - - if (!ModifiedCachedPoint) - continue; - - if (ModifiedCachedPoint->Position != CurvePosition) - { - ModifiedCachedPoint->Position = CurvePosition; - SelectedRampFloat->bCaching = true; - if (!bCookingEnabled) - { - //SelectedRampFloat->MarkChanged(true); - if (ModifiedCachedPoint->PositionParentParm) - ModifiedCachedPoint->PositionParentParm->MarkChanged(true); - } - } - - if (ModifiedCachedPoint->Value != CurveValue) - { - ModifiedCachedPoint->Value = CurveValue; - SelectedRampFloat->bCaching = true; - if (!bCookingEnabled) - { - //SelectedRampFloat->MarkChanged(true); - if (ModifiedCachedPoint->ValueParentParm) - ModifiedCachedPoint->ValueParentParm->MarkChanged(true); - } - } - } - } - } - } - } - - - if (bNeedToRefreshEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } - - return Reply; -} - -FReply -SHoudiniFloatRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) -{ - FReply Reply = SCurveEditor::OnKeyDown(MyGeometry, InKeyEvent); - - if (InKeyEvent.GetKey().ToString() != FString("Enter")) - return Reply; - - if (!HoudiniFloatRampCurve.IsValid() || !HoudiniFloatRampCurve.Get()) - return Reply; - - TArray> FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; - - if (FloatRampParameters.Num() < 1) - return Reply; - - if (!FloatRampParameters[0].IsValid()) - return Reply; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Do nothing if the main param is on auto update mode - if (MainParam->IsAutoUpdate() && bCookingEnabled) - return Reply; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points in the main float ramp to the float ramp parameters in all selected HDAs. - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - for (auto& NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - if (!SelectedFloatRamp) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not sync the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - // Sync the cached points if the selected float ramp parameter is on manual update mode - //FHoudiniParameterDetails::SyncCachedFloatRampPoints(SelectedFloatRamp); - SelectedFloatRamp->SyncCachedPoints(); - } - - return Reply; -} - -void -UHoudiniFloatRampCurve::OnCurveChanged(const TArray& ChangedCurveEditInfos) -{ - Super::OnCurveChanged(ChangedCurveEditInfos); - - if (FloatRampParameters.Num() < 1) - return; - - if (!FloatRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return; - - // Do not allow modification when the parent HDA of the main param is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - // Push all the points of the Main parameter to other parameters - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode, otherwise use its cached points. - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - bool bNeedUpdateEditor = false; - - // OnCurveChanged handler handles point delete and insertion only - - // A point is deleted. - if (FloatCurve.GetNumKeys() < NumMainPoints) - { - // Find the index of the deleted point - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float CurPointPosition = MainPoint->GetPosition(); - float CurCurvePosition = -1.0f; - - if (FloatCurve.Keys.IsValidIndex(Idx)) - CurCurvePosition = FloatCurve.Keys[Idx].Time; - - // Delete the point at Idx - if (CurCurvePosition != CurPointPosition) - { - // Iterate through all the float ramp parameter in all the selected HDAs - for (auto & NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - if (!SelectedFloatRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - TArray & SelectedRampPoints = SelectedFloatRamp->Points; - - // The selected float ramp is on auto update mode: - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // If the number of synced points of the selected float ramp is greater or equal to the number of points of that in the main param, - // Create a Houdini engine manager event to delete the point at Idx of the selected float ramp; - - UHoudiniParameterRampFloatPoint* PointToDelete = SelectedRampPoints[Idx]; - - if (!PointToDelete) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(SelectedFloatRamp, PointToDelete->InstanceIndex); - SelectedFloatRamp->MarkChanged(true); - } - else - { - // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, - // delete the corresponding inserting event. - - int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - SelectedFloatRamp->ModificationEvents.RemoveAt(IdxInEventsArray); - } - } - else - { - // The selected float ramp is on manual update mode: - // Since we have pushed all the points in main param to the cached points of the selected float ramp, - // remove the corresponding points from the cached points array. - - if (SelectedFloatRamp->CachedPoints.IsValidIndex(Idx)) - { - SelectedFloatRamp->CachedPoints.RemoveAt(Idx); - SelectedFloatRamp->bCaching = true; - } - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point is inserted - else if (FloatCurve.GetNumKeys() > NumMainPoints) - { - // Find the index of the inserted point - for (int32 Idx = 0; Idx < FloatCurve.GetNumKeys(); ++Idx) - { - - float CurPointPosition = -1.0f; - float CurCurvePosition = FloatCurve.Keys[Idx].Time; - - if (MainPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - CurPointPosition = MainPoint->GetPosition(); - } - - // Insert instance at Idx - if (CurPointPosition != CurCurvePosition) - { - // Iterate through the float ramp parameter of all selected HDAs. - for (auto & NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - // If the selected float ramp is on auto update mode: - // Since we have pushed all the points of main parameter to the selected, - // create a Houdini engine manager event to insert a point. - - FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent( - SelectedFloatRamp, CurCurvePosition, FloatCurve.Keys[Idx].Value, EHoudiniRampInterpolationType::LINEAR); - - SelectedFloatRamp->MarkChanged(true); - } - else - { - // If the selected float ramp is on manual update mode: - // push a new point to the cached points array - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(SelectedFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = CurCurvePosition; - NewCachedPoint->Value = FloatCurve.Keys[Idx].Value; - NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; - - if (Idx >= SelectedFloatRamp->CachedPoints.Num()) - SelectedFloatRamp->CachedPoints.Add(NewCachedPoint); - else - SelectedFloatRamp->CachedPoints.Insert(NewCachedPoint, Idx); - - SelectedFloatRamp->bCaching = true; - - if (!bCookingEnabled) - SelectedFloatRamp->MarkChanged(true); - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - if (bNeedUpdateEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } - -} - - -FReply -SHoudiniColorRampCurveEditor::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - FReply Reply = SColorGradientEditor::OnMouseButtonDown(MyGeometry, MouseEvent); - - if (HoudiniColorRampCurve.IsValid()) - { - UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); - if (Curve) - Curve->bEditing = true; - } - - return Reply; -} - -FReply -SHoudiniColorRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - - FReply Reply = SColorGradientEditor::OnMouseButtonUp(MyGeometry, MouseEvent); - - if (HoudiniColorRampCurve.IsValid()) - { - UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); - - if (Curve) - { - Curve->bEditing = false; - Curve->OnColorRampCurveChanged(true); - } - } - - return Reply; - -} - -FReply -SHoudiniColorRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) -{ - FReply Reply = SColorGradientEditor::OnKeyDown(MyGeometry, InKeyEvent); - - if (InKeyEvent.GetKey().ToString() != FString("Enter")) - return Reply; - - if (!HoudiniColorRampCurve.IsValid() || !HoudiniColorRampCurve.Get()) - return Reply; - - TArray> &ColorRampParameters = HoudiniColorRampCurve.Get()->ColorRampParameters; - - if (ColorRampParameters.Num() < 1) - return Reply; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - // Do nothing if the main param is on auto update mode - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - if (MainParam->IsAutoUpdate() && bCookingEnabled) - return Reply; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points in the main color ramp to the color ramp parameters in all selected HDAs. - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); - - for (auto& NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not sync the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - // Sync the cached points if the selected color ramp is on manual update mode - FHoudiniParameterDetails::SyncCachedColorRampPoints(SelectedColorRamp); - } - - return Reply; -} - -void -UHoudiniColorRampCurve::OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) -{ - Super::OnCurveChanged(ChangedCurveEditInfos); - - OnColorRampCurveChanged(); -} - -void -UHoudiniColorRampCurve::OnColorRampCurveChanged(bool bModificationOnly) -{ - // Array is always true in this case - // if (!FloatCurves) - // return; - - if (ColorRampParameters.Num() < 1) - return; - - if (!ColorRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return; - - // Do not allow modification when the parent HDA of the main param is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - // Push all the points of the main parameter to other parameters - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); - - // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode,otherwise use its cached points. - bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - bool bNeedUpdateEditor = false; - - // OnCurveChanged handler of color ramp curve editor handles point delete, insert and color change - - // A point is deleted - if (FloatCurves->GetNumKeys() < NumMainPoints) - { - if (bModificationOnly) - return; - - // Find the index of the deleted point - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float CurPointPosition = MainPoint->GetPosition(); - float CurCurvePosition = -1.0f; - - if (FloatCurves[0].Keys.IsValidIndex(Idx)) - CurCurvePosition = FloatCurves[0].Keys[Idx].Time; - - // Delete the point at Idx - if (CurCurvePosition != CurPointPosition) - { - // Iterate through all the color ramp parameter in all the selected HDAs - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - TArray & SelectedRampPoints = SelectedColorRamp->Points; - - // The selected color ramp is on auto update mode: - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // If the number of synced points of the selected color ramp is greater or equal to the number of points of that in the main param, - // create a Houdini engine manager event to delete the point at Idx of the selected float ramp; - - UHoudiniParameterRampColorPoint* PointToDelete = SelectedRampPoints[Idx]; - - if (!PointToDelete) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(SelectedColorRamp, PointToDelete->InstanceIndex); - SelectedColorRamp->MarkChanged(true); - } - else - { - // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, - // delete the corresponding inserting event. - - int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - SelectedColorRamp->ModificationEvents.RemoveAt(IdxInEventsArray); - } - } - else - { - // The selected color ramp is on manual update mode: - // Since we have pushed all the points in main param to the cached points of the selected float ramp, - // remove the corresponding points from the cached points array - if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) - { - SelectedColorRamp->CachedPoints.RemoveAt(Idx); - SelectedColorRamp->bCaching = true; - } - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point is inserted - else if (FloatCurves[0].GetNumKeys() > NumMainPoints) - { - - if (bModificationOnly) - return; - - // Find the index of the inserted point - for (int32 Idx = 0; Idx < FloatCurves[0].GetNumKeys(); ++Idx) - { - - float CurPointPosition = -1.0f; - float CurCurvePosition = FloatCurves[0].Keys[Idx].Time; - - if (MainPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - CurPointPosition = MainPoint->GetPosition(); - } - - // Insert a point at Idx - if (CurPointPosition != CurCurvePosition) - { - // Get the interpolation value of inserted color point - - FLinearColor ColorPrev = FLinearColor::Black; - FLinearColor ColorNext = FLinearColor::White; - float PositionPrev = 0.0f; - float PositionNext = 1.0f; - - if (MainParam->IsAutoUpdate() && bCookingEnabled) - { - // Try to get its previous point's color - if (MainParam->Points.IsValidIndex(Idx - 1)) - { - ColorPrev = MainParam->Points[Idx - 1]->GetValue(); - PositionPrev = MainParam->Points[Idx - 1]->GetPosition(); - } - - // Try to get its next point's color - if (MainParam->Points.IsValidIndex(Idx)) - { - ColorNext = MainParam->Points[Idx]->GetValue(); - PositionNext = MainParam->Points[Idx]->GetPosition(); - } - } - else - { - // Try to get its previous point's color - if (MainParam->CachedPoints.IsValidIndex(Idx - 1)) - { - ColorPrev = MainParam->CachedPoints[Idx - 1]->GetValue(); - PositionPrev = MainParam->CachedPoints[Idx - 1]->GetPosition(); - } - - // Try to get its next point's color - if (MainParam->CachedPoints.IsValidIndex(Idx)) - { - ColorNext = MainParam->CachedPoints[Idx]->GetValue(); - PositionNext = MainParam->CachedPoints[Idx]->GetPosition(); - } - } - - float TotalWeight = FMath::Abs(PositionNext - PositionPrev); - float PrevWeight = FMath::Abs(CurCurvePosition - PositionPrev); - float NextWeight = FMath::Abs(PositionNext - CurCurvePosition); - - FLinearColor InsertedColor = ColorPrev * (PrevWeight / TotalWeight) + ColorNext * (NextWeight / TotalWeight); - - // Iterate through the color ramp parameter of all selected HDAs. - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - // If the selected color ramp is on auto update mode: - // Since we have pushed all the points of main parameter to the selected, - // create a Houdini engine manager event to insert a point. - - FHoudiniParameterDetails::CreateColorRampParameterInsertEvent( - SelectedColorRamp, CurCurvePosition, InsertedColor, EHoudiniRampInterpolationType::LINEAR); - - SelectedColorRamp->MarkChanged(true); - } - else - { - // If the selected color ramp is on manual update mode: - // Push a new point to the cached points array - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(SelectedColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = CurCurvePosition; - NewCachedPoint->Value = InsertedColor; - NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; - - if (Idx >= SelectedColorRamp->CachedPoints.Num()) - SelectedColorRamp->CachedPoints.Add(NewCachedPoint); - else - SelectedColorRamp->CachedPoints.Insert(NewCachedPoint, Idx); - - SelectedColorRamp->bCaching = true; - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!MainParam->IsAutoUpdate() && bCookingEnabled) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point's color is changed - else - { - if (bEditing) - return; - - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - // Only handle color change - { - float CurvePosition = FloatCurves[0].Keys[Idx].Time; - float PointPosition = MainPoint->GetPosition(); - - FLinearColor CurveColor = FLinearColor::Black; - FLinearColor PointColor = MainPoint->GetValue(); - - CurveColor.R = FloatCurves[0].Keys[Idx].Value; - CurveColor.G = FloatCurves[1].Keys[Idx].Value; - CurveColor.B = FloatCurves[2].Keys[Idx].Value; - - // Color is changed at Idx - if (CurveColor != PointColor || CurvePosition != PointPosition) - { - // Iterate through the all selected color ramp parameters - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - // The selected color ramp parameter is on auto update mode - - if (SelectedColorRamp->Points.IsValidIndex(Idx)) - { - // If the number of synced points in the selected color ramp is more or equal to that in the main parameter: - // Modify the corresponding synced point of the selected color ramp, and marked it as changed. - - UHoudiniParameterRampColorPoint* Point = SelectedColorRamp->Points[Idx]; - - if (!Point) - continue; - - if (Point->GetValue() != CurveColor && Point->ValueParentParm) - { - Point->SetValue(CurveColor); - Point->ValueParentParm->MarkChanged(true); - } - - if (Point->GetPosition() != CurvePosition && Point->PositionParentParm) - { - Point->SetPosition(CurvePosition); - Point->PositionParentParm->MarkChanged(true); - } - } - else - { - // If the number of synced points in the selected color ramp is less than that in the main parameter: - // Since we have push the points in the main parameter to all selected parameters, - // we need to modify the corresponding insert event. - - int32 IdxInEventsArray = Idx - SelectedColorRamp->Points.Num(); - - if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = SelectedColorRamp->ModificationEvents[IdxInEventsArray]; - - if (!Event) - continue; - - if (Event->InsertColor != CurveColor) - Event->InsertColor = CurveColor; - - if (Event->InsertPosition != CurvePosition) - Event->InsertPosition = CurvePosition; - } - } - } - else - { - // The selected color ramp is on manual update mode - // Since we have push the points in the main parameter to all selected parameters, - // modify the corresponding point in the cached points array of the selected color ramp. - if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampColorPoint* CachedPoint = SelectedColorRamp->CachedPoints[Idx]; - - if (!CachedPoint) - continue; - - if (CachedPoint->Value != CurveColor) - { - CachedPoint->Value = CurveColor; - bNeedUpdateEditor = true; - } - - if (CachedPoint->Position != CurvePosition) - { - CachedPoint->Position = CurvePosition; - SelectedColorRamp->bCaching = true; - bNeedUpdateEditor = true; - } - } - } - } - } - } - } - } - - - if (bNeedUpdateEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } -} - -template< class T > -bool FHoudiniParameterDetails::CastParameters( - TArray InParams, TArray& OutCastedParams ) -{ - for (auto CurrentParam : InParams) - { - T* CastedParam = Cast(CurrentParam); - if (CastedParam && !CastedParam->IsPendingKill()) - OutCastedParams.Add(CastedParam); - } - - return (OutCastedParams.Num() == InParams.Num()); -} - - -void -FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* InParam = InParams[0]; - if (!InParam || InParam->IsPendingKill()) - return; - - // The directory won't parse if parameter ids are -1 - // simply return - if (InParam->GetParmId() < 0) - return; - - // This parameter is a part of the last float ramp. - if (CurrentRampFloat) - { - CreateWidgetFloatRamp(HouParameterCategory, InParams); - return; - } - // This parameter is a part of the last float ramp. - if (CurrentRampColor) - { - CreateWidgetColorRamp(HouParameterCategory, InParams); - return; - } - - switch (InParam->GetParameterType()) - { - case EHoudiniParameterType::Float: - { - CreateWidgetFloat(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Int: - { - CreateWidgetInt(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::String: - { - CreateWidgetString(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::IntChoice: - case EHoudiniParameterType::StringChoice: - { - CreateWidgetChoice(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Separator: - { - TArray SepParams; - if (CastParameters(InParams, SepParams)) - { - bool bEnabled = InParams.IsValidIndex(0) ? !SepParams[0]->IsDisabled() : true; - CreateWidgetSeparator(HouParameterCategory, InParams, bEnabled); - } - } - break; - - case EHoudiniParameterType::Color: - { - CreateWidgetColor(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Button: - { - CreateWidgetButton(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - CreateWidgetButtonStrip(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Label: - { - CreateWidgetLabel(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Toggle: - { - CreateWidgetToggle(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - CreateWidgetFile(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::FolderList: - { - CreateWidgetFolderList(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Folder: - { - CreateWidgetFolder(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::MultiParm: - { - CreateWidgetMultiParm(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::FloatRamp: - { - CreateWidgetFloatRamp(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::ColorRamp: - { - CreateWidgetColorRamp(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Input: - { - CreateWidgetOperatorPath(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Invalid: - { - HandleUnsupportedParmType(HouParameterCategory, InParams); - } - break; - - default: - { - HandleUnsupportedParmType(HouParameterCategory, InParams); - } - break; - } - - // Remove a divider lines recurrsively if current parameter hits the end of a tabs - RemoveTabDividers(HouParameterCategory, InParam); - -} - -void -FHoudiniParameterDetails::CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory) -{ - FDetailWidgetRow & Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()); - TSharedPtr TabEndingRow = SNew(SCustomizedBox); - - TabEndingRow->DividerLinePositions = DividerLinePositions; - - if (TabEndingRow.IsValid()) - CurrentTabEndingRow = TabEndingRow.Get(); - - Row.WholeRowWidget.Widget = TabEndingRow.ToSharedRef(); - Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniParameterDetails::CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam|| MainParam->IsPendingKill()) - return; - - if (!Row) - return; - - TSharedRef< SCustomizedBox > HorizontalBox = SNew(SCustomizedBox); - - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - - - if (MainParam->IsDirectChildOfMultiParm()) - { - FString ParameterLabelStr = MainParam->GetParameterLabel(); - - // If it is head of an multiparm instance - if (MainParam->GetChildIndex() == 0) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - } - ParameterLabelStr += TEXT(" (") + FString("") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - HorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - else - { - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(MainParam->GetParameterLabel()) : FText::GetEmpty(); - HorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - - Row->NameWidget.Widget = HorizontalBox; -} - -void -FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) -{ - if (!Row) - return; - - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - FString ParameterLabelStr = MainParam->GetParameterLabel(); - TSharedRef HorizontalBox = SNew(SCustomizedBox); - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - - TSharedPtr VerticalBox; - HorizontalBox->AddSlot() - [ - SAssignNew(VerticalBox, SVerticalBox) - ]; - - if (MainParam->IsDirectChildOfMultiParm()) - { - // If it is head of an multiparm instance - if (MainParam->GetChildIndex() == 0) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - } - - ParameterLabelStr += TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor* RampParameter = Cast(MainParam); - if (RampParameter) - { - if (RampParameter->bCaching) - ParameterLabelStr += "*"; - } - } - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - else - { - // TODO: Refactor me...extend 'auto/manual update' to all parameters? (It only applies to color and float ramps for now.) - bool bParamNeedUpdate = false; - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor* RampParameter = Cast(MainParam); - if (RampParameter) - bParamNeedUpdate = RampParameter->bCaching; - } - else if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat* RampParameter = Cast(MainParam); - if (RampParameter) - bParamNeedUpdate = RampParameter->bCaching; - } - - if (bParamNeedUpdate) - ParameterLabelStr += "*"; - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - - auto IsAutoUpdateChecked = [MainParam]() - { - if (!MainParam || MainParam->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainParam->IsAutoUpdate() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - auto OnAutoUpdateCheckBoxStateChanged = [MainParam, InParams, bCookingEnabled](ECheckBoxState NewState) - { - if (NewState == ECheckBoxState::Checked) - { - for (auto & NextSelectedParam : InParams) - { - if (!NextSelectedParam) - continue; - - if (NextSelectedParam->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not allow mode change when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) - continue; - - switch (MainParam->GetParameterType()) - { - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* ColorRampParameter = Cast(NextSelectedParam); - - if (!ColorRampParameter) - continue; - - // Do not sync the selected color ramp parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) - continue; - - // Sync the Cached curve points at update mode switch. - FHoudiniParameterDetails::SyncCachedColorRampPoints(ColorRampParameter); - } - break; - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParameter = Cast(NextSelectedParam); - - if (!FloatRampParameter) - continue; - - // Do not sync the selected float ramp parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) - continue; - - // Sync the Cached curve points at update mode switch. - //FHoudiniParameterDetails::SyncCachedFloatRampPoints(FloatRampParameter); - FloatRampParameter->SyncCachedPoints(); - } - break; - - default: - break; - } - - NextSelectedParam->SetAutoUpdate(true); - } - } - else - { - for (auto & NextSelectedParam : InParams) - { - if (!NextSelectedParam) - continue; - - if (!(NextSelectedParam->IsAutoUpdate() && bCookingEnabled)) - continue; - - // Do not allow mode change when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) - continue; - - NextSelectedParam->SetAutoUpdate(false); - } - } - }; - - // Auto update check box - TSharedPtr CheckBox; - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(SHorizontalBox) - - + SHorizontalBox::Slot() - [ - SAssignNew(CheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([OnAutoUpdateCheckBoxStateChanged](ECheckBoxState NewState) - { - OnAutoUpdateCheckBoxStateChanged(NewState); - }) - .IsChecked_Lambda([IsAutoUpdateChecked]() - { - return IsAutoUpdateChecked(); - }) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("AutoUpdate", "Auto-update")) - .ToolTipText(LOCTEXT("AutoUpdateTip", "When enabled, this parameter will automatically update its value while editing. Turning this off will allow you to more easily update it, and the update can be pushed by checking the toggle again.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - if ((MainParam->GetParameterType() != EHoudiniParameterType::FloatRamp) && (MainParam->GetParameterType() != EHoudiniParameterType::ColorRamp)) - CheckBox->SetVisibility(EVisibility::Hidden); - - Row->NameWidget.Widget = HorizontalBox; -} - -FDetailWidgetRow* -FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount) -{ - if (InParams.Num() <= 0) - return nullptr; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return nullptr; - - // Created row for the current parameter (if there is not a row created, do not show the parameter). - FDetailWidgetRow* Row = nullptr; - - // Current parameter is in a multiparm instance (directly) - if (MainParam->IsDirectChildOfMultiParm()) - { - int32 ParentMultiParmId = MainParam->GetParentParmId(); - - // If this is a folder param, its folder list parent parm is the multiparm - if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) - { - if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) // This should not happen - return nullptr; - - UHoudiniParameterFolderList* ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); - if (!ParentFolderList || ParentFolderList->IsPendingKill()) - return nullptr; // This should not happen - - ParentMultiParmId = ParentFolderList->GetParentParmId(); - } - - if (!AllMultiParms.Contains(ParentMultiParmId)) // This should not happen normally - return nullptr; - - // Get the parent multiparm - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentMultiParmId]; - - // The parent multiparm is visible. - if (ParentMultiParm && ParentMultiParm->IsShown() && MainParam->ShouldDisplay()) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - - } - // This item is not a direct child of a multiparm. - else - { - bool bIsFolder = MainParam->GetParameterType() == EHoudiniParameterType::Folder; - - // If this parameter is a folder, its parent folder should be the second top of the stack - int32 NestedMinStackDepth = bIsFolder ? 1 : 0; - - // Current parameter is inside a folder. - if (FolderStack.Num() > NestedMinStackDepth) - { - // If the current parameter is a folder, we take the top second queue on the stack, since the top one represents itself. - // Otherwise take the top queue on the stack. - TArray & CurrentLayerFolderQueue = bIsFolder ? - FolderStack[FolderStack.Num() - 2] : FolderStack.Last(); - - if (CurrentLayerFolderQueue.Num() <= 0) // Error state - return nullptr; - - bool bParentFolderVisible = CurrentLayerFolderQueue[0]->IsContentShown(); - - bool bIsSelectedTabVisible = false; - - // If its parent folder is visible, display current parameter, - // Otherwise, just prune the stacks. - if (bParentFolderVisible) - { - int32 ParentFolderId = MainParam->GetParentParmId(); - - // If the current parameter is a folder, its parent is a folderlist. - // So we need to continue to get the parent of the folderlist. - if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) - { - if (AllFoldersAndFolderLists.Contains(ParentFolderId)) - ParentFolderId = AllFoldersAndFolderLists[ParentFolderId]->GetParentParmId(); - else - return nullptr; // error state - } - - UHoudiniParameterFolder* ParentFolder = nullptr; - - if (AllFoldersAndFolderLists.Contains(ParentFolderId)) - ParentFolder = Cast(AllFoldersAndFolderLists[ParentFolderId]); - - bool bShouldDisplayRow = MainParam->ShouldDisplay(); - - // This row should be shown if its parent folder is shown. - if (ParentFolder) - bShouldDisplayRow &= (ParentFolder->IsTab() && ParentFolder->IsChosen()) || (!ParentFolder->IsTab() && ParentFolder->IsExpanded()); - - if (bShouldDisplayRow) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - } - - // prune the stack finally - if (bDecreaseChildCount) - { - CurrentLayerFolderQueue[0]->GetChildCounter() -= 1; - PruneStack(); - } - } - // If this parameter is in the root dir, just create a row. - else - { - if (MainParam->ShouldDisplay()) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - } - } - - if (!MainParam->IsVisible()) - return nullptr; - - - if (Row) - CurrentTabEndingRow = nullptr; - - return Row; -} - -void -FHoudiniParameterDetails::HandleUnsupportedParmType(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - CreateNestedRow(HouParameterCategory, (TArray)InParams); -} - -void -FHoudiniParameterDetails::CreateWidgetFloat( - IDetailCategoryBuilder & HouParameterCategory, - TArray& InParams ) -{ - TArray FloatParams; - if (!CastParameters(InParams, FloatParams)) - return; - - if (FloatParams.Num() <= 0) - return; - - UHoudiniParameterFloat* MainParam = FloatParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - // Helper function to find a unit from a string (name or abbreviation) - auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); - EUnit Unit = EUnit::Unspecified; - if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) - Unit = ParmUnit.GetValue(); - - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - // Lambdas for slider begin - auto SliderBegin = [&](TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), - FloatParams[0]->GetOuter()); - - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - FloatParams[Idx]->Modify(); - } - }; - - // Lambdas for slider end - auto SliderEnd = [&](TArray FloatParams) - { - // Mark the value as changed to trigger an update - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - FloatParams[Idx]->MarkChanged(true); - } - }; - - // Lambdas for changing the parameter value - auto ChangeFloatValueAt = [&](const float& Value, const int32& Index, const bool& DoChange, TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), - FloatParams[0]->GetOuter() ); - - bool bChanged = false; - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - FloatParams[Idx]->Modify(); - if (FloatParams[Idx]->SetValueAt(Value, Index)) - { - // Only mark the param has changed if DoChange is true!!! - if(DoChange) - FloatParams[Idx]->MarkChanged(true); - bChanged = true; - } - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if no parameter's value has actually been changed - Transaction.Cancel(); - } - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Revert to default value"), - FloatParams[0]->GetOuter()); - - if (TupleIndex < 0) - { - for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - if (FloatParams[Idx]->IsDefault()) - continue; - - FloatParams[Idx]->RevertToDefault(-1); - } - } - else - { - for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - if (FloatParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - FloatParams[Idx]->RevertToDefault(TupleIndex); - } - } - return FReply::Handled(); - }; - - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - //TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - if (MainParam->GetTupleSize() == 3) - { - // Should we swap Y and Z fields (only relevant for Vector3) - // Ignore the swapping if that parameter has the noswap tag - bool SwapVector3 = !MainParam->GetNoSwap(); - - auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float & Val) - { - ChangeFloatValueAt(Val, 0, true, FloatParams); - ChangeFloatValueAt(Val, 1, true, FloatParams); - ChangeFloatValueAt(Val, 2, true, FloatParams); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, 0))) - .Y(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 2 : 1))) - .Z(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 1 : 2))) - .OnXCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); - else - ChangeFloatValueAt( Val, 0, true, FloatParams); - }) - .OnYCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); - else - ChangeFloatValueAt( Val, SwapVector3 ? 2 : 1, true, FloatParams); - }) - .OnZCommitted_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); - else - ChangeFloatValueAt( Val, SwapVector3 ? 1 : 2, true, FloatParams); - }) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("FloatParameterLockButtonToolTip", "When locked, change the vector value uniformly.")) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(MainParam->IsUniformLocked() ? FEditorStyle::GetBrush("Genericlock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([FloatParams, MainParam]() - { - if (!MainParam || MainParam->IsPendingKill()) - return FReply::Handled(); - - for (auto & CurParam : FloatParams) - { - if (!CurParam || CurParam->IsPendingKill()) - continue; - - CurParam->SwitchUniformLock(); - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - - return FReply::Handled(); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([FloatParams]() - { - for (auto & SelectedParam : FloatParams) - { - if (!SelectedParam) - continue; - - if (!SelectedParam->IsDefault()) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([FloatParams, RevertToDefault]() { return RevertToDefault(-1, FloatParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - else - { - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - TSharedPtr> NumericEntryBox; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< float >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - - .MinValue(MainParam->GetMin()) - .MaxValue(MainParam->GetMax()) - - .MinSliderValue(MainParam->GetUIMin()) - .MaxSliderValue(MainParam->GetUIMax()) - - .Value(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, Idx))) - .OnValueChanged_Lambda([=](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) - .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) - .OnBeginSliderMovement_Lambda([=]() { SliderBegin(FloatParams); }) - .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(FloatParams); }) - .SliderExponent(MainParam->IsLogarithmic() ?8.0f : 1.0f) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .OnClicked_Lambda([Idx, FloatParams, RevertToDefault]() { return RevertToDefault(Idx, FloatParams); }) - .Visibility_Lambda([Idx, FloatParams]() - { - for (auto & SelectedParam :FloatParams) - { - if (!SelectedParam) - continue; - - if (!SelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - } - } - - Row->ValueWidget.Widget =VerticalBox; - - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetInt(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray IntParams; - if (!CastParameters(InParams, IntParams)) - - if (IntParams.Num() <= 0) - return; - - UHoudiniParameterInt* MainParam = IntParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - // Helper function to find a unit from a string (name or abbreviation) - auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); - EUnit Unit = EUnit::Unspecified; - if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) - Unit = ParmUnit.GetValue(); - - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - // Lambda for slider begin - auto SliderBegin = [&](TArray IntParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), - IntParams[0]->GetOuter()); - - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - IntParams[Idx]->Modify(); - } - }; - - // Lambda for slider end - auto SliderEnd = [&](TArray IntParams) - { - // Mark the value as changed to trigger an update - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - IntParams[Idx]->MarkChanged(true); - } - }; - - // Lambda for changing the parameter value - auto ChangeIntValueAt = [&](const int32& Value, const int32& Index, const bool& DoChange, TArray IntParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), - IntParams[0]->GetOuter()); - - bool bChanged = false; - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - if (!IntParams[Idx]) - continue; - - IntParams[Idx]->Modify(); - if (IntParams[Idx]->SetValueAt(Value, Index)) - { - // Only mark the param has changed if DoChange is true!!! - if (DoChange) - IntParams[Idx]->MarkChanged(true); - bChanged = true; - } - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if there is no param has actually been changed - Transaction.Cancel(); - } - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray IntParams) - { - for (int32 Idx = 0; Idx < IntParams.Num(); Idx++) - { - if (!IntParams[Idx]) - continue; - - if (IntParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - IntParams[Idx]->RevertToDefault(TupleIndex); - } - - return FReply::Handled(); - }; - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - - .MinValue(MainParam->GetMin()) - .MaxValue(MainParam->GetMax()) - - .MinSliderValue(MainParam->GetUIMin()) - .MaxSliderValue(MainParam->GetUIMax()) - - .Value( TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterInt::GetValue, Idx))) - .OnValueChanged_Lambda( [=](int32 Val) { ChangeIntValueAt(Val, Idx, false, IntParams); } ) - .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeIntValueAt(Val, Idx, true, IntParams); }) - .OnBeginSliderMovement_Lambda( [=]() { SliderBegin(IntParams); }) - .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(IntParams); }) - .SliderExponent(MainParam->IsLogarithmic() ? 8.0f : 1.0f) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, IntParams]() - { - for (auto & NextSelectedParam : IntParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, IntParams, RevertToDefault]() { return RevertToDefault(Idx, IntParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - /* - if (NumericEntryBox.IsValid()) - NumericEntryBox->SetEnabled(!MainParam->IsDisabled()); - */ - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray StringParams; - if (!CastParameters(InParams, StringParams)) - return; - - if (StringParams.Num() <= 0) - return; - - UHoudiniParameterString* MainParam = StringParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - bool bIsMultiLine = false; - bool bIsUnrealRef = false; - UClass* UnrealRefClass = UObject::StaticClass(); - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - TMap& Tags = MainParam->GetTags(); - if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_TAG) && FCString::Atoi(*Tags[HOUDINI_PARAMETER_STRING_REF_TAG]) == 1) - { - bIsUnrealRef = true; - - if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_CLASS_TAG)) - { - UClass * FoundClass = FindObject(ANY_PACKAGE, *Tags[HOUDINI_PARAMETER_STRING_REF_CLASS_TAG]); - if (FoundClass != nullptr) - { - UnrealRefClass = FoundClass; - } - } - } - - if (Tags.Contains(HOUDINI_PARAMETER_STRING_MULTILINE_TAG)) - { - bIsMultiLine = true; - } - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - // Lambda for changing the parameter value - auto ChangeStringValueAt = [&](const FString& Value, UObject* ChosenObj, const int32& Index, const bool& DoChange, TArray StringParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterSrtingChange", "Houdini Parameter String: Changing a value"), - StringParams[0]->GetOuter()); - - bool bChanged = false; - for (int Idx = 0; Idx < StringParams.Num(); Idx++) - { - if (!StringParams[Idx]) - continue; - - StringParams[Idx]->Modify(); - if (StringParams[Idx]->SetValueAt(Value, Index)) - { - StringParams[Idx]->MarkChanged(true); - bChanged = true; - } - - StringParams[Idx]->SetAssetAt(ChosenObj, Index); - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if there is no param actually has been changed - Transaction.Cancel(); - } - - FHoudiniEngineUtils::UpdateEditorProperties(StringParams[0], false); - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray StringParams) - { - for (int32 Idx = 0; Idx < StringParams.Num(); Idx++) - { - if (!StringParams[Idx]) - continue; - - if (StringParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - StringParams[Idx]->RevertToDefault(TupleIndex); - } - - return FReply::Handled(); - }; - - if (bIsUnrealRef) - { - TSharedPtr< SEditableTextBox > EditableTextBox; - TSharedPtr< SHorizontalBox > HorizontalBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([UnrealRefClass](const UObject* InObject) - { - return InObject->IsA(UnrealRefClass); - }) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - ChangeStringValueAt(ReferenceStr, InObject, Idx, true, StringParams); - }) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - // Thumbnail - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouParameterCategory.GetParentLayout().GetThumbnailPool(); - - // Create a thumbnail for the selected object / class - UObject* EditObject = nullptr; - const FString AssetPath = MainParam->GetValueAt(Idx); - EditObject = StaticFindObject(nullptr, nullptr, *AssetPath, true); - - FAssetData AssetData; - if (IsValid(EditObject)) - { - AssetData = FAssetData(EditObject); - } - else - { - AssetData.AssetClass = UnrealRefClass->GetFName(); - } - - TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = MakeShareable( - new FAssetThumbnail(AssetData, 64, 64, AssetThumbnailPool)); - - TSharedPtr ThumbnailBorder; - HorizontalBox->AddSlot().Padding(0.f, 0.f, 2.f, 0.f).AutoWidth() - [ - SAssignNew(ThumbnailBorder, SBorder) - .OnMouseDoubleClick_Lambda([EditObject, Idx](const FGeometry&, const FPointerEvent&) - { - if (EditObject && GEditor) - GEditor->EditObject(EditObject); - - return FReply::Handled(); - }) - .Padding(5.f) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - ThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush * >::Create( - TAttribute< const FSlateBrush * >::FGetter::CreateLambda([ThumbnailBorder]() - { - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ))); - - FText MeshNameText = FText::GetEmpty(); - //if (InputObject) - // MeshNameText = FText::FromString(InputObject->GetName()); - - TSharedPtr StaticMeshComboButton; - - TSharedPtr ButtonBox; - HorizontalBox->AddSlot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .HAlign(HAlign_Fill) - [ - SAssignNew(ButtonBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SAssignNew(StaticMeshComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromName(AssetData.AssetName)) - .ToolTipText(FText::FromString(MainParam->GetValueAt(Idx))) - ] - ] - ] - ]; - - StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda([UnrealRefClass, StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() - { - TArray AllowedClasses; - AllowedClasses.Add(UnrealRefClass); - TArray NewAssetFactories; - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(nullptr), - true, - AllowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) - { - UObject * Object = AssetData.GetAsset(); - - // Get the asset reference string for this object - // !! Accept null objects to allow clearing the asset picker !! - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); - - StaticMeshComboButton->SetIsOpen(false); - ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); - }), - FSimpleDelegate::CreateLambda([]() {})); - }) - ); - } - else if (bIsMultiLine) - { - TSharedPtr< SMultiLineEditableTextBox > MultiLineEditableTextBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) - {return true;}) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - FString NewString = ReferenceStr; - if (StringParams[0]->GetValueAt(Idx).Len() > 0) - NewString = StringParams[0]->GetValueAt(Idx) + "\n" + NewString; - - ChangeStringValueAt(NewString, nullptr, Idx, true, StringParams); - }) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Top).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(MultiLineEditableTextBox, SMultiLineEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainParam->GetValueAt(Idx))) - .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, StringParams]() - { - for (auto & NextSelectedParam : StringParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() { return RevertToDefault(Idx, StringParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - else - { - TSharedPtr< SEditableTextBox > EditableTextBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) - {return true;}) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - ChangeStringValueAt(ReferenceStr, nullptr, Idx, true, StringParams); - }) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(EditableTextBox, SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainParam->GetValueAt(Idx))) - .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) - { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, StringParams]() - { - for (auto & NextSelectedParam : StringParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() - { return RevertToDefault(Idx, StringParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ColorParams; - if (!CastParameters(InParams, ColorParams)) - return; - - if (ColorParams.Num() <= 0) - return; - - UHoudiniParameterColor* MainParam = ColorParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - bool bHasAlpha = (MainParam->GetTupleSize() == 4); - - // Add color picker UI. - TSharedPtr ColorBlock; - TSharedRef VerticalBox = SNew(SVerticalBox); - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(ColorBlock, SColorBlock) - .Color(MainParam->GetColorValue()) - .ShowBackgroundForAlpha(bHasAlpha) - .OnMouseButtonDown(FPointerEventHandler::CreateLambda( - [MainParam, ColorParams, ColorBlock, bHasAlpha](const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) - { - if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) - return FReply::Unhandled(); - - FColorPickerArgs PickerArgs; - PickerArgs.ParentWidget = ColorBlock; - PickerArgs.bUseAlpha = bHasAlpha; - PickerArgs.DisplayGamma = TAttribute< float >::Create( - TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), - MainParam->GetOuter(), true); - - bool bChanged = false; - for (auto & Param : ColorParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetColorValue(InColor)) - { - Param->MarkChanged(true); - bChanged = true; - } - } - - // cancel the transaction if there is actually no value changed - if (!bChanged) - { - Transaction.Cancel(); - } - - }); - PickerArgs.InitialColorOverride = MainParam->GetColorValue(); - PickerArgs.bOnlyRefreshOnOk = true; - OpenColorPicker(PickerArgs); - return FReply::Handled(); - })) - ]; - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetButton(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ButtonParams; - if (!CastParameters(InParams, ButtonParams)) - return; - - if (ButtonParams.Num() <= 0) - return; - - UHoudiniParameterButton* MainParam = ButtonParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - FText ParameterTooltip = GetParameterTooltip(MainParam); - - TSharedRef HorizontalBox = SNew(SHorizontalBox); - TSharedPtr Button; - - // Add button UI. - HorizontalBox->AddSlot().Padding(1, 2, 4, 2) - [ - SAssignNew(Button, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ParameterLabelText) - .ToolTipText(ParameterTooltip) - .OnClicked(FOnClicked::CreateLambda( [MainParam, ButtonParams]() - { - for (auto & Param : ButtonParams) - { - if (!Param) - continue; - - // There is no undo redo operation for button - Param->MarkChanged(true); - } - - return FReply::Handled(); - })) - ]; - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ButtonStripParams; - if (!CastParameters(InParams, ButtonStripParams)) - return; - - if (ButtonStripParams.Num() <= 0) - return; - - UHoudiniParameterButtonStrip* MainParam = ButtonStripParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - if (!Row) - return; - - auto OnButtonStateChanged = [MainParam, ButtonStripParams](ECheckBoxState NewState, int32 Idx) - { - - /* - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterButtonStripChange", "Houdini Parameter Button Strip: Changing value"), - MainParam->GetOuter(), true); - */ - int32 StateInt = NewState == ECheckBoxState::Checked ? 1 : 0; - bool bChanged = false; - - for (auto & NextParam : ButtonStripParams) - { - if (!NextParam || NextParam->IsPendingKill()) - continue; - - if (!NextParam->Values.IsValidIndex(Idx)) - continue; - - //NextParam->Modify(); - if (NextParam->SetValueAt(Idx, StateInt)) - { - NextParam->MarkChanged(true); - bChanged = true; - } - } - - //if (!bChanged) - // Transaction.Cancel(); - - }; - - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - FText ParameterTooltip = GetParameterTooltip(MainParam); - - TSharedRef HorizontalBox = SNew(SHorizontalBox); - FLinearColor BgColor(0.53f, 0.81f, 0.82f, 1.0f); // Sky Blue Backgroud color - - for (int32 Idx = 0; Idx < MainParam->Count; ++Idx) - { - if (!MainParam->Values.IsValidIndex(Idx) || !MainParam->Labels.IsValidIndex(Idx)) - continue; - - bool bPressed = MainParam->Values[Idx] > 0; - FText LabelText = FText::FromString(MainParam->Labels[Idx]); - - TSharedPtr Button; - - HorizontalBox->AddSlot().Padding(0).FillWidth(1.0f) - [ - SAssignNew(Button, SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") - .IsChecked(bPressed ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - .OnCheckStateChanged_Lambda([OnButtonStateChanged, Idx](ECheckBoxState NewState) - { - OnButtonStateChanged(NewState, Idx); - }) - .Content() - [ - SNew(STextBlock) - .Text(LabelText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - Button->SetColorAndOpacity(BgColor); - } - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetLabel(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray LabelParams; - if (!CastParameters(InParams, LabelParams)) - return; - - if (LabelParams.Num() <= 0) - return; - - UHoudiniParameterLabel* MainParam = LabelParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - - for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) - { - FString NextLabelString = MainParam->GetStringAtIndex(Index); - FText ParameterLabelText = FText::FromString(NextLabelString); - - TSharedPtr TextBlock; - - // Add Label UI. - VerticalBox->AddSlot().Padding(1, 2, 4, 2) - [ - SAssignNew(TextBlock, STextBlock).Text(ParameterLabelText) - ]; - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetToggle(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray ToggleParams; - if (!CastParameters(InParams, ToggleParams)) - return; - - if (ToggleParams.Num() <= 0) - return; - - UHoudiniParameterToggle* MainParam = ToggleParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - - TSharedRef VerticalBox = SNew(SVerticalBox); - auto IsToggleCheckedLambda = [MainParam](int32 Index) - { - if (Index >= MainParam->GetNumValues()) - return ECheckBoxState::Unchecked; - - if (MainParam->GetValueAt(Index)) - return ECheckBoxState::Checked; - - return ECheckBoxState::Unchecked; - }; - - auto OnToggleCheckStateChanged = [MainParam, ToggleParams](ECheckBoxState NewState, int32 Index) - { - if (Index >= MainParam->GetNumValues()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterToggleChange", "Houdini Parameter Toggle: Changing value"), - MainParam->GetOuter(), true); - - bool bState = (NewState == ECheckBoxState::Checked); - - bool bChanged = false; - for (auto & Param : ToggleParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetValueAt(bState, Index)) - { - bChanged = true; - Param->MarkChanged(true); - } - } - - // Cancel the transaction if no parameter has actually been changed - if (!bChanged) - { - Transaction.Cancel(); - } - }; - - for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) - { - TSharedPtr< SCheckBox > CheckBox; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - [ - SAssignNew(CheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([OnToggleCheckStateChanged, Index](ECheckBoxState NewState) { - OnToggleCheckStateChanged(NewState, Index); - - }) - .IsChecked_Lambda([IsToggleCheckedLambda, Index]() { - return IsToggleCheckedLambda(Index); - }) - .Content() - [ - SNew(STextBlock) - .Text(ParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FileParams; - if (!CastParameters(InParams, FileParams)) - return; - - if (FileParams.Num() <= 0) - return; - - UHoudiniParameterFile* MainParam = FileParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - - FString FileTypeWidgetFilter = TEXT("All files (*.*)|*.*"); - if (!MainParam->GetFileFilters().IsEmpty()) - FileTypeWidgetFilter = FString::Printf(TEXT("%s files (%s)|%s"), *MainParam->GetFileFilters(), *MainParam->GetFileFilters(), *MainParam->GetFileFilters()); - - FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); - - auto UpdateCheckRelativePath = [MainParam](const FString & PickedPath) - { - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(MainParam->GetOuter()); - if (MainParam->GetOuter() && !PickedPath.IsEmpty() && FPaths::IsRelative(PickedPath)) - { - // Check if the path is relative to the UE4 project - FString AbsolutePath = FPaths::ConvertRelativePathToFull(PickedPath); - if (FPaths::FileExists(AbsolutePath)) - { - return AbsolutePath; - } - - // Check if the path is relative to the asset - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) - { - if (HoudiniAssetComponent->HoudiniAsset && !HoudiniAssetComponent->HoudiniAsset->IsPendingKill()) - { - FString AssetFilePath = FPaths::GetPath(HoudiniAssetComponent->HoudiniAsset->AssetFileName); - if (FPaths::FileExists(AssetFilePath)) - { - FString UpdatedFileWidgetPath = FPaths::Combine(*AssetFilePath, *PickedPath); - if (FPaths::FileExists(UpdatedFileWidgetPath)) - { - return UpdatedFileWidgetPath; - } - } - } - } - } - - return PickedPath; - }; - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - FString FileWidgetPath = MainParam->GetValueAt(Idx); - FString FileWidgetBrowsePath = BrowseWidgetDirectory; - - if (!FileWidgetPath.IsEmpty()) - { - FString FileWidgetDirPath = FPaths::GetPath(FileWidgetPath); - if (!FileWidgetDirPath.IsEmpty()) - FileWidgetBrowsePath = FileWidgetDirPath; - } - - bool IsDirectoryPicker = MainParam->GetParameterType() == EHoudiniParameterType::FileDir; - bool bIsNewFile = !MainParam->IsReadOnly(); - - FText BrowseTooltip = LOCTEXT("FileButtonToolTipText", "Choose a file from this computer"); - if (IsDirectoryPicker) - BrowseTooltip = LOCTEXT("DirButtonToolTipText", "Choose a directory from this computer"); - - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SNewFilePathPicker) - .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) - .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") - .BrowseButtonToolTip(BrowseTooltip) - .BrowseDirectory(FileWidgetBrowsePath) - .BrowseTitle(LOCTEXT("PropertyEditorTitle", "File picker...")) - .FilePath(FileWidgetPath) - .FileTypeFilter(FileTypeWidgetFilter) - .IsNewFile(bIsNewFile) - .IsDirectoryPicker(IsDirectoryPicker) - .ToolTipText_Lambda([MainParam]() - { - // return the current param value as a tooltip - FString FileValue = MainParam ? MainParam->GetValueAt(0) : FString(); - return FText::FromString(FileValue); - }) - .OnPathPicked(FOnPathPicked::CreateLambda([MainParam, FileParams, UpdateCheckRelativePath, Idx](const FString & PickedPath) - { - if (MainParam->GetNumValues() <= Idx) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFileChange", "Houdini Parameter File: Changing a file path"), - MainParam->GetOuter(), true); - - bool bChanged = false; - - for (auto & Param : FileParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetValueAt(UpdateCheckRelativePath(PickedPath), Idx)) - { - bChanged = true; - Param->MarkChanged(true); - } - } - - // Cancel the transaction if no value has actually been changed - if (!bChanged) - { - Transaction.Cancel(); - } - })) - ] - ]; - - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - - -void -FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ChoiceParams; - if (!CastParameters(InParams, ChoiceParams)) - return; - - if (ChoiceParams.Num() <= 0) - return; - - UHoudiniParameterChoice* MainParam = ChoiceParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - // Lambda for changing the parameter value - auto ChangeSelectionLambda = [ChoiceParams](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterChoiceChange", "Houdini Parameter Choice: Changing selection"), - ChoiceParams[0]->GetOuter()); - - const int32 NewIntValue = ChoiceParams[0]->GetIntValueFromLabel(*NewChoice); - - bool bChanged = false; - for (int Idx = 0; Idx < ChoiceParams.Num(); Idx++) - { - if (!ChoiceParams[Idx]) - continue; - - ChoiceParams[Idx]->Modify(); - if (ChoiceParams[Idx]->SetIntValue(NewIntValue)) - { - bChanged = true; - ChoiceParams[Idx]->MarkChanged(true); - ChoiceParams[Idx]->UpdateStringValueFromInt(); - } - } - - if (!bChanged) - { - // Cancel the transaction if no parameter was changed - Transaction.Cancel(); - } - }; - - // - MainParam->UpdateChoiceLabelsPtr(); - TArray>* OptionSource = MainParam->GetChoiceLabelsPtr(); - TSharedPtr IntialSelec; - if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValue())) - { - IntialSelec = (*OptionSource)[MainParam->GetIntValue()]; - } - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBox; - HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) - [ - SAssignNew( ComboBox, SComboBox< TSharedPtr< FString > > ) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - []( TSharedPtr< FString > InItem ) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [ChangeSelectionLambda](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - ChangeSelectionLambda(NewChoice, SelectType); - }) - [ - SNew(STextBlock) - .Text_Lambda([MainParam]() { return FText::FromString(MainParam->GetLabel()); }) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - ]; - - if ( ComboBox.IsValid() ) - ComboBox->SetEnabled( !MainParam->IsDisabled() ); - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetSeparator(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - TSharedRef HorizontalBox = SNew(SCustomizedBox); - - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - - Row->WholeRowWidget.Widget = HorizontalBox; -} - -void -FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray OperatorPathParams; - if (!CastParameters(InParams, OperatorPathParams)) - return; - - if (OperatorPathParams.Num() <= 0) - return; - - UHoudiniParameterOperatorPath* MainParam = OperatorPathParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - UHoudiniInput* MainInput = MainParam->HoudiniInput.Get(); - if (!MainInput) - return; - - // Build an array of edited inputs for multi edition - TArray EditedInputs; - EditedInputs.Add(MainInput); - - // Add the corresponding inputs found in the other HAC - for (int LinkedIdx = 1; LinkedIdx < OperatorPathParams.Num(); LinkedIdx++) - { - UHoudiniInput* LinkedInput = OperatorPathParams[LinkedIdx]->HoudiniInput.Get(); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if (!LinkedInput->Matches(*MainInput)) - continue; - - EditedInputs.Add(LinkedInput); - } - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FHoudiniInputDetails::CreateWidget(HouParameterCategory, EditedInputs, Row); - - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetFloatRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Parsing a float ramp: 1->(2->3->4)*->5 // - switch (MainParam->GetParameterType()) - { - //*****State 1: Float Ramp*****// - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); - if (FloatRampParameter) - { - CurrentRampFloat = FloatRampParameter; - CurrentRampFloatPointsArray.Empty(); - - CurrentRampParameterList = InParams; - - FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - CurrentRampRow = Row; - } - break; - } - - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParameter = Cast(MainParam); - if (FloatParameter) - { - bool bCreateNewPoint = true; - if (CurrentRampFloatPointsArray.Num() > 0) - { - UHoudiniParameterRampFloatPoint* LastPtInArr = CurrentRampFloatPointsArray.Last(); - if (LastPtInArr && !LastPtInArr->ValueParentParm) - bCreateNewPoint = false; - } - - //*****State 2: Float Parameter (position)*****// - if (bCreateNewPoint) - { - UHoudiniParameterRampFloatPoint* NewRampFloatPoint = nullptr; - - int32 PointIndex = CurrentRampFloatPointsArray.Num(); - if (CurrentRampFloat->Points.IsValidIndex(PointIndex)) - { - - // TODO: We should reuse existing point objects, if they exist. Currently - // this causes results in unexpected behaviour in other parts of this detail code. - // Give this code a bit of an overhaul at some point. - // NewRampFloatPoint = CurrentRampFloat->Points[PointIndex]; - } - - if (!NewRampFloatPoint) - { - // Create a new float ramp point, and add its pointer to the current float points buffer array. - NewRampFloatPoint = NewObject< UHoudiniParameterRampFloatPoint >(CurrentRampFloat, FName(), CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); - - } - - CurrentRampFloatPointsArray.Add(NewRampFloatPoint); - - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Set the float ramp point's position parent parm, and value - NewRampFloatPoint->PositionParentParm = FloatParameter; - NewRampFloatPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); - } - //*****State 3: Float Parameter (value)*****// - else - { - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Get the last point in the buffer array - if (CurrentRampFloatPointsArray.Num() > 0) - { - // Set the last inserted float ramp point's float parent parm, and value - UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); - LastAddedFloatRampPoint->ValueParentParm = FloatParameter; - LastAddedFloatRampPoint->SetValue(FloatParameter->GetValuesPtr()[0]); - } - } - } - - break; - } - //*****State 4: Choice parameter*****// - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); - if (ChoiceParameter && CurrentRampFloatPointsArray.Num() > 0) - { - // Set the last inserted float ramp point's interpolation parent parm, and value - UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); - - LastAddedFloatRampPoint->InterpolationParentParm = ChoiceParameter; - LastAddedFloatRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); - - // Set the index of this point in the multi parm. - LastAddedFloatRampPoint->InstanceIndex = CurrentRampFloatPointsArray.Num() - 1; - } - - - //*****State 5: All ramp points have been parsed, finish!*****// - if (CurrentRampFloatPointsArray.Num() >= (int32)CurrentRampFloat->MultiParmInstanceCount) - { - CurrentRampFloatPointsArray.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { - return P1.Position < P2.Position; - }); - - CurrentRampFloat->Points = CurrentRampFloatPointsArray; - - // Not caching, points are synced, update cached points - if (!CurrentRampFloat->bCaching) - { - const int32 NumPoints = CurrentRampFloat->Points.Num(); - CurrentRampFloat->CachedPoints.SetNum(NumPoints); - for (int32 i = 0; i < NumPoints; ++i) - { - UHoudiniParameterRampFloatPoint* FromPoint = CurrentRampFloat->Points[i]; - UHoudiniParameterRampFloatPoint* ToPoint = CurrentRampFloat->CachedPoints[i]; - ToPoint = nullptr; - check(FromPoint) - if (!ToPoint) - { - ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampFloat, RF_NoFlags, CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); - } - else - { - ToPoint->CopyStateFrom(FromPoint, true); - } - CurrentRampFloat->CachedPoints[i] = ToPoint; - } - } - - CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampFloat, CurrentRampParameterList); - - CurrentRampFloat->SetDefaultValues(); - - CurrentRampFloat = nullptr; - CurrentRampRow = nullptr; - } - - break; - } - - default: - break; - } - -} - -void -FHoudiniParameterDetails::CreateWidgetColorRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Parsing a color ramp: 1->(2->3->4)*->5 // - switch (MainParam->GetParameterType()) - { - //*****State 1: Color Ramp*****// - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* RampColor = Cast(MainParam); - if (RampColor) - { - CurrentRampColor = RampColor; - CurrentRampColorPointsArray.Empty(); - - CurrentRampParameterList = InParams; - - FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - CurrentRampRow = Row; - } - - break; - } - //*****State 2: Float parameter*****// - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParameter = Cast(MainParam); - if (FloatParameter) - { - // Create a new color ramp point, and add its pointer to the current color points buffer array. - UHoudiniParameterRampColorPoint* NewRampColorPoint = nullptr; - int32 PointIndex = CurrentRampColorPointsArray.Num(); - - if (CurrentRampColor->Points.IsValidIndex(PointIndex)) - { - NewRampColorPoint = CurrentRampColor->Points[PointIndex]; - } - - if (!NewRampColorPoint) - { - NewRampColorPoint = NewObject< UHoudiniParameterRampColorPoint >(CurrentRampColor, FName(), CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); - } - - CurrentRampColorPointsArray.Add(NewRampColorPoint); - - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Set the color ramp point's position parent parm, and value - NewRampColorPoint->PositionParentParm = FloatParameter; - NewRampColorPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); - } - - break; - } - //*****State 3: Color parameter*****// - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* ColorParameter = Cast(MainParam); - if (ColorParameter && CurrentRampColorPointsArray.Num() > 0) - { - // Set the last inserted color ramp point's color parent parm, and value - UHoudiniParameterRampColorPoint* LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); - LastAddedColorRampPoint->ValueParentParm = ColorParameter; - LastAddedColorRampPoint->SetValue(ColorParameter->GetColorValue()); - } - - break; - } - //*****State 4: Choice Parameter*****// - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); - if (ChoiceParameter) - { - // Set the last inserted color ramp point's interpolation parent parm, and value - UHoudiniParameterRampColorPoint*& LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); - - LastAddedColorRampPoint->InterpolationParentParm = ChoiceParameter; - LastAddedColorRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); - - // Set the index of this point in the multi parm. - LastAddedColorRampPoint->InstanceIndex = CurrentRampColorPointsArray.Num() - 1; - } - - - //*****State 5: All ramp points have been parsed, finish!*****// - if (CurrentRampColorPointsArray.Num() >= (int32)CurrentRampColor->MultiParmInstanceCount) - { - CurrentRampColorPointsArray.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) - { - return P1.Position < P2.Position; - }); - - CurrentRampColor->Points = CurrentRampColorPointsArray; - - // Not caching, points are synced, update cached points - - if (!CurrentRampColor->bCaching) - { - const int32 NumPoints = CurrentRampColor->Points.Num(); - CurrentRampColor->CachedPoints.SetNum(NumPoints); - - for (int32 i = 0; i < NumPoints; ++i) - { - UHoudiniParameterRampColorPoint* FromPoint = CurrentRampColor->Points[i]; - UHoudiniParameterRampColorPoint* ToPoint = CurrentRampColor->CachedPoints[i]; - - if (!ToPoint) - { - ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampColor, RF_NoFlags, CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); - } - else - { - ToPoint->CopyStateFrom(FromPoint, true); - } - CurrentRampColor->CachedPoints[i] = ToPoint; - } - } - - - CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampColor, CurrentRampParameterList); - - CurrentRampColor->SetDefaultValues(); - - CurrentRampColor = nullptr; - CurrentRampRow = nullptr; - } - - break; - } - - default: - break; - } - -} - - -FDetailWidgetRow* -FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() <= 0) - return nullptr; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam) - return nullptr; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, (TArray)InParams); - if (!Row) - return nullptr; - - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - - // Create the standard parameter name widget with an added autoupdate checkbox. - CreateNameWidgetWithAutoUpdate(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor *RampColorParam = Cast(MainParam); - if (!RampColorParam) - return nullptr; - - TSharedPtr ColorGradientEditor; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - SAssignNew(ColorGradientEditor, SHoudiniColorRampCurveEditor) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - ] - ]; - - if (!ColorGradientEditor.IsValid()) - return nullptr; - - // Avoid showing tooltips inside of the curve editor - ColorGradientEditor->EnableToolTipForceField(true); - - CurrentRampParameterColorCurve = NewObject( - MainParam, UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); - - if (!CurrentRampParameterColorCurve) - return nullptr; - - CreatedColorRampCurves.Add(CurrentRampParameterColorCurve); - - // Add the ramp curve to root to avoid garabage collected. - CurrentRampParameterColorCurve->AddToRoot(); - - TArray ColorRampParameters; - CastParameters(InParams, ColorRampParameters); - - for (auto NextColorRamp : ColorRampParameters) - { - CurrentRampParameterColorCurve->ColorRampParameters.Add(NextColorRamp); - } - ColorGradientEditor->HoudiniColorRampCurve = CurrentRampParameterColorCurve; - - // Clear default curve points - for (int Idx = 0; Idx < 4; ++Idx) - { - FRichCurve& RichCurve = (CurrentRampParameterColorCurve->FloatCurves)[Idx]; - if (RichCurve.GetNumKeys() > 0) - RichCurve.Keys.Empty(); - } - ColorGradientEditor->SetCurveOwner(CurrentRampParameterColorCurve); - } - else if(MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat *RampFloatParam = Cast(MainParam); - if (!RampFloatParam) - return nullptr; - - TSharedPtr FloatCurveEditor; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - SAssignNew(FloatCurveEditor, SHoudiniFloatRampCurveEditor) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - .HideUI(true) - .DrawCurve(true) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - .ViewMinOutput(0.0f) - .ViewMaxOutput(1.0f) - .TimelineLength(1.0f) - .AllowZoomOutput(false) - .ShowInputGridNumbers(false) - .ShowOutputGridNumbers(false) - .ShowZoomButtons(false) - .ZoomToFitHorizontal(false) - .ZoomToFitVertical(false) - .XAxisName(FString("X")) - .YAxisName(FString("Y")) - .ShowCurveSelector(false) - - ] - ]; - - if (!FloatCurveEditor.IsValid()) - return nullptr; - - // Avoid showing tooltips inside of the curve editor - FloatCurveEditor->EnableToolTipForceField(true); - - CurrentRampParameterFloatCurve = NewObject( - MainParam, UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); - - if (!CurrentRampParameterFloatCurve) - return nullptr; - - CreatedFloatRampCurves.Add(CurrentRampParameterFloatCurve); - - // Add the ramp curve to root to avoid garbage collected - CurrentRampParameterFloatCurve->AddToRoot(); - - TArray FloatRampParameters; - CastParameters(InParams, FloatRampParameters); - for (auto NextFloatRamp : FloatRampParameters) - { - CurrentRampParameterFloatCurve->FloatRampParameters.Add(NextFloatRamp); - } - FloatCurveEditor->HoudiniFloatRampCurve = CurrentRampParameterFloatCurve; - - FloatCurveEditor->SetCurveOwner(CurrentRampParameterFloatCurve, true); - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - return Row; -} - - -void -FHoudiniParameterDetails::CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray& InParams) -{ - if (!Row || !InParameter) - return; - - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - UHoudiniParameterRampFloat * MainFloatRampParameter = nullptr; - UHoudiniParameterRampColor * MainColorRampParameter = nullptr; - - TArray FloatRampParameterList; - TArray ColorRampParameterList; - if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - MainFloatRampParameter = Cast(MainParam); - - if (!MainFloatRampParameter) - return; - - if (!CastParameters(InParams, FloatRampParameterList)) - return; - } - else if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - MainColorRampParameter = Cast(MainParam); - - if (!MainColorRampParameter) - return; - - if (!CastParameters(InParams, ColorRampParameterList)) - return; - } - else - { - return; - } - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Lambda for computing the float point to be inserted - auto GetInsertFloatPointLambda = [MainFloatRampParameter]( - const int32& InsertAtIndex, - float& OutPosition, - float& OutValue, - EHoudiniRampInterpolationType& OutInterpType) mutable - { - if (!MainFloatRampParameter) - return; - - float PrevPosition = 0.0f; - float NextPosition = 1.0f; - - TArray &CurrentPoints = MainFloatRampParameter->Points; - TArray &CachedPoints = MainFloatRampParameter->CachedPoints; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - int32 NumPoints = 0; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - { - NumPoints = CurrentPoints.Num(); - } - else - { - MainFloatRampParameter->SetCaching(true); - NumPoints = CachedPoints.Num(); - } - - if (InsertAtIndex >= NumPoints) - { - // Insert at the end - if (NumPoints > 0) - { - UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - PrevPoint = CurrentPoints.Last(); - else - PrevPoint = CachedPoints.Last(); - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - } - } - else if (InsertAtIndex <= 0) - { - // Insert at the beginning - if (NumPoints > 0) - { - UHoudiniParameterRampFloatPoint* NextPoint = nullptr; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - NextPoint = CurrentPoints[0]; - else - NextPoint = CachedPoints[0]; - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - OutInterpType = NextPoint->GetInterpolation(); - } - } - } - else - { - // Insert in the middle - if (NumPoints > 1) - { - UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; - UHoudiniParameterRampFloatPoint* NextPoint = nullptr; - - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - { - PrevPoint = CurrentPoints[InsertAtIndex - 1]; - NextPoint = CurrentPoints[InsertAtIndex]; - } - else - { - PrevPoint = CachedPoints[InsertAtIndex - 1]; - NextPoint = CachedPoints[InsertAtIndex]; - } - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - } - - if (PrevPoint && NextPoint) - { - OutValue = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; - } - } - } - - OutPosition = (PrevPosition + NextPosition) / 2.0f; - }; - - - // Lambda for computing the color point to be inserted - auto GetInsertColorPointLambda = [MainColorRampParameter]( - const int32& InsertAtIndex, - float& OutPosition, - FLinearColor& OutColor, - EHoudiniRampInterpolationType& OutInterpType) mutable - { - if (!MainColorRampParameter) - return; - - float PrevPosition = 0.0f; - float NextPosition = 1.0f; - - TArray &CurrentPoints = MainColorRampParameter->Points; - TArray &CachedPoints = MainColorRampParameter->CachedPoints; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - int32 NumPoints = 0; - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NumPoints = CurrentPoints.Num(); - else - NumPoints = CachedPoints.Num(); - - if (InsertAtIndex >= NumPoints) - { - // Insert at the end - if (NumPoints > 0) - { - UHoudiniParameterRampColorPoint* PrevPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - PrevPoint = CurrentPoints.Last(); - else - PrevPoint = CachedPoints.Last(); - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - } - } - else if (InsertAtIndex <= 0) - { - // Insert at the beginning - if (NumPoints > 0) - { - UHoudiniParameterRampColorPoint* NextPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NextPoint = CurrentPoints[0]; - else - NextPoint = CachedPoints[0]; - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - OutInterpType = NextPoint->GetInterpolation(); - } - } - } - else - { - // Insert in the middle - if (NumPoints > 1) - { - UHoudiniParameterRampColorPoint* PrevPoint = nullptr; - UHoudiniParameterRampColorPoint* NextPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - { - PrevPoint = CurrentPoints[InsertAtIndex - 1]; - NextPoint = CurrentPoints[InsertAtIndex]; - } - else - { - PrevPoint = CachedPoints[InsertAtIndex - 1]; - NextPoint = CachedPoints[InsertAtIndex]; - } - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - } - - if (PrevPoint && NextPoint) - { - OutColor = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; - } - } - } - - OutPosition = (PrevPosition + NextPosition) / 2.0f; - }; - - int32 RowIndex = 0; - auto InsertRampPoint_Lambda = [GetInsertColorPointLambda, GetInsertFloatPointLambda, &CategoryBuilder, bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, - UHoudiniParameterRampColor* MainRampColor, - TArray &RampFloatList, - TArray &RampColorList, - const int32& Index) mutable - { - if (MainRampFloat) - { - float InsertPosition = 0.0f; - float InsertValue = 1.0f; - EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; - - GetInsertFloatPointLambda(Index, InsertPosition, InsertValue, InsertInterp); - - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); - - for (auto & NextFloatRamp : RampFloatList) - { - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - CreateFloatRampParameterInsertEvent( - NextFloatRamp, InsertPosition, InsertValue, InsertInterp); - - NextFloatRamp->MarkChanged(true); - } - else - { - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject - (NextFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); - NewCachedPoint->Position = InsertPosition; - NewCachedPoint->Value = InsertValue; - NewCachedPoint->Interpolation = InsertInterp; - - NextFloatRamp->CachedPoints.Add(NewCachedPoint); - NextFloatRamp->bCaching = true; - if (!bCookingEnabled) - { - // If cooking is not enabled, be sure to mark this parameter as changed - // so that it triggers an update once cooking is enabled again. - NextFloatRamp->MarkChanged(true); - } - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - { - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - } - else if (MainRampColor) - { - float InsertPosition = 0.0f; - FLinearColor InsertColor = FLinearColor::Black; - EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; - - GetInsertColorPointLambda(Index, InsertPosition, InsertColor, InsertInterp); - - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); - for (auto& NextColorRamp : RampColorList) - { - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - CreateColorRampParameterInsertEvent( - NextColorRamp, InsertPosition, InsertColor, InsertInterp); - - NextColorRamp->MarkChanged(true); - } - else - { - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject - (NextColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); - NewCachedPoint->Position = InsertPosition; - NewCachedPoint->Value = InsertColor; - NewCachedPoint->Interpolation = InsertInterp; - - NextColorRamp->CachedPoints.Add(NewCachedPoint); - NextColorRamp->bCaching = true; - if (!bCookingEnabled) - NextColorRamp->MarkChanged(true); - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - auto DeleteRampPoint_Lambda = [bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, - UHoudiniParameterRampColor* MainRampColor, - TArray &FloatRampList, - TArray &ColorRampList, - const int32& Index) mutable - { - if (MainRampFloat) - { - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampList); - - for (auto& NextFloatRamp : FloatRampList) - { - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextFloatRamp->Points.Num() == 0) - return; - - UHoudiniParameterRampFloatPoint* PointToDelete = nullptr; - - if (Index == -1) - PointToDelete = NextFloatRamp->Points.Last(); - else if (NextFloatRamp->Points.IsValidIndex(Index)) - PointToDelete = NextFloatRamp->Points[Index]; - - if (!PointToDelete) - return; - - const int32 & InstanceIndexToDelete = PointToDelete->InstanceIndex; - - CreateFloatRampParameterDeleteEvent(NextFloatRamp, InstanceIndexToDelete); - NextFloatRamp->MarkChanged(true); - } - else - { - if (NextFloatRamp->CachedPoints.Num() == 0) - return; - - if (Index == -1) - NextFloatRamp->CachedPoints.Pop(); - else if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) - NextFloatRamp->CachedPoints.RemoveAt(Index); - else - return; - - NextFloatRamp->bCaching = true; - if (!bCookingEnabled) - NextFloatRamp->MarkChanged(true); - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); - } - } - else - { - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampList); - - for (auto& NextColorRamp : ColorRampList) - { - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextColorRamp->Points.Num() == 0) - return; - - UHoudiniParameterRampColorPoint* PointToRemove = nullptr; - - if (Index == -1) - PointToRemove = NextColorRamp->Points.Last(); - else if (NextColorRamp->Points.IsValidIndex(Index)) - PointToRemove = NextColorRamp->Points[Index]; - - if (!PointToRemove) - return; - - const int32 & InstanceIndexToDelete = PointToRemove->InstanceIndex; - - CreateColorRampParameterDeleteEvent(NextColorRamp, InstanceIndexToDelete); - - NextColorRamp->MarkChanged(true); - } - else - { - if (NextColorRamp->CachedPoints.Num() == 0) - return; - - if (Index == -1) - NextColorRamp->CachedPoints.Pop(); - else if (NextColorRamp->CachedPoints.IsValidIndex(Index)) - NextColorRamp->CachedPoints.RemoveAt(Index); - else - return; - - NextColorRamp->bCaching = true; - if (!bCookingEnabled) - NextColorRamp->MarkChanged(true); - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - - TSharedRef VerticalBox = StaticCastSharedRef(Row->ValueWidget.Widget); - - TSharedPtr GridPanel; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SAssignNew(GridPanel, SUniformGridPanel) - ]; - - //AllUniformGridPanels.Add(GridPanel.Get()); - - GridPanel->SetSlotPadding(FMargin(2.f, 2.f, 5.f, 3.f)); - GridPanel->AddSlot(0, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Position"))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - FString ValueString = TEXT("Value"); - if (!MainFloatRampParameter) - ValueString = TEXT("Color"); - - GridPanel->AddSlot(1, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(ValueString)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - GridPanel->AddSlot(2, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Interp."))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - - GridPanel->AddSlot(3, RowIndex) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton( - FSimpleDelegate::CreateLambda([InsertRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, bCookingEnabled]() mutable - { - int32 InsertAtIndex = -1; - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - InsertAtIndex = MainFloatRampParameter->Points.Num(); - else - InsertAtIndex = MainFloatRampParameter->CachedPoints.Num(); - } - else if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - InsertAtIndex = MainColorRampParameter->Points.Num(); - else - InsertAtIndex = MainColorRampParameter->CachedPoints.Num(); - } - - InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, InsertAtIndex); - }), - LOCTEXT("AddRampPoint", "Add a ramp point to the end"), true) - ] - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeRemoveButton( - FSimpleDelegate::CreateLambda([DeleteRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList]() mutable - { - DeleteRampPoint_Lambda( - MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, -1); - }), - LOCTEXT("DeleteRampPoint", "Delete the last ramp point"), true) - ] - - ]; - - EUnit Unit = EUnit::Unspecified; - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - int32 PointCount = 0; - // Use Synced points on auto update mode - // Use Cached points on manual update mode - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - PointCount = MainFloatRampParameter->Points.Num(); - else - PointCount = MainFloatRampParameter->CachedPoints.Num(); - } - - if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate()) - PointCount = MainColorRampParameter->Points.Num(); - else - PointCount = MainColorRampParameter->CachedPoints.Num(); - } - - // Lambda function for changing a ramp point - auto OnPointChangeCommit = [bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, UHoudiniParameterRampColor* MainRampColor, - UHoudiniParameterRampFloatPoint* MainRampFloatPoint, UHoudiniParameterRampColorPoint* MainRampColorPoint, - TArray &RampFloatList, TArray &RampColorList, - const int32& Index, const FString& ChangedDataName, - const float& NewPosition, const float& NewFloat, - const FLinearColor& NewColor, - const EHoudiniRampInterpolationType& NewInterpType) mutable - { - if (MainRampFloat && MainRampFloatPoint) - { - if (ChangedDataName == FString("position") && MainRampFloatPoint->GetPosition() == NewPosition) - return; - if (ChangedDataName == FString("value") && MainRampFloatPoint->GetValue() == NewFloat) - return; - if (ChangedDataName == FString("interp") && MainRampFloatPoint->GetInterpolation() == NewInterpType) - return; - - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); - for (auto NextFloatRamp : RampFloatList) - { - if (!NextFloatRamp) - continue; - - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextFloatRamp->Points.IsValidIndex(Index)) - { - UHoudiniParameterRampFloatPoint* CurrentFloatRampPoint = NextFloatRamp->Points[Index]; - if (!CurrentFloatRampPoint) - continue; - - if (ChangedDataName == FString("position")) - { - if (!CurrentFloatRampPoint->PositionParentParm) - continue; - - CurrentFloatRampPoint->SetPosition(NewPosition); - CurrentFloatRampPoint->PositionParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("value")) - { - if (!CurrentFloatRampPoint->PositionParentParm) - continue; - - CurrentFloatRampPoint->SetValue(NewFloat); - CurrentFloatRampPoint->ValueParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("interp")) - { - if (!CurrentFloatRampPoint->InterpolationParentParm) - continue; - - CurrentFloatRampPoint->SetInterpolation(NewInterpType); - CurrentFloatRampPoint->InterpolationParentParm->MarkChanged(true); - } - } - else - { - int32 IdxInEventsArray = Index - NextFloatRamp->Points.Num(); - if (NextFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = NextFloatRamp->ModificationEvents[IdxInEventsArray]; - if (!Event) - continue; - - if (ChangedDataName == FString("position")) - { - Event->InsertPosition = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - Event->InsertFloat = NewFloat; - } - else if (ChangedDataName == FString("interp")) - { - Event->InsertInterpolation = NewInterpType; - } - } - } - } - else - { - if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) - { - UHoudiniParameterRampFloatPoint* CachedPoint = NextFloatRamp->CachedPoints[Index]; - - if (!CachedPoint) - continue; - - if (ChangedDataName == FString("position")) - { - CachedPoint->Position = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - CachedPoint->Value = NewFloat; - } - else if (ChangedDataName == FString("interp")) - { - CachedPoint->Interpolation = NewInterpType; - } - - NextFloatRamp->bCaching = true; - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); - } - } - else if (MainRampColor && MainRampColorPoint) - { - if (ChangedDataName == FString("position") && MainRampColorPoint->GetPosition() == NewPosition) - return; - - if (ChangedDataName == FString("value") && MainRampColorPoint->GetValue() == NewColor) - return; - - if (ChangedDataName == FString("interp") && MainRampColorPoint->GetInterpolation() == NewInterpType) - return; - - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); - for (auto NextColorRamp : RampColorList) - { - if (!NextColorRamp) - continue; - - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextColorRamp->Points.IsValidIndex(Index)) - { - UHoudiniParameterRampColorPoint* CurrentColorRampPoint = NextColorRamp->Points[Index]; - if (!CurrentColorRampPoint) - continue; - - if (ChangedDataName == FString("position")) - { - if (!CurrentColorRampPoint->PositionParentParm) - continue; - - CurrentColorRampPoint->SetPosition(NewPosition); - CurrentColorRampPoint->PositionParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("value")) - { - if (!CurrentColorRampPoint->PositionParentParm) - continue; - - CurrentColorRampPoint->SetValue(NewColor); - CurrentColorRampPoint->ValueParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("interp")) - { - if (!CurrentColorRampPoint->InterpolationParentParm) - continue; - - CurrentColorRampPoint->SetInterpolation(NewInterpType); - CurrentColorRampPoint->InterpolationParentParm->MarkChanged(true); - } - } - else - { - int32 IdxInEventsArray = Index - NextColorRamp->Points.Num(); - if (NextColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = NextColorRamp->ModificationEvents[IdxInEventsArray]; - if (!Event) - continue; - - if (ChangedDataName == FString("position")) - { - Event->InsertPosition = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - Event->InsertColor = NewColor; - } - else if (ChangedDataName == FString("interp")) - { - Event->InsertInterpolation = NewInterpType; - } - - } - } - } - else - { - if (NextColorRamp->CachedPoints.IsValidIndex(Index)) - { - UHoudiniParameterRampColorPoint* CachedPoint = NextColorRamp->CachedPoints[Index]; - - if (!CachedPoint) - continue; - - if (ChangedDataName == FString("position")) - { - CachedPoint->Position = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - CachedPoint->Value = NewColor; - } - else if (ChangedDataName == FString("interp")) - { - CachedPoint->Interpolation = NewInterpType; - } - - NextColorRamp->bCaching = true; - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - for (int32 Index = 0; Index < PointCount; ++Index) - { - UHoudiniParameterRampFloatPoint* NextFloatRampPoint = nullptr; - UHoudiniParameterRampColorPoint* NextColorRampPoint = nullptr; - - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - NextFloatRampPoint = MainFloatRampParameter->Points[Index]; - else - NextFloatRampPoint = MainFloatRampParameter->CachedPoints[Index]; - } - if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NextColorRampPoint = MainColorRampParameter->Points[Index]; - else - NextColorRampPoint = MainColorRampParameter->CachedPoints[Index]; - } - - if (!NextFloatRampPoint && !NextColorRampPoint) - continue; - - RowIndex += 1; - - float CurPos = 0.f; - if (NextFloatRampPoint) - CurPos = NextFloatRampPoint->Position; - else - CurPos = NextColorRampPoint->Position; - - - GridPanel->AddSlot(0, RowIndex) - [ - SNew(SNumericEntryBox) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Value(CurPos) - .OnValueChanged_Lambda([](float Val) {}) - .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("position"), - Val, float(-1.0), - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .OnBeginSliderMovement_Lambda([]() {}) - .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("position"), - Val, float(-1.0), - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .SliderExponent(1.0f) - .TypeInterface(paramTypeInterface) - ]; - - if (NextFloatRampPoint) - { - GridPanel->AddSlot(1, RowIndex) - [ - SNew(SNumericEntryBox< float >) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Value(NextFloatRampPoint->Value) - .OnValueChanged_Lambda([](float Val){}) - .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), Val, - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .OnBeginSliderMovement_Lambda([]() {}) - .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), Val, - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .SliderExponent(1.0f) - .TypeInterface(paramTypeInterface) - ]; - } - else if (NextColorRampPoint) - { - auto OnColorChangeLambda = [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](FLinearColor InColor) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), float(-1.0), - InColor, - EHoudiniRampInterpolationType::LINEAR); - }; - - // Add color picker UI. - //TSharedPtr ColorBlock; - GridPanel->AddSlot(1, RowIndex) - [ - SNew(SColorBlock) - .Color(NextColorRampPoint->Value) - .OnMouseButtonDown( FPointerEventHandler::CreateLambda( - [NextColorRampPoint, OnColorChangeLambda](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) - { - if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) - return FReply::Unhandled(); - - FColorPickerArgs PickerArgs; - PickerArgs.bUseAlpha = true; - PickerArgs.DisplayGamma = TAttribute< float >::Create( - TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda(OnColorChangeLambda); - FLinearColor InitColor = NextColorRampPoint->Value; - PickerArgs.InitialColorOverride = InitColor; - PickerArgs.bOnlyRefreshOnOk = true; - OpenColorPicker(PickerArgs); - return FReply::Handled(); - })) - ]; - } - - int32 CurChoice = 0; - if (NextFloatRampPoint) - CurChoice = (int)NextFloatRampPoint->Interpolation; - else - CurChoice = (int)NextColorRampPoint->Interpolation; - - TSharedPtr >> ComboBoxCurveMethod; - GridPanel->AddSlot(2, RowIndex) - [ - SAssignNew(ComboBoxCurveMethod, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels())[CurChoice]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, - ColorRampParameterList, Index](TSharedPtr NewChoice, ESelectInfo::Type SelectType) mutable - { - EHoudiniRampInterpolationType NewInterpType = UHoudiniParameter::GetHoudiniInterpMethodFromString(*NewChoice.Get()); - - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("interp"), - float(-1.0), float(-1.0), - FLinearColor(), - NewInterpType); - }) - [ - SNew(STextBlock) - .Text_Lambda([NextFloatRampPoint, NextColorRampPoint]() - { - EHoudiniRampInterpolationType CurInterpType = EHoudiniRampInterpolationType::InValid; - if (NextFloatRampPoint) - CurInterpType = NextFloatRampPoint->GetInterpolation(); - else - CurInterpType = NextColorRampPoint->GetInterpolation(); - - return FText::FromString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(CurInterpType)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - GridPanel->AddSlot(3, RowIndex) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateLambda( - [InsertRampPoint_Lambda, MainFloatRampParameter, - MainColorRampParameter, FloatRampParameterList, - ColorRampParameterList, Index]() mutable - { - InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); - }), - LOCTEXT("AddRampPoint", "Add a ramp point before this point"), true) - ] - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateLambda( - [DeleteRampPoint_Lambda, MainFloatRampParameter, - MainColorRampParameter, FloatRampParameterList, - ColorRampParameterList, Index]() mutable - { - DeleteRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); - }), - LOCTEXT("DeleteFloatRampPoint", "Delete this ramp point"), true) - ] - ]; - - if (MainFloatRampParameter && CurrentRampParameterFloatCurve) - { - ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextFloatRampPoint->GetInterpolation()); - FRichCurve & RichCurve = CurrentRampParameterFloatCurve->FloatCurve; - FKeyHandle const KeyHandle = RichCurve.AddKey(NextFloatRampPoint->GetPosition(), NextFloatRampPoint->GetValue()); - RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); - } - - if (MainColorRampParameter && CurrentRampParameterColorCurve) - { - ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextColorRampPoint->GetInterpolation()); - for (int32 CurveIdx = 0; CurveIdx < 4; ++CurveIdx) - { - FRichCurve & RichCurve = CurrentRampParameterColorCurve->FloatCurves[CurveIdx]; - - FKeyHandle const KeyHandle = RichCurve.AddKey(NextColorRampPoint->GetPosition(), NextColorRampPoint->GetValue().Component(CurveIdx)); - RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); - } - } - } - - if (MainFloatRampParameter) - GridPanel->SetEnabled(!MainFloatRampParameter->IsDisabled()); - - if (MainColorRampParameter) - GridPanel->SetEnabled(!MainColorRampParameter->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetFolderList(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FolderListParams; - if (!CastParameters(InParams, FolderListParams)) - return; - - if (FolderListParams.Num() <= 0) - return; - - UHoudiniParameterFolderList* MainParam = FolderListParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Add this folder list to the folder map - AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); - - MainParam->GetTabs().Empty(); - - // A folder list will be followed by all its child folders, - // so set the CurrentFolderListSize to the tuple size, we'll process such many folder parameters right after - CurrentFolderListSize = MainParam->GetTupleSize(); - - if (MainParam->IsDirectChildOfMultiParm()) - MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); - - if (CurrentFolderListSize <= 0) // There should not be empty folder list, this will not happen normally - return; - - // The following folders belong to current folder list - CurrentFolderList = MainParam; - - // If the tab is either a tabs or radio button and the parameter is visible - if (MainParam->IsTabMenu() && MainParam->ShouldDisplay()) - { - // Set the current tabs to be not shown by default now. CreateWidgetTab will decide if the tabs is shown. - CurrentFolderList->SetTabsShown(false); - - // Create a row to hold tab buttons if the folder list is a tabs or radio button - - // CreateNestedRow does not actually create a row for tabs, it is responsible to prune the folder stack. - // ( CreateWidgetTab will be responsible to create a row according to the visibility of its outer level folders ) - FDetailWidgetRow* TabRow = CreateNestedRow(HouParameterCategory, InParams, false); - - } - - // When see a folder list, go depth first search at this step. - // Push an empty queue to the stack. - FolderStack.Add(TArray()); -} - - -void -FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FolderParams; - if (!CastParameters(InParams, FolderParams)) - return; - - if (FolderParams.Num() <= 0) - return; - - UHoudiniParameterFolder* MainParam = FolderParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen - return; - // If a folder is invisible, its children won't be listed by HAPI. - // So just reduce FolderListSize by 1, reduce the child counter of its parent folder by 1 if necessary, - // and prune the stack in such case. - if (!MainParam->IsVisible()) - { - CurrentFolderListSize -= 1; - - if (CurrentFolderListSize == 0) - { - if (FolderStack.Num() > 1) - { - TArray &ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) - ParentFolderQueue[0]->GetChildCounter() -= 1; - } - - PruneStack(); - } - - return; - } - - // We expect 'TupleSize' children param of this folder after finish processing all the child folders of cur folderlist - MainParam->ResetChildCounter(); - - // Add this folder to the folder map - AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); - - // Set the parent param to current folderList. - // it was parent multiparm's id if this folder is a child of a multiparms. - // This will cause problem if the folder is inside of a multiparm - MainParam->SetParentParmId(CurrentFolderList->GetParmId()); - - - // Case 1: The folder is a direct child of a multiparm. - if (MainParam->IsDirectChildOfMultiParm()) - { - if (FolderStack.Num() <= 0) // This should not happen - return; - - // Get its parent multiparm first - UHoudiniParameterMultiParm* ParentMultiParm = nullptr; - { - UHoudiniParameterFolderList * ParentFolderList = nullptr; - if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) - return; - - ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); - - if (!ParentFolderList) - return; - - if (AllMultiParms.Contains(ParentFolderList->GetParentParmId())) - ParentMultiParm = AllMultiParms[ParentFolderList->GetParentParmId()]; - - if (!ParentMultiParm) // This should not happen - return; - } - - bool bShown = ParentMultiParm->IsShown(); - - // Case 1-1: The folder is NOT tabs - if (!MainParam->IsTab()) - { - bShown = MainParam->IsExpanded() && bShown; - - // If the parent multiparm is shown. - if (ParentMultiParm->IsShown()) - { - FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderHeaderRow, InParams); - } - } - // Case 1-2: The folder IS tabs. - else - { - CreateWidgetTab(HouParameterCategory, MainParam, ParentMultiParm->IsShown()); - } - - // Push the folder to the queue if it is not a tab folder - // This step is handled by CreateWidgetTab() if it is tabs - if ((!MainParam->IsTab() || !ParentMultiParm->IsShown()) && MainParam->GetTupleSize() > 0) - { - TArray & MyQueue = FolderStack.Last(); - MainParam->SetIsContentShown(bShown); - MyQueue.Add(MainParam); - } - } - - // Case 2: The folder is NOT a direct child of a multiparm. - else - { - // Case 2-1: The folder is in another folder. - if (FolderStack.Num() > 1 && CurrentFolderListSize > 0) - { - TArray & MyFolderQueue = FolderStack.Last(); - TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - - if (ParentFolderQueue.Num() <= 0) //This should happen - return; - - // Peek the folder queue of the last layer to get its parent folder parm. - bool ParentFolderVisible = ParentFolderQueue[0]->IsContentShown(); - - // If this folder is expanded (selected if is tabs) - bool bExpanded = ParentFolderVisible; - - // Case 2-1-1: The folder is NOT in a tab menu. - if (!MainParam->IsTab()) - { - bExpanded &= MainParam->IsExpanded(); - - // The parent folder is visible. - if (ParentFolderVisible) - { - // Add the folder header UI. - FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderHeaderRow, InParams); - } - - MainParam->SetIsContentShown(bExpanded); - MyFolderQueue.Add(MainParam); - } - // Case 2-1-2: The folder IS in a tab menu. - else - { - bExpanded &= MainParam->IsChosen(); - - CreateWidgetTab(HouParameterCategory, MainParam, ParentFolderVisible); - } - } - // Case 2-2: The folder is in the root. - else - { - bool bExpanded = true; - - // Case 2-2-1: The folder is NOT under a tab menu. - if (!MainParam->IsTab()) - { - if (FolderStack.Num() <= 0) // This will not happen - return; - - // Create Folder header under root. - FDetailWidgetRow* FolderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderRow, InParams); - - if (FolderStack.Num() == 0) // This should not happen - return; - - TArray& MyFolderQueue = FolderStack[0]; - bExpanded &= MainParam->IsExpanded(); - MainParam->SetIsContentShown(bExpanded); - MyFolderQueue.Add(MainParam); - } - // Case 2-2-2: The folder IS under a tab menu. - else - { - // Tabs in root is always visible - CreateWidgetTab(HouParameterCategory, MainParam, true); - } - } - } - - - CurrentFolderListSize -= 1; - - // Prune the stack if finished parsing current folderlist - if (CurrentFolderListSize == 0) - { - if (FolderStack.Num() > 1 && !MainParam->IsDirectChildOfMultiParm()) - { - TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) - ParentFolderQueue[0]->GetChildCounter() -= 1; - } - - PruneStack(); - - CurrentFolderList = nullptr; - } -} - -void -FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray &InParams) -{ - if (!HeaderRow) // The folder is invisible. - return; - - TArray FolderParams; - if (!CastParameters(InParams, FolderParams)) - return; - - if (FolderParams.Num() <= 0) - return; - - UHoudiniParameterFolder* MainParam = FolderParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return; - - TSharedPtr VerticalBox; - - FString LabelStr = MainParam->GetParameterLabel(); - - TSharedPtr HorizontalBox; - TSharedPtr ExpanderArrow; - TSharedPtr ExpanderImage; - - HeaderRow->NameWidget.Widget = SAssignNew(HorizontalBox, SCustomizedBox); - - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - - if (MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 1) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - LabelStr = LabelStr + TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex) + TEXT(")"); - } - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SAssignNew(ExpanderArrow, SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([=]() - { - MainParam->ExpandButtonClicked(); - - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - - return FReply::Handled(); - }) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - - FText LabelText = FText::FromString(LabelStr); - - HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SNew(STextBlock) - .Text(LabelText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - ExpanderImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([=]() { - FName ResourceName; - if(MainParam->IsExpanded()) - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - }))); - - if(MainParam->GetFolderType() == EHoudiniFolderParameterType::Simple) - ExpanderArrow->SetEnabled(false); - -} - -void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InFolder, const bool& bIsShown) -{ - if (!InFolder || InFolder->IsPendingKill() || !CurrentFolderList) - return; - - if (FolderStack.Num() <= 0) // error state - return; - - TArray & FolderQueue = FolderStack.Last(); - - // Cache all tabs of current tab folder list. - CurrentFolderList->AddTabFolder(InFolder); - - // If the tabs is not shown, just push the folder param into the queue. - if (!bIsShown) - { - InFolder->SetIsContentShown(bIsShown); - FolderQueue.Add(InFolder); - return; - } - - // tabs currently being processed - CurrentTabs.Add(InFolder); - - if (CurrentFolderListSize > 1) - return; - - // The tabs belong to current folder list - UHoudiniParameterFolderList* CurrentTabMenuFolderList = CurrentFolderList; - - // Create a row (UI) for current tabs - TSharedPtr HorizontalBox; - FDetailWidgetRow &Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()) - [ - SAssignNew(HorizontalBox, SCustomizedBox) - ]; - - // Put current tab folder list param into an array - TArray CurrentTabMenuFolderListArr; - CurrentTabMenuFolderListArr.Add(CurrentTabMenuFolderList); - - HorizontalBox->SetHoudiniParameter(CurrentTabMenuFolderListArr); - DividerLinePositions.Add(HorizontalBox->AddIndentation(InFolder, AllMultiParms, AllFoldersAndFolderLists)); - HorizontalBox->DividerLinePositions = DividerLinePositions; - - float DesiredHeight = 0.0f; - float DesiredWidth = 0.0f; - - // Process all tabs of current folder list at once when done. - - for (auto & CurTab : CurrentTabs) - { - if (!CurTab || CurTab->IsPendingKill()) - continue; - - CurTab->SetIsContentShown(CurTab->IsChosen()); - FolderQueue.Add(CurTab); - - auto OnTabClickedLambda = [CurrentTabMenuFolderList, CurTab, &HouParameterCategory]() - { - if (CurrentTabMenuFolderList) - { - if (!CurrentTabMenuFolderList->bIsTabMenu || CurrentTabMenuFolderList->TabFolders.Num() <= 0) - return FReply::Handled(); - - if (CurTab->IsChosen()) - return FReply::Handled(); - - CurTab->SetChosen(true); - - for (UHoudiniParameterFolder* NextFolder : CurrentTabMenuFolderList->TabFolders) - { - if (CurTab->GetParmId() != NextFolder->GetParmId() && NextFolder->IsChosen()) - NextFolder->SetChosen(false); - } - - HouParameterCategory.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); - }; - - FString FolderLabelString = TEXT(" ") + CurTab->GetParameterLabel(); - if (CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio) - FolderLabelString = TEXT(" ") + FolderLabelString; - - bool bChosen = CurTab->IsTab() && CurTab->IsChosen(); - - TSharedPtr CurCustomizedButton; - - HorizontalBox->AddSlot().VAlign(VAlign_Bottom) - .AutoWidth() - .Padding(0.f) - .HAlign(HAlign_Left) - [ - SAssignNew(CurCustomizedButton, SCustomizedButton) - .OnClicked_Lambda(OnTabClickedLambda) - .Content() - [ - SNew(STextBlock) - .Text(FText::FromString(FolderLabelString)) - ] - ]; - - CurCustomizedButton->bChosen = bChosen; - CurCustomizedButton->bIsRadioButton = CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio; - - DesiredHeight = CurCustomizedButton->GetDesiredSize().Y; - DesiredWidth += CurCustomizedButton->GetDesiredSize().X; - } - - HorizontalBox->bIsTabFolderListRow = true; - - Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - // Set the current tabs to be shown, since slate widgets have been created - CurrentTabMenuFolderList->SetTabsShown(true); - - // Clear the temporary tabs - CurrentTabs.Empty(); -} - -void -FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray MultiParmParams; - if (!CastParameters(InParams, MultiParmParams)) - return; - - if (MultiParmParams.Num() <= 0) - return; - - UHoudiniParameterMultiParm* MainParam = MultiParmParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Add current multiparm parameter to AllmultiParms map - AllMultiParms.Add(MainParam->GetParmId(), MainParam); - - // Create a new detail row - FDetailWidgetRow * Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - { - MainParam->SetIsShown(false); - return; - } - - MainParam->SetIsShown(true); - - MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); - - CreateNameWidget(Row, InParams, true); - - auto OnInstanceValueChangedLambda = [MainParam](int32 InValue) - { - if (InValue < 0) - return; - - int32 ChangesCount = FMath::Abs(MainParam->MultiParmInstanceLastModifyArray.Num() - InValue); - - if (MainParam->MultiParmInstanceLastModifyArray.Num() > InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->RemoveElement(-1); - - MainParam->MarkChanged(true); - } - else if (MainParam->MultiParmInstanceLastModifyArray.Num() < InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->InsertElement(); - - MainParam->MarkChanged(true); - } - }; - - // Add multiparm UI. - TSharedRef HorizontalBox = SNew(SHorizontalBox); - TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; - int32 NumericalCount = MainParam->MultiParmInstanceCount; - HorizontalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateLambda([OnInstanceValueChangedLambda](int32 InValue) { - OnInstanceValueChangedLambda(InValue); - })) - .Value(NumericalCount) - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamAddInstance", "Houdini Parameter Multi Parameter: Adding an instance"), - MainParam->GetOuter(), true); - - for (auto& Param : MultiParmParams) - { - if (!Param) - continue; - - // Add a reverse step for redo/undo - Param->MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Removed); - - Param->MarkChanged(true); - Param->Modify(); - - if (Param->MultiParmInstanceLastModifyArray.Num() > 0) - Param->MultiParmInstanceLastModifyArray.RemoveAt(Param->MultiParmInstanceLastModifyArray.Num() - 1); - - Param->InsertElement(); - - } - }), - LOCTEXT("AddMultiparmInstanceToolTipAddLastInstance", "Add an Instance"), true) - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - // Remove the last multiparm instance - PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamDeleteInstance", "Houdini Parameter Multi Parameter: Deleting an instance"), - MainParam->GetOuter(), true); - - for (auto & Param : MultiParmParams) - { - TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; - int32 RemovedIndex = LastModifiedArray.Num() - 1; - while (LastModifiedArray.IsValidIndex(RemovedIndex) && LastModifiedArray[RemovedIndex] == EHoudiniMultiParmModificationType::Removed) - RemovedIndex -= 1; - - // Add a reverse step for redo/undo - EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; - if (LastModifiedArray.IsValidIndex(RemovedIndex)) - { - PreviousModType = LastModifiedArray[RemovedIndex]; - LastModifiedArray[RemovedIndex] = EHoudiniMultiParmModificationType::Inserted; - } - - Param->MarkChanged(true); - - Param->Modify(); - - if (LastModifiedArray.IsValidIndex(RemovedIndex)) - { - LastModifiedArray[RemovedIndex] = PreviousModType; - } - - Param->RemoveElement(RemovedIndex); - } - - }), - LOCTEXT("RemoveLastMultiParamLastToolTipRemoveLastInstance", "Remove the last instance"), true) - - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamDeleteAllInstances", "Houdini Parameter Multi Parameter: Deleting all instances"), - MainParam->GetOuter(), true); - - for (auto & Param : MultiParmParams) - { - TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; - TArray IndicesToReverse; - - for (int32 Index = 0; Index < LastModifiedArray.Num(); ++Index) - { - if (LastModifiedArray[Index] == EHoudiniMultiParmModificationType::None) - { - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; - IndicesToReverse.Add(Index); - } - } - - Param->MarkChanged(true); - - Param->Modify(); - - for (int32 & Index : IndicesToReverse) - { - if (LastModifiedArray.IsValidIndex(Index)) - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::None; - } - - - Param->EmptyElements(); - } - - }), - LOCTEXT("HoudiniParameterRemoveAllMultiparmInstancesToolTip", "Remove all instances"), true) - ]; - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniParameterDetails::CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams) -{ - - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return; - - if (!HorizontalBox || !AllMultiParms.Contains(MainParam->GetParentParmId()) || !MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - return; - - UHoudiniParameterMultiParm* MainParentMultiParm = AllMultiParms[MainParam->GetParentParmId()]; - - if (!MainParentMultiParm) - return; - - if (!MainParentMultiParm->IsShown()) - return; - - // push all parent multiparm of the InParams to the array - TArray ParentMultiParams; - for (auto & InParam : InParams) - { - if (!InParam) - continue; - - if (!MultiParmInstanceIndices.Contains(InParam->GetParentParmId())) - continue; - - if (InParam->GetChildIndex() == 0) - { - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[InParam->GetParentParmId()]; - - if (ParentMultiParm) - ParentMultiParams.Add(ParentMultiParm); - } - } - - - int32 InstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - - TSharedRef AddButton = PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() - { - for (auto & ParentParam : ParentMultiParams) - { - // Add button call back - if (!ParentParam) - continue; - - TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; - - if (!LastModifiedArray.IsValidIndex(InstanceIndex)) - continue; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParmAddBeforeCurInstance", "Houdini Parameter Multi Parm: Adding an instance"), - ParentParam->GetOuter(), true); - - - int32 Index = InstanceIndex; - - // Add a reverse step for undo/redo - if (Index >= LastModifiedArray.Num()) - LastModifiedArray.Add(EHoudiniMultiParmModificationType::Removed); - else - LastModifiedArray.Insert(EHoudiniMultiParmModificationType::Removed, Index); - - ParentParam->MarkChanged(true); - ParentParam->Modify(); - - if (Index >= LastModifiedArray.Num() - 1 && LastModifiedArray.Num()) - LastModifiedArray.RemoveAt(LastModifiedArray.Num() - 1); - else - LastModifiedArray.RemoveAt(Index); - - ParentParam->InsertElementAt(InstanceIndex); - - } - }), - LOCTEXT("HoudiniParameterMultiParamAddBeforeCurrentInstanceToolTip", "Insert an instance before this instance")); - - - TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() - { - for (auto & ParentParam : ParentMultiParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParmDeleteCurInstance", "Houdini Parameter Multi Parm: Deleting an instance"), - ParentParam->GetOuter(), true); - - - TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; - - int32 Index = InstanceIndex; - EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; - while (LastModifiedArray.IsValidIndex(Index) && LastModifiedArray[Index] == EHoudiniMultiParmModificationType::Removed) - { - Index -= 1; - } - - if (LastModifiedArray.IsValidIndex(Index)) - { - PreviousModType = LastModifiedArray[Index]; - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; - } - - ParentParam->MarkChanged(true); - - ParentParam->Modify(); - - if (LastModifiedArray.IsValidIndex(Index)) - { - LastModifiedArray[Index] = PreviousModType; - } - - ParentParam->RemoveElement(InstanceIndex); - } - - }), - LOCTEXT("HoudiniParameterMultiParamDeleteCurrentInstanceToolTip", "Remove an instance"), true); - - - HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[AddButton]; - HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[RemoveButton]; - - int32 StartIdx = MainParam->GetParameterType() == EHoudiniParameterType::Folder ? 1 : 0; - if (MainParam->GetChildIndex() != StartIdx) - { - AddButton.Get().SetVisibility(EVisibility::Hidden); - RemoveButton.Get().SetVisibility(EVisibility::Hidden); - } - -} - -void -FHoudiniParameterDetails::PruneStack() -{ - for (int32 StackItr = FolderStack.Num() - 1; StackItr >= 0; --StackItr) - { - TArray &CurrentQueue = FolderStack[StackItr]; - - for (int32 QueueItr = CurrentQueue.Num() - 1; QueueItr >= 0; --QueueItr) - { - UHoudiniParameterFolder * CurrentFolder = CurrentQueue[QueueItr]; - if (!CurrentFolder || CurrentFolder->IsPendingKill()) - continue; - - if (CurrentFolder->GetChildCounter() == 0) - { - CurrentQueue.RemoveAt(QueueItr); - } - } - - if (CurrentQueue.Num() == 0) - { - FolderStack.RemoveAt(StackItr); - } - } -} - -FText -FHoudiniParameterDetails::GetParameterTooltip(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return FText(); - - // Tooltip starts with Label (name) - FString Tooltip = InParam->GetParameterLabel() + TEXT(" (") + InParam->GetParameterName() + TEXT(")"); - - // Append the parameter type - FString ParmTypeStr = GetParameterTypeString(InParam->GetParameterType(), InParam->GetTupleSize()); - if (!ParmTypeStr.IsEmpty()) - Tooltip += TEXT("\n") + ParmTypeStr; - - // If the parameter has some help, append it - FString Help = InParam->GetParameterHelp(); - if (!Help.IsEmpty()) - Tooltip += TEXT("\n") + Help; - - // If the parameter has an expression, append it - if (InParam->HasExpression()) - { - FString Expr = InParam->GetExpression(); - if (!Expr.IsEmpty()) - Tooltip += TEXT("\nExpression: ") + Expr; - } - - return FText::FromString(Tooltip); -} - -FString -FHoudiniParameterDetails::GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize) -{ - FString ParamStr; - - switch (InType) - { - case EHoudiniParameterType::Button: - ParamStr = TEXT("Button"); - break; - - case EHoudiniParameterType::ButtonStrip: - ParamStr = TEXT("Button Strip"); - break; - - case EHoudiniParameterType::Color: - { - if (InTupleSize == 4) - ParamStr = TEXT("Color with Alpha"); - else - ParamStr = TEXT("Color"); - } - break; - - case EHoudiniParameterType::ColorRamp: - ParamStr = TEXT("Color Ramp"); - break; - - case EHoudiniParameterType::File: - ParamStr = TEXT("File (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileDir: - ParamStr = TEXT("File Dir (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileGeo: - ParamStr = TEXT("File Geo (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileImage: - ParamStr = TEXT("File Image (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::Float: - ParamStr = TEXT("Float (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); - break; - - case EHoudiniParameterType::FloatRamp: - ParamStr = TEXT("Float Ramp"); - break; - - case EHoudiniParameterType::Folder: - case EHoudiniParameterType::FolderList: - break; - - case EHoudiniParameterType::Input: - ParamStr = TEXT("Opearator Path"); - break; - - case EHoudiniParameterType::Int: - ParamStr = TEXT("Integer (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); - break; - - case EHoudiniParameterType::IntChoice: - ParamStr = TEXT("Int Choice"); - break; - - case EHoudiniParameterType::Label: - ParamStr = TEXT("Label (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::MultiParm: - ParamStr = TEXT("MultiParm"); - break; - - case EHoudiniParameterType::Separator: - break; - - case EHoudiniParameterType::String: - ParamStr = TEXT("String (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::StringAssetRef: - ParamStr = TEXT("String Asset Ref (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::StringChoice: - ParamStr = TEXT("String Choice"); - break; - - case EHoudiniParameterType::Toggle: - ParamStr = TEXT("Toggle (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - default: - ParamStr = TEXT("invalid parameter type"); - break; - } - - - return ParamStr; -} - -void -FHoudiniParameterDetails::SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter) -{ - if (!ColorRampParameter) - return; - - // Do not sync when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) - return; - - TArray &CachedPoints = ColorRampParameter->CachedPoints; - TArray &CurrentPoints = ColorRampParameter->Points; - - bool bCurveNeedsUpdate = false; - bool bRampParmNeedsUpdate = false; - - int32 Idx = 0; - - while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) - { - UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[Idx]; - UHoudiniParameterRampColorPoint* CurrentPoint = CurrentPoints[Idx]; - - if (!CachedPoint || !CurrentPoint) - continue; - - if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) - { - if (CurrentPoint->PositionParentParm) - { - CurrentPoint->SetPosition(CachedPoint->GetPosition()); - CurrentPoint->PositionParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - if (CachedPoint->GetValue() != CurrentPoint->GetValue()) - { - if (CurrentPoint->ValueParentParm) - { - CurrentPoint->SetValue(CachedPoint->GetValue()); - CurrentPoint->ValueParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) - { - if (CurrentPoint->InterpolationParentParm) - { - CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); - CurrentPoint->InterpolationParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - Idx += 1; - } - - // Insert points - for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) - { - UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[IdxCachedPointLeft]; - - CreateColorRampParameterInsertEvent( - ColorRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); - - bCurveNeedsUpdate = true; - bRampParmNeedsUpdate = true; - - } - - // Delete points - for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) - { - ColorRampParameter->RemoveElement(IdxCurrentPointLeft); - - UHoudiniParameterRampColorPoint* Point = CurrentPoints[IdxCurrentPointLeft]; - - if (!Point) - continue; - - CreateColorRampParameterDeleteEvent(ColorRampParameter, Point->InstanceIndex); - - bCurveNeedsUpdate = true; - bRampParmNeedsUpdate = true; - } - - - ColorRampParameter->MarkChanged(bRampParmNeedsUpdate); -} - -//void -//FHoudiniParameterDetails::SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter) -//{ -// if (!FloatRampParameter) -// return; -// -// // Do not sync when the Houdini asset component is cooking -// if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) -// return; -// -// TArray &CachedPoints = FloatRampParameter->CachedPoints; -// TArray &CurrentPoints = FloatRampParameter->Points; -// -// int32 Idx = 0; -// -// while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) -// { -// UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; -// UHoudiniParameterRampFloatPoint* &CurrentPoint = CurrentPoints[Idx]; -// -// if (!CachedPoint || !CurrentPoint) -// continue; -// -// if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) -// { -// if (CurrentPoint->PositionParentParm) -// { -// CurrentPoint->SetPosition(CachedPoint->GetPosition()); -// CurrentPoint->PositionParentParm->MarkChanged(true); -// } -// } -// -// if (CachedPoint->GetValue() != CurrentPoint->GetValue()) -// { -// if (CurrentPoint->ValueParentParm) -// { -// CurrentPoint->SetValue(CachedPoint->GetValue()); -// CurrentPoint->ValueParentParm->MarkChanged(true); -// } -// } -// -// if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) -// { -// if (CurrentPoint->InterpolationParentParm) -// { -// CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); -// CurrentPoint->InterpolationParentParm->MarkChanged(true); -// } -// } -// -// Idx += 1; -// } -// -// // Insert points -// for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) -// { -// UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; -// if (!CachedPoint) -// continue; -// -// CreateFloatRampParameterInsertEvent( -// FloatRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); -// -// FloatRampParameter->MarkChanged(true); -// } -// -// // Remove points -// for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) -// { -// FloatRampParameter->RemoveElement(IdxCurrentPointLeft); -// -// UHoudiniParameterRampFloatPoint* Point = CurrentPoints[IdxCurrentPointLeft]; -// -// if (!Point) -// continue; -// -// CreateFloatRampParameterDeleteEvent(FloatRampParameter, Point->InstanceIndex); -// -// FloatRampParameter->MarkChanged(true); -// } -//} - -void -FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetFloatRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - InParam->ModificationEvents.Add(DeleteEvent); -} - -void -FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetColorRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - InParam->ModificationEvents.Add(DeleteEvent); -} - -void -FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, - const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetFloatRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertFloat = InValue; - InsertEvent->InsertInterpolation = InInterp; - - InParam->ModificationEvents.Add(InsertEvent); -} - -void -FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, - const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetColorRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertColor = InColor; - InsertEvent->InsertInterpolation = InInterp; - - InParam->ModificationEvents.Add(InsertEvent); -} - -void -FHoudiniParameterDetails:: ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters) -{ - if (FloatRampParameters.Num() < 1) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0]; - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) - { - UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx]; - - if (!NextFloatRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); - } -} - -void -FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters) -{ - if (FloatRampParameters.Num() < 1) - return; - - if (!FloatRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - - for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) - { - if (!FloatRampParameters[Idx].IsValid()) - continue; - - UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx].Get(); - - if (!NextFloatRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); - } - -} - -void -FHoudiniParameterDetails:: ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam) -{ - if (!Param || !MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) - return; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Use Synced points if the MainParam is on auto update mode - // Use Cached points if the Mainparam is on manual update mode - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - if (Param->IsAutoUpdate() && bCookingEnabled) - { - TArray & Points = Param->Points; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (MainPoint->GetPosition() != Point->GetPosition()) - { - if (Point->PositionParentParm) - { - Point->SetPosition(MainPoint->GetPosition()); - Point->PositionParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetValue() != Point->GetValue()) - { - if (Point->ValueParentParm) - { - Point->SetValue(MainPoint->GetValue()); - Point->ValueParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetInterpolation() != Point->GetInterpolation()) - { - if (Point->InterpolationParentParm) - { - Point->SetInterpolation(MainPoint->GetInterpolation()); - Point->InterpolationParentParm->MarkChanged(true); - } - } - - PointIdx += 1; - } - - int32 PointInsertIdx = PointIdx; - int32 PointDeleteIdx = PointIdx; - - // skip the pending modification events - for (auto & Event : Param->ModificationEvents) - { - if (!Event) - continue; - - if (Event->IsInsertEvent()) - PointInsertIdx += 1; - - if (Event->IsDeleteEvent()) - PointDeleteIdx += 1; - } - - // There are more points in MainPoints array - for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) - { - UHoudiniParameterRampFloatPoint*& NextMainPoint = MainPoints[PointInsertIdx]; - - if (!NextMainPoint) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(Param, - NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); - - Param->MarkChanged(true); - } - - // There are more points in Points array - for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) - { - UHoudiniParameterRampFloatPoint*& NextPoint = Points[PointDeleteIdx]; - - if (!NextPoint) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); - - Param->MarkChanged(true); - } - - } - else - { - TArray &Points = Param->CachedPoints; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (Point->Position != MainPoint->Position) - { - Point->Position = MainPoint->Position; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->PositionParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Value != MainPoint->Value) - { - Point->Value = MainPoint->Value; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->ValueParentParm) - Point->ValueParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Interpolation != MainPoint->Interpolation) - { - Point->Interpolation = MainPoint->Interpolation; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->InterpolationParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - PointIdx += 1; - } - - // There are more points in MainPoints array - for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) - { - UHoudiniParameterRampFloatPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; - - if (!NextMainPoint) - continue; - - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampFloatPoint::StaticClass()); - - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = NextMainPoint->GetPosition(); - NewCachedPoint->Value = NextMainPoint->GetValue(); - NewCachedPoint->Interpolation = NextMainPoint->GetInterpolation(); - - Points.Add(NewCachedPoint); - - Param->bCaching = true; - } - - // there are more points in Points array - for (int32 PointsLeftIdx = PointIdx; PointIdx < Points.Num(); ++PointIdx) - { - Points.Pop(); - Param->bCaching = true; - } - } - -} - - -void -FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters) -{ - if (ColorRampParameters.Num() < 1) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0]; - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) - { - UHoudiniParameterRampColor* NextColorRampParam = ColorRampParameters[Idx]; - - if (!NextColorRampParam) - continue; - - FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParam); - } -} - -void -FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters) -{ - if (ColorRampParameters.Num() < 1) - return; - - if (!ColorRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) - { - if (!ColorRampParameters[Idx].IsValid()) - continue; - - UHoudiniParameterRampColor* NextColorRampParameter = ColorRampParameters[Idx].Get(); - - if (!NextColorRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParameter); - - } - -} - -void -FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParam) -{ - if (!Param || !MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) - return; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Use Synced points if the MainParam is on auto update mode - // Use Cached points if the Mainparam is on manual update mode - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - if (Param->IsAutoUpdate() && bCookingEnabled) - { - TArray & Points = Param->Points; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (MainPoint->GetPosition() != Point->GetPosition()) - { - if (Point->PositionParentParm) - { - Point->SetPosition(MainPoint->GetPosition()); - Point->PositionParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetValue() != Point->GetValue()) - { - if (Point->ValueParentParm) - { - Point->SetValue(MainPoint->GetValue()); - Point->ValueParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetInterpolation() != Point->GetInterpolation()) - { - if (Point->InterpolationParentParm) - { - Point->SetInterpolation(MainPoint->GetInterpolation()); - Point->InterpolationParentParm->MarkChanged(true); - } - } - - PointIdx += 1; - - } - - int32 PointInsertIdx = PointIdx; - int32 PointDeleteIdx = PointIdx; - - // skip the pending modification events - for (auto & Event : Param->ModificationEvents) - { - if (!Event) - continue; - - if (Event->IsInsertEvent()) - PointInsertIdx += 1; - - if (Event->IsDeleteEvent()) - PointDeleteIdx += 1; - } - - // There are more points in MainPoints array - for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) - { - UHoudiniParameterRampColorPoint*& NextMainPoint = MainPoints[PointInsertIdx]; - - if (!NextMainPoint) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(Param, - NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); - - Param->MarkChanged(true); - } - - // There are more points in Points array - for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) - { - UHoudiniParameterRampColorPoint*& NextPoint = Points[PointDeleteIdx]; - - if (!NextPoint) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); - - Param->MarkChanged(true); - } - } - else - { - TArray &Points = Param->CachedPoints; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (Point->Position != MainPoint->Position) - { - Point->Position = MainPoint->Position; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->PositionParentParm) - Point->PositionParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Value != MainPoint->Value) - { - Point->Value = MainPoint->Value; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->ValueParentParm) - Point->ValueParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Interpolation != MainPoint->Interpolation) - { - Point->Interpolation = MainPoint->Interpolation; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->InterpolationParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - PointIdx += 1; - } - - // There are more points in Main Points array. - for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) - { - UHoudiniParameterRampColorPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; - - if (!NextMainPoint) - continue; - - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampColorPoint::StaticClass()); - - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = NextMainPoint->Position; - NewCachedPoint->Value = NextMainPoint->Value; - NewCachedPoint->Interpolation = NextMainPoint->Interpolation; - - Points.Add(NewCachedPoint); - - Param->bCaching = true; - } - - // There are more points in Points array - for (int32 PointsleftIdx = PointIdx; PointIdx < MainPoints.Num(); ++PointsleftIdx) - { - Points.Pop(); - - Param->bCaching = true; - } - } -} - -// Check recussively if a parameter hits the end of a visible tabs -void -FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - // When the paramId is invalid, the directory won't parse. - // So simply return the function - if (InParam->GetParmId() < 0) - return; - - // Do not end the tab if this param is a non empty parent type, leave it to its children - EHoudiniParameterType ParmType = InParam->GetParameterType(); - if ((ParmType == EHoudiniParameterType::FolderList || - ParmType == EHoudiniParameterType::Folder) && InParam->GetTupleSize() > 0) - return; - - if (ParmType == EHoudiniParameterType::MultiParm) - { - UHoudiniParameterMultiParm * InMultiParm = Cast(InParam); - if (!InMultiParm) - return; - - if (InMultiParm->MultiParmInstanceCount * InMultiParm->MultiParmInstanceLength > 0) - return; - } - - int32 ParentParamId = InParam->GetParentParmId(); - UHoudiniParameter* CurParam = InParam; - - while (AllFoldersAndFolderLists.Contains(ParentParamId) || AllMultiParms.Contains(ParentParamId)) - { - // The parent is a multiparm - if (AllMultiParms.Contains(ParentParamId)) - { - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentParamId]; - if (!ParentMultiParm || ParentMultiParm->IsPendingKill()) - return; - - if (ParentMultiParm->MultiParmInstanceCount * ParentMultiParm->MultiParmInstanceLength - 1 == CurParam->GetChildIndex()) - { - ParentParamId = ParentMultiParm->GetParentParmId(); - CurParam = ParentMultiParm; - - continue; - } - else - { - // return directly if the parameter is not the last child param of the multiparm - return; - } - } - // The parent is a folder or folderlist - else - { - UHoudiniParameter* ParentFolderParam = AllFoldersAndFolderLists[ParentParamId]; - CurParam = ParentFolderParam; - - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) - return; - - // The parent is a folder - if (ParentFolderParam->GetParameterType() == EHoudiniParameterType::Folder) - { - ParentParamId = ParentFolderParam->GetParentParmId(); - - continue; - } - // The parent is a folderlist - else - { - UHoudiniParameterFolderList* ParentFolderList = Cast(ParentFolderParam); - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) - return; - - if (ParentFolderList->IsTabMenu() && ParentFolderList->IsTabsShown() && ParentFolderList->IsTabParseFinished() && DividerLinePositions.Num() > 0) - { - if (!CurrentTabEndingRow) - CreateTabEndingRow(HouParameterCategory); - - if (CurrentTabEndingRow) - { - CurrentTabEndingRow->EndingDividerLinePositions.Add(DividerLinePositions.Top()); - CurrentTabEndingRow->DividerLinePositions.Pop(); - } - - DividerLinePositions.Pop(); - - ParentParamId = ParentFolderList->GetParentParmId(); - } - else - { - return; - } - - } - - } - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterDetails.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniInput.h" +#include "HoudiniAsset.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "SNewFilePathPicker.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "IDetailGroup.h" +#include "IDetailCustomization.h" +#include "PropertyCustomizationHelpers.h" +#include "DetailWidgetRow.h" +#include "Math/UnitConversion.h" +#include "ScopedTransaction.h" +#include "EditorDirectories.h" + +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "Widgets/Colors/SColorPicker.h" +#include "Widgets/Views/SExpanderArrow.h" +#include "Widgets/Layout/SExpandableArea.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Input/NumericUnitTypeInterface.inl" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SSplitter.h" +#include "SCurveEditorView.h" +#include "SAssetDropTarget.h" +#include "AssetThumbnail.h" + +#include "Sound/SoundBase.h" +#include "Engine/SkeletalMesh.h" +#include "Particles/ParticleSystem.h" +#include "FoliageType.h" + +#include "HoudiniInputDetails.h" + +#include "Framework/SlateDelegates.h" +#include "Templates/SharedPointer.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +int32 +SCustomizedButton::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + TSharedPtr Content = GetContent(); + + // 0. Initialize Line Buffer. + TArray Line; + Line.SetNumUninitialized(2); + + // Initialize Color buffer. + FLinearColor Color = FLinearColor::White; + + // 1. Draw the radio button. + if (bIsRadioButton) + { + // Construct the radio button circles exactly once, + // All radio buttons share the same circles then + if (FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER || + FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER) + { + ConstructRadioButtonCircles(); + } + + DrawRadioButton(AllottedGeometry, OutDrawElements, LayerId, bChosen); + } + + // 2. Draw background color (if selected) + if (bChosen) + { + Line[0].X = AllottedGeometry.Size.X - AllottedGeometry.Size.Y / 2.0f + 2.5f; + Line[0].Y = Content->GetDesiredSize().Y / 2.0f; + Line[1].X = AllottedGeometry.Size.Y / 2.0f - 2.5f; + Line[1].Y = Content->GetDesiredSize().Y / 2.0f; + + Color = FLinearColor::White; + Color.A = bIsRadioButton ? 0.05 : 0.1; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, AllottedGeometry.Size.Y); + } + + // 3. Drawing square around the text + { + // Switch the point order for each line to save few value assignment cycles + Line[0].X = 0.0f; + Line[0].Y = 0.0f; + Line[1].X = 0.0f; + Line[1].Y = Content->GetDesiredSize().Y; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); + + //Line[0].X = 0.0f; + //Line[0].Y = Content->GetDesiredSize().Y; + Line[0].X = AllottedGeometry.Size.X; + Line[0].Y = Content->GetDesiredSize().Y; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, bChosen ? FLinearColor::Gray : FLinearColor::Black, true, 1.0f); + + //Line[0].X = AllottedGeometry.Size.X; + //Line[0].Y = Content->GetDesiredSize().Y; + Line[1].X = AllottedGeometry.Size.X; + Line[1].Y = 0.0f; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); /* draw gray bottom line if this tab is selected, black otherwise*/ + + //Line[0].X = AllottedGeometry.Size.X; + //Line[0].Y = 0.0f; + Line[0].X = 0.0f; + Line[0].Y = 0.0f; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); + } + + // 4. Draw child widget + Content->Paint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + return LayerId; +}; + +void +SCustomizedButton::ConstructRadioButtonCircles() const +{ + TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); + TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); + OuterPoints.Empty(); + InnerPoints.Empty(); + + OuterPoints.SetNumZeroed(HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER); + InnerPoints.SetNumZeroed(8); + + // Construct outer circle + int32 CurDegree = 0; + int32 DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; + + for (int32 Idx = 0; Idx < HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; ++Idx) + { + OuterPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); + OuterPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); + + CurDegree += DegStep; + } + + // Construct inner circle + CurDegree = 0; + DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER; + for (int32 Idx = 0; Idx < 8; ++Idx) + { + InnerPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); + InnerPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); + + CurDegree += DegStep; + } +} + +void +SCustomizedButton::DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const +{ + TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); + TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); + if (OuterPoints.Num() <= 1 || InnerPoints.Num() <= 1) + return; + + FLinearColor ColorNonSelected = FLinearColor::White; + FLinearColor ColorSelected = FLinearColor::Yellow; + + // initialize line buffer + TArray Line; + Line.SetNumZeroed(2); + bool alternator = false; + + // Draw outer circle + Line[0] = OuterPoints.Last(); + for (int32 Idx = 0; Idx < OuterPoints.Num(); ++Idx) + { + // alternate the points order each time to some some assignment cycles + if (alternator) + { + Line[0].X = OuterPoints[Idx].X; + Line[0].Y = OuterPoints[Idx].Y; + } + else + { + Line[1].X = OuterPoints[Idx].X; + Line[1].Y = OuterPoints[Idx].Y; + } + + alternator = !alternator; + + // Draw a line segment + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, ColorNonSelected, true, 1.0f); + } + + // Draw inner circle + alternator = false; + Line[0] = InnerPoints.Last(); + for (int32 Idx = 0; Idx < InnerPoints.Num(); ++Idx) + { + // alternate the points order each time to some some assignment cycles + if (alternator) + { + Line[0].X = InnerPoints[Idx].X; + Line[0].Y = InnerPoints[Idx].Y; + } + else + { + Line[1].X = InnerPoints[Idx].X; + Line[1].Y = InnerPoints[Idx].Y; + } + + alternator = !alternator; + + // Draw a line segment + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, bSelected ? ColorSelected : ColorNonSelected, true, 3.0f); + } +} + +void +SCustomizedBox::SetHoudiniParameter(TArray& InParams) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + + bool bIsMultiparmInstanceHeader = MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 0; + + switch (MainParam->GetParameterType()) + { + case EHoudiniParameterType::Button: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON; + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP; + } + break; + + case EHoudiniParameterType::Color: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR; + } + break; + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor * ColorRampParameter = Cast(MainParam); + if (!ColorRampParameter || ColorRampParameter->IsPendingKill()) + return; + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP; + if (ColorRampParameter->CachedPoints.Num() > 0) + MarginHeight = MarginHeight + HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE * (float)(ColorRampParameter->CachedPoints.Num() - 1); + } + break; + + case EHoudiniParameterType::File: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE; + } + break; + + case EHoudiniParameterType::FileDir: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR; + } + break; + + case EHoudiniParameterType::FileGeo: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO; + } + break; + + case EHoudiniParameterType::FileImage: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE; + } + break; + + case EHoudiniParameterType::Float: + { + if (MainParam->GetTupleSize() == 3) + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3; + } + else + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT + + (MainParam->GetTupleSize() - 1)* HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE; + } + } + } + break; + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat * FloatRampParameter = Cast(MainParam); + if (!FloatRampParameter || FloatRampParameter->IsPendingKill()) + return; + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP; + + if (FloatRampParameter->CachedPoints.Num() > 0) + MarginHeight = MarginHeight + Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE * (float)(FloatRampParameter->CachedPoints.Num() - 1); + } + break; + + case EHoudiniParameterType::Folder: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER; + } + break; + + case EHoudiniParameterType::FolderList: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST; + } + break; + + case EHoudiniParameterType::Input: + { + UHoudiniParameterOperatorPath* InputParam = Cast(MainParam); + + if (!InputParam || InputParam->IsPendingKill() || !InputParam->HoudiniInput.IsValid()) + break; + + UHoudiniInput* Input = InputParam->HoudiniInput.Get(); + + if (!Input || Input->IsPendingKill()) + break; + + + if (bIsMultiparmInstanceHeader) + { + switch (Input->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + int32 ExpandedTransformUIs = 0; + for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) + { + if (Input->IsTransformUIExpanded(Idx)) + ExpandedTransformUIs += 1; + } + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER + + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Curve: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Asset: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Landscape: + { + if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::World: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Skeletal: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER; + } + break; + default: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER; + break; + } + } + else + { + switch (Input->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + int32 ExpandedTransformUIs = 0; + for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) + { + if (Input->IsTransformUIExpanded(Idx)) + ExpandedTransformUIs += 1; + } + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE + + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM; + } + break; + case EHoudiniInputType::Curve: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE; + } + break; + case EHoudiniInputType::Asset: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET; + } + break; + case EHoudiniInputType::Landscape: + { + if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH; + } + break; + case EHoudiniInputType::World: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD; + } + break; + case EHoudiniInputType::Skeletal: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL; + } + break; + default: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT; + break; + + } + } + } + break; + + case EHoudiniParameterType::Int: + { + if (MainParam->GetTupleSize() == 3) + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3; + } + else + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE; + } + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE; + } + break; + + case EHoudiniParameterType::Label: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL; + } + break; + + case EHoudiniParameterType::MultiParm: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM; + } + break; + + case EHoudiniParameterType::Separator: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR; + bIsSeparator = true; + } + break; + + case EHoudiniParameterType::String: + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE; + } + } + break; + + case EHoudiniParameterType::StringAssetRef: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF; + } + break; + + case EHoudiniParameterType::StringChoice: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE; + } + break; + + case EHoudiniParameterType::Toggle: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE; + } + break; + + case EHoudiniParameterType::Invalid: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID; + break; + + default: + MarginHeight = 0.0f; + break; + } +} + +float +SCustomizedBox::AddIndentation(UHoudiniParameter* InParam, + TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists) +{ + if (!InParam || InParam->IsPendingKill()) + return 0.0f; + + bool bIsMainParmSimpleFolder = false; + // Get if this Parameter is a simple / collapsible folder + if (InParam->GetParameterType() == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParm = Cast(InParam); + if (FolderParm) + bIsMainParmSimpleFolder = !FolderParm->IsTab(); + } + + int32 ParentId = InParam->GetParentParmId(); + UHoudiniParameter* CurParm = InParam; + float Indentation = 0.0f; + + while (ParentId >= 0) + { + UHoudiniParameter* ParentFolder = nullptr; + UHoudiniParameterMultiParm* ParentMultiParm = nullptr; + + if (InAllFoldersAndFolderLists.Contains(ParentId)) + ParentFolder = InAllFoldersAndFolderLists[ParentId]; + + if (InAllMultiParms.Contains(ParentId)) + ParentMultiParm = InAllMultiParms[ParentId]; + + // The parent is a folder, add one unit of indentation + if (ParentFolder) + { + // Update the parent parm id + ParentId = ParentFolder->GetParentParmId(); + + if (ParentFolder->GetParameterType() == EHoudiniParameterType::FolderList) + continue; + + UHoudiniParameterFolder* Folder = Cast(ParentFolder); + + if (!Folder) + continue; + + // update the current parm, find the parent of new cur param in the next round + CurParm = Folder; + Indentation += 1.0f; + } + // The parent is a multiparm + else if (ParentMultiParm) + { + // Update the parent parm id + ParentId = ParentMultiParm->GetParentParmId(); + + if (CurParm->GetChildIndex() == 0) + { + Indentation += 0.0f; + } + else + { + Indentation += 2.0f; + } + + // update the current parm, find the parent of new cur param in the next round + CurParm = ParentMultiParm; + } + else + { + // no folder/multiparm parent, end the loop + ParentId = -1; + } + } + + + float IndentationWidth = INDENTATION_UNIT_WIDTH * Indentation; + + // Add a base indentation to non simple/collapsible param + // Since it needs more space to offset the arrow width + if (!bIsMainParmSimpleFolder) + IndentationWidth += NON_FOLDER_OFFSET_WIDTH; + + this->AddSlot().AutoWidth() + [ + SNew(SBox).WidthOverride(IndentationWidth) + ]; + + + return IndentationWidth; +}; + +int32 +SCustomizedBox::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + + SHorizontalBox::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + // Initialize line buffer + TArray Line; + Line.SetNumZeroed(2); + // Initialize color buffer + FLinearColor Color = FLinearColor::White; + Color.A = 0.3; + + // draw the bottom line if this row is the tab folder list + if (bIsTabFolderListRow) + { + // Get the start position of the tabs bottom line (right bottom pt of the right most child widget) + float VerticalLineStartPosX = 0.0f; + float VerticalLineStartPosY = 0.0f; + float BottomLineStartPosX = 0.0f; + float BottomLineStartPosY = -1.0f; + + for (int32 Idx = 0; Idx < Children.Num(); ++Idx) + { + TSharedPtr CurChild = Children.GetChildAt(Idx); + if (!CurChild.IsValid()) + continue; + + if (Idx == 0) + { + VerticalLineStartPosX = CurChild->GetDesiredSize().X; + VerticalLineStartPosY = CurChild->GetDesiredSize().Y; + } + + BottomLineStartPosX += CurChild->GetDesiredSize().X; + + if (BottomLineStartPosY < 0.0f) + BottomLineStartPosY= CurChild->GetDesiredSize().Y; + } + + // Draw bottom line + Line[0].X = BottomLineStartPosX; + Line[0].Y = BottomLineStartPosY; + Line[1].X = AllottedGeometry.Size.X; + Line[1].Y = BottomLineStartPosY; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + + // Draw divider lines + { + Line[0].Y = -MarginHeight; + Line[1].Y = AllottedGeometry.Size.Y + MarginHeight; + + int32 NumOfLinesToDraw = bIsTabFolderListRow ? DividerLinePositions.Num() - 1 : DividerLinePositions.Num(); + for (int32 Idx = 0; Idx < NumOfLinesToDraw; ++Idx) + { + const float& CurDivider = DividerLinePositions[Idx]; + Line[0].X = CurDivider; + Line[1].X = CurDivider; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + + // Draw the last inner most divider line differently when this the tabs' row. + if (bIsTabFolderListRow && DividerLinePositions.Num() > 0) + { + const float& TabDivider = DividerLinePositions.Last(); + Line[0].X = TabDivider; + Line[1].X = TabDivider; + Line[0].Y = 0.f; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + } + + // Draw tab ending lines + { + float YPos = 0.0f; + + for (const float & CurEndingDivider : EndingDividerLinePositions) + { + // Draw cur ending line (vertical) + + Line[0].X = CurEndingDivider; + Line[0].Y = -2.3f; + Line[1].X = CurEndingDivider; + Line[1].Y = YPos; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + + // Draw cur ending line (horizontal) + + // Line[0].X = CurEndingDivider; + Line[0].Y = YPos; + Line[1].X = AllottedGeometry.Size.X; + // Line[1].Y = YPos; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + + YPos += 2.0f; + } + } + + // Draw the separator line if this is the row of a separator parameter + { + if (bIsSeparator) + { + Line[0].X = 25.f; + if (DividerLinePositions.Num() > 0) + Line[0].X += DividerLinePositions.Last(); + + Line[0].Y = AllottedGeometry.Size.Y / 2.f; + Line[1].X = AllottedGeometry.Size.X - 20.f; + Line[1].Y = Line[0].Y; + + Color.A = 0.7; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.5f); + } + } + + return LayerId; +}; + +void +SHoudiniFloatRampCurveEditor::Construct(const FArguments & InArgs) +{ + SCurveEditor::Construct(SCurveEditor::FArguments() + .ViewMinInput(InArgs._ViewMinInput) + .ViewMaxInput(InArgs._ViewMaxInput) + .ViewMinOutput(InArgs._ViewMinOutput) + .ViewMaxOutput(InArgs._ViewMaxOutput) + .XAxisName(InArgs._XAxisName) + .YAxisName(InArgs._YAxisName) + .HideUI(InArgs._HideUI) + .DrawCurve(InArgs._DrawCurve) + .TimelineLength(InArgs._TimelineLength) + .AllowZoomOutput(InArgs._AllowZoomOutput) + .ShowInputGridNumbers(InArgs._ShowInputGridNumbers) + .ShowOutputGridNumbers(InArgs._ShowOutputGridNumbers) + .ShowZoomButtons(InArgs._ShowZoomButtons) + .ZoomToFitHorizontal(InArgs._ZoomToFitHorizontal) + .ZoomToFitVertical(InArgs._ZoomToFitVertical) + ); + + + UCurveEditorSettings * CurveEditorSettings = GetSettings(); + if (CurveEditorSettings) + { + CurveEditorSettings->SetTangentVisibility(ECurveEditorTangentVisibility::NoTangents); + } +} + +void +SHoudiniColorRampCurveEditor::Construct(const FArguments & InArgs) +{ + SColorGradientEditor::Construct(SColorGradientEditor::FArguments() + .ViewMinInput(InArgs._ViewMinInput) + .ViewMaxInput(InArgs._ViewMaxInput) + ); +} + + +FReply +SHoudiniFloatRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = SCurveEditor::OnMouseButtonUp(MyGeometry, MouseEvent); + + if (!HoudiniFloatRampCurve.IsValid()) + return Reply; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + FRichCurve& FloatCurve = HoudiniFloatRampCurve.Get()->FloatCurve; + + TArray>& FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; + + if (FloatRampParameters.Num() < 1) + return Reply; + + if (!FloatRampParameters[0].IsValid()) + return Reply; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + // Do not allow modification when the parent HDA of the main param is being cooked. + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points of the main float ramp param to other parameters + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + // Modification is based on the main parameter, use synced points if the main param is on auto update mode, use cached points otherwise. + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + // On mouse button up handler handles point modification only + if (FloatCurve.GetNumKeys() != NumMainPoints) + return Reply; + + bool bNeedToRefreshEditor= false; + + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float& CurvePosition = FloatCurve.Keys[Idx].Time; + float& CurveValue = FloatCurve.Keys[Idx].Value; + + // This point is modified + if (MainPoint->GetPosition() != CurvePosition || MainPoint->GetValue() != CurveValue) + { + + // The editor needs refresh only if the main parameter is on manual mode, and has been modified + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedToRefreshEditor = true; + + // Iterate through the float ramp parameter of all selected HDAs. + for (auto & NextRampFloat : FloatRampParameters) + { + if (!NextRampFloat.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedRampFloat = NextRampFloat.Get(); + + if (!SelectedRampFloat) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedRampFloat)) + continue; + + if (SelectedRampFloat->IsAutoUpdate() && bCookingEnabled) + { + // The selected float ramp parameter is on auto update mode, use its synced points. + TArray &SelectedRampPoints = SelectedRampFloat->Points; + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // Synced points in the selected ramp is more than or the same number as that in the main parameter, + // modify the position and value of the synced point and mark them as changed. + + UHoudiniParameterRampFloatPoint*& ModifiedPoint = SelectedRampPoints[Idx]; + + if (!ModifiedPoint) + continue; + + if (ModifiedPoint->GetPosition() != CurvePosition && ModifiedPoint->PositionParentParm) + { + ModifiedPoint->SetPosition(CurvePosition); + ModifiedPoint->PositionParentParm->MarkChanged(true); + } + + if (ModifiedPoint->GetValue() != CurveValue && ModifiedPoint->ValueParentParm) + { + ModifiedPoint->SetValue(CurveValue); + ModifiedPoint->ValueParentParm->MarkChanged(true); + } + } + else + { + // Synced points in the selected ramp is less than that in the main parameter + // Since we have pushed the points of the main param to all of the selected ramps, + // We need to modify the insert event. + + int32 IndexInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedRampFloat->ModificationEvents.IsValidIndex(Idx)) + { + UHoudiniParameterRampModificationEvent*& ModEvent = SelectedRampFloat->ModificationEvents[Idx]; + if (!ModEvent) + continue; + + if (ModEvent->InsertPosition != CurvePosition) + ModEvent->InsertPosition = CurvePosition; + + if (ModEvent->InsertFloat != CurveValue) + ModEvent->InsertFloat = CurveValue; + } + + } + } + else + { + // The selected float ramp is on manual update mode, use the cached points. + TArray &FloatRampCachedPoints = SelectedRampFloat->CachedPoints; + + // Since we have pushed the points in main param to all the selected float ramp, + // we need to modify the corresponding cached point in the selected float ramp. + + if (FloatRampCachedPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampFloatPoint*& ModifiedCachedPoint = FloatRampCachedPoints[Idx]; + + if (!ModifiedCachedPoint) + continue; + + if (ModifiedCachedPoint->Position != CurvePosition) + { + ModifiedCachedPoint->Position = CurvePosition; + SelectedRampFloat->bCaching = true; + if (!bCookingEnabled) + { + //SelectedRampFloat->MarkChanged(true); + if (ModifiedCachedPoint->PositionParentParm) + ModifiedCachedPoint->PositionParentParm->MarkChanged(true); + } + } + + if (ModifiedCachedPoint->Value != CurveValue) + { + ModifiedCachedPoint->Value = CurveValue; + SelectedRampFloat->bCaching = true; + if (!bCookingEnabled) + { + //SelectedRampFloat->MarkChanged(true); + if (ModifiedCachedPoint->ValueParentParm) + ModifiedCachedPoint->ValueParentParm->MarkChanged(true); + } + } + } + } + } + } + } + + + if (bNeedToRefreshEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } + + return Reply; +} + +FReply +SHoudiniFloatRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + FReply Reply = SCurveEditor::OnKeyDown(MyGeometry, InKeyEvent); + + if (InKeyEvent.GetKey().ToString() != FString("Enter")) + return Reply; + + if (!HoudiniFloatRampCurve.IsValid() || !HoudiniFloatRampCurve.Get()) + return Reply; + + TArray> FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; + + if (FloatRampParameters.Num() < 1) + return Reply; + + if (!FloatRampParameters[0].IsValid()) + return Reply; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Do nothing if the main param is on auto update mode + if (MainParam->IsAutoUpdate() && bCookingEnabled) + return Reply; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points in the main float ramp to the float ramp parameters in all selected HDAs. + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + for (auto& NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + if (!SelectedFloatRamp) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not sync the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + // Sync the cached points if the selected float ramp parameter is on manual update mode + //FHoudiniParameterDetails::SyncCachedFloatRampPoints(SelectedFloatRamp); + SelectedFloatRamp->SyncCachedPoints(); + } + + return Reply; +} + +void +UHoudiniFloatRampCurve::OnCurveChanged(const TArray& ChangedCurveEditInfos) +{ + Super::OnCurveChanged(ChangedCurveEditInfos); + + if (FloatRampParameters.Num() < 1) + return; + + if (!FloatRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return; + + // Do not allow modification when the parent HDA of the main param is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + // Push all the points of the Main parameter to other parameters + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode, otherwise use its cached points. + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + bool bNeedUpdateEditor = false; + + // OnCurveChanged handler handles point delete and insertion only + + // A point is deleted. + if (FloatCurve.GetNumKeys() < NumMainPoints) + { + // Find the index of the deleted point + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float CurPointPosition = MainPoint->GetPosition(); + float CurCurvePosition = -1.0f; + + if (FloatCurve.Keys.IsValidIndex(Idx)) + CurCurvePosition = FloatCurve.Keys[Idx].Time; + + // Delete the point at Idx + if (CurCurvePosition != CurPointPosition) + { + // Iterate through all the float ramp parameter in all the selected HDAs + for (auto & NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + if (!SelectedFloatRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + TArray & SelectedRampPoints = SelectedFloatRamp->Points; + + // The selected float ramp is on auto update mode: + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // If the number of synced points of the selected float ramp is greater or equal to the number of points of that in the main param, + // Create a Houdini engine manager event to delete the point at Idx of the selected float ramp; + + UHoudiniParameterRampFloatPoint* PointToDelete = SelectedRampPoints[Idx]; + + if (!PointToDelete) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(SelectedFloatRamp, PointToDelete->InstanceIndex); + SelectedFloatRamp->MarkChanged(true); + } + else + { + // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, + // delete the corresponding inserting event. + + int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + SelectedFloatRamp->ModificationEvents.RemoveAt(IdxInEventsArray); + } + } + else + { + // The selected float ramp is on manual update mode: + // Since we have pushed all the points in main param to the cached points of the selected float ramp, + // remove the corresponding points from the cached points array. + + if (SelectedFloatRamp->CachedPoints.IsValidIndex(Idx)) + { + SelectedFloatRamp->CachedPoints.RemoveAt(Idx); + SelectedFloatRamp->bCaching = true; + } + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point is inserted + else if (FloatCurve.GetNumKeys() > NumMainPoints) + { + // Find the index of the inserted point + for (int32 Idx = 0; Idx < FloatCurve.GetNumKeys(); ++Idx) + { + + float CurPointPosition = -1.0f; + float CurCurvePosition = FloatCurve.Keys[Idx].Time; + + if (MainPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + CurPointPosition = MainPoint->GetPosition(); + } + + // Insert instance at Idx + if (CurPointPosition != CurCurvePosition) + { + // Iterate through the float ramp parameter of all selected HDAs. + for (auto & NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + // If the selected float ramp is on auto update mode: + // Since we have pushed all the points of main parameter to the selected, + // create a Houdini engine manager event to insert a point. + + FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent( + SelectedFloatRamp, CurCurvePosition, FloatCurve.Keys[Idx].Value, EHoudiniRampInterpolationType::LINEAR); + + SelectedFloatRamp->MarkChanged(true); + } + else + { + // If the selected float ramp is on manual update mode: + // push a new point to the cached points array + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(SelectedFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = CurCurvePosition; + NewCachedPoint->Value = FloatCurve.Keys[Idx].Value; + NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; + + if (Idx >= SelectedFloatRamp->CachedPoints.Num()) + SelectedFloatRamp->CachedPoints.Add(NewCachedPoint); + else + SelectedFloatRamp->CachedPoints.Insert(NewCachedPoint, Idx); + + SelectedFloatRamp->bCaching = true; + + if (!bCookingEnabled) + SelectedFloatRamp->MarkChanged(true); + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + if (bNeedUpdateEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } + +} + + +FReply +SHoudiniColorRampCurveEditor::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = SColorGradientEditor::OnMouseButtonDown(MyGeometry, MouseEvent); + + if (HoudiniColorRampCurve.IsValid()) + { + UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); + if (Curve) + Curve->bEditing = true; + } + + return Reply; +} + +FReply +SHoudiniColorRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + + FReply Reply = SColorGradientEditor::OnMouseButtonUp(MyGeometry, MouseEvent); + + if (HoudiniColorRampCurve.IsValid()) + { + UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); + + if (Curve) + { + Curve->bEditing = false; + Curve->OnColorRampCurveChanged(true); + } + } + + return Reply; + +} + +FReply +SHoudiniColorRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + FReply Reply = SColorGradientEditor::OnKeyDown(MyGeometry, InKeyEvent); + + if (InKeyEvent.GetKey().ToString() != FString("Enter")) + return Reply; + + if (!HoudiniColorRampCurve.IsValid() || !HoudiniColorRampCurve.Get()) + return Reply; + + TArray> &ColorRampParameters = HoudiniColorRampCurve.Get()->ColorRampParameters; + + if (ColorRampParameters.Num() < 1) + return Reply; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + // Do nothing if the main param is on auto update mode + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + if (MainParam->IsAutoUpdate() && bCookingEnabled) + return Reply; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points in the main color ramp to the color ramp parameters in all selected HDAs. + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); + + for (auto& NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not sync the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + // Sync the cached points if the selected color ramp is on manual update mode + FHoudiniParameterDetails::SyncCachedColorRampPoints(SelectedColorRamp); + } + + return Reply; +} + +void +UHoudiniColorRampCurve::OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) +{ + Super::OnCurveChanged(ChangedCurveEditInfos); + + OnColorRampCurveChanged(); +} + +void +UHoudiniColorRampCurve::OnColorRampCurveChanged(bool bModificationOnly) +{ + // Array is always true in this case + // if (!FloatCurves) + // return; + + if (ColorRampParameters.Num() < 1) + return; + + if (!ColorRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return; + + // Do not allow modification when the parent HDA of the main param is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + // Push all the points of the main parameter to other parameters + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); + + // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode,otherwise use its cached points. + bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + bool bNeedUpdateEditor = false; + + // OnCurveChanged handler of color ramp curve editor handles point delete, insert and color change + + // A point is deleted + if (FloatCurves->GetNumKeys() < NumMainPoints) + { + if (bModificationOnly) + return; + + // Find the index of the deleted point + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float CurPointPosition = MainPoint->GetPosition(); + float CurCurvePosition = -1.0f; + + if (FloatCurves[0].Keys.IsValidIndex(Idx)) + CurCurvePosition = FloatCurves[0].Keys[Idx].Time; + + // Delete the point at Idx + if (CurCurvePosition != CurPointPosition) + { + // Iterate through all the color ramp parameter in all the selected HDAs + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + TArray & SelectedRampPoints = SelectedColorRamp->Points; + + // The selected color ramp is on auto update mode: + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // If the number of synced points of the selected color ramp is greater or equal to the number of points of that in the main param, + // create a Houdini engine manager event to delete the point at Idx of the selected float ramp; + + UHoudiniParameterRampColorPoint* PointToDelete = SelectedRampPoints[Idx]; + + if (!PointToDelete) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(SelectedColorRamp, PointToDelete->InstanceIndex); + SelectedColorRamp->MarkChanged(true); + } + else + { + // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, + // delete the corresponding inserting event. + + int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + SelectedColorRamp->ModificationEvents.RemoveAt(IdxInEventsArray); + } + } + else + { + // The selected color ramp is on manual update mode: + // Since we have pushed all the points in main param to the cached points of the selected float ramp, + // remove the corresponding points from the cached points array + if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) + { + SelectedColorRamp->CachedPoints.RemoveAt(Idx); + SelectedColorRamp->bCaching = true; + } + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point is inserted + else if (FloatCurves[0].GetNumKeys() > NumMainPoints) + { + + if (bModificationOnly) + return; + + // Find the index of the inserted point + for (int32 Idx = 0; Idx < FloatCurves[0].GetNumKeys(); ++Idx) + { + + float CurPointPosition = -1.0f; + float CurCurvePosition = FloatCurves[0].Keys[Idx].Time; + + if (MainPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + CurPointPosition = MainPoint->GetPosition(); + } + + // Insert a point at Idx + if (CurPointPosition != CurCurvePosition) + { + // Get the interpolation value of inserted color point + + FLinearColor ColorPrev = FLinearColor::Black; + FLinearColor ColorNext = FLinearColor::White; + float PositionPrev = 0.0f; + float PositionNext = 1.0f; + + if (MainParam->IsAutoUpdate() && bCookingEnabled) + { + // Try to get its previous point's color + if (MainParam->Points.IsValidIndex(Idx - 1)) + { + ColorPrev = MainParam->Points[Idx - 1]->GetValue(); + PositionPrev = MainParam->Points[Idx - 1]->GetPosition(); + } + + // Try to get its next point's color + if (MainParam->Points.IsValidIndex(Idx)) + { + ColorNext = MainParam->Points[Idx]->GetValue(); + PositionNext = MainParam->Points[Idx]->GetPosition(); + } + } + else + { + // Try to get its previous point's color + if (MainParam->CachedPoints.IsValidIndex(Idx - 1)) + { + ColorPrev = MainParam->CachedPoints[Idx - 1]->GetValue(); + PositionPrev = MainParam->CachedPoints[Idx - 1]->GetPosition(); + } + + // Try to get its next point's color + if (MainParam->CachedPoints.IsValidIndex(Idx)) + { + ColorNext = MainParam->CachedPoints[Idx]->GetValue(); + PositionNext = MainParam->CachedPoints[Idx]->GetPosition(); + } + } + + float TotalWeight = FMath::Abs(PositionNext - PositionPrev); + float PrevWeight = FMath::Abs(CurCurvePosition - PositionPrev); + float NextWeight = FMath::Abs(PositionNext - CurCurvePosition); + + FLinearColor InsertedColor = ColorPrev * (PrevWeight / TotalWeight) + ColorNext * (NextWeight / TotalWeight); + + // Iterate through the color ramp parameter of all selected HDAs. + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + // If the selected color ramp is on auto update mode: + // Since we have pushed all the points of main parameter to the selected, + // create a Houdini engine manager event to insert a point. + + FHoudiniParameterDetails::CreateColorRampParameterInsertEvent( + SelectedColorRamp, CurCurvePosition, InsertedColor, EHoudiniRampInterpolationType::LINEAR); + + SelectedColorRamp->MarkChanged(true); + } + else + { + // If the selected color ramp is on manual update mode: + // Push a new point to the cached points array + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(SelectedColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = CurCurvePosition; + NewCachedPoint->Value = InsertedColor; + NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; + + if (Idx >= SelectedColorRamp->CachedPoints.Num()) + SelectedColorRamp->CachedPoints.Add(NewCachedPoint); + else + SelectedColorRamp->CachedPoints.Insert(NewCachedPoint, Idx); + + SelectedColorRamp->bCaching = true; + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!MainParam->IsAutoUpdate() && bCookingEnabled) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point's color is changed + else + { + if (bEditing) + return; + + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + // Only handle color change + { + float CurvePosition = FloatCurves[0].Keys[Idx].Time; + float PointPosition = MainPoint->GetPosition(); + + FLinearColor CurveColor = FLinearColor::Black; + FLinearColor PointColor = MainPoint->GetValue(); + + CurveColor.R = FloatCurves[0].Keys[Idx].Value; + CurveColor.G = FloatCurves[1].Keys[Idx].Value; + CurveColor.B = FloatCurves[2].Keys[Idx].Value; + + // Color is changed at Idx + if (CurveColor != PointColor || CurvePosition != PointPosition) + { + // Iterate through the all selected color ramp parameters + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + // The selected color ramp parameter is on auto update mode + + if (SelectedColorRamp->Points.IsValidIndex(Idx)) + { + // If the number of synced points in the selected color ramp is more or equal to that in the main parameter: + // Modify the corresponding synced point of the selected color ramp, and marked it as changed. + + UHoudiniParameterRampColorPoint* Point = SelectedColorRamp->Points[Idx]; + + if (!Point) + continue; + + if (Point->GetValue() != CurveColor && Point->ValueParentParm) + { + Point->SetValue(CurveColor); + Point->ValueParentParm->MarkChanged(true); + } + + if (Point->GetPosition() != CurvePosition && Point->PositionParentParm) + { + Point->SetPosition(CurvePosition); + Point->PositionParentParm->MarkChanged(true); + } + } + else + { + // If the number of synced points in the selected color ramp is less than that in the main parameter: + // Since we have push the points in the main parameter to all selected parameters, + // we need to modify the corresponding insert event. + + int32 IdxInEventsArray = Idx - SelectedColorRamp->Points.Num(); + + if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = SelectedColorRamp->ModificationEvents[IdxInEventsArray]; + + if (!Event) + continue; + + if (Event->InsertColor != CurveColor) + Event->InsertColor = CurveColor; + + if (Event->InsertPosition != CurvePosition) + Event->InsertPosition = CurvePosition; + } + } + } + else + { + // The selected color ramp is on manual update mode + // Since we have push the points in the main parameter to all selected parameters, + // modify the corresponding point in the cached points array of the selected color ramp. + if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampColorPoint* CachedPoint = SelectedColorRamp->CachedPoints[Idx]; + + if (!CachedPoint) + continue; + + if (CachedPoint->Value != CurveColor) + { + CachedPoint->Value = CurveColor; + bNeedUpdateEditor = true; + } + + if (CachedPoint->Position != CurvePosition) + { + CachedPoint->Position = CurvePosition; + SelectedColorRamp->bCaching = true; + bNeedUpdateEditor = true; + } + } + } + } + } + } + } + } + + + if (bNeedUpdateEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } +} + +template< class T > +bool FHoudiniParameterDetails::CastParameters( + TArray InParams, TArray& OutCastedParams ) +{ + for (auto CurrentParam : InParams) + { + T* CastedParam = Cast(CurrentParam); + if (CastedParam && !CastedParam->IsPendingKill()) + OutCastedParams.Add(CastedParam); + } + + return (OutCastedParams.Num() == InParams.Num()); +} + + +void +FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* InParam = InParams[0]; + if (!InParam || InParam->IsPendingKill()) + return; + + // The directory won't parse if parameter ids are -1 + // simply return + if (InParam->GetParmId() < 0) + return; + + // This parameter is a part of the last float ramp. + if (CurrentRampFloat) + { + CreateWidgetFloatRamp(HouParameterCategory, InParams); + return; + } + // This parameter is a part of the last float ramp. + if (CurrentRampColor) + { + CreateWidgetColorRamp(HouParameterCategory, InParams); + return; + } + + switch (InParam->GetParameterType()) + { + case EHoudiniParameterType::Float: + { + CreateWidgetFloat(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Int: + { + CreateWidgetInt(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::String: + { + CreateWidgetString(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::StringChoice: + { + CreateWidgetChoice(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Separator: + { + TArray SepParams; + if (CastParameters(InParams, SepParams)) + { + bool bEnabled = InParams.IsValidIndex(0) ? !SepParams[0]->IsDisabled() : true; + CreateWidgetSeparator(HouParameterCategory, InParams, bEnabled); + } + } + break; + + case EHoudiniParameterType::Color: + { + CreateWidgetColor(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Button: + { + CreateWidgetButton(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + CreateWidgetButtonStrip(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Label: + { + CreateWidgetLabel(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Toggle: + { + CreateWidgetToggle(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + CreateWidgetFile(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::FolderList: + { + CreateWidgetFolderList(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Folder: + { + CreateWidgetFolder(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::MultiParm: + { + CreateWidgetMultiParm(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::FloatRamp: + { + CreateWidgetFloatRamp(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::ColorRamp: + { + CreateWidgetColorRamp(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Input: + { + CreateWidgetOperatorPath(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Invalid: + { + HandleUnsupportedParmType(HouParameterCategory, InParams); + } + break; + + default: + { + HandleUnsupportedParmType(HouParameterCategory, InParams); + } + break; + } + + // Remove a divider lines recurrsively if current parameter hits the end of a tabs + RemoveTabDividers(HouParameterCategory, InParam); + +} + +void +FHoudiniParameterDetails::CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory) +{ + FDetailWidgetRow & Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()); + TSharedPtr TabEndingRow = SNew(SCustomizedBox); + + TabEndingRow->DividerLinePositions = DividerLinePositions; + + if (TabEndingRow.IsValid()) + CurrentTabEndingRow = TabEndingRow.Get(); + + Row.WholeRowWidget.Widget = TabEndingRow.ToSharedRef(); + Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniParameterDetails::CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam|| MainParam->IsPendingKill()) + return; + + if (!Row) + return; + + TSharedRef< SCustomizedBox > HorizontalBox = SNew(SCustomizedBox); + + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + + + if (MainParam->IsDirectChildOfMultiParm()) + { + FString ParameterLabelStr = MainParam->GetParameterLabel(); + + // If it is head of an multiparm instance + if (MainParam->GetChildIndex() == 0) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + } + ParameterLabelStr += TEXT(" (") + FString("") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + HorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + else + { + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(MainParam->GetParameterLabel()) : FText::GetEmpty(); + HorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + + Row->NameWidget.Widget = HorizontalBox; +} + +void +FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) +{ + if (!Row) + return; + + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + FString ParameterLabelStr = MainParam->GetParameterLabel(); + TSharedRef HorizontalBox = SNew(SCustomizedBox); + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + + TSharedPtr VerticalBox; + HorizontalBox->AddSlot() + [ + SAssignNew(VerticalBox, SVerticalBox) + ]; + + if (MainParam->IsDirectChildOfMultiParm()) + { + // If it is head of an multiparm instance + if (MainParam->GetChildIndex() == 0) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + } + + ParameterLabelStr += TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* RampParameter = Cast(MainParam); + if (RampParameter) + { + if (RampParameter->bCaching) + ParameterLabelStr += "*"; + } + } + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + else + { + // TODO: Refactor me...extend 'auto/manual update' to all parameters? (It only applies to color and float ramps for now.) + bool bParamNeedUpdate = false; + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* RampParameter = Cast(MainParam); + if (RampParameter) + bParamNeedUpdate = RampParameter->bCaching; + } + else if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat* RampParameter = Cast(MainParam); + if (RampParameter) + bParamNeedUpdate = RampParameter->bCaching; + } + + if (bParamNeedUpdate) + ParameterLabelStr += "*"; + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + + auto IsAutoUpdateChecked = [MainParam]() + { + if (!MainParam || MainParam->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainParam->IsAutoUpdate() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + auto OnAutoUpdateCheckBoxStateChanged = [MainParam, InParams, bCookingEnabled](ECheckBoxState NewState) + { + if (NewState == ECheckBoxState::Checked) + { + for (auto & NextSelectedParam : InParams) + { + if (!NextSelectedParam) + continue; + + if (NextSelectedParam->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not allow mode change when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) + continue; + + switch (MainParam->GetParameterType()) + { + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* ColorRampParameter = Cast(NextSelectedParam); + + if (!ColorRampParameter) + continue; + + // Do not sync the selected color ramp parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) + continue; + + // Sync the Cached curve points at update mode switch. + FHoudiniParameterDetails::SyncCachedColorRampPoints(ColorRampParameter); + } + break; + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* FloatRampParameter = Cast(NextSelectedParam); + + if (!FloatRampParameter) + continue; + + // Do not sync the selected float ramp parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) + continue; + + // Sync the Cached curve points at update mode switch. + //FHoudiniParameterDetails::SyncCachedFloatRampPoints(FloatRampParameter); + FloatRampParameter->SyncCachedPoints(); + } + break; + + default: + break; + } + + NextSelectedParam->SetAutoUpdate(true); + } + } + else + { + for (auto & NextSelectedParam : InParams) + { + if (!NextSelectedParam) + continue; + + if (!(NextSelectedParam->IsAutoUpdate() && bCookingEnabled)) + continue; + + // Do not allow mode change when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) + continue; + + NextSelectedParam->SetAutoUpdate(false); + } + } + }; + + // Auto update check box + TSharedPtr CheckBox; + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + [ + SAssignNew(CheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([OnAutoUpdateCheckBoxStateChanged](ECheckBoxState NewState) + { + OnAutoUpdateCheckBoxStateChanged(NewState); + }) + .IsChecked_Lambda([IsAutoUpdateChecked]() + { + return IsAutoUpdateChecked(); + }) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AutoUpdate", "Auto-update")) + .ToolTipText(LOCTEXT("AutoUpdateTip", "When enabled, this parameter will automatically update its value while editing. Turning this off will allow you to more easily update it, and the update can be pushed by checking the toggle again.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + if ((MainParam->GetParameterType() != EHoudiniParameterType::FloatRamp) && (MainParam->GetParameterType() != EHoudiniParameterType::ColorRamp)) + CheckBox->SetVisibility(EVisibility::Hidden); + + Row->NameWidget.Widget = HorizontalBox; +} + +FDetailWidgetRow* +FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount) +{ + if (InParams.Num() <= 0) + return nullptr; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return nullptr; + + // Created row for the current parameter (if there is not a row created, do not show the parameter). + FDetailWidgetRow* Row = nullptr; + + // Current parameter is in a multiparm instance (directly) + if (MainParam->IsDirectChildOfMultiParm()) + { + int32 ParentMultiParmId = MainParam->GetParentParmId(); + + // If this is a folder param, its folder list parent parm is the multiparm + if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) + { + if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) // This should not happen + return nullptr; + + UHoudiniParameterFolderList* ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); + if (!ParentFolderList || ParentFolderList->IsPendingKill()) + return nullptr; // This should not happen + + ParentMultiParmId = ParentFolderList->GetParentParmId(); + } + + if (!AllMultiParms.Contains(ParentMultiParmId)) // This should not happen normally + return nullptr; + + // Get the parent multiparm + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentMultiParmId]; + + // The parent multiparm is visible. + if (ParentMultiParm && ParentMultiParm->IsShown() && MainParam->ShouldDisplay()) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + + } + // This item is not a direct child of a multiparm. + else + { + bool bIsFolder = MainParam->GetParameterType() == EHoudiniParameterType::Folder; + + // If this parameter is a folder, its parent folder should be the second top of the stack + int32 NestedMinStackDepth = bIsFolder ? 1 : 0; + + // Current parameter is inside a folder. + if (FolderStack.Num() > NestedMinStackDepth) + { + // If the current parameter is a folder, we take the top second queue on the stack, since the top one represents itself. + // Otherwise take the top queue on the stack. + TArray & CurrentLayerFolderQueue = bIsFolder ? + FolderStack[FolderStack.Num() - 2] : FolderStack.Last(); + + if (CurrentLayerFolderQueue.Num() <= 0) // Error state + return nullptr; + + bool bParentFolderVisible = CurrentLayerFolderQueue[0]->IsContentShown(); + + bool bIsSelectedTabVisible = false; + + // If its parent folder is visible, display current parameter, + // Otherwise, just prune the stacks. + if (bParentFolderVisible) + { + int32 ParentFolderId = MainParam->GetParentParmId(); + + // If the current parameter is a folder, its parent is a folderlist. + // So we need to continue to get the parent of the folderlist. + if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) + { + if (AllFoldersAndFolderLists.Contains(ParentFolderId)) + ParentFolderId = AllFoldersAndFolderLists[ParentFolderId]->GetParentParmId(); + else + return nullptr; // error state + } + + UHoudiniParameterFolder* ParentFolder = nullptr; + + if (AllFoldersAndFolderLists.Contains(ParentFolderId)) + ParentFolder = Cast(AllFoldersAndFolderLists[ParentFolderId]); + + bool bShouldDisplayRow = MainParam->ShouldDisplay(); + + // This row should be shown if its parent folder is shown. + if (ParentFolder) + bShouldDisplayRow &= (ParentFolder->IsTab() && ParentFolder->IsChosen()) || (!ParentFolder->IsTab() && ParentFolder->IsExpanded()); + + if (bShouldDisplayRow) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + } + + // prune the stack finally + if (bDecreaseChildCount) + { + CurrentLayerFolderQueue[0]->GetChildCounter() -= 1; + PruneStack(); + } + } + // If this parameter is in the root dir, just create a row. + else + { + if (MainParam->ShouldDisplay()) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + } + } + + if (!MainParam->IsVisible()) + return nullptr; + + + if (Row) + CurrentTabEndingRow = nullptr; + + return Row; +} + +void +FHoudiniParameterDetails::HandleUnsupportedParmType(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + CreateNestedRow(HouParameterCategory, (TArray)InParams); +} + +void +FHoudiniParameterDetails::CreateWidgetFloat( + IDetailCategoryBuilder & HouParameterCategory, + TArray& InParams ) +{ + TArray FloatParams; + if (!CastParameters(InParams, FloatParams)) + return; + + if (FloatParams.Num() <= 0) + return; + + UHoudiniParameterFloat* MainParam = FloatParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + // Helper function to find a unit from a string (name or abbreviation) + auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); + EUnit Unit = EUnit::Unspecified; + if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) + Unit = ParmUnit.GetValue(); + + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + // Lambdas for slider begin + auto SliderBegin = [&](TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), + FloatParams[0]->GetOuter()); + + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + FloatParams[Idx]->Modify(); + } + }; + + // Lambdas for slider end + auto SliderEnd = [&](TArray FloatParams) + { + // Mark the value as changed to trigger an update + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + FloatParams[Idx]->MarkChanged(true); + } + }; + + // Lambdas for changing the parameter value + auto ChangeFloatValueAt = [&](const float& Value, const int32& Index, const bool& DoChange, TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), + FloatParams[0]->GetOuter() ); + + bool bChanged = false; + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + FloatParams[Idx]->Modify(); + if (FloatParams[Idx]->SetValueAt(Value, Index)) + { + // Only mark the param has changed if DoChange is true!!! + if(DoChange) + FloatParams[Idx]->MarkChanged(true); + bChanged = true; + } + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if no parameter's value has actually been changed + Transaction.Cancel(); + } + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Revert to default value"), + FloatParams[0]->GetOuter()); + + if (TupleIndex < 0) + { + for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + if (FloatParams[Idx]->IsDefault()) + continue; + + FloatParams[Idx]->RevertToDefault(-1); + } + } + else + { + for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + if (FloatParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + FloatParams[Idx]->RevertToDefault(TupleIndex); + } + } + return FReply::Handled(); + }; + + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + //TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + if (MainParam->GetTupleSize() == 3) + { + // Should we swap Y and Z fields (only relevant for Vector3) + // Ignore the swapping if that parameter has the noswap tag + bool SwapVector3 = !MainParam->GetNoSwap(); + + auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float & Val) + { + ChangeFloatValueAt(Val, 0, true, FloatParams); + ChangeFloatValueAt(Val, 1, true, FloatParams); + ChangeFloatValueAt(Val, 2, true, FloatParams); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, 0))) + .Y(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 2 : 1))) + .Z(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 1 : 2))) + .OnXCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val); + else + ChangeFloatValueAt( Val, 0, true, FloatParams); + }) + .OnYCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val); + else + ChangeFloatValueAt( Val, SwapVector3 ? 2 : 1, true, FloatParams); + }) + .OnZCommitted_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val); + else + ChangeFloatValueAt( Val, SwapVector3 ? 1 : 2, true, FloatParams); + }) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("FloatParameterLockButtonToolTip", "When locked, change the vector value uniformly.")) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(MainParam->IsUniformLocked() ? FEditorStyle::GetBrush("Genericlock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([FloatParams, MainParam]() + { + if (!MainParam || MainParam->IsPendingKill()) + return FReply::Handled(); + + for (auto & CurParam : FloatParams) + { + if (!CurParam || CurParam->IsPendingKill()) + continue; + + CurParam->SwitchUniformLock(); + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + + return FReply::Handled(); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([FloatParams]() + { + for (auto & SelectedParam : FloatParams) + { + if (!SelectedParam) + continue; + + if (!SelectedParam->IsDefault()) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([FloatParams, RevertToDefault]() { return RevertToDefault(-1, FloatParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + else + { + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + TSharedPtr> NumericEntryBox; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< float >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + + .MinValue(MainParam->GetMin()) + .MaxValue(MainParam->GetMax()) + + .MinSliderValue(MainParam->GetUIMin()) + .MaxSliderValue(MainParam->GetUIMax()) + + .Value(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, Idx))) + .OnValueChanged_Lambda([=](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) + .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) + .OnBeginSliderMovement_Lambda([=]() { SliderBegin(FloatParams); }) + .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(FloatParams); }) + .SliderExponent(MainParam->IsLogarithmic() ?8.0f : 1.0f) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .OnClicked_Lambda([Idx, FloatParams, RevertToDefault]() { return RevertToDefault(Idx, FloatParams); }) + .Visibility_Lambda([Idx, FloatParams]() + { + for (auto & SelectedParam :FloatParams) + { + if (!SelectedParam) + continue; + + if (!SelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + } + } + + Row->ValueWidget.Widget =VerticalBox; + + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetInt(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray IntParams; + if (!CastParameters(InParams, IntParams)) + + if (IntParams.Num() <= 0) + return; + + UHoudiniParameterInt* MainParam = IntParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + // Helper function to find a unit from a string (name or abbreviation) + auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); + EUnit Unit = EUnit::Unspecified; + if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) + Unit = ParmUnit.GetValue(); + + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + // Lambda for slider begin + auto SliderBegin = [&](TArray IntParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), + IntParams[0]->GetOuter()); + + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + IntParams[Idx]->Modify(); + } + }; + + // Lambda for slider end + auto SliderEnd = [&](TArray IntParams) + { + // Mark the value as changed to trigger an update + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + IntParams[Idx]->MarkChanged(true); + } + }; + + // Lambda for changing the parameter value + auto ChangeIntValueAt = [&](const int32& Value, const int32& Index, const bool& DoChange, TArray IntParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), + IntParams[0]->GetOuter()); + + bool bChanged = false; + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + if (!IntParams[Idx]) + continue; + + IntParams[Idx]->Modify(); + if (IntParams[Idx]->SetValueAt(Value, Index)) + { + // Only mark the param has changed if DoChange is true!!! + if (DoChange) + IntParams[Idx]->MarkChanged(true); + bChanged = true; + } + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if there is no param has actually been changed + Transaction.Cancel(); + } + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray IntParams) + { + for (int32 Idx = 0; Idx < IntParams.Num(); Idx++) + { + if (!IntParams[Idx]) + continue; + + if (IntParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + IntParams[Idx]->RevertToDefault(TupleIndex); + } + + return FReply::Handled(); + }; + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + + .MinValue(MainParam->GetMin()) + .MaxValue(MainParam->GetMax()) + + .MinSliderValue(MainParam->GetUIMin()) + .MaxSliderValue(MainParam->GetUIMax()) + + .Value( TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterInt::GetValue, Idx))) + .OnValueChanged_Lambda( [=](int32 Val) { ChangeIntValueAt(Val, Idx, false, IntParams); } ) + .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeIntValueAt(Val, Idx, true, IntParams); }) + .OnBeginSliderMovement_Lambda( [=]() { SliderBegin(IntParams); }) + .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(IntParams); }) + .SliderExponent(MainParam->IsLogarithmic() ? 8.0f : 1.0f) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, IntParams]() + { + for (auto & NextSelectedParam : IntParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, IntParams, RevertToDefault]() { return RevertToDefault(Idx, IntParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + /* + if (NumericEntryBox.IsValid()) + NumericEntryBox->SetEnabled(!MainParam->IsDisabled()); + */ + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray StringParams; + if (!CastParameters(InParams, StringParams)) + return; + + if (StringParams.Num() <= 0) + return; + + UHoudiniParameterString* MainParam = StringParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + bool bIsMultiLine = false; + bool bIsUnrealRef = false; + UClass* UnrealRefClass = UObject::StaticClass(); + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + TMap& Tags = MainParam->GetTags(); + if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_TAG) && FCString::Atoi(*Tags[HOUDINI_PARAMETER_STRING_REF_TAG]) == 1) + { + bIsUnrealRef = true; + + if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_CLASS_TAG)) + { + UClass * FoundClass = FindObject(ANY_PACKAGE, *Tags[HOUDINI_PARAMETER_STRING_REF_CLASS_TAG]); + if (FoundClass != nullptr) + { + UnrealRefClass = FoundClass; + } + } + } + + if (Tags.Contains(HOUDINI_PARAMETER_STRING_MULTILINE_TAG)) + { + bIsMultiLine = true; + } + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + // Lambda for changing the parameter value + auto ChangeStringValueAt = [&](const FString& Value, UObject* ChosenObj, const int32& Index, const bool& DoChange, TArray StringParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterSrtingChange", "Houdini Parameter String: Changing a value"), + StringParams[0]->GetOuter()); + + bool bChanged = false; + for (int Idx = 0; Idx < StringParams.Num(); Idx++) + { + if (!StringParams[Idx]) + continue; + + StringParams[Idx]->Modify(); + if (StringParams[Idx]->SetValueAt(Value, Index)) + { + StringParams[Idx]->MarkChanged(true); + bChanged = true; + } + + StringParams[Idx]->SetAssetAt(ChosenObj, Index); + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if there is no param actually has been changed + Transaction.Cancel(); + } + + FHoudiniEngineUtils::UpdateEditorProperties(StringParams[0], false); + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray StringParams) + { + for (int32 Idx = 0; Idx < StringParams.Num(); Idx++) + { + if (!StringParams[Idx]) + continue; + + if (StringParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + StringParams[Idx]->RevertToDefault(TupleIndex); + } + + return FReply::Handled(); + }; + + if (bIsUnrealRef) + { + TSharedPtr EditableTextBox; + TSharedPtr HorizontalBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([UnrealRefClass](const UObject* InObject) + { + return InObject->IsA(UnrealRefClass); + }) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + ChangeStringValueAt(ReferenceStr, InObject, Idx, true, StringParams); + }) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + // Thumbnail + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouParameterCategory.GetParentLayout().GetThumbnailPool(); + + // Create a thumbnail for the selected object / class + UObject* EditObject = nullptr; + const FString AssetPath = MainParam->GetValueAt(Idx); + EditObject = StaticFindObject(nullptr, nullptr, *AssetPath, true); + + FAssetData AssetData; + if (IsValid(EditObject)) + { + AssetData = FAssetData(EditObject); + } + else + { + AssetData.AssetClass = UnrealRefClass->GetFName(); + } + + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = MakeShareable( + new FAssetThumbnail(AssetData, 64, 64, AssetThumbnailPool)); + + TSharedPtr ThumbnailBorder; + HorizontalBox->AddSlot().Padding(0.f, 0.f, 2.f, 0.f).AutoWidth() + [ + SAssignNew(ThumbnailBorder, SBorder) + .OnMouseDoubleClick_Lambda([EditObject, Idx](const FGeometry&, const FPointerEvent&) + { + if (EditObject && GEditor) + GEditor->EditObject(EditObject); + + return FReply::Handled(); + }) + .Padding(5.f) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + ThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush * >::Create( + TAttribute< const FSlateBrush * >::FGetter::CreateLambda([ThumbnailBorder]() + { + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ))); + + FText MeshNameText = FText::GetEmpty(); + //if (InputObject) + // MeshNameText = FText::FromString(InputObject->GetName()); + + TSharedPtr StaticMeshComboButton; + + TSharedPtr ButtonBox; + HorizontalBox->AddSlot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + [ + SAssignNew(ButtonBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SAssignNew(StaticMeshComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromName(AssetData.AssetName)) + .ToolTipText(FText::FromString(MainParam->GetValueAt(Idx))) + ] + ] + ] + ]; + + StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( + [UnrealRefClass, StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() + { + TArray AllowedClasses; + if (UnrealRefClass != UObject::StaticClass()) + { + // Use the class specified by the user + AllowedClasses.Add(UnrealRefClass); + } + else + { + // Using UObject would list way too many assets, and take a long time to open the menu, + // so we need to reestrict the classes a bit + AllowedClasses.Add(UStaticMesh::StaticClass()); + AllowedClasses.Add(UHoudiniAsset::StaticClass()); + AllowedClasses.Add(USkeletalMesh::StaticClass()); + AllowedClasses.Add(UBlueprint::StaticClass()); + AllowedClasses.Add(UMaterialInterface::StaticClass()); + AllowedClasses.Add(UTexture::StaticClass()); + AllowedClasses.Add(ULevel::StaticClass()); + AllowedClasses.Add(UStreamableRenderAsset::StaticClass()); + AllowedClasses.Add(USoundBase::StaticClass()); + AllowedClasses.Add(UParticleSystem::StaticClass()); + AllowedClasses.Add(UFoliageType::StaticClass()); + } + + TArray NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(nullptr), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda([StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) + { + if (StaticMeshComboButton.IsValid()) + { + StaticMeshComboButton->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + // Get the asset reference string for this object + // !! Accept null objects to allow clearing the asset picker !! + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); + + ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); + } + }), + FSimpleDelegate::CreateLambda([]() {})); + }) + ); + } + else if (bIsMultiLine) + { + TSharedPtr< SMultiLineEditableTextBox > MultiLineEditableTextBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) + {return true;}) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + FString NewString = ReferenceStr; + if (StringParams[0]->GetValueAt(Idx).Len() > 0) + NewString = StringParams[0]->GetValueAt(Idx) + "\n" + NewString; + + ChangeStringValueAt(NewString, nullptr, Idx, true, StringParams); + }) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Top).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(MultiLineEditableTextBox, SMultiLineEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainParam->GetValueAt(Idx))) + .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, StringParams]() + { + for (auto & NextSelectedParam : StringParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() { return RevertToDefault(Idx, StringParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + else + { + TSharedPtr< SEditableTextBox > EditableTextBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) + {return true;}) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + ChangeStringValueAt(ReferenceStr, nullptr, Idx, true, StringParams); + }) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(EditableTextBox, SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainParam->GetValueAt(Idx))) + .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) + { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, StringParams]() + { + for (auto & NextSelectedParam : StringParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() + { return RevertToDefault(Idx, StringParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ColorParams; + if (!CastParameters(InParams, ColorParams)) + return; + + if (ColorParams.Num() <= 0) + return; + + UHoudiniParameterColor* MainParam = ColorParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + bool bHasAlpha = (MainParam->GetTupleSize() == 4); + + // Add color picker UI. + TSharedPtr ColorBlock; + TSharedRef VerticalBox = SNew(SVerticalBox); + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(ColorBlock, SColorBlock) + .Color(MainParam->GetColorValue()) + .ShowBackgroundForAlpha(bHasAlpha) + .OnMouseButtonDown(FPointerEventHandler::CreateLambda( + [MainParam, ColorParams, ColorBlock, bHasAlpha](const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) + { + if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) + return FReply::Unhandled(); + + FColorPickerArgs PickerArgs; + PickerArgs.ParentWidget = ColorBlock; + PickerArgs.bUseAlpha = bHasAlpha; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) { + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), + MainParam->GetOuter(), true); + + bool bChanged = false; + for (auto & Param : ColorParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetColorValue(InColor)) + { + Param->MarkChanged(true); + bChanged = true; + } + } + + // cancel the transaction if there is actually no value changed + if (!bChanged) + { + Transaction.Cancel(); + } + + }); + PickerArgs.InitialColorOverride = MainParam->GetColorValue(); + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker(PickerArgs); + return FReply::Handled(); + })) + ]; + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetButton(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ButtonParams; + if (!CastParameters(InParams, ButtonParams)) + return; + + if (ButtonParams.Num() <= 0) + return; + + UHoudiniParameterButton* MainParam = ButtonParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + FText ParameterTooltip = GetParameterTooltip(MainParam); + + TSharedRef HorizontalBox = SNew(SHorizontalBox); + TSharedPtr Button; + + // Add button UI. + HorizontalBox->AddSlot().Padding(1, 2, 4, 2) + [ + SAssignNew(Button, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ParameterLabelText) + .ToolTipText(ParameterTooltip) + .OnClicked(FOnClicked::CreateLambda( [MainParam, ButtonParams]() + { + for (auto & Param : ButtonParams) + { + if (!Param) + continue; + + // There is no undo redo operation for button + Param->MarkChanged(true); + } + + return FReply::Handled(); + })) + ]; + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ButtonStripParams; + if (!CastParameters(InParams, ButtonStripParams)) + return; + + if (ButtonStripParams.Num() <= 0) + return; + + UHoudiniParameterButtonStrip* MainParam = ButtonStripParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + if (!Row) + return; + + auto OnButtonStateChanged = [MainParam, ButtonStripParams](ECheckBoxState NewState, int32 Idx) + { + + /* + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterButtonStripChange", "Houdini Parameter Button Strip: Changing value"), + MainParam->GetOuter(), true); + */ + int32 StateInt = NewState == ECheckBoxState::Checked ? 1 : 0; + bool bChanged = false; + + for (auto & NextParam : ButtonStripParams) + { + if (!NextParam || NextParam->IsPendingKill()) + continue; + + if (!NextParam->Values.IsValidIndex(Idx)) + continue; + + //NextParam->Modify(); + if (NextParam->SetValueAt(Idx, StateInt)) + { + NextParam->MarkChanged(true); + bChanged = true; + } + } + + //if (!bChanged) + // Transaction.Cancel(); + + }; + + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + FText ParameterTooltip = GetParameterTooltip(MainParam); + + TSharedRef HorizontalBox = SNew(SHorizontalBox); + FLinearColor BgColor(0.53f, 0.81f, 0.82f, 1.0f); // Sky Blue Backgroud color + + for (int32 Idx = 0; Idx < MainParam->Count; ++Idx) + { + if (!MainParam->Values.IsValidIndex(Idx) || !MainParam->Labels.IsValidIndex(Idx)) + continue; + + bool bPressed = MainParam->Values[Idx] > 0; + FText LabelText = FText::FromString(MainParam->Labels[Idx]); + + TSharedPtr Button; + + HorizontalBox->AddSlot().Padding(0).FillWidth(1.0f) + [ + SAssignNew(Button, SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") + .IsChecked(bPressed ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged_Lambda([OnButtonStateChanged, Idx](ECheckBoxState NewState) + { + OnButtonStateChanged(NewState, Idx); + }) + .Content() + [ + SNew(STextBlock) + .Text(LabelText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + Button->SetColorAndOpacity(BgColor); + } + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetLabel(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray LabelParams; + if (!CastParameters(InParams, LabelParams)) + return; + + if (LabelParams.Num() <= 0) + return; + + UHoudiniParameterLabel* MainParam = LabelParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + + for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) + { + FString NextLabelString = MainParam->GetStringAtIndex(Index); + FText ParameterLabelText = FText::FromString(NextLabelString); + + TSharedPtr TextBlock; + + // Add Label UI. + VerticalBox->AddSlot().Padding(1, 2, 4, 2) + [ + SAssignNew(TextBlock, STextBlock).Text(ParameterLabelText) + ]; + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetToggle(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray ToggleParams; + if (!CastParameters(InParams, ToggleParams)) + return; + + if (ToggleParams.Num() <= 0) + return; + + UHoudiniParameterToggle* MainParam = ToggleParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + + TSharedRef VerticalBox = SNew(SVerticalBox); + auto IsToggleCheckedLambda = [MainParam](int32 Index) + { + if (Index >= MainParam->GetNumValues()) + return ECheckBoxState::Unchecked; + + if (MainParam->GetValueAt(Index)) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; + }; + + auto OnToggleCheckStateChanged = [MainParam, ToggleParams](ECheckBoxState NewState, int32 Index) + { + if (Index >= MainParam->GetNumValues()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterToggleChange", "Houdini Parameter Toggle: Changing value"), + MainParam->GetOuter(), true); + + bool bState = (NewState == ECheckBoxState::Checked); + + bool bChanged = false; + for (auto & Param : ToggleParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetValueAt(bState, Index)) + { + bChanged = true; + Param->MarkChanged(true); + } + } + + // Cancel the transaction if no parameter has actually been changed + if (!bChanged) + { + Transaction.Cancel(); + } + }; + + for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) + { + TSharedPtr< SCheckBox > CheckBox; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + [ + SAssignNew(CheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([OnToggleCheckStateChanged, Index](ECheckBoxState NewState) { + OnToggleCheckStateChanged(NewState, Index); + + }) + .IsChecked_Lambda([IsToggleCheckedLambda, Index]() { + return IsToggleCheckedLambda(Index); + }) + .Content() + [ + SNew(STextBlock) + .Text(ParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FileParams; + if (!CastParameters(InParams, FileParams)) + return; + + if (FileParams.Num() <= 0) + return; + + UHoudiniParameterFile* MainParam = FileParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + + FString FileTypeWidgetFilter = TEXT("All files (*.*)|*.*"); + if (!MainParam->GetFileFilters().IsEmpty()) + FileTypeWidgetFilter = FString::Printf(TEXT("%s files (%s)|%s"), *MainParam->GetFileFilters(), *MainParam->GetFileFilters(), *MainParam->GetFileFilters()); + + FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); + + auto UpdateCheckRelativePath = [MainParam](const FString & PickedPath) + { + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(MainParam->GetOuter()); + if (MainParam->GetOuter() && !PickedPath.IsEmpty() && FPaths::IsRelative(PickedPath)) + { + // Check if the path is relative to the UE4 project + FString AbsolutePath = FPaths::ConvertRelativePathToFull(PickedPath); + if (FPaths::FileExists(AbsolutePath)) + { + return AbsolutePath; + } + + // Check if the path is relative to the asset + if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + { + if (HoudiniAssetComponent->HoudiniAsset && !HoudiniAssetComponent->HoudiniAsset->IsPendingKill()) + { + FString AssetFilePath = FPaths::GetPath(HoudiniAssetComponent->HoudiniAsset->AssetFileName); + if (FPaths::FileExists(AssetFilePath)) + { + FString UpdatedFileWidgetPath = FPaths::Combine(*AssetFilePath, *PickedPath); + if (FPaths::FileExists(UpdatedFileWidgetPath)) + { + return UpdatedFileWidgetPath; + } + } + } + } + } + + return PickedPath; + }; + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + FString FileWidgetPath = MainParam->GetValueAt(Idx); + FString FileWidgetBrowsePath = BrowseWidgetDirectory; + + if (!FileWidgetPath.IsEmpty()) + { + FString FileWidgetDirPath = FPaths::GetPath(FileWidgetPath); + if (!FileWidgetDirPath.IsEmpty()) + FileWidgetBrowsePath = FileWidgetDirPath; + } + + bool IsDirectoryPicker = MainParam->GetParameterType() == EHoudiniParameterType::FileDir; + bool bIsNewFile = !MainParam->IsReadOnly(); + + FText BrowseTooltip = LOCTEXT("FileButtonToolTipText", "Choose a file from this computer"); + if (IsDirectoryPicker) + BrowseTooltip = LOCTEXT("DirButtonToolTipText", "Choose a directory from this computer"); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SNewFilePathPicker) + .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) + .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") + .BrowseButtonToolTip(BrowseTooltip) + .BrowseDirectory(FileWidgetBrowsePath) + .BrowseTitle(LOCTEXT("PropertyEditorTitle", "File picker...")) + .FilePath(FileWidgetPath) + .FileTypeFilter(FileTypeWidgetFilter) + .IsNewFile(bIsNewFile) + .IsDirectoryPicker(IsDirectoryPicker) + .ToolTipText_Lambda([MainParam]() + { + // return the current param value as a tooltip + FString FileValue = MainParam ? MainParam->GetValueAt(0) : FString(); + return FText::FromString(FileValue); + }) + .OnPathPicked(FOnPathPicked::CreateLambda([MainParam, FileParams, UpdateCheckRelativePath, Idx](const FString & PickedPath) + { + if (MainParam->GetNumValues() <= Idx) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFileChange", "Houdini Parameter File: Changing a file path"), + MainParam->GetOuter(), true); + + bool bChanged = false; + + for (auto & Param : FileParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetValueAt(UpdateCheckRelativePath(PickedPath), Idx)) + { + bChanged = true; + Param->MarkChanged(true); + } + } + + // Cancel the transaction if no value has actually been changed + if (!bChanged) + { + Transaction.Cancel(); + } + })) + ] + ]; + + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + + +void +FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ChoiceParams; + if (!CastParameters(InParams, ChoiceParams)) + return; + + if (ChoiceParams.Num() <= 0) + return; + + UHoudiniParameterChoice* MainParam = ChoiceParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + // Lambda for changing the parameter value + auto ChangeSelectionLambda = [ChoiceParams](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterChoiceChange", "Houdini Parameter Choice: Changing selection"), + ChoiceParams[0]->GetOuter()); + + const int32 NewIntValue = ChoiceParams[0]->GetIntValueFromLabel(*NewChoice); + + bool bChanged = false; + for (int Idx = 0; Idx < ChoiceParams.Num(); Idx++) + { + if (!ChoiceParams[Idx]) + continue; + + ChoiceParams[Idx]->Modify(); + if (ChoiceParams[Idx]->SetIntValue(NewIntValue)) + { + bChanged = true; + ChoiceParams[Idx]->MarkChanged(true); + ChoiceParams[Idx]->UpdateStringValueFromInt(); + } + } + + if (!bChanged) + { + // Cancel the transaction if no parameter was changed + Transaction.Cancel(); + } + }; + + // + MainParam->UpdateChoiceLabelsPtr(); + TArray>* OptionSource = MainParam->GetChoiceLabelsPtr(); + TSharedPtr IntialSelec; + if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValue())) + { + IntialSelec = (*OptionSource)[MainParam->GetIntValue()]; + } + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBox; + HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SAssignNew( ComboBox, SComboBox< TSharedPtr< FString > > ) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + []( TSharedPtr< FString > InItem ) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [ChangeSelectionLambda](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + ChangeSelectionLambda(NewChoice, SelectType); + }) + [ + SNew(STextBlock) + .Text_Lambda([MainParam]() { return FText::FromString(MainParam->GetLabel()); }) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + if ( ComboBox.IsValid() ) + ComboBox->SetEnabled( !MainParam->IsDisabled() ); + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetSeparator(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + TSharedRef HorizontalBox = SNew(SCustomizedBox); + + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + + Row->WholeRowWidget.Widget = HorizontalBox; +} + +void +FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray OperatorPathParams; + if (!CastParameters(InParams, OperatorPathParams)) + return; + + if (OperatorPathParams.Num() <= 0) + return; + + UHoudiniParameterOperatorPath* MainParam = OperatorPathParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + UHoudiniInput* MainInput = MainParam->HoudiniInput.Get(); + if (!MainInput) + return; + + // Build an array of edited inputs for multi edition + TArray EditedInputs; + EditedInputs.Add(MainInput); + + // Add the corresponding inputs found in the other HAC + for (int LinkedIdx = 1; LinkedIdx < OperatorPathParams.Num(); LinkedIdx++) + { + UHoudiniInput* LinkedInput = OperatorPathParams[LinkedIdx]->HoudiniInput.Get(); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if (!LinkedInput->Matches(*MainInput)) + continue; + + EditedInputs.Add(LinkedInput); + } + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FHoudiniInputDetails::CreateWidget(HouParameterCategory, EditedInputs, Row); + + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetFloatRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Parsing a float ramp: 1->(2->3->4)*->5 // + switch (MainParam->GetParameterType()) + { + //*****State 1: Float Ramp*****// + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); + if (FloatRampParameter) + { + CurrentRampFloat = FloatRampParameter; + CurrentRampFloatPointsArray.Empty(); + + CurrentRampParameterList = InParams; + + FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + CurrentRampRow = Row; + } + break; + } + + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* FloatParameter = Cast(MainParam); + if (FloatParameter) + { + bool bCreateNewPoint = true; + if (CurrentRampFloatPointsArray.Num() > 0) + { + UHoudiniParameterRampFloatPoint* LastPtInArr = CurrentRampFloatPointsArray.Last(); + if (LastPtInArr && !LastPtInArr->ValueParentParm) + bCreateNewPoint = false; + } + + //*****State 2: Float Parameter (position)*****// + if (bCreateNewPoint) + { + UHoudiniParameterRampFloatPoint* NewRampFloatPoint = nullptr; + + int32 PointIndex = CurrentRampFloatPointsArray.Num(); + if (CurrentRampFloat->Points.IsValidIndex(PointIndex)) + { + + // TODO: We should reuse existing point objects, if they exist. Currently + // this causes results in unexpected behaviour in other parts of this detail code. + // Give this code a bit of an overhaul at some point. + // NewRampFloatPoint = CurrentRampFloat->Points[PointIndex]; + } + + if (!NewRampFloatPoint) + { + // Create a new float ramp point, and add its pointer to the current float points buffer array. + NewRampFloatPoint = NewObject< UHoudiniParameterRampFloatPoint >(CurrentRampFloat, FName(), CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); + + } + + CurrentRampFloatPointsArray.Add(NewRampFloatPoint); + + if (FloatParameter->GetNumberOfValues() <= 0) + return; + // Set the float ramp point's position parent parm, and value + NewRampFloatPoint->PositionParentParm = FloatParameter; + NewRampFloatPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); + } + //*****State 3: Float Parameter (value)*****// + else + { + if (FloatParameter->GetNumberOfValues() <= 0) + return; + // Get the last point in the buffer array + if (CurrentRampFloatPointsArray.Num() > 0) + { + // Set the last inserted float ramp point's float parent parm, and value + UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); + LastAddedFloatRampPoint->ValueParentParm = FloatParameter; + LastAddedFloatRampPoint->SetValue(FloatParameter->GetValuesPtr()[0]); + } + } + } + + break; + } + //*****State 4: Choice parameter*****// + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); + if (ChoiceParameter && CurrentRampFloatPointsArray.Num() > 0) + { + // Set the last inserted float ramp point's interpolation parent parm, and value + UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); + + LastAddedFloatRampPoint->InterpolationParentParm = ChoiceParameter; + LastAddedFloatRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + + // Set the index of this point in the multi parm. + LastAddedFloatRampPoint->InstanceIndex = CurrentRampFloatPointsArray.Num() - 1; + } + + + //*****State 5: All ramp points have been parsed, finish!*****// + if (CurrentRampFloatPointsArray.Num() >= (int32)CurrentRampFloat->MultiParmInstanceCount) + { + CurrentRampFloatPointsArray.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { + return P1.Position < P2.Position; + }); + + CurrentRampFloat->Points = CurrentRampFloatPointsArray; + + // Not caching, points are synced, update cached points + if (!CurrentRampFloat->bCaching) + { + const int32 NumPoints = CurrentRampFloat->Points.Num(); + CurrentRampFloat->CachedPoints.SetNum(NumPoints); + for (int32 i = 0; i < NumPoints; ++i) + { + UHoudiniParameterRampFloatPoint* FromPoint = CurrentRampFloat->Points[i]; + UHoudiniParameterRampFloatPoint* ToPoint = CurrentRampFloat->CachedPoints[i]; + ToPoint = nullptr; + check(FromPoint) + if (!ToPoint) + { + ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampFloat, RF_NoFlags, CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); + } + else + { + ToPoint->CopyStateFrom(FromPoint, true); + } + CurrentRampFloat->CachedPoints[i] = ToPoint; + } + } + + CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampFloat, CurrentRampParameterList); + + CurrentRampFloat->SetDefaultValues(); + + CurrentRampFloat = nullptr; + CurrentRampRow = nullptr; + } + + break; + } + + default: + break; + } + +} + +void +FHoudiniParameterDetails::CreateWidgetColorRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Parsing a color ramp: 1->(2->3->4)*->5 // + switch (MainParam->GetParameterType()) + { + //*****State 1: Color Ramp*****// + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* RampColor = Cast(MainParam); + if (RampColor) + { + CurrentRampColor = RampColor; + CurrentRampColorPointsArray.Empty(); + + CurrentRampParameterList = InParams; + + FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + CurrentRampRow = Row; + } + + break; + } + //*****State 2: Float parameter*****// + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* FloatParameter = Cast(MainParam); + if (FloatParameter) + { + // Create a new color ramp point, and add its pointer to the current color points buffer array. + UHoudiniParameterRampColorPoint* NewRampColorPoint = nullptr; + int32 PointIndex = CurrentRampColorPointsArray.Num(); + + if (CurrentRampColor->Points.IsValidIndex(PointIndex)) + { + NewRampColorPoint = CurrentRampColor->Points[PointIndex]; + } + + if (!NewRampColorPoint) + { + NewRampColorPoint = NewObject< UHoudiniParameterRampColorPoint >(CurrentRampColor, FName(), CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); + } + + CurrentRampColorPointsArray.Add(NewRampColorPoint); + + if (FloatParameter->GetNumberOfValues() <= 0) + return; + // Set the color ramp point's position parent parm, and value + NewRampColorPoint->PositionParentParm = FloatParameter; + NewRampColorPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); + } + + break; + } + //*****State 3: Color parameter*****// + case EHoudiniParameterType::Color: + { + UHoudiniParameterColor* ColorParameter = Cast(MainParam); + if (ColorParameter && CurrentRampColorPointsArray.Num() > 0) + { + // Set the last inserted color ramp point's color parent parm, and value + UHoudiniParameterRampColorPoint* LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); + LastAddedColorRampPoint->ValueParentParm = ColorParameter; + LastAddedColorRampPoint->SetValue(ColorParameter->GetColorValue()); + } + + break; + } + //*****State 4: Choice Parameter*****// + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); + if (ChoiceParameter) + { + // Set the last inserted color ramp point's interpolation parent parm, and value + UHoudiniParameterRampColorPoint*& LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); + + LastAddedColorRampPoint->InterpolationParentParm = ChoiceParameter; + LastAddedColorRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + + // Set the index of this point in the multi parm. + LastAddedColorRampPoint->InstanceIndex = CurrentRampColorPointsArray.Num() - 1; + } + + + //*****State 5: All ramp points have been parsed, finish!*****// + if (CurrentRampColorPointsArray.Num() >= (int32)CurrentRampColor->MultiParmInstanceCount) + { + CurrentRampColorPointsArray.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) + { + return P1.Position < P2.Position; + }); + + CurrentRampColor->Points = CurrentRampColorPointsArray; + + // Not caching, points are synced, update cached points + + if (!CurrentRampColor->bCaching) + { + const int32 NumPoints = CurrentRampColor->Points.Num(); + CurrentRampColor->CachedPoints.SetNum(NumPoints); + + for (int32 i = 0; i < NumPoints; ++i) + { + UHoudiniParameterRampColorPoint* FromPoint = CurrentRampColor->Points[i]; + UHoudiniParameterRampColorPoint* ToPoint = CurrentRampColor->CachedPoints[i]; + + if (!ToPoint) + { + ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampColor, RF_NoFlags, CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); + } + else + { + ToPoint->CopyStateFrom(FromPoint, true); + } + CurrentRampColor->CachedPoints[i] = ToPoint; + } + } + + + CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampColor, CurrentRampParameterList); + + CurrentRampColor->SetDefaultValues(); + + CurrentRampColor = nullptr; + CurrentRampRow = nullptr; + } + + break; + } + + default: + break; + } + +} + + +FDetailWidgetRow* +FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() <= 0) + return nullptr; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam) + return nullptr; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, (TArray)InParams); + if (!Row) + return nullptr; + + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + + // Create the standard parameter name widget with an added autoupdate checkbox. + CreateNameWidgetWithAutoUpdate(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor *RampColorParam = Cast(MainParam); + if (!RampColorParam) + return nullptr; + + TSharedPtr ColorGradientEditor; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SBorder) + .VAlign(VAlign_Fill) + [ + SAssignNew(ColorGradientEditor, SHoudiniColorRampCurveEditor) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + ] + ]; + + if (!ColorGradientEditor.IsValid()) + return nullptr; + + // Avoid showing tooltips inside of the curve editor + ColorGradientEditor->EnableToolTipForceField(true); + + CurrentRampParameterColorCurve = NewObject( + MainParam, UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + + if (!CurrentRampParameterColorCurve) + return nullptr; + + CreatedColorRampCurves.Add(CurrentRampParameterColorCurve); + + // Add the ramp curve to root to avoid garabage collected. + CurrentRampParameterColorCurve->AddToRoot(); + + TArray ColorRampParameters; + CastParameters(InParams, ColorRampParameters); + + for (auto NextColorRamp : ColorRampParameters) + { + CurrentRampParameterColorCurve->ColorRampParameters.Add(NextColorRamp); + } + ColorGradientEditor->HoudiniColorRampCurve = CurrentRampParameterColorCurve; + + // Clear default curve points + for (int Idx = 0; Idx < 4; ++Idx) + { + FRichCurve& RichCurve = (CurrentRampParameterColorCurve->FloatCurves)[Idx]; + if (RichCurve.GetNumKeys() > 0) + RichCurve.Keys.Empty(); + } + ColorGradientEditor->SetCurveOwner(CurrentRampParameterColorCurve); + } + else if(MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat *RampFloatParam = Cast(MainParam); + if (!RampFloatParam) + return nullptr; + + TSharedPtr FloatCurveEditor; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SBorder) + .VAlign(VAlign_Fill) + [ + SAssignNew(FloatCurveEditor, SHoudiniFloatRampCurveEditor) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + .HideUI(true) + .DrawCurve(true) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + .ViewMinOutput(0.0f) + .ViewMaxOutput(1.0f) + .TimelineLength(1.0f) + .AllowZoomOutput(false) + .ShowInputGridNumbers(false) + .ShowOutputGridNumbers(false) + .ShowZoomButtons(false) + .ZoomToFitHorizontal(false) + .ZoomToFitVertical(false) + .XAxisName(FString("X")) + .YAxisName(FString("Y")) + .ShowCurveSelector(false) + + ] + ]; + + if (!FloatCurveEditor.IsValid()) + return nullptr; + + // Avoid showing tooltips inside of the curve editor + FloatCurveEditor->EnableToolTipForceField(true); + + CurrentRampParameterFloatCurve = NewObject( + MainParam, UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + + if (!CurrentRampParameterFloatCurve) + return nullptr; + + CreatedFloatRampCurves.Add(CurrentRampParameterFloatCurve); + + // Add the ramp curve to root to avoid garbage collected + CurrentRampParameterFloatCurve->AddToRoot(); + + TArray FloatRampParameters; + CastParameters(InParams, FloatRampParameters); + for (auto NextFloatRamp : FloatRampParameters) + { + CurrentRampParameterFloatCurve->FloatRampParameters.Add(NextFloatRamp); + } + FloatCurveEditor->HoudiniFloatRampCurve = CurrentRampParameterFloatCurve; + + FloatCurveEditor->SetCurveOwner(CurrentRampParameterFloatCurve, true); + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + return Row; +} + + +void +FHoudiniParameterDetails::CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray& InParams) +{ + if (!Row || !InParameter) + return; + + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + UHoudiniParameterRampFloat * MainFloatRampParameter = nullptr; + UHoudiniParameterRampColor * MainColorRampParameter = nullptr; + + TArray FloatRampParameterList; + TArray ColorRampParameterList; + if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + MainFloatRampParameter = Cast(MainParam); + + if (!MainFloatRampParameter) + return; + + if (!CastParameters(InParams, FloatRampParameterList)) + return; + } + else if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + MainColorRampParameter = Cast(MainParam); + + if (!MainColorRampParameter) + return; + + if (!CastParameters(InParams, ColorRampParameterList)) + return; + } + else + { + return; + } + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Lambda for computing the float point to be inserted + auto GetInsertFloatPointLambda = [MainFloatRampParameter]( + const int32& InsertAtIndex, + float& OutPosition, + float& OutValue, + EHoudiniRampInterpolationType& OutInterpType) mutable + { + if (!MainFloatRampParameter) + return; + + float PrevPosition = 0.0f; + float NextPosition = 1.0f; + + TArray &CurrentPoints = MainFloatRampParameter->Points; + TArray &CachedPoints = MainFloatRampParameter->CachedPoints; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + int32 NumPoints = 0; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + { + NumPoints = CurrentPoints.Num(); + } + else + { + MainFloatRampParameter->SetCaching(true); + NumPoints = CachedPoints.Num(); + } + + if (InsertAtIndex >= NumPoints) + { + // Insert at the end + if (NumPoints > 0) + { + UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + PrevPoint = CurrentPoints.Last(); + else + PrevPoint = CachedPoints.Last(); + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + } + } + else if (InsertAtIndex <= 0) + { + // Insert at the beginning + if (NumPoints > 0) + { + UHoudiniParameterRampFloatPoint* NextPoint = nullptr; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + NextPoint = CurrentPoints[0]; + else + NextPoint = CachedPoints[0]; + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + OutInterpType = NextPoint->GetInterpolation(); + } + } + } + else + { + // Insert in the middle + if (NumPoints > 1) + { + UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; + UHoudiniParameterRampFloatPoint* NextPoint = nullptr; + + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + { + PrevPoint = CurrentPoints[InsertAtIndex - 1]; + NextPoint = CurrentPoints[InsertAtIndex]; + } + else + { + PrevPoint = CachedPoints[InsertAtIndex - 1]; + NextPoint = CachedPoints[InsertAtIndex]; + } + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + } + + if (PrevPoint && NextPoint) + { + OutValue = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; + } + } + } + + OutPosition = (PrevPosition + NextPosition) / 2.0f; + }; + + + // Lambda for computing the color point to be inserted + auto GetInsertColorPointLambda = [MainColorRampParameter]( + const int32& InsertAtIndex, + float& OutPosition, + FLinearColor& OutColor, + EHoudiniRampInterpolationType& OutInterpType) mutable + { + if (!MainColorRampParameter) + return; + + float PrevPosition = 0.0f; + float NextPosition = 1.0f; + + TArray &CurrentPoints = MainColorRampParameter->Points; + TArray &CachedPoints = MainColorRampParameter->CachedPoints; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + int32 NumPoints = 0; + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NumPoints = CurrentPoints.Num(); + else + NumPoints = CachedPoints.Num(); + + if (InsertAtIndex >= NumPoints) + { + // Insert at the end + if (NumPoints > 0) + { + UHoudiniParameterRampColorPoint* PrevPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + PrevPoint = CurrentPoints.Last(); + else + PrevPoint = CachedPoints.Last(); + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + } + } + else if (InsertAtIndex <= 0) + { + // Insert at the beginning + if (NumPoints > 0) + { + UHoudiniParameterRampColorPoint* NextPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NextPoint = CurrentPoints[0]; + else + NextPoint = CachedPoints[0]; + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + OutInterpType = NextPoint->GetInterpolation(); + } + } + } + else + { + // Insert in the middle + if (NumPoints > 1) + { + UHoudiniParameterRampColorPoint* PrevPoint = nullptr; + UHoudiniParameterRampColorPoint* NextPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + { + PrevPoint = CurrentPoints[InsertAtIndex - 1]; + NextPoint = CurrentPoints[InsertAtIndex]; + } + else + { + PrevPoint = CachedPoints[InsertAtIndex - 1]; + NextPoint = CachedPoints[InsertAtIndex]; + } + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + } + + if (PrevPoint && NextPoint) + { + OutColor = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; + } + } + } + + OutPosition = (PrevPosition + NextPosition) / 2.0f; + }; + + int32 RowIndex = 0; + auto InsertRampPoint_Lambda = [GetInsertColorPointLambda, GetInsertFloatPointLambda, &CategoryBuilder, bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, + UHoudiniParameterRampColor* MainRampColor, + TArray &RampFloatList, + TArray &RampColorList, + const int32& Index) mutable + { + if (MainRampFloat) + { + float InsertPosition = 0.0f; + float InsertValue = 1.0f; + EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; + + GetInsertFloatPointLambda(Index, InsertPosition, InsertValue, InsertInterp); + + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); + + for (auto & NextFloatRamp : RampFloatList) + { + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + CreateFloatRampParameterInsertEvent( + NextFloatRamp, InsertPosition, InsertValue, InsertInterp); + + NextFloatRamp->MarkChanged(true); + } + else + { + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject + (NextFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); + NewCachedPoint->Position = InsertPosition; + NewCachedPoint->Value = InsertValue; + NewCachedPoint->Interpolation = InsertInterp; + + NextFloatRamp->CachedPoints.Add(NewCachedPoint); + NextFloatRamp->bCaching = true; + if (!bCookingEnabled) + { + // If cooking is not enabled, be sure to mark this parameter as changed + // so that it triggers an update once cooking is enabled again. + NextFloatRamp->MarkChanged(true); + } + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + { + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + } + else if (MainRampColor) + { + float InsertPosition = 0.0f; + FLinearColor InsertColor = FLinearColor::Black; + EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; + + GetInsertColorPointLambda(Index, InsertPosition, InsertColor, InsertInterp); + + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); + for (auto& NextColorRamp : RampColorList) + { + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + CreateColorRampParameterInsertEvent( + NextColorRamp, InsertPosition, InsertColor, InsertInterp); + + NextColorRamp->MarkChanged(true); + } + else + { + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject + (NextColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); + NewCachedPoint->Position = InsertPosition; + NewCachedPoint->Value = InsertColor; + NewCachedPoint->Interpolation = InsertInterp; + + NextColorRamp->CachedPoints.Add(NewCachedPoint); + NextColorRamp->bCaching = true; + if (!bCookingEnabled) + NextColorRamp->MarkChanged(true); + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + auto DeleteRampPoint_Lambda = [bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, + UHoudiniParameterRampColor* MainRampColor, + TArray &FloatRampList, + TArray &ColorRampList, + const int32& Index) mutable + { + if (MainRampFloat) + { + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampList); + + for (auto& NextFloatRamp : FloatRampList) + { + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextFloatRamp->Points.Num() == 0) + return; + + UHoudiniParameterRampFloatPoint* PointToDelete = nullptr; + + if (Index == -1) + PointToDelete = NextFloatRamp->Points.Last(); + else if (NextFloatRamp->Points.IsValidIndex(Index)) + PointToDelete = NextFloatRamp->Points[Index]; + + if (!PointToDelete) + return; + + const int32 & InstanceIndexToDelete = PointToDelete->InstanceIndex; + + CreateFloatRampParameterDeleteEvent(NextFloatRamp, InstanceIndexToDelete); + NextFloatRamp->MarkChanged(true); + } + else + { + if (NextFloatRamp->CachedPoints.Num() == 0) + return; + + if (Index == -1) + NextFloatRamp->CachedPoints.Pop(); + else if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) + NextFloatRamp->CachedPoints.RemoveAt(Index); + else + return; + + NextFloatRamp->bCaching = true; + if (!bCookingEnabled) + NextFloatRamp->MarkChanged(true); + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); + } + } + else + { + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampList); + + for (auto& NextColorRamp : ColorRampList) + { + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextColorRamp->Points.Num() == 0) + return; + + UHoudiniParameterRampColorPoint* PointToRemove = nullptr; + + if (Index == -1) + PointToRemove = NextColorRamp->Points.Last(); + else if (NextColorRamp->Points.IsValidIndex(Index)) + PointToRemove = NextColorRamp->Points[Index]; + + if (!PointToRemove) + return; + + const int32 & InstanceIndexToDelete = PointToRemove->InstanceIndex; + + CreateColorRampParameterDeleteEvent(NextColorRamp, InstanceIndexToDelete); + + NextColorRamp->MarkChanged(true); + } + else + { + if (NextColorRamp->CachedPoints.Num() == 0) + return; + + if (Index == -1) + NextColorRamp->CachedPoints.Pop(); + else if (NextColorRamp->CachedPoints.IsValidIndex(Index)) + NextColorRamp->CachedPoints.RemoveAt(Index); + else + return; + + NextColorRamp->bCaching = true; + if (!bCookingEnabled) + NextColorRamp->MarkChanged(true); + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + + TSharedRef VerticalBox = StaticCastSharedRef(Row->ValueWidget.Widget); + + TSharedPtr GridPanel; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SAssignNew(GridPanel, SUniformGridPanel) + ]; + + //AllUniformGridPanels.Add(GridPanel.Get()); + + GridPanel->SetSlotPadding(FMargin(2.f, 2.f, 5.f, 3.f)); + GridPanel->AddSlot(0, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Position"))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + FString ValueString = TEXT("Value"); + if (!MainFloatRampParameter) + ValueString = TEXT("Color"); + + GridPanel->AddSlot(1, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(ValueString)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + GridPanel->AddSlot(2, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Interp."))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + + GridPanel->AddSlot(3, RowIndex) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateLambda([InsertRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, bCookingEnabled]() mutable + { + int32 InsertAtIndex = -1; + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + InsertAtIndex = MainFloatRampParameter->Points.Num(); + else + InsertAtIndex = MainFloatRampParameter->CachedPoints.Num(); + } + else if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + InsertAtIndex = MainColorRampParameter->Points.Num(); + else + InsertAtIndex = MainColorRampParameter->CachedPoints.Num(); + } + + InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, InsertAtIndex); + }), + LOCTEXT("AddRampPoint", "Add a ramp point to the end"), true) + ] + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateLambda([DeleteRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList]() mutable + { + DeleteRampPoint_Lambda( + MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, -1); + }), + LOCTEXT("DeleteRampPoint", "Delete the last ramp point"), true) + ] + + ]; + + EUnit Unit = EUnit::Unspecified; + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + int32 PointCount = 0; + // Use Synced points on auto update mode + // Use Cached points on manual update mode + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + PointCount = MainFloatRampParameter->Points.Num(); + else + PointCount = MainFloatRampParameter->CachedPoints.Num(); + } + + if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate()) + PointCount = MainColorRampParameter->Points.Num(); + else + PointCount = MainColorRampParameter->CachedPoints.Num(); + } + + // Lambda function for changing a ramp point + auto OnPointChangeCommit = [bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, UHoudiniParameterRampColor* MainRampColor, + UHoudiniParameterRampFloatPoint* MainRampFloatPoint, UHoudiniParameterRampColorPoint* MainRampColorPoint, + TArray &RampFloatList, TArray &RampColorList, + const int32& Index, const FString& ChangedDataName, + const float& NewPosition, const float& NewFloat, + const FLinearColor& NewColor, + const EHoudiniRampInterpolationType& NewInterpType) mutable + { + if (MainRampFloat && MainRampFloatPoint) + { + if (ChangedDataName == FString("position") && MainRampFloatPoint->GetPosition() == NewPosition) + return; + if (ChangedDataName == FString("value") && MainRampFloatPoint->GetValue() == NewFloat) + return; + if (ChangedDataName == FString("interp") && MainRampFloatPoint->GetInterpolation() == NewInterpType) + return; + + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); + for (auto NextFloatRamp : RampFloatList) + { + if (!NextFloatRamp) + continue; + + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextFloatRamp->Points.IsValidIndex(Index)) + { + UHoudiniParameterRampFloatPoint* CurrentFloatRampPoint = NextFloatRamp->Points[Index]; + if (!CurrentFloatRampPoint) + continue; + + if (ChangedDataName == FString("position")) + { + if (!CurrentFloatRampPoint->PositionParentParm) + continue; + + CurrentFloatRampPoint->SetPosition(NewPosition); + CurrentFloatRampPoint->PositionParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("value")) + { + if (!CurrentFloatRampPoint->PositionParentParm) + continue; + + CurrentFloatRampPoint->SetValue(NewFloat); + CurrentFloatRampPoint->ValueParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("interp")) + { + if (!CurrentFloatRampPoint->InterpolationParentParm) + continue; + + CurrentFloatRampPoint->SetInterpolation(NewInterpType); + CurrentFloatRampPoint->InterpolationParentParm->MarkChanged(true); + } + } + else + { + int32 IdxInEventsArray = Index - NextFloatRamp->Points.Num(); + if (NextFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = NextFloatRamp->ModificationEvents[IdxInEventsArray]; + if (!Event) + continue; + + if (ChangedDataName == FString("position")) + { + Event->InsertPosition = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + Event->InsertFloat = NewFloat; + } + else if (ChangedDataName == FString("interp")) + { + Event->InsertInterpolation = NewInterpType; + } + } + } + } + else + { + if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) + { + UHoudiniParameterRampFloatPoint* CachedPoint = NextFloatRamp->CachedPoints[Index]; + + if (!CachedPoint) + continue; + + if (ChangedDataName == FString("position")) + { + CachedPoint->Position = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + CachedPoint->Value = NewFloat; + } + else if (ChangedDataName == FString("interp")) + { + CachedPoint->Interpolation = NewInterpType; + } + + NextFloatRamp->bCaching = true; + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); + } + } + else if (MainRampColor && MainRampColorPoint) + { + if (ChangedDataName == FString("position") && MainRampColorPoint->GetPosition() == NewPosition) + return; + + if (ChangedDataName == FString("value") && MainRampColorPoint->GetValue() == NewColor) + return; + + if (ChangedDataName == FString("interp") && MainRampColorPoint->GetInterpolation() == NewInterpType) + return; + + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); + for (auto NextColorRamp : RampColorList) + { + if (!NextColorRamp) + continue; + + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextColorRamp->Points.IsValidIndex(Index)) + { + UHoudiniParameterRampColorPoint* CurrentColorRampPoint = NextColorRamp->Points[Index]; + if (!CurrentColorRampPoint) + continue; + + if (ChangedDataName == FString("position")) + { + if (!CurrentColorRampPoint->PositionParentParm) + continue; + + CurrentColorRampPoint->SetPosition(NewPosition); + CurrentColorRampPoint->PositionParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("value")) + { + if (!CurrentColorRampPoint->PositionParentParm) + continue; + + CurrentColorRampPoint->SetValue(NewColor); + CurrentColorRampPoint->ValueParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("interp")) + { + if (!CurrentColorRampPoint->InterpolationParentParm) + continue; + + CurrentColorRampPoint->SetInterpolation(NewInterpType); + CurrentColorRampPoint->InterpolationParentParm->MarkChanged(true); + } + } + else + { + int32 IdxInEventsArray = Index - NextColorRamp->Points.Num(); + if (NextColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = NextColorRamp->ModificationEvents[IdxInEventsArray]; + if (!Event) + continue; + + if (ChangedDataName == FString("position")) + { + Event->InsertPosition = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + Event->InsertColor = NewColor; + } + else if (ChangedDataName == FString("interp")) + { + Event->InsertInterpolation = NewInterpType; + } + + } + } + } + else + { + if (NextColorRamp->CachedPoints.IsValidIndex(Index)) + { + UHoudiniParameterRampColorPoint* CachedPoint = NextColorRamp->CachedPoints[Index]; + + if (!CachedPoint) + continue; + + if (ChangedDataName == FString("position")) + { + CachedPoint->Position = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + CachedPoint->Value = NewColor; + } + else if (ChangedDataName == FString("interp")) + { + CachedPoint->Interpolation = NewInterpType; + } + + NextColorRamp->bCaching = true; + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + for (int32 Index = 0; Index < PointCount; ++Index) + { + UHoudiniParameterRampFloatPoint* NextFloatRampPoint = nullptr; + UHoudiniParameterRampColorPoint* NextColorRampPoint = nullptr; + + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + NextFloatRampPoint = MainFloatRampParameter->Points[Index]; + else + NextFloatRampPoint = MainFloatRampParameter->CachedPoints[Index]; + } + if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NextColorRampPoint = MainColorRampParameter->Points[Index]; + else + NextColorRampPoint = MainColorRampParameter->CachedPoints[Index]; + } + + if (!NextFloatRampPoint && !NextColorRampPoint) + continue; + + RowIndex += 1; + + float CurPos = 0.f; + if (NextFloatRampPoint) + CurPos = NextFloatRampPoint->Position; + else + CurPos = NextColorRampPoint->Position; + + + GridPanel->AddSlot(0, RowIndex) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Value(CurPos) + .OnValueChanged_Lambda([](float Val) {}) + .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("position"), + Val, float(-1.0), + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .OnBeginSliderMovement_Lambda([]() {}) + .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("position"), + Val, float(-1.0), + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .SliderExponent(1.0f) + .TypeInterface(paramTypeInterface) + ]; + + if (NextFloatRampPoint) + { + GridPanel->AddSlot(1, RowIndex) + [ + SNew(SNumericEntryBox< float >) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Value(NextFloatRampPoint->Value) + .OnValueChanged_Lambda([](float Val){}) + .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), Val, + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .OnBeginSliderMovement_Lambda([]() {}) + .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), Val, + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .SliderExponent(1.0f) + .TypeInterface(paramTypeInterface) + ]; + } + else if (NextColorRampPoint) + { + auto OnColorChangeLambda = [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](FLinearColor InColor) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), float(-1.0), + InColor, + EHoudiniRampInterpolationType::LINEAR); + }; + + // Add color picker UI. + //TSharedPtr ColorBlock; + GridPanel->AddSlot(1, RowIndex) + [ + SNew(SColorBlock) + .Color(NextColorRampPoint->Value) + .OnMouseButtonDown( FPointerEventHandler::CreateLambda( + [NextColorRampPoint, OnColorChangeLambda](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) + { + if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) + return FReply::Unhandled(); + + FColorPickerArgs PickerArgs; + PickerArgs.bUseAlpha = true; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda(OnColorChangeLambda); + FLinearColor InitColor = NextColorRampPoint->Value; + PickerArgs.InitialColorOverride = InitColor; + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker(PickerArgs); + return FReply::Handled(); + })) + ]; + } + + int32 CurChoice = 0; + if (NextFloatRampPoint) + CurChoice = (int)NextFloatRampPoint->Interpolation; + else + CurChoice = (int)NextColorRampPoint->Interpolation; + + TSharedPtr >> ComboBoxCurveMethod; + GridPanel->AddSlot(2, RowIndex) + [ + SAssignNew(ComboBoxCurveMethod, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels())[CurChoice]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, + ColorRampParameterList, Index](TSharedPtr NewChoice, ESelectInfo::Type SelectType) mutable + { + EHoudiniRampInterpolationType NewInterpType = UHoudiniParameter::GetHoudiniInterpMethodFromString(*NewChoice.Get()); + + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("interp"), + float(-1.0), float(-1.0), + FLinearColor(), + NewInterpType); + }) + [ + SNew(STextBlock) + .Text_Lambda([NextFloatRampPoint, NextColorRampPoint]() + { + EHoudiniRampInterpolationType CurInterpType = EHoudiniRampInterpolationType::InValid; + if (NextFloatRampPoint) + CurInterpType = NextFloatRampPoint->GetInterpolation(); + else + CurInterpType = NextColorRampPoint->GetInterpolation(); + + return FText::FromString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(CurInterpType)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + GridPanel->AddSlot(3, RowIndex) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateLambda( + [InsertRampPoint_Lambda, MainFloatRampParameter, + MainColorRampParameter, FloatRampParameterList, + ColorRampParameterList, Index]() mutable + { + InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); + }), + LOCTEXT("AddRampPoint", "Add a ramp point before this point"), true) + ] + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateLambda( + [DeleteRampPoint_Lambda, MainFloatRampParameter, + MainColorRampParameter, FloatRampParameterList, + ColorRampParameterList, Index]() mutable + { + DeleteRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); + }), + LOCTEXT("DeleteFloatRampPoint", "Delete this ramp point"), true) + ] + ]; + + if (MainFloatRampParameter && CurrentRampParameterFloatCurve) + { + ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextFloatRampPoint->GetInterpolation()); + FRichCurve & RichCurve = CurrentRampParameterFloatCurve->FloatCurve; + FKeyHandle const KeyHandle = RichCurve.AddKey(NextFloatRampPoint->GetPosition(), NextFloatRampPoint->GetValue()); + RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); + } + + if (MainColorRampParameter && CurrentRampParameterColorCurve) + { + ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextColorRampPoint->GetInterpolation()); + for (int32 CurveIdx = 0; CurveIdx < 4; ++CurveIdx) + { + FRichCurve & RichCurve = CurrentRampParameterColorCurve->FloatCurves[CurveIdx]; + + FKeyHandle const KeyHandle = RichCurve.AddKey(NextColorRampPoint->GetPosition(), NextColorRampPoint->GetValue().Component(CurveIdx)); + RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); + } + } + } + + if (MainFloatRampParameter) + GridPanel->SetEnabled(!MainFloatRampParameter->IsDisabled()); + + if (MainColorRampParameter) + GridPanel->SetEnabled(!MainColorRampParameter->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetFolderList(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FolderListParams; + if (!CastParameters(InParams, FolderListParams)) + return; + + if (FolderListParams.Num() <= 0) + return; + + UHoudiniParameterFolderList* MainParam = FolderListParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Add this folder list to the folder map + AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); + + MainParam->GetTabs().Empty(); + + // A folder list will be followed by all its child folders, + // so set the CurrentFolderListSize to the tuple size, we'll process such many folder parameters right after + CurrentFolderListSize = MainParam->GetTupleSize(); + + if (MainParam->IsDirectChildOfMultiParm()) + MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); + + if (CurrentFolderListSize <= 0) // There should not be empty folder list, this will not happen normally + return; + + // The following folders belong to current folder list + CurrentFolderList = MainParam; + + // If the tab is either a tabs or radio button and the parameter is visible + if (MainParam->IsTabMenu() && MainParam->ShouldDisplay()) + { + // Set the current tabs to be not shown by default now. CreateWidgetTab will decide if the tabs is shown. + CurrentFolderList->SetTabsShown(false); + + // Create a row to hold tab buttons if the folder list is a tabs or radio button + + // CreateNestedRow does not actually create a row for tabs, it is responsible to prune the folder stack. + // ( CreateWidgetTab will be responsible to create a row according to the visibility of its outer level folders ) + FDetailWidgetRow* TabRow = CreateNestedRow(HouParameterCategory, InParams, false); + + } + + // When see a folder list, go depth first search at this step. + // Push an empty queue to the stack. + FolderStack.Add(TArray()); +} + + +void +FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FolderParams; + if (!CastParameters(InParams, FolderParams)) + return; + + if (FolderParams.Num() <= 0) + return; + + UHoudiniParameterFolder* MainParam = FolderParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen + return; + // If a folder is invisible, its children won't be listed by HAPI. + // So just reduce FolderListSize by 1, reduce the child counter of its parent folder by 1 if necessary, + // and prune the stack in such case. + if (!MainParam->IsVisible()) + { + CurrentFolderListSize -= 1; + + if (CurrentFolderListSize == 0) + { + if (FolderStack.Num() > 1) + { + TArray &ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + ParentFolderQueue[0]->GetChildCounter() -= 1; + } + + PruneStack(); + } + + return; + } + + // We expect 'TupleSize' children param of this folder after finish processing all the child folders of cur folderlist + MainParam->ResetChildCounter(); + + // Add this folder to the folder map + AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); + + // Set the parent param to current folderList. + // it was parent multiparm's id if this folder is a child of a multiparms. + // This will cause problem if the folder is inside of a multiparm + MainParam->SetParentParmId(CurrentFolderList->GetParmId()); + + + // Case 1: The folder is a direct child of a multiparm. + if (MainParam->IsDirectChildOfMultiParm()) + { + if (FolderStack.Num() <= 0) // This should not happen + return; + + // Get its parent multiparm first + UHoudiniParameterMultiParm* ParentMultiParm = nullptr; + { + UHoudiniParameterFolderList * ParentFolderList = nullptr; + if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) + return; + + ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); + + if (!ParentFolderList) + return; + + if (AllMultiParms.Contains(ParentFolderList->GetParentParmId())) + ParentMultiParm = AllMultiParms[ParentFolderList->GetParentParmId()]; + + if (!ParentMultiParm) // This should not happen + return; + } + + bool bShown = ParentMultiParm->IsShown(); + + // Case 1-1: The folder is NOT tabs + if (!MainParam->IsTab()) + { + bShown = MainParam->IsExpanded() && bShown; + + // If the parent multiparm is shown. + if (ParentMultiParm->IsShown()) + { + FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderHeaderRow, InParams); + } + } + // Case 1-2: The folder IS tabs. + else + { + CreateWidgetTab(HouParameterCategory, MainParam, ParentMultiParm->IsShown()); + } + + // Push the folder to the queue if it is not a tab folder + // This step is handled by CreateWidgetTab() if it is tabs + if ((!MainParam->IsTab() || !ParentMultiParm->IsShown()) && MainParam->GetTupleSize() > 0) + { + TArray & MyQueue = FolderStack.Last(); + MainParam->SetIsContentShown(bShown); + MyQueue.Add(MainParam); + } + } + + // Case 2: The folder is NOT a direct child of a multiparm. + else + { + // Case 2-1: The folder is in another folder. + if (FolderStack.Num() > 1 && CurrentFolderListSize > 0) + { + TArray & MyFolderQueue = FolderStack.Last(); + TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + + if (ParentFolderQueue.Num() <= 0) //This should happen + return; + + // Peek the folder queue of the last layer to get its parent folder parm. + bool ParentFolderVisible = ParentFolderQueue[0]->IsContentShown(); + + // If this folder is expanded (selected if is tabs) + bool bExpanded = ParentFolderVisible; + + // Case 2-1-1: The folder is NOT in a tab menu. + if (!MainParam->IsTab()) + { + bExpanded &= MainParam->IsExpanded(); + + // The parent folder is visible. + if (ParentFolderVisible) + { + // Add the folder header UI. + FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderHeaderRow, InParams); + } + + MainParam->SetIsContentShown(bExpanded); + MyFolderQueue.Add(MainParam); + } + // Case 2-1-2: The folder IS in a tab menu. + else + { + bExpanded &= MainParam->IsChosen(); + + CreateWidgetTab(HouParameterCategory, MainParam, ParentFolderVisible); + } + } + // Case 2-2: The folder is in the root. + else + { + bool bExpanded = true; + + // Case 2-2-1: The folder is NOT under a tab menu. + if (!MainParam->IsTab()) + { + if (FolderStack.Num() <= 0) // This will not happen + return; + + // Create Folder header under root. + FDetailWidgetRow* FolderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderRow, InParams); + + if (FolderStack.Num() == 0) // This should not happen + return; + + TArray& MyFolderQueue = FolderStack[0]; + bExpanded &= MainParam->IsExpanded(); + MainParam->SetIsContentShown(bExpanded); + MyFolderQueue.Add(MainParam); + } + // Case 2-2-2: The folder IS under a tab menu. + else + { + // Tabs in root is always visible + CreateWidgetTab(HouParameterCategory, MainParam, true); + } + } + } + + + CurrentFolderListSize -= 1; + + // Prune the stack if finished parsing current folderlist + if (CurrentFolderListSize == 0) + { + if (FolderStack.Num() > 1 && !MainParam->IsDirectChildOfMultiParm()) + { + TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + ParentFolderQueue[0]->GetChildCounter() -= 1; + } + + PruneStack(); + + CurrentFolderList = nullptr; + } +} + +void +FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray &InParams) +{ + if (!HeaderRow) // The folder is invisible. + return; + + TArray FolderParams; + if (!CastParameters(InParams, FolderParams)) + return; + + if (FolderParams.Num() <= 0) + return; + + UHoudiniParameterFolder* MainParam = FolderParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return; + + TSharedPtr VerticalBox; + + FString LabelStr = MainParam->GetParameterLabel(); + + TSharedPtr HorizontalBox; + TSharedPtr ExpanderArrow; + TSharedPtr ExpanderImage; + + HeaderRow->NameWidget.Widget = SAssignNew(HorizontalBox, SCustomizedBox); + + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + + if (MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 1) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + LabelStr = LabelStr + TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex) + TEXT(")"); + } + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SAssignNew(ExpanderArrow, SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([=]() + { + MainParam->ExpandButtonClicked(); + + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + + return FReply::Handled(); + }) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + + FText LabelText = FText::FromString(LabelStr); + + HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SNew(STextBlock) + .Text(LabelText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + ExpanderImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([=]() { + FName ResourceName; + if(MainParam->IsExpanded()) + { + ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + }))); + + if(MainParam->GetFolderType() == EHoudiniFolderParameterType::Simple) + ExpanderArrow->SetEnabled(false); + +} + +void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InFolder, const bool& bIsShown) +{ + if (!InFolder || InFolder->IsPendingKill() || !CurrentFolderList) + return; + + if (FolderStack.Num() <= 0) // error state + return; + + TArray & FolderQueue = FolderStack.Last(); + + // Cache all tabs of current tab folder list. + CurrentFolderList->AddTabFolder(InFolder); + + // If the tabs is not shown, just push the folder param into the queue. + if (!bIsShown) + { + InFolder->SetIsContentShown(bIsShown); + FolderQueue.Add(InFolder); + return; + } + + // tabs currently being processed + CurrentTabs.Add(InFolder); + + if (CurrentFolderListSize > 1) + return; + + // The tabs belong to current folder list + UHoudiniParameterFolderList* CurrentTabMenuFolderList = CurrentFolderList; + + // Create a row (UI) for current tabs + TSharedPtr HorizontalBox; + FDetailWidgetRow &Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()) + [ + SAssignNew(HorizontalBox, SCustomizedBox) + ]; + + // Put current tab folder list param into an array + TArray CurrentTabMenuFolderListArr; + CurrentTabMenuFolderListArr.Add(CurrentTabMenuFolderList); + + HorizontalBox->SetHoudiniParameter(CurrentTabMenuFolderListArr); + DividerLinePositions.Add(HorizontalBox->AddIndentation(InFolder, AllMultiParms, AllFoldersAndFolderLists)); + HorizontalBox->DividerLinePositions = DividerLinePositions; + + float DesiredHeight = 0.0f; + float DesiredWidth = 0.0f; + + // Process all tabs of current folder list at once when done. + + for (auto & CurTab : CurrentTabs) + { + if (!CurTab || CurTab->IsPendingKill()) + continue; + + CurTab->SetIsContentShown(CurTab->IsChosen()); + FolderQueue.Add(CurTab); + + auto OnTabClickedLambda = [CurrentTabMenuFolderList, CurTab, &HouParameterCategory]() + { + if (CurrentTabMenuFolderList) + { + if (!CurrentTabMenuFolderList->bIsTabMenu || CurrentTabMenuFolderList->TabFolders.Num() <= 0) + return FReply::Handled(); + + if (CurTab->IsChosen()) + return FReply::Handled(); + + CurTab->SetChosen(true); + + for (UHoudiniParameterFolder* NextFolder : CurrentTabMenuFolderList->TabFolders) + { + if (CurTab->GetParmId() != NextFolder->GetParmId() && NextFolder->IsChosen()) + NextFolder->SetChosen(false); + } + + HouParameterCategory.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); + }; + + FString FolderLabelString = TEXT(" ") + CurTab->GetParameterLabel(); + if (CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio) + FolderLabelString = TEXT(" ") + FolderLabelString; + + bool bChosen = CurTab->IsTab() && CurTab->IsChosen(); + + TSharedPtr CurCustomizedButton; + + HorizontalBox->AddSlot().VAlign(VAlign_Bottom) + .AutoWidth() + .Padding(0.f) + .HAlign(HAlign_Left) + [ + SAssignNew(CurCustomizedButton, SCustomizedButton) + .OnClicked_Lambda(OnTabClickedLambda) + .Content() + [ + SNew(STextBlock) + .Text(FText::FromString(FolderLabelString)) + ] + ]; + + CurCustomizedButton->bChosen = bChosen; + CurCustomizedButton->bIsRadioButton = CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio; + + DesiredHeight = CurCustomizedButton->GetDesiredSize().Y; + DesiredWidth += CurCustomizedButton->GetDesiredSize().X; + } + + HorizontalBox->bIsTabFolderListRow = true; + + Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + // Set the current tabs to be shown, since slate widgets have been created + CurrentTabMenuFolderList->SetTabsShown(true); + + // Clear the temporary tabs + CurrentTabs.Empty(); +} + +void +FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray MultiParmParams; + if (!CastParameters(InParams, MultiParmParams)) + return; + + if (MultiParmParams.Num() <= 0) + return; + + UHoudiniParameterMultiParm* MainParam = MultiParmParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Add current multiparm parameter to AllmultiParms map + AllMultiParms.Add(MainParam->GetParmId(), MainParam); + + // Create a new detail row + FDetailWidgetRow * Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + { + MainParam->SetIsShown(false); + return; + } + + MainParam->SetIsShown(true); + + MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); + + CreateNameWidget(Row, InParams, true); + + auto OnInstanceValueChangedLambda = [MainParam](int32 InValue) + { + if (InValue < 0) + return; + + int32 ChangesCount = FMath::Abs(MainParam->MultiParmInstanceLastModifyArray.Num() - InValue); + + if (MainParam->MultiParmInstanceLastModifyArray.Num() > InValue) + { + for (int32 Idx = 0; Idx < ChangesCount; ++Idx) + MainParam->RemoveElement(-1); + + MainParam->MarkChanged(true); + } + else if (MainParam->MultiParmInstanceLastModifyArray.Num() < InValue) + { + for (int32 Idx = 0; Idx < ChangesCount; ++Idx) + MainParam->InsertElement(); + + MainParam->MarkChanged(true); + } + }; + + // Add multiparm UI. + TSharedRef HorizontalBox = SNew(SHorizontalBox); + TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; + int32 NumericalCount = MainParam->MultiParmInstanceCount; + HorizontalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateLambda([OnInstanceValueChangedLambda](int32 InValue) { + OnInstanceValueChangedLambda(InValue); + })) + .Value(NumericalCount) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamAddInstance", "Houdini Parameter Multi Parameter: Adding an instance"), + MainParam->GetOuter(), true); + + for (auto& Param : MultiParmParams) + { + if (!Param) + continue; + + // Add a reverse step for redo/undo + Param->MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Removed); + + Param->MarkChanged(true); + Param->Modify(); + + if (Param->MultiParmInstanceLastModifyArray.Num() > 0) + Param->MultiParmInstanceLastModifyArray.RemoveAt(Param->MultiParmInstanceLastModifyArray.Num() - 1); + + Param->InsertElement(); + + } + }), + LOCTEXT("AddMultiparmInstanceToolTipAddLastInstance", "Add an Instance"), true) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + // Remove the last multiparm instance + PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamDeleteInstance", "Houdini Parameter Multi Parameter: Deleting an instance"), + MainParam->GetOuter(), true); + + for (auto & Param : MultiParmParams) + { + TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; + int32 RemovedIndex = LastModifiedArray.Num() - 1; + while (LastModifiedArray.IsValidIndex(RemovedIndex) && LastModifiedArray[RemovedIndex] == EHoudiniMultiParmModificationType::Removed) + RemovedIndex -= 1; + + // Add a reverse step for redo/undo + EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; + if (LastModifiedArray.IsValidIndex(RemovedIndex)) + { + PreviousModType = LastModifiedArray[RemovedIndex]; + LastModifiedArray[RemovedIndex] = EHoudiniMultiParmModificationType::Inserted; + } + + Param->MarkChanged(true); + + Param->Modify(); + + if (LastModifiedArray.IsValidIndex(RemovedIndex)) + { + LastModifiedArray[RemovedIndex] = PreviousModType; + } + + Param->RemoveElement(RemovedIndex); + } + + }), + LOCTEXT("RemoveLastMultiParamLastToolTipRemoveLastInstance", "Remove the last instance"), true) + + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamDeleteAllInstances", "Houdini Parameter Multi Parameter: Deleting all instances"), + MainParam->GetOuter(), true); + + for (auto & Param : MultiParmParams) + { + TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; + TArray IndicesToReverse; + + for (int32 Index = 0; Index < LastModifiedArray.Num(); ++Index) + { + if (LastModifiedArray[Index] == EHoudiniMultiParmModificationType::None) + { + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; + IndicesToReverse.Add(Index); + } + } + + Param->MarkChanged(true); + + Param->Modify(); + + for (int32 & Index : IndicesToReverse) + { + if (LastModifiedArray.IsValidIndex(Index)) + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::None; + } + + + Param->EmptyElements(); + } + + }), + LOCTEXT("HoudiniParameterRemoveAllMultiparmInstancesToolTip", "Remove all instances"), true) + ]; + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniParameterDetails::CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams) +{ + + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return; + + if (!HorizontalBox || !AllMultiParms.Contains(MainParam->GetParentParmId()) || !MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + return; + + UHoudiniParameterMultiParm* MainParentMultiParm = AllMultiParms[MainParam->GetParentParmId()]; + + if (!MainParentMultiParm) + return; + + if (!MainParentMultiParm->IsShown()) + return; + + // push all parent multiparm of the InParams to the array + TArray ParentMultiParams; + for (auto & InParam : InParams) + { + if (!InParam) + continue; + + if (!MultiParmInstanceIndices.Contains(InParam->GetParentParmId())) + continue; + + if (InParam->GetChildIndex() == 0) + { + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[InParam->GetParentParmId()]; + + if (ParentMultiParm) + ParentMultiParams.Add(ParentMultiParm); + } + } + + + int32 InstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + + TSharedRef AddButton = PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() + { + for (auto & ParentParam : ParentMultiParams) + { + // Add button call back + if (!ParentParam) + continue; + + TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; + + if (!LastModifiedArray.IsValidIndex(InstanceIndex)) + continue; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParmAddBeforeCurInstance", "Houdini Parameter Multi Parm: Adding an instance"), + ParentParam->GetOuter(), true); + + + int32 Index = InstanceIndex; + + // Add a reverse step for undo/redo + if (Index >= LastModifiedArray.Num()) + LastModifiedArray.Add(EHoudiniMultiParmModificationType::Removed); + else + LastModifiedArray.Insert(EHoudiniMultiParmModificationType::Removed, Index); + + ParentParam->MarkChanged(true); + ParentParam->Modify(); + + if (Index >= LastModifiedArray.Num() - 1 && LastModifiedArray.Num()) + LastModifiedArray.RemoveAt(LastModifiedArray.Num() - 1); + else + LastModifiedArray.RemoveAt(Index); + + ParentParam->InsertElementAt(InstanceIndex); + + } + }), + LOCTEXT("HoudiniParameterMultiParamAddBeforeCurrentInstanceToolTip", "Insert an instance before this instance")); + + + TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() + { + for (auto & ParentParam : ParentMultiParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParmDeleteCurInstance", "Houdini Parameter Multi Parm: Deleting an instance"), + ParentParam->GetOuter(), true); + + + TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; + + int32 Index = InstanceIndex; + EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; + while (LastModifiedArray.IsValidIndex(Index) && LastModifiedArray[Index] == EHoudiniMultiParmModificationType::Removed) + { + Index -= 1; + } + + if (LastModifiedArray.IsValidIndex(Index)) + { + PreviousModType = LastModifiedArray[Index]; + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; + } + + ParentParam->MarkChanged(true); + + ParentParam->Modify(); + + if (LastModifiedArray.IsValidIndex(Index)) + { + LastModifiedArray[Index] = PreviousModType; + } + + ParentParam->RemoveElement(InstanceIndex); + } + + }), + LOCTEXT("HoudiniParameterMultiParamDeleteCurrentInstanceToolTip", "Remove an instance"), true); + + + HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[AddButton]; + HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[RemoveButton]; + + int32 StartIdx = MainParam->GetParameterType() == EHoudiniParameterType::Folder ? 1 : 0; + if (MainParam->GetChildIndex() != StartIdx) + { + AddButton.Get().SetVisibility(EVisibility::Hidden); + RemoveButton.Get().SetVisibility(EVisibility::Hidden); + } + +} + +void +FHoudiniParameterDetails::PruneStack() +{ + for (int32 StackItr = FolderStack.Num() - 1; StackItr >= 0; --StackItr) + { + TArray &CurrentQueue = FolderStack[StackItr]; + + for (int32 QueueItr = CurrentQueue.Num() - 1; QueueItr >= 0; --QueueItr) + { + UHoudiniParameterFolder * CurrentFolder = CurrentQueue[QueueItr]; + if (!CurrentFolder || CurrentFolder->IsPendingKill()) + continue; + + if (CurrentFolder->GetChildCounter() == 0) + { + CurrentQueue.RemoveAt(QueueItr); + } + } + + if (CurrentQueue.Num() == 0) + { + FolderStack.RemoveAt(StackItr); + } + } +} + +FText +FHoudiniParameterDetails::GetParameterTooltip(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return FText(); + + // Tooltip starts with Label (name) + FString Tooltip = InParam->GetParameterLabel() + TEXT(" (") + InParam->GetParameterName() + TEXT(")"); + + // Append the parameter type + FString ParmTypeStr = GetParameterTypeString(InParam->GetParameterType(), InParam->GetTupleSize()); + if (!ParmTypeStr.IsEmpty()) + Tooltip += TEXT("\n") + ParmTypeStr; + + // If the parameter has some help, append it + FString Help = InParam->GetParameterHelp(); + if (!Help.IsEmpty()) + Tooltip += TEXT("\n") + Help; + + // If the parameter has an expression, append it + if (InParam->HasExpression()) + { + FString Expr = InParam->GetExpression(); + if (!Expr.IsEmpty()) + Tooltip += TEXT("\nExpression: ") + Expr; + } + + return FText::FromString(Tooltip); +} + +FString +FHoudiniParameterDetails::GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize) +{ + FString ParamStr; + + switch (InType) + { + case EHoudiniParameterType::Button: + ParamStr = TEXT("Button"); + break; + + case EHoudiniParameterType::ButtonStrip: + ParamStr = TEXT("Button Strip"); + break; + + case EHoudiniParameterType::Color: + { + if (InTupleSize == 4) + ParamStr = TEXT("Color with Alpha"); + else + ParamStr = TEXT("Color"); + } + break; + + case EHoudiniParameterType::ColorRamp: + ParamStr = TEXT("Color Ramp"); + break; + + case EHoudiniParameterType::File: + ParamStr = TEXT("File (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileDir: + ParamStr = TEXT("File Dir (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileGeo: + ParamStr = TEXT("File Geo (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileImage: + ParamStr = TEXT("File Image (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::Float: + ParamStr = TEXT("Float (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); + break; + + case EHoudiniParameterType::FloatRamp: + ParamStr = TEXT("Float Ramp"); + break; + + case EHoudiniParameterType::Folder: + case EHoudiniParameterType::FolderList: + break; + + case EHoudiniParameterType::Input: + ParamStr = TEXT("Opearator Path"); + break; + + case EHoudiniParameterType::Int: + ParamStr = TEXT("Integer (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); + break; + + case EHoudiniParameterType::IntChoice: + ParamStr = TEXT("Int Choice"); + break; + + case EHoudiniParameterType::Label: + ParamStr = TEXT("Label (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::MultiParm: + ParamStr = TEXT("MultiParm"); + break; + + case EHoudiniParameterType::Separator: + break; + + case EHoudiniParameterType::String: + ParamStr = TEXT("String (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::StringAssetRef: + ParamStr = TEXT("String Asset Ref (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::StringChoice: + ParamStr = TEXT("String Choice"); + break; + + case EHoudiniParameterType::Toggle: + ParamStr = TEXT("Toggle (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + default: + ParamStr = TEXT("invalid parameter type"); + break; + } + + + return ParamStr; +} + +void +FHoudiniParameterDetails::SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter) +{ + if (!ColorRampParameter) + return; + + // Do not sync when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) + return; + + TArray &CachedPoints = ColorRampParameter->CachedPoints; + TArray &CurrentPoints = ColorRampParameter->Points; + + bool bCurveNeedsUpdate = false; + bool bRampParmNeedsUpdate = false; + + int32 Idx = 0; + + while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) + { + UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[Idx]; + UHoudiniParameterRampColorPoint* CurrentPoint = CurrentPoints[Idx]; + + if (!CachedPoint || !CurrentPoint) + continue; + + if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) + { + if (CurrentPoint->PositionParentParm) + { + CurrentPoint->SetPosition(CachedPoint->GetPosition()); + CurrentPoint->PositionParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + if (CachedPoint->GetValue() != CurrentPoint->GetValue()) + { + if (CurrentPoint->ValueParentParm) + { + CurrentPoint->SetValue(CachedPoint->GetValue()); + CurrentPoint->ValueParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) + { + if (CurrentPoint->InterpolationParentParm) + { + CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); + CurrentPoint->InterpolationParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + Idx += 1; + } + + // Insert points + for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) + { + UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[IdxCachedPointLeft]; + + CreateColorRampParameterInsertEvent( + ColorRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); + + bCurveNeedsUpdate = true; + bRampParmNeedsUpdate = true; + + } + + // Delete points + for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) + { + ColorRampParameter->RemoveElement(IdxCurrentPointLeft); + + UHoudiniParameterRampColorPoint* Point = CurrentPoints[IdxCurrentPointLeft]; + + if (!Point) + continue; + + CreateColorRampParameterDeleteEvent(ColorRampParameter, Point->InstanceIndex); + + bCurveNeedsUpdate = true; + bRampParmNeedsUpdate = true; + } + + + ColorRampParameter->MarkChanged(bRampParmNeedsUpdate); +} + +//void +//FHoudiniParameterDetails::SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter) +//{ +// if (!FloatRampParameter) +// return; +// +// // Do not sync when the Houdini asset component is cooking +// if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) +// return; +// +// TArray &CachedPoints = FloatRampParameter->CachedPoints; +// TArray &CurrentPoints = FloatRampParameter->Points; +// +// int32 Idx = 0; +// +// while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) +// { +// UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; +// UHoudiniParameterRampFloatPoint* &CurrentPoint = CurrentPoints[Idx]; +// +// if (!CachedPoint || !CurrentPoint) +// continue; +// +// if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) +// { +// if (CurrentPoint->PositionParentParm) +// { +// CurrentPoint->SetPosition(CachedPoint->GetPosition()); +// CurrentPoint->PositionParentParm->MarkChanged(true); +// } +// } +// +// if (CachedPoint->GetValue() != CurrentPoint->GetValue()) +// { +// if (CurrentPoint->ValueParentParm) +// { +// CurrentPoint->SetValue(CachedPoint->GetValue()); +// CurrentPoint->ValueParentParm->MarkChanged(true); +// } +// } +// +// if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) +// { +// if (CurrentPoint->InterpolationParentParm) +// { +// CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); +// CurrentPoint->InterpolationParentParm->MarkChanged(true); +// } +// } +// +// Idx += 1; +// } +// +// // Insert points +// for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) +// { +// UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; +// if (!CachedPoint) +// continue; +// +// CreateFloatRampParameterInsertEvent( +// FloatRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); +// +// FloatRampParameter->MarkChanged(true); +// } +// +// // Remove points +// for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) +// { +// FloatRampParameter->RemoveElement(IdxCurrentPointLeft); +// +// UHoudiniParameterRampFloatPoint* Point = CurrentPoints[IdxCurrentPointLeft]; +// +// if (!Point) +// continue; +// +// CreateFloatRampParameterDeleteEvent(FloatRampParameter, Point->InstanceIndex); +// +// FloatRampParameter->MarkChanged(true); +// } +//} + +void +FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetFloatRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + InParam->ModificationEvents.Add(DeleteEvent); +} + +void +FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetColorRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + InParam->ModificationEvents.Add(DeleteEvent); +} + +void +FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, + const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetFloatRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertFloat = InValue; + InsertEvent->InsertInterpolation = InInterp; + + InParam->ModificationEvents.Add(InsertEvent); +} + +void +FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, + const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetColorRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertColor = InColor; + InsertEvent->InsertInterpolation = InInterp; + + InParam->ModificationEvents.Add(InsertEvent); +} + +void +FHoudiniParameterDetails:: ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters) +{ + if (FloatRampParameters.Num() < 1) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0]; + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) + { + UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx]; + + if (!NextFloatRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); + } +} + +void +FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters) +{ + if (FloatRampParameters.Num() < 1) + return; + + if (!FloatRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + + for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) + { + if (!FloatRampParameters[Idx].IsValid()) + continue; + + UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx].Get(); + + if (!NextFloatRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); + } + +} + +void +FHoudiniParameterDetails:: ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam) +{ + if (!Param || !MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) + return; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Use Synced points if the MainParam is on auto update mode + // Use Cached points if the Mainparam is on manual update mode + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + if (Param->IsAutoUpdate() && bCookingEnabled) + { + TArray & Points = Param->Points; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (MainPoint->GetPosition() != Point->GetPosition()) + { + if (Point->PositionParentParm) + { + Point->SetPosition(MainPoint->GetPosition()); + Point->PositionParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetValue() != Point->GetValue()) + { + if (Point->ValueParentParm) + { + Point->SetValue(MainPoint->GetValue()); + Point->ValueParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetInterpolation() != Point->GetInterpolation()) + { + if (Point->InterpolationParentParm) + { + Point->SetInterpolation(MainPoint->GetInterpolation()); + Point->InterpolationParentParm->MarkChanged(true); + } + } + + PointIdx += 1; + } + + int32 PointInsertIdx = PointIdx; + int32 PointDeleteIdx = PointIdx; + + // skip the pending modification events + for (auto & Event : Param->ModificationEvents) + { + if (!Event) + continue; + + if (Event->IsInsertEvent()) + PointInsertIdx += 1; + + if (Event->IsDeleteEvent()) + PointDeleteIdx += 1; + } + + // There are more points in MainPoints array + for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) + { + UHoudiniParameterRampFloatPoint*& NextMainPoint = MainPoints[PointInsertIdx]; + + if (!NextMainPoint) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(Param, + NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); + + Param->MarkChanged(true); + } + + // There are more points in Points array + for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) + { + UHoudiniParameterRampFloatPoint*& NextPoint = Points[PointDeleteIdx]; + + if (!NextPoint) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); + + Param->MarkChanged(true); + } + + } + else + { + TArray &Points = Param->CachedPoints; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (Point->Position != MainPoint->Position) + { + Point->Position = MainPoint->Position; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->PositionParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Value != MainPoint->Value) + { + Point->Value = MainPoint->Value; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->ValueParentParm) + Point->ValueParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Interpolation != MainPoint->Interpolation) + { + Point->Interpolation = MainPoint->Interpolation; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->InterpolationParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + PointIdx += 1; + } + + // There are more points in MainPoints array + for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) + { + UHoudiniParameterRampFloatPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; + + if (!NextMainPoint) + continue; + + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampFloatPoint::StaticClass()); + + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = NextMainPoint->GetPosition(); + NewCachedPoint->Value = NextMainPoint->GetValue(); + NewCachedPoint->Interpolation = NextMainPoint->GetInterpolation(); + + Points.Add(NewCachedPoint); + + Param->bCaching = true; + } + + // there are more points in Points array + for (int32 PointsLeftIdx = PointIdx; PointIdx < Points.Num(); ++PointIdx) + { + Points.Pop(); + Param->bCaching = true; + } + } + +} + + +void +FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters) +{ + if (ColorRampParameters.Num() < 1) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0]; + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) + { + UHoudiniParameterRampColor* NextColorRampParam = ColorRampParameters[Idx]; + + if (!NextColorRampParam) + continue; + + FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParam); + } +} + +void +FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters) +{ + if (ColorRampParameters.Num() < 1) + return; + + if (!ColorRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) + { + if (!ColorRampParameters[Idx].IsValid()) + continue; + + UHoudiniParameterRampColor* NextColorRampParameter = ColorRampParameters[Idx].Get(); + + if (!NextColorRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParameter); + + } + +} + +void +FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParam) +{ + if (!Param || !MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) + return; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Use Synced points if the MainParam is on auto update mode + // Use Cached points if the Mainparam is on manual update mode + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + if (Param->IsAutoUpdate() && bCookingEnabled) + { + TArray & Points = Param->Points; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (MainPoint->GetPosition() != Point->GetPosition()) + { + if (Point->PositionParentParm) + { + Point->SetPosition(MainPoint->GetPosition()); + Point->PositionParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetValue() != Point->GetValue()) + { + if (Point->ValueParentParm) + { + Point->SetValue(MainPoint->GetValue()); + Point->ValueParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetInterpolation() != Point->GetInterpolation()) + { + if (Point->InterpolationParentParm) + { + Point->SetInterpolation(MainPoint->GetInterpolation()); + Point->InterpolationParentParm->MarkChanged(true); + } + } + + PointIdx += 1; + + } + + int32 PointInsertIdx = PointIdx; + int32 PointDeleteIdx = PointIdx; + + // skip the pending modification events + for (auto & Event : Param->ModificationEvents) + { + if (!Event) + continue; + + if (Event->IsInsertEvent()) + PointInsertIdx += 1; + + if (Event->IsDeleteEvent()) + PointDeleteIdx += 1; + } + + // There are more points in MainPoints array + for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) + { + UHoudiniParameterRampColorPoint*& NextMainPoint = MainPoints[PointInsertIdx]; + + if (!NextMainPoint) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(Param, + NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); + + Param->MarkChanged(true); + } + + // There are more points in Points array + for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) + { + UHoudiniParameterRampColorPoint*& NextPoint = Points[PointDeleteIdx]; + + if (!NextPoint) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); + + Param->MarkChanged(true); + } + } + else + { + TArray &Points = Param->CachedPoints; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (Point->Position != MainPoint->Position) + { + Point->Position = MainPoint->Position; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->PositionParentParm) + Point->PositionParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Value != MainPoint->Value) + { + Point->Value = MainPoint->Value; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->ValueParentParm) + Point->ValueParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Interpolation != MainPoint->Interpolation) + { + Point->Interpolation = MainPoint->Interpolation; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->InterpolationParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + PointIdx += 1; + } + + // There are more points in Main Points array. + for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) + { + UHoudiniParameterRampColorPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; + + if (!NextMainPoint) + continue; + + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampColorPoint::StaticClass()); + + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = NextMainPoint->Position; + NewCachedPoint->Value = NextMainPoint->Value; + NewCachedPoint->Interpolation = NextMainPoint->Interpolation; + + Points.Add(NewCachedPoint); + + Param->bCaching = true; + } + + // There are more points in Points array + for (int32 PointsleftIdx = PointIdx; PointIdx < MainPoints.Num(); ++PointsleftIdx) + { + Points.Pop(); + + Param->bCaching = true; + } + } +} + +// Check recussively if a parameter hits the end of a visible tabs +void +FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + // When the paramId is invalid, the directory won't parse. + // So simply return the function + if (InParam->GetParmId() < 0) + return; + + // Do not end the tab if this param is a non empty parent type, leave it to its children + EHoudiniParameterType ParmType = InParam->GetParameterType(); + if ((ParmType == EHoudiniParameterType::FolderList || + ParmType == EHoudiniParameterType::Folder) && InParam->GetTupleSize() > 0) + return; + + if (ParmType == EHoudiniParameterType::MultiParm) + { + UHoudiniParameterMultiParm * InMultiParm = Cast(InParam); + if (!InMultiParm) + return; + + if (InMultiParm->MultiParmInstanceCount * InMultiParm->MultiParmInstanceLength > 0) + return; + } + + int32 ParentParamId = InParam->GetParentParmId(); + UHoudiniParameter* CurParam = InParam; + + while (AllFoldersAndFolderLists.Contains(ParentParamId) || AllMultiParms.Contains(ParentParamId)) + { + // The parent is a multiparm + if (AllMultiParms.Contains(ParentParamId)) + { + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentParamId]; + if (!ParentMultiParm || ParentMultiParm->IsPendingKill()) + return; + + if (ParentMultiParm->MultiParmInstanceCount * ParentMultiParm->MultiParmInstanceLength - 1 == CurParam->GetChildIndex()) + { + ParentParamId = ParentMultiParm->GetParentParmId(); + CurParam = ParentMultiParm; + + continue; + } + else + { + // return directly if the parameter is not the last child param of the multiparm + return; + } + } + // The parent is a folder or folderlist + else + { + UHoudiniParameter* ParentFolderParam = AllFoldersAndFolderLists[ParentParamId]; + CurParam = ParentFolderParam; + + if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) + return; + + // The parent is a folder + if (ParentFolderParam->GetParameterType() == EHoudiniParameterType::Folder) + { + ParentParamId = ParentFolderParam->GetParentParmId(); + + continue; + } + // The parent is a folderlist + else + { + UHoudiniParameterFolderList* ParentFolderList = Cast(ParentFolderParam); + if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) + return; + + if (ParentFolderList->IsTabMenu() && ParentFolderList->IsTabsShown() && ParentFolderList->IsTabParseFinished() && DividerLinePositions.Num() > 0) + { + if (!CurrentTabEndingRow) + CreateTabEndingRow(HouParameterCategory); + + if (CurrentTabEndingRow) + { + CurrentTabEndingRow->EndingDividerLinePositions.Add(DividerLinePositions.Top()); + CurrentTabEndingRow->DividerLinePositions.Pop(); + } + + DividerLinePositions.Pop(); + + ParentParamId = ParentFolderList->GetParentParmId(); + } + else + { + return; + } + + } + + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h index f0f4662dd..a953fc74c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h @@ -1,484 +1,484 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" - -#include "CoreMinimal.h" - -#include "Widgets/Layout/SUniformGridPanel.h" -#include "SCurveEditor.h" -#include "Editor/CurveEditor/Public/CurveEditorSettings.h" -#include "HoudiniParameterTranslator.h" -#include "Curves/CurveFloat.h" -#include "SColorGradientEditor.h" -#include "Curves/CurveLinearColor.h" - -#include "Widgets/SBoxPanel.h" -#include "Widgets/Input/SButton.h" - -#include "HoudiniParameterDetails.generated.h" - -class UHoudiniAssetComponent; -class UHoudiniParameter; -class UHoudiniParameterFloat; -class UHoudiniParameterInt; -class UHoudiniParameterString; -class UHoudiniParameterColor; -class UHoudiniParameterButton; -class UHoudiniParameterButtonStrip; -class UHoudiniParameterLabel; -class UHoudiniParameterToggle; -class UHoudiniParameterFile; -class UHoudiniParameterChoice; -class UHoudiniParameterFolder; -class UHoudiniParameterFolderList; -class UHoudiniParameterMultiParm; -class UHoudiniParameterRampFloat; -class UHoudiniParameterRampColor; -class UHoudiniParameterOperatorPath; - -class UHoudiniParameterRampColorPoint; -class UHoudiniParameterRampFloatPoint; - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class SHorizontalBox; -class SHoudiniAssetParameterRampCurveEditor; - -enum class EHoudiniRampInterpolationType : int8; - -class SCustomizedButton : public SButton -{ -public: - bool bChosen; - - bool bIsRadioButton; - -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; - - // Construct the circles for all radio buttons. Initialize at first use - void ConstructRadioButtonCircles() const; - - void DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const; -}; - -class SCustomizedBox : public SHorizontalBox -{ -public: - bool bIsTabFolderListRow; - - bool bIsSeparator; - - TArray DividerLinePositions; - - TArray EndingDividerLinePositions; - - float MarginHeight; - -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; - - // Add indentation to current row, computed by tracing the directory hierarchy, - // return the indentation width of this parameter row. - float AddIndentation(UHoudiniParameter* InParam, TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists); - - void SetHoudiniParameter(TArray& InParams); -}; - -class SHoudiniFloatRampCurveEditor : public SCurveEditor -{ -public: - SLATE_BEGIN_ARGS(SHoudiniFloatRampCurveEditor) - : _ViewMinInput(0.0f) - , _ViewMaxInput(10.0f) - , _ViewMinOutput(0.0f) - , _ViewMaxOutput(1.0f) - , _InputSnap(0.1f) - , _OutputSnap(0.05f) - , _InputSnappingEnabled(false) - , _OutputSnappingEnabled(false) - , _ShowTimeInFrames(false) - , _TimelineLength(5.0f) - , _DesiredSize(FVector2D::ZeroVector) - , _DrawCurve(true) - , _HideUI(true) - , _AllowZoomOutput(true) - , _AlwaysDisplayColorCurves(false) - , _ZoomToFitVertical(true) - , _ZoomToFitHorizontal(true) - , _ShowZoomButtons(true) - , _XAxisName() - , _YAxisName() - , _ShowInputGridNumbers(true) - , _ShowOutputGridNumbers(true) - , _ShowCurveSelector(true) - , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) - { - _Clipping = EWidgetClipping::ClipToBounds; - } - - SLATE_ATTRIBUTE(float, ViewMinInput) - SLATE_ATTRIBUTE(float, ViewMaxInput) - SLATE_ATTRIBUTE(TOptional, DataMinInput) - SLATE_ATTRIBUTE(TOptional, DataMaxInput) - SLATE_ATTRIBUTE(float, ViewMinOutput) - SLATE_ATTRIBUTE(float, ViewMaxOutput) - SLATE_ATTRIBUTE(float, InputSnap) - SLATE_ATTRIBUTE(float, OutputSnap) - SLATE_ATTRIBUTE(bool, InputSnappingEnabled) - SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) - SLATE_ATTRIBUTE(bool, ShowTimeInFrames) - SLATE_ATTRIBUTE(float, TimelineLength) - SLATE_ATTRIBUTE(FVector2D, DesiredSize) - SLATE_ATTRIBUTE(bool, AreCurvesVisible) - SLATE_ARGUMENT(bool, DrawCurve) - SLATE_ARGUMENT(bool, HideUI) - SLATE_ARGUMENT(bool, AllowZoomOutput) - SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) - SLATE_ARGUMENT(bool, ZoomToFitVertical) - SLATE_ARGUMENT(bool, ZoomToFitHorizontal) - SLATE_ARGUMENT(bool, ShowZoomButtons) - SLATE_ARGUMENT(TOptional, XAxisName) - SLATE_ARGUMENT(TOptional, YAxisName) - SLATE_ARGUMENT(bool, ShowInputGridNumbers) - SLATE_ARGUMENT(bool, ShowOutputGridNumbers) - SLATE_ARGUMENT(bool, ShowCurveSelector) - SLATE_ARGUMENT(FLinearColor, GridColor) - SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) - SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) - SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) - SLATE_EVENT(FSimpleDelegate, OnCreateAsset) - SLATE_END_ARGS() - - public: - TWeakObjectPtr HoudiniFloatRampCurve; - - /** Widget construction. **/ - void Construct(const FArguments & InArgs); - - virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; - -}; - - -class SHoudiniColorRampCurveEditor : public SColorGradientEditor -{ - -public: - SLATE_BEGIN_ARGS(SHoudiniColorRampCurveEditor) - : _ViewMinInput(0.0f) - , _ViewMaxInput(10.0f) - , _InputSnap(0.1f) - , _OutputSnap(0.05f) - , _InputSnappingEnabled(false) - , _OutputSnappingEnabled(false) - , _ShowTimeInFrames(false) - , _TimelineLength(5.0f) - , _DesiredSize(FVector2D::ZeroVector) - , _DrawCurve(true) - , _HideUI(true) - , _AllowZoomOutput(true) - , _AlwaysDisplayColorCurves(false) - , _ZoomToFitVertical(true) - , _ZoomToFitHorizontal(true) - , _ShowZoomButtons(true) - , _XAxisName() - , _YAxisName() - , _ShowInputGridNumbers(true) - , _ShowOutputGridNumbers(true) - , _ShowCurveSelector(true) - , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) - { - _Clipping = EWidgetClipping::ClipToBounds; - } - - SLATE_ATTRIBUTE(float, ViewMinInput) - SLATE_ATTRIBUTE(float, ViewMaxInput) - SLATE_ATTRIBUTE(TOptional, DataMinInput) - SLATE_ATTRIBUTE(TOptional, DataMaxInput) - SLATE_ATTRIBUTE(float, InputSnap) - SLATE_ATTRIBUTE(float, OutputSnap) - SLATE_ATTRIBUTE(bool, InputSnappingEnabled) - SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) - SLATE_ATTRIBUTE(bool, ShowTimeInFrames) - SLATE_ATTRIBUTE(float, TimelineLength) - SLATE_ATTRIBUTE(FVector2D, DesiredSize) - SLATE_ATTRIBUTE(bool, AreCurvesVisible) - SLATE_ARGUMENT(bool, DrawCurve) - SLATE_ARGUMENT(bool, HideUI) - SLATE_ARGUMENT(bool, AllowZoomOutput) - SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) - SLATE_ARGUMENT(bool, ZoomToFitVertical) - SLATE_ARGUMENT(bool, ZoomToFitHorizontal) - SLATE_ARGUMENT(bool, ShowZoomButtons) - SLATE_ARGUMENT(TOptional, XAxisName) - SLATE_ARGUMENT(TOptional, YAxisName) - SLATE_ARGUMENT(bool, ShowInputGridNumbers) - SLATE_ARGUMENT(bool, ShowOutputGridNumbers) - SLATE_ARGUMENT(bool, ShowCurveSelector) - SLATE_ARGUMENT(FLinearColor, GridColor) - SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) - SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) - SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) - SLATE_EVENT(FSimpleDelegate, OnCreateAsset) - SLATE_END_ARGS() - - public: - /** Widget construction. **/ - void Construct(const FArguments & InArgs); - - TWeakObjectPtr HoudiniColorRampCurve; - - virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; -}; - -UCLASS() -class UHoudiniFloatRampCurve : public UCurveFloat -{ - GENERATED_BODY() - - public: - - TArray> FloatRampParameters; - - virtual void OnCurveChanged(const TArray& ChangedCurveEditInfos) override; -}; - - -UCLASS() -class UHoudiniColorRampCurve : public UCurveLinearColor -{ - GENERATED_BODY() - - public: - bool bEditing = false; - - TArray> ColorRampParameters; - - virtual void OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) override; - - void OnColorRampCurveChanged(bool bModificationOnly = false); - -}; - - -//class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface -class FHoudiniParameterDetails : public TSharedFromThis -{ - public: - void CreateWidget( - IDetailCategoryBuilder & HouParameterCategory, - TArray &InParams); - - void CreateWidgetInt( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetFloat( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetString( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetColor( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetButton( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetButtonStrip( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetLabel( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetToggle( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFile( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetChoice( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetSeparator( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled); - void CreateWidgetFolderList( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFolder( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetMultiParm( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetOperatorPath( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFloatRamp( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetColorRamp( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - - void CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory); - - - void HandleUnsupportedParmType( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams - ); - - - static FText GetParameterTooltip(UHoudiniParameter* InParam); - - static FString GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize); - - static void SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter); - - //static void SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter); - - // replace the children parameter values of all (multi-selected) float ramp parameters with the main parameter (weak object pointer version) - static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters); - // raw pointer version - static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters); - // helper - static void ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam); - - - // replace the children parameter values of all (multi-selected) color ramp parameters with the main parameter (weak object pointer version) - static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters); - // raw pointer version - static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters); - // helper - static void ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParame); - - - - // Create an insert event for a float ramp parameter - static void CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, - const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); - - // Create an insert event for a color ramp parameter - static void CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, - const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp); - - // Create a delete event for a float ramp parameter - static void CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex); - - // Create a delete event for a color ramp parameter - static void CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex); - - - private: - - template< class T > - static bool CastParameters( - TArray InParams, TArray& OutCastedParams); - - // - // Private helper functions for widget creation - // - - // Creates the default name widget, the parameter will then fill the value after - void CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); - - // Creates the default name widget, with an extra checkbox for disabling the the parameter update - void CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); - - FDetailWidgetRow* CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount = true); // - - void CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray& InParams); // - - void CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InParam, const bool& bIsShown); // - - void CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams); // - - // Create the UI for ramp's curve editor. - FDetailWidgetRow* CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); // - - // Create the UI for ramp's stop points. - void CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray< - UHoudiniParameter*>& InParams); // - - void PruneStack(); - - void RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam); - - public: - // Stores the created ramp curves - // In order to avoid being grabage collected, curves are added to root, thus need to handle GC manually. - // These points are for releasing the memory when the detail class are destroyed - TArray CreatedFloatRampCurves; - TArray CreatedColorRampCurves; - - private: - // The parameter directory is flattened with BFS inside of DFS. - // When a folderlist is encountered, it goes 'one step' of DFS, otherwise BFS. - // So that use a Stack structure to reconstruct the tree. - TArray> FolderStack; - - // Float Ramp currently being processed - UHoudiniParameterRampFloat* CurrentRampFloat; - - // Color Ramp currently being processed - UHoudiniParameterRampColor* CurrentRampColor; - - TArray CurrentRampParameterList; - - // Cached curve points of float ramp which being processed - TArray CurrentRampFloatPointsArray; - - // Cached curve points of color ramp which being processed - TArray CurrentRampColorPointsArray; - - // Cached color ramp curve which being processed - UHoudiniColorRampCurve* CurrentRampParameterColorCurve; - - // Cached float ramp curve which being processed - UHoudiniFloatRampCurve* CurrentRampParameterFloatCurve; - - FDetailWidgetRow * CurrentRampRow; - - - /* Variables for keeping expansion state after adding multiparm instance*/ - TMap AllMultiParms; - - // Cached the map of parameter id and folders/folder lists - TMap AllFoldersAndFolderLists; - - /* Variables for keeping expansion state after adding multiparm instance*/ - - TMap MultiParmInstanceIndices; - - // Number of remaining folders for current folder list - int32 CurrentFolderListSize = 0; - - // The folder list currently being processed - UHoudiniParameterFolderList* CurrentFolderList; - - // Cached child folders of current tabs - TArray CurrentTabs; - - TArray DividerLinePositions; - - SCustomizedBox* CurrentTabEndingRow; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" + +#include "CoreMinimal.h" + +#include "Widgets/Layout/SUniformGridPanel.h" +#include "SCurveEditor.h" +#include "Editor/CurveEditor/Public/CurveEditorSettings.h" +#include "HoudiniParameterTranslator.h" +#include "Curves/CurveFloat.h" +#include "SColorGradientEditor.h" +#include "Curves/CurveLinearColor.h" + +#include "Widgets/SBoxPanel.h" +#include "Widgets/Input/SButton.h" + +#include "HoudiniParameterDetails.generated.h" + +class UHoudiniAssetComponent; +class UHoudiniParameter; +class UHoudiniParameterFloat; +class UHoudiniParameterInt; +class UHoudiniParameterString; +class UHoudiniParameterColor; +class UHoudiniParameterButton; +class UHoudiniParameterButtonStrip; +class UHoudiniParameterLabel; +class UHoudiniParameterToggle; +class UHoudiniParameterFile; +class UHoudiniParameterChoice; +class UHoudiniParameterFolder; +class UHoudiniParameterFolderList; +class UHoudiniParameterMultiParm; +class UHoudiniParameterRampFloat; +class UHoudiniParameterRampColor; +class UHoudiniParameterOperatorPath; + +class UHoudiniParameterRampColorPoint; +class UHoudiniParameterRampFloatPoint; + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class SHorizontalBox; +class SHoudiniAssetParameterRampCurveEditor; + +enum class EHoudiniRampInterpolationType : int8; + +class SCustomizedButton : public SButton +{ +public: + bool bChosen; + + bool bIsRadioButton; + +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + // Construct the circles for all radio buttons. Initialize at first use + void ConstructRadioButtonCircles() const; + + void DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const; +}; + +class SCustomizedBox : public SHorizontalBox +{ +public: + bool bIsTabFolderListRow; + + bool bIsSeparator; + + TArray DividerLinePositions; + + TArray EndingDividerLinePositions; + + float MarginHeight; + +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + // Add indentation to current row, computed by tracing the directory hierarchy, + // return the indentation width of this parameter row. + float AddIndentation(UHoudiniParameter* InParam, TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists); + + void SetHoudiniParameter(TArray& InParams); +}; + +class SHoudiniFloatRampCurveEditor : public SCurveEditor +{ +public: + SLATE_BEGIN_ARGS(SHoudiniFloatRampCurveEditor) + : _ViewMinInput(0.0f) + , _ViewMaxInput(10.0f) + , _ViewMinOutput(0.0f) + , _ViewMaxOutput(1.0f) + , _InputSnap(0.1f) + , _OutputSnap(0.05f) + , _InputSnappingEnabled(false) + , _OutputSnappingEnabled(false) + , _ShowTimeInFrames(false) + , _TimelineLength(5.0f) + , _DesiredSize(FVector2D::ZeroVector) + , _DrawCurve(true) + , _HideUI(true) + , _AllowZoomOutput(true) + , _AlwaysDisplayColorCurves(false) + , _ZoomToFitVertical(true) + , _ZoomToFitHorizontal(true) + , _ShowZoomButtons(true) + , _XAxisName() + , _YAxisName() + , _ShowInputGridNumbers(true) + , _ShowOutputGridNumbers(true) + , _ShowCurveSelector(true) + , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + + SLATE_ATTRIBUTE(float, ViewMinInput) + SLATE_ATTRIBUTE(float, ViewMaxInput) + SLATE_ATTRIBUTE(TOptional, DataMinInput) + SLATE_ATTRIBUTE(TOptional, DataMaxInput) + SLATE_ATTRIBUTE(float, ViewMinOutput) + SLATE_ATTRIBUTE(float, ViewMaxOutput) + SLATE_ATTRIBUTE(float, InputSnap) + SLATE_ATTRIBUTE(float, OutputSnap) + SLATE_ATTRIBUTE(bool, InputSnappingEnabled) + SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) + SLATE_ATTRIBUTE(bool, ShowTimeInFrames) + SLATE_ATTRIBUTE(float, TimelineLength) + SLATE_ATTRIBUTE(FVector2D, DesiredSize) + SLATE_ATTRIBUTE(bool, AreCurvesVisible) + SLATE_ARGUMENT(bool, DrawCurve) + SLATE_ARGUMENT(bool, HideUI) + SLATE_ARGUMENT(bool, AllowZoomOutput) + SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) + SLATE_ARGUMENT(bool, ZoomToFitVertical) + SLATE_ARGUMENT(bool, ZoomToFitHorizontal) + SLATE_ARGUMENT(bool, ShowZoomButtons) + SLATE_ARGUMENT(TOptional, XAxisName) + SLATE_ARGUMENT(TOptional, YAxisName) + SLATE_ARGUMENT(bool, ShowInputGridNumbers) + SLATE_ARGUMENT(bool, ShowOutputGridNumbers) + SLATE_ARGUMENT(bool, ShowCurveSelector) + SLATE_ARGUMENT(FLinearColor, GridColor) + SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) + SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) + SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) + SLATE_EVENT(FSimpleDelegate, OnCreateAsset) + SLATE_END_ARGS() + + public: + TWeakObjectPtr HoudiniFloatRampCurve; + + /** Widget construction. **/ + void Construct(const FArguments & InArgs); + + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + +}; + + +class SHoudiniColorRampCurveEditor : public SColorGradientEditor +{ + +public: + SLATE_BEGIN_ARGS(SHoudiniColorRampCurveEditor) + : _ViewMinInput(0.0f) + , _ViewMaxInput(10.0f) + , _InputSnap(0.1f) + , _OutputSnap(0.05f) + , _InputSnappingEnabled(false) + , _OutputSnappingEnabled(false) + , _ShowTimeInFrames(false) + , _TimelineLength(5.0f) + , _DesiredSize(FVector2D::ZeroVector) + , _DrawCurve(true) + , _HideUI(true) + , _AllowZoomOutput(true) + , _AlwaysDisplayColorCurves(false) + , _ZoomToFitVertical(true) + , _ZoomToFitHorizontal(true) + , _ShowZoomButtons(true) + , _XAxisName() + , _YAxisName() + , _ShowInputGridNumbers(true) + , _ShowOutputGridNumbers(true) + , _ShowCurveSelector(true) + , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + + SLATE_ATTRIBUTE(float, ViewMinInput) + SLATE_ATTRIBUTE(float, ViewMaxInput) + SLATE_ATTRIBUTE(TOptional, DataMinInput) + SLATE_ATTRIBUTE(TOptional, DataMaxInput) + SLATE_ATTRIBUTE(float, InputSnap) + SLATE_ATTRIBUTE(float, OutputSnap) + SLATE_ATTRIBUTE(bool, InputSnappingEnabled) + SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) + SLATE_ATTRIBUTE(bool, ShowTimeInFrames) + SLATE_ATTRIBUTE(float, TimelineLength) + SLATE_ATTRIBUTE(FVector2D, DesiredSize) + SLATE_ATTRIBUTE(bool, AreCurvesVisible) + SLATE_ARGUMENT(bool, DrawCurve) + SLATE_ARGUMENT(bool, HideUI) + SLATE_ARGUMENT(bool, AllowZoomOutput) + SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) + SLATE_ARGUMENT(bool, ZoomToFitVertical) + SLATE_ARGUMENT(bool, ZoomToFitHorizontal) + SLATE_ARGUMENT(bool, ShowZoomButtons) + SLATE_ARGUMENT(TOptional, XAxisName) + SLATE_ARGUMENT(TOptional, YAxisName) + SLATE_ARGUMENT(bool, ShowInputGridNumbers) + SLATE_ARGUMENT(bool, ShowOutputGridNumbers) + SLATE_ARGUMENT(bool, ShowCurveSelector) + SLATE_ARGUMENT(FLinearColor, GridColor) + SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) + SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) + SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) + SLATE_EVENT(FSimpleDelegate, OnCreateAsset) + SLATE_END_ARGS() + + public: + /** Widget construction. **/ + void Construct(const FArguments & InArgs); + + TWeakObjectPtr HoudiniColorRampCurve; + + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; +}; + +UCLASS() +class UHoudiniFloatRampCurve : public UCurveFloat +{ + GENERATED_BODY() + + public: + + TArray> FloatRampParameters; + + virtual void OnCurveChanged(const TArray& ChangedCurveEditInfos) override; +}; + + +UCLASS() +class UHoudiniColorRampCurve : public UCurveLinearColor +{ + GENERATED_BODY() + + public: + bool bEditing = false; + + TArray> ColorRampParameters; + + virtual void OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) override; + + void OnColorRampCurveChanged(bool bModificationOnly = false); + +}; + + +//class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface +class FHoudiniParameterDetails : public TSharedFromThis +{ + public: + void CreateWidget( + IDetailCategoryBuilder & HouParameterCategory, + TArray &InParams); + + void CreateWidgetInt( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetFloat( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetString( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetColor( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetButton( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetButtonStrip( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetLabel( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetToggle( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFile( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetChoice( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetSeparator( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled); + void CreateWidgetFolderList( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFolder( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetMultiParm( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetOperatorPath( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFloatRamp( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetColorRamp( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + + void CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory); + + + void HandleUnsupportedParmType( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams + ); + + + static FText GetParameterTooltip(UHoudiniParameter* InParam); + + static FString GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize); + + static void SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter); + + //static void SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter); + + // replace the children parameter values of all (multi-selected) float ramp parameters with the main parameter (weak object pointer version) + static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters); + // raw pointer version + static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters); + // helper + static void ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam); + + + // replace the children parameter values of all (multi-selected) color ramp parameters with the main parameter (weak object pointer version) + static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters); + // raw pointer version + static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters); + // helper + static void ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParame); + + + + // Create an insert event for a float ramp parameter + static void CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, + const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); + + // Create an insert event for a color ramp parameter + static void CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, + const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp); + + // Create a delete event for a float ramp parameter + static void CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex); + + // Create a delete event for a color ramp parameter + static void CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex); + + + private: + + template< class T > + static bool CastParameters( + TArray InParams, TArray& OutCastedParams); + + // + // Private helper functions for widget creation + // + + // Creates the default name widget, the parameter will then fill the value after + void CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); + + // Creates the default name widget, with an extra checkbox for disabling the the parameter update + void CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); + + FDetailWidgetRow* CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount = true); // + + void CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray& InParams); // + + void CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InParam, const bool& bIsShown); // + + void CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams); // + + // Create the UI for ramp's curve editor. + FDetailWidgetRow* CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); // + + // Create the UI for ramp's stop points. + void CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray< + UHoudiniParameter*>& InParams); // + + void PruneStack(); + + void RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam); + + public: + // Stores the created ramp curves + // In order to avoid being grabage collected, curves are added to root, thus need to handle GC manually. + // These points are for releasing the memory when the detail class are destroyed + TArray CreatedFloatRampCurves; + TArray CreatedColorRampCurves; + + private: + // The parameter directory is flattened with BFS inside of DFS. + // When a folderlist is encountered, it goes 'one step' of DFS, otherwise BFS. + // So that use a Stack structure to reconstruct the tree. + TArray> FolderStack; + + // Float Ramp currently being processed + UHoudiniParameterRampFloat* CurrentRampFloat; + + // Color Ramp currently being processed + UHoudiniParameterRampColor* CurrentRampColor; + + TArray CurrentRampParameterList; + + // Cached curve points of float ramp which being processed + TArray CurrentRampFloatPointsArray; + + // Cached curve points of color ramp which being processed + TArray CurrentRampColorPointsArray; + + // Cached color ramp curve which being processed + UHoudiniColorRampCurve* CurrentRampParameterColorCurve; + + // Cached float ramp curve which being processed + UHoudiniFloatRampCurve* CurrentRampParameterFloatCurve; + + FDetailWidgetRow * CurrentRampRow; + + + /* Variables for keeping expansion state after adding multiparm instance*/ + TMap AllMultiParms; + + // Cached the map of parameter id and folders/folder lists + TMap AllFoldersAndFolderLists; + + /* Variables for keeping expansion state after adding multiparm instance*/ + + TMap MultiParmInstanceIndices; + + // Number of remaining folders for current folder list + int32 CurrentFolderListSize = 0; + + // The folder list currently being processed + UHoudiniParameterFolderList* CurrentFolderList; + + // Cached child folders of current tabs + TArray CurrentTabs; + + TArray DividerLinePositions; + + SCustomizedBox* CurrentTabEndingRow; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp index fbba21eb9..b5791424f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp @@ -1,323 +1,323 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniRuntimeSettingsDetails.h" - -#include "HoudiniApi.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimeUtils.h" - -#include "HAPI/HAPI_Version.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "Internationalization/Internationalization.h" -#include "DetailLayoutBuilder.h" -#include "DetailWidgetRow.h" -#include "Widgets/Input/SNumericEntryBox.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -TSharedRef< IDetailCustomization > -FHoudiniRuntimeSettingsDetails::MakeInstance() -{ - return MakeShareable(new FHoudiniRuntimeSettingsDetails); -} - -FHoudiniRuntimeSettingsDetails::FHoudiniRuntimeSettingsDetails() -{} - -FHoudiniRuntimeSettingsDetails::~FHoudiniRuntimeSettingsDetails() -{} - -void -FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) -{ - // Create basic categories. - DetailBuilder.EditCategory("Session", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Instantiating", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Cooking", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Parameters", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("PDGSettings", FText::GetEmpty(), ECategoryPriority::Important); - - // Create Plugin Information category. - { - static const FName InformationCategory = TEXT("Plugin Information"); - IDetailCategoryBuilder & InformationCategoryBuilder = DetailBuilder.EditCategory(InformationCategory); - - // Add built Houdini version. - CreateHoudiniEntry( - LOCTEXT("HInformationBuilt", "Built against Houdini"), - InformationCategoryBuilder, HAPI_VERSION_HOUDINI_MAJOR, HAPI_VERSION_HOUDINI_MINOR, - HAPI_VERSION_HOUDINI_BUILD, HAPI_VERSION_HOUDINI_PATCH); - - // Add built against Houdini Engine version. - CreateHoudiniEngineEntry( - LOCTEXT("HEngineInformationBuilt", "Built against Houdini Engine"), - InformationCategoryBuilder, HAPI_VERSION_HOUDINI_ENGINE_MAJOR, - HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API); - - // Add running against Houdini version. - { - int32 RunningMajor = 0; - int32 RunningMinor = 0; - int32 RunningBuild = 0; - int32 RunningPatch = 0; - - if (FHoudiniApi::IsHAPIInitialized()) - { - const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); - // Retrieve version numbers for running Houdini. - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MAJOR, &RunningMajor); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MINOR, &RunningMinor); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_BUILD, &RunningBuild); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_PATCH, &RunningPatch); - } - - CreateHoudiniEntry( - LOCTEXT("HInformationRunning", "Running against Houdini"), - InformationCategoryBuilder, RunningMajor, RunningMinor, RunningBuild, RunningPatch); - } - - // Add running against Houdini Engine version. - { - int32 RunningEngineMajor = 0; - int32 RunningEngineMinor = 0; - int32 RunningEngineApi = 0; - - if (FHoudiniApi::IsHAPIInitialized()) - { - 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); - } - - CreateHoudiniEngineEntry( - LOCTEXT("HEngineInformationRunning", "Running against Houdini Engine"), - InformationCategoryBuilder, RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - } - - // Add path of libHAPI. - { - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - if (LibHAPILocation.IsEmpty()) - LibHAPILocation = TEXT("Not Found"); - - CreateHAPILibraryPathEntry(LibHAPILocation, InformationCategoryBuilder); - } - - // Add licensing info. - { - FString HAPILicenseType = TEXT(""); - if (!FHoudiniEngineUtils::GetLicenseType(HAPILicenseType)) - HAPILicenseType = TEXT("Unknown"); - - CreateHAPILicenseEntry(HAPILicenseType, InformationCategoryBuilder); - } - } - - DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHoudiniEntry( - const FText & EntryName, - IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, - int32 VersionPatch) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(EntryName) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMajor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMinor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionBuild) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionPatch) - ]; - NumericEntryBox->SetEnabled(false); - } - - Row.ValueWidget.Widget = HorizontalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHoudiniEngineEntry( - const FText & EntryName, - IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionApi) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(EntryName) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMajor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMinor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionApi) - ]; - NumericEntryBox->SetEnabled(false); - } - - Row.ValueWidget.Widget = HorizontalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHAPILibraryPathEntry( - const FString & LibHAPIPath, - IDetailCategoryBuilder & DetailCategoryBuilder) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - FString LibHAPIName = FString::Printf(TEXT("Location of %s"), *FHoudiniEngineRuntimeUtils::GetLibHAPIName()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(FText::FromString(LibHAPIName)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef TextBlock = - SNew(STextBlock) - .Text(FText::FromString(LibHAPIPath)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TextBlock->SetEnabled(false); - Row.ValueWidget.Widget = TextBlock; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHAPILicenseEntry( - const FString & LibHAPILicense, - IDetailCategoryBuilder & DetailCategoryBuilder) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - FString LibHAPILicenseTypeText = TEXT("Acquired License Type"); - - Row.NameWidget.Widget = SNew(STextBlock) - .Text(FText::FromString(LibHAPILicenseTypeText)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef TextBlock = SNew(STextBlock) - .Text(FText::FromString(LibHAPILicense)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TextBlock->SetEnabled(false); - Row.ValueWidget.Widget = TextBlock; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniRuntimeSettingsDetails.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimeUtils.h" + +#include "HAPI/HAPI_Version.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "Internationalization/Internationalization.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SNumericEntryBox.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +TSharedRef< IDetailCustomization > +FHoudiniRuntimeSettingsDetails::MakeInstance() +{ + return MakeShareable(new FHoudiniRuntimeSettingsDetails); +} + +FHoudiniRuntimeSettingsDetails::FHoudiniRuntimeSettingsDetails() +{} + +FHoudiniRuntimeSettingsDetails::~FHoudiniRuntimeSettingsDetails() +{} + +void +FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) +{ + // Create basic categories. + DetailBuilder.EditCategory("Session", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Instantiating", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Cooking", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Parameters", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("PDGSettings", FText::GetEmpty(), ECategoryPriority::Important); + + // Create Plugin Information category. + { + static const FName InformationCategory = TEXT("Plugin Information"); + IDetailCategoryBuilder & InformationCategoryBuilder = DetailBuilder.EditCategory(InformationCategory); + + // Add built Houdini version. + CreateHoudiniEntry( + LOCTEXT("HInformationBuilt", "Built against Houdini"), + InformationCategoryBuilder, HAPI_VERSION_HOUDINI_MAJOR, HAPI_VERSION_HOUDINI_MINOR, + HAPI_VERSION_HOUDINI_BUILD, HAPI_VERSION_HOUDINI_PATCH); + + // Add built against Houdini Engine version. + CreateHoudiniEngineEntry( + LOCTEXT("HEngineInformationBuilt", "Built against Houdini Engine"), + InformationCategoryBuilder, HAPI_VERSION_HOUDINI_ENGINE_MAJOR, + HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API); + + // Add running against Houdini version. + { + int32 RunningMajor = 0; + int32 RunningMinor = 0; + int32 RunningBuild = 0; + int32 RunningPatch = 0; + + if (FHoudiniApi::IsHAPIInitialized()) + { + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + // Retrieve version numbers for running Houdini. + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MAJOR, &RunningMajor); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MINOR, &RunningMinor); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_BUILD, &RunningBuild); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_PATCH, &RunningPatch); + } + + CreateHoudiniEntry( + LOCTEXT("HInformationRunning", "Running against Houdini"), + InformationCategoryBuilder, RunningMajor, RunningMinor, RunningBuild, RunningPatch); + } + + // Add running against Houdini Engine version. + { + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + if (FHoudiniApi::IsHAPIInitialized()) + { + 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); + } + + CreateHoudiniEngineEntry( + LOCTEXT("HEngineInformationRunning", "Running against Houdini Engine"), + InformationCategoryBuilder, RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + } + + // Add path of libHAPI. + { + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + if (LibHAPILocation.IsEmpty()) + LibHAPILocation = TEXT("Not Found"); + + CreateHAPILibraryPathEntry(LibHAPILocation, InformationCategoryBuilder); + } + + // Add licensing info. + { + FString HAPILicenseType = TEXT(""); + if (!FHoudiniEngineUtils::GetLicenseType(HAPILicenseType)) + HAPILicenseType = TEXT("Unknown"); + + CreateHAPILicenseEntry(HAPILicenseType, InformationCategoryBuilder); + } + } + + DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHoudiniEntry( + const FText & EntryName, + IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, + int32 VersionPatch) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(EntryName) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMajor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMinor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionBuild) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionPatch) + ]; + NumericEntryBox->SetEnabled(false); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHoudiniEngineEntry( + const FText & EntryName, + IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionApi) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(EntryName) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMajor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMinor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionApi) + ]; + NumericEntryBox->SetEnabled(false); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHAPILibraryPathEntry( + const FString & LibHAPIPath, + IDetailCategoryBuilder & DetailCategoryBuilder) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + FString LibHAPIName = FString::Printf(TEXT("Location of %s"), *FHoudiniEngineRuntimeUtils::GetLibHAPIName()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(FText::FromString(LibHAPIName)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef TextBlock = + SNew(STextBlock) + .Text(FText::FromString(LibHAPIPath)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TextBlock->SetEnabled(false); + Row.ValueWidget.Widget = TextBlock; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHAPILicenseEntry( + const FString & LibHAPILicense, + IDetailCategoryBuilder & DetailCategoryBuilder) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + FString LibHAPILicenseTypeText = TEXT("Acquired License Type"); + + Row.NameWidget.Widget = SNew(STextBlock) + .Text(FText::FromString(LibHAPILicenseTypeText)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef TextBlock = SNew(STextBlock) + .Text(FText::FromString(LibHAPILicense)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TextBlock->SetEnabled(false); + Row.ValueWidget.Widget = TextBlock; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h index c0372513e..1123bbdcf 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "DetailCategoryBuilder.h" -#include "IDetailCustomization.h" - -class FHoudiniRuntimeSettingsDetails : public IDetailCustomization -{ -public: - - /** Constructor. **/ - FHoudiniRuntimeSettingsDetails(); - - /** Destructor. **/ - virtual ~FHoudiniRuntimeSettingsDetails(); - - /** IDetailCustomization methods. **/ -public: - - virtual void CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) override; - -public: - - /** Create an instance of this detail layout class. **/ - static TSharedRef< IDetailCustomization > MakeInstance(); - -protected: - - /** Used to create Houdini version entry. **/ - void CreateHoudiniEntry( - const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, int32 VersionPatch); - - /** Used to create Houdini Engine version entry. **/ - void CreateHoudiniEngineEntry( - const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionApi); - - /** Used to create libHAPI dynamic library path entry. **/ - void CreateHAPILibraryPathEntry( - const FString & LibHAPIPath, IDetailCategoryBuilder & DetailCategoryBuilder); - - /** Used to create libHAPI license information entry. **/ - void CreateHAPILicenseEntry( - const FString & LibHAPILicense, IDetailCategoryBuilder & DetailCategoryBuilder); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" + +class FHoudiniRuntimeSettingsDetails : public IDetailCustomization +{ +public: + + /** Constructor. **/ + FHoudiniRuntimeSettingsDetails(); + + /** Destructor. **/ + virtual ~FHoudiniRuntimeSettingsDetails(); + + /** IDetailCustomization methods. **/ +public: + + virtual void CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) override; + +public: + + /** Create an instance of this detail layout class. **/ + static TSharedRef< IDetailCustomization > MakeInstance(); + +protected: + + /** Used to create Houdini version entry. **/ + void CreateHoudiniEntry( + const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, int32 VersionPatch); + + /** Used to create Houdini Engine version entry. **/ + void CreateHoudiniEngineEntry( + const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionApi); + + /** Used to create libHAPI dynamic library path entry. **/ + void CreateHAPILibraryPathEntry( + const FString & LibHAPIPath, IDetailCategoryBuilder & DetailCategoryBuilder); + + /** Used to create libHAPI license information entry. **/ + void CreateHAPILicenseEntry( + const FString & LibHAPILicense, IDetailCategoryBuilder & DetailCategoryBuilder); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp index 66f772465..d6a8cbc6f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp @@ -1,1033 +1,1033 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineComponentVisualizer.h" - -#include "ActorEditorUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniApi.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInputObject.h" -#include "HoudiniInput.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniEngineUtils.h" - -#include "Editor/UnrealEdEngine.h" -#include "UnrealEdGlobals.h" -#include "ComponentVisualizerManager.h" - -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "ScopedTransaction.h" -#include "EditorViewportClient.h" -#include "Engine/Selection.h" -#include "HModel.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_HIT_PROXY(HHoudiniSplineVisProxy, HComponentVisProxy); -IMPLEMENT_HIT_PROXY(HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy); -IMPLEMENT_HIT_PROXY(HHoudiniSplineCurveSegmentVisProxy, HHoudiniSplineVisProxy); - -HHoudiniSplineVisProxy::HHoudiniSplineVisProxy(const UActorComponent * InComponent) - : HComponentVisProxy(InComponent, HPP_Wireframe) -{} - -HHoudiniSplineControlPointVisProxy::HHoudiniSplineControlPointVisProxy( - const UActorComponent * InComponent, int32 InControlPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , ControlPointIndex(InControlPointIndex) -{} - -HHoudiniSplineCurveSegmentVisProxy::HHoudiniSplineCurveSegmentVisProxy( - const UActorComponent * InComponent, int32 InDisplayPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , DisplayPointIndex(InDisplayPointIndex) -{} - -FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() - : TCommands< FHoudiniSplineComponentVisualizerCommands >( - "HoudiniSplineComponentVisualizer", - LOCTEXT("HoudiniSplineComponentVisualizer", "Houdini Spline Component Visualizer"), - NAME_None, - FEditorStyle::GetStyleSetName()) -{} - -void -FHoudiniSplineComponentVisualizerCommands::RegisterCommands() -{ - UI_COMMAND( - CommandAddControlPoint, "Add Control Point", "Add control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND( - CommandDuplicateControlPoint, "Duplicate Control Point", "Duplicate control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND( - CommandDeleteControlPoint, "Delete Control Point", "delete control points.", - EUserInterfaceActionType::Button, FInputChord(EKeys::Delete)); - - UI_COMMAND(CommandDeselectAllControlPoints, "Deselect All", "Deselect all control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(CommandInsertControlPoint, "Insert Control Point", "Insert a control point on curve.", - EUserInterfaceActionType::Button, FInputChord()); -} - - -FHoudiniSplineComponentVisualizer::FHoudiniSplineComponentVisualizer() - :FComponentVisualizer() - ,bAllowDuplication(false) - ,EditedCurveSegmentIndex(-1) - ,CachedRotation(FQuat::Identity) - ,CachedScale3D(FVector::OneVector) - ,bMovingPoints(false) - ,bInsertingOnCurveControlPoints(false) - ,bRecordingMovingPoints(false) -{ - FHoudiniSplineComponentVisualizerCommands::Register(); - VisualizerActions = MakeShareable(new FUICommandList); -} - -void -FHoudiniSplineComponentVisualizer::OnRegister() -{ - HOUDINI_LOG_MESSAGE(TEXT("Houdini Spline Component Visualizer Registered!")); - const auto & Commands = FHoudiniSplineComponentVisualizerCommands::Get(); - - VisualizerActions->MapAction( - Commands.CommandAddControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnAddControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsAddControlPointValid)); - - VisualizerActions->MapAction( - Commands.CommandDuplicateControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid)); - - VisualizerActions->MapAction( - Commands.CommandDeleteControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeleteControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid)); - - VisualizerActions->MapAction(Commands.CommandDeselectAllControlPoints, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid)); - - VisualizerActions->MapAction(Commands.CommandInsertControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnInsertControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsInsertControlPointValid)); -} - - -void -FHoudiniSplineComponentVisualizer::DrawVisualization( - const UActorComponent * Component, - const FSceneView * View, - FPrimitiveDrawInterface * PDI) -{ - const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >(Component); - - if (!HoudiniSplineComponent - || !PDI - || HoudiniSplineComponent->IsPendingKill() - || !HoudiniSplineComponent->IsVisible() - || !HoudiniSplineComponent->IsHoudiniSplineVisible()) - return; - - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - - // Note: Undo a transaction clears the active visualizer in ComponnetVisMangaer, which is private to Visualizer manager. - // HandleProxyForComponentVis() sets the active visualizer. So the selection will be lost after undo. - - // A Way to bypass this annoying UE4 implementation: - // If the drawing spline is the one being edited and an undo just happened, - // force to trigger a 'bubble' hit proxy to re-activate the visualizer. - if (HoudiniSplineComponent == EditedHoudiniSplineComponent && EditedHoudiniSplineComponent->bPostUndo) - { - EditedHoudiniSplineComponent->bPostUndo = false; - - FEditorViewportClient * FoundViewportClient = FindViewportClient(EditedHoudiniSplineComponent, View); - HComponentVisProxy * BubbleComponentHitProxy = new HComponentVisProxy(EditedHoudiniSplineComponent); - - if (FoundViewportClient && BubbleComponentHitProxy) - { - FViewportClick BubbleClick(View, FoundViewportClient, FKey(), EInputEvent::IE_Axis, 0, 0); - GUnrealEd->ComponentVisManager.HandleProxyForComponentVis(FoundViewportClient, BubbleComponentHitProxy, BubbleClick); - } - } - - static const FColor ColorNormal = FColor(255.f, 255.f, 255.f); - static const FColor ColorNormalHandleFirst(172.f, 255.f, 172.f); - static const FColor ColorNormalHandleSecond(254.f, 216.f, 177.f); - - static const FColor ColorSelectedHandle(255.f, 0.f, 0.f); - static const FColor ColorSelectedHandleFirst(0.f, 192.f, 0.f); - static const FColor ColorSelectedHandleSecond(255.f, 159.f, 0.f); - - static const float SizeGrabHandleSelected = 15.f; - static const float SizeGrabHandleNormalLarge = 18.f; - static const float SizeGrabHandleNormalSmall = 12.f; - - FVector PreviousPosition; - - if (HoudiniSplineComponent) - { - const FTransform & HoudiniSplineComponentTransform = HoudiniSplineComponent->GetComponentTransform(); - - const TArray< FVector > & DisplayPoints = HoudiniSplineComponent->DisplayPoints; // not used yet - const TArray< FTransform > & CurvePoints = HoudiniSplineComponent->CurvePoints; - - // Draw display points (simply linearly connect the control points for temporary) - for (int32 Index = 0; Index < DisplayPoints.Num(); ++Index) - { - const FVector & CurrentPoint = DisplayPoints[Index]; - FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); - //CurrentPosition = CurrentPoint; - if (Index > 0) - { - // Add a hitproxy for the line segment - PDI->SetHitProxy(new HHoudiniSplineCurveSegmentVisProxy(HoudiniSplineComponent, Index)); - // Draw a line connecting the previous point and the current point - PDI->DrawLine(PreviousPosition, CurrentPosition, ColorNormal, SDPG_Foreground); - PDI->SetHitProxy(nullptr); - } - - PreviousPosition = CurrentPosition; - } - - // Draw control points (do not draw control points if the curve is an output) - if (!HoudiniSplineComponent->bIsOutputCurve) - { - for (int32 Index = 0; Index < CurvePoints.Num(); ++Index) - { - const FVector & ControlPoint = HoudiniSplineComponentTransform.TransformPosition(CurvePoints[Index].GetLocation()); - - HHoudiniSplineControlPointVisProxy * HitProxy = new HHoudiniSplineControlPointVisProxy(HoudiniSplineComponent, Index); - PDI->SetHitProxy(HitProxy); - - FColor DrawColor = ColorNormal; - float DrawSize = SizeGrabHandleNormalSmall; - - if (Index == 0) - { - DrawColor = ColorNormalHandleFirst; - DrawSize = SizeGrabHandleNormalLarge; - } - - if (Index == 1) - DrawColor = ColorNormalHandleSecond; - - - // If this is an point that being editted - if (EditedHoudiniSplineComponent == HoudiniSplineComponent && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Contains(Index)) - { - if (Index == 0) - { - DrawColor = ColorSelectedHandleFirst; - } - - else if (Index == 1) - { - DrawColor = ColorSelectedHandleSecond; - DrawSize = SizeGrabHandleSelected; - } - - else - { - DrawColor = ColorSelectedHandle; - DrawSize = SizeGrabHandleSelected; - - } - } - - PDI->DrawPoint(ControlPoint, DrawColor, DrawSize, SDPG_Foreground); - PDI->SetHitProxy(nullptr); - } - - } - } -} - - -bool -FHoudiniSplineComponentVisualizer::VisProxyHandleClick( - FEditorViewportClient* InViewportClient, - HComponentVisProxy* VisProxy, - const FViewportClick& Click) -{ - if (!InViewportClient || !VisProxy || !VisProxy->Component.IsValid()) - return false; - - const UHoudiniSplineComponent * HoudiniSplineComponent = CastChecked< const UHoudiniSplineComponent >(VisProxy->Component.Get()); - - AActor* OldSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); - SplinePropertyPath = FComponentPropertyPath(HoudiniSplineComponent); - AActor* NewSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); - - if (!SplinePropertyPath.IsValid()) - { - SplinePropertyPath.Reset(); - return false; - } - - if (OldSplineOwningActor != NewSplineOwningActor) - { - // Reset selection state if we are selecting a different actor to the one previously selected - EditedCurveSegmentIndex = INDEX_NONE; - } - - // Note: This is for re-activating the component visualizer an undo. - // Return true if the hit proxy is a bubble (Neither HHoudiniSplineControlPointVisProxy nor HHoudiniSplineCurveSegmentVisProxy ) - // - if (!VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType()) && !VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) - return true; - - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - - EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); - - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - bool editingCurve = false; - - // If VisProxy is a HHoudiniSplineControlPointVisProxy - if (VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType())) - { - HHoudiniSplineControlPointVisProxy * ControlPointProxy = (HHoudiniSplineControlPointVisProxy*)VisProxy; - - if (!ControlPointProxy) - return editingCurve; - - editingCurve = true; - - // Clear the edited curve segment if a control point is clicked. - EditedCurveSegmentIndex = -1; - - if (Click.GetKey() != EKeys::LeftMouseButton) - return editingCurve; - - - if (InViewportClient->IsCtrlPressed()) - { - if (EditedControlPointsIndexes.Contains(ControlPointProxy->ControlPointIndex)) - { - EditedControlPointsIndexes.Remove(ControlPointProxy->ControlPointIndex); - } - else - { - EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); - } - } - else - { - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); - } - } - // VisProxy is a HHoudiniSplineCurveSegmentProxy - else if (VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) - { - //HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = Cast(VisProxy); - - HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = (HHoudiniSplineCurveSegmentVisProxy*)(VisProxy); - - if (!CurveSegmentProxy) - return false; - - editingCurve = true; - - if (Click.GetKey() == EKeys::LeftMouseButton && InViewportClient->IsAltPressed() && EditedHoudiniSplineComponent) - { - // Continuesly (Alt) inserting on-curve control points is only valid with Breakpoints mode, otherwise it has to be on linear curve type. - if (EditedHoudiniSplineComponent->CurveType != EHoudiniCurveType::Polygon && EditedHoudiniSplineComponent->CurveMethod != EHoudiniCurveMethod::Breakpoints) - return editingCurve; - - bInsertingOnCurveControlPoints = true; - - editingCurve = true; - EditedControlPointsIndexes.Empty(); - - EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; - int32 InsertedIndex = OnInsertControlPointWithoutUpdate(); - - if (InsertedIndex < 0) return false; - EditedControlPointsIndexes.Add(InsertedIndex); - - EditedCurveSegmentIndex = -1; - bInsertingOnCurveControlPoints = true; - - RefreshViewport(); - } - // Insert one on-curve control point. - else - { - EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; - return editingCurve; - } - } - - return editingCurve; -} - -bool -FHoudiniSplineComponentVisualizer::HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - if (Key == EKeys::Enter) - { - EditedHoudiniSplineComponent->MarkChanged(true); - - return true; - } - - bool bHandled = false; - - if (Key == EKeys::LeftMouseButton) - { - if (Event == IE_Pressed) - { - bMovingPoints = true; // Started moving points when the left mouse button is pressed - bAllowDuplication = true; - bRecordingMovingPoints = false; - } - - if (Event == IE_Released) - { - bMovingPoints = false; // Stopped moving points when the left mouse button is released - bAllowDuplication = false; - - if (bRecordingMovingPoints) - { - // Only mark the component as changed if a point was actually moved otherwise it will - // cook even if a point was selected. - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - } - - bRecordingMovingPoints = false; // allow recording pt moving again - } - } - - - if (Key == EKeys::Delete) - { - if (Event == IE_Pressed) return true; - - if (IsDeleteControlPointValid()) - { - OnDeleteControlPoint(); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - return true; - } - } - - - if (Event == IE_Pressed && VisualizerActions) - { - if (FSlateApplication::IsInitialized()) - bHandled = VisualizerActions->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), false); - } - - RefreshViewport(); - - return bHandled; -} - -void -FHoudiniSplineComponentVisualizer::EndEditing() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - // Clear edited spline if the EndEditing() function is not called from postUndo - if (!EditedHoudiniSplineComponent->bPostUndo) - { - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); - - EditedHoudiniSplineComponent = nullptr; - EditedCurveSegmentIndex = -1; - } - - //RefreshViewport(); -} - -bool -FHoudiniSplineComponentVisualizer::GetWidgetLocation( - const FEditorViewportClient* ViewportClient, - FVector& OutLocation) const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - // Set the widget location to the center of mass of the selected control points - FVector CenterLocation = FVector::ZeroVector; - - for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i) - { - CenterLocation += CurvePoints[EditedControlPointsIndexes[i]].GetLocation(); - } - - CenterLocation /= EditedControlPointsIndexes.Num(); - OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(CenterLocation); - - return true; -} - -bool -FHoudiniSplineComponentVisualizer::IsVisualizingArchetype() const -{ - UHoudiniSplineComponent* SplineComp = GetEditedHoudiniSplineComponent(); - return (SplineComp && SplineComp->GetOwner() && FActorEditorUtils::IsAPreviewOrInactiveActor(SplineComp->GetOwner())); -} - -bool -FHoudiniSplineComponentVisualizer::HandleInputDelta( - FEditorViewportClient* ViewportClient, - FViewport* Viewport, - FVector& DeltaTranslate, - FRotator& DeltaRotate, - FVector& DeltaScale) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!ViewportClient || !EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - if (ViewportClient->IsAltPressed() && bAllowDuplication) - { - OnDuplicateControlPoint(); - bAllowDuplication = false; - } - else - { - if (!bRecordingMovingPoints) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentMovingPointsTransaction", "Houdini Spline Component: Moving curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - - EditedHoudiniSplineComponent->Modify(); - - bRecordingMovingPoints = true; - } - } - - - TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - const FTransform & HoudiniSplineComponentTransform = EditedHoudiniSplineComponent->GetComponentTransform(); - - for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i ) - { - - FTransform CurrentPoint = EditedHoudiniSplineComponent->CurvePoints[EditedControlPointsIndexes[i]]; - - if (!DeltaTranslate.IsZero()) - { - FVector OldWorldPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint.GetLocation()); - FVector NewWorldPosition = OldWorldPosition + DeltaTranslate; - FVector NewLocalPosition = HoudiniSplineComponentTransform.InverseTransformPosition(NewWorldPosition); - CurrentPoint.SetLocation( NewLocalPosition ); - } - - if (!DeltaRotate.IsZero()) - { - FQuat OldWorldRotation = HoudiniSplineComponentTransform.GetRotation() * CurrentPoint.GetRotation(); - FQuat NewWorldRotation = DeltaRotate.Quaternion() * OldWorldRotation; - FQuat NewLocalRotation = HoudiniSplineComponentTransform.GetRotation().Inverse() * NewWorldRotation; - CurrentPoint.SetRotation(NewLocalRotation); - } - - if (!DeltaScale.IsZero()) - { - FVector NewScale = CurrentPoint.GetScale3D() * (FVector(1.f, 1.f, 1.f) + DeltaScale); - CurrentPoint.SetScale3D(NewScale); - } - - - EditedHoudiniSplineComponent->EditPointAtindex(CurrentPoint, EditedControlPointsIndexes[i]); - } - - RefreshViewport(); - - return true; -} - -TSharedPtr -FHoudiniSplineComponentVisualizer::GenerateContextMenu() const -{ - FHoudiniEngineEditor& HoudiniEngineEditor = FHoudiniEngineEditor::Get(); - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - FMenuBuilder MenuBuilder(true, VisualizerActions); - MenuBuilder.BeginSection("Houdini Spline actions"); - - // Create the context menu section - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - { - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, - NAME_None, TAttribute(), TAttribute(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDuplicateControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDeleteControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDeselectAllControlPoints, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandInsertControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - } - - MenuBuilder.EndSection(); - TSharedPtr MenuWidget = MenuBuilder.MakeWidget(); - return MenuWidget; -} - -// Used by alt-pressed on-curve control port insertion. -// We don't want it to be cooked before finishing editing. -// * Need to call WaitForHoudiniInputUpdate() after done. -int32 -FHoudiniSplineComponentVisualizer::OnInsertControlPointWithoutUpdate() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return -1; - - TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - TArray & DisplayPoints = EditedHoudiniSplineComponent->DisplayPoints; - - if (EditedCurveSegmentIndex >= DisplayPoints.Num()) - return -1; - - // ... // - int InsertAfterIndex = 0; - - TArray & DisplayPointIndexDivider = EditedHoudiniSplineComponent->DisplayPointIndexDivider; - for (int itr = 0; itr < DisplayPointIndexDivider.Num(); ++itr) - { - if (DisplayPointIndexDivider[itr] >= EditedCurveSegmentIndex) - { - InsertAfterIndex = itr; - break; - } - } - // ... // - - if (InsertAfterIndex >= CurvePoints.Num()) return -1; - - FTransform NewPoint = CurvePoints[InsertAfterIndex]; - NewPoint.SetLocation(DisplayPoints[EditedCurveSegmentIndex]); - // To Do: Should interpolate the rotation and scale as well here. - // ... - - // Insert new control point on curve, and add it to selected CP. - int32 NewPointIndex = AddControlPointAfter(NewPoint, InsertAfterIndex); - - // Don't have to reconstruct the index divider each time. - //EditedHoudiniSplineComponent->Construct(EditedHoudiniSplineComponent->DisplayPoints); - EditedHoudiniSplineComponent->DisplayPointIndexDivider.Insert(EditedCurveSegmentIndex, InsertAfterIndex); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - return NewPointIndex; -} - -void -FHoudiniSplineComponentVisualizer::OnInsertControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - int32 NewPointIndex = OnInsertControlPointWithoutUpdate(); - - if (NewPointIndex < 0) return; - - - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Add(NewPointIndex); - - RefreshViewport(); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); -} - -bool -FHoudiniSplineComponentVisualizer::IsInsertControlPointValid() const -{ - return EditedCurveSegmentIndex >= 0; -} - -void -FHoudiniSplineComponentVisualizer::OnAddControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentInsertingPointsTransaction", "Houdini Spline Component: Inserting curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - - EditedHoudiniSplineComponent->Modify(); - - EditedControlPointsIndexes.Sort(); - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - TArray tNewSelectedPoints; - - if (EditedControlPointsIndexes.Num() == 1) - { - FTransform Point = CurvePoints[EditedControlPointsIndexes[0]]; - FTransform NewTransform = FTransform::Identity; - FVector Location = Point.GetLocation(); - //FQuat Rotation = Point.GetRotation(); - //FVector Scale = Point.GetScale3D(); - - NewTransform.SetLocation(Location + 1.f); - //NewTransform.SetRotation(Rotation); - //NewTransform.SetScale3D(Scale); - - - - int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[0]); - tNewSelectedPoints.Add(NewPointIndex); - } - else - { - int IndexIncrement = 0; - int CurrentPointIndex, LastPointIndex; - FTransform CurrentPoint, LastPoint; - - for (int32 n = 0; n < EditedControlPointsIndexes.Num(); ++n) - { - // Insert a new point between each adjacent pair of points - if (n > 0) - { - CurrentPointIndex = EditedControlPointsIndexes[n]; - LastPointIndex = EditedControlPointsIndexes[n - 1]; - CurrentPoint = CurvePoints[CurrentPointIndex + IndexIncrement]; - LastPoint = CurvePoints[LastPointIndex + IndexIncrement]; - - // Insert a point in the middle of LastPoint and CurrentPoint - FVector NewPointLocation = LastPoint.GetLocation() + (CurrentPoint.GetLocation() - LastPoint.GetLocation()) / 2.f; - FVector NewPointScale = LastPoint.GetScale3D() + (CurrentPoint.GetScale3D() - LastPoint.GetScale3D()) / 2.f; - FQuat NewPointRotation = FQuat::Slerp(LastPoint.GetRotation(), CurrentPoint.GetRotation(), .5f); - - FTransform NewTransform = FTransform::Identity; - NewTransform.SetLocation(NewPointLocation); - NewTransform.SetScale3D(NewPointScale); - NewTransform.SetRotation(NewPointRotation); - - int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[n - 1] + IndexIncrement); - tNewSelectedPoints.Add(NewPointIndex); - - - IndexIncrement += 1; - } - } - } - - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes = tNewSelectedPoints; - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - RefreshViewport(); -} - - -bool -FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - return EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill() && - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; -} - -void -FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentDeletingPointsTransaction", "Houdini Spline Component: Deleting curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - EditedHoudiniSplineComponent->Modify(); - - EditedControlPointsIndexes.Sort(); - - int32 SelectedIndexAfterDelete = EditedControlPointsIndexes[0] - 1; - SelectedIndexAfterDelete = FMath::Max(SelectedIndexAfterDelete, 0); - - for (int32 n = EditedControlPointsIndexes.Num() - 1; n >= 0; --n) - { - int32 RemoveIndex = EditedControlPointsIndexes[n]; - EditedHoudiniSplineComponent->RemovePointAtIndex(RemoveIndex); - - } - - EditedControlPointsIndexes.Empty(); - OnDeselectAllControlPoints(); - EditedControlPointsIndexes.Add(SelectedIndexAfterDelete); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - // Force refresh the viewport after deleting points to ensure the consistency of HitProxy - RefreshViewport(); - -} - -bool -FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - // We only allow the number of Control Points is at least 2 after delete - if (EditedHoudiniSplineComponent->GetCurvePointCount() - EditedControlPointsIndexes.Num() < 2) - return false; - - return true; -} - -void -FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentDuplicatingPointsTransaction", "Houdini Spline Component: Duplicating curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - EditedHoudiniSplineComponent->Modify(); - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - EditedControlPointsIndexes.Sort(); - - TArray tNewSelectedPoints; - int IncrementIndex = 0; - for (int n = 0; n < EditedControlPointsIndexes.Num(); ++n) - { - int32 IndexAfter = EditedControlPointsIndexes[n] + IncrementIndex; - FTransform CurrentPoint = CurvePoints[IndexAfter]; - if (IndexAfter == 0) - IndexAfter = -1; - int32 NewPointIndex = AddControlPointAfter(CurrentPoint, IndexAfter); - tNewSelectedPoints.Add(NewPointIndex); - IncrementIndex ++; - } - - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes = tNewSelectedPoints; - - EditedHoudiniSplineComponent->MarkModified(true); - - RefreshViewport(); -} - -bool -FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if(!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill() - || EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() == 0) - return false; - - return true; -} - -void -FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); -} - -bool -FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - return EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; - - return false; -} - -int32 -FHoudiniSplineComponentVisualizer::AddControlPointAfter( - const FTransform & NewPoint, - const int32 & nIndex) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return nIndex; - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - int32 NewControlPointIndex = nIndex + 1; - - if (NewControlPointIndex == CurvePoints.Num()) - EditedHoudiniSplineComponent->AppendPoint(NewPoint); - else - EditedHoudiniSplineComponent->InsertPointAtIndex(NewPoint, NewControlPointIndex); - - // Return the index of the inserted control point - return NewControlPointIndex; -} - -void -FHoudiniSplineComponentVisualizer::RefreshViewport() -{ - if (GEditor) - GEditor->RedrawLevelEditingViewports(true); -} - -// Find the EditorViewportClient of the viewport where the Houdini Spline Component lives in -FEditorViewportClient * -FHoudiniSplineComponentVisualizer::FindViewportClient( - const UHoudiniSplineComponent * InHoudiniSplineComponent, - const FSceneView * View) -{ - if (!View || !InHoudiniSplineComponent) - return nullptr; - - UWorld * World = InHoudiniSplineComponent->GetWorld(); - uint32 ViewKey = View->GetViewKey(); - - const TArray & AllViewportClients = GUnrealEd->GetAllViewportClients(); - - for (auto & NextViewportClient : AllViewportClients) - { - if (!NextViewportClient) - continue; - - if (NextViewportClient->GetWorld() != World) - continue; - - // Found the viewport client which matches the unique key of the current scene view - if (NextViewportClient->ViewState.GetReference()->GetViewKey() == ViewKey) - return NextViewportClient; - } - - return nullptr; -} - -bool -FHoudiniSplineComponentVisualizer::IsCookOnCurveChanged(UHoudiniSplineComponent * InHoudiniSplineComponent) -{ - if (!InHoudiniSplineComponent) - return true; - - return InHoudiniSplineComponent->bCookOnCurveChanged; - - // UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - // UHoudiniInputObject * InputObject = Cast(EditedHoudiniSplineComponent->GetOuter()); - // if (!InputObject) - // return true; - // - // UHoudiniInput * Input = Cast(InputObject->GetOuter()); - // - // if (!Input) - // return true; - // - // return Input->GetCookOnCurveChange(); -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineComponentVisualizer.h" + +#include "ActorEditorUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniApi.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInputObject.h" +#include "HoudiniInput.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniEngineUtils.h" + +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" +#include "ComponentVisualizerManager.h" + +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "ScopedTransaction.h" +#include "EditorViewportClient.h" +#include "Engine/Selection.h" +#include "HModel.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_HIT_PROXY(HHoudiniSplineVisProxy, HComponentVisProxy); +IMPLEMENT_HIT_PROXY(HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy); +IMPLEMENT_HIT_PROXY(HHoudiniSplineCurveSegmentVisProxy, HHoudiniSplineVisProxy); + +HHoudiniSplineVisProxy::HHoudiniSplineVisProxy(const UActorComponent * InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) +{} + +HHoudiniSplineControlPointVisProxy::HHoudiniSplineControlPointVisProxy( + const UActorComponent * InComponent, int32 InControlPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , ControlPointIndex(InControlPointIndex) +{} + +HHoudiniSplineCurveSegmentVisProxy::HHoudiniSplineCurveSegmentVisProxy( + const UActorComponent * InComponent, int32 InDisplayPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , DisplayPointIndex(InDisplayPointIndex) +{} + +FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() + : TCommands< FHoudiniSplineComponentVisualizerCommands >( + "HoudiniSplineComponentVisualizer", + LOCTEXT("HoudiniSplineComponentVisualizer", "Houdini Spline Component Visualizer"), + NAME_None, + FEditorStyle::GetStyleSetName()) +{} + +void +FHoudiniSplineComponentVisualizerCommands::RegisterCommands() +{ + UI_COMMAND( + CommandAddControlPoint, "Add Control Point", "Add control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND( + CommandDuplicateControlPoint, "Duplicate Control Point", "Duplicate control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND( + CommandDeleteControlPoint, "Delete Control Point", "delete control points.", + EUserInterfaceActionType::Button, FInputChord(EKeys::Delete)); + + UI_COMMAND(CommandDeselectAllControlPoints, "Deselect All", "Deselect all control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(CommandInsertControlPoint, "Insert Control Point", "Insert a control point on curve.", + EUserInterfaceActionType::Button, FInputChord()); +} + + +FHoudiniSplineComponentVisualizer::FHoudiniSplineComponentVisualizer() + :FComponentVisualizer() + ,bAllowDuplication(false) + ,EditedCurveSegmentIndex(-1) + ,CachedRotation(FQuat::Identity) + ,CachedScale3D(FVector::OneVector) + ,bMovingPoints(false) + ,bInsertingOnCurveControlPoints(false) + ,bRecordingMovingPoints(false) +{ + FHoudiniSplineComponentVisualizerCommands::Register(); + VisualizerActions = MakeShareable(new FUICommandList); +} + +void +FHoudiniSplineComponentVisualizer::OnRegister() +{ + HOUDINI_LOG_MESSAGE(TEXT("Houdini Spline Component Visualizer Registered!")); + const auto & Commands = FHoudiniSplineComponentVisualizerCommands::Get(); + + VisualizerActions->MapAction( + Commands.CommandAddControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnAddControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsAddControlPointValid)); + + VisualizerActions->MapAction( + Commands.CommandDuplicateControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid)); + + VisualizerActions->MapAction( + Commands.CommandDeleteControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeleteControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid)); + + VisualizerActions->MapAction(Commands.CommandDeselectAllControlPoints, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid)); + + VisualizerActions->MapAction(Commands.CommandInsertControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnInsertControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsInsertControlPointValid)); +} + + +void +FHoudiniSplineComponentVisualizer::DrawVisualization( + const UActorComponent * Component, + const FSceneView * View, + FPrimitiveDrawInterface * PDI) +{ + const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >(Component); + + if (!HoudiniSplineComponent + || !PDI + || HoudiniSplineComponent->IsPendingKill() + || !HoudiniSplineComponent->IsVisible() + || !HoudiniSplineComponent->IsHoudiniSplineVisible()) + return; + + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + + // Note: Undo a transaction clears the active visualizer in ComponnetVisMangaer, which is private to Visualizer manager. + // HandleProxyForComponentVis() sets the active visualizer. So the selection will be lost after undo. + + // A Way to bypass this annoying UE4 implementation: + // If the drawing spline is the one being edited and an undo just happened, + // force to trigger a 'bubble' hit proxy to re-activate the visualizer. + if (HoudiniSplineComponent == EditedHoudiniSplineComponent && EditedHoudiniSplineComponent->bPostUndo) + { + EditedHoudiniSplineComponent->bPostUndo = false; + + FEditorViewportClient * FoundViewportClient = FindViewportClient(EditedHoudiniSplineComponent, View); + HComponentVisProxy * BubbleComponentHitProxy = new HComponentVisProxy(EditedHoudiniSplineComponent); + + if (FoundViewportClient && BubbleComponentHitProxy) + { + FViewportClick BubbleClick(View, FoundViewportClient, FKey(), EInputEvent::IE_Axis, 0, 0); + GUnrealEd->ComponentVisManager.HandleProxyForComponentVis(FoundViewportClient, BubbleComponentHitProxy, BubbleClick); + } + } + + static const FColor ColorNormal = FColor(255.f, 255.f, 255.f); + static const FColor ColorNormalHandleFirst(172.f, 255.f, 172.f); + static const FColor ColorNormalHandleSecond(254.f, 216.f, 177.f); + + static const FColor ColorSelectedHandle(255.f, 0.f, 0.f); + static const FColor ColorSelectedHandleFirst(0.f, 192.f, 0.f); + static const FColor ColorSelectedHandleSecond(255.f, 159.f, 0.f); + + static const float SizeGrabHandleSelected = 15.f; + static const float SizeGrabHandleNormalLarge = 18.f; + static const float SizeGrabHandleNormalSmall = 12.f; + + FVector PreviousPosition; + + if (HoudiniSplineComponent) + { + const FTransform & HoudiniSplineComponentTransform = HoudiniSplineComponent->GetComponentTransform(); + + const TArray< FVector > & DisplayPoints = HoudiniSplineComponent->DisplayPoints; // not used yet + const TArray< FTransform > & CurvePoints = HoudiniSplineComponent->CurvePoints; + + // Draw display points (simply linearly connect the control points for temporary) + for (int32 Index = 0; Index < DisplayPoints.Num(); ++Index) + { + const FVector & CurrentPoint = DisplayPoints[Index]; + FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); + //CurrentPosition = CurrentPoint; + if (Index > 0) + { + // Add a hitproxy for the line segment + PDI->SetHitProxy(new HHoudiniSplineCurveSegmentVisProxy(HoudiniSplineComponent, Index)); + // Draw a line connecting the previous point and the current point + PDI->DrawLine(PreviousPosition, CurrentPosition, ColorNormal, SDPG_Foreground); + PDI->SetHitProxy(nullptr); + } + + PreviousPosition = CurrentPosition; + } + + // Draw control points (do not draw control points if the curve is an output) + if (!HoudiniSplineComponent->bIsOutputCurve) + { + for (int32 Index = 0; Index < CurvePoints.Num(); ++Index) + { + const FVector & ControlPoint = HoudiniSplineComponentTransform.TransformPosition(CurvePoints[Index].GetLocation()); + + HHoudiniSplineControlPointVisProxy * HitProxy = new HHoudiniSplineControlPointVisProxy(HoudiniSplineComponent, Index); + PDI->SetHitProxy(HitProxy); + + FColor DrawColor = ColorNormal; + float DrawSize = SizeGrabHandleNormalSmall; + + if (Index == 0) + { + DrawColor = ColorNormalHandleFirst; + DrawSize = SizeGrabHandleNormalLarge; + } + + if (Index == 1) + DrawColor = ColorNormalHandleSecond; + + + // If this is an point that being editted + if (EditedHoudiniSplineComponent == HoudiniSplineComponent && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Contains(Index)) + { + if (Index == 0) + { + DrawColor = ColorSelectedHandleFirst; + } + + else if (Index == 1) + { + DrawColor = ColorSelectedHandleSecond; + DrawSize = SizeGrabHandleSelected; + } + + else + { + DrawColor = ColorSelectedHandle; + DrawSize = SizeGrabHandleSelected; + + } + } + + PDI->DrawPoint(ControlPoint, DrawColor, DrawSize, SDPG_Foreground); + PDI->SetHitProxy(nullptr); + } + + } + } +} + + +bool +FHoudiniSplineComponentVisualizer::VisProxyHandleClick( + FEditorViewportClient* InViewportClient, + HComponentVisProxy* VisProxy, + const FViewportClick& Click) +{ + if (!InViewportClient || !VisProxy || !VisProxy->Component.IsValid()) + return false; + + const UHoudiniSplineComponent * HoudiniSplineComponent = CastChecked< const UHoudiniSplineComponent >(VisProxy->Component.Get()); + + AActor* OldSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); + SplinePropertyPath = FComponentPropertyPath(HoudiniSplineComponent); + AActor* NewSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); + + if (!SplinePropertyPath.IsValid()) + { + SplinePropertyPath.Reset(); + return false; + } + + if (OldSplineOwningActor != NewSplineOwningActor) + { + // Reset selection state if we are selecting a different actor to the one previously selected + EditedCurveSegmentIndex = INDEX_NONE; + } + + // Note: This is for re-activating the component visualizer an undo. + // Return true if the hit proxy is a bubble (Neither HHoudiniSplineControlPointVisProxy nor HHoudiniSplineCurveSegmentVisProxy ) + // + if (!VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType()) && !VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) + return true; + + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + + EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); + + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + bool editingCurve = false; + + // If VisProxy is a HHoudiniSplineControlPointVisProxy + if (VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType())) + { + HHoudiniSplineControlPointVisProxy * ControlPointProxy = (HHoudiniSplineControlPointVisProxy*)VisProxy; + + if (!ControlPointProxy) + return editingCurve; + + editingCurve = true; + + // Clear the edited curve segment if a control point is clicked. + EditedCurveSegmentIndex = -1; + + if (Click.GetKey() != EKeys::LeftMouseButton) + return editingCurve; + + + if (InViewportClient->IsCtrlPressed()) + { + if (EditedControlPointsIndexes.Contains(ControlPointProxy->ControlPointIndex)) + { + EditedControlPointsIndexes.Remove(ControlPointProxy->ControlPointIndex); + } + else + { + EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); + } + } + else + { + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); + } + } + // VisProxy is a HHoudiniSplineCurveSegmentProxy + else if (VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) + { + //HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = Cast(VisProxy); + + HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = (HHoudiniSplineCurveSegmentVisProxy*)(VisProxy); + + if (!CurveSegmentProxy) + return false; + + editingCurve = true; + + if (Click.GetKey() == EKeys::LeftMouseButton && InViewportClient->IsAltPressed() && EditedHoudiniSplineComponent) + { + // Continuesly (Alt) inserting on-curve control points is only valid with Breakpoints mode, otherwise it has to be on linear curve type. + if (EditedHoudiniSplineComponent->CurveType != EHoudiniCurveType::Polygon && EditedHoudiniSplineComponent->CurveMethod != EHoudiniCurveMethod::Breakpoints) + return editingCurve; + + bInsertingOnCurveControlPoints = true; + + editingCurve = true; + EditedControlPointsIndexes.Empty(); + + EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; + int32 InsertedIndex = OnInsertControlPointWithoutUpdate(); + + if (InsertedIndex < 0) return false; + EditedControlPointsIndexes.Add(InsertedIndex); + + EditedCurveSegmentIndex = -1; + bInsertingOnCurveControlPoints = true; + + RefreshViewport(); + } + // Insert one on-curve control point. + else + { + EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; + return editingCurve; + } + } + + return editingCurve; +} + +bool +FHoudiniSplineComponentVisualizer::HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + if (Key == EKeys::Enter) + { + EditedHoudiniSplineComponent->MarkChanged(true); + + return true; + } + + bool bHandled = false; + + if (Key == EKeys::LeftMouseButton) + { + if (Event == IE_Pressed) + { + bMovingPoints = true; // Started moving points when the left mouse button is pressed + bAllowDuplication = true; + bRecordingMovingPoints = false; + } + + if (Event == IE_Released) + { + bMovingPoints = false; // Stopped moving points when the left mouse button is released + bAllowDuplication = false; + + if (bRecordingMovingPoints) + { + // Only mark the component as changed if a point was actually moved otherwise it will + // cook even if a point was selected. + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + } + + bRecordingMovingPoints = false; // allow recording pt moving again + } + } + + + if (Key == EKeys::Delete) + { + if (Event == IE_Pressed) return true; + + if (IsDeleteControlPointValid()) + { + OnDeleteControlPoint(); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + return true; + } + } + + + if (Event == IE_Pressed && VisualizerActions) + { + if (FSlateApplication::IsInitialized()) + bHandled = VisualizerActions->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), false); + } + + RefreshViewport(); + + return bHandled; +} + +void +FHoudiniSplineComponentVisualizer::EndEditing() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + // Clear edited spline if the EndEditing() function is not called from postUndo + if (!EditedHoudiniSplineComponent->bPostUndo) + { + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); + + EditedHoudiniSplineComponent = nullptr; + EditedCurveSegmentIndex = -1; + } + + //RefreshViewport(); +} + +bool +FHoudiniSplineComponentVisualizer::GetWidgetLocation( + const FEditorViewportClient* ViewportClient, + FVector& OutLocation) const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + // Set the widget location to the center of mass of the selected control points + FVector CenterLocation = FVector::ZeroVector; + + for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i) + { + CenterLocation += CurvePoints[EditedControlPointsIndexes[i]].GetLocation(); + } + + CenterLocation /= EditedControlPointsIndexes.Num(); + OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(CenterLocation); + + return true; +} + +bool +FHoudiniSplineComponentVisualizer::IsVisualizingArchetype() const +{ + UHoudiniSplineComponent* SplineComp = GetEditedHoudiniSplineComponent(); + return (SplineComp && SplineComp->GetOwner() && FActorEditorUtils::IsAPreviewOrInactiveActor(SplineComp->GetOwner())); +} + +bool +FHoudiniSplineComponentVisualizer::HandleInputDelta( + FEditorViewportClient* ViewportClient, + FViewport* Viewport, + FVector& DeltaTranslate, + FRotator& DeltaRotate, + FVector& DeltaScale) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!ViewportClient || !EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + if (ViewportClient->IsAltPressed() && bAllowDuplication) + { + OnDuplicateControlPoint(); + bAllowDuplication = false; + } + else + { + if (!bRecordingMovingPoints) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentMovingPointsTransaction", "Houdini Spline Component: Moving curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + + EditedHoudiniSplineComponent->Modify(); + + bRecordingMovingPoints = true; + } + } + + + TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + const FTransform & HoudiniSplineComponentTransform = EditedHoudiniSplineComponent->GetComponentTransform(); + + for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i ) + { + + FTransform CurrentPoint = EditedHoudiniSplineComponent->CurvePoints[EditedControlPointsIndexes[i]]; + + if (!DeltaTranslate.IsZero()) + { + FVector OldWorldPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint.GetLocation()); + FVector NewWorldPosition = OldWorldPosition + DeltaTranslate; + FVector NewLocalPosition = HoudiniSplineComponentTransform.InverseTransformPosition(NewWorldPosition); + CurrentPoint.SetLocation( NewLocalPosition ); + } + + if (!DeltaRotate.IsZero()) + { + FQuat OldWorldRotation = HoudiniSplineComponentTransform.GetRotation() * CurrentPoint.GetRotation(); + FQuat NewWorldRotation = DeltaRotate.Quaternion() * OldWorldRotation; + FQuat NewLocalRotation = HoudiniSplineComponentTransform.GetRotation().Inverse() * NewWorldRotation; + CurrentPoint.SetRotation(NewLocalRotation); + } + + if (!DeltaScale.IsZero()) + { + FVector NewScale = CurrentPoint.GetScale3D() * (FVector(1.f, 1.f, 1.f) + DeltaScale); + CurrentPoint.SetScale3D(NewScale); + } + + + EditedHoudiniSplineComponent->EditPointAtindex(CurrentPoint, EditedControlPointsIndexes[i]); + } + + RefreshViewport(); + + return true; +} + +TSharedPtr +FHoudiniSplineComponentVisualizer::GenerateContextMenu() const +{ + FHoudiniEngineEditor& HoudiniEngineEditor = FHoudiniEngineEditor::Get(); + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + FMenuBuilder MenuBuilder(true, VisualizerActions); + MenuBuilder.BeginSection("Houdini Spline actions"); + + // Create the context menu section + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + { + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, + NAME_None, TAttribute(), TAttribute(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDuplicateControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDeleteControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDeselectAllControlPoints, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandInsertControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + } + + MenuBuilder.EndSection(); + TSharedPtr MenuWidget = MenuBuilder.MakeWidget(); + return MenuWidget; +} + +// Used by alt-pressed on-curve control port insertion. +// We don't want it to be cooked before finishing editing. +// * Need to call WaitForHoudiniInputUpdate() after done. +int32 +FHoudiniSplineComponentVisualizer::OnInsertControlPointWithoutUpdate() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return -1; + + TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + TArray & DisplayPoints = EditedHoudiniSplineComponent->DisplayPoints; + + if (EditedCurveSegmentIndex >= DisplayPoints.Num()) + return -1; + + // ... // + int InsertAfterIndex = 0; + + TArray & DisplayPointIndexDivider = EditedHoudiniSplineComponent->DisplayPointIndexDivider; + for (int itr = 0; itr < DisplayPointIndexDivider.Num(); ++itr) + { + if (DisplayPointIndexDivider[itr] >= EditedCurveSegmentIndex) + { + InsertAfterIndex = itr; + break; + } + } + // ... // + + if (InsertAfterIndex >= CurvePoints.Num()) return -1; + + FTransform NewPoint = CurvePoints[InsertAfterIndex]; + NewPoint.SetLocation(DisplayPoints[EditedCurveSegmentIndex]); + // To Do: Should interpolate the rotation and scale as well here. + // ... + + // Insert new control point on curve, and add it to selected CP. + int32 NewPointIndex = AddControlPointAfter(NewPoint, InsertAfterIndex); + + // Don't have to reconstruct the index divider each time. + //EditedHoudiniSplineComponent->Construct(EditedHoudiniSplineComponent->DisplayPoints); + EditedHoudiniSplineComponent->DisplayPointIndexDivider.Insert(EditedCurveSegmentIndex, InsertAfterIndex); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + return NewPointIndex; +} + +void +FHoudiniSplineComponentVisualizer::OnInsertControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + int32 NewPointIndex = OnInsertControlPointWithoutUpdate(); + + if (NewPointIndex < 0) return; + + + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Add(NewPointIndex); + + RefreshViewport(); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); +} + +bool +FHoudiniSplineComponentVisualizer::IsInsertControlPointValid() const +{ + return EditedCurveSegmentIndex >= 0; +} + +void +FHoudiniSplineComponentVisualizer::OnAddControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentInsertingPointsTransaction", "Houdini Spline Component: Inserting curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + + EditedHoudiniSplineComponent->Modify(); + + EditedControlPointsIndexes.Sort(); + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + TArray tNewSelectedPoints; + + if (EditedControlPointsIndexes.Num() == 1) + { + FTransform Point = CurvePoints[EditedControlPointsIndexes[0]]; + FTransform NewTransform = FTransform::Identity; + FVector Location = Point.GetLocation(); + //FQuat Rotation = Point.GetRotation(); + //FVector Scale = Point.GetScale3D(); + + NewTransform.SetLocation(Location + 1.f); + //NewTransform.SetRotation(Rotation); + //NewTransform.SetScale3D(Scale); + + + + int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[0]); + tNewSelectedPoints.Add(NewPointIndex); + } + else + { + int IndexIncrement = 0; + int CurrentPointIndex, LastPointIndex; + FTransform CurrentPoint, LastPoint; + + for (int32 n = 0; n < EditedControlPointsIndexes.Num(); ++n) + { + // Insert a new point between each adjacent pair of points + if (n > 0) + { + CurrentPointIndex = EditedControlPointsIndexes[n]; + LastPointIndex = EditedControlPointsIndexes[n - 1]; + CurrentPoint = CurvePoints[CurrentPointIndex + IndexIncrement]; + LastPoint = CurvePoints[LastPointIndex + IndexIncrement]; + + // Insert a point in the middle of LastPoint and CurrentPoint + FVector NewPointLocation = LastPoint.GetLocation() + (CurrentPoint.GetLocation() - LastPoint.GetLocation()) / 2.f; + FVector NewPointScale = LastPoint.GetScale3D() + (CurrentPoint.GetScale3D() - LastPoint.GetScale3D()) / 2.f; + FQuat NewPointRotation = FQuat::Slerp(LastPoint.GetRotation(), CurrentPoint.GetRotation(), .5f); + + FTransform NewTransform = FTransform::Identity; + NewTransform.SetLocation(NewPointLocation); + NewTransform.SetScale3D(NewPointScale); + NewTransform.SetRotation(NewPointRotation); + + int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[n - 1] + IndexIncrement); + tNewSelectedPoints.Add(NewPointIndex); + + + IndexIncrement += 1; + } + } + } + + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes = tNewSelectedPoints; + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + RefreshViewport(); +} + + +bool +FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + return EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill() && + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; +} + +void +FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentDeletingPointsTransaction", "Houdini Spline Component: Deleting curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + EditedHoudiniSplineComponent->Modify(); + + EditedControlPointsIndexes.Sort(); + + int32 SelectedIndexAfterDelete = EditedControlPointsIndexes[0] - 1; + SelectedIndexAfterDelete = FMath::Max(SelectedIndexAfterDelete, 0); + + for (int32 n = EditedControlPointsIndexes.Num() - 1; n >= 0; --n) + { + int32 RemoveIndex = EditedControlPointsIndexes[n]; + EditedHoudiniSplineComponent->RemovePointAtIndex(RemoveIndex); + + } + + EditedControlPointsIndexes.Empty(); + OnDeselectAllControlPoints(); + EditedControlPointsIndexes.Add(SelectedIndexAfterDelete); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + // Force refresh the viewport after deleting points to ensure the consistency of HitProxy + RefreshViewport(); + +} + +bool +FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + // We only allow the number of Control Points is at least 2 after delete + if (EditedHoudiniSplineComponent->GetCurvePointCount() - EditedControlPointsIndexes.Num() < 2) + return false; + + return true; +} + +void +FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentDuplicatingPointsTransaction", "Houdini Spline Component: Duplicating curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + EditedHoudiniSplineComponent->Modify(); + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + EditedControlPointsIndexes.Sort(); + + TArray tNewSelectedPoints; + int IncrementIndex = 0; + for (int n = 0; n < EditedControlPointsIndexes.Num(); ++n) + { + int32 IndexAfter = EditedControlPointsIndexes[n] + IncrementIndex; + FTransform CurrentPoint = CurvePoints[IndexAfter]; + if (IndexAfter == 0) + IndexAfter = -1; + int32 NewPointIndex = AddControlPointAfter(CurrentPoint, IndexAfter); + tNewSelectedPoints.Add(NewPointIndex); + IncrementIndex ++; + } + + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes = tNewSelectedPoints; + + EditedHoudiniSplineComponent->MarkModified(true); + + RefreshViewport(); +} + +bool +FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if(!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill() + || EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() == 0) + return false; + + return true; +} + +void +FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); +} + +bool +FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + return EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; + + return false; +} + +int32 +FHoudiniSplineComponentVisualizer::AddControlPointAfter( + const FTransform & NewPoint, + const int32 & nIndex) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return nIndex; + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + int32 NewControlPointIndex = nIndex + 1; + + if (NewControlPointIndex == CurvePoints.Num()) + EditedHoudiniSplineComponent->AppendPoint(NewPoint); + else + EditedHoudiniSplineComponent->InsertPointAtIndex(NewPoint, NewControlPointIndex); + + // Return the index of the inserted control point + return NewControlPointIndex; +} + +void +FHoudiniSplineComponentVisualizer::RefreshViewport() +{ + if (GEditor) + GEditor->RedrawLevelEditingViewports(true); +} + +// Find the EditorViewportClient of the viewport where the Houdini Spline Component lives in +FEditorViewportClient * +FHoudiniSplineComponentVisualizer::FindViewportClient( + const UHoudiniSplineComponent * InHoudiniSplineComponent, + const FSceneView * View) +{ + if (!View || !InHoudiniSplineComponent) + return nullptr; + + UWorld * World = InHoudiniSplineComponent->GetWorld(); + uint32 ViewKey = View->GetViewKey(); + + const TArray & AllViewportClients = GUnrealEd->GetAllViewportClients(); + + for (auto & NextViewportClient : AllViewportClients) + { + if (!NextViewportClient) + continue; + + if (NextViewportClient->GetWorld() != World) + continue; + + // Found the viewport client which matches the unique key of the current scene view + if (NextViewportClient->ViewState.GetReference()->GetViewKey() == ViewKey) + return NextViewportClient; + } + + return nullptr; +} + +bool +FHoudiniSplineComponentVisualizer::IsCookOnCurveChanged(UHoudiniSplineComponent * InHoudiniSplineComponent) +{ + if (!InHoudiniSplineComponent) + return true; + + return InHoudiniSplineComponent->bCookOnCurveChanged; + + // UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + // UHoudiniInputObject * InputObject = Cast(EditedHoudiniSplineComponent->GetOuter()); + // if (!InputObject) + // return true; + // + // UHoudiniInput * Input = Cast(InputObject->GetOuter()); + // + // if (!Input) + // return true; + // + // return Input->GetCookOnCurveChange(); +}; + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h index 53ab9547f..fee4a7497 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h @@ -1,176 +1,176 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniSplineComponent.h" - -#include "ComponentVisualizer.h" -#include "Framework/Commands/UICommandList.h" -#include "Framework/Commands/Commands.h" - -class FEditorViewportClient; - -/** Base class for clickable spline editing proxies. **/ -struct HHoudiniSplineVisProxy : public HComponentVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineVisProxy(const UActorComponent * InComponent); -}; - -/** Proxy for a spline control point. **/ -struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex); - - int32 ControlPointIndex; -}; - -/** Proxy for a spline display point. **/ -struct HHoudiniSplineCurveSegmentVisProxy : public HHoudiniSplineVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 IndisplayPointIndex); - - int32 DisplayPointIndex; -}; - -class FHoudiniSplineComponentVisualizerCommands : public TCommands< FHoudiniSplineComponentVisualizerCommands > -{ - public: - FHoudiniSplineComponentVisualizerCommands(); - - /** Register commands. **/ - virtual void RegisterCommands() override; - - public: - TSharedPtr CommandAddControlPoint; - - TSharedPtr CommandDuplicateControlPoint; - - TSharedPtr CommandDeleteControlPoint; - - TSharedPtr CommandDeselectAllControlPoints; - - TSharedPtr CommandInsertControlPoint; -}; - - -/** **/ -class FHoudiniSplineComponentVisualizer : public FComponentVisualizer -{ - public: - FHoudiniSplineComponentVisualizer(); - - private: - void RefreshViewport(); - - public: - virtual void OnRegister() override; - - virtual void DrawVisualization( - const UActorComponent * Component, const FSceneView * View, - FPrimitiveDrawInterface * PDI) override; - - virtual bool VisProxyHandleClick( - FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, - const FViewportClick& Click) override; - - virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; - virtual bool IsVisualizingArchetype() const override; - - virtual void EndEditing() override; - - virtual bool HandleInputDelta( - FEditorViewportClient* ViewportClient, FViewport* Viewport, - FVector& DeltaTranslate, FRotator& DeltaRotate, - FVector& DeltaScale) override; - - virtual bool HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) override; - - virtual TSharedPtr GenerateContextMenu() const override; - - protected: - - /** Callbacks for add control point action**/ - void OnAddControlPoint(); - bool IsAddControlPointValid() const; - - /** Callbacks for delete control point action. **/ - void OnDeleteControlPoint(); - bool IsDeleteControlPointValid() const; - - /** Callbacks for duplicate control point action. **/ - void OnDuplicateControlPoint(); - bool IsDuplicateControlPointValid() const; - - /** Callbacks for deselect all control points action. **/ - void OnDeselectAllControlPoints(); - bool IsDeselectAllControlPointsValid() const; - - /** Callbacks for inserting a control point action.**/ - void OnInsertControlPoint(); - bool IsInsertControlPointValid() const; - // For alt-pressed inserting control point on curve. - int32 OnInsertControlPointWithoutUpdate(); - - int32 AddControlPointAfter(const FTransform & NewPoint, const int32 & nIndex); - - public: - /** Property path from the parent actor to the component */ - // NOTE: We need to use SplinePropertyPath on the visualizer as opposed to a direct pointer since the - // direct pointer breaks during Blueprint reconstructions properly - // (see SplineComponent / SplineMeshComponent visualizers). - FComponentPropertyPath SplinePropertyPath; - UHoudiniSplineComponent* GetEditedHoudiniSplineComponent() const { return Cast(SplinePropertyPath.GetComponent()); } - - protected: - - bool bAllowDuplication; - - int32 EditedCurveSegmentIndex; - - TSharedPtr VisualizerActions; - - /** Rotation used for the gizmo widgets **/ - FQuat CachedRotation; - - FVector CachedScale3D; - - /** Indicates wether or not a transaction should be recorded when moving a point **/ - bool bMovingPoints; - - bool bInsertingOnCurveControlPoints; - - bool bRecordingMovingPoints; - - private: - FEditorViewportClient * FindViewportClient(const UHoudiniSplineComponent * InHoudiniSplineComponent, const FSceneView * View); - - bool IsCookOnCurveChanged(UHoudiniSplineComponent* InHoudiniSplineComponent); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniSplineComponent.h" + +#include "ComponentVisualizer.h" +#include "Framework/Commands/UICommandList.h" +#include "Framework/Commands/Commands.h" + +class FEditorViewportClient; + +/** Base class for clickable spline editing proxies. **/ +struct HHoudiniSplineVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineVisProxy(const UActorComponent * InComponent); +}; + +/** Proxy for a spline control point. **/ +struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex); + + int32 ControlPointIndex; +}; + +/** Proxy for a spline display point. **/ +struct HHoudiniSplineCurveSegmentVisProxy : public HHoudiniSplineVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 IndisplayPointIndex); + + int32 DisplayPointIndex; +}; + +class FHoudiniSplineComponentVisualizerCommands : public TCommands< FHoudiniSplineComponentVisualizerCommands > +{ + public: + FHoudiniSplineComponentVisualizerCommands(); + + /** Register commands. **/ + virtual void RegisterCommands() override; + + public: + TSharedPtr CommandAddControlPoint; + + TSharedPtr CommandDuplicateControlPoint; + + TSharedPtr CommandDeleteControlPoint; + + TSharedPtr CommandDeselectAllControlPoints; + + TSharedPtr CommandInsertControlPoint; +}; + + +/** **/ +class FHoudiniSplineComponentVisualizer : public FComponentVisualizer +{ + public: + FHoudiniSplineComponentVisualizer(); + + private: + void RefreshViewport(); + + public: + virtual void OnRegister() override; + + virtual void DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI) override; + + virtual bool VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, + const FViewportClick& Click) override; + + virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; + virtual bool IsVisualizingArchetype() const override; + + virtual void EndEditing() override; + + virtual bool HandleInputDelta( + FEditorViewportClient* ViewportClient, FViewport* Viewport, + FVector& DeltaTranslate, FRotator& DeltaRotate, + FVector& DeltaScale) override; + + virtual bool HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) override; + + virtual TSharedPtr GenerateContextMenu() const override; + + protected: + + /** Callbacks for add control point action**/ + void OnAddControlPoint(); + bool IsAddControlPointValid() const; + + /** Callbacks for delete control point action. **/ + void OnDeleteControlPoint(); + bool IsDeleteControlPointValid() const; + + /** Callbacks for duplicate control point action. **/ + void OnDuplicateControlPoint(); + bool IsDuplicateControlPointValid() const; + + /** Callbacks for deselect all control points action. **/ + void OnDeselectAllControlPoints(); + bool IsDeselectAllControlPointsValid() const; + + /** Callbacks for inserting a control point action.**/ + void OnInsertControlPoint(); + bool IsInsertControlPointValid() const; + // For alt-pressed inserting control point on curve. + int32 OnInsertControlPointWithoutUpdate(); + + int32 AddControlPointAfter(const FTransform & NewPoint, const int32 & nIndex); + + public: + /** Property path from the parent actor to the component */ + // NOTE: We need to use SplinePropertyPath on the visualizer as opposed to a direct pointer since the + // direct pointer breaks during Blueprint reconstructions properly + // (see SplineComponent / SplineMeshComponent visualizers). + FComponentPropertyPath SplinePropertyPath; + UHoudiniSplineComponent* GetEditedHoudiniSplineComponent() const { return Cast(SplinePropertyPath.GetComponent()); } + + protected: + + bool bAllowDuplication; + + int32 EditedCurveSegmentIndex; + + TSharedPtr VisualizerActions; + + /** Rotation used for the gizmo widgets **/ + FQuat CachedRotation; + + FVector CachedScale3D; + + /** Indicates wether or not a transaction should be recorded when moving a point **/ + bool bMovingPoints; + + bool bInsertingOnCurveControlPoints; + + bool bRecordingMovingPoints; + + private: + FEditorViewportClient * FindViewportClient(const UHoudiniSplineComponent * InHoudiniSplineComponent, const FSceneView * View); + + bool IsCookOnCurveChanged(UHoudiniSplineComponent* InHoudiniSplineComponent); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp b/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp index 17ac8fd8a..7f8046a09 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp @@ -1,26 +1,26 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniTool.h b/Source/HoudiniEngineEditor/Private/HoudiniTool.h index bacb6e09f..87d50681e 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniTool.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniTool.h @@ -1,57 +1,56 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#pragma once - -UENUM() -enum class EHoudiniToolType : uint8 -{ - // For tools that generates geometry, and do not need input - HTOOLTYPE_GENERATOR UMETA(DisplayName = "Generator"), - - // For tools that have a single input, the selection will be merged in that single input - HTOOLTYPE_OPERATOR_SINGLE UMETA(DisplayName = "Operator (single)"), - - // For Tools that have multiple input, a single selected asset will be applied to each input - HTOOLTYPE_OPERATOR_MULTI UMETA(DisplayName = "Operator (multiple)"), - - // For tools that needs to be applied each time for each single selected - HTOOLTYPE_OPERATOR_BATCH UMETA(DisplayName = "Batch Operator") -}; - -UENUM() -enum class EHoudiniToolSelectionType : uint8 -{ - // For tools that can be applied both to Content Browser and World selection - HTOOL_SELECTION_ALL UMETA(DisplayName = "Content Browser AND World"), - - // For tools that can be applied only to World selection - HTOOL_SELECTION_WORLD_ONLY UMETA(DisplayName = "World selection only"), - - // For tools that can be applied only to Content Browser selection - HTOOL_SELECTION_CB_ONLY UMETA(DisplayName = "Content browser selection only") +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +UENUM() +enum class EHoudiniToolType : uint8 +{ + // For tools that generates geometry, and do not need input + HTOOLTYPE_GENERATOR UMETA(DisplayName = "Generator"), + + // For tools that have a single input, the selection will be merged in that single input + HTOOLTYPE_OPERATOR_SINGLE UMETA(DisplayName = "Operator (single)"), + + // For Tools that have multiple input, a single selected asset will be applied to each input + HTOOLTYPE_OPERATOR_MULTI UMETA(DisplayName = "Operator (multiple)"), + + // For tools that needs to be applied each time for each single selected + HTOOLTYPE_OPERATOR_BATCH UMETA(DisplayName = "Batch Operator") +}; + +UENUM() +enum class EHoudiniToolSelectionType : uint8 +{ + // For tools that can be applied both to Content Browser and World selection + HTOOL_SELECTION_ALL UMETA(DisplayName = "Content Browser AND World"), + + // For tools that can be applied only to World selection + HTOOL_SELECTION_WORLD_ONLY UMETA(DisplayName = "World selection only"), + + // For tools that can be applied only to Content Browser selection + HTOOL_SELECTION_CB_ONLY UMETA(DisplayName = "Content browser selection only") }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp index bbcfde8ec..44c91fec7 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp @@ -1,349 +1,352 @@ -/* -* 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 "SNewFilePathPicker.h" - -#include "HoudiniApi.h" -#include "DesktopPlatformModule.h" -#include "Widgets/SBoxPanel.h" -#include "Framework/Application/SlateApplication.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SButton.h" - -#define LOCTEXT_NAMESPACE "SNewFilePathPicker" - -/* SNewFilePathPicker interface - *****************************************************************************/ - -void SNewFilePathPicker::Construct( const FArguments& InArgs ) -{ - BrowseDirectory = InArgs._BrowseDirectory; - BrowseTitle = InArgs._BrowseTitle; - FilePath = InArgs._FilePath; - FileTypeFilter = InArgs._FileTypeFilter; - OnPathPicked = InArgs._OnPathPicked; - IsNewFile = InArgs._IsNewFile; - IsDirectoryPicker = InArgs._IsDirectoryPicker; - - ChildSlot - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - [ - // file path text box - SAssignNew(TextBox, SEditableTextBox) - .Text(this, &SNewFilePathPicker::HandleTextBoxText) - .Font(InArgs._Font) - .SelectAllTextWhenFocused(true) - .ClearKeyboardFocusOnCommit(false) - .OnTextCommitted(this, &SNewFilePathPicker::HandleTextBoxTextCommitted) - .SelectAllTextOnCommit(false) - .IsReadOnly(InArgs._IsReadOnly) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(4.0f, 0.0f, 0.0f, 0.0f) - .VAlign(VAlign_Center) - [ - // browse button - SNew(SButton) - .ButtonStyle(InArgs._BrowseButtonStyle) - .ToolTipText(InArgs._BrowseButtonToolTip) - .OnClicked(this, &SNewFilePathPicker::HandleBrowseButtonClicked) - .ContentPadding(2.0f) - .ForegroundColor(FSlateColor::UseForeground()) - .IsFocusable(false) - [ - SNew(SImage) - .Image(InArgs._BrowseButtonImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ] - ]; -} - - -/* SNewFilePathPicker callbacks - *****************************************************************************/ -#if PLATFORM_WINDOWS - -#include "Windows/WindowsHWrapper.h" -#include "Windows/COMPointer.h" -//#include "Misc/Paths.h" -//#include "Misc/Guid.h" -#include "HAL/FileManager.h" -#include "Windows/AllowWindowsPlatformTypes.h" -#include -//#include -#include -//#include -//#include -//#include -//#include -#include "Windows/HideWindowsPlatformTypes.h" -//#pragma comment( lib, "version.lib" ) - -bool FileDialogShared( bool bSave, const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames, int32& OutFilterIndex ) -{ - FScopedSystemModalMode SystemModalScope; - - bool bSuccess = false; - TComPtr FileDialog; - if ( SUCCEEDED( ::CoCreateInstance( bSave ? CLSID_FileSaveDialog : CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, bSave ? IID_IFileSaveDialog : IID_IFileOpenDialog, IID_PPV_ARGS_Helper( &FileDialog ) ) ) ) - { - if ( bSave ) - { - // Set the default "filename" - if ( !DefaultFile.IsEmpty() ) - { - FileDialog->SetFileName( *FPaths::GetCleanFilename( DefaultFile ) ); - } - DWORD dwFlags = 0; - FileDialog->GetOptions( &dwFlags ); - FileDialog->SetOptions( dwFlags & ~FOS_OVERWRITEPROMPT ); - } - else - { - // Set this up as a multi-select picker - if ( Flags & EFileDialogFlags::Multiple ) - { - DWORD dwFlags = 0; - FileDialog->GetOptions( &dwFlags ); - FileDialog->SetOptions( dwFlags | FOS_ALLOWMULTISELECT ); - } - } - - // Set up common settings - FileDialog->SetTitle( *DialogTitle ); - if ( !DefaultPath.IsEmpty() ) - { - // SHCreateItemFromParsingName requires the given path be absolute and use \ rather than / as our normalized paths do - FString DefaultWindowsPath = FPaths::ConvertRelativePathToFull( DefaultPath ); - DefaultWindowsPath.ReplaceInline( TEXT( "/" ), TEXT( "\\" ), ESearchCase::CaseSensitive ); - - TComPtr DefaultPathItem; - if ( SUCCEEDED( ::SHCreateItemFromParsingName( *DefaultWindowsPath, nullptr, IID_PPV_ARGS( &DefaultPathItem ) ) ) ) - { - FileDialog->SetFolder( DefaultPathItem ); - } - } - - // Set-up the file type filters - TArray UnformattedExtensions; - TArray FileDialogFilters; - { - // Split the given filter string (formatted as "Pair1String1|Pair1String2|Pair2String1|Pair2String2") into the Windows specific filter struct - FileTypes.ParseIntoArray( UnformattedExtensions, TEXT( "|" ), true ); - - if ( UnformattedExtensions.Num() % 2 == 0 ) - { - FileDialogFilters.Reserve( UnformattedExtensions.Num() / 2 ); - for ( int32 ExtensionIndex = 0; ExtensionIndex < UnformattedExtensions.Num();) - { - COMDLG_FILTERSPEC& NewFilterSpec = FileDialogFilters[FileDialogFilters.AddDefaulted()]; - NewFilterSpec.pszName = *UnformattedExtensions[ExtensionIndex++]; - NewFilterSpec.pszSpec = *UnformattedExtensions[ExtensionIndex++]; - } - } - } - FileDialog->SetFileTypes( FileDialogFilters.Num(), FileDialogFilters.GetData() ); - - // Show the picker - if ( SUCCEEDED( FileDialog->Show( (HWND)ParentWindowHandle ) ) ) - { - OutFilterIndex = 0; - if ( SUCCEEDED( FileDialog->GetFileTypeIndex( (UINT*)&OutFilterIndex ) ) ) - { - OutFilterIndex -= 1; // GetFileTypeIndex returns a 1-based index - } - - auto AddOutFilename = [&OutFilenames]( const FString& InFilename ) - { - FString& OutFilename = OutFilenames[OutFilenames.Add( InFilename )]; - OutFilename = IFileManager::Get().ConvertToRelativePath( *OutFilename ); - FPaths::NormalizeFilename( OutFilename ); - }; - - if ( bSave ) - { - TComPtr Result; - if ( SUCCEEDED( FileDialog->GetResult( &Result ) ) ) - { - PWSTR pFilePath = nullptr; - if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) - { - bSuccess = true; - - // Apply the selected extension if the given filename doesn't already have one - FString SaveFilePath = pFilePath; - if ( FileDialogFilters.IsValidIndex( OutFilterIndex ) ) - { - // Build a "clean" version of the selected extension (without the wildcard) - FString CleanExtension = FileDialogFilters[OutFilterIndex].pszSpec; - if ( CleanExtension == TEXT( "*.*" ) ) - { - CleanExtension.Reset(); - } - else - { - const int32 WildCardIndex = CleanExtension.Find( TEXT( "*" ) ); - if ( WildCardIndex != INDEX_NONE ) - { - CleanExtension = CleanExtension.RightChop( WildCardIndex + 1 ); - } - } - - // We need to split these before testing the extension to avoid anything within the path being treated as a file extension - FString SaveFileName = FPaths::GetCleanFilename( SaveFilePath ); - SaveFilePath = FPaths::GetPath( SaveFilePath ); - - // Apply the extension if the file name doesn't already have one - if ( FPaths::GetExtension( SaveFileName ).IsEmpty() && !CleanExtension.IsEmpty() ) - { - SaveFileName = FPaths::SetExtension( SaveFileName, CleanExtension ); - } - - SaveFilePath /= SaveFileName; - } - AddOutFilename( SaveFilePath ); - - ::CoTaskMemFree( pFilePath ); - } - } - } - else - { - IFileOpenDialog* FileOpenDialog = static_cast( FileDialog.Get() ); - - TComPtr Results; - if ( SUCCEEDED( FileOpenDialog->GetResults( &Results ) ) ) - { - DWORD NumResults = 0; - Results->GetCount( &NumResults ); - for ( DWORD ResultIndex = 0; ResultIndex < NumResults; ++ResultIndex ) - { - TComPtr Result; - if ( SUCCEEDED( Results->GetItemAt( ResultIndex, &Result ) ) ) - { - PWSTR pFilePath = nullptr; - if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) - { - bSuccess = true; - AddOutFilename( pFilePath ); - ::CoTaskMemFree( pFilePath ); - } - } - } - } - } - } - } - - return bSuccess; -} - -bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) -{ - int32 DummyFilterIndex = 0; - return FileDialogShared( true, ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames, DummyFilterIndex ); -} - -#else - -bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) -{ - IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); - return DesktopPlatform->SaveFileDialog( ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames ); -} -#endif - -FReply SNewFilePathPicker::HandleBrowseButtonClicked() -{ - IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); - - if (DesktopPlatform == nullptr) - { - return FReply::Handled(); - } - - const FString DefaultPath = BrowseDirectory.IsSet() - ? BrowseDirectory.Get() - : FPaths::GetPath(FilePath.Get()); - - // show the file browse dialog - if (!FSlateApplication::IsInitialized()) - return FReply::Handled(); - - TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); - void* ParentWindowHandle = (ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid()) - ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() - : nullptr; - - if(!IsDirectoryPicker.Get()) - { - TArray OutFiles; - // CG: Use SaveFileDialog instead of OpenFileDialog - if ( IsNewFile.Get() ) - { - if ( SaveFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) - { - OnPathPicked.ExecuteIfBound( OutFiles[0] ); - } - } - else - { - if ( DesktopPlatform->OpenFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) - { - OnPathPicked.ExecuteIfBound( OutFiles[0] ); - } - } - } - else - { - FString OutDir; - if (DesktopPlatform->OpenDirectoryDialog(ParentWindowHandle, LOCTEXT("FolderDialogTitle", "Choose a directory").ToString(), DefaultPath, OutDir)) - { - OnPathPicked.ExecuteIfBound(OutDir); - } - } - - return FReply::Handled(); -} - - -FText SNewFilePathPicker::HandleTextBoxText() const -{ - return FText::FromString(FilePath.Get()); -} - - -void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type CommitInfo ) -{ - OnPathPicked.ExecuteIfBound(NewText.ToString()); -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "SNewFilePathPicker.h" + +#include "HoudiniApi.h" +#include "DesktopPlatformModule.h" +#include "Widgets/SBoxPanel.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SButton.h" + +#define LOCTEXT_NAMESPACE "SNewFilePathPicker" + +/* SNewFilePathPicker interface + *****************************************************************************/ + +void SNewFilePathPicker::Construct( const FArguments& InArgs ) +{ + BrowseDirectory = InArgs._BrowseDirectory; + BrowseTitle = InArgs._BrowseTitle; + FilePath = InArgs._FilePath; + FileTypeFilter = InArgs._FileTypeFilter; + OnPathPicked = InArgs._OnPathPicked; + IsNewFile = InArgs._IsNewFile; + IsDirectoryPicker = InArgs._IsDirectoryPicker; + + ChildSlot + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + // file path text box + SAssignNew(TextBox, SEditableTextBox) + .Text(this, &SNewFilePathPicker::HandleTextBoxText) + .Font(InArgs._Font) + .SelectAllTextWhenFocused(true) + .ClearKeyboardFocusOnCommit(false) + .OnTextCommitted(this, &SNewFilePathPicker::HandleTextBoxTextCommitted) + .SelectAllTextOnCommit(false) + .IsReadOnly(InArgs._IsReadOnly) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(4.0f, 0.0f, 0.0f, 0.0f) + .VAlign(VAlign_Center) + [ + // browse button + SNew(SButton) + .ButtonStyle(InArgs._BrowseButtonStyle) + .ToolTipText(InArgs._BrowseButtonToolTip) + .OnClicked(this, &SNewFilePathPicker::HandleBrowseButtonClicked) + .ContentPadding(2.0f) + .ForegroundColor(FSlateColor::UseForeground()) + .IsFocusable(false) + [ + SNew(SImage) + .Image(InArgs._BrowseButtonImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ] + ]; +} + + +/* SNewFilePathPicker callbacks + *****************************************************************************/ +#if PLATFORM_WINDOWS + +#include "Windows/WindowsHWrapper.h" +#include "Windows/COMPointer.h" +//#include "Misc/Paths.h" +//#include "Misc/Guid.h" +#include "HAL/FileManager.h" +#include "Windows/AllowWindowsPlatformTypes.h" +#include +//#include +#include +//#include +//#include +//#include +//#include +#include "Windows/HideWindowsPlatformTypes.h" +//#pragma comment( lib, "version.lib" ) + +bool FileDialogShared( bool bSave, const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames, int32& OutFilterIndex ) +{ + FScopedSystemModalMode SystemModalScope; + + bool bSuccess = false; + TComPtr FileDialog; + if ( SUCCEEDED( ::CoCreateInstance( bSave ? CLSID_FileSaveDialog : CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, bSave ? IID_IFileSaveDialog : IID_IFileOpenDialog, IID_PPV_ARGS_Helper( &FileDialog ) ) ) ) + { + if ( bSave ) + { + // Set the default "filename" + if ( !DefaultFile.IsEmpty() ) + { + FileDialog->SetFileName( *FPaths::GetCleanFilename( DefaultFile ) ); + } + DWORD dwFlags = 0; + FileDialog->GetOptions( &dwFlags ); + FileDialog->SetOptions( dwFlags & ~FOS_OVERWRITEPROMPT ); + } + else + { + // Set this up as a multi-select picker + if ( Flags & EFileDialogFlags::Multiple ) + { + DWORD dwFlags = 0; + FileDialog->GetOptions( &dwFlags ); + FileDialog->SetOptions( dwFlags | FOS_ALLOWMULTISELECT ); + } + } + + // Set up common settings + FileDialog->SetTitle( *DialogTitle ); + if ( !DefaultPath.IsEmpty() ) + { + // SHCreateItemFromParsingName requires the given path be absolute and use \ rather than / as our normalized paths do + FString DefaultWindowsPath = FPaths::ConvertRelativePathToFull( DefaultPath ); + DefaultWindowsPath.ReplaceInline( TEXT( "/" ), TEXT( "\\" ), ESearchCase::CaseSensitive ); + + TComPtr DefaultPathItem; + if ( SUCCEEDED( ::SHCreateItemFromParsingName( *DefaultWindowsPath, nullptr, IID_PPV_ARGS( &DefaultPathItem ) ) ) ) + { + FileDialog->SetFolder( DefaultPathItem ); + } + } + + // Set-up the file type filters + TArray UnformattedExtensions; + TArray FileDialogFilters; + { + // Split the given filter string (formatted as "Pair1String1|Pair1String2|Pair2String1|Pair2String2") into the Windows specific filter struct + FileTypes.ParseIntoArray( UnformattedExtensions, TEXT( "|" ), true ); + + if ( UnformattedExtensions.Num() % 2 == 0 ) + { + FileDialogFilters.Reserve( UnformattedExtensions.Num() / 2 ); + for ( int32 ExtensionIndex = 0; ExtensionIndex < UnformattedExtensions.Num();) + { + COMDLG_FILTERSPEC& NewFilterSpec = FileDialogFilters[FileDialogFilters.AddDefaulted()]; + NewFilterSpec.pszName = *UnformattedExtensions[ExtensionIndex++]; + NewFilterSpec.pszSpec = *UnformattedExtensions[ExtensionIndex++]; + } + } + } + FileDialog->SetFileTypes( FileDialogFilters.Num(), FileDialogFilters.GetData() ); + + // Show the picker + if ( SUCCEEDED( FileDialog->Show( (HWND)ParentWindowHandle ) ) ) + { + OutFilterIndex = 0; + if ( SUCCEEDED( FileDialog->GetFileTypeIndex( (UINT*)&OutFilterIndex ) ) ) + { + OutFilterIndex -= 1; // GetFileTypeIndex returns a 1-based index + } + + auto AddOutFilename = [&OutFilenames]( const FString& InFilename ) + { + FString& OutFilename = OutFilenames[OutFilenames.Add( InFilename )]; + OutFilename = IFileManager::Get().ConvertToRelativePath( *OutFilename ); + FPaths::NormalizeFilename( OutFilename ); + }; + + if ( bSave ) + { + TComPtr Result; + if ( SUCCEEDED( FileDialog->GetResult( &Result ) ) ) + { + PWSTR pFilePath = nullptr; + if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) + { + bSuccess = true; + + // Apply the selected extension if the given filename doesn't already have one + FString SaveFilePath = pFilePath; + if ( FileDialogFilters.IsValidIndex( OutFilterIndex ) ) + { + // Build a "clean" version of the selected extension (without the wildcard) + FString CleanExtension = FileDialogFilters[OutFilterIndex].pszSpec; + if ( CleanExtension == TEXT( "*.*" ) ) + { + CleanExtension.Reset(); + } + else + { + const int32 WildCardIndex = CleanExtension.Find( TEXT( "*" ) ); + if ( WildCardIndex != INDEX_NONE ) + { + CleanExtension = CleanExtension.RightChop( WildCardIndex + 1 ); + } + } + + // We need to split these before testing the extension to avoid anything within the path being treated as a file extension + FString SaveFileName = FPaths::GetCleanFilename( SaveFilePath ); + SaveFilePath = FPaths::GetPath( SaveFilePath ); + + // Apply the extension if the file name doesn't already have one + if ( FPaths::GetExtension( SaveFileName ).IsEmpty() && !CleanExtension.IsEmpty() ) + { + SaveFileName = FPaths::SetExtension( SaveFileName, CleanExtension ); + } + + SaveFilePath /= SaveFileName; + } + AddOutFilename( SaveFilePath ); + + ::CoTaskMemFree( pFilePath ); + } + } + } + else + { + IFileOpenDialog* FileOpenDialog = static_cast( FileDialog.Get() ); + + TComPtr Results; + if ( SUCCEEDED( FileOpenDialog->GetResults( &Results ) ) ) + { + DWORD NumResults = 0; + Results->GetCount( &NumResults ); + for ( DWORD ResultIndex = 0; ResultIndex < NumResults; ++ResultIndex ) + { + TComPtr Result; + if ( SUCCEEDED( Results->GetItemAt( ResultIndex, &Result ) ) ) + { + PWSTR pFilePath = nullptr; + if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) + { + bSuccess = true; + AddOutFilename( pFilePath ); + ::CoTaskMemFree( pFilePath ); + } + } + } + } + } + } + } + + return bSuccess; +} + +bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) +{ + int32 DummyFilterIndex = 0; + return FileDialogShared( true, ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames, DummyFilterIndex ); +} + +#else + +bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + return DesktopPlatform->SaveFileDialog( ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames ); +} +#endif + +FReply SNewFilePathPicker::HandleBrowseButtonClicked() +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + + if (DesktopPlatform == nullptr) + { + return FReply::Handled(); + } + + const FString DefaultPath = BrowseDirectory.IsSet() + ? BrowseDirectory.Get() + : FPaths::GetPath(FilePath.Get()); + + // show the file browse dialog + if (!FSlateApplication::IsInitialized()) + return FReply::Handled(); + + TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + void* ParentWindowHandle = (ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid()) + ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() + : nullptr; + + if(!IsDirectoryPicker.Get()) + { + TArray OutFiles; + // CG: Use SaveFileDialog instead of OpenFileDialog + if ( IsNewFile.Get() ) + { + if ( SaveFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) + { + OnPathPicked.ExecuteIfBound( OutFiles[0] ); + } + } + else + { + if ( DesktopPlatform->OpenFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) + { + OnPathPicked.ExecuteIfBound( OutFiles[0] ); + } + } + } + else + { + FString OutDir; + if (DesktopPlatform->OpenDirectoryDialog(ParentWindowHandle, LOCTEXT("FolderDialogTitle", "Choose a directory").ToString(), DefaultPath, OutDir)) + { + OnPathPicked.ExecuteIfBound(OutDir); + } + } + + return FReply::Handled(); +} + + +FText SNewFilePathPicker::HandleTextBoxText() const +{ + return FText::FromString(FilePath.Get()); +} + + +void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type CommitInfo ) +{ + OnPathPicked.ExecuteIfBound(NewText.ToString()); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h index 64b256546..8ca1dbbca 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h @@ -1,147 +1,150 @@ -/* -* 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. -* -*/ - -// This is the same as SFilePathPicker but it uses SaveFileDialog instead of OpenFileDialog -// to allow browsing to a new path - -#pragma once - -#include "CoreMinimal.h" -#include "Misc/Attribute.h" -#include "Fonts/SlateFontInfo.h" -#include "Input/Reply.h" -#include "Styling/SlateWidgetStyleAsset.h" -#include "Styling/ISlateStyle.h" -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SCompoundWidget.h" -#include "Styling/SlateTypes.h" - -class SEditableTextBox; - -/** - * Declares a delegate that is executed when a file was picked in the SFilePathPicker widget. - * - * The first parameter will contain the path to the picked file. - */ -DECLARE_DELEGATE_OneParam(FOnPathPicked, const FString& /*PickedPath*/); - - -/** - * Implements an editable text box with a browse button. - */ -class SNewFilePathPicker - : public SCompoundWidget -{ -public: - - SLATE_BEGIN_ARGS(SNewFilePathPicker) - : _BrowseButtonToolTip(NSLOCTEXT("SNewFilePathPicker", "BrowseButtonToolTip", "Choose a file from this computer")) - , _FileTypeFilter(TEXT("All files (*.*)|*.*")) - , _Font() - , _IsReadOnly(false) - , _IsNewFile(true) - , _IsDirectoryPicker(false) - { } - - /** Browse button image resource. */ - SLATE_ATTRIBUTE(const FSlateBrush*, BrowseButtonImage) - - /** Browse button visual style. */ - SLATE_STYLE_ARGUMENT(FButtonStyle, BrowseButtonStyle) - - /** Browse button tool tip text. */ - SLATE_ATTRIBUTE(FText, BrowseButtonToolTip) - - /** The directory to browse by default */ - SLATE_ATTRIBUTE(FString, BrowseDirectory) - - /** Title for the browse dialog window. */ - SLATE_ATTRIBUTE(FText, BrowseTitle) - - /** The currently selected file path. */ - SLATE_ATTRIBUTE(FString, FilePath) - - /** File type filter string. */ - SLATE_ATTRIBUTE(FString, FileTypeFilter) - - /** Font color and opacity of the path text box. */ - SLATE_ATTRIBUTE(FSlateFontInfo, Font) - - /** Whether the path text box can be modified by the user. */ - SLATE_ATTRIBUTE(bool, IsReadOnly) - - /** Whether to use the new-file dialog instead of open-file */ - SLATE_ATTRIBUTE(bool, IsNewFile) - - /** Whether to use the a directory picker dialog */ - SLATE_ATTRIBUTE(bool, IsDirectoryPicker) - - /** Called when a file path has been picked. */ - SLATE_EVENT(FOnPathPicked, OnPathPicked) - - SLATE_END_ARGS() - - /** - * Constructs a new widget. - * - * @param InArgs The construction arguments. - */ - void Construct( const FArguments& InArgs ); - -private: - - /** Callback for clicking the browse button. */ - FReply HandleBrowseButtonClicked( ); - - /** Callback for getting the text in the path text box. */ - FText HandleTextBoxText( ) const; - - /** Callback for committing the text in the path text box. */ - void HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type /*CommitInfo*/ ); - -private: - - /** Holds the directory path to browse by default. */ - TAttribute BrowseDirectory; - - /** Holds the title for the browse dialog window. */ - TAttribute BrowseTitle; - - /** Holds the currently selected file path. */ - TAttribute FilePath; - - /** Holds the file type filter string. */ - TAttribute FileTypeFilter; - - /** Holds the editable text box. */ - TSharedPtr TextBox; - - TAttribute IsNewFile; - - TAttribute IsDirectoryPicker; - -private: - - /** Holds a delegate that is executed when a file was picked. */ - FOnPathPicked OnPathPicked; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// This is the same as SFilePathPicker but it uses SaveFileDialog instead of OpenFileDialog +// to allow browsing to a new path + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Attribute.h" +#include "Fonts/SlateFontInfo.h" +#include "Input/Reply.h" +#include "Styling/SlateWidgetStyleAsset.h" +#include "Styling/ISlateStyle.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Styling/SlateTypes.h" + +class SEditableTextBox; + +/** + * Declares a delegate that is executed when a file was picked in the SFilePathPicker widget. + * + * The first parameter will contain the path to the picked file. + */ +DECLARE_DELEGATE_OneParam(FOnPathPicked, const FString& /*PickedPath*/); + + +/** + * Implements an editable text box with a browse button. + */ +class SNewFilePathPicker + : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SNewFilePathPicker) + : _BrowseButtonToolTip(NSLOCTEXT("SNewFilePathPicker", "BrowseButtonToolTip", "Choose a file from this computer")) + , _FileTypeFilter(TEXT("All files (*.*)|*.*")) + , _Font() + , _IsReadOnly(false) + , _IsNewFile(true) + , _IsDirectoryPicker(false) + { } + + /** Browse button image resource. */ + SLATE_ATTRIBUTE(const FSlateBrush*, BrowseButtonImage) + + /** Browse button visual style. */ + SLATE_STYLE_ARGUMENT(FButtonStyle, BrowseButtonStyle) + + /** Browse button tool tip text. */ + SLATE_ATTRIBUTE(FText, BrowseButtonToolTip) + + /** The directory to browse by default */ + SLATE_ATTRIBUTE(FString, BrowseDirectory) + + /** Title for the browse dialog window. */ + SLATE_ATTRIBUTE(FText, BrowseTitle) + + /** The currently selected file path. */ + SLATE_ATTRIBUTE(FString, FilePath) + + /** File type filter string. */ + SLATE_ATTRIBUTE(FString, FileTypeFilter) + + /** Font color and opacity of the path text box. */ + SLATE_ATTRIBUTE(FSlateFontInfo, Font) + + /** Whether the path text box can be modified by the user. */ + SLATE_ATTRIBUTE(bool, IsReadOnly) + + /** Whether to use the new-file dialog instead of open-file */ + SLATE_ATTRIBUTE(bool, IsNewFile) + + /** Whether to use the a directory picker dialog */ + SLATE_ATTRIBUTE(bool, IsDirectoryPicker) + + /** Called when a file path has been picked. */ + SLATE_EVENT(FOnPathPicked, OnPathPicked) + + SLATE_END_ARGS() + + /** + * Constructs a new widget. + * + * @param InArgs The construction arguments. + */ + void Construct( const FArguments& InArgs ); + +private: + + /** Callback for clicking the browse button. */ + FReply HandleBrowseButtonClicked( ); + + /** Callback for getting the text in the path text box. */ + FText HandleTextBoxText( ) const; + + /** Callback for committing the text in the path text box. */ + void HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type /*CommitInfo*/ ); + +private: + + /** Holds the directory path to browse by default. */ + TAttribute BrowseDirectory; + + /** Holds the title for the browse dialog window. */ + TAttribute BrowseTitle; + + /** Holds the currently selected file path. */ + TAttribute FilePath; + + /** Holds the file type filter string. */ + TAttribute FileTypeFilter; + + /** Holds the editable text box. */ + TSharedPtr TextBox; + + TAttribute IsNewFile; + + TAttribute IsDirectoryPicker; + +private: + + /** Holds a delegate that is executed when a file was picked. */ + FOnPathPicked OnPathPicked; +}; diff --git a/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h b/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h index 940f0ab60..3a722a67b 100644 --- a/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h +++ b/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Modules/ModuleInterface.h" - -class IHoudiniEngineEditor : public IModuleInterface -{ - public: - /** Register and unregister component visualizers used by this module. **/ - virtual void RegisterComponentVisualizers() {} - virtual void UnregisterComponentVisualizers() {} - - /** Register and unregister detail presenters used by this module. **/ - virtual void RegisterDetails() {} - virtual void UnregisterDetails() {} - - /** Register and unregister asset type actions. **/ - virtual void RegisterAssetTypeActions() {} - virtual void UnregisterAssetTypeActions() {} - - /** Create and register / unregister asset brokers. **/ - virtual void RegisterAssetBrokers() {} - virtual void UnregisterAssetBrokers() {} - - /** Create and register actor factories. **/ - virtual void RegisterActorFactories() {} - - /** Extend menu. **/ - virtual void ExtendMenu() {} - - /** Register and unregister thumbnails. **/ - virtual void RegisterThumbnails() {} - virtual void UnregisterThumbnails() {} - - /** Register and unregister for undo/redo notifications. **/ - virtual void RegisterForUndo() {} - virtual void UnregisterForUndo() {} - - /** Create custom modes **/ - virtual void RegisterModes() {} - virtual void UnregisterModes() {} - - /** Create custom placement extensions */ - virtual void RegisterPlacementModeExtensions() {} - virtual void UnregisterPlacementModeExtensions() {} -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Modules/ModuleInterface.h" + +class IHoudiniEngineEditor : public IModuleInterface +{ + public: + /** Register and unregister component visualizers used by this module. **/ + virtual void RegisterComponentVisualizers() {} + virtual void UnregisterComponentVisualizers() {} + + /** Register and unregister detail presenters used by this module. **/ + virtual void RegisterDetails() {} + virtual void UnregisterDetails() {} + + /** Register and unregister asset type actions. **/ + virtual void RegisterAssetTypeActions() {} + virtual void UnregisterAssetTypeActions() {} + + /** Create and register / unregister asset brokers. **/ + virtual void RegisterAssetBrokers() {} + virtual void UnregisterAssetBrokers() {} + + /** Create and register actor factories. **/ + virtual void RegisterActorFactories() {} + + /** Extend menu. **/ + virtual void ExtendMenu() {} + + /** Register and unregister thumbnails. **/ + virtual void RegisterThumbnails() {} + virtual void UnregisterThumbnails() {} + + /** Register and unregister for undo/redo notifications. **/ + virtual void RegisterForUndo() {} + virtual void UnregisterForUndo() {} + + /** Create custom modes **/ + virtual void RegisterModes() {} + virtual void UnregisterModes() {} + + /** Create custom placement extensions */ + virtual void RegisterPlacementModeExtensions() {} + virtual void UnregisterPlacementModeExtensions() {} +}; diff --git a/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs b/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs index e6caf2291..ce72dab42 100644 --- a/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs +++ b/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs @@ -1,96 +1,92 @@ -/* - * Copyright (c) <2020> 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. - * - * Produced by: - * Side Effects Software Inc - * 123 Front Street West, Suite 1401 - * Toronto, Ontario - * Canada M5J 2M2 - * 416-504-9876 - * - */ - -using UnrealBuildTool; -using System; -using System.IO; - -public class HoudiniEngineRuntime : ModuleRules -{ - public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) - { - bPrecompile = true; - PCHUsage = PCHUsageMode.NoSharedPCHs; - PrivatePCHHeaderFile = "Private/HoudiniEngineRuntimePrivatePCH.h"; - - // Check if we are compiling for unsupported platforms. - if ( Target.Platform != UnrealTargetPlatform.Win64 && - Target.Platform != UnrealTargetPlatform.Mac && - Target.Platform != UnrealTargetPlatform.Linux && - Target.Platform != UnrealTargetPlatform.Switch ) - { - System.Console.WriteLine( string.Format( "Houdini Engine Runtime: Compiling for untested target platform. Please let us know how it goes!" ) ); - } - - - PublicIncludePaths.AddRange( - new string[] {} - ); - - PrivateIncludePaths.AddRange( - new string[] { } - ); - - // Add common dependencies. - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "Engine", - "RenderCore", - "InputCore", - "RHI", - "Foliage", - "Landscape" - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "Landscape", - "PropertyPath", - "PhysicsCore" - } - ); - - if (Target.bBuildEditor == true) - { - PrivateDependencyModuleNames.AddRange( - new string[] - { - "UnrealEd", - "Kismet", - } - ); - } - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using UnrealBuildTool; +using System; +using System.IO; + +public class HoudiniEngineRuntime : ModuleRules +{ + public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) + { + bPrecompile = true; + PCHUsage = PCHUsageMode.NoSharedPCHs; + PrivatePCHHeaderFile = "Private/HoudiniEngineRuntimePrivatePCH.h"; + + // Check if we are compiling for unsupported platforms. + if ( Target.Platform != UnrealTargetPlatform.Win64 && + Target.Platform != UnrealTargetPlatform.Mac && + Target.Platform != UnrealTargetPlatform.Linux && + Target.Platform != UnrealTargetPlatform.Switch ) + { + System.Console.WriteLine( string.Format( "Houdini Engine Runtime: Compiling for untested target platform. Please let us know how it goes!" ) ); + } + + + PublicIncludePaths.AddRange( + new string[] {} + ); + + PrivateIncludePaths.AddRange( + new string[] { } + ); + + // Add common dependencies. + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "RenderCore", + "InputCore", + "RHI", + "Foliage", + "Landscape" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Landscape", + "PhysicsCore" + } + ); + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "Kismet", + } + ); + } + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp index 2f7587ca8..a05783499 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp @@ -1,200 +1,200 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAsset.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Misc/Paths.h" -#include "HAL/UnrealMemory.h" - -UHoudiniAsset::UHoudiniAsset(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , AssetFileName(TEXT("")) - , AssetBytesCount(0) - , bAssetLimitedCommercial(false) - , bAssetNonCommercial(false) - , bAssetExpanded(false) -{} - -void -UHoudiniAsset::CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName) -{ - AssetFileName = InFileName; - - // Calculate buffer size. - AssetBytesCount = BufferEnd - BufferStart; - - if (AssetBytesCount) - { - // Allocate buffer to store the raw data. - AssetBytes.SetNumUninitialized(AssetBytesCount); - // Copy data into the newly allocated buffer. - FMemory::Memcpy(AssetBytes.GetData(), BufferStart, AssetBytesCount); - } - - FString FileExtension = FPaths::GetExtension(InFileName); - - // Expanded HDAs are imported via a "houdini.hdalibrary" file inside the .hda directory - // Identify them first, then update the file path to point to the .hda dir - if (FileExtension.Equals(TEXT("hdalibrary"), ESearchCase::IgnoreCase)) - { - bAssetExpanded = true; - - // Use the parent ".hda" directory as the filename - AssetFileName = FPaths::GetPath(AssetFileName); - FileExtension = FPaths::GetExtension(AssetFileName); - } - - if (FileExtension.Equals(TEXT("hdalc"), ESearchCase::IgnoreCase) - || FileExtension.Equals(TEXT("otlc"), ESearchCase::IgnoreCase)) - { - // Check if the HDA is limited (Indie) ... - bAssetLimitedCommercial = true; - } - else if (FileExtension.Equals(TEXT("hdanc"), ESearchCase::IgnoreCase) - || FileExtension.Equals(TEXT("otlnc"), ESearchCase::IgnoreCase)) - { - // ... or non commercial (Apprentice) - bAssetNonCommercial = true; - } -} - -void -UHoudiniAsset::FinishDestroy() -{ - // Release buffer which was used to store raw OTL data. - AssetBytes.Empty(); - Super::FinishDestroy(); -} - -const uint8 * -UHoudiniAsset::GetAssetBytes() const -{ - return AssetBytes.GetData(); -} - -const FString & -UHoudiniAsset::GetAssetFileName() const -{ - return AssetFileName; -} - -uint32 -UHoudiniAsset::GetAssetBytesCount() const -{ - return AssetBytesCount; -} - -void -UHoudiniAsset::Serialize(FArchive & Ar) -{ - // Serializes our UProperties - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Get the version - uint32 HoudiniAssetVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - - // Only version 1 assets needs manual serialization - if ( HoudiniAssetVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE - || HoudiniAssetVersion > VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION ) - return SerializeLegacy(Ar); -} - -void -UHoudiniAsset::SerializeLegacy(FArchive & Ar) -{ - uint32 FileFormatVersion; - Ar << FileFormatVersion; - - Ar << AssetBytesCount; - if (Ar.IsLoading()) - { - // Allocate sufficient space to read stored raw OTL data. - AssetBytes.SetNumUninitialized(AssetBytesCount); - } - - if (AssetBytesCount) - Ar.Serialize(AssetBytes.GetData(), AssetBytesCount); - - // Serialized flags. - union - { - struct - { - uint32 bLegacyPreviewHoudiniLogo : 1; - uint32 bLegacyAssetLimitedCommercial : 1; - uint32 bLegacyAssetNonCommercial : 1; - }; - uint32 HoudiniAssetFlagsPacked; - }; - Ar << HoudiniAssetFlagsPacked; - - bAssetNonCommercial = bLegacyAssetNonCommercial; - bAssetLimitedCommercial = bLegacyAssetLimitedCommercial; - - // Serialize asset file path. - Ar << AssetFileName; -} - -void -UHoudiniAsset::GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const -{ - // Filename - OutTags.Add(FAssetRegistryTag("FileName", AssetFileName, FAssetRegistryTag::TT_Alphabetical)); - - // Bytes - OutTags.Add(FAssetRegistryTag("Bytes", FString::FromInt(AssetBytesCount), FAssetRegistryTag::TT_Numerical)); - - // Indicate if the Asset is Full / Indie / NC - FString AssetType = TEXT("Full"); - if (bAssetLimitedCommercial) - AssetType = TEXT("Limited Commercial (LC)"); - else if (bAssetNonCommercial) - AssetType = TEXT("Non Commercial (NC)"); - - OutTags.Add(FAssetRegistryTag("Asset Type", AssetType, FAssetRegistryTag::TT_Alphabetical)); - - Super::GetAssetRegistryTags(OutTags); -} - -bool -UHoudiniAsset::IsAssetLimitedCommercial() const -{ - return bAssetLimitedCommercial; -} - -bool -UHoudiniAsset::IsAssetNonCommercial() const -{ - return bAssetNonCommercial; -} - -bool -UHoudiniAsset::IsExpandedHDA() const -{ - return bAssetExpanded; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAsset.h" +#include "HoudiniPluginSerializationVersion.h" + +#include "Misc/Paths.h" +#include "HAL/UnrealMemory.h" + +UHoudiniAsset::UHoudiniAsset(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , AssetFileName(TEXT("")) + , AssetBytesCount(0) + , bAssetLimitedCommercial(false) + , bAssetNonCommercial(false) + , bAssetExpanded(false) +{} + +void +UHoudiniAsset::CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName) +{ + AssetFileName = InFileName; + + // Calculate buffer size. + AssetBytesCount = BufferEnd - BufferStart; + + if (AssetBytesCount) + { + // Allocate buffer to store the raw data. + AssetBytes.SetNumUninitialized(AssetBytesCount); + // Copy data into the newly allocated buffer. + FMemory::Memcpy(AssetBytes.GetData(), BufferStart, AssetBytesCount); + } + + FString FileExtension = FPaths::GetExtension(InFileName); + + // Expanded HDAs are imported via a "houdini.hdalibrary" file inside the .hda directory + // Identify them first, then update the file path to point to the .hda dir + if (FileExtension.Equals(TEXT("hdalibrary"), ESearchCase::IgnoreCase)) + { + bAssetExpanded = true; + + // Use the parent ".hda" directory as the filename + AssetFileName = FPaths::GetPath(AssetFileName); + FileExtension = FPaths::GetExtension(AssetFileName); + } + + if (FileExtension.Equals(TEXT("hdalc"), ESearchCase::IgnoreCase) + || FileExtension.Equals(TEXT("otlc"), ESearchCase::IgnoreCase)) + { + // Check if the HDA is limited (Indie) ... + bAssetLimitedCommercial = true; + } + else if (FileExtension.Equals(TEXT("hdanc"), ESearchCase::IgnoreCase) + || FileExtension.Equals(TEXT("otlnc"), ESearchCase::IgnoreCase)) + { + // ... or non commercial (Apprentice) + bAssetNonCommercial = true; + } +} + +void +UHoudiniAsset::FinishDestroy() +{ + // Release buffer which was used to store raw OTL data. + AssetBytes.Empty(); + Super::FinishDestroy(); +} + +const uint8 * +UHoudiniAsset::GetAssetBytes() const +{ + return AssetBytes.GetData(); +} + +const FString & +UHoudiniAsset::GetAssetFileName() const +{ + return AssetFileName; +} + +uint32 +UHoudiniAsset::GetAssetBytesCount() const +{ + return AssetBytesCount; +} + +void +UHoudiniAsset::Serialize(FArchive & Ar) +{ + // Serializes our UProperties + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Get the version + uint32 HoudiniAssetVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + + // Only version 1 assets needs manual serialization + if ( HoudiniAssetVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE + || HoudiniAssetVersion > VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION ) + return SerializeLegacy(Ar); +} + +void +UHoudiniAsset::SerializeLegacy(FArchive & Ar) +{ + uint32 FileFormatVersion; + Ar << FileFormatVersion; + + Ar << AssetBytesCount; + if (Ar.IsLoading()) + { + // Allocate sufficient space to read stored raw OTL data. + AssetBytes.SetNumUninitialized(AssetBytesCount); + } + + if (AssetBytesCount) + Ar.Serialize(AssetBytes.GetData(), AssetBytesCount); + + // Serialized flags. + union + { + struct + { + uint32 bLegacyPreviewHoudiniLogo : 1; + uint32 bLegacyAssetLimitedCommercial : 1; + uint32 bLegacyAssetNonCommercial : 1; + }; + uint32 HoudiniAssetFlagsPacked; + }; + Ar << HoudiniAssetFlagsPacked; + + bAssetNonCommercial = bLegacyAssetNonCommercial; + bAssetLimitedCommercial = bLegacyAssetLimitedCommercial; + + // Serialize asset file path. + Ar << AssetFileName; +} + +void +UHoudiniAsset::GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const +{ + // Filename + OutTags.Add(FAssetRegistryTag("FileName", AssetFileName, FAssetRegistryTag::TT_Alphabetical)); + + // Bytes + OutTags.Add(FAssetRegistryTag("Bytes", FString::FromInt(AssetBytesCount), FAssetRegistryTag::TT_Numerical)); + + // Indicate if the Asset is Full / Indie / NC + FString AssetType = TEXT("Full"); + if (bAssetLimitedCommercial) + AssetType = TEXT("Limited Commercial (LC)"); + else if (bAssetNonCommercial) + AssetType = TEXT("Non Commercial (NC)"); + + OutTags.Add(FAssetRegistryTag("Asset Type", AssetType, FAssetRegistryTag::TT_Alphabetical)); + + Super::GetAssetRegistryTags(OutTags); +} + +bool +UHoudiniAsset::IsAssetLimitedCommercial() const +{ + return bAssetLimitedCommercial; +} + +bool +UHoudiniAsset::IsAssetNonCommercial() const +{ + return bAssetNonCommercial; +} + +bool +UHoudiniAsset::IsExpandedHDA() const +{ + return bAssetExpanded; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h index 676a3db95..0fa3734cb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h @@ -1,104 +1,104 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/Object.h" -#include "HoudiniAsset.generated.h" - -class UAssetImportData; - -UCLASS(BlueprintType, EditInlineNew, config = Engine) -class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject -{ - GENERATED_UCLASS_BODY() - - public: - - // UOBject functions - virtual void FinishDestroy() override; - virtual void Serialize(FArchive & Ar) override; - virtual void GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const override; - - // Creates and initialize this asset from a given buffer / file. - void CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName); - - // Return buffer containing the raw Houdini OTL data. - const uint8* GetAssetBytes() const; - - // Return path of the corresponding OTL/HDA file. - const FString& GetAssetFileName() const; - - // Return the size in bytes of raw Houdini OTL data. - uint32 GetAssetBytesCount() const; - - // Return true if this asset is a limited commercial asset. - bool IsAssetLimitedCommercial() const; - - // Return true if this asset is a non commercial asset. - bool IsAssetNonCommercial() const; - - // Return true if this asset is an expanded HDA (HDA dir) - bool IsExpandedHDA() const; - - private: - // Used to load old (version1) versions of HoudiniAssets - void SerializeLegacy(FArchive & Ar); - - public: - - // Source filename of the OTL. - UPROPERTY() - FString AssetFileName; - -#if WITH_EDITORONLY_DATA - // Importing data and options used for this Houdini asset. - UPROPERTY(Category = ImportSettings, VisibleAnywhere, Instanced) - UAssetImportData * AssetImportData; -#endif - - private: - - // Buffer containing the raw HDA OTL data. - UPROPERTY() - TArray AssetBytes; - - // Size in bytes of the raw HDA data. - UPROPERTY() - uint32 AssetBytesCount; - - // Indicates if this is a limited commercial asset. - UPROPERTY() - bool bAssetLimitedCommercial; - - // Indicates if this is a non-commercial license asset. - UPROPERTY() - bool bAssetNonCommercial; - - // Indicates if this is an expanded HDA file - UPROPERTY() - bool bAssetExpanded; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/Object.h" +#include "HoudiniAsset.generated.h" + +class UAssetImportData; + +UCLASS(BlueprintType, EditInlineNew, config = Engine) +class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject +{ + GENERATED_UCLASS_BODY() + + public: + + // UOBject functions + virtual void FinishDestroy() override; + virtual void Serialize(FArchive & Ar) override; + virtual void GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const override; + + // Creates and initialize this asset from a given buffer / file. + void CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName); + + // Return buffer containing the raw Houdini OTL data. + const uint8* GetAssetBytes() const; + + // Return path of the corresponding OTL/HDA file. + const FString& GetAssetFileName() const; + + // Return the size in bytes of raw Houdini OTL data. + uint32 GetAssetBytesCount() const; + + // Return true if this asset is a limited commercial asset. + bool IsAssetLimitedCommercial() const; + + // Return true if this asset is a non commercial asset. + bool IsAssetNonCommercial() const; + + // Return true if this asset is an expanded HDA (HDA dir) + bool IsExpandedHDA() const; + + private: + // Used to load old (version1) versions of HoudiniAssets + void SerializeLegacy(FArchive & Ar); + + public: + + // Source filename of the OTL. + UPROPERTY() + FString AssetFileName; + +#if WITH_EDITORONLY_DATA + // Importing data and options used for this Houdini asset. + UPROPERTY(Category = ImportSettings, VisibleAnywhere, Instanced) + UAssetImportData * AssetImportData; +#endif + + private: + + // Buffer containing the raw HDA OTL data. + UPROPERTY() + TArray AssetBytes; + + // Size in bytes of the raw HDA data. + UPROPERTY() + uint32 AssetBytesCount; + + // Indicates if this is a limited commercial asset. + UPROPERTY() + bool bAssetLimitedCommercial; + + // Indicates if this is a non-commercial license asset. + UPROPERTY() + bool bAssetNonCommercial; + + // Indicates if this is an expanded HDA file + UPROPERTY() + bool bAssetExpanded; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp index 5ad5af6d2..c00daee6a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp @@ -1,145 +1,145 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniPDGAssetLink.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -AHoudiniAssetActor::AHoudiniAssetActor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - SetCanBeDamaged(false); - //PrimaryActorTick.bCanEverTick = true; - //PrimaryActorTick.bStartWithTickEnabled = true; - - // Create Houdini component and attach it to a root component. - HoudiniAssetComponent = - ObjectInitializer.CreateDefaultSubobject< UHoudiniAssetComponent >(this, TEXT("HoudiniAssetComponent")); - - //HoudiniAssetComponent->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); - - RootComponent = HoudiniAssetComponent; -} - -UHoudiniAssetComponent * -AHoudiniAssetActor::GetHoudiniAssetComponent() const -{ - return HoudiniAssetComponent; -} - -/* -#if WITH_EDITOR -bool -AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) -{ - if (!ActorPropString) - return false; - - // Locate actor which is being copied in clipboard string. - AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor(this, *ActorPropString); - - // We no longer need clipboard string and can empty it. This seems to avoid occasional crash bug in UE4 which - // happens on copy / paste. - ActorPropString->Empty(); - - if (!CopiedActor || CopiedActor->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import from copy: Duplicated actor not found")); - return false; - } - - // Get Houdini component of an actor which is being copied. - UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; - if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill()) - return false; - - HoudiniAssetComponent->OnComponentClipboardCopy(CopiedActorHoudiniAssetComponent); - - // If actor is copied through moving, we need to copy main transform. - const FTransform & ComponentWorldTransform = CopiedActorHoudiniAssetComponent->GetComponentTransform(); - HoudiniAssetComponent->SetWorldLocationAndRotation( - ComponentWorldTransform.GetLocation(), - ComponentWorldTransform.GetRotation()); - - // We also need to copy actor label. - const FString & CopiedActorLabel = CopiedActor->GetActorLabel(); - FActorLabelUtilities::SetActorLabelUnique(this, CopiedActorLabel); - - return true; -} -#endif -*/ -#if WITH_EDITOR -bool -AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) const -{ - Super::GetReferencedContentObjects(Objects); - - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) - { - UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); - if (HoudiniAsset && !HoudiniAsset->IsPendingKill()) - Objects.AddUnique(HoudiniAsset); - } - - return true; -} -#endif - -#if WITH_EDITOR -void -AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - // Some property changes need to be forwarded to the component (ie Transform) - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return; - - FProperty* Property = PropertyChangedEvent.MemberProperty; - if (!Property) - return; - - FName PropertyName = Property->GetFName(); - if (PropertyName == HoudiniAssetComponent->GetRelativeLocationPropertyName() - || PropertyName == HoudiniAssetComponent->GetRelativeRotationPropertyName() - || PropertyName == HoudiniAssetComponent->GetRelativeScale3DPropertyName()) - { - HoudiniAssetComponent->SetHasComponentTransformChanged(true); - } -} -#endif - - -bool -AHoudiniAssetActor::IsUsedForPreview() const -{ - return HasAnyFlags(RF_Transient); -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniPDGAssetLink.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +AHoudiniAssetActor::AHoudiniAssetActor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + SetCanBeDamaged(false); + //PrimaryActorTick.bCanEverTick = true; + //PrimaryActorTick.bStartWithTickEnabled = true; + + // Create Houdini component and attach it to a root component. + HoudiniAssetComponent = + ObjectInitializer.CreateDefaultSubobject< UHoudiniAssetComponent >(this, TEXT("HoudiniAssetComponent")); + + //HoudiniAssetComponent->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); + + RootComponent = HoudiniAssetComponent; +} + +UHoudiniAssetComponent * +AHoudiniAssetActor::GetHoudiniAssetComponent() const +{ + return HoudiniAssetComponent; +} + +/* +#if WITH_EDITOR +bool +AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) +{ + if (!ActorPropString) + return false; + + // Locate actor which is being copied in clipboard string. + AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor(this, *ActorPropString); + + // We no longer need clipboard string and can empty it. This seems to avoid occasional crash bug in UE4 which + // happens on copy / paste. + ActorPropString->Empty(); + + if (!CopiedActor || CopiedActor->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import from copy: Duplicated actor not found")); + return false; + } + + // Get Houdini component of an actor which is being copied. + UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; + if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill()) + return false; + + HoudiniAssetComponent->OnComponentClipboardCopy(CopiedActorHoudiniAssetComponent); + + // If actor is copied through moving, we need to copy main transform. + const FTransform & ComponentWorldTransform = CopiedActorHoudiniAssetComponent->GetComponentTransform(); + HoudiniAssetComponent->SetWorldLocationAndRotation( + ComponentWorldTransform.GetLocation(), + ComponentWorldTransform.GetRotation()); + + // We also need to copy actor label. + const FString & CopiedActorLabel = CopiedActor->GetActorLabel(); + FActorLabelUtilities::SetActorLabelUnique(this, CopiedActorLabel); + + return true; +} +#endif +*/ +#if WITH_EDITOR +bool +AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) const +{ + Super::GetReferencedContentObjects(Objects); + + if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + { + UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); + if (HoudiniAsset && !HoudiniAsset->IsPendingKill()) + Objects.AddUnique(HoudiniAsset); + } + + return true; +} +#endif + +#if WITH_EDITOR +void +AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + // Some property changes need to be forwarded to the component (ie Transform) + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return; + + FProperty* Property = PropertyChangedEvent.MemberProperty; + if (!Property) + return; + + FName PropertyName = Property->GetFName(); + if (PropertyName == HoudiniAssetComponent->GetRelativeLocationPropertyName() + || PropertyName == HoudiniAssetComponent->GetRelativeRotationPropertyName() + || PropertyName == HoudiniAssetComponent->GetRelativeScale3DPropertyName()) + { + HoudiniAssetComponent->SetHasComponentTransformChanged(true); + } +} +#endif + + +bool +AHoudiniAssetActor::IsUsedForPreview() const +{ + return HasAnyFlags(RF_Transient); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h index 39079d617..f081efc07 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h @@ -1,77 +1,77 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" - -#include "UObject/ObjectMacros.h" -#include "Components/ActorComponent.h" -#include "GameFramework/Actor.h" - -#include "HoudiniAssetActor.generated.h" - -class UHoudiniPDGAssetLink; - -UCLASS(hidecategories = (Input), ConversionRoot, meta = (ChildCanTick), Blueprintable) -class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor -{ - GENERATED_UCLASS_BODY() - - // Pointer to the root HoudiniAssetComponent - UPROPERTY(Category = HoudiniAssetActor, VisibleAnywhere, BlueprintReadOnly, meta = (ExposeFunctionCategories = "Mesh,Rendering,Physics,Components|HoudiniEngine")/*, AllowPrivateAccess = "true"*/) - UHoudiniAssetComponent * HoudiniAssetComponent; - -public: - - // Returns the actor's houdini component. - UHoudiniAssetComponent* GetHoudiniAssetComponent() const; - - bool IsUsedForPreview() const; - - // Gets the Houdini PDG asset link associated with this actor, if it has one. - UHoudiniPDGAssetLink* GetPDGAssetLink() const { return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; } - -#if WITH_EDITOR - - // Called after a property has been changed - // Used to forward property changes to the HAC - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; - - // Used by the "Sync to Content Browser" right-click menu option in the editor. - virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; - -/* -public: - - // Called before editor paste, true allow import - virtual bool ShouldImport(FString * ActorPropString, bool IsMovingLevel) override; - - // Used by the "Sync to Content Browser" right-click menu option in the editor. - virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; -*/ -#endif -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" + +#include "UObject/ObjectMacros.h" +#include "Components/ActorComponent.h" +#include "GameFramework/Actor.h" + +#include "HoudiniAssetActor.generated.h" + +class UHoudiniPDGAssetLink; + +UCLASS(hidecategories = (Input), ConversionRoot, meta = (ChildCanTick), Blueprintable) +class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor +{ + GENERATED_UCLASS_BODY() + + // Pointer to the root HoudiniAssetComponent + UPROPERTY(Category = HoudiniAssetActor, VisibleAnywhere, BlueprintReadOnly, meta = (ExposeFunctionCategories = "Mesh,Rendering,Physics,Components|HoudiniEngine")/*, AllowPrivateAccess = "true"*/) + UHoudiniAssetComponent * HoudiniAssetComponent; + +public: + + // Returns the actor's houdini component. + UHoudiniAssetComponent* GetHoudiniAssetComponent() const; + + bool IsUsedForPreview() const; + + // Gets the Houdini PDG asset link associated with this actor, if it has one. + UHoudiniPDGAssetLink* GetPDGAssetLink() const { return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; } + +#if WITH_EDITOR + + // Called after a property has been changed + // Used to forward property changes to the HAC + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; + + // Used by the "Sync to Content Browser" right-click menu option in the editor. + virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; + +/* +public: + + // Called before editor paste, true allow import + virtual bool ShouldImport(FString * ActorPropString, bool IsMovingLevel) override; + + // Used by the "Sync to Content Browser" right-click menu option in the editor. + virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; +*/ +#endif +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp index 113b0f8ef..cd4b5f54c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp @@ -1,2369 +1,2368 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniAssetBlueprintComponent.h" - -#include "HoudiniEngineCopyPropertiesInterface.h" -#include "HoudiniOutput.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Engine/SCS_Node.h" -#include "Engine/SimpleConstructionScript.h" -#include "UObject/Object.h" -#include "Logging/LogMacros.h" - -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniInput.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Kismet2/BlueprintEditorUtils.h" - #include "Kismet2/KismetEditorUtilities.h" - #include "Toolkits/AssetEditorManager.h" - #include "Kismet2/ComponentEditorUtils.h" - #include "ComponentAssetBroker.h" -#endif - -HOUDINI_BP_DEFINE_LOG_CATEGORY(); - -UHoudiniAssetBlueprintComponent::UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - -#if WITH_EDITOR - if (IsTemplate()) - { - // CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); - //GEditor->GetEditorSubsystem()->OnAssetEditorRequestClose().AddUObject( this, &UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent ); - } -#endif - - bForceNeedUpdate = false; - bHoudiniAssetChanged = false; - bIsInBlueprintEditor = false; - bCanDeleteHoudiniNodes = false; - - // AssetState will be updated by changes to the HoudiniAsset - // or parameter changes on the Component template. - AssetState = EHoudiniAssetState::None; - bHasRegisteredComponentTemplate = false; - bHasBeenLoaded = false; - bUpdatedFromTemplate = false; - - // Disable proxy mesh by default (unsupported for now) - bOverrideGlobalProxyStaticMeshSettings = true; - bEnableProxyStaticMeshOverride = false; - bEnableProxyStaticMeshRefinementByTimerOverride = false; - bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = false; - bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = false; - StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; - - // Set default mobility to Movable - Mobility = EComponentMobility::Movable; -} - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() -{ - // We need to propagate changes made here back to the corresponding component in - // the Blueprint Generated Class ("_GEN_VARIABLE"). The reason being that - // the Blueprint editor works directly with the GEN_VARIABLE component (all - // PostEditChange() calls, Details Customizations, etc will receive the GEN_VARIABLE instance) BUT - // when the Editor runs the construction script it uses a different component instance, so all changes - // made to that instance won't write back to the Blueprint definition. - // To Summarize: - // Be sure to sync the Parameters array (and any other relevant properties) back - // to the corresponding component on the Blueprint Generated class otherwise these wont be - // accessible in the Details Customization callbacks. - - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] To Component: %s"), *(CachedTemplateComponent->GetPathName())); - - // This should never be called by component templates. - check(!IsTemplate()); - - if (!CachedTemplateComponent.IsValid()) - return; - - USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; - check(SCS); - - /* - USCS_Node* SCSNodeForInstance = FindSCSNodeForInstanceComponent(SCS, this); - if (SCSNodeForInstance) - { - - } - else - { - - } - */ - - //// If we don't have an SCS node for this preview instance, we need to create one, regardless - //// of whether output updates are required. - //if (!CachedTemplateComponent->bOutputsRequireUpdate && SCSNodeForInstance != nullptr) - // return; - - // TODO: If the blueprint editor is NOT open, then we shouldn't attempting - // to copy state back to the BPGC at all! - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - check(BlueprintEditor); - - TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); - check(SCSEditor); - - USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - // check(SCSHACNode); - - // This is the actor instance that is being used for component editing. - AActor* PreviewActor = GetPreviewActor(); - check(PreviewActor); - - // NOTE: Inputs are only from component templates to instances, not the other way around ... I think. - - // ----------------------------------------------------- - // Copy outputs to component template - // ----------------------------------------------------- - - // Populate / update the outputs for the template from the preview / instance. - // TODO: Wrap the Blueprint manipulation in a transaction - TArray& TemplateOutputs = CachedTemplateComponent->Outputs; - TSet StaleTemplateOutputs(TemplateOutputs); - - TemplateOutputs.SetNum(Outputs.Num()); - CachedOutputNodes.Empty(); - - for (int i = 0; i < Outputs.Num(); i++) - { - // Find a output on the template that corresponds to this output from the instance. - UHoudiniOutput* TemplateOutput = nullptr; - UHoudiniOutput* InstanceOutput = nullptr; - InstanceOutput = Outputs[i]; - - check(InstanceOutput) - // Ensure that instance outputs won't delete houdini content. - // Houdini content should only be allowed to be deleted from - // the component template. - InstanceOutput->SetCanDeleteHoudiniNodes(false); - - TemplateOutput = TemplateOutputs[i]; - - if (TemplateOutput) - { - check(TemplateOutput->GetOuter() == CachedTemplateComponent.Get()); - StaleTemplateOutputs.Remove(TemplateOutput); - } - - - if (TemplateOutput) - { - // Copy properties from the current instance component while preserving output objects - // and instanced outputs. - TemplateOutput->CopyPropertiesFrom(InstanceOutput, true); - } - else - { - // NOTE: If the template output is NULL it means that the HDA spawned a new component / output in the transient world - // and the new output object needs to be copied back to the BPGC. - - // Corresponding template output could not be found. Create one by duplicating the instance output. - TemplateOutput = InstanceOutput->DuplicateAndCopyProperties(CachedTemplateComponent.Get(), FName(InstanceOutput->GetName())); - // Treat these the same one would components created by CreateDefaultSubObject. - // NOTE: CreateDefaultSubobject performs lots of checks, and unfortunately we can't use it directly (it is - // only allowed to be used in a constructor). Not sure whether we need to either. For now, we just set the - // object flags to be similar to components created by CreateDefaultSubobject. - TemplateOutput->SetFlags(RF_Public|RF_ArchetypeObject|RF_DefaultSubObject); - TemplateOutputs[i] = TemplateOutput; - } - - check(TemplateOutput); - TemplateOutput->SetCanDeleteHoudiniNodes(false); - - // Keep track of potential stale output objects on the template component, for this output. - TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); - TArray StaleTemplateObjects; - TemplateOutputObjects.GetKeys(StaleTemplateObjects); - - for (auto& Entry : InstanceOutput->GetOutputObjects()) - { - - // Prepare the FHoudiniOutputObject for the template component - const FHoudiniOutputObject& InstanceObj = Entry.Value; - FHoudiniOutputObject TemplateObj; - - // Any output present in the Instance Outputs should be - // transferred to the template. - // Remove this output object from stale outputs list. - StaleTemplateObjects.Remove(Entry.Key); - - if (TemplateOutputObjects.Contains(Entry.Key)) - { - // Reuse the existing template object - TemplateObj = TemplateOutputObjects.FindChecked(Entry.Key); - } - else - { - // Create a new template output object object by duplicating the instance object. - // Keep the output object, but clear the output component since we have to - // create a new component template. - TemplateObj = InstanceObj; - TemplateObj.ProxyComponent = nullptr; - TemplateObj.OutputComponent = nullptr; - TemplateObj.ProxyObject = nullptr; - } - - USceneComponent* ComponentInstance = Cast(InstanceObj.OutputComponent); - USceneComponent* ComponentTemplate = Cast(TemplateObj.OutputComponent); - UObject* OutputObject = InstanceObj.OutputObject; - - if (ComponentInstance) - { - // The translation process has either constructed new components, or it is - // reusing existing components, or changed an output (or all or none of the aforementioned). - // Carefully inspect the SCS graph to determine whether there is a corresponding - // (and compatible) node for this output. If not, create a new node and remove unusable node, if any. - - USCS_Node* ComponentNode = nullptr; - { - // Check whether the current OutputComponent being referenced by the template is still valid. - // Even if it was removed in the editor, it doesn't have any associated destroyed / pendingkill state. - // Instead we're going to check for validity by finding an SCS node with a matching template component. - bool bValidComponentTemplate = (ComponentTemplate != nullptr); - if (ComponentTemplate) - { - - ComponentNode = FindSCSNodeForTemplateComponentInClassHierarchy(ComponentTemplate); - bValidComponentTemplate = bValidComponentTemplate && (ComponentNode != nullptr); - } - - if (!bValidComponentTemplate) - { - // Either this component was removed from the editor or it doesn't exist yet. - // Ensure the references are cleared - TemplateObj.OutputComponent = nullptr; - ComponentTemplate = nullptr; - } - } - - // NOTE: we can't use the component instance name directly due to the Blueprint compiler performing an internal checking - // using FComponentEditorUtils::IsValidVariableNameString(), which will return false if the name looks like an autogenerated name... - //FString ComponentName = ComponentInstance->GetName(); - FString ComponentName = FBlueprintEditorUtils::GetClassNameWithoutSuffix(ComponentInstance->GetClass()); - FName ComponentFName = FName(ComponentName); - - - const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )( - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | - EditorUtilities::ECopyOptions::CallPostEditChangeProperty | - EditorUtilities::ECopyOptions::CallPostEditMove); - - if (IsValid(ComponentNode)) - { - // Check if we have an existing and compatible SCS node containing a USceneComponent as a template component. - bool bComponentNodeIsValid = true; - - ComponentTemplate = Cast(ComponentNode->ComponentTemplate); - - bComponentNodeIsValid = bComponentNodeIsValid && ComponentInstance->GetClass() == ComponentNode->ComponentClass; - bComponentNodeIsValid = bComponentNodeIsValid && ComponentTemplate != nullptr; - // TODO: Do we need to perform any other compatibility checks? - - if (!bComponentNodeIsValid) - { - // Component template is not compatible. We can't reuse it. - - SCSHACNode->RemoveChildNode(ComponentNode); - SCS->RemoveNode(ComponentNode); - ComponentNode = nullptr; - ComponentTemplate = nullptr; - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - } - - if (ComponentNode) - { - // We found a reusable SCS node. Just copy the component instance - // properties over to the existing template. - check(ComponentNode->ComponentTemplate); - - // UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - // //Params.bReplaceObjectClassReferences = false; - // Params.bDoDelta = false; // Perform a deep copy - // Params.bClearReferences = false; - // UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - - FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - - ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; - } - else - { - // We couldn't find a reusable SCS node. - // Duplicate the instance component and create a new corresponding SCS node - ComponentNode = SCS->CreateNode(ComponentInstance->GetClass(), ComponentFName); - - UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // We need a deep copy of parameters here so the CDO values get copied as well - UEditorEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - // FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); - - // { - // UInstancedStaticMeshComponent* Component = Cast(ComponentNode->ComponentTemplate); - // if (Component) - // { - // } - // } - - // NOTE: The EComponentCreationMethod here is currently set to be the same as a component that was - // created manually in the editor. - ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; - - // Add this node to the SCS root set. - - // Attach the new node the HAC SCS node - // NOTE: This will add the node to the SCS->AllNodes list too but it won't update - // the nodename map. We can't forcibly update the Node/Name map either since the - // relevant functions have not been exported. - SCSHACNode->AddChildNode(ComponentNode); - - // Set the output component. - TemplateObj.OutputComponent = ComponentNode->ComponentTemplate; - - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - - // Cache the mapping between the output and the SCS node. - check(ComponentNode); - CachedOutputNodes.Add(Entry.Key, ComponentNode->VariableGuid); - } // if (ComponentInstance) - /* - else if (InstanceObj.OutputObject) - { - - } - */ - - // Add the updated output object to the template output - TemplateOutputObjects.Add(Entry.Key, TemplateObj); - } - - // Cleanup stale objects for this template output. - for (const auto& StaleId : StaleTemplateObjects) - { - FHoudiniOutputObject& OutputObj = TemplateOutputObjects.FindChecked(StaleId); - - // Ensure the component template is no longer referencing this output. - TemplateOutputObjects.Remove(StaleId); - - USceneComponent* TemplateComponent = Cast(OutputObj.OutputComponent); - - if (TemplateComponent) - { - USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(TemplateComponent); - if (StaleNode) - { - - SCS->RemoveNode(StaleNode, false); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - /* - else - { - - } - */ - } - /* - else - { - - } - */ - } - } //for (int i = 0; i < Outputs.Num(); i++) - - // Clean up stale outputs on the component template. - for (UHoudiniOutput* StaleOutput : StaleTemplateOutputs) - { - if (!StaleOutput) - continue; - - // Remove any components contained in this output from the SCS graph - for (auto& Entry : StaleOutput->GetOutputObjects()) - { - FHoudiniOutputObject& StaleObject = Entry.Value; - USceneComponent* OutputComponent = Cast(StaleObject.OutputComponent); - - if (OutputComponent) - { - - USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(OutputComponent); - if (StaleNode) - { - - SCS->RemoveNode(StaleNode, false); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - } - } - - TemplateOutputs.Remove(StaleOutput); - //StaleOutput->ConditionalBeginDestroy(); - } - - SCS->ValidateSceneRootNodes(); - - // Copy parameters from this component to the template component. - // NOTE: We need to do this since the preview component will be cooking the HDA and get populated with - // all the parameters. This data needs to be sent back to the component template. - UClass* ComponentClass = CachedTemplateComponent->GetClass(); - UHoudiniAssetBlueprintComponent* DefaultObj = Cast(ComponentClass->GetDefaultObject()); - bool bBPStructureModified = false; - CachedTemplateComponent->CopyDetailsFromComponent( - this, - true, - true, - true, - false, - true, - bBPStructureModified, - /* SetFlags */ CachedTemplateComponent->GetMaskedFlags(RF_PropagateToSubObjects)); - - if (bBPStructureModified) - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - - // Copy the cached output nodes back to the template so that - // reconstructed actors can correctly update output objects - // with newly constructed components during ApplyComponentInstanceData() calls. - CachedTemplateComponent->CachedOutputNodes = CachedOutputNodes; - - CachedTemplateComponent->MarkPackageDirty(); - PostEditChange(); - - CachedTemplateComponent->AssetId = AssetId; - CachedTemplateComponent->HapiGUID = HapiGUID; - CachedTemplateComponent->AssetCookCount = AssetCookCount; - CachedTemplateComponent->AssetStateResult = AssetStateResult; - CachedTemplateComponent->bLastCookSuccess = bLastCookSuccess; - -#if WITH_EDITOR - // TODO: Do we need to handle this right now or can we wait for the next Houdini Engine manager tick to process it? - if (CachedTemplateComponent->NeedBlueprintStructureUpdate()) - { - // We are about to recompile the blueprint. This will reconstruct the preview actor so we need to ensure - // that the old actor won't release the houdini nodes. - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); - SetCanDeleteHoudiniNodes(false); - } - /*else if (CachedTemplateComponent->NeedBlueprintUpdate()) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(CachedTemplateComponent.Get()); - }*/ -#endif -} -#endif - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties) -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] From Component: %s"), *(FromComponent->GetPathName())); - - // This should never be called by component templates. - check(!IsTemplate()); - - // Make sure all TransientDuplicate properties from the Template Component needed by this transient component - // gets copied. - - ComponentGUID = FromComponent->ComponentGUID; - - /* - { - const TArray Children = GetAttachChildren(); - for (USceneComponent* Child : Children) - { - if (!Child) - continue; - } - } - */ - - // AssetState = FromComponent->PreviewAssetState; - - // This state should not be shared between template / instance components. - //bFullyLoaded = FromComponent->bFullyLoaded; - - bNoProxyMeshNextCookRequested = FromComponent->bNoProxyMeshNextCookRequested; - - // Reconstruct outputs and update them to point to component instances as opposed to templates. - UObject* TemplateOuter = CachedTemplateComponent->GetOuter(); - - USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; - check(SCS); - - // NOTE: We can find the SCS node for the HoudiniAssetComponent from either the template component or the instance (editor preview) component. - USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - check(SCSHACNode); - - // ----------------------------------------------------- - // Copy outputs to component template - // ----------------------------------------------------- - - TArray& TemplateOutputs = CachedTemplateComponent->Outputs; - - TSet StaleInstanceOutputs(Outputs); - - Outputs.SetNum(TemplateOutputs.Num()); - - for (int i = 0; i < TemplateOutputs.Num(); i++) - { - UHoudiniOutput* TemplateOutput = TemplateOutputs[i]; - if (!IsValid(TemplateOutput)) - continue; - - UHoudiniOutput* InstanceOutput = Outputs[i]; - if (!(InstanceOutput->GetOuter() == this)) - InstanceOutput = nullptr; - - if (InstanceOutput) - { - StaleInstanceOutputs.Remove(InstanceOutput); - } - - if (InstanceOutput) - { - // Copy properties from the current instance component while preserving output objects - // and instanced outputs. - InstanceOutput->CopyPropertiesFrom(TemplateOutput, true); - } - else - { - InstanceOutput = TemplateOutput->DuplicateAndCopyProperties(this, FName(TemplateOutput->GetName())); - InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); - } - - InstanceOutput->SetCanDeleteHoudiniNodes(false); - Outputs[i] = InstanceOutput; - - TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); - TMap& InstanceOutputObjects = InstanceOutput->GetOutputObjects(); - TArray StaleOutputObjects; - InstanceOutputObjects.GetKeys(StaleOutputObjects); - - for (auto& Entry : TemplateOutputObjects) - { - const FHoudiniOutputObject& TemplateObj = Entry.Value; - FHoudiniOutputObject InstanceObj = TemplateObj; - - if (!InstanceOutputObjects.Contains(Entry.Key)) - continue; - - StaleOutputObjects.Remove(Entry.Key); - InstanceObj = InstanceOutputObjects.FindChecked(Entry.Key); - - } // for (auto& Entry : TemplateOutputObjects) - - // Cleanup stale output objects for this output. - for (const auto& StaleId : StaleOutputObjects) - { - //TemplateOutput - //check(TemplateOutputs); - - FHoudiniOutputObject& OutputObj = InstanceOutputObjects.FindChecked(StaleId); - - InstanceOutputObjects.Remove(StaleId); - if (OutputObj.OutputComponent) - { - //OutputObj.OutputComponent->ConditionalBeginDestroy(); - OutputObj.OutputComponent = nullptr; - } - } - } // for (int i = 0; i < TemplateOutputs.Num(); i++) - - // Cleanup any stale outputs found on the component instance. - for (UHoudiniOutput* StaleOutput : StaleInstanceOutputs) - { - if (!StaleOutput) - continue; - - if (!(StaleOutput->GetOuter() == this)) - continue; - - // We don't want to clear stale outputs on components instances. Only on template components. - StaleOutput->SetCanDeleteHoudiniNodes(false); - } - - // Copy parameters from the component template to the instance. - bool bBlueprintStructureChanged = false; - CopyDetailsFromComponent(FromComponent, - false, - bClearFromInputs, - bClearToInputs, - false, - true, - bBlueprintStructureChanged, - /*SetFlags*/ RF_Public, - /*ClearFlags*/ RF_DefaultSubObject|RF_ArchetypeObject); -} -#endif - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent( - UHoudiniAssetBlueprintComponent* FromComponent, - const bool bCreateSCSNodes, - const bool bClearChangedToInputs, - const bool bClearChangedFromInputs, - const bool bInCanDeleteHoudiniNodes, - const bool bCopyInputObjectComponentProperties, - bool &bOutBlueprintStructureChanged, - EObjectFlags SetFlags, - EObjectFlags ClearFlags) -{ - check(FromComponent); - - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] FromComponent: %s"), *(FromComponent->GetPathName())); - - /* - if (!FromComponent->HoudiniAsset) - { - return; - } - */ - - // TODO: Try to reuse objects here when we're able. - //// Copy UHoudiniOutput state from instance to template - //UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - ////Params.bReplaceObjectClassReferences = false; - ////Params.bClearReferences = false; - //Params.bDoDelta = true; - //UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - - // Record input remapping that will need to take place when duplicating parameters. - TMap InputMapping; - - // ----------------------------------------------------- - // Copy inputs - // ----------------------------------------------------- - - // TODO: Add support for input components - { - TArray& FromInputs = FromComponent->Inputs; - TSet StaleInputs(Inputs); - USimpleConstructionScript* SCS = GetSCS(); - USCS_Node* SCSHACNode = nullptr; - - if (bCreateSCSNodes) - { - SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - } - - Inputs.SetNum(FromInputs.Num()); - for (int i = 0; i < FromInputs.Num(); i++) - { - UHoudiniInput* FromInput = nullptr; - UHoudiniInput* ToInput = nullptr; - FromInput = FromInputs[i]; - - check(FromInput); - - ToInput = Inputs[i]; - - if (ToInput) - { - // Check whether the instance and template input objects are compatible. - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - - if (!bIsValid) - { - ToInput = nullptr; - } - } - - // TODO: Process stale input objects - - // NOTE: The CopyStateFrom() / DuplicateAndCopyState() will copy/duplicate/cleanup internal inputs to - // ensure that there aren't any shared instances between the ToInput/FromInput. - if (ToInput) - { - // We have a compatible input that we can reuse. - StaleInputs.Remove(ToInput); - ToInput->CopyStateFrom(FromInput, true, bInCanDeleteHoudiniNodes); - } - else - { - - // We don't have an existing / compatible input. Create a new one. - ToInput = FromInput->DuplicateAndCopyState(this, bInCanDeleteHoudiniNodes); - if (SetFlags != RF_NoFlags) - ToInput->SetFlags(SetFlags); - if (ClearFlags != RF_NoFlags) - ToInput->ClearFlags( ClearFlags ); - } - - check(ToInput); - - - UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, bCopyInputObjectComponentProperties, bCreateSCSNodes, SCSHACNode, &bOutBlueprintStructureChanged); - - Inputs[i] = ToInput; - InputMapping.Add(FromInput, ToInput); - - if (bClearChangedToInputs) - { - // Clear the changed flags on the FromInput so that it doesn't trigger - // another update. The ToInput will now be carrying to changed/update flags. - ToInput->MarkChanged(false); - ToInput->MarkAllInputObjectsChanged(false); - } - - if (bClearChangedFromInputs) - { - // Clear the changed flags on the FromInput so that it doesn't trigger - // another update. The ToInput will now be carrying to changed/update flags. - FromInput->MarkChanged(false); - FromInput->MarkAllInputObjectsChanged(false); - } - } - - // Cleanup any stale inputs from this component. - // NOTE: We would typically only have stale inputs when copying state from - // the component instance to the component template. Garbage collection - // eventually picks up the input objects and removes the content - // but until such time we are stuck with those nodes as inputs in the Houdini session - // so we get rid of those nodes immediately here to avoid some user confusion. - for (UHoudiniInput* StaleInput : StaleInputs) - { - if (!IsValid(StaleInput)) - continue; - - check(StaleInput->GetOuter() == this); - - if (StaleInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - StaleInput->ConditionalBeginDestroy(); - } - } - - - // ----------------------------------------------------- - // Copy parameters (and optionally remap inputs). - // ----------------------------------------------------- - TMap ParameterMapping; - - TArray& FromParameters = FromComponent->Parameters; - Parameters.SetNum(FromParameters.Num()); - - for (int i = 0; i < FromParameters.Num(); i++) - { - UHoudiniParameter* FromParameter = nullptr; - UHoudiniParameter* ToParameter = nullptr; - - FromParameter = FromParameters[i]; - - check(FromParameter); - - if (Parameters.IsValidIndex(i)) - { - ToParameter = Parameters[i]; - } - - if (ToParameter) - { - bool bIsValid = true; - // Check whether To/From parameters are compatible - bIsValid = bIsValid && ToParameter->Matches(*FromParameter); - bIsValid = bIsValid && ToParameter->GetOuter() == this; - - if (!bIsValid) - ToParameter = nullptr; - } - - if (ToParameter) - { - // Parameter already exists. Simply sync the state. - ToParameter->CopyStateFrom(FromParameter, true, ClearFlags, SetFlags); - } - else - { - // TODO: Check whether parameters are the same to avoid recreating them. - ToParameter = FromParameter->DuplicateAndCopyState(this, ClearFlags, SetFlags); - Parameters[i] = ToParameter; - } - - check(ToParameter); - ParameterMapping.Add(FromParameter, ToParameter); - - if (bClearChangedFromInputs) - { - // We clear the Changed flag on the FromParameter (most likely on the component template) - // since the template parameter state has now been transfered to the preview component and - // will resume processing from there. - FromParameter->MarkChanged(false); - } - } - - // Apply remappings on the new parameters - for (UHoudiniParameter* ToParameter : Parameters) - { - ToParameter->RemapParameters(ParameterMapping); - ToParameter->RemapInputs(InputMapping); - } - - FProperty* ParametersProperty = GetClass()->FindPropertyByName(TEXT("Parameters")); - FPropertyChangedEvent Evt(ParametersProperty); - PostEditChangeProperty(Evt); - - bEnableCooking = FromComponent->bEnableCooking; - bRecookRequested = FromComponent->bRecookRequested; - bRebuildRequested = FromComponent->bRebuildRequested; -} - -void -UHoudiniAssetBlueprintComponent::UpdateInputObjectComponentReferences( - USimpleConstructionScript* SCS, - UHoudiniInput* FromInput, - UHoudiniInput* ToInput, - const bool bCopyInputObjectProperties, - const bool bCreateMissingSCSNodes, - USCS_Node* SCSHACParent, - bool* bOutSCSNodeCreated) -{ - TArray ToInputObjects; - TArray FromInputObjects; - TArray StaleInputObjects; - - ToInput->GetAllHoudiniInputSplineComponents(ToInputObjects); - FromInput->GetAllHoudiniInputSplineComponents(FromInputObjects); - - StaleInputObjects = ToInputObjects; - - const int32 NumInputObjects = FromInputObjects.Num(); - ToInputObjects.SetNum(NumInputObjects); - - const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - //Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; - - for(int32 InputObjectIndex = 0; InputObjectIndex < NumInputObjects; ++InputObjectIndex) - { - UHoudiniInputHoudiniSplineComponent* FromInputObject = FromInputObjects[InputObjectIndex]; - UHoudiniInputHoudiniSplineComponent* ToInputObject = ToInputObjects[InputObjectIndex]; - if (!FromInputObject) - continue; - if (!ToInputObject) - continue; - - USCS_Node* SCSNode = nullptr; - if (CachedInputNodes.Contains(ToInputObject->Guid)) - { - // Reuse / update the existing SCS node. - SCSNode = SCS->FindSCSNodeByGuid( CachedInputNodes.FindChecked(ToInputObject->Guid) ); - } - - if (!SCSNode) - { - if (!bCreateMissingSCSNodes) - continue; // This input object should be removed. - } - - USceneComponent* ToComponent = nullptr; - USceneComponent* FromComponent = Cast(FromInputObject->GetObject()); - - StaleInputObjects.Remove(ToInputObject); - - if (FromComponent) - { - if (!SCSNode) - { - if (bCreateMissingSCSNodes) - { - // Create a new SCS node - SCSNode = SCS->CreateNode(FromComponent->GetClass()); - SCSHACParent->AddChildNode(SCSNode); - if (bOutSCSNodeCreated) - { - *bOutSCSNodeCreated = true; - } - AddInputObjectMapping(ToInputObject->Guid, SCSNode->VariableGuid); - } - } - - if (SCSNode) - { - if (bCreateMissingSCSNodes) - { - // If we have been instructed to create missing SCS nodes, assume we are copying - // the the component template. - ToComponent = Cast(SCSNode->ComponentTemplate); - } - else - { - // We are not copying to the component template, so we're assuming this is a - // component instance. Find the component on the owning actor that matches the SCS node. - AActor* ToOwningActor = ToInput->GetTypedOuter(); - check(ToOwningActor); - - ToComponent = Cast(FindComponentInstanceInActor(ToOwningActor, SCSNode)); - } - - if (bCopyInputObjectProperties && ToComponent) - { - USceneComponent* ToAttachParent = ToComponent->GetAttachParent(); - // Copy specific properties from the component template to the instance, if supported by the component. - // We typically resort to this in order to transfer Transient and TransientDuplicate properties from the - // component template over to the instance (typically HasChanged / NeedsToTriggerUpdate flags) in order for - // the instance to cook properly. - IHoudiniEngineCopyPropertiesInterface* ToCopyableComponent = Cast(ToComponent); - if (ToCopyableComponent) - { - // Let the component manage its own data copying. - ToCopyableComponent->CopyPropertiesFrom(FromComponent); - } - else - { - // The component doesn't implement the property copy interface. Simply do a general property copy. - //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, ToComponent, Params); - FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, ToComponent, ComponentCopyOptions); - } - ToComponent->PostEditChange(); - } - } - } - - ToInputObject->Update(ToComponent); - ToInputObjects[InputObjectIndex] = ToInputObject; - } - - for (UHoudiniInputObject* StaleInputObject : StaleInputObjects) - { - if (!StaleInputObject) - continue; - StaleInputObject->InvalidateData(); - ToInput->RemoveHoudiniInputObject(StaleInputObject); - ToInput->MarkChanged(true); - // TODO: Find the corresponding SCS node and remove it - } -} - -#endif - -#if WITH_EDITOR -bool -UHoudiniAssetBlueprintComponent::HasOpenEditor() const -{ - if (IsTemplate()) - { - IAssetEditorInstance* EditorInstance = FindEditorInstance(); - - return EditorInstance != nullptr; - } - - return false; -} -#endif - -#if WITH_EDITOR -IAssetEditorInstance* -UHoudiniAssetBlueprintComponent::FindEditorInstance() const -{ - UClass* BPGC = Cast(GetOuter()); - if (!IsValid(BPGC)) - return nullptr; - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!IsValid(Blueprint)) - return nullptr; - if (!CachedAssetEditorSubsystem.IsValid()) - return nullptr; - - IAssetEditorInstance* EditorInstance = CachedAssetEditorSubsystem->FindEditorForAsset(Blueprint, false); - - return EditorInstance; -} -#endif - -#if WITH_EDITOR -AActor* -UHoudiniAssetBlueprintComponent::GetPreviewActor() const -{ - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - if (BlueprintEditor) - { - return BlueprintEditor->GetPreviewActor(); - } - return nullptr; -} -#endif - -UHoudiniAssetComponent* -UHoudiniAssetBlueprintComponent::GetCachedTemplate() const -{ - return CachedTemplateComponent.Get(); -} - -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateParameters() const -//{ -// return IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateInputs() const -//{ -// return !IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateOutputs() const -//{ -// return !IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanProcessOutputs() const -//{ -// return !IsTemplate(); -//} - -//bool -//UHoudiniAssetBlueprintComponent::CanInstantiateAsset() const -//{ -// // If this is a preview component, it should not trigger an asset instantiation. It should wait -// // for the BPGC template component to finish the cook, get the synced data and then translate. -// -// if (IsPreview()) -// return false; -// -// return true; -//} -// -//// Check whether the HAC can translate Houdini outputs at all -//bool -//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini() const -//{ -// // Template components can't translate Houdini output since they typically do not exist in a world. -// if (IsTemplate()) -// return false; -// // Preview components and normally instanced actors can translate Houdini outputs. -// return true; -//} -// -//// Check whether the HAC can translate a specific output type. -//bool -//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini(EHoudiniOutputType OutputType) const -//{ -// // Blueprint components have limited translation support, for now. -// if (OutputType == EHoudiniOutputType::Mesh) -// return true; -// -// return false; -//} -// -bool -UHoudiniAssetBlueprintComponent::CanDeleteHoudiniNodes() const -{ - return bCanDeleteHoudiniNodes; -} - -void -UHoudiniAssetBlueprintComponent::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; - - for (UHoudiniInput* Input : Inputs) - { - Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for (UHoudiniOutput* Output : Outputs) - { - Output->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -bool -UHoudiniAssetBlueprintComponent::IsValidComponent() const -{ - if (!Super::IsValidComponent()) - return false; - - if (IsTemplate()) - { - UObject* Outer = this->GetOuter(); - if (!IsValid(Outer)) - return false; - UBlueprintGeneratedClass* BPGC = Cast(Outer); - if (!BPGC) - return false; - // Ensure this component is still in the SCS - USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; - if (!SCS) - return false; - USCS_Node* SCSNode = FindSCSNodeForTemplateComponentInClassHierarchy(this); - if (!SCSNode) - return false; - /*UClass* OwnerClass = Outer->GetClass(); - if (!IsValid(OwnerClass)) - return false;*/ - /*UBlueprint* Blueprint = Cast(Outhe); - if (Blueprint) - { - - }*/ - } - -#if WITH_EDITOR - if (!IsTemplate()) - { - if (!GetOwner()) - { - // If it's not a template, it needs an owner! - return false; - } - - USimpleConstructionScript* SCS = GetSCS(); - if (SCS) - { - // We're dealing with a Blueprint related component. - AActor* PreviewActor = GetPreviewActor(); - AActor* OwningActor = GetOwner(); - if (!OwningActor) - return false; - if (OwningActor != PreviewActor) - { - return false; - } - } - - } - - if (IsPreview() && false) - { - USimpleConstructionScript* SCS = GetSCS(); - if (!SCS) - return false; // Preview components should have an SCS. - - // We want to specifically detect whether an editor component is still being previewed. We do this - // by checking whether the owning actor is still the active editor actor in the SCS. - AActor* PreviewActor = GetPreviewActor(); - if (!PreviewActor) - { - return false; - } - - // Ensure this component still belongs the to the current preview actor. - if (PreviewActor != GetOwner()) - { - return false; - } - - /* - AActor* EditorActor = SCS->GetComponentEditorActorInstance(); - if (GetOwner() != EditorActor) - { - return false; - } - */ - } -#endif - - return true; -} - -bool -UHoudiniAssetBlueprintComponent::IsInputTypeSupported(EHoudiniInputType InType) const -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - case EHoudiniInputType::Curve: - return true; - break; - default: - break; - } - return false; -} - -bool -UHoudiniAssetBlueprintComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const -{ - switch (InType) - { - case EHoudiniOutputType::Mesh: - case EHoudiniOutputType::Instancer: - return true; - break; - default: - break; - } - return false; -} - -bool -UHoudiniAssetBlueprintComponent::IsProxyStaticMeshEnabled() const -{ - // TODO: Investigate adding support for proxy meshes in BP - // Disabled for now - return false; -} - -//void -//UHoudiniAssetBlueprintComponent::BroadcastPreAssetCook() -//{ -// // ------------------------------------------------ -// // NOTE: This code will run on TEMPLATE components -// // ------------------------------------------------ -// -// // The HoudiniAsset is about to be recooked. This flag will indicate to -// // the transient components that output processing needs to be baked -// // back to the BP definition. -// bOutputsRequireUpdate = true; -// -// Super::BroadcastPreAssetCook(); -//} - -void -UHoudiniAssetBlueprintComponent::OnPrePreCook() -{ - check(IsPreview()); - - Super::OnPrePreCook(); - - // We need to allow deleting houdini nodes - SetCanDeleteHoudiniNodes(true); -} - -void -UHoudiniAssetBlueprintComponent::OnPostPreCook() -{ - check(IsPreview()); - - Super::OnPostPreCook(); - - // Ensure the houdini nodes can be deleted during the translation process. - SetCanDeleteHoudiniNodes(false); -} - -void -UHoudiniAssetBlueprintComponent::OnPreOutputProcessing() -{ - check(IsPreview()); - - Super::OnPreOutputProcessing(); - - // Ensure the houdini nodes can be deleted during the translation process. - SetCanDeleteHoudiniNodes(true); -} - -void -UHoudiniAssetBlueprintComponent::OnPostOutputProcessing() -{ - Super::OnPostOutputProcessing(); - - // ------------------------------------------------ - // NOTE: - // In Blueprint editor mode, this code will run on PREVIEW components - // In Map editor mode, this code will run on component instances. - // ------------------------------------------------ - if (IsPreview()) - { - // Ensure all the inputs / outputs belonging to the - // preview actor won't be deleted by PreviewActor destruction. - SetCanDeleteHoudiniNodes(false); - -#if WITH_EDITOR - CopyStateToTemplateComponent(); -#endif - - } - bUpdatedFromTemplate = false; -} - -void UHoudiniAssetBlueprintComponent::OnPrePreInstantiation() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnPrePreInstantiation] Component: %s"), *(GetPathName())); - - check(IsPreview()); - - if (bUpdatedFromTemplate) - return; - - check(CachedTemplateComponent.IsValid()); - - // This HDA is about to be cooked but not through template parameter changes. It is likely that an input changed directly in the preview world. - // We need to flag our inputs and parameters appropriately in order to preserve their values. - - // We need to mark all our parameters as changed/not triggering update - for (auto CurrentParam : Parameters) - { - if (CurrentParam) - { - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(false); - } - } - - // We need to mark all our inputs as changed/not triggering update - for (auto CurrentInput : Inputs) - { - if (CurrentInput) - { - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(false); - CurrentInput->MarkDataUploadNeeded(true); - } - } -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniRegisterCompleted() -{ - if (IsTemplate()) - { - // TODO: Do we need to set any status flags or clear stuff to ensure - // the BP HAC will cook properly when the BP is opened again... - - // If the template is being registered, we need to invalidate the AssetId here since it likely - // contains a stale asset id from its last cook. - AssetId = -1; - // Template component's have very limited update requirements / capabilities. - // Mostly just cache parameters and cook state. - AssetState = EHoudiniAssetState::ProcessTemplate; - } - - Super::NotifyHoudiniRegisterCompleted(); -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniPreUnregister() -{ - if (IsTemplate()) - { - // Templates can delete Houdini nodes when they get deregistered. - SetCanDeleteHoudiniNodes(true); - } - Super::NotifyHoudiniPreUnregister(); -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniPostUnregister() -{ - InvalidateData(); - - Super::NotifyHoudiniPostUnregister(); - - if (IsTemplate()) - { - SetCanDeleteHoudiniNodes(false); - } -} - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::OnComponentCreated() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnComponentCreated] Component: %s"), *(GetPathName())); - - Super::OnComponentCreated(); - bUpdatedFromTemplate = false; - - CachePreviewState(); - - if (IsPreview()) - { - // Don't set an initial AssetState here. Preview components should only cook when template's - // Houdini Asset or HDA parameters have changed. - - // Clear these to ensure that we're not sharing references with the component template (otherwise - // the shared objects will get deleted when the component instance gets destroyed). - // These objects will be properly duplicated when copying state from the component template. - Inputs.Empty(); - Parameters.Empty(); - } - - // Wait until InitializeComponent() for blueprint construction to complete before we start caching blueprint data. - -} -#endif - - -void -UHoudiniAssetBlueprintComponent::OnRegister() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnRegister] Component: %s"), *(GetPathName())); - - Super::OnRegister(); - - // We run our Blueprint caching functions here since this the last hook that we have before - // entering HoudiniEngineTick(); - - CacheBlueprintData(); - CachePreviewState(); - - if (IsPreview()) - { - check(CachedTemplateComponent.Get()); - // Ensure that the component template has been registered since it needs to be processed for parameter updates by the HE manager. - if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(CachedTemplateComponent.Get())) - { - // The template component has not been registered yet, which means that we're probably busy opening a Blueprint editor and this - // preview component will need to be updated. - - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(CachedTemplateComponent.Get(), true); - CachedTemplateComponent->SetCanDeleteHoudiniNodes(false); - // Since we're likely opening a fresh blueprint editor, we'll need to instantiate the HDA. - bHasRegisteredComponentTemplate = true; - } - } - - if (IsTemplate()) - { - // We're initializing the asset id for HAC template here since it doesn't get unloaded - // from memory, for example, between Blueprint Editor open/close so we need to make sure - // that the AssetId has indeed been reset between registrations. - AssetId = -1; - } - - //TickInitialization(); -} - -void -UHoudiniAssetBlueprintComponent::BeginDestroy() -{ - Super::BeginDestroy(); -} - -void -UHoudiniAssetBlueprintComponent::DestroyComponent(bool bPromoteChildren) -{ - //FDebug::DumpStackTraceToLog(); - if (CachedTemplateComponent.IsValid() && TemplatePropertiesChangedHandle.IsValid()) - { - CachedTemplateComponent->Modify(); - CachedTemplateComponent->OnParametersChangedEvent.Remove(TemplatePropertiesChangedHandle); - } - Super::DestroyComponent(bPromoteChildren); -} - -void -UHoudiniAssetBlueprintComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - Super::OnComponentDestroyed(bDestroyingHierarchy); -} - -TStructOnScope -UHoudiniAssetBlueprintComponent::GetComponentInstanceData() const -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::GetComponentInstanceData] Component: %s"), *(GetPathName())); - - TStructOnScope ComponentInstanceData = MakeStructOnScope(this); - FHoudiniAssetBlueprintInstanceData* InstanceData = ComponentInstanceData.Cast(); - - InstanceData->AssetId = AssetId; - InstanceData->AssetState = AssetState; - InstanceData->SubAssetIndex = SubAssetIndex; - InstanceData->ComponentGUID = ComponentGUID; - InstanceData->HapiGUID = HapiGUID; - InstanceData->HoudiniAsset = HoudiniAsset; - InstanceData->SourceName = GetPathName(); - InstanceData->AssetCookCount = AssetCookCount; - InstanceData->bHasBeenLoaded = bHasBeenLoaded; - InstanceData->bHasBeenDuplicated = bHasBeenDuplicated; - InstanceData->bPendingDelete = bPendingDelete; - InstanceData->bRecookRequested = bRecookRequested; - InstanceData->bEnableCooking = bEnableCooking; - InstanceData->bForceNeedUpdate = bForceNeedUpdate; - InstanceData->bLastCookSuccess = bLastCookSuccess; - InstanceData->bRegisteredComponentTemplate = bHasRegisteredComponentTemplate; - - InstanceData->Inputs.Empty(); - - for (UHoudiniInput* Input : Inputs) - { - if (!Input) - continue; - UHoudiniInput* TransientInput = Input->DuplicateAndCopyState(GetTransientPackage(), false); - InstanceData->Inputs.Add(TransientInput); - } - - // Cache the current outputs - InstanceData->Outputs.Empty(); - int OutputIndex = 0; - for(UHoudiniOutput* Output : Outputs) - { - if (!Output) - continue; - - TMap OutputObjects = Output->GetOutputObjects(); - for (auto& Entry : OutputObjects) - { - FHoudiniAssetBlueprintOutput OutputObjectData; - OutputObjectData.OutputIndex = OutputIndex; - OutputObjectData.OutputObject = Entry.Value; - InstanceData->Outputs.Add(Entry.Key, OutputObjectData); - } - - ++OutputIndex; - } - - return ComponentInstanceData; - -} - -void -UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData(FHoudiniAssetBlueprintInstanceData* InstanceData, const bool bPostUCS) -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData] Component: %s"), *(GetPathName())); - check(InstanceData); - - if (!bPostUCS) - { - // Initialize the component before the User Construction Script runs - USimpleConstructionScript* SCS = GetSCS(); - check(SCS); - - TArray StaleInputs(Inputs); - - // We need to update references contain in inputs / outputs to point to new reconstructed components. - const int32 NumInputs = InstanceData->Inputs.Num(); - Inputs.SetNum(NumInputs); - for (int i = 0; i < NumInputs; ++i) - { - UHoudiniInput* FromInput = InstanceData->Inputs[i]; - UHoudiniInput* ToInput = Inputs[i]; - - if (ToInput) - { - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - if (!bIsValid) - { - ToInput = nullptr; - } - } - - if (ToInput) - { - // Reuse input - StaleInputs.Remove(ToInput); - ToInput->CopyStateFrom(FromInput, true, false); - } - else - { - // Create new input - ToInput = FromInput->DuplicateAndCopyState(this, false); - } - -#if WITH_EDITOR - // We can't create missing SCS nodes here since we're likely already in the middle of a - // Blueprint reconstruction. We'll have to recreate missing SCS nodes next time the - // component state if copied to the template. - UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, true, false); -#endif - - Inputs[i] = ToInput; - } - - // We need to update FHoudiniOutputObject SceneComponent references to - // the newly created components. Since we cached a map of Output Object IDs to - // SCSNodes (during CopyStateToTemplateComponent), we can the SCSNode that corresponds to this output objects and find - // the SceneComponent that matches the SCSNode's variable name. - // It is important to note that it is safe to do it this way since we're in the pre-UCS - // phase so that current components should match the SCS graph exactly (no user construction script - // interference here yet). - - for (auto& Entry : InstanceData->Outputs) - { - FHoudiniOutputObjectIdentifier& ObjectId = Entry.Key; - FHoudiniAssetBlueprintOutput& OutputData = Entry.Value; - - // NOTE: Output objects are going to be empty here since they dissapear during actor reconstruction. - // We'll need to repopulate from the instance data. - - check(Outputs.IsValidIndex(OutputData.OutputIndex)); - UHoudiniOutput* Output = Outputs[OutputData.OutputIndex]; - check(Output); - TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniOutputObject NewObject = OutputData.OutputObject; - - if (OutputData.OutputObject.OutputComponent) - { - // Update the output component reference. - check(CachedOutputNodes.Contains(ObjectId)) - const FGuid VariableGuid = CachedOutputNodes.FindChecked(ObjectId); - USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(VariableGuid); - - if (SCSNode) - { - // Find the component that corresponds to the SCS node. - USceneComponent* SceneComponent = FindActorComponentByName(GetOwner(), SCSNode->GetVariableName()); - NewObject.OutputComponent = SceneComponent; - } - else - { - NewObject.OutputComponent = nullptr; - } - } - - OutputObjects.Add(ObjectId, NewObject); - } - - if (CachedTemplateComponent.IsValid()) - { -#if WITH_EDITOR - CopyStateFromTemplateComponent( CachedTemplateComponent.Get(), false, false, true); -#endif - } - - AssetId = InstanceData->AssetId; - SubAssetIndex = InstanceData->SubAssetIndex; - ComponentGUID = InstanceData->ComponentGUID; - HapiGUID = InstanceData->HapiGUID; - - // Apply the previous HoudiniAsset to the component - // so that we can compare it against the template during CopyStateFromTemplate() calls to see whether it changed. - HoudiniAsset = InstanceData->HoudiniAsset; - - AssetCookCount = InstanceData->AssetCookCount; - bHasBeenLoaded = InstanceData->bHasBeenLoaded; - bHasBeenDuplicated = InstanceData->bHasBeenDuplicated; - bPendingDelete = InstanceData->bPendingDelete; - bRecookRequested = InstanceData->bRecookRequested; - bEnableCooking = InstanceData->bEnableCooking; - bForceNeedUpdate = InstanceData->bForceNeedUpdate; - bLastCookSuccess = InstanceData->bLastCookSuccess; - bHasRegisteredComponentTemplate = InstanceData->bRegisteredComponentTemplate; - - AssetState = InstanceData->AssetState; - - SetCanDeleteHoudiniNodes(false); - - } // if (!bPostUCS) - /* - else - { - // PostUCS - - } - */ -} - - -void -UHoudiniAssetBlueprintComponent::HoudiniEngineTick() -{ - if (!IsFullyLoaded()) - { - USimpleConstructionScript* SCS = GetSCS(); - if (SCS == nullptr) - { - OnFullyLoaded(); - } - else if (IsPreview()) - { - AActor* OwningActor = GetOwner(); - check(OwningActor); - - // If this is a *preview component*, it is important to wait for the template component to be fully loaded - // since it needs to be initialized so that the component instance can copy initial values from the template. - check(CachedTemplateComponent.Get()); - - if (CachedTemplateComponent->IsFullyLoaded()) - { -#if WITH_EDITOR - if(SCS->IsConstructingEditorComponents()) - { - // We're stuck in an editor blueprint construction / preview actor update. Wait some more. - } - else - { - OnFullyLoaded(); - } -#else - OnFullyLoaded(); -#endif - } - } - else - { - // Anything else can go onto being fully loaded at this point. - OnFullyLoaded(); - } - } -} - -void -UHoudiniAssetBlueprintComponent::OnFullyLoaded() -{ - Super::OnFullyLoaded(); - - // Check whether this component is inside a Blueprint editor. If this object lives outside the blueprint editor (in , then we need to ensure that this - // component won't be influencing the Blueprint asset. - - if (!IsTemplate()) - { -#if WITH_EDITOR - AActor* PreviewActor = GetPreviewActor(); -#else - AActor* PreviewActor = nullptr; -#endif - AActor* OwningActor = GetOwner(); - - if (!PreviewActor) - { - bIsInBlueprintEditor = false; - AssetState = EHoudiniAssetState::None; - return; - } - - if (OwningActor && PreviewActor != OwningActor) - { - bIsInBlueprintEditor = false; - AssetState = EHoudiniAssetState::None; - return; - } - } - - bIsInBlueprintEditor = true; - - CachePreviewState(); - CacheBlueprintData(); - - /* - for (UHoudiniOutput* Output : Outputs) - { - if (!Output) - continue; - } - */ - - if (IsTemplate()) - { - AssetId = -1; - AssetState = EHoudiniAssetState::ProcessTemplate; - } - - if (IsPreview()) - { - check(CachedTemplateComponent.Get()); - - // If this is a preview actor, sync initial settings from the component template -#if WITH_EDITOR - CopyStateFromTemplateComponent(CachedTemplateComponent.Get(), false, false, true); -#endif - - TemplatePropertiesChangedHandle = CachedTemplateComponent->OnParametersChangedEvent.AddUObject(this, &UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler); - if (bHoudiniAssetChanged) - { - - // The HoudiniAsset has changed, so we need to force the PreviewInstance to re-instantiate - AssetState = EHoudiniAssetState::NeedInstantiation; - bForceNeedUpdate = true; - bHoudiniAssetChanged = false; - // TODO: Make this better? - CachedTemplateComponent->bHoudiniAssetChanged = false; - } - - if (bHasRegisteredComponentTemplate) - { - // We have a newly registered component template. One of two things happened to cause this: - // 1. A new HoudiniAssetBlueprintComponent was created and registered. - // 2. The Blueprint Editor was closed / opened. - // The problem that arises in the #2 is that the template component was never fully unloaded - // from memory (it was deregistered but not destroyed). After deregistration we had the - // opportunity to invalidate asset/node ids but now that it has reregistered (without going - // through the normal initialization process) we will have to force a call to MarkAsNeedInstantiation - // during the next OnTemplateParametersChangedHandler() invocation. - bHasBeenLoaded = true; - } - } -} - -void -UHoudiniAssetBlueprintComponent::OnTemplateParametersChanged() -{ - OnParametersChangedEvent.Broadcast(this); -} - -void UHoudiniAssetBlueprintComponent::OnBlueprintStructureModified() -{ - check(IsTemplate()); - bBlueprintStructureModified = false; - -#if WITH_EDITOR - if (IsTemplate()) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(this); - } - else - { - check(CachedTemplateComponent.IsValid()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); - } -#endif -} - -void UHoudiniAssetBlueprintComponent::OnBlueprintModified() -{ - check(IsTemplate()); - bBlueprintModified = false; -#if WITH_EDITOR - FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(this); -#endif -} - -void -UHoudiniAssetBlueprintComponent::OnHoudiniAssetChanged() -{ - if (IsTemplate()) - { - // Invalidate data associated with this component since we're about to change and reinstantiate the Houdini Asset. - SetCanDeleteHoudiniNodes(true); - InvalidateData(); - SetCanDeleteHoudiniNodes(false); - Parameters.Empty(); - Inputs.Empty(); - } - - Super::OnHoudiniAssetChanged(); - - // Set on template components, then copied to preview components, and - // then used (and reset) during OnFullyLoaded. - bHoudiniAssetChanged = true; -} - -void -UHoudiniAssetBlueprintComponent::RegisterHoudiniComponent(UHoudiniAssetComponent *InComponent) -{ - // We only want to register this component if it is the preview actor for the Blueprint editor. -#if WITH_EDITOR - AActor* PreviewActor = GetPreviewActor(); -#else - AActor* PreviewActor = nullptr; -#endif - AActor* OwningActor = GetOwner(); - if (!OwningActor) - return; - - if (PreviewActor != OwningActor) - return; - - Super::RegisterHoudiniComponent(InComponent); -} - - - - -//bool UHoudiniAssetBlueprintComponent::TickInitialization() -//{ -// return true; -// -// if (IsFullyLoaded()) -// return true; -// -// bool bHasFinishedLoading = Super::TickInitialization(); -// -// if (!bHasFinishedLoading) -// return false; -// -// if (!IsTemplate()) -// { -// -// if (CachedTemplateComponent.Get()) -// { -// // Now that that SCS has finished constructing editor components, we can continue. -// // Copy the current state from the template component, in case there is something that can be processed. -// CopyStateFromTemplateComponent(CachedTemplateComponent.Get()); -// } -// -// AssetStateResult = EHoudiniAssetStateResult::None; -// AssetState = EHoudiniAssetState::None; -// -// bForceNeedUpdate = true; -// AssetState = EHoudiniAssetState::PostCook; -// } -// -// return true; -//} - -template -inline void -UHoudiniAssetBlueprintComponent::SetTypedValueAt(const FString& Name, ValueT& Value, int Index) -{ - ParamT* Parameter = Cast(FindParameterByName(Name)); - if (!Parameter) - return; - - Parameter->SetValueAt(Value, Index); -} - -bool -UHoudiniAssetBlueprintComponent::HasParameter(FString Name) -{ - return FindParameterByName(Name) != nullptr; -} - -void -UHoudiniAssetBlueprintComponent::SetFloatParameter(FString Name, float Value, int Index) -{ - SetTypedValueAt(Name, Value, Index); -} - -void -UHoudiniAssetBlueprintComponent::SetToggleValueAt(FString Name, bool Value, int Index) -{ - UHoudiniParameterToggle* Parameter = Cast(FindParameterByName(Name)); - if (!Parameter) - return; - - Parameter->SetValueAt(Value, Index); -} - -//void UHoudiniAssetBlueprintComponent::OnPostCookHandler(UHoudiniAssetComponent* InComponent) -//{ -// -// // Before this component handles any translation, we need to make sure that it still belongs to the editor actor. -// // When a blueprint gets recompiled, the editor actor gets replaced with a new one but the old actor has not yet -// // been ftroyed / garbage collected so its components still receive cook events from the template. -// -// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponent); -// if (!IsValid(ComponentTemplate)) -// return; -// -// CopyStateFromTemplateComponent(ComponentTemplate); -// bForceNeedUpdate = true; -//} - -void -UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler(UHoudiniAssetComponent* InComponentTemplate) -{ - if (!(AssetState == EHoudiniAssetState::None || AssetState == EHoudiniAssetState::NeedInstantiation || AssetState == EHoudiniAssetState::NeedRebuild)) - // Don't process parameter changes since we're already cooking -- it is going to break things badly if we do. - return; - - if (!IsValidComponent()) - return; - - UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); - if (!ComponentTemplate) - return; - - // The component instance needs to copy values from the template. - bool bBlueprintStructureChanged = false; -#if WITH_EDITOR - CopyDetailsFromComponent(ComponentTemplate, - false, - false, - true, - false, - true, - bBlueprintStructureChanged, - RF_Public, - RF_ClassDefaultObject|RF_ArchetypeObject); -#endif - - SetCanDeleteHoudiniNodes(false); - - if (bHasRegisteredComponentTemplate) - { - // NOTE: It is very important to call this *after* CopyDetailsFromComponent(), since CopyDetailsFromComponent - // will clobber the inputs and parameter states on this component. - - // If we already have a valid asset id, keep it. - if (AssetId >= 0) - { - MarkAsNeedCook(); - } - else - { - MarkAsNeedInstantiation(); - } - - bHasRegisteredComponentTemplate = false; - bFullyLoaded = true; // MarkAsNeedInstantiation sets this to false. Force to true. - // While MarkAsNeedInstantiation() sets ParametersChanged to true, it does not - // set the 'NeedToTriggerUpdate' flag (both of which needs to be true in order - // to trigger an HDA update) so we are going to force NeedUpdate() to return true - // in order to get an initial cook. - bForceNeedUpdate = true; - } - - bUpdatedFromTemplate = true; -} - -void -UHoudiniAssetBlueprintComponent::InvalidateData() -{ - if (IsTemplate()) - { - // Ensure transient properties are invalidated/released for parameters, inputs and outputs as if the - // the object was undergoing destruction since the template component will likely be reregistered - // without being destroyed. - for(UHoudiniParameter* Param : Parameters) - { - Param->InvalidateData(); - } - - for(UHoudiniInput* Input : Inputs) - { - Input->InvalidateData(); - } - - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - AssetId = -1; - } -} - -//void UHoudiniAssetBlueprintComponent::OnTemplateHoudiniAssetChangedHandler(UHoudiniAssetComponent* InComponentTemplate) -//{ -// -// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); -// if (!ComponentTemplate) -// return; -// check(IsPreview()); -// -// // The Houdini Asset was changed on the template. We need to recook. -// AssetState = EHoudiniAssetState::NeedInstantiation; -// -//} - -USceneComponent* -UHoudiniAssetBlueprintComponent::FindOwnerComponentByName(FName ComponentName) const -{ - AActor* Owner = GetOwner(); - if (!Owner) - return nullptr; - - return FindActorComponentByName(Owner, ComponentName); -} - -USceneComponent* -UHoudiniAssetBlueprintComponent::FindActorComponentByName(AActor* InActor, FName ComponentName) const -{ - const TSet& Components = InActor->GetComponents(); - - for (UActorComponent* Component : Components) - { - USceneComponent* SceneComponent = Cast(Component); - if (!IsValid(SceneComponent)) - continue; - if (FName(SceneComponent->GetName()) == ComponentName) - return SceneComponent; - } - - return nullptr; -} - -bool UHoudiniAssetBlueprintComponent::GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid) -{ - FGuid* SCSGuid = CachedInputNodes.Find(InputGuid); - if (!SCSGuid) - return false; - OutSCSGuid = *SCSGuid; - return true; -} - -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const -{ - const TArray& AllNodes = SCS->GetAllNodes(); - - for (USCS_Node* Node : AllNodes) - { - if (!Node) - continue; - - if (Node->ComponentTemplate == InComponent) - return Node; - } - - return nullptr; -} - -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponentInClassHierarchy( - const UActorComponent* InComponent) const -{ - UObject* Outer = this->GetOuter(); - if (!IsValid(Outer)) - return nullptr; - UBlueprintGeneratedClass* MainBPGC; - if (IsTemplate()) - { - MainBPGC = Cast(Outer); - } - else - { - AActor* OwningActor = GetOwner(); - MainBPGC = Cast(OwningActor->GetClass()); - } - - check(MainBPGC); - TArray BPGCStack; - UBlueprintGeneratedClass::GetGeneratedClassesHierarchy(MainBPGC, BPGCStack); - for(const UBlueprintGeneratedClass* BPGC : BPGCStack) - { - USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; - if (!SCS) - return nullptr; - USCS_Node* SCSNode = FindSCSNodeForTemplateComponent(SCS, InComponent); - SCSNode = SCS->FindSCSNode(InComponent->GetFName()); - if (SCSNode) - return SCSNode; - } - - return nullptr; -} - -#if WITH_EDITOR -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const -{ - const TArray& AllNodes = SCS->GetAllNodes(); - - if (!InComponent) - return nullptr; - - for (USCS_Node* Node : AllNodes) - { - if (!Node) - continue; - if (Node->EditorComponentInstance.Get() == InComponent) - return Node; - } - - return nullptr; -} -#endif - -UActorComponent* -UHoudiniAssetBlueprintComponent::FindComponentInstanceInActor(const AActor* InActor, - USCS_Node* SCSNode) const -{ - UActorComponent* ComponentTemplate = SCSNode->ComponentTemplate; - - UActorComponent* ComponentInstance = NULL; - if (InActor != NULL) - { - if (SCSNode != NULL) - { - FName VariableName = SCSNode->GetVariableName(); - if (VariableName != NAME_None) - { - UWorld* World = InActor->GetWorld(); - FObjectPropertyBase* Property = FindFProperty(InActor->GetClass(), VariableName); - if (Property != NULL) - { - // Return the component instance that's stored in the property with the given variable name - ComponentInstance = Cast(Property->GetObjectPropertyValue_InContainer(InActor)); - } - else if (World != nullptr && World->WorldType == EWorldType::EditorPreview) - { - // If this is the preview actor, return the cached component instance that's being used for the pmnaview actor prior to recompiling the Blueprint -#if WITH_EDITOR - ComponentInstance = SCSNode->EditorComponentInstance.Get(); -#endif - } - } - } - else if (ComponentTemplate != NULL) - { -#if WITH_EDITOR - TInlineComponentArray Components; - InActor->GetComponents(Components); - ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components); -#endif - } - } - - return ComponentInstance; -} - - -//void UHoudiniAssetBlueprintComponent::OnOutputProcessingCompletedHandler(UHoudiniAssetComponent* InComponent) -//{ -// -// UHoudiniAssetBlueprintComponent* TemplateComponent = Cast(InComponent); -// if (!IsValid(TemplateComponent)) -// return; -// -// CopyStateFromComponent(TemplateComponent); -// bForceNeedUpdate = true; -//} - -//#if WITH_EDITOR -//void UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason) -//{ -// -// if (CachedBlueprint.Get()) -// { -// } -// -// if (Asset) -// { -// -// } -// -//} -//#endif - -void -UHoudiniAssetBlueprintComponent::CachePreviewState() -{ - bCachedIsPreview = false; - -#if WITH_EDITOR - AActor* ComponentOwner = GetOwner(); - if (!IsValid(ComponentOwner)) - return; - - USimpleConstructionScript* SCS = GetSCS(); - if (SCS == nullptr) - return; - - // Get the preview actor directly from the BlueprintEditor. - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - if (BlueprintEditor) - { - AActor* PreviewActor = BlueprintEditor->GetPreviewActor(); - if (PreviewActor == ComponentOwner) - { - bCachedIsPreview = true; - return; - } - } -#endif -} - -void -UHoudiniAssetBlueprintComponent::CacheBlueprintData() -{ - CachedBlueprint = nullptr; - CachedActorCDO = nullptr; - CachedTemplateComponent = IsTemplate() ? this : nullptr; - -#if WITH_EDITOR - CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); -#endif - - UBlueprintGeneratedClass* BPGC = Cast(GetOuter()); - if (BPGC) - { - // Dealing with a component template - CachedBlueprint = Cast(BPGC->ClassGeneratedBy); - } - else - { - // Dealing with a component instance. - CachedBlueprint = Cast(GetOuter()->GetClass()->ClassGeneratedBy); - } - - if (CreationMethod != EComponentCreationMethod::SimpleConstructionScript) - return; - - AActor* ComponentOwner = this->GetOwner(); - if (!IsValid(ComponentOwner)) - return; - UClass* OwnerClass = ComponentOwner->GetClass(); - if (!IsValid(OwnerClass)) - return; - - if (!IsTemplate()) - { - // NOTE: The following code allows us to find the component template from an instance. - CachedActorCDO = Cast< AActor >(CachedBlueprint->GeneratedClass->GetDefaultObject()); - if (!CachedActorCDO.IsValid() || (CachedActorCDO.Get() == ComponentOwner)) - return; -#if WITH_EDITOR - UActorComponent* TargetComponent = EditorUtilities::FindMatchingComponentInstance(this, CachedActorCDO.Get()); - CachedTemplateComponent = Cast(TargetComponent); -#endif - } - -} - -USimpleConstructionScript* -UHoudiniAssetBlueprintComponent::GetSCS() const -{ - if (!CachedBlueprint.Get()) - return nullptr; - - return CachedBlueprint->SimpleConstructionScript; -} - -//------------------------------------------------------------------------------------------------ -// FHoudiniAssetBlueprintInstanceData -//------------------------------------------------------------------------------------------------ - -FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData() - : HoudiniAsset(nullptr) - , AssetId(-1) - , AssetState(EHoudiniAssetState::None) - , SubAssetIndex(-1) - , AssetCookCount(0) - , bHasBeenLoaded(false) - , bHasBeenDuplicated(false) - , bPendingDelete(false) - , bRecookRequested(false) - , bRebuildRequested(false) - , bEnableCooking(true) - , bForceNeedUpdate(false) - , bLastCookSuccess(false) - , ComponentGUID(FGuid()) - , HapiGUID(FGuid()) - , bRegisteredComponentTemplate(false) - , SourceName() -{ - -} - -FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent) - : FActorComponentInstanceData(SourceComponent) -{ - -} - -void -FHoudiniAssetBlueprintInstanceData::AddReferencedObjects(FReferenceCollector & Collector) -{ - Super::AddReferencedObjects(Collector); - // TODO: Do we need to add references to output objects here? - // Any other references? - // What are the implications? -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetBlueprintComponent.h" + +#include "HoudiniEngineCopyPropertiesInterface.h" +#include "HoudiniOutput.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Engine/SCS_Node.h" +#include "Engine/SimpleConstructionScript.h" +#include "UObject/Object.h" +#include "Logging/LogMacros.h" + +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniInput.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Kismet2/BlueprintEditorUtils.h" + #include "Kismet2/KismetEditorUtilities.h" + #include "Toolkits/AssetEditorManager.h" + #include "Kismet2/ComponentEditorUtils.h" + #include "ComponentAssetBroker.h" +#endif + +HOUDINI_BP_DEFINE_LOG_CATEGORY(); + +UHoudiniAssetBlueprintComponent::UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + +#if WITH_EDITOR + if (IsTemplate()) + { + // CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); + //GEditor->GetEditorSubsystem()->OnAssetEditorRequestClose().AddUObject( this, &UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent ); + } +#endif + + bForceNeedUpdate = false; + bHoudiniAssetChanged = false; + bIsInBlueprintEditor = false; + bCanDeleteHoudiniNodes = false; + + // AssetState will be updated by changes to the HoudiniAsset + // or parameter changes on the Component template. + AssetState = EHoudiniAssetState::None; + bHasRegisteredComponentTemplate = false; + bHasBeenLoaded = false; + bUpdatedFromTemplate = false; + + // Disable proxy mesh by default (unsupported for now) + bOverrideGlobalProxyStaticMeshSettings = true; + bEnableProxyStaticMeshOverride = false; + bEnableProxyStaticMeshRefinementByTimerOverride = false; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = false; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = false; + StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; + + // Set default mobility to Movable + Mobility = EComponentMobility::Movable; +} + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() +{ + // We need to propagate changes made here back to the corresponding component in + // the Blueprint Generated Class ("_GEN_VARIABLE"). The reason being that + // the Blueprint editor works directly with the GEN_VARIABLE component (all + // PostEditChange() calls, Details Customizations, etc will receive the GEN_VARIABLE instance) BUT + // when the Editor runs the construction script it uses a different component instance, so all changes + // made to that instance won't write back to the Blueprint definition. + // To Summarize: + // Be sure to sync the Parameters array (and any other relevant properties) back + // to the corresponding component on the Blueprint Generated class otherwise these wont be + // accessible in the Details Customization callbacks. + + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] To Component: %s"), *(CachedTemplateComponent->GetPathName())); + + // This should never be called by component templates. + check(!IsTemplate()); + + if (!CachedTemplateComponent.IsValid()) + return; + + USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; + check(SCS); + + /* + USCS_Node* SCSNodeForInstance = FindSCSNodeForInstanceComponent(SCS, this); + if (SCSNodeForInstance) + { + + } + else + { + + } + */ + + //// If we don't have an SCS node for this preview instance, we need to create one, regardless + //// of whether output updates are required. + //if (!CachedTemplateComponent->bOutputsRequireUpdate && SCSNodeForInstance != nullptr) + // return; + + // TODO: If the blueprint editor is NOT open, then we shouldn't attempting + // to copy state back to the BPGC at all! + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + check(BlueprintEditor); + + TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); + check(SCSEditor); + + USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + // check(SCSHACNode); + + // This is the actor instance that is being used for component editing. + AActor* PreviewActor = GetPreviewActor(); + check(PreviewActor); + + // NOTE: Inputs are only from component templates to instances, not the other way around ... I think. + + // ----------------------------------------------------- + // Copy outputs to component template + // ----------------------------------------------------- + + // Populate / update the outputs for the template from the preview / instance. + // TODO: Wrap the Blueprint manipulation in a transaction + TArray& TemplateOutputs = CachedTemplateComponent->Outputs; + TSet StaleTemplateOutputs(TemplateOutputs); + + TemplateOutputs.SetNum(Outputs.Num()); + CachedOutputNodes.Empty(); + + for (int i = 0; i < Outputs.Num(); i++) + { + // Find a output on the template that corresponds to this output from the instance. + UHoudiniOutput* TemplateOutput = nullptr; + UHoudiniOutput* InstanceOutput = nullptr; + InstanceOutput = Outputs[i]; + + check(InstanceOutput) + // Ensure that instance outputs won't delete houdini content. + // Houdini content should only be allowed to be deleted from + // the component template. + InstanceOutput->SetCanDeleteHoudiniNodes(false); + + TemplateOutput = TemplateOutputs[i]; + + if (TemplateOutput) + { + check(TemplateOutput->GetOuter() == CachedTemplateComponent.Get()); + StaleTemplateOutputs.Remove(TemplateOutput); + } + + + if (TemplateOutput) + { + // Copy properties from the current instance component while preserving output objects + // and instanced outputs. + TemplateOutput->CopyPropertiesFrom(InstanceOutput, true); + } + else + { + // NOTE: If the template output is NULL it means that the HDA spawned a new component / output in the transient world + // and the new output object needs to be copied back to the BPGC. + + // Corresponding template output could not be found. Create one by duplicating the instance output. + TemplateOutput = InstanceOutput->DuplicateAndCopyProperties(CachedTemplateComponent.Get(), FName(InstanceOutput->GetName())); + // Treat these the same one would components created by CreateDefaultSubObject. + // NOTE: CreateDefaultSubobject performs lots of checks, and unfortunately we can't use it directly (it is + // only allowed to be used in a constructor). Not sure whether we need to either. For now, we just set the + // object flags to be similar to components created by CreateDefaultSubobject. + TemplateOutput->SetFlags(RF_Public|RF_ArchetypeObject|RF_DefaultSubObject); + TemplateOutputs[i] = TemplateOutput; + } + + check(TemplateOutput); + TemplateOutput->SetCanDeleteHoudiniNodes(false); + + // Keep track of potential stale output objects on the template component, for this output. + TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); + TArray StaleTemplateObjects; + TemplateOutputObjects.GetKeys(StaleTemplateObjects); + + for (auto& Entry : InstanceOutput->GetOutputObjects()) + { + + // Prepare the FHoudiniOutputObject for the template component + const FHoudiniOutputObject& InstanceObj = Entry.Value; + FHoudiniOutputObject TemplateObj; + + // Any output present in the Instance Outputs should be + // transferred to the template. + // Remove this output object from stale outputs list. + StaleTemplateObjects.Remove(Entry.Key); + + if (TemplateOutputObjects.Contains(Entry.Key)) + { + // Reuse the existing template object + TemplateObj = TemplateOutputObjects.FindChecked(Entry.Key); + } + else + { + // Create a new template output object object by duplicating the instance object. + // Keep the output object, but clear the output component since we have to + // create a new component template. + TemplateObj = InstanceObj; + TemplateObj.ProxyComponent = nullptr; + TemplateObj.OutputComponent = nullptr; + TemplateObj.ProxyObject = nullptr; + } + + USceneComponent* ComponentInstance = Cast(InstanceObj.OutputComponent); + USceneComponent* ComponentTemplate = Cast(TemplateObj.OutputComponent); + UObject* OutputObject = InstanceObj.OutputObject; + + if (ComponentInstance) + { + // The translation process has either constructed new components, or it is + // reusing existing components, or changed an output (or all or none of the aforementioned). + // Carefully inspect the SCS graph to determine whether there is a corresponding + // (and compatible) node for this output. If not, create a new node and remove unusable node, if any. + + USCS_Node* ComponentNode = nullptr; + { + // Check whether the current OutputComponent being referenced by the template is still valid. + // Even if it was removed in the editor, it doesn't have any associated destroyed / pendingkill state. + // Instead we're going to check for validity by finding an SCS node with a matching template component. + bool bValidComponentTemplate = (ComponentTemplate != nullptr); + if (ComponentTemplate) + { + + ComponentNode = FindSCSNodeForTemplateComponentInClassHierarchy(ComponentTemplate); + bValidComponentTemplate = bValidComponentTemplate && (ComponentNode != nullptr); + } + + if (!bValidComponentTemplate) + { + // Either this component was removed from the editor or it doesn't exist yet. + // Ensure the references are cleared + TemplateObj.OutputComponent = nullptr; + ComponentTemplate = nullptr; + } + } + + // NOTE: we can't use the component instance name directly due to the Blueprint compiler performing an internal checking + // using FComponentEditorUtils::IsValidVariableNameString(), which will return false if the name looks like an autogenerated name... + //FString ComponentName = ComponentInstance->GetName(); + FString ComponentName = FBlueprintEditorUtils::GetClassNameWithoutSuffix(ComponentInstance->GetClass()); + FName ComponentFName = FName(ComponentName); + + + const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )( + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | + EditorUtilities::ECopyOptions::CallPostEditChangeProperty | + EditorUtilities::ECopyOptions::CallPostEditMove); + + if (IsValid(ComponentNode)) + { + // Check if we have an existing and compatible SCS node containing a USceneComponent as a template component. + bool bComponentNodeIsValid = true; + + ComponentTemplate = Cast(ComponentNode->ComponentTemplate); + + bComponentNodeIsValid = bComponentNodeIsValid && ComponentInstance->GetClass() == ComponentNode->ComponentClass; + bComponentNodeIsValid = bComponentNodeIsValid && ComponentTemplate != nullptr; + // TODO: Do we need to perform any other compatibility checks? + + if (!bComponentNodeIsValid) + { + // Component template is not compatible. We can't reuse it. + + SCSHACNode->RemoveChildNode(ComponentNode); + SCS->RemoveNode(ComponentNode); + ComponentNode = nullptr; + ComponentTemplate = nullptr; + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + } + + if (ComponentNode) + { + // We found a reusable SCS node. Just copy the component instance + // properties over to the existing template. + check(ComponentNode->ComponentTemplate); + + // UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + // //Params.bReplaceObjectClassReferences = false; + // Params.bDoDelta = false; // Perform a deep copy + // Params.bClearReferences = false; + // UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + + FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + + ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; + } + else + { + // We couldn't find a reusable SCS node. + // Duplicate the instance component and create a new corresponding SCS node + ComponentNode = SCS->CreateNode(ComponentInstance->GetClass(), ComponentFName); + + UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // We need a deep copy of parameters here so the CDO values get copied as well + UEditorEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + // FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); + + // { + // UInstancedStaticMeshComponent* Component = Cast(ComponentNode->ComponentTemplate); + // if (Component) + // { + // } + // } + + // NOTE: The EComponentCreationMethod here is currently set to be the same as a component that was + // created manually in the editor. + ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; + + // Add this node to the SCS root set. + + // Attach the new node the HAC SCS node + // NOTE: This will add the node to the SCS->AllNodes list too but it won't update + // the nodename map. We can't forcibly update the Node/Name map either since the + // relevant functions have not been exported. + SCSHACNode->AddChildNode(ComponentNode); + + // Set the output component. + TemplateObj.OutputComponent = ComponentNode->ComponentTemplate; + + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + + // Cache the mapping between the output and the SCS node. + check(ComponentNode); + CachedOutputNodes.Add(Entry.Key, ComponentNode->VariableGuid); + } // if (ComponentInstance) + /* + else if (InstanceObj.OutputObject) + { + + } + */ + + // Add the updated output object to the template output + TemplateOutputObjects.Add(Entry.Key, TemplateObj); + } + + // Cleanup stale objects for this template output. + for (const auto& StaleId : StaleTemplateObjects) + { + FHoudiniOutputObject& OutputObj = TemplateOutputObjects.FindChecked(StaleId); + + // Ensure the component template is no longer referencing this output. + TemplateOutputObjects.Remove(StaleId); + + USceneComponent* TemplateComponent = Cast(OutputObj.OutputComponent); + + if (TemplateComponent) + { + USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(TemplateComponent); + if (StaleNode) + { + + SCS->RemoveNode(StaleNode, false); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + /* + else + { + + } + */ + } + /* + else + { + + } + */ + } + } //for (int i = 0; i < Outputs.Num(); i++) + + // Clean up stale outputs on the component template. + for (UHoudiniOutput* StaleOutput : StaleTemplateOutputs) + { + if (!StaleOutput) + continue; + + // Remove any components contained in this output from the SCS graph + for (auto& Entry : StaleOutput->GetOutputObjects()) + { + FHoudiniOutputObject& StaleObject = Entry.Value; + USceneComponent* OutputComponent = Cast(StaleObject.OutputComponent); + + if (OutputComponent) + { + + USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(OutputComponent); + if (StaleNode) + { + + SCS->RemoveNode(StaleNode, false); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + } + } + + TemplateOutputs.Remove(StaleOutput); + //StaleOutput->ConditionalBeginDestroy(); + } + + SCS->ValidateSceneRootNodes(); + + // Copy parameters from this component to the template component. + // NOTE: We need to do this since the preview component will be cooking the HDA and get populated with + // all the parameters. This data needs to be sent back to the component template. + UClass* ComponentClass = CachedTemplateComponent->GetClass(); + UHoudiniAssetBlueprintComponent* DefaultObj = Cast(ComponentClass->GetDefaultObject()); + bool bBPStructureModified = false; + CachedTemplateComponent->CopyDetailsFromComponent( + this, + true, + true, + true, + false, + true, + bBPStructureModified, + /* SetFlags */ CachedTemplateComponent->GetMaskedFlags(RF_PropagateToSubObjects)); + + if (bBPStructureModified) + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + + // Copy the cached output nodes back to the template so that + // reconstructed actors can correctly update output objects + // with newly constructed components during ApplyComponentInstanceData() calls. + CachedTemplateComponent->CachedOutputNodes = CachedOutputNodes; + + CachedTemplateComponent->MarkPackageDirty(); + PostEditChange(); + + CachedTemplateComponent->AssetId = AssetId; + CachedTemplateComponent->HapiGUID = HapiGUID; + CachedTemplateComponent->AssetCookCount = AssetCookCount; + CachedTemplateComponent->AssetStateResult = AssetStateResult; + CachedTemplateComponent->bLastCookSuccess = bLastCookSuccess; + +#if WITH_EDITOR + // TODO: Do we need to handle this right now or can we wait for the next Houdini Engine manager tick to process it? + if (CachedTemplateComponent->NeedBlueprintStructureUpdate()) + { + // We are about to recompile the blueprint. This will reconstruct the preview actor so we need to ensure + // that the old actor won't release the houdini nodes. + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); + SetCanDeleteHoudiniNodes(false); + } + /*else if (CachedTemplateComponent->NeedBlueprintUpdate()) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(CachedTemplateComponent.Get()); + }*/ +#endif +} +#endif + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties) +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] From Component: %s"), *(FromComponent->GetPathName())); + + // This should never be called by component templates. + check(!IsTemplate()); + + // Make sure all TransientDuplicate properties from the Template Component needed by this transient component + // gets copied. + + ComponentGUID = FromComponent->ComponentGUID; + + /* + { + const TArray Children = GetAttachChildren(); + for (USceneComponent* Child : Children) + { + if (!Child) + continue; + } + } + */ + + // AssetState = FromComponent->PreviewAssetState; + + // This state should not be shared between template / instance components. + //bFullyLoaded = FromComponent->bFullyLoaded; + + bNoProxyMeshNextCookRequested = FromComponent->bNoProxyMeshNextCookRequested; + + // Reconstruct outputs and update them to point to component instances as opposed to templates. + UObject* TemplateOuter = CachedTemplateComponent->GetOuter(); + + USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; + check(SCS); + + // NOTE: We can find the SCS node for the HoudiniAssetComponent from either the template component or the instance (editor preview) component. + USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + check(SCSHACNode); + + // ----------------------------------------------------- + // Copy outputs to component template + // ----------------------------------------------------- + + TArray& TemplateOutputs = CachedTemplateComponent->Outputs; + + TSet StaleInstanceOutputs(Outputs); + + Outputs.SetNum(TemplateOutputs.Num()); + + for (int i = 0; i < TemplateOutputs.Num(); i++) + { + UHoudiniOutput* TemplateOutput = TemplateOutputs[i]; + if (!IsValid(TemplateOutput)) + continue; + + UHoudiniOutput* InstanceOutput = Outputs[i]; + if (!(InstanceOutput->GetOuter() == this)) + InstanceOutput = nullptr; + + if (InstanceOutput) + { + StaleInstanceOutputs.Remove(InstanceOutput); + } + + if (InstanceOutput) + { + // Copy properties from the current instance component while preserving output objects + // and instanced outputs. + InstanceOutput->CopyPropertiesFrom(TemplateOutput, true); + } + else + { + InstanceOutput = TemplateOutput->DuplicateAndCopyProperties(this, FName(TemplateOutput->GetName())); + InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); + } + + InstanceOutput->SetCanDeleteHoudiniNodes(false); + Outputs[i] = InstanceOutput; + + TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); + TMap& InstanceOutputObjects = InstanceOutput->GetOutputObjects(); + TArray StaleOutputObjects; + InstanceOutputObjects.GetKeys(StaleOutputObjects); + + for (auto& Entry : TemplateOutputObjects) + { + const FHoudiniOutputObject& TemplateObj = Entry.Value; + FHoudiniOutputObject InstanceObj = TemplateObj; + + if (!InstanceOutputObjects.Contains(Entry.Key)) + continue; + + StaleOutputObjects.Remove(Entry.Key); + InstanceObj = InstanceOutputObjects.FindChecked(Entry.Key); + + } // for (auto& Entry : TemplateOutputObjects) + + // Cleanup stale output objects for this output. + for (const auto& StaleId : StaleOutputObjects) + { + //TemplateOutput + //check(TemplateOutputs); + + FHoudiniOutputObject& OutputObj = InstanceOutputObjects.FindChecked(StaleId); + + InstanceOutputObjects.Remove(StaleId); + if (OutputObj.OutputComponent) + { + //OutputObj.OutputComponent->ConditionalBeginDestroy(); + OutputObj.OutputComponent = nullptr; + } + } + } // for (int i = 0; i < TemplateOutputs.Num(); i++) + + // Cleanup any stale outputs found on the component instance. + for (UHoudiniOutput* StaleOutput : StaleInstanceOutputs) + { + if (!StaleOutput) + continue; + + if (!(StaleOutput->GetOuter() == this)) + continue; + + // We don't want to clear stale outputs on components instances. Only on template components. + StaleOutput->SetCanDeleteHoudiniNodes(false); + } + + // Copy parameters from the component template to the instance. + bool bBlueprintStructureChanged = false; + CopyDetailsFromComponent(FromComponent, + false, + bClearFromInputs, + bClearToInputs, + false, + true, + bBlueprintStructureChanged, + /*SetFlags*/ RF_Public, + /*ClearFlags*/ RF_DefaultSubObject|RF_ArchetypeObject); +} +#endif + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent( + UHoudiniAssetBlueprintComponent* FromComponent, + const bool bCreateSCSNodes, + const bool bClearChangedToInputs, + const bool bClearChangedFromInputs, + const bool bInCanDeleteHoudiniNodes, + const bool bCopyInputObjectComponentProperties, + bool &bOutBlueprintStructureChanged, + EObjectFlags SetFlags, + EObjectFlags ClearFlags) +{ + check(FromComponent); + + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] FromComponent: %s"), *(FromComponent->GetPathName())); + + /* + if (!FromComponent->HoudiniAsset) + { + return; + } + */ + + // TODO: Try to reuse objects here when we're able. + //// Copy UHoudiniOutput state from instance to template + //UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + ////Params.bReplaceObjectClassReferences = false; + ////Params.bClearReferences = false; + //Params.bDoDelta = true; + //UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + + // Record input remapping that will need to take place when duplicating parameters. + TMap InputMapping; + + // ----------------------------------------------------- + // Copy inputs + // ----------------------------------------------------- + + // TODO: Add support for input components + { + TArray& FromInputs = FromComponent->Inputs; + TSet StaleInputs(Inputs); + USimpleConstructionScript* SCS = GetSCS(); + USCS_Node* SCSHACNode = nullptr; + + if (bCreateSCSNodes) + { + SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + } + + Inputs.SetNum(FromInputs.Num()); + for (int i = 0; i < FromInputs.Num(); i++) + { + UHoudiniInput* FromInput = nullptr; + UHoudiniInput* ToInput = nullptr; + FromInput = FromInputs[i]; + + check(FromInput); + + ToInput = Inputs[i]; + + if (ToInput) + { + // Check whether the instance and template input objects are compatible. + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + + if (!bIsValid) + { + ToInput = nullptr; + } + } + + // TODO: Process stale input objects + + // NOTE: The CopyStateFrom() / DuplicateAndCopyState() will copy/duplicate/cleanup internal inputs to + // ensure that there aren't any shared instances between the ToInput/FromInput. + if (ToInput) + { + // We have a compatible input that we can reuse. + StaleInputs.Remove(ToInput); + ToInput->CopyStateFrom(FromInput, true, bInCanDeleteHoudiniNodes); + } + else + { + + // We don't have an existing / compatible input. Create a new one. + ToInput = FromInput->DuplicateAndCopyState(this, bInCanDeleteHoudiniNodes); + if (SetFlags != RF_NoFlags) + ToInput->SetFlags(SetFlags); + if (ClearFlags != RF_NoFlags) + ToInput->ClearFlags( ClearFlags ); + } + + check(ToInput); + + + UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, bCopyInputObjectComponentProperties, bCreateSCSNodes, SCSHACNode, &bOutBlueprintStructureChanged); + + Inputs[i] = ToInput; + InputMapping.Add(FromInput, ToInput); + + if (bClearChangedToInputs) + { + // Clear the changed flags on the FromInput so that it doesn't trigger + // another update. The ToInput will now be carrying to changed/update flags. + ToInput->MarkChanged(false); + ToInput->MarkAllInputObjectsChanged(false); + } + + if (bClearChangedFromInputs) + { + // Clear the changed flags on the FromInput so that it doesn't trigger + // another update. The ToInput will now be carrying to changed/update flags. + FromInput->MarkChanged(false); + FromInput->MarkAllInputObjectsChanged(false); + } + } + + // Cleanup any stale inputs from this component. + // NOTE: We would typically only have stale inputs when copying state from + // the component instance to the component template. Garbage collection + // eventually picks up the input objects and removes the content + // but until such time we are stuck with those nodes as inputs in the Houdini session + // so we get rid of those nodes immediately here to avoid some user confusion. + for (UHoudiniInput* StaleInput : StaleInputs) + { + if (!IsValid(StaleInput)) + continue; + + check(StaleInput->GetOuter() == this); + + if (StaleInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + StaleInput->ConditionalBeginDestroy(); + } + } + + + // ----------------------------------------------------- + // Copy parameters (and optionally remap inputs). + // ----------------------------------------------------- + TMap ParameterMapping; + + TArray& FromParameters = FromComponent->Parameters; + Parameters.SetNum(FromParameters.Num()); + + for (int i = 0; i < FromParameters.Num(); i++) + { + UHoudiniParameter* FromParameter = nullptr; + UHoudiniParameter* ToParameter = nullptr; + + FromParameter = FromParameters[i]; + + check(FromParameter); + + if (Parameters.IsValidIndex(i)) + { + ToParameter = Parameters[i]; + } + + if (ToParameter) + { + bool bIsValid = true; + // Check whether To/From parameters are compatible + bIsValid = bIsValid && ToParameter->Matches(*FromParameter); + bIsValid = bIsValid && ToParameter->GetOuter() == this; + + if (!bIsValid) + ToParameter = nullptr; + } + + if (ToParameter) + { + // Parameter already exists. Simply sync the state. + ToParameter->CopyStateFrom(FromParameter, true, ClearFlags, SetFlags); + } + else + { + // TODO: Check whether parameters are the same to avoid recreating them. + ToParameter = FromParameter->DuplicateAndCopyState(this, ClearFlags, SetFlags); + Parameters[i] = ToParameter; + } + + check(ToParameter); + ParameterMapping.Add(FromParameter, ToParameter); + + if (bClearChangedFromInputs) + { + // We clear the Changed flag on the FromParameter (most likely on the component template) + // since the template parameter state has now been transfered to the preview component and + // will resume processing from there. + FromParameter->MarkChanged(false); + } + } + + // Apply remappings on the new parameters + for (UHoudiniParameter* ToParameter : Parameters) + { + ToParameter->RemapParameters(ParameterMapping); + ToParameter->RemapInputs(InputMapping); + } + + FProperty* ParametersProperty = GetClass()->FindPropertyByName(TEXT("Parameters")); + FPropertyChangedEvent Evt(ParametersProperty); + PostEditChangeProperty(Evt); + + bEnableCooking = FromComponent->bEnableCooking; + bRecookRequested = FromComponent->bRecookRequested; + bRebuildRequested = FromComponent->bRebuildRequested; +} + +void +UHoudiniAssetBlueprintComponent::UpdateInputObjectComponentReferences( + USimpleConstructionScript* SCS, + UHoudiniInput* FromInput, + UHoudiniInput* ToInput, + const bool bCopyInputObjectProperties, + const bool bCreateMissingSCSNodes, + USCS_Node* SCSHACParent, + bool* bOutSCSNodeCreated) +{ + TArray ToInputObjects; + TArray FromInputObjects; + TArray StaleInputObjects; + + ToInput->GetAllHoudiniInputSplineComponents(ToInputObjects); + FromInput->GetAllHoudiniInputSplineComponents(FromInputObjects); + + StaleInputObjects = ToInputObjects; + + const int32 NumInputObjects = FromInputObjects.Num(); + ToInputObjects.SetNum(NumInputObjects); + + const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + //Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; + + for(int32 InputObjectIndex = 0; InputObjectIndex < NumInputObjects; ++InputObjectIndex) + { + UHoudiniInputHoudiniSplineComponent* FromInputObject = FromInputObjects[InputObjectIndex]; + UHoudiniInputHoudiniSplineComponent* ToInputObject = ToInputObjects[InputObjectIndex]; + if (!FromInputObject) + continue; + if (!ToInputObject) + continue; + + USCS_Node* SCSNode = nullptr; + if (CachedInputNodes.Contains(ToInputObject->Guid)) + { + // Reuse / update the existing SCS node. + SCSNode = SCS->FindSCSNodeByGuid( CachedInputNodes.FindChecked(ToInputObject->Guid) ); + } + + if (!SCSNode) + { + if (!bCreateMissingSCSNodes) + continue; // This input object should be removed. + } + + USceneComponent* ToComponent = nullptr; + USceneComponent* FromComponent = Cast(FromInputObject->GetObject()); + + StaleInputObjects.Remove(ToInputObject); + + if (FromComponent) + { + if (!SCSNode) + { + if (bCreateMissingSCSNodes) + { + // Create a new SCS node + SCSNode = SCS->CreateNode(FromComponent->GetClass()); + SCSHACParent->AddChildNode(SCSNode); + if (bOutSCSNodeCreated) + { + *bOutSCSNodeCreated = true; + } + AddInputObjectMapping(ToInputObject->Guid, SCSNode->VariableGuid); + } + } + + if (SCSNode) + { + if (bCreateMissingSCSNodes) + { + // If we have been instructed to create missing SCS nodes, assume we are copying + // the the component template. + ToComponent = Cast(SCSNode->ComponentTemplate); + } + else + { + // We are not copying to the component template, so we're assuming this is a + // component instance. Find the component on the owning actor that matches the SCS node. + AActor* ToOwningActor = ToInput->GetTypedOuter(); + check(ToOwningActor); + + ToComponent = Cast(FindComponentInstanceInActor(ToOwningActor, SCSNode)); + } + + if (bCopyInputObjectProperties && ToComponent) + { + USceneComponent* ToAttachParent = ToComponent->GetAttachParent(); + // Copy specific properties from the component template to the instance, if supported by the component. + // We typically resort to this in order to transfer Transient and TransientDuplicate properties from the + // component template over to the instance (typically HasChanged / NeedsToTriggerUpdate flags) in order for + // the instance to cook properly. + IHoudiniEngineCopyPropertiesInterface* ToCopyableComponent = Cast(ToComponent); + if (ToCopyableComponent) + { + // Let the component manage its own data copying. + ToCopyableComponent->CopyPropertiesFrom(FromComponent); + } + else + { + // The component doesn't implement the property copy interface. Simply do a general property copy. + //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, ToComponent, Params); + FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, ToComponent, ComponentCopyOptions); + } + ToComponent->PostEditChange(); + } + } + } + + ToInputObject->Update(ToComponent); + ToInputObjects[InputObjectIndex] = ToInputObject; + } + + for (UHoudiniInputObject* StaleInputObject : StaleInputObjects) + { + if (!StaleInputObject) + continue; + StaleInputObject->InvalidateData(); + ToInput->RemoveHoudiniInputObject(StaleInputObject); + ToInput->MarkChanged(true); + // TODO: Find the corresponding SCS node and remove it + } +} + +#endif + +#if WITH_EDITOR +bool +UHoudiniAssetBlueprintComponent::HasOpenEditor() const +{ + if (IsTemplate()) + { + IAssetEditorInstance* EditorInstance = FindEditorInstance(); + + return EditorInstance != nullptr; + } + + return false; +} +#endif + +#if WITH_EDITOR +IAssetEditorInstance* +UHoudiniAssetBlueprintComponent::FindEditorInstance() const +{ + UClass* BPGC = Cast(GetOuter()); + if (!IsValid(BPGC)) + return nullptr; + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!IsValid(Blueprint)) + return nullptr; + if (!CachedAssetEditorSubsystem.IsValid()) + return nullptr; + + IAssetEditorInstance* EditorInstance = CachedAssetEditorSubsystem->FindEditorForAsset(Blueprint, false); + + return EditorInstance; +} +#endif + +#if WITH_EDITOR +AActor* +UHoudiniAssetBlueprintComponent::GetPreviewActor() const +{ + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + if (BlueprintEditor) + { + return BlueprintEditor->GetPreviewActor(); + } + return nullptr; +} +#endif + +UHoudiniAssetComponent* +UHoudiniAssetBlueprintComponent::GetCachedTemplate() const +{ + return CachedTemplateComponent.Get(); +} + +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateParameters() const +//{ +// return IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateInputs() const +//{ +// return !IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateOutputs() const +//{ +// return !IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanProcessOutputs() const +//{ +// return !IsTemplate(); +//} + +//bool +//UHoudiniAssetBlueprintComponent::CanInstantiateAsset() const +//{ +// // If this is a preview component, it should not trigger an asset instantiation. It should wait +// // for the BPGC template component to finish the cook, get the synced data and then translate. +// +// if (IsPreview()) +// return false; +// +// return true; +//} +// +//// Check whether the HAC can translate Houdini outputs at all +//bool +//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini() const +//{ +// // Template components can't translate Houdini output since they typically do not exist in a world. +// if (IsTemplate()) +// return false; +// // Preview components and normally instanced actors can translate Houdini outputs. +// return true; +//} +// +//// Check whether the HAC can translate a specific output type. +//bool +//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini(EHoudiniOutputType OutputType) const +//{ +// // Blueprint components have limited translation support, for now. +// if (OutputType == EHoudiniOutputType::Mesh) +// return true; +// +// return false; +//} +// +bool +UHoudiniAssetBlueprintComponent::CanDeleteHoudiniNodes() const +{ + return bCanDeleteHoudiniNodes; +} + +void +UHoudiniAssetBlueprintComponent::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; + + for (UHoudiniInput* Input : Inputs) + { + Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for (UHoudiniOutput* Output : Outputs) + { + Output->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +bool +UHoudiniAssetBlueprintComponent::IsValidComponent() const +{ + if (!Super::IsValidComponent()) + return false; + + if (IsTemplate()) + { + UObject* Outer = this->GetOuter(); + if (!IsValid(Outer)) + return false; + UBlueprintGeneratedClass* BPGC = Cast(Outer); + if (!BPGC) + return false; + // Ensure this component is still in the SCS + USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; + if (!SCS) + return false; + USCS_Node* SCSNode = FindSCSNodeForTemplateComponentInClassHierarchy(this); + if (!SCSNode) + return false; + /*UClass* OwnerClass = Outer->GetClass(); + if (!IsValid(OwnerClass)) + return false;*/ + /*UBlueprint* Blueprint = Cast(Outhe); + if (Blueprint) + { + + }*/ + } + +#if WITH_EDITOR + if (!IsTemplate()) + { + if (!GetOwner()) + { + // If it's not a template, it needs an owner! + return false; + } + + USimpleConstructionScript* SCS = GetSCS(); + if (SCS) + { + // We're dealing with a Blueprint related component. + AActor* PreviewActor = GetPreviewActor(); + AActor* OwningActor = GetOwner(); + if (!OwningActor) + return false; + if (OwningActor != PreviewActor) + { + return false; + } + } + + } + + if (IsPreview() && false) + { + USimpleConstructionScript* SCS = GetSCS(); + if (!SCS) + return false; // Preview components should have an SCS. + + // We want to specifically detect whether an editor component is still being previewed. We do this + // by checking whether the owning actor is still the active editor actor in the SCS. + AActor* PreviewActor = GetPreviewActor(); + if (!PreviewActor) + { + return false; + } + + // Ensure this component still belongs the to the current preview actor. + if (PreviewActor != GetOwner()) + { + return false; + } + + /* + AActor* EditorActor = SCS->GetComponentEditorActorInstance(); + if (GetOwner() != EditorActor) + { + return false; + } + */ + } +#endif + + return true; +} + +bool +UHoudiniAssetBlueprintComponent::IsInputTypeSupported(EHoudiniInputType InType) const +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + case EHoudiniInputType::Curve: + return true; + break; + default: + break; + } + return false; +} + +bool +UHoudiniAssetBlueprintComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const +{ + switch (InType) + { + case EHoudiniOutputType::Mesh: + case EHoudiniOutputType::Instancer: + return true; + break; + default: + break; + } + return false; +} + +bool +UHoudiniAssetBlueprintComponent::IsProxyStaticMeshEnabled() const +{ + // TODO: Investigate adding support for proxy meshes in BP + // Disabled for now + return false; +} + +//void +//UHoudiniAssetBlueprintComponent::BroadcastPreAssetCook() +//{ +// // ------------------------------------------------ +// // NOTE: This code will run on TEMPLATE components +// // ------------------------------------------------ +// +// // The HoudiniAsset is about to be recooked. This flag will indicate to +// // the transient components that output processing needs to be baked +// // back to the BP definition. +// bOutputsRequireUpdate = true; +// +// Super::BroadcastPreAssetCook(); +//} + +void +UHoudiniAssetBlueprintComponent::OnPrePreCook() +{ + check(IsPreview()); + + Super::OnPrePreCook(); + + // We need to allow deleting houdini nodes + SetCanDeleteHoudiniNodes(true); +} + +void +UHoudiniAssetBlueprintComponent::OnPostPreCook() +{ + check(IsPreview()); + + Super::OnPostPreCook(); + + // Ensure the houdini nodes can be deleted during the translation process. + SetCanDeleteHoudiniNodes(false); +} + +void +UHoudiniAssetBlueprintComponent::OnPreOutputProcessing() +{ + check(IsPreview()); + + Super::OnPreOutputProcessing(); + + // Ensure the houdini nodes can be deleted during the translation process. + SetCanDeleteHoudiniNodes(true); +} + +void +UHoudiniAssetBlueprintComponent::OnPostOutputProcessing() +{ + Super::OnPostOutputProcessing(); + + // ------------------------------------------------ + // NOTE: + // In Blueprint editor mode, this code will run on PREVIEW components + // In Map editor mode, this code will run on component instances. + // ------------------------------------------------ + if (IsPreview()) + { + // Ensure all the inputs / outputs belonging to the + // preview actor won't be deleted by PreviewActor destruction. + SetCanDeleteHoudiniNodes(false); + +#if WITH_EDITOR + CopyStateToTemplateComponent(); +#endif + + } + bUpdatedFromTemplate = false; +} + +void UHoudiniAssetBlueprintComponent::OnPrePreInstantiation() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnPrePreInstantiation] Component: %s"), *(GetPathName())); + + check(IsPreview()); + + if (bUpdatedFromTemplate) + return; + + check(CachedTemplateComponent.IsValid()); + + // This HDA is about to be cooked but not through template parameter changes. It is likely that an input changed directly in the preview world. + // We need to flag our inputs and parameters appropriately in order to preserve their values. + + // We need to mark all our parameters as changed/not triggering update + for (auto CurrentParam : Parameters) + { + if (CurrentParam) + { + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(false); + } + } + + // We need to mark all our inputs as changed/not triggering update + for (auto CurrentInput : Inputs) + { + if (CurrentInput) + { + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(false); + CurrentInput->MarkDataUploadNeeded(true); + } + } +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniRegisterCompleted() +{ + if (IsTemplate()) + { + // TODO: Do we need to set any status flags or clear stuff to ensure + // the BP HAC will cook properly when the BP is opened again... + + // If the template is being registered, we need to invalidate the AssetId here since it likely + // contains a stale asset id from its last cook. + AssetId = -1; + // Template component's have very limited update requirements / capabilities. + // Mostly just cache parameters and cook state. + AssetState = EHoudiniAssetState::ProcessTemplate; + } + + Super::NotifyHoudiniRegisterCompleted(); +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniPreUnregister() +{ + if (IsTemplate()) + { + // Templates can delete Houdini nodes when they get deregistered. + SetCanDeleteHoudiniNodes(true); + } + Super::NotifyHoudiniPreUnregister(); +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniPostUnregister() +{ + InvalidateData(); + + Super::NotifyHoudiniPostUnregister(); + + if (IsTemplate()) + { + SetCanDeleteHoudiniNodes(false); + } +} + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::OnComponentCreated() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnComponentCreated] Component: %s"), *(GetPathName())); + + Super::OnComponentCreated(); + bUpdatedFromTemplate = false; + + CachePreviewState(); + + if (IsPreview()) + { + // Don't set an initial AssetState here. Preview components should only cook when template's + // Houdini Asset or HDA parameters have changed. + + // Clear these to ensure that we're not sharing references with the component template (otherwise + // the shared objects will get deleted when the component instance gets destroyed). + // These objects will be properly duplicated when copying state from the component template. + Inputs.Empty(); + Parameters.Empty(); + } + + // Wait until InitializeComponent() for blueprint construction to complete before we start caching blueprint data. + +} +#endif + + +void +UHoudiniAssetBlueprintComponent::OnRegister() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnRegister] Component: %s"), *(GetPathName())); + + Super::OnRegister(); + + // We run our Blueprint caching functions here since this the last hook that we have before + // entering HoudiniEngineTick(); + + CacheBlueprintData(); + CachePreviewState(); + + if (IsPreview()) + { + check(CachedTemplateComponent.Get()); + // Ensure that the component template has been registered since it needs to be processed for parameter updates by the HE manager. + if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(CachedTemplateComponent.Get())) + { + // The template component has not been registered yet, which means that we're probably busy opening a Blueprint editor and this + // preview component will need to be updated. + + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(CachedTemplateComponent.Get(), true); + CachedTemplateComponent->SetCanDeleteHoudiniNodes(false); + // Since we're likely opening a fresh blueprint editor, we'll need to instantiate the HDA. + bHasRegisteredComponentTemplate = true; + } + } + + if (IsTemplate()) + { + // We're initializing the asset id for HAC template here since it doesn't get unloaded + // from memory, for example, between Blueprint Editor open/close so we need to make sure + // that the AssetId has indeed been reset between registrations. + AssetId = -1; + } + + //TickInitialization(); +} + +void +UHoudiniAssetBlueprintComponent::BeginDestroy() +{ + Super::BeginDestroy(); +} + +void +UHoudiniAssetBlueprintComponent::DestroyComponent(bool bPromoteChildren) +{ + //FDebug::DumpStackTraceToLog(); + if (CachedTemplateComponent.IsValid() && TemplatePropertiesChangedHandle.IsValid()) + { + CachedTemplateComponent->Modify(); + CachedTemplateComponent->OnParametersChangedEvent.Remove(TemplatePropertiesChangedHandle); + } + Super::DestroyComponent(bPromoteChildren); +} + +void +UHoudiniAssetBlueprintComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); +} + +TStructOnScope +UHoudiniAssetBlueprintComponent::GetComponentInstanceData() const +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::GetComponentInstanceData] Component: %s"), *(GetPathName())); + + TStructOnScope ComponentInstanceData = MakeStructOnScope(this); + FHoudiniAssetBlueprintInstanceData* InstanceData = ComponentInstanceData.Cast(); + + InstanceData->AssetId = AssetId; + InstanceData->AssetState = AssetState; + InstanceData->SubAssetIndex = SubAssetIndex; + InstanceData->ComponentGUID = ComponentGUID; + InstanceData->HapiGUID = HapiGUID; + InstanceData->HoudiniAsset = HoudiniAsset; + InstanceData->SourceName = GetPathName(); + InstanceData->AssetCookCount = AssetCookCount; + InstanceData->bHasBeenLoaded = bHasBeenLoaded; + InstanceData->bHasBeenDuplicated = bHasBeenDuplicated; + InstanceData->bPendingDelete = bPendingDelete; + InstanceData->bRecookRequested = bRecookRequested; + InstanceData->bEnableCooking = bEnableCooking; + InstanceData->bForceNeedUpdate = bForceNeedUpdate; + InstanceData->bLastCookSuccess = bLastCookSuccess; + InstanceData->bRegisteredComponentTemplate = bHasRegisteredComponentTemplate; + + InstanceData->Inputs.Empty(); + + for (UHoudiniInput* Input : Inputs) + { + if (!Input) + continue; + UHoudiniInput* TransientInput = Input->DuplicateAndCopyState(GetTransientPackage(), false); + InstanceData->Inputs.Add(TransientInput); + } + + // Cache the current outputs + InstanceData->Outputs.Empty(); + int OutputIndex = 0; + for(UHoudiniOutput* Output : Outputs) + { + if (!Output) + continue; + + TMap OutputObjects = Output->GetOutputObjects(); + for (auto& Entry : OutputObjects) + { + FHoudiniAssetBlueprintOutput OutputObjectData; + OutputObjectData.OutputIndex = OutputIndex; + OutputObjectData.OutputObject = Entry.Value; + InstanceData->Outputs.Add(Entry.Key, OutputObjectData); + } + + ++OutputIndex; + } + + return ComponentInstanceData; + +} + +void +UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData(FHoudiniAssetBlueprintInstanceData* InstanceData, const bool bPostUCS) +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData] Component: %s"), *(GetPathName())); + check(InstanceData); + + if (!bPostUCS) + { + // Initialize the component before the User Construction Script runs + USimpleConstructionScript* SCS = GetSCS(); + check(SCS); + + TArray StaleInputs(Inputs); + + // We need to update references contain in inputs / outputs to point to new reconstructed components. + const int32 NumInputs = InstanceData->Inputs.Num(); + Inputs.SetNum(NumInputs); + for (int i = 0; i < NumInputs; ++i) + { + UHoudiniInput* FromInput = InstanceData->Inputs[i]; + UHoudiniInput* ToInput = Inputs[i]; + + if (ToInput) + { + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + if (!bIsValid) + { + ToInput = nullptr; + } + } + + if (ToInput) + { + // Reuse input + StaleInputs.Remove(ToInput); + ToInput->CopyStateFrom(FromInput, true, false); + } + else + { + // Create new input + ToInput = FromInput->DuplicateAndCopyState(this, false); + } + +#if WITH_EDITOR + // We can't create missing SCS nodes here since we're likely already in the middle of a + // Blueprint reconstruction. We'll have to recreate missing SCS nodes next time the + // component state if copied to the template. + UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, true, false); +#endif + + Inputs[i] = ToInput; + } + + // We need to update FHoudiniOutputObject SceneComponent references to + // the newly created components. Since we cached a map of Output Object IDs to + // SCSNodes (during CopyStateToTemplateComponent), we can the SCSNode that corresponds to this output objects and find + // the SceneComponent that matches the SCSNode's variable name. + // It is important to note that it is safe to do it this way since we're in the pre-UCS + // phase so that current components should match the SCS graph exactly (no user construction script + // interference here yet). + + for (auto& Entry : InstanceData->Outputs) + { + FHoudiniOutputObjectIdentifier& ObjectId = Entry.Key; + FHoudiniAssetBlueprintOutput& OutputData = Entry.Value; + + // NOTE: Output objects are going to be empty here since they dissapear during actor reconstruction. + // We'll need to repopulate from the instance data. + + check(Outputs.IsValidIndex(OutputData.OutputIndex)); + UHoudiniOutput* Output = Outputs[OutputData.OutputIndex]; + check(Output); + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject NewObject = OutputData.OutputObject; + + if (OutputData.OutputObject.OutputComponent) + { + // Update the output component reference. + check(CachedOutputNodes.Contains(ObjectId)) + const FGuid VariableGuid = CachedOutputNodes.FindChecked(ObjectId); + USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(VariableGuid); + + if (SCSNode) + { + // Find the component that corresponds to the SCS node. + USceneComponent* SceneComponent = FindActorComponentByName(GetOwner(), SCSNode->GetVariableName()); + NewObject.OutputComponent = SceneComponent; + } + else + { + NewObject.OutputComponent = nullptr; + } + } + + OutputObjects.Add(ObjectId, NewObject); + } + + if (CachedTemplateComponent.IsValid()) + { +#if WITH_EDITOR + CopyStateFromTemplateComponent( CachedTemplateComponent.Get(), false, false, true); +#endif + } + + AssetId = InstanceData->AssetId; + SubAssetIndex = InstanceData->SubAssetIndex; + ComponentGUID = InstanceData->ComponentGUID; + HapiGUID = InstanceData->HapiGUID; + + // Apply the previous HoudiniAsset to the component + // so that we can compare it against the template during CopyStateFromTemplate() calls to see whether it changed. + HoudiniAsset = InstanceData->HoudiniAsset; + + AssetCookCount = InstanceData->AssetCookCount; + bHasBeenLoaded = InstanceData->bHasBeenLoaded; + bHasBeenDuplicated = InstanceData->bHasBeenDuplicated; + bPendingDelete = InstanceData->bPendingDelete; + bRecookRequested = InstanceData->bRecookRequested; + bEnableCooking = InstanceData->bEnableCooking; + bForceNeedUpdate = InstanceData->bForceNeedUpdate; + bLastCookSuccess = InstanceData->bLastCookSuccess; + bHasRegisteredComponentTemplate = InstanceData->bRegisteredComponentTemplate; + + AssetState = InstanceData->AssetState; + + SetCanDeleteHoudiniNodes(false); + + } // if (!bPostUCS) + /* + else + { + // PostUCS + + } + */ +} + + +void +UHoudiniAssetBlueprintComponent::HoudiniEngineTick() +{ + if (!IsFullyLoaded()) + { + USimpleConstructionScript* SCS = GetSCS(); + if (SCS == nullptr) + { + OnFullyLoaded(); + } + else if (IsPreview()) + { + AActor* OwningActor = GetOwner(); + check(OwningActor); + + // If this is a *preview component*, it is important to wait for the template component to be fully loaded + // since it needs to be initialized so that the component instance can copy initial values from the template. + check(CachedTemplateComponent.Get()); + + if (CachedTemplateComponent->IsFullyLoaded()) + { +#if WITH_EDITOR + if(SCS->IsConstructingEditorComponents()) + { + // We're stuck in an editor blueprint construction / preview actor update. Wait some more. + } + else + { + OnFullyLoaded(); + } +#else + OnFullyLoaded(); +#endif + } + } + else + { + // Anything else can go onto being fully loaded at this point. + OnFullyLoaded(); + } + } +} + +void +UHoudiniAssetBlueprintComponent::OnFullyLoaded() +{ + Super::OnFullyLoaded(); + + // Check whether this component is inside a Blueprint editor. If this object lives outside the blueprint editor (in , then we need to ensure that this + // component won't be influencing the Blueprint asset. + + if (!IsTemplate()) + { +#if WITH_EDITOR + AActor* PreviewActor = GetPreviewActor(); +#else + AActor* PreviewActor = nullptr; +#endif + AActor* OwningActor = GetOwner(); + + if (!PreviewActor) + { + bIsInBlueprintEditor = false; + AssetState = EHoudiniAssetState::None; + return; + } + + if (OwningActor && PreviewActor != OwningActor) + { + bIsInBlueprintEditor = false; + AssetState = EHoudiniAssetState::None; + return; + } + } + + bIsInBlueprintEditor = true; + + CachePreviewState(); + CacheBlueprintData(); + + /* + for (UHoudiniOutput* Output : Outputs) + { + if (!Output) + continue; + } + */ + + if (IsTemplate()) + { + AssetId = -1; + AssetState = EHoudiniAssetState::ProcessTemplate; + } + + if (IsPreview()) + { + check(CachedTemplateComponent.Get()); + + // If this is a preview actor, sync initial settings from the component template +#if WITH_EDITOR + CopyStateFromTemplateComponent(CachedTemplateComponent.Get(), false, false, true); +#endif + + TemplatePropertiesChangedHandle = CachedTemplateComponent->OnParametersChangedEvent.AddUObject(this, &UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler); + if (bHoudiniAssetChanged) + { + + // The HoudiniAsset has changed, so we need to force the PreviewInstance to re-instantiate + AssetState = EHoudiniAssetState::NeedInstantiation; + bForceNeedUpdate = true; + bHoudiniAssetChanged = false; + // TODO: Make this better? + CachedTemplateComponent->bHoudiniAssetChanged = false; + } + + if (bHasRegisteredComponentTemplate) + { + // We have a newly registered component template. One of two things happened to cause this: + // 1. A new HoudiniAssetBlueprintComponent was created and registered. + // 2. The Blueprint Editor was closed / opened. + // The problem that arises in the #2 is that the template component was never fully unloaded + // from memory (it was deregistered but not destroyed). After deregistration we had the + // opportunity to invalidate asset/node ids but now that it has reregistered (without going + // through the normal initialization process) we will have to force a call to MarkAsNeedInstantiation + // during the next OnTemplateParametersChangedHandler() invocation. + bHasBeenLoaded = true; + } + } +} + +void +UHoudiniAssetBlueprintComponent::OnTemplateParametersChanged() +{ + OnParametersChangedEvent.Broadcast(this); +} + +void UHoudiniAssetBlueprintComponent::OnBlueprintStructureModified() +{ + check(IsTemplate()); + bBlueprintStructureModified = false; + +#if WITH_EDITOR + if (IsTemplate()) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(this); + } + else + { + check(CachedTemplateComponent.IsValid()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); + } +#endif +} + +void UHoudiniAssetBlueprintComponent::OnBlueprintModified() +{ + check(IsTemplate()); + bBlueprintModified = false; +#if WITH_EDITOR + FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(this); +#endif +} + +void +UHoudiniAssetBlueprintComponent::OnHoudiniAssetChanged() +{ + if (IsTemplate()) + { + // Invalidate data associated with this component since we're about to change and reinstantiate the Houdini Asset. + SetCanDeleteHoudiniNodes(true); + InvalidateData(); + SetCanDeleteHoudiniNodes(false); + Parameters.Empty(); + Inputs.Empty(); + } + + Super::OnHoudiniAssetChanged(); + + // Set on template components, then copied to preview components, and + // then used (and reset) during OnFullyLoaded. + bHoudiniAssetChanged = true; +} + +void +UHoudiniAssetBlueprintComponent::RegisterHoudiniComponent(UHoudiniAssetComponent *InComponent) +{ + // We only want to register this component if it is the preview actor for the Blueprint editor. +#if WITH_EDITOR + AActor* PreviewActor = GetPreviewActor(); +#else + AActor* PreviewActor = nullptr; +#endif + AActor* OwningActor = GetOwner(); + if (!OwningActor) + return; + + if (PreviewActor != OwningActor) + return; + + Super::RegisterHoudiniComponent(InComponent); +} + + + + +//bool UHoudiniAssetBlueprintComponent::TickInitialization() +//{ +// return true; +// +// if (IsFullyLoaded()) +// return true; +// +// bool bHasFinishedLoading = Super::TickInitialization(); +// +// if (!bHasFinishedLoading) +// return false; +// +// if (!IsTemplate()) +// { +// +// if (CachedTemplateComponent.Get()) +// { +// // Now that that SCS has finished constructing editor components, we can continue. +// // Copy the current state from the template component, in case there is something that can be processed. +// CopyStateFromTemplateComponent(CachedTemplateComponent.Get()); +// } +// +// AssetStateResult = EHoudiniAssetStateResult::None; +// AssetState = EHoudiniAssetState::None; +// +// bForceNeedUpdate = true; +// AssetState = EHoudiniAssetState::PostCook; +// } +// +// return true; +//} + +template +inline void +UHoudiniAssetBlueprintComponent::SetTypedValueAt(const FString& Name, ValueT& Value, int Index) +{ + ParamT* Parameter = Cast(FindParameterByName(Name)); + if (!Parameter) + return; + + Parameter->SetValueAt(Value, Index); +} + +bool +UHoudiniAssetBlueprintComponent::HasParameter(FString Name) +{ + return FindParameterByName(Name) != nullptr; +} + +void +UHoudiniAssetBlueprintComponent::SetFloatParameter(FString Name, float Value, int Index) +{ + SetTypedValueAt(Name, Value, Index); +} + +void +UHoudiniAssetBlueprintComponent::SetToggleValueAt(FString Name, bool Value, int Index) +{ + UHoudiniParameterToggle* Parameter = Cast(FindParameterByName(Name)); + if (!Parameter) + return; + + Parameter->SetValueAt(Value, Index); +} + +//void UHoudiniAssetBlueprintComponent::OnPostCookHandler(UHoudiniAssetComponent* InComponent) +//{ +// +// // Before this component handles any translation, we need to make sure that it still belongs to the editor actor. +// // When a blueprint gets recompiled, the editor actor gets replaced with a new one but the old actor has not yet +// // been ftroyed / garbage collected so its components still receive cook events from the template. +// +// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponent); +// if (!IsValid(ComponentTemplate)) +// return; +// +// CopyStateFromTemplateComponent(ComponentTemplate); +// bForceNeedUpdate = true; +//} + +void +UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler(UHoudiniAssetComponent* InComponentTemplate) +{ + if (!(AssetState == EHoudiniAssetState::None || AssetState == EHoudiniAssetState::NeedInstantiation || AssetState == EHoudiniAssetState::NeedRebuild)) + // Don't process parameter changes since we're already cooking -- it is going to break things badly if we do. + return; + + if (!IsValidComponent()) + return; + + UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); + if (!ComponentTemplate) + return; + + // The component instance needs to copy values from the template. + bool bBlueprintStructureChanged = false; +#if WITH_EDITOR + CopyDetailsFromComponent(ComponentTemplate, + false, + false, + true, + false, + true, + bBlueprintStructureChanged, + RF_Public, + RF_ClassDefaultObject|RF_ArchetypeObject); +#endif + + SetCanDeleteHoudiniNodes(false); + + if (bHasRegisteredComponentTemplate) + { + // NOTE: It is very important to call this *after* CopyDetailsFromComponent(), since CopyDetailsFromComponent + // will clobber the inputs and parameter states on this component. + + // If we already have a valid asset id, keep it. + if (AssetId >= 0) + { + MarkAsNeedCook(); + } + else + { + MarkAsNeedInstantiation(); + } + + bHasRegisteredComponentTemplate = false; + bFullyLoaded = true; // MarkAsNeedInstantiation sets this to false. Force to true. + // While MarkAsNeedInstantiation() sets ParametersChanged to true, it does not + // set the 'NeedToTriggerUpdate' flag (both of which needs to be true in order + // to trigger an HDA update) so we are going to force NeedUpdate() to return true + // in order to get an initial cook. + bForceNeedUpdate = true; + } + + bUpdatedFromTemplate = true; +} + +void +UHoudiniAssetBlueprintComponent::InvalidateData() +{ + if (IsTemplate()) + { + // Ensure transient properties are invalidated/released for parameters, inputs and outputs as if the + // the object was undergoing destruction since the template component will likely be reregistered + // without being destroyed. + for(UHoudiniParameter* Param : Parameters) + { + Param->InvalidateData(); + } + + for(UHoudiniInput* Input : Inputs) + { + Input->InvalidateData(); + } + + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + AssetId = -1; + } +} + +//void UHoudiniAssetBlueprintComponent::OnTemplateHoudiniAssetChangedHandler(UHoudiniAssetComponent* InComponentTemplate) +//{ +// +// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); +// if (!ComponentTemplate) +// return; +// check(IsPreview()); +// +// // The Houdini Asset was changed on the template. We need to recook. +// AssetState = EHoudiniAssetState::NeedInstantiation; +// +//} + +USceneComponent* +UHoudiniAssetBlueprintComponent::FindOwnerComponentByName(FName ComponentName) const +{ + AActor* Owner = GetOwner(); + if (!Owner) + return nullptr; + + return FindActorComponentByName(Owner, ComponentName); +} + +USceneComponent* +UHoudiniAssetBlueprintComponent::FindActorComponentByName(AActor* InActor, FName ComponentName) const +{ + const TSet& Components = InActor->GetComponents(); + + for (UActorComponent* Component : Components) + { + USceneComponent* SceneComponent = Cast(Component); + if (!IsValid(SceneComponent)) + continue; + if (FName(SceneComponent->GetName()) == ComponentName) + return SceneComponent; + } + + return nullptr; +} + +bool UHoudiniAssetBlueprintComponent::GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid) +{ + FGuid* SCSGuid = CachedInputNodes.Find(InputGuid); + if (!SCSGuid) + return false; + OutSCSGuid = *SCSGuid; + return true; +} + +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const +{ + const TArray& AllNodes = SCS->GetAllNodes(); + + for (USCS_Node* Node : AllNodes) + { + if (!Node) + continue; + + if (Node->ComponentTemplate == InComponent) + return Node; + } + + return nullptr; +} + +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponentInClassHierarchy( + const UActorComponent* InComponent) const +{ + UObject* Outer = this->GetOuter(); + if (!IsValid(Outer)) + return nullptr; + UBlueprintGeneratedClass* MainBPGC; + if (IsTemplate()) + { + MainBPGC = Cast(Outer); + } + else + { + AActor* OwningActor = GetOwner(); + MainBPGC = Cast(OwningActor->GetClass()); + } + + check(MainBPGC); + TArray BPGCStack; + UBlueprintGeneratedClass::GetGeneratedClassesHierarchy(MainBPGC, BPGCStack); + for(const UBlueprintGeneratedClass* BPGC : BPGCStack) + { + USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; + if (!SCS) + return nullptr; + USCS_Node* SCSNode = FindSCSNodeForTemplateComponent(SCS, InComponent); + SCSNode = SCS->FindSCSNode(InComponent->GetFName()); + if (SCSNode) + return SCSNode; + } + + return nullptr; +} + +#if WITH_EDITOR +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const +{ + const TArray& AllNodes = SCS->GetAllNodes(); + + if (!InComponent) + return nullptr; + + for (USCS_Node* Node : AllNodes) + { + if (!Node) + continue; + if (Node->EditorComponentInstance.Get() == InComponent) + return Node; + } + + return nullptr; +} +#endif + +UActorComponent* +UHoudiniAssetBlueprintComponent::FindComponentInstanceInActor(const AActor* InActor, + USCS_Node* SCSNode) const +{ + UActorComponent* ComponentTemplate = SCSNode->ComponentTemplate; + + UActorComponent* ComponentInstance = NULL; + if (InActor != NULL) + { + if (SCSNode != NULL) + { + FName VariableName = SCSNode->GetVariableName(); + if (VariableName != NAME_None) + { + UWorld* World = InActor->GetWorld(); + FObjectPropertyBase* Property = FindFProperty(InActor->GetClass(), VariableName); + if (Property != NULL) + { + // Return the component instance that's stored in the property with the given variable name + ComponentInstance = Cast(Property->GetObjectPropertyValue_InContainer(InActor)); + } + else if (World != nullptr && World->WorldType == EWorldType::EditorPreview) + { + // If this is the preview actor, return the cached component instance that's being used for the pmnaview actor prior to recompiling the Blueprint +#if WITH_EDITOR + ComponentInstance = SCSNode->EditorComponentInstance.Get(); +#endif + } + } + } + else if (ComponentTemplate != NULL) + { +#if WITH_EDITOR + TInlineComponentArray Components; + InActor->GetComponents(Components); + ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components); +#endif + } + } + + return ComponentInstance; +} + + +//void UHoudiniAssetBlueprintComponent::OnOutputProcessingCompletedHandler(UHoudiniAssetComponent* InComponent) +//{ +// +// UHoudiniAssetBlueprintComponent* TemplateComponent = Cast(InComponent); +// if (!IsValid(TemplateComponent)) +// return; +// +// CopyStateFromComponent(TemplateComponent); +// bForceNeedUpdate = true; +//} + +//#if WITH_EDITOR +//void UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason) +//{ +// +// if (CachedBlueprint.Get()) +// { +// } +// +// if (Asset) +// { +// +// } +// +//} +//#endif + +void +UHoudiniAssetBlueprintComponent::CachePreviewState() +{ + bCachedIsPreview = false; + +#if WITH_EDITOR + AActor* ComponentOwner = GetOwner(); + if (!IsValid(ComponentOwner)) + return; + + USimpleConstructionScript* SCS = GetSCS(); + if (SCS == nullptr) + return; + + // Get the preview actor directly from the BlueprintEditor. + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + if (BlueprintEditor) + { + AActor* PreviewActor = BlueprintEditor->GetPreviewActor(); + if (PreviewActor == ComponentOwner) + { + bCachedIsPreview = true; + return; + } + } +#endif +} + +void +UHoudiniAssetBlueprintComponent::CacheBlueprintData() +{ + CachedBlueprint = nullptr; + CachedActorCDO = nullptr; + CachedTemplateComponent = IsTemplate() ? this : nullptr; + +#if WITH_EDITOR + CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); +#endif + + UBlueprintGeneratedClass* BPGC = Cast(GetOuter()); + if (BPGC) + { + // Dealing with a component template + CachedBlueprint = Cast(BPGC->ClassGeneratedBy); + } + else + { + // Dealing with a component instance. + CachedBlueprint = Cast(GetOuter()->GetClass()->ClassGeneratedBy); + } + + if (CreationMethod != EComponentCreationMethod::SimpleConstructionScript) + return; + + AActor* ComponentOwner = this->GetOwner(); + if (!IsValid(ComponentOwner)) + return; + UClass* OwnerClass = ComponentOwner->GetClass(); + if (!IsValid(OwnerClass)) + return; + + if (!IsTemplate()) + { + // NOTE: The following code allows us to find the component template from an instance. + CachedActorCDO = Cast< AActor >(CachedBlueprint->GeneratedClass->GetDefaultObject()); + if (!CachedActorCDO.IsValid() || (CachedActorCDO.Get() == ComponentOwner)) + return; +#if WITH_EDITOR + UActorComponent* TargetComponent = EditorUtilities::FindMatchingComponentInstance(this, CachedActorCDO.Get()); + CachedTemplateComponent = Cast(TargetComponent); +#endif + } + +} + +USimpleConstructionScript* +UHoudiniAssetBlueprintComponent::GetSCS() const +{ + if (!CachedBlueprint.Get()) + return nullptr; + + return CachedBlueprint->SimpleConstructionScript; +} + +//------------------------------------------------------------------------------------------------ +// FHoudiniAssetBlueprintInstanceData +//------------------------------------------------------------------------------------------------ + +FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData() + : HoudiniAsset(nullptr) + , AssetId(-1) + , AssetState(EHoudiniAssetState::None) + , SubAssetIndex(-1) + , AssetCookCount(0) + , bHasBeenLoaded(false) + , bHasBeenDuplicated(false) + , bPendingDelete(false) + , bRecookRequested(false) + , bRebuildRequested(false) + , bEnableCooking(true) + , bForceNeedUpdate(false) + , bLastCookSuccess(false) + , ComponentGUID(FGuid()) + , HapiGUID(FGuid()) + , bRegisteredComponentTemplate(false) + , SourceName() +{ + +} + +FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent) + : FActorComponentInstanceData(SourceComponent) +{ + +} + +void +FHoudiniAssetBlueprintInstanceData::AddReferencedObjects(FReferenceCollector & Collector) +{ + Super::AddReferencedObjects(Collector); + // TODO: Do we need to add references to output objects here? + // Any other references? + // What are the implications? +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h index 194d2977c..d02640bd5 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h @@ -1,351 +1,351 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Delegates/IDelegateInstance.h" -#include "Engine/Blueprint.h" -#include "HoudiniAssetComponent.h" - -#if WITH_EDITOR - #include "Subsystems/AssetEditorSubsystem.h" -#endif - -#include "HoudiniAssetBlueprintComponent.generated.h" - -class USCS_Node; - -UCLASS(NotBlueprintType, Experimental, meta=(BlueprintSpawnableComponent, DisplayName="Houdini Asset")) -class HOUDINIENGINERUNTIME_API UHoudiniAssetBlueprintComponent : public UHoudiniAssetComponent -{ - GENERATED_BODY() - -public: - UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer); - -#if WITH_EDITOR - // Sync certain variables of this HoudiniAssetComponent to the blueprint generated class. - // This is typically used when the Blueprint definition is being edited and the HAC cook - // took place in a transient HoudiniAssetComponent. Certain properties needs to be copied - // from the transient component back to the Blueprint generated class in order to be retained - // as part of the Client MeetingBlueprint definition. - - void CopyStateToTemplateComponent(); - - void CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties); - - void CopyDetailsFromComponent( - UHoudiniAssetBlueprintComponent* FromComponent, - const bool bCreateSCSNodes, - const bool bClearChangedToInputs, - const bool bClearChangedFromInputs, - const bool bInCanDeleteHoudiniNodes, - const bool bCopyInputObjectComponentProperties, - bool &bOutBlueprintStructureChanged, - EObjectFlags SetFlags=RF_NoFlags, - EObjectFlags ClearFlags=RF_NoFlags); - - // Update references on ToInput by looking up component references on FromInput in the SCS graph, on locating the correct component for ToInput. - void UpdateInputObjectComponentReferences( - USimpleConstructionScript* SCS, - UHoudiniInput* FromInput, - UHoudiniInput* ToInput, - const bool bCopyInputObjectProperties, - const bool bCreateMissingSCSNodes=false, - USCS_Node* ParentSCSNode=nullptr, - bool* bOutSCSNodeCreated=nullptr); - - virtual bool HasOpenEditor() const override; - IAssetEditorInstance* FindEditorInstance() const; - AActor* GetPreviewActor() const; -#endif - - virtual UHoudiniAssetComponent* GetCachedTemplate() const override; - - //------------------------------------------------------------------------------------------------ - // Supported Features - //------------------------------------------------------------------------------------------------ - - // Some features may be unavaible depending on the context in which the Houdini Asset Component - // has been instantiated. - - virtual bool CanDeleteHoudiniNodes() const override; - - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - - virtual bool IsValidComponent() const override; - - virtual bool IsInputTypeSupported(EHoudiniInputType InType) const override; - virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const override; - - virtual bool IsProxyStaticMeshEnabled() const override; - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - //virtual void BroadcastPreAssetCook() override; - virtual void OnPrePreCook() override; - virtual void OnPostPreCook() override; - virtual void OnPreOutputProcessing() override; - virtual void OnPostOutputProcessing() override; - virtual void OnPrePreInstantiation() override; - virtual void NotifyHoudiniRegisterCompleted() override; - virtual void NotifyHoudiniPreUnregister() override; - virtual void NotifyHoudiniPostUnregister() override; - - //------------------------------------------------------------------------------------------------ - // UActorComponent overrides - //------------------------------------------------------------------------------------------------ -#if WITH_EDITOR - virtual void OnComponentCreated() override; -#endif - - virtual void OnRegister() override; - - virtual void BeginDestroy() override; - virtual void DestroyComponent(bool bPromoteChildren = false) override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - // Refer USplineComponent for a decent reference on how to use component instance data. - virtual TStructOnScope GetComponentInstanceData() const override; - void ApplyComponentInstanceData(struct FHoudiniAssetBlueprintInstanceData* ComponentInstanceData, const bool bPostUCS); - - //------------------------------------------------------------------------------------------------ - // UHoudiniAssetComponent overrides - //------------------------------------------------------------------------------------------------ - - FHoudiniAssetComponentEvent OnParametersChangedEvent; - FHoudiniAssetComponentEvent OnHoudiniAssetChangedEvent; - - virtual void HoudiniEngineTick() override; - virtual void OnFullyLoaded() override; - virtual void OnTemplateParametersChanged() override; - virtual void OnHoudiniAssetChanged() override; - virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) override; - - virtual void OnBlueprintStructureModified() override; - virtual void OnBlueprintModified() override; - - - //------------------------------------------------------------------------------------------------ - // Blueprint functions - //------------------------------------------------------------------------------------------------ - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - bool HasParameter(FString Name); - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - void SetFloatParameter(FString Name, float Value, int Index=0); - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - void SetToggleValueAt(FString Name, bool Value, int Index=0); - - void AddInputObjectMapping(const FGuid& InputGuid, const FGuid& SCSVariableGuid) { CachedInputNodes.Add(InputGuid, SCSVariableGuid); } - bool GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid); - void RemoveInputObjectSCSVariableGuid(const FGuid& InputGuid) { CachedInputNodes.Remove(InputGuid); }; - - USCS_Node* FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; - USCS_Node* FindSCSNodeForTemplateComponentInClassHierarchy(const UActorComponent* InComponent) const; -#if WITH_EDITOR - USCS_Node* FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; -#endif // WITH_EDITOR - UActorComponent* FindComponentInstanceInActor(const AActor* InActor, USCS_Node* SCSNode) const; - -protected: - - template - void SetTypedValueAt(const FString& Name, ValueT& Value, int Index=0); - - void OnTemplateParametersChangedHandler(UHoudiniAssetComponent* ComponentTemplate); - void InvalidateData(); - - USceneComponent* FindOwnerComponentByName(FName ComponentName) const; - USceneComponent* FindActorComponentByName(AActor * InActor, FName ComponentName) const; - - void CachePreviewState(); - void CacheBlueprintData(); - - USimpleConstructionScript* GetSCS() const; - - //// The output translation has finished. - //void OnOutputProcessingCompletedHandler(UHoudiniAssetComponent * InComponent); - -#if WITH_EDITOR - //void ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason); - TWeakObjectPtr CachedAssetEditorSubsystem; -#endif - - TWeakObjectPtr CachedBlueprint; - TWeakObjectPtr CachedActorCDO; - TWeakObjectPtr CachedTemplateComponent; - - /*UPROPERTY(DuplicateTransient) - bool bOutputsRequireUpdate;*/ - UPROPERTY() - bool FauxBPProperty; - - UPROPERTY() - bool bHoudiniAssetChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bUpdatedFromTemplate; - - UPROPERTY() - bool bIsInBlueprintEditor; - - UPROPERTY(Transient, DuplicateTransient) - bool bCanDeleteHoudiniNodes; - - UPROPERTY(Transient, DuplicateTransient) - bool bHasRegisteredComponentTemplate; - - FDelegateHandle TemplatePropertiesChangedHandle; - - // This is used to keep track of which SCS variable names correspond to which - // output objects. - // This seems like it will cause issues in the map. - UPROPERTY() - TMap CachedOutputNodes; - - // This is used to keep track of which (SCS) variable guids correspond to which - // input objects. - UPROPERTY() - TMap CachedInputNodes; -}; - - -///** Used to keep track of output data and mappings during reconstruction */ -USTRUCT() -struct FHoudiniAssetBlueprintOutput -{ - GENERATED_BODY() - - UPROPERTY() - int32 OutputIndex; - - UPROPERTY() - FHoudiniOutputObject OutputObject; - - FHoudiniAssetBlueprintOutput() - : OutputIndex(INDEX_NONE) - { - - } -}; - - -/** Used to store HoudiniAssetComponent data during BP reconstruction */ -USTRUCT() -struct FHoudiniAssetBlueprintInstanceData : public FActorComponentInstanceData -{ - GENERATED_BODY() -public: - FHoudiniAssetBlueprintInstanceData(); - FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent); - - virtual ~FHoudiniAssetBlueprintInstanceData() = default; - - /*virtual bool ContainsData() const override - { - return (HAC != nullptr) || Super::ContainsData(); - }*/ - - virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override - { - Super::ApplyToComponent(Component, CacheApplyPhase); - CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); - } - - virtual void AddReferencedObjects(FReferenceCollector& Collector) override; - - // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. - UPROPERTY() - UHoudiniAsset* HoudiniAsset; - - UPROPERTY() - int32 AssetId; - - UPROPERTY() - EHoudiniAssetState AssetState; - - // Subasset index - UPROPERTY() - uint32 SubAssetIndex; - - UPROPERTY() - uint32 AssetCookCount; - - UPROPERTY() - bool bHasBeenLoaded; - - UPROPERTY() - bool bHasBeenDuplicated; - - UPROPERTY() - bool bPendingDelete; - - UPROPERTY() - bool bRecookRequested; - - UPROPERTY() - bool bRebuildRequested; - - UPROPERTY() - bool bEnableCooking; - - UPROPERTY() - bool bForceNeedUpdate; - - UPROPERTY() - bool bLastCookSuccess; - - /*UPROPERTY(DuplicateTransient) - TSet DownstreamHoudiniAssets;*/ - - UPROPERTY() - FGuid ComponentGUID; - - UPROPERTY() - FGuid HapiGUID; - - UPROPERTY() - bool bRegisteredComponentTemplate; - - // Name of the component from which this - // data was copied. Used for debugging purposes. - UPROPERTY() - FString SourceName; - - UPROPERTY() - TMap Outputs; - - UPROPERTY() - TArray Inputs; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Delegates/IDelegateInstance.h" +#include "Engine/Blueprint.h" +#include "HoudiniAssetComponent.h" + +#if WITH_EDITOR + #include "Subsystems/AssetEditorSubsystem.h" +#endif + +#include "HoudiniAssetBlueprintComponent.generated.h" + +class USCS_Node; + +UCLASS(NotBlueprintType, Experimental, meta=(BlueprintSpawnableComponent, DisplayName="Houdini Asset")) +class HOUDINIENGINERUNTIME_API UHoudiniAssetBlueprintComponent : public UHoudiniAssetComponent +{ + GENERATED_BODY() + +public: + UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer); + +#if WITH_EDITOR + // Sync certain variables of this HoudiniAssetComponent to the blueprint generated class. + // This is typically used when the Blueprint definition is being edited and the HAC cook + // took place in a transient HoudiniAssetComponent. Certain properties needs to be copied + // from the transient component back to the Blueprint generated class in order to be retained + // as part of the Client MeetingBlueprint definition. + + void CopyStateToTemplateComponent(); + + void CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties); + + void CopyDetailsFromComponent( + UHoudiniAssetBlueprintComponent* FromComponent, + const bool bCreateSCSNodes, + const bool bClearChangedToInputs, + const bool bClearChangedFromInputs, + const bool bInCanDeleteHoudiniNodes, + const bool bCopyInputObjectComponentProperties, + bool &bOutBlueprintStructureChanged, + EObjectFlags SetFlags=RF_NoFlags, + EObjectFlags ClearFlags=RF_NoFlags); + + // Update references on ToInput by looking up component references on FromInput in the SCS graph, on locating the correct component for ToInput. + void UpdateInputObjectComponentReferences( + USimpleConstructionScript* SCS, + UHoudiniInput* FromInput, + UHoudiniInput* ToInput, + const bool bCopyInputObjectProperties, + const bool bCreateMissingSCSNodes=false, + USCS_Node* ParentSCSNode=nullptr, + bool* bOutSCSNodeCreated=nullptr); + + virtual bool HasOpenEditor() const override; + IAssetEditorInstance* FindEditorInstance() const; + AActor* GetPreviewActor() const; +#endif + + virtual UHoudiniAssetComponent* GetCachedTemplate() const override; + + //------------------------------------------------------------------------------------------------ + // Supported Features + //------------------------------------------------------------------------------------------------ + + // Some features may be unavaible depending on the context in which the Houdini Asset Component + // has been instantiated. + + virtual bool CanDeleteHoudiniNodes() const override; + + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + + virtual bool IsValidComponent() const override; + + virtual bool IsInputTypeSupported(EHoudiniInputType InType) const override; + virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const override; + + virtual bool IsProxyStaticMeshEnabled() const override; + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + //virtual void BroadcastPreAssetCook() override; + virtual void OnPrePreCook() override; + virtual void OnPostPreCook() override; + virtual void OnPreOutputProcessing() override; + virtual void OnPostOutputProcessing() override; + virtual void OnPrePreInstantiation() override; + virtual void NotifyHoudiniRegisterCompleted() override; + virtual void NotifyHoudiniPreUnregister() override; + virtual void NotifyHoudiniPostUnregister() override; + + //------------------------------------------------------------------------------------------------ + // UActorComponent overrides + //------------------------------------------------------------------------------------------------ +#if WITH_EDITOR + virtual void OnComponentCreated() override; +#endif + + virtual void OnRegister() override; + + virtual void BeginDestroy() override; + virtual void DestroyComponent(bool bPromoteChildren = false) override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + // Refer USplineComponent for a decent reference on how to use component instance data. + virtual TStructOnScope GetComponentInstanceData() const override; + void ApplyComponentInstanceData(struct FHoudiniAssetBlueprintInstanceData* ComponentInstanceData, const bool bPostUCS); + + //------------------------------------------------------------------------------------------------ + // UHoudiniAssetComponent overrides + //------------------------------------------------------------------------------------------------ + + FHoudiniAssetComponentEvent OnParametersChangedEvent; + FHoudiniAssetComponentEvent OnHoudiniAssetChangedEvent; + + virtual void HoudiniEngineTick() override; + virtual void OnFullyLoaded() override; + virtual void OnTemplateParametersChanged() override; + virtual void OnHoudiniAssetChanged() override; + virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) override; + + virtual void OnBlueprintStructureModified() override; + virtual void OnBlueprintModified() override; + + + //------------------------------------------------------------------------------------------------ + // Blueprint functions + //------------------------------------------------------------------------------------------------ + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + bool HasParameter(FString Name); + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + void SetFloatParameter(FString Name, float Value, int Index=0); + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + void SetToggleValueAt(FString Name, bool Value, int Index=0); + + void AddInputObjectMapping(const FGuid& InputGuid, const FGuid& SCSVariableGuid) { CachedInputNodes.Add(InputGuid, SCSVariableGuid); } + bool GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid); + void RemoveInputObjectSCSVariableGuid(const FGuid& InputGuid) { CachedInputNodes.Remove(InputGuid); }; + + USCS_Node* FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; + USCS_Node* FindSCSNodeForTemplateComponentInClassHierarchy(const UActorComponent* InComponent) const; +#if WITH_EDITOR + USCS_Node* FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; +#endif // WITH_EDITOR + UActorComponent* FindComponentInstanceInActor(const AActor* InActor, USCS_Node* SCSNode) const; + +protected: + + template + void SetTypedValueAt(const FString& Name, ValueT& Value, int Index=0); + + void OnTemplateParametersChangedHandler(UHoudiniAssetComponent* ComponentTemplate); + void InvalidateData(); + + USceneComponent* FindOwnerComponentByName(FName ComponentName) const; + USceneComponent* FindActorComponentByName(AActor * InActor, FName ComponentName) const; + + void CachePreviewState(); + void CacheBlueprintData(); + + USimpleConstructionScript* GetSCS() const; + + //// The output translation has finished. + //void OnOutputProcessingCompletedHandler(UHoudiniAssetComponent * InComponent); + +#if WITH_EDITOR + //void ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason); + TWeakObjectPtr CachedAssetEditorSubsystem; +#endif + + TWeakObjectPtr CachedBlueprint; + TWeakObjectPtr CachedActorCDO; + TWeakObjectPtr CachedTemplateComponent; + + /*UPROPERTY(DuplicateTransient) + bool bOutputsRequireUpdate;*/ + UPROPERTY() + bool FauxBPProperty; + + UPROPERTY() + bool bHoudiniAssetChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bUpdatedFromTemplate; + + UPROPERTY() + bool bIsInBlueprintEditor; + + UPROPERTY(Transient, DuplicateTransient) + bool bCanDeleteHoudiniNodes; + + UPROPERTY(Transient, DuplicateTransient) + bool bHasRegisteredComponentTemplate; + + FDelegateHandle TemplatePropertiesChangedHandle; + + // This is used to keep track of which SCS variable names correspond to which + // output objects. + // This seems like it will cause issues in the map. + UPROPERTY() + TMap CachedOutputNodes; + + // This is used to keep track of which (SCS) variable guids correspond to which + // input objects. + UPROPERTY() + TMap CachedInputNodes; +}; + + +///** Used to keep track of output data and mappings during reconstruction */ +USTRUCT() +struct FHoudiniAssetBlueprintOutput +{ + GENERATED_BODY() + + UPROPERTY() + int32 OutputIndex; + + UPROPERTY() + FHoudiniOutputObject OutputObject; + + FHoudiniAssetBlueprintOutput() + : OutputIndex(INDEX_NONE) + { + + } +}; + + +/** Used to store HoudiniAssetComponent data during BP reconstruction */ +USTRUCT() +struct FHoudiniAssetBlueprintInstanceData : public FActorComponentInstanceData +{ + GENERATED_BODY() +public: + FHoudiniAssetBlueprintInstanceData(); + FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent); + + virtual ~FHoudiniAssetBlueprintInstanceData() = default; + + /*virtual bool ContainsData() const override + { + return (HAC != nullptr) || Super::ContainsData(); + }*/ + + virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override + { + Super::ApplyToComponent(Component, CacheApplyPhase); + CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); + } + + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + + // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. + UPROPERTY() + UHoudiniAsset* HoudiniAsset; + + UPROPERTY() + int32 AssetId; + + UPROPERTY() + EHoudiniAssetState AssetState; + + // Subasset index + UPROPERTY() + uint32 SubAssetIndex; + + UPROPERTY() + uint32 AssetCookCount; + + UPROPERTY() + bool bHasBeenLoaded; + + UPROPERTY() + bool bHasBeenDuplicated; + + UPROPERTY() + bool bPendingDelete; + + UPROPERTY() + bool bRecookRequested; + + UPROPERTY() + bool bRebuildRequested; + + UPROPERTY() + bool bEnableCooking; + + UPROPERTY() + bool bForceNeedUpdate; + + UPROPERTY() + bool bLastCookSuccess; + + /*UPROPERTY(DuplicateTransient) + TSet DownstreamHoudiniAssets;*/ + + UPROPERTY() + FGuid ComponentGUID; + + UPROPERTY() + FGuid HapiGUID; + + UPROPERTY() + bool bRegisteredComponentTemplate; + + // Name of the component from which this + // data was copied. Used for debugging purposes. + UPROPERTY() + FString SourceName; + + UPROPERTY() + TMap Outputs; + + UPROPERTY() + TArray Inputs; +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp index b4b57d8f6..c71e5e896 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp @@ -1,2317 +1,2706 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetComponent.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniHandleComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "Engine/StaticMesh.h" -#include "Components/StaticMeshComponent.h" -#include "TimerManager.h" -#include "Landscape.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -void -UHoudiniAssetComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - - // Legacy serialization - // Either try to convert or skip depending the setting value - if (bEnableBackwardCompatibility) - { - // Attemp to convert the v1 object to v2 - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - // Deserialize the legacy data, we'll do the actual conversion in PostLoad() - // After everything has been deserialized - Version1CompatibilityHAC = NewObject(this); - Version1CompatibilityHAC->Serialize(Ar); - } - else - { - // Skip the v1 object - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -bool -UHoudiniAssetComponent::ConvertLegacyData() -{ - if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill()) - return false; - - // Set the Houdini Asset - if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill()) - return false; - - HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset; - - // Convert all parameters - for (auto& LegacyParmPair : Version1CompatibilityHAC->Parameters) - { - if (!LegacyParmPair.Value) - continue; - - UHoudiniParameter* Parm = LegacyParmPair.Value->ConvertLegacyData(this); - LegacyParmPair.Value->CopyLegacyParameterData(Parm); - Parameters.Add(Parm); - } - - // Convert all inputs - for (auto& LegacyInput : Version1CompatibilityHAC->Inputs) - { - // Convert v1 input to v2 - UHoudiniInput* Input = LegacyInput->ConvertLegacyInput(this); - - Inputs.Add(Input); - } - - // Lambdas for finding/creating outputs from an HGPO - auto FindOrCreateOutput = [&](FHoudiniGeoPartObject& InNewHGPO, bool& bNew) - { - UHoudiniOutput* NewOutput = nullptr; - - // See if we can add to an existing output - UHoudiniOutput** FoundOutput = nullptr; - FoundOutput = Outputs.FindByPredicate( - [InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; }); - - if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill()) - { - // FoundOutput is valid, add to it - NewOutput = *FoundOutput; - bNew = false; - } - else - { - // Create a new output object - NewOutput = NewObject( - this, UHoudiniOutput::StaticClass(), NAME_None, RF_NoFlags); - bNew = true; - } - - return NewOutput; - }; - - - // Convert all outputs - // Start by handling the Static Meshes - for (auto& LegacySM : Version1CompatibilityHAC->StaticMeshes) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacySM.Key.ConvertLegacyData(); - - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(NewHGPO); - // Mark if the HoudiniOutput is editable - NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); - } - - // Build a new output object identifier - FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); - Identifier.bLoaded = true; - Identifier.PartName = NewHGPO.PartName; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); - OutputObj.OutputObject = LegacySM.Value; - OutputObj.OutputComponent = nullptr; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - // Handle the SMC for this SM / HGPO - if (LegacySM.Value && !LegacySM.Value->IsPendingKill()) - { - UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value); - if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill()) - OutputObj.OutputComponent = *FoundSMC; - } - - // Add to the outputs - Outputs.AddUnique(NewOutput); - - //NewOutput->StaleCount; - //NewOutput->bLandscapeWorldComposition; - //NewOutput->HoudiniCreatedSocketActors; - //NewOutput->HoudiniAttachedSocketActors; - //NewOutput->bHasEditableNodeBuilt - false; - } - - // ... then Landscapes - for (auto& LegacyLandscape : Version1CompatibilityHAC->LandscapeComponents) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacyLandscape.Key.ConvertLegacyData(); - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(NewHGPO); - // Mark if the HoudiniOutput is editable - NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); - } - - // Build a new output object identifier - FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); - Identifier.bLoaded = true; - Identifier.PartName = NewHGPO.PartName; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); - - // We need to create a LandscapePtr wrapper for the landscaope - UHoudiniLandscapePtr* LandscapePtr = NewObject(NewOutput); - LandscapePtr->SetSoftPtr(LegacyLandscape.Value.IsValid() ? LegacyLandscape.Value.Get() : nullptr); - - OutputObj.OutputObject = LandscapePtr; - OutputObj.OutputComponent = nullptr; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - // Add to the outputs - Outputs.AddUnique(NewOutput); - } - - // ... instancers - for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs) - { - if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill()) - continue; - - FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData(); - - // Prepare this output object's output identifier - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = InstancerHGPO.ObjectId; - OutputIdentifier.GeoId = InstancerHGPO.GeoId; - OutputIdentifier.PartId = InstancerHGPO.PartId; - OutputIdentifier.PartName = InstancerHGPO.PartName; - - EHoudiniInstancerType InstancerType = EHoudiniInstancerType::ObjectInstancer; - if (LegacyInstanceIn->Flags.bIsPackedPrimitiveInstancer) - InstancerType = EHoudiniInstancerType::PackedPrimitive; - else if (LegacyInstanceIn->Flags.bAttributeInstancerOverride) - InstancerType = EHoudiniInstancerType::AttributeInstancer; - else if (LegacyInstanceIn->Flags.bIsAttributeInstancer) - InstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - else if (LegacyInstanceIn->ObjectToInstanceId >= 0) - InstancerType = EHoudiniInstancerType::ObjectInstancer; - - InstancerHGPO.InstancerType = InstancerType; - - //bool bIsMSIC = LegacyInstanceIn->Flags.bIsSplitMeshInstancer; - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(InstancerHGPO); - } - - // Get the output's instanced outputs - TMap& InstancedOutputs = NewOutput->GetInstancedOutputs(); - - int32 InstFieldIdx = 0; - for (auto& LegacyInstanceInputField : LegacyInstanceIn->InstanceInputFields) - { - FHoudiniGeoPartObject InstInputFieldHGPO = LegacyInstanceInputField->HoudiniGeoPartObject.ConvertLegacyData(); - - // Create an instanced output for this object - FHoudiniInstancedOutput NewInstOut; - NewInstOut.OriginalObject = LegacyInstanceInputField->OriginalObject; - NewInstOut.OriginalObjectIndex = -1; - NewInstOut.OriginalTransforms = LegacyInstanceInputField->InstancedTransforms; - - for (auto& InstObj : LegacyInstanceInputField->InstancedObjects) - NewInstOut.VariationObjects.Add(InstObj); - - int32 NumVar = LegacyInstanceInputField->RotationOffsets.Num(); - for (int32 Idx = 0; Idx < NumVar; Idx++) - { - FTransform TransOffset; - TransOffset.SetLocation(FVector::ZeroVector); - if (LegacyInstanceInputField->RotationOffsets.IsValidIndex(Idx)) - TransOffset.SetRotation(LegacyInstanceInputField->RotationOffsets[Idx].Quaternion()); - - if (LegacyInstanceInputField->ScaleOffsets.IsValidIndex(Idx)) - TransOffset.SetScale3D(LegacyInstanceInputField->ScaleOffsets[Idx]); - - NewInstOut.VariationTransformOffsets.Add(TransOffset); - } - - // Build an identifier for the instance output - FHoudiniOutputObjectIdentifier Identifier; - Identifier.ObjectId = InstInputFieldHGPO.ObjectId; - Identifier.GeoId = InstInputFieldHGPO.GeoId; - Identifier.PartId = InstInputFieldHGPO.PartId; - Identifier.PartName = InstInputFieldHGPO.PartName; - Identifier.SplitIdentifier = FString::FromInt(InstFieldIdx); - Identifier.bLoaded = true; - - // Add the instance output to the outputs - InstancedOutputs.Add(Identifier, NewInstOut); - - // Now create an Output object for each variation - int32 VarIdx = 0; - for (auto& LegacyComp : LegacyInstanceInputField->InstancerComponents) - { - // Build a new output object identifier for this variation - FHoudiniOutputObjectIdentifier VarIdentifier; - VarIdentifier.ObjectId = InstInputFieldHGPO.ObjectId; - VarIdentifier.GeoId = InstInputFieldHGPO.GeoId; - VarIdentifier.PartId = InstInputFieldHGPO.PartId; - VarIdentifier.PartName = InstInputFieldHGPO.PartName; - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - VarIdentifier.SplitIdentifier = - FString::FromInt(InstFieldIdx) + TEXT("_") + FString::FromInt(VarIdx); - VarIdentifier.bLoaded = true; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(VarIdentifier); - - OutputObj.OutputObject = nullptr; - OutputObj.OutputComponent = LegacyComp; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - VarIdx++; - } - - // ??? - //LegacyInstanceInputField->VariationTransformsArray; - //LegacyInstanceInputField->InstanceColorOverride; - //LegacyInstanceInputField->VariationInstanceColorOverrideArray; - - // Index of the variation used for each transform - //NewInstOut.TransformVariationIndices; - //NewInstOut.bUniformScaleLocked = false; - - InstFieldIdx++; - } - - // Add to the outputs - Outputs.AddUnique(NewOutput); - } - - // ... then Spline Components (for Curve IN) - for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents) - { - UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value; - if (!CurSplineComp || CurSplineComp->IsPendingKill()) - continue; - - // TODO: Needed? - // Attach the spline to the HAC - CurSplineComp->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - - // Editable curve? / Should create an output for it! - if (CurSplineComp->IsEditableOutputCurve()) - { - FHoudiniGeoPartObject CurHGPO = LegacyCurve.Key.ConvertLegacyData(); - - // Look for an output for that HGPO - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(CurHGPO); - } - - // Build an output object id for the editable curve output - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; - EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; - EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; - EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; - - TMap& OutputObjects = NewOutput->GetOutputObjects(); - FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - FoundOutputObject.OutputComponent = CurSplineComp; - - //CurSplineComp->SetHasEditableNodeBuilt(true); - CurSplineComp->SetIsInputCurve(false); - } - else - { - // Input! - // Conversion of the inputs should have done the job already - CurSplineComp->SetIsInputCurve(true); - } - } - - // ... Handles - for (auto& LegacyHandle : Version1CompatibilityHAC->HandleComponents) - { - // TODO: Handles!! - UHoudiniHandleComponent* NewHandle = nullptr; - HandleComponents.Add(NewHandle); - } - - // ... Materials - UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials; - if(LegacyMaterials && !LegacyMaterials->IsPendingKill()) - { - // Assignements: Apply to all outputs since they're not tied to an HGPO... - for (auto& CurOutput : Outputs) - { - TMap& CurrAssign = CurOutput->GetAssignementMaterials(); - for (auto& LegacyMaterial : LegacyMaterials->Assignments) - { - CurrAssign.Add(LegacyMaterial.Key, LegacyMaterial.Value); - } - } - - // Replacements - // Try to find the output matching the HGPO - for (auto& LegacyMaterial : LegacyMaterials->Replacements) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacyMaterial.Key.ConvertLegacyData(); - - TMap& LegacyReplacement = LegacyMaterial.Value; - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - if (bCreatedNew) - continue; - - TMap& CurReplacement = NewOutput->GetReplacementMaterials(); - for (auto& CurLegacyReplacement : LegacyReplacement) - { - CurReplacement.Add(CurLegacyReplacement.Key, CurLegacyReplacement.Value); - } - } - } - - - // ... Bake Name overrides - for (auto& LegacyBakeNameOverride : Version1CompatibilityHAC->BakeNameOverrides) - { - // In Outputs? - } - - // ... then Downstream asset connections (due to Asset inputs) - for (auto& LegacyDownstreamHAC : Version1CompatibilityHAC->DownstreamAssetConnections) - { - //TSet DownstreamHoudiniAssets; - } - - // Then convert all remaing flags and properties - bGeneratedDoubleSidedGeometry = Version1CompatibilityHAC->bGeneratedDoubleSidedGeometry; - GeneratedPhysMaterial = Version1CompatibilityHAC->GeneratedPhysMaterial; - DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; - GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; - GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; - GeneratedLpvBiasMultiplier = Version1CompatibilityHAC->GeneratedLpvBiasMultiplier; - GeneratedDistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; - GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; - GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; - bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; - GeneratedStreamingDistanceMultiplier = Version1CompatibilityHAC->GeneratedStreamingDistanceMultiplier; - //GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; - GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; - - BakeFolder.Path = Version1CompatibilityHAC->BakeFolder.ToString(); - TemporaryCookFolder.Path = Version1CompatibilityHAC->TempCookFolder.ToString(); - - ComponentGUID = Version1CompatibilityHAC->ComponentGUID; - - bEnableCooking = Version1CompatibilityHAC->bEnableCooking; - bUploadTransformsToHoudiniEngine = Version1CompatibilityHAC->bUploadTransformsToHoudiniEngine; - bCookOnTransformChange = Version1CompatibilityHAC->bTransformChangeTriggersCooks; - bCookOnParameterChange = true; - //Version1CompatibilityHAC->bCookingTriggersDownstreamCooks; - bCookOnAssetInputCook = true; - bOutputless = false; - bOutputTemplateGeos = false; - bFullyLoaded = Version1CompatibilityHAC->bFullyLoaded; - - //bContainsHoudiniLogoGeometry = Version1CompatibilityHAC->bContainsHoudiniLogoGeometry; - //bIsNativeComponent = Version1CompatibilityHAC->bIsNativeComponent; - //bIsPreviewComponent = Version1CompatibilityHAC->bIsPreviewComponent; - //bLoadedComponent = Version1CompatibilityHAC->bLoadedComponent; - //bIsPlayModeActive_Unused = Version1CompatibilityHAC->bIsPlayModeActive_Unused; - //Version1CompatibilityHAC->bTimeCookInPlaymode_Unused; - //Version1CompatibilityHAC->bUseHoudiniMaterials; - - //Version1CompatibilityHAC->GeneratedGeometryScaleFactor; - //Version1CompatibilityHAC->TransformScaleFactor; - //Version1CompatibilityHAC->PresetBuffer; - //Version1CompatibilityHAC->DefaultPresetBuffer; - //Version1CompatibilityHAC->ParameterByName; - - // Now that we're done, update all the output's types - for (auto& CurOutput : Outputs) - { - CurOutput->UpdateOutputType(); - } - - // - // Clean up the legacy HAC - // - - Version1CompatibilityHAC->Parameters.Empty(); - Version1CompatibilityHAC->Inputs.Empty(); - Version1CompatibilityHAC->StaticMeshes.Empty(); - Version1CompatibilityHAC->LandscapeComponents.Empty(); - Version1CompatibilityHAC->InstanceInputs.Empty(); - Version1CompatibilityHAC->SplineComponents.Empty(); - Version1CompatibilityHAC->HandleComponents.Empty(); - //Version1CompatibilityHAC->HoudiniAssetComponentMaterials.Empty(); - Version1CompatibilityHAC->BakeNameOverrides.Empty(); - Version1CompatibilityHAC->DownstreamAssetConnections.Empty(); - Version1CompatibilityHAC->MarkPendingKill(); - Version1CompatibilityHAC = nullptr; - - return true; -} - - -UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - HoudiniAsset = nullptr; - bCookOnParameterChange = true; - bUploadTransformsToHoudiniEngine = true; - bCookOnTransformChange = false; - //bUseNativeHoudiniMaterials = true; - bCookOnAssetInputCook = true; - - AssetId = -1; - AssetState = EHoudiniAssetState::PreInstantiation; - AssetStateResult = EHoudiniAssetStateResult::None; - AssetCookCount = 0; - - SubAssetIndex = -1; - - // Make an invalid GUID, since we do not have any cooking requests. - HapiGUID.Invalidate(); - - // Create unique component GUID. - ComponentGUID = FGuid::NewGuid(); - - bUploadTransformsToHoudiniEngine = true; - - bHasBeenLoaded = false; - bHasBeenDuplicated = false; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = false; - bEnableCooking = true; - bForceNeedUpdate = false; - bLastCookSuccess = false; - bBlueprintStructureModified = false; - bBlueprintModified = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // Folder used for cooking, the value is initialized by Output Translator - // TemporaryCookFolder.Path = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - - // Folder used for baking this asset's outputs, the value is initialized by Output Translator - // BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - bHasComponentTransformChanged = false; - - bFullyLoaded = false; - - bOutputless = false; - - bOutputTemplateGeos = false; - - PDGAssetLink = nullptr; - - StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bEnableProxyStaticMeshOverride = HoudiniRuntimeSettings->bEnableProxyStaticMesh; - bEnableProxyStaticMeshRefinementByTimerOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; - ProxyMeshAutoRefineTimeoutSecondsOverride = HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; - bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; - bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; - } - - bNoProxyMeshNextCookRequested = false; - bBakeAfterNextCook = false; - -#if WITH_EDITORONLY_DATA - bGenerateMenuExpanded = true; - bBakeMenuExpanded = true; - bAssetOptionMenuExpanded = true; - bHelpAndDebugMenuExpanded = true; - - HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; - - bRemoveOutputAfterBake = false; - bRecenterBakedActors = false; - bReplacePreviousBake = false; -#endif - - // - // Set component properties. - // - - Mobility = EComponentMobility::Static; - - 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; - - Bounds = FBox(ForceInitToZero); -} - -UHoudiniAssetComponent::~UHoudiniAssetComponent() -{ - // Unregister ourself so our houdini node can be delete. - - // This gets called in UnRegisterHoudiniComponent, with appropriate checks. Don't call it here. - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); -} - -void UHoudiniAssetComponent::PostInitProperties() -{ - Super::PostInitProperties(); - - // Register ourself to the HER singleton - RegisterHoudiniComponent(this); -} - -UHoudiniAsset * -UHoudiniAssetComponent::GetHoudiniAsset() const -{ - return HoudiniAsset; -} - -FString -UHoudiniAssetComponent::GetDisplayName() const -{ - return GetOwner() ? GetOwner()->GetName() : GetName(); -} - -void -UHoudiniAssetComponent::GetOutputs(TArray& OutOutputs) const -{ - for (UHoudiniOutput* Output : Outputs) - { - OutOutputs.Add(Output); - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh; - } - else - { - return false; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementByTimerEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; - } - else - { - return false; - } - } -} - -float -UHoudiniAssetComponent::GetProxyMeshAutoRefineTimeoutSeconds() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return ProxyMeshAutoRefineTimeoutSecondsOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; - } - else - { - return 5.0f; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; - } - else - { - return false; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; - } - else - { - return false; - } - } -} - -void -UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) -{ - // Check the asset validity - if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill()) - return; - - // If it is the same asset, do nothing. - if ( InHoudiniAsset == HoudiniAsset ) - return; - - HoudiniAsset = InHoudiniAsset; -} - - -void -UHoudiniAssetComponent::OnHoudiniAssetChanged() -{ - // TODO: clear input/params/outputs? - Parameters.Empty(); - - // The asset has been changed, mark us as needing to be reinstantiated - MarkAsNeedInstantiation(); - - // Force an update on the next tick - bForceNeedUpdate = true; -} - -bool -UHoudiniAssetComponent::NeedUpdateParameters() const -{ - // This is being split into a separate function to that it can - // be called separately for component templates. - - if (!bCookOnParameterChange) - return false; - - // Go through all our parameters, return true if they have been updated - for (auto CurrentParm : Parameters) - { - if (!CurrentParm || CurrentParm->IsPendingKill()) - continue; - - if (!CurrentParm->HasChanged()) - continue; - - // See if the parameter doesn't require an update - // (because it has failed to upload previously or has been loaded) - if (!CurrentParm->NeedsToTriggerUpdate()) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateParameters()] Parameters need update for component: %s"), *(GetPathName())); - return true; - } - - return false; -} - -bool -UHoudiniAssetComponent::NeedUpdateInputs() const -{ - // Go through all our inputs, return true if they have been updated - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (!CurrentInput->HasChanged()) - continue; - - // See if the input doesn't require an update - // (because it has failed to upload previously or has been loaded) - if (!CurrentInput->NeedsToTriggerUpdate()) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateInputs()] Inputs need update for component: %s"), *(GetPathName())); - return true; - } - - return false; -} - -bool -UHoudiniAssetComponent::HasPreviousBakeOutput() const -{ - // Look for any bake output objects in the output array - for (const UHoudiniOutput* Output : Outputs) - { - if (!IsValid(Output)) - continue; - - if (BakedOutputs.Num() == 0) - return false; - - for (const FHoudiniBakedOutput& BakedOutput : BakedOutputs) - { - if (BakedOutput.BakedOutputObjects.Num() > 0) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::NeedUpdate() const -{ - if (AssetState != DebugLastAssetState) - { - DebugLastAssetState = AssetState; - } - - // It is important to check this when dealing with Blueprints since the - // preview components start receiving events from the template component - // before the preview component have finished initialization. - if (!IsFullyLoaded()) - return false; - - // We must have a valid asset - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return false; - - if (bForceNeedUpdate) - return true; - - // If we don't want to cook on parameter/input change dont bother looking for updates - if (!bCookOnParameterChange && !bRecookRequested && !bRebuildRequested) - return false; - - // Check if the HAC's transform has changed and transform triggers cook is enabled - if (bCookOnTransformChange && bHasComponentTransformChanged) - return true; - - if (NeedUpdateParameters()) - return true; - - if (NeedUpdateInputs()) - return true; - - // Go through all outputs, filter the editable nodes. Return true if they have been updated. - for (auto CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - // We only care about editable outputs - if (!CurrentOutput->IsEditableNode()) - continue; - - // Trigger an update if the output object is marked as modified by user. - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - for (auto& NextPair : OutputObjects) - { - // For now, only editable curves can trigger update - UHoudiniSplineComponent* HoudiniSplineComponent = Cast(NextPair.Value.OutputComponent); - if (!HoudiniSplineComponent) - continue; - - // Output curves cant trigger an update! - if (HoudiniSplineComponent->bIsOutputCurve) - continue; - - if (HoudiniSplineComponent->NeedsToTriggerUpdate()) - return true; - } - } - - return false; -} - -// Indicates if any of the HAC's output components needs to be updated (no recook needed) -bool -UHoudiniAssetComponent::NeedOutputUpdate() const -{ - // Go through all outputs - for (auto CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs()) - { - if (InstOutput.Value.bChanged) - return true; - } - } - - return false; -} - -bool UHoudiniAssetComponent::NeedBlueprintStructureUpdate() const -{ - // TODO: Add similar flags to inputs, parametsr - return bBlueprintStructureModified; -} - -bool UHoudiniAssetComponent::NeedBlueprintUpdate() const -{ - // TODO: Add similar flags to inputs, parametsr - return bBlueprintModified; -} - -bool -UHoudiniAssetComponent::NotifyCookedToDownstreamAssets() -{ - // Before notifying, clean up our downstream assets - // - check that they are still valid - // - check that we are still connected to one of its asset input - // - check that the asset as the CookOnAssetInputCook trigger enabled - TArray DownstreamToDelete; - for(auto& CurrentDownstreamHAC : DownstreamHoudiniAssets) - { - // Remove the downstream connection by default, - // unless we actually were properly connected to one of this HDa's input. - bool bRemoveDownstream = true; - if (CurrentDownstreamHAC && !CurrentDownstreamHAC->IsPendingKill()) - { - // Go through the HAC's input - for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs) - { - if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill()) - continue; - - EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType(); - if (CurrentDownstreamInputType != EHoudiniInputType::Asset - && CurrentDownstreamInputType != EHoudiniInputType::World) - continue; - - if (!CurrentDownstreamInput->ContainsInputObject(this, CurrentDownstreamInputType)) - continue; - - if (CurrentDownstreamHAC->bCookOnAssetInputCook) - { - // Mark that HAC's input has changed - CurrentDownstreamInput->MarkChanged(true); - } - bRemoveDownstream = false; - } - } - - if (bRemoveDownstream) - { - DownstreamToDelete.Add(CurrentDownstreamHAC); - } - } - - for (auto ToDelete : DownstreamToDelete) - { - DownstreamHoudiniAssets.Remove(ToDelete); - } - - return true; -} - -bool -UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() -{ - for (auto& CurrentInput : Inputs) - { - EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World) - continue; - - TArray* ObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInputType); - if (!ObjectArray) - continue; - - for (auto& CurrentInputObject : (*ObjectArray)) - { - // Get the input HDA - UHoudiniAssetComponent* InputHAC = CurrentInputObject - ? Cast(CurrentInputObject->GetObject()) - : nullptr; - - if (!InputHAC) - continue; - - // If the input HDA needs to be instantiated, force him to instantiate - // if the input HDA is in any other state than None, we need to wait for him - // to finish whatever it's doing - if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) - { - // Tell the input HAC to instantiate - InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; - - // We need to wait - return true; - } - else if (InputHAC->GetAssetState() != EHoudiniAssetState::None) - { - // We need to wait - return true; - } - } - } - - return false; -} - -void -UHoudiniAssetComponent::BeginDestroy() -{ - if (CanDeleteHoudiniNodes()) - { - } - - // Gets called through UnRegisterHoudiniComponent(). - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - - // Unregister ourself so our houdini node can be deleted - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - Super::BeginDestroy(); -} - -void -UHoudiniAssetComponent::MarkAsNeedCook() -{ - // Force the asset state to NeedCook - //AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = true; - bRebuildRequested = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/trigger update - for (auto CurrentParam : Parameters) - { - if (!IsValid(CurrentParam)) - continue; - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(true); - } - - // We need to mark all our inputs as changed/trigger update - for (auto CurrentInput : Inputs) - { - if (!IsValid(CurrentInput)) - continue; - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(true); - CurrentInput->MarkDataUploadNeeded(true); - } - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -void -UHoudiniAssetComponent::MarkAsNeedRebuild() -{ - // Invalidate the asset ID - //AssetId = -1; - - // Force the asset state to NeedRebuild - AssetState = EHoudiniAssetState::NeedRebuild; - AssetStateResult = EHoudiniAssetStateResult::None; - - // Reset some of the asset's flag - //AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = true; - bFullyLoaded = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/trigger update - for (auto CurrentParam : Parameters) - { - if (!IsValid(CurrentParam)) - continue; - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(true); - } - - // We need to mark all our inputs as changed/trigger update - for (auto CurrentInput : Inputs) - { - if (!IsValid(CurrentInput)) - continue; - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(true); - CurrentInput->MarkDataUploadNeeded(true); - } - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -// Marks the asset as needing to be instantiated -void -UHoudiniAssetComponent::MarkAsNeedInstantiation() -{ - // Invalidate the asset ID - AssetId = -1; - - if (Parameters.Num() <= 0 && Inputs.Num() <= 0 && Outputs.Num() <= 0) - { - // The asset has no parameters or inputs. - // This likely indicates it has never cooked/been instantiated. - // Set its state to PreInstantiation to force its instantiation - // so that we can have its parameters/input interface - AssetState = EHoudiniAssetState::PreInstantiation; - } - else - { - // The asset has cooked before since we have a parameter/input interface - // Set its state to need instantiation so that the asset is instantiated - // after being modified - AssetState = EHoudiniAssetState::NeedInstantiation; - } - - AssetStateResult = EHoudiniAssetStateResult::None; - - // Reset some of the asset's flag - AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = false; - bFullyLoaded = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/not triggering update - for (auto CurrentParam : Parameters) - { - if (CurrentParam) - { - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(false); - } - } - - // We need to mark all our inputs as changed/not triggering update - for (auto CurrentInput : Inputs) - { - if (CurrentInput) - { - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(false); - CurrentInput->MarkDataUploadNeeded(true); - } - } - - /*if (!CanInstantiateAsset()) - { - AssetState = EHoudiniAssetState::None; - AssetStateResult = EHoudiniAssetStateResult::None; - }*/ - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -void UHoudiniAssetComponent::MarkAsBlueprintStructureModified() -{ - bBlueprintStructureModified = true; -} - -void UHoudiniAssetComponent::MarkAsBlueprintModified() -{ - bBlueprintModified = true; -} - -void -UHoudiniAssetComponent::PostLoad() -{ - Super::PostLoad(); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - bool bAutomaticLegacyHDARebuild = HoudiniRuntimeSettings->bAutomaticLegacyHDARebuild; - - // Legacy serialization: either try to convert or skip depending the setting value - if (bEnableBackwardCompatibility && Version1CompatibilityHAC != nullptr) - { - // If we have deserialized legacy v1 data, attempt to convert it now - ConvertLegacyData(); - - if(bAutomaticLegacyHDARebuild) - MarkAsNeedRebuild(); - else - MarkAsNeedInstantiation(); - } - else - { - // Normal v2 objet, mark as need instantiation - MarkAsNeedInstantiation(); - } - - // Component has been loaded, not duplicated - bHasBeenDuplicated = false; - - // We need to register ourself - RegisterHoudiniComponent(this); - - // Register our PDG Asset link if we have any - -} - -void -UHoudiniAssetComponent::PostEditImport() -{ - Super::PostEditImport(); - - MarkAsNeedInstantiation(); - - // Component has been duplicated, not loaded - // We do need the loaded flag to reapply parameters, inputs - // and properly update some of the output objects - bHasBeenDuplicated = true; - - //RemoveAllAttachedComponents(); - - AssetState = EHoudiniAssetState::PreInstantiation; - AssetStateResult = EHoudiniAssetStateResult::None; - - // TODO? - // REGISTER? -} - -void -UHoudiniAssetComponent::UpdatePostDuplicate() -{ - // TODO: - // - Keep the output objects/components (remove duplicatetransient on the output object uproperties) - // - Duplicate created objects (ie SM) and materials - // - Update the output components to use these instead - // This should remove the need for a cook on duplicate - - // For now, we simply clean some of the HAC's component manually - const TArray Children = GetAttachChildren(); - - for (auto & NextChild : Children) - { - if (!NextChild || NextChild->IsPendingKill()) - continue; - - USceneComponent * ComponentToRemove = nullptr; - if (NextChild->IsA()) - { - ComponentToRemove = NextChild; - } - else if (NextChild->IsA()) - { - ComponentToRemove = NextChild; - } - /* do not destroy attached duplicated editable curves, they are needed to restore editable curves - else if (NextChild->IsA()) - { - // Remove duplicated editable curve output's Houdini Spline Component, since they will be re-built at duplication. - UHoudiniSplineComponent * HoudiniSplineComponent = Cast(NextChild); - if (HoudiniSplineComponent && HoudiniSplineComponent->IsEditableOutputCurve()) - ComponentToRemove = NextChild; - } - */ - if (ComponentToRemove) - { - ComponentToRemove->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - ComponentToRemove->UnregisterComponent(); - ComponentToRemove->DestroyComponent(); - } - } - - // if there is an associated PDG asset link, call its UpdatePostDuplicate to cleanup references to - // to the original instance's PDG output actors - if (IsValid(PDGAssetLink)) - { - PDGAssetLink->UpdatePostDuplicate(); - } - - SetHasBeenDuplicated(false); -} - -bool UHoudiniAssetComponent::IsInputTypeSupported(EHoudiniInputType InType) const -{ - return true; -} - -bool UHoudiniAssetComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const -{ - return true; -} - -bool -UHoudiniAssetComponent::IsPreview() const -{ - return bCachedIsPreview; -} - -bool UHoudiniAssetComponent::IsValidComponent() const -{ - return true; -} - -void UHoudiniAssetComponent::OnFullyLoaded() -{ - bFullyLoaded = true; -} - - -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); - } - */ -} - -void -UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - - if (CanDeleteHoudiniNodes()) - { - } - - // Unregister ourself so our houdini node can be deleted - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - HoudiniAsset = nullptr; - - // Clear Parameters - for (UHoudiniParameter*& CurrentParm : Parameters) - { - if (CurrentParm && !CurrentParm->IsPendingKill()) - { - CurrentParm->ConditionalBeginDestroy(); - } - else if (GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE) - { - // TODO unneeded log? - // Avoid spamming that error when leaving PIE mode - HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); - } - - CurrentParm = nullptr; - } - - Parameters.Empty(); - - // Clear Inputs - for (UHoudiniInput*& CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - // Destroy connected Houdini asset. - CurrentInput->ConditionalBeginDestroy(); - CurrentInput = nullptr; - } - - Inputs.Empty(); - - // Clear Output - for (UHoudiniOutput*& CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - // Destroy all Houdini created socket actors. - TArray & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors(); - for (auto & CurCreatedActor : CurCreatedSocketActors) - { - if (!CurCreatedActor || CurCreatedActor->IsPendingKill()) - continue; - - CurCreatedActor->Destroy(); - } - CurCreatedSocketActors.Empty(); - - // Detach all Houdini attached socket actors - TArray & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors(); - for (auto & CurAttachedSocketActor : CurAttachedSocketActors) - { - if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill()) - continue; - - CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - } - CurAttachedSocketActors.Empty(); - -#if WITH_EDITOR - // Clean up foliages instances - for (auto& CurrentOutputObject : CurrentOutput->GetOutputObjects()) - { - // Foliage instancers store a HISMC in the components - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentOutputObject.Value.OutputComponent); - if (!FoliageHISMC) - continue; - - UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - continue; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(FoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - continue; - - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - continue; - - // Clean up the instances generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(this, FoliageType); - - if (FoliageHISMC->GetInstanceCount() > 0) - { - // If the component still has instances left after the cleanup, - // make sure that we dont delete it, as the leftover instances are likely hand-placed - CurrentOutputObject.Value.OutputComponent = nullptr; - } - else - { - // Remove the foliage type if it doesn't have any more instances - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - } - } -#endif - - CurrentOutput->Clear(); - // Destroy connected Houdini asset. - CurrentOutput->ConditionalBeginDestroy(); - CurrentOutput = nullptr; - } - - Outputs.Empty(); - - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - // Unregister ourself so our houdini node can be delete. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); - - - // Clear all TOP data and temporary geo/objects from the PDG asset link (if valid) - if (IsValid(PDGAssetLink)) - { -#if WITH_EDITOR - const UWorld* const World = GetWorld(); - if (IsValid(World)) - { - // Only do this for editor worlds, only interactively (not during engine shutdown or garbage collection) - if (World->WorldType == EWorldType::Editor && GIsRunning && !GIsGarbageCollecting) - { - // In case we are recording a transaction (undo, for example) notify that the object will be - // modified. - PDGAssetLink->Modify(); - PDGAssetLink->ClearAllTOPData(); - } - } -#endif - } - - Super::OnComponentDestroyed(bDestroyingHierarchy); -} - -void UHoudiniAssetComponent::RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) -{ - // Registration of this component is wrapped in this virtual function to allow - // derived classed to override this behaviour. - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(InComponent); -} - -void -UHoudiniAssetComponent::OnRegister() -{ - Super::OnRegister(); - - // NOTE: Wait until HoudiniEngineTick() before deciding to mark this object as fully loaded - // since preview components need to wait for component templates to finish their initialization - // before being able to perform state transfers. - - /* - // 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(); - } - } - */ - - // Let TickInitialization() take care of manipulating the bFullyLoaded state. - //bFullyLoaded = true; - - //// If we're constructing editable components in the SCS editor, set the component instance corresponding to this node for editing purposes - // - //USimpleConstructionScript* SCS = GetSCS(); - //if (SCS == nullptr) - //{ - // bFullyLoaded = true; - //} - //else - //{ - // if(SCS->IsConstructingEditorComponents()) - // { - // // We're not fully loaded yet. We're expecting - // } - // else - // { - // bFullyLoaded = true; - // } - //} - -} - -UHoudiniParameter* -UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) -{ - if (!InOtherParam || InOtherParam->IsPendingKill()) - return nullptr; - - for (auto CurrentParam : Parameters) - { - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - if (CurrentParam->Matches(*InOtherParam)) - return CurrentParam; - } - - return nullptr; -} - -UHoudiniInput* -UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) -{ - if (!InOtherInput || InOtherInput->IsPendingKill()) - return nullptr; - - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->Matches(*InOtherInput)) - return CurrentInput; - } - - return nullptr; -} - -UHoudiniHandleComponent* -UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle) -{ - if (!InOtherHandle || InOtherHandle->IsPendingKill()) - return nullptr; - - for (auto CurrentHandle : HandleComponents) - { - if (!CurrentHandle || CurrentHandle->IsPendingKill()) - continue; - - if (CurrentHandle->Matches(*InOtherHandle)) - return CurrentHandle; - } - - return nullptr; -} - -UHoudiniParameter* -UHoudiniAssetComponent::FindParameterByName(const FString& InParamName) -{ - for (auto CurrentParam : Parameters) - { - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - if (CurrentParam->GetParameterName().Equals(InParamName)) - return CurrentParam; - } - - return nullptr; -} - - -void -UHoudiniAssetComponent::OnChildAttached(USceneComponent* ChildComponent) -{ - Super::OnChildAttached(ChildComponent); - - // ... Do corresponding things for other houdini component types. - // ... -} - - -void -UHoudiniAssetComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) -{ - Super::OnUpdateTransform(UpdateTransformFlags, Teleport); - - SetHasComponentTransformChanged(true); -} - -void UHoudiniAssetComponent::HoudiniEngineTick() -{ - if (!IsFullyLoaded()) - { - OnFullyLoaded(); - } -} - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - FProperty * Property = PropertyChangedEvent.MemberProperty; - if (!Property) - return; - - FName PropertyName = Property->GetFName(); - - // Changing the Houdini Asset? - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, HoudiniAsset)) - { - OnHoudiniAssetChanged(); - } - else if (PropertyName == GetRelativeLocationPropertyName() - || PropertyName == GetRelativeRotationPropertyName() - || PropertyName == GetRelativeScale3DPropertyName()) - { - SetHasComponentTransformChanged(true); - } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bOverrideGlobalProxyStaticMeshSettings) - || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bEnableProxyStaticMeshRefinementByTimerOverride) - || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, ProxyMeshAutoRefineTimeoutSecondsOverride)) - { - ClearRefineMeshesTimer(); - // Reset the timer - // SetRefineMeshesTimer will check the relevant settings and only set the timer if enabled via settings - SetRefineMeshesTimer(); - } - //else if (PropertyName == TEXT("Mobility")) - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, 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); - } - } - //else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bVisible)) - else if (PropertyName == TEXT("bVisible")) - { - // Visibility has changed, propagate it to children. - SetVisibility(IsVisible(), true); - } - //else if (PropertyName == TEXT("bHiddenInGame")) - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bHiddenInGame)) - { - // Visibility has changed, propagate it to children. - SetHiddenInGame(bHiddenInGame, true); - } - else - { - // TODO: - // Propagate properties (mobility/visibility etc.. to children components) - // Look in v1 for: if (Property->HasMetaData(TEXT("Category"))) {} and HOUDINI_UPDATE_ALL_CHILD_COMPONENTS - } - -} -#endif - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::PostEditUndo() -{ - Super::PostEditUndo(); - - if (!IsPendingKill()) - { - // Make sure we are registered with the HER singleton - // We could be undoing a HoudiniActor delete - if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(this)) - { - MarkAsNeedInstantiation(); - - // Component has been loaded, not duplicated - bHasBeenDuplicated = false; - - RegisterHoudiniComponent(this); - } - } -} - -#endif - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::OnActorMoved(AActor* Actor) -{ - if (GetOwner() != Actor) - return; - - SetHasComponentTransformChanged(true); -} -#endif - -void -UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged) -{ - // Only update the value if we're fully loaded - // This avoid triggering a recook when loading a level - if(bFullyLoaded) - bHasComponentTransformChanged = InHasChanged; -} - -void -UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // Check the object validity - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // If it is the same object, do nothing. - if (InPDGAssetLink == PDGAssetLink) - return; - - PDGAssetLink = InPDGAssetLink; -} - - -FBoxSphereBounds -UHoudiniAssetComponent::CalcBounds(const FTransform & LocalToWorld) const -{ - FBoxSphereBounds LocalBounds; - FBox BoundingBox = GetAssetBounds(nullptr, false); - 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; -} - - -FBox -UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const -{ - FBox BoxBounds(ForceInitToZero); - - // Query the bounds for all output objects - for (auto & CurOutput : Outputs) - { - if (!CurOutput || CurOutput->IsPendingKill()) - continue; - - BoxBounds += CurOutput->GetBounds(); - } - - // Query the bounds for all our inputs - for (auto & CurInput : Inputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - BoxBounds += CurInput->GetBounds(); - } - - // Query the bounds for all input parameters - for (auto & CurParam : Parameters) - { - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (CurParam->GetParameterType() != EHoudiniParameterType::Input) - continue; - - UHoudiniParameterOperatorPath* InputParam = Cast(CurParam); - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (!InputParam->HoudiniInput.IsValid()) - continue; - - BoxBounds += InputParam->HoudiniInput.Get()->GetBounds(); - } - - // Query the bounds for all our Houdini handles - for (auto & CurHandleComp : HandleComponents) - { - if (!CurHandleComp || CurHandleComp->IsPendingKill()) - continue; - - BoxBounds += CurHandleComp->GetBounds(); - } - - // Also scan all our decendants for SMC bounds not just top-level children - // ( split mesh instances' mesh bounds were not gathered proiperly ) - TArray 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(pChild)) - { - if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) - continue; - - FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); - if (StaticMeshBounds.IsValid) - BoxBounds += StaticMeshBounds; - } - } - - // If nothing was found, init with the asset's location - if (BoxBounds.GetVolume() == 0.0f) - BoxBounds += GetComponentLocation(); - - return BoxBounds; -} - -void -UHoudiniAssetComponent::ClearRefineMeshesTimer() -{ - UWorld *World = GetWorld(); - if (!World) - { - //HOUDINI_LOG_ERROR(TEXT("Cannot ClearRefineMeshesTimer, World is nullptr!")); - return; - } - - World->GetTimerManager().ClearTimer(RefineMeshesTimer); -} - -void -UHoudiniAssetComponent::SetRefineMeshesTimer() -{ - UWorld *World = GetWorld(); - if (!World) - { - HOUDINI_LOG_ERROR(TEXT("Cannot SetRefineMeshesTimer, World is nullptr!")); - return; - } - - // Check if timer-based proxy mesh refinement is enable for this component - const bool bEnableTimer = IsProxyStaticMeshRefinementByTimerEnabled(); - const float TimeSeconds = GetProxyMeshAutoRefineTimeoutSeconds(); - if (bEnableTimer) - { - World->GetTimerManager().SetTimer(RefineMeshesTimer, this, &UHoudiniAssetComponent::OnRefineMeshesTimerFired, 1.0f, false, TimeSeconds); - } - else - { - World->GetTimerManager().ClearTimer(RefineMeshesTimer); - } -} - -void -UHoudiniAssetComponent::OnRefineMeshesTimerFired() -{ - HOUDINI_LOG_MESSAGE(TEXT("UHoudiniAssetComponent::OnRefineMeshesTimerFired()")); - if (OnRefineMeshesTimerDelegate.IsBound()) - { - OnRefineMeshesTimerDelegate.Broadcast(this); - } -} - -bool -UHoudiniAssetComponent::HasAnyCurrentProxyOutput() const -{ - for (const UHoudiniOutput *Output : Outputs) - { - if (Output->HasAnyCurrentProxy()) - { - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasAnyProxyOutput() const -{ - for (const UHoudiniOutput *Output : Outputs) - { - if (Output->HasAnyProxy()) - { - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasAnyOutputComponent() const -{ - for (UHoudiniOutput *Output : Outputs) - { - for(auto& CurrentOutputObject : Output->GetOutputObjects()) - { - if(CurrentOutputObject.Value.OutputComponent) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasOutputObject(UObject* InOutputObjectToFind) const -{ - for (const auto& CurOutput : Outputs) - { - for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) - { - if (CurOutputObject.Value.OutputObject == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.OutputComponent == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.ProxyObject == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.ProxyComponent == InOutputObjectToFind) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const -{ - // Get the state of the asset and check if it is pre-cook, cooked, pending delete/rebuild or invalid - bOutNeedsRebuildOrDelete = false; - bOutInvalidState = false; - switch (AssetState) - { - case EHoudiniAssetState::NeedInstantiation: - case EHoudiniAssetState::PreInstantiation: - case EHoudiniAssetState::Instantiating: - case EHoudiniAssetState::PreCook: - case EHoudiniAssetState::Cooking: - case EHoudiniAssetState::PostCook: - case EHoudiniAssetState::PreProcess: - case EHoudiniAssetState::Processing: - return false; - break; - case EHoudiniAssetState::None: - return true; - break; - case EHoudiniAssetState::NeedRebuild: - case EHoudiniAssetState::NeedDelete: - case EHoudiniAssetState::Deleting: - bOutNeedsRebuildOrDelete = true; - break; - default: - bOutInvalidState = true; - break; - } - - return false; -} - -void -UHoudiniAssetComponent::SetInputPresets(const TMap& InPresets) -{ - // Set the input preset for this HAC -#if WITH_EDITOR - InputPresets = InPresets; -#endif -} - - -void -UHoudiniAssetComponent::ApplyInputPresets() -{ - if (InputPresets.Num() <= 0) - return; - -#if WITH_EDITOR - // Ignore inputs that have been preset to curve - TArray InputArray; - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetInputType() != EHoudiniInputType::Curve) - InputArray.Add(CurrentInput); - } - - // Try to apply the supplied Object to the Input - for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset) - { - UObject * Object = IterToolPreset.Key(); - if (!Object || Object->IsPendingKill()) - continue; - - int32 InputNumber = IterToolPreset.Value(); - if (!InputArray.IsValidIndex(InputNumber)) - continue; - - // If the object is a landscape, add a new landscape input - if (Object->IsA()) - { - // selecting a landscape - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Landscape); - if (InsertNum == 0) - { - // Landscape inputs only support one object! - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Landscape, InsertNum, Object); - } - } - - // If the object is an actor, add a new world input - if (Object->IsA()) - { - // selecting an actor - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::World); - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::World, InsertNum, Object); - } - - // If the object is a static mesh, add a new geometry input (TODO: or BP ? ) - if (Object->IsA()) - { - // selecting a Staticn Mesh - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Geometry, InsertNum, Object); - } - - if (Object->IsA()) - { - // selecting a Houdini Asset - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Asset); - if (InsertNum == 0) - { - // Assert inputs only support one object! - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Asset, InsertNum, Object); - } - } - } - - // The input objects have been set, now change the input type - bool bBPStructureModified = false; - for (auto CurrentInput : Inputs) - { - int32 NumGeo = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - int32 NumAsset = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Asset); - int32 NumWorld = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::World); - int32 NumLandscape = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Landscape); - - EHoudiniInputType NewInputType = EHoudiniInputType::Invalid; - if (NumLandscape > 0 && NumLandscape >= NumGeo && NumLandscape >= NumAsset && NumLandscape >= NumWorld) - NewInputType = EHoudiniInputType::Landscape; - else if (NumWorld > 0 && NumWorld >= NumGeo && NumWorld >= NumAsset && NumWorld >= NumLandscape) - NewInputType = EHoudiniInputType::World; - else if (NumAsset > 0 && NumAsset >= NumGeo && NumAsset >= NumWorld && NumAsset >= NumLandscape) - NewInputType = EHoudiniInputType::Asset; - else if (NumGeo > 0 && NumGeo >= NumAsset && NumGeo >= NumWorld && NumGeo >= NumLandscape) - NewInputType = EHoudiniInputType::Geometry; - - if (NewInputType == EHoudiniInputType::Invalid) - continue; - - // Change the input type, unless if it was preset to a different type and we have object for the preset type - if (CurrentInput->GetInputType() == EHoudiniInputType::Geometry && NewInputType != EHoudiniInputType::Geometry) - { - CurrentInput->SetInputType(NewInputType, bBPStructureModified); - } - else - { - // Input type was preset, only change if that type is empty - if(CurrentInput->GetNumberOfInputObjects() <= 0) - CurrentInput->SetInputType(NewInputType, bBPStructureModified); - } - } - if (bBPStructureModified) - { - MarkAsBlueprintStructureModified(); - } -#endif - - // Discard the tool presets after their first setup - InputPresets.Empty(); -} - - -bool -UHoudiniAssetComponent::IsComponentValid() const -{ - if (!IsValidLowLevel()) - return false; - - if (IsTemplate()) - return false; - - if (IsPendingKillOrUnreachable()) - return false; - - if (!GetOuter()) //|| !GetOuter()->GetLevel() ) - return false; - - return true; -} - -bool -UHoudiniAssetComponent::IsInstantiatingOrCooking() const -{ - return HapiGUID.IsValid(); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" +#include "TimerManager.h" +#include "Landscape.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" +#include "PhysicsEngine/BodySetup.h" +#include "UObject/UObjectGlobals.h" + +#if WITH_EDITOR + #include "Editor/UnrealEd/Private/GeomFitUtils.h" +#endif + +#include "ComponentReregisterContext.h" + +// Macro to update given properties on all children components of the HAC. +#define HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( COMPONENT_CLASS, PROPERTY ) \ + do \ + { \ + TArray ReregisterComponents; \ + TArray LocalAttachChildren;\ + GetChildrenComponents(true, LocalAttachChildren); \ + for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) \ + { \ + COMPONENT_CLASS * Component = Cast(*Iter); \ + if (Component) \ + { \ + Component->PROPERTY = PROPERTY; \ + ReregisterComponents.Add(Component); \ + } \ + } \ + \ + if (ReregisterComponents.Num() > 0) \ + { \ + FMultiComponentReregisterContext MultiComponentReregisterContext(ReregisterComponents); \ + } \ + } \ + while(0) + + +void +UHoudiniAssetComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + + // Legacy serialization + // Either try to convert or skip depending the setting value + if (bEnableBackwardCompatibility) + { + // Attemp to convert the v1 object to v2 + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + // Deserialize the legacy data, we'll do the actual conversion in PostLoad() + // After everything has been deserialized + Version1CompatibilityHAC = NewObject(this); + Version1CompatibilityHAC->Serialize(Ar); + } + else + { + // Skip the v1 object + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +bool +UHoudiniAssetComponent::ConvertLegacyData() +{ + if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill()) + return false; + + // Set the Houdini Asset + if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill()) + return false; + + HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset; + + // Convert all parameters + for (auto& LegacyParmPair : Version1CompatibilityHAC->Parameters) + { + if (!LegacyParmPair.Value) + continue; + + UHoudiniParameter* Parm = LegacyParmPair.Value->ConvertLegacyData(this); + LegacyParmPair.Value->CopyLegacyParameterData(Parm); + Parameters.Add(Parm); + } + + // Convert all inputs + for (auto& LegacyInput : Version1CompatibilityHAC->Inputs) + { + // Convert v1 input to v2 + UHoudiniInput* Input = LegacyInput->ConvertLegacyInput(this); + + Inputs.Add(Input); + } + + // Lambdas for finding/creating outputs from an HGPO + auto FindOrCreateOutput = [&](FHoudiniGeoPartObject& InNewHGPO, bool& bNew) + { + UHoudiniOutput* NewOutput = nullptr; + + // See if we can add to an existing output + UHoudiniOutput** FoundOutput = nullptr; + FoundOutput = Outputs.FindByPredicate( + [InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; }); + + if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill()) + { + // FoundOutput is valid, add to it + NewOutput = *FoundOutput; + bNew = false; + } + else + { + // Create a new output object + NewOutput = NewObject( + this, UHoudiniOutput::StaticClass(), NAME_None, RF_NoFlags); + bNew = true; + } + + return NewOutput; + }; + + + // Convert all outputs + // Start by handling the Static Meshes + for (auto& LegacySM : Version1CompatibilityHAC->StaticMeshes) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacySM.Key.ConvertLegacyData(); + + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(NewHGPO); + // Mark if the HoudiniOutput is editable + NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); + } + + // Build a new output object identifier + FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); + Identifier.bLoaded = true; + Identifier.PartName = NewHGPO.PartName; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); + OutputObj.OutputObject = LegacySM.Value; + OutputObj.OutputComponent = nullptr; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + // Handle the SMC for this SM / HGPO + if (LegacySM.Value && !LegacySM.Value->IsPendingKill()) + { + UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value); + if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill()) + OutputObj.OutputComponent = *FoundSMC; + } + + // Add to the outputs + Outputs.AddUnique(NewOutput); + + //NewOutput->StaleCount; + //NewOutput->bLandscapeWorldComposition; + //NewOutput->HoudiniCreatedSocketActors; + //NewOutput->HoudiniAttachedSocketActors; + //NewOutput->bHasEditableNodeBuilt - false; + } + + // ... then Landscapes + for (auto& LegacyLandscape : Version1CompatibilityHAC->LandscapeComponents) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacyLandscape.Key.ConvertLegacyData(); + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(NewHGPO); + // Mark if the HoudiniOutput is editable + NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); + } + + // Build a new output object identifier + FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); + Identifier.bLoaded = true; + Identifier.PartName = NewHGPO.PartName; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); + + // We need to create a LandscapePtr wrapper for the landscaope + UHoudiniLandscapePtr* LandscapePtr = NewObject(NewOutput); + LandscapePtr->SetSoftPtr(LegacyLandscape.Value.IsValid() ? LegacyLandscape.Value.Get() : nullptr); + + OutputObj.OutputObject = LandscapePtr; + OutputObj.OutputComponent = nullptr; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + // Add to the outputs + Outputs.AddUnique(NewOutput); + } + + // ... instancers + for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs) + { + if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill()) + continue; + + FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData(); + + // Prepare this output object's output identifier + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = InstancerHGPO.ObjectId; + OutputIdentifier.GeoId = InstancerHGPO.GeoId; + OutputIdentifier.PartId = InstancerHGPO.PartId; + OutputIdentifier.PartName = InstancerHGPO.PartName; + + EHoudiniInstancerType InstancerType = EHoudiniInstancerType::ObjectInstancer; + if (LegacyInstanceIn->Flags.bIsPackedPrimitiveInstancer) + InstancerType = EHoudiniInstancerType::PackedPrimitive; + else if (LegacyInstanceIn->Flags.bAttributeInstancerOverride) + InstancerType = EHoudiniInstancerType::AttributeInstancer; + else if (LegacyInstanceIn->Flags.bIsAttributeInstancer) + InstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + else if (LegacyInstanceIn->ObjectToInstanceId >= 0) + InstancerType = EHoudiniInstancerType::ObjectInstancer; + + InstancerHGPO.InstancerType = InstancerType; + + //bool bIsMSIC = LegacyInstanceIn->Flags.bIsSplitMeshInstancer; + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(InstancerHGPO); + } + + // Get the output's instanced outputs + TMap& InstancedOutputs = NewOutput->GetInstancedOutputs(); + + int32 InstFieldIdx = 0; + for (auto& LegacyInstanceInputField : LegacyInstanceIn->InstanceInputFields) + { + FHoudiniGeoPartObject InstInputFieldHGPO = LegacyInstanceInputField->HoudiniGeoPartObject.ConvertLegacyData(); + + // Create an instanced output for this object + FHoudiniInstancedOutput NewInstOut; + NewInstOut.OriginalObject = LegacyInstanceInputField->OriginalObject; + NewInstOut.OriginalObjectIndex = -1; + NewInstOut.OriginalTransforms = LegacyInstanceInputField->InstancedTransforms; + + for (auto& InstObj : LegacyInstanceInputField->InstancedObjects) + NewInstOut.VariationObjects.Add(InstObj); + + int32 NumVar = LegacyInstanceInputField->RotationOffsets.Num(); + for (int32 Idx = 0; Idx < NumVar; Idx++) + { + FTransform TransOffset; + TransOffset.SetLocation(FVector::ZeroVector); + if (LegacyInstanceInputField->RotationOffsets.IsValidIndex(Idx)) + TransOffset.SetRotation(LegacyInstanceInputField->RotationOffsets[Idx].Quaternion()); + + if (LegacyInstanceInputField->ScaleOffsets.IsValidIndex(Idx)) + TransOffset.SetScale3D(LegacyInstanceInputField->ScaleOffsets[Idx]); + + NewInstOut.VariationTransformOffsets.Add(TransOffset); + } + + // Build an identifier for the instance output + FHoudiniOutputObjectIdentifier Identifier; + Identifier.ObjectId = InstInputFieldHGPO.ObjectId; + Identifier.GeoId = InstInputFieldHGPO.GeoId; + Identifier.PartId = InstInputFieldHGPO.PartId; + Identifier.PartName = InstInputFieldHGPO.PartName; + Identifier.SplitIdentifier = FString::FromInt(InstFieldIdx); + Identifier.bLoaded = true; + + // Add the instance output to the outputs + InstancedOutputs.Add(Identifier, NewInstOut); + + // Now create an Output object for each variation + int32 VarIdx = 0; + for (auto& LegacyComp : LegacyInstanceInputField->InstancerComponents) + { + // Build a new output object identifier for this variation + FHoudiniOutputObjectIdentifier VarIdentifier; + VarIdentifier.ObjectId = InstInputFieldHGPO.ObjectId; + VarIdentifier.GeoId = InstInputFieldHGPO.GeoId; + VarIdentifier.PartId = InstInputFieldHGPO.PartId; + VarIdentifier.PartName = InstInputFieldHGPO.PartName; + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + VarIdentifier.SplitIdentifier = + FString::FromInt(InstFieldIdx) + TEXT("_") + FString::FromInt(VarIdx); + VarIdentifier.bLoaded = true; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(VarIdentifier); + + OutputObj.OutputObject = nullptr; + OutputObj.OutputComponent = LegacyComp; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + VarIdx++; + } + + // ??? + //LegacyInstanceInputField->VariationTransformsArray; + //LegacyInstanceInputField->InstanceColorOverride; + //LegacyInstanceInputField->VariationInstanceColorOverrideArray; + + // Index of the variation used for each transform + //NewInstOut.TransformVariationIndices; + //NewInstOut.bUniformScaleLocked = false; + + InstFieldIdx++; + } + + // Add to the outputs + Outputs.AddUnique(NewOutput); + } + + // ... then Spline Components (for Curve IN) + for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents) + { + UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value; + if (!CurSplineComp || CurSplineComp->IsPendingKill()) + continue; + + // TODO: Needed? + // Attach the spline to the HAC + CurSplineComp->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + // Editable curve? / Should create an output for it! + if (CurSplineComp->IsEditableOutputCurve()) + { + FHoudiniGeoPartObject CurHGPO = LegacyCurve.Key.ConvertLegacyData(); + + // Look for an output for that HGPO + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(CurHGPO); + } + + // Build an output object id for the editable curve output + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; + EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; + EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; + EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; + + TMap& OutputObjects = NewOutput->GetOutputObjects(); + FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + FoundOutputObject.OutputComponent = CurSplineComp; + + //CurSplineComp->SetHasEditableNodeBuilt(true); + CurSplineComp->SetIsInputCurve(false); + } + else + { + // Input! + // Conversion of the inputs should have done the job already + CurSplineComp->SetIsInputCurve(true); + } + } + + // ... Handles + for (auto& LegacyHandle : Version1CompatibilityHAC->HandleComponents) + { + // TODO: Handles!! + UHoudiniHandleComponent* NewHandle = nullptr; + HandleComponents.Add(NewHandle); + } + + // ... Materials + UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials; + if(LegacyMaterials && !LegacyMaterials->IsPendingKill()) + { + // Assignements: Apply to all outputs since they're not tied to an HGPO... + for (auto& CurOutput : Outputs) + { + TMap& CurrAssign = CurOutput->GetAssignementMaterials(); + for (auto& LegacyMaterial : LegacyMaterials->Assignments) + { + CurrAssign.Add(LegacyMaterial.Key, LegacyMaterial.Value); + } + } + + // Replacements + // Try to find the output matching the HGPO + for (auto& LegacyMaterial : LegacyMaterials->Replacements) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacyMaterial.Key.ConvertLegacyData(); + + TMap& LegacyReplacement = LegacyMaterial.Value; + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + if (bCreatedNew) + continue; + + TMap& CurReplacement = NewOutput->GetReplacementMaterials(); + for (auto& CurLegacyReplacement : LegacyReplacement) + { + CurReplacement.Add(CurLegacyReplacement.Key, CurLegacyReplacement.Value); + } + } + } + + + // ... Bake Name overrides + for (auto& LegacyBakeNameOverride : Version1CompatibilityHAC->BakeNameOverrides) + { + // In Outputs? + } + + // ... then Downstream asset connections (due to Asset inputs) + for (auto& LegacyDownstreamHAC : Version1CompatibilityHAC->DownstreamAssetConnections) + { + //TSet DownstreamHoudiniAssets; + } + + // Then convert all remaing flags and properties + StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = Version1CompatibilityHAC->bGeneratedDoubleSidedGeometry; + StaticMeshGenerationProperties.GeneratedPhysMaterial = Version1CompatibilityHAC->GeneratedPhysMaterial; + StaticMeshGenerationProperties.DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; + StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; + StaticMeshGenerationProperties.GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; + StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier = Version1CompatibilityHAC->GeneratedLpvBiasMultiplier; + StaticMeshGenerationProperties.GeneratedDistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; + StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; + StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; + StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; + StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = Version1CompatibilityHAC->GeneratedStreamingDistanceMultiplier; + //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; + StaticMeshGenerationProperties.GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; + + BakeFolder.Path = Version1CompatibilityHAC->BakeFolder.ToString(); + TemporaryCookFolder.Path = Version1CompatibilityHAC->TempCookFolder.ToString(); + + ComponentGUID = Version1CompatibilityHAC->ComponentGUID; + + bEnableCooking = Version1CompatibilityHAC->bEnableCooking; + bUploadTransformsToHoudiniEngine = Version1CompatibilityHAC->bUploadTransformsToHoudiniEngine; + bCookOnTransformChange = Version1CompatibilityHAC->bTransformChangeTriggersCooks; + bCookOnParameterChange = true; + //Version1CompatibilityHAC->bCookingTriggersDownstreamCooks; + bCookOnAssetInputCook = true; + bOutputless = false; + bOutputTemplateGeos = false; + bFullyLoaded = Version1CompatibilityHAC->bFullyLoaded; + + //bContainsHoudiniLogoGeometry = Version1CompatibilityHAC->bContainsHoudiniLogoGeometry; + //bIsNativeComponent = Version1CompatibilityHAC->bIsNativeComponent; + //bIsPreviewComponent = Version1CompatibilityHAC->bIsPreviewComponent; + //bLoadedComponent = Version1CompatibilityHAC->bLoadedComponent; + //bIsPlayModeActive_Unused = Version1CompatibilityHAC->bIsPlayModeActive_Unused; + //Version1CompatibilityHAC->bTimeCookInPlaymode_Unused; + //Version1CompatibilityHAC->bUseHoudiniMaterials; + + //Version1CompatibilityHAC->GeneratedGeometryScaleFactor; + //Version1CompatibilityHAC->TransformScaleFactor; + //Version1CompatibilityHAC->PresetBuffer; + //Version1CompatibilityHAC->DefaultPresetBuffer; + //Version1CompatibilityHAC->ParameterByName; + + // Now that we're done, update all the output's types + for (auto& CurOutput : Outputs) + { + CurOutput->UpdateOutputType(); + } + + // + // Clean up the legacy HAC + // + + Version1CompatibilityHAC->Parameters.Empty(); + Version1CompatibilityHAC->Inputs.Empty(); + Version1CompatibilityHAC->StaticMeshes.Empty(); + Version1CompatibilityHAC->LandscapeComponents.Empty(); + Version1CompatibilityHAC->InstanceInputs.Empty(); + Version1CompatibilityHAC->SplineComponents.Empty(); + Version1CompatibilityHAC->HandleComponents.Empty(); + //Version1CompatibilityHAC->HoudiniAssetComponentMaterials.Empty(); + Version1CompatibilityHAC->BakeNameOverrides.Empty(); + Version1CompatibilityHAC->DownstreamAssetConnections.Empty(); + Version1CompatibilityHAC->MarkPendingKill(); + Version1CompatibilityHAC = nullptr; + + return true; +} + + +UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + HoudiniAsset = nullptr; + bCookOnParameterChange = true; + bUploadTransformsToHoudiniEngine = true; + bCookOnTransformChange = false; + //bUseNativeHoudiniMaterials = true; + bCookOnAssetInputCook = true; + + AssetId = -1; + AssetState = EHoudiniAssetState::PreInstantiation; + AssetStateResult = EHoudiniAssetStateResult::None; + AssetCookCount = 0; + + SubAssetIndex = -1; + + // Make an invalid GUID, since we do not have any cooking requests. + HapiGUID.Invalidate(); + + // Create unique component GUID. + ComponentGUID = FGuid::NewGuid(); + + bUploadTransformsToHoudiniEngine = true; + + bHasBeenLoaded = false; + bHasBeenDuplicated = false; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = false; + bEnableCooking = true; + bForceNeedUpdate = false; + bLastCookSuccess = false; + bBlueprintStructureModified = false; + bBlueprintModified = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // Folder used for cooking, the value is initialized by Output Translator + // TemporaryCookFolder.Path = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + + // Folder used for baking this asset's outputs, the value is initialized by Output Translator + // BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + bHasComponentTransformChanged = false; + + bFullyLoaded = false; + + bOutputless = false; + + bOutputTemplateGeos = false; + + PDGAssetLink = nullptr; + + StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bEnableProxyStaticMeshOverride = HoudiniRuntimeSettings->bEnableProxyStaticMesh; + bEnableProxyStaticMeshRefinementByTimerOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; + ProxyMeshAutoRefineTimeoutSecondsOverride = HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; + } + + bNoProxyMeshNextCookRequested = false; + bBakeAfterNextCook = false; + +#if WITH_EDITORONLY_DATA + bGenerateMenuExpanded = true; + bBakeMenuExpanded = true; + bAssetOptionMenuExpanded = true; + bHelpAndDebugMenuExpanded = true; + + HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; + + bRemoveOutputAfterBake = false; + bRecenterBakedActors = false; + bReplacePreviousBake = false; +#endif + + // + // Set component properties. + // + + Mobility = EComponentMobility::Static; + + SetGenerateOverlapEvents(false); + + // Similar to UMeshComponent. + CastShadow = true; + bUseAsOccluder = true; + bCanEverAffectNavigation = true; + + // This component requires render update. + bNeverNeedsRenderUpdate = false; + + Bounds = FBox(ForceInitToZero); +} + +UHoudiniAssetComponent::~UHoudiniAssetComponent() +{ + // Unregister ourself so our houdini node can be delete. + + // This gets called in UnRegisterHoudiniComponent, with appropriate checks. Don't call it here. + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); +} + +void UHoudiniAssetComponent::PostInitProperties() +{ + Super::PostInitProperties(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + // Copy default static mesh generation parameters from settings. + StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; + StaticMeshGenerationProperties.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; + StaticMeshGenerationProperties.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; + StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; + StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; + StaticMeshGenerationProperties.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; + StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; + StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; + StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; + StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; + StaticMeshGenerationProperties.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; + StaticMeshGenerationProperties.GeneratedDistanceFieldResolutionScale = HoudiniRuntimeSettings->GeneratedDistanceFieldResolutionScale; + } + + // Register ourself to the HER singleton + RegisterHoudiniComponent(this); +} + +UHoudiniAsset * +UHoudiniAssetComponent::GetHoudiniAsset() const +{ + return HoudiniAsset; +} + +FString +UHoudiniAssetComponent::GetDisplayName() const +{ + return GetOwner() ? GetOwner()->GetName() : GetName(); +} + +void +UHoudiniAssetComponent::GetOutputs(TArray& OutOutputs) const +{ + for (UHoudiniOutput* Output : Outputs) + { + OutOutputs.Add(Output); + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh; + } + else + { + return false; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementByTimerEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; + } + else + { + return false; + } + } +} + +float +UHoudiniAssetComponent::GetProxyMeshAutoRefineTimeoutSeconds() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return ProxyMeshAutoRefineTimeoutSecondsOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; + } + else + { + return 5.0f; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; + } + else + { + return false; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; + } + else + { + return false; + } + } +} + +void +UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) +{ + // Check the asset validity + if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill()) + return; + + // If it is the same asset, do nothing. + if ( InHoudiniAsset == HoudiniAsset ) + return; + + HoudiniAsset = InHoudiniAsset; +} + + +void +UHoudiniAssetComponent::OnHoudiniAssetChanged() +{ + // TODO: clear input/params/outputs? + Parameters.Empty(); + + // The asset has been changed, mark us as needing to be reinstantiated + MarkAsNeedInstantiation(); + + // Force an update on the next tick + bForceNeedUpdate = true; +} + +bool +UHoudiniAssetComponent::NeedUpdateParameters() const +{ + // This is being split into a separate function to that it can + // be called separately for component templates. + + if (!bCookOnParameterChange) + return false; + + // Go through all our parameters, return true if they have been updated + for (auto CurrentParm : Parameters) + { + if (!CurrentParm || CurrentParm->IsPendingKill()) + continue; + + if (!CurrentParm->HasChanged()) + continue; + + // See if the parameter doesn't require an update + // (because it has failed to upload previously or has been loaded) + if (!CurrentParm->NeedsToTriggerUpdate()) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateParameters()] Parameters need update for component: %s"), *(GetPathName())); + return true; + } + + return false; +} + +bool +UHoudiniAssetComponent::NeedUpdateInputs() const +{ + // Go through all our inputs, return true if they have been updated + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (!CurrentInput->HasChanged()) + continue; + + // See if the input doesn't require an update + // (because it has failed to upload previously or has been loaded) + if (!CurrentInput->NeedsToTriggerUpdate()) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateInputs()] Inputs need update for component: %s"), *(GetPathName())); + return true; + } + + return false; +} + +bool +UHoudiniAssetComponent::HasPreviousBakeOutput() const +{ + // Look for any bake output objects in the output array + for (const UHoudiniOutput* Output : Outputs) + { + if (!IsValid(Output)) + continue; + + if (BakedOutputs.Num() == 0) + return false; + + for (const FHoudiniBakedOutput& BakedOutput : BakedOutputs) + { + if (BakedOutput.BakedOutputObjects.Num() > 0) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::NeedUpdate() const +{ + if (AssetState != DebugLastAssetState) + { + DebugLastAssetState = AssetState; + } + + // It is important to check this when dealing with Blueprints since the + // preview components start receiving events from the template component + // before the preview component have finished initialization. + if (!IsFullyLoaded()) + return false; + + // We must have a valid asset + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return false; + + if (bForceNeedUpdate) + return true; + + // If we don't want to cook on parameter/input change dont bother looking for updates + if (!bCookOnParameterChange && !bRecookRequested && !bRebuildRequested) + return false; + + // Check if the HAC's transform has changed and transform triggers cook is enabled + if (bCookOnTransformChange && bHasComponentTransformChanged) + return true; + + if (NeedUpdateParameters()) + return true; + + if (NeedUpdateInputs()) + return true; + + // Go through all outputs, filter the editable nodes. Return true if they have been updated. + for (auto CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + // We only care about editable outputs + if (!CurrentOutput->IsEditableNode()) + continue; + + // Trigger an update if the output object is marked as modified by user. + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + for (auto& NextPair : OutputObjects) + { + // For now, only editable curves can trigger update + UHoudiniSplineComponent* HoudiniSplineComponent = Cast(NextPair.Value.OutputComponent); + if (!HoudiniSplineComponent) + continue; + + // Output curves cant trigger an update! + if (HoudiniSplineComponent->bIsOutputCurve) + continue; + + if (HoudiniSplineComponent->NeedsToTriggerUpdate()) + return true; + } + } + + return false; +} + +// Indicates if any of the HAC's output components needs to be updated (no recook needed) +bool +UHoudiniAssetComponent::NeedOutputUpdate() const +{ + // Go through all outputs + for (auto CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs()) + { + if (InstOutput.Value.bChanged) + return true; + } + } + + return false; +} + +bool UHoudiniAssetComponent::NeedBlueprintStructureUpdate() const +{ + // TODO: Add similar flags to inputs, parametsr + return bBlueprintStructureModified; +} + +bool UHoudiniAssetComponent::NeedBlueprintUpdate() const +{ + // TODO: Add similar flags to inputs, parametsr + return bBlueprintModified; +} + +bool +UHoudiniAssetComponent::NotifyCookedToDownstreamAssets() +{ + // Before notifying, clean up our downstream assets + // - check that they are still valid + // - check that we are still connected to one of its asset input + // - check that the asset as the CookOnAssetInputCook trigger enabled + TArray DownstreamToDelete; + for(auto& CurrentDownstreamHAC : DownstreamHoudiniAssets) + { + // Remove the downstream connection by default, + // unless we actually were properly connected to one of this HDa's input. + bool bRemoveDownstream = true; + if (CurrentDownstreamHAC && !CurrentDownstreamHAC->IsPendingKill()) + { + // Go through the HAC's input + for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs) + { + if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill()) + continue; + + EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType(); + if (CurrentDownstreamInputType != EHoudiniInputType::Asset + && CurrentDownstreamInputType != EHoudiniInputType::World) + continue; + + if (!CurrentDownstreamInput->ContainsInputObject(this, CurrentDownstreamInputType)) + continue; + + if (CurrentDownstreamHAC->bCookOnAssetInputCook) + { + // Mark that HAC's input has changed + CurrentDownstreamInput->MarkChanged(true); + } + bRemoveDownstream = false; + } + } + + if (bRemoveDownstream) + { + DownstreamToDelete.Add(CurrentDownstreamHAC); + } + } + + for (auto ToDelete : DownstreamToDelete) + { + DownstreamHoudiniAssets.Remove(ToDelete); + } + + return true; +} + +bool +UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() +{ + for (auto& CurrentInput : Inputs) + { + EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World) + continue; + + TArray* ObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInputType); + if (!ObjectArray) + continue; + + for (auto& CurrentInputObject : (*ObjectArray)) + { + // Get the input HDA + UHoudiniAssetComponent* InputHAC = CurrentInputObject + ? Cast(CurrentInputObject->GetObject()) + : nullptr; + + if (!InputHAC) + continue; + + // If the input HDA needs to be instantiated, force him to instantiate + // if the input HDA is in any other state than None, we need to wait for him + // to finish whatever it's doing + if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) + { + // Tell the input HAC to instantiate + InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; + + // We need to wait + return true; + } + else if (InputHAC->GetAssetState() != EHoudiniAssetState::None) + { + // We need to wait + return true; + } + } + } + + return false; +} + +void +UHoudiniAssetComponent::BeginDestroy() +{ + if (CanDeleteHoudiniNodes()) + { + } + + // Gets called through UnRegisterHoudiniComponent(). + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + + // Unregister ourself so our houdini node can be deleted + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + Super::BeginDestroy(); +} + +void +UHoudiniAssetComponent::MarkAsNeedCook() +{ + // Force the asset state to NeedCook + //AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = true; + bRebuildRequested = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/trigger update + for (auto CurrentParam : Parameters) + { + if (!IsValid(CurrentParam)) + continue; + + // Do not trigger parameter update for Button/Button strip when recooking + // As we don't want to trigger the buttons + if (CurrentParam->IsA() || CurrentParam->IsA()) + continue; + + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(true); + } + + // We need to mark all our inputs as changed/trigger update + for (auto CurrentInput : Inputs) + { + if (!IsValid(CurrentInput)) + continue; + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(true); + CurrentInput->MarkDataUploadNeeded(true); + } + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +void +UHoudiniAssetComponent::MarkAsNeedRebuild() +{ + // Invalidate the asset ID + //AssetId = -1; + + // Force the asset state to NeedRebuild + AssetState = EHoudiniAssetState::NeedRebuild; + AssetStateResult = EHoudiniAssetStateResult::None; + + // Reset some of the asset's flag + //AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = true; + bFullyLoaded = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/trigger update + for (auto CurrentParam : Parameters) + { + if (!IsValid(CurrentParam)) + continue; + + // Do not trigger parameter update for Button/Button strip when rebuilding + // As we don't want to trigger the buttons + if (CurrentParam->IsA() || CurrentParam->IsA()) + continue; + + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(true); + } + + // We need to mark all our inputs as changed/trigger update + for (auto CurrentInput : Inputs) + { + if (!IsValid(CurrentInput)) + continue; + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(true); + CurrentInput->MarkDataUploadNeeded(true); + } + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +// Marks the asset as needing to be instantiated +void +UHoudiniAssetComponent::MarkAsNeedInstantiation() +{ + // Invalidate the asset ID + AssetId = -1; + + if (Parameters.Num() <= 0 && Inputs.Num() <= 0 && Outputs.Num() <= 0) + { + // The asset has no parameters or inputs. + // This likely indicates it has never cooked/been instantiated. + // Set its state to PreInstantiation to force its instantiation + // so that we can have its parameters/input interface + AssetState = EHoudiniAssetState::PreInstantiation; + } + else + { + // The asset has cooked before since we have a parameter/input interface + // Set its state to need instantiation so that the asset is instantiated + // after being modified + AssetState = EHoudiniAssetState::NeedInstantiation; + } + + AssetStateResult = EHoudiniAssetStateResult::None; + + // Reset some of the asset's flag + AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = false; + bFullyLoaded = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/not triggering update + for (auto CurrentParam : Parameters) + { + if (CurrentParam) + { + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(false); + } + } + + // We need to mark all our inputs as changed/not triggering update + for (auto CurrentInput : Inputs) + { + if (CurrentInput) + { + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(false); + CurrentInput->MarkDataUploadNeeded(true); + } + } + + /*if (!CanInstantiateAsset()) + { + AssetState = EHoudiniAssetState::None; + AssetStateResult = EHoudiniAssetStateResult::None; + }*/ + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +void UHoudiniAssetComponent::MarkAsBlueprintStructureModified() +{ + bBlueprintStructureModified = true; +} + +void UHoudiniAssetComponent::MarkAsBlueprintModified() +{ + bBlueprintModified = true; +} + +void +UHoudiniAssetComponent::PostLoad() +{ + Super::PostLoad(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + bool bAutomaticLegacyHDARebuild = HoudiniRuntimeSettings->bAutomaticLegacyHDARebuild; + + // Legacy serialization: either try to convert or skip depending the setting value + if (bEnableBackwardCompatibility && Version1CompatibilityHAC != nullptr) + { + // If we have deserialized legacy v1 data, attempt to convert it now + ConvertLegacyData(); + + if(bAutomaticLegacyHDARebuild) + MarkAsNeedRebuild(); + else + MarkAsNeedInstantiation(); + } + else + { + // Normal v2 objet, mark as need instantiation + MarkAsNeedInstantiation(); + } + + // Component has been loaded, not duplicated + bHasBeenDuplicated = false; + + // We need to register ourself + RegisterHoudiniComponent(this); + + // Register our PDG Asset link if we have any + +} + +void +UHoudiniAssetComponent::PostEditImport() +{ + Super::PostEditImport(); + + MarkAsNeedInstantiation(); + + // Component has been duplicated, not loaded + // We do need the loaded flag to reapply parameters, inputs + // and properly update some of the output objects + bHasBeenDuplicated = true; + + //RemoveAllAttachedComponents(); + + AssetState = EHoudiniAssetState::PreInstantiation; + AssetStateResult = EHoudiniAssetStateResult::None; + + // TODO? + // REGISTER? +} + +void +UHoudiniAssetComponent::UpdatePostDuplicate() +{ + // TODO: + // - Keep the output objects/components (remove duplicatetransient on the output object uproperties) + // - Duplicate created objects (ie SM) and materials + // - Update the output components to use these instead + // This should remove the need for a cook on duplicate + + // For now, we simply clean some of the HAC's component manually + const TArray Children = GetAttachChildren(); + + for (auto & NextChild : Children) + { + if (!NextChild || NextChild->IsPendingKill()) + continue; + + USceneComponent * ComponentToRemove = nullptr; + if (NextChild->IsA()) + { + ComponentToRemove = NextChild; + } + else if (NextChild->IsA()) + { + ComponentToRemove = NextChild; + } + /* do not destroy attached duplicated editable curves, they are needed to restore editable curves + else if (NextChild->IsA()) + { + // Remove duplicated editable curve output's Houdini Spline Component, since they will be re-built at duplication. + UHoudiniSplineComponent * HoudiniSplineComponent = Cast(NextChild); + if (HoudiniSplineComponent && HoudiniSplineComponent->IsEditableOutputCurve()) + ComponentToRemove = NextChild; + } + */ + if (ComponentToRemove) + { + ComponentToRemove->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + ComponentToRemove->UnregisterComponent(); + ComponentToRemove->DestroyComponent(); + } + } + + // if there is an associated PDG asset link, call its UpdatePostDuplicate to cleanup references to + // to the original instance's PDG output actors + if (IsValid(PDGAssetLink)) + { + PDGAssetLink->UpdatePostDuplicate(); + } + + SetHasBeenDuplicated(false); +} + +bool UHoudiniAssetComponent::IsInputTypeSupported(EHoudiniInputType InType) const +{ + return true; +} + +bool UHoudiniAssetComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const +{ + return true; +} + +bool +UHoudiniAssetComponent::IsPreview() const +{ + return bCachedIsPreview; +} + +bool UHoudiniAssetComponent::IsValidComponent() const +{ + return true; +} + +void UHoudiniAssetComponent::OnFullyLoaded() +{ + bFullyLoaded = true; +} + + +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); + } + */ +} + +void +UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + + if (CanDeleteHoudiniNodes()) + { + } + + // Unregister ourself so our houdini node can be deleted + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + HoudiniAsset = nullptr; + + // Clear Parameters + for (UHoudiniParameter*& CurrentParm : Parameters) + { + if (CurrentParm && !CurrentParm->IsPendingKill()) + { + CurrentParm->ConditionalBeginDestroy(); + } + else if (GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE) + { + // TODO unneeded log? + // Avoid spamming that error when leaving PIE mode + HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } + + CurrentParm = nullptr; + } + + Parameters.Empty(); + + // Clear Inputs + for (UHoudiniInput*& CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + // Destroy connected Houdini asset. + CurrentInput->ConditionalBeginDestroy(); + CurrentInput = nullptr; + } + + Inputs.Empty(); + + // Clear Output + for (UHoudiniOutput*& CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + // Destroy all Houdini created socket actors. + TArray & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors(); + for (auto & CurCreatedActor : CurCreatedSocketActors) + { + if (!CurCreatedActor || CurCreatedActor->IsPendingKill()) + continue; + + CurCreatedActor->Destroy(); + } + CurCreatedSocketActors.Empty(); + + // Detach all Houdini attached socket actors + TArray & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors(); + for (auto & CurAttachedSocketActor : CurAttachedSocketActors) + { + if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill()) + continue; + + CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + } + CurAttachedSocketActors.Empty(); + +#if WITH_EDITOR + // Clean up foliages instances + for (auto& CurrentOutputObject : CurrentOutput->GetOutputObjects()) + { + // Foliage instancers store a HISMC in the components + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentOutputObject.Value.OutputComponent); + if (!FoliageHISMC) + continue; + + UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + continue; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(FoliageHISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + continue; + + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + if (!FoliageType || FoliageType->IsPendingKill()) + continue; + + if (IsInGameThread() && IsGarbageCollecting()) + { + // TODO: ?? + // Calling DeleteInstancesForComponent during GC will cause unreal to crash... + HOUDINI_LOG_WARNING(TEXT("%s: Unable to clear foliage instances because of GC"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } + else + { + // Clean up the instances generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(this, FoliageType); + } + + if (FoliageHISMC->GetInstanceCount() > 0) + { + // If the component still has instances left after the cleanup, + // make sure that we dont delete it, as the leftover instances are likely hand-placed + CurrentOutputObject.Value.OutputComponent = nullptr; + } + else + { + // Remove the foliage type if it doesn't have any more instances + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + } + } +#endif + + CurrentOutput->Clear(); + // Destroy connected Houdini asset. + CurrentOutput->ConditionalBeginDestroy(); + CurrentOutput = nullptr; + } + + Outputs.Empty(); + + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + // Unregister ourself so our houdini node can be delete. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); + + + // Clear all TOP data and temporary geo/objects from the PDG asset link (if valid) + if (IsValid(PDGAssetLink)) + { +#if WITH_EDITOR + const UWorld* const World = GetWorld(); + if (IsValid(World)) + { + // Only do this for editor worlds, only interactively (not during engine shutdown or garbage collection) + if (World->WorldType == EWorldType::Editor && GIsRunning && !GIsGarbageCollecting) + { + // In case we are recording a transaction (undo, for example) notify that the object will be + // modified. + PDGAssetLink->Modify(); + PDGAssetLink->ClearAllTOPData(); + } + } +#endif + } + + Super::OnComponentDestroyed(bDestroyingHierarchy); +} + +void UHoudiniAssetComponent::RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) +{ + // Registration of this component is wrapped in this virtual function to allow + // derived classed to override this behaviour. + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(InComponent); +} + +void +UHoudiniAssetComponent::OnRegister() +{ + Super::OnRegister(); + + // NOTE: Wait until HoudiniEngineTick() before deciding to mark this object as fully loaded + // since preview components need to wait for component templates to finish their initialization + // before being able to perform state transfers. + + /* + // 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(); + } + } + */ + + // Let TickInitialization() take care of manipulating the bFullyLoaded state. + //bFullyLoaded = true; + + //// If we're constructing editable components in the SCS editor, set the component instance corresponding to this node for editing purposes + // + //USimpleConstructionScript* SCS = GetSCS(); + //if (SCS == nullptr) + //{ + // bFullyLoaded = true; + //} + //else + //{ + // if(SCS->IsConstructingEditorComponents()) + // { + // // We're not fully loaded yet. We're expecting + // } + // else + // { + // bFullyLoaded = true; + // } + //} + +} + +UHoudiniParameter* +UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) +{ + if (!InOtherParam || InOtherParam->IsPendingKill()) + return nullptr; + + for (auto CurrentParam : Parameters) + { + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + if (CurrentParam->Matches(*InOtherParam)) + return CurrentParam; + } + + return nullptr; +} + +UHoudiniInput* +UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) +{ + if (!InOtherInput || InOtherInput->IsPendingKill()) + return nullptr; + + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->Matches(*InOtherInput)) + return CurrentInput; + } + + return nullptr; +} + +UHoudiniHandleComponent* +UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle) +{ + if (!InOtherHandle || InOtherHandle->IsPendingKill()) + return nullptr; + + for (auto CurrentHandle : HandleComponents) + { + if (!CurrentHandle || CurrentHandle->IsPendingKill()) + continue; + + if (CurrentHandle->Matches(*InOtherHandle)) + return CurrentHandle; + } + + return nullptr; +} + +UHoudiniParameter* +UHoudiniAssetComponent::FindParameterByName(const FString& InParamName) +{ + for (auto CurrentParam : Parameters) + { + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + if (CurrentParam->GetParameterName().Equals(InParamName)) + return CurrentParam; + } + + return nullptr; +} + + +void +UHoudiniAssetComponent::OnChildAttached(USceneComponent* ChildComponent) +{ + Super::OnChildAttached(ChildComponent); + + // ... Do corresponding things for other houdini component types. + // ... +} + + +void +UHoudiniAssetComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + + SetHasComponentTransformChanged(true); +} + +void UHoudiniAssetComponent::HoudiniEngineTick() +{ + if (!IsFullyLoaded()) + { + OnFullyLoaded(); + } +} + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + if (!Property) + return; + + FName PropertyName = Property->GetFName(); + + // Changing the Houdini Asset? + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, HoudiniAsset)) + { + OnHoudiniAssetChanged(); + } + else if (PropertyName == GetRelativeLocationPropertyName() + || PropertyName == GetRelativeRotationPropertyName() + || PropertyName == GetRelativeScale3DPropertyName()) + { + SetHasComponentTransformChanged(true); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bOverrideGlobalProxyStaticMeshSettings) + || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bEnableProxyStaticMeshRefinementByTimerOverride) + || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, ProxyMeshAutoRefineTimeoutSecondsOverride)) + { + ClearRefineMeshesTimer(); + // Reset the timer + // SetRefineMeshesTimer will check the relevant settings and only set the timer if enabled via settings + SetRefineMeshesTimer(); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, 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); + } + } + else if (PropertyName == TEXT("bVisible")) + { + // Visibility has changed, propagate it to children. + SetVisibility(IsVisible(), true); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bHiddenInGame)) + { + // Visibility has changed, propagate it to children. + SetHiddenInGame(bHiddenInGame, true); + } + else + { + // TODO: + // Propagate properties (mobility/visibility etc.. to children components) + // Look in v1 for: if (Property->HasMetaData(TEXT("Category"))) {} and HOUDINI_UPDATE_ALL_CHILD_COMPONENTS + } + + 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 (UHoudiniOutput* CurOutput : Outputs) + { + if (!CurOutput) + continue; + + for (auto& Pair : CurOutput->GetOutputObjects()) + { + UStaticMesh* StaticMesh = Cast(Pair.Value.OutputObject); + if (!StaticMesh || StaticMesh->IsPendingKill()) + continue; + + SetStaticMeshGenerationProperties(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 ReregisterComponents; + { + TArray 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); + } + } + } +} +#endif + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::PostEditUndo() +{ + Super::PostEditUndo(); + + if (!IsPendingKill()) + { + // Make sure we are registered with the HER singleton + // We could be undoing a HoudiniActor delete + if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(this)) + { + MarkAsNeedInstantiation(); + + // Component has been loaded, not duplicated + bHasBeenDuplicated = false; + + RegisterHoudiniComponent(this); + } + } +} + +#endif + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::OnActorMoved(AActor* Actor) +{ + if (GetOwner() != Actor) + return; + + SetHasComponentTransformChanged(true); +} +#endif + +void +UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged) +{ + // Only update the value if we're fully loaded + // This avoid triggering a recook when loading a level + if(bFullyLoaded) + bHasComponentTransformChanged = InHasChanged; +} + +void +UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // Check the object validity + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // If it is the same object, do nothing. + if (InPDGAssetLink == PDGAssetLink) + return; + + PDGAssetLink = InPDGAssetLink; +} + + +FBoxSphereBounds +UHoudiniAssetComponent::CalcBounds(const FTransform & LocalToWorld) const +{ + FBoxSphereBounds LocalBounds; + FBox BoundingBox = GetAssetBounds(nullptr, false); + 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; +} + + +FBox +UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const +{ + FBox BoxBounds(ForceInitToZero); + + // Query the bounds for all output objects + for (auto & CurOutput : Outputs) + { + if (!CurOutput || CurOutput->IsPendingKill()) + continue; + + BoxBounds += CurOutput->GetBounds(); + } + + // Query the bounds for all our inputs + for (auto & CurInput : Inputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + BoxBounds += CurInput->GetBounds(); + } + + // Query the bounds for all input parameters + for (auto & CurParam : Parameters) + { + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (CurParam->GetParameterType() != EHoudiniParameterType::Input) + continue; + + UHoudiniParameterOperatorPath* InputParam = Cast(CurParam); + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (!InputParam->HoudiniInput.IsValid()) + continue; + + BoxBounds += InputParam->HoudiniInput.Get()->GetBounds(); + } + + // Query the bounds for all our Houdini handles + for (auto & CurHandleComp : HandleComponents) + { + if (!CurHandleComp || CurHandleComp->IsPendingKill()) + continue; + + BoxBounds += CurHandleComp->GetBounds(); + } + + // Also scan all our decendants for SMC bounds not just top-level children + // ( split mesh instances' mesh bounds were not gathered proiperly ) + TArray 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(pChild)) + { + if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + continue; + + FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); + if (StaticMeshBounds.IsValid) + BoxBounds += StaticMeshBounds; + } + } + + // If nothing was found, init with the asset's location + if (BoxBounds.GetVolume() == 0.0f) + BoxBounds += GetComponentLocation(); + + return BoxBounds; +} + +void +UHoudiniAssetComponent::ClearRefineMeshesTimer() +{ + UWorld *World = GetWorld(); + if (!World) + { + //HOUDINI_LOG_ERROR(TEXT("Cannot ClearRefineMeshesTimer, World is nullptr!")); + return; + } + + World->GetTimerManager().ClearTimer(RefineMeshesTimer); +} + +void +UHoudiniAssetComponent::SetRefineMeshesTimer() +{ + UWorld *World = GetWorld(); + if (!World) + { + HOUDINI_LOG_ERROR(TEXT("Cannot SetRefineMeshesTimer, World is nullptr!")); + return; + } + + // Check if timer-based proxy mesh refinement is enable for this component + const bool bEnableTimer = IsProxyStaticMeshRefinementByTimerEnabled(); + const float TimeSeconds = GetProxyMeshAutoRefineTimeoutSeconds(); + if (bEnableTimer) + { + World->GetTimerManager().SetTimer(RefineMeshesTimer, this, &UHoudiniAssetComponent::OnRefineMeshesTimerFired, 1.0f, false, TimeSeconds); + } + else + { + World->GetTimerManager().ClearTimer(RefineMeshesTimer); + } +} + +void +UHoudiniAssetComponent::OnRefineMeshesTimerFired() +{ + HOUDINI_LOG_MESSAGE(TEXT("UHoudiniAssetComponent::OnRefineMeshesTimerFired()")); + if (OnRefineMeshesTimerDelegate.IsBound()) + { + OnRefineMeshesTimerDelegate.Broadcast(this); + } +} + +bool +UHoudiniAssetComponent::HasAnyCurrentProxyOutput() const +{ + for (const UHoudiniOutput *Output : Outputs) + { + if (Output->HasAnyCurrentProxy()) + { + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasAnyProxyOutput() const +{ + for (const UHoudiniOutput *Output : Outputs) + { + if (Output->HasAnyProxy()) + { + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasAnyOutputComponent() const +{ + for (UHoudiniOutput *Output : Outputs) + { + for(auto& CurrentOutputObject : Output->GetOutputObjects()) + { + if(CurrentOutputObject.Value.OutputComponent) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasOutputObject(UObject* InOutputObjectToFind) const +{ + for (const auto& CurOutput : Outputs) + { + for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) + { + if (CurOutputObject.Value.OutputObject == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.OutputComponent == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.ProxyObject == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.ProxyComponent == InOutputObjectToFind) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const +{ + // Get the state of the asset and check if it is pre-cook, cooked, pending delete/rebuild or invalid + bOutNeedsRebuildOrDelete = false; + bOutInvalidState = false; + switch (AssetState) + { + case EHoudiniAssetState::NeedInstantiation: + case EHoudiniAssetState::PreInstantiation: + case EHoudiniAssetState::Instantiating: + case EHoudiniAssetState::PreCook: + case EHoudiniAssetState::Cooking: + case EHoudiniAssetState::PostCook: + case EHoudiniAssetState::PreProcess: + case EHoudiniAssetState::Processing: + return false; + break; + case EHoudiniAssetState::None: + return true; + break; + case EHoudiniAssetState::NeedRebuild: + case EHoudiniAssetState::NeedDelete: + case EHoudiniAssetState::Deleting: + bOutNeedsRebuildOrDelete = true; + break; + default: + bOutInvalidState = true; + break; + } + + return false; +} + +void +UHoudiniAssetComponent::SetInputPresets(const TMap& InPresets) +{ + // Set the input preset for this HAC +#if WITH_EDITOR + InputPresets = InPresets; +#endif +} + + +void +UHoudiniAssetComponent::ApplyInputPresets() +{ + if (InputPresets.Num() <= 0) + return; + +#if WITH_EDITOR + // Ignore inputs that have been preset to curve + TArray InputArray; + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetInputType() != EHoudiniInputType::Curve) + InputArray.Add(CurrentInput); + } + + // Try to apply the supplied Object to the Input + for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset) + { + UObject * Object = IterToolPreset.Key(); + if (!Object || Object->IsPendingKill()) + continue; + + int32 InputNumber = IterToolPreset.Value(); + if (!InputArray.IsValidIndex(InputNumber)) + continue; + + // If the object is a landscape, add a new landscape input + if (Object->IsA()) + { + // selecting a landscape + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Landscape); + if (InsertNum == 0) + { + // Landscape inputs only support one object! + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Landscape, InsertNum, Object); + } + } + + // If the object is an actor, add a new world input + if (Object->IsA()) + { + // selecting an actor + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::World); + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::World, InsertNum, Object); + } + + // If the object is a static mesh, add a new geometry input (TODO: or BP ? ) + if (Object->IsA()) + { + // selecting a Staticn Mesh + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Geometry, InsertNum, Object); + } + + if (Object->IsA()) + { + // selecting a Houdini Asset + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Asset); + if (InsertNum == 0) + { + // Assert inputs only support one object! + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Asset, InsertNum, Object); + } + } + } + + // The input objects have been set, now change the input type + bool bBPStructureModified = false; + for (auto CurrentInput : Inputs) + { + int32 NumGeo = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + int32 NumAsset = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Asset); + int32 NumWorld = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::World); + int32 NumLandscape = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Landscape); + + EHoudiniInputType NewInputType = EHoudiniInputType::Invalid; + if (NumLandscape > 0 && NumLandscape >= NumGeo && NumLandscape >= NumAsset && NumLandscape >= NumWorld) + NewInputType = EHoudiniInputType::Landscape; + else if (NumWorld > 0 && NumWorld >= NumGeo && NumWorld >= NumAsset && NumWorld >= NumLandscape) + NewInputType = EHoudiniInputType::World; + else if (NumAsset > 0 && NumAsset >= NumGeo && NumAsset >= NumWorld && NumAsset >= NumLandscape) + NewInputType = EHoudiniInputType::Asset; + else if (NumGeo > 0 && NumGeo >= NumAsset && NumGeo >= NumWorld && NumGeo >= NumLandscape) + NewInputType = EHoudiniInputType::Geometry; + + if (NewInputType == EHoudiniInputType::Invalid) + continue; + + // Change the input type, unless if it was preset to a different type and we have object for the preset type + if (CurrentInput->GetInputType() == EHoudiniInputType::Geometry && NewInputType != EHoudiniInputType::Geometry) + { + CurrentInput->SetInputType(NewInputType, bBPStructureModified); + } + else + { + // Input type was preset, only change if that type is empty + if(CurrentInput->GetNumberOfInputObjects() <= 0) + CurrentInput->SetInputType(NewInputType, bBPStructureModified); + } + } + if (bBPStructureModified) + { + MarkAsBlueprintStructureModified(); + } +#endif + + // Discard the tool presets after their first setup + InputPresets.Empty(); +} + + +bool +UHoudiniAssetComponent::IsComponentValid() const +{ + if (!IsValidLowLevel()) + return false; + + if (IsTemplate()) + return false; + + if (IsPendingKillOrUnreachable()) + return false; + + if (!GetOuter()) //|| !GetOuter()->GetLevel() ) + return false; + + return true; +} + +bool +UHoudiniAssetComponent::IsInstantiatingOrCooking() const +{ + return HapiGUID.IsValid(); +} + + +void +UHoudiniAssetComponent::SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const +{ +#if WITH_EDITOR + if (!InStaticMesh) + return; + + // Make sure static mesh has a new lighting guid. + InStaticMesh->LightingGuid = FGuid::NewGuid(); + InStaticMesh->LODGroup = NAME_None; + + // Set resolution of lightmap. + InStaticMesh->LightMapResolution = StaticMeshGenerationProperties.GeneratedLightMapResolution; + + // Set Bias multiplier for Light Propagation Volume lighting. + InStaticMesh->LpvBiasMultiplier = StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier; + + // Set the global light map coordinate index if it looks valid + if (InStaticMesh->RenderData.IsValid() && InStaticMesh->RenderData->LODResources.Num() > 0) + { + int32 NumUVs = InStaticMesh->RenderData->LODResources[0].GetNumTexCoords(); + if (NumUVs > StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex) + { + InStaticMesh->LightMapCoordinateIndex = StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex; + } + } + + // Set method for LOD texture factor computation. + /* TODO_414 + //InStaticMesh->bUseMaximumStreamingTexelRatio = StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; + + // Set distance where textures using UV 0 are streamed in/out. - GOES ON COMPONENT + // InStaticMesh->StreamingDistanceMultiplier = StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; + */ + + // Add user data. + for (int32 AssetUserDataIdx = 0; AssetUserDataIdx < StaticMeshGenerationProperties.GeneratedAssetUserData.Num(); AssetUserDataIdx++) + InStaticMesh->AddAssetUserData(StaticMeshGenerationProperties.GeneratedAssetUserData[AssetUserDataIdx]); + + // + if (!InStaticMesh->BodySetup) + InStaticMesh->CreateBodySetup(); + + UBodySetup* BodySetup = InStaticMesh->BodySetup; + if (!InStaticMesh->BodySetup) + return; + + // Set flag whether physics triangle mesh will use double sided faces when doing scene queries. + BodySetup->bDoubleSidedGeometry = StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry; + + // Assign physical material for simple collision. + BodySetup->PhysMaterial = StaticMeshGenerationProperties.GeneratedPhysMaterial; + + BodySetup->DefaultInstance.CopyBodyInstancePropertiesFrom(&StaticMeshGenerationProperties.DefaultBodyInstance); + + // Assign collision trace behavior. + BodySetup->CollisionTraceFlag = StaticMeshGenerationProperties.GeneratedCollisionTraceFlag; + + // Assign walkable slope behavior. + BodySetup->WalkableSlopeOverride = StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride; + BodySetup->WalkableSlopeOverride = StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride; + + // We want to use all of geometry for collision detection purposes. + BodySetup->bMeshCollideAll = true; + +#endif } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h index 11aa53915..1fb2164e2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h @@ -1,806 +1,734 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniOutput.h" -#include "HoudiniInputTypes.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Components/PrimitiveComponent.h" -#include "Components/SceneComponent.h" - -#include "HoudiniAssetComponent.generated.h" - -class UHoudiniAsset; -class UHoudiniParameter; -class UHoudiniInput; -class UHoudiniOutput; -class UHoudiniHandleComponent; -class UHoudiniPDGAssetLink; -class UHoudiniAssetComponent_V1; - -UENUM() -enum class EHoudiniAssetState : uint8 -{ - // Loaded / Duplicated HDA, - // Will need to be instantiated upon change/update - NeedInstantiation, - - // Newly created HDA, needs to be instantiated immediately - PreInstantiation, - - // Instantiating task in progress - Instantiating, - - // Instantiated HDA, needs to be cooked immediately - PreCook, - - // Cooking task in progress - Cooking, - - // Cooking has finished - PostCook, - - // Cooked HDA, needs to be processed immediately - PreProcess, - - // Processing task in progress - Processing, - - // Processed / Updated HDA - // Will need to be cooked upon change/update - None, - - // Asset needs to be rebuilt (Deleted/Instantiated/Cooked) - NeedRebuild, - - // Asset needs to be deleted - NeedDelete, - - // Deleting - Deleting, - - // Process component template. This is ticking has very limited - // functionality, typically limited to checking for parameter updates - // in order to trigger PostEditChange() to run construction scripts again. - ProcessTemplate, -}; - -UENUM() -enum class EHoudiniAssetStateResult : uint8 -{ - None, - Working, - Success, - FinishedWithError, - FinishedWithFatalError, - Aborted -}; - -UENUM() -enum class EHoudiniStaticMeshMethod : uint8 -{ - // Use the RawMesh method to build the UStaticMesh - RawMesh, - // Use the FMeshDescription method to build the UStaticMesh - FMeshDescription, - // Build a fast proxy mesh: UHoudiniStaticMesh - UHoudiniStaticMesh, -}; - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EHoudiniEngineBakeOption : uint8 -{ - ToActor, - ToBlueprint, - ToFoliage, - ToWorldOutliner, -}; -#endif - -class UHoudiniAssetComponent; - -DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetEvent, UHoudiniAsset*); -DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetComponentEvent, UHoudiniAssetComponent*) - -UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility), editinlinenew) -class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent -{ - GENERATED_UCLASS_BODY() - - // Declare translators as friend so they can easily directly modify - // Inputs, outputs and parameters - friend class FHoudiniEngineManager; - friend struct FHoudiniOutputTranslator; - friend struct FHoudiniInputTranslator; - friend struct FHoudiniSplineTranslator; - friend struct FHoudiniParameterTranslator; - friend struct FHoudiniPDGManager; - friend struct FHoudiniHandleTranslator; - -#if WITH_EDITORONLY_DATA - friend class FHoudiniAssetComponentDetails; -#endif - -public: - - // Declare the delegate that is broadcast when RefineMeshesTimer fires - DECLARE_MULTICAST_DELEGATE_OneParam(FOnRefineMeshesTimerDelegate, UHoudiniAssetComponent*); - DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPostCookBakeDelegate, UHoudiniAssetComponent*); - - virtual ~UHoudiniAssetComponent(); - - virtual void Serialize(FArchive & Ar) override; - - virtual bool ConvertLegacyData(); - - // Called after the C++ constructor and after the properties have been initialized, including those loaded from config. - // This is called before any serialization or other setup has happened. - virtual void PostInitProperties() override; - - // Returns the Owner actor / HAC name - FString GetDisplayName() const; - - // Indicates if the HAC needs to be updated - bool NeedUpdate() const; - - // Indicates if the HAC's transform needs to be updated - bool NeedTransformUpdate() const { return (bHasComponentTransformChanged && bUploadTransformsToHoudiniEngine); }; - - // Indicates if any of the HAC's output components needs to be updated (no recook needed) - bool NeedOutputUpdate() const; - - // Check whether any inputs / outputs / parameters have made blueprint modifications. - bool NeedBlueprintStructureUpdate() const; - bool NeedBlueprintUpdate() const; - - // Try to find one of our parameter that matches another (name, type, size and enabled) - UHoudiniParameter* FindMatchingParameter(UHoudiniParameter* InOtherParam); - - // Try to find one of our input that matches another one (name, isobjpath, index / parmId) - UHoudiniInput* FindMatchingInput(UHoudiniInput* InOtherInput); - - // Try to find one of our handle that matches another one (name and handle type) - UHoudiniHandleComponent* FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle); - - // Finds a parameter by name - UHoudiniParameter* FindParameterByName(const FString& InParamName); - - // Returns True if the component has at least one mesh output of class U - template - bool HasMeshOutputObjectOfClass() const; - - // Returns True if the component has at least one mesh output with a current proxy - bool HasAnyCurrentProxyOutput() const; - - // Returns True if the component has at least one proxy mesh output (not necessarily current/displayed) - bool HasAnyProxyOutput() const; - - // Returns True if the component has at least one non-proxy output component amongst its outputs - bool HasAnyOutputComponent() const; - - // Returns true if the component has InOutputObjectToFind in its output object - bool HasOutputObject(UObject* InOutputObjectToFind) const; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - UHoudiniAsset * GetHoudiniAsset() const; - int32 GetAssetId() const { return AssetId; }; - EHoudiniAssetState GetAssetState() const { return AssetState; }; - FString GetAssetStateAsString() const { return FHoudiniEngineRuntimeUtils::EnumToString(TEXT("EHoudiniAssetState"), GetAssetState()); }; - EHoudiniAssetStateResult GetAssetStateResult() const { return AssetStateResult; }; - FGuid GetHapiGUID() const { return HapiGUID; }; - FGuid GetComponentGUID() const { return ComponentGUID; }; - - int32 GetNumInputs() const { return Inputs.Num(); }; - int32 GetNumOutputs() const { return Outputs.Num(); }; - int32 GetNumParameters() const { return Parameters.Num(); }; - int32 GetNumHandles() const { return HandleComponents.Num(); }; - - UHoudiniInput* GetInputAt(const int32& Idx) { return Inputs.IsValidIndex(Idx) ? Inputs[Idx] : nullptr; }; - UHoudiniOutput* GetOutputAt(const int32& Idx) { return Outputs.IsValidIndex(Idx) ? Outputs[Idx] : nullptr;}; - UHoudiniParameter* GetParameterAt(const int32& Idx) { return Parameters.IsValidIndex(Idx) ? Parameters[Idx] : nullptr;}; - UHoudiniHandleComponent* GetHandleComponentAt(const int32& Idx) { return HandleComponents.IsValidIndex(Idx) ? HandleComponents[Idx] : nullptr; }; - - void GetOutputs(TArray& OutOutputs) const; - - TArray& GetBakedOutputs() { return BakedOutputs; } - const TArray& GetBakedOutputs() const { return BakedOutputs; } - - /* - TArray& GetParameters() { return Parameters; }; - TArray& GetInputs() { return Inputs; }; - TArray& GetOutputs() { return Outputs; }; - */ - - bool IsCookingEnabled() const { return bEnableCooking; }; - bool HasBeenLoaded() const { return bHasBeenLoaded; }; - bool HasBeenDuplicated() const { return bHasBeenDuplicated; }; - bool HasRecookBeenRequested() const { return bRecookRequested; }; - bool HasRebuildBeenRequested() const { return bRebuildRequested; }; - - //bool GetEditorPropertiesNeedFullUpdate() const { return bEditorPropertiesNeedFullUpdate; }; - - int32 GetAssetCookCount() const { return AssetCookCount; }; - - bool IsFullyLoaded() const { return bFullyLoaded; }; - - UHoudiniPDGAssetLink * GetPDGAssetLink() const { return PDGAssetLink; }; - - virtual bool IsProxyStaticMeshEnabled() const; - bool IsProxyStaticMeshRefinementByTimerEnabled() const; - float GetProxyMeshAutoRefineTimeoutSeconds() const; - bool IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const; - bool IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const; - // If true, then the next cook should not build proxy meshes, regardless of global or override settings, - // but should instead directly build UStaticMesh - bool HasNoProxyMeshNextCookBeenRequested() const { return bNoProxyMeshNextCookRequested; } - // Returns true if the asset state indicates that it has been cooked in this session, false otherwise. - bool IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const; - // Returns true if the asset should be bake after the next cook - bool IsBakeAfterNextCookEnabled() const { return bBakeAfterNextCook; } - - FOnPostCookBakeDelegate& GetOnPostCookBakeDelegate() { return OnPostCookBakeDelegate; } - - // Derived blueprint based components will check whether the template - // component contains updates that needs to processed. - bool NeedUpdateParameters() const; - bool NeedUpdateInputs() const; - - // Returns true if the component has any previous baked output recorded in its outputs - bool HasPreviousBakeOutput() const; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - //void SetAssetId(const int& InAssetId); - //void SetAssetState(const EHoudiniAssetState& InAssetState) { AssetState = InAssetState; }; - //void SetAssetStateResult(const EHoudiniAssetStateResult& InAssetStateResult) { AssetStateResult = InAssetStateResult; }; - - //void SetHapiGUID(const FGuid& InGUID) { HapiGUID = InGUID; }; - //void SetComponentGUID(const FGuid& InGUID) { ComponentGUID = InGUID; }; - - //UFUNCTION(BlueprintSetter) - virtual void SetHoudiniAsset(UHoudiniAsset * NewHoudiniAsset); - - void SetHasBeenLoaded(const bool& InLoaded) { bHasBeenLoaded = InLoaded; }; - - void SetHasBeenDuplicated(const bool& InDuplicated) { bHasBeenDuplicated = InDuplicated; }; - - //void SetEditorPropertiesNeedFullUpdate(const bool& InUpdate) { bEditorPropertiesNeedFullUpdate = InUpdate; }; - - // Marks the assets as needing a recook - void MarkAsNeedCook(); - // Marks the assets as needing a full rebuild - void MarkAsNeedRebuild(); - // Marks the asset as needing to be instantiated - void MarkAsNeedInstantiation(); - // The blueprint has been structurally modified - void MarkAsBlueprintStructureModified(); - // The blueprint has been modified but not structurally changed. - void MarkAsBlueprintModified(); - - // - void SetAssetCookCount(const int32& InCount) { AssetCookCount = InCount; }; - // - void SetRecookRequested(const bool& InRecook) { bRecookRequested = InRecook; }; - // - void SetRebuildRequested(const bool& InRebuild) { bRebuildRequested = InRebuild; }; - // - void SetHasComponentTransformChanged(const bool& InHasChanged); - - // Set to True to force the next cook to not build a proxy mesh (regardless of global or override settings) and - // instead build a UStaticMesh directly (if applicable for the output type). - void SetNoProxyMeshNextCookRequested(bool bInNoProxyMeshNextCookRequested) { bNoProxyMeshNextCookRequested = bInNoProxyMeshNextCookRequested; } - - // Set to True to force the next cook to bake the asset after the cook completes. - void SetBakeAfterNextCookEnabled(bool bInEnabled) { bBakeAfterNextCook = bInEnabled; } - - // - void SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink); - // - virtual void OnHoudiniAssetChanged(); - - // - void AddDownstreamHoudiniAsset(UHoudiniAssetComponent* InDownstreamAsset) { DownstreamHoudiniAssets.Add(InDownstreamAsset); }; - // - void RemoveDownstreamHoudiniAsset(UHoudiniAssetComponent* InRemoveDownstreamAsset) { DownstreamHoudiniAssets.Remove(InRemoveDownstreamAsset); }; - // - void ClearDownstreamHoudiniAsset() { DownstreamHoudiniAssets.Empty(); }; - // - bool NotifyCookedToDownstreamAssets(); - // - bool NeedsToWaitForInputHoudiniAssets(); - - // Clear/disable the RefineMeshesTimer. - void ClearRefineMeshesTimer(); - - // Re-set the RefineMeshesTimer to its default value. - void SetRefineMeshesTimer(); - - virtual void OnRefineMeshesTimerFired(); - - // Called by RefineMeshesTimer when the timer is triggered. - // Checks for any UHoudiniStaticMesh in Outputs and bakes UStaticMesh for them via FHoudiniMeshTranslator. - FOnRefineMeshesTimerDelegate& GetOnRefineMeshesTimerDelegate() { return OnRefineMeshesTimerDelegate; } - - // Returns true if the asset is valid for cook/bake - virtual bool IsComponentValid() const; - // Return false if this component has no cooking or instantiation in progress. - bool IsInstantiatingOrCooking() const; - - // HoudiniEngineTick will be called by HoudiniEngineManager::Tick() - virtual void HoudiniEngineTick(); - -#if WITH_EDITOR - // This alternate version of PostEditChange is called when properties inside structs are modified. The property that was actually modified - // is located at the tail of the list. The head of the list of the FStructProperty member variable that contains the property that was modified. - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; - - //Called after applying a transaction to the object. Default implementation simply calls PostEditChange. - virtual void PostEditUndo() override; - - // Whether this component is currently open in a Blueprint editor. This - // method is overridden by HoudiniAssetBlueprintComponent. - virtual bool HasOpenEditor() const { return false; }; - -#endif - - virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent); - - virtual void OnRegister() override; - - // USceneComponent methods. - virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override; - virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; - - FBox GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const; - - // Set this component's input presets - void SetInputPresets(const TMap& InPresets); - // Apply the preset input for HoudiniTools - void ApplyInputPresets(); - - // return the cached component template, if available. - virtual UHoudiniAssetComponent* GetCachedTemplate() const { return nullptr; } - - //------------------------------------------------------------------------------------------------ - // Supported Features - //------------------------------------------------------------------------------------------------ - - // Whether or not this component should be able to delete the Houdini nodes - // that correspond to the HoudiniAsset when being deregistered. - virtual bool CanDeleteHoudiniNodes() const { return true; } - - virtual bool IsInputTypeSupported(EHoudiniInputType InType) const; - virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const; - - //------------------------------------------------------------------------------------------------5 - // Characteristics - //------------------------------------------------------------------------------------------------ - - // Try to determine whether this component belongs to a preview actor. - // Preview / Template components need to sync their data for HDA cooks and output translations. - bool IsPreview() const; - - virtual bool IsValidComponent() const; - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - // TODO: After the cook worfklow rework, most of these won't be needed anymore, so clean up! - //FHoudiniAssetComponentEvent OnTemplateParametersChanged; - //FHoudiniAssetComponentEvent OnPreAssetCook; - //FHoudiniAssetComponentEvent OnCookCompleted; - //FHoudiniAssetComponentEvent OnOutputProcessingCompleted; - - /*virtual void BroadcastParametersChanged(); - virtual void BroadcastPreAssetCook(); - virtual void BroadcastCookCompleted();*/ - - virtual void OnPrePreCook() {}; - virtual void OnPostPreCook() {}; - virtual void OnPreOutputProcessing() {}; - virtual void OnPostOutputProcessing() {}; - virtual void OnPrePreInstantiation() {}; - - - virtual void NotifyHoudiniRegisterCompleted() {}; - virtual void NotifyHoudiniPreUnregister() {}; - virtual void NotifyHoudiniPostUnregister() {}; - - virtual void OnFullyLoaded(); - - // Component template parameters have been updated. - // Broadcast delegate, and let preview components take care of the rest. - virtual void OnTemplateParametersChanged() { }; - virtual void OnBlueprintStructureModified() { }; - virtual void OnBlueprintModified() { }; - - -protected: - - // UActorComponents Method - virtual void OnComponentCreated() override; - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - - virtual void OnChildAttached(USceneComponent* ChildComponent) override; - - virtual void BeginDestroy() override; - - // Do any object - specific cleanup required immediately after loading an object. - // This is not called for newly - created objects, and by default will always execute on the game thread. - virtual void PostLoad() override; - - // Called after importing property values for this object (paste, duplicate or .t3d import) - // Allow the object to perform any cleanup for properties which shouldn't be duplicated or - // Are unsupported by the script serialization - virtual void PostEditImport() override; - - // - void OnActorMoved(AActor* Actor); - - // - void UpdatePostDuplicate(); - - // - //static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); - -public: - - // Houdini Asset associated with this component. - /*Category = HoudiniAsset, EditAnywhere, meta = (DisplayPriority=0)*/ - UPROPERTY(Category = HoudiniAsset, EditAnywhere)// BlueprintSetter = SetHoudiniAsset, BlueprintReadWrite, ) - UHoudiniAsset* HoudiniAsset; - - // Automatically cook when a parameter or input is changed - UPROPERTY() - bool bCookOnParameterChange; - - // Enables uploading of transformation changes back to Houdini Engine. - UPROPERTY() - bool bUploadTransformsToHoudiniEngine; - - // Transform changes automatically trigger cooks. - UPROPERTY() - bool bCookOnTransformChange; - - // Houdini materials will be converted to Unreal Materials. - //UPROPERTY() - //bool bUseNativeHoudiniMaterials; - - // This asset will cook when its asset input cook - UPROPERTY() - bool bCookOnAssetInputCook; - - // Enabling this will prevent the HDA from producing any output after cooking. - UPROPERTY() - bool bOutputless; - - // Enabling this will allow outputing the asset's templated geos - UPROPERTY() - bool bOutputTemplateGeos; - - // Temporary cook folder - UPROPERTY() - FDirectoryPath TemporaryCookFolder; - - // Folder used for baking this asset's outputs - UPROPERTY() - FDirectoryPath BakeFolder; - - // HoudiniGeneratedStaticMeshSettings - /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Double Sided Geometry")) - uint32 bGeneratedDoubleSidedGeometry : 1; - - /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * GeneratedPhysMaterial; - - /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ - UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Collision Complexity")) - TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; - - /** Resolution of lightmap. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 GeneratedLightMapResolution; - - /** Bias multiplier for Light Propagation Volume lighting. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float GeneratedLpvBiasMultiplier; - - /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) - float GeneratedDistanceFieldResolutionScale; - - /** Custom walkable slope setting for generated mesh's body. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride GeneratedWalkableSlopeOverride; - - /** The light map coordinate index. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light map coordinate index")) - int32 GeneratedLightMapCoordinateIndex; - - /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; - - /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Streaming Distance Multiplier")) - float GeneratedStreamingDistanceMultiplier; - - /** Default settings when using this mesh for instanced foliage. */ - /* - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; - */ - - /** Array of user data stored with the asset. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Asset User Data")) - TArray GeneratedAssetUserData; - - // Override the global fast proxy mesh settings on this component? - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayPriority = 0)) - bool bOverrideGlobalProxyStaticMeshSettings; - - // For StaticMesh outputs: should a fast proxy be created first? - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Enable Proxy Static Mesh", EditCondition="bOverrideGlobalProxyStaticMeshSettings", DisplayPriority = 0)) - bool bEnableProxyStaticMeshOverride; - - // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes After a Timeout", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementByTimerOverride; - - // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride")) - float ProxyMeshAutoRefineTimeoutSecondsOverride; - - // Automatically refine proxy meshes to UStaticMesh before the map is saved - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes When Saving a Map", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; - - // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session - UPROPERTY(Category = "HoudiniAsset | Static Mesh", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; - - // The method to use to create the mesh - UPROPERTY(Category = "HoudiniAsset | Development", EditAnywhere, meta = (DisplayPriority = 0)) - EHoudiniStaticMeshMethod StaticMeshMethod; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bGenerateMenuExpanded; - - UPROPERTY() - bool bBakeMenuExpanded; - - UPROPERTY() - bool bAssetOptionMenuExpanded; - - UPROPERTY() - bool bHelpAndDebugMenuExpanded; - - UPROPERTY() - EHoudiniEngineBakeOption HoudiniEngineBakeOption; - - // If true, then after a successful bake, the HACs outputs will be cleared and removed. - UPROPERTY() - bool bRemoveOutputAfterBake; - - // If true, recenter baked actors to their bounding box center after bake - UPROPERTY() - bool bRecenterBakedActors; - - // If true, replace the previously baked output (if any) instead of creating new objects - UPROPERTY() - bool bReplacePreviousBake; -#endif - -protected: - - // Id of corresponding Houdini asset. - UPROPERTY(DuplicateTransient) - int32 AssetId; - - // List of dependent downstream HACs that have us as an asset input - UPROPERTY(DuplicateTransient) - TSet DownstreamHoudiniAssets; - - // Unique GUID created by component. - UPROPERTY(DuplicateTransient) - FGuid ComponentGUID; - - // GUID used to track asynchronous cooking requests. - UPROPERTY(DuplicateTransient) - FGuid HapiGUID; - - // Current state of the asset - UPROPERTY(DuplicateTransient) - EHoudiniAssetState AssetState; - - // Last asset state logged. - UPROPERTY(DuplicateTransient) - mutable EHoudiniAssetState DebugLastAssetState; - - // Result of the current asset's state - UPROPERTY(DuplicateTransient) - EHoudiniAssetStateResult AssetStateResult; - - //// Contains the context for keeping track of shared - //// Houdini data. - //UPROPERTY(DuplicateTransient) - //UHoudiniAssetContext* AssetContext; - - // Subasset index - UPROPERTY() - uint32 SubAssetIndex; - - // Number of times this asset has been cooked. - UPROPERTY(DuplicateTransient) - int32 AssetCookCount; - - // - UPROPERTY(DuplicateTransient) - bool bHasBeenLoaded; - - UPROPERTY(DuplicateTransient) - bool bHasBeenDuplicated; - - UPROPERTY(DuplicateTransient) - bool bPendingDelete; - - UPROPERTY(DuplicateTransient) - bool bRecookRequested; - - UPROPERTY(DuplicateTransient) - bool bRebuildRequested; - - UPROPERTY(DuplicateTransient) - bool bEnableCooking; - - UPROPERTY(DuplicateTransient) - bool bForceNeedUpdate; - - UPROPERTY(DuplicateTransient) - bool bLastCookSuccess; - - UPROPERTY(DuplicateTransient) - bool bBlueprintStructureModified; - - UPROPERTY(DuplicateTransient) - bool bBlueprintModified; - - //UPROPERTY(DuplicateTransient) - //bool bEditorPropertiesNeedFullUpdate; - - UPROPERTY(Instanced) - TArray Parameters; - - UPROPERTY(Instanced) - TArray Inputs; - - UPROPERTY(Instanced) - TArray Outputs; - - // The baked outputs from the last bake. - UPROPERTY() - TArray BakedOutputs; - - // Any actors that aren't explicitly - // tracked by output objects should be registered - // here so that they can be cleaned up. - UPROPERTY() - TArray> UntrackedOutputs; - - UPROPERTY() - TArray HandleComponents; - - UPROPERTY(Transient, DuplicateTransient) - bool bHasComponentTransformChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bFullyLoaded; - - UPROPERTY() - UHoudiniPDGAssetLink* PDGAssetLink; - - // Timer that is used to trigger creation of UStaticMesh for all mesh outputs - // that still have UHoudiniStaticMeshes. The timer is cleared on PreCook and reset - // at the end of the PostCook. - UPROPERTY() - FTimerHandle RefineMeshesTimer; - - // Delegate that is used to broadcast when RefineMeshesTimer fires - FOnRefineMeshesTimerDelegate OnRefineMeshesTimerDelegate; - - // If true, don't build a proxy mesh next cook (regardless of global or override settings), - // instead build the UStaticMesh directly (if applicable for the output types). - UPROPERTY(DuplicateTransient) - bool bNoProxyMeshNextCookRequested; - - // Maps a UObject to an Input number, used to preset the asset's inputs - UPROPERTY(Transient, DuplicateTransient) - TMap InputPresets; - - // If true, bake the asset after its next cook. - UPROPERTY(DuplicateTransient) - bool bBakeAfterNextCook; - - // Delegate to broadcast when baking after a cook. - // Currently we cannot call the bake functions from here (Runtime module) - // or from the HoudiniEngineManager (HoudiniEngine) module, so we use - // a delegate. - FOnPostCookBakeDelegate OnPostCookBakeDelegate; - - // Cached flag of whether this object is considered to be a 'preview' component or not. - // This is typically useful in destructors when references to the World, for example, - // is no longer available. - UPROPERTY(Transient, DuplicateTransient) - bool bCachedIsPreview; - - USimpleConstructionScript* GetSCS() const; - - // Object used to convert V1 HAC to V2 HAC - UHoudiniAssetComponent_V1* Version1CompatibilityHAC; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniOutput.h" +#include "HoudiniInputTypes.h" +#include "HoudiniPluginSerializationVersion.h" + +#include "Components/PrimitiveComponent.h" +#include "Components/SceneComponent.h" + +#include "HoudiniAssetComponent.generated.h" + +class UHoudiniAsset; +class UHoudiniParameter; +class UHoudiniInput; +class UHoudiniOutput; +class UHoudiniHandleComponent; +class UHoudiniPDGAssetLink; +class UHoudiniAssetComponent_V1; + +UENUM() +enum class EHoudiniAssetState : uint8 +{ + // Loaded / Duplicated HDA, + // Will need to be instantiated upon change/update + NeedInstantiation, + + // Newly created HDA, needs to be instantiated immediately + PreInstantiation, + + // Instantiating task in progress + Instantiating, + + // Instantiated HDA, needs to be cooked immediately + PreCook, + + // Cooking task in progress + Cooking, + + // Cooking has finished + PostCook, + + // Cooked HDA, needs to be processed immediately + PreProcess, + + // Processing task in progress + Processing, + + // Processed / Updated HDA + // Will need to be cooked upon change/update + None, + + // Asset needs to be rebuilt (Deleted/Instantiated/Cooked) + NeedRebuild, + + // Asset needs to be deleted + NeedDelete, + + // Deleting + Deleting, + + // Process component template. This is ticking has very limited + // functionality, typically limited to checking for parameter updates + // in order to trigger PostEditChange() to run construction scripts again. + ProcessTemplate, +}; + +UENUM() +enum class EHoudiniAssetStateResult : uint8 +{ + None, + Working, + Success, + FinishedWithError, + FinishedWithFatalError, + Aborted +}; + +UENUM() +enum class EHoudiniStaticMeshMethod : uint8 +{ + // Static Meshes will be generated by using Raw Meshes. + RawMesh, + // Static Meshes will be generated by using Mesh Descriptions. + FMeshDescription, + // Always build Houdini Proxy Meshes (dev) + UHoudiniStaticMesh, +}; + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EHoudiniEngineBakeOption : uint8 +{ + ToActor, + ToBlueprint, + ToFoliage, + ToWorldOutliner, +}; +#endif + +class UHoudiniAssetComponent; + +DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetEvent, UHoudiniAsset*); +DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetComponentEvent, UHoudiniAssetComponent*) + +UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility), editinlinenew) +class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent +{ + GENERATED_UCLASS_BODY() + + // Declare translators as friend so they can easily directly modify + // Inputs, outputs and parameters + friend class FHoudiniEngineManager; + friend struct FHoudiniOutputTranslator; + friend struct FHoudiniInputTranslator; + friend struct FHoudiniSplineTranslator; + friend struct FHoudiniParameterTranslator; + friend struct FHoudiniPDGManager; + friend struct FHoudiniHandleTranslator; + +#if WITH_EDITORONLY_DATA + friend class FHoudiniAssetComponentDetails; +#endif + +public: + + // Declare the delegate that is broadcast when RefineMeshesTimer fires + DECLARE_MULTICAST_DELEGATE_OneParam(FOnRefineMeshesTimerDelegate, UHoudiniAssetComponent*); + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPostCookBakeDelegate, UHoudiniAssetComponent*); + + virtual ~UHoudiniAssetComponent(); + + virtual void Serialize(FArchive & Ar) override; + + virtual bool ConvertLegacyData(); + + // Called after the C++ constructor and after the properties have been initialized, including those loaded from config. + // This is called before any serialization or other setup has happened. + virtual void PostInitProperties() override; + + // Returns the Owner actor / HAC name + FString GetDisplayName() const; + + // Indicates if the HAC needs to be updated + bool NeedUpdate() const; + + // Indicates if the HAC's transform needs to be updated + bool NeedTransformUpdate() const { return (bHasComponentTransformChanged && bUploadTransformsToHoudiniEngine); }; + + // Indicates if any of the HAC's output components needs to be updated (no recook needed) + bool NeedOutputUpdate() const; + + // Check whether any inputs / outputs / parameters have made blueprint modifications. + bool NeedBlueprintStructureUpdate() const; + bool NeedBlueprintUpdate() const; + + // Try to find one of our parameter that matches another (name, type, size and enabled) + UHoudiniParameter* FindMatchingParameter(UHoudiniParameter* InOtherParam); + + // Try to find one of our input that matches another one (name, isobjpath, index / parmId) + UHoudiniInput* FindMatchingInput(UHoudiniInput* InOtherInput); + + // Try to find one of our handle that matches another one (name and handle type) + UHoudiniHandleComponent* FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle); + + // Finds a parameter by name + UHoudiniParameter* FindParameterByName(const FString& InParamName); + + // Returns True if the component has at least one mesh output of class U + template + bool HasMeshOutputObjectOfClass() const; + + // Returns True if the component has at least one mesh output with a current proxy + bool HasAnyCurrentProxyOutput() const; + + // Returns True if the component has at least one proxy mesh output (not necessarily current/displayed) + bool HasAnyProxyOutput() const; + + // Returns True if the component has at least one non-proxy output component amongst its outputs + bool HasAnyOutputComponent() const; + + // Returns true if the component has InOutputObjectToFind in its output object + bool HasOutputObject(UObject* InOutputObjectToFind) const; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + UHoudiniAsset * GetHoudiniAsset() const; + int32 GetAssetId() const { return AssetId; }; + EHoudiniAssetState GetAssetState() const { return AssetState; }; + FString GetAssetStateAsString() const { return FHoudiniEngineRuntimeUtils::EnumToString(TEXT("EHoudiniAssetState"), GetAssetState()); }; + EHoudiniAssetStateResult GetAssetStateResult() const { return AssetStateResult; }; + FGuid GetHapiGUID() const { return HapiGUID; }; + FGuid GetComponentGUID() const { return ComponentGUID; }; + + int32 GetNumInputs() const { return Inputs.Num(); }; + int32 GetNumOutputs() const { return Outputs.Num(); }; + int32 GetNumParameters() const { return Parameters.Num(); }; + int32 GetNumHandles() const { return HandleComponents.Num(); }; + + UHoudiniInput* GetInputAt(const int32& Idx) { return Inputs.IsValidIndex(Idx) ? Inputs[Idx] : nullptr; }; + UHoudiniOutput* GetOutputAt(const int32& Idx) { return Outputs.IsValidIndex(Idx) ? Outputs[Idx] : nullptr;}; + UHoudiniParameter* GetParameterAt(const int32& Idx) { return Parameters.IsValidIndex(Idx) ? Parameters[Idx] : nullptr;}; + UHoudiniHandleComponent* GetHandleComponentAt(const int32& Idx) { return HandleComponents.IsValidIndex(Idx) ? HandleComponents[Idx] : nullptr; }; + + void GetOutputs(TArray& OutOutputs) const; + + TArray& GetBakedOutputs() { return BakedOutputs; } + const TArray& GetBakedOutputs() const { return BakedOutputs; } + + /* + TArray& GetParameters() { return Parameters; }; + TArray& GetInputs() { return Inputs; }; + TArray& GetOutputs() { return Outputs; }; + */ + + bool IsCookingEnabled() const { return bEnableCooking; }; + bool HasBeenLoaded() const { return bHasBeenLoaded; }; + bool HasBeenDuplicated() const { return bHasBeenDuplicated; }; + bool HasRecookBeenRequested() const { return bRecookRequested; }; + bool HasRebuildBeenRequested() const { return bRebuildRequested; }; + + //bool GetEditorPropertiesNeedFullUpdate() const { return bEditorPropertiesNeedFullUpdate; }; + + int32 GetAssetCookCount() const { return AssetCookCount; }; + + bool IsFullyLoaded() const { return bFullyLoaded; }; + + UHoudiniPDGAssetLink * GetPDGAssetLink() const { return PDGAssetLink; }; + + virtual bool IsProxyStaticMeshEnabled() const; + bool IsProxyStaticMeshRefinementByTimerEnabled() const; + float GetProxyMeshAutoRefineTimeoutSeconds() const; + bool IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const; + bool IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const; + // If true, then the next cook should not build proxy meshes, regardless of global or override settings, + // but should instead directly build UStaticMesh + bool HasNoProxyMeshNextCookBeenRequested() const { return bNoProxyMeshNextCookRequested; } + // Returns true if the asset state indicates that it has been cooked in this session, false otherwise. + bool IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const; + // Returns true if the asset should be bake after the next cook + bool IsBakeAfterNextCookEnabled() const { return bBakeAfterNextCook; } + + FOnPostCookBakeDelegate& GetOnPostCookBakeDelegate() { return OnPostCookBakeDelegate; } + + // Derived blueprint based components will check whether the template + // component contains updates that needs to processed. + bool NeedUpdateParameters() const; + bool NeedUpdateInputs() const; + + // Returns true if the component has any previous baked output recorded in its outputs + bool HasPreviousBakeOutput() const; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + //void SetAssetId(const int& InAssetId); + //void SetAssetState(const EHoudiniAssetState& InAssetState) { AssetState = InAssetState; }; + //void SetAssetStateResult(const EHoudiniAssetStateResult& InAssetStateResult) { AssetStateResult = InAssetStateResult; }; + + //void SetHapiGUID(const FGuid& InGUID) { HapiGUID = InGUID; }; + //void SetComponentGUID(const FGuid& InGUID) { ComponentGUID = InGUID; }; + + //UFUNCTION(BlueprintSetter) + virtual void SetHoudiniAsset(UHoudiniAsset * NewHoudiniAsset); + + void SetHasBeenLoaded(const bool& InLoaded) { bHasBeenLoaded = InLoaded; }; + + void SetHasBeenDuplicated(const bool& InDuplicated) { bHasBeenDuplicated = InDuplicated; }; + + //void SetEditorPropertiesNeedFullUpdate(const bool& InUpdate) { bEditorPropertiesNeedFullUpdate = InUpdate; }; + + // Marks the assets as needing a recook + void MarkAsNeedCook(); + // Marks the assets as needing a full rebuild + void MarkAsNeedRebuild(); + // Marks the asset as needing to be instantiated + void MarkAsNeedInstantiation(); + // The blueprint has been structurally modified + void MarkAsBlueprintStructureModified(); + // The blueprint has been modified but not structurally changed. + void MarkAsBlueprintModified(); + + // + void SetAssetCookCount(const int32& InCount) { AssetCookCount = InCount; }; + // + void SetRecookRequested(const bool& InRecook) { bRecookRequested = InRecook; }; + // + void SetRebuildRequested(const bool& InRebuild) { bRebuildRequested = InRebuild; }; + // + void SetHasComponentTransformChanged(const bool& InHasChanged); + + // Set to True to force the next cook to not build a proxy mesh (regardless of global or override settings) and + // instead build a UStaticMesh directly (if applicable for the output type). + void SetNoProxyMeshNextCookRequested(bool bInNoProxyMeshNextCookRequested) { bNoProxyMeshNextCookRequested = bInNoProxyMeshNextCookRequested; } + + // Set to True to force the next cook to bake the asset after the cook completes. + void SetBakeAfterNextCookEnabled(bool bInEnabled) { bBakeAfterNextCook = bInEnabled; } + + // + void SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink); + // + virtual void OnHoudiniAssetChanged(); + + // + void AddDownstreamHoudiniAsset(UHoudiniAssetComponent* InDownstreamAsset) { DownstreamHoudiniAssets.Add(InDownstreamAsset); }; + // + void RemoveDownstreamHoudiniAsset(UHoudiniAssetComponent* InRemoveDownstreamAsset) { DownstreamHoudiniAssets.Remove(InRemoveDownstreamAsset); }; + // + void ClearDownstreamHoudiniAsset() { DownstreamHoudiniAssets.Empty(); }; + // + bool NotifyCookedToDownstreamAssets(); + // + bool NeedsToWaitForInputHoudiniAssets(); + + // Clear/disable the RefineMeshesTimer. + void ClearRefineMeshesTimer(); + + // Re-set the RefineMeshesTimer to its default value. + void SetRefineMeshesTimer(); + + virtual void OnRefineMeshesTimerFired(); + + // Called by RefineMeshesTimer when the timer is triggered. + // Checks for any UHoudiniStaticMesh in Outputs and bakes UStaticMesh for them via FHoudiniMeshTranslator. + FOnRefineMeshesTimerDelegate& GetOnRefineMeshesTimerDelegate() { return OnRefineMeshesTimerDelegate; } + + // Returns true if the asset is valid for cook/bake + virtual bool IsComponentValid() const; + // Return false if this component has no cooking or instantiation in progress. + bool IsInstantiatingOrCooking() const; + + // HoudiniEngineTick will be called by HoudiniEngineManager::Tick() + virtual void HoudiniEngineTick(); + +#if WITH_EDITOR + // This alternate version of PostEditChange is called when properties inside structs are modified. The property that was actually modified + // is located at the tail of the list. The head of the list of the FStructProperty member variable that contains the property that was modified. + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; + + //Called after applying a transaction to the object. Default implementation simply calls PostEditChange. + virtual void PostEditUndo() override; + + // Whether this component is currently open in a Blueprint editor. This + // method is overridden by HoudiniAssetBlueprintComponent. + virtual bool HasOpenEditor() const { return false; }; + +#endif + + void SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const; + + virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent); + + virtual void OnRegister() override; + + // USceneComponent methods. + virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; + + FBox GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const; + + // Set this component's input presets + void SetInputPresets(const TMap& InPresets); + // Apply the preset input for HoudiniTools + void ApplyInputPresets(); + + // return the cached component template, if available. + virtual UHoudiniAssetComponent* GetCachedTemplate() const { return nullptr; } + + //------------------------------------------------------------------------------------------------ + // Supported Features + //------------------------------------------------------------------------------------------------ + + // Whether or not this component should be able to delete the Houdini nodes + // that correspond to the HoudiniAsset when being deregistered. + virtual bool CanDeleteHoudiniNodes() const { return true; } + + virtual bool IsInputTypeSupported(EHoudiniInputType InType) const; + virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const; + + //------------------------------------------------------------------------------------------------5 + // Characteristics + //------------------------------------------------------------------------------------------------ + + // Try to determine whether this component belongs to a preview actor. + // Preview / Template components need to sync their data for HDA cooks and output translations. + bool IsPreview() const; + + virtual bool IsValidComponent() const; + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + // TODO: After the cook worfklow rework, most of these won't be needed anymore, so clean up! + //FHoudiniAssetComponentEvent OnTemplateParametersChanged; + //FHoudiniAssetComponentEvent OnPreAssetCook; + //FHoudiniAssetComponentEvent OnCookCompleted; + //FHoudiniAssetComponentEvent OnOutputProcessingCompleted; + + /*virtual void BroadcastParametersChanged(); + virtual void BroadcastPreAssetCook(); + virtual void BroadcastCookCompleted();*/ + + virtual void OnPrePreCook() {}; + virtual void OnPostPreCook() {}; + virtual void OnPreOutputProcessing() {}; + virtual void OnPostOutputProcessing() {}; + virtual void OnPrePreInstantiation() {}; + + + virtual void NotifyHoudiniRegisterCompleted() {}; + virtual void NotifyHoudiniPreUnregister() {}; + virtual void NotifyHoudiniPostUnregister() {}; + + virtual void OnFullyLoaded(); + + // Component template parameters have been updated. + // Broadcast delegate, and let preview components take care of the rest. + virtual void OnTemplateParametersChanged() { }; + virtual void OnBlueprintStructureModified() { }; + virtual void OnBlueprintModified() { }; + + +protected: + + // UActorComponents Method + virtual void OnComponentCreated() override; + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + virtual void OnChildAttached(USceneComponent* ChildComponent) override; + + virtual void BeginDestroy() override; + + // Do any object - specific cleanup required immediately after loading an object. + // This is not called for newly - created objects, and by default will always execute on the game thread. + virtual void PostLoad() override; + + // Called after importing property values for this object (paste, duplicate or .t3d import) + // Allow the object to perform any cleanup for properties which shouldn't be duplicated or + // Are unsupported by the script serialization + virtual void PostEditImport() override; + + // + void OnActorMoved(AActor* Actor); + + // + void UpdatePostDuplicate(); + + // + //static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); + +public: + + // Houdini Asset associated with this component. + /*Category = HoudiniAsset, EditAnywhere, meta = (DisplayPriority=0)*/ + UPROPERTY(Category = HoudiniAsset, EditAnywhere)// BlueprintSetter = SetHoudiniAsset, BlueprintReadWrite, ) + UHoudiniAsset* HoudiniAsset; + + // Automatically cook when a parameter or input is changed + UPROPERTY() + bool bCookOnParameterChange; + + // Enables uploading of transformation changes back to Houdini Engine. + UPROPERTY() + bool bUploadTransformsToHoudiniEngine; + + // Transform changes automatically trigger cooks. + UPROPERTY() + bool bCookOnTransformChange; + + // Houdini materials will be converted to Unreal Materials. + //UPROPERTY() + //bool bUseNativeHoudiniMaterials; + + // This asset will cook when its asset input cook + UPROPERTY() + bool bCookOnAssetInputCook; + + // Enabling this will prevent the HDA from producing any output after cooking. + UPROPERTY() + bool bOutputless; + + // Enabling this will allow outputing the asset's templated geos + UPROPERTY() + bool bOutputTemplateGeos; + + // Temporary cook folder + UPROPERTY() + FDirectoryPath TemporaryCookFolder; + + // Folder used for baking this asset's outputs + UPROPERTY() + FDirectoryPath BakeFolder; + + // The method to use to create the mesh + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 0)) + EHoudiniStaticMeshMethod StaticMeshMethod; + + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 1)/*, meta = (ShowOnlyInnerProperties)*/) + FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; + + // Override the global fast proxy mesh settings on this component? + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere/*, meta = (DisplayAfter = "StaticMeshGenerationProperties")*/) + bool bOverrideGlobalProxyStaticMeshSettings; + + // For StaticMesh outputs: should a fast proxy be created first? + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Enable Proxy Static Mesh", EditCondition="bOverrideGlobalProxyStaticMeshSettings")) + bool bEnableProxyStaticMeshOverride; + + // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes After a Timeout", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementByTimerOverride; + + // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride")) + float ProxyMeshAutoRefineTimeoutSecondsOverride; + + // Automatically refine proxy meshes to UStaticMesh before the map is saved + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes When Saving a Map", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; + + // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; + + + +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bGenerateMenuExpanded; + + UPROPERTY() + bool bBakeMenuExpanded; + + UPROPERTY() + bool bAssetOptionMenuExpanded; + + UPROPERTY() + bool bHelpAndDebugMenuExpanded; + + UPROPERTY() + EHoudiniEngineBakeOption HoudiniEngineBakeOption; + + // If true, then after a successful bake, the HACs outputs will be cleared and removed. + UPROPERTY() + bool bRemoveOutputAfterBake; + + // If true, recenter baked actors to their bounding box center after bake + UPROPERTY() + bool bRecenterBakedActors; + + // If true, replace the previously baked output (if any) instead of creating new objects + UPROPERTY() + bool bReplacePreviousBake; +#endif + +protected: + + // Id of corresponding Houdini asset. + UPROPERTY(DuplicateTransient) + int32 AssetId; + + // List of dependent downstream HACs that have us as an asset input + UPROPERTY(DuplicateTransient) + TSet DownstreamHoudiniAssets; + + // Unique GUID created by component. + UPROPERTY(DuplicateTransient) + FGuid ComponentGUID; + + // GUID used to track asynchronous cooking requests. + UPROPERTY(DuplicateTransient) + FGuid HapiGUID; + + // Current state of the asset + UPROPERTY(DuplicateTransient) + EHoudiniAssetState AssetState; + + // Last asset state logged. + UPROPERTY(DuplicateTransient) + mutable EHoudiniAssetState DebugLastAssetState; + + // Result of the current asset's state + UPROPERTY(DuplicateTransient) + EHoudiniAssetStateResult AssetStateResult; + + //// Contains the context for keeping track of shared + //// Houdini data. + //UPROPERTY(DuplicateTransient) + //UHoudiniAssetContext* AssetContext; + + // Subasset index + UPROPERTY() + uint32 SubAssetIndex; + + // Number of times this asset has been cooked. + UPROPERTY(DuplicateTransient) + int32 AssetCookCount; + + // + UPROPERTY(DuplicateTransient) + bool bHasBeenLoaded; + + UPROPERTY(DuplicateTransient) + bool bHasBeenDuplicated; + + UPROPERTY(DuplicateTransient) + bool bPendingDelete; + + UPROPERTY(DuplicateTransient) + bool bRecookRequested; + + UPROPERTY(DuplicateTransient) + bool bRebuildRequested; + + UPROPERTY(DuplicateTransient) + bool bEnableCooking; + + UPROPERTY(DuplicateTransient) + bool bForceNeedUpdate; + + UPROPERTY(DuplicateTransient) + bool bLastCookSuccess; + + UPROPERTY(DuplicateTransient) + bool bBlueprintStructureModified; + + UPROPERTY(DuplicateTransient) + bool bBlueprintModified; + + //UPROPERTY(DuplicateTransient) + //bool bEditorPropertiesNeedFullUpdate; + + UPROPERTY(Instanced) + TArray Parameters; + + UPROPERTY(Instanced) + TArray Inputs; + + UPROPERTY(Instanced) + TArray Outputs; + + // The baked outputs from the last bake. + UPROPERTY() + TArray BakedOutputs; + + // Any actors that aren't explicitly + // tracked by output objects should be registered + // here so that they can be cleaned up. + UPROPERTY() + TArray> UntrackedOutputs; + + UPROPERTY() + TArray HandleComponents; + + UPROPERTY(Transient, DuplicateTransient) + bool bHasComponentTransformChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bFullyLoaded; + + UPROPERTY() + UHoudiniPDGAssetLink* PDGAssetLink; + + // Timer that is used to trigger creation of UStaticMesh for all mesh outputs + // that still have UHoudiniStaticMeshes. The timer is cleared on PreCook and reset + // at the end of the PostCook. + UPROPERTY() + FTimerHandle RefineMeshesTimer; + + // Delegate that is used to broadcast when RefineMeshesTimer fires + FOnRefineMeshesTimerDelegate OnRefineMeshesTimerDelegate; + + // If true, don't build a proxy mesh next cook (regardless of global or override settings), + // instead build the UStaticMesh directly (if applicable for the output types). + UPROPERTY(DuplicateTransient) + bool bNoProxyMeshNextCookRequested; + + // Maps a UObject to an Input number, used to preset the asset's inputs + UPROPERTY(Transient, DuplicateTransient) + TMap InputPresets; + + // If true, bake the asset after its next cook. + UPROPERTY(DuplicateTransient) + bool bBakeAfterNextCook; + + // Delegate to broadcast when baking after a cook. + // Currently we cannot call the bake functions from here (Runtime module) + // or from the HoudiniEngineManager (HoudiniEngine) module, so we use + // a delegate. + FOnPostCookBakeDelegate OnPostCookBakeDelegate; + + // Cached flag of whether this object is considered to be a 'preview' component or not. + // This is typically useful in destructors when references to the World, for example, + // is no longer available. + UPROPERTY(Transient, DuplicateTransient) + bool bCachedIsPreview; + + USimpleConstructionScript* GetSCS() const; + + // Object used to convert V1 HAC to V2 HAC + UHoudiniAssetComponent_V1* Version1CompatibilityHAC; +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp index a9671bb7f..42a5c38b3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp @@ -1,1799 +1,1802 @@ -/* -* Copyright (c) <2020> 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 "HoudiniCompatibilityHelpers.h" - -#include "HoudiniPluginSerializationVersion.h" - -#include "HoudiniInput.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniHandleComponent.h" - -#include "Engine/StaticMesh.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "EngineUtils.h" // for TActorIterator<> - -#if WITH_EDITOR - #include "Editor.h" - #include "Editor/UnrealEdEngine.h" - #include "UnrealEdGlobals.h" -#endif - -// TODO: -// HoudiniInstancedActorComponent ? -// HoudiniMeshSplitInstancerComponent ? - -UHoudiniAssetComponent_V1::UHoudiniAssetComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - - -void -UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) -{ - if (!Ar.IsLoading()) - return; - - //Super::Serialize(Ar); - - //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Serialize component flags. - Ar << HoudiniAssetComponentFlagsPacked; - - // Serialize format version. - uint32 HoudiniAssetComponentVersion = 0;//Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - Ar << HoudiniAssetComponentVersion; - - // ComponenState Enum, saved as uint8 - // 0 invalid - // 1 None - // 2 Instantiated - // 3 BeingCooked - // Serialize component state. - uint8 ComponentState = 0; - Ar << ComponentState; - - // Serialize scaling information and import axis. - Ar << GeneratedGeometryScaleFactor; - Ar << TransformScaleFactor; - - // ImportAxis Enum, saved as uint8 - // 0 unreal 1 Houdini - //uint8 ImportAxis = 0; - Ar << ImportAxis; - - // Serialize generated component GUID. - Ar << ComponentGUID; - - // If component is in invalid state, we can skip the rest of serialization. - //if (ComponentState == EHoudiniAssetComponentState_V1::Invalid) - if (ComponentState == 0) - return; - - // Serialize Houdini asset. - Ar << HoudiniAsset; - - // 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; - int TempId; - Ar << TempId; - bIsPlayModeActive_Unused = false; - } - - // Serialization of default preset. - Ar << DefaultPresetBuffer; - - // Serialization of preset. - { - bool bPresetSaved = false; - Ar << bPresetSaved; - - if (bPresetSaved) - { - Ar << PresetBuffer; - } - } - - // Serialize parameters. - //SerializeParameters(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::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; - } - - // 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::TIterator IterParams(Parameters); IterParams; ++IterParams) - { - UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); - if (HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill()) - ParameterByName.Add(HoudiniAssetParameter->ParameterName, HoudiniAssetParameter); - } - } - } - - // Serialize inputs. - //SerializeInputs(Ar); - { - Ar << Inputs; - - /* - if (Ar.IsLoading()) - { - for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) - { - UHoudiniAssetInput_V1 * HoudiniAssetInput = Inputs[InputIdx]; - if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) - Inputs[InputIdx]->SetHoudiniAssetComponent(this); - } - } - */ - } - - // 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); - { - //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; - int HoudiniInstanceInputKey = -1; - - Ar << HoudiniInstanceInputKey; - Ar << InstanceInputs[InstanceInputIdx]; - } - } - } - - // 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 DirtyPackages; - if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES) - { - TMap SavedPackages; - Ar << SavedPackages; - } - - if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS) - { - // Temporary Mesh Packages - TMap MeshPackages; - Ar << MeshPackages; - - // Temporary Landscape Layers Packages - TMap LayerPackages; - Ar << LayerPackages; - } -} - -uint32 -GetTypeHash(const FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) -{ - return HoudiniGeoPartObject.GetTypeHash(); -} - -FArchive & -operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) -{ - HoudiniGeoPartObject.Serialize(Ar); - return Ar; -} - -uint32 -FHoudiniGeoPartObject_V1::GetTypeHash() const -{ - int32 HashBuffer[4] = { ObjectId, GeoId, PartId, SplitId }; - int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); - return FCrc::StrCrc32(*SplitName, Hash); -} - -bool -FHoudiniGeoPartObject_V1SortPredicate::operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const -{ - /*if (!A.IsValid() || !B.IsValid()) - return false;*/ - - if (A.ObjectId == B.ObjectId) - { - if (A.GeoId == B.GeoId) - { - if (A.PartId == B.PartId) - return A.SplitId < B.SplitId; - else - return A.PartId < B.PartId; - } - else - { - return A.GeoId < B.GeoId; - } - } - - return A.ObjectId < B.ObjectId; -} - -void -FHoudiniGeoPartObject_V1::Serialize(FArchive & Ar) -{ - //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniGeoPartObjectVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniGeoPartObjectVersion; - - Ar << TransformMatrix; - - Ar << ObjectName; - Ar << PartName; - Ar << SplitName; - - // Serialize instancer material. - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME) - Ar << InstancerMaterialName; - - // Serialize instancer attribute material. - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME) - Ar << InstancerAttributeMaterialName; - - Ar << AssetId; - Ar << ObjectId; - Ar << GeoId; - Ar << PartId; - Ar << SplitId; - - Ar << HoudiniGeoPartObjectFlagsPacked; - - if (HoudiniGeoPartObjectVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - // Prior to this version the unused flags space was not zero-initialized, so - // zero them out now to prevent misinterpreting any 1s - HoudiniGeoPartObjectFlagsPacked &= 0x3FFFF; - } - - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH) - { - Ar << NodePath; - } -} - - -FHoudiniGeoPartObject -FHoudiniGeoPartObject_V1::ConvertLegacyData() -{ - FHoudiniGeoPartObject NewHGPO; - - NewHGPO.AssetId = AssetId; - // NewHGPO.AssetName; - - NewHGPO.ObjectId = ObjectId; - NewHGPO.ObjectName = ObjectName; - - NewHGPO.GeoId = GeoId; - - NewHGPO.PartId = PartId; - NewHGPO.PartName = PartName; - NewHGPO.bHasCustomPartName = bHasCustomName; - - NewHGPO.SplitGroups.Add(SplitName); - - NewHGPO.TransformMatrix = TransformMatrix; - NewHGPO.NodePath = NodePath; - - // NewHGPO.VolumeName; - // NewHGPO.VolumeTileIndex; - - NewHGPO.bIsVisible = bIsVisible; - NewHGPO.bIsEditable = bIsEditable; - // NewHGPO.bIsTemplated; - // NewHGPO.bIsInstanced; - - NewHGPO.bHasGeoChanged = bHasGeoChanged; - // NewHGPO.bHasPartChanged; - // NewHGPO.bHasTransformChanged; - // NewHGPO.bHasMaterialsChanged; - NewHGPO.bLoaded = true; //bIsLoaded; - - // Hamdle Part Type - if (bIsCurve) - { - NewHGPO.Type = EHoudiniPartType::Curve; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - else if (bIsVolume) - { - NewHGPO.Type = EHoudiniPartType::Volume; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - else if (bIsInstancer) - { - NewHGPO.Type = EHoudiniPartType::Instancer; - NewHGPO.InstancerType = EHoudiniInstancerType::ObjectInstancer; - } - else if (bIsPackedPrimitiveInstancer) - { - NewHGPO.Type = EHoudiniPartType::Instancer; - NewHGPO.InstancerType = EHoudiniInstancerType::PackedPrimitive; - } - else - { - NewHGPO.Type = EHoudiniPartType::Mesh; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - - // Instancer specific flags - if (NewHGPO.Type == EHoudiniPartType::Instancer) - { - //bInstancerMaterialAvailable - //bInstancerAttributeMaterialAvailable - //InstancerMaterialName - //InstancerAttributeMaterialName - } - - // Collision specific flags - if (NewHGPO.Type == EHoudiniPartType::Mesh) - { - //bIsCollidable - //bIsRenderCollidable - //bIsUCXCollisionGeo - //bIsSimpleCollisionGeo - //bHasCollisionBeenAdded - //bHasSocketBeenAdded - } - - //bIsBox - //bIsSphere - - if (NewHGPO.SplitGroups.Num() <= 0) - { - NewHGPO.SplitGroups.Add("main_geo"); - } - - return NewHGPO; -} - -UHoudiniAssetInput::UHoudiniAssetInput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} -void -UHoudiniAssetInput::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Serialize current choice selection. - // Enum serialized as uint8 - // 0 GeometryInput - // 1 AssetInput - // 2 CurveInput - // 3 LandscapeInput - // 4 WorldInput - // 5 SkeletonInput - //SerializeEnumeration< EHoudiniAssetInputType::Enum >(Ar, ChoiceIndex); - Ar << ChoiceIndex; - - Ar << ChoiceStringValue; - - // We need these temporary variables for undo state tracking. - bool bLocalInputAssetConnectedInHoudini = bInputAssetConnectedInHoudini; - UHoudiniAssetComponent_V1 * LocalInputAssetComponent = InputAssetComponent; - - Ar << HoudiniAssetInputFlagsPacked; - - // Serialize input index. - Ar << InputIndex; - - // Serialize input objects (if it's assigned). - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT) - { - Ar << InputObjects; - } - else - { - UObject* InputObject = nullptr; - Ar << InputObject; - InputObjects.Empty(); - InputObjects.Add(InputObject); - } - - // Serialize input asset. - Ar << InputAssetComponent; - - // Serialize curve and curve parameters (if we have those). - Ar << InputCurve; - Ar << InputCurveParameters; - - // Serialize landscape used for input. - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT) - { - if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF) - { - ALandscapeProxy* InputLandscapePtr = nullptr; - Ar << InputLandscapePtr; - - InputLandscapeProxy = InputLandscapePtr; - } - else - { - Ar << InputLandscapeProxy; - } - - } - - // Serialize world outliner inputs. - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT) - { - Ar << InputOutlinerMeshArray; - } - - // Create necessary widget resources. - bLoadedParameter = true; - // If we're loading for real for the first time we need to reset this - // flag so we can reconnect when we get our parameters uploaded. - bInputAssetConnectedInHoudini = false; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT) - Ar << UnrealSplineResolution; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS) - { - Ar << InputTransforms; - } - else - { - InputTransforms.SetNum(InputObjects.Num()); - for (int32 n = 0; n < InputTransforms.Num(); n++) - InputTransforms[n] = FTransform::Identity; - } - - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - Ar << InputLandscapeTransform; -} - -UHoudiniInput* -UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) -{ - UHoudiniInput* Input = NewObject( - InOuter, UHoudiniInput::StaticClass(), FName(*ParameterLabel), RF_Transactional); - - EHoudiniInputType InputType = EHoudiniInputType::Invalid; - if (ChoiceIndex == 0) - InputType = EHoudiniInputType::Geometry; - else if (ChoiceIndex == 1) - InputType = EHoudiniInputType::Asset; - else if (ChoiceIndex == 2) - InputType = EHoudiniInputType::Curve; - else if (ChoiceIndex == 3) - InputType = EHoudiniInputType::Landscape; - else if (ChoiceIndex == 4) - InputType = EHoudiniInputType::World; - else if (ChoiceIndex == 5) - { - //InputType = EHoudiniInputType::Skeletal; - InputType = EHoudiniInputType::Invalid; - } - else - { - // Invalid - InputType = EHoudiniInputType::Invalid; - } - - bool bBlueprintStructureModified = false; - Input->SetInputType(InputType, bBlueprintStructureModified); - - Input->SetExportColliders(false); - Input->SetExportLODs(bExportAllLODs); - Input->SetExportSockets(bExportSockets); - Input->SetCookOnCurveChange(true); - - // If KeepWorldTransform is set to 2, use the default value - if (bKeepWorldTransform == 2) - Input->SetKeepWorldTransform((bool)Input->GetDefaultXTransformType()); - else - Input->SetKeepWorldTransform((bool)bKeepWorldTransform); - - Input->SetUnrealSplineResolution(UnrealSplineResolution); - Input->SetPackBeforeMerge(bPackBeforeMerge); - if(bIsObjectPathParameter) - Input->SetObjectPathParameter(ParmId); - else - Input->SetSOPInput(InputIndex); - - Input->SetImportAsReference(false); - Input->SetHelp(ParameterHelp); - //Input->SetInputNodeId(-1); - - if (bLandscapeExportAsHeightfield) - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Heightfield); - else if (bLandscapeExportAsMesh) - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Mesh); - else - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Points); - - Input->SetLabel(ParameterLabel); - Input->SetName(ParameterName); - Input->SetUpdateInputLandscape(bUpdateInputLandscape); - - if (InputType == EHoudiniInputType::Geometry) - { - // Get the geo input object array - bool bNeedToEmpty = true; - TArray* GeoInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(GeoInputObjectsPtr)) - { - // Add the geometry input objects - for (int32 AtIndex = 0; AtIndex < InputObjects.Num(); AtIndex++) - { - // Create a new InputObject wrapper - UObject* CurObject = InputObjects[AtIndex]; - if (!CurObject || CurObject->IsPendingKill()) - continue; - - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurObject, Input, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - continue; - - // Remove the default/null object - if (bNeedToEmpty) - { - GeoInputObjectsPtr->Empty(); - bNeedToEmpty = false; - } - - // Add to the geo input array - GeoInputObjectsPtr->Add(NewInputObject); - } - } - } - else if (InputType == EHoudiniInputType::Asset && InputAssetComponent != nullptr && !InputAssetComponent->IsPendingKill()) - { - // Get the asset input object array - TArray* AssetInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(AssetInputObjectsPtr)) - { - // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent - // We can simply use the v1's HAC outer for that - UHoudiniAssetComponent* InputHAC = Cast(InputAssetComponent->GetOuter()); - if (InputHAC && !InputHAC->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputHAC, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the asset input array - AssetInputObjectsPtr->Add(NewInputObject); - } - } - } - } - else if (InputType == EHoudiniInputType::Curve) - { - // Get the curve input object array - TArray* CurveInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(CurveInputObjectsPtr)) - { - if (InputCurve && !InputCurve->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputCurve, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the curve input array - CurveInputObjectsPtr->Add(NewInputObject); - } - - // InputCurve->SetInputObject(NewInputObject); - - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = Cast(NewInputObject); - if(HoudiniSplineInput) - HoudiniSplineInput->Update(InputCurve); - } - } - - // TODO ??? - //InputCurveParameters; - } - else if (InputType == EHoudiniInputType::Landscape) - { - // Get the Landscape input object array - TArray* LandscapeInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(LandscapeInputObjectsPtr)) - { - // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent - // We can simply use the v1's HAC outer for that - ALandscapeProxy* InLandscape = InputLandscapeProxy.Get(); - if (InLandscape && !InLandscape->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InLandscape, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the geo input array - LandscapeInputObjectsPtr->Add(NewInputObject); - } - } - } - - Input->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; - Input->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; - Input->bLandscapeExportLighting = bLandscapeExportLighting; - Input->bLandscapeExportMaterials = bLandscapeExportMaterials; - Input->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; - Input->bLandscapeExportTileUVs = bLandscapeExportTileUVs; - - //bLandscapeExportCurves; - } - else if (InputType == EHoudiniInputType::World) - { - // Get the world input object array - TArray* WorldInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - - UWorld* MyWorld = InOuter->GetWorld(); - if (ensure(WorldInputObjectsPtr)) - { - // Add the geometry input objects - for (int32 AtIndex = 0; AtIndex < InputOutlinerMeshArray.Num(); AtIndex++) - { - FHoudiniAssetInputOutlinerMesh_V1& CurWorldInObj = InputOutlinerMeshArray[AtIndex]; - - AActor* CurActor = nullptr; - if (CurWorldInObj.ActorPtr.IsValid()) - { - CurActor = CurWorldInObj.ActorPtr.Get(); - } - else - { - // Try to update the actor ptr via the pathname - CurWorldInObj.TryToUpdateActorPtrFromActorPathName(MyWorld); - if (CurWorldInObj.ActorPtr.IsValid()) - { - CurActor = CurWorldInObj.ActorPtr.Get(); - } - } - - if(!CurActor || CurActor->IsPendingKill()) - continue; - - // Create a new InputObject wrapper for the actor - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurActor, Input, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - continue; - - // Add to the geo input array - WorldInputObjectsPtr->Add(NewInputObject); - } - } - - /* - CurWorldInObj->HoudiniAssetParameterVersion; - CurWorldInObj->ActorPtr; - CurWorldInObj->ActorPathName; - CurWorldInObj->StaticMeshComponent; - CurWorldInObj->StaticMesh; - CurWorldInObj->SplineComponent; - CurWorldInObj->NumberOfSplineControlPoints; - CurWorldInObj->SplineControlPointsTransform; - CurWorldInObj->SplineLength; - CurWorldInObj->SplineResolution; - CurWorldInObj->ActorTransform; - CurWorldInObj->ComponentTransform; - CurWorldInObj->AssetId; - CurWorldInObj->KeepWorldTransform; - CurWorldInObj->MeshComponentsMaterials; - CurWorldInObj->InstanceIndex; - */ - - //InputOutlinerMeshArray; - } - else - { - // Invalid - } - - //ChoiceStringValue; - //bStaticMeshChanged; - //bSwitchedToCurve; - //bLoadedParameter = true; - //bInputAssetConnectedInHoudini; - //InputTransforms; - //InputLandscapeTransform; - - return Input; -} - -FArchive& -operator<<(FArchive& Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh) -{ - HoudiniAssetInputOutlinerMesh.Serialize(Ar); - return Ar; -} - -void -FHoudiniAssetInputOutlinerMesh_V1::Serialize(FArchive& Ar) -{ - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniAssetParameterVersion; - - Ar << ActorPtr; - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - { - Ar << ActorPathName; - } - - if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) - { - Ar << StaticMeshComponent; - Ar << StaticMesh; - } - - Ar << ActorTransform; - - Ar << AssetId; - if (Ar.IsLoading() && !Ar.IsTransacting()) - AssetId = -1; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE - && HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) - { - Ar << SplineComponent; - Ar << NumberOfSplineControlPoints; - Ar << SplineLength; - Ar << SplineResolution; - Ar << ComponentTransform; - } - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM) - Ar << KeepWorldTransform; - - // UE4.19 SERIALIZATION FIX: - // The component materials serialization (24) was actually missing in the UE4.19 H17.0 / H16.5 plugin. - // However subsequent serialized changes (25+) were present in those version. This caused crashes when loading - // a level that was saved with 4.19+16.5/17.0 on a newer version of Unreal or Houdini... - // If the serialized version is exactly that of the fix, we can ignore the materials paths as well - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - Ar << MeshComponentsMaterials; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX) - Ar << InstanceIndex; -} - -bool -FHoudiniAssetInputOutlinerMesh_V1::TryToUpdateActorPtrFromActorPathName(UWorld* InWorld) -{ - // Ensure our current ActorPathName looks valid - if (ActorPathName.IsEmpty() || ActorPathName.Equals(TEXT("None"), ESearchCase::IgnoreCase)) - return false; - - // We'll try to find the corresponding actor by browsing through all the actors in the world.. - // Get the editor world - UWorld* World = InWorld; - if (!World) - return false; - - // Then try to find the actor corresponding to our path/name - bool FoundActor = false; - for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) - { - if (ActorIt->GetPathName() != ActorPathName) - continue; - - // We found the actor - ActorPtr = *ActorIt; - FoundActor = true; - - break; - } - - if (FoundActor) - { - // We need to invalid our components so they can be updated later - // from the new actor - StaticMesh = NULL; - StaticMeshComponent = NULL; - SplineComponent = NULL; - } - - return FoundActor; -} - -UHoudiniAssetComponentMaterials_V1::UHoudiniAssetComponentMaterials_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} -void -UHoudiniAssetComponentMaterials_V1::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Assignments; - Ar << Replacements; -} - -UHoudiniHandleComponent_V1::UHoudiniHandleComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniAssetInstanceInput::UHoudiniAssetInstanceInput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetInstanceInput::Serialize(FArchive& Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Flags.HoudiniAssetInstanceInputFlagsPacked; - Ar << HoudiniGeoPartObject; - - Ar << ObjectToInstanceId; - // Object id is transient - if (Ar.IsLoading() && !Ar.IsTransacting()) - ObjectToInstanceId = -1; - - // Serialize fields. - Ar << InstanceInputFields; -} - -UHoudiniAssetInstanceInputField::UHoudiniAssetInstanceInputField(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetInstanceInputField::Serialize(FArchive& Ar) -{ - // Call base implementation first. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - const int32 InstanceInputFieldVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - - Ar << HoudiniAssetInstanceInputFieldFlagsPacked; - Ar << HoudiniGeoPartObject; - - FString UnusedInstancePathName; - Ar << UnusedInstancePathName; - Ar << RotationOffsets; - Ar << ScaleOffsets; - Ar << bScaleOffsetsLinearlyArray; - - Ar << InstancedTransforms; - Ar << VariationTransformsArray; - - if (Ar.IsSaving() || (Ar.IsLoading() && InstanceInputFieldVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS)) - { - Ar << InstanceColorOverride; - Ar << VariationInstanceColorOverrideArray; - } - - Ar << InstancerComponents; - Ar << InstancedObjects; - Ar << OriginalObject; -} - -void -UHoudiniHandleComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // XFormn Parames is an array of 9 float params + tuple index - // TX TY TZ - // RX RY RZ - // SX SY SZ - - //UHoudiniAssetParameterFloat_V1* XFormParams[9]; - //int32 XFormParamsTupleIndex[9]; - for (int32 i = 0; i < 9; ++i) - { - Ar << XFormParams[i]; - Ar << XFormParamsTupleIndex[i]; - } - - //UHoudiniAssetParameterChoice_V1* RSTParm; - Ar << RSTParm; - //int32 RSTParmTupleIdx; - Ar << RSTParmTupleIdx; - - //UHoudiniAssetParameterChoice_V1* RotOrderParm; - Ar << RotOrderParm; - //int32 RotOrderParmTupleIdx; - Ar << RotOrderParmTupleIdx; -} - -/* -UHoudiniHandleComponent* -UHoudiniHandleComponent_V1::ConvertLegacyData(UObject* Outer) -{ - UHoudiniHandleComponent* NewHandle = nullptr; - - return NewHandle; -} -*/ - -bool -UHoudiniHandleComponent_V1::UpdateFromLegacyData(UHoudiniHandleComponent* NewHC) -{ - if (!NewHC || NewHC->IsPendingKill()) - return false; - - // TODO - //NewHC->XformParms; - //NewHC->RSTParm; - //NewHC->RotOrderParm; - //NewHC->HandleType; - //NewHC->HandleName; - - return true; -} - -UHoudiniSplineComponent_V1::UHoudiniSplineComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniSplineComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - int32 Version = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << Version; - - Ar << HoudiniGeoPartObject; - - if (Version < VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM) - { - // Before, curve points where stored as Vectors, not Transforms - TArray OldCurvePoints; - Ar << OldCurvePoints; - - CurvePoints.SetNumUninitialized(OldCurvePoints.Num()); - - FTransform trans = FTransform::Identity; - for (int32 n = 0; n < CurvePoints.Num(); n++) - { - trans.SetLocation(OldCurvePoints[n]); - CurvePoints[n] = trans; - } - } - else - { - Ar << CurvePoints; - } - - Ar << CurveDisplayPoints; - - Ar << CurveType; - Ar << CurveMethod; - Ar << bClosedCurve; -} - -UHoudiniSplineComponent* -UHoudiniSplineComponent_V1::ConvertLegacyData(UObject* Outer) -{ - UHoudiniSplineComponent* NewSpline = NewObject( - GetOuter(), UHoudiniSplineComponent::StaticClass()); - - UpdateFromLegacyData(NewSpline); - - return NewSpline; -} - -bool -UHoudiniSplineComponent_V1::UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline) -{ - if (!NewSpline || NewSpline->IsPendingKill()) - return false; - - NewSpline->SetFlags(RF_Transactional); - - NewSpline->CurvePoints = CurvePoints; - NewSpline->DisplayPoints = CurveDisplayPoints; - //NewSpline->DisplayPointIndexDivider; - //NewSpline->HoudiniSplineName; - NewSpline->bClosed = bClosedCurve; - NewSpline->bReversed = false; - NewSpline->bIsHoudiniSplineVisible = true; - - //0 Polygon 1 Nurbs 2 Bezier - if (CurveType == 0) - NewSpline->CurveType = EHoudiniCurveType::Polygon; - else if (CurveType == 1) - NewSpline->CurveType = EHoudiniCurveType::Nurbs; - else if (CurveType == 2) - NewSpline->CurveType = EHoudiniCurveType::Bezier; - else - NewSpline->CurveType = EHoudiniCurveType::Invalid; - - // 0 CVs, 1 Breakpoints, 2 Freehand - if (CurveMethod == 0) - NewSpline->CurveMethod = EHoudiniCurveMethod::CVs; - else if (CurveMethod == 1) - NewSpline->CurveMethod = EHoudiniCurveMethod::Breakpoints; - else if (CurveMethod == 2) - NewSpline->CurveMethod = EHoudiniCurveMethod::Freehand; - else - NewSpline->CurveMethod = EHoudiniCurveMethod::Invalid; - - NewSpline->bIsOutputCurve = false; - - NewSpline->HoudiniGeoPartObject = HoudiniGeoPartObject.ConvertLegacyData(); - - if (NewSpline->HoudiniGeoPartObject.bIsEditable) - { - NewSpline->bIsEditableOutputCurve = true; - NewSpline->bIsInputCurve = false; - } - else - { - NewSpline->bIsInputCurve = false; - NewSpline->bIsEditableOutputCurve = true; - } - - // Create a default Houdini spline input if a null pointer is passed in. - FName HoudiniSplineName = MakeUniqueObjectName(GetOuter(), UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); - NewSpline->SetHoudiniSplineName(HoudiniSplineName.ToString()); - - //NewSpline->bHasChanged; - //NewSpline->bNeedsToTriggerUpdate; - //NewSpline->InputObject; - //NewSpline->NodeId; - //NewSpline->PartName; - - return true; -} - -UHoudiniAssetParameter::UHoudiniAssetParameter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameter::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniAssetParameterVersion; - - Ar << HoudiniAssetParameterFlagsPacked; - - if (Ar.IsLoading()) - bChanged = false; - - Ar << ParameterName; - Ar << ParameterLabel; - - Ar << NodeId; - if (!Ar.IsTransacting() && Ar.IsLoading()) - { - // NodeId is invalid after load - NodeId = -1; - } - Ar << ParmId; - - Ar << ParmParentId; - Ar << ChildIndex; - - Ar << TupleSize; - Ar << ValuesIndex; - Ar << MultiparmInstanceIndex; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER) - { - UObject* Dummy = nullptr; - Ar << Dummy; - } - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP) - { - Ar << ParameterHelp; - } - else - { - ParameterHelp = TEXT(""); - } - /* - if (Ar.IsTransacting()) - { - Ar << PrimaryObject; - Ar << ParentParameter; - } - */ -} - -UHoudiniParameter* -UHoudiniAssetParameter::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameter::Create(Outer, ParameterName); -} - -void -UHoudiniAssetParameter::CopyLegacyParameterData(UHoudiniParameter* InNewParm) -{ - if (!InNewParm || InNewParm->IsPendingKill()) - return; - - InNewParm->Name = ParameterName; - InNewParm->Label = ParameterLabel; - //InNewParm->ParmType; - InNewParm->TupleSize = TupleSize; - InNewParm->NodeId = NodeId; - InNewParm->ParmId = ParmId; - InNewParm->ParentParmId = ParmParentId; - InNewParm->ChildIndex = ChildIndex; - InNewParm->bIsVisible = true; - InNewParm->bIsDisabled = bIsDisabled; - InNewParm->bHasChanged = bChanged; - //InNewParm->bNeedsToTriggerUpdate; - //InNewParm->bIsDefault; - InNewParm->bIsSpare = bIsSpare; - InNewParm->bJoinNext = false; - InNewParm->bIsChildOfMultiParm = bIsChildOfMultiparm; - // TODO: MultiparmInstanceIndex ? - //InNewParm->bIsDirectChildOfMultiParm; - InNewParm->bPendingRevertToDefault = false; - //InNewParm->TuplePendingRevertToDefault = false; - InNewParm->Help = ParameterHelp; - InNewParm->TagCount = 0; - InNewParm->ValueIndex = ValuesIndex; - //InNewParm->bHasExpression; - //InNewParm->bShowExpression; - //InNewParm->ParamExpression; - //InNewParm->Tags; - InNewParm->bAutoUpdate = true; -} - -UHoudiniAssetParameterChoice::UHoudiniAssetParameterChoice(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterChoice::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - { - StringChoiceValues.Empty(); - StringChoiceLabels.Empty(); - } - - int32 NumChoices = StringChoiceValues.Num(); - Ar << NumChoices; - - int32 NumLabels = StringChoiceLabels.Num(); - Ar << NumLabels; - - if (Ar.IsLoading()) - { - FString Temp; - for (int32 ChoiceIdx = 0; ChoiceIdx < NumChoices; ++ChoiceIdx) - { - Ar << Temp; - StringChoiceValues.Add(Temp); - } - - for (int32 LabelIdx = 0; LabelIdx < NumLabels; ++LabelIdx) - { - Ar << Temp; - StringChoiceLabels.Add(Temp); - } - } - - Ar << StringValue; - Ar << CurrentValue; - - Ar << bStringChoiceList; -} - -UHoudiniParameter* -UHoudiniAssetParameterChoice::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterChoice* Parm = nullptr; - if (bStringChoiceList) - { - Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::StringChoice); - } - else - { - Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::IntChoice); - } - - Parm->SetNumChoices(StringChoiceValues.Num()); - for (int32 Idx = 0; Idx < StringChoiceValues.Num(); Idx++) - { - FString * ChoiceValue = Parm->GetStringChoiceValueAt(Idx); - if (ChoiceValue) - *ChoiceValue = StringChoiceValues[Idx]; - FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); - } - - for (int32 Idx = 0; Idx < StringChoiceLabels.Num(); Idx++) - { - FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); - if (ChoiceLabel) - *ChoiceLabel = StringChoiceValues[Idx]; - } - - Parm->SetStringValue(StringValue); - Parm->SetIntValue(CurrentValue); - //Parm->DefaultStringValue = StringValue; - //Parm->SetDefault(); - //Parm->DefaultIntValue = CurrentValue; - - return Parm; -} - -UHoudiniAssetParameterButton::UHoudiniAssetParameterButton(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterButton::ConvertLegacyData(UObject* Outer) -{ - // Button strips where not supported in v1, just create a normal button - return UHoudiniParameterButton::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterColor::UHoudiniAssetParameterColor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterColor::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - Color = FColor::White; - - Ar << Color; -} - -UHoudiniParameter* -UHoudiniAssetParameterColor::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterColor* Parm = UHoudiniParameterColor::Create(Outer, ParameterName); - Parm->SetColorValue(Color); - - //Parm->DefaultColor = Color; - Parm->SetDefaultValue(); - - //Parm->bIsChildOfRamp = false; - - return Parm; -} - -UHoudiniAssetParameterFile::UHoudiniAssetParameterFile(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterFile::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - Ar << Filters; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY) - Ar << IsReadOnly; -} - -UHoudiniParameter* -UHoudiniAssetParameterFile::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterFile* Parm = UHoudiniParameterFile::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - Parm->SetFileFilters(Filters); - Parm->SetReadOnly(IsReadOnly); - - return Parm; -} - -UHoudiniAssetParameterFloat::UHoudiniAssetParameterFloat(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterFloat::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - - Ar << ValueMin; - Ar << ValueMax; - - Ar << ValueUIMin; - Ar << ValueUIMax; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) - Ar << ValueUnit; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP) - Ar << NoSwap; -} - -UHoudiniParameter* -UHoudiniAssetParameterFloat::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterFloat* Parm = UHoudiniParameterFloat::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - Parm->SetUnit(ValueUnit); - Parm->SetHasMin(true); - Parm->SetHasMax(true); - Parm->SetHasUIMin(true); - Parm->SetHasUIMax(true); - Parm->SetIsLogarithmic(false); - Parm->SetMin(ValueMin); - Parm->SetMax(ValueMax); - Parm->SetUIMin(ValueUIMin); - Parm->SetUIMax(ValueUIMax); - - //Parm->bIsChildOfRamp = false; - - return Parm; -} - -UHoudiniAssetParameterFolder::UHoudiniAssetParameterFolder(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterFolder::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterFolder::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterFolderList::UHoudiniAssetParameterFolderList(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterFolderList::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterFolderList::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterInt::UHoudiniAssetParameterInt(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterInt::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - - Ar << ValueMin; - Ar << ValueMax; - - Ar << ValueUIMin; - Ar << ValueUIMax; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) - Ar << ValueUnit; -} - -UHoudiniParameter* -UHoudiniAssetParameterInt::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterInt* Parm = UHoudiniParameterInt::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - - //Parm->DefaultValues = Values; - Parm->SetUnit(ValueUnit); - Parm->SetHasMin(true); - Parm->SetHasMax(true); - Parm->SetHasUIMin(true); - Parm->SetHasUIMax(true); - Parm->SetIsLogarithmic(false); - Parm->SetMin(ValueMin); - Parm->SetMax(ValueMax); - Parm->SetUIMin(ValueUIMin); - Parm->SetUIMax(ValueUIMax); - - return Parm; -} - -UHoudiniAssetParameterLabel::UHoudiniAssetParameterLabel(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterLabel::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterLabel::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterMultiparm::UHoudiniAssetParameterMultiparm(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterMultiparm::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - MultiparmValue = 0; - - Ar << MultiparmValue; -} - -UHoudiniParameter* -UHoudiniAssetParameterMultiparm::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterMultiParm* Parm = UHoudiniParameterMultiParm::Create(Outer, ParameterName); - - //Parm->bIsShown; - //Parm->Value; - //Parm->TemplateName; - Parm->MultiparmValue = MultiparmValue; - //Parm->MultiParmInstanceNum; - //Parm->MultiParmInstanceLength; - //Parm->MultiParmInstanceCount; - //Parm->InstanceStartOffset; - //Parm->DefaultInstanceCount; - - // TODO: - // MultiparmInstanceIndex? - - return Parm; -} - -UHoudiniAssetParameterRamp::UHoudiniAssetParameterRamp(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterRamp::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - int32 multiparmvalue = 0; - Ar << multiparmvalue; - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << HoudiniAssetParameterRampCurveFloat; - Ar << HoudiniAssetParameterRampCurveColor; - - Ar << bIsFloatRamp; -} - -UHoudiniParameter* -UHoudiniAssetParameterRamp::ConvertLegacyData(UObject* Outer) -{ - if (bIsFloatRamp) - { - UHoudiniParameterRampFloat* Parm = UHoudiniParameterRampFloat::Create(Outer, ParameterName); - - // TODO: - // Convert HoudiniAssetParameterRampCurveFloat - - return Parm; - } - else - { - UHoudiniParameterRampColor* Parm = UHoudiniParameterRampColor::Create(Outer, ParameterName); - - // TODO: - // Convert HoudiniAssetParameterRampCurveColor - - return Parm; - } -} - -UHoudiniAssetParameterSeparator::UHoudiniAssetParameterSeparator(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterSeparator::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterSeparator::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterString::UHoudiniAssetParameterString(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterString::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; -} - -UHoudiniParameter* -UHoudiniAssetParameterString::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterString* Parm = UHoudiniParameterString::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetIsAssetRef(false); - Parm->SetDefaultValues(); - - //Parm->DefaultValues = Values; - //Parm->ChosenAssets.Empty(); - //Parm->bIsAssetRef = false; - - return Parm; -} - -UHoudiniAssetParameterToggle::UHoudiniAssetParameterToggle(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterToggle::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; -} - -UHoudiniParameter* -UHoudiniAssetParameterToggle::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterToggle* Parm = UHoudiniParameterToggle::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt((bool)Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - return Parm; -} - -UHoudiniMeshSplitInstancerComponent_V1::UHoudiniMeshSplitInstancerComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniMeshSplitInstancerComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << InstancedMesh; - Ar << OverrideMaterial; - Ar << Instances; -} - -bool -UHoudiniMeshSplitInstancerComponent_V1::UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC) -{ - if (!NewMSIC || NewMSIC->IsPendingKill()) - return false; - - NewMSIC->Instances = Instances; - NewMSIC->OverrideMaterials.Add(OverrideMaterial); - NewMSIC->InstancedMesh = InstancedMesh; - - return true; -} - -UHoudiniInstancedActorComponent_V1::UHoudiniInstancedActorComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniInstancedActorComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << InstancedAsset; - Ar << Instances; -} - -bool -UHoudiniInstancedActorComponent_V1::UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC) -{ - if (!NewIAC || NewIAC->IsPendingKill()) - return false; - - //NewIAC->SetInstancedObject(InstancedAsset); - NewIAC->InstancedObject = InstancedAsset; - NewIAC->InstancedActors = Instances; - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniCompatibilityHelpers.h" + +#include "HoudiniPluginSerializationVersion.h" + +#include "HoudiniInput.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniHandleComponent.h" + +#include "Engine/StaticMesh.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "EngineUtils.h" // for TActorIterator<> + +#if WITH_EDITOR + #include "Editor.h" + #include "Editor/UnrealEdEngine.h" + #include "UnrealEdGlobals.h" +#endif + +// TODO: +// HoudiniInstancedActorComponent ? +// HoudiniMeshSplitInstancerComponent ? + +UHoudiniAssetComponent_V1::UHoudiniAssetComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + + +void +UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) +{ + if (!Ar.IsLoading()) + return; + + //Super::Serialize(Ar); + + //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Serialize component flags. + Ar << HoudiniAssetComponentFlagsPacked; + + // Serialize format version. + uint32 HoudiniAssetComponentVersion = 0;//Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + Ar << HoudiniAssetComponentVersion; + + // ComponenState Enum, saved as uint8 + // 0 invalid + // 1 None + // 2 Instantiated + // 3 BeingCooked + // Serialize component state. + uint8 ComponentState = 0; + Ar << ComponentState; + + // Serialize scaling information and import axis. + Ar << GeneratedGeometryScaleFactor; + Ar << TransformScaleFactor; + + // ImportAxis Enum, saved as uint8 + // 0 unreal 1 Houdini + //uint8 ImportAxis = 0; + Ar << ImportAxis; + + // Serialize generated component GUID. + Ar << ComponentGUID; + + // If component is in invalid state, we can skip the rest of serialization. + //if (ComponentState == EHoudiniAssetComponentState_V1::Invalid) + if (ComponentState == 0) + return; + + // Serialize Houdini asset. + Ar << HoudiniAsset; + + // 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; + int TempId; + Ar << TempId; + bIsPlayModeActive_Unused = false; + } + + // Serialization of default preset. + Ar << DefaultPresetBuffer; + + // Serialization of preset. + { + bool bPresetSaved = false; + Ar << bPresetSaved; + + if (bPresetSaved) + { + Ar << PresetBuffer; + } + } + + // Serialize parameters. + //SerializeParameters(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::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; + } + + // 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::TIterator IterParams(Parameters); IterParams; ++IterParams) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill()) + ParameterByName.Add(HoudiniAssetParameter->ParameterName, HoudiniAssetParameter); + } + } + } + + // Serialize inputs. + //SerializeInputs(Ar); + { + Ar << Inputs; + + /* + if (Ar.IsLoading()) + { + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) + { + UHoudiniAssetInput_V1 * HoudiniAssetInput = Inputs[InputIdx]; + if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) + Inputs[InputIdx]->SetHoudiniAssetComponent(this); + } + } + */ + } + + // 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); + { + //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; + int HoudiniInstanceInputKey = -1; + + Ar << HoudiniInstanceInputKey; + Ar << InstanceInputs[InstanceInputIdx]; + } + } + } + + // 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 DirtyPackages; + if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES) + { + TMap SavedPackages; + Ar << SavedPackages; + } + + if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS) + { + // Temporary Mesh Packages + TMap MeshPackages; + Ar << MeshPackages; + + // Temporary Landscape Layers Packages + TMap LayerPackages; + Ar << LayerPackages; + } +} + +uint32 +GetTypeHash(const FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) +{ + return HoudiniGeoPartObject.GetTypeHash(); +} + +FArchive & +operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) +{ + HoudiniGeoPartObject.Serialize(Ar); + return Ar; +} + +uint32 +FHoudiniGeoPartObject_V1::GetTypeHash() const +{ + int32 HashBuffer[4] = { ObjectId, GeoId, PartId, SplitId }; + int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitName, Hash); +} + +bool +FHoudiniGeoPartObject_V1SortPredicate::operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const +{ + /*if (!A.IsValid() || !B.IsValid()) + return false;*/ + + if (A.ObjectId == B.ObjectId) + { + if (A.GeoId == B.GeoId) + { + if (A.PartId == B.PartId) + return A.SplitId < B.SplitId; + else + return A.PartId < B.PartId; + } + else + { + return A.GeoId < B.GeoId; + } + } + + return A.ObjectId < B.ObjectId; +} + +void +FHoudiniGeoPartObject_V1::Serialize(FArchive & Ar) +{ + //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniGeoPartObjectVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniGeoPartObjectVersion; + + Ar << TransformMatrix; + + Ar << ObjectName; + Ar << PartName; + Ar << SplitName; + + // Serialize instancer material. + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME) + Ar << InstancerMaterialName; + + // Serialize instancer attribute material. + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME) + Ar << InstancerAttributeMaterialName; + + Ar << AssetId; + Ar << ObjectId; + Ar << GeoId; + Ar << PartId; + Ar << SplitId; + + Ar << HoudiniGeoPartObjectFlagsPacked; + + if (HoudiniGeoPartObjectVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + // Prior to this version the unused flags space was not zero-initialized, so + // zero them out now to prevent misinterpreting any 1s + HoudiniGeoPartObjectFlagsPacked &= 0x3FFFF; + } + + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH) + { + Ar << NodePath; + } +} + + +FHoudiniGeoPartObject +FHoudiniGeoPartObject_V1::ConvertLegacyData() +{ + FHoudiniGeoPartObject NewHGPO; + + NewHGPO.AssetId = AssetId; + // NewHGPO.AssetName; + + NewHGPO.ObjectId = ObjectId; + NewHGPO.ObjectName = ObjectName; + + NewHGPO.GeoId = GeoId; + + NewHGPO.PartId = PartId; + NewHGPO.PartName = PartName; + NewHGPO.bHasCustomPartName = bHasCustomName; + + NewHGPO.SplitGroups.Add(SplitName); + + NewHGPO.TransformMatrix = TransformMatrix; + NewHGPO.NodePath = NodePath; + + // NewHGPO.VolumeName; + // NewHGPO.VolumeTileIndex; + + NewHGPO.bIsVisible = bIsVisible; + NewHGPO.bIsEditable = bIsEditable; + // NewHGPO.bIsTemplated; + // NewHGPO.bIsInstanced; + + NewHGPO.bHasGeoChanged = bHasGeoChanged; + // NewHGPO.bHasPartChanged; + // NewHGPO.bHasTransformChanged; + // NewHGPO.bHasMaterialsChanged; + NewHGPO.bLoaded = true; //bIsLoaded; + + // Hamdle Part Type + if (bIsCurve) + { + NewHGPO.Type = EHoudiniPartType::Curve; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + else if (bIsVolume) + { + NewHGPO.Type = EHoudiniPartType::Volume; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + else if (bIsInstancer) + { + NewHGPO.Type = EHoudiniPartType::Instancer; + NewHGPO.InstancerType = EHoudiniInstancerType::ObjectInstancer; + } + else if (bIsPackedPrimitiveInstancer) + { + NewHGPO.Type = EHoudiniPartType::Instancer; + NewHGPO.InstancerType = EHoudiniInstancerType::PackedPrimitive; + } + else + { + NewHGPO.Type = EHoudiniPartType::Mesh; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + + // Instancer specific flags + if (NewHGPO.Type == EHoudiniPartType::Instancer) + { + //bInstancerMaterialAvailable + //bInstancerAttributeMaterialAvailable + //InstancerMaterialName + //InstancerAttributeMaterialName + } + + // Collision specific flags + if (NewHGPO.Type == EHoudiniPartType::Mesh) + { + //bIsCollidable + //bIsRenderCollidable + //bIsUCXCollisionGeo + //bIsSimpleCollisionGeo + //bHasCollisionBeenAdded + //bHasSocketBeenAdded + } + + //bIsBox + //bIsSphere + + if (NewHGPO.SplitGroups.Num() <= 0) + { + NewHGPO.SplitGroups.Add("main_geo"); + } + + return NewHGPO; +} + +UHoudiniAssetInput::UHoudiniAssetInput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} +void +UHoudiniAssetInput::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Serialize current choice selection. + // Enum serialized as uint8 + // 0 GeometryInput + // 1 AssetInput + // 2 CurveInput + // 3 LandscapeInput + // 4 WorldInput + // 5 SkeletonInput + //SerializeEnumeration< EHoudiniAssetInputType::Enum >(Ar, ChoiceIndex); + Ar << ChoiceIndex; + + Ar << ChoiceStringValue; + + // We need these temporary variables for undo state tracking. + bool bLocalInputAssetConnectedInHoudini = bInputAssetConnectedInHoudini; + UHoudiniAssetComponent_V1 * LocalInputAssetComponent = InputAssetComponent; + + Ar << HoudiniAssetInputFlagsPacked; + + // Serialize input index. + Ar << InputIndex; + + // Serialize input objects (if it's assigned). + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT) + { + Ar << InputObjects; + } + else + { + UObject* InputObject = nullptr; + Ar << InputObject; + InputObjects.Empty(); + InputObjects.Add(InputObject); + } + + // Serialize input asset. + Ar << InputAssetComponent; + + // Serialize curve and curve parameters (if we have those). + Ar << InputCurve; + Ar << InputCurveParameters; + + // Serialize landscape used for input. + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT) + { + if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF) + { + ALandscapeProxy* InputLandscapePtr = nullptr; + Ar << InputLandscapePtr; + + InputLandscapeProxy = InputLandscapePtr; + } + else + { + Ar << InputLandscapeProxy; + } + + } + + // Serialize world outliner inputs. + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT) + { + Ar << InputOutlinerMeshArray; + } + + // Create necessary widget resources. + bLoadedParameter = true; + // If we're loading for real for the first time we need to reset this + // flag so we can reconnect when we get our parameters uploaded. + bInputAssetConnectedInHoudini = false; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT) + Ar << UnrealSplineResolution; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS) + { + Ar << InputTransforms; + } + else + { + InputTransforms.SetNum(InputObjects.Num()); + for (int32 n = 0; n < InputTransforms.Num(); n++) + InputTransforms[n] = FTransform::Identity; + } + + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + Ar << InputLandscapeTransform; +} + +UHoudiniInput* +UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) +{ + UHoudiniInput* Input = NewObject( + InOuter, UHoudiniInput::StaticClass(), FName(*ParameterLabel), RF_Transactional); + + EHoudiniInputType InputType = EHoudiniInputType::Invalid; + if (ChoiceIndex == 0) + InputType = EHoudiniInputType::Geometry; + else if (ChoiceIndex == 1) + InputType = EHoudiniInputType::Asset; + else if (ChoiceIndex == 2) + InputType = EHoudiniInputType::Curve; + else if (ChoiceIndex == 3) + InputType = EHoudiniInputType::Landscape; + else if (ChoiceIndex == 4) + InputType = EHoudiniInputType::World; + else if (ChoiceIndex == 5) + { + //InputType = EHoudiniInputType::Skeletal; + InputType = EHoudiniInputType::Invalid; + } + else + { + // Invalid + InputType = EHoudiniInputType::Invalid; + } + + bool bBlueprintStructureModified = false; + Input->SetInputType(InputType, bBlueprintStructureModified); + + Input->SetExportColliders(false); + Input->SetExportLODs(bExportAllLODs); + Input->SetExportSockets(bExportSockets); + Input->SetCookOnCurveChange(true); + + // If KeepWorldTransform is set to 2, use the default value + if (bKeepWorldTransform == 2) + Input->SetKeepWorldTransform((bool)Input->GetDefaultXTransformType()); + else + Input->SetKeepWorldTransform((bool)bKeepWorldTransform); + + Input->SetUnrealSplineResolution(UnrealSplineResolution); + Input->SetPackBeforeMerge(bPackBeforeMerge); + if(bIsObjectPathParameter) + Input->SetObjectPathParameter(ParmId); + else + Input->SetSOPInput(InputIndex); + + Input->SetImportAsReference(false); + Input->SetHelp(ParameterHelp); + //Input->SetInputNodeId(-1); + + if (bLandscapeExportAsHeightfield) + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Heightfield); + else if (bLandscapeExportAsMesh) + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Mesh); + else + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Points); + + Input->SetLabel(ParameterLabel); + Input->SetName(ParameterName); + Input->SetUpdateInputLandscape(bUpdateInputLandscape); + + if (InputType == EHoudiniInputType::Geometry) + { + // Get the geo input object array + bool bNeedToEmpty = true; + TArray* GeoInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(GeoInputObjectsPtr)) + { + // Add the geometry input objects + for (int32 AtIndex = 0; AtIndex < InputObjects.Num(); AtIndex++) + { + // Create a new InputObject wrapper + UObject* CurObject = InputObjects[AtIndex]; + if (!CurObject || CurObject->IsPendingKill()) + continue; + + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurObject, Input, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + continue; + + // Remove the default/null object + if (bNeedToEmpty) + { + GeoInputObjectsPtr->Empty(); + bNeedToEmpty = false; + } + + // Add to the geo input array + GeoInputObjectsPtr->Add(NewInputObject); + } + } + } + else if (InputType == EHoudiniInputType::Asset && InputAssetComponent != nullptr && !InputAssetComponent->IsPendingKill()) + { + // Get the asset input object array + TArray* AssetInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(AssetInputObjectsPtr)) + { + // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent + // We can simply use the v1's HAC outer for that + UHoudiniAssetComponent* InputHAC = Cast(InputAssetComponent->GetOuter()); + if (InputHAC && !InputHAC->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputHAC, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the asset input array + AssetInputObjectsPtr->Add(NewInputObject); + } + } + } + } + else if (InputType == EHoudiniInputType::Curve) + { + // Get the curve input object array + TArray* CurveInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(CurveInputObjectsPtr)) + { + if (InputCurve && !InputCurve->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputCurve, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the curve input array + CurveInputObjectsPtr->Add(NewInputObject); + } + + // InputCurve->SetInputObject(NewInputObject); + + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = Cast(NewInputObject); + if(HoudiniSplineInput) + HoudiniSplineInput->Update(InputCurve); + } + } + + // TODO ??? + //InputCurveParameters; + } + else if (InputType == EHoudiniInputType::Landscape) + { + // Get the Landscape input object array + TArray* LandscapeInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(LandscapeInputObjectsPtr)) + { + // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent + // We can simply use the v1's HAC outer for that + ALandscapeProxy* InLandscape = InputLandscapeProxy.Get(); + if (InLandscape && !InLandscape->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InLandscape, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the geo input array + LandscapeInputObjectsPtr->Add(NewInputObject); + } + } + } + + Input->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; + Input->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; + Input->bLandscapeExportLighting = bLandscapeExportLighting; + Input->bLandscapeExportMaterials = bLandscapeExportMaterials; + Input->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; + Input->bLandscapeExportTileUVs = bLandscapeExportTileUVs; + + //bLandscapeExportCurves; + } + else if (InputType == EHoudiniInputType::World) + { + // Get the world input object array + TArray* WorldInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + + UWorld* MyWorld = InOuter->GetWorld(); + if (ensure(WorldInputObjectsPtr)) + { + // Add the geometry input objects + for (int32 AtIndex = 0; AtIndex < InputOutlinerMeshArray.Num(); AtIndex++) + { + FHoudiniAssetInputOutlinerMesh_V1& CurWorldInObj = InputOutlinerMeshArray[AtIndex]; + + AActor* CurActor = nullptr; + if (CurWorldInObj.ActorPtr.IsValid()) + { + CurActor = CurWorldInObj.ActorPtr.Get(); + } + else + { + // Try to update the actor ptr via the pathname + CurWorldInObj.TryToUpdateActorPtrFromActorPathName(MyWorld); + if (CurWorldInObj.ActorPtr.IsValid()) + { + CurActor = CurWorldInObj.ActorPtr.Get(); + } + } + + if(!CurActor || CurActor->IsPendingKill()) + continue; + + // Create a new InputObject wrapper for the actor + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurActor, Input, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + continue; + + // Add to the geo input array + WorldInputObjectsPtr->Add(NewInputObject); + } + } + + /* + CurWorldInObj->HoudiniAssetParameterVersion; + CurWorldInObj->ActorPtr; + CurWorldInObj->ActorPathName; + CurWorldInObj->StaticMeshComponent; + CurWorldInObj->StaticMesh; + CurWorldInObj->SplineComponent; + CurWorldInObj->NumberOfSplineControlPoints; + CurWorldInObj->SplineControlPointsTransform; + CurWorldInObj->SplineLength; + CurWorldInObj->SplineResolution; + CurWorldInObj->ActorTransform; + CurWorldInObj->ComponentTransform; + CurWorldInObj->AssetId; + CurWorldInObj->KeepWorldTransform; + CurWorldInObj->MeshComponentsMaterials; + CurWorldInObj->InstanceIndex; + */ + + //InputOutlinerMeshArray; + } + else + { + // Invalid + } + + //ChoiceStringValue; + //bStaticMeshChanged; + //bSwitchedToCurve; + //bLoadedParameter = true; + //bInputAssetConnectedInHoudini; + //InputTransforms; + //InputLandscapeTransform; + + return Input; +} + +FArchive& +operator<<(FArchive& Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh) +{ + HoudiniAssetInputOutlinerMesh.Serialize(Ar); + return Ar; +} + +void +FHoudiniAssetInputOutlinerMesh_V1::Serialize(FArchive& Ar) +{ + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniAssetParameterVersion; + + Ar << ActorPtr; + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + { + Ar << ActorPathName; + } + + if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) + { + Ar << StaticMeshComponent; + Ar << StaticMesh; + } + + Ar << ActorTransform; + + Ar << AssetId; + if (Ar.IsLoading() && !Ar.IsTransacting()) + AssetId = -1; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE + && HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) + { + Ar << SplineComponent; + Ar << NumberOfSplineControlPoints; + Ar << SplineLength; + Ar << SplineResolution; + Ar << ComponentTransform; + } + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM) + Ar << KeepWorldTransform; + + // UE4.19 SERIALIZATION FIX: + // The component materials serialization (24) was actually missing in the UE4.19 H17.0 / H16.5 plugin. + // However subsequent serialized changes (25+) were present in those version. This caused crashes when loading + // a level that was saved with 4.19+16.5/17.0 on a newer version of Unreal or Houdini... + // If the serialized version is exactly that of the fix, we can ignore the materials paths as well + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + Ar << MeshComponentsMaterials; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX) + Ar << InstanceIndex; +} + +bool +FHoudiniAssetInputOutlinerMesh_V1::TryToUpdateActorPtrFromActorPathName(UWorld* InWorld) +{ + // Ensure our current ActorPathName looks valid + if (ActorPathName.IsEmpty() || ActorPathName.Equals(TEXT("None"), ESearchCase::IgnoreCase)) + return false; + + // We'll try to find the corresponding actor by browsing through all the actors in the world.. + // Get the editor world + UWorld* World = InWorld; + if (!World) + return false; + + // Then try to find the actor corresponding to our path/name + bool FoundActor = false; + for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) + { + if (ActorIt->GetPathName() != ActorPathName) + continue; + + // We found the actor + ActorPtr = *ActorIt; + FoundActor = true; + + break; + } + + if (FoundActor) + { + // We need to invalid our components so they can be updated later + // from the new actor + StaticMesh = NULL; + StaticMeshComponent = NULL; + SplineComponent = NULL; + } + + return FoundActor; +} + +UHoudiniAssetComponentMaterials_V1::UHoudiniAssetComponentMaterials_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} +void +UHoudiniAssetComponentMaterials_V1::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Assignments; + Ar << Replacements; +} + +UHoudiniHandleComponent_V1::UHoudiniHandleComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniAssetInstanceInput::UHoudiniAssetInstanceInput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetInstanceInput::Serialize(FArchive& Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Flags.HoudiniAssetInstanceInputFlagsPacked; + Ar << HoudiniGeoPartObject; + + Ar << ObjectToInstanceId; + // Object id is transient + if (Ar.IsLoading() && !Ar.IsTransacting()) + ObjectToInstanceId = -1; + + // Serialize fields. + Ar << InstanceInputFields; +} + +UHoudiniAssetInstanceInputField::UHoudiniAssetInstanceInputField(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetInstanceInputField::Serialize(FArchive& Ar) +{ + // Call base implementation first. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + const int32 InstanceInputFieldVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + + Ar << HoudiniAssetInstanceInputFieldFlagsPacked; + Ar << HoudiniGeoPartObject; + + FString UnusedInstancePathName; + Ar << UnusedInstancePathName; + Ar << RotationOffsets; + Ar << ScaleOffsets; + Ar << bScaleOffsetsLinearlyArray; + + Ar << InstancedTransforms; + Ar << VariationTransformsArray; + + if (Ar.IsSaving() || (Ar.IsLoading() && InstanceInputFieldVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS)) + { + Ar << InstanceColorOverride; + Ar << VariationInstanceColorOverrideArray; + } + + Ar << InstancerComponents; + Ar << InstancedObjects; + Ar << OriginalObject; +} + +void +UHoudiniHandleComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // XFormn Parames is an array of 9 float params + tuple index + // TX TY TZ + // RX RY RZ + // SX SY SZ + + //UHoudiniAssetParameterFloat_V1* XFormParams[9]; + //int32 XFormParamsTupleIndex[9]; + for (int32 i = 0; i < 9; ++i) + { + Ar << XFormParams[i]; + Ar << XFormParamsTupleIndex[i]; + } + + //UHoudiniAssetParameterChoice_V1* RSTParm; + Ar << RSTParm; + //int32 RSTParmTupleIdx; + Ar << RSTParmTupleIdx; + + //UHoudiniAssetParameterChoice_V1* RotOrderParm; + Ar << RotOrderParm; + //int32 RotOrderParmTupleIdx; + Ar << RotOrderParmTupleIdx; +} + +/* +UHoudiniHandleComponent* +UHoudiniHandleComponent_V1::ConvertLegacyData(UObject* Outer) +{ + UHoudiniHandleComponent* NewHandle = nullptr; + + return NewHandle; +} +*/ + +bool +UHoudiniHandleComponent_V1::UpdateFromLegacyData(UHoudiniHandleComponent* NewHC) +{ + if (!NewHC || NewHC->IsPendingKill()) + return false; + + // TODO + //NewHC->XformParms; + //NewHC->RSTParm; + //NewHC->RotOrderParm; + //NewHC->HandleType; + //NewHC->HandleName; + + return true; +} + +UHoudiniSplineComponent_V1::UHoudiniSplineComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniSplineComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + int32 Version = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << Version; + + Ar << HoudiniGeoPartObject; + + if (Version < VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM) + { + // Before, curve points where stored as Vectors, not Transforms + TArray OldCurvePoints; + Ar << OldCurvePoints; + + CurvePoints.SetNumUninitialized(OldCurvePoints.Num()); + + FTransform trans = FTransform::Identity; + for (int32 n = 0; n < CurvePoints.Num(); n++) + { + trans.SetLocation(OldCurvePoints[n]); + CurvePoints[n] = trans; + } + } + else + { + Ar << CurvePoints; + } + + Ar << CurveDisplayPoints; + + Ar << CurveType; + Ar << CurveMethod; + Ar << bClosedCurve; +} + +UHoudiniSplineComponent* +UHoudiniSplineComponent_V1::ConvertLegacyData(UObject* Outer) +{ + UHoudiniSplineComponent* NewSpline = NewObject( + GetOuter(), UHoudiniSplineComponent::StaticClass()); + + UpdateFromLegacyData(NewSpline); + + return NewSpline; +} + +bool +UHoudiniSplineComponent_V1::UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline) +{ + if (!NewSpline || NewSpline->IsPendingKill()) + return false; + + NewSpline->SetFlags(RF_Transactional); + + NewSpline->CurvePoints = CurvePoints; + NewSpline->DisplayPoints = CurveDisplayPoints; + //NewSpline->DisplayPointIndexDivider; + //NewSpline->HoudiniSplineName; + NewSpline->bClosed = bClosedCurve; + NewSpline->bReversed = false; + NewSpline->bIsHoudiniSplineVisible = true; + + //0 Polygon 1 Nurbs 2 Bezier + if (CurveType == 0) + NewSpline->CurveType = EHoudiniCurveType::Polygon; + else if (CurveType == 1) + NewSpline->CurveType = EHoudiniCurveType::Nurbs; + else if (CurveType == 2) + NewSpline->CurveType = EHoudiniCurveType::Bezier; + else + NewSpline->CurveType = EHoudiniCurveType::Invalid; + + // 0 CVs, 1 Breakpoints, 2 Freehand + if (CurveMethod == 0) + NewSpline->CurveMethod = EHoudiniCurveMethod::CVs; + else if (CurveMethod == 1) + NewSpline->CurveMethod = EHoudiniCurveMethod::Breakpoints; + else if (CurveMethod == 2) + NewSpline->CurveMethod = EHoudiniCurveMethod::Freehand; + else + NewSpline->CurveMethod = EHoudiniCurveMethod::Invalid; + + NewSpline->bIsOutputCurve = false; + + NewSpline->HoudiniGeoPartObject = HoudiniGeoPartObject.ConvertLegacyData(); + + if (NewSpline->HoudiniGeoPartObject.bIsEditable) + { + NewSpline->bIsEditableOutputCurve = true; + NewSpline->bIsInputCurve = false; + } + else + { + NewSpline->bIsInputCurve = false; + NewSpline->bIsEditableOutputCurve = true; + } + + // Create a default Houdini spline input if a null pointer is passed in. + FName HoudiniSplineName = MakeUniqueObjectName(GetOuter(), UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); + NewSpline->SetHoudiniSplineName(HoudiniSplineName.ToString()); + + //NewSpline->bHasChanged; + //NewSpline->bNeedsToTriggerUpdate; + //NewSpline->InputObject; + //NewSpline->NodeId; + //NewSpline->PartName; + + return true; +} + +UHoudiniAssetParameter::UHoudiniAssetParameter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameter::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniAssetParameterVersion; + + Ar << HoudiniAssetParameterFlagsPacked; + + if (Ar.IsLoading()) + bChanged = false; + + Ar << ParameterName; + Ar << ParameterLabel; + + Ar << NodeId; + if (!Ar.IsTransacting() && Ar.IsLoading()) + { + // NodeId is invalid after load + NodeId = -1; + } + Ar << ParmId; + + Ar << ParmParentId; + Ar << ChildIndex; + + Ar << TupleSize; + Ar << ValuesIndex; + Ar << MultiparmInstanceIndex; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER) + { + UObject* Dummy = nullptr; + Ar << Dummy; + } + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP) + { + Ar << ParameterHelp; + } + else + { + ParameterHelp = TEXT(""); + } + /* + if (Ar.IsTransacting()) + { + Ar << PrimaryObject; + Ar << ParentParameter; + } + */ +} + +UHoudiniParameter* +UHoudiniAssetParameter::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameter::Create(Outer, ParameterName); +} + +void +UHoudiniAssetParameter::CopyLegacyParameterData(UHoudiniParameter* InNewParm) +{ + if (!InNewParm || InNewParm->IsPendingKill()) + return; + + InNewParm->Name = ParameterName; + InNewParm->Label = ParameterLabel; + //InNewParm->ParmType; + InNewParm->TupleSize = TupleSize; + InNewParm->NodeId = NodeId; + InNewParm->ParmId = ParmId; + InNewParm->ParentParmId = ParmParentId; + InNewParm->ChildIndex = ChildIndex; + InNewParm->bIsVisible = true; + InNewParm->bIsDisabled = bIsDisabled; + InNewParm->bHasChanged = bChanged; + //InNewParm->bNeedsToTriggerUpdate; + //InNewParm->bIsDefault; + InNewParm->bIsSpare = bIsSpare; + InNewParm->bJoinNext = false; + InNewParm->bIsChildOfMultiParm = bIsChildOfMultiparm; + // TODO: MultiparmInstanceIndex ? + //InNewParm->bIsDirectChildOfMultiParm; + InNewParm->bPendingRevertToDefault = false; + //InNewParm->TuplePendingRevertToDefault = false; + InNewParm->Help = ParameterHelp; + InNewParm->TagCount = 0; + InNewParm->ValueIndex = ValuesIndex; + //InNewParm->bHasExpression; + //InNewParm->bShowExpression; + //InNewParm->ParamExpression; + //InNewParm->Tags; + InNewParm->bAutoUpdate = true; +} + +UHoudiniAssetParameterChoice::UHoudiniAssetParameterChoice(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterChoice::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + { + StringChoiceValues.Empty(); + StringChoiceLabels.Empty(); + } + + int32 NumChoices = StringChoiceValues.Num(); + Ar << NumChoices; + + int32 NumLabels = StringChoiceLabels.Num(); + Ar << NumLabels; + + if (Ar.IsLoading()) + { + FString Temp; + for (int32 ChoiceIdx = 0; ChoiceIdx < NumChoices; ++ChoiceIdx) + { + Ar << Temp; + StringChoiceValues.Add(Temp); + } + + for (int32 LabelIdx = 0; LabelIdx < NumLabels; ++LabelIdx) + { + Ar << Temp; + StringChoiceLabels.Add(Temp); + } + } + + Ar << StringValue; + Ar << CurrentValue; + + Ar << bStringChoiceList; +} + +UHoudiniParameter* +UHoudiniAssetParameterChoice::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterChoice* Parm = nullptr; + if (bStringChoiceList) + { + Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::StringChoice); + } + else + { + Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::IntChoice); + } + + Parm->SetNumChoices(StringChoiceValues.Num()); + for (int32 Idx = 0; Idx < StringChoiceValues.Num(); Idx++) + { + FString * ChoiceValue = Parm->GetStringChoiceValueAt(Idx); + if (ChoiceValue) + *ChoiceValue = StringChoiceValues[Idx]; + FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); + } + + for (int32 Idx = 0; Idx < StringChoiceLabels.Num(); Idx++) + { + FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); + if (ChoiceLabel) + *ChoiceLabel = StringChoiceValues[Idx]; + } + + Parm->SetStringValue(StringValue); + Parm->SetIntValue(CurrentValue); + //Parm->DefaultStringValue = StringValue; + //Parm->SetDefault(); + //Parm->DefaultIntValue = CurrentValue; + + return Parm; +} + +UHoudiniAssetParameterButton::UHoudiniAssetParameterButton(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterButton::ConvertLegacyData(UObject* Outer) +{ + // Button strips where not supported in v1, just create a normal button + return UHoudiniParameterButton::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterColor::UHoudiniAssetParameterColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterColor::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + Color = FColor::White; + + Ar << Color; +} + +UHoudiniParameter* +UHoudiniAssetParameterColor::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterColor* Parm = UHoudiniParameterColor::Create(Outer, ParameterName); + Parm->SetColorValue(Color); + + //Parm->DefaultColor = Color; + Parm->SetDefaultValue(); + + //Parm->bIsChildOfRamp = false; + + return Parm; +} + +UHoudiniAssetParameterFile::UHoudiniAssetParameterFile(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterFile::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + Ar << Filters; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY) + Ar << IsReadOnly; +} + +UHoudiniParameter* +UHoudiniAssetParameterFile::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterFile* Parm = UHoudiniParameterFile::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + Parm->SetFileFilters(Filters); + Parm->SetReadOnly(IsReadOnly); + + return Parm; +} + +UHoudiniAssetParameterFloat::UHoudiniAssetParameterFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterFloat::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + + Ar << ValueMin; + Ar << ValueMax; + + Ar << ValueUIMin; + Ar << ValueUIMax; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) + Ar << ValueUnit; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP) + Ar << NoSwap; +} + +UHoudiniParameter* +UHoudiniAssetParameterFloat::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterFloat* Parm = UHoudiniParameterFloat::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + Parm->SetUnit(ValueUnit); + Parm->SetHasMin(true); + Parm->SetHasMax(true); + Parm->SetHasUIMin(true); + Parm->SetHasUIMax(true); + Parm->SetIsLogarithmic(false); + Parm->SetMin(ValueMin); + Parm->SetMax(ValueMax); + Parm->SetUIMin(ValueUIMin); + Parm->SetUIMax(ValueUIMax); + + //Parm->bIsChildOfRamp = false; + + return Parm; +} + +UHoudiniAssetParameterFolder::UHoudiniAssetParameterFolder(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterFolder::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterFolder::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterFolderList::UHoudiniAssetParameterFolderList(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterFolderList::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterFolderList::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterInt::UHoudiniAssetParameterInt(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterInt::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + + Ar << ValueMin; + Ar << ValueMax; + + Ar << ValueUIMin; + Ar << ValueUIMax; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) + Ar << ValueUnit; +} + +UHoudiniParameter* +UHoudiniAssetParameterInt::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterInt* Parm = UHoudiniParameterInt::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + + //Parm->DefaultValues = Values; + Parm->SetUnit(ValueUnit); + Parm->SetHasMin(true); + Parm->SetHasMax(true); + Parm->SetHasUIMin(true); + Parm->SetHasUIMax(true); + Parm->SetIsLogarithmic(false); + Parm->SetMin(ValueMin); + Parm->SetMax(ValueMax); + Parm->SetUIMin(ValueUIMin); + Parm->SetUIMax(ValueUIMax); + + return Parm; +} + +UHoudiniAssetParameterLabel::UHoudiniAssetParameterLabel(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterLabel::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterLabel::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterMultiparm::UHoudiniAssetParameterMultiparm(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterMultiparm::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + MultiparmValue = 0; + + Ar << MultiparmValue; +} + +UHoudiniParameter* +UHoudiniAssetParameterMultiparm::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterMultiParm* Parm = UHoudiniParameterMultiParm::Create(Outer, ParameterName); + + //Parm->bIsShown; + //Parm->Value; + //Parm->TemplateName; + Parm->MultiparmValue = MultiparmValue; + //Parm->MultiParmInstanceNum; + //Parm->MultiParmInstanceLength; + //Parm->MultiParmInstanceCount; + //Parm->InstanceStartOffset; + //Parm->DefaultInstanceCount; + + // TODO: + // MultiparmInstanceIndex? + + return Parm; +} + +UHoudiniAssetParameterRamp::UHoudiniAssetParameterRamp(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterRamp::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + int32 multiparmvalue = 0; + Ar << multiparmvalue; + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << HoudiniAssetParameterRampCurveFloat; + Ar << HoudiniAssetParameterRampCurveColor; + + Ar << bIsFloatRamp; +} + +UHoudiniParameter* +UHoudiniAssetParameterRamp::ConvertLegacyData(UObject* Outer) +{ + if (bIsFloatRamp) + { + UHoudiniParameterRampFloat* Parm = UHoudiniParameterRampFloat::Create(Outer, ParameterName); + + // TODO: + // Convert HoudiniAssetParameterRampCurveFloat + + return Parm; + } + else + { + UHoudiniParameterRampColor* Parm = UHoudiniParameterRampColor::Create(Outer, ParameterName); + + // TODO: + // Convert HoudiniAssetParameterRampCurveColor + + return Parm; + } +} + +UHoudiniAssetParameterSeparator::UHoudiniAssetParameterSeparator(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterSeparator::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterSeparator::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterString::UHoudiniAssetParameterString(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterString::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; +} + +UHoudiniParameter* +UHoudiniAssetParameterString::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterString* Parm = UHoudiniParameterString::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetIsAssetRef(false); + Parm->SetDefaultValues(); + + //Parm->DefaultValues = Values; + //Parm->ChosenAssets.Empty(); + //Parm->bIsAssetRef = false; + + return Parm; +} + +UHoudiniAssetParameterToggle::UHoudiniAssetParameterToggle(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterToggle::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; +} + +UHoudiniParameter* +UHoudiniAssetParameterToggle::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterToggle* Parm = UHoudiniParameterToggle::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt((bool)Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + return Parm; +} + +UHoudiniMeshSplitInstancerComponent_V1::UHoudiniMeshSplitInstancerComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniMeshSplitInstancerComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << InstancedMesh; + Ar << OverrideMaterial; + Ar << Instances; +} + +bool +UHoudiniMeshSplitInstancerComponent_V1::UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC) +{ + if (!NewMSIC || NewMSIC->IsPendingKill()) + return false; + + NewMSIC->Instances = Instances; + NewMSIC->OverrideMaterials.Add(OverrideMaterial); + NewMSIC->InstancedMesh = InstancedMesh; + + return true; +} + +UHoudiniInstancedActorComponent_V1::UHoudiniInstancedActorComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniInstancedActorComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << InstancedAsset; + Ar << Instances; +} + +bool +UHoudiniInstancedActorComponent_V1::UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC) +{ + if (!NewIAC || NewIAC->IsPendingKill()) + return false; + + //NewIAC->SetInstancedObject(InstancedAsset); + NewIAC->InstancedObject = InstancedAsset; + NewIAC->InstancedActors = Instances; + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h index f6701285a..8e50b6cb1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h @@ -1,1098 +1,1098 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniAsset.h" - -#include "Components/PrimitiveComponent.h" - -#include "Curves/CurveFloat.h" -#include "Curves/CurveLinearColor.h" - -#include "HoudiniCompatibilityHelpers.generated.h" - -class UStaticMesh; -class UStaticMeshComponent; -class USplineComponent; -class ALandscapeProxy; -class UMaterialInterface; -class UHoudiniInput; -class UHoudiniParameter; -class UHoudiniHandleComponent; -class UHoudiniSplineComponent; -class UHoudiniInstancedActorComponent; -class UHoudiniMeshSplitInstancerComponent; -class UFoliageType_InstancedStaticMesh; - - -struct FHoudiniGeoPartObject; - - -struct FHoudiniGeoPartObject_V1 -{ -public: - - void Serialize(FArchive & Ar); - - FHoudiniGeoPartObject ConvertLegacyData(); - - /** Return hash value for this object, used when using this object as a key inside hashing containers. **/ - uint32 GetTypeHash() const; - - /** Transform of this geo part object. **/ - FTransform TransformMatrix; - - /** Name of associated object. **/ - FString ObjectName; - - /** Name of associated part. **/ - FString PartName; - - /** Name of group which was used for splitting, empty if there's none. **/ - FString SplitName; - - /** Name of the instancer material, if available. **/ - FString InstancerMaterialName; - - /** Name of attribute material, if available. **/ - FString InstancerAttributeMaterialName; - - /** Id of corresponding HAPI Asset. **/ - //HAPI_NodeId AssetId; - int AssetId; - - /** Id of corresponding HAPI Object. **/ - //HAPI_NodeId ObjectId; - int ObjectId; - - /** Id of corresponding HAPI Geo. **/ - //HAPI_NodeId GeoId; - int GeoId; - - /** Id of corresponding HAPI Part. **/ - //HAPI_PartId PartId; - int PartId; - - /** Id of a split. In most cases this will be 0. **/ - int32 SplitId; - - /** Path to the corresponding node */ - mutable FString NodePath; - - /** Flags used by geo part object. **/ - union - { - struct - { - /* Is set to true when referenced object is visible. This is typically used by instancers. **/ - uint32 bIsVisible : 1; - - /** Is set to true when referenced object is an instancer. **/ - uint32 bIsInstancer : 1; - - /** Is set to true when referenced object is a curve. **/ - uint32 bIsCurve : 1; - - /** Is set to true when referenced object is editable. **/ - uint32 bIsEditable : 1; - - /** Is set to true when geometry has changed. **/ - uint32 bHasGeoChanged : 1; - - /** Is set to true when referenced object is collidable. **/ - uint32 bIsCollidable : 1; - - /** Is set to true when referenced object is collidable and is renderable. **/ - uint32 bIsRenderCollidable : 1; - - /** Is set to true when referenced object has just been loaded. **/ - uint32 bIsLoaded : 1; - - /** Unused flags. **/ - uint32 bPlaceHolderFlags : 3; - - /** Is set to true when referenced object has been loaded during transaction. **/ - uint32 bIsTransacting : 1; - - /** Is set to true when referenced object has a custom name. **/ - uint32 bHasCustomName : 1; - - /** Is set to true when referenced object is a box. **/ - uint32 bIsBox : 1; - - /** Is set to true when referenced object is a sphere. **/ - uint32 bIsSphere : 1; - - /** Is set to true when instancer material is available. **/ - uint32 bInstancerMaterialAvailable : 1; - - /** Is set to true when referenced object is a volume. **/ - uint32 bIsVolume : 1; - - /** Is set to true when instancer attribute material is available. **/ - uint32 bInstancerAttributeMaterialAvailable : 1; - - /** Is set when referenced object contains packed primitive instancing */ - uint32 bIsPackedPrimitiveInstancer : 1; - - /** Is set to true when referenced object is a UCX collision geo. **/ - uint32 bIsUCXCollisionGeo : 1; - - /** Is set to true when referenced object is a rendered UCX collision geo. **/ - uint32 bIsSimpleCollisionGeo : 1; - - /** Is set to true when new collision geo has been generated **/ - uint32 bHasCollisionBeenAdded : 1; - - /** Is set to true when new sockets have been added **/ - uint32 bHasSocketBeenAdded : 1; - - /** unused flag space is zero initialized */ - uint32 UnusedFlagsSpace : 14; - }; - - uint32 HoudiniGeoPartObjectFlagsPacked; - }; - - /** Temporary variable holding serialization version. **/ - uint32 HoudiniGeoPartObjectVersion; -}; - -/** Function used by hashing containers to create a unique hash for this type of object. **/ -uint32 GetTypeHash(const FHoudiniGeoPartObject_V1 & HoudiniGeoPartObject); - -/** Serialization function. **/ -FArchive& operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject); - -/** Functor used to sort geo part objects. **/ -struct FHoudiniGeoPartObject_V1SortPredicate -{ - bool operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const; -}; - - -struct FHoudiniAssetInputOutlinerMesh_V1 -{ - /** Serialization. **/ - void Serialize(FArchive & Ar); - - /** Update the Actor pointer from the store Actor path/name **/ - bool TryToUpdateActorPtrFromActorPathName(UWorld* InWorld); - - /** Temporary variable holding serialization version. **/ - uint32 HoudiniAssetParameterVersion; - - /** Selected Actor. **/ - TWeakObjectPtr ActorPtr = nullptr; - - /** Selected Actor's path, used to find the actor back after loading. **/ - FString ActorPathName = TEXT("NONE"); - - /** Selected mesh's component, for reference. **/ - UStaticMeshComponent * StaticMeshComponent = nullptr; - - /** The selected mesh. **/ - UStaticMesh * StaticMesh = nullptr; - - /** Spline Component **/ - USplineComponent * SplineComponent = nullptr; - - /** Number of CVs used by the spline component, used to detect modification **/ - int32 NumberOfSplineControlPoints = -1; - - /** Transform of the UnrealSpline CVs, used to detect modification of the spline (Rotation/Scale) **/ - TArray SplineControlPointsTransform; - - /** Spline Length, used to detect modification of the spline.. **/ - float SplineLength = -1.0f; - - /** Spline resolution used to generate the asset, used to detect setting modification **/ - float SplineResolution = -1.0f; - - /** Actor transform used to see if the transfrom changed since last marshal into Houdini. **/ - FTransform ActorTransform; - - /** Component transform used to see if the transform has changed since last marshalling **/ - FTransform ComponentTransform; - - /** Mesh's input asset id. **/ - //HAPI_NodeId AssetId = -1; - int AssetId = -1; - - /** TranformType used to generate the asset **/ - int32 KeepWorldTransform = 2; - - /** Path Materials assigned on the SMC **/ - TArray MeshComponentsMaterials; - - /** If the world In is a ISM, index of this instance **/ - uint32 InstanceIndex = -1; -}; - -/** Serialization function. **/ -FArchive & operator<<(FArchive & Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh); - -/* -UCLASS(EditInlineNew, config = Engine) -class UHoudiniAsset_V1 -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; -}; -*/ - -UCLASS() -class UHoudiniAssetParameter : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer); - - void CopyLegacyParameterData(UHoudiniParameter* InNewParm); - - /** Name of this parameter. **/ - FString ParameterName; - - /** Label of this parameter. **/ - FString ParameterLabel; - - /** Node this parameter belongs to. **/ - int NodeId; - - /** Id of this parameter. **/ - int ParmId; - - /** Id of parent parameter, -1 if root is parent. **/ - int ParmParentId; - - /** Child index within its parent parameter. **/ - int32 ChildIndex; - - /** Tuple size - arrays. **/ - int32 TupleSize; - - /** Internal HAPI cached value index. **/ - int32 ValuesIndex; - - /** The multiparm instance index. **/ - int32 MultiparmInstanceIndex; - - /** The parameter's help, to be used as a tooltip **/ - FString ParameterHelp; - - /** Flags used by this parameter. **/ - union - { - struct - { - /** Is set to true if this parameter is spare, that is, created by Houdini Engine only. **/ - uint32 bIsSpare : 1; - - /** Is set to true if this parameter is disabled. **/ - uint32 bIsDisabled : 1; - - /** Is set to true if value of this parameter has been changed by user. **/ - uint32 bChanged : 1; - - /** Is set to true when parameter's slider (if it has one) is being dragged. Transient. **/ - uint32 bSliderDragged : 1; - - /** Is set to true if the parameter is a multiparm child parameter. **/ - uint32 bIsChildOfMultiparm : 1; - - /** Is set to true if this parameter is a Substance parameter. **/ - uint32 bIsSubstanceParameter : 1; - - /** Is set to true if this parameter is a multiparm **/ - uint32 bIsMultiparm : 1; - }; - - uint32 HoudiniAssetParameterFlagsPacked; - }; - - /** Temporary variable holding parameter serialization version. **/ - uint32 HoudiniAssetParameterVersion; -}; - -UCLASS() -class UHoudiniAssetParameterButton : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterChoice : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Choice values for this property. **/ - TArray StringChoiceValues; - - /** Choice labels for this property. **/ - TArray StringChoiceLabels; - - /** Value of this property. **/ - FString StringValue; - - /** Current value for this property. **/ - int32 CurrentValue; - - /** Is set to true when this choice list is a string choice list. **/ - bool bStringChoiceList; -}; - -UCLASS() -class UHoudiniAssetParameterColor : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Color for this property. **/ - FLinearColor Color; -}; - -UCLASS() -class UHoudiniAssetParameterFile : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< FString > Values; - - /** Filters of this property. **/ - FString Filters; - - /** Is the file parameter read-only? **/ - bool IsReadOnly; -}; - -UCLASS() -class UHoudiniAssetParameterFloat : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< float > Values; - - /** Min and Max values for this property. **/ - float ValueMin; - float ValueMax; - - /** Min and Max values for UI for this property. **/ - float ValueUIMin; - float ValueUIMax; - - /** Unit for this property **/ - FString ValueUnit; - - /** Do we have the noswap tag? **/ - bool NoSwap; -}; - -UCLASS() -class UHoudiniAssetParameterFolder : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterFolderList : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterInt : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< int32 > Values; - - /** Min and Max values for this property. **/ - int32 ValueMin; - int32 ValueMax; - - /** Min and Max values for UI for this property. **/ - int32 ValueUIMin; - int32 ValueUIMax; - - /** Unit for this property **/ - FString ValueUnit; -}; - -UCLASS() -class UHoudiniAssetParameterLabel : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterMultiparm : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Value of this property. **/ - int32 MultiparmValue; -}; - -UCLASS() -class UHoudiniAssetParameterRamp : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - void CopyLegacyParameterData(UHoudiniParameter* InNewParm); - - //! Curves which are being edited. - UCurveFloat * HoudiniAssetParameterRampCurveFloat; - UCurveLinearColor * HoudiniAssetParameterRampCurveColor; - - //! Set to true if this ramp is a float ramp. Otherwise is considered a color ramp. - bool bIsFloatRamp; -}; - -UCLASS() -class UHoudiniAssetParameterSeparator : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterString : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< FString > Values; -}; - -UCLASS() -class UHoudiniAssetParameterToggle : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< int32 > Values; -}; - -UCLASS() -class UHoudiniAssetComponentMaterials_V1 : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** Material assignments. **/ - TMap Assignments; - - /** Material replacements. **/ - TMap> Replacements; -}; - -UCLASS() -class UHoudiniHandleComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniHandleComponent* NewHC); - - //virtual UHoudiniHandleComponent* ConvertLegacyData(UObject* Outer); - - UHoudiniAssetParameterFloat* XFormParams[9]; - int32 XFormParamsTupleIndex[9]; - - UHoudiniAssetParameterChoice* RSTParm; - int32 RSTParmTupleIdx; - - UHoudiniAssetParameterChoice* RotOrderParm; - int32 RotOrderParmTupleIdx; -}; - -UCLASS() -class UHoudiniSplineComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniSplineComponent* ConvertLegacyData(UObject* Outer); - - bool UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline); - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** List of points composing this curve. **/ - TArray CurvePoints; - - /** List of refined points used for drawing. **/ - TArray CurveDisplayPoints; - - /** Type of this curve. **/ - // 0 Polygon 1 Nurbs 2 Bezier - uint8 CurveType; - - /** Method used for this curve. **/ - // 0 CVs, 1 Breakpoints, 2 Freehand - uint8 CurveMethod; - - /** Whether this spline is closed. **/ - bool bClosedCurve; -}; - -UCLASS() -class UHoudiniAssetInput : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - UHoudiniInput* ConvertLegacyInput(UObject* Outer); - - // Input type: - // 0 GeometryInput - // 1 AssetInput - // 2 CurveInput - // 3 LandscapeInput - // 4 WorldInput - // 5 SkeletonInput - uint8 ChoiceIndex; - - /** Value of choice option. **/ - FString ChoiceStringValue; - - /** Index of this input. **/ - int32 InputIndex; - - /** Objects used for geometry input. **/ - TArray InputObjects; - - /** Houdini spline component which is used for curve input. **/ - UHoudiniSplineComponent * InputCurve; - - /** Houdini asset component pointer of the input asset (actor). **/ - UHoudiniAssetComponent_V1 * InputAssetComponent; - - /** Landscape actor used for input. **/ - TSoftObjectPtr InputLandscapeProxy; - - /** List of selected meshes and actors from the World Outliner. **/ - TArray InputOutlinerMeshArray; - - /** Parameters used by a curve input asset. **/ - TMap InputCurveParameters; - - float UnrealSplineResolution; - - /** Array containing the transform corrections for the assets in a geometry input **/ - TArray InputTransforms; - - /** Transform used by the input landscape **/ - FTransform InputLandscapeTransform; - - /** Flags used by this input. **/ - union - { - struct - { - /** Is set to true when static mesh used for geometry input has changed. **/ - uint32 bStaticMeshChanged : 1; - - /** Is set to true when choice switches to curve mode. **/ - uint32 bSwitchedToCurve : 1; - - /** Is set to true if this parameter has been loaded. **/ - uint32 bLoadedParameter : 1; - - /** Is set to true if the asset input is actually connected inside Houdini. **/ - uint32 bInputAssetConnectedInHoudini : 1; - - /** Is set to true when landscape input is set to selection only. **/ - uint32 bLandscapeExportSelectionOnly : 1; - - /** Is set to true when landscape curves are to be exported. **/ - uint32 bLandscapeExportCurves : 1; - - /** Is set to true when the landscape is to be exported as a mesh, not just points. **/ - uint32 bLandscapeExportAsMesh : 1; - - /** Is set to true when materials are to be exported. **/ - uint32 bLandscapeExportMaterials : 1; - - /** Is set to true when lightmap information export is desired. **/ - uint32 bLandscapeExportLighting : 1; - - /** Is set to true when uvs should be exported in [0,1] space. **/ - uint32 bLandscapeExportNormalizedUVs : 1; - - /** Is set to true when uvs should be exported for each tile separately. **/ - uint32 bLandscapeExportTileUVs : 1; - - /** Is set to true when being used as an object-path parameter instead of an input */ - uint32 bIsObjectPathParameter : 1; - - /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value **/ - uint32 bKeepWorldTransform : 2; - - /** Is set to true when the landscape is to be exported as a heightfield **/ - uint32 bLandscapeExportAsHeightfield : 1; - - /** Is set to true when the automatic selection of landscape component is active **/ - uint32 bLandscapeAutoSelectComponent : 1; - - /** Indicates that the geometry must be packed before merging it into the input **/ - uint32 bPackBeforeMerge : 1; - - /** Indicates that all LODs in the input should be marshalled to Houdini **/ - uint32 bExportAllLODs : 1; - - /** Indicates that all sockets in the input should be marshalled to Houdini **/ - uint32 bExportSockets : 1; - - /** Indicates that the landscape input's source landscape should be updated instead of creating a new component **/ - uint32 bUpdateInputLandscape : 1; - }; - - uint32 HoudiniAssetInputFlagsPacked; - }; -}; - -UCLASS() -class UHoudiniAssetInstanceInput : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** List of fields created by this instance input. **/ - TArray InstanceInputFields; - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** Id of an object to instance. **/ - int ObjectToInstanceId; - -public: - /** Flags used by this input. **/ - union FHoudiniAssetInstanceInputFlags - { - struct - { - /** Set to true if this is an attribute instancer. **/ - uint32 bIsAttributeInstancer : 1; - - /** Set to true if this attribute instancer uses overrides. **/ - uint32 bAttributeInstancerOverride : 1; - - /** Set to true if this is a packed primitive instancer **/ - uint32 bIsPackedPrimitiveInstancer : 1; - - /** Set to true if this is a split mesh instancer */ - uint32 bIsSplitMeshInstancer : 1; - }; - - uint32 HoudiniAssetInstanceInputFlagsPacked; - }; - FHoudiniAssetInstanceInputFlags Flags; -}; - -UCLASS() -class UHoudiniAssetInstanceInputField : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** Original object used by the instancer. **/ - UObject* OriginalObject; - - /** Currently used Objects */ - TArray< UObject* > InstancedObjects; - - /** Used instanced actor component. **/ - TArray< USceneComponent * > InstancerComponents; - - /** Flags used by this input field. **/ - uint32 HoudiniAssetInstanceInputFieldFlagsPacked; - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** Rotation offset for instanced component. **/ - TArray< FRotator > RotationOffsets; - - /** Scale offset for instanced component. **/ - TArray< FVector > ScaleOffsets; - - /** Whether to scale linearly for all fields. **/ - TArray< bool > bScaleOffsetsLinearlyArray; - - /** Transforms, one for each instance. **/ - TArray< FTransform > InstancedTransforms; - - /** Assignment of Transforms to each variation **/ - TArray< TArray< FTransform > > VariationTransformsArray; - - /** Color overrides, one per instance **/ - TArray InstanceColorOverride; - - /** Per-variation color override assignments */ - TArray< TArray< FLinearColor > > VariationInstanceColorOverrideArray; -}; - -//UCLASS() -UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), - ShowCategories = (Mobility), editinlinenew) -class UHoudiniAssetComponent_V1 : public UPrimitiveComponent//, public IHoudiniCookHandler -{ - GENERATED_UCLASS_BODY() - -public: - /* - // IHoudiniCookHandler interface - virtual FString GetBakingBaseName(const struct FHoudiniGeoPartObject_V1& GeoPartObject) override { return FString(); }; - virtual void SetStaticMeshGenerationParameters(class UStaticMesh* StaticMesh) override {}; - virtual class UMaterialInterface * GetAssignmentMaterial(const FString& MaterialName) override { return nullptr; }; - virtual void ClearAssignmentMaterials() override {}; - virtual void AddAssignmentMaterial(const FString& MaterialName, class UMaterialInterface* MaterialInterface) override {}; - virtual class UMaterialInterface * GetReplacementMaterial(const struct FHoudiniGeoPartObject_V1& GeoPartObject, const FString& MaterialName) override { return nullptr; }; - */ - - /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Double Sided Geometry")) - uint32 bGeneratedDoubleSidedGeometry : 1; - - /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * GeneratedPhysMaterial; - - /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ - UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Collision Complexity")) - TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; - - /** Resolution of lightmap. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 GeneratedLightMapResolution; - - /** Bias multiplier for Light Propagation Volume lighting. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float GeneratedLpvBiasMultiplier; - - /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) - float GeneratedDistanceFieldResolutionScale; - - /** Custom walkable slope setting for generated mesh's body. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride GeneratedWalkableSlopeOverride; - - /** The light map coordinate index. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light map coordinate index")) - int32 GeneratedLightMapCoordinateIndex; - - /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; - - /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Streaming Distance Multiplier")) - float GeneratedStreamingDistanceMultiplier; - - /** Default settings when using this mesh for instanced foliage. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; - - /** Array of user data stored with the asset. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Asset User Data")) - TArray GeneratedAssetUserData; - - /** The output folder for baking actions */ - UPROPERTY() - FText BakeFolder; - - /** The temporary output folder for cooking actions */ - UPROPERTY() - FText TempCookFolder; - - virtual void Serialize(FArchive & Ar) override; - - /** Houdini Asset associated with this component. **/ - UHoudiniAsset* HoudiniAsset; - - /** Unique GUID created by component. **/ - FGuid ComponentGUID; - - /** Scale factor used for generated geometry of this component. **/ - float GeneratedGeometryScaleFactor; - - /** Scale factor used for geo transforms of this component. **/ - float TransformScaleFactor; - - /** Buffer to hold preset data for serialization purposes. Used only during serialization. **/ - TArray PresetBuffer; - - /** Buffer to hold default preset for reset purposes. **/ - TArray DefaultPresetBuffer; - - /** Parameters for this component's asset, indexed by parameter id. **/ - //TMap Parameters; - TMap Parameters; - - /** Parameters for this component's asset, indexed by name for fast look up. **/ - TMap ParameterByName; - - /** Inputs for this component's asset. **/ - TArray Inputs; - - /** Instance inputs for this component's asset **/ - TArray InstanceInputs; - - /** Material assignments. **/ - UHoudiniAssetComponentMaterials_V1 * HoudiniAssetComponentMaterials; - - /** Map of HAPI objects and corresponding static meshes. Also map of static meshes and corresponding components. **/ - TMap StaticMeshes; - TMap StaticMeshComponents; - - /** List of dependent downstream asset connections that have this asset as an asset input. **/ - TMap> DownstreamAssetConnections; - - /** Map of asset handle components. **/ - TMap HandleComponents; - - /** Map of curve / spline components. **/ - TMap SplineComponents; - - /** Map of Landscape / Heightfield components. **/ - TMap> LandscapeComponents; - - /** Overrides for baking names per part */ - TMap BakeNameOverrides; - - /** Import axis. **/ - uint8 ImportAxis; - - /** Flags used by Houdini component. **/ - union - { - struct - { - /** Enables cooking for this Houdini Asset. **/ - uint32 bEnableCooking : 1; - - /** Enables uploading of transformation changes back to Houdini Engine. **/ - uint32 bUploadTransformsToHoudiniEngine : 1; - - /** Enables cooking upon transformation changes. **/ - uint32 bTransformChangeTriggersCooks : 1; - - /** Is set to true when this component contains Houdini logo geometry. **/ - uint32 bContainsHoudiniLogoGeometry : 1; - - /** Is set to true when this component is native and false is when it is dynamic. **/ - uint32 bIsNativeComponent : 1; - - /** Is set to true when this component belongs to a preview actor. **/ - uint32 bIsPreviewComponent : 1; - - /** Is set to true if this component has been loaded. **/ - uint32 bLoadedComponent : 1; - - /** Unused **/ - uint32 bIsPlayModeActive_Unused : 1; - - /** unused flag **/ - uint32 bTimeCookInPlaymode_Unused : 1; - - /** Is set to true when Houdini materials are used. **/ - uint32 bUseHoudiniMaterials : 1; - - /** Is set to true when cooking this asset will trigger cooks of downstream connected assets. **/ - uint32 bCookingTriggersDownstreamCooks : 1; - - /** Is set to true after the asset is fully loaded and registered **/ - uint32 bFullyLoaded : 1; - }; - - uint32 HoudiniAssetComponentFlagsPacked; - }; -}; - -UCLASS() -class UHoudiniInstancedActorComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC); - - //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) - UObject* InstancedAsset; - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - TArray Instances; -}; - -UCLASS() -class UHoudiniMeshSplitInstancerComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC); - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - TArray Instances; - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - UMaterialInterface* OverrideMaterial; - - //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) - UStaticMesh* InstancedMesh; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniAsset.h" + +#include "Components/PrimitiveComponent.h" + +#include "Curves/CurveFloat.h" +#include "Curves/CurveLinearColor.h" + +#include "HoudiniCompatibilityHelpers.generated.h" + +class UStaticMesh; +class UStaticMeshComponent; +class USplineComponent; +class ALandscapeProxy; +class UMaterialInterface; +class UHoudiniInput; +class UHoudiniParameter; +class UHoudiniHandleComponent; +class UHoudiniSplineComponent; +class UHoudiniInstancedActorComponent; +class UHoudiniMeshSplitInstancerComponent; +class UFoliageType_InstancedStaticMesh; + + +struct FHoudiniGeoPartObject; + + +struct FHoudiniGeoPartObject_V1 +{ +public: + + void Serialize(FArchive & Ar); + + FHoudiniGeoPartObject ConvertLegacyData(); + + /** Return hash value for this object, used when using this object as a key inside hashing containers. **/ + uint32 GetTypeHash() const; + + /** Transform of this geo part object. **/ + FTransform TransformMatrix; + + /** Name of associated object. **/ + FString ObjectName; + + /** Name of associated part. **/ + FString PartName; + + /** Name of group which was used for splitting, empty if there's none. **/ + FString SplitName; + + /** Name of the instancer material, if available. **/ + FString InstancerMaterialName; + + /** Name of attribute material, if available. **/ + FString InstancerAttributeMaterialName; + + /** Id of corresponding HAPI Asset. **/ + //HAPI_NodeId AssetId; + int AssetId; + + /** Id of corresponding HAPI Object. **/ + //HAPI_NodeId ObjectId; + int ObjectId; + + /** Id of corresponding HAPI Geo. **/ + //HAPI_NodeId GeoId; + int GeoId; + + /** Id of corresponding HAPI Part. **/ + //HAPI_PartId PartId; + int PartId; + + /** Id of a split. In most cases this will be 0. **/ + int32 SplitId; + + /** Path to the corresponding node */ + mutable FString NodePath; + + /** Flags used by geo part object. **/ + union + { + struct + { + /* Is set to true when referenced object is visible. This is typically used by instancers. **/ + uint32 bIsVisible : 1; + + /** Is set to true when referenced object is an instancer. **/ + uint32 bIsInstancer : 1; + + /** Is set to true when referenced object is a curve. **/ + uint32 bIsCurve : 1; + + /** Is set to true when referenced object is editable. **/ + uint32 bIsEditable : 1; + + /** Is set to true when geometry has changed. **/ + uint32 bHasGeoChanged : 1; + + /** Is set to true when referenced object is collidable. **/ + uint32 bIsCollidable : 1; + + /** Is set to true when referenced object is collidable and is renderable. **/ + uint32 bIsRenderCollidable : 1; + + /** Is set to true when referenced object has just been loaded. **/ + uint32 bIsLoaded : 1; + + /** Unused flags. **/ + uint32 bPlaceHolderFlags : 3; + + /** Is set to true when referenced object has been loaded during transaction. **/ + uint32 bIsTransacting : 1; + + /** Is set to true when referenced object has a custom name. **/ + uint32 bHasCustomName : 1; + + /** Is set to true when referenced object is a box. **/ + uint32 bIsBox : 1; + + /** Is set to true when referenced object is a sphere. **/ + uint32 bIsSphere : 1; + + /** Is set to true when instancer material is available. **/ + uint32 bInstancerMaterialAvailable : 1; + + /** Is set to true when referenced object is a volume. **/ + uint32 bIsVolume : 1; + + /** Is set to true when instancer attribute material is available. **/ + uint32 bInstancerAttributeMaterialAvailable : 1; + + /** Is set when referenced object contains packed primitive instancing */ + uint32 bIsPackedPrimitiveInstancer : 1; + + /** Is set to true when referenced object is a UCX collision geo. **/ + uint32 bIsUCXCollisionGeo : 1; + + /** Is set to true when referenced object is a rendered UCX collision geo. **/ + uint32 bIsSimpleCollisionGeo : 1; + + /** Is set to true when new collision geo has been generated **/ + uint32 bHasCollisionBeenAdded : 1; + + /** Is set to true when new sockets have been added **/ + uint32 bHasSocketBeenAdded : 1; + + /** unused flag space is zero initialized */ + uint32 UnusedFlagsSpace : 14; + }; + + uint32 HoudiniGeoPartObjectFlagsPacked; + }; + + /** Temporary variable holding serialization version. **/ + uint32 HoudiniGeoPartObjectVersion; +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +uint32 GetTypeHash(const FHoudiniGeoPartObject_V1 & HoudiniGeoPartObject); + +/** Serialization function. **/ +FArchive& operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject); + +/** Functor used to sort geo part objects. **/ +struct FHoudiniGeoPartObject_V1SortPredicate +{ + bool operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const; +}; + + +struct FHoudiniAssetInputOutlinerMesh_V1 +{ + /** Serialization. **/ + void Serialize(FArchive & Ar); + + /** Update the Actor pointer from the store Actor path/name **/ + bool TryToUpdateActorPtrFromActorPathName(UWorld* InWorld); + + /** Temporary variable holding serialization version. **/ + uint32 HoudiniAssetParameterVersion; + + /** Selected Actor. **/ + TWeakObjectPtr ActorPtr = nullptr; + + /** Selected Actor's path, used to find the actor back after loading. **/ + FString ActorPathName = TEXT("NONE"); + + /** Selected mesh's component, for reference. **/ + UStaticMeshComponent * StaticMeshComponent = nullptr; + + /** The selected mesh. **/ + UStaticMesh * StaticMesh = nullptr; + + /** Spline Component **/ + USplineComponent * SplineComponent = nullptr; + + /** Number of CVs used by the spline component, used to detect modification **/ + int32 NumberOfSplineControlPoints = -1; + + /** Transform of the UnrealSpline CVs, used to detect modification of the spline (Rotation/Scale) **/ + TArray SplineControlPointsTransform; + + /** Spline Length, used to detect modification of the spline.. **/ + float SplineLength = -1.0f; + + /** Spline resolution used to generate the asset, used to detect setting modification **/ + float SplineResolution = -1.0f; + + /** Actor transform used to see if the transfrom changed since last marshal into Houdini. **/ + FTransform ActorTransform; + + /** Component transform used to see if the transform has changed since last marshalling **/ + FTransform ComponentTransform; + + /** Mesh's input asset id. **/ + //HAPI_NodeId AssetId = -1; + int AssetId = -1; + + /** TranformType used to generate the asset **/ + int32 KeepWorldTransform = 2; + + /** Path Materials assigned on the SMC **/ + TArray MeshComponentsMaterials; + + /** If the world In is a ISM, index of this instance **/ + uint32 InstanceIndex = -1; +}; + +/** Serialization function. **/ +FArchive & operator<<(FArchive & Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh); + +/* +UCLASS(EditInlineNew, config = Engine) +class UHoudiniAsset_V1 +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; +}; +*/ + +UCLASS() +class UHoudiniAssetParameter : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer); + + void CopyLegacyParameterData(UHoudiniParameter* InNewParm); + + /** Name of this parameter. **/ + FString ParameterName; + + /** Label of this parameter. **/ + FString ParameterLabel; + + /** Node this parameter belongs to. **/ + int NodeId; + + /** Id of this parameter. **/ + int ParmId; + + /** Id of parent parameter, -1 if root is parent. **/ + int ParmParentId; + + /** Child index within its parent parameter. **/ + int32 ChildIndex; + + /** Tuple size - arrays. **/ + int32 TupleSize; + + /** Internal HAPI cached value index. **/ + int32 ValuesIndex; + + /** The multiparm instance index. **/ + int32 MultiparmInstanceIndex; + + /** The parameter's help, to be used as a tooltip **/ + FString ParameterHelp; + + /** Flags used by this parameter. **/ + union + { + struct + { + /** Is set to true if this parameter is spare, that is, created by Houdini Engine only. **/ + uint32 bIsSpare : 1; + + /** Is set to true if this parameter is disabled. **/ + uint32 bIsDisabled : 1; + + /** Is set to true if value of this parameter has been changed by user. **/ + uint32 bChanged : 1; + + /** Is set to true when parameter's slider (if it has one) is being dragged. Transient. **/ + uint32 bSliderDragged : 1; + + /** Is set to true if the parameter is a multiparm child parameter. **/ + uint32 bIsChildOfMultiparm : 1; + + /** Is set to true if this parameter is a Substance parameter. **/ + uint32 bIsSubstanceParameter : 1; + + /** Is set to true if this parameter is a multiparm **/ + uint32 bIsMultiparm : 1; + }; + + uint32 HoudiniAssetParameterFlagsPacked; + }; + + /** Temporary variable holding parameter serialization version. **/ + uint32 HoudiniAssetParameterVersion; +}; + +UCLASS() +class UHoudiniAssetParameterButton : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterChoice : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Choice values for this property. **/ + TArray StringChoiceValues; + + /** Choice labels for this property. **/ + TArray StringChoiceLabels; + + /** Value of this property. **/ + FString StringValue; + + /** Current value for this property. **/ + int32 CurrentValue; + + /** Is set to true when this choice list is a string choice list. **/ + bool bStringChoiceList; +}; + +UCLASS() +class UHoudiniAssetParameterColor : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Color for this property. **/ + FLinearColor Color; +}; + +UCLASS() +class UHoudiniAssetParameterFile : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< FString > Values; + + /** Filters of this property. **/ + FString Filters; + + /** Is the file parameter read-only? **/ + bool IsReadOnly; +}; + +UCLASS() +class UHoudiniAssetParameterFloat : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< float > Values; + + /** Min and Max values for this property. **/ + float ValueMin; + float ValueMax; + + /** Min and Max values for UI for this property. **/ + float ValueUIMin; + float ValueUIMax; + + /** Unit for this property **/ + FString ValueUnit; + + /** Do we have the noswap tag? **/ + bool NoSwap; +}; + +UCLASS() +class UHoudiniAssetParameterFolder : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterFolderList : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterInt : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< int32 > Values; + + /** Min and Max values for this property. **/ + int32 ValueMin; + int32 ValueMax; + + /** Min and Max values for UI for this property. **/ + int32 ValueUIMin; + int32 ValueUIMax; + + /** Unit for this property **/ + FString ValueUnit; +}; + +UCLASS() +class UHoudiniAssetParameterLabel : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterMultiparm : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Value of this property. **/ + int32 MultiparmValue; +}; + +UCLASS() +class UHoudiniAssetParameterRamp : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + void CopyLegacyParameterData(UHoudiniParameter* InNewParm); + + //! Curves which are being edited. + UCurveFloat * HoudiniAssetParameterRampCurveFloat; + UCurveLinearColor * HoudiniAssetParameterRampCurveColor; + + //! Set to true if this ramp is a float ramp. Otherwise is considered a color ramp. + bool bIsFloatRamp; +}; + +UCLASS() +class UHoudiniAssetParameterSeparator : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterString : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< FString > Values; +}; + +UCLASS() +class UHoudiniAssetParameterToggle : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< int32 > Values; +}; + +UCLASS() +class UHoudiniAssetComponentMaterials_V1 : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** Material assignments. **/ + TMap Assignments; + + /** Material replacements. **/ + TMap> Replacements; +}; + +UCLASS() +class UHoudiniHandleComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniHandleComponent* NewHC); + + //virtual UHoudiniHandleComponent* ConvertLegacyData(UObject* Outer); + + UHoudiniAssetParameterFloat* XFormParams[9]; + int32 XFormParamsTupleIndex[9]; + + UHoudiniAssetParameterChoice* RSTParm; + int32 RSTParmTupleIdx; + + UHoudiniAssetParameterChoice* RotOrderParm; + int32 RotOrderParmTupleIdx; +}; + +UCLASS() +class UHoudiniSplineComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniSplineComponent* ConvertLegacyData(UObject* Outer); + + bool UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline); + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** List of points composing this curve. **/ + TArray CurvePoints; + + /** List of refined points used for drawing. **/ + TArray CurveDisplayPoints; + + /** Type of this curve. **/ + // 0 Polygon 1 Nurbs 2 Bezier + uint8 CurveType; + + /** Method used for this curve. **/ + // 0 CVs, 1 Breakpoints, 2 Freehand + uint8 CurveMethod; + + /** Whether this spline is closed. **/ + bool bClosedCurve; +}; + +UCLASS() +class UHoudiniAssetInput : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + UHoudiniInput* ConvertLegacyInput(UObject* Outer); + + // Input type: + // 0 GeometryInput + // 1 AssetInput + // 2 CurveInput + // 3 LandscapeInput + // 4 WorldInput + // 5 SkeletonInput + uint8 ChoiceIndex; + + /** Value of choice option. **/ + FString ChoiceStringValue; + + /** Index of this input. **/ + int32 InputIndex; + + /** Objects used for geometry input. **/ + TArray InputObjects; + + /** Houdini spline component which is used for curve input. **/ + UHoudiniSplineComponent * InputCurve; + + /** Houdini asset component pointer of the input asset (actor). **/ + UHoudiniAssetComponent_V1 * InputAssetComponent; + + /** Landscape actor used for input. **/ + TSoftObjectPtr InputLandscapeProxy; + + /** List of selected meshes and actors from the World Outliner. **/ + TArray InputOutlinerMeshArray; + + /** Parameters used by a curve input asset. **/ + TMap InputCurveParameters; + + float UnrealSplineResolution; + + /** Array containing the transform corrections for the assets in a geometry input **/ + TArray InputTransforms; + + /** Transform used by the input landscape **/ + FTransform InputLandscapeTransform; + + /** Flags used by this input. **/ + union + { + struct + { + /** Is set to true when static mesh used for geometry input has changed. **/ + uint32 bStaticMeshChanged : 1; + + /** Is set to true when choice switches to curve mode. **/ + uint32 bSwitchedToCurve : 1; + + /** Is set to true if this parameter has been loaded. **/ + uint32 bLoadedParameter : 1; + + /** Is set to true if the asset input is actually connected inside Houdini. **/ + uint32 bInputAssetConnectedInHoudini : 1; + + /** Is set to true when landscape input is set to selection only. **/ + uint32 bLandscapeExportSelectionOnly : 1; + + /** Is set to true when landscape curves are to be exported. **/ + uint32 bLandscapeExportCurves : 1; + + /** Is set to true when the landscape is to be exported as a mesh, not just points. **/ + uint32 bLandscapeExportAsMesh : 1; + + /** Is set to true when materials are to be exported. **/ + uint32 bLandscapeExportMaterials : 1; + + /** Is set to true when lightmap information export is desired. **/ + uint32 bLandscapeExportLighting : 1; + + /** Is set to true when uvs should be exported in [0,1] space. **/ + uint32 bLandscapeExportNormalizedUVs : 1; + + /** Is set to true when uvs should be exported for each tile separately. **/ + uint32 bLandscapeExportTileUVs : 1; + + /** Is set to true when being used as an object-path parameter instead of an input */ + uint32 bIsObjectPathParameter : 1; + + /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value **/ + uint32 bKeepWorldTransform : 2; + + /** Is set to true when the landscape is to be exported as a heightfield **/ + uint32 bLandscapeExportAsHeightfield : 1; + + /** Is set to true when the automatic selection of landscape component is active **/ + uint32 bLandscapeAutoSelectComponent : 1; + + /** Indicates that the geometry must be packed before merging it into the input **/ + uint32 bPackBeforeMerge : 1; + + /** Indicates that all LODs in the input should be marshalled to Houdini **/ + uint32 bExportAllLODs : 1; + + /** Indicates that all sockets in the input should be marshalled to Houdini **/ + uint32 bExportSockets : 1; + + /** Indicates that the landscape input's source landscape should be updated instead of creating a new component **/ + uint32 bUpdateInputLandscape : 1; + }; + + uint32 HoudiniAssetInputFlagsPacked; + }; +}; + +UCLASS() +class UHoudiniAssetInstanceInput : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** List of fields created by this instance input. **/ + TArray InstanceInputFields; + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** Id of an object to instance. **/ + int ObjectToInstanceId; + +public: + /** Flags used by this input. **/ + union FHoudiniAssetInstanceInputFlags + { + struct + { + /** Set to true if this is an attribute instancer. **/ + uint32 bIsAttributeInstancer : 1; + + /** Set to true if this attribute instancer uses overrides. **/ + uint32 bAttributeInstancerOverride : 1; + + /** Set to true if this is a packed primitive instancer **/ + uint32 bIsPackedPrimitiveInstancer : 1; + + /** Set to true if this is a split mesh instancer */ + uint32 bIsSplitMeshInstancer : 1; + }; + + uint32 HoudiniAssetInstanceInputFlagsPacked; + }; + FHoudiniAssetInstanceInputFlags Flags; +}; + +UCLASS() +class UHoudiniAssetInstanceInputField : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** Original object used by the instancer. **/ + UObject* OriginalObject; + + /** Currently used Objects */ + TArray< UObject* > InstancedObjects; + + /** Used instanced actor component. **/ + TArray< USceneComponent * > InstancerComponents; + + /** Flags used by this input field. **/ + uint32 HoudiniAssetInstanceInputFieldFlagsPacked; + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** Rotation offset for instanced component. **/ + TArray< FRotator > RotationOffsets; + + /** Scale offset for instanced component. **/ + TArray< FVector > ScaleOffsets; + + /** Whether to scale linearly for all fields. **/ + TArray< bool > bScaleOffsetsLinearlyArray; + + /** Transforms, one for each instance. **/ + TArray< FTransform > InstancedTransforms; + + /** Assignment of Transforms to each variation **/ + TArray< TArray< FTransform > > VariationTransformsArray; + + /** Color overrides, one per instance **/ + TArray InstanceColorOverride; + + /** Per-variation color override assignments */ + TArray< TArray< FLinearColor > > VariationInstanceColorOverrideArray; +}; + +//UCLASS() +UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), + ShowCategories = (Mobility), editinlinenew) +class UHoudiniAssetComponent_V1 : public UPrimitiveComponent//, public IHoudiniCookHandler +{ + GENERATED_UCLASS_BODY() + +public: + /* + // IHoudiniCookHandler interface + virtual FString GetBakingBaseName(const struct FHoudiniGeoPartObject_V1& GeoPartObject) override { return FString(); }; + virtual void SetStaticMeshGenerationParameters(class UStaticMesh* StaticMesh) override {}; + virtual class UMaterialInterface * GetAssignmentMaterial(const FString& MaterialName) override { return nullptr; }; + virtual void ClearAssignmentMaterials() override {}; + virtual void AddAssignmentMaterial(const FString& MaterialName, class UMaterialInterface* MaterialInterface) override {}; + virtual class UMaterialInterface * GetReplacementMaterial(const struct FHoudiniGeoPartObject_V1& GeoPartObject, const FString& MaterialName) override { return nullptr; }; + */ + + /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Double Sided Geometry")) + uint32 bGeneratedDoubleSidedGeometry : 1; + + /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * GeneratedPhysMaterial; + + /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ + UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Collision Complexity")) + TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; + + /** Resolution of lightmap. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 GeneratedLightMapResolution; + + /** Bias multiplier for Light Propagation Volume lighting. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) + float GeneratedLpvBiasMultiplier; + + /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /** Custom walkable slope setting for generated mesh's body. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride GeneratedWalkableSlopeOverride; + + /** The light map coordinate index. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Light map coordinate index")) + int32 GeneratedLightMapCoordinateIndex; + + /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; + + /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Streaming Distance Multiplier")) + float GeneratedStreamingDistanceMultiplier; + + /** Default settings when using this mesh for instanced foliage. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; + + /** Array of user data stored with the asset. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Asset User Data")) + TArray GeneratedAssetUserData; + + /** The output folder for baking actions */ + UPROPERTY() + FText BakeFolder; + + /** The temporary output folder for cooking actions */ + UPROPERTY() + FText TempCookFolder; + + virtual void Serialize(FArchive & Ar) override; + + /** Houdini Asset associated with this component. **/ + UHoudiniAsset* HoudiniAsset; + + /** Unique GUID created by component. **/ + FGuid ComponentGUID; + + /** Scale factor used for generated geometry of this component. **/ + float GeneratedGeometryScaleFactor; + + /** Scale factor used for geo transforms of this component. **/ + float TransformScaleFactor; + + /** Buffer to hold preset data for serialization purposes. Used only during serialization. **/ + TArray PresetBuffer; + + /** Buffer to hold default preset for reset purposes. **/ + TArray DefaultPresetBuffer; + + /** Parameters for this component's asset, indexed by parameter id. **/ + //TMap Parameters; + TMap Parameters; + + /** Parameters for this component's asset, indexed by name for fast look up. **/ + TMap ParameterByName; + + /** Inputs for this component's asset. **/ + TArray Inputs; + + /** Instance inputs for this component's asset **/ + TArray InstanceInputs; + + /** Material assignments. **/ + UHoudiniAssetComponentMaterials_V1 * HoudiniAssetComponentMaterials; + + /** Map of HAPI objects and corresponding static meshes. Also map of static meshes and corresponding components. **/ + TMap StaticMeshes; + TMap StaticMeshComponents; + + /** List of dependent downstream asset connections that have this asset as an asset input. **/ + TMap> DownstreamAssetConnections; + + /** Map of asset handle components. **/ + TMap HandleComponents; + + /** Map of curve / spline components. **/ + TMap SplineComponents; + + /** Map of Landscape / Heightfield components. **/ + TMap> LandscapeComponents; + + /** Overrides for baking names per part */ + TMap BakeNameOverrides; + + /** Import axis. **/ + uint8 ImportAxis; + + /** Flags used by Houdini component. **/ + union + { + struct + { + /** Enables cooking for this Houdini Asset. **/ + uint32 bEnableCooking : 1; + + /** Enables uploading of transformation changes back to Houdini Engine. **/ + uint32 bUploadTransformsToHoudiniEngine : 1; + + /** Enables cooking upon transformation changes. **/ + uint32 bTransformChangeTriggersCooks : 1; + + /** Is set to true when this component contains Houdini logo geometry. **/ + uint32 bContainsHoudiniLogoGeometry : 1; + + /** Is set to true when this component is native and false is when it is dynamic. **/ + uint32 bIsNativeComponent : 1; + + /** Is set to true when this component belongs to a preview actor. **/ + uint32 bIsPreviewComponent : 1; + + /** Is set to true if this component has been loaded. **/ + uint32 bLoadedComponent : 1; + + /** Unused **/ + uint32 bIsPlayModeActive_Unused : 1; + + /** unused flag **/ + uint32 bTimeCookInPlaymode_Unused : 1; + + /** Is set to true when Houdini materials are used. **/ + uint32 bUseHoudiniMaterials : 1; + + /** Is set to true when cooking this asset will trigger cooks of downstream connected assets. **/ + uint32 bCookingTriggersDownstreamCooks : 1; + + /** Is set to true after the asset is fully loaded and registered **/ + uint32 bFullyLoaded : 1; + }; + + uint32 HoudiniAssetComponentFlagsPacked; + }; +}; + +UCLASS() +class UHoudiniInstancedActorComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC); + + //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) + UObject* InstancedAsset; + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + TArray Instances; +}; + +UCLASS() +class UHoudiniMeshSplitInstancerComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC); + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + TArray Instances; + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + UMaterialInterface* OverrideMaterial; + + //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) + UStaticMesh* InstancedMesh; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp index 272172316..9e71ff1ac 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp @@ -1,32 +1,32 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineCopyPropertiesInterface.h" - -void IHoudiniEngineCopyPropertiesInterface::CopyPropertiesFrom(UObject* FromObject) -{ - -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineCopyPropertiesInterface.h" + +void IHoudiniEngineCopyPropertiesInterface::CopyPropertiesFrom(UObject* FromObject) +{ + +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h index b2d51a8dc..aa49d8f5a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Engine/Engine.h" -#include "UObject/ObjectMacros.h" -#include "UObject/Interface.h" -#include "HoudiniEngineCopyPropertiesInterface.generated.h" - - -UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) -class UHoudiniEngineCopyPropertiesInterface : public UInterface -{ - GENERATED_BODY() -}; - -class HOUDINIENGINERUNTIME_API IHoudiniEngineCopyPropertiesInterface -{ - GENERATED_BODY() - -public: - virtual void CopyPropertiesFrom(UObject* FromObject); -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Engine/Engine.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Interface.h" +#include "HoudiniEngineCopyPropertiesInterface.generated.h" + + +UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) +class UHoudiniEngineCopyPropertiesInterface : public UInterface +{ + GENERATED_BODY() +}; + +class HOUDINIENGINERUNTIME_API IHoudiniEngineCopyPropertiesInterface +{ + GENERATED_BODY() + +public: + virtual void CopyPropertiesFrom(UObject* FromObject); +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp index b4170bf63..a10eb445d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp @@ -1,323 +1,323 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" - -#include "HoudiniAssetComponent.h" - -#include "Modules/ModuleManager.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FHoudiniEngineRuntime, HoudiniEngineRuntime); -DEFINE_LOG_CATEGORY(LogHoudiniEngineRuntime); - -FHoudiniEngineRuntime * -FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; - - -FHoudiniEngineRuntime & -FHoudiniEngineRuntime::Get() -{ - return *HoudiniEngineRuntimeInstance; -} - - -bool -FHoudiniEngineRuntime::IsInitialized() -{ - return FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance != nullptr; -} - - -FHoudiniEngineRuntime::FHoudiniEngineRuntime() -{ -} - - -void FHoudiniEngineRuntime::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module - // Store the instance. - FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = this; -} - - -void FHoudiniEngineRuntime::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. - FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; -} - - -int32 -FHoudiniEngineRuntime::GetRegisteredHoudiniComponentCount() -{ - if (!IsInitialized()) - return 0; - - FScopeLock ScopeLock(&CriticalSection); - return RegisteredHoudiniComponents.Num(); -} - - -UHoudiniAssetComponent* -FHoudiniEngineRuntime::GetRegisteredHoudiniComponentAt(const int32& Index) -{ - if (!IsInitialized()) - return nullptr; - - FScopeLock ScopeLock(&CriticalSection); - - if (!RegisteredHoudiniComponents.IsValidIndex(Index)) - return nullptr; - - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Index]; - if (!Ptr.IsValid()) - return nullptr; - - if (Ptr.IsStale()) - return nullptr; - - return Ptr.Get(); -} - - -void -FHoudiniEngineRuntime::CleanUpRegisteredHoudiniComponents() -{ - // Remove Stale and invalid components - FScopeLock ScopeLock(&CriticalSection); - for (int Idx = RegisteredHoudiniComponents.Num() - 1; Idx >= 0; Idx--) - { - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Idx]; - if ( !Ptr.IsValid() || Ptr.IsStale() ) - { - UnRegisterHoudiniComponent(Idx); - continue; - } - - UHoudiniAssetComponent* CurrentHAC = Ptr.Get(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) - { - UnRegisterHoudiniComponent(Idx); - continue; - } - } -} - - -bool -FHoudiniEngineRuntime::IsComponentRegistered(UHoudiniAssetComponent* HAC) const -{ - // No need for duplicates - if (HAC && RegisteredHoudiniComponents.Find(HAC) != INDEX_NONE) - return true; - - return false; -} - - -void -FHoudiniEngineRuntime::RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype) -{ - if (!FHoudiniEngineRuntime::IsInitialized()) - return; - - if (!HAC || HAC->IsPendingKill()) - return; - - // RF_Transient indicates a temporary/preview object - // No need to instantiate/cook those in Houdini - // RF_ArchetypeObject is the template for blueprinted HDA, so we need to be able to register those. - if (HAC->HasAnyFlags(RF_Transient) || (HAC->HasAnyFlags(RF_ArchetypeObject) && !bAllowArchetype) || HAC->HasAnyFlags(RF_ClassDefaultObject)) - return; - - // No need for duplicates - if (IsComponentRegistered(HAC)) - return; - - HOUDINI_BP_MESSAGE(TEXT("[FHoudiniEngineRuntime::RegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); - - // Before adding, clean up the all ready registered - CleanUpRegisteredHoudiniComponents(); - - // Add the new component - { - FScopeLock ScopeLock(&CriticalSection); - RegisteredHoudiniComponents.Add(HAC); - } - - HAC->NotifyHoudiniRegisterCompleted(); -} - - -void -FHoudiniEngineRuntime::MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent) -{ - if (InNodeId >= 0) - { - // FDebug::DumpStackTraceToLog(); - - NodeIdsPendingDelete.AddUnique(InNodeId); - - if (bDeleteParent) - { - NodeIdsParentPendingDelete.AddUnique(InNodeId); - } - } -} - - -void -FHoudiniEngineRuntime::UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC) -{ - if (!IsInitialized()) - return; - - if (!HAC || HAC->IsPendingKill()) - return; - - // Calling GetPathName here may lead to some crashes due to invalid outers... - //HOUDINI_LOG_DISPLAY(TEXT("[FHoudiniEngineRuntime::UnRegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); - - FScopeLock ScopeLock(&CriticalSection); - - int32 FoundIdx = RegisteredHoudiniComponents.Find(HAC); - if (!RegisteredHoudiniComponents.IsValidIndex(FoundIdx)) - return; - HAC->NotifyHoudiniPreUnregister(); - UnRegisterHoudiniComponent(FoundIdx); - HAC->NotifyHoudiniPostUnregister(); -} - - -void -FHoudiniEngineRuntime::UnRegisterHoudiniComponent(const int32& ValidIndex) -{ - if (!IsInitialized()) - return; - - FScopeLock ScopeLock(&CriticalSection); - - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[ValidIndex]; - if (Ptr.IsValid(true, false)) - { - UHoudiniAssetComponent* HAC = Ptr.Get(); - if (HAC && HAC->CanDeleteHoudiniNodes()) - { - MarkNodeIdAsPendingDelete(HAC->GetAssetId(), true); - } - } - - RegisteredHoudiniComponents.RemoveAt(ValidIndex); -} - - -int32 -FHoudiniEngineRuntime::GetNodeIdsPendingDeleteCount() -{ - if (!IsInitialized()) - return 0; - - FScopeLock ScopeLock(&CriticalSection); - - return NodeIdsPendingDelete.Num(); -} - - -int32 -FHoudiniEngineRuntime::GetNodeIdsPendingDeleteAt(const int32& Index) -{ - if (!IsInitialized()) - return -1; - - FScopeLock ScopeLock(&CriticalSection); - - if (!NodeIdsPendingDelete.IsValidIndex(Index)) - return -1; - - return NodeIdsPendingDelete[Index]; -} - - -void -FHoudiniEngineRuntime::RemoveNodeIdPendingDeleteAt(const int32& Index) -{ - if (!IsInitialized()) - return; - - FScopeLock ScopeLock(&CriticalSection); - if (!NodeIdsPendingDelete.IsValidIndex(Index)) - return; - - NodeIdsPendingDelete.RemoveAt(Index); -} - - -bool -FHoudiniEngineRuntime::IsParentNodePendingDelete(const int32& NodeId) -{ - return NodeIdsParentPendingDelete.Contains(NodeId); -} - - -void -FHoudiniEngineRuntime::RemoveParentNodePendingDelete(const int32& NodeId) -{ - if (NodeIdsParentPendingDelete.Contains(NodeId)) - NodeIdsParentPendingDelete.Remove(NodeId); -} - - -FString -FHoudiniEngineRuntime::GetDefaultTemporaryCookFolder() const -{ - // Get Runtime settings to get the Temp Cook Folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (!HoudiniRuntimeSettings) - return HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - - return HoudiniRuntimeSettings->DefaultTemporaryCookFolder; -} - - -FString -FHoudiniEngineRuntime::GetDefaultBakeFolder() const -{ - // Get Runtime settings to get the default bake Folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (!HoudiniRuntimeSettings) - return HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - return HoudiniRuntimeSettings->DefaultBakeFolder; -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" + +#include "HoudiniAssetComponent.h" + +#include "Modules/ModuleManager.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHoudiniEngineRuntime, HoudiniEngineRuntime); +DEFINE_LOG_CATEGORY(LogHoudiniEngineRuntime); + +FHoudiniEngineRuntime * +FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; + + +FHoudiniEngineRuntime & +FHoudiniEngineRuntime::Get() +{ + return *HoudiniEngineRuntimeInstance; +} + + +bool +FHoudiniEngineRuntime::IsInitialized() +{ + return FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance != nullptr; +} + + +FHoudiniEngineRuntime::FHoudiniEngineRuntime() +{ +} + + +void FHoudiniEngineRuntime::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + // Store the instance. + FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = this; +} + + +void FHoudiniEngineRuntime::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. + FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; +} + + +int32 +FHoudiniEngineRuntime::GetRegisteredHoudiniComponentCount() +{ + if (!IsInitialized()) + return 0; + + FScopeLock ScopeLock(&CriticalSection); + return RegisteredHoudiniComponents.Num(); +} + + +UHoudiniAssetComponent* +FHoudiniEngineRuntime::GetRegisteredHoudiniComponentAt(const int32& Index) +{ + if (!IsInitialized()) + return nullptr; + + FScopeLock ScopeLock(&CriticalSection); + + if (!RegisteredHoudiniComponents.IsValidIndex(Index)) + return nullptr; + + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Index]; + if (!Ptr.IsValid()) + return nullptr; + + if (Ptr.IsStale()) + return nullptr; + + return Ptr.Get(); +} + + +void +FHoudiniEngineRuntime::CleanUpRegisteredHoudiniComponents() +{ + // Remove Stale and invalid components + FScopeLock ScopeLock(&CriticalSection); + for (int Idx = RegisteredHoudiniComponents.Num() - 1; Idx >= 0; Idx--) + { + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Idx]; + if ( !Ptr.IsValid() || Ptr.IsStale() ) + { + UnRegisterHoudiniComponent(Idx); + continue; + } + + UHoudiniAssetComponent* CurrentHAC = Ptr.Get(); + if (!CurrentHAC || CurrentHAC->IsPendingKill()) + { + UnRegisterHoudiniComponent(Idx); + continue; + } + } +} + + +bool +FHoudiniEngineRuntime::IsComponentRegistered(UHoudiniAssetComponent* HAC) const +{ + // No need for duplicates + if (HAC && RegisteredHoudiniComponents.Find(HAC) != INDEX_NONE) + return true; + + return false; +} + + +void +FHoudiniEngineRuntime::RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype) +{ + if (!FHoudiniEngineRuntime::IsInitialized()) + return; + + if (!HAC || HAC->IsPendingKill()) + return; + + // RF_Transient indicates a temporary/preview object + // No need to instantiate/cook those in Houdini + // RF_ArchetypeObject is the template for blueprinted HDA, so we need to be able to register those. + if (HAC->HasAnyFlags(RF_Transient) || (HAC->HasAnyFlags(RF_ArchetypeObject) && !bAllowArchetype) || HAC->HasAnyFlags(RF_ClassDefaultObject)) + return; + + // No need for duplicates + if (IsComponentRegistered(HAC)) + return; + + HOUDINI_BP_MESSAGE(TEXT("[FHoudiniEngineRuntime::RegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); + + // Before adding, clean up the all ready registered + CleanUpRegisteredHoudiniComponents(); + + // Add the new component + { + FScopeLock ScopeLock(&CriticalSection); + RegisteredHoudiniComponents.Add(HAC); + } + + HAC->NotifyHoudiniRegisterCompleted(); +} + + +void +FHoudiniEngineRuntime::MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent) +{ + if (InNodeId >= 0) + { + // FDebug::DumpStackTraceToLog(); + + NodeIdsPendingDelete.AddUnique(InNodeId); + + if (bDeleteParent) + { + NodeIdsParentPendingDelete.AddUnique(InNodeId); + } + } +} + + +void +FHoudiniEngineRuntime::UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC) +{ + if (!IsInitialized()) + return; + + if (!HAC || HAC->IsPendingKill()) + return; + + // Calling GetPathName here may lead to some crashes due to invalid outers... + //HOUDINI_LOG_DISPLAY(TEXT("[FHoudiniEngineRuntime::UnRegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); + + FScopeLock ScopeLock(&CriticalSection); + + int32 FoundIdx = RegisteredHoudiniComponents.Find(HAC); + if (!RegisteredHoudiniComponents.IsValidIndex(FoundIdx)) + return; + HAC->NotifyHoudiniPreUnregister(); + UnRegisterHoudiniComponent(FoundIdx); + HAC->NotifyHoudiniPostUnregister(); +} + + +void +FHoudiniEngineRuntime::UnRegisterHoudiniComponent(const int32& ValidIndex) +{ + if (!IsInitialized()) + return; + + FScopeLock ScopeLock(&CriticalSection); + + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[ValidIndex]; + if (Ptr.IsValid(true, false)) + { + UHoudiniAssetComponent* HAC = Ptr.Get(); + if (HAC && HAC->CanDeleteHoudiniNodes()) + { + MarkNodeIdAsPendingDelete(HAC->GetAssetId(), true); + } + } + + RegisteredHoudiniComponents.RemoveAt(ValidIndex); +} + + +int32 +FHoudiniEngineRuntime::GetNodeIdsPendingDeleteCount() +{ + if (!IsInitialized()) + return 0; + + FScopeLock ScopeLock(&CriticalSection); + + return NodeIdsPendingDelete.Num(); +} + + +int32 +FHoudiniEngineRuntime::GetNodeIdsPendingDeleteAt(const int32& Index) +{ + if (!IsInitialized()) + return -1; + + FScopeLock ScopeLock(&CriticalSection); + + if (!NodeIdsPendingDelete.IsValidIndex(Index)) + return -1; + + return NodeIdsPendingDelete[Index]; +} + + +void +FHoudiniEngineRuntime::RemoveNodeIdPendingDeleteAt(const int32& Index) +{ + if (!IsInitialized()) + return; + + FScopeLock ScopeLock(&CriticalSection); + if (!NodeIdsPendingDelete.IsValidIndex(Index)) + return; + + NodeIdsPendingDelete.RemoveAt(Index); +} + + +bool +FHoudiniEngineRuntime::IsParentNodePendingDelete(const int32& NodeId) +{ + return NodeIdsParentPendingDelete.Contains(NodeId); +} + + +void +FHoudiniEngineRuntime::RemoveParentNodePendingDelete(const int32& NodeId) +{ + if (NodeIdsParentPendingDelete.Contains(NodeId)) + NodeIdsParentPendingDelete.Remove(NodeId); +} + + +FString +FHoudiniEngineRuntime::GetDefaultTemporaryCookFolder() const +{ + // Get Runtime settings to get the Temp Cook Folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (!HoudiniRuntimeSettings) + return HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + + return HoudiniRuntimeSettings->DefaultTemporaryCookFolder; +} + + +FString +FHoudiniEngineRuntime::GetDefaultBakeFolder() const +{ + // Get Runtime settings to get the default bake Folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (!HoudiniRuntimeSettings) + return HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + return HoudiniRuntimeSettings->DefaultBakeFolder; +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h index d1ebc3c95..ecf7c8bfd 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h @@ -1,107 +1,107 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" -#include "HoudiniPDGAssetLink.h" - -#include "Modules/ModuleInterface.h" -#include "Misc/ScopeLock.h" -#include "UObject/WeakObjectPtrTemplates.h" - -class HOUDINIENGINERUNTIME_API FHoudiniEngineRuntime : public IModuleInterface -{ - public: - FHoudiniEngineRuntime(); - - // - // IModuleInterface methods. - // - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // Return singleton instance of Houdini Engine Runtime, used internally. - static FHoudiniEngineRuntime & Get(); - - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // - // Houdini Asset Component registry - // - // Ensure that the registered components are all still valid - void CleanUpRegisteredHoudiniComponents(); - - void RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype=false); - - void UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC); - void UnRegisterHoudiniComponent(const int32& ValidIdx); - - bool IsComponentRegistered(UHoudiniAssetComponent* HAC) const; - int32 GetRegisteredHoudiniComponentCount(); - UHoudiniAssetComponent* GetRegisteredHoudiniComponentAt(const int32& Index); - - virtual TArray>* GetRegisteredHoudiniComponents() { return &RegisteredHoudiniComponents; }; - - // - // Node deletion - // - void MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent = false); - - int32 GetNodeIdsPendingDeleteCount(); - int32 GetNodeIdsPendingDeleteAt(const int32& Index); - void RemoveNodeIdPendingDeleteAt(const int32& Index); - - bool IsParentNodePendingDelete(const int32& NodeId); - - void RemoveParentNodePendingDelete(const int32& NodeId); - - // - // - // - - // Returns the folder to be used for temporary cook content - FString GetDefaultTemporaryCookFolder() const; - - // Returns the defualt folder used for baking - FString GetDefaultBakeFolder() const; - - private: - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // Singleton instance. - static FHoudiniEngineRuntime * HoudiniEngineRuntimeInstance; - - // - TArray> RegisteredHoudiniComponents; - - TArray NodeIdsPendingDelete; - - TArray NodeIdsParentPendingDelete; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" +#include "HoudiniPDGAssetLink.h" + +#include "Modules/ModuleInterface.h" +#include "Misc/ScopeLock.h" +#include "UObject/WeakObjectPtrTemplates.h" + +class HOUDINIENGINERUNTIME_API FHoudiniEngineRuntime : public IModuleInterface +{ + public: + FHoudiniEngineRuntime(); + + // + // IModuleInterface methods. + // + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // Return singleton instance of Houdini Engine Runtime, used internally. + static FHoudiniEngineRuntime & Get(); + + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // + // Houdini Asset Component registry + // + // Ensure that the registered components are all still valid + void CleanUpRegisteredHoudiniComponents(); + + void RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype=false); + + void UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC); + void UnRegisterHoudiniComponent(const int32& ValidIdx); + + bool IsComponentRegistered(UHoudiniAssetComponent* HAC) const; + int32 GetRegisteredHoudiniComponentCount(); + UHoudiniAssetComponent* GetRegisteredHoudiniComponentAt(const int32& Index); + + virtual TArray>* GetRegisteredHoudiniComponents() { return &RegisteredHoudiniComponents; }; + + // + // Node deletion + // + void MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent = false); + + int32 GetNodeIdsPendingDeleteCount(); + int32 GetNodeIdsPendingDeleteAt(const int32& Index); + void RemoveNodeIdPendingDeleteAt(const int32& Index); + + bool IsParentNodePendingDelete(const int32& NodeId); + + void RemoveParentNodePendingDelete(const int32& NodeId); + + // + // + // + + // Returns the folder to be used for temporary cook content + FString GetDefaultTemporaryCookFolder() const; + + // Returns the defualt folder used for baking + FString GetDefaultBakeFolder() const; + + private: + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // Singleton instance. + static FHoudiniEngineRuntime * HoudiniEngineRuntimeInstance; + + // + TArray> RegisteredHoudiniComponents; + + TArray NodeIdsPendingDelete; + + TArray NodeIdsParentPendingDelete; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h index 9c5329d83..df2294efb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h @@ -1,219 +1,234 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Logging/LogMacros.h" - -// Define module names. -#define HOUDINI_MODULE "HoudiniEngine" -#define HOUDINI_MODULE_EDITOR "HoudiniEngineEditor" -#define HOUDINI_MODULE_RUNTIME "HoudiniEngineRuntime" - -// Declare the log category depending on the module we're in -#ifdef HOUDINI_ENGINE_EDITOR - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_EDITOR - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, All); -#else - #ifdef HOUDINI_ENGINE - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngine, Log, All); - #else - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineRuntime, Log, All); - #endif -#endif - -//--------------------------------------------------------------------------------------------------------------------- -// Default session settings -//--------------------------------------------------------------------------------------------------------------------- - -#define HAPI_UNREAL_SESSION_SERVER_AUTOSTART true -#define HAPI_UNREAL_SESSION_SERVER_TIMEOUT 3000.0f -#define HAPI_UNREAL_SESSION_SERVER_HOST TEXT( "localhost" ) -#define HAPI_UNREAL_SESSION_SERVER_PORT 9090 -#if PLATFORM_MAC - #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "/tmp/hapi" ) -#else - #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "hapi" ) -#endif - - - -// Names of HAPI libraries on different platforms. -#define HAPI_LIB_OBJECT_WINDOWS TEXT( "libHAPIL.dll" ) -#define HAPI_LIB_OBJECT_MAC TEXT( "libHAPIL.dylib" ) -#define HAPI_LIB_OBJECT_LINUX TEXT( "libHAPIL.so" ) - -//--------------------------------------------------------------------------------------------------------------------- -// LOG MACROS -//--------------------------------------------------------------------------------------------------------------------- - -// Whether to enable logging. -#define HOUDINI_ENGINE_LOGGING 1 - -#ifdef HOUDINI_ENGINE_LOGGING - #ifdef HOUDINI_ENGINE - #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngine, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #else - #ifdef HOUDINI_ENGINE_EDITOR - #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngineEditor, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #else - #define HOUDINI_LOG_HELPER( VERBOSITY, HOUDINI_LOG_TEXT, ... ) \ - do \ - { \ - UE_LOG( LogHoudiniEngineRuntime, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #endif - #endif - - #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Display, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) -#endif - -// HOUDINI_ENGINE_DEBUG_BP: blueprint related debug logging -#ifndef HOUDINI_ENGINE_DEBUG_BP - #define HOUDINI_ENGINE_DEBUG_BP 0 -#endif - -// NOTE: Set HOUDINI_ENGINE_DEBUG_BP to enable BP logging -#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_BP - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); - - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); - - #define HOUDINI_BP_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngineBlueprint, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - - #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() - #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - -// HOUDINI_ENGINE_DEBUG_PDG: PDG related debug logging -#ifndef HOUDINI_ENGINE_DEBUG_PDG - #define HOUDINI_ENGINE_DEBUG_PDG 0 -#endif - -// NOTE: Set HOUDINI_ENGINE_DEBUG_PDG to enable BP logging -#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_PDG - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEnginePDG, Log, All); - - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); - - #define HOUDINI_PDG_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEnginePDG, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - - #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() - #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - -// HAPI_Common -enum HAPI_UNREAL_NodeType -{ - HAPI_UNREAL_NODETYPE_ANY = -1, - HAPU_UNREAL_NODETYPE_NONE = 0 -}; - -enum HAPI_UNREAL_NodeFlags -{ - HAPI_UNREAL_NODEFLAGS_ANY = -1, - HAPI_UNREAL_NODEFLAGS_NONE = 0 -}; - -// Default cook/bake folder -#define HAPI_UNREAL_DEFAULT_BAKE_FOLDER TEXT("/Game/HoudiniEngine/Bake"); -#define HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER TEXT("/Game/HoudiniEngine/Temp"); - -// Default PDG Filters -#define HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER "HE_"; -#define HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER "HE_OUT_"; - - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" + +// Define module names. +#define HOUDINI_MODULE "HoudiniEngine" +#define HOUDINI_MODULE_EDITOR "HoudiniEngineEditor" +#define HOUDINI_MODULE_RUNTIME "HoudiniEngineRuntime" + +// Declare the log category depending on the module we're in +#ifdef HOUDINI_ENGINE_EDITOR + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_EDITOR +HOUDINIENGINEEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, All); +#else + #ifdef HOUDINI_ENGINE + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE + HOUDINIENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngine, Log, All); + #else + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME + HOUDINIENGINERUNTIME_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineRuntime, Log, All); + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------- +// Default session settings +//--------------------------------------------------------------------------------------------------------------------- + +#define HAPI_UNREAL_SESSION_SERVER_AUTOSTART true +#define HAPI_UNREAL_SESSION_SERVER_TIMEOUT 3000.0f +#define HAPI_UNREAL_SESSION_SERVER_HOST TEXT( "localhost" ) +#define HAPI_UNREAL_SESSION_SERVER_PORT 9090 +#if PLATFORM_MAC + #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "/tmp/hapi" ) +#else + #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "hapi" ) +#endif + + + +// Names of HAPI libraries on different platforms. +#define HAPI_LIB_OBJECT_WINDOWS TEXT( "libHAPIL.dll" ) +#define HAPI_LIB_OBJECT_MAC TEXT( "libHAPIL.dylib" ) +#define HAPI_LIB_OBJECT_LINUX TEXT( "libHAPIL.so" ) + +//--------------------------------------------------------------------------------------------------------------------- +// LOG MACROS +//--------------------------------------------------------------------------------------------------------------------- + +// Whether to enable logging. +#define HOUDINI_ENGINE_LOGGING 1 + +#ifdef HOUDINI_ENGINE_LOGGING + #ifdef HOUDINI_ENGINE + #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngine, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #else + #ifdef HOUDINI_ENGINE_EDITOR + #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngineEditor, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #else + #define HOUDINI_LOG_HELPER( VERBOSITY, HOUDINI_LOG_TEXT, ... ) \ + do \ + { \ + UE_LOG( LogHoudiniEngineRuntime, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #endif + #endif + + #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Display, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) +#endif + +// HOUDINI_ENGINE_DEBUG_BP: blueprint related debug logging +#ifndef HOUDINI_ENGINE_DEBUG_BP + #define HOUDINI_ENGINE_DEBUG_BP 0 +#endif + +// NOTE: Set HOUDINI_ENGINE_DEBUG_BP to enable BP logging +#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_BP + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); + + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); + + #define HOUDINI_BP_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngineBlueprint, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + + #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_BP_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_BP_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_BP_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_BP_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() + #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + +// HOUDINI_ENGINE_DEBUG_PDG: PDG related debug logging +#ifndef HOUDINI_ENGINE_DEBUG_PDG + #define HOUDINI_ENGINE_DEBUG_PDG 0 +#endif + +// NOTE: Set HOUDINI_ENGINE_DEBUG_PDG to enable BP logging +#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_PDG + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEnginePDG, Log, All); + + #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); + + #define HOUDINI_PDG_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEnginePDG, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + + #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_PDG_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_PDG_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_PDG_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_PDG_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() + #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + +// HAPI_Common +enum HAPI_UNREAL_NodeType +{ + HAPI_UNREAL_NODETYPE_ANY = -1, + HAPU_UNREAL_NODETYPE_NONE = 0 +}; + +enum HAPI_UNREAL_NodeFlags +{ + HAPI_UNREAL_NODEFLAGS_ANY = -1, + HAPI_UNREAL_NODEFLAGS_NONE = 0 +}; + +// Default cook/bake folder +#define HAPI_UNREAL_DEFAULT_BAKE_FOLDER TEXT("/Game/HoudiniEngine/Bake"); +#define HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER TEXT("/Game/HoudiniEngine/Temp"); + +// Default PDG Filters +#define HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER "HE_"; +#define HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER "HE_OUT_"; + +// Struct to enable global silent flag - this will force dialogs to not show up. +struct FHoudiniScopedGlobalSilence +{ + FHoudiniScopedGlobalSilence() + { + bGlobalSilent = GIsSilent; + GIsSilent = true; + } + + ~FHoudiniScopedGlobalSilence() + { + GIsSilent = bGlobalSilent; + } + + bool bGlobalSilent; +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp index bf3c028f4..71c480e88 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp @@ -1,535 +1,562 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineRuntimeUtils.h" -#include "EngineUtils.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Kismet2/BlueprintEditorUtils.h" -#endif - - -FString -FHoudiniEngineRuntimeUtils::GetLibHAPIName() -{ - static const FString LibHAPIName = - -#if PLATFORM_WINDOWS - HAPI_LIB_OBJECT_WINDOWS; -#elif PLATFORM_MAC - HAPI_LIB_OBJECT_MAC; -#elif PLATFORM_LINUX - HAPI_LIB_OBJECT_LINUX; -#else - TEXT(""); -#endif - - return LibHAPIName; -} - - -void -FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes) -{ - OutBBoxes.Empty(); - - for (auto CurrentActor : InActors) - { - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true)); - } -} - - -bool -FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors) -{ - if (!IsValid(World)) - return false; - - OutActors.Empty(); - for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) - { - AActor* CurrentActor = *ActorItr; - if (!IsValid(CurrentActor)) - continue; - - if (!CurrentActor->GetClass()->IsChildOf(ActorType.Get())) - continue; - - if (ExcludeActors && ExcludeActors->Contains(CurrentActor)) - continue; - - // Special case - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); - for (auto InBounds : BBoxes) - { - // Check if both actor's bounds intersects - if (!ActorBounds.Intersect(InBounds)) - continue; - - OutActors.Add(CurrentActor); - break; - } - } - - return true; -} - -bool -FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly) -{ - bool bDeleted = false; - OutPackage = nullptr; - bOutPackageIsInMemoryOnly = false; - - if (!IsValid(InObjectToDelete)) - return false; - - // Don't try to delete the object if it has references (we do this here to avoid the FMessageDialog in DeleteSingleObject - bool bIsReferenced = false; - bool bIsReferencedByUndo = false; - if (!GatherObjectReferencersForDeletion(InObjectToDelete, bIsReferenced, bIsReferencedByUndo)) - return false; - - if (bIsReferenced) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject] Not deleting %s: there are still references to it."), *InObjectToDelete->GetFullName()); - } - else - { - // Even though we already checked for references, we still let DeleteSingleObject check for references, since - // we want that code path where it'll clean up in-memory references (undo buffer/transactions) - const bool bCheckForReferences = true; - if (DeleteSingleObject(InObjectToDelete, bCheckForReferences)) - { - bDeleted = true; - - OutPackage = InObjectToDelete->GetOutermost(); - - FString PackageFilename; - if (!IsValid(OutPackage) || !FPackageName::DoesPackageExist(OutPackage->GetName(), nullptr, &PackageFilename)) - { - // Package is in memory only, we don't have call CleanUpAfterSuccessfulDelete on it, just do garbage - // collection to pick up the stale package - bOutPackageIsInMemoryOnly = true; - } - else - { - // There is an on-disk package that is now potentially empty, CleanUpAfterSuccessfulDelete must be - // called on this. Since CleanUpAfterSuccessfulDelete does garbage collection, we return the Package - // as part of this function so that the caller can collect all Packages and do one call to - // CleanUpAfterSuccessfulDelete with an array - } - } - } - - return bDeleted; -} - -int32 -FHoudiniEngineRuntimeUtils::SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted) -{ - int32 NumDeleted = 0; - bool bGarbageCollectionRequired = false; - TSet PackagesToCleanUp; - TSet ProcessedObjects; - while (InObjectsToDelete.Num() > 0) - { - UObject* const ObjectToDelete = InObjectsToDelete.Pop(); - - if (ProcessedObjects.Contains(ObjectToDelete)) - continue; - - ProcessedObjects.Add(ObjectToDelete); - - if (!IsValid(ObjectToDelete)) - continue; - - UPackage* Package = nullptr; - bool bInMemoryPackageOnly = false; - if (SafeDeleteSingleObject(ObjectToDelete, Package, bInMemoryPackageOnly)) - { - NumDeleted++; - if (bInMemoryPackageOnly) - { - // Packages that are in-memory only are cleaned up by garbage collection - if (!bGarbageCollectionRequired) - bGarbageCollectionRequired = true; - } - else - { - // Clean up potentially empty packages in one call to CleanupAfterSuccessfulDelete at the end - PackagesToCleanUp.Add(Package); - } - } - else if (OutObjectsNotDeleted) - { - OutObjectsNotDeleted->Add(ObjectToDelete); - } - } - - // CleanupAfterSuccessfulDelete calls CollectGarbage, so don't call it here if we have PackagesToCleanUp - if (bGarbageCollectionRequired && PackagesToCleanUp.Num() <= 0) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - if (PackagesToCleanUp.Num() > 0) - CleanupAfterSuccessfulDelete(PackagesToCleanUp.Array()); - - return NumDeleted; -} - - -#if WITH_EDITOR -int32 -FHoudiniEngineRuntimeUtils::CopyComponentProperties(UActorComponent* SourceComponent, UActorComponent* TargetComponent, const EditorUtilities::FCopyOptions& Options) -{ - UClass* ComponentClass = SourceComponent->GetClass(); - check( ComponentClass == TargetComponent->GetClass() ); - - const bool bIsPreviewing = ( Options.Flags & EditorUtilities::ECopyOptions::PreviewOnly ) != 0; - int32 CopiedPropertyCount = 0; - bool bTransformChanged = false; - - // Build a list of matching component archetype instances for propagation (if requested) - TArray ComponentArchetypeInstances; - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - TArray Instances; - TargetComponent->GetArchetypeInstances(Instances); - for(UObject* ObjInstance : Instances) - { - UActorComponent* ComponentInstance = Cast(ObjInstance); - if (ComponentInstance && ComponentInstance != SourceComponent && ComponentInstance != TargetComponent) - { - ComponentArchetypeInstances.Add(ComponentInstance); - } - } - } - - TSet SourceUCSModifiedProperties; - SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties); - - TArray ComponentInstancesToReregister; - - // Copy component properties - for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) - { - const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); - const bool bIsIdentical = Property->Identical_InContainer( SourceComponent, TargetComponent ); - const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); - const bool bIsTransform = - Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); - - // auto SourceComponentIsRoot = [&]() - // { - // USceneComponent* RootComponent = SourceActor->GetRootComponent(); - // if (SourceComponent == RootComponent) - // { - // return true; - // } - // else if (RootComponent == nullptr && bSourceActorIsBPCDO) - // { - // // If we're dealing with a BP CDO as source, then look at the target for whether this is the root component - // return (TargetComponent == TargetActor->GetRootComponent()); - // } - // return false; - // }; - - TSet ModifiedObjects; - // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - // && ( !bIsTransform || (!bSourceActorIsCDO && !bTargetActorIsCDO) || !SourceComponentIsRoot() ) ) - if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - && ( !bIsTransform )) - { - const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) - && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); - if( bIsSafeToCopy ) - { - // if (!Options.CanCopyProperty(*Property, *SourceActor)) - // { - // continue; - // } - if (!Options.CanCopyProperty(*Property, *SourceComponent)) - { - continue; - } - - if( !bIsPreviewing ) - { - if( !ModifiedObjects.Contains(TargetComponent) ) - { - TargetComponent->SetFlags(RF_Transactional); - TargetComponent->Modify(); - ModifiedObjects.Add(TargetComponent); - } - - if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) - { - TargetComponent->PreEditChange( Property ); - } - - // Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances. - TArray ComponentArchetypeInstancesToChange; - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances) - { - if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) ) - { - bool bAdd = true; - // We also need to double check that either the direct archetype of the target is also identical - if (ComponentArchetypeInstance->GetArchetype() != TargetComponent) - { - UActorComponent* CheckComponent = CastChecked(ComponentArchetypeInstance->GetArchetype()); - while (CheckComponent != ComponentArchetypeInstance) - { - if (!Property->Identical_InContainer( CheckComponent, TargetComponent )) - { - bAdd = false; - break; - } - CheckComponent = CastChecked(CheckComponent->GetArchetype()); - } - } - - if (bAdd) - { - ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance ); - } - } - } - } - - EditorUtilities::CopySingleProperty(SourceComponent, TargetComponent, Property); - - if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) - { - FPropertyChangedEvent PropertyChangedEvent( Property ); - TargetComponent->PostEditChangeProperty( PropertyChangedEvent ); - } - - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex ) - { - UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex]; - if( ComponentArchetypeInstance != nullptr ) - { - if( !ModifiedObjects.Contains(ComponentArchetypeInstance) ) - { - // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. - // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. - if (!ComponentArchetypeInstance->IsCreatedByConstructionScript()) - { - ComponentArchetypeInstance->SetFlags(RF_Transactional); - ComponentArchetypeInstance->Modify(); - ModifiedObjects.Add(ComponentArchetypeInstance); - } - - // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. - AActor* Owner = ComponentArchetypeInstance->GetOwner(); - if( Owner != nullptr && !ModifiedObjects.Contains(Owner)) - { - Owner->Modify(); - ModifiedObjects.Add(Owner); - } - } - - if (ComponentArchetypeInstance->IsRegistered()) - { - ComponentArchetypeInstance->UnregisterComponent(); - ComponentInstancesToReregister.Add(ComponentArchetypeInstance); - } - - EditorUtilities::CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property ); - } - } - } - } - - ++CopiedPropertyCount; - - if( bIsTransform ) - { - bTransformChanged = true; - } - } - } - } - - for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister) - { - ModifiedComponentInstance->RegisterComponent(); - } - - return CopiedPropertyCount; -} -#endif - - -#if WITH_EDITOR -FBlueprintEditor* -FHoudiniEngineRuntimeUtils::GetBlueprintEditor(const UObject* InObject) -{ - if (!IsValid(InObject)) - return nullptr; - - UObject* Outer = InObject->GetOuter(); - if (!IsValid(Outer)) - return nullptr; - - UBlueprintGeneratedClass* OuterBPClass = Cast(Outer->GetClass()); - - if (!OuterBPClass) - return nullptr; - - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - return static_cast(AssetEditorSubsystem->FindEditorForAsset(OuterBPClass->ClassGeneratedBy, false)); -} -#endif - - -#if WITH_EDITOR -void -FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate) -{ - if (!ComponentTemplate) - return; - - UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); - if (!BPGC) - return; - - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!Blueprint) - return; - - Blueprint->Modify(); - - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(Blueprint, false)); - check(BlueprintEditor); - - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - TSharedPtr SCSEditor = nullptr; - - SCSEditor = BlueprintEditor->GetSCSEditor(); - check(SCSEditor); - SCSEditor->SaveSCSCurrentState(SCS); - - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); - - SCSEditor->UpdateTree(true); -} -#endif - -#if WITH_EDITOR -void -FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTemplate) -{ - if (!ComponentTemplate) - return; - - UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); - if (!BPGC) - return; - - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!Blueprint) - return; - - Blueprint->Modify(); - - FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); -} -#endif - - -#if WITH_EDITOR -void -FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) -{ - FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); - Obj->PostEditChangeProperty(Evt); -} - -void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FProperty* Property) -{ - FPropertyChangedEvent Evt(Property); - Obj->PostEditChangeProperty(Evt); -} - -void FHoudiniEngineRuntimeUtils::PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange) -{ - if (!InObject) - return; - if (!InObject->HasAnyFlags(RF_ArchetypeObject)) - return; - - // Iterate over the modified properties and propagate value changed to all archetype instances - TArray ArchetypeInstances; - InObject->GetArchetypeInstances(ArchetypeInstances); - for (UObject* Instance : ArchetypeInstances) - { - UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Found Archetype instance: %s"), *(Instance->GetPathName())); - for (FName PropertyName : DeltaChange.ChangedProperties) - { - UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Changed property: %s"), *(PropertyName.ToString())); - // FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation()); - } - } -} - -void FHoudiniEngineRuntimeUtils::ForAllArchetypeInstances(UObject* InTemplateObj, TFunctionRef Operation) -{ - if (!InTemplateObj) - return; - if (!InTemplateObj->HasAnyFlags(RF_ArchetypeObject|RF_DefaultSubObject)) - return; - - TArray Instances; - InTemplateObj->GetArchetypeInstances(Instances); - - for(UObject* Instance : Instances) - { - Operation(Instance); - } -} - - -#endif - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" + +#include "EngineUtils.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Kismet2/BlueprintEditorUtils.h" +#endif + + +FString +FHoudiniEngineRuntimeUtils::GetLibHAPIName() +{ + static const FString LibHAPIName = + +#if PLATFORM_WINDOWS + HAPI_LIB_OBJECT_WINDOWS; +#elif PLATFORM_MAC + HAPI_LIB_OBJECT_MAC; +#elif PLATFORM_LINUX + HAPI_LIB_OBJECT_LINUX; +#else + TEXT(""); +#endif + + return LibHAPIName; +} + + +void +FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes) +{ + OutBBoxes.Empty(); + + for (auto CurrentActor : InActors) + { + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true)); + } +} + + +bool +FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors) +{ + if (!IsValid(World)) + return false; + + OutActors.Empty(); + for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) + { + AActor* CurrentActor = *ActorItr; + if (!IsValid(CurrentActor)) + continue; + + if (!CurrentActor->GetClass()->IsChildOf(ActorType.Get())) + continue; + + if (ExcludeActors && ExcludeActors->Contains(CurrentActor)) + continue; + + // Special case + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); + for (auto InBounds : BBoxes) + { + // Check if both actor's bounds intersects + if (!ActorBounds.Intersect(InBounds)) + continue; + + OutActors.Add(CurrentActor); + break; + } + } + + return true; +} + +bool +FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly) +{ + bool bDeleted = false; + OutPackage = nullptr; + bOutPackageIsInMemoryOnly = false; + + if (!IsValid(InObjectToDelete)) + return false; + + // Don't try to delete the object if it has references (we do this here to avoid the FMessageDialog in DeleteSingleObject + bool bIsReferenced = false; + bool bIsReferencedByUndo = false; + if (!GatherObjectReferencersForDeletion(InObjectToDelete, bIsReferenced, bIsReferencedByUndo)) + return false; + + if (bIsReferenced) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject] Not deleting %s: there are still references to it."), *InObjectToDelete->GetFullName()); + } + else + { + // Even though we already checked for references, we still let DeleteSingleObject check for references, since + // we want that code path where it'll clean up in-memory references (undo buffer/transactions) + const bool bCheckForReferences = true; + if (DeleteSingleObject(InObjectToDelete, bCheckForReferences)) + { + bDeleted = true; + + OutPackage = InObjectToDelete->GetOutermost(); + + FString PackageFilename; + if (!IsValid(OutPackage) || !FPackageName::DoesPackageExist(OutPackage->GetName(), nullptr, &PackageFilename)) + { + // Package is in memory only, we don't have call CleanUpAfterSuccessfulDelete on it, just do garbage + // collection to pick up the stale package + bOutPackageIsInMemoryOnly = true; + } + else + { + // There is an on-disk package that is now potentially empty, CleanUpAfterSuccessfulDelete must be + // called on this. Since CleanUpAfterSuccessfulDelete does garbage collection, we return the Package + // as part of this function so that the caller can collect all Packages and do one call to + // CleanUpAfterSuccessfulDelete with an array + } + } + } + + return bDeleted; +} + +int32 +FHoudiniEngineRuntimeUtils::SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted) +{ + int32 NumDeleted = 0; + bool bGarbageCollectionRequired = false; + TSet PackagesToCleanUp; + TSet ProcessedObjects; + while (InObjectsToDelete.Num() > 0) + { + UObject* const ObjectToDelete = InObjectsToDelete.Pop(); + + if (ProcessedObjects.Contains(ObjectToDelete)) + continue; + + ProcessedObjects.Add(ObjectToDelete); + + if (!IsValid(ObjectToDelete)) + continue; + + UPackage* Package = nullptr; + bool bInMemoryPackageOnly = false; + if (SafeDeleteSingleObject(ObjectToDelete, Package, bInMemoryPackageOnly)) + { + NumDeleted++; + if (bInMemoryPackageOnly) + { + // Packages that are in-memory only are cleaned up by garbage collection + if (!bGarbageCollectionRequired) + bGarbageCollectionRequired = true; + } + else + { + // Clean up potentially empty packages in one call to CleanupAfterSuccessfulDelete at the end + PackagesToCleanUp.Add(Package); + } + } + else if (OutObjectsNotDeleted) + { + OutObjectsNotDeleted->Add(ObjectToDelete); + } + } + + // CleanupAfterSuccessfulDelete calls CollectGarbage, so don't call it here if we have PackagesToCleanUp + if (bGarbageCollectionRequired && PackagesToCleanUp.Num() <= 0) + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + if (PackagesToCleanUp.Num() > 0) + CleanupAfterSuccessfulDelete(PackagesToCleanUp.Array()); + + return NumDeleted; +} + + +#if WITH_EDITOR +int32 +FHoudiniEngineRuntimeUtils::CopyComponentProperties(UActorComponent* SourceComponent, UActorComponent* TargetComponent, const EditorUtilities::FCopyOptions& Options) +{ + UClass* ComponentClass = SourceComponent->GetClass(); + check( ComponentClass == TargetComponent->GetClass() ); + + const bool bIsPreviewing = ( Options.Flags & EditorUtilities::ECopyOptions::PreviewOnly ) != 0; + int32 CopiedPropertyCount = 0; + bool bTransformChanged = false; + + // Build a list of matching component archetype instances for propagation (if requested) + TArray ComponentArchetypeInstances; + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + TArray Instances; + TargetComponent->GetArchetypeInstances(Instances); + for(UObject* ObjInstance : Instances) + { + UActorComponent* ComponentInstance = Cast(ObjInstance); + if (ComponentInstance && ComponentInstance != SourceComponent && ComponentInstance != TargetComponent) + { + ComponentArchetypeInstances.Add(ComponentInstance); + } + } + } + + TSet SourceUCSModifiedProperties; + SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties); + + TArray ComponentInstancesToReregister; + + // Copy component properties + for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) + { + const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); + const bool bIsIdentical = Property->Identical_InContainer( SourceComponent, TargetComponent ); + const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); + const bool bIsTransform = + Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); + + // auto SourceComponentIsRoot = [&]() + // { + // USceneComponent* RootComponent = SourceActor->GetRootComponent(); + // if (SourceComponent == RootComponent) + // { + // return true; + // } + // else if (RootComponent == nullptr && bSourceActorIsBPCDO) + // { + // // If we're dealing with a BP CDO as source, then look at the target for whether this is the root component + // return (TargetComponent == TargetActor->GetRootComponent()); + // } + // return false; + // }; + + TSet ModifiedObjects; + // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + // && ( !bIsTransform || (!bSourceActorIsCDO && !bTargetActorIsCDO) || !SourceComponentIsRoot() ) ) + if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + && ( !bIsTransform )) + { + const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) + && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); + if( bIsSafeToCopy ) + { + // if (!Options.CanCopyProperty(*Property, *SourceActor)) + // { + // continue; + // } + if (!Options.CanCopyProperty(*Property, *SourceComponent)) + { + continue; + } + + if( !bIsPreviewing ) + { + if( !ModifiedObjects.Contains(TargetComponent) ) + { + TargetComponent->SetFlags(RF_Transactional); + TargetComponent->Modify(); + ModifiedObjects.Add(TargetComponent); + } + + if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) + { + TargetComponent->PreEditChange( Property ); + } + + // Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances. + TArray ComponentArchetypeInstancesToChange; + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances) + { + if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) ) + { + bool bAdd = true; + // We also need to double check that either the direct archetype of the target is also identical + if (ComponentArchetypeInstance->GetArchetype() != TargetComponent) + { + UActorComponent* CheckComponent = CastChecked(ComponentArchetypeInstance->GetArchetype()); + while (CheckComponent != ComponentArchetypeInstance) + { + if (!Property->Identical_InContainer( CheckComponent, TargetComponent )) + { + bAdd = false; + break; + } + CheckComponent = CastChecked(CheckComponent->GetArchetype()); + } + } + + if (bAdd) + { + ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance ); + } + } + } + } + + EditorUtilities::CopySingleProperty(SourceComponent, TargetComponent, Property); + + if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) + { + FPropertyChangedEvent PropertyChangedEvent( Property ); + TargetComponent->PostEditChangeProperty( PropertyChangedEvent ); + } + + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex ) + { + UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex]; + if( ComponentArchetypeInstance != nullptr ) + { + if( !ModifiedObjects.Contains(ComponentArchetypeInstance) ) + { + // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. + // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. + if (!ComponentArchetypeInstance->IsCreatedByConstructionScript()) + { + ComponentArchetypeInstance->SetFlags(RF_Transactional); + ComponentArchetypeInstance->Modify(); + ModifiedObjects.Add(ComponentArchetypeInstance); + } + + // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. + AActor* Owner = ComponentArchetypeInstance->GetOwner(); + if( Owner != nullptr && !ModifiedObjects.Contains(Owner)) + { + Owner->Modify(); + ModifiedObjects.Add(Owner); + } + } + + if (ComponentArchetypeInstance->IsRegistered()) + { + ComponentArchetypeInstance->UnregisterComponent(); + ComponentInstancesToReregister.Add(ComponentArchetypeInstance); + } + + EditorUtilities::CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property ); + } + } + } + } + + ++CopiedPropertyCount; + + if( bIsTransform ) + { + bTransformChanged = true; + } + } + } + } + + for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister) + { + ModifiedComponentInstance->RegisterComponent(); + } + + return CopiedPropertyCount; +} +#endif + + +#if WITH_EDITOR +FBlueprintEditor* +FHoudiniEngineRuntimeUtils::GetBlueprintEditor(const UObject* InObject) +{ + if (!IsValid(InObject)) + return nullptr; + + UObject* Outer = InObject->GetOuter(); + if (!IsValid(Outer)) + return nullptr; + + UBlueprintGeneratedClass* OuterBPClass = Cast(Outer->GetClass()); + + if (!OuterBPClass) + return nullptr; + + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + return static_cast(AssetEditorSubsystem->FindEditorForAsset(OuterBPClass->ClassGeneratedBy, false)); +} +#endif + + +#if WITH_EDITOR +void +FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate) +{ + if (!ComponentTemplate) + return; + + UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); + if (!BPGC) + return; + + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!Blueprint) + return; + + Blueprint->Modify(); + + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(Blueprint, false)); + check(BlueprintEditor); + + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + TSharedPtr SCSEditor = nullptr; + + SCSEditor = BlueprintEditor->GetSCSEditor(); + check(SCSEditor); + SCSEditor->SaveSCSCurrentState(SCS); + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + + SCSEditor->UpdateTree(true); +} +#endif + +#if WITH_EDITOR +void +FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTemplate) +{ + if (!ComponentTemplate) + return; + + UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); + if (!BPGC) + return; + + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!Blueprint) + return; + + Blueprint->Modify(); + + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); +} +#endif + + +#if WITH_EDITOR +void +FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) +{ + FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); + Obj->PostEditChangeProperty(Evt); +} + +void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FProperty* Property) +{ + FPropertyChangedEvent Evt(Property); + Obj->PostEditChangeProperty(Evt); +} + +void FHoudiniEngineRuntimeUtils::PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange) +{ + if (!InObject) + return; + if (!InObject->HasAnyFlags(RF_ArchetypeObject)) + return; + + // Iterate over the modified properties and propagate value changed to all archetype instances + TArray ArchetypeInstances; + InObject->GetArchetypeInstances(ArchetypeInstances); + for (UObject* Instance : ArchetypeInstances) + { + UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Found Archetype instance: %s"), *(Instance->GetPathName())); + for (FName PropertyName : DeltaChange.ChangedProperties) + { + UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Changed property: %s"), *(PropertyName.ToString())); + // FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation()); + } + } +} + +void FHoudiniEngineRuntimeUtils::ForAllArchetypeInstances(UObject* InTemplateObj, TFunctionRef Operation) +{ + if (!InTemplateObj) + return; + if (!InTemplateObj->HasAnyFlags(RF_ArchetypeObject|RF_DefaultSubObject)) + return; + + TArray Instances; + InTemplateObj->GetArchetypeInstances(Instances); + + for(UObject* Instance : Instances) + { + Operation(Instance); + } +} +#endif + +FHoudiniStaticMeshGenerationProperties +FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties() +{ + FHoudiniStaticMeshGenerationProperties SMGP; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + SMGP.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; + SMGP.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; + SMGP.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; + SMGP.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; + SMGP.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; + SMGP.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + SMGP.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; + SMGP.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; + SMGP.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; + SMGP.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; + SMGP.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; + SMGP.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; + SMGP.GeneratedDistanceFieldResolutionScale = HoudiniRuntimeSettings->GeneratedDistanceFieldResolutionScale; + } + + return SMGP; +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h index 7bffd43af..ec854d9bb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h @@ -1,337 +1,344 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/ObjectMacros.h" - -#if WITH_EDITOR - #include "SSCSEditor.h" - #include "ObjectTools.h" - #include "Kismet2/ComponentEditorUtils.h" - #include "Editor/Transactor.h" -#endif - -class AActor; -class UWorld; - -template -class TSubclassOf; - -struct FBox; - -struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils -{ - public: - - // Return platform specific name of libHAPI. - static FString GetLibHAPIName(); - - // ----------------------------------------------- - // Bounding Box utilities - // ----------------------------------------------- - - // Collect all the bounding boxes form the specified list of actors. OutBBoxes will be emptied. - static void GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes); - - // Collect actors that derive from the given class that intersect with the given array of bounding boxes. - static bool FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors); - - // ----------------------------------------------- - // File path utilities - // ----------------------------------------------- - - // Joins paths by taking into account whether paths - // successive paths are relative or absolute. - // Truncate everything preceding an absolute path. - // Taken and adapted from FPaths::Combine(). - template - FORCEINLINE static FString JoinPaths(PathTypes&&... InPaths) - { - const TCHAR* Paths[] = { GetTCharPtr(Forward(InPaths))... }; - const int32 NumPaths = UE_ARRAY_COUNT(Paths); - - FString Out = TEXT(""); - if (NumPaths <= 0) - return Out; - Out = Paths[NumPaths-1]; - // Process paths in reverse and terminate when we reach an absolute path. - for (int32 i=NumPaths-2; i >= 0; --i) - { - if (FCString::Strlen(Paths[i]) == 0) - continue; - if (Out[0] == '/') - { - // We already have an absolute path. Terminate. - break; - } - Out = Paths[i] / Out; - } - if (Out.Len() > 0 && Out[0] != '/') - Out = TEXT("/") + Out; - return Out; - } - - // ------------------------------------------------------------------ - // ObjectTools (Make some editor only ObjectTools functions available - // in editor builds) - // ------------------------------------------------------------------ - - // Check/gather references to InObject. - // Returns true if the function could execute (editor vs runtime) - FORCEINLINE static bool GatherObjectReferencersForDeletion(UObject* InObject, bool& bOutIsReferenced, bool& bOutIsReferencedByUndo, FReferencerInformationList* OutMemoryReferences = nullptr, bool bInRequireReferencingProperties = false) - { -#if WITH_EDITOR - ObjectTools::GatherObjectReferencersForDeletion(InObject, bOutIsReferenced, bOutIsReferencedByUndo, OutMemoryReferences, bInRequireReferencingProperties); - - return true; -#else - return false; -#endif - } - - // Delete a single object from its package. Returns true if the object was deleted. In non-editor - // builds this function returns false. - FORCEINLINE static bool DeleteSingleObject(UObject* InObjectToDelete, bool bInPerformReferenceCheck=true) - { -#if WITH_EDITOR - return ObjectTools::DeleteSingleObject(InObjectToDelete, bInPerformReferenceCheck); -#else - return false; -#endif - } - - // Collects garbage and marks truely empty packages for delete - // Returns true if the function could execute (editor vs runtime) - FORCEINLINE static bool CleanupAfterSuccessfulDelete(const TArray& InObjectsDeletedSuccessfully, bool bInPerformReferenceCheck=true) - { -#if WITH_EDITOR - ObjectTools::CleanupAfterSuccessfulDelete(InObjectsDeletedSuccessfully, bInPerformReferenceCheck); - return true; -#else - return false; -#endif - } - - // Deletes a single object. Returns true if the object was deleted. - // The object is only deleted if there are no references to it. - // If the package is on disk then bOutPackageIsInMemoryOnly is false and the CleanUpAfterSuccessfulDelete - // must be called on the package after execution of this function. - static bool SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly); - - // Deletes and cleans up on disk empty-packages for the objects in InObjectsToDelete. - // Objects are popped from InObjectsToDelete as they are processed (ran into cases where objects are garbage collected - // before we can properly delete them and cleanup their packages, so we tend to pass in a UPROPERTY based TArray - // that holds references to UObject to prevent garbage collection until we can delete them in this function) - // OutObjectsNotDeleted can optionally be used to return objects that could not be deleted. - // The function returns the number of objects that were deleted. - static int32 SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted=nullptr); - - // ------------------------------------------------- - // Type utilities - // ------------------------------------------------- - - // Taken from here: https://answers.unrealengine.com/questions/330496/conversion-of-enum-to-string.html - // Return the string representation of an enum value. - template - static FString EnumToString(const FString& enumName, const T value) - { - UEnum* pEnum = FindObject((UObject*)ANY_PACKAGE, *enumName); - return *(pEnum ? pEnum->GetNameStringByIndex(static_cast(value)) : "null"); - } - - // ------------------------------------------------- - // Blueprint utilities - // ------------------------------------------------- -#if WITH_EDITOR - // This function contains an excerpt from UEditorUtilities::CopyActorProperties() - // for specifically dealing with copying properties between components as well as propagating - // property changes to archetype instances. - static int32 CopyComponentProperties(UActorComponent* FromComponent, UActorComponent* ToComponent, const EditorUtilities::FCopyOptions& Options); - - // Get the SCSEditor for the given HoudiniAssetComponent - static FBlueprintEditor* GetBlueprintEditor(const UObject* InObject); - - static void MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate); - static void MarkBlueprintAsModified(UActorComponent* ComponentTemplate); -#endif - - // ------------------------------------------------- - // Editor Helpers - // ------------------------------------------------- -#if WITH_EDITOR - static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); - - static void DoPostEditChangeProperty(UObject* Obj, FProperty* Property); - - static void PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange); - - template - static TSet PropagateDefaultValueChange(USceneComponent* InSceneComponentTemplate, const FName& PropertyName, const T& OldValue, const T& NewValue) - { - TSet UpdatedInstances; - FComponentEditorUtils::PropagateDefaultValueChange(InSceneComponentTemplate, FindFieldChecked(InSceneComponentTemplate->GetClass(), PropertyName), OldValue, NewValue, UpdatedInstances); - return UpdatedInstances; - } - - // Perform this operation on the given archetype as well as each archetype instance. If the given - // object in not an archetype instance, then don't do anything. - static void ForAllArchetypeInstances(UObject* Archetype, TFunctionRef Operation); - -#endif - - /** - // * Set the value on an UObject using reflection. - // * @param Object The object to copy the value into. - // * @param PropertyName The name of the property to set. - // * @param Value The value to assign to the property. - // * - // * @return true if the value was set correctly - // */ - //template - //static bool SetPropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) - //{ - // // Get the property addresses for the source and destination objects. - // FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - - // // Get the property addresses for the object - // ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); - - // if ( SourceAddr == NULL ) - // { - // return false; - // } - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - // { - // FEditPropertyChain PropertyChain; - // PropertyChain.AddHead(Property); - // Object->PreEditChange(PropertyChain); - // } - - // // Set the value on the destination object. - // *SourceAddr = Value; - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - // { - // FPropertyChangedEvent PropertyEvent(Property); - // Object->PostEditChangeProperty(PropertyEvent); - // } - - // return true; - //} -#if WITH_EDITOR - template - static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) - { - // Get the property addresses for the source and destination objects. - FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - - // Get the property addresses for the object - ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); - - if ( SourceAddr == NULL ) - { - return false; - } - - if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FEditPropertyChain PropertyChain; - PropertyChain.AddHead(Property); - ((UObject*)Object)->PreEditChange(PropertyChain); - } - - // Set the value on the destination object. - if (*SourceAddr != Value) - { - TSet UpdatedInstances; - *SourceAddr = Value; - PropagateDefaultValueChange(Object, Property, *SourceAddr, Value, UpdatedInstances); - } - - if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FPropertyChangedEvent PropertyEvent(Property); - Object->PostEditChangeProperty(PropertyEvent); - } - - return true; - } -#endif - -#if WITH_EDITOR - // Bool specialization - template - static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, bool NewBool) - { - // Get the property addresses for the source and destination objects. - FBoolProperty* BoolProperty = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - check(BoolProperty); - - // Get the property addresses for the object - const int32 PropertyOffset = INDEX_NONE; - void* CurrentValue = PropertyOffset == INDEX_NONE ? BoolProperty->ContainerPtrToValuePtr(Object) : ((uint8*)Object + PropertyOffset); - check(CurrentValue); - - const bool CurrentBool = BoolProperty->GetPropertyValue(CurrentValue); - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FEditPropertyChain PropertyChain; - PropertyChain.AddHead(BoolProperty); - ((UObject*)Object)->PreEditChange(PropertyChain); - } - - // Set the value on the destination object. - if (CurrentBool != NewBool) - { - TSet UpdatedInstances; - BoolProperty->SetPropertyValue(CurrentValue, NewBool); - FComponentEditorUtils::PropagateDefaultValueChange(Object, BoolProperty, CurrentBool, NewBool, UpdatedInstances); - } - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FPropertyChangedEvent PropertyEvent(BoolProperty); - Object->PostEditChangeProperty(PropertyEvent); - } - - return true; - } -#endif - - protected: - // taken from FPaths::GetTCharPtr - FORCEINLINE static const TCHAR* GetTCharPtr(const TCHAR* Ptr) - { - return Ptr; - } - FORCEINLINE static const TCHAR* GetTCharPtr(const FString& Str) - { - return *Str; - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/ObjectMacros.h" +#include "UObject/UObjectGlobals.h" +#include "UObject/Class.h" + +#if WITH_EDITOR + #include "SSCSEditor.h" + #include "ObjectTools.h" + #include "Kismet2/ComponentEditorUtils.h" + #include "Editor/Transactor.h" +#endif + +class AActor; +class UWorld; +struct FHoudiniStaticMeshGenerationProperties; + +template +class TSubclassOf; + +struct FBox; + + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils +{ + public: + + // Return platform specific name of libHAPI. + static FString GetLibHAPIName(); + + // Reterurns default SM Generation Properties using the default settings values + static FHoudiniStaticMeshGenerationProperties GetDefaultStaticMeshGenerationProperties(); + + // ----------------------------------------------- + // Bounding Box utilities + // ----------------------------------------------- + + // Collect all the bounding boxes form the specified list of actors. OutBBoxes will be emptied. + static void GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes); + + // Collect actors that derive from the given class that intersect with the given array of bounding boxes. + static bool FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors); + + // ----------------------------------------------- + // File path utilities + // ----------------------------------------------- + + // Joins paths by taking into account whether paths + // successive paths are relative or absolute. + // Truncate everything preceding an absolute path. + // Taken and adapted from FPaths::Combine(). + template + FORCEINLINE static FString JoinPaths(PathTypes&&... InPaths) + { + const TCHAR* Paths[] = { GetTCharPtr(Forward(InPaths))... }; + const int32 NumPaths = UE_ARRAY_COUNT(Paths); + + FString Out = TEXT(""); + if (NumPaths <= 0) + return Out; + Out = Paths[NumPaths-1]; + // Process paths in reverse and terminate when we reach an absolute path. + for (int32 i=NumPaths-2; i >= 0; --i) + { + if (FCString::Strlen(Paths[i]) == 0) + continue; + if (Out[0] == '/') + { + // We already have an absolute path. Terminate. + break; + } + Out = Paths[i] / Out; + } + if (Out.Len() > 0 && Out[0] != '/') + Out = TEXT("/") + Out; + return Out; + } + + // ------------------------------------------------------------------ + // ObjectTools (Make some editor only ObjectTools functions available + // in editor builds) + // ------------------------------------------------------------------ + + // Check/gather references to InObject. + // Returns true if the function could execute (editor vs runtime) + FORCEINLINE static bool GatherObjectReferencersForDeletion(UObject* InObject, bool& bOutIsReferenced, bool& bOutIsReferencedByUndo, FReferencerInformationList* OutMemoryReferences = nullptr, bool bInRequireReferencingProperties = false) + { +#if WITH_EDITOR + ObjectTools::GatherObjectReferencersForDeletion(InObject, bOutIsReferenced, bOutIsReferencedByUndo, OutMemoryReferences, bInRequireReferencingProperties); + + return true; +#else + return false; +#endif + } + + // Delete a single object from its package. Returns true if the object was deleted. In non-editor + // builds this function returns false. + FORCEINLINE static bool DeleteSingleObject(UObject* InObjectToDelete, bool bInPerformReferenceCheck=true) + { +#if WITH_EDITOR + return ObjectTools::DeleteSingleObject(InObjectToDelete, bInPerformReferenceCheck); +#else + return false; +#endif + } + + // Collects garbage and marks truely empty packages for delete + // Returns true if the function could execute (editor vs runtime) + FORCEINLINE static bool CleanupAfterSuccessfulDelete(const TArray& InObjectsDeletedSuccessfully, bool bInPerformReferenceCheck=true) + { +#if WITH_EDITOR + ObjectTools::CleanupAfterSuccessfulDelete(InObjectsDeletedSuccessfully, bInPerformReferenceCheck); + return true; +#else + return false; +#endif + } + + // Deletes a single object. Returns true if the object was deleted. + // The object is only deleted if there are no references to it. + // If the package is on disk then bOutPackageIsInMemoryOnly is false and the CleanUpAfterSuccessfulDelete + // must be called on the package after execution of this function. + static bool SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly); + + // Deletes and cleans up on disk empty-packages for the objects in InObjectsToDelete. + // Objects are popped from InObjectsToDelete as they are processed (ran into cases where objects are garbage collected + // before we can properly delete them and cleanup their packages, so we tend to pass in a UPROPERTY based TArray + // that holds references to UObject to prevent garbage collection until we can delete them in this function) + // OutObjectsNotDeleted can optionally be used to return objects that could not be deleted. + // The function returns the number of objects that were deleted. + static int32 SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted=nullptr); + + // ------------------------------------------------- + // Type utilities + // ------------------------------------------------- + + // Taken from here: https://answers.unrealengine.com/questions/330496/conversion-of-enum-to-string.html + // Return the string representation of an enum value. + template + static FString EnumToString(const FString& enumName, const T value) + { + UEnum* pEnum = FindObject((UObject*)ANY_PACKAGE, *enumName); + return *(pEnum ? pEnum->GetNameStringByIndex(static_cast(value)) : "null"); + } + + // ------------------------------------------------- + // Blueprint utilities + // ------------------------------------------------- +#if WITH_EDITOR + // This function contains an excerpt from UEditorUtilities::CopyActorProperties() + // for specifically dealing with copying properties between components as well as propagating + // property changes to archetype instances. + static int32 CopyComponentProperties(UActorComponent* FromComponent, UActorComponent* ToComponent, const EditorUtilities::FCopyOptions& Options); + + // Get the SCSEditor for the given HoudiniAssetComponent + static FBlueprintEditor* GetBlueprintEditor(const UObject* InObject); + + static void MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate); + static void MarkBlueprintAsModified(UActorComponent* ComponentTemplate); +#endif + + // ------------------------------------------------- + // Editor Helpers + // ------------------------------------------------- +#if WITH_EDITOR + static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); + + static void DoPostEditChangeProperty(UObject* Obj, FProperty* Property); + + static void PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange); + + template + static TSet PropagateDefaultValueChange(USceneComponent* InSceneComponentTemplate, const FName& PropertyName, const T& OldValue, const T& NewValue) + { + TSet UpdatedInstances; + FComponentEditorUtils::PropagateDefaultValueChange(InSceneComponentTemplate, FindFieldChecked(InSceneComponentTemplate->GetClass(), PropertyName), OldValue, NewValue, UpdatedInstances); + return UpdatedInstances; + } + + // Perform this operation on the given archetype as well as each archetype instance. If the given + // object in not an archetype instance, then don't do anything. + static void ForAllArchetypeInstances(UObject* Archetype, TFunctionRef Operation); + +#endif + + /** + // * Set the value on an UObject using reflection. + // * @param Object The object to copy the value into. + // * @param PropertyName The name of the property to set. + // * @param Value The value to assign to the property. + // * + // * @return true if the value was set correctly + // */ + //template + //static bool SetPropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) + //{ + // // Get the property addresses for the source and destination objects. + // FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + + // // Get the property addresses for the object + // ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); + + // if ( SourceAddr == NULL ) + // { + // return false; + // } + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + // { + // FEditPropertyChain PropertyChain; + // PropertyChain.AddHead(Property); + // Object->PreEditChange(PropertyChain); + // } + + // // Set the value on the destination object. + // *SourceAddr = Value; + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + // { + // FPropertyChangedEvent PropertyEvent(Property); + // Object->PostEditChangeProperty(PropertyEvent); + // } + + // return true; + //} +#if WITH_EDITOR + template + static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) + { + // Get the property addresses for the source and destination objects. + FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + + // Get the property addresses for the object + ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); + + if ( SourceAddr == NULL ) + { + return false; + } + + if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FEditPropertyChain PropertyChain; + PropertyChain.AddHead(Property); + ((UObject*)Object)->PreEditChange(PropertyChain); + } + + // Set the value on the destination object. + if (*SourceAddr != Value) + { + TSet UpdatedInstances; + *SourceAddr = Value; + PropagateDefaultValueChange(Object, Property, *SourceAddr, Value, UpdatedInstances); + } + + if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FPropertyChangedEvent PropertyEvent(Property); + Object->PostEditChangeProperty(PropertyEvent); + } + + return true; + } +#endif + +#if WITH_EDITOR + // Bool specialization + template + static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, bool NewBool) + { + // Get the property addresses for the source and destination objects. + FBoolProperty* BoolProperty = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + check(BoolProperty); + + // Get the property addresses for the object + const int32 PropertyOffset = INDEX_NONE; + void* CurrentValue = PropertyOffset == INDEX_NONE ? BoolProperty->ContainerPtrToValuePtr(Object) : ((uint8*)Object + PropertyOffset); + check(CurrentValue); + + const bool CurrentBool = BoolProperty->GetPropertyValue(CurrentValue); + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FEditPropertyChain PropertyChain; + PropertyChain.AddHead(BoolProperty); + ((UObject*)Object)->PreEditChange(PropertyChain); + } + + // Set the value on the destination object. + if (CurrentBool != NewBool) + { + TSet UpdatedInstances; + BoolProperty->SetPropertyValue(CurrentValue, NewBool); + FComponentEditorUtils::PropagateDefaultValueChange(Object, BoolProperty, CurrentBool, NewBool, UpdatedInstances); + } + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FPropertyChangedEvent PropertyEvent(BoolProperty); + Object->PostEditChangeProperty(PropertyEvent); + } + + return true; + } +#endif + + protected: + // taken from FPaths::GetTCharPtr + FORCEINLINE static const TCHAR* GetTCharPtr(const TCHAR* Ptr) + { + return Ptr; + } + FORCEINLINE static const TCHAR* GetTCharPtr(const FString& Str) + { + return *Str; + } }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp index ef258164a..f92cace38 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp @@ -1,1077 +1,1079 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGenericAttribute.h" - -#include "Engine/StaticMesh.h" -#include "Components/ActorComponent.h" -#include "Components/PrimitiveComponent.h" -#include "Components/StaticMeshComponent.h" - -#include "PhysicsEngine/BodySetup.h" -#include "EditorFramework/AssetImportData.h" -#include "AI/Navigation/NavCollisionBase.h" - -double -FHoudiniGenericAttribute::GetDoubleValue(int32 index) -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return DoubleValues[index]; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return (double)IntValues[index]; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return FCString::Atod(*StringValues[index]); - } - - return 0.0f; -} - -void -FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 index) -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetDoubleValue(index * AttributeTupleSize + n); -} - -int64 -FHoudiniGenericAttribute::GetIntValue(int32 index) -{ - if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return IntValues[index]; - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return (int64)DoubleValues[index]; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return FCString::Atoi64(*StringValues[index]); - } - - return 0; -} - -void -FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetIntValue(index * AttributeTupleSize + n); -} - -FString -FHoudiniGenericAttribute::GetStringValue(int32 index) -{ - if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return StringValues[index]; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return FString::FromInt((int32)IntValues[index]); - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return FString::SanitizeFloat(DoubleValues[index]); - } - - return FString(); -} - -void -FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 index) -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetStringValue(index * AttributeTupleSize + n); -} - -bool -FHoudiniGenericAttribute::GetBoolValue(int32 index) -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return DoubleValues[index] == 0.0 ? false : true; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return IntValues[index] == 0 ? false : true; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return StringValues[index].Equals(TEXT("true"), ESearchCase::IgnoreCase) ? true : false; - } - - return false; -} - -void -FHoudiniGenericAttribute::GetBoolTuple(TArray& TupleValues, int32 index) -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetBoolValue(index * AttributeTupleSize + n); -} - -void* -FHoudiniGenericAttribute::GetData() -{ - if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.Num() > 0) - return StringValues.GetData(); - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.Num() > 0) - return IntValues.GetData(); - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.Num() > 0) - return DoubleValues.GetData(); - } - - return nullptr; -} - -bool -FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( - UObject* InObject, FHoudiniGenericAttribute InPropertyAttribute, const int32& AtIndex) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Get the Property name - const FString& PropertyName = InPropertyAttribute.AttributeName; - if (PropertyName.IsEmpty()) - return false; - - // Some Properties need to be handle and modified manually... - if (PropertyName == "CollisionProfileName") - { - UPrimitiveComponent* PC = Cast(InObject); - if (PC && !PC->IsPendingKill()) - { - FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); - FName Value = FName(*StringValue); - PC->SetCollisionProfileName(Value); - - return true; - } - return false; - } - - // Handle Component Tags manually here - if (PropertyName.Contains("Tags")) - { - UActorComponent* AC = Cast< UActorComponent >(InObject); - if (AC && !AC->IsPendingKill()) - { - FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); - if (!AC->ComponentTags.Contains(NameAttr)) - AC->ComponentTags.Add(NameAttr); - /* - for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++) - { - FName NameAttr = FName(*InPropertyAttribute.GetStringValue(nIdx)); - if (!AC->ComponentTags.Contains(NameAttr)) - AC->ComponentTags.Add(NameAttr); - } - */ - return true; - } - return false; - } - - // Try to find the corresponding UProperty - void* OutContainer = nullptr; - FProperty* FoundProperty = nullptr; - UObject* FoundPropertyObject = nullptr; - if (!FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) - return false; - - // Modify the Property we found - if (!ModifyPropertyValueOnObject(FoundPropertyObject, InPropertyAttribute, FoundProperty, OutContainer, AtIndex)) - return false; - - return true; -} - - -bool -FHoudiniGenericAttribute::FindPropertyOnObject( - UObject* InObject, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - UObject*& OutFoundPropertyObject, - void*& OutContainer) -{ -#if WITH_EDITOR - if (!InObject || InObject->IsPendingKill()) - return false; - - if (InPropertyName.IsEmpty()) - return false; - - UClass* ObjectClass = InObject->GetClass(); - if (!ObjectClass || ObjectClass->IsPendingKill()) - return false; - - // Set the result pointer to null - OutContainer = nullptr; - OutFoundProperty = nullptr; - OutFoundPropertyObject = InObject; - - bool bPropertyHasBeenFound = false; - FHoudiniGenericAttribute::TryToFindProperty( - InObject, - ObjectClass, - InPropertyName, - OutFoundProperty, - bPropertyHasBeenFound, - OutContainer); - - /* - // TODO: Parsing needs to be made recursively! - // Iterate manually on the properties, in order to handle StructProperties correctly - for (TFieldIterator PropIt(ObjectClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) - { - FProperty* CurrentProperty = *PropIt; - if (!CurrentProperty) - continue; - - FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = CurrentProperty->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - OutFoundProperty = CurrentProperty; - - // If it's an equality, we dont need to keep searching - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bPropertyHasBeenFound = true; - break; - } - } - - // StructProperty need to be a nested struct - //if (UStructProperty* StructProperty = Cast< UStructProperty >(CurrentProperty)) - // bPropertyHasBeenFound = TryToFindInStructProperty(InObject, InPropertyName, StructProperty, OutFoundProperty, OutStructContainer); - //else if (UArrayProperty* ArrayProperty = Cast< UArrayProperty >(CurrentProperty)) - // bPropertyHasBeenFound = TryToFindInArrayProperty(InObject, InPropertyName, ArrayProperty, OutFoundProperty, OutStructContainer); - - // Handle StructProperty - FStructProperty* StructProperty = CastField(CurrentProperty); - if (StructProperty) - { - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) - continue; - - for (TFieldIterator It(Struct); It; ++It) - { - FProperty* Property = *It; - if (!Property) - continue; - - DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - Name = Property->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - OutFoundProperty = Property; - OutStructContainer = StructProperty->ContainerPtrToValuePtr< void >(InObject, 0); - - // If it's an equality, we dont need to keep searching - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bPropertyHasBeenFound = true; - break; - } - } - } - } - - if (bPropertyHasBeenFound) - break; - } - - if (bPropertyHasBeenFound) - return true; - */ - - // Try with FindField?? - if (!OutFoundProperty) - OutFoundProperty = FindFProperty(ObjectClass, *InPropertyName); - - // Try with FindPropertyByName ?? - if (!OutFoundProperty) - OutFoundProperty = ObjectClass->FindPropertyByName(*InPropertyName); - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - - // Handle common properties nested in classes - // Static Meshes - UStaticMesh* SM = Cast(InObject); - if (SM && !SM->IsPendingKill()) - { - if (SM->BodySetup && FindPropertyOnObject( - SM->BodySetup, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - - if (SM->AssetImportData && FindPropertyOnObject( - SM->AssetImportData, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - - if (SM->NavCollision && FindPropertyOnObject( - SM->NavCollision, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - } - - // For Actors, parse their components - AActor* Actor = Cast(InObject); - if (Actor && !Actor->IsPendingKill()) - { - TArray AllComponents; - Actor->GetComponents(AllComponents, true); - - int32 CompIdx = 0; - for (USceneComponent * SceneComponent : AllComponents) - { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; - - if (FindPropertyOnObject( - SceneComponent, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - } - } - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - -#endif - return false; -} - - -bool -FHoudiniGenericAttribute::TryToFindProperty( - void* InContainer, - UStruct* InStruct, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - bool& bOutPropertyHasBeenFound, - void*& OutContainer) -{ -#if WITH_EDITOR - if (!InContainer) - return false; - - if (!InStruct || InStruct->IsPendingKill()) - return false; - - if (InPropertyName.IsEmpty()) - return false; - - // Iterate manually on the properties, in order to handle StructProperties correctly - for (TFieldIterator PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) - { - FProperty* CurrentProperty = *PropIt; - if (!CurrentProperty) - continue; - - FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = CurrentProperty->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - OutFoundProperty = CurrentProperty; - OutContainer = InContainer; - - // If it's an equality, we dont need to keep searching anymore - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bOutPropertyHasBeenFound = true; - break; - } - } - - // Do a recursive parsing for StructProperties - FStructProperty* StructProperty = CastField(CurrentProperty); - if (StructProperty) - { - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) - continue; - - TryToFindProperty( - StructProperty->ContainerPtrToValuePtr(InContainer, 0), - Struct, - InPropertyName, - OutFoundProperty, - bOutPropertyHasBeenFound, - OutContainer); - } - - if (bOutPropertyHasBeenFound) - break; - } - - if (bOutPropertyHasBeenFound) - return true; - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - -#endif - return false; -} - - -bool -FHoudiniGenericAttribute::ModifyPropertyValueOnObject( - UObject* InObject, - FHoudiniGenericAttribute InGenericAttribute, - FProperty* FoundProperty, - void* InContainer, - const int32& InAtIndex) -{ - if (!InObject || InObject->IsPendingKill() || !FoundProperty) - return false; - - // Determine the container to use (either InContainer if specified, or InObject) - void* Container = InContainer ? InContainer : InObject; - - // Initialize using the found property - FProperty* InnerProperty = FoundProperty; - - AActor* InOwner = Cast(InObject->GetOuter()); - bool bHasModifiedProperty = false; - - - auto OnPropertyChanged = [InObject, InOwner, &bHasModifiedProperty](FProperty* InProperty) - { -#if WITH_EDITOR - FPropertyChangedEvent Evt(InProperty); - InObject->PostEditChangeProperty(Evt); - if (InOwner) - { - // If we are setting properties on an Actor component, we want to notify the - // actor of the changes too since the property change might be handled in the actor's - // PostEditChange callbacks (one such an example occurs when changing the material for a decal actor). - InOwner->PostEditChangeProperty(Evt); - } -#endif - bHasModifiedProperty = true; - }; - - int32 NumberOfProperties = 1; - FArrayProperty* ArrayProperty = CastField(FoundProperty); - if (ArrayProperty) - { - InnerProperty = ArrayProperty->Inner; - NumberOfProperties = ArrayProperty->ArrayDim; - - // Do we need to add values to the array? - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - - //ArrayHelper.ExpandForIndex( InGenericAttribute.AttributeTupleSize - 1 ); - if (InGenericAttribute.AttributeTupleSize > NumberOfProperties) - { - ArrayHelper.Resize(InGenericAttribute.AttributeTupleSize); - NumberOfProperties = InGenericAttribute.AttributeTupleSize; - } - } - - // Get the "proper" AtIndex in the flat array by using the attribute tuple size - // TODO: fix the issue when changing array of tuple properties! - int32 AtIndex = InAtIndex * InGenericAttribute.AttributeTupleSize; - - for (int32 nPropIdx = 0; nPropIdx < NumberOfProperties; nPropIdx++) - { - if (FFloatProperty* FloatProperty = CastField(InnerProperty)) - { - // FLOAT PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - FloatProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(FloatProperty); - } - } - else - { - double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - FloatProperty->SetFloatingPointPropertyValue(ValuePtr, Value); - OnPropertyChanged(FloatProperty); - } - } - } - else if (FIntProperty* IntProperty = CastField(InnerProperty)) - { - // INT PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - IntProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(IntProperty); - } - } - else - { - int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - IntProperty->SetIntPropertyValue(ValuePtr, Value); - OnPropertyChanged(IntProperty); - } - } - } - else if (FBoolProperty* BoolProperty = CastField(InnerProperty)) - { - // BOOL PROPERTY - bool Value = InGenericAttribute.GetBoolValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - BoolProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(BoolProperty); - } - } - else if (FStrProperty* StringProperty = CastField(InnerProperty)) - { - // STRING PROPERTY - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - StringProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(StringProperty); - } - } - else if (FNumericProperty *NumericProperty = CastField(InnerProperty)) - { - // NUMERIC PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - NumericProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(NumericProperty); - } - } - else if (NumericProperty->IsInteger()) - { - int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - NumericProperty->SetIntPropertyValue(ValuePtr, (int64)Value); - OnPropertyChanged(NumericProperty); - } - } - else if (NumericProperty->IsFloatingPoint()) - { - double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - NumericProperty->SetFloatingPointPropertyValue(ValuePtr, Value); - OnPropertyChanged(NumericProperty); - } - } - else - { - // Numeric Property was found, but is of an unsupported type - HOUDINI_LOG_MESSAGE(TEXT("Unsupported Numeric UProperty")); - } - } - else if (FNameProperty* NameProperty = CastField(InnerProperty)) - { - // NAME PROPERTY - FString StringValue = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - FName Value = FName(*StringValue); - - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - NameProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(NameProperty); - } - } - else if (FStructProperty* StructProperty = CastField(InnerProperty)) - { - // STRUCT PROPERTY - const FName PropertyName = StructProperty->Struct->GetFName(); - if (PropertyName == NAME_Vector) - { - FVector* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - // Found a vector property, fill it with the 3 tuple values - PropertyValue->X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); - PropertyValue->Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - PropertyValue->Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - OnPropertyChanged(StructProperty); - } - } - else if (PropertyName == NAME_Transform) - { - FTransform* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - // Found a transform property fill it with the attribute tuple values - FVector Translation; - Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); - Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - - FQuat Rotation; - Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); - Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 4); - Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 5); - Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 6); - - FVector Scale; - Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 7); - Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 8); - Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 9); - - PropertyValue->SetTranslation(Translation); - PropertyValue->SetRotation(Rotation); - PropertyValue->SetScale3D(Scale); - - OnPropertyChanged(StructProperty); - } - } - else if (PropertyName == NAME_Color) - { - FColor* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->R = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - PropertyValue->G = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); - PropertyValue->B = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 2); - if (InGenericAttribute.AttributeTupleSize == 4) - PropertyValue->A = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 3); - - OnPropertyChanged(StructProperty); - } - } - else if (PropertyName == NAME_LinearColor) - { - FLinearColor* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->R = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - PropertyValue->G = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - PropertyValue->B = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - if (InGenericAttribute.AttributeTupleSize == 4) - PropertyValue->A = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); - - OnPropertyChanged(StructProperty); - } - } - else if (PropertyName == "Int32Interval") - { - FInt32Interval* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->Min = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - PropertyValue->Max = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); - - OnPropertyChanged(StructProperty); - } - } - else if (PropertyName == "FloatInterval") - { - FFloatInterval* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - PropertyValue->Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - - OnPropertyChanged(StructProperty); - } - } - } - else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) - { - // OBJECT PATH PROPERTY - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - TSoftObjectPtr ValueObjectPtr; - ValueObjectPtr = Value; - UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); - - // Ensure the ObjectProperty class matches the ValueObject that we just loaded - if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) - { - ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); - OnPropertyChanged(ObjectProperty); - } - } - } - else - { - // Property was found, but is of an unsupported type - FString PropertyClass = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); - HOUDINI_LOG_MESSAGE(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClass, *InGenericAttribute.AttributeName); - return false; - } - } - - if (bHasModifiedProperty) - { -#if WITH_EDITOR - InObject->PostEditChange(); - if (InOwner) - { - InOwner->PostEditChange(); - } -#endif - } - - return true; -} - -/* -bool -FHoudiniEngineUtils::TryToFindInStructProperty( - UObject* Object, FString UPropertyNameToFind, UStructProperty* StructProperty, UProperty*& FoundProperty, void*& StructContainer ) -{ - if ( !StructProperty || !Object ) - return false; - - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - for (TFieldIterator< UProperty > It(Struct); It; ++It) - { - UProperty* Property = *It; - if ( !Property ) - continue; - - FString DisplayName = It->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = It->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - FoundProperty = Property; - StructContainer = StructProperty->ContainerPtrToValuePtr< void >( Object, 0); - - // If it's an equality, we dont need to keep searching - if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) - return true; - } - - if ( FoundProperty ) - continue; - - UStructProperty* NestedStruct = Cast( Property ); - if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) - return true; - - UArrayProperty* ArrayProp = Cast( Property ); - if ( ArrayProp && TryToFindInArrayProperty( Object, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::TryToFindInArrayProperty( - UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ) -{ - if ( !ArrayProperty || !Object ) - return false; - - UProperty* Property = ArrayProperty->Inner; - if ( !Property ) - return false; - - FString DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = Property->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - FoundProperty = Property; - StructContainer = ArrayProperty->ContainerPtrToValuePtr< void >( Object, 0); - - // If it's an equality, we dont need to keep searching - if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) - return true; - } - - if ( !FoundProperty ) - { - UStructProperty* NestedStruct = Cast( Property ); - if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) - return true; - - UArrayProperty* ArrayProp = Cast( Property ); - if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) - return true; - } - - return false; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGenericAttribute.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Engine/StaticMesh.h" +#include "Components/ActorComponent.h" +#include "Components/PrimitiveComponent.h" +#include "Components/StaticMeshComponent.h" + +#include "PhysicsEngine/BodySetup.h" +#include "EditorFramework/AssetImportData.h" +#include "AI/Navigation/NavCollisionBase.h" + +double +FHoudiniGenericAttribute::GetDoubleValue(int32 index) const +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return DoubleValues[index]; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return (double)IntValues[index]; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return FCString::Atod(*StringValues[index]); + } + + return 0.0f; +} + +void +FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetDoubleValue(index * AttributeTupleSize + n); +} + +int64 +FHoudiniGenericAttribute::GetIntValue(int32 index) const +{ + if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return IntValues[index]; + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return (int64)DoubleValues[index]; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return FCString::Atoi64(*StringValues[index]); + } + + return 0; +} + +void +FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetIntValue(index * AttributeTupleSize + n); +} + +FString +FHoudiniGenericAttribute::GetStringValue(int32 index) const +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return StringValues[index]; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return FString::FromInt((int32)IntValues[index]); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return FString::SanitizeFloat(DoubleValues[index]); + } + + return FString(); +} + +void +FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetStringValue(index * AttributeTupleSize + n); +} + +bool +FHoudiniGenericAttribute::GetBoolValue(int32 index) const +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return DoubleValues[index] == 0.0 ? false : true; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return IntValues[index] == 0 ? false : true; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return StringValues[index].Equals(TEXT("true"), ESearchCase::IgnoreCase) ? true : false; + } + + return false; +} + +void +FHoudiniGenericAttribute::GetBoolTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetBoolValue(index * AttributeTupleSize + n); +} + +void* +FHoudiniGenericAttribute::GetData() +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.Num() > 0) + return StringValues.GetData(); + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.Num() > 0) + return IntValues.GetData(); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.Num() > 0) + return DoubleValues.GetData(); + } + + return nullptr; +} + +bool +FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( + UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Get the Property name + const FString& PropertyName = InPropertyAttribute.AttributeName; + if (PropertyName.IsEmpty()) + return false; + + // Some Properties need to be handle and modified manually... + if (PropertyName == "CollisionProfileName") + { + UPrimitiveComponent* PC = Cast(InObject); + if (PC && !PC->IsPendingKill()) + { + FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); + FName Value = FName(*StringValue); + PC->SetCollisionProfileName(Value); + + return true; + } + return false; + } + + // Handle Component Tags manually here + if (PropertyName.Contains("Tags")) + { + UActorComponent* AC = Cast< UActorComponent >(InObject); + if (AC && !AC->IsPendingKill()) + { + FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); + if (!AC->ComponentTags.Contains(NameAttr)) + AC->ComponentTags.Add(NameAttr); + /* + for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++) + { + FName NameAttr = FName(*InPropertyAttribute.GetStringValue(nIdx)); + if (!AC->ComponentTags.Contains(NameAttr)) + AC->ComponentTags.Add(NameAttr); + } + */ + return true; + } + return false; + } + + // Try to find the corresponding UProperty + void* OutContainer = nullptr; + FProperty* FoundProperty = nullptr; + UObject* FoundPropertyObject = nullptr; + if (!FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) + return false; + + // Modify the Property we found + if (!ModifyPropertyValueOnObject(FoundPropertyObject, InPropertyAttribute, FoundProperty, OutContainer, AtIndex)) + return false; + + return true; +} + + +bool +FHoudiniGenericAttribute::FindPropertyOnObject( + UObject* InObject, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + UObject*& OutFoundPropertyObject, + void*& OutContainer) +{ +#if WITH_EDITOR + if (!InObject || InObject->IsPendingKill()) + return false; + + if (InPropertyName.IsEmpty()) + return false; + + UClass* ObjectClass = InObject->GetClass(); + if (!ObjectClass || ObjectClass->IsPendingKill()) + return false; + + // Set the result pointer to null + OutContainer = nullptr; + OutFoundProperty = nullptr; + OutFoundPropertyObject = InObject; + + bool bPropertyHasBeenFound = false; + FHoudiniGenericAttribute::TryToFindProperty( + InObject, + ObjectClass, + InPropertyName, + OutFoundProperty, + bPropertyHasBeenFound, + OutContainer); + + /* + // TODO: Parsing needs to be made recursively! + // Iterate manually on the properties, in order to handle StructProperties correctly + for (TFieldIterator PropIt(ObjectClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) + { + FProperty* CurrentProperty = *PropIt; + if (!CurrentProperty) + continue; + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + OutFoundProperty = CurrentProperty; + + // If it's an equality, we dont need to keep searching + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bPropertyHasBeenFound = true; + break; + } + } + + // StructProperty need to be a nested struct + //if (UStructProperty* StructProperty = Cast< UStructProperty >(CurrentProperty)) + // bPropertyHasBeenFound = TryToFindInStructProperty(InObject, InPropertyName, StructProperty, OutFoundProperty, OutStructContainer); + //else if (UArrayProperty* ArrayProperty = Cast< UArrayProperty >(CurrentProperty)) + // bPropertyHasBeenFound = TryToFindInArrayProperty(InObject, InPropertyName, ArrayProperty, OutFoundProperty, OutStructContainer); + + // Handle StructProperty + FStructProperty* StructProperty = CastField(CurrentProperty); + if (StructProperty) + { + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + if (!Struct || Struct->IsPendingKill()) + continue; + + for (TFieldIterator It(Struct); It; ++It) + { + FProperty* Property = *It; + if (!Property) + continue; + + DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + Name = Property->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + OutFoundProperty = Property; + OutStructContainer = StructProperty->ContainerPtrToValuePtr< void >(InObject, 0); + + // If it's an equality, we dont need to keep searching + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bPropertyHasBeenFound = true; + break; + } + } + } + } + + if (bPropertyHasBeenFound) + break; + } + + if (bPropertyHasBeenFound) + return true; + */ + + // Try with FindField?? + if (!OutFoundProperty) + OutFoundProperty = FindFProperty(ObjectClass, *InPropertyName); + + // Try with FindPropertyByName ?? + if (!OutFoundProperty) + OutFoundProperty = ObjectClass->FindPropertyByName(*InPropertyName); + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + + // Handle common properties nested in classes + // Static Meshes + UStaticMesh* SM = Cast(InObject); + if (SM && !SM->IsPendingKill()) + { + if (SM->BodySetup && FindPropertyOnObject( + SM->BodySetup, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + + if (SM->AssetImportData && FindPropertyOnObject( + SM->AssetImportData, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + + if (SM->NavCollision && FindPropertyOnObject( + SM->NavCollision, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + } + + // For Actors, parse their components + AActor* Actor = Cast(InObject); + if (Actor && !Actor->IsPendingKill()) + { + TArray AllComponents; + Actor->GetComponents(AllComponents, true); + + int32 CompIdx = 0; + for (USceneComponent * SceneComponent : AllComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + if (FindPropertyOnObject( + SceneComponent, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + } + } + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + +#endif + return false; +} + + +bool +FHoudiniGenericAttribute::TryToFindProperty( + void* InContainer, + UStruct* InStruct, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + bool& bOutPropertyHasBeenFound, + void*& OutContainer) +{ +#if WITH_EDITOR + if (!InContainer) + return false; + + if (!InStruct || InStruct->IsPendingKill()) + return false; + + if (InPropertyName.IsEmpty()) + return false; + + // Iterate manually on the properties, in order to handle StructProperties correctly + for (TFieldIterator PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) + { + FProperty* CurrentProperty = *PropIt; + if (!CurrentProperty) + continue; + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + OutFoundProperty = CurrentProperty; + OutContainer = InContainer; + + // If it's an equality, we dont need to keep searching anymore + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bOutPropertyHasBeenFound = true; + break; + } + } + + // Do a recursive parsing for StructProperties + FStructProperty* StructProperty = CastField(CurrentProperty); + if (StructProperty) + { + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + if (!Struct || Struct->IsPendingKill()) + continue; + + TryToFindProperty( + StructProperty->ContainerPtrToValuePtr(InContainer, 0), + Struct, + InPropertyName, + OutFoundProperty, + bOutPropertyHasBeenFound, + OutContainer); + } + + if (bOutPropertyHasBeenFound) + break; + } + + if (bOutPropertyHasBeenFound) + return true; + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + +#endif + return false; +} + + +bool +FHoudiniGenericAttribute::ModifyPropertyValueOnObject( + UObject* InObject, + FHoudiniGenericAttribute InGenericAttribute, + FProperty* FoundProperty, + void* InContainer, + const int32& InAtIndex) +{ + if (!InObject || InObject->IsPendingKill() || !FoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Initialize using the found property + FProperty* InnerProperty = FoundProperty; + + AActor* InOwner = Cast(InObject->GetOuter()); + bool bHasModifiedProperty = false; + + + auto OnPropertyChanged = [InObject, InOwner, &bHasModifiedProperty](FProperty* InProperty) + { +#if WITH_EDITOR + FPropertyChangedEvent Evt(InProperty); + InObject->PostEditChangeProperty(Evt); + if (InOwner) + { + // If we are setting properties on an Actor component, we want to notify the + // actor of the changes too since the property change might be handled in the actor's + // PostEditChange callbacks (one such an example occurs when changing the material for a decal actor). + InOwner->PostEditChangeProperty(Evt); + } +#endif + bHasModifiedProperty = true; + }; + + int32 NumberOfProperties = 1; + FArrayProperty* ArrayProperty = CastField(FoundProperty); + if (ArrayProperty) + { + InnerProperty = ArrayProperty->Inner; + NumberOfProperties = ArrayProperty->ArrayDim; + + // Do we need to add values to the array? + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + + //ArrayHelper.ExpandForIndex( InGenericAttribute.AttributeTupleSize - 1 ); + if (InGenericAttribute.AttributeTupleSize > NumberOfProperties) + { + ArrayHelper.Resize(InGenericAttribute.AttributeTupleSize); + NumberOfProperties = InGenericAttribute.AttributeTupleSize; + } + } + + // Get the "proper" AtIndex in the flat array by using the attribute tuple size + // TODO: fix the issue when changing array of tuple properties! + int32 AtIndex = InAtIndex * InGenericAttribute.AttributeTupleSize; + + for (int32 nPropIdx = 0; nPropIdx < NumberOfProperties; nPropIdx++) + { + if (FFloatProperty* FloatProperty = CastField(InnerProperty)) + { + // FLOAT PROPERTY + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + FloatProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); + OnPropertyChanged(FloatProperty); + } + } + else + { + double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + FloatProperty->SetFloatingPointPropertyValue(ValuePtr, Value); + OnPropertyChanged(FloatProperty); + } + } + } + else if (FIntProperty* IntProperty = CastField(InnerProperty)) + { + // INT PROPERTY + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + IntProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); + OnPropertyChanged(IntProperty); + } + } + else + { + int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + IntProperty->SetIntPropertyValue(ValuePtr, Value); + OnPropertyChanged(IntProperty); + } + } + } + else if (FBoolProperty* BoolProperty = CastField(InnerProperty)) + { + // BOOL PROPERTY + bool Value = InGenericAttribute.GetBoolValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + BoolProperty->SetPropertyValue(ValuePtr, Value); + OnPropertyChanged(BoolProperty); + } + } + else if (FStrProperty* StringProperty = CastField(InnerProperty)) + { + // STRING PROPERTY + FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + StringProperty->SetPropertyValue(ValuePtr, Value); + OnPropertyChanged(StringProperty); + } + } + else if (FNumericProperty *NumericProperty = CastField(InnerProperty)) + { + // NUMERIC PROPERTY + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + NumericProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); + OnPropertyChanged(NumericProperty); + } + } + else if (NumericProperty->IsInteger()) + { + int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + NumericProperty->SetIntPropertyValue(ValuePtr, (int64)Value); + OnPropertyChanged(NumericProperty); + } + } + else if (NumericProperty->IsFloatingPoint()) + { + double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + NumericProperty->SetFloatingPointPropertyValue(ValuePtr, Value); + OnPropertyChanged(NumericProperty); + } + } + else + { + // Numeric Property was found, but is of an unsupported type + HOUDINI_LOG_MESSAGE(TEXT("Unsupported Numeric UProperty")); + } + } + else if (FNameProperty* NameProperty = CastField(InnerProperty)) + { + // NAME PROPERTY + FString StringValue = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + FName Value = FName(*StringValue); + + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + NameProperty->SetPropertyValue(ValuePtr, Value); + OnPropertyChanged(NameProperty); + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // STRUCT PROPERTY + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + FVector* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + // Found a vector property, fill it with the 3 tuple values + PropertyValue->X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); + PropertyValue->Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); + PropertyValue->Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); + OnPropertyChanged(StructProperty); + } + } + else if (PropertyName == NAME_Transform) + { + FTransform* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + // Found a transform property fill it with the attribute tuple values + FVector Translation; + Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); + Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); + Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); + + FQuat Rotation; + Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); + Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 4); + Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 5); + Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 6); + + FVector Scale; + Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 7); + Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 8); + Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 9); + + PropertyValue->SetTranslation(Translation); + PropertyValue->SetRotation(Rotation); + PropertyValue->SetScale3D(Scale); + + OnPropertyChanged(StructProperty); + } + } + else if (PropertyName == NAME_Color) + { + FColor* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + PropertyValue->R = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); + PropertyValue->G = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); + PropertyValue->B = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 2); + if (InGenericAttribute.AttributeTupleSize == 4) + PropertyValue->A = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 3); + + OnPropertyChanged(StructProperty); + } + } + else if (PropertyName == NAME_LinearColor) + { + FLinearColor* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + PropertyValue->R = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); + PropertyValue->G = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); + PropertyValue->B = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); + if (InGenericAttribute.AttributeTupleSize == 4) + PropertyValue->A = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); + + OnPropertyChanged(StructProperty); + } + } + else if (PropertyName == "Int32Interval") + { + FInt32Interval* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + PropertyValue->Min = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); + PropertyValue->Max = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); + + OnPropertyChanged(StructProperty); + } + } + else if (PropertyName == "FloatInterval") + { + FFloatInterval* PropertyValue = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); + } + else + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (PropertyValue) + { + PropertyValue->Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); + PropertyValue->Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); + + OnPropertyChanged(StructProperty); + } + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + // OBJECT PATH PROPERTY + FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); + void * ValuePtr = nullptr; + if (ArrayProperty) + { + FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); + ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + } + else + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + } + + if (ValuePtr) + { + TSoftObjectPtr ValueObjectPtr; + ValueObjectPtr = Value; + UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); + + // Ensure the ObjectProperty class matches the ValueObject that we just loaded + if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) + { + ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); + OnPropertyChanged(ObjectProperty); + } + } + } + else + { + // Property was found, but is of an unsupported type + FString PropertyClass = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); + HOUDINI_LOG_MESSAGE(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClass, *InGenericAttribute.AttributeName); + return false; + } + } + + if (bHasModifiedProperty) + { +#if WITH_EDITOR + InObject->PostEditChange(); + if (InOwner) + { + InOwner->PostEditChange(); + } +#endif + } + + return true; +} + +/* +bool +FHoudiniEngineUtils::TryToFindInStructProperty( + UObject* Object, FString UPropertyNameToFind, UStructProperty* StructProperty, UProperty*& FoundProperty, void*& StructContainer ) +{ + if ( !StructProperty || !Object ) + return false; + + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + for (TFieldIterator< UProperty > It(Struct); It; ++It) + { + UProperty* Property = *It; + if ( !Property ) + continue; + + FString DisplayName = It->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = It->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = StructProperty->ContainerPtrToValuePtr< void >( Object, 0); + + // If it's an equality, we dont need to keep searching + if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) + return true; + } + + if ( FoundProperty ) + continue; + + UStructProperty* NestedStruct = Cast( Property ); + if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) + return true; + + UArrayProperty* ArrayProp = Cast( Property ); + if ( ArrayProp && TryToFindInArrayProperty( Object, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::TryToFindInArrayProperty( + UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ) +{ + if ( !ArrayProperty || !Object ) + return false; + + UProperty* Property = ArrayProperty->Inner; + if ( !Property ) + return false; + + FString DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = Property->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = ArrayProperty->ContainerPtrToValuePtr< void >( Object, 0); + + // If it's an equality, we dont need to keep searching + if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) + return true; + } + + if ( !FoundProperty ) + { + UStructProperty* NestedStruct = Cast( Property ); + if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) + return true; + + UArrayProperty* ArrayProp = Cast( Property ); + if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) + return true; + } + + return false; +} */ \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h index 778ef5fba..9ccab2a46 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h @@ -1,123 +1,123 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniGenericAttribute.generated.h" - -UENUM() -enum class EAttribStorageType : int8 -{ - Invalid = -1, - - INT = 0, - INT64 = 1, - FLOAT = 2, - FLOAT64 = 3, - STRING = 4 -}; - -UENUM() -enum class EAttribOwner : int8 -{ - Invalid = -1, - - Vertex, - Point, - Prim, - Detail, -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY() - FString AttributeName; - - UPROPERTY() - EAttribStorageType AttributeType; - UPROPERTY() - EAttribOwner AttributeOwner; - - UPROPERTY() - int32 AttributeCount; - UPROPERTY() - int32 AttributeTupleSize; - - UPROPERTY() - TArray DoubleValues; - UPROPERTY() - TArray IntValues; - UPROPERTY() - TArray StringValues; - - double GetDoubleValue(int32 index = 0); - void GetDoubleTuple(TArray& TupleValues, int32 index = 0); - - int64 GetIntValue(int32 index = 0); - void GetIntTuple(TArray& TupleValues, int32 index = 0); - - FString GetStringValue(int32 index = 0); - void GetStringTuple(TArray& TupleValues, int32 index = 0); - - bool GetBoolValue(int32 index = 0); - void GetBoolTuple(TArray& TupleValues, int32 index = 0); - - void* GetData(); - - // - static bool UpdatePropertyAttributeOnObject( - UObject* InObject, FHoudiniGenericAttribute InPropertyAttribute, const int32& AtIndex = 0); - - // Tries to find a Uproperty by name/label on an object - // FoundPropertyObject will be the object that actually contains the property - // and can be different from InObject if the property is nested. - static bool FindPropertyOnObject( - UObject* InObject, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - UObject*& OutFoundPropertyObject, - void*& OutContainer); - - // Modifies the value of a found Property - static bool ModifyPropertyValueOnObject( - UObject* InObject, - FHoudiniGenericAttribute InGenericAttribute, - FProperty* FoundProperty, - void* InContainer, - const int32& AtIndex = 0 ); - - // Recursive search for a given property on a UObject - static bool TryToFindProperty( - void* InContainer, - UStruct* InStruct, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - bool& bOutPropertyHasBeenFound, - void*& OutContainer); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniGenericAttribute.generated.h" + +UENUM() +enum class EAttribStorageType : int8 +{ + Invalid = -1, + + INT = 0, + INT64 = 1, + FLOAT = 2, + FLOAT64 = 3, + STRING = 4 +}; + +UENUM() +enum class EAttribOwner : int8 +{ + Invalid = -1, + + Vertex, + Point, + Prim, + Detail, +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY() + FString AttributeName; + + UPROPERTY() + EAttribStorageType AttributeType; + UPROPERTY() + EAttribOwner AttributeOwner; + + UPROPERTY() + int32 AttributeCount; + UPROPERTY() + int32 AttributeTupleSize; + + UPROPERTY() + TArray DoubleValues; + UPROPERTY() + TArray IntValues; + UPROPERTY() + TArray StringValues; + + double GetDoubleValue(int32 index = 0) const; + void GetDoubleTuple(TArray& TupleValues, int32 index = 0) const; + + int64 GetIntValue(int32 index = 0) const; + void GetIntTuple(TArray& TupleValues, int32 index = 0) const; + + FString GetStringValue(int32 index = 0) const; + void GetStringTuple(TArray& TupleValues, int32 index = 0) const; + + bool GetBoolValue(int32 index = 0) const; + void GetBoolTuple(TArray& TupleValues, int32 index = 0) const; + + void* GetData(); + + // + static bool UpdatePropertyAttributeOnObject( + UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex = 0); + + // Tries to find a Uproperty by name/label on an object + // FoundPropertyObject will be the object that actually contains the property + // and can be different from InObject if the property is nested. + static bool FindPropertyOnObject( + UObject* InObject, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + UObject*& OutFoundPropertyObject, + void*& OutContainer); + + // Modifies the value of a found Property + static bool ModifyPropertyValueOnObject( + UObject* InObject, + FHoudiniGenericAttribute InGenericAttribute, + FProperty* FoundProperty, + void* InContainer, + const int32& AtIndex = 0 ); + + // Recursive search for a given property on a UObject + static bool TryToFindProperty( + void* InContainer, + UStruct* InStruct, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + bool& bOutPropertyHasBeenFound, + void*& OutContainer); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp deleted file mode 100644 index 8a63839cc..000000000 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h b/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h deleted file mode 100644 index 2853e8148..000000000 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoAsset.h +++ /dev/null @@ -1,28 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp index bd9cb8ebf..49171ceb6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp @@ -1,185 +1,185 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoPartObject.h" - -// -FHoudiniGeoPartObject::FHoudiniGeoPartObject() - : AssetId(-1) - , AssetName(TEXT("")) - , ObjectId(-1) - , ObjectName(TEXT("")) - , GeoId(-1) - , PartId(-1) - , PartName(TEXT("")) - , bHasCustomPartName(false) - , TransformMatrix(FMatrix::Identity) - , NodePath(TEXT("")) - , Type(EHoudiniPartType::Invalid) - , InstancerType(EHoudiniInstancerType::Invalid) - , VolumeName(TEXT("")) - , VolumeTileIndex(-1) - , bIsVisible(false) - , bIsEditable(false) - , bIsTemplated(false) - , bIsInstanced(false) - , bHasGeoChanged(true) - , bHasPartChanged(true) - , bHasTransformChanged(true) - , bHasMaterialsChanged(true) - , bLoaded(false) -{ - -} - -bool -FHoudiniGeoPartObject::IsValid() const -{ - return (ObjectId >= 0 && GeoId >= 0 && PartId >= 0); -} - -bool -FHoudiniGeoPartObject::operator==(const FHoudiniGeoPartObject & InGeoPartObject) const -{ - // TODO: split?? - return Equals(InGeoPartObject, true); -} - -bool -FHoudiniGeoPartObject::Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const -{ - // TODO: This will likely need some improvement! - - /* - // Object/Geo/Part IDs must match - if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) - return false; - - if (!bIgnoreSplit) - { - // If the split type and index match, we're equal... - if (SplitType == GeoPartObject.SplitType && SplitIndex == GeoPartObject.SplitIndex) - return true; - - // ... if not we should compare our names - return CompareNames(GeoPartObject, bIgnoreSplit); - } - */ - - // See if objects / geo / part ids match - bool MatchingIDs = true; - if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) - MatchingIDs = false; - - // See if the type matches - bool MatchingType = (Type == GeoPartObject.Type); - // Both IDs and type match, consider the two HGPO as equals - if (MatchingIDs && MatchingType) - return true; - - // Both IDs and type do not match, consider the two HGPOs as different - if (!MatchingIDs && !MatchingType) - return false; - - // If only the ID dont match we can do some further checks - - // If one of the two HGPO has been loaded - if ((bLoaded && !GeoPartObject.bLoaded) - || (!bLoaded && GeoPartObject.bLoaded)) - { - // For loaded HGPOs, part names should be a sufficent comparison - if (PartName.Equals(GeoPartObject.PartName)) - return true; - } - - // TODO: This was causing issues somehow with tiled landscapes - // ... if not, compare by names - if(!MatchingIDs) - return CompareNames(GeoPartObject, bIgnoreSplit); - - return false; -} - -void -FHoudiniGeoPartObject::SetCustomPartName(const FString & InName) -{ - if (InName.IsEmpty()) - return; - - PartName = InName; - bHasCustomPartName = true; -} - -bool -FHoudiniGeoPartObject::CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const -{ - //TODO: AssetName? - - // Object, part and split names must match - if (!ObjectName.Equals(HoudiniGeoPartObject.ObjectName) - || !PartName.Equals(HoudiniGeoPartObject.PartName)) - { - return false; - } - - /* - // Split should also match if we dont ignore it - if (!bIgnoreSplit && !SplitName.Equals(HoudiniGeoPartObject.SplitName)) - { - return false; - } - */ - - return true; -} - -FString -FHoudiniGeoPartObject::HoudiniPartTypeToString(const EHoudiniPartType& InType) -{ - FString OutTypeStr; - switch (InType) - { - case EHoudiniPartType::Mesh: - OutTypeStr = TEXT("Mesh"); - break; - case EHoudiniPartType::Instancer: - OutTypeStr = TEXT("Instancer"); - break; - case EHoudiniPartType::Curve: - OutTypeStr = TEXT("Curve"); - break; - case EHoudiniPartType::Volume: - OutTypeStr = TEXT("Volume"); - break; - - default: - case EHoudiniPartType::Invalid: - OutTypeStr = TEXT("Invalid"); - break; - } - - return OutTypeStr; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoPartObject.h" + +// +FHoudiniGeoPartObject::FHoudiniGeoPartObject() + : AssetId(-1) + , AssetName(TEXT("")) + , ObjectId(-1) + , ObjectName(TEXT("")) + , GeoId(-1) + , PartId(-1) + , PartName(TEXT("")) + , bHasCustomPartName(false) + , TransformMatrix(FMatrix::Identity) + , NodePath(TEXT("")) + , Type(EHoudiniPartType::Invalid) + , InstancerType(EHoudiniInstancerType::Invalid) + , VolumeName(TEXT("")) + , VolumeTileIndex(-1) + , bIsVisible(false) + , bIsEditable(false) + , bIsTemplated(false) + , bIsInstanced(false) + , bHasGeoChanged(true) + , bHasPartChanged(true) + , bHasTransformChanged(true) + , bHasMaterialsChanged(true) + , bLoaded(false) +{ + +} + +bool +FHoudiniGeoPartObject::IsValid() const +{ + return (ObjectId >= 0 && GeoId >= 0 && PartId >= 0); +} + +bool +FHoudiniGeoPartObject::operator==(const FHoudiniGeoPartObject & InGeoPartObject) const +{ + // TODO: split?? + return Equals(InGeoPartObject, true); +} + +bool +FHoudiniGeoPartObject::Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const +{ + // TODO: This will likely need some improvement! + + /* + // Object/Geo/Part IDs must match + if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) + return false; + + if (!bIgnoreSplit) + { + // If the split type and index match, we're equal... + if (SplitType == GeoPartObject.SplitType && SplitIndex == GeoPartObject.SplitIndex) + return true; + + // ... if not we should compare our names + return CompareNames(GeoPartObject, bIgnoreSplit); + } + */ + + // See if objects / geo / part ids match + bool MatchingIDs = true; + if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) + MatchingIDs = false; + + // See if the type matches + bool MatchingType = (Type == GeoPartObject.Type); + // Both IDs and type match, consider the two HGPO as equals + if (MatchingIDs && MatchingType) + return true; + + // Both IDs and type do not match, consider the two HGPOs as different + if (!MatchingIDs && !MatchingType) + return false; + + // If only the ID dont match we can do some further checks + + // If one of the two HGPO has been loaded + if ((bLoaded && !GeoPartObject.bLoaded) + || (!bLoaded && GeoPartObject.bLoaded)) + { + // For loaded HGPOs, part names should be a sufficent comparison + if (PartName.Equals(GeoPartObject.PartName)) + return true; + } + + // TODO: This was causing issues somehow with tiled landscapes + // ... if not, compare by names + if(!MatchingIDs) + return CompareNames(GeoPartObject, bIgnoreSplit); + + return false; +} + +void +FHoudiniGeoPartObject::SetCustomPartName(const FString & InName) +{ + if (InName.IsEmpty()) + return; + + PartName = InName; + bHasCustomPartName = true; +} + +bool +FHoudiniGeoPartObject::CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const +{ + //TODO: AssetName? + + // Object, part and split names must match + if (!ObjectName.Equals(HoudiniGeoPartObject.ObjectName) + || !PartName.Equals(HoudiniGeoPartObject.PartName)) + { + return false; + } + + /* + // Split should also match if we dont ignore it + if (!bIgnoreSplit && !SplitName.Equals(HoudiniGeoPartObject.SplitName)) + { + return false; + } + */ + + return true; +} + +FString +FHoudiniGeoPartObject::HoudiniPartTypeToString(const EHoudiniPartType& InType) +{ + FString OutTypeStr; + switch (InType) + { + case EHoudiniPartType::Mesh: + OutTypeStr = TEXT("Mesh"); + break; + case EHoudiniPartType::Instancer: + OutTypeStr = TEXT("Instancer"); + break; + case EHoudiniPartType::Curve: + OutTypeStr = TEXT("Curve"); + break; + case EHoudiniPartType::Volume: + OutTypeStr = TEXT("Volume"); + break; + + default: + case EHoudiniPartType::Invalid: + OutTypeStr = TEXT("Invalid"); + break; + } + + return OutTypeStr; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h index 5255c4d99..099a37da6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h @@ -1,419 +1,419 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniGeoPartObject.generated.h" - -UENUM() -enum class EHoudiniGeoType : uint8 -{ - Invalid, - - Default, - Intermediate, - Input, - Curve -}; - -UENUM() -enum class EHoudiniPartType : uint8 -{ - Invalid, - - Mesh, - Instancer, - Curve, - Volume -}; - -UENUM() -enum class EHoudiniInstancerType : uint8 -{ - Invalid, - - ObjectInstancer, - PackedPrimitive, - AttributeInstancer, - OldSchoolAttributeInstancer -}; - -UENUM() -enum class EHoudiniCurveType : int8 -{ - Invalid = -1, - - Polygon = 0, - Nurbs = 1, - Bezier = 2, - Points = 3 -}; - -UENUM() -enum class EHoudiniCurveMethod : int8 -{ - Invalid = -1, - - CVs = 0, - Breakpoints = 1, - Freehand = 2 -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniObjectInfo -{ - GENERATED_USTRUCT_BODY() - - FString Name = TEXT(""); - - int32 NodeId = -1; - int32 ObjectToInstanceID = -1; - - bool bHasTransformChanged = false; - bool bHaveGeosChanged = false; - bool bIsVisible = false; - bool bIsInstancer = false; - bool bIsInstanced = false; - - int32 GeoCount = -1; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGeoInfo -{ - GENERATED_USTRUCT_BODY() - - EHoudiniGeoType Type = EHoudiniGeoType::Invalid; - FString Name = TEXT(""); - int32 NodeId = -1; - - bool bIsEditable = false; - bool bIsTemplated = false; - bool bIsDisplayGeo = false; - bool bHasGeoChanged = false; - bool bHasMaterialChanged = false; - - int32 PartCount = -1; - int32 PointGroupCount = -1; - int32 PrimitiveGroupCount = -1; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniPartInfo -{ - GENERATED_USTRUCT_BODY() - - int32 PartId = -1; - FString Name = TEXT(""); - - EHoudiniPartType Type = EHoudiniPartType::Invalid; - - int32 FaceCount = -1; - int32 VertexCount = -1; - int32 PointCount = -1; - - int32 PointAttributeCounts = -1; - int32 VertexAttributeCounts = -1; - int32 PrimitiveAttributeCounts = -1; - int32 DetailAttributeCounts = -1; - - bool bIsInstanced = false; - - int32 InstancedPartCount = -1; - int32 InstanceCount = -1; - - bool bHasChanged = false; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniVolumeInfo -{ - GENERATED_USTRUCT_BODY() - - FString Name = TEXT(""); - bool bIsVDB = false; // replaces VolumeType Type; - - int32 TupleSize = -1; - bool bIsFloat = false; // replaces StorageType StorageType; - int32 TileSize = -1; - - FTransform Transform = FTransform::Identity; - bool bHasTaper = false; - - int32 XLength = -1; - int32 YLength = -1; - int32 ZLength = -1; - - int32 MinX = -1; - int32 MinY = -1; - int32 MinZ = -1; - - float XTaper = 0.0f; - float YTaper = 0.0f; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniCurveInfo -{ - GENERATED_USTRUCT_BODY() - - EHoudiniCurveType Type = EHoudiniCurveType::Invalid; - - int32 CurveCount = -1; - int32 VertexCount = -1; - int32 KnotCount = -1; - - bool bIsPeriodic = false; - bool bIsRational = false; - - int32 Order = -1; - - bool bHasKnots = false; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniMeshSocket -{ - GENERATED_USTRUCT_BODY() - - // Equality operator, used by containers - bool operator==(const FHoudiniMeshSocket& InSocket) const - { - return Transform.Equals(InSocket.Transform) - && Name == InSocket.Name - && Actor == InSocket.Actor - && Tag == InSocket.Tag; - } - - // Members - FTransform Transform = FTransform::Identity; - FString Name = TEXT("Socket"); - FString Actor = FString(); - FString Tag = FString(); -}; - -/* -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniSplitDataCache -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY() - FString SplitName; - //UPROPERTY() - //FHoudiniOutputObjectIdentifier SplitIdentifier; - //EHoudiniSplitType SplitType; - - UPROPERTY() - TArray Positions; - UPROPERTY() - TArray Indices; - - UPROPERTY() - TArray Normals; - UPROPERTY() - TArray Tangents; - UPROPERTY() - TArray Binormals; - - UPROPERTY() - TArray Colors; - - //UPROPERTY() - //TArray> UVs; - - //TArray EdgeHardnesses; - UPROPERTY() - TArray FaceSmoothingMasks; - UPROPERTY() - TArray LightMapResolutions; - - UPROPERTY() - TArray MaterialIndices; - UPROPERTY() - TArray Materials; - - UPROPERTY() - float lod_screensize; - - UPROPERTY() - FKAggregateGeom AggregateCollisions; -}; -*/ - - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject -{ -public: - - GENERATED_USTRUCT_BODY() - - FHoudiniGeoPartObject(); - - // Indicates if this HGPO is valid - bool IsValid() const; - - // Equality operator, used by containers - bool operator==(const FHoudiniGeoPartObject & GeoPartObject) const; - - // Checks equality, with the possibility to ignore the HGPO's splits - bool Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const; - - // Comparison based on object/part/split name. - bool CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const; - - void SetCustomPartName(const FString & InName); - - static FString HoudiniPartTypeToString(const EHoudiniPartType& InType); - -public: - - // NodeId of corresponding HAPI Asset. - UPROPERTY() - int32 AssetId; - - // Name of corresponding HDA. - UPROPERTY() - FString AssetName; - - // NodeId of corresponding HAPI Object. - UPROPERTY() - int32 ObjectId; - - // Name of associated object. - UPROPERTY() - FString ObjectName; - - // NodeId of corresponding HAPI Geo. - UPROPERTY() - int32 GeoId; - - // PartId of corresponding HAPI Part. - UPROPERTY() - int32 PartId; - - // Name of associated part. - UPROPERTY() - FString PartName; - - UPROPERTY() - bool bHasCustomPartName; - - /* - // Type of the split. - UPROPERTY() - EHoudiniSplitType SplitType; - - // Index of a split. In most cases this will be 0. - UPROPERTY() - int32 SplitIndex; - - // Name of group which was used for splitting, empty if there's none. - UPROPERTY() - FString SplitName; - */ - - // Split groups handled by this HGPO - UPROPERTY() - TArray SplitGroups; - - // Transform of this geo part object. - UPROPERTY() - FTransform TransformMatrix; - - // Path to the corresponding node - UPROPERTY() - FString NodePath; - - // Indicates the type of the referenced object - UPROPERTY() - EHoudiniPartType Type; - - // Indicates the type of instancer - UPROPERTY() - EHoudiniInstancerType InstancerType; - - // - UPROPERTY() - FString VolumeName; - - // - UPROPERTY() - int32 VolumeTileIndex; - - // Is set to true when referenced object is visible. - UPROPERTY() - bool bIsVisible; - - // Is set to true when referenced object is editable. - UPROPERTY() - bool bIsEditable; - - // Is set to true when referenced object is templated. - UPROPERTY() - bool bIsTemplated; - - // Is set to true when the referenced object is instanced. - UPROPERTY() - bool bIsInstanced; - - // Indicates the parent geo has changed and needs to be rebuilt - UPROPERTY() - bool bHasGeoChanged; - - // Indicates the part has changed and needs to be rebuilt - UPROPERTY() - bool bHasPartChanged; - - // Indicates only the transform needs to be updated - UPROPERTY() - bool bHasTransformChanged; - - // Indicates only the material needs to be updated - UPROPERTY() - bool bHasMaterialsChanged; - - // Indicates this object has been loaded - bool bLoaded; - - // We also keep a cache of the various info objects - // That we've extracted from HAPI - - // ObjectInfo cache - FHoudiniObjectInfo ObjectInfo; - // GeoInfo cache - FHoudiniGeoInfo GeoInfo; - // PartInfo cache - FHoudiniPartInfo PartInfo; - // VolumeInfo cache - FHoudiniVolumeInfo VolumeInfo; - // CurveInfo cache - FHoudiniCurveInfo CurveInfo; - - // Cache of this HGPO split data - //TArray SplitCache; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniGeoPartObject.generated.h" + +UENUM() +enum class EHoudiniGeoType : uint8 +{ + Invalid, + + Default, + Intermediate, + Input, + Curve +}; + +UENUM() +enum class EHoudiniPartType : uint8 +{ + Invalid, + + Mesh, + Instancer, + Curve, + Volume +}; + +UENUM() +enum class EHoudiniInstancerType : uint8 +{ + Invalid, + + ObjectInstancer, + PackedPrimitive, + AttributeInstancer, + OldSchoolAttributeInstancer +}; + +UENUM() +enum class EHoudiniCurveType : int8 +{ + Invalid = -1, + + Polygon = 0, + Nurbs = 1, + Bezier = 2, + Points = 3 +}; + +UENUM() +enum class EHoudiniCurveMethod : int8 +{ + Invalid = -1, + + CVs = 0, + Breakpoints = 1, + Freehand = 2 +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniObjectInfo +{ + GENERATED_USTRUCT_BODY() + + FString Name = TEXT(""); + + int32 NodeId = -1; + int32 ObjectToInstanceID = -1; + + bool bHasTransformChanged = false; + bool bHaveGeosChanged = false; + bool bIsVisible = false; + bool bIsInstancer = false; + bool bIsInstanced = false; + + int32 GeoCount = -1; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGeoInfo +{ + GENERATED_USTRUCT_BODY() + + EHoudiniGeoType Type = EHoudiniGeoType::Invalid; + FString Name = TEXT(""); + int32 NodeId = -1; + + bool bIsEditable = false; + bool bIsTemplated = false; + bool bIsDisplayGeo = false; + bool bHasGeoChanged = false; + bool bHasMaterialChanged = false; + + int32 PartCount = -1; + int32 PointGroupCount = -1; + int32 PrimitiveGroupCount = -1; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniPartInfo +{ + GENERATED_USTRUCT_BODY() + + int32 PartId = -1; + FString Name = TEXT(""); + + EHoudiniPartType Type = EHoudiniPartType::Invalid; + + int32 FaceCount = -1; + int32 VertexCount = -1; + int32 PointCount = -1; + + int32 PointAttributeCounts = -1; + int32 VertexAttributeCounts = -1; + int32 PrimitiveAttributeCounts = -1; + int32 DetailAttributeCounts = -1; + + bool bIsInstanced = false; + + int32 InstancedPartCount = -1; + int32 InstanceCount = -1; + + bool bHasChanged = false; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniVolumeInfo +{ + GENERATED_USTRUCT_BODY() + + FString Name = TEXT(""); + bool bIsVDB = false; // replaces VolumeType Type; + + int32 TupleSize = -1; + bool bIsFloat = false; // replaces StorageType StorageType; + int32 TileSize = -1; + + FTransform Transform = FTransform::Identity; + bool bHasTaper = false; + + int32 XLength = -1; + int32 YLength = -1; + int32 ZLength = -1; + + int32 MinX = -1; + int32 MinY = -1; + int32 MinZ = -1; + + float XTaper = 0.0f; + float YTaper = 0.0f; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniCurveInfo +{ + GENERATED_USTRUCT_BODY() + + EHoudiniCurveType Type = EHoudiniCurveType::Invalid; + + int32 CurveCount = -1; + int32 VertexCount = -1; + int32 KnotCount = -1; + + bool bIsPeriodic = false; + bool bIsRational = false; + + int32 Order = -1; + + bool bHasKnots = false; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniMeshSocket +{ + GENERATED_USTRUCT_BODY() + + // Equality operator, used by containers + bool operator==(const FHoudiniMeshSocket& InSocket) const + { + return Transform.Equals(InSocket.Transform) + && Name == InSocket.Name + && Actor == InSocket.Actor + && Tag == InSocket.Tag; + } + + // Members + FTransform Transform = FTransform::Identity; + FString Name = TEXT("Socket"); + FString Actor = FString(); + FString Tag = FString(); +}; + +/* +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniSplitDataCache +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY() + FString SplitName; + //UPROPERTY() + //FHoudiniOutputObjectIdentifier SplitIdentifier; + //EHoudiniSplitType SplitType; + + UPROPERTY() + TArray Positions; + UPROPERTY() + TArray Indices; + + UPROPERTY() + TArray Normals; + UPROPERTY() + TArray Tangents; + UPROPERTY() + TArray Binormals; + + UPROPERTY() + TArray Colors; + + //UPROPERTY() + //TArray> UVs; + + //TArray EdgeHardnesses; + UPROPERTY() + TArray FaceSmoothingMasks; + UPROPERTY() + TArray LightMapResolutions; + + UPROPERTY() + TArray MaterialIndices; + UPROPERTY() + TArray Materials; + + UPROPERTY() + float lod_screensize; + + UPROPERTY() + FKAggregateGeom AggregateCollisions; +}; +*/ + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject +{ +public: + + GENERATED_USTRUCT_BODY() + + FHoudiniGeoPartObject(); + + // Indicates if this HGPO is valid + bool IsValid() const; + + // Equality operator, used by containers + bool operator==(const FHoudiniGeoPartObject & GeoPartObject) const; + + // Checks equality, with the possibility to ignore the HGPO's splits + bool Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const; + + // Comparison based on object/part/split name. + bool CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const; + + void SetCustomPartName(const FString & InName); + + static FString HoudiniPartTypeToString(const EHoudiniPartType& InType); + +public: + + // NodeId of corresponding HAPI Asset. + UPROPERTY() + int32 AssetId; + + // Name of corresponding HDA. + UPROPERTY() + FString AssetName; + + // NodeId of corresponding HAPI Object. + UPROPERTY() + int32 ObjectId; + + // Name of associated object. + UPROPERTY() + FString ObjectName; + + // NodeId of corresponding HAPI Geo. + UPROPERTY() + int32 GeoId; + + // PartId of corresponding HAPI Part. + UPROPERTY() + int32 PartId; + + // Name of associated part. + UPROPERTY() + FString PartName; + + UPROPERTY() + bool bHasCustomPartName; + + /* + // Type of the split. + UPROPERTY() + EHoudiniSplitType SplitType; + + // Index of a split. In most cases this will be 0. + UPROPERTY() + int32 SplitIndex; + + // Name of group which was used for splitting, empty if there's none. + UPROPERTY() + FString SplitName; + */ + + // Split groups handled by this HGPO + UPROPERTY() + TArray SplitGroups; + + // Transform of this geo part object. + UPROPERTY() + FTransform TransformMatrix; + + // Path to the corresponding node + UPROPERTY() + FString NodePath; + + // Indicates the type of the referenced object + UPROPERTY() + EHoudiniPartType Type; + + // Indicates the type of instancer + UPROPERTY() + EHoudiniInstancerType InstancerType; + + // + UPROPERTY() + FString VolumeName; + + // + UPROPERTY() + int32 VolumeTileIndex; + + // Is set to true when referenced object is visible. + UPROPERTY() + bool bIsVisible; + + // Is set to true when referenced object is editable. + UPROPERTY() + bool bIsEditable; + + // Is set to true when referenced object is templated. + UPROPERTY() + bool bIsTemplated; + + // Is set to true when the referenced object is instanced. + UPROPERTY() + bool bIsInstanced; + + // Indicates the parent geo has changed and needs to be rebuilt + UPROPERTY() + bool bHasGeoChanged; + + // Indicates the part has changed and needs to be rebuilt + UPROPERTY() + bool bHasPartChanged; + + // Indicates only the transform needs to be updated + UPROPERTY() + bool bHasTransformChanged; + + // Indicates only the material needs to be updated + UPROPERTY() + bool bHasMaterialsChanged; + + // Indicates this object has been loaded + bool bLoaded; + + // We also keep a cache of the various info objects + // That we've extracted from HAPI + + // ObjectInfo cache + FHoudiniObjectInfo ObjectInfo; + // GeoInfo cache + FHoudiniGeoInfo GeoInfo; + // PartInfo cache + FHoudiniPartInfo PartInfo; + // VolumeInfo cache + FHoudiniVolumeInfo VolumeInfo; + // CurveInfo cache + FHoudiniCurveInfo CurveInfo; + + // Cache of this HGPO split data + //TArray SplitCache; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp index d306f8808..6d76a2c12 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp @@ -1,253 +1,255 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleComponent.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniRuntimeSettings.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -void -UHoudiniHandleComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniHandleComponent_V1* CompatibilityHC = NewObject(); - CompatibilityHC->Serialize(Ar); - CompatibilityHC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : serialized data will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -UHoudiniHandleParameter::UHoudiniHandleParameter(const FObjectInitializer & ObjectInitializer) - :Super(ObjectInitializer) -{}; - -UHoudiniHandleComponent::UHoudiniHandleComponent(const FObjectInitializer & ObjectInitializer) - :Super(ObjectInitializer) -{}; - - -bool -UHoudiniHandleParameter::Bind(float & OutValue, const char * CmpName, int32 InTupleIdx, - const FString & HandleParmName, UHoudiniParameter* Parameter) -{ - if (!Parameter) - return false; - - if (HandleParmName != CmpName) - return false; - - UHoudiniParameterFloat* FloatParameter = Cast(Parameter); - - if (!FloatParameter) - return false; - - AssetParameter = Parameter; - - if (FloatParameter) - { - // It is possible that the handle param is bound to a single tuple param. - // Ignore the preset tuple index if that's the case or we'll crash. - if (Parameter->GetTupleSize() <= InTupleIdx) - InTupleIdx = 0; - - auto Optional = FloatParameter->GetValue(InTupleIdx); - if (Optional.IsSet()) - { - TupleIndex = InTupleIdx; - OutValue = Optional.GetValue(); - return true; - } - } - - return false; -} - -bool -UHoudiniHandleParameter::Bind(TSharedPtr & OutValue, const char * CmpName, - int32 InTupleIdx, const FString & HandleParmName, UHoudiniParameter* Parameter) -{ - if (!Parameter) - return false; - - if (HandleParmName != CmpName) - return false; - - UHoudiniParameterChoice* ChoiceParameter = Cast(Parameter); - - if (!ChoiceParameter) - return false; - - AssetParameter = Parameter; - - if (ChoiceParameter) - { - // It is possible that the handle param is bound to a single tuple param. - // Ignore the preset tuple index if that's the case or we'll crash. - if (Parameter->GetTupleSize() <= InTupleIdx) - InTupleIdx = 0; - - auto Optional = ChoiceParameter->GetValue(InTupleIdx); - if (Optional.IsSet()) - { - TupleIndex = InTupleIdx; - OutValue = Optional.GetValue(); - return true; - } - } - - return false; -} - -TSharedPtr -UHoudiniHandleParameter::Get(TSharedPtr DefaultValue) const -{ - UHoudiniParameterChoice* ChoiceParameter = Cast(AssetParameter); - if (ChoiceParameter) - { - auto Optional = ChoiceParameter->GetValue(TupleIndex); - if (Optional.IsSet()) - return Optional.GetValue(); - } - - return DefaultValue; -} - -UHoudiniHandleParameter & -UHoudiniHandleParameter::operator=(float Value) -{ - UHoudiniParameterFloat* FloatParameter = Cast(AssetParameter); - if (FloatParameter) - { - FloatParameter->SetValue(Value, TupleIndex); - FloatParameter->MarkChanged(true); - } - - return *this; -} - -void -UHoudiniHandleComponent::InitializeHandleParameters() -{ - if (XformParms.Num() < int32(EXformParameter::COUNT)) - { - XformParms.Empty(); - for (int32 n = 0; n < int32(EXformParameter::COUNT); ++n) - { - UHoudiniHandleParameter* XformHandle = NewObject(this, UHoudiniHandleParameter::StaticClass()); - XformParms.Add(XformHandle); - } - } - - if (!RSTParm) - { - RSTParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); - } - - if (!RotOrderParm) - { - RotOrderParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); - } -} - -bool -UHoudiniHandleComponent::CheckHandleValid() const -{ - if (XformParms.Num() < int32(EXformParameter::COUNT)) - return false; - - for (auto& XformParm : XformParms) - { - if (!XformParm) - return false; - } - - if (!RSTParm) - return false; - - if (!RotOrderParm) - return false; - - return true; -} - -FBox -UHoudiniHandleComponent::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - return BoxBounds + GetComponentLocation(); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniRuntimeSettings.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +void +UHoudiniHandleComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniHandleComponent_V1* CompatibilityHC = NewObject(); + CompatibilityHC->Serialize(Ar); + CompatibilityHC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : serialized data will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +UHoudiniHandleParameter::UHoudiniHandleParameter(const FObjectInitializer & ObjectInitializer) + :Super(ObjectInitializer) +{}; + +UHoudiniHandleComponent::UHoudiniHandleComponent(const FObjectInitializer & ObjectInitializer) + :Super(ObjectInitializer) +{}; + + +bool +UHoudiniHandleParameter::Bind(float & OutValue, const char * CmpName, int32 InTupleIdx, + const FString & HandleParmName, UHoudiniParameter* Parameter) +{ + if (!Parameter) + return false; + + if (HandleParmName != CmpName) + return false; + + UHoudiniParameterFloat* FloatParameter = Cast(Parameter); + + if (!FloatParameter) + return false; + + AssetParameter = Parameter; + + if (FloatParameter) + { + // It is possible that the handle param is bound to a single tuple param. + // Ignore the preset tuple index if that's the case or we'll crash. + if (Parameter->GetTupleSize() <= InTupleIdx) + InTupleIdx = 0; + + auto Optional = FloatParameter->GetValue(InTupleIdx); + if (Optional.IsSet()) + { + TupleIndex = InTupleIdx; + OutValue = Optional.GetValue(); + return true; + } + } + + return false; +} + +bool +UHoudiniHandleParameter::Bind(TSharedPtr & OutValue, const char * CmpName, + int32 InTupleIdx, const FString & HandleParmName, UHoudiniParameter* Parameter) +{ + if (!Parameter) + return false; + + if (HandleParmName != CmpName) + return false; + + UHoudiniParameterChoice* ChoiceParameter = Cast(Parameter); + + if (!ChoiceParameter) + return false; + + AssetParameter = Parameter; + + if (ChoiceParameter) + { + // It is possible that the handle param is bound to a single tuple param. + // Ignore the preset tuple index if that's the case or we'll crash. + if (Parameter->GetTupleSize() <= InTupleIdx) + InTupleIdx = 0; + + auto Optional = ChoiceParameter->GetValue(InTupleIdx); + if (Optional.IsSet()) + { + TupleIndex = InTupleIdx; + OutValue = Optional.GetValue(); + return true; + } + } + + return false; +} + +TSharedPtr +UHoudiniHandleParameter::Get(TSharedPtr DefaultValue) const +{ + UHoudiniParameterChoice* ChoiceParameter = Cast(AssetParameter); + if (ChoiceParameter) + { + auto Optional = ChoiceParameter->GetValue(TupleIndex); + if (Optional.IsSet()) + return Optional.GetValue(); + } + + return DefaultValue; +} + +UHoudiniHandleParameter & +UHoudiniHandleParameter::operator=(float Value) +{ + UHoudiniParameterFloat* FloatParameter = Cast(AssetParameter); + if (FloatParameter) + { + FloatParameter->SetValue(Value, TupleIndex); + FloatParameter->MarkChanged(true); + } + + return *this; +} + +void +UHoudiniHandleComponent::InitializeHandleParameters() +{ + if (XformParms.Num() < int32(EXformParameter::COUNT)) + { + XformParms.Empty(); + for (int32 n = 0; n < int32(EXformParameter::COUNT); ++n) + { + UHoudiniHandleParameter* XformHandle = NewObject(this, UHoudiniHandleParameter::StaticClass()); + XformParms.Add(XformHandle); + } + } + + if (!RSTParm) + { + RSTParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); + } + + if (!RotOrderParm) + { + RotOrderParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); + } +} + +bool +UHoudiniHandleComponent::CheckHandleValid() const +{ + if (XformParms.Num() < int32(EXformParameter::COUNT)) + return false; + + for (auto& XformParm : XformParms) + { + if (!XformParm) + return false; + } + + if (!RSTParm) + return false; + + if (!RotOrderParm) + return false; + + return true; +} + +FBox +UHoudiniHandleComponent::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + return BoxBounds + GetComponentLocation(); +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h index 93f327bbe..d422e95ea 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h @@ -1,135 +1,135 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" - -#include "HoudiniHandleComponent.generated.h" - -class UHoudiniParameter; - -UENUM() -enum class EXformParameter : uint8 -{ - TX, TY, TZ, - RX, RY, RZ, - SX, SY, SZ, - COUNT -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniHandleParameter : public UObject -{ -public: - GENERATED_UCLASS_BODY() - - UPROPERTY() - UHoudiniParameter* AssetParameter; - - UPROPERTY() - int32 TupleIndex; - - - bool Bind( - float & OutValue, - const char * CmpName, - int32 InTupleIdx, - const FString & HandleParmName, - UHoudiniParameter* Parameter); - - bool Bind( - TSharedPtr & OutValue, - const char * CmpName, - int32 InTupleIdx, - const FString & HandleParmName, - UHoudiniParameter* Parameter); - - TSharedPtr Get(TSharedPtr DefaultValue) const; - - UHoudiniHandleParameter & operator=(float Value); - -}; - -UENUM() -enum class EHoudiniHandleType : uint8 -{ - Xform, - Bounder, - Unsupported -}; - -UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) -class HOUDINIENGINERUNTIME_API UHoudiniHandleComponent : public USceneComponent -{ -public: - - friend class UHoudiniAssetComponent; - - friend class FHoudiniHandleComponentVisualizer; - - GENERATED_UCLASS_BODY() - - virtual void Serialize(FArchive & Ar) override; - - FString GetHandleName() const { return HandleName; }; - EHoudiniHandleType GetHandleType() const { return HandleType; }; - - void SetHandleName(const FString& InHandleName) { HandleName = InHandleName; }; - void SetHandleType(const EHoudiniHandleType& InHandleType) { HandleType = InHandleType; }; - - // Equality, consider two handle equals if they have the same name, type, tuple size and disabled status - bool operator==(const UHoudiniHandleComponent& other) const - { - return (HandleType == other.HandleType && HandleName.Equals(other.HandleName)); - } - - bool Matches(const UHoudiniHandleComponent& other) const { return (*this == other); }; - - void InitializeHandleParameters(); - - bool CheckHandleValid() const; - - FBox GetBounds() const; - -public: - UPROPERTY() - TArray XformParms; - - UPROPERTY() - UHoudiniHandleParameter* RSTParm; - - UPROPERTY() - UHoudiniHandleParameter* RotOrderParm; - -private: - UPROPERTY() - EHoudiniHandleType HandleType; - - UPROPERTY() - FString HandleName; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" + +#include "HoudiniHandleComponent.generated.h" + +class UHoudiniParameter; + +UENUM() +enum class EXformParameter : uint8 +{ + TX, TY, TZ, + RX, RY, RZ, + SX, SY, SZ, + COUNT +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniHandleParameter : public UObject +{ +public: + GENERATED_UCLASS_BODY() + + UPROPERTY() + UHoudiniParameter* AssetParameter; + + UPROPERTY() + int32 TupleIndex; + + + bool Bind( + float & OutValue, + const char * CmpName, + int32 InTupleIdx, + const FString & HandleParmName, + UHoudiniParameter* Parameter); + + bool Bind( + TSharedPtr & OutValue, + const char * CmpName, + int32 InTupleIdx, + const FString & HandleParmName, + UHoudiniParameter* Parameter); + + TSharedPtr Get(TSharedPtr DefaultValue) const; + + UHoudiniHandleParameter & operator=(float Value); + +}; + +UENUM() +enum class EHoudiniHandleType : uint8 +{ + Xform, + Bounder, + Unsupported +}; + +UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) +class HOUDINIENGINERUNTIME_API UHoudiniHandleComponent : public USceneComponent +{ +public: + + friend class UHoudiniAssetComponent; + + friend class FHoudiniHandleComponentVisualizer; + + GENERATED_UCLASS_BODY() + + virtual void Serialize(FArchive & Ar) override; + + FString GetHandleName() const { return HandleName; }; + EHoudiniHandleType GetHandleType() const { return HandleType; }; + + void SetHandleName(const FString& InHandleName) { HandleName = InHandleName; }; + void SetHandleType(const EHoudiniHandleType& InHandleType) { HandleType = InHandleType; }; + + // Equality, consider two handle equals if they have the same name, type, tuple size and disabled status + bool operator==(const UHoudiniHandleComponent& other) const + { + return (HandleType == other.HandleType && HandleName.Equals(other.HandleName)); + } + + bool Matches(const UHoudiniHandleComponent& other) const { return (*this == other); }; + + void InitializeHandleParameters(); + + bool CheckHandleValid() const; + + FBox GetBounds() const; + +public: + UPROPERTY() + TArray XformParms; + + UPROPERTY() + UHoudiniHandleParameter* RSTParm; + + UPROPERTY() + UHoudiniHandleParameter* RotOrderParm; + +private: + UPROPERTY() + EHoudiniHandleType HandleType; + + UPROPERTY() + FString HandleName; + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp index 8a3001058..b2860ca5b 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp @@ -1,2584 +1,2584 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInput.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutput.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniAsset.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetBlueprintComponent.h" - -#include "EngineUtils.h" -#include "Engine/Brush.h" -#include "Engine/Engine.h" -#include "Engine/DataTable.h" -#include "Model.h" -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "UObject/UObjectGlobals.h" - -#include "Components/SplineComponent.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Landscape.h" - -#if WITH_EDITOR - -#include "Kismet2/BlueprintEditorUtils.h" -#include "Kismet2/KismetEditorUtilities.h" - -#endif - -// -UHoudiniInput::UHoudiniInput() - : Type(EHoudiniInputType::Invalid) - , PreviousType(EHoudiniInputType::Invalid) - , AssetNodeId(-1) - , InputNodeId(-1) - , InputIndex(0) - , ParmId(-1) - , bIsObjectPathParameter(false) - , bHasChanged(false) - , bPackBeforeMerge(false) - , bExportLODs(false) - , bExportSockets(false) - , bExportColliders(false) - , bCookOnCurveChanged(true) - , bStaticMeshChanged(false) - , bInputAssetConnectedInHoudini(false) - , bSwitchedToCurve(false) - , DefaultCurveOffset(0.f) - , bIsWorldInputBoundSelector(false) - , bWorldInputBoundSelectorAutoUpdate(false) - , UnrealSplineResolution(50.0f) - , bUpdateInputLandscape(false) - , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) - , bLandscapeExportSelectionOnly(false) - , bLandscapeAutoSelectComponent(false) - , bLandscapeExportMaterials(false) - , bLandscapeExportLighting(false) - , bLandscapeExportNormalizedUVs(false) - , bLandscapeExportTileUVs(false) -{ - Name = TEXT(""); - Label = TEXT(""); - SetFlags(RF_Transactional); - - // Geometry inputs always have one null default object - GeometryInputObjects.Add(nullptr); - - KeepWorldTransform = GetDefaultXTransformType(); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; -} - -void -UHoudiniInput::BeginDestroy() -{ - InvalidateData(); - - // DO NOT MANUALLY DESTROY OUR INPUT OBJECTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - - // Mark all our input objects for destruction - ForAllHoudiniInputObjectArrays([](TArray& ObjectArray) { - ObjectArray.Empty(); - }); - - Super::BeginDestroy(); -} - -#if WITH_EDITOR -void UHoudiniInput::PostEditUndo() -{ - Super::PostEditUndo(); - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!InputObjectsPtr) - return; - - MarkChanged(true); - bool bBlueprintStructureChanged = false; - - if (HasInputTypeChanged()) - { - // If the input type has changed on undo, previousType becomes new type - /* This does not work properly, see the corresponding part in FHoudiniInputDetails::AddInputTypeComboBox(...), after Transaction(... - ) - { - EHoudiniInputType NewType = PreviousType; - SetInputType(NewType); - } - */ - EHoudiniInputType Temp = Type; - Type = PreviousType; - PreviousType = EHoudiniInputType::Invalid; - - // If the undo action caused input type changing, treat it as a regular type changing - // after set up the new and prev types properly - SetInputType(Temp, bBlueprintStructureChanged); - } - else - { - if (Type == EHoudiniInputType::Asset) - { - // Mark the input asset object as changed, since only undo changing asset will get into here. - // The input array will be empty when undo adding asset (only support single asset input object in an input now) - for (auto & NextAssetInputObj : *InputObjectsPtr) - { - if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) - continue; - - NextAssetInputObj->MarkChanged(true); - } - } - - - if (Type == EHoudiniInputType::World) - { - if (WorldInputObjects.Num() == 0 && InputNodeId >= 0) - { - for (auto & NextNodeId : CreatedDataNodeIds) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NextNodeId, true); - } - - CreatedDataNodeIds.Empty(); - - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - InputNodeId = -1; - } - } - - if (Type == EHoudiniInputType::Curve) - { - if (PreviousType != EHoudiniInputType::Curve) - { - for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); - if (!SplineInput || SplineInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - USceneComponent* OuterComponent = Cast(GetOuter()); - - // Attach the new Houdini spline component to it's owner - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - HoudiniSplineComponent->MarkChanged(true); - } - return; - } - bool bUndoDelete = false; - bool bUndoInsert = false; - bool bUndoDeletedObjArrayEmptied = false; - - TArray< USceneComponent* > childActor; - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) - childActor = OuterHAC->GetAttachChildren(); - - // Undo delete input objects action - for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) - { - UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; - if (!InputObject || InputObject->IsPendingKill()) - continue; - - UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); - - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) - continue; - - UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - // If the last change deleted this curve input, recreate this Houdini Spline input. - if (!SplineComponent->GetAttachParent()) - { - bUndoDelete = true; - - if (!bUndoDeletedObjArrayEmptied) - LastUndoDeletedInputs.Empty(); - - bUndoDeletedObjArrayEmptied = true; - - UHoudiniSplineComponent * ReconstructedSpline = NewObject( - GetOuter(), UHoudiniSplineComponent::StaticClass()); - - if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) - continue; - - ReconstructedSpline->SetFlags(RF_Transactional); - ReconstructedSpline->CopyHoudiniData(SplineComponent); - - UHoudiniInputObject * ReconstructedInputObject = UHoudiniInputHoudiniSplineComponent::Create( - ReconstructedSpline, GetOuter(), ReconstructedSpline->GetHoudiniSplineName()); - UHoudiniInputHoudiniSplineComponent *ReconstructedHoudiniSplineInput = (UHoudiniInputHoudiniSplineComponent*)ReconstructedInputObject; - (*InputObjectsPtr)[Index] = ReconstructedHoudiniSplineInput; - - ReconstructedSpline->RegisterComponent(); - ReconstructedSpline->SetFlags(RF_Transactional); - - CreateHoudiniSplineInput(ReconstructedHoudiniSplineInput, true, true, bBlueprintStructureChanged); - - // Cast the reconstructed Houdini Spline Input to a generic HoudiniInput object. - UHoudiniInputObject * ReconstructedHoudiniInput = Cast(ReconstructedHoudiniSplineInput); - - LastUndoDeletedInputs.Add(ReconstructedHoudiniInput); - // Reset the LastInsertedInputsArray for redoing this undo action. - } - } - - if (bUndoDelete) - return; - - // Undo insert input objects action - for (int Index = 0; Index < LastInsertedInputs.Num(); ++Index) - { - bUndoInsert = true; - UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - HoudiniSplineComponent->DestroyComponent(); - } - - if (bUndoInsert) - return; - - for (int Index = 0; Index < LastUndoDeletedInputs.Num(); ++Index) - { - UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; - - UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) - continue; - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - - HoudiniSplineComponent->DestroyComponent(); - } - } - } - - if (bBlueprintStructureChanged) - { - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); - } - -} -#endif - - -FBox -UHoudiniInput::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - - switch (Type) - { - case EHoudiniInputType::Curve: - { - for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) - { - const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); - if (!CurInCurve || CurInCurve->IsPendingKill()) - continue; - - UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); - if (!CurCurve || CurCurve->IsPendingKill()) - continue; - - FBox CurCurveBound(ForceInitToZero); - for (auto & Trans : CurCurve->CurvePoints) - { - CurCurveBound += Trans.GetLocation(); - } - - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - - if (OuterHAC && !OuterHAC->IsPendingKill()) - BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); - } - } - break; - - case EHoudiniInputType::Asset: - { - for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) - { - UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); - if (!CurInAsset || CurInAsset->IsPendingKill()) - continue; - - UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) - continue; - - BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); - } - } - break; - - case EHoudiniInputType::World: - { - for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) - { - UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); - if (CurInActor && !CurInActor->IsPendingKill()) - { - AActor* Actor = CurInActor->GetActor(); - if (!Actor || Actor->IsPendingKill()) - continue; - - FVector Origin, Extent; - Actor->GetActorBounds(false, Origin, Extent); - - BoxBounds += FBox::BuildAABB(Origin, Extent); - } - else - { - // World Input now also support HoudiniAssets - UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); - if (CurInAsset && !CurInAsset->IsPendingKill()) - { - UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) - continue; - - BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); - continue; - } - } - } - } - break; - - case EHoudiniInputType::Landscape: - { - for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) - { - UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); - if (!CurInLandscape || CurInLandscape->IsPendingKill()) - continue; - - ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); - if (!CurLandscape || CurLandscape->IsPendingKill()) - continue; - - FVector Origin, Extent; - CurLandscape->GetActorBounds(false, Origin, Extent); - - BoxBounds += FBox::BuildAABB(Origin, Extent); - } - } - break; - - case EHoudiniInputType::Skeletal: - case EHoudiniInputType::Invalid: - default: - break; - } - - return BoxBounds; -} - -FString -UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) -{ - FString InputTypeStr; - switch (InInputType) - { - case EHoudiniInputType::Geometry: - { - InputTypeStr = TEXT("Geometry Input"); - } - break; - - case EHoudiniInputType::Asset: - { - InputTypeStr = TEXT("Asset Input"); - } - break; - - case EHoudiniInputType::Curve: - { - InputTypeStr = TEXT("Curve Input"); - } - break; - - case EHoudiniInputType::Landscape: - { - InputTypeStr = TEXT("Landscape Input"); - } - break; - - case EHoudiniInputType::World: - { - InputTypeStr = TEXT("World Outliner Input"); - } - break; - - case EHoudiniInputType::Skeletal: - { - InputTypeStr = TEXT("Skeletal Mesh Input"); - } - break; - } - - return InputTypeStr; -} - - -EHoudiniInputType -UHoudiniInput::StringToInputType(const FString& InInputTypeString) -{ - if (InInputTypeString.StartsWith(TEXT("Geometry"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Geometry; - } - else if (InInputTypeString.StartsWith(TEXT("Asset"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Asset; - } - else if (InInputTypeString.StartsWith(TEXT("Curve"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Curve; - } - else if (InInputTypeString.StartsWith(TEXT("Landscape"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Landscape; - } - else if (InInputTypeString.StartsWith(TEXT("World"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::World; - } - else if (InInputTypeString.StartsWith(TEXT("Skeletal"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Skeletal; - } - - return EHoudiniInputType::Invalid; -} - - -EHoudiniCurveType UHoudiniInput::StringToHoudiniCurveType(const FString& HoudiniCurveTypeString) -{ - if (HoudiniCurveTypeString.StartsWith(TEXT("Polygon"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Polygon; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Nurbs"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Nurbs; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Bezier"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Bezier; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Points"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Points; - } - - return EHoudiniCurveType::Invalid; -} - -EHoudiniCurveMethod UHoudiniInput::StringToHoudiniCurveMethod(const FString& HoudiniCurveMethodString) -{ - if (HoudiniCurveMethodString.StartsWith(TEXT("CVs"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::CVs; - } - else if (HoudiniCurveMethodString.StartsWith(TEXT("Breakpoints"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::Breakpoints; - } - - else if (HoudiniCurveMethodString.StartsWith(TEXT("Freehand"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::Freehand; - } - - return EHoudiniCurveMethod::Invalid; - -} - -// -void -UHoudiniInput::SetSOPInput(const int32& InInputIndex) -{ - // Set the input index - InputIndex = InInputIndex; - - // Invalidate objpath parameter - ParmId = -1; - bIsObjectPathParameter = false; -} - -void -UHoudiniInput::SetObjectPathParameter(const int32& InParmId) -{ - // Set as objpath parameter - ParmId = InParmId; - bIsObjectPathParameter = true; - - // Invalidate the geo input - InputIndex = -1; -} - -EHoudiniXformType -UHoudiniInput::GetDefaultXTransformType() -{ - switch (Type) - { - case EHoudiniInputType::Curve: - case EHoudiniInputType::Geometry: - case EHoudiniInputType::Skeletal: - return EHoudiniXformType::None; - case EHoudiniInputType::Asset: - case EHoudiniInputType::Landscape: - case EHoudiniInputType::World: - return EHoudiniXformType::IntoThisObject; - } - - return EHoudiniXformType::Auto; -} - -bool -UHoudiniInput::GetKeepWorldTransform() const -{ - bool bReturn = false; - switch (KeepWorldTransform) - { - case EHoudiniXformType::Auto: - { - // Return default values corresponding to the input type: - if (Type == EHoudiniInputType::Curve - || Type == EHoudiniInputType::Geometry - || Type == EHoudiniInputType::Skeletal ) - { - // NONE for Geo, Curve and skeletal mesh IN - bReturn = false; - } - else - { - // INTO THIS OBJECT for Asset, Landscape and World IN - bReturn = true; - } - break; - } - - case EHoudiniXformType::None: - { - bReturn = false; - break; - } - - case EHoudiniXformType::IntoThisObject: - { - bReturn = true; - break; - } - } - - return bReturn; -} - -void -UHoudiniInput::SetKeepWorldTransform(const bool& bInKeepWorldTransform) -{ - if (bInKeepWorldTransform) - { - KeepWorldTransform = EHoudiniXformType::IntoThisObject; - } - else - { - KeepWorldTransform = EHoudiniXformType::None; - } -} - -void -UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlueprintStructureModified) -{ - if (InInputType == Type) - return; - - SetPreviousInputType(Type); - - // Mark this input as changed - MarkChanged(true); - bOutBlueprintStructureModified = true; - - // Check previous input type - switch (PreviousType) - { - case EHoudiniInputType::Asset: - { - break; - } - - case EHoudiniInputType::Curve: - { - // detach the input curves from the asset component - if (GetNumberOfInputObjects() > 0) - { - for (UHoudiniInputObject * CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); - - if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); - - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - HoudiniSplineComponent->Modify(); - - const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); - - if (bIsArchetype) - { -#if WITH_EDITOR - check(HoudiniSplineComponent->IsTemplate()); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); -#endif - } - else - { - AActor* OwningActor = HoudiniSplineComponent->GetOwner(); - check(OwningActor); - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - HoudiniSplineComponent->SetVisibility(false, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(false); - HoudiniSplineComponent->SetHiddenInGame(true, true); - - // This NodeId shouldn't be invalidated like this. If a spline component - // or curve input is no longer valid, the input object should be removed from the HoudinInput - // to get cleaned up properly. - // HoudiniSplineComponent->SetNodeId(-1); - HoudiniSplineComponent->MarkChanged(true); - } - - bOutBlueprintStructureModified = true; - } - } - break; - } - - case EHoudiniInputType::Geometry: - { - break; - } - - case EHoudiniInputType::Landscape: - { - TArray* InputObjectsArray = GetHoudiniInputObjectArray(PreviousType); - - if (!InputObjectsArray) - break; - - for (int32 Idx = 0; Idx < InputObjectsArray->Num(); ++Idx) - { - UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; - - if (!InputObj || InputObj->IsPendingKill()) - continue; - - UHoudiniInputLandscape* InputLandscape = Cast(InputObj); - - if (!InputLandscape || InputLandscape->IsPendingKill()) - continue; - - // do something? - } - - break; - } - - case EHoudiniInputType::Skeletal: - { - break; - } - - case EHoudiniInputType::World: - { - break; - } - - default: - break; - } - - - Type = InInputType; - - // TODO: NOPE, not needed - // Set keep world transform to default w.r.t to new input type. - //KeepWorldTransform = GetDefaultXTransformType(); - - // Check current input type - switch (InInputType) - { - case EHoudiniInputType::World: - case EHoudiniInputType::Asset: - { - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !bImportAsReference) - { - for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) - continue; - - UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) - continue; - - CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); - } - } - } - break; - - case EHoudiniInputType::Curve: - { - if (GetNumberOfInputObjects() == 0) - { - CreateNewCurveInputObject(bOutBlueprintStructureModified); - MarkChanged(true); - } - else - { - for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(CurrentInput); - if (!IsValid(SplineInput)) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); - if (!IsValid(HoudiniSplineComponent)) - continue; - - HoudiniSplineComponent->Modify(); - - const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); - - if (bIsArchetype) - { -#if WITH_EDITOR - check(HoudiniSplineComponent->IsTemplate()); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); -#endif - } - else - { - // Attach the new Houdini spline component to it's owner - AActor* OwningActor = HoudiniSplineComponent->GetOwner(); - check(OwningActor); - USceneComponent* OuterComponent = Cast(GetOuter()); - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->MarkChanged(true); - - } - - bOutBlueprintStructureModified = true; - } - } - } - break; - - case EHoudiniInputType::Geometry: - { - - } - break; - - case EHoudiniInputType::Landscape: - { - // Need to do anything on select? - } - break; - - case EHoudiniInputType::Skeletal: - { - } - break; - - default: - { - } - break; - } -} - -UHoudiniInputObject* -UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) -{ - if (CurveInputObjects.Num() > 0) - return nullptr; - - UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); - if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) - return nullptr; - - UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - // Default Houdini spline component input should not be visible at initialization - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - - CurveInputObjects.Add(NewCurveInputObject); - SetInputObjectsNumber(EHoudiniInputType::Curve, 1); - CurveInputObjects.SetNum(1); - - return NewCurveInputObject; -} - -void -UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) -{ - MarkDataUploadNeeded(bInChanged); - - // Mark all the objects from this input has changed so they upload themselves - - TSet InputTypes; - InputTypes.Add(Type); - InputTypes.Add(EHoudiniInputType::Curve); - - TArray* NewInputObjects = GetHoudiniInputObjectArray(Type); - if (NewInputObjects) - { - for (auto CurInputObject : *NewInputObjects) - { - if (CurInputObject && !CurInputObject->IsPendingKill()) - CurInputObject->MarkChanged(bInChanged); - } - } -} - -UHoudiniInput * UHoudiniInput::DuplicateAndCopyState(UObject * DestOuter, bool bInCanDeleteHoudiniNodes) -{ - UHoudiniInput* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); - - NewInput->CopyStateFrom(this, false, bInCanDeleteHoudiniNodes); - - return NewInput; -} - -void UHoudiniInput::CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes) -{ - - // Preserve the current input objects before the copy to ensure we don't lose - // access to input objects and have them end up in the garbage. - - TMap*> PrevInputObjectsMap; - - for(EHoudiniInputType InputType : HoudiniInputTypeList) - { - PrevInputObjectsMap.Add(InputType, GetHoudiniInputObjectArray(InputType)); - } - - // TArray PrevInputObjects; - // TArray* OldToInputObjects = GetHoudiniInputObjectArray(Type); - // if (OldToInputObjects) - // PrevInputObjects = *OldToInputObjects; - - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - } - - AssetNodeId = InInput->AssetNodeId; - InputNodeId = InInput->InputNodeId; - ParmId = InInput->ParmId; - bCanDeleteHoudiniNodes = bInCanDeleteHoudiniNodes; - - //if (bInCanDeleteHoudiniNodes) - //{ - // // Delete stale data nodes before they get overwritten. - // TSet NewNodeIds(InInput->CreatedDataNodeIds); - // for (int32 NodeId : CreatedDataNodeIds) - // { - // if (!NewNodeIds.Contains(NodeId)) - // { - // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); - // } - // } - //} - - CreatedDataNodeIds = InInput->CreatedDataNodeIds; - - // Important note: At this point the new object may still share objects with InInput. - // The CopyInputs() will properly duplicate inputs where necessary. - - // Copy states of Input Objects that correspond to the current type. - - for(auto& Entry : PrevInputObjectsMap) - { - EHoudiniInputType InputType = Entry.Key; - TArray* PrevInputObjects = Entry.Value; - TArray* ToInputObjects = GetHoudiniInputObjectArray(InputType); - TArray* FromInputObjects = InInput->GetHoudiniInputObjectArray(InputType); - - if (ToInputObjects && FromInputObjects) - { - *ToInputObjects = *PrevInputObjects; - CopyInputs(*ToInputObjects, *FromInputObjects, bInCanDeleteHoudiniNodes); - } - } - -} - -void UHoudiniInput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -void UHoudiniInput::InvalidateData() -{ - // If valid, mark our input node for deletion - if (InputNodeId >= 0) - { - // .. but if we're an asset input, don't delete the node as InputNodeId - // is set to the input HDA's node ID! - if (Type != EHoudiniInputType::Asset) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - } - - InputNodeId = -1; - } - - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - if (!InputObject) - continue; - - if (InputObject->IsA()) - { - // When the input object is a HoudiniAssetComponent, - // we need to be sure that this HDA node id is not in CreatedDataNodeIds - // We dont want to delete the input HDA node! - CreatedDataNodeIds.Remove(InputObject->InputNodeId); - } - - InputObject->InvalidateData(); - } - - if (bCanDeleteHoudiniNodes) - { - auto& HoudiniEngineRuntime = FHoudiniEngineRuntime::Get(); - for(int32 NodeId : CreatedDataNodeIds) - { - HoudiniEngineRuntime.MarkNodeIdAsPendingDelete(NodeId, true); - } - } - - CreatedDataNodeIds.Empty(); -} - -void UHoudiniInput::CopyInputs(TArray& ToInputObjects, TArray& FromInputObjects, bool bInCanDeleteHoudiniNodes) -{ - TSet StaleObjects(ToInputObjects); - - const int32 NumInputs = FromInputObjects.Num(); - UObject* TargetOuter = GetOuter(); - - ToInputObjects.SetNum(NumInputs); - - - for (int i = 0; i < NumInputs; i++) - { - UHoudiniInputObject* FromObject = FromInputObjects[i]; - UHoudiniInputObject* ToObject = ToInputObjects[i]; - - if (!FromObject) - { - ToInputObjects[i] = nullptr; - continue; - } - - if (ToObject) - { - bool IsValid = true; - // Is ToInput and FromInput the same or do we have to create a input object? - IsValid = IsValid && ToObject->Matches(*FromObject); - IsValid = IsValid && ToObject->GetOuter() == TargetOuter; - - if (!IsValid) - { - ToObject = nullptr; - } - } - - if (ToObject) - { - // We have an existing (matching) object. Copy the - // state from the incoming input. - StaleObjects.Remove(ToObject); - ToObject->CopyStateFrom(FromObject, true); - } - else - { - // We need to create a new input here. - ToObject = FromObject->DuplicateAndCopyState(TargetOuter); - ToInputObjects[i] = ToObject; - } - - ToObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); - } - - - for (UHoudiniInputObject* StaleInputObject : StaleObjects) - { - if (!StaleInputObject) - continue; - if (StaleInputObject->GetOuter() == this) - { - StaleInputObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); - } - } -} - - -UHoudiniInputHoudiniSplineComponent* -UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * FromHoudiniSplineInputComponent, const bool & bAttachToparent, const bool & bAppendToInputArray, bool& bOutBlueprintStructureModified) -{ - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = nullptr; - UHoudiniSplineComponent* HoudiniSplineComponent = nullptr; - - UObject* OuterObj = GetOuter(); - USceneComponent* OuterComp = Cast(GetOuter()); - bool bOuterIsTemplate = (OuterObj && OuterObj->IsTemplate()); - - if (!FromHoudiniSplineInputComponent) - { - // NOTE: If we're inside the Blueprint editor, the outer here is going to the be HAC component template. - check(OuterObj) - - // Create a default Houdini spline input if a null pointer is passed in. - FName HoudiniSplineName = MakeUniqueObjectName(OuterComp, UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); - - // Create a Houdini Input Object. - UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( - nullptr, OuterObj, HoudiniSplineName.ToString()); - - if (!NewInputObject || NewInputObject->IsPendingKill()) - return nullptr; - - HoudiniSplineInput = Cast(NewInputObject); - if (!HoudiniSplineInput) - return nullptr; - - HoudiniSplineComponent = NewObject( - HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - HoudiniSplineInput->Update(HoudiniSplineComponent); - - HoudiniSplineComponent->SetHoudiniSplineName(HoudiniSplineName.ToString()); - HoudiniSplineComponent->SetFlags(RF_Transactional); - - // Set the default position of curve to avoid overlapping. - HoudiniSplineComponent->SetOffset(DefaultCurveOffset); - DefaultCurveOffset += 100.f; - - if (!bOuterIsTemplate) - { - HoudiniSplineComponent->RegisterComponent(); - - // Attach the new Houdini spline component to it's owner. - if (bAttachToparent) - HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); - } - - //push the new input object to the array for new type. - if (bAppendToInputArray && Type == EHoudiniInputType::Curve) - GetHoudiniInputObjectArray(Type)->Add(NewInputObject); - -#if WITH_EDITOR - if (bOuterIsTemplate) - { - UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); - if (HAB) - { - UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (Blueprint) - { - TArray Components; - Components.Add(HoudiniSplineComponent); - - USCS_Node* HABNode = HAB->FindSCSNodeForTemplateComponentInClassHierarchy(HAB); - - // NOTE: FAddComponentsToBlueprintParams was introduced in 4.26 so for the sake of - // backwards compatibility, manually determine which SCSNode was added instead of - // relying on Params.OutNodes. - FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - Params.OptionalNewRootNode = HABNode; - const TSet PreviousSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); - - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, Params); - USCS_Node* NewNode = nullptr; - const TSet CurrentSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); - const TSet AddedNodes = CurrentSCSNodes.Difference(PreviousSCSNodes); - - if (AddedNodes.Num() > 0) - { - // Record Input / SCS node mapping - USCS_Node* SCSNode = AddedNodes.Array()[0]; - HAB->AddInputObjectMapping(NewInputObject->GetInputGuid(), SCSNode->VariableGuid); - SCSNode->ComponentTemplate->SetFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject); - } - - Blueprint->Modify(); - bOutBlueprintStructureModified = true; - } - } - } -#endif - } - else - { - // Otherwise, get the Houdini spline, and Houdini spline input from the argument. - HoudiniSplineInput = FromHoudiniSplineInputComponent; - HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - // Attach the new Houdini spline component to it's owner. - HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); - } - - // Mark the created UHoudiniSplineComponent as an input, and set its InputObject. - HoudiniSplineComponent->SetIsInputCurve(true); - - // HoudiniSplineComponent->SetInputObject(HoudiniSplineInput); - - // Set Houdini Spline Component bHasChanged and bNeedsToTrigerUpdate to true. - HoudiniSplineComponent->MarkChanged(true); - - - - return HoudiniSplineInput; -} - -void -UHoudiniInput::RemoveSplineFromInputObject( - UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, - bool& bOutBlueprintStructureModified) const -{ - if (!InHoudiniSplineInputObject) - return; - - UObject* OuterObj = GetOuter(); - const bool bOuterIsTemplate = OuterObj && OuterObj->IsTemplate(); - - if (bOuterIsTemplate) - { -#if WITH_EDITOR - // Find the SCS node that corresponds to this input and remove it. - UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); - if (HAB) - { - const UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (Blueprint) - { - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - check(SCS); - FGuid SCSGuid; - if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) - { - // TODO: Move this SCS variable removal code to a reusable utility function. We're - // going to need to reuse this in a few other places too. - USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(SCSGuid); - if (SCSNode) - { - SCS->RemoveNodeAndPromoteChildren(SCSNode); - SCSNode->SetOnNameChanged(FSCSNodeNameChanged()); - bOutBlueprintStructureModified = true; - HAB->RemoveInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid); - - if (SCSNode->ComponentTemplate != nullptr) - { - const FName TemplateName = SCSNode->ComponentTemplate->GetFName(); - const FString RemovedName = SCSNode->GetVariableName().ToString() + TEXT("_REMOVED_") + FGuid::NewGuid().ToString(); - - SCSNode->ComponentTemplate->Modify(); - SCSNode->ComponentTemplate->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); - - TArray ArchetypeInstances; - auto DestroyArchetypeInstances = [&ArchetypeInstances, &RemovedName](UActorComponent* ComponentTemplate) - { - ComponentTemplate->GetArchetypeInstances(ArchetypeInstances); - for (UObject* ArchetypeInstance : ArchetypeInstances) - { - if (!ArchetypeInstance->HasAllFlags(RF_ArchetypeObject | RF_InheritableComponentTemplate)) - { - CastChecked(ArchetypeInstance)->DestroyComponent(); - ArchetypeInstance->Rename(*RemovedName, nullptr, REN_DontCreateRedirectors); - } - } - }; - - DestroyArchetypeInstances(SCSNode->ComponentTemplate); - - if (Blueprint) - { - // Children need to have their inherited component template instance of the component renamed out of the way as well - TArray ChildrenOfClass; - GetDerivedClasses(Blueprint->GeneratedClass, ChildrenOfClass); - - for (UClass* ChildClass : ChildrenOfClass) - { - UBlueprintGeneratedClass* BPChildClass = CastChecked(ChildClass); - - if (UActorComponent* Component = (UActorComponent*)FindObjectWithOuter(BPChildClass, UActorComponent::StaticClass(), TemplateName)) - { - Component->Modify(); - Component->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); - - DestroyArchetypeInstances(Component); - } - } - } - } - } - } // if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) - } // if (Blueprint) - } -#endif - } // if (bIsOuterTemplate) - else - { - UHoudiniSplineComponent* HoudiniSplineComponent = InHoudiniSplineInputObject->GetCurveComponent(); - if (HoudiniSplineComponent) - { - // detach the input curves from the asset component - //FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - //HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - // Destroy the Houdini Spline Component - //InputObjectsPtr->RemoveAt(AtIndex); - HoudiniSplineComponent->DestroyComponent(); - } - } - InHoudiniSplineInputObject->Update(nullptr); -} - - -TArray* -UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - return &GeometryInputObjects; - - case EHoudiniInputType::Curve: - return &CurveInputObjects; - - case EHoudiniInputType::Asset: - return &AssetInputObjects; - - case EHoudiniInputType::Landscape: - return &LandscapeInputObjects; - - case EHoudiniInputType::World: - return &WorldInputObjects; - - case EHoudiniInputType::Skeletal: - return &SkeletalInputObjects; - - default: - case EHoudiniInputType::Invalid: - return nullptr; - } - - return nullptr; -} - -TArray* -UHoudiniInput::GetBoundSelectorObjectArray() -{ - return &WorldInputBoundSelectorObjects; -} - -const TArray* -UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - return &GeometryInputObjects; - - case EHoudiniInputType::Curve: - return &CurveInputObjects; - - case EHoudiniInputType::Asset: - return &AssetInputObjects; - - case EHoudiniInputType::Landscape: - return &LandscapeInputObjects; - - case EHoudiniInputType::World: - return &WorldInputObjects; - - case EHoudiniInputType::Skeletal: - return &SkeletalInputObjects; - - default: - case EHoudiniInputType::Invalid: - return nullptr; - } - - return nullptr; -} - -UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) -{ - return GetHoudiniInputObjectAt(Type, AtIndex); -} - -const UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) const -{ - const TArray* InputObjectsArray = GetHoudiniInputObjectArray(Type); - if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) - return nullptr; - - return (*InputObjectsArray)[AtIndex]; -} - -UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsArray = GetHoudiniInputObjectArray(InType); - if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) - return nullptr; - - return (*InputObjectsArray)[AtIndex]; -} - -UObject* -UHoudiniInput::GetInputObjectAt(const int32& AtIndex) -{ - return GetInputObjectAt(Type, AtIndex); -} - -AActor* -UHoudiniInput::GetBoundSelectorObjectAt(const int32& AtIndex) -{ - if (!WorldInputBoundSelectorObjects.IsValidIndex(AtIndex)) - return nullptr; - - return WorldInputBoundSelectorObjects[AtIndex]; -} - -UObject* -UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); - if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) - return nullptr; - - return HoudiniInputObject->GetObject(); -} - -void -UHoudiniInput::InsertInputObjectAt(const int32& AtIndex) -{ - InsertInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - InputObjectsPtr->InsertDefaulted(AtIndex, 1); - MarkChanged(true); -} - -void -UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) -{ - DeleteInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (!InputObjectsPtr->IsValidIndex(AtIndex)) - return; - - bool bBlueprintStructureModified = false; - - if (Type == EHoudiniInputType::Asset) - { - // ... TODO operations for removing asset input type - } - else if (Type == EHoudiniInputType::Curve) - { - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast((*InputObjectsPtr)[AtIndex]); - if (HoudiniSplineInputObject) - { - RemoveSplineFromInputObject(HoudiniSplineInputObject, bBlueprintStructureModified); - } - } - else if (Type == EHoudiniInputType::Geometry) - { - // ... TODO operations for removing geometry input type - } - else if (Type == EHoudiniInputType::Landscape) - { - // ... TODO operations for removing landscape input type - } - else if (Type == EHoudiniInputType::Skeletal) - { - // ... TODO operations for removing skeletal input type - } - else if (Type == EHoudiniInputType::World) - { - // ... TODO operations for removing world input type - } - else - { - // ... invalid input type - } - - MarkChanged(true); - - UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; - if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) - { - // Mark the input object's nodes for deletion - InputObjectToDelete->InvalidateData(); - - // If the deleted object wasnt null, trigger a re upload of the input data - MarkDataUploadNeeded(true); - } - - InputObjectsPtr->RemoveAt(AtIndex); - - // Delete the merge node when all the input objects are deleted. - if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); - InputNodeId = -1; - } - -#if WITH_EDITOR - if (bBlueprintStructureModified) - { - UActorComponent* Component = Cast(GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(Component); - } -#endif -} - -void -UHoudiniInput::DuplicateInputObjectAt(const int32& AtIndex) -{ - DuplicateInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (!InputObjectsPtr->IsValidIndex(AtIndex)) - return; - - // If the duplicated object is not null, trigger a re upload of the input data - bool bTriggerUpload = (*InputObjectsPtr)[AtIndex] != nullptr; - - // TODO: Duplicate the UHoudiniInputObject!! - UHoudiniInputObject* DuplicateInput = (*InputObjectsPtr)[AtIndex]; - InputObjectsPtr->Insert(DuplicateInput, AtIndex); - - MarkChanged(true); - - if (bTriggerUpload) - MarkDataUploadNeeded(true); -} - -int32 -UHoudiniInput::GetNumberOfInputObjects() -{ - return GetNumberOfInputObjects(Type); -} - -int32 -UHoudiniInput::GetNumberOfInputObjects(const EHoudiniInputType& InType) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return 0; - - return InputObjectsPtr->Num(); -} - -int32 -UHoudiniInput::GetNumberOfInputMeshes() -{ - return GetNumberOfInputMeshes(Type); -} - -int32 -UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return 0; - - // TODO? - // If geometry input, and we only have one null object, return 0 - int32 Num = InputObjectsPtr->Num(); - - // TODO: Fix BP properly! - // Special case for SM in BP: - // we need to add extra input objects store in BlueprintStaticMeshes - // Same thing for Actor InputObjects! - for (auto InputObj : *InputObjectsPtr) - { - if (!InputObj || InputObj->IsPendingKill()) - continue; - - UHoudiniInputStaticMesh* InputSM = Cast(InputObj); - if (InputSM && !InputSM->IsPendingKill()) - { - if (InputSM->BlueprintStaticMeshes.Num() > 0) - { - Num += (InputSM->BlueprintStaticMeshes.Num() - 1); - } - } - - UHoudiniInputActor* InputActor = Cast(InputObj); - if (InputActor && !InputActor->IsPendingKill()) - { - if (InputActor->ActorComponents.Num() > 0) - { - Num += (InputActor->ActorComponents.Num() - 1); - } - } - } - - return Num; -} - - -int32 -UHoudiniInput::GetNumberOfBoundSelectorObjects() const -{ - return WorldInputBoundSelectorObjects.Num(); -} - -void -UHoudiniInput::SetInputObjectAt(const int32& AtIndex, UObject* InObject) -{ - return SetInputObjectAt(Type, AtIndex, InObject); -} - -void -UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject) -{ - // Start by making sure we have the proper number of input objects - int32 NumIntObject = GetNumberOfInputObjects(InType); - if (NumIntObject <= AtIndex) - { - // We need to resize the array - SetInputObjectsNumber(InType, AtIndex + 1); - } - - UObject* CurrentInputObject = GetInputObjectAt(InType, AtIndex); - if (CurrentInputObject == InObject) - { - // Nothing to do - return; - } - - UHoudiniInputObject* CurrentInputObjectWrapper = GetHoudiniInputObjectAt(InType, AtIndex); - if (!InObject) - { - // We want to set the input object to null - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!ensure(InputObjectsPtr != nullptr && InputObjectsPtr->IsValidIndex(AtIndex))) - return; - - if (CurrentInputObjectWrapper) - { - // TODO: Check this case - // Do not destroy the input object manually! this messes up GC - //CurrentInputObjectWrapper->ConditionalBeginDestroy(); - MarkDataUploadNeeded(true); - } - - (*InputObjectsPtr)[AtIndex] = nullptr; - return; - } - - // Get the type of the previous and new input objects - EHoudiniInputObjectType NewObjectType = InObject ? UHoudiniInputObject::GetInputObjectTypeFromObject(InObject) : EHoudiniInputObjectType::Invalid; - EHoudiniInputObjectType CurrentObjectType = CurrentInputObjectWrapper ? CurrentInputObjectWrapper->Type : EHoudiniInputObjectType::Invalid; - - // See if we can reuse the existing InputObject - if (CurrentObjectType == NewObjectType && NewObjectType != EHoudiniInputObjectType::Invalid) - { - // The InputObjectTypes match, we can just update the existing object - CurrentInputObjectWrapper->Update(InObject); - CurrentInputObjectWrapper->MarkChanged(true); - return; - } - - // Destroy the existing input object - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!ensure(InputObjectsPtr)) - return; - - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InObject, this, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - return; - - // Mark that input object as changed so we know we need to update it - NewInputObject->MarkChanged(true); - if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) - { - // TODO: - // For some input type, we may have to copy some of the previous object's property before deleting it - - // Delete the previous object - CurrentInputObjectWrapper->MarkPendingKill(); - (*InputObjectsPtr)[AtIndex] = nullptr; - } - - // Update the input object array with the newly created input object - (*InputObjectsPtr)[AtIndex] = NewInputObject; -} - -void -UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (InputObjectsPtr->Num() == InNewCount) - { - // Nothing to do - return; - } - - if (InNewCount > InputObjectsPtr->Num()) - { - // Simply add new default InputObjects - InputObjectsPtr->SetNum(InNewCount); - } - else - { - // TODO: Check this case! - // Do not destroy the input object themselves manually, - // destroy the input object's nodes and reduce the array's size - for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - if (bCanDeleteHoudiniNodes) - CurrentInputObject->InvalidateData(); - - /*/ - //FHoudiniInputTranslator::DestroyInput(Inputs[InputIdx]); - CurrentObject->ConditionalBeginDestroy(); - (*InputObjectsPtr)[InObjIdx] = nullptr; - */ - } - - // Decrease the input object array size - InputObjectsPtr->SetNum(InNewCount); - } - - // Also delete the input's merge node when all the input objects are deleted. - if (InNewCount == 0 && InputNodeId >= 0) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - InputNodeId = -1; - } -} - -void -UHoudiniInput::SetBoundSelectorObjectsNumber(const int32& InNewCount) -{ - if (WorldInputBoundSelectorObjects.Num() == InNewCount) - { - // Nothing to do - return; - } - - if (InNewCount > WorldInputBoundSelectorObjects.Num()) - { - // Simply add new default InputObjects - WorldInputBoundSelectorObjects.SetNum(InNewCount); - } - else - { - /* - // TODO: Not Needed? - // Do not destroy the input object themselves manually, - // destroy the input object's nodes and reduce the array's size - for (int32 InObjIdx = WorldInputBoundSelectorObjects.Num() - 1; InObjIdx >= InNewCount; InObjIdx--) - { - UHoudiniInputObject* CurrentInputObject = WorldInputBoundSelectorObjects[InObjIdx]; - if (!CurrentInputObject) - continue; - - CurrentInputObject->MarkInputNodesForDeletion(); - } - */ - - // Decrease the input object array size - WorldInputBoundSelectorObjects.SetNum(InNewCount); - } -} - -void -UHoudiniInput::SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor) -{ - // Start by making sure we have the proper number of objects - int32 NumIntObject = GetNumberOfBoundSelectorObjects(); - if (NumIntObject <= AtIndex) - { - // We need to resize the array - SetBoundSelectorObjectsNumber(AtIndex + 1); - } - - AActor* CurrentActor = GetBoundSelectorObjectAt(AtIndex); - if (CurrentActor == InActor) - { - // Nothing to do - return; - } - - // Update the array with the new object - WorldInputBoundSelectorObjects[AtIndex] = InActor; -} - -// Helper function indicating what classes are supported by an input type -TArray -UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) -{ - TArray AllowedClasses; - switch (InInputType) - { - case EHoudiniInputType::Geometry: - AllowedClasses.Add(UStaticMesh::StaticClass()); - AllowedClasses.Add(USkeletalMesh::StaticClass()); - AllowedClasses.Add(UBlueprint::StaticClass()); - AllowedClasses.Add(UDataTable::StaticClass()); - break; - - case EHoudiniInputType::Curve: - AllowedClasses.Add(USplineComponent::StaticClass()); - AllowedClasses.Add(UHoudiniSplineComponent::StaticClass()); - break; - - case EHoudiniInputType::Asset: - AllowedClasses.Add(UHoudiniAssetComponent::StaticClass()); - break; - - case EHoudiniInputType::Landscape: - AllowedClasses.Add(ALandscapeProxy::StaticClass()); - break; - - case EHoudiniInputType::World: - AllowedClasses.Add(AActor::StaticClass()); - break; - - case EHoudiniInputType::Skeletal: - AllowedClasses.Add(USkeletalMesh::StaticClass()); - break; - - default: - break; - } - - return AllowedClasses; -} - -// Helper function indicating if an object is supported by an input type -bool -UHoudiniInput::IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject) -{ - TArray AllowedClasses = GetAllowedClasses(InInputType); - for (auto CurClass : AllowedClasses) - { - if (InObject->IsA(CurClass)) - return true; - } - - return false; -} - -bool -UHoudiniInput::IsDataUploadNeeded() -{ - if (bDataUploadNeeded) - return true; - - return HasChanged(); -} - -// Indicates if this input has changed and should be updated -bool -UHoudiniInput::HasChanged() -{ - if (bHasChanged) - return true; - - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->HasChanged()) - return true; - } - - return false; -} - -bool -UHoudiniInput::IsTransformUploadNeeded() -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->HasTransformChanged()) - return true; - } - - return false; -} - -// Indicates if this input needs to trigger an update -bool -UHoudiniInput::NeedsToTriggerUpdate() -{ - if (bNeedsToTriggerUpdate) - return true; - - const TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->NeedsToTriggerUpdate()) - return true; - } - - return false; -} - -FString -UHoudiniInput::GetNodeBaseName() const -{ - UHoudiniAssetComponent* HAC = Cast(GetOuter()); - FString NodeBaseName = HAC ? HAC->GetDisplayName() : TEXT("HoudiniAsset"); - - // Unfortunately CreateInputNode always prefix with input_... - if (IsObjectPathParameter()) - NodeBaseName += TEXT("_") + GetName(); - else - NodeBaseName += TEXT("_input") + FString::FromInt(GetInputIndex()); - - return NodeBaseName; -} - -void -UHoudiniInput::OnTransformUIExpand(const int32& AtIndex) -{ -#if WITH_EDITORONLY_DATA - if (TransformUIExpanded.IsValidIndex(AtIndex)) - { - TransformUIExpanded[AtIndex] = !TransformUIExpanded[AtIndex]; - } - else - { - // We need to append values to the expanded array - for (int32 Index = TransformUIExpanded.Num(); Index <= AtIndex; Index++) - { - TransformUIExpanded.Add(Index == AtIndex ? true : false); - } - } -#endif -} - -bool -UHoudiniInput::IsTransformUIExpanded(const int32& AtIndex) -{ -#if WITH_EDITORONLY_DATA - return TransformUIExpanded.IsValidIndex(AtIndex) ? TransformUIExpanded[AtIndex] : false; -#else - return false; -#endif -}; - -FTransform* -UHoudiniInput::GetTransformOffset(const int32& AtIndex) -{ - UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); - if (InObject) - return &(InObject->Transform); - - return nullptr; -} - -const FTransform -UHoudiniInput::GetTransformOffset(const int32& AtIndex) const -{ - const UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); - if (InObject) - return InObject->Transform; - - return FTransform::Identity; -} - -TOptional -UHoudiniInput::GetPositionOffsetX(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().X; -} - -TOptional -UHoudiniInput::GetPositionOffsetY(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().Y; -} - -TOptional -UHoudiniInput::GetPositionOffsetZ(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().Z; -} - -TOptional -UHoudiniInput::GetRotationOffsetRoll(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Roll; -} - -TOptional -UHoudiniInput::GetRotationOffsetPitch(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Pitch; -} - -TOptional -UHoudiniInput::GetRotationOffsetYaw(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Yaw; -} - -TOptional -UHoudiniInput::GetScaleOffsetX(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().X; -} - -TOptional -UHoudiniInput::GetScaleOffsetY(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().Y; -} - -TOptional -UHoudiniInput::GetScaleOffsetZ(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().Z; -} - -bool -UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = GetTransformOffset(AtIndex); - if (!Transform) - return false; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - if (Position[XYZIndex] == Value) - return false; - Position[XYZIndex] = Value; - Transform->SetLocation(Position); - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - if (Rotator.Roll == Value) - return false; - Rotator.Roll = Value; - break; - } - - case 1: - { - if (Rotator.Pitch == Value) - return false; - Rotator.Pitch = Value; - break; - } - - case 2: - { - if (Rotator.Yaw == Value) - return false; - Rotator.Yaw = Value; - break; - } - } - Transform->SetRotation(Rotator.Quaternion()); - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - if (Scale[XYZIndex] == Value) - return false; - - Scale[XYZIndex] = Value; - Transform->SetScale3D(Scale); - } - - MarkChanged(true); - bStaticMeshChanged = true; - - return true; -} - -#if WITH_EDITOR -FText -UHoudiniInput::GetCurrentSelectionText() const -{ - FText CurrentSelectionText; - switch (Type) - { - case EHoudiniInputType::Landscape : - { - if (LandscapeInputObjects.Num() > 0) - { - UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; - - UHoudiniInputLandscape* InputLandscape = Cast(InputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) - return CurrentSelectionText; - - ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) - return CurrentSelectionText; - - CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); - } - } - break; - - case EHoudiniInputType::Asset : - { - if (AssetInputObjects.Num() > 0) - { - UHoudiniInputObject* InputObject = AssetInputObjects[0]; - - UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) - return CurrentSelectionText; - - UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!HAC || HAC->IsPendingKill()) - return CurrentSelectionText; - - UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return CurrentSelectionText; - - CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); - } - } - break; - - default: - break; - } - - return CurrentSelectionText; -} -#endif - -bool -UHoudiniInput::HasLandscapeExportTypeChanged () const -{ - if (Type != EHoudiniInputType::Landscape) - return false; - - return bLandscapeHasExportTypeChanged; -} - -void -UHoudiniInput::SetHasLandscapeExportTypeChanged(const bool InChanged) -{ - if (Type != EHoudiniInputType::Landscape) - return; - - bLandscapeHasExportTypeChanged = InChanged; -} - -bool -UHoudiniInput::GetUpdateInputLandscape() const -{ - if (Type != EHoudiniInputType::Landscape) - return false; - - return bUpdateInputLandscape; -} - -void -UHoudiniInput::SetUpdateInputLandscape(const bool bInUpdateInputLandcape) -{ - if (Type != EHoudiniInputType::Landscape) - return; - - bUpdateInputLandscape = bInUpdateInputLandcape; -} - - -bool -UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() -{ - // Dont do anything if we're not a World Input - if (Type != EHoudiniInputType::World) - return false; - - // Build an array of the current selection's bounds - TArray AllBBox; - for (auto CurrentActor : WorldInputBoundSelectorObjects) - { - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); - } - - // - // Select all actors in our bound selectors bounding boxes - // - - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - - //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); - UWorld* MyWorld = GetWorld(); - TArray NewSelectedActors; - for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) - { - AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - // Check that actor is currently not selected - if (WorldInputBoundSelectorObjects.Contains(CurrentActor)) - continue; - - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - // For BrushActors, both the actor and its brush must be valid - ABrush* BrushActor = Cast(CurrentActor); - if (BrushActor) - { - if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) - continue; - } - - FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); - for (auto InBounds : AllBBox) - { - // Check if both actor's bounds intersects - if (!ActorBounds.Intersect(InBounds)) - continue; - - NewSelectedActors.Add(CurrentActor); - break; - } - } - - return UpdateWorldSelection(NewSelectedActors); -} - -bool -UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) -{ - TArray NewSelectedActors = InNewSelection; - - // Update our current selection with the new one - // Keep actors that are still selected, remove the one that are not selected anymore - bool bHasSelectionChanged = false; - for (int32 Idx = WorldInputObjects.Num() - 1; Idx >= 0; Idx--) - { - UHoudiniInputActor* InputActor = Cast(WorldInputObjects[Idx]); - AActor* CurActor = InputActor ? InputActor->GetActor() : nullptr; - - if (CurActor && NewSelectedActors.Contains(CurActor)) - { - // The actor is still selected, remove it from the new selection - NewSelectedActors.Remove(CurActor); - } - else - { - // That actor is no longer selected, remove itr from our current selection - DeleteInputObjectAt(EHoudiniInputType::World, Idx); - bHasSelectionChanged = true; - } - } - - if (NewSelectedActors.Num() > 0) - bHasSelectionChanged = true; - - // Then add the newly selected Actors - int32 InputObjectIdx = GetNumberOfInputObjects(EHoudiniInputType::World); - int32 NewInputObjectNumber = InputObjectIdx + NewSelectedActors.Num(); - SetInputObjectsNumber(EHoudiniInputType::World, NewInputObjectNumber); - for (const auto& CurActor : NewSelectedActors) - { - // Update the input objects from the valid selected actors array - SetInputObjectAt(InputObjectIdx++, CurActor); - } - - MarkChanged(bHasSelectionChanged); - - return bHasSelectionChanged; -} - - -bool -UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Returns true if the object is one of our input object for the given type - const TArray* ObjectArray = GetHoudiniInputObjectArray(InType); - if (!ObjectArray) - return false; - - for (auto& CurrentInputObject : (*ObjectArray)) - { - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - if (CurrentInputObject->GetObject() == InObject) - return true; - } - - return false; -} - -void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const -{ - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : SkeletalInputObjects) - { - Fn(InputObject); - } -} - -TArray*> UHoudiniInput::GetAllObjectArrays() const -{ - return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; -} - -TArray*> UHoudiniInput::GetAllObjectArrays() -{ - return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; -} - -void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const -{ - TArray*> ObjectArrays = GetAllObjectArrays(); - for (const TArray* ObjectArrayPtr : ObjectArrays) - { - if (!ObjectArrayPtr) - continue; - Fn(*ObjectArrayPtr); - } -} - -void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) -{ - TArray*> ObjectArrays = GetAllObjectArrays(); - for (TArray* ObjectArrayPtr : ObjectArrays) - { - if (!ObjectArrayPtr) - continue; - Fn(*ObjectArrayPtr); - } -} - -void UHoudiniInput::GetAllHoudiniInputObjects(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddInputObject = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (InputObject) - OutObjects.Add(InputObject); - }; - ForAllHoudiniInputObjects(AddInputObject); -} - -void UHoudiniInput::ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const -{ - auto ProcessSceneComponent = [Fn](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - Fn(SceneComponentInput); - }; - ForAllHoudiniInputObjects(ProcessSceneComponent); -} - -void UHoudiniInput::GetAllHoudiniInputSceneComponents(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - OutObjects.Add(SceneComponentInput); - }; - ForAllHoudiniInputObjects(AddSceneComponent); -} - -void UHoudiniInput::GetAllHoudiniInputSplineComponents(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputHoudiniSplineComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - OutObjects.Add(SceneComponentInput); - }; - ForAllHoudiniInputObjects(AddSceneComponent); -} - - -void UHoudiniInput::RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject) -{ - if (!InInputObject) - return; - - ForAllHoudiniInputObjectArrays([InInputObject](TArray& ObjectArray) { - ObjectArray.Remove(InInputObject); - }); - - return; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInput.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutput.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniAsset.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetBlueprintComponent.h" + +#include "EngineUtils.h" +#include "Engine/Brush.h" +#include "Engine/Engine.h" +#include "Engine/DataTable.h" +#include "Model.h" +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "UObject/UObjectGlobals.h" + +#include "Components/SplineComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Landscape.h" + +#if WITH_EDITOR + +#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" + +#endif + +// +UHoudiniInput::UHoudiniInput() + : Type(EHoudiniInputType::Invalid) + , PreviousType(EHoudiniInputType::Invalid) + , AssetNodeId(-1) + , InputNodeId(-1) + , InputIndex(0) + , ParmId(-1) + , bIsObjectPathParameter(false) + , bHasChanged(false) + , bPackBeforeMerge(false) + , bExportLODs(false) + , bExportSockets(false) + , bExportColliders(false) + , bCookOnCurveChanged(true) + , bStaticMeshChanged(false) + , bInputAssetConnectedInHoudini(false) + , bSwitchedToCurve(false) + , DefaultCurveOffset(0.f) + , bIsWorldInputBoundSelector(false) + , bWorldInputBoundSelectorAutoUpdate(false) + , UnrealSplineResolution(50.0f) + , bUpdateInputLandscape(false) + , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) + , bLandscapeExportSelectionOnly(false) + , bLandscapeAutoSelectComponent(false) + , bLandscapeExportMaterials(false) + , bLandscapeExportLighting(false) + , bLandscapeExportNormalizedUVs(false) + , bLandscapeExportTileUVs(false) +{ + Name = TEXT(""); + Label = TEXT(""); + SetFlags(RF_Transactional); + + // Geometry inputs always have one null default object + GeometryInputObjects.Add(nullptr); + + KeepWorldTransform = GetDefaultXTransformType(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; +} + +void +UHoudiniInput::BeginDestroy() +{ + InvalidateData(); + + // DO NOT MANUALLY DESTROY OUR INPUT OBJECTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + + // Mark all our input objects for destruction + ForAllHoudiniInputObjectArrays([](TArray& ObjectArray) { + ObjectArray.Empty(); + }); + + Super::BeginDestroy(); +} + +#if WITH_EDITOR +void UHoudiniInput::PostEditUndo() +{ + Super::PostEditUndo(); + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!InputObjectsPtr) + return; + + MarkChanged(true); + bool bBlueprintStructureChanged = false; + + if (HasInputTypeChanged()) + { + // If the input type has changed on undo, previousType becomes new type + /* This does not work properly, see the corresponding part in FHoudiniInputDetails::AddInputTypeComboBox(...), after Transaction(... + ) + { + EHoudiniInputType NewType = PreviousType; + SetInputType(NewType); + } + */ + EHoudiniInputType Temp = Type; + Type = PreviousType; + PreviousType = EHoudiniInputType::Invalid; + + // If the undo action caused input type changing, treat it as a regular type changing + // after set up the new and prev types properly + SetInputType(Temp, bBlueprintStructureChanged); + } + else + { + if (Type == EHoudiniInputType::Asset) + { + // Mark the input asset object as changed, since only undo changing asset will get into here. + // The input array will be empty when undo adding asset (only support single asset input object in an input now) + for (auto & NextAssetInputObj : *InputObjectsPtr) + { + if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) + continue; + + NextAssetInputObj->MarkChanged(true); + } + } + + + if (Type == EHoudiniInputType::World) + { + if (WorldInputObjects.Num() == 0 && InputNodeId >= 0) + { + for (auto & NextNodeId : CreatedDataNodeIds) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NextNodeId, true); + } + + CreatedDataNodeIds.Empty(); + + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + InputNodeId = -1; + } + } + + if (Type == EHoudiniInputType::Curve) + { + if (PreviousType != EHoudiniInputType::Curve) + { + for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); + if (!SplineInput || SplineInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + USceneComponent* OuterComponent = Cast(GetOuter()); + + // Attach the new Houdini spline component to it's owner + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + HoudiniSplineComponent->MarkChanged(true); + } + return; + } + bool bUndoDelete = false; + bool bUndoInsert = false; + bool bUndoDeletedObjArrayEmptied = false; + + TArray< USceneComponent* > childActor; + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !OuterHAC->IsPendingKill()) + childActor = OuterHAC->GetAttachChildren(); + + // Undo delete input objects action + for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) + { + UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; + if (!InputObject || InputObject->IsPendingKill()) + continue; + + UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); + + if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + continue; + + UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + // If the last change deleted this curve input, recreate this Houdini Spline input. + if (!SplineComponent->GetAttachParent()) + { + bUndoDelete = true; + + if (!bUndoDeletedObjArrayEmptied) + LastUndoDeletedInputs.Empty(); + + bUndoDeletedObjArrayEmptied = true; + + UHoudiniSplineComponent * ReconstructedSpline = NewObject( + GetOuter(), UHoudiniSplineComponent::StaticClass()); + + if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) + continue; + + ReconstructedSpline->SetFlags(RF_Transactional); + ReconstructedSpline->CopyHoudiniData(SplineComponent); + + UHoudiniInputObject * ReconstructedInputObject = UHoudiniInputHoudiniSplineComponent::Create( + ReconstructedSpline, GetOuter(), ReconstructedSpline->GetHoudiniSplineName()); + UHoudiniInputHoudiniSplineComponent *ReconstructedHoudiniSplineInput = (UHoudiniInputHoudiniSplineComponent*)ReconstructedInputObject; + (*InputObjectsPtr)[Index] = ReconstructedHoudiniSplineInput; + + ReconstructedSpline->RegisterComponent(); + ReconstructedSpline->SetFlags(RF_Transactional); + + CreateHoudiniSplineInput(ReconstructedHoudiniSplineInput, true, true, bBlueprintStructureChanged); + + // Cast the reconstructed Houdini Spline Input to a generic HoudiniInput object. + UHoudiniInputObject * ReconstructedHoudiniInput = Cast(ReconstructedHoudiniSplineInput); + + LastUndoDeletedInputs.Add(ReconstructedHoudiniInput); + // Reset the LastInsertedInputsArray for redoing this undo action. + } + } + + if (bUndoDelete) + return; + + // Undo insert input objects action + for (int Index = 0; Index < LastInsertedInputs.Num(); ++Index) + { + bUndoInsert = true; + UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; + if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + HoudiniSplineComponent->DestroyComponent(); + } + + if (bUndoInsert) + return; + + for (int Index = 0; Index < LastUndoDeletedInputs.Num(); ++Index) + { + UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; + + UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); + if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); + if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) + continue; + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + + HoudiniSplineComponent->DestroyComponent(); + } + } + } + + if (bBlueprintStructureChanged) + { + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); + } + +} +#endif + + +FBox +UHoudiniInput::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + + switch (Type) + { + case EHoudiniInputType::Curve: + { + for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) + { + const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); + if (!CurInCurve || CurInCurve->IsPendingKill()) + continue; + + UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); + if (!CurCurve || CurCurve->IsPendingKill()) + continue; + + FBox CurCurveBound(ForceInitToZero); + for (auto & Trans : CurCurve->CurvePoints) + { + CurCurveBound += Trans.GetLocation(); + } + + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + + if (OuterHAC && !OuterHAC->IsPendingKill()) + BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); + } + } + break; + + case EHoudiniInputType::Asset: + { + for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) + { + UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); + if (!CurInAsset || CurInAsset->IsPendingKill()) + continue; + + UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); + if (!CurInHAC || CurInHAC->IsPendingKill()) + continue; + + BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); + } + } + break; + + case EHoudiniInputType::World: + { + for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) + { + UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); + if (CurInActor && !CurInActor->IsPendingKill()) + { + AActor* Actor = CurInActor->GetActor(); + if (!Actor || Actor->IsPendingKill()) + continue; + + FVector Origin, Extent; + Actor->GetActorBounds(false, Origin, Extent); + + BoxBounds += FBox::BuildAABB(Origin, Extent); + } + else + { + // World Input now also support HoudiniAssets + UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); + if (CurInAsset && !CurInAsset->IsPendingKill()) + { + UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); + if (!CurInHAC || CurInHAC->IsPendingKill()) + continue; + + BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); + continue; + } + } + } + } + break; + + case EHoudiniInputType::Landscape: + { + for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) + { + UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); + if (!CurInLandscape || CurInLandscape->IsPendingKill()) + continue; + + ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); + if (!CurLandscape || CurLandscape->IsPendingKill()) + continue; + + FVector Origin, Extent; + CurLandscape->GetActorBounds(false, Origin, Extent); + + BoxBounds += FBox::BuildAABB(Origin, Extent); + } + } + break; + + case EHoudiniInputType::Skeletal: + case EHoudiniInputType::Invalid: + default: + break; + } + + return BoxBounds; +} + +FString +UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) +{ + FString InputTypeStr; + switch (InInputType) + { + case EHoudiniInputType::Geometry: + { + InputTypeStr = TEXT("Geometry Input"); + } + break; + + case EHoudiniInputType::Asset: + { + InputTypeStr = TEXT("Asset Input"); + } + break; + + case EHoudiniInputType::Curve: + { + InputTypeStr = TEXT("Curve Input"); + } + break; + + case EHoudiniInputType::Landscape: + { + InputTypeStr = TEXT("Landscape Input"); + } + break; + + case EHoudiniInputType::World: + { + InputTypeStr = TEXT("World Outliner Input"); + } + break; + + case EHoudiniInputType::Skeletal: + { + InputTypeStr = TEXT("Skeletal Mesh Input"); + } + break; + } + + return InputTypeStr; +} + + +EHoudiniInputType +UHoudiniInput::StringToInputType(const FString& InInputTypeString) +{ + if (InInputTypeString.StartsWith(TEXT("Geometry"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Geometry; + } + else if (InInputTypeString.StartsWith(TEXT("Asset"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Asset; + } + else if (InInputTypeString.StartsWith(TEXT("Curve"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Curve; + } + else if (InInputTypeString.StartsWith(TEXT("Landscape"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Landscape; + } + else if (InInputTypeString.StartsWith(TEXT("World"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::World; + } + else if (InInputTypeString.StartsWith(TEXT("Skeletal"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Skeletal; + } + + return EHoudiniInputType::Invalid; +} + + +EHoudiniCurveType UHoudiniInput::StringToHoudiniCurveType(const FString& HoudiniCurveTypeString) +{ + if (HoudiniCurveTypeString.StartsWith(TEXT("Polygon"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Polygon; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Nurbs"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Nurbs; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Bezier"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Bezier; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Points"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Points; + } + + return EHoudiniCurveType::Invalid; +} + +EHoudiniCurveMethod UHoudiniInput::StringToHoudiniCurveMethod(const FString& HoudiniCurveMethodString) +{ + if (HoudiniCurveMethodString.StartsWith(TEXT("CVs"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::CVs; + } + else if (HoudiniCurveMethodString.StartsWith(TEXT("Breakpoints"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::Breakpoints; + } + + else if (HoudiniCurveMethodString.StartsWith(TEXT("Freehand"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::Freehand; + } + + return EHoudiniCurveMethod::Invalid; + +} + +// +void +UHoudiniInput::SetSOPInput(const int32& InInputIndex) +{ + // Set the input index + InputIndex = InInputIndex; + + // Invalidate objpath parameter + ParmId = -1; + bIsObjectPathParameter = false; +} + +void +UHoudiniInput::SetObjectPathParameter(const int32& InParmId) +{ + // Set as objpath parameter + ParmId = InParmId; + bIsObjectPathParameter = true; + + // Invalidate the geo input + InputIndex = -1; +} + +EHoudiniXformType +UHoudiniInput::GetDefaultXTransformType() +{ + switch (Type) + { + case EHoudiniInputType::Curve: + case EHoudiniInputType::Geometry: + case EHoudiniInputType::Skeletal: + return EHoudiniXformType::None; + case EHoudiniInputType::Asset: + case EHoudiniInputType::Landscape: + case EHoudiniInputType::World: + return EHoudiniXformType::IntoThisObject; + } + + return EHoudiniXformType::Auto; +} + +bool +UHoudiniInput::GetKeepWorldTransform() const +{ + bool bReturn = false; + switch (KeepWorldTransform) + { + case EHoudiniXformType::Auto: + { + // Return default values corresponding to the input type: + if (Type == EHoudiniInputType::Curve + || Type == EHoudiniInputType::Geometry + || Type == EHoudiniInputType::Skeletal ) + { + // NONE for Geo, Curve and skeletal mesh IN + bReturn = false; + } + else + { + // INTO THIS OBJECT for Asset, Landscape and World IN + bReturn = true; + } + break; + } + + case EHoudiniXformType::None: + { + bReturn = false; + break; + } + + case EHoudiniXformType::IntoThisObject: + { + bReturn = true; + break; + } + } + + return bReturn; +} + +void +UHoudiniInput::SetKeepWorldTransform(const bool& bInKeepWorldTransform) +{ + if (bInKeepWorldTransform) + { + KeepWorldTransform = EHoudiniXformType::IntoThisObject; + } + else + { + KeepWorldTransform = EHoudiniXformType::None; + } +} + +void +UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlueprintStructureModified) +{ + if (InInputType == Type) + return; + + SetPreviousInputType(Type); + + // Mark this input as changed + MarkChanged(true); + bOutBlueprintStructureModified = true; + + // Check previous input type + switch (PreviousType) + { + case EHoudiniInputType::Asset: + { + break; + } + + case EHoudiniInputType::Curve: + { + // detach the input curves from the asset component + if (GetNumberOfInputObjects() > 0) + { + for (UHoudiniInputObject * CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); + + if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); + + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + HoudiniSplineComponent->Modify(); + + const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); + + if (bIsArchetype) + { +#if WITH_EDITOR + check(HoudiniSplineComponent->IsTemplate()); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); +#endif + } + else + { + AActor* OwningActor = HoudiniSplineComponent->GetOwner(); + check(OwningActor); + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + HoudiniSplineComponent->SetVisibility(false, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(false); + HoudiniSplineComponent->SetHiddenInGame(true, true); + + // This NodeId shouldn't be invalidated like this. If a spline component + // or curve input is no longer valid, the input object should be removed from the HoudinInput + // to get cleaned up properly. + // HoudiniSplineComponent->SetNodeId(-1); + HoudiniSplineComponent->MarkChanged(true); + } + + bOutBlueprintStructureModified = true; + } + } + break; + } + + case EHoudiniInputType::Geometry: + { + break; + } + + case EHoudiniInputType::Landscape: + { + TArray* InputObjectsArray = GetHoudiniInputObjectArray(PreviousType); + + if (!InputObjectsArray) + break; + + for (int32 Idx = 0; Idx < InputObjectsArray->Num(); ++Idx) + { + UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; + + if (!InputObj || InputObj->IsPendingKill()) + continue; + + UHoudiniInputLandscape* InputLandscape = Cast(InputObj); + + if (!InputLandscape || InputLandscape->IsPendingKill()) + continue; + + // do something? + } + + break; + } + + case EHoudiniInputType::Skeletal: + { + break; + } + + case EHoudiniInputType::World: + { + break; + } + + default: + break; + } + + + Type = InInputType; + + // TODO: NOPE, not needed + // Set keep world transform to default w.r.t to new input type. + //KeepWorldTransform = GetDefaultXTransformType(); + + // Check current input type + switch (InInputType) + { + case EHoudiniInputType::World: + case EHoudiniInputType::Asset: + { + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !bImportAsReference) + { + for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); + if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + continue; + + UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); + if (!CurrentHAC || CurrentHAC->IsPendingKill()) + continue; + + CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); + } + } + } + break; + + case EHoudiniInputType::Curve: + { + if (GetNumberOfInputObjects() == 0) + { + CreateNewCurveInputObject(bOutBlueprintStructureModified); + MarkChanged(true); + } + else + { + for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(CurrentInput); + if (!IsValid(SplineInput)) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); + if (!IsValid(HoudiniSplineComponent)) + continue; + + HoudiniSplineComponent->Modify(); + + const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); + + if (bIsArchetype) + { +#if WITH_EDITOR + check(HoudiniSplineComponent->IsTemplate()); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); +#endif + } + else + { + // Attach the new Houdini spline component to it's owner + AActor* OwningActor = HoudiniSplineComponent->GetOwner(); + check(OwningActor); + USceneComponent* OuterComponent = Cast(GetOuter()); + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->MarkChanged(true); + + } + + bOutBlueprintStructureModified = true; + } + } + } + break; + + case EHoudiniInputType::Geometry: + { + + } + break; + + case EHoudiniInputType::Landscape: + { + // Need to do anything on select? + } + break; + + case EHoudiniInputType::Skeletal: + { + } + break; + + default: + { + } + break; + } +} + +UHoudiniInputObject* +UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) +{ + if (CurveInputObjects.Num() > 0) + return nullptr; + + UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); + if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) + return nullptr; + + UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + // Default Houdini spline component input should not be visible at initialization + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + + CurveInputObjects.Add(NewCurveInputObject); + SetInputObjectsNumber(EHoudiniInputType::Curve, 1); + CurveInputObjects.SetNum(1); + + return NewCurveInputObject; +} + +void +UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) +{ + MarkDataUploadNeeded(bInChanged); + + // Mark all the objects from this input has changed so they upload themselves + + TSet InputTypes; + InputTypes.Add(Type); + InputTypes.Add(EHoudiniInputType::Curve); + + TArray* NewInputObjects = GetHoudiniInputObjectArray(Type); + if (NewInputObjects) + { + for (auto CurInputObject : *NewInputObjects) + { + if (CurInputObject && !CurInputObject->IsPendingKill()) + CurInputObject->MarkChanged(bInChanged); + } + } +} + +UHoudiniInput * UHoudiniInput::DuplicateAndCopyState(UObject * DestOuter, bool bInCanDeleteHoudiniNodes) +{ + UHoudiniInput* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); + + NewInput->CopyStateFrom(this, false, bInCanDeleteHoudiniNodes); + + return NewInput; +} + +void UHoudiniInput::CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes) +{ + + // Preserve the current input objects before the copy to ensure we don't lose + // access to input objects and have them end up in the garbage. + + TMap*> PrevInputObjectsMap; + + for(EHoudiniInputType InputType : HoudiniInputTypeList) + { + PrevInputObjectsMap.Add(InputType, GetHoudiniInputObjectArray(InputType)); + } + + // TArray PrevInputObjects; + // TArray* OldToInputObjects = GetHoudiniInputObjectArray(Type); + // if (OldToInputObjects) + // PrevInputObjects = *OldToInputObjects; + + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + } + + AssetNodeId = InInput->AssetNodeId; + InputNodeId = InInput->InputNodeId; + ParmId = InInput->ParmId; + bCanDeleteHoudiniNodes = bInCanDeleteHoudiniNodes; + + //if (bInCanDeleteHoudiniNodes) + //{ + // // Delete stale data nodes before they get overwritten. + // TSet NewNodeIds(InInput->CreatedDataNodeIds); + // for (int32 NodeId : CreatedDataNodeIds) + // { + // if (!NewNodeIds.Contains(NodeId)) + // { + // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); + // } + // } + //} + + CreatedDataNodeIds = InInput->CreatedDataNodeIds; + + // Important note: At this point the new object may still share objects with InInput. + // The CopyInputs() will properly duplicate inputs where necessary. + + // Copy states of Input Objects that correspond to the current type. + + for(auto& Entry : PrevInputObjectsMap) + { + EHoudiniInputType InputType = Entry.Key; + TArray* PrevInputObjects = Entry.Value; + TArray* ToInputObjects = GetHoudiniInputObjectArray(InputType); + TArray* FromInputObjects = InInput->GetHoudiniInputObjectArray(InputType); + + if (ToInputObjects && FromInputObjects) + { + *ToInputObjects = *PrevInputObjects; + CopyInputs(*ToInputObjects, *FromInputObjects, bInCanDeleteHoudiniNodes); + } + } + +} + +void UHoudiniInput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +void UHoudiniInput::InvalidateData() +{ + // If valid, mark our input node for deletion + if (InputNodeId >= 0) + { + // .. but if we're an asset input, don't delete the node as InputNodeId + // is set to the input HDA's node ID! + if (Type != EHoudiniInputType::Asset) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + } + + InputNodeId = -1; + } + + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + if (!InputObject) + continue; + + if (InputObject->IsA()) + { + // When the input object is a HoudiniAssetComponent, + // we need to be sure that this HDA node id is not in CreatedDataNodeIds + // We dont want to delete the input HDA node! + CreatedDataNodeIds.Remove(InputObject->InputNodeId); + } + + InputObject->InvalidateData(); + } + + if (bCanDeleteHoudiniNodes) + { + auto& HoudiniEngineRuntime = FHoudiniEngineRuntime::Get(); + for(int32 NodeId : CreatedDataNodeIds) + { + HoudiniEngineRuntime.MarkNodeIdAsPendingDelete(NodeId, true); + } + } + + CreatedDataNodeIds.Empty(); +} + +void UHoudiniInput::CopyInputs(TArray& ToInputObjects, TArray& FromInputObjects, bool bInCanDeleteHoudiniNodes) +{ + TSet StaleObjects(ToInputObjects); + + const int32 NumInputs = FromInputObjects.Num(); + UObject* TargetOuter = GetOuter(); + + ToInputObjects.SetNum(NumInputs); + + + for (int i = 0; i < NumInputs; i++) + { + UHoudiniInputObject* FromObject = FromInputObjects[i]; + UHoudiniInputObject* ToObject = ToInputObjects[i]; + + if (!FromObject) + { + ToInputObjects[i] = nullptr; + continue; + } + + if (ToObject) + { + bool IsValid = true; + // Is ToInput and FromInput the same or do we have to create a input object? + IsValid = IsValid && ToObject->Matches(*FromObject); + IsValid = IsValid && ToObject->GetOuter() == TargetOuter; + + if (!IsValid) + { + ToObject = nullptr; + } + } + + if (ToObject) + { + // We have an existing (matching) object. Copy the + // state from the incoming input. + StaleObjects.Remove(ToObject); + ToObject->CopyStateFrom(FromObject, true); + } + else + { + // We need to create a new input here. + ToObject = FromObject->DuplicateAndCopyState(TargetOuter); + ToInputObjects[i] = ToObject; + } + + ToObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); + } + + + for (UHoudiniInputObject* StaleInputObject : StaleObjects) + { + if (!StaleInputObject) + continue; + if (StaleInputObject->GetOuter() == this) + { + StaleInputObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); + } + } +} + + +UHoudiniInputHoudiniSplineComponent* +UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * FromHoudiniSplineInputComponent, const bool & bAttachToparent, const bool & bAppendToInputArray, bool& bOutBlueprintStructureModified) +{ + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = nullptr; + UHoudiniSplineComponent* HoudiniSplineComponent = nullptr; + + UObject* OuterObj = GetOuter(); + USceneComponent* OuterComp = Cast(GetOuter()); + bool bOuterIsTemplate = (OuterObj && OuterObj->IsTemplate()); + + if (!FromHoudiniSplineInputComponent) + { + // NOTE: If we're inside the Blueprint editor, the outer here is going to the be HAC component template. + check(OuterObj) + + // Create a default Houdini spline input if a null pointer is passed in. + FName HoudiniSplineName = MakeUniqueObjectName(OuterComp, UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); + + // Create a Houdini Input Object. + UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( + nullptr, OuterObj, HoudiniSplineName.ToString()); + + if (!NewInputObject || NewInputObject->IsPendingKill()) + return nullptr; + + HoudiniSplineInput = Cast(NewInputObject); + if (!HoudiniSplineInput) + return nullptr; + + HoudiniSplineComponent = NewObject( + HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + HoudiniSplineInput->Update(HoudiniSplineComponent); + + HoudiniSplineComponent->SetHoudiniSplineName(HoudiniSplineName.ToString()); + HoudiniSplineComponent->SetFlags(RF_Transactional); + + // Set the default position of curve to avoid overlapping. + HoudiniSplineComponent->SetOffset(DefaultCurveOffset); + DefaultCurveOffset += 100.f; + + if (!bOuterIsTemplate) + { + HoudiniSplineComponent->RegisterComponent(); + + // Attach the new Houdini spline component to it's owner. + if (bAttachToparent) + HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); + } + + //push the new input object to the array for new type. + if (bAppendToInputArray && Type == EHoudiniInputType::Curve) + GetHoudiniInputObjectArray(Type)->Add(NewInputObject); + +#if WITH_EDITOR + if (bOuterIsTemplate) + { + UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); + if (HAB) + { + UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (Blueprint) + { + TArray Components; + Components.Add(HoudiniSplineComponent); + + USCS_Node* HABNode = HAB->FindSCSNodeForTemplateComponentInClassHierarchy(HAB); + + // NOTE: FAddComponentsToBlueprintParams was introduced in 4.26 so for the sake of + // backwards compatibility, manually determine which SCSNode was added instead of + // relying on Params.OutNodes. + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.OptionalNewRootNode = HABNode; + const TSet PreviousSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); + + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, Params); + USCS_Node* NewNode = nullptr; + const TSet CurrentSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); + const TSet AddedNodes = CurrentSCSNodes.Difference(PreviousSCSNodes); + + if (AddedNodes.Num() > 0) + { + // Record Input / SCS node mapping + USCS_Node* SCSNode = AddedNodes.Array()[0]; + HAB->AddInputObjectMapping(NewInputObject->GetInputGuid(), SCSNode->VariableGuid); + SCSNode->ComponentTemplate->SetFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject); + } + + Blueprint->Modify(); + bOutBlueprintStructureModified = true; + } + } + } +#endif + } + else + { + // Otherwise, get the Houdini spline, and Houdini spline input from the argument. + HoudiniSplineInput = FromHoudiniSplineInputComponent; + HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + // Attach the new Houdini spline component to it's owner. + HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); + } + + // Mark the created UHoudiniSplineComponent as an input, and set its InputObject. + HoudiniSplineComponent->SetIsInputCurve(true); + + // HoudiniSplineComponent->SetInputObject(HoudiniSplineInput); + + // Set Houdini Spline Component bHasChanged and bNeedsToTrigerUpdate to true. + HoudiniSplineComponent->MarkChanged(true); + + + + return HoudiniSplineInput; +} + +void +UHoudiniInput::RemoveSplineFromInputObject( + UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, + bool& bOutBlueprintStructureModified) const +{ + if (!InHoudiniSplineInputObject) + return; + + UObject* OuterObj = GetOuter(); + const bool bOuterIsTemplate = OuterObj && OuterObj->IsTemplate(); + + if (bOuterIsTemplate) + { +#if WITH_EDITOR + // Find the SCS node that corresponds to this input and remove it. + UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); + if (HAB) + { + const UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (Blueprint) + { + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + check(SCS); + FGuid SCSGuid; + if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) + { + // TODO: Move this SCS variable removal code to a reusable utility function. We're + // going to need to reuse this in a few other places too. + USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(SCSGuid); + if (SCSNode) + { + SCS->RemoveNodeAndPromoteChildren(SCSNode); + SCSNode->SetOnNameChanged(FSCSNodeNameChanged()); + bOutBlueprintStructureModified = true; + HAB->RemoveInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid); + + if (SCSNode->ComponentTemplate != nullptr) + { + const FName TemplateName = SCSNode->ComponentTemplate->GetFName(); + const FString RemovedName = SCSNode->GetVariableName().ToString() + TEXT("_REMOVED_") + FGuid::NewGuid().ToString(); + + SCSNode->ComponentTemplate->Modify(); + SCSNode->ComponentTemplate->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); + + TArray ArchetypeInstances; + auto DestroyArchetypeInstances = [&ArchetypeInstances, &RemovedName](UActorComponent* ComponentTemplate) + { + ComponentTemplate->GetArchetypeInstances(ArchetypeInstances); + for (UObject* ArchetypeInstance : ArchetypeInstances) + { + if (!ArchetypeInstance->HasAllFlags(RF_ArchetypeObject | RF_InheritableComponentTemplate)) + { + CastChecked(ArchetypeInstance)->DestroyComponent(); + ArchetypeInstance->Rename(*RemovedName, nullptr, REN_DontCreateRedirectors); + } + } + }; + + DestroyArchetypeInstances(SCSNode->ComponentTemplate); + + if (Blueprint) + { + // Children need to have their inherited component template instance of the component renamed out of the way as well + TArray ChildrenOfClass; + GetDerivedClasses(Blueprint->GeneratedClass, ChildrenOfClass); + + for (UClass* ChildClass : ChildrenOfClass) + { + UBlueprintGeneratedClass* BPChildClass = CastChecked(ChildClass); + + if (UActorComponent* Component = (UActorComponent*)FindObjectWithOuter(BPChildClass, UActorComponent::StaticClass(), TemplateName)) + { + Component->Modify(); + Component->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); + + DestroyArchetypeInstances(Component); + } + } + } + } + } + } // if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) + } // if (Blueprint) + } +#endif + } // if (bIsOuterTemplate) + else + { + UHoudiniSplineComponent* HoudiniSplineComponent = InHoudiniSplineInputObject->GetCurveComponent(); + if (HoudiniSplineComponent) + { + // detach the input curves from the asset component + //FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + //HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + // Destroy the Houdini Spline Component + //InputObjectsPtr->RemoveAt(AtIndex); + HoudiniSplineComponent->DestroyComponent(); + } + } + InHoudiniSplineInputObject->Update(nullptr); +} + + +TArray* +UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + return &GeometryInputObjects; + + case EHoudiniInputType::Curve: + return &CurveInputObjects; + + case EHoudiniInputType::Asset: + return &AssetInputObjects; + + case EHoudiniInputType::Landscape: + return &LandscapeInputObjects; + + case EHoudiniInputType::World: + return &WorldInputObjects; + + case EHoudiniInputType::Skeletal: + return &SkeletalInputObjects; + + default: + case EHoudiniInputType::Invalid: + return nullptr; + } + + return nullptr; +} + +TArray* +UHoudiniInput::GetBoundSelectorObjectArray() +{ + return &WorldInputBoundSelectorObjects; +} + +const TArray* +UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + return &GeometryInputObjects; + + case EHoudiniInputType::Curve: + return &CurveInputObjects; + + case EHoudiniInputType::Asset: + return &AssetInputObjects; + + case EHoudiniInputType::Landscape: + return &LandscapeInputObjects; + + case EHoudiniInputType::World: + return &WorldInputObjects; + + case EHoudiniInputType::Skeletal: + return &SkeletalInputObjects; + + default: + case EHoudiniInputType::Invalid: + return nullptr; + } + + return nullptr; +} + +UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) +{ + return GetHoudiniInputObjectAt(Type, AtIndex); +} + +const UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) const +{ + const TArray* InputObjectsArray = GetHoudiniInputObjectArray(Type); + if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) + return nullptr; + + return (*InputObjectsArray)[AtIndex]; +} + +UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsArray = GetHoudiniInputObjectArray(InType); + if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) + return nullptr; + + return (*InputObjectsArray)[AtIndex]; +} + +UObject* +UHoudiniInput::GetInputObjectAt(const int32& AtIndex) +{ + return GetInputObjectAt(Type, AtIndex); +} + +AActor* +UHoudiniInput::GetBoundSelectorObjectAt(const int32& AtIndex) +{ + if (!WorldInputBoundSelectorObjects.IsValidIndex(AtIndex)) + return nullptr; + + return WorldInputBoundSelectorObjects[AtIndex]; +} + +UObject* +UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); + if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) + return nullptr; + + return HoudiniInputObject->GetObject(); +} + +void +UHoudiniInput::InsertInputObjectAt(const int32& AtIndex) +{ + InsertInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + InputObjectsPtr->InsertDefaulted(AtIndex, 1); + MarkChanged(true); +} + +void +UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) +{ + DeleteInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (!InputObjectsPtr->IsValidIndex(AtIndex)) + return; + + bool bBlueprintStructureModified = false; + + if (Type == EHoudiniInputType::Asset) + { + // ... TODO operations for removing asset input type + } + else if (Type == EHoudiniInputType::Curve) + { + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast((*InputObjectsPtr)[AtIndex]); + if (HoudiniSplineInputObject) + { + RemoveSplineFromInputObject(HoudiniSplineInputObject, bBlueprintStructureModified); + } + } + else if (Type == EHoudiniInputType::Geometry) + { + // ... TODO operations for removing geometry input type + } + else if (Type == EHoudiniInputType::Landscape) + { + // ... TODO operations for removing landscape input type + } + else if (Type == EHoudiniInputType::Skeletal) + { + // ... TODO operations for removing skeletal input type + } + else if (Type == EHoudiniInputType::World) + { + // ... TODO operations for removing world input type + } + else + { + // ... invalid input type + } + + MarkChanged(true); + + UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; + if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) + { + // Mark the input object's nodes for deletion + InputObjectToDelete->InvalidateData(); + + // If the deleted object wasnt null, trigger a re upload of the input data + MarkDataUploadNeeded(true); + } + + InputObjectsPtr->RemoveAt(AtIndex); + + // Delete the merge node when all the input objects are deleted. + if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); + InputNodeId = -1; + } + +#if WITH_EDITOR + if (bBlueprintStructureModified) + { + UActorComponent* Component = Cast(GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(Component); + } +#endif +} + +void +UHoudiniInput::DuplicateInputObjectAt(const int32& AtIndex) +{ + DuplicateInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (!InputObjectsPtr->IsValidIndex(AtIndex)) + return; + + // If the duplicated object is not null, trigger a re upload of the input data + bool bTriggerUpload = (*InputObjectsPtr)[AtIndex] != nullptr; + + // TODO: Duplicate the UHoudiniInputObject!! + UHoudiniInputObject* DuplicateInput = (*InputObjectsPtr)[AtIndex]; + InputObjectsPtr->Insert(DuplicateInput, AtIndex); + + MarkChanged(true); + + if (bTriggerUpload) + MarkDataUploadNeeded(true); +} + +int32 +UHoudiniInput::GetNumberOfInputObjects() +{ + return GetNumberOfInputObjects(Type); +} + +int32 +UHoudiniInput::GetNumberOfInputObjects(const EHoudiniInputType& InType) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return 0; + + return InputObjectsPtr->Num(); +} + +int32 +UHoudiniInput::GetNumberOfInputMeshes() +{ + return GetNumberOfInputMeshes(Type); +} + +int32 +UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return 0; + + // TODO? + // If geometry input, and we only have one null object, return 0 + int32 Num = InputObjectsPtr->Num(); + + // TODO: Fix BP properly! + // Special case for SM in BP: + // we need to add extra input objects store in BlueprintStaticMeshes + // Same thing for Actor InputObjects! + for (auto InputObj : *InputObjectsPtr) + { + if (!InputObj || InputObj->IsPendingKill()) + continue; + + UHoudiniInputStaticMesh* InputSM = Cast(InputObj); + if (InputSM && !InputSM->IsPendingKill()) + { + if (InputSM->BlueprintStaticMeshes.Num() > 0) + { + Num += (InputSM->BlueprintStaticMeshes.Num() - 1); + } + } + + UHoudiniInputActor* InputActor = Cast(InputObj); + if (InputActor && !InputActor->IsPendingKill()) + { + if (InputActor->ActorComponents.Num() > 0) + { + Num += (InputActor->ActorComponents.Num() - 1); + } + } + } + + return Num; +} + + +int32 +UHoudiniInput::GetNumberOfBoundSelectorObjects() const +{ + return WorldInputBoundSelectorObjects.Num(); +} + +void +UHoudiniInput::SetInputObjectAt(const int32& AtIndex, UObject* InObject) +{ + return SetInputObjectAt(Type, AtIndex, InObject); +} + +void +UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject) +{ + // Start by making sure we have the proper number of input objects + int32 NumIntObject = GetNumberOfInputObjects(InType); + if (NumIntObject <= AtIndex) + { + // We need to resize the array + SetInputObjectsNumber(InType, AtIndex + 1); + } + + UObject* CurrentInputObject = GetInputObjectAt(InType, AtIndex); + if (CurrentInputObject == InObject) + { + // Nothing to do + return; + } + + UHoudiniInputObject* CurrentInputObjectWrapper = GetHoudiniInputObjectAt(InType, AtIndex); + if (!InObject) + { + // We want to set the input object to null + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!ensure(InputObjectsPtr != nullptr && InputObjectsPtr->IsValidIndex(AtIndex))) + return; + + if (CurrentInputObjectWrapper) + { + // TODO: Check this case + // Do not destroy the input object manually! this messes up GC + //CurrentInputObjectWrapper->ConditionalBeginDestroy(); + MarkDataUploadNeeded(true); + } + + (*InputObjectsPtr)[AtIndex] = nullptr; + return; + } + + // Get the type of the previous and new input objects + EHoudiniInputObjectType NewObjectType = InObject ? UHoudiniInputObject::GetInputObjectTypeFromObject(InObject) : EHoudiniInputObjectType::Invalid; + EHoudiniInputObjectType CurrentObjectType = CurrentInputObjectWrapper ? CurrentInputObjectWrapper->Type : EHoudiniInputObjectType::Invalid; + + // See if we can reuse the existing InputObject + if (CurrentObjectType == NewObjectType && NewObjectType != EHoudiniInputObjectType::Invalid) + { + // The InputObjectTypes match, we can just update the existing object + CurrentInputObjectWrapper->Update(InObject); + CurrentInputObjectWrapper->MarkChanged(true); + return; + } + + // Destroy the existing input object + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!ensure(InputObjectsPtr)) + return; + + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InObject, this, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + return; + + // Mark that input object as changed so we know we need to update it + NewInputObject->MarkChanged(true); + if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) + { + // TODO: + // For some input type, we may have to copy some of the previous object's property before deleting it + + // Delete the previous object + CurrentInputObjectWrapper->MarkPendingKill(); + (*InputObjectsPtr)[AtIndex] = nullptr; + } + + // Update the input object array with the newly created input object + (*InputObjectsPtr)[AtIndex] = NewInputObject; +} + +void +UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (InputObjectsPtr->Num() == InNewCount) + { + // Nothing to do + return; + } + + if (InNewCount > InputObjectsPtr->Num()) + { + // Simply add new default InputObjects + InputObjectsPtr->SetNum(InNewCount); + } + else + { + // TODO: Check this case! + // Do not destroy the input object themselves manually, + // destroy the input object's nodes and reduce the array's size + for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + if (bCanDeleteHoudiniNodes) + CurrentInputObject->InvalidateData(); + + /*/ + //FHoudiniInputTranslator::DestroyInput(Inputs[InputIdx]); + CurrentObject->ConditionalBeginDestroy(); + (*InputObjectsPtr)[InObjIdx] = nullptr; + */ + } + + // Decrease the input object array size + InputObjectsPtr->SetNum(InNewCount); + } + + // Also delete the input's merge node when all the input objects are deleted. + if (InNewCount == 0 && InputNodeId >= 0) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + InputNodeId = -1; + } +} + +void +UHoudiniInput::SetBoundSelectorObjectsNumber(const int32& InNewCount) +{ + if (WorldInputBoundSelectorObjects.Num() == InNewCount) + { + // Nothing to do + return; + } + + if (InNewCount > WorldInputBoundSelectorObjects.Num()) + { + // Simply add new default InputObjects + WorldInputBoundSelectorObjects.SetNum(InNewCount); + } + else + { + /* + // TODO: Not Needed? + // Do not destroy the input object themselves manually, + // destroy the input object's nodes and reduce the array's size + for (int32 InObjIdx = WorldInputBoundSelectorObjects.Num() - 1; InObjIdx >= InNewCount; InObjIdx--) + { + UHoudiniInputObject* CurrentInputObject = WorldInputBoundSelectorObjects[InObjIdx]; + if (!CurrentInputObject) + continue; + + CurrentInputObject->MarkInputNodesForDeletion(); + } + */ + + // Decrease the input object array size + WorldInputBoundSelectorObjects.SetNum(InNewCount); + } +} + +void +UHoudiniInput::SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor) +{ + // Start by making sure we have the proper number of objects + int32 NumIntObject = GetNumberOfBoundSelectorObjects(); + if (NumIntObject <= AtIndex) + { + // We need to resize the array + SetBoundSelectorObjectsNumber(AtIndex + 1); + } + + AActor* CurrentActor = GetBoundSelectorObjectAt(AtIndex); + if (CurrentActor == InActor) + { + // Nothing to do + return; + } + + // Update the array with the new object + WorldInputBoundSelectorObjects[AtIndex] = InActor; +} + +// Helper function indicating what classes are supported by an input type +TArray +UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) +{ + TArray AllowedClasses; + switch (InInputType) + { + case EHoudiniInputType::Geometry: + AllowedClasses.Add(UStaticMesh::StaticClass()); + AllowedClasses.Add(USkeletalMesh::StaticClass()); + AllowedClasses.Add(UBlueprint::StaticClass()); + AllowedClasses.Add(UDataTable::StaticClass()); + break; + + case EHoudiniInputType::Curve: + AllowedClasses.Add(USplineComponent::StaticClass()); + AllowedClasses.Add(UHoudiniSplineComponent::StaticClass()); + break; + + case EHoudiniInputType::Asset: + AllowedClasses.Add(UHoudiniAssetComponent::StaticClass()); + break; + + case EHoudiniInputType::Landscape: + AllowedClasses.Add(ALandscapeProxy::StaticClass()); + break; + + case EHoudiniInputType::World: + AllowedClasses.Add(AActor::StaticClass()); + break; + + case EHoudiniInputType::Skeletal: + AllowedClasses.Add(USkeletalMesh::StaticClass()); + break; + + default: + break; + } + + return AllowedClasses; +} + +// Helper function indicating if an object is supported by an input type +bool +UHoudiniInput::IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject) +{ + TArray AllowedClasses = GetAllowedClasses(InInputType); + for (auto CurClass : AllowedClasses) + { + if (InObject->IsA(CurClass)) + return true; + } + + return false; +} + +bool +UHoudiniInput::IsDataUploadNeeded() +{ + if (bDataUploadNeeded) + return true; + + return HasChanged(); +} + +// Indicates if this input has changed and should be updated +bool +UHoudiniInput::HasChanged() +{ + if (bHasChanged) + return true; + + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->HasChanged()) + return true; + } + + return false; +} + +bool +UHoudiniInput::IsTransformUploadNeeded() +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->HasTransformChanged()) + return true; + } + + return false; +} + +// Indicates if this input needs to trigger an update +bool +UHoudiniInput::NeedsToTriggerUpdate() +{ + if (bNeedsToTriggerUpdate) + return true; + + const TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->NeedsToTriggerUpdate()) + return true; + } + + return false; +} + +FString +UHoudiniInput::GetNodeBaseName() const +{ + UHoudiniAssetComponent* HAC = Cast(GetOuter()); + FString NodeBaseName = HAC ? HAC->GetDisplayName() : TEXT("HoudiniAsset"); + + // Unfortunately CreateInputNode always prefix with input_... + if (IsObjectPathParameter()) + NodeBaseName += TEXT("_") + GetName(); + else + NodeBaseName += TEXT("_input") + FString::FromInt(GetInputIndex()); + + return NodeBaseName; +} + +void +UHoudiniInput::OnTransformUIExpand(const int32& AtIndex) +{ +#if WITH_EDITORONLY_DATA + if (TransformUIExpanded.IsValidIndex(AtIndex)) + { + TransformUIExpanded[AtIndex] = !TransformUIExpanded[AtIndex]; + } + else + { + // We need to append values to the expanded array + for (int32 Index = TransformUIExpanded.Num(); Index <= AtIndex; Index++) + { + TransformUIExpanded.Add(Index == AtIndex ? true : false); + } + } +#endif +} + +bool +UHoudiniInput::IsTransformUIExpanded(const int32& AtIndex) +{ +#if WITH_EDITORONLY_DATA + return TransformUIExpanded.IsValidIndex(AtIndex) ? TransformUIExpanded[AtIndex] : false; +#else + return false; +#endif +}; + +FTransform* +UHoudiniInput::GetTransformOffset(const int32& AtIndex) +{ + UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); + if (InObject) + return &(InObject->Transform); + + return nullptr; +} + +const FTransform +UHoudiniInput::GetTransformOffset(const int32& AtIndex) const +{ + const UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); + if (InObject) + return InObject->Transform; + + return FTransform::Identity; +} + +TOptional +UHoudiniInput::GetPositionOffsetX(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().X; +} + +TOptional +UHoudiniInput::GetPositionOffsetY(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().Y; +} + +TOptional +UHoudiniInput::GetPositionOffsetZ(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().Z; +} + +TOptional +UHoudiniInput::GetRotationOffsetRoll(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Roll; +} + +TOptional +UHoudiniInput::GetRotationOffsetPitch(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Pitch; +} + +TOptional +UHoudiniInput::GetRotationOffsetYaw(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Yaw; +} + +TOptional +UHoudiniInput::GetScaleOffsetX(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().X; +} + +TOptional +UHoudiniInput::GetScaleOffsetY(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().Y; +} + +TOptional +UHoudiniInput::GetScaleOffsetZ(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().Z; +} + +bool +UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = GetTransformOffset(AtIndex); + if (!Transform) + return false; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + if (Position[XYZIndex] == Value) + return false; + Position[XYZIndex] = Value; + Transform->SetLocation(Position); + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + if (Rotator.Roll == Value) + return false; + Rotator.Roll = Value; + break; + } + + case 1: + { + if (Rotator.Pitch == Value) + return false; + Rotator.Pitch = Value; + break; + } + + case 2: + { + if (Rotator.Yaw == Value) + return false; + Rotator.Yaw = Value; + break; + } + } + Transform->SetRotation(Rotator.Quaternion()); + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + if (Scale[XYZIndex] == Value) + return false; + + Scale[XYZIndex] = Value; + Transform->SetScale3D(Scale); + } + + MarkChanged(true); + bStaticMeshChanged = true; + + return true; +} + +#if WITH_EDITOR +FText +UHoudiniInput::GetCurrentSelectionText() const +{ + FText CurrentSelectionText; + switch (Type) + { + case EHoudiniInputType::Landscape : + { + if (LandscapeInputObjects.Num() > 0) + { + UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; + + UHoudiniInputLandscape* InputLandscape = Cast(InputObject); + if (!InputLandscape || InputLandscape->IsPendingKill()) + return CurrentSelectionText; + + ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); + if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + return CurrentSelectionText; + + CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); + } + } + break; + + case EHoudiniInputType::Asset : + { + if (AssetInputObjects.Num() > 0) + { + UHoudiniInputObject* InputObject = AssetInputObjects[0]; + + UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); + if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + return CurrentSelectionText; + + UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); + if (!HAC || HAC->IsPendingKill()) + return CurrentSelectionText; + + UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return CurrentSelectionText; + + CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); + } + } + break; + + default: + break; + } + + return CurrentSelectionText; +} +#endif + +bool +UHoudiniInput::HasLandscapeExportTypeChanged () const +{ + if (Type != EHoudiniInputType::Landscape) + return false; + + return bLandscapeHasExportTypeChanged; +} + +void +UHoudiniInput::SetHasLandscapeExportTypeChanged(const bool InChanged) +{ + if (Type != EHoudiniInputType::Landscape) + return; + + bLandscapeHasExportTypeChanged = InChanged; +} + +bool +UHoudiniInput::GetUpdateInputLandscape() const +{ + if (Type != EHoudiniInputType::Landscape) + return false; + + return bUpdateInputLandscape; +} + +void +UHoudiniInput::SetUpdateInputLandscape(const bool bInUpdateInputLandcape) +{ + if (Type != EHoudiniInputType::Landscape) + return; + + bUpdateInputLandscape = bInUpdateInputLandcape; +} + + +bool +UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() +{ + // Dont do anything if we're not a World Input + if (Type != EHoudiniInputType::World) + return false; + + // Build an array of the current selection's bounds + TArray AllBBox; + for (auto CurrentActor : WorldInputBoundSelectorObjects) + { + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); + } + + // + // Select all actors in our bound selectors bounding boxes + // + + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + + //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); + UWorld* MyWorld = GetWorld(); + TArray NewSelectedActors; + for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) + { + AActor *CurrentActor = *ActorItr; + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + // Check that actor is currently not selected + if (WorldInputBoundSelectorObjects.Contains(CurrentActor)) + continue; + + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + // For BrushActors, both the actor and its brush must be valid + ABrush* BrushActor = Cast(CurrentActor); + if (BrushActor) + { + if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) + continue; + } + + FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); + for (auto InBounds : AllBBox) + { + // Check if both actor's bounds intersects + if (!ActorBounds.Intersect(InBounds)) + continue; + + NewSelectedActors.Add(CurrentActor); + break; + } + } + + return UpdateWorldSelection(NewSelectedActors); +} + +bool +UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) +{ + TArray NewSelectedActors = InNewSelection; + + // Update our current selection with the new one + // Keep actors that are still selected, remove the one that are not selected anymore + bool bHasSelectionChanged = false; + for (int32 Idx = WorldInputObjects.Num() - 1; Idx >= 0; Idx--) + { + UHoudiniInputActor* InputActor = Cast(WorldInputObjects[Idx]); + AActor* CurActor = InputActor ? InputActor->GetActor() : nullptr; + + if (CurActor && NewSelectedActors.Contains(CurActor)) + { + // The actor is still selected, remove it from the new selection + NewSelectedActors.Remove(CurActor); + } + else + { + // That actor is no longer selected, remove itr from our current selection + DeleteInputObjectAt(EHoudiniInputType::World, Idx); + bHasSelectionChanged = true; + } + } + + if (NewSelectedActors.Num() > 0) + bHasSelectionChanged = true; + + // Then add the newly selected Actors + int32 InputObjectIdx = GetNumberOfInputObjects(EHoudiniInputType::World); + int32 NewInputObjectNumber = InputObjectIdx + NewSelectedActors.Num(); + SetInputObjectsNumber(EHoudiniInputType::World, NewInputObjectNumber); + for (const auto& CurActor : NewSelectedActors) + { + // Update the input objects from the valid selected actors array + SetInputObjectAt(InputObjectIdx++, CurActor); + } + + MarkChanged(bHasSelectionChanged); + + return bHasSelectionChanged; +} + + +bool +UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Returns true if the object is one of our input object for the given type + const TArray* ObjectArray = GetHoudiniInputObjectArray(InType); + if (!ObjectArray) + return false; + + for (auto& CurrentInputObject : (*ObjectArray)) + { + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + if (CurrentInputObject->GetObject() == InObject) + return true; + } + + return false; +} + +void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const +{ + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : SkeletalInputObjects) + { + Fn(InputObject); + } +} + +TArray*> UHoudiniInput::GetAllObjectArrays() const +{ + return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; +} + +TArray*> UHoudiniInput::GetAllObjectArrays() +{ + return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; +} + +void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const +{ + TArray*> ObjectArrays = GetAllObjectArrays(); + for (const TArray* ObjectArrayPtr : ObjectArrays) + { + if (!ObjectArrayPtr) + continue; + Fn(*ObjectArrayPtr); + } +} + +void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) +{ + TArray*> ObjectArrays = GetAllObjectArrays(); + for (TArray* ObjectArrayPtr : ObjectArrays) + { + if (!ObjectArrayPtr) + continue; + Fn(*ObjectArrayPtr); + } +} + +void UHoudiniInput::GetAllHoudiniInputObjects(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddInputObject = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (InputObject) + OutObjects.Add(InputObject); + }; + ForAllHoudiniInputObjects(AddInputObject); +} + +void UHoudiniInput::ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const +{ + auto ProcessSceneComponent = [Fn](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + Fn(SceneComponentInput); + }; + ForAllHoudiniInputObjects(ProcessSceneComponent); +} + +void UHoudiniInput::GetAllHoudiniInputSceneComponents(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + OutObjects.Add(SceneComponentInput); + }; + ForAllHoudiniInputObjects(AddSceneComponent); +} + +void UHoudiniInput::GetAllHoudiniInputSplineComponents(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputHoudiniSplineComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + OutObjects.Add(SceneComponentInput); + }; + ForAllHoudiniInputObjects(AddSceneComponent); +} + + +void UHoudiniInput::RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject) +{ + if (!InInputObject) + return; + + ForAllHoudiniInputObjectArrays([InInputObject](TArray& ObjectArray) { + ObjectArray.Remove(InInputObject); + }); + + return; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h index 68c033317..7efc3fe39 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h @@ -1,561 +1,561 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreTypes.h" -#include "UObject/Object.h" -#include "UObject/ObjectMacros.h" -#include "Misc/Optional.h" - -#include "HoudiniInputTypes.h" -#include "HoudiniInputObject.h" - -#include "GameFramework/Actor.h" -#include "LandscapeProxy.h" - -#include "HoudiniInput.generated.h" - - - -class FReply; - -enum class EHoudiniCurveType : int8; -enum class ECheckBoxState : unsigned char; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject -{ - GENERATED_BODY() - -public: - - UHoudiniInput(); - - // Equality operator, - // We consider two inputs equals if they have the same name, objparam state, and input index/parmId - // TODO: ParmId might be an incorrect condition - bool operator==(const UHoudiniInput& other) const - { - return (bIsObjectPathParameter == other.bIsObjectPathParameter - && InputIndex == other.InputIndex - && ParmId == other.ParmId - && Name.Equals(other.Name) - && Label.Equals(other.Label)); - } - - bool Matches(const UHoudiniInput& other) const { return (*this == other); }; - - // Helper function returning a string from an InputType - static FString InputTypeToString(const EHoudiniInputType& InInputType); - - // Helper function returning an InputType from a string - static EHoudiniInputType StringToInputType(const FString& InInputTypeString); - // Helper function returning a Houdini curve type from a string - static EHoudiniCurveType StringToHoudiniCurveType(const FString& CurveTypeString); - // Helper function returning a Houdini curve method from a string - static EHoudiniCurveMethod StringToHoudiniCurveMethod(const FString& CurveMethodString); - - // Helper function indicating what classes are supported by an input type - static TArray GetAllowedClasses(const EHoudiniInputType& InInputType); - - // Helper function indicating if an object is supported by an input type - static bool IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject); - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - // Returns the NodeId of the asset / object merge we are associated with - int32 GetAssetNodeId() const { return AssetNodeId; }; - // For objpath parameter, return the associated ParamId, -1 if we're a Geo input - int32 GetParameterId() const { return bIsObjectPathParameter ? ParmId : -1; }; - // Returns the NodeId of the node plugged into this input - int32 GetInputNodeId() const { return InputNodeId; }; - - // For Geo inputs, returns the InputIndex, -1 if we're an object path parameter - int32 GetInputIndex() const { return bIsObjectPathParameter ? -1 : InputIndex; }; - // Return the array containing all the nodes created for this input's data - TArray& GetCreatedDataNodeIds() { return CreatedDataNodeIds; }; - // Returns the current input type - EHoudiniInputType GetInputType() const { return Type; }; - // Returns the previous input type - EHoudiniInputType GetPreviousInputType() const { return PreviousType; }; - // Returns the current input type as a string - FString GetInputTypeAsString() const { return InputTypeToString(Type); }; - - EHoudiniXformType GetDefaultXTransformType(); - // Returns true when this input's Transform Type is set to NONE, - // false if set to INTO_THIS_OBJECT, 2 will use the input's type default value - bool GetKeepWorldTransform() const; - // Indicates if this input has changed and should be updated - bool HasChanged(); - // Indicates if this input needs to trigger an update - bool NeedsToTriggerUpdate(); - // Indicates this input should upload its data - bool IsDataUploadNeeded(); - // Indicates this input's transform need to be uploaded - bool IsTransformUploadNeeded(); - // Indicates if this input type has been changed - bool HasInputTypeChanged() const { return PreviousType != EHoudiniInputType::Invalid ? PreviousType != Type : false; } - // - bool GetUpdateInputLandscape() const; - - void SetUpdateInputLandscape(const bool bInUpdateInputLandcape); - - FString GetName() const { return Name; }; - FString GetLabel() const { return Label; }; - FString GetHelp() const { return Help; }; - bool GetPackBeforeMerge() const { return bPackBeforeMerge; }; - bool GetImportAsReference() const { return bImportAsReference; }; - bool GetExportLODs() const { return bExportLODs; }; - bool GetExportSockets() const { return bExportSockets; }; - bool GetExportColliders() const { return bExportColliders; }; - bool IsObjectPathParameter() const { return bIsObjectPathParameter; }; - float GetUnrealSplineResolution() const { return UnrealSplineResolution; }; - - virtual bool GetCookOnCurveChange() const { return bCookOnCurveChanged; }; - - TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType); - const TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const; - TArray* GetBoundSelectorObjectArray(); - - UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex); - const UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex) const; - AActor* GetBoundSelectorObjectAt(const int32& AtIndex); - - UHoudiniInputObject* GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - UObject* GetInputObjectAt(const int32& AtIndex); - UObject* GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - int32 GetNumberOfInputObjects(); - int32 GetNumberOfInputObjects(const EHoudiniInputType& InType); - - int32 GetNumberOfInputMeshes(); - int32 GetNumberOfInputMeshes(const EHoudiniInputType& InType); - - int32 GetNumberOfBoundSelectorObjects() const; - - bool IsWorldInputBoundSelector() const { return bIsWorldInputBoundSelector; }; - bool GetWorldInputBoundSelectorAutoUpdates() const { return bWorldInputBoundSelectorAutoUpdate; }; - - FString GetNodeBaseName() const; - - bool IsTransformUIExpanded(const int32& AtIndex); - - // Return the transform offset for a given input object - FTransform* GetTransformOffset(const int32& AtIndex); - const FTransform GetTransformOffset(const int32& AtIndex) const; - - // Returns the position offset for a given input object - TOptional GetPositionOffsetX(int32 AtIndex) const; - TOptional GetPositionOffsetY(int32 AtIndex) const; - TOptional GetPositionOffsetZ(int32 AtIndex) const; - - // Returns the rotation offset for a given input object - TOptional GetRotationOffsetRoll(int32 AtIndex) const; - TOptional GetRotationOffsetPitch(int32 AtIndex) const; - TOptional GetRotationOffsetYaw(int32 AtIndex) const; - - // Returns the scale offset for a given input object - TOptional GetScaleOffsetX(int32 AtIndex) const; - TOptional GetScaleOffsetY(int32 AtIndex) const; - TOptional GetScaleOffsetZ(int32 AtIndex) const; - - // Returns true if the object is one of our input object for the given type - bool ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const; - - // Get all input object arrays - TArray*> GetAllObjectArrays() const; - TArray*> GetAllObjectArrays(); - - // Iterate over all input object arrays - void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const; - void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn); - - void ForAllHoudiniInputObjects(TFunctionRef Fn) const; - // Collect top-level HoudiniInputObjects from this UHoudiniInput. Does not traverse nested input objects. - void GetAllHoudiniInputObjects(TArray& OutObjects) const; - // Collect top-level UHoudiniInputSceneComponent from this UHoudiniInput. Does not traverse nested input objects. - void ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const; - void GetAllHoudiniInputSceneComponents(TArray& OutObjects) const; - void GetAllHoudiniInputSplineComponents(TArray& OutObjects) const; - - - // Remove all instances of this input object from all object arrays. - void RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject); - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - - void MarkChanged(const bool& bInChanged) - { - bHasChanged = bInChanged; - SetNeedsToTriggerUpdate(bInChanged); - }; - void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - void MarkDataUploadNeeded(const bool& bInDataUploadNeeded) { bDataUploadNeeded = bInDataUploadNeeded; }; - void MarkAllInputObjectsChanged(const bool& bInChanged); - - void SetSOPInput(const int32& InInputIndex); - void SetObjectPathParameter(const int32& InParmId); - void SetKeepWorldTransform(const bool& bInKeepWorldTransform); - - void SetName(const FString& InName) { Name = InName; }; - void SetLabel(const FString& InLabel) { Label = InLabel; }; - void SetHelp(const FString& InHelp) { Help = InHelp; }; - void SetAssetNodeId(const int32& InNodeId) { AssetNodeId = InNodeId; }; - void SetInputType(const EHoudiniInputType &InInputType, bool& bOutBlueprintStructureModified); - void SetPreviousInputType(const EHoudiniInputType& InType) { PreviousType = InType; }; - void SetPackBeforeMerge(const bool& bInPackBeforeMerge) { bPackBeforeMerge = bInPackBeforeMerge; }; - void SetImportAsReference(const bool& bInImportAsReference) { bImportAsReference = bInImportAsReference; }; - void SetExportLODs(const bool& bInExportLODs) { bExportLODs = bInExportLODs; }; - void SetExportSockets(const bool& bInExportSockets) { bExportSockets = bInExportSockets; }; - void SetExportColliders(const bool& bInExportColliders) { bExportColliders = bInExportColliders; }; - void SetInputNodeId(const int32& InCreatedNodeId) { InputNodeId = InCreatedNodeId; }; - void SetUnrealSplineResolution(const float& InResolution) { UnrealSplineResolution = InResolution; }; - - virtual void SetCookOnCurveChange(const bool & bInCookOnCurveChanged) { bCookOnCurveChanged = bInCookOnCurveChanged; }; - - void ResetDefaultCurveOffset() { DefaultCurveOffset = 0.f; } - - UHoudiniInputObject* CreateNewCurveInputObject(bool& bBlueprintStructureModified); - - void SetGeometryInputObjectsNumber(const int32& NewCount); - void SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount); - - void InsertInputObjectAt(const int32& AtIndex); - void InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void DeleteInputObjectAt(const int32& AtIndex); - void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void DuplicateInputObjectAt(const int32& AtIndex); - void DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void SetInputObjectAt(const int32& AtIndex, UObject* InObject); - void SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject); - - void SetBoundSelectorObjectsNumber(const int32& InNewCount); - void SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor); - void SetWorldInputBoundSelector(const bool& InIsBoundSelector) { bIsWorldInputBoundSelector = InIsBoundSelector; }; - void SetWorldInputBoundSelectorAutoUpdates(const bool& InAutoUpdate) { bWorldInputBoundSelectorAutoUpdate = InAutoUpdate; }; - - // Updates the world selection using bound selectors - // returns false if the selection hasn't changed - bool UpdateWorldSelectionFromBoundSelectors(); - // Updates the world selection - // returns false if the selection hasn't changed - bool UpdateWorldSelection(const TArray& InNewSelection); - - void OnTransformUIExpand(const int32& AtIndex); - - // Sets the input's transform offset - bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - - // Sets the input's transform scale values - void SetPositionOffsetX(float InValue, int32 AtIndex); - void SetPositionOffsetY(float InValue, int32 AtIndex); - void SetPositionOffsetZ(float InValue, int32 AtIndex); - - // Sets the input's transform rotation value - void SetRotationOffsetRoll(float InValue, int32 AtIndex); - void SetRotationOffsetPitch(float InValue, int32 AtIndex); - void SetRotationOffsetYaw(float InValue, int32 AtIndex); - - // Sets the input's transform scale values - void SetScaleOffsetX(float InValue, int32 AtIndex); - void SetScaleOffsetY(float InValue, int32 AtIndex); - void SetScaleOffsetZ(float InValue, int32 AtIndex); - - - // Duplicate this object and copy its state to the resulting object. - // This is typically used to transfer state between between template and instance components. - UHoudiniInput* DuplicateAndCopyState(UObject* DestOuter, bool bInCanDeleteHoudiniNodes); - virtual void CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes); - - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() { return bCanDeleteHoudiniNodes; } - - virtual void InvalidateData(); - -protected: - void CopyInputs(TArray& ToInputs, TArray& FromInputs, bool bInCanDeleteHoudiniNodes); - -public: - - // Create a Houdini Spline input component, with an existing Houdini Spline input Object. - // Pass in nullptr to create a default Houdini Spline - UHoudiniInputHoudiniSplineComponent* CreateHoudiniSplineInput( - UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputObject, - const bool & bAttachToParent, - const bool & bAppendToInputArray, - bool& bOutBlueprintStructureModified); - - // Given an existing spline input object, remove the associated - // Houdini Spline component from the owning actor / blueprint. - void RemoveSplineFromInputObject( - UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, - bool& bOutBlueprintStructureModified) const; - - bool HasLandscapeExportTypeChanged () const; - - void SetHasLandscapeExportTypeChanged(const bool InChanged); - -#if WITH_EDITOR - FText GetCurrentSelectionText() const; -#endif - - EHoudiniLandscapeExportType GetLandscapeExportType() const { return LandscapeExportType; }; - - void SetLandscapeExportType(const EHoudiniLandscapeExportType InType) { LandscapeExportType = InType; }; - - virtual void BeginDestroy() override; - -#if WITH_EDITOR - virtual void PostEditUndo() override; -#endif - - FBox GetBounds() const; - -protected: - - // Name of the input / Object path parameter - UPROPERTY() - FString Name; - - // Label of the SOP input or of the object path parameter - UPROPERTY() - FString Label; - - // Input type - UPROPERTY() - EHoudiniInputType Type; - - // Previous type, used to detect input type changes - UPROPERTY(Transient, DuplicateTransient) - EHoudiniInputType PreviousType; - - // NodeId of the asset / object merge we are associated with - UPROPERTY(Transient, DuplicateTransient) - int32 AssetNodeId; - - // NodeId of the created input node - // when there is multiple inputs objects, this will be the merge node. - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputNodeId; - - // SOP input index (-1 if we're an object path input) - UPROPERTY() - int32 InputIndex; - - // Parameter Id of the associated object path parameter (-1 if we're a SOP input) - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 ParmId; - - // Indicates if we're an object path parameter input - UPROPERTY() - bool bIsObjectPathParameter; - - // Array containing all the node Ids created by this input - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - TArray CreatedDataNodeIds; - - // Indicates data connected to this input should be uploaded - UPROPERTY(Transient, DuplicateTransient) - bool bHasChanged; - - // Indicates this input should trigger an HDA update/cook - UPROPERTY(Transient, DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Indicates data for this input needs to be uploaded - // If this is false but the input has changed, we may have just updated in input parameter, - // and don't need to resend all the input data - bool bDataUploadNeeded; - - // Help for this parameter/input - UPROPERTY() - FString Help; - - //------------------------------------------------------------------------------------------------------------------------- - // General Input options - - // Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value - UPROPERTY() - EHoudiniXformType KeepWorldTransform; - - // Indicates that the geometry must be packed before merging it into the input - UPROPERTY() - bool bPackBeforeMerge; - - // Indicates that all the input objects are imported to Houdini as references instead of actual geo - // (for Geo/World/Asset input types only) - UPROPERTY() - bool bImportAsReference = false; - - // Indicates that all LODs in the input should be marshalled to Houdini - UPROPERTY() - bool bExportLODs; - - // Indicates that all sockets in the input should be marshalled to Houdini - UPROPERTY() - bool bExportSockets; - - // Indicates that all colliders in the input should be marshalled to Houdini - UPROPERTY() - bool bExportColliders; - - // Indicates that if trigger cook automatically on curve Input spline modified - UPROPERTY() - bool bCookOnCurveChanged; - - //------------------------------------------------------------------------------------------------------------------------- - // Geometry objects - UPROPERTY() - TArray GeometryInputObjects; - // ?? TArray GeometryInputObjects; - - // Is set to true when static mesh used for geometry input has changed. - UPROPERTY() - bool bStaticMeshChanged; - -#if WITH_EDITORONLY_DATA - // Are the transform UI expanded ? - // Values default to false and are actually added to the array in OnTransformUIExpand() - UPROPERTY() - TArray TransformUIExpanded; -#endif - - //------------------------------------------------------------------------------------------------------------------------- - // Asset inputs - UPROPERTY() - TArray AssetInputObjects; - // ?? TArray AssetInputObjects; - - // Is set to true if the asset input is actually connected inside Houdini. - UPROPERTY() - bool bInputAssetConnectedInHoudini; - - //------------------------------------------------------------------------------------------------------------------------- - // Curve/Spline inputs - UPROPERTY() - TArray CurveInputObjects; - // ?? TArray CurveInputObjects; - - // Is set to true when choice switches to curve mode. - UPROPERTY() - bool bSwitchedToCurve; - - UPROPERTY() - float DefaultCurveOffset; - - //------------------------------------------------------------------------------------------------------------------------- - // Landscape inputs - UPROPERTY() - TArray LandscapeInputObjects; - // ?? TArray LandscapeInputObjects; - - UPROPERTY() - bool bLandscapeHasExportTypeChanged = false; - - //------------------------------------------------------------------------------------------------------------------------- - // World inputs - UPROPERTY() - TArray WorldInputObjects; - // ?? TArray WorldInputObjects; - - // Objects used for automatic bound selection - // ?? TArray WorldInputBoundSelectorObjects; - UPROPERTY() - TArray WorldInputBoundSelectorObjects; - - // Indicates that this world input is in "BoundSelector" mode - UPROPERTY() - bool bIsWorldInputBoundSelector; - - // Indicates that selected actors by the bound selectors should update automatically - UPROPERTY() - bool bWorldInputBoundSelectorAutoUpdate; - - // Resolution used when converting unreal splines to houdini curves - UPROPERTY() - float UnrealSplineResolution; - - //------------------------------------------------------------------------------------------------------------------------- - // Skeletal Inputs - UPROPERTY() - TArray SkeletalInputObjects; - // ?? TArray SkeletalInputObjects; - -public: - - // This array is to record the last insert action, for undo input insertion actions. - UPROPERTY(Transient, DuplicateTransient) - TArray LastInsertedInputs; - - // This array is to cache the action of last undo delete action, and redo that action. - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - TArray LastUndoDeletedInputs; - - - // Indicates that the landscape input's source landscape should be updated instead of creating a new component - UPROPERTY() - bool bUpdateInputLandscape; - - // Indicates if the landscape should be exported as heightfield, mesh or points - UPROPERTY() - EHoudiniLandscapeExportType LandscapeExportType = EHoudiniLandscapeExportType::Heightfield; - - // Is set to true when landscape input is set to selection only. - UPROPERTY() - bool bLandscapeExportSelectionOnly = false; - - // Is set to true when the automatic selection of landscape component is active - UPROPERTY() - bool bLandscapeAutoSelectComponent = false; - - // Is set to true when materials are to be exported. - UPROPERTY() - bool bLandscapeExportMaterials = false; - - // Is set to true when lightmap information export is desired. - UPROPERTY() - bool bLandscapeExportLighting = false; - - // Is set to true when uvs should be exported in [0,1] space. - UPROPERTY() - bool bLandscapeExportNormalizedUVs = false; - - // Is set to true when uvs should be exported for each tile separately. - UPROPERTY() - bool bLandscapeExportTileUVs = false; - - UPROPERTY() - bool bCanDeleteHoudiniNodes = true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreTypes.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" +#include "Misc/Optional.h" + +#include "HoudiniInputTypes.h" +#include "HoudiniInputObject.h" + +#include "GameFramework/Actor.h" +#include "LandscapeProxy.h" + +#include "HoudiniInput.generated.h" + + + +class FReply; + +enum class EHoudiniCurveType : int8; +enum class ECheckBoxState : unsigned char; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject +{ + GENERATED_BODY() + +public: + + UHoudiniInput(); + + // Equality operator, + // We consider two inputs equals if they have the same name, objparam state, and input index/parmId + // TODO: ParmId might be an incorrect condition + bool operator==(const UHoudiniInput& other) const + { + return (bIsObjectPathParameter == other.bIsObjectPathParameter + && InputIndex == other.InputIndex + && ParmId == other.ParmId + && Name.Equals(other.Name) + && Label.Equals(other.Label)); + } + + bool Matches(const UHoudiniInput& other) const { return (*this == other); }; + + // Helper function returning a string from an InputType + static FString InputTypeToString(const EHoudiniInputType& InInputType); + + // Helper function returning an InputType from a string + static EHoudiniInputType StringToInputType(const FString& InInputTypeString); + // Helper function returning a Houdini curve type from a string + static EHoudiniCurveType StringToHoudiniCurveType(const FString& CurveTypeString); + // Helper function returning a Houdini curve method from a string + static EHoudiniCurveMethod StringToHoudiniCurveMethod(const FString& CurveMethodString); + + // Helper function indicating what classes are supported by an input type + static TArray GetAllowedClasses(const EHoudiniInputType& InInputType); + + // Helper function indicating if an object is supported by an input type + static bool IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject); + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + // Returns the NodeId of the asset / object merge we are associated with + int32 GetAssetNodeId() const { return AssetNodeId; }; + // For objpath parameter, return the associated ParamId, -1 if we're a Geo input + int32 GetParameterId() const { return bIsObjectPathParameter ? ParmId : -1; }; + // Returns the NodeId of the node plugged into this input + int32 GetInputNodeId() const { return InputNodeId; }; + + // For Geo inputs, returns the InputIndex, -1 if we're an object path parameter + int32 GetInputIndex() const { return bIsObjectPathParameter ? -1 : InputIndex; }; + // Return the array containing all the nodes created for this input's data + TArray& GetCreatedDataNodeIds() { return CreatedDataNodeIds; }; + // Returns the current input type + EHoudiniInputType GetInputType() const { return Type; }; + // Returns the previous input type + EHoudiniInputType GetPreviousInputType() const { return PreviousType; }; + // Returns the current input type as a string + FString GetInputTypeAsString() const { return InputTypeToString(Type); }; + + EHoudiniXformType GetDefaultXTransformType(); + // Returns true when this input's Transform Type is set to NONE, + // false if set to INTO_THIS_OBJECT, 2 will use the input's type default value + bool GetKeepWorldTransform() const; + // Indicates if this input has changed and should be updated + bool HasChanged(); + // Indicates if this input needs to trigger an update + bool NeedsToTriggerUpdate(); + // Indicates this input should upload its data + bool IsDataUploadNeeded(); + // Indicates this input's transform need to be uploaded + bool IsTransformUploadNeeded(); + // Indicates if this input type has been changed + bool HasInputTypeChanged() const { return PreviousType != EHoudiniInputType::Invalid ? PreviousType != Type : false; } + // + bool GetUpdateInputLandscape() const; + + void SetUpdateInputLandscape(const bool bInUpdateInputLandcape); + + FString GetName() const { return Name; }; + FString GetLabel() const { return Label; }; + FString GetHelp() const { return Help; }; + bool GetPackBeforeMerge() const { return bPackBeforeMerge; }; + bool GetImportAsReference() const { return bImportAsReference; }; + bool GetExportLODs() const { return bExportLODs; }; + bool GetExportSockets() const { return bExportSockets; }; + bool GetExportColliders() const { return bExportColliders; }; + bool IsObjectPathParameter() const { return bIsObjectPathParameter; }; + float GetUnrealSplineResolution() const { return UnrealSplineResolution; }; + + virtual bool GetCookOnCurveChange() const { return bCookOnCurveChanged; }; + + TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType); + const TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const; + TArray* GetBoundSelectorObjectArray(); + + UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex); + const UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex) const; + AActor* GetBoundSelectorObjectAt(const int32& AtIndex); + + UHoudiniInputObject* GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + UObject* GetInputObjectAt(const int32& AtIndex); + UObject* GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + int32 GetNumberOfInputObjects(); + int32 GetNumberOfInputObjects(const EHoudiniInputType& InType); + + int32 GetNumberOfInputMeshes(); + int32 GetNumberOfInputMeshes(const EHoudiniInputType& InType); + + int32 GetNumberOfBoundSelectorObjects() const; + + bool IsWorldInputBoundSelector() const { return bIsWorldInputBoundSelector; }; + bool GetWorldInputBoundSelectorAutoUpdates() const { return bWorldInputBoundSelectorAutoUpdate; }; + + FString GetNodeBaseName() const; + + bool IsTransformUIExpanded(const int32& AtIndex); + + // Return the transform offset for a given input object + FTransform* GetTransformOffset(const int32& AtIndex); + const FTransform GetTransformOffset(const int32& AtIndex) const; + + // Returns the position offset for a given input object + TOptional GetPositionOffsetX(int32 AtIndex) const; + TOptional GetPositionOffsetY(int32 AtIndex) const; + TOptional GetPositionOffsetZ(int32 AtIndex) const; + + // Returns the rotation offset for a given input object + TOptional GetRotationOffsetRoll(int32 AtIndex) const; + TOptional GetRotationOffsetPitch(int32 AtIndex) const; + TOptional GetRotationOffsetYaw(int32 AtIndex) const; + + // Returns the scale offset for a given input object + TOptional GetScaleOffsetX(int32 AtIndex) const; + TOptional GetScaleOffsetY(int32 AtIndex) const; + TOptional GetScaleOffsetZ(int32 AtIndex) const; + + // Returns true if the object is one of our input object for the given type + bool ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const; + + // Get all input object arrays + TArray*> GetAllObjectArrays() const; + TArray*> GetAllObjectArrays(); + + // Iterate over all input object arrays + void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const; + void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn); + + void ForAllHoudiniInputObjects(TFunctionRef Fn) const; + // Collect top-level HoudiniInputObjects from this UHoudiniInput. Does not traverse nested input objects. + void GetAllHoudiniInputObjects(TArray& OutObjects) const; + // Collect top-level UHoudiniInputSceneComponent from this UHoudiniInput. Does not traverse nested input objects. + void ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const; + void GetAllHoudiniInputSceneComponents(TArray& OutObjects) const; + void GetAllHoudiniInputSplineComponents(TArray& OutObjects) const; + + + // Remove all instances of this input object from all object arrays. + void RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject); + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + + void MarkChanged(const bool& bInChanged) + { + bHasChanged = bInChanged; + SetNeedsToTriggerUpdate(bInChanged); + }; + void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + void MarkDataUploadNeeded(const bool& bInDataUploadNeeded) { bDataUploadNeeded = bInDataUploadNeeded; }; + void MarkAllInputObjectsChanged(const bool& bInChanged); + + void SetSOPInput(const int32& InInputIndex); + void SetObjectPathParameter(const int32& InParmId); + void SetKeepWorldTransform(const bool& bInKeepWorldTransform); + + void SetName(const FString& InName) { Name = InName; }; + void SetLabel(const FString& InLabel) { Label = InLabel; }; + void SetHelp(const FString& InHelp) { Help = InHelp; }; + void SetAssetNodeId(const int32& InNodeId) { AssetNodeId = InNodeId; }; + void SetInputType(const EHoudiniInputType &InInputType, bool& bOutBlueprintStructureModified); + void SetPreviousInputType(const EHoudiniInputType& InType) { PreviousType = InType; }; + void SetPackBeforeMerge(const bool& bInPackBeforeMerge) { bPackBeforeMerge = bInPackBeforeMerge; }; + void SetImportAsReference(const bool& bInImportAsReference) { bImportAsReference = bInImportAsReference; }; + void SetExportLODs(const bool& bInExportLODs) { bExportLODs = bInExportLODs; }; + void SetExportSockets(const bool& bInExportSockets) { bExportSockets = bInExportSockets; }; + void SetExportColliders(const bool& bInExportColliders) { bExportColliders = bInExportColliders; }; + void SetInputNodeId(const int32& InCreatedNodeId) { InputNodeId = InCreatedNodeId; }; + void SetUnrealSplineResolution(const float& InResolution) { UnrealSplineResolution = InResolution; }; + + virtual void SetCookOnCurveChange(const bool & bInCookOnCurveChanged) { bCookOnCurveChanged = bInCookOnCurveChanged; }; + + void ResetDefaultCurveOffset() { DefaultCurveOffset = 0.f; } + + UHoudiniInputObject* CreateNewCurveInputObject(bool& bBlueprintStructureModified); + + void SetGeometryInputObjectsNumber(const int32& NewCount); + void SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount); + + void InsertInputObjectAt(const int32& AtIndex); + void InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void DeleteInputObjectAt(const int32& AtIndex); + void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void DuplicateInputObjectAt(const int32& AtIndex); + void DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void SetInputObjectAt(const int32& AtIndex, UObject* InObject); + void SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject); + + void SetBoundSelectorObjectsNumber(const int32& InNewCount); + void SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor); + void SetWorldInputBoundSelector(const bool& InIsBoundSelector) { bIsWorldInputBoundSelector = InIsBoundSelector; }; + void SetWorldInputBoundSelectorAutoUpdates(const bool& InAutoUpdate) { bWorldInputBoundSelectorAutoUpdate = InAutoUpdate; }; + + // Updates the world selection using bound selectors + // returns false if the selection hasn't changed + bool UpdateWorldSelectionFromBoundSelectors(); + // Updates the world selection + // returns false if the selection hasn't changed + bool UpdateWorldSelection(const TArray& InNewSelection); + + void OnTransformUIExpand(const int32& AtIndex); + + // Sets the input's transform offset + bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + + // Sets the input's transform scale values + void SetPositionOffsetX(float InValue, int32 AtIndex); + void SetPositionOffsetY(float InValue, int32 AtIndex); + void SetPositionOffsetZ(float InValue, int32 AtIndex); + + // Sets the input's transform rotation value + void SetRotationOffsetRoll(float InValue, int32 AtIndex); + void SetRotationOffsetPitch(float InValue, int32 AtIndex); + void SetRotationOffsetYaw(float InValue, int32 AtIndex); + + // Sets the input's transform scale values + void SetScaleOffsetX(float InValue, int32 AtIndex); + void SetScaleOffsetY(float InValue, int32 AtIndex); + void SetScaleOffsetZ(float InValue, int32 AtIndex); + + + // Duplicate this object and copy its state to the resulting object. + // This is typically used to transfer state between between template and instance components. + UHoudiniInput* DuplicateAndCopyState(UObject* DestOuter, bool bInCanDeleteHoudiniNodes); + virtual void CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes); + + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() { return bCanDeleteHoudiniNodes; } + + virtual void InvalidateData(); + +protected: + void CopyInputs(TArray& ToInputs, TArray& FromInputs, bool bInCanDeleteHoudiniNodes); + +public: + + // Create a Houdini Spline input component, with an existing Houdini Spline input Object. + // Pass in nullptr to create a default Houdini Spline + UHoudiniInputHoudiniSplineComponent* CreateHoudiniSplineInput( + UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputObject, + const bool & bAttachToParent, + const bool & bAppendToInputArray, + bool& bOutBlueprintStructureModified); + + // Given an existing spline input object, remove the associated + // Houdini Spline component from the owning actor / blueprint. + void RemoveSplineFromInputObject( + UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, + bool& bOutBlueprintStructureModified) const; + + bool HasLandscapeExportTypeChanged () const; + + void SetHasLandscapeExportTypeChanged(const bool InChanged); + +#if WITH_EDITOR + FText GetCurrentSelectionText() const; +#endif + + EHoudiniLandscapeExportType GetLandscapeExportType() const { return LandscapeExportType; }; + + void SetLandscapeExportType(const EHoudiniLandscapeExportType InType) { LandscapeExportType = InType; }; + + virtual void BeginDestroy() override; + +#if WITH_EDITOR + virtual void PostEditUndo() override; +#endif + + FBox GetBounds() const; + +protected: + + // Name of the input / Object path parameter + UPROPERTY() + FString Name; + + // Label of the SOP input or of the object path parameter + UPROPERTY() + FString Label; + + // Input type + UPROPERTY() + EHoudiniInputType Type; + + // Previous type, used to detect input type changes + UPROPERTY(Transient, DuplicateTransient) + EHoudiniInputType PreviousType; + + // NodeId of the asset / object merge we are associated with + UPROPERTY(Transient, DuplicateTransient) + int32 AssetNodeId; + + // NodeId of the created input node + // when there is multiple inputs objects, this will be the merge node. + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputNodeId; + + // SOP input index (-1 if we're an object path input) + UPROPERTY() + int32 InputIndex; + + // Parameter Id of the associated object path parameter (-1 if we're a SOP input) + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 ParmId; + + // Indicates if we're an object path parameter input + UPROPERTY() + bool bIsObjectPathParameter; + + // Array containing all the node Ids created by this input + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + TArray CreatedDataNodeIds; + + // Indicates data connected to this input should be uploaded + UPROPERTY(Transient, DuplicateTransient) + bool bHasChanged; + + // Indicates this input should trigger an HDA update/cook + UPROPERTY(Transient, DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Indicates data for this input needs to be uploaded + // If this is false but the input has changed, we may have just updated in input parameter, + // and don't need to resend all the input data + bool bDataUploadNeeded; + + // Help for this parameter/input + UPROPERTY() + FString Help; + + //------------------------------------------------------------------------------------------------------------------------- + // General Input options + + // Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value + UPROPERTY() + EHoudiniXformType KeepWorldTransform; + + // Indicates that the geometry must be packed before merging it into the input + UPROPERTY() + bool bPackBeforeMerge; + + // Indicates that all the input objects are imported to Houdini as references instead of actual geo + // (for Geo/World/Asset input types only) + UPROPERTY() + bool bImportAsReference = false; + + // Indicates that all LODs in the input should be marshalled to Houdini + UPROPERTY() + bool bExportLODs; + + // Indicates that all sockets in the input should be marshalled to Houdini + UPROPERTY() + bool bExportSockets; + + // Indicates that all colliders in the input should be marshalled to Houdini + UPROPERTY() + bool bExportColliders; + + // Indicates that if trigger cook automatically on curve Input spline modified + UPROPERTY() + bool bCookOnCurveChanged; + + //------------------------------------------------------------------------------------------------------------------------- + // Geometry objects + UPROPERTY() + TArray GeometryInputObjects; + // ?? TArray GeometryInputObjects; + + // Is set to true when static mesh used for geometry input has changed. + UPROPERTY() + bool bStaticMeshChanged; + +#if WITH_EDITORONLY_DATA + // Are the transform UI expanded ? + // Values default to false and are actually added to the array in OnTransformUIExpand() + UPROPERTY() + TArray TransformUIExpanded; +#endif + + //------------------------------------------------------------------------------------------------------------------------- + // Asset inputs + UPROPERTY() + TArray AssetInputObjects; + // ?? TArray AssetInputObjects; + + // Is set to true if the asset input is actually connected inside Houdini. + UPROPERTY() + bool bInputAssetConnectedInHoudini; + + //------------------------------------------------------------------------------------------------------------------------- + // Curve/Spline inputs + UPROPERTY() + TArray CurveInputObjects; + // ?? TArray CurveInputObjects; + + // Is set to true when choice switches to curve mode. + UPROPERTY() + bool bSwitchedToCurve; + + UPROPERTY() + float DefaultCurveOffset; + + //------------------------------------------------------------------------------------------------------------------------- + // Landscape inputs + UPROPERTY() + TArray LandscapeInputObjects; + // ?? TArray LandscapeInputObjects; + + UPROPERTY() + bool bLandscapeHasExportTypeChanged = false; + + //------------------------------------------------------------------------------------------------------------------------- + // World inputs + UPROPERTY() + TArray WorldInputObjects; + // ?? TArray WorldInputObjects; + + // Objects used for automatic bound selection + // ?? TArray WorldInputBoundSelectorObjects; + UPROPERTY() + TArray WorldInputBoundSelectorObjects; + + // Indicates that this world input is in "BoundSelector" mode + UPROPERTY() + bool bIsWorldInputBoundSelector; + + // Indicates that selected actors by the bound selectors should update automatically + UPROPERTY() + bool bWorldInputBoundSelectorAutoUpdate; + + // Resolution used when converting unreal splines to houdini curves + UPROPERTY() + float UnrealSplineResolution; + + //------------------------------------------------------------------------------------------------------------------------- + // Skeletal Inputs + UPROPERTY() + TArray SkeletalInputObjects; + // ?? TArray SkeletalInputObjects; + +public: + + // This array is to record the last insert action, for undo input insertion actions. + UPROPERTY(Transient, DuplicateTransient) + TArray LastInsertedInputs; + + // This array is to cache the action of last undo delete action, and redo that action. + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + TArray LastUndoDeletedInputs; + + + // Indicates that the landscape input's source landscape should be updated instead of creating a new component + UPROPERTY() + bool bUpdateInputLandscape; + + // Indicates if the landscape should be exported as heightfield, mesh or points + UPROPERTY() + EHoudiniLandscapeExportType LandscapeExportType = EHoudiniLandscapeExportType::Heightfield; + + // Is set to true when landscape input is set to selection only. + UPROPERTY() + bool bLandscapeExportSelectionOnly = false; + + // Is set to true when the automatic selection of landscape component is active + UPROPERTY() + bool bLandscapeAutoSelectComponent = false; + + // Is set to true when materials are to be exported. + UPROPERTY() + bool bLandscapeExportMaterials = false; + + // Is set to true when lightmap information export is desired. + UPROPERTY() + bool bLandscapeExportLighting = false; + + // Is set to true when uvs should be exported in [0,1] space. + UPROPERTY() + bool bLandscapeExportNormalizedUVs = false; + + // Is set to true when uvs should be exported for each tile separately. + UPROPERTY() + bool bLandscapeExportTileUVs = false; + + UPROPERTY() + bool bCanDeleteHoudiniNodes = true; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp index a03c3cdae..5172fe1bf 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp @@ -1,1672 +1,1672 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInputObject.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInput.h" - -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "Engine/DataTable.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "Engine/Brush.h" -#include "Engine/Engine.h" -#include "GameFramework/Volume.h" -#include "Camera/CameraComponent.h" - -#include "Model.h" -#include "Engine/Brush.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "Kismet/KismetSystemLibrary.h" - -//----------------------------------------------------------------------------------------------------------------------------- -// Constructors -//----------------------------------------------------------------------------------------------------------------------------- - -// -UHoudiniInputObject::UHoudiniInputObject(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , Transform(FTransform::Identity) - , Type(EHoudiniInputObjectType::Invalid) - , InputNodeId(-1) - , InputObjectNodeId(-1) - , bHasChanged(false) - , bNeedsToTriggerUpdate(false) - , bTransformChanged(false) - , bImportAsReference(false) - , bCanDeleteHoudiniNodes(true) -{ - Guid = FGuid::NewGuid(); -} - -// -UHoudiniInputStaticMesh::UHoudiniInputStaticMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSkeletalMesh::UHoudiniInputSkeletalMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSceneComponent::UHoudiniInputSceneComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputMeshComponent::UHoudiniInputMeshComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputInstancedMeshComponent::UHoudiniInputInstancedMeshComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSplineComponent::UHoudiniInputSplineComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , NumberOfSplineControlPoints(-1) - , SplineLength(-1.0f) - , SplineResolution(-1.0f) - , SplineClosed(false) -{ - -} - -// -UHoudiniInputCameraComponent::UHoudiniInputCameraComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , FOV(0.0f) - , AspectRatio(1.0f) - , bIsOrthographic(false) - , OrthoWidth(2.0f) - , OrthoNearClipPlane(0.0f) - , OrthoFarClipPlane(-1.0f) -{ - -} - -// Returns true if the attached actor's (parent) transform has been modified -bool -UHoudiniInputSplineComponent::HasActorTransformChanged() const -{ - return false; -} - -// Returns true if the attached component's transform has been modified -bool -UHoudiniInputSplineComponent::HasComponentTransformChanged() const -{ - return false; -} - -// Return true if the component itself has been modified -bool -UHoudiniInputSplineComponent::HasComponentChanged() const -{ - USplineComponent* SplineComponent = Cast(InputObject.LoadSynchronous()); - - if (!SplineComponent) - return false; - - if (SplineClosed != SplineComponent->IsClosedLoop()) - return true; - - - if (SplineComponent->GetNumberOfSplinePoints() != NumberOfSplineControlPoints) - return true; - - for (int32 n = 0; n < SplineComponent->GetNumberOfSplinePoints(); ++n) - { - const FTransform &CurSplineComponentTransform = SplineComponent->GetTransformAtSplinePoint(n, ESplineCoordinateSpace::Local); - const FTransform &CurInputTransform = SplineControlPoints[n]; - - if (CurInputTransform.GetLocation() != CurSplineComponentTransform.GetLocation()) - return true; - - if (CurInputTransform.GetRotation().Rotator() != CurSplineComponentTransform.GetRotation().Rotator()) - return true; - - if (CurInputTransform.GetScale3D() != CurSplineComponentTransform.GetScale3D()) - return true; - } - - return false; -} - -bool -UHoudiniInputSplineComponent::HasSplineComponentChanged(float fCurrentSplineResolution) const -{ - return false; -} - -// -UHoudiniInputHoudiniSplineComponent::UHoudiniInputHoudiniSplineComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , CurveType(EHoudiniCurveType::Polygon) - , CurveMethod(EHoudiniCurveMethod::CVs) - , Reversed(false) -{ - -} - -// -UHoudiniInputHoudiniAsset::UHoudiniInputHoudiniAsset(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , AssetOutputIndex(-1) -{ - -} - -// -UHoudiniInputActor::UHoudiniInputActor(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputLandscape::UHoudiniInputLandscape(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputBrush::UHoudiniInputBrush() - : CombinedModel(nullptr) - , bIgnoreInputObject(false) -{ - -} - -//----------------------------------------------------------------------------------------------------------------------------- -// Accessors -//----------------------------------------------------------------------------------------------------------------------------- - -UObject* -UHoudiniInputObject::GetObject() const -{ - return InputObject.LoadSynchronous(); -} - -UStaticMesh* -UHoudiniInputStaticMesh::GetStaticMesh() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UBlueprint* -UHoudiniInputStaticMesh::GetBlueprint() -{ - return Cast(InputObject.LoadSynchronous()); -} - -bool UHoudiniInputStaticMesh::bIsBlueprint() const -{ - return (InputObject.IsValid() && InputObject.Get()->IsA()); -} - -USkeletalMesh* -UHoudiniInputSkeletalMesh::GetSkeletalMesh() -{ - return Cast(InputObject.LoadSynchronous()); -} - -USceneComponent* -UHoudiniInputSceneComponent::GetSceneComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UStaticMeshComponent* -UHoudiniInputMeshComponent::GetStaticMeshComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UStaticMesh* -UHoudiniInputMeshComponent::GetStaticMesh() -{ - return StaticMesh.Get(); -} - -UInstancedStaticMeshComponent* -UHoudiniInputInstancedMeshComponent::GetInstancedStaticMeshComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -USplineComponent* -UHoudiniInputSplineComponent::GetSplineComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UHoudiniSplineComponent* -UHoudiniInputHoudiniSplineComponent::GetCurveComponent() const -{ - return Cast(GetObject()); - //return Cast(InputObject.LoadSynchronous()); -} - -UCameraComponent* -UHoudiniInputCameraComponent::GetCameraComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UHoudiniAssetComponent* -UHoudiniInputHoudiniAsset::GetHoudiniAssetComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -AActor* -UHoudiniInputActor::GetActor() -{ - return Cast(InputObject.LoadSynchronous()); -} - -ALandscapeProxy* -UHoudiniInputLandscape::GetLandscapeProxy() -{ - return Cast(InputObject.LoadSynchronous()); -} - -void -UHoudiniInputLandscape::SetLandscapeProxy(UObject* InLandscapeProxy) -{ - UObject* LandscapeProxy = Cast(InLandscapeProxy); - if (LandscapeProxy) - InputObject = LandscapeProxy; -} - -ABrush* -UHoudiniInputBrush::GetBrush() const -{ - return Cast(InputObject.LoadSynchronous()); -} - - -//----------------------------------------------------------------------------------------------------------------------------- -// CREATE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -UHoudiniInputObject * -UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InName) -{ - if (!InObject) - return nullptr; - - UHoudiniInputObject* HoudiniInputObject = nullptr; - - EHoudiniInputObjectType InputObjectType = GetInputObjectTypeFromObject(InObject); - switch (InputObjectType) - { - case EHoudiniInputObjectType::Object: - HoudiniInputObject = UHoudiniInputObject::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::StaticMesh: - HoudiniInputObject = UHoudiniInputStaticMesh::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::SkeletalMesh: - HoudiniInputObject = UHoudiniInputSkeletalMesh::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::SceneComponent: - // Do not create input objects for unknown scene component! - //HoudiniInputObject = UHoudiniInputSceneComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::StaticMeshComponent: - HoudiniInputObject = UHoudiniInputMeshComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - HoudiniInputObject = UHoudiniInputInstancedMeshComponent::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::SplineComponent: - HoudiniInputObject = UHoudiniInputSplineComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::HoudiniSplineComponent: - HoudiniInputObject = UHoudiniInputHoudiniSplineComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::HoudiniAssetActor: - { - AHoudiniAssetActor* HoudiniActor = Cast(InObject); - if (HoudiniActor) - { - HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniActor->GetHoudiniAssetComponent(), InOuter, InName); - } - else - { - HoudiniInputObject = nullptr; - } - } - break; - - case EHoudiniInputObjectType::HoudiniAssetComponent: - HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::Actor: - HoudiniInputObject = UHoudiniInputActor::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Landscape: - HoudiniInputObject = UHoudiniInputLandscape::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Brush: - HoudiniInputObject = UHoudiniInputBrush::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::CameraComponent: - HoudiniInputObject = UHoudiniInputCameraComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::DataTable: - HoudiniInputObject = UHoudiniInputDataTable::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Invalid: - default: - break; - } - - return HoudiniInputObject; -} - - -UHoudiniInputObject * -UHoudiniInputInstancedMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_ISMC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputInstancedMeshComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::InstancedStaticMeshComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SMC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputMeshComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputMeshComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMeshComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Spline_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSplineComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSplineComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SplineComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputHoudiniSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_HoudiniSpline_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputHoudiniSplineComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniSplineComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputCameraComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Camera_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputCameraComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputCameraComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputCameraComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::CameraComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputHoudiniAsset::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - UHoudiniAssetComponent * InHoudiniAssetComponent = Cast(InObject); - if (!InHoudiniAssetComponent) - return nullptr; - - FString InputObjectNameStr = "HoudiniInputObject_HAC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniAsset::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputHoudiniAsset * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputHoudiniAsset::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniAssetComponent; - - HoudiniInputObject->InputNodeId = InHoudiniAssetComponent->GetAssetId(); - HoudiniInputObject->InputObjectNodeId = InHoudiniAssetComponent->GetAssetId(); - - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputSceneComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SceneComp_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSceneComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSceneComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSceneComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SceneComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputLandscape::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Landscape_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputLandscape::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputLandscape * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputLandscape::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Landscape; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputBrush * -UHoudiniInputBrush::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Brush_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputBrush::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputBrush * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputBrush::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Brush; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputActor::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Actor_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputActor::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputActor * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputActor::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Actor; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SM_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputStaticMesh::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputStaticMesh * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMesh; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -// void UHoudiniInputStaticMesh::DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputStaticMesh*& OutNewInput) -// { -// UHoudiniInputStaticMesh* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); -// OutNewInput = NewInput; -// OutNewInput->CopyStateFrom(this, false); -// } - -void -UHoudiniInputStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - UHoudiniInputStaticMesh* StaticMeshInput = Cast(InInput); - check(InInput); - - TArray PrevInputs = BlueprintStaticMeshes; - - Super::CopyStateFrom(StaticMeshInput, bCopyAllProperties); - - const int32 NumInputs = StaticMeshInput->BlueprintStaticMeshes.Num(); - BlueprintStaticMeshes = PrevInputs; - TArray StaleInputs(BlueprintStaticMeshes); - - BlueprintStaticMeshes.SetNum(NumInputs); - - for (int i = 0; i < NumInputs; ++i) - { - UHoudiniInputStaticMesh* FromInput = StaticMeshInput->BlueprintStaticMeshes[i]; - UHoudiniInputStaticMesh* ToInput = BlueprintStaticMeshes[i]; - - if (!FromInput) - { - BlueprintStaticMeshes[i] = nullptr; - continue; - } - - if (ToInput) - { - // Check whether the ToInput can be reused - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - if (!bIsValid) - { - ToInput = nullptr; - } - } - - if (ToInput) - { - // We have a reusable input - ToInput->CopyStateFrom(FromInput, true); - } - else - { - // We need to create a new input - ToInput = Cast(FromInput->DuplicateAndCopyState(this)); - } - - BlueprintStaticMeshes[i] = ToInput; - } - - for(UHoudiniInputStaticMesh* StaleInput : StaleInputs) - { - if (!StaleInput) - continue; - StaleInput->InvalidateData(); - } -} - -void -UHoudiniInputStaticMesh::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - Super::SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) - { - if (!Input) - continue; - Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -void -UHoudiniInputStaticMesh::InvalidateData() -{ - for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) - { - if (!Input) - continue; - Input->InvalidateData(); - } - - Super::InvalidateData(); -} - - -UHoudiniInputObject * -UHoudiniInputSkeletalMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SkelMesh_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSkeletalMesh::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSkeletalMesh * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSkeletalMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SkeletalMesh; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputObject::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputObject::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputObject * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputObject::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Object; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -bool -UHoudiniInputObject::Matches(const UHoudiniInputObject& Other) const -{ - return (Type == Other.Type - && InputNodeId == Other.InputNodeId - && InputObjectNodeId == Other.InputObjectNodeId - ); -} - -//----------------------------------------------------------------------------------------------------------------------------- -// DELETE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -void -UHoudiniInputObject::InvalidateData() -{ - // If valid, mark our input nodes for deletion.. - if (this->IsA() || !bCanDeleteHoudiniNodes) - { - // Unless if we're a HoudiniAssetInput! we don't want to delete the other HDA's node! - // just invalidate the node IDs! - InputNodeId = -1; - InputObjectNodeId = -1; - return; - } - - if (InputNodeId >= 0) - { - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); - InputNodeId = -1; - } - - // ... and the parent OBJ as well to clean up - if (InputObjectNodeId >= 0) - { - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputObjectNodeId); - InputObjectNodeId = -1; - } - - -} - -void -UHoudiniInputObject::BeginDestroy() -{ - // Invalidate and mark our input node for deletion - InvalidateData(); - - Super::BeginDestroy(); -} - -//----------------------------------------------------------------------------------------------------------------------------- -// UPDATE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -void -UHoudiniInputObject::Update(UObject * InObject) -{ - InputObject = InObject; -} - -void -UHoudiniInputStaticMesh::Update(UObject * InObject) -{ - // Nothing to do - Super::Update(InObject); - // Static Mesh input accpets either SM and BP. - UStaticMesh* SM = Cast(InObject); - UBlueprint* BP = Cast(InObject); - - ensure(SM || BP); -} - -void -UHoudiniInputSkeletalMesh::Update(UObject * InObject) -{ - // Nothing to do - Super::Update(InObject); - - USkeletalMesh* SkelMesh = Cast(InObject); - ensure(SkelMesh); -} - -void -UHoudiniInputSceneComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - USceneComponent* USC = Cast(InObject); - ensure(USC); - if (USC) - { - Transform = USC->GetComponentTransform(); - } -} - - -bool -UHoudiniInputSceneComponent::HasActorTransformChanged() const -{ - // Returns true if the attached actor's (parent) transform has been modified - USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); - if (!MyComp || MyComp->IsPendingKill()) - return false; - - AActor* MyActor = MyComp->GetOwner(); - if (!MyActor) - return false; - - return (!ActorTransform.Equals(MyActor->GetTransform())); -} - - -bool -UHoudiniInputSceneComponent::HasComponentTransformChanged() const -{ - // Returns true if the attached actor's (parent) transform has been modified - USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); - if (!MyComp || MyComp->IsPendingKill()) - return false; - - return !Transform.Equals(MyComp->GetComponentTransform()); -} - - -bool -UHoudiniInputSceneComponent::HasComponentChanged() const -{ - // Should return true if the component itself has been modified - // Should be overriden in child classes - return false; -} - - -bool -UHoudiniInputMeshComponent::HasComponentChanged() const -{ - UStaticMeshComponent* SMC = Cast(InputObject.LoadSynchronous()); - UStaticMesh* MySM = StaticMesh.Get(); - - // Return true if SMC's static mesh has been modified - return (MySM != SMC->GetStaticMesh()); -} - -bool -UHoudiniInputCameraComponent::HasComponentChanged() const -{ - UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); - if (Camera && !Camera->IsPendingKill()) - { - bool bOrtho = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; - if (bOrtho != bIsOrthographic) - return true; - - if (Camera->FieldOfView != FOV) - return true; - - if (Camera->AspectRatio != AspectRatio) - return true; - - if (Camera->OrthoWidth != OrthoWidth) - return true; - - if (Camera->OrthoNearClipPlane != OrthoNearClipPlane) - return true; - - if (Camera->OrthoFarClipPlane != OrthoFarClipPlane) - return true; - } - - return false; -} - - - -void -UHoudiniInputCameraComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); - - ensure(Camera); - - if (Camera && !Camera->IsPendingKill()) - { - bIsOrthographic = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; - FOV = Camera->FieldOfView; - AspectRatio = Camera->AspectRatio; - OrthoWidth = Camera->OrthoWidth; - OrthoNearClipPlane = Camera->OrthoNearClipPlane; - OrthoFarClipPlane = Camera->OrthoFarClipPlane; - } -} - -void -UHoudiniInputMeshComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UStaticMeshComponent* SMC = Cast(InObject); - - ensure(SMC); - - if (SMC) - { - StaticMesh = TSoftObjectPtr(SMC->GetStaticMesh()); - - TArray Materials = SMC->GetMaterials(); - for (auto CurrentMat : Materials) - { - // TODO: Update material ref here - FString MatRef; - MeshComponentsMaterials.Add(MatRef); - } - } -} - -void -UHoudiniInputInstancedMeshComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UInstancedStaticMeshComponent* ISMC = Cast(InObject); - - ensure(ISMC); - - if (ISMC) - { - uint32 InstanceCount = ISMC->GetInstanceCount(); - InstanceTransforms.SetNum(InstanceCount); - - // Copy the instances' transforms - for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) - { - FTransform CurTransform = FTransform::Identity; - ISMC->GetInstanceTransform(InstIdx, CurTransform); - InstanceTransforms[InstIdx] = CurTransform; - } - } -} - -bool -UHoudiniInputInstancedMeshComponent::HasInstancesChanged() const -{ - UInstancedStaticMeshComponent* ISMC = Cast(InputObject.LoadSynchronous()); - if (!ISMC) - return false; - - uint32 InstanceCount = ISMC->GetInstanceCount(); - if (InstanceTransforms.Num() != InstanceCount) - return true; - - // Copy the instances' transforms - for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) - { - FTransform CurTransform = FTransform::Identity; - ISMC->GetInstanceTransform(InstIdx, CurTransform); - - if(!InstanceTransforms[InstIdx].Equals(CurTransform)) - return true; - } - - return false; -} - -bool -UHoudiniInputInstancedMeshComponent::HasComponentTransformChanged() const -{ - if (Super::HasComponentTransformChanged()) - return true; - - return HasInstancesChanged(); -} - -void -UHoudiniInputSplineComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - USplineComponent* Spline = Cast(InObject); - - ensure(Spline); - - if (Spline) - { - NumberOfSplineControlPoints = Spline->GetNumberOfSplinePoints(); - SplineLength = Spline->GetSplineLength(); - SplineClosed = Spline->IsClosedLoop(); - - //SplineResolution = -1.0f; - - SplineControlPoints.SetNumZeroed(NumberOfSplineControlPoints); - for (int32 Idx = 0; Idx < NumberOfSplineControlPoints; Idx++) - { - SplineControlPoints[Idx] = Spline->GetTransformAtSplinePoint(Idx, ESplineCoordinateSpace::Local); - } - } -} - -void -UHoudiniInputHoudiniSplineComponent::Update(UObject* InObject) -{ - Super::Update(InObject); - - // We store the component references as a normal pointer property instead of using a soft object reference. - // If we use a soft object reference, the editor will complain about deleting a reference that is in use - // everytime we try to delete the actor, even though everything is contained within the actor. - - CachedComponent = Cast(InObject); - InputObject = nullptr; - - // We need a strong ref to the spline component to prevent it from being GCed - //MyHoudiniSplineComponent = Cast(InObject); - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - { - // Use default values - CurveType = EHoudiniCurveType::Polygon; - CurveMethod = EHoudiniCurveMethod::CVs; - Reversed = false; - } - else - { - CurveType = HoudiniSplineComponent->GetCurveType(); - CurveMethod = HoudiniSplineComponent->GetCurveMethod(); - Reversed = false;//Spline->IsReversed(); - } -} - -UObject* -UHoudiniInputHoudiniSplineComponent::GetObject() const -{ - return CachedComponent; -} - -void -UHoudiniInputHoudiniSplineComponent::MarkChanged(const bool& bInChanged) -{ - Super::MarkChanged(bInChanged); - - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent) - { - HoudiniSplineComponent->MarkChanged(bInChanged); - } -} - -void -UHoudiniInputHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) -{ - Super::SetNeedsToTriggerUpdate(bInTriggersUpdate); - - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent) - { - HoudiniSplineComponent->SetNeedsToTriggerUpdate(bInTriggersUpdate); - } -} - -bool -UHoudiniInputHoudiniSplineComponent::HasChanged() const -{ - if (Super::HasChanged()) - return true; - - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent && HoudiniSplineComponent->HasChanged()) - return true; - - return false; -} - -bool -UHoudiniInputHoudiniSplineComponent::NeedsToTriggerUpdate() const -{ - if (Super::NeedsToTriggerUpdate()) - return true; - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent && HoudiniSplineComponent->NeedsToTriggerUpdate()) - return true; - - return false; -} - -void -UHoudiniInputHoudiniAsset::Update(UObject * InObject) -{ - Super::Update(InObject); - - UHoudiniAssetComponent* HAC = Cast(InObject); - - ensure(HAC); - - if (HAC) - { - // TODO: Notify HAC that we're a downstream? - InputNodeId = HAC->GetAssetId(); - InputObjectNodeId = HAC->GetAssetId(); - - // TODO: Allow selection of the asset output - AssetOutputIndex = 0; - } -} - - -void -UHoudiniInputActor::Update(UObject * InObject) -{ - Super::Update(InObject); - - AActor* Actor = Cast(InObject); - ensure(Actor); - - if (Actor) - { - Transform = Actor->GetTransform(); - - // The actor's components that can be sent as inputs - ActorComponents.Empty(); - - TArray AllComponents; - Actor->GetComponents(AllComponents, true); - - int32 CompIdx = 0; - ActorComponents.SetNum(AllComponents.Num()); - for (USceneComponent * SceneComponent : AllComponents) - { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; - - UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( - SceneComponent, GetOuter(), Actor->GetName()); - if (!InputObj) - continue; - - UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); - if (!SceneInput) - continue; - - ActorComponents[CompIdx++] = SceneInput; - } - ActorComponents.SetNum(CompIdx); - } -} - -bool -UHoudiniInputActor::HasActorTransformChanged() -{ - if (!GetActor()) - return false; - - if (!Transform.Equals(GetActor()->GetTransform())) - return true; - - return false; -} - -bool -UHoudiniInputActor::HasContentChanged() const -{ - return false; -} - -bool -UHoudiniInputLandscape::HasActorTransformChanged() -{ - return Super::HasActorTransformChanged(); - //return false; -} - -void -UHoudiniInputLandscape::Update(UObject * InObject) -{ - Super::Update(InObject); - - ALandscapeProxy* Landscape = Cast(InObject); - - //ensure(Landscape); - - if (Landscape) - { - // Nothing to do for landscapes? - } -} - -EHoudiniInputObjectType -UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) -{ - if (InObject->IsA(USceneComponent::StaticClass())) - { - // Handle component inputs - // UISMC derived from USMC, so always test instances before static meshes - if (InObject->IsA(UInstancedStaticMeshComponent::StaticClass())) - { - return EHoudiniInputObjectType::InstancedStaticMeshComponent; - } - else if (InObject->IsA(UStaticMeshComponent::StaticClass())) - { - return EHoudiniInputObjectType::StaticMeshComponent; - } - else if (InObject->IsA(USplineComponent::StaticClass())) - { - return EHoudiniInputObjectType::SplineComponent; - } - else if (InObject->IsA(UHoudiniSplineComponent::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniSplineComponent; - } - else if (InObject->IsA(UHoudiniAssetComponent::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniAssetComponent; - } - else if (InObject->IsA(UCameraComponent::StaticClass())) - { - return EHoudiniInputObjectType::CameraComponent; - } - else - { - return EHoudiniInputObjectType::SceneComponent; - } - } - else if (InObject->IsA(AActor::StaticClass())) - { - // Handle actors - if (InObject->IsA(ALandscapeProxy::StaticClass())) - { - return EHoudiniInputObjectType::Landscape; - } - else if (InObject->IsA(ABrush::StaticClass())) - { - return EHoudiniInputObjectType::Brush; - } - else if (InObject->IsA(AHoudiniAssetActor::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniAssetActor; - } - else - { - return EHoudiniInputObjectType::Actor; - } - } - else if (InObject->IsA(UBlueprint::StaticClass())) - { - return EHoudiniInputObjectType::StaticMesh; - } - else - { - if (InObject->IsA(UStaticMesh::StaticClass())) - { - return EHoudiniInputObjectType::StaticMesh; - } - else if (InObject->IsA(USkeletalMesh::StaticClass())) - { - return EHoudiniInputObjectType::SkeletalMesh; - } - else if (InObject->IsA(UDataTable::StaticClass())) - { - return EHoudiniInputObjectType::DataTable; - } - else - { - return EHoudiniInputObjectType::Object; - } - } - - return EHoudiniInputObjectType::Invalid; -} - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniInputBrush -//----------------------------------------------------------------------------------------------------------------------------- - -FHoudiniBrushInfo::FHoudiniBrushInfo() - : CachedTransform() - , CachedOrigin(ForceInitToZero) - , CachedExtent(ForceInitToZero) - , CachedBrushType(EBrushType::Brush_Default) - , CachedSurfaceHash(0) -{ -} - -FHoudiniBrushInfo::FHoudiniBrushInfo(ABrush* InBrushActor) -{ - if (!InBrushActor) - return; - - BrushActor = InBrushActor; - CachedTransform = BrushActor->GetActorTransform(); - BrushActor->GetActorBounds(false, CachedOrigin, CachedExtent); - CachedBrushType = BrushActor->BrushType; - -#if WITH_EDITOR - UModel* Model = BrushActor->Brush; - - // Cache the hash of the surface properties - if (IsValid(Model) && IsValid(Model->Polys)) - { - int32 NumPolys = Model->Polys->Element.Num(); - CachedSurfaceHash = 0; - for(int32 iPoly = 0; iPoly < NumPolys; ++iPoly) - { - const FPoly& Poly = Model->Polys->Element[iPoly]; - CombinePolyHash(CachedSurfaceHash, Poly); - } - } - else - { - CachedSurfaceHash = 0; - } -#endif -} - -bool FHoudiniBrushInfo::HasChanged() const -{ - if (!BrushActor.IsValid()) - return false; - - // Has the transform changed? - if (!BrushActor->GetActorTransform().Equals(CachedTransform)) - return true; - - if (BrushActor->BrushType != CachedBrushType) - return true; - - // Has the actor bounds changed? - FVector TmpOrigin, TmpExtent; - BrushActor->GetActorBounds(false, TmpOrigin, TmpExtent); - - if (!(TmpOrigin.Equals(CachedOrigin) && TmpExtent.Equals(CachedExtent) )) - return true; -#if WITH_EDITOR - // Is there a tracked surface property that changed? - UModel* Model = BrushActor->Brush; - if (IsValid(Model) && IsValid(Model->Polys)) - { - // Hash the incoming surface properties and compared it against the cached hash. - int32 NumPolys = Model->Polys->Element.Num(); - uint64 SurfaceHash = 0; - for (int32 iPoly = 0; iPoly < NumPolys; ++iPoly) - { - const FPoly& Poly = Model->Polys->Element[iPoly]; - CombinePolyHash(SurfaceHash, Poly); - } - if (SurfaceHash != CachedSurfaceHash) - return true; - } - else - { - if (CachedSurfaceHash != 0) - return true; - } -#endif - return false; -} - -int32 FHoudiniBrushInfo::GetNumVertexIndicesFromModel(const UModel* Model) -{ - const TArray& Nodes = Model->Nodes; - int32 NumIndices = 0; - // Build the face counts buffer by iterating over the BSP nodes. - for(const FBspNode& Node : Nodes) - { - NumIndices += Node.NumVertices; - } - return NumIndices; -} - -UModel* UHoudiniInputBrush::GetCachedModel() const -{ - return CombinedModel; -} - -bool UHoudiniInputBrush::HasBrushesChanged(const TArray& InBrushes) const -{ - if (InBrushes.Num() != BrushesInfo.Num()) - return true; - - int32 NumBrushes = BrushesInfo.Num(); - - for (int32 InfoIndex = 0; InfoIndex < NumBrushes; ++InfoIndex) - { - const FHoudiniBrushInfo& BrushInfo = BrushesInfo[InfoIndex]; - // Has the cached brush actor invalid? - if (!BrushInfo.BrushActor.IsValid()) - return true; - - // Has there been an order change in the actors list? - if (InBrushes[InfoIndex] != BrushInfo.BrushActor.Get()) - return true; - - // Has there been any other changes to the brush? - if (BrushInfo.HasChanged()) - return true; - } - - // Nothing has changed. - return false; -} - -void UHoudiniInputBrush::UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes) -{ - ABrush* InputBrush = GetBrush(); - if (IsValid(InputBrush)) - { - CachedInputBrushType = InputBrush->BrushType; - } - - // Cache the combined model aswell as the brushes used to generate this model. - CombinedModel = InCombinedModel; - - BrushesInfo.SetNumUninitialized(InBrushes.Num()); - for (int i = 0; i < InBrushes.Num(); ++i) - { - if (!InBrushes[i]) - continue; - BrushesInfo[i] = FHoudiniBrushInfo(InBrushes[i]); - } -} - - -void -UHoudiniInputBrush::Update(UObject * InObject) -{ - Super::Update(InObject); - - ABrush* BrushActor = GetBrush(); - if (!IsValid(BrushActor)) - { - bIgnoreInputObject = true; - return; - } - - CachedInputBrushType = BrushActor->BrushType; - - bIgnoreInputObject = ShouldIgnoreThisInput(); -} - -bool -UHoudiniInputBrush::ShouldIgnoreThisInput() -{ - // Invalid brush, should be ignored - ABrush* BrushActor = GetBrush(); - ensure(BrushActor); - if (!BrushActor) - return true; - - // If the BrushType has changed since caching this object, this object cannot be ignored. - if (CachedInputBrushType != BrushActor->BrushType) - return false; - - // If it's not an additive brush, we want to ignore it - bool bShouldBeIgnored = BrushActor->BrushType != EBrushType::Brush_Add; - - // If this is not a static brush (e.g., AVolume), ignore it. - if (!bShouldBeIgnored) - bShouldBeIgnored = !BrushActor->IsStaticBrush(); - - return bShouldBeIgnored; -} - -bool UHoudiniInputBrush::HasContentChanged() const -{ - ABrush* BrushActor = GetBrush(); - ensure(BrushActor); - - if (!BrushActor) - return false; - - if (BrushActor->BrushType != CachedInputBrushType) - return true; - - if (bIgnoreInputObject) - return false; - - // Find intersecting actors and capture their properties so that - // we can determine whether something has changed. - TArray IntersectingBrushes; - FindIntersectingSubtractiveBrushes(this, IntersectingBrushes); - - if (HasBrushesChanged(IntersectingBrushes)) - { - return true; - } - - return false; -} - -bool -UHoudiniInputBrush::HasActorTransformChanged() -{ - if (bIgnoreInputObject) - return false; - - return Super::HasActorTransformChanged(); -} - - -bool UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes) -{ - TArray IntersectingActors; - TArray Bounds; - - - if (!IsValid(InputBrush)) - return false; - - ABrush* BrushActor = InputBrush->GetBrush(); - if (!IsValid(BrushActor)) - return false; - - - OutBrushes.Empty(); - - Bounds.Add( BrushActor->GetComponentsBoundingBox(true, true) ); - - FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(BrushActor->GetWorld(), ABrush::StaticClass(), Bounds, nullptr, IntersectingActors); - - //-------------------------------------------------------------------------------------------------- - // Filter the actors to only keep intersecting subtractive brushes. - //-------------------------------------------------------------------------------------------------- - for (AActor* Actor : IntersectingActors) - { - // Filter out anything that is not a static brush (typically volume actors). - ABrush* Brush = Cast(Actor); - - // NOTE: The brush actor needs to be added in the correct map/level order - // together with the subtractive brushes otherwise the CSG operations - // will not match the BSP in the level. - if (Actor == BrushActor) - OutBrushes.Add(Brush); - - if (!(Brush && Brush->IsStaticBrush())) - continue; - - if (Brush->BrushType == Brush_Subtract) - OutBrushes.Add(Brush); - } - - return true; -} - -#if WITH_EDITOR -void -UHoudiniInputObject::PostEditUndo() -{ - Super::PostEditUndo(); - MarkChanged(true); -} -#endif - -UHoudiniInputObject* -UHoudiniInputObject::DuplicateAndCopyState(UObject * DestOuter) -{ - UHoudiniInputObject* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); - NewInput->CopyStateFrom(this, false); - return NewInput; -} - -void -UHoudiniInputObject::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References should be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - } - - InputNodeId = InInput->InputNodeId; - InputObjectNodeId = InInput->InputObjectNodeId; - bHasChanged = InInput->bHasChanged; - bNeedsToTriggerUpdate = InInput->bNeedsToTriggerUpdate; - bTransformChanged = InInput->bTransformChanged; - Guid = InInput->Guid; - -#if WITH_EDITORONLY_DATA - bUniformScaleLocked = InInput->bUniformScaleLocked; -#endif - -} - -void -UHoudiniInputObject::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; -} - - -// -UHoudiniInputDataTable::UHoudiniInputDataTable(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -UHoudiniInputObject * -UHoudiniInputDataTable::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_DT_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputDataTable::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputDataTable * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputDataTable::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::DataTable; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UDataTable* -UHoudiniInputDataTable::GetDataTable() const -{ - return Cast(InputObject.LoadSynchronous()); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputObject.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInput.h" + +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "Engine/DataTable.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "Engine/Brush.h" +#include "Engine/Engine.h" +#include "GameFramework/Volume.h" +#include "Camera/CameraComponent.h" + +#include "Model.h" +#include "Engine/Brush.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "Kismet/KismetSystemLibrary.h" + +//----------------------------------------------------------------------------------------------------------------------------- +// Constructors +//----------------------------------------------------------------------------------------------------------------------------- + +// +UHoudiniInputObject::UHoudiniInputObject(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , Transform(FTransform::Identity) + , Type(EHoudiniInputObjectType::Invalid) + , InputNodeId(-1) + , InputObjectNodeId(-1) + , bHasChanged(false) + , bNeedsToTriggerUpdate(false) + , bTransformChanged(false) + , bImportAsReference(false) + , bCanDeleteHoudiniNodes(true) +{ + Guid = FGuid::NewGuid(); +} + +// +UHoudiniInputStaticMesh::UHoudiniInputStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSkeletalMesh::UHoudiniInputSkeletalMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSceneComponent::UHoudiniInputSceneComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputMeshComponent::UHoudiniInputMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputInstancedMeshComponent::UHoudiniInputInstancedMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSplineComponent::UHoudiniInputSplineComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , NumberOfSplineControlPoints(-1) + , SplineLength(-1.0f) + , SplineResolution(-1.0f) + , SplineClosed(false) +{ + +} + +// +UHoudiniInputCameraComponent::UHoudiniInputCameraComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , FOV(0.0f) + , AspectRatio(1.0f) + , bIsOrthographic(false) + , OrthoWidth(2.0f) + , OrthoNearClipPlane(0.0f) + , OrthoFarClipPlane(-1.0f) +{ + +} + +// Returns true if the attached actor's (parent) transform has been modified +bool +UHoudiniInputSplineComponent::HasActorTransformChanged() const +{ + return false; +} + +// Returns true if the attached component's transform has been modified +bool +UHoudiniInputSplineComponent::HasComponentTransformChanged() const +{ + return false; +} + +// Return true if the component itself has been modified +bool +UHoudiniInputSplineComponent::HasComponentChanged() const +{ + USplineComponent* SplineComponent = Cast(InputObject.LoadSynchronous()); + + if (!SplineComponent) + return false; + + if (SplineClosed != SplineComponent->IsClosedLoop()) + return true; + + + if (SplineComponent->GetNumberOfSplinePoints() != NumberOfSplineControlPoints) + return true; + + for (int32 n = 0; n < SplineComponent->GetNumberOfSplinePoints(); ++n) + { + const FTransform &CurSplineComponentTransform = SplineComponent->GetTransformAtSplinePoint(n, ESplineCoordinateSpace::Local); + const FTransform &CurInputTransform = SplineControlPoints[n]; + + if (CurInputTransform.GetLocation() != CurSplineComponentTransform.GetLocation()) + return true; + + if (CurInputTransform.GetRotation().Rotator() != CurSplineComponentTransform.GetRotation().Rotator()) + return true; + + if (CurInputTransform.GetScale3D() != CurSplineComponentTransform.GetScale3D()) + return true; + } + + return false; +} + +bool +UHoudiniInputSplineComponent::HasSplineComponentChanged(float fCurrentSplineResolution) const +{ + return false; +} + +// +UHoudiniInputHoudiniSplineComponent::UHoudiniInputHoudiniSplineComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , CurveType(EHoudiniCurveType::Polygon) + , CurveMethod(EHoudiniCurveMethod::CVs) + , Reversed(false) +{ + +} + +// +UHoudiniInputHoudiniAsset::UHoudiniInputHoudiniAsset(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , AssetOutputIndex(-1) +{ + +} + +// +UHoudiniInputActor::UHoudiniInputActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputLandscape::UHoudiniInputLandscape(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputBrush::UHoudiniInputBrush() + : CombinedModel(nullptr) + , bIgnoreInputObject(false) +{ + +} + +//----------------------------------------------------------------------------------------------------------------------------- +// Accessors +//----------------------------------------------------------------------------------------------------------------------------- + +UObject* +UHoudiniInputObject::GetObject() const +{ + return InputObject.LoadSynchronous(); +} + +UStaticMesh* +UHoudiniInputStaticMesh::GetStaticMesh() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UBlueprint* +UHoudiniInputStaticMesh::GetBlueprint() +{ + return Cast(InputObject.LoadSynchronous()); +} + +bool UHoudiniInputStaticMesh::bIsBlueprint() const +{ + return (InputObject.IsValid() && InputObject.Get()->IsA()); +} + +USkeletalMesh* +UHoudiniInputSkeletalMesh::GetSkeletalMesh() +{ + return Cast(InputObject.LoadSynchronous()); +} + +USceneComponent* +UHoudiniInputSceneComponent::GetSceneComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UStaticMeshComponent* +UHoudiniInputMeshComponent::GetStaticMeshComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UStaticMesh* +UHoudiniInputMeshComponent::GetStaticMesh() +{ + return StaticMesh.Get(); +} + +UInstancedStaticMeshComponent* +UHoudiniInputInstancedMeshComponent::GetInstancedStaticMeshComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +USplineComponent* +UHoudiniInputSplineComponent::GetSplineComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniSplineComponent* +UHoudiniInputHoudiniSplineComponent::GetCurveComponent() const +{ + return Cast(GetObject()); + //return Cast(InputObject.LoadSynchronous()); +} + +UCameraComponent* +UHoudiniInputCameraComponent::GetCameraComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniAssetComponent* +UHoudiniInputHoudiniAsset::GetHoudiniAssetComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +AActor* +UHoudiniInputActor::GetActor() +{ + return Cast(InputObject.LoadSynchronous()); +} + +ALandscapeProxy* +UHoudiniInputLandscape::GetLandscapeProxy() +{ + return Cast(InputObject.LoadSynchronous()); +} + +void +UHoudiniInputLandscape::SetLandscapeProxy(UObject* InLandscapeProxy) +{ + UObject* LandscapeProxy = Cast(InLandscapeProxy); + if (LandscapeProxy) + InputObject = LandscapeProxy; +} + +ABrush* +UHoudiniInputBrush::GetBrush() const +{ + return Cast(InputObject.LoadSynchronous()); +} + + +//----------------------------------------------------------------------------------------------------------------------------- +// CREATE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +UHoudiniInputObject * +UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InName) +{ + if (!InObject) + return nullptr; + + UHoudiniInputObject* HoudiniInputObject = nullptr; + + EHoudiniInputObjectType InputObjectType = GetInputObjectTypeFromObject(InObject); + switch (InputObjectType) + { + case EHoudiniInputObjectType::Object: + HoudiniInputObject = UHoudiniInputObject::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::StaticMesh: + HoudiniInputObject = UHoudiniInputStaticMesh::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::SkeletalMesh: + HoudiniInputObject = UHoudiniInputSkeletalMesh::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::SceneComponent: + // Do not create input objects for unknown scene component! + //HoudiniInputObject = UHoudiniInputSceneComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::StaticMeshComponent: + HoudiniInputObject = UHoudiniInputMeshComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + HoudiniInputObject = UHoudiniInputInstancedMeshComponent::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::SplineComponent: + HoudiniInputObject = UHoudiniInputSplineComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::HoudiniSplineComponent: + HoudiniInputObject = UHoudiniInputHoudiniSplineComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::HoudiniAssetActor: + { + AHoudiniAssetActor* HoudiniActor = Cast(InObject); + if (HoudiniActor) + { + HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniActor->GetHoudiniAssetComponent(), InOuter, InName); + } + else + { + HoudiniInputObject = nullptr; + } + } + break; + + case EHoudiniInputObjectType::HoudiniAssetComponent: + HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::Actor: + HoudiniInputObject = UHoudiniInputActor::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Landscape: + HoudiniInputObject = UHoudiniInputLandscape::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Brush: + HoudiniInputObject = UHoudiniInputBrush::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::CameraComponent: + HoudiniInputObject = UHoudiniInputCameraComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::DataTable: + HoudiniInputObject = UHoudiniInputDataTable::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Invalid: + default: + break; + } + + return HoudiniInputObject; +} + + +UHoudiniInputObject * +UHoudiniInputInstancedMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_ISMC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputInstancedMeshComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::InstancedStaticMeshComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SMC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputMeshComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputMeshComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMeshComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Spline_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSplineComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSplineComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SplineComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputHoudiniSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_HoudiniSpline_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputHoudiniSplineComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniSplineComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputCameraComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Camera_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputCameraComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputCameraComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputCameraComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::CameraComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputHoudiniAsset::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + UHoudiniAssetComponent * InHoudiniAssetComponent = Cast(InObject); + if (!InHoudiniAssetComponent) + return nullptr; + + FString InputObjectNameStr = "HoudiniInputObject_HAC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniAsset::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputHoudiniAsset * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputHoudiniAsset::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniAssetComponent; + + HoudiniInputObject->InputNodeId = InHoudiniAssetComponent->GetAssetId(); + HoudiniInputObject->InputObjectNodeId = InHoudiniAssetComponent->GetAssetId(); + + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputSceneComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SceneComp_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSceneComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSceneComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSceneComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SceneComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputLandscape::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Landscape_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputLandscape::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputLandscape * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputLandscape::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Landscape; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputBrush * +UHoudiniInputBrush::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Brush_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputBrush::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputBrush * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputBrush::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Brush; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputActor::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Actor_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputActor::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputActor * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputActor::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Actor; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SM_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputStaticMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputStaticMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +// void UHoudiniInputStaticMesh::DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputStaticMesh*& OutNewInput) +// { +// UHoudiniInputStaticMesh* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); +// OutNewInput = NewInput; +// OutNewInput->CopyStateFrom(this, false); +// } + +void +UHoudiniInputStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + UHoudiniInputStaticMesh* StaticMeshInput = Cast(InInput); + check(InInput); + + TArray PrevInputs = BlueprintStaticMeshes; + + Super::CopyStateFrom(StaticMeshInput, bCopyAllProperties); + + const int32 NumInputs = StaticMeshInput->BlueprintStaticMeshes.Num(); + BlueprintStaticMeshes = PrevInputs; + TArray StaleInputs(BlueprintStaticMeshes); + + BlueprintStaticMeshes.SetNum(NumInputs); + + for (int i = 0; i < NumInputs; ++i) + { + UHoudiniInputStaticMesh* FromInput = StaticMeshInput->BlueprintStaticMeshes[i]; + UHoudiniInputStaticMesh* ToInput = BlueprintStaticMeshes[i]; + + if (!FromInput) + { + BlueprintStaticMeshes[i] = nullptr; + continue; + } + + if (ToInput) + { + // Check whether the ToInput can be reused + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + if (!bIsValid) + { + ToInput = nullptr; + } + } + + if (ToInput) + { + // We have a reusable input + ToInput->CopyStateFrom(FromInput, true); + } + else + { + // We need to create a new input + ToInput = Cast(FromInput->DuplicateAndCopyState(this)); + } + + BlueprintStaticMeshes[i] = ToInput; + } + + for(UHoudiniInputStaticMesh* StaleInput : StaleInputs) + { + if (!StaleInput) + continue; + StaleInput->InvalidateData(); + } +} + +void +UHoudiniInputStaticMesh::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + Super::SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) + { + if (!Input) + continue; + Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +void +UHoudiniInputStaticMesh::InvalidateData() +{ + for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) + { + if (!Input) + continue; + Input->InvalidateData(); + } + + Super::InvalidateData(); +} + + +UHoudiniInputObject * +UHoudiniInputSkeletalMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SkelMesh_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSkeletalMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSkeletalMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSkeletalMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SkeletalMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputObject::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputObject::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputObject * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputObject::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Object; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +bool +UHoudiniInputObject::Matches(const UHoudiniInputObject& Other) const +{ + return (Type == Other.Type + && InputNodeId == Other.InputNodeId + && InputObjectNodeId == Other.InputObjectNodeId + ); +} + +//----------------------------------------------------------------------------------------------------------------------------- +// DELETE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +void +UHoudiniInputObject::InvalidateData() +{ + // If valid, mark our input nodes for deletion.. + if (this->IsA() || !bCanDeleteHoudiniNodes) + { + // Unless if we're a HoudiniAssetInput! we don't want to delete the other HDA's node! + // just invalidate the node IDs! + InputNodeId = -1; + InputObjectNodeId = -1; + return; + } + + if (InputNodeId >= 0) + { + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); + InputNodeId = -1; + } + + // ... and the parent OBJ as well to clean up + if (InputObjectNodeId >= 0) + { + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputObjectNodeId); + InputObjectNodeId = -1; + } + + +} + +void +UHoudiniInputObject::BeginDestroy() +{ + // Invalidate and mark our input node for deletion + InvalidateData(); + + Super::BeginDestroy(); +} + +//----------------------------------------------------------------------------------------------------------------------------- +// UPDATE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +void +UHoudiniInputObject::Update(UObject * InObject) +{ + InputObject = InObject; +} + +void +UHoudiniInputStaticMesh::Update(UObject * InObject) +{ + // Nothing to do + Super::Update(InObject); + // Static Mesh input accpets either SM and BP. + UStaticMesh* SM = Cast(InObject); + UBlueprint* BP = Cast(InObject); + + ensure(SM || BP); +} + +void +UHoudiniInputSkeletalMesh::Update(UObject * InObject) +{ + // Nothing to do + Super::Update(InObject); + + USkeletalMesh* SkelMesh = Cast(InObject); + ensure(SkelMesh); +} + +void +UHoudiniInputSceneComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + USceneComponent* USC = Cast(InObject); + ensure(USC); + if (USC) + { + Transform = USC->GetComponentTransform(); + } +} + + +bool +UHoudiniInputSceneComponent::HasActorTransformChanged() const +{ + // Returns true if the attached actor's (parent) transform has been modified + USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); + if (!MyComp || MyComp->IsPendingKill()) + return false; + + AActor* MyActor = MyComp->GetOwner(); + if (!MyActor) + return false; + + return (!ActorTransform.Equals(MyActor->GetTransform())); +} + + +bool +UHoudiniInputSceneComponent::HasComponentTransformChanged() const +{ + // Returns true if the attached actor's (parent) transform has been modified + USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); + if (!MyComp || MyComp->IsPendingKill()) + return false; + + return !Transform.Equals(MyComp->GetComponentTransform()); +} + + +bool +UHoudiniInputSceneComponent::HasComponentChanged() const +{ + // Should return true if the component itself has been modified + // Should be overriden in child classes + return false; +} + + +bool +UHoudiniInputMeshComponent::HasComponentChanged() const +{ + UStaticMeshComponent* SMC = Cast(InputObject.LoadSynchronous()); + UStaticMesh* MySM = StaticMesh.Get(); + + // Return true if SMC's static mesh has been modified + return (MySM != SMC->GetStaticMesh()); +} + +bool +UHoudiniInputCameraComponent::HasComponentChanged() const +{ + UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); + if (Camera && !Camera->IsPendingKill()) + { + bool bOrtho = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; + if (bOrtho != bIsOrthographic) + return true; + + if (Camera->FieldOfView != FOV) + return true; + + if (Camera->AspectRatio != AspectRatio) + return true; + + if (Camera->OrthoWidth != OrthoWidth) + return true; + + if (Camera->OrthoNearClipPlane != OrthoNearClipPlane) + return true; + + if (Camera->OrthoFarClipPlane != OrthoFarClipPlane) + return true; + } + + return false; +} + + + +void +UHoudiniInputCameraComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); + + ensure(Camera); + + if (Camera && !Camera->IsPendingKill()) + { + bIsOrthographic = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; + FOV = Camera->FieldOfView; + AspectRatio = Camera->AspectRatio; + OrthoWidth = Camera->OrthoWidth; + OrthoNearClipPlane = Camera->OrthoNearClipPlane; + OrthoFarClipPlane = Camera->OrthoFarClipPlane; + } +} + +void +UHoudiniInputMeshComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UStaticMeshComponent* SMC = Cast(InObject); + + ensure(SMC); + + if (SMC) + { + StaticMesh = TSoftObjectPtr(SMC->GetStaticMesh()); + + TArray Materials = SMC->GetMaterials(); + for (auto CurrentMat : Materials) + { + // TODO: Update material ref here + FString MatRef; + MeshComponentsMaterials.Add(MatRef); + } + } +} + +void +UHoudiniInputInstancedMeshComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UInstancedStaticMeshComponent* ISMC = Cast(InObject); + + ensure(ISMC); + + if (ISMC) + { + uint32 InstanceCount = ISMC->GetInstanceCount(); + InstanceTransforms.SetNum(InstanceCount); + + // Copy the instances' transforms + for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) + { + FTransform CurTransform = FTransform::Identity; + ISMC->GetInstanceTransform(InstIdx, CurTransform); + InstanceTransforms[InstIdx] = CurTransform; + } + } +} + +bool +UHoudiniInputInstancedMeshComponent::HasInstancesChanged() const +{ + UInstancedStaticMeshComponent* ISMC = Cast(InputObject.LoadSynchronous()); + if (!ISMC) + return false; + + uint32 InstanceCount = ISMC->GetInstanceCount(); + if (InstanceTransforms.Num() != InstanceCount) + return true; + + // Copy the instances' transforms + for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) + { + FTransform CurTransform = FTransform::Identity; + ISMC->GetInstanceTransform(InstIdx, CurTransform); + + if(!InstanceTransforms[InstIdx].Equals(CurTransform)) + return true; + } + + return false; +} + +bool +UHoudiniInputInstancedMeshComponent::HasComponentTransformChanged() const +{ + if (Super::HasComponentTransformChanged()) + return true; + + return HasInstancesChanged(); +} + +void +UHoudiniInputSplineComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + USplineComponent* Spline = Cast(InObject); + + ensure(Spline); + + if (Spline) + { + NumberOfSplineControlPoints = Spline->GetNumberOfSplinePoints(); + SplineLength = Spline->GetSplineLength(); + SplineClosed = Spline->IsClosedLoop(); + + //SplineResolution = -1.0f; + + SplineControlPoints.SetNumZeroed(NumberOfSplineControlPoints); + for (int32 Idx = 0; Idx < NumberOfSplineControlPoints; Idx++) + { + SplineControlPoints[Idx] = Spline->GetTransformAtSplinePoint(Idx, ESplineCoordinateSpace::Local); + } + } +} + +void +UHoudiniInputHoudiniSplineComponent::Update(UObject* InObject) +{ + Super::Update(InObject); + + // We store the component references as a normal pointer property instead of using a soft object reference. + // If we use a soft object reference, the editor will complain about deleting a reference that is in use + // everytime we try to delete the actor, even though everything is contained within the actor. + + CachedComponent = Cast(InObject); + InputObject = nullptr; + + // We need a strong ref to the spline component to prevent it from being GCed + //MyHoudiniSplineComponent = Cast(InObject); + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + { + // Use default values + CurveType = EHoudiniCurveType::Polygon; + CurveMethod = EHoudiniCurveMethod::CVs; + Reversed = false; + } + else + { + CurveType = HoudiniSplineComponent->GetCurveType(); + CurveMethod = HoudiniSplineComponent->GetCurveMethod(); + Reversed = false;//Spline->IsReversed(); + } +} + +UObject* +UHoudiniInputHoudiniSplineComponent::GetObject() const +{ + return CachedComponent; +} + +void +UHoudiniInputHoudiniSplineComponent::MarkChanged(const bool& bInChanged) +{ + Super::MarkChanged(bInChanged); + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent) + { + HoudiniSplineComponent->MarkChanged(bInChanged); + } +} + +void +UHoudiniInputHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) +{ + Super::SetNeedsToTriggerUpdate(bInTriggersUpdate); + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent) + { + HoudiniSplineComponent->SetNeedsToTriggerUpdate(bInTriggersUpdate); + } +} + +bool +UHoudiniInputHoudiniSplineComponent::HasChanged() const +{ + if (Super::HasChanged()) + return true; + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent && HoudiniSplineComponent->HasChanged()) + return true; + + return false; +} + +bool +UHoudiniInputHoudiniSplineComponent::NeedsToTriggerUpdate() const +{ + if (Super::NeedsToTriggerUpdate()) + return true; + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent && HoudiniSplineComponent->NeedsToTriggerUpdate()) + return true; + + return false; +} + +void +UHoudiniInputHoudiniAsset::Update(UObject * InObject) +{ + Super::Update(InObject); + + UHoudiniAssetComponent* HAC = Cast(InObject); + + ensure(HAC); + + if (HAC) + { + // TODO: Notify HAC that we're a downstream? + InputNodeId = HAC->GetAssetId(); + InputObjectNodeId = HAC->GetAssetId(); + + // TODO: Allow selection of the asset output + AssetOutputIndex = 0; + } +} + + +void +UHoudiniInputActor::Update(UObject * InObject) +{ + Super::Update(InObject); + + AActor* Actor = Cast(InObject); + ensure(Actor); + + if (Actor) + { + Transform = Actor->GetTransform(); + + // The actor's components that can be sent as inputs + ActorComponents.Empty(); + + TArray AllComponents; + Actor->GetComponents(AllComponents, true); + + int32 CompIdx = 0; + ActorComponents.SetNum(AllComponents.Num()); + for (USceneComponent * SceneComponent : AllComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( + SceneComponent, GetOuter(), Actor->GetName()); + if (!InputObj) + continue; + + UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); + if (!SceneInput) + continue; + + ActorComponents[CompIdx++] = SceneInput; + } + ActorComponents.SetNum(CompIdx); + } +} + +bool +UHoudiniInputActor::HasActorTransformChanged() +{ + if (!GetActor()) + return false; + + if (!Transform.Equals(GetActor()->GetTransform())) + return true; + + return false; +} + +bool +UHoudiniInputActor::HasContentChanged() const +{ + return false; +} + +bool +UHoudiniInputLandscape::HasActorTransformChanged() +{ + return Super::HasActorTransformChanged(); + //return false; +} + +void +UHoudiniInputLandscape::Update(UObject * InObject) +{ + Super::Update(InObject); + + ALandscapeProxy* Landscape = Cast(InObject); + + //ensure(Landscape); + + if (Landscape) + { + // Nothing to do for landscapes? + } +} + +EHoudiniInputObjectType +UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) +{ + if (InObject->IsA(USceneComponent::StaticClass())) + { + // Handle component inputs + // UISMC derived from USMC, so always test instances before static meshes + if (InObject->IsA(UInstancedStaticMeshComponent::StaticClass())) + { + return EHoudiniInputObjectType::InstancedStaticMeshComponent; + } + else if (InObject->IsA(UStaticMeshComponent::StaticClass())) + { + return EHoudiniInputObjectType::StaticMeshComponent; + } + else if (InObject->IsA(USplineComponent::StaticClass())) + { + return EHoudiniInputObjectType::SplineComponent; + } + else if (InObject->IsA(UHoudiniSplineComponent::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniSplineComponent; + } + else if (InObject->IsA(UHoudiniAssetComponent::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniAssetComponent; + } + else if (InObject->IsA(UCameraComponent::StaticClass())) + { + return EHoudiniInputObjectType::CameraComponent; + } + else + { + return EHoudiniInputObjectType::SceneComponent; + } + } + else if (InObject->IsA(AActor::StaticClass())) + { + // Handle actors + if (InObject->IsA(ALandscapeProxy::StaticClass())) + { + return EHoudiniInputObjectType::Landscape; + } + else if (InObject->IsA(ABrush::StaticClass())) + { + return EHoudiniInputObjectType::Brush; + } + else if (InObject->IsA(AHoudiniAssetActor::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniAssetActor; + } + else + { + return EHoudiniInputObjectType::Actor; + } + } + else if (InObject->IsA(UBlueprint::StaticClass())) + { + return EHoudiniInputObjectType::StaticMesh; + } + else + { + if (InObject->IsA(UStaticMesh::StaticClass())) + { + return EHoudiniInputObjectType::StaticMesh; + } + else if (InObject->IsA(USkeletalMesh::StaticClass())) + { + return EHoudiniInputObjectType::SkeletalMesh; + } + else if (InObject->IsA(UDataTable::StaticClass())) + { + return EHoudiniInputObjectType::DataTable; + } + else + { + return EHoudiniInputObjectType::Object; + } + } + + return EHoudiniInputObjectType::Invalid; +} + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniInputBrush +//----------------------------------------------------------------------------------------------------------------------------- + +FHoudiniBrushInfo::FHoudiniBrushInfo() + : CachedTransform() + , CachedOrigin(ForceInitToZero) + , CachedExtent(ForceInitToZero) + , CachedBrushType(EBrushType::Brush_Default) + , CachedSurfaceHash(0) +{ +} + +FHoudiniBrushInfo::FHoudiniBrushInfo(ABrush* InBrushActor) +{ + if (!InBrushActor) + return; + + BrushActor = InBrushActor; + CachedTransform = BrushActor->GetActorTransform(); + BrushActor->GetActorBounds(false, CachedOrigin, CachedExtent); + CachedBrushType = BrushActor->BrushType; + +#if WITH_EDITOR + UModel* Model = BrushActor->Brush; + + // Cache the hash of the surface properties + if (IsValid(Model) && IsValid(Model->Polys)) + { + int32 NumPolys = Model->Polys->Element.Num(); + CachedSurfaceHash = 0; + for(int32 iPoly = 0; iPoly < NumPolys; ++iPoly) + { + const FPoly& Poly = Model->Polys->Element[iPoly]; + CombinePolyHash(CachedSurfaceHash, Poly); + } + } + else + { + CachedSurfaceHash = 0; + } +#endif +} + +bool FHoudiniBrushInfo::HasChanged() const +{ + if (!BrushActor.IsValid()) + return false; + + // Has the transform changed? + if (!BrushActor->GetActorTransform().Equals(CachedTransform)) + return true; + + if (BrushActor->BrushType != CachedBrushType) + return true; + + // Has the actor bounds changed? + FVector TmpOrigin, TmpExtent; + BrushActor->GetActorBounds(false, TmpOrigin, TmpExtent); + + if (!(TmpOrigin.Equals(CachedOrigin) && TmpExtent.Equals(CachedExtent) )) + return true; +#if WITH_EDITOR + // Is there a tracked surface property that changed? + UModel* Model = BrushActor->Brush; + if (IsValid(Model) && IsValid(Model->Polys)) + { + // Hash the incoming surface properties and compared it against the cached hash. + int32 NumPolys = Model->Polys->Element.Num(); + uint64 SurfaceHash = 0; + for (int32 iPoly = 0; iPoly < NumPolys; ++iPoly) + { + const FPoly& Poly = Model->Polys->Element[iPoly]; + CombinePolyHash(SurfaceHash, Poly); + } + if (SurfaceHash != CachedSurfaceHash) + return true; + } + else + { + if (CachedSurfaceHash != 0) + return true; + } +#endif + return false; +} + +int32 FHoudiniBrushInfo::GetNumVertexIndicesFromModel(const UModel* Model) +{ + const TArray& Nodes = Model->Nodes; + int32 NumIndices = 0; + // Build the face counts buffer by iterating over the BSP nodes. + for(const FBspNode& Node : Nodes) + { + NumIndices += Node.NumVertices; + } + return NumIndices; +} + +UModel* UHoudiniInputBrush::GetCachedModel() const +{ + return CombinedModel; +} + +bool UHoudiniInputBrush::HasBrushesChanged(const TArray& InBrushes) const +{ + if (InBrushes.Num() != BrushesInfo.Num()) + return true; + + int32 NumBrushes = BrushesInfo.Num(); + + for (int32 InfoIndex = 0; InfoIndex < NumBrushes; ++InfoIndex) + { + const FHoudiniBrushInfo& BrushInfo = BrushesInfo[InfoIndex]; + // Has the cached brush actor invalid? + if (!BrushInfo.BrushActor.IsValid()) + return true; + + // Has there been an order change in the actors list? + if (InBrushes[InfoIndex] != BrushInfo.BrushActor.Get()) + return true; + + // Has there been any other changes to the brush? + if (BrushInfo.HasChanged()) + return true; + } + + // Nothing has changed. + return false; +} + +void UHoudiniInputBrush::UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes) +{ + ABrush* InputBrush = GetBrush(); + if (IsValid(InputBrush)) + { + CachedInputBrushType = InputBrush->BrushType; + } + + // Cache the combined model aswell as the brushes used to generate this model. + CombinedModel = InCombinedModel; + + BrushesInfo.SetNumUninitialized(InBrushes.Num()); + for (int i = 0; i < InBrushes.Num(); ++i) + { + if (!InBrushes[i]) + continue; + BrushesInfo[i] = FHoudiniBrushInfo(InBrushes[i]); + } +} + + +void +UHoudiniInputBrush::Update(UObject * InObject) +{ + Super::Update(InObject); + + ABrush* BrushActor = GetBrush(); + if (!IsValid(BrushActor)) + { + bIgnoreInputObject = true; + return; + } + + CachedInputBrushType = BrushActor->BrushType; + + bIgnoreInputObject = ShouldIgnoreThisInput(); +} + +bool +UHoudiniInputBrush::ShouldIgnoreThisInput() +{ + // Invalid brush, should be ignored + ABrush* BrushActor = GetBrush(); + ensure(BrushActor); + if (!BrushActor) + return true; + + // If the BrushType has changed since caching this object, this object cannot be ignored. + if (CachedInputBrushType != BrushActor->BrushType) + return false; + + // If it's not an additive brush, we want to ignore it + bool bShouldBeIgnored = BrushActor->BrushType != EBrushType::Brush_Add; + + // If this is not a static brush (e.g., AVolume), ignore it. + if (!bShouldBeIgnored) + bShouldBeIgnored = !BrushActor->IsStaticBrush(); + + return bShouldBeIgnored; +} + +bool UHoudiniInputBrush::HasContentChanged() const +{ + ABrush* BrushActor = GetBrush(); + ensure(BrushActor); + + if (!BrushActor) + return false; + + if (BrushActor->BrushType != CachedInputBrushType) + return true; + + if (bIgnoreInputObject) + return false; + + // Find intersecting actors and capture their properties so that + // we can determine whether something has changed. + TArray IntersectingBrushes; + FindIntersectingSubtractiveBrushes(this, IntersectingBrushes); + + if (HasBrushesChanged(IntersectingBrushes)) + { + return true; + } + + return false; +} + +bool +UHoudiniInputBrush::HasActorTransformChanged() +{ + if (bIgnoreInputObject) + return false; + + return Super::HasActorTransformChanged(); +} + + +bool UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes) +{ + TArray IntersectingActors; + TArray Bounds; + + + if (!IsValid(InputBrush)) + return false; + + ABrush* BrushActor = InputBrush->GetBrush(); + if (!IsValid(BrushActor)) + return false; + + + OutBrushes.Empty(); + + Bounds.Add( BrushActor->GetComponentsBoundingBox(true, true) ); + + FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(BrushActor->GetWorld(), ABrush::StaticClass(), Bounds, nullptr, IntersectingActors); + + //-------------------------------------------------------------------------------------------------- + // Filter the actors to only keep intersecting subtractive brushes. + //-------------------------------------------------------------------------------------------------- + for (AActor* Actor : IntersectingActors) + { + // Filter out anything that is not a static brush (typically volume actors). + ABrush* Brush = Cast(Actor); + + // NOTE: The brush actor needs to be added in the correct map/level order + // together with the subtractive brushes otherwise the CSG operations + // will not match the BSP in the level. + if (Actor == BrushActor) + OutBrushes.Add(Brush); + + if (!(Brush && Brush->IsStaticBrush())) + continue; + + if (Brush->BrushType == Brush_Subtract) + OutBrushes.Add(Brush); + } + + return true; +} + +#if WITH_EDITOR +void +UHoudiniInputObject::PostEditUndo() +{ + Super::PostEditUndo(); + MarkChanged(true); +} +#endif + +UHoudiniInputObject* +UHoudiniInputObject::DuplicateAndCopyState(UObject * DestOuter) +{ + UHoudiniInputObject* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); + NewInput->CopyStateFrom(this, false); + return NewInput; +} + +void +UHoudiniInputObject::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References should be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + } + + InputNodeId = InInput->InputNodeId; + InputObjectNodeId = InInput->InputObjectNodeId; + bHasChanged = InInput->bHasChanged; + bNeedsToTriggerUpdate = InInput->bNeedsToTriggerUpdate; + bTransformChanged = InInput->bTransformChanged; + Guid = InInput->Guid; + +#if WITH_EDITORONLY_DATA + bUniformScaleLocked = InInput->bUniformScaleLocked; +#endif + +} + +void +UHoudiniInputObject::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; +} + + +// +UHoudiniInputDataTable::UHoudiniInputDataTable(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniInputObject * +UHoudiniInputDataTable::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_DT_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputDataTable::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputDataTable * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputDataTable::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::DataTable; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UDataTable* +UHoudiniInputDataTable::GetDataTable() const +{ + return Cast(InputObject.LoadSynchronous()); } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h index 775b375bd..6142a016f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h @@ -1,805 +1,805 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include - -#include "HoudiniSplineComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "CoreTypes.h" -#include "Materials/MaterialInterface.h" -#include "UObject/Object.h" -#include "UObject/ObjectMacros.h" - -#include "Engine/Brush.h" -#include "Engine/Polys.h" -#include "UObject/SoftObjectPtr.h" - -#include "HoudiniInputObject.generated.h" - -class UStaticMesh; -class USkeletalMesh; -class USceneComponent; -class UStaticMeshComponent; -class UInstancedStaticMeshComponent; -class USplineComponent; -class UHoudiniAssetComponent; -class AActor; -class ALandscapeProxy; -class ABrush; -class UHoudiniInput; -class ALandscapeProxy; -class UModel; -class UHoudiniInput; -class UCameraComponent; - -UENUM() -enum class EHoudiniInputObjectType : uint8 -{ - Invalid, - - Object, - StaticMesh, - SkeletalMesh, - SceneComponent, - StaticMeshComponent, - InstancedStaticMeshComponent, - SplineComponent, - HoudiniSplineComponent, - HoudiniAssetComponent, - Actor, - Landscape, - Brush, - CameraComponent, - DataTable, - HoudiniAssetActor, -}; - -//----------------------------------------------------------------------------------------------------------------------------- -// UObjects input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - // Create the proper input object - static UHoudiniInputObject * CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InParamName); - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // Check whether two input objects match - virtual bool Matches(const UHoudiniInputObject& Other) const; - - // - static EHoudiniInputObjectType GetInputObjectTypeFromObject(UObject* InObject); - - // - virtual void Update(UObject * InObject); - - // Invalidate and ask for the deletion of this input object's node - virtual void InvalidateData(); - - // UObject accessor - virtual UObject* GetObject() const; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const { return bHasChanged; }; - - // Indicates if this input has changed and should be updated - virtual bool HasTransformChanged() const { return bTransformChanged; }; - - // Indicates if this input needs to trigger an update - virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; - - virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - void MarkTransformChanged(const bool& bInChanged) { bTransformChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - - void SetImportAsReference(const bool& bInImportAsRef) { bImportAsReference = bInImportAsRef; }; - bool GetImportAsReference() const { return bImportAsReference; }; - -#if WITH_EDITOR - void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; - bool IsUniformScaleLocked() const { return bUniformScaleLocked; }; - - void PostEditUndo() override; -#endif - - virtual UHoudiniInputObject* DuplicateAndCopyState(UObject* DestOuter); - virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties); - - // Set whether this object can delete Houdini nodes. - virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } - - FGuid GetInputGuid() const { return Guid; } - - -protected: - - virtual void BeginDestroy() override; - -public: - - // The object referenced by this input - // This property should be protected. Don't access this directly. Use GetObject() / Update() instead. - UPROPERTY() - TSoftObjectPtr InputObject; - - // The object's transform/transform offset - UPROPERTY() - FTransform Transform; - - // The type of Object this input refers to - UPROPERTY() - EHoudiniInputObjectType Type; - - // This input object's "main" (SOP) NodeId - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputNodeId; - - // This input object's "container" (OBJ) NodeId - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputObjectNodeId; - - // Guid that uniquely identifies this input object. - // Also useful to correlate inputs between blueprint component templates and instances. - UPROPERTY(DuplicateTransient) - FGuid Guid; - -protected: - - // Indicates this input object has changed - UPROPERTY(DuplicateTransient) - bool bHasChanged; - - // Indicates this input object should trigger an input update/cook - UPROPERTY(DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Indicates that this input transform should be updated - UPROPERTY(DuplicateTransient) - bool bTransformChanged; - - UPROPERTY() - bool bImportAsReference; - - // Indicates if change the scale of Transfrom Offset of this object uniformly -#if WITH_EDITORONLY_DATA - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - bool bUniformScaleLocked; -#endif - - UPROPERTY() - bool bCanDeleteHoudiniNodes; -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UStaticMesh input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputStaticMesh : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // UHoudiniInputObject overrides - - // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; - virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; - virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) override; - virtual void InvalidateData() override; - - // - virtual void Update(UObject * InObject) override; - - // Nothing to add for Static Meshes? - - // StaticMesh accessor - class UStaticMesh* GetStaticMesh(); - - // Blueprint accessor - class UBlueprint* GetBlueprint(); - - // Check if this SM Input object is passed in as a BP - bool bIsBlueprint() const; - - // The Blueprint's Static Meshe Components that can be sent as inputs - UPROPERTY() - TArray BlueprintStaticMeshes; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USkeletalMesh input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSkeletalMesh : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // Nothing to add for SkeletalMesh Meshes? - - // StaticMesh accessor - class USkeletalMesh* GetSkeletalMesh(); -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USceneComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSceneComponent : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // SceneComponent accessor - class USceneComponent* GetSceneComponent(); - - // Returns true if the attached actor's (parent) transform has been modified - virtual bool HasActorTransformChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const; - - // Return true if the component itself has been modified - virtual bool HasComponentChanged() const; - -public: - - // This component's parent Actor transform - UPROPERTY() - FTransform ActorTransform = FTransform::Identity; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UStaticMeshComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputMeshComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // StaticMeshComponent accessor - UStaticMeshComponent* GetStaticMeshComponent(); - - // Get the referenced StaticMesh - UStaticMesh* GetStaticMesh(); - - // Returns true if the attached component's materials have been modified - bool HasComponentMaterialsChanged() const; - - // Return true if SMC's static mesh has been modified - virtual bool HasComponentChanged() const override; - -public: - - // Keep track of the selected Static Mesh - UPROPERTY() - TSoftObjectPtr StaticMesh = nullptr; - - // Path to the materials assigned on the SMC - UPROPERTY() - TArray MeshComponentsMaterials; -}; - - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UInstancedStaticMeshComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputInstancedMeshComponent : public UHoudiniInputMeshComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // InstancedStaticMeshComponent accessor - UInstancedStaticMeshComponent* GetInstancedStaticMeshComponent(); - - // Returns true if the instances have changed - bool HasInstancesChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const override; - -public: - - // Array of transform for this ISMC's instances - UPROPERTY() - TArray InstanceTransforms; -}; - - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USplineComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSplineComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // USplineComponent accessor - USplineComponent* GetSplineComponent(); - - // Returns true if the attached spline component has been modified - bool HasSplineComponentChanged(float fCurrentSplineResolution) const; - - // Returns true if the attached actor's (parent) transform has been modified - virtual bool HasActorTransformChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const; - - // Return true if the component itself has been modified - virtual bool HasComponentChanged() const; - -public: - - // Number of CVs used by the spline component, used to detect modification - UPROPERTY() - int32 NumberOfSplineControlPoints = -1; - - // Spline Length, used for fast detection of modifications of the spline.. - UPROPERTY() - float SplineLength = -1.0f; - - // Spline resolution used to generate the asset, used to detect setting modification - UPROPERTY() - float SplineResolution = -1.0f; - - // Is the spline closed? - UPROPERTY() - bool SplineClosed = false; - - // Transforms of each of the spline's control points - UPROPERTY() - TArray SplineControlPoints; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniSplineComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniSplineComponent : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - virtual void Update(UObject * InObject) override; - - virtual UObject* GetObject() const override; - - virtual void MarkChanged(const bool& bInChanged) override; - - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) override; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const override; - - // Indicates if this input needs to trigger an update - virtual bool NeedsToTriggerUpdate() const override; - - // UHoudiniSplineComponent accessor - UHoudiniSplineComponent* GetCurveComponent() const; - -public: - - // The type of curve (polygon, NURBS, bezier) - UPROPERTY() - EHoudiniCurveType CurveType = EHoudiniCurveType::Polygon; - - // The curve's method (CVs, Breakpoint, Freehand) - UPROPERTY() - EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::CVs; - - UPROPERTY() - bool Reversed = false; - - -protected: - - // NOTE: We are using this reference to the component since the component, for now, - // lives on the same actor as this input object. If we use a Soft Object Reference instead the editor - // will complain about breaking references everytime we try to delete the actor. - UPROPERTY(Instanced) - UHoudiniSplineComponent* CachedComponent; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UCameraComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputCameraComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // UCameraComponent accessor - UCameraComponent* GetCameraComponent(); - - // Return true if SMC's static mesh has been modified - virtual bool HasComponentChanged() const override; - -public: - - float FOV; - float AspectRatio; - - //TEnumAsByte ProjectionType; - bool bIsOrthographic; - - float OrthoWidth; - float OrthoNearClipPlane; - float OrthoFarClipPlane; - -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniAssetComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniAsset : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // UHoudiniAssetComponent accessor - UHoudiniAssetComponent* GetHoudiniAssetComponent(); -public: - - // The output index of the node that we want to use as input - UPROPERTY() - int32 AssetOutputIndex; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// AActor input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // - virtual bool HasActorTransformChanged(); - - // Return true if any content of this actor has possibly changed (for example geometry edits on a - // Brush or changes on procedurally generated content). - // NOTE: This is more generally applicable and could be moved to the HoudiniInputObject class. - virtual bool HasContentChanged() const; - - // AActor accessor - AActor* GetActor(); - -public: - - // The actor's components that can be sent as inputs - UPROPERTY() - TArray ActorComponents; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// ALandscapeProxy input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActor -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - virtual bool HasActorTransformChanged() override; - - // ALandscapeProxy accessor - ALandscapeProxy* GetLandscapeProxy(); - - void SetLandscapeProxy(UObject* InLandscapeProxy); - - // Used to restore an input landscape's transform to its original state - UPROPERTY() - FTransform CachedInputLandscapeTraqnsform; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// ABrush input -//----------------------------------------------------------------------------------------------------------------------------- -// Cache info for a brush in order to determine whether it has changed. - -#define BRUSH_HASH_SURFACE_PROPERTIES 0 - -//USTRUCT() -//struct FHoudiniBrushSurfaceInfo { -// GENERATED_BODY() -// -// FVector Base; -// FVector Normal; -// FVector TextureU; -// FVector TextureV; -// TSoftObjectPtr Material; -// -// FHoudiniBrushSurfaceInfo(const FVector& InBase, const FVector& InNormal, const FVector& InTextureU, const FVector& InTextureV, UMaterialInterface* InMaterial) -// : Base(InBase) -// , Normal(InNormal) -// , TextureU(InTextureU) -// , TextureV(InTextureV) -// , Material(InMaterial) -// { } -// -// inline bool operator==(const FHoudiniBrushSurfaceInfo& Other) { -// return Base.Equals(Other.Base) -// && Normal.Equals(Other.Normal) -// && TextureU.Equals(Other.TextureU) -// && TextureV.Equals(Other.TextureV) -// && Material.Get() == Other.Material.Get(); -// } -// -// inline bool operator==(const FPoly& Poly) { -// return Base.Equals(Poly.Base) -// && Normal.Equals(Poly.Normal) -// && TextureU.Equals(Poly.TextureU) -// && TextureV.Equals(Poly.TextureV) -// && Material.Get() == Poly.Material; -// } -//}; - -USTRUCT() -struct FHoudiniBrushInfo -{ - GENERATED_BODY() - - UPROPERTY() - TWeakObjectPtr BrushActor; - UPROPERTY() - FTransform CachedTransform; - UPROPERTY() - FVector CachedOrigin; - UPROPERTY() - FVector CachedExtent; - UPROPERTY() - TEnumAsByte CachedBrushType; - - UPROPERTY() - uint64 CachedSurfaceHash; - - bool HasChanged() const; - - static int32 GetNumVertexIndicesFromModel(const UModel* Model); - - FHoudiniBrushInfo(); - FHoudiniBrushInfo(ABrush* InBrushActor); - - template - inline void HashCombine(uint64& s, const T & v) const - { - std::hash h; - s^= h(v) + 0x9e3779b9 + (s<< 6) + (s>> 2); - } - - inline void HashCombine(uint64& s, const FVector & V) const - { - HashCombine(s, V.X); - HashCombine(s, V.Y); - HashCombine(s, V.Z); - } - - inline void CombinePolyHash(uint64& Hash, const FPoly& Poly) const - { - HashCombine(Hash, Poly.Base); - HashCombine(Hash, Poly.TextureU); - HashCombine(Hash, Poly.TextureV); - HashCombine(Hash, Poly.Normal); - HashCombine(Hash, (uint64)(Poly.Material)); - } -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor -{ - GENERATED_BODY() - -public: - - UHoudiniInputBrush(); - - // Factory function - static UHoudiniInputBrush* Create(UObject* InObject, UObject* InOuter, const FString& InName); - - //---------------------------------------------------------------------- - // UHoudiniInputActor Interface - Begin - //---------------------------------------------------------------------- - - virtual void Update(UObject * InObject) override; - - // Indicates if this input has changed and should be updated - virtual bool HasContentChanged() const override; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const override { return (!bIgnoreInputObject) && bHasChanged; }; - - // Indicates if this input has changed and should be updated - virtual bool HasTransformChanged() const override { return (!bIgnoreInputObject) && bTransformChanged; }; - - virtual bool HasActorTransformChanged() override; - - virtual bool NeedsToTriggerUpdate() const override { return (!bIgnoreInputObject) && bNeedsToTriggerUpdate; }; - - //---------------------------------------------------------------------- - // UHoudiniInputActor Interface - End - //---------------------------------------------------------------------- - - - // ABrush accessor - ABrush* GetBrush() const; - - UModel* GetCachedModel() const; - - // Check whether any of the brushes, or their transforms, used to generate this model have changed. - bool HasBrushesChanged(const TArray& InBrushes) const; - - // Cache the combined model as well as the input brushes. - void UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes); - - // Returns whether this input object should be ignored when uploading objects to Houdini. - // This mechanism could be implemented on UHoudiniInputObject. - bool ShouldIgnoreThisInput(); - - - // Find only the subtractive brush actors that intersect with the InputObject (Brush actor) bounding box but - // excluding any selector bounds actors. - static bool FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes); - -protected: - UPROPERTY() - TArray BrushesInfo; - - UPROPERTY(Transient, DuplicateTransient) - UModel* CombinedModel; - - UPROPERTY() - bool bIgnoreInputObject; - - UPROPERTY() - TEnumAsByte CachedInputBrushType; -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UDataTable input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputDataTable : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // DataTable accessor - class UDataTable* GetDataTable() const; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include + +#include "HoudiniSplineComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "CoreTypes.h" +#include "Materials/MaterialInterface.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" + +#include "Engine/Brush.h" +#include "Engine/Polys.h" +#include "UObject/SoftObjectPtr.h" + +#include "HoudiniInputObject.generated.h" + +class UStaticMesh; +class USkeletalMesh; +class USceneComponent; +class UStaticMeshComponent; +class UInstancedStaticMeshComponent; +class USplineComponent; +class UHoudiniAssetComponent; +class AActor; +class ALandscapeProxy; +class ABrush; +class UHoudiniInput; +class ALandscapeProxy; +class UModel; +class UHoudiniInput; +class UCameraComponent; + +UENUM() +enum class EHoudiniInputObjectType : uint8 +{ + Invalid, + + Object, + StaticMesh, + SkeletalMesh, + SceneComponent, + StaticMeshComponent, + InstancedStaticMeshComponent, + SplineComponent, + HoudiniSplineComponent, + HoudiniAssetComponent, + Actor, + Landscape, + Brush, + CameraComponent, + DataTable, + HoudiniAssetActor, +}; + +//----------------------------------------------------------------------------------------------------------------------------- +// UObjects input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + // Create the proper input object + static UHoudiniInputObject * CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InParamName); + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // Check whether two input objects match + virtual bool Matches(const UHoudiniInputObject& Other) const; + + // + static EHoudiniInputObjectType GetInputObjectTypeFromObject(UObject* InObject); + + // + virtual void Update(UObject * InObject); + + // Invalidate and ask for the deletion of this input object's node + virtual void InvalidateData(); + + // UObject accessor + virtual UObject* GetObject() const; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const { return bHasChanged; }; + + // Indicates if this input has changed and should be updated + virtual bool HasTransformChanged() const { return bTransformChanged; }; + + // Indicates if this input needs to trigger an update + virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; + + virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + void MarkTransformChanged(const bool& bInChanged) { bTransformChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + + void SetImportAsReference(const bool& bInImportAsRef) { bImportAsReference = bInImportAsRef; }; + bool GetImportAsReference() const { return bImportAsReference; }; + +#if WITH_EDITOR + void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; + bool IsUniformScaleLocked() const { return bUniformScaleLocked; }; + + void PostEditUndo() override; +#endif + + virtual UHoudiniInputObject* DuplicateAndCopyState(UObject* DestOuter); + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties); + + // Set whether this object can delete Houdini nodes. + virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } + + FGuid GetInputGuid() const { return Guid; } + + +protected: + + virtual void BeginDestroy() override; + +public: + + // The object referenced by this input + // This property should be protected. Don't access this directly. Use GetObject() / Update() instead. + UPROPERTY() + TSoftObjectPtr InputObject; + + // The object's transform/transform offset + UPROPERTY() + FTransform Transform; + + // The type of Object this input refers to + UPROPERTY() + EHoudiniInputObjectType Type; + + // This input object's "main" (SOP) NodeId + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputNodeId; + + // This input object's "container" (OBJ) NodeId + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputObjectNodeId; + + // Guid that uniquely identifies this input object. + // Also useful to correlate inputs between blueprint component templates and instances. + UPROPERTY(DuplicateTransient) + FGuid Guid; + +protected: + + // Indicates this input object has changed + UPROPERTY(DuplicateTransient) + bool bHasChanged; + + // Indicates this input object should trigger an input update/cook + UPROPERTY(DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Indicates that this input transform should be updated + UPROPERTY(DuplicateTransient) + bool bTransformChanged; + + UPROPERTY() + bool bImportAsReference; + + // Indicates if change the scale of Transfrom Offset of this object uniformly +#if WITH_EDITORONLY_DATA + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + bool bUniformScaleLocked; +#endif + + UPROPERTY() + bool bCanDeleteHoudiniNodes; +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UStaticMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputStaticMesh : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // UHoudiniInputObject overrides + + // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; + virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) override; + virtual void InvalidateData() override; + + // + virtual void Update(UObject * InObject) override; + + // Nothing to add for Static Meshes? + + // StaticMesh accessor + class UStaticMesh* GetStaticMesh(); + + // Blueprint accessor + class UBlueprint* GetBlueprint(); + + // Check if this SM Input object is passed in as a BP + bool bIsBlueprint() const; + + // The Blueprint's Static Meshe Components that can be sent as inputs + UPROPERTY() + TArray BlueprintStaticMeshes; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USkeletalMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSkeletalMesh : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // Nothing to add for SkeletalMesh Meshes? + + // StaticMesh accessor + class USkeletalMesh* GetSkeletalMesh(); +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USceneComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSceneComponent : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // SceneComponent accessor + class USceneComponent* GetSceneComponent(); + + // Returns true if the attached actor's (parent) transform has been modified + virtual bool HasActorTransformChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const; + + // Return true if the component itself has been modified + virtual bool HasComponentChanged() const; + +public: + + // This component's parent Actor transform + UPROPERTY() + FTransform ActorTransform = FTransform::Identity; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UStaticMeshComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputMeshComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // StaticMeshComponent accessor + UStaticMeshComponent* GetStaticMeshComponent(); + + // Get the referenced StaticMesh + UStaticMesh* GetStaticMesh(); + + // Returns true if the attached component's materials have been modified + bool HasComponentMaterialsChanged() const; + + // Return true if SMC's static mesh has been modified + virtual bool HasComponentChanged() const override; + +public: + + // Keep track of the selected Static Mesh + UPROPERTY() + TSoftObjectPtr StaticMesh = nullptr; + + // Path to the materials assigned on the SMC + UPROPERTY() + TArray MeshComponentsMaterials; +}; + + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UInstancedStaticMeshComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputInstancedMeshComponent : public UHoudiniInputMeshComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // InstancedStaticMeshComponent accessor + UInstancedStaticMeshComponent* GetInstancedStaticMeshComponent(); + + // Returns true if the instances have changed + bool HasInstancesChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const override; + +public: + + // Array of transform for this ISMC's instances + UPROPERTY() + TArray InstanceTransforms; +}; + + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USplineComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSplineComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // USplineComponent accessor + USplineComponent* GetSplineComponent(); + + // Returns true if the attached spline component has been modified + bool HasSplineComponentChanged(float fCurrentSplineResolution) const; + + // Returns true if the attached actor's (parent) transform has been modified + virtual bool HasActorTransformChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const; + + // Return true if the component itself has been modified + virtual bool HasComponentChanged() const; + +public: + + // Number of CVs used by the spline component, used to detect modification + UPROPERTY() + int32 NumberOfSplineControlPoints = -1; + + // Spline Length, used for fast detection of modifications of the spline.. + UPROPERTY() + float SplineLength = -1.0f; + + // Spline resolution used to generate the asset, used to detect setting modification + UPROPERTY() + float SplineResolution = -1.0f; + + // Is the spline closed? + UPROPERTY() + bool SplineClosed = false; + + // Transforms of each of the spline's control points + UPROPERTY() + TArray SplineControlPoints; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniSplineComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniSplineComponent : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + virtual void Update(UObject * InObject) override; + + virtual UObject* GetObject() const override; + + virtual void MarkChanged(const bool& bInChanged) override; + + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) override; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const override; + + // Indicates if this input needs to trigger an update + virtual bool NeedsToTriggerUpdate() const override; + + // UHoudiniSplineComponent accessor + UHoudiniSplineComponent* GetCurveComponent() const; + +public: + + // The type of curve (polygon, NURBS, bezier) + UPROPERTY() + EHoudiniCurveType CurveType = EHoudiniCurveType::Polygon; + + // The curve's method (CVs, Breakpoint, Freehand) + UPROPERTY() + EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::CVs; + + UPROPERTY() + bool Reversed = false; + + +protected: + + // NOTE: We are using this reference to the component since the component, for now, + // lives on the same actor as this input object. If we use a Soft Object Reference instead the editor + // will complain about breaking references everytime we try to delete the actor. + UPROPERTY(Instanced) + UHoudiniSplineComponent* CachedComponent; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UCameraComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputCameraComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // UCameraComponent accessor + UCameraComponent* GetCameraComponent(); + + // Return true if SMC's static mesh has been modified + virtual bool HasComponentChanged() const override; + +public: + + float FOV; + float AspectRatio; + + //TEnumAsByte ProjectionType; + bool bIsOrthographic; + + float OrthoWidth; + float OrthoNearClipPlane; + float OrthoFarClipPlane; + +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniAssetComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniAsset : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // UHoudiniAssetComponent accessor + UHoudiniAssetComponent* GetHoudiniAssetComponent(); +public: + + // The output index of the node that we want to use as input + UPROPERTY() + int32 AssetOutputIndex; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// AActor input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // + virtual bool HasActorTransformChanged(); + + // Return true if any content of this actor has possibly changed (for example geometry edits on a + // Brush or changes on procedurally generated content). + // NOTE: This is more generally applicable and could be moved to the HoudiniInputObject class. + virtual bool HasContentChanged() const; + + // AActor accessor + AActor* GetActor(); + +public: + + // The actor's components that can be sent as inputs + UPROPERTY() + TArray ActorComponents; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// ALandscapeProxy input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActor +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + virtual bool HasActorTransformChanged() override; + + // ALandscapeProxy accessor + ALandscapeProxy* GetLandscapeProxy(); + + void SetLandscapeProxy(UObject* InLandscapeProxy); + + // Used to restore an input landscape's transform to its original state + UPROPERTY() + FTransform CachedInputLandscapeTraqnsform; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// ABrush input +//----------------------------------------------------------------------------------------------------------------------------- +// Cache info for a brush in order to determine whether it has changed. + +#define BRUSH_HASH_SURFACE_PROPERTIES 0 + +//USTRUCT() +//struct FHoudiniBrushSurfaceInfo { +// GENERATED_BODY() +// +// FVector Base; +// FVector Normal; +// FVector TextureU; +// FVector TextureV; +// TSoftObjectPtr Material; +// +// FHoudiniBrushSurfaceInfo(const FVector& InBase, const FVector& InNormal, const FVector& InTextureU, const FVector& InTextureV, UMaterialInterface* InMaterial) +// : Base(InBase) +// , Normal(InNormal) +// , TextureU(InTextureU) +// , TextureV(InTextureV) +// , Material(InMaterial) +// { } +// +// inline bool operator==(const FHoudiniBrushSurfaceInfo& Other) { +// return Base.Equals(Other.Base) +// && Normal.Equals(Other.Normal) +// && TextureU.Equals(Other.TextureU) +// && TextureV.Equals(Other.TextureV) +// && Material.Get() == Other.Material.Get(); +// } +// +// inline bool operator==(const FPoly& Poly) { +// return Base.Equals(Poly.Base) +// && Normal.Equals(Poly.Normal) +// && TextureU.Equals(Poly.TextureU) +// && TextureV.Equals(Poly.TextureV) +// && Material.Get() == Poly.Material; +// } +//}; + +USTRUCT() +struct FHoudiniBrushInfo +{ + GENERATED_BODY() + + UPROPERTY() + TWeakObjectPtr BrushActor; + UPROPERTY() + FTransform CachedTransform; + UPROPERTY() + FVector CachedOrigin; + UPROPERTY() + FVector CachedExtent; + UPROPERTY() + TEnumAsByte CachedBrushType; + + UPROPERTY() + uint64 CachedSurfaceHash; + + bool HasChanged() const; + + static int32 GetNumVertexIndicesFromModel(const UModel* Model); + + FHoudiniBrushInfo(); + FHoudiniBrushInfo(ABrush* InBrushActor); + + template + inline void HashCombine(uint64& s, const T & v) const + { + std::hash h; + s^= h(v) + 0x9e3779b9 + (s<< 6) + (s>> 2); + } + + inline void HashCombine(uint64& s, const FVector & V) const + { + HashCombine(s, V.X); + HashCombine(s, V.Y); + HashCombine(s, V.Z); + } + + inline void CombinePolyHash(uint64& Hash, const FPoly& Poly) const + { + HashCombine(Hash, Poly.Base); + HashCombine(Hash, Poly.TextureU); + HashCombine(Hash, Poly.TextureV); + HashCombine(Hash, Poly.Normal); + HashCombine(Hash, (uint64)(Poly.Material)); + } +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor +{ + GENERATED_BODY() + +public: + + UHoudiniInputBrush(); + + // Factory function + static UHoudiniInputBrush* Create(UObject* InObject, UObject* InOuter, const FString& InName); + + //---------------------------------------------------------------------- + // UHoudiniInputActor Interface - Begin + //---------------------------------------------------------------------- + + virtual void Update(UObject * InObject) override; + + // Indicates if this input has changed and should be updated + virtual bool HasContentChanged() const override; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const override { return (!bIgnoreInputObject) && bHasChanged; }; + + // Indicates if this input has changed and should be updated + virtual bool HasTransformChanged() const override { return (!bIgnoreInputObject) && bTransformChanged; }; + + virtual bool HasActorTransformChanged() override; + + virtual bool NeedsToTriggerUpdate() const override { return (!bIgnoreInputObject) && bNeedsToTriggerUpdate; }; + + //---------------------------------------------------------------------- + // UHoudiniInputActor Interface - End + //---------------------------------------------------------------------- + + + // ABrush accessor + ABrush* GetBrush() const; + + UModel* GetCachedModel() const; + + // Check whether any of the brushes, or their transforms, used to generate this model have changed. + bool HasBrushesChanged(const TArray& InBrushes) const; + + // Cache the combined model as well as the input brushes. + void UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes); + + // Returns whether this input object should be ignored when uploading objects to Houdini. + // This mechanism could be implemented on UHoudiniInputObject. + bool ShouldIgnoreThisInput(); + + + // Find only the subtractive brush actors that intersect with the InputObject (Brush actor) bounding box but + // excluding any selector bounds actors. + static bool FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes); + +protected: + UPROPERTY() + TArray BrushesInfo; + + UPROPERTY(Transient, DuplicateTransient) + UModel* CombinedModel; + + UPROPERTY() + bool bIgnoreInputObject; + + UPROPERTY() + TEnumAsByte CachedInputBrushType; +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UDataTable input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputDataTable : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // DataTable accessor + class UDataTable* GetDataTable() const; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h index 3c17896d1..9632a6b57 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h @@ -1,68 +1,68 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -UENUM() -enum class EHoudiniInputType : uint8 -{ - Invalid, - - Geometry, - Curve, - Asset, - Landscape, - World, - Skeletal, - -}; -// Maintain an iterable list of houdini input types -static const EHoudiniInputType HoudiniInputTypeList[] = { - EHoudiniInputType::Geometry, - EHoudiniInputType::Curve, - EHoudiniInputType::Asset, - EHoudiniInputType::Landscape, - EHoudiniInputType::World, - EHoudiniInputType::Skeletal }; - -UENUM() -enum class EHoudiniXformType : uint8 -{ - None, - IntoThisObject, - Auto -}; - -UENUM() -enum class EHoudiniLandscapeExportType : uint8 -{ - Heightfield, - Mesh, - Points +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +UENUM() +enum class EHoudiniInputType : uint8 +{ + Invalid, + + Geometry, + Curve, + Asset, + Landscape, + World, + Skeletal, + +}; +// Maintain an iterable list of houdini input types +static const EHoudiniInputType HoudiniInputTypeList[] = { + EHoudiniInputType::Geometry, + EHoudiniInputType::Curve, + EHoudiniInputType::Asset, + EHoudiniInputType::Landscape, + EHoudiniInputType::World, + EHoudiniInputType::Skeletal }; + +UENUM() +enum class EHoudiniXformType : uint8 +{ + None, + IntoThisObject, + Auto +}; + +UENUM() +enum class EHoudiniLandscapeExportType : uint8 +{ + Heightfield, + Mesh, + Points }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp index f6c6f1eed..f0fced9b7 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp @@ -1,245 +1,248 @@ -/* -* 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 "HoudiniInstancedActorComponent.h" - -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniInstancedActorComponent::UHoudiniInstancedActorComponent( const FObjectInitializer& ObjectInitializer ) -: Super( ObjectInitializer ) -, InstancedObject( nullptr ) -{ - // - // Set default component properties. - // - Mobility = EComponentMobility::Static; - bCanEverAffectNavigation = true; - bNeverNeedsRenderUpdate = false; - Bounds = FBox(ForceInitToZero); -} - - -void -UHoudiniInstancedActorComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on the setting value - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniInstancedActorComponent_V1* CompatibilityIAC = NewObject(); - CompatibilityIAC->Serialize(Ar); - CompatibilityIAC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - - -void UHoudiniInstancedActorComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) -{ - ClearAllInstances(); - Super::OnComponentDestroyed( bDestroyingHierarchy ); -} - - -void -UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector ) -{ - UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); - if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) - { - if ( ThisHIAC->InstancedObject && !ThisHIAC->InstancedObject->IsPendingKill() ) - Collector.AddReferencedObject( ThisHIAC->InstancedObject, ThisHIAC ); - - Collector.AddReferencedObjects(ThisHIAC->InstancedActors, ThisHIAC ); - } -} - - -int32 -UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform, AActor * NewActor) -{ - if (!NewActor || NewActor->IsPendingKill()) - return -1; - - NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - NewActor->SetActorRelativeTransform(InstanceTransform); - return InstancedActors.Add(NewActor); -} - - -bool -UHoudiniInstancedActorComponent::SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor) -{ - if (!NewActor || NewActor->IsPendingKill()) - return false; - - if (!InstancedActors.IsValidIndex(Idx)) - return false; - - NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - NewActor->SetActorRelativeTransform(InstanceTransform); - NewActor->RegisterAllComponents(); - InstancedActors[Idx] = NewActor; - - return true; -} - - -bool -UHoudiniInstancedActorComponent::SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform) -{ - if (!InstancedActors.IsValidIndex(Idx)) - return false; - - InstancedActors[Idx]->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - InstancedActors[Idx]->SetActorRelativeTransform(InstanceTransform); - - return true; -} - - -void -UHoudiniInstancedActorComponent::ClearAllInstances() -{ - for ( AActor* Instance : InstancedActors ) - { - if ( Instance && !Instance->IsPendingKill() ) - Instance->Destroy(); - } - InstancedActors.Empty(); -} - - -void -UHoudiniInstancedActorComponent::SetNumberOfInstances(const int32& NewInstanceNum) -{ - int32 OldInstanceNum = InstancedActors.Num(); - - // If we want less instances than we already have, destroy the extra properly - if (NewInstanceNum < OldInstanceNum) - { - for (int32 Idx = NewInstanceNum - 1; Idx < InstancedActors.Num(); Idx++) - { - AActor* Instance = InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; - if (Instance && !Instance->IsPendingKill()) - Instance->Destroy(); - } - } - - // Grow the array with nulls if needed - InstancedActors.SetNumZeroed(NewInstanceNum); -} - - -void -UHoudiniInstancedActorComponent::OnComponentCreated() -{ - Super::OnComponentCreated(); - - // If our instances are parented to another actor we should duplicate them - bool bNeedDuplicate = false; - for (auto CurrentInstance : InstancedActors) - { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) - continue; - - if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) - bNeedDuplicate = true; - } - - if ( !bNeedDuplicate ) - return; - - // TODO: CHECK ME! - // We need to duplicate our instances - TArray SourceInstances = InstancedActors; - InstancedActors.Empty(); - for (AActor* CurrentInstance : SourceInstances) - { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) - continue; - - FTransform InstanceTransform; - if ( CurrentInstance->GetRootComponent() ) - InstanceTransform = CurrentInstance->GetRootComponent()->GetRelativeTransform(); - - // AddInstance( InstanceTransform ); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInstancedActorComponent.h" + +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniInstancedActorComponent::UHoudiniInstancedActorComponent( const FObjectInitializer& ObjectInitializer ) +: Super( ObjectInitializer ) +, InstancedObject( nullptr ) +{ + // + // Set default component properties. + // + Mobility = EComponentMobility::Static; + bCanEverAffectNavigation = true; + bNeverNeedsRenderUpdate = false; + Bounds = FBox(ForceInitToZero); +} + + +void +UHoudiniInstancedActorComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on the setting value + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniInstancedActorComponent_V1* CompatibilityIAC = NewObject(); + CompatibilityIAC->Serialize(Ar); + CompatibilityIAC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + + +void UHoudiniInstancedActorComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + ClearAllInstances(); + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + + +void +UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); + if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) + { + if ( ThisHIAC->InstancedObject && !ThisHIAC->InstancedObject->IsPendingKill() ) + Collector.AddReferencedObject( ThisHIAC->InstancedObject, ThisHIAC ); + + Collector.AddReferencedObjects(ThisHIAC->InstancedActors, ThisHIAC ); + } +} + + +int32 +UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform, AActor * NewActor) +{ + if (!NewActor || NewActor->IsPendingKill()) + return -1; + + NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + NewActor->SetActorRelativeTransform(InstanceTransform); + return InstancedActors.Add(NewActor); +} + + +bool +UHoudiniInstancedActorComponent::SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor) +{ + if (!NewActor || NewActor->IsPendingKill()) + return false; + + if (!InstancedActors.IsValidIndex(Idx)) + return false; + + NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + NewActor->SetActorRelativeTransform(InstanceTransform); + NewActor->RegisterAllComponents(); + InstancedActors[Idx] = NewActor; + + return true; +} + + +bool +UHoudiniInstancedActorComponent::SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform) +{ + if (!InstancedActors.IsValidIndex(Idx)) + return false; + + InstancedActors[Idx]->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + InstancedActors[Idx]->SetActorRelativeTransform(InstanceTransform); + + return true; +} + + +void +UHoudiniInstancedActorComponent::ClearAllInstances() +{ + for ( AActor* Instance : InstancedActors ) + { + if ( Instance && !Instance->IsPendingKill() ) + Instance->Destroy(); + } + InstancedActors.Empty(); +} + + +void +UHoudiniInstancedActorComponent::SetNumberOfInstances(const int32& NewInstanceNum) +{ + int32 OldInstanceNum = InstancedActors.Num(); + + // If we want less instances than we already have, destroy the extra properly + if (NewInstanceNum < OldInstanceNum) + { + for (int32 Idx = NewInstanceNum - 1; Idx < InstancedActors.Num(); Idx++) + { + AActor* Instance = InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; + if (Instance && !Instance->IsPendingKill()) + Instance->Destroy(); + } + } + + // Grow the array with nulls if needed + InstancedActors.SetNumZeroed(NewInstanceNum); +} + + +void +UHoudiniInstancedActorComponent::OnComponentCreated() +{ + Super::OnComponentCreated(); + + // If our instances are parented to another actor we should duplicate them + bool bNeedDuplicate = false; + for (auto CurrentInstance : InstancedActors) + { + if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + continue; + + if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) + bNeedDuplicate = true; + } + + if ( !bNeedDuplicate ) + return; + + // TODO: CHECK ME! + // We need to duplicate our instances + TArray SourceInstances = InstancedActors; + InstancedActors.Empty(); + for (AActor* CurrentInstance : SourceInstances) + { + if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + continue; + + FTransform InstanceTransform; + if ( CurrentInstance->GetRootComponent() ) + InstanceTransform = CurrentInstance->GetRootComponent()->GetRelativeTransform(); + + // AddInstance( InstanceTransform ); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h index 8ab2b8cae..32a61b696 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h @@ -1,91 +1,91 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" - -#include "HoudiniInstancedActorComponent.generated.h" - - -UCLASS()//( config = Engine ) -class HOUDINIENGINERUNTIME_API UHoudiniInstancedActorComponent : public USceneComponent -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniInstancedActorComponent_V1; - - public: - - virtual void Serialize(FArchive & Ar) override; - - virtual void OnComponentCreated() override; - virtual void OnComponentDestroyed( bool bDestroyingHierarchy ) override; - - static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); - - // Object mutator - void SetInstancedObject(class UObject* InObject) { InstancedObject = InObject; } - // Object accessor - class UObject* GetInstancedObject() const { return InstancedObject; } - - - // Instance Accessor - TArray& GetInstancedActorsForWrite() { return InstancedActors; } - // const Instance accessor - const TArray& GetInstancedActors() const { return InstancedActors; } - - // Returns the instanced actor at a given index - AActor* GetInstancedActorAt(const int32& Idx) { return InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; } - - // Add an instance to this component. Transform is given in local space of this component. - int32 AddInstance(const FTransform& InstanceTransform, AActor * NewActor); - - // Sets the instance at a given index in this component. Transform is given in local space of this component. - bool SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor); - - // Updates the transform for a given actor. Transform is given in local space of this component. - bool SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform); - - // Destroy all existing instances - void ClearAllInstances(); - - // Sets the number of instances needed - // Properly deletes extras, new instance actors are nulled - void SetNumberOfInstances(const int32& NewInstanceNum); - - // Set the instances. Transforms are given in local space of this component. - bool SetInstanceTransforms(const TArray& InstanceTransforms); - - private: - - UPROPERTY(VisibleAnywhere, Category = Instances ) - UObject* InstancedObject; - - UPROPERTY(VisibleInstanceOnly, Category = Instances ) - TArray InstancedActors; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" + +#include "HoudiniInstancedActorComponent.generated.h" + + +UCLASS()//( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniInstancedActorComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniInstancedActorComponent_V1; + + public: + + virtual void Serialize(FArchive & Ar) override; + + virtual void OnComponentCreated() override; + virtual void OnComponentDestroyed( bool bDestroyingHierarchy ) override; + + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + + // Object mutator + void SetInstancedObject(class UObject* InObject) { InstancedObject = InObject; } + // Object accessor + class UObject* GetInstancedObject() const { return InstancedObject; } + + + // Instance Accessor + TArray& GetInstancedActorsForWrite() { return InstancedActors; } + // const Instance accessor + const TArray& GetInstancedActors() const { return InstancedActors; } + + // Returns the instanced actor at a given index + AActor* GetInstancedActorAt(const int32& Idx) { return InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; } + + // Add an instance to this component. Transform is given in local space of this component. + int32 AddInstance(const FTransform& InstanceTransform, AActor * NewActor); + + // Sets the instance at a given index in this component. Transform is given in local space of this component. + bool SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor); + + // Updates the transform for a given actor. Transform is given in local space of this component. + bool SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform); + + // Destroy all existing instances + void ClearAllInstances(); + + // Sets the number of instances needed + // Properly deletes extras, new instance actors are nulled + void SetNumberOfInstances(const int32& NewInstanceNum); + + // Set the instances. Transforms are given in local space of this component. + bool SetInstanceTransforms(const TArray& InstanceTransforms); + + private: + + UPROPERTY(VisibleAnywhere, Category = Instances ) + UObject* InstancedObject; + + UPROPERTY(VisibleInstanceOnly, Category = Instances ) + TArray InstancedActors; + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp deleted file mode 100644 index 8a63839cc..000000000 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h b/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h deleted file mode 100644 index 2853e8148..000000000 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMaterialMap.h +++ /dev/null @@ -1,28 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp index 5c10bd98a..dbb9fda15 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp @@ -1,244 +1,247 @@ -/* -* 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 "HoudiniMeshSplitInstancerComponent.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -#include "Components/StaticMeshComponent.h" - -/* -#if WITH_EDITOR - #include "ScopedTransaction.h" - #include "LevelEditorViewport.h" - #include "MeshPaintHelpers.h" -#endif -*/ - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniMeshSplitInstancerComponent::UHoudiniMeshSplitInstancerComponent(const FObjectInitializer& ObjectInitializer) - : Super( ObjectInitializer ) - , InstancedMesh( nullptr ) -{ -} - -void -UHoudiniMeshSplitInstancerComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on the setting value - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniMeshSplitInstancerComponent_V1* CompatibilityMSIC = NewObject(); - CompatibilityMSIC->Serialize(Ar); - CompatibilityMSIC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -void -UHoudiniMeshSplitInstancerComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) -{ - ClearInstances(0); - Super::OnComponentDestroyed( bDestroyingHierarchy ); -} - - -void -UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) -{ - UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); - if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) - { - Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); - for(auto& Mat : ThisMSIC->OverrideMaterials) - Collector.AddReferencedObject(Mat, ThisMSIC); - Collector.AddReferencedObjects(ThisMSIC->Instances, ThisMSIC); - } -} - -bool -UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( - const TArray& InstanceTransforms) -{ - if (Instances.Num() <= 0 && InstanceTransforms.Num() <= 0) - return false; - - if (!GetOwner() || GetOwner()->IsPendingKill()) - return false; - - // Destroy previous instances while keeping some of the one that we'll be able to reuse - ClearInstances(InstanceTransforms.Num()); - - // - if( !InstancedMesh || InstancedMesh->IsPendingKill() ) - { - HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); - return false; - } - - // Only create new SMC for newly added instances - for (int32 iAdd = Instances.Num(); iAdd < InstanceTransforms.Num(); iAdd++) - { - const FTransform& InstanceTransform = InstanceTransforms[iAdd]; - UStaticMeshComponent* SMC = NewObject< UStaticMeshComponent >( - GetOwner(), UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - SMC->SetRelativeTransform(InstanceTransform); - Instances.Add(SMC); - GetOwner()->AddInstanceComponent(SMC); - } - - // We should now have the same number of instances than transform - ensure(InstanceTransforms.Num() == Instances.Num()); - if (InstanceTransforms.Num() != Instances.Num()) - return false; - - for (int32 iIns = 0; iIns < Instances.Num(); ++iIns) - { - UStaticMeshComponent* SMC = Instances[iIns]; - const FTransform& InstanceTransform = InstanceTransforms[iIns]; - - if (!SMC || SMC->IsPendingKill()) - continue; - - SMC->SetRelativeTransform(InstanceTransform); - - // Attach created static mesh component to this thing - SMC->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - - SMC->SetStaticMesh(InstancedMesh); - SMC->SetVisibility(IsVisible()); - SMC->SetMobility(Mobility); - - // TODO: Revert to default if override is null?? - UMaterialInterface* MI = nullptr; - if (OverrideMaterials.Num() > 0) - { - if (OverrideMaterials.IsValidIndex(iIns)) - MI = OverrideMaterials[iIns]; - else - MI = OverrideMaterials[0]; - } - - if (MI && !MI->IsPendingKill()) - { - int32 MeshMaterialCount = InstancedMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - SMC->SetMaterial(Idx, MI); - } - - SMC->RegisterComponent(); - - /* - // TODO: - // Properties not being propagated to newly created UStaticMeshComponents - if (UHoudiniAssetComponent * pHoudiniAsset = Cast(GetAttachParent())) - { - pHoudiniAsset->CopyComponentPropertiesTo(SMC); - } - */ - } - - return true; -} - -void -UHoudiniMeshSplitInstancerComponent::ClearInstances(int32 NumToKeep) -{ - if (NumToKeep <= 0) - { - for (auto&& Instance : Instances) - { - if (Instance) - { - Instance->ConditionalBeginDestroy(); - } - } - Instances.Empty(); - } - else if (NumToKeep > 0 && NumToKeep < Instances.Num()) - { - for (int32 i = NumToKeep; i < Instances.Num(); ++i) - { - UStaticMeshComponent * Instance = Instances[i]; - if (Instance) - { - Instance->ConditionalBeginDestroy(); - } - } - Instances.SetNum(NumToKeep); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniMeshSplitInstancerComponent.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +#include "Components/StaticMeshComponent.h" + +/* +#if WITH_EDITOR + #include "ScopedTransaction.h" + #include "LevelEditorViewport.h" + #include "MeshPaintHelpers.h" +#endif +*/ + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniMeshSplitInstancerComponent::UHoudiniMeshSplitInstancerComponent(const FObjectInitializer& ObjectInitializer) + : Super( ObjectInitializer ) + , InstancedMesh( nullptr ) +{ +} + +void +UHoudiniMeshSplitInstancerComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on the setting value + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniMeshSplitInstancerComponent_V1* CompatibilityMSIC = NewObject(); + CompatibilityMSIC->Serialize(Ar); + CompatibilityMSIC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +void +UHoudiniMeshSplitInstancerComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + ClearInstances(0); + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + + +void +UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); + if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) + { + Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); + for(auto& Mat : ThisMSIC->OverrideMaterials) + Collector.AddReferencedObject(Mat, ThisMSIC); + Collector.AddReferencedObjects(ThisMSIC->Instances, ThisMSIC); + } +} + +bool +UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( + const TArray& InstanceTransforms) +{ + if (Instances.Num() <= 0 && InstanceTransforms.Num() <= 0) + return false; + + if (!GetOwner() || GetOwner()->IsPendingKill()) + return false; + + // Destroy previous instances while keeping some of the one that we'll be able to reuse + ClearInstances(InstanceTransforms.Num()); + + // + if( !InstancedMesh || InstancedMesh->IsPendingKill() ) + { + HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); + return false; + } + + // Only create new SMC for newly added instances + for (int32 iAdd = Instances.Num(); iAdd < InstanceTransforms.Num(); iAdd++) + { + const FTransform& InstanceTransform = InstanceTransforms[iAdd]; + UStaticMeshComponent* SMC = NewObject< UStaticMeshComponent >( + GetOwner(), UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + SMC->SetRelativeTransform(InstanceTransform); + Instances.Add(SMC); + GetOwner()->AddInstanceComponent(SMC); + } + + // We should now have the same number of instances than transform + ensure(InstanceTransforms.Num() == Instances.Num()); + if (InstanceTransforms.Num() != Instances.Num()) + return false; + + for (int32 iIns = 0; iIns < Instances.Num(); ++iIns) + { + UStaticMeshComponent* SMC = Instances[iIns]; + const FTransform& InstanceTransform = InstanceTransforms[iIns]; + + if (!SMC || SMC->IsPendingKill()) + continue; + + SMC->SetRelativeTransform(InstanceTransform); + + // Attach created static mesh component to this thing + SMC->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + SMC->SetStaticMesh(InstancedMesh); + SMC->SetVisibility(IsVisible()); + SMC->SetMobility(Mobility); + + // TODO: Revert to default if override is null?? + UMaterialInterface* MI = nullptr; + if (OverrideMaterials.Num() > 0) + { + if (OverrideMaterials.IsValidIndex(iIns)) + MI = OverrideMaterials[iIns]; + else + MI = OverrideMaterials[0]; + } + + if (MI && !MI->IsPendingKill()) + { + int32 MeshMaterialCount = InstancedMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + SMC->SetMaterial(Idx, MI); + } + + SMC->RegisterComponent(); + + /* + // TODO: + // Properties not being propagated to newly created UStaticMeshComponents + if (UHoudiniAssetComponent * pHoudiniAsset = Cast(GetAttachParent())) + { + pHoudiniAsset->CopyComponentPropertiesTo(SMC); + } + */ + } + + return true; +} + +void +UHoudiniMeshSplitInstancerComponent::ClearInstances(int32 NumToKeep) +{ + if (NumToKeep <= 0) + { + for (auto&& Instance : Instances) + { + if (Instance) + { + Instance->ConditionalBeginDestroy(); + } + } + Instances.Empty(); + } + else if (NumToKeep > 0 && NumToKeep < Instances.Num()) + { + for (int32 i = NumToKeep; i < Instances.Num(); ++i) + { + UStaticMeshComponent * Instance = Instances[i]; + if (Instance) + { + Instance->ConditionalBeginDestroy(); + } + } + Instances.SetNum(NumToKeep); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h index 263b210ba..835546667 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h @@ -1,85 +1,85 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" -#include "Engine/StaticMesh.h" -#include "Materials/MaterialInterface.h" -#include "HoudiniMeshSplitInstancerComponent.generated.h" - -/** -* UHoudiniMeshSplitInstancerComponent is used to manage a single static mesh being -* 'instanced' multiple times by multiple UStaticMeshComponents. This is as opposed to the -* UInstancedStaticMeshComponent wherein a single mesh is instanced multiple times by one component. -*/ - -UCLASS()//( config = Engine ) -class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USceneComponent -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniMeshSplitInstancerComponent_V1; - - public: - - virtual void Serialize(FArchive & Ar) override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - - static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); - - // Static Mesh mutator - void SetStaticMesh(class UStaticMesh* StaticMesh) { InstancedMesh = StaticMesh; } - - // Static mesh accessor - class UStaticMesh* GetStaticMesh() const { return InstancedMesh; } - - // Overide material mutator - void SetOverrideMaterials(const TArray& InMaterialOverrides) { OverrideMaterials = InMaterialOverrides; } - - // Destroy existing instances, keeping a given number of them to be reused - void ClearInstances(int32 NumToKeep); - - // Set the instances. Transforms are given in local space of this component. - bool SetInstanceTransforms(const TArray& InstanceTransforms); - - // Instance Accessor - TArray& GetInstancesForWrite() { return Instances; } - // const Instance accessor - const TArray& GetInstances() const { return Instances; } - - private: - - UPROPERTY(VisibleInstanceOnly, Category = Instances) - TArray Instances; - - UPROPERTY(VisibleInstanceOnly, Category = Instances) - TArray OverrideMaterials; - - UPROPERTY(VisibleAnywhere, Category = Instances) - class UStaticMesh* InstancedMesh; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" +#include "Engine/StaticMesh.h" +#include "Materials/MaterialInterface.h" +#include "HoudiniMeshSplitInstancerComponent.generated.h" + +/** +* UHoudiniMeshSplitInstancerComponent is used to manage a single static mesh being +* 'instanced' multiple times by multiple UStaticMeshComponents. This is as opposed to the +* UInstancedStaticMeshComponent wherein a single mesh is instanced multiple times by one component. +*/ + +UCLASS()//( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniMeshSplitInstancerComponent_V1; + + public: + + virtual void Serialize(FArchive & Ar) override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); + + // Static Mesh mutator + void SetStaticMesh(class UStaticMesh* StaticMesh) { InstancedMesh = StaticMesh; } + + // Static mesh accessor + class UStaticMesh* GetStaticMesh() const { return InstancedMesh; } + + // Overide material mutator + void SetOverrideMaterials(const TArray& InMaterialOverrides) { OverrideMaterials = InMaterialOverrides; } + + // Destroy existing instances, keeping a given number of them to be reused + void ClearInstances(int32 NumToKeep); + + // Set the instances. Transforms are given in local space of this component. + bool SetInstanceTransforms(const TArray& InstanceTransforms); + + // Instance Accessor + TArray& GetInstancesForWrite() { return Instances; } + // const Instance accessor + const TArray& GetInstances() const { return Instances; } + + private: + + UPROPERTY(VisibleInstanceOnly, Category = Instances) + TArray Instances; + + UPROPERTY(VisibleInstanceOnly, Category = Instances) + TArray OverrideMaterials; + + UPROPERTY(VisibleAnywhere, Category = Instances) + class UStaticMesh* InstancedMesh; +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp index 59dadef63..520ca1c07 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp @@ -1,858 +1,857 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniOutput.h" -#include "HoudiniAssetComponent.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniSplineComponent.h" - -#include "Components/SceneComponent.h" -#include "Components/MeshComponent.h" -#include "Components/SplineComponent.h" -#include "Misc/StringFormatArg.h" -#include "Engine/Engine.h" - -UHoudiniLandscapePtr::UHoudiniLandscapePtr(class FObjectInitializer const& Initializer) -{ - // bIsWorldCompositionLandscape = false; - // BakeType = EHoudiniLandscapeOutputBakeType::Detachment; -}; - -uint32 -GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier) -{ - return HoudiniOutputObjectIdentifier.GetTypeHash(); -} - -void -FHoudiniInstancedOutput::SetVariationObjectAt(const int32& AtIndex, UObject* InObject) -{ - // Resize the array if needed - if (VariationObjects.Num() <= AtIndex) - VariationObjects.SetNum(AtIndex + 1); - - if (VariationTransformOffsets.Num() <= AtIndex) - VariationTransformOffsets.SetNum(AtIndex + 1); - - UObject* CurrentObject = VariationObjects[AtIndex].LoadSynchronous(); - if (CurrentObject == InObject) - return; - - VariationObjects[AtIndex] = InObject; -} - -bool -FHoudiniInstancedOutput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; - if (!Transform) - return false; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - if (Position[XYZIndex] == Value) - return false; - Position[XYZIndex] = Value; - Transform->SetLocation(Position); - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - if (Rotator.Roll == Value) - return false; - Rotator.Roll = Value; - break; - } - - case 1: - { - if (Rotator.Pitch == Value) - return false; - Rotator.Pitch = Value; - break; - } - - case 2: - { - if (Rotator.Yaw == Value) - return false; - Rotator.Yaw = Value; - break; - } - } - Transform->SetRotation(Rotator.Quaternion()); - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - if (Scale[XYZIndex] == Value) - return false; - - Scale[XYZIndex] = Value; - Transform->SetScale3D(Scale); - } - - MarkChanged(true); - - return true; -} - -float -FHoudiniInstancedOutput::GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; - if (!Transform) - return 0.0f; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - return Position[XYZIndex]; - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - return Rotator.Roll; - } - - case 1: - { - return Rotator.Pitch; - } - - case 2: - { - return Rotator.Yaw; - } - } - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - return Scale[XYZIndex]; - } - - return 0.0f; -} - -// ---------------------------------------------------- -// FHoudiniOutputObjectIdentifier -// ---------------------------------------------------- - -FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier() -{ - ObjectId = -1; - GeoId = -1; - PartId = -1; - SplitIdentifier = FString(); - PartName = FString(); -} - -FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier( - const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier) -{ - ObjectId = InObjectId; - GeoId = InGeoId; - PartId = InPartId; - SplitIdentifier = InSplitIdentifier; -} - -uint32 -FHoudiniOutputObjectIdentifier::GetTypeHash() const -{ - int32 HashBuffer[3] = { ObjectId, GeoId, PartId }; - int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); - return FCrc::StrCrc32(*SplitIdentifier, Hash); -} - -bool -FHoudiniOutputObjectIdentifier::operator==(const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier) const -{ - // Object/Geo/Part IDs must match - bool bMatchingIds = true; - if (ObjectId != InOutputObjectIdentifier.ObjectId - || GeoId != InOutputObjectIdentifier.GeoId - || PartId != InOutputObjectIdentifier.PartId) - bMatchingIds = false; - - if ((bLoaded && !InOutputObjectIdentifier.bLoaded) - || (!bLoaded && InOutputObjectIdentifier.bLoaded)) - { - // If one of the two identifier is loaded, - // we can simply compare the part names - if (PartName.Equals(InOutputObjectIdentifier.PartName) - && SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) - return true; - } - - if (!bMatchingIds) - { - return false; - } - - // If split ID and name match, we're equal... - if (SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) - return true; - - // ... if not we're different - return false; -} - -bool -FHoudiniOutputObjectIdentifier::Matches(const FHoudiniGeoPartObject& InHGPO) const -{ - // Object/Geo/Part IDs must match - bool bMatchingIds = true; - if (ObjectId != InHGPO.ObjectId - || GeoId != InHGPO.GeoId - || PartId != InHGPO.PartId) - bMatchingIds = false; - - if ((bLoaded && !InHGPO.bLoaded) || (!bLoaded && InHGPO.bLoaded)) - { - // If either the HGPO or the Identifer is nmarked as loaded, - // we can simply compare the part names - if (PartName.Equals(InHGPO.PartName)) - return true; - } - - if (!bMatchingIds) - { - return false; - } - - // If the HGPO has our split identifier - //if (InHGPO.SplitGroups.Contains(SplitIdentifier)) - // return true; - - // - return true; -} - - -FHoudiniBakedOutputObject::FHoudiniBakedOutputObject() - : Actor() - , ActorBakeName(NAME_None) - , BakedObject() - , BakedComponent() -{ -} - - -FHoudiniBakedOutputObject::FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject, UObject* InBakedComponent) - : Actor(FSoftObjectPath(InActor).ToString()) - , ActorBakeName(InActorBakeName) - , BakedObject(FSoftObjectPath(InBakeObject).ToString()) - , BakedComponent(FSoftObjectPath(InBakedComponent).ToString()) -{ -} - - -AActor* -FHoudiniBakedOutputObject::GetActorIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ActorPath(Actor); - - if (!ActorPath.IsValid()) - return nullptr; - - UObject* Object = ActorPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ActorPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Cast(Object); -} - -UObject* -FHoudiniBakedOutputObject::GetBakedObjectIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ObjectPath(BakedObject); - - if (!ObjectPath.IsValid()) - return nullptr; - - UObject* Object = ObjectPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ObjectPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Object; -} - -UObject* -FHoudiniBakedOutputObject::GetBakedComponentIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ComponentPath(BakedComponent); - - if (!ComponentPath.IsValid()) - return nullptr; - - UObject* Object = ComponentPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ComponentPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Object; -} - -UBlueprint* -FHoudiniBakedOutputObject::GetBlueprintIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath BlueprintPath(Blueprint); - - if (!BlueprintPath.IsValid()) - return nullptr; - - UObject* Object = BlueprintPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = BlueprintPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Cast(Object); -} - - -UHoudiniOutput::UHoudiniOutput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Type(EHoudiniOutputType::Invalid) - , StaleCount(0) - , bLandscapeWorldComposition(false) - , bIsEditableNode(false) - , bHasEditableNodeBuilt(false) - , bCanDeleteHoudiniNodes(true) -{ - -} - -UHoudiniOutput::~UHoudiniOutput() -{ - Type = EHoudiniOutputType::Invalid; - StaleCount = 0; - bIsUpdating = false; - - HoudiniGeoPartObjects.Empty(); - OutputObjects.Empty(); - InstancedOutputs.Empty(); - AssignementMaterials.Empty(); - ReplacementMaterials.Empty(); -} - -void -UHoudiniOutput::BeginDestroy() -{ - Super::BeginDestroy(); -} - -FBox -UHoudiniOutput::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - - switch (GetType()) - { - case EHoudiniOutputType::Mesh: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - - UMeshComponent* MeshComp = nullptr; - if (CurObj.bProxyIsCurrent) - { - MeshComp = Cast(CurObj.ProxyComponent); - } - else - { - MeshComp = Cast(CurObj.OutputComponent); - } - - if (!MeshComp || MeshComp->IsPendingKill()) - continue; - - BoxBounds += MeshComp->Bounds.GetBox(); - } - } - break; - - case EHoudiniOutputType::Landscape: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - UHoudiniLandscapePtr* CurLandscapeObj = Cast(CurObj.OutputObject); - if (!CurLandscapeObj || CurLandscapeObj->IsPendingKill()) - continue; - - ALandscapeProxy* Landscape = Cast(CurLandscapeObj->GetRawPtr()); - if (!Landscape || Landscape->IsPendingKill()) - continue; - - FVector Origin, Extent; - Landscape->GetActorBounds(false, Origin, Extent); - - FBox LandscapeBounds = FBox::BuildAABB(Origin, Extent); - BoxBounds += LandscapeBounds; - } - } - break; - - case EHoudiniOutputType::Instancer: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - USceneComponent* InstancedComp = Cast(CurObj.OutputObject); - if (!InstancedComp || InstancedComp->IsPendingKill()) - continue; - - BoxBounds += InstancedComp->Bounds.GetBox(); - } - } - break; - - case EHoudiniOutputType::Curve: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - UHoudiniSplineComponent* CurHoudiniSplineComp = Cast(CurObj.OutputComponent); - if (!CurHoudiniSplineComp || CurHoudiniSplineComp->IsPendingKill()) - continue; - - FBox CurCurveBound(ForceInitToZero); - for (auto & Trans : CurHoudiniSplineComp->CurvePoints) - { - CurCurveBound += Trans.GetLocation(); - } - - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) - BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); - } - - } - break; - - case EHoudiniOutputType::Skeletal: - case EHoudiniOutputType::Invalid: - break; - - default: - break; - } - - return BoxBounds; -} - -void -UHoudiniOutput::Clear() -{ - StaleCount = 0; - - HoudiniGeoPartObjects.Empty(); - - for (auto& CurrentOutputObject : OutputObjects) - { - // Clear the output component - USceneComponent* SceneComp = Cast(CurrentOutputObject.Value.OutputComponent); - if (SceneComp && !SceneComp->IsPendingKill()) - { - SceneComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComp->UnregisterComponent(); - SceneComp->DestroyComponent(); - } - - // Also destroy proxy components - USceneComponent* ProxyComp = Cast(CurrentOutputObject.Value.ProxyComponent); - if (ProxyComp && !ProxyComp->IsPendingKill()) - { - ProxyComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - ProxyComp->UnregisterComponent(); - ProxyComp->DestroyComponent(); - } - - if (Type == EHoudiniOutputType::Landscape && !bLandscapeWorldComposition && !IsGarbageCollecting()) - { - // NOTE: We cannot resolve soft pointers during garbage collection. Any Get() or IsValid() call - // will result in a StaticFindObject() call which will raise an exception during GC. - UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentOutputObject.Value.OutputObject); - ALandscapeProxy* LandscapeProxy = LandscapePtr ? LandscapePtr->GetRawPtr() : nullptr; - if (LandscapeProxy && !LandscapeProxy->IsPendingKill()) - { - LandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - LandscapeProxy->ConditionalBeginDestroy(); - LandscapeProxy->Destroy(); - } - } - } - - OutputObjects.Empty(); - InstancedOutputs.Empty(); - AssignementMaterials.Empty(); - ReplacementMaterials.Empty(); - - Type = EHoudiniOutputType::Invalid; -} - -const bool -UHoudiniOutput::HasGeoChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasGeoChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasTransformChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasTransformChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasMaterialsChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasMaterialsChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const -{ - return HoudiniGeoPartObjects.Find(InHGPO) != INDEX_NONE; -} - -const bool -UHoudiniOutput::HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const -{ - if (InHGPO.Type != EHoudiniPartType::Volume) - return false; - - if (InHGPO.VolumeName.IsEmpty()) - return false; - - for (auto& currentHGPO : HoudiniGeoPartObjects) - { - // Asset/Object/Geo IDs should match - if (currentHGPO.AssetId != InHGPO.AssetId - || currentHGPO.ObjectId != InHGPO.ObjectId - || currentHGPO.GeoId != InHGPO.GeoId) - { - continue; - } - - // Both HGPO type should be volumes - if (currentHGPO.Type != EHoudiniPartType::Volume) - { - continue; - } - - // Volume tile index should match - if (currentHGPO.VolumeTileIndex != InHGPO.VolumeTileIndex) - { - continue; - } - - // We've specified if we want the name to match/to be different: - // when looking in previous outputs, we want the name to match - // when looking in newly created outputs, we want to be sure the names are different - bool bNameMatch = InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase); - if (bNameMatch != bVolumeNameShouldMatch) - continue; - - return true; - } - - return false; -} - -void -UHoudiniOutput::MarkAllHGPOsAsStale(const bool& bInStale) -{ - // Since objects can only be added to this array, - // Simply keep track of the current number of HoudiniGeoPartObject - StaleCount = bInStale ? HoudiniGeoPartObjects.Num() : 0; -} - -void -UHoudiniOutput::DeleteAllStaleHGPOs() -{ - // Simply delete the first "StaleCount" objects and reset the stale marker - HoudiniGeoPartObjects.RemoveAt(0, StaleCount); - StaleCount = 0; -} - -void -UHoudiniOutput::AddNewHGPO(const FHoudiniGeoPartObject& InHGPO) -{ - HoudiniGeoPartObjects.Add(InHGPO); -} - -void -UHoudiniOutput::UpdateOutputType() -{ - int32 MeshCount = 0; - int32 CurveCount = 0; - int32 VolumeCount = 0; - int32 InstancerCount = 0; - for (auto& HGPO : HoudiniGeoPartObjects) - { - switch (HGPO.Type) - { - case EHoudiniPartType::Mesh: - MeshCount++; - break; - case EHoudiniPartType::Curve: - CurveCount++; - break; - case EHoudiniPartType::Volume: - VolumeCount++; - break; - case EHoudiniPartType::Instancer: - InstancerCount++; - break; - default: - case EHoudiniPartType::Invalid: - break; - } - } - - if (VolumeCount > 0) - { - // If we have a volume, we're a landscape - Type = EHoudiniOutputType::Landscape; - } - else if (InstancerCount > 0) - { - // if we have at least an instancer, we're one - Type = EHoudiniOutputType::Instancer; - } - else if (MeshCount > 0) - { - Type = EHoudiniOutputType::Mesh; - } - else if (CurveCount > 0) - { - Type = EHoudiniOutputType::Curve; - } - else - { - // No valid HGPO detected... - Type = EHoudiniOutputType::Invalid; - } -} - -UHoudiniOutput* -UHoudiniOutput::DuplicateAndCopyProperties(UObject* DestOuter, FName NewName) -{ - UHoudiniOutput* NewOutput = Cast(StaticDuplicateObject(this, DestOuter, NewName)); - - NewOutput->CopyPropertiesFrom(this, false); - - return NewOutput; -} - -void -UHoudiniOutput::CopyPropertiesFrom(UHoudiniOutput* InInput, bool bCopyAllProperties) -{ - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - // Stash all the data that we want to preserve, and re-apply after property copy took place - // (similar to Get/Apply component instance data). This is typically only needed - // for certain properties that require cleanup when being replaced / removed. - TMap PrevOutputObjects = OutputObjects; - TMap PrevInstancedOutputs = InstancedOutputs; - - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - - // Restore the desired properties. - OutputObjects = PrevOutputObjects; - InstancedOutputs = PrevInstancedOutputs; - } - - // Copy any additional DuplicateTransient properties. - bHasEditableNodeBuilt = InInput->bHasEditableNodeBuilt; -} - -void -UHoudiniOutput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; -} - -FString -UHoudiniOutput::OutputTypeToString(const EHoudiniOutputType& InOutputType) -{ - FString OutputTypeStr; - switch (InOutputType) - { - case EHoudiniOutputType::Mesh: - OutputTypeStr = TEXT("Mesh"); - break; - case EHoudiniOutputType::Instancer: - OutputTypeStr = TEXT("Instancer"); - break; - case EHoudiniOutputType::Landscape: - OutputTypeStr = TEXT("Landscape"); - break; - case EHoudiniOutputType::Curve: - OutputTypeStr = TEXT("Curve"); - break; - case EHoudiniOutputType::Skeletal: - OutputTypeStr = TEXT("Skeletal"); - break; - - default: - case EHoudiniOutputType::Invalid: - OutputTypeStr = TEXT("Invalid"); - break; - } - - return OutputTypeStr; -} - -void -UHoudiniOutput::MarkAsLoaded(const bool& InLoaded) -{ - // Mark all HGPO as loaded - for (auto& HGPO : HoudiniGeoPartObjects) - { - HGPO.bLoaded = InLoaded; - } - - // Mark all output object's identifier as loaded - for (auto& Iter : OutputObjects) - { - FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; - Identifier.bLoaded = InLoaded; - } - - // Instanced outputs - for (auto& Iter : InstancedOutputs) - { - FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; - Identifier.bLoaded = InLoaded; - } -} - - -const bool -UHoudiniOutput::HasAnyProxy() const -{ - for (const auto& Pair : OutputObjects) - { - UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) - { - return true; - } - } - - return false; -} - -const bool -UHoudiniOutput::HasProxy(const FHoudiniOutputObjectIdentifier& InIdentifier) const -{ - const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - if (!FoundOutputObject) - return false; - - UObject* FoundProxy = FoundOutputObject->ProxyObject; - if (!FoundProxy || FoundProxy->IsPendingKill()) - return false; - - return true; -} - -const bool -UHoudiniOutput::HasAnyCurrentProxy() const -{ - for (const auto& Pair : OutputObjects) - { - UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) - { - if(Pair.Value.bProxyIsCurrent) - { - return true; - } - } - } - - return false; -} - -const bool -UHoudiniOutput::IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const -{ - if (!HasProxy(InIdentifier)) - return false; - - const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - if (!FoundOutputObject) - return false; - - return FoundOutputObject->bProxyIsCurrent; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniOutput.h" +#include "HoudiniAssetComponent.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniSplineComponent.h" + +#include "Components/SceneComponent.h" +#include "Components/MeshComponent.h" +#include "Components/SplineComponent.h" +#include "Misc/StringFormatArg.h" +#include "Engine/Engine.h" + +UHoudiniLandscapePtr::UHoudiniLandscapePtr(class FObjectInitializer const& Initializer) +{ + // bIsWorldCompositionLandscape = false; + // BakeType = EHoudiniLandscapeOutputBakeType::Detachment; +}; + +uint32 +GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier) +{ + return HoudiniOutputObjectIdentifier.GetTypeHash(); +} + +void +FHoudiniInstancedOutput::SetVariationObjectAt(const int32& AtIndex, UObject* InObject) +{ + // Resize the array if needed + if (VariationObjects.Num() <= AtIndex) + VariationObjects.SetNum(AtIndex + 1); + + if (VariationTransformOffsets.Num() <= AtIndex) + VariationTransformOffsets.SetNum(AtIndex + 1); + + UObject* CurrentObject = VariationObjects[AtIndex].LoadSynchronous(); + if (CurrentObject == InObject) + return; + + VariationObjects[AtIndex] = InObject; +} + +bool +FHoudiniInstancedOutput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; + if (!Transform) + return false; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + if (Position[XYZIndex] == Value) + return false; + Position[XYZIndex] = Value; + Transform->SetLocation(Position); + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + if (Rotator.Roll == Value) + return false; + Rotator.Roll = Value; + break; + } + + case 1: + { + if (Rotator.Pitch == Value) + return false; + Rotator.Pitch = Value; + break; + } + + case 2: + { + if (Rotator.Yaw == Value) + return false; + Rotator.Yaw = Value; + break; + } + } + Transform->SetRotation(Rotator.Quaternion()); + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + if (Scale[XYZIndex] == Value) + return false; + + Scale[XYZIndex] = Value; + Transform->SetScale3D(Scale); + } + + MarkChanged(true); + + return true; +} + +float +FHoudiniInstancedOutput::GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; + if (!Transform) + return 0.0f; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + return Position[XYZIndex]; + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + return Rotator.Roll; + } + + case 1: + { + return Rotator.Pitch; + } + + case 2: + { + return Rotator.Yaw; + } + } + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + return Scale[XYZIndex]; + } + + return 0.0f; +} + +// ---------------------------------------------------- +// FHoudiniOutputObjectIdentifier +// ---------------------------------------------------- + +FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier() +{ + ObjectId = -1; + GeoId = -1; + PartId = -1; + SplitIdentifier = FString(); + PartName = FString(); +} + +FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier( + const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier) +{ + ObjectId = InObjectId; + GeoId = InGeoId; + PartId = InPartId; + SplitIdentifier = InSplitIdentifier; +} + +uint32 +FHoudiniOutputObjectIdentifier::GetTypeHash() const +{ + int32 HashBuffer[3] = { ObjectId, GeoId, PartId }; + int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitIdentifier, Hash); +} + +bool +FHoudiniOutputObjectIdentifier::operator==(const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier) const +{ + // Object/Geo/Part IDs must match + bool bMatchingIds = true; + if (ObjectId != InOutputObjectIdentifier.ObjectId + || GeoId != InOutputObjectIdentifier.GeoId + || PartId != InOutputObjectIdentifier.PartId) + bMatchingIds = false; + + if ((bLoaded && !InOutputObjectIdentifier.bLoaded) + || (!bLoaded && InOutputObjectIdentifier.bLoaded)) + { + // If one of the two identifier is loaded, + // we can simply compare the part names + if (PartName.Equals(InOutputObjectIdentifier.PartName) + && SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) + return true; + } + + if (!bMatchingIds) + { + return false; + } + + // If split ID and name match, we're equal... + if (SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) + return true; + + // ... if not we're different + return false; +} + +bool +FHoudiniOutputObjectIdentifier::Matches(const FHoudiniGeoPartObject& InHGPO) const +{ + // Object/Geo/Part IDs must match + bool bMatchingIds = true; + if (ObjectId != InHGPO.ObjectId + || GeoId != InHGPO.GeoId + || PartId != InHGPO.PartId) + bMatchingIds = false; + + if ((bLoaded && !InHGPO.bLoaded) || (!bLoaded && InHGPO.bLoaded)) + { + // If either the HGPO or the Identifer is nmarked as loaded, + // we can simply compare the part names + if (PartName.Equals(InHGPO.PartName)) + return true; + } + + if (!bMatchingIds) + { + return false; + } + + // If the HGPO has our split identifier + //if (InHGPO.SplitGroups.Contains(SplitIdentifier)) + // return true; + + // + return true; +} + + +FHoudiniBakedOutputObject::FHoudiniBakedOutputObject() + : Actor() + , ActorBakeName(NAME_None) + , BakedObject() + , BakedComponent() +{ +} + + +FHoudiniBakedOutputObject::FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject, UObject* InBakedComponent) + : Actor(FSoftObjectPath(InActor).ToString()) + , ActorBakeName(InActorBakeName) + , BakedObject(FSoftObjectPath(InBakeObject).ToString()) + , BakedComponent(FSoftObjectPath(InBakedComponent).ToString()) +{ +} + + +AActor* +FHoudiniBakedOutputObject::GetActorIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ActorPath(Actor); + + if (!ActorPath.IsValid()) + return nullptr; + + UObject* Object = ActorPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ActorPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + +UObject* +FHoudiniBakedOutputObject::GetBakedObjectIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ObjectPath(BakedObject); + + if (!ObjectPath.IsValid()) + return nullptr; + + UObject* Object = ObjectPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ObjectPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Object; +} + +UObject* +FHoudiniBakedOutputObject::GetBakedComponentIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ComponentPath(BakedComponent); + + if (!ComponentPath.IsValid()) + return nullptr; + + UObject* Object = ComponentPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ComponentPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Object; +} + +UBlueprint* +FHoudiniBakedOutputObject::GetBlueprintIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath BlueprintPath(Blueprint); + + if (!BlueprintPath.IsValid()) + return nullptr; + + UObject* Object = BlueprintPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = BlueprintPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + + +UHoudiniOutput::UHoudiniOutput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Type(EHoudiniOutputType::Invalid) + , StaleCount(0) + , bLandscapeWorldComposition(false) + , bIsEditableNode(false) + , bHasEditableNodeBuilt(false) + , bCanDeleteHoudiniNodes(true) +{ + +} + +UHoudiniOutput::~UHoudiniOutput() +{ + Type = EHoudiniOutputType::Invalid; + StaleCount = 0; + bIsUpdating = false; + + HoudiniGeoPartObjects.Empty(); + OutputObjects.Empty(); + InstancedOutputs.Empty(); + AssignementMaterials.Empty(); + ReplacementMaterials.Empty(); +} + +void +UHoudiniOutput::BeginDestroy() +{ + Super::BeginDestroy(); +} + +FBox +UHoudiniOutput::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + + switch (GetType()) + { + case EHoudiniOutputType::Mesh: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + + UMeshComponent* MeshComp = nullptr; + if (CurObj.bProxyIsCurrent) + { + MeshComp = Cast(CurObj.ProxyComponent); + } + else + { + MeshComp = Cast(CurObj.OutputComponent); + } + + if (!MeshComp || MeshComp->IsPendingKill()) + continue; + + BoxBounds += MeshComp->Bounds.GetBox(); + } + } + break; + + case EHoudiniOutputType::Landscape: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + UHoudiniLandscapePtr* CurLandscapeObj = Cast(CurObj.OutputObject); + if (!CurLandscapeObj || CurLandscapeObj->IsPendingKill()) + continue; + + ALandscapeProxy* Landscape = Cast(CurLandscapeObj->GetRawPtr()); + if (!Landscape || Landscape->IsPendingKill()) + continue; + + FVector Origin, Extent; + Landscape->GetActorBounds(false, Origin, Extent); + + FBox LandscapeBounds = FBox::BuildAABB(Origin, Extent); + BoxBounds += LandscapeBounds; + } + } + break; + + case EHoudiniOutputType::Instancer: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + USceneComponent* InstancedComp = Cast(CurObj.OutputObject); + if (!InstancedComp || InstancedComp->IsPendingKill()) + continue; + + BoxBounds += InstancedComp->Bounds.GetBox(); + } + } + break; + + case EHoudiniOutputType::Curve: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + UHoudiniSplineComponent* CurHoudiniSplineComp = Cast(CurObj.OutputComponent); + if (!CurHoudiniSplineComp || CurHoudiniSplineComp->IsPendingKill()) + continue; + + FBox CurCurveBound(ForceInitToZero); + for (auto & Trans : CurHoudiniSplineComp->CurvePoints) + { + CurCurveBound += Trans.GetLocation(); + } + + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !OuterHAC->IsPendingKill()) + BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); + } + + } + break; + + case EHoudiniOutputType::Skeletal: + case EHoudiniOutputType::Invalid: + break; + + default: + break; + } + + return BoxBounds; +} + +void +UHoudiniOutput::Clear() +{ + StaleCount = 0; + + HoudiniGeoPartObjects.Empty(); + + for (auto& CurrentOutputObject : OutputObjects) + { + // Clear the output component + USceneComponent* SceneComp = Cast(CurrentOutputObject.Value.OutputComponent); + if (SceneComp && !SceneComp->IsPendingKill()) + { + SceneComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComp->UnregisterComponent(); + SceneComp->DestroyComponent(); + } + + // Also destroy proxy components + USceneComponent* ProxyComp = Cast(CurrentOutputObject.Value.ProxyComponent); + if (ProxyComp && !ProxyComp->IsPendingKill()) + { + ProxyComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + ProxyComp->UnregisterComponent(); + ProxyComp->DestroyComponent(); + } + + if (Type == EHoudiniOutputType::Landscape && !bLandscapeWorldComposition && !IsGarbageCollecting()) + { + // NOTE: We cannot resolve soft pointers during garbage collection. Any Get() or IsValid() call + // will result in a StaticFindObject() call which will raise an exception during GC. + UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentOutputObject.Value.OutputObject); + ALandscapeProxy* LandscapeProxy = LandscapePtr ? LandscapePtr->GetRawPtr() : nullptr; + if (LandscapeProxy && !LandscapeProxy->IsPendingKill()) + { + LandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + LandscapeProxy->ConditionalBeginDestroy(); + LandscapeProxy->Destroy(); + } + } + } + + OutputObjects.Empty(); + InstancedOutputs.Empty(); + AssignementMaterials.Empty(); + ReplacementMaterials.Empty(); + + Type = EHoudiniOutputType::Invalid; +} + +const bool +UHoudiniOutput::HasGeoChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasGeoChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasTransformChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasTransformChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasMaterialsChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasMaterialsChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const +{ + return HoudiniGeoPartObjects.Find(InHGPO) != INDEX_NONE; +} + +const bool +UHoudiniOutput::HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const +{ + if (InHGPO.Type != EHoudiniPartType::Volume) + return false; + + if (InHGPO.VolumeName.IsEmpty()) + return false; + + for (auto& currentHGPO : HoudiniGeoPartObjects) + { + // Asset/Object/Geo IDs should match + if (currentHGPO.AssetId != InHGPO.AssetId + || currentHGPO.ObjectId != InHGPO.ObjectId + || currentHGPO.GeoId != InHGPO.GeoId) + { + continue; + } + + // Both HGPO type should be volumes + if (currentHGPO.Type != EHoudiniPartType::Volume) + { + continue; + } + + // Volume tile index should match + if (currentHGPO.VolumeTileIndex != InHGPO.VolumeTileIndex) + { + continue; + } + + // We've specified if we want the name to match/to be different: + // when looking in previous outputs, we want the name to match + // when looking in newly created outputs, we want to be sure the names are different + bool bNameMatch = InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase); + if (bNameMatch != bVolumeNameShouldMatch) + continue; + + return true; + } + + return false; +} + +void +UHoudiniOutput::MarkAllHGPOsAsStale(const bool& bInStale) +{ + // Since objects can only be added to this array, + // Simply keep track of the current number of HoudiniGeoPartObject + StaleCount = bInStale ? HoudiniGeoPartObjects.Num() : 0; +} + +void +UHoudiniOutput::DeleteAllStaleHGPOs() +{ + // Simply delete the first "StaleCount" objects and reset the stale marker + HoudiniGeoPartObjects.RemoveAt(0, StaleCount); + StaleCount = 0; +} + +void +UHoudiniOutput::AddNewHGPO(const FHoudiniGeoPartObject& InHGPO) +{ + HoudiniGeoPartObjects.Add(InHGPO); +} + +void +UHoudiniOutput::UpdateOutputType() +{ + int32 MeshCount = 0; + int32 CurveCount = 0; + int32 VolumeCount = 0; + int32 InstancerCount = 0; + for (auto& HGPO : HoudiniGeoPartObjects) + { + switch (HGPO.Type) + { + case EHoudiniPartType::Mesh: + MeshCount++; + break; + case EHoudiniPartType::Curve: + CurveCount++; + break; + case EHoudiniPartType::Volume: + VolumeCount++; + break; + case EHoudiniPartType::Instancer: + InstancerCount++; + break; + default: + case EHoudiniPartType::Invalid: + break; + } + } + + if (VolumeCount > 0) + { + // If we have a volume, we're a landscape + Type = EHoudiniOutputType::Landscape; + } + else if (InstancerCount > 0) + { + // if we have at least an instancer, we're one + Type = EHoudiniOutputType::Instancer; + } + else if (MeshCount > 0) + { + Type = EHoudiniOutputType::Mesh; + } + else if (CurveCount > 0) + { + Type = EHoudiniOutputType::Curve; + } + else + { + // No valid HGPO detected... + Type = EHoudiniOutputType::Invalid; + } +} + +UHoudiniOutput* +UHoudiniOutput::DuplicateAndCopyProperties(UObject* DestOuter, FName NewName) +{ + UHoudiniOutput* NewOutput = Cast(StaticDuplicateObject(this, DestOuter, NewName)); + + NewOutput->CopyPropertiesFrom(this, false); + + return NewOutput; +} + +void +UHoudiniOutput::CopyPropertiesFrom(UHoudiniOutput* InInput, bool bCopyAllProperties) +{ + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + // Stash all the data that we want to preserve, and re-apply after property copy took place + // (similar to Get/Apply component instance data). This is typically only needed + // for certain properties that require cleanup when being replaced / removed. + TMap PrevOutputObjects = OutputObjects; + TMap PrevInstancedOutputs = InstancedOutputs; + + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + + // Restore the desired properties. + OutputObjects = PrevOutputObjects; + InstancedOutputs = PrevInstancedOutputs; + } + + // Copy any additional DuplicateTransient properties. + bHasEditableNodeBuilt = InInput->bHasEditableNodeBuilt; +} + +void +UHoudiniOutput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; +} + +FString +UHoudiniOutput::OutputTypeToString(const EHoudiniOutputType& InOutputType) +{ + FString OutputTypeStr; + switch (InOutputType) + { + case EHoudiniOutputType::Mesh: + OutputTypeStr = TEXT("Mesh"); + break; + case EHoudiniOutputType::Instancer: + OutputTypeStr = TEXT("Instancer"); + break; + case EHoudiniOutputType::Landscape: + OutputTypeStr = TEXT("Landscape"); + break; + case EHoudiniOutputType::Curve: + OutputTypeStr = TEXT("Curve"); + break; + case EHoudiniOutputType::Skeletal: + OutputTypeStr = TEXT("Skeletal"); + break; + + default: + case EHoudiniOutputType::Invalid: + OutputTypeStr = TEXT("Invalid"); + break; + } + + return OutputTypeStr; +} + +void +UHoudiniOutput::MarkAsLoaded(const bool& InLoaded) +{ + // Mark all HGPO as loaded + for (auto& HGPO : HoudiniGeoPartObjects) + { + HGPO.bLoaded = InLoaded; + } + + // Mark all output object's identifier as loaded + for (auto& Iter : OutputObjects) + { + FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; + Identifier.bLoaded = InLoaded; + } + + // Instanced outputs + for (auto& Iter : InstancedOutputs) + { + FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; + Identifier.bLoaded = InLoaded; + } +} + + +const bool +UHoudiniOutput::HasAnyProxy() const +{ + for (const auto& Pair : OutputObjects) + { + UObject* FoundProxy = Pair.Value.ProxyObject; + if (FoundProxy && !FoundProxy->IsPendingKill()) + { + return true; + } + } + + return false; +} + +const bool +UHoudiniOutput::HasProxy(const FHoudiniOutputObjectIdentifier& InIdentifier) const +{ + const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + if (!FoundOutputObject) + return false; + + UObject* FoundProxy = FoundOutputObject->ProxyObject; + if (!FoundProxy || FoundProxy->IsPendingKill()) + return false; + + return true; +} + +const bool +UHoudiniOutput::HasAnyCurrentProxy() const +{ + for (const auto& Pair : OutputObjects) + { + UObject* FoundProxy = Pair.Value.ProxyObject; + if (FoundProxy && !FoundProxy->IsPendingKill()) + { + if(Pair.Value.bProxyIsCurrent) + { + return true; + } + } + } + + return false; +} + +const bool +UHoudiniOutput::IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const +{ + if (!HasProxy(InIdentifier)) + return false; + + const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + if (!FoundOutputObject) + return false; + + return FoundOutputObject->bProxyIsCurrent; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h index 14136730e..a73e3551d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h @@ -1,561 +1,561 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "HoudiniGeoPartObject.h" -#include "LandscapeProxy.h" -#include "Misc/StringFormatArg.h" -#include "UObject/SoftObjectPtr.h" - -#include "HoudiniOutput.generated.h" - -class UMaterialInterface; - -UENUM() -enum class EHoudiniOutputType : uint8 -{ - Invalid, - - Mesh, - Instancer, - Landscape, - Curve, - Skeletal -}; - -UENUM() -enum class EHoudiniCurveOutputType : uint8 -{ - UnrealSpline, - HoudiniSpline, -}; - -UENUM() -enum class EHoudiniLandscapeOutputBakeType : uint8 -{ - Detachment, - BakeToImage, - BakeToWorld, - InValid, -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniCurveOutputProperties -{ - GENERATED_USTRUCT_BODY() - - // Curve output properties - UPROPERTY() - EHoudiniCurveOutputType CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; - - UPROPERTY() - int32 NumPoints = -1; - - UPROPERTY() - bool bClosed = false; - - UPROPERTY() - EHoudiniCurveType CurveType = EHoudiniCurveType::Invalid; - - UPROPERTY() - EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::Invalid; -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniLandscapePtr : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - FORCEINLINE - void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; - - FORCEINLINE - TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; - - // Calling Get() during GC will raise an exception because Get calls StaticFindObject. - FORCEINLINE - ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; - - FORCEINLINE - FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; - - FORCEINLINE - void SetLandscapeOutputBakeType(const EHoudiniLandscapeOutputBakeType & InBakeType) { BakeType = InBakeType; }; - - FORCEINLINE - EHoudiniLandscapeOutputBakeType GetLandscapeOutputBakeType() const { return BakeType; }; - - UPROPERTY() - TSoftObjectPtr LandscapeSoftPtr; - - UPROPERTY() - EHoudiniLandscapeOutputBakeType BakeType; -}; - - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniOutputObjectIdentifier -{ - GENERATED_USTRUCT_BODY() - -public: - // Constructors - FHoudiniOutputObjectIdentifier(); - FHoudiniOutputObjectIdentifier(const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier); - - // Return hash value for this object, used when using this object as a key inside hashing containers. - uint32 GetTypeHash() const; - - // Comparison operator, used by hashing containers. - bool operator==(const FHoudiniOutputObjectIdentifier& HoudiniGeoPartObject) const; - - bool Matches(const FHoudiniGeoPartObject& HoudiniGeoPartObject) const; - -public: - - // NodeId of corresponding Houdini Object. - UPROPERTY() - int32 ObjectId = -1; - - // NodeId of corresponding Houdini Geo. - UPROPERTY() - int32 GeoId = -1; - - // PartId - UPROPERTY() - int32 PartId = -1; - - // String identifier for the split that created this - UPROPERTY() - FString SplitIdentifier = FString(); - - // Name of the part used to generate the output - UPROPERTY() - FString PartName = FString(); - - // First valid primitive index for this output - // (used to read generic attributes) - UPROPERTY() - int32 PrimitiveIndex = -1; - - // First valid point index for this output - // (used to read generic attributes) - UPROPERTY() - int32 PointIndex = -1; - - bool bLoaded = false; -}; - -/** Function used by hashing containers to create a unique hash for this type of object. **/ -HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier); - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput -{ - GENERATED_USTRUCT_BODY() - -public: - - void MarkChanged(const bool& InChanged) { bChanged = InChanged; }; - - void SetVariationObjectAt(const int32& AtIndex, UObject* InObject); - - bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - - float GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - -#if WITH_EDITOR - void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; - bool IsUnformScaleLocked() const { return bUniformScaleLocked; }; -#endif - -public: - - // Original object used by the instancer. - UPROPERTY() - TSoftObjectPtr OriginalObject = nullptr; - - UPROPERTY() - int32 OriginalObjectIndex = -1; - - // Original HoudiniGeoPartObject used by the instancer - //UPROPERTY() - //FHoudiniGeoPartObject OriginalHGPO; - - // Original Instance transforms - UPROPERTY() - TArray OriginalTransforms; - - // Variation objects currently used for instancing - UPROPERTY() - TArray> VariationObjects; - - // Transform offsets, one for each variation. - UPROPERTY() - TArray VariationTransformOffsets; - - // Index of the variation used for each transform - UPROPERTY() - TArray TransformVariationIndices; - - // Indicates this instanced output's component should be recreated - UPROPERTY() - bool bChanged = false; - - // Indicates this instanced output is stale and should be removed - UPROPERTY() - bool bStale = false; - - // Indicates if change the scale of Transfrom Offset of this object uniformly -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bUniformScaleLocked = false; -#endif - // TODO - // Color overrides?? -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject -{ - GENERATED_USTRUCT_BODY() - - public: - FHoudiniBakedOutputObject(); - - FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject=nullptr, UObject* InBakedComponent=nullptr); - - // Returns Actor if valid, otherwise nullptr - AActor* GetActorIfValid(bool bInTryLoad=true) const; - - // Returns BakedObject if valid, otherwise nullptr - UObject* GetBakedObjectIfValid(bool bInTryLoad=true) const; - - // Returns BakedComponent if valid, otherwise nullptr - UObject* GetBakedComponentIfValid(bool bInTryLoad=true) const; - - // Returns Blueprint if valid, otherwise nullptr - UBlueprint* GetBlueprintIfValid(bool bInTryLoad=true) const; - - // The actor that the baked output was associated with - UPROPERTY() - FString Actor; - - // The blueprint that baked output was associated with, if any - UPROPERTY() - FString Blueprint; - - // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. - UPROPERTY() - FName ActorBakeName = NAME_None; - - // The baked output asset - UPROPERTY() - FString BakedObject; - - // The baked output component - UPROPERTY() - FString BakedComponent; - - // In the case of instance actor component baking, this is the array of instanced actors - UPROPERTY() - TArray InstancedActors; - - // In the case of mesh split instancer baking: this is the array of instance components - UPROPERTY() - TArray InstancedComponents; -}; - -// Container to hold the map of baked objects. There should be one of -// these for each UHoudiniOutput. We manage this separately from UHoudiniOutput so -// that the "previous/last" bake objects can survive output reconstruction or PDG -// dirty/dirty all operations. -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutput -{ - GENERATED_USTRUCT_BODY() - - public: - UPROPERTY() - TMap BakedOutputObjects; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject -{ - GENERATED_USTRUCT_BODY() - - public: - - // The main output object - UPROPERTY() - UObject* OutputObject = nullptr; - - // The main output component - UPROPERTY() - UObject* OutputComponent = nullptr; - - // Proxy object - UPROPERTY() - UObject* ProxyObject = nullptr; - - // Proxy Component - UPROPERTY() - UObject* ProxyComponent = nullptr; - - // Mesh output properties - // If this is true the proxy mesh is "current", - // in other words, it is newer than the UStaticMesh - UPROPERTY() - bool bProxyIsCurrent = false; - - // Bake Name override for this output object - UPROPERTY() - FString BakeName; - - UPROPERTY() - FHoudiniCurveOutputProperties CurveOutputProperty; - - - // NOTE: The idea behind CachedAttributes and CachedTokens is to - // collect attributes (such as unreal_level_path and unreal_output_name) - // and context-specific tokens (hda name, hda actor name, geo and part ids, tile_id, etc) - // and cache them directly on the output object. When the object gets baked, - // certain tokens can be updated specifically for the bake pass afterwhich the - // the string / path attributes can be resolved with the updated tokens. - // - // A more concrete example: - // unreal_output_name = "{hda_actor_name}_PurplePlants_{geo_id}_{part_id}" - // unreal_level_path = "{out}/{hda_name}/{guid}/PurplePlants/{geo_id}/{part_id}" - // - // All of the aforementions tokens and attributes would be cached on the output object - // when it is being cooked so that the same values are available at bake time. During - // a bake some tokens may be updated, such as `{out}` to change where assets get serialized. - - UPROPERTY() - TMap CachedAttributes; - - // Cache any tokens here that is needed for string resolving - // at bake time. - UPROPERTY() - TMap CachedTokens; -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject -{ - GENERATED_UCLASS_BODY() - - // Declare translators as friend so they can easily directly modify - // and access our HGPO and Output objects - friend struct FHoudiniMeshTranslator; - friend struct FHoudiniInstanceTranslator; - - virtual ~UHoudiniOutput(); - -public: - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - const EHoudiniOutputType& GetType() const { return Type; }; - - const TArray& GetHoudiniGeoPartObjects() const { return HoudiniGeoPartObjects; }; - - // Returns true if we have a HGPO that matches - const bool HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const; - - // Returns true if the HGPO is fromn the same HF as us - const bool HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const; - - // Returns the output objects and their corresponding identifiers - TMap& GetOutputObjects() { return OutputObjects; }; - - // Returns the output objects and their corresponding identifiers - const TMap& GetOutputObjects() const { return OutputObjects; }; - - // Returns this output's assignement material map - TMap& GetAssignementMaterials() { return AssignementMaterials; }; - - // Returns this output's replacement material map - TMap& GetReplacementMaterials() { return ReplacementMaterials; }; - - // Returns the instanced outputs maps - TMap& GetInstancedOutputs() { return InstancedOutputs; }; - - const bool HasGeoChanged() const; - const bool HasTransformChanged() const; - const bool HasMaterialsChanged() const; - - // Returns true if there are any proxy objects in output (current or not) - const bool HasAnyProxy() const; - // Returns true if the specified identifier has a proxy object (current or not) - const bool HasProxy(const FHoudiniOutputObjectIdentifier &InIdentifier) const; - // Returns true if there are any current (most up to date and visible) proxy in the output - const bool HasAnyCurrentProxy() const; - // Returns true if the specified identifier's proxy is "current" (in other words, newer than - // the non-proxy and the proxy should thus be shown instead. - const bool IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const; - - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - void UpdateOutputType(); - - // Adds a new HoudiniGeoPartObject to our array - void AddNewHGPO(const FHoudiniGeoPartObject& InHGPO); - - // Mark all the current HGPO as stale (from a previous cook) - // So we can delte them all by calling DeleteAllStaleHGPOs after. - void MarkAllHGPOsAsStale(const bool& InStale); - - // Delete all the HGPO that were marked as stale - void DeleteAllStaleHGPOs(); - - void SetOutputObjects(const TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; - - void SetInstancedOutputs(const TMap& InInstancedOuput) { InstancedOutputs = InInstancedOuput; }; - - // Marks all HGPO and OutputIdentifier as loaded - void MarkAsLoaded(const bool& InLoaded); - - FORCEINLINE - const bool IsEditableNode() { return bIsEditableNode; }; - - FORCEINLINE - void SetIsEditableNode(bool IsEditable) { bIsEditableNode = IsEditable; } - - FORCEINLINE - const bool HasEditableNodeBuilt() { return bHasEditableNodeBuilt; }; - - FORCEINLINE - void SetHasEditableNodeBuilt(bool HasBuilt) { bHasEditableNodeBuilt = HasBuilt; } - - FORCEINLINE - void SetIsUpdating(bool bInIsUpdating) { bIsUpdating = bInIsUpdating; }; - - FORCEINLINE - bool IsUpdating() const { return bIsUpdating; }; - - FORCEINLINE - void SetLandscapeWorldComposition(const bool bInLandscapeWorldComposition) { bLandscapeWorldComposition = bInLandscapeWorldComposition; }; - - FORCEINLINE - bool IsLandscapeWorldComposition () const { return bLandscapeWorldComposition; }; - - FORCEINLINE - TArray & GetHoudiniCreatedSocketActors() { return HoudiniCreatedSocketActors; }; - - FORCEINLINE - TArray & GetHoudiniAttachedSocketActors() { return HoudiniAttachedSocketActors; } - - // Duplicate this object and copy its state to the resulting object. - // This is typically used to transfer state between between template and instance components. - UHoudiniOutput* DuplicateAndCopyProperties(UObject* DestOuter, FName NewName); - - // Copy properties but preserve output objects - virtual void CopyPropertiesFrom(UHoudiniOutput* InOutput, bool bCopyAllProperties); - - // Set whether this object can delete Houdini nodes. - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } - - //------------------------------------------------------------------------------------------------ - // Helpers - //------------------------------------------------------------------------------------------------ - static FString OutputTypeToString(const EHoudiniOutputType& InOutputType); - - FBox GetBounds() const; - - void Clear(); - -protected: - - virtual void BeginDestroy() override; - -protected: - - // Indicates the type of output we're dealing with - UPROPERTY() - EHoudiniOutputType Type; - - // The output's corresponding HGPO - UPROPERTY() - TArray HoudiniGeoPartObjects; - - // - UPROPERTY(DuplicateTransient) - TMap OutputObjects; - - // Instanced outputs - // Stores the instance variations objects (replacement), transform offsets - UPROPERTY() - TMap InstancedOutputs; - - // The material assignments for this output - UPROPERTY() - TMap AssignementMaterials; - - UPROPERTY() - TMap ReplacementMaterials; - - // Indicates the number of stale HGPO - int32 StaleCount; - - UPROPERTY() - bool bLandscapeWorldComposition; - - // stores the created actors for sockets with actor references. - // - UPROPERTY() - TArray HoudiniCreatedSocketActors; - - UPROPERTY() - TArray HoudiniAttachedSocketActors; - -private: - // Use HoudiniOutput to represent an editable curve. - // This flag tells whether this output is an editable curve. - UPROPERTY() - bool bIsEditableNode; - - // An editable node is only built once. This flag indicates whether this node has been built. - UPROPERTY(DuplicateTransient) - bool bHasEditableNodeBuilt; - - // The IsUpdating flag is set to true when this out exists and is being updated. - UPROPERTY() - bool bIsUpdating; - - UPROPERTY() - bool bCanDeleteHoudiniNodes; -}; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "HoudiniGeoPartObject.h" +#include "LandscapeProxy.h" +#include "Misc/StringFormatArg.h" +#include "UObject/SoftObjectPtr.h" + +#include "HoudiniOutput.generated.h" + +class UMaterialInterface; + +UENUM() +enum class EHoudiniOutputType : uint8 +{ + Invalid, + + Mesh, + Instancer, + Landscape, + Curve, + Skeletal +}; + +UENUM() +enum class EHoudiniCurveOutputType : uint8 +{ + UnrealSpline, + HoudiniSpline, +}; + +UENUM() +enum class EHoudiniLandscapeOutputBakeType : uint8 +{ + Detachment, + BakeToImage, + BakeToWorld, + InValid, +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniCurveOutputProperties +{ + GENERATED_USTRUCT_BODY() + + // Curve output properties + UPROPERTY() + EHoudiniCurveOutputType CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; + + UPROPERTY() + int32 NumPoints = -1; + + UPROPERTY() + bool bClosed = false; + + UPROPERTY() + EHoudiniCurveType CurveType = EHoudiniCurveType::Invalid; + + UPROPERTY() + EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::Invalid; +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniLandscapePtr : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + FORCEINLINE + void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; + + FORCEINLINE + TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; + + // Calling Get() during GC will raise an exception because Get calls StaticFindObject. + FORCEINLINE + ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; + + FORCEINLINE + FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; + + FORCEINLINE + void SetLandscapeOutputBakeType(const EHoudiniLandscapeOutputBakeType & InBakeType) { BakeType = InBakeType; }; + + FORCEINLINE + EHoudiniLandscapeOutputBakeType GetLandscapeOutputBakeType() const { return BakeType; }; + + UPROPERTY() + TSoftObjectPtr LandscapeSoftPtr; + + UPROPERTY() + EHoudiniLandscapeOutputBakeType BakeType; +}; + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniOutputObjectIdentifier +{ + GENERATED_USTRUCT_BODY() + +public: + // Constructors + FHoudiniOutputObjectIdentifier(); + FHoudiniOutputObjectIdentifier(const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier); + + // Return hash value for this object, used when using this object as a key inside hashing containers. + uint32 GetTypeHash() const; + + // Comparison operator, used by hashing containers. + bool operator==(const FHoudiniOutputObjectIdentifier& HoudiniGeoPartObject) const; + + bool Matches(const FHoudiniGeoPartObject& HoudiniGeoPartObject) const; + +public: + + // NodeId of corresponding Houdini Object. + UPROPERTY() + int32 ObjectId = -1; + + // NodeId of corresponding Houdini Geo. + UPROPERTY() + int32 GeoId = -1; + + // PartId + UPROPERTY() + int32 PartId = -1; + + // String identifier for the split that created this + UPROPERTY() + FString SplitIdentifier = FString(); + + // Name of the part used to generate the output + UPROPERTY() + FString PartName = FString(); + + // First valid primitive index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PrimitiveIndex = -1; + + // First valid point index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PointIndex = -1; + + bool bLoaded = false; +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier); + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput +{ + GENERATED_USTRUCT_BODY() + +public: + + void MarkChanged(const bool& InChanged) { bChanged = InChanged; }; + + void SetVariationObjectAt(const int32& AtIndex, UObject* InObject); + + bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + + float GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + +#if WITH_EDITOR + void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; + bool IsUnformScaleLocked() const { return bUniformScaleLocked; }; +#endif + +public: + + // Original object used by the instancer. + UPROPERTY() + TSoftObjectPtr OriginalObject = nullptr; + + UPROPERTY() + int32 OriginalObjectIndex = -1; + + // Original HoudiniGeoPartObject used by the instancer + //UPROPERTY() + //FHoudiniGeoPartObject OriginalHGPO; + + // Original Instance transforms + UPROPERTY() + TArray OriginalTransforms; + + // Variation objects currently used for instancing + UPROPERTY() + TArray> VariationObjects; + + // Transform offsets, one for each variation. + UPROPERTY() + TArray VariationTransformOffsets; + + // Index of the variation used for each transform + UPROPERTY() + TArray TransformVariationIndices; + + // Indicates this instanced output's component should be recreated + UPROPERTY() + bool bChanged = false; + + // Indicates this instanced output is stale and should be removed + UPROPERTY() + bool bStale = false; + + // Indicates if change the scale of Transfrom Offset of this object uniformly +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bUniformScaleLocked = false; +#endif + // TODO + // Color overrides?? +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject +{ + GENERATED_USTRUCT_BODY() + + public: + FHoudiniBakedOutputObject(); + + FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject=nullptr, UObject* InBakedComponent=nullptr); + + // Returns Actor if valid, otherwise nullptr + AActor* GetActorIfValid(bool bInTryLoad=true) const; + + // Returns BakedObject if valid, otherwise nullptr + UObject* GetBakedObjectIfValid(bool bInTryLoad=true) const; + + // Returns BakedComponent if valid, otherwise nullptr + UObject* GetBakedComponentIfValid(bool bInTryLoad=true) const; + + // Returns Blueprint if valid, otherwise nullptr + UBlueprint* GetBlueprintIfValid(bool bInTryLoad=true) const; + + // The actor that the baked output was associated with + UPROPERTY() + FString Actor; + + // The blueprint that baked output was associated with, if any + UPROPERTY() + FString Blueprint; + + // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. + UPROPERTY() + FName ActorBakeName = NAME_None; + + // The baked output asset + UPROPERTY() + FString BakedObject; + + // The baked output component + UPROPERTY() + FString BakedComponent; + + // In the case of instance actor component baking, this is the array of instanced actors + UPROPERTY() + TArray InstancedActors; + + // In the case of mesh split instancer baking: this is the array of instance components + UPROPERTY() + TArray InstancedComponents; +}; + +// Container to hold the map of baked objects. There should be one of +// these for each UHoudiniOutput. We manage this separately from UHoudiniOutput so +// that the "previous/last" bake objects can survive output reconstruction or PDG +// dirty/dirty all operations. +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutput +{ + GENERATED_USTRUCT_BODY() + + public: + UPROPERTY() + TMap BakedOutputObjects; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject +{ + GENERATED_USTRUCT_BODY() + + public: + + // The main output object + UPROPERTY() + UObject* OutputObject = nullptr; + + // The main output component + UPROPERTY() + UObject* OutputComponent = nullptr; + + // Proxy object + UPROPERTY() + UObject* ProxyObject = nullptr; + + // Proxy Component + UPROPERTY() + UObject* ProxyComponent = nullptr; + + // Mesh output properties + // If this is true the proxy mesh is "current", + // in other words, it is newer than the UStaticMesh + UPROPERTY() + bool bProxyIsCurrent = false; + + // Bake Name override for this output object + UPROPERTY() + FString BakeName; + + UPROPERTY() + FHoudiniCurveOutputProperties CurveOutputProperty; + + + // NOTE: The idea behind CachedAttributes and CachedTokens is to + // collect attributes (such as unreal_level_path and unreal_output_name) + // and context-specific tokens (hda name, hda actor name, geo and part ids, tile_id, etc) + // and cache them directly on the output object. When the object gets baked, + // certain tokens can be updated specifically for the bake pass afterwhich the + // the string / path attributes can be resolved with the updated tokens. + // + // A more concrete example: + // unreal_output_name = "{hda_actor_name}_PurplePlants_{geo_id}_{part_id}" + // unreal_level_path = "{out}/{hda_name}/{guid}/PurplePlants/{geo_id}/{part_id}" + // + // All of the aforementions tokens and attributes would be cached on the output object + // when it is being cooked so that the same values are available at bake time. During + // a bake some tokens may be updated, such as `{out}` to change where assets get serialized. + + UPROPERTY() + TMap CachedAttributes; + + // Cache any tokens here that is needed for string resolving + // at bake time. + UPROPERTY() + TMap CachedTokens; +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject +{ + GENERATED_UCLASS_BODY() + + // Declare translators as friend so they can easily directly modify + // and access our HGPO and Output objects + friend struct FHoudiniMeshTranslator; + friend struct FHoudiniInstanceTranslator; + + virtual ~UHoudiniOutput(); + +public: + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + const EHoudiniOutputType& GetType() const { return Type; }; + + const TArray& GetHoudiniGeoPartObjects() const { return HoudiniGeoPartObjects; }; + + // Returns true if we have a HGPO that matches + const bool HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const; + + // Returns true if the HGPO is fromn the same HF as us + const bool HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const; + + // Returns the output objects and their corresponding identifiers + TMap& GetOutputObjects() { return OutputObjects; }; + + // Returns the output objects and their corresponding identifiers + const TMap& GetOutputObjects() const { return OutputObjects; }; + + // Returns this output's assignement material map + TMap& GetAssignementMaterials() { return AssignementMaterials; }; + + // Returns this output's replacement material map + TMap& GetReplacementMaterials() { return ReplacementMaterials; }; + + // Returns the instanced outputs maps + TMap& GetInstancedOutputs() { return InstancedOutputs; }; + + const bool HasGeoChanged() const; + const bool HasTransformChanged() const; + const bool HasMaterialsChanged() const; + + // Returns true if there are any proxy objects in output (current or not) + const bool HasAnyProxy() const; + // Returns true if the specified identifier has a proxy object (current or not) + const bool HasProxy(const FHoudiniOutputObjectIdentifier &InIdentifier) const; + // Returns true if there are any current (most up to date and visible) proxy in the output + const bool HasAnyCurrentProxy() const; + // Returns true if the specified identifier's proxy is "current" (in other words, newer than + // the non-proxy and the proxy should thus be shown instead. + const bool IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const; + + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + void UpdateOutputType(); + + // Adds a new HoudiniGeoPartObject to our array + void AddNewHGPO(const FHoudiniGeoPartObject& InHGPO); + + // Mark all the current HGPO as stale (from a previous cook) + // So we can delte them all by calling DeleteAllStaleHGPOs after. + void MarkAllHGPOsAsStale(const bool& InStale); + + // Delete all the HGPO that were marked as stale + void DeleteAllStaleHGPOs(); + + void SetOutputObjects(const TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; + + void SetInstancedOutputs(const TMap& InInstancedOuput) { InstancedOutputs = InInstancedOuput; }; + + // Marks all HGPO and OutputIdentifier as loaded + void MarkAsLoaded(const bool& InLoaded); + + FORCEINLINE + const bool IsEditableNode() { return bIsEditableNode; }; + + FORCEINLINE + void SetIsEditableNode(bool IsEditable) { bIsEditableNode = IsEditable; } + + FORCEINLINE + const bool HasEditableNodeBuilt() { return bHasEditableNodeBuilt; }; + + FORCEINLINE + void SetHasEditableNodeBuilt(bool HasBuilt) { bHasEditableNodeBuilt = HasBuilt; } + + FORCEINLINE + void SetIsUpdating(bool bInIsUpdating) { bIsUpdating = bInIsUpdating; }; + + FORCEINLINE + bool IsUpdating() const { return bIsUpdating; }; + + FORCEINLINE + void SetLandscapeWorldComposition(const bool bInLandscapeWorldComposition) { bLandscapeWorldComposition = bInLandscapeWorldComposition; }; + + FORCEINLINE + bool IsLandscapeWorldComposition () const { return bLandscapeWorldComposition; }; + + FORCEINLINE + TArray & GetHoudiniCreatedSocketActors() { return HoudiniCreatedSocketActors; }; + + FORCEINLINE + TArray & GetHoudiniAttachedSocketActors() { return HoudiniAttachedSocketActors; } + + // Duplicate this object and copy its state to the resulting object. + // This is typically used to transfer state between between template and instance components. + UHoudiniOutput* DuplicateAndCopyProperties(UObject* DestOuter, FName NewName); + + // Copy properties but preserve output objects + virtual void CopyPropertiesFrom(UHoudiniOutput* InOutput, bool bCopyAllProperties); + + // Set whether this object can delete Houdini nodes. + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } + + //------------------------------------------------------------------------------------------------ + // Helpers + //------------------------------------------------------------------------------------------------ + static FString OutputTypeToString(const EHoudiniOutputType& InOutputType); + + FBox GetBounds() const; + + void Clear(); + +protected: + + virtual void BeginDestroy() override; + +protected: + + // Indicates the type of output we're dealing with + UPROPERTY() + EHoudiniOutputType Type; + + // The output's corresponding HGPO + UPROPERTY() + TArray HoudiniGeoPartObjects; + + // + UPROPERTY(DuplicateTransient) + TMap OutputObjects; + + // Instanced outputs + // Stores the instance variations objects (replacement), transform offsets + UPROPERTY() + TMap InstancedOutputs; + + // The material assignments for this output + UPROPERTY() + TMap AssignementMaterials; + + UPROPERTY() + TMap ReplacementMaterials; + + // Indicates the number of stale HGPO + int32 StaleCount; + + UPROPERTY() + bool bLandscapeWorldComposition; + + // stores the created actors for sockets with actor references. + // + UPROPERTY() + TArray HoudiniCreatedSocketActors; + + UPROPERTY() + TArray HoudiniAttachedSocketActors; + +private: + // Use HoudiniOutput to represent an editable curve. + // This flag tells whether this output is an editable curve. + UPROPERTY() + bool bIsEditableNode; + + // An editable node is only built once. This flag indicates whether this node has been built. + UPROPERTY(DuplicateTransient) + bool bHasEditableNodeBuilt; + + // The IsUpdating flag is set to true when this out exists and is being updated. + UPROPERTY() + bool bIsUpdating; + + UPROPERTY() + bool bCanDeleteHoudiniNodes; +}; + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h deleted file mode 100644 index f59a4c09d..000000000 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutputTypes.h +++ /dev/null @@ -1,30 +0,0 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp index fe8a371c6..c1b2c06a9 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp @@ -1,1481 +1,1481 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGAssetLink.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniOutput.h" - -#include "Engine/StaticMesh.h" -#include "GameFramework/Actor.h" -#include "Landscape.h" - -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#if WITH_EDITOR - #include "FileHelpers.h" - #include "EditorModeManager.h" - #include "EditorModes.h" -#endif - -// -UHoudiniPDGAssetLink::UHoudiniPDGAssetLink(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , AssetName() - , AssetNodePath() - , AssetID(-1) - , SelectedTOPNetworkIndex(-1) - , LinkState(EPDGLinkState::Inactive) - , bAutoCook(false) - , bUseTOPNodeFilter(true) - , bUseTOPOutputFilter(true) - , NumWorkitems(0) - , WorkItemTally() - , OutputCachePath() - , bNeedsUIRefresh(false) - , OutputParentActor(nullptr) -{ - TOPNodeFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER; - TOPOutputFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER; - -#if WITH_EDITORONLY_DATA - bBakeMenuExpanded = true; - HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; - PDGBakeSelectionOption = EPDGBakeSelectionOption::All; - PDGBakePackageReplaceMode = EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - bRecenterBakedActors = false; - bBakeAfterWorkResultObjectLoaded = false; -#endif - - // Folder used for baking PDG outputs - BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - // TODO: - // Update init, move default filter to PCH -} - -FTOPWorkResultObject::FTOPWorkResultObject() -{ - // ResultObjects = nullptr; - Name = FString(); - FilePath = FString(); - State = EPDGWorkResultState::None; -} - -FTOPWorkResultObject::~FTOPWorkResultObject() -{ - // DestroyResultOutputs(); -} - -FTOPWorkResult::FTOPWorkResult() -{ - WorkItemIndex = -1; - WorkItemID = -1; - - ResultObjects.SetNum(0); -} - -bool -FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const -{ - if (WorkItemIndex != OtherWorkResult.WorkItemIndex) - return false; - if (WorkItemID != OtherWorkResult.WorkItemID) - return false; - /* - if (ResultObjects != OtherWorkResult.ResultObjects) - return false; - */ - - return true; -} - - -FWorkItemTally::FWorkItemTally() -{ - TotalWorkItems = 0; - WaitingWorkItems = 0; - ScheduledWorkItems = 0; - CookingWorkItems = 0; - CookedWorkItems = 0; - ErroredWorkItems = 0; -} - -void -FWorkItemTally::ZeroAll() -{ - TotalWorkItems = 0; - WaitingWorkItems = 0; - ScheduledWorkItems = 0; - CookingWorkItems = 0; - CookedWorkItems = 0; - ErroredWorkItems = 0; -} - -bool -FWorkItemTally::AreAllWorkItemsComplete() const -{ - return ( - WaitingWorkItems == 0 && CookingWorkItems == 0 && ScheduledWorkItems == 0 - && (TotalWorkItems == (CookedWorkItems + ErroredWorkItems)) ); -} - -bool -FWorkItemTally::AnyWorkItemsFailed() const -{ - return ErroredWorkItems > 0; -} - -bool -FWorkItemTally::AnyWorkItemsPending() const -{ - return (TotalWorkItems > 0 && (WaitingWorkItems > 0 || CookingWorkItems > 0 || ScheduledWorkItems > 0)); -} - -FString -FWorkItemTally::ProgressRatio() const -{ - float Ratio = TotalWorkItems > 0 ? (CookedWorkItems / TotalWorkItems) * 100.f : 0; - - return FString::Printf(TEXT("%.1f%%"), Ratio); -} - - -UTOPNode::UTOPNode() -{ - NodeId = -1; - NodeName = FString(); - NodePath = FString(); - ParentName = FString(); - - WorkResultParent = nullptr; - WorkResult.SetNum(0); - - bHidden = false; - bAutoLoad = false; - - NodeState = EPDGNodeState::None; - - bCachedHaveNotLoadedWorkResults = false; - bCachedHaveLoadedWorkResults = false; - bHasChildNodes = false; - - bShow = false; -} - -bool -UTOPNode::operator==(const UTOPNode& Other) const -{ - if (!NodeName.Equals(Other.NodeName)) - return false; - - if (!ParentName.Equals(Other.ParentName)) - return false; - - //if (NodeId != Other.NodeId) - // return false; - - return true; -} - -void -UTOPNode::Reset() -{ - NodeState = EPDGNodeState::None; - WorkItemTally.ZeroAll(); -} - -void -UTOPNode::SetVisibleInLevel(bool bInVisible) -{ - if (bShow == bInVisible) - return; - - bShow = bInVisible; - UpdateOutputVisibilityInLevel(); -} - -void -UTOPNode::UpdateOutputVisibilityInLevel() -{ - AActor* Actor = OutputActorOwner.GetOutputActor(); - if (IsValid(Actor)) - { - Actor->SetHidden(!bShow); -#if WITH_EDITOR - Actor->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - AActor* WROActor = WRO.GetOutputActorOwner().GetOutputActor(); - if (IsValid(WROActor)) - { - WROActor->SetHidden(!bShow); -#if WITH_EDITOR - WROActor->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - - // We need to manually handle child landscape's visiblity - for (UHoudiniOutput* ResultOutput : WRO.GetResultOutputs()) - { - if (!ResultOutput || ResultOutput->IsPendingKill()) - continue; - - for (auto Pair : ResultOutput->GetOutputObjects()) - { - FHoudiniOutputObject OutputObject = Pair.Value; - ALandscapeProxy* LandscapeProxy = Cast(OutputObject.OutputObject); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) - continue; - - ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); - if (!Landscape || Landscape->IsPendingKill()) - continue; - - Landscape->SetHidden(!bShow); -#if WITH_EDITOR - Landscape->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - } - } - } -} - -void -UTOPNode::SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad) -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::NotLoaded || - (WRO.State == EPDGWorkResultState::Deleted && bInAlsoSetDeletedToLoad)) - WRO.State = EPDGWorkResultState::ToLoad; - } - } -} - -void -UTOPNode::SetLoadedWorkResultsToDelete() -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::Loaded) - WRO.State = EPDGWorkResultState::ToDelete; - } - } -} - - -void -UTOPNode::DeleteWorkResultOutputObjects() -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::Loaded) - { - // Delete and clean up that WRObj - WRO.DestroyResultOutputs(); - WRO.GetOutputActorOwner().DestroyOutputActor(); - WRO.State = EPDGWorkResultState::Deleted; - } - } - } - bCachedHaveLoadedWorkResults = false; -} - -FString -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex) -{ - return FString::Printf(TEXT("%d_%d"), InWorkResultIndex, InWorkResultObjectIndex); -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const -{ - // Check that indices are valid - if (!WorkResult.IsValidIndex(InWorkResultIndex)) - return false; - if (!WorkResult[InWorkResultIndex].ResultObjects.IsValidIndex(InWorkResultObjectIndex)) - return false; - - OutKey = GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex); - - return true; -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) -{ - FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) - return false; - OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); - if (!OutBakedOutput) - return false; - - return true; -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const -{ - FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) - return false; - OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); - if (!OutBakedOutput) - return false; - - return true; -} - -#if WITH_EDITOR -void -UTOPNode::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) -{ - Super::PostEditChangeChainProperty(InPropertyChangedEvent); - - const FName PropertyName = InPropertyChangedEvent.GetPropertyName(); - if (PropertyName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) - { - UpdateOutputVisibilityInLevel(); - } -} -#endif - -#if WITH_EDITOR -void -UTOPNode::PostTransacted(const FTransactionObjectEvent& TransactionEvent) -{ - Super::PostTransacted(TransactionEvent); - - if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) - return; - - bool bUpdateVisibility = false; - for (const FName& PropName : TransactionEvent.GetChangedProperties()) - { - if (PropName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) - { - bUpdateVisibility = true; - } - } - - if (bUpdateVisibility) - UpdateOutputVisibilityInLevel(); -} -#endif - -UTOPNetwork::UTOPNetwork() -{ - NodeId = -1; - NodeName = FString(); - - AllTOPNodes.SetNum(0); - SelectedTOPIndex = -1; - - ParentName = FString(); - - bShowResults = false; - bAutoLoadResults = false; -} - -bool -UTOPNetwork::operator==(const UTOPNetwork& Other) const -{ - if (!NodeName.Equals(Other.NodeName)) - return false; - - if (!ParentName.Equals(Other.ParentName)) - return false; - - //if (NodeId != Other.NodeId) - // return false; - - return true; -} - -void -UTOPNetwork::SetLoadedWorkResultsToDelete() -{ - for (UTOPNode* Node : AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - Node->SetLoadedWorkResultsToDelete(); - } -} - -void -UTOPNetwork::DeleteWorkResultOutputObjects() -{ - for (UTOPNode* Node : AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - Node->DeleteWorkResultOutputObjects(); - } -} - -bool -UTOPNetwork::AnyWorkItemsPending() const -{ - for (const UTOPNode* const TOPNode : AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->AnyWorkItemsPending()) - return true; - } - - return false; -} - - -void -UHoudiniPDGAssetLink::SelectTOPNetwork(const int32& AtIndex) -{ - if (!AllTOPNetworks.IsValidIndex(AtIndex)) - return; - - SelectedTOPNetworkIndex = AtIndex; -} - - -void -UHoudiniPDGAssetLink::SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex) -{ - if (!IsValid(InTOPNetwork)) - return; - - if (!InTOPNetwork->AllTOPNodes.IsValidIndex(AtIndex)) - return; - - InTOPNetwork->SelectedTOPIndex = AtIndex; -} - - -UTOPNetwork* -UHoudiniPDGAssetLink::GetSelectedTOPNetwork() -{ - return GetTOPNetwork(SelectedTOPNetworkIndex); -} - - -UTOPNode* -UHoudiniPDGAssetLink::GetSelectedTOPNode() -{ - UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); - if (!IsValid(SelectedTOPNetwork)) - return nullptr; - - if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) - return nullptr; - - UTOPNode* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; - if (!IsValid(SelectedTOPNode)) - return nullptr; - - return SelectedTOPNode; -} - -FString -UHoudiniPDGAssetLink::GetSelectedTOPNodeName() -{ - FString NodeName = FString(); - - const UTOPNode* const SelectedTOPNode = GetSelectedTOPNode(); - if (IsValid(SelectedTOPNode)) - NodeName = SelectedTOPNode->NodeName; - - return NodeName; -} - -FString -UHoudiniPDGAssetLink::GetSelectedTOPNetworkName() -{ - FString NetworkName = FString(); - - const UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNetwork)) - NetworkName = SelectedTOPNetwork->NodeName; - - return NetworkName; -} - -UTOPNetwork* -UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) -{ - if(AllTOPNetworks.IsValidIndex(AtIndex)) - { - return AllTOPNetworks[AtIndex]; - } - - return nullptr; -} - -UTOPNetwork* -UHoudiniPDGAssetLink::GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex) -{ - OutIndex = INDEX_NONE; - int32 Index = -1; - for (UTOPNetwork* CurrentTOPNet : InTOPNetworks) - { - Index += 1; - - if (!IsValid(CurrentTOPNet)) - continue; - - if (CurrentTOPNet->NodePath.Equals(InNodePath)) - { - OutIndex = Index; - return CurrentTOPNet; - } - } - - return nullptr; -} - -UTOPNode* -UHoudiniPDGAssetLink::GetParentTOPNode(const UTOPNode* InNode) -{ - if (!IsValid(InNode)) - return nullptr; - - FString NodePath = InNode->NodePath; - FString ParentPath; - - if (NodePath.EndsWith("/")) - NodePath.LeftChopInline(1); - - if (NodePath.Split("/", &ParentPath, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromEnd) && !ParentPath.IsEmpty()) - { - for (UTOPNetwork* TOPNet : AllTOPNetworks) - { - if (!IsValid(TOPNet)) - continue; - - for (UTOPNode* TOPNode : TOPNet->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->NodePath == ParentPath && InNode->NodeId != TOPNode->NodeId) - { - return TOPNode; - } - } - } - } - - return nullptr; -} - -UTOPNode* -UHoudiniPDGAssetLink::GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex) -{ - OutIndex = INDEX_NONE; - int32 Index = -1; - for (UTOPNode* CurrentTOPNode : InTOPNodes) - { - Index += 1; - - if (!IsValid(CurrentTOPNode)) - continue; - - if (CurrentTOPNode->NodePath.Equals(InNodePath)) - { - OutIndex = Index; - return CurrentTOPNode; - } - } - - return nullptr; -} - -void -UHoudiniPDGAssetLink::ClearAllTOPData() -{ - // Clears all TOP data - for(UTOPNetwork* CurrentNetwork : AllTOPNetworks) - { - if (!IsValid(CurrentNetwork)) - continue; - - for(UTOPNode* CurrentTOPNode : CurrentNetwork->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - ClearTOPNodeWorkItemResults(CurrentTOPNode); - } - } - - AllTOPNetworks.Empty(); -} - -void -UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork) -{ - if (!IsValid(TOPNetwork)) - return; - - for(UTOPNode* CurrentTOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - ClearTOPNodeWorkItemResults(CurrentTOPNode); - } -} - -void -UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) -{ - if (!IsValid(TOPNode)) - return; - - for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) - { - DestroyWorkItemResultData(CurrentWorkResult, TOPNode); - } - TOPNode->WorkResult.Empty(); - - FOutputActorOwner& OutputActorOwner = TOPNode->GetOutputActorOwner(); - AActor* OutputActor = OutputActorOwner.GetOutputActor(); - if (IsValid(OutputActor)) - { - // Destroy any attached actors (which we'll assume that any attachments left - // are untracked actors associated with the TOPNode) - TArray AttachedActors; - OutputActor->GetAttachedActors(AttachedActors); - for (AActor* Actor : AttachedActors) - { - if (!IsValid(Actor)) - continue; - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - } - } - - if (TOPNode->WorkResultParent && !TOPNode->WorkResultParent->IsPendingKill()) - { - - // TODO: Destroy the Parent Object - // DestroyImmediate(topNode._workResultParentGO); - } - - OutputActorOwner.DestroyOutputActor(); -} - - -void -UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); - if (WorkResult) - { - DestroyWorkItemResultData(*WorkResult, InTOPNode); - // TODO: Should we destroy the FTOPWorkResult struct entirely here? - //TOPNode.WorkResult.RemoveByPredicate - } -} - -void -UHoudiniPDGAssetLink::DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - // TODO: Update ClearWorkItemResultByID or GetWorkResultByID to return the index of the work item - // so that we don't have to find its index again to remove it from the array - ClearWorkItemResultByID(InWorkItemID, InTOPNode); - // Find the index of the FTOPWorkResult for InWorkItemID in InTOPNode.WorkResult and remove it - const int32 Index = InTOPNode->WorkResult.IndexOfByPredicate( - [InWorkItemID](const FTOPWorkResult& InWorkItem) { return InWorkItem.WorkItemID == InWorkItemID; }); - if (Index != INDEX_NONE && Index >= 0) - InTOPNode->WorkResult.RemoveAt(Index); -} - -FTOPWorkResult* -UHoudiniPDGAssetLink::GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return nullptr; - - for(FTOPWorkResult& CurResult : InTOPNode->WorkResult) - { - if (CurResult.WorkItemID == InWorkItemID) - { - return &CurResult; - } - } - - return nullptr; -} - -FDirectoryPath -UHoudiniPDGAssetLink::GetTemporaryCookFolder() const -{ - UObject* Owner = GetOuter(); - UHoudiniAssetComponent* HAC = Cast(Owner); - if (HAC) - return HAC->TemporaryCookFolder; - - FDirectoryPath TempPath; - TempPath.Path = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - return TempPath; -} - -void -UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) -{ - ResultObject.DestroyResultOutputs(); - ResultObject.GetOutputActorOwner().DestroyOutputActor(); -} - -void -UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode) -{ - if (Result.ResultObjects.Num() <= 0) - return; - - for (FTOPWorkResultObject& ResultObject : Result.ResultObjects) - { - DestoryWorkResultObjectData(ResultObject); - } - - Result.ResultObjects.Empty(); -} - - -UTOPNode* -UHoudiniPDGAssetLink::GetTOPNode(const int32& InNodeID) -{ - for (UTOPNetwork* CurrentTOPNet : AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - if (CurrentTOPNode->NodeId == InNodeID) - return CurrentTOPNode; - } - } - - return nullptr; -} - -void -UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork) -{ - if (!IsValid(InNode) || !IsValid(InNetwork)) - return; - - if (!InNode->bHasChildNodes) - return; - - FString PrefixPath = InNode->NodePath; - if (!PrefixPath.EndsWith("/")) - PrefixPath += "/"; - InNode->WorkItemTally.ZeroAll(); - InNode->NodeState = EPDGNodeState::None; - - TMap NodeStateOrder; - NodeStateOrder.Add(EPDGNodeState::None, 0); - NodeStateOrder.Add(EPDGNodeState::Cook_Complete, 1); - NodeStateOrder.Add(EPDGNodeState::Dirtied, 2); - NodeStateOrder.Add(EPDGNodeState::Cook_Failed, 3); - NodeStateOrder.Add(EPDGNodeState::Dirtying, 4); - NodeStateOrder.Add(EPDGNodeState::Cooking, 5); - - int8 CurrentState = 0; - - for (const UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - if (Node->NodePath.StartsWith(PrefixPath) && !Node->bHasChildNodes) - { - InNode->WorkItemTally.TotalWorkItems += Node->WorkItemTally.TotalWorkItems; - InNode->WorkItemTally.WaitingWorkItems += Node->WorkItemTally.WaitingWorkItems; - InNode->WorkItemTally.ScheduledWorkItems += Node->WorkItemTally.ScheduledWorkItems; - InNode->WorkItemTally.CookingWorkItems += Node->WorkItemTally.CookingWorkItems; - InNode->WorkItemTally.CookedWorkItems += Node->WorkItemTally.CookedWorkItems; - InNode->WorkItemTally.ErroredWorkItems += Node->WorkItemTally.ErroredWorkItems; - const int8 VisistedNodeState = NodeStateOrder.FindChecked(Node->NodeState); - if (VisistedNodeState > CurrentState) - CurrentState = VisistedNodeState; - } - } - - EPDGNodeState const* const NewState = NodeStateOrder.FindKey(CurrentState); - if (NewState) - InNode->NodeState = *NewState; -} - -void -UHoudiniPDGAssetLink::UpdateWorkItemTally() -{ - WorkItemTally.ZeroAll(); - for(UTOPNetwork* CurrentTOPNet : AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - for(UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - // Only add up the tallys from nodes without children (since parent's aggregate the child work items counts) - if (CurrentTOPNode->bHasChildNodes) - { - UpdateTOPNodeWithChildrenWorkItemTallyAndState(CurrentTOPNode, CurrentTOPNet); - } - else - { - WorkItemTally.TotalWorkItems += CurrentTOPNode->WorkItemTally.TotalWorkItems; - WorkItemTally.WaitingWorkItems += CurrentTOPNode->WorkItemTally.WaitingWorkItems; - WorkItemTally.ScheduledWorkItems += CurrentTOPNode->WorkItemTally.ScheduledWorkItems; - WorkItemTally.CookingWorkItems += CurrentTOPNode->WorkItemTally.CookingWorkItems; - WorkItemTally.CookedWorkItems += CurrentTOPNode->WorkItemTally.CookedWorkItems; - WorkItemTally.ErroredWorkItems += CurrentTOPNode->WorkItemTally.ErroredWorkItems; - } - } - } -} - - -void -UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork) -{ - if (!IsValid(TOPNetwork)) - return; - - for (UTOPNode* CurTOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(CurTOPNode)) - continue; - - CurTOPNode->WorkItemTally.ZeroAll(); - } -} - - -FString -UHoudiniPDGAssetLink::GetAssetLinkStatus(const EPDGLinkState& InLinkState) -{ - FString Status; - switch (InLinkState) - { - case EPDGLinkState::Inactive: - Status = TEXT("Inactive"); - case EPDGLinkState::Linking: - Status = TEXT("Linking"); - case EPDGLinkState::Linked: - Status = TEXT("Linked"); - case EPDGLinkState::Error_Not_Linked: - Status = TEXT("Not Linked"); - default: - Status = TEXT(""); - } - - return Status; -} - -FString -UHoudiniPDGAssetLink::GetTOPNodeStatus(const UTOPNode* InTOPNode) -{ - static const FString InvalidOrUnknownStatus = TEXT(""); - - if (!IsValid(InTOPNode)) - return InvalidOrUnknownStatus; - - if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) - { - return TEXT("Cook Failed"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) - { - return TEXT("Cook Completed"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Cooking) - { - return TEXT("Cook In Progress"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) - { - return TEXT("Dirtied"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) - { - return TEXT("Dirtying"); - } - - return InvalidOrUnknownStatus; -} - -FLinearColor -UHoudiniPDGAssetLink::GetTOPNodeStatusColor(const UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return FLinearColor::White; - - if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) - { - return FLinearColor::Red; - } - else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) - { - return FLinearColor::Green; - } - else if (InTOPNode->NodeState == EPDGNodeState::Cooking) - { - return FLinearColor(0.0, 1.0f, 1.0f); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) - { - return FLinearColor(1.0f, 0.5f, 0.0f); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) - { - return FLinearColor::Yellow; - } - - return FLinearColor::White; -} - -AActor* -UHoudiniPDGAssetLink::GetOwnerActor() const -{ - UObject* Outer = GetOuter(); - UActorComponent* Component = Cast(Outer); - if (IsValid(Component)) - return Component->GetOwner(); - else - return Cast(Outer); -} - -bool -UHoudiniPDGAssetLink::HasTemporaryOutputs() const -{ - // Loop over all networks, all nodes, all work items and check for any valid output objects - for (const UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (const UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - for (const FTOPWorkResult& WorkResult : TOPNode->WorkResult) - { - for (const FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) - { - // If the WorkResultObject's actor is not valid, then it no longer has temporary objects in the - // scene - if (!IsValid(WorkResultObject.GetOutputActorOwner().GetOutputActor())) - continue; - - for (UHoudiniOutput* Output : WorkResultObject.GetResultOutputs()) - { - if (!IsValid(Output)) - continue; - - const EHoudiniOutputType OutputType = Output->GetType(); - for (const auto& OutputObjectPair : Output->GetOutputObjects()) - { - if ((OutputType == EHoudiniOutputType::Landscape && IsValid(OutputObjectPair.Value.OutputObject)) || - IsValid(OutputObjectPair.Value.OutputComponent)) - { - return true; - } - } - } - } - } - } - } - - return false; -} - -void -UHoudiniPDGAssetLink::UpdatePostDuplicate() -{ - // Loop over all networks, all nodes, all work items and clear output actors - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - for (FTOPWorkResult& WorkResult : TOPNode->WorkResult) - { - for (FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) - { - WorkResultObject.GetOutputActorOwner().SetOutputActor(nullptr); - WorkResultObject.State = EPDGWorkResultState::None; - WorkResultObject.SetResultOutputs(TArray()); - } - } - TOPNode->GetOutputActorOwner().SetOutputActor(nullptr); - TOPNode->bCachedHaveNotLoadedWorkResults = false; - TOPNode->bCachedHaveLoadedWorkResults = false; - } - } -} - -void -UHoudiniPDGAssetLink::UpdateTOPNodeAutoloadAndVisibility() -{ - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->bAutoLoad) - { - // // Set work results that are cooked but in NotLoaded state to ToLoad - // TOPNode.SetNotLoadedWorkResultsToLoad(); - } - - TOPNode->UpdateOutputVisibilityInLevel(); - } - } -} - -void -UHoudiniPDGAssetLink::FilterTOPNodesAndOutputs() -{ - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - // TOP Node visibility filter via TOPNodeFilter - if (bUseTOPNodeFilter) - { - TOPNode->bHidden = !TOPNodeFilter.IsEmpty() && !TOPNode->NodeName.StartsWith(TOPNodeFilter); - } - else - { - TOPNode->bHidden = false; - } - - // Auto load results filter via TOPNodeOutputFilter - if (bUseTOPOutputFilter) - { - const bool bNewAutoLoad = TOPOutputFilter.IsEmpty() || TOPNode->NodeName.StartsWith(TOPOutputFilter); - if (bNewAutoLoad != TOPNode->bAutoLoad) - { - if (bNewAutoLoad) - { - // Set work results that are cooked but in NotLoaded state to ToLoad - TOPNode->bAutoLoad = true; - // TOPNode->SetNotLoadedWorkResultsToLoad(); - TOPNode->SetVisibleInLevel(true); - } - else - { - TOPNode->bAutoLoad = false; - TOPNode->SetVisibleInLevel(false); - } - TOPNode->UpdateOutputVisibilityInLevel(); - } - } - } - } -} - -#if WITH_EDITORONLY_DATA -void -UHoudiniPDGAssetLink::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedChainEvent) -{ - Super::PostEditChangeChainProperty(InPropertyChangedChainEvent); - - const FName PropertyName = InPropertyChangedChainEvent.GetPropertyName(); - if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) - { - // Refilter TOP nodes - FilterTOPNodesAndOutputs(); - bNeedsUIRefresh = true; - } - else if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) - { - bNeedsUIRefresh = true; - } -} - -#endif - -#if WITH_EDITORONLY_DATA -void -UHoudiniPDGAssetLink::PostTransacted(const FTransactionObjectEvent& TransactionEvent) -{ - Super::PostTransacted(TransactionEvent); - - if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) - return; - - bool bDoFilterTOPNodesAndOutputs = false; - for (const FName& PropName : TransactionEvent.GetChangedProperties()) - { - if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) - { - bDoFilterTOPNodesAndOutputs = true; - bNeedsUIRefresh = true; - } - else if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) - { - bNeedsUIRefresh = true; - } - } - - if (bDoFilterTOPNodesAndOutputs) - FilterTOPNodesAndOutputs(); -} -#endif - -void -FTOPWorkResultObject::DestroyResultOutputs() -{ - // Delete output components and gather output objects for deletion - bool bDidDestroyObjects = false; - bool bDidModifyFoliage = false; - - AActor* const OutputActor = OutputActorOwner.GetOutputActor(); - - for (UHoudiniOutput* CurOutput : ResultOutputs) - { - for (auto& Pair : CurOutput->GetOutputObjects()) - { - FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - FHoudiniOutputObject& OutputObject = Pair.Value; - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) - { - // Instancer components require some special handling around foliage - // TODO: move/refactor so that we can use the InstanceTranslator's helper functions (RemoveAndDestroyComponent and CleanupFoliageInstances) - bool bDestroyComponent = true; - if (OutputObject.OutputComponent->IsA()) - { - UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OutputObject.OutputComponent); - if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) - { - // Make sure foliage our foliage instances have been removed - USceneComponent* ParentComponent = nullptr; - if (IsValid(OutputActor)) - ParentComponent = Cast(OutputActor->GetRootComponent()); - else - ParentComponent = Cast(HISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) - { - UStaticMesh* FoliageSM = HISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - return; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(HISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return; - - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - return; -#if WITH_EDITOR - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); - - // Remove the foliage type if it doesn't have any more instances - if(HISMC->GetInstanceCount() == 0) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - bDidModifyFoliage = true; -#endif - } - - // do not delete FISMC that still have instances left - // as we have cleaned up our instances before, these have been hand-placed - if (HISMC->GetInstanceCount() > 0) - bDestroyComponent = false; - } - - if (bDestroyComponent) - { - USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from its actor first - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - // Detach from its parent component if attached - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - bDidDestroyObjects = true; - - OutputObject.OutputComponent = nullptr; - } - } - } - } - if (OutputObject.OutputObject && !OutputObject.OutputObject->IsPendingKill()) - { - // For actors we detach them first and then destroy - AActor* Actor = Cast(OutputObject.OutputObject); - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - if (LandscapePtr) - { - Actor = LandscapePtr->GetRawPtr(); - } - - if (Actor) - { - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - - bDidDestroyObjects = true; - } - else - { - // ... if not an actor, mark as pending kill - // OutputObject.OutputObject->MarkPendingKill(); - if (IsValid(OutputObject.OutputObject)) - OutputObjectsToDelete.Add(OutputObject.OutputObject); - OutputObject.OutputObject = nullptr; - } - } - } - } - - ResultOutputs.Empty(); - - if (bDidDestroyObjects) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - // Delete the output objects we found - if (OutputObjectsToDelete.Num() > 0) - FHoudiniEngineRuntimeUtils::SafeDeleteObjects(OutputObjectsToDelete); - -#if WITH_EDITOR - if (bDidModifyFoliage) - { - // Repopulate the foliage types in the foliage mode UI if foliage mode is active - // There is a helper function in FHoudiniEngineUtils for this, but we cannot access it from this module. - // TODO: refactor? - FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); - if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) - { - EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); - EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); - } - } -#endif -} - -bool -FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) -{ - // InAssetLink and InWorld must not be null - if (!InAssetLink || InAssetLink->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InAssetLink is null!")); - return false; - } - if (!InWorld || InWorld->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InWorld is null!")); - return false; - } - - AActor* AssetLinkActor = InAssetLink->GetOwnerActor(); - - const bool bParentActorIsValid = IsValid(InParentActor); - ULevel* LevelToSpawnIn = nullptr; - if (bParentActorIsValid) - { - LevelToSpawnIn = InParentActor->GetLevel(); - } - else - { - // Get the level containing the asset link's actor - if (IsValid(AssetLinkActor)) - LevelToSpawnIn = AssetLinkActor->GetLevel(); - } - - // Fallback to InWorld's current level - UWorld* WorldToSpawnIn = nullptr; - if (!IsValid(LevelToSpawnIn)) - { - LevelToSpawnIn = InWorld->GetCurrentLevel(); - WorldToSpawnIn = InWorld; - } - else - { - WorldToSpawnIn = LevelToSpawnIn->GetWorld(); - } - - if (!IsValid(WorldToSpawnIn) || !IsValid(LevelToSpawnIn)) - { - HOUDINI_LOG_WARNING( - TEXT("Could not determine level and world to spawn PDG output actor in: asset link %s, name %s"), - *(InAssetLink->GetPathName()), - *(InName.ToString())); - return false; - } - - FActorSpawnParameters SpawnParams; - SpawnParams.Name = MakeUniqueObjectName(InWorld, AActor::StaticClass(), InName); - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; - SpawnParams.OverrideLevel = LevelToSpawnIn; - AActor *Actor = WorldToSpawnIn->SpawnActor(SpawnParams); - SetOutputActor(Actor); -#if WITH_EDITOR - Actor->SetActorLabel(InName.ToString()); -#endif - - // Set the actor transform: create a root component if it does not have one - USceneComponent* RootComponent = Actor->GetRootComponent(); - if (!RootComponent || RootComponent->IsPendingKill()) - { - RootComponent = NewObject(Actor, USceneComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - RootComponent->CreationMethod = EComponentCreationMethod::Instance; - Actor->SetRootComponent(RootComponent); - RootComponent->OnComponentCreated(); - RootComponent->RegisterComponent(); - } - - RootComponent->SetVisibility(true); - RootComponent->SetMobility(EComponentMobility::Static); - - const FVector ActorSpawnLocation = InParentActor ? InParentActor->GetActorLocation() : FVector::ZeroVector; - const FRotator ActorSpawnRotator = InParentActor ? InParentActor->GetActorRotation() : FRotator::ZeroRotator; - Actor->SetActorLocation(ActorSpawnLocation); - Actor->SetActorRotation(ActorSpawnRotator); - -#if WITH_EDITOR - if (IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) - { - Actor->SetFolderPath(InParentActor->GetFolderPath()); - Actor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); - } - else if (IsValid(AssetLinkActor) && AssetLinkActor->GetLevel() == LevelToSpawnIn) - { - Actor->SetFolderPath(*FString::Format( - TEXT("{0}/{1}_Output"), - { FStringFormatArg(AssetLinkActor->GetFolderPath().ToString()), FStringFormatArg(AssetLinkActor->GetActorLabel()) } - )); - } - else - { - Actor->SetFolderPath(*FString::Format(TEXT("{0}_Output"), { FStringFormatArg(InAssetLink->GetName()) })); - } -#else - if(IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) - { - OutputActor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); - } -#endif - - return true; -} - -bool -FOutputActorOwner::DestroyOutputActor() -{ - bool bDestroyed = false; - AActor *Actor = GetOutputActor(); - if (IsValid(Actor)) - { - // Detach from parent before destroying the actor - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - - bDestroyed = true; - } - - SetOutputActor(nullptr); - - return bDestroyed; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGAssetLink.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniOutput.h" + +#include "Engine/StaticMesh.h" +#include "GameFramework/Actor.h" +#include "Landscape.h" + +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#if WITH_EDITOR + #include "FileHelpers.h" + #include "EditorModeManager.h" + #include "EditorModes.h" +#endif + +// +UHoudiniPDGAssetLink::UHoudiniPDGAssetLink(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , AssetName() + , AssetNodePath() + , AssetID(-1) + , SelectedTOPNetworkIndex(-1) + , LinkState(EPDGLinkState::Inactive) + , bAutoCook(false) + , bUseTOPNodeFilter(true) + , bUseTOPOutputFilter(true) + , NumWorkitems(0) + , WorkItemTally() + , OutputCachePath() + , bNeedsUIRefresh(false) + , OutputParentActor(nullptr) +{ + TOPNodeFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER; + TOPOutputFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER; + +#if WITH_EDITORONLY_DATA + bBakeMenuExpanded = true; + HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; + PDGBakeSelectionOption = EPDGBakeSelectionOption::All; + PDGBakePackageReplaceMode = EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + bRecenterBakedActors = false; + bBakeAfterWorkResultObjectLoaded = false; +#endif + + // Folder used for baking PDG outputs + BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + // TODO: + // Update init, move default filter to PCH +} + +FTOPWorkResultObject::FTOPWorkResultObject() +{ + // ResultObjects = nullptr; + Name = FString(); + FilePath = FString(); + State = EPDGWorkResultState::None; +} + +FTOPWorkResultObject::~FTOPWorkResultObject() +{ + // DestroyResultOutputs(); +} + +FTOPWorkResult::FTOPWorkResult() +{ + WorkItemIndex = -1; + WorkItemID = -1; + + ResultObjects.SetNum(0); +} + +bool +FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const +{ + if (WorkItemIndex != OtherWorkResult.WorkItemIndex) + return false; + if (WorkItemID != OtherWorkResult.WorkItemID) + return false; + /* + if (ResultObjects != OtherWorkResult.ResultObjects) + return false; + */ + + return true; +} + + +FWorkItemTally::FWorkItemTally() +{ + TotalWorkItems = 0; + WaitingWorkItems = 0; + ScheduledWorkItems = 0; + CookingWorkItems = 0; + CookedWorkItems = 0; + ErroredWorkItems = 0; +} + +void +FWorkItemTally::ZeroAll() +{ + TotalWorkItems = 0; + WaitingWorkItems = 0; + ScheduledWorkItems = 0; + CookingWorkItems = 0; + CookedWorkItems = 0; + ErroredWorkItems = 0; +} + +bool +FWorkItemTally::AreAllWorkItemsComplete() const +{ + return ( + WaitingWorkItems == 0 && CookingWorkItems == 0 && ScheduledWorkItems == 0 + && (TotalWorkItems == (CookedWorkItems + ErroredWorkItems)) ); +} + +bool +FWorkItemTally::AnyWorkItemsFailed() const +{ + return ErroredWorkItems > 0; +} + +bool +FWorkItemTally::AnyWorkItemsPending() const +{ + return (TotalWorkItems > 0 && (WaitingWorkItems > 0 || CookingWorkItems > 0 || ScheduledWorkItems > 0)); +} + +FString +FWorkItemTally::ProgressRatio() const +{ + float Ratio = TotalWorkItems > 0 ? (CookedWorkItems / TotalWorkItems) * 100.f : 0; + + return FString::Printf(TEXT("%.1f%%"), Ratio); +} + + +UTOPNode::UTOPNode() +{ + NodeId = -1; + NodeName = FString(); + NodePath = FString(); + ParentName = FString(); + + WorkResultParent = nullptr; + WorkResult.SetNum(0); + + bHidden = false; + bAutoLoad = false; + + NodeState = EPDGNodeState::None; + + bCachedHaveNotLoadedWorkResults = false; + bCachedHaveLoadedWorkResults = false; + bHasChildNodes = false; + + bShow = false; +} + +bool +UTOPNode::operator==(const UTOPNode& Other) const +{ + if (!NodeName.Equals(Other.NodeName)) + return false; + + if (!ParentName.Equals(Other.ParentName)) + return false; + + //if (NodeId != Other.NodeId) + // return false; + + return true; +} + +void +UTOPNode::Reset() +{ + NodeState = EPDGNodeState::None; + WorkItemTally.ZeroAll(); +} + +void +UTOPNode::SetVisibleInLevel(bool bInVisible) +{ + if (bShow == bInVisible) + return; + + bShow = bInVisible; + UpdateOutputVisibilityInLevel(); +} + +void +UTOPNode::UpdateOutputVisibilityInLevel() +{ + AActor* Actor = OutputActorOwner.GetOutputActor(); + if (IsValid(Actor)) + { + Actor->SetHidden(!bShow); +#if WITH_EDITOR + Actor->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + AActor* WROActor = WRO.GetOutputActorOwner().GetOutputActor(); + if (IsValid(WROActor)) + { + WROActor->SetHidden(!bShow); +#if WITH_EDITOR + WROActor->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + + // We need to manually handle child landscape's visiblity + for (UHoudiniOutput* ResultOutput : WRO.GetResultOutputs()) + { + if (!ResultOutput || ResultOutput->IsPendingKill()) + continue; + + for (auto& Pair : ResultOutput->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = Pair.Value; + ALandscapeProxy* LandscapeProxy = Cast(OutputObject.OutputObject); + if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + continue; + + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (!Landscape || Landscape->IsPendingKill()) + continue; + + Landscape->SetHidden(!bShow); +#if WITH_EDITOR + Landscape->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + } + } + } +} + +void +UTOPNode::SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad) +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::NotLoaded || + (WRO.State == EPDGWorkResultState::Deleted && bInAlsoSetDeletedToLoad)) + WRO.State = EPDGWorkResultState::ToLoad; + } + } +} + +void +UTOPNode::SetLoadedWorkResultsToDelete() +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::Loaded) + WRO.State = EPDGWorkResultState::ToDelete; + } + } +} + + +void +UTOPNode::DeleteWorkResultOutputObjects() +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::Loaded) + { + // Delete and clean up that WRObj + WRO.DestroyResultOutputs(); + WRO.GetOutputActorOwner().DestroyOutputActor(); + WRO.State = EPDGWorkResultState::Deleted; + } + } + } + bCachedHaveLoadedWorkResults = false; +} + +FString +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex) +{ + return FString::Printf(TEXT("%d_%d"), InWorkResultIndex, InWorkResultObjectIndex); +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const +{ + // Check that indices are valid + if (!WorkResult.IsValidIndex(InWorkResultIndex)) + return false; + if (!WorkResult[InWorkResultIndex].ResultObjects.IsValidIndex(InWorkResultObjectIndex)) + return false; + + OutKey = GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex); + + return true; +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) +{ + FString Key; + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) + return false; + OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); + if (!OutBakedOutput) + return false; + + return true; +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const +{ + FString Key; + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) + return false; + OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); + if (!OutBakedOutput) + return false; + + return true; +} + +#if WITH_EDITOR +void +UTOPNode::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) +{ + Super::PostEditChangeChainProperty(InPropertyChangedEvent); + + const FName PropertyName = InPropertyChangedEvent.GetPropertyName(); + if (PropertyName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) + { + UpdateOutputVisibilityInLevel(); + } +} +#endif + +#if WITH_EDITOR +void +UTOPNode::PostTransacted(const FTransactionObjectEvent& TransactionEvent) +{ + Super::PostTransacted(TransactionEvent); + + if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) + return; + + bool bUpdateVisibility = false; + for (const FName& PropName : TransactionEvent.GetChangedProperties()) + { + if (PropName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) + { + bUpdateVisibility = true; + } + } + + if (bUpdateVisibility) + UpdateOutputVisibilityInLevel(); +} +#endif + +UTOPNetwork::UTOPNetwork() +{ + NodeId = -1; + NodeName = FString(); + + AllTOPNodes.SetNum(0); + SelectedTOPIndex = -1; + + ParentName = FString(); + + bShowResults = false; + bAutoLoadResults = false; +} + +bool +UTOPNetwork::operator==(const UTOPNetwork& Other) const +{ + if (!NodeName.Equals(Other.NodeName)) + return false; + + if (!ParentName.Equals(Other.ParentName)) + return false; + + //if (NodeId != Other.NodeId) + // return false; + + return true; +} + +void +UTOPNetwork::SetLoadedWorkResultsToDelete() +{ + for (UTOPNode* Node : AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + Node->SetLoadedWorkResultsToDelete(); + } +} + +void +UTOPNetwork::DeleteWorkResultOutputObjects() +{ + for (UTOPNode* Node : AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + Node->DeleteWorkResultOutputObjects(); + } +} + +bool +UTOPNetwork::AnyWorkItemsPending() const +{ + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->AnyWorkItemsPending()) + return true; + } + + return false; +} + + +void +UHoudiniPDGAssetLink::SelectTOPNetwork(const int32& AtIndex) +{ + if (!AllTOPNetworks.IsValidIndex(AtIndex)) + return; + + SelectedTOPNetworkIndex = AtIndex; +} + + +void +UHoudiniPDGAssetLink::SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex) +{ + if (!IsValid(InTOPNetwork)) + return; + + if (!InTOPNetwork->AllTOPNodes.IsValidIndex(AtIndex)) + return; + + InTOPNetwork->SelectedTOPIndex = AtIndex; +} + + +UTOPNetwork* +UHoudiniPDGAssetLink::GetSelectedTOPNetwork() +{ + return GetTOPNetwork(SelectedTOPNetworkIndex); +} + + +UTOPNode* +UHoudiniPDGAssetLink::GetSelectedTOPNode() +{ + UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNetwork)) + return nullptr; + + if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) + return nullptr; + + UTOPNode* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; + if (!IsValid(SelectedTOPNode)) + return nullptr; + + return SelectedTOPNode; +} + +FString +UHoudiniPDGAssetLink::GetSelectedTOPNodeName() +{ + FString NodeName = FString(); + + const UTOPNode* const SelectedTOPNode = GetSelectedTOPNode(); + if (IsValid(SelectedTOPNode)) + NodeName = SelectedTOPNode->NodeName; + + return NodeName; +} + +FString +UHoudiniPDGAssetLink::GetSelectedTOPNetworkName() +{ + FString NetworkName = FString(); + + const UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNetwork)) + NetworkName = SelectedTOPNetwork->NodeName; + + return NetworkName; +} + +UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) +{ + if(AllTOPNetworks.IsValidIndex(AtIndex)) + { + return AllTOPNetworks[AtIndex]; + } + + return nullptr; +} + +UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex) +{ + OutIndex = INDEX_NONE; + int32 Index = -1; + for (UTOPNetwork* CurrentTOPNet : InTOPNetworks) + { + Index += 1; + + if (!IsValid(CurrentTOPNet)) + continue; + + if (CurrentTOPNet->NodePath.Equals(InNodePath)) + { + OutIndex = Index; + return CurrentTOPNet; + } + } + + return nullptr; +} + +UTOPNode* +UHoudiniPDGAssetLink::GetParentTOPNode(const UTOPNode* InNode) +{ + if (!IsValid(InNode)) + return nullptr; + + FString NodePath = InNode->NodePath; + FString ParentPath; + + if (NodePath.EndsWith("/")) + NodePath.LeftChopInline(1); + + if (NodePath.Split("/", &ParentPath, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromEnd) && !ParentPath.IsEmpty()) + { + for (UTOPNetwork* TOPNet : AllTOPNetworks) + { + if (!IsValid(TOPNet)) + continue; + + for (UTOPNode* TOPNode : TOPNet->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->NodePath == ParentPath && InNode->NodeId != TOPNode->NodeId) + { + return TOPNode; + } + } + } + } + + return nullptr; +} + +UTOPNode* +UHoudiniPDGAssetLink::GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex) +{ + OutIndex = INDEX_NONE; + int32 Index = -1; + for (UTOPNode* CurrentTOPNode : InTOPNodes) + { + Index += 1; + + if (!IsValid(CurrentTOPNode)) + continue; + + if (CurrentTOPNode->NodePath.Equals(InNodePath)) + { + OutIndex = Index; + return CurrentTOPNode; + } + } + + return nullptr; +} + +void +UHoudiniPDGAssetLink::ClearAllTOPData() +{ + // Clears all TOP data + for(UTOPNetwork* CurrentNetwork : AllTOPNetworks) + { + if (!IsValid(CurrentNetwork)) + continue; + + for(UTOPNode* CurrentTOPNode : CurrentNetwork->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + ClearTOPNodeWorkItemResults(CurrentTOPNode); + } + } + + AllTOPNetworks.Empty(); +} + +void +UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork) +{ + if (!IsValid(TOPNetwork)) + return; + + for(UTOPNode* CurrentTOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + ClearTOPNodeWorkItemResults(CurrentTOPNode); + } +} + +void +UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) +{ + if (!IsValid(TOPNode)) + return; + + for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) + { + DestroyWorkItemResultData(CurrentWorkResult, TOPNode); + } + TOPNode->WorkResult.Empty(); + + FOutputActorOwner& OutputActorOwner = TOPNode->GetOutputActorOwner(); + AActor* OutputActor = OutputActorOwner.GetOutputActor(); + if (IsValid(OutputActor)) + { + // Destroy any attached actors (which we'll assume that any attachments left + // are untracked actors associated with the TOPNode) + TArray AttachedActors; + OutputActor->GetAttachedActors(AttachedActors); + for (AActor* Actor : AttachedActors) + { + if (!IsValid(Actor)) + continue; + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + } + } + + if (TOPNode->WorkResultParent && !TOPNode->WorkResultParent->IsPendingKill()) + { + + // TODO: Destroy the Parent Object + // DestroyImmediate(topNode._workResultParentGO); + } + + OutputActorOwner.DestroyOutputActor(); +} + + +void +UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); + if (WorkResult) + { + DestroyWorkItemResultData(*WorkResult, InTOPNode); + // TODO: Should we destroy the FTOPWorkResult struct entirely here? + //TOPNode.WorkResult.RemoveByPredicate + } +} + +void +UHoudiniPDGAssetLink::DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + // TODO: Update ClearWorkItemResultByID or GetWorkResultByID to return the index of the work item + // so that we don't have to find its index again to remove it from the array + ClearWorkItemResultByID(InWorkItemID, InTOPNode); + // Find the index of the FTOPWorkResult for InWorkItemID in InTOPNode.WorkResult and remove it + const int32 Index = InTOPNode->WorkResult.IndexOfByPredicate( + [InWorkItemID](const FTOPWorkResult& InWorkItem) { return InWorkItem.WorkItemID == InWorkItemID; }); + if (Index != INDEX_NONE && Index >= 0) + InTOPNode->WorkResult.RemoveAt(Index); +} + +FTOPWorkResult* +UHoudiniPDGAssetLink::GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return nullptr; + + for(FTOPWorkResult& CurResult : InTOPNode->WorkResult) + { + if (CurResult.WorkItemID == InWorkItemID) + { + return &CurResult; + } + } + + return nullptr; +} + +FDirectoryPath +UHoudiniPDGAssetLink::GetTemporaryCookFolder() const +{ + UObject* Owner = GetOuter(); + UHoudiniAssetComponent* HAC = Cast(Owner); + if (HAC) + return HAC->TemporaryCookFolder; + + FDirectoryPath TempPath; + TempPath.Path = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + return TempPath; +} + +void +UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) +{ + ResultObject.DestroyResultOutputs(); + ResultObject.GetOutputActorOwner().DestroyOutputActor(); +} + +void +UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode) +{ + if (Result.ResultObjects.Num() <= 0) + return; + + for (FTOPWorkResultObject& ResultObject : Result.ResultObjects) + { + DestoryWorkResultObjectData(ResultObject); + } + + Result.ResultObjects.Empty(); +} + + +UTOPNode* +UHoudiniPDGAssetLink::GetTOPNode(const int32& InNodeID) +{ + for (UTOPNetwork* CurrentTOPNet : AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + if (CurrentTOPNode->NodeId == InNodeID) + return CurrentTOPNode; + } + } + + return nullptr; +} + +void +UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork) +{ + if (!IsValid(InNode) || !IsValid(InNetwork)) + return; + + if (!InNode->bHasChildNodes) + return; + + FString PrefixPath = InNode->NodePath; + if (!PrefixPath.EndsWith("/")) + PrefixPath += "/"; + InNode->WorkItemTally.ZeroAll(); + InNode->NodeState = EPDGNodeState::None; + + TMap NodeStateOrder; + NodeStateOrder.Add(EPDGNodeState::None, 0); + NodeStateOrder.Add(EPDGNodeState::Cook_Complete, 1); + NodeStateOrder.Add(EPDGNodeState::Dirtied, 2); + NodeStateOrder.Add(EPDGNodeState::Cook_Failed, 3); + NodeStateOrder.Add(EPDGNodeState::Dirtying, 4); + NodeStateOrder.Add(EPDGNodeState::Cooking, 5); + + int8 CurrentState = 0; + + for (const UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + if (Node->NodePath.StartsWith(PrefixPath) && !Node->bHasChildNodes) + { + InNode->WorkItemTally.TotalWorkItems += Node->WorkItemTally.TotalWorkItems; + InNode->WorkItemTally.WaitingWorkItems += Node->WorkItemTally.WaitingWorkItems; + InNode->WorkItemTally.ScheduledWorkItems += Node->WorkItemTally.ScheduledWorkItems; + InNode->WorkItemTally.CookingWorkItems += Node->WorkItemTally.CookingWorkItems; + InNode->WorkItemTally.CookedWorkItems += Node->WorkItemTally.CookedWorkItems; + InNode->WorkItemTally.ErroredWorkItems += Node->WorkItemTally.ErroredWorkItems; + const int8 VisistedNodeState = NodeStateOrder.FindChecked(Node->NodeState); + if (VisistedNodeState > CurrentState) + CurrentState = VisistedNodeState; + } + } + + EPDGNodeState const* const NewState = NodeStateOrder.FindKey(CurrentState); + if (NewState) + InNode->NodeState = *NewState; +} + +void +UHoudiniPDGAssetLink::UpdateWorkItemTally() +{ + WorkItemTally.ZeroAll(); + for(UTOPNetwork* CurrentTOPNet : AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + for(UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + // Only add up the tallys from nodes without children (since parent's aggregate the child work items counts) + if (CurrentTOPNode->bHasChildNodes) + { + UpdateTOPNodeWithChildrenWorkItemTallyAndState(CurrentTOPNode, CurrentTOPNet); + } + else + { + WorkItemTally.TotalWorkItems += CurrentTOPNode->WorkItemTally.TotalWorkItems; + WorkItemTally.WaitingWorkItems += CurrentTOPNode->WorkItemTally.WaitingWorkItems; + WorkItemTally.ScheduledWorkItems += CurrentTOPNode->WorkItemTally.ScheduledWorkItems; + WorkItemTally.CookingWorkItems += CurrentTOPNode->WorkItemTally.CookingWorkItems; + WorkItemTally.CookedWorkItems += CurrentTOPNode->WorkItemTally.CookedWorkItems; + WorkItemTally.ErroredWorkItems += CurrentTOPNode->WorkItemTally.ErroredWorkItems; + } + } + } +} + + +void +UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork) +{ + if (!IsValid(TOPNetwork)) + return; + + for (UTOPNode* CurTOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(CurTOPNode)) + continue; + + CurTOPNode->WorkItemTally.ZeroAll(); + } +} + + +FString +UHoudiniPDGAssetLink::GetAssetLinkStatus(const EPDGLinkState& InLinkState) +{ + FString Status; + switch (InLinkState) + { + case EPDGLinkState::Inactive: + Status = TEXT("Inactive"); + case EPDGLinkState::Linking: + Status = TEXT("Linking"); + case EPDGLinkState::Linked: + Status = TEXT("Linked"); + case EPDGLinkState::Error_Not_Linked: + Status = TEXT("Not Linked"); + default: + Status = TEXT(""); + } + + return Status; +} + +FString +UHoudiniPDGAssetLink::GetTOPNodeStatus(const UTOPNode* InTOPNode) +{ + static const FString InvalidOrUnknownStatus = TEXT(""); + + if (!IsValid(InTOPNode)) + return InvalidOrUnknownStatus; + + if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) + { + return TEXT("Cook Failed"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) + { + return TEXT("Cook Completed"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Cooking) + { + return TEXT("Cook In Progress"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) + { + return TEXT("Dirtied"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) + { + return TEXT("Dirtying"); + } + + return InvalidOrUnknownStatus; +} + +FLinearColor +UHoudiniPDGAssetLink::GetTOPNodeStatusColor(const UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return FLinearColor::White; + + if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) + { + return FLinearColor::Red; + } + else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) + { + return FLinearColor::Green; + } + else if (InTOPNode->NodeState == EPDGNodeState::Cooking) + { + return FLinearColor(0.0, 1.0f, 1.0f); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) + { + return FLinearColor(1.0f, 0.5f, 0.0f); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) + { + return FLinearColor::Yellow; + } + + return FLinearColor::White; +} + +AActor* +UHoudiniPDGAssetLink::GetOwnerActor() const +{ + UObject* Outer = GetOuter(); + UActorComponent* Component = Cast(Outer); + if (IsValid(Component)) + return Component->GetOwner(); + else + return Cast(Outer); +} + +bool +UHoudiniPDGAssetLink::HasTemporaryOutputs() const +{ + // Loop over all networks, all nodes, all work items and check for any valid output objects + for (const UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (const UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + for (const FTOPWorkResult& WorkResult : TOPNode->WorkResult) + { + for (const FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) + { + // If the WorkResultObject's actor is not valid, then it no longer has temporary objects in the + // scene + if (!IsValid(WorkResultObject.GetOutputActorOwner().GetOutputActor())) + continue; + + for (UHoudiniOutput* Output : WorkResultObject.GetResultOutputs()) + { + if (!IsValid(Output)) + continue; + + const EHoudiniOutputType OutputType = Output->GetType(); + for (const auto& OutputObjectPair : Output->GetOutputObjects()) + { + if ((OutputType == EHoudiniOutputType::Landscape && IsValid(OutputObjectPair.Value.OutputObject)) || + IsValid(OutputObjectPair.Value.OutputComponent)) + { + return true; + } + } + } + } + } + } + } + + return false; +} + +void +UHoudiniPDGAssetLink::UpdatePostDuplicate() +{ + // Loop over all networks, all nodes, all work items and clear output actors + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + for (FTOPWorkResult& WorkResult : TOPNode->WorkResult) + { + for (FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) + { + WorkResultObject.GetOutputActorOwner().SetOutputActor(nullptr); + WorkResultObject.State = EPDGWorkResultState::None; + WorkResultObject.SetResultOutputs(TArray()); + } + } + TOPNode->GetOutputActorOwner().SetOutputActor(nullptr); + TOPNode->bCachedHaveNotLoadedWorkResults = false; + TOPNode->bCachedHaveLoadedWorkResults = false; + } + } +} + +void +UHoudiniPDGAssetLink::UpdateTOPNodeAutoloadAndVisibility() +{ + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->bAutoLoad) + { + // // Set work results that are cooked but in NotLoaded state to ToLoad + // TOPNode.SetNotLoadedWorkResultsToLoad(); + } + + TOPNode->UpdateOutputVisibilityInLevel(); + } + } +} + +void +UHoudiniPDGAssetLink::FilterTOPNodesAndOutputs() +{ + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + // TOP Node visibility filter via TOPNodeFilter + if (bUseTOPNodeFilter) + { + TOPNode->bHidden = !TOPNodeFilter.IsEmpty() && !TOPNode->NodeName.StartsWith(TOPNodeFilter); + } + else + { + TOPNode->bHidden = false; + } + + // Auto load results filter via TOPNodeOutputFilter + if (bUseTOPOutputFilter) + { + const bool bNewAutoLoad = TOPOutputFilter.IsEmpty() || TOPNode->NodeName.StartsWith(TOPOutputFilter); + if (bNewAutoLoad != TOPNode->bAutoLoad) + { + if (bNewAutoLoad) + { + // Set work results that are cooked but in NotLoaded state to ToLoad + TOPNode->bAutoLoad = true; + // TOPNode->SetNotLoadedWorkResultsToLoad(); + TOPNode->SetVisibleInLevel(true); + } + else + { + TOPNode->bAutoLoad = false; + TOPNode->SetVisibleInLevel(false); + } + TOPNode->UpdateOutputVisibilityInLevel(); + } + } + } + } +} + +#if WITH_EDITORONLY_DATA +void +UHoudiniPDGAssetLink::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedChainEvent) +{ + Super::PostEditChangeChainProperty(InPropertyChangedChainEvent); + + const FName PropertyName = InPropertyChangedChainEvent.GetPropertyName(); + if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) + { + // Refilter TOP nodes + FilterTOPNodesAndOutputs(); + bNeedsUIRefresh = true; + } + else if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) + { + bNeedsUIRefresh = true; + } +} + +#endif + +#if WITH_EDITORONLY_DATA +void +UHoudiniPDGAssetLink::PostTransacted(const FTransactionObjectEvent& TransactionEvent) +{ + Super::PostTransacted(TransactionEvent); + + if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) + return; + + bool bDoFilterTOPNodesAndOutputs = false; + for (const FName& PropName : TransactionEvent.GetChangedProperties()) + { + if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) + { + bDoFilterTOPNodesAndOutputs = true; + bNeedsUIRefresh = true; + } + else if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) + { + bNeedsUIRefresh = true; + } + } + + if (bDoFilterTOPNodesAndOutputs) + FilterTOPNodesAndOutputs(); +} +#endif + +void +FTOPWorkResultObject::DestroyResultOutputs() +{ + // Delete output components and gather output objects for deletion + bool bDidDestroyObjects = false; + bool bDidModifyFoliage = false; + + AActor* const OutputActor = OutputActorOwner.GetOutputActor(); + + for (UHoudiniOutput* CurOutput : ResultOutputs) + { + for (auto& Pair : CurOutput->GetOutputObjects()) + { + FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniOutputObject& OutputObject = Pair.Value; + if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) + { + // Instancer components require some special handling around foliage + // TODO: move/refactor so that we can use the InstanceTranslator's helper functions (RemoveAndDestroyComponent and CleanupFoliageInstances) + bool bDestroyComponent = true; + if (OutputObject.OutputComponent->IsA()) + { + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OutputObject.OutputComponent); + if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) + { + // Make sure foliage our foliage instances have been removed + USceneComponent* ParentComponent = nullptr; + if (IsValid(OutputActor)) + ParentComponent = Cast(OutputActor->GetRootComponent()); + else + ParentComponent = Cast(HISMC->GetOuter()); + if (ParentComponent && !ParentComponent->IsPendingKill()) + { + UStaticMesh* FoliageSM = HISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + return; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(HISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return; + + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + if (!FoliageType || FoliageType->IsPendingKill()) + return; +#if WITH_EDITOR + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); + + // Remove the foliage type if it doesn't have any more instances + if(HISMC->GetInstanceCount() == 0) + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + + bDidModifyFoliage = true; +#endif + } + + // do not delete FISMC that still have instances left + // as we have cleaned up our instances before, these have been hand-placed + if (HISMC->GetInstanceCount() > 0) + bDestroyComponent = false; + } + + if (bDestroyComponent) + { + USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from its actor first + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + // Detach from its parent component if attached + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + bDidDestroyObjects = true; + + OutputObject.OutputComponent = nullptr; + } + } + } + } + if (OutputObject.OutputObject && !OutputObject.OutputObject->IsPendingKill()) + { + // For actors we detach them first and then destroy + AActor* Actor = Cast(OutputObject.OutputObject); + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + if (LandscapePtr) + { + Actor = LandscapePtr->GetRawPtr(); + } + + if (Actor) + { + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + + bDidDestroyObjects = true; + } + else + { + // ... if not an actor, mark as pending kill + // OutputObject.OutputObject->MarkPendingKill(); + if (IsValid(OutputObject.OutputObject)) + OutputObjectsToDelete.Add(OutputObject.OutputObject); + OutputObject.OutputObject = nullptr; + } + } + } + } + + ResultOutputs.Empty(); + + if (bDidDestroyObjects) + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Delete the output objects we found + if (OutputObjectsToDelete.Num() > 0) + FHoudiniEngineRuntimeUtils::SafeDeleteObjects(OutputObjectsToDelete); + +#if WITH_EDITOR + if (bDidModifyFoliage) + { + // Repopulate the foliage types in the foliage mode UI if foliage mode is active + // There is a helper function in FHoudiniEngineUtils for this, but we cannot access it from this module. + // TODO: refactor? + FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); + if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) + { + EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); + EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); + } + } +#endif +} + +bool +FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) +{ + // InAssetLink and InWorld must not be null + if (!InAssetLink || InAssetLink->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InAssetLink is null!")); + return false; + } + if (!InWorld || InWorld->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InWorld is null!")); + return false; + } + + AActor* AssetLinkActor = InAssetLink->GetOwnerActor(); + + const bool bParentActorIsValid = IsValid(InParentActor); + ULevel* LevelToSpawnIn = nullptr; + if (bParentActorIsValid) + { + LevelToSpawnIn = InParentActor->GetLevel(); + } + else + { + // Get the level containing the asset link's actor + if (IsValid(AssetLinkActor)) + LevelToSpawnIn = AssetLinkActor->GetLevel(); + } + + // Fallback to InWorld's current level + UWorld* WorldToSpawnIn = nullptr; + if (!IsValid(LevelToSpawnIn)) + { + LevelToSpawnIn = InWorld->GetCurrentLevel(); + WorldToSpawnIn = InWorld; + } + else + { + WorldToSpawnIn = LevelToSpawnIn->GetWorld(); + } + + if (!IsValid(WorldToSpawnIn) || !IsValid(LevelToSpawnIn)) + { + HOUDINI_LOG_WARNING( + TEXT("Could not determine level and world to spawn PDG output actor in: asset link %s, name %s"), + *(InAssetLink->GetPathName()), + *(InName.ToString())); + return false; + } + + FActorSpawnParameters SpawnParams; + SpawnParams.Name = MakeUniqueObjectName(InWorld, AActor::StaticClass(), InName); + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; + SpawnParams.OverrideLevel = LevelToSpawnIn; + AActor *Actor = WorldToSpawnIn->SpawnActor(SpawnParams); + SetOutputActor(Actor); +#if WITH_EDITOR + Actor->SetActorLabel(InName.ToString()); +#endif + + // Set the actor transform: create a root component if it does not have one + USceneComponent* RootComponent = Actor->GetRootComponent(); + if (!RootComponent || RootComponent->IsPendingKill()) + { + RootComponent = NewObject(Actor, USceneComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + RootComponent->CreationMethod = EComponentCreationMethod::Instance; + Actor->SetRootComponent(RootComponent); + RootComponent->OnComponentCreated(); + RootComponent->RegisterComponent(); + } + + RootComponent->SetVisibility(true); + RootComponent->SetMobility(EComponentMobility::Static); + + const FVector ActorSpawnLocation = InParentActor ? InParentActor->GetActorLocation() : FVector::ZeroVector; + const FRotator ActorSpawnRotator = InParentActor ? InParentActor->GetActorRotation() : FRotator::ZeroRotator; + Actor->SetActorLocation(ActorSpawnLocation); + Actor->SetActorRotation(ActorSpawnRotator); + +#if WITH_EDITOR + if (IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) + { + Actor->SetFolderPath(InParentActor->GetFolderPath()); + Actor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); + } + else if (IsValid(AssetLinkActor) && AssetLinkActor->GetLevel() == LevelToSpawnIn) + { + Actor->SetFolderPath(*FString::Format( + TEXT("{0}/{1}_Output"), + { FStringFormatArg(AssetLinkActor->GetFolderPath().ToString()), FStringFormatArg(AssetLinkActor->GetActorLabel()) } + )); + } + else + { + Actor->SetFolderPath(*FString::Format(TEXT("{0}_Output"), { FStringFormatArg(InAssetLink->GetName()) })); + } +#else + if(IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) + { + OutputActor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); + } +#endif + + return true; +} + +bool +FOutputActorOwner::DestroyOutputActor() +{ + bool bDestroyed = false; + AActor *Actor = GetOutputActor(); + if (IsValid(Actor)) + { + // Detach from parent before destroying the actor + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + + bDestroyed = true; + } + + SetOutputActor(nullptr); + + return bDestroyed; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h index 6d95a7b79..5ad250ecb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h @@ -1,620 +1,620 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -//#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" - - -#include "HoudiniPDGAssetLink.generated.h" - -struct FHoudiniPackageParams; - -UENUM() -enum class EPDGLinkState : uint8 -{ - Inactive, - Linking, - Linked, - Error_Not_Linked -}; - - -UENUM() -enum class EPDGNodeState : uint8 -{ - None, - Dirtied, - Dirtying, - Cooking, - Cook_Complete, - Cook_Failed -}; - -UENUM() -enum class EPDGWorkResultState : uint8 -{ - None, - ToLoad, - Loading, - Loaded, - ToDelete, - Deleting, - Deleted, - NotLoaded -}; - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EPDGBakeSelectionOption : uint8 -{ - All, - SelectedNetwork, - SelectedNode -}; -#endif - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EPDGBakePackageReplaceModeOption : uint8 -{ - CreateNewAssets, - ReplaceExistingAssets -}; -#endif - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FOutputActorOwner -{ - GENERATED_BODY(); -public: - FOutputActorOwner() - : OutputActor(nullptr) {}; - - virtual ~FOutputActorOwner() {}; - - // Create OutputActor, an actor to hold work result output - virtual bool CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName); - - // Return OutputActor - virtual AActor* GetOutputActor() const { return OutputActor; } - - // Setter for OutputActor - virtual void SetOutputActor(AActor* InActor) { OutputActor = InActor; } - - // Destroy OutputActor if it is valid. - virtual bool DestroyOutputActor(); - -private: - UPROPERTY(NonTransactional) - AActor* OutputActor; - -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FTOPWorkResultObject(); - - // Call DestroyResultObjects in the destructor - virtual ~FTOPWorkResultObject(); - - // Set ResultObjects to a copy of InUpdatedOutputs - void SetResultOutputs(const TArray& InUpdatedOutputs) { ResultOutputs = InUpdatedOutputs; } - - // Getter for ResultOutputs - TArray& GetResultOutputs() { return ResultOutputs; } - - // Getter for ResultOutputs - const TArray& GetResultOutputs() const { return ResultOutputs; } - - // Destroy ResultOutputs - void DestroyResultOutputs(); - - // Get the OutputActor owner struct - FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } - - // Get the OutputActor owner struct - const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } - -public: - - UPROPERTY(NonTransactional) - FString Name; - UPROPERTY(NonTransactional) - FString FilePath; - UPROPERTY(NonTransactional) - EPDGWorkResultState State; - -protected: - // UPROPERTY() - // TArray ResultObjects; - - UPROPERTY(NonTransactional) - TArray ResultOutputs; - -private: - // List of objects to delete, internal use only (DestroyResultOutputs) - UPROPERTY(NonTransactional) - TArray OutputObjectsToDelete; - - UPROPERTY(NonTransactional) - FOutputActorOwner OutputActorOwner; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FTOPWorkResult -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FTOPWorkResult(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const FTOPWorkResult& OtherWorkResult) const; - -public: - - UPROPERTY(NonTransactional) - int32 WorkItemIndex; - UPROPERTY(Transient) - int32 WorkItemID; - - UPROPERTY(NonTransactional) - TArray ResultObjects; - - /* - UPROPERTY() - TArray ResultObjects; - - UPROPERTY() - TArray ResultNames; - UPROPERTY() - TArray ResultFilePaths; - UPROPERTY() - TArray ResultStates; - */ -}; - - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FWorkItemTally -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FWorkItemTally(); - - void ZeroAll(); - - bool AreAllWorkItemsComplete() const; - bool AnyWorkItemsFailed() const; - bool AnyWorkItemsPending() const; - - FString ProgressRatio() const; - -public: - - UPROPERTY() - int32 TotalWorkItems; - UPROPERTY() - int32 WaitingWorkItems; - UPROPERTY() - int32 ScheduledWorkItems; - UPROPERTY() - int32 CookingWorkItems; - UPROPERTY() - int32 CookedWorkItems; - UPROPERTY() - int32 ErroredWorkItems; -}; - -// Container for baked outputs of a PDG work result object. -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniPDGWorkResultObjectBakedOutput -{ - GENERATED_BODY() - - public: - // Array of baked output per output index of the work result object's outputs. - UPROPERTY() - TArray BakedOutputs; -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UTOPNode : public UObject -{ - GENERATED_BODY() - -public: - - // Constructor - UTOPNode(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const UTOPNode& Other) const; - - void Reset(); - - bool AreAllWorkItemsComplete() const { return WorkItemTally.AreAllWorkItemsComplete(); }; - bool AnyWorkItemsFailed() const { return WorkItemTally.AnyWorkItemsFailed(); }; - bool AnyWorkItemsPending() const { return WorkItemTally.AnyWorkItemsPending(); }; - - bool IsVisibleInLevel() const { return bShow; } - void SetVisibleInLevel(bool bInVisible); - void UpdateOutputVisibilityInLevel(); - - // Sets all WorkResultObjects that are in the NotLoaded state to ToLoad. - void SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad=false); - - // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output - // actors). - void SetLoadedWorkResultsToDelete(); - - // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output - // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); - - // Get the OutputActor owner struct - FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } - - // Get the OutputActor owner struct - const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } - - // Get the baked outputs from the last bake. The map keys are [work_result_index]_[work_result_object_index] - TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } - const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } - bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const; - static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex); - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; - -#if WITH_EDITOR - void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; -#endif - -#if WITH_EDITOR - void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; -#endif - -public: - - UPROPERTY(Transient, NonTransactional) - int32 NodeId; - UPROPERTY(NonTransactional) - FString NodeName; - UPROPERTY(NonTransactional) - FString NodePath; - UPROPERTY(NonTransactional) - FString ParentName; - - UPROPERTY() - UObject* WorkResultParent; - UPROPERTY(NonTransactional) - TArray WorkResult; - - // Hidden in the nodes combobox - UPROPERTY() - bool bHidden; - UPROPERTY() - bool bAutoLoad; - - UPROPERTY(Transient, NonTransactional) - EPDGNodeState NodeState; - - UPROPERTY(Transient, NonTransactional) - FWorkItemTally WorkItemTally; - - // This is set when the TOP node's work items are processed by - // FHoudiniPDGManager based on if any NotLoaded work result objects are found - UPROPERTY(NonTransactional) - bool bCachedHaveNotLoadedWorkResults; - - // This is set when the TOP node's work items are processed by - // FHoudiniPDGManager based on if any Loaded work result objects are found - UPROPERTY(NonTransactional) - bool bCachedHaveLoadedWorkResults; - - // True if this node has child nodes - UPROPERTY(NonTransactional) - bool bHasChildNodes; - -protected: - // Visible in the level - UPROPERTY() - bool bShow; - - // Map of [work_result_index]_[work_result_object_index] to the work result object's baked outputs. - UPROPERTY() - TMap BakedWorkResultObjectOutputs; - -private: - UPROPERTY() - FOutputActorOwner OutputActorOwner; -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject -{ - GENERATED_BODY() - -public: - - // Constructor - UTOPNetwork(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const UTOPNetwork& Other) const; - - // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output - // actors). - void SetLoadedWorkResultsToDelete(); - - // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output - // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); - - // Returns true if any node in this TOP net has pending (waiting, scheduled, cooking) work items. - bool AnyWorkItemsPending() const; - -public: - - UPROPERTY(Transient, NonTransactional) - int32 NodeId; - UPROPERTY(NonTransactional) - FString NodeName; - // HAPI path to this node (relative to the HDA) - UPROPERTY(NonTransactional) - FString NodePath; - - UPROPERTY() - TArray AllTOPNodes; - - // TODO: Should be using SelectedNodeName instead? - // Index is not consistent after updating filter - UPROPERTY() - int32 SelectedTOPIndex; - - UPROPERTY(NonTransactional) - FString ParentName; - - UPROPERTY() - bool bShowResults; - UPROPERTY() - bool bAutoLoadResults; -}; - - -class UHoudiniPDGAssetLink; -DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemId*/, const FString& /*WorkResultObjectName*/); - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - friend class UHoudiniAssetComponent; - - static FString GetAssetLinkStatus(const EPDGLinkState& InLinkState); - static FString GetTOPNodeStatus(const UTOPNode* InTOPNode); - static FLinearColor GetTOPNodeStatusColor(const UTOPNode* InTOPNode); - - void UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork); - void UpdateWorkItemTally(); - static void ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork); - - // Set the TOP network at the given index as currently selected TOP network - void SelectTOPNetwork(const int32& AtIndex); - // Set the TOP node at the given index in the given TOP network as currently selected TOP node - void SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex); - - UTOPNode* GetSelectedTOPNode(); - UTOPNetwork* GetSelectedTOPNetwork(); - - FString GetSelectedTOPNodeName(); - FString GetSelectedTOPNetworkName(); - - UTOPNode* GetTOPNode(const int32& InNodeID); - UTOPNetwork* GetTOPNetwork(const int32& AtIndex); - - // Find the node with relative path 'InNodePath' from its topnet. - static UTOPNode* GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex); - // Find the network with relative path 'InNetPath' from the HDA - static UTOPNetwork* GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex); - - // Get the parent TOP node of the specified node. This is resolved - UTOPNode* GetParentTOPNode(const UTOPNode* InNode); - - static void ClearTOPNodeWorkItemResults(UTOPNode* TOPNode); - static void ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork); - // Clear the result objects of a work item (FTOPWorkResult.ResultObjects), but don't delete the work item from - // TOPNode.WorkResults (for example, the work item was dirtied but not removed from PDG) - static void ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - // Calls ClearWorkItemResultByID and then deletes the FTOPWorkResult from InTOPNode.Result as well. For example: - // the work item was removed in PDG. - static void DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - static FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - - // This should be called after the owner and this PDG asset link is duplicated. Set all output parent actors to - // null in all TOP networks/nodes. Since the TOP Networks/TOP nodes are all structs, we cannot set - // DuplicateTransient property on their OutputActor properties. - void UpdatePostDuplicate(); - - // Load the geometry generated as results of the given work item, of the given TOP node. - // The load will be done asynchronously. - // Results must be tagged with 'file', and must have a file path, otherwise will not be loaded. - //void LoadResults(FTOPNode TOPNode, HAPI_PDG_WorkitemInfo workItemInfo, HAPI_PDG_WorkitemResultInfo[] resultInfos, HAPI_PDG_WorkitemId workItemID) - - // Gets the temporary cook folder. If the parent of this asset link is a HoudiniAssetComponent use that, otherwise - // use the default static mesh temporary cook folder. - FDirectoryPath GetTemporaryCookFolder() const; - - // Get the actor that owns this PDG asset link. If the asset link is owned by a component, - // then the component's owning actor is returned. Can return null if this is now owned by - // an actor or component. - AActor* GetOwnerActor() const; - - // Checks if the asset link has any temporary outputs and returns true if it has - bool HasTemporaryOutputs() const; - - // Filter TOP nodes and outputs (hidden/visible) by TOPNodeFilter and TOPOutputFilter. - void FilterTOPNodesAndOutputs(); - - // On all FTOPNodes: Load not loaded items if bAutoload is true, and update the level visibility of work items - // result. Used when FTOPNode.bShow and/or FTOPNode.bAutoload changed. - void UpdateTOPNodeAutoloadAndVisibility(); - -#if WITH_EDITORONLY_DATA - void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; -#endif - -#if WITH_EDITORONLY_DATA - void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; -#endif - -private: - - void ClearAllTOPData(); - - static void DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode); - - static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); - -public: - - //UPROPERTY() - //UHoudiniAsset* HoudiniAsset; - - //UPROPERTY() - //UHoudiniAssetComponent* ParentHAC; - - UPROPERTY(DuplicateTransient, NonTransactional) - FString AssetName; - - // The full path to the HDA in HAPI - UPROPERTY(DuplicateTransient, NonTransactional) - FString AssetNodePath; - - UPROPERTY(DuplicateTransient, NonTransactional) - int32 AssetID; - - UPROPERTY() - TArray AllTOPNetworks; - - UPROPERTY() - int32 SelectedTOPNetworkIndex; - - UPROPERTY(Transient, NonTransactional) - EPDGLinkState LinkState; - - UPROPERTY() - bool bAutoCook; - UPROPERTY() - bool bUseTOPNodeFilter; - UPROPERTY() - bool bUseTOPOutputFilter; - UPROPERTY() - FString TOPNodeFilter; - UPROPERTY() - FString TOPOutputFilter; - - UPROPERTY(NonTransactional) - int32 NumWorkitems; - UPROPERTY(Transient, NonTransactional) - FWorkItemTally WorkItemTally; - - UPROPERTY() - FString OutputCachePath; - - UPROPERTY(Transient) - bool bNeedsUIRefresh; - - // A parent actor to serve as the parent of any output actors - // that are created. - // If null, then output actors are created under a folder - UPROPERTY(EditAnywhere, Category="Output") - AActor* OutputParentActor; - - // Folder used for baking PDG outputs - UPROPERTY() - FDirectoryPath BakeFolder; - - // - // Notifications - // - - // Delegate that is broadcast when a work result object has been loaded - FHoudiniPDGAssetLinkWorkResultObjectLoaded OnWorkResultObjectLoaded; - - // - // End: Notifications - // - -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bBakeMenuExpanded; - - // What kind of output to bake, for example, bake actors, bake to blueprint - UPROPERTY() - EHoudiniEngineBakeOption HoudiniEngineBakeOption; - - // Which outputs to bake, for example, all, selected network, selected node - UPROPERTY() - EPDGBakeSelectionOption PDGBakeSelectionOption; - - // This determines if the baked assets should replace existing assets with the same name, - // or always generate new assets (with numerical suffixes if needed to create unique names) - UPROPERTY() - EPDGBakePackageReplaceModeOption PDGBakePackageReplaceMode; - - // If true, recenter baked actors to their bounding box center after bake - UPROPERTY() - bool bRecenterBakedActors; - - // Auto-bake: if this is true, it indicates that a work result object should be baked after it is loaded. - UPROPERTY() - bool bBakeAfterWorkResultObjectLoaded; - - // The delegate handle of the auto bake helper function bound to OnWorkResultObjectLoaded. - FDelegateHandle AutoBakeDelegateHandle; -#endif -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +//#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" + + +#include "HoudiniPDGAssetLink.generated.h" + +struct FHoudiniPackageParams; + +UENUM() +enum class EPDGLinkState : uint8 +{ + Inactive, + Linking, + Linked, + Error_Not_Linked +}; + + +UENUM() +enum class EPDGNodeState : uint8 +{ + None, + Dirtied, + Dirtying, + Cooking, + Cook_Complete, + Cook_Failed +}; + +UENUM() +enum class EPDGWorkResultState : uint8 +{ + None, + ToLoad, + Loading, + Loaded, + ToDelete, + Deleting, + Deleted, + NotLoaded +}; + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EPDGBakeSelectionOption : uint8 +{ + All, + SelectedNetwork, + SelectedNode +}; +#endif + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EPDGBakePackageReplaceModeOption : uint8 +{ + CreateNewAssets, + ReplaceExistingAssets +}; +#endif + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FOutputActorOwner +{ + GENERATED_BODY(); +public: + FOutputActorOwner() + : OutputActor(nullptr) {}; + + virtual ~FOutputActorOwner() {}; + + // Create OutputActor, an actor to hold work result output + virtual bool CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName); + + // Return OutputActor + virtual AActor* GetOutputActor() const { return OutputActor; } + + // Setter for OutputActor + virtual void SetOutputActor(AActor* InActor) { OutputActor = InActor; } + + // Destroy OutputActor if it is valid. + virtual bool DestroyOutputActor(); + +private: + UPROPERTY(NonTransactional) + AActor* OutputActor; + +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FTOPWorkResultObject(); + + // Call DestroyResultObjects in the destructor + virtual ~FTOPWorkResultObject(); + + // Set ResultObjects to a copy of InUpdatedOutputs + void SetResultOutputs(const TArray& InUpdatedOutputs) { ResultOutputs = InUpdatedOutputs; } + + // Getter for ResultOutputs + TArray& GetResultOutputs() { return ResultOutputs; } + + // Getter for ResultOutputs + const TArray& GetResultOutputs() const { return ResultOutputs; } + + // Destroy ResultOutputs + void DestroyResultOutputs(); + + // Get the OutputActor owner struct + FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } + + // Get the OutputActor owner struct + const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } + +public: + + UPROPERTY(NonTransactional) + FString Name; + UPROPERTY(NonTransactional) + FString FilePath; + UPROPERTY(NonTransactional) + EPDGWorkResultState State; + +protected: + // UPROPERTY() + // TArray ResultObjects; + + UPROPERTY(NonTransactional) + TArray ResultOutputs; + +private: + // List of objects to delete, internal use only (DestroyResultOutputs) + UPROPERTY(NonTransactional) + TArray OutputObjectsToDelete; + + UPROPERTY(NonTransactional) + FOutputActorOwner OutputActorOwner; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FTOPWorkResult +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FTOPWorkResult(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const FTOPWorkResult& OtherWorkResult) const; + +public: + + UPROPERTY(NonTransactional) + int32 WorkItemIndex; + UPROPERTY(Transient) + int32 WorkItemID; + + UPROPERTY(NonTransactional) + TArray ResultObjects; + + /* + UPROPERTY() + TArray ResultObjects; + + UPROPERTY() + TArray ResultNames; + UPROPERTY() + TArray ResultFilePaths; + UPROPERTY() + TArray ResultStates; + */ +}; + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FWorkItemTally +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FWorkItemTally(); + + void ZeroAll(); + + bool AreAllWorkItemsComplete() const; + bool AnyWorkItemsFailed() const; + bool AnyWorkItemsPending() const; + + FString ProgressRatio() const; + +public: + + UPROPERTY() + int32 TotalWorkItems; + UPROPERTY() + int32 WaitingWorkItems; + UPROPERTY() + int32 ScheduledWorkItems; + UPROPERTY() + int32 CookingWorkItems; + UPROPERTY() + int32 CookedWorkItems; + UPROPERTY() + int32 ErroredWorkItems; +}; + +// Container for baked outputs of a PDG work result object. +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniPDGWorkResultObjectBakedOutput +{ + GENERATED_BODY() + + public: + // Array of baked output per output index of the work result object's outputs. + UPROPERTY() + TArray BakedOutputs; +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UTOPNode : public UObject +{ + GENERATED_BODY() + +public: + + // Constructor + UTOPNode(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const UTOPNode& Other) const; + + void Reset(); + + bool AreAllWorkItemsComplete() const { return WorkItemTally.AreAllWorkItemsComplete(); }; + bool AnyWorkItemsFailed() const { return WorkItemTally.AnyWorkItemsFailed(); }; + bool AnyWorkItemsPending() const { return WorkItemTally.AnyWorkItemsPending(); }; + + bool IsVisibleInLevel() const { return bShow; } + void SetVisibleInLevel(bool bInVisible); + void UpdateOutputVisibilityInLevel(); + + // Sets all WorkResultObjects that are in the NotLoaded state to ToLoad. + void SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad=false); + + // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output + // actors). + void SetLoadedWorkResultsToDelete(); + + // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output + // objects and actors and sets the state to Deleted. + void DeleteWorkResultOutputObjects(); + + // Get the OutputActor owner struct + FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } + + // Get the OutputActor owner struct + const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } + + // Get the baked outputs from the last bake. The map keys are [work_result_index]_[work_result_object_index] + TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } + const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } + bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const; + static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex); + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; + +#if WITH_EDITOR + void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif + +#if WITH_EDITOR + void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; +#endif + +public: + + UPROPERTY(Transient, NonTransactional) + int32 NodeId; + UPROPERTY(NonTransactional) + FString NodeName; + UPROPERTY(NonTransactional) + FString NodePath; + UPROPERTY(NonTransactional) + FString ParentName; + + UPROPERTY() + UObject* WorkResultParent; + UPROPERTY(NonTransactional) + TArray WorkResult; + + // Hidden in the nodes combobox + UPROPERTY() + bool bHidden; + UPROPERTY() + bool bAutoLoad; + + UPROPERTY(Transient, NonTransactional) + EPDGNodeState NodeState; + + UPROPERTY(Transient, NonTransactional) + FWorkItemTally WorkItemTally; + + // This is set when the TOP node's work items are processed by + // FHoudiniPDGManager based on if any NotLoaded work result objects are found + UPROPERTY(NonTransactional) + bool bCachedHaveNotLoadedWorkResults; + + // This is set when the TOP node's work items are processed by + // FHoudiniPDGManager based on if any Loaded work result objects are found + UPROPERTY(NonTransactional) + bool bCachedHaveLoadedWorkResults; + + // True if this node has child nodes + UPROPERTY(NonTransactional) + bool bHasChildNodes; + +protected: + // Visible in the level + UPROPERTY() + bool bShow; + + // Map of [work_result_index]_[work_result_object_index] to the work result object's baked outputs. + UPROPERTY() + TMap BakedWorkResultObjectOutputs; + +private: + UPROPERTY() + FOutputActorOwner OutputActorOwner; +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject +{ + GENERATED_BODY() + +public: + + // Constructor + UTOPNetwork(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const UTOPNetwork& Other) const; + + // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output + // actors). + void SetLoadedWorkResultsToDelete(); + + // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output + // objects and actors and sets the state to Deleted. + void DeleteWorkResultOutputObjects(); + + // Returns true if any node in this TOP net has pending (waiting, scheduled, cooking) work items. + bool AnyWorkItemsPending() const; + +public: + + UPROPERTY(Transient, NonTransactional) + int32 NodeId; + UPROPERTY(NonTransactional) + FString NodeName; + // HAPI path to this node (relative to the HDA) + UPROPERTY(NonTransactional) + FString NodePath; + + UPROPERTY() + TArray AllTOPNodes; + + // TODO: Should be using SelectedNodeName instead? + // Index is not consistent after updating filter + UPROPERTY() + int32 SelectedTOPIndex; + + UPROPERTY(NonTransactional) + FString ParentName; + + UPROPERTY() + bool bShowResults; + UPROPERTY() + bool bAutoLoadResults; +}; + + +class UHoudiniPDGAssetLink; +DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemId*/, const FString& /*WorkResultObjectName*/); + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + friend class UHoudiniAssetComponent; + + static FString GetAssetLinkStatus(const EPDGLinkState& InLinkState); + static FString GetTOPNodeStatus(const UTOPNode* InTOPNode); + static FLinearColor GetTOPNodeStatusColor(const UTOPNode* InTOPNode); + + void UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork); + void UpdateWorkItemTally(); + static void ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork); + + // Set the TOP network at the given index as currently selected TOP network + void SelectTOPNetwork(const int32& AtIndex); + // Set the TOP node at the given index in the given TOP network as currently selected TOP node + void SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex); + + UTOPNode* GetSelectedTOPNode(); + UTOPNetwork* GetSelectedTOPNetwork(); + + FString GetSelectedTOPNodeName(); + FString GetSelectedTOPNetworkName(); + + UTOPNode* GetTOPNode(const int32& InNodeID); + UTOPNetwork* GetTOPNetwork(const int32& AtIndex); + + // Find the node with relative path 'InNodePath' from its topnet. + static UTOPNode* GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex); + // Find the network with relative path 'InNetPath' from the HDA + static UTOPNetwork* GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex); + + // Get the parent TOP node of the specified node. This is resolved + UTOPNode* GetParentTOPNode(const UTOPNode* InNode); + + static void ClearTOPNodeWorkItemResults(UTOPNode* TOPNode); + static void ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork); + // Clear the result objects of a work item (FTOPWorkResult.ResultObjects), but don't delete the work item from + // TOPNode.WorkResults (for example, the work item was dirtied but not removed from PDG) + static void ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + // Calls ClearWorkItemResultByID and then deletes the FTOPWorkResult from InTOPNode.Result as well. For example: + // the work item was removed in PDG. + static void DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + static FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + + // This should be called after the owner and this PDG asset link is duplicated. Set all output parent actors to + // null in all TOP networks/nodes. Since the TOP Networks/TOP nodes are all structs, we cannot set + // DuplicateTransient property on their OutputActor properties. + void UpdatePostDuplicate(); + + // Load the geometry generated as results of the given work item, of the given TOP node. + // The load will be done asynchronously. + // Results must be tagged with 'file', and must have a file path, otherwise will not be loaded. + //void LoadResults(FTOPNode TOPNode, HAPI_PDG_WorkitemInfo workItemInfo, HAPI_PDG_WorkitemResultInfo[] resultInfos, HAPI_PDG_WorkitemId workItemID) + + // Gets the temporary cook folder. If the parent of this asset link is a HoudiniAssetComponent use that, otherwise + // use the default static mesh temporary cook folder. + FDirectoryPath GetTemporaryCookFolder() const; + + // Get the actor that owns this PDG asset link. If the asset link is owned by a component, + // then the component's owning actor is returned. Can return null if this is now owned by + // an actor or component. + AActor* GetOwnerActor() const; + + // Checks if the asset link has any temporary outputs and returns true if it has + bool HasTemporaryOutputs() const; + + // Filter TOP nodes and outputs (hidden/visible) by TOPNodeFilter and TOPOutputFilter. + void FilterTOPNodesAndOutputs(); + + // On all FTOPNodes: Load not loaded items if bAutoload is true, and update the level visibility of work items + // result. Used when FTOPNode.bShow and/or FTOPNode.bAutoload changed. + void UpdateTOPNodeAutoloadAndVisibility(); + +#if WITH_EDITORONLY_DATA + void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif + +#if WITH_EDITORONLY_DATA + void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; +#endif + +private: + + void ClearAllTOPData(); + + static void DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode); + + static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); + +public: + + //UPROPERTY() + //UHoudiniAsset* HoudiniAsset; + + //UPROPERTY() + //UHoudiniAssetComponent* ParentHAC; + + UPROPERTY(DuplicateTransient, NonTransactional) + FString AssetName; + + // The full path to the HDA in HAPI + UPROPERTY(DuplicateTransient, NonTransactional) + FString AssetNodePath; + + UPROPERTY(DuplicateTransient, NonTransactional) + int32 AssetID; + + UPROPERTY() + TArray AllTOPNetworks; + + UPROPERTY() + int32 SelectedTOPNetworkIndex; + + UPROPERTY(Transient, NonTransactional) + EPDGLinkState LinkState; + + UPROPERTY() + bool bAutoCook; + UPROPERTY() + bool bUseTOPNodeFilter; + UPROPERTY() + bool bUseTOPOutputFilter; + UPROPERTY() + FString TOPNodeFilter; + UPROPERTY() + FString TOPOutputFilter; + + UPROPERTY(NonTransactional) + int32 NumWorkitems; + UPROPERTY(Transient, NonTransactional) + FWorkItemTally WorkItemTally; + + UPROPERTY() + FString OutputCachePath; + + UPROPERTY(Transient) + bool bNeedsUIRefresh; + + // A parent actor to serve as the parent of any output actors + // that are created. + // If null, then output actors are created under a folder + UPROPERTY(EditAnywhere, Category="Output") + AActor* OutputParentActor; + + // Folder used for baking PDG outputs + UPROPERTY() + FDirectoryPath BakeFolder; + + // + // Notifications + // + + // Delegate that is broadcast when a work result object has been loaded + FHoudiniPDGAssetLinkWorkResultObjectLoaded OnWorkResultObjectLoaded; + + // + // End: Notifications + // + +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bBakeMenuExpanded; + + // What kind of output to bake, for example, bake actors, bake to blueprint + UPROPERTY() + EHoudiniEngineBakeOption HoudiniEngineBakeOption; + + // Which outputs to bake, for example, all, selected network, selected node + UPROPERTY() + EPDGBakeSelectionOption PDGBakeSelectionOption; + + // This determines if the baked assets should replace existing assets with the same name, + // or always generate new assets (with numerical suffixes if needed to create unique names) + UPROPERTY() + EPDGBakePackageReplaceModeOption PDGBakePackageReplaceMode; + + // If true, recenter baked actors to their bounding box center after bake + UPROPERTY() + bool bRecenterBakedActors; + + // Auto-bake: if this is true, it indicates that a work result object should be baked after it is loaded. + UPROPERTY() + bool bBakeAfterWorkResultObjectLoaded; + + // The delegate handle of the auto bake helper function bound to OnWorkResultObjectLoaded. + FDelegateHandle AutoBakeDelegateHandle; +#endif +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp index 7d20cbb5b..c2d744215 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp @@ -1,258 +1,258 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameter.h" - -UHoudiniParameter::UHoudiniParameter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , ParmType(EHoudiniParameterType::Invalid) - , TupleSize(0) - , NodeId(-1) - , ParmId(-1) - , ParentParmId(-1) - , ChildIndex(-1) - , bIsVisible(true) - , bIsDisabled(false) - , bHasChanged(false) - , bNeedsToTriggerUpdate(true) - , bIsDefault(false) - , bIsSpare(false) - , bJoinNext(false) - , bIsChildOfMultiParm(false) - , bIsDirectChildOfMultiParm(false) - , TagCount(0) - , ValueIndex(-1) - , bHasExpression(false) - , bShowExpression(false) -{ - Name = TEXT(""); - Label = TEXT(""); - Help = TEXT(""); - -} - -UHoudiniParameter * -UHoudiniParameter::Create( UObject* InOuter, const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameter_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameter::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameter * HoudiniAssetParameter = NewObject< UHoudiniParameter >( - InOuter, UHoudiniParameter::StaticClass(), ParamName, RF_Public | RF_Transactional); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameter::IsChildParameter() const -{ - return ParentParmId >= 0; -} - -void -UHoudiniParameter::RevertToDefault() -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); -} - -void -UHoudiniParameter::RevertToDefault(const int32& InAtTupleIndex) -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.AddUnique(InAtTupleIndex); - - MarkChanged(true); -} - -void -UHoudiniParameter::MarkDefault(const bool& bInDefault) -{ - bIsDefault = bInDefault; - - if (bInDefault) - { - // No need to revert default parameter - bPendingRevertToDefault = false; - TuplePendingRevertToDefault.Empty(); - } -} - -EHoudiniRampInterpolationType UHoudiniParameter::GetHoudiniInterpMethodFromInt(int32 InInt) -{ - EHoudiniRampInterpolationType Result = EHoudiniRampInterpolationType::InValid; - switch (InInt) - { - case 0: - Result = EHoudiniRampInterpolationType::CONSTANT; - break; - case 1: - Result = EHoudiniRampInterpolationType::LINEAR; - break; - case 2: - Result = EHoudiniRampInterpolationType::CATMULL_ROM; - break; - case 3: - Result = EHoudiniRampInterpolationType::MONOTONE_CUBIC; - break; - case 4: - Result = EHoudiniRampInterpolationType::BEZIER; - break; - case 5: - Result = EHoudiniRampInterpolationType::BSPLINE; - break; - case 6: - Result = EHoudiniRampInterpolationType::HERMITE; - break; - } - - return Result; -} - -FString -UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType) -{ - FString Result = FString("InValid"); - switch (InType) - { - case EHoudiniRampInterpolationType::CONSTANT: - Result = FString("Constant"); - break; - case EHoudiniRampInterpolationType::LINEAR: - Result = FString("Linear"); - break; - case EHoudiniRampInterpolationType::BEZIER: - Result = FString("Bezier"); - break; - case EHoudiniRampInterpolationType::BSPLINE: - Result = FString("B-Spline"); - break; - case EHoudiniRampInterpolationType::MONOTONE_CUBIC: - Result = FString("Monotone Cubic"); - break; - case EHoudiniRampInterpolationType::CATMULL_ROM: - Result = FString("Catmull Rom"); - break; - case EHoudiniRampInterpolationType::HERMITE: - Result = FString("Hermite"); - break; - } - - return Result; -} -EHoudiniRampInterpolationType -UHoudiniParameter::GetHoudiniInterpMethodFromString(const FString& InString) -{ - if (InString.StartsWith(TEXT("Constant"))) - return EHoudiniRampInterpolationType::CONSTANT; - - else if (InString.StartsWith("Linear")) - return EHoudiniRampInterpolationType::LINEAR; - - else if (InString.StartsWith("Bezier")) - return EHoudiniRampInterpolationType::BEZIER; - - else if (InString.StartsWith("B-Spline")) - return EHoudiniRampInterpolationType::BSPLINE; - - else if (InString.StartsWith("Monotone Cubic")) - return EHoudiniRampInterpolationType::MONOTONE_CUBIC; - - else if (InString.StartsWith("Catmull Rom")) - return EHoudiniRampInterpolationType::CATMULL_ROM; - - else if (InString.StartsWith("Hermite")) - return EHoudiniRampInterpolationType::HERMITE; - - return EHoudiniRampInterpolationType::InValid; -} - - - -ERichCurveInterpMode -UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType) -{ - switch (InType) - { - case EHoudiniRampInterpolationType::CONSTANT: - return ERichCurveInterpMode::RCIM_Constant; - - case EHoudiniRampInterpolationType::LINEAR: - return ERichCurveInterpMode::RCIM_Linear; - - default: - break; - } - - return ERichCurveInterpMode::RCIM_Cubic; -} - -UHoudiniParameter* -UHoudiniParameter::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags, EObjectFlags SetFlags) -{ - UHoudiniParameter* NewParameter = Cast(StaticDuplicateObject(this, DestOuter)); - - NewParameter->CopyStateFrom(this, false); - - return NewParameter; -} - -void -UHoudiniParameter::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - - NodeId = InParameter->NodeId; - ParmId = InParameter->ParmId; - ParentParmId = InParameter->ParentParmId; - bIsDefault = InParameter->bIsDefault; - bPendingRevertToDefault = InParameter->bPendingRevertToDefault; - TuplePendingRevertToDefault = InParameter->TuplePendingRevertToDefault; - bShowExpression = InParameter->bShowExpression; -} - -void -UHoudiniParameter::InvalidateData() -{ - -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameter.h" + +UHoudiniParameter::UHoudiniParameter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , ParmType(EHoudiniParameterType::Invalid) + , TupleSize(0) + , NodeId(-1) + , ParmId(-1) + , ParentParmId(-1) + , ChildIndex(-1) + , bIsVisible(true) + , bIsDisabled(false) + , bHasChanged(false) + , bNeedsToTriggerUpdate(true) + , bIsDefault(false) + , bIsSpare(false) + , bJoinNext(false) + , bIsChildOfMultiParm(false) + , bIsDirectChildOfMultiParm(false) + , TagCount(0) + , ValueIndex(-1) + , bHasExpression(false) + , bShowExpression(false) +{ + Name = TEXT(""); + Label = TEXT(""); + Help = TEXT(""); + +} + +UHoudiniParameter * +UHoudiniParameter::Create( UObject* InOuter, const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameter_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameter::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameter * HoudiniAssetParameter = NewObject< UHoudiniParameter >( + InOuter, UHoudiniParameter::StaticClass(), ParamName, RF_Public | RF_Transactional); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameter::IsChildParameter() const +{ + return ParentParmId >= 0; +} + +void +UHoudiniParameter::RevertToDefault() +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); +} + +void +UHoudiniParameter::RevertToDefault(const int32& InAtTupleIndex) +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.AddUnique(InAtTupleIndex); + + MarkChanged(true); +} + +void +UHoudiniParameter::MarkDefault(const bool& bInDefault) +{ + bIsDefault = bInDefault; + + if (bInDefault) + { + // No need to revert default parameter + bPendingRevertToDefault = false; + TuplePendingRevertToDefault.Empty(); + } +} + +EHoudiniRampInterpolationType UHoudiniParameter::GetHoudiniInterpMethodFromInt(int32 InInt) +{ + EHoudiniRampInterpolationType Result = EHoudiniRampInterpolationType::InValid; + switch (InInt) + { + case 0: + Result = EHoudiniRampInterpolationType::CONSTANT; + break; + case 1: + Result = EHoudiniRampInterpolationType::LINEAR; + break; + case 2: + Result = EHoudiniRampInterpolationType::CATMULL_ROM; + break; + case 3: + Result = EHoudiniRampInterpolationType::MONOTONE_CUBIC; + break; + case 4: + Result = EHoudiniRampInterpolationType::BEZIER; + break; + case 5: + Result = EHoudiniRampInterpolationType::BSPLINE; + break; + case 6: + Result = EHoudiniRampInterpolationType::HERMITE; + break; + } + + return Result; +} + +FString +UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType) +{ + FString Result = FString("InValid"); + switch (InType) + { + case EHoudiniRampInterpolationType::CONSTANT: + Result = FString("Constant"); + break; + case EHoudiniRampInterpolationType::LINEAR: + Result = FString("Linear"); + break; + case EHoudiniRampInterpolationType::BEZIER: + Result = FString("Bezier"); + break; + case EHoudiniRampInterpolationType::BSPLINE: + Result = FString("B-Spline"); + break; + case EHoudiniRampInterpolationType::MONOTONE_CUBIC: + Result = FString("Monotone Cubic"); + break; + case EHoudiniRampInterpolationType::CATMULL_ROM: + Result = FString("Catmull Rom"); + break; + case EHoudiniRampInterpolationType::HERMITE: + Result = FString("Hermite"); + break; + } + + return Result; +} +EHoudiniRampInterpolationType +UHoudiniParameter::GetHoudiniInterpMethodFromString(const FString& InString) +{ + if (InString.StartsWith(TEXT("Constant"))) + return EHoudiniRampInterpolationType::CONSTANT; + + else if (InString.StartsWith("Linear")) + return EHoudiniRampInterpolationType::LINEAR; + + else if (InString.StartsWith("Bezier")) + return EHoudiniRampInterpolationType::BEZIER; + + else if (InString.StartsWith("B-Spline")) + return EHoudiniRampInterpolationType::BSPLINE; + + else if (InString.StartsWith("Monotone Cubic")) + return EHoudiniRampInterpolationType::MONOTONE_CUBIC; + + else if (InString.StartsWith("Catmull Rom")) + return EHoudiniRampInterpolationType::CATMULL_ROM; + + else if (InString.StartsWith("Hermite")) + return EHoudiniRampInterpolationType::HERMITE; + + return EHoudiniRampInterpolationType::InValid; +} + + + +ERichCurveInterpMode +UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType) +{ + switch (InType) + { + case EHoudiniRampInterpolationType::CONSTANT: + return ERichCurveInterpMode::RCIM_Constant; + + case EHoudiniRampInterpolationType::LINEAR: + return ERichCurveInterpMode::RCIM_Linear; + + default: + break; + } + + return ERichCurveInterpMode::RCIM_Cubic; +} + +UHoudiniParameter* +UHoudiniParameter::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags, EObjectFlags SetFlags) +{ + UHoudiniParameter* NewParameter = Cast(StaticDuplicateObject(this, DestOuter)); + + NewParameter->CopyStateFrom(this, false); + + return NewParameter; +} + +void +UHoudiniParameter::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + + NodeId = InParameter->NodeId; + ParmId = InParameter->ParmId; + ParentParmId = InParameter->ParentParmId; + bIsDefault = InParameter->bIsDefault; + bPendingRevertToDefault = InParameter->bPendingRevertToDefault; + TuplePendingRevertToDefault = InParameter->TuplePendingRevertToDefault; + bShowExpression = InParameter->bShowExpression; +} + +void +UHoudiniParameter::InvalidateData() +{ + +} + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h index 1d1d5ec19..45214ff43 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h @@ -1,325 +1,325 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/Object.h" -#include "Curves/RealCurve.h" -#include "HoudiniInput.h" - -#include "HoudiniParameter.generated.h" - -UENUM() -enum class EHoudiniParameterType : uint8 -{ - Invalid, - - Button, - ButtonStrip, - Color, - ColorRamp, - File, - FileDir, - FileGeo, - FileImage, - Float, - FloatRamp, - Folder, - FolderList, - Input, - Int, - IntChoice, - Label, - MultiParm, - Separator, - String, - StringChoice, - StringAssetRef, - Toggle, -}; - -UENUM() -enum class EHoudiniRampInterpolationType : int8 -{ - InValid = -1, - - CONSTANT = 0, - LINEAR = 1, - CATMULL_ROM = 2, - MONOTONE_CUBIC = 3, - BEZIER = 4, - BSPLINE = 5, - HERMITE = 6 -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject -{ - -public: - - GENERATED_UCLASS_BODY() - - friend class UHoudiniAssetParameter; - - // - static UHoudiniParameter * Create(UObject* Outer, const FString& ParamName); - - // Equality, consider two param equals if they have the same name, type, tuple size and disabled status - bool operator==(const UHoudiniParameter& other) const - { - return ( TupleSize == other.TupleSize && ParmType == other.ParmType - && Name.Equals(other.Name) && bIsDisabled == other.bIsDisabled ); - } - - bool Matches(const UHoudiniParameter& other) const { return (*this == other); }; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - // Get parent parameter for this parameter. - virtual const FString & GetParameterName() const { return Name; }; - virtual const FString & GetParameterLabel() const { return Label; }; - virtual const FString GetParameterHelp() const { return Help; }; - - virtual EHoudiniParameterType GetParameterType() const { return ParmType; }; - virtual int32 GetTupleSize() const { return TupleSize; }; - virtual int32 GetNodeId() const { return NodeId; }; - virtual int32 GetParmId() const { return ParmId; }; - virtual int32 GetParentParmId() const { return ParentParmId; }; - virtual int32 GetChildIndex() const { return ChildIndex; }; - - virtual bool IsVisible() const { return bIsVisible; }; - virtual bool ShouldDisplay() const{ return bIsVisible && ParmType != EHoudiniParameterType::Invalid; }; - virtual bool IsDisabled() const { return bIsDisabled; }; - virtual bool HasChanged() const { return bHasChanged; }; - virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; - virtual bool IsDefault() const { return true; }; - virtual bool IsSpare() const { return bIsSpare; }; - virtual bool GetJoinNext() const { return bJoinNext; }; - virtual bool IsAutoUpdate() const { return bAutoUpdate; }; - - virtual int32 GetTagCount() const { return TagCount; }; - virtual int32 GetValueIndex() const { return ValueIndex; }; - - virtual TMap& GetTags() { return Tags; }; - - virtual bool IsChildParameter() const; - - virtual bool IsPendingRevertToDefault() const { return bPendingRevertToDefault; }; - virtual void GetTuplePendingRevertToDefault(TArray& OutArray) { OutArray = TuplePendingRevertToDefault; }; - - virtual bool HasExpression() const { return bHasExpression; }; - virtual bool IsShowingExpression() const { return bShowExpression; }; - virtual FString GetExpression() const { return ParamExpression; }; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - - // Set parent parameter for this parameter. - virtual void SetParameterName(const FString& InName) { Name = InName; }; - virtual void SetParameterLabel(const FString& InLabel) { Label = InLabel; }; - virtual void SetParameterHelp(const FString& InHelp) { Help = InHelp; }; - - virtual void SetParameterType(const EHoudiniParameterType& InType) { ParmType = InType; }; - virtual void SetTupleSize(const uint32& InTupleSize) { TupleSize = InTupleSize; }; - virtual void SetNodeId(const int32& InNodeId) { NodeId = InNodeId; }; - virtual void SetParmId(const int32& InParmId) { ParmId = InParmId; }; - virtual void SetParentParmId(const int32& InParentParmId) { ParentParmId = InParentParmId; }; - virtual void SetChildIndex(const int32& InChildIndex) { ChildIndex = InChildIndex; }; - - virtual void SetIsChildOfMultiParm(const bool& IsChildOfMultiParam) { bIsChildOfMultiParm = IsChildOfMultiParam; }; - virtual bool GetIsChildOfMultiParm() const { return bIsChildOfMultiParm; }; - - virtual void SetIsDirectChildOfMultiParm(const bool& IsDirectChildOfMultiParam) { bIsDirectChildOfMultiParm = IsDirectChildOfMultiParam; }; - virtual bool IsDirectChildOfMultiParm() const { return bIsDirectChildOfMultiParm; }; - - virtual void SetVisible(const bool& InIsVisible) { bIsVisible = InIsVisible; }; - virtual void SetDisabled(const bool& InIsDisabled) { bIsDisabled = InIsDisabled; }; - virtual void SetDefault(const bool& InIsDefault) { bIsDefault = InIsDefault; }; - virtual void SetSpare(const bool& InIsSpare) { bIsSpare = InIsSpare; }; - virtual void SetJoinNext(const bool& InJoinNext) { bJoinNext = InJoinNext; }; - - virtual void SetTagCount(const uint32& InTagCount) { TagCount = InTagCount; }; - virtual void SetValueIndex(const uint32& InValueIndex) { ValueIndex = InValueIndex; }; - - virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - virtual void RevertToDefault(); - virtual void RevertToDefault(const int32& TupleIndex); - virtual void MarkDefault(const bool& bInDefault); - - virtual void SetHasExpression(const bool& InHasExpression) { bHasExpression = InHasExpression; }; - virtual void SetShowExpression(const bool& InShowExpression) { bShowExpression = InShowExpression; }; - virtual void SetExpression(const FString& InParamExpression) { ParamExpression = InParamExpression; }; - - virtual void SetAutoUpdate(const bool& InAutoUpdate) { bAutoUpdate = InAutoUpdate; }; - - static FString GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType); - static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromInt(int32 InInt); - static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromString(const FString& InString); - - static ERichCurveInterpMode EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType); - - // Duplicate this object for state transfer between component instances and templates - UHoudiniParameter* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - // Replace any input references using the provided mapping - virtual void RemapInputs(const TMap& InputMapping) {}; - - // Replace any parameter references using the provided mapping - virtual void RemapParameters(const TMap& ParameterMapping) {}; - - // Invalidate ids - virtual void InvalidateData(); - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - virtual void OnPreCook() {}; - -protected: - - //--------------------------------------------------------------------------------------------- - // ParmInfos - //--------------------------------------------------------------------------------------------- - - // - UPROPERTY() - FString Name; - - // - UPROPERTY() - FString Label; - - // Unreal type of the parameter - UPROPERTY() - EHoudiniParameterType ParmType; - - // Tuple size. For scalar parameters this value is 1, but for vector parameters this value can be greater. - UPROPERTY() - uint32 TupleSize; - - // Node this parameter belongs to. - UPROPERTY(DuplicateTransient) - int32 NodeId; - - // Id of this parameter. - UPROPERTY(DuplicateTransient) - int32 ParmId; - - // Id of parent parameter, -1 if root is parent. - UPROPERTY(DuplicateTransient) - int32 ParentParmId; - - // Child index within its parent parameter. - UPROPERTY() - int32 ChildIndex; - - // - UPROPERTY() - bool bIsVisible; - - // - UPROPERTY() - bool bIsDisabled; - - // Is set to true if value of this parameter has been changed by user. - UPROPERTY() - bool bHasChanged; - - // Is set to true if value of this parameter will trigger an update of the asset - UPROPERTY() - bool bNeedsToTriggerUpdate; - - // Indicates that this parameter is still using its default value - UPROPERTY(Transient, DuplicateTransient) - bool bIsDefault; - - // Permissions for file parms - UPROPERTY() - bool bIsSpare; - - // - UPROPERTY() - bool bJoinNext; - - // - UPROPERTY() - bool bIsChildOfMultiParm; - - UPROPERTY() - bool bIsDirectChildOfMultiParm; - - // Indicates a parameter value needs to be reverted to its default - UPROPERTY(DuplicateTransient) - bool bPendingRevertToDefault; - - UPROPERTY(DuplicateTransient) - TArray TuplePendingRevertToDefault; - - // - UPROPERTY() - FString Help; - - // Number of tags on this parameter - UPROPERTY() - uint32 TagCount; - - // The index to use to look into the values array in order to retrieve the actual value(s) of this parameter. - UPROPERTY() - int32 ValueIndex; - - //------------------------------------------------------------------------------------------------------------------------- - // Expression - // TODO: Use tuple array for this - // Indicates the parameters has an expression value - UPROPERTY() - bool bHasExpression; - - // Indicates we are currently displaying the parameter's value - UPROPERTY(DuplicateTransient) - bool bShowExpression; - - // The parameter's expression - UPROPERTY() - FString ParamExpression; - - UPROPERTY() - TMap Tags; - - UPROPERTY() - bool bAutoUpdate = true; - - -}; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/Object.h" +#include "Curves/RealCurve.h" +#include "HoudiniInput.h" + +#include "HoudiniParameter.generated.h" + +UENUM() +enum class EHoudiniParameterType : uint8 +{ + Invalid, + + Button, + ButtonStrip, + Color, + ColorRamp, + File, + FileDir, + FileGeo, + FileImage, + Float, + FloatRamp, + Folder, + FolderList, + Input, + Int, + IntChoice, + Label, + MultiParm, + Separator, + String, + StringChoice, + StringAssetRef, + Toggle, +}; + +UENUM() +enum class EHoudiniRampInterpolationType : int8 +{ + InValid = -1, + + CONSTANT = 0, + LINEAR = 1, + CATMULL_ROM = 2, + MONOTONE_CUBIC = 3, + BEZIER = 4, + BSPLINE = 5, + HERMITE = 6 +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject +{ + +public: + + GENERATED_UCLASS_BODY() + + friend class UHoudiniAssetParameter; + + // + static UHoudiniParameter * Create(UObject* Outer, const FString& ParamName); + + // Equality, consider two param equals if they have the same name, type, tuple size and disabled status + bool operator==(const UHoudiniParameter& other) const + { + return ( TupleSize == other.TupleSize && ParmType == other.ParmType + && Name.Equals(other.Name) && bIsDisabled == other.bIsDisabled ); + } + + bool Matches(const UHoudiniParameter& other) const { return (*this == other); }; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + // Get parent parameter for this parameter. + virtual const FString & GetParameterName() const { return Name; }; + virtual const FString & GetParameterLabel() const { return Label; }; + virtual const FString GetParameterHelp() const { return Help; }; + + virtual EHoudiniParameterType GetParameterType() const { return ParmType; }; + virtual int32 GetTupleSize() const { return TupleSize; }; + virtual int32 GetNodeId() const { return NodeId; }; + virtual int32 GetParmId() const { return ParmId; }; + virtual int32 GetParentParmId() const { return ParentParmId; }; + virtual int32 GetChildIndex() const { return ChildIndex; }; + + virtual bool IsVisible() const { return bIsVisible; }; + virtual bool ShouldDisplay() const{ return bIsVisible && ParmType != EHoudiniParameterType::Invalid; }; + virtual bool IsDisabled() const { return bIsDisabled; }; + virtual bool HasChanged() const { return bHasChanged; }; + virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; + virtual bool IsDefault() const { return true; }; + virtual bool IsSpare() const { return bIsSpare; }; + virtual bool GetJoinNext() const { return bJoinNext; }; + virtual bool IsAutoUpdate() const { return bAutoUpdate; }; + + virtual int32 GetTagCount() const { return TagCount; }; + virtual int32 GetValueIndex() const { return ValueIndex; }; + + virtual TMap& GetTags() { return Tags; }; + + virtual bool IsChildParameter() const; + + virtual bool IsPendingRevertToDefault() const { return bPendingRevertToDefault; }; + virtual void GetTuplePendingRevertToDefault(TArray& OutArray) { OutArray = TuplePendingRevertToDefault; }; + + virtual bool HasExpression() const { return bHasExpression; }; + virtual bool IsShowingExpression() const { return bShowExpression; }; + virtual FString GetExpression() const { return ParamExpression; }; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + + // Set parent parameter for this parameter. + virtual void SetParameterName(const FString& InName) { Name = InName; }; + virtual void SetParameterLabel(const FString& InLabel) { Label = InLabel; }; + virtual void SetParameterHelp(const FString& InHelp) { Help = InHelp; }; + + virtual void SetParameterType(const EHoudiniParameterType& InType) { ParmType = InType; }; + virtual void SetTupleSize(const uint32& InTupleSize) { TupleSize = InTupleSize; }; + virtual void SetNodeId(const int32& InNodeId) { NodeId = InNodeId; }; + virtual void SetParmId(const int32& InParmId) { ParmId = InParmId; }; + virtual void SetParentParmId(const int32& InParentParmId) { ParentParmId = InParentParmId; }; + virtual void SetChildIndex(const int32& InChildIndex) { ChildIndex = InChildIndex; }; + + virtual void SetIsChildOfMultiParm(const bool& IsChildOfMultiParam) { bIsChildOfMultiParm = IsChildOfMultiParam; }; + virtual bool GetIsChildOfMultiParm() const { return bIsChildOfMultiParm; }; + + virtual void SetIsDirectChildOfMultiParm(const bool& IsDirectChildOfMultiParam) { bIsDirectChildOfMultiParm = IsDirectChildOfMultiParam; }; + virtual bool IsDirectChildOfMultiParm() const { return bIsDirectChildOfMultiParm; }; + + virtual void SetVisible(const bool& InIsVisible) { bIsVisible = InIsVisible; }; + virtual void SetDisabled(const bool& InIsDisabled) { bIsDisabled = InIsDisabled; }; + virtual void SetDefault(const bool& InIsDefault) { bIsDefault = InIsDefault; }; + virtual void SetSpare(const bool& InIsSpare) { bIsSpare = InIsSpare; }; + virtual void SetJoinNext(const bool& InJoinNext) { bJoinNext = InJoinNext; }; + + virtual void SetTagCount(const uint32& InTagCount) { TagCount = InTagCount; }; + virtual void SetValueIndex(const uint32& InValueIndex) { ValueIndex = InValueIndex; }; + + virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + virtual void RevertToDefault(); + virtual void RevertToDefault(const int32& TupleIndex); + virtual void MarkDefault(const bool& bInDefault); + + virtual void SetHasExpression(const bool& InHasExpression) { bHasExpression = InHasExpression; }; + virtual void SetShowExpression(const bool& InShowExpression) { bShowExpression = InShowExpression; }; + virtual void SetExpression(const FString& InParamExpression) { ParamExpression = InParamExpression; }; + + virtual void SetAutoUpdate(const bool& InAutoUpdate) { bAutoUpdate = InAutoUpdate; }; + + static FString GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType); + static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromInt(int32 InInt); + static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromString(const FString& InString); + + static ERichCurveInterpMode EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType); + + // Duplicate this object for state transfer between component instances and templates + UHoudiniParameter* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + // Replace any input references using the provided mapping + virtual void RemapInputs(const TMap& InputMapping) {}; + + // Replace any parameter references using the provided mapping + virtual void RemapParameters(const TMap& ParameterMapping) {}; + + // Invalidate ids + virtual void InvalidateData(); + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + virtual void OnPreCook() {}; + +protected: + + //--------------------------------------------------------------------------------------------- + // ParmInfos + //--------------------------------------------------------------------------------------------- + + // + UPROPERTY() + FString Name; + + // + UPROPERTY() + FString Label; + + // Unreal type of the parameter + UPROPERTY() + EHoudiniParameterType ParmType; + + // Tuple size. For scalar parameters this value is 1, but for vector parameters this value can be greater. + UPROPERTY() + uint32 TupleSize; + + // Node this parameter belongs to. + UPROPERTY(DuplicateTransient) + int32 NodeId; + + // Id of this parameter. + UPROPERTY(DuplicateTransient) + int32 ParmId; + + // Id of parent parameter, -1 if root is parent. + UPROPERTY(DuplicateTransient) + int32 ParentParmId; + + // Child index within its parent parameter. + UPROPERTY() + int32 ChildIndex; + + // + UPROPERTY() + bool bIsVisible; + + // + UPROPERTY() + bool bIsDisabled; + + // Is set to true if value of this parameter has been changed by user. + UPROPERTY() + bool bHasChanged; + + // Is set to true if value of this parameter will trigger an update of the asset + UPROPERTY() + bool bNeedsToTriggerUpdate; + + // Indicates that this parameter is still using its default value + UPROPERTY(Transient, DuplicateTransient) + bool bIsDefault; + + // Permissions for file parms + UPROPERTY() + bool bIsSpare; + + // + UPROPERTY() + bool bJoinNext; + + // + UPROPERTY() + bool bIsChildOfMultiParm; + + UPROPERTY() + bool bIsDirectChildOfMultiParm; + + // Indicates a parameter value needs to be reverted to its default + UPROPERTY(DuplicateTransient) + bool bPendingRevertToDefault; + + UPROPERTY(DuplicateTransient) + TArray TuplePendingRevertToDefault; + + // + UPROPERTY() + FString Help; + + // Number of tags on this parameter + UPROPERTY() + uint32 TagCount; + + // The index to use to look into the values array in order to retrieve the actual value(s) of this parameter. + UPROPERTY() + int32 ValueIndex; + + //------------------------------------------------------------------------------------------------------------------------- + // Expression + // TODO: Use tuple array for this + // Indicates the parameters has an expression value + UPROPERTY() + bool bHasExpression; + + // Indicates we are currently displaying the parameter's value + UPROPERTY(DuplicateTransient) + bool bShowExpression; + + // The parameter's expression + UPROPERTY() + FString ParamExpression; + + UPROPERTY() + TMap Tags; + + UPROPERTY() + bool bAutoUpdate = true; + + +}; + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp index f24c5a07b..269c23cc6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterButton.h" - -UHoudiniParameterButton::UHoudiniParameterButton(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Button; -} - -UHoudiniParameterButton * -UHoudiniParameterButton::Create( UObject* InOuter, const FString& InParamName ) -{ - FString ParamNameStr = "HoudiniParameterButton_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButton::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterButton * HoudiniAssetParameter = NewObject< UHoudiniParameterButton >( - InOuter, UHoudiniParameterButton::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Button); - - //HoudiniAssetParameterButton->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterButton.h" + +UHoudiniParameterButton::UHoudiniParameterButton(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Button; +} + +UHoudiniParameterButton * +UHoudiniParameterButton::Create( UObject* InOuter, const FString& InParamName ) +{ + FString ParamNameStr = "HoudiniParameterButton_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButton::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterButton * HoudiniAssetParameter = NewObject< UHoudiniParameterButton >( + InOuter, UHoudiniParameterButton::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Button); + + //HoudiniAssetParameterButton->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h index ff285a923..e35ee513e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterButton.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterButton : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterButton * Create( - UObject* Outer, - const FString& ParamName); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterButton.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterButton : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterButton * Create( + UObject* Outer, + const FString& ParamName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp index be51713f0..785c2fc90 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp @@ -1,74 +1,74 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterButtonStrip.h" - -UHoudiniParameterButtonStrip::UHoudiniParameterButtonStrip(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::ButtonStrip; -} - -UHoudiniParameterButtonStrip * -UHoudiniParameterButtonStrip::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterButton_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButtonStrip::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterButtonStrip * HoudiniAssetParameter = NewObject< UHoudiniParameterButtonStrip >( - InOuter, UHoudiniParameterButtonStrip::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::ButtonStrip); - - HoudiniAssetParameter->Count = 0; - - return HoudiniAssetParameter; -} - -FString * -UHoudiniParameterButtonStrip::GetStringLabelAt(const int32 & InIndex) -{ - if (!Labels.IsValidIndex(InIndex)) - return nullptr; - - return &(Labels[InIndex]); -} - -bool -UHoudiniParameterButtonStrip::SetValueAt(const int32 & InIdx, int32 InVal) -{ - if (!Values.IsValidIndex(InIdx)) - return false; - - if (Values[InIdx] == InVal) - return false; - - Values[InIdx] = InVal; - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterButtonStrip.h" + +UHoudiniParameterButtonStrip::UHoudiniParameterButtonStrip(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::ButtonStrip; +} + +UHoudiniParameterButtonStrip * +UHoudiniParameterButtonStrip::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterButton_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButtonStrip::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterButtonStrip * HoudiniAssetParameter = NewObject< UHoudiniParameterButtonStrip >( + InOuter, UHoudiniParameterButtonStrip::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::ButtonStrip); + + HoudiniAssetParameter->Count = 0; + + return HoudiniAssetParameter; +} + +FString * +UHoudiniParameterButtonStrip::GetStringLabelAt(const int32 & InIndex) +{ + if (!Labels.IsValidIndex(InIndex)) + return nullptr; + + return &(Labels[InIndex]); +} + +bool +UHoudiniParameterButtonStrip::SetValueAt(const int32 & InIdx, int32 InVal) +{ + if (!Values.IsValidIndex(InIdx)) + return false; + + if (Values[InIdx] == InVal) + return false; + + Values[InIdx] = InVal; + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h index c923c6ffa..a515e51cb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h @@ -1,66 +1,66 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterButtonStrip.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterButtonStrip : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterButtonStrip * Create( - UObject* InOuter, - const FString& InParamName); - - UPROPERTY() - int32 Count; - - UPROPERTY() - TArray Labels; - - UPROPERTY() - TArray Values; - - - void InitializeLabels(const int32 & InSize) { Labels.SetNumZeroed(InSize); Values.SetNumZeroed(InSize); Count = InSize; }; - - bool SetValueAt(const int32 & InIdx, int32 InVal); - - - FString * GetStringLabelAt(const int32 & InIndex); - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterButtonStrip.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterButtonStrip : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterButtonStrip * Create( + UObject* InOuter, + const FString& InParamName); + + UPROPERTY() + int32 Count; + + UPROPERTY() + TArray Labels; + + UPROPERTY() + TArray Values; + + + void InitializeLabels(const int32 & InSize) { Labels.SetNumZeroed(InSize); Values.SetNumZeroed(InSize); Count = InSize; }; + + bool SetValueAt(const int32 & InIdx, int32 InVal); + + + FString * GetStringLabelAt(const int32 & InIndex); + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp index 58ae4ecd2..73dde74db 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp @@ -1,260 +1,260 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterChoice.h" - -UHoudiniParameterChoice::UHoudiniParameterChoice(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , IntValue(-1) -{ - ParmType = EHoudiniParameterType::IntChoice; -} - -UHoudiniParameterChoice * -UHoudiniParameterChoice::Create( UObject* InOuter, const FString& InParamName, const EHoudiniParameterType& InParmType) -{ - FString ParamNameStr = "HoudiniParameterChoice_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterChoice::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterChoice * HoudiniAssetParameter = NewObject< UHoudiniParameterChoice >( - InOuter, UHoudiniParameterChoice::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(InParmType); - - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -void -UHoudiniParameterChoice::BeginDestroy() -{ - // We need to clean up our arrays - for (auto& Ptr : ChoiceLabelsPtr) - { - Ptr.Reset(); - } - ChoiceLabelsPtr.Empty(); - - // Then the string arrays - StringChoiceLabels.Empty(); - StringChoiceValues.Empty(); - - Super::BeginDestroy(); -} - - -FString* -UHoudiniParameterChoice::GetStringChoiceValueAt(const int32& InAtIndex) -{ - if (!StringChoiceValues.IsValidIndex(InAtIndex)) - return nullptr; - - return &(StringChoiceValues[InAtIndex]); -} - -FString* -UHoudiniParameterChoice::GetStringChoiceLabelAt(const int32& InAtIndex) -{ - if (!StringChoiceLabels.IsValidIndex(InAtIndex)) - return nullptr; - - return &(StringChoiceLabels[InAtIndex]); -} - -void -UHoudiniParameterChoice::SetNumChoices(const int32& InNumChoices) -{ - // Set the array sizes - StringChoiceValues.SetNumZeroed(InNumChoices); - StringChoiceLabels.SetNumZeroed(InNumChoices); - - UpdateChoiceLabelsPtr(); -} - -// Update the pointers to the ChoiceLabels -bool -UHoudiniParameterChoice::UpdateChoiceLabelsPtr() -{ - /* - bool bNeedUpdate = false; - if (StringChoiceLabels.Num() != ChoiceLabelsPtr.Num()) - { - bNeedUpdate = true; - } - else - { - for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) - { - if (ChoiceLabelsPtr[Idx].Get() == &(StringChoiceLabels[Idx])) - continue; - - bNeedUpdate = true; - break; - } - } - - if (!bNeedUpdate) - return true; - */ - - // Updates the Label Ptr array - ChoiceLabelsPtr.SetNumZeroed(StringChoiceLabels.Num()); - for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) - { - ChoiceLabelsPtr[Idx] = MakeShared(StringChoiceLabels[Idx]); - } - - return true; -} - -bool -UHoudiniParameterChoice::SetIntValue(const int32& InIntValue) -{ - if (InIntValue == IntValue) - return false; - - IntValue = InIntValue; - - return true; -} - -bool -UHoudiniParameterChoice::SetStringValue(const FString& InStringValue) -{ - if (InStringValue.Equals(StringValue)) - return false; - - StringValue = InStringValue; - - return true; -}; - -bool -UHoudiniParameterChoice::UpdateIntValueFromString() -{ - int32 FoundInt = INDEX_NONE; - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - // Update the int values from the string value - FoundInt = StringChoiceLabels.Find(StringValue); - } - else - { - // Update the int values from the string value - FoundInt = StringChoiceValues.Find(StringValue); - } - - if (FoundInt == INDEX_NONE) - return false; - - if (IntValue == FoundInt) - return false; - - IntValue = FoundInt; - - return true; -} - -bool -UHoudiniParameterChoice::UpdateStringValueFromInt() -{ - // Update the string value from the int value - FString NewStringValue; - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - // IntChoices only have labels - if (!StringChoiceLabels.IsValidIndex(IntValue)) - return false; - - NewStringValue = StringChoiceLabels[IntValue]; - } - else - { - // StringChoices should use values - if (!StringChoiceValues.IsValidIndex(IntValue)) - return false; - - NewStringValue = StringChoiceValues[IntValue]; - } - - if (StringValue.Equals(NewStringValue)) - return false; - - StringValue = NewStringValue; - - return true; -} - -const int32 -UHoudiniParameterChoice::GetIntValueFromLabel(const FString& InSelectedLabel) const -{ - return StringChoiceLabels.Find(InSelectedLabel); -} - -TOptional< TSharedPtr > -UHoudiniParameterChoice::GetValue(int32 Idx) const -{ - if (Idx == 0 && StringChoiceValues.IsValidIndex(IntValue)) - { - return TOptional< TSharedPtr< FString > >(MakeShared(StringChoiceValues[IntValue])); - } - - return TOptional< TSharedPtr< FString > >(); -} - -bool -UHoudiniParameterChoice::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - return IntValue == DefaultIntValue; - } - - if (GetParameterType() == EHoudiniParameterType::StringChoice) - { - return StringValue == DefaultStringValue; - } - - return true; -} - -void -UHoudiniParameterChoice::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterChoice.h" + +UHoudiniParameterChoice::UHoudiniParameterChoice(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , IntValue(-1) +{ + ParmType = EHoudiniParameterType::IntChoice; +} + +UHoudiniParameterChoice * +UHoudiniParameterChoice::Create( UObject* InOuter, const FString& InParamName, const EHoudiniParameterType& InParmType) +{ + FString ParamNameStr = "HoudiniParameterChoice_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterChoice::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterChoice * HoudiniAssetParameter = NewObject< UHoudiniParameterChoice >( + InOuter, UHoudiniParameterChoice::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(InParmType); + + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +void +UHoudiniParameterChoice::BeginDestroy() +{ + // We need to clean up our arrays + for (auto& Ptr : ChoiceLabelsPtr) + { + Ptr.Reset(); + } + ChoiceLabelsPtr.Empty(); + + // Then the string arrays + StringChoiceLabels.Empty(); + StringChoiceValues.Empty(); + + Super::BeginDestroy(); +} + + +FString* +UHoudiniParameterChoice::GetStringChoiceValueAt(const int32& InAtIndex) +{ + if (!StringChoiceValues.IsValidIndex(InAtIndex)) + return nullptr; + + return &(StringChoiceValues[InAtIndex]); +} + +FString* +UHoudiniParameterChoice::GetStringChoiceLabelAt(const int32& InAtIndex) +{ + if (!StringChoiceLabels.IsValidIndex(InAtIndex)) + return nullptr; + + return &(StringChoiceLabels[InAtIndex]); +} + +void +UHoudiniParameterChoice::SetNumChoices(const int32& InNumChoices) +{ + // Set the array sizes + StringChoiceValues.SetNumZeroed(InNumChoices); + StringChoiceLabels.SetNumZeroed(InNumChoices); + + UpdateChoiceLabelsPtr(); +} + +// Update the pointers to the ChoiceLabels +bool +UHoudiniParameterChoice::UpdateChoiceLabelsPtr() +{ + /* + bool bNeedUpdate = false; + if (StringChoiceLabels.Num() != ChoiceLabelsPtr.Num()) + { + bNeedUpdate = true; + } + else + { + for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) + { + if (ChoiceLabelsPtr[Idx].Get() == &(StringChoiceLabels[Idx])) + continue; + + bNeedUpdate = true; + break; + } + } + + if (!bNeedUpdate) + return true; + */ + + // Updates the Label Ptr array + ChoiceLabelsPtr.SetNumZeroed(StringChoiceLabels.Num()); + for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) + { + ChoiceLabelsPtr[Idx] = MakeShared(StringChoiceLabels[Idx]); + } + + return true; +} + +bool +UHoudiniParameterChoice::SetIntValue(const int32& InIntValue) +{ + if (InIntValue == IntValue) + return false; + + IntValue = InIntValue; + + return true; +} + +bool +UHoudiniParameterChoice::SetStringValue(const FString& InStringValue) +{ + if (InStringValue.Equals(StringValue)) + return false; + + StringValue = InStringValue; + + return true; +}; + +bool +UHoudiniParameterChoice::UpdateIntValueFromString() +{ + int32 FoundInt = INDEX_NONE; + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + // Update the int values from the string value + FoundInt = StringChoiceLabels.Find(StringValue); + } + else + { + // Update the int values from the string value + FoundInt = StringChoiceValues.Find(StringValue); + } + + if (FoundInt == INDEX_NONE) + return false; + + if (IntValue == FoundInt) + return false; + + IntValue = FoundInt; + + return true; +} + +bool +UHoudiniParameterChoice::UpdateStringValueFromInt() +{ + // Update the string value from the int value + FString NewStringValue; + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + // IntChoices only have labels + if (!StringChoiceLabels.IsValidIndex(IntValue)) + return false; + + NewStringValue = StringChoiceLabels[IntValue]; + } + else + { + // StringChoices should use values + if (!StringChoiceValues.IsValidIndex(IntValue)) + return false; + + NewStringValue = StringChoiceValues[IntValue]; + } + + if (StringValue.Equals(NewStringValue)) + return false; + + StringValue = NewStringValue; + + return true; +} + +const int32 +UHoudiniParameterChoice::GetIntValueFromLabel(const FString& InSelectedLabel) const +{ + return StringChoiceLabels.Find(InSelectedLabel); +} + +TOptional< TSharedPtr > +UHoudiniParameterChoice::GetValue(int32 Idx) const +{ + if (Idx == 0 && StringChoiceValues.IsValidIndex(IntValue)) + { + return TOptional< TSharedPtr< FString > >(MakeShared(StringChoiceValues[IntValue])); + } + + return TOptional< TSharedPtr< FString > >(); +} + +bool +UHoudiniParameterChoice::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + return IntValue == DefaultIntValue; + } + + if (GetParameterType() == EHoudiniParameterType::StringChoice) + { + return StringValue == DefaultStringValue; + } + + return true; +} + +void +UHoudiniParameterChoice::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h index 2c91cb0ea..c23fc2bd2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h @@ -1,125 +1,125 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterChoice.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create an instance of this class. - static UHoudiniParameterChoice * Create( - UObject* Outer, - const FString& ParamName, - const EHoudiniParameterType& ParmType); - - virtual void BeginDestroy() override; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - const int32 GetIntValue() const { return IntValue; }; - const FString GetStringValue() const { return StringValue; }; - const int32 GetNumChoices() const { return StringChoiceLabels.Num(); }; - const FString GetLabel() const { return StringChoiceLabels.IsValidIndex(IntValue) ? StringChoiceLabels[IntValue] : FString(); }; - const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; - - bool IsDefault() const override; - - TOptional> GetValue(int32 Idx) const; - - const int32 GetIntValueFromLabel(const FString& InSelectedLabel) const; - - FString* GetStringChoiceValueAt(const int32& InAtIndex); - FString* GetStringChoiceLabelAt(const int32& InAtIndex); - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Returns the ChoiceLabel SharedPtr array, used for UI only - TArray>* GetChoiceLabelsPtr() { return &ChoiceLabelsPtr; }; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - bool SetIntValue(const int32& InIntValue); - bool SetStringValue(const FString& InStringValue); - void SetNumChoices(const int32& InNumChoices); - - // For string choices only, update the int values from the string value - bool UpdateIntValueFromString(); - // For int choices only, update the string value from the int value - bool UpdateStringValueFromInt(); - // Update the pointers to the ChoiceLabels - bool UpdateChoiceLabelsPtr(); - - void SetDefaultIntValue() { DefaultIntValue = IntValue; }; - void SetDefaultStringValue() { DefaultStringValue = StringValue; }; - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - void RevertToDefault() override; - -protected: - - // Current int value for this property. - UPROPERTY() - int32 IntValue; - - // Default int value for this property, assigned at creating the parameter. - UPROPERTY() - int32 DefaultIntValue; - - // Current string value for this property - UPROPERTY() - FString StringValue; - - // Default string value for this property, assigned at creating the parameter. - UPROPERTY() - FString DefaultStringValue; - - // Used only for StringChoices! - // All the possible string values for this parameter's choices - UPROPERTY() - TArray StringChoiceValues; - - // Labels corresponding to this parameter's choices. - UPROPERTY() - TArray StringChoiceLabels; - - // Array of SharedPtr pointing to this parameter's label, used for UI only - TArray> ChoiceLabelsPtr; - - UPROPERTY() - bool bIsChildOfRamp; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterChoice.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create an instance of this class. + static UHoudiniParameterChoice * Create( + UObject* Outer, + const FString& ParamName, + const EHoudiniParameterType& ParmType); + + virtual void BeginDestroy() override; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + const int32 GetIntValue() const { return IntValue; }; + const FString GetStringValue() const { return StringValue; }; + const int32 GetNumChoices() const { return StringChoiceLabels.Num(); }; + const FString GetLabel() const { return StringChoiceLabels.IsValidIndex(IntValue) ? StringChoiceLabels[IntValue] : FString(); }; + const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; + + bool IsDefault() const override; + + TOptional> GetValue(int32 Idx) const; + + const int32 GetIntValueFromLabel(const FString& InSelectedLabel) const; + + FString* GetStringChoiceValueAt(const int32& InAtIndex); + FString* GetStringChoiceLabelAt(const int32& InAtIndex); + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Returns the ChoiceLabel SharedPtr array, used for UI only + TArray>* GetChoiceLabelsPtr() { return &ChoiceLabelsPtr; }; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + bool SetIntValue(const int32& InIntValue); + bool SetStringValue(const FString& InStringValue); + void SetNumChoices(const int32& InNumChoices); + + // For string choices only, update the int values from the string value + bool UpdateIntValueFromString(); + // For int choices only, update the string value from the int value + bool UpdateStringValueFromInt(); + // Update the pointers to the ChoiceLabels + bool UpdateChoiceLabelsPtr(); + + void SetDefaultIntValue() { DefaultIntValue = IntValue; }; + void SetDefaultStringValue() { DefaultStringValue = StringValue; }; + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + void RevertToDefault() override; + +protected: + + // Current int value for this property. + UPROPERTY() + int32 IntValue; + + // Default int value for this property, assigned at creating the parameter. + UPROPERTY() + int32 DefaultIntValue; + + // Current string value for this property + UPROPERTY() + FString StringValue; + + // Default string value for this property, assigned at creating the parameter. + UPROPERTY() + FString DefaultStringValue; + + // Used only for StringChoices! + // All the possible string values for this parameter's choices + UPROPERTY() + TArray StringChoiceValues; + + // Labels corresponding to this parameter's choices. + UPROPERTY() + TArray StringChoiceLabels; + + // Array of SharedPtr pointing to this parameter's label, used for UI only + TArray> ChoiceLabelsPtr; + + UPROPERTY() + bool bIsChildOfRamp; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp index a77d5cf41..e981690ce 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp @@ -1,84 +1,84 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterColor.h" - -UHoudiniParameterColor::UHoudiniParameterColor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Color( FLinearColor::White ) -{ - ParmType = EHoudiniParameterType::Color; -} - -UHoudiniParameterColor * -UHoudiniParameterColor::Create( UObject* InOuter, const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterColor_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterColor::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterColor * HoudiniAssetParameter = NewObject< UHoudiniParameterColor >( - InOuter, UHoudiniParameterColor::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Color); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->bIsChildOfRamp = false; - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterColor::SetColorValue(const FLinearColor& InColor) -{ - if (InColor == Color) - return false; - - Color = InColor; - - return true; -} - -bool -UHoudiniParameterColor::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - return Color == DefaultColor; -} - -void -UHoudiniParameterColor::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterColor.h" + +UHoudiniParameterColor::UHoudiniParameterColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Color( FLinearColor::White ) +{ + ParmType = EHoudiniParameterType::Color; +} + +UHoudiniParameterColor * +UHoudiniParameterColor::Create( UObject* InOuter, const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterColor_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterColor::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterColor * HoudiniAssetParameter = NewObject< UHoudiniParameterColor >( + InOuter, UHoudiniParameterColor::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Color); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->bIsChildOfRamp = false; + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterColor::SetColorValue(const FLinearColor& InColor) +{ + if (InColor == Color) + return false; + + Color = InColor; + + return true; +} + +bool +UHoudiniParameterColor::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + return Color == DefaultColor; +} + +void +UHoudiniParameterColor::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h index 3703524f0..7224b183f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h @@ -1,73 +1,73 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterColor.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterColor : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - - public: - - // Create instance of this class. - static UHoudiniParameterColor * Create( - UObject* Outer, - const FString& ParamName); - - // Accessor - FLinearColor GetColorValue() const { return Color; }; - - bool IsDefault() const override; - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Mutators - bool SetColorValue(const FLinearColor& InColor); - - void SetDefaultValue() { DefaultColor = Color; }; - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - void RevertToDefault() override; - - protected: - - // Color for this property. - UPROPERTY() - FLinearColor Color; - - // Default color for this property - UPROPERTY() - FLinearColor DefaultColor; - - UPROPERTY() - bool bIsChildOfRamp; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterColor.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterColor : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + + public: + + // Create instance of this class. + static UHoudiniParameterColor * Create( + UObject* Outer, + const FString& ParamName); + + // Accessor + FLinearColor GetColorValue() const { return Color; }; + + bool IsDefault() const override; + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Mutators + bool SetColorValue(const FLinearColor& InColor); + + void SetDefaultValue() { DefaultColor = Color; }; + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + void RevertToDefault() override; + + protected: + + // Color for this property. + UPROPERTY() + FLinearColor Color; + + // Default color for this property + UPROPERTY() + FLinearColor DefaultColor; + + UPROPERTY() + bool bIsChildOfRamp; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp index 97b5f7992..c474d3cf9 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp @@ -1,101 +1,101 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFile.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" - -#include "Misc/Paths.h" - -UHoudiniParameterFile::UHoudiniParameterFile(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Filters() - , bIsReadOnly(false) -{ - ParmType = EHoudiniParameterType::File; -} - -UHoudiniParameterFile * -UHoudiniParameterFile::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFile_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFile::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFile * HoudiniAssetParameter = NewObject< UHoudiniParameterFile >( - InOuter, UHoudiniParameterFile::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::File); - - return HoudiniAssetParameter; -} - - -bool -UHoudiniParameterFile::SetValueAt(const FString& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index] == InValue) - return false; - - Values[Index] = InValue; - - return true; -} - -bool -UHoudiniParameterFile::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!DefaultValues.IsValidIndex(Idx)) - break; - - if (Values[Idx] != DefaultValues[Idx]) - return false; - } - - return true; -} - -void -UHoudiniParameterFile::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFile.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" + +#include "Misc/Paths.h" + +UHoudiniParameterFile::UHoudiniParameterFile(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Filters() + , bIsReadOnly(false) +{ + ParmType = EHoudiniParameterType::File; +} + +UHoudiniParameterFile * +UHoudiniParameterFile::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFile_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFile::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFile * HoudiniAssetParameter = NewObject< UHoudiniParameterFile >( + InOuter, UHoudiniParameterFile::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::File); + + return HoudiniAssetParameter; +} + + +bool +UHoudiniParameterFile::SetValueAt(const FString& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index] == InValue) + return false; + + Values[Index] = InValue; + + return true; +} + +bool +UHoudiniParameterFile::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!DefaultValues.IsValidIndex(Idx)) + break; + + if (Values[Idx] != DefaultValues[Idx]) + return false; + } + + return true; +} + +void +UHoudiniParameterFile::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h index 3e8bf344c..00a4ea8b6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h @@ -1,78 +1,78 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFile.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFile : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFile * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetFileFilters() const { return Filters; }; - bool IsReadOnly() const { return bIsReadOnly; }; - FString GetValueAt(int32 Index) { return Values[Index]; }; - int32 GetNumValues() { return Values.Num(); }; - - void SetNumberOfValues(const uint32& NumValues) { Values.SetNum(NumValues); }; - bool SetValueAt(const FString& InValue, const uint32& Index); - - bool IsDefault() const override; - - // Mutators - void SetFileFilters(const FString& InFilters) { Filters = InFilters; }; - void SetReadOnly(const bool& InReadOnly) { bIsReadOnly = InReadOnly; }; - - void SetDefaultValues(); - -protected: - - // Values of this property. - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; - - // Filters of this property. - UPROPERTY() - FString Filters; - - // Is the file parameter read-only? - UPROPERTY() - bool bIsReadOnly; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFile.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFile : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFile * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetFileFilters() const { return Filters; }; + bool IsReadOnly() const { return bIsReadOnly; }; + FString GetValueAt(int32 Index) { return Values[Index]; }; + int32 GetNumValues() { return Values.Num(); }; + + void SetNumberOfValues(const uint32& NumValues) { Values.SetNum(NumValues); }; + bool SetValueAt(const FString& InValue, const uint32& Index); + + bool IsDefault() const override; + + // Mutators + void SetFileFilters(const FString& InFilters) { Filters = InFilters; }; + void SetReadOnly(const bool& InReadOnly) { bIsReadOnly = InReadOnly; }; + + void SetDefaultValues(); + +protected: + + // Values of this property. + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; + + // Filters of this property. + UPROPERTY() + FString Filters; + + // Is the file parameter read-only? + UPROPERTY() + bool bIsReadOnly; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp index fb2361814..43b987411 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp @@ -1,158 +1,158 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFloat.h" - -UHoudiniParameterFloat::UHoudiniParameterFloat(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Unit(TEXT("")) - , bNoSwap(false) - , bHasMin(false) - , bHasMax(false) - , bHasUIMin(false) - , bHasUIMax(false) - , bIsLogarithmic(false) - , Min(TNumericLimits::Lowest()) - , Max(TNumericLimits::Max()) - , UIMin(TNumericLimits::Lowest()) - , UIMax(TNumericLimits::Max()) -{ - ParmType = EHoudiniParameterType::Float; -} - -UHoudiniParameterFloat * -UHoudiniParameterFloat::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFloat_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFloat::StaticClass(), *ParamNameStr); - // We need to create a new parameter - UHoudiniParameterFloat * HoudiniAssetParameter = NewObject< UHoudiniParameterFloat >( - InOuter, UHoudiniParameterFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); - - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Float); - - HoudiniAssetParameter->bIsChildOfRamp = false; - HoudiniAssetParameter->bIsLogarithmic = false; - return HoudiniAssetParameter; -} - -TOptional< float > -UHoudiniParameterFloat::GetValue(int32 Idx) const -{ - if (Values.IsValidIndex(Idx)) - return TOptional< float >(Values[Idx]); - else - return TOptional< float >(); -} - -bool -UHoudiniParameterFloat::SetValueAt(const float& InValue, const int32& AtIndex) -{ - if (!Values.IsValidIndex(AtIndex)) - return false; - - if (InValue == Values[AtIndex]) - return false; - - Values[AtIndex] = FMath::Clamp< float >(InValue, Min, Max); - - return true; -} - -void -UHoudiniParameterFloat::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterFloat::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterFloat::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; -} - -void -UHoudiniParameterFloat::SetValue(float InValue, int32 Idx) -{ - if (!Values.IsValidIndex(Idx)) - return; - - if (InValue == Values[Idx]) - return; - - Values[Idx] = FMath::Clamp< float >(InValue, Min, Max); -} - -void -UHoudiniParameterFloat::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } -} - -void -UHoudiniParameterFloat::RevertToDefault(const int32& TupleIndex) -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.AddUnique(TupleIndex); - - MarkChanged(true); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFloat.h" + +UHoudiniParameterFloat::UHoudiniParameterFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Unit(TEXT("")) + , bNoSwap(false) + , bHasMin(false) + , bHasMax(false) + , bHasUIMin(false) + , bHasUIMax(false) + , bIsLogarithmic(false) + , Min(TNumericLimits::Lowest()) + , Max(TNumericLimits::Max()) + , UIMin(TNumericLimits::Lowest()) + , UIMax(TNumericLimits::Max()) +{ + ParmType = EHoudiniParameterType::Float; +} + +UHoudiniParameterFloat * +UHoudiniParameterFloat::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFloat_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFloat::StaticClass(), *ParamNameStr); + // We need to create a new parameter + UHoudiniParameterFloat * HoudiniAssetParameter = NewObject< UHoudiniParameterFloat >( + InOuter, UHoudiniParameterFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); + + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Float); + + HoudiniAssetParameter->bIsChildOfRamp = false; + HoudiniAssetParameter->bIsLogarithmic = false; + return HoudiniAssetParameter; +} + +TOptional< float > +UHoudiniParameterFloat::GetValue(int32 Idx) const +{ + if (Values.IsValidIndex(Idx)) + return TOptional< float >(Values[Idx]); + else + return TOptional< float >(); +} + +bool +UHoudiniParameterFloat::SetValueAt(const float& InValue, const int32& AtIndex) +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + if (InValue == Values[AtIndex]) + return false; + + Values[AtIndex] = FMath::Clamp< float >(InValue, Min, Max); + + return true; +} + +void +UHoudiniParameterFloat::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterFloat::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterFloat::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; +} + +void +UHoudiniParameterFloat::SetValue(float InValue, int32 Idx) +{ + if (!Values.IsValidIndex(Idx)) + return; + + if (InValue == Values[Idx]) + return; + + Values[Idx] = FMath::Clamp< float >(InValue, Min, Max); +} + +void +UHoudiniParameterFloat::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } +} + +void +UHoudiniParameterFloat::RevertToDefault(const int32& TupleIndex) +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.AddUnique(TupleIndex); + + MarkChanged(true); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h index 38291bd04..a13ac87d7 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h @@ -1,165 +1,165 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFloat.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFloat : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFloat * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetUnit() const { return Unit; }; - bool GetNoSwap() const { return bNoSwap; }; - - bool HasMin() const { return bHasMin; }; - bool HasMax() const { return bHasMax; }; - bool HasUIMin() const { return bHasUIMin; }; - bool HasUIMax() const { return bHasUIMax; }; - bool IsLogarithmic() const { return bIsLogarithmic; }; - - float GetMin() const { return Min; }; - float GetMax() const { return Max; }; - float GetUIMin() const { return UIMin; }; - float GetUIMax() const { return UIMax; }; - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Check if current value at Index is the default - bool IsDefaultValueAtIndex(const int32& Idx) const; - - bool IsDefault() const override; - - // Get value of this property - TOptional< float > GetValue(int32 Idx) const; - - // Write access to the value array - float* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - // Mutators - void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; - void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; - void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; - void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; - void SetIsLogarithmic(const bool& bInLog) { bIsLogarithmic = bInLog; }; - - void SetMin(const float& InMin) { Min = InMin; }; - void SetMax(const float& InMax) { Max = InMax; }; - void SetUIMin(const float& InUIMin) { UIMin = InUIMin; }; - void SetUIMax(const float& InUIMax) { UIMax = InUIMax; }; - - void SetUnit(const FString& InUnit) { Unit = InUnit; }; - void SetNoSwap(const bool& InNoSwap) { bNoSwap = InNoSwap; }; - - void SetDefaultValues(); - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - bool SetValueAt(const float& InValue, const int32& AtIndex); - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - /** Set value of this property, used by Slate. **/ - void SetValue(float InValue, int32 Idx); - - void RevertToDefault() override; - void RevertToDefault(const int32& TupleIndex) override; - -#if WITH_EDITOR - void SwitchUniformLock() { bUniformLocked = !bUniformLocked; }; - bool IsUniformLocked() const { return bUniformLocked; }; -#endif - -protected: - - // Float Values - UPROPERTY() - TArray Values; - - // Default float values, assigned at creating the parameter - UPROPERTY() - TArray DefaultValues; - - // Unit for this property - UPROPERTY() - FString Unit; - - // Do we have the noswap tag? - UPROPERTY() - bool bNoSwap; - - // Indicates we have a min/max value - UPROPERTY() - bool bHasMin; - // - UPROPERTY() - bool bHasMax; - - // Indicates we have a UI min/max - UPROPERTY() - bool bHasUIMin; - // - UPROPERTY() - bool bHasUIMax; - - UPROPERTY() - bool bIsLogarithmic; - - // Min and Max values for this property. - UPROPERTY() - float Min; - // - UPROPERTY() - float Max; - - // Min and Max values of this property for slider UI - UPROPERTY() - float UIMin; - // - UPROPERTY() - float UIMax; - - UPROPERTY() - bool bIsChildOfRamp; - -#if WITH_EDITORONLY_DATA - // Indicates whether the float VEC change value uniformly - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - bool bUniformLocked; -#endif +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFloat.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFloat : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFloat * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetUnit() const { return Unit; }; + bool GetNoSwap() const { return bNoSwap; }; + + bool HasMin() const { return bHasMin; }; + bool HasMax() const { return bHasMax; }; + bool HasUIMin() const { return bHasUIMin; }; + bool HasUIMax() const { return bHasUIMax; }; + bool IsLogarithmic() const { return bIsLogarithmic; }; + + float GetMin() const { return Min; }; + float GetMax() const { return Max; }; + float GetUIMin() const { return UIMin; }; + float GetUIMax() const { return UIMax; }; + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Check if current value at Index is the default + bool IsDefaultValueAtIndex(const int32& Idx) const; + + bool IsDefault() const override; + + // Get value of this property + TOptional< float > GetValue(int32 Idx) const; + + // Write access to the value array + float* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + // Mutators + void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; + void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; + void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; + void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; + void SetIsLogarithmic(const bool& bInLog) { bIsLogarithmic = bInLog; }; + + void SetMin(const float& InMin) { Min = InMin; }; + void SetMax(const float& InMax) { Max = InMax; }; + void SetUIMin(const float& InUIMin) { UIMin = InUIMin; }; + void SetUIMax(const float& InUIMax) { UIMax = InUIMax; }; + + void SetUnit(const FString& InUnit) { Unit = InUnit; }; + void SetNoSwap(const bool& InNoSwap) { bNoSwap = InNoSwap; }; + + void SetDefaultValues(); + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + bool SetValueAt(const float& InValue, const int32& AtIndex); + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + /** Set value of this property, used by Slate. **/ + void SetValue(float InValue, int32 Idx); + + void RevertToDefault() override; + void RevertToDefault(const int32& TupleIndex) override; + +#if WITH_EDITOR + void SwitchUniformLock() { bUniformLocked = !bUniformLocked; }; + bool IsUniformLocked() const { return bUniformLocked; }; +#endif + +protected: + + // Float Values + UPROPERTY() + TArray Values; + + // Default float values, assigned at creating the parameter + UPROPERTY() + TArray DefaultValues; + + // Unit for this property + UPROPERTY() + FString Unit; + + // Do we have the noswap tag? + UPROPERTY() + bool bNoSwap; + + // Indicates we have a min/max value + UPROPERTY() + bool bHasMin; + // + UPROPERTY() + bool bHasMax; + + // Indicates we have a UI min/max + UPROPERTY() + bool bHasUIMin; + // + UPROPERTY() + bool bHasUIMax; + + UPROPERTY() + bool bIsLogarithmic; + + // Min and Max values for this property. + UPROPERTY() + float Min; + // + UPROPERTY() + float Max; + + // Min and Max values of this property for slider UI + UPROPERTY() + float UIMin; + // + UPROPERTY() + float UIMax; + + UPROPERTY() + bool bIsChildOfRamp; + +#if WITH_EDITORONLY_DATA + // Indicates whether the float VEC change value uniformly + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + bool bUniformLocked; +#endif }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp index e2b9d79b4..4bb4968bc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp @@ -1,52 +1,52 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFolder.h" - -UHoudiniParameterFolder::UHoudiniParameterFolder(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - ,bExpanded(true) - ,bChosen(false) -{ - ParmType = EHoudiniParameterType::Folder; -} - -UHoudiniParameterFolder * -UHoudiniParameterFolder::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFolder_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolder::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFolder * HoudiniAssetParameter = NewObject< UHoudiniParameterFolder >( - InOuter, UHoudiniParameterFolder::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Folder); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFolder.h" + +UHoudiniParameterFolder::UHoudiniParameterFolder(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + ,bExpanded(true) + ,bChosen(false) +{ + ParmType = EHoudiniParameterType::Folder; +} + +UHoudiniParameterFolder * +UHoudiniParameterFolder::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFolder_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolder::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFolder * HoudiniAssetParameter = NewObject< UHoudiniParameterFolder >( + InOuter, UHoudiniParameterFolder::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Folder); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h index a75f3c87a..206ca2ac6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h @@ -1,110 +1,110 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFolder.generated.h" - -UENUM() -enum class EHoudiniFolderParameterType : uint8 -{ - Invalid, - - Collapsible, - Simple, - Tabs, - Radio, - Other, -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFolder : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFolder * Create( - UObject* Outer, - const FString& ParamName); - - FORCEINLINE - EHoudiniFolderParameterType GetFolderType() const { return FolderType; }; - - FORCEINLINE - void SetFolderType(EHoudiniFolderParameterType Type) { FolderType = Type; }; - - FORCEINLINE - void SetExpanded(const bool InExpanded) { bExpanded = InExpanded; }; - FORCEINLINE - bool IsExpanded() const { return bExpanded; }; - FORCEINLINE - void ExpandButtonClicked() { bExpanded = !bExpanded; }; - - FORCEINLINE - void SetChosen(const bool InChosen) { bChosen = InChosen; }; - FORCEINLINE - bool IsChosen() const { return bChosen; }; - - FORCEINLINE - bool IsTab() const { return FolderType == EHoudiniFolderParameterType::Tabs || FolderType == EHoudiniFolderParameterType::Radio; }; - - - FORCEINLINE - void ResetChildCounter() { ChildCounter = TupleSize; } - - FORCEINLINE - int32& GetChildCounter() { return ChildCounter; }; - - FORCEINLINE - bool IsContentShown() const { return bIsContentShown; }; - - FORCEINLINE - void SetIsContentShown(const bool& bInShown) { bIsContentShown = bInShown; }; - - - -private: - UPROPERTY() - EHoudiniFolderParameterType FolderType; - - UPROPERTY() - bool bExpanded; - - UPROPERTY() - bool bChosen; - - - UPROPERTY() - int32 ChildCounter; - - UPROPERTY() - bool bIsContentShown; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFolder.generated.h" + +UENUM() +enum class EHoudiniFolderParameterType : uint8 +{ + Invalid, + + Collapsible, + Simple, + Tabs, + Radio, + Other, +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFolder : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFolder * Create( + UObject* Outer, + const FString& ParamName); + + FORCEINLINE + EHoudiniFolderParameterType GetFolderType() const { return FolderType; }; + + FORCEINLINE + void SetFolderType(EHoudiniFolderParameterType Type) { FolderType = Type; }; + + FORCEINLINE + void SetExpanded(const bool InExpanded) { bExpanded = InExpanded; }; + FORCEINLINE + bool IsExpanded() const { return bExpanded; }; + FORCEINLINE + void ExpandButtonClicked() { bExpanded = !bExpanded; }; + + FORCEINLINE + void SetChosen(const bool InChosen) { bChosen = InChosen; }; + FORCEINLINE + bool IsChosen() const { return bChosen; }; + + FORCEINLINE + bool IsTab() const { return FolderType == EHoudiniFolderParameterType::Tabs || FolderType == EHoudiniFolderParameterType::Radio; }; + + + FORCEINLINE + void ResetChildCounter() { ChildCounter = TupleSize; } + + FORCEINLINE + int32& GetChildCounter() { return ChildCounter; }; + + FORCEINLINE + bool IsContentShown() const { return bIsContentShown; }; + + FORCEINLINE + void SetIsContentShown(const bool& bInShown) { bIsContentShown = bInShown; }; + + + +private: + UPROPERTY() + EHoudiniFolderParameterType FolderType; + + UPROPERTY() + bool bExpanded; + + UPROPERTY() + bool bChosen; + + + UPROPERTY() + int32 ChildCounter; + + UPROPERTY() + bool bIsContentShown; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp index 84d383c0f..31ea2c061 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp @@ -1,108 +1,108 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterFolder.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -UHoudiniParameterFolderList::UHoudiniParameterFolderList(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), bIsTabMenu(false), bIsTabsShown(false) -{ - ParmType = EHoudiniParameterType::FolderList; -} - -UHoudiniParameterFolderList * -UHoudiniParameterFolderList::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFolderList_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolderList::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFolderList * HoudiniAssetParameter = NewObject< UHoudiniParameterFolderList >( - InOuter, UHoudiniParameterFolderList::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::FolderList); - - HoudiniAssetParameter->bIsTabMenu = false; - return HoudiniAssetParameter; -} - -void -UHoudiniParameterFolderList::AddTabFolder(UHoudiniParameterFolder* InFolderParm) -{ - TabFolders.Add(InFolderParm); -} - -bool -UHoudiniParameterFolderList::IsTabParseFinished() const -{ - for (auto & CurTab : TabFolders) - { - if (!CurTab || CurTab->IsPendingKill()) - continue; - - if (!CurTab->IsTab()) - continue; - - // Go through visible tab only - if (!CurTab->IsChosen()) - continue; - - if (CurTab->GetChildCounter() > 0) - return false; - } - - return true; -} - -void -UHoudiniParameterFolderList::RemapParameters(const TMap& InputMapping) -{ - const int32 NumFolders = TabFolders.Num(); - for (int i = 0; i < NumFolders; i++) - { - UHoudiniParameter* FromParameter = TabFolders[i]; - - if (!FromParameter) - continue; - - UHoudiniParameterFolder* ToParameter = nullptr; - if (InputMapping.Contains(FromParameter)) - { - ToParameter = Cast(InputMapping.FindRef(FromParameter)); - } - - if (!ToParameter) - { - HOUDINI_LOG_WARNING(TEXT("[UHoudiniParameterFolderList::RemapParameters] Could not find mapping for existing parameter %s (%s)."), *(FromParameter->GetParameterName()), *(FromParameter->GetPathName()) ); - } - - TabFolders[i] = ToParameter; - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterFolder.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +UHoudiniParameterFolderList::UHoudiniParameterFolderList(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), bIsTabMenu(false), bIsTabsShown(false) +{ + ParmType = EHoudiniParameterType::FolderList; +} + +UHoudiniParameterFolderList * +UHoudiniParameterFolderList::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFolderList_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolderList::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFolderList * HoudiniAssetParameter = NewObject< UHoudiniParameterFolderList >( + InOuter, UHoudiniParameterFolderList::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::FolderList); + + HoudiniAssetParameter->bIsTabMenu = false; + return HoudiniAssetParameter; +} + +void +UHoudiniParameterFolderList::AddTabFolder(UHoudiniParameterFolder* InFolderParm) +{ + TabFolders.Add(InFolderParm); +} + +bool +UHoudiniParameterFolderList::IsTabParseFinished() const +{ + for (auto & CurTab : TabFolders) + { + if (!CurTab || CurTab->IsPendingKill()) + continue; + + if (!CurTab->IsTab()) + continue; + + // Go through visible tab only + if (!CurTab->IsChosen()) + continue; + + if (CurTab->GetChildCounter() > 0) + return false; + } + + return true; +} + +void +UHoudiniParameterFolderList::RemapParameters(const TMap& InputMapping) +{ + const int32 NumFolders = TabFolders.Num(); + for (int i = 0; i < NumFolders; i++) + { + UHoudiniParameter* FromParameter = TabFolders[i]; + + if (!FromParameter) + continue; + + UHoudiniParameterFolder* ToParameter = nullptr; + if (InputMapping.Contains(FromParameter)) + { + ToParameter = Cast(InputMapping.FindRef(FromParameter)); + } + + if (!ToParameter) + { + HOUDINI_LOG_WARNING(TEXT("[UHoudiniParameterFolderList::RemapParameters] Could not find mapping for existing parameter %s (%s)."), *(FromParameter->GetParameterName()), *(FromParameter->GetPathName()) ); + } + + TabFolders[i] = ToParameter; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h index 61358252d..011118cf6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h @@ -1,81 +1,81 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFolderList.generated.h" - -class UHoudiniParameterFolder; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFolderList : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFolderList * Create( - UObject* Outer, - const FString& ParamName); - - void AddTabFolder(UHoudiniParameterFolder* InFolderParm); - - FORCEINLINE - TArray& GetTabs() { return TabFolders; }; - - FORCEINLINE - bool IsTabMenu() const { return bIsTabMenu; }; - - FORCEINLINE - void SetIsTabMenu(const bool InIsTabMenu) { bIsTabMenu = InIsTabMenu; }; - - FORCEINLINE - bool IsTabsShown() const { return bIsTabsShown; }; - - FORCEINLINE - void SetTabsShown(const bool& bInTabsShown) { bIsTabsShown = bInTabsShown; }; - - bool IsTabParseFinished() const; - - UPROPERTY() - bool bIsTabMenu; - - UPROPERTY() - bool bIsTabsShown; - - UPROPERTY() - TArray TabFolders; - - - //------------------------------------------------------------------------------------------------ - // UHoudiniParameter overrides - //------------------------------------------------------------------------------------------------ - virtual void RemapParameters(const TMap& InputMapping) override; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFolderList.generated.h" + +class UHoudiniParameterFolder; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFolderList : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFolderList * Create( + UObject* Outer, + const FString& ParamName); + + void AddTabFolder(UHoudiniParameterFolder* InFolderParm); + + FORCEINLINE + TArray& GetTabs() { return TabFolders; }; + + FORCEINLINE + bool IsTabMenu() const { return bIsTabMenu; }; + + FORCEINLINE + void SetIsTabMenu(const bool InIsTabMenu) { bIsTabMenu = InIsTabMenu; }; + + FORCEINLINE + bool IsTabsShown() const { return bIsTabsShown; }; + + FORCEINLINE + void SetTabsShown(const bool& bInTabsShown) { bIsTabsShown = bInTabsShown; }; + + bool IsTabParseFinished() const; + + UPROPERTY() + bool bIsTabMenu; + + UPROPERTY() + bool bIsTabsShown; + + UPROPERTY() + TArray TabFolders; + + + //------------------------------------------------------------------------------------------------ + // UHoudiniParameter overrides + //------------------------------------------------------------------------------------------------ + virtual void RemapParameters(const TMap& InputMapping) override; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp index 81a99fdd5..69e8ce113 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp @@ -1,119 +1,119 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterInt.h" - -UHoudiniParameterInt::UHoudiniParameterInt(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Unit() - , bHasMin(false) - , bHasMax(false) - , bHasUIMin(false) - , bHasUIMax(false) - , bIsLogarithmic(false) - , Min(0) - , Max(0) - , UIMin(0) - , UIMax(0) -{ - ParmType = EHoudiniParameterType::Int; -} - -UHoudiniParameterInt * -UHoudiniParameterInt::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterInt_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterInt::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterInt * HoudiniAssetParameter = NewObject< UHoudiniParameterInt >( - InOuter, UHoudiniParameterInt::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Int); - HoudiniAssetParameter->bIsLogarithmic = false; - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -TOptional -UHoudiniParameterInt::GetValue(int32 Idx) const -{ - if (Values.IsValidIndex(Idx)) - return TOptional(Values[Idx]); - else - return TOptional(); -} - -bool -UHoudiniParameterInt::SetValueAt(const int32& InValue, const int32& AtIndex) -{ - if (!Values.IsValidIndex(AtIndex)) - return false; - - if (InValue == Values[AtIndex]) - return false; - - Values[AtIndex] = FMath::Clamp< int32 >(InValue, Min, Max); - - return true; -} - -void -UHoudiniParameterInt::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterInt::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterInt::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterInt.h" + +UHoudiniParameterInt::UHoudiniParameterInt(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Unit() + , bHasMin(false) + , bHasMax(false) + , bHasUIMin(false) + , bHasUIMax(false) + , bIsLogarithmic(false) + , Min(0) + , Max(0) + , UIMin(0) + , UIMax(0) +{ + ParmType = EHoudiniParameterType::Int; +} + +UHoudiniParameterInt * +UHoudiniParameterInt::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterInt_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterInt::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterInt * HoudiniAssetParameter = NewObject< UHoudiniParameterInt >( + InOuter, UHoudiniParameterInt::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Int); + HoudiniAssetParameter->bIsLogarithmic = false; + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +TOptional +UHoudiniParameterInt::GetValue(int32 Idx) const +{ + if (Values.IsValidIndex(Idx)) + return TOptional(Values[Idx]); + else + return TOptional(); +} + +bool +UHoudiniParameterInt::SetValueAt(const int32& InValue, const int32& AtIndex) +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + if (InValue == Values[AtIndex]) + return false; + + Values[AtIndex] = FMath::Clamp< int32 >(InValue, Min, Max); + + return true; +} + +void +UHoudiniParameterInt::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterInt::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterInt::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h index a90b09e80..1c21464d6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h @@ -1,131 +1,131 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterInt.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterInt : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterInt * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetUnit() const { return Unit; }; - - bool HasMin() const { return bHasMin; }; - bool HasMax() const { return bHasMax; }; - bool HasUIMin() const { return bHasUIMin; }; - bool HasUIMax() const { return bHasUIMax; }; - bool IsLogarithmic() const { return bIsLogarithmic; }; - - int32 GetMin() const { return Min; }; - int32 GetMax() const { return Max; }; - int32 GetUIMin() const { return UIMin; }; - int32 GetUIMax() const { return UIMax; }; - - // Get value of this property - TOptional GetValue(int32 Idx) const; - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - bool IsDefaultValueAtIndex(const int32& Idx) const; - bool IsDefault() const override; - - // Mutators - void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; - void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; - void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; - void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; - - void SetMin(const int32& InMin) { Min = InMin; }; - void SetMax(const int32& InMax) { Max = InMax; }; - void SetUIMin(const int32& InUIMin) { UIMin = InUIMin; }; - void SetUIMax(const int32& InUIMax) { UIMax = InUIMax; }; - void SetIsLogarithmic(const bool& bInIsLog) { bIsLogarithmic = bInIsLog; }; - - void SetUnit(const FString& InUnit) { Unit = InUnit; }; - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - bool SetValueAt(const int32& InValue, const int32& AtIndex); - - void SetDefaultValues(); - -protected: - - // Int Values - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; - - // Unit for this property - UPROPERTY() - FString Unit; - - // Indicates we have a min/max value - UPROPERTY() - bool bHasMin; - // - UPROPERTY() - bool bHasMax; - - // Indicates we have a UI min/max - UPROPERTY() - bool bHasUIMin; - // - UPROPERTY() - bool bHasUIMax; - - UPROPERTY() - bool bIsLogarithmic; - - // Min and Max values for this property. - UPROPERTY() - int32 Min; - // - UPROPERTY() - int32 Max; - - // Min and Max values of this property for slider UI - UPROPERTY() - int32 UIMin; - // - UPROPERTY() - int32 UIMax; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterInt.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterInt : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterInt * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetUnit() const { return Unit; }; + + bool HasMin() const { return bHasMin; }; + bool HasMax() const { return bHasMax; }; + bool HasUIMin() const { return bHasUIMin; }; + bool HasUIMax() const { return bHasUIMax; }; + bool IsLogarithmic() const { return bIsLogarithmic; }; + + int32 GetMin() const { return Min; }; + int32 GetMax() const { return Max; }; + int32 GetUIMin() const { return UIMin; }; + int32 GetUIMax() const { return UIMax; }; + + // Get value of this property + TOptional GetValue(int32 Idx) const; + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + bool IsDefaultValueAtIndex(const int32& Idx) const; + bool IsDefault() const override; + + // Mutators + void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; + void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; + void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; + void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; + + void SetMin(const int32& InMin) { Min = InMin; }; + void SetMax(const int32& InMax) { Max = InMax; }; + void SetUIMin(const int32& InUIMin) { UIMin = InUIMin; }; + void SetUIMax(const int32& InUIMax) { UIMax = InUIMax; }; + void SetIsLogarithmic(const bool& bInIsLog) { bIsLogarithmic = bInIsLog; }; + + void SetUnit(const FString& InUnit) { Unit = InUnit; }; + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + bool SetValueAt(const int32& InValue, const int32& AtIndex); + + void SetDefaultValues(); + +protected: + + // Int Values + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; + + // Unit for this property + UPROPERTY() + FString Unit; + + // Indicates we have a min/max value + UPROPERTY() + bool bHasMin; + // + UPROPERTY() + bool bHasMax; + + // Indicates we have a UI min/max + UPROPERTY() + bool bHasUIMin; + // + UPROPERTY() + bool bHasUIMax; + + UPROPERTY() + bool bIsLogarithmic; + + // Min and Max values for this property. + UPROPERTY() + int32 Min; + // + UPROPERTY() + int32 Max; + + // Min and Max values of this property for slider UI + UPROPERTY() + int32 UIMin; + // + UPROPERTY() + int32 UIMax; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp index 5cf1efc72..a1939516d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp @@ -1,62 +1,62 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterLabel.h" - -UHoudiniParameterLabel::UHoudiniParameterLabel(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Label; -} - -UHoudiniParameterLabel * -UHoudiniParameterLabel::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterLabel_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterLabel::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterLabel * HoudiniAssetParameter = NewObject< UHoudiniParameterLabel >( - InOuter, UHoudiniParameterLabel::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Label); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -FString -UHoudiniParameterLabel::GetStringAtIndex(int32 Index) -{ - if (LabelStrings.IsValidIndex(Index)) - { - return LabelStrings[Index]; - } - - return FString(""); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterLabel.h" + +UHoudiniParameterLabel::UHoudiniParameterLabel(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Label; +} + +UHoudiniParameterLabel * +UHoudiniParameterLabel::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterLabel_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterLabel::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterLabel * HoudiniAssetParameter = NewObject< UHoudiniParameterLabel >( + InOuter, UHoudiniParameterLabel::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Label); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +FString +UHoudiniParameterLabel::GetStringAtIndex(int32 Index) +{ + if (LabelStrings.IsValidIndex(Index)) + { + return LabelStrings[Index]; + } + + return FString(""); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h index 99e469a5b..780dda43c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h @@ -1,56 +1,56 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterLabel.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterLabel : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterLabel * Create( - UObject* Outer, - const FString& ParamName); - - UPROPERTY() - TArray LabelStrings; - - FORCEINLINE - void AddLabelString(FString &NewString) { LabelStrings.Add(NewString); }; - - FString GetStringAtIndex(int32 Index); - - FORCEINLINE - void EmptyLabelString() { LabelStrings.Empty(); }; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterLabel.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterLabel : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterLabel * Create( + UObject* Outer, + const FString& ParamName); + + UPROPERTY() + TArray LabelStrings; + + FORCEINLINE + void AddLabelString(FString &NewString) { LabelStrings.Add(NewString); }; + + FString GetStringAtIndex(int32 Index); + + FORCEINLINE + void EmptyLabelString() { LabelStrings.Empty(); }; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp index 92eb805a0..15aa3cf27 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp @@ -1,144 +1,144 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterMultiParm.h" - -UHoudiniParameterMultiParm::UHoudiniParameterMultiParm(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), bIsShown(false), InstanceStartOffset(0) -{ - // TODO Proper Init - ParmType = EHoudiniParameterType::MultiParm; -} - -UHoudiniParameterMultiParm * -UHoudiniParameterMultiParm::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterMultiParm::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterMultiParm * HoudiniAssetParameter = NewObject< UHoudiniParameterMultiParm >( - InOuter, UHoudiniParameterMultiParm::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::MultiParm); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->DefaultInstanceCount = -1; - - return HoudiniAssetParameter; -} - - -void -UHoudiniParameterMultiParm::InsertElement() -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); -} - -void -UHoudiniParameterMultiParm::InsertElementAt(int32 Index) -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - if (Index >= MultiParmInstanceLastModifyArray.Num()) - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); - else - MultiParmInstanceLastModifyArray.Insert(EHoudiniMultiParmModificationType::Inserted, Index); -} - -/** Decrement value, used by Slate. **/ -void -UHoudiniParameterMultiParm::RemoveElement(int32 Index) -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - // Remove the last element - if (Index == -1) - { - Index = MultiParmInstanceLastModifyArray.Num() - 1; - while (MultiParmInstanceLastModifyArray.IsValidIndex(Index) && MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Removed) - Index -= 1; - } - - if (MultiParmInstanceLastModifyArray.IsValidIndex(Index)) - { - // If the removed is a to be inserted instance, simply remove it. - if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) - MultiParmInstanceLastModifyArray.RemoveAt(Index); - // Otherwise mark it as to be removed. - else - MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; - } -} - -void -UHoudiniParameterMultiParm::EmptyElements() -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - for (int32 Index = MultiParmInstanceLastModifyArray.Num() - 1; Index >= 0; --Index) - { - // If the removed is a to be inserted instance, simply remove it. - // Interation starts from the tail, so that the indices won't be changed by element removal. - if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) - MultiParmInstanceLastModifyArray.RemoveAt(Index); - else // Otherwise mark it as to be removed. - MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; - } -} - -void -UHoudiniParameterMultiParm::InitializeModifyArray() -{ - for (uint32 Index = 0; Index < MultiParmInstanceCount; ++Index) - { - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::None); - } -} - -bool -UHoudiniParameterMultiParm::IsDefault() const -{ - //UE_LOG(LogTemp, Warning, TEXT("%d, %d"), MultiParmInstanceNum, MultiParmInstanceCount); - return DefaultInstanceCount == MultiParmInstanceCount; -} - -void -UHoudiniParameterMultiParm::SetDefaultInstanceCount(int32 InCount) -{ - if (DefaultInstanceCount >= 0) - return; - - DefaultInstanceCount = InCount; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterMultiParm.h" + +UHoudiniParameterMultiParm::UHoudiniParameterMultiParm(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), bIsShown(false), InstanceStartOffset(0) +{ + // TODO Proper Init + ParmType = EHoudiniParameterType::MultiParm; +} + +UHoudiniParameterMultiParm * +UHoudiniParameterMultiParm::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterMultiParm::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterMultiParm * HoudiniAssetParameter = NewObject< UHoudiniParameterMultiParm >( + InOuter, UHoudiniParameterMultiParm::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::MultiParm); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->DefaultInstanceCount = -1; + + return HoudiniAssetParameter; +} + + +void +UHoudiniParameterMultiParm::InsertElement() +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); +} + +void +UHoudiniParameterMultiParm::InsertElementAt(int32 Index) +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + if (Index >= MultiParmInstanceLastModifyArray.Num()) + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); + else + MultiParmInstanceLastModifyArray.Insert(EHoudiniMultiParmModificationType::Inserted, Index); +} + +/** Decrement value, used by Slate. **/ +void +UHoudiniParameterMultiParm::RemoveElement(int32 Index) +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + // Remove the last element + if (Index == -1) + { + Index = MultiParmInstanceLastModifyArray.Num() - 1; + while (MultiParmInstanceLastModifyArray.IsValidIndex(Index) && MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Removed) + Index -= 1; + } + + if (MultiParmInstanceLastModifyArray.IsValidIndex(Index)) + { + // If the removed is a to be inserted instance, simply remove it. + if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) + MultiParmInstanceLastModifyArray.RemoveAt(Index); + // Otherwise mark it as to be removed. + else + MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; + } +} + +void +UHoudiniParameterMultiParm::EmptyElements() +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + for (int32 Index = MultiParmInstanceLastModifyArray.Num() - 1; Index >= 0; --Index) + { + // If the removed is a to be inserted instance, simply remove it. + // Interation starts from the tail, so that the indices won't be changed by element removal. + if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) + MultiParmInstanceLastModifyArray.RemoveAt(Index); + else // Otherwise mark it as to be removed. + MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; + } +} + +void +UHoudiniParameterMultiParm::InitializeModifyArray() +{ + for (uint32 Index = 0; Index < MultiParmInstanceCount; ++Index) + { + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::None); + } +} + +bool +UHoudiniParameterMultiParm::IsDefault() const +{ + //UE_LOG(LogTemp, Warning, TEXT("%d, %d"), MultiParmInstanceNum, MultiParmInstanceCount); + return DefaultInstanceCount == MultiParmInstanceCount; +} + +void +UHoudiniParameterMultiParm::SetDefaultInstanceCount(int32 InCount) +{ + if (DefaultInstanceCount >= 0) + return; + + DefaultInstanceCount = InCount; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h index 226cdcdef..9536fd70d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h @@ -1,129 +1,129 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterMultiParm.generated.h" - -UENUM() -enum class EHoudiniMultiParmModificationType : uint8 -{ - None, - - Inserted, - Removed, - Modified -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterMultiParm * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FORCEINLINE - int32 GetValue() const { return Value; }; - FORCEINLINE - int32 GetInstanceCount() const { return MultiParmInstanceCount; }; - - // Mutators - FORCEINLINE - void SetValue(const int32& InValue) { Value = InValue; }; - FORCEINLINE - void SetInstanceCount(const int32 InCount) { MultiParmInstanceCount = InCount; }; - - FORCEINLINE - void SetIsShown(const bool InIsShown) { bIsShown = InIsShown; }; - - FORCEINLINE - bool IsShown() const { return bIsShown; }; - - - /** Increment value, used by Slate. **/ - void InsertElement(); - - void InsertElementAt(int32 Index); - - /** Decrement value, used by Slate. **/ - void RemoveElement(int32 Index); - - /** Empty the values, used by Slate. **/ - void EmptyElements(); - - UPROPERTY() - bool bIsShown; - - // Value of the multiparm - UPROPERTY() - int32 Value; - - // - UPROPERTY() - FString TemplateName; - - // Value of this property. - UPROPERTY() - int32 MultiparmValue; - - // - UPROPERTY() - uint32 MultiParmInstanceNum; - - // - UPROPERTY() - uint32 MultiParmInstanceLength; - - // - UPROPERTY() - uint32 MultiParmInstanceCount; - - UPROPERTY() - uint32 InstanceStartOffset; - - // This array records the last modified instance of the multiparm - UPROPERTY() - TArray MultiParmInstanceLastModifyArray; - - UPROPERTY() - int32 DefaultInstanceCount; - - bool IsDefault() const override; - - void SetDefaultInstanceCount(int32 InCount); - -private: - void InitializeModifyArray(); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterMultiParm.generated.h" + +UENUM() +enum class EHoudiniMultiParmModificationType : uint8 +{ + None, + + Inserted, + Removed, + Modified +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterMultiParm * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FORCEINLINE + int32 GetValue() const { return Value; }; + FORCEINLINE + int32 GetInstanceCount() const { return MultiParmInstanceCount; }; + + // Mutators + FORCEINLINE + void SetValue(const int32& InValue) { Value = InValue; }; + FORCEINLINE + void SetInstanceCount(const int32 InCount) { MultiParmInstanceCount = InCount; }; + + FORCEINLINE + void SetIsShown(const bool InIsShown) { bIsShown = InIsShown; }; + + FORCEINLINE + bool IsShown() const { return bIsShown; }; + + + /** Increment value, used by Slate. **/ + void InsertElement(); + + void InsertElementAt(int32 Index); + + /** Decrement value, used by Slate. **/ + void RemoveElement(int32 Index); + + /** Empty the values, used by Slate. **/ + void EmptyElements(); + + UPROPERTY() + bool bIsShown; + + // Value of the multiparm + UPROPERTY() + int32 Value; + + // + UPROPERTY() + FString TemplateName; + + // Value of this property. + UPROPERTY() + int32 MultiparmValue; + + // + UPROPERTY() + uint32 MultiParmInstanceNum; + + // + UPROPERTY() + uint32 MultiParmInstanceLength; + + // + UPROPERTY() + uint32 MultiParmInstanceCount; + + UPROPERTY() + uint32 InstanceStartOffset; + + // This array records the last modified instance of the multiparm + UPROPERTY() + TArray MultiParmInstanceLastModifyArray; + + UPROPERTY() + int32 DefaultInstanceCount; + + bool IsDefault() const override; + + void SetDefaultInstanceCount(int32 InCount); + +private: + void InitializeModifyArray(); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp index 75f1187cb..2bf983369 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp @@ -1,95 +1,95 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniInput.h" - - -UHoudiniParameterOperatorPath::UHoudiniParameterOperatorPath( - const FObjectInitializer &ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -UHoudiniParameterOperatorPath * -UHoudiniParameterOperatorPath::Create(UObject *InOuter, const FString &InParamName) -{ - FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; - FName ParamName = MakeUniqueObjectName( - InOuter, UHoudiniParameterOperatorPath::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterOperatorPath *HoudiniAssetParameter = - NewObject( - InOuter, UHoudiniParameterOperatorPath::StaticClass(), ParamName, - RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Input); - - - return HoudiniAssetParameter; -} - -bool UHoudiniParameterOperatorPath::HasChanged() const -{ - if (Super::HasChanged()) - return true; - if (HoudiniInput.IsValid() && HoudiniInput->HasChanged()) - return true; - return false; -} - -bool UHoudiniParameterOperatorPath::NeedsToTriggerUpdate() const -{ - if (Super::NeedsToTriggerUpdate()) - return true; - if (HoudiniInput.IsValid() && HoudiniInput->NeedsToTriggerUpdate()) - return true; - return false; -} - -void UHoudiniParameterOperatorPath::MarkChanged(const bool & bInChanged) -{ - Super::MarkChanged(bInChanged); -} - -void -UHoudiniParameterOperatorPath::RemapInputs(const TMap& InputMapping) -{ - if (!HoudiniInput.IsValid()) - return; - - if (InputMapping.Contains(HoudiniInput.Get())) - { - HoudiniInput = InputMapping.FindRef(HoudiniInput.Get()); - } - else - { - HoudiniInput = nullptr; - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniInput.h" + + +UHoudiniParameterOperatorPath::UHoudiniParameterOperatorPath( + const FObjectInitializer &ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniParameterOperatorPath * +UHoudiniParameterOperatorPath::Create(UObject *InOuter, const FString &InParamName) +{ + FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; + FName ParamName = MakeUniqueObjectName( + InOuter, UHoudiniParameterOperatorPath::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterOperatorPath *HoudiniAssetParameter = + NewObject( + InOuter, UHoudiniParameterOperatorPath::StaticClass(), ParamName, + RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Input); + + + return HoudiniAssetParameter; +} + +bool UHoudiniParameterOperatorPath::HasChanged() const +{ + if (Super::HasChanged()) + return true; + if (HoudiniInput.IsValid() && HoudiniInput->HasChanged()) + return true; + return false; +} + +bool UHoudiniParameterOperatorPath::NeedsToTriggerUpdate() const +{ + if (Super::NeedsToTriggerUpdate()) + return true; + if (HoudiniInput.IsValid() && HoudiniInput->NeedsToTriggerUpdate()) + return true; + return false; +} + +void UHoudiniParameterOperatorPath::MarkChanged(const bool & bInChanged) +{ + Super::MarkChanged(bInChanged); +} + +void +UHoudiniParameterOperatorPath::RemapInputs(const TMap& InputMapping) +{ + if (!HoudiniInput.IsValid()) + return; + + if (InputMapping.Contains(HoudiniInput.Get())) + { + HoudiniInput = InputMapping.FindRef(HoudiniInput.Get()); + } + else + { + HoudiniInput = nullptr; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h index a16ef2aee..c22b93e5e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h @@ -1,57 +1,58 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterOperatorPath.generated.h" - - -class UHoudiniInput; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterOperatorPath - : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - // Create instance of this class. - static UHoudiniParameterOperatorPath * - Create(UObject *Outer, const FString &ParamName); - - virtual bool HasChanged() const override; - virtual bool NeedsToTriggerUpdate() const override; - virtual void MarkChanged(const bool& bInChanged) override; - - //------------------------------------------------------------------------------------------------ - // UHoudiniParameter overrides - //------------------------------------------------------------------------------------------------ - virtual void RemapInputs(const TMap& InputMapping) override; - - UPROPERTY() - TWeakObjectPtr HoudiniInput; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterOperatorPath.generated.h" + + +class UHoudiniInput; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterOperatorPath + : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + // Create instance of this class. + static UHoudiniParameterOperatorPath * + Create(UObject *Outer, const FString &ParamName); + + virtual bool HasChanged() const override; + virtual bool NeedsToTriggerUpdate() const override; + virtual void MarkChanged(const bool& bInChanged) override; + + //------------------------------------------------------------------------------------------------ + // UHoudiniParameter overrides + //------------------------------------------------------------------------------------------------ + virtual void RemapInputs(const TMap& InputMapping) override; + + UPROPERTY() + TWeakObjectPtr HoudiniInput; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp index fc2898720..1fcd9bb92 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp @@ -1,683 +1,701 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterRamp.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterChoice.h" -#include "UObject/UnrealType.h" - - -void -UHoudiniParameterRampFloatPoint::SetPosition(const float InPosition) -{ - if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Position = InPosition; - PositionParentParm->SetValueAt(InPosition, 0); - PositionParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampFloatPoint::SetValue(const float InValue) -{ - if (ValueParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Value = InValue; - ValueParentParm->SetValueAt(InValue, 0); - ValueParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampFloatPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) -{ - if (InterpolationParentParm) - { - Interpolation = InInterpolation; - InterpolationParentParm->SetIntValue((int32)InInterpolation); - InterpolationParentParm->UpdateStringValueFromInt(); - InterpolationParentParm->SetIsChildOfRamp(); - } -} - -UHoudiniParameterRampFloatPoint* -UHoudiniParameterRampFloatPoint::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - UHoudiniParameterRampFloatPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); - - NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); - - return NewPoint; -} - -void -UHoudiniParameterRampFloatPoint::CopyStateFrom(UHoudiniParameterRampFloatPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - // Ensure this object's flags match the desired flags. - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -} - -void -UHoudiniParameterRampFloatPoint::RemapParameters( - const TMap& ParameterMapping) -{ - if (ParameterMapping.Contains(PositionParentParm)) - { - PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); - } - else - { - PositionParentParm = nullptr; - } - - if (ParameterMapping.Contains(ValueParentParm)) - { - ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); - } - else - { - ValueParentParm = nullptr; - } - - if (ParameterMapping.Contains(InterpolationParentParm)) - { - InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); - } - else - { - InterpolationParentParm = nullptr; - } -}; - - -void -UHoudiniParameterRampColorPoint::SetPosition(const float InPosition) -{ - if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Position = InPosition; - PositionParentParm->SetValueAt(InPosition, 0); - PositionParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampColorPoint::SetValue(const FLinearColor InValue) -{ - if (!ValueParentParm) - return; - - Value = InValue; - ValueParentParm->SetColorValue(InValue); - ValueParentParm->SetIsChildOfRamp(); -}; - -void -UHoudiniParameterRampColorPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) -{ - if (!InterpolationParentParm) - return; - - Interpolation = InInterpolation; - InterpolationParentParm->SetIntValue((int32)InInterpolation); - InterpolationParentParm->UpdateStringValueFromInt(); - InterpolationParentParm->SetIsChildOfRamp(); -} -UHoudiniParameterRampColorPoint * UHoudiniParameterRampColorPoint::DuplicateAndCopyState(UObject * DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - - UHoudiniParameterRampColorPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); - - NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); - - return NewPoint; - -} -void UHoudiniParameterRampColorPoint::CopyStateFrom(UHoudiniParameterRampColorPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ -#if WITH_EDITOR - PreEditChange(nullptr); -#endif - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - // Ensure this object's flags match the desired flags. - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -#if WITH_EDITOR - PostEditChange(); -#endif -} - -void UHoudiniParameterRampColorPoint::RemapParameters(const TMap& ParameterMapping) -{ - if (ParameterMapping.Contains(PositionParentParm)) - { - PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); - } - else - { - PositionParentParm = nullptr; - } - - if (ParameterMapping.Contains(ValueParentParm)) - { - ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); - } - else - { - ValueParentParm = nullptr; - } - - if (ParameterMapping.Contains(InterpolationParentParm)) - { - InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); - } - else - { - InterpolationParentParm = nullptr; - } -} - - -void -UHoudiniParameterRampFloat::OnPreCook() -{ - if (bCaching) - { - SyncCachedPoints(); - bCaching = false; - } -} - -UHoudiniParameterRampFloat * -UHoudiniParameterRampFloat::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampFloat::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterRampFloat * HoudiniParameter = NewObject< UHoudiniParameterRampFloat >( - InOuter, UHoudiniParameterRampFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniParameter->SetParameterType(EHoudiniParameterType::FloatRamp); - - HoudiniParameter->NumDefaultPoints = -1; - - HoudiniParameter->bCaching = false; - - return HoudiniParameter; -} - -void UHoudiniParameterRampFloat::CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ -#if WITH_EDITOR - PreEditChange(nullptr); -#endif - - UHoudiniParameterRampFloat* FromParameter = Cast(InParameter); - check(FromParameter); - - TArray PrevCachedPoints = CachedPoints; - TArray PrevPoints = Points; - - Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); - - CachedPoints = PrevCachedPoints; - Points = PrevPoints; - - - auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) - { - const int32 NumPts = FromArray.Num(); - - ToArray.SetNum(NumPts); - - for(int32 i = 0; i < NumPts; ++i) - { - UHoudiniParameterRampFloatPoint* FromPoint = FromArray[i]; - UHoudiniParameterRampFloatPoint* ToPoint = ToArray[i]; - - check(FromPoint); - - if (ToPoint) - { - // Ensure the destination point is outered to this parameter - bool bIsValid = ToPoint->GetOuter() == NewOuter; - if (!bIsValid) - ToPoint = nullptr; - } - - if (!ToPoint) - { - // Duplicate a new copy using FromPoint - ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); - } - else - { - // We have a valid point that we can reuse. Simply copy state. - ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); - } - - ToArray[i] = ToPoint; - } - }; - - CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); - CopyPointsStateFn(FromParameter->Points, Points, this); - -#if WITH_EDITOR - PostEditChange(); -#endif -} - -void -UHoudiniParameterRampFloat::RemapParameters(const TMap& ParameterMapping) -{ - Super::RemapParameters(ParameterMapping); - - AActor* OuterActor = GetTypedOuter(); - - - for(UHoudiniParameterRampFloatPoint* CurrentPoint : Points) - { - CurrentPoint->RemapParameters(ParameterMapping); - } - - for(UHoudiniParameterRampFloatPoint* CurrentPoint : CachedPoints) - { - CurrentPoint->RemapParameters(ParameterMapping); - } -} - -void -UHoudiniParameterRampFloat::SyncCachedPoints() -{ - int32 Idx = 0; - - while (Idx < CachedPoints.Num() && Idx < Points.Num()) - { - UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; - UHoudiniParameterRampFloatPoint* &CurrentPoint = Points[Idx]; - - if (!CachedPoint || !CurrentPoint) - continue; - - if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) - { - if (CurrentPoint->PositionParentParm) - { - CurrentPoint->SetPosition(CachedPoint->GetPosition()); - CurrentPoint->PositionParentParm->MarkChanged(true); - } - } - - if (CachedPoint->GetValue() != CurrentPoint->GetValue()) - { - if (CurrentPoint->ValueParentParm) - { - CurrentPoint->SetValue(CachedPoint->GetValue()); - CurrentPoint->ValueParentParm->MarkChanged(true); - } - } - - if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) - { - if (CurrentPoint->InterpolationParentParm) - { - CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); - CurrentPoint->InterpolationParentParm->MarkChanged(true); - } - } - - Idx += 1; - } - - // Insert points - for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) - { - UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; - if (!CachedPoint) - continue; - - CreateInsertEvent(CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); - - MarkChanged(true); - } - - // Remove points - for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < Points.Num(); ++IdxCurrentPointLeft) - { - RemoveElement(IdxCurrentPointLeft); - - UHoudiniParameterRampFloatPoint* Point = Points[IdxCurrentPointLeft]; - - if (!Point) - continue; - - CreateDeleteEvent(Point->InstanceIndex); - - MarkChanged(true); - } -} - - -void -UHoudiniParameterRampFloat::CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) -{ - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - this, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetFloatRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertFloat = InValue; - InsertEvent->InsertInterpolation = InInterp; - - ModificationEvents.Add(InsertEvent); -} - -void -UHoudiniParameterRampFloat::CreateDeleteEvent(const int32 &InDeleteIndex) -{ - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - this, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetFloatRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - ModificationEvents.Add(DeleteEvent); -} - - -UHoudiniParameterRampColor * -UHoudiniParameterRampColor::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampColor::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterRampColor * HoudiniParameter = NewObject< UHoudiniParameterRampColor >( - InOuter, UHoudiniParameterRampColor::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniParameter->SetParameterType(EHoudiniParameterType::ColorRamp); - - HoudiniParameter->NumDefaultPoints = -1; - - HoudiniParameter->bCaching = false; - - return HoudiniParameter; -} - - -bool -UHoudiniParameterRampFloat::IsDefault() const -{ - if (NumDefaultPoints < 0) - return true; - - if (NumDefaultPoints != Points.Num()) - { - return false; - } - - TArray Positions = DefaultPositions; - TArray Values = DefaultValues; - TArray Choices = DefaultChoices; - - for (auto & NextPt : Points) - { - if (!NextPt) - return false; - - bool bFoundMatch = false; - for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) - { - if (Positions[DefaultIdx] == NextPt->Position && - Values[DefaultIdx] == NextPt->Value && - Choices[DefaultIdx] == (int32)NextPt->Interpolation) - { - Positions.RemoveAt(DefaultIdx); - Values.RemoveAt(DefaultIdx); - Choices.RemoveAt(DefaultIdx); - bFoundMatch = true; - } - } - - if (!bFoundMatch) - return false; - } - - if (Positions.Num() > 0) - return false; - - return true; -} - -void UHoudiniParameterRampColor::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - UHoudiniParameterRampColor* FromParameter = Cast(InParameter); - check(FromParameter); - - TArray PrevCachedPoints = CachedPoints; - TArray PrevPoints = Points; - - Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); - - CachedPoints = PrevCachedPoints; - Points = PrevPoints; - - - auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) - { - const int32 NumPts = FromArray.Num(); - - ToArray.SetNum(NumPts); - - for(int32 i = 0; i < NumPts; ++i) - { - UHoudiniParameterRampColorPoint* FromPoint = FromArray[i]; - UHoudiniParameterRampColorPoint* ToPoint = ToArray[i]; - - check(FromPoint); - - if (ToPoint) - { - // Ensure the destination point is outered to this parameter - bool bIsValid = ToPoint->GetOuter() == NewOuter; - if (!bIsValid) - ToPoint = nullptr; - } - - if (!ToPoint) - { - // Duplicate a new copy using FromPoint - ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); - } - else - { - // We have a valid point that we can reuse. Simply copy state. - ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); - } - - ToArray[i] = ToPoint; - } - }; - - CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); - CopyPointsStateFn(FromParameter->Points, Points, this); - - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -} - -void UHoudiniParameterRampColor::RemapParameters(const TMap& ParameterMapping) -{ - Super::RemapParameters(ParameterMapping); - - AActor* OuterActor = GetTypedOuter(); - - - for(UHoudiniParameterRampColorPoint* CurrentPoint : Points) - { - CurrentPoint->RemapParameters(ParameterMapping); - } - - for(UHoudiniParameterRampColorPoint* CurrentPoint : CachedPoints) - { - CurrentPoint->RemapParameters(ParameterMapping); - } -} - -bool -UHoudiniParameterRampColor::IsDefault() const -{ - if (NumDefaultPoints < 0) - return true; - - if (NumDefaultPoints != Points.Num()) - return false; - - TArray Positions = DefaultPositions; - TArray Values = DefaultValues; - TArray Choices = DefaultChoices; - - for (auto & NextPt : Points) - { - if (!NextPt) - return false; - - bool bFoundMatch = false; - for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) - { - if (Positions[DefaultIdx] == NextPt->Position && - Values[DefaultIdx] == NextPt->Value && - Choices[DefaultIdx] == (int32)NextPt->Interpolation) - { - Positions.RemoveAt(DefaultIdx); - Values.RemoveAt(DefaultIdx); - Choices.RemoveAt(DefaultIdx); - bFoundMatch = true; - } - } - - if (!bFoundMatch) - return false; - } - - if (Positions.Num() > 0) - return false; - - return true; -} - - -void -UHoudiniParameterRampColor::SetDefaultValues() -{ - if (NumDefaultPoints >= 0) - return; - - - if (DefaultPositions.Num() > 0) - return; - - DefaultPositions.Empty(); - DefaultValues.Empty(); - DefaultChoices.Empty(); - - for (auto & NextPoint : Points) - { - if (!NextPoint) - continue; - - DefaultPositions.Add(NextPoint->Position); - DefaultValues.Add(NextPoint->Value); - DefaultChoices.Add((int32)NextPoint->Interpolation); - - } - - NumDefaultPoints = Points.Num(); -} - -void -UHoudiniParameterRampFloat::SetDefaultValues() -{ - if (DefaultPositions.Num() > 0) - return; - - DefaultPositions.Empty(); - DefaultValues.Empty(); - DefaultChoices.Empty(); - - for (auto & NextPoint : Points) - { - if (!NextPoint) - continue; - - DefaultPositions.Add(NextPoint->Position); - DefaultValues.Add(NextPoint->Value); - DefaultChoices.Add((int32)NextPoint->Interpolation); - - } - - NumDefaultPoints = Points.Num(); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterRamp.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterChoice.h" + +#include "UObject/UnrealType.h" + + +void +UHoudiniParameterRampFloatPoint::SetPosition(const float InPosition) +{ + if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Position = InPosition; + PositionParentParm->SetValueAt(InPosition, 0); + PositionParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampFloatPoint::SetValue(const float InValue) +{ + if (ValueParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Value = InValue; + ValueParentParm->SetValueAt(InValue, 0); + ValueParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampFloatPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) +{ + if (InterpolationParentParm) + { + Interpolation = InInterpolation; + InterpolationParentParm->SetIntValue((int32)InInterpolation); + InterpolationParentParm->UpdateStringValueFromInt(); + InterpolationParentParm->SetIsChildOfRamp(); + } +} + +UHoudiniParameterRampFloatPoint* +UHoudiniParameterRampFloatPoint::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + UHoudiniParameterRampFloatPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); + + NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); + + return NewPoint; +} + +void +UHoudiniParameterRampFloatPoint::CopyStateFrom(UHoudiniParameterRampFloatPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + // Ensure this object's flags match the desired flags. + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +} + +void +UHoudiniParameterRampFloatPoint::RemapParameters( + const TMap& ParameterMapping) +{ + if (ParameterMapping.Contains(PositionParentParm)) + { + PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); + } + else + { + PositionParentParm = nullptr; + } + + if (ParameterMapping.Contains(ValueParentParm)) + { + ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); + } + else + { + ValueParentParm = nullptr; + } + + if (ParameterMapping.Contains(InterpolationParentParm)) + { + InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); + } + else + { + InterpolationParentParm = nullptr; + } +}; + + +void +UHoudiniParameterRampColorPoint::SetPosition(const float InPosition) +{ + if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Position = InPosition; + PositionParentParm->SetValueAt(InPosition, 0); + PositionParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampColorPoint::SetValue(const FLinearColor InValue) +{ + if (!ValueParentParm) + return; + + Value = InValue; + ValueParentParm->SetColorValue(InValue); + ValueParentParm->SetIsChildOfRamp(); +}; + +void +UHoudiniParameterRampColorPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) +{ + if (!InterpolationParentParm) + return; + + Interpolation = InInterpolation; + InterpolationParentParm->SetIntValue((int32)InInterpolation); + InterpolationParentParm->UpdateStringValueFromInt(); + InterpolationParentParm->SetIsChildOfRamp(); +} +UHoudiniParameterRampColorPoint * UHoudiniParameterRampColorPoint::DuplicateAndCopyState(UObject * DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + + UHoudiniParameterRampColorPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); + + NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); + + return NewPoint; + +} +void UHoudiniParameterRampColorPoint::CopyStateFrom(UHoudiniParameterRampColorPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ +#if WITH_EDITOR + PreEditChange(nullptr); +#endif + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + // Ensure this object's flags match the desired flags. + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +#if WITH_EDITOR + PostEditChange(); +#endif +} + +void UHoudiniParameterRampColorPoint::RemapParameters(const TMap& ParameterMapping) +{ + if (ParameterMapping.Contains(PositionParentParm)) + { + PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); + } + else + { + PositionParentParm = nullptr; + } + + if (ParameterMapping.Contains(ValueParentParm)) + { + ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); + } + else + { + ValueParentParm = nullptr; + } + + if (ParameterMapping.Contains(InterpolationParentParm)) + { + InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); + } + else + { + InterpolationParentParm = nullptr; + } +} + + +UHoudiniParameterRampFloat::UHoudiniParameterRampFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), + NumDefaultPoints(-1), + bCaching(false) +{ + ParmType = EHoudiniParameterType::FloatRamp; +} + +void +UHoudiniParameterRampFloat::OnPreCook() +{ + if (bCaching) + { + SyncCachedPoints(); + bCaching = false; + } +} + +UHoudiniParameterRampFloat * +UHoudiniParameterRampFloat::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampFloat::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterRampFloat * HoudiniParameter = NewObject< UHoudiniParameterRampFloat >( + InOuter, UHoudiniParameterRampFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniParameter->SetParameterType(EHoudiniParameterType::FloatRamp); + + HoudiniParameter->NumDefaultPoints = -1; + + HoudiniParameter->bCaching = false; + + return HoudiniParameter; +} + +void UHoudiniParameterRampFloat::CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ +#if WITH_EDITOR + PreEditChange(nullptr); +#endif + + UHoudiniParameterRampFloat* FromParameter = Cast(InParameter); + check(FromParameter); + + TArray PrevCachedPoints = CachedPoints; + TArray PrevPoints = Points; + + Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); + + CachedPoints = PrevCachedPoints; + Points = PrevPoints; + + + auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) + { + const int32 NumPts = FromArray.Num(); + + ToArray.SetNum(NumPts); + + for(int32 i = 0; i < NumPts; ++i) + { + UHoudiniParameterRampFloatPoint* FromPoint = FromArray[i]; + UHoudiniParameterRampFloatPoint* ToPoint = ToArray[i]; + + check(FromPoint); + + if (ToPoint) + { + // Ensure the destination point is outered to this parameter + bool bIsValid = ToPoint->GetOuter() == NewOuter; + if (!bIsValid) + ToPoint = nullptr; + } + + if (!ToPoint) + { + // Duplicate a new copy using FromPoint + ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); + } + else + { + // We have a valid point that we can reuse. Simply copy state. + ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); + } + + ToArray[i] = ToPoint; + } + }; + + CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); + CopyPointsStateFn(FromParameter->Points, Points, this); + +#if WITH_EDITOR + PostEditChange(); +#endif +} + +void +UHoudiniParameterRampFloat::RemapParameters(const TMap& ParameterMapping) +{ + Super::RemapParameters(ParameterMapping); + + AActor* OuterActor = GetTypedOuter(); + + + for(UHoudiniParameterRampFloatPoint* CurrentPoint : Points) + { + CurrentPoint->RemapParameters(ParameterMapping); + } + + for(UHoudiniParameterRampFloatPoint* CurrentPoint : CachedPoints) + { + CurrentPoint->RemapParameters(ParameterMapping); + } +} + +void +UHoudiniParameterRampFloat::SyncCachedPoints() +{ + int32 Idx = 0; + + while (Idx < CachedPoints.Num() && Idx < Points.Num()) + { + UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; + UHoudiniParameterRampFloatPoint* &CurrentPoint = Points[Idx]; + + if (!CachedPoint || !CurrentPoint) + continue; + + if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) + { + if (CurrentPoint->PositionParentParm) + { + CurrentPoint->SetPosition(CachedPoint->GetPosition()); + CurrentPoint->PositionParentParm->MarkChanged(true); + } + } + + if (CachedPoint->GetValue() != CurrentPoint->GetValue()) + { + if (CurrentPoint->ValueParentParm) + { + CurrentPoint->SetValue(CachedPoint->GetValue()); + CurrentPoint->ValueParentParm->MarkChanged(true); + } + } + + if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) + { + if (CurrentPoint->InterpolationParentParm) + { + CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); + CurrentPoint->InterpolationParentParm->MarkChanged(true); + } + } + + Idx += 1; + } + + // Insert points + for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) + { + UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; + if (!CachedPoint) + continue; + + CreateInsertEvent(CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); + + MarkChanged(true); + } + + // Remove points + for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < Points.Num(); ++IdxCurrentPointLeft) + { + RemoveElement(IdxCurrentPointLeft); + + UHoudiniParameterRampFloatPoint* Point = Points[IdxCurrentPointLeft]; + + if (!Point) + continue; + + CreateDeleteEvent(Point->InstanceIndex); + + MarkChanged(true); + } +} + + +void +UHoudiniParameterRampFloat::CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) +{ + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + this, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetFloatRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertFloat = InValue; + InsertEvent->InsertInterpolation = InInterp; + + ModificationEvents.Add(InsertEvent); +} + +void +UHoudiniParameterRampFloat::CreateDeleteEvent(const int32 &InDeleteIndex) +{ + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + this, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetFloatRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + ModificationEvents.Add(DeleteEvent); +} + + +UHoudiniParameterRampColor::UHoudiniParameterRampColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), + bCaching(false), + NumDefaultPoints(-1) +{ + ParmType = EHoudiniParameterType::ColorRamp; +} + + +UHoudiniParameterRampColor * +UHoudiniParameterRampColor::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampColor::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterRampColor * HoudiniParameter = NewObject< UHoudiniParameterRampColor >( + InOuter, UHoudiniParameterRampColor::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniParameter->SetParameterType(EHoudiniParameterType::ColorRamp); + + HoudiniParameter->NumDefaultPoints = -1; + + HoudiniParameter->bCaching = false; + + return HoudiniParameter; +} + + +bool +UHoudiniParameterRampFloat::IsDefault() const +{ + if (NumDefaultPoints < 0) + return true; + + if (NumDefaultPoints != Points.Num()) + { + return false; + } + + TArray Positions = DefaultPositions; + TArray Values = DefaultValues; + TArray Choices = DefaultChoices; + + for (auto & NextPt : Points) + { + if (!NextPt) + return false; + + bool bFoundMatch = false; + for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) + { + if (Positions[DefaultIdx] == NextPt->Position && + Values[DefaultIdx] == NextPt->Value && + Choices[DefaultIdx] == (int32)NextPt->Interpolation) + { + Positions.RemoveAt(DefaultIdx); + Values.RemoveAt(DefaultIdx); + Choices.RemoveAt(DefaultIdx); + bFoundMatch = true; + } + } + + if (!bFoundMatch) + return false; + } + + if (Positions.Num() > 0) + return false; + + return true; +} + +void UHoudiniParameterRampColor::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + UHoudiniParameterRampColor* FromParameter = Cast(InParameter); + check(FromParameter); + + TArray PrevCachedPoints = CachedPoints; + TArray PrevPoints = Points; + + Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); + + CachedPoints = PrevCachedPoints; + Points = PrevPoints; + + + auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) + { + const int32 NumPts = FromArray.Num(); + + ToArray.SetNum(NumPts); + + for(int32 i = 0; i < NumPts; ++i) + { + UHoudiniParameterRampColorPoint* FromPoint = FromArray[i]; + UHoudiniParameterRampColorPoint* ToPoint = ToArray[i]; + + check(FromPoint); + + if (ToPoint) + { + // Ensure the destination point is outered to this parameter + bool bIsValid = ToPoint->GetOuter() == NewOuter; + if (!bIsValid) + ToPoint = nullptr; + } + + if (!ToPoint) + { + // Duplicate a new copy using FromPoint + ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); + } + else + { + // We have a valid point that we can reuse. Simply copy state. + ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); + } + + ToArray[i] = ToPoint; + } + }; + + CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); + CopyPointsStateFn(FromParameter->Points, Points, this); + + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +} + +void UHoudiniParameterRampColor::RemapParameters(const TMap& ParameterMapping) +{ + Super::RemapParameters(ParameterMapping); + + AActor* OuterActor = GetTypedOuter(); + + + for(UHoudiniParameterRampColorPoint* CurrentPoint : Points) + { + CurrentPoint->RemapParameters(ParameterMapping); + } + + for(UHoudiniParameterRampColorPoint* CurrentPoint : CachedPoints) + { + CurrentPoint->RemapParameters(ParameterMapping); + } +} + +bool +UHoudiniParameterRampColor::IsDefault() const +{ + if (NumDefaultPoints < 0) + return true; + + if (NumDefaultPoints != Points.Num()) + return false; + + TArray Positions = DefaultPositions; + TArray Values = DefaultValues; + TArray Choices = DefaultChoices; + + for (auto & NextPt : Points) + { + if (!NextPt) + return false; + + bool bFoundMatch = false; + for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) + { + if (Positions[DefaultIdx] == NextPt->Position && + Values[DefaultIdx] == NextPt->Value && + Choices[DefaultIdx] == (int32)NextPt->Interpolation) + { + Positions.RemoveAt(DefaultIdx); + Values.RemoveAt(DefaultIdx); + Choices.RemoveAt(DefaultIdx); + bFoundMatch = true; + } + } + + if (!bFoundMatch) + return false; + } + + if (Positions.Num() > 0) + return false; + + return true; +} + + +void +UHoudiniParameterRampColor::SetDefaultValues() +{ + if (NumDefaultPoints >= 0) + return; + + + if (DefaultPositions.Num() > 0) + return; + + DefaultPositions.Empty(); + DefaultValues.Empty(); + DefaultChoices.Empty(); + + for (auto & NextPoint : Points) + { + if (!NextPoint) + continue; + + DefaultPositions.Add(NextPoint->Position); + DefaultValues.Add(NextPoint->Value); + DefaultChoices.Add((int32)NextPoint->Interpolation); + + } + + NumDefaultPoints = Points.Num(); +} + +void +UHoudiniParameterRampFloat::SetDefaultValues() +{ + if (DefaultPositions.Num() > 0) + return; + + DefaultPositions.Empty(); + DefaultValues.Empty(); + DefaultChoices.Empty(); + + for (auto & NextPoint : Points) + { + if (!NextPoint) + continue; + + DefaultPositions.Add(NextPoint->Position); + DefaultValues.Add(NextPoint->Value); + DefaultChoices.Add((int32)NextPoint->Interpolation); + + } + + NumDefaultPoints = Points.Num(); + } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h index 11aa9fd87..72dbac599 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h @@ -1,312 +1,312 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Curves/CurveFloat.h" -#include "Curves/CurveLinearColor.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.generated.h" - -class UHoudiniParameterRampFloat; -class UHoudiniParameterRampColor; -class UHoudiniParameter; -class UHoudiniParameterFloat; -class UHoudiniParameterChoice; -class UHoudiniParameterColor; - -UENUM() -enum class EHoudiniRampPointConstructStatus : uint8 -{ - None, - - INITIALIZED, - POSITION_INSERTED, - VALUE_INSERTED, - INTERPTYPE_INSERTED -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampModificationEvent : public UObject -{ - GENERATED_BODY() -public: - FORCEINLINE - void SetInsertEvent() { bIsInsertEvent = true; }; - - FORCEINLINE - void SetDeleteEvent() { bIsInsertEvent = false; }; - - FORCEINLINE - void SetFloatRampEvent() { bIsFloatRamp = true; }; - - FORCEINLINE - void SetColorRampEvent() { bIsFloatRamp = false; }; - - FORCEINLINE - bool IsInsertEvent() const { return bIsInsertEvent; }; - - FORCEINLINE - bool IsDeleteEvent() const { return !bIsInsertEvent; }; - - FORCEINLINE - bool IsFloatRampEvent() { return bIsFloatRamp; }; - - FORCEINLINE - bool IsColorRampEvent() { return !bIsFloatRamp; }; - - -private: - UPROPERTY() - bool bIsInsertEvent = false; - - UPROPERTY() - bool bIsFloatRamp = false; - -public: - UPROPERTY() - int32 DeleteInstanceIndex = -1; - - UPROPERTY() - float InsertPosition; - - UPROPERTY() - float InsertFloat; - - UPROPERTY() - FLinearColor InsertColor; - - UPROPERTY() - EHoudiniRampInterpolationType InsertInterpolation; -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloatPoint : public UObject -{ - GENERATED_BODY() - -public: - - UPROPERTY() - float Position; - - UPROPERTY() - float Value; - - UPROPERTY() - EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; - - UPROPERTY() - int32 InstanceIndex = -1; - - UPROPERTY() - UHoudiniParameterFloat* PositionParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterFloat* ValueParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterChoice* InterpolationParentParm = nullptr; - - FORCEINLINE - float GetPosition() const { return Position; }; - - void SetPosition(const float InPosition); - - FORCEINLINE - float GetValue() const { return Value; }; - - void SetValue(const float InValue); - - FORCEINLINE - EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; - - void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); - - UHoudiniParameterRampFloatPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); - - void CopyStateFrom(UHoudiniParameterRampFloatPoint* InParameter, bool bCopyAllProperties, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); - - void RemapParameters(const TMap& ParameterMapping); - -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColorPoint : public UObject -{ - GENERATED_BODY() - -public: - - UPROPERTY() - float Position; - - UPROPERTY() - FLinearColor Value; - - UPROPERTY() - EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; - - UPROPERTY() - int32 InstanceIndex = -1; - - UPROPERTY() - UHoudiniParameterFloat * PositionParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterColor* ValueParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterChoice* InterpolationParentParm = nullptr; - - FORCEINLINE - float GetPosition() const { return Position; }; - - void SetPosition(const float InPosition); - FORCEINLINE - FLinearColor GetValue() const { return Value; }; - - void SetValue(const FLinearColor InValue); - - FORCEINLINE - EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; - - void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); - - UHoudiniParameterRampColorPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - void CopyStateFrom(UHoudiniParameterRampColorPoint* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - void RemapParameters(const TMap& ParameterMapping); -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParameterMultiParm -{ - GENERATED_BODY() - -public: - - virtual void OnPreCook() override; - - // Create instance of this class. - static UHoudiniParameterRampFloat * Create( - UObject* Outer, - const FString& ParamName); - - FORCEINLINE - bool IsCaching() const { return bCaching; }; - - FORCEINLINE - void SetCaching(const bool bInCaching) { bCaching = bInCaching; }; - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; - - virtual void RemapParameters(const TMap& ParameterMapping) override; - - void SyncCachedPoints(); - - void CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); - - void CreateDeleteEvent(const int32 &InDeleteIndex); - - UPROPERTY() - TArray Points; - - UPROPERTY() - TArray CachedPoints; - - UPROPERTY() - TArray DefaultPositions; - - UPROPERTY() - TArray DefaultValues; - - UPROPERTY() - TArray DefaultChoices; - - UPROPERTY() - int32 NumDefaultPoints; - - UPROPERTY() - bool bCaching; - - UPROPERTY() - TArray ModificationEvents; - - bool IsDefault() const override; - - void SetDefaultValues(); - -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColor : public UHoudiniParameterMultiParm -{ - GENERATED_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterRampColor * Create( - UObject* Outer, - const FString& ParamName); - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; - - virtual void RemapParameters(const TMap& ParameterMapping) override; - - UPROPERTY(Instanced) - TArray Points; - - UPROPERTY() - bool bCaching; - - UPROPERTY(Instanced) - TArray CachedPoints; - - UPROPERTY() - TArray DefaultPositions; - - UPROPERTY() - TArray DefaultValues; - - UPROPERTY() - TArray DefaultChoices; - - UPROPERTY() - int32 NumDefaultPoints; - - UPROPERTY() - TArray ModificationEvents; - - bool IsDefault() const override; - - void SetDefaultValues(); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Curves/CurveFloat.h" +#include "Curves/CurveLinearColor.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.generated.h" + +class UHoudiniParameterRampFloat; +class UHoudiniParameterRampColor; +class UHoudiniParameter; +class UHoudiniParameterFloat; +class UHoudiniParameterChoice; +class UHoudiniParameterColor; + +UENUM() +enum class EHoudiniRampPointConstructStatus : uint8 +{ + None, + + INITIALIZED, + POSITION_INSERTED, + VALUE_INSERTED, + INTERPTYPE_INSERTED +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampModificationEvent : public UObject +{ + GENERATED_BODY() +public: + FORCEINLINE + void SetInsertEvent() { bIsInsertEvent = true; }; + + FORCEINLINE + void SetDeleteEvent() { bIsInsertEvent = false; }; + + FORCEINLINE + void SetFloatRampEvent() { bIsFloatRamp = true; }; + + FORCEINLINE + void SetColorRampEvent() { bIsFloatRamp = false; }; + + FORCEINLINE + bool IsInsertEvent() const { return bIsInsertEvent; }; + + FORCEINLINE + bool IsDeleteEvent() const { return !bIsInsertEvent; }; + + FORCEINLINE + bool IsFloatRampEvent() { return bIsFloatRamp; }; + + FORCEINLINE + bool IsColorRampEvent() { return !bIsFloatRamp; }; + + +private: + UPROPERTY() + bool bIsInsertEvent = false; + + UPROPERTY() + bool bIsFloatRamp = false; + +public: + UPROPERTY() + int32 DeleteInstanceIndex = -1; + + UPROPERTY() + float InsertPosition; + + UPROPERTY() + float InsertFloat; + + UPROPERTY() + FLinearColor InsertColor; + + UPROPERTY() + EHoudiniRampInterpolationType InsertInterpolation; +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloatPoint : public UObject +{ + GENERATED_BODY() + +public: + + UPROPERTY() + float Position; + + UPROPERTY() + float Value; + + UPROPERTY() + EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; + + UPROPERTY() + int32 InstanceIndex = -1; + + UPROPERTY() + UHoudiniParameterFloat* PositionParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterFloat* ValueParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterChoice* InterpolationParentParm = nullptr; + + FORCEINLINE + float GetPosition() const { return Position; }; + + void SetPosition(const float InPosition); + + FORCEINLINE + float GetValue() const { return Value; }; + + void SetValue(const float InValue); + + FORCEINLINE + EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; + + void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); + + UHoudiniParameterRampFloatPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); + + void CopyStateFrom(UHoudiniParameterRampFloatPoint* InParameter, bool bCopyAllProperties, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); + + void RemapParameters(const TMap& ParameterMapping); + +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColorPoint : public UObject +{ + GENERATED_BODY() + +public: + + UPROPERTY() + float Position; + + UPROPERTY() + FLinearColor Value; + + UPROPERTY() + EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; + + UPROPERTY() + int32 InstanceIndex = -1; + + UPROPERTY() + UHoudiniParameterFloat * PositionParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterColor* ValueParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterChoice* InterpolationParentParm = nullptr; + + FORCEINLINE + float GetPosition() const { return Position; }; + + void SetPosition(const float InPosition); + FORCEINLINE + FLinearColor GetValue() const { return Value; }; + + void SetValue(const FLinearColor InValue); + + FORCEINLINE + EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; + + void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); + + UHoudiniParameterRampColorPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + void CopyStateFrom(UHoudiniParameterRampColorPoint* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + void RemapParameters(const TMap& ParameterMapping); +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParameterMultiParm +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void OnPreCook() override; + + // Create instance of this class. + static UHoudiniParameterRampFloat * Create( + UObject* Outer, + const FString& ParamName); + + FORCEINLINE + bool IsCaching() const { return bCaching; }; + + FORCEINLINE + void SetCaching(const bool bInCaching) { bCaching = bInCaching; }; + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; + + virtual void RemapParameters(const TMap& ParameterMapping) override; + + void SyncCachedPoints(); + + void CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); + + void CreateDeleteEvent(const int32 &InDeleteIndex); + + UPROPERTY() + TArray Points; + + UPROPERTY() + TArray CachedPoints; + + UPROPERTY() + TArray DefaultPositions; + + UPROPERTY() + TArray DefaultValues; + + UPROPERTY() + TArray DefaultChoices; + + UPROPERTY() + int32 NumDefaultPoints; + + UPROPERTY() + bool bCaching; + + UPROPERTY() + TArray ModificationEvents; + + bool IsDefault() const override; + + void SetDefaultValues(); + +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColor : public UHoudiniParameterMultiParm +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterRampColor * Create( + UObject* Outer, + const FString& ParamName); + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; + + virtual void RemapParameters(const TMap& ParameterMapping) override; + + UPROPERTY(Instanced) + TArray Points; + + UPROPERTY() + bool bCaching; + + UPROPERTY(Instanced) + TArray CachedPoints; + + UPROPERTY() + TArray DefaultPositions; + + UPROPERTY() + TArray DefaultValues; + + UPROPERTY() + TArray DefaultChoices; + + UPROPERTY() + int32 NumDefaultPoints; + + UPROPERTY() + TArray ModificationEvents; + + bool IsDefault() const override; + + void SetDefaultValues(); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp index d59f4177f..9fe3a8db1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp @@ -1,51 +1,51 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterSeparator.h" - -UHoudiniParameterSeparator::UHoudiniParameterSeparator(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Separator; -} - -UHoudiniParameterSeparator * -UHoudiniParameterSeparator::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterSeparator_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterSeparator::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterSeparator * HoudiniAssetParameter = NewObject< UHoudiniParameterSeparator >( - InOuter, UHoudiniParameterSeparator::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Separator); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterSeparator.h" + +UHoudiniParameterSeparator::UHoudiniParameterSeparator(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Separator; +} + +UHoudiniParameterSeparator * +UHoudiniParameterSeparator::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterSeparator_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterSeparator::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterSeparator * HoudiniAssetParameter = NewObject< UHoudiniParameterSeparator >( + InOuter, UHoudiniParameterSeparator::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Separator); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h index 35b8e3f45..f408b88dc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterSeparator.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterSeparator : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterSeparator * Create( - UObject* Outer, - const FString& ParamName); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterSeparator.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterSeparator : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterSeparator * Create( + UObject* Outer, + const FString& ParamName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp index 99daaa002..1751a07a8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp @@ -1,136 +1,136 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterString.h" - -UHoudiniParameterString::UHoudiniParameterString(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , bIsAssetRef(false) -{ - ParmType = EHoudiniParameterType::String; -} - -UHoudiniParameterString * -UHoudiniParameterString::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterString_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterString::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterString * HoudiniAssetParameter = NewObject< UHoudiniParameterString >( - InOuter, UHoudiniParameterString::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::String); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterString::SetValueAt(const FString& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index].Equals(InValue, ESearchCase::CaseSensitive)) - return false; - - Values[Index] = InValue; - - return true; -} - -void -UHoudiniParameterString::SetAssetAt(UObject* InObject, const uint32& Index) -{ - if (!ChosenAssets.IsValidIndex(Index)) - return; - - ChosenAssets[Index] = InObject; - -} - -FString -UHoudiniParameterString::GetAssetReference(UObject* InObject) -{ - // Get the asset reference string for a given UObject - if (!InObject || InObject->IsPendingKill()) - return FString(); - - // Start by getting the Object's full name - FString AssetReference = InObject->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - return AssetReference; -} - -void -UHoudiniParameterString::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterString::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterString::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterString.h" + +UHoudiniParameterString::UHoudiniParameterString(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , bIsAssetRef(false) +{ + ParmType = EHoudiniParameterType::String; +} + +UHoudiniParameterString * +UHoudiniParameterString::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterString_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterString::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterString * HoudiniAssetParameter = NewObject< UHoudiniParameterString >( + InOuter, UHoudiniParameterString::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::String); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterString::SetValueAt(const FString& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index].Equals(InValue, ESearchCase::CaseSensitive)) + return false; + + Values[Index] = InValue; + + return true; +} + +void +UHoudiniParameterString::SetAssetAt(UObject* InObject, const uint32& Index) +{ + if (!ChosenAssets.IsValidIndex(Index)) + return; + + ChosenAssets[Index] = InObject; + +} + +FString +UHoudiniParameterString::GetAssetReference(UObject* InObject) +{ + // Get the asset reference string for a given UObject + if (!InObject || InObject->IsPendingKill()) + return FString(); + + // Start by getting the Object's full name + FString AssetReference = InObject->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + return AssetReference; +} + +void +UHoudiniParameterString::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterString::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterString::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h index 2e76f0c35..1f4c13bdc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h @@ -1,91 +1,91 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterString.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterString : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterString * Create( - UObject* Outer, const FString& ParamName); - - // Accessor - FString GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? Values[Index] : FString(); }; - - UObject* GetAssetAt(const uint32& Index) const { return ChosenAssets.IsValidIndex(Index) ? ChosenAssets[Index] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - bool IsAssetRef() const { return bIsAssetRef; }; - - bool IsDefaultValueAtIndex(const int32& Idx) const; - bool IsDefault() const override; - - // Mutators - void SetIsAssetRef(const bool& InIsAssetRef) { bIsAssetRef = InIsAssetRef; }; - - bool SetValueAt(const FString& InValue, const uint32& Index); - - void SetAssetAt(UObject* InObject, const uint32& Index); - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNum(InNumValues); ChosenAssets.SetNum(InNumValues); }; - - TArray & GetChosenAssets() { return ChosenAssets; }; - - void SetDefaultValues(); - - // Utility - - // Get the asset reference string for a given UObject - static FString GetAssetReference(UObject* InObject); - - -protected: - - // Values of this property. - UPROPERTY() - TArray< FString > Values; - - UPROPERTY() - TArray< FString > DefaultValues; - - UPROPERTY() - TArray ChosenAssets; - - // Indicates this string parameter should be treated as an asset reference - // and display an object picker - UPROPERTY() - bool bIsAssetRef; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterString.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterString : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterString * Create( + UObject* Outer, const FString& ParamName); + + // Accessor + FString GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? Values[Index] : FString(); }; + + UObject* GetAssetAt(const uint32& Index) const { return ChosenAssets.IsValidIndex(Index) ? ChosenAssets[Index] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + bool IsAssetRef() const { return bIsAssetRef; }; + + bool IsDefaultValueAtIndex(const int32& Idx) const; + bool IsDefault() const override; + + // Mutators + void SetIsAssetRef(const bool& InIsAssetRef) { bIsAssetRef = InIsAssetRef; }; + + bool SetValueAt(const FString& InValue, const uint32& Index); + + void SetAssetAt(UObject* InObject, const uint32& Index); + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNum(InNumValues); ChosenAssets.SetNum(InNumValues); }; + + TArray & GetChosenAssets() { return ChosenAssets; }; + + void SetDefaultValues(); + + // Utility + + // Get the asset reference string for a given UObject + static FString GetAssetReference(UObject* InObject); + + +protected: + + // Values of this property. + UPROPERTY() + TArray< FString > Values; + + UPROPERTY() + TArray< FString > DefaultValues; + + UPROPERTY() + TArray ChosenAssets; + + // Indicates this string parameter should be treated as an asset reference + // and display an object picker + UPROPERTY() + bool bIsAssetRef; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp index 6fa8ff2e2..5c7e1d205 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp @@ -1,95 +1,95 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterToggle.h" - -UHoudiniParameterToggle::UHoudiniParameterToggle(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Toggle; -} - -UHoudiniParameterToggle * -UHoudiniParameterToggle::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterToggle_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterToggle::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterToggle * HoudiniAssetParameter = NewObject< UHoudiniParameterToggle >( - InOuter, UHoudiniParameterToggle::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Toggle); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterToggle::SetValueAt(const bool& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index] == 0 && !InValue) - return false; - - if (Values[Index] == 1 && InValue) - return false; - - Values[Index] = InValue ? 1 : 0; - return true; -} - -bool -UHoudiniParameterToggle::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!DefaultValues.IsValidIndex(Idx)) - break; - - if (Values[Idx] != DefaultValues[Idx]) - return false; - } - - return true; -} - -void -UHoudiniParameterToggle::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterToggle.h" + +UHoudiniParameterToggle::UHoudiniParameterToggle(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Toggle; +} + +UHoudiniParameterToggle * +UHoudiniParameterToggle::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterToggle_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterToggle::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterToggle * HoudiniAssetParameter = NewObject< UHoudiniParameterToggle >( + InOuter, UHoudiniParameterToggle::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Toggle); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterToggle::SetValueAt(const bool& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index] == 0 && !InValue) + return false; + + if (Values[Index] == 1 && InValue) + return false; + + Values[Index] = InValue ? 1 : 0; + return true; +} + +bool +UHoudiniParameterToggle::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!DefaultValues.IsValidIndex(Idx)) + break; + + if (Values[Idx] != DefaultValues[Idx]) + return false; + } + + return true; +} + +void +UHoudiniParameterToggle::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h index c80572fb7..63209a9bf 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h @@ -1,68 +1,68 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" -#include "Styling/SlateTypes.h" -#include "HoudiniParameterToggle.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterToggle : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterToggle * Create( - UObject* Outer, - const FString& ParamName); - - // Accessor - bool GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? (bool)Values[Index] : false; }; - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - bool IsDefault() const override; - - // Mutators - bool SetValueAt(const bool& InValue, const uint32& Index); - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - - int32 GetNumValues() { return Values.Num(); }; - - void SetDefaultValues(); - -protected: - - // Values of this property. - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" +#include "Styling/SlateTypes.h" +#include "HoudiniParameterToggle.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterToggle : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterToggle * Create( + UObject* Outer, + const FString& ParamName); + + // Accessor + bool GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? (bool)Values[Index] : false; }; + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + bool IsDefault() const override; + + // Mutators + bool SetValueAt(const bool& InValue, const uint32& Index); + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + + int32 GetNumValues() { return Values.Num(); }; + + void SetDefaultValues(); + +protected: + + // Values of this property. + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp index f774d31b3..e54063122 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp @@ -1,33 +1,35 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPluginSerializationVersion.h" -#include "Serialization/CustomVersion.h" - -const FGuid FHoudiniCustomSerializationVersion::GUID( 0x1AB9CECC, 0x6913, 0x4875, 0x203d51fb ); - -// Register the custom version with core -FCustomVersionRegistration GRegisterHoudiniCustomVersion( FHoudiniCustomSerializationVersion::GUID, VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION, TEXT( "HoudiniUE4PluginVer" ) ); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPluginSerializationVersion.h" + +#include "Serialization/CustomVersion.h" +#include "Misc/Guid.h" + +const FGuid FHoudiniCustomSerializationVersion::GUID( 0x1AB9CECC, 0x6913, 0x4875, 0x203d51fb ); + +// Register the custom version with core +FCustomVersionRegistration GRegisterHoudiniCustomVersion( FHoudiniCustomSerializationVersion::GUID, VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION, TEXT( "HoudiniUE4PluginVer" ) ); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h index 6d9a95fe7..e765a93ad 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h @@ -1,92 +1,93 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - - -// Deprecated per-class versions used to load old files -// -// Serialization of parameter name map. -#define VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP 2 -// Serialization of instancer material, if it is available. -#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME 1 -// Serialization of attribute instancer material, if it is available. -#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME 2 -// Landscape serialization in asset inputs. -#define VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT 1 -// Asset instance member. -#define VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER 2 -// World Outliner inputs. -#define VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT 3 - - -enum EHoudiniPluginSerializationVersion -{ - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE = 5, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE = 6, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM = 7, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT = 8, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH = 9, - VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM = 10, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT = 11, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_CUSTOM_LINKER = 12, // added custom linker version to archives - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ACTOR_INSTANCING = 13, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES = 14, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT = 15, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE = 16, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES = 17, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS = 18, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY = 19, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS = 20, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP = 21, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS = 22, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP = 23, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT = 24, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY = 25, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX = 26, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM = 27, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME = 28, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX = 29, // Version 29 is a fix for a serialization issue with UE4.19 / H17.0/16.5, 29 is actually version 26 minus the version 24 changes... - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_POST_419_SERIALIZATION_FIX = 30, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF = 31, - - //------------------------------------------------------------ - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE = 100, - - // ------------------------------------------------------ - // - this needs to be the last line (see note below) - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE, - VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION = VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE - 1 -}; - -struct FHoudiniCustomSerializationVersion -{ - // The GUID for this custom version number - const static FGuid GUID; - -private: - FHoudiniCustomSerializationVersion() {} -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Misc/Guid.h" + +// Deprecated per-class versions used to load old files +// +// Serialization of parameter name map. +#define VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP 2 +// Serialization of instancer material, if it is available. +#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME 1 +// Serialization of attribute instancer material, if it is available. +#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME 2 +// Landscape serialization in asset inputs. +#define VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT 1 +// Asset instance member. +#define VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER 2 +// World Outliner inputs. +#define VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT 3 + + +enum EHoudiniPluginSerializationVersion +{ + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE = 5, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE = 6, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM = 7, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT = 8, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH = 9, + VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM = 10, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT = 11, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_CUSTOM_LINKER = 12, // added custom linker version to archives + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ACTOR_INSTANCING = 13, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES = 14, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT = 15, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE = 16, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES = 17, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS = 18, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY = 19, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS = 20, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP = 21, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS = 22, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP = 23, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT = 24, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY = 25, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX = 26, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM = 27, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME = 28, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX = 29, // Version 29 is a fix for a serialization issue with UE4.19 / H17.0/16.5, 29 is actually version 26 minus the version 24 changes... + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_POST_419_SERIALIZATION_FIX = 30, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF = 31, + + //------------------------------------------------------------ + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE = 100, + + // ------------------------------------------------------ + // - this needs to be the last line (see note below) + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE, + VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION = VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE - 1 +}; + +struct FHoudiniCustomSerializationVersion +{ + // The GUID for this custom version number + const static FGuid GUID; + +private: + FHoudiniCustomSerializationVersion() {} +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp index c08b809ca..73bd0e8c3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp @@ -1,352 +1,404 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniRuntimeSettings.h" - - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Misc/Paths.h" -// #include "Internationalization/Internationalization.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) - : Super( ObjectInitializer ) -{ - // Session options. - SessionType = HRSST_NamedPipe; - ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; - ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; - ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; - bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; - AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; - - bSyncWithHoudiniCook = true; - bCookUsingHoudiniTime = true; - bSyncViewport = false; - bSyncHoudiniViewport = false; - bSyncUnrealViewport = false; - - // Instantiating options. - bShowMultiAssetDialog = true; - - // Cooking options. - bPauseCookingOnStart = false; - bDisplaySlateCookingNotifications = true; - DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - // Parameter options - //bTreatRampParametersAsMultiparms = false; - - // Custom Houdini location. - bUseCustomHoudiniLocation = false; - CustomHoudiniLocation.Path = TEXT(""); - - // Arguments for HAPI_Initialize - CookingThreadStackSize = -1; - - // Landscape marshalling default values. - MarshallingLandscapesUseDefaultUnrealScaling = false; - MarshallingLandscapesUseFullResolution = true; - MarshallingLandscapesForceMinMaxValues = false; - MarshallingLandscapesForcedMinValue = -2000.0f; - MarshallingLandscapesForcedMaxValue = 4553.0f; - - // Spline marshalling - MarshallingSplineResolution = 50.0f; - - // Static mesh proxy refinement settings - bEnableProxyStaticMesh = false; - bShowDefaultMesh = true; - bEnableProxyStaticMeshRefinementByTimer = true; - ProxyMeshAutoRefineTimeoutSeconds = 10.0f; - bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; - bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; - - bPDGAsyncCommandletImportEnabled = false; - - // Legacy settings - bEnableBackwardCompatibility = true; - bAutomaticLegacyHDARebuild = false; -} - -UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() -{} - - -FProperty * -UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const -{ - for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) - { - FProperty * Property = *PropIt; - - if (Property->GetNameCPP() == PropertyName) - return Property; - } - - return nullptr; -} - - -void -UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) -{ - FProperty * Property = LocateProperty(PropertyName); - if (Property) - { - if (bReadOnly) - Property->SetPropertyFlags(CPF_EditConst); - else - Property->ClearPropertyFlags(CPF_EditConst); - } -} - - -void -UHoudiniRuntimeSettings::PostInitProperties() -{ - Super::PostInitProperties(); - - // Set Collision generation options as read only - { - if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Set marshalling attributes options as read only - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - /* - // Set Cook Folder as read-only - { - if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) - Property->SetPropertyFlags( CPF_EditConst ); - } - */ - - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - Property->SetPropertyFlags(CPF_EditConst); - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Disable UI elements depending on current session type. -#if WITH_EDITOR - - UpdateSessionUI(); - -#endif // WITH_EDITOR - - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); -} - - -#if WITH_EDITOR - -void -UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - FProperty * Property = PropertyChangedEvent.MemberProperty; - FProperty * LookupProperty = nullptr; - - if (!Property) - return; - if (Property->GetName() == TEXT("SessionType")) - UpdateSessionUI(); - else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); - else if (Property->GetName() == TEXT("CustomHoudiniLocation")) - { - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; - FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); - - // If path does not point to libHAPI location, we need to let user know. - if (!FPaths::FileExists(LibHAPICustomPath)) - { - FString MessageString = FString::Printf( - TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); - - FPlatformMisc::MessageBoxExt( - EAppMsgType::Ok, *MessageString, - TEXT("Invalid Custom Location Specified, resetting.")); - - CustomHoudiniLocationPath = TEXT(""); - } - } - else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) - { - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->SetPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->SetPropertyFlags(CPF_EditConst); - } - else - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->ClearPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->ClearPropertyFlags(CPF_EditConst); - } - } - - /* - if ( Property->GetName() == TEXT( "bEnableCooking" ) ) - { - // Cooking is disabled, we need to disable transform change triggers cooks option is as well. - if ( bEnableCooking ) - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) - { - // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. - if ( bUploadTransformsToHoudiniEngine ) - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - */ -} - - - -void -UHoudiniRuntimeSettings::UpdateSessionUI() -{ - SetPropertyReadOnly(TEXT("ServerHost"), true); - SetPropertyReadOnly(TEXT("ServerPort"), true); - SetPropertyReadOnly(TEXT("ServerPipeName"), true); - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); - - bool bServerType = false; - - switch (SessionType) - { - case HRSST_Socket: - { - SetPropertyReadOnly(TEXT("ServerHost"), false); - SetPropertyReadOnly(TEXT("ServerPort"), false); - bServerType = true; - break; - } - - case HRSST_NamedPipe: - { - SetPropertyReadOnly(TEXT("ServerPipeName"), false); - bServerType = true; - break; - } - - default: - break; - } - - if (bServerType) - { - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); - } -} - -#endif // WITH_EDITOR - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniRuntimeSettings.h" + + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Misc/Paths.h" +// #include "Internationalization/Internationalization.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + + +FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() +{ + bGeneratedDoubleSidedGeometry = false; + GeneratedPhysMaterial = nullptr; + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); + GeneratedCollisionTraceFlag = CTF_UseDefault; + GeneratedLpvBiasMultiplier = 1.0f; + GeneratedLightMapResolution = 64; + GeneratedLightMapCoordinateIndex = 1; + bGeneratedUseMaximumStreamingTexelRatio = false; + GeneratedStreamingDistanceMultiplier = 1.0f; + GeneratedDistanceFieldResolutionScale = 2.0f; + + GeneratedWalkableSlopeOverride; + GeneratedFoliageDefaultSettings; + GeneratedAssetUserData; +} + + +UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // Session options. + SessionType = HRSST_NamedPipe; + ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; + ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; + ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; + bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; + AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; + + bSyncWithHoudiniCook = true; + bCookUsingHoudiniTime = true; + bSyncViewport = false; + bSyncHoudiniViewport = false; + bSyncUnrealViewport = false; + + // Instantiating options. + bShowMultiAssetDialog = true; + + // Cooking options. + bPauseCookingOnStart = false; + bDisplaySlateCookingNotifications = true; + DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + // Parameter options + //bTreatRampParametersAsMultiparms = false; + + // Custom Houdini location. + bUseCustomHoudiniLocation = false; + CustomHoudiniLocation.Path = TEXT(""); + + // Arguments for HAPI_Initialize + CookingThreadStackSize = -1; + + // Landscape marshalling default values. + MarshallingLandscapesUseDefaultUnrealScaling = false; + MarshallingLandscapesUseFullResolution = true; + MarshallingLandscapesForceMinMaxValues = false; + MarshallingLandscapesForcedMinValue = -2000.0f; + MarshallingLandscapesForcedMaxValue = 4553.0f; + + // Spline marshalling + MarshallingSplineResolution = 50.0f; + + // Static mesh proxy refinement settings + bEnableProxyStaticMesh = false; + bShowDefaultMesh = true; + bEnableProxyStaticMeshRefinementByTimer = true; + ProxyMeshAutoRefineTimeoutSeconds = 10.0f; + bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; + bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; + + // Generated StaticMesh settings. + bDoubleSidedGeometry = false; + PhysMaterial = nullptr; + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); + CollisionTraceFlag = CTF_UseDefault; + LightMapResolution = 32; + LpvBiasMultiplier = 1.0f; + LightMapCoordinateIndex = 1; + bUseMaximumStreamingTexelRatio = false; + StreamingDistanceMultiplier = 1.0f; + GeneratedDistanceFieldResolutionScale = 0.0f; + + // Static Mesh build settings. + bUseFullPrecisionUVs = false; + SrcLightmapIndex = 0; + DstLightmapIndex = 1; + MinLightmapResolution = 64; + bRemoveDegenerates = true; + GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; + RecomputeNormalsFlag = HRSRF_OnlyIfMissing; + RecomputeTangentsFlag = HRSRF_OnlyIfMissing; + bUseMikkTSpace = true; + bBuildAdjacencyBuffer = true; // v1 default false + + bComputeWeightedNormals = false; + bBuildReversedIndexBuffer = true; + bUseHighPrecisionTangentBasis = false; + bGenerateDistanceFieldAsIfTwoSided = false; + bSupportFaceRemap = false; + //BuildScale3D = FVector(1.0f, 1.0f, 1.0f); + DistanceFieldResolutionScale = 2.0f; // ue default is 1.0 + + bPDGAsyncCommandletImportEnabled = false; + + // Legacy settings + bEnableBackwardCompatibility = true; + bAutomaticLegacyHDARebuild = false; +} + +UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() +{} + + +FProperty * +UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const +{ + for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) + { + FProperty * Property = *PropIt; + + if (Property->GetNameCPP() == PropertyName) + return Property; + } + + return nullptr; +} + + +void +UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) +{ + FProperty * Property = LocateProperty(PropertyName); + if (Property) + { + if (bReadOnly) + Property->SetPropertyFlags(CPF_EditConst); + else + Property->ClearPropertyFlags(CPF_EditConst); + } +} + + +void +UHoudiniRuntimeSettings::PostInitProperties() +{ + Super::PostInitProperties(); + + // Set Collision generation options as read only + { + if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Set marshalling attributes options as read only + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + /* + // Set Cook Folder as read-only + { + if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + } + */ + + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + Property->SetPropertyFlags(CPF_EditConst); + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Disable UI elements depending on current session type. +#if WITH_EDITOR + + UpdateSessionUI(); + +#endif // WITH_EDITOR + + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); +} + + +#if WITH_EDITOR + +void +UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + FProperty * LookupProperty = nullptr; + + if (!Property) + return; + if (Property->GetName() == TEXT("SessionType")) + UpdateSessionUI(); + else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); + else if (Property->GetName() == TEXT("CustomHoudiniLocation")) + { + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; + FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); + + // If path does not point to libHAPI location, we need to let user know. + if (!FPaths::FileExists(LibHAPICustomPath)) + { + FString MessageString = FString::Printf( + TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); + + FPlatformMisc::MessageBoxExt( + EAppMsgType::Ok, *MessageString, + TEXT("Invalid Custom Location Specified, resetting.")); + + CustomHoudiniLocationPath = TEXT(""); + } + } + else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) + { + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->SetPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->SetPropertyFlags(CPF_EditConst); + } + else + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->ClearPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->ClearPropertyFlags(CPF_EditConst); + } + } + + /* + if ( Property->GetName() == TEXT( "bEnableCooking" ) ) + { + // Cooking is disabled, we need to disable transform change triggers cooks option is as well. + if ( bEnableCooking ) + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) + { + // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. + if ( bUploadTransformsToHoudiniEngine ) + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + */ +} + + + +void +UHoudiniRuntimeSettings::UpdateSessionUI() +{ + SetPropertyReadOnly(TEXT("ServerHost"), true); + SetPropertyReadOnly(TEXT("ServerPort"), true); + SetPropertyReadOnly(TEXT("ServerPipeName"), true); + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); + + bool bServerType = false; + + switch (SessionType) + { + case HRSST_Socket: + { + SetPropertyReadOnly(TEXT("ServerHost"), false); + SetPropertyReadOnly(TEXT("ServerPort"), false); + bServerType = true; + break; + } + + case HRSST_NamedPipe: + { + SetPropertyReadOnly(TEXT("ServerPipeName"), false); + bServerType = true; + break; + } + + default: + break; + } + + if (bServerType) + { + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); + } +} + +#endif // WITH_EDITOR + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h index 89df901c0..62845537d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h @@ -1,277 +1,491 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Object.h" -#include "Engine/EngineTypes.h" - -#include "HoudiniRuntimeSettings.generated.h" - -UENUM() -enum EHoudiniRuntimeSettingsSessionType -{ - // In process session. - HRSST_InProcess UMETA(Hidden), - - // TCP socket connection to Houdini Engine server. - HRSST_Socket UMETA(DisplayName = "TCP socket"), - - // Connection to Houdini Engine server via pipe connection. - HRSST_NamedPipe UMETA(DisplayName = "Named pipe or domain socket"), - - // No session, prevents license/Engine cook - HRSST_None UMETA(DisplayName = "None"), - - HRSST_MAX -}; - -UCLASS(config = Engine, defaultconfig) -class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject -{ - GENERATED_UCLASS_BODY() - - public: - - // Destructor. - virtual ~UHoudiniRuntimeSettings(); - - // - virtual void PostInitProperties() override; - -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; -#endif - -protected: - - // Locate property of this class by name. - FProperty * LocateProperty(const FString & PropertyName) const; - - // Make specified property read only. - void SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly = true); - -#if WITH_EDITOR - // Update session ui elements. - void UpdateSessionUI(); -#endif - - public: - - //------------------------------------------------------------------------------------------------------------- - // Session options. - //------------------------------------------------------------------------------------------------------------- - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - TEnumAsByte SessionType; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - FString ServerHost; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - int32 ServerPort; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - FString ServerPipeName; - - // Whether to automatically start a HARS process - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - bool bStartAutomaticServer; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - float AutomaticServerTimeout; - - // If enabled, changes made in Houdini, when connected to Houdini running in Session Sync mode will be automatically be pushed to Unreal. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bSyncWithHoudiniCook; - - // If enabled, the Houdini Timeline time will be used to cook assets. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bCookUsingHoudiniTime; - - // Enable when wanting to sync the Houdini and Unreal viewport when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bSyncViewport; - - // If enabled, Houdini's viewport will be synchronized to Unreal's when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Houdini Viewport to Unreal's viewport.", EditCondition = "bSyncViewport")) - bool bSyncHoudiniViewport; - - // If enabled, Unreal's viewport will be synchronized to Houdini's when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Unreal Viewport to Houdini's viewport", EditCondition = "bSyncViewport")) - bool bSyncUnrealViewport; - - //------------------------------------------------------------------------------------------------------------- - // Instantiating options. - //------------------------------------------------------------------------------------------------------------- - - // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. - // TODO: PORT THE DIALOG!! - UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) - bool bShowMultiAssetDialog; - - //------------------------------------------------------------------------------------------------------------- - // Cooking options. - //------------------------------------------------------------------------------------------------------------- - - // Whether houdini engine cooking is paused or not upon initializing the plugin - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - bool bPauseCookingOnStart; - - // Whether to display instantiation and cooking Slate notifications. - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - bool bDisplaySlateCookingNotifications; - - // Default content folder storing all the temporary cook data (Static meshes, materials, textures, landscape layer infos...) - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - FString DefaultTemporaryCookFolder; - - // Default content folder used when baking houdini asset data to native unreal objects - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - FString DefaultBakeFolder; - - //------------------------------------------------------------------------------------------------------------- - // Parameter options. - //------------------------------------------------------------------------------------------------------------- - - /* Deprecated! - // Forces the treatment of ramp parameters as multiparms. - UPROPERTY(GlobalConfig, EditAnywhere, Category = Parameters) - bool bTreatRampParametersAsMultiparms; - */ - - //------------------------------------------------------------------------------------------------------------- - // Geometry Marshalling - //------------------------------------------------------------------------------------------------------------- - - // If true, generated Landscapes will be marshalled using default unreal scaling. - // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms - // as Unreal's default landscape - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - bool MarshallingLandscapesUseDefaultUnrealScaling; - // If true, generated Landscapes will be using full precision for their ZAxis, - // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - bool MarshallingLandscapesUseFullResolution; - // If true, the min/max values used to convert heightfields to landscape will be forced values - // This is usefull when importing multiple landscapes from different HDAs - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - bool MarshallingLandscapesForceMinMaxValues; - // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - float MarshallingLandscapesForcedMinValue; - // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - float MarshallingLandscapesForcedMaxValue; - - // Default resolution used when converting Unreal Spline Components to Houdini Curves (step in cm between control points, 0 only send the control points) - UPROPERTY(GlobalConfig, EditAnywhere, Category = GeometryMarshalling) - float MarshallingSplineResolution; - - //------------------------------------------------------------------------------------------------------------- - // Static Mesh Options - //------------------------------------------------------------------------------------------------------------- - - // For StaticMesh outputs: should a fast proxy be created first? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Enable Proxy Static Mesh")) - bool bEnableProxyStaticMesh; - - // For static mesh outputs and socket actors: should spawn a default actor if the reference is invalid? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Show Default Mesh")) - bool bShowDefaultMesh; - - // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes After a Timeout", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementByTimer; - - // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bEnableProxyStaticMesh && bEnableProxyStaticMeshRefinementByTimer")) - float ProxyMeshAutoRefineTimeoutSeconds; - - // Automatically refine proxy meshes to UStaticMesh before the map is saved - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes When Saving a Map", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementOnPreSaveWorld; - - // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes On PIE", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementOnPreBeginPIE; - - //------------------------------------------------------------------------------------------------------------- - // Legacy - //------------------------------------------------------------------------------------------------------------- - // Whether to enable backward compatibility - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", Meta = (DisplayName = "Enable backward compatibility with Version 1")) - bool bEnableBackwardCompatibility; - - // Automatically rebuild legacy HAC - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", meta = (DisplayName = "Automatically rebuild legacy Houdini Asset Components", EditCondition = "bEnableBackwardCompatibility")) - bool bAutomaticLegacyHDARebuild; - - //------------------------------------------------------------------------------------------------------------- - // Custom Houdini Location - //------------------------------------------------------------------------------------------------------------- - // Whether to use custom Houdini location. - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Use custom Houdini location (requires restart)")) - bool bUseCustomHoudiniLocation; - - // Custom Houdini location (where HAPI library is located). - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Custom Houdini location")) - FDirectoryPath CustomHoudiniLocation; - - //------------------------------------------------------------------------------------------------------------- - // HAPI_Initialize - //------------------------------------------------------------------------------------------------------------- - // Evaluation thread stack size in bytes. -1 for default - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - int32 CookingThreadStackSize; - - // List of paths to Houdini-compatible .env files (; separated on Windows, : otherwise) - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString HoudiniEnvironmentFiles; - - // Path to find other OTL/HDA files - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString OtlSearchPath; - - // Sets HOUDINI_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString DsoSearchPath; - - // Sets HOUDINI_IMAGE_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString ImageDsoSearchPath; - - // Sets HOUDINI_AUDIO_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString AudioDsoSearchPath; - - //------------------------------------------------------------------------------------------------------------- - // PDG Commandlet import - //------------------------------------------------------------------------------------------------------------- - // Is the PDG commandlet enabled? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta=(DisplayName="Async Importer Enabled")) - bool bPDGAsyncCommandletImportEnabled; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" +#include "Engine/AssetUserData.h" +#include "PhysicsEngine/BodyInstance.h" + +#include "HoudiniRuntimeSettings.generated.h" + +class UFoliageType_InstancedStaticMesh; + +UENUM() +enum EHoudiniRuntimeSettingsSessionType +{ + // In process session. + HRSST_InProcess UMETA(Hidden), + + // TCP socket connection to Houdini Engine server. + HRSST_Socket UMETA(DisplayName = "TCP socket"), + + // Connection to Houdini Engine server via pipe connection. + HRSST_NamedPipe UMETA(DisplayName = "Named pipe or domain socket"), + + // No session, prevents license/Engine cook + HRSST_None UMETA(DisplayName = "None"), + + HRSST_MAX +}; + + +UENUM() +enum EHoudiniRuntimeSettingsRecomputeFlag +{ + // Recompute always. + HRSRF_Always UMETA(DisplayName = "Always"), + + // Recompute only if missing. + HRSRF_OnlyIfMissing UMETA(DisplayName = "Only if missing"), + + // Do not recompute. + HRSRF_Never UMETA(DisplayName = "Never"), + + HRSRF_MAX, +}; + +USTRUCT(BlueprintType) +struct HOUDINIENGINERUNTIME_API FHoudiniStaticMeshGenerationProperties +{ + GENERATED_USTRUCT_BODY() + + // Constructor + FHoudiniStaticMeshGenerationProperties(); + + public: + + /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Double Sided Geometry")) + uint32 bGeneratedDoubleSidedGeometry : 1; + + /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * GeneratedPhysMaterial; + + /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Collision Complexity")) + TEnumAsByte GeneratedCollisionTraceFlag; + + /** Resolution of lightmap. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 GeneratedLightMapResolution; + + /** Bias multiplier for Light Propagation Volume lighting. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) + float GeneratedLpvBiasMultiplier; + + /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /** Custom walkable slope setting for generated mesh's body. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride GeneratedWalkableSlopeOverride; + + /** The light map coordinate index. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light map coordinate index")) + int32 GeneratedLightMapCoordinateIndex; + + /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; + + /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Streaming Distance Multiplier")) + float GeneratedStreamingDistanceMultiplier; + + /** Default settings when using this mesh for instanced foliage. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh* GeneratedFoliageDefaultSettings; + + /** Array of user data stored with the asset. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Asset User Data")) + TArray GeneratedAssetUserData; +}; + + +UCLASS(config = Engine, defaultconfig) +class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject +{ + GENERATED_UCLASS_BODY() + + public: + + // Destructor. + virtual ~UHoudiniRuntimeSettings(); + + // + virtual void PostInitProperties() override; + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; +#endif + +protected: + + // Locate property of this class by name. + FProperty * LocateProperty(const FString & PropertyName) const; + + // Make specified property read only. + void SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly = true); + +#if WITH_EDITOR + // Update session ui elements. + void UpdateSessionUI(); +#endif + + public: + + //------------------------------------------------------------------------------------------------------------- + // Session options. + //------------------------------------------------------------------------------------------------------------- + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + TEnumAsByte SessionType; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + FString ServerHost; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + int32 ServerPort; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + FString ServerPipeName; + + // Whether to automatically start a HARS process + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + bool bStartAutomaticServer; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + float AutomaticServerTimeout; + + // If enabled, changes made in Houdini, when connected to Houdini running in Session Sync mode will be automatically be pushed to Unreal. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bSyncWithHoudiniCook; + + // If enabled, the Houdini Timeline time will be used to cook assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bCookUsingHoudiniTime; + + // Enable when wanting to sync the Houdini and Unreal viewport when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bSyncViewport; + + // If enabled, Houdini's viewport will be synchronized to Unreal's when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Houdini Viewport to Unreal's viewport.", EditCondition = "bSyncViewport")) + bool bSyncHoudiniViewport; + + // If enabled, Unreal's viewport will be synchronized to Houdini's when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Unreal Viewport to Houdini's viewport", EditCondition = "bSyncViewport")) + bool bSyncUnrealViewport; + + //------------------------------------------------------------------------------------------------------------- + // Instantiating options. + //------------------------------------------------------------------------------------------------------------- + + // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. + // TODO: PORT THE DIALOG!! + UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) + bool bShowMultiAssetDialog; + + //------------------------------------------------------------------------------------------------------------- + // Cooking options. + //------------------------------------------------------------------------------------------------------------- + + // Whether houdini engine cooking is paused or not upon initializing the plugin + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + bool bPauseCookingOnStart; + + // Whether to display instantiation and cooking Slate notifications. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + bool bDisplaySlateCookingNotifications; + + // Default content folder storing all the temporary cook data (Static meshes, materials, textures, landscape layer infos...) + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + FString DefaultTemporaryCookFolder; + + // Default content folder used when baking houdini asset data to native unreal objects + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + FString DefaultBakeFolder; + + //------------------------------------------------------------------------------------------------------------- + // Parameter options. + //------------------------------------------------------------------------------------------------------------- + + /* Deprecated! + // Forces the treatment of ramp parameters as multiparms. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Parameters) + bool bTreatRampParametersAsMultiparms; + */ + + //------------------------------------------------------------------------------------------------------------- + // Geometry Marshalling + //------------------------------------------------------------------------------------------------------------- + + // If true, generated Landscapes will be marshalled using default unreal scaling. + // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms + // as Unreal's default landscape + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + bool MarshallingLandscapesUseDefaultUnrealScaling; + // If true, generated Landscapes will be using full precision for their ZAxis, + // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + bool MarshallingLandscapesUseFullResolution; + // If true, the min/max values used to convert heightfields to landscape will be forced values + // This is usefull when importing multiple landscapes from different HDAs + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + bool MarshallingLandscapesForceMinMaxValues; + // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + float MarshallingLandscapesForcedMinValue; + // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + float MarshallingLandscapesForcedMaxValue; + + // Default resolution used when converting Unreal Spline Components to Houdini Curves (step in cm between control points, 0 only send the control points) + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + float MarshallingSplineResolution; + + //------------------------------------------------------------------------------------------------------------- + // Static Mesh Options + //------------------------------------------------------------------------------------------------------------- + + // For StaticMesh outputs: should a fast proxy be created first? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Enable Proxy Static Mesh")) + bool bEnableProxyStaticMesh; + + // For static mesh outputs and socket actors: should spawn a default actor if the reference is invalid? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Show Default Mesh")) + bool bShowDefaultMesh; + + // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes After a Timeout", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementByTimer; + + // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bEnableProxyStaticMesh && bEnableProxyStaticMeshRefinementByTimer")) + float ProxyMeshAutoRefineTimeoutSeconds; + + // Automatically refine proxy meshes to UStaticMesh before the map is saved + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes When Saving a Map", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementOnPreSaveWorld; + + // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes On PIE", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementOnPreBeginPIE; + + //------------------------------------------------------------------------------------------------------------- + // Generated StaticMesh settings. + //------------------------------------------------------------------------------------------------------------- + /* + UPROPERTY(Category = "GeneratedStaticMeshSettings", EditAnywhere, meta = (ShowOnlyInnerProperties)) + FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; + */ + + /// If true, the physics triangle mesh will use double sided faces for new Houdini Assets when doing scene queries. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Double Sided Geometry")) + uint32 bDoubleSidedGeometry : 1; + + /// Physical material to use for simple collision of new Houdini Assets. Encodes information about density, friction etc. + UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * PhysMaterial; + + /// Default properties of the body instance + UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /// Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate for new Houdini Assets. + UPROPERTY(GlobalConfig, VisibleDefaultsOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Collision Complexity")) + TEnumAsByte CollisionTraceFlag; + + /// Resolution of lightmap for baked lighting. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 LightMapResolution; + + /// Bias multiplier for Light Propagation Volume lighting for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, BlueprintReadOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) + float LpvBiasMultiplier; + + /// Default Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /// Custom walkable slope setting for bodies of new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride WalkableSlopeOverride; + + /// The UV coordinate index of lightmap + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light map coordinate index")) + int32 LightMapCoordinateIndex; + + /// True if mesh should use a less-conservative method of mip LOD texture factor computation for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bUseMaximumStreamingTexelRatio : 1; + + /// Allows artists to adjust the distance where textures using UV 0 are streamed in/out for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Streaming Distance Multiplier")) + float StreamingDistanceMultiplier; + + /// Default settings when using new Houdini Asset mesh for instanced foliage. + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh * FoliageDefaultSettings; + + /// Array of user data stored with the new Houdini Asset. + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Asset User Data")) + TArray AssetUserData; + + //------------------------------------------------------------------------------------------------------------- + // Static Mesh build settings. + //------------------------------------------------------------------------------------------------------------- + + // If true, UVs will be stored at full floating point precision. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bUseFullPrecisionUVs; + + // Source UV set for generated lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Source Lightmap Index")) + int32 SrcLightmapIndex; + + // Destination UV set for generated lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Destination Lightmap Index")) + int32 DstLightmapIndex; + + // Target lightmap resolution to for generated lightmap. Determines the padding between UV shells in a packed lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + int32 MinLightmapResolution; + + // If true, degenerate triangles will be removed. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bRemoveDegenerates; + + // Lightmap UV generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Lightmap UVs")) + TEnumAsByte GenerateLightmapUVsFlag; + + // Normals generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Normals")) + TEnumAsByte RecomputeNormalsFlag; + + // Tangents generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Tangents")) + TEnumAsByte RecomputeTangentsFlag; + + // If true, recomputed tangents and normals will be calculated using MikkT Space. This method does require properly laid out UVs though otherwise you'll get a degenerate tangent warning + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Using MikkT Space")) + bool bUseMikkTSpace; + + // Required for PNT tessellation but can be slow. Recommend disabling for larger meshes. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bBuildAdjacencyBuffer; + + // If true, we will use the surface area and the corner angle of the triangle as a ratio when computing the normals. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings) + uint8 bComputeWeightedNormals : 1; + + // Required to optimize mesh in mirrored transform. Double index buffer size. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings) + uint8 bBuildReversedIndexBuffer : 1; + + // If true, Tangents will be stored at 16 bit vs 8 bit precision. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings) + uint8 bUseHighPrecisionTangentBasis : 1; + + // Scale to apply to the mesh when allocating the distance field volume texture. + // The default scale is 1, which is assuming that the mesh will be placed unscaled in the world. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings) + float DistanceFieldResolutionScale; + + // Whether to generate the distance field treating every triangle hit as a front face. + // When enabled prevents the distance field from being discarded due to the mesh being open, but also lowers Distance Field AO quality. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings, meta = (DisplayName = "Two-Sided Distance Field Generation")) + uint8 bGenerateDistanceFieldAsIfTwoSided : 1; + + // Enable the Physical Material Mask + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings, meta = (DisplayName = "Enable Physical Material Mask")) + uint8 bSupportFaceRemap : 1; + + //------------------------------------------------------------------------------------------------------------- + // Legacy + //------------------------------------------------------------------------------------------------------------- + // Whether to enable backward compatibility + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", Meta = (DisplayName = "Enable backward compatibility with Version 1")) + bool bEnableBackwardCompatibility; + + // Automatically rebuild legacy HAC + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", meta = (DisplayName = "Automatically rebuild legacy Houdini Asset Components", EditCondition = "bEnableBackwardCompatibility")) + bool bAutomaticLegacyHDARebuild; + + //------------------------------------------------------------------------------------------------------------- + // Custom Houdini Location + //------------------------------------------------------------------------------------------------------------- + // Whether to use custom Houdini location. + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Use custom Houdini location (requires restart)")) + bool bUseCustomHoudiniLocation; + + // Custom Houdini location (where HAPI library is located). + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Custom Houdini location")) + FDirectoryPath CustomHoudiniLocation; + + //------------------------------------------------------------------------------------------------------------- + // HAPI_Initialize + //------------------------------------------------------------------------------------------------------------- + // Evaluation thread stack size in bytes. -1 for default + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + int32 CookingThreadStackSize; + + // List of paths to Houdini-compatible .env files (; separated on Windows, : otherwise) + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString HoudiniEnvironmentFiles; + + // Path to find other OTL/HDA files + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString OtlSearchPath; + + // Sets HOUDINI_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString DsoSearchPath; + + // Sets HOUDINI_IMAGE_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString ImageDsoSearchPath; + + // Sets HOUDINI_AUDIO_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString AudioDsoSearchPath; + + //------------------------------------------------------------------------------------------------------------- + // PDG Commandlet import + //------------------------------------------------------------------------------------------------------------- + // Is the PDG commandlet enabled? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta=(DisplayName="Async Importer Enabled")) + bool bPDGAsyncCommandletImportEnabled; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp index 47a0b2e14..2e44476a0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp @@ -1,689 +1,689 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineComponent.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniInput.h" -#include "HoudiniInputObject.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Components/MeshComponent.h" -#include "Algo/Reverse.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -void -UHoudiniSplineComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on HOUDINI_ENGINE_ENABLE_BACKWARD_COMPATIBILITY - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniSplineComponent_V1* CompatibilitySC = NewObject(); - CompatibilitySC->Serialize(Ar); - CompatibilitySC->UpdateFromLegacyData(this); - - Construct(CompatibilitySC->CurveDisplayPoints); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -UHoudiniSplineComponent::UHoudiniSplineComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , bClosed(false) - , bReversed(false) - , bIsHoudiniSplineVisible(true) - , CurveType(EHoudiniCurveType::Polygon) - , CurveMethod(EHoudiniCurveMethod::CVs) - , bHasChanged(false) - , bNeedsToTriggerUpdate(false) - , bIsInputCurve(false) - , bIsEditableOutputCurve(false) - , NodeId(-1) -{ - - // Add two default points to the curve - FTransform defaultPoint = FTransform::Identity; - - // Set this component to not tick? - // SetComponentTickEnabled(false); - - // Default curve. - CurvePoints.Add(defaultPoint); - DisplayPoints.Add(defaultPoint.GetLocation()); - - defaultPoint.SetTranslation(FVector(200.f, 0.f, 0.f)); - CurvePoints.Add(defaultPoint); - DisplayPoints.Add(defaultPoint.GetLocation()); - - bIsOutputCurve = false; - bCookOnCurveChanged = true; - -#if WITH_EDITOR - bPostUndo = false; -#endif -} - -void -UHoudiniSplineComponent::Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint) -{ - DisplayPoints.Empty(); - DisplayPointIndexDivider.Empty(); - - float DisplayPointStepSize; - - // Resample the display points for linear curve. - - if (InCurveDisplayPoints.Num() <= 0) - return; - - // Add an additional displaypoint to the end for closed curve - if (bClosed && InCurveDisplayPoints.Num() > 2) - { - FVector & FirstPoint = InCurveDisplayPoints[0]; - FVector ClosingPoint; - ClosingPoint.X = FirstPoint.X; - ClosingPoint.Y = FirstPoint.Y; - ClosingPoint.Z = FirstPoint.Z; - - InCurveDisplayPoints.Add(ClosingPoint); - } - - - if (CurveType == EHoudiniCurveType::Polygon) - { - FVector Pt1, Pt2; - Pt1 = InCurveDisplayPoints[0]; - int32 CurrentDisplayPointIndex = 0; - - for (int Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) - { - DisplayPointStepSize = 10.f; - - Pt2 = InCurveDisplayPoints[Index]; - - FVector Direction = Pt2 - Pt1; - - float SegmentLength = Direction.Size(); - - int32 NumOfDisplayPt = SegmentLength / DisplayPointStepSize; - - // Make sure there are at least 20 display points on a segment. - while( NumOfDisplayPt < 20 && SegmentLength > 0.01) - { - DisplayPointStepSize /= 2.f; - NumOfDisplayPt = SegmentLength / DisplayPointStepSize; - } - - Direction.Normalize(0.01f); - - FVector StepVector = Direction * DisplayPointStepSize; - - // Always add the start point of a line segment - FVector NextDisplayPt = Pt1; - if (NumOfDisplayPt == 0) DisplayPoints.Add(NextDisplayPt); - - for (int32 itr = 0; itr < NumOfDisplayPt; ++itr) - { - DisplayPoints.Add(NextDisplayPt); - NextDisplayPt += StepVector; - CurrentDisplayPointIndex += 1; - } - - DisplayPointIndexDivider.Add(CurrentDisplayPointIndex); - - Pt1 = Pt2; - } - - // Add the ending point - DisplayPoints.Add(Pt1); - // Duplicate the last index, to make the DisplaPointyIndexDivider array matches the length of DP array - DisplayPointIndexDivider.Add(CurrentDisplayPointIndex + 1); - } - else if (CurveType == EHoudiniCurveType::Points) - { - // do not add display points for Points curve type, just show the CVs - } - else - { - // Needs a better algorithm to divide the display points - // Refined display points does not strictly interpolate the curve points. - - FVector Pt1, Pt2; - Pt1 = InCurveDisplayPoints[0]; - - int Itr = 1; - - int32 ClosestIndex = -1; - float ClosestDistance = -1.f; - - for (int32 Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) - { - if (Itr >= CurvePoints.Num()) break; - - Pt2 = InCurveDisplayPoints[Index]; - - FVector ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); - - float Distance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); - - if (ClosestDistance < 0.f || Distance < ClosestDistance) - { - ClosestDistance = Distance; - ClosestIndex = Index; - } - else - { - Itr += 1; - if (Itr >= CurvePoints.Num()) break; - - DisplayPointIndexDivider.Add(Index-1); - - ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); - ClosestDistance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); - - } - } - - DisplayPointIndexDivider.Add(InCurveDisplayPoints.Num()); - - DisplayPoints = InCurveDisplayPoints; - } -} - - -void -UHoudiniSplineComponent::CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent) -{ - if (!OtherHoudiniSplineComponent) - return; - - CurvePoints = OtherHoudiniSplineComponent->CurvePoints; - DisplayPoints = OtherHoudiniSplineComponent->DisplayPoints; - DisplayPointIndexDivider = OtherHoudiniSplineComponent->DisplayPointIndexDivider; - CurveType = OtherHoudiniSplineComponent->CurveType; - CurveMethod = OtherHoudiniSplineComponent->CurveMethod; - bClosed = OtherHoudiniSplineComponent->bClosed; -#if WITH_EDITORONLY_DATA - bVisualizeComponent = OtherHoudiniSplineComponent->bVisualizeComponent; -#endif - bReversed = OtherHoudiniSplineComponent->bReversed; - SetVisibility(OtherHoudiniSplineComponent->IsVisible()); - - HoudiniSplineName = OtherHoudiniSplineComponent->HoudiniSplineName; -} - -UHoudiniSplineComponent::~UHoudiniSplineComponent() -{} - -void -UHoudiniSplineComponent::AppendPoint(const FTransform& NewPoint) -{ - CurvePoints.Add(NewPoint); -} - -void -UHoudiniSplineComponent::InsertPointAtIndex(const FTransform& NewPoint, const int32& Index) -{ - check(Index >= 0 && Index < CurvePoints.Num()); - CurvePoints.Insert(NewPoint, Index); - bHasChanged = true; -} - - -void -UHoudiniSplineComponent::RemovePointAtIndex(const int32& Index) -{ - check(Index >= 0 && Index < CurvePoints.Num()); - CurvePoints.RemoveAt(Index); - bHasChanged = true; -} - -void -UHoudiniSplineComponent::SetReversed(const bool& InReversed) -{ - // don't need to do anything if the reversed state doesn't change. - if (InReversed == bReversed) - return; - - bReversed = InReversed; - ReverseCurvePoints(); - MarkChanged(true); -} - -void -UHoudiniSplineComponent::ReverseCurvePoints() -{ - if (CurvePoints.Num() < 2) - return; - - Algo::Reverse(CurvePoints); -} - -void -UHoudiniSplineComponent::EditPointAtindex(const FTransform& NewPoint, const int32& Index) -{ - if (!CurvePoints.IsValidIndex(Index)) - return; - - CurvePoints[Index] = NewPoint; - bHasChanged = true; -} - -#if WITH_EDITOR -void -UHoudiniSplineComponent::PostEditChangeProperty(FPropertyChangedEvent& PeopertyChangedEvent) -{ - Super::PostEditChangeProperty(PeopertyChangedEvent); - - FName PropertyName = (PeopertyChangedEvent.Property != nullptr) ? PeopertyChangedEvent.Property->GetFName() : NAME_None; - - // Responses to the uproperty changes - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bClosed)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bClosed")); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bReversed)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bReversed")); - ReverseCurvePoints(); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveType)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveType")); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveMethod)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveMethod")); - MarkChanged(true); - } -} -#endif - -void -UHoudiniSplineComponent::PostLoad() -{ - Super::PostLoad(); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::PostLoad()] Component: %s"), *GetPathName()); - -} - -TStructOnScope -UHoudiniSplineComponent::GetComponentInstanceData() const -{ - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::GetComponentInstanceData()] Component: %s"), *GetPathName()); - TStructOnScope ComponentInstanceData = MakeStructOnScope(this); - FHoudiniSplineComponentInstanceData* InstanceData = ComponentInstanceData.Cast(); - - - // NOTE: We need to capture these properties here before the component gets torn down - // since the Spline visualizer changed values on the instance directly and is not present on the - // template yet. - /*InstanceData->CurvePoints = CurvePoints; - InstanceData->DisplayPoints = DisplayPoints; - InstanceData->DisplayPointIndexDivider = DisplayPointIndexDivider; - -#if WITH_EDITOR_DATA - InstanceData->EditedControlPointsIndexes = EditedControlPointsIndexes; -#endif*/ - - return ComponentInstanceData; -} - -void -UHoudiniSplineComponent::ApplyComponentInstanceData(FHoudiniSplineComponentInstanceData* ComponentInstanceData, - const bool bPostUCS) -{ - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %s"), *GetPathName()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %p"), this); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] bHiddenInGame: %d"), bHiddenInGame); - - check(ComponentInstanceData); - - if (!bPostUCS) - { - //bHasChanged = ComponentInstanceData->bHasChanged; - //bNeedsToTriggerUpdate = ComponentInstanceData->bNeedsToTriggerUpdate; - /*CurvePoints = ComponentInstanceData->CurvePoints; - DisplayPoints = ComponentInstanceData->DisplayPoints; - DisplayPointIndexDivider = ComponentInstanceData->DisplayPointIndexDivider; - -#if WITH_EDITOR_DATA - EditedControlPointsIndexes = ComponentInstanceData->EditedControlPointsIndexes; -#endif*/ - } -} - -void -UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) -{ - // Capture properties that we want to preserve during copy - const int32 PrevNodeId = NodeId; - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - bHiddenInGame: %d"), bHiddenInGame); - - UActorComponent* FromComponent = Cast(FromObject); - check(FromComponent); - - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - //Params.bDoDelta = false; // Perform a deep copy - //Params.bClearReferences = false; - //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, this, Params); - - /*const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); - FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, this, ComponentCopyOptions);*/ - - UHoudiniSplineComponent* FromSplineComponent = Cast(FromObject); - if (FromSplineComponent) - { - CurvePoints = FromSplineComponent->CurvePoints; - DisplayPoints = FromSplineComponent->DisplayPoints; - DisplayPointIndexDivider = FromSplineComponent->DisplayPointIndexDivider; -#if WITH_EDITORONLY_DATA - EditedControlPointsIndexes = FromSplineComponent->EditedControlPointsIndexes; -#endif - - HoudiniSplineName = FromSplineComponent->HoudiniSplineName; - bClosed = FromSplineComponent->bClosed; - bIsHoudiniSplineVisible = FromSplineComponent->bIsHoudiniSplineVisible; - CurveType = FromSplineComponent->CurveType; - CurveMethod = FromSplineComponent->CurveMethod; - bIsInputCurve = FromSplineComponent->bIsInputCurve; - bIsOutputCurve = FromSplineComponent->bIsOutputCurve; - bIsEditableOutputCurve = FromSplineComponent->bIsEditableOutputCurve; - bCookOnCurveChanged = FromSplineComponent->bCookOnCurveChanged; - - - - bHasChanged = FromSplineComponent->bHasChanged; - bNeedsToTriggerUpdate = FromSplineComponent->bNeedsToTriggerUpdate; - } - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - bHiddenInGame: %d"), bHiddenInGame); - - // Restore properties that we want to preserve - NodeId = PrevNodeId; -} - - -void -UHoudiniSplineComponent::OnUnregister() -{ - Super::OnUnregister(); -} - -void -UHoudiniSplineComponent::OnComponentCreated() -{ - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] Component: %s"), *GetPathName()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bHiddenInGame: %d"), bHiddenInGame); - Super::OnComponentCreated(); -} - -void -UHoudiniSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - Super::OnComponentDestroyed(bDestroyingHierarchy); - - if (IsInputCurve()) - { - // This component can't just come out of nowhere and decide to delete an input object! - // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! - - // InputObject->MarkPendingKill(); - - // if(NodeId > -1) - // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); - - SetNodeId(-1); // Set nodeId to invalid for reconstruct on re-do - } -} - - -#if WITH_EDITOR -void -UHoudiniSplineComponent::PostEditUndo() -{ - Super::PostEditUndo(); - - bPostUndo = true; - - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return; - // - // CurrentInputObject->MarkChanged(true); - // } - // - // if (bIsEditableOutputCurve) - // { - // MarkChanged(true); - // } - - MarkChanged(true); - -} -#endif - -void -UHoudiniSplineComponent::SetOffset(const float& Offset) -{ - for (int n = 0; n < CurvePoints.Num(); ++n) - CurvePoints[n].AddToTranslation(FVector(0.f, Offset, 0.f)); - - for (int n = 0; n < DisplayPoints.Num(); ++n) - DisplayPoints[n] += FVector(0.f, Offset, 0.f); -} - -void -UHoudiniSplineComponent::ResetCurvePoints() -{ - CurvePoints.Empty(); -} - -void -UHoudiniSplineComponent::ResetDisplayPoints() -{ - DisplayPoints.Empty(); -} - -void -UHoudiniSplineComponent::AddCurvePoints(const TArray& Points) -{ - CurvePoints.Append(Points); -} - -void -UHoudiniSplineComponent::AddDisplayPoints(const TArray& Points) -{ - DisplayPoints.Append(Points); -} - -bool -UHoudiniSplineComponent::NeedsToTriggerUpdate() const -{ - return bNeedsToTriggerUpdate; - - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return false; - // - // return CurrentInputObject->NeedsToTriggerUpdate(); - // } - // - // if (bIsEditableOutputCurve) - // { - // return bNeedsToTriggerUpdate; - // } - // - // return false; -} - -void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate) -{ - bNeedsToTriggerUpdate = NeedsToTriggerUpdate; -} - -void UHoudiniSplineComponent::SetCurveType(const EHoudiniCurveType & NewCurveType) -{ - CurveType = NewCurveType; -#if WITH_EDITOR - //FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(this, TEXT("CurveType")); -#endif -} - -void -UHoudiniSplineComponent::MarkInputObjectChanged() -{ - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return; - // - // if (HasChanged()) - // CurrentInputObject->MarkChanged(true); - // } - // - // if (bIsEditableOutputCurve) - // { - // if (HasChanged()) - // MarkChanged(true); - // } - - // NOTE: This component should be trying to push ANY state changes to Input or Output objects. This - // component should strictly be a data container. Input / Output objects that reference this component should - // be polling this component's state. - MarkChanged(true); -} - -bool UHoudiniSplineComponent::HasChanged() const -{ - return bHasChanged; -} - -void UHoudiniSplineComponent::MarkChanged(const bool& Changed) -{ - bHasChanged = Changed; - bNeedsToTriggerUpdate = Changed; -} - -// UHoudiniAssetComponent* -// UHoudiniSplineComponent::GetParentHAC() -// { -// UHoudiniAssetComponent* ParentHAC = nullptr; -// if (bIsInputCurve) -// { -// if (!InputObject) -// return nullptr; -// -// UHoudiniInput* Input = Cast(InputObject->GetOuter()); -// if (!Input) -// return nullptr; -// -// ParentHAC = Cast(Input->GetOuter()); -// } -// else -// { -// // may do something else if this is not an input curve instead of returning Null. -// } -// -// return ParentHAC; -// -// } - -FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() -{ -} - -FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent) - : FActorComponentInstanceData(SourceComponent) -{ -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniInput.h" +#include "HoudiniInputObject.h" +#include "HoudiniPluginSerializationVersion.h" + +#include "Components/MeshComponent.h" +#include "Algo/Reverse.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +void +UHoudiniSplineComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on HOUDINI_ENGINE_ENABLE_BACKWARD_COMPATIBILITY + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniSplineComponent_V1* CompatibilitySC = NewObject(); + CompatibilitySC->Serialize(Ar); + CompatibilitySC->UpdateFromLegacyData(this); + + Construct(CompatibilitySC->CurveDisplayPoints); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +UHoudiniSplineComponent::UHoudiniSplineComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , bClosed(false) + , bReversed(false) + , bIsHoudiniSplineVisible(true) + , CurveType(EHoudiniCurveType::Polygon) + , CurveMethod(EHoudiniCurveMethod::CVs) + , bHasChanged(false) + , bNeedsToTriggerUpdate(false) + , bIsInputCurve(false) + , bIsEditableOutputCurve(false) + , NodeId(-1) +{ + + // Add two default points to the curve + FTransform defaultPoint = FTransform::Identity; + + // Set this component to not tick? + // SetComponentTickEnabled(false); + + // Default curve. + CurvePoints.Add(defaultPoint); + DisplayPoints.Add(defaultPoint.GetLocation()); + + defaultPoint.SetTranslation(FVector(200.f, 0.f, 0.f)); + CurvePoints.Add(defaultPoint); + DisplayPoints.Add(defaultPoint.GetLocation()); + + bIsOutputCurve = false; + bCookOnCurveChanged = true; + +#if WITH_EDITOR + bPostUndo = false; +#endif +} + +void +UHoudiniSplineComponent::Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint) +{ + DisplayPoints.Empty(); + DisplayPointIndexDivider.Empty(); + + float DisplayPointStepSize; + + // Resample the display points for linear curve. + + if (InCurveDisplayPoints.Num() <= 0) + return; + + // Add an additional displaypoint to the end for closed curve + if (bClosed && InCurveDisplayPoints.Num() > 2) + { + FVector & FirstPoint = InCurveDisplayPoints[0]; + FVector ClosingPoint; + ClosingPoint.X = FirstPoint.X; + ClosingPoint.Y = FirstPoint.Y; + ClosingPoint.Z = FirstPoint.Z; + + InCurveDisplayPoints.Add(ClosingPoint); + } + + + if (CurveType == EHoudiniCurveType::Polygon) + { + FVector Pt1, Pt2; + Pt1 = InCurveDisplayPoints[0]; + int32 CurrentDisplayPointIndex = 0; + + for (int Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) + { + DisplayPointStepSize = 10.f; + + Pt2 = InCurveDisplayPoints[Index]; + + FVector Direction = Pt2 - Pt1; + + float SegmentLength = Direction.Size(); + + int32 NumOfDisplayPt = SegmentLength / DisplayPointStepSize; + + // Make sure there are at least 20 display points on a segment. + while( NumOfDisplayPt < 20 && SegmentLength > 0.01) + { + DisplayPointStepSize /= 2.f; + NumOfDisplayPt = SegmentLength / DisplayPointStepSize; + } + + Direction.Normalize(0.01f); + + FVector StepVector = Direction * DisplayPointStepSize; + + // Always add the start point of a line segment + FVector NextDisplayPt = Pt1; + if (NumOfDisplayPt == 0) DisplayPoints.Add(NextDisplayPt); + + for (int32 itr = 0; itr < NumOfDisplayPt; ++itr) + { + DisplayPoints.Add(NextDisplayPt); + NextDisplayPt += StepVector; + CurrentDisplayPointIndex += 1; + } + + DisplayPointIndexDivider.Add(CurrentDisplayPointIndex); + + Pt1 = Pt2; + } + + // Add the ending point + DisplayPoints.Add(Pt1); + // Duplicate the last index, to make the DisplaPointyIndexDivider array matches the length of DP array + DisplayPointIndexDivider.Add(CurrentDisplayPointIndex + 1); + } + else if (CurveType == EHoudiniCurveType::Points) + { + // do not add display points for Points curve type, just show the CVs + } + else + { + // Needs a better algorithm to divide the display points + // Refined display points does not strictly interpolate the curve points. + + FVector Pt1, Pt2; + Pt1 = InCurveDisplayPoints[0]; + + int Itr = 1; + + int32 ClosestIndex = -1; + float ClosestDistance = -1.f; + + for (int32 Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) + { + if (Itr >= CurvePoints.Num()) break; + + Pt2 = InCurveDisplayPoints[Index]; + + FVector ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); + + float Distance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); + + if (ClosestDistance < 0.f || Distance < ClosestDistance) + { + ClosestDistance = Distance; + ClosestIndex = Index; + } + else + { + Itr += 1; + if (Itr >= CurvePoints.Num()) break; + + DisplayPointIndexDivider.Add(Index-1); + + ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); + ClosestDistance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); + + } + } + + DisplayPointIndexDivider.Add(InCurveDisplayPoints.Num()); + + DisplayPoints = InCurveDisplayPoints; + } +} + + +void +UHoudiniSplineComponent::CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent) +{ + if (!OtherHoudiniSplineComponent) + return; + + CurvePoints = OtherHoudiniSplineComponent->CurvePoints; + DisplayPoints = OtherHoudiniSplineComponent->DisplayPoints; + DisplayPointIndexDivider = OtherHoudiniSplineComponent->DisplayPointIndexDivider; + CurveType = OtherHoudiniSplineComponent->CurveType; + CurveMethod = OtherHoudiniSplineComponent->CurveMethod; + bClosed = OtherHoudiniSplineComponent->bClosed; +#if WITH_EDITORONLY_DATA + bVisualizeComponent = OtherHoudiniSplineComponent->bVisualizeComponent; +#endif + bReversed = OtherHoudiniSplineComponent->bReversed; + SetVisibility(OtherHoudiniSplineComponent->IsVisible()); + + HoudiniSplineName = OtherHoudiniSplineComponent->HoudiniSplineName; +} + +UHoudiniSplineComponent::~UHoudiniSplineComponent() +{} + +void +UHoudiniSplineComponent::AppendPoint(const FTransform& NewPoint) +{ + CurvePoints.Add(NewPoint); +} + +void +UHoudiniSplineComponent::InsertPointAtIndex(const FTransform& NewPoint, const int32& Index) +{ + check(Index >= 0 && Index < CurvePoints.Num()); + CurvePoints.Insert(NewPoint, Index); + bHasChanged = true; +} + + +void +UHoudiniSplineComponent::RemovePointAtIndex(const int32& Index) +{ + check(Index >= 0 && Index < CurvePoints.Num()); + CurvePoints.RemoveAt(Index); + bHasChanged = true; +} + +void +UHoudiniSplineComponent::SetReversed(const bool& InReversed) +{ + // don't need to do anything if the reversed state doesn't change. + if (InReversed == bReversed) + return; + + bReversed = InReversed; + ReverseCurvePoints(); + MarkChanged(true); +} + +void +UHoudiniSplineComponent::ReverseCurvePoints() +{ + if (CurvePoints.Num() < 2) + return; + + Algo::Reverse(CurvePoints); +} + +void +UHoudiniSplineComponent::EditPointAtindex(const FTransform& NewPoint, const int32& Index) +{ + if (!CurvePoints.IsValidIndex(Index)) + return; + + CurvePoints[Index] = NewPoint; + bHasChanged = true; +} + +#if WITH_EDITOR +void +UHoudiniSplineComponent::PostEditChangeProperty(FPropertyChangedEvent& PeopertyChangedEvent) +{ + Super::PostEditChangeProperty(PeopertyChangedEvent); + + FName PropertyName = (PeopertyChangedEvent.Property != nullptr) ? PeopertyChangedEvent.Property->GetFName() : NAME_None; + + // Responses to the uproperty changes + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bClosed)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bClosed")); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bReversed)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bReversed")); + ReverseCurvePoints(); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveType)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveType")); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveMethod)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveMethod")); + MarkChanged(true); + } +} +#endif + +void +UHoudiniSplineComponent::PostLoad() +{ + Super::PostLoad(); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::PostLoad()] Component: %s"), *GetPathName()); + +} + +TStructOnScope +UHoudiniSplineComponent::GetComponentInstanceData() const +{ + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::GetComponentInstanceData()] Component: %s"), *GetPathName()); + TStructOnScope ComponentInstanceData = MakeStructOnScope(this); + FHoudiniSplineComponentInstanceData* InstanceData = ComponentInstanceData.Cast(); + + + // NOTE: We need to capture these properties here before the component gets torn down + // since the Spline visualizer changed values on the instance directly and is not present on the + // template yet. + /*InstanceData->CurvePoints = CurvePoints; + InstanceData->DisplayPoints = DisplayPoints; + InstanceData->DisplayPointIndexDivider = DisplayPointIndexDivider; + +#if WITH_EDITOR_DATA + InstanceData->EditedControlPointsIndexes = EditedControlPointsIndexes; +#endif*/ + + return ComponentInstanceData; +} + +void +UHoudiniSplineComponent::ApplyComponentInstanceData(FHoudiniSplineComponentInstanceData* ComponentInstanceData, + const bool bPostUCS) +{ + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %s"), *GetPathName()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %p"), this); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] IsVisible: %d"), IsVisible()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] bHiddenInGame: %d"), bHiddenInGame); + + check(ComponentInstanceData); + + if (!bPostUCS) + { + //bHasChanged = ComponentInstanceData->bHasChanged; + //bNeedsToTriggerUpdate = ComponentInstanceData->bNeedsToTriggerUpdate; + /*CurvePoints = ComponentInstanceData->CurvePoints; + DisplayPoints = ComponentInstanceData->DisplayPoints; + DisplayPointIndexDivider = ComponentInstanceData->DisplayPointIndexDivider; + +#if WITH_EDITOR_DATA + EditedControlPointsIndexes = ComponentInstanceData->EditedControlPointsIndexes; +#endif*/ + } +} + +void +UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) +{ + // Capture properties that we want to preserve during copy + const int32 PrevNodeId = NodeId; + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - IsVisible: %d"), IsVisible()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - bHiddenInGame: %d"), bHiddenInGame); + + UActorComponent* FromComponent = Cast(FromObject); + check(FromComponent); + + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + //Params.bDoDelta = false; // Perform a deep copy + //Params.bClearReferences = false; + //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, this, Params); + + /*const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); + FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, this, ComponentCopyOptions);*/ + + UHoudiniSplineComponent* FromSplineComponent = Cast(FromObject); + if (FromSplineComponent) + { + CurvePoints = FromSplineComponent->CurvePoints; + DisplayPoints = FromSplineComponent->DisplayPoints; + DisplayPointIndexDivider = FromSplineComponent->DisplayPointIndexDivider; +#if WITH_EDITORONLY_DATA + EditedControlPointsIndexes = FromSplineComponent->EditedControlPointsIndexes; +#endif + + HoudiniSplineName = FromSplineComponent->HoudiniSplineName; + bClosed = FromSplineComponent->bClosed; + bIsHoudiniSplineVisible = FromSplineComponent->bIsHoudiniSplineVisible; + CurveType = FromSplineComponent->CurveType; + CurveMethod = FromSplineComponent->CurveMethod; + bIsInputCurve = FromSplineComponent->bIsInputCurve; + bIsOutputCurve = FromSplineComponent->bIsOutputCurve; + bIsEditableOutputCurve = FromSplineComponent->bIsEditableOutputCurve; + bCookOnCurveChanged = FromSplineComponent->bCookOnCurveChanged; + + + + bHasChanged = FromSplineComponent->bHasChanged; + bNeedsToTriggerUpdate = FromSplineComponent->bNeedsToTriggerUpdate; + } + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - IsVisible: %d"), IsVisible()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - bHiddenInGame: %d"), bHiddenInGame); + + // Restore properties that we want to preserve + NodeId = PrevNodeId; +} + + +void +UHoudiniSplineComponent::OnUnregister() +{ + Super::OnUnregister(); +} + +void +UHoudiniSplineComponent::OnComponentCreated() +{ + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] Component: %s"), *GetPathName()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bVisible: %d"), IsVisible()); + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bHiddenInGame: %d"), bHiddenInGame); + Super::OnComponentCreated(); +} + +void +UHoudiniSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); + + if (IsInputCurve()) + { + // This component can't just come out of nowhere and decide to delete an input object! + // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! + + // InputObject->MarkPendingKill(); + + // if(NodeId > -1) + // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); + + SetNodeId(-1); // Set nodeId to invalid for reconstruct on re-do + } +} + + +#if WITH_EDITOR +void +UHoudiniSplineComponent::PostEditUndo() +{ + Super::PostEditUndo(); + + bPostUndo = true; + + // if (bIsInputCurve) + // { + // UHoudiniInputObject * CurrentInputObject = GetInputObject(); + // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + // return; + // + // CurrentInputObject->MarkChanged(true); + // } + // + // if (bIsEditableOutputCurve) + // { + // MarkChanged(true); + // } + + MarkChanged(true); + +} +#endif + +void +UHoudiniSplineComponent::SetOffset(const float& Offset) +{ + for (int n = 0; n < CurvePoints.Num(); ++n) + CurvePoints[n].AddToTranslation(FVector(0.f, Offset, 0.f)); + + for (int n = 0; n < DisplayPoints.Num(); ++n) + DisplayPoints[n] += FVector(0.f, Offset, 0.f); +} + +void +UHoudiniSplineComponent::ResetCurvePoints() +{ + CurvePoints.Empty(); +} + +void +UHoudiniSplineComponent::ResetDisplayPoints() +{ + DisplayPoints.Empty(); +} + +void +UHoudiniSplineComponent::AddCurvePoints(const TArray& Points) +{ + CurvePoints.Append(Points); +} + +void +UHoudiniSplineComponent::AddDisplayPoints(const TArray& Points) +{ + DisplayPoints.Append(Points); +} + +bool +UHoudiniSplineComponent::NeedsToTriggerUpdate() const +{ + return bNeedsToTriggerUpdate; + + // if (bIsInputCurve) + // { + // UHoudiniInputObject * CurrentInputObject = GetInputObject(); + // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + // return false; + // + // return CurrentInputObject->NeedsToTriggerUpdate(); + // } + // + // if (bIsEditableOutputCurve) + // { + // return bNeedsToTriggerUpdate; + // } + // + // return false; +} + +void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate) +{ + bNeedsToTriggerUpdate = NeedsToTriggerUpdate; +} + +void UHoudiniSplineComponent::SetCurveType(const EHoudiniCurveType & NewCurveType) +{ + CurveType = NewCurveType; +#if WITH_EDITOR + //FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(this, TEXT("CurveType")); +#endif +} + +void +UHoudiniSplineComponent::MarkInputObjectChanged() +{ + // if (bIsInputCurve) + // { + // UHoudiniInputObject * CurrentInputObject = GetInputObject(); + // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + // return; + // + // if (HasChanged()) + // CurrentInputObject->MarkChanged(true); + // } + // + // if (bIsEditableOutputCurve) + // { + // if (HasChanged()) + // MarkChanged(true); + // } + + // NOTE: This component should be trying to push ANY state changes to Input or Output objects. This + // component should strictly be a data container. Input / Output objects that reference this component should + // be polling this component's state. + MarkChanged(true); +} + +bool UHoudiniSplineComponent::HasChanged() const +{ + return bHasChanged; +} + +void UHoudiniSplineComponent::MarkChanged(const bool& Changed) +{ + bHasChanged = Changed; + bNeedsToTriggerUpdate = Changed; +} + +// UHoudiniAssetComponent* +// UHoudiniSplineComponent::GetParentHAC() +// { +// UHoudiniAssetComponent* ParentHAC = nullptr; +// if (bIsInputCurve) +// { +// if (!InputObject) +// return nullptr; +// +// UHoudiniInput* Input = Cast(InputObject->GetOuter()); +// if (!Input) +// return nullptr; +// +// ParentHAC = Cast(Input->GetOuter()); +// } +// else +// { +// // may do something else if this is not an input curve instead of returning Null. +// } +// +// return ParentHAC; +// +// } + +FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() +{ +} + +FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent) + : FActorComponentInstanceData(SourceComponent) +{ +} + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h index 8e45a2e9d..a860e8eff 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h @@ -1,299 +1,299 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "HoudiniEngineCopyPropertiesInterface.h" -#include "UObject/ObjectMacros.h" -#include "Components/SceneComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniSplineComponent.generated.h" - -class UHoudiniAssetComponent; - -enum class EHoudiniCurveType : int8; - -enum class EHoudiniCurveMethod : int8; - -class UHoudiniInputObject; - -UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) -class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, public IHoudiniEngineCopyPropertiesInterface -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniSplineComponent_V1; - - virtual ~UHoudiniSplineComponent(); - - virtual void Serialize(FArchive & Ar) override; - - public: - - void Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint = -1); - - void CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent); - - void ResetCurvePoints(); - - void ResetDisplayPoints(); - - void AddCurvePoints(const TArray& Points); - - void AddDisplayPoints(const TArray& Points); - - void AppendPoint(const FTransform& NewPoint); - - void InsertPointAtIndex(const FTransform& NewPoint, const int32& Index); - - void RemovePointAtIndex(const int32& Index); - - void EditPointAtindex(const FTransform& NewPoint, const int32& Index); - - // UHoudiniAssetComponent* GetParentHAC(); - - void MarkModified(const bool & InModified) { bHasChanged = InModified; }; - - // To set the offset of default position of houdini curve - void SetOffset(const float& Offset); - - UE_DEPRECATED(4.25, "Use MarkChanged() instead") - // This component should not be aware of whether it is being referenced by any input - // or output objects. - void MarkInputObjectChanged(); - - bool HasChanged() const; - - void MarkChanged(const bool& Changed); - - FORCEINLINE - FString& GetHoudiniSplineName() { return HoudiniSplineName; } - - FORCEINLINE - void SetHoudiniSplineName(const FString& NewName) { HoudiniSplineName = NewName; } - - bool NeedsToTriggerUpdate() const; - - void SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate); - - // FORCEINLINE - // UHoudiniInputObject* GetInputObject() const { return InputObject; } - - // FORCEINLINE - // void SetInputObject(UHoudiniInputObject* NewInputObject) { InputObject = NewInputObject; } - - FORCEINLINE - EHoudiniCurveType GetCurveType() const { return CurveType; } - - void SetCurveType(const EHoudiniCurveType& NewCurveType); - - FORCEINLINE - EHoudiniCurveMethod GetCurveMethod() const { return CurveMethod; } - - FORCEINLINE - void SetCurveMethod(const EHoudiniCurveMethod& NewCurveMethod) { CurveMethod = NewCurveMethod; } - - FORCEINLINE - int32 GetCurvePointCount() const { return CurvePoints.Num(); } - - FORCEINLINE - bool IsClosedCurve() const { return bClosed; } - - FORCEINLINE - void SetClosedCurve(const bool& Closed) { bClosed = Closed; } - - FORCEINLINE - bool IsReversed() const { return bReversed; } - - void SetReversed(const bool& Reversed); - - FORCEINLINE - bool IsInputCurve() const { return bIsInputCurve; } - - FORCEINLINE - void SetIsInputCurve(const bool& bIsInput) { bIsInputCurve = bIsInput; } - - FORCEINLINE - bool IsEditableOutputCurve() const { return bIsEditableOutputCurve; } - - FORCEINLINE - void SetIsEditableOutputCurve(const bool& bInIsEditable) { bIsEditableOutputCurve = bInIsEditable; }; - - FORCEINLINE - int32 GetNodeId() const { return NodeId; } - - FORCEINLINE - void SetNodeId(const int32& NewNodeId) { NodeId = NewNodeId; } - - FORCEINLINE - FString GetGeoPartName() const { return PartName; } - - FORCEINLINE - bool IsHoudiniSplineVisible() const { return bIsHoudiniSplineVisible; } - - FORCEINLINE - void SetHoudiniSplineVisible(bool Visible) { bIsHoudiniSplineVisible = Visible; } - - FORCEINLINE - void SetGeoPartName(const FString & InPartName) { PartName = InPartName; } - - virtual void OnUnregister() override; - - virtual void OnComponentCreated() override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - -#if WITH_EDITOR - virtual void PostEditUndo() override; - virtual void PostEditChangeProperty(FPropertyChangedEvent & PeopertyChangedEvent) override; -#endif - - virtual void PostLoad() override; - - virtual TStructOnScope GetComponentInstanceData() const override; - void ApplyComponentInstanceData(struct FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS); - - virtual void CopyPropertiesFrom(UObject* FromObject) override; - - private: - - void ReverseCurvePoints(); - - public: - - UPROPERTY() - TArray CurvePoints; - - UPROPERTY() - TArray DisplayPoints; - - UPROPERTY() - TArray DisplayPointIndexDivider; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - FString HoudiniSplineName; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bClosed; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bReversed; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bIsHoudiniSplineVisible; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - EHoudiniCurveType CurveType; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - EHoudiniCurveMethod CurveMethod; - - UPROPERTY() - bool bIsOutputCurve; - - UPROPERTY() - bool bCookOnCurveChanged; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - TArray EditedControlPointsIndexes; - - UPROPERTY(NonTransactional) - bool bPostUndo; -#endif - - protected: - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject HoudiniGeoPartObject; - - private: - UPROPERTY(Transient, DuplicateTransient) - bool bHasChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Whether this is a Houdini curve input - UPROPERTY() - bool bIsInputCurve; - - UPROPERTY() - bool bIsEditableOutputCurve; - - // UPROPERTY() - // UHoudiniInputObject * InputObject; - - // Corresponds to the Curve NodeId in Houdini - UPROPERTY(Transient, DuplicateTransient) - int32 NodeId; - - UPROPERTY() - FString PartName; -}; - -/** Used to store HoudiniAssetComponent data during BP reconstruction */ -USTRUCT() -struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData -{ - GENERATED_BODY() -public: - - FHoudiniSplineComponentInstanceData(); - FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent); - - virtual ~FHoudiniSplineComponentInstanceData() = default; - - virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override - { - Super::ApplyToComponent(Component, CacheApplyPhase); - CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); - } - - // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. - /*UPROPERTY() - bool bHasChanged; - - UPROPERTY() - bool bNeedsToTriggerUpdate;*/ - - UPROPERTY() - TArray CurvePoints; - - UPROPERTY() - TArray DisplayPoints; - - UPROPERTY() - TArray DisplayPointIndexDivider; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - TArray EditedControlPointsIndexes; -#endif - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniEngineCopyPropertiesInterface.h" +#include "UObject/ObjectMacros.h" +#include "Components/SceneComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniSplineComponent.generated.h" + +class UHoudiniAssetComponent; + +enum class EHoudiniCurveType : int8; + +enum class EHoudiniCurveMethod : int8; + +class UHoudiniInputObject; + +UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) +class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, public IHoudiniEngineCopyPropertiesInterface +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniSplineComponent_V1; + + virtual ~UHoudiniSplineComponent(); + + virtual void Serialize(FArchive & Ar) override; + + public: + + void Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint = -1); + + void CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent); + + void ResetCurvePoints(); + + void ResetDisplayPoints(); + + void AddCurvePoints(const TArray& Points); + + void AddDisplayPoints(const TArray& Points); + + void AppendPoint(const FTransform& NewPoint); + + void InsertPointAtIndex(const FTransform& NewPoint, const int32& Index); + + void RemovePointAtIndex(const int32& Index); + + void EditPointAtindex(const FTransform& NewPoint, const int32& Index); + + // UHoudiniAssetComponent* GetParentHAC(); + + void MarkModified(const bool & InModified) { bHasChanged = InModified; }; + + // To set the offset of default position of houdini curve + void SetOffset(const float& Offset); + + UE_DEPRECATED(4.25, "Use MarkChanged() instead") + // This component should not be aware of whether it is being referenced by any input + // or output objects. + void MarkInputObjectChanged(); + + bool HasChanged() const; + + void MarkChanged(const bool& Changed); + + FORCEINLINE + FString& GetHoudiniSplineName() { return HoudiniSplineName; } + + FORCEINLINE + void SetHoudiniSplineName(const FString& NewName) { HoudiniSplineName = NewName; } + + bool NeedsToTriggerUpdate() const; + + void SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate); + + // FORCEINLINE + // UHoudiniInputObject* GetInputObject() const { return InputObject; } + + // FORCEINLINE + // void SetInputObject(UHoudiniInputObject* NewInputObject) { InputObject = NewInputObject; } + + FORCEINLINE + EHoudiniCurveType GetCurveType() const { return CurveType; } + + void SetCurveType(const EHoudiniCurveType& NewCurveType); + + FORCEINLINE + EHoudiniCurveMethod GetCurveMethod() const { return CurveMethod; } + + FORCEINLINE + void SetCurveMethod(const EHoudiniCurveMethod& NewCurveMethod) { CurveMethod = NewCurveMethod; } + + FORCEINLINE + int32 GetCurvePointCount() const { return CurvePoints.Num(); } + + FORCEINLINE + bool IsClosedCurve() const { return bClosed; } + + FORCEINLINE + void SetClosedCurve(const bool& Closed) { bClosed = Closed; } + + FORCEINLINE + bool IsReversed() const { return bReversed; } + + void SetReversed(const bool& Reversed); + + FORCEINLINE + bool IsInputCurve() const { return bIsInputCurve; } + + FORCEINLINE + void SetIsInputCurve(const bool& bIsInput) { bIsInputCurve = bIsInput; } + + FORCEINLINE + bool IsEditableOutputCurve() const { return bIsEditableOutputCurve; } + + FORCEINLINE + void SetIsEditableOutputCurve(const bool& bInIsEditable) { bIsEditableOutputCurve = bInIsEditable; }; + + FORCEINLINE + int32 GetNodeId() const { return NodeId; } + + FORCEINLINE + void SetNodeId(const int32& NewNodeId) { NodeId = NewNodeId; } + + FORCEINLINE + FString GetGeoPartName() const { return PartName; } + + FORCEINLINE + bool IsHoudiniSplineVisible() const { return bIsHoudiniSplineVisible; } + + FORCEINLINE + void SetHoudiniSplineVisible(bool Visible) { bIsHoudiniSplineVisible = Visible; } + + FORCEINLINE + void SetGeoPartName(const FString & InPartName) { PartName = InPartName; } + + virtual void OnUnregister() override; + + virtual void OnComponentCreated() override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + +#if WITH_EDITOR + virtual void PostEditUndo() override; + virtual void PostEditChangeProperty(FPropertyChangedEvent & PeopertyChangedEvent) override; +#endif + + virtual void PostLoad() override; + + virtual TStructOnScope GetComponentInstanceData() const override; + void ApplyComponentInstanceData(struct FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS); + + virtual void CopyPropertiesFrom(UObject* FromObject) override; + + private: + + void ReverseCurvePoints(); + + public: + + UPROPERTY() + TArray CurvePoints; + + UPROPERTY() + TArray DisplayPoints; + + UPROPERTY() + TArray DisplayPointIndexDivider; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + FString HoudiniSplineName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bClosed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bReversed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bIsHoudiniSplineVisible; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + EHoudiniCurveType CurveType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + EHoudiniCurveMethod CurveMethod; + + UPROPERTY() + bool bIsOutputCurve; + + UPROPERTY() + bool bCookOnCurveChanged; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + TArray EditedControlPointsIndexes; + + UPROPERTY(NonTransactional) + bool bPostUndo; +#endif + + protected: + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject HoudiniGeoPartObject; + + private: + UPROPERTY(Transient, DuplicateTransient) + bool bHasChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Whether this is a Houdini curve input + UPROPERTY() + bool bIsInputCurve; + + UPROPERTY() + bool bIsEditableOutputCurve; + + // UPROPERTY() + // UHoudiniInputObject * InputObject; + + // Corresponds to the Curve NodeId in Houdini + UPROPERTY(Transient, DuplicateTransient) + int32 NodeId; + + UPROPERTY() + FString PartName; +}; + +/** Used to store HoudiniAssetComponent data during BP reconstruction */ +USTRUCT() +struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData +{ + GENERATED_BODY() +public: + + FHoudiniSplineComponentInstanceData(); + FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent); + + virtual ~FHoudiniSplineComponentInstanceData() = default; + + virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override + { + Super::ApplyToComponent(Component, CacheApplyPhase); + CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); + } + + // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. + /*UPROPERTY() + bool bHasChanged; + + UPROPERTY() + bool bNeedsToTriggerUpdate;*/ + + UPROPERTY() + TArray CurvePoints; + + UPROPERTY() + TArray DisplayPoints; + + UPROPERTY() + TArray DisplayPointIndexDivider; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + TArray EditedControlPointsIndexes; +#endif + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp index 34cc1be89..30bdeec46 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp @@ -1,303 +1,303 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMesh.h" - -UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - bHasNormals = false; - bHasTangents = false; - bHasColors = false; - NumUVLayers = false; - bHasPerFaceMaterials = false; -} - -void UHoudiniStaticMesh::Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials) -{ - // Initialize the vertex positions and triangle indices arrays - VertexPositions.Init(FVector::ZeroVector, InNumVertices); - TriangleIndices.Init(FIntVector(-1, -1, -1), InNumTriangles); - if (InInitialNumStaticMaterials > 0) - StaticMaterials.Init(FStaticMaterial(), InInitialNumStaticMaterials); - else - StaticMaterials.Empty(); - - SetNumUVLayers(InNumUVLayers); - SetHasNormals(bInHasNormals); - SetHasTangents(bInHasTangents); - SetHasColors(bInHasColors); - SetHasPerFaceMaterials(bInHasPerFaceMaterials); -} - -void UHoudiniStaticMesh::SetHasPerFaceMaterials(bool bInHasPerFaceMaterials) -{ - bHasPerFaceMaterials = bInHasPerFaceMaterials; - if (bHasPerFaceMaterials) - MaterialIDsPerTriangle.Init(-1, GetNumTriangles()); - else - MaterialIDsPerTriangle.Empty(); -} - -void UHoudiniStaticMesh::SetHasNormals(bool bInHasNormals) -{ - bHasNormals = bInHasNormals; - if (bHasNormals) - VertexInstanceNormals.Init(FVector(0, 0, 1), GetNumVertexInstances()); - else - VertexInstanceNormals.Empty(); -} - -void UHoudiniStaticMesh::SetHasTangents(bool bInHasTangents) -{ - bHasTangents = bInHasTangents; - if (bHasTangents) - { - VertexInstanceUTangents.Init(FVector(1, 0, 0), GetNumVertexInstances()); - VertexInstanceVTangents.Init(FVector(0, 1, 0), GetNumVertexInstances()); - } - else - { - VertexInstanceUTangents.Empty(); - VertexInstanceVTangents.Empty(); - } -} - -void UHoudiniStaticMesh::SetHasColors(bool bInHasColors) -{ - bHasColors = bInHasColors; - if (bHasColors) - VertexInstanceColors.Init(FColor(127, 127, 127), GetNumVertexInstances()); - else - VertexInstanceColors.Empty(); -} - -void UHoudiniStaticMesh::SetNumUVLayers(uint32 InNumUVLayers) -{ - NumUVLayers = InNumUVLayers; - if (NumUVLayers > 0) - VertexInstanceUVs.Init(FVector2D::ZeroVector, GetNumVertexInstances() * NumUVLayers); - else - VertexInstanceUVs.Empty(); -} - -void UHoudiniStaticMesh::SetNumStaticMaterials(uint32 InNumMaterials) -{ - if (InNumMaterials > 0) - StaticMaterials.SetNum(InNumMaterials); - else - StaticMaterials.Empty(); -} - -void UHoudiniStaticMesh::SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition) -{ - check(VertexPositions.IsValidIndex(InVertexIndex)); - - VertexPositions[InVertexIndex] = InPosition; -} - -void UHoudiniStaticMesh::SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices) -{ - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[0])); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[1])); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[2])); - - TriangleIndices[InTriangleIndex] = InTriangleVertexIndices; -} - -void UHoudiniStaticMesh::SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal) -{ - if (!bHasNormals) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceNormals.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceNormals[VertexInstanceIndex] = InNormal; -} - -void UHoudiniStaticMesh::SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent) -{ - if (!bHasTangents) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceUTangents.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceUTangents[VertexInstanceIndex] = InUTangent; -} - -void UHoudiniStaticMesh::SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent) -{ - if (!bHasTangents) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceVTangents.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceVTangents[VertexInstanceIndex] = InVTangent; -} - -void UHoudiniStaticMesh::SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor) -{ - if (!bHasColors) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceColors.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceColors[VertexInstanceIndex] = InColor; -} - -void UHoudiniStaticMesh::SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV) -{ - if (NumUVLayers <= 0) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceUVIndex = InUVLayer * GetNumVertexInstances() + InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceUVs.IsValidIndex(VertexInstanceUVIndex)); - - VertexInstanceUVs[VertexInstanceUVIndex] = InUV; -} - -void UHoudiniStaticMesh::SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID) -{ - if (!bHasPerFaceMaterials) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - check(MaterialIDsPerTriangle.IsValidIndex(InTriangleIndex)); - - MaterialIDsPerTriangle[InTriangleIndex] = InMaterialID; -} - -void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial) -{ - check(StaticMaterials.IsValidIndex(InMaterialIndex)); - StaticMaterials[InMaterialIndex] = InStaticMaterial; -} - -void UHoudiniStaticMesh::Optimize() -{ - VertexPositions.Shrink(); - TriangleIndices.Shrink(); - VertexInstanceColors.Shrink(); - VertexInstanceNormals.Shrink(); - VertexInstanceUTangents.Shrink(); - VertexInstanceVTangents.Shrink(); - VertexInstanceUVs.Shrink(); - MaterialIDsPerTriangle.Shrink(); - StaticMaterials.Shrink(); -} - -FBox UHoudiniStaticMesh::CalcBounds() const -{ - const uint32 NumVertices = VertexPositions.Num(); - - if (NumVertices == 0) - return FBox(); - - const FVector InitPosition = VertexPositions[0]; - double MinX = InitPosition.X, MaxX = InitPosition.X, MinY = InitPosition.Y, MaxY = InitPosition.Y, MinZ = InitPosition.Z, MaxZ = InitPosition.Z; - for (uint32 VertIdx = 0; VertIdx < NumVertices; ++VertIdx) - { - const FVector Position = VertexPositions[VertIdx]; - if (Position.X < MinX) MinX = Position.X; else if (Position.X > MaxX) MaxX = Position.X; - if (Position.Y < MinY) MinY = Position.Y; else if (Position.Y > MaxY) MaxY = Position.Y; - if (Position.Z < MinZ) MinZ = Position.Z; else if (Position.Z > MaxZ) MaxZ = Position.Z; - } - - return FBox(FVector(MinX, MinY, MinZ), FVector(MaxX, MaxY, MaxZ)); -} - -UMaterialInterface* UHoudiniStaticMesh::GetMaterial(int32 InMaterialIndex) -{ - check(StaticMaterials.IsValidIndex(InMaterialIndex)); - - return StaticMaterials[InMaterialIndex].MaterialInterface; -} - -int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const -{ - if (InMaterialSlotName == NAME_None) - return -1; - - const uint32 NumMaterials = StaticMaterials.Num(); - for (uint32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) - { - if (StaticMaterials[MaterialIndex].MaterialSlotName == InMaterialSlotName) - return (int32)MaterialIndex; - } - - return -1; -} - -void UHoudiniStaticMesh::Serialize(FArchive &InArchive) -{ - Super::Serialize(InArchive); - - VertexPositions.Shrink(); - VertexPositions.BulkSerialize(InArchive); - - TriangleIndices.Shrink(); - TriangleIndices.BulkSerialize(InArchive); - - VertexInstanceColors.Shrink(); - VertexInstanceColors.BulkSerialize(InArchive); - - VertexInstanceNormals.Shrink(); - VertexInstanceNormals.BulkSerialize(InArchive); - - VertexInstanceUTangents.Shrink(); - VertexInstanceUTangents.BulkSerialize(InArchive); - - VertexInstanceVTangents.Shrink(); - VertexInstanceVTangents.BulkSerialize(InArchive); - - VertexInstanceUVs.Shrink(); - VertexInstanceUVs.BulkSerialize(InArchive); - - MaterialIDsPerTriangle.Shrink(); - MaterialIDsPerTriangle.BulkSerialize(InArchive); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMesh.h" + +UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bHasNormals = false; + bHasTangents = false; + bHasColors = false; + NumUVLayers = false; + bHasPerFaceMaterials = false; +} + +void UHoudiniStaticMesh::Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials) +{ + // Initialize the vertex positions and triangle indices arrays + VertexPositions.Init(FVector::ZeroVector, InNumVertices); + TriangleIndices.Init(FIntVector(-1, -1, -1), InNumTriangles); + if (InInitialNumStaticMaterials > 0) + StaticMaterials.Init(FStaticMaterial(), InInitialNumStaticMaterials); + else + StaticMaterials.Empty(); + + SetNumUVLayers(InNumUVLayers); + SetHasNormals(bInHasNormals); + SetHasTangents(bInHasTangents); + SetHasColors(bInHasColors); + SetHasPerFaceMaterials(bInHasPerFaceMaterials); +} + +void UHoudiniStaticMesh::SetHasPerFaceMaterials(bool bInHasPerFaceMaterials) +{ + bHasPerFaceMaterials = bInHasPerFaceMaterials; + if (bHasPerFaceMaterials) + MaterialIDsPerTriangle.Init(-1, GetNumTriangles()); + else + MaterialIDsPerTriangle.Empty(); +} + +void UHoudiniStaticMesh::SetHasNormals(bool bInHasNormals) +{ + bHasNormals = bInHasNormals; + if (bHasNormals) + VertexInstanceNormals.Init(FVector(0, 0, 1), GetNumVertexInstances()); + else + VertexInstanceNormals.Empty(); +} + +void UHoudiniStaticMesh::SetHasTangents(bool bInHasTangents) +{ + bHasTangents = bInHasTangents; + if (bHasTangents) + { + VertexInstanceUTangents.Init(FVector(1, 0, 0), GetNumVertexInstances()); + VertexInstanceVTangents.Init(FVector(0, 1, 0), GetNumVertexInstances()); + } + else + { + VertexInstanceUTangents.Empty(); + VertexInstanceVTangents.Empty(); + } +} + +void UHoudiniStaticMesh::SetHasColors(bool bInHasColors) +{ + bHasColors = bInHasColors; + if (bHasColors) + VertexInstanceColors.Init(FColor(127, 127, 127), GetNumVertexInstances()); + else + VertexInstanceColors.Empty(); +} + +void UHoudiniStaticMesh::SetNumUVLayers(uint32 InNumUVLayers) +{ + NumUVLayers = InNumUVLayers; + if (NumUVLayers > 0) + VertexInstanceUVs.Init(FVector2D::ZeroVector, GetNumVertexInstances() * NumUVLayers); + else + VertexInstanceUVs.Empty(); +} + +void UHoudiniStaticMesh::SetNumStaticMaterials(uint32 InNumMaterials) +{ + if (InNumMaterials > 0) + StaticMaterials.SetNum(InNumMaterials); + else + StaticMaterials.Empty(); +} + +void UHoudiniStaticMesh::SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition) +{ + check(VertexPositions.IsValidIndex(InVertexIndex)); + + VertexPositions[InVertexIndex] = InPosition; +} + +void UHoudiniStaticMesh::SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices) +{ + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[0])); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[1])); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[2])); + + TriangleIndices[InTriangleIndex] = InTriangleVertexIndices; +} + +void UHoudiniStaticMesh::SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal) +{ + if (!bHasNormals) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceNormals.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceNormals[VertexInstanceIndex] = InNormal; +} + +void UHoudiniStaticMesh::SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent) +{ + if (!bHasTangents) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceUTangents.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceUTangents[VertexInstanceIndex] = InUTangent; +} + +void UHoudiniStaticMesh::SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent) +{ + if (!bHasTangents) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceVTangents.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceVTangents[VertexInstanceIndex] = InVTangent; +} + +void UHoudiniStaticMesh::SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor) +{ + if (!bHasColors) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceColors.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceColors[VertexInstanceIndex] = InColor; +} + +void UHoudiniStaticMesh::SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV) +{ + if (NumUVLayers <= 0) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceUVIndex = InUVLayer * GetNumVertexInstances() + InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceUVs.IsValidIndex(VertexInstanceUVIndex)); + + VertexInstanceUVs[VertexInstanceUVIndex] = InUV; +} + +void UHoudiniStaticMesh::SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID) +{ + if (!bHasPerFaceMaterials) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + check(MaterialIDsPerTriangle.IsValidIndex(InTriangleIndex)); + + MaterialIDsPerTriangle[InTriangleIndex] = InMaterialID; +} + +void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial) +{ + check(StaticMaterials.IsValidIndex(InMaterialIndex)); + StaticMaterials[InMaterialIndex] = InStaticMaterial; +} + +void UHoudiniStaticMesh::Optimize() +{ + VertexPositions.Shrink(); + TriangleIndices.Shrink(); + VertexInstanceColors.Shrink(); + VertexInstanceNormals.Shrink(); + VertexInstanceUTangents.Shrink(); + VertexInstanceVTangents.Shrink(); + VertexInstanceUVs.Shrink(); + MaterialIDsPerTriangle.Shrink(); + StaticMaterials.Shrink(); +} + +FBox UHoudiniStaticMesh::CalcBounds() const +{ + const uint32 NumVertices = VertexPositions.Num(); + + if (NumVertices == 0) + return FBox(); + + const FVector InitPosition = VertexPositions[0]; + double MinX = InitPosition.X, MaxX = InitPosition.X, MinY = InitPosition.Y, MaxY = InitPosition.Y, MinZ = InitPosition.Z, MaxZ = InitPosition.Z; + for (uint32 VertIdx = 0; VertIdx < NumVertices; ++VertIdx) + { + const FVector Position = VertexPositions[VertIdx]; + if (Position.X < MinX) MinX = Position.X; else if (Position.X > MaxX) MaxX = Position.X; + if (Position.Y < MinY) MinY = Position.Y; else if (Position.Y > MaxY) MaxY = Position.Y; + if (Position.Z < MinZ) MinZ = Position.Z; else if (Position.Z > MaxZ) MaxZ = Position.Z; + } + + return FBox(FVector(MinX, MinY, MinZ), FVector(MaxX, MaxY, MaxZ)); +} + +UMaterialInterface* UHoudiniStaticMesh::GetMaterial(int32 InMaterialIndex) +{ + check(StaticMaterials.IsValidIndex(InMaterialIndex)); + + return StaticMaterials[InMaterialIndex].MaterialInterface; +} + +int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const +{ + if (InMaterialSlotName == NAME_None) + return -1; + + const uint32 NumMaterials = StaticMaterials.Num(); + for (uint32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) + { + if (StaticMaterials[MaterialIndex].MaterialSlotName == InMaterialSlotName) + return (int32)MaterialIndex; + } + + return -1; +} + +void UHoudiniStaticMesh::Serialize(FArchive &InArchive) +{ + Super::Serialize(InArchive); + + VertexPositions.Shrink(); + VertexPositions.BulkSerialize(InArchive); + + TriangleIndices.Shrink(); + TriangleIndices.BulkSerialize(InArchive); + + VertexInstanceColors.Shrink(); + VertexInstanceColors.BulkSerialize(InArchive); + + VertexInstanceNormals.Shrink(); + VertexInstanceNormals.BulkSerialize(InArchive); + + VertexInstanceUTangents.Shrink(); + VertexInstanceUTangents.BulkSerialize(InArchive); + + VertexInstanceVTangents.Shrink(); + VertexInstanceVTangents.BulkSerialize(InArchive); + + VertexInstanceUVs.Shrink(); + VertexInstanceUVs.BulkSerialize(InArchive); + + MaterialIDsPerTriangle.Shrink(); + MaterialIDsPerTriangle.BulkSerialize(InArchive); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h index 4cda5c8da..6a2917419 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h @@ -1,226 +1,226 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Engine/StaticMesh.h" - -#include "HoudiniStaticMesh.generated.h" - -/** - * This is a simple static mesh that is meant to be built in one go, without modifications afterwards. - * The number of vertices and triangles must be known before hand. - */ -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject -{ - GENERATED_BODY() - -public: - - UHoudiniStaticMesh(const FObjectInitializer &ObjectInitializer); - - // Clears all existing data and initializes internal arrays to the relevant sizes to accommodate the - // mesh based InNumVertices, InNumTriangles, UVs etc. - UFUNCTION() - void Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials); - - UFUNCTION() - bool HasPerFaceMaterials() const { return bHasPerFaceMaterials; } - - UFUNCTION() - void SetHasPerFaceMaterials(bool bInHasPerFaceMaterials); - - UFUNCTION() - bool HasNormals() const { return bHasNormals; } - - UFUNCTION() - void SetHasNormals(bool bInHasNormals); - - UFUNCTION() - bool HasTangents() const { return bHasTangents; } - - UFUNCTION() - void SetHasTangents(bool bInHasTangents); - - UFUNCTION() - bool HasColors() const { return bHasColors; } - - UFUNCTION() - void SetHasColors(bool bInHasColors); - - UFUNCTION() - uint32 GetNumUVLayers() const { return NumUVLayers; } - - UFUNCTION() - void SetNumUVLayers(uint32 InNumUVLayers); - - UFUNCTION() - uint32 GetNumStaticMaterials() const { return StaticMaterials.Num(); } - - UFUNCTION() - void SetNumStaticMaterials(uint32 InNumStaticMaterials); - - UFUNCTION() - uint32 GetNumVertices() const { return VertexPositions.Num(); } - - UFUNCTION() - uint32 GetNumTriangles() const { return TriangleIndices.Num(); } - - UFUNCTION() - uint32 GetNumVertexInstances() const { return TriangleIndices.Num() * 3; } - - UFUNCTION() - void SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition); - - UFUNCTION() - void SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices); - - UFUNCTION() - void SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal); - - UFUNCTION() - void SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent); - - UFUNCTION() - void SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent); - - UFUNCTION() - void SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor); - - UFUNCTION() - void SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV); - - UFUNCTION() - void SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID); - - UFUNCTION() - void SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial); - - UFUNCTION() - uint32 AddStaticMaterial(const FStaticMaterial& InStaticMaterial) { return StaticMaterials.Add(InStaticMaterial); } - - // Meant to be called after the mesh data arrays are populated. - // Currently only calls Shrink on the arrays - UFUNCTION() - void Optimize(); - - UFUNCTION() - FBox CalcBounds() const; - - UFUNCTION() - const TArray& GetVertexPositions() const { return VertexPositions; } - - UFUNCTION() - const TArray& GetTriangleIndices() const { return TriangleIndices; } - - UFUNCTION() - const TArray& GetVertexInstanceColors() const { return VertexInstanceColors; } - - UFUNCTION() - const TArray& GetVertexInstanceNormals() const { return VertexInstanceNormals; } - - UFUNCTION() - const TArray& GetVertexInstanceUTangents() const { return VertexInstanceUTangents; } - - UFUNCTION() - const TArray& GetVertexInstanceVTangents() const { return VertexInstanceVTangents; } - - UFUNCTION() - const TArray& GetVertexInstanceUVs() const { return VertexInstanceUVs; } - - UFUNCTION() - const TArray& GetMaterialIDsPerTriangle() const { return MaterialIDsPerTriangle; } - - UFUNCTION() - const TArray& GetStaticMaterials() const { return StaticMaterials; } - - UFUNCTION() - UMaterialInterface* GetMaterial(int32 InMaterialIndex); - - UFUNCTION() - int32 GetMaterialIndex(FName InMaterialSlotName) const; - - // Custom serialization: we use TArray::BulkSerialize to speed up array serialization - virtual void Serialize(FArchive &InArchive) override; - -protected: - - UPROPERTY() - bool bHasNormals; - - UPROPERTY() - bool bHasTangents; - - UPROPERTY() - bool bHasColors; - - /** The number of UV layers that the mesh has */ - UPROPERTY() - uint32 NumUVLayers; - - UPROPERTY() - bool bHasPerFaceMaterials; - - /** Vertex positions. The vertex id == vertex index => indexes into this array. */ - UPROPERTY(SkipSerialization) - TArray VertexPositions; - - /** Triangle vertices. Triangle id == triangle index => indexes into this array, which returns a FIntVector of - * vertex ids/indices for VertexPositions. - */ - UPROPERTY(SkipSerialization) - TArray TriangleIndices; - - /** Array of colors per vertex instance, in other words, a color per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceColors; - - /** Array of normals per vertex instance, in other words, a normal per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceNormals; - - /** Array of U tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceUTangents; - - /** Array of V tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceVTangents; - - /** Array of UV layers to array of per triangle-vertex UVs. Index: UVLayerIndex * (NumVertexInstances) + 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceUVs; - - /** Array of material ID per triangle. Indexed by Triangle ID/Index. */ - UPROPERTY(SkipSerialization) - TArray MaterialIDsPerTriangle; - - /** The materials of the mesh. Index by MaterialID (MaterialIndex). */ - UPROPERTY() - TArray StaticMaterials; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/StaticMesh.h" + +#include "HoudiniStaticMesh.generated.h" + +/** + * This is a simple static mesh that is meant to be built in one go, without modifications afterwards. + * The number of vertices and triangles must be known before hand. + */ +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject +{ + GENERATED_BODY() + +public: + + UHoudiniStaticMesh(const FObjectInitializer &ObjectInitializer); + + // Clears all existing data and initializes internal arrays to the relevant sizes to accommodate the + // mesh based InNumVertices, InNumTriangles, UVs etc. + UFUNCTION() + void Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials); + + UFUNCTION() + bool HasPerFaceMaterials() const { return bHasPerFaceMaterials; } + + UFUNCTION() + void SetHasPerFaceMaterials(bool bInHasPerFaceMaterials); + + UFUNCTION() + bool HasNormals() const { return bHasNormals; } + + UFUNCTION() + void SetHasNormals(bool bInHasNormals); + + UFUNCTION() + bool HasTangents() const { return bHasTangents; } + + UFUNCTION() + void SetHasTangents(bool bInHasTangents); + + UFUNCTION() + bool HasColors() const { return bHasColors; } + + UFUNCTION() + void SetHasColors(bool bInHasColors); + + UFUNCTION() + uint32 GetNumUVLayers() const { return NumUVLayers; } + + UFUNCTION() + void SetNumUVLayers(uint32 InNumUVLayers); + + UFUNCTION() + uint32 GetNumStaticMaterials() const { return StaticMaterials.Num(); } + + UFUNCTION() + void SetNumStaticMaterials(uint32 InNumStaticMaterials); + + UFUNCTION() + uint32 GetNumVertices() const { return VertexPositions.Num(); } + + UFUNCTION() + uint32 GetNumTriangles() const { return TriangleIndices.Num(); } + + UFUNCTION() + uint32 GetNumVertexInstances() const { return TriangleIndices.Num() * 3; } + + UFUNCTION() + void SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition); + + UFUNCTION() + void SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices); + + UFUNCTION() + void SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal); + + UFUNCTION() + void SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent); + + UFUNCTION() + void SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent); + + UFUNCTION() + void SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor); + + UFUNCTION() + void SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV); + + UFUNCTION() + void SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID); + + UFUNCTION() + void SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial); + + UFUNCTION() + uint32 AddStaticMaterial(const FStaticMaterial& InStaticMaterial) { return StaticMaterials.Add(InStaticMaterial); } + + // Meant to be called after the mesh data arrays are populated. + // Currently only calls Shrink on the arrays + UFUNCTION() + void Optimize(); + + UFUNCTION() + FBox CalcBounds() const; + + UFUNCTION() + const TArray& GetVertexPositions() const { return VertexPositions; } + + UFUNCTION() + const TArray& GetTriangleIndices() const { return TriangleIndices; } + + UFUNCTION() + const TArray& GetVertexInstanceColors() const { return VertexInstanceColors; } + + UFUNCTION() + const TArray& GetVertexInstanceNormals() const { return VertexInstanceNormals; } + + UFUNCTION() + const TArray& GetVertexInstanceUTangents() const { return VertexInstanceUTangents; } + + UFUNCTION() + const TArray& GetVertexInstanceVTangents() const { return VertexInstanceVTangents; } + + UFUNCTION() + const TArray& GetVertexInstanceUVs() const { return VertexInstanceUVs; } + + UFUNCTION() + const TArray& GetMaterialIDsPerTriangle() const { return MaterialIDsPerTriangle; } + + UFUNCTION() + const TArray& GetStaticMaterials() const { return StaticMaterials; } + + UFUNCTION() + UMaterialInterface* GetMaterial(int32 InMaterialIndex); + + UFUNCTION() + int32 GetMaterialIndex(FName InMaterialSlotName) const; + + // Custom serialization: we use TArray::BulkSerialize to speed up array serialization + virtual void Serialize(FArchive &InArchive) override; + +protected: + + UPROPERTY() + bool bHasNormals; + + UPROPERTY() + bool bHasTangents; + + UPROPERTY() + bool bHasColors; + + /** The number of UV layers that the mesh has */ + UPROPERTY() + uint32 NumUVLayers; + + UPROPERTY() + bool bHasPerFaceMaterials; + + /** Vertex positions. The vertex id == vertex index => indexes into this array. */ + UPROPERTY(SkipSerialization) + TArray VertexPositions; + + /** Triangle vertices. Triangle id == triangle index => indexes into this array, which returns a FIntVector of + * vertex ids/indices for VertexPositions. + */ + UPROPERTY(SkipSerialization) + TArray TriangleIndices; + + /** Array of colors per vertex instance, in other words, a color per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceColors; + + /** Array of normals per vertex instance, in other words, a normal per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceNormals; + + /** Array of U tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceUTangents; + + /** Array of V tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceVTangents; + + /** Array of UV layers to array of per triangle-vertex UVs. Index: UVLayerIndex * (NumVertexInstances) + 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceUVs; + + /** Array of material ID per triangle. Indexed by Triangle ID/Index. */ + UPROPERTY(SkipSerialization) + TArray MaterialIDsPerTriangle; + + /** The materials of the mesh. Index by MaterialID (MaterialIndex). */ + UPROPERTY() + TArray StaticMaterials; +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp index 460a17b60..452daab7a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp @@ -1,213 +1,213 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMeshComponent.h" - -#include "Components/BillboardComponent.h" -#include "Engine/CollisionProfile.h" - -#include "HoudiniStaticMesh.h" -#include "HoudiniStaticMeshSceneProxy.h" - - -UHoudiniStaticMeshComponent::UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer) : - Super(InInitialzer) -{ - PrimaryComponentTick.bCanEverTick = false; - - SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); - - Mesh = nullptr; - bHoudiniIconVisible = true; - -#if WITH_EDITOR - bVisualizeComponent = true; -#endif -} - -void UHoudiniStaticMeshComponent::OnRegister() -{ - Super::OnRegister(); - -#if WITH_EDITORONLY_DATA - if (bVisualizeComponent && SpriteComponent != nullptr && GetOwner()) - { - SpriteComponent->SetSprite(LoadObject(nullptr, TEXT("/HoudiniEngine/Textures/icon_houdini_logo_128.icon_houdini_logo_128"))); - UpdateSpriteComponent(); - } -#endif -} - -//void -//UHoudiniStaticMeshComponent::PostLoad() -//{ -// Super::PostLoad(); -// -// //NotifyMeshUpdated(); -//} - -void UHoudiniStaticMeshComponent::SetMesh(UHoudiniStaticMesh *InMesh) -{ - if (Mesh == InMesh) - return; - - Mesh = InMesh; - NotifyMeshUpdated(); -} - -FPrimitiveSceneProxy* UHoudiniStaticMeshComponent::CreateSceneProxy() -{ - check(SceneProxy == nullptr); - - FHoudiniStaticMeshSceneProxy* NewProxy = nullptr; - if (Mesh && Mesh->GetNumTriangles() > 0) - { - NewProxy = new FHoudiniStaticMeshSceneProxy(this, GetScene()->GetFeatureLevel()); - NewProxy->Build(); - } - return NewProxy; -} - -FBoxSphereBounds UHoudiniStaticMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const -{ - FBox LocalBoundingBox = LocalBounds; - FBoxSphereBounds Ret(LocalBoundingBox.TransformBy(InLocalToWorld)); - Ret.BoxExtent *= BoundsScale; - Ret.SphereRadius *= BoundsScale; - return Ret; -} - -#if WITH_EDITOR -void UHoudiniStaticMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - const FName NAME_HoudiniIconVisible(TEXT("bHoudiniIconVisible")); - const FName NAME_Mesh(TEXT("Mesh")); - const FName NAME_PropertyChanged = PropertyChangedEvent.GetPropertyName(); - if (NAME_PropertyChanged == NAME_HoudiniIconVisible) - { - UpdateSpriteComponent(); - } - else if (NAME_PropertyChanged == NAME_Mesh) - { - NotifyMeshUpdated(); - } -} -#endif - -void UHoudiniStaticMeshComponent::SetHoudiniIconVisible(bool bInHoudiniIconVisible) -{ - bHoudiniIconVisible = bInHoudiniIconVisible; -#if WITH_EDITORONLY_DATA - UpdateSpriteComponent(); -#endif -} - -void UHoudiniStaticMeshComponent::NotifyMeshUpdated() -{ - MarkRenderStateDirty(); - if (Mesh) - { - LocalBounds = Mesh->CalcBounds(); - } - else - { - LocalBounds.Init(); - } - -#if WITH_EDITORONLY_DATA - UpdateSpriteComponent(); -#endif - - UpdateBounds(); -} - -#if WITH_EDITORONLY_DATA -void UHoudiniStaticMeshComponent::UpdateSpriteComponent() -{ - if (SpriteComponent) - { - SpriteComponent->SetRelativeLocation(FVector(0, 0, LocalBounds.GetExtent().Z)); - SpriteComponent->SetVisibility(bHoudiniIconVisible); - } -} -#endif - -int32 UHoudiniStaticMeshComponent::GetNumMaterials() const -{ - // From UStaticMesh: - // @note : you don't have to consider Materials.Num() - // that only counts if overridden and it can't be more than GetStaticMesh()->Materials. - if (Mesh) - { - return Mesh->GetNumStaticMaterials(); - } - else - { - return 0; - } -} - -int32 UHoudiniStaticMeshComponent::GetMaterialIndex(FName MaterialSlotName) const -{ - return Mesh ? Mesh->GetMaterialIndex(MaterialSlotName) : -1; -} - -TArray UHoudiniStaticMeshComponent::GetMaterialSlotNames() const -{ - TArray MaterialNames; - if (Mesh) - { - const TArray &StaticMaterials = Mesh->GetStaticMaterials(); - const int32 NumMaterials = StaticMaterials.Num(); - for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) - { - const FStaticMaterial &StaticMaterial = StaticMaterials[MaterialIndex]; - MaterialNames.Add(StaticMaterial.MaterialSlotName); - } - } - return MaterialNames; -} - -bool UHoudiniStaticMeshComponent::IsMaterialSlotNameValid(FName MaterialSlotName) const -{ - return GetMaterialIndex(MaterialSlotName) >= 0; -} - -UMaterialInterface* UHoudiniStaticMeshComponent::GetMaterial(int32 MaterialIndex) const -{ - // From UStaticMesh: - // If we have a base materials array, use that - if (OverrideMaterials.IsValidIndex(MaterialIndex) && OverrideMaterials[MaterialIndex]) - { - return OverrideMaterials[MaterialIndex]; - } - // Otherwise get from static mesh - else - { - return Mesh ? Mesh->GetMaterial(MaterialIndex) : nullptr; - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMeshComponent.h" + +#include "Components/BillboardComponent.h" +#include "Engine/CollisionProfile.h" + +#include "HoudiniStaticMesh.h" +#include "HoudiniStaticMeshSceneProxy.h" + + +UHoudiniStaticMeshComponent::UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer) : + Super(InInitialzer) +{ + PrimaryComponentTick.bCanEverTick = false; + + SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); + + Mesh = nullptr; + bHoudiniIconVisible = true; + +#if WITH_EDITOR + bVisualizeComponent = true; +#endif +} + +void UHoudiniStaticMeshComponent::OnRegister() +{ + Super::OnRegister(); + +#if WITH_EDITORONLY_DATA + if (bVisualizeComponent && SpriteComponent != nullptr && GetOwner()) + { + SpriteComponent->SetSprite(LoadObject(nullptr, TEXT("/HoudiniEngine/Textures/icon_houdini_logo_128.icon_houdini_logo_128"))); + UpdateSpriteComponent(); + } +#endif +} + +//void +//UHoudiniStaticMeshComponent::PostLoad() +//{ +// Super::PostLoad(); +// +// //NotifyMeshUpdated(); +//} + +void UHoudiniStaticMeshComponent::SetMesh(UHoudiniStaticMesh *InMesh) +{ + if (Mesh == InMesh) + return; + + Mesh = InMesh; + NotifyMeshUpdated(); +} + +FPrimitiveSceneProxy* UHoudiniStaticMeshComponent::CreateSceneProxy() +{ + check(SceneProxy == nullptr); + + FHoudiniStaticMeshSceneProxy* NewProxy = nullptr; + if (Mesh && Mesh->GetNumTriangles() > 0) + { + NewProxy = new FHoudiniStaticMeshSceneProxy(this, GetScene()->GetFeatureLevel()); + NewProxy->Build(); + } + return NewProxy; +} + +FBoxSphereBounds UHoudiniStaticMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const +{ + FBox LocalBoundingBox = LocalBounds; + FBoxSphereBounds Ret(LocalBoundingBox.TransformBy(InLocalToWorld)); + Ret.BoxExtent *= BoundsScale; + Ret.SphereRadius *= BoundsScale; + return Ret; +} + +#if WITH_EDITOR +void UHoudiniStaticMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + const FName NAME_HoudiniIconVisible(TEXT("bHoudiniIconVisible")); + const FName NAME_Mesh(TEXT("Mesh")); + const FName NAME_PropertyChanged = PropertyChangedEvent.GetPropertyName(); + if (NAME_PropertyChanged == NAME_HoudiniIconVisible) + { + UpdateSpriteComponent(); + } + else if (NAME_PropertyChanged == NAME_Mesh) + { + NotifyMeshUpdated(); + } +} +#endif + +void UHoudiniStaticMeshComponent::SetHoudiniIconVisible(bool bInHoudiniIconVisible) +{ + bHoudiniIconVisible = bInHoudiniIconVisible; +#if WITH_EDITORONLY_DATA + UpdateSpriteComponent(); +#endif +} + +void UHoudiniStaticMeshComponent::NotifyMeshUpdated() +{ + MarkRenderStateDirty(); + if (Mesh) + { + LocalBounds = Mesh->CalcBounds(); + } + else + { + LocalBounds.Init(); + } + +#if WITH_EDITORONLY_DATA + UpdateSpriteComponent(); +#endif + + UpdateBounds(); +} + +#if WITH_EDITORONLY_DATA +void UHoudiniStaticMeshComponent::UpdateSpriteComponent() +{ + if (SpriteComponent) + { + SpriteComponent->SetRelativeLocation(FVector(0, 0, LocalBounds.GetExtent().Z)); + SpriteComponent->SetVisibility(bHoudiniIconVisible); + } +} +#endif + +int32 UHoudiniStaticMeshComponent::GetNumMaterials() const +{ + // From UStaticMesh: + // @note : you don't have to consider Materials.Num() + // that only counts if overridden and it can't be more than GetStaticMesh()->Materials. + if (Mesh) + { + return Mesh->GetNumStaticMaterials(); + } + else + { + return 0; + } +} + +int32 UHoudiniStaticMeshComponent::GetMaterialIndex(FName MaterialSlotName) const +{ + return Mesh ? Mesh->GetMaterialIndex(MaterialSlotName) : -1; +} + +TArray UHoudiniStaticMeshComponent::GetMaterialSlotNames() const +{ + TArray MaterialNames; + if (Mesh) + { + const TArray &StaticMaterials = Mesh->GetStaticMaterials(); + const int32 NumMaterials = StaticMaterials.Num(); + for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) + { + const FStaticMaterial &StaticMaterial = StaticMaterials[MaterialIndex]; + MaterialNames.Add(StaticMaterial.MaterialSlotName); + } + } + return MaterialNames; +} + +bool UHoudiniStaticMeshComponent::IsMaterialSlotNameValid(FName MaterialSlotName) const +{ + return GetMaterialIndex(MaterialSlotName) >= 0; +} + +UMaterialInterface* UHoudiniStaticMeshComponent::GetMaterial(int32 MaterialIndex) const +{ + // From UStaticMesh: + // If we have a base materials array, use that + if (OverrideMaterials.IsValidIndex(MaterialIndex) && OverrideMaterials[MaterialIndex]) + { + return OverrideMaterials[MaterialIndex]; + } + // Otherwise get from static mesh + else + { + return Mesh ? Mesh->GetMaterial(MaterialIndex) : nullptr; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h index 98910af02..e428c9d4e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h @@ -1,98 +1,98 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Components/MeshComponent.h" - -#include "HoudiniStaticMeshComponent.generated.h" - -class UHoudiniStaticMesh; -class UBillboardComponent; - -UCLASS(EditInlineNew, ClassGroup = "Houdini Engine | Rendering") -class HOUDINIENGINERUNTIME_API UHoudiniStaticMeshComponent : public UMeshComponent -{ - GENERATED_BODY() - -public: - UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer); - - UFUNCTION() - void SetMesh(UHoudiniStaticMesh *InMesh); - - UFUNCTION() - UHoudiniStaticMesh* GetMesh() { return Mesh; } - - // Call this if the mesh updated (outside of calling SetMesh). - UFUNCTION() - void NotifyMeshUpdated(); - - virtual void OnRegister() override; - - //virtual void PostLoad() override; - - // UPrimitiveComponent interface - virtual FPrimitiveSceneProxy* CreateSceneProxy() override; - virtual int32 GetNumMaterials() const override; - virtual UMaterialInterface* GetMaterial(int32 ElementIndex) const override; - virtual int32 GetMaterialIndex(FName MaterialSlotName) const override; - virtual TArray GetMaterialSlotNames() const override; - virtual bool IsMaterialSlotNameValid(FName MaterialSlotName) const override; - // end - UPrimitiveComponent interface - - // USceneComponent Interface. - virtual FBoxSphereBounds CalcBounds(const FTransform& InLocalToWorld) const override; - // end - USceneComponent Interface. - -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; -#endif - - UFUNCTION() - bool IsHoudiniIconVisible() const { return bHoudiniIconVisible; } - - UFUNCTION() - void SetHoudiniIconVisible(bool bInHoudiniIconVisible); - -protected: -#if WITH_EDITORONLY_DATA - virtual void UpdateSpriteComponent(); -#endif - - /** The mesh. */ - UPROPERTY(EditAnywhere, Category = "Mesh") - UHoudiniStaticMesh *Mesh; - - /** Local space bounds of mesh. */ - UPROPERTY() - FBox LocalBounds; - - UPROPERTY(EditAnywhere, Category = "Icons") - bool bHoudiniIconVisible; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Components/MeshComponent.h" + +#include "HoudiniStaticMeshComponent.generated.h" + +class UHoudiniStaticMesh; +class UBillboardComponent; + +UCLASS(EditInlineNew, ClassGroup = "Houdini Engine | Rendering") +class HOUDINIENGINERUNTIME_API UHoudiniStaticMeshComponent : public UMeshComponent +{ + GENERATED_BODY() + +public: + UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer); + + UFUNCTION() + void SetMesh(UHoudiniStaticMesh *InMesh); + + UFUNCTION() + UHoudiniStaticMesh* GetMesh() { return Mesh; } + + // Call this if the mesh updated (outside of calling SetMesh). + UFUNCTION() + void NotifyMeshUpdated(); + + virtual void OnRegister() override; + + //virtual void PostLoad() override; + + // UPrimitiveComponent interface + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + virtual int32 GetNumMaterials() const override; + virtual UMaterialInterface* GetMaterial(int32 ElementIndex) const override; + virtual int32 GetMaterialIndex(FName MaterialSlotName) const override; + virtual TArray GetMaterialSlotNames() const override; + virtual bool IsMaterialSlotNameValid(FName MaterialSlotName) const override; + // end - UPrimitiveComponent interface + + // USceneComponent Interface. + virtual FBoxSphereBounds CalcBounds(const FTransform& InLocalToWorld) const override; + // end - USceneComponent Interface. + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + UFUNCTION() + bool IsHoudiniIconVisible() const { return bHoudiniIconVisible; } + + UFUNCTION() + void SetHoudiniIconVisible(bool bInHoudiniIconVisible); + +protected: +#if WITH_EDITORONLY_DATA + virtual void UpdateSpriteComponent(); +#endif + + /** The mesh. */ + UPROPERTY(EditAnywhere, Category = "Mesh") + UHoudiniStaticMesh *Mesh; + + /** Local space bounds of mesh. */ + UPROPERTY() + FBox LocalBounds; + + UPROPERTY(EditAnywhere, Category = "Icons") + bool bHoudiniIconVisible; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp index da8c7c112..775b438a2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp @@ -1,541 +1,541 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMeshSceneProxy.h" - -#include "Async/ParallelFor.h" -#include "Materials/Material.h" -#include "PrimitiveViewRelevance.h" -#include "Engine/Engine.h" - -#include "ProfilingDebugging/CpuProfilerTrace.h" - -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniStaticMesh.h" - -// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h - -// -// FHoudiniStaticMeshRenderBufferSet -// - -FHoudiniStaticMeshRenderBufferSet::FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type InFeatureLevel) - : LocalVertexFactory(InFeatureLevel, "FHoudiniStaticMeshRenderBufferSet") -{ -} - - -FHoudiniStaticMeshRenderBufferSet::~FHoudiniStaticMeshRenderBufferSet() -{ - check(IsInRenderingThread()); - - if (NumTriangles > 0) - { - PositionVertexBuffer.ReleaseResource(); - ColorVertexBuffer.ReleaseResource(); - StaticMeshVertexBuffer.ReleaseResource(); - LocalVertexFactory.ReleaseResource(); - if (TriangleIndexBuffer.IsInitialized()) - { - TriangleIndexBuffer.ReleaseResource(); - } - } -} - -void FHoudiniStaticMeshRenderBufferSet::CopyBuffers() -{ - check(IsInRenderingThread()); - - if (NumTriangles == 0) - { - return; - } - - InitOrUpdateResource(&PositionVertexBuffer); - InitOrUpdateResource(&ColorVertexBuffer); - InitOrUpdateResource(&StaticMeshVertexBuffer); - - FLocalVertexFactory::FDataType Data; - PositionVertexBuffer.BindPositionVertexBuffer(&LocalVertexFactory, Data); - StaticMeshVertexBuffer.BindTangentVertexBuffer(&LocalVertexFactory, Data); - StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&LocalVertexFactory, Data); - ColorVertexBuffer.BindColorVertexBuffer(&LocalVertexFactory, Data); - - LocalVertexFactory.SetData(Data); - InitOrUpdateResource(&LocalVertexFactory); - - if (TriangleIndexBuffer.Indices.Num() > 0) - { - TriangleIndexBuffer.InitResource(); - } -} - -void FHoudiniStaticMeshRenderBufferSet::InitOrUpdateResource(FRenderResource* Resource) -{ - check(IsInRenderingThread()); - - if (Resource->IsInitialized()) - Resource->UpdateRHI(); - else - Resource->InitResource(); -} - -void FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet) -{ - if (BufferSet->NumTriangles == 0) - { - return; - } - - ENQUEUE_RENDER_COMMAND(FMeshRenderBufferSetDestroy)( - [BufferSet](FRHICommandListImmediate& RHICmdList) - { - delete BufferSet; - }); -} - -// -// End - FHoudiniStaticMeshRenderBufferSet -// - -// -// FHoudiniStaticMeshSceneProxy -// - -FHoudiniStaticMeshSceneProxy::FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel) - : FPrimitiveSceneProxy(InComponent) - , DefaultVertexColor(255, 255, 255) - , FeatureLevel(InFeatureLevel) - , Component(InComponent) - , MaterialRelevance(InComponent ? InComponent->GetMaterialRelevance(InFeatureLevel) : FMaterialRelevance()) -{ -} - -FHoudiniStaticMeshSceneProxy::~FHoudiniStaticMeshSceneProxy() -{ - check(IsInRenderingThread()); - - for (FHoudiniStaticMeshRenderBufferSet* BufferSet : BufferSets) - { - FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); - } -} - -uint32 FHoudiniStaticMeshSceneProxy::AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet) -{ - OutBufferSet = MakeNewBufferSet(); - OutBufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); - - BufferSetsLock.Lock(); - const uint32 NewIndex = BufferSets.Add(OutBufferSet); - BufferSetsLock.Unlock(); - return NewIndex; -} - -void FHoudiniStaticMeshSceneProxy::ReleaseRenderBufferSet(uint32 InIndex) -{ - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - - BufferSetsLock.Lock(); - check(BufferSets.IsValidIndex(InIndex)); - BufferSet = BufferSets[InIndex]; - BufferSets.RemoveAt(InIndex); - BufferSetsLock.Unlock(); - - FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); -} - -void FHoudiniStaticMeshSceneProxy::UpdatedReferencedMaterials() -{ - // copied from FPrimitiveSceneProxy::FPrimitiveSceneProxy() -#if WITH_EDITOR - TArray Materials; - Component->GetUsedMaterials(Materials, true); - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshRenderBufferSetDestroy)( - [this, Materials](FRHICommandListImmediate& RHICmdList) - { - this->SetUsedMaterialForVerification(Materials); - }); -#endif -} - -void FHoudiniStaticMeshSceneProxy::Build() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::Build")); - - // Allocate a buffer set per material - const uint32 NumMaterials = GetNumMaterials(); - if (NumMaterials == 0) - { - // No materials, allocate a singel buffer set using the default material - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - AllocateNewRenderBufferSet(BufferSet); - BufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); - } - else - { - for (uint32 MaterialIdx = 0; MaterialIdx < NumMaterials; ++MaterialIdx) - { - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - AllocateNewRenderBufferSet(BufferSet); - BufferSet->Material = GetMaterial(MaterialIdx); - } - } - - if (Component) - { - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (Mesh) - { - if (NumMaterials > 1 && Mesh->HasPerFaceMaterials()) - { - BuildBufferSetsByMaterial(); - } - else - { - BuildSingleBufferSet(); - } - } - } -} - -void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const -{ - const bool bRenderAsWireframe = (AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe); - - // Set up the wireframe material - FMaterialRenderProxy *WireframeMaterialProxy = nullptr; - if (bRenderAsWireframe) - { - FColoredMaterialRenderProxy *WireframeMaterialInstance = new FColoredMaterialRenderProxy( - GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, - FLinearColor(0.6f, 0.6f, 0.6f) - ); - Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); - WireframeMaterialProxy = WireframeMaterialInstance; - } - - ESceneDepthPriorityGroup DepthPriority = SDPG_World; - - const int32 NumViews = Views.Num(); - for (int32 ViewIdx = 0; ViewIdx < NumViews; ++ViewIdx) - { - if (!(VisibilityMap & (1 << ViewIdx))) - continue; - - const FSceneView *View = Views[ViewIdx]; - - bool bHasPrecomputedVolumetricLightmap; - FMatrix PreviousLocalToWorld; - int32 SingleCaptureIndex; - bool bOutputVelocity; - GetScene().GetPrimitiveUniformShaderParameters_RenderThread( - GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity); - - const uint32 NumBufferSets = BufferSets.Num(); - for (uint32 BufferSetIdx = 0; BufferSetIdx < NumBufferSets; ++BufferSetIdx) - { - FHoudiniStaticMeshRenderBufferSet *BufferSet = BufferSets[BufferSetIdx]; - - UMaterialInterface *Material = BufferSet->Material; - FMaterialRenderProxy *MaterialProxy = Material->GetRenderProxy(); - - if (BufferSet->NumTriangles == 0) - continue; - - FDynamicPrimitiveUniformBuffer &DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); - DynamicPrimitiveUniformBuffer.Set( - GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity); - - if (BufferSet->TriangleIndexBuffer.Indices.Num() > 0) - { - FMeshBatch& Mesh = Collector.AllocateMesh(); - if (PopulateMeshElement(Mesh, *BufferSet, MaterialProxy, false, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) - { - Collector.AddMesh(ViewIdx, Mesh); - } - if (bRenderAsWireframe) - { - FMeshBatch& WireframeMesh = Collector.AllocateMesh(); - if (PopulateMeshElement(WireframeMesh, *BufferSet, WireframeMaterialProxy, true, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) - { - Collector.AddMesh(ViewIdx, WireframeMesh); - } - } - } - } - } -} - -bool FHoudiniStaticMeshSceneProxy::PopulateMeshElement( - FMeshBatch &InMeshBatch, - const FHoudiniStaticMeshRenderBufferSet& Buffers, - FMaterialRenderProxy* Material, - bool bRenderAsWireframe, - ESceneDepthPriorityGroup DepthPriority, - int ViewIndex, - FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const -{ - FMeshBatchElement& BatchElement = InMeshBatch.Elements[0]; - BatchElement.IndexBuffer = &Buffers.TriangleIndexBuffer; - InMeshBatch.bWireframe = bRenderAsWireframe; - InMeshBatch.VertexFactory = &Buffers.LocalVertexFactory; - InMeshBatch.MaterialRenderProxy = Material; - - BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; - - BatchElement.FirstIndex = 0; - BatchElement.NumPrimitives = Buffers.NumTriangles; - BatchElement.MinVertexIndex = 0; - BatchElement.MaxVertexIndex = Buffers.PositionVertexBuffer.GetNumVertices() - 1; - InMeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative(); - InMeshBatch.Type = PT_TriangleList; - InMeshBatch.DepthPriorityGroup = DepthPriority; - InMeshBatch.bCanApplyViewModeOverrides = false; - - return true; -} - - -FPrimitiveViewRelevance FHoudiniStaticMeshSceneProxy::GetViewRelevance(const FSceneView* View) const -{ - FPrimitiveViewRelevance Result; - - Result.bDrawRelevance = IsShown(View); - Result.bDynamicRelevance = true; - Result.bRenderCustomDepth = ShouldRenderCustomDepth(); - Result.bRenderInMainPass = ShouldRenderInMainPass(); - Result.bShadowRelevance = IsShadowCast(View); - Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; - Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); - MaterialRelevance.SetPrimitiveViewRelevance(Result); - Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass; - - return Result; -} - -bool FHoudiniStaticMeshSceneProxy::CanBeOccluded() const -{ - return !MaterialRelevance.bDisableDepthTest; -} - -void FHoudiniStaticMeshSceneProxy::PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs, uint32 InTriangleGroupStartIdx, uint32 InNumTrianglesInGroup) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::PopulateBuffers")); - - check(InMesh); - check(InBuffers); - - const uint32 NumTriangles = InTriangleIDs ? InNumTrianglesInGroup : InMesh->GetNumTriangles(); - InBuffers->NumTriangles = NumTriangles; - - if (NumTriangles == 0) - return; - - const uint32 NumVertices = NumTriangles * 3; - const uint32 NumUVLayers = InMesh->GetNumUVLayers(); - - InBuffers->PositionVertexBuffer.Init(NumVertices); - // There must be at least one UV layer - // TODO: Would it be possible to have no UV layers and bind to a dummy 0/black SRV? - InBuffers->StaticMeshVertexBuffer.Init(NumVertices, NumUVLayers > 0 ? NumUVLayers : 1); - InBuffers->ColorVertexBuffer.Init(NumVertices); - InBuffers->TriangleIndexBuffer.Indices.AddUninitialized(NumTriangles * 3); - - const TArray& VertexPositions = InMesh->GetVertexPositions(); - const TArray& TriangleIndices = InMesh->GetTriangleIndices(); - const TArray& VertexInstanceColors = InMesh->GetVertexInstanceColors(); - const TArray& VertexInstanceNormals = InMesh->GetVertexInstanceNormals(); - const TArray& VertexInstanceUTangents = InMesh->GetVertexInstanceUTangents(); - const TArray& VertexInstanceVTangents = InMesh->GetVertexInstanceVTangents(); - const TArray& VertexInstanceUVs = InMesh->GetVertexInstanceUVs(); - - const bool bHasColors = InMesh->HasColors(); - const bool bHasNormals = InMesh->HasNormals(); - const bool bHasTangents = InMesh->HasTangents(); - - FThreadSafeCounter VertCounter(0); - //for (uint32 TriangleIDIdx = 0; TriangleIDIdx < NumTriangles; ++TriangleIDIdx) - ParallelFor(NumTriangles, [&](uint32 TriangleIDIdx) - { - const uint32 TriangleID = InTriangleIDs ? (*InTriangleIDs)[InTriangleGroupStartIdx + TriangleIDIdx] : TriangleIDIdx; - const FIntVector &TriIndices = TriangleIndices[TriangleID]; - - FVector TangentU; - FVector TangentV; - uint32 VertIdx = VertCounter.Add(3); - for (uint8 TriVertIdx = 0; TriVertIdx < 3; ++TriVertIdx) - { - const uint32 MeshVtxIdx = TriIndices[TriVertIdx]; - const uint32 MeshVtxInstanceIdx = TriangleID * 3 + TriVertIdx; - - InBuffers->PositionVertexBuffer.VertexPosition(VertIdx) = VertexPositions[TriIndices[TriVertIdx]]; - - FVector Normal = bHasNormals ? VertexInstanceNormals[MeshVtxInstanceIdx] : FVector(0, 0, 1); - if (bHasTangents) - { - TangentU = VertexInstanceUTangents[MeshVtxInstanceIdx]; - TangentV = VertexInstanceVTangents[MeshVtxInstanceIdx]; - } - else - { - Normal.FindBestAxisVectors(TangentU, TangentV); - } - InBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, TangentU, TangentV, Normal); - - if (NumUVLayers > 0) - { - for (uint8 UVLayerIdx = 0; UVLayerIdx < NumUVLayers; ++UVLayerIdx) - { - InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, UVLayerIdx, VertexInstanceUVs[MeshVtxInstanceIdx]); - } - } - else - { - InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, 0, FVector2D::ZeroVector); - } - - InBuffers->ColorVertexBuffer.VertexColor(VertIdx) = bHasColors ? VertexInstanceColors[MeshVtxInstanceIdx] : DefaultVertexColor; - - InBuffers->TriangleIndexBuffer.Indices[VertIdx] = VertIdx; - VertIdx++; - } - }); -} - -void FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet")); - - if (!Component) - return; - - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (!Mesh) - return; - - if (BufferSets.Num() == 0) - return; - - FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets.Last(); - - PopulateBuffers(Mesh, Buffers); - - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( - [Buffers](FRHICommandListImmediate& RHICMdList) - { - Buffers->CopyBuffers(); - }); -} - -void FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial")); - - // We need to group tris by which material they use, and populate a buffer set for each group - if (!Component) - return; - - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (!Mesh) - return; - - if (BufferSets.Num() == 0) - return; - - const TArray& MaterialIDsPerTriangle = Mesh->GetMaterialIDsPerTriangle(); - - const uint32 NumTriangles = MaterialIDsPerTriangle.Num(); - const uint32 NumMaterials = GetNumMaterials(); - TArray TriCountPerMaterialSafe; - TriCountPerMaterialSafe.Init(FThreadSafeCounter(0), NumMaterials); - ParallelFor(NumTriangles, [&](uint32 TriangleID) - { - const int32 MatID = MaterialIDsPerTriangle[TriangleID]; - if (MatID >= 0 && (uint32) MatID < NumMaterials) - { - TriCountPerMaterialSafe[MatID].Increment(); - } - }); - - TArray TriCountPerMaterial; - TArray OffsetPerMaterial; - TArray WrittenPerMaterial; - TriCountPerMaterial.Init(0, NumMaterials); - OffsetPerMaterial.Init(0, NumMaterials); - WrittenPerMaterial.Init(FThreadSafeCounter(0), NumMaterials); - for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) - { - const uint32 Count = TriCountPerMaterialSafe[MatID].GetValue(); - TriCountPerMaterial[MatID] = Count; - if (MatID > 0) - { - OffsetPerMaterial[MatID] = OffsetPerMaterial[MatID - 1] + TriCountPerMaterial[MatID - 1]; - } - } - - TArray GroupTriangleIDs; - GroupTriangleIDs.Init(0, NumTriangles); - ParallelFor(NumTriangles, [&](uint32 TriangleID) - { - const int32 MatID = MaterialIDsPerTriangle[TriangleID]; - if (MatID >= 0 && (uint32) MatID < NumMaterials) - { - GroupTriangleIDs[OffsetPerMaterial[MatID] + WrittenPerMaterial[MatID].Add(1)] = TriangleID; - } - }); - - for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) - { - if (TriCountPerMaterial[MatID] == 0) - continue; - - FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets[MatID]; - - PopulateBuffers( - Mesh, Buffers, - &GroupTriangleIDs, OffsetPerMaterial[MatID], TriCountPerMaterial[MatID] - ); - - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( - [Buffers](FRHICommandListImmediate& RHICMdList) - { - Buffers->CopyBuffers(); - }); - } -} - -UMaterialInterface* FHoudiniStaticMeshSceneProxy::GetMaterial(uint32 InMaterialIdx) const -{ - if (!Component) - return UMaterial::GetDefaultMaterial(MD_Surface); - UMaterialInterface *Material = Component->GetMaterial(InMaterialIdx); - return Material ? Material : UMaterial::GetDefaultMaterial(MD_Surface); -} - -// -// End - FHoudiniStaticMeshSceneProxy -// +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMeshSceneProxy.h" + +#include "Async/ParallelFor.h" +#include "Materials/Material.h" +#include "PrimitiveViewRelevance.h" +#include "Engine/Engine.h" + +#include "ProfilingDebugging/CpuProfilerTrace.h" + +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniStaticMesh.h" + +// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h + +// +// FHoudiniStaticMeshRenderBufferSet +// + +FHoudiniStaticMeshRenderBufferSet::FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type InFeatureLevel) + : LocalVertexFactory(InFeatureLevel, "FHoudiniStaticMeshRenderBufferSet") +{ +} + + +FHoudiniStaticMeshRenderBufferSet::~FHoudiniStaticMeshRenderBufferSet() +{ + check(IsInRenderingThread()); + + if (NumTriangles > 0) + { + PositionVertexBuffer.ReleaseResource(); + ColorVertexBuffer.ReleaseResource(); + StaticMeshVertexBuffer.ReleaseResource(); + LocalVertexFactory.ReleaseResource(); + if (TriangleIndexBuffer.IsInitialized()) + { + TriangleIndexBuffer.ReleaseResource(); + } + } +} + +void FHoudiniStaticMeshRenderBufferSet::CopyBuffers() +{ + check(IsInRenderingThread()); + + if (NumTriangles == 0) + { + return; + } + + InitOrUpdateResource(&PositionVertexBuffer); + InitOrUpdateResource(&ColorVertexBuffer); + InitOrUpdateResource(&StaticMeshVertexBuffer); + + FLocalVertexFactory::FDataType Data; + PositionVertexBuffer.BindPositionVertexBuffer(&LocalVertexFactory, Data); + StaticMeshVertexBuffer.BindTangentVertexBuffer(&LocalVertexFactory, Data); + StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&LocalVertexFactory, Data); + ColorVertexBuffer.BindColorVertexBuffer(&LocalVertexFactory, Data); + + LocalVertexFactory.SetData(Data); + InitOrUpdateResource(&LocalVertexFactory); + + if (TriangleIndexBuffer.Indices.Num() > 0) + { + TriangleIndexBuffer.InitResource(); + } +} + +void FHoudiniStaticMeshRenderBufferSet::InitOrUpdateResource(FRenderResource* Resource) +{ + check(IsInRenderingThread()); + + if (Resource->IsInitialized()) + Resource->UpdateRHI(); + else + Resource->InitResource(); +} + +void FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet) +{ + if (BufferSet->NumTriangles == 0) + { + return; + } + + ENQUEUE_RENDER_COMMAND(FMeshRenderBufferSetDestroy)( + [BufferSet](FRHICommandListImmediate& RHICmdList) + { + delete BufferSet; + }); +} + +// +// End - FHoudiniStaticMeshRenderBufferSet +// + +// +// FHoudiniStaticMeshSceneProxy +// + +FHoudiniStaticMeshSceneProxy::FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel) + : FPrimitiveSceneProxy(InComponent) + , DefaultVertexColor(255, 255, 255) + , FeatureLevel(InFeatureLevel) + , Component(InComponent) + , MaterialRelevance(InComponent ? InComponent->GetMaterialRelevance(InFeatureLevel) : FMaterialRelevance()) +{ +} + +FHoudiniStaticMeshSceneProxy::~FHoudiniStaticMeshSceneProxy() +{ + check(IsInRenderingThread()); + + for (FHoudiniStaticMeshRenderBufferSet* BufferSet : BufferSets) + { + FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); + } +} + +uint32 FHoudiniStaticMeshSceneProxy::AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet) +{ + OutBufferSet = MakeNewBufferSet(); + OutBufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); + + BufferSetsLock.Lock(); + const uint32 NewIndex = BufferSets.Add(OutBufferSet); + BufferSetsLock.Unlock(); + return NewIndex; +} + +void FHoudiniStaticMeshSceneProxy::ReleaseRenderBufferSet(uint32 InIndex) +{ + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + + BufferSetsLock.Lock(); + check(BufferSets.IsValidIndex(InIndex)); + BufferSet = BufferSets[InIndex]; + BufferSets.RemoveAt(InIndex); + BufferSetsLock.Unlock(); + + FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); +} + +void FHoudiniStaticMeshSceneProxy::UpdatedReferencedMaterials() +{ + // copied from FPrimitiveSceneProxy::FPrimitiveSceneProxy() +#if WITH_EDITOR + TArray Materials; + Component->GetUsedMaterials(Materials, true); + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshRenderBufferSetDestroy)( + [this, Materials](FRHICommandListImmediate& RHICmdList) + { + this->SetUsedMaterialForVerification(Materials); + }); +#endif +} + +void FHoudiniStaticMeshSceneProxy::Build() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::Build")); + + // Allocate a buffer set per material + const uint32 NumMaterials = GetNumMaterials(); + if (NumMaterials == 0) + { + // No materials, allocate a singel buffer set using the default material + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + AllocateNewRenderBufferSet(BufferSet); + BufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); + } + else + { + for (uint32 MaterialIdx = 0; MaterialIdx < NumMaterials; ++MaterialIdx) + { + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + AllocateNewRenderBufferSet(BufferSet); + BufferSet->Material = GetMaterial(MaterialIdx); + } + } + + if (Component) + { + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (Mesh) + { + if (NumMaterials > 1 && Mesh->HasPerFaceMaterials()) + { + BuildBufferSetsByMaterial(); + } + else + { + BuildSingleBufferSet(); + } + } + } +} + +void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const +{ + const bool bRenderAsWireframe = (AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe); + + // Set up the wireframe material + FMaterialRenderProxy *WireframeMaterialProxy = nullptr; + if (bRenderAsWireframe) + { + FColoredMaterialRenderProxy *WireframeMaterialInstance = new FColoredMaterialRenderProxy( + GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, + FLinearColor(0.6f, 0.6f, 0.6f) + ); + Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); + WireframeMaterialProxy = WireframeMaterialInstance; + } + + ESceneDepthPriorityGroup DepthPriority = SDPG_World; + + const int32 NumViews = Views.Num(); + for (int32 ViewIdx = 0; ViewIdx < NumViews; ++ViewIdx) + { + if (!(VisibilityMap & (1 << ViewIdx))) + continue; + + const FSceneView *View = Views[ViewIdx]; + + bool bHasPrecomputedVolumetricLightmap; + FMatrix PreviousLocalToWorld; + int32 SingleCaptureIndex; + bool bOutputVelocity; + GetScene().GetPrimitiveUniformShaderParameters_RenderThread( + GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity); + + const uint32 NumBufferSets = BufferSets.Num(); + for (uint32 BufferSetIdx = 0; BufferSetIdx < NumBufferSets; ++BufferSetIdx) + { + FHoudiniStaticMeshRenderBufferSet *BufferSet = BufferSets[BufferSetIdx]; + + UMaterialInterface *Material = BufferSet->Material; + FMaterialRenderProxy *MaterialProxy = Material->GetRenderProxy(); + + if (BufferSet->NumTriangles == 0) + continue; + + FDynamicPrimitiveUniformBuffer &DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); + DynamicPrimitiveUniformBuffer.Set( + GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity); + + if (BufferSet->TriangleIndexBuffer.Indices.Num() > 0) + { + FMeshBatch& Mesh = Collector.AllocateMesh(); + if (PopulateMeshElement(Mesh, *BufferSet, MaterialProxy, false, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) + { + Collector.AddMesh(ViewIdx, Mesh); + } + if (bRenderAsWireframe) + { + FMeshBatch& WireframeMesh = Collector.AllocateMesh(); + if (PopulateMeshElement(WireframeMesh, *BufferSet, WireframeMaterialProxy, true, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) + { + Collector.AddMesh(ViewIdx, WireframeMesh); + } + } + } + } + } +} + +bool FHoudiniStaticMeshSceneProxy::PopulateMeshElement( + FMeshBatch &InMeshBatch, + const FHoudiniStaticMeshRenderBufferSet& Buffers, + FMaterialRenderProxy* Material, + bool bRenderAsWireframe, + ESceneDepthPriorityGroup DepthPriority, + int ViewIndex, + FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const +{ + FMeshBatchElement& BatchElement = InMeshBatch.Elements[0]; + BatchElement.IndexBuffer = &Buffers.TriangleIndexBuffer; + InMeshBatch.bWireframe = bRenderAsWireframe; + InMeshBatch.VertexFactory = &Buffers.LocalVertexFactory; + InMeshBatch.MaterialRenderProxy = Material; + + BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; + + BatchElement.FirstIndex = 0; + BatchElement.NumPrimitives = Buffers.NumTriangles; + BatchElement.MinVertexIndex = 0; + BatchElement.MaxVertexIndex = Buffers.PositionVertexBuffer.GetNumVertices() - 1; + InMeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative(); + InMeshBatch.Type = PT_TriangleList; + InMeshBatch.DepthPriorityGroup = DepthPriority; + InMeshBatch.bCanApplyViewModeOverrides = false; + + return true; +} + + +FPrimitiveViewRelevance FHoudiniStaticMeshSceneProxy::GetViewRelevance(const FSceneView* View) const +{ + FPrimitiveViewRelevance Result; + + Result.bDrawRelevance = IsShown(View); + Result.bDynamicRelevance = true; + Result.bRenderCustomDepth = ShouldRenderCustomDepth(); + Result.bRenderInMainPass = ShouldRenderInMainPass(); + Result.bShadowRelevance = IsShadowCast(View); + Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; + Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); + MaterialRelevance.SetPrimitiveViewRelevance(Result); + Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass; + + return Result; +} + +bool FHoudiniStaticMeshSceneProxy::CanBeOccluded() const +{ + return !MaterialRelevance.bDisableDepthTest; +} + +void FHoudiniStaticMeshSceneProxy::PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs, uint32 InTriangleGroupStartIdx, uint32 InNumTrianglesInGroup) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::PopulateBuffers")); + + check(InMesh); + check(InBuffers); + + const uint32 NumTriangles = InTriangleIDs ? InNumTrianglesInGroup : InMesh->GetNumTriangles(); + InBuffers->NumTriangles = NumTriangles; + + if (NumTriangles == 0) + return; + + const uint32 NumVertices = NumTriangles * 3; + const uint32 NumUVLayers = InMesh->GetNumUVLayers(); + + InBuffers->PositionVertexBuffer.Init(NumVertices); + // There must be at least one UV layer + // TODO: Would it be possible to have no UV layers and bind to a dummy 0/black SRV? + InBuffers->StaticMeshVertexBuffer.Init(NumVertices, NumUVLayers > 0 ? NumUVLayers : 1); + InBuffers->ColorVertexBuffer.Init(NumVertices); + InBuffers->TriangleIndexBuffer.Indices.AddUninitialized(NumTriangles * 3); + + const TArray& VertexPositions = InMesh->GetVertexPositions(); + const TArray& TriangleIndices = InMesh->GetTriangleIndices(); + const TArray& VertexInstanceColors = InMesh->GetVertexInstanceColors(); + const TArray& VertexInstanceNormals = InMesh->GetVertexInstanceNormals(); + const TArray& VertexInstanceUTangents = InMesh->GetVertexInstanceUTangents(); + const TArray& VertexInstanceVTangents = InMesh->GetVertexInstanceVTangents(); + const TArray& VertexInstanceUVs = InMesh->GetVertexInstanceUVs(); + + const bool bHasColors = InMesh->HasColors(); + const bool bHasNormals = InMesh->HasNormals(); + const bool bHasTangents = InMesh->HasTangents(); + + FThreadSafeCounter VertCounter(0); + //for (uint32 TriangleIDIdx = 0; TriangleIDIdx < NumTriangles; ++TriangleIDIdx) + ParallelFor(NumTriangles, [&](uint32 TriangleIDIdx) + { + const uint32 TriangleID = InTriangleIDs ? (*InTriangleIDs)[InTriangleGroupStartIdx + TriangleIDIdx] : TriangleIDIdx; + const FIntVector &TriIndices = TriangleIndices[TriangleID]; + + FVector TangentU; + FVector TangentV; + uint32 VertIdx = VertCounter.Add(3); + for (uint8 TriVertIdx = 0; TriVertIdx < 3; ++TriVertIdx) + { + const uint32 MeshVtxIdx = TriIndices[TriVertIdx]; + const uint32 MeshVtxInstanceIdx = TriangleID * 3 + TriVertIdx; + + InBuffers->PositionVertexBuffer.VertexPosition(VertIdx) = VertexPositions[TriIndices[TriVertIdx]]; + + FVector Normal = bHasNormals ? VertexInstanceNormals[MeshVtxInstanceIdx] : FVector(0, 0, 1); + if (bHasTangents) + { + TangentU = VertexInstanceUTangents[MeshVtxInstanceIdx]; + TangentV = VertexInstanceVTangents[MeshVtxInstanceIdx]; + } + else + { + Normal.FindBestAxisVectors(TangentU, TangentV); + } + InBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, TangentU, TangentV, Normal); + + if (NumUVLayers > 0) + { + for (uint8 UVLayerIdx = 0; UVLayerIdx < NumUVLayers; ++UVLayerIdx) + { + InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, UVLayerIdx, VertexInstanceUVs[MeshVtxInstanceIdx]); + } + } + else + { + InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, 0, FVector2D::ZeroVector); + } + + InBuffers->ColorVertexBuffer.VertexColor(VertIdx) = bHasColors ? VertexInstanceColors[MeshVtxInstanceIdx] : DefaultVertexColor; + + InBuffers->TriangleIndexBuffer.Indices[VertIdx] = VertIdx; + VertIdx++; + } + }); +} + +void FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet")); + + if (!Component) + return; + + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (!Mesh) + return; + + if (BufferSets.Num() == 0) + return; + + FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets.Last(); + + PopulateBuffers(Mesh, Buffers); + + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( + [Buffers](FRHICommandListImmediate& RHICMdList) + { + Buffers->CopyBuffers(); + }); +} + +void FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial")); + + // We need to group tris by which material they use, and populate a buffer set for each group + if (!Component) + return; + + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (!Mesh) + return; + + if (BufferSets.Num() == 0) + return; + + const TArray& MaterialIDsPerTriangle = Mesh->GetMaterialIDsPerTriangle(); + + const uint32 NumTriangles = MaterialIDsPerTriangle.Num(); + const uint32 NumMaterials = GetNumMaterials(); + TArray TriCountPerMaterialSafe; + TriCountPerMaterialSafe.Init(FThreadSafeCounter(0), NumMaterials); + ParallelFor(NumTriangles, [&](uint32 TriangleID) + { + const int32 MatID = MaterialIDsPerTriangle[TriangleID]; + if (MatID >= 0 && (uint32) MatID < NumMaterials) + { + TriCountPerMaterialSafe[MatID].Increment(); + } + }); + + TArray TriCountPerMaterial; + TArray OffsetPerMaterial; + TArray WrittenPerMaterial; + TriCountPerMaterial.Init(0, NumMaterials); + OffsetPerMaterial.Init(0, NumMaterials); + WrittenPerMaterial.Init(FThreadSafeCounter(0), NumMaterials); + for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) + { + const uint32 Count = TriCountPerMaterialSafe[MatID].GetValue(); + TriCountPerMaterial[MatID] = Count; + if (MatID > 0) + { + OffsetPerMaterial[MatID] = OffsetPerMaterial[MatID - 1] + TriCountPerMaterial[MatID - 1]; + } + } + + TArray GroupTriangleIDs; + GroupTriangleIDs.Init(0, NumTriangles); + ParallelFor(NumTriangles, [&](uint32 TriangleID) + { + const int32 MatID = MaterialIDsPerTriangle[TriangleID]; + if (MatID >= 0 && (uint32) MatID < NumMaterials) + { + GroupTriangleIDs[OffsetPerMaterial[MatID] + WrittenPerMaterial[MatID].Add(1)] = TriangleID; + } + }); + + for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) + { + if (TriCountPerMaterial[MatID] == 0) + continue; + + FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets[MatID]; + + PopulateBuffers( + Mesh, Buffers, + &GroupTriangleIDs, OffsetPerMaterial[MatID], TriCountPerMaterial[MatID] + ); + + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( + [Buffers](FRHICommandListImmediate& RHICMdList) + { + Buffers->CopyBuffers(); + }); + } +} + +UMaterialInterface* FHoudiniStaticMeshSceneProxy::GetMaterial(uint32 InMaterialIdx) const +{ + if (!Component) + return UMaterial::GetDefaultMaterial(MD_Surface); + UMaterialInterface *Material = Component->GetMaterial(InMaterialIdx); + return Material ? Material : UMaterial::GetDefaultMaterial(MD_Surface); +} + +// +// End - FHoudiniStaticMeshSceneProxy +// diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h index 66e474054..8de764e79 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h @@ -1,168 +1,168 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h - -#pragma once - -#include "CoreMinimal.h" -#include "PrimitiveSceneProxy.h" -#include "VertexFactory.h" -#include "LocalVertexFactory.h" -#include "Rendering/ColorVertexBuffer.h" -#include "Rendering/PositionVertexBuffer.h" -#include "Rendering/StaticMeshVertexBuffer.h" -#include "DynamicMeshBuilder.h" - -#include "HoudiniStaticMeshComponent.h" - -class UHoudiniStaticMesh; - -class FHoudiniStaticMeshRenderBufferSet -{ -public: - // Data members - - /** The number of triangles in the buffer set. */ - int NumTriangles; - - /** The static mesh data buffer. */ - FStaticMeshVertexBuffer StaticMeshVertexBuffer; - - /** The position buffer. */ - FPositionVertexBuffer PositionVertexBuffer; - - /** The triangle indices buffer. */ - FDynamicMeshIndexBuffer32 TriangleIndexBuffer; - - /** The color buffer */ - FColorVertexBuffer ColorVertexBuffer; - - FLocalVertexFactory LocalVertexFactory; - - /** Default material for this mesh. */ - UMaterialInterface* Material = nullptr; - - // Functions - - FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type FeatureLevelType); - - virtual ~FHoudiniStaticMeshRenderBufferSet(); - - /** - * Copy buffers to GPU. - * @warning render thread only. - */ - virtual void CopyBuffers(); - - /** - * Initialize (or update) a render resource. - * @warning Render thread only. - */ - void InitOrUpdateResource(FRenderResource* Resource); - -protected: - friend class FHoudiniStaticMeshSceneProxy; - - // Queue a command on the render thread to destroy the given buffer set - static void DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet); -}; - - -class FHoudiniStaticMeshSceneProxy : public FPrimitiveSceneProxy -{ -public: - FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel); - - virtual ~FHoudiniStaticMeshSceneProxy(); - - uint32 AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet); - - void ReleaseRenderBufferSet(uint32 InIndex); - - void UpdatedReferencedMaterials(); - - // Build buffer sets to render the mesh. - virtual void Build(); - - // FPrimitiveSceneProxy - virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; - - virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; - - virtual bool CanBeOccluded() const override; - - virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + FPrimitiveSceneProxy::GetAllocatedSize()); } - - SIZE_T GetTypeHash() const override - { - static size_t UniquePointer; - return reinterpret_cast(&UniquePointer); - } - - // end - FPrimitiveSceneProxy - - // Color to use if vertex does not have an assigned color. - FColor DefaultVertexColor; - - ERHIFeatureLevel::Type FeatureLevel; - -protected: - void PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs=nullptr, uint32 InTriangleGroupStartIdx=0u, uint32 InNumTrianglesInGroup=0u); - - // Virtual function for creating a new buffer set instances. - // Subclasses can overwrite this is they use a different buffer set with - // different instantiation requirements. - virtual FHoudiniStaticMeshRenderBufferSet* MakeNewBufferSet() { return new FHoudiniStaticMeshRenderBufferSet(FeatureLevel); } - - // Build a single buffer set for the entire mesh (one material for the entire mesh). - void BuildSingleBufferSet(); - - void BuildBufferSetsByMaterial(); - - // Get the number of materials from the parent mesh/component - uint32 GetNumMaterials() const { return Component ? Component->GetNumMaterials() : 0; } - - virtual bool PopulateMeshElement( - FMeshBatch &InMeshBatch, - const FHoudiniStaticMeshRenderBufferSet& Buffers, - FMaterialRenderProxy* Material, - bool bRenderAsWireframe, - ESceneDepthPriorityGroup DepthPriority, - int ViewIndex, - FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const; - - virtual UMaterialInterface* GetMaterial(uint32 InMaterialIdx) const; - - UHoudiniStaticMeshComponent *Component; - - TArray BufferSets; - - FCriticalSection BufferSetsLock; - - FMaterialRelevance MaterialRelevance; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h + +#pragma once + +#include "CoreMinimal.h" +#include "PrimitiveSceneProxy.h" +#include "VertexFactory.h" +#include "LocalVertexFactory.h" +#include "Rendering/ColorVertexBuffer.h" +#include "Rendering/PositionVertexBuffer.h" +#include "Rendering/StaticMeshVertexBuffer.h" +#include "DynamicMeshBuilder.h" + +#include "HoudiniStaticMeshComponent.h" + +class UHoudiniStaticMesh; + +class FHoudiniStaticMeshRenderBufferSet +{ +public: + // Data members + + /** The number of triangles in the buffer set. */ + int NumTriangles; + + /** The static mesh data buffer. */ + FStaticMeshVertexBuffer StaticMeshVertexBuffer; + + /** The position buffer. */ + FPositionVertexBuffer PositionVertexBuffer; + + /** The triangle indices buffer. */ + FDynamicMeshIndexBuffer32 TriangleIndexBuffer; + + /** The color buffer */ + FColorVertexBuffer ColorVertexBuffer; + + FLocalVertexFactory LocalVertexFactory; + + /** Default material for this mesh. */ + UMaterialInterface* Material = nullptr; + + // Functions + + FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type FeatureLevelType); + + virtual ~FHoudiniStaticMeshRenderBufferSet(); + + /** + * Copy buffers to GPU. + * @warning render thread only. + */ + virtual void CopyBuffers(); + + /** + * Initialize (or update) a render resource. + * @warning Render thread only. + */ + void InitOrUpdateResource(FRenderResource* Resource); + +protected: + friend class FHoudiniStaticMeshSceneProxy; + + // Queue a command on the render thread to destroy the given buffer set + static void DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet); +}; + + +class FHoudiniStaticMeshSceneProxy : public FPrimitiveSceneProxy +{ +public: + FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel); + + virtual ~FHoudiniStaticMeshSceneProxy(); + + uint32 AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet); + + void ReleaseRenderBufferSet(uint32 InIndex); + + void UpdatedReferencedMaterials(); + + // Build buffer sets to render the mesh. + virtual void Build(); + + // FPrimitiveSceneProxy + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; + + virtual bool CanBeOccluded() const override; + + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + FPrimitiveSceneProxy::GetAllocatedSize()); } + + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast(&UniquePointer); + } + + // end - FPrimitiveSceneProxy + + // Color to use if vertex does not have an assigned color. + FColor DefaultVertexColor; + + ERHIFeatureLevel::Type FeatureLevel; + +protected: + void PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs=nullptr, uint32 InTriangleGroupStartIdx=0u, uint32 InNumTrianglesInGroup=0u); + + // Virtual function for creating a new buffer set instances. + // Subclasses can overwrite this is they use a different buffer set with + // different instantiation requirements. + virtual FHoudiniStaticMeshRenderBufferSet* MakeNewBufferSet() { return new FHoudiniStaticMeshRenderBufferSet(FeatureLevel); } + + // Build a single buffer set for the entire mesh (one material for the entire mesh). + void BuildSingleBufferSet(); + + void BuildBufferSetsByMaterial(); + + // Get the number of materials from the parent mesh/component + uint32 GetNumMaterials() const { return Component ? Component->GetNumMaterials() : 0; } + + virtual bool PopulateMeshElement( + FMeshBatch &InMeshBatch, + const FHoudiniStaticMeshRenderBufferSet& Buffers, + FMaterialRenderProxy* Material, + bool bRenderAsWireframe, + ESceneDepthPriorityGroup DepthPriority, + int ViewIndex, + FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const; + + virtual UMaterialInterface* GetMaterial(uint32 InMaterialIdx) const; + + UHoudiniStaticMeshComponent *Component; + + TArray BufferSets; + + FCriticalSection BufferSetsLock; + + FMaterialRelevance MaterialRelevance; + }; \ No newline at end of file From 79633ddb896fb4c263eb66401b015b6d43262e4f Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Tue, 19 Jan 2021 17:23:14 -0500 Subject: [PATCH 03/16] Updated Readme.md for the v2.0 release. --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9fb56de55..a09e3f438 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# Houdini Engine for Unreal - Version 2 - Beta +# Houdini Engine for Unreal - Version 2.0 -Welcome to the repository for the Beta of Version 2 of the Houdini Engine For Unreal Plugin. +Welcome to the repository for Version 2 of the Houdini Engine For Unreal Plugin. This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. -Here are some of the new features and improvements currently available in Beta1: +Here are some of the new features and improvements currently available: Core: @@ -56,23 +56,24 @@ General: This lets you preset and use HDAs on Blueprint Actors, and changing parameters/inputs on the Houdini Asset will automatically update all placed instances of that Blueprint. -For more details on the new features and improvements available in the Beta, please visit the [Wiki](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/wiki/What's-new-%3F). +For more details on the new features and improvements available, please visit the [Wiki](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/wiki/What's-new-%3F). +Documentation for version 2.0 of the plugin is also available on the Side FX [Website](https://www.sidefx.com/docs/unreal/). -Some of the new features in the beta are still in their early stage, and will be improved upon in subsequent release of the plugin. # Feedback -Please use this repository's "Issues" to report any bugs, problems, or simply to give us feedback on your experience with version2. +Please send bug reports, feature requests and questions to [Side FX's support](https://www.sidefx.com/bugs/submit/). + # Compatibility -Currently, the [Beta1](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/releases) release of V2 has binaries that have been built for UE4.25 and UE4.24, and is linked with the latest production build of Houdini, H18.0.597. +Currently, [Version 2.0](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/releases) has binaries that have been built for UE4.26 and UE4.25, and is linked with the latest production build of Houdini, H18.5.462. -Source code for the plugin is available on this repository for UE4.25, UE4.24, UE4.23 and the master branch of Unreal (4.26). +Source code for the plugin is available on this repository for UE4.26, UE4.25, UE4.24, UE4.23 and the master branch of Unreal (4.27). -As of Beta1, Version 2 is now backward compatible with version 1 of the Houdini Engine for Unreal plugin. +Version 2 is also partially backward compatible with version 1 of the Houdini Engine for Unreal plugin. -When loading a level that contains Houdini objects made with version 1, the plugin now tries to convert the V1 components, parameters, inputs and outputs to their v2 equivalents. +When loading a level that contains Houdini objects made with version 1, the plugin will attempt to convert the V1 components, parameters, inputs and outputs to their v2 equivalents. Some HDAs might need to be rebuilt after the conversion for their parameters and inputs to be displayed properly by the v2 plugin. From d05d7b86620913e5f87334646e5d1bb9ba5e416a Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Tue, 26 Jan 2021 09:51:48 -0500 Subject: [PATCH 04/16] Fixed Linux build error due to case issue. --- Source/HoudiniEngine/Private/HoudiniHandleTranslator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h index 55e60ae4f..93bf71fbe 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h @@ -27,7 +27,7 @@ #pragma once #include "HAPI/HAPI_Common.h" -#include "HoudiniAPI.h" +#include "HoudiniApi.h" #include "Templates/SharedPointer.h" From 32153758f7f04773bc22584850c26b7049c0edc9 Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Thu, 28 Jan 2021 21:03:18 -0500 Subject: [PATCH 05/16] Unreal: Version 2 - Updated FHoudiniStaticMeshGenerationProperties to fix Linux build issues. --- .../Private/HoudiniRuntimeSettings.cpp | 805 +++++++++--------- 1 file changed, 402 insertions(+), 403 deletions(-) diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp index 73bd0e8c3..9bcc4c9a8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp @@ -1,404 +1,403 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniRuntimeSettings.h" - - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Misc/Paths.h" -// #include "Internationalization/Internationalization.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - - -FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() -{ - bGeneratedDoubleSidedGeometry = false; - GeneratedPhysMaterial = nullptr; - DefaultBodyInstance.SetCollisionProfileName("BlockAll"); - GeneratedCollisionTraceFlag = CTF_UseDefault; - GeneratedLpvBiasMultiplier = 1.0f; - GeneratedLightMapResolution = 64; - GeneratedLightMapCoordinateIndex = 1; - bGeneratedUseMaximumStreamingTexelRatio = false; - GeneratedStreamingDistanceMultiplier = 1.0f; - GeneratedDistanceFieldResolutionScale = 2.0f; - - GeneratedWalkableSlopeOverride; - GeneratedFoliageDefaultSettings; - GeneratedAssetUserData; -} - - -UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) - : Super( ObjectInitializer ) -{ - // Session options. - SessionType = HRSST_NamedPipe; - ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; - ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; - ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; - bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; - AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; - - bSyncWithHoudiniCook = true; - bCookUsingHoudiniTime = true; - bSyncViewport = false; - bSyncHoudiniViewport = false; - bSyncUnrealViewport = false; - - // Instantiating options. - bShowMultiAssetDialog = true; - - // Cooking options. - bPauseCookingOnStart = false; - bDisplaySlateCookingNotifications = true; - DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - // Parameter options - //bTreatRampParametersAsMultiparms = false; - - // Custom Houdini location. - bUseCustomHoudiniLocation = false; - CustomHoudiniLocation.Path = TEXT(""); - - // Arguments for HAPI_Initialize - CookingThreadStackSize = -1; - - // Landscape marshalling default values. - MarshallingLandscapesUseDefaultUnrealScaling = false; - MarshallingLandscapesUseFullResolution = true; - MarshallingLandscapesForceMinMaxValues = false; - MarshallingLandscapesForcedMinValue = -2000.0f; - MarshallingLandscapesForcedMaxValue = 4553.0f; - - // Spline marshalling - MarshallingSplineResolution = 50.0f; - - // Static mesh proxy refinement settings - bEnableProxyStaticMesh = false; - bShowDefaultMesh = true; - bEnableProxyStaticMeshRefinementByTimer = true; - ProxyMeshAutoRefineTimeoutSeconds = 10.0f; - bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; - bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; - - // Generated StaticMesh settings. - bDoubleSidedGeometry = false; - PhysMaterial = nullptr; - DefaultBodyInstance.SetCollisionProfileName("BlockAll"); - CollisionTraceFlag = CTF_UseDefault; - LightMapResolution = 32; - LpvBiasMultiplier = 1.0f; - LightMapCoordinateIndex = 1; - bUseMaximumStreamingTexelRatio = false; - StreamingDistanceMultiplier = 1.0f; - GeneratedDistanceFieldResolutionScale = 0.0f; - - // Static Mesh build settings. - bUseFullPrecisionUVs = false; - SrcLightmapIndex = 0; - DstLightmapIndex = 1; - MinLightmapResolution = 64; - bRemoveDegenerates = true; - GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; - RecomputeNormalsFlag = HRSRF_OnlyIfMissing; - RecomputeTangentsFlag = HRSRF_OnlyIfMissing; - bUseMikkTSpace = true; - bBuildAdjacencyBuffer = true; // v1 default false - - bComputeWeightedNormals = false; - bBuildReversedIndexBuffer = true; - bUseHighPrecisionTangentBasis = false; - bGenerateDistanceFieldAsIfTwoSided = false; - bSupportFaceRemap = false; - //BuildScale3D = FVector(1.0f, 1.0f, 1.0f); - DistanceFieldResolutionScale = 2.0f; // ue default is 1.0 - - bPDGAsyncCommandletImportEnabled = false; - - // Legacy settings - bEnableBackwardCompatibility = true; - bAutomaticLegacyHDARebuild = false; -} - -UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() -{} - - -FProperty * -UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const -{ - for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) - { - FProperty * Property = *PropIt; - - if (Property->GetNameCPP() == PropertyName) - return Property; - } - - return nullptr; -} - - -void -UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) -{ - FProperty * Property = LocateProperty(PropertyName); - if (Property) - { - if (bReadOnly) - Property->SetPropertyFlags(CPF_EditConst); - else - Property->ClearPropertyFlags(CPF_EditConst); - } -} - - -void -UHoudiniRuntimeSettings::PostInitProperties() -{ - Super::PostInitProperties(); - - // Set Collision generation options as read only - { - if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Set marshalling attributes options as read only - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - /* - // Set Cook Folder as read-only - { - if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) - Property->SetPropertyFlags( CPF_EditConst ); - } - */ - - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - Property->SetPropertyFlags(CPF_EditConst); - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Disable UI elements depending on current session type. -#if WITH_EDITOR - - UpdateSessionUI(); - -#endif // WITH_EDITOR - - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); -} - - -#if WITH_EDITOR - -void -UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - FProperty * Property = PropertyChangedEvent.MemberProperty; - FProperty * LookupProperty = nullptr; - - if (!Property) - return; - if (Property->GetName() == TEXT("SessionType")) - UpdateSessionUI(); - else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); - else if (Property->GetName() == TEXT("CustomHoudiniLocation")) - { - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; - FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); - - // If path does not point to libHAPI location, we need to let user know. - if (!FPaths::FileExists(LibHAPICustomPath)) - { - FString MessageString = FString::Printf( - TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); - - FPlatformMisc::MessageBoxExt( - EAppMsgType::Ok, *MessageString, - TEXT("Invalid Custom Location Specified, resetting.")); - - CustomHoudiniLocationPath = TEXT(""); - } - } - else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) - { - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->SetPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->SetPropertyFlags(CPF_EditConst); - } - else - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->ClearPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->ClearPropertyFlags(CPF_EditConst); - } - } - - /* - if ( Property->GetName() == TEXT( "bEnableCooking" ) ) - { - // Cooking is disabled, we need to disable transform change triggers cooks option is as well. - if ( bEnableCooking ) - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) - { - // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. - if ( bUploadTransformsToHoudiniEngine ) - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - */ -} - - - -void -UHoudiniRuntimeSettings::UpdateSessionUI() -{ - SetPropertyReadOnly(TEXT("ServerHost"), true); - SetPropertyReadOnly(TEXT("ServerPort"), true); - SetPropertyReadOnly(TEXT("ServerPipeName"), true); - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); - - bool bServerType = false; - - switch (SessionType) - { - case HRSST_Socket: - { - SetPropertyReadOnly(TEXT("ServerHost"), false); - SetPropertyReadOnly(TEXT("ServerPort"), false); - bServerType = true; - break; - } - - case HRSST_NamedPipe: - { - SetPropertyReadOnly(TEXT("ServerPipeName"), false); - bServerType = true; - break; - } - - default: - break; - } - - if (bServerType) - { - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); - } -} - -#endif // WITH_EDITOR - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniRuntimeSettings.h" + + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Misc/Paths.h" +// #include "Internationalization/Internationalization.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + + +FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() + : bGeneratedDoubleSidedGeometry(false) + , GeneratedPhysMaterial(nullptr) + , GeneratedCollisionTraceFlag(CTF_UseDefault) + , GeneratedLightMapResolution(64) + , GeneratedLpvBiasMultiplier(1.0f) + , GeneratedDistanceFieldResolutionScale(2.0f) + , GeneratedWalkableSlopeOverride() + , GeneratedLightMapCoordinateIndex(1) + , bGeneratedUseMaximumStreamingTexelRatio(false) + , GeneratedStreamingDistanceMultiplier(1.0f) + , GeneratedFoliageDefaultSettings(nullptr) + , GeneratedAssetUserData() +{ + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); +} + + +UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // Session options. + SessionType = HRSST_NamedPipe; + ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; + ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; + ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; + bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; + AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; + + bSyncWithHoudiniCook = true; + bCookUsingHoudiniTime = true; + bSyncViewport = false; + bSyncHoudiniViewport = false; + bSyncUnrealViewport = false; + + // Instantiating options. + bShowMultiAssetDialog = true; + + // Cooking options. + bPauseCookingOnStart = false; + bDisplaySlateCookingNotifications = true; + DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + // Parameter options + //bTreatRampParametersAsMultiparms = false; + + // Custom Houdini location. + bUseCustomHoudiniLocation = false; + CustomHoudiniLocation.Path = TEXT(""); + + // Arguments for HAPI_Initialize + CookingThreadStackSize = -1; + + // Landscape marshalling default values. + MarshallingLandscapesUseDefaultUnrealScaling = false; + MarshallingLandscapesUseFullResolution = true; + MarshallingLandscapesForceMinMaxValues = false; + MarshallingLandscapesForcedMinValue = -2000.0f; + MarshallingLandscapesForcedMaxValue = 4553.0f; + + // Spline marshalling + MarshallingSplineResolution = 50.0f; + + // Static mesh proxy refinement settings + bEnableProxyStaticMesh = false; + bShowDefaultMesh = true; + bEnableProxyStaticMeshRefinementByTimer = true; + ProxyMeshAutoRefineTimeoutSeconds = 10.0f; + bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; + bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; + + // Generated StaticMesh settings. + bDoubleSidedGeometry = false; + PhysMaterial = nullptr; + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); + CollisionTraceFlag = CTF_UseDefault; + LightMapResolution = 32; + LpvBiasMultiplier = 1.0f; + LightMapCoordinateIndex = 1; + bUseMaximumStreamingTexelRatio = false; + StreamingDistanceMultiplier = 1.0f; + GeneratedDistanceFieldResolutionScale = 0.0f; + + // Static Mesh build settings. + bUseFullPrecisionUVs = false; + SrcLightmapIndex = 0; + DstLightmapIndex = 1; + MinLightmapResolution = 64; + bRemoveDegenerates = true; + GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; + RecomputeNormalsFlag = HRSRF_OnlyIfMissing; + RecomputeTangentsFlag = HRSRF_OnlyIfMissing; + bUseMikkTSpace = true; + bBuildAdjacencyBuffer = true; // v1 default false + + bComputeWeightedNormals = false; + bBuildReversedIndexBuffer = true; + bUseHighPrecisionTangentBasis = false; + bGenerateDistanceFieldAsIfTwoSided = false; + bSupportFaceRemap = false; + //BuildScale3D = FVector(1.0f, 1.0f, 1.0f); + DistanceFieldResolutionScale = 2.0f; // ue default is 1.0 + + bPDGAsyncCommandletImportEnabled = false; + + // Legacy settings + bEnableBackwardCompatibility = true; + bAutomaticLegacyHDARebuild = false; +} + +UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() +{} + + +FProperty * +UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const +{ + for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) + { + FProperty * Property = *PropIt; + + if (Property->GetNameCPP() == PropertyName) + return Property; + } + + return nullptr; +} + + +void +UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) +{ + FProperty * Property = LocateProperty(PropertyName); + if (Property) + { + if (bReadOnly) + Property->SetPropertyFlags(CPF_EditConst); + else + Property->ClearPropertyFlags(CPF_EditConst); + } +} + + +void +UHoudiniRuntimeSettings::PostInitProperties() +{ + Super::PostInitProperties(); + + // Set Collision generation options as read only + { + if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Set marshalling attributes options as read only + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + /* + // Set Cook Folder as read-only + { + if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + } + */ + + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + Property->SetPropertyFlags(CPF_EditConst); + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Disable UI elements depending on current session type. +#if WITH_EDITOR + + UpdateSessionUI(); + +#endif // WITH_EDITOR + + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); +} + + +#if WITH_EDITOR + +void +UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + FProperty * LookupProperty = nullptr; + + if (!Property) + return; + if (Property->GetName() == TEXT("SessionType")) + UpdateSessionUI(); + else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); + else if (Property->GetName() == TEXT("CustomHoudiniLocation")) + { + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; + FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); + + // If path does not point to libHAPI location, we need to let user know. + if (!FPaths::FileExists(LibHAPICustomPath)) + { + FString MessageString = FString::Printf( + TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); + + FPlatformMisc::MessageBoxExt( + EAppMsgType::Ok, *MessageString, + TEXT("Invalid Custom Location Specified, resetting.")); + + CustomHoudiniLocationPath = TEXT(""); + } + } + else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) + { + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->SetPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->SetPropertyFlags(CPF_EditConst); + } + else + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->ClearPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->ClearPropertyFlags(CPF_EditConst); + } + } + + /* + if ( Property->GetName() == TEXT( "bEnableCooking" ) ) + { + // Cooking is disabled, we need to disable transform change triggers cooks option is as well. + if ( bEnableCooking ) + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) + { + // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. + if ( bUploadTransformsToHoudiniEngine ) + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + */ +} + + + +void +UHoudiniRuntimeSettings::UpdateSessionUI() +{ + SetPropertyReadOnly(TEXT("ServerHost"), true); + SetPropertyReadOnly(TEXT("ServerPort"), true); + SetPropertyReadOnly(TEXT("ServerPipeName"), true); + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); + + bool bServerType = false; + + switch (SessionType) + { + case HRSST_Socket: + { + SetPropertyReadOnly(TEXT("ServerHost"), false); + SetPropertyReadOnly(TEXT("ServerPort"), false); + bServerType = true; + break; + } + + case HRSST_NamedPipe: + { + SetPropertyReadOnly(TEXT("ServerPipeName"), false); + bServerType = true; + break; + } + + default: + break; + } + + if (bServerType) + { + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); + } +} + +#endif // WITH_EDITOR + #undef LOCTEXT_NAMESPACE \ No newline at end of file From 6cfc93a8f7f4929e13823a1611532bfd102bc190 Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Tue, 23 Feb 2021 10:34:26 -0500 Subject: [PATCH 06/16] - Added LICENSE.md file --- LICENSE.md | 117 +++++++++++------------------------------------------ 1 file changed, 24 insertions(+), 93 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 109dbc2ff..bd8f132c4 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,93 +1,24 @@ - ALPHA AND BETA SOFTWARE - CONFIDENTIAL DISCLOSURE AGREEMENT -Revised 10/2011 -This Agreement is made today between Side Effects Software Inc., a corporation -incorporated under the laws of Ontario, Canada and having a place of business -at 123 Front Street West, Suite 1401, Toronto ("Side Effects Software") and -you ("Beta Tester"). - -BACKGROUND: - -1. Side Effects Software is in the business of developing and marketing certain - computer graphics software and related materials. -2. Beta Tester, in order to permit Side Effects Software in refining and - perfecting such software and materials, has expressed an interest in testing - certain alpha/beta versions of software more fully described in Schedule A - (the "Software & Materials"). -3. Each Animator, as an employee, contractor or agent of the Beta Tester, may - have access to the Software & Materials and perhaps to other confidential - information of Side Effects Software such as trade secrets, business or - product plans, which might be disclosed during the course of the software - testing (the "Confidential Information"). -4. Side Effects Software wishes to ensure that the Software & Materials are not - used by Beta Tester for purposes other than alpha/beta testing and that they - are not disclosed to any other party without the prior written consent of - Side Effects Software; - -NOW THEREFORE, in consideration of this background and the provision of -such materials to Beta Tester and other good and valuable consideration (the -receipt and sufficiency of which are hereby acknowledged), Beta Tester agrees -with Side Effects Software as follows: - -1. Side Effects Software hereby grants to Beta Tester on the terms set out - herein a personal, non-transferable and non-exclusive license to use the - object code version of the Software & Materials for its internal operations - on its computers. Any commercial exploitation of the Software is at the - Beta Tester's risk. Beta Tester's right to use the Software & Materials is - limited to those rights expressly set out in this Agreement. Beta Tester - shall carry out testing of the Software & Materials in accordance with such - reasonable instructions as Side Effects Software may provide to it from time - to time. -2. Beta Tester shall use all reasonable efforts (which shall consist of at - least the same level of diligence as it uses to protect its own proprietary - information and trade secrets) to protect the confidentiality of all - Software & Materials, including all product features, and other Confidential - Information of Side Effects Software that may come to the attention of or - knowledge of Beta Tester as a result of undertaking such testing. Beta - Tester shall not discuss product features or show the Software & Materials - to anyone. Beta Tester shall not copy, publish, disclose, attempt to - recreate the source code version of the Software or make any use other than - as contemplated herein of any of the Software & Material or any such - Confidential Information. For the purposes hereof, Confidential Information - shall not include any information that: - - At the time of such disclosure, is generally available to the public - through no fault of Beta Tester; - - Was in possession of Beta Tester without any obligation of confidentiality - prior to the date hereof and was not acquired directly or indirectly from - Side Effects Software; or - - Was received by Beta Tester after the date hereof from a third party who - imposed no obligation of confidentiality and who did not acquire any such - information directly or indirectly from Side Effects Software. -3. Beta Tester shall not communicate or otherwise disclose to Side Effects - Software during the term of this Agreement any confidential or proprietary - information of any other third party. -4. In accepting this Agreement the Beta Tester agrees to test and evaluate the - Software & Materials and to report all problems, concerns, deficiencies and - suggestions for improvements to Side Effects Software. A representative from - Side Effects Software may be contacting Beta Tester weekly for a report. -5. Upon completion of such testing or at any time on the request of Side - Effects Software, Beta Tester shall promptly return to Side Effects Software - all copies of the Software & Materials, as well as any Confidential - Information, then in its possession or control and shall, if requested, - provide Side Effects Software with a certificate signed by an authorized - representative of Beta Tester to such effect from an officer of Beta Tester. -6. All Software & Materials, as well as any Confidential Information, is - provided "as is". Side Effects Software makes no representation, warranty or - guarantee with respect to any such material and assumes no liability for the - use and performance of any alpha and beta software. Side Effects Software - reserves the right to alter all aspects of the Software and Documentation - from one alpha or beta version to the next, including the user interface, - screen displays, fonts and functionality. -7. The Software will timeout and cease to function one month after its build - date, regardless of when it was downloaded or installed. - -SCHEDULE A -SOFTWARE AND MATERIALS -The following Software and related Materials are bound by the attached Alpha -and Beta Software -Test Agreement: -Software: Houdini Engine for Unreal -Version: Version 2.0 - alpha - -You must accept these terms and conditions to install the Software and -Materials. + + Copyright (c) 2021 + Side Effects Software Inc. All rights reserved. + + Redistribution and use of in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. The names Side Effects Software and SideFX may not be used to endorse or + promote products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 1e5010560e9ec5cb8e922550c407884580a9bf1f Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Tue, 30 Mar 2021 18:06:42 -0400 Subject: [PATCH 07/16] Houdini Engine for Unreal - Version 2.0 Houdini 18.5.532 update. The plug-in is now linked to Houdini 18.5.532 / HAPI 3.5.2. Documentation for version 2.0 of the plug-in is available: https://www.sidefx.com/docs/unreal/ ------------------------------------------------------- New features: ------------------------------------------------------- - Added support for setting/modifying per-instance custom data on instancer. The number of custom floats per instance can be set via the "unreal_num_custom_floats" attribute. The custom data itself can be set on the instances via "unreal_per_instance_custom_dataX" attributes, where X is the zero-based index of the custom float data. - Improved Houdini Engine Manager Ticking: Instead of processing each Houdini Asset Components one by one, in sequential order, per tick, we now additionally process the selected HACS and the active HACs for each tick. We also now continue the processing of the HACS when they are not in a "blocking" state. This greatly improves the time it takes to cook an HDA, especially when a lot of HDAs are present, in the level, by reducing the amount of ticks needed. - Improved Ticking: Added a time limit to HAC processing/ticking. The limit can be modified via HoudiniEngine.TickTimeLimit CVar. - New plugin setting: "Prefer Hda Memory Copy Over Hda Source File". When enabled, the plugin will always instantiate the memory copy of a HDA stored in the .uasset file instead of using the latest version of the HDA file itself. This helps ensuring consistency between users when using HDAs, but will not work with expanded HDAs. The memory copy can be updated by manually reimporting the HDA. - Added overrides for Static Mesh Build settings directly on the Houdini Asset Component. This allows overriding the default build settings values from the plugin settings per HAC. Additionally, this now also allow for changing those properties via generic property attributes (details). ie: i@unreal_uproperty_UseFullPrecisionUVs = 1; - It is now possible to preset geometry inputs with multiple assets when filling the object path value with multiple asset references, separated by semi-colons. Similarly, it is now possible to preset World, Landscape and Asset types inputs by setting the object path parameter value to actor path/name. - Generic propery attributes on details can now be applied to the Houdini Asset Component. This allow the use of detail attributes to preset some of the HAC's property (Cook Triggers, Auto-bake etc...) - It is now possible to decide whether or not a curve inputs adds additional rot/scale attributes. (the setting has been moved from the Houdini Asset Component to the Curve input). The default value for this can be changed in the plugin settings (the default is now to not add rot/scale) Editable curves no longer add additional rot/scale attributes. - Added support for generic propery attributes on Landscapes. - Added a widget to indicate the status of the Houdini Engine Session to the details panel. - Added support for resolving level output paths with {hda_level} token during baking. ------------------------------------------------------- Bug fixes: ------------------------------------------------------- - Fixed various issues with the "unreal_bake_folder" attribute: - "unreal_bake_folder" is now read from primitive and detail attributes on mesh,curve, instancer and landscape inputs, and cached in CachedAttributes on the FHoudiniOutputObject. - The specified BakeFolder no longer has to exist on disk to be considered valid. - The unreal_bake_folder no longer automatically sets the UI BakeFolder property on the HAC or PDG Asset Link. - The bake folder resolution order is now: primitive attribute first, then detail attribute, then BakeFolder on HAC/PDG UI and finally the default from the plugin settings. Updated the baking code to take this into account. - Added support for baking foliage to specified levels or sub-levels using "unreal_level_path". - "Bake to actors" now bakes `unreal_foliage` as foliage. - Added support for baking foliage to blueprint as HISMC. - Fixed index of out range when baking WC landscape/terrain and curve outputs with PDG - Fixed bug causing invalid transforms on Static Mesh Component upon multiple bake. - Fixed position offset on Landscapes due to Heightfield tiles having a slightly different size. - Baking: CenterActorToBoundingBoxCenter: exclude editor-only components from the bounding box calculation (such as the editor only billboard/sprite components) - Added the ability to copy the world transform in CopyPropertyToNewActorAndComponent - The component world transform is now set when baking to existing actors for SMC, single instance SMC and curves/spline components. - Calculate and cache certain landscape tile properties for first tile for temp/bake workflow. Reuse cached properties for remaining tiles. - Invalidate landscape cache when TOP node is dirtied or all work items are cleared. - Cache global landscape data on TOP node after processing the first landscape tile. All subsequently processed tiles will use cached data. - Fixed Generic Attributes not being applied on the Houdini Asset Component like in Version 1. - Fixed GeneratedDistanceFieldResolutionScale Static Mesh Generation Property not being properly applied before building the Static Mesh. - Fix for landscape transforms when generating landscapes using PDG. - Fixed possible crash when duplicating HDA that contained multiparms. - PDG/commandlet: fixed an issue where CachedAttributes were not set on FHoudiniOutputObjects of PDG meshes imported via the commandlet - HoudiniGeoImportCommandlet/HoudiniGeoImporter now also supports reading unreal_bake_folder from the individual output objects when importing in bake mode. - FHoudiniPackageParams: fixed an issue where the {temp}, {bake}, {out} and {out_basepath} tokens had the last sub-directory removed from their value - FHoudiniStringResolver: { and } in token values are replaced by __ - Fixed bug with the "unreal_bake_folder" attribute: It no longer automatically sets the UI BakeFolder property. Its resolution order is now: primitive attribute first, then detail attribute, then BakeFolder on HAC/PDG UI and finally the default from the plugin settings. The specified BakeFolder no longer has to exist on disk to be considered valid. - Fixed the unreal_output_name and unreal_bake_folder being ignored when baking instancers and the attributes were set on the instanced mesh. - Instancer baking: when a temp mesh is baked as part of an instancer, and the mesh has a mesh output on the asset, the mesh is baked with package params that are built from its FHoudiniOutputObject, taking into account the unreal_output_name and unreal_bake_folder set on the mesh itself, and not the values from the instancer (this matches what foliage baking is doing as well). - Baking: Set PackageParams.bAttemptToLoadMissingPackages to true when baking in EPackageReplaceMode::CreateNewAssets mode. This is needed since FindPackage can return null if a package is not in memory even if it exists on disk. So in such cases we have to also call LoadPackage to check if a valid package already exists on disk. - PDG Commandlet: re-using existing temporary results is problematic when the commandlet is used, since the node and geo ids can change every time the commandlet imports or re-imports a result. So now if we receive a new work result object (where an existing object is already loaded) via the commandlet we first unload the existing object before loading the new object. - HAC output cleanup: simplified the checks for the base/parent component when cleaning foliage in FHoudiniOutputTranslator::ClearOutput to check if the output's owner is a HAC, otherwise try the foliage component's outer. - PDG: Fixed a bug where DestroyResultOutputs didn't destroy the output components. - PDG: Improved tracking/reuse of previous PDG WorkItem results - PDG: When a node has successfully cooked all of its work items, call a function to check that the node's WorkResults array is in sync with the work item IDs from HAPI. This helps in cases where work item counts change, but WORK_ITEM_REMOVED events are not received (such as when doing node rebuilds, manually deleting the node in session sync, or reloading the map where the loaded asset link is then no longer linked to the node in the Houdini Engine session). Currently this just affects the entries in UTOPNode::WorkResults, the issue where WorkItemTallys are sometimes out of sync has not yet been resolved. - PDG: when looking for a FTOPWorkResult object in the TOP Node's array, if an entry with the given WorkItemID does not exist, try to find an entry with matching WorkItemIndex and WorkItemID == INDEX_NONE (invalid entries or entries that had IDs cleared upon save/load of the map). - PDG: FTOPWorkResultObjects were given a name that was always the same (for each FTOPWorkResultObject in a FTOPWorkResult) and this name was used to match a FTOPWorkResultObject for re-use. This has now been changed to include the index from the HAPI ResultInfo array to make the name unique. When work item results are processed, any old FTOPWorkResultObjects that were not re-used are destroyed (and have their old output objects removed and destroyed). - Baking: PDG: track previous bake outputs using the WorkItemIndex (from HAPI), not the array index in the UTOPNode::WorkResults array or the WorkItemID. The WorkItemIndex is more stable/deterministic than the other two options. Perhaps we can add support for a custom attribute to work items to help with reliably identifying the same item/object in subsequent cooks and bakes. - Fixed visibility layer issues with tiled landscapes. - PDG Auto-Bake: Use the WorkItemHAPIIndex and WorkResultObjectIndex to identify the work item and result object to bake. - PDG Auto-Bake: when auto-baking also apply the selection option (All vs Selected Network vs Selected Node) - Baking: Actor names: fixed an issue where if unreal_bake_actor is set to test_1, incremental baking would generate test_2 (if test_1 already exists) instead of test_1_1. Also use our own loop instead of MakeUniqueObjectName, to generate more consistent and continuous ranges (MakeUniqueObjectName increases a counter per class). - Baking PDG: we now pass in all the actors baked in the current bake run all the way through to work result object baking (previously an empty array was always passed to BakePDGWorkResultObject) - PDG: Fixed the work items tallies reporting incorrect numbers. - FHoudiniPackageParams: removed bAttemptToLoadMissingPackages: in CreateNewAssets mode we now always check if a package exists on disk (using LOAD_Verify) if FindPackage returns nullptr. - Removed redundant for loops when updating generic attributes on instancers. - Fixed the "unreal_output_name" attribute being ignored on landscapes. - Fixed geometry input's defaults value (object path inputs) being ignored. - Fixed potential endless cook loop when using Session Sync. - Fixed a lost session failing to be reported if HARS/Houdini crashed/was shutdown before instantiating an asset. This could lead to an instantiation loop. - Fixed the Houdini Engine status sometimes being incorrectly reported. - Fixed bugs when using foliage instancers: Previous foliage instances where not always properly cleaned up when directly instancing FoliageTypes, or when switching from foliage instances to "normal" instances. - Updated the path that is used to find the houdini executable on Linux for session sync, tested on Ubuntu 20.04 and Houdini 18.5.499. - Updated the path that is used to find the houdini executable on Mac for session sync. - Fixed a crash/memory leak with UHoudiniFloatRampCurve and UHoudiniColorRampCurve from the parameter details when there is an attempt to the destroy the owning UHoudiniParameter or its world (while the curves are still in the root set) or if the curves are destroyed but the SCurveEditors still reference them as CurveOwners. - Fixed an issue where static mesh material slots were appended over and over when recooking HDAs that output collision meshes with materials. - Fix up complex collision properties when outputting a static mesh. - The first invisible complex collision meshes is treated as implicit, i.e. it will not be explicitly created as a component. - Fixed extra "ghost" curve appearing with HDAs that contained closed editable curves, either upon duplication, or first cook after loading a level with the HDA. - When rebuilding an HDA editable output curves (HoudiniSplineComponent) are now marked as changed/trigger update, so that the curve on the Houdini side is updated/kept in sync with the edited values in Unreal. - Fixed Multi-parm instances being replaced with the first instance when rebuilding/cooking a loaded HDA actor. (when opening a map and cooking for the first time, for example) - Fixed possible crash (Bad MeshDescription) when an HDA generates invalid output mesh data. - MeshTranslator: Fix crash if main Static Mesh didn't have a BodySetup. - FHoudiniOutputTranslator::UpdateLoadedOutputs: don't skip closed editable output curves that are identified as meshes: HAPI treats closed curves as meshes not curves. - Fixed issue where RawMesh LODs would get incorrect material assignments. - Fixed bug preventing the modifications of RVT via generic uprop attributes - Generic Attributes: Fixed bug when assigning single value to array property - Generic Attributes: Added log infos when properties are incompatible or when we cannot find valid ValuePtrs for the UProperties. - Recook now also resend the input objects to Houdini. - Added persistent notification mechanism to prevent notification spamming during cooking. - PDG Auto-bake: we now auto-bake a node once all work items of the node are complete (but we still support baking additional dynamically generated work items automatically if they come in later) - World Inputs now detect added/removed components on an actor used as a world input and trigger an update. - Fixed world inputs auto-starting the Houdini Engine Session when a camera object was selected. - Merged PR#101 on behalf of dearamy: Fix Misusing of Y and Z This fixes incorrect terrain output scale. - Merged PR#122 on behalf of eliiik: Fix Houdini Spline visualization rotation and scale error This fixes invalid Houdini Spline rotation/scale when the Actor has been scaled/rotated. - Fixed HARS/Houdini crashing when sending additional rot/scale attributes on editable curves. - Fixed Editable curves not displaying the proper curve points upon instantiation if the editable curve was set with 2 points or less. - Prevent crash when the user tries to set per instance custom data via generic attributes. We now skip the attribute and log a warning that indicates the proper attributes to use. - The PhysicsState, RenderState and Bounds of the HAC and its children are now properly updated upon recook. - Fixed HoudiniAssetComponent being ignored by editor ray tracing due the HAC not having a SceneProxy in the SceneManager. This fixes issues like surface snapping not working with Houdini Generated Static Meshes. This only applies to "refined" static meshes, the temporary HoudiniStaticMeshComponents still don't have collisions. - Fixed folders sometimes loosing their selected (tabs) / expanded (collapsible) by improving the reuse of parameter objects. The lookup table (by name) that we use to find previously created parameters can now store multiple parameters, as it is sometimes possible to have two parameters with the same name (ie: Folder/FolderList). - Fixed crash when importing Landscapes via the PDG Asset Link. This was caused by FHoudiniEngineUtils::GetOuterHoudiniAssetComponent() not returning an HAC if it wasn't the direct outer of the object. For PDG outputs, the output's outer is actually the PDG Asset Link, not the HAC. - Fixed crash when attempting to visualize a curve with no CV points but with display points. - Fixed curve inputs loosing their type/method/closed/reversed properties after loosing/restarting the Houdini Engine Session. - Added missing includes/forward declarations for NoPCH/Non-Unity builds --- HoudiniEngine.uplugin | 4 +- LICENSE.md | 117 +- Source/HoudiniEngine/HoudiniEngine.Build.cs | 6 +- .../HoudiniEngine/Private/HoudiniEngine.cpp | 169 +- Source/HoudiniEngine/Private/HoudiniEngine.h | 36 + .../Private/HoudiniEngineManager.cpp | 290 ++- .../Private/HoudiniEngineManager.h | 3 + .../Private/HoudiniEnginePrivatePCH.h | 2 + .../Private/HoudiniEngineUtils.cpp | 456 +++-- .../Private/HoudiniEngineUtils.h | 66 +- .../Private/HoudiniGeoImportCommandlet.cpp | 18 +- .../Private/HoudiniGeoImporter.cpp | 81 +- .../Private/HoudiniHandleTranslator.cpp | 16 +- .../Private/HoudiniInputTranslator.cpp | 200 +- .../Private/HoudiniInputTranslator.h | 5 +- .../Private/HoudiniInstanceTranslator.cpp | 351 +++- .../Private/HoudiniInstanceTranslator.h | 37 +- .../Private/HoudiniLandscapeTranslator.cpp | 548 ++++-- .../Private/HoudiniLandscapeTranslator.h | 22 +- .../Private/HoudiniMaterialTranslator.cpp | 18 +- .../Private/HoudiniMeshTranslator.cpp | 854 +++++---- .../Private/HoudiniMeshTranslator.h | 29 +- .../Private/HoudiniOutputTranslator.cpp | 211 +- .../Private/HoudiniOutputTranslator.h | 14 +- .../Private/HoudiniPDGManager.cpp | 360 +++- .../HoudiniEngine/Private/HoudiniPDGManager.h | 32 +- .../Private/HoudiniPDGTranslator.cpp | 23 +- .../Private/HoudiniPDGTranslator.h | 9 +- .../Private/HoudiniPackageParams.cpp | 30 +- .../Private/HoudiniPackageParams.h | 25 +- .../Private/HoudiniParameterTranslator.cpp | 165 +- .../Private/HoudiniSplineTranslator.cpp | 209 +- .../Private/HoudiniSplineTranslator.h | 6 +- .../Private/HoudiniStringResolver.cpp | 71 +- .../Private/HoudiniStringResolver.h | 13 +- .../Private/UnrealLandscapeTranslator.cpp | 8 +- .../Private/UnrealLandscapeTranslator.h | 2 +- Source/HoudiniEngine/Public/HAPI/HAPI.h | 502 ++++- .../HoudiniEngine/Public/HAPI/HAPI_Common.h | 31 +- .../HoudiniEngine/Public/HAPI/HAPI_Version.h | 4 +- .../Private/HoudiniAssetComponentDetails.cpp | 129 +- .../Private/HoudiniAssetComponentDetails.h | 5 + .../Private/HoudiniEngineBakeUtils.cpp | 1690 ++++++++++------- .../Private/HoudiniEngineBakeUtils.h | 125 +- .../Private/HoudiniEngineCommands.cpp | 18 +- .../Private/HoudiniEngineDetails.cpp | 149 +- .../Private/HoudiniEngineEditorUtils.cpp | 7 +- .../Private/HoudiniInputDetails.cpp | 39 +- .../Private/HoudiniOutputDetails.cpp | 82 +- .../Private/HoudiniOutputDetails.h | 2 + .../Private/HoudiniPDGDetails.cpp | 32 +- .../Private/HoudiniParameterDetails.cpp | 6 +- .../Private/HoudiniParameterDetails.h | 7 + .../Private/HoudiniRuntimeSettingsDetails.cpp | 9 +- .../HoudiniSplineComponentVisualizer.cpp | 43 +- .../HoudiniSplineComponentVisualizer.h | 15 +- .../HoudiniAssetBlueprintComponent.cpp | 15 +- .../Private/HoudiniAssetComponent.cpp | 153 +- .../Private/HoudiniAssetComponent.h | 26 +- .../Private/HoudiniEngineRuntimePrivatePCH.h | 143 +- .../Private/HoudiniEngineRuntimeUtils.cpp | 86 +- .../Private/HoudiniEngineRuntimeUtils.h | 6 +- .../Private/HoudiniGenericAttribute.cpp | 580 +++--- .../Private/HoudiniInput.cpp | 20 +- .../Private/HoudiniInput.h | 18 +- .../Private/HoudiniInputObject.cpp | 161 +- .../Private/HoudiniInputObject.h | 28 +- .../Private/HoudiniOutput.cpp | 59 + .../Private/HoudiniOutput.h | 39 +- .../Private/HoudiniPDGAssetLink.cpp | 472 ++++- .../Private/HoudiniPDGAssetLink.h | 268 ++- .../Private/HoudiniRuntimeSettings.cpp | 807 ++++---- .../Private/HoudiniRuntimeSettings.h | 73 +- .../Private/HoudiniStaticMesh.cpp | 40 + .../Private/HoudiniStaticMesh.h | 8 + .../Private/HoudiniTranslatorTypes.cpp | 55 + .../Private/HoudiniTranslatorTypes.h | 77 + 77 files changed, 7354 insertions(+), 3181 deletions(-) create mode 100644 Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.cpp create mode 100644 Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.h diff --git a/HoudiniEngine.uplugin b/HoudiniEngine.uplugin index f3abb5cda..869034b71 100644 --- a/HoudiniEngine.uplugin +++ b/HoudiniEngine.uplugin @@ -1,8 +1,8 @@ { "FileVersion" : 3, "FriendlyName" : "Houdini Engine v2", - "Version" : 18050462, - "VersionName" : "v2.0 - H18.5.462", + "Version" : 18050532, + "VersionName" : "v2.0 - H18.5.532", "CreatedBy" : "Side Effects Software Inc.", "CreatedByURL" : "http://www.sidefx.com", "DocsURL" : "http://www.sidefx.com/docs/unreal/", diff --git a/LICENSE.md b/LICENSE.md index bd8f132c4..109dbc2ff 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,24 +1,93 @@ - - Copyright (c) 2021 - Side Effects Software Inc. All rights reserved. - - Redistribution and use of in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. The names Side Effects Software and SideFX may not be used to endorse or - promote products derived from this software without specific prior - written permission. - - THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS - OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ALPHA AND BETA SOFTWARE + CONFIDENTIAL DISCLOSURE AGREEMENT +Revised 10/2011 +This Agreement is made today between Side Effects Software Inc., a corporation +incorporated under the laws of Ontario, Canada and having a place of business +at 123 Front Street West, Suite 1401, Toronto ("Side Effects Software") and +you ("Beta Tester"). + +BACKGROUND: + +1. Side Effects Software is in the business of developing and marketing certain + computer graphics software and related materials. +2. Beta Tester, in order to permit Side Effects Software in refining and + perfecting such software and materials, has expressed an interest in testing + certain alpha/beta versions of software more fully described in Schedule A + (the "Software & Materials"). +3. Each Animator, as an employee, contractor or agent of the Beta Tester, may + have access to the Software & Materials and perhaps to other confidential + information of Side Effects Software such as trade secrets, business or + product plans, which might be disclosed during the course of the software + testing (the "Confidential Information"). +4. Side Effects Software wishes to ensure that the Software & Materials are not + used by Beta Tester for purposes other than alpha/beta testing and that they + are not disclosed to any other party without the prior written consent of + Side Effects Software; + +NOW THEREFORE, in consideration of this background and the provision of +such materials to Beta Tester and other good and valuable consideration (the +receipt and sufficiency of which are hereby acknowledged), Beta Tester agrees +with Side Effects Software as follows: + +1. Side Effects Software hereby grants to Beta Tester on the terms set out + herein a personal, non-transferable and non-exclusive license to use the + object code version of the Software & Materials for its internal operations + on its computers. Any commercial exploitation of the Software is at the + Beta Tester's risk. Beta Tester's right to use the Software & Materials is + limited to those rights expressly set out in this Agreement. Beta Tester + shall carry out testing of the Software & Materials in accordance with such + reasonable instructions as Side Effects Software may provide to it from time + to time. +2. Beta Tester shall use all reasonable efforts (which shall consist of at + least the same level of diligence as it uses to protect its own proprietary + information and trade secrets) to protect the confidentiality of all + Software & Materials, including all product features, and other Confidential + Information of Side Effects Software that may come to the attention of or + knowledge of Beta Tester as a result of undertaking such testing. Beta + Tester shall not discuss product features or show the Software & Materials + to anyone. Beta Tester shall not copy, publish, disclose, attempt to + recreate the source code version of the Software or make any use other than + as contemplated herein of any of the Software & Material or any such + Confidential Information. For the purposes hereof, Confidential Information + shall not include any information that: + - At the time of such disclosure, is generally available to the public + through no fault of Beta Tester; + - Was in possession of Beta Tester without any obligation of confidentiality + prior to the date hereof and was not acquired directly or indirectly from + Side Effects Software; or + - Was received by Beta Tester after the date hereof from a third party who + imposed no obligation of confidentiality and who did not acquire any such + information directly or indirectly from Side Effects Software. +3. Beta Tester shall not communicate or otherwise disclose to Side Effects + Software during the term of this Agreement any confidential or proprietary + information of any other third party. +4. In accepting this Agreement the Beta Tester agrees to test and evaluate the + Software & Materials and to report all problems, concerns, deficiencies and + suggestions for improvements to Side Effects Software. A representative from + Side Effects Software may be contacting Beta Tester weekly for a report. +5. Upon completion of such testing or at any time on the request of Side + Effects Software, Beta Tester shall promptly return to Side Effects Software + all copies of the Software & Materials, as well as any Confidential + Information, then in its possession or control and shall, if requested, + provide Side Effects Software with a certificate signed by an authorized + representative of Beta Tester to such effect from an officer of Beta Tester. +6. All Software & Materials, as well as any Confidential Information, is + provided "as is". Side Effects Software makes no representation, warranty or + guarantee with respect to any such material and assumes no liability for the + use and performance of any alpha and beta software. Side Effects Software + reserves the right to alter all aspects of the Software and Documentation + from one alpha or beta version to the next, including the user interface, + screen displays, fonts and functionality. +7. The Software will timeout and cease to function one month after its build + date, regardless of when it was downloaded or installed. + +SCHEDULE A +SOFTWARE AND MATERIALS +The following Software and related Materials are bound by the attached Alpha +and Beta Software +Test Agreement: +Software: Houdini Engine for Unreal +Version: Version 2.0 - alpha + +You must accept these terms and conditions to install the Software and +Materials. diff --git a/Source/HoudiniEngine/HoudiniEngine.Build.cs b/Source/HoudiniEngine/HoudiniEngine.Build.cs index 8986c9afc..d1060175e 100644 --- a/Source/HoudiniEngine/HoudiniEngine.Build.cs +++ b/Source/HoudiniEngine/HoudiniEngine.Build.cs @@ -32,8 +32,8 @@ /* - Houdini Version: 18.5.462 - Houdini Engine Version: 3.5.1 + Houdini Version: 18.5.532 + Houdini Engine Version: 3.5.2 Unreal Version: 4.26.0 */ @@ -47,7 +47,7 @@ public class HoudiniEngine : ModuleRules { private string GetHFSPath() { - string HoudiniVersion = "18.5.462"; + string HoudiniVersion = "18.5.532"; bool bIsRelease = true; string HFSPath = ""; string RegistryPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Side Effects Software"; diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.cpp b/Source/HoudiniEngine/Private/HoudiniEngine.cpp index 42ef76d66..ec8be793e 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngine.cpp @@ -85,9 +85,11 @@ FHoudiniEngine::FHoudiniEngine() Session.type = HAPI_SESSION_MAX; Session.id = -1; + SetSessionStatus(EHoudiniSessionStatus::Invalid); #if WITH_EDITOR HapiNotificationStarted = 0.0; + TimeSinceLastPersistentNotification = 0.0; #endif } @@ -204,18 +206,23 @@ FHoudiniEngine::StartupModule() // Create Houdini Asset Manager HoudiniEngineManager = new FHoudiniEngineManager(); + // Set the session status to Not Started + SetSessionStatus(EHoudiniSessionStatus::NotStarted); + // Set the default value for pausing houdini engine cooking const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); bEnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; // Check if a null session is set bool bNoneSession = (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None); + if (bNoneSession) + SetSessionStatus(EHoudiniSessionStatus::None); // Initialize the singleton with this instance FHoudiniEngine::HoudiniEngineInstance = this; // See if we need to start the manager ticking if needed - // Don tick if we failed to load HAPI, if cooking is disabled or if we're using a null session + // Dont tick if we failed to load HAPI, if cooking is disabled or if we're using a null session if (FHoudiniApi::IsHAPIInitialized()) { if (bEnableCookingGlobal && !bNoneSession) @@ -321,6 +328,7 @@ FHoudiniEngine::ShutdownModule() { FHoudiniApi::Cleanup(GetSession()); FHoudiniApi::CloseSession(GetSession()); + SessionStatus = EHoudiniSessionStatus::Invalid; } FHoudiniApi::FinalizeHAPI(); @@ -394,6 +402,61 @@ FHoudiniEngine::GetSession() const return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; } +const EHoudiniSessionStatus& +FHoudiniEngine::GetSessionStatus() const +{ + return SessionStatus; +} + +void +FHoudiniEngine::SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus) +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None) + { + // Check for none sessions first + SessionStatus = EHoudiniSessionStatus::None; + return; + } + + if (!bFirstSessionCreated) + { + // Don't change the status unless we've attempted to start the session once + SessionStatus = EHoudiniSessionStatus::NotStarted; + return; + } + + switch (InSessionStatus) + { + case EHoudiniSessionStatus::NotStarted: + case EHoudiniSessionStatus::NoLicense: + case EHoudiniSessionStatus::Lost: + case EHoudiniSessionStatus::None: + case EHoudiniSessionStatus::Invalid: + case EHoudiniSessionStatus::Connected: + { + SessionStatus = InSessionStatus; + } + break; + + case EHoudiniSessionStatus::Stopped: + { + // Only set to stop status if the session was valid + if (SessionStatus == EHoudiniSessionStatus::Connected) + SessionStatus = EHoudiniSessionStatus::Stopped; + } + break; + + case EHoudiniSessionStatus::Failed: + { + // Preserve No License / Lost status + if (SessionStatus != EHoudiniSessionStatus::NoLicense && SessionStatus != EHoudiniSessionStatus::Lost) + SessionStatus = EHoudiniSessionStatus::Failed; + } + break; + } +} + HAPI_CookOptions FHoudiniEngine::GetDefaultCookOptions() { @@ -434,6 +497,10 @@ FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) return true; + // Set the HAPI_CLIENT_NAME environment variable to "unreal" + // We need to do this before starting HARS. + FPlatformMisc::SetEnvironmentVar(TEXT("HAPI_CLIENT_NAME"), TEXT("unreal")); + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; HAPI_ThriftServerOptions ServerOptions; @@ -525,6 +592,9 @@ FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, break; } + if(SessionType != EHoudiniRuntimeSettingsSessionType::HRSST_None) + FHoudiniEngine::Get().SetFirstSessionCreated(true); + if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) { // Disable session sync as well? @@ -554,6 +624,9 @@ FHoudiniEngine::SessionSyncConnect( if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(&Session)) return true; + // Consider the session failed as long as we dont connect + SetSessionStatus(EHoudiniSessionStatus::Failed); + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); @@ -593,6 +666,7 @@ FHoudiniEngine::SessionSyncConnect( // Enable session sync bEnableSessionSync = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); // Update this session's license type HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( @@ -695,7 +769,7 @@ FHoudiniEngine::InitializeHAPISession() else { HOUDINI_LOG_ERROR( - TEXT("Starting up the Houdini Engine module failed: %s"), + TEXT("Houdini Engine API initialization failed: %s"), *FHoudiniEngineUtils::GetErrorDescription(Result)); return false; @@ -725,6 +799,8 @@ FHoudiniEngine::OnSessionLost() // Mark the session as invalid Session.id = -1; Session.type = HAPI_SESSION_MAX; + SetSessionStatus(EHoudiniSessionStatus::Lost); + bEnableSessionSync = false; HoudiniEngineManager->StopHoudiniTicking(); @@ -758,6 +834,7 @@ FHoudiniEngine::StopSession(HAPI_Session*& SessionPtr) Session.id = -1; Session.type = HAPI_SESSION_MAX; + SetSessionStatus(EHoudiniSessionStatus::Stopped); bEnableSessionSync = false; HoudiniEngineManager->StopHoudiniTicking(); @@ -794,17 +871,20 @@ FHoudiniEngine::RestartSession() HoudiniRuntimeSettings->ServerHost)) { HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to start the new Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { // Now initialize HAPI with this session if (!InitializeHAPISession()) { - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); } } } @@ -845,6 +925,7 @@ FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionT HoudiniRuntimeSettings->ServerHost)) { HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { @@ -852,10 +933,12 @@ FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionT if (!InitializeHAPISession()) { HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); } } @@ -895,6 +978,7 @@ FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& Session HoudiniRuntimeSettings->ServerHost)) { HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { @@ -902,10 +986,12 @@ FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& Session if (!InitializeHAPISession()) { HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); } else { bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); } } @@ -1000,6 +1086,7 @@ FHoudiniEngine::CreateTaskSlateNotification( */ NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + //FSlateNotificationManager::Get().Tick(); } #endif @@ -1015,6 +1102,8 @@ FHoudiniEngine::UpdateTaskSlateNotification(const FText& InText) TSharedPtr NotificationItem = NotificationPtr.Pin(); if (NotificationItem.IsValid()) NotificationItem->SetText(InText); + + //FSlateNotificationManager::Get().Tick(); #endif return true; @@ -1040,6 +1129,80 @@ FHoudiniEngine::FinishTaskSlateNotification(const FText& InText) return true; } +bool FHoudiniEngine::UpdateCookingNotification(const FText& InText, const bool bExpireAndFade) +{ +#if WITH_EDITOR + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return false; + + UpdatePersistentNotification(InText, bExpireAndFade); + +#endif + return true; +} + +bool +FHoudiniEngine::UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade) +{ +#if WITH_EDITOR + TimeSinceLastPersistentNotification = 0.0; + + if (!PersistentNotificationPtr.IsValid()) + { + FNotificationInfo Info(InText); + Info.bFireAndForget = false; + Info.FadeOutDuration = HAPI_UNREAL_NOTIFICATION_FADEOUT; + Info.ExpireDuration = HAPI_UNREAL_NOTIFICATION_EXPIRE; + const TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + + PersistentNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + //FSlateNotificationManager::Get().Tick(); + } + + TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); + + if (NotificationItem.IsValid()) + { + // Update the persistent notification. + NotificationItem->SetText(InText); + bPersistentAllowExpiry = bExpireAndFade; + } + + //FSlateNotificationManager::Get().Tick(); +#endif + + return true; +} + +void FHoudiniEngine::TickPersistentNotification(const float DeltaTime) +{ + if (PersistentNotificationPtr.IsValid() && DeltaTime > 0.0f) + { + TimeSinceLastPersistentNotification += DeltaTime; + if (bPersistentAllowExpiry && TimeSinceLastPersistentNotification > HAPI_UNREAL_NOTIFICATION_EXPIRE) + { + TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); + if (NotificationItem.IsValid()) + { + NotificationItem->Fadeout(); + PersistentNotificationPtr.Reset(); + } + } + } + + // Tick the notification manager + //FSlateNotificationManager::Get().Tick(); +} + void FHoudiniEngine::UpdateSessionSyncInfoFromHoudini() { diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.h b/Source/HoudiniEngine/Private/HoudiniEngine.h index e5755b0d4..494ca2049 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.h +++ b/Source/HoudiniEngine/Private/HoudiniEngine.h @@ -44,6 +44,20 @@ struct FSlateDynamicImageBrush; enum class EHoudiniBGEOCommandletStatus : uint8; +UENUM() +enum class EHoudiniSessionStatus : int8 +{ + Invalid = -1, + + NotStarted, // Session not initialized yet + Connected, // Session successfully started + None, // Session type set to None + Stopped, // Session stopped + Failed, // Session failed to connect + Lost, // Session Lost (HARS/Houdini Crash?) + NoLicense, // Failed to acquire a license +}; + // Not using the IHoudiniEngine interface for now class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface { @@ -65,6 +79,10 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface // Session accessor virtual const HAPI_Session* GetSession() const; + virtual const EHoudiniSessionStatus& GetSessionStatus() const; + + virtual void SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus); + // Default cook options static HAPI_CookOptions GetDefaultCookOptions(); @@ -117,6 +135,15 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface bool UpdateTaskSlateNotification(const FText& InText); bool FinishTaskSlateNotification(const FText& InText); + // Only update persistent notification if cooking notification has been enabled in the settings. + bool UpdateCookingNotification(const FText& InText, const bool bExpireAndFade); + + // Update persistent notification irrespective of any notification enable/disable settings. + bool UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade); + + // If the time since last persistent notification has expired, fade out the persistent notification. + void TickPersistentNotification(float DeltaTime); + void SetHapiNotificationStartedTime(const double& InTime) { HapiNotificationStarted = InTime; }; // Register task for execution. @@ -226,6 +253,9 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface // The Houdini Engine session. HAPI_Session Session; + // The Houdini Engine session's status + EHoudiniSessionStatus SessionStatus; + // The type of HE license used by the current session HAPI_License LicenseType; @@ -302,6 +332,12 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface #if WITH_EDITOR /** Notification used by this component. **/ TWeakPtr NotificationPtr; + + /** Persistent notification. **/ + bool bPersistentAllowExpiry; + TWeakPtr PersistentNotificationPtr; + float TimeSinceLastPersistentNotification; + /** Used to delay notification updates for HAPI asynchronous work. **/ double HapiNotificationStarted; #endif diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp index 3fc8db540..52855d897 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp @@ -38,9 +38,11 @@ #include "HoudiniOutputTranslator.h" #include "HoudiniHandleTranslator.h" #include "HoudiniSplineTranslator.h" + #include "Misc/MessageDialog.h" #include "Misc/ScopedSlowTask.h" #include "Containers/Ticker.h" +#include "HAL/IConsoleManager.h" #if WITH_EDITOR #include "Editor.h" @@ -53,6 +55,14 @@ #include "IPackageAutoSaver.h" #endif +static TAutoConsoleVariable CVarHoudiniEngineTickTimeLimit( + TEXT("HoudiniEngine.TickTimeLimit"), + 1.0, + TEXT("Time limit after which HDA processing will be stopped, until the next tick of the Houdini Engine Manager.\n") + TEXT("<= 0.0: No Limit\n") + TEXT("1.0: Default\n") +); + FHoudiniEngineManager::FHoudiniEngineManager() : CurrentIndex(0) , ComponentCount(0) @@ -120,6 +130,8 @@ FHoudiniEngineManager::Tick(float DeltaTime) EnableEditorAutoSave(nullptr); + FHoudiniEngine::Get().TickPersistentNotification(DeltaTime); + if (bMustStopTicking) { // Ticking should be stopped immediately @@ -127,56 +139,109 @@ FHoudiniEngineManager::Tick(float DeltaTime) return true; } - // Process the current component if possible - while (true) + // Build a set of components that need to be processed + // 1 - selected HACs + // 2 - "Active" HACs + // 3 - The "next" inactive HAC + TArray ComponentsToProcess; + if (FHoudiniEngineRuntime::IsInitialized()) { - UHoudiniAssetComponent * CurrentComponent = nullptr; - if (FHoudiniEngineRuntime::IsInitialized()) + FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); + + //FScopeLock ScopeLock(&CriticalSection); + ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); + + // Wrap around if needed + if (CurrentIndex >= ComponentCount) + CurrentIndex = 0; + + for (uint32 nIdx = 0; nIdx < ComponentCount; nIdx++) { - FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); + UHoudiniAssetComponent * CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(nIdx); + if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) + { + // Invalid component, do not process + continue; + } + else if (CurrentComponent->IsPendingKill() + || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) + { + // Component being deleted, do not process + continue; + } - //FScopeLock ScopeLock(&CriticalSection); - ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); + if (!CurrentComponent->IsFullyLoaded()) + { + // Let the component figure out whether it's fully loaded or not. + CurrentComponent->HoudiniEngineTick(); + if (!CurrentComponent->IsFullyLoaded()) + continue; // We need to wait some more. + } - // No work to be done - if (ComponentCount <= 0) - break; + if (!CurrentComponent->IsValidComponent()) + { + // This component is no longer valid. Prevent it from being processed, and remove it. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); + continue; + } - // Wrap around if needed - if (CurrentIndex >= ComponentCount) - CurrentIndex = 0; + AActor* Owner = CurrentComponent->GetOwner(); + if (Owner && Owner->IsSelectedInEditor()) + { + // 1. Add selected HACs + // If the component's owner is selected, add it to the set + ComponentsToProcess.Add(CurrentComponent); + } + else if (CurrentComponent->GetAssetState() != EHoudiniAssetState::NeedInstantiation + && CurrentComponent->GetAssetState() != EHoudiniAssetState::None) + { + // 2. Add "Active" HACs, the only two non-active states are: + // NeedInstantiation (loaded, not instantiated in H yet, not modified) + // None (no processing currently) + ComponentsToProcess.Add(CurrentComponent); + } + else if(nIdx == CurrentIndex) + { + // 3. Add the "Current" HAC + ComponentsToProcess.Add(CurrentComponent); + } - CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(CurrentIndex); - CurrentIndex++; + // Set the LastTickTime on the "current" HAC to 0 to ensure it's treated first + if (nIdx == CurrentIndex) + { + CurrentComponent->LastTickTime = 0.0; + } } - if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) - { - // Invalid component, do not process - break; - } - else if (CurrentComponent->IsPendingKill() - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) - { - // Component being deleted, do not process - break; - } + // Increment the current index for the next tick + CurrentIndex++; + } - if (!CurrentComponent->IsFullyLoaded()) - { - // Let the component figure out whether it's fully loaded or not. - CurrentComponent->HoudiniEngineTick(); - if (!CurrentComponent->IsFullyLoaded()) - continue; // We need to wait some more. - } + // Sort the components by last tick time + ComponentsToProcess.Sort([](const UHoudiniAssetComponent& A, const UHoudiniAssetComponent& B) { return A.LastTickTime < B.LastTickTime; }); + + // Time limit for processing + double dProcessTimeLimit = CVarHoudiniEngineTickTimeLimit.GetValueOnAnyThread(); + double dProcessStartTime = FPlatformTime::Seconds(); + + // Process all the components in the list + for(UHoudiniAssetComponent* CurrentComponent : ComponentsToProcess) + { + // Tick the notification manager + //FHoudiniEngine::Get().TickPersistentNotification(0.0f); - if (!CurrentComponent->IsValidComponent()) + double dNow = FPlatformTime::Seconds(); + if (dProcessTimeLimit > 0.0 + && dNow - dProcessStartTime > dProcessTimeLimit) { - // This component is no longer valid. Prevent it from being processed, and remove it. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); - continue; + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); + break; } + // Update the tick time for this component + CurrentComponent->LastTickTime = dNow; + + // Handle template processing (for BP) first // We don't want to the template component processing to trigger session creation if (CurrentComponent->GetAssetState() == EHoudiniAssetState::ProcessTemplate) { @@ -212,51 +277,62 @@ FHoudiniEngineManager::Tick(float DeltaTime) { // TODO: Transfer template output changes over to the preview instance. } - - break; + continue; } - // See if we should start the default "first" session - if(!FHoudiniEngine::Get().GetSession() && !FHoudiniEngine::Get().GetFirstSessionCreated()) + // Process the component + bool bKeepProcessing = true; + while (bKeepProcessing) { - // Only try to start the default session if we have an "active" HAC - if (CurrentComponent->GetAssetState() == EHoudiniAssetState::PreInstantiation - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Instantiating - || CurrentComponent->GetAssetState() == EHoudiniAssetState::PreCook - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Cooking) - { - FString StatusText = TEXT("Initializing Houdini Engine..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + // Tick the notification manager + FHoudiniEngine::Get().TickPersistentNotification(0.0f); - // We want to yield for a bit. - //FPlatformProcess::Sleep(0.5f); + // See if we should start the default "first" session + AutoStartFirstSessionIfNeeded(CurrentComponent); - // Indicates that we've tried to start the session once no matter if it failed or succeed - FHoudiniEngine::Get().SetFirstSessionCreated(true); + EHoudiniAssetState PrevState = CurrentComponent->GetAssetState(); + ProcessComponent(CurrentComponent); + EHoudiniAssetState NewState = CurrentComponent->GetAssetState(); - // Attempt to restart the session - if (!FHoudiniEngine::Get().RestartSession()) - { - // We failed to start the session - // Stop ticking until it's manually restarted - StopHoudiniTicking(); + // In order to process components faster / with less ticks, + // we may continue processing the component if it ends up in certain states + switch (NewState) + { + case EHoudiniAssetState::PreInstantiation: + case EHoudiniAssetState::PreCook: + case EHoudiniAssetState::PostCook: + case EHoudiniAssetState::PreProcess: + case EHoudiniAssetState::Processing: + bKeepProcessing = true; + break; + + case EHoudiniAssetState::NeedInstantiation: + case EHoudiniAssetState::Instantiating: + case EHoudiniAssetState::Cooking: + case EHoudiniAssetState::None: + case EHoudiniAssetState::ProcessTemplate: + case EHoudiniAssetState::NeedRebuild: + case EHoudiniAssetState::NeedDelete: + case EHoudiniAssetState::Deleting: + bKeepProcessing = false; + break; + } - StatusText = TEXT("Houdini Engine failed to initialize."); - } - else - { - StatusText = TEXT("Houdini Engine successfully initialized."); - } + // Safeguard, useless? + // Stop processing if the state hasn't changed + if (PrevState == NewState) + bKeepProcessing = false; - // Finish the notification and display the results - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + dNow = FPlatformTime::Seconds(); + if (dProcessTimeLimit > 0.0 && dNow - dProcessStartTime > dProcessTimeLimit) + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); + break; } - } - // Process the component - // try to catch (apache::thrift::transport::TTransportException * e) for session loss? - ProcessComponent(CurrentComponent); - break; + // Update the tick time for this component + CurrentComponent->LastTickTime = dNow; + } } // Handle Asset delete @@ -309,9 +385,55 @@ FHoudiniEngineManager::Tick(float DeltaTime) bOffsetZeroed = false; } + // Tick the notification manager + FHoudiniEngine::Get().TickPersistentNotification(0.0f); + return true; } +void +FHoudiniEngineManager::AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC) +{ + // See if we should start the default "first" session + if (FHoudiniEngine::Get().GetSession() + || FHoudiniEngine::Get().GetFirstSessionCreated() + || !InCurrentHAC) + return; + + // Only try to start the default session if we have an "active" HAC + if (InCurrentHAC->GetAssetState() == EHoudiniAssetState::PreInstantiation + || InCurrentHAC->GetAssetState() == EHoudiniAssetState::Instantiating + || InCurrentHAC->GetAssetState() == EHoudiniAssetState::PreCook + || InCurrentHAC->GetAssetState() == EHoudiniAssetState::Cooking) + { + FString StatusText = TEXT("Initializing Houdini Engine..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // We want to yield for a bit. + //FPlatformProcess::Sleep(0.5f); + + // Indicates that we've tried to start the session once no matter if it failed or succeed + FHoudiniEngine::Get().SetFirstSessionCreated(true); + + // Attempt to restart the session + if (!FHoudiniEngine::Get().RestartSession()) + { + // We failed to start the session + // Stop ticking until it's manually restarted + StopHoudiniTicking(); + + StatusText = TEXT("Houdini Engine failed to initialize."); + } + else + { + StatusText = TEXT("Houdini Engine successfully initialized."); + } + + // Finish the notification and display the results + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + } +} + void FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) { @@ -384,7 +506,6 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) { // Update the HAC's state HAC->AssetState = EHoudiniAssetState::Instantiating; - //HAC->AssetStateResult = EHoudiniAssetStateResult::None; // Update the Task GUID HAC->HapiGUID = TaskGuid; @@ -392,9 +513,8 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) else { // If we couldnt instantiate the asset - // Change the state to NeedInstantiating + // Change the state back to NeedInstantiating HAC->AssetState = EHoudiniAssetState::NeedInstantiation; - //HAC->AssetStateResult = EHoudiniAssetStateResult::None; } break; } @@ -500,6 +620,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) UpdateProcess(HAC); int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + HAC->SetAssetCookCount(CookCount); HAC->OnPostOutputProcessing(); FHoudiniEngineUtils::UpdateBlueprintEditor(HAC); @@ -780,6 +901,8 @@ FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudini FText WarningTitleText = FText::FromString(WarningTitle); FString WarningMessage = FString::Printf(TEXT("Houdini License issue - %s."), *StatusMessage); + FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); + FMessageDialog::Debugf(FText::FromString(WarningMessage), &WarningTitleText); } @@ -985,7 +1108,7 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces bool bNeedsToTriggerViewportUpdate = false; if (bCookSuccess) { - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Processing outputs...")); + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Processing outputs..."), false); // Set new asset id. HAC->AssetId = TaskAssetId; @@ -1014,11 +1137,10 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces HAC->SetHasBeenDuplicated(false); } - // TODO: Need to update rendering information. - // UpdateRenderingInformation(); - HAC->UpdateBounds(); + // Update rendering information. + HAC->UpdateRenderingInformation(); - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString("Finished processing outputs")); + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Finished processing outputs"), true); // Trigger a details panel update FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); @@ -1057,7 +1179,7 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces { OnPostCookBakeDelegate.Unbind(); // Notify the user that the bake failed since the cook failed. - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString("Cook failed, therefore the bake also failed...")); + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Cook failed, therefore the bake also failed..."), true); } } @@ -1187,7 +1309,7 @@ FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskIn if (EHoudiniEngineTaskState::None != OutTaskInfo.TaskState && bDisplaySlateCookingNotifications) { - FHoudiniEngine::Get().CreateTaskSlateNotification(OutTaskInfo.StatusText); + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); } switch (OutTaskInfo.TaskState) @@ -1201,7 +1323,7 @@ FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskIn // Terminate the slate notification if they exist and delete/invalidate the task if (bDisplaySlateCookingNotifications) { - FHoudiniEngine::Get().FinishTaskSlateNotification(OutTaskInfo.StatusText); + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, true); } FHoudiniEngine::Get().RemoveTaskInfo(OutTaskGUID); @@ -1214,7 +1336,7 @@ FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskIn // The current task is still running, simply update the current notification if (bDisplaySlateCookingNotifications) { - FHoudiniEngine::Get().UpdateTaskSlateNotification(OutTaskInfo.StatusText); + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); } } break; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.h b/Source/HoudiniEngine/Private/HoudiniEngineManager.h index d858a224e..0d7e5aa7b 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.h @@ -140,6 +140,9 @@ class FHoudiniEngineManager void EnableEditorAutoSave(const UHoudiniAssetComponent* HAC); + // Automatically try to start the First HE session if needed + void AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC); + private: // Ticker handle, used for processing HAC. diff --git a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h index 98b9fd867..8fae01a00 100644 --- a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h +++ b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h @@ -186,6 +186,8 @@ #define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" #define HAPI_UNREAL_ATTRIB_SPLIT_ATTR "unreal_split_attr" #define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" +#define HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" +#define HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX "unreal_per_instance_custom_data" #define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp index 27f4137aa..16a3bf9b1 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp @@ -51,6 +51,7 @@ #include "HoudiniAssetComponent.h" #include "HoudiniParameter.h" #include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntime.h" #if WITH_EDITOR #include "SAssetSelectionWidget.h" @@ -825,7 +826,8 @@ FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( const FString &BakeFolder, const FString &ObjectName, const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode) + EPackageReplaceMode InReplaceMode, + bool bAutomaticallySetAttemptToLoadMissingPackages) { OutPackageParams.GeoId = InIdentifier.GeoId; OutPackageParams.ObjectId = InIdentifier.ObjectId; @@ -838,6 +840,97 @@ FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( OutPackageParams.ObjectName = ObjectName; } +void +FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + UWorld* const InWorldContext, + const UHoudiniAssetComponent* HoudiniAssetComponent, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FString &InDefaultObjectName, + const FString &InHoudiniAssetName, + FHoudiniPackageParams& OutPackageParams, + FHoudiniAttributeResolver& OutResolver, + const FString &InDefaultBakeFolder, + EPackageReplaceMode InReplaceMode, + bool bAutomaticallySetAttemptToLoadMissingPackages, + bool bInSkipObjectNameResolutionAndUseDefault, + bool bInSkipBakeFolderResolutionAndUseDefault) +{ + // Configure OutPackageParams with the default (UI value first then fallback to default from settings) object name + // and bake folder. We use the "initial" PackageParams as a helper to populate tokens for the resolver. + // + // User specified attributes (eg unreal_bake_folder) are then resolved, with the defaults being those tokens configured + // from the initial PackageParams. Once resolved, we updated the relevant fields in PackageParams + // (ObjectName and BakeFolder), and update the resolver tokens with these final values. + // + // The resolver is then ready to be used to resolve the rest of the user attributes, such as unreal_level_path. + // + const FString DefaultBakeFolder = !InDefaultBakeFolder.IsEmpty() ? InDefaultBakeFolder : + FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + const bool bHasBakeNameUIOverride = !InOutputObject.BakeName.IsEmpty(); + FillInPackageParamsForBakingOutput( + OutPackageParams, + InIdentifier, + DefaultBakeFolder, + bHasBakeNameUIOverride ? InOutputObject.BakeName : InDefaultObjectName, + InHoudiniAssetName, + InReplaceMode, + bAutomaticallySetAttemptToLoadMissingPackages); + + const TMap& CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); + OutResolver.SetCachedAttributes(CachedAttributes); + OutResolver.SetTokensFromStringMap(Tokens); + +#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING + // Log the cached attributes and tokens for debugging + OutResolver.LogCachedAttributesAndTokens(); +#endif + + if (!bInSkipObjectNameResolutionAndUseDefault) + { + // Resolve the object name + // TODO: currently the UI override is checked first (this should probably change so that attributes are used first) + FString ObjectName; + if (bHasBakeNameUIOverride) + { + ObjectName = InOutputObject.BakeName; + } + else + { + ObjectName = OutResolver.ResolveOutputName(); + if (ObjectName.IsEmpty()) + ObjectName = InDefaultObjectName; + } + // Update the object name in the package params and also update its token + OutPackageParams.ObjectName = ObjectName; + OutResolver.SetToken("object_name", OutPackageParams.ObjectName); + } + + if (!bInSkipBakeFolderResolutionAndUseDefault) + { + // Now resolve the bake folder + const FString BakeFolder = OutResolver.ResolveBakeFolder(); + if (!BakeFolder.IsEmpty()) + OutPackageParams.BakeFolder = BakeFolder; + } + + if (!bInSkipObjectNameResolutionAndUseDefault || !bInSkipBakeFolderResolutionAndUseDefault) + { + // Update the tokens from the package params + OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); + OutResolver.SetTokensFromStringMap(Tokens); + +#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING + // Log the final tokens + OutResolver.LogCachedAttributesAndTokens(); +#endif + } +} + + bool FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() { @@ -854,20 +947,19 @@ FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() return false; } -bool -FHoudiniEngineUtils::IsOuterHoudiniAssetComponent(UObject* Obj) -{ - if (!Obj) - return false; - return Obj->GetOuter() && Obj->GetOuter()->IsA(); -} UHoudiniAssetComponent* -FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(UObject* Obj) +FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(const UObject* Obj) { - return Cast(Obj->GetOuter()); + UObject* Outer = Obj->GetOuter(); + UHoudiniAssetComponent* OuterHAC = Cast(Obj->GetOuter()); + if(IsValid(OuterHAC)) + return OuterHAC; + + return Obj->GetTypedOuter(); } + FString FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) { @@ -884,6 +976,7 @@ FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) return HoudiniVersionString; } + void * FHoudiniEngineUtils::LoadLibHAPI(FString & StoredLibHAPILocation) { @@ -1091,7 +1184,10 @@ FHoudiniEngineUtils::IsInitialized() if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) return false; - return (FHoudiniApi::IsInitialized(SessionPtr) == HAPI_RESULT_SUCCESS); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) + return false; + + return true; } bool @@ -1200,7 +1296,17 @@ FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLi return false; if (!FHoudiniEngineUtils::IsInitialized()) + { + // If we're not initialized now, it likely means the session has been lost + FHoudiniEngine::Get().OnSessionLost(); return false; + } + + // Get the preferences + bool bMemoryCopyFirst = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bMemoryCopyFirst = HoudiniRuntimeSettings->bPreferHdaMemoryCopyOverHdaSourceFile; // Get the HDA's file path // We need to convert relative file path to absolute @@ -1216,21 +1322,22 @@ FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLi AssetFileName = FPaths::GetPath(AssetFileName); } + //Check whether we can Load from file/memory + bool bCanLoadFromMemory = (!HoudiniAsset->IsExpandedHDA() && HoudiniAsset->GetAssetBytesCount() > 0); + // If the hda file exists, we can simply load it directly - HAPI_Result Result = HAPI_RESULT_FAILURE; + bool bCanLoadFromFile = false; if ( !AssetFileName.IsEmpty() ) { - if ( FPaths::FileExists(AssetFileName) - || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName) ) ) + if (FPaths::FileExists(AssetFileName) + || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName))) { - // Load the asset from file. - std::string AssetFileNamePlain; - FHoudiniEngineUtils::ConvertUnrealString(AssetFileName, AssetFileNamePlain); - Result = FHoudiniApi::LoadAssetLibraryFromFile( - FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); + bCanLoadFromFile = true; } } + HAPI_Result Result = HAPI_RESULT_FAILURE; + // Lambda to detect license issues auto CheckLicenseValid = [&AssetFileName](const HAPI_Result& Result) { @@ -1244,6 +1351,9 @@ FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLi // as this could lead to unreal becoming stuck and unresponsive due to license timeout FHoudiniEngine::Get().StopSession(); + // Set the HE status to "no license" + FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); + return false; } else @@ -1252,35 +1362,97 @@ FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLi } }; - // Detect license issues when loading the source HDA - if (!CheckLicenseValid(Result)) - return false; + // Lambda to load an HDA from file + auto LoadAssetFromFile = [&Result, &OutAssetLibraryId](const FString& InAssetFileName) + { + // Load the asset from file. + std::string AssetFileNamePlain; + FHoudiniEngineUtils::ConvertUnrealString(InAssetFileName, AssetFileNamePlain); + Result = FHoudiniApi::LoadAssetLibraryFromFile( + FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); - // If loading from file failed, try to load using the memory copy - if (Result != HAPI_RESULT_SUCCESS) + }; + + // Lambda to load an HDA from memory + auto LoadAssetFromMemory = [&Result, &OutAssetLibraryId](UHoudiniAsset* InHoudiniAsset) + { + // Load the asset from the cached memory buffer + Result = FHoudiniApi::LoadAssetLibraryFromMemory( + FHoudiniEngine::Get().GetSession(), + reinterpret_cast(InHoudiniAsset->GetAssetBytes()), + InHoudiniAsset->GetAssetBytesCount(), + true, + &OutAssetLibraryId); + }; + + if (!bMemoryCopyFirst) { - // Expanded hdas cannot be loaded from Memory - if (HoudiniAsset->IsExpandedHDA() || HoudiniAsset->GetAssetBytesCount() <= 0) + // Load from File first + if (bCanLoadFromFile) { - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); - return false; + LoadAssetFromFile(AssetFileName); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; } - else + + // If we failed to load from file ... + if (Result != HAPI_RESULT_SUCCESS) { - // Warn the user that we are loading from memory + // ... warn the user that we will be loading from memory. HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from Memory: source asset file not found."), *AssetFileName); - // Load the asset from the cached memory buffer - Result = FHoudiniApi::LoadAssetLibraryFromMemory( - FHoudiniEngine::Get().GetSession(), - reinterpret_cast(HoudiniAsset->GetAssetBytes()), - HoudiniAsset->GetAssetBytesCount(), true, &OutAssetLibraryId); + // Attempt to load from memory + if (bCanLoadFromMemory) + { + LoadAssetFromMemory(HoudiniAsset); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); + return false; + } } } + else + { + // Load from Memory first + if(bCanLoadFromMemory) + { + LoadAssetFromMemory(HoudiniAsset); - // Detect license issues when loading the memory copy of the HDA - if (!CheckLicenseValid(Result)) - return false; + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + + // If we failed to load from memory ... + if (Result != HAPI_RESULT_SUCCESS) + { + // ... warn the user that we will be loading from file + HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from File: no memory copy available."), *AssetFileName); + + // Attempt to load from file + if (bCanLoadFromFile) + { + LoadAssetFromFile(AssetFileName); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); + return false; + } + } + } if (Result != HAPI_RESULT_SUCCESS) { @@ -2938,6 +3110,7 @@ FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( return true; } + bool FHoudiniEngineUtils::HapiCheckAttributeExists( const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, @@ -3993,24 +4166,6 @@ FHoudiniEngineUtils::GetUnrealTagAttributes( } -int32 -FHoudiniEngineUtils::GetPropertyAttributeList( - const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundPropertyAttributes) -{ - // Get all the detail uprop attributes on the HGPO - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive uprop attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InHGPO.GeoInfo.NodeId, (HAPI_PartId)InHGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutFoundPropertyAttributes, HAPI_ATTROWNER_PRIM); - - return FoundCount; -} - - int32 FHoudiniEngineUtils::GetGenericAttributeList( const HAPI_NodeId& InGeoNodeId, @@ -4142,6 +4297,51 @@ FHoudiniEngineUtils::GetGenericAttributeList( } else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT64) { +#if PLATFORM_LINUX + // On Linux, we unfortunately cannot guarantee that int64 and HAPI_Int64 + // are of the same type, to properly read the value, we must first check the + // size, then either cast them (if sizes match) or convert the values (if sizes don't match) + if (sizeof(int64) != sizeof(HAPI_Int64)) + { + // int64 and HAPI_Int64 are of different size, we need to cast + TArray HAPIIntValues; + HAPIIntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, HAPIIntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to int64 + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < HAPIIntValues.Num(); n++) + CurrentGenericAttribute.IntValues[n] = (int64)HAPIIntValues[n]; + } + else + { + // Initialize the value array + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) with a reinterpret_cast since sizes match + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, reinterpret_cast(CurrentGenericAttribute.IntValues.GetData()), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + } +#else // Initialize the value array CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); @@ -4156,6 +4356,7 @@ FHoudiniEngineUtils::GetGenericAttributeList( // failed to get that attribute's data continue; } +#endif } else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT) { @@ -4218,34 +4419,69 @@ FHoudiniEngineUtils::GetGenericAttributeList( } -void -FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject( - UObject* InObject, const FHoudiniGeoPartObject& InHGPO) +bool +FHoudiniEngineUtils::GetGenericPropertiesAttributes(const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, + const bool InbFindDetailAttributes, const int32& InFirstValidPrimIndex, const int32& InFirstValidVertexIndex, const int32& InFirstValidPointIndex, + TArray& OutPropertyAttributes) { - if (!InObject || InObject->IsPendingKill()) - return; + int32 FoundCount = 0; - // Get the list of all the Properties to modify from the HGPO's attributes - TArray PropertiesAttributesToModify; - if (!FHoudiniEngineUtils::GetPropertyAttributeList(InHGPO, PropertiesAttributesToModify)) - return; + // List all the generic property detail attributes ... + if (InbFindDetailAttributes) + { + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + } - // Iterate over the found Property attributes - for (const auto& CurrentPropAttribute : PropertiesAttributesToModify) + // .. then the primitive property attributes for the given prim + if (InFirstValidPrimIndex != INDEX_NONE) { - // Get the current Property Attribute - const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; - if (CurrentPropertyName.IsEmpty()) - continue; + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); + } + + if (InFirstValidVertexIndex != INDEX_NONE) + { + // .. then finally, point uprop attributes for the given point + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_VERTEX, InFirstValidVertexIndex); + } + if (InFirstValidPointIndex != INDEX_NONE) + { + // .. then finally, point uprop attributes for the given point + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidPointIndex); + } + + return FoundCount > 0; +} + +bool +FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(UObject* InObject, + const TArray& InAllPropertyAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current Property Attribute if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) continue; // Success! - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropertyName, *ClassName, *ObjectName); + NumSuccess++; +#if defined(HOUDINI_ENGINE_LOGGING) + const FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + const FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); +#endif } + + return (NumSuccess > 0); } @@ -4673,6 +4909,7 @@ FHoudiniEngineUtils::GetTileAttribute( bool FHoudiniEngineUtils::GetBakeFolderAttribute( const HAPI_NodeId& InGeoId, + HAPI_AttributeOwner InAttributeOwner, TArray& OutBakeFolder, HAPI_PartId InPartId) { @@ -4681,19 +4918,36 @@ FHoudiniEngineUtils::GetBakeFolderAttribute( HAPI_AttributeInfo BakeFolderAttribInfo; FHoudiniApi::AttributeInfo_Init(&BakeFolderAttribInfo); if (HapiGetAttributeDataAsString( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_DETAIL)) + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, InAttributeOwner)) { if (OutBakeFolder.Num() > 0) return true; } - if (HapiGetAttributeDataAsString( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, HAPI_ATTROWNER_PRIM)) + OutBakeFolder.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + TArray& OutBakeFolder, + HAPI_PartId InPartId) +{ + OutBakeFolder.Empty(); + + if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_PRIM, OutBakeFolder, InPartId)) { if (OutBakeFolder.Num() > 0) return true; } + if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_DETAIL, OutBakeFolder, InPartId)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + OutBakeFolder.Empty(); return false; } @@ -4746,54 +5000,6 @@ FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( return false; } -bool -FHoudiniEngineUtils::GetBakeFolderOverridePath( - const HAPI_NodeId& InGeoId, - FString& OutBakeFolder, - HAPI_PartId InPartId) -{ - FString BakeFolderOverride; - - TArray StringData; - if (GetBakeFolderAttribute(InGeoId, StringData, InPartId)) - { - BakeFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - - if (BakeFolderOverride.StartsWith("Game/")) - { - BakeFolderOverride = "/" + BakeFolderOverride; - } - - FString AbsoluteOverridePath; - if (BakeFolderOverride.StartsWith("/Game/")) - { - const FString RelativePath = FPaths::ProjectContentDir() + BakeFolderOverride.Mid(6, BakeFolderOverride.Len() - 6); - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - if (!BakeFolderOverride.IsEmpty()) - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolderOverride); - } - - // Check Validity of the path - if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) - { - // Only display a warning if the path is invalid, empty is fine - if (!AbsoluteOverridePath.IsEmpty()) - HOUDINI_LOG_WARNING(TEXT("Invalid override bake path: %s"), *BakeFolderOverride); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - OutBakeFolder = HoudiniRuntimeSettings->DefaultBakeFolder; - - return false; - } - - OutBakeFolder = BakeFolderOverride; - return true; -} - bool FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) { diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h index 9f40a517b..d06c71f2f 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h @@ -318,14 +318,6 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils const std::string& ParmName, const float& DefaultValue, float& OutValue); - - // Returns a list with all the Property attributes found on a HGPO - static int32 GetPropertyAttributeList( - const FHoudiniGeoPartObject& InHGPO, TArray& OutFoundUProps); - - // Updates all FProperty attributes found on a given object - static void UpdateAllPropertyAttributesOnObject( - UObject* InObject, const FHoudiniGeoPartObject& InHGPO); // Returns a list of all the generic attributes for a given attribute owner static int32 GetGenericAttributeList( @@ -336,6 +328,19 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils const HAPI_AttributeOwner& AttributeOwner, const int32& InAttribIndex = -1); + // Helper functions for generic property attributes + static bool GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const bool InFindDetailAttributes, // if true, find default attributes + const int32& InFirstValidPrimIndex, // If not INDEX_NONE, look for primitive attribute + const int32& InFirstValidVertexIndex, // If this is not INDEX_NONE, look for vertex attribute + const int32& InFirstValidPointIndex, // If this is not INDEX_NONE, look for point attribute + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes); + /* // Tries to update values for all the UProperty attributes to apply on the object. static void ApplyUPropertyAttributesOnObject( @@ -411,6 +416,15 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); // Helper function to access the "unreal_bake_folder" attribute + static bool GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + HAPI_AttributeOwner InAttributeOwner, + TArray& OutBakeFolder, + HAPI_PartId InPartId=0); + + // Helper function to access the "unreal_bake_folder" attribute + // We check for a primitive attribute first, if the primitive attribute does not exist, we check for a + // detail attribute. static bool GetBakeFolderAttribute( const HAPI_NodeId& InGeoId, TArray& OutBakeFolder, @@ -430,13 +444,6 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils TArray& OutBakeOutlinerFolders, HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - // Helper function to get the bake folder override path. This is the "unreal_bake_folder" attribute, or if this - // does not exist or is invalid, the default bake folder path configured in the settings. - static bool GetBakeFolderOverridePath( - const HAPI_NodeId& InGeoId, - FString& OutBakeFolder, - HAPI_PartId InPartId=0); - // Adds the "unreal_level_path" primitive attribute static bool AddLevelPathAttribute( const HAPI_NodeId& InNodeId, @@ -578,13 +585,36 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // PackageParam utilities // ------------------------------------------------- + // Helper for populating FHoudiniPackageParams. + // If bAutomaticallySetAttemptToLoadMissingPackages is true, then + // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. static void FillInPackageParamsForBakingOutput( FHoudiniPackageParams& OutPackageParams, const FHoudiniOutputObjectIdentifier& InIdentifier, const FString &BakeFolder, const FString &ObjectName, const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets); + EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, + bool bAutomaticallySetAttemptToLoadMissingPackages=true); + + // Helper for populating FHoudiniPackageParams when baking. This includes configuring the resolver to + // resolve the object name and unreal_bake_folder and setting these resolved values on the PackageParams. + // If bAutomaticallySetAttemptToLoadMissingPackages is true, then + // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. + static void FillInPackageParamsForBakingOutputWithResolver( + UWorld* const InWorldContext, + const UHoudiniAssetComponent* HoudiniAssetComponent, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FString &InDefaultObjectName, + const FString &InHoudiniAssetName, + FHoudiniPackageParams& OutPackageParams, + FHoudiniAttributeResolver& OutResolver, + const FString &InDefaultBakeFolder=FString(), + EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, + bool bAutomaticallySetAttemptToLoadMissingPackages=true, + bool bInSkipObjectNameResolutionAndUseDefault=false, + bool bInSkipBakeFolderResolutionAndUseDefault=false); // ------------------------------------------------- // Foliage utilities @@ -596,10 +626,8 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // Returns true if the list was repopulated. static bool RepopulateFoliageTypeListInUI(); - public: - static bool IsOuterHoudiniAssetComponent(UObject* Obj); - static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(UObject* Obj); + static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(const UObject* Obj); protected: diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp index bb9e6db58..6bcb4c5c2 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp @@ -149,8 +149,6 @@ void UHoudiniGeoImportCommandlet::PopulatePackageParams(const FString &InBGEOFil // TODO: will need to reuse the GUID when reimporting? OutPackageParams.ComponentGUID = FGuid::NewGuid(); } - - OutPackageParams.bAttemptToLoadMissingPackages = true; } void UHoudiniGeoImportCommandlet::TickDiscoveredFiles() @@ -307,9 +305,6 @@ void UHoudiniGeoImportCommandlet::HandleImportBGEOMessage( FHoudiniPackageParams PackageParams; InMessage.PopulatePackageParams(PackageParams); - // The commandlet must try to load packages if FindPackage fails, since we unload packages when done - PackageParams.bAttemptToLoadMissingPackages = true; - TArray Outputs; TMap> OutputObjectAttributes; TMap InstancedOutputPartData; @@ -477,12 +472,15 @@ int32 UHoudiniGeoImportCommandlet::ImportBGEO( FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), NodeId, &DisplayGeoInfo)) { + TArray BakeFolderOverrideArray; FString BakeFolderOverride; - const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); - if (bFoundOverride && !BakeFolderOverride.IsEmpty()) + const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderAttribute(DisplayGeoInfo.nodeId, HAPI_ATTROWNER_DETAIL,BakeFolderOverrideArray); + if (bFoundOverride && BakeFolderOverrideArray.Num() > 0) + BakeFolderOverride = BakeFolderOverrideArray[0]; + if (!BakeFolderOverride.IsEmpty()) { PackageParams.BakeFolder = BakeFolderOverride; - HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override: %s"), *PackageParams.BakeFolder); + HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override (detail attrib): %s"), *PackageParams.BakeFolder); } else { @@ -558,9 +556,9 @@ int32 UHoudiniGeoImportCommandlet::ImportBGEO( { const FHoudiniOutputObjectIdentifier OutputIdentifier = Entry.Key; TArray PropertyAttributes; - FHoudiniMeshTranslator::GetGenericPropertiesAttributes( + FHoudiniEngineUtils::GetGenericPropertiesAttributes( OutputIdentifier.GeoId, OutputIdentifier.PartId, - OutputIdentifier.PointIndex, OutputIdentifier.PrimitiveIndex, + true, OutputIdentifier.PrimitiveIndex, INDEX_NONE, OutputIdentifier.PointIndex, PropertyAttributes); OutGenericAttributes->Add(OutputIdentifier, PropertyAttributes); } diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp index 6f8c173ae..8f073376c 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp @@ -195,9 +195,19 @@ UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObj PackageParams.ObjectName = OutputNames[0]; } } + // Could have prim attribute unreal_bake_folder override + TArray BakeFolderNames; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(CurHGPO.GeoId, BakeFolderNames, CurHGPO.PartId)) + { + if (BakeFolderNames.Num() > 0 && !BakeFolderNames[0].IsEmpty()) + { + PackageParams.BakeFolder = BakeFolderNames[0]; + } + } } FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); + FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( CurHGPO, PackageParams, @@ -207,7 +217,8 @@ UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObj ReplacementMaterials, true, EHoudiniStaticMeshMethod::RawMesh, - SMGP); + SMGP, + MBS); } // Add all output objects and materials @@ -261,24 +272,43 @@ UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* I for (auto& CurOutput : CurveOutputs) { bool bFoundOutputName = false; + bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) { if (HGPO.Type != EHoudiniPartType::Curve) continue; - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) + if (!bFoundOutputName) { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - break; + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + } + } + } + + if (!bFoundBakeFolder) + { + TArray Strings; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.BakeFolder = Strings[0]; + bFoundBakeFolder = true; + } } } + + if (bFoundOutputName && bFoundBakeFolder) + break; } - if (bFoundOutputName) + if (bFoundOutputName && bFoundBakeFolder) break; } @@ -449,24 +479,45 @@ UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObjec continue; bool bFoundOutputName = false; + bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) { if (HGPO.Type != EHoudiniPartType::Instancer) continue; - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) + if (!bFoundOutputName) { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - break; + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + break; + } } } + + if (!bFoundBakeFolder) + { + TArray Strings; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.BakeFolder = Strings[0]; + bFoundBakeFolder = true; + break; + } + } + } + + if (bFoundOutputName && bFoundBakeFolder) + break; } - if (bFoundOutputName) + if (bFoundOutputName && bFoundBakeFolder) break; } diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp index 8dca013bf..0c559f453 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp @@ -58,9 +58,11 @@ FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) } bool -FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAssetComponent* OuterObject, - TArray& CurrentHandles, - TArray& NewHandles) +FHoudiniHandleTranslator::BuildAllHandles( + const HAPI_NodeId& AssetId, + UHoudiniAssetComponent* OuterObject, + TArray& CurrentHandles, + TArray& NewHandles) { if (AssetId < 0) return false; @@ -134,13 +136,12 @@ FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAs if (HandleType == EHoudiniHandleType::Unsupported) { HOUDINI_LOG_DISPLAY(TEXT("%s: Unsupported Handle Type %s for handle %s"), - OuterObject ? *(OuterObject->GetName()) : *(OuterObject->GetName()), *TypeName, *HandleName); + OuterObject ? *(OuterObject->GetName()) : TEXT("?"), *TypeName, *HandleName); continue; } UHoudiniHandleComponent* HandleComponent = nullptr; UHoudiniHandleComponent** FoundHandleComponent = CurrentHandlesByName.Find(HandleName); - if (FoundHandleComponent) { HandleComponent = *FoundHandleComponent; @@ -150,7 +151,8 @@ FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAs } else { - HandleComponent = NewObject(OuterObject, + HandleComponent = NewObject( + OuterObject, UHoudiniHandleComponent::StaticClass(), NAME_None, RF_Public | RF_Transactional); @@ -159,7 +161,6 @@ FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAs // Change the creation method so the component is listed in the details panels HandleComponent->CreationMethod = EComponentCreationMethod::Instance; - } if (!HandleComponent) @@ -236,7 +237,6 @@ FHoudiniHandleTranslator::BuildAllHandles(const HAPI_NodeId& AssetId, UHoudiniAs ); } - HapiEulerXform.rstOrder = GetHapiRSTOrder(RSTOrderStrPtr); HapiEulerXform.rotationOrder = GetHapiXYZOrder(XYZOrderStrPtr); diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp index 108204faa..8f4ce3b8d 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp @@ -270,7 +270,7 @@ FHoudiniInputTranslator::BuildAllInputs( CurrentInput->SetInputType(GetDefaultInputTypeFromLabel(CurrentInputLabel), bBlueprintStructureChanged); // Preset the default HDA for objpath input - SetDefaultAssetFromHDA(CurrentInput); + SetDefaultAssetFromHDA(CurrentInput, bBlueprintStructureChanged); } // Update input objects data on UE side for all types of inputs. @@ -385,7 +385,7 @@ FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const UHoudiniInputActor* CurActorInputObject = Cast(CurInputObject); if (CurActorInputObject) { - for (auto & CurActorComponent : CurActorInputObject->ActorComponents) + for (auto & CurActorComponent : CurActorInputObject->GetActorComponents()) { if (!CurActorComponent || CurActorComponent->IsPendingKill()) continue; @@ -544,24 +544,16 @@ FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bFo } bool -FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input) +FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified) { // if (!Input || Input->IsPendingKill()) return false; - // We just handle geo inputs - if (EHoudiniInputType::Geometry != Input->GetInputType()) - return false; - // Make sure we're linked to a valid object path parameter if (Input->GetParameterId() < 0) return false; - // There is a default slot, don't add if slot is already filled - //if (InputObjects.Num() > 1) - // return false; - // Get our ParmInfo HAPI_ParmInfo FoundParamInfo; FHoudiniApi::ParmInfo_Init(&FoundParamInfo); @@ -572,35 +564,111 @@ FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input) return false; } - // TODO: FINISH ME! - - /* // Get our string value HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - Input->GetInputNodeId(), false, - &StringHandle, FoundParamInfo.stringValuesIndex, 1) ) - { - FString OutValue; - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(OutValue)) - { - // Set default object on the HDA instance - will override the parameter string - // and apply the object input local-path thing for the HDA cook. - if (OutValue.Len() > 0) - { - UObject * pObject = LoadObject(nullptr, *OutValue); - if (pObject) - { - return AddInputObject(pObject); - } - } + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + Input->GetAssetNodeId(), + false, + &StringHandle, + FoundParamInfo.stringValuesIndex, + 1)) + { + return false; + } + + FString ParamValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (!HoudiniEngineString.ToFString(ParamValue)) + { + return false; + } + + if (ParamValue.Len() <= 0) + { + return false; + } + + // Chop the default value using semi-colons as separators + TArray Tokens; + ParamValue.ParseIntoArray(Tokens, TEXT(";"), true); + + // Start by setting geometry input objects + int32 GeoIdx = 0; + for (auto& CurToken : Tokens) + { + if (CurToken.IsEmpty()) + continue; + + // Set default objects on the HDA instance - will override the parameter string + // and apply the object input local-path thing for the HDA cook. + UObject * pObject = LoadObject(nullptr, *CurToken); + if (!pObject) + continue; + + Input->SetInputObjectAt(EHoudiniInputType::Geometry, GeoIdx++, pObject); + } + + // See if we can preset world objects as well + int32 WorldIdx = 0; + int32 LandscapedIdx = 0; + int32 HDAIdx = 0; + for (TActorIterator ActorIt(Input->GetWorld(), AActor::StaticClass(), EActorIteratorFlags::SkipPendingKill); ActorIt; ++ActorIt) + { + AActor* CurActor = *ActorIt; + if (!CurActor) + continue; + + AActor* FoundActor = nullptr; + int32 FoundIdx = Tokens.Find(CurActor->GetFName().ToString()); + if (FoundIdx == INDEX_NONE) + FoundIdx = Tokens.Find(CurActor->GetActorLabel()); + + if(FoundIdx != INDEX_NONE) + FoundActor = CurActor; + + if (!FoundActor) + continue; + + // Select the found actor in the world input + Input->SetInputObjectAt(EHoudiniInputType::World, WorldIdx++, FoundActor); + + if (FoundActor->IsA()) + { + // Select the HDA in the asset input + Input->SetInputObjectAt(EHoudiniInputType::Asset, HDAIdx++, FoundActor); + } + else if (FoundActor->IsA()) + { + // Select the landscape in the landscape input + Input->SetInputObjectAt(EHoudiniInputType::Landscape, LandscapedIdx++, FoundActor); } + + // Remove the Found Token + Tokens.RemoveAt(FoundIdx); } - */ - return false; + // See if we should change the default input type + if (Input->GetInputType() == EHoudiniInputType::Geometry && WorldIdx > 0 && GeoIdx == 0) + { + if (LandscapedIdx == WorldIdx) + { + // We've only selected landscapes, set to landscape IN + Input->SetInputType(EHoudiniInputType::Landscape, bOutBlueprintStructureModified); + } + else if (HDAIdx == WorldIdx) + { + // We've only selected Houdini Assets, set to Asset IN + Input->SetInputType(EHoudiniInputType::Asset, bOutBlueprintStructureModified); + } + else + { + // Set to world input + Input->SetInputType(EHoudiniInputType::World, bOutBlueprintStructureModified); + } + } + + return true; } bool @@ -874,7 +942,7 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) UHoudiniInputActor* InputActor = Cast(CurrentInputObject); if (InputActor && !InputActor->IsPendingKill()) { - for (auto CurrentComp : InputActor->ActorComponents) + for (auto CurrentComp : InputActor->GetActorComponents()) { if (!CurrentComp || CurrentComp->IsPendingKill()) continue; @@ -1218,11 +1286,12 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( case EHoudiniInputObjectType::HoudiniSplineComponent: { UHoudiniInputHoudiniSplineComponent* InputCurve = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve); - + + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve, InInput->IsAddRotAndScaleAttributesEnabled()); + if (bSuccess) OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - + break; } @@ -1399,7 +1468,7 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( // Iterate on all the actor input objects and see if their transform needs to be uploaded // TODO? Also update the component's actor transform?? - for (auto& CurrentComponent : InputActor->ActorComponents) + for (auto& CurrentComponent : InputActor->GetActorComponents()) { if (!CurrentComponent || CurrentComponent->IsPendingKill()) continue; @@ -2005,7 +2074,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& In bool FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject) + const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject, bool bInAddRotAndScaleAttributes) { if (!InObject || InObject->IsPendingKill()) return false; @@ -2014,7 +2083,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( if (!Curve || Curve->IsPendingKill()) return true; - if (!FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent(InObjNodeName, Curve)) + if (!FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(Curve, bInAddRotAndScaleAttributes)) return false; // See if the component needs it node Id invalidated @@ -2203,7 +2272,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForActor( HAC->SetNoProxyMeshNextCookRequested(true); } } - else if (InObject->ActorComponents.Num() == 0 && HAC->HasAnyOutputComponent()) + else if (InObject->GetActorComponents().Num() == 0 && HAC->HasAnyOutputComponent()) { // The HAC has non-proxy output components, but the InObject does not have any // actor components. This can arise after a cook if previously there were only @@ -2221,7 +2290,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForActor( // Now, commit all of this actor's component int32 ComponentIdx = 0; - for (UHoudiniInputSceneComponent* CurComponent : InObject->ActorComponents) + for (UHoudiniInputSceneComponent* CurComponent : InObject->GetActorComponents()) { if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) ComponentIdx++; @@ -2484,7 +2553,8 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) continue; // Make sure the actor is still valid - bool bValidActorObject = ActorObject->GetActor() && !ActorObject->GetActor()->IsPendingKill(); + AActor* const Actor = ActorObject->GetActor(); + bool bValidActorObject = Actor && !Actor->IsPendingKill(); // For BrushActors, the brush and actors must be valid as well UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); @@ -2524,32 +2594,12 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) bHasChanged = true; } - // Iterates on all of the actor's component - TArray ComponentToDeleteIndices; - for (int32 CompIdx = 0; CompIdx < ActorObject->ActorComponents.Num(); CompIdx++) - { - UHoudiniInputSceneComponent* CurActorComp = ActorObject->ActorComponents[CompIdx]; - if (!CurActorComp || CurActorComp->IsPendingKill()) - continue; - - // Make sure the actor is still valid - if (!CurActorComp->InputObject || CurActorComp->InputObject->IsPendingKill()) - { - // If it's not, mark it for deletion - if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) - { - CurActorComp->InvalidateData(); - - // We only need to update the input if the object were created in Houdini - bHasChanged = true; - } - - // Delete the component object - ComponentToDeleteIndices.Add(CompIdx); - - continue; - } + // Ensure we are aware of all the components of the actor + ActorObject->Update(Actor); + // Check if any components have content or transform changes + for (auto CurActorComp : ActorObject->GetActorComponents()) + { if (CurActorComp->HasComponentTransformChanged()) { CurActorComp->MarkTransformChanged(true); @@ -2563,9 +2613,13 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) } } - // Delete the components objects on the actor that were marked for deletion - for (int32 ToDeleteIdx = ComponentToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) - ActorObject->ActorComponents.RemoveAt(ComponentToDeleteIndices[ToDeleteIdx]); + // Check if we added/removed any components in the call to update + if (ActorObject->GetLastUpdateNumComponentsAdded() > 0 || ActorObject->GetLastUpdateNumComponentsRemoved() > 0) + { + bHasChanged = true; + if (ActorObject->GetLastUpdateNumComponentsRemoved() > 0) + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } } // Delete the actor objects that were marked for deletion diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h index b877f0fe9..4a683f973 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h @@ -123,7 +123,7 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator static EHoudiniInputType GetDefaultInputTypeFromLabel(const FString& InputName); - static bool SetDefaultAssetFromHDA(UHoudiniInput* Input); + static bool SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified); static bool ChangeInputType(UHoudiniInput* Input, const bool& bForce); @@ -140,7 +140,8 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator static bool HapiCreateInputNodeForHoudiniSplineComponent( const FString& InObjNodeName, - UHoudiniInputHoudiniSplineComponent* InObject); + UHoudiniInputHoudiniSplineComponent* InObject, + bool bInSetRotAndScaleAttributes); static bool HapiCreateInputNodeForLandscape( const FString& InObjNodeName, diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp index 04880b326..3dec7a1b6 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp @@ -89,6 +89,9 @@ FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( // Extract the generic attributes GetGenericPropertiesAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllPropertyAttributes); + // Check for per instance custom data + GetPerInstanceCustomData(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData); + //Get the level path attribute on the instancer if (!FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllLevelPaths)) { @@ -117,6 +120,13 @@ FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( OutInstancedOutputPartData.AllBakeActorNames.Empty(); } + // Get the unreal_bake_folder attribute + if (!FHoudiniEngineUtils::GetBakeFolderAttribute(InHGPO.GeoId, OutInstancedOutputPartData.AllBakeFolders, InHGPO.PartId)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeFolders.Empty(); + } + // Get the bake outliner folder attribute if (!FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeOutlinerFolders)) { @@ -166,10 +176,10 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( { // Foliage instancers store a HISMC in the components UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentPair.Value.OutputComponent); - if (!FoliageHISMC || FoliageHISMC->IsPendingKill()) + if (!IsValid(FoliageHISMC)) continue; - CleanupFoliageInstances(FoliageHISMC, ParentComponent); + CleanupFoliageInstances(FoliageHISMC, CurrentPair.Value.OutputObject, ParentComponent); bHaveAnyFoliageInstancers = true; } @@ -198,7 +208,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( } if (!InstancedOutputPartDataPtr) { - if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs,InstancedOutputPartDataTmp)) + if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs, InstancedOutputPartDataTmp)) continue; InstancedOutputPartDataPtr = &InstancedOutputPartDataTmp; } @@ -206,7 +216,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( const FHoudiniInstancedOutputPartData& InstancedOutputPartData = *InstancedOutputPartDataPtr; TArray InstancerMaterials; - if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes,InstancerMaterials)) + if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes, InstancerMaterials)) InstancerMaterials.Empty(); if (InstancedOutputPartData.bIsFoliageInstancer) @@ -239,9 +249,12 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( UpdateInstanceVariationObjects( OutputIdentifier, InstancedOutputPartData.OriginalInstancedObjects, - InstancedOutputPartData.OriginalInstancedTransforms, InOutput->GetInstancedOutputs(), - VariationInstancedObjects, VariationInstancedTransforms, - VariationOriginalObjectIndices, VariationIndices); + InstancedOutputPartData.OriginalInstancedTransforms, + InOutput->GetInstancedOutputs(), + VariationInstancedObjects, + VariationInstancedTransforms, + VariationOriginalObjectIndices, + VariationIndices); // Create the instancer components now for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) @@ -297,9 +310,13 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( USceneComponent* NewInstancerComponent = nullptr; if (!CreateOrUpdateInstanceComponent( - InstancedObject, InstancedObjectTransforms, - InstancedOutputPartData.AllPropertyAttributes, CurHGPO, - ParentComponent, OldInstancerComponent, NewInstancerComponent, + InstancedObject, + InstancedObjectTransforms, + InstancedOutputPartData.AllPropertyAttributes, + CurHGPO, + ParentComponent, + OldInstancerComponent, + NewInstancerComponent, InstancedOutputPartData.bSplitMeshInstancer, InstancedOutputPartData.bIsFoliageInstancer, VariationMaterials, @@ -312,6 +329,12 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (!NewInstancerComponent) continue; + // Copy the per-instance custom data if we have any + UpdateChangedPerInstanceCustomData( + InstancedOutputPartData.NumCustomFloats, + InstancedOutputPartData.PerInstanceCustomData, + NewInstancerComponent); + // If the instanced object (by ref) wasn't found, hide the component if(InstancedObject == DefaultReferenceSM) NewInstancerComponent->SetHiddenInGame(true); @@ -322,10 +345,12 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (bIsProxyMesh) { NewOutputObject.ProxyComponent = NewInstancerComponent; + NewOutputObject.ProxyObject = InstancedObject; } else { NewOutputObject.OutputComponent = NewInstancerComponent; + NewOutputObject.OutputObject = InstancedObject; } // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before @@ -351,6 +376,9 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (InstancedOutputPartData.AllBakeActorNames.Num() > 0 && !InstancedOutputPartData.AllBakeActorNames[0].IsEmpty()) NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[0]); + if (InstancedOutputPartData.AllBakeFolders.Num() > 0 && !InstancedOutputPartData.AllBakeFolders[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, InstancedOutputPartData.AllBakeFolders[0]); + if (InstancedOutputPartData.AllBakeOutlinerFolders.Num() > 0 && !InstancedOutputPartData.AllBakeOutlinerFolders[0].IsEmpty()) NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[0]); @@ -377,6 +405,8 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, PerSplitAttributes->BakeActorName); if (!PerSplitAttributes->BakeOutlinerFolder.IsEmpty()) NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, PerSplitAttributes->BakeOutlinerFolder); + if (!PerSplitAttributes->BakeFolder.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, PerSplitAttributes->BakeFolder); } } } @@ -441,16 +471,18 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( } if(bDestroy) - RemoveAndDestroyComponent(OldComponent); + RemoveAndDestroyComponent(OldComponent, OldPair.Value.OutputObject); OldPair.Value.OutputComponent = nullptr; + OldPair.Value.OutputObject = nullptr; } UObject* OldProxyComponent = OldPair.Value.ProxyComponent; if (OldProxyComponent) { - RemoveAndDestroyComponent(OldProxyComponent); + RemoveAndDestroyComponent(OldProxyComponent, OldPair.Value.ProxyObject); OldPair.Value.ProxyComponent = nullptr; + OldPair.Value.ProxyObject = nullptr; } } OldOutputObjects.Empty(); @@ -602,7 +634,7 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( if (OldInstancerComponent != NewInstancerComponent) { // Previous component wasn't reused, detach and delete it - RemoveAndDestroyComponent(OldInstancerComponent); + RemoveAndDestroyComponent(OldInstancerComponent, nullptr); // Replace it with the new component if (FoundOutputObject) @@ -629,14 +661,14 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( UObject* OldComponent = ToDeletePair.Value.OutputComponent; if (OldComponent) { - RemoveAndDestroyComponent(OldComponent); + RemoveAndDestroyComponent(OldComponent, ToDeletePair.Value.OutputObject); ToDeletePair.Value.OutputComponent = nullptr; } UObject* OldProxyComponent = ToDeletePair.Value.ProxyComponent; if (OldProxyComponent) { - RemoveAndDestroyComponent(OldProxyComponent); + RemoveAndDestroyComponent(OldProxyComponent, ToDeletePair.Value.ProxyObject); ToDeletePair.Value.ProxyComponent = nullptr; } @@ -1064,12 +1096,17 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_PRIM); + // Get the unreal_bake_folder attribute + TArray AllBakeFolders; + const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( + InHGPO.GeoId, HAPI_ATTROWNER_PRIM, AllBakeFolders, InHGPO.PartId); + // Get the bake outliner folder attribute TArray AllBakeOutlinerFolders; const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_PRIM); - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; for (const auto& InstancedPartId : InstancedPartIds) { @@ -1134,6 +1171,10 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( { PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; } + if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; + } if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) { PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; @@ -1235,12 +1276,17 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_POINT); + // Get the unreal_bake_folder attribute + TArray AllBakeFolders; + const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( + InHGPO.GeoId, HAPI_ATTROWNER_POINT, AllBakeFolders, InHGPO.PartId); + // Get the bake outliner folder attribute TArray AllBakeOutlinerFolders; const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_POINT); - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders; + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; // Array used to store the split values per objects // Will only be used if we have a split attribute @@ -1477,6 +1523,10 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( { PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; } + if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; + } if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) { PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; @@ -1668,9 +1718,11 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( // See if we can reuse the old component InstancerComponentType OldType = InstancerComponentType::Invalid; - if (OldComponent && !OldComponent->IsPendingKill()) + if (OldComponent/*&& !OldComponent->IsPendingKill()*/) // The old component could be marked as pending kill { - if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) + if(OldComponent->IsA()) + OldType = Foliage; + else if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) OldType = Foliage; else if (OldComponent->IsA()) OldType = HierarchicalInstancedStaticMeshComponent; @@ -1808,7 +1860,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( // If the old component couldn't be reused, dettach/ destroy it if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) { - RemoveAndDestroyComponent(OldComponent); + RemoveAndDestroyComponent(OldComponent, nullptr); } return bSuccess; @@ -1883,10 +1935,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( // Apply generic attributes if we have any // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, 0); - } + UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, 0); // Assign the new ISMC / HISMC to the output component if we created a new one if(bCreatedNewComponent) @@ -2156,10 +2205,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( if (!CurSMC || CurSMC->IsPendingKill()) continue; - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); - } + UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); } } @@ -2223,14 +2269,14 @@ FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( } // Now add the instances Transform - SMC->SetRelativeTransform(InstancedObjectTransforms[0]); + if (InstancedObjectTransforms.Num() > 0) + { + SMC->SetRelativeTransform(InstancedObjectTransforms[0]); + } // Apply generic attributes if we have any // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, 0); - } + UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, 0); // Assign the new ISMC / HISMC to the output component if we created a new one if (bCreatedNewComponent) @@ -2295,10 +2341,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( // Apply generic attributes if we have any // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, 0); - } + UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, 0); // Assign the new HSMC to the output component if we created a new one if (bCreatedNewComponent) @@ -2320,7 +2363,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, + USceneComponent*& NewInstancedComponent, UMaterialInterface * InstancerMaterial /*=nullptr*/) { // We need either a valid SM or a valid Foliage Type @@ -2376,7 +2419,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( bCreatedNew = true; } - if (!bCreatedNew && CreatedInstancedComponent) + if (!bCreatedNew && NewInstancedComponent) { // TODO: Shouldnt be needed anymore // Clean up the instances previously generated for that component @@ -2417,37 +2460,30 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( CurrentInstanceCount++; } - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); - // TODO: This was due to a bug in UE4.22-20, check if still needed! - if (FoliageHISMC) - FoliageHISMC->BuildTreeIfOutdated(true, true); - - if (InstancerMaterial) + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); + if (IsValid(FoliageHISMC)) { - FoliageHISMC->OverrideMaterials.Empty(); - int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->StaticMaterials.Num() : 1; - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - FoliageHISMC->SetMaterial(Idx, InstancerMaterial); - } + // TODO: This was due to a bug in UE4.22-20, check if still needed! + FoliageHISMC->BuildTreeIfOutdated(true, true); - // Apply generic attributes if we have any - /* - // TODO: Handle variations w/ index - for (const auto& CurrentAttrib : AllPropertyAttributes) - { - UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); + if (InstancerMaterial) + { + FoliageHISMC->OverrideMaterials.Empty(); + int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->StaticMaterials.Num() : 1; + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + FoliageHISMC->SetMaterial(Idx, InstancerMaterial); + } } - */ - // Try to aplly generic properties attributes + // Try to apply generic properties attributes // either on the instancer, mesh or foliage type // TODO: Use proper atIndex!! UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, 0); UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, 0); - if (bCreatedNew && FoliageHISMC) - CreatedInstancedComponent = FoliageHISMC; + if (IsValid(FoliageHISMC)) + NewInstancedComponent = FoliageHISMC; // TODO: // We want to make this invisible if it's a collision instancer. @@ -2516,13 +2552,22 @@ bool FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( UObject* InObject, const TArray& InAllPropertyAttributes, const int32& AtIndex) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; // Iterate over the found Property attributes int32 NumSuccess = 0; for (const auto& CurrentPropAttribute : InAllPropertyAttributes) { + if (CurrentPropAttribute.AttributeName.Equals(TEXT("NumCustomDataFloats"), ESearchCase::IgnoreCase)) + { + // Skip, as setting NumCustomDataFloats this way causes Unreal to crash! + HOUDINI_LOG_WARNING( + TEXT("Skipping UProperty %s on %s, custom data floats should be modified via the unreal_num_custom_floats and unreal_per_instance_custom_dataX attributes"), + *CurrentPropAttribute.AttributeName, *InObject->GetName()); + continue; + } + // Update the current property for the given instance index if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute, AtIndex)) continue; @@ -2536,7 +2581,7 @@ FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( } bool -FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent) +FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent, UObject* InFoliageObject) { if (!InComponent || InComponent->IsPendingKill()) return false; @@ -2547,7 +2592,7 @@ FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent) // Make sure foliage our foliage instances have been removed USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); if (ParentComponent && !ParentComponent->IsPendingKill()) - CleanupFoliageInstances(FISMC, ParentComponent); + CleanupFoliageInstances(FISMC, InFoliageObject, ParentComponent); // do not delete FISMC that still have instances left // as we have cleaned up our instances before, these have been hand-placed @@ -2812,7 +2857,10 @@ FHoudiniInstanceTranslator::IsFoliageInstancer(const int32& InGeoId, const int32 AActor* -FHoudiniInstanceTranslator::SpawnInstanceActor(const FTransform& InTransform, ULevel* InSpawnLevel, UHoudiniInstancedActorComponent* InIAC) +FHoudiniInstanceTranslator::SpawnInstanceActor( + const FTransform& InTransform, + ULevel* InSpawnLevel, + UHoudiniInstancedActorComponent* InIAC) { if (!InIAC || InIAC->IsPendingKill()) return nullptr; @@ -2846,7 +2894,10 @@ FHoudiniInstanceTranslator::SpawnInstanceActor(const FTransform& InTransform, UL void -FHoudiniInstanceTranslator::CleanupFoliageInstances(/*const FHoudiniInstancedOutput& InInstancedOutput,*/ UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, USceneComponent* InParentComponent) +FHoudiniInstanceTranslator::CleanupFoliageInstances( + UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, + UObject* InInstancedObject, + USceneComponent* InParentComponent) { if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) return; @@ -2861,9 +2912,16 @@ FHoudiniInstanceTranslator::CleanupFoliageInstances(/*const FHoudiniInstancedOut if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) return; - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + // Get the Foliage Type + UFoliageType *FoliageType = Cast(InInstancedObject); if (!FoliageType || FoliageType->IsPendingKill()) - return; + { + // Try to get the foliage type for the instanced mesh from the actor + FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InInstancedObject); + + if (!FoliageType || FoliageType->IsPendingKill()) + return; + } // Clean up the instances previously generated for that component InstancedFoliageActor->DeleteInstancesForComponent(InParentComponent, FoliageType); @@ -2987,7 +3045,8 @@ FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAP return bHISM; } -void FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() +void +FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() { NumInstancedTransformsPerObject.Empty(); OriginalInstancedTransformsFlat.Empty(); @@ -3011,7 +3070,8 @@ void FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths } } -void FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() +void +FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() { const int32 NumObjects = NumInstancedTransformsPerObject.Num(); OriginalInstancedTransforms.Init(TArray(), NumObjects); @@ -3053,4 +3113,157 @@ void FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectA } } +bool +FHoudiniInstanceTranslator::GetPerInstanceCustomData( + const int32& InGeoNodeId, + const int32& InPartId, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) +{ + // Initialize sizes to zero + OutInstancedOutputPartData.NumCustomFloats = 0; + OutInstancedOutputPartData.PerInstanceCustomData.SetNum(0); + + // First look for the number of custom floats + // If we dont have the attribute, or it is set to zero, we dont have PerInstanceCustomData + // HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" + HAPI_AttributeInfo AttribInfoNumCustomFloats; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNumCustomFloats); + + TArray CustomFloatsArray; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoNodeId, InPartId, + HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS, + AttribInfoNumCustomFloats, + CustomFloatsArray)) + { + return false; + } + + if (CustomFloatsArray.Num() <= 0) + return false; + + OutInstancedOutputPartData.NumCustomFloats = CustomFloatsArray[0]; + if (OutInstancedOutputPartData.NumCustomFloats <= 0) + return false; + + // We do have custom float, now read the per instance custom data + // They are stored in attributes that uses the "unreal_per_instance_custom" prefix + // ie, unreal_per_instance_custom0, unreal_per_instance_custom1 etc... + // We do not supprot tuples/arrays attributes for now. + TArray> AllCustomDataAttributeValues; + AllCustomDataAttributeValues.SetNum(OutInstancedOutputPartData.NumCustomFloats); + + // Read the custom data attributes + int32 NumInstance = 0; + for (int32 nIdx = 0; nIdx < OutInstancedOutputPartData.NumCustomFloats; nIdx++) + { + // Build the custom data attribute + FString CurrentAttr = TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX) + FString::FromInt(nIdx); + + // TODO? Tuple values Array attributes? + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + // Retrieve the custom data values + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + InGeoNodeId, InPartId, + TCHAR_TO_ANSI(*CurrentAttr), + AttribInfo, + AllCustomDataAttributeValues[nIdx], + 1)) + { + // Skip, we'll fill the values with zeros later on + continue; + } + + if (NumInstance < AllCustomDataAttributeValues[nIdx].Num()) + NumInstance = AllCustomDataAttributeValues[nIdx].Num(); + + if (NumInstance != AllCustomDataAttributeValues[nIdx].Num()) + { + HOUDINI_LOG_ERROR(TEXT("Instancer: Invalid number of Per-Instance Custom data attributes, ignoring...")); + OutInstancedOutputPartData.NumCustomFloats = 0; + return false; + } + } + + // Check sizes + if (AllCustomDataAttributeValues.Num() != OutInstancedOutputPartData.NumCustomFloats) + { + HOUDINI_LOG_ERROR(TEXT("Instancer: Number of Per-Instance Custom data attributes don't match the number of custom floats, ignoring...")); + OutInstancedOutputPartData.NumCustomFloats = 0; + return false; + } + + // Now that we have read all the custom data values, we need to "interlace" them + // in the final per-instance custom data array, fill missing values with zeroes + OutInstancedOutputPartData.PerInstanceCustomData.SetNumZeroed(OutInstancedOutputPartData.NumCustomFloats * NumInstance); + + // Fill the custom data array by interlacing the custom float values + for (int32 nCustomIdx = 0; nCustomIdx < OutInstancedOutputPartData.NumCustomFloats; nCustomIdx++) + { + int32 CurrentNumInstance = NumInstance; + if (NumInstance < AllCustomDataAttributeValues[nCustomIdx].Num()) + CurrentNumInstance = AllCustomDataAttributeValues[nCustomIdx].Num(); + + // Copy the attribute value we read into the custom data array + for (int32 nInstanceIdx = 0; nInstanceIdx < CurrentNumInstance; nInstanceIdx++) + { + OutInstancedOutputPartData.PerInstanceCustomData[nInstanceIdx * OutInstancedOutputPartData.NumCustomFloats + nCustomIdx] = AllCustomDataAttributeValues[nCustomIdx][nInstanceIdx]; + } + } + + return true; +} + + +bool +FHoudiniInstanceTranslator::UpdateChangedPerInstanceCustomData( + const int32& InNumCustomFloats, + const TArray& InPerInstanceCustomData, + USceneComponent* InComponentToUpdate) +{ + // Checks + if (InNumCustomFloats < 0) + return false; + + UInstancedStaticMeshComponent* ISMC = Cast(InComponentToUpdate); + if (!IsValid(ISMC)) + return false; + + // No Custom data to add/remove + if (ISMC->NumCustomDataFloats == 0 && InNumCustomFloats == 0) + return false; + + // We can copy the per instance custom data if we have any + // TODO: Properly extract only needed values! + ISMC->NumCustomDataFloats = InNumCustomFloats; + + int32 InstanceCount = ISMC->GetInstanceCount(); + + // Clear out and reinit to 0 the PerInstanceCustomData array + ISMC->PerInstanceSMCustomData.Empty(InstanceCount * InNumCustomFloats); + ISMC->PerInstanceSMCustomData.SetNumZeroed(InstanceCount * InNumCustomFloats); + + // Behaviour copied From UInstancedStaticMeshComponent::SetCustomData() + // except we modify all the instance/custom values at once + ISMC->Modify(); + + // MemCopy + const int32 NumToCopy = FMath::Min(ISMC->PerInstanceSMCustomData.Num(), InPerInstanceCustomData.Num()); + if (NumToCopy > 0) + { + FMemory::Memcpy(&ISMC->PerInstanceSMCustomData[0], InPerInstanceCustomData.GetData(), NumToCopy * InPerInstanceCustomData.GetTypeSize()); + } + + // Force recreation of the render data when proxy is created + //NewISMC->InstanceUpdateCmdBuffer.Edit(); + // Cant call the edit function above because the function is defined in a different cpp file than the .h it is declared in... + ISMC->InstanceUpdateCmdBuffer.NumEdits++; + + ISMC->MarkRenderStateDirty(); + + return true; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h index 92af07f8d..b89e07cf7 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h @@ -59,6 +59,10 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes // bake outliner folder attribute value UPROPERTY() FString BakeOutlinerFolder; + + // unreal_bake_folder attribute value + UPROPERTY() + FString BakeFolder; }; USTRUCT() @@ -108,6 +112,10 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData UPROPERTY() TArray AllBakeActorNames; + // All unreal_bake_folder attributes (prim attr is checked first then detail) + UPROPERTY() + TArray AllBakeFolders; + // All bake outliner folder attributes from the first attribute owner we could find UPROPERTY() TArray AllBakeOutlinerFolders; @@ -126,6 +134,15 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData UPROPERTY() TArray MaterialAttributes; + // Number of custom floats for the instancer + UPROPERTY() + int32 NumCustomFloats; + + // Custom float array + // Size is NumCustomFloat * NumberOfInstances + UPROPERTY() + TArray PerInstanceCustomData; + void BuildFlatInstancedTransformsAndObjectPaths(); void BuildOriginalInstancedTransformsAndObjectArrays(); @@ -144,7 +161,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator UHoudiniOutput* InOutput, const TArray& InAllOutputs, UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); + const TMap* InPreBuiltInstancedOutputPartData = nullptr); static bool GetInstancerObjectsAndTransforms( const FHoudiniGeoPartObject& InHGPO, @@ -283,12 +300,13 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, + USceneComponent*& NewInstancedComponent, UMaterialInterface * InstancerMaterial /*=nullptr*/); // Helper fumction to properly remove/destroy a component static bool RemoveAndDestroyComponent( - UObject* InComponent); + UObject* InComponent, + UObject* InFoliageObject); // Utility function // Fetches instance transforms and convert them to ue4 coordinates @@ -344,6 +362,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator static void CleanupFoliageInstances( UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, + UObject* InInstancedObject, USceneComponent* InParentComponent); static FString GetInstancerTypeFromComponent( @@ -360,4 +379,16 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator // Get if force using HISM from attribute static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); + + // Checks for PerInstanceCustomData on the instancer part + static bool GetPerInstanceCustomData( + const int32& InGeoNodeId, + const int32& InPartId, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); + + // Update PerInstanceCustom data on the given component if possible + static bool UpdateChangedPerInstanceCustomData( + const int32& InNumCustomFloats, + const TArray& InPerInstanceCustomData, + USceneComponent* InComponentToUpdate); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp index c991fae05..667e1f122 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp @@ -53,32 +53,22 @@ #include "UObject/UnrealType.h" #include "GameFramework/WorldSettings.h" -#include "HAL/PlatformFilemanager.h" #include "Misc/Paths.h" -#include "Engine/LevelStreamingDynamic.h" #include "Modules/ModuleManager.h" #include "AssetToolsModule.h" #include "HoudiniEngineRuntimeUtils.h" -#include "LevelUtils.h" #include "Factories/WorldFactory.h" #include "Misc/Guid.h" #include "Engine/LevelBounds.h" #include "HAL/IConsoleManager.h" #include "Engine/AssetManager.h" -#include "Engine/LevelStreamingAlwaysLoaded.h" -#include "LandscapeEditor/Private/LandscapeEdMode.h" -#include "Misc/AssetRegistryInterface.h" -#include "Misc/StringFormatArg.h" -#include "Engine/WorldComposition.h" +#include "Misc/ScopedSlowTask.h" #if WITH_EDITOR + #include "EditorLevelUtils.h" #include "LandscapeEditorModule.h" #include "LandscapeFileFormatInterface.h" - #include "EditorLevelUtils.h" - #include "WorldBrowserModule.h" - #include "EditorLevelUtils.h" - #include "Misc/WorldCompositionUtility.h" #endif static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( @@ -91,6 +81,10 @@ static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( typedef FHoudiniEngineUtils FHUtils; +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY(); + bool FHoudiniLandscapeTranslator::CreateLandscape( UHoudiniOutput* InOutput, @@ -102,6 +96,9 @@ FHoudiniLandscapeTranslator::CreateLandscape( UWorld* InWorld, // Persistent / root world for the landscape const TMap& LayerMinimums, const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, FHoudiniPackageParams InPackageParams, TArray& OutCreatedPackages ) @@ -132,6 +129,8 @@ FHoudiniLandscapeTranslator::CreateLandscape( FString NodeNameSuffix = GetActorNameSuffix(InPackageParams.PackageMode); + const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + TArray IntData; TArray StrData; // Output attributes will be stored on the Output object and will be used again during baking to determine @@ -140,7 +139,7 @@ FHoudiniLandscapeTranslator::CreateLandscape( TMap OutputAttributes; TMap OutputTokens; FHoudiniAttributeResolver Resolver; - InPackageParams.UpdateTokensFromParams(InWorld, OutputTokens); + InPackageParams.UpdateTokensFromParams(InWorld, HoudiniAssetComponent, OutputTokens); bool bHasTile = Heightfield->VolumeTileIndex >= 0; @@ -205,13 +204,25 @@ FHoudiniLandscapeTranslator::CreateLandscape( // --------------------------------------------- FString LandscapeTileActorName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; TArray AllOutputNames; - if (!FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames)) + if (FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames)) { if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) LandscapeTileActorName = AllOutputNames[0]; } OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); + // --------------------------------------------- + // Attribute: unreal_bake_folder + // --------------------------------------------- + TArray AllBakeFolders; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(GeoId, AllBakeFolders, PartId)) + { + FString BakeFolder; + if (AllBakeFolders.Num() > 0 && !AllBakeFolders[0].IsEmpty()) + BakeFolder = AllBakeFolders[0]; + OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_BAKE_FOLDER), BakeFolder); + } + // Streaming proxy actors/tiles requires a "main" landscape actor // that contains the shared landscape state. bool bRequiresSharedLandscape = false; @@ -325,20 +336,33 @@ FHoudiniLandscapeTranslator::CreateLandscape( FloatMax = fGlobalMax; // Get the Unreal landscape size - int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; - int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; - int32 UnrealTileSizeX = -1; - int32 UnrealTileSizeY = -1; - int32 NumSectionPerLandscapeComponent = -1; - int32 NumQuadsPerLandscapeSection = -1; - - if (!FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - HoudiniHeightfieldXSize, HoudiniHeightfieldYSize, - UnrealTileSizeX, UnrealTileSizeY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection)) + const int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; + const int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; + + if (!LandscapeTileSizeInfo.bIsCached) { - return false; + // Calculate a landscape size info from this heightfield to be + // used by subsequent tiles on the same landscape + if (FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + LandscapeTileSizeInfo.UnrealSizeX, + LandscapeTileSizeInfo.UnrealSizeY, + LandscapeTileSizeInfo.NumSectionsPerComponent, + LandscapeTileSizeInfo.NumQuadsPerSection)) + { + LandscapeTileSizeInfo.bIsCached = true; + } + else + { + return false; + } } + + const int32 UnrealTileSizeX = LandscapeTileSizeInfo.UnrealSizeX; + const int32 UnrealTileSizeY = LandscapeTileSizeInfo.UnrealSizeY; + const int32 NumSectionPerLandscapeComponent = LandscapeTileSizeInfo.NumSectionsPerComponent; + const int32 NumQuadsPerLandscapeSection = LandscapeTileSizeInfo.NumQuadsPerSection; // ---------------------------------------------------- // Export of layer textures @@ -403,13 +427,13 @@ FHoudiniLandscapeTranslator::CreateLandscape( // ---------------------------------------------------- // Calculate Tile location and landscape offset // ---------------------------------------------------- - FTransform LandscapeTransform, NewTileTransform; + FTransform LandscapeTransform; FIntPoint TileLoc; // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate // for any landscape shifts due to section base aligment offsets. - CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeTransform, NewTileTransform, TileLoc); - + CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeReferenceLocation, LandscapeTransform, TileLoc); + // ---------------------------------------------------- // Find or create *shared* landscape // ---------------------------------------------------- @@ -428,23 +452,42 @@ FHoudiniLandscapeTranslator::CreateLandscape( if (bIsValidSharedLandscape) { - // We have a possible valid shared landscape. Check whether it is compatible with the Houdini volume. + // We have a target landscape. Check whether it is compatible with the Houdini volume. + ULandscapeInfo* LandscapeInfo = SharedLandscapeActor->GetLandscapeInfo(); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape: %s"), *(SharedLandscapeActor->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape with num proxies: %d"), LandscapeInfo->Proxies.Num()); + + if (!LandscapeExtent.bIsCached) + { + LandscapeInfo->FixupProxiesTransform(); + // Cache the landscape extents. Note that GetLandscapeExtent() will only take into account the currently loaded landscape tiles. + PopulateLandscapeExtents(LandscapeExtent, LandscapeInfo); + } + bool bIsCompatible = IsLandscapeInfoCompatible( - SharedLandscapeActor->GetLandscapeInfo(), + LandscapeInfo, NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Checking landscape compatibility ...")); bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); if (!bIsCompatible) { - // Current landscape actor is not compatible. Destroy it. - SharedLandscapeActor->Destroy(); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Shared landscape is incompatible. Cannot reuse.")); + // We can't resize the landscape in-place. We have to create a new one. + DestroyLandscape(SharedLandscapeActor); SharedLandscapeActor = nullptr; bIsValidSharedLandscape = false; } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Existing shared landscape is compatible.")); + } } if (!bIsValidSharedLandscape) { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Create new shared landscape...")); // Create and configure the main landscape actor. // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos SharedLandscapeActor = InWorld->SpawnActor(); @@ -452,9 +495,9 @@ FHoudiniLandscapeTranslator::CreateLandscape( { CreatedUntrackedOutputs.Add( SharedLandscapeActor ); - // NOTE that share landscape is always located at the origin, but not the tile actors. The + // NOTE that shared landscape is always located at the origin, but not the tile actors. The // tiles are properly transformed. - SharedLandscapeActor->SetActorTransform(LandscapeTransform); + // If we working with landscape tiles, this actor will become the "Main landscape" actor but // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. SharedLandscapeActor->bCanHaveLayersContent = false; @@ -469,9 +512,16 @@ FHoudiniLandscapeTranslator::CreateLandscape( } SharedLandscapeActor->CreateLandscapeInfo(); bCreatedSharedLandscape = true; - + + // NOTE: It is important to set Landscape materials BEFORE blitting layer data. For example, setting + // data in the visibility layer (on tiles) will have no effect until Landscape materials have been applied / processed. + SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + // Ensure the landscape actor name and label matches `LandscapeActorName`. FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, SharedLandscapeActorName); + SharedLandscapeActor->MarkPackageDirty(); } else @@ -480,16 +530,13 @@ FHoudiniLandscapeTranslator::CreateLandscape( return false; } } - else - { - // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Reusing existing shared landscape...")); - } + // else -- Reusing shared landscape } if (SharedLandscapeActor) { // Ensure the existing landscape actor transform is correct. - SharedLandscapeActor->SetActorTransform(LandscapeTransform); + SharedLandscapeActor->SetActorRelativeTransform(LandscapeTransform); bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; @@ -514,7 +561,7 @@ FHoudiniLandscapeTranslator::CreateLandscape( { DoPreEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - //SharedLandscapeActor->ChangedPhysMaterial(); + SharedLandscapeActor->ChangedPhysMaterial(); } } @@ -579,6 +626,10 @@ FHoudiniLandscapeTranslator::CreateLandscape( if (!CurrentInputLandscape) continue; + if (SharedLandscapeActor && CurrentInputLandscape->GetLandscapeActor() != SharedLandscapeActor) + // This tile actor no longer associated with the current shared landscape + continue; + ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); if (!CurrentInfo) continue; @@ -587,7 +638,21 @@ FHoudiniLandscapeTranslator::CreateLandscape( int32 InputMinY = 0; int32 InputMaxX = 0; int32 InputMaxY = 0; - CurrentInfo->GetLandscapeExtent(InputMinX, InputMinY, InputMaxX, InputMaxY); + if (!LandscapeExtent.bIsCached) + { + PopulateLandscapeExtents(LandscapeExtent, CurrentInfo); + } + + if (!LandscapeExtent.bIsCached) + { + HOUDINI_LOG_WARNING(TEXT("Warning: Could not determine landscape extents. Cannot re-use input landscape actor.")); + continue; + } + + InputMinX = LandscapeExtent.MinY; + InputMinY = LandscapeExtent.MinY; + InputMaxX = LandscapeExtent.MaxX; + InputMaxY = LandscapeExtent.MaxY; // If the full size matches, we'll update that input landscape bool SizeMatch = false; @@ -608,6 +673,8 @@ FHoudiniLandscapeTranslator::CreateLandscape( if (!FoundLandscapeProxy) { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Could not find input landscape to update. Searching output objects...")); + // Try to see if we can reuse one of our previous output landscape. // Keep track of the previous cook's landscapes TMap& OldOutputObjects = InOutput->GetOutputObjects(); @@ -638,6 +705,14 @@ FHoudiniLandscapeTranslator::CreateLandscape( continue; } + if (SharedLandscapeActor && FoundLandscapeProxy->GetLandscapeActor() != SharedLandscapeActor) + { + // This landscape proxy is no longer part of the shared landscape. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Output landscape proxy is no longer part of the landscape. Skipping")); + FoundLandscapeProxy = nullptr; + continue; + } + // If we found a possible candidate, make sure that its size matches ours as we can only update a landscape tile of the same size if (!IsLandscapeTileCompatible( FoundLandscapeProxy, @@ -663,10 +738,14 @@ FHoudiniLandscapeTranslator::CreateLandscape( // We found a valid Candidate! if (FoundLandscapeProxy) + { break; + } } } + + if (IsValid(FoundLandscapeProxy)) { TileActor = FoundLandscapeProxy; @@ -710,9 +789,14 @@ FHoudiniLandscapeTranslator::CreateLandscape( LandscapeInfo->UnregisterActor(TileActor); } } + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Incompatible tile actor. Destroying: %s"), *(TileActor->GetPathName())); TileActor->Destroy(); TileActor = nullptr; } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape tile is compatible: %s"), *(TileActor->GetPathName())); + } } // ---------------------------------------------------- @@ -727,12 +811,19 @@ FHoudiniLandscapeTranslator::CreateLandscape( ALandscape* CachedLandscapeActor = nullptr; ULandscapeInfo *LandscapeInfo; +#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Loc: %d, %d"), TileLoc.X, TileLoc.Y); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Size: %d, %d"), UnrealTileSizeX, UnrealTileSizeY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Quads/Section: %d"), NumQuadsPerLandscapeSection); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Sections/Component: %d"), NumSectionPerLandscapeComponent); +#endif if (!TileActor) { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Creating new tile actor: %s"), *(LandscapeTileActorName)); // Create a new Landscape tile in the TileWorld TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( - IntHeightData, LayerInfos, TileTransform, + IntHeightData, LayerInfos, TileTransform, TileLoc, UnrealTileSizeX, UnrealTileSizeY, NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, @@ -746,17 +837,6 @@ FHoudiniLandscapeTranslator::CreateLandscape( if (!TileActor || !TileActor->IsValidLowLevel()) return false; - // Update the visibility mask / layer if we have any - for (auto CurrLayerInfo : LayerInfos) - { - if (CurrLayerInfo.LayerInfo && CurrLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - TileActor->VisibilityLayer = CurrLayerInfo.LayerInfo; - TileActor->VisibilityLayer->bNoWeightBlend = true; - TileActor->VisibilityLayer->AddToRoot(); - } - } - LandscapeInfo = TileActor->GetLandscapeInfo(); bCreatedTileActor = true; @@ -766,7 +846,7 @@ FHoudiniLandscapeTranslator::CreateLandscape( bHeightLayerDataChanged = true; bCustomLayerDataChanged = true; } - else + else { LandscapeInfo = TileActor->GetLandscapeInfo(); @@ -774,21 +854,18 @@ FHoudiniLandscapeTranslator::CreateLandscape( // If we change the number of tiles, or switch from outputting single tile to multiple, // then its fairly likely that the unreal transform has changed even if the // Houdini Transform remained the same - if (!TileActor->GetTransform().Equals(TileTransform)) - { - // HOUDINI_LOG_DISPLAY(TEXT("[CreateLandscape] Updating tile transform: %s"), *(TileTransform.ToString())); - TileActor->SetActorTransform(TileTransform); - TileActor->SetAbsoluteSectionBase(TileLoc); -#if WITH_EDITOR - GEngine->BroadcastOnActorMoved(TileActor); -#endif - LandscapeInfo->RecreateLandscapeInfo(InWorld,true); - } - + bool bUpdateTransform = !TileActor->GetActorTransform().Equals(TileTransform); + // Update existing landscape / tile if (SharedLandscapeActor && TileActorType == LandscapeActorType::LandscapeStreamingProxy) { TileActor->FixupSharedData(SharedLandscapeActor); + if (bUpdateTransform) + { + TileActor->SetAbsoluteSectionBase(TileLoc); + LandscapeInfo->FixupProxiesTransform(); + LandscapeInfo->RecreateLandscapeInfo(InWorld,true); + } // This is a tile with a shared landscape. // Check whether the LandscapeActor changed. If so, update the proxy's shared properties. @@ -817,9 +894,14 @@ FHoudiniLandscapeTranslator::CreateLandscape( else { // This is a standalone tile / landscape actor. + if (bUpdateTransform) + { + TileActor->SetActorRelativeTransform(TileTransform); + TileActor->SetAbsoluteSectionBase(TileLoc); + } CachedLandscapeActor = Cast(TileActor); } - + ULandscapeInfo* PreviousInfo = TileActor->GetLandscapeInfo(); if (!PreviousInfo) return false; @@ -851,14 +933,16 @@ FHoudiniLandscapeTranslator::CreateLandscape( // Update the layers on the landscape. for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) { - FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); - AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) { - TileActor->VisibilityLayer = NextUpdatedLayerInfo.LayerInfo; - TileActor->VisibilityLayer->bNoWeightBlend = true; - TileActor->VisibilityLayer->AddToRoot(); + // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. + FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); } bCustomLayerDataChanged = true; @@ -953,6 +1037,43 @@ FHoudiniLandscapeTranslator::CreateLandscape( if (LandscapeInfo) { LandscapeInfo->RecreateLandscapeInfo(InWorld, true); + LandscapeInfo->RecreateCollisionComponents(); + } + + { + // Update UProperties + + // Apply detail attributes to both the Shared Landscape and the Landscape Tile actor + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + GeoId, PartId, + true, + INDEX_NONE, INDEX_NONE, INDEX_NONE, + PropertyAttributes)) + { + if (IsValid(TileActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); + } + if (IsValid(SharedLandscapeActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(SharedLandscapeActor, PropertyAttributes); + } + } + + // Apply point attributes only to the Shared Landscape and the Landscape Tile actor + PropertyAttributes.Empty(); + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + GeoId, PartId, + false, + 0, INDEX_NONE, 0, + PropertyAttributes)) + { + if (IsValid(TileActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); + } + } } // Add objects to the HAC output. @@ -968,6 +1089,19 @@ FHoudiniLandscapeTranslator::CreateLandscape( TileActor, InPackageParams.PackageMode); +#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + if (LandscapeInfo) + { + int32 MinX, MinY, MaxX, MaxY; + LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape extent: %d, %d -> %d, %d"), MinX, MinY, MaxX, MaxY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Cached extent: %d, %d -> %d, %d"), LandscapeExtent.MinX, LandscapeExtent.MinY, LandscapeExtent.MaxX, LandscapeExtent.MaxY); + } + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Ending with num of output objects: %d"), InOutput->GetOutputObjects().Num()); +#endif + return true; } @@ -987,6 +1121,13 @@ FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( if (LandscapeInfo->SubsectionSizeQuads != InNumQuadsPerSection) return false; + + int32 MinX, MinY, MaxX, MaxY; + if (LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + { + const int32 NumComponentsX = (MaxX - MinX) / (InNumQuadsPerSection*InNumSectionsPerComponent); + const int32 NumComponentsY = (MaxY - MinY) / (InNumQuadsPerSection*InNumSectionsPerComponent); + } return true; } @@ -1039,6 +1180,23 @@ bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, return false; } +bool FHoudiniLandscapeTranslator::PopulateLandscapeExtents(FHoudiniLandscapeExtent& Extent, + const ULandscapeInfo* LandscapeInfo) +{ + if (LandscapeInfo->GetLandscapeExtent(Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY)) + { + Extent.ExtentsX = Extent.MaxX - Extent.MinX; + Extent.ExtentsY = Extent.MaxY - Extent.MinY; + Extent.bIsCached = true; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Cached extent: %d, %d -> %d, %d"), Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY); + + return true; + } + return false; +} + ALandscapeProxy* FHoudiniLandscapeTranslator::FindExistingLandscapeActor( UWorld* InWorld, @@ -1324,12 +1482,14 @@ FHoudiniLandscapeTranslator::AttachActorToHAC(UHoudiniOutput* InOutput, AActor* UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); if (IsValid(HAC)) { - InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); + return true; } return false; } + FString FHoudiniLandscapeTranslator::GetActorNameSuffix(const EPackageMode& InPackageMode) { @@ -1425,10 +1585,10 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( DigitZRange = dUINT16_MAX - 1.0; DigitCenterOffset = 0; - // Default unreal landscape scaling is -256m:256m at Scale = 100 - // We need to apply the scale back to - FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; - FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Z * 2.0f; + // Default unreal landscape scaling is -256m:256m at Scale = 100, + // We need to apply the scale back, and swap Y/Z axis + FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f; + FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f; MeterZRange = (double)(FloatMax - FloatMin); ZSpacing = ((double)DigitZRange) / MeterZRange; @@ -1482,14 +1642,18 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( FVector LandscapeScale; // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing + // Swap Y/Z axis from H to UE LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; - LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Y * 2.0f; + LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Z * 2.0f; // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini // Unreal has a default Z range is 512m for a scale of a 100% LandscapeScale.Z = (float)((double)(dUINT16_MAX / DigitZRange) * MeterZRange / 512.0); if (bUseDefaultUE4Scaling) - LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Z * 2.0f; + { + // Swap Y/Z axis from H to UE + LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Y * 2.0f; + } LandscapeScale *= 100.f; // If the data was resized and not expanded, we need to modify the landscape's scale @@ -1997,17 +2161,11 @@ FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutpu } } -bool -FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) +bool FHoudiniLandscapeTranslator::GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo) { - OutFloatArr.Empty(); - OutFloatMin = 0.f; - OutFloatMax = 0.f; - if (HGPO->Type != EHoudiniPartType::Volume) return false; - HAPI_VolumeInfo VolumeInfo; FHoudiniApi::VolumeInfo_Init(&VolumeInfo); HAPI_Result Result = FHoudiniApi::GetVolumeInfo( @@ -2028,6 +2186,20 @@ FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPar if ((VolumeInfo.xLength < 2) || (VolumeInfo.yLength < 2)) return false; + + return true; +} + +bool +FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) +{ + OutFloatArr.Empty(); + OutFloatMin = 0.f; + OutFloatMax = 0.f; + + HAPI_VolumeInfo VolumeInfo; + if (!GetHoudiniHeightfieldVolumeInfo(HGPO, VolumeInfo)) + return false; const int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; @@ -2251,6 +2423,8 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; LayerInfo->LayerUsageDebugColor.A = PI; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers] Processing layer: %s"), *(LayerName)); + // Visibility are by default non weight blended if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) LayerInfo->bNoWeightBlend = true; @@ -2605,16 +2779,17 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( const TArray< uint16 >& IntHeightData, const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, const FTransform& TileTransform, + const FIntPoint& TileLocation, const int32& XSize, const int32& YSize, const int32& NumSectionPerLandscapeComponent, const int32& NumQuadsPerLandscapeSection, UMaterialInterface* LandscapeMaterial, UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhsyicalMaterial, + UPhysicalMaterial* LandscapePhysicalMaterial, const FString& LandscapeTileActorName, LandscapeActorType ActorType, - ALandscape* SharedLandscapeActor, + ALandscape* SharedLandscapeActor, UWorld* InWorld, ULevel* InLevel, FHoudiniPackageParams InPackageParams) @@ -2688,14 +2863,16 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( if (!LandscapeTile) return nullptr; - // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. - - // Autosaving the layers prevents them for being deleted with the Asset - // Save the packages created for the LayerInfos - //if ( CreatedLayerInfoPackage.Num() > 0 ) - // FEditorFileUtils::PromptForCheckoutAndSave( CreatedLayerInfoPackage, true, false ); + // Only import non-visibility layers. Visibility will be handled explicitly. + TArray CustomImportLayerInfos; + for (const FLandscapeImportLayerInfo& LayerInfo : ImportLayerInfos) + { + if (LayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + continue; + CustomImportLayerInfos.Add(LayerInfo); + } - // Import the landscape data + // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue LandscapeTile->bCastStaticShadow = false; @@ -2710,21 +2887,14 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( TMap> HeightmapDataPerLayers; TMap> MaterialLayerDataPerLayer; HeightmapDataPerLayers.Add(FGuid(), IntHeightData); - MaterialLayerDataPerLayer.Add(FGuid(), ImportLayerInfos); - - FTransform NewTileTransform, LandscapeOffset; - FIntPoint TileLoc; - - // NOTE: The following Import call will reregister all components, causing the actor to lose its transform. - // So we'll be importing the tile data as if the actor was located at the origin and fix up transforms afterward. - CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeOffset, NewTileTransform, TileLoc); + TArray& MaterialImportLayerInfos = MaterialLayerDataPerLayer.Add(FGuid(), CustomImportLayerInfos); // Before we import new tile data, remove previous tile data that will be overlapped by the new tile. TSet OverlappingComponents; - const int32 DestMinX = TileLoc.X; - const int32 DestMinY = TileLoc.Y; - const int32 DestMaxX = TileLoc.X + XSize - 1; - const int32 DestMaxY = TileLoc.Y + YSize - 1; + const int32 DestMinX = TileLocation.X; + const int32 DestMinY = TileLocation.Y; + const int32 DestMaxX = TileLocation.X + XSize - 1; + const int32 DestMaxY = TileLocation.Y + YSize - 1; ULandscapeInfo* LandscapeInfo = LandscapeTile->GetLandscapeInfo(); @@ -2760,24 +2930,27 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( bool bIsStandalone = LandscapeTile->GetLandscapeActor() == LandscapeTile; - // We set the actor transform and absolute section base before importing heighfield data. This allows us to + // We set the actor transform and absolute section base before importing heightfield data. This allows us to // use the correct (quad-space) blitting region without causing overlaps during import. // NOTE: The following SetAbsoluteSectionBase() calls are very important. This tells the Landscape system - // where on the landscape, in quad space, a specific tile is located. This influences is used by various + // where on the landscape, in quad space, a specific tile is located. This is used by various // mechanisms and tools that tie into the Landscape system such as World Composition and Landscape editor tools. // If the Section Offset for a landscape tile is wrong, it will appear in the wrong place in the World Composition // viewer. Worse than that, none of Landscape Editor tools will work properly because it won't be able to - // locate the correct Landscape component when calculating the "Landscape Component Key" for the given word position. - // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blit functions use the + // locate the correct Landscape component when calculating the "Landscape Component Key" for the given world position. + // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blitting functions use the // same underlying Landscape Component Key calculation to find the correct landscape component to draw to. So if the // Section Offsets are wrong ... all manner of chaos will follow. // It is also super important to call ULandscapeInfo::RecreateLandscapeInfo() after change a LandscapeProxy's - // section offset in order to update the landscape's internal caches otherwise component key calculations - // won't work correctly. - - LandscapeTile->SetActorTransform(TileTransform); - LandscapeTile->SetAbsoluteSectionBase(TileLoc); + // section offset in order to update the landscape's internal caches (more specifically the component keys, which + // are based on the section offsets) otherwise component key calculations won't work correctly. + + LandscapeTile->SetActorRelativeTransform(TileTransform); + LandscapeTile->SetAbsoluteSectionBase(TileLocation); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Importing tile for actor: %s "), *(LandscapeTile->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Dest region: %d, %d -> %d, %d"), DestMinX, DestMinY, DestMaxX, DestMaxY); LandscapeTile->Import( LandscapeTile->GetLandscapeGuid(), @@ -2786,18 +2959,20 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( HeightmapDataPerLayers, NULL, MaterialLayerDataPerLayer, ImportLayerType); - LandscapeTile->RecreateComponentsState(); if (!LandscapeInfo) { LandscapeInfo = LandscapeTile->GetLandscapeInfo(); } + check(LandscapeInfo); + // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so - // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo, - // and only then are we able to "blit" the new alpha data into the correct place on the landscape. + // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo component keys. + // Only then are we able to "blit" the new alpha data into the correct place on the landscape. ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); + LandscapeTile->RecreateComponentsState(); // TODO: Verify whether we still need to manually set the LandscapeLightingLOD setting or whether // calling PostEditChange() will properly fix the state. @@ -2807,6 +2982,20 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 LandscapeTile->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + // ---------------------------------------------------- + // Update visibility layer + // ---------------------------------------------------- + + // Update the visibility mask / layer if we have any (TileImport does not update the visibility layer). + for (auto CurLayerInfo : ImportLayerInfos) + { + if (CurLayerInfo.LayerInfo && CurLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(DestMinX, DestMinY, DestMaxX, DestMaxY, CurLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + // ---------------------------------------------------- // Rename the actor // ---------------------------------------------------- @@ -2821,53 +3010,93 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( HOUDINI_LOG_WARNING(TEXT("Editor suppressed MarkPackageDirty() for landscape tile: %s"), *(LandscapeTile->GetPathName())); } +#if WITH_EDITOR + GEngine->BroadcastOnActorMoved(LandscapeTile); +#endif + return LandscapeTile; } +void +FHoudiniLandscapeTranslator::DestroyLandscape(ALandscape* Landscape) +{ + if (!IsValid(Landscape)) + return; + + ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); + if (!IsValid(Info)) + return; + + TArray Proxies = Info->Proxies; + for(ALandscapeStreamingProxy* Proxy : Proxies) + { + Info->UnregisterActor(Proxy); + Proxy->Destroy(); + } + Landscape->Destroy(); +} + + void FHoudiniLandscapeTranslator::CalculateTileLocation( int32 NumSectionsPerComponent, int32 NumQuadsPerSection, - const FTransform& TileTransform, - FTransform& OutLandscapeOffset, - FTransform& OutTileTransform, + const FTransform& TileTransformWS, + FHoudiniLandscapeReferenceLocation& RefLoc, + FTransform& OutLandscapeTransform, FIntPoint& OutTileLocation) -{ +{ // IMPORTANT: Tile Location (Section Base) needs to be in multiples of the landscape component's quad size const int32 ComponentSize = NumSectionsPerComponent * NumQuadsPerSection; - OutLandscapeOffset = FTransform(FRotator::ZeroRotator, FVector::ZeroVector, TileTransform.GetScale3D()); - OutTileTransform = TileTransform; + OutLandscapeTransform = FTransform::Identity; + const FTransform TileSR = FTransform(TileTransformWS.GetRotation(), FVector::ZeroVector, TileTransformWS.GetScale3D()); - // Sometimes the calculated tile coordinate falls on a half-unit so we would need to remove that offset first - // before calculating integer (quad space) tile location. - // For example, 123.5, should become 123. -456.5 should become -456. - const FVector TileScale = TileTransform.GetScale3D(); - const float TileCoordX = TileTransform.GetLocation().X / TileScale.X; - const float TileCoordY = TileTransform.GetLocation().Y / TileScale.Y; + const FVector BaseLoc = TileSR.InverseTransformPosition(TileTransformWS.GetLocation()); - float NearestMultipleX = FMath::RoundHalfFromZero(TileCoordX / ComponentSize) * ComponentSize; - float NearestMultipleY = FMath::RoundHalfFromZero(TileCoordY / ComponentSize) * ComponentSize; + const FVector TileScale = TileTransformWS.GetScale3D(); + const float TileLocX = BaseLoc.X; // / TileScale.X; + const float TileLocY = BaseLoc.Y; // / TileScale.Y; - // If the multiples are too close to the middle, offset by 0.5 to avoid some tiles snapping up and others snapping down. - if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleX), 0.5f, 0.1f)) - { - NearestMultipleX += 0.5f; - } - if (FMath::IsNearlyEqual(FMath::Frac(NearestMultipleY), 0.5f, 0.1f)) + if (!RefLoc.bIsCached) { - NearestMultipleY += 0.5f; - } + // If there is no landscape reference location yet, calculate one now. - const float TileOffsetX = NearestMultipleX - TileCoordX; - const float TileOffsetY = NearestMultipleY - TileCoordY; + // We cache this tile as a reference point for the other landscape tiles so that they can calculate + // section base offsets in a consistent manner, relative to this tile. + const float NearestMultipleX = FMath::RoundHalfFromZero(TileLocX / ComponentSize) * ComponentSize; + const float NearestMultipleY = FMath::RoundHalfFromZero(TileLocY / ComponentSize) * ComponentSize; - OutTileLocation.X = FMath::RoundHalfFromZero(NearestMultipleX); - OutTileLocation.Y = FMath::RoundHalfFromZero(NearestMultipleY); + RefLoc.SectionCoordX = FMath::RoundHalfFromZero(NearestMultipleX); + RefLoc.SectionCoordY = FMath::RoundHalfFromZero(NearestMultipleY); + RefLoc.TileLocationX = TileLocX; + RefLoc.TileLocationY = TileLocY; + } + // Calculate the section coordinate for this tile + const float DeltaLocX = TileLocX - RefLoc.TileLocationX; + const float DeltaLocY = TileLocY - RefLoc.TileLocationY; + + const float DeltaCoordX = FMath::RoundHalfFromZero(DeltaLocX / ComponentSize) * ComponentSize; + const float DeltaCoordY = FMath::RoundHalfFromZero(DeltaLocY / ComponentSize) * ComponentSize; + + OutTileLocation.X = RefLoc.SectionCoordX + DeltaCoordX; + OutTileLocation.Y = RefLoc.SectionCoordY + DeltaCoordY; + // Adjust landscape offset to compensate for tile location / section base shifting. - OutLandscapeOffset.SetLocation( FVector(-TileOffsetX * TileScale.X, -TileOffsetY * TileScale.Y, 0.f) ); + if (!RefLoc.bIsCached) + { + FVector Offset((TileLocX - OutTileLocation.X), (TileLocY - OutTileLocation.Y), BaseLoc.Z); + Offset = TileSR.TransformPosition(Offset); + + RefLoc.MainTransform = TileTransformWS; + RefLoc.MainTransform.SetTranslation(Offset); + // Reference locations are now fully cached. + RefLoc.bIsCached = true; + } + + OutLandscapeTransform = RefLoc.MainTransform; } @@ -3178,29 +3407,6 @@ FHoudiniLandscapeTranslator::EnableWorldComposition() return true; } - -bool -FHoudiniLandscapeTranslator::GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InPrimIndex, TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive property attributes - // Volumes apparently dont have prim attributes because they're converted to pointmeshes somehow... - //FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - // InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InPrimIndex); - - // .. then the point property attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InPrimIndex); - - return FoundCount > 0; -} - - bool FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( UObject* InObject, const TArray& InAllPropertyAttributes) diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h index 668e1c8a8..73a5275ef 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h @@ -34,6 +34,7 @@ #include "EngineUtils.h" #include "HoudiniEngineOutputStats.h" #include "HoudiniPackageParams.h" +#include "HoudiniTranslatorTypes.h" class UHoudiniAssetComponent; class ULandscapeLayerInfoObject; @@ -59,6 +60,9 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator UWorld* World, const TMap& LayerMinimums, const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, FHoudiniPackageParams InPackageParams, TArray& OutCreatedPackages); @@ -91,6 +95,11 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator static bool IsLandscapeTypeCompatible( const AActor* Actor, LandscapeActorType ActorType); + + static bool PopulateLandscapeExtents( + FHoudiniLandscapeExtent& Extent, + const ULandscapeInfo* LandscapeInfo + ); /** @@ -193,6 +202,8 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers); + static bool GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo); + static bool GetHoudiniHeightfieldFloatData( const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, @@ -305,13 +316,14 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator const TArray< uint16 >& IntHeightData, const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, const FTransform& TileTransform, + const FIntPoint& TileLocation, const int32& XSize, const int32& YSize, const int32& NumSectionPerLandscapeComponent, const int32& NumQuadsPerLandscapeSection, UMaterialInterface* LandscapeMaterial, UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhsyicalMaterial, + UPhysicalMaterial* LandscapePhysicalMaterial, const FString& LandscapeTileActorName, LandscapeActorType ActorType, ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies @@ -319,8 +331,10 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator ULevel* InLevel, // Level, contained in World, in which to spawn. FHoudiniPackageParams InPackageParams); -protected: + // Destroy the given landscape and all its proxies + static void DestroyLandscape(ALandscape* Landscape); +protected: /** * Calculate the location of a landscape tile. * This location is typically used in conjunction with ALandscapeProxy::SetAbsoluteSectionBase(). @@ -329,8 +343,8 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator int32 NumSectionsPerComponent, int32 NumQuadsPerSection, const FTransform& InTileTransform, - FTransform& OutLandscapeOffset, - FTransform& OutTileTransform, + FHoudiniLandscapeReferenceLocation& RefLoc, + FTransform& OutLandscapeTransform, FIntPoint& OutTileLocation); public: diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp index 97523fae4..b1b544510 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp @@ -826,7 +826,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( // If texture sampling expression does exist, attempt to look up corresponding texture. UTexture2D * TextureDiffuse = nullptr; - if (ExpressionTextureSample && !ExpressionTextureSample->IsPendingKill()) + if (IsValid(ExpressionTextureSample)) TextureDiffuse = Cast< UTexture2D >(ExpressionTextureSample->Texture); // Locate uniform color expression. @@ -1014,7 +1014,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( // Create diffuse sampling expression, if needed. if (!ExpressionTextureSample) { - ExpressionTextureSample = NewObject< UMaterialExpressionTextureSampleParameter2D >( + ExpressionTextureSample = NewObject( Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); } @@ -1065,7 +1065,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( { if (!MaterialExpressionMultiplySecondary) { - MaterialExpressionMultiplySecondary = NewObject< UMaterialExpressionMultiply >( + MaterialExpressionMultiplySecondary = NewObject( Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); // Add expression. @@ -1113,10 +1113,14 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; - ExpressionTextureSample->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; + if (ExpressionTextureSample) + { + ExpressionTextureSample->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; + } + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp index 5a29919bb..bdd6a85d8 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp @@ -82,6 +82,7 @@ FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( const FHoudiniPackageParams& InPackageParams, const EHoudiniStaticMeshMethod& InStaticMeshMethod, const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, UObject* InOuterComponent, bool bInTreatExistingMaterialsAsUpToDate, bool bInDestroyProxies) @@ -114,6 +115,18 @@ FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( if (CurHGPO.Type != EHoudiniPartType::Mesh) continue; + // See if we have some uproperty attributes to update on + // the outer component (in most case, the HAC) + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + CurHGPO.GeoId, CurHGPO.PartId, + true, 0, 0, 0, + PropertyAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + InOuterComponent, PropertyAttributes); + } + CreateStaticMeshFromHoudiniGeoPartObject( CurHGPO, InPackageParams, @@ -124,6 +137,7 @@ FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( InForceRebuild, InStaticMeshMethod, InSMGenerationProperties, + InMeshBuildSettings, bInTreatExistingMaterialsAsUpToDate); } @@ -269,6 +283,23 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( const FHoudiniOutputObjectIdentifier& OutputIdentifier = NewPair.Key; FHoudiniOutputObject& OutputObject = NewPair.Value; + if (OutputObject.bIsImplicit) + { + // This output is implicit and shouldn't have a representative component/proxy in the scene + // Remove the old component from the map + if (OutputObject.OutputComponent) + { + RemoveAndDestroyComponent(OutputObject.OutputComponent); + OutputObject.OutputComponent = nullptr; + } + + // Remove the old proxy component from the map + RemoveAndDestroyComponent(OutputObject.ProxyComponent); + OutputObject.ProxyComponent = nullptr; + + continue; // Skip any proxy / component creation below + } + // Check if we should create a Proxy/SMC if (OutputObject.bProxyIsCurrent) { @@ -497,12 +528,15 @@ FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, con InMeshComponent->ComponentTags.Empty(); // Update the property attributes on the component TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( InOutputIdentifier.GeoId, InOutputIdentifier.PartId, - InOutputIdentifier.PointIndex, InOutputIdentifier.PrimitiveIndex, + true, + InOutputIdentifier.PrimitiveIndex, + INDEX_NONE, + InOutputIdentifier.PointIndex, PropertyAttributes)) { - UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); } } } @@ -518,6 +552,7 @@ FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( const bool& InForceRebuild, const EHoudiniStaticMeshMethod& InStaticMeshMethod, const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InSMBuildSettings, bool bInTreatExistingMaterialsAsUpToDate) { // If we're not forcing the rebuild @@ -539,6 +574,7 @@ FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( CurrentTranslator.SetPackageParams(InPackageParams, true); CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); CurrentTranslator.SetStaticMeshGenerationProperties(InSMGenerationProperties); + CurrentTranslator.SetStaticMeshBuildSettings(InSMBuildSettings); // TODO: Fetch from settings/HAC CurrentTranslator.DefaultMeshSmoothing = 1; @@ -1441,12 +1477,14 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // New mesh list TMap StaticMeshToBuild; - // Map of Houdini Material IDs to Unreal Material Indices - TMap MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap MapHoudiniMatAttributesToUnrealIndex; + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; - bool MeshMaterialsHaveBeenReset = false; + // bool MeshMaterialsHaveBeenReset = false; // Mesh Socket array TArray AllSockets; @@ -1455,6 +1493,10 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() FHoudiniEngineUtils::AddMeshSocketsToArray_Group( HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + UStaticMesh* MainStaticMesh = nullptr; + bool bAssignedCustomCollisionMesh = false; + ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; + // Iterate through all detected split groups we care about and split geometry. // The split are ordered in the following way: // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders @@ -1504,6 +1546,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Handle UCX / Convex Hull colliders if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; // Get the part position if needed UpdatePartPositionIfNeeded(); @@ -1522,6 +1565,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; // Get the part position if needed UpdatePartPositionIfNeeded(); @@ -1580,6 +1624,14 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); } + if (SplitType == EHoudiniSplitType::Normal && !MainStaticMesh) + { + MainStaticMesh = FoundStaticMesh; + MainStaticMesh->ComplexCollisionMesh = nullptr; + MainStaticMesh->bCustomizedCollision = false; + // NOTE: The main static mesh collision trace flag will be set after all splits have been processed. + } + if (!FoundOutputObject) { FHoudiniOutputObject NewOutputObject; @@ -2104,17 +2156,24 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Get face indices for this split. TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; - // We need to reset the Static Mesh's materials once per SM: - // so, for the first lod, or the main geo... - if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // // We need to reset the Static Mesh's materials once per SM: + // // so, for the first lod, or the main geo... + // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // { + // FoundStaticMesh->StaticMaterials.Empty(); + // MeshMaterialsHaveBeenReset = true; + // } + // + // // .. or for each visible complex collider + // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + // FoundStaticMesh->StaticMaterials.Empty(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) { FoundStaticMesh->StaticMaterials.Empty(); - MeshMaterialsHaveBeenReset = true; } - - // .. or for each visible complex collider - if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - FoundStaticMesh->StaticMaterials.Empty(); + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); // Process material overrides first if (PartFaceMaterialOverrides.Num() > 0) @@ -2127,21 +2186,19 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) continue; - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); + UMaterialInterface * MaterialInterface = nullptr; int32 CurrentFaceMaterialIdx = 0; - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else + const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) { // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; // Start by looking in our assignment map - auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); if (FoundMaterialInterface) MaterialInterface = *FoundMaterialInterface; @@ -2164,9 +2221,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() if (ReplacementMaterialInterface && *ReplacementMaterialInterface) MaterialInterface = *ReplacementMaterialInterface; - // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); } else { @@ -2177,13 +2232,10 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + if (!MaterialInterface) { // If everything fails, we'll use the default material MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); @@ -2200,17 +2252,30 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); } } } - // Update the Face Material on the mesh - RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + if (MaterialInterface) + { + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + + // Update the Face Material on the mesh + RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + } } } else if (PartUniqueMaterialIds.Num() > 0) @@ -2257,35 +2322,50 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) { - // This material has been mapped already, just assign the mat index - RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } } + else + { + MaterialInterface = Cast(DefaultMaterial); - UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } - // Add the material to the Static mesh - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + if (MaterialInterface) + { + // Add the material to the Static mesh + int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); - // Update the face index - RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; + // Update the face index + RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } } } } @@ -2307,7 +2387,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } // Update the Build Settings using the default setting values - SetMeshBuildSettings( + UpdateMeshBuildSettings( SrcModel->BuildSettings, RawMesh.WedgeTangentZ.Num() > 0, (RawMesh.WedgeTangentX.Num() > 0 && RawMesh.WedgeTangentY.Num() > 0), @@ -2323,18 +2403,59 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() else FoundStaticMesh->LightMapResolution = 64; + // TODO + //StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; + //StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; + //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings; + // TODO: // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - // By default the distance field resolution should be set to 2.0 - // TODO should come from the HAC - //SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; - // This is required due to the impeding deprecation of FRawMesh // If we dont update this UE4 will crash upon deleting an asset. SrcModel->StaticMeshOwner = FoundStaticMesh; - // Store the new raw mesh. - SrcModel->SaveRawMesh(RawMesh); + + // Store the new raw mesh if it is valid + if (RawMesh.IsValid()) + { + SrcModel->SaveRawMesh(RawMesh); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("[CreateStaticMesh_RawMesh]: Invalid StaticMesh data for %s LOD %i in cook output! Please check the log."), + *FoundStaticMesh->GetName(), LODIndex); + // Create an "empty" valid raw mesh (single zero-area triangle) + // TODO: is there a cleaner way to do this? Perhaps committing an empty mesh description? Empty RawMesh is + // a no-op on SrcModel->SaveRawMesh (leaves previous data in place). + // TODO: perhaps we can use an alternative "error" mesh? + RawMesh.Empty(); + RawMesh.VertexPositions.Add(FVector::ZeroVector); + RawMesh.WedgeIndices.SetNumZeroed(3); + RawMesh.WedgeTexCoords[0].Init(FVector2D::ZeroVector, RawMesh.WedgeIndices.Num()); + SrcModel->SaveRawMesh(RawMesh); + } + + // NOTE: This Mesh Description patch causes crashes in certain situations and need to be revised. Until + // this patch has been properly fixed, we're just going to revert back to old (non-crashing) behaviour. + // if (IsValid(FoundStaticMesh)) + // { + // // Patch the MeshDescription data structure that is being output from SaveRawMesh. SaveRawMesh leaves invalid entries + // // in the PolyGroups array that causes issues later when the static mesh is built and LOD material assignments + // // are being done (materials aren't correctly assigned to LODs if LODs use different materials). + // FPolygonGroupArray& PolyGroups = SrcModel->MeshDescription->PolygonGroups(); + // TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = SrcModel->MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); + // for(int32 MaterialIndex = 0; MaterialIndex < FoundStaticMesh->StaticMaterials.Num(); ++MaterialIndex) + // { + // FStaticMaterial& Material = FoundStaticMesh->StaticMaterials[MaterialIndex]; + // FPolygonGroupID PolygonGroupID(MaterialIndex); + // if (!PolyGroups.IsValid(PolygonGroupID)) + // { + // PolyGroups.Insert(PolygonGroupID); + // } + // PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = Material.MaterialSlotName; + // } + // } // LOD Screensize // default values has already been set, see if we have any attribute override for this @@ -2355,13 +2476,15 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Update property attributes on the SM TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( HGPO.GeoId, HGPO.PartId, - AllSplitFirstValidVertexIndex[SplitGroupName], + true, AllSplitFirstValidPrimIndex[SplitGroupName], + INDEX_NONE, + AllSplitFirstValidVertexIndex[SplitGroupName], PropertyAttributes)) { - UpdateGenericPropertiesAttributes( + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( FoundStaticMesh, PropertyAttributes); } @@ -2405,6 +2528,16 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } } + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + TArray BakeOutlinerFolders; if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) { @@ -2479,14 +2612,19 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; } - RefreshCollisionChange(*SM); - SM->bCustomizedCollision = true; - // See if we need to enable collisions on the whole mesh if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) { - // Complex collider, enable collisions for this static mesh. + // Complex collider, enable collisions for this (collider) static mesh. BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + + // Apply the collider to the Main static mesh, if relevant. + ApplyComplexColliderHelper( + MainStaticMesh, + SM, + SplitType, + bAssignedCustomCollisionMesh, + OutputObjects.Find(Current.Key)); } else { @@ -2512,6 +2650,20 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } } + if (MainStaticMesh) + { + UBodySetup* MainBodySetup = MainStaticMesh->BodySetup; + if (!IsValid(MainBodySetup)) + { + MainStaticMesh->CreateBodySetup(); + MainBodySetup = MainStaticMesh->BodySetup; + } + + check(MainBodySetup); + // Set the main static mesh to whatever the final CTF should be. + MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; + } + // BUILD the Static Mesh // bSilent doesnt add the Build Errors... double build_start = FPlatformTime::Seconds(); @@ -2520,6 +2672,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() double build_end = FPlatformTime::Seconds(); HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); + RefreshCollisionChange(*SM); + SM->GetOnMeshChanged().Broadcast(); /* @@ -2634,10 +2788,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // New mesh list TMap StaticMeshToBuild; - // Map of Houdini Material IDs to Unreal Material Indices - TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; bool MeshMaterialsHaveBeenReset = false; @@ -2651,6 +2807,10 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() double tick = FPlatformTime::Seconds(); HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); + UStaticMesh* MainStaticMesh = nullptr; + bool bAssignedCustomCollisionMesh = false; + ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; + // Iterate through all detected split groups we care about and split geometry. // The split are ordered in the following way: // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders @@ -2708,6 +2868,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // Create the convex hull colliders and add them to the Aggregate if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; // Failed to generate a convex collider HOUDINI_LOG_WARNING( TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), @@ -2720,6 +2881,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; // Get the part position if needed UpdatePartPositionIfNeeded(); @@ -2778,6 +2940,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); } + if (SplitType == EHoudiniSplitType::Normal) + { + MainStaticMesh = FoundStaticMesh; + MainStaticMesh->ComplexCollisionMesh = nullptr; + MainStaticMesh->bCustomizedCollision = false; + } + if (!FoundOutputObject) { FHoudiniOutputObject NewOutputObject; @@ -3013,18 +3182,25 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // MATERIALS //--------------------------------------------------------------------------------------------------------------------- - // TODO: Check if still needed for MeshDescription - // We need to reset the Static Mesh's materials once per SM: - // so, for the first lod, or the main geo... - if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // // TODO: Check if still needed for MeshDescription + // // We need to reset the Static Mesh's materials once per SM: + // // so, for the first lod, or the main geo... + // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // { + // FoundStaticMesh->StaticMaterials.Empty(); + // MeshMaterialsHaveBeenReset = true; + // } + // + // // .. or for each visible complex collider + // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + // FoundStaticMesh->StaticMaterials.Empty(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) { FoundStaticMesh->StaticMaterials.Empty(); - MeshMaterialsHaveBeenReset = true; } - - // .. or for each visible complex collider - if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - FoundStaticMesh->StaticMaterials.Empty(); + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); // Get this split's faces TArray& SplitGroupFaceIndices = AllSplitFaceIndices[SplitGroupName]; @@ -3047,6 +3223,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; + FoundStaticMesh->StaticMaterials.Empty(); FoundStaticMesh->StaticMaterials.Add(MaterialInterface); // TODO: ? Add default mat to the assignement map? @@ -3072,6 +3249,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; + FoundStaticMesh->StaticMaterials.Empty(); FoundStaticMesh->StaticMaterials.Add(MaterialInterface); // TODO: ? Add the mat to the assignement map? @@ -3093,36 +3271,51 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) { - // This material has been mapped already, just use its material index - SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } } + else + { + MaterialInterface = Cast(MaterialDefault); - UMaterialInterface * MaterialInterface = Cast(MaterialDefault); + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } - // Add the material to the Static mesh - //int32 UnrealMatIndex = SplitMaterials.Add(Material); - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + if (MaterialInterface) + { + // Add the material to the Static mesh + //int32 UnrealMatIndex = SplitMaterials.Add(Material); + int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); - // Update the face index - SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; + // Update the face index + SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } } } } @@ -3133,20 +3326,25 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() { int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; + UMaterialInterface * MaterialInterface = nullptr; int32 CurrentFaceMaterialIdx = -1; if (PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) { const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); - if (FoundFaceMaterialIdx) - { - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) { // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; - if (!MaterialName.IsEmpty()) + + // Start by looking in our assignment map + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface && !MaterialName.IsEmpty()) { // Only try to load a material if has a chance to be valid! MaterialInterface = Cast< UMaterialInterface >( @@ -3166,12 +3364,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() MaterialInterface = *ReplacementMaterialInterface; // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); } } - if (CurrentFaceMaterialIdx < 0) + if (!MaterialInterface) { // The attribute Material or its replacement do not exist // See if we can fallback to the Houdini material assigned on the face @@ -3180,16 +3377,14 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) { // If everything else fails, we'll use the default material - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); // We need to add this material to the map FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; @@ -3203,15 +3398,25 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() if (ReplacementMaterialInterface && *ReplacementMaterialInterface) MaterialInterface = *ReplacementMaterialInterface; - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); } } } + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + // Update the Face Material on the mesh SplitFaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; } @@ -3528,7 +3733,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } // Update the Build Settings using the default setting values - SetMeshBuildSettings( + UpdateMeshBuildSettings( SrcModel->BuildSettings, bHasNormal, bHasTangents, @@ -3551,10 +3756,6 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // TODO: // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - // By default the distance field resolution should be set to 2.0 - // TODO should come from the HAC - //SrcModel->BuildSettings.DistanceFieldResolutionScale = 2.0; - // RAW MESH CHECKS // TODO: Check not needed w/ FMeshDesc @@ -3562,6 +3763,14 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // If we dont update this UE4 will crash upon deleting an asset. //SrcModel->StaticMeshOwner = FoundStaticMesh; + // Check if the mesh has at least one triangle, if not, log a message + if (MeshDescription->Triangles().Num() == 0) + { + HOUDINI_LOG_WARNING( + TEXT("[CreateStaticMesh_MeshDescription]: 0 valid triangles in StaticMesh data for %s LOD %i! Please check the log."), + *FoundStaticMesh->GetName(), LODIndex); + } + // Store the new MeshDescription FoundStaticMesh->CommitMeshDescription(LODIndex); //Set the Imported version before calling the build @@ -3586,13 +3795,15 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // UPDATE UPROPERTY ATTRIBS // Update property attributes on the SM TArray PropertyAttributes; - if (GetGenericPropertiesAttributes( + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( HGPO.GeoId, HGPO.PartId, - AllSplitFirstValidVertexIndex[SplitGroupName], + true, AllSplitFirstValidPrimIndex[SplitGroupName], + INDEX_NONE, + AllSplitFirstValidVertexIndex[SplitGroupName], PropertyAttributes)) { - UpdateGenericPropertiesAttributes( + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( FoundStaticMesh, PropertyAttributes); } @@ -3636,6 +3847,16 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } } + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + TArray BakeOutlinerFolders; if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) { @@ -3655,6 +3876,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() { FoundOutputObject->OutputObject = FoundStaticMesh; FoundOutputObject->bProxyIsCurrent = false; + FoundOutputObject->bIsImplicit = false; OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); } @@ -3715,13 +3937,19 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // Moved RefreshCollisionChange to after the SM->Build call // RefreshCollisionChange(*SM); - SM->bCustomizedCollision = true; + // SM->bCustomizedCollision = true; // See if we need to enable collisions on the whole mesh if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) { // Complex collider, enable collisions for this static mesh. BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + ApplyComplexColliderHelper( + MainStaticMesh, + SM, + SplitType, + bAssignedCustomCollisionMesh, + OutputObjects.Find(Current.Key)); } else { @@ -3747,6 +3975,20 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } } + if (MainStaticMesh) + { + UBodySetup* MainBodySetup = MainStaticMesh->BodySetup; + if (!IsValid(MainBodySetup)) + { + MainStaticMesh->CreateBodySetup(); + MainBodySetup = MainStaticMesh->BodySetup; + } + + check(MainBodySetup); + // Set the main static mesh to whatever the final CTF should be. + MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; + } + // BUILD the Static Mesh // bSilent doesnt add the Build Errors... double build_start = FPlatformTime::Seconds(); @@ -3894,12 +4136,14 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() } } - // Map of Houdini Material IDs to Unreal Material Indices - TMap< HAPI_NodeId, int32 > MapHoudiniMatIdToUnrealIndex; - // Map of Houdini Material Attributes to Unreal Material Indices - TMap< FString, int32 > MapHoudiniMatAttributesToUnrealIndex; + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; - bool MeshMaterialsHaveBeenReset = false; + // bool MeshMaterialsHaveBeenReset = false; double tick = FPlatformTime::Seconds(); HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); @@ -4407,6 +4651,16 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() // Get face indices for this split. TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; + // Fetch the FoundMesh's Static Materials array + TArray& FoundStaticMaterials = FoundStaticMesh->GetStaticMaterials(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) + { + FoundStaticMaterials.Empty(); + } + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); + // Process material overrides first if (PartFaceMaterialOverrides.Num() > 0) { @@ -4418,21 +4672,19 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) continue; - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - int32 const * FoundFaceMaterialIdx = MapHoudiniMatAttributesToUnrealIndex.Find(MaterialName); + UMaterialInterface * MaterialInterface = nullptr; int32 CurrentFaceMaterialIdx = 0; - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else + const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) { // Try to locate the corresponding material interface - UMaterialInterface * MaterialInterface = nullptr; // Start by looking in our assignment map - auto FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); if (FoundMaterialInterface) MaterialInterface = *FoundMaterialInterface; @@ -4456,8 +4708,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() MaterialInterface = *ReplacementMaterialInterface; // Add this material to the map - CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - MapHoudiniMatAttributesToUnrealIndex.Add(MaterialName, CurrentFaceMaterialIdx); + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); } else { @@ -4468,13 +4719,10 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - CurrentFaceMaterialIdx = *FoundUnrealMatIndex; - } - else + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + if (!MaterialInterface) { // If everything fails, we'll use the default material MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); @@ -4491,17 +4739,29 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - // Add the material to the mesh - CurrentFaceMaterialIdx = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, CurrentFaceMaterialIdx); + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); } } } - // Update the Face Material on the mesh - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); + if (MaterialInterface) + { + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + // Update the Face Material on the mesh + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); + } } } else if (PartUniqueMaterialIds.Num() > 0) @@ -4526,7 +4786,8 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); } else { @@ -4546,35 +4807,51 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; // See if we have already treated that material - int32 const * FoundUnrealMatIndex = MapHoudiniMatIdToUnrealIndex.Find(MaterialId); - if (FoundUnrealMatIndex) + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) { - // This material has been mapped already, just assign the mat index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); - continue; + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); + continue; + } } + else + { + MaterialInterface = Cast(DefaultMaterial); - UMaterialInterface * MaterialInterface = Cast(DefaultMaterial); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; - // Add the material to the mesh - int32 UnrealMatIndex = FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealIndex.Add(MaterialId, UnrealMatIndex); + if (MaterialInterface) + { + // Add the material to the Static mesh + int32 UnrealMatIndex = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - // Update the face index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); + + // Update the face index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); + } } } } @@ -4583,8 +4860,6 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Default Material")); // No materials were found, we need to use default Houdini material. - int32 SplitFaceCount = SplitFaceIndices.Num(); - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); // See if we have a replacement material and use it on the mesh instead @@ -4592,7 +4867,8 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() if (ReplacementMaterial && *ReplacementMaterial) MaterialInterface = *ReplacementMaterial; - FoundStaticMesh->AddStaticMaterial(FStaticMaterial(MaterialInterface)); + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); } //// Update property attributes on the mesh @@ -4609,6 +4885,16 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() FoundStaticMesh->Optimize(); + // Check if the mesh is valid (check all the counts (vertex, triangles, vertex instances, UVs etc) but skip + // looping over each individual triangle vertex index to check if the value is valid). + const bool bSkipVertexIndicesCheck = true; + if (!FoundStaticMesh->IsValid(bSkipVertexIndicesCheck)) + { + HOUDINI_LOG_WARNING( + TEXT("[CreateHoudiniStaticMesh]: Invalid StaticMesh data for %s in cook output! Please check the log."), + *FoundStaticMesh->GetName()); + } + //// Try to find the outer package so we can dirty it up //if (FoundStaticMesh->GetOuter()) //{ @@ -4646,6 +4932,36 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() return true; } +void +FHoudiniMeshTranslator::ApplyComplexColliderHelper( + UStaticMesh* TargetStaticMesh, + UStaticMesh* ComplexStaticMesh, + const EHoudiniSplitType SplitType, + bool& bAssignedCustomCollisionMesh, + FHoudiniOutputObject* OutputObject) +{ + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider && TargetStaticMesh) + { + if (!bAssignedCustomCollisionMesh) + { + bAssignedCustomCollisionMesh = true; + TargetStaticMesh->ComplexCollisionMesh = ComplexStaticMesh; + TargetStaticMesh->bCustomizedCollision = true; + bAssignedCustomCollisionMesh = true; + // We don't want an actor/component for this object in the scene, so flag it as an implicit output. + if (OutputObject) + { + OutputObject->bIsImplicit = true; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("More than one (invisible) complex collision mesh found. Static Mesh assets only support a single complex collision mesh. Creating additional collision geo as Static Mesh Components.")); + } + } +} + + bool FHoudiniMeshTranslator::CreateNeededMaterials() { @@ -5847,54 +6163,6 @@ FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(const TArray& InP } -bool -FHoudiniMeshTranslator::GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, - TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then the primitive property attributes for the given prim - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); - - // .. then finally, point uprop attributes for the given vert - // TODO: !! get the correct Index here? - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidVertexIndex); - - return FoundCount > 0; -} - -bool -FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (const auto& CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current Property Attribute - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); - } - - return (NumSuccess > 0); -} - - void FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO) { @@ -6299,99 +6567,31 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati } void -FHoudiniMeshTranslator::SetMeshBuildSettings( +FHoudiniMeshTranslator::UpdateMeshBuildSettings( FMeshBuildSettings& OutMeshBuildSettings, const bool& bHasNormals, const bool& bHasTangents, const bool& bHasLightmapUVSet) { + // Use the values provided to the translator + OutMeshBuildSettings = StaticMeshBuildSettings; + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - OutMeshBuildSettings.bRemoveDegenerates = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bRemoveDegenerates : true; - OutMeshBuildSettings.bUseMikkTSpace = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bUseMikkTSpace : true; - OutMeshBuildSettings.bBuildAdjacencyBuffer = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bBuildAdjacencyBuffer : false; - OutMeshBuildSettings.MinLightmapResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MinLightmapResolution : 64; - OutMeshBuildSettings.bUseFullPrecisionUVs = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bUseFullPrecisionUVs : false; - OutMeshBuildSettings.SrcLightmapIndex = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->SrcLightmapIndex : 0; - OutMeshBuildSettings.DstLightmapIndex = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->DstLightmapIndex : 1; - - OutMeshBuildSettings.bComputeWeightedNormals = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bComputeWeightedNormals : false; - OutMeshBuildSettings.bBuildReversedIndexBuffer = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bBuildReversedIndexBuffer : true; - OutMeshBuildSettings.bUseHighPrecisionTangentBasis = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bUseHighPrecisionTangentBasis : false; - OutMeshBuildSettings.bGenerateDistanceFieldAsIfTwoSided = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bGenerateDistanceFieldAsIfTwoSided : false; - OutMeshBuildSettings.bSupportFaceRemap = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bSupportFaceRemap : false; - OutMeshBuildSettings.DistanceFieldResolutionScale = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->DistanceFieldResolutionScale : 2.0f; // Recomputing normals. EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag : HRSRF_OnlyIfMissing; - switch (RecomputeNormalFlag) - { - case HRSRF_Always: - { - OutMeshBuildSettings.bRecomputeNormals = true; - break; - } - - case HRSRF_OnlyIfMissing: - { - OutMeshBuildSettings.bRecomputeNormals = !bHasNormals; - break; - } - - case HRSRF_Never: - default: - { - OutMeshBuildSettings.bRecomputeNormals = false; - break; - } - } + if(RecomputeNormalFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bRecomputeNormals = !bHasNormals; // Recomputing tangents. EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; - switch (RecomputeTangentFlag) - { - case HRSRF_Always: - { - OutMeshBuildSettings.bRecomputeTangents = true; - break; - } - - case HRSRF_OnlyIfMissing: - { - OutMeshBuildSettings.bRecomputeTangents = !bHasTangents; - break; - } - - case HRSRF_Never: - default: - { - OutMeshBuildSettings.bRecomputeTangents = false; - break; - } - } + if (RecomputeTangentFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bRecomputeTangents = !bHasTangents; // Lightmap UV generation. EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; - switch (GenerateLightmapUVFlag) - { - case HRSRF_Always: - { - OutMeshBuildSettings.bGenerateLightmapUVs = true; - break; - } - - case HRSRF_OnlyIfMissing: - { - OutMeshBuildSettings.bGenerateLightmapUVs = !bHasLightmapUVSet; - break; - } - - case HRSRF_Never: - default: - { - OutMeshBuildSettings.bGenerateLightmapUVs = false; - break; - } - } + if (GenerateLightmapUVFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bGenerateLightmapUVs = !bHasLightmapUVSet; } -#undef LOCTEXT_NAMESPACE \ No newline at end of file +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h index e4fb420db..375a282d8 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h @@ -83,6 +83,7 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator const FHoudiniPackageParams& InPackageParams, const EHoudiniStaticMeshMethod& InStaticMeshMethod, const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, UObject* InOuterComponent, bool bInTreatExistingMaterialsAsUpToDate=false, bool bInDestroyProxies=false); @@ -97,6 +98,7 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator const bool& InForceRebuild, const EHoudiniStaticMeshMethod& InStaticMeshMethod, const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, bool bInTreatExistingMaterialsAsUpToDate = false); static bool CreateOrUpdateAllComponents( @@ -129,8 +131,8 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator const TArray& InData, TArray& OutSplitData); - // Update the MeshBuild Settings using the Houdini runtime settings - static void SetMeshBuildSettings( + // Update the MeshBuild Settings using the values from the runtime settings/overrides on the HAC + void UpdateMeshBuildSettings( FMeshBuildSettings& OutMeshBuildSettings, const bool& bHasNormals, const bool& bHasTangents, @@ -161,18 +163,7 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator void SetStaticMeshGenerationProperties(const FHoudiniStaticMeshGenerationProperties& InStaticMeshGenerationProperties) { StaticMeshGenerationProperties = InStaticMeshGenerationProperties; }; - //----------------------------------------------------------------------------------------------------------------------------- - // Helpers - //----------------------------------------------------------------------------------------------------------------------------- - - // Helper functions for generic property attributes - static bool GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const int32& InFirstValidVertexIndex, const int32& InFirstValidPrimIndex, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes); + void SetStaticMeshBuildSettings(const FMeshBuildSettings& InMBS) { StaticMeshBuildSettings = InMBS; }; protected: @@ -185,6 +176,13 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator // Create a UHoudiniStaticMesh bool CreateHoudiniStaticMesh(); + static void ApplyComplexColliderHelper( + UStaticMesh* TargetStaticMesh, + UStaticMesh* ComplexStaticMesh, + const EHoudiniSplitType SplitType, + bool& AssignedCustomCollisionMesh, + FHoudiniOutputObject* OutputObject); + void ResetPartCache(); bool UpdatePartVertexList(); @@ -415,4 +413,7 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator // Default properties to be used when generating Static Meshes FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; + + // Default Mesh Build settings to be used when generating Static Meshes + FMeshBuildSettings StaticMeshBuildSettings; }; diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp index 9160fa8e9..92bfe3143 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp @@ -63,17 +63,22 @@ // bool -FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& bInForceUpdate, bool& bOutHasHoudiniStaticMeshOutput) +FHoudiniOutputTranslator::UpdateOutputs( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate, + bool& bOutHasHoudiniStaticMeshOutput) { if (!HAC || HAC->IsPendingKill()) return false; - // Get the bake folder override - FHoudiniOutputTranslator::GetBakeFolderFromAttribute(HAC); - // Get the temp folder override FHoudiniOutputTranslator::GetTempFolderFromAttribute(HAC); + // Outputs that should be cleared, but only AFTER new output processing have taken place. + // This is needed for landscape resizing where the new landscape needs to copy data from the original landscape + // before the original landscape gets destroyed. + TArray DeferredClearOutputs; + // Check if the HDA has been marked as not producing outputs if (!HAC->bOutputless) { @@ -92,7 +97,13 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& TArray NewOutputs; if (FHoudiniOutputTranslator::BuildAllOutputs(HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos)) { - ClearAndRemoveOutputs(HAC); + // NOTE: For now we are currently forcing all outputs to be cleared here. There is still an issue where, in some + // circumstances, landscape tiles disappear when clearing outputs after processing. + // The reason we may need to defer landscape clearing is to allow the landscape creation code to + // capture the extent of the landscape. The extent of the landscape can only be calculated if all landscape + // tiles are still present in the map. If we find that we don't need this for updating of Input landscapes, + // we can safely remove this feature. + ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); // Replace with the new parameters HAC->Outputs = NewOutputs; } @@ -100,7 +111,40 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& else { // This HDA is marked as not supposed to produce any output - ClearAndRemoveOutputs(HAC); + ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); + } + + // Look for details generic property attributes on the outputs, + // and try to apply them to the HAC. + // This can be used to preset some of the HDA's uproperty via attribute + TArray GenericAttributes; + for (auto& CurrentOutput : HAC->Outputs) + { + const TArray& CurrentOutputHGPO = CurrentOutput->GetHoudiniGeoPartObjects(); + for (auto& CurrentHGPO : CurrentOutputHGPO) + { + FHoudiniEngineUtils::GetGenericAttributeList( + CurrentHGPO.GeoId, + CurrentHGPO.PartId, + HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, + GenericAttributes, + HAPI_ATTROWNER_DETAIL); + } + } + + // Attempt to apply the attributes to the HAC if we have any + for (const auto& CurrentPropAttribute : GenericAttributes) + { + // Get the current Property Attribute + const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; + if (CurrentPropertyName.IsEmpty()) + continue; + + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(HAC, CurrentPropAttribute)) + continue; + + // Success! + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on Houdini Asset Component named %s"), *CurrentPropertyName, *HAC->GetName()); } // NOTE: PersistentWorld can be NULL when, for example, working with @@ -139,6 +183,10 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); PackageParams.ComponentGUID = HAC->GetComponentGUID(); PackageParams.ObjectName = FString(); + + // ---------------------------------------------------- + // Outputs prepass + // ---------------------------------------------------- TArray CreatedWorldCompositionPackages; bool bCreatedNewMaps = false; @@ -217,6 +265,13 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& // ---------------------------------------------------- // Process outputs // ---------------------------------------------------- + // Landscape creation will cache the first tile as a reference location + // in this struct to be used by during construction of subsequent tiles. + FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; + // Landscape Size info will be cached by the first tile, similar to LandscapeReferenceLocation + FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; + FHoudiniLandscapeExtent LandscapeExtent; + TArray CreatedPackages; for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) { @@ -265,6 +320,7 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& PackageParams, bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, HAC->StaticMeshGenerationProperties, + HAC->StaticMeshBuildSettings, OuterComponent); NumVisibleOutputs++; @@ -348,6 +404,9 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& PersistentWorld, LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, + LandscapeExtent, + LandscapeSizeInfo, + LandscapeReferenceLocation, PackageParams, CreatedPackages); @@ -367,15 +426,16 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& // Attach the created landscapes to HAC // Output Transforms are always relative to the HDA HAC->SetMobility(EComponentMobility::Static); - OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); // Note that the above attach will cause the collision components to crap out. This manifests // itself via the Landscape editor tools not being able to trace Landscape collision components. - // By recreating collision components here, it appears to put things back into working order. + // By recreating collision components here, it appears to put things back into working order. + OutputLandscape->GetLandscapeInfo()->FixupProxiesTransform(); + OutputLandscape->GetLandscapeInfo()->RecreateLandscapeInfo(PersistentWorld, true); OutputLandscape->RecreateCollisionComponents(); } bCreatedNewMaps |= bNewMapCreated; - break; } default: @@ -402,6 +462,22 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& FHoudiniEngineUtils::AddHoudiniLogoToComponent(HAC); } + // Clear any old outputs that was marked as "Should Defer Clear". + // This should happen before SharedLandscapeActor cleanup + // since this needs to remove old landscape proxies so that empty SharedLandscapeActors + // can be removed afterward. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniOutputTranslator::UpdateOutputs] Clearing old outputs: %d"), DeferredClearOutputs.Num()); + for(UHoudiniOutput* OldOutput : DeferredClearOutputs) + { + ClearOutput(OldOutput); + } + + // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) + // { + // LandscapeExtents.IntermediateResizeLandscape->Destroy(); + // LandscapeExtents.IntermediateResizeLandscape = nullptr; + // } + if (bHasLandscape) { // ---------------------------------------------------- @@ -450,11 +526,8 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& if (TrackedLandscapes.Contains(Landscape)) continue; - ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); - if (!Info || Info->Proxies.Num() == 0) - { + if (Landscape->GetLandscapeInfo()->Proxies.Num() == 0) Landscape->Destroy(); - } } } @@ -462,6 +535,12 @@ FHoudiniOutputTranslator::UpdateOutputs(UHoudiniAssetComponent* HAC, const bool& ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); } + // Destroy the intermediate resize landscape, if there is one. + // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) + // { + // FHoudiniLandscapeTranslator::DestroyLandscape(LandscapeExtents.IntermediateResizeLandscape); + // } + if (IsValid(WorldComposition)) { // Disable the flag that we set before starting the import process. @@ -542,6 +621,7 @@ FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAss PackageParams, HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, HAC->StaticMeshGenerationProperties, + HAC->StaticMeshBuildSettings, OuterComponent, true, // bInTreatExistingMaterialsAsUpToDate bInDestroyProxies @@ -626,13 +706,25 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) continue; // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) - continue; - - // Only catch editable curves if (CurrentEditableGeoInfo.type != HAPI_GeoType::HAPI_GEOTYPE_CURVE) continue; + // Check if the curve is closed (-1 unknown, could not find parameter on node). A closed curve will + // be returned as a mesh by HAPI instead of a curve + int32 CurveClosed = -1; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + EditableNodeIds[nEditable], HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) + { + CurveClosed = -1; + } + else + { + if (CurveClosed) + CurveClosed = 1; + else + CurveClosed = 0; + } + // Cook the editable node to get its parts if (CurrentEditableGeoInfo.partCount <= 0) { @@ -657,7 +749,9 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) continue; - if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE) + // A closed curve will be returned as a mesh in HAPI + if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE && + (CurveClosed <= 0 || CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_MESH)) continue; // Get the editable curve's part name @@ -668,11 +762,10 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) EditableCurveObjIds.Add(CurrentHapiObjectInfo.nodeId); EditableCurveGeoIds.Add(CurrentEditableGeoInfo.nodeId); EditableCurvePartIds.Add(CurrentHapiPartInfo.id); - EditableCurvePartNames.Add(PartName); + EditableCurvePartNames.Add(PartName); } } } - } int32 Idx = 0; @@ -741,7 +834,10 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) NewOutputObject.OutputComponent = CurAttachedSplineComp; CurrentOutput->SetHasEditableNodeBuilt(true); - FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(CurAttachedSplineComp); + + // Never add additional rot/scale attributes on editable curves as this crashes HAPI + FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + CurAttachedSplineComp, false); Idx += 1; break; @@ -793,7 +889,9 @@ FHoudiniOutputTranslator::UploadChangedEditableOutput( if (!HoudiniSplineComponent->HasChanged()) continue; - if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(HoudiniSplineComponent)) + // Dont add rot/scale on editable curves as this crashes HAPI + if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + HoudiniSplineComponent, false)) HoudiniSplineComponent->MarkChanged(false); else HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); @@ -1800,7 +1898,7 @@ FHoudiniOutputTranslator::CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHou void -FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC) +FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll) { if (!IsValid(InHAC)) return; @@ -1810,7 +1908,14 @@ FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC) // Simply clearing the array is enough for (auto& OldOutput : InHAC->Outputs) { - ClearOutput(OldOutput); + if (OldOutput->ShouldDeferClear() && !bForceClearAll) + { + OutputsPendingClear.Add(OldOutput); + } + else + { + ClearOutput(OldOutput); + } } InHAC->Outputs.Empty(); @@ -1881,32 +1986,27 @@ FHoudiniOutputTranslator::ClearOutput(UHoudiniOutput* Output) UHierarchicalInstancedStaticMeshComponent* const FoliageHISMC = Cast(Component); if (IsValid(FoliageHISMC)) { - // Find the parent component: the foliage component outer, otherwise, if a houdini asset actor, the - // houdini asset component, otherwise for a normal actor its root component, finally try and see - // if the outer itself is a component. - USceneComponent* ParentComponent = Cast(FoliageHISMC->GetOuter()); - if (!IsValid(ParentComponent)) + // Find the parent component: the output is typically owned by an HAC. + USceneComponent* ParentComponent = nullptr; + UObject* const OutputOuter = Output->GetOuter(); + if (IsValid(OutputOuter)) { - UObject* const OutputOuter = Output->GetOuter(); - if (IsValid(OutputOuter)) + if (OutputOuter->IsA()) { - if (OutputOuter->IsA()) - { - ParentComponent = Cast(OutputOuter)->GetHoudiniAssetComponent(); - } - else if (OutputOuter->IsA()) - { - ParentComponent = Cast(OutputOuter)->GetRootComponent(); - } - else - { - ParentComponent = Cast(OutputOuter); - } + ParentComponent = Cast(OutputOuter); } + // other possibilities? + } + + // fallback to trying the owner of the HISMC + if (!ParentComponent) + { + ParentComponent = Cast(FoliageHISMC); } + if (IsValid(ParentComponent)) { - FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, ParentComponent); + FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, OutputObject.Value.OutputObject, ParentComponent); FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); } } @@ -1957,29 +2057,6 @@ FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & Nod return true; } -void -FHoudiniOutputTranslator::GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) - return; - - FString BakeFolderOverride = FString(); - - FHoudiniEngineUtils::GetBakeFolderOverridePath(DisplayGeoInfo.nodeId, BakeFolderOverride); - - // If the BakeFolder of the HAC is non-empty and is different from the override path. - // do not override it if the current temp cook path is valid. (it was user specified) - if (!HAC->BakeFolder.Path.IsEmpty() && !HAC->BakeFolder.Path.Equals(BakeFolderOverride)) - return; - - HAC->BakeFolder.Path = BakeFolderOverride; -} - void FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) { diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h index ffc33156a..791b74cc8 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h @@ -85,12 +85,20 @@ struct HOUDINIENGINE_API FHoudiniOutputTranslator static void CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache); static void CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache); - // Helper to clear the outputs of the houdini asset component - static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC); + /** + * Helper to clear the outputs of the houdini asset component + * + * Some outputs (such as landscapes) need "deferred clearing". This means that + * these outputs should only be destroyed AFTER the new outputs have been processed. + * + * @param InHAC All outputs for this Houdini Asset Component will be cleared. + * @param OutputsPendingClear Any outputs that is "pending" clear. These outputs should typically be cleared AFTER the new outputs have been fully processed. + * @param bForceClearAll Setting this flag will force outputs to be cleared here and not take into account outputs requested a deferred clear. + */ + static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll = false); // Helper to clear an individual UHoudiniOutput static void ClearOutput(UHoudiniOutput* Output); static bool GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName); - static void GetBakeFolderFromAttribute(UHoudiniAssetComponent * HAC); static void GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp index 01ad03181..1a496e0b4 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp @@ -492,7 +492,7 @@ FHoudiniPDGManager::PopulateTOPNodes( if (bInZeroWorkItemTallys) { - CurrentTOPNode->WorkItemTally.ZeroAll(); + CurrentTOPNode->ZeroWorkItemTally(); CurrentTOPNode->NodeState = EPDGNodeState::None; } } @@ -912,14 +912,15 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, break; case HAPI_PDG_EVENT_WORKITEM_ADD: + CreateOrRelinkWorkItem(TOPNode, InContextID, EventInfo.workitemId); bUpdatePDGNodeState = true; - NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeCreatedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); break; case HAPI_PDG_EVENT_WORKITEM_REMOVE: RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, TOPNode); bUpdatePDGNodeState = true; - NotifyTOPNodeTotalWorkItem(PDGAssetLink, TOPNode, -1); + NotifyTOPNodeRemovedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); break; case HAPI_PDG_EVENT_COOK_WARNING: @@ -948,44 +949,39 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, bUpdatePDGNodeState = true; if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) { - NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, -1); } else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) { - NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, -1); } else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); } else if ( (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE || LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) { // Handled previously cooked WI - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, -1); } else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, -1); } else { // TODO: // unhandled state change - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); + HOUDINI_PDG_WARNING(TEXT("Unhandled PDG state change! Node: %s, WorkItemID %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId); } if (LastWorkItemState == CurrentWorkItemState) { // TODO: // Not a change!! shouldnt happen! - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 0); + HOUDINI_PDG_WARNING(TEXT("Last state == current state! Node: %s, WorkItemID %d, state %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId, EventInfo.lastState); } // New states if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) { - NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNCOOKED) { @@ -999,29 +995,29 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) { - NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS || CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE) { - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); // On cook success, handle results - CreateWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); + CreateOrRelinkWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) { // TODO: on cook failure, get log path? - NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, 1); + NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); MsgColor = FLinearColor::Red; } else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CANCEL) { - // Ignore it because in-progress cooks can be cancelled when automatically recooking graph + NotifyTOPNodeCookCancelledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); } } break; @@ -1064,13 +1060,20 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, { if (TOPNode->AreAllWorkItemsComplete()) { - if (TOPNode->AnyWorkItemsFailed()) + // At the end of a node/net cook, ensure that the work items are in sync with HAPI and remove any + // work items with invalid ids or that don't exist on the HAPI side anymore. + SyncAndPruneWorkItems(TOPNode); + // Check that all work items are still complete after the sync + if (TOPNode->AreAllWorkItemsComplete()) { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); - } - else - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + if (TOPNode->AnyWorkItemsFailed()) + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); + } + else + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + } } } } @@ -1165,7 +1168,8 @@ FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetL //Debug.LogFormat("NotifyTOPNodePDGStateClear:: {0}", topNode._nodeName); InTOPNode->NodeState = EPDGNodeState::None; - InTOPNode->WorkItemTally.ZeroAll(); + InTOPNode->ZeroWorkItemTally(); + InTOPNode->OnDirtyNode(); HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ZeroAll"), *(InTOPNode->NodePath)); @@ -1175,89 +1179,114 @@ FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetL } void -FHoudiniPDGManager::NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; - InTOPNode->WorkItemTally.TotalWorkItems = FMath::Max(InTOPNode->WorkItemTally.TotalWorkItems + Increment, 0); + InTOPNode->OnWorkItemCreated(InWorkItemID); - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally TotalWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Created WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } void -FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; - InTOPNode->WorkItemTally.CookedWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookedWorkItems + Increment, 0); + InTOPNode->OnWorkItemRemoved(InWorkItemID); - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Removed WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } void -FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; - - InTOPNode->WorkItemTally.ErroredWorkItems = FMath::Max(InTOPNode->WorkItemTally.ErroredWorkItems + Increment, 0); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + + InTOPNode->OnWorkItemCooked(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookedWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } void -FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; + + InTOPNode->OnWorkItemErrored(InWorkItemID); - InTOPNode->WorkItemTally.WaitingWorkItems = FMath::Max(InTOPNode->WorkItemTally.WaitingWorkItems + Increment, 0); + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemWaiting(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } void -FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; - - InTOPNode->WorkItemTally.ScheduledWorkItems = FMath::Max(InTOPNode->WorkItemTally.ScheduledWorkItems + Increment, 0); - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + InTOPNode->OnWorkItemScheduled(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumScheduledWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } void -FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment) +FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) { if (!IsValid(InTOPNode)) return; - - InTOPNode->WorkItemTally.CookingWorkItems = FMath::Max(InTOPNode->WorkItemTally.CookingWorkItems + Increment, 0); - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Increment %d"), *(InTOPNode->NodePath), Increment); + InTOPNode->OnWorkItemCooking(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookingWorkItems()); // InPDGAssetLink->bNeedsUIRefresh = true; //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); } +void +FHoudiniPDGManager::NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemCookCancelled(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookCancelledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookCancelledWorkItems()); +} + void FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) { @@ -1330,8 +1359,55 @@ FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const b } } +int32 +FHoudiniPDGManager::CreateOrRelinkWorkItem( + UTOPNode* InTOPNode, + const HAPI_PDG_GraphContextId& InContextID, + HAPI_PDG_WorkitemId InWorkItemID) +{ + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); + return INDEX_NONE; + } + + HAPI_PDG_WorkitemInfo WorkItemInfo; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( + FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return INDEX_NONE; + } + + // Try to find the existing WorkItem by ID. + int32 Index = InTOPNode->IndexOfWorkResultByID(InWorkItemID); + if (Index == INDEX_NONE) + { + // Try to find an entry with WorkItemID == INDEX_NONE but where workItemIndex matches. The WorkItemIDs are + // transient, so not saved when the map / asset link is saved. So when loading a map containing the asset + // link all the IDs are INDEX_NONE and the WorkItemIndex is the best way to find an entry to re-use. + const bool bWithInvalidWorkItemID = true; + Index = InTOPNode->IndexOfWorkResultByHAPIIndex(WorkItemInfo.index, bWithInvalidWorkItemID); + if (Index == INDEX_NONE) + { + // If we couldn't find an existing entry, or a stale entry to re-use, create a new one + FTOPWorkResult LocalWorkResult; + LocalWorkResult.WorkItemID = InWorkItemID; + LocalWorkResult.WorkItemIndex = WorkItemInfo.index; + Index = InTOPNode->WorkResult.Add(LocalWorkResult); + } + else + { + InTOPNode->WorkResult[Index].WorkItemID = InWorkItemID; + } + } + + return Index; +} + bool -FHoudiniPDGManager::CreateWorkItemResult( +FHoudiniPDGManager::CreateOrRelinkWorkItemResult( UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, @@ -1352,6 +1428,27 @@ FHoudiniPDGManager::CreateWorkItemResult( return false; } + // Try to find the existing WorkResult by ID. + FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); + if (!WorkResult) + { + // TODO: This shouldn't really happen, it means a work item finished cooking and generated a result before + // we received an event that the work item was added/generated. + int32 ArrayIndex = CreateOrRelinkWorkItem(InTOPNode, InContextID, InWorkItemID); + if (ArrayIndex != INDEX_NONE) + { + WorkResult = InTOPNode->GetWorkResultByArrayIndex(ArrayIndex); + } + } + + if (!WorkResult) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get or add a FTOPWorkResult for WorkItemID %d for %s"), InWorkItemID, *(InTOPNode->NodeName)); + return false; + } + + TArray NewResultObjects; + TSet ResultIndicesThatWereReused; if (WorkItemInfo.numResults > 0) { TArray ResultInfos; @@ -1366,16 +1463,6 @@ FHoudiniPDGManager::CreateWorkItemResult( return false; } - FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); - if (!WorkResult) - { - FTOPWorkResult LocalWorkResult; - LocalWorkResult.WorkItemID = InWorkItemID; - LocalWorkResult.WorkItemIndex = WorkItemInfo.index; - const int32 Idx = InTOPNode->WorkResult.Add(LocalWorkResult); - WorkResult = &(InTOPNode->WorkResult[Idx]); - } - FString WorkItemName; FHoudiniEngineString::ToFString(WorkItemInfo.nameSH, WorkItemName); @@ -1397,54 +1484,150 @@ FHoudiniPDGManager::CreateWorkItemResult( // Construct the name and look for an existing work result, re-use it if found, otherwise create a new one const FString WorkResultName = FString::Printf( - TEXT("%s_%s_%d"), + TEXT("%s_%s_%d_%d"), *(InTOPNode->ParentName), *WorkItemName, - WorkItemInfo.index); + WorkItemInfo.index, + Idx); - FTOPWorkResultObject* ExistingResultObject = WorkResult->ResultObjects.FindByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) + // int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) + // { + // return InResultObject.Name == WorkResultName; + // }); + int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([Idx](const FTOPWorkResultObject& InResultObject) { - return InResultObject.Name == WorkResultName; + return InResultObject.WorkItemResultInfoIndex == Idx; }); - if (ExistingResultObject) + if (WorkResult->ResultObjects.IsValidIndex(ExistingObjectIndex)) { - ExistingResultObject->FilePath = CurrentPath; - if (ExistingResultObject->State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) + FTOPWorkResultObject& ExistingResultObject = WorkResult->ResultObjects[ExistingObjectIndex]; + + ExistingResultObject.Name = WorkResultName; + ExistingResultObject.FilePath = CurrentPath; + ExistingResultObject.SetAutoBakedSinceLastLoad(false); + if (ExistingResultObject.State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) { - ExistingResultObject->State = EPDGWorkResultState::ToDelete; + ExistingResultObject.State = EPDGWorkResultState::ToDelete; } - else if ((ExistingResultObject->State == EPDGWorkResultState::Loaded || - ExistingResultObject->State == EPDGWorkResultState::ToDelete || - ExistingResultObject->State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) + else { // When the commandlet is not being used, we could leave the outputs in place and have // translators try to re-use objects/components. When the commandlet is being used, the packages // are always saved and standalone, so if we want to automatically clean up old results then we // need to destroy the existing outputs if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - ExistingResultObject->DestroyResultOutputs(); - ExistingResultObject->State = EPDGWorkResultState::ToLoad; - } - else - { - ExistingResultObject->State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + ExistingResultObject.DestroyResultOutputs(); + + if ((ExistingResultObject.State == EPDGWorkResultState::Loaded || + ExistingResultObject.State == EPDGWorkResultState::ToDelete || + ExistingResultObject.State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) + { + ExistingResultObject.State = EPDGWorkResultState::ToLoad; + } + else + { + ExistingResultObject.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + } } + + NewResultObjects.Add(ExistingResultObject); + ResultIndicesThatWereReused.Add(ExistingObjectIndex); } else { FTOPWorkResultObject ResultObj; ResultObj.Name = WorkResultName; ResultObj.FilePath = CurrentPath; - ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + ResultObj.WorkItemResultInfoIndex = Idx; + ResultObj.SetAutoBakedSinceLastLoad(false); - WorkResult->ResultObjects.Add(ResultObj); + NewResultObjects.Add(ResultObj); } } } + // Destroy any old ResultObjects that were not re-used + const int32 NumPrevResultObjects = WorkResult->ResultObjects.Num(); + for (int32 ResultObjectIndex = 0; ResultObjectIndex < NumPrevResultObjects; ++ResultObjectIndex) + { + if (ResultIndicesThatWereReused.Contains(ResultObjectIndex)) + continue; + FTOPWorkResultObject& ResultObject = WorkResult->ResultObjects[ResultObjectIndex]; + ResultObject.DestroyResultOutputsAndRemoveOutputActor(); + } + WorkResult->ResultObjects = NewResultObjects; return true; } +int32 +FHoudiniPDGManager::SyncAndPruneWorkItems(UTOPNode* InTOPNode) +{ + TSet WorkItemIDSet; + TArray WorkItemIDs; + int NumWorkItems = -1; + + if (!IsValid(InTOPNode)) + return -1; + + HAPI_Session const * const HAPISession = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNumWorkitems(HAPISession, InTOPNode->NodeId, &NumWorkItems)) + { + HOUDINI_LOG_WARNING(TEXT("GetNumWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + WorkItemIDs.SetNum(NumWorkItems); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitems(HAPISession, InTOPNode->NodeId, WorkItemIDs.GetData(), NumWorkItems)) + { + HOUDINI_LOG_WARNING(TEXT("GetWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + HAPI_PDG_GraphContextId ContextId; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId(HAPISession, InTOPNode->NodeId, &ContextId)) + { + HOUDINI_LOG_WARNING(TEXT("GetPDGGraphContextId call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + for (const HAPI_PDG_WorkitemId& WorkItemID : WorkItemIDs) + { + WorkItemIDSet.Add(static_cast(WorkItemID)); + + // If the WorkItemID is not present in FTOPWorkResult array, sync from HAPI + if (InTOPNode->GetWorkResultByID(WorkItemID) == nullptr) + { + CreateOrRelinkWorkItemResult(InTOPNode, ContextId, WorkItemID); + InTOPNode->OnWorkItemCreated(WorkItemID); + } + } + + // TODO: refactor functions that access the TOPNode's array and properties directly to rather be functions on the + // UTOPNode and make access to these arrays protected/private + + // Remove any work result entries with invalid IDs or where the WorkItemID is not in the set of ids returned by + // HAPI (only if we could get the IDs from HAPI). + int32 NumRemoved = 0; + const int32 NumWorkItemsInArray = InTOPNode->WorkResult.Num(); + for (int32 Index = NumWorkItemsInArray - 1; Index >= 0; --Index) + { + FTOPWorkResult& WorkResult = InTOPNode->WorkResult[Index]; + if (WorkResult.WorkItemID == INDEX_NONE || !WorkItemIDSet.Contains(WorkResult.WorkItemID)) + { + HOUDINI_PDG_WARNING( + TEXT("Pruning a FTOPWorkResult entry from TOP Node %d, WorkItemID %d, WorkItemIndex %d, Array Index %d"), + InTOPNode->NodeId, WorkResult.WorkItemID, WorkResult.WorkItemIndex, Index); + WorkResult.ClearAndDestroyResultObjects(); + InTOPNode->WorkResult.RemoveAt(Index); + InTOPNode->OnWorkItemRemoved(WorkResult.WorkItemID); + NumRemoved++; + } + } + + return NumRemoved; +} + void FHoudiniPDGManager::ProcessWorkItemResults() { @@ -1486,7 +1669,7 @@ FHoudiniPDGManager::ProcessWorkItemResults() } else { - PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); + PackageParams.OuterPackage = AssetLinkParent ? AssetLinkParent->GetOutermost() : nullptr; PackageParams.HoudiniAssetName = FString(); PackageParams.HoudiniAssetActorName = FString(); // PackageParams.ComponentGUID = HAC->GetComponentGUID(); @@ -1520,7 +1703,7 @@ FHoudiniPDGManager::ProcessWorkItemResults() // ... All WorkResult CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; CurrentTOPNode->bCachedHaveLoadedWorkResults = false; - for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) + for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) { // ... All WorkResultObjects for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) @@ -1553,11 +1736,13 @@ FHoudiniPDGManager::ProcessWorkItemResults() PackageParams)) { CurrentWorkResultObj.State = EPDGWorkResultState::Loaded; + CurrentWorkResultObj.SetAutoBakedSinceLastLoad(false); CurrentTOPNode->bCachedHaveLoadedWorkResults = true; // Broadcast that we have loaded the work result object to those interested AssetLink->OnWorkResultObjectLoaded.Broadcast( - AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemID, CurrentWorkResultObj.Name); + AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemIndex, + CurrentWorkResultObj.WorkItemResultInfoIndex); } else { @@ -1816,12 +2001,15 @@ void FHoudiniPDGManager::HandleImportBGEOResultMessage( const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; FHoudiniOutputObject *OutputObject = NewOutput->GetOutputObjects().Find(Identifier); - if (OutputObject && IsValid(OutputObject->OutputComponent)) + if (OutputObject) { - // Update generic property attributes - FHoudiniMeshTranslator::UpdateGenericPropertiesAttributes( - OutputObject->OutputComponent, - ImportOutputObject.GenericAttributes.PropertyAttributes); + if (IsValid(OutputObject->OutputComponent)) + { + // Update generic property attributes + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + OutputObject->OutputComponent, + ImportOutputObject.GenericAttributes.PropertyAttributes); + } // Copy cached attributes OutputObject->CachedAttributes.Append(ImportOutputObject.CachedAttributes); @@ -1837,9 +2025,11 @@ void FHoudiniPDGManager::HandleImportBGEOResultMessage( if (bSuccess) { WorkResultObject->State = EPDGWorkResultState::Loaded; + WorkResultObject->SetAutoBakedSinceLastLoad(false); HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); // Broadcast that we have loaded the work result object to those interested - AssetLink->OnWorkResultObjectLoaded.Broadcast(AssetLink, TOPNode, WorkResult->WorkItemID, WorkResultObject->Name); + AssetLink->OnWorkResultObjectLoaded.Broadcast( + AssetLink, TOPNode, WorkResult->WorkItemIndex, WorkResultObject->WorkItemResultInfoIndex); } else { diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.h b/Source/HoudiniEngine/Private/HoudiniPDGManager.h index 83c87030a..a48064977 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.h @@ -125,11 +125,23 @@ struct HOUDINIENGINE_API FHoudiniPDGManager // node. This destroys any loaded results (geometry etc), and the work item struct. void RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); - // Create FTOPWorkResult for a given TOP node, and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. + // Create a (or re-use an existing) FTOPWorkResult for a given TOPNode and the specified work item ID, without + // creating its FTOPWorkResultObjects. + // Returns INDEX_NONE if an entry could not be created or data could not be retrieved from HAPI. + int32 CreateOrRelinkWorkItem(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID); + + // Ensure that FTOPWorkResult exists, and create its FTOPWorkResultObjects for a given TOP node and work item id, + // and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. // Geometry is not directly loaded by this function, the FTOPWorkResultObjects' states will be set to ToLoad and // the ProcessWorkItemResults function will take care of loading the geo. // Results must be tagged with 'file', and must have a file path, otherwise will not included. - bool CreateWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); + bool CreateOrRelinkWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); + + // First Create or re-link FTOPWorkResults based on the work items that exist on InTOPNode in HAPI. + // Then remove any FTOPWorkResults (and clean up their output) from the WorkResults of InTOPNode if: + // WorkResult.WorkItemID is INDEX_NONE + // WorkResult.WorkItemID is not in the list of work item ids that HAPI returns for this node + int32 SyncAndPruneWorkItems(UTOPNode* InTOPNode); // Handles replies from commandlets in response to a FHoudiniPDGImportBGEODiscoverMessage void HandleImportBGEODiscoverMessage( @@ -166,17 +178,21 @@ struct HOUDINIENGINE_API FHoudiniPDGManager void NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode); - void NotifyTOPNodeTotalWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& Increment); + void NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); private: diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp index d80ebc009..0407edba1 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp @@ -46,6 +46,7 @@ #include "HoudiniOutputTranslator.h" #include "HoudiniSplineComponent.h" #include "HoudiniSplineTranslator.h" +#include "HoudiniTranslatorTypes.h" #define LOCTEXT_NAMESPACE "HoudiniEngine" @@ -133,6 +134,9 @@ FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( NewTOPOutputs, InPackageParams, WorkItemOutputActor->GetRootComponent(), + InTOPNode->GetLandscapeExtent(), + InTOPNode->GetLandscapeReferenceLocation(), + InTOPNode->GetLandscapeSizeInfo(), InOutputTypesToProcess, bInTreatExistingMaterialsAsUpToDate); @@ -207,10 +211,13 @@ FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( const bool bInTreatExistingMaterialsAsUpToDate = true; const bool bOnlyUseExistingAssets = true; - bool bResult = CreateAllResultObjectsFromPDGOutputs( + const bool bResult = CreateAllResultObjectsFromPDGOutputs( InOutputs, InPackageParams, WorkItemOutputActor->GetRootComponent(), + InTOPNode->GetLandscapeExtent(), + InTOPNode->GetLandscapeReferenceLocation(), + InTOPNode->GetLandscapeSizeInfo(), InOutputTypesToProcess, bInTreatExistingMaterialsAsUpToDate, bOnlyUseExistingAssets, @@ -233,10 +240,14 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( TArray& InOutputs, const FHoudiniPackageParams& InPackageParams, UObject* InOuterComponent, + FHoudiniLandscapeExtent& CachedLandscapeExtent, + FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, + FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, TArray InOutputTypesToProcess, bool bInTreatExistingMaterialsAsUpToDate, bool bInOnlyUseExistingAssets, - const TMap* InPreBuiltInstancedOutputPartData) + const TMap* InPreBuiltInstancedOutputPartData + ) { // Process the new/updated outputs via the various translators // We try to maintain as much parity with the existing HoudiniAssetComponent workflow @@ -287,7 +298,7 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( FHoudiniLandscapeTranslator::GetLayersZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums); } } - + TArray InstancerOutputs; TArray LandscapeOutputs; TArray CreatedPackages; @@ -322,12 +333,15 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( } else { + // TODO: If Outer is an HAC, get SMGP/MBS from it ?? FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); + FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( CurOutput, InPackageParams, EHoudiniStaticMeshMethod::RawMesh, SMGP, + MBS, InOuterComponent, bInTreatExistingMaterialsAsUpToDate, bInDestroyProxies @@ -375,6 +389,9 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( PersistentWorld, LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, + CachedLandscapeExtent, + CachedLandscapeSizeInfo, + CachedLandscapeRefLoc, InPackageParams, //bCreatedNewMaps, CreatedPackages); diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h index 07d466767..f3957ae80 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h @@ -39,6 +39,9 @@ struct FHoudiniPackageParams; struct FTOPWorkResultObject; struct FHoudiniOutputObjectIdentifier; struct FHoudiniInstancedOutputPartData; +struct FHoudiniLandscapeExtent; +struct FHoudiniLandscapeReferenceLocation; +struct FHoudiniLandscapeTileSizeInfo; struct HOUDINIENGINE_API FHoudiniPDGTranslator { @@ -68,8 +71,12 @@ struct HOUDINIENGINE_API FHoudiniPDGTranslator TArray& InOutputs, const FHoudiniPackageParams& InPackageParams, UObject* InOuterComponent, + FHoudiniLandscapeExtent& CachedLandscapeExtent, + FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, + FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, TArray InOutputTypesToProcess={}, bool bInTreatExistingMaterialsAsUpToDate=false, bool bInOnlyUseExistingAssets=false, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); + const TMap* InPreBuiltInstancedOutputPartData=nullptr + ); }; diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp index 4cd3bd031..81960c089 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp @@ -61,8 +61,6 @@ FHoudiniPackageParams::FHoudiniPackageParams() PDGTOPNetworkName.Empty(); PDGTOPNodeName.Empty(); PDGWorkItemIndex = INDEX_NONE; - - bAttemptToLoadMissingPackages = false; } @@ -329,19 +327,23 @@ FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InB } */ - // See if a package named similarly already exists - UPackage* FoundPackage = FindPackage(PackageOuter, *FinalPackageName); - if (FoundPackage == nullptr && bAttemptToLoadMissingPackages) - { - FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_NoWarn); - } - if (ReplaceMode == EPackageReplaceMode::CreateNewAssets - && FoundPackage && !FoundPackage->IsPendingKill()) + // If we are set to create new assets, check if a package named similarly already exists + if (ReplaceMode == EPackageReplaceMode::CreateNewAssets) { - // we need to generate a new name for it - CurrentGuid = FGuid::NewGuid(); - BakeCounter++; - continue; + UPackage* FoundPackage = FindPackage(PackageOuter, *FinalPackageName); + if (FoundPackage == nullptr) + { + // Package might not be in memory, check if it exists on disk + FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_Verify | LOAD_NoWarn); + } + + if (FoundPackage && !FoundPackage->IsPendingKill()) + { + // we need to generate a new name for it + CurrentGuid = FGuid::NewGuid(); + BakeCounter++; + continue; + } } // Create actual package. diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.h b/Source/HoudiniEngine/Private/HoudiniPackageParams.h index 87939cac4..380035125 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.h +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.h @@ -26,6 +26,7 @@ #pragma once +#include "HoudiniAssetComponent.h" #include "UObject/ObjectMacros.h" #include "Engine/World.h" #include "Misc/Paths.h" @@ -159,12 +160,6 @@ struct HOUDINIENGINE_API FHoudiniPackageParams UPROPERTY() int32 PDGWorkItemIndex; - // If FindPackage returns null, if this flag is true then a LoadPackage will also be attempted - // This is for use cases, such as commandlets, that might unload packages once done with them, but that must - // reliably be able to determine if a package exists later - UPROPERTY() - bool bAttemptToLoadMissingPackages; - ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; //FString GetBakeFolderArgument(ERuntimePackageMode PackageMode) const; @@ -191,11 +186,17 @@ struct HOUDINIENGINE_API FHoudiniPackageParams template void UpdateTokensFromParams( const UWorld* WorldContext, + const UHoudiniAssetComponent* HAC, TMap& OutTokens) const { UpdateOutputPathTokens(PackageMode, OutTokens); - OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); + if(IsValid(WorldContext)) + OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); + + if(HAC && HAC->GetOutermost()) + OutTokens.Add("hda_level", ValueT( HAC->GetOutermost()->GetPathName() )); + OutTokens.Add("object_name", ValueT( ObjectName )); OutTokens.Add("object_id", ValueT( FString::FromInt(ObjectId) )); OutTokens.Add("geo_id", ValueT( FString::FromInt(GeoId) )); @@ -214,8 +215,8 @@ struct HOUDINIENGINE_API FHoudiniPackageParams { const FString PackagePath = GetPackagePath(); - OutTokens.Add("temp", ValueT(FPaths::GetPath(TempCookFolder))); - OutTokens.Add("bake", ValueT(FPaths::GetPath(BakeFolder))); + OutTokens.Add("temp", ValueT(TempCookFolder)); + OutTokens.Add("bake", ValueT(BakeFolder)); // `out_basepath` is useful if users want to organize their cook/bake assets // different to the convention defined by GetPackagePath(). This would typically @@ -224,14 +225,14 @@ struct HOUDINIENGINE_API FHoudiniPackageParams { case EPackageMode::CookToTemp: case EPackageMode::CookToLevel: - OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(TempCookFolder))); + OutTokens.Add("out_basepath", ValueT(TempCookFolder)); break; case EPackageMode::Bake: - OutTokens.Add("out_basepath", ValueT(FPaths::GetPath(BakeFolder))); + OutTokens.Add("out_basepath", ValueT(BakeFolder)); break; } - OutTokens.Add("out", ValueT( FPaths::GetPath(PackagePath) )); + OutTokens.Add("out", ValueT(PackagePath)); } }; diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp index 967a9701e..5ff30c0c3 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp @@ -214,11 +214,19 @@ FHoudiniParameterTranslator::BuildAllParameters( // Get the asset's info HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HAPI_Result Result = FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo); + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } // .. the asset's node info HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); @@ -234,29 +242,47 @@ FHoudiniParameterTranslator::BuildAllParameters( return false; } - TArray AllMultiParams; + // Retrieve all the parameter infos. - TArray< HAPI_ParmInfo > ParmInfos; + TArray ParmInfos; ParmInfos.SetNumUninitialized(NodeInfo.parmCount); HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); + // Create a name lookup cache for the current parameters - TMap CurrentParametersByName; + // Use an array has in some cases, multiple parameters can have the same name! + TMap> CurrentParametersByName; CurrentParametersByName.Reserve(CurrentParameters.Num()); - for (auto& Parm : CurrentParameters) + for (const auto& Parm : CurrentParameters) { - if (!Parm) + if (!IsValid(Parm)) continue; - CurrentParametersByName.Add(Parm->GetParameterName(), Parm); + + FString ParmName = Parm->GetParameterName(); + TArray* FoundParmArray = CurrentParametersByName.Find(ParmName); + if (!FoundParmArray) + { + // Create a new array + TArray ParmArray; + ParmArray.Add(Parm); + + // add the new array to the map + CurrentParametersByName.Add(ParmName, ParmArray); + } + else + { + // add this parameter to the existing array + FoundParmArray->Add(Parm); + } } // Create properties for parameters. TArray NewParmIds; + TArray AllMultiParams; for (int32 ParamIdx = 0; ParamIdx < NodeInfo.parmCount; ++ParamIdx) { - // Retrieve param info at this index. const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; @@ -303,48 +329,58 @@ FHoudiniParameterTranslator::BuildAllParameters( EHoudiniParameterType ParmType = EHoudiniParameterType::Invalid; FHoudiniParameterTranslator::GetParmTypeFromParmInfo(ParmInfo, ParmType); - UHoudiniParameter ** FoundHoudiniParameter = CurrentParametersByName.Find(NewParmName); - - // If that parameter exists, we might be able to simply reuse it. - bool IsFoundParameterValid = false; - if (FoundHoudiniParameter && *FoundHoudiniParameter && !(*FoundHoudiniParameter)->IsPendingKill()) + // Not using the name lookup map! + UHoudiniParameter* FoundHoudiniParameter = nullptr; + TArray* MatchingParameters = CurrentParametersByName.Find(NewParmName); + if ((ParmType != EHoudiniParameterType::Invalid) && MatchingParameters) { - // First, we can simply check that the tuple size hasn't changed - if ((*FoundHoudiniParameter)->GetTupleSize() != ParmInfo.size) - { - IsFoundParameterValid = false; - } - else if (ParmType == EHoudiniParameterType::Invalid ) - { - IsFoundParameterValid = false; - } - else if (ParmType != (*FoundHoudiniParameter)->GetParameterType() ) + //for (auto& CurrentParm : *MatchingParameters) + for(int32 Idx = MatchingParameters->Num() - 1; Idx >= 0; Idx--) { - // Types do not match - IsFoundParameterValid = false; - } - else if ( !CheckParameterTypeAndClassMatch( *FoundHoudiniParameter, ParmType) ) - { - // Found parameter class does not match - IsFoundParameterValid = false; - } - else - { - // We can reuse the parameter - IsFoundParameterValid = true; + UHoudiniParameter* CurrentParm = (*MatchingParameters)[Idx]; + if (!CurrentParm) + continue; + + // First Check the parameter types match + if (ParmType != CurrentParm->GetParameterType()) + { + // Types do not match + continue; + } + + // Then, make sure the tuple size hasn't changed + if (CurrentParm->GetTupleSize() != ParmInfo.size) + { + // Tuple do not match + continue; + } + + if (!CheckParameterTypeAndClassMatch(CurrentParm, ParmType)) + { + // Wrong class + continue; + } + + // We can reuse this parameter + FoundHoudiniParameter = CurrentParm; + + // Remove it from the array/map + MatchingParameters->RemoveAt(Idx); + if (MatchingParameters->Num() <= 0) + CurrentParametersByName.Remove(NewParmName); + + break; } } - + UHoudiniParameter * HoudiniAssetParameter = nullptr; - - if (IsFoundParameterValid) + if (FoundHoudiniParameter) { // We can reuse the parameter we found - HoudiniAssetParameter = *FoundHoudiniParameter; + HoudiniAssetParameter = FoundHoudiniParameter; // Transfer param object from current map to new map CurrentParameters.Remove(HoudiniAssetParameter); - CurrentParametersByName.Remove(NewParmName); // Do a fast update of this parameter if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, InForceFullUpdate, bUpdateValues)) @@ -389,14 +425,12 @@ FHoudiniParameterTranslator::BuildAllParameters( // Fully update this parameter if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, true, true)) continue; - } // Add the new parameters NewParameters.Add(HoudiniAssetParameter); NewParmIds.Add(ParmInfo.id); - // Check if the parameter is a direct child of a multiparam. if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::MultiParm) AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); @@ -409,7 +443,6 @@ FHoudiniParameterTranslator::BuildAllParameters( if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::FolderList) AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); } - } // Assign folder type to all folderlists, @@ -2477,19 +2510,40 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InPara if (!GetMultiParmInstanceStartIdx(AssetInfo, MultiParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) return false; - - for (int n = 0; n < InstanceCount - MultiParam->GetInstanceCount(); ++n) + const int32 InstanceCountInUnreal = FMath::Max(MultiParam->GetInstanceCount(), 0); + if (InstanceCount > InstanceCountInUnreal) { - FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset); + // The multiparm has more instances on the Houdini side, remove instances from the end until it has the same + // number as in Unreal. + // NOTE: Initially this code always removed the first instance. But that causes an issue if HAPI/Houdini does + // not immediately update the parameter names (param1, param2, param3, when param1 is removed, 2 -> 1, + // 3 -> 2) so that could result in GetParameters returning parameters with unique IDs, but where the names + // are not up to date, so in the above example, the last param could still be named param3 when it should + // be named param2. + const int32 Delta = InstanceCount - InstanceCountInUnreal; + for (int32 n = 0; n < Delta; ++n) + { + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset + InstanceCount - 1 - n); + } } - - for (int n = 0; n < MultiParam->GetInstanceCount() - InstanceCount; ++n) + else if (InstanceCountInUnreal > InstanceCount) { - FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset); + // The multiparm has fewer instances on the Houdini side, add instances at the end until it has the same + // number as in Unreal. + // NOTE: Initially this code always inserted before the first instance. But that causes an issue if HAPI/Houdini + // does not immediately update the parameter names (param1, param2, param3, when a param is inserted + // before 1, then 1->2, 2->3, 3->4 so that could result in GetParameters returning parameters with unique + // IDs, but where the names are not up to date, so in the above example, the now second param could still + // be named param1 when it should be named param2. + const int32 Delta = InstanceCountInUnreal - InstanceCount; + for (int32 n = 0; n < Delta; ++n) + { + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset + InstanceCount + n); + } } @@ -2497,6 +2551,9 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InPara for (int32 ParamIdx = CurrentIndex; ParamIdx < OldParams.Num(); ++ParamIdx) { UHoudiniParameter* NextParm = OldParams[ParamIdx]; + if (!NextParm || NextParm->IsPendingKill()) + continue; + if (NextParm->GetParentParmId() == ParmId) { if (NextParm->GetParameterType() == EHoudiniParameterType::MultiParm) diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp index 40b10d0f5..90a56b21e 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp @@ -141,41 +141,52 @@ FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniInput* Input) } bool -FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent) +FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent* HoudiniSplineComponent) { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return false; int32 CurveNode_id = HoudiniSplineComponent->GetNodeId(); if (CurveNode_id < 0) return false; - - bool Success = true; - FString CurvePointsString = FString(); - int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; - int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; - int32 CurveClosed = 0; - int32 CurveReversed = 0; - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsString( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - - Success &= FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + FString CurvePointsString = FString(); + if (!FHoudiniEngineUtils::HapiGetParameterDataAsString( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString)) + { + return false; + } + int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue)) + { + return false; + } HoudiniSplineComponent->SetCurveType((EHoudiniCurveType)CurveTypeValue); + + int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue)) + { + return false; + } HoudiniSplineComponent->SetCurveMethod((EHoudiniCurveMethod)CurveMethodValue); + + int32 CurveClosed = 0; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) + { + return false; + } HoudiniSplineComponent->SetClosedCurve(CurveClosed == 1); + + int32 CurveReversed = 0; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed)) + { + return false; + } HoudiniSplineComponent->SetReversed(CurveReversed == 1); // We need to get the NodeInfo to get the parent id @@ -184,11 +195,14 @@ FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSp HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), CurveNode_id, &NodeInfo), false); - TArray< float > RefinedCurvePositions; + TArray RefinedCurvePositions; HAPI_AttributeInfo AttributeRefinedCurvePositions; FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); - Success &= FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions)) + { + return false; + } // Process coords string and extract positions. TArray CurvePoints; @@ -197,15 +211,15 @@ FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSp TArray CurveDisplayPoints; FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurveDisplayPoints); - // build curve points for editable curves. - if (HoudiniSplineComponent->CurvePoints.Num() < CurvePoints.Num()) + // Build curve points for editable curves. + if (HoudiniSplineComponent->CurvePoints.Num() != CurvePoints.Num()) { - HoudiniSplineComponent->CurvePoints.Empty(); - for (FVector NextPos : CurvePoints) + HoudiniSplineComponent->CurvePoints.SetNum(CurvePoints.Num()); + for(int32 Idx = 0; Idx < CurvePoints.Num(); Idx++) { - FTransform NextTrans = FTransform::Identity; - NextTrans.SetLocation(NextPos); - HoudiniSplineComponent->CurvePoints.Add(NextTrans); + FTransform Transform = FTransform::Identity; + Transform.SetLocation(CurvePoints[Idx]); + HoudiniSplineComponent->CurvePoints[Idx] = Transform; } } @@ -214,12 +228,14 @@ FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSp HoudiniSplineComponent->MarkChanged(false); - return Success; + return true; } bool -FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent) +FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + UHoudiniSplineComponent* HoudiniSplineComponent, + bool bInAddRotAndScaleAttributes) { if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) return true; @@ -227,13 +243,14 @@ FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSpline TArray PositionArray; TArray RotationArray; TArray Scales3dArray; - TArray UniformScaleArray; - for (FTransform & NextTransform : HoudiniSplineComponent->CurvePoints) + for (FTransform& CurrentTransform : HoudiniSplineComponent->CurvePoints) { - PositionArray.Add(NextTransform.GetLocation()); - RotationArray.Add(NextTransform.GetRotation()); - Scales3dArray.Add(NextTransform.GetScale3D()); - UniformScaleArray.Add(1.f); + PositionArray.Add(CurrentTransform.GetLocation()); + if (bInAddRotAndScaleAttributes) + { + RotationArray.Add(CurrentTransform.GetRotation()); + Scales3dArray.Add(CurrentTransform.GetScale3D()); + } } HAPI_NodeId CurveNode_id = HoudiniSplineComponent->GetNodeId(); @@ -255,8 +272,8 @@ FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSpline CurveNode_id, InputNodeNameString, &PositionArray, - &RotationArray, - &Scales3dArray, + bInAddRotAndScaleAttributes ? &RotationArray : nullptr, + bInAddRotAndScaleAttributes ? &Scales3dArray : nullptr, HoudiniSplineComponent->GetCurveType(), HoudiniSplineComponent->GetCurveMethod(), HoudiniSplineComponent->IsClosedCurve(), @@ -270,18 +287,6 @@ FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSpline return Success; } -bool -FHoudiniSplineTranslator::HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent) -{ - if (!SplineComponent || SplineComponent->IsPendingKill()) - return true; - - bool Success = HapiUpdateNodeForHoudiniSplineComponent(SplineComponent); - - return Success; -} - bool FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( HAPI_NodeId& CurveNodeId, @@ -674,6 +679,35 @@ FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( } break; + case HAPI_STORAGETYPE_INT64: + { + // Storing IntData + TArray Int64Data; + Int64Data.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + Int64Data.GetData(), 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info, Int64Data.GetData(), + 0, attr_info.count), false); + } + break; + case HAPI_STORAGETYPE_FLOAT: { // Storing Float Data @@ -749,7 +783,14 @@ FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( break; default: + { + // Unhandled attribute type - warn + HOUDINI_LOG_WARNING( + TEXT("HapiCreateCurveInputNodeForData() - Unhandled attribute type - skipping") + TEXT("- consider disabling additionnal rot/scale if having issues with the HDA")); continue; + } + } } } @@ -961,8 +1002,6 @@ FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( // And cook it with refinement enabled CookOptions.refineCurveToLinear = true; - //HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - // FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, false)) return false; #endif @@ -1038,6 +1077,9 @@ FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(co HoudiniSplineComponent->RegisterComponent(); HoudiniSplineComponent->AttachToComponent(SceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + // Delete the curve points so that UpdateHoudiniCurves initializes them from HAPI + HoudiniSplineComponent->CurvePoints.Empty(); + HoudiniSplineComponent->DisplayPoints.Empty(); UpdateHoudiniCurve(HoudiniSplineComponent); ReselectSelectedActors(); @@ -1475,6 +1517,57 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( */ } + // Cache commonly supported Houdini attributes on the OutputAttributes + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, BakeOutputActorNames)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(InHGPO.GeoId, BakeFolders, InHGPO.PartId)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, BakeOutlinerFolders)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + if (bReusedPreviousOutput) { // Remove the reused output unreal spline from the old map to avoid its deletion diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h index 078696156..2c90a3ffe 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h @@ -57,11 +57,7 @@ struct HOUDINIENGINE_API FHoudiniSplineTranslator static void UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC); // Upload Houdini spline component data to the curve node, and then sync the Houdini Spline Component with the curve node. - static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent); - - // Create a new curve node. - static bool HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniSplineComponent* SplineComponent); + static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent, bool bInSetRotAndScaleAttributes); // Update the curve node data, or create a new curve node if the CurveNodeId is valid. static bool HapiCreateCurveInputNodeForData( diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp index 21c57a62a..b4133609e 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp @@ -26,8 +26,10 @@ #include "HoudiniStringResolver.h" #include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntime.h" #include "HoudiniEnginePrivatePCH.h" +#include "HAL/FileManager.h" void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutTokens) const { @@ -37,9 +39,19 @@ void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutToke } } +FString FHoudiniStringResolver::SanitizeTokenValue(const FString& InValue) +{ + // Replace {} characters with __ + FString OutString = InValue; + OutString.ReplaceInline(ANSI_TO_TCHAR("{"), ANSI_TO_TCHAR("__")); + OutString.ReplaceInline(ANSI_TO_TCHAR("}"), ANSI_TO_TCHAR("__")); + + return OutString; +} + void FHoudiniStringResolver::SetToken(const FString& InName, const FString& InValue) { - CachedTokens.Add(InName, InValue); + CachedTokens.Add(InName, SanitizeTokenValue(InValue)); } void FHoudiniStringResolver::SetTokensFromStringMap(const TMap& InTokens, bool bClearTokens) @@ -51,7 +63,7 @@ void FHoudiniStringResolver::SetTokensFromStringMap(const TMap for (auto& Elem : InTokens) { - CachedTokens.Add(Elem.Key, Elem.Value); + CachedTokens.Add(Elem.Key, SanitizeTokenValue(Elem.Value)); } } @@ -150,3 +162,58 @@ FString FHoudiniAttributeResolver::ResolveOutputName() const return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); } +FString FHoudiniAttributeResolver::ResolveBakeFolder() const +{ + const FString DefaultBakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + FString BakeFolder = ResolveAttribute(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, TEXT("{bake}")); + if (BakeFolder.IsEmpty()) + return DefaultBakeFolder; + + //if (BakeFolder.StartsWith("Game/")) + //{ + // BakeFolder = "/" + BakeFolder; + //} + + //FString AbsoluteOverridePath; + //if (BakeFolder.StartsWith("/Game/")) + //{ + // const FString RelativePath = FPaths::ProjectContentDir() + BakeFolder.Mid(6, BakeFolder.Len() - 6); + // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + //} + //else + //{ + // if (!BakeFolder.IsEmpty()) + // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolder); + //} + + //// Check Validity of the path + //if (AbsoluteOverridePath.IsEmpty()) + //{ + // return DefaultBakeFolder; + //} + + return BakeFolder; +} + +void FHoudiniAttributeResolver::LogCachedAttributesAndTokens() const +{ + TArray Lines; + Lines.Add(TEXT("==================")); + Lines.Add(TEXT("Cached Attributes:")); + Lines.Add(TEXT("==================")); + for (const auto& Entry : CachedAttributes) + { + Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value))); + } + + Lines.Add(TEXT("==============")); + Lines.Add(TEXT("Cached Tokens:")); + Lines.Add(TEXT("==============")); + for (const auto& Entry : CachedTokens) + { + Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value.StringValue))); + } + + HOUDINI_LOG_DISPLAY(TEXT("%s"), *FString::Join(Lines, TEXT("\n"))); +} diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.h b/Source/HoudiniEngine/Private/HoudiniStringResolver.h index dae37b1e3..bedf2768d 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.h +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.h @@ -52,7 +52,10 @@ struct HOUDINIENGINE_API FHoudiniStringResolver // Set a named argument that will be used for argument replacement during GetAttribute calls. void SetToken(const FString& InName, const FString& InValue); - + + // Sanitize a token value. Currently only replaces { and } with __. + static FString SanitizeTokenValue(const FString& InValue); + void GetTokensAsStringMap(TMap& OutTokens) const; void SetTokensFromStringMap(const TMap& InValue, bool bClearTokens=true); @@ -98,4 +101,12 @@ struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolv // Helper for resolver custom output name attributes. FString ResolveOutputName() const; + // Helper for resolving the unreal_bake_folder attribute. Converts to an absolute path. + FString ResolveBakeFolder() const; + + // ---------------------------------- + // Debug + // ---------------------------------- + // Logs the resolver's cached attributes and tokens + void LogCachedAttributesAndTokens() const; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp index 791526a38..0e187ca0d 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp @@ -318,7 +318,7 @@ FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( //-------------------------------------------------------------------------------------------------- // Set the Height volume's data HAPI_PartId PartId = 0; - if (!SetHeighfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) + if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) return false; // Add the materials used @@ -414,7 +414,7 @@ FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( // 4. Set the layer/mask heighfield data in Houdini HAPI_PartId CurrentPartId = 0; - if (!SetHeighfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) + if (!SetHeightfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) continue; // Get the physical material used by that layer @@ -886,7 +886,7 @@ FUnrealLandscapeTranslator::CreateHeightfieldInputNode( } bool -FUnrealLandscapeTranslator::SetHeighfieldData( +FUnrealLandscapeTranslator::SetHeightfieldData( const HAPI_NodeId& VolumeNodeId, const HAPI_PartId& PartId, TArray& FloatValues, @@ -1232,7 +1232,7 @@ FUnrealLandscapeTranslator::InitDefaultHeightfieldMask( // Set the heighfield data in Houdini FString MaskName = TEXT("mask"); HAPI_PartId PartId = 0; - if (!SetHeighfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) + if (!SetHeightfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) return false; return true; diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h index ee33bf81e..ea5803f41 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h @@ -95,7 +95,7 @@ struct HOUDINIENGINE_API FUnrealLandscapeTranslator HAPI_NodeId& MergeNodeId ); // Set the volume float value for a heightfield - static bool SetHeighfieldData( + static bool SetHeightfieldData( const HAPI_NodeId& AssetId, const HAPI_PartId& PartId, TArray< float >& FloatValues, diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI.h b/Source/HoudiniEngine/Public/HAPI/HAPI.h index 8ea30ffdd..a18cda255 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI.h @@ -1019,7 +1019,7 @@ HAPI_DECL HAPI_GetString( const HAPI_Session * session, /// HAPI_DECL HAPI_SetCustomString( const HAPI_Session * session, const char * string_value, - int * handle_value ); + HAPI_StringHandle * handle_value ); /// @brief Removes the specified string from the server /// and invalidates the handle @@ -1036,7 +1036,7 @@ HAPI_DECL HAPI_SetCustomString( const HAPI_Session * session, /// Handle of the string that was added /// HAPI_DECL HAPI_RemoveCustomString( const HAPI_Session * session, - const int string_handle ); + const HAPI_StringHandle string_handle ); /// @brief Gives back the length of the buffer needed to hold /// all the values null-separated for the given string @@ -4563,6 +4563,363 @@ HAPI_DECL HAPI_GetAttributeIntArrayData( const HAPI_Session * session, int * sizes_fixed_array, int start, int sizes_fixed_length ); +/// @brief Get attribute unsigned 8-bit integer data. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] stride +/// Specifies how many items to skip over for each element. +/// With a stride of -1, the stride will be set to +/// @c attr_info->tuple_size. Otherwise, the stride will be +/// set to the maximum of @c attr_info->tuple_size and +/// @c stride. +/// +/// @param[out] data_array +/// An unsigned 8-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeUInt8Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + HAPI_UInt8 * data_array, + int start, int length ); + +/// @brief Get array attribute unsigned 8-bit integer data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An unsigned 8-bit integer array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeUInt8ArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + HAPI_UInt8 * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + +/// @brief Get attribute 8-bit integer data. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] stride +/// Specifies how many items to skip over for each element. +/// With a stride of -1, the stride will be set to +/// @c attr_info->tuple_size. Otherwise, the stride will be +/// set to the maximum of @c attr_info->tuple_size and +/// @c stride. +/// +/// @param[out] data_array +/// An 8-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeInt8Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + HAPI_Int8 * data_array, + int start, int length ); + +/// @brief Get array attribute 8-bit integer data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An 8-bit integer array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeInt8ArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + HAPI_Int8 * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + +/// @brief Get attribute 16-bit integer data. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] stride +/// Specifies how many items to skip over for each element. +/// With a stride of -1, the stride will be set to +/// @c attr_info->tuple_size. Otherwise, the stride will be +/// set to the maximum of @c attr_info->tuple_size and +/// @c stride. +/// +/// @param[out] data_array +/// An 16-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeInt16Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + int stride, + HAPI_Int16 * data_array, + int start, int length ); + +/// @brief Get array attribute 16-bit integer data. +/// Each entry in an array attribute can have varying array lengths. +/// Therefore the array values are returned as a flat array, with +/// another sizes array containing the lengths of each array entry. +/// +/// @ingroup GeometryGetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[out] data_fixed_array +/// An 16-bit integer array at least the size of +/// ::HAPI_AttributeInfo::totalArrayElements. +/// +/// @param[in] data_fixed_length +/// Must be ::HAPI_AttributeInfo::totalArrayElements. +/// +/// +/// @param[out] sizes_fixed_array +/// An integer array at least the size of +/// sizes_fixed_length to hold the size of each entry. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] sizes_fixed_length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// Note, if 0 is passed for length, the function will just +/// do nothing and return ::HAPI_RESULT_SUCCESS. +/// +/// +HAPI_DECL HAPI_GetAttributeInt16ArrayData( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + HAPI_AttributeInfo * attr_info, + HAPI_Int16 * data_fixed_array, + int data_fixed_length, + int * sizes_fixed_array, + int start, int sizes_fixed_length ); + /// @brief Get attribute 64-bit integer data. /// /// @ingroup GeometryGetters Attributes @@ -5534,6 +5891,147 @@ HAPI_DECL HAPI_SetAttributeIntData( const HAPI_Session * session, const int * data_array, int start, int length ); +/// @brief Set unsigned 8-bit attribute integer data. +/// +/// @ingroup GeometrySetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An unsigned 8-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeUInt8Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const HAPI_UInt8 * data_array, + int start, int length ); + +/// @brief Set 8-bit attribute integer data. +/// +/// @ingroup GeometrySetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An 8-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeInt8Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const HAPI_Int8 * data_array, + int start, int length ); + +/// @brief Set 16-bit attribute integer data. +/// +/// @ingroup GeometrySetters Attributes +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// Currently not used. Just pass 0. +/// +/// @param[in] name +/// Attribute name. +/// +/// @param[in] attr_info +/// ::HAPI_AttributeInfo used as input for what tuple size. +/// you want. Also contains some sanity checks like +/// data type. Generally should be the same struct +/// returned by ::HAPI_GetAttributeInfo(). +/// +/// @param[in] data_array +/// An 16-bit integer array at least the size of +/// length * ::HAPI_AttributeInfo::tupleSize. +/// +/// @param[in] start +/// First index of range. Must be at least 0 and at +/// most ::HAPI_AttributeInfo::count - 1. +/// +/// +/// @param[in] length +/// Must be at least 0 and at most +/// ::HAPI_AttributeInfo::count - @p start. +/// +/// +HAPI_DECL HAPI_SetAttributeInt16Data( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * name, + const HAPI_AttributeInfo * attr_info, + const HAPI_Int16 * data_array, + int start, int length ); + /// @brief Set 64-bit attribute integer data. /// /// @ingroup GeometrySetters Attributes diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h index 7273fd16b..b0c825272 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h @@ -110,9 +110,31 @@ typedef char HAPI_Bool; #endif // __cplusplus -// 64-bit Integers -typedef long long HAPI_Int64; -HAPI_STATIC_ASSERT( sizeof( HAPI_Int64 ) == 8, unsupported_size_of_long ); +// x-bit Integers +// Thrift doesn't support unsigned integers, so we cast it as a 16-bit int, but only +// for automated code generation +#ifdef HAPI_AUTOGEN + typedef signed char int8_t; + typedef short int16_t; + typedef long long int64_t; + typedef short HAPI_UInt8; // 16-bit type for thrift +#else + #include + #ifdef HAPI_THRIFT_ABI + typedef int16_t HAPI_UInt8; + #else + typedef uint8_t HAPI_UInt8; + HAPI_STATIC_ASSERT(sizeof(HAPI_UInt8) == 1, unsupported_size_of_uint8); + #endif +#endif + +typedef int8_t HAPI_Int8; +HAPI_STATIC_ASSERT(sizeof(HAPI_Int8) == 1, unsupported_size_of_int8); +typedef int16_t HAPI_Int16; +HAPI_STATIC_ASSERT(sizeof(HAPI_Int16) == 2, unsupported_size_of_int16); +typedef int64_t HAPI_Int64; +HAPI_STATIC_ASSERT(sizeof(HAPI_Int64) == 8, unsupported_size_of_long); + // The process id has to be uint on Windows and int on any other platform. #if ( defined _WIN32 || defined WIN32 ) @@ -565,6 +587,9 @@ enum HAPI_StorageType HAPI_STORAGETYPE_FLOAT, HAPI_STORAGETYPE_FLOAT64, HAPI_STORAGETYPE_STRING, + HAPI_STORAGETYPE_UINT8, + HAPI_STORAGETYPE_INT8, + HAPI_STORAGETYPE_INT16, HAPI_STORAGETYPE_MAX }; HAPI_C_ENUM_TYPEDEF( HAPI_StorageType ) diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h index be6f3a1ac..c829454b0 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h @@ -27,7 +27,7 @@ // expecting to compile against. #define HAPI_VERSION_HOUDINI_MAJOR 18 #define HAPI_VERSION_HOUDINI_MINOR 5 -#define HAPI_VERSION_HOUDINI_BUILD 462 +#define HAPI_VERSION_HOUDINI_BUILD 532 #define HAPI_VERSION_HOUDINI_PATCH 0 // The two components of the Houdini Engine (marketed) version. @@ -40,6 +40,6 @@ // might no longer compile. Semantic changes to the methods will also // cause this version to increase. This number will be reset to 0 // every time the Houdini Engine version is bumped. -#define HAPI_VERSION_HOUDINI_ENGINE_API 1 +#define HAPI_VERSION_HOUDINI_ENGINE_API 2 #endif // __HAPI_VERSION_h__ diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp index 8ab4cdb45..6499e571c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp @@ -74,6 +74,14 @@ FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() { FHoudiniParameterDetails* ParamDetailsPtr = ParameterDetails.Get(); + for (auto& CurFloatRampCurveEditor : ParamDetailsPtr->CreatedFloatCurveEditors) + { + if (CurFloatRampCurveEditor.IsValid()) + { + CurFloatRampCurveEditor->HoudiniFloatRampCurve = nullptr; + CurFloatRampCurveEditor->SetCurveOwner(nullptr); + } + } for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) { if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) @@ -82,6 +90,14 @@ FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() CurFloatRampCurve->RemoveFromRoot(); } + for (auto& CurColorRampCurveEditor : ParamDetailsPtr->CreatedColorGradientEditors) + { + if (CurColorRampCurveEditor.IsValid()) + { + CurColorRampCurveEditor->HoudiniColorRampCurve = nullptr; + CurColorRampCurveEditor->SetCurveOwner(nullptr); + } + } for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) { if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) @@ -89,7 +105,9 @@ FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() CurColorRampCurve->RemoveFromRoot(); } - + + ParamDetailsPtr->CreatedFloatCurveEditors.Empty(); + ParamDetailsPtr->CreatedColorGradientEditors.Empty(); ParamDetailsPtr->CreatedFloatRampCurves.Empty(); ParamDetailsPtr->CreatedColorRampCurves.Empty(); } @@ -128,6 +146,115 @@ FHoudiniAssetComponentDetails::AddIndieLicenseRow(IDetailCategoryBuilder& InCate ]; } + +void +FHoudiniAssetComponentDetails::AddSessionStatusRow(IDetailCategoryBuilder& InCategory) +{ + FDetailWidgetRow& PDGStatusRow = InCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetSessionStatusAndColor(StatusString, StatusColor); + return FText::FromString(StatusString); + }) + .ColorAndOpacity_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetSessionStatusAndColor(StatusString, StatusColor); + return FSlateColor(StatusColor); + }) + ] + ]; +} + +bool +FHoudiniAssetComponentDetails::GetSessionStatusAndColor( + FString& OutStatusString, FLinearColor& OutStatusColor) +{ + OutStatusString = FString(); + OutStatusColor = FLinearColor::White; + + const EHoudiniSessionStatus& SessionStatus = FHoudiniEngine::Get().GetSessionStatus(); + + switch (SessionStatus) + { + case EHoudiniSessionStatus::NotStarted: + // Session not initialized yet + OutStatusString = TEXT("Houdini Engine Session - Not Started"); + OutStatusColor = FLinearColor::White; + break; + + case EHoudiniSessionStatus::Connected: + // Session successfully started + OutStatusString = TEXT("Houdini Engine Session READY"); + OutStatusColor = FLinearColor::Green; + break; + case EHoudiniSessionStatus::Stopped: + // Session stopped + OutStatusString = TEXT("Houdini Engine Session STOPPED"); + OutStatusColor = FLinearColor(1.0f, 0.5f, 0.0f); + break; + case EHoudiniSessionStatus::Failed: + // Session failed to be created/connected + OutStatusString = TEXT("Houdini Engine Session FAILED"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::Lost: + // Session Lost (HARS/Houdini Crash?) + OutStatusString = TEXT("Houdini Engine Session LOST"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::NoLicense: + // Failed to acquire a license + OutStatusString = TEXT("Houdini Engine Session FAILED - No License"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::None: + // Session type set to None + OutStatusString = TEXT("Houdini Engine Session DISABLED"); + OutStatusColor = FLinearColor::White; + break; + default: + case EHoudiniSessionStatus::Invalid: + OutStatusString = TEXT("Houdini Engine Session INVALID"); + OutStatusColor = FLinearColor::Red; + break; + } + + // Handle a few specific case for active session + if (SessionStatus == EHoudiniSessionStatus::Connected) + { + bool bPaused = !FHoudiniEngine::Get().IsCookingEnabled(); + bool bSSync = FHoudiniEngine::Get().IsSessionSyncEnabled(); + if (bPaused) + { + OutStatusString = TEXT("Houdini Engine Session PAUSED"); + OutStatusColor = FLinearColor::Yellow; + } + /* + else if (bSSync) + { + OutStatusString = TEXT("Houdini Engine Session Sync READY"); + OutStatusColor = FLinearColor::Blue; + } + */ + } + + return true; +} + void FHoudiniAssetComponentDetails::AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC) { diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h index c908ea27b..f640fb21b 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h @@ -52,6 +52,11 @@ class FHoudiniAssetComponentDetails : public IDetailCustomization // Create an instance of this detail layout class. static TSharedRef MakeInstance(); + // Adds a text row that indicate the status of the Houdini Session + static void AddSessionStatusRow(IDetailCategoryBuilder& InCategory); + + static bool GetSessionStatusAndColor(FString& OutStatusString, FLinearColor& OutStatusColor); + private: // Adds a text row indicate we're using a Houdini indie license diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp index 3ee39bf1c..6a752e8ca 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp @@ -91,6 +91,9 @@ #include "Particles/ParticleSystemComponent.h" #include "Sound/SoundBase.h" #include "UObject/UnrealType.h" +#include "Math/Box.h" + +HOUDINI_BAKING_DEFINE_LOG_CATEGORY(); #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE @@ -101,6 +104,8 @@ FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() , ActorBakeName(NAME_None) , BakedObject(nullptr) , SourceObject(nullptr) + , BakeFolderPath() + , bInstancerOutput(false) { } @@ -111,7 +116,10 @@ FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( int32 InOutputIndex, const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, UObject* InBakedObject, - UObject* InSourceObject) + UObject* InSourceObject, + UObject* InBakedComponent, + const FString& InBakeFolderPath, + const FHoudiniPackageParams& InBakedObjectPackageParams) : Actor(InActor) , OutputIndex(InOutputIndex) , OutputObjectIdentifier(InOutputObjectIdentifier) @@ -119,6 +127,10 @@ FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( , WorldOutlinerFolder(InWorldOutlinerFolder) , BakedObject(InBakedObject) , SourceObject(InSourceObject) + , BakedComponent(InBakedComponent) + , BakeFolderPath(InBakeFolderPath) + , BakedObjectPackageParams(InBakedObjectPackageParams) + , bInstancerOutput(false) { } @@ -171,7 +183,10 @@ FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( } if (bSuccess && bInRemoveHACOutputOnSuccess) - FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake); + { + TArray DeferredClearOutputs; + FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake, DeferredClearOutputs, true); + } return bSuccess; } @@ -263,6 +278,7 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( BakedOutputs.SetNum(NumOutputs); return BakeHoudiniOutputsToActors( + HoudiniAssetComponent, Outputs, BakedOutputs, HoudiniAssetName, @@ -282,6 +298,7 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( bool FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, const TArray& InOutputs, TArray& InBakedOutputs, const FString& InHoudiniAssetName, @@ -306,6 +323,10 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( TArray BakedActors; + // Ensure that InBakedOutputs is the same size as InOutputs + if (InBakedOutputs.Num() != NumOutputs) + InBakedOutputs.SetNum(NumOutputs); + // First bake everything except instancers, then bake instancers. Since instancers might use meshes in // from the other outputs. bool bHasAnyInstancers = false; @@ -335,6 +356,7 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( case EHoudiniOutputType::Mesh: { FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + HoudiniAssetComponent, OutputIdx, InOutputs, InBakedOutputs, @@ -361,9 +383,10 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( case EHoudiniOutputType::Landscape: { const bool bResult = BakeLandscape( + HoudiniAssetComponent, OutputIdx, - Output, - InBakedOutputs[OutputIdx].BakedOutputObjects, + InOutputs, + InBakedOutputs, bInReplaceActors, bInReplaceAssets, InBakeFolder.Path, @@ -378,8 +401,9 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( case EHoudiniOutputType::Curve: { FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - Output, - InBakedOutputs[OutputIdx].BakedOutputObjects, + HoudiniAssetComponent, + OutputIdx, + InOutputs, InBakedOutputs, InHoudiniAssetName, InBakeFolder, @@ -415,10 +439,12 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( if (Output->GetType() == EHoudiniOutputType::Instancer) { FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + HoudiniAssetComponent, OutputIdx, InOutputs, InBakedOutputs, InParentTransform, + InHoudiniAssetName, InBakeFolder, InTempCookFolder, bInReplaceActors, @@ -439,6 +465,242 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( return true; } +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave) +{ + UHoudiniOutput* Output = InAllOutputs[InOutputIndex]; + if (!Output || Output->IsPendingKill()) + return false; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + return false; + + if (!IsValid(InOutputObject.OutputComponent)) + return false; + + UStaticMeshComponent* SMC = Cast(InOutputObject.OutputComponent); + if (!IsValid(SMC)) + { + HOUDINI_LOG_WARNING( + TEXT("Unsupported component for foliage: %s"),*(InOutputObject.OutputComponent->GetClass()->GetName())); + return false; + } + + UStaticMesh* InstancedStaticMesh = SMC->GetStaticMesh(); + if (!IsValid(InstancedStaticMesh)) + { + // No mesh, skip this instancer + return false; + } + + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets + ? EPackageReplaceMode::ReplaceExistingAssets + : EPackageReplaceMode::CreateNewAssets; + UWorld* DesiredWorld = Output ? Output->GetWorld() : GWorld; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + const bool bFoundMeshOutput = FindOutputObject(InstancedStaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + InHoudiniAssetName, MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + } + else + { + BakedStaticMesh = InstancedStaticMesh; + } + + // Update the baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // const FString InstancerName = FString::Printf(TEXT("%s_foliage_%s"), *ObjectName, *(InOutputObjectIdentifier.SplitIdentifier)); + // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone + // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the + // package params. + FHoudiniPackageParams InstancerPackageParams; + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + InHoudiniAssetName, InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + HOUDINI_LOG_ERROR(TEXT("Could not find or create a level: %s"), *LevelPackagePath); + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Get foliage actor for the level + const bool bCreateIfNone = true; + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, bCreateIfNone); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Could not find or create an instanced foliage actor for level %s"), *(DesiredLevel->GetPathName())); + return false; + } + + // Get the previous bake data for this instancer + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // Foliage type is replaced in replacement mode if: + // the previous baked object is this foliage type + // and we haven't bake this foliage type during this bake (BakeResults) + // NOTE: foliage type is only recorded as the previous bake object if we created the foliage type + // TODO: replacement mode should probably only affect the instances themselves and not the foliage type + // since the foliage type is already linked to whatever mesh we are using (which will be replaced + // incremented already). To track instances it looks like we would have to use the locations of the + // baked instances (likely cannot use the indices, since the user might modify/add/remove instances + // after the bake). + + // See if we already have a FoliageType for that static mesh + UFoliageType* FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(BakedStaticMesh); + if (!FoliageType || FoliageType->IsPendingKill()) + { + // We need to create a new FoliageType for this Static Mesh + // TODO: Add foliage default settings + InstancedFoliageActor->AddMesh(BakedStaticMesh, &FoliageType); + // Update the previous bake results with the foliage type we created + InBakedOutputObject.BakedComponent = FSoftObjectPath(FoliageType).ToString(); + } + else + { + const FString FoliageTypePath = FSoftObjectPath(FoliageType).ToString(); + if (bInReplaceAssets && InBakedOutputObject.BakedComponent == FoliageTypePath && + !OutActors.FindByPredicate([FoliageType](const FHoudiniEngineBakedActor& Entry) { return Entry.BakedComponent == FoliageType; })) + { + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + // Update the previous bake results with the foliage type + InBakedOutputObject.BakedComponent = FoliageTypePath; + } + else + { + // If we didn't create the foliage type, don't set the baked component + InBakedOutputObject.BakedComponent.Empty(); + } + } + + // Record the foliage bake in the current results + FHoudiniEngineBakedActor& NewResult = OutActors.Add_GetRef(FHoudiniEngineBakedActor()); + NewResult.OutputIndex = InOutputIndex; + NewResult.OutputObjectIdentifier = InOutputObjectIdentifier; + NewResult.SourceObject = InstancedStaticMesh; + NewResult.BakedObject = BakedStaticMesh; + NewResult.BakedComponent = FoliageType; + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if (!FoliageInfo) + return false; + + int32 CurrentInstanceCount = 0; + if (SMC->IsA()) + { + UInstancedStaticMeshComponent* ISMC = Cast(SMC); + const int32 NumInstances = ISMC->GetInstanceCount(); + for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex) + { + FTransform InstanceTransform; + const bool bWorldSpace = true; + if (ISMC->GetInstanceTransform(InstanceIndex, InstanceTransform, bWorldSpace)) + { + FFoliageInstance FoliageInstance; + FoliageInstance.Location = InstanceTransform.GetLocation(); + FoliageInstance.Rotation = InstanceTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = InstanceTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + + CurrentInstanceCount++; + } + } + } + else + { + const FTransform ComponentToWorldTransform = SMC->GetComponentToWorld(); + FFoliageInstance FoliageInstance; + FoliageInstance.Location = ComponentToWorldTransform.GetLocation(); + FoliageInstance.Rotation = ComponentToWorldTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = ComponentToWorldTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + + CurrentInstanceCount++; + } + + // TODO: This was due to a bug in UE4.22-20, check if still needed! + if (FoliageInfo->GetComponent()) + FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); + + // Notify the user that we succesfully bake the instances to foliage + FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + BakedStaticMesh->GetName() + TEXT(" to Foliage"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + InstancedFoliageActor->RegisterAllComponents(); + + // Update / repopulate the foliage editor mode's mesh list + if (CurrentInstanceCount > 0) + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + + return true; +} bool FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) @@ -480,212 +742,84 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* Houdi if (!OwnerActor || OwnerActor->IsPendingKill()) return false; - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return false; - - int32 BakedCount = 0; TArray PackagesToSave; + TArray BakedResults; FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); + const FString HoudiniAssetName = OwnerActor->GetName(); // Build an array of the outputs so that we can search for meshes/previous baked meshes - const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); TArray Outputs; - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); - if (!Output || Output->IsPendingKill()) - continue; - - Outputs.Add(Output); - } - + HoudiniAssetComponent->GetOutputs(Outputs); + const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); + // Get the previous bake outputs and match the output array size TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); if (BakedOutputs.Num() != NumOutputs) BakedOutputs.SetNum(NumOutputs); + bool bSuccess = true; // Map storing original and baked Static Meshes - TMap< const UStaticMesh*, UStaticMesh* > OriginalToBakedMesh; for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(OutputIdx); + UHoudiniOutput* Output = Outputs[OutputIdx]; if (!Output || Output->IsPendingKill()) continue; if (Output->GetType() != EHoudiniOutputType::Instancer) continue; - // TODO: No need to use the instanced outputs for this - // We should simply iterate on the Output Objects instead! TMap& OutputObjects = Output->GetOutputObjects(); - TMap& InstancedOutputs = Output->GetInstancedOutputs(); - for (auto & Pair : InstancedOutputs) + const TMap& OldBakedOutputObjects = BakedOutputs[OutputIdx].BakedOutputObjects; + TMap NewBakedOutputObjects; + + for (auto & Pair : OutputObjects) { - FString InstanceName = OwnerActor->GetName(); - - // // See if we have a bake name for that output - // FHoudiniOutputObject* OutputObj = OutputObjects.Find(Pair.Key); - // if (OutputObj && OutputObj->BakeName.IsEmpty()) - // InstanceName = OutputObj->BakeName; - - FHoudiniInstancedOutput& InstancedOutput = Pair.Value; - for (int32 VariarionIdx = 0; VariarionIdx < InstancedOutput.VariationObjects.Num(); ++VariarionIdx) - { - // TODO: !!! what if the instanced object/var is not a static mesh!!!!!! - UObject* CurrentVariationObject = InstancedOutput.VariationObjects[VariarionIdx].Get(); - UStaticMesh* InstancedStaticMesh = Cast(CurrentVariationObject); - if (!InstancedStaticMesh) - { - if (CurrentVariationObject) - { - HOUDINI_LOG_ERROR(TEXT("Failed to bake the instances of %s to Foliage"), *CurrentVariationObject->GetName()); - } - continue; - } - - // Check if we have already handled this mesh (already baked it from a previous variation), if so, - // use that - UStaticMesh* OutStaticMesh = nullptr; - bool bCreateNewType = true; - if (OriginalToBakedMesh.Contains(InstancedStaticMesh)) - { - OutStaticMesh = OriginalToBakedMesh.FindChecked(InstancedStaticMesh); - bCreateNewType = false; - } - - if (!IsValid(OutStaticMesh)) - { - // Find the output object and identifier for the mesh and previous bake of the mesh (if it exists) - FString ObjectName; - int32 MeshOutputIdx = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshOutputIdentifier; - UStaticMesh* PreviousBakeMesh = nullptr; - FHoudiniBakedOutputObject* BakedOutputObject = nullptr; - if (FindOutputObject(InstancedStaticMesh, Outputs, MeshOutputIdx, MeshOutputIdentifier)) - { - GetTemporaryOutputObjectBakeName(InstancedStaticMesh, Outputs, ObjectName); - - BakedOutputObject = &BakedOutputs[MeshOutputIdx].BakedOutputObjects.FindOrAdd(MeshOutputIdentifier); - if (BakedOutputObject) - { - PreviousBakeMesh = Cast(BakedOutputObject->GetBakedObjectIfValid()); - } - } - else - { - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); - } - - // If the instanced static mesh is still a temporary Houdini created Static Mesh - // we will duplicate/bake it first before baking to foliage - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - MeshOutputIdentifier, - HoudiniAssetComponent->BakeFolder.Path, - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // DuplicateStaticMeshAndCreatePackageIfNeeded uses baked results to find a baked version of - // InstancedStaticMesh in the current bake results, but since we are already using - // OriginalToBakedMesh we don't have to populate BakedResults - const TArray BakedResults; - OutStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - InstancedStaticMesh, - PreviousBakeMesh, - PackageParams, - Outputs, - BakedResults, - HoudiniAssetComponent->TemporaryCookFolder.Path, - PackagesToSave); - OriginalToBakedMesh.Add(InstancedStaticMesh, OutStaticMesh); - - // Update our tracked baked output - if (BakedOutputObject) - BakedOutputObject->BakedObject = FSoftObjectPath(OutStaticMesh).ToString(); - - bCreateNewType = true; - } - - // See if we already have a FoliageType for that static mesh - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(OutStaticMesh); - if (!FoliageType || FoliageType->IsPendingKill()) - { - // We need to create a new FoliageType for this Static Mesh - // TODO: Add foliage default settings - InstancedFoliageActor->AddMesh(OutStaticMesh, &FoliageType); - bCreateNewType = true; - } - - // If we are baking in replace mode, remove the foliage type if it already exists - // and a create a new one - if (bInReplaceAssets && bCreateNewType && IsValid(FoliageType)) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it - FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); - if (!FoliageInfo) - continue; - - // Apply the transform offset on the transforms for this variation - TArray ProcessedTransforms; - FHoudiniInstanceTranslator::ProcessInstanceTransforms(InstancedOutput, VariarionIdx, ProcessedTransforms); - - FFoliageInstance FoliageInstance; - int32 CurrentInstanceCount = 0; - for (auto CurrentTransform : ProcessedTransforms) - { - FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); - FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - CurrentInstanceCount++; - } - - // TODO: This was due to a bug in UE4.22-20, check if still needed! - if (FoliageInfo->GetComponent()) - FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniOutputObject& OutputObject = Pair.Value; - // Notify the user that we succesfully bake the instances to foliage - FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + OutStaticMesh->GetName() + TEXT(" to Foliage"); - FHoudiniEngineUtils::CreateSlateNotification(Notification); + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); - BakedCount += ProcessedTransforms.Num(); - } + const bool bInReplaceActors = false; + bSuccess &= BakeInstancerOutputToFoliage( + HoudiniAssetComponent, + OutputIdx, + Outputs, + Identifier, + OutputObject, + BakedOutputObject, + HoudiniAssetName, + HoudiniAssetComponent->BakeFolder, + HoudiniAssetComponent->TemporaryCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedResults, + PackagesToSave); } - } - InstancedFoliageActor->RegisterAllComponents(); + // Update the cached baked output data + BakedOutputs[OutputIdx].BakedOutputObjects = NewBakedOutputObjects; + } - // Update / repopulate the foliage editor mode's mesh list - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - - if (BakedCount > 0) + if (PackagesToSave.Num() > 0) { FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - return true; } - return false; + return bSuccess; } bool FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, const FTransform& InTransform, + const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, @@ -703,17 +837,23 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( if (!InOutput || InOutput->IsPendingKill()) return false; - TMap& OutputObjects = InOutput->GetOutputObjects(); - // Ensure we have the same number of baked outputs and asset outputs if (InBakedOutputs.Num() != InAllOutputs.Num()) InBakedOutputs.SetNum(InAllOutputs.Num()); + + TMap& OutputObjects = InOutput->GetOutputObjects(); + const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + TMap NewBakedOutputObjects; // Iterate on the output objects, baking their object/component as we go for (auto& Pair : OutputObjects) { + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; FHoudiniOutputObject& CurrentOutputObject = Pair.Value; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputs[InOutputIndex].BakedOutputObjects.FindOrAdd(Pair.Key); + + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); if (CurrentOutputObject.bProxyIsCurrent) { @@ -726,12 +866,53 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( if (CurrentOutputObject.OutputComponent->IsA()) { - // TODO: Baking foliage instancer to actors it not supported currently + // Bake foliage as foliage + if (!InInstancerComponentTypesToBake || + InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageInstancedStaticMeshComponent)) + { + BakeInstancerOutputToFoliage( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InHoudiniAssetName, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave); + } + else if (!InInstancerComponentTypesToBake || + InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent)) + { + BakeInstancerOutputToActors_ISMC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } } else if (CurrentOutputObject.OutputComponent->IsA() && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent))) { BakeInstancerOutputToActors_ISMC( + HoudiniAssetComponent, InOutputIndex, InAllOutputs, // InBakedOutputs, @@ -752,6 +933,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent))) { BakeInstancerOutputToActors_IAC( + HoudiniAssetComponent, InOutputIndex, Pair.Key, CurrentOutputObject, @@ -766,6 +948,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) { BakeInstancerOutputToActors_MSIC( + HoudiniAssetComponent, InOutputIndex, InAllOutputs, // InBakedOutputs, @@ -786,6 +969,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) { BakeInstancerOutputToActors_SMC( + HoudiniAssetComponent, InOutputIndex, InAllOutputs, // InBakedOutputs, @@ -808,11 +992,15 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( } + // Update the cached baked output data + InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; + return true; } bool FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -841,61 +1029,66 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( if (!StaticMesh || StaticMesh->IsPendingKill()) return false; - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + OwnerActor->GetName(), MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + } + else { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + BakedStaticMesh = StaticMesh; } + // Update the baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) const FString BaseName = OwnerActor->GetName(); const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // ObjectName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, PackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone + // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the + // package params. + FHoudiniPackageParams InstancerPackageParams; + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); - // Update the baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - // By default spawn in the current level unless specified via the unreal_level_path attribute ULevel* DesiredLevel = GWorld->GetCurrentLevel(); bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); bool bCreatedPackage = false; if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( @@ -968,10 +1161,10 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( continue; } - FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName, FoundActor); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName.ToString(), FoundActor); // FoundActor->Rename(*NewName.ToString()); // FoundActor->SetActorLabel(NewName.ToString()); - RenameAndRelabelActor(FoundActor, NewName.ToString(), false); + RenameAndRelabelActor(FoundActor, NewNameStr, false); // The folder is named after the original actor and contains all generated actors SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); @@ -983,14 +1176,19 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( // Copy properties from the existing component CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC); - OutActors.Add(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, BakedStaticMesh, - StaticMesh)); + StaticMesh, + SMActor->GetStaticMeshComponent(), + bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; } } else @@ -1002,7 +1200,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( FActorSpawnParameters SpawnInfo; SpawnInfo.OverrideLevel = DesiredLevel; SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); + SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); SpawnInfo.bDeferConstruction = true; // Spawn the new Actor @@ -1024,8 +1222,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( RemovePreviouslyBakedComponent(InPrevComponent); } - const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); + const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); } // The folder is named after the original actor and contains all generated actors @@ -1042,17 +1240,29 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast(InISMC); if (InHISMC) { - NewISMC = DuplicateObject( - InHISMC, - FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetFName())); + // Handle foliage: don't duplicate foliage component, create a new hierarchical one and copy what we can + // from the foliage component + if (InHISMC->IsA()) + { + NewISMC = NewObject( + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); + CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); + } + else + { + NewISMC = DuplicateObject( + InHISMC, + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); + } } else { NewISMC = DuplicateObject( InISMC, FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetFName())); + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetName()))); } if (!NewISMC) @@ -1080,14 +1290,19 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( FoundActor->FinishSpawning(InTransform); InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, BakedStaticMesh, - StaticMesh)); + StaticMesh, + NewISMC, + bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; FoundActor->InvalidateLightingCache(); FoundActor->PostEditMove(true); @@ -1109,6 +1324,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( bool FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -1136,15 +1352,44 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( if (!StaticMesh || StaticMesh->IsPendingKill()) return false; - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + OwnerActor->GetName(), MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + } + else { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + BakedStaticMesh = StaticMesh; } + // Update the previous baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + // BaseName holds the Actor / HDA name // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) const FString BaseName = OwnerActor->GetName(); @@ -1153,45 +1398,22 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( // See if the instanced static mesh is still a temporary Houdini created Static Mesh // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, - OutActors, InTempCookFolder.Path, OutPackagesToSave); - - // Update the previous baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + FHoudiniPackageParams InstancerPackageParams; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); // By default spawn in the current level unless specified via the unreal_level_path attribute ULevel* DesiredLevel = GWorld->GetCurrentLevel(); bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); bool bCreatedPackage = false; if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( @@ -1269,10 +1491,10 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( } } - FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName, FoundActor); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName.ToString(), FoundActor); // FoundActor->Rename(*NewName.ToString()); // FoundActor->SetActorLabel(NewName.ToString()); - RenameAndRelabelActor(FoundActor, NewName.ToString(), false); + RenameAndRelabelActor(FoundActor, NewNameStr, false); // The folder is named after the original actor and contains all generated actors SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); @@ -1284,19 +1506,25 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( return false; // Copy properties from the existing component - CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC); + const bool bCopyWorldTransform = true; + CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC, bCopyWorldTransform); StaticMeshComponent->SetStaticMesh(BakedStaticMesh); InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, BakedStaticMesh, - StaticMesh)); - + StaticMesh, + StaticMeshComponent, + MeshPackageParams.BakeFolder, + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + // If we are baking in replace mode, remove previously baked components/instancers if (bInReplaceActors && bInReplaceAssets) { @@ -1312,6 +1540,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( bool FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, const FHoudiniOutputObject& InOutputObject, @@ -1341,31 +1570,21 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( FHoudiniPackageParams PackageParams; const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - BaseName.ToString(), - OwnerActor->GetName(), - AssetPackageReplaceMode); + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, BaseName.ToString(), + OwnerActor->GetName(), PackageParams, Resolver, + InBakeFolder.Path, AssetPackageReplaceMode); // By default spawn in the current level unless specified via the unreal_level_path attribute - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; ULevel* DesiredLevel = GWorld->GetCurrentLevel(); bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) { - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // Get the package path from the unreal_level_apth attribute FString LevelPackagePath = Resolver.ResolveFullLevelPath(); @@ -1428,8 +1647,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) continue; - FName NewInstanceName = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName); - FString NewNameStr = NewInstanceName.ToString(); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName.ToString()); FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); @@ -1450,14 +1668,19 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); - OutActors.Add(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( NewActor, BaseName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, nullptr, - InstancedObject)); + InstancedObject, + nullptr, + PackageParams.BakeFolder, + PackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = PackageParams; } // TODO: @@ -1478,6 +1701,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( bool FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -1506,15 +1730,44 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( if (!StaticMesh || StaticMesh->IsPendingKill()) return false; - // Find the incoming mesh in the output (only if its temporary) and get its bake name. If not temporary, get its - // name from its package. - FString ObjectName; - if (!GetTemporaryOutputObjectBakeName(StaticMesh, InAllOutputs, ObjectName)) + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + OwnerActor->GetName(), MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + } + else { - // Not found in HDA/temp outputs, use its package name - ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + BakedStaticMesh = StaticMesh; } + // Update the baked output + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) const FString BaseName = OwnerActor->GetName(); const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; @@ -1522,47 +1775,23 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( // See if the instanced static mesh is still a temporary Houdini created Static Mesh // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - InOutputObjectIdentifier, - InBakeFolder.Path, - // BaseName + "_" + FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh), - ObjectName, - OwnerActor->GetName(), - AssetPackageReplaceMode); + FHoudiniPackageParams InstancerPackageParams; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, Cast(InBakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, - OutActors, InTempCookFolder.Path, OutPackagesToSave); - - // Update the baked output - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - // By default spawn in the current level unless specified via the unreal_level_path attribute ULevel* DesiredLevel = GWorld->GetCurrentLevel(); bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) { - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Get the level specified by attribute - // Access some of the attributes that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); bool bCreatedPackage = false; if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( @@ -1601,7 +1830,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( FActorSpawnParameters SpawnInfo; SpawnInfo.OverrideLevel = DesiredLevel; SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName); + SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); SpawnInfo.bDeferConstruction = true; // Spawn the new Actor @@ -1630,8 +1859,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( RemovePreviouslyBakedComponent(PrevComponent); } - const FName UniqueActorName = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName, FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorName.ToString(), false); + const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); } // The folder is named after the original actor and contains all generated actors SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); @@ -1654,7 +1883,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( UStaticMeshComponent* NewSMC = DuplicateObject( CurrentSMC, FoundActor, - MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetFName())); + FName(MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetName()))); if (!NewSMC || NewSMC->IsPendingKill()) continue; @@ -1677,14 +1906,19 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( FoundActor->FinishSpawning(InTransform); InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, BakedStaticMesh, - StaticMesh)); + StaticMesh, + nullptr, + MeshPackageParams.BakeFolder, + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; FoundActor->InvalidateLightingCache(); FoundActor->PostEditMove(true); @@ -1759,6 +1993,7 @@ FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( bool FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( const UObject* InObject, + EHoudiniOutputType InOutputType, const TArray& InAllOutputs, FString& OutBakeName) { @@ -1769,7 +2004,7 @@ FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( int32 MeshOutputIdx = INDEX_NONE; FHoudiniOutputObjectIdentifier MeshIdentifier; - if (FindOutputObject(InObject, InAllOutputs, MeshOutputIdx, MeshIdentifier)) + if (FindOutputObject(InObject, InOutputType, InAllOutputs, MeshOutputIdx, MeshIdentifier)) { // Found the mesh, get its name const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier); @@ -1783,6 +2018,7 @@ FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( bool FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, @@ -1796,6 +2032,10 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { + // Check that index is not negative + if (InOutputIndex < 0) + return false; + if (!InAllOutputs.IsValidIndex(InOutputIndex)) return false; @@ -1808,20 +2048,24 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( return false; TMap& OutputObjects = InOutput->GetOutputObjects(); -const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); + const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); // Get the previous bake objects - if (InOutputIndex >= 0 && !InBakedOutputs.IsValidIndex(InOutputIndex)) + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) InBakedOutputs.SetNum(InOutputIndex + 1); - TMap& BakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + + const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + TMap NewBakedOutputObjects; for (auto& Pair : OutputObjects) { const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; const FHoudiniOutputObject& OutputObject = Pair.Value; - // Fetch previous bake output - FHoudiniBakedOutputObject& BakedOutputObject = BakedOutputObjects.FindOrAdd(Identifier); + // Add a new baked output object entry and update it with the previous bake's data, if available + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); if (!StaticMesh || StaticMesh->IsPendingKill()) @@ -1840,54 +2084,29 @@ const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects( continue; FHoudiniAttributeResolver Resolver; - Resolver.SetCachedAttributes(OutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(OutputObject.CachedTokens); const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - // The default output name (if not set via attributes) is {object_name}, which look for an object_name - // key-value token - if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) - Resolver.SetToken(TEXT("object_name"), DefaultObjectName); - // The bake name override has priority - FString SMName = OutputObject.BakeName; - if (SMName.IsEmpty()) - { - // // ... finally the part name - // if (FoundHGPO && FoundHGPO->bHasCustomPartName) - // SMName = FoundHGPO->PartName; - // else - SMName = Resolver.ResolveOutputName(); - if (SMName.IsEmpty()) - SMName = DefaultObjectName; - } + UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); FHoudiniPackageParams PackageParams; // Set the replace mode based on if we are doing a replacement or incremental asset bake const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, Identifier, InBakeFolder.Path, SMName, - InHoudiniAssetName, AssetPackageReplaceMode); - + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, + InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); - UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - // See if this output object has an unreal_level_path attribute specified // In which case, we need to create/find the desired level for baking instead of using the current one bool bHasLevelPathAttribute = OutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) { - // Access some of the attribute that were cached on the output object - // FHoudiniAttributeResolver Resolver; - // const TMap& CachedAttributes = OutputObject.CachedAttributes; - TMap Tokens = OutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - // Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - - // Get the package path from the unreal_level_apth attribute + // Get the package path from the unreal_level_path attribute FString LevelPackagePath = Resolver.ResolveFullLevelPath(); bool bCreatedPackage = false; @@ -1982,8 +2201,7 @@ const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects( } // We need to make a unique name for the actor, renaming an object on top of another is a fatal error - const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName, FoundActor); - const FString NewNameStr = NewName.ToString(); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName.ToString(), FoundActor); // FoundActor->Rename(*NewNameStr); // FoundActor->SetActorLabel(NewNameStr); RenameAndRelabelActor(FoundActor, NewNameStr, false); @@ -1991,14 +2209,16 @@ const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects( if (IsValid(SMC)) { - CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC); + const bool bCopyWorldTransform = true; + CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC, bCopyWorldTransform); SMC->SetStaticMesh(BakedSM); BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString(); } BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh)); + FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh, SMC, + PackageParams.BakeFolder, PackageParams)); // If we are baking in replace mode, remove previously baked components/instancers if (bInReplaceActors && bInReplaceAssets) @@ -2011,14 +2231,18 @@ const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects( } } + // Update the cached baked output data + InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; + return true; } bool FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - UHoudiniOutput* Output, - TMap& InBakedOutputObjects, - const TArray& InAllBakedOutputs, + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, bool bInReplaceActors, @@ -2027,12 +2251,29 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { + // Check that index is not negative + if (InOutputIndex < 0) + return false; + + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; if (!Output || Output->IsPendingKill()) return false; TArray PackagesToSave; + // Find the previous baked output data for this output index. If an entry + // does not exist, create entries up to and including this output index + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; + const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; + TMap NewBakedOutputObjects; + const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); for (auto & Pair : OutputObjects) @@ -2042,8 +2283,10 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( if (!SplineComponent || SplineComponent->IsPendingKill()) continue; - FHoudiniOutputObjectIdentifier & Identifier = Pair.Key; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(Identifier); + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); // TODO: FIX ME!! May not work 100% const FHoudiniGeoPartObject* FoundHGPO = nullptr; @@ -2061,27 +2304,29 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( if (!FoundHGPO) continue; - FString CurveName = Pair.Value.BakeName; - if (CurveName.IsEmpty()) - { - if (FoundHGPO->bHasCustomPartName) - CurveName = FoundHGPO->PartName; - else - CurveName = InHoudiniAssetName + "_" + SplineComponent->GetName(); - } + const FString DefaultObjectName = InHoudiniAssetName + "_" + SplineComponent->GetName(); FHoudiniPackageParams PackageParams; // Set the replace mode based on if we are doing a replacement or incremental asset bake const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, Identifier, InBakeFolder.Path, CurveName, - InHoudiniAssetName, AssetPackageReplaceMode); - BakeCurve(OutputObject, BakedOutputObject, PackageParams, bInReplaceActors, bInReplaceAssets, OutActors, - PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* const DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, + InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); + + BakeCurve( + OutputObject, BakedOutputObject, PackageParams, Resolver, bInReplaceActors, bInReplaceAssets, + OutActors, PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); } + // Update the cached bake output results + BakedOutput.BakedOutputObjects = NewBakedOutputObjects; + SaveBakedPackages(PackagesToSave); return true; @@ -2215,6 +2460,7 @@ FHoudiniEngineBakeUtils::BakeBlueprints( EHoudiniInstancerComponentType::StaticMeshComponent, EHoudiniInstancerComponentType::InstancedStaticMeshComponent, EHoudiniInstancerComponentType::MeshSplitInstancerComponent, + EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent }; // When baking blueprints we always create new actors since they are deleted from the world once copied into the // blueprint @@ -2285,9 +2531,10 @@ FHoudiniEngineBakeUtils::BakeStaticMesh( bool FHoudiniEngineBakeUtils::BakeLandscape( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, - UHoudiniOutput* InOutput, - TMap& InBakedOutputObjects, + const TArray& InAllOutputs, + TArray& InBakedOutputs, bool bInReplaceActors, bool bInReplaceAssets, FString BakePath, @@ -2295,10 +2542,26 @@ FHoudiniEngineBakeUtils::BakeLandscape( FHoudiniEngineOutputStats& BakeStats ) { - if (!IsValid(InOutput)) + // Check that index is not negative + if (InOutputIndex < 0) return false; - TMap& OutputObjects = InOutput->GetOutputObjects(); + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; + if (!IsValid(Output)) + return false; + + // Find the previous baked output data for this output index. If an entry + // does not exist, create entries up to and including this output index + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; + const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; + TMap NewBakedOutputObjects; TArray PackagesToSave; TArray LandscapeWorldsToUpdate; @@ -2308,7 +2571,9 @@ FHoudiniEngineBakeUtils::BakeLandscape( { const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key; FHoudiniOutputObject& OutputObject = Elem.Value; - FHoudiniBakedOutputObject& BakedOutputObject = InBakedOutputObjects.FindOrAdd(ObjectIdentifier); + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(ObjectIdentifier); + if (OldBakedOutputObjects.Contains(ObjectIdentifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(ObjectIdentifier); // Populate the package params for baking this output object. if (!IsValid(OutputObject.OutputObject)) @@ -2325,21 +2590,24 @@ FHoudiniEngineBakeUtils::BakeLandscape( FString ObjectName = Landscape->GetName(); // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - ObjectIdentifier, - BakePath, - ObjectName, - HoudiniAssetName, - AssetPackageReplaceMode - ); + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* const DesiredWorld = Landscape ? Landscape->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, ObjectIdentifier, OutputObject, ObjectName, + HoudiniAssetName, PackageParams, Resolver, BakePath, AssetPackageReplaceMode); BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, - PackageParams, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); + PackageParams, Resolver, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); } + // Update the cached baked output data + BakedOutput.BakedOutputObjects = NewBakedOutputObjects; + if (PackagesToSave.Num() > 0) { FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); @@ -2386,6 +2654,7 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( bool bInReplaceActors, bool bInReplaceAssets, FHoudiniPackageParams& PackageParams, + FHoudiniAttributeResolver& InResolver, TArray& WorldsToUpdate, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& BakeStats) @@ -2406,17 +2675,6 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true); - // At this point we reconstruct the resolver using cached attributes and tokens - // and just update certain tokens (output paths) for bake mode. - FHoudiniAttributeResolver Resolver; - { - TMap Tokens = InOutputObject.CachedTokens; - // PackageParams.UpdateOutputPathTokens(EPackageMode::Bake, Tokens); - PackageParams.UpdateTokensFromParams(TileWorld, Tokens); - Resolver.SetCachedAttributes(InOutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC // and has the appropriate name. ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor(); @@ -2436,13 +2694,13 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( // actor const FString DesiredSharedLandscapeName = bHasPreviousSharedLandscape && bInReplaceActors ? PreviousSharedLandscapeActor->GetName() - : Resolver.ResolveAttribute( + : InResolver.ResolveAttribute( HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActor->GetName()); // If we are not baking in replacement mode, create a unique name if the name is already in use const FString SharedLandscapeName = !bInReplaceActors - ? MakeUniqueObjectNameIfNeeded(SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *DesiredSharedLandscapeName).ToString() + ? MakeUniqueObjectNameIfNeeded(SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *DesiredSharedLandscapeName) : DesiredSharedLandscapeName; if (SharedLandscapeActor->GetName() != SharedLandscapeName) @@ -2470,13 +2728,13 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( TArray ValidLandscapes; - FString ActorName = Resolver.ResolveOutputName(); + FString ActorName = InResolver.ResolveOutputName(); // If the unreal_level_path was not specified, then fallback to the tile world's package FString PackagePath = TileWorld->GetPackage() ? TileWorld->GetPackage()->GetPathName() : FString(); bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); if (bHasLevelPathAttribute) - PackagePath = Resolver.ResolveFullLevelPath(); + PackagePath = InResolver.ResolveFullLevelPath(); if (bInReplaceActors) { @@ -2622,7 +2880,7 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( if (!InStaticMesh || InStaticMesh->IsPendingKill()) return nullptr; - bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, InParentOutputs, InTemporaryCookFolder); + const bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, EHoudiniOutputType::Mesh, InParentOutputs, InTemporaryCookFolder); if (!bIsTemporaryStaticMesh) { // The Static Mesh is not a temporary one/already baked, we can simply reuse it @@ -2684,10 +2942,12 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( // it so that its render resources can be safely replaced/updated, and then reattach it UStaticMesh * DuplicatedStaticMesh = nullptr; UStaticMesh* ExistingMesh = FindObject(MeshPackage, *CreatedPackageName); + bool bFoundExistingMesh = false; if (IsValid(ExistingMesh)) { FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + bFoundExistingMesh = true; } else { @@ -2715,7 +2975,7 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( continue; // Only duplicate the material if it is temporary - if (IsObjectTemporary(MaterialInterface, InParentOutputs, InTemporaryCookFolder)) + if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InParentOutputs, InTemporaryCookFolder)) { UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); if (MaterialPackage && !MaterialPackage->IsPendingKill()) @@ -2761,7 +3021,8 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; // Notify registry that we have created a new duplicate mesh. - FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); + if (!bFoundExistingMesh) + FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); // Dirty the static mesh package. DuplicatedStaticMesh->MarkPackageDirty(); @@ -2967,8 +3228,7 @@ FHoudiniEngineBakeUtils::BakeCurve( // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset const FName BaseActorName(PackageParams.ObjectName); - const FName NewName = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName, OutActor); - const FString NewNameStr = NewName.ToString(); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName.ToString(), OutActor); // OutActor->Rename(*NewNameStr); // OutActor->SetActorLabel(NewNameStr); RenameAndRelabelActor(OutActor, NewNameStr, false); @@ -2977,11 +3237,15 @@ FHoudiniEngineBakeUtils::BakeCurve( USplineComponent* DuplicatedSplineComponent = DuplicateObject( InSplineComponent, OutActor, - MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), FName(PackageParams.ObjectName))); + FName(MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), PackageParams.ObjectName))); OutActor->AddInstanceComponent(DuplicatedSplineComponent); const bool bCreateIfMissing = true; USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + + // We duplicated the InSplineComponent, so we don't have to copy all of its properties, but we must set the + // world transform + DuplicatedSplineComponent->SetWorldTransform(InSplineComponent->GetComponentTransform()); FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent); DuplicatedSplineComponent->RegisterComponent(); @@ -2996,6 +3260,7 @@ FHoudiniEngineBakeUtils::BakeCurve( FHoudiniBakedOutputObject& InBakedOutputObject, // const TArray& InAllBakedOutputs, const FHoudiniPackageParams &PackageParams, + FHoudiniAttributeResolver& InResolver, bool bInReplaceActors, bool bInReplaceAssets, TArray& OutActors, @@ -3014,18 +3279,8 @@ FHoudiniEngineBakeUtils::BakeCurve( { UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; - // Access some of the attribute that were cached on the output object - FHoudiniAttributeResolver Resolver; - { - TMap CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - PackageParams.UpdateTokensFromParams(DesiredWorld, Tokens); - Resolver.SetCachedAttributes(CachedAttributes); - Resolver.SetTokensFromStringMap(Tokens); - } - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + FString LevelPackagePath = InResolver.ResolveFullLevelPath(); bool bCreatedPackage = false; if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( @@ -3090,6 +3345,8 @@ FHoudiniEngineBakeUtils::BakeCurve( FHoudiniEngineBakedActor Result; Result.Actor = FoundActor; Result.ActorBakeName = BakeActorName; + Result.BakeFolderPath = PackageParams.BakeFolder; + Result.BakedObjectPackageParams = PackageParams; OutActors.Add(Result); return true; @@ -3158,8 +3415,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( BakedUnrealSplineComponent->RegisterComponent(); // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FName NewName = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); - const FString NewNameStr = NewName.ToString(); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); // NewActor->Rename(*NewNameStr); // NewActor->SetActorLabel(NewNameStr); RenameAndRelabelActor(NewActor, NewNameStr, false); @@ -3539,8 +3795,7 @@ FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, b } bool -FHoudiniEngineBakeUtils::FindOutputObject( - const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) +FHoudiniEngineBakeUtils::FindOutputObject(const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) { if (!InObjectToFind || InObjectToFind->IsPendingKill()) return false; @@ -3551,8 +3806,11 @@ FHoudiniEngineBakeUtils::FindOutputObject( const UHoudiniOutput* CurOutput = InOutputs[OutputIdx]; if (!IsValid(CurOutput)) continue; + + if (CurOutput->GetType() != InOutputType) + continue; - for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) + for (auto& CurOutputObject : CurOutput->GetOutputObjects()) { if (CurOutputObject.Value.OutputObject == InObjectToFind || CurOutputObject.Value.OutputComponent == InObjectToFind @@ -3570,7 +3828,7 @@ FHoudiniEngineBakeUtils::FindOutputObject( } bool -FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC) +FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC) { if (!InObject || InObject->IsPendingKill()) return false; @@ -3591,18 +3849,18 @@ FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, UHoudiniAssetCompo TempPath = InHAC->TemporaryCookFolder.Path; } - return IsObjectTemporary(InObject, Outputs, TempPath); + return IsObjectTemporary(InObject, InOutputType, Outputs, TempPath); } bool FHoudiniEngineBakeUtils::IsObjectTemporary( - UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) + UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) { if (!InObject || InObject->IsPendingKill()) return false; int32 ParentOutputIndex = -1; FHoudiniOutputObjectIdentifier Identifier; - if (FindOutputObject(InObject, InParentOutputs, ParentOutputIndex, Identifier)) + if (FindOutputObject(InObject, InOutputType, InParentOutputs, ParentOutputIndex, Identifier)) return true; // Check the package path for this object @@ -3637,7 +3895,11 @@ bool FHoudiniEngineBakeUtils::IsObjectTemporary( } void -FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC) +FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent( + AActor* NewActor, + UStaticMeshComponent* NewSMC, + UStaticMeshComponent* InSMC, + bool bInCopyWorldTransform) { if (!NewSMC || NewSMC->IsPendingKill()) return; @@ -3680,8 +3942,16 @@ FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, US // FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO); // The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another - UClass* ComponentClass = InSMC->GetClass(); - if (ComponentClass != NewSMC->GetClass()) + UClass* ComponentClass = nullptr; + if (InSMC->GetClass()->IsChildOf(NewSMC->GetClass())) + { + ComponentClass = NewSMC->GetClass(); + } + else if (NewSMC->GetClass()->IsChildOf(InSMC->GetClass())) + { + ComponentClass = InSMC->GetClass(); + } + else { HOUDINI_LOG_WARNING( TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), @@ -3764,6 +4034,11 @@ FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent(AActor* NewActor, US } } + if (bInCopyWorldTransform) + { + NewSMC->SetWorldTransform(InSMC->GetComponentTransform()); + } + NewSMC->PostEditChange(); }; @@ -3848,7 +4123,7 @@ FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, FString NewName; if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetPackage(), InAsset->GetClass(), FName(InNewName), InAsset).ToString(); + NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetPackage(), InAsset->GetClass(), InNewName, InAsset); else NewName = InNewName; @@ -3875,7 +4150,7 @@ FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& I FString NewName; if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName), InActor).ToString(); + NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), InNewName, InActor); else NewName = InNewName; @@ -3926,11 +4201,12 @@ bool FHoudiniEngineBakeUtils::BakePDGWorkResultObject( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, - int32 InWorkResultIndex, - int32 InWorkResultObjectIndex, + int32 InWorkResultArrayIndex, + int32 InWorkResultObjectArrayIndex, bool bInReplaceActors, bool bInReplaceAssets, bool bInBakeToWorkResultActor, + bool bInIsAutoBake, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats, @@ -3944,18 +4220,30 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( if (!IsValid(InNode)) return false; - if (!InNode->WorkResult.IsValidIndex(InWorkResultIndex)) + if (!InNode->WorkResult.IsValidIndex(InWorkResultArrayIndex)) return false; - FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultIndex]; - if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectIndex)) + FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultArrayIndex]; + if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) return false; - FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectIndex]; + FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectArrayIndex]; TArray& Outputs = WorkResultObject.GetResultOutputs(); if (Outputs.Num() == 0) return true; + if (WorkResultObject.State != EPDGWorkResultState::Loaded) + { + if (bInIsAutoBake && WorkResultObject.AutoBakedSinceLastLoad()) + { + HOUDINI_LOG_MESSAGE(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded but was auto-baked since its last load."), *WorkResultObject.Name); + return true; + } + + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded, cannot bake it."), *WorkResultObject.Name); + return false; + } + AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor(); if (!IsValid(WorkResultObjectActor)) { @@ -3963,18 +4251,23 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( return false; } - // BakedActorsForWorkResultObject contains each actor that contains baked PDG results. Actors may + // OutBakedActors contains each actor that contains baked PDG results. Actors may // appear in the array more than once if they have more than one baked result/component associated with // them - TArray BakedActorsForWorkResultObject; + // TArray BakedActorsForWorkResultObject; + const int32 NumBakedPre = OutBakedActors.Num(); const FString HoudiniAssetName(WorkResultObject.Name); // Find the previous bake output for this work result object FString Key; - InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key); + InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key); FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key); + + const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InPDGAssetLink); + check(IsValid(HoudiniAssetComponent)); BakeHoudiniOutputsToActors( + HoudiniAssetComponent, Outputs, BakedOutputContainer.BakedOutputs, HoudiniAssetName, @@ -3983,7 +4276,7 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( InPDGAssetLink->GetTemporaryCookFolder(), bInReplaceActors, bInReplaceAssets, - BakedActorsForWorkResultObject, + OutBakedActors, OutPackagesToSave, OutBakeStats, InOutputTypesToBake, @@ -3992,34 +4285,39 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( InFallbackWorldOutlinerFolder); // Set the PDG indices on the output baked actor entries - if (BakedActorsForWorkResultObject.Num() > 0) + FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); + AActor* const WROActor = OutputActorOwner.GetOutputActor(); + FHoudiniEngineBakedActor const * BakedWROActorEntry = nullptr; + const int32 NumBakedPost = OutBakedActors.Num(); + if (NumBakedPost > NumBakedPre) { - for (FHoudiniEngineBakedActor& BakedActorEntry : BakedActorsForWorkResultObject) + for (int32 Index = FMath::Max(0, NumBakedPre); Index < NumBakedPost; ++Index) { - BakedActorEntry.PDGWorkResultIndex = InWorkResultIndex; - BakedActorEntry.PDGWorkResultObjectIndex = InWorkResultObjectIndex; + FHoudiniEngineBakedActor& BakedActorEntry = OutBakedActors[Index]; + BakedActorEntry.PDGWorkResultArrayIndex = InWorkResultArrayIndex; + BakedActorEntry.PDGWorkItemIndex = WorkResult.WorkItemIndex; + BakedActorEntry.PDGWorkResultObjectArrayIndex = InWorkResultObjectArrayIndex; + + if (WROActor && BakedActorEntry.Actor == WROActor) + { + BakedWROActorEntry = &BakedActorEntry; + } } } // If anything was baked to WorkResultObjectActor, detach it from its parent if (bInBakeToWorkResultActor) { - FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); // if we re-used the temp actor as a bake actor, then remove its temp outputs WorkResultObject.DestroyResultOutputs(); - AActor* WROActor = OutputActorOwner.GetOutputActor(); if (WROActor) { - const FHoudiniEngineBakedActor* BakedActorEntry = BakedActorsForWorkResultObject.FindByPredicate([WROActor](const FHoudiniEngineBakedActor& Entry) - { - return Entry.Actor == WROActor; - }); - if (BakedActorEntry) + if (BakedWROActorEntry) { OutputActorOwner.SetOutputActor(nullptr); const FString OldActorPath = FSoftObjectPath(WROActor).ToString(); DetachAndRenameBakedPDGOutputActor( - WROActor, BakedActorEntry->ActorBakeName.ToString(), BakedActorEntry->WorldOutlinerFolder); + WROActor, BakedWROActorEntry->ActorBakeName.ToString(), BakedWROActorEntry->WorldOutlinerFolder); const FString NewActorPath = FSoftObjectPath(WROActor).ToString(); if (OldActorPath != NewActorPath) { @@ -4040,160 +4338,88 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( } } } - OutBakedActors.Append(BakedActorsForWorkResultObject); + + if (bInIsAutoBake) + WorkResultObject.SetAutoBakedSinceLastLoad(true); + // OutBakedActors.Append(BakedActorsForWorkResultObject); return true; } - -bool -FHoudiniEngineBakeUtils::BakePDGWorkResultObject( +void +FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName) + int32 InWorkItemHAPIIndex, + int32 InWorkItemResultInfoIndex) { if (!IsValid(InPDGAssetLink)) - return false; - - if (!IsValid(InNode)) - return false; - - // Find the work result index and work result object index - const int32 WorkResultIndex = InNode->WorkResult.IndexOfByPredicate([InWorkResultId](const FTOPWorkResult& Entry) - { - return Entry.WorkItemID == InWorkResultId; - }); - if (!InNode->WorkResult.IsValidIndex(WorkResultIndex)) - return false; - FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIndex]; - const int32 WorkResultObjectIndex = WorkResult.ResultObjects.IndexOfByPredicate([InWorkResultObjectName](const FTOPWorkResultObject& Entry) - { - return Entry.Name.Equals(InWorkResultObjectName); - }); - if (!WorkResult.ResultObjects.IsValidIndex(WorkResultObjectIndex)) - return false; - - // Determine the output world outliner folder path via the PDG asset link's - // owner's folder path and name - UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); - if (!PDGOwner) - PDGOwner = InPDGAssetLink->GetOuter(); - const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); - - // Determine the actor/package replacement settings - const bool bBakeBlueprints = InPDGAssetLink->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToBlueprint; - const bool bReplaceActors = !bBakeBlueprints && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Determine the output types to bake: don't bake landscapes in blueprint baking mode - TArray OutputTypesToBake; - TArray InstancerComponentTypesToBake; - if (bBakeBlueprints) - { - OutputTypesToBake.Add(EHoudiniOutputType::Mesh); - OutputTypesToBake.Add(EHoudiniOutputType::Instancer); - OutputTypesToBake.Add(EHoudiniOutputType::Curve); + return; - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); - } + if (!InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded) + return; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - TArray BakedActors; + if (!IsValid(InNode)) + return; - bool bSuccess = BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - WorkResultIndex, - WorkResultObjectIndex, - bReplaceActors, - bReplaceAssets, - !bBakeBlueprints, - BakedActors, - PackagesToSave, - BakeStats, - OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, - InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, - FallbackWorldOutlinerFolderPath.ToString() - ); + // Check if the node is ready for baking: all work items must be complete + if (!InNode->AreAllWorkItemsComplete() || InNode->AnyWorkItemsFailed()) + return; - // Recenter and select the baked actors - if (GEditor && BakedActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : BakedActors) + // Check if the node is ready for baking: all work items must be loaded + for (const FTOPWorkResult& WorkResult : InNode->WorkResult) { - if (!IsValid(Entry.Actor)) - continue; - - if (InPDGAssetLink->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); + for (const FTOPWorkResultObject& WRO : WorkResult.ResultObjects) + { + if (WRO.State != EPDGWorkResultState::Loaded && !WRO.AutoBakedSinceLastLoad()) + return; + } } - - if (GEditor && BakedActors.Num() > 0) - GEditor->NoteSelectionChange(); - if (bBakeBlueprints && bSuccess) + // Check which outputs are selected for baking: selected node, selected network or all + // And only bake if the node falls within the criteria + UTOPNetwork const * const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + UTOPNode const * const SelectedTOPNode = InPDGAssetLink->GetSelectedTOPNode(); + switch (InPDGAssetLink->PDGBakeSelectionOption) { - TArray Blueprints; - bSuccess = BakeBlueprintsFromBakedActors( - BakedActors, - InPDGAssetLink->bRecenterBakedActors, - bReplaceAssets, - InPDGAssetLink->AssetName, - InPDGAssetLink->BakeFolder, - nullptr, - &InNode->GetBakedWorkResultObjectsOutputs(), - Blueprints, - PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) + case EPDGBakeSelectionOption::SelectedNetwork: + if (!IsValid(SelectedTOPNetwork) || !InNode->IsParentTOPNetwork(SelectedTOPNetwork)) { - Assets.Add(Blueprint); + HOUDINI_LOG_WARNING( + TEXT("Not baking Node %s (Net %s): not in selected network"), + InNode ? *InNode->GetName() : TEXT(""), + SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); + return; } - GEditor->SyncBrowserToObjects(Assets); - } + break; + case EPDGBakeSelectionOption::SelectedNode: + if (InNode != SelectedTOPNode) + { + HOUDINI_LOG_WARNING( + TEXT("Not baking Node %s (Net %s): not the selected node"), + InNode ? *InNode->GetName() : TEXT(""), + SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); + return; + } + break; + case EPDGBakeSelectionOption::All: + default: + break; } - SaveBakedPackages(PackagesToSave); - + const bool bIsAutoBake = true; + switch (InPDGAssetLink->HoudiniEngineBakeOption) { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return bSuccess; -} - -void -FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName) -{ - if (!IsValid(InPDGAssetLink)) - return; + case EHoudiniEngineBakeOption::ToActor: + FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InNode, bIsAutoBake); + break; - if (!InPDGAssetLink->bBakeAfterWorkResultObjectLoaded) - return; + case EHoudiniEngineBakeOption::ToBlueprint: + FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(InPDGAssetLink, InNode, bIsAutoBake); + break; - BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - InWorkResultId, - InWorkResultObjectName); + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported HoudiniEngineBakeOption %i"), InPDGAssetLink->HoudiniEngineBakeOption); + } } bool @@ -4201,6 +4427,7 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, bool bInBakeForBlueprint, + bool bInIsAutoBake, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats) @@ -4234,23 +4461,25 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent); } const int32 NumWorkResults = InNode->WorkResult.Num(); - for (int32 WorkResultIdx = 0; WorkResultIdx < NumWorkResults; ++WorkResultIdx) + for (int32 WorkResultArrayIdx = 0; WorkResultArrayIdx < NumWorkResults; ++WorkResultArrayIdx) { - FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultIdx]; + FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultArrayIdx]; const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); - for (int32 WorkResultObjectIdx = 0; WorkResultObjectIdx < NumWorkResultObjects; ++WorkResultObjectIdx) + for (int32 WorkResultObjectArrayIdx = 0; WorkResultObjectArrayIdx < NumWorkResultObjects; ++WorkResultObjectArrayIdx) { BakePDGWorkResultObject( InPDGAssetLink, InNode, - WorkResultIdx, - WorkResultObjectIdx, + WorkResultArrayIdx, + WorkResultObjectArrayIdx, bReplaceActors, bReplaceAssets, !bInBakeForBlueprint, + bInIsAutoBake, OutBakedActors, OutPackagesToSave, OutBakeStats, @@ -4264,11 +4493,54 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( return true; } +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake) +{ + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + TArray BakedActors; + + const bool bBakeBlueprints = false; + + bool bSuccess = BakePDGTOPNodeOutputsKeepActors( + InPDGAssetLink, InTOPNode, bBakeBlueprints, bInIsAutoBake, BakedActors, PackagesToSave, BakeStats); + + SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && BakedActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : BakedActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (InPDGAssetLink->bRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && BakedActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + return bSuccess; +} + bool FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InNetwork, bool bInBakeForBlueprint, + bool bInIsAutoBake, TArray& BakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats) @@ -4285,7 +4557,7 @@ FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( if (!IsValid(Node)) continue; - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, BakedActors, OutPackagesToSave, OutBakeStats); + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, bInIsAutoBake, BakedActors, OutPackagesToSave, OutBakeStats); } return bSuccess; @@ -4302,6 +4574,7 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* TArray BakedActors; const bool bBakeBlueprints = false; + const bool bIsAutoBake = false; bool bSuccess = true; switch(InPDGAssetLink->PDGBakeSelectionOption) @@ -4317,14 +4590,14 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* if (!IsValid(Node)) continue; - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, bIsAutoBake, BakedActors, PackagesToSave, BakeStats); } } break; case EPDGBakeSelectionOption::SelectedNetwork: - bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, bIsAutoBake, BakedActors, PackagesToSave, BakeStats); case EPDGBakeSelectionOption::SelectedNode: - bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, BakedActors, PackagesToSave, BakeStats); + bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, bIsAutoBake, BakedActors, PackagesToSave, BakeStats); } SaveBakedPackages(PackagesToSave); @@ -4364,7 +4637,7 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( bool bInReplaceAssets, const FString& InAssetName, const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOuputs, + TArray* const InNonPDGBakedOutputs, TMap* const InPDGBakedOutputs, TArray& OutBlueprints, TArray& OutPackagesToSave) @@ -4404,6 +4677,17 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( // Create package for out Blueprint FString BlueprintName; + // For instancers we determine the bake folder from the instancer, + // for everything else we use the baked object's bake folder + // If all of that is blank, we fall back to InBakeFolder. + FString BakeFolderPath; + if (Entry.bInstancerOutput) + BakeFolderPath = Entry.InstancerPackageParams.BakeFolder; + else + BakeFolderPath = Entry.BakeFolderPath; + if (BakeFolderPath.IsEmpty()) + BakeFolderPath = InBakeFolder.Path; + FHoudiniPackageParams PackageParams; // Set the replace mode based on if we are doing a replacement or incremental asset bake const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? @@ -4411,21 +4695,21 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( PackageParams, FHoudiniOutputObjectIdentifier(), - InBakeFolder.Path, + BakeFolderPath, Entry.ActorBakeName.ToString() + "_BP", InAssetName, AssetPackageReplaceMode); // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment // is consistent with the bake counter - int32 BakeCounter = 0; + int32 BakeCounter = 0; UBlueprint* InPreviousBlueprint = nullptr; FHoudiniBakedOutputObject* BakedOutputObject = nullptr; FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; // Get the baked output object - if (Entry.PDGWorkResultIndex >= 0 && Entry.PDGWorkResultObjectIndex >= 0 && InPDGBakedOutputs) + if (Entry.PDGWorkResultArrayIndex >= 0 && Entry.PDGWorkItemIndex >= 0 && Entry.PDGWorkResultObjectArrayIndex >= 0 && InPDGBakedOutputs) { - const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkResultIndex, Entry.PDGWorkResultObjectIndex); + const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkItemIndex, Entry.PDGWorkResultObjectArrayIndex); WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); if (WorkResultObjectBakedOutput) { @@ -4435,11 +4719,11 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( } } } - else if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs) + else if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs) { - if (Entry.OutputIndex >= 0 && InNonPDGBakedOuputs->IsValidIndex(Entry.OutputIndex)) + if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs->IsValidIndex(Entry.OutputIndex)) { - BakedOutputObject = (*InNonPDGBakedOuputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + BakedOutputObject = (*InNonPDGBakedOutputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); } } if (BakedOutputObject) @@ -4466,7 +4750,7 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( Package->FullyLoad(); //Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false); - // Find existing asset first first (only relevant if we are in replacement mode). If the existing asset has a + // Find existing asset first (only relevant if we are in replacement mode). If the existing asset has a // different base class than the incoming actor, we reparent the blueprint to the new base class before // clearing the SCS graph and repopulating it from the temp actor. Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName)); @@ -4509,8 +4793,8 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); Asset = AssetToolsModule.Get().CreateAsset( - BlueprintName, InBakeFolder.Path, - UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + BlueprintName, PackageParams.GetPackagePath(), + UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); } UBlueprint* Blueprint = Cast(Asset); @@ -4578,6 +4862,7 @@ bool FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, + bool bInIsAutoBake, TArray& OutBlueprints, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats) @@ -4606,6 +4891,7 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( InPDGAssetLink, InNode, bInBakeForBlueprint, + bInIsAutoBake, BakedActors, OutPackagesToSave, OutBakeStats @@ -4628,6 +4914,49 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( return bSuccess; } +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake) +{ + TArray Blueprints; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + const bool bSuccess = BakePDGTOPNodeBlueprints( + InPDGAssetLink, + InTOPNode, + bInIsAutoBake, + Blueprints, + PackagesToSave, + BakeStats); + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + return bSuccess; +} + bool FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( UHoudiniPDGAssetLink* InPDGAssetLink, @@ -4642,13 +4971,14 @@ FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( if (!IsValid(InNetwork)) return false; + const bool bIsAutoBake = false; bool bSuccess = true; for (UTOPNode* Node : InNetwork->AllTOPNodes) { if (!IsValid(Node)) continue; - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, OutBlueprints, OutPackagesToSave, OutBakeStats); + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, OutBlueprints, OutPackagesToSave, OutBakeStats); } return bSuccess; @@ -4664,6 +4994,7 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGA if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) return false; + const bool bIsAutoBake = false; bool bSuccess = true; switch(InPDGAssetLink->PDGBakeSelectionOption) { @@ -4678,7 +5009,7 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGA if (!IsValid(Node)) continue; - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, Blueprints, PackagesToSave, BakeStats); + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, Blueprints, PackagesToSave, BakeStats); } } break; @@ -4693,6 +5024,7 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGA bSuccess &= BakePDGTOPNodeBlueprints( InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), + bIsAutoBake, Blueprints, PackagesToSave, BakeStats); @@ -4810,8 +5142,8 @@ FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( *MakeUniqueObjectNameIfNeeded( FoundActor->GetOuter(), FoundActor->GetClass(), - FName(FoundActor->GetName() + "_Pending_Kill"), - FoundActor).ToString(), + FoundActor->GetName() + "_Pending_Kill", + FoundActor), false); } if (bInNoPendingKillActors) @@ -4854,7 +5186,7 @@ bool FHoudiniEngineBakeUtils::FindUnrealBakeActor( } else { - OutBakeActorName = *BakeActorNameStr; + OutBakeActorName = FName(BakeActorNameStr, NAME_NO_NUMBER_INTERNAL); // We have a bake actor name, look for the actor AActor* BakeNameActor = nullptr; if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor)) @@ -5041,7 +5373,19 @@ FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor) const bool bIncludeFromChildActors = true; FVector Origin; FVector BoxExtent; - InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); + // InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); + FBox Box(ForceInit); + + InActor->ForEachComponent(bIncludeFromChildActors, [&](const UPrimitiveComponent* InPrimComp) + { + // Only use non-editor-only components for the bounds calculation (to exclude things like editor only sprite/billboard components) + if (InPrimComp->IsRegistered() && !InPrimComp->IsEditorOnly() && + (!bOnlyCollidingComponents || InPrimComp->IsCollisionEnabled())) + { + Box += InPrimComp->Bounds.GetBox(); + } + }); + Box.GetCenterAndExtents(Origin, BoxExtent); const FVector Delta = Origin - RootComponent->GetComponentLocation(); // Actor->SetActorLocation(Origin); @@ -5086,34 +5430,56 @@ FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMi return RootComponent; } -FName -FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed) +FString +FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed) { if (IsValid(InObjectThatWouldBeRenamed)) { const FName CurrentName = InObjectThatWouldBeRenamed->GetFName(); - if (CurrentName == InName) + if (CurrentName.ToString() == InName) return InName; // Check if the prefix matches (without counter suffix) the new name - const FString CurrentNamePlainStr = CurrentName.GetPlainNameString(); - if (CurrentNamePlainStr == InName.ToString()) - return CurrentName; + // In other words, if InName is 'my_actor' and the object is already an increment of it, 'my_actor_5' then + // don't we can just keep the current name + if (CurrentName.GetPlainNameString() == InName) + return CurrentName.ToString(); } UObject* ExistingObject = nullptr; - if (InOuter == ANY_PACKAGE) - { - ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *InName.ToString()); - } - else - { - ExistingObject = StaticFindObjectFast(nullptr, InOuter, InName); - } + FName CandidateName(InName); + bool bAppendedNumber = false; + // Do our own loop for generate suffixes as sequentially as possible. If this turns out to be expensive we can + // revert to MakeUniqueObjectName. + // return MakeUniqueObjectName(InOuter, InClass, CandidateName).ToString(); + do + { + if (InOuter == ANY_PACKAGE) + { + ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *(CandidateName.ToString())); + } + else + { + ExistingObject = StaticFindObjectFast(nullptr, InOuter, CandidateName); + } + + if (ExistingObject) + { + if (!bAppendedNumber) + { + const bool bSplitName = false; + CandidateName = FName(*InName, NAME_EXTERNAL_TO_INTERNAL(1), FNAME_Add, bSplitName); + bAppendedNumber = true; + } + else + { + CandidateName.SetNumber(CandidateName.GetNumber() + 1); + } + // CandidateName = FString::Printf(TEXT("%s_%d"), *InName, ++Counter); + } + } while (ExistingObject); - if (ExistingObject) - return MakeUniqueObjectName(InOuter, InClass, InName); - return InName; + return CandidateName.ToString(); } FName diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h index 08e780c30..bbef6b40e 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h @@ -28,6 +28,7 @@ #include "HoudiniPDGAssetLink.h" #include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" class UHoudiniAssetComponent; class UHoudiniOutput; @@ -49,6 +50,7 @@ struct FHoudiniOutputObject; struct FHoudiniOutputObjectIdentifier; struct FHoudiniEngineOutputStats; struct FHoudiniBakedOutputObject; +struct FHoudiniAttributeResolver; enum class EHoudiniLandscapeOutputBakeType : uint8; @@ -56,10 +58,16 @@ enum class EHoudiniLandscapeOutputBakeType : uint8; UENUM() enum class EHoudiniInstancerComponentType : uint8 { + // Single static mesh component StaticMeshComponent, + // (Hierarichal)InstancedStaticMeshComponent InstancedStaticMeshComponent, MeshSplitInstancerComponent, - InstancedActorComponent + InstancedActorComponent, + // For baking foliage as foliage + FoliageInstancedStaticMeshComponent, + // Baking foliage as HISMC + FoliageAsHierarchicalInstancedStaticMeshComponent }; // Helper struct to track actors created/used when baking, with @@ -76,7 +84,10 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor int32 InOutputIndex, const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, UObject* InBakedObject, - UObject* InSourceObject); + UObject* InSourceObject, + UObject* InBakedComponent, + const FString& InBakeFolderPath, + const FHoudiniPackageParams& InBakedObjectPackageParams); // The actor that the baked output was associated with AActor* Actor = nullptr; @@ -93,17 +104,38 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor // The world outliner folder the actor is placed in FName WorldOutlinerFolder = NAME_None; - // The index of the work item when baking PDG - int32 PDGWorkResultIndex = INDEX_NONE; + // The array index of the work result when baking PDG + int32 PDGWorkResultArrayIndex = INDEX_NONE; - // The index of the work result object of the work item when baking PDG - int32 PDGWorkResultObjectIndex = INDEX_NONE; + // The work item index (as returned by HAPI) for the work item/work result, used when baking PDG + int32 PDGWorkItemIndex = INDEX_NONE; + + // The array index of the work result object of the work result when baking PDG + int32 PDGWorkResultObjectArrayIndex = INDEX_NONE; // The baked primary asset (such as static mesh) UObject* BakedObject = nullptr; // The temp asset that was baked to BakedObject UObject* SourceObject = nullptr; + + // The baked component or foliage type in the case of foliage + UObject* BakedComponent = nullptr; + + // The bake folder path to where BakedObject was baked + FString BakeFolderPath; + + // The package params for the BakedObject + FHoudiniPackageParams BakedObjectPackageParams; + + // True if this entry was created by an instancer output. + bool bInstancerOutput; + + // The package params built for the instancer part of the output, if this was an instancer. + // This would mostly be useful in situations for we later need the resolver and/or cached attributes and + // tokens, such as for blueprint baking. + FHoudiniPackageParams InstancerPackageParams; + }; struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils @@ -136,6 +168,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils FHoudiniBakedOutputObject& InBakedOutputObject, // const TArray& InAllBakedOutputs, const FHoudiniPackageParams &PackageParams, + FHoudiniAttributeResolver& InResolver, bool bInReplaceActors, bool bInReplaceAssets, TArray& OutActors, @@ -162,9 +195,10 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FDirectoryPath& InTempCookFolder); static bool BakeLandscape( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, - UHoudiniOutput* InOutput, - TMap& InBakedOutputObjects, + const TArray& InAllOutputs, + TArray& InBakedOutputs, bool bInReplaceActors, bool bInReplaceAssets, FString BakePath, @@ -177,15 +211,18 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplaceActors, bool bInReplaceAssets, FHoudiniPackageParams& PackageParams, + FHoudiniAttributeResolver& InResolver, TArray& WorldsToUpdate, TArray& OutPackagesToUnload, FHoudiniEngineOutputStats& BakeStats); static bool BakeInstancerOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetcomponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, const FTransform& InTransform, + const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, @@ -197,6 +234,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FString& InFallbackWorldOutlinerFolder=""); static bool BakeInstancerOutputToActors_ISMC( + const UHoudiniAssetComponent* HoudiniAssetcomponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -214,6 +252,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FString& InFallbackWorldOutlinerFolder=""); static bool BakeInstancerOutputToActors_IAC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, const FHoudiniOutputObject& InOutputObject, @@ -225,6 +264,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils TArray& OutPackagesToSave); static bool BakeInstancerOutputToActors_MSIC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -242,6 +282,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FString& InFallbackWorldOutlinerFolder=""); static bool BakeInstancerOutputToActors_SMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -310,6 +351,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FString& InFallbackWorldOutlinerFolder=""); static bool BakeHoudiniOutputsToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, const TArray& InOutputs, TArray& InBakedOutputs, const FString& InHoudiniAssetName, @@ -326,11 +368,28 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); + static bool BakeInstancerOutputToFoliage( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave); + static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); static bool BakeStaticMeshOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, @@ -345,9 +404,10 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FString& InFallbackWorldOutlinerFolder=""); static bool BakeHoudiniCurveOutputToActors( - UHoudiniOutput* Output, - TMap& InBakedOutputObjects, - const TArray& InAllBakedOutputs, + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, bool bInReplaceActors, @@ -362,7 +422,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplaceAssets, const FString& InAssetName, const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOuputs, + TArray* const InNonPDGBakedOutputs, TMap* const InPDGBakedOutputs, TArray& OutBlueprints, TArray& OutPackagesToSave); @@ -386,15 +446,19 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils // Look for InObjectToFind among InOutputs. Return true if found and set OutOutputIndex and OutIdentifier. static bool FindOutputObject( - const UObject* InObjectToFind, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); + const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); - static bool IsObjectTemporary(UObject* InObject, UHoudiniAssetComponent* InHAC); + static bool IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC); static bool IsObjectTemporary( - UObject* InObject, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); + UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); // Function used to copy properties from the source Static Mesh Component to the new (baked) one - static void CopyPropertyToNewActorAndComponent(AActor* NewActor, UStaticMeshComponent* NewSMC, UStaticMeshComponent* InSMC); + static void CopyPropertyToNewActorAndComponent( + AActor* NewActor, + UStaticMeshComponent* NewSMC, + UStaticMeshComponent* InSMC, + bool bInCopyWorldTransform=false); // Finds the world/level indicated by the package path. // If the level doesn't exists, it will be created. @@ -477,11 +541,12 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils static bool BakePDGWorkResultObject( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, - int32 InWorkResultIndex, - int32 InWorkResultObjectIndex, + int32 InWorkResultArrayIndex, + int32 InWorkResultObjectArrayIndex, bool bInReplaceActors, bool bInReplaceAssets, bool bInBakeToWorkResultActor, + bool bInIsAutoBake, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats, @@ -489,18 +554,12 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils TArray const* InInstancerComponentTypesToBake=nullptr, const FString& InFallbackWorldOutlinerFolder=""); - static bool BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName); - // Checks if auto-bake is enabled on InPDGAssetLink, and if it is, calls BakePDGWorkResultObject. - static void AutoBakePDGWorkResultObject( + static void CheckPDGAutoBakeAfterResultObjectLoaded( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, - int32 InWorkResultId, - const FString& InWorkResultObjectName); + int32 InWorkItemHAPIIndex, + int32 InWorkItemResultInfoIndex); // Bake PDG output. This bakes all assets from all work items in the specified InNode (FTOPNode). // It uses the existing output actors in the level, but breaks any links from these actors to the PDG link and @@ -509,10 +568,14 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, bool bInBakeForBlueprint, + bool bInIsAutoBake, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats); + // Helper function to bake only a specific PDG TOP node's outputs to actors. + static bool BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake); + // Bake PDG output. This bakes all assets from all work items in the specified TOP network. // It uses the existing output actors in the level, but breaks any links // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent @@ -521,6 +584,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InNetwork, bool bInBakeForBlueprint, + bool bInIsAutoBake, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats); @@ -537,9 +601,13 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils static bool BakePDGTOPNodeBlueprints( UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InNode, + bool bInIsAutoBake, TArray& OutBlueprints, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats); + + // Helper to bake only a specific PDG TOP node's outputs to blueprint(s). + static bool BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake); // Bake PDG output. This bakes all supported assets from all work items in the specified TOP network. // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from @@ -579,6 +647,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils // name. static bool GetTemporaryOutputObjectBakeName( const UObject* InObject, + EHoudiniOutputType InOutputType, const TArray& InAllOutputs, FString& OutBakeName); @@ -605,7 +674,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils AActor* InActor, bool bCreateIfMissing=true, EComponentMobility::Type InMobilityIfCreated=EComponentMobility::Static); // Helper function to return a unique object name if the given is already in use - static FName MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, FName InName, UObject* InObjectThatWouldBeRenamed=nullptr); + static FString MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed=nullptr); // Helper for getting the actor folder path for the world outliner, based unreal_bake_outliner_folder static FName GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp index ce29b517d..ed3be6fdf 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp @@ -980,8 +980,19 @@ FHoudiniEngineCommands::OpenSessionSync() if (!FPlatformProcess::IsProcRunning(PreviousHESS)) { // Start houdini with the -hess commandline args - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); + const FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); +# if PLATFORM_MAC + const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../Resources/bin"); +# elif PLATFORM_LINUX + const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../bin"); +# elif PLATFORM_WINDOWS + const FString HoudiniExeLocationRelativeToLibHAPI; +# else + // Treat an unknown platform the same as Windows for now + const FString HoudiniExeLocationRelativeToLibHAPI; +# endif + FString HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/houdini"); + HOUDINI_LOG_MESSAGE(TEXT("Path to houdini executable: %s"), *HoudiniLocation); FProcHandle HESSHandle = FPlatformProcess::CreateProc( *HoudiniLocation, *SessionSyncArgs, @@ -993,7 +1004,8 @@ FHoudiniEngineCommands::OpenSessionSync() if (!HESSHandle.IsValid()) { // Try with the steam version executable instead - HoudiniLocation = LibHAPILocation + TEXT("//hindie.steam"); + HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/hindie.steam"); + HOUDINI_LOG_MESSAGE(TEXT("Path to hindie.steam executable: %s"), *HoudiniLocation); HESSHandle = FPlatformProcess::CreateProc( *HoudiniLocation, diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp index 300c2fff1..790fadaf1 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp @@ -29,6 +29,7 @@ #include "HoudiniEngineEditorPrivatePCH.h" #include "HoudiniAssetComponent.h" +#include "HoudiniAssetComponentDetails.h" #include "HoudiniAssetActor.h" #include "HoudiniAsset.h" #include "HoudiniParameter.h" @@ -118,17 +119,20 @@ FHoudiniEngineDetails::CreateWidget( // 0. Houdini Engine Icon FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs); + + // 1. Houdini Engine Session Status + FHoudiniAssetComponentDetails::AddSessionStatusRow(HoudiniEngineCategoryBuilder); - // 1. Create Generate Category + // 2. Create Generate Category FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs); - // 2. Create Bake Category + // 3. Create Bake Category FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs); - // 3. Create Asset Options Category + // 4. Create Asset Options Category FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs); - // 4. Create Help and Debug Category + // 5. Create Help and Debug Category FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs); } @@ -319,133 +323,133 @@ FHoudiniEngineDetails::CreateGenerateWidgets( FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); TSharedRef ButtonHorizontalBox = SNew(SHorizontalBox); - // Rebuild button - TSharedPtr RebuildButton; - TSharedPtr RebuildButtonHorizontalBox; + // Recook button + TSharedPtr RecookButton; + TSharedPtr RecookButtonHorizontalBox; ButtonHorizontalBox->AddSlot() .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(15.0f, 0.0f, 0.0f, 2.0f) //.Padding(2.0f, 0.0f, 0.0f, 2.0f) [ SNew(SBox) .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) [ - SAssignNew(RebuildButton, SButton) + SAssignNew(RecookButton, SButton) .VAlign(VAlign_Center) .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) - //.Text(FText::FromString("Rebuild")) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) + //.Text(FText::FromString("Recook")) .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnRecookClickedLambda) .Content() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Center) [ - SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) + SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) ] ] - .OnClicked_Lambda(OnReBuildClickedLambda) ] ]; - if (HoudiniEngineUIRebuildIconBrush.IsValid()) + if (HoudiniEngineUIRecookIconBrush.IsValid()) { - TSharedPtr RebuildImage; - RebuildButtonHorizontalBox->AddSlot() - //.Padding(25.0f, 0.0f, 3.0f, 0.0f) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + TSharedPtr RecookImage; + RecookButtonHorizontalBox->AddSlot() .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ - SAssignNew(RebuildImage, SImage) + SAssignNew(RecookImage, SImage) //.ColorAndOpacity(FSlateColor::UseForeground()) ] ]; - RebuildImage->SetImage( + RecookImage->SetImage( TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { - return HoudiniEngineUIRebuildIconBrush.Get(); + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { + return HoudiniEngineUIRecookIconBrush.Get(); }))); } - RebuildButtonHorizontalBox->AddSlot() + RecookButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) .VAlign(VAlign_Center) .AutoWidth() - .Padding(5.0, 0.0, 0.0, 0.0) [ SNew(STextBlock) - .Text(FText::FromString("Rebuild")) + .Text(FText::FromString("Recook")) ]; - - ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; - ButtonRow.IsEnabled(false); - - // Recook button - TSharedPtr RecookButton; - TSharedPtr RecookButtonHorizontalBox; + + // Rebuild button + TSharedPtr RebuildButton; + TSharedPtr RebuildButtonHorizontalBox; ButtonHorizontalBox->AddSlot() .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(15.0f, 0.0f, 0.0f, 2.0f) //.Padding(2.0f, 0.0f, 0.0f, 2.0f) [ SNew(SBox) .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) [ - SAssignNew(RecookButton, SButton) + SAssignNew(RebuildButton, SButton) .VAlign(VAlign_Center) .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) - //.Text(FText::FromString("Recook")) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) + //.Text(FText::FromString("Rebuild")) .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnRecookClickedLambda) .Content() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Center) [ - SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) + SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) ] ] + .OnClicked_Lambda(OnReBuildClickedLambda) ] ]; - if (HoudiniEngineUIRecookIconBrush.IsValid()) + if (HoudiniEngineUIRebuildIconBrush.IsValid()) { - TSharedPtr RecookImage; - RecookButtonHorizontalBox->AddSlot() + TSharedPtr RebuildImage; + RebuildButtonHorizontalBox->AddSlot() + //.Padding(25.0f, 0.0f, 3.0f, 0.0f) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ - SAssignNew(RecookImage, SImage) + SAssignNew(RebuildImage, SImage) //.ColorAndOpacity(FSlateColor::UseForeground()) ] ]; - RecookImage->SetImage( + RebuildImage->SetImage( TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { - return HoudiniEngineUIRecookIconBrush.Get(); + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { + return HoudiniEngineUIRebuildIconBrush.Get(); }))); } - RecookButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + RebuildButtonHorizontalBox->AddSlot() .VAlign(VAlign_Center) .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) [ SNew(STextBlock) - .Text(FText::FromString("Recook")) + .Text(FText::FromString("Rebuild")) ]; + + ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; + ButtonRow.IsEnabled(false); // Reset Parameters button TSharedPtr ResetParametersButton; @@ -614,34 +618,6 @@ FHoudiniEngineDetails::CreateBakeWidgets( FString NewPathStr = Val.ToString(); - if (NewPathStr.IsEmpty()) - return; - - if (NewPathStr.StartsWith("Game/")) - { - NewPathStr = "/" + NewPathStr; - } - - FString AbsolutePath; - if (NewPathStr.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); - } - - if (!FPaths::DirectoryExists(AbsolutePath)) - { - HOUDINI_LOG_WARNING(TEXT("Invalid path")); - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - return; - } - - for (auto& NextHAC : InHACs) { if (!NextHAC || NextHAC->IsPendingKill()) @@ -989,7 +965,11 @@ FHoudiniEngineDetails::CreateBakeWidgets( [ SNew(STextBlock) .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) + .ToolTipText(LOCTEXT( + "HoudiniEngineBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) ] ]; @@ -1002,10 +982,19 @@ FHoudiniEngineDetails::CreateBakeWidgets( [ SNew(SEditableTextBox) .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEngineBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini Asset when baking.")) + .ToolTipText(LOCTEXT( + "HoudiniEngineBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainHAC->BakeFolder.Path)) + .Text_Lambda([MainHAC]() + { + if (!IsValid(MainHAC)) + return FText(); + return FText::FromString(MainHAC->BakeFolder.Path); + }) .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) ] ]; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp index b76c6797f..c22bc9227 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp @@ -75,7 +75,7 @@ FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& Conten } int32 -FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly) +FHoudiniEngineEditorUtils::GetWorldSelection(TArray& WorldSelection, bool bHoudiniAssetActorsOnly) { WorldSelection.Empty(); @@ -87,8 +87,8 @@ FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, { for (FSelectionIterator It(*SelectedActors); It; ++It) { - AActor * Actor = Cast< AActor >(*It); - if (!Actor && Actor->IsPendingKill()) + AActor * Actor = Cast(*It); + if (!IsValid(Actor)) continue; // Ignore the SkySphere? @@ -101,7 +101,6 @@ FHoudiniEngineEditorUtils::GetWorldSelection(TArray< UObject* >& WorldSelection, WorldSelection.Add(Actor); } } - } // If we only want Houdini Actors... diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp index d03008293..629f4f89d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp @@ -1881,8 +1881,45 @@ FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, T CategoryBuilder.GetParentLayout().ForceRefreshDetails(); }; + // Add Rot/Scale attribute checkbox + FText TooltipText = LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesTooltip", "If enabled, rot and scale attributes will be added per to the input curve on each control points."); + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesLabel", "Add rot & scale Attributes")) + .ToolTipText(TooltipText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + //.MinDesiredWidth(160.f) + ] + .OnCheckStateChanged_Lambda([InInputs](ECheckBoxState NewState) + { + const bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& CurrentInput : InInputs) + { + if (!IsValid(CurrentInput)) + continue; + + CurrentInput->SetAddRotAndScaleAttributes(bChecked); + } + }) + .IsChecked_Lambda([MainInput]() + { + if (!IsValid(MainInput)) + return ECheckBoxState::Unchecked; + + return MainInput->IsAddRotAndScaleAttributesEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .ToolTipText(TooltipText) + ]; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp index 5c0fe7b7f..02895419e 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp @@ -322,7 +322,9 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( FoundOutputObject->BakeName, Landscape, OutputIdentifier, + *FoundOutputObject, HGPO, + HAC, OwnerActor->GetName(), HAC->BakeFolder.Path, HAC->TemporaryCookFolder.Path, @@ -902,7 +904,7 @@ FHoudiniOutputDetails::CreateCurveWidgets( .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) .IsEnabled(true) .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) - .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName]() + .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName, OutputObject]() { TArray AllOutputs; AllOutputs.Reserve(HAC->GetNumOutputs()); @@ -911,7 +913,9 @@ FHoudiniOutputDetails::CreateCurveWidgets( OutputCurveName, SplineComponent, OutputIdentifier, + OutputObject, HoudiniGeoPartObject, + HAC, OwnerActor->GetName(), HAC->BakeFolder.Path, HAC->TemporaryCookFolder.Path, @@ -1113,28 +1117,33 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( .HAlign( HAlign_Center ) .Text( LOCTEXT( "Bake", "Bake" ) ) .IsEnabled(true) - .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC]() + .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC, FoundOutputObject]() { - TArray AllOutputs; - FString TempCookFolder; - if (IsValid(OwningHAC)) + if (FoundOutputObject) { - AllOutputs.Reserve(OwningHAC->GetNumOutputs()); - OwningHAC->GetOutputs(AllOutputs); + TArray AllOutputs; + FString TempCookFolder; + if (IsValid(OwningHAC)) + { + AllOutputs.Reserve(OwningHAC->GetNumOutputs()); + OwningHAC->GetOutputs(AllOutputs); - TempCookFolder = OwningHAC->TemporaryCookFolder.Path; + TempCookFolder = OwningHAC->TemporaryCookFolder.Path; + } + FHoudiniOutputDetails::OnBakeOutputObject( + BakeName, + StaticMesh, + OutputIdentifier, + *FoundOutputObject, + HoudiniGeoPartObject, + OwningHAC, + HoudiniAssetName, + BakeFolder, + TempCookFolder, + InOutput->GetType(), + EHoudiniLandscapeOutputBakeType::InValid, + AllOutputs); } - FHoudiniOutputDetails::OnBakeOutputObject( - BakeName, - StaticMesh, - OutputIdentifier, - HoudiniGeoPartObject, - HoudiniAssetName, - BakeFolder, - TempCookFolder, - InOutput->GetType(), - EHoudiniLandscapeOutputBakeType::InValid, - AllOutputs); return FReply::Handled(); }) @@ -3130,7 +3139,9 @@ FHoudiniOutputDetails::OnBakeOutputObject( const FString& InBakeName, UObject * BakedOutputObject, const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniOutputObject& InOutputObject, const FHoudiniGeoPartObject & HGPO, + const UObject* OutputOwner, const FString & HoudiniAssetName, const FString & BakeFolder, const FString & TempCookFolder, @@ -3141,25 +3152,24 @@ FHoudiniOutputDetails::OnBakeOutputObject( if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) return; - FString ObjectName = InBakeName; - - // Set Object name according to priority Default Name > Attrib Custom Name > UI Custom Name - if(InBakeName.IsEmpty()) - { - if (HGPO.bHasCustomPartName) - ObjectName = HGPO.PartName; - else - ObjectName = BakedOutputObject->GetName(); - } - // Fill in the package params FHoudiniPackageParams PackageParams; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - OutputIdentifier, - BakeFolder, - ObjectName, - HoudiniAssetName); + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + // Determine the relevant WorldContext based on the output owner + UWorld* WorldContext = OutputOwner ? OutputOwner->GetWorld() : GWorld; + const UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(OutputOwner); + check(IsValid(HAC)); + const bool bAutomaticallySetAttemptToLoadMissingPackages = true; + const bool bSkipObjectNameResolutionAndUseDefault = !InBakeName.IsEmpty(); // If InBakeName is set use it as is for the object name + const bool bSkipBakeFolderResolutionAndUseDefault = false; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + WorldContext, HAC, OutputIdentifier, InOutputObject, BakedOutputObject->GetName(), + HoudiniAssetName, PackageParams, Resolver, + BakeFolder, EPackageReplaceMode::ReplaceExistingAssets, + bAutomaticallySetAttemptToLoadMissingPackages, bSkipObjectNameResolutionAndUseDefault, + bSkipBakeFolderResolutionAndUseDefault); switch (Type) { diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h index 138abaab4..a380d94bf 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h @@ -131,7 +131,9 @@ class FHoudiniOutputDetails : public TSharedFromThis const FString& InBakeName, UObject * BakedOutputObject, const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniOutputObject& InOutputObject, const FHoudiniGeoPartObject & HGPO, + const UObject* OutputOwner, const FString & HoudiniAssetName, const FString & BakeFolder, const FString & TempCookFolder, diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp index 24e0f284c..ef908d776 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp @@ -500,7 +500,7 @@ FHoudiniPDGDetails::AddPDGAssetWidget( // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); - InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::AutoBakePDGWorkResultObject); + InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded); // WORK ITEM STATUS { @@ -664,12 +664,12 @@ FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( return false; bool bFound = false; - FWorkItemTally* TallyPtr = nullptr; + const FWorkItemTallyBase* TallyPtr = nullptr; if (bInForSelectedNode) { UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); if (TOPNode && !TOPNode->bHidden) - TallyPtr = &(TOPNode->WorkItemTally); + TallyPtr = &(TOPNode->GetWorkItemTally()); } else TallyPtr = &(InAssetLink->WorkItemTally); @@ -679,25 +679,25 @@ FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( if (InTallyItemString == TEXT("WAITING")) { // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI - OutValue = TallyPtr->WaitingWorkItems + TallyPtr->ScheduledWorkItems; + OutValue = TallyPtr->NumWaitingWorkItems() + TallyPtr->NumScheduledWorkItems(); OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; bFound = true; } else if (InTallyItemString == TEXT("COOKING")) { - OutValue = TallyPtr->CookingWorkItems; + OutValue = TallyPtr->NumCookingWorkItems(); OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; bFound = true; } else if (InTallyItemString == TEXT("COOKED")) { - OutValue = TallyPtr->CookedWorkItems; + OutValue = TallyPtr->NumCookedWorkItems(); OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; bFound = true; } else if (InTallyItemString == TEXT("FAILED")) { - OutValue = TallyPtr->ErroredWorkItems; + OutValue = TallyPtr->NumErroredWorkItems(); OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; bFound = true; } @@ -2462,7 +2462,11 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, [ SNew(STextBlock) .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) + .ToolTipText(LOCTEXT( + "HoudiniEnginePDGBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) ] ]; @@ -2476,7 +2480,11 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, [ SNew(SEditableTextBox) .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEnginePDGBakeFolderTooltip", "Default folder used to store the objects that are generated by this Houdini PDG Asset when baking.")) + .ToolTipText(LOCTEXT( + "HoudiniEnginePDGBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) @@ -2571,7 +2579,7 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, ] .IsChecked_Lambda([InPDGAssetLink]() { - return InPDGAssetLink->bBakeAfterWorkResultObjectLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + return InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) { @@ -2587,11 +2595,11 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, InPDGAssetLink); InPDGAssetLink->Modify(); - InPDGAssetLink->bBakeAfterWorkResultObjectLoaded = bNewState; + InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded = bNewState; // Notify that we have changed the property FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterWorkResultObjectLoaded), InPDGAssetLink); + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterAllWorkResultObjectsLoaded), InPDGAssetLink); }) ] ]; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp index 2dc560544..a28feebb3 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp @@ -4395,7 +4395,7 @@ FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & H ColorGradientEditor->EnableToolTipForceField(true); CurrentRampParameterColorCurve = NewObject( - MainParam, UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + GetTransientPackage(), UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); if (!CurrentRampParameterColorCurve) return nullptr; @@ -4422,6 +4422,7 @@ FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & H RichCurve.Keys.Empty(); } ColorGradientEditor->SetCurveOwner(CurrentRampParameterColorCurve); + CreatedColorGradientEditors.Add(ColorGradientEditor); } else if(MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) { @@ -4467,7 +4468,7 @@ FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & H FloatCurveEditor->EnableToolTipForceField(true); CurrentRampParameterFloatCurve = NewObject( - MainParam, UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + GetTransientPackage(), UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); if (!CurrentRampParameterFloatCurve) return nullptr; @@ -4486,6 +4487,7 @@ FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & H FloatCurveEditor->HoudiniFloatRampCurve = CurrentRampParameterFloatCurve; FloatCurveEditor->SetCurveOwner(CurrentRampParameterFloatCurve, true); + CreatedFloatCurveEditors.Add(FloatCurveEditor); } Row->ValueWidget.Widget = VerticalBox; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h index a953fc74c..2c21be90d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h @@ -65,6 +65,9 @@ class UHoudiniParameterOperatorPath; class UHoudiniParameterRampColorPoint; class UHoudiniParameterRampFloatPoint; +class UHoudiniColorRampCurve; +class UHoudiniFloatRampCurve; + class IDetailCategoryBuilder; class FDetailWidgetRow; class SHorizontalBox; @@ -428,6 +431,10 @@ class FHoudiniParameterDetails : public TSharedFromThis CreatedFloatRampCurves; TArray CreatedColorRampCurves; + // The curve editors reference the UHoudini*Curves as "CurveOwners" as raw (non UObject) pointers, so we have + // to set their owners to null here before we destroy the Created*RampCuvers + TArray> CreatedColorGradientEditors; + TArray> CreatedFloatCurveEditors; private: // The parameter directory is flattened with BFS inside of DFS. diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp index b5791424f..2520b6b90 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp @@ -66,9 +66,12 @@ FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBu DetailBuilder.EditCategory("CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important); DetailBuilder.EditCategory("GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important); DetailBuilder.EditCategory("GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Static Mesh", FText::GetEmpty(), ECategoryPriority::Important); DetailBuilder.EditCategory("GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important); DetailBuilder.EditCategory("StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("PDGSettings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("PDG Settings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Legacy", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); // Create Plugin Information category. { @@ -146,9 +149,7 @@ FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBu CreateHAPILicenseEntry(HAPILicenseType, InformationCategoryBuilder); } - } - - DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); + } } void diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp index d6a8cbc6f..e159cb270 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp @@ -53,22 +53,6 @@ IMPLEMENT_HIT_PROXY(HHoudiniSplineVisProxy, HComponentVisProxy); IMPLEMENT_HIT_PROXY(HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy); IMPLEMENT_HIT_PROXY(HHoudiniSplineCurveSegmentVisProxy, HHoudiniSplineVisProxy); -HHoudiniSplineVisProxy::HHoudiniSplineVisProxy(const UActorComponent * InComponent) - : HComponentVisProxy(InComponent, HPP_Wireframe) -{} - -HHoudiniSplineControlPointVisProxy::HHoudiniSplineControlPointVisProxy( - const UActorComponent * InComponent, int32 InControlPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , ControlPointIndex(InControlPointIndex) -{} - -HHoudiniSplineCurveSegmentVisProxy::HHoudiniSplineCurveSegmentVisProxy( - const UActorComponent * InComponent, int32 InDisplayPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , DisplayPointIndex(InDisplayPointIndex) -{} - FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() : TCommands< FHoudiniSplineComponentVisualizerCommands >( "HoudiniSplineComponentVisualizer", @@ -153,9 +137,8 @@ FHoudiniSplineComponentVisualizer::DrawVisualization( { const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >(Component); - if (!HoudiniSplineComponent + if (!IsValid(HoudiniSplineComponent) || !PDI - || HoudiniSplineComponent->IsPendingKill() || !HoudiniSplineComponent->IsVisible() || !HoudiniSplineComponent->IsHoudiniSplineVisible()) return; @@ -207,13 +190,14 @@ FHoudiniSplineComponentVisualizer::DrawVisualization( for (int32 Index = 0; Index < DisplayPoints.Num(); ++Index) { const FVector & CurrentPoint = DisplayPoints[Index]; - FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); - //CurrentPosition = CurrentPoint; + // Fix incorrect scale when actor has been scaled + //FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); + FVector CurrentPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint); if (Index > 0) { // Add a hitproxy for the line segment PDI->SetHitProxy(new HHoudiniSplineCurveSegmentVisProxy(HoudiniSplineComponent, Index)); - // Draw a line connecting the previous point and the current point + // Draw a line connecting the previous point and the current point PDI->DrawLine(PreviousPosition, CurrentPosition, ColorNormal, SDPG_Foreground); PDI->SetHitProxy(nullptr); } @@ -242,7 +226,6 @@ FHoudiniSplineComponentVisualizer::DrawVisualization( if (Index == 1) DrawColor = ColorNormalHandleSecond; - // If this is an point that being editted if (EditedHoudiniSplineComponent == HoudiniSplineComponent && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Contains(Index)) @@ -501,17 +484,23 @@ FHoudiniSplineComponentVisualizer::GetWidgetLocation( if (EditedControlPointsIndexes.Num() <= 0) return false; - const TArray< FTransform > & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + const TArray& CurvePoints = EditedHoudiniSplineComponent->CurvePoints; // Set the widget location to the center of mass of the selected control points + int32 Sum = 0; FVector CenterLocation = FVector::ZeroVector; - - for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i) + for (int32 EditedIdx = 0; EditedIdx < EditedControlPointsIndexes.Num(); EditedIdx++) { - CenterLocation += CurvePoints[EditedControlPointsIndexes[i]].GetLocation(); + if (!CurvePoints.IsValidIndex(EditedIdx)) + continue; + + CenterLocation += CurvePoints[EditedControlPointsIndexes[EditedIdx]].GetLocation(); + Sum++; } - CenterLocation /= EditedControlPointsIndexes.Num(); + if(Sum > 0) + CenterLocation /= Sum; + OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(CenterLocation); return true; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h index fee4a7497..d98c976d0 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h @@ -28,6 +28,7 @@ #include "HoudiniSplineComponent.h" +#include "HitProxies.h" #include "ComponentVisualizer.h" #include "Framework/Commands/UICommandList.h" #include "Framework/Commands/Commands.h" @@ -38,14 +39,19 @@ class FEditorViewportClient; struct HHoudiniSplineVisProxy : public HComponentVisProxy { DECLARE_HIT_PROXY(); - HHoudiniSplineVisProxy(const UActorComponent * InComponent); + HHoudiniSplineVisProxy(const UActorComponent * InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) + {} }; /** Proxy for a spline control point. **/ struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy { DECLARE_HIT_PROXY(); - HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex); + HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , ControlPointIndex(InControlPointIndex) + {} int32 ControlPointIndex; }; @@ -54,7 +60,10 @@ struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy struct HHoudiniSplineCurveSegmentVisProxy : public HHoudiniSplineVisProxy { DECLARE_HIT_PROXY(); - HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 IndisplayPointIndex); + HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 InDisplayPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , DisplayPointIndex(InDisplayPointIndex) + {} int32 DisplayPointIndex; }; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp index cd4b5f54c..0c78efa42 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp @@ -38,6 +38,7 @@ #include "HoudiniParameterFloat.h" #include "HoudiniParameterToggle.h" #include "HoudiniInput.h" +#include "HoudiniEngineRuntimePrivatePCH.h" #if WITH_EDITOR #include "Editor.h" @@ -166,7 +167,10 @@ UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() UHoudiniOutput* InstanceOutput = nullptr; InstanceOutput = Outputs[i]; - check(InstanceOutput) + //check(InstanceOutput) + if (!InstanceOutput || InstanceOutput->IsPendingKill()) + continue; + // Ensure that instance outputs won't delete houdini content. // Houdini content should only be allowed to be deleted from // the component template. @@ -567,12 +571,17 @@ UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent(UHoudiniAssetBlu else { InstanceOutput = TemplateOutput->DuplicateAndCopyProperties(this, FName(TemplateOutput->GetName())); - InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); + if (IsValid(InstanceOutput)) + InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); } - InstanceOutput->SetCanDeleteHoudiniNodes(false); Outputs[i] = InstanceOutput; + if (!IsValid(InstanceOutput)) + continue; + + InstanceOutput->SetCanDeleteHoudiniNodes(false); + TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); TMap& InstanceOutputObjects = InstanceOutput->GetOutputObjects(); TArray StaleOutputObjects; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp index c71e5e896..fd4871cd4 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp @@ -539,7 +539,6 @@ UHoudiniAssetComponent::ConvertLegacyData() StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; StaticMeshGenerationProperties.GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier = Version1CompatibilityHAC->GeneratedLpvBiasMultiplier; - StaticMeshGenerationProperties.GeneratedDistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; @@ -547,6 +546,8 @@ UHoudiniAssetComponent::ConvertLegacyData() //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; StaticMeshGenerationProperties.GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; + StaticMeshBuildSettings.DistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; + BakeFolder.Path = Version1CompatibilityHAC->BakeFolder.ToString(); TemporaryCookFolder.Path = Version1CompatibilityHAC->TempCookFolder.ToString(); @@ -659,6 +660,7 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; + bOverrideGlobalProxyStaticMeshSettings = false; const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); if (HoudiniRuntimeSettings) { @@ -668,7 +670,15 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; } - + else + { + bEnableProxyStaticMeshOverride = false; + bEnableProxyStaticMeshRefinementByTimerOverride = true; + ProxyMeshAutoRefineTimeoutSecondsOverride = 10.0f; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = true; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = true; + } + bNoProxyMeshNextCookRequested = false; bBakeAfterNextCook = false; @@ -702,6 +712,11 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object bNeverNeedsRenderUpdate = false; Bounds = FBox(ForceInitToZero); + + LastTickTime = 0.0; + + // Initialize the default SM Build settings with the plugin's settings default values + StaticMeshBuildSettings = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); } UHoudiniAssetComponent::~UHoudiniAssetComponent() @@ -734,7 +749,6 @@ void UHoudiniAssetComponent::PostInitProperties() StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; StaticMeshGenerationProperties.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; - StaticMeshGenerationProperties.GeneratedDistanceFieldResolutionScale = HoudiniRuntimeSettings->GeneratedDistanceFieldResolutionScale; } // Register ourself to the HER singleton @@ -867,6 +881,7 @@ UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const } } + void UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) { @@ -1212,6 +1227,27 @@ UHoudiniAssetComponent::MarkAsNeedCook() CurrentParam->SetNeedsToTriggerUpdate(true); } + // We need to mark all of our editable curves as changed + for (auto Output : Outputs) + { + if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) + continue; + + for (auto& OutputObjectEntry : Output->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; + if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + continue; + + UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + continue; + + // This sets bHasChanged and bNeedsToTriggerUpdate + SplineComponent->MarkChanged(true); + } + } + // We need to mark all our inputs as changed/trigger update for (auto CurrentInput : Inputs) { @@ -1220,6 +1256,29 @@ UHoudiniAssetComponent::MarkAsNeedCook() CurrentInput->MarkChanged(true); CurrentInput->SetNeedsToTriggerUpdate(true); CurrentInput->MarkDataUploadNeeded(true); + + // In addition to marking the input as changed/need update, we also need to make sure that any changes on the + // Unreal side have been recorded for the input before sending to Houdini. For that we also mark each input + // object as changed/need update and explicitly call the Update function on each input object. For example, for + // input actors this would recreate the Houdini input actor components from the actor's components, picking up + // any new components since the last call to Update. + TArray* InputObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); + if (InputObjectArray && InputObjectArray->Num() > 0) + { + for (auto CurrentInputObject : *InputObjectArray) + { + if (!IsValid(CurrentInputObject)) + continue; + + UObject* const Object = CurrentInputObject->GetObject(); + if (IsValid(Object)) + CurrentInputObject->Update(Object); + + CurrentInputObject->MarkChanged(true); + CurrentInputObject->SetNeedsToTriggerUpdate(true); + CurrentInputObject->MarkTransformChanged(true); + } + } } // Clear the static mesh bake timer @@ -1261,6 +1320,27 @@ UHoudiniAssetComponent::MarkAsNeedRebuild() CurrentParam->SetNeedsToTriggerUpdate(true); } + // We need to mark all of our editable curves as changed + for (auto Output : Outputs) + { + if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) + continue; + + for (auto& OutputObjectEntry : Output->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; + if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + continue; + + UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + continue; + + // This sets bHasChanged and bNeedsToTriggerUpdate + SplineComponent->MarkChanged(true); + } + } + // We need to mark all our inputs as changed/trigger update for (auto CurrentInput : Inputs) { @@ -1385,6 +1465,8 @@ UHoudiniAssetComponent::PostLoad() // Register our PDG Asset link if we have any + // From v1: + UpdateRenderingInformation(); } void @@ -2664,14 +2746,12 @@ UHoudiniAssetComponent::SetStaticMeshGenerationProperties(UStaticMesh* InStaticM } } + // TODO // Set method for LOD texture factor computation. - /* TODO_414 //InStaticMesh->bUseMaximumStreamingTexelRatio = StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; - // Set distance where textures using UV 0 are streamed in/out. - GOES ON COMPONENT // InStaticMesh->StreamingDistanceMultiplier = StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; - */ - + // Add user data. for (int32 AssetUserDataIdx = 0; AssetUserDataIdx < StaticMeshGenerationProperties.GeneratedAssetUserData.Num(); AssetUserDataIdx++) InStaticMesh->AddAssetUserData(StaticMeshGenerationProperties.GeneratedAssetUserData[AssetUserDataIdx]); @@ -2697,10 +2777,67 @@ UHoudiniAssetComponent::SetStaticMeshGenerationProperties(UStaticMesh* InStaticM // Assign walkable slope behavior. BodySetup->WalkableSlopeOverride = StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride; - BodySetup->WalkableSlopeOverride = StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride; // We want to use all of geometry for collision detection purposes. BodySetup->bMeshCollideAll = true; #endif +} + + +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 LocalAttachChildren; + GetChildrenComponents(true, LocalAttachChildren); + for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) + { + USceneComponent * SceneComponent = *Iter; + if (IsValid(SceneComponent)) + SceneComponent->RecreatePhysicsState(); + } + + // Since we have new asset, we need to update bounds. + UpdateBounds(); +} + + +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(&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); } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h index 1fb2164e2..69668f86a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h @@ -35,6 +35,7 @@ #include "HoudiniInputTypes.h" #include "HoudiniPluginSerializationVersion.h" +#include "Engine/EngineTypes.h" #include "Components/PrimitiveComponent.h" #include "Components/SceneComponent.h" @@ -310,7 +311,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone void MarkAsBlueprintStructureModified(); // The blueprint has been modified but not structurally changed. void MarkAsBlueprintModified(); - + // void SetAssetCookCount(const int32& InCount) { AssetCookCount = InCount; }; // @@ -397,6 +398,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // return the cached component template, if available. virtual UHoudiniAssetComponent* GetCachedTemplate() const { return nullptr; } + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + //------------------------------------------------------------------------------------------------ // Supported Features //------------------------------------------------------------------------------------------------ @@ -480,6 +483,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // //static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); + // Updates physics state, bounds, and mark render state dirty + // Should be call PostLoad and PostProcessing + void UpdateRenderingInformation(); + public: // Houdini Asset associated with this component. @@ -519,17 +526,23 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone UPROPERTY() FDirectoryPath TemporaryCookFolder; - // Folder used for baking this asset's outputs + // Folder used for baking this asset's outputs (unless set by prim/detail attribute on the output). Falls back to + // the default from the plugin settings if not set. UPROPERTY() FDirectoryPath BakeFolder; - // The method to use to create the mesh + // The method used to create Static Meshes UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 0)) EHoudiniStaticMeshMethod StaticMeshMethod; + // Generation properties for the Static Meshes generated by this Houdini Asset UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 1)/*, meta = (ShowOnlyInnerProperties)*/) FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; + // Build Settings to be used when generating the Static Meshes for this Houdini Asset + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 2)) + FMeshBuildSettings StaticMeshBuildSettings; + // Override the global fast proxy mesh settings on this component? UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere/*, meta = (DisplayAfter = "StaticMeshGenerationProperties")*/) bool bOverrideGlobalProxyStaticMeshSettings; @@ -554,8 +567,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) bool bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; - - #if WITH_EDITORONLY_DATA UPROPERTY() bool bGenerateMenuExpanded; @@ -731,4 +742,9 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // Object used to convert V1 HAC to V2 HAC UHoudiniAssetComponent_V1* Version1CompatibilityHAC; + + // The last timestamp this component was ticked + // used to prioritize/limit the number of HAC processed per tick + UPROPERTY(Transient) + double LastTickTime; }; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h index df2294efb..b5c8ace83 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h @@ -124,76 +124,115 @@ HOUDINIENGINEEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) #endif -// HOUDINI_ENGINE_DEBUG_BP: blueprint related debug logging -#ifndef HOUDINI_ENGINE_DEBUG_BP - #define HOUDINI_ENGINE_DEBUG_BP 0 -#endif -// NOTE: Set HOUDINI_ENGINE_DEBUG_BP to enable BP logging -#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_BP - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); +#define HOUDINI_DEBUG_EXPAND_UE_LOG(LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngine##LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); - - #define HOUDINI_BP_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngineBlueprint, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) +// --------------------------------------------------------- +// Blueprint Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_BP=1 to enable Blueprint logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BP) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_BP_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() - #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() + #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) #endif -// HOUDINI_ENGINE_DEBUG_PDG: PDG related debug logging -#ifndef HOUDINI_ENGINE_DEBUG_PDG - #define HOUDINI_ENGINE_DEBUG_PDG 0 -#endif -// NOTE: Set HOUDINI_ENGINE_DEBUG_PDG to enable BP logging -#if defined(HOUDINI_ENGINE_LOGGING) && HOUDINI_ENGINE_DEBUG_PDG +// --------------------------------------------------------- +// PDG Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_PDG=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_PDG) DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEnginePDG, Log, All); - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); - - #define HOUDINI_PDG_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEnginePDG, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - + DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_PDG_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() - #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() + #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + +// --------------------------------------------------------- +// Landscape Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_LANDSCAPE=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineLandscape, Log, All); + #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineLandscape); + #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() + #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) #endif + +// --------------------------------------------------------- +// Baking Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_BAKING=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BAKING) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBaking, Log, All); + #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineBaking); + #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() + #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + // HAPI_Common enum HAPI_UNREAL_NodeType { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp index 71c480e88..313185d36 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp @@ -29,6 +29,7 @@ #include "HoudiniRuntimeSettings.h" #include "EngineUtils.h" +#include "Engine/EngineTypes.h" #if WITH_EDITOR #include "Editor.h" @@ -554,9 +555,92 @@ FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties() SMGP.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; SMGP.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; SMGP.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; - SMGP.GeneratedDistanceFieldResolutionScale = HoudiniRuntimeSettings->GeneratedDistanceFieldResolutionScale; } return SMGP; } +FMeshBuildSettings +FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings() +{ + FMeshBuildSettings DefaultBuildSettings; + + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + if(HoudiniRuntimeSettings) + { + DefaultBuildSettings.bRemoveDegenerates = HoudiniRuntimeSettings->bRemoveDegenerates; + DefaultBuildSettings.bUseMikkTSpace = HoudiniRuntimeSettings->bUseMikkTSpace; + DefaultBuildSettings.bBuildAdjacencyBuffer = HoudiniRuntimeSettings->bBuildAdjacencyBuffer; + DefaultBuildSettings.MinLightmapResolution = HoudiniRuntimeSettings->MinLightmapResolution; + DefaultBuildSettings.bUseFullPrecisionUVs = HoudiniRuntimeSettings->bUseFullPrecisionUVs; + DefaultBuildSettings.SrcLightmapIndex = HoudiniRuntimeSettings->SrcLightmapIndex; + DefaultBuildSettings.DstLightmapIndex = HoudiniRuntimeSettings->DstLightmapIndex; + + DefaultBuildSettings.bComputeWeightedNormals = HoudiniRuntimeSettings->bComputeWeightedNormals; + DefaultBuildSettings.bBuildReversedIndexBuffer = HoudiniRuntimeSettings->bBuildReversedIndexBuffer; + DefaultBuildSettings.bUseHighPrecisionTangentBasis = HoudiniRuntimeSettings->bUseHighPrecisionTangentBasis; + DefaultBuildSettings.bGenerateDistanceFieldAsIfTwoSided = HoudiniRuntimeSettings->bGenerateDistanceFieldAsIfTwoSided; + DefaultBuildSettings.bSupportFaceRemap = HoudiniRuntimeSettings->bSupportFaceRemap; + DefaultBuildSettings.DistanceFieldResolutionScale = HoudiniRuntimeSettings->DistanceFieldResolutionScale; + + // Recomputing normals. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag; + switch (RecomputeNormalFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bRecomputeNormals = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bRecomputeNormals = true; + break; + } + } + + // Recomputing tangents. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; + switch (RecomputeTangentFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bRecomputeTangents = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bRecomputeTangents = true; + break; + } + } + + // Lightmap UV generation. + EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; + switch (GenerateLightmapUVFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bGenerateLightmapUVs = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bGenerateLightmapUVs = true; + break; + } + } + } + + return DefaultBuildSettings; +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h index ec854d9bb..ba13761a0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h @@ -40,6 +40,7 @@ class AActor; class UWorld; struct FHoudiniStaticMeshGenerationProperties; +struct FMeshBuildSettings; template class TSubclassOf; @@ -54,9 +55,12 @@ struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils // Return platform specific name of libHAPI. static FString GetLibHAPIName(); - // Reterurns default SM Generation Properties using the default settings values + // Returns default SM Generation Properties using the default settings values static FHoudiniStaticMeshGenerationProperties GetDefaultStaticMeshGenerationProperties(); + // Returns default SM Build Settings using the default settings values + static FMeshBuildSettings GetDefaultMeshBuildSettings(); + // ----------------------------------------------- // Bounding Box utilities // ----------------------------------------------- diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp index f92cace38..4a0e42e70 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp @@ -512,6 +512,9 @@ FHoudiniGenericAttribute::ModifyPropertyValueOnObject( // Determine the container to use (either InContainer if specified, or InObject) void* Container = InContainer ? InContainer : InObject; + // Property class name, used for logging + const FString PropertyClassName = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); + // Initialize using the found property FProperty* InnerProperty = FoundProperty; @@ -535,445 +538,286 @@ FHoudiniGenericAttribute::ModifyPropertyValueOnObject( bHasModifiedProperty = true; }; - int32 NumberOfProperties = 1; FArrayProperty* ArrayProperty = CastField(FoundProperty); + TSharedPtr ArrayHelper; if (ArrayProperty) { InnerProperty = ArrayProperty->Inner; - NumberOfProperties = ArrayProperty->ArrayDim; - - // Do we need to add values to the array? - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - - //ArrayHelper.ExpandForIndex( InGenericAttribute.AttributeTupleSize - 1 ); - if (InGenericAttribute.AttributeTupleSize > NumberOfProperties) - { - ArrayHelper.Resize(InGenericAttribute.AttributeTupleSize); - NumberOfProperties = InGenericAttribute.AttributeTupleSize; - } + ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); } + // TODO: implement support for array attributes received from Houdini + // Get the "proper" AtIndex in the flat array by using the attribute tuple size // TODO: fix the issue when changing array of tuple properties! - int32 AtIndex = InAtIndex * InGenericAttribute.AttributeTupleSize; - - for (int32 nPropIdx = 0; nPropIdx < NumberOfProperties; nPropIdx++) + const int32 TupleSize = InGenericAttribute.AttributeTupleSize; + int32 AtIndex = InAtIndex * TupleSize; + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) { - if (FFloatProperty* FloatProperty = CastField(InnerProperty)) - { - // FLOAT PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + // Supported non-struct properties - if (ValuePtr) - { - FloatProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(FloatProperty); - } - } - else - { - double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + // If the attribute from Houdini has a tuple size > 1, we support setting it on arrays on the unreal side + // For example: a 3 float from Houdini can be set as a TArray in Unreal. + + // If this is an ArrayProperty, ensure that it is at least large enough for our tuple + // TODO: should we just set this to exactly our tuple size? + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleSize - 1); - if (ValuePtr) - { - FloatProperty->SetFloatingPointPropertyValue(ValuePtr, Value); - OnPropertyChanged(FloatProperty); - } - } - } - else if (FIntProperty* IntProperty = CastField(InnerProperty)) + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) { - // INT PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - IntProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(IntProperty); - } + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); } else { - int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - IntProperty->SetIntPropertyValue(ValuePtr, Value); - OnPropertyChanged(IntProperty); - } - } - } - else if (FBoolProperty* BoolProperty = CastField(InnerProperty)) - { - // BOOL PROPERTY - bool Value = InGenericAttribute.GetBoolValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim + // on the property to determine if our TupleIndex is in range, if not, give up, we cannot set any more + // of our tuple indices on this property. + if (TupleIndex >= InnerProperty->ArrayDim) + break; - if (ValuePtr) - { - BoolProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(BoolProperty); + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); } - } - else if (FStrProperty* StringProperty = CastField(InnerProperty)) - { - // STRING PROPERTY - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } if (ValuePtr) { - StringProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(StringProperty); - } - } - else if (FNumericProperty *NumericProperty = CastField(InnerProperty)) - { - // NUMERIC PROPERTY - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - NumericProperty->SetNumericPropertyValueFromString(ValuePtr, *Value); - OnPropertyChanged(NumericProperty); - } - } - else if (NumericProperty->IsInteger()) - { - int64 Value = InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + Property->SetNumericPropertyValueFromString(ValuePtr, *InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); + } + else if (Property->IsFloatingPoint()) + { + Property->SetFloatingPointPropertyValue(ValuePtr, InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex)); + } + else if (Property->IsInteger()) + { + Property->SetIntPropertyValue(ValuePtr, InGenericAttribute.GetIntValue(AtIndex + TupleIndex)); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } } - - if (ValuePtr) + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) { - NumericProperty->SetIntPropertyValue(ValuePtr, (int64)Value); - OnPropertyChanged(NumericProperty); + FBoolProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetBoolValue(AtIndex + TupleIndex)); } - } - else if (NumericProperty->IsFloatingPoint()) - { - double Value = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + FStrProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); } - else + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); + FNameProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, FName(*InGenericAttribute.GetStringValue(AtIndex + TupleIndex))); } - if (ValuePtr) - { - NumericProperty->SetFloatingPointPropertyValue(ValuePtr, Value); - OnPropertyChanged(NumericProperty); - } + OnPropertyChanged(InnerProperty); } else { - // Numeric Property was found, but is of an unsupported type - HOUDINI_LOG_MESSAGE(TEXT("Unsupported Numeric UProperty")); + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); + if (TupleIndex == 0) + return false; } } - else if (FNameProperty* NameProperty = CastField(InnerProperty)) - { - // NAME PROPERTY - FString StringValue = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - FName Value = FName(*StringValue); - - void * ValuePtr = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); - } - else - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties + + // If we receive an attribute with tuple size > 1 and the target is an Unreal struct property, then we set + // as many of the values as we can in the struct. For example: a 4-float received from Houdini where the + // target is an FVector, the FVector.X, Y and Z would be set from the 4-float indices 0-2. Index 3 from the + // 4-float would then be ignored. + + const int32 TupleIndex = 0; + // If this is an array property, ensure it has enough space + // TODO: should we just set the array size to 1 for non-arrays and to the array size for arrays (once we support array attributes from Houdini)? + // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleIndex); + + void* PropertyValue = nullptr; + if (ArrayHelper.IsValid()) + PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); + else + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - if (ValuePtr) - { - NameProperty->SetPropertyValue(ValuePtr, Value); - OnPropertyChanged(NameProperty); - } - } - else if (FStructProperty* StructProperty = CastField(InnerProperty)) + if (PropertyValue) { - // STRUCT PROPERTY const FName PropertyName = StructProperty->Struct->GetFName(); if (PropertyName == NAME_Vector) { - FVector* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - // Found a vector property, fill it with the 3 tuple values - PropertyValue->X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); - PropertyValue->Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - PropertyValue->Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - OnPropertyChanged(StructProperty); - } + // Found a vector property, fill it with up to 3 tuple values + FVector& Vector = *static_cast(PropertyValue); + Vector = FVector::ZeroVector; + Vector.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + Vector.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Vector.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + + OnPropertyChanged(StructProperty); } else if (PropertyName == NAME_Transform) { - FTransform* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - // Found a transform property fill it with the attribute tuple values - FVector Translation; - Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 0); - Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - - FQuat Rotation; - Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); - Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 4); - Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 5); - Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 6); - - FVector Scale; - Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 7); - Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 8); - Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 9); - - PropertyValue->SetTranslation(Translation); - PropertyValue->SetRotation(Rotation); - PropertyValue->SetScale3D(Scale); - - OnPropertyChanged(StructProperty); - } + // Found a transform property fill it with the attribute tuple values + FVector Translation; + Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + + FQuat Rotation; + if (InGenericAttribute.AttributeTupleSize > 3) + Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); + if (InGenericAttribute.AttributeTupleSize > 4) + Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 4); + if (InGenericAttribute.AttributeTupleSize > 5) + Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 5); + if (InGenericAttribute.AttributeTupleSize > 6) + Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 6); + + FVector Scale(1, 1, 1); + if (InGenericAttribute.AttributeTupleSize > 7) + Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 7); + if (InGenericAttribute.AttributeTupleSize > 8) + Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 8); + if (InGenericAttribute.AttributeTupleSize > 9) + Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 9); + + FTransform& Transform = *static_cast(PropertyValue); + Transform = FTransform::Identity; + Transform.SetTranslation(Translation); + Transform.SetRotation(Rotation); + Transform.SetScale3D(Scale); + + OnPropertyChanged(StructProperty); } else if (PropertyName == NAME_Color) { - FColor* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->R = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - PropertyValue->G = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); - PropertyValue->B = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 2); - if (InGenericAttribute.AttributeTupleSize == 4) - PropertyValue->A = (int8)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 3); - - OnPropertyChanged(StructProperty); - } + FColor& Color = *static_cast(PropertyValue); + Color = FColor::Black; + Color.R = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Color.G = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Color.B = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + Color.A = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 3); + + OnPropertyChanged(StructProperty); } else if (PropertyName == NAME_LinearColor) { - FLinearColor* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->R = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - PropertyValue->G = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - PropertyValue->B = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 2); - if (InGenericAttribute.AttributeTupleSize == 4) - PropertyValue->A = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 3); - - OnPropertyChanged(StructProperty); - } + FLinearColor& LinearColor = *static_cast(PropertyValue); + LinearColor = FLinearColor::Black; + LinearColor.R = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + LinearColor.G = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + LinearColor.B = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + LinearColor.A = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); + + OnPropertyChanged(StructProperty); } else if (PropertyName == "Int32Interval") { - FInt32Interval* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (PropertyValue) - { - PropertyValue->Min = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx); - PropertyValue->Max = (float)InGenericAttribute.GetIntValue(AtIndex + nPropIdx + 1); + FInt32Interval& Interval = *static_cast(PropertyValue); + Interval = FInt32Interval(); + Interval.Min = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Interval.Max = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); - OnPropertyChanged(StructProperty); - } + OnPropertyChanged(StructProperty); } else if (PropertyName == "FloatInterval") { - FFloatInterval* PropertyValue = nullptr; - if (ArrayProperty) - { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - PropertyValue = reinterpret_cast(ArrayHelper.GetRawPtr(nPropIdx)); - } - else - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } + FFloatInterval& Interval = *static_cast(PropertyValue); + Interval = FFloatInterval(); + Interval.Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Interval.Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); - if (PropertyValue) - { - PropertyValue->Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx); - PropertyValue->Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + nPropIdx + 1); - - OnPropertyChanged(StructProperty); - } + OnPropertyChanged(StructProperty); } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; } - else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + // OBJECT PATH PROPERTY + const int32 TupleIndex = 0; + // If this is an array property, ensure it has enough space + // TODO: should we just set the array size to 1 for non-arrays or to the array size for arrays (once we support array attributes from Houdini)? + // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleIndex); + + FString Value = InGenericAttribute.GetStringValue(AtIndex + TupleIndex); + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + else + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + + if (ValuePtr) { - // OBJECT PATH PROPERTY - FString Value = InGenericAttribute.GetStringValue(AtIndex + nPropIdx); - void * ValuePtr = nullptr; - if (ArrayProperty) + TSoftObjectPtr ValueObjectPtr; + ValueObjectPtr = Value; + UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); + + // Ensure the ObjectProperty class matches the ValueObject that we just loaded + if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) { - FScriptArrayHelper_InContainer ArrayHelper(ArrayProperty, Container); - ValuePtr = ArrayHelper.GetRawPtr(nPropIdx); + ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); + OnPropertyChanged(ObjectProperty); } else { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, nPropIdx); - } - - if (ValuePtr) - { - TSoftObjectPtr ValueObjectPtr; - ValueObjectPtr = Value; - UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); - - // Ensure the ObjectProperty class matches the ValueObject that we just loaded - if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) - { - ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); - OnPropertyChanged(ObjectProperty); - } + HOUDINI_LOG_WARNING( + TEXT("Could net set object property %s: ObjectProperty's object class (%s) does not match referenced object class (%s)!"), + *InGenericAttribute.AttributeName, *(ObjectProperty->PropertyClass->GetName()), IsValid(ValueObject) ? *(ValueObject->GetClass()->GetName()) : TEXT("NULL")); + return false; } } else { - // Property was found, but is of an unsupported type - FString PropertyClass = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); - HOUDINI_LOG_MESSAGE(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClass, *InGenericAttribute.AttributeName); + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); return false; } } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); + return false; + } if (bHasModifiedProperty) { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp index b2860ca5b..6475e2be0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp @@ -73,8 +73,8 @@ UHoudiniInput::UHoudiniInput() , bCookOnCurveChanged(true) , bStaticMeshChanged(false) , bInputAssetConnectedInHoudini(false) - , bSwitchedToCurve(false) , DefaultCurveOffset(0.f) + , bAddRotAndScaleAttributesOnCurves(false) , bIsWorldInputBoundSelector(false) , bWorldInputBoundSelectorAutoUpdate(false) , UnrealSplineResolution(50.0f) @@ -98,6 +98,8 @@ UHoudiniInput::UHoudiniInput() const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; + + bAddRotAndScaleAttributesOnCurves = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bAddRotAndScaleAttributesOnCurves : false; } void @@ -1709,9 +1711,9 @@ UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) UHoudiniInputActor* InputActor = Cast(InputObj); if (InputActor && !InputActor->IsPendingKill()) { - if (InputActor->ActorComponents.Num() > 0) + if (InputActor->GetActorComponents().Num() > 0) { - Num += (InputActor->ActorComponents.Num() - 1); + Num += (InputActor->GetActorComponents().Num() - 1); } } } @@ -2218,6 +2220,18 @@ UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, co return true; } +void +UHoudiniInput::SetAddRotAndScaleAttributes(const bool& InValue) +{ + if (bAddRotAndScaleAttributesOnCurves == InValue) + return; + + bAddRotAndScaleAttributesOnCurves = InValue; + + // Mark all input obj as changed + MarkAllInputObjectsChanged(true); +} + #if WITH_EDITOR FText UHoudiniInput::GetCurrentSelectionText() const diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h index 7efc3fe39..c00e2084b 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h @@ -205,10 +205,11 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject void GetAllHoudiniInputSceneComponents(TArray& OutObjects) const; void GetAllHoudiniInputSplineComponents(TArray& OutObjects) const; - // Remove all instances of this input object from all object arrays. void RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject); + bool IsAddRotAndScaleAttributesEnabled() const { return bAddRotAndScaleAttributesOnCurves; }; + //------------------------------------------------------------------------------------------------ // Mutators //------------------------------------------------------------------------------------------------ @@ -293,6 +294,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject void SetScaleOffsetY(float InValue, int32 AtIndex); void SetScaleOffsetZ(float InValue, int32 AtIndex); + void SetAddRotAndScaleAttributes(const bool& InValue); // Duplicate this object and copy its state to the resulting object. // This is typically used to transfer state between between template and instance components. @@ -439,7 +441,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject // Geometry objects UPROPERTY() TArray GeometryInputObjects; - // ?? TArray GeometryInputObjects; // Is set to true when static mesh used for geometry input has changed. UPROPERTY() @@ -456,7 +457,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject // Asset inputs UPROPERTY() TArray AssetInputObjects; - // ?? TArray AssetInputObjects; // Is set to true if the asset input is actually connected inside Houdini. UPROPERTY() @@ -466,20 +466,19 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject // Curve/Spline inputs UPROPERTY() TArray CurveInputObjects; - // ?? TArray CurveInputObjects; - // Is set to true when choice switches to curve mode. + // Offset used when using muiltiple curves UPROPERTY() - bool bSwitchedToCurve; + float DefaultCurveOffset; + // Set this to true to add rot and scale attributes on curve inputs. UPROPERTY() - float DefaultCurveOffset; + bool bAddRotAndScaleAttributesOnCurves; //------------------------------------------------------------------------------------------------------------------------- // Landscape inputs UPROPERTY() TArray LandscapeInputObjects; - // ?? TArray LandscapeInputObjects; UPROPERTY() bool bLandscapeHasExportTypeChanged = false; @@ -488,10 +487,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject // World inputs UPROPERTY() TArray WorldInputObjects; - // ?? TArray WorldInputObjects; // Objects used for automatic bound selection - // ?? TArray WorldInputBoundSelectorObjects; UPROPERTY() TArray WorldInputBoundSelectorObjects; @@ -511,7 +508,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject // Skeletal Inputs UPROPERTY() TArray SkeletalInputObjects; - // ?? TArray SkeletalInputObjects; public: diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp index 5172fe1bf..7a56207a9 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp @@ -204,6 +204,8 @@ UHoudiniInputHoudiniAsset::UHoudiniInputHoudiniAsset(const FObjectInitializer& O // UHoudiniInputActor::UHoudiniInputActor(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) + , LastUpdateNumComponentsAdded(0) + , LastUpdateNumComponentsRemoved(0) { } @@ -1157,6 +1159,8 @@ UHoudiniInputHoudiniAsset::Update(UObject * InObject) void UHoudiniInputActor::Update(UObject * InObject) { + const bool bHasInputObjectChanged = InputObject != InObject; + Super::Update(InObject); AActor* Actor = Cast(InObject); @@ -1166,31 +1170,152 @@ UHoudiniInputActor::Update(UObject * InObject) { Transform = Actor->GetTransform(); - // The actor's components that can be sent as inputs - ActorComponents.Empty(); + // If we are updating (InObject == InputObject), then remove stale components and add new components, + // if InObject != InputObject, remove all components and rebuild + + if (bHasInputObjectChanged) + { + // The actor's components that can be sent as inputs + LastUpdateNumComponentsRemoved = ActorComponents.Num(); + + ActorComponents.Empty(); + ActorSceneComponents.Empty(); + + TArray AllComponents; + Actor->GetComponents(AllComponents, true); + + int32 CompIdx = 0; + ActorComponents.SetNum(AllComponents.Num()); + for (USceneComponent * SceneComponent : AllComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( + SceneComponent, GetOuter(), Actor->GetName()); + if (!InputObj) + continue; - TArray AllComponents; - Actor->GetComponents(AllComponents, true); + UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); + if (!SceneInput) + continue; - int32 CompIdx = 0; - ActorComponents.SetNum(AllComponents.Num()); - for (USceneComponent * SceneComponent : AllComponents) + ActorComponents[CompIdx++] = SceneInput; + ActorSceneComponents.Add(TSoftObjectPtr(SceneComponent)); + } + ActorComponents.SetNum(CompIdx); + LastUpdateNumComponentsAdded = CompIdx; + } + else { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = 0; + + // Look for any components to add or remove + TSet NewComponents; + const bool bIncludeFromChildActors = true; + Actor->ForEachComponent(bIncludeFromChildActors, [&](USceneComponent* InComp) + { + if (IsValid(InComp)) + { + if (!ActorSceneComponents.Contains(InComp)) + { + NewComponents.Add(InComp); + } + } + }); + + // Update the actor input components (from the same actor) + TArray ComponentIndicesToRemove; + const int32 NumActorComponents = ActorComponents.Num(); + for (int32 Index = 0; Index < NumActorComponents; ++Index) + { + UHoudiniInputSceneComponent* CurActorComp = ActorComponents[Index]; + if (!CurActorComp || CurActorComp->IsPendingKill()) + { + ComponentIndicesToRemove.Add(Index); + continue; + } + + // Does the component still exist on Actor? + UObject* const CompObj = CurActorComp->GetObject(); + // Make sure the actor is still valid + if (!CompObj || CompObj->IsPendingKill()) + { + // If it's not, mark it for deletion + if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) + { + CurActorComp->InvalidateData(); + } + + ComponentIndicesToRemove.Add(Index); + continue; + } + } + + // Remove the destroyed/invalid components + const int32 NumToRemove = ComponentIndicesToRemove.Num(); + if (NumToRemove > 0) + { + for (int32 Index = NumToRemove - 1; Index >= 0; --Index) + { + const int32& IndexToRemove = ComponentIndicesToRemove[Index]; + + UHoudiniInputSceneComponent* const CurActorComp = ActorComponents[IndexToRemove]; + if (CurActorComp) + ActorSceneComponents.Remove(CurActorComp->InputObject); + + const bool bAllowShrink = false; + ActorComponents.RemoveAtSwap(IndexToRemove, 1, bAllowShrink); + + LastUpdateNumComponentsRemoved++; + } + } - UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( - SceneComponent, GetOuter(), Actor->GetName()); - if (!InputObj) - continue; + if (NewComponents.Num() > 0) + { + for (USceneComponent * SceneComponent : NewComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( + SceneComponent, GetOuter(), Actor->GetName()); + if (!InputObj) + continue; - UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); - if (!SceneInput) - continue; + UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); + if (!SceneInput) + continue; - ActorComponents[CompIdx++] = SceneInput; + ActorComponents.Add(SceneInput); + ActorSceneComponents.Add(SceneComponent); + + LastUpdateNumComponentsAdded++; + } + } + + if (LastUpdateNumComponentsAdded > 0 || LastUpdateNumComponentsRemoved > 0) + { + ActorComponents.Shrink(); + } + } + } + else + { + // If we don't have a valid actor or null, delete any input components we still have and mark as changed + if (ActorComponents.Num() > 0) + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = ActorComponents.Num(); + ActorComponents.Empty(); + ActorSceneComponents.Empty(); + } + else + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = 0; } - ActorComponents.SetNum(CompIdx); } } diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h index 6142a016f..b02d32c3f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h @@ -518,14 +518,21 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputCameraComponent : public UHoudiniInp public: + UPROPERTY() float FOV; + + UPROPERTY() float AspectRatio; + UPROPERTY() //TEnumAsByte ProjectionType; bool bIsOrthographic; + UPROPERTY() float OrthoWidth; + UPROPERTY() float OrthoNearClipPlane; + UPROPERTY() float OrthoFarClipPlane; }; @@ -585,11 +592,30 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject // AActor accessor AActor* GetActor(); -public: + const TArray& GetActorComponents() const { return ActorComponents; } + + // The number of components added with the last call to Update + int32 GetLastUpdateNumComponentsAdded() const { return LastUpdateNumComponentsAdded; } + // The number of components remove with the last call to Update + int32 GetLastUpdateNumComponentsRemoved() const { return LastUpdateNumComponentsRemoved; } + +protected: // The actor's components that can be sent as inputs UPROPERTY() TArray ActorComponents; + + // The USceneComponents the actor had the last time we called Update (matches the ones in ActorComponents). + UPROPERTY() + TSet> ActorSceneComponents; + + // The number of components added with the last call to Update + UPROPERTY() + int32 LastUpdateNumComponentsAdded; + + // The number of components remove with the last call to Update + UPROPERTY() + int32 LastUpdateNumComponentsRemoved; }; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp index 520ca1c07..d7661d317 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp @@ -263,6 +263,56 @@ FHoudiniOutputObjectIdentifier::Matches(const FHoudiniGeoPartObject& InHGPO) con } +// ---------------------------------------------------- +// FHoudiniBakedOutputObjectIdentifier +// ---------------------------------------------------- + + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier() +{ + PartId = -1; + SplitIdentifier = FString(); +} + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier( + const int32& InPartId, const FString& InSplitIdentifier) +{ + PartId = InPartId; + SplitIdentifier = InSplitIdentifier; +} + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + PartId = InIdentifier.PartId; + SplitIdentifier = InIdentifier.SplitIdentifier; +} + +uint32 +FHoudiniBakedOutputObjectIdentifier::GetTypeHash() const +{ + const int32 HashBuffer = PartId; + const int32 Hash = FCrc::MemCrc32((void *)&HashBuffer, sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitIdentifier, Hash); +} + +uint32 +GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) +{ + return InIdentifier.GetTypeHash(); +} + +bool +FHoudiniBakedOutputObjectIdentifier::operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const +{ + return (InIdentifier.PartId == PartId && InIdentifier.SplitIdentifier.Equals(SplitIdentifier)); +} + + +// ---------------------------------------------------- +// FHoudiniBakedOutputObject +// ---------------------------------------------------- + + FHoudiniBakedOutputObject::FHoudiniBakedOutputObject() : Actor() , ActorBakeName(NAME_None) @@ -536,6 +586,15 @@ UHoudiniOutput::Clear() Type = EHoudiniOutputType::Invalid; } +bool +UHoudiniOutput::ShouldDeferClear() const +{ + if (Type == EHoudiniOutputType::Landscape) + return true; + + return false; +} + const bool UHoudiniOutput::HasGeoChanged() const { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h index a73e3551d..dcff22373 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h @@ -175,6 +175,37 @@ struct HOUDINIENGINERUNTIME_API FHoudiniOutputObjectIdentifier /** Function used by hashing containers to create a unique hash for this type of object. **/ HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier); +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObjectIdentifier +{ + GENERATED_USTRUCT_BODY() + +public: + // Constructors + FHoudiniBakedOutputObjectIdentifier(); + FHoudiniBakedOutputObjectIdentifier(const int32& InPartId, const FString& InSplitIdentifier); + FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); + + // Return hash value for this object, used when using this object as a key inside hashing containers. + uint32 GetTypeHash() const; + + // Comparison operator, used by hashing containers. + bool operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const; + +public: + + // PartId + UPROPERTY() + int32 PartId = -1; + + // String identifier for the split that created this + UPROPERTY() + FString SplitIdentifier = FString(); +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier); + USTRUCT() struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput { @@ -303,7 +334,7 @@ struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutput public: UPROPERTY() - TMap BakedOutputObjects; + TMap BakedOutputObjects; }; USTRUCT() @@ -335,6 +366,10 @@ struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject UPROPERTY() bool bProxyIsCurrent = false; + // Implicit output objects shouldn't be created as actors / components in the scene. + UPROPERTY() + bool bIsImplicit = false; + // Bake Name override for this output object UPROPERTY() FString BakeName; @@ -496,6 +531,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject void Clear(); + bool ShouldDeferClear() const; + protected: virtual void BeginDestroy() override; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp index c1b2c06a9..31cc58d44 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp @@ -70,7 +70,7 @@ UHoudiniPDGAssetLink::UHoudiniPDGAssetLink(const FObjectInitializer& ObjectIniti PDGBakeSelectionOption = EPDGBakeSelectionOption::All; PDGBakePackageReplaceMode = EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; bRecenterBakedActors = false; - bBakeAfterWorkResultObjectLoaded = false; + bBakeAfterAllWorkResultObjectsLoaded = false; #endif // Folder used for baking PDG outputs @@ -86,6 +86,7 @@ FTOPWorkResultObject::FTOPWorkResultObject() Name = FString(); FilePath = FString(); State = EPDGWorkResultState::None; + WorkItemResultInfoIndex = INDEX_NONE; } FTOPWorkResultObject::~FTOPWorkResultObject() @@ -116,8 +117,184 @@ FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const return true; } +void +FTOPWorkResult::ClearAndDestroyResultObjects() +{ + if (ResultObjects.Num() <= 0) + return; + + for (FTOPWorkResultObject& ResultObject : ResultObjects) + { + ResultObject.DestroyResultOutputsAndRemoveOutputActor(); + } + + ResultObjects.Empty(); +} + +int32 +FTOPWorkResult::IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) +{ + const int32 NumEntries = ResultObjects.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResultObject& CurResultObject = ResultObjects[Index]; + if (CurResultObject.WorkItemResultInfoIndex == InWorkItemResultInfoIndex) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResultObject* +FTOPWorkResult::GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) +{ + const int32 ArrayIndex = IndexOfWorkResultObjectByHAPIResultInfoIndex(InWorkItemResultInfoIndex); + if (ArrayIndex == INDEX_NONE) + { + return nullptr; + } + + return GetWorkResultObjectByArrayIndex(ArrayIndex); +} + +FTOPWorkResultObject* +FTOPWorkResult::GetWorkResultObjectByArrayIndex(const int32& InArrayIndex) +{ + if (!ResultObjects.IsValidIndex(InArrayIndex)) + { + return nullptr; + } + + return &ResultObjects[InArrayIndex]; +} + + +FWorkItemTallyBase::~FWorkItemTallyBase() +{ +} + +bool +FWorkItemTallyBase::AreAllWorkItemsComplete() const +{ + return ( + NumWaitingWorkItems() == 0 && NumCookingWorkItems() == 0 && NumScheduledWorkItems() == 0 + && (NumWorkItems() == (NumCookedWorkItems() + NumErroredWorkItems())) ); +} + +bool +FWorkItemTallyBase::AnyWorkItemsFailed() const +{ + return NumErroredWorkItems() > 0; +} + +bool +FWorkItemTallyBase::AnyWorkItemsPending() const +{ + return (NumWorkItems() > 0 && (NumWaitingWorkItems() > 0 || NumCookingWorkItems() > 0 || NumScheduledWorkItems() > 0)); +} + +FString +FWorkItemTallyBase::ProgressRatio() const +{ + const float Ratio = NumWorkItems() > 0 ? (NumCookedWorkItems() / NumWorkItems()) * 100.f : 0; + + return FString::Printf(TEXT("%.1f%%"), Ratio); +} + FWorkItemTally::FWorkItemTally() +{ + AllWorkItems.Empty(); + WaitingWorkItems.Empty(); + ScheduledWorkItems.Empty(); + CookingWorkItems.Empty(); + CookedWorkItems.Empty(); + ErroredWorkItems.Empty(); + CookCancelledWorkItems.Empty(); +} + +void +FWorkItemTally::ZeroAll() +{ + AllWorkItems.Empty(); + WaitingWorkItems.Empty(); + ScheduledWorkItems.Empty(); + CookingWorkItems.Empty(); + CookedWorkItems.Empty(); + ErroredWorkItems.Empty(); + CookCancelledWorkItems.Empty(); +} + +void +FWorkItemTally::RemoveWorkItem(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + AllWorkItems.Remove(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsWaiting(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + WaitingWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsScheduled(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + ScheduledWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCooking(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookingWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCooked(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookedWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsErrored(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + ErroredWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCookCancelled(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookCancelledWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RemoveWorkItemFromAllStateSets(int32 InWorkItemID) +{ + WaitingWorkItems.Remove(InWorkItemID); + ScheduledWorkItems.Remove(InWorkItemID); + CookingWorkItems.Remove(InWorkItemID); + CookedWorkItems.Remove(InWorkItemID); + ErroredWorkItems.Remove(InWorkItemID); + CookCancelledWorkItems.Remove(InWorkItemID); +} + + +FAggregatedWorkItemTally::FAggregatedWorkItemTally() { TotalWorkItems = 0; WaitingWorkItems = 0; @@ -125,10 +302,11 @@ FWorkItemTally::FWorkItemTally() CookingWorkItems = 0; CookedWorkItems = 0; ErroredWorkItems = 0; + CookCancelledWorkItems = 0; } void -FWorkItemTally::ZeroAll() +FAggregatedWorkItemTally::ZeroAll() { TotalWorkItems = 0; WaitingWorkItems = 0; @@ -136,34 +314,31 @@ FWorkItemTally::ZeroAll() CookingWorkItems = 0; CookedWorkItems = 0; ErroredWorkItems = 0; + CookCancelledWorkItems = 0; } -bool -FWorkItemTally::AreAllWorkItemsComplete() const -{ - return ( - WaitingWorkItems == 0 && CookingWorkItems == 0 && ScheduledWorkItems == 0 - && (TotalWorkItems == (CookedWorkItems + ErroredWorkItems)) ); -} - -bool -FWorkItemTally::AnyWorkItemsFailed() const -{ - return ErroredWorkItems > 0; -} - -bool -FWorkItemTally::AnyWorkItemsPending() const +void +FAggregatedWorkItemTally::Add(const FWorkItemTallyBase& InWorkItemTally) { - return (TotalWorkItems > 0 && (WaitingWorkItems > 0 || CookingWorkItems > 0 || ScheduledWorkItems > 0)); + TotalWorkItems += InWorkItemTally.NumWorkItems(); + WaitingWorkItems += InWorkItemTally.NumWaitingWorkItems(); + ScheduledWorkItems += InWorkItemTally.NumScheduledWorkItems(); + CookingWorkItems += InWorkItemTally.NumCookingWorkItems(); + CookedWorkItems += InWorkItemTally.NumCookedWorkItems(); + ErroredWorkItems += InWorkItemTally.NumErroredWorkItems(); + CookCancelledWorkItems += InWorkItemTally.NumCookCancelledWorkItems(); } -FString -FWorkItemTally::ProgressRatio() const +void +FAggregatedWorkItemTally::Subtract(const FWorkItemTallyBase& InWorkItemTally) { - float Ratio = TotalWorkItems > 0 ? (CookedWorkItems / TotalWorkItems) * 100.f : 0; - - return FString::Printf(TEXT("%.1f%%"), Ratio); + TotalWorkItems -= InWorkItemTally.NumWorkItems(); + WaitingWorkItems -= InWorkItemTally.NumWaitingWorkItems(); + ScheduledWorkItems -= InWorkItemTally.NumScheduledWorkItems(); + CookingWorkItems -= InWorkItemTally.NumCookingWorkItems(); + CookedWorkItems -= InWorkItemTally.NumCookedWorkItems(); + ErroredWorkItems -= InWorkItemTally.NumErroredWorkItems(); + CookCancelledWorkItems -= InWorkItemTally.NumCookCancelledWorkItems(); } @@ -187,6 +362,8 @@ UTOPNode::UTOPNode() bHasChildNodes = false; bShow = false; + + InvalidateLandscapeCache(); } bool @@ -209,6 +386,33 @@ UTOPNode::Reset() { NodeState = EPDGNodeState::None; WorkItemTally.ZeroAll(); + AggregatedWorkItemTally.ZeroAll(); +} + +void UTOPNode::OnWorkItemWaiting(int32 InWorkItemID) +{ + FTOPWorkResult* const WorkItem = GetWorkResultByID(InWorkItemID); + if (WorkItem) + { + // Clear the bAutoBakedSinceLastLoad flag on the work results since we are expecting a cook of the work item + for (FTOPWorkResultObject& WRO : WorkItem->ResultObjects) + { + WRO.SetAutoBakedSinceLastLoad(false); + } + } + WorkItemTally.RecordWorkItemAsWaiting(InWorkItemID); +} + +void +UTOPNode::OnWorkItemCooked(int32 InWorkItemID) +{ + if (GetWorkItemTally().NumCookedWorkItems() == 0) + { + // We want to invalidate the landscape cache values in any situation where + // all the work items are being recooked. + InvalidateLandscapeCache(); + } + WorkItemTally.RecordWorkItemAsCooked(InWorkItemID); } void @@ -320,30 +524,37 @@ UTOPNode::DeleteWorkResultOutputObjects() } FString -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex) +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex) +{ + return FString::Printf(TEXT("%d_%d"), InWorkItemIndex, InWorkResultObjectArrayIndex); +} + +FString +UTOPNode::GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex) { - return FString::Printf(TEXT("%d_%d"), InWorkResultIndex, InWorkResultObjectIndex); + return GetBakedWorkResultObjectOutputsKey(InWorkResult.WorkItemIndex, InWorkResultObjectArrayIndex); } bool -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const { // Check that indices are valid - if (!WorkResult.IsValidIndex(InWorkResultIndex)) + if (!WorkResult.IsValidIndex(InWorkResultArrayIndex)) return false; - if (!WorkResult[InWorkResultIndex].ResultObjects.IsValidIndex(InWorkResultObjectIndex)) + const FTOPWorkResult& WorkResultEntry = WorkResult[InWorkResultArrayIndex]; + if (!WorkResultEntry.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) return false; - OutKey = GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex); + OutKey = GetBakedWorkResultObjectOutputsKey(WorkResultEntry, InWorkResultObjectArrayIndex); return true; } bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) { FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) return false; OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); if (!OutBakedOutput) @@ -353,10 +564,10 @@ UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkR } bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const { FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultIndex, InWorkResultObjectIndex, Key)) + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) return false; OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); if (!OutBakedOutput) @@ -365,6 +576,76 @@ UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkR return true; } +int32 +UTOPNode::IndexOfWorkResultByID(const int32& InWorkItemID) +{ + const int32 NumEntries = WorkResult.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResult& CurResult = WorkResult[Index]; + if (CurResult.WorkItemID == InWorkItemID) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByID(const int32& InWorkItemID) +{ + const int32 ArrayIndex = IndexOfWorkResultByID(InWorkItemID); + if (!WorkResult.IsValidIndex(ArrayIndex)) + return nullptr; + + return &WorkResult[ArrayIndex]; +} + +int32 +UTOPNode::IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) +{ + const int32 NumEntries = WorkResult.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResult& CurResult = WorkResult[Index]; + if (CurResult.WorkItemIndex == InWorkItemIndex && (!bInWithInvalidWorkItemID || CurResult.WorkItemID == INDEX_NONE)) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) +{ + const int32 ArrayIndex = IndexOfWorkResultByHAPIIndex(InWorkItemIndex, bInWithInvalidWorkItemID); + if (!WorkResult.IsValidIndex(ArrayIndex)) + return nullptr; + return &WorkResult[ArrayIndex]; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByArrayIndex(const int32& InArrayIndex) +{ + if (!WorkResult.IsValidIndex(InArrayIndex)) + return nullptr; + return &WorkResult[InArrayIndex]; +} + +bool +UTOPNode::IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const +{ + if (!IsValid(InNetwork)) + { + return false; + } + + return ParentName == FString::Printf(TEXT("%s_%s"), *InNetwork->ParentName, *InNetwork->NodeName); +} + #if WITH_EDITOR void UTOPNode::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) @@ -402,6 +683,19 @@ UTOPNode::PostTransacted(const FTransactionObjectEvent& TransactionEvent) } #endif +void +UTOPNode::OnDirtyNode() +{ + InvalidateLandscapeCache(); +} + +void +UTOPNode::InvalidateLandscapeCache() +{ + LandscapeReferenceLocation.bIsCached = false; + LandscapeSizeInfo.bIsCached = false; +} + UTOPNetwork::UTOPNetwork() { NodeId = -1; @@ -673,10 +967,12 @@ UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) { if (!IsValid(TOPNode)) return; + + TOPNode->OnDirtyNode(); for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) { - DestroyWorkItemResultData(CurrentWorkResult, TOPNode); + DestroyWorkItemResultData(CurrentWorkResult); } TOPNode->WorkResult.Empty(); @@ -717,7 +1013,7 @@ UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNod FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); if (WorkResult) { - DestroyWorkItemResultData(*WorkResult, InTOPNode); + DestroyWorkItemResultData(*WorkResult); // TODO: Should we destroy the FTOPWorkResult struct entirely here? //TOPNode.WorkResult.RemoveByPredicate } @@ -744,16 +1040,7 @@ UHoudiniPDGAssetLink::GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InT { if (!IsValid(InTOPNode)) return nullptr; - - for(FTOPWorkResult& CurResult : InTOPNode->WorkResult) - { - if (CurResult.WorkItemID == InWorkItemID) - { - return &CurResult; - } - } - - return nullptr; + return InTOPNode->GetWorkResultByID(InWorkItemID); } FDirectoryPath @@ -772,22 +1059,13 @@ UHoudiniPDGAssetLink::GetTemporaryCookFolder() const void UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) { - ResultObject.DestroyResultOutputs(); - ResultObject.GetOutputActorOwner().DestroyOutputActor(); + ResultObject.DestroyResultOutputsAndRemoveOutputActor(); } void -UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode) +UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result) { - if (Result.ResultObjects.Num() <= 0) - return; - - for (FTOPWorkResultObject& ResultObject : Result.ResultObjects) - { - DestoryWorkResultObjectData(ResultObject); - } - - Result.ResultObjects.Empty(); + Result.ClearAndDestroyResultObjects(); } @@ -824,7 +1102,7 @@ UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* I FString PrefixPath = InNode->NodePath; if (!PrefixPath.EndsWith("/")) PrefixPath += "/"; - InNode->WorkItemTally.ZeroAll(); + InNode->ZeroWorkItemTally(); InNode->NodeState = EPDGNodeState::None; TMap NodeStateOrder; @@ -844,15 +1122,10 @@ UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* I if (Node->NodePath.StartsWith(PrefixPath) && !Node->bHasChildNodes) { - InNode->WorkItemTally.TotalWorkItems += Node->WorkItemTally.TotalWorkItems; - InNode->WorkItemTally.WaitingWorkItems += Node->WorkItemTally.WaitingWorkItems; - InNode->WorkItemTally.ScheduledWorkItems += Node->WorkItemTally.ScheduledWorkItems; - InNode->WorkItemTally.CookingWorkItems += Node->WorkItemTally.CookingWorkItems; - InNode->WorkItemTally.CookedWorkItems += Node->WorkItemTally.CookedWorkItems; - InNode->WorkItemTally.ErroredWorkItems += Node->WorkItemTally.ErroredWorkItems; - const int8 VisistedNodeState = NodeStateOrder.FindChecked(Node->NodeState); - if (VisistedNodeState > CurrentState) - CurrentState = VisistedNodeState; + InNode->AggregateTallyFromChildNode(Node); + const int8 VisitedNodeState = NodeStateOrder.FindChecked(Node->NodeState); + if (VisitedNodeState > CurrentState) + CurrentState = VisitedNodeState; } } @@ -882,12 +1155,7 @@ UHoudiniPDGAssetLink::UpdateWorkItemTally() } else { - WorkItemTally.TotalWorkItems += CurrentTOPNode->WorkItemTally.TotalWorkItems; - WorkItemTally.WaitingWorkItems += CurrentTOPNode->WorkItemTally.WaitingWorkItems; - WorkItemTally.ScheduledWorkItems += CurrentTOPNode->WorkItemTally.ScheduledWorkItems; - WorkItemTally.CookingWorkItems += CurrentTOPNode->WorkItemTally.CookingWorkItems; - WorkItemTally.CookedWorkItems += CurrentTOPNode->WorkItemTally.CookedWorkItems; - WorkItemTally.ErroredWorkItems += CurrentTOPNode->WorkItemTally.ErroredWorkItems; + WorkItemTally.Add(CurrentTOPNode->GetWorkItemTally()); } } } @@ -905,7 +1173,7 @@ UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork) if (!IsValid(CurTOPNode)) continue; - CurTOPNode->WorkItemTally.ZeroAll(); + CurTOPNode->ZeroWorkItemTally(); } } @@ -1270,30 +1538,32 @@ FTOPWorkResultObject::DestroyResultOutputs() #endif } - // do not delete FISMC that still have instances left - // as we have cleaned up our instances before, these have been hand-placed - if (HISMC->GetInstanceCount() > 0) - bDestroyComponent = false; - } + // // do not delete FISMC that still have instances left + // // as we have cleaned up our instances before, these have been hand-placed + // if (HISMC->GetInstanceCount() > 0) + bDestroyComponent = false; - if (bDestroyComponent) + OutputObject.OutputComponent = nullptr; + } + } + + if (bDestroyComponent) + { + USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) { - USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from its actor first - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - // Detach from its parent component if attached - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - bDidDestroyObjects = true; - - OutputObject.OutputComponent = nullptr; - } + // Remove from its actor first + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + // Detach from its parent component if attached + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + bDidDestroyObjects = true; + + OutputObject.OutputComponent = nullptr; } } } @@ -1329,7 +1599,7 @@ FTOPWorkResultObject::DestroyResultOutputs() ResultOutputs.Empty(); if (bDidDestroyObjects) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); // Delete the output objects we found if (OutputObjectsToDelete.Num() > 0) @@ -1351,6 +1621,12 @@ FTOPWorkResultObject::DestroyResultOutputs() #endif } +void FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor() +{ + DestroyResultOutputs(); + GetOutputActorOwner().DestroyOutputActor(); +} + bool FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h index 5ad250ecb..5ebbcc7ba 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h @@ -31,7 +31,7 @@ #include "HoudiniAsset.h" #include "HoudiniAssetComponent.h" - +#include "HoudiniTranslatorTypes.h" #include "HoudiniPDGAssetLink.generated.h" @@ -149,6 +149,14 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject // Get the OutputActor owner struct const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } + // Destroy the ResultOutputs and remove the output actor. + void DestroyResultOutputsAndRemoveOutputActor(); + + // Getter for bAutoBakedSinceLastLoad: indicates if this work result object has been auto-baked since it's last load. + bool AutoBakedSinceLastLoad() const { return bAutoBakedSinceLastLoad; } + // Setter for bAutoBakedSinceLastLoad + void SetAutoBakedSinceLastLoad(bool bInAutoBakedSinceLastLoad) { bAutoBakedSinceLastLoad = bInAutoBakedSinceLastLoad; } + public: UPROPERTY(NonTransactional) @@ -157,6 +165,9 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject FString FilePath; UPROPERTY(NonTransactional) EPDGWorkResultState State; + // The index in the WorkItemResultInfo array of this item as it was received from HAPI. + UPROPERTY(NonTransactional) + int32 WorkItemResultInfoIndex; protected: // UPROPERTY() @@ -165,6 +176,10 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject UPROPERTY(NonTransactional) TArray ResultOutputs; + // If true, indicates that the work result object has been auto-baked since it was last loaded. + UPROPERTY(NonTransactional) + bool bAutoBakedSinceLastLoad; + private: // List of objects to delete, internal use only (DestroyResultOutputs) UPROPERTY(NonTransactional) @@ -187,6 +202,16 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResult // Comparison operator, used by hashing containers and arrays. bool operator==(const FTOPWorkResult& OtherWorkResult) const; + // Calls FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor on each entry in ResultObjects and clears the array. + void ClearAndDestroyResultObjects(); + + // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. + int32 IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); + // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. + FTOPWorkResultObject* GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); + // Return the FTOPWorkResultObject at InArrayIndex in the ResultObjects array, or nullptr if InArrayIndex is not a valid index. + FTOPWorkResultObject* GetWorkResultObjectByArrayIndex(const int32& InArrayIndex); + public: UPROPERTY(NonTransactional) @@ -210,9 +235,42 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResult */ }; +USTRUCT() +struct HOUDINIENGINERUNTIME_API FWorkItemTallyBase +{ + GENERATED_USTRUCT_BODY() + +public: + virtual ~FWorkItemTallyBase(); + + // + // Mutators + // + + // Zero all counts, including total. + virtual void ZeroAll() {}; + + // + // Accessors + // + + bool AreAllWorkItemsComplete() const; + bool AnyWorkItemsFailed() const; + bool AnyWorkItemsPending() const; + + virtual int32 NumWorkItems() const { return 0; }; + virtual int32 NumWaitingWorkItems() const { return 0; }; + virtual int32 NumScheduledWorkItems() const { return 0; }; + virtual int32 NumCookingWorkItems() const { return 0; }; + virtual int32 NumCookedWorkItems() const { return 0; }; + virtual int32 NumErroredWorkItems() const { return 0; }; + virtual int32 NumCookCancelledWorkItems() const { return 0; }; + + FString ProgressRatio() const; +}; USTRUCT() -struct HOUDINIENGINERUNTIME_API FWorkItemTally +struct HOUDINIENGINERUNTIME_API FWorkItemTally : public FWorkItemTallyBase { GENERATED_USTRUCT_BODY() @@ -221,16 +279,82 @@ struct HOUDINIENGINERUNTIME_API FWorkItemTally // Constructor FWorkItemTally(); - void ZeroAll(); + // + // Mutators + // + + // Empty all state sets, as well as AllWorkItems. + virtual void ZeroAll() override; - bool AreAllWorkItemsComplete() const; - bool AnyWorkItemsFailed() const; - bool AnyWorkItemsPending() const; + // Remove a work item from all state sets and AllWorkItems. + void RemoveWorkItem(int32 InWorkItemID); - FString ProgressRatio() const; + void RecordWorkItemAsWaiting(int32 InWorkItemID); + void RecordWorkItemAsScheduled(int32 InWorkItemID); + void RecordWorkItemAsCooking(int32 InWorkItemID); + void RecordWorkItemAsCooked(int32 InWorkItemID); + void RecordWorkItemAsErrored(int32 InWorkItemID); + void RecordWorkItemAsCookCancelled(int32 InWorkItemID); + + // + // Accessors + // + + virtual int32 NumWorkItems() const override { return AllWorkItems.Num(); } + virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems.Num(); } + virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems.Num(); } + virtual int32 NumCookingWorkItems() const override { return CookingWorkItems.Num(); } + virtual int32 NumCookedWorkItems() const override { return CookedWorkItems.Num(); } + virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems.Num(); } + virtual int32 NumCookCancelledWorkItems() const override { return CookCancelledWorkItems.Num(); } + +protected: + + // Removes the work item id from all state sets (but not from AllWorkItems -- use RemoveWorkItem for that). + void RemoveWorkItemFromAllStateSets(int32 InWorkItemID); + + // We use sets to keep track of in what state a work item is. The set stores the WorkItemID. + + UPROPERTY() + TSet AllWorkItems; + UPROPERTY() + TSet WaitingWorkItems; + UPROPERTY() + TSet ScheduledWorkItems; + UPROPERTY() + TSet CookingWorkItems; + UPROPERTY() + TSet CookedWorkItems; + UPROPERTY() + TSet ErroredWorkItems; + UPROPERTY() + TSet CookCancelledWorkItems; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FAggregatedWorkItemTally : public FWorkItemTallyBase +{ + GENERATED_USTRUCT_BODY() public: + // Constructor + FAggregatedWorkItemTally(); + + virtual void ZeroAll() override; + + void Add(const FWorkItemTallyBase& InWorkItemTally); + + void Subtract(const FWorkItemTallyBase& InWorkItemTally); + + virtual int32 NumWorkItems() const override { return TotalWorkItems; } + virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems; } + virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems; } + virtual int32 NumCookingWorkItems() const override { return CookingWorkItems; } + virtual int32 NumCookedWorkItems() const override { return CookedWorkItems; } + virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems; } + +protected: UPROPERTY() int32 TotalWorkItems; UPROPERTY() @@ -243,6 +367,9 @@ struct HOUDINIENGINERUNTIME_API FWorkItemTally int32 CookedWorkItems; UPROPERTY() int32 ErroredWorkItems; + UPROPERTY() + int32 CookCancelledWorkItems; + }; // Container for baked outputs of a PDG work result object. @@ -257,13 +384,15 @@ struct HOUDINIENGINERUNTIME_API FHoudiniPDGWorkResultObjectBakedOutput TArray BakedOutputs; }; +// Forward declare the UTOPNetwork here for some references in the UTOPNode +class UTOPNetwork; + UCLASS() class HOUDINIENGINERUNTIME_API UTOPNode : public UObject { GENERATED_BODY() public: - // Constructor UTOPNode(); @@ -272,9 +401,53 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject void Reset(); - bool AreAllWorkItemsComplete() const { return WorkItemTally.AreAllWorkItemsComplete(); }; - bool AnyWorkItemsFailed() const { return WorkItemTally.AnyWorkItemsFailed(); }; - bool AnyWorkItemsPending() const { return WorkItemTally.AnyWorkItemsPending(); }; + const FWorkItemTallyBase& GetWorkItemTally() const + { + if (bHasChildNodes) + return AggregatedWorkItemTally; + return WorkItemTally; + } + + void AggregateTallyFromChildNode(const UTOPNode* InChildNode) + { + if (IsValid(InChildNode)) + AggregatedWorkItemTally.Add(InChildNode->GetWorkItemTally()); + } + + bool AreAllWorkItemsComplete() const { return GetWorkItemTally().AreAllWorkItemsComplete(); }; + bool AnyWorkItemsFailed() const { return GetWorkItemTally().AnyWorkItemsFailed(); }; + bool AnyWorkItemsPending() const { return GetWorkItemTally().AnyWorkItemsPending(); }; + void ZeroWorkItemTally() + { + WorkItemTally.ZeroAll(); + AggregatedWorkItemTally.ZeroAll(); + } + + // Called by PDG manager when work item events are received + + // Notification that a work item has been created + void OnWorkItemCreated(int32 InWorkItemID) { }; + + // Notification that a work item has been removed. + void OnWorkItemRemoved(int32 InWorkItemID) { WorkItemTally.RemoveWorkItem(InWorkItemID); }; + + // Notification that a work item has moved to the waiting state. + void OnWorkItemWaiting(int32 InWorkItemID); + + // Notification that a work item has been scheduled. + void OnWorkItemScheduled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsScheduled(InWorkItemID); }; + + // Notification that a work item has started cooking. + void OnWorkItemCooking(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCooking(InWorkItemID); }; + + // Notification that a work item has been cooked. + void OnWorkItemCooked(int32 InWorkItemID); + + // Notification that a work item has errored. + void OnWorkItemErrored(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsErrored(InWorkItemID); }; + + // Notification that a work item cook has been cancelled. + void OnWorkItemCookCancelled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCookCancelled(InWorkItemID); }; bool IsVisibleInLevel() const { return bShow; } void SetVisibleInLevel(bool bInVisible); @@ -297,14 +470,36 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject // Get the OutputActor owner struct const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } - // Get the baked outputs from the last bake. The map keys are [work_result_index]_[work_result_object_index] + // Get the baked outputs from the last bake. The map keys are [work_result.work_item_index]_[work_result_object_index] TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } - bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FString& OutKey) const; - static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkResultIndex, int32 InWorkResultObjectIndex); - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultIndex, int32 InWorkResultObjectIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; - + // Helper to construct the key used to look up baked work results. + static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex); + // Helper to construct the key used to look up baked work results. + static FString GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex); + // Helper to construct the key used to look up baked work results. + bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const; + // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object. + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); + // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object (const version). + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; + + // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. + int32 IndexOfWorkResultByID(const int32& InWorkItemID); + // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. + FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID); + // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. + // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. + int32 IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); + // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. + // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. + FTOPWorkResult* GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); + // Return the FTOPWorkResult at InArrayIndex in the WorkResult array, or nullptr if InArrayIndex is not a valid index. + FTOPWorkResult* GetWorkResultByArrayIndex(const int32& InArrayIndex); + + // Returns true if InNetwork is the parent TOP Net of this node. + bool IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const; + #if WITH_EDITOR void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; #endif @@ -338,9 +533,6 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject UPROPERTY(Transient, NonTransactional) EPDGNodeState NodeState; - UPROPERTY(Transient, NonTransactional) - FWorkItemTally WorkItemTally; - // This is set when the TOP node's work items are processed by // FHoudiniPDGManager based on if any NotLoaded work result objects are found UPROPERTY(NonTransactional) @@ -355,7 +547,25 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject UPROPERTY(NonTransactional) bool bHasChildNodes; + // These notification events have been introduced so that we can start encapsulating code. + // in this class as opposed to modifying this object in various places throughout the codebase. + + // Notification that this TOP node has been dirtied. + void OnDirtyNode(); + + // Accessors for the landscape data caches + FHoudiniLandscapeExtent& GetLandscapeExtent() { return LandscapeExtent; } + FHoudiniLandscapeReferenceLocation& GetLandscapeReferenceLocation() { return LandscapeReferenceLocation; } + FHoudiniLandscapeTileSizeInfo& GetLandscapeSizeInfo() { return LandscapeSizeInfo; } + protected: + void InvalidateLandscapeCache(); + + // Value caches used during landscape tile creation. + FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; + FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; + FHoudiniLandscapeExtent LandscapeExtent; + // Visible in the level UPROPERTY() bool bShow; @@ -364,6 +574,13 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject UPROPERTY() TMap BakedWorkResultObjectOutputs; + // This node's own work items, used when bHasChildNodes is false. + UPROPERTY(Transient, NonTransactional) + FWorkItemTally WorkItemTally; + // This node's aggregated work item tallys (sum of child work item tally, use when bHasChildNodes is true) + UPROPERTY(Transient, NonTransactional) + FAggregatedWorkItemTally AggregatedWorkItemTally; + private: UPROPERTY() FOutputActorOwner OutputActorOwner; @@ -423,7 +640,7 @@ class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject class UHoudiniPDGAssetLink; -DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemId*/, const FString& /*WorkResultObjectName*/); +DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemHAPIIndex*/, int32 /*WorkItemResultInfoIndex*/); UCLASS() class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject @@ -515,7 +732,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject void ClearAllTOPData(); - static void DestroyWorkItemResultData(FTOPWorkResult& Result, UTOPNode* InTOPNode); + static void DestroyWorkItemResultData(FTOPWorkResult& Result); static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); @@ -560,7 +777,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject UPROPERTY(NonTransactional) int32 NumWorkitems; UPROPERTY(Transient, NonTransactional) - FWorkItemTally WorkItemTally; + FAggregatedWorkItemTally WorkItemTally; UPROPERTY() FString OutputCachePath; @@ -610,9 +827,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject UPROPERTY() bool bRecenterBakedActors; - // Auto-bake: if this is true, it indicates that a work result object should be baked after it is loaded. + // Auto-bake: if this is true, it indicates that once all work result objects for the node is loaded they should + // all be baked UPROPERTY() - bool bBakeAfterWorkResultObjectLoaded; + bool bBakeAfterAllWorkResultObjectsLoaded; // The delegate handle of the auto bake helper function bound to OnWorkResultObjectLoaded. FDelegateHandle AutoBakeDelegateHandle; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp index 9bcc4c9a8..e80c6b5a8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp @@ -1,403 +1,406 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniRuntimeSettings.h" - - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Misc/Paths.h" -// #include "Internationalization/Internationalization.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - - -FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() - : bGeneratedDoubleSidedGeometry(false) - , GeneratedPhysMaterial(nullptr) - , GeneratedCollisionTraceFlag(CTF_UseDefault) - , GeneratedLightMapResolution(64) - , GeneratedLpvBiasMultiplier(1.0f) - , GeneratedDistanceFieldResolutionScale(2.0f) - , GeneratedWalkableSlopeOverride() - , GeneratedLightMapCoordinateIndex(1) - , bGeneratedUseMaximumStreamingTexelRatio(false) - , GeneratedStreamingDistanceMultiplier(1.0f) - , GeneratedFoliageDefaultSettings(nullptr) - , GeneratedAssetUserData() -{ - DefaultBodyInstance.SetCollisionProfileName("BlockAll"); -} - - -UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) - : Super( ObjectInitializer ) -{ - // Session options. - SessionType = HRSST_NamedPipe; - ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; - ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; - ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; - bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; - AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; - - bSyncWithHoudiniCook = true; - bCookUsingHoudiniTime = true; - bSyncViewport = false; - bSyncHoudiniViewport = false; - bSyncUnrealViewport = false; - - // Instantiating options. - bShowMultiAssetDialog = true; - - // Cooking options. - bPauseCookingOnStart = false; - bDisplaySlateCookingNotifications = true; - DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - // Parameter options - //bTreatRampParametersAsMultiparms = false; - - // Custom Houdini location. - bUseCustomHoudiniLocation = false; - CustomHoudiniLocation.Path = TEXT(""); - - // Arguments for HAPI_Initialize - CookingThreadStackSize = -1; - - // Landscape marshalling default values. - MarshallingLandscapesUseDefaultUnrealScaling = false; - MarshallingLandscapesUseFullResolution = true; - MarshallingLandscapesForceMinMaxValues = false; - MarshallingLandscapesForcedMinValue = -2000.0f; - MarshallingLandscapesForcedMaxValue = 4553.0f; - - // Spline marshalling - MarshallingSplineResolution = 50.0f; - - // Static mesh proxy refinement settings - bEnableProxyStaticMesh = false; - bShowDefaultMesh = true; - bEnableProxyStaticMeshRefinementByTimer = true; - ProxyMeshAutoRefineTimeoutSeconds = 10.0f; - bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; - bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; - - // Generated StaticMesh settings. - bDoubleSidedGeometry = false; - PhysMaterial = nullptr; - DefaultBodyInstance.SetCollisionProfileName("BlockAll"); - CollisionTraceFlag = CTF_UseDefault; - LightMapResolution = 32; - LpvBiasMultiplier = 1.0f; - LightMapCoordinateIndex = 1; - bUseMaximumStreamingTexelRatio = false; - StreamingDistanceMultiplier = 1.0f; - GeneratedDistanceFieldResolutionScale = 0.0f; - - // Static Mesh build settings. - bUseFullPrecisionUVs = false; - SrcLightmapIndex = 0; - DstLightmapIndex = 1; - MinLightmapResolution = 64; - bRemoveDegenerates = true; - GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; - RecomputeNormalsFlag = HRSRF_OnlyIfMissing; - RecomputeTangentsFlag = HRSRF_OnlyIfMissing; - bUseMikkTSpace = true; - bBuildAdjacencyBuffer = true; // v1 default false - - bComputeWeightedNormals = false; - bBuildReversedIndexBuffer = true; - bUseHighPrecisionTangentBasis = false; - bGenerateDistanceFieldAsIfTwoSided = false; - bSupportFaceRemap = false; - //BuildScale3D = FVector(1.0f, 1.0f, 1.0f); - DistanceFieldResolutionScale = 2.0f; // ue default is 1.0 - - bPDGAsyncCommandletImportEnabled = false; - - // Legacy settings - bEnableBackwardCompatibility = true; - bAutomaticLegacyHDARebuild = false; -} - -UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() -{} - - -FProperty * -UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const -{ - for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) - { - FProperty * Property = *PropIt; - - if (Property->GetNameCPP() == PropertyName) - return Property; - } - - return nullptr; -} - - -void -UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) -{ - FProperty * Property = LocateProperty(PropertyName); - if (Property) - { - if (bReadOnly) - Property->SetPropertyFlags(CPF_EditConst); - else - Property->ClearPropertyFlags(CPF_EditConst); - } -} - - -void -UHoudiniRuntimeSettings::PostInitProperties() -{ - Super::PostInitProperties(); - - // Set Collision generation options as read only - { - if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Set marshalling attributes options as read only - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - /* - // Set Cook Folder as read-only - { - if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) - Property->SetPropertyFlags( CPF_EditConst ); - } - */ - - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - Property->SetPropertyFlags(CPF_EditConst); - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Disable UI elements depending on current session type. -#if WITH_EDITOR - - UpdateSessionUI(); - -#endif // WITH_EDITOR - - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); -} - - -#if WITH_EDITOR - -void -UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - FProperty * Property = PropertyChangedEvent.MemberProperty; - FProperty * LookupProperty = nullptr; - - if (!Property) - return; - if (Property->GetName() == TEXT("SessionType")) - UpdateSessionUI(); - else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); - else if (Property->GetName() == TEXT("CustomHoudiniLocation")) - { - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; - FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); - - // If path does not point to libHAPI location, we need to let user know. - if (!FPaths::FileExists(LibHAPICustomPath)) - { - FString MessageString = FString::Printf( - TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); - - FPlatformMisc::MessageBoxExt( - EAppMsgType::Ok, *MessageString, - TEXT("Invalid Custom Location Specified, resetting.")); - - CustomHoudiniLocationPath = TEXT(""); - } - } - else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) - { - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->SetPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->SetPropertyFlags(CPF_EditConst); - } - else - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->ClearPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->ClearPropertyFlags(CPF_EditConst); - } - } - - /* - if ( Property->GetName() == TEXT( "bEnableCooking" ) ) - { - // Cooking is disabled, we need to disable transform change triggers cooks option is as well. - if ( bEnableCooking ) - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) - { - // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. - if ( bUploadTransformsToHoudiniEngine ) - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - */ -} - - - -void -UHoudiniRuntimeSettings::UpdateSessionUI() -{ - SetPropertyReadOnly(TEXT("ServerHost"), true); - SetPropertyReadOnly(TEXT("ServerPort"), true); - SetPropertyReadOnly(TEXT("ServerPipeName"), true); - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); - - bool bServerType = false; - - switch (SessionType) - { - case HRSST_Socket: - { - SetPropertyReadOnly(TEXT("ServerHost"), false); - SetPropertyReadOnly(TEXT("ServerPort"), false); - bServerType = true; - break; - } - - case HRSST_NamedPipe: - { - SetPropertyReadOnly(TEXT("ServerPipeName"), false); - bServerType = true; - break; - } - - default: - break; - } - - if (bServerType) - { - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); - } -} - -#endif // WITH_EDITOR - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniRuntimeSettings.h" + + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Misc/Paths.h" +// #include "Internationalization/Internationalization.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + + +FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() + : bGeneratedDoubleSidedGeometry(false) + , GeneratedPhysMaterial(nullptr) + , GeneratedCollisionTraceFlag(CTF_UseDefault) + , GeneratedLightMapResolution(64) + , GeneratedLpvBiasMultiplier(1.0f) + , GeneratedWalkableSlopeOverride() + , GeneratedLightMapCoordinateIndex(1) + , bGeneratedUseMaximumStreamingTexelRatio(false) + , GeneratedStreamingDistanceMultiplier(1.0f) + , GeneratedFoliageDefaultSettings(nullptr) + , GeneratedAssetUserData() +{ + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); +} + + +UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // Session options. + SessionType = HRSST_NamedPipe; + ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; + ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; + ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; + bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; + AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; + + bSyncWithHoudiniCook = true; + bCookUsingHoudiniTime = true; + bSyncViewport = false; + bSyncHoudiniViewport = false; + bSyncUnrealViewport = false; + + // Instantiating options. + bShowMultiAssetDialog = true; + bPreferHdaMemoryCopyOverHdaSourceFile = false; + + // Cooking options. + bPauseCookingOnStart = false; + bDisplaySlateCookingNotifications = true; + DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + // Parameter options + //bTreatRampParametersAsMultiparms = false; + + // Custom Houdini location. + bUseCustomHoudiniLocation = false; + CustomHoudiniLocation.Path = TEXT(""); + + // Arguments for HAPI_Initialize + CookingThreadStackSize = -1; + + // Landscape marshalling default values. + MarshallingLandscapesUseDefaultUnrealScaling = false; + MarshallingLandscapesUseFullResolution = true; + MarshallingLandscapesForceMinMaxValues = false; + MarshallingLandscapesForcedMinValue = -2000.0f; + MarshallingLandscapesForcedMaxValue = 4553.0f; + + // Spline marshalling + MarshallingSplineResolution = 50.0f; + + // Static mesh proxy refinement settings + bEnableProxyStaticMesh = false; + bShowDefaultMesh = true; + bEnableProxyStaticMeshRefinementByTimer = true; + ProxyMeshAutoRefineTimeoutSeconds = 10.0f; + bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; + bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; + + // Generated StaticMesh settings. + bDoubleSidedGeometry = false; + PhysMaterial = nullptr; + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); + CollisionTraceFlag = CTF_UseDefault; + LightMapResolution = 32; + LpvBiasMultiplier = 1.0f; + LightMapCoordinateIndex = 1; + bUseMaximumStreamingTexelRatio = false; + StreamingDistanceMultiplier = 1.0f; + GeneratedDistanceFieldResolutionScale = 0.0f; + + // Static Mesh build settings. + bUseFullPrecisionUVs = false; + SrcLightmapIndex = 0; + DstLightmapIndex = 1; + MinLightmapResolution = 64; + bRemoveDegenerates = true; + GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; + RecomputeNormalsFlag = HRSRF_OnlyIfMissing; + RecomputeTangentsFlag = HRSRF_OnlyIfMissing; + bUseMikkTSpace = true; + bBuildAdjacencyBuffer = true; // v1 default false + + bComputeWeightedNormals = false; + bBuildReversedIndexBuffer = true; + bUseHighPrecisionTangentBasis = false; + bGenerateDistanceFieldAsIfTwoSided = false; + bSupportFaceRemap = false; + //BuildScale3D = FVector(1.0f, 1.0f, 1.0f); + DistanceFieldResolutionScale = 2.0f; // ue default is 1.0 + + bPDGAsyncCommandletImportEnabled = false; + + // Legacy settings + bEnableBackwardCompatibility = true; + bAutomaticLegacyHDARebuild = false; + + // Curve inputs and editable output curves + bAddRotAndScaleAttributesOnCurves = false; +} + +UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() +{} + + +FProperty * +UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const +{ + for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) + { + FProperty * Property = *PropIt; + + if (Property->GetNameCPP() == PropertyName) + return Property; + } + + return nullptr; +} + + +void +UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) +{ + FProperty * Property = LocateProperty(PropertyName); + if (Property) + { + if (bReadOnly) + Property->SetPropertyFlags(CPF_EditConst); + else + Property->ClearPropertyFlags(CPF_EditConst); + } +} + + +void +UHoudiniRuntimeSettings::PostInitProperties() +{ + Super::PostInitProperties(); + + // Set Collision generation options as read only + { + if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Set marshalling attributes options as read only + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + /* + // Set Cook Folder as read-only + { + if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + } + */ + + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + Property->SetPropertyFlags(CPF_EditConst); + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Disable UI elements depending on current session type. +#if WITH_EDITOR + + UpdateSessionUI(); + +#endif // WITH_EDITOR + + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); +} + + +#if WITH_EDITOR + +void +UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + FProperty * LookupProperty = nullptr; + + if (!Property) + return; + if (Property->GetName() == TEXT("SessionType")) + UpdateSessionUI(); + else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); + else if (Property->GetName() == TEXT("CustomHoudiniLocation")) + { + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; + FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); + + // If path does not point to libHAPI location, we need to let user know. + if (!FPaths::FileExists(LibHAPICustomPath)) + { + FString MessageString = FString::Printf( + TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); + + FPlatformMisc::MessageBoxExt( + EAppMsgType::Ok, *MessageString, + TEXT("Invalid Custom Location Specified, resetting.")); + + CustomHoudiniLocationPath = TEXT(""); + } + } + else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) + { + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->SetPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->SetPropertyFlags(CPF_EditConst); + } + else + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->ClearPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->ClearPropertyFlags(CPF_EditConst); + } + } + + /* + if ( Property->GetName() == TEXT( "bEnableCooking" ) ) + { + // Cooking is disabled, we need to disable transform change triggers cooks option is as well. + if ( bEnableCooking ) + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) + { + // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. + if ( bUploadTransformsToHoudiniEngine ) + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + */ +} + + + +void +UHoudiniRuntimeSettings::UpdateSessionUI() +{ + SetPropertyReadOnly(TEXT("ServerHost"), true); + SetPropertyReadOnly(TEXT("ServerPort"), true); + SetPropertyReadOnly(TEXT("ServerPipeName"), true); + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); + + bool bServerType = false; + + switch (SessionType) + { + case HRSST_Socket: + { + SetPropertyReadOnly(TEXT("ServerHost"), false); + SetPropertyReadOnly(TEXT("ServerPort"), false); + bServerType = true; + break; + } + + case HRSST_NamedPipe: + { + SetPropertyReadOnly(TEXT("ServerPipeName"), false); + bServerType = true; + break; + } + + default: + break; + } + + if (bServerType) + { + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); + } +} + +#endif // WITH_EDITOR + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h index 62845537d..e33abb796 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h @@ -104,10 +104,6 @@ struct HOUDINIENGINERUNTIME_API FHoudiniStaticMeshGenerationProperties UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) float GeneratedLpvBiasMultiplier; - /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) - float GeneratedDistanceFieldResolutionScale; - /** Custom walkable slope setting for generated mesh's body. */ UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Walkable Slope Override")) FWalkableSlopeOverride GeneratedWalkableSlopeOverride; @@ -126,7 +122,7 @@ struct HOUDINIENGINERUNTIME_API FHoudiniStaticMeshGenerationProperties /** Default settings when using this mesh for instanced foliage. */ UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh* GeneratedFoliageDefaultSettings; + UFoliageType_InstancedStaticMesh* GeneratedFoliageDefaultSettings = nullptr; /** Array of user data stored with the asset. */ UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Asset User Data")) @@ -167,7 +163,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject public: //------------------------------------------------------------------------------------------------------------- - // Session options. + // Session options. //------------------------------------------------------------------------------------------------------------- UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) TEnumAsByte SessionType; @@ -213,12 +209,19 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject //------------------------------------------------------------------------------------------------------------- // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. - // TODO: PORT THE DIALOG!! UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) bool bShowMultiAssetDialog; + // When enabled, the plugin will always instantiate the memory copy of a HDA stored in the .uasset file + // instead of using the latest version of the HDA file itself. + // This helps ensuring consistency between users when using HDAs, but will not work with expanded HDAs. + // When disabled, the plugin will always instantiate the latest version of the source HDA file if it is + // available, and will fallback to the memory copy if the source file cannot be found + UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) + bool bPreferHdaMemoryCopyOverHdaSourceFile; + //------------------------------------------------------------------------------------------------------------- - // Cooking options. + // Cooking options. //------------------------------------------------------------------------------------------------------------- // Whether houdini engine cooking is paused or not upon initializing the plugin @@ -238,7 +241,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject FString DefaultBakeFolder; //------------------------------------------------------------------------------------------------------------- - // Parameter options. + // Parameter options. //------------------------------------------------------------------------------------------------------------- /* Deprecated! @@ -254,25 +257,33 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject // If true, generated Landscapes will be marshalled using default unreal scaling. // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms // as Unreal's default landscape - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use default Unreal scaling.")) bool MarshallingLandscapesUseDefaultUnrealScaling; + // If true, generated Landscapes will be using full precision for their ZAxis, // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use full resolution for data conversion.")) bool MarshallingLandscapesUseFullResolution; + // If true, the min/max values used to convert heightfields to landscape will be forced values // This is usefull when importing multiple landscapes from different HDAs - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Force Min/Max values for data conversion")) bool MarshallingLandscapesForceMinMaxValues; + // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced min value")) float MarshallingLandscapesForcedMinValue; + // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced max value")) float MarshallingLandscapesForcedMaxValue; + // If this is enabled, additional rot & scale attributes are added on curve inputs + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Add rot & scale attributes on curve inputs")) + bool bAddRotAndScaleAttributesOnCurves; + // Default resolution used when converting Unreal Spline Components to Houdini Curves (step in cm between control points, 0 only send the control points) - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling") + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Default spline resolution (cm)")) float MarshallingSplineResolution; //------------------------------------------------------------------------------------------------------------- @@ -306,11 +317,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject //------------------------------------------------------------------------------------------------------------- // Generated StaticMesh settings. //------------------------------------------------------------------------------------------------------------- - /* - UPROPERTY(Category = "GeneratedStaticMeshSettings", EditAnywhere, meta = (ShowOnlyInnerProperties)) - FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; - */ - + /// If true, the physics triangle mesh will use double sided faces for new Houdini Assets when doing scene queries. UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Double Sided Geometry")) uint32 bDoubleSidedGeometry : 1; @@ -408,31 +415,38 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject bool bBuildAdjacencyBuffer; // If true, we will use the surface area and the corner angle of the triangle as a ratio when computing the normals. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") uint8 bComputeWeightedNormals : 1; // Required to optimize mesh in mirrored transform. Double index buffer size. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") uint8 bBuildReversedIndexBuffer : 1; // If true, Tangents will be stored at 16 bit vs 8 bit precision. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") uint8 bUseHighPrecisionTangentBasis : 1; // Scale to apply to the mesh when allocating the distance field volume texture. // The default scale is 1, which is assuming that the mesh will be placed unscaled in the world. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") float DistanceFieldResolutionScale; // Whether to generate the distance field treating every triangle hit as a front face. // When enabled prevents the distance field from being discarded due to the mesh being open, but also lowers Distance Field AO quality. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings, meta = (DisplayName = "Two-Sided Distance Field Generation")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Two-Sided Distance Field Generation")) uint8 bGenerateDistanceFieldAsIfTwoSided : 1; // Enable the Physical Material Mask - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = BuildSettings, meta = (DisplayName = "Enable Physical Material Mask")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Enable Physical Material Mask")) uint8 bSupportFaceRemap : 1; + //------------------------------------------------------------------------------------------------------------- + // PDG Commandlet import + //------------------------------------------------------------------------------------------------------------- + // Is the PDG commandlet enabled? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta = (DisplayName = "Async Importer Enabled")) + bool bPDGAsyncCommandletImportEnabled; + //------------------------------------------------------------------------------------------------------------- // Legacy //------------------------------------------------------------------------------------------------------------- @@ -481,11 +495,4 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject // Sets HOUDINI_AUDIO_DSO_PATH UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) FString AudioDsoSearchPath; - - //------------------------------------------------------------------------------------------------------------- - // PDG Commandlet import - //------------------------------------------------------------------------------------------------------------- - // Is the PDG commandlet enabled? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta=(DisplayName="Async Importer Enabled")) - bool bPDGAsyncCommandletImportEnabled; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp index 30bdeec46..7b0975753 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp @@ -273,6 +273,46 @@ int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const return -1; } +bool UHoudiniStaticMesh::IsValid(bool bInSkipVertexIndicesCheck) const +{ + // Validate the number of vertices, indices and triangles. This is basically the same function as FRawMesh::IsValid() + const int32 NumVertices = GetNumVertices(); + const int32 NumVertexInstances = GetNumVertexInstances(); + const int32 NumTriangles = GetNumTriangles(); + + auto ValidateAttributeArraySize = [](int32 InArrayNum, int32 InExpectedSize) + { + return InArrayNum == 0 || InArrayNum == InExpectedSize; + }; + + bool bValid = NumVertices > 0 + && NumVertexInstances > 0 + && NumTriangles > 0 + && (NumVertexInstances / 3) == NumTriangles + && ValidateAttributeArraySize(MaterialIDsPerTriangle.Num(), NumTriangles) + && ValidateAttributeArraySize(VertexInstanceNormals.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceUTangents.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceVTangents.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceColors.Num(), NumVertexInstances) + // Must have at least 1 UV layer + && NumUVLayers > 0 + && VertexInstanceUVs.Num() == NumUVLayers * NumVertexInstances; + + if (!bInSkipVertexIndicesCheck) + { + int32 TriangleIndex = 0; + while (bValid && TriangleIndex < NumTriangles) + { + bValid = bValid && (TriangleIndices[TriangleIndex].X < NumVertices); + bValid = bValid && (TriangleIndices[TriangleIndex].Y < NumVertices); + bValid = bValid && (TriangleIndices[TriangleIndex].Z < NumVertices); + TriangleIndex++; + } + } + + return bValid; +} + void UHoudiniStaticMesh::Serialize(FArchive &InArchive) { Super::Serialize(InArchive); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h index 6a2917419..99aa0830f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h @@ -159,12 +159,20 @@ class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject UFUNCTION() const TArray& GetStaticMaterials() const { return StaticMaterials; } + TArray& GetStaticMaterials() { return StaticMaterials; } + UFUNCTION() UMaterialInterface* GetMaterial(int32 InMaterialIndex); UFUNCTION() int32 GetMaterialIndex(FName InMaterialSlotName) const; + // Checks if the mesh is valid by checking face, vertex and attribute (normals etc) counts. + // If bSkipVertexIndicesCheck is true, then we don't loop over all triangle vertex indices to + // check if each index is valid (< NumVertices) + UFUNCTION() + bool IsValid(bool bInSkipVertexIndicesCheck=false) const; + // Custom serialization: we use TArray::BulkSerialize to speed up array serialization virtual void Serialize(FArchive &InArchive) override; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.cpp new file mode 100644 index 000000000..b6b669d80 --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.cpp @@ -0,0 +1,55 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniTranslatorTypes.h" + +FHoudiniLandscapeTileSizeInfo::FHoudiniLandscapeTileSizeInfo() + : bIsCached(false) + , UnrealSizeX(-1) + , UnrealSizeY(-1) + , NumSectionsPerComponent(-1) + , NumQuadsPerSection(-1) +{ + +} + +FHoudiniLandscapeExtent::FHoudiniLandscapeExtent() + : bIsCached(false) + , MinX(0), MaxX(0), MinY(0), MaxY(0) + , ExtentsX(0) + , ExtentsY(0) +{ + +} + +FHoudiniLandscapeReferenceLocation::FHoudiniLandscapeReferenceLocation() + : bIsCached(false) + , SectionCoordX(0) + , SectionCoordY(0) + , TileLocationX(0.f) + , TileLocationY(0.f) +{ +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.h new file mode 100644 index 000000000..a32aca0db --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/HoudiniTranslatorTypes.h @@ -0,0 +1,77 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineRuntimePrivatePCH.h" + +class ALandscape; + +// Used to cache data to use as a reference point during landscape construction +// The very first tile will calculate landscape +struct HOUDINIENGINERUNTIME_API FHoudiniLandscapeTileSizeInfo +{ + FHoudiniLandscapeTileSizeInfo(); + bool bIsCached; + + // Tile sizes + int32 UnrealSizeX; + int32 UnrealSizeY; + int32 NumSectionsPerComponent; + int32 NumQuadsPerSection; +}; + +// Used to cache the extent of the landscape so that it doesn't have to be recalculated +// for each landscape tile. +// The very first tile will calculate landscape +struct HOUDINIENGINERUNTIME_API FHoudiniLandscapeExtent +{ + FHoudiniLandscapeExtent(); + bool bIsCached; + + // Landscape extents (in quads) + int32 MinX, MaxX, MinY, MaxY; + int32 ExtentsX; + int32 ExtentsY; +}; + +// Used to cache data to use as a reference point during landscape construction +// The very first tile will calculate a reference point as well as a component-space location. +// Every subsequent tile can then derive a component-space location from this location. +struct HOUDINIENGINERUNTIME_API FHoudiniLandscapeReferenceLocation +{ + FHoudiniLandscapeReferenceLocation(); + + bool bIsCached; + // Absolute section base coordinate for the cached tile. + int32 SectionCoordX; + int32 SectionCoordY; + // Scaled location for the reference tile. + float TileLocationX; + float TileLocationY; + // Transform of the main landscape actor. + FTransform MainTransform; +}; \ No newline at end of file From fafa5db458655b265d27bc0b0ef489185f65255b Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Fri, 30 Apr 2021 17:26:51 -0400 Subject: [PATCH 08/16] Houdini Engine for Unreal - Version 2.0 Houdini 18.5.563 update. The plug-in is now linked to Houdini 18.5.563 / HAPI 3.6.1. Documentation for version 2.0 of the plug-in is available: https://www.sidefx.com/docs/unreal/ ------------------------------------------------------- Changes: ------------------------------------------------------- - Fixed a regression that caused generic property attributes to not always be properly applied to generated Static Mesh Components. (specifically when using RawMeshes). - Fixed "unreal_uproperty_CollisionProfileName" not being properly applied in some cases. - Fixed crash when using the bake button in the output section. - Generic property attributes are now applied on spline outputs. - Textures from Houdini Materials are now created only if the corresponding "use" parameter isn't disabled. - Reduced the number of HAPI calls when translating Houdini materials. - Optimized mesh creation. (removed redundant calls to Physics/NavCollisions functions that are already part of the StaticMesh::Build function) - Mesh creation: removed unecessary attempt to load an unreal material with an invalid path. - Fixed World inputs creating invalid input nodes with "Import as Reference" enabled. - Fixed "Import as Reference" not triggering input updates when disabled. - Fixed issues with multiparm/ramps sync if a HDA's parameter interface had changed. This could cause mixups in the multiparms children parameters, and even crashes due to a recursive loop after load/rebuild. - Fixed missing output detail UI when an output's node is marked as editable but is not an editable curve. - Fixed potential crash caused by the plugin attempting to update rendering while loading a level. - Fixed an issue that caused OBJ HDAs with nested OBJ_geo subnets to not display/use the main OBJ geo. This caused some HDAs that used vellum solvers/DOP networks to not properly output their results. - Float Vector3 parameters now have spin buttons on the XYZ values. --- HoudiniEngine.uplugin | 4 +- Source/HoudiniEngine/HoudiniEngine.Build.cs | 6 +- Source/HoudiniEngine/Private/HoudiniApi.cpp | 112 ++- .../Private/HoudiniEngineManager.cpp | 15 + .../Private/HoudiniEnginePrivatePCH.h | 94 ++- .../Private/HoudiniEngineScheduler.cpp | 6 + .../Private/HoudiniEngineScheduler.h | 2 + .../Private/HoudiniEngineUtils.cpp | 371 +++++++-- .../Private/HoudiniEngineUtils.h | 33 +- .../Private/HoudiniInputTranslator.cpp | 107 ++- .../Private/HoudiniInputTranslator.h | 10 + .../Private/HoudiniInstanceTranslator.cpp | 8 +- .../Private/HoudiniInstanceTranslator.h | 8 +- .../Private/HoudiniMaterialTranslator.cpp | 768 +++++++++++------- .../Private/HoudiniMaterialTranslator.h | 12 +- .../Private/HoudiniMeshTranslator.cpp | 394 ++++++--- .../Private/HoudiniOutputTranslator.cpp | 13 +- .../Private/HoudiniParameterTranslator.cpp | 125 ++- .../Private/HoudiniParameterTranslator.h | 5 +- .../Private/HoudiniSplineTranslator.cpp | 13 +- .../Private/UnrealMeshTranslator.cpp | 12 +- Source/HoudiniEngine/Public/HAPI/HAPI.h | 57 +- .../HoudiniEngine/Public/HAPI/HAPI_Common.h | 5 +- .../HoudiniEngine/Public/HAPI/HAPI_Version.h | 6 +- Source/HoudiniEngine/Public/HoudiniApi.h | 35 +- .../Private/HoudiniEngineBakeUtils.cpp | 65 +- .../Private/HoudiniEngineBakeUtils.h | 5 +- .../Private/HoudiniEngineDetails.h | 2 +- .../Private/HoudiniEngineEditor.cpp | 2 +- .../Private/HoudiniInputDetails.cpp | 85 +- .../Private/HoudiniInputDetails.h | 2 +- .../Private/HoudiniOutputDetails.cpp | 53 +- .../Private/HoudiniPDGDetails.h | 2 +- .../Private/HoudiniParameterDetails.cpp | 144 ++-- .../Private/HoudiniParameterDetails.h | 2 +- .../Private/HoudiniAssetActor.cpp | 1 + .../Private/HoudiniAssetComponent.cpp | 15 +- .../Private/HoudiniAssetComponent.h | 9 + .../Private/HoudiniEngineRuntimeUtils.cpp | 14 + .../Private/HoudiniEngineRuntimeUtils.h | 14 +- .../Private/HoudiniGenericAttribute.cpp | 575 ++++++++++++- .../Private/HoudiniGenericAttribute.h | 41 +- .../Private/HoudiniInput.cpp | 2 + .../Private/HoudiniInputObject.cpp | 75 +- .../Private/HoudiniInputObject.h | 38 +- .../Private/HoudiniPDGAssetLink.cpp | 3 +- .../Private/HoudiniPDGAssetLink.h | 2 +- .../Private/HoudiniSplineComponent.cpp | 139 +--- .../Private/HoudiniSplineComponent.h | 27 +- 49 files changed, 2562 insertions(+), 976 deletions(-) diff --git a/HoudiniEngine.uplugin b/HoudiniEngine.uplugin index 869034b71..8784d7118 100644 --- a/HoudiniEngine.uplugin +++ b/HoudiniEngine.uplugin @@ -1,8 +1,8 @@ { "FileVersion" : 3, "FriendlyName" : "Houdini Engine v2", - "Version" : 18050532, - "VersionName" : "v2.0 - H18.5.532", + "Version" : 18050563, + "VersionName" : "v2.0 - H18.5.563", "CreatedBy" : "Side Effects Software Inc.", "CreatedByURL" : "http://www.sidefx.com", "DocsURL" : "http://www.sidefx.com/docs/unreal/", diff --git a/Source/HoudiniEngine/HoudiniEngine.Build.cs b/Source/HoudiniEngine/HoudiniEngine.Build.cs index d1060175e..3195cd118 100644 --- a/Source/HoudiniEngine/HoudiniEngine.Build.cs +++ b/Source/HoudiniEngine/HoudiniEngine.Build.cs @@ -32,8 +32,8 @@ /* - Houdini Version: 18.5.532 - Houdini Engine Version: 3.5.2 + Houdini Version: 18.5.563 + Houdini Engine Version: 3.6.1 Unreal Version: 4.26.0 */ @@ -47,7 +47,7 @@ public class HoudiniEngine : ModuleRules { private string GetHFSPath() { - string HoudiniVersion = "18.5.532"; + string HoudiniVersion = "18.5.563"; bool bIsRelease = true; string HFSPath = ""; string RegistryPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Side Effects Software"; diff --git a/Source/HoudiniEngine/Private/HoudiniApi.cpp b/Source/HoudiniEngine/Private/HoudiniApi.cpp index 71a183d9e..0fef0024c 100644 --- a/Source/HoudiniEngine/Private/HoudiniApi.cpp +++ b/Source/HoudiniEngine/Private/HoudiniApi.cpp @@ -210,12 +210,24 @@ FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStu FHoudiniApi::GetAttributeInfoFuncPtr FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; +FHoudiniApi::GetAttributeInt16ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt16DataFuncPtr +FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; + FHoudiniApi::GetAttributeInt64ArrayDataFuncPtr FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; FHoudiniApi::GetAttributeInt64DataFuncPtr FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; +FHoudiniApi::GetAttributeInt8ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt8DataFuncPtr +FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; + FHoudiniApi::GetAttributeIntArrayDataFuncPtr FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; @@ -231,6 +243,12 @@ FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArray FHoudiniApi::GetAttributeStringDataFuncPtr FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; +FHoudiniApi::GetAttributeUInt8ArrayDataFuncPtr +FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeUInt8DataFuncPtr +FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; + FHoudiniApi::GetAvailableAssetCountFuncPtr FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; @@ -786,15 +804,24 @@ FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmpt FHoudiniApi::SetAttributeFloatDataFuncPtr FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; +FHoudiniApi::SetAttributeInt16DataFuncPtr +FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; + FHoudiniApi::SetAttributeInt64DataFuncPtr FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; +FHoudiniApi::SetAttributeInt8DataFuncPtr +FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; + FHoudiniApi::SetAttributeIntDataFuncPtr FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; FHoudiniApi::SetAttributeStringDataFuncPtr FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; +FHoudiniApi::SetAttributeUInt8DataFuncPtr +FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; + FHoudiniApi::SetCachePropertyFuncPtr FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; @@ -1024,13 +1051,19 @@ FHoudiniApi::InitializeHAPI(void* LibraryHandle) FHoudiniApi::GetAttributeFloatArrayData = (GetAttributeFloatArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatArrayData")); FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); + FHoudiniApi::GetAttributeInt16ArrayData = (GetAttributeInt16ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16ArrayData")); + FHoudiniApi::GetAttributeInt16Data = (GetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16Data")); FHoudiniApi::GetAttributeInt64ArrayData = (GetAttributeInt64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64ArrayData")); FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); + FHoudiniApi::GetAttributeInt8ArrayData = (GetAttributeInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8ArrayData")); + FHoudiniApi::GetAttributeInt8Data = (GetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8Data")); FHoudiniApi::GetAttributeIntArrayData = (GetAttributeIntArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntArrayData")); FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); FHoudiniApi::GetAttributeStringArrayData = (GetAttributeStringArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringArrayData")); FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); + FHoudiniApi::GetAttributeUInt8ArrayData = (GetAttributeUInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8ArrayData")); + FHoudiniApi::GetAttributeUInt8Data = (GetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8Data")); FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); @@ -1216,9 +1249,12 @@ FHoudiniApi::InitializeHAPI(void* LibraryHandle) FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); + FHoudiniApi::SetAttributeInt16Data = (SetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt16Data")); FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); + FHoudiniApi::SetAttributeInt8Data = (SetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt8Data")); FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); + FHoudiniApi::SetAttributeUInt8Data = (SetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeUInt8Data")); FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); @@ -1340,13 +1376,19 @@ FHoudiniApi::FinalizeHAPI() FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; + FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; @@ -1532,9 +1574,12 @@ FHoudiniApi::FinalizeHAPI() FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; @@ -2026,6 +2071,20 @@ FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId } +HAPI_Result +FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) { @@ -2040,6 +2099,20 @@ FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_N } +HAPI_Result +FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) { @@ -2075,6 +2148,20 @@ FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_ } +HAPI_Result +FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) { @@ -3245,7 +3332,7 @@ FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session HAPI_Result -FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle) +FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle) { return HAPI_RESULT_FAILURE; } @@ -3370,6 +3457,13 @@ FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_N } +HAPI_Result +FHoudiniApi::SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) { @@ -3377,6 +3471,13 @@ FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_N } +HAPI_Result +FHoudiniApi::SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) { @@ -3391,6 +3492,13 @@ FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_ } +HAPI_Result +FHoudiniApi::SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + HAPI_Result FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) { @@ -3427,7 +3535,7 @@ FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId n HAPI_Result -FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value) +FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value) { return HAPI_RESULT_FAILURE; } diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp index 52855d897..28deb4131 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp @@ -1140,6 +1140,9 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces // Update rendering information. HAC->UpdateRenderingInformation(); + // Since we have new asset, we need to update bounds. + HAC->UpdateBounds(); + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Finished processing outputs"), true); // Trigger a details panel update @@ -1158,6 +1161,12 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces if (bHasHoudiniStaticMeshOutput) bNeedsToTriggerViewportUpdate = true; + UHoudiniAssetComponent::FOnPostCookDelegate& OnPostCookDelegate = HAC->GetOnPostCookDelegate(); + if (OnPostCookDelegate.IsBound()) + { + OnPostCookDelegate.Execute(HAC, true); + } + UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); if (OnPostCookBakeDelegate.IsBound()) { @@ -1173,6 +1182,12 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces //CreateInputs(); //CreateHandles(); + UHoudiniAssetComponent::FOnPostCookDelegate& OnPostCookDelegate = HAC->GetOnPostCookDelegate(); + if (OnPostCookDelegate.IsBound()) + { + OnPostCookDelegate.Execute(HAC, false); + } + // Clear the bake after cook delegate if UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); if (OnPostCookBakeDelegate.IsBound() && !HAC->IsBakeAfterNextCookEnabled()) diff --git a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h index 8fae01a00..9e0aa9433 100644 --- a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h +++ b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h @@ -290,62 +290,90 @@ #define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" // Materials Diffuse. -#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL "ogl_diff" +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE "basecolor" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_0 "ogl_tex1" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_1 "basecolor_texture" +#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0 "ogl_diff" -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1 "basecolor" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL "ogl_tex1" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED "ogl_use_tex1" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE "basecolor_texture" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED "basecolor_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" // Materials Normal. -#define HAPI_UNREAL_PARAM_MAP_NORMAL_0 "ogl_normalmap" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_1 "normalTexture" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_OGL "ogl_normalmap" + +//#define HAPI_UNREAL_PARAM_MAP_NORMAL "normalTexture" +//#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "normalUseTexture" +#define HAPI_UNREAL_PARAM_MAP_NORMAL "baseNormal_texture" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "baseBumpAndNormal_enable" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" // Materials Specular. -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_0 "ogl_specmap" -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_1 "reflect_texture" +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL "ogl_spec" +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR "reflect" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL "ogl_specmap" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED "ogl_use_specmap" -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_0 "ogl_spec" -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_1 "reflect" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR "reflect_texture" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED "reflect_useTexture" -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" // Materials Roughness. -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0 "ogl_roughmap" -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1 "rough_texture" +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL "ogl_rough" +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS "rough" -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0 "ogl_rough" -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1 "rough" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL "ogl_roughmap" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED "ogl_use_roughmap" -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS "rough_texture" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED "rough_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" // Materials Metallic. -#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" -#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" -#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" +#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" +#define HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL "ogl_metallic" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL "ogl_metallicmap" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED "ogl_use_metallicmap" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED "metallic_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" // Materials Emissive. -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL "ogl_emit" +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE "emitcolor" + +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL "ogl_emissionmap" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED "ogl_use_emissionmap" -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0 "ogl_emit" -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1 "emitcolor" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED "emitcolor_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" // Materials Opacity. -#define HAPI_UNREAL_PARAM_ALPHA_0 "ogl_alpha" -#define HAPI_UNREAL_PARAM_ALPHA_1 "opac" +#define HAPI_UNREAL_PARAM_ALPHA_OGL "ogl_alpha" +#define HAPI_UNREAL_PARAM_ALPHA "opac" + +#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL "ogl_opacitymap" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED "ogl_use_opacitymap" -#define HAPI_UNREAL_PARAM_MAP_OPACITY_0 "ogl_opacitymap" -#define HAPI_UNREAL_PARAM_MAP_OPACITY_1 "opaccolor_texture" +#define HAPI_UNREAL_PARAM_MAP_OPACITY "opaccolor_texture" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED "opaccolor_useTexture" // Number of GUID characters to keep for packages #define PACKAGE_GUID_LENGTH 8 diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp index 57a7a760f..280915c4a 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp @@ -537,6 +537,12 @@ FHoudiniEngineScheduler::TaskProccessAsset(const FHoudiniEngineTask & Task) // TODO: Process results! } +bool FHoudiniEngineScheduler::HasPendingTasks() +{ + FScopeLock ScopeLock(&CriticalSection); + return (PositionWrite != PositionRead); +} + void FHoudiniEngineScheduler::AddTask(const FHoudiniEngineTask & Task) { diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h index c29098dce..2c9354540 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h @@ -50,6 +50,8 @@ class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable // Adds a task. void AddTask(const FHoudiniEngineTask & Task); + bool HasPendingTasks(); + // Adds instantiation response task info. void AddResponseTaskInfo( HAPI_Result Result, diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp index 16a3bf9b1..439fcabbe 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp @@ -790,32 +790,62 @@ FHoudiniEngineUtils::HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& return FString::Printf(TEXT("Unknown HAPI_PDG_WorkitemState %d"), InWorkitemState); } + +// Centralized call to track renaming of objects +bool FHoudiniEngineUtils::RenameObject(UObject* Object, const TCHAR* NewName /*= nullptr*/, UObject* NewOuter /*= nullptr*/, ERenameFlags Flags /*= REN_None*/) +{ + check(Object); + if (AActor* Actor = Cast(Object)) + { + if (Actor->IsPackageExternal()) + { + // There should be no need to choose a specific name for an actor in Houdini Engine, instead setting its label should be enough. + if (FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, NewName)) + { + HOUDINI_LOG_WARNING(TEXT("Called SetActorLabel(%s) on external actor %s instead of Rename : Explicit naming of an actor that is saved in its own external package is prone to cause name clashes when submitting the file.)"), NewName, *Actor->GetName()); + } + // Force to return false (make sure nothing in Houdini Engine plugin relies on actor being renamed to provided name) + return false; + } + } + return Object->Rename(NewName, NewOuter, Flags); +} + FName FHoudiniEngineUtils::RenameToUniqueActor(AActor* InActor, const FString& InName) { const FName NewName = MakeUniqueObjectName(InActor->GetOuter(), InActor->GetClass(), FName(InName)); - InActor->Rename( *(NewName.ToString()) ); - // TODO: Can we set actor label when actor is pending kill? - InActor->SetActorLabel(NewName.ToString()); + + FHoudiniEngineUtils::RenameObject(InActor, *(NewName.ToString())); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName.ToString()); + return NewName; } -UObject* FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) +UObject* +FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) { check(InActor); - + UObject* PrevObj = nullptr; UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, InActor->GetOuter(), *InName, true); if (ExistingObject && ExistingObject != InActor) { // Rename the existing object - const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName+TEXT("_old")) ); - ExistingObject->Rename(*(NewName.ToString())); + const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName + TEXT("_old"))); + FHoudiniEngineUtils::RenameObject(ExistingObject, *(NewName.ToString())); PrevObj = ExistingObject; } - InActor->Rename(*InName); + + FHoudiniEngineUtils::RenameObject(InActor, *InName); + if (UpdateLabel) - InActor->SetActorLabel(InName, true); + { + //InActor->SetActorLabel(InName, true); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, InName); + InActor->Modify(true); + } + return PrevObj; } @@ -951,12 +981,26 @@ FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() UHoudiniAssetComponent* FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(const UObject* Obj) { - UObject* Outer = Obj->GetOuter(); + if (!IsValid(Obj)) + return nullptr; + + // Check the direct Outer UHoudiniAssetComponent* OuterHAC = Cast(Obj->GetOuter()); if(IsValid(OuterHAC)) return OuterHAC; - return Obj->GetTypedOuter(); + // Check the whole outer chain + OuterHAC = Obj->GetTypedOuter(); + if (IsValid(OuterHAC)) + return OuterHAC; + + // Finally check if the Object itself is a HaC + UObject* NonConstObj = const_cast(Obj); + OuterHAC = Cast(NonConstObj); + if (IsValid(OuterHAC)) + return OuterHAC; + + return nullptr; } @@ -1687,7 +1731,7 @@ FHoudiniEngineUtils::HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FStrin bool -FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos) +FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms) { HAPI_NodeInfo NodeInfo; FHoudiniApi::NodeInfo_Init(&NodeInfo); @@ -1705,6 +1749,16 @@ FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); + + // Increment the object count by one if we should add ourself + OutObjectInfos.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); + OutObjectTransforms.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); for (int32 Idx = 0; Idx < OutObjectInfos.Num(); Idx++) - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + { + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[Idx])); + FHoudiniApi::Transform_Init(&(OutObjectTransforms[Idx])); + } + // Get our object info in 0 if needed + if (bAddSelf) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[0]), false); + + // Use the identity transform + OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; + OutObjectTransforms[0].scale[0] = 1.0f; + OutObjectTransforms[0].scale[1] = 1.0f; + OutObjectTransforms[0].scale[2] = 1.0f; + OutObjectTransforms[0].rstOrder = HAPI_SRT; + } + + // Get the other object infos HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[0], 0, ObjectCount), false); - } - } - else - return false; - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId,&NodeInfo), false); - - int32 ObjectCount = 1; - OutObjectTransforms.SetNumUninitialized(1); - FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); - - OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; - OutObjectTransforms[0].scale[0] = 1.0f; - OutObjectTransforms[0].scale[1] = 1.0f; - OutObjectTransforms[0].scale[2] = 1.0f; - OutObjectTransforms[0].rstOrder = HAPI_SRT; - - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - // Do nothing. Identity transform will be used for the main parent object. - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( - FHoudiniEngine::Get().GetSession(), - InNodeId, nullptr, &ObjectCount), false); + &OutObjectInfos[bAddSelf ? 1 : 0], 0, ObjectCount), false); - if (ObjectCount <= 0) - { - // Do nothing. Identity transform will be used for the main asset object. - } - else - { - OutObjectTransforms.SetNumUninitialized(ObjectCount); + // Get the composed object transforms for the others (1 - Count) HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_SRT, &OutObjectTransforms[0], 0, ObjectCount), false); + InNodeId, HAPI_SRT, &OutObjectTransforms[bAddSelf ? 1 : 0], 0, ObjectCount), false); } } else @@ -3276,48 +3333,44 @@ FHoudiniEngineUtils::HapiGetParameterDataAsFloat( return true; } -HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo) +HAPI_ParmId +FHoudiniEngineUtils::HapiFindParameterByName(const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo) { - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo); - if (NodeInfo.parmCount <= 0) - return -1; + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + InNodeId, InParmName.c_str(), &ParmId), -1); - HAPI_ParmId ParmId = HapiFindParameterByNameOrTag(NodeInfo.id, ParmName); - if ((ParmId < 0) || (ParmId >= NodeInfo.parmCount)) + if (ParmId < 0) return -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), -1); + InNodeId, ParmId, &OutFoundParmInfo), -1); return ParmId; } - -HAPI_ParmId FHoudiniEngineUtils::HapiFindParameterByNameOrTag(const HAPI_NodeId& NodeId, const std::string& ParmName) +HAPI_ParmId +FHoudiniEngineUtils::HapiFindParameterByTag(const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo) { - // First, try to find the parameter by its name + // Try to find the parameter by its tag HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmWithTag( FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), -1); + InNodeId, InParmTag.c_str(), &ParmId), -1); - if (ParmId >= 0) - return ParmId; + if (ParmId < 0) + return -1; - // Second, try to find it by its tag - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmWithTag( + FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), -1); - - if (ParmId >= 0) - return ParmId; + InNodeId, ParmId, &OutFoundParmInfo), -1); - return -1; + return ParmId; } int32 @@ -4484,6 +4537,154 @@ FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(UObject* InObject, return (NumSuccess > 0); } +bool +FHoudiniEngineUtils::SetGenericPropertyAttribute( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FHoudiniGenericAttribute& InPropertyAttribute) +{ + HAPI_AttributeOwner AttribOwner; + switch (InPropertyAttribute.AttributeOwner) + { + case EAttribOwner::Point: + AttribOwner = HAPI_ATTROWNER_POINT; + break; + case EAttribOwner::Vertex: + AttribOwner = HAPI_ATTROWNER_VERTEX; + break; + case EAttribOwner::Prim: + AttribOwner = HAPI_ATTROWNER_PRIM; + break; + case EAttribOwner::Detail: + AttribOwner = HAPI_ATTROWNER_DETAIL; + break; + case EAttribOwner::Invalid: + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InPropertyAttribute.AttributeOwner); + return false; + } + + // Create the attribute via HAPI + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.tupleSize = InPropertyAttribute.AttributeTupleSize; + AttributeInfo.count = InPropertyAttribute.AttributeCount; + AttributeInfo.exists = true; + AttributeInfo.owner = AttribOwner; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + switch(InPropertyAttribute.AttributeType) + { + case (EAttribStorageType::INT): + AttributeInfo.storage = HAPI_STORAGETYPE_INT; + break; + case (EAttribStorageType::INT64): + AttributeInfo.storage = HAPI_STORAGETYPE_INT64; + break; + case (EAttribStorageType::FLOAT): + AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT; + break; + case (EAttribStorageType::FLOAT64): + AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT64; + break; + case (EAttribStorageType::STRING): + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + break; + case (EAttribStorageType::Invalid): + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Storage Type: %d"), InPropertyAttribute.AttributeType); + return false; + } + + // Create the new attribute + if (HAPI_RESULT_SUCCESS != FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo)) + { + return false; + } + + // The New attribute has been successfully created, set its value + switch (InPropertyAttribute.AttributeType) + { + case EAttribStorageType::INT: + { + TArray TempArray; + TempArray.Reserve(InPropertyAttribute.IntValues.Num()); + for (auto Value : InPropertyAttribute.IntValues) + { + TempArray.Add(static_cast(Value)); + } + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + TempArray.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + case EAttribStorageType::INT64: + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + InPropertyAttribute.IntValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + case EAttribStorageType::FLOAT: + { + + TArray TempArray; + TempArray.Reserve(InPropertyAttribute.DoubleValues.Num()); + for (auto Value : InPropertyAttribute.DoubleValues) + { + TempArray.Add(static_cast(Value)); + } + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + TempArray.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + case EAttribStorageType::FLOAT64: + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloat64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + InPropertyAttribute.DoubleValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + case EAttribStorageType::STRING: + { + if (HAPI_RESULT_SUCCESS != FHoudiniEngineUtils::SetAttributeStringData( + InPropertyAttribute.StringValues, + InGeoNodeId, + InPartId, + InPropertyAttribute.AttributeName, + AttributeInfo)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + default: + // Unsupported storage type + HOUDINI_LOG_WARNING(TEXT("Unsupported storage type: %d"), InPropertyAttribute.AttributeType); + break; + } + + return true; +} void FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( @@ -5015,7 +5216,7 @@ FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) CurrentWorld->RemoveActor(InActor, true); //Set the outer of Actor to NewLevel - InActor->Rename((const TCHAR *)0, InDesiredLevel); + FHoudiniEngineUtils::RenameObject(InActor, (const TCHAR*)0, InDesiredLevel); InDesiredLevel->Actors.Add(InActor); return true; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h index d06c71f2f..b84a39afb 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h @@ -115,9 +115,6 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // HAPI : Retrieve the asset node's object transform. **/ static bool HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform); - // HAPI : Retrieve object transforms from given asset node id. - static bool HapiGetObjectTransforms(const HAPI_NodeId& InNodeId, TArray& OutObjectTransforms); - // HAPI : Translate HAPI transform to Unreal one. static void TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform); @@ -134,7 +131,7 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils static bool IsHoudiniNodeValid(const HAPI_NodeId& AssetId); // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. - static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos); + static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms); // HAPI: Retrieve Path to the given Node, relative to the given Node static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); @@ -223,22 +220,22 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils TArray& MatchingAttributesInfo, TArray& MatchingAttributesName); - // HAPI : Look for a parameter by name or tag and returns its index. Returns -1 if not found. - static HAPI_ParmId HapiFindParameterByNameOrTag( - const HAPI_NodeId& NodeId, const std::string& ParmName); - static HAPI_ParmId HapiFindParameterByNameOrTag( - const HAPI_NodeId& NodeId, const std::string& ParmName, HAPI_ParmInfo& FoundParmInfo); + // HAPI : Look for a parameter by name and returns its index. Returns -1 if not found. + static HAPI_ParmId HapiFindParameterByName( + const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo); - // Returns true is the given Geo-Part is an attribute instancer - static bool IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); + // HAPI : Look for a parameter by tag and returns its index. Returns -1 if not found. + static HAPI_ParmId HapiFindParameterByTag( + const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo); - // Return true if given asset id is valid. - //static bool IsValidNodeId(HAPI_NodeId AssetId); + // Returns true is the given Geo-Part is an attribute instancer + static bool IsAttributeInstancer( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); // HAPI : Return a give node's parent ID, -1 if none static HAPI_NodeId HapiGetParentNodeId(const HAPI_NodeId& NodeId); - /** HAPI : Marshaling, disconnect input asset from a given slot. **/ + // HAPI : Marshaling, disconnect input asset from a given slot. static bool HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex); // Destroy asset, returns the status. @@ -341,6 +338,12 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils static bool UpdateGenericPropertiesAttributes( UObject* InObject, const TArray& InAllPropertyAttributes); + // Helper function for setting a generic attribute on geo (UE -> HAPI) + static bool SetGenericPropertyAttribute( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FHoudiniGenericAttribute& InPropertyAttribute); + /* // Tries to update values for all the UProperty attributes to apply on the object. static void ApplyUPropertyAttributesOnObject( @@ -574,6 +577,8 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // Generic naming / pathing utilities // ------------------------------------------------- + static bool RenameObject(UObject* Object, const TCHAR* NewName = nullptr, UObject* NewOuter = nullptr, ERenameFlags Flags = REN_None); + // Rename the actor to a unique / generated name. static FName RenameToUniqueActor(AActor* InActor, const FString& InName); diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp index 8f4ce3b8d..1f8b55ca8 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp @@ -46,6 +46,7 @@ #include "UnrealMeshTranslator.h" #include "UnrealInstanceTranslator.h" #include "UnrealLandscapeTranslator.h" +#include "UnrealFoliageTypeTranslator.h" #include "Engine/StaticMesh.h" #include "Engine/SkeletalMesh.h" @@ -56,6 +57,7 @@ #include "Engine/Brush.h" #include "Engine/DataTable.h" #include "Camera/CameraComponent.h" +#include "FoliageType_InstancedStaticMesh.h" #include "Engine/SimpleConstructionScript.h" #include "Engine/SCS_Node.h" @@ -1358,6 +1360,19 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( break; } + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + { + UHoudiniInputFoliageType_InstancedStaticMesh* const InputFoliageTypeSM = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + ObjBaseName, InputFoliageTypeSM, InInput->GetExportLODs(), InInput->GetExportSockets(), + InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + case EHoudiniInputObjectType::Invalid: //default: break; @@ -1575,6 +1590,15 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( break; } + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + { + // Simply update the Input mesh's Transform offset + if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + break; + } + // Unsupported case EHoudiniInputObjectType::Object: case EHoudiniInputObjectType::SkeletalMesh: @@ -1954,7 +1978,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( // Attach another '\'' to the end AssetReference += FString("'"); - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, SMCName, AssetReference, InObject->Transform); + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, AssetReference, SMCName, InObject->Transform); } else @@ -2637,8 +2661,8 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) bool FHoudiniInputTranslator::CreateInputNodeForReference( HAPI_NodeId& InputNodeId, - const FString & InRef, - const FString & InputNodeName, + const FString& InRef, + const FString& InputNodeName, const FTransform& InTransform) { HAPI_NodeId NewNodeId = -1; @@ -2941,4 +2965,81 @@ FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeNa return true; } +bool +FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + const FString& InObjNodeName, + UHoudiniInputFoliageType_InstancedStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!IsValid(InObject)) + return false; + + FString FTName = InObjNodeName + TEXT("_"); + + UFoliageType_InstancedStaticMesh* FoliageType = Cast(InObject->GetObject()); + if (!IsValid(FoliageType)) + return true; + + UStaticMesh* const SM = FoliageType->GetStaticMesh(); + if (!IsValid(SM)) + return true; + + FTName += FoliageType->GetName(); + + // Marshall the Static Mesh to Houdini + bool bSuccess = true; + + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference; + AssetReference += SM->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FUnrealFoliageTypeTranslator::CreateInputNodeForReference( + FoliageType, InObject->InputNodeId, AssetReference, FTName, InObject->Transform); + } + else + { + bSuccess = FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + FoliageType, InObject->InputNodeId, FTName, bExportLODs, bExportSockets, bExportColliders); + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // If the Input mesh has a Transform offset + const FTransform TransformOffset = InObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h index 4a683f973..553a5bb8c 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h @@ -29,6 +29,7 @@ #include "HAPI/HAPI_Common.h" #include "HoudiniEngineRuntimePrivatePCH.h" +#include "CoreMinimal.h" class AActor; @@ -51,6 +52,7 @@ class UHoudiniInputBrush; class UHoudiniSplineComponent; class UHoudiniInputCameraComponent; class UHoudiniInputDataTable; +class UHoudiniInputFoliageType_InstancedStaticMesh; class AActor; @@ -193,6 +195,14 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator static bool HapiCreateInputNodeForDataTable( const FString& InNodeName, UHoudiniInputDataTable* InInputObject); + static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + const FString& InObjNodeName, + UHoudiniInputFoliageType_InstancedStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference = false); + // HAPI: Create an input node for reference static bool CreateInputNodeForReference( HAPI_NodeId& InputNodeId, diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp index 3dec7a1b6..789dd224f 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp @@ -256,6 +256,12 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( VariationOriginalObjectIndices, VariationIndices); + // Preload objects so we can benefit from async compilation as much as possible + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) + { + VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); + } + // Create the instancer components now for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) { @@ -1928,7 +1934,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( // TODO: We should be calling UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( ... ) InstancedStaticMeshComponent->ClearInstances(); InstancedStaticMeshComponent->PreAllocateInstancesMemory(InstancedObjectTransforms.Num()); - for (const auto& Transform : InstancedObjectTransforms) + for (const FTransform& Transform : InstancedObjectTransforms) { InstancedStaticMeshComponent->AddInstance(Transform); } diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h index b89e07cf7..c0eb647f2 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h @@ -73,7 +73,7 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData GENERATED_BODY() UPROPERTY() - bool bForceHISM; + bool bForceHISM = false; UPROPERTY() TArray OriginalInstancedObjects; @@ -96,10 +96,10 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData TArray SplitAttributeValues; UPROPERTY() - bool bSplitMeshInstancer; + bool bSplitMeshInstancer = false; UPROPERTY() - bool bIsFoliageInstancer; + bool bIsFoliageInstancer = false; UPROPERTY() TArray AllPropertyAttributes; @@ -136,7 +136,7 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData // Number of custom floats for the instancer UPROPERTY() - int32 NumCustomFloats; + int32 NumCustomFloats = -1; // Custom float array // Size is NumCustomFloat * NumberOfInstances diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp index b1b544510..322646b0f 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp @@ -128,15 +128,9 @@ bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( FString MaterialPathName = TEXT(""); if (!FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, MaterialInfo, MaterialPathName)) - continue; - - // TODO: GetAssetName! - FString AssetName = TEXT("HoudiniAsset"); - - bool bCreatedNewMaterial = false; + continue; - // TODO: Check existing material map!! - //UMaterial * Material = HoudiniCookParams.HoudiniCookManager ? Cast< UMaterial >(HoudiniCookParams.HoudiniCookManager->GetAssignmentMaterial(MaterialShopName)) : nullptr; + // Check first in the existing material map UMaterial * Material = nullptr; UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); if (FoundMaterial) @@ -144,6 +138,7 @@ bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( Material = Cast(*FoundMaterial); } + bool bCreatedNewMaterial = false; if (Material && !Material->IsPendingKill()) { // If cached material exists and has not changed, we can reuse it. @@ -157,8 +152,6 @@ bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( else { // Previous Material was not found, we need to create a new one. - // TODO: Handle this! - //EObjectFlags ObjFlags = (HoudiniCookParams.MaterialAndTextureBakeMode == EBakeMode::Intermediate) ? RF_Transactional : RF_Public | RF_Standalone; EObjectFlags ObjFlags = RF_Public | RF_Standalone; // Create material package and get material name. @@ -181,6 +174,9 @@ bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( if (!Material || Material->IsPendingKill()) continue; + // Get the asset name from the package params + FString AssetName = InPackageParams.HoudiniAssetName.IsEmpty() ? TEXT("HoudiniAsset") : InPackageParams.HoudiniAssetName; + // Get the package and add it to our list UPackage* Package = Material->GetOutermost(); OutPackages.AddUnique(Package); @@ -799,9 +795,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Names of generating Houdini parameters. FString GeneratingParameterNameDiffuseTexture = TEXT(""); @@ -877,48 +871,43 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( Cast(MaterialExpressionMultiply->A.Expression); // See if a diffuse texture is available. - HAPI_ParmId ParmDiffuseTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - - if (ParmDiffuseTextureId >= 0) - { - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + HAPI_ParmInfo ParmDiffuseTextureInfo; + HAPI_ParmId ParmDiffuseTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, + true, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via OGL tag + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, + false, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via Parm name + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); } else { - ParmDiffuseTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - - if (ParmDiffuseTextureId >= 0) - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - } - - // See if uniform color is available. - HAPI_ParmInfo ParmInfoDiffuseColor; - FHoudiniApi::ParmInfo_Init(&ParmInfoDiffuseColor); - HAPI_ParmId ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0, ParmInfoDiffuseColor); - - if (ParmDiffuseColorId >= 0) - { - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_0); - } - else - { - ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1, ParmInfoDiffuseColor); - - if (ParmDiffuseColorId >= 0) - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_1); + // failed to find the texture + ParmDiffuseTextureId = -1; } // If we have diffuse texture parameter. if (ParmDiffuseTextureId >= 0) { - TArray< char > ImageBuffer; + TArray ImageBuffer; // Get image planes of diffuse map. - TArray< FString > DiffuseImagePlanes; + TArray DiffuseImagePlanes; bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( ParmDiffuseTextureId, InMaterialInfo, DiffuseImagePlanes); @@ -1041,6 +1030,24 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( } } + // See if uniform color is available. + HAPI_ParmInfo ParmDiffuseColorInfo; + HAPI_ParmId ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL, ParmDiffuseColorInfo); + + if (ParmDiffuseColorId >= 0) + { + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL); + } + else + { + ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE, ParmDiffuseColorInfo); + + if (ParmDiffuseColorId >= 0) + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE); + } + // If we have uniform color parameter. if (ParmDiffuseColorId >= 0) { @@ -1048,9 +1055,9 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&Color.R, - ParmInfoDiffuseColor.floatValuesIndex, ParmInfoDiffuseColor.size) == HAPI_RESULT_SUCCESS) + ParmDiffuseColorInfo.floatValuesIndex, ParmDiffuseColorInfo.size) == HAPI_RESULT_SUCCESS) { - if (ParmInfoDiffuseColor.size == 3) + if (ParmDiffuseColorInfo.size == 3) Color.A = 1.0f; // Record generating parameter. @@ -1166,9 +1173,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Opacity expressions. UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; @@ -1183,20 +1188,34 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( CreateTexture2DParameters.bSRGB = true; // See if opacity texture is available. - HAPI_ParmId ParmOpacityTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_0); - - if (ParmOpacityTextureId >= 0) - { - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_0); + HAPI_ParmInfo ParmOpacityTextureInfo; + HAPI_ParmId ParmOpacityTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_OPACITY_OGL, + HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED, + true, + ParmOpacityTextureId, + ParmOpacityTextureInfo)) + { + // Found via OGL tag + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_OPACITY, + HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED, + false, + ParmOpacityTextureId, + ParmOpacityTextureInfo)) + { + // Found via Parm name + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY); } else { - ParmOpacityTextureId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_OPACITY_1); - - if (ParmOpacityTextureId >= 0) - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_1); + // failed to find the texture + ParmOpacityTextureId = -1; } // If we have opacity texture parameter. @@ -1366,9 +1385,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( float OpacityValue = 1.0f; bool bNeedsTranslucency = false; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameters. FString GeneratingParameterNameScalar = TEXT(""); @@ -1416,33 +1433,32 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( } } - // Retrieve opacity uniform parameter. - HAPI_ParmInfo ParmInfoOpacityValue; - FHoudiniApi::ParmInfo_Init(&ParmInfoOpacityValue); + // Retrieve opacity value + HAPI_ParmInfo ParmOpacityValueInfo; HAPI_ParmId ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_0, ParmInfoOpacityValue); + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_OGL, ParmOpacityValueInfo); if (ParmOpacityValueId >= 0) { - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_0); + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_OGL); } else { ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_1, ParmInfoOpacityValue); + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA, ParmOpacityValueInfo); if (ParmOpacityValueId >= 0) - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_1); + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA); } if (ParmOpacityValueId >= 0) { - if (ParmInfoOpacityValue.size > 0 && ParmInfoOpacityValue.floatValuesIndex >= 0) + if (ParmOpacityValueInfo.size > 0 && ParmOpacityValueInfo.floatValuesIndex >= 0) { float OpacityValueRetrieved = 1.0f; if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, - (float *)&OpacityValue, ParmInfoOpacityValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + (float *)&OpacityValue, ParmOpacityValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) { if (!ExpressionScalarOpacity) { @@ -1556,9 +1572,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( bool bTangentSpaceNormal = true; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameter. FString GeneratingParameterName = TEXT(""); @@ -1572,36 +1586,47 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( CreateTexture2DParameters.bSRGB = false; // See if separate normal texture is available. - HAPI_ParmId ParmNameNormalId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_0); - - if (ParmNameNormalId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_0); + HAPI_ParmInfo ParmNormalTextureInfo; + HAPI_ParmId ParmNormalTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_NORMAL, + HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED, + false, + ParmNormalTextureId, + ParmNormalTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_NORMAL_OGL, + "", + true, + ParmNormalTextureId, + ParmNormalTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_OGL); } else { - ParmNameNormalId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_1); - - if (ParmNameNormalId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_1); + // failed to find the texture + ParmNormalTextureId = -1; } - if (ParmNameNormalId >= 0) + if (ParmNormalTextureId >= 0) { // Retrieve space for this normal texture. HAPI_ParmInfo ParmInfoNormalType; - FHoudiniApi::ParmInfo_Init(&ParmInfoNormalType); int32 ParmNormalTypeId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); // Retrieve value for normal type choice list (if exists). - if (ParmNormalTypeId >= 0) { FString NormalType = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT); - if (ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0) { HAPI_StringHandle StringHandle; @@ -1621,12 +1646,11 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( if (NormalType.Equals(TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD), ESearchCase::IgnoreCase)) bTangentSpaceNormal = false; } - - TArray< char > ImageBuffer; - + // Retrieve color plane. + TArray ImageBuffer; if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameNormalId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + ParmNormalTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) { UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = @@ -1748,37 +1772,50 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( if (!bExpressionCreated) { // See if diffuse texture is available. - HAPI_ParmId ParmNameBaseId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); - - if (ParmNameBaseId >= 0) + HAPI_ParmInfo ParmDiffuseTextureInfo; + HAPI_ParmId ParmDiffuseTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, + true, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_0); + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, + false, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); } else { - ParmNameBaseId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); - - if (ParmNameBaseId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_1); + // failed to find the texture + ParmDiffuseTextureId = -1; } - if (ParmNameBaseId >= 0) + if (ParmDiffuseTextureId >= 0) { // Normal plane is available in diffuse map. - - TArray< char > ImageBuffer; + TArray ImageBuffer; // Retrieve color plane - this will contain normal data. if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameBaseId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, + ParmDiffuseTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true, ImageBuffer)) { UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); + Cast(Material->Normal.Expression); - UTexture2D * TextureNormal = nullptr; + UTexture2D* TextureNormal = nullptr; if (ExpressionNormal) { TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); @@ -1793,15 +1830,14 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( } } - UPackage * TextureNormalPackage = nullptr; + UPackage* TextureNormalPackage = nullptr; if (TextureNormal) - TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); + TextureNormalPackage = Cast(TextureNormal->GetOuter()); HAPI_ImageInfo ImageInfo; FHoudiniApi::ImageInfo_Init(&ImageInfo); Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &ImageInfo); if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) { @@ -1910,9 +1946,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( bool bExpressionCreated = false; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameter. FString GeneratingParameterName = TEXT(""); @@ -1926,29 +1960,43 @@ FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( CreateTexture2DParameters.bSRGB = false; // See if specular texture is available. - HAPI_ParmId ParmNameSpecularId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_0); - - if (ParmNameSpecularId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_0); + HAPI_ParmInfo ParmSpecularTextureInfo; + HAPI_ParmId ParmSpecularTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL, + HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED, + true, + ParmSpecularTextureId, + ParmSpecularTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_SPECULAR, + HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED, + false, + ParmSpecularTextureId, + ParmSpecularTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR); } else { - ParmNameSpecularId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_SPECULAR_1); - - if (ParmNameSpecularId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_1); + // failed to find the texture + ParmSpecularTextureId = -1; } - if (ParmNameSpecularId >= 0) + if (ParmSpecularTextureId >= 0) { - TArray< char > ImageBuffer; + TArray ImageBuffer; // Retrieve color plane. if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameSpecularId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + ParmSpecularTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) { UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = @@ -2065,35 +2113,33 @@ FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( } } - HAPI_ParmInfo ParmInfoSpecularColor; - FHoudiniApi::ParmInfo_Init(&ParmInfoSpecularColor); - HAPI_ParmId ParmNameSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_0, ParmInfoSpecularColor); + // See if we have a specular color + HAPI_ParmInfo ParmSpecularColorInfo; + HAPI_ParmId ParmSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL, ParmSpecularColorInfo); - if (ParmNameSpecularColorId >= 0) + if (ParmSpecularColorId >= 0) { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_0); + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL); } else { - ParmNameSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_1, ParmInfoSpecularColor); + ParmSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR, ParmSpecularColorInfo); - if (ParmNameSpecularColorId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_1); + if (ParmSpecularColorId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR); } - if (!bExpressionCreated && ParmNameSpecularColorId >= 0) + if (!bExpressionCreated && ParmSpecularColorId >= 0) { // Specular color is available. - FLinearColor Color = FLinearColor::White; - if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmInfoSpecularColor.floatValuesIndex, ParmInfoSpecularColor.size) == HAPI_RESULT_SUCCESS) + ParmSpecularColorInfo.floatValuesIndex, ParmSpecularColorInfo.size) == HAPI_RESULT_SUCCESS) { - if (ParmInfoSpecularColor.size == 3) + if (ParmSpecularColorInfo.size == 3) Color.A = 1.0f; UMaterialExpressionVectorParameter * ExpressionSpecularColor = @@ -2151,9 +2197,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( bool bExpressionCreated = false; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameter. FString GeneratingParameterName = TEXT(""); @@ -2167,29 +2211,42 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( CreateTexture2DParameters.bSRGB = false; // See if roughness texture is available. - HAPI_ParmId ParmNameRoughnessId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); - - if (ParmNameRoughnessId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_0); + HAPI_ParmInfo ParmRoughnessTextureInfo; + HAPI_ParmId ParmRoughnessTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED, + true, + ParmRoughnessTextureId, + ParmRoughnessTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED, + false, + ParmRoughnessTextureId, + ParmRoughnessTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS); } else { - ParmNameRoughnessId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); - - if (ParmNameRoughnessId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_1); + // failed to find the texture + ParmRoughnessTextureId = -1; } - if (ParmNameRoughnessId >= 0) + if (ParmRoughnessTextureId >= 0) { - TArray< char > ImageBuffer; - + TArray ImageBuffer; // Retrieve color plane. if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameRoughnessId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + ParmRoughnessTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer ) ) { UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = @@ -2304,25 +2361,25 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( } } - HAPI_ParmInfo ParmInfoRoughnessValue; - FHoudiniApi::ParmInfo_Init(&ParmInfoRoughnessValue); - HAPI_ParmId ParmNameRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0, ParmInfoRoughnessValue); + // See if we have a roughness value + HAPI_ParmInfo ParmRoughnessValueInfo; + HAPI_ParmId ParmRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL, ParmRoughnessValueInfo); - if (ParmNameRoughnessValueId >= 0) + if (ParmRoughnessValueId >= 0) { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_0); + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL); } else { - ParmNameRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1, ParmInfoRoughnessValue); + ParmRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS, ParmRoughnessValueInfo); - if (ParmNameRoughnessValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_1); + if (ParmRoughnessValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS); } - if (!bExpressionCreated && ParmNameRoughnessValueId >= 0) + if (!bExpressionCreated && ParmRoughnessValueId >= 0) { // Roughness value is available. @@ -2330,7 +2387,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&RoughnessValue, - ParmInfoRoughnessValue.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + ParmRoughnessValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) { UMaterialExpressionScalarParameter * ExpressionRoughnessValue = Cast< UMaterialExpressionScalarParameter >(Material->Roughness.Expression); @@ -2392,9 +2449,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( bool bExpressionCreated = false; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameter. FString GeneratingParameterName = TEXT(""); @@ -2408,21 +2463,43 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( CreateTexture2DParameters.bSRGB = false; // See if metallic texture is available. - HAPI_ParmId ParmNameMetallicId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_METALLIC); - - if (ParmNameMetallicId >= 0) - { + HAPI_ParmInfo ParmMetallicTextureInfo; + HAPI_ParmId ParmMetallicTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_METALLIC_OGL, + HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED, + true, + ParmMetallicTextureId, + ParmMetallicTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_METALLIC, + HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED, + false, + ParmMetallicTextureId, + ParmMetallicTextureInfo)) + { + // Found via Parm name GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC); } + else + { + // failed to find the texture + ParmMetallicTextureId = -1; + } - if (ParmNameMetallicId >= 0) + if (ParmMetallicTextureId >= 0) { - TArray< char > ImageBuffer; + TArray ImageBuffer; // Retrieve color plane. if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameMetallicId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + ParmMetallicTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) { UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = @@ -2431,7 +2508,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( UTexture2D * TextureMetallic = nullptr; if (ExpressionMetallic) { - TextureMetallic = Cast< UTexture2D >(ExpressionMetallic->Texture); + TextureMetallic = Cast(ExpressionMetallic->Texture); } else { @@ -2538,23 +2615,32 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( } } - HAPI_ParmInfo ParmInfoMetallic; - FHoudiniApi::ParmInfo_Init(&ParmInfoMetallic); - HAPI_ParmId ParmNameMetallicValueIdx = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmInfoMetallic); + // Get the metallic value + HAPI_ParmInfo ParmMetallicValueInfo; + HAPI_ParmId ParmMetallicValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL, ParmMetallicValueInfo); - if (ParmNameMetallicValueIdx >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); + if (ParmMetallicValueId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL); + } + else + { + ParmMetallicValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmMetallicValueInfo); + + if (ParmMetallicValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); + } - if (!bExpressionCreated && ParmNameMetallicValueIdx >= 0) + if (!bExpressionCreated && ParmMetallicValueId >= 0) { // Metallic value is available. - float MetallicValue = 0.0f; if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&MetallicValue, - ParmInfoMetallic.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + ParmMetallicTextureInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) { UMaterialExpressionScalarParameter * ExpressionMetallicValue = Cast< UMaterialExpressionScalarParameter >(Material->Metallic.Expression); @@ -2616,9 +2702,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( bool bExpressionCreated = false; HAPI_Result Result = HAPI_RESULT_SUCCESS; - //EBakeMode BakeMode = HoudiniCookParams.MaterialAndTextureBakeMode; - //EObjectFlags ObjectFlag = (BakeMode == EBakeMode::CookToTemp) ? RF_NoFlags : RF_Standalone; - EObjectFlags ObjectFlag = RF_NoFlags; + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; // Name of generating Houdini parameter. FString GeneratingParameterName = TEXT(""); @@ -2632,21 +2716,43 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( CreateTexture2DParameters.bSRGB = false; // See if emissive texture is available. - HAPI_ParmId ParmNameEmissiveId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_EMISSIVE); - - if (ParmNameEmissiveId >= 0) - { + HAPI_ParmInfo ParmEmissiveTextureInfo; + HAPI_ParmId ParmEmissiveTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED, + true, + ParmEmissiveTextureId, + ParmEmissiveTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_EMISSIVE, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED, + false, + ParmEmissiveTextureId, + ParmEmissiveTextureInfo)) + { + // Found via Parm name GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE); } + else + { + // failed to find the texture + ParmEmissiveTextureId = -1; + } - if (ParmNameEmissiveId >= 0) + if (ParmEmissiveTextureId >= 0) { TArray< char > ImageBuffer; // Retrieve color plane. if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNameEmissiveId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + ParmEmissiveTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) { UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = @@ -2761,23 +2867,22 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( } } - HAPI_ParmInfo ParmInfoEmissive; - FHoudiniApi::ParmInfo_Init(&ParmInfoEmissive); - HAPI_ParmId ParmNameEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0, ParmInfoEmissive); + HAPI_ParmInfo ParmEmissiveValueInfo; + HAPI_ParmId ParmEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL, ParmEmissiveValueInfo); - if (ParmNameEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_0); + if (ParmEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL); else { - ParmNameEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByNameOrTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1, ParmInfoEmissive); + ParmEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE, ParmEmissiveValueInfo); - if (ParmNameEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_1); + if (ParmEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE); } - if (!bExpressionCreated && ParmNameEmissiveValueId >= 0) + if (!bExpressionCreated && ParmEmissiveValueId >= 0) { // Emissive color is available. @@ -2785,9 +2890,9 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( if (FHoudiniApi::GetParmFloatValues( FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmInfoEmissive.floatValuesIndex, ParmInfoEmissive.size) == HAPI_RESULT_SUCCESS) + ParmEmissiveValueInfo.floatValuesIndex, ParmEmissiveValueInfo.size) == HAPI_RESULT_SUCCESS) { - if (ParmInfoEmissive.size == 3) + if (ParmEmissiveValueInfo.size == 3) Color.A = 1.0f; UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = @@ -3140,96 +3245,169 @@ FHoudiniMaterialTranslator::FindGeneratedTexture(const FString& TextureString, c return nullptr; // Try to find the corresponding texture in the cooked temporary package generated by an HDA - UTexture* FoundTexture = nullptr; - for(const auto& CurrentPackage : InPackages) - { - // Iterate through the cooked packages - if (!CurrentPackage || CurrentPackage->IsPendingKill()) - continue; +UTexture* FoundTexture = nullptr; +for (const auto& CurrentPackage : InPackages) +{ + // Iterate through the cooked packages + if (!CurrentPackage || CurrentPackage->IsPendingKill()) + continue; - // First, check if the package contains a texture - FString CurrentPackageName = CurrentPackage->GetName(); - UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); - if (!PackageTexture) - continue; + // First, check if the package contains a texture + FString CurrentPackageName = CurrentPackage->GetName(); + UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); + if (!PackageTexture) + continue; - // Then check if the package's metadata match what we're looking for - // Make sure this texture was generated by Houdini Engine - UMetaData* MetaData = CurrentPackage->GetMetaData(); - if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - continue; + // Then check if the package's metadata match what we're looking for + // Make sure this texture was generated by Houdini Engine + UMetaData* MetaData = CurrentPackage->GetMetaData(); + if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + continue; - // Get the texture type from the meta data - // Texture type store has meta data will be C_A, N, S, R etc.. - const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); - if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } + // Get the texture type from the meta data + // Texture type store has meta data will be C_A, N, S, R etc.. + const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); + if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } - // Convert the texture type to a "friendly" version - // C_A to diffuse, N to Normal, S to Specular etc... - FString TextureTypeFriendlyString = TextureTypeString; - FString TextureTypeFriendlyAlternateString = TEXT(""); - if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) - { - TextureTypeFriendlyString = TEXT("diffuse"); - TextureTypeFriendlyAlternateString = TEXT("basecolor"); - } - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("normal"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("emissive"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("specular"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("roughness"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("metallic"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("opacity"); - - // See if we have a match between the texture string and the friendly name - if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) - { - FoundTexture = PackageTexture; - break; - } + // Convert the texture type to a "friendly" version + // C_A to diffuse, N to Normal, S to Specular etc... + FString TextureTypeFriendlyString = TextureTypeString; + FString TextureTypeFriendlyAlternateString = TEXT(""); + if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) + { + TextureTypeFriendlyString = TEXT("diffuse"); + TextureTypeFriendlyAlternateString = TEXT("basecolor"); + } + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("normal"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("emissive"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("specular"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("roughness"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("metallic"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("opacity"); - // Get the node path from the meta data - const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); - if (NodePath.IsEmpty()) - continue; + // See if we have a match between the texture string and the friendly name + if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) + { + FoundTexture = PackageTexture; + break; + } - // See if we have a match with the path and texture type - FString PathAndType = NodePath + TEXT("/") + TextureTypeString; - if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } + // Get the node path from the meta data + const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); + if (NodePath.IsEmpty()) + continue; + + // See if we have a match with the path and texture type + FString PathAndType = NodePath + TEXT("/") + TextureTypeString; + if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // See if we have a match with the friendly path and texture type + FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; + if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } - // See if we have a match with the friendly path and texture type - FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; + // Try the alternate friendly string + if (!TextureTypeFriendlyAlternateString.IsEmpty()) + { + PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) { FoundTexture = PackageTexture; break; } + } +} - // Try the alternate friendly string - if (!TextureTypeFriendlyAlternateString.IsEmpty()) +return FoundTexture; +} + + +bool +FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + const HAPI_NodeId& InNodeId, + const std::string& InTextureParmName, + const std::string& InUseTextureParmName, + const bool& bFindByTag, + HAPI_ParmId& OutParmId, + HAPI_ParmInfo& OutParmInfo) +{ + OutParmId = -1; + + if(bFindByTag) + OutParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InTextureParmName, OutParmInfo); + else + OutParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InTextureParmName, OutParmInfo); + + if (OutParmId < 0) + { + // Failed to find the texture + return false; + } + + // We found a valid parameter, check if the matching "use" parameter exists + HAPI_ParmInfo FoundUseParmInfo; + HAPI_ParmId FoundUseParmId = -1; + if(bFindByTag) + FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InUseTextureParmName, FoundUseParmInfo); + else + FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InUseTextureParmName, FoundUseParmInfo); + + if (FoundUseParmId >= 0) + { + // We found a valid "use" parameter, check if it is disabled + // Get the param value + int32 UseValue = 0; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &UseValue, FoundUseParmInfo.intValuesIndex, 1)) { - PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; - if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + if (UseValue == 0) { - FoundTexture = PackageTexture; - break; + // We found the texture parm, but the "use" param/tag is disabled, so don't use it! + // We still return true as we found the parameter, this will prevent looking for other parms + OutParmId = -1; + return true; } } } - return FoundTexture; + // Finally, make sure that the found texture Parm is not empty! + FString ParmValue = FString(); + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandle, OutParmInfo.stringValuesIndex, 1)) + { + // Convert the string handle to FString + FHoudiniEngineString::ToFString(StringHandle, ParmValue); + } + + if (ParmValue.IsEmpty()) + { + // We found the parm, but it's empty, don't use it! + // We still return true as we found the parameter, this will prevent looking for other parms + OutParmId = -1; + return true; + } + + return true; } + diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h index 678171f3e..9805049bf 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h @@ -32,7 +32,7 @@ #include "UObject/ObjectMacros.h" #include "Engine/TextureDefines.h" -//#include "HoudiniMaterialTranslator.generated.h" +#include class UMaterial; class UMaterialInterface; @@ -131,6 +131,16 @@ struct HOUDINIENGINE_API FHoudiniMaterialTranslator const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialNodeInfo, FString& OutRelativePath); static bool GetMaterialRelativePath( const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath); + + // Returns true if a texture parameter was found + // Ensures that the texture is not disabled via the "UseTexture" Parm name/tag + static bool FindTextureParamByNameOrTag( + const HAPI_NodeId& InNodeId, + const std::string& InTextureParmName, + const std::string& InUseTextureParmName, + const bool& bFindByTag, + HAPI_ParmId& OutParmId, + HAPI_ParmInfo& OutParmInfo); protected: diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp index bdd6a85d8..f930813b0 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp @@ -75,6 +75,12 @@ #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE +static TAutoConsoleVariable CVarHoudiniEngineMeshBuildTimer( + TEXT("HoudiniEngine.MeshBuildTimer"), + 0.0, + TEXT("When enabled, the plugin will output timings during the Mesh creation.\n") +); + // bool FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( @@ -1425,6 +1431,9 @@ FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentif bool FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() { + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + double time_start = FPlatformTime::Seconds(); // Start by updating the vertex list @@ -1493,6 +1502,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() FHoudiniEngineUtils::AddMeshSocketsToArray_Group( HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); + double tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre Split-Loop in %f seconds."), tick - time_start); + } + UStaticMesh* MainStaticMesh = nullptr; bool bAssignedCustomCollisionMesh = false; ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; @@ -1502,6 +1517,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) { + double split_tick = FPlatformTime::Seconds(); + // Get split group name const FString& SplitGroupName = AllSplitGroups[SplitId]; @@ -1539,6 +1556,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() FHoudiniOutputObjectIdentifier OutputObjectIdentifier( HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; // Get/Create the Aggregate Collisions for this mesh identifier FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); @@ -1708,6 +1727,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() continue; } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + // Load existing raw model. This will be empty as we are constructing a new mesh. FRawMesh RawMesh; if (!bRebuildStaticMesh) @@ -1716,6 +1741,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // the geometry hasn't changed, but the materials have. // We can just load the old data into the Raw mesh and reuse it. SrcModel->LoadRawMesh(RawMesh); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - LoadRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } } else { @@ -1750,6 +1781,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() RawMesh.WedgeTangentZ[WedgeTangentZIdx].Z = SplitNormals[WedgeTangentZIdx * 3 + 1]; } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Normals in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // TANGENTS @@ -1827,6 +1863,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Tangents in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // VERTEX COLORS AND ALPHAS //--------------------------------------------------------------------------------------------------------------------- @@ -1894,6 +1936,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() RawMesh.WedgeColors.Init(DefaultWedgeColor, WedgeColorsCount); } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Cd and Alpha in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // FACE SMOOTHING //--------------------------------------------------------------------------------------------------------------------- @@ -1924,6 +1972,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() RawMesh.FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - FaceSmoothing in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // UVS //--------------------------------------------------------------------------------------------------------------------- @@ -1977,6 +2031,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // If not, the first UV set will be used FoundStaticMesh->LightMapCoordinateIndex = LightMapUVChannel; + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - UVs in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // LIGHTMAP RESOLUTION //--------------------------------------------------------------------------------------------------------------------- @@ -1987,6 +2047,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // make sure the mesh has a new lighting guid FoundStaticMesh->LightingGuid = FGuid::NewGuid(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Lightmap Resolutions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // INDICES //--------------------------------------------------------------------------------------------------------------------- @@ -2089,6 +2155,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() ValidVertexId += 3; } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // POSITIONS //--------------------------------------------------------------------------------------------------------------------- @@ -2136,6 +2208,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() continue; } */ + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } } //--------------------------------------------------------------------------------------------------------------------- @@ -2146,6 +2224,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Need to update!! UpdatePartFaceMaterialOverridesIfNeeded(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Material Overrides in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + //--------------------------------------------------------------------------------------------------------------------- // FACE MATERIALS //--------------------------------------------------------------------------------------------------------------------- @@ -2178,6 +2262,9 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Process material overrides first if (PartFaceMaterialOverrides.Num() > 0) { + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + // If the part has material overrides RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) @@ -2202,12 +2289,15 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() if (FoundMaterialInterface) MaterialInterface = *FoundMaterialInterface; - if (!MaterialInterface && !MaterialName.IsEmpty()) + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) { // Only try to load a material if has a chance to be valid! MaterialInterface = Cast( StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); } if (MaterialInterface) @@ -2385,6 +2475,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() FoundStaticMesh->StaticMaterials.Empty(); FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Face Materials in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } // Update the Build Settings using the default setting values UpdateMeshBuildSettings( @@ -2411,10 +2507,16 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // TODO: // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - // This is required due to the impeding deprecation of FRawMesh + // This was required due to the impeding deprecation of FRawMesh // If we dont update this UE4 will crash upon deleting an asset. - SrcModel->StaticMeshOwner = FoundStaticMesh; - + //SrcModel->StaticMeshOwner = FoundStaticMesh; + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreSaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + // Store the new raw mesh if it is valid if (RawMesh.IsValid()) { @@ -2436,6 +2538,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() SrcModel->SaveRawMesh(RawMesh); } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - SaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + // NOTE: This Mesh Description patch causes crashes in certain situations and need to be revised. Until // this patch has been properly fixed, we're just going to revert back to old (non-crashing) behaviour. // if (IsValid(FoundStaticMesh)) @@ -2548,6 +2656,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Attributes in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + // Notify that we created a new Static Mesh if needed if (bNewStaticMeshCreated) FAssetRegistryModule::AssetCreated(FoundStaticMesh); @@ -2561,6 +2675,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Total Split time: %f seconds."), tick - split_tick); + } } // Look if we only have colliders @@ -2579,6 +2700,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() FHoudiniScopedGlobalSilence ScopedGlobalSilence; for (auto& Current : StaticMeshToBuild) { + tick = FPlatformTime::Seconds(); + UStaticMesh* SM = Current.Value; if (!SM || SM->IsPendingKill()) continue; @@ -2601,8 +2724,6 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // Clean up old colliders from a previous cook BodySetup->Modify(); BodySetup->RemoveSimpleCollision(); - // Create new GUID - BodySetup->InvalidatePhysicsData(); FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); @@ -2664,68 +2785,61 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; } + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + // BUILD the Static Mesh // bSilent doesnt add the Build Errors... double build_start = FPlatformTime::Seconds(); TArray SMBuildErrors; SM->Build(true, &SMBuildErrors); - double build_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); - - RefreshCollisionChange(*SM); - - SM->GetOnMeshChanged().Broadcast(); - - /* - // Try to find the outer package so we can dirty it up - if (SM->GetOuter()) + if (bDoTiming) { - SM->GetOuter()->MarkPackageDirty(); + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - StaticMesh->Build() executed in %f seconds."), tick - build_start); } - else + + // This replaces the call to RefreshCollision below, but without CreateNavCollision + // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, + // and can be expensive depending on the vert/poly count of the mesh + // RefreshCollisionChange(*SM); { - SM->MarkPackageDirty(); + for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + { + UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); + if (StaticMeshComponent->GetStaticMesh() == SM) + { + // it needs to recreate IF it already has been created + if (StaticMeshComponent->IsPhysicsStateCreated()) + { + StaticMeshComponent->RecreatePhysicsState(); + } + } + } + + FEditorSupportDelegates::RedrawAllViewports.Broadcast(); } - */ - + + SM->GetOnMeshChanged().Broadcast(); UPackage* MeshPackage = SM->GetOutermost(); if (MeshPackage && !MeshPackage->IsPendingKill()) { MeshPackage->MarkPackageDirty(); - - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - TArray PackageToSave; - PackageToSave.Add(MeshPackage); - - // Save the created package - FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); - */ } - } - // TODO: Still necessary ? SM->Build should actually update the navmesh... - // Now that all the meshes are built and their collisions meshes and primitives updated, - // we need to update their pre-built navigation collision used by the navmesh - for (auto& Iter : OutputObjects) - { - UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) - continue; - - UBodySetup * BodySetup = StaticMesh->BodySetup; - if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) + if (bDoTiming) { - // Unreal caches the Navigation Collision and never updates it for StaticMeshes, - // so we need to manually flush and recreate the data to have proper navigation collision - BodySetup->InvalidatePhysicsData(); - BodySetup->CreatePhysicsMeshes(); - StaticMesh->NavCollision->Setup(BodySetup); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); } } + // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup + // Here as it has already been handled by the StaticMesh Build call + double time_end = FPlatformTime::Seconds(); HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() executed in %f seconds."), time_end - time_start); @@ -2735,6 +2849,9 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() bool FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() { + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + double time_start = FPlatformTime::Seconds(); // Start by updating the vertex list @@ -2805,7 +2922,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); double tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); + if (bDoTiming) + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); UStaticMesh* MainStaticMesh = nullptr; bool bAssignedCustomCollisionMesh = false; @@ -2816,6 +2934,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) { + double split_tick = FPlatformTime::Seconds(); + // Get split group name const FString& SplitGroupName = AllSplitGroups[SplitId]; @@ -3022,8 +3142,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() continue; } - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } bool bHasNormal = false; bool bHasTangents = false; @@ -3132,8 +3255,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() ValidVertexId += 3; } - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // POSITIONS @@ -3175,8 +3301,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } } - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // MATERIALS @@ -3321,6 +3450,9 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } else { + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + // If we have material overrides for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) { @@ -3344,12 +3476,15 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() if (FoundMaterialInterface) MaterialInterface = *FoundMaterialInterface; - if (!MaterialInterface && !MaterialName.IsEmpty()) + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) { // Only try to load a material if has a chance to be valid! MaterialInterface = Cast< UMaterialInterface >( StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); } if (MaterialInterface) @@ -3447,8 +3582,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } } - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } // // VERTEX INSTANCE ATTRIBUTES @@ -3556,8 +3694,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); VertexInstanceUVs.SetNumIndices(UVSetCount); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } // Allocate space for the vertex instances and polygons MeshDescription->ReserveNewVertexInstances(SplitIndices.Num()); @@ -3679,8 +3820,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() MeshDescription->CreateTriangle(PolygonGroupID, FaceVertexInstanceIDs); } - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // FACE SMOOTHING @@ -3719,8 +3863,11 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // Check FStaticMeshOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, *MeshDescription); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } //--------------------------------------------------------------------------------------------------------------------- // LIGHTMAP RESOLUTION @@ -3882,8 +4029,12 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished MD in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Total Split time: %f seconds."), tick - split_tick); + } } // Look if we only have colliders @@ -3901,7 +4052,9 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() FHoudiniScopedGlobalSilence ScopedGlobalSilence; for (auto& Current : StaticMeshToBuild) - { + { + tick = FPlatformTime::Seconds(); + UStaticMesh* SM = Current.Value; if (!SM || SM->IsPendingKill()) continue; @@ -3989,25 +4142,30 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + // BUILD the Static Mesh // bSilent doesnt add the Build Errors... double build_start = FPlatformTime::Seconds(); TArray SMBuildErrors; - SM->Build(true, &SMBuildErrors); - double build_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->Build() executed in %f seconds."), build_end - build_start); + SM->Build(true, &SMBuildErrors); - // TODO: copied the content of RefreshCollision below and commented out CreateNavCollision - // it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, + if (bDoTiming) + { + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - StaticMesh->Build() executed in %f seconds."), tick - build_start); + } + + // This replaces the call to RefreshCollision below, but without CreateNavCollision + // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, // and can be expensive depending on the vert/poly count of the mesh - // TODO: also moved this to after the call to Build, since Build updates the mesh's - // physics state (calling this before Build when rebuilding an existing high poly mesh as - // low poly mesh, incurs quite a performance hit. This is likely due to processing of physics - // meshes with high vert/poly count before the Build // RefreshCollisionChange(*SM); { - // SM->CreateNavCollision(/*bIsUpdate=*/true); - for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) { UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); @@ -4025,61 +4183,21 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } SM->GetOnMeshChanged().Broadcast(); - /* - // Try to find the outer package so we can dirty it up - if (SM->GetOuter()) - { - SM->GetOuter()->MarkPackageDirty(); - } - else - { - SM->MarkPackageDirty(); - } - */ UPackage* MeshPackage = SM->GetOutermost(); if (MeshPackage && !MeshPackage->IsPendingKill()) { MeshPackage->MarkPackageDirty(); - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - TArray PackageToSave; - PackageToSave.Add(MeshPackage); + } - // Save the created package - FEditorFileUtils::PromptForCheckoutAndSave(PackageToSave, false, false); - */ + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); } } - // TODO: Still necessary ? SM->Build should actually update the navmesh... - // TODO: Commented out for now, since it appears that the content of the loop is - // already called in UStaticMesh::BuildInternal and UStaticMesh::PostBuildInternal - //// Now that all the meshes are built and their collisions meshes and primitives updated, - //// we need to update their pre-built navigation collision used by the navmesh - //for (auto& Iter : OutputObjects) - //{ - // UStaticMesh* StaticMesh = Cast(Iter.Value.OutputObject); - // if (!StaticMesh || StaticMesh->IsPendingKill()) - // continue; - - // UBodySetup * BodySetup = StaticMesh->BodySetup; - // if (BodySetup && !BodySetup->IsPendingKill() && StaticMesh->NavCollision) - // { - // // Unreal caches the Navigation Collision and never updates it for StaticMeshes, - // // so we need to manually flush and recreate the data to have proper navigation collision - // // TODO: Is this still required? These two functions are called by - // // UStaticMesh::BuildInternal, which is called by UStaticMesh::Build/BatchBuild - // // BodySetup->InvalidatePhysicsData(); - // // BodySetup->CreatePhysicsMeshes(); - - // // TODO: Is this still required? This function is called by UStaticMesh::CreateNavCollision - // // which is called by the UStaticMesh::PostBuildInternal function, which is called at the - // // end of the build. - // // StaticMesh->NavCollision->Setup(BodySetup); - // } - //} + // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup + // Here as it has already been handled by the StaticMesh Build call double time_end = FPlatformTime::Seconds(); HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() executed in %f seconds."), time_end - time_start); @@ -4090,6 +4208,9 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() bool FHoudiniMeshTranslator::CreateHoudiniStaticMesh() { + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh")); const double time_start = FPlatformTime::Seconds(); @@ -4146,7 +4267,8 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() // bool MeshMaterialsHaveBeenReset = false; double tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); + if(bDoTiming) + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); // Iterate through all detected split groups we care about and split geometry. bool bMainGeoOrFirstLODFound = false; @@ -4226,7 +4348,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() FHoudiniOutputObjectIdentifier OutputObjectIdentifier( HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName]; OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; // Try to find existing properties for this identifier @@ -4266,8 +4388,11 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() } FoundOutputObject->bProxyIsCurrent = true; - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } if (bRebuildStaticMesh) { @@ -4519,6 +4644,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") TEXT("- skipping."), HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; } // We need to swap Z and Y coordinate here, and convert from m to cm. @@ -4666,6 +4792,9 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() { TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Material Overrides")); + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) { int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; @@ -4674,7 +4803,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() UMaterialInterface * MaterialInterface = nullptr; int32 CurrentFaceMaterialIdx = 0; - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); if (FoundMaterialInterface) MaterialInterface = *FoundMaterialInterface; @@ -4688,12 +4817,15 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() if (FoundMaterialInterface) MaterialInterface = *FoundMaterialInterface; - if (!MaterialInterface && !MaterialName.IsEmpty()) + // Only try to load a material if it has a chance to be valid! + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) { - // Only try to load a material if has a chance to be valid! MaterialInterface = Cast( StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); } if (MaterialInterface) diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp index 92bfe3143..5a6f68c73 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp @@ -658,8 +658,9 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); // Retrieve information about each object contained within our asset. - TArray< HAPI_ObjectInfo > ObjectInfos; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos)) + TArray ObjectInfos; + TArray ObjectTransforms; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) return false; TArray EditableCurveObjIds; @@ -936,14 +937,8 @@ FHoudiniOutputTranslator::BuildAllOutputs( // Retrieve information about each object contained within our asset. TArray ObjectInfos; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos)) - return false; - - //const int32 ObjectCount = ObjectInfos.Num(); - - // Retrieve transforms for each object in this asset. TArray ObjectTransforms; - if (!FHoudiniEngineUtils::HapiGetObjectTransforms(AssetId, ObjectTransforms)) + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) return false; // Mark all the previous HGPOs on the outputs as stale diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp index 5ff30c0c3..b95d8bb84 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp @@ -134,7 +134,11 @@ FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) // We set "UpdateValues" to false because we do not want to "read" the parameter value // from Houdini but keep the loaded value - // This is the first cook on loading after a save or duplication, + // Share AssetInfo if needed + bool bNeedToFetchAssetInfo = true; + HAPI_AssetInfo AssetInfo; + + // This is the first cook on loading after a save or duplication for (int32 Idx = 0; Idx < HAC->Parameters.Num(); ++Idx) { UHoudiniParameter* Param = HAC->Parameters[Idx]; @@ -149,8 +153,14 @@ FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) case EHoudiniParameterType::MultiParm: { // We need to sync the Ramp parameters first, so that their child parameters can be kept + if (bNeedToFetchAssetInfo) + { + FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &AssetInfo); + bNeedToFetchAssetInfo = false; + } + // TODO: Simplify this, should be handled in BuildAllParameters - SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, Idx); + SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, AssetInfo); } break; @@ -2479,11 +2489,10 @@ FHoudiniParameterTranslator::GetFolderTypeFromParamInfo(const HAPI_ParmInfo* Par } bool -FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InParam, TArray &OldParams, const int32& InAssetId, const int32 CurrentIndex) +FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad( + UHoudiniParameter* InParam, TArray& OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo) { - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam || MultiParam->IsPendingKill()) return false; @@ -2492,15 +2501,9 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InPara if (MultiParam->GetParameterType() == EHoudiniParameterType::FloatRamp) FloatRampParameter = Cast(MultiParam); - else if (MultiParam->GetParameterType() == EHoudiniParameterType::ColorRamp) ColorRampParameter = Cast(MultiParam); - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); - HAPI_NodeId NodeId = AssetInfo.nodeId; int32 Idx = 0; @@ -2546,24 +2549,49 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InPara } } - - // Sync nested multi-params recursively - for (int32 ParamIdx = CurrentIndex; ParamIdx < OldParams.Num(); ++ParamIdx) + // We are going to Sync nested multi-params recursively + int32 MyParmId = InParam->GetParmId(); + // First, we need to manually look for our index in the old map + // Since there is a possibility that the parameter interface has changed since our previous cook + int32 MyIndex = -1; + for (int32 ParamIdx = 0; ParamIdx < OldParams.Num(); ParamIdx++) { - UHoudiniParameter* NextParm = OldParams[ParamIdx]; - if (!NextParm || NextParm->IsPendingKill()) + UHoudiniParameter* CurrentOldParm = OldParams[ParamIdx]; + if (!IsValid(CurrentOldParm)) continue; - if (NextParm->GetParentParmId() == ParmId) + if (CurrentOldParm->GetParmId() != MyParmId) + continue; + + // We found ourself, exit now + MyIndex = ParamIdx; + break; + } + + if (MyIndex >= 0) + { + // Now Sync nested multi-params recursively + for (int32 ParamIdx = MyIndex + 1; ParamIdx < OldParams.Num(); ParamIdx++) { - if (NextParm->GetParameterType() == EHoudiniParameterType::MultiParm) - { - SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, ParamIdx); - } + UHoudiniParameter* NextParm = OldParams[ParamIdx]; + if (!IsValid(NextParm)) + continue; + + if (NextParm->GetParentParmId() != ParmId) + continue; + + if (NextParm->GetParameterType() != EHoudiniParameterType::MultiParm) + continue; + + // Always make sure to NOT recurse on ourselves! + // This could happen if parms have been deleted... + if (NextParm->GetParmId() == MyParmId) + continue; + + SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, AssetInfo); } } - // The multiparm is a ramp, Get the param infos again, since the number of param instances is changed if (!GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) return false; @@ -2618,7 +2646,6 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad(UHoudiniParameter* InPara } } - return true; } @@ -2706,8 +2733,7 @@ bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam int32 Idx = 0; int32 InstanceCount = -1; HAPI_ParmId ParmId = -1; - TArray< HAPI_ParmInfo > ParmInfos; - + TArray ParmInfos; if (!FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) return false; @@ -2719,10 +2745,11 @@ bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam if (InsertIndex != InstanceCount) return false; - - // Starting index of parameters which just inserted + // Starting index of parameters which we just inserted Idx += 3 * InsertIndexStart; - + + if (!ParmInfos.IsValidIndex(Idx + 2)) + return false; for (auto & Event : *Events) { @@ -2841,44 +2868,56 @@ FHoudiniParameterTranslator::UploadDirectoryPath(UHoudiniParameterFile* InParam) } bool -FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, +FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos) { + // TODO: FIX/IMPROVE THIS! + // This is bad, that function can be called recursively, fetches all parameters, + // iterates on them, and fetches their name!! WTF! + // TODO: Slightly better now, at least we dont fetch every parameter's name! + // Reset outputs - OutStartIdx = 0; + OutStartIdx = -1; OutInstanceCount = -1; OutParmId = -1; OutParmInfos.Empty(); - // .. the asset's node info + // Try to find the parameter by its name + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, TCHAR_TO_UTF8(*InParmName), &OutParmId), false); + + if (OutParmId < 0) + return false; + + // Get the asset's node info HAPI_NodeInfo NodeInfo; HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &NodeInfo), false); + // Get all parameters OutParmInfos.SetNumUninitialized(NodeInfo.parmCount); HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &OutParmInfos[0], 0, NodeInfo.parmCount), false); - - while (OutStartIdx < OutParmInfos.Num()) + OutStartIdx = 0; + for (const auto& CurrentParmInfo : OutParmInfos) { - FString ParmNameBuffer; - FHoudiniEngineString(OutParmInfos[OutStartIdx].nameSH).ToFString(ParmNameBuffer); - - if (ParmNameBuffer == InParmName) + if (OutParmId == CurrentParmInfo.id) { - OutParmId = OutParmInfos[OutStartIdx].id; OutInstanceCount = OutParmInfos[OutStartIdx].instanceCount; - break; + + // Increment, to get the Start index of the ramp children parameters + OutStartIdx++; + return true; } - OutStartIdx += 1; + OutStartIdx++; } - // Start index of the ramp children parameters - OutStartIdx += 1; + // We failed to find the parm + OutStartIdx = -1; - return true; + return false; } bool diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h index 46ef25b34..0cdba1cd1 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h @@ -66,10 +66,11 @@ struct HOUDINIENGINE_API FHoudiniParameterTranslator static bool RevertParameterToDefault(UHoudiniParameter* InParam); // - static bool SyncMultiParmValuesAtLoad(UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const int32 Idx); + static bool SyncMultiParmValuesAtLoad( + UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo); // - static bool GetMultiParmInstanceStartIdx(HAPI_AssetInfo& InAssetInfo, const FString InParmName, + static bool GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos); /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp index 90a56b21e..e67b3b415 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp @@ -34,7 +34,7 @@ #include "HoudiniSplineComponent.h" #include "HoudiniEngineUtils.h" #include "HoudiniEngineString.h" - +#include "HoudiniGenericAttribute.h" #include "HoudiniGeoPartObject.h" #include "Components/SplineComponent.h" @@ -1434,6 +1434,9 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( // Fill in the rest of output curve properties OutSplines.Add(CurveIdentifier, NewOutputObject); + + // Update FOundOutputObject so we can cache attributes after + FoundOutputObject = OutSplines.Find(CurveIdentifier); } else { @@ -1567,6 +1570,14 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); } } + + // Update generic properties attributes on the spline component + TArray GenericAttributes; + if (FoundOutputObject && FHoudiniEngineUtils::GetGenericPropertiesAttributes( + InHGPO.GeoId, InHGPO.PartId, true, 0, 0, 0, GenericAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(FoundOutputObject->OutputComponent, GenericAttributes); + } if (bReusedPreviousOutput) { diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp index ee0fcf6c7..4ddad9501 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp @@ -3600,7 +3600,8 @@ FUnrealMeshTranslator::CreateInputNodeForBox( InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); const char * GroupNameStr = ""; { FString LODGroup = TEXT("collision_geo_simple_box") + FString::FromInt(ColliderIndex); @@ -3674,7 +3675,8 @@ FUnrealMeshTranslator::CreateInputNodeForSphere( InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); const char * GroupNameStr = ""; { FString LODGroup = TEXT("collision_geo_simple_sphere") + FString::FromInt(ColliderIndex); @@ -3809,7 +3811,8 @@ FUnrealMeshTranslator::CreateInputNodeForSphyl( InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); // Set its group name param to collision_geo_simple_box - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); const char * GroupNameStr = ""; { FString LODGroup = TEXT("collision_geo_simple_capsule") + FString::FromInt(ColliderIndex); @@ -3929,7 +3932,8 @@ FUnrealMeshTranslator::CreateInputNodeForConvex( InParentNodeID, "groupcreate", GroupNodeName, false, &GroupNodeId), false); // Set its group name param to collision_geo_simple_ucx - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByNameOrTag(GroupNodeId, "groupname"); + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); const char * GroupNameStr = ""; { FString LODGroup = TEXT("collision_geo_simple_ucx") + FString::FromInt(ColliderIndex); diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI.h b/Source/HoudiniEngine/Public/HAPI/HAPI.h index a18cda255..0f10baad9 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI.h @@ -4294,6 +4294,36 @@ HAPI_DECL HAPI_GetPartInfo( const HAPI_Session * session, HAPI_PartId part_id, HAPI_PartInfo * part_info ); + +/// @brief Gets the number of edges that belong to an edge group on a geometry +/// part. +/// +/// @ingroup GeometryGetters +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The SOP node id. +/// +/// @param[in] part_id +/// The part id. +/// +/// @param[in] group_name +/// The name of the edge group. +/// +/// @param[out] edge_count +/// The number of edges that belong to the group. +/// +HAPI_DECL HAPI_GetEdgeCountOfEdgeGroup( const HAPI_Session * session, + HAPI_NodeId node_id, + HAPI_PartId part_id, + const char * group_name, + int * edge_count ); + /// @brief Get the array of faces where the nth integer in the array is /// the number of vertices the nth face has. /// @@ -5454,23 +5484,33 @@ HAPI_DECL HAPI_GetGroupNames( const HAPI_Session * session, /// @param[out] membership_array_all_equal /// (optional) Quick way to determine if all items are in /// the given group or all items our not in the group. -/// You can just pass NULL here if not interested. +/// If you do not need this information or you are getting edge +/// group information, you can just pass NULL for this argument. /// /// @param[out] membership_array /// Array of ints that represent the membership of this -/// group. Should be the size given by -/// ::HAPI_PartInfo_GetElementCountByGroupType() with -/// @p group_type and the ::HAPI_PartInfo of @p part_id. +/// group. When getting membership information for a point or +/// primitive group, the size of the array should be the size +/// given by ::HAPI_PartInfo_GetElementCountByGroupType() with +/// @p group_type and the ::HAPI_PartInfo of @p part_id. When +/// retrieving the edges belonging to an edge group, the +/// membership array will be filled with point numbers that +/// comprise the edges of the edge group. Each edge is specified +/// by two points, which means that the size of the array should +/// be the size given by ::HAPI_GetEdgeCountOfEdgeGroup() * 2. /// /// @param[in] start /// Start offset into the membership array. Must be -/// less than ::HAPI_PartInfo_GetElementCountByGroupType(). +/// less than ::HAPI_PartInfo_GetElementCountByGroupType() when +/// it is a point or primitive group. When getting the +/// membership information for an edge group, this argument must +/// be less than the size returned by +/// ::HAPI_GetEdgeCountOfEdgeGroup() * 2 - 1. /// /// /// @param[in] length /// Should be less than or equal to the size /// of @p membership_array. -/// /// HAPI_DECL HAPI_GetGroupMembership( const HAPI_Session * session, HAPI_NodeId node_id, @@ -6312,7 +6352,10 @@ HAPI_DECL HAPI_DeleteGroup( const HAPI_Session * session, /// /// @param[in] length /// Should be less than or equal to the size -/// of @p membership_array. +/// of @p membership_array. When setting edge group membership, +/// this parameter should be set to the number of points (which +/// are used to implictly define the edges), not to the number +/// edges in the group. /// /// HAPI_DECL HAPI_SetGroupMembership( const HAPI_Session * session, diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h index b0c825272..c02bbb5f8 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h @@ -522,8 +522,9 @@ enum HAPI_NodeFlags /// TOP Node Specific Flags /// All TOP nodes except schedulers - HAPI_NODEFLAGS_TOP_NONSCHEDULER = 1 << 13 + HAPI_NODEFLAGS_TOP_NONSCHEDULER = 1 << 13, + HAPI_NODEFLAGS_NON_BYPASS = 1 << 14 /// Nodes that are not bypassed }; HAPI_C_ENUM_TYPEDEF( HAPI_NodeFlags ) typedef int HAPI_NodeFlagsBits; @@ -533,6 +534,7 @@ enum HAPI_GroupType HAPI_GROUPTYPE_INVALID = -1, HAPI_GROUPTYPE_POINT, HAPI_GROUPTYPE_PRIM, + HAPI_GROUPTYPE_EDGE, HAPI_GROUPTYPE_MAX }; HAPI_C_ENUM_TYPEDEF( HAPI_GroupType ) @@ -1564,6 +1566,7 @@ struct HAPI_API HAPI_GeoInfo /// @{ int pointGroupCount; int primitiveGroupCount; + int edgeGroupCount; /// @} /// Total number of parts this geometry contains. diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h index c829454b0..c172cfd27 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h @@ -27,12 +27,12 @@ // expecting to compile against. #define HAPI_VERSION_HOUDINI_MAJOR 18 #define HAPI_VERSION_HOUDINI_MINOR 5 -#define HAPI_VERSION_HOUDINI_BUILD 532 +#define HAPI_VERSION_HOUDINI_BUILD 563 #define HAPI_VERSION_HOUDINI_PATCH 0 // The two components of the Houdini Engine (marketed) version. #define HAPI_VERSION_HOUDINI_ENGINE_MAJOR 3 -#define HAPI_VERSION_HOUDINI_ENGINE_MINOR 5 +#define HAPI_VERSION_HOUDINI_ENGINE_MINOR 6 // This is a monotonously increasing API version number that can be used // to lock against a certain API for compatibility purposes. Basically, @@ -40,6 +40,6 @@ // might no longer compile. Semantic changes to the methods will also // cause this version to increase. This number will be reset to 0 // every time the Houdini Engine version is bumped. -#define HAPI_VERSION_HOUDINI_ENGINE_API 2 +#define HAPI_VERSION_HOUDINI_ENGINE_API 1 #endif // __HAPI_VERSION_h__ diff --git a/Source/HoudiniEngine/Public/HoudiniApi.h b/Source/HoudiniEngine/Public/HoudiniApi.h index 00b066cd6..fe1972dbf 100644 --- a/Source/HoudiniEngine/Public/HoudiniApi.h +++ b/Source/HoudiniEngine/Public/HoudiniApi.h @@ -99,13 +99,19 @@ struct HOUDINIENGINE_API FHoudiniApi typedef HAPI_Result (*GetAttributeFloatArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*GetAttributeInt16ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); typedef HAPI_Result (*GetAttributeInt64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); typedef HAPI_Result (*GetAttributeIntArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); typedef HAPI_Result (*GetAttributeStringArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeUInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); @@ -273,7 +279,7 @@ struct HOUDINIENGINE_API FHoudiniApi typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const int string_handle); + typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const HAPI_StringHandle string_handle); typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); @@ -291,15 +297,18 @@ struct HOUDINIENGINE_API FHoudiniApi typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + typedef HAPI_Result (*SetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, int * handle_value); + typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); @@ -412,13 +421,19 @@ struct HOUDINIENGINE_API FHoudiniApi static GetAttributeFloatArrayDataFuncPtr GetAttributeFloatArrayData; static GetAttributeFloatDataFuncPtr GetAttributeFloatData; static GetAttributeInfoFuncPtr GetAttributeInfo; + static GetAttributeInt16ArrayDataFuncPtr GetAttributeInt16ArrayData; + static GetAttributeInt16DataFuncPtr GetAttributeInt16Data; static GetAttributeInt64ArrayDataFuncPtr GetAttributeInt64ArrayData; static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; + static GetAttributeInt8ArrayDataFuncPtr GetAttributeInt8ArrayData; + static GetAttributeInt8DataFuncPtr GetAttributeInt8Data; static GetAttributeIntArrayDataFuncPtr GetAttributeIntArrayData; static GetAttributeIntDataFuncPtr GetAttributeIntData; static GetAttributeNamesFuncPtr GetAttributeNames; static GetAttributeStringArrayDataFuncPtr GetAttributeStringArrayData; static GetAttributeStringDataFuncPtr GetAttributeStringData; + static GetAttributeUInt8ArrayDataFuncPtr GetAttributeUInt8ArrayData; + static GetAttributeUInt8DataFuncPtr GetAttributeUInt8Data; static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; static GetAvailableAssetsFuncPtr GetAvailableAssets; static GetBoxInfoFuncPtr GetBoxInfo; @@ -604,9 +619,12 @@ struct HOUDINIENGINE_API FHoudiniApi static SetAnimCurveFuncPtr SetAnimCurve; static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; static SetAttributeFloatDataFuncPtr SetAttributeFloatData; + static SetAttributeInt16DataFuncPtr SetAttributeInt16Data; static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; + static SetAttributeInt8DataFuncPtr SetAttributeInt8Data; static SetAttributeIntDataFuncPtr SetAttributeIntData; static SetAttributeStringDataFuncPtr SetAttributeStringData; + static SetAttributeUInt8DataFuncPtr SetAttributeUInt8Data; static SetCachePropertyFuncPtr SetCacheProperty; static SetCurveCountsFuncPtr SetCurveCounts; static SetCurveInfoFuncPtr SetCurveInfo; @@ -725,13 +743,19 @@ struct HOUDINIENGINE_API FHoudiniApi static HAPI_Result GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + static HAPI_Result GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); static HAPI_Result GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + static HAPI_Result GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); static HAPI_Result GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); static HAPI_Result GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + static HAPI_Result GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); @@ -899,7 +923,7 @@ struct HOUDINIENGINE_API FHoudiniApi static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const int string_handle); + static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle); static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); @@ -917,15 +941,18 @@ struct HOUDINIENGINE_API FHoudiniApi static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + static HAPI_Result SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + static HAPI_Result SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + static HAPI_Result SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, int * handle_value); + static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp index 6a752e8ca..269ff3fc0 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp @@ -46,6 +46,7 @@ #include "HoudiniPDGAssetLink.h" #include "HoudiniStringResolver.h" #include "HoudiniEngineCommands.h" +#include "HoudiniEngineRuntimeUtils.h" #include "Engine/StaticMesh.h" #include "Engine/World.h" @@ -92,6 +93,7 @@ #include "Sound/SoundBase.h" #include "UObject/UnrealType.h" #include "Math/Box.h" +#include "Misc/ScopedSlowTask.h" HOUDINI_BAKING_DEFINE_LOG_CATEGORY(); @@ -106,6 +108,7 @@ FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() , SourceObject(nullptr) , BakeFolderPath() , bInstancerOutput(false) + , bPostBakeProcessPostponed(false) { } @@ -131,6 +134,7 @@ FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( , BakeFolderPath(InBakeFolderPath) , BakedObjectPackageParams(InBakedObjectPackageParams) , bInstancerOutput(false) + , bPostBakeProcessPostponed(false) { } @@ -460,6 +464,25 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( } } + // Only do the post bake post-process once per Actor + TSet UniqueActors; + for (FHoudiniEngineBakedActor& BakedActor : BakedActors) + { + if (BakedActor.bPostBakeProcessPostponed && BakedActor.Actor) + { + BakedActor.bPostBakeProcessPostponed = false; + AActor* Actor = BakedActor.Actor; + bool bIsAlreadyInSet = false; + UniqueActors.Add(Actor, &bIsAlreadyInSet); + if (!bIsAlreadyInSet) + { + Actor->InvalidateLightingCache(); + Actor->PostEditMove(true); + Actor->MarkPackageDirty(); + } + } + } + OutNewActors.Append(BakedActors); return true; @@ -1162,8 +1185,6 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( } const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName.ToString(), FoundActor); - // FoundActor->Rename(*NewName.ToString()); - // FoundActor->SetActorLabel(NewName.ToString()); RenameAndRelabelActor(FoundActor, NewNameStr, false); // The folder is named after the original actor and contains all generated actors @@ -1209,7 +1230,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( return false; bSpawnedActor = true; - FoundActor->SetActorLabel(FoundActor->GetName()); + FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); } else @@ -1304,9 +1325,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( OutputEntry.bInstancerOutput = true; OutputEntry.InstancerPackageParams = InstancerPackageParams; - FoundActor->InvalidateLightingCache(); - FoundActor->PostEditMove(true); - FoundActor->MarkPackageDirty(); + // Postpone post-bake calls to do them once per actor + OutActors.Last().bPostBakeProcessPostponed = true; } // If we are baking in replace mode, remove previously baked components/instancers @@ -1492,8 +1512,6 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( } const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName.ToString(), FoundActor); - // FoundActor->Rename(*NewName.ToString()); - // FoundActor->SetActorLabel(NewName.ToString()); RenameAndRelabelActor(FoundActor, NewNameStr, false); // The folder is named after the original actor and contains all generated actors @@ -1662,7 +1680,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); - NewActor->SetActorLabel(NewNameStr); + FHoudiniEngineRuntimeUtils::SetActorLabel(NewActor, NewNameStr); + SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); NewActor->SetActorTransform(CurrentTransform); @@ -1839,7 +1858,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( return false; bSpawnedActor = true; - FoundActor->SetActorLabel(FoundActor->GetName()); + FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); + FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); } else @@ -1920,9 +1940,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( OutputEntry.bInstancerOutput = true; OutputEntry.InstancerPackageParams = InstancerPackageParams; - FoundActor->InvalidateLightingCache(); - FoundActor->PostEditMove(true); - FoundActor->MarkPackageDirty(); + // Postpone these calls to do them once per actor + OutActors.Last().bPostBakeProcessPostponed = true; // If we are baking in replace mode, remove previously baked components/instancers if (bInReplaceActors && bInReplaceAssets) @@ -2202,8 +2221,6 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( // We need to make a unique name for the actor, renaming an object on top of another is a fatal error const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName.ToString(), FoundActor); - // FoundActor->Rename(*NewNameStr); - // FoundActor->SetActorLabel(NewNameStr); RenameAndRelabelActor(FoundActor, NewNameStr, false); SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath); @@ -3229,8 +3246,6 @@ FHoudiniEngineBakeUtils::BakeCurve( // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset const FName BaseActorName(PackageParams.ObjectName); const FString NewNameStr = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName.ToString(), OutActor); - // OutActor->Rename(*NewNameStr); - // OutActor->SetActorLabel(NewNameStr); RenameAndRelabelActor(OutActor, NewNameStr, false); OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); @@ -3416,8 +3431,6 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); - // NewActor->Rename(*NewNameStr); - // NewActor->SetActorLabel(NewNameStr); RenameAndRelabelActor(NewActor, NewNameStr, false); NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName)); @@ -4127,8 +4140,8 @@ FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, else NewName = InNewName; - InAsset->Rename(*NewName); - + FHoudiniEngineUtils::RenameObject(InAsset, *NewName); + const FSoftObjectPath NewPath = FSoftObjectPath(InAsset); if (OldPath != NewPath) { @@ -4154,8 +4167,8 @@ FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& I else NewName = InNewName; - InActor->Rename(*NewName); - InActor->SetActorLabel(NewName); + FHoudiniEngineUtils::RenameObject(InActor, *NewName); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName); const FSoftObjectPath NewPath = FSoftObjectPath(InActor); if (OldPath != NewPath) @@ -4187,8 +4200,6 @@ FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor( // Detach from parent InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); // Rename - // InActor->Rename(*MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), FName(InNewName)).ToString()); - // InActor->SetActorLabel(InNewName); const bool bMakeUniqueIfNotUnique = true; RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique); @@ -4465,12 +4476,16 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( } const int32 NumWorkResults = InNode->WorkResult.Num(); + FScopedSlowTask Progress(NumWorkResults, FText::FromString(FString::Printf(TEXT("Baking PDG Node Output %s ..."), *InNode->GetName()))); + Progress.MakeDialog(); for (int32 WorkResultArrayIdx = 0; WorkResultArrayIdx < NumWorkResults; ++WorkResultArrayIdx) { FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultArrayIdx]; const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); for (int32 WorkResultObjectArrayIdx = 0; WorkResultObjectArrayIdx < NumWorkResultObjects; ++WorkResultObjectArrayIdx) { + Progress.EnterProgressFrame(1.0f); + BakePDGWorkResultObject( InPDGAssetLink, InNode, diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h index bbef6b40e..d51cb36d6 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h @@ -135,7 +135,10 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor // This would mostly be useful in situations for we later need the resolver and/or cached attributes and // tokens, such as for blueprint baking. FHoudiniPackageParams InstancerPackageParams; - + + // Used to delay all post bake calls so they are done only once per baked actor + bool bPostBakeProcessPostponed = false; + }; struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h index e19fe9a76..bd6b0b568 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h @@ -58,7 +58,7 @@ class SHoudiniAssetLogWidget : public SCompoundWidget void Construct(const FArguments & InArgs); }; -class FHoudiniEngineDetails : public TSharedFromThis +class FHoudiniEngineDetails : public TSharedFromThis { public: static void CreateWidget( diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp index 9a3fc34d6..49f96aa4c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp @@ -1330,7 +1330,7 @@ FHoudiniEngineEditor::UnregisterEditorDelegates() FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldEditorDelegateHandle); if (PreBeginPIEEditorDelegateHandle.IsValid()) - FEditorDelegates::PreSaveWorld.Remove(PreBeginPIEEditorDelegateHandle); + FEditorDelegates::PreBeginPIE.Remove(PreBeginPIEEditorDelegateHandle); if (OnDeleteActorsBegin.IsValid()) FEditorDelegates::OnDeleteActorsBegin.Remove(OnDeleteActorsBegin); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp index 629f4f89d..78d89d155 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp @@ -631,7 +631,10 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve continue; if (CurInputObj->GetImportAsReference() != bNewState) + { + CurInputObj->SetImportAsReference(bNewState); CurInputObj->MarkChanged(true); + } } } @@ -1071,15 +1074,17 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( ] ]; + TWeakPtr WeakStaticMeshThumbnailBorder(StaticMeshThumbnailBorder); StaticMeshThumbnailBorder->SetBorderImage(TAttribute::Create( - TAttribute::FGetter::CreateLambda([StaticMeshThumbnailBorder]() + TAttribute::FGetter::CreateLambda([WeakStaticMeshThumbnailBorder]() { - if (StaticMeshThumbnailBorder.IsValid() && StaticMeshThumbnailBorder->IsHovered()) + TSharedPtr ThumbnailBorder = WeakStaticMeshThumbnailBorder.Pin(); + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); else return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); } - ) ) ); + ))); FText MeshNameText = FText::GetEmpty(); if (InputObject) @@ -1116,30 +1121,36 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( ]; + TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( - [MainInput, InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt]() - { - TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); - UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); - - TArray< UFactory * > NewAssetFactories; - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(DefaultObj), - true, - AllowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([InInputs, InGeometryObjectIdx, StaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) + [MainInput, InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt]() { - if (StaticMeshComboButton.IsValid()) - { - StaticMeshComboButton->SetIsOpen(false); - UObject * Object = AssetData.GetAsset(); - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); - } - }), - FSimpleDelegate::CreateLambda([]() {})); - })); + TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); + UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); + + TArray< UFactory * > NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(DefaultObj), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( + [InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) + { + TSharedPtr ComboButton = WeakStaticMeshComboButton.Pin(); + if (ComboButton.IsValid()) + { + ComboButton->SetIsOpen(false); + UObject * Object = AssetData.GetAsset(); + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); + } + } + ), + FSimpleDelegate::CreateLambda([]() {}) + ); + } + )); // Add buttons @@ -1364,21 +1375,25 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( ] ]; + TWeakPtr WeakExpanderArrow(ExpanderArrow); // Set delegate for image ExpanderImage->SetImage( TAttribute::Create( - TAttribute::FGetter::CreateLambda( [=]() { - FName ResourceName; - if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx) ) + TAttribute::FGetter::CreateLambda([InGeometryObjectIdx, MainInput, WeakExpanderArrow]() { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + FName ResourceName; + TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); + if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + return FEditorStyle::GetBrush(ResourceName); } - return FEditorStyle::GetBrush( ResourceName ); - } ) ) ); + ))); } // Lambda for changing the transform values diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h index 831dbe331..b6be86b23 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h @@ -42,7 +42,7 @@ class IDetailsView; class FReply; class FAssetThumbnailPool; -class FHoudiniInputDetails : public TSharedFromThis +class FHoudiniInputDetails : public TSharedFromThis { public: static void CreateWidget( diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp index 02895419e..ea1fdae46 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp @@ -86,16 +86,15 @@ FHoudiniOutputDetails::CreateWidget( return; UHoudiniOutput* MainOutput = InOutputs[0]; + if (!IsValid(MainOutput)) + return; // Don't create UI for editable curve. - if (!MainOutput || MainOutput->IsPendingKill() || MainOutput->IsEditableNode()) + if (MainOutput->IsEditableNode() && MainOutput->GetType() == EHoudiniOutputType::Curve) return; // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); - - // TODO - // For now we just handle Mesh Outputs + TSharedPtr AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); switch (MainOutput->GetType()) { @@ -128,9 +127,7 @@ FHoudiniOutputDetails::CreateWidget( FHoudiniOutputDetails::CreateDefaultOutputWidget(HouOutputCategory, MainOutput); break; } - } - } @@ -545,7 +542,6 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); } } - } void @@ -2570,15 +2566,16 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( // Add an asset drop target PickerVerticalBox->AddSlot() - .Padding( 0, 2 ) + .Padding(0, 2) .AutoHeight() [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop( SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( - [=]( const UObject* Obj ) { - for ( auto Klass : DisallowedClasses ) + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop(SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( + [DisallowedClasses](const UObject* Obj) + { + for (auto Klass : DisallowedClasses) { - if ( Obj && Obj->IsA( Klass ) ) + if (Obj && Obj->IsA(Klass)) return false; } return true; @@ -2589,7 +2586,7 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( return SetObjectAt(CurInstanceOutput, VariationIdx, InObject); }) [ - SAssignNew( PickerHorizontalBox, SHorizontalBox ) + SAssignNew(PickerHorizontalBox, SHorizontalBox) ] ]; @@ -2609,15 +2606,17 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( ] ]; + TWeakPtr WeakVariationThumbnailBorder(VariationThumbnailBorder); VariationThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush *>::Create( - TAttribute::FGetter::CreateLambda([=]() + TAttribute::FGetter::CreateLambda([WeakVariationThumbnailBorder]() { - if (VariationThumbnailBorder.IsValid() && VariationThumbnailBorder->IsHovered()) + TSharedPtr ThumbnailBorder = WeakVariationThumbnailBorder.Pin(); + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); else return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); } - ) ) ); + ))); PickerHorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 28.0f, 0.0f, 28.0f) [ @@ -2680,6 +2679,7 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( // Create asset picker for this combo button. { + TWeakPtr WeakAssetComboButton(AssetComboButton); TArray NewAssetFactories; TSharedRef PropertyMenuAssetPicker = PropertyCustomizationHelpers::MakeAssetPickerWithMenu( FAssetData(InstancedObject), @@ -2688,15 +2688,18 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( DisallowedClasses, NewAssetFactories, FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([&CurInstanceOutput, VariationIdx, SetObjectAt, AssetComboButton](const FAssetData& AssetData) - { - if ( AssetComboButton.IsValid() ) + FOnAssetSelected::CreateLambda( + [&CurInstanceOutput, VariationIdx, SetObjectAt, WeakAssetComboButton](const FAssetData& AssetData) { - AssetComboButton->SetIsOpen( false ); - UObject * Object = AssetData.GetAsset(); - SetObjectAt( CurInstanceOutput, VariationIdx, Object); + TSharedPtr AssetComboButtonPtr = WeakAssetComboButton.Pin(); + if (AssetComboButtonPtr.IsValid()) + { + AssetComboButtonPtr->SetIsOpen(false); + UObject * Object = AssetData.GetAsset(); + SetObjectAt(CurInstanceOutput, VariationIdx, Object); + } } - }), + ), // Nothing to do on close FSimpleDelegate::CreateLambda([](){}) ); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h index eb5953860..00a8b3629 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h @@ -55,7 +55,7 @@ struct FTextAndTooltip int32 Value; }; -class FHoudiniPDGDetails : public TSharedFromThis +class FHoudiniPDGDetails : public TSharedFromThis { public: diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp index a28feebb3..b63e4d8d1 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp @@ -2610,11 +2610,11 @@ FHoudiniParameterDetails::CreateWidgetFloat( // Ignore the swapping if that parameter has the noswap tag bool SwapVector3 = !MainParam->GetNoSwap(); - auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float & Val) + auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float& Val, const bool& bDoChange) { - ChangeFloatValueAt(Val, 0, true, FloatParams); - ChangeFloatValueAt(Val, 1, true, FloatParams); - ChangeFloatValueAt(Val, 2, true, FloatParams); + ChangeFloatValueAt(Val, 0, bDoChange, FloatParams); + ChangeFloatValueAt(Val, 1, bDoChange, FloatParams); + ChangeFloatValueAt(Val, 2, bDoChange, FloatParams); }; VerticalBox->AddSlot().Padding(2, 2, 5, 2) @@ -2625,30 +2625,54 @@ FHoudiniParameterDetails::CreateWidgetFloat( [ SNew(SVectorInputBox) .bColorAxisLabels(true) + .AllowSpin(true) .X(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, 0))) .Y(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 2 : 1))) .Z(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 1 : 2))) .OnXCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) { if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); + ChangeFloatValueUniformly(Val, true); else ChangeFloatValueAt( Val, 0, true, FloatParams); }) .OnYCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) { if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); + ChangeFloatValueUniformly(Val, true); else ChangeFloatValueAt( Val, SwapVector3 ? 2 : 1, true, FloatParams); }) .OnZCommitted_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) { if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val); + ChangeFloatValueUniformly(Val, true); else ChangeFloatValueAt( Val, SwapVector3 ? 1 : 2, true, FloatParams); }) + .OnXChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, 0, false, FloatParams); + }) + .OnYChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, SwapVector3 ? 2 : 1, false, FloatParams); + }) + .OnZChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, SwapVector3 ? 1 : 2, false, FloatParams); + }) + .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) + .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) .TypeInterface(paramTypeInterface) ] + SHorizontalBox::Slot() @@ -2738,10 +2762,10 @@ FHoudiniParameterDetails::CreateWidgetFloat( .MaxSliderValue(MainParam->GetUIMax()) .Value(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, Idx))) - .OnValueChanged_Lambda([=](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) - .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) - .OnBeginSliderMovement_Lambda([=]() { SliderBegin(FloatParams); }) - .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(FloatParams); }) + .OnValueChanged_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) + .OnValueCommitted_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) + .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) + .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) .SliderExponent(MainParam->IsLogarithmic() ?8.0f : 1.0f) .TypeInterface(paramTypeInterface) ] @@ -3123,15 +3147,19 @@ FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParame ] ]; - ThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush * >::Create( - TAttribute< const FSlateBrush * >::FGetter::CreateLambda([ThumbnailBorder]() - { - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ))); + TWeakPtr WeakThumbnailBorder(ThumbnailBorder); + ThumbnailBorder->SetBorderImage(TAttribute::Create( + TAttribute::FGetter::CreateLambda( + [WeakThumbnailBorder]() + { + TSharedPtr ThumbnailBorderPtr = WeakThumbnailBorder.Pin(); + if (ThumbnailBorderPtr.IsValid() && ThumbnailBorderPtr->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ) + )); FText MeshNameText = FText::GetEmpty(); //if (InputObject) @@ -3167,8 +3195,9 @@ FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParame ] ]; + TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( - [UnrealRefClass, StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() + [UnrealRefClass, WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() { TArray AllowedClasses; if (UnrealRefClass != UObject::StaticClass()) @@ -3200,20 +3229,23 @@ FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParame AllowedClasses, NewAssetFactories, FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda([StaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) - { - if (StaticMeshComboButton.IsValid()) + FOnAssetSelected::CreateLambda( + [WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) { - StaticMeshComboButton->SetIsOpen(false); + TSharedPtr StaticMeshComboButtonPtr = WeakStaticMeshComboButton.Pin(); + if (StaticMeshComboButtonPtr.IsValid()) + { + StaticMeshComboButtonPtr->SetIsOpen(false); - UObject * Object = AssetData.GetAsset(); - // Get the asset reference string for this object - // !! Accept null objects to allow clearing the asset picker !! - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); + UObject * Object = AssetData.GetAsset(); + // Get the asset reference string for this object + // !! Accept null objects to allow clearing the asset picker !! + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); - ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); + ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); + } } - }), + ), FSimpleDelegate::CreateLambda([]() {})); }) ); @@ -3376,19 +3408,23 @@ FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParamete SAssignNew(ColorBlock, SColorBlock) .Color(MainParam->GetColorValue()) .ShowBackgroundForAlpha(bHasAlpha) - .OnMouseButtonDown(FPointerEventHandler::CreateLambda( - [MainParam, ColorParams, ColorBlock, bHasAlpha](const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) + ]; + + TWeakPtr WeakColorBlock(ColorBlock); + ColorBlock->SetOnMouseButtonDown(FPointerEventHandler::CreateLambda( + [MainParam, ColorParams, WeakColorBlock, bHasAlpha](const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) { if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) return FReply::Unhandled(); + TSharedPtr ColorBlockPtr = WeakColorBlock.Pin(); FColorPickerArgs PickerArgs; - PickerArgs.ParentWidget = ColorBlock; + PickerArgs.ParentWidget = ColorBlockPtr.IsValid() ? ColorBlockPtr : nullptr; PickerArgs.bUseAlpha = bHasAlpha; PickerArgs.DisplayGamma = TAttribute< float >::Create( TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) { - + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) + { FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), @@ -3413,14 +3449,13 @@ FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParamete { Transaction.Cancel(); } - }); PickerArgs.InitialColorOverride = MainParam->GetColorValue(); PickerArgs.bOnlyRefreshOnOk = true; OpenColorPicker(PickerArgs); return FReply::Handled(); - })) - ]; + } + )); Row->ValueWidget.Widget = VerticalBox; Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); @@ -5854,22 +5889,27 @@ FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArr .Text(LabelText) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ]; - + + TWeakPtr WeakExpanderArrow(ExpanderArrow); ExpanderImage->SetImage( TAttribute::Create( - TAttribute::FGetter::CreateLambda([=]() { - FName ResourceName; - if(MainParam->IsExpanded()) - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } + TAttribute::FGetter::CreateLambda([MainParam, WeakExpanderArrow]() + { + FName ResourceName; + TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); + if (MainParam->IsExpanded()) + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } - return FEditorStyle::GetBrush(ResourceName); - }))); + return FEditorStyle::GetBrush(ResourceName); + } + ) + )); if(MainParam->GetFolderType() == EHoudiniFolderParameterType::Simple) ExpanderArrow->SetEnabled(false); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h index 2c21be90d..73cf64d3f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h @@ -300,7 +300,7 @@ class UHoudiniColorRampCurve : public UCurveLinearColor //class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface -class FHoudiniParameterDetails : public TSharedFromThis +class FHoudiniParameterDetails : public TSharedFromThis { public: void CreateWidget( diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp index c00daee6a..cf7257fee 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp @@ -94,6 +94,7 @@ AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) } #endif */ + #if WITH_EDITOR bool AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) const diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp index fd4871cd4..1b7059f4d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp @@ -1446,7 +1446,7 @@ UHoudiniAssetComponent::PostLoad() // If we have deserialized legacy v1 data, attempt to convert it now ConvertLegacyData(); - if(bAutomaticLegacyHDARebuild) + if (bAutomaticLegacyHDARebuild) MarkAsNeedRebuild(); else MarkAsNeedInstantiation(); @@ -1465,8 +1465,15 @@ UHoudiniAssetComponent::PostLoad() // Register our PDG Asset link if we have any - // From v1: + // !!! Do not update rendering while loading, do it when setting up the render state + // UpdateRenderingInformation(); +} + +void +UHoudiniAssetComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context) +{ UpdateRenderingInformation(); + Super::CreateRenderState_Concurrent(Context); } void @@ -2805,8 +2812,8 @@ UHoudiniAssetComponent::UpdateRenderingInformation() SceneComponent->RecreatePhysicsState(); } - // Since we have new asset, we need to update bounds. - UpdateBounds(); + // !!! Do not call UpdateBounds() here as this could cause + // a loading loop in post load on game builds! } diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h index 69668f86a..f64504a82 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h @@ -158,6 +158,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // Declare the delegate that is broadcast when RefineMeshesTimer fires DECLARE_MULTICAST_DELEGATE_OneParam(FOnRefineMeshesTimerDelegate, UHoudiniAssetComponent*); DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPostCookBakeDelegate, UHoudiniAssetComponent*); + DECLARE_DELEGATE_TwoParams(FOnPostCookDelegate, UHoudiniAssetComponent*, bool); virtual ~UHoudiniAssetComponent(); @@ -272,6 +273,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // Returns true if the asset should be bake after the next cook bool IsBakeAfterNextCookEnabled() const { return bBakeAfterNextCook; } + FOnPostCookDelegate& GetOnPostCookDelegate() { return OnPostCookDelegate; } FOnPostCookBakeDelegate& GetOnPostCookBakeDelegate() { return OnPostCookBakeDelegate; } // Derived blueprint based components will check whether the template @@ -465,6 +467,9 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone virtual void BeginDestroy() override; + // + virtual void CreateRenderState_Concurrent(FRegisterComponentContext* Context) override; + // Do any object - specific cleanup required immediately after loading an object. // This is not called for newly - created objects, and by default will always execute on the game thread. virtual void PostLoad() override; @@ -726,6 +731,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone UPROPERTY(DuplicateTransient) bool bBakeAfterNextCook; + // Delegate to broadcast after a post cook event + // Arguments are (HoudiniAssetComponent* HAC, bool IsSuccessful) + FOnPostCookDelegate OnPostCookDelegate; + // Delegate to broadcast when baking after a cook. // Currently we cannot call the bake functions from here (Runtime module) // or from the HoudiniEngineManager (HoudiniEngine) module, so we use diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp index 313185d36..544b13b96 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp @@ -484,6 +484,20 @@ FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTe #if WITH_EDITOR + +// Centralized call to set actor label (changing Actor's implementation was too risky) +bool FHoudiniEngineRuntimeUtils::SetActorLabel(AActor* Actor, const FString& ActorLabel) +{ + // Clean up the incoming string a bit + FString NewActorLabel = ActorLabel.TrimStartAndEnd(); + if (NewActorLabel == Actor->GetActorLabel()) + { + return false; + } + Actor->SetActorLabel(NewActorLabel); + return true; +} + void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h index ba13761a0..b2569d2e3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h @@ -168,10 +168,16 @@ struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils // Taken from here: https://answers.unrealengine.com/questions/330496/conversion-of-enum-to-string.html // Return the string representation of an enum value. template - static FString EnumToString(const FString& enumName, const T value) + static FString EnumToString(const FString& EnumName, const T Value) { - UEnum* pEnum = FindObject((UObject*)ANY_PACKAGE, *enumName); - return *(pEnum ? pEnum->GetNameStringByIndex(static_cast(value)) : "null"); + UEnum* Enum = FindObject(ANY_PACKAGE, *EnumName); + return *(Enum ? Enum->GetNameStringByValue(static_cast(Value)) : "null"); + } + + template + static FString EnumToString(const T Value) + { + return UEnum::GetValueAsString(Value); } // ------------------------------------------------- @@ -194,6 +200,8 @@ struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils // Editor Helpers // ------------------------------------------------- #if WITH_EDITOR + static bool SetActorLabel(AActor* Actor, const FString& ActorLabel); + static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); static void DoPostEditChangeProperty(UObject* Obj, FProperty* Property); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp index 4a0e42e70..af591214d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp @@ -27,6 +27,7 @@ #include "HoudiniGenericAttribute.h" #include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" #include "Engine/StaticMesh.h" #include "Components/ActorComponent.h" @@ -183,6 +184,138 @@ FHoudiniGenericAttribute::GetData() return nullptr; } +void +FHoudiniGenericAttribute::SetDoubleValue(double InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = FString::SanitizeFloat(InValue); + } +} + +void +FHoudiniGenericAttribute::SetDoubleTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetDoubleValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetIntValue(int64 InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = FString::Printf(TEXT("%lld"), InValue); + } +} + +void +FHoudiniGenericAttribute::SetIntTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetIntValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetStringValue(const FString& InValue, int32 index) +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = FCString::Atoi64(*InValue); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = FCString::Atod(*InValue); + } +} + +void +FHoudiniGenericAttribute::SetStringTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetStringValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetBoolValue(bool InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue ? 1.0 : 0.0; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue ? 1 : 0; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = InValue ? "true" : "false"; + } +} + +void +FHoudiniGenericAttribute::SetBoolTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetBoolValue(InTupleValues[n], index * AttributeTupleSize + n); +} + bool FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex) @@ -199,12 +332,62 @@ FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( if (PropertyName == "CollisionProfileName") { UPrimitiveComponent* PC = Cast(InObject); - if (PC && !PC->IsPendingKill()) + if (IsValid(PC)) { FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); FName Value = FName(*StringValue); PC->SetCollisionProfileName(Value); + // Patch the StaticMeshGenerationProperties on the HAC + UHoudiniAssetComponent* HAC = Cast(InObject); + if (IsValid(HAC)) + { + HAC->StaticMeshGenerationProperties.DefaultBodyInstance.SetCollisionProfileName(Value); + } + + return true; + } + return false; + } + + if (PropertyName == "CollisionEnabled") + { + UPrimitiveComponent* PC = Cast(InObject); + if (PC && !PC->IsPendingKill()) + { + FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); + if (StringValue == "NoCollision") + { + PC->SetCollisionEnabled(ECollisionEnabled::NoCollision); + return true; + } + else if (StringValue == "QueryOnly") + { + PC->SetCollisionEnabled(ECollisionEnabled::QueryOnly); + return true; + } + else if (StringValue == "PhysicsOnly") + { + PC->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); + return true; + } + else if (StringValue == "QueryAndPhysics") + { + PC->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + return true; + } + return false; + } + } + + // Specialize CastShadow to avoid paying the cost of finding property + calling Property change twice + if (PropertyName == "CastShadow") + { + UPrimitiveComponent* Component = Cast< UPrimitiveComponent >(InObject); + if (Component && !Component->IsPendingKill()) + { + bool Value = InPropertyAttribute.GetBoolValue(AtIndex); + Component->SetCastShadow(Value); return true; } return false; @@ -218,7 +401,7 @@ FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( { FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); if (!AC->ComponentTags.Contains(NameAttr)) - AC->ComponentTags.Add(NameAttr); + AC->ComponentTags.Add(NameAttr); /* for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++) { @@ -833,6 +1016,394 @@ FHoudiniGenericAttribute::ModifyPropertyValueOnObject( return true; } +bool +FHoudiniGenericAttribute::GetPropertyValueFromObject( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + FHoudiniGenericAttribute& InGenericAttribute, + const int32& InAtIndex) +{ + if (!InObject || InObject->IsPendingKill() || !InFoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Property class name, used for logging + const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); + + // Initialize using the found property + FProperty* InnerProperty = InFoundProperty; + + FArrayProperty* ArrayProperty = CastField(InFoundProperty); + TSharedPtr ArrayHelper; + if (ArrayProperty) + { + InnerProperty = ArrayProperty->Inner; + ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); + } + + // TODO: implement support for array attributes received from Houdini + + // Get the "proper" AtIndex in the flat array by using the attribute tuple size + // TODO: fix the issue when changing array of tuple properties! + const int32 TupleSize = InGenericAttribute.AttributeTupleSize; + int32 AtIndex = InAtIndex * TupleSize; + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + // Supported non-struct properties + + // If the attribute from Houdini has a tuple size > 1, we support getting it on arrays on the unreal side + // For example: a 3 float in Houdini can be set from a TArray in Unreal. + + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + { + // Check that we are not out of range + if (TupleIndex >= ArrayHelper->Num()) + break; + + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + } + else + { + // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim + // on the property to determine if our TupleIndex is in range, if not, give up, we cannot get any more + // of our tuple indices from this property. + if (TupleIndex >= InnerProperty->ArrayDim) + break; + + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (ValuePtr) + { + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) + { + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + InGenericAttribute.SetStringValue(Property->GetNumericPropertyValueToString(ValuePtr), AtIndex + TupleIndex); + } + else if (Property->IsFloatingPoint()) + { + InGenericAttribute.SetDoubleValue(Property->GetFloatingPointPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else if (Property->IsInteger()) + { + InGenericAttribute.SetIntValue(Property->GetSignedIntPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) + { + FBoolProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetBoolValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) + { + FStrProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + FNameProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr).ToString(), AtIndex + TupleIndex); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); + if (TupleIndex == 0) + return false; + } + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties + + // Set as many as the tuple values as we can from the Unreal struct. + + const int32 TupleIndex = 0; + + void* PropertyValue = nullptr; + if (ArrayHelper.IsValid()) + { + if (ArrayHelper->IsValidIndex(TupleIndex)) + PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); + } + else if (TupleIndex < InnerProperty->ArrayDim) + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (PropertyValue) + { + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + // Found a vector property, fill it with up to 3 tuple values + const FVector& Vector = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(Vector.X, AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Vector.Y, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(Vector.Z, AtIndex + TupleIndex + 2); + } + else if (PropertyName == NAME_Transform) + { + // Found a transform property fill it with the attribute tuple values + const FTransform& Transform = *static_cast(PropertyValue); + const FVector Translation = Transform.GetTranslation(); + const FQuat Rotation = Transform.GetRotation(); + const FVector Scale = Transform.GetScale3D(); + + InGenericAttribute.SetDoubleValue(Translation.X, AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Translation.Y, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(Translation.Z, AtIndex + TupleIndex + 2); + + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetDoubleValue(Rotation.W, AtIndex + TupleIndex + 3); + if (InGenericAttribute.AttributeTupleSize > 4) + InGenericAttribute.SetDoubleValue(Rotation.X, AtIndex + TupleIndex + 4); + if (InGenericAttribute.AttributeTupleSize > 5) + InGenericAttribute.SetDoubleValue(Rotation.Y, AtIndex + TupleIndex + 5); + if (InGenericAttribute.AttributeTupleSize > 6) + InGenericAttribute.SetDoubleValue(Rotation.Z, AtIndex + TupleIndex + 6); + + if (InGenericAttribute.AttributeTupleSize > 7) + InGenericAttribute.SetDoubleValue(Scale.X, AtIndex + TupleIndex + 7); + if (InGenericAttribute.AttributeTupleSize > 8) + InGenericAttribute.SetDoubleValue(Scale.Y, AtIndex + TupleIndex + 8); + if (InGenericAttribute.AttributeTupleSize > 9) + InGenericAttribute.SetDoubleValue(Scale.Z, AtIndex + TupleIndex + 9); + } + else if (PropertyName == NAME_Color) + { + const FColor& Color = *static_cast(PropertyValue); + InGenericAttribute.SetIntValue(Color.R, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetIntValue(Color.G, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetIntValue(Color.B, AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetIntValue(Color.A, AtIndex + TupleIndex + 3); + } + else if (PropertyName == NAME_LinearColor) + { + const FLinearColor& LinearColor = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(LinearColor.R, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(LinearColor.G, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(LinearColor.B, AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetDoubleValue(LinearColor.A, AtIndex + TupleIndex + 3); + } + else if (PropertyName == "Int32Interval") + { + const FInt32Interval& Interval = *static_cast(PropertyValue); + InGenericAttribute.SetIntValue(Interval.Min, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetIntValue(Interval.Max, AtIndex + TupleIndex + 1); + } + else if (PropertyName == "FloatInterval") + { + const FFloatInterval& Interval = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(Interval.Min, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Interval.Max, AtIndex + TupleIndex + 1); + } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + // OBJECT PATH PROPERTY + const int32 TupleIndex = 0; + + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + { + if (ArrayHelper->IsValidIndex(TupleIndex)) + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + } + else if (TupleIndex < InnerProperty->ArrayDim) + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (ValuePtr) + { + UObject* ValueObject = ObjectProperty->GetObjectPropertyValue(ValuePtr); + const TSoftObjectPtr ValueObjectPtr = ValueObject; + InGenericAttribute.SetStringValue(ValueObjectPtr.ToString(), AtIndex + TupleIndex); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); + return false; + } + + return true; +} + +bool +FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + int32& OutAttributeTupleSize, + EAttribStorageType& OutAttributeStorageType) +{ + if (!InObject || InObject->IsPendingKill() || !InFoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Property class name, used for logging + const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); + + // Initialize using the found property + FProperty* InnerProperty = InFoundProperty; + + // FArrayProperty* ArrayProperty = CastField(InFoundProperty); + // TSharedPtr ArrayHelper; + // if (ArrayProperty) + // { + // InnerProperty = ArrayProperty->Inner; + // ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); + // } + + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + // Supported non-struct properties + + // Here we cannot really do better than tuple size of 1 (since we need to support arrays in the future, we + // cannot just assume array size == tuple size going to Houdini) + OutAttributeTupleSize = 1; + + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) + { + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (Property->IsFloatingPoint()) + { + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (Property->IsInteger()) + { + OutAttributeStorageType = EAttribStorageType::INT; + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *Property->GetName(), *PropertyClassName); + return false; + } + } + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::STRING; + } + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::STRING; + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties + + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + OutAttributeTupleSize = 3; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == NAME_Transform) + { + OutAttributeTupleSize = 10; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == NAME_Color) + { + OutAttributeTupleSize = 4; + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyName == NAME_LinearColor) + { + OutAttributeTupleSize = 4; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == "Int32Interval") + { + OutAttributeTupleSize = 2; + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyName == "FloatInterval") + { + OutAttributeTupleSize = 2; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InFoundProperty->GetName(), *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + OutAttributeTupleSize = 1; + OutAttributeStorageType = EAttribStorageType::STRING; + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InFoundProperty->GetName()); + return false; + } + + return true; +} + /* bool FHoudiniEngineUtils::TryToFindInStructProperty( diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h index 9ccab2a46..52ea4322c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h @@ -60,14 +60,14 @@ struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute FString AttributeName; UPROPERTY() - EAttribStorageType AttributeType; + EAttribStorageType AttributeType = EAttribStorageType::Invalid; UPROPERTY() - EAttribOwner AttributeOwner; + EAttribOwner AttributeOwner = EAttribOwner::Invalid; UPROPERTY() - int32 AttributeCount; + int32 AttributeCount = -1; UPROPERTY() - int32 AttributeTupleSize; + int32 AttributeTupleSize = -1; UPROPERTY() TArray DoubleValues; @@ -76,6 +76,8 @@ struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute UPROPERTY() TArray StringValues; + // Accessors + double GetDoubleValue(int32 index = 0) const; void GetDoubleTuple(TArray& TupleValues, int32 index = 0) const; @@ -90,6 +92,20 @@ struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute void* GetData(); + // Mutators + + void SetDoubleValue(double InValue, int32 index = 0); + void SetDoubleTuple(const TArray& InTupleValues, int32 index = 0); + + void SetIntValue(int64 InValue, int32 index = 0); + void SetIntTuple(const TArray& InTupleValues, int32 index = 0); + + void SetStringValue(const FString& InValue, int32 index = 0); + void SetStringTuple(const TArray& InTupleValues, int32 index = 0); + + void SetBoolValue(bool InValue, int32 index = 0); + void SetBoolTuple(const TArray& InTupleValues, int32 index = 0); + // static bool UpdatePropertyAttributeOnObject( UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex = 0); @@ -112,6 +128,23 @@ struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute void* InContainer, const int32& AtIndex = 0 ); + // Gets the value of a found Property and sets it in the appropriate + // array and index in InGenericAttribute. + static bool GetPropertyValueFromObject( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + FHoudiniGenericAttribute& InGenericAttribute, + const int32& InAtIndex = 0); + + // Helper: determines a valid tuple size and storage type for a Houdini attribute from an Unreal FProperty + static bool GetAttributeTupleSizeAndStorageFromProperty( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + int32& OutAttributeTupleSize, + EAttribStorageType& OutAttributeStorageType); + // Recursive search for a given property on a UObject static bool TryToFindProperty( void* InContainer, diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp index 6475e2be0..3276ed190 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp @@ -43,6 +43,7 @@ #include "Engine/StaticMesh.h" #include "Engine/SkeletalMesh.h" #include "UObject/UObjectGlobals.h" +#include "FoliageType_InstancedStaticMesh.h" #include "Components/SplineComponent.h" #include "Components/StaticMeshComponent.h" @@ -1931,6 +1932,7 @@ UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) AllowedClasses.Add(USkeletalMesh::StaticClass()); AllowedClasses.Add(UBlueprint::StaticClass()); AllowedClasses.Add(UDataTable::StaticClass()); + AllowedClasses.Add(UFoliageType_InstancedStaticMesh::StaticClass()); break; case EHoudiniInputType::Curve: diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp index 7a56207a9..fe5f2def3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp @@ -43,6 +43,7 @@ #include "Engine/Engine.h" #include "GameFramework/Volume.h" #include "Camera/CameraComponent.h" +#include "FoliageType_InstancedStaticMesh.h" #include "Model.h" #include "Engine/Brush.h" @@ -236,13 +237,13 @@ UHoudiniInputObject::GetObject() const } UStaticMesh* -UHoudiniInputStaticMesh::GetStaticMesh() +UHoudiniInputStaticMesh::GetStaticMesh() const { return Cast(InputObject.LoadSynchronous()); } UBlueprint* -UHoudiniInputStaticMesh::GetBlueprint() +UHoudiniInputStaticMesh::GetBlueprint() const { return Cast(InputObject.LoadSynchronous()); } @@ -416,6 +417,10 @@ UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter case EHoudiniInputObjectType::DataTable: HoudiniInputObject = UHoudiniInputDataTable::Create(InObject, InOuter, InName); break; + + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + HoudiniInputObject = UHoudiniInputFoliageType_InstancedStaticMesh::Create(InObject, InOuter, InName); + break; case EHoudiniInputObjectType::Invalid: default: @@ -815,7 +820,7 @@ UHoudiniInputStaticMesh::Update(UObject * InObject) { // Nothing to do Super::Update(InObject); - // Static Mesh input accpets either SM and BP. + // Static Mesh input accepts SM, BP, FoliageType_InstancedStaticMesh (static mesh) and FoliageType_Actor (if blueprint actor). UStaticMesh* SM = Cast(InObject); UBlueprint* BP = Cast(InObject); @@ -1419,6 +1424,10 @@ UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) { return EHoudiniInputObjectType::StaticMesh; } + else if (InObject->IsA(UFoliageType_InstancedStaticMesh::StaticClass())) + { + return EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; + } else { if (InObject->IsA(UStaticMesh::StaticClass())) @@ -1794,4 +1803,62 @@ UDataTable* UHoudiniInputDataTable::GetDataTable() const { return Cast(InputObject.LoadSynchronous()); -} \ No newline at end of file +} + +UHoudiniInputFoliageType_InstancedStaticMesh::UHoudiniInputFoliageType_InstancedStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniInputObject* +UHoudiniInputFoliageType_InstancedStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_FoliageSM_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputFoliageType_InstancedStaticMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +void +UHoudiniInputFoliageType_InstancedStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + UHoudiniInputFoliageType_InstancedStaticMesh* FoliageTypeSM = Cast(InInput); + if (!IsValid(FoliageTypeSM)) + return; + + UHoudiniInputObject::CopyStateFrom(FoliageTypeSM, bCopyAllProperties); + + // BlueprintStaticMeshes array is not used in UHoudiniInputFoliageType_InstancedStaticMesh + BlueprintStaticMeshes.Empty(); +} + +void +UHoudiniInputFoliageType_InstancedStaticMesh::Update(UObject * InObject) +{ + UHoudiniInputObject::Update(InObject); + UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InObject); + ensure(FoliageType); + ensure(FoliageType->GetStaticMesh()); +} + +UStaticMesh* +UHoudiniInputFoliageType_InstancedStaticMesh::GetStaticMesh() const +{ + if (!InputObject.IsValid()) + return nullptr; + + UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InputObject.LoadSynchronous()); + if (!IsValid(FoliageType)) + return nullptr; + + return FoliageType->GetStaticMesh(); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h index b02d32c3f..ce7b11c51 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h @@ -78,6 +78,7 @@ enum class EHoudiniInputObjectType : uint8 CameraComponent, DataTable, HoudiniAssetActor, + FoliageType_InstancedStaticMesh, }; //----------------------------------------------------------------------------------------------------------------------------- @@ -230,13 +231,13 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputStaticMesh : public UHoudiniInputObj // Nothing to add for Static Meshes? // StaticMesh accessor - class UStaticMesh* GetStaticMesh(); + virtual class UStaticMesh* GetStaticMesh() const; // Blueprint accessor - class UBlueprint* GetBlueprint(); + virtual class UBlueprint* GetBlueprint() const; // Check if this SM Input object is passed in as a BP - bool bIsBlueprint() const; + virtual bool bIsBlueprint() const; // The Blueprint's Static Meshe Components that can be sent as inputs UPROPERTY() @@ -828,4 +829,33 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputDataTable : public UHoudiniInputObje // DataTable accessor class UDataTable* GetDataTable() const; -}; \ No newline at end of file +}; + +//----------------------------------------------------------------------------------------------------------------------------- +// UFoliageType_InstancedStaticMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputFoliageType_InstancedStaticMesh : public UHoudiniInputStaticMesh +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // UHoudiniInputObject overrides + + // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; + + // + virtual void Update(UObject * InObject) override; + + // StaticMesh accessor + virtual class UStaticMesh* GetStaticMesh() const override; + + virtual class UBlueprint* GetBlueprint() const override { return nullptr; } + + virtual bool bIsBlueprint() const override { return false; } +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp index 31cc58d44..702d5d25d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp @@ -87,6 +87,7 @@ FTOPWorkResultObject::FTOPWorkResultObject() FilePath = FString(); State = EPDGWorkResultState::None; WorkItemResultInfoIndex = INDEX_NONE; + bAutoBakedSinceLastLoad = false; } FTOPWorkResultObject::~FTOPWorkResultObject() @@ -1686,7 +1687,7 @@ FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAs AActor *Actor = WorldToSpawnIn->SpawnActor(SpawnParams); SetOutputActor(Actor); #if WITH_EDITOR - Actor->SetActorLabel(InName.ToString()); + FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, InName.ToString()); #endif // Set the actor transform: create a root component if it does not have one diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h index 5ebbcc7ba..0fc8a5c8c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h @@ -178,7 +178,7 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject // If true, indicates that the work result object has been auto-baked since it was last loaded. UPROPERTY(NonTransactional) - bool bAutoBakedSinceLastLoad; + bool bAutoBakedSinceLastLoad = false; private: // List of objects to delete, internal use only (DestroyResultOutputs) diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp index 2e44476a0..e54c857d0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp @@ -381,55 +381,22 @@ void UHoudiniSplineComponent::PostLoad() { Super::PostLoad(); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::PostLoad()] Component: %s"), *GetPathName()); - } TStructOnScope UHoudiniSplineComponent::GetComponentInstanceData() const { - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::GetComponentInstanceData()] Component: %s"), *GetPathName()); TStructOnScope ComponentInstanceData = MakeStructOnScope(this); FHoudiniSplineComponentInstanceData* InstanceData = ComponentInstanceData.Cast(); - - // NOTE: We need to capture these properties here before the component gets torn down - // since the Spline visualizer changed values on the instance directly and is not present on the - // template yet. - /*InstanceData->CurvePoints = CurvePoints; - InstanceData->DisplayPoints = DisplayPoints; - InstanceData->DisplayPointIndexDivider = DisplayPointIndexDivider; - -#if WITH_EDITOR_DATA - InstanceData->EditedControlPointsIndexes = EditedControlPointsIndexes; -#endif*/ - return ComponentInstanceData; } void -UHoudiniSplineComponent::ApplyComponentInstanceData(FHoudiniSplineComponentInstanceData* ComponentInstanceData, - const bool bPostUCS) +UHoudiniSplineComponent::ApplyComponentInstanceData( + FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS) { - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %s"), *GetPathName()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] Component: %p"), this); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::ApplyComponentInstanceData()] bHiddenInGame: %d"), bHiddenInGame); - check(ComponentInstanceData); - - if (!bPostUCS) - { - //bHasChanged = ComponentInstanceData->bHasChanged; - //bNeedsToTriggerUpdate = ComponentInstanceData->bNeedsToTriggerUpdate; - /*CurvePoints = ComponentInstanceData->CurvePoints; - DisplayPoints = ComponentInstanceData->DisplayPoints; - DisplayPointIndexDivider = ComponentInstanceData->DisplayPointIndexDivider; - -#if WITH_EDITOR_DATA - EditedControlPointsIndexes = ComponentInstanceData->EditedControlPointsIndexes; -#endif*/ - } } void @@ -438,20 +405,9 @@ UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) // Capture properties that we want to preserve during copy const int32 PrevNodeId = NodeId; - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] BEFORE - bHiddenInGame: %d"), bHiddenInGame); - UActorComponent* FromComponent = Cast(FromObject); check(FromComponent); - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - //Params.bDoDelta = false; // Perform a deep copy - //Params.bClearReferences = false; - //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, this, Params); - - /*const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); - FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, this, ComponentCopyOptions);*/ - UHoudiniSplineComponent* FromSplineComponent = Cast(FromObject); if (FromSplineComponent) { @@ -472,15 +428,10 @@ UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) bIsEditableOutputCurve = FromSplineComponent->bIsEditableOutputCurve; bCookOnCurveChanged = FromSplineComponent->bCookOnCurveChanged; - - bHasChanged = FromSplineComponent->bHasChanged; bNeedsToTriggerUpdate = FromSplineComponent->bNeedsToTriggerUpdate; } - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - IsVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::CopyPropertiesFrom()] AFTER - bHiddenInGame: %d"), bHiddenInGame); - // Restore properties that we want to preserve NodeId = PrevNodeId; } @@ -495,9 +446,6 @@ UHoudiniSplineComponent::OnUnregister() void UHoudiniSplineComponent::OnComponentCreated() { - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] Component: %s"), *GetPathName()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bVisible: %d"), IsVisible()); - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniSplineComponent::OnComponentCreated()] bHiddenInGame: %d"), bHiddenInGame); Super::OnComponentCreated(); } @@ -529,22 +477,7 @@ UHoudiniSplineComponent::PostEditUndo() bPostUndo = true; - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return; - // - // CurrentInputObject->MarkChanged(true); - // } - // - // if (bIsEditableOutputCurve) - // { - // MarkChanged(true); - // } - MarkChanged(true); - } #endif @@ -586,22 +519,6 @@ bool UHoudiniSplineComponent::NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; - - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return false; - // - // return CurrentInputObject->NeedsToTriggerUpdate(); - // } - // - // if (bIsEditableOutputCurve) - // { - // return bNeedsToTriggerUpdate; - // } - // - // return false; } void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate) @@ -612,34 +529,6 @@ void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTrigger void UHoudiniSplineComponent::SetCurveType(const EHoudiniCurveType & NewCurveType) { CurveType = NewCurveType; -#if WITH_EDITOR - //FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(this, TEXT("CurveType")); -#endif -} - -void -UHoudiniSplineComponent::MarkInputObjectChanged() -{ - // if (bIsInputCurve) - // { - // UHoudiniInputObject * CurrentInputObject = GetInputObject(); - // if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - // return; - // - // if (HasChanged()) - // CurrentInputObject->MarkChanged(true); - // } - // - // if (bIsEditableOutputCurve) - // { - // if (HasChanged()) - // MarkChanged(true); - // } - - // NOTE: This component should be trying to push ANY state changes to Input or Output objects. This - // component should strictly be a data container. Input / Output objects that reference this component should - // be polling this component's state. - MarkChanged(true); } bool UHoudiniSplineComponent::HasChanged() const @@ -653,30 +542,6 @@ void UHoudiniSplineComponent::MarkChanged(const bool& Changed) bNeedsToTriggerUpdate = Changed; } -// UHoudiniAssetComponent* -// UHoudiniSplineComponent::GetParentHAC() -// { -// UHoudiniAssetComponent* ParentHAC = nullptr; -// if (bIsInputCurve) -// { -// if (!InputObject) -// return nullptr; -// -// UHoudiniInput* Input = Cast(InputObject->GetOuter()); -// if (!Input) -// return nullptr; -// -// ParentHAC = Cast(Input->GetOuter()); -// } -// else -// { -// // may do something else if this is not an input curve instead of returning Null. -// } -// -// return ParentHAC; -// -// } - FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() { } diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h index a860e8eff..ef8160bdf 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h @@ -76,18 +76,11 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, void EditPointAtindex(const FTransform& NewPoint, const int32& Index); - // UHoudiniAssetComponent* GetParentHAC(); - void MarkModified(const bool & InModified) { bHasChanged = InModified; }; // To set the offset of default position of houdini curve void SetOffset(const float& Offset); - UE_DEPRECATED(4.25, "Use MarkChanged() instead") - // This component should not be aware of whether it is being referenced by any input - // or output objects. - void MarkInputObjectChanged(); - bool HasChanged() const; void MarkChanged(const bool& Changed); @@ -102,12 +95,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, void SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate); - // FORCEINLINE - // UHoudiniInputObject* GetInputObject() const { return InputObject; } - - // FORCEINLINE - // void SetInputObject(UHoudiniInputObject* NewInputObject) { InputObject = NewInputObject; } - FORCEINLINE EHoudiniCurveType GetCurveType() const { return CurveType; } @@ -229,7 +216,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, #endif protected: - /** Corresponding geo part object. **/ + // Corresponding geo part object. FHoudiniGeoPartObject HoudiniGeoPartObject; private: @@ -246,9 +233,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, UPROPERTY() bool bIsEditableOutputCurve; - // UPROPERTY() - // UHoudiniInputObject * InputObject; - // Corresponds to the Curve NodeId in Houdini UPROPERTY(Transient, DuplicateTransient) int32 NodeId; @@ -257,7 +241,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, FString PartName; }; -/** Used to store HoudiniAssetComponent data during BP reconstruction */ +// Used to store HoudiniAssetComponent data during BP reconstruction USTRUCT() struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData { @@ -275,13 +259,6 @@ struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); } - // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. - /*UPROPERTY() - bool bHasChanged; - - UPROPERTY() - bool bNeedsToTriggerUpdate;*/ - UPROPERTY() TArray CurvePoints; From 8a254df019f6aee46dcdc45f41f2e0baa06bae01 Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Mon, 3 May 2021 10:46:25 -0400 Subject: [PATCH 09/16] Unreal: Version 2 - Added missing new files. --- .../Private/Tests/HoudiniCoreTests.cpp | 13 + .../Private/Tests/HoudiniCoreTests.h | 7 + .../Private/UnrealFoliageTypeTranslator.cpp | 289 ++++++++++++ .../Private/UnrealFoliageTypeTranslator.h | 66 +++ .../Private/Tests/HoudiniEditorTestUtils.cpp | 445 ++++++++++++++++++ .../Private/Tests/HoudiniEditorTestUtils.h | 56 +++ .../Private/Tests/HoudiniEditorTests.cpp | 31 ++ .../Private/Tests/HoudiniEditorTests.h | 6 + .../Private/Tests/HoudiniRuntimeTests.cpp | 17 + .../Private/Tests/HoudiniRuntimeTests.h | 7 + 10 files changed, 937 insertions(+) create mode 100644 Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.cpp create mode 100644 Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.h create mode 100644 Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp create mode 100644 Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h create mode 100644 Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp create mode 100644 Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h create mode 100644 Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.cpp create mode 100644 Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.h create mode 100644 Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.cpp create mode 100644 Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.h diff --git a/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.cpp b/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.cpp new file mode 100644 index 000000000..251e169c4 --- /dev/null +++ b/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.cpp @@ -0,0 +1,13 @@ +#include "../HoudiniEngine.h" +#include "Misc/AutomationTest.h" + +#if WITH_DEV_AUTOMATION_TESTS + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(HoudiniCoreTest, "Houdini.Core.TestAutomation", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter) + +bool HoudiniCoreTest::RunTest(const FString & Parameters) +{ + return true; +} + +#endif \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.h b/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.h new file mode 100644 index 000000000..a9d659de4 --- /dev/null +++ b/Source/HoudiniEngine/Private/Tests/HoudiniCoreTests.h @@ -0,0 +1,7 @@ +#pragma once +#if WITH_DEV_AUTOMATION_TESTS + +#include "CoreMinimal.h" + +#endif + diff --git a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp new file mode 100644 index 000000000..9b3ce7d63 --- /dev/null +++ b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp @@ -0,0 +1,289 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealFoliageTypeTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "FoliageType_InstancedStaticMesh.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInputTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" + + +bool +FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + UFoliageType_InstancedStaticMesh* InFoliageType, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + const bool& ExportAllLODs, + const bool& ExportSockets, + const bool& ExportColliders) +{ + if (!IsValid(InFoliageType)) + return false; + + UStaticMesh* const InputSM = InFoliageType->GetStaticMesh(); + if (!IsValid(InputSM)) + return false; + + UStaticMeshComponent* const StaticMeshComponent = nullptr; + bool bSuccess = HapiCreateInputNodeForStaticMesh( + InputSM, + InputObjectNodeId, + InputNodeName, + StaticMeshComponent, + ExportAllLODs, + ExportSockets, + ExportColliders); + + if (bSuccess) + { + const int32 PartId = 0; + CreateHoudiniFoliageTypeAttributes(InFoliageType, InputObjectNodeId, PartId, HAPI_ATTROWNER_DETAIL); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId), false); + } + + return bSuccess; +} + +bool FUnrealFoliageTypeTranslator::CreateInputNodeForReference( + UFoliageType* InFoliageType, + HAPI_NodeId& InInputNodeId, + const FString& InRef, + const FString& InInputNodeName, + const FTransform& InTransform) +{ + bool bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InInputNodeId, InRef, InInputNodeName, InTransform); + if (!bSuccess) + return false; + + const int32 PartId = 0; + if (CreateHoudiniFoliageTypeAttributes(InFoliageType, InInputNodeId, PartId, HAPI_ATTROWNER_POINT)) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InInputNodeId), false); + return true; + } + + return false; +} + +bool +FUnrealFoliageTypeTranslator::CreateHoudiniFoliageTypeAttributes(UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner) +{ + if (InNodeId < 0) + return false; + + bool bSuccess = true; + + // Create attribute for unreal_foliage + HAPI_AttributeInfo AttributeInfoUnrealFoliage; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoUnrealFoliage); + AttributeInfoUnrealFoliage.tupleSize = 1; + AttributeInfoUnrealFoliage.count = 1; + AttributeInfoUnrealFoliage.exists = true; + AttributeInfoUnrealFoliage.owner = InAttributeOwner; + AttributeInfoUnrealFoliage.storage = HAPI_STORAGETYPE_INT; + AttributeInfoUnrealFoliage.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage)) + { + // The New attribute has been successfully created, set its value + int UnrealFoliage = 1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage, + &UnrealFoliage, 0, 1)) + { + bSuccess = false; + } + } + + if (!bSuccess) + return false; + + // Foliage type properties that should be sent to Houdini as unreal_uproperty_ attributes. + static TArray FoliageTypePropertyNames({ + // float + // FName("Density"), + // FName("DensityAdjustmentFactor"), + // FName("Radius"), + // FName("SingleInstanceModeRadius"), + FName("AlignMaxAngle"), + FName("RandomPitchAngle"), + FName("MinimumLayerWeight"), + FName("MinimumExclusionLayerWeight"), + FName("CollisionRadius"), + FName("ShadeRadius"), + FName("InitialSeedDensity"), + FName("AverageSpreadDistance"), + FName("SpreadVariance"), + FName("MaxInitialSeedOffset"), + FName("MaxInitialAge"), + FName("MaxAge"), + FName("OverlapPriority"), + + // bool + // FName("bSingleInstanceModeOverrideRadius"), + FName("bCanGrowInShade"), + FName("bSpawnsInShade"), + + // int32 + FName("OverriddenLightMapRes"), + FName("CustomDepthStencilValue"), + FName("TranslucencySortPriority"), + FName("NumSteps"), + FName("SeedsPerStep"), + FName("DistributionSeed"), + FName("ChangeCount"), + FName("VirtualTextureCullMips"), + + // uint32 + FName("AlignToNormal"), + FName("RandomYaw"), + FName("CollisionWithWorld"), + FName("CastShadow"), + FName("bAffectDynamicIndirectLighting"), + FName("bAffectDistanceFieldLighting"), + FName("bCastDynamicShadow"), + FName("bCastStaticShadow"), + FName("bCastShadowAsTwoSided"), + FName("bReceivesDecals"), + FName("bOverrideLightMapRes"), + FName("bUseAsOccluder"), + FName("bRenderCustomDepth"), + FName("ReapplyDensity"), + FName("ReapplyRadius"), + FName("ReapplyAlignToNormal"), + FName("ReapplyRandomYaw"), + FName("ReapplyScaling"), + FName("ReapplyScaleX"), + FName("ReapplyScaleY"), + FName("ReapplyScaleZ"), + FName("ReapplyRandomPitchAngle"), + FName("ReapplyGroundSlope"), + FName("ReapplyHeight"), + FName("ReapplyLandscapeLayers"), + FName("ReapplyZOffset"), + FName("ReapplyCollisionWithWorld"), + FName("ReapplyVertexColorMask"), + FName("bEnableDensityScaling"), + FName("bEnableDiscardOnLoad"), + + // enums + // FName("Scaling"), + FName("LightmapType"), + + // FFloatInterval + // FName("ScaleX"), + // FName("ScaleY"), + // FName("ScaleZ"), + FName("ZOffset"), + FName("GroundSlopeAngle"), + FName("Height"), + FName("ProceduralScale"), + + // FVector + FName("CollisionScale"), + FName("LowBoundOriginRadius")}); + + EAttribOwner AttribOwner; + switch (InAttributeOwner) + { + case HAPI_ATTROWNER_POINT: + AttribOwner = EAttribOwner::Point; + break; + case HAPI_ATTROWNER_VERTEX: + AttribOwner = EAttribOwner::Vertex; + break; + case HAPI_ATTROWNER_PRIM: + AttribOwner = EAttribOwner::Prim; + break; + case HAPI_ATTROWNER_DETAIL: + AttribOwner = EAttribOwner::Detail; + break; + case HAPI_ATTROWNER_INVALID: + case HAPI_ATTROWNER_MAX: + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InAttributeOwner); + return false; + } + FHoudiniGenericAttribute GenericAttribute; + GenericAttribute.AttributeCount = 1; + GenericAttribute.AttributeOwner = AttribOwner; + + // Reserve enough space in the arrays (we only have a single point (or all are detail attributes), so attribute + // count is 1, but the tuple size could be up to 10 for transforms + GenericAttribute.DoubleValues.Reserve(10); + GenericAttribute.IntValues.Reserve(10); + GenericAttribute.StringValues.Reserve(10); + + for (const FName& PropertyName : FoliageTypePropertyNames) + { + const FString PropertyNameStr = PropertyName.ToString(); + GenericAttribute.AttributeName = FString::Printf(TEXT("unreal_uproperty_%s"), *PropertyNameStr); + // Find the property on the foliage type instance + FProperty* FoundProperty = nullptr; + UObject* FoundPropertyObject = nullptr; + void* Container = nullptr; + if (!FHoudiniGenericAttribute::FindPropertyOnObject( + InFoliageType, + PropertyNameStr, + FoundProperty, + FoundPropertyObject, + Container)) + continue; + + if (!FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( + InFoliageType, + FoundProperty, + Container, + GenericAttribute.AttributeTupleSize, + GenericAttribute.AttributeType)) + continue; + + const int32 AtIndex = 0; + if (!FHoudiniGenericAttribute::GetPropertyValueFromObject( + InFoliageType, + FoundProperty, + Container, + GenericAttribute, + AtIndex)) + continue; + + FHoudiniEngineUtils::SetGenericPropertyAttribute(InNodeId, InPartId, GenericAttribute); + } + + return bSuccess; +} diff --git a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h new file mode 100644 index 000000000..2e0d14c81 --- /dev/null +++ b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h @@ -0,0 +1,66 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "UnrealMeshTranslator.h" + +class UFoliageType; +class UFoliageType_InstancedStaticMesh; +class UStaticMeshComponent; + +struct HOUDINIENGINE_API FUnrealFoliageTypeTranslator : public FUnrealMeshTranslator +{ +public: + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + UFoliageType_InstancedStaticMesh* InFoliageType, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false, + const bool& ExportColliders = false); + + // Create an input node that references the asset via InRef (unreal_instance). + // Also calls CreateHoudiniFoliageTypeAttributes, to create the unreal_foliage attribute, as well as + // unreal_uproperty_ attributes for the foliage type settings. + static bool CreateInputNodeForReference( + UFoliageType* InFoliageType, + HAPI_NodeId& InInputNodeId, + const FString& InRef, + const FString& InInputNodeName, + const FTransform& InTransform); + +protected: + // Creates the unreal_foliage and unreal_uproperty_ attributes for the foliage type. + static bool CreateHoudiniFoliageTypeAttributes( + UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner); +}; diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp new file mode 100644 index 000000000..0e24db99a --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp @@ -0,0 +1,445 @@ + + +#if WITH_DEV_AUTOMATION_TESTS +#include "HoudiniEditorTestUtils.h" +#include "IAssetViewport.h" +#include "Slate/SceneViewport.h" +#include "Widgets/SViewport.h" +#include "FileHelpers.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" +#include "LevelEditor.h" +#include "AssetRegistryModule.h" +#include "Core/Public/HAL/FileManager.h" +#include "Core/Public/HAL/PlatformFilemanager.h" +#include "Editor/EditorPerformanceSettings.h" +#include "Engine/Selection.h" +#include "Interfaces/IMainFrameModule.h" +#include "Misc/AutomationTest.h" +#include "Tests/AutomationCommon.h" + +const FVector2D FHoudiniEditorTestUtils::GDefaultEditorSize = FVector2D(1280, 720); + +void FHoudiniEditorTestUtils::InitializeTests(FAutomationTestBase* Test) +{ + FHoudiniEditorTestUtils::GetMainFrameWindow()->Resize(GDefaultEditorSize); + FEditorFileUtils::LoadMap(TEXT("/Game/TestLevel"), false, false); +} + +UObject* FHoudiniEditorTestUtils::FindAssetUObject(FAutomationTestBase* Test, const FName AssetUObjectPath) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); + TArray AssetData; + AssetRegistryModule.Get().GetAssetsByPackageName( AssetUObjectPath, AssetData ); + if( AssetData.Num() > 0 ) + { + return AssetData[ 0 ].GetAsset(); + } + + Test->AddError(FString::Printf(TEXT("Could not find UObject: %s"), *AssetUObjectPath.ToString())); + return nullptr; +} + +UHoudiniAssetComponent* FHoudiniEditorTestUtils::InstantiateAsset(FAutomationTestBase* Test, + const FName AssetUObjectPath, TFunction OnFinishInstantiate, const bool ErrorOnFail) +{ + SetUseLessCPUInTheBackground(); + + UHoudiniAsset * HoudiniAsset = Cast(FindAssetUObject(Test, AssetUObjectPath)); + + if (!HoudiniAsset) + { + Test->AddError(FString::Printf(TEXT("Could not find UObject: %s"), *AssetUObjectPath.ToString())); + return nullptr; + } + + + FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, FTransform::Identity); + + USelection* SelectedActors = GEditor->GetSelectedActors(); + TArray Actors; + TArray UniqueLevels; + for (FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) + { + AActor* Actor = Cast(*Iter); + if (Actor) + { + Actors.Add(Actor); + } + } + Test->TestEqual(TEXT("Only one actor should be selected"), Actors.Num(), 1); + + AActor* TheActor = Actors[0]; + UHoudiniAssetComponent * HoudiniComponent = TheActor->FindComponentByClass(); + + + // Need to allocate on heap otherwise it will be garbage collected. + bool * FinishedCook = new bool(false); + bool * CookSuccessful = new bool(false); + + HoudiniComponent->GetOnPostCookDelegate().BindLambda([=](UHoudiniAssetComponent* HAC, bool IsSuccess) + { + if (FinishedCook != nullptr && CookSuccessful != nullptr) + { + *FinishedCook = true; + *CookSuccessful = IsSuccess; + HoudiniComponent->GetOnPostCookDelegate().Unbind(); + } + }); + + Test->AddCommand(new FFunctionLatentCommand([=]() + { + const bool FinishedCookResult = *FinishedCook; + const bool CookSuccessfulResult = *CookSuccessful; + + if (FinishedCookResult == true && HoudiniComponent->GetAssetState() == EHoudiniAssetState::None) + { + if (ErrorOnFail && CookSuccessfulResult == false) + { + Test->AddError(FString::Printf(TEXT("Cook was unsuccessful: %s"), *AssetUObjectPath.ToString())); + } + + ForceRefreshViewport(); + + OnFinishInstantiate(HoudiniComponent, CookSuccessfulResult); + delete FinishedCook; + delete CookSuccessful; + + return true; + } + + return false; + } + )); + + return HoudiniComponent; +} + +void FHoudiniEditorTestUtils::SetUseLessCPUInTheBackground() +{ + // Equivalent of setting Edit > Editor Preferences > General > Performance > "Use less CPU when in background" is OFF + // This ensures that objects are rendered even in the background + UEditorPerformanceSettings* Settings = GetMutableDefault(); + Settings->bThrottleCPUWhenNotForeground = false; + Settings->bMonitorEditorPerformance = false; + Settings->PostEditChange(); +} + +TSharedPtr FHoudiniEditorTestUtils::GetMainFrameWindow() +{ + + if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + // Check if the main frame is loaded. When using the old main frame it may not be. + IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); + return MainFrame.GetParentWindow(); + } + + return nullptr; +} + +TSharedPtr FHoudiniEditorTestUtils::GetActiveTopLevelWindow() +{ + return FSlateApplication::Get().GetActiveTopLevelWindow(); +} + +static bool ShouldShowProperty(const FPropertyAndParent& PropertyAndParent, bool bHaveTemplate) +{ + const FProperty& Property = PropertyAndParent.Property; + + if ( bHaveTemplate ) + { + const UClass* PropertyOwnerClass = Property.GetOwner(); + const bool bDisableEditOnTemplate = PropertyOwnerClass + && PropertyOwnerClass->IsNative() + && Property.HasAnyPropertyFlags(CPF_DisableEditOnTemplate); + if(bDisableEditOnTemplate) + { + return false; + } + } + return true; +} + +TSharedPtr FHoudiniEditorTestUtils::CreateNewDetailsWindow() +{ + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + // Check if the main frame is loaded. When using the old main frame it may not be. + FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked("PropertyEditor"); + + USelection* SelectedActors = GEditor->GetSelectedActors(); + TArray Actors; + for (FSelectionIterator Iter(*SelectedActors); Iter; ++Iter) + { + AActor* Actor = Cast(*Iter); + if (Actor) + { + Actors.Add(Actor); + } + } + + TSharedRef Details = PropertyEditorModule.CreateFloatingDetailsView(Actors, false);// + + return Details; + } + + return nullptr; +} + +TSharedPtr FHoudiniEditorTestUtils::CreateViewportWindow() +{ + if (!FModuleManager::Get().IsModuleLoaded("LevelEditor")) + { + return nullptr; + } + + TSharedRef NewSlateWindow = SNew(SWindow) + .Title( NSLOCTEXT("ViewportEditor", "WindowTitle", "Viewport Editor") ) + .ClientSize( FVector2D(400, 550) ); + + // If the main frame exists parent the window to it + TSharedPtr< SWindow > ParentWindow; + if( FModuleManager::Get().IsModuleLoaded( "MainFrame" ) ) + { + IMainFrameModule& MainFrame = FModuleManager::GetModuleChecked( "MainFrame" ); + ParentWindow = MainFrame.GetParentWindow(); + } + + if( ParentWindow.IsValid() ) + { + // Parent the window to the main frame + FSlateApplication::Get().AddWindowAsNativeChild( NewSlateWindow, ParentWindow.ToSharedRef() ); + } + else + { + FSlateApplication::Get().AddWindow( NewSlateWindow ); + } + + FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked("LevelEditor"); + TSharedPtr Viewport = LevelEditor.GetFirstActiveViewport(); + + NewSlateWindow->SetContent( + SNew(SBorder) + [ + Viewport->AsWidget() + ] + ); + + return NewSlateWindow; +} + +void FHoudiniEditorTestUtils::TakeScreenshotEditor(FAutomationTestBase* Test, const FString ScreenshotName, const EEditorScreenshotType EditorScreenshotType, const FVector2D Size) +{ + // Wait one frame just in case for pending redraws + Test->AddCommand(new FDelayedFunctionLatentCommand( [=]() + { + TSharedPtr ScreenshotWindow; + + bool DestroyWindowOnEnd = false; + + switch (EditorScreenshotType) + { + case EEditorScreenshotType::ENTIRE_EDITOR: + ScreenshotWindow = GetMainFrameWindow(); + break; + case EEditorScreenshotType::ACTIVE_WINDOW: + ScreenshotWindow = GetActiveTopLevelWindow(); + break; + case EEditorScreenshotType::DETAILS_WINDOW: + ScreenshotWindow = CreateNewDetailsWindow(); + DestroyWindowOnEnd = true; + break; + case EEditorScreenshotType::VIEWPORT: + ScreenshotWindow = CreateViewportWindow(); + DestroyWindowOnEnd = true; + break; + default: + break; + } + + if (!ScreenshotWindow) + { + return; + } + + WindowScreenshotParameters ScreenshotParameters; + ScreenshotParameters.ScreenshotName = ScreenshotName; + ScreenshotParameters.CurrentWindow = ScreenshotWindow; + + // Creates a file in Engine\Saved\Automation\Tmp + TSharedRef WidgetToFind = ScreenshotWindow.ToSharedRef(); + + bool ScreenshotSuccessful; + + TArray OutImageData; + FIntVector OutImageSize; + + bool bRenderOffScreen = FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen")); + + if (!bRenderOffScreen) + { + // Take a screenshot like a normal person + // Note that this sizing method is slightly different than the offscreen one, so DO NOT copy the result to SVN + ScreenshotWindow->Resize(Size); + ScreenshotSuccessful = FSlateApplication::Get().TakeScreenshot(WidgetToFind, OutImageData, OutImageSize); + } + else + { + // Rendering offscreen results in a slightly different render pipeline. + // Resizing as usual doesn't seem to work unless we do it in this very specific way + // Mostly copied from FSlateApplication::Get().TakeScreenshot(WindowRef, OutImageData, OutImageSize) , but forces size + FWidgetPath WidgetPath; + FSlateApplication::Get().GeneratePathToWidgetChecked(WidgetToFind, WidgetPath); + + FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(WidgetToFind).Get(FArrangedWidget::GetNullWidget()); + FVector2D Position = ArrangedWidget.Geometry.AbsolutePosition; + FVector2D WindowPosition = ScreenshotWindow->GetPositionInScreen(); + + FIntRect ScreenshotRect = FIntRect(0, 0, (int32)Size.X, (int32)Size.Y); + + ScreenshotRect.Min.X += ( Position.X - WindowPosition.X ); + ScreenshotRect.Min.Y += ( Position.Y - WindowPosition.Y ); + ScreenshotRect.Max.X += ( Position.X - WindowPosition.X ); + ScreenshotRect.Max.Y += ( Position.Y - WindowPosition.Y ); + + FSlateApplication::Get().GetRenderer()->RequestResize(ScreenshotWindow, Size.X, Size.Y); + ScreenshotWindow->Resize(Size); + FSlateApplication::Get().ForceRedrawWindow(ScreenshotWindow.ToSharedRef()); + + FSlateApplication::Get().GetRenderer()->PrepareToTakeScreenshot(ScreenshotRect, &OutImageData, ScreenshotWindow.Get()); + FSlateApplication::Get().ForceRedrawWindow(ScreenshotWindow.ToSharedRef()); + ScreenshotSuccessful = (ScreenshotRect.Size().X != 0 && ScreenshotRect.Size().Y != 0 && OutImageData.Num() >= ScreenshotRect.Size().X * ScreenshotRect.Size().Y); + OutImageSize.X = ScreenshotRect.Size().X; + OutImageSize.Y = ScreenshotRect.Size().Y; + } + + if (!ScreenshotSuccessful) + { + Test->AddError("Taking screenshot not successful!"); + return; + } + + FAutomationScreenshotData Data; + Data.Width = OutImageSize.X; + Data.Height = OutImageSize.Y; + Data.ScreenshotName = ScreenshotName; + + FAutomationTestFramework::Get().OnScreenshotCaptured().ExecuteIfBound(OutImageData, Data); + + WaitForScreenshotAndCopy(Test, ScreenshotName, [=] (FAutomationTestBase* AutomationTest, FString BaseName) + { + CopyScreenshotToTestFolder(AutomationTest, BaseName); + + if (DestroyWindowOnEnd) + { + ScreenshotWindow->RequestDestroyWindow(); + } + }); + + }, 0.1f)); +} + +void FHoudiniEditorTestUtils::TakeScreenshotViewport(FAutomationTestBase* Test, const FString ScreenshotName) +{ + Test->AddCommand(new FDelayedFunctionLatentCommand([=]() + { + const FString BaseName = ScreenshotName; + const FString ScreenshotPath = GetUnrealTestDirectory() + BaseName; + FScreenshotRequest::RequestScreenshot(ScreenshotPath, false, false); + ForceRefreshViewport(); + + WaitForScreenshotAndCopy(Test, BaseName, CopyScreenshotToTestFolder); + }, 0.1f)); +} + +void FHoudiniEditorTestUtils::WaitForScreenshotAndCopy(FAutomationTestBase* Test, FString BaseName, TFunction OnScreenshotGenerated) +{ + const FString TestDirectory = GetUnrealTestDirectory(); + const FString FileName = TestDirectory + BaseName; + + // Wait for screenshot to finish generating, and then copy to $RT/hapi/unreal/ + Test->AddCommand(new FFunctionLatentCommand([=]() + { + IPlatformFile& FileManager = FPlatformFileManager::Get().GetPlatformFile(); + if (FileManager.FileExists(*FileName)) + { + OnScreenshotGenerated(Test, BaseName); + return true; + } + else + { + ForceRefreshViewport(); + return false; + } + })); +} + +void FHoudiniEditorTestUtils::CopyScreenshotToTestFolder(FAutomationTestBase* Test, FString BaseName) +{ + const FString TestDirectory = GetUnrealTestDirectory(); + const FString FileName = TestDirectory + BaseName; + FString DestFileName = GetTestDirectory(); + if (!DestFileName.IsEmpty()) + { + DestFileName += FormatScreenshotOutputName(BaseName); + } + + IPlatformFile& FileManager = FPlatformFileManager::Get().GetPlatformFile(); + + // Copy output file to our test directory, if it exists. + if (!DestFileName.IsEmpty()) + { + UE_LOG(LogTemp, Verbose, TEXT("Copied file to: %s"), *DestFileName); + FileManager.CopyFile(*DestFileName, *FileName); + } + else + { + UE_LOG(LogTemp, Warning, TEXT("Unable to copy file!")); + } + + Test->AddCommand(new FFunctionLatentCommand([=]() + { + IPlatformFile& CopyFileManager = FPlatformFileManager::Get().GetPlatformFile(); + if (CopyFileManager.FileExists(*FileName)) + { + return true; + } + else + { + return false; + } + })); + +} + +FString FHoudiniEditorTestUtils::GetTestDirectory() +{ + return FPlatformMisc::GetEnvironmentVariable(TEXT("TEST_OUTPUT_DIR")) + FPlatformMisc::GetDefaultPathSeparator(); +} + +FString FHoudiniEditorTestUtils::GetUnrealTestDirectory() +{ + //return FPaths::AutomationDir() + "/Incoming/"; // 4.25 + return FPaths::AutomationTransientDir(); // 4.26 +} + +FString FHoudiniEditorTestUtils::FormatScreenshotOutputName(FString BaseName) +{ + const FString Prefix = FPlatformMisc::GetEnvironmentVariable(TEXT("TEST_PREFIX")); + return FString::Printf(TEXT("%s_%s"), *Prefix, *BaseName); +} + +void FHoudiniEditorTestUtils::ForceRefreshViewport() +{ + // Force redraws viewport even if not in focus to prevent hanging + FLevelEditorModule &PropertyEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + if (FModuleManager::Get().IsModuleLoaded("LevelEditor")) + { + PropertyEditorModule.BroadcastRedrawViewports(false); + } +} + +#endif diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h new file mode 100644 index 000000000..9be7e92d3 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h @@ -0,0 +1,56 @@ +#pragma once + +#if WITH_DEV_AUTOMATION_TESTS + +#include "CoreMinimal.h" + +class UHoudiniAssetComponent; + +class FHoudiniEditorTestUtils +{ +public: + enum EEditorScreenshotType + { + ENTIRE_EDITOR, + ACTIVE_WINDOW, // Gets the active window. Probably never use this. + DETAILS_WINDOW, + VIEWPORT + }; + + static void InitializeTests(FAutomationTestBase* Test); + + static UObject* FindAssetUObject(FAutomationTestBase* Test, const FName AssetUObjectPath); + + static UHoudiniAssetComponent* InstantiateAsset(FAutomationTestBase* Test, const FName AssetUObjectPath, TFunction OnFinishInstantiate, const bool ErrorOnFail = true); + + static void TakeScreenshotEditor(FAutomationTestBase* Test, const FString ScreenshotName, const EEditorScreenshotType EditorScreenshotType, const FVector2D Size); + + static void TakeScreenshotViewport(FAutomationTestBase* Test, const FString ScreenshotName); + + static void SetUseLessCPUInTheBackground(); + + static TSharedPtr GetMainFrameWindow(); + + static TSharedPtr GetActiveTopLevelWindow(); + + static TSharedPtr CreateNewDetailsWindow(); + + static TSharedPtr CreateViewportWindow(); + + static const FVector2D GDefaultEditorSize; + +private: + static void WaitForScreenshotAndCopy(FAutomationTestBase* Test, FString BaseName, TFunction OnScreenshotGenerated); + + static void CopyScreenshotToTestFolder(FAutomationTestBase* Test, FString BaseName); + + static FString GetTestDirectory(); + + static FString GetUnrealTestDirectory(); + + static FString FormatScreenshotOutputName(FString BaseName); + + static void ForceRefreshViewport(); +}; + +#endif \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.cpp b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.cpp new file mode 100644 index 000000000..4fe68b1ae --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.cpp @@ -0,0 +1,31 @@ +#include "HoudiniEditorTests.h" + + +#if WITH_DEV_AUTOMATION_TESTS +#include "HoudiniEditorTestUtils.h" + +#include "Core/Public/HAL/FileManager.h" +#include "Misc/AutomationTest.h" +#include "HoudiniAssetComponent.h" + + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(HoudiniEditorEvergreenTest, "Houdini.Editor.EvergreenScreenshots", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter) + +bool HoudiniEditorEvergreenTest::RunTest(const FString & Parameters) +{ + // Really force editor size + // TODO: Move to HoudiniEditorUtils + FHoudiniEditorTestUtils::InitializeTests(this); + + FHoudiniEditorTestUtils::InstantiateAsset(this, TEXT("/Game/TestHDAs/Evergreen"), + [=](UHoudiniAssetComponent * HAC, const bool IsSuccessful) + { + FHoudiniEditorTestUtils::TakeScreenshotEditor(this, "EverGreen_EntireEditor.png", FHoudiniEditorTestUtils::ENTIRE_EDITOR, FHoudiniEditorTestUtils::GDefaultEditorSize); + FHoudiniEditorTestUtils::TakeScreenshotEditor(this, "EverGreen_Details.png", FHoudiniEditorTestUtils::DETAILS_WINDOW, FVector2D(400, 1130)); + FHoudiniEditorTestUtils::TakeScreenshotEditor(this, "EverGreen_EditorViewport.png", FHoudiniEditorTestUtils::VIEWPORT, FVector2D(640, 360)); + //FHoudiniEditorTestUtils::TakeScreenshotViewport(this, "EverGreen_Viewport.png"); // Viewport resolution might be inconsisent + }); + return true; +} + +#endif diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.h b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.h new file mode 100644 index 000000000..a2f02c8f9 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTests.h @@ -0,0 +1,6 @@ +#pragma once +#if WITH_DEV_AUTOMATION_TESTS + +#include "CoreMinimal.h" + +#endif diff --git a/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.cpp b/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.cpp new file mode 100644 index 000000000..cb4d59173 --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.cpp @@ -0,0 +1,17 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#if WITH_DEV_AUTOMATION_TESTS + +#include "HoudiniRuntimeTests.h" +#include "HoudiniEngineRuntime.h" +#include "Misc/AutomationTest.h" + +IMPLEMENT_SIMPLE_AUTOMATION_TEST(HoudiniRuntimeTestAutomation, "Houdini.Runtime.TestAutomation", EAutomationTestFlags::EditorContext | EAutomationTestFlags::ProductFilter) + +bool HoudiniRuntimeTestAutomation::RunTest(const FString & Parameters) +{ + + return true; +} + +#endif \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.h b/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.h new file mode 100644 index 000000000..a42240fbd --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/Tests/HoudiniRuntimeTests.h @@ -0,0 +1,7 @@ +#pragma once + +#if WITH_DEV_AUTOMATION_TESTS + +#include "CoreMinimal.h" + +#endif From 7f651910d8108de620c7f0d3349c058a20ab70cf Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Thu, 3 Jun 2021 10:45:49 -0400 Subject: [PATCH 10/16] Houdini Engine for Unreal - Version 2.0.2 Update 2 of the V2 Plugin. The plug-in is now linked to Houdini 18.5.596 / HAPI 3.6.2. Documentation for version 2.0 of the plug-in is available: https://www.sidefx.com/docs/unreal/ ------------------------------------------------------- New features: ------------------------------------------------------- - Added support for Landscape Edit Layers: Output: Heightfield data can now be assigned to landscape edit layers on existing landscapes. Those landscapes need to have the "Enable Edit Layers" option enabled. The "unreal_landscape_editlayer_landscape" prim string attribute can be used to set the name of the target landscape actor. The "unreal_landscape_editlayer_name" prim string attribute can be used to set the name of the targeted edit layer. Finally, the "unreal_landscape_editlayer_after" primitive string attributes can be used to specify the name of another layer that the target layer should be created after. Input: If a Landscape with edit layers is used as input, the (final) heightfield and paint layers are still imported as usual, but we will also create heightfields of the individual edit layers using the layer names, prefixed by landscapelayer_. The imported layers will be hidden by default, and the visibility SOP is needed to unhide them. - Added a public API to the plugin: The API supports instantiating HDAs, setting parameters and inputs, cooking, inspecting and baking outputs of instantiated HDAs via C++, Blueprints and Python. The API has an example Editor Utility Widget and example Python scripts in the plugin's "Content/Examples" folder. Documentation for the public API will be added soon to the plugin's documentation. ------------------------------------------------------- Bug fixes: ------------------------------------------------------- - Fixed a regression that caused generic property attributes to not always be properly applied to generated Static Mesh Components. (specifically when using RawMeshes). - Fixed "unreal_uproperty_CollisionProfileName" not being properly applied in some cases. - Fixed crash when using the bake button in the output section. - Generic property attributes are now applied on spline outputs. - Textures from Houdini Materials are now created only if the corresponding "use" parameter isn't disabled. - Reduced the number of HAPI calls when translating Houdini materials. - Optimized mesh creation. (removed redundant calls to Physics/NavCollisions functions that are already part of the StaticMesh::Build function) - Mesh creation: removed unnecessary attempt to load an unreal material with an invalid path. - Fixed World inputs creating invalid input nodes with "Import as Reference" enabled. - Fixed "Import as Reference" not triggering input updates when disabled. - Fixed issues with multiparm/ramps sync if a HDA's parameter interface had changed. This could cause mix-ups in the multiparms children parameters, and even crashes due to a recursive loop after load/rebuild. - Fixed missing output detail UI when an output's node is marked as editable but is not an editable curve. - Fixed potential crash caused by the plugin attempting to update rendering while loading a level. - Fixed an issue that caused OBJ HDAs with nested OBJ_geo subnets to not display/use the main OBJ geo. This caused some HDAs that used vellum solvers/DOP networks to not properly output their results. - Float Vector3 parameters now have spin buttons on the XYZ values. - Fixed regression causing mesh sockets to be ignored when they were on a separate part. This was likely to happen when using socket groups on "floating" points (point that are not used by a mesh). - Fixed generated mesh sockets displaying the default "Not found" mesh when they had no actor assigned. We only display the default mesh when we failed to attach or spawn the assigned Actor. - The output UI for Proxy meshes now display the number of sockets found in the output. - Fixed issue where landscape updates wouldn't immediately update viewport after cooking. - Fixed material assignments for Meshes with LODs (when using RawMesh) Before, all LODs used the first material. - Fixed materials being mixed up when using multiple point instancers with the "unreal_split_attr" attributes. Only the first instancer had the proper materials. - Fixed multiple issues when importing one/multiple landscape proxies via a world/landscape input: - The paint layers data were not properly converted to HF masks when converting the landscape to Heightfield. - Fixed landscape proxies having an incorrect transform in Houdini. - Fixed multiple landscapes not being imported properly with World Inputs. --- Content/Examples/EUW/EUW_APIExample.uasset | Bin 0 -> 794732 bytes .../Examples/Maps/LandscapeInputExample.umap | Bin 0 -> 1531290 bytes .../LandscapeInputExample_BuiltData.uasset | Bin 0 -> 1134497 bytes .../Examples/Python/asset_input_example.py | 143 + .../Python/bake_all_outputs_example.py | 110 + .../Python/bake_output_object_example.py | 79 + .../Examples/Python/curve_input_example.py | 155 + Content/Examples/Python/geo_input_example.py | 157 + Content/Examples/Python/instances_example.py | 43 + .../Python/landscape_input_example.py | 112 + Content/Examples/Python/outputs_example.py | 90 + Content/Examples/Python/pdg_example.py | 117 + .../Examples/Python/process_hda_example.py | 172 + .../Examples/Python/ramp_parameter_example.py | 128 + .../Examples/Python/start_session_example.py | 19 + .../Examples/Python/world_input_example.py | 163 + Content/Examples/hda/copy_to_curve.1.0.hda | Bin 0 -> 13104 bytes Content/Examples/hda/copy_to_curve_1_0.uasset | Bin 0 -> 15311 bytes .../hda/hilly_landscape_erode.1.0.hda | Bin 0 -> 53011 bytes .../hda/hilly_landscape_erode_1_0.uasset | Bin 0 -> 55274 bytes Content/Examples/hda/pdg_pighead_grid.2.0.hda | Bin 0 -> 35985 bytes .../Examples/hda/pdg_pighead_grid_2_0.uasset | Bin 0 -> 38213 bytes .../Examples/hda/pig_head_subdivider_v01.hda | Bin 0 -> 43159 bytes .../hda/pig_head_subdivider_v01.uasset | Bin 0 -> 45412 bytes Content/Examples/hda/ramp_example.1.0.hda | Bin 0 -> 9695 bytes Content/Examples/hda/ramp_example_1_0.uasset | Bin 0 -> 11894 bytes Content/Examples/hda/subnet_test.2.0.hda | Bin 0 -> 4997 bytes Content/Examples/hda/subnet_test_2_0.uasset | Bin 0 -> 7189 bytes Content/Python/HoudiniEngineV2/__init__.py | 0 .../Python/HoudiniEngineV2/asyncprocessor.py | 530 + Content/Python/__init__.py | 0 HoudiniEngine.uplugin | 4 +- LICENSE.md | 186 +- README.md | 212 +- Source/HoudiniEngine/HoudiniEngine.Build.cs | 6 +- Source/HoudiniEngine/Private/HBSPOps.cpp | 2966 ++-- Source/HoudiniEngine/Private/HBSPOps.h | 362 +- Source/HoudiniEngine/Private/HCsgUtils.cpp | 2922 +-- Source/HoudiniEngine/Private/HCsgUtils.h | 556 +- .../HoudiniEngine/Private/HoudiniEngine.cpp | 2566 +-- Source/HoudiniEngine/Private/HoudiniEngine.h | 686 +- .../Private/HoudiniEngineManager.cpp | 3417 ++-- .../Private/HoudiniEngineManager.h | 362 +- .../Private/HoudiniEngineOutputStats.cpp | 118 +- .../Private/HoudiniEngineOutputStats.h | 142 +- .../Private/HoudiniEnginePrivatePCH.h | 807 +- .../Private/HoudiniEngineScheduler.cpp | 1250 +- .../Private/HoudiniEngineScheduler.h | 238 +- .../Private/HoudiniEngineString.cpp | 428 +- .../Private/HoudiniEngineString.h | 148 +- .../Private/HoudiniEngineTask.cpp | 96 +- .../HoudiniEngine/Private/HoudiniEngineTask.h | 200 +- .../Private/HoudiniEngineTaskInfo.cpp | 92 +- .../Private/HoudiniEngineTaskInfo.h | 156 +- .../Private/HoudiniEngineUtils.cpp | 10624 +++++------ .../Private/HoudiniEngineUtils.h | 1334 +- .../Private/HoudiniGeoImportCommandlet.cpp | 1550 +- .../Private/HoudiniGeoImportCommandlet.h | 304 +- .../Private/HoudiniGeoImporter.cpp | 1790 +- .../Private/HoudiniGeoImporter.h | 242 +- .../Private/HoudiniHandleTranslator.cpp | 738 +- .../Private/HoudiniHandleTranslator.h | 108 +- .../Private/HoudiniInputTranslator.cpp | 6090 +++---- .../Private/HoudiniInputTranslator.h | 428 +- .../Private/HoudiniInstanceTranslator.cpp | 6561 +++---- .../Private/HoudiniInstanceTranslator.h | 786 +- .../Private/HoudiniLandscapeTranslator.cpp | 8355 +++++---- .../Private/HoudiniLandscapeTranslator.h | 872 +- .../Private/HoudiniMaterialTranslator.cpp | 6826 +++---- .../Private/HoudiniMaterialTranslator.h | 458 +- .../Private/HoudiniMeshTranslator.cpp | 13462 +++++++------- .../Private/HoudiniMeshTranslator.h | 838 +- .../Private/HoudiniOutputTranslator.cpp | 4293 ++--- .../Private/HoudiniOutputTranslator.h | 206 +- .../Private/HoudiniPDGImporterMessages.cpp | 212 +- .../Private/HoudiniPDGImporterMessages.h | 374 +- .../Private/HoudiniPDGManager.cpp | 4513 ++--- .../HoudiniEngine/Private/HoudiniPDGManager.h | 428 +- .../Private/HoudiniPDGTranslator.cpp | 1016 +- .../Private/HoudiniPDGTranslator.h | 168 +- .../Private/HoudiniPackageParams.cpp | 874 +- .../Private/HoudiniPackageParams.h | 480 +- .../Private/HoudiniParameterTranslator.cpp | 6518 +++---- .../Private/HoudiniParameterTranslator.h | 307 +- .../Private/HoudiniSplineTranslator.cpp | 3426 ++-- .../Private/HoudiniSplineTranslator.h | 222 +- .../Private/HoudiniStringResolver.cpp | 438 +- .../Private/HoudiniStringResolver.h | 222 +- .../Private/SAssetSelectionWidget.cpp | 316 +- .../Private/SAssetSelectionWidget.h | 198 +- .../Private/UnrealBrushTranslator.cpp | 896 +- .../Private/UnrealBrushTranslator.h | 98 +- .../Private/UnrealFoliageTypeTranslator.cpp | 578 +- .../Private/UnrealFoliageTypeTranslator.h | 132 +- .../Private/UnrealInstanceTranslator.cpp | 422 +- .../Private/UnrealInstanceTranslator.h | 96 +- .../Private/UnrealLandscapeTranslator.cpp | 4190 ++--- .../Private/UnrealLandscapeTranslator.h | 490 +- .../Private/UnrealMeshTranslator.cpp | 8516 ++++----- .../Private/UnrealMeshTranslator.h | 350 +- .../Private/UnrealSplineTranslator.cpp | 248 +- .../Private/UnrealSplineTranslator.h | 76 +- Source/HoudiniEngine/Public/HAPI/HAPI.h | 30 + .../HoudiniEngine/Public/HAPI/HAPI_Common.h | 11 + .../HoudiniEngine/Public/HAPI/HAPI_Helpers.h | 7 + .../HoudiniEngine/Public/HAPI/HAPI_Version.h | 4 +- .../HoudiniEngineEditor.Build.cs | 238 +- .../Private/AssetTypeActions_HoudiniAsset.cpp | 916 +- .../Private/AssetTypeActions_HoudiniAsset.h | 168 +- .../Private/HoudiniAssetActorFactory.cpp | 232 +- .../Private/HoudiniAssetActorFactory.h | 116 +- .../Private/HoudiniAssetBroker.cpp | 142 +- .../Private/HoudiniAssetBroker.h | 98 +- .../Private/HoudiniAssetComponentDetails.cpp | 1184 +- .../Private/HoudiniAssetComponentDetails.h | 180 +- .../Private/HoudiniAssetFactory.cpp | 416 +- .../Private/HoudiniAssetFactory.h | 142 +- .../Private/HoudiniEngineBakeUtils.cpp | 11236 ++++++------ .../Private/HoudiniEngineBakeUtils.h | 1402 +- .../Private/HoudiniEngineCommands.cpp | 3573 ++-- .../Private/HoudiniEngineCommands.h | 568 +- .../Private/HoudiniEngineDetails.cpp | 3880 ++-- .../Private/HoudiniEngineDetails.h | 244 +- .../Private/HoudiniEngineEditor.cpp | 3086 ++-- .../Private/HoudiniEngineEditor.h | 698 +- .../Private/HoudiniEngineEditorPrivatePCH.h | 296 +- .../Private/HoudiniEngineEditorUtils.cpp | 1318 +- .../Private/HoudiniEngineEditorUtils.h | 172 +- .../Private/HoudiniEngineStyle.cpp | 620 +- .../Private/HoudiniEngineStyle.h | 82 +- .../Private/HoudiniGeoFactory.cpp | 762 +- .../Private/HoudiniGeoFactory.h | 166 +- .../HoudiniHandleComponentVisualizer.cpp | 518 +- .../HoudiniHandleComponentVisualizer.h | 226 +- .../Private/HoudiniHandleDetails.cpp | 792 +- .../Private/HoudiniHandleDetails.h | 86 +- .../Private/HoudiniInputDetails.cpp | 10046 +++++------ .../Private/HoudiniInputDetails.h | 334 +- .../Private/HoudiniOutputDetails.cpp | 6909 ++++---- .../Private/HoudiniOutputDetails.h | 442 +- .../Private/HoudiniPDGDetails.cpp | 5270 +++--- .../Private/HoudiniPDGDetails.h | 280 +- .../Private/HoudiniParameterDetails.cpp | 14797 ++++++++-------- .../Private/HoudiniParameterDetails.h | 980 +- .../Private/HoudiniPublicAPI.cpp | 261 + .../Private/HoudiniPublicAPIAssetWrapper.cpp | 4145 +++++ .../Private/HoudiniPublicAPIBlueprintLib.cpp | 40 + .../Private/HoudiniPublicAPIInputTypes.cpp | 856 + .../Private/HoudiniPublicAPIObjectBase.cpp | 79 + .../Private/HoudiniPublicAPIOutputTypes.cpp | 49 + .../HoudiniPublicAPIProcessHDANode.cpp | 314 + .../Private/HoudiniRuntimeSettingsDetails.cpp | 646 +- .../Private/HoudiniRuntimeSettingsDetails.h | 142 +- .../HoudiniSplineComponentVisualizer.cpp | 2042 +-- .../HoudiniSplineComponentVisualizer.h | 368 +- .../Private/HoudiniTool.cpp | 52 +- .../HoudiniEngineEditor/Private/HoudiniTool.h | 110 +- .../Private/SNewFilePathPicker.cpp | 704 +- .../Private/SNewFilePathPicker.h | 300 +- .../Private/Tests/HoudiniEditorTestUtils.cpp | 14 +- .../Public/HoudiniPublicAPI.h | 214 + .../Public/HoudiniPublicAPIAssetWrapper.h | 1557 ++ .../Public/HoudiniPublicAPIBlueprintLib.h | 48 + .../Public/HoudiniPublicAPIInputTypes.h | 552 + .../Public/HoudiniPublicAPIObjectBase.h | 110 + .../Public/HoudiniPublicAPIOutputTypes.h | 71 + .../Public/HoudiniPublicAPIProcessHDANode.h | 286 + .../Public/IHoudiniEngineEditor.h | 142 +- .../HoudiniEngineRuntime.Build.cs | 184 +- .../Private/HoudiniAsset.cpp | 400 +- .../Private/HoudiniAsset.h | 206 +- .../Private/HoudiniAssetActor.cpp | 292 +- .../Private/HoudiniAssetActor.h | 154 +- .../HoudiniAssetBlueprintComponent.cpp | 4754 ++--- .../Private/HoudiniAssetBlueprintComponent.h | 702 +- .../Private/HoudiniAssetComponent.cpp | 5748 +++--- .../Private/HoudiniAssetComponent.h | 1529 +- .../Private/HoudiniAssetStateTypes.h | 92 + .../Private/HoudiniCompatibilityHelpers.cpp | 3602 ++-- .../Private/HoudiniCompatibilityHelpers.h | 2194 +-- .../HoudiniEngineCopyPropertiesInterface.cpp | 64 +- .../HoudiniEngineCopyPropertiesInterface.h | 100 +- .../Private/HoudiniEngineRuntime.cpp | 646 +- .../Private/HoudiniEngineRuntime.h | 212 +- .../Private/HoudiniEngineRuntimePrivatePCH.h | 546 +- .../Private/HoudiniEngineRuntimeUtils.cpp | 1320 +- .../Private/HoudiniEngineRuntimeUtils.h | 710 +- .../Private/HoudiniGenericAttribute.cpp | 3004 ++-- .../Private/HoudiniGenericAttribute.h | 310 +- .../Private/HoudiniGeoPartObject.cpp | 368 +- .../Private/HoudiniGeoPartObject.h | 840 +- .../Private/HoudiniHandleComponent.cpp | 510 +- .../Private/HoudiniHandleComponent.h | 270 +- .../Private/HoudiniInput.cpp | 5206 +++--- .../Private/HoudiniInput.h | 1113 +- .../Private/HoudiniInputObject.cpp | 3728 ++-- .../Private/HoudiniInputObject.h | 1722 +- .../Private/HoudiniInputTypes.h | 134 +- .../HoudiniInstancedActorComponent.cpp | 494 +- .../Private/HoudiniInstancedActorComponent.h | 182 +- .../HoudiniMeshSplitInstancerComponent.cpp | 492 +- .../HoudiniMeshSplitInstancerComponent.h | 170 +- .../Private/HoudiniOutput.cpp | 1832 +- .../Private/HoudiniOutput.h | 1225 +- .../Private/HoudiniPDGAssetLink.cpp | 3737 ++-- .../Private/HoudiniPDGAssetLink.h | 1747 +- .../Private/HoudiniParameter.cpp | 516 +- .../Private/HoudiniParameter.h | 650 +- .../Private/HoudiniParameterButton.cpp | 100 +- .../Private/HoudiniParameterButton.h | 86 +- .../Private/HoudiniParameterButtonStrip.cpp | 146 +- .../Private/HoudiniParameterButtonStrip.h | 130 +- .../Private/HoudiniParameterChoice.cpp | 518 +- .../Private/HoudiniParameterChoice.h | 248 +- .../Private/HoudiniParameterColor.cpp | 166 +- .../Private/HoudiniParameterColor.h | 144 +- .../Private/HoudiniParameterFile.cpp | 202 +- .../Private/HoudiniParameterFile.h | 154 +- .../Private/HoudiniParameterFloat.cpp | 327 +- .../Private/HoudiniParameterFloat.h | 329 +- .../Private/HoudiniParameterFolder.cpp | 104 +- .../Private/HoudiniParameterFolder.h | 218 +- .../Private/HoudiniParameterFolderList.cpp | 216 +- .../Private/HoudiniParameterFolderList.h | 160 +- .../Private/HoudiniParameterInt.cpp | 247 +- .../Private/HoudiniParameterInt.h | 261 +- .../Private/HoudiniParameterLabel.cpp | 124 +- .../Private/HoudiniParameterLabel.h | 110 +- .../Private/HoudiniParameterMultiParm.cpp | 298 +- .../Private/HoudiniParameterMultiParm.h | 255 +- .../Private/HoudiniParameterOperatorPath.cpp | 190 +- .../Private/HoudiniParameterOperatorPath.h | 114 +- .../Private/HoudiniParameterRamp.cpp | 1668 +- .../Private/HoudiniParameterRamp.h | 640 +- .../Private/HoudiniParameterSeparator.cpp | 102 +- .../Private/HoudiniParameterSeparator.h | 86 +- .../Private/HoudiniParameterString.cpp | 270 +- .../Private/HoudiniParameterString.h | 180 +- .../Private/HoudiniParameterToggle.cpp | 190 +- .../Private/HoudiniParameterToggle.h | 134 +- .../HoudiniPluginSerializationVersion.cpp | 70 +- .../HoudiniPluginSerializationVersion.h | 186 +- .../Private/HoudiniRuntimeSettings.cpp | 810 +- .../Private/HoudiniRuntimeSettings.h | 994 +- .../Private/HoudiniSplineComponent.cpp | 1108 +- .../Private/HoudiniSplineComponent.h | 552 +- .../Private/HoudiniStaticMesh.cpp | 686 +- .../Private/HoudiniStaticMesh.h | 468 +- .../Private/HoudiniStaticMeshComponent.cpp | 426 +- .../Private/HoudiniStaticMeshComponent.h | 194 +- .../Private/HoudiniStaticMeshSceneProxy.cpp | 1082 +- .../Private/HoudiniStaticMeshSceneProxy.h | 334 +- .../Private/IHoudiniAssetStateEvents.cpp | 38 + .../Private/IHoudiniAssetStateEvents.h | 57 + 254 files changed, 143981 insertions(+), 130222 deletions(-) create mode 100644 Content/Examples/EUW/EUW_APIExample.uasset create mode 100644 Content/Examples/Maps/LandscapeInputExample.umap create mode 100644 Content/Examples/Maps/LandscapeInputExample_BuiltData.uasset create mode 100644 Content/Examples/Python/asset_input_example.py create mode 100644 Content/Examples/Python/bake_all_outputs_example.py create mode 100644 Content/Examples/Python/bake_output_object_example.py create mode 100644 Content/Examples/Python/curve_input_example.py create mode 100644 Content/Examples/Python/geo_input_example.py create mode 100644 Content/Examples/Python/instances_example.py create mode 100644 Content/Examples/Python/landscape_input_example.py create mode 100644 Content/Examples/Python/outputs_example.py create mode 100644 Content/Examples/Python/pdg_example.py create mode 100644 Content/Examples/Python/process_hda_example.py create mode 100644 Content/Examples/Python/ramp_parameter_example.py create mode 100644 Content/Examples/Python/start_session_example.py create mode 100644 Content/Examples/Python/world_input_example.py create mode 100644 Content/Examples/hda/copy_to_curve.1.0.hda create mode 100644 Content/Examples/hda/copy_to_curve_1_0.uasset create mode 100644 Content/Examples/hda/hilly_landscape_erode.1.0.hda create mode 100644 Content/Examples/hda/hilly_landscape_erode_1_0.uasset create mode 100644 Content/Examples/hda/pdg_pighead_grid.2.0.hda create mode 100644 Content/Examples/hda/pdg_pighead_grid_2_0.uasset create mode 100644 Content/Examples/hda/pig_head_subdivider_v01.hda create mode 100644 Content/Examples/hda/pig_head_subdivider_v01.uasset create mode 100644 Content/Examples/hda/ramp_example.1.0.hda create mode 100644 Content/Examples/hda/ramp_example_1_0.uasset create mode 100644 Content/Examples/hda/subnet_test.2.0.hda create mode 100644 Content/Examples/hda/subnet_test_2_0.uasset create mode 100644 Content/Python/HoudiniEngineV2/__init__.py create mode 100644 Content/Python/HoudiniEngineV2/asyncprocessor.py create mode 100644 Content/Python/__init__.py create mode 100644 Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp create mode 100644 Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp create mode 100644 Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp create mode 100644 Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp create mode 100644 Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp create mode 100644 Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp create mode 100644 Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp create mode 100644 Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h create mode 100644 Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h create mode 100644 Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h create mode 100644 Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h create mode 100644 Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h create mode 100644 Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h create mode 100644 Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h create mode 100644 Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h create mode 100644 Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp create mode 100644 Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h diff --git a/Content/Examples/EUW/EUW_APIExample.uasset b/Content/Examples/EUW/EUW_APIExample.uasset new file mode 100644 index 0000000000000000000000000000000000000000..cb8db8dfe0c6b7e83fab75d83059d7a2cfdfaffa GIT binary patch literal 794732 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%0AiGLehF*SouNX%a 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

    72T7Tvll>{*SuUgucjOBJ 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
    =`YypxLn;7?^@(42krQv-b(Ah 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

    KK+ z{lCClj<}bymhht;)EA;3%EgB8k8-bXh`OhBv4?ZvU7)Nso51-HKd?9tcU9X2&qL?b z4}$&xAlxO!gTf=Mf6w~9whs>SM@BBKHiv%yC%$>j8#S44 zj!kKyaL>wKkv)iY!*Z^1mZP7ixq5hRF|K=h-u3N*_bsA3?!o(^Z?-%K-mU(lN;;ek zr4X$Ooa?X)mEkNcr|L=cYir_plHpt?rx`oYyS)A?&V$rzF(090l`-a0cu(-kQ!{u| zRku|d-Vszu5$oLrs>F(ThYYfVMV*5esRH>ix?lS+Ao9HxJ-zT}^tS$8;X$}HGo<*< zy8lbR%$6AZVSSiC{JBupzt~Fgv+3{MsY#Ce$((btH)T!0J}h*8?M$Gb9q!E@1K$0f zF#ig8?|DCx#C=B;(b2QutsqrZMcvZ@B5N1kFQs*wxVMXw^L23^>M4I1wEt`5SYr&l zzi_`H?mP2%{bmu`UukLz|M$nr#-RPX&pEdn+_I`!HaN#o1x>_xoTd8dYBU~uC*r$X zXpTUCbdEMKa235x_HQ4!yAuUm1@2hmY2hvOcVkmgYxwt9Nve9E@ogk5Pgn=c$dl#^+D)&a%1BsG(uSz-0xY+9pfnc zgA~aW^-4Ze#+f3&?IN#T`rwaID_h&pwyJE2^X>w*rzQOVKaK-L5m28 zd#@Fo`_G}_`jJ2{+Tla|Kk@KyCrclNYM zK{iNZ^paX)Oh=!puNo)NirUiPKDa7glQ!_zz&rRZ8l%OSBL5*;Q%qskBDMBrKeRkv zm2K!F^*ys9?Ql`uVRlCkQY~2ciMFYsRy_Rs)f}rU`c&;094E=zc|I0^z8nYpe^)t?%*dU2{o47eS#lbl)B_vJMMK#m%gZJRY8SAse zI9igGmYYMn{+hEgU(|(<%O6u9>W2O9-0n`Jeb>55yrM2#M^9=0`?Pa^?_}v8*>3CY zTP815?ZmmSRyJ#P@+eoRi*-BeNn>rMVaI>=CFfzgEln=eTUd76V0p8C75$t%hY!n6mu422*)TUT_(Pw>0|Jwh$VHLs(D~kL7wCXFd z18ZX12jBmzlXx|0XA1K@<+|guTAVx6MrPE)4rXQU$#$_`G{|X{m%;own0L?Fit!X* z(8n$6!gq6b^-hBKd(RmENO-S$f09ZmcH$VHk|%AaUSov17v4rvxV91A&C*P51H9G9 z^V|jROY$82W_IF7su*SAjHWv7a$E`9`<{?;oV3T_4;t;X)CM)=prpH z(5bfF_ME;qP^0A8{Qd1+g8d&6(LKC6>tp@s>M^ggK6Xne6|DciDCLGD2kr*xIhlRn z4v_Wu2<{5`FFI4`=Wy2t?j(5YdCK|3yQl-b9%;U0x4q@-CKtjx)&I6)hqF-n1pCkL z$@%a)yw!R2=T~?K$m6m9cJ8@DjrnMtGQp7Hd>PL3=sx8&b3B~!>K~>A=WCpU?xQ!< zkyd9oJ=&Mn*XUR+HP8UwyV|V4C3LD@P`DWGWrm~Z{W{O)KWZNo%xmjs#{)~pGUztwC1EHrvwSb!$hg8R+v`g}^W9 zLakAtIowyc=h=&z`rm;PxOWgWehnRJ?MPkplm&s=cd5GrpH4EJ|1nZ%$f# zT61_^*>|%#u%69uW;hF3$NqFr_B>)f_wu&!xAOAu_H%-?NN zsx}8+vH-R2@OF@+bT_<<QXk}we;ZyXWW544F>-gTRQ&~@$&ja$;XP+A$=(6P!iBOLCJl&_3#=qaVNxgE8uhFKZ@Y_gvFqHWYI z><4Kt^}d-yJM2~ytXb$Iwa_XBcQVnlg=iz9!6u>OwI8gD=vl2GP?vU4_1S@g=sKzj zJ%_%UdpI|P_3Qb9lLbTA*ADSScuO;`OMHj?b?N6r{(QNyShplaxes@eTvKg9Kd+YW zszvZ-D($sz(f5=s+H-I}RVwJ$&^Brt{YAK!VYPlouc%k_q40-sEAkJTrnNULxFzi! zV-#9P`^@+pZNY6pDY!dm)y-nGi)NVPg4*3Ygm%)#nc<=xwB=??v@y{n3(-RDC-V;K z(Mnszv`2bFwQ1ubB!`KRW7T~G0Tw0_om*|D#JPdcl{iyDBX+~17&RQD94R5 zg=K6%D~0ANw5|F%{-QFrd-y+Mip$t0lU?}%`m8U`-Zr@Y*A24=^Z(_ji0ZSj10Tn9 z58D4`iPwYY|4u2tI`%Pdeo31UwEsn!-)2j!j~#RR=4CT~Zsa|6ig!|^g4eMB*kAVx z-qG;Z^3L-2hqtq@oP1L*O?T2A{haO^=&lIwTb@6?De(U2tt}0LH^x6ruByOA_Uu`B7t^ob!&_ZmsGWv)lhW^-LTf6 zUsF9K1x}MF`VHt+^1gbN@ZWQr+NRF4`H$KM1o@9u-dL?J{k}Ioq2^oI|02h0Y5TGN zEwid*{|$GsGv3)e-)?)so$0ym3gI1>FMQX$rEK$kJ0xndF@CEn`S8w=2B;6=jg-4; zci9REshk7nqL-CeGZ#)5uO1vg$Eu|)!Fxv4t+H^|;C!IMbb<=dhpjO!mwx2;tp{j{afr zX8SA49pKfazDhW}N2Sly1Mph-L%)D`Df`zA%-_enZoC5C#_%quf8WFYwGQ&yO)-D7 z)IMf+cn7H8n0L^voa=_d{Zwseg~FRgw%kIriMG*7fOCX)%X$g@h&aD5&|94EYQgK$ zw+E)7)5sc&fV;9Ww{RtT%4k^BAO0QYlcK8d*NV!k+Ao~*TjJP+AvHM1rrmMWhWF*{ z#aR#N=N--y`9Ei|&U#+(RC0#ce(}EOpXn(@{$h-L&0m_j3>}n3%-?O&I8}z%Ave}~ z!aGX-Mk|2#8@WV(iq=pz>fgZKUWqnVigimFY?$zV!>dPY(F$s!83S*j`jR;T9j1P2 z{)ui>%UHePzr*?M2eg{j(rOLAME37Vw4=7h%7J?VRU!wY+q7zd{pcT5%S(j6L|+}4 zh;G%R3q#@WXna)oG1|-cpzsi?r|omBVq7=L-jOv8-Us<_=J#b>k90S856ug)Eg;)r zjH?uR47;UnzS7*g-j{FD&l994$}sx*7wL|AMJ;2iLwx%JO{~WUwPJYBk-v5eji#E{ z^k9E~q5I)}L#b$7KtJNuheEi|D^rcxXgT#O;|5xwmNgZ)JE@jA6`e?S_Z4(Ac3*@4 zviiF@2d${ZSP#%z-2Qf;9ZIyW)|Y5s&UMM~&!cK^FZ87Lg!`c~whX;#peF4wRo@br zgFfV3prU2+zRJDDzV>FpHw6W`cIx;?d9ON4+3xxN^!N9a#$WfmyoP@MM;fn;r=Q=K zhpP9KGPWGb233dmBjs&vCF)Rb2K#%GYU$hHzN${wHMnDm2H1nj?1qZqZmo4R_Mf>lT#pDBcj8&ppCh!C%()rKhofi4;nH z)E?=99KpLsA<6``vaN+wsIJti*uIrq+C04~`OcqeZyQmzO|q(wH=~Gu{zNafqB#HN z7>Ch4%KJu(KsEd><;|<;E9x-wjlyd9#~zxQMb&J@S_A7I`aM--Un>Ua8DEUOU2y#` z4!cuP#QzmVRjNK4JFqOKS1|v-L*j#^Z7i96QvPxrzz&Ahnx2shcbm*x*#>s7KlLQ? zu>WO!q2e+-YEG6 z?Ok{$%MY|UMpgXYZ|d*E+fgaadKtz0tz(SKXm#~(V{jk}&cW;ps}b*g*IbXLX^Pbm z&feM>D;&;!tPdkmgK9G6YFD#WF#f_GKAZoAf6xDah8KrtVE>Ki(lKqZ|Igw7kHP*g z!S9g(cSG|3TEgwi{yFCo+-ve$IZGATZAq@$?g#EN)H68gedw(~ott8*o)m6d?O!FI zlPlQu7`Fj8^aA?})-K^fMQt zN7QSkR0zN3w7!S8zSiFw2k%&_WhKIShCmuU0LT|gm;E^nEib<+hNYX&!gq_X4t`M zwpRKFfdY89a^JBIm5ot_g>XM6?!PqL601SQy8p}nxs6Wc>sToeakL!sI__a?N9^J7 z)Y7TZN$d-H;L?l5xU?ZrLjI&_0N(=34ZA@g-B znnZON9qu};?}y-iNt=oNtH%0%$od&Qq&>BY;Qfbv?Oasm_U|4#R{tu{4(=;t#T-X_ z8>ehk^5??@ct$Ls@*~x z61B8NkFw2CzQZ3JW&2(+jplI2s_z?B;BBqmH%6c{)H>!h^r$-5YzB8J?V7oZ^(%?$ z2$^sjTEKb_?MyaI1l*%(hwuZ# z>ln}Vy_KmaTSaLrRk8if>wY)oD&(P7(^{)lZF{I+(;D6?%5Ckj7DXOv1AQ^v^Qd}T z4EJ&6SN%F#TJ;-m!S7XHGpfVeL;cj~iB3}=8e4f_ez)o|%ftP%`m)&$4b@hgYta<# zs%e8?)hb)<&{oX*E$BezLv`9=hBnlC6a7S6iXX6=?Yedf|4=oXLyrnfMn~wq0=Lnt z`r$wq+M$7N1gfDU^QPryVz)ZFrWY*EE<^sxO;5}GFzPd0_uqC^CSK(=`Bh(}ZIHZK z`4#S1WwY8tscPGxG}KnWEvcKe=i%L;R?+XE;aVSkFx-l^Q-6SV)5;h_;U23|zXDyX zO)`eSzfId`{BBmWeWm?nNN}IS?hiq)X>H6+=v{4;`5XE>S;6`6-_wqoFQLC^znbgO z8(IbHH}oR=Ac=N3p=Da5&@Wi`zeTrejje3jVYAlDnt|@tCR^p;|54j%HAl{cH zv%ZVA|2hA53vCp79sB=NWW%UP{&P?CftdB!|0!|R6I-w@}1@GOUX>@LpzR6 zYnPG5x;QE0an=Rwe|&b&+|JnlHo4`U-LU_=^S>=vh(GD&f+&yWWgq08>J9T(w*BGh z;a@Bz+XniMNWaM9z+6okq#B%q`>C_E66!g%&_?MEi1+?c8(=i1zC{oHd9xYw>PJ1_ zYEC`t*2YIx3+jCBGYSGNZOhdH{70>9rM1OogThueORHqvE^KX^u64537PYoz>Zh%y z#ciI2Z;Q6K53c`O*qMq=vH$fs|IfefMhe`F0QHA1zTk-xIs*bDc3&JB$N zEo@Jyl6wJekq!AOyf2Ye8dKDYI;U4UNdUQxkJ?#Gr2_Z?9F%_#*esn~_T|)iedT{4wU&%QE_nf?j&ikB`elLh~ zpJbgp=2`AN>8)vd&zCPbq(_ah5O)W{Ah)|$ERpBNJP3vUCT-IZ*R^Eli)q2 zlr?6+tEf|q=2lbNyK0zu5#B%5iOho*=4 z|HB*&(_Y5@@63EJYckxg=PTrgbFOxmxbs}mwlBSneW~6I+kF2T=_G}K7h(V3lnvrF zwy26)Y`Z4EqfOB25{K&5N5Xqd`C5OS@$9BL*m!uSsuzs<@cyi}Gf&aaN!oGF!_A3j zOR;{3dzjY28V~n}_`j3kJ;y$N4;rOM27K_gB#L}Gx|FEUzu+#57pxz=ld=Dg(3WOi z(Z-V2ww_jc@#Z@J%m2BJLCtrhk}qN&$AZwfp0OhCp?YeEl)tfu*D}A$cpdKL&gpsm zvO}rsztvshOynJ=OFq?;&%1*mawWe?J%+cGB)Jg3@89az1ezsL6{Se4k6)uiPuCmT zep629k?_`6TNokml9y>bU>@IARdXo33EDFA0sS1%%2=zlF^c%Qw5pDA@tl9NduOg?elK>Kd8IQd@-9_t_l=ws-ete! z3v;>2+bAnv;NjXw(kse%so3_m{Fxe~)U%aWhG@&x2DSmp6K$T>h&l|z^-g*d_USkE zT1HdiG@BZCjb`|*)*Cy_=EQB3GmGI3=RS8e>sPk6#*BowuJ+I*aufbk>k2x7b4z3R zSMfT~F6Q@FRLONQ&hN1gT7%ZqA6hlwZb{Z?7y5mcJ|M6Oy{YdBRE58bkySVm4KqJ2 zOk&(eo7)RZ(C^b64vqEve)hJ^?#%1j&Zc=U*D7a=_T;*DX4T|<&1>Goe7CKZv`EVJ zn7pG?QF*~%V(TI+YLQ%@x->}|wh({hY%N4gRq+hXLta;CR7^LB?) zB-O{>992J)8;SXA zU_N?-Xs#Hz?fQAvx0d8#R58oK>(C3$Le$ABFWSMczh~~ST5(_TIrE_v@jKT{H~fCR zv^5Yd(5qP+(R4k{dJt%Bi`6}rfqje6ORULgh~D1%5w#IzQcQb<>hr9fXg*m6X>hmI z{~&JUf9L=IYY4B{{^$JLC$w(pP3-@jNIB{$^FxjPJZ1~y<(;^c#I{Mq+tj?8EMfoW zq^N1bu>UL4`ew+iiyvgfX5YmA7iYho+aLQsJU1p-C-rXr?Sc*NQnp#Vu2k0>Zd>kN z>aFU}=DoL}*#Dlk^}Hr|Ltba|O9@IB^_tBtzp0MVBJ8W=URqB*-R@Q%Y0V7DzFrxn z8)lI`L4B-8TMg_Z)d9p&G_#*){R|JZw#RGr@E^6aH_^74F@+uNli4S2EbMH*r1h{~ zDC%k-u7A%8*v z*Zt+Ohr4WZq(MrCdfV1e{!V>K3%B2pXJ~EoRQq5hORsOZ?Y}77bjvjCom9=JX_eT& zP`BYPYG{wr^37U-X7)PV2dxjZvX9UrEVHn!{S%@h&lh&!VsMr1lYWbM;m-C8 zdb_~#;;#1f#-9PDcK2uUKlg9^|AFx2@Dl95Gdd!s5%#}VLfNG9tP_hyNmsz z(YkP1?XLE6mQ>WgQ zdE@g++xob7y6zSfk|%t_d&%>b?Xmwgse^y7?S>SkT_5ImvwiDp=^N+%ko&oVQgh!q{Bxa@)zV}3tv{%Blqmb#$|CJ|)nPBC zn)(?n-`+?48o!5RKZqS%VVL$x+96|>S!|ay*(`#$GyC;jjOz(nDN718v9F~{%x1>- zDdw%Gu%-Pe@eW%HTidhs!|eCkGOnLkM~d3nXX;YG1^2gPOOL1DQ|Rw2^!r#Nv9K}x z5$3GIlki_K^`bXRy4ibJKNTgz-Q8M?I&M z`)gXfZI4HH&Cedh`-_tAjPq056rwwR@!TR`u}Hb$54GQu=c~iz1p82>kM=~#w*N%c ze@pY&11h)Sx@MoHUe^!k0s95Du`$Ugu}5m_j3#D1yGtu;R)n`X_MaCT?EN|Ko?spO z|Ju9H=%~uI;o~!qO-ZsfKsJzMCWJsBf%G~vd*8dvOeg}%1uvf7pJ~dpCZSf2%ljka~Z$voU(S`WpXr&ZE)VQvbirsne{v%=7%z`mNks zq@3O2ZUvV#H++rD&64YKuak3}r4`5W_sDzax`eJQ{YLgNz7fu;Oz}S-ZfGtFHSk9j6yOTf1 zT5R`rx(LlX_Bp4UzlW@MSG(Q)*UP;3eQpo`bn9;C1NTb*GV6eo@AdTWuzqtUN`L;c zmFd3h_3@vwl-o@Dw>oN|J6ZboJT=Wd5$*5qrXF=W#0L1MsrTIjv4Q^QRJ!y7SNVhX zaBrIQdo%5WG7oT#|0}sQZFhW--(S=te@IgqmzDM@&TQfNj#qqL)}z&W>8C!ZTvhmm z>_f^6A1X`ncZtl41gly|e2pY&=N};XKW?@0FO>X$&}#2L zD65%mR7Yv2+4erw*pFO3%jFh}LS#F#7F?NlAgZj~pNcZr|Mowl=X&-}{e9_dBeqnQV`<%Z?{@;@` zHuv{@@MLa&>)+oaF|75Rf{BveD9xvm6z4(`s$7No4dr49GWfd8+k7!dxO4Tr5 zsA786(onYloyxaE$HSo-1LYR6_NH00eR#a&!(VFFjtsThsYwZI<@vIe{dz(x^DcFX zbBXqVsoCE-Ax-vs+Bdj8wKD4?*-v|eHrTq%x!RkmEwUbyb$~hAn^r>^7cbNDrM@(a zt=A5!qcQ^6A}#+vfBY-{yyRz+u9E!UE;Xs)RLTGQ(*|6;|8G)ejiv`A|If~@n{!C= zV7SG`+zN^N&b-9@nB>7_`AZ6mCI5FU$|~tzs`;kLtjE0aY}rS2Lq(s;xxS?pW2$1I z$^JD}--Kp`r`I?b>J{;u{cA1`?>DVX!<$CpzCI95X}q z1t+LwTifl4uF`tSywT6@mD()perK|Gowm=a?beRos{Lxk-TR|6wKj6AlpUL|ty3Sk z7h(@+E$nvQ`1nKGdHaNyQ2p2O_x|(#-_?3!y^G}k(ngKb21viyE0~b=o0R*??32xF zOWf^SrstJN-15Hg{GAf_QavhE0NVTGvxHJYc)yO6M0Ko(9H?$&9M^i&V>C^t__^i32AaWUR^guyWFbn z%yJ8~sn%rYQmTcf^}c;{%ps<@1Y z7Hi||M`Ye(mDWO54Te>3&|1l^fxI?b|FZlK`TyAeHzehdF-!8`tiamLG|B(ZG)vAY zlKkH>FS}K##9dZ6qp+Kd|Id|1OP?rNEAIojsp7%%$NXub%Fxf157!9D>dwi~keWY) z2b<#}U)Ee3xzRGLjtOOEK>cFPOW15aq0Xr16H=^d`&0YtgqY;RkEC9YwLIr0H&mS?@shO%YyA?GVd`!D^a((bE6})p=y`R15D6XNqgQCyG=VOd9W-#TPw3~ z_vXasYm4pU-ud`4E!*iFy|sFccE&j?=qe8nPcd z{b1vr(%$Xtvsp9cc{ed{ddpj-owq9bHb1LX1Gxw8t&*BWx&D)tYbxF-t*)^kG$ORS zqFK$E;o34zdb(!k$XxSKxOKv*$lt7C=7fZyW>C$twj{h|ZdSw9y9q(7)V|FAXF_{x zgY<)jHcs|8>uxJ;g*DZ=-L

    vY+X^+d(@c#ru%s84JrTIQtPD*ka+(YteO6~F6UfwhBehBAMS4zZh1UY_MsKE&Y#=dmV55& zl%%&B>u+Dxuj1RH2WmVO>JiE>A0zGcqwwj<3pLwC+L`k1mV{%GxHT|xe?lkoP3vv* zrGzc!m1=|aRYFbcMKw&-(OO$2cCMYNb++c(pWDUSDC={3gKPwtYne`k+ezCZd2gZH zOFL>ElX2fQ+BZ_}y}c1yJ?Y=~dN*lhYJl4!I#KJTW=Q$puHCM-xiw-lwJmb`X_D0U zPi4N_A77y5*p!qn&ZMZgp(_p zCNzmW8~L{C)`SNmADex`k0%7oG1fzo6A8D|(;hU{)kr&%MTl#h3i`j3>{jy_A zr?wnWV{GW-iroCEHMfT!53Mh0nXoQW8F{s0X2MAGI&(niKti%rX{`;PNw`v0|6hzG zX^Smgy=yj=c6yzg`;+mpwqcL%n3bVh_=l zDxVW|MrseM66Y~zjP{rs;G{~td(>Udq?rd$B zoFa@!UfZIA?k(Q${d>N<+gmKZ&vt7`p1V(VbFYl9(&nlW?$+o!ZMobkmKA$gdtU8z z*Tyz$-^zS&ip2k)v!CpT@b$Zx|HmeV62FuD|Bd8-pY#W<8tqEkE93t|8Lcz>OCA`~ z)zU~!ssuqXN*Z8sWSm?Fz>$N6?E{Rl`^XpCu4>Si_y_0&$ zKFcE2DY=&Uz3iKQDtWOvRefaF)|2G4#j}pmqjIZg7k7w0)_P6m#i!}(Wjqn_?$_V5 zRyj|4oAh)!brFy5)kjHvIUPN$C)#^ufBZ3N@&EbbU-73UZ%Y~~`Cp|bHe4e4e^1)g zfrHW?PR~qi`uq6*q3i}ZZ%7{O+G1Po@9&XXl2vcW~l|?>|d|CHMc!?RY1}DvzTg2)q zGdW^5Q0LV0K*%&32%INQmvwKap|5?$|zks9oT7jvvu0o!#EC_+Q80?a%zbR-dY0CHcQ~ zqvmNhN&fFI>;FGXxi6Ice>EiTb}bv{wU)SZS|7>ZB5_||SXBH{aV_6tlO}TcSTH&ia?uVQ08ETyH9;iOzbH^xmqM z%n#4e7t4G{YU~01Lv>8fNj<7Z>>Mu=-=%N0XLt|A59n7pmqe?okLqpQ_oClazw?*n z|H7a7e{jk%V~phgv4O>z=VW|zce9V1H<0|FmiJ{|9f|wp0=xEip%3JGHgl`T%5^9u~6G=8(M>;2c5{X$y%=F}`=GWFiYgBSgYqso* z+nVfHY4$4nWb#;Rsl8C*l|3&q4rr|(v!*)--DLTs7dL{1Ou@eTS7k_U&xcj&L%JG__Ud-Xm}vuNAu7xen>Q_;_> zU)5h0o#)&9JN`w!KPUp1W+(kVD!3-CPGd{j`>LjqtQ4v5cjnG)(OlZOUD!E)S6+SJ z=ViYZ-&2@cb64f*@}8w*YOe@=U)8B%UfoB+^JQP$H;L0DP0V58uaa7u)z-O4x8%pn zQ&xLxVRAz`6_=%6O1{kcRsE{IO`d3tvya;i^oOjI_A^eQ{;Ji%=_c|1Q~LdbZZEyL zjCUg5wR$I2>2CFI)^AaR-PX}N^z~|i`*8F={g}*W7D?PG_64cm>-8>n#A_YjqOY*0 zcvIs$^_tG>-pBEM`rVEf?O%OJFO=OP|EzvpU+vx;n=kRVN&Q^^P`ZHO!OI&jOqBh- z&3|fgTHFcb=?GDam`tw#DC&BHbe{S`5u5pL!b=5j&pF2*^Q)iu~-c-GV)bnZHZ2fu_ zb3gOu>wlM1z+Ixt^*vJG_e9t0U#Ms0T*xL}-iGWx5qn%8Y!`XWrQCPg6TDlb-j_IU zdH;yNtbgcS868^vhW@oPG`d9U|FE1BO-@VuPHBB@PQFZpTwhebRqxDue3@lQg}&zP z6V_EeP`WSoK%MoW+bb_GdNlEY@CTui^3zEJB3|TFWxwR_BBRVhq1DNw

    1N$eYP; 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 z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY** z5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ z1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009IL zKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~ z0R#|0009ILKmY**5I_I{1peOw&rWN#!gukb#>LM)YW?xu6+MiLUo86LTlxIQ-~Vs> z{>DH5|E}oazxdV7%f5Qfulsz-&#mmR`Mb__nvY%Z@c-A3)Aq&{Ngn@O|Gh#$>%Zsp z$4{Y!*8fD{fAX{c>+9BSb$z}%&#b)s#HsJwth}oGhW|-$|IMF0{r8(1p8i0)?e`@4 EU%Z^Yu>b%7 literal 0 HcmV?d00001 diff --git a/Content/Examples/Maps/LandscapeInputExample_BuiltData.uasset b/Content/Examples/Maps/LandscapeInputExample_BuiltData.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c53a5a3105932ee766838df9d3f2fc8901d351b5 GIT binary patch literal 1134497 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 z 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ⅈiSN}W@>JP~Og`aQ#J?G8MeDA5C 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%0Dq0y9>{DN+VPnfuW3qqA6o6vhh;y=nC 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

    cULtatK~ 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 literal 0 HcmV?d00001 diff --git a/Content/Examples/Python/asset_input_example.py b/Content/Examples/Python/asset_input_example.py new file mode 100644 index 000000000..a9ac3f867 --- /dev/null +++ b/Content/Examples/Python/asset_input_example.py @@ -0,0 +1,143 @@ +""" An example script that uses the API to instantiate two HDAs. The first HDA +will be used as an input to the second HDA. For the second HDA we set 2 inputs: +an asset input (the first instantiated HDA) and a curve input (a helix). The +inputs are set during post instantiation (before the first cook). After the +first cook and output creation (post processing) the input structure is fetched +and logged. + +""" +import math + +import unreal + +_g_wrapper1 = None +_g_wrapper2 = None + + +def get_copy_curve_hda_path(): + return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' + + +def get_copy_curve_hda(): + return unreal.load_object(None, get_copy_curve_hda_path()) + + +def get_pig_head_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_pig_head_hda(): + return unreal.load_object(None, get_pig_head_hda_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Create a geo input + asset_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIAssetInput) + # Set the input objects/assets for this input + # asset_input.set_input_objects((_g_wrapper1.get_houdini_asset_actor().houdini_asset_component, )) + asset_input.set_input_objects((_g_wrapper1, )) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, asset_input) + # We can now discard the API input object + asset_input = None + + # Create a curve input + curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(10): + t = i / 10.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i * 10.0 + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Copy the input data to the HDA as node input 1 + in_wrapper.set_input_at_index(1, curve_input) + # We can now discard the API input object + curve_input = None + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPICurveInput): + print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) + print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): + print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) + print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) + print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) + print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) + print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper1, _g_wrapper2 + # instantiate the input HDA with auto-cook enabled + _g_wrapper1 = api.instantiate_asset(get_pig_head_hda(), unreal.Transform()) + + # instantiate the copy curve HDA + _g_wrapper2 = api.instantiate_asset(get_copy_curve_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper2.on_post_instantiation_delegate.add_callable(configure_inputs) + # Print the input state after the cook and output creation. + _g_wrapper2.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/bake_all_outputs_example.py b/Content/Examples/Python/bake_all_outputs_example.py new file mode 100644 index 000000000..dbf9c3875 --- /dev/null +++ b/Content/Examples/Python/bake_all_outputs_example.py @@ -0,0 +1,110 @@ +import unreal + +""" Example script for instantiating an asset, cooking it and baking all of +its outputs. + +""" + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def on_post_instantiation(in_wrapper): + print('on_post_instantiation') + # in_wrapper.on_post_instantiation_state_exited_delegate_delegate.remove_callable(on_post_instantiation) + + # Set parameter values for the next cook + # in_wrapper.set_bool_parameter_value('add_instances', True) + # in_wrapper.set_int_parameter_value('num_instances', 8) + in_wrapper.set_parameter_tuples({ + 'add_instances': unreal.HoudiniParameterTuple(bool_values=(True, )), + 'num_instances': unreal.HoudiniParameterTuple(int32_values=(8, )), + }) + + # Print all parameter values + param_tuples = in_wrapper.get_parameter_tuples() + print('parameter tuples: {}'.format(len(param_tuples) if param_tuples else 0)) + if param_tuples: + for param_tuple_name, param_tuple in param_tuples.items(): + print('parameter tuple name: {}'.format(param_tuple_name)) + print('\tbool_values: {}'.format(param_tuple.bool_values)) + print('\tfloat_values: {}'.format(param_tuple.float_values)) + print('\tint32_values: {}'.format(param_tuple.int32_values)) + print('\tstring_values: {}'.format(param_tuple.string_values)) + + # Force a cook/recook + in_wrapper.recook() + + +def on_post_bake(in_wrapper, success): + in_wrapper.on_post_bake_delegate.remove_callable(on_post_bake) + print('bake complete ... {}'.format('success' if success else 'failed')) + + # Delete the hda after the bake + in_wrapper.delete_instantiated_asset() + global _g_wrapper + _g_wrapper = None + + +def on_post_process(in_wrapper): + print('on_post_process') + + # in_wrapper.on_post_processing_delegate.remove_callable(on_post_process) + + # Print out all outputs generated by the HDA + num_outputs = in_wrapper.get_num_outputs() + print('num_outputs: {}'.format(num_outputs)) + if num_outputs > 0: + for output_idx in range(num_outputs): + identifiers = in_wrapper.get_output_identifiers_at(output_idx) + print('\toutput index: {}'.format(output_idx)) + print('\toutput type: {}'.format(in_wrapper.get_output_type_at(output_idx))) + print('\tnum_output_objects: {}'.format(len(identifiers))) + if identifiers: + for identifier in identifiers: + output_object = in_wrapper.get_output_object_at(output_idx, identifier) + output_component = in_wrapper.get_output_component_at(output_idx, identifier) + is_proxy = in_wrapper.is_output_current_proxy_at(output_idx, identifier) + print('\t\tidentifier: {}'.format(identifier)) + print('\t\toutput_object: {}'.format(output_object.get_name() if output_object else 'None')) + print('\t\toutput_component: {}'.format(output_component.get_name() if output_component else 'None')) + print('\t\tis_proxy: {}'.format(is_proxy)) + print('') + + # bind to the post bake delegate + in_wrapper.on_post_bake_delegate.add_callable(on_post_bake) + + # Bake all outputs to actors + print('baking all outputs to actors') + in_wrapper.bake_all_outputs_with_settings( + unreal.HoudiniEngineBakeOption.TO_ACTOR, + replace_previous_bake=False, + remove_temp_outputs_on_success=False) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + + # instantiate an asset, disabling auto-cook of the asset (so we have to + # call wrapper.reCook() to cook it) + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform(), enable_auto_cook=False) + + # Bind to the on post instantiation delegate (before the first cook) + _g_wrapper.on_post_instantiation_delegate.add_callable(on_post_instantiation) + # Bind to the on post processing delegate (after a cook and after all + # outputs have been generated in Unreal) + _g_wrapper.on_post_processing_delegate.add_callable(on_post_process) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/bake_output_object_example.py b/Content/Examples/Python/bake_output_object_example.py new file mode 100644 index 000000000..ac265067e --- /dev/null +++ b/Content/Examples/Python/bake_output_object_example.py @@ -0,0 +1,79 @@ +import unreal + +""" Example script for instantiating an asset, cooking it and baking an +individual output object. + +""" + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def on_post_process(in_wrapper): + print('on_post_process') + # Print details about the outputs and record the first static mesh we find + + sm_index = None + sm_identifier = None + + # in_wrapper.on_post_processing_delegate.remove_callable(on_post_process) + + num_outputs = in_wrapper.get_num_outputs() + print('num_outputs: {}'.format(num_outputs)) + if num_outputs > 0: + for output_idx in range(num_outputs): + identifiers = in_wrapper.get_output_identifiers_at(output_idx) + output_type = in_wrapper.get_output_type_at(output_idx) + print('\toutput index: {}'.format(output_idx)) + print('\toutput type: {}'.format(output_type)) + print('\tnum_output_objects: {}'.format(len(identifiers))) + if identifiers: + for identifier in identifiers: + output_object = in_wrapper.get_output_object_at(output_idx, identifier) + output_component = in_wrapper.get_output_component_at(output_idx, identifier) + is_proxy = in_wrapper.is_output_current_proxy_at(output_idx, identifier) + print('\t\tidentifier: {}'.format(identifier)) + print('\t\toutput_object: {}'.format(output_object.get_name() if output_object else 'None')) + print('\t\toutput_component: {}'.format(output_component.get_name() if output_component else 'None')) + print('\t\tis_proxy: {}'.format(is_proxy)) + print('') + + if (output_type == unreal.HoudiniOutputType.MESH and + isinstance(output_object, unreal.StaticMesh)): + sm_index = output_idx + sm_identifier = identifier + + # Bake the first static mesh we found to the CB + if sm_index is not None and sm_identifier is not None: + print('baking {}'.format(sm_identifier)) + success = in_wrapper.bake_output_object_at(sm_index, sm_identifier) + print('success' if success else 'failed') + + # Delete the instantiated asset + in_wrapper.delete_instantiated_asset() + global _g_wrapper + _g_wrapper = None + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Bind to the on post processing delegate (after a cook and after all + # outputs have been generated in Unreal) + _g_wrapper.on_post_processing_delegate.add_callable(on_post_process) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/curve_input_example.py b/Content/Examples/Python/curve_input_example.py new file mode 100644 index 000000000..f01cee415 --- /dev/null +++ b/Content/Examples/Python/curve_input_example.py @@ -0,0 +1,155 @@ +""" An example script that uses the API to instantiate an HDA and then +set 2 inputs: a geometry input (a cube) and a curve input (a helix). The +inputs are set during post instantiation (before the first cook). After the +first cook and output creation (post processing) the input structure is fetched +and logged. + +""" +import math + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Create a geo input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input + geo_object = get_geo_asset() + if not geo_input.set_input_objects((geo_object, )): + # If any errors occurred, get the last error message + print('Error on geo_input: {0}'.format(geo_input.get_last_error_message())) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, geo_input) + # We can now discard the API input object + geo_input = None + + # Create a curve input + curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(100): + t = i / 20.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Error handling/message example: try to set geo_object on curve input + if not curve_input.set_input_objects((geo_object, )): + print('Error (example) while setting \'{0}\' on curve input: {1}'.format( + geo_object.get_name(), curve_input.get_last_error_message() + )) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Copy the input data to the HDA as node input 1 + in_wrapper.set_input_at_index(1, curve_input) + # We can now discard the API input object + curve_input = None + + # Check for errors on the wrapper + last_error = in_wrapper.get_last_error_message() + if last_error: + print('Error on wrapper during input configuration: {0}'.format(last_error)) + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + elif isinstance(in_input, unreal.HoudiniPublicAPICurveInput): + print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) + print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): + print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) + print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) + print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) + print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) + print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Print the input state after the cook and output creation. + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/geo_input_example.py b/Content/Examples/Python/geo_input_example.py new file mode 100644 index 000000000..721f76cd4 --- /dev/null +++ b/Content/Examples/Python/geo_input_example.py @@ -0,0 +1,157 @@ +""" An example script that uses the API to instantiate an HDA and then +set 2 geometry inputs: one node input and one object path parameter +input. The inputs are set during post instantiation (before the first +cook). After the first cook and output creation (post processing) the +input structure is fetched and logged. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/subnet_test_2_0.subnet_test_2_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def get_cylinder_asset_path(): + return '/Engine/BasicShapes/Cylinder.Cylinder' + + +def get_cylinder_asset(): + return unreal.load_object(None, get_cylinder_asset_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Deprecated input functions + # in_wrapper.set_input_type(0, unreal.HoudiniInputType.GEOMETRY) + # in_wrapper.set_input_objects(0, (get_geo_asset(), )) + + # in_wrapper.set_input_type(1, unreal.HoudiniInputType.GEOMETRY) + # in_wrapper.set_input_objects(1, (get_geo_asset(), )) + # in_wrapper.set_input_import_as_reference(1, True) + + # Create a geometry input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input + geo_asset = get_geo_asset() + geo_input.set_input_objects((geo_asset, )) + # Set the transform of the input geo + geo_input.set_object_transform_offset( + geo_asset, + unreal.Transform( + (200, 0, 100), + (45, 0, 45), + (2, 2, 2), + ) + ) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, geo_input) + # We can now discard the API input object + geo_input = None + + # Create a another geometry input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input (cylinder in this case) + geo_asset = get_cylinder_asset() + geo_input.set_input_objects((geo_asset, )) + # Set the transform of the input geo + geo_input.set_object_transform_offset( + geo_asset, + unreal.Transform( + (-200, 0, 0), + (0, 0, 0), + (2, 2, 2), + ) + ) + # copy the input data to the HDA as input parameter 'objpath1' + in_wrapper.set_input_parameter('objpath1', geo_input) + # We can now discard the API input object + geo_input = None + + # Set the subnet_test HDA to output its first input + in_wrapper.set_int_parameter_value('enable_geo', 1) + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Bind on_post_processing, after cook + output creation + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/instances_example.py b/Content/Examples/Python/instances_example.py new file mode 100644 index 000000000..2611889d0 --- /dev/null +++ b/Content/Examples/Python/instances_example.py @@ -0,0 +1,43 @@ +""" Example script that instantiates an HDA using the API and then +setting some parameter values after instantiation but before the +first cook. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def on_post_instantiation(in_wrapper): + print('on_post_instantiation') + # in_wrapper.on_post_instantiation_delegate.remove_callable(on_post_instantiation) + + # Set some parameters to create instances and enable a material + in_wrapper.set_bool_parameter_value('add_instances', True) + in_wrapper.set_int_parameter_value('num_instances', 8) + in_wrapper.set_bool_parameter_value('addshader', True) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Bind on_post_instantiation (before the first cook) callback to set parameters + _g_wrapper.on_post_instantiation_delegate.add_callable(on_post_instantiation) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/landscape_input_example.py b/Content/Examples/Python/landscape_input_example.py new file mode 100644 index 000000000..5bf6b8f69 --- /dev/null +++ b/Content/Examples/Python/landscape_input_example.py @@ -0,0 +1,112 @@ +""" Example script using the API to instantiate an HDA and set a landscape +input. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/hilly_landscape_erode_1_0.hilly_landscape_erode_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def load_map(): + return unreal.EditorLoadingAndSavingUtils.load_map('/HoudiniEngine/Examples/Maps/LandscapeInputExample.LandscapeInputExample') + +def get_landscape(): + return unreal.EditorLevelLibrary.get_actor_reference('PersistentLevel.Landscape_0') + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Create a landscape input + landscape_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPILandscapeInput) + # Send landscapes as heightfields + landscape_input.landscape_export_type = unreal.HoudiniLandscapeExportType.HEIGHTFIELD + # Keep world transform + landscape_input.keep_world_transform = True + # Set the input objects/assets for this input + landscape_object = get_landscape() + landscape_input.set_input_objects((landscape_object, )) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, landscape_input) + # We can now discard the API input object + landscape_input = None + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + if isinstance(in_input, unreal.HoudiniPublicAPILandscapeInput): + print('\t\tbLandscapeAutoSelectComponent: {0}'.format(in_input.landscape_auto_select_component)) + print('\t\tbLandscapeExportLighting: {0}'.format(in_input.landscape_export_lighting)) + print('\t\tbLandscapeExportMaterials: {0}'.format(in_input.landscape_export_materials)) + print('\t\tbLandscapeExportNormalizedUVs: {0}'.format(in_input.landscape_export_normalized_u_vs)) + print('\t\tbLandscapeExportSelectionOnly: {0}'.format(in_input.landscape_export_selection_only)) + print('\t\tbLandscapeExportTileUVs: {0}'.format(in_input.landscape_export_tile_u_vs)) + print('\t\tbLandscapeExportType: {0}'.format(in_input.landscape_export_type)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # Load the example map + load_map() + + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Print the input state after the cook and output creation. + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/outputs_example.py b/Content/Examples/Python/outputs_example.py new file mode 100644 index 000000000..1179aead7 --- /dev/null +++ b/Content/Examples/Python/outputs_example.py @@ -0,0 +1,90 @@ +import unreal + +""" Example script for instantiating an asset, setting parameters, cooking it +and iterating over and logging all of its output objects. + +""" + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def on_post_instantiation(in_wrapper): + print('on_post_instantiation') + # in_wrapper.on_post_instantiation_state_exited_delegate_delegate.remove_callable(on_post_instantiation) + + # Set parameter values for the next cook + # in_wrapper.set_bool_parameter_value('add_instances', True) + # in_wrapper.set_int_parameter_value('num_instances', 8) + in_wrapper.set_parameter_tuples({ + 'add_instances': unreal.HoudiniParameterTuple(bool_values=(True, )), + 'num_instances': unreal.HoudiniParameterTuple(int32_values=(8, )), + }) + + # Print all parameter values + param_tuples = in_wrapper.get_parameter_tuples() + print('parameter tuples: {}'.format(len(param_tuples) if param_tuples else 0)) + if param_tuples: + for param_tuple_name, param_tuple in param_tuples.items(): + print('parameter tuple name: {}'.format(param_tuple_name)) + print('\tbool_values: {}'.format(param_tuple.bool_values)) + print('\tfloat_values: {}'.format(param_tuple.float_values)) + print('\tint32_values: {}'.format(param_tuple.int32_values)) + print('\tstring_values: {}'.format(param_tuple.string_values)) + + # Force a cook/recook + in_wrapper.recook() + + +def on_post_process(in_wrapper): + print('on_post_process') + + # in_wrapper.on_post_processing_delegate.remove_callable(on_post_process) + + # Print out all outputs generated by the HDA + num_outputs = in_wrapper.get_num_outputs() + print('num_outputs: {}'.format(num_outputs)) + if num_outputs > 0: + for output_idx in range(num_outputs): + identifiers = in_wrapper.get_output_identifiers_at(output_idx) + print('\toutput index: {}'.format(output_idx)) + print('\toutput type: {}'.format(in_wrapper.get_output_type_at(output_idx))) + print('\tnum_output_objects: {}'.format(len(identifiers))) + if identifiers: + for identifier in identifiers: + output_object = in_wrapper.get_output_object_at(output_idx, identifier) + output_component = in_wrapper.get_output_component_at(output_idx, identifier) + is_proxy = in_wrapper.is_output_current_proxy_at(output_idx, identifier) + print('\t\tidentifier: {}'.format(identifier)) + print('\t\toutput_object: {}'.format(output_object.get_name() if output_object else 'None')) + print('\t\toutput_component: {}'.format(output_component.get_name() if output_component else 'None')) + print('\t\tis_proxy: {}'.format(is_proxy)) + print('') + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + + # instantiate an asset, disabling auto-cook of the asset (so we have to + # call wrapper.reCook() to cook it) + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform(), enable_auto_cook=False) + + # Bind to the on post instantiation delegate (before the first cook) + _g_wrapper.on_post_instantiation_delegate.add_callable(on_post_instantiation) + # Bind to the on post processing delegate (after a cook and after all + # outputs have been generated in Unreal) + _g_wrapper.on_post_processing_delegate.add_callable(on_post_process) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/pdg_example.py b/Content/Examples/Python/pdg_example.py new file mode 100644 index 000000000..8c04af287 --- /dev/null +++ b/Content/Examples/Python/pdg_example.py @@ -0,0 +1,117 @@ +""" An example script that instantiates an HDA that contains a TOP network. +After the HDA itself has cooked (in on_post_process) we iterate over all +TOP networks in the HDA and print their paths. Auto-bake is then enabled for +PDG and the TOP networks are cooked. + +""" + +import os + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/pdg_pighead_grid_2_0.pdg_pighead_grid_2_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_hda_directory(): + """ Attempt to get the directory containing the .hda files. This is + /HoudiniEngine/Examples/hda. In newer versions of UE we can use + ``unreal.SystemLibrary.get_system_path(asset)``, in older versions + we manually construct the path to the plugin. + + Returns: + (str): the path to the example hda directory or ``None``. + + """ + if hasattr(unreal.SystemLibrary, 'get_system_path'): + return os.path.dirname(os.path.normpath( + unreal.SystemLibrary.get_system_path(get_test_hda()))) + else: + plugin_dir = os.path.join( + os.path.normpath(unreal.Paths.project_plugins_dir()), + 'Runtime', 'HoudiniEngine', 'Content', 'Examples', 'hda' + ) + if not os.path.exists(plugin_dir): + plugin_dir = os.path.join( + os.path.normpath(unreal.Paths.engine_plugins_dir()), + 'Runtime', 'HoudiniEngine', 'Content', 'Examples', 'hda' + ) + if not os.path.exists(plugin_dir): + return None + return plugin_dir + + +def delete_instantiated_asset(): + global _g_wrapper + if _g_wrapper: + result = _g_wrapper.delete_instantiated_asset() + _g_wrapper = None + return result + else: + return False + + +def on_pre_instantiation(in_wrapper): + print('on_pre_instantiation') + + # Set the hda_directory parameter to the directory that contains the + # example .hda files + hda_directory = get_hda_directory() + print('Setting "hda_directory" to {0}'.format(hda_directory)) + in_wrapper.set_string_parameter_value('hda_directory', hda_directory) + + # Cook the HDA (not PDG yet) + in_wrapper.recook() + + +def on_post_bake(in_wrapper, success): + # in_wrapper.on_post_bake_delegate.remove_callable(on_post_bake) + print('bake complete ... {}'.format('success' if success else 'failed')) + + +def on_post_process(in_wrapper): + print('on_post_process') + + # in_wrapper.on_post_processing_delegate.remove_callable(on_post_process) + + # Iterate over all PDG/TOP networks and nodes and log them + print('TOP networks:') + for network_path in in_wrapper.get_pdgtop_network_paths(): + print('\t{}'.format(network_path)) + for node_path in in_wrapper.get_pdgtop_node_paths(network_path): + print('\t\t{}'.format(node_path)) + + # Enable PDG auto-bake (auto bake TOP nodes after they are cooked) + in_wrapper.set_pdg_auto_bake_enabled(True) + # Bind to PDG post bake delegate (called after all baking is complete) + in_wrapper.on_post_pdg_bake_delegate.add_callable(on_post_bake) + # Cook the specified TOP node + in_wrapper.pdg_cook_node('topnet1', 'HE_OUT_PIGHEAD_GRID') + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + + # instantiate an asset, disabling auto-cook of the asset + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform(), enable_auto_cook=False) + + # Bind to the on pre instantiation delegate (before the first cook) and + # set parameters + _g_wrapper.on_pre_instantiation_delegate.add_callable(on_pre_instantiation) + # Bind to the on post processing delegate (after a cook and after all + # outputs have been generated in Unreal) + _g_wrapper.on_post_processing_delegate.add_callable(on_post_process) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/process_hda_example.py b/Content/Examples/Python/process_hda_example.py new file mode 100644 index 000000000..2002e8183 --- /dev/null +++ b/Content/Examples/Python/process_hda_example.py @@ -0,0 +1,172 @@ +""" An example script that uses the API and +HoudiniEngineV2.asyncprocessor.ProcessHDA. ProcessHDA is configured with the +asset to instantiate, as well as 2 inputs: a geometry input (a cube) and a +curve input (a helix). + +ProcessHDA is then activiated upon which the asset will be instantiated, +inputs set, and cooked. The ProcessHDA class's on_post_processing() function is +overridden to fetch the input structure and logged. The other state/phase +functions (on_pre_instantiate(), on_post_instantiate() etc) are overridden to +simply log the function name, in order to observe progress in the log. + +""" +import math + +import unreal + +from HoudiniEngineV2.asyncprocessor import ProcessHDA + + +_g_processor = None + + +class ProcessHDAExample(ProcessHDA): + @staticmethod + def _print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + elif isinstance(in_input, unreal.HoudiniPublicAPICurveInput): + print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) + print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): + print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) + print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) + print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) + print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) + print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) + + def on_failure(self): + print('on_failure') + global _g_processor + _g_processor = None + + def on_complete(self): + print('on_complete') + global _g_processor + _g_processor = None + + def on_pre_instantiation(self): + print('on_pre_instantiation') + + def on_post_instantiation(self): + print('on_post_instantiation') + + def on_post_auto_cook(self, cook_success): + print('on_post_auto_cook, success = {0}'.format(cook_success)) + + def on_pre_process(self): + print('on_pre_process') + + def on_post_processing(self): + print('on_post_processing') + + # Fetch inputs, iterate over it and log + node_inputs = self.asset_wrapper.get_inputs_at_indices() + parm_inputs = self.asset_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + self._print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + self._print_api_input(input_wrapper) + + def on_post_auto_bake(self, bake_success): + print('on_post_auto_bake, succes = {0}'.format(bake_success)) + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def build_inputs(): + print('configure_inputs') + + # get the API singleton + houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + node_inputs = {} + + # Create a geo input + geo_input = houdini_api.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input + geo_object = get_geo_asset() + geo_input.set_input_objects((geo_object, )) + # store the input data to the HDA as node input 0 + node_inputs[0] = geo_input + + # Create a curve input + curve_input = houdini_api.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(100): + t = i / 20.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Store the input data to the HDA as node input 1 + node_inputs[1] = curve_input + + return node_inputs + + +def run(): + # Create the processor with preconfigured inputs + global _g_processor + _g_processor = ProcessHDAExample( + get_test_hda(), node_inputs=build_inputs()) + # Activate the processor, this will starts instantiation, and then cook + if not _g_processor.activate(): + unreal.log_warning('Activation failed.') + else: + unreal.log('Activated!') + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/ramp_parameter_example.py b/Content/Examples/Python/ramp_parameter_example.py new file mode 100644 index 000000000..eca32f603 --- /dev/null +++ b/Content/Examples/Python/ramp_parameter_example.py @@ -0,0 +1,128 @@ +""" An example script that uses the API to instantiate an HDA and then set +the ramp points of a float ramp and a color ramp. + +""" +import math + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/ramp_example_1_0.ramp_example_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def set_parameters(in_wrapper): + print('set_parameters') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(set_parameters) + + # There are two ramps: heightramp and colorramp. The height ramp is a float + # ramp. As an example we'll set the number of ramp points and then set + # each point individually + in_wrapper.set_ramp_parameter_num_points('heightramp', 6) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 0, 0.0, 0.1) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 1, 0.2, 0.6) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 2, 0.4, 1.0) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 3, 0.6, 1.4) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 4, 0.8, 1.8) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 5, 1.0, 2.2) + + # For the color ramp, as an example, we can set the all the points via an + # array. + in_wrapper.set_color_ramp_parameter_points('colorramp', ( + unreal.HoudiniPublicAPIColorRampPoint(position=0.0, value=unreal.LinearColor.GRAY), + unreal.HoudiniPublicAPIColorRampPoint(position=0.5, value=unreal.LinearColor.GREEN), + unreal.HoudiniPublicAPIColorRampPoint(position=1.0, value=unreal.LinearColor.RED), + )) + + +def print_parameters(in_wrapper): + print('print_parameters') + + in_wrapper.on_post_processing_delegate.remove_callable(print_parameters) + + # Print the ramp points directly + print('heightramp: num points {0}:'.format(in_wrapper.get_ramp_parameter_num_points('heightramp'))) + heightramp_data = in_wrapper.get_float_ramp_parameter_points('heightramp') + if not heightramp_data: + print('\tNone') + else: + for idx, point_data in enumerate(heightramp_data): + print('\t\t{0}: position={1:.6f}; value={2:.6f}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + + print('colorramp: num points {0}:'.format(in_wrapper.get_ramp_parameter_num_points('colorramp'))) + colorramp_data = in_wrapper.get_color_ramp_parameter_points('colorramp') + if not colorramp_data: + print('\tNone') + else: + for idx, point_data in enumerate(colorramp_data): + print('\t\t{0}: position={1:.6f}; value={2}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + + # Print all parameter values + param_tuples = in_wrapper.get_parameter_tuples() + print('parameter tuples: {}'.format(len(param_tuples) if param_tuples else 0)) + if param_tuples: + for param_tuple_name, param_tuple in param_tuples.items(): + print('parameter tuple name: {}'.format(param_tuple_name)) + print('\tbool_values: {}'.format(param_tuple.bool_values)) + print('\tfloat_values: {}'.format(param_tuple.float_values)) + print('\tint32_values: {}'.format(param_tuple.int32_values)) + print('\tstring_values: {}'.format(param_tuple.string_values)) + if not param_tuple.float_ramp_points: + print('\tfloat_ramp_points: None') + else: + print('\tfloat_ramp_points:') + for idx, point_data in enumerate(param_tuple.float_ramp_points): + print('\t\t{0}: position={1:.6f}; value={2:.6f}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + if not param_tuple.color_ramp_points: + print('\tcolor_ramp_points: None') + else: + print('\tcolor_ramp_points:') + for idx, point_data in enumerate(param_tuple.color_ramp_points): + print('\t\t{0}: position={1:.6f}; value={2}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Set the float and color ramps on post instantiation, before the first + # cook. + _g_wrapper.on_post_instantiation_delegate.add_callable(set_parameters) + # Print the parameter state after the cook and output creation. + _g_wrapper.on_post_processing_delegate.add_callable(print_parameters) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/start_session_example.py b/Content/Examples/Python/start_session_example.py new file mode 100644 index 000000000..f8f41cc3c --- /dev/null +++ b/Content/Examples/Python/start_session_example.py @@ -0,0 +1,19 @@ +import unreal + +""" Example for getting the API instance and starting/creating the Houdini +Engine Session. + +""" + + +def run(): + # Get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + # Check if there is an existing valid session + if not api.is_session_valid(): + # Create a new session + api.create_session() + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/world_input_example.py b/Content/Examples/Python/world_input_example.py new file mode 100644 index 000000000..706bbc4dc --- /dev/null +++ b/Content/Examples/Python/world_input_example.py @@ -0,0 +1,163 @@ +""" An example script that spawns some actors and then uses the API to +instantiate an HDA and set the actors as world inputs. The inputs are set +during post instantiation (before the first cook). After the first cook and +output creation (post processing) the input structure is fetched and logged. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/subnet_test_2_0.subnet_test_2_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def get_cylinder_asset_path(): + return '/Engine/BasicShapes/Cylinder.Cylinder' + + +def get_cylinder_asset(): + return unreal.load_object(None, get_cylinder_asset_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Spawn some actors + actors = spawn_actors() + + # Create a world input + world_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIWorldInput) + # Set the input objects/assets for this input + world_input.set_input_objects(actors) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, world_input) + # We can now discard the API input object + world_input = None + + # Set the subnet_test HDA to output its first input + in_wrapper.set_int_parameter_value('enable_geo', 1) + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIWorldInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + print('\t\tbIsWorldInputBoundSelector: {0}'.format(in_input.is_world_input_bound_selector)) + print('\t\tbWorldInputBoundSelectorAutoUpdate: {0}'.format(in_input.world_input_bound_selector_auto_update)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def spawn_actors(): + actors = [] + # Spawn a static mesh actor and assign a cylinder to its static mesh + # component + actor = unreal.EditorLevelLibrary.spawn_actor_from_class( + unreal.StaticMeshActor, location=(0, 0, 0)) + actor.static_mesh_component.set_static_mesh(get_cylinder_asset()) + actor.set_actor_label('Cylinder') + actor.set_actor_transform( + unreal.Transform( + (-200, 0, 0), + (0, 0, 0), + (2, 2, 2), + ), + sweep=False, + teleport=True + ) + actors.append(actor) + + # Spawn a static mesh actor and assign a cube to its static mesh + # component + actor = unreal.EditorLevelLibrary.spawn_actor_from_class( + unreal.StaticMeshActor, location=(0, 0, 0)) + actor.static_mesh_component.set_static_mesh(get_geo_asset()) + actor.set_actor_label('Cube') + actor.set_actor_transform( + unreal.Transform( + (200, 0, 100), + (45, 0, 45), + (2, 2, 2), + ), + sweep=False, + teleport=True + ) + actors.append(actor) + + return actors + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Bind on_post_processing, after cook + output creation + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/hda/copy_to_curve.1.0.hda b/Content/Examples/hda/copy_to_curve.1.0.hda new file mode 100644 index 0000000000000000000000000000000000000000..d3323f0ef05198f37e9297f3063a9d825ab9195e GIT binary patch literal 13104 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 r1wLJB literal 0 HcmV?d00001 diff --git a/Content/Examples/hda/hilly_landscape_erode_1_0.uasset b/Content/Examples/hda/hilly_landscape_erode_1_0.uasset new file mode 100644 index 0000000000000000000000000000000000000000..582999e0664611b6de8e43f086165e83b082ea0a GIT binary patch literal 55274 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 PhZ~*dMw6>gT$%GH?SN!bLd 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

    6JRTpACXN+gR z9LAQ&LC=590V2&w`qY|pum`bwCG+_sS&GJXS&@%iPUa$phIf)DRnCr@m!D_I#*e&s zTb=04`=#AeoN;|0aw z?%o~p_)baW1H?Xe&J)J-9EO38oQJzE5k79@Xg~2(%u6uh!4qd3?K%9Hp+VDrg&}*} zJNpRK?h6Jxe7u6(^@&;qro1;QSV+OJ$fWr>N`;FfrCWWd-JD3h)K7BPc)Ge{)^oDn zXMOvf2OPXKFv@P>O!?>W^z}jt(gp-vHLWF;hmdfFUvzl+seg>bD=-rbFxhmIAYw+#1w z?jQI=7=JE_9aGc)xG={+1Y)WIZ}rUU2W#X#YP{VTRycce(L0IZ5D6!93R+wKtTC zma}g{MBhKt4ty_pg!`1a-W+E;q)>SxfMJ)yn_$)8d!yFIcZ1JHoRP}#tR61E#OV}n zR(MnNM)4=>VS~@dPflTpBr8Zg>L*87-_CYvZMFJ?cwIro~p999;xnVekL6Mif;V;2}Sp_g5#D6WolYw zbHyw~vEr8D3W5AAM?a_d()+lnqOJS`rt!1WdQkgteJ1BbmZVWA391Z1#L)S1(6>^7b!=p0gsF89ycRR9sv}`hz0d z5>>swm+Rwj8RKjcSL*WqsbcSD9xwbs1%`J!Drnwvxxhjic;3p@!-@E7hsBSKc^op1 zA&}(JukeoTRjj1CA3sdnIKaKnqTx;E8ECnX+emnnvE*+1X)vji5*PJA8jh3m-`b^sBbGn$ z^0@8EUe7o4dHI>ZUKWL8H_?;*7_2&VTI}55Y3d+rC;F~I%;FpAqu2d-am+nb#7XLsynNb0I zZX~=jA}rj4>abNslDG}~9A6xJX{qQ@&LW2{aoyOJ*ksKGN6teRU7K+Cu-@dPozrW` zn6(}4eQY~*NH>bUxf%^$%o9qpS;6l(PN3a$42!+V^#nq|AmXNWN}}b;(fMH`dI@De zZY#BWru%|8Y0kaeuhEcbw;PE%kzT#n`C#th^QuvrX}>f9tK%;gU*XJM!L5+aPmO)t zpN8uCCSa;8v<*pPUc;zGDWOEd1<<4P{snTA~Mp>qx@=$D6k zipUm)TeEq|mCuVyYh8R~E^nK9i8dd9x840zr=$|$o|S81TKi%yJrO4mvxyjSvmq31oL zMq5(}jjAqM+tJL#XvK3OiVOJaJ5D<|nJEv|RYFAu<6Gb2eZ2mnDoMPQH!n*!-0~3v zA8W9;icO6WaCUU3mokaay zj)aHlcy0o;-Gb*3R?uJ zF)K6zM;oTC--jh$pMZZ(s8X)}f&U_9REDqQJfr1Ne=>xhpJbpghi~>yzxvm?#{v_n zJC=wV$QYXGm^z%e503T6q8hQx>cdl(^JqO`ON@Cp8?YXa^cL~5r6Z$w16n>Ty*gG_ zCeX-FYKLXJbtU92b)^kLtdHrE*1o zZppdWD-(xOhE#rJ;e+f@S@ccGv?4JY4qxOQLHF7VI~hpo-yJ#1gEQTb4$94l3t{My-1OV3|*&$ld=WDsC~yie&j!oGL)a_>3RUbPc%tn6jz3{gS)inuW@Ij@?iD)e zmGWxnwZL6Vrm8Df=rd!PBP)B;pqA2s8omQzvxE3;=XL0h@AbYbb=vMuMjowPwhvy; zRjU0BJ3{IAPPU94f}U2nj?!Xm&8rtHe|Mv##Aoo*d~WTYz*h&`?g=RJbT;)=#Yq|l zUW}O>9Xx$S^D#j&FBVFcxqkZ5lVsV-luuZL;}hibVL7S~1_+9S(xqRb-gO4Z(UO*^ zN}gdD$df6cmVL&lc3^*Z$r)PX92|%d?Zx9nJ&a3v^tjy9txL2W^*C>4PBcl+)h|`O zd$#m3+QIX5YSCQ<@|=he^W8F{+_IHMQjD)2^%PTMr7%?9p7%gqtD{U~!>3Y6PR?Q) z&Yr9(mw3W-yRU`<$bpb&x$K*zGcE5`rovvE@G=@B7 zhn(moY`JB-!WTh(S*NUb?Y``%E2Kuev!qfu&T*{fF@uNSz_CDkZSK&Ur>Azl4X_r{ zOpK~~gcsys?6!-*Eg@G5F{jo%|KVXYT(9EkE$VFAY3V-SL-8*erllUNe72+V=FY+S zaFluPj(|sJjuB#T9f=qYDrL%d?B36^1lhsylEeGn$%ASoS0HW2sp78I?;TE%J_0pp z70%%qd`6d2IYPu{$?J95;>|GvDC5J&B(0`AE8MxkYQ6Q>HQeu65!)M4UdG>X`NJ*M zn-*%1oT!hqOAsmfmujoTJm?h(GUS%D3U#^1Fhvn@#NSihy7B^!nqG&-Be$448IRJ~ z$)6e}$IjZvsi{8GG&^E(H`a`>>0>Ch2a7hZhQA)~DZz!k7VIGg!`7T<_0IWOU^|oJ zj172clxKu$Ns>2WxIEKHxkwl&SZQMF*}Cj4_G@sSE}f6G+DB7ymB)X)zrNfy^eOGU zrnBB;<}^uertgZEm8IU$3Sq2d{17hV?o#;9kmrmP_|=|wFZd05 zRSal-*!7-3)_?Gp&M{4Qul(6*oNH&J{(MAV_p`x#Bkl`|N|_bf=- zj+~%if07jEq6@3OJo2Hg+rLcj)&7%Gf_w|AP028AvZR`xz(O(KPcN5Z7bvuqvr9%6X92f@h?0+ywhtb z@ztBHRuH z@^J>8(@DP6b~xiYXQ+Lx%yig9^z)0fGQ!z{*-45kXI-bn^(d885c=UAmFj36w!acYJIQAL$v^1F+e!zs^~ z1Ydd8uGH{&Ql3UDmrgb0#SrT|4OAAM&@SUELAq?dOlHa@_?77*1*8T$=rQ^`Va;-t z>ABu2k@2P1SZ&>Kp2WQ1%napwC|jwc%59peFHVm8p#`mDLXRde&ukfI^34ve9KAGaO;s!F%sNS9JKj-PICc52F7e#s6s zNk(>!LAmiogq=hRD{+8j+UI+kgU<<1KH3>mPa+Y`OrR~GD0e?x=5cHLnPWTZO)5y2 zE>>cyR(#%;60kTQA!8FQrjBm7ocpSMO7q&m;jS@mZddzbGFi?dhdDe++x691d6*FDqGL=JRF=L`_d;5kio$7u}A9F4~9ina5^=PpllR=Y=B+@^lQ4o~6eDvQh*e!6f4cFG+xcG^s_7Mi}_os{$ z-=~=5JZs!XziPQTav&aZQyE;nB4IhY%SH@NMw{+KjNW~hI z5U0GwO*Y(h&&`trKN_7}|B-OEKTD($=1w2wIa2o`D)!*}^xBW&944x0;*(_!<7i`5 zpY$_0-6?N+XVuR?y-WwK&8)qTI$J#8m7MW;B8kMGSi?_JeYEMm!9d;#9h6gK+3e$s z2XCHhp!MOo9?H9`B!-+Xeh!ft1-()&L_UATJnnhoglHjah_F_>Sqs0k`b+U@;Y$1A zaP`i8d#}}yOw#i}<{g&}T%B<{Cy33Ow2YzqNx8yy;J26uAWw8srz0MRFK`p{vLxiH zC-GV3Qe>aahYCnJTTnlj*;~R_5z)SUjf<9ucZrMl0}In_1_wA|#Nm15O-;nP_p+AA zC|eC{xD2+KxoCi&ZGi=aonIqS4zcesVw5F$K#BH>7wxE6et8UMB!SvlcJ7y#+=$O# zZuQH)dUZMdQ(lmz^@aZ35>Zia9%%AY@cFRZIx`!2xm}a+Lm=ff=oJ~FGc202Zo?v) z^y$Y|-q{^`Cu&dT`h-F2aDTj**x)m1nSexv4BBJl>5ojJri}P2US78Mrxqg{E&t%6 zfO>Iprph6;!@OX!JvW52prbffjA_EWzen#Q;Z-+krh5;s(51RHm#^=AU5HU4aJrIQO~OKV91K_O*8N0`whbSCWT#9eE0?K{-9HRQF(lIY~6B&|{wE zB3!-%?9me|RQJ!?7n9y9QzaR=s(LNZ^ZbfwaJPMs%&9Xf2cU-vBM5LiQCV8kH8Dj5cz$mM} zJM2|`LQ|HEuVfPL`|25`{-tWgPeJ8LFpHxw*O}?*$mREgrH7+#<`T&76Y<=wrghpM znfr47;tkE`tZLn{)e`=OA;$(UNm0v=iBD#l@dRF8h=q%NuDlx{gLvD*OZHr z$t5AfBW=>cIpU|(2qFsyFD}R4kGgjOFF%gJ7@&b-qc=NLq(C&T*fr2Soy zS(vQSfuXCc$4?#$3yi};?vh+7!X?kZzNbSf>w!a;n!>Ajk&R68gmHsdBw|9ann&#m$Z}(3^S`wXA#U= zIWpEn)?T|myUeuAi+_3I@mSjo-y1*W-Bt9jJSt$glonNWQ3T}Lta7Z8tg^34w5K`C zZyYsH>1%I~v2wRa@Z=hrZ8D?1R_AFjMBtFvnrc;cqpM9;gj1n!He%W+?Rx>ud*k$8L^86a<{=A*H*|>rz}#T_Tf@ix$b%$a5X*&5K+x5DaXN zqkDLFQY57Nc3|dFN2U0=NrTViHRrP72b@}yt`tIL#b`yXC#fFzUU^5&H-9T%g-u1z zAg9By5%==#42_GXMJ@Lo)$22F=aq*ZW;DXUd?w5J;sFm#`bn9OMN=I%Pal75r!tP^ z9w1X`o6e^9+Z#E2o`Nf)_Xsk_Z!o5`$l|&)*20`OD&=H8^qYYK-e{L@cATG_>MzJ6W-{DUcrf>AJWaO^vq`Pi z;Y7OhyAj2-G$U>UK7_6DbXxl)_UsWFOJ~)r7pIR=q+homO<@gyv^O` zIYqQ$?0w?=Ie1vrkam2~A zYlkvV47+BrlXVT8;+&-x#-|ftottCde4hB2^QI2-p%Y?JPE zbgYV^xhK&xfy@2P5~rGqc7dj{h{Cm~cc!fgY2k8vcCvO@$G3}$z{ui7A77)H)Ehe` zT^ZNJDj;*;j<5><>5Dkh4AGv0O4Tt=6UR%W7>1r|=EOBDBg7ABwsWMRr^0SZSvRrA z>_qp;dAdcu+lL#gwcGC{cNn&{`Qrl)=ek?Yr(-c2WRrfrb>_Yv8!sJ!$KL2l&c3rB zj!}ls)XSQVN1xR{BQk8UJgO_{Q{#JkuKrG@w5r(&)tvO0Ve#tDH< zmR2IPV_GasaA-r5kL$^Khy+Q#Z7K}a<{_teV9u129zRK%a?BK~b~%y%xSe%#+wL-n z*sGQw{Obho?mc(hQoB(^eTK&&zY}Ko=JMn)@=CKYudQ>8@6m-&mfL9T;pygQZ`+^M za;4rK8yM-Feu~m<+nrc8fG+RddDm?xJk_>3M?r!bO`_S$nDI7NF|}hN!VFUy=v|oL zc>mO%THG3g^F@=Ecn@sIvG=_qbE99rbU6`MqD&5=Ojy<*= zIT|U2x3nU2W@a#cZ*(-}&fUX1RB(M)!Vb-}1#Y<^!UOjQG-8!`-`dT-v%1jEo93<> zfp;kO=1l`wOP78A0c_>#OgF^tKggJP%C2;@>R3DFyC`e=th{sg{n+S5=b7IQ?zK)7 z@V7pz^f}^cMoa?D@g|jXhzf&gNpV8r`}Gg5i?u)Zqd#`x!??)uzP(feH{+%DWXVKl z*Tfw7sCzp85NmAl%*}8#k?T8K4O8L9$M#&LS;Y!3_q2R;A|8Dfv$!kQ=^innv5U+6 z48aA=oNkWcG+b|vLRlVrPI;oQD<35jhVL?W5`Q_Wj0WOQiK(|szw+{ZHwtQSEhDGg zHzYZXRRq35m2Gg52Xb91B-`9VB;}lQudz>LPG(fK_y_!aa=ps(NDIB9wmxGPjRl(AI(8rYX3Plhxⅈ`<^jy0)OpV07$>hI}E z39BTvSl}P1>w#Jr24mY2QQkV$!a=D>s*jh2D$ODFyEwM*eR3@IDXA42!(~pVi;=qraqGg+5CrL?RkX3ma zl^xnKK53`^e(^;eH`m#{vEDPPA-X$ac$0b;^=M8|=^mvFFH5$4rKs#DCxQWB_O z6~r<6RD{)2_{2-$jU+Gas4qNMEEX5+!3W9Dszb5K38j|8AG@Sjw6V3DBu7Mb)khkWBc)1Qk zz0GVWoVbcs!m#^ReC-T&`*gS5Ta$atXD^$DH4*DSWthjMVC`Ww>@7HY+tG?l z?$*9In&2~MUywKJrFy40A0fGWTofKj1z(D}ADOcf!q?w4G&-pdU+j2YPbg;@>FC?= z)_)R^u$~ z{KvVWOQQMGnph;WtQChYzv&L8ZF9V+ubik#YnoWYL}QPfdW3SI?3kZRovi!(q}XRL zndn)Y-oq?;*O{rV3n$~3^QZf76)k+mUXlo+?v7f$NkD)_Vgy_u;ZyW*N2A<<>jOL2 zt`Gn>57wTw`v%~>gEc^Pv@&od3dmc_*tv?Wj@DiMiNWuZ^;dsR^Gott5&9?(cN_37 z0}k-V2>v1{++7X0VdD(fLfLva!}-8FML^K@)t@?E1-s!#OvDUc!U5w;09RC)C77AH zzb36-OZ_Iz77pC7_$Cd!1Op_osHuV1SL_h>eBgB@^yZ>$;np7ZQ1H?SvjqR9Du9bR zKxn0{X=?m{|aq<**pmIMT-eVQhcIP%6j}5a9u{m7legE$pl%ZQ*7u p>1WJrAbrEjTJjcF){?g8WbM}inL&Z0f!}@vxQ8&W1}G*D_#ZxMd6)nI literal 0 HcmV?d00001 diff --git a/Content/Examples/hda/pdg_pighead_grid_2_0.uasset b/Content/Examples/hda/pdg_pighead_grid_2_0.uasset new file mode 100644 index 0000000000000000000000000000000000000000..54e24afb06e59328e86f3fa0baf008f8676deaf0 GIT binary patch literal 38213 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 literal 0 HcmV?d00001 diff --git a/Content/Examples/hda/pig_head_subdivider_v01.hda b/Content/Examples/hda/pig_head_subdivider_v01.hda new file mode 100644 index 0000000000000000000000000000000000000000..327292a9c35b0f157fa4bfbbccb649300596531e GIT binary patch literal 43159 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 literal 0 HcmV?d00001 diff --git a/Content/Examples/hda/pig_head_subdivider_v01.uasset b/Content/Examples/hda/pig_head_subdivider_v01.uasset new file mode 100644 index 0000000000000000000000000000000000000000..8392103c7d2f8368015639098c2dbbbd4797bd16 GIT binary patch literal 45412 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 L(P^L&WYd2E7C$y` literal 0 HcmV?d00001 diff --git a/Content/Examples/hda/ramp_example_1_0.uasset b/Content/Examples/hda/ramp_example_1_0.uasset new file mode 100644 index 0000000000000000000000000000000000000000..170496f397d7bbbc1e2b6afc709334818d6834f7 GIT binary patch literal 11894 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 literal 0 HcmV?d00001 diff --git a/Content/Examples/hda/subnet_test.2.0.hda b/Content/Examples/hda/subnet_test.2.0.hda new file mode 100644 index 0000000000000000000000000000000000000000..0946cdccd747c76895bd41fa7ccac6b73e323f4f GIT binary patch literal 4997 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 Q2o;JRNc6NGO2mc#1?02$WB>pF literal 0 HcmV?d00001 diff --git a/Content/Examples/hda/subnet_test_2_0.uasset b/Content/Examples/hda/subnet_test_2_0.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0e8bd0b3c0d36cfda7c5d2b6d5b43c4401970214 GIT binary patch literal 7189 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 R8yN2J``gcNhv9?Ze*x?k+64dr literal 0 HcmV?d00001 diff --git a/Content/Python/HoudiniEngineV2/__init__.py b/Content/Python/HoudiniEngineV2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Content/Python/HoudiniEngineV2/asyncprocessor.py b/Content/Python/HoudiniEngineV2/asyncprocessor.py new file mode 100644 index 000000000..57f8a8b80 --- /dev/null +++ b/Content/Python/HoudiniEngineV2/asyncprocessor.py @@ -0,0 +1,530 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unreal + + +class ProcessHDA(object): + """ An object that wraps async processing of an HDA (instantiating, + cooking/processing/baking an HDA), with functions that are called at the + various stages of the process, that can be overridden by subclasses for + custom funtionality: + + - on_failure() + - on_complete(): upon successful completion (could be PostInstantiation + if auto cook is disabled, PostProcessing if auto bake is disabled, or + after PostAutoBake if auto bake is enabled. + - on_pre_instantiation(): before the HDA is instantiated, a good place + to set parameter values before the first cook. + - on_post_instantiation(): after the HDA is instantiated, a good place + to set/configure inputs before the first cook. + - on_post_auto_cook(): right after a cook + - on_pre_process(): after a cook but before output objects have been + created/processed + - on_post_processing(): after output objects have been created + - on_post_auto_bake(): after outputs have been baked + + Instantiate the processor via the constructor and then call the activate() + function to start the asynchronous process. + + """ + def __init__( + self, + houdini_asset, + instantiate_at=unreal.Transform(), + parameters=None, + node_inputs=None, + parameter_inputs=None, + world_context_object=None, + spawn_in_level_override=None, + enable_auto_cook=True, + enable_auto_bake=False, + bake_directory_path="", + bake_method=unreal.HoudiniEngineBakeOption.TO_ACTOR, + remove_output_after_bake=False, + recenter_baked_actors=False, + replace_previous_bake=False, + delete_instantiated_asset_on_completion_or_failure=False): + """ Instantiates an HDA in the specified world/level. Sets parameters + and inputs supplied in InParameters, InNodeInputs and parameter_inputs. + If bInEnableAutoCook is true, cooks the HDA. If bInEnableAutoBake is + true, bakes the cooked outputs according to the supplied baking + parameters. + + This all happens asynchronously, with the various output pins firing at + the various points in the process: + + - PreInstantiation: before the HDA is instantiated, a good place + to set parameter values before the first cook (parameter values + from ``parameters`` are automatically applied at this point) + - PostInstantiation: after the HDA is instantiated, a good place + to set/configure inputs before the first cook (inputs from + ``node_inputs`` and ``parameter_inputs`` are automatically applied + at this point) + - PostAutoCook: right after a cook + - PreProcess: after a cook but before output objects have been + created/processed + - PostProcessing: after output objects have been created + - PostAutoBake: after outputs have been baked + - Completed: upon successful completion (could be PostInstantiation + if auto cook is disabled, PostProcessing if auto bake is disabled, + or after PostAutoBake if auto bake is enabled). + - Failed: If the process failed at any point. + + Args: + houdini_asset (HoudiniAsset): The HDA to instantiate. + instantiate_at (Transform): The Transform to instantiate the HDA with. + parameters (Map(Name, HoudiniParameterTuple)): The parameters to set before cooking the instantiated HDA. + node_inputs (Map(int32, HoudiniPublicAPIInput)): The node inputs to set before cooking the instantiated HDA. + parameter_inputs (Map(Name, HoudiniPublicAPIInput)): The parameter-based inputs to set before cooking the instantiated HDA. + world_context_object (Object): A world context object for identifying the world to spawn in, if spawn_in_level_override is null. + spawn_in_level_override (Level): If not nullptr, then the HoudiniAssetActor is spawned in that level. If both spawn_in_level_override and world_context_object are null, then the actor is spawned in the current editor context world's current level. + enable_auto_cook (bool): If true (the default) the HDA will cook automatically after instantiation and after parameter, transform and input changes. + enable_auto_bake (bool): If true, the HDA output is automatically baked after a cook. Defaults to false. + bake_directory_path (str): The directory to bake to if the bake path is not set via attributes on the HDA output. + bake_method (HoudiniEngineBakeOption): The bake target (to actor vs blueprint). @see HoudiniEngineBakeOption. + remove_output_after_bake (bool): If true, HDA temporary outputs are removed after a bake. Defaults to false. + recenter_baked_actors (bool): Recenter the baked actors to their bounding box center. Defaults to false. + replace_previous_bake (bool): If true, on every bake replace the previous bake's output (assets + actors) with the new bake's output. Defaults to false. + delete_instantiated_asset_on_completion_or_failure (bool): If true, deletes the instantiated asset actor on completion or failure. Defaults to false. + + """ + super(ProcessHDA, self).__init__() + self._houdini_asset = houdini_asset + self._instantiate_at = instantiate_at + self._parameters = parameters + self._node_inputs = node_inputs + self._parameter_inputs = parameter_inputs + self._world_context_object = world_context_object + self._spawn_in_level_override = spawn_in_level_override + self._enable_auto_cook = enable_auto_cook + self._enable_auto_bake = enable_auto_bake + self._bake_directory_path = bake_directory_path + self._bake_method = bake_method + self._remove_output_after_bake = remove_output_after_bake + self._recenter_baked_actors = recenter_baked_actors + self._replace_previous_bake = replace_previous_bake + self._delete_instantiated_asset_on_completion_or_failure = delete_instantiated_asset_on_completion_or_failure + + self._asset_wrapper = None + self._cook_success = False + self._bake_success = False + + @property + def asset_wrapper(self): + """ The asset wrapper for the instantiated HDA processed by this node. """ + return self._asset_wrapper + + @property + def cook_success(self): + """ True if the last cook was successful. """ + return self._cook_success + + @property + def bake_success(self): + """ True if the last bake was successful. """ + return self._bake_success + + @property + def houdini_asset(self): + """ The HDA to instantiate. """ + return self._houdini_asset + + @property + def instantiate_at(self): + """ The transform the instantiate the asset with. """ + return self._instantiate_at + + @property + def parameters(self): + """ The parameters to set on on_pre_instantiation """ + return self._parameters + + @property + def node_inputs(self): + """ The node inputs to set on on_post_instantiation """ + return self._node_inputs + + @property + def parameter_inputs(self): + """ The object path parameter inputs to set on on_post_instantiation """ + return self._parameter_inputs + + @property + def world_context_object(self): + """ The world context object: spawn in this world if spawn_in_level_override is not set. """ + return self._world_context_object + + @property + def spawn_in_level_override(self): + """ The level to spawn in. If both this and world_context_object is not set, spawn in the editor context's level. """ + return self._spawn_in_level_override + + @property + def enable_auto_cook(self): + """ Whether to set the instantiated asset to auto cook. """ + return self._enable_auto_cook + + @property + def enable_auto_bake(self): + """ Whether to set the instantiated asset to auto bake after a cook. """ + return self._enable_auto_bake + + @property + def bake_directory_path(self): + """ Set the fallback bake directory, for if output attributes do not specify it. """ + return self._bake_directory_path + + @property + def bake_method(self): + """ The bake method/target: for example, to actors vs to blueprints. """ + return self._bake_method + + @property + def remove_output_after_bake(self): + """ Remove temporary HDA output after a bake. """ + return self._remove_output_after_bake + + @property + def recenter_baked_actors(self): + """ Recenter the baked actors at their bounding box center. """ + return self._recenter_baked_actors + + @property + def replace_previous_bake(self): + """ Replace previous bake output on each bake. For the purposes of this + node, this would mostly apply to .uassets and not actors. + + """ + return self._replace_previous_bake + + @property + def delete_instantiated_asset_on_completion_or_failure(self): + """ Whether or not to delete the instantiated asset after Complete is called. """ + return self._delete_instantiated_asset_on_completion_or_failure + + def activate(self): + """ Activate the process. This will: + + - instantiate houdini_asset and wrap it as asset_wrapper + - call on_failure() for any immediate failures + - otherwise bind to delegates from asset_wrapper so that the + various self.on_*() functions are called as appropriate + + Returns immediately (does not block until cooking/processing is + complete). + + Returns: + (bool): False if activation failed. + + """ + # Get the API instance + houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + if not houdini_api: + # Handle failures: this will unbind delegates and call on_failure() + self._handle_on_failure() + return False + + # Create an empty API asset wrapper + self._asset_wrapper = unreal.HoudiniPublicAPIAssetWrapper.create_empty_wrapper(houdini_api) + if not self._asset_wrapper: + # Handle failures: this will unbind delegates and call on_failure() + self._handle_on_failure() + return False + + # Bind to the wrapper's delegates for instantiation, cooking, baking + # etc events + self._asset_wrapper.on_pre_instantiation_delegate.add_callable( + self._handle_on_pre_instantiation) + self._asset_wrapper.on_post_instantiation_delegate.add_callable( + self._handle_on_post_instantiation) + self._asset_wrapper.on_post_cook_delegate.add_callable( + self._handle_on_post_auto_cook) + self._asset_wrapper.on_pre_process_state_exited_delegate.add_callable( + self._handle_on_pre_process) + self._asset_wrapper.on_post_processing_delegate.add_callable( + self._handle_on_post_processing) + self._asset_wrapper.on_post_bake_delegate.add_callable( + self._handle_on_post_auto_bake) + + # Begin the instantiation process of houdini_asset and wrap it with + # self.asset_wrapper + if not houdini_api.instantiate_asset_with_existing_wrapper( + self.asset_wrapper, + self.houdini_asset, + self.instantiate_at, + self.world_context_object, + self.spawn_in_level_override, + self.enable_auto_cook, + self.enable_auto_bake, + self.bake_directory_path, + self.bake_method, + self.remove_output_after_bake, + self.recenter_baked_actors, + self.replace_previous_bake): + # Handle failures: this will unbind delegates and call on_failure() + self._handle_on_failure() + return False + + return True + + def _unbind_delegates(self): + """ Unbinds from self.asset_wrapper's delegates (if valid). """ + if not self._asset_wrapper: + return + + self._asset_wrapper.on_pre_instantiation_delegate.add_callable( + self._handle_on_pre_instantiation) + self._asset_wrapper.on_post_instantiation_delegate.add_callable( + self._handle_on_post_instantiation) + self._asset_wrapper.on_post_cook_delegate.add_callable( + self._handle_on_post_auto_cook) + self._asset_wrapper.on_pre_process_state_exited_delegate.add_callable( + self._handle_on_pre_process) + self._asset_wrapper.on_post_processing_delegate.add_callable( + self._handle_on_post_processing) + self._asset_wrapper.on_post_bake_delegate.add_callable( + self._handle_on_post_auto_bake) + + def _check_wrapper(self, wrapper): + """ Checks that wrapper matches self.asset_wrapper. Logs a warning if + it does not. + + Args: + wrapper (HoudiniPublicAPIAssetWrapper): the wrapper to check + against self.asset_wrapper + + Returns: + (bool): True if the wrappers match. + + """ + if wrapper != self._asset_wrapper: + unreal.log_warning( + '[UHoudiniPublicAPIProcessHDANode] Received delegate event ' + 'from unexpected asset wrapper ({0} vs {1})!'.format( + self._asset_wrapper.get_name() if self._asset_wrapper else '', + wrapper.get_name() if wrapper else '' + ) + ) + return False + return True + + def _handle_on_failure(self): + """ Handle any failures during the lifecycle of the process. Calls + self.on_failure() and then unbinds from self.asset_wrapper and + optionally deletes the instantiated asset. + + """ + self.on_failure() + + self._unbind_delegates() + + if self.delete_instantiated_asset_on_completion_or_failure and self.asset_wrapper: + self.asset_wrapper.delete_instantiated_asset() + + def _handle_on_complete(self): + """ Handles completion of the process. This can happen at one of + three stages: + + - After on_post_instantiate(), if enable_auto_cook is False. + - After on_post_auto_cook(), if enable_auto_cook is True but + enable_auto_bake is False. + - After on_post_auto_bake(), if both enable_auto_cook and + enable_auto_bake are True. + + Calls self.on_complete() and then unbinds from self.asset_wrapper's + delegates and optionally deletes the instantiated asset. + + """ + self.on_complete() + + self._unbind_delegates() + + if self.delete_instantiated_asset_on_completion_or_failure and self.asset_wrapper: + self.asset_wrapper.delete_instantiated_asset() + + def _handle_on_pre_instantiation(self, wrapper): + """ Called during pre_instantiation. Sets ``parameters`` on the HDA + and calls self.on_pre_instantiation(). + + """ + if not self._check_wrapper(wrapper): + return + + # Set any parameters specified for the HDA + if self.asset_wrapper and self.parameters: + self.asset_wrapper.set_parameter_tuples(self.parameters) + + self.on_pre_instantiation() + + def _handle_on_post_instantiation(self, wrapper): + """ Called during post_instantiation. Sets inputs (``node_inputs`` and + ``parameter_inputs``) on the HDA and calls self.on_post_instantiation(). + + Completes execution if enable_auto_cook is False. + + """ + if not self._check_wrapper(wrapper): + return + + # Set any inputs specified when the node was created + if self.asset_wrapper: + if self.node_inputs: + self.asset_wrapper.set_inputs_at_indices(self.node_inputs) + if self.parameter_inputs: + self.asset_wrapper.set_input_parameters(self.parameter_inputs) + + self.on_post_instantiation() + + # If not set to auto cook, complete execution now + if not self.enable_auto_cook: + self._handle_on_complete() + + def _handle_on_post_auto_cook(self, wrapper, cook_success): + """ Called during post_cook. Sets self.cook_success and calls + self.on_post_auto_cook(). + + Args: + cook_success (bool): True if the cook was successful. + + """ + if not self._check_wrapper(wrapper): + return + + self._cook_success = cook_success + + self.on_post_auto_cook(cook_success) + + def _handle_on_pre_process(self, wrapper): + """ Called during pre_process. Calls self.on_pre_process(). + + """ + if not self._check_wrapper(wrapper): + return + + self.on_pre_process() + + def _handle_on_post_processing(self, wrapper): + """ Called during post_processing. Calls self.on_post_processing(). + + Completes execution if enable_auto_bake is False. + + """ + if not self._check_wrapper(wrapper): + return + + self.on_post_processing() + + # If not set to auto bake, complete execution now + if not self.enable_auto_bake: + self._handle_on_complete() + + def _handle_on_post_auto_bake(self, wrapper, bake_success): + """ Called during post_bake. Sets self.bake_success and calls + self.on_post_auto_bake(). + + Args: + bake_success (bool): True if the bake was successful. + + """ + if not self._check_wrapper(wrapper): + return + + self._bake_success = bake_success + + self.on_post_auto_bake(bake_success) + + self._handle_on_complete() + + def on_failure(self): + """ Called if the process fails to instantiate or fails to start + a cook. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_complete(self): + """ Called if the process completes instantiation, cook and/or baking, + depending on enable_auto_cook and enable_auto_bake. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_pre_instantiation(self): + """ Called during pre_instantiation. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_instantiation(self): + """ Called during post_instantiation. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_auto_cook(self, cook_success): + """ Called during post_cook. + + Subclasses can override this function implement custom functionality. + + Args: + cook_success (bool): True if the cook was successful. + + """ + pass + + def on_pre_process(self): + """ Called during pre_process. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_processing(self): + """ Called during post_processing. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_auto_bake(self, bake_success): + """ Called during post_bake. + + Subclasses can override this function implement custom functionality. + + Args: + bake_success (bool): True if the bake was successful. + + """ + pass diff --git a/Content/Python/__init__.py b/Content/Python/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/HoudiniEngine.uplugin b/HoudiniEngine.uplugin index 8784d7118..6258a8302 100644 --- a/HoudiniEngine.uplugin +++ b/HoudiniEngine.uplugin @@ -1,8 +1,8 @@ { "FileVersion" : 3, "FriendlyName" : "Houdini Engine v2", - "Version" : 18050563, - "VersionName" : "v2.0 - H18.5.563", + "Version" : 18050596, + "VersionName" : "v2.0 - H18.5.596", "CreatedBy" : "Side Effects Software Inc.", "CreatedByURL" : "http://www.sidefx.com", "DocsURL" : "http://www.sidefx.com/docs/unreal/", diff --git a/LICENSE.md b/LICENSE.md index 109dbc2ff..1fffeda34 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,93 +1,93 @@ - ALPHA AND BETA SOFTWARE - CONFIDENTIAL DISCLOSURE AGREEMENT -Revised 10/2011 -This Agreement is made today between Side Effects Software Inc., a corporation -incorporated under the laws of Ontario, Canada and having a place of business -at 123 Front Street West, Suite 1401, Toronto ("Side Effects Software") and -you ("Beta Tester"). - -BACKGROUND: - -1. Side Effects Software is in the business of developing and marketing certain - computer graphics software and related materials. -2. Beta Tester, in order to permit Side Effects Software in refining and - perfecting such software and materials, has expressed an interest in testing - certain alpha/beta versions of software more fully described in Schedule A - (the "Software & Materials"). -3. Each Animator, as an employee, contractor or agent of the Beta Tester, may - have access to the Software & Materials and perhaps to other confidential - information of Side Effects Software such as trade secrets, business or - product plans, which might be disclosed during the course of the software - testing (the "Confidential Information"). -4. Side Effects Software wishes to ensure that the Software & Materials are not - used by Beta Tester for purposes other than alpha/beta testing and that they - are not disclosed to any other party without the prior written consent of - Side Effects Software; - -NOW THEREFORE, in consideration of this background and the provision of -such materials to Beta Tester and other good and valuable consideration (the -receipt and sufficiency of which are hereby acknowledged), Beta Tester agrees -with Side Effects Software as follows: - -1. Side Effects Software hereby grants to Beta Tester on the terms set out - herein a personal, non-transferable and non-exclusive license to use the - object code version of the Software & Materials for its internal operations - on its computers. Any commercial exploitation of the Software is at the - Beta Tester's risk. Beta Tester's right to use the Software & Materials is - limited to those rights expressly set out in this Agreement. Beta Tester - shall carry out testing of the Software & Materials in accordance with such - reasonable instructions as Side Effects Software may provide to it from time - to time. -2. Beta Tester shall use all reasonable efforts (which shall consist of at - least the same level of diligence as it uses to protect its own proprietary - information and trade secrets) to protect the confidentiality of all - Software & Materials, including all product features, and other Confidential - Information of Side Effects Software that may come to the attention of or - knowledge of Beta Tester as a result of undertaking such testing. Beta - Tester shall not discuss product features or show the Software & Materials - to anyone. Beta Tester shall not copy, publish, disclose, attempt to - recreate the source code version of the Software or make any use other than - as contemplated herein of any of the Software & Material or any such - Confidential Information. For the purposes hereof, Confidential Information - shall not include any information that: - - At the time of such disclosure, is generally available to the public - through no fault of Beta Tester; - - Was in possession of Beta Tester without any obligation of confidentiality - prior to the date hereof and was not acquired directly or indirectly from - Side Effects Software; or - - Was received by Beta Tester after the date hereof from a third party who - imposed no obligation of confidentiality and who did not acquire any such - information directly or indirectly from Side Effects Software. -3. Beta Tester shall not communicate or otherwise disclose to Side Effects - Software during the term of this Agreement any confidential or proprietary - information of any other third party. -4. In accepting this Agreement the Beta Tester agrees to test and evaluate the - Software & Materials and to report all problems, concerns, deficiencies and - suggestions for improvements to Side Effects Software. A representative from - Side Effects Software may be contacting Beta Tester weekly for a report. -5. Upon completion of such testing or at any time on the request of Side - Effects Software, Beta Tester shall promptly return to Side Effects Software - all copies of the Software & Materials, as well as any Confidential - Information, then in its possession or control and shall, if requested, - provide Side Effects Software with a certificate signed by an authorized - representative of Beta Tester to such effect from an officer of Beta Tester. -6. All Software & Materials, as well as any Confidential Information, is - provided "as is". Side Effects Software makes no representation, warranty or - guarantee with respect to any such material and assumes no liability for the - use and performance of any alpha and beta software. Side Effects Software - reserves the right to alter all aspects of the Software and Documentation - from one alpha or beta version to the next, including the user interface, - screen displays, fonts and functionality. -7. The Software will timeout and cease to function one month after its build - date, regardless of when it was downloaded or installed. - -SCHEDULE A -SOFTWARE AND MATERIALS -The following Software and related Materials are bound by the attached Alpha -and Beta Software -Test Agreement: -Software: Houdini Engine for Unreal -Version: Version 2.0 - alpha - -You must accept these terms and conditions to install the Software and -Materials. + ALPHA AND BETA SOFTWARE + CONFIDENTIAL DISCLOSURE AGREEMENT +Revised 10/2011 +This Agreement is made today between Side Effects Software Inc., a corporation +incorporated under the laws of Ontario, Canada and having a place of business +at 123 Front Street West, Suite 1401, Toronto ("Side Effects Software") and +you ("Beta Tester"). + +BACKGROUND: + +1. Side Effects Software is in the business of developing and marketing certain + computer graphics software and related materials. +2. Beta Tester, in order to permit Side Effects Software in refining and + perfecting such software and materials, has expressed an interest in testing + certain alpha/beta versions of software more fully described in Schedule A + (the "Software & Materials"). +3. Each Animator, as an employee, contractor or agent of the Beta Tester, may + have access to the Software & Materials and perhaps to other confidential + information of Side Effects Software such as trade secrets, business or + product plans, which might be disclosed during the course of the software + testing (the "Confidential Information"). +4. Side Effects Software wishes to ensure that the Software & Materials are not + used by Beta Tester for purposes other than alpha/beta testing and that they + are not disclosed to any other party without the prior written consent of + Side Effects Software; + +NOW THEREFORE, in consideration of this background and the provision of +such materials to Beta Tester and other good and valuable consideration (the +receipt and sufficiency of which are hereby acknowledged), Beta Tester agrees +with Side Effects Software as follows: + +1. Side Effects Software hereby grants to Beta Tester on the terms set out + herein a personal, non-transferable and non-exclusive license to use the + object code version of the Software & Materials for its internal operations + on its computers. Any commercial exploitation of the Software is at the + Beta Tester's risk. Beta Tester's right to use the Software & Materials is + limited to those rights expressly set out in this Agreement. Beta Tester + shall carry out testing of the Software & Materials in accordance with such + reasonable instructions as Side Effects Software may provide to it from time + to time. +2. Beta Tester shall use all reasonable efforts (which shall consist of at + least the same level of diligence as it uses to protect its own proprietary + information and trade secrets) to protect the confidentiality of all + Software & Materials, including all product features, and other Confidential + Information of Side Effects Software that may come to the attention of or + knowledge of Beta Tester as a result of undertaking such testing. Beta + Tester shall not discuss product features or show the Software & Materials + to anyone. Beta Tester shall not copy, publish, disclose, attempt to + recreate the source code version of the Software or make any use other than + as contemplated herein of any of the Software & Material or any such + Confidential Information. For the purposes hereof, Confidential Information + shall not include any information that: + - At the time of such disclosure, is generally available to the public + through no fault of Beta Tester; + - Was in possession of Beta Tester without any obligation of confidentiality + prior to the date hereof and was not acquired directly or indirectly from + Side Effects Software; or + - Was received by Beta Tester after the date hereof from a third party who + imposed no obligation of confidentiality and who did not acquire any such + information directly or indirectly from Side Effects Software. +3. Beta Tester shall not communicate or otherwise disclose to Side Effects + Software during the term of this Agreement any confidential or proprietary + information of any other third party. +4. In accepting this Agreement the Beta Tester agrees to test and evaluate the + Software & Materials and to report all problems, concerns, deficiencies and + suggestions for improvements to Side Effects Software. A representative from + Side Effects Software may be contacting Beta Tester weekly for a report. +5. Upon completion of such testing or at any time on the request of Side + Effects Software, Beta Tester shall promptly return to Side Effects Software + all copies of the Software & Materials, as well as any Confidential + Information, then in its possession or control and shall, if requested, + provide Side Effects Software with a certificate signed by an authorized + representative of Beta Tester to such effect from an officer of Beta Tester. +6. All Software & Materials, as well as any Confidential Information, is + provided "as is". Side Effects Software makes no representation, warranty or + guarantee with respect to any such material and assumes no liability for the + use and performance of any alpha and beta software. Side Effects Software + reserves the right to alter all aspects of the Software and Documentation + from one alpha or beta version to the next, including the user interface, + screen displays, fonts and functionality. +7. The Software will timeout and cease to function one month after its build + date, regardless of when it was downloaded or installed. + +SCHEDULE A +SOFTWARE AND MATERIALS +The following Software and related Materials are bound by the attached Alpha +and Beta Software +Test Agreement: +Software: Houdini Engine for Unreal +Version: Version 2.0 - alpha + +You must accept these terms and conditions to install the Software and +Materials. diff --git a/README.md b/README.md index a09e3f438..09588d4f1 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,106 @@ -# Houdini Engine for Unreal - Version 2.0 - -Welcome to the repository for Version 2 of the Houdini Engine For Unreal Plugin. - -This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. - -Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. - -Here are some of the new features and improvements currently available: - - -Core: -- New and redesigned core architecture, more modular and lightweight. - All the Houdini Engine/HAPI logic is now Editor-only and contained in the “HoudiniEngine” module. All the custom runtime components and actors used by the plugin now simply acts as data-holders, and are processed by the HoudiniEngine modules, removing the need to bake HDA before packaging a game. -- The plugin now relies exclusively on native, UProperties based serialization, so operations like cut and paste, move between level, duplicate etc.. do not exhibit any of the issues that version 1 had with those operations. - -Outputs: -- Static Mesh creation time has been optimized and now uses Mesh Descriptions. -- Alternatively, you can also decide to use an even faster Proxy Mesh generation while editing the HDA. -  Those can then be automatically refined to Static Meshes, either on a timer, or when saving/playing the level. -- World composition support: Tiled heightfields can now be baked to multiple landscape actors/steaming proxies, and will create/update the levels needed for world composition. -  You can specify the level's path by using the new “unreal_level_path” attribute, that is also used by meshes and instancers so they can also be baked out to separate levels. -- Material overrides and generic uproperty attributes can either be applied on the instancer, or per instance (when using mesh split instancers or instanced Actors). -- It is possible to create foliage instances directly, without baking, when using the “unreal_foliage” attribute on an instancer. -- A class can be directly instantiated by the "unreal_instance" attribute (ie “PointLight”, “AudioVolume”… ). -- Curves can be outputed to SplineComponents by using the "unreal_output_curve" primitive attribute. - -Inputs: - -- Colliders on a Static Mesh can now be imported as group geometry. -- World inputs can now read data from BSP brushes. -- Instancers and Foliage are now imported as packed primitives. -- World inputs have an improved bound selector mode, that lets them send all the actors and objects contained in the bounds of the selected object. -- World inputs can now import data from all supported input objects (landscape, houdini asset actors..) -- World inputs can now import data from actors placed in a different level than the Houdini Asset Actors's. -- A single curve input can now create and import any number of curves. -- You can alt-click on curve inputs or editable curves to create new points. -  -Parameters: -- HDA parameters and inputs editing now support multi-selection. -- Parameter UI/UX has been improved: -- Folder UI (tabs, radio, collapsible) has been improved -- Ramps UI has been improved, and it is easy to turn off auto-update while editing them. -- When an asset is dropped on a string parameter, it automatically sets its value to the asset ref. -- String parameters can now be turned into an asset picker via the “unreal_ref” tag. -- Support for File parameters has been improved (custom extension, directory, new file...) -- Multi-line strings, Column Labels, Button Strip, Log Int and Floats are now supported. - -General: -- The plugin's UI has been completely revamped, a new Houdini Engine menu has been added to the editor. -- bgeo/bgeo.sc files can be imported natively in the content browser (Mesh and instancers). -- The PDG Asset Link has been added, allowing control of TOP networks nested in HDAs, and works similarly to the one in the Unity plugin. -- Session Sync is supported, allowing the plugin to connect to a session of Houdini Engine running inside Houdini. -  The state of Houdini Engine can be viewed in Houdini while working with the plugin in Unreal, and changes on either end, whether in Unreal via the plugin or in Houdini via its various interfaces, will be synchronized across so that both applications will be able to make changes and see the same results. -- Blueprint support: It is now possible to use Houdini Asset Components in the Blueprint Editor. - This lets you preset and use HDAs on Blueprint Actors, and changing parameters/inputs on the Houdini Asset will automatically update all placed instances of that Blueprint. - - -For more details on the new features and improvements available, please visit the [Wiki](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/wiki/What's-new-%3F). -Documentation for version 2.0 of the plugin is also available on the Side FX [Website](https://www.sidefx.com/docs/unreal/). - - -# Feedback - -Please send bug reports, feature requests and questions to [Side FX's support](https://www.sidefx.com/bugs/submit/). - - -# Compatibility - -Currently, [Version 2.0](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/releases) has binaries that have been built for UE4.26 and UE4.25, and is linked with the latest production build of Houdini, H18.5.462. - -Source code for the plugin is available on this repository for UE4.26, UE4.25, UE4.24, UE4.23 and the master branch of Unreal (4.27). - -Version 2 is also partially backward compatible with version 1 of the Houdini Engine for Unreal plugin. - -When loading a level that contains Houdini objects made with version 1, the plugin will attempt to convert the V1 components, parameters, inputs and outputs to their v2 equivalents. - -Some HDAs might need to be rebuilt after the conversion for their parameters and inputs to be displayed properly by the v2 plugin. - -The conversion of the legacy v1 data is still in progress and will be improved upon in the future. -However, the Houdini Digital Assets themselves (the HDA files), that were created for version 1 of the plugin are fully compatible with version 2, as it supports most of version 1 workflows. - -# Installing the plugin - -01. Download the pre-built binaries of the plugin in the "Releases" section of this repository. - -01. Extract the "HoudiniEngine" folder in the release to the "Plugins/Runtime" folder of Unreal. - You can install the plugin either directly in the engine folder (in "Engine/Plugins/Runtime/HoudiniEngine") or in your project folder (in "Plugins/Runtime/HoudiniEngine"). -01. Start Unreal Engine, open the Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. -01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". -01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. -01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters and inputs in the `Details` panel. Modifying any of the parameters will force the asset to recook and possibly update its geometry. - - -# Building from source - -01. Get the UE4 source code from: https://github.com/EpicGames/UnrealEngine/releases -01. Within the UE4 source, navigate to `Engine/Plugins/Runtime`, and clone this repo into a folder named `HoudiniEngine`. -01. Download and install the correct build of 64-bit Houdini. To get the build number, look at the header of `Source/HoudiniEngine/HoudiniEngine.Build.cs`, under `Houdini Version`. -01. Generate the UE4 Project Files (by running `GenerateProjectFiles`) and build Unreal, either in x64 `Debug Editor` or x64 `Development Editor`. -01. When starting the Unreal Engine editor, go to Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. -01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". -01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. -01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters in the `Details` pane. Modifying any of the parameters will force the asset to recook and possibly update its geometry. - - +# Houdini Engine for Unreal - Version 2.0 + +Welcome to the repository for Version 2 of the Houdini Engine For Unreal Plugin. + +This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. + +Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. + +Here are some of the new features and improvements currently available: + + +Core: +- New and redesigned core architecture, more modular and lightweight. + All the Houdini Engine/HAPI logic is now Editor-only and contained in the “HoudiniEngine” module. All the custom runtime components and actors used by the plugin now simply acts as data-holders, and are processed by the HoudiniEngine modules, removing the need to bake HDA before packaging a game. +- The plugin now relies exclusively on native, UProperties based serialization, so operations like cut and paste, move between level, duplicate etc.. do not exhibit any of the issues that version 1 had with those operations. + +Outputs: +- Static Mesh creation time has been optimized and now uses Mesh Descriptions. +- Alternatively, you can also decide to use an even faster Proxy Mesh generation while editing the HDA. +  Those can then be automatically refined to Static Meshes, either on a timer, or when saving/playing the level. +- World composition support: Tiled heightfields can now be baked to multiple landscape actors/steaming proxies, and will create/update the levels needed for world composition. +  You can specify the level's path by using the new “unreal_level_path” attribute, that is also used by meshes and instancers so they can also be baked out to separate levels. +- Material overrides and generic uproperty attributes can either be applied on the instancer, or per instance (when using mesh split instancers or instanced Actors). +- It is possible to create foliage instances directly, without baking, when using the “unreal_foliage” attribute on an instancer. +- A class can be directly instantiated by the "unreal_instance" attribute (ie “PointLight”, “AudioVolume”… ). +- Curves can be outputed to SplineComponents by using the "unreal_output_curve" primitive attribute. + +Inputs: + +- Colliders on a Static Mesh can now be imported as group geometry. +- World inputs can now read data from BSP brushes. +- Instancers and Foliage are now imported as packed primitives. +- World inputs have an improved bound selector mode, that lets them send all the actors and objects contained in the bounds of the selected object. +- World inputs can now import data from all supported input objects (landscape, houdini asset actors..) +- World inputs can now import data from actors placed in a different level than the Houdini Asset Actors's. +- A single curve input can now create and import any number of curves. +- You can alt-click on curve inputs or editable curves to create new points. +  +Parameters: +- HDA parameters and inputs editing now support multi-selection. +- Parameter UI/UX has been improved: +- Folder UI (tabs, radio, collapsible) has been improved +- Ramps UI has been improved, and it is easy to turn off auto-update while editing them. +- When an asset is dropped on a string parameter, it automatically sets its value to the asset ref. +- String parameters can now be turned into an asset picker via the “unreal_ref” tag. +- Support for File parameters has been improved (custom extension, directory, new file...) +- Multi-line strings, Column Labels, Button Strip, Log Int and Floats are now supported. + +General: +- The plugin's UI has been completely revamped, a new Houdini Engine menu has been added to the editor. +- bgeo/bgeo.sc files can be imported natively in the content browser (Mesh and instancers). +- The PDG Asset Link has been added, allowing control of TOP networks nested in HDAs, and works similarly to the one in the Unity plugin. +- Session Sync is supported, allowing the plugin to connect to a session of Houdini Engine running inside Houdini. +  The state of Houdini Engine can be viewed in Houdini while working with the plugin in Unreal, and changes on either end, whether in Unreal via the plugin or in Houdini via its various interfaces, will be synchronized across so that both applications will be able to make changes and see the same results. +- Blueprint support: It is now possible to use Houdini Asset Components in the Blueprint Editor. + This lets you preset and use HDAs on Blueprint Actors, and changing parameters/inputs on the Houdini Asset will automatically update all placed instances of that Blueprint. + + +For more details on the new features and improvements available, please visit the [Wiki](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/wiki/What's-new-%3F). +Documentation for version 2.0 of the plugin is also available on the Side FX [Website](https://www.sidefx.com/docs/unreal/). + + +# Feedback + +Please send bug reports, feature requests and questions to [Side FX's support](https://www.sidefx.com/bugs/submit/). + + +# Compatibility + +Currently, [Version 2.0](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/releases) has binaries that have been built for UE4.26 and UE4.25, and is linked with the latest production build of Houdini, H18.5.462. + +Source code for the plugin is available on this repository for UE4.26, UE4.25, UE4.24, UE4.23 and the master branch of Unreal (4.27). + +Version 2 is also partially backward compatible with version 1 of the Houdini Engine for Unreal plugin. + +When loading a level that contains Houdini objects made with version 1, the plugin will attempt to convert the V1 components, parameters, inputs and outputs to their v2 equivalents. + +Some HDAs might need to be rebuilt after the conversion for their parameters and inputs to be displayed properly by the v2 plugin. + +The conversion of the legacy v1 data is still in progress and will be improved upon in the future. +However, the Houdini Digital Assets themselves (the HDA files), that were created for version 1 of the plugin are fully compatible with version 2, as it supports most of version 1 workflows. + +# Installing the plugin + +01. Download the pre-built binaries of the plugin in the "Releases" section of this repository. + +01. Extract the "HoudiniEngine" folder in the release to the "Plugins/Runtime" folder of Unreal. + You can install the plugin either directly in the engine folder (in "Engine/Plugins/Runtime/HoudiniEngine") or in your project folder (in "Plugins/Runtime/HoudiniEngine"). +01. Start Unreal Engine, open the Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. +01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". +01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. +01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters and inputs in the `Details` panel. Modifying any of the parameters will force the asset to recook and possibly update its geometry. + + +# Building from source + +01. Get the UE4 source code from: https://github.com/EpicGames/UnrealEngine/releases +01. Within the UE4 source, navigate to `Engine/Plugins/Runtime`, and clone this repo into a folder named `HoudiniEngine`. +01. Download and install the correct build of 64-bit Houdini. To get the build number, look at the header of `Source/HoudiniEngine/HoudiniEngine.Build.cs`, under `Houdini Version`. +01. Generate the UE4 Project Files (by running `GenerateProjectFiles`) and build Unreal, either in x64 `Debug Editor` or x64 `Development Editor`. +01. When starting the Unreal Engine editor, go to Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. +01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". +01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. +01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters in the `Details` pane. Modifying any of the parameters will force the asset to recook and possibly update its geometry. + + diff --git a/Source/HoudiniEngine/HoudiniEngine.Build.cs b/Source/HoudiniEngine/HoudiniEngine.Build.cs index 3195cd118..76fd317f0 100644 --- a/Source/HoudiniEngine/HoudiniEngine.Build.cs +++ b/Source/HoudiniEngine/HoudiniEngine.Build.cs @@ -32,8 +32,8 @@ /* - Houdini Version: 18.5.563 - Houdini Engine Version: 3.6.1 + Houdini Version: 18.5.596 + Houdini Engine Version: 3.6.2 Unreal Version: 4.26.0 */ @@ -47,7 +47,7 @@ public class HoudiniEngine : ModuleRules { private string GetHFSPath() { - string HoudiniVersion = "18.5.563"; + string HoudiniVersion = "18.5.596"; bool bIsRelease = true; string HFSPath = ""; string RegistryPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Side Effects Software"; diff --git a/Source/HoudiniEngine/Private/HBSPOps.cpp b/Source/HoudiniEngine/Private/HBSPOps.cpp index e1101a6df..1fcee919d 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.cpp +++ b/Source/HoudiniEngine/Private/HBSPOps.cpp @@ -1,1483 +1,1483 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HBSPOps.h" -#include "EngineDefines.h" -#include "Model.h" -#include "Materials/Material.h" -#include "Engine/BrushBuilder.h" -#include "Editor/EditorEngine.h" -#include "Components/BrushComponent.h" -#include "GameFramework/Volume.h" - -DEFINE_LOG_CATEGORY_STATIC(LogBSPOps, Log, All); - -/** Errors encountered in Csg operation. */ -int32 FHBSPOps::GErrors = 0; -bool FHBSPOps::GFastRebuild = false; - -static void TagReferencedNodes( UModel *Model, int32 *NodeRef, int32 *PolyRef, int32 iNode ) -{ - FBspNode &Node = Model->Nodes[iNode]; - - NodeRef[iNode ] = 0; - PolyRef[Node.iSurf] = 0; - - if( Node.iFront != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iFront); - if( Node.iBack != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iBack ); - if( Node.iPlane != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iPlane); -} - -// -// Update a bounding volume by expanding it to enclose a list of polys. -// -static void UpdateBoundWithPolys( FBox& Bound, FPoly** PolyList, int32 nPolys ) -{ - for( int32 i=0; iVertices.Num(); j++ ) - Bound += PolyList[i]->Vertices[j]; -} - -// -// Update a convolution hull with a list of polys. -// -static void UpdateConvolutionWithPolys( UModel *Model, int32 iNode, FPoly **PolyList, int32 nPolys ) -{ - FBox Box(ForceInit); - - FBspNode &Node = Model->Nodes[iNode]; - Node.iCollisionBound = Model->LeafHulls.Num(); - for( int32 i=0; iiBrushPoly != INDEX_NONE ) - { - int32 j; - for( j=0; jiBrushPoly == PolyList[i]->iBrushPoly ) - break; - if( j >= i ) - Model->LeafHulls.Add(PolyList[i]->iBrushPoly); - } - for( int32 j=0; jVertices.Num(); j++ ) - Box += PolyList[i]->Vertices[j]; - } - Model->LeafHulls.Add(INDEX_NONE); - - // Add bounds. - Model->LeafHulls.Add( *(int32*)&Box.Min.X ); - Model->LeafHulls.Add( *(int32*)&Box.Min.Y ); - Model->LeafHulls.Add( *(int32*)&Box.Min.Z ); - Model->LeafHulls.Add( *(int32*)&Box.Max.X ); - Model->LeafHulls.Add( *(int32*)&Box.Max.Y ); - Model->LeafHulls.Add( *(int32*)&Box.Max.Z ); - -} - -// -// Cut a partitioning poly by a list of polys, and add the resulting inside pieces to the -// front list and back list. -// -static void SplitPartitioner -( - UModel* Model, - FPoly** PolyList, - FPoly** FrontList, - FPoly** BackList, - int32 n, - int32 nPolys, - int32& nFront, - int32& nBack, - FPoly InfiniteEdPoly, - TArray& AllocatedFPolys -) -{ - FPoly FrontPoly,BackPoly; - while( n < nPolys ) - { - FPoly* Poly = PolyList[n]; - switch( InfiniteEdPoly.SplitWithPlane(Poly->Vertices[0],Poly->Normal,&FrontPoly,&BackPoly,0) ) - { - case SP_Coplanar: - // May occasionally happen. -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got inficoplanar") ); - break; - - case SP_Front: - // Shouldn't happen if hull is correct. -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got infifront") ); - return; - - case SP_Split: - InfiniteEdPoly = BackPoly; - break; - - case SP_Back: - break; - } - n++; - } - - FPoly* New = new FPoly; - *New = InfiniteEdPoly; - New->Reverse(); - New->iBrushPoly |= 0x40000000; - FrontList[nFront++] = New; - AllocatedFPolys.Add( New ); - - New = new FPoly; - *New = InfiniteEdPoly; - BackList[nBack++] = New; - AllocatedFPolys.Add( New ); -} - -// -// Build an FPoly representing an "infinite" plane (which exceeds the maximum -// dimensions of the world in all directions) for a particular Bsp node. -// -FPoly FHBSPOps::BuildInfiniteFPoly( UModel* Model, int32 iNode ) -{ - FBspNode &Node = Model->Nodes [iNode ]; - FBspSurf &Poly = Model->Surfs [Node.iSurf ]; - FVector Base = Poly.Plane * Poly.Plane.W; - FVector Normal = Poly.Plane; - FVector Axis1,Axis2; - - // Find two non-problematic axis vectors. - Normal.FindBestAxisVectors( Axis1, Axis2 ); - - // Set up the FPoly. - FPoly EdPoly; - EdPoly.Init(); - EdPoly.Normal = Normal; - EdPoly.Base = Base; - new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX + Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX + Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX - Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX - Axis2*WORLD_MAX); - - return EdPoly; -} - -// -// Recursively filter a set of polys defining a convex hull down the Bsp, -// splitting it into two halves at each node and adding in the appropriate -// face polys at splits. -// -static void FilterBound -( - UModel* Model, - FBox* ParentBound, - int32 iNode, - FPoly** PolyList, - int32 nPolys, - int32 Outside -) -{ - FMemMark Mark(FMemStack::Get()); - FBspNode& Node = Model->Nodes [iNode]; - FBspSurf& Surf = Model->Surfs [Node.iSurf]; - FVector Base = Surf.Plane * Surf.Plane.W; - FVector& Normal = Model->Vectors[Surf.vNormal]; - FBox Bound(ForceInit); - - Bound.Min.X = Bound.Min.Y = Bound.Min.Z = +WORLD_MAX; - Bound.Max.X = Bound.Max.Y = Bound.Max.Z = -WORLD_MAX; - - // Split bound into front half and back half. - FPoly** FrontList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nFront=0; - FPoly** BackList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nBack=0; - - // Keeping track of allocated FPoly structures to delete later on. - TArray AllocatedFPolys; - - FPoly* FrontPoly = new FPoly; - FPoly* BackPoly = new FPoly; - - // Keep track of allocations. - AllocatedFPolys.Add( FrontPoly ); - AllocatedFPolys.Add( BackPoly ); - - for( int32 i=0; iSplitWithPlane( Base, Normal, FrontPoly, BackPoly, 0 ) ) - { - case SP_Coplanar: -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got coplanar") ); - FrontList[nFront++] = Poly; - BackList[nBack++] = Poly; - break; - - case SP_Front: - FrontList[nFront++] = Poly; - break; - - case SP_Back: - BackList[nBack++] = Poly; - break; - - case SP_Split: - FrontList[nFront++] = FrontPoly; - BackList [nBack++] = BackPoly; - - FrontPoly = new FPoly; - BackPoly = new FPoly; - - // Keep track of allocations. - AllocatedFPolys.Add( FrontPoly ); - AllocatedFPolys.Add( BackPoly ); - - break; - - default: - UE_LOG(LogBSPOps, Fatal, TEXT("FZoneFilter::FilterToLeaf: Unknown split code") ); - } - } - if( nFront && nBack ) - { - // Add partitioner plane to front and back. - FPoly InfiniteEdPoly = FHBSPOps::BuildInfiniteFPoly( Model, iNode ); - InfiniteEdPoly.iBrushPoly = iNode; - - SplitPartitioner(Model,PolyList,FrontList,BackList,0,nPolys,nFront,nBack,InfiniteEdPoly,AllocatedFPolys); - } - else - { -// if( !nFront ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty fronthull") ); -// if( !nBack ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty backhull") ); - } - - // Recursively update all our childrens' bounding volumes. - if( nFront > 0 ) - { - if( Node.iFront != INDEX_NONE ) - FilterBound( Model, &Bound, Node.iFront, FrontList, nFront, Outside || Node.IsCsg() ); - else if( Outside || Node.IsCsg() ) - UpdateBoundWithPolys( Bound, FrontList, nFront ); - else - UpdateConvolutionWithPolys( Model, iNode, FrontList, nFront ); - } - if( nBack > 0 ) - { - if( Node.iBack != INDEX_NONE) - FilterBound( Model, &Bound,Node.iBack, BackList, nBack, Outside && !Node.IsCsg() ); - else if( Outside && !Node.IsCsg() ) - UpdateBoundWithPolys( Bound, BackList, nBack ); - else - UpdateConvolutionWithPolys( Model, iNode, BackList, nBack ); - } - - // Update parent bound to enclose this bound. - if( ParentBound ) - *ParentBound += Bound; - - // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. - for( int32 i=0; i0); - - // No need to test if only one poly. - if( NumPolys==1 ) - return PolyList[0]; - - FPoly *Poly, *Best=NULL; - float Score, BestScore; - int32 i, Index, j, Inc; - int32 Splits, Front, Back, Coplanar, AllSemiSolids; - - //PortalBias -- added by Legend on 4/12/2000 - float PortalBias = InPortalBias / 100.0f; - Balance &= 0xFF; // keep only the low byte to recover "Balance" - //UE_LOG(LogBSPOps, Log, TEXT("Balance=%d PortalBias=%f"), Balance, PortalBias ); - - if (Opt==FHBSPOps::BSP_Optimal) Inc = 1; // Test lots of nodes. - else if (Opt==FHBSPOps::BSP_Good) Inc = FMath::Max(1,NumPolys/20); // Test 20 nodes. - else /* BSP_Lame */ Inc = FMath::Max(1,NumPolys/4); // Test 4 nodes. - - // See if there are any non-semisolid polygons here. - for( i=0; iPolyFlags & PF_AddLast) ) - break; - AllSemiSolids = (i>=NumPolys); - - // Search through all polygons in the pool and find: - // A. The number of splits each poly would make. - // B. The number of front and back nodes the polygon would create. - // C. Number of coplanars. - BestScore = 0; - for( i=0; iPolyFlags & PF_AddLast) && !(Poly->PolyFlags & PF_Portal) ) - && !AllSemiSolids ); - if( Index>=i+Inc || Index>=NumPolys ) - continue; - - for( j=0; jSplitWithPlaneFast( FPlane( Poly->Vertices[0], Poly->Normal), NULL, NULL ) ) - { - case SP_Coplanar: - Coplanar++; - break; - - case SP_Front: - Front++; - break; - - case SP_Back: - Back++; - break; - - case SP_Split: - // Disfavor splitting polys that are zone portals. - if( !(OtherPoly->PolyFlags & PF_Portal) ) - Splits++; - else - Splits += 16; - break; - } - } - // added by Legend 1/31/1999 - // Score optimization: minimize cuts vs. balance tree (as specified in BSP Rebuilder dialog) - Score = ( 100.0 - float(Balance) ) * Splits + float(Balance) * FMath::Abs( Front - Back ); - if( Poly->PolyFlags & PF_Portal ) - { - // PortalBias -- added by Legend on 4/12/2000 - // - // PortalBias enables level designers to control the effect of Portals on the BSP. - // This effect can range from 0.0 (ignore portals), to 1.0 (portals cut everything). - // - // In builds prior to this (since the 221 build dating back to 1/31/1999) the bias - // has been 1.0 causing the portals to cut the BSP in ways that will potentially - // degrade level performance, and increase the BSP complexity. - // - // By setting the bias to a value between 0.3 and 0.7 the positive effects of - // the portals are preserved without giving them unreasonable priority in the BSP. - // - // Portals should be weighted high enough in the BSP to separate major parts of the - // level from each other (pushing entire rooms down the branches of the BSP), but - // should not be so high that portals cut through adjacent geometry in a way that - // increases complexity of the room being (typically, accidentally) cut. - // - Score -= ( 100.0 - float(Balance) ) * Splits * PortalBias; // ignore PortalBias of the split polys -- bias toward portal selection for cutting planes! - } - //UE_LOG(LogBSPOps, Log, " %4d: Score = %f (Front = %4d, Back = %4d, Splits = %4d, Flags = %08X)", Index, Score, Front, Back, Splits, Poly->PolyFlags ); //LEC - - if( Score AllocatedFPolys; - - // To account for big EdPolys split up. - int32 NumPolysToAlloc = NumPolys + 8 + NumPolys/4; - int32 NumFront=0; FPoly **FrontList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; - int32 NumBack =0; FPoly **BackList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; - - FPoly *SplitPoly = FindBestSplit( NumPolys, PolyList, Opt, Balance, PortalBias ); - - // Add the splitter poly to the Bsp with either a new BspSurf or an existing one. - if( RebuildSimplePolys ) - { - SplitPoly->iLinkSurf = Model->Surfs.Num(); - } - - int32 iOurNode = bspAddNode(Model,iParent,NodePlace,0,SplitPoly, BspPoints, BspVectors); - int32 iPlaneNode = iOurNode; - - // Now divide all polygons in the pool into (A) polygons that are - // in front of Poly, and (B) polygons that are in back of Poly. - // Coplanar polys are inserted immediately, before recursing. - - // If any polygons are split by Poly, we ignrore the original poly, - // split it into two polys, and add two new polys to the pool. - FPoly *FrontEdPoly = new FPoly; - FPoly *BackEdPoly = new FPoly; - // Keep track of allocations. - AllocatedFPolys.Add( FrontEdPoly ); - AllocatedFPolys.Add( BackEdPoly ); - - for( int32 i=0; iSplitWithPlane( SplitPoly->Vertices[0], SplitPoly->Normal, FrontEdPoly, BackEdPoly, 0 ) ) - { - case SP_Coplanar: - if( RebuildSimplePolys ) - { - EdPoly->iLinkSurf = Model->Surfs.Num()-1; - } - iPlaneNode = bspAddNode( Model, iPlaneNode, NODE_Plane, 0, EdPoly, BspPoints, BspVectors ); - break; - - case SP_Front: - FrontList[NumFront++] = PolyList[i]; - break; - - case SP_Back: - BackList[NumBack++] = PolyList[i]; - break; - - case SP_Split: - - // Create front & back nodes. - FrontList[NumFront++] = FrontEdPoly; - BackList [NumBack ++] = BackEdPoly; - - FrontEdPoly = new FPoly; - BackEdPoly = new FPoly; - // Keep track of allocations. - AllocatedFPolys.Add( FrontEdPoly ); - AllocatedFPolys.Add( BackEdPoly ); - - break; - } - } - - // Recursively split the front and back pools. - if( NumFront > 0 ) SplitPolyList( Model, iOurNode, NODE_Front, NumFront, FrontList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); - if( NumBack > 0 ) SplitPolyList( Model, iOurNode, NODE_Back, NumBack, BackList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); - - // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. - for( int32 i=0; iGetName() ); // moved here so that we can easily debug when an actor has lost parts of its brush - - check(Actor->GetBrushComponent()); - check(Actor->Brush); - check(Actor->Brush->RootOutside); - - RebuildBrush(Actor->Brush, BspPoints, BspVectors); - - // Make sure simplified collision is up to date. - Actor->GetBrushComponent()->BuildSimpleBrushCollision(); - Actor->RebuildNavigationData(); -} - -/** - * Duplicates the specified brush and makes it into a CSG-able level brush. - * @return The new brush, or NULL if the original was empty. - */ -void FHBSPOps::csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - check(Src); - check(Src->GetBrushComponent()); - check(Src->Brush); - - // Handle empty brush. - if( !bAllowEmpty && !Src->Brush->Polys->Element.Num() ) - { - Dest->Brush = NULL; - Dest->GetBrushComponent()->Brush = NULL; - return; - } - - // Duplicate the brush and its polys. - Dest->PolyFlags = PolyFlags; - Dest->Brush = NewObject(Dest, NAME_None, ResFlags); - Dest->Brush->Initialize(nullptr, Src->Brush->RootOutside); - Dest->Brush->Polys = NewObject(Dest->Brush, NAME_None, ResFlags); - Dest->Brush->Polys->Element = Src->Brush->Polys->Element; - Dest->GetBrushComponent()->Brush = Dest->Brush; - if(Src->BrushBuilder != nullptr) - { - Dest->BrushBuilder = DuplicateObject(Src->BrushBuilder, Dest); - } - - // Update poly textures. - for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) - { - Dest->Brush->Polys->Element[i].iBrushPoly = INDEX_NONE; - } - - // Copy positioning, and build bounding box. - if(bCopyPosRotScale) - { - Dest->CopyPosRotScaleFrom( Src ); - } - - // If it's a moving brush, prep it. - if( bNeedsPrep ) - { - csgPrepMovingBrush( Dest, BspPoints, BspVectors ); - } -} - -/** - * Adds a brush to the list of CSG brushes in the level, using a CSG operation. - * - * @return A newly-created copy of the brush. - */ -ABrush* FHBSPOps::csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - check(Actor); - check(Actor->GetBrushComponent()); - check(Actor->Brush); - check(Actor->Brush->Polys); - check(Actor->GetWorld()); - - // Can't do this if brush has no polys. - if( !Actor->Brush->Polys->Element.Num() ) - return NULL; - - // Spawn a new actor for the brush. - - ABrush* Result = Actor->GetWorld()->SpawnBrush(); - Result->SetNotForClientOrServer(); - - // Duplicate the brush. - csgCopyBrush - ( - Result, - Actor, - PolyFlags, - RF_Transactional, - 0, - true, - false, - BspPoints, - BspVectors - ); - check(Result->Brush); - - if( Result->GetBrushBuilder() ) - { - FActorLabelUtilities::SetActorLabelUnique(Result, FText::Format(NSLOCTEXT("BSPBrushOps", "BrushName", "{0} Brush"), FText::FromString(Result->GetBrushBuilder()->GetClass()->GetDescription())).ToString()); - } - // Assign the default material to the brush's polys. - for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) - { - FPoly& CurrentPoly = Result->Brush->Polys->Element[i]; - if ( !CurrentPoly.Material ) - { - CurrentPoly.Material = UMaterial::GetDefaultMaterial(MD_Surface); - } - } - - // Set add-info. - Result->BrushType = BrushType; - - Result->ReregisterAllComponents(); - - return Result; -} - -/** Add a new point to the model (preventing duplicates) and return its index. */ -static int32 AddThing( TArray& Vectors, FVector& V, float Thresh, int32 Check ) -{ - if( Check ) - { - // See if this is very close to an existing point/vector. - for( int32 i=0; i -Thresh) && (Temp < Thresh) ) - { - Temp=(V.Y - TableVect.Y); - if( (Temp > -Thresh) && (Temp < Thresh) ) - { - Temp=(V.Z - TableVect.Z); - if( (Temp > -Thresh) && (Temp < Thresh) ) - { - // Found nearly-matching vector. - return i; - } - } - } - } - } - return Vectors.Add( V ); -} - -/** Add a new vector to the model, merging near-duplicates, and return its index. */ -int32 FHBSPOps::bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ) -{ - const float Thresh = Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR; - - if (BspVectors) - { - // If a points grid has been built for quick vector lookup, use that instead of doing a linear search - const int32 NextIndex = Model->Vectors.Num(); - const int32 ReturnedIndex = BspVectors->FindOrAddPoint(*V, NextIndex, Thresh); - if (ReturnedIndex == NextIndex) - { - Model->Vectors.Add(*V); - } - - return ReturnedIndex; - } - - return AddThing - ( - Model->Vectors, - *V, - Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR, - 1 - ); -} - -/** Add a new point to the model, merging near-duplicates, and return its index. */ -int32 FHBSPOps::bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ) -{ - const float Thresh = Exact ? THRESH_POINTS_ARE_SAME : THRESH_POINTS_ARE_NEAR; - - if (BspPoints) - { - // If a points grid has been built for quick point lookup, use that instead of doing a linear search - const int32 NextIndex = Model->Points.Num(); - // Always look for points with a low threshold; a generous threshold can result in 'leaks' in the BSP and unwanted polys being generated - const int32 ReturnedIndex = BspPoints->FindOrAddPoint(*V, NextIndex, THRESH_POINTS_ARE_SAME); - if (ReturnedIndex == NextIndex) - { - Model->Points.Add(*V); - } - - return ReturnedIndex; - } - - // Try to find a match quickly from the Bsp. This finds all potential matches - // except for any dissociated from nodes/surfaces during a rebuild. - FVector Temp; - int32 pVertex; - float NearestDist = Model->FindNearestVertex(*V,Temp,Thresh,pVertex); - if( (NearestDist >= 0.0) && (NearestDist <= Thresh) ) - { - // Found an existing point. - return pVertex; - } - else - { - // No match found; add it slowly to find duplicates. - return AddThing(Model->Points, *V, Thresh, !GFastRebuild); - } -} - - -/** - * Builds Bsp from the editor polygon set (EdPolys) of a model. - * - * Opt = Bsp optimization, BSP_Lame (fast), BSP_Good (medium), BSP_Optimal (slow) - * Balance = 0-100, 0=only worry about minimizing splits, 100=only balance tree. - */ -void FHBSPOps::bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - int32 OriginalPolys = Model->Polys->Element.Num(); - - // Empty the model's tables. - if( RebuildSimplePolys==1 ) - { - // Empty everything but polys. - Model->EmptyModel( 1, 0 ); - } - else if( RebuildSimplePolys==0 ) - { - // Empty node vertices. - for( int32 i=0; iNodes.Num(); i++ ) - Model->Nodes[i].NumVertices = 0; - - // Refresh the Bsp. - bspRefresh(Model,1); - - // Empty nodes. - Model->EmptyModel( 0, 0 ); - } - if( Model->Polys->Element.Num() ) - { - // Allocate polygon pool. - FMemMark Mark(FMemStack::Get()); - FPoly** PolyList = new( FMemStack::Get(), Model->Polys->Element.Num() )FPoly*; - - // Add all FPolys to active list. - for( int32 i=0; iPolys->Element.Num(); i++ ) - if( Model->Polys->Element[i].Vertices.Num() ) - PolyList[i] = &Model->Polys->Element[i]; - - // Now split the entire Bsp by splitting the list of all polygons. - SplitPolyList - ( - Model, - INDEX_NONE, - NODE_Root, - Model->Polys->Element.Num(), - PolyList, - Opt, - Balance, - PortalBias, - RebuildSimplePolys, - BspPoints, - BspVectors - ); - - // Now build the bounding boxes for all nodes. - if( RebuildSimplePolys==0 ) - { - // Remove unreferenced things. - bspRefresh( Model, 1 ); - - // Rebuild all bounding boxes. - bspBuildBounds( Model ); - } - - Mark.Pop(); - } - -// UE_LOG(LogBSPOps, Log, TEXT("bspBuild built %i convex polys into %i nodes"), OriginalPolys, Model->Nodes.Num() ); -} - -/** - * If the Bsp's point and vector tables are nearly full, reorder them and delete unused ones. - */ -void FHBSPOps::bspRefresh( UModel* Model, bool NoRemapSurfs ) -{ - FMemStack& MemStack = FMemStack::Get(); - - FMemMark Mark(MemStack); - - int32 NumNodes = Model->Nodes.Num(); - int32 NumSurfs = Model->Surfs.Num(); - int32 NumVectors = Model->Vectors.Num(); - int32 NumPoints = Model->Points.Num(); - - // Remove unreferenced Bsp surfs. - int32* PolyRef; - if( NoRemapSurfs ) - { - PolyRef = NewZeroed(MemStack, NumSurfs); - } - else - { - PolyRef = NewOned(MemStack, NumSurfs); - } - - int32* NodeRef = NewOned(MemStack, NumNodes); - if( NumNodes > 0 ) - { - TagReferencedNodes( Model, NodeRef, PolyRef, 0 ); - } - - // Remap Bsp surfs. - { - int32 n=0; - for( int32 i=0; iSurfs[n] = Model->Surfs[i]; - PolyRef[i]=n++; - } - } - //UE_LOG(LogBSPOps, Log, TEXT("Polys: %i -> %i"), NumSurfs, n ); - Model->Surfs.RemoveAt( n, NumSurfs-n ); - NumSurfs = n; - } - - // Remap Bsp nodes. - { - int32 n=0; - for( int32 i=0; iNodes[n] = Model->Nodes[i]; - NodeRef[i]=n++; - } - } - //UE_LOG(LogBSPOps, Log, TEXT("Nodes: %i -> %i"), NumNodes, n ); - Model->Nodes.RemoveAt( n, NumNodes-n ); - NumNodes = n; - } - - // Update Bsp nodes. - for( int32 i=0; iNodes[i]; - Node->iSurf = PolyRef[Node->iSurf]; - if (Node->iFront != INDEX_NONE) Node->iFront = NodeRef[Node->iFront]; - if (Node->iBack != INDEX_NONE) Node->iBack = NodeRef[Node->iBack]; - if (Node->iPlane != INDEX_NONE) Node->iPlane = NodeRef[Node->iPlane]; - } - - // Remove unreferenced points and vectors. - int32* VectorRef = NewOned(MemStack, NumVectors); - int32* PointRef = NewOned(MemStack, NumPoints); - - // Check Bsp surfs. - TArray VertexRef; - for( int32 i=0; iSurfs[i]; - VectorRef [Surf->vNormal ] = 0; - VectorRef [Surf->vTextureU ] = 0; - VectorRef [Surf->vTextureV ] = 0; - PointRef [Surf->pBase ] = 0; - } - - // Check Bsp nodes. - for( int32 i=0; iNodes[i]; - FVert* VertPool = &Model->Verts[Node->iVertPool]; - for( int B=0; BNumVertices; B++ ) - { - PointRef[VertPool->pVertex] = 0; - VertPool++; - } - } - - // Remap points. - { - int32 n=0; - for( int32 i=0; iPoints[n] = Model->Points[i]; - PointRef[i] = n++; - } - //UE_LOG(LogBSPOps, Log, TEXT("Points: %i -> %i"), NumPoints, n ); - Model->Points.RemoveAt( n, NumPoints-n ); - NumPoints = n; - } - - // Remap vectors. - { - int32 n=0; - for (int32 i=0; iVectors[n] = Model->Vectors[i]; - VectorRef[i] = n++; - } - //UE_LOG(LogBSPOps, Log, TEXT("Vectors: %i -> %i"), NumVectors, n ); - Model->Vectors.RemoveAt( n, NumVectors-n ); - NumVectors = n; - } - - // Update Bsp surfs. - for( int32 i=0; iSurfs[i]; - Surf->vNormal = VectorRef [Surf->vNormal ]; - Surf->vTextureU = VectorRef [Surf->vTextureU]; - Surf->vTextureV = VectorRef [Surf->vTextureV]; - Surf->pBase = PointRef [Surf->pBase ]; - } - - // Update Bsp nodes. - for( int32 i=0; iNodes[i]; - FVert* VertPool = &Model->Verts[Node->iVertPool]; - for( int B=0; BNumVertices; B++ ) - { - VertPool->pVertex = PointRef [VertPool->pVertex]; - VertPool++; - } - } - - // Shrink the objects. - Model->ShrinkModel(); - - Mark.Pop(); -} - -// Build bounding volumes for all Bsp nodes. The bounding volume of the node -// completely encloses the "outside" space occupied by the nodes. Note that -// this is not the same as representing the bounding volume of all of the -// polygons within the node. -// -// We start with a practically-infinite cube and filter it down the Bsp, -// whittling it away until all of its convex volume fragments land in leaves. -void FHBSPOps::bspBuildBounds( UModel* Model ) -{ - if( Model->Nodes.Num()==0 ) - return; - - FPoly Polys[6], *PolyList[6]; - for( int32 i=0; i<6; i++ ) - { - PolyList[i] = &Polys[i]; - PolyList[i]->Init(); - PolyList[i]->iBrushPoly = INDEX_NONE; - } - - new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); - Polys[0].Normal =FVector( 0.000000, 0.000000, 1.000000 ); - Polys[0].Base =Polys[0].Vertices[0]; - - new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[1].Normal =FVector( 0.000000, 0.000000, -1.000000 ); - Polys[1].Base =Polys[1].Vertices[0]; - - new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[2].Normal =FVector( 0.000000, 1.000000, 0.000000 ); - Polys[2].Base =Polys[2].Vertices[0]; - - new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[3].Normal =FVector( 0.000000, -1.000000, 0.000000 ); - Polys[3].Base =Polys[3].Vertices[0]; - - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[4].Normal =FVector( 1.000000, 0.000000, 0.000000 ); - Polys[4].Base =Polys[4].Vertices[0]; - - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[5].Normal =FVector(-1.000000, 0.000000, 0.000000 ); - Polys[5].Base =Polys[5].Vertices[0]; - // Empty hulls. - Model->LeafHulls.Empty(); - for( int32 i=0; iNodes.Num(); i++ ) - Model->Nodes[i].iCollisionBound = INDEX_NONE; - FilterBound( Model, NULL, 0, PolyList, 6, Model->RootOutside ); -// UE_LOG(LogBSPOps, Log, TEXT("bspBuildBounds: Generated %i hulls"), Model->LeafHulls.Num() ); -} - -/** - * Validate a brush, and set iLinks on all EdPolys to index of the - * first identical EdPoly in the list, or its index if it's the first. - * Not transactional. - */ -void FHBSPOps::bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ) -{ - check(Brush != nullptr); - Brush->Modify(); - if( ForceValidate || !Brush->Linked ) - { - Brush->Linked = 1; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - Brush->Polys->Element[i].iLink = i; - } - int32 n=0; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - FPoly* EdPoly = &Brush->Polys->Element[i]; - if( EdPoly->iLink==i ) - { - for( int32 j=i+1; jPolys->Element.Num(); j++ ) - { - FPoly* OtherPoly = &Brush->Polys->Element[j]; - if - ( OtherPoly->iLink == j - && OtherPoly->Material == EdPoly->Material - && OtherPoly->TextureU == EdPoly->TextureU - && OtherPoly->TextureV == EdPoly->TextureV - && OtherPoly->PolyFlags == EdPoly->PolyFlags - && (OtherPoly->Normal | EdPoly->Normal)>0.9999 ) - { - float Dist = FVector::PointPlaneDist( OtherPoly->Vertices[0], EdPoly->Vertices[0], EdPoly->Normal ); - if( Dist>-0.001 && Dist<0.001 ) - { - OtherPoly->iLink = i; - n++; - } - } - } - } - } -// UE_LOG(LogBSPOps, Log, TEXT("BspValidateBrush linked %i of %i polys"), n, Brush->Polys->Element.Num() ); - } - - // Build bounds. - Brush->BuildBound(); -} - -void FHBSPOps::bspUnlinkPolys( UModel* Brush ) -{ - Brush->Modify(); - Brush->Linked = 1; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - Brush->Polys->Element[i].iLink = i; - } -} - -// Add an editor polygon to the Bsp, and also stick a reference to it -// in the editor polygon's BspNodes list. If the editor polygon has more sides -// than the Bsp will allow, split it up into several sub-polygons. -// -// Returns: Index to newly-created node of Bsp. If several nodes were created because -// of split polys, returns the parent (highest one up in the Bsp). -int32 FHBSPOps::bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - if( NodePlace == NODE_Plane ) - { - // Make sure coplanars are added at the end of the coplanar list so that - // we don't insert NF_IsNew nodes with non NF_IsNew coplanar children. - while( Model->Nodes[iParent].iPlane != INDEX_NONE ) - { - iParent = Model->Nodes[iParent].iPlane; - } - } - FBspSurf* Surf = NULL; - if( EdPoly->iLinkSurf == Model->Surfs.Num() ) - { - int32 NewIndex = Model->Surfs.AddZeroed(); - Surf = &Model->Surfs[NewIndex]; - - // This node has a new polygon being added by bspBrushCSG; must set its properties here. - Surf->pBase = bspAddPoint (Model,&EdPoly->Base,1,BspPoints); - Surf->vNormal = bspAddVector (Model,&EdPoly->Normal,1,BspVectors); - Surf->vTextureU = bspAddVector (Model,&EdPoly->TextureU,0,BspVectors); - Surf->vTextureV = bspAddVector (Model,&EdPoly->TextureV,0,BspVectors); - Surf->Material = EdPoly->Material; - Surf->Actor = NULL; - - Surf->PolyFlags = EdPoly->PolyFlags & ~PF_NoAddToBSP; - Surf->LightMapScale= EdPoly->LightMapScale; - - // Find the LightmassPrimitiveSettings in the UModel... - int32 FoundLightmassIndex = INDEX_NONE; - if (Model->LightmassSettings.Find(EdPoly->LightmassSettings, FoundLightmassIndex) == false) - { - FoundLightmassIndex = Model->LightmassSettings.Add(EdPoly->LightmassSettings); - } - Surf->iLightmassIndex = FoundLightmassIndex; - - Surf->Actor = EdPoly->Actor; - Surf->iBrushPoly = EdPoly->iBrushPoly; - - if (EdPoly->Actor) - { - Surf->bHiddenEdTemporary = EdPoly->Actor->IsTemporarilyHiddenInEditor(); - Surf->bHiddenEdLevel = EdPoly->Actor->bHiddenEdLevel; - Surf->bHiddenEdLayer = EdPoly->Actor->bHiddenEdLayer; - } - - Surf->Plane = FPlane(EdPoly->Vertices[0],EdPoly->Normal); - } - else - { - check(EdPoly->iLinkSurf!=INDEX_NONE); - check(EdPoly->iLinkSurfSurfs.Num()); - Surf = &Model->Surfs[EdPoly->iLinkSurf]; - } - - // Set NodeFlags. - if( Surf->PolyFlags & PF_NotSolid ) NodeFlags |= NF_NotCsg; - if( Surf->PolyFlags & (PF_Invisible|PF_Portal) ) NodeFlags |= NF_NotVisBlocking; - - if( EdPoly->Vertices.Num() > FBspNode::MAX_NODE_VERTICES ) - { - // Split up into two coplanar sub-polygons (one with MAX_NODE_VERTICES vertices and - // one with all the remaining vertices) and recursively add them. - - // EdPoly1 is just the first MAX_NODE_VERTICES from EdPoly. - FMemMark Mark(FMemStack::Get()); - FPoly *EdPoly1 = new FPoly; - *EdPoly1 = *EdPoly; - EdPoly1->Vertices.RemoveAt(FBspNode::MAX_NODE_VERTICES,EdPoly->Vertices.Num() - FBspNode::MAX_NODE_VERTICES); - - // EdPoly2 is the first vertex from EdPoly, and the last EdPoly->Vertices.Num() - MAX_NODE_VERTICES + 1. - FPoly *EdPoly2 = new FPoly; - *EdPoly2 = *EdPoly; - EdPoly2->Vertices.RemoveAt(1,FBspNode::MAX_NODE_VERTICES - 2); - - int32 iNode = bspAddNode( Model, iParent, NodePlace, NodeFlags, EdPoly1, BspPoints, BspVectors ); // Add this poly first. - bspAddNode( Model, iNode, NODE_Plane, NodeFlags, EdPoly2, BspPoints, BspVectors ); // Then add other (may be bigger). - - delete EdPoly1; - delete EdPoly2; - - Mark.Pop(); - return iNode; // Return coplanar "parent" node (not coplanar child) - } - else - { - // Add node. - int32 iNode = Model->Nodes.AddZeroed(); - FBspNode& Node = Model->Nodes[iNode]; - - // Tell transaction tracking system that parent is about to be modified. - FBspNode* Parent=NULL; - if( NodePlace!=NODE_Root ) - Parent = &Model->Nodes[iParent]; - - // Set node properties. - Node.iSurf = EdPoly->iLinkSurf; - Node.NodeFlags = NodeFlags; - Node.iCollisionBound = INDEX_NONE; - Node.Plane = FPlane( EdPoly->Vertices[0], EdPoly->Normal ); - Node.iVertPool = Model->Verts.AddUninitialized(EdPoly->Vertices.Num()); - Node.iFront = INDEX_NONE; - Node.iBack = INDEX_NONE; - Node.iPlane = INDEX_NONE; - if( NodePlace==NODE_Root ) - { - Node.iLeaf[0] = INDEX_NONE; - Node.iLeaf[1] = INDEX_NONE; - Node.iZone[0] = 0; - Node.iZone[1] = 0; - } - else if( NodePlace==NODE_Front || NodePlace==NODE_Back ) - { - int32 ZoneFront=NodePlace==NODE_Front; - Node.iLeaf[0] = Parent->iLeaf[ZoneFront]; - Node.iLeaf[1] = Parent->iLeaf[ZoneFront]; - Node.iZone[0] = Parent->iZone[ZoneFront]; - Node.iZone[1] = Parent->iZone[ZoneFront]; - } - else - { - int32 IsFlipped = (Node.Plane|Parent->Plane)<0.0; - Node.iLeaf[0] = Parent->iLeaf[IsFlipped ]; - Node.iLeaf[1] = Parent->iLeaf[1-IsFlipped]; - Node.iZone[0] = Parent->iZone[IsFlipped ]; - Node.iZone[1] = Parent->iZone[1-IsFlipped]; - } - - // Link parent to this node. - if (NodePlace == NODE_Front) - { - Parent->iFront = iNode; - } - else if (NodePlace == NODE_Back) - { - Parent->iBack = iNode; - } - else if (NodePlace == NODE_Plane) - { - Parent->iPlane = iNode; - } - - // Add all points to point table, merging nearly-overlapping polygon points - // with other points in the poly to prevent criscrossing vertices from - // being generated. - - // Must maintain Node->NumVertices on the fly so that bspAddPoint is always - // called with the Bsp in a clean state. - Node.NumVertices = 0; - FVert* VertPool = &Model->Verts[ Node.iVertPool ]; - for( uint8 i=0; iVertices.Num(); i++ ) - { - int32 pVertex = bspAddPoint(Model,&EdPoly->Vertices[i],0, BspPoints); - if( Node.NumVertices==0 || VertPool[Node.NumVertices-1].pVertex!=pVertex ) - { - VertPool[Node.NumVertices].iSide = INDEX_NONE; - VertPool[Node.NumVertices].pVertex = pVertex; - Node.NumVertices++; - } - } - if( Node.NumVertices>=2 && VertPool[0].pVertex==VertPool[Node.NumVertices-1].pVertex ) - { - Node.NumVertices--; - } - if( Node.NumVertices < 3 ) - { - GErrors++; -// UE_LOG(LogBSPOps, Warning, TEXT("bspAddNode: Infinitesimal polygon %i (%i)"), Node.NumVertices, EdPoly->Vertices.Num() ); - Node.NumVertices = 0; - } - - return iNode; - } -} - -/** - * Rebuild some brush internals - */ -void FHBSPOps::RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - Brush->Modify(); - Brush->EmptyModel(1, 0); - - // Build bounding box. - Brush->BuildBound(); - - // Build BSP for the brush. - bspBuild(Brush, BSP_Good, 15, 70, 1, 0, BspPoints, BspVectors); - bspRefresh(Brush, 1); - bspBuildBounds(Brush); -} - -/** - * Rotates the specified brush's vertices. - */ -void FHBSPOps::RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - if(Brush->GetBrushComponent()->Brush && Brush->GetBrushComponent()->Brush->Polys) - { - for( int32 poly = 0 ; poly < Brush->GetBrushComponent()->Brush->Polys->Element.Num() ; poly++ ) - { - FPoly* Poly = &(Brush->GetBrushComponent()->Brush->Polys->Element[poly]); - - // Rotate the vertices. - const FRotationMatrix RotMatrix( Rotation ); - for( int32 vertex = 0 ; vertex < Poly->Vertices.Num() ; vertex++ ) - { - Poly->Vertices[vertex] = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Vertices[vertex] - Brush->GetPivotOffset()); - } - Poly->Base = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Base - Brush->GetPivotOffset()); - - // Rotate the texture vectors. - Poly->TextureU = RotMatrix.TransformVector( Poly->TextureU ); - Poly->TextureV = RotMatrix.TransformVector( Poly->TextureV ); - - // Recalc the normal for the poly. - Poly->Normal = FVector::ZeroVector; - Poly->Finalize(Brush,0); - } - - Brush->GetBrushComponent()->Brush->BuildBound(); - - if( !Brush->IsStaticBrush() ) - { - csgPrepMovingBrush( Brush, BspPoints, BspVectors ); - } - - if ( bClearComponents ) - { - Brush->ReregisterAllComponents(); - } - } -} - - -void FHBSPOps::HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - // The default physics volume doesn't have an associated UModel, so we need to handle that case gracefully. - if(Volume.Brush) - { - FHBSPOps::csgPrepMovingBrush( &Volume, BspPoints, BspVectors); - } -} - -UHBspPointsGrid* UHBspPointsGrid::Create(float InGranularity, float InThreshold, int32 InitialSize) -{ - check(InThreshold / InGranularity <= 0.5f); - - UHBspPointsGrid* Obj = NewObject(GetTransientPackage(), UHBspPointsGrid::StaticClass()); - Obj->OneOverGranularity = 1.0f / InGranularity; - Obj->Threshold = InThreshold; - Obj->Clear(InitialSize); - - return Obj; -} - -void UHBspPointsGrid::Clear(int32 InitialSize) -{ - GridMap.Empty(InitialSize); -} - - -// Given a grid index in one axis, a real position on the grid and a threshold radius, -// return either: -// - the additional grid index it can overlap in that axis, or -// - the original grid index if there is no overlap. -int32 UHBspPointsGrid::GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold) -{ - if (GridPos - GridIndex < GridThreshold) - { - return GridIndex - 1; - } - else if (1.0f - (GridPos - GridIndex) < GridThreshold) - { - return GridIndex + 1; - } - else - { - return GridIndex; - } -} - -int32 UHBspPointsGrid::FindOrAddPoint(const FVector& Point, int32 Index, float PointThreshold) -{ - // Offset applied to the grid coordinates so aligned vertices (the normal case) don't overlap several grid items (taking into account the threshold) - const float GridOffset = 0.12345f; - - const float AdjustedPointX = Point.X - GridOffset; - const float AdjustedPointY = Point.Y - GridOffset; - const float AdjustedPointZ = Point.Z - GridOffset; - - const float GridX = AdjustedPointX * OneOverGranularity; - const float GridY = AdjustedPointY * OneOverGranularity; - const float GridZ = AdjustedPointZ * OneOverGranularity; - - // Get the grid indices corresponding to the point coordinates - const int32 GridIndexX = FMath::FloorToInt(GridX); - const int32 GridIndexY = FMath::FloorToInt(GridY); - const int32 GridIndexZ = FMath::FloorToInt(GridZ); - - // Find grid item in map - FHBspPointsGridItem& GridItem = GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, GridIndexZ)); - - // Iterate through grid item points and return a point if it's close to the threshold - const float PointThresholdSquared = PointThreshold * PointThreshold; - for (const FHBspIndexedPoint& IndexedPoint : GridItem.IndexedPoints) - { - if (FVector::DistSquared(IndexedPoint.Point, Point) <= PointThresholdSquared) - { - return IndexedPoint.Index; - } - } - - // Otherwise, the point is new: add it to the grid item. - GridItem.IndexedPoints.Emplace(Point, Index); - - // The grid has a maximum threshold of a certain radius. If the point is near the edge of a grid cube, it may overlap into other items. - // Add it to all grid items it can be seen from. - const float GridThreshold = Threshold * OneOverGranularity; - const int32 NeighbourX = GetAdjacentIndexIfOverlapping(GridIndexX, GridX, GridThreshold); - const int32 NeighbourY = GetAdjacentIndexIfOverlapping(GridIndexY, GridY, GridThreshold); - const int32 NeighbourZ = GetAdjacentIndexIfOverlapping(GridIndexZ, GridZ, GridThreshold); - - const bool bOverlapsInX = (NeighbourX != GridIndexX); - const bool bOverlapsInY = (NeighbourY != GridIndexY); - const bool bOverlapsInZ = (NeighbourZ != GridIndexZ); - - if (bOverlapsInX) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInY) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else - { - if (bOverlapsInY) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - - return Index; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HBSPOps.h" +#include "EngineDefines.h" +#include "Model.h" +#include "Materials/Material.h" +#include "Engine/BrushBuilder.h" +#include "Editor/EditorEngine.h" +#include "Components/BrushComponent.h" +#include "GameFramework/Volume.h" + +DEFINE_LOG_CATEGORY_STATIC(LogBSPOps, Log, All); + +/** Errors encountered in Csg operation. */ +int32 FHBSPOps::GErrors = 0; +bool FHBSPOps::GFastRebuild = false; + +static void TagReferencedNodes( UModel *Model, int32 *NodeRef, int32 *PolyRef, int32 iNode ) +{ + FBspNode &Node = Model->Nodes[iNode]; + + NodeRef[iNode ] = 0; + PolyRef[Node.iSurf] = 0; + + if( Node.iFront != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iFront); + if( Node.iBack != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iBack ); + if( Node.iPlane != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iPlane); +} + +// +// Update a bounding volume by expanding it to enclose a list of polys. +// +static void UpdateBoundWithPolys( FBox& Bound, FPoly** PolyList, int32 nPolys ) +{ + for( int32 i=0; iVertices.Num(); j++ ) + Bound += PolyList[i]->Vertices[j]; +} + +// +// Update a convolution hull with a list of polys. +// +static void UpdateConvolutionWithPolys( UModel *Model, int32 iNode, FPoly **PolyList, int32 nPolys ) +{ + FBox Box(ForceInit); + + FBspNode &Node = Model->Nodes[iNode]; + Node.iCollisionBound = Model->LeafHulls.Num(); + for( int32 i=0; iiBrushPoly != INDEX_NONE ) + { + int32 j; + for( j=0; jiBrushPoly == PolyList[i]->iBrushPoly ) + break; + if( j >= i ) + Model->LeafHulls.Add(PolyList[i]->iBrushPoly); + } + for( int32 j=0; jVertices.Num(); j++ ) + Box += PolyList[i]->Vertices[j]; + } + Model->LeafHulls.Add(INDEX_NONE); + + // Add bounds. + Model->LeafHulls.Add( *(int32*)&Box.Min.X ); + Model->LeafHulls.Add( *(int32*)&Box.Min.Y ); + Model->LeafHulls.Add( *(int32*)&Box.Min.Z ); + Model->LeafHulls.Add( *(int32*)&Box.Max.X ); + Model->LeafHulls.Add( *(int32*)&Box.Max.Y ); + Model->LeafHulls.Add( *(int32*)&Box.Max.Z ); + +} + +// +// Cut a partitioning poly by a list of polys, and add the resulting inside pieces to the +// front list and back list. +// +static void SplitPartitioner +( + UModel* Model, + FPoly** PolyList, + FPoly** FrontList, + FPoly** BackList, + int32 n, + int32 nPolys, + int32& nFront, + int32& nBack, + FPoly InfiniteEdPoly, + TArray& AllocatedFPolys +) +{ + FPoly FrontPoly,BackPoly; + while( n < nPolys ) + { + FPoly* Poly = PolyList[n]; + switch( InfiniteEdPoly.SplitWithPlane(Poly->Vertices[0],Poly->Normal,&FrontPoly,&BackPoly,0) ) + { + case SP_Coplanar: + // May occasionally happen. +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got inficoplanar") ); + break; + + case SP_Front: + // Shouldn't happen if hull is correct. +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got infifront") ); + return; + + case SP_Split: + InfiniteEdPoly = BackPoly; + break; + + case SP_Back: + break; + } + n++; + } + + FPoly* New = new FPoly; + *New = InfiniteEdPoly; + New->Reverse(); + New->iBrushPoly |= 0x40000000; + FrontList[nFront++] = New; + AllocatedFPolys.Add( New ); + + New = new FPoly; + *New = InfiniteEdPoly; + BackList[nBack++] = New; + AllocatedFPolys.Add( New ); +} + +// +// Build an FPoly representing an "infinite" plane (which exceeds the maximum +// dimensions of the world in all directions) for a particular Bsp node. +// +FPoly FHBSPOps::BuildInfiniteFPoly( UModel* Model, int32 iNode ) +{ + FBspNode &Node = Model->Nodes [iNode ]; + FBspSurf &Poly = Model->Surfs [Node.iSurf ]; + FVector Base = Poly.Plane * Poly.Plane.W; + FVector Normal = Poly.Plane; + FVector Axis1,Axis2; + + // Find two non-problematic axis vectors. + Normal.FindBestAxisVectors( Axis1, Axis2 ); + + // Set up the FPoly. + FPoly EdPoly; + EdPoly.Init(); + EdPoly.Normal = Normal; + EdPoly.Base = Base; + new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX + Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX + Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX - Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX - Axis2*WORLD_MAX); + + return EdPoly; +} + +// +// Recursively filter a set of polys defining a convex hull down the Bsp, +// splitting it into two halves at each node and adding in the appropriate +// face polys at splits. +// +static void FilterBound +( + UModel* Model, + FBox* ParentBound, + int32 iNode, + FPoly** PolyList, + int32 nPolys, + int32 Outside +) +{ + FMemMark Mark(FMemStack::Get()); + FBspNode& Node = Model->Nodes [iNode]; + FBspSurf& Surf = Model->Surfs [Node.iSurf]; + FVector Base = Surf.Plane * Surf.Plane.W; + FVector& Normal = Model->Vectors[Surf.vNormal]; + FBox Bound(ForceInit); + + Bound.Min.X = Bound.Min.Y = Bound.Min.Z = +WORLD_MAX; + Bound.Max.X = Bound.Max.Y = Bound.Max.Z = -WORLD_MAX; + + // Split bound into front half and back half. + FPoly** FrontList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nFront=0; + FPoly** BackList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nBack=0; + + // Keeping track of allocated FPoly structures to delete later on. + TArray AllocatedFPolys; + + FPoly* FrontPoly = new FPoly; + FPoly* BackPoly = new FPoly; + + // Keep track of allocations. + AllocatedFPolys.Add( FrontPoly ); + AllocatedFPolys.Add( BackPoly ); + + for( int32 i=0; iSplitWithPlane( Base, Normal, FrontPoly, BackPoly, 0 ) ) + { + case SP_Coplanar: +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got coplanar") ); + FrontList[nFront++] = Poly; + BackList[nBack++] = Poly; + break; + + case SP_Front: + FrontList[nFront++] = Poly; + break; + + case SP_Back: + BackList[nBack++] = Poly; + break; + + case SP_Split: + FrontList[nFront++] = FrontPoly; + BackList [nBack++] = BackPoly; + + FrontPoly = new FPoly; + BackPoly = new FPoly; + + // Keep track of allocations. + AllocatedFPolys.Add( FrontPoly ); + AllocatedFPolys.Add( BackPoly ); + + break; + + default: + UE_LOG(LogBSPOps, Fatal, TEXT("FZoneFilter::FilterToLeaf: Unknown split code") ); + } + } + if( nFront && nBack ) + { + // Add partitioner plane to front and back. + FPoly InfiniteEdPoly = FHBSPOps::BuildInfiniteFPoly( Model, iNode ); + InfiniteEdPoly.iBrushPoly = iNode; + + SplitPartitioner(Model,PolyList,FrontList,BackList,0,nPolys,nFront,nBack,InfiniteEdPoly,AllocatedFPolys); + } + else + { +// if( !nFront ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty fronthull") ); +// if( !nBack ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty backhull") ); + } + + // Recursively update all our childrens' bounding volumes. + if( nFront > 0 ) + { + if( Node.iFront != INDEX_NONE ) + FilterBound( Model, &Bound, Node.iFront, FrontList, nFront, Outside || Node.IsCsg() ); + else if( Outside || Node.IsCsg() ) + UpdateBoundWithPolys( Bound, FrontList, nFront ); + else + UpdateConvolutionWithPolys( Model, iNode, FrontList, nFront ); + } + if( nBack > 0 ) + { + if( Node.iBack != INDEX_NONE) + FilterBound( Model, &Bound,Node.iBack, BackList, nBack, Outside && !Node.IsCsg() ); + else if( Outside && !Node.IsCsg() ) + UpdateBoundWithPolys( Bound, BackList, nBack ); + else + UpdateConvolutionWithPolys( Model, iNode, BackList, nBack ); + } + + // Update parent bound to enclose this bound. + if( ParentBound ) + *ParentBound += Bound; + + // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. + for( int32 i=0; i0); + + // No need to test if only one poly. + if( NumPolys==1 ) + return PolyList[0]; + + FPoly *Poly, *Best=NULL; + float Score, BestScore; + int32 i, Index, j, Inc; + int32 Splits, Front, Back, Coplanar, AllSemiSolids; + + //PortalBias -- added by Legend on 4/12/2000 + float PortalBias = InPortalBias / 100.0f; + Balance &= 0xFF; // keep only the low byte to recover "Balance" + //UE_LOG(LogBSPOps, Log, TEXT("Balance=%d PortalBias=%f"), Balance, PortalBias ); + + if (Opt==FHBSPOps::BSP_Optimal) Inc = 1; // Test lots of nodes. + else if (Opt==FHBSPOps::BSP_Good) Inc = FMath::Max(1,NumPolys/20); // Test 20 nodes. + else /* BSP_Lame */ Inc = FMath::Max(1,NumPolys/4); // Test 4 nodes. + + // See if there are any non-semisolid polygons here. + for( i=0; iPolyFlags & PF_AddLast) ) + break; + AllSemiSolids = (i>=NumPolys); + + // Search through all polygons in the pool and find: + // A. The number of splits each poly would make. + // B. The number of front and back nodes the polygon would create. + // C. Number of coplanars. + BestScore = 0; + for( i=0; iPolyFlags & PF_AddLast) && !(Poly->PolyFlags & PF_Portal) ) + && !AllSemiSolids ); + if( Index>=i+Inc || Index>=NumPolys ) + continue; + + for( j=0; jSplitWithPlaneFast( FPlane( Poly->Vertices[0], Poly->Normal), NULL, NULL ) ) + { + case SP_Coplanar: + Coplanar++; + break; + + case SP_Front: + Front++; + break; + + case SP_Back: + Back++; + break; + + case SP_Split: + // Disfavor splitting polys that are zone portals. + if( !(OtherPoly->PolyFlags & PF_Portal) ) + Splits++; + else + Splits += 16; + break; + } + } + // added by Legend 1/31/1999 + // Score optimization: minimize cuts vs. balance tree (as specified in BSP Rebuilder dialog) + Score = ( 100.0 - float(Balance) ) * Splits + float(Balance) * FMath::Abs( Front - Back ); + if( Poly->PolyFlags & PF_Portal ) + { + // PortalBias -- added by Legend on 4/12/2000 + // + // PortalBias enables level designers to control the effect of Portals on the BSP. + // This effect can range from 0.0 (ignore portals), to 1.0 (portals cut everything). + // + // In builds prior to this (since the 221 build dating back to 1/31/1999) the bias + // has been 1.0 causing the portals to cut the BSP in ways that will potentially + // degrade level performance, and increase the BSP complexity. + // + // By setting the bias to a value between 0.3 and 0.7 the positive effects of + // the portals are preserved without giving them unreasonable priority in the BSP. + // + // Portals should be weighted high enough in the BSP to separate major parts of the + // level from each other (pushing entire rooms down the branches of the BSP), but + // should not be so high that portals cut through adjacent geometry in a way that + // increases complexity of the room being (typically, accidentally) cut. + // + Score -= ( 100.0 - float(Balance) ) * Splits * PortalBias; // ignore PortalBias of the split polys -- bias toward portal selection for cutting planes! + } + //UE_LOG(LogBSPOps, Log, " %4d: Score = %f (Front = %4d, Back = %4d, Splits = %4d, Flags = %08X)", Index, Score, Front, Back, Splits, Poly->PolyFlags ); //LEC + + if( Score AllocatedFPolys; + + // To account for big EdPolys split up. + int32 NumPolysToAlloc = NumPolys + 8 + NumPolys/4; + int32 NumFront=0; FPoly **FrontList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; + int32 NumBack =0; FPoly **BackList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; + + FPoly *SplitPoly = FindBestSplit( NumPolys, PolyList, Opt, Balance, PortalBias ); + + // Add the splitter poly to the Bsp with either a new BspSurf or an existing one. + if( RebuildSimplePolys ) + { + SplitPoly->iLinkSurf = Model->Surfs.Num(); + } + + int32 iOurNode = bspAddNode(Model,iParent,NodePlace,0,SplitPoly, BspPoints, BspVectors); + int32 iPlaneNode = iOurNode; + + // Now divide all polygons in the pool into (A) polygons that are + // in front of Poly, and (B) polygons that are in back of Poly. + // Coplanar polys are inserted immediately, before recursing. + + // If any polygons are split by Poly, we ignrore the original poly, + // split it into two polys, and add two new polys to the pool. + FPoly *FrontEdPoly = new FPoly; + FPoly *BackEdPoly = new FPoly; + // Keep track of allocations. + AllocatedFPolys.Add( FrontEdPoly ); + AllocatedFPolys.Add( BackEdPoly ); + + for( int32 i=0; iSplitWithPlane( SplitPoly->Vertices[0], SplitPoly->Normal, FrontEdPoly, BackEdPoly, 0 ) ) + { + case SP_Coplanar: + if( RebuildSimplePolys ) + { + EdPoly->iLinkSurf = Model->Surfs.Num()-1; + } + iPlaneNode = bspAddNode( Model, iPlaneNode, NODE_Plane, 0, EdPoly, BspPoints, BspVectors ); + break; + + case SP_Front: + FrontList[NumFront++] = PolyList[i]; + break; + + case SP_Back: + BackList[NumBack++] = PolyList[i]; + break; + + case SP_Split: + + // Create front & back nodes. + FrontList[NumFront++] = FrontEdPoly; + BackList [NumBack ++] = BackEdPoly; + + FrontEdPoly = new FPoly; + BackEdPoly = new FPoly; + // Keep track of allocations. + AllocatedFPolys.Add( FrontEdPoly ); + AllocatedFPolys.Add( BackEdPoly ); + + break; + } + } + + // Recursively split the front and back pools. + if( NumFront > 0 ) SplitPolyList( Model, iOurNode, NODE_Front, NumFront, FrontList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); + if( NumBack > 0 ) SplitPolyList( Model, iOurNode, NODE_Back, NumBack, BackList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); + + // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. + for( int32 i=0; iGetName() ); // moved here so that we can easily debug when an actor has lost parts of its brush + + check(Actor->GetBrushComponent()); + check(Actor->Brush); + check(Actor->Brush->RootOutside); + + RebuildBrush(Actor->Brush, BspPoints, BspVectors); + + // Make sure simplified collision is up to date. + Actor->GetBrushComponent()->BuildSimpleBrushCollision(); + Actor->RebuildNavigationData(); +} + +/** + * Duplicates the specified brush and makes it into a CSG-able level brush. + * @return The new brush, or NULL if the original was empty. + */ +void FHBSPOps::csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + check(Src); + check(Src->GetBrushComponent()); + check(Src->Brush); + + // Handle empty brush. + if( !bAllowEmpty && !Src->Brush->Polys->Element.Num() ) + { + Dest->Brush = NULL; + Dest->GetBrushComponent()->Brush = NULL; + return; + } + + // Duplicate the brush and its polys. + Dest->PolyFlags = PolyFlags; + Dest->Brush = NewObject(Dest, NAME_None, ResFlags); + Dest->Brush->Initialize(nullptr, Src->Brush->RootOutside); + Dest->Brush->Polys = NewObject(Dest->Brush, NAME_None, ResFlags); + Dest->Brush->Polys->Element = Src->Brush->Polys->Element; + Dest->GetBrushComponent()->Brush = Dest->Brush; + if(Src->BrushBuilder != nullptr) + { + Dest->BrushBuilder = DuplicateObject(Src->BrushBuilder, Dest); + } + + // Update poly textures. + for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) + { + Dest->Brush->Polys->Element[i].iBrushPoly = INDEX_NONE; + } + + // Copy positioning, and build bounding box. + if(bCopyPosRotScale) + { + Dest->CopyPosRotScaleFrom( Src ); + } + + // If it's a moving brush, prep it. + if( bNeedsPrep ) + { + csgPrepMovingBrush( Dest, BspPoints, BspVectors ); + } +} + +/** + * Adds a brush to the list of CSG brushes in the level, using a CSG operation. + * + * @return A newly-created copy of the brush. + */ +ABrush* FHBSPOps::csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + check(Actor); + check(Actor->GetBrushComponent()); + check(Actor->Brush); + check(Actor->Brush->Polys); + check(Actor->GetWorld()); + + // Can't do this if brush has no polys. + if( !Actor->Brush->Polys->Element.Num() ) + return NULL; + + // Spawn a new actor for the brush. + + ABrush* Result = Actor->GetWorld()->SpawnBrush(); + Result->SetNotForClientOrServer(); + + // Duplicate the brush. + csgCopyBrush + ( + Result, + Actor, + PolyFlags, + RF_Transactional, + 0, + true, + false, + BspPoints, + BspVectors + ); + check(Result->Brush); + + if( Result->GetBrushBuilder() ) + { + FActorLabelUtilities::SetActorLabelUnique(Result, FText::Format(NSLOCTEXT("BSPBrushOps", "BrushName", "{0} Brush"), FText::FromString(Result->GetBrushBuilder()->GetClass()->GetDescription())).ToString()); + } + // Assign the default material to the brush's polys. + for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) + { + FPoly& CurrentPoly = Result->Brush->Polys->Element[i]; + if ( !CurrentPoly.Material ) + { + CurrentPoly.Material = UMaterial::GetDefaultMaterial(MD_Surface); + } + } + + // Set add-info. + Result->BrushType = BrushType; + + Result->ReregisterAllComponents(); + + return Result; +} + +/** Add a new point to the model (preventing duplicates) and return its index. */ +static int32 AddThing( TArray& Vectors, FVector& V, float Thresh, int32 Check ) +{ + if( Check ) + { + // See if this is very close to an existing point/vector. + for( int32 i=0; i -Thresh) && (Temp < Thresh) ) + { + Temp=(V.Y - TableVect.Y); + if( (Temp > -Thresh) && (Temp < Thresh) ) + { + Temp=(V.Z - TableVect.Z); + if( (Temp > -Thresh) && (Temp < Thresh) ) + { + // Found nearly-matching vector. + return i; + } + } + } + } + } + return Vectors.Add( V ); +} + +/** Add a new vector to the model, merging near-duplicates, and return its index. */ +int32 FHBSPOps::bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ) +{ + const float Thresh = Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR; + + if (BspVectors) + { + // If a points grid has been built for quick vector lookup, use that instead of doing a linear search + const int32 NextIndex = Model->Vectors.Num(); + const int32 ReturnedIndex = BspVectors->FindOrAddPoint(*V, NextIndex, Thresh); + if (ReturnedIndex == NextIndex) + { + Model->Vectors.Add(*V); + } + + return ReturnedIndex; + } + + return AddThing + ( + Model->Vectors, + *V, + Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR, + 1 + ); +} + +/** Add a new point to the model, merging near-duplicates, and return its index. */ +int32 FHBSPOps::bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ) +{ + const float Thresh = Exact ? THRESH_POINTS_ARE_SAME : THRESH_POINTS_ARE_NEAR; + + if (BspPoints) + { + // If a points grid has been built for quick point lookup, use that instead of doing a linear search + const int32 NextIndex = Model->Points.Num(); + // Always look for points with a low threshold; a generous threshold can result in 'leaks' in the BSP and unwanted polys being generated + const int32 ReturnedIndex = BspPoints->FindOrAddPoint(*V, NextIndex, THRESH_POINTS_ARE_SAME); + if (ReturnedIndex == NextIndex) + { + Model->Points.Add(*V); + } + + return ReturnedIndex; + } + + // Try to find a match quickly from the Bsp. This finds all potential matches + // except for any dissociated from nodes/surfaces during a rebuild. + FVector Temp; + int32 pVertex; + float NearestDist = Model->FindNearestVertex(*V,Temp,Thresh,pVertex); + if( (NearestDist >= 0.0) && (NearestDist <= Thresh) ) + { + // Found an existing point. + return pVertex; + } + else + { + // No match found; add it slowly to find duplicates. + return AddThing(Model->Points, *V, Thresh, !GFastRebuild); + } +} + + +/** + * Builds Bsp from the editor polygon set (EdPolys) of a model. + * + * Opt = Bsp optimization, BSP_Lame (fast), BSP_Good (medium), BSP_Optimal (slow) + * Balance = 0-100, 0=only worry about minimizing splits, 100=only balance tree. + */ +void FHBSPOps::bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + int32 OriginalPolys = Model->Polys->Element.Num(); + + // Empty the model's tables. + if( RebuildSimplePolys==1 ) + { + // Empty everything but polys. + Model->EmptyModel( 1, 0 ); + } + else if( RebuildSimplePolys==0 ) + { + // Empty node vertices. + for( int32 i=0; iNodes.Num(); i++ ) + Model->Nodes[i].NumVertices = 0; + + // Refresh the Bsp. + bspRefresh(Model,1); + + // Empty nodes. + Model->EmptyModel( 0, 0 ); + } + if( Model->Polys->Element.Num() ) + { + // Allocate polygon pool. + FMemMark Mark(FMemStack::Get()); + FPoly** PolyList = new( FMemStack::Get(), Model->Polys->Element.Num() )FPoly*; + + // Add all FPolys to active list. + for( int32 i=0; iPolys->Element.Num(); i++ ) + if( Model->Polys->Element[i].Vertices.Num() ) + PolyList[i] = &Model->Polys->Element[i]; + + // Now split the entire Bsp by splitting the list of all polygons. + SplitPolyList + ( + Model, + INDEX_NONE, + NODE_Root, + Model->Polys->Element.Num(), + PolyList, + Opt, + Balance, + PortalBias, + RebuildSimplePolys, + BspPoints, + BspVectors + ); + + // Now build the bounding boxes for all nodes. + if( RebuildSimplePolys==0 ) + { + // Remove unreferenced things. + bspRefresh( Model, 1 ); + + // Rebuild all bounding boxes. + bspBuildBounds( Model ); + } + + Mark.Pop(); + } + +// UE_LOG(LogBSPOps, Log, TEXT("bspBuild built %i convex polys into %i nodes"), OriginalPolys, Model->Nodes.Num() ); +} + +/** + * If the Bsp's point and vector tables are nearly full, reorder them and delete unused ones. + */ +void FHBSPOps::bspRefresh( UModel* Model, bool NoRemapSurfs ) +{ + FMemStack& MemStack = FMemStack::Get(); + + FMemMark Mark(MemStack); + + int32 NumNodes = Model->Nodes.Num(); + int32 NumSurfs = Model->Surfs.Num(); + int32 NumVectors = Model->Vectors.Num(); + int32 NumPoints = Model->Points.Num(); + + // Remove unreferenced Bsp surfs. + int32* PolyRef; + if( NoRemapSurfs ) + { + PolyRef = NewZeroed(MemStack, NumSurfs); + } + else + { + PolyRef = NewOned(MemStack, NumSurfs); + } + + int32* NodeRef = NewOned(MemStack, NumNodes); + if( NumNodes > 0 ) + { + TagReferencedNodes( Model, NodeRef, PolyRef, 0 ); + } + + // Remap Bsp surfs. + { + int32 n=0; + for( int32 i=0; iSurfs[n] = Model->Surfs[i]; + PolyRef[i]=n++; + } + } + //UE_LOG(LogBSPOps, Log, TEXT("Polys: %i -> %i"), NumSurfs, n ); + Model->Surfs.RemoveAt( n, NumSurfs-n ); + NumSurfs = n; + } + + // Remap Bsp nodes. + { + int32 n=0; + for( int32 i=0; iNodes[n] = Model->Nodes[i]; + NodeRef[i]=n++; + } + } + //UE_LOG(LogBSPOps, Log, TEXT("Nodes: %i -> %i"), NumNodes, n ); + Model->Nodes.RemoveAt( n, NumNodes-n ); + NumNodes = n; + } + + // Update Bsp nodes. + for( int32 i=0; iNodes[i]; + Node->iSurf = PolyRef[Node->iSurf]; + if (Node->iFront != INDEX_NONE) Node->iFront = NodeRef[Node->iFront]; + if (Node->iBack != INDEX_NONE) Node->iBack = NodeRef[Node->iBack]; + if (Node->iPlane != INDEX_NONE) Node->iPlane = NodeRef[Node->iPlane]; + } + + // Remove unreferenced points and vectors. + int32* VectorRef = NewOned(MemStack, NumVectors); + int32* PointRef = NewOned(MemStack, NumPoints); + + // Check Bsp surfs. + TArray VertexRef; + for( int32 i=0; iSurfs[i]; + VectorRef [Surf->vNormal ] = 0; + VectorRef [Surf->vTextureU ] = 0; + VectorRef [Surf->vTextureV ] = 0; + PointRef [Surf->pBase ] = 0; + } + + // Check Bsp nodes. + for( int32 i=0; iNodes[i]; + FVert* VertPool = &Model->Verts[Node->iVertPool]; + for( int B=0; BNumVertices; B++ ) + { + PointRef[VertPool->pVertex] = 0; + VertPool++; + } + } + + // Remap points. + { + int32 n=0; + for( int32 i=0; iPoints[n] = Model->Points[i]; + PointRef[i] = n++; + } + //UE_LOG(LogBSPOps, Log, TEXT("Points: %i -> %i"), NumPoints, n ); + Model->Points.RemoveAt( n, NumPoints-n ); + NumPoints = n; + } + + // Remap vectors. + { + int32 n=0; + for (int32 i=0; iVectors[n] = Model->Vectors[i]; + VectorRef[i] = n++; + } + //UE_LOG(LogBSPOps, Log, TEXT("Vectors: %i -> %i"), NumVectors, n ); + Model->Vectors.RemoveAt( n, NumVectors-n ); + NumVectors = n; + } + + // Update Bsp surfs. + for( int32 i=0; iSurfs[i]; + Surf->vNormal = VectorRef [Surf->vNormal ]; + Surf->vTextureU = VectorRef [Surf->vTextureU]; + Surf->vTextureV = VectorRef [Surf->vTextureV]; + Surf->pBase = PointRef [Surf->pBase ]; + } + + // Update Bsp nodes. + for( int32 i=0; iNodes[i]; + FVert* VertPool = &Model->Verts[Node->iVertPool]; + for( int B=0; BNumVertices; B++ ) + { + VertPool->pVertex = PointRef [VertPool->pVertex]; + VertPool++; + } + } + + // Shrink the objects. + Model->ShrinkModel(); + + Mark.Pop(); +} + +// Build bounding volumes for all Bsp nodes. The bounding volume of the node +// completely encloses the "outside" space occupied by the nodes. Note that +// this is not the same as representing the bounding volume of all of the +// polygons within the node. +// +// We start with a practically-infinite cube and filter it down the Bsp, +// whittling it away until all of its convex volume fragments land in leaves. +void FHBSPOps::bspBuildBounds( UModel* Model ) +{ + if( Model->Nodes.Num()==0 ) + return; + + FPoly Polys[6], *PolyList[6]; + for( int32 i=0; i<6; i++ ) + { + PolyList[i] = &Polys[i]; + PolyList[i]->Init(); + PolyList[i]->iBrushPoly = INDEX_NONE; + } + + new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); + Polys[0].Normal =FVector( 0.000000, 0.000000, 1.000000 ); + Polys[0].Base =Polys[0].Vertices[0]; + + new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[1].Normal =FVector( 0.000000, 0.000000, -1.000000 ); + Polys[1].Base =Polys[1].Vertices[0]; + + new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[2].Normal =FVector( 0.000000, 1.000000, 0.000000 ); + Polys[2].Base =Polys[2].Vertices[0]; + + new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[3].Normal =FVector( 0.000000, -1.000000, 0.000000 ); + Polys[3].Base =Polys[3].Vertices[0]; + + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[4].Normal =FVector( 1.000000, 0.000000, 0.000000 ); + Polys[4].Base =Polys[4].Vertices[0]; + + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[5].Normal =FVector(-1.000000, 0.000000, 0.000000 ); + Polys[5].Base =Polys[5].Vertices[0]; + // Empty hulls. + Model->LeafHulls.Empty(); + for( int32 i=0; iNodes.Num(); i++ ) + Model->Nodes[i].iCollisionBound = INDEX_NONE; + FilterBound( Model, NULL, 0, PolyList, 6, Model->RootOutside ); +// UE_LOG(LogBSPOps, Log, TEXT("bspBuildBounds: Generated %i hulls"), Model->LeafHulls.Num() ); +} + +/** + * Validate a brush, and set iLinks on all EdPolys to index of the + * first identical EdPoly in the list, or its index if it's the first. + * Not transactional. + */ +void FHBSPOps::bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ) +{ + check(Brush != nullptr); + Brush->Modify(); + if( ForceValidate || !Brush->Linked ) + { + Brush->Linked = 1; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + Brush->Polys->Element[i].iLink = i; + } + int32 n=0; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + FPoly* EdPoly = &Brush->Polys->Element[i]; + if( EdPoly->iLink==i ) + { + for( int32 j=i+1; jPolys->Element.Num(); j++ ) + { + FPoly* OtherPoly = &Brush->Polys->Element[j]; + if + ( OtherPoly->iLink == j + && OtherPoly->Material == EdPoly->Material + && OtherPoly->TextureU == EdPoly->TextureU + && OtherPoly->TextureV == EdPoly->TextureV + && OtherPoly->PolyFlags == EdPoly->PolyFlags + && (OtherPoly->Normal | EdPoly->Normal)>0.9999 ) + { + float Dist = FVector::PointPlaneDist( OtherPoly->Vertices[0], EdPoly->Vertices[0], EdPoly->Normal ); + if( Dist>-0.001 && Dist<0.001 ) + { + OtherPoly->iLink = i; + n++; + } + } + } + } + } +// UE_LOG(LogBSPOps, Log, TEXT("BspValidateBrush linked %i of %i polys"), n, Brush->Polys->Element.Num() ); + } + + // Build bounds. + Brush->BuildBound(); +} + +void FHBSPOps::bspUnlinkPolys( UModel* Brush ) +{ + Brush->Modify(); + Brush->Linked = 1; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + Brush->Polys->Element[i].iLink = i; + } +} + +// Add an editor polygon to the Bsp, and also stick a reference to it +// in the editor polygon's BspNodes list. If the editor polygon has more sides +// than the Bsp will allow, split it up into several sub-polygons. +// +// Returns: Index to newly-created node of Bsp. If several nodes were created because +// of split polys, returns the parent (highest one up in the Bsp). +int32 FHBSPOps::bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + if( NodePlace == NODE_Plane ) + { + // Make sure coplanars are added at the end of the coplanar list so that + // we don't insert NF_IsNew nodes with non NF_IsNew coplanar children. + while( Model->Nodes[iParent].iPlane != INDEX_NONE ) + { + iParent = Model->Nodes[iParent].iPlane; + } + } + FBspSurf* Surf = NULL; + if( EdPoly->iLinkSurf == Model->Surfs.Num() ) + { + int32 NewIndex = Model->Surfs.AddZeroed(); + Surf = &Model->Surfs[NewIndex]; + + // This node has a new polygon being added by bspBrushCSG; must set its properties here. + Surf->pBase = bspAddPoint (Model,&EdPoly->Base,1,BspPoints); + Surf->vNormal = bspAddVector (Model,&EdPoly->Normal,1,BspVectors); + Surf->vTextureU = bspAddVector (Model,&EdPoly->TextureU,0,BspVectors); + Surf->vTextureV = bspAddVector (Model,&EdPoly->TextureV,0,BspVectors); + Surf->Material = EdPoly->Material; + Surf->Actor = NULL; + + Surf->PolyFlags = EdPoly->PolyFlags & ~PF_NoAddToBSP; + Surf->LightMapScale= EdPoly->LightMapScale; + + // Find the LightmassPrimitiveSettings in the UModel... + int32 FoundLightmassIndex = INDEX_NONE; + if (Model->LightmassSettings.Find(EdPoly->LightmassSettings, FoundLightmassIndex) == false) + { + FoundLightmassIndex = Model->LightmassSettings.Add(EdPoly->LightmassSettings); + } + Surf->iLightmassIndex = FoundLightmassIndex; + + Surf->Actor = EdPoly->Actor; + Surf->iBrushPoly = EdPoly->iBrushPoly; + + if (EdPoly->Actor) + { + Surf->bHiddenEdTemporary = EdPoly->Actor->IsTemporarilyHiddenInEditor(); + Surf->bHiddenEdLevel = EdPoly->Actor->bHiddenEdLevel; + Surf->bHiddenEdLayer = EdPoly->Actor->bHiddenEdLayer; + } + + Surf->Plane = FPlane(EdPoly->Vertices[0],EdPoly->Normal); + } + else + { + check(EdPoly->iLinkSurf!=INDEX_NONE); + check(EdPoly->iLinkSurfSurfs.Num()); + Surf = &Model->Surfs[EdPoly->iLinkSurf]; + } + + // Set NodeFlags. + if( Surf->PolyFlags & PF_NotSolid ) NodeFlags |= NF_NotCsg; + if( Surf->PolyFlags & (PF_Invisible|PF_Portal) ) NodeFlags |= NF_NotVisBlocking; + + if( EdPoly->Vertices.Num() > FBspNode::MAX_NODE_VERTICES ) + { + // Split up into two coplanar sub-polygons (one with MAX_NODE_VERTICES vertices and + // one with all the remaining vertices) and recursively add them. + + // EdPoly1 is just the first MAX_NODE_VERTICES from EdPoly. + FMemMark Mark(FMemStack::Get()); + FPoly *EdPoly1 = new FPoly; + *EdPoly1 = *EdPoly; + EdPoly1->Vertices.RemoveAt(FBspNode::MAX_NODE_VERTICES,EdPoly->Vertices.Num() - FBspNode::MAX_NODE_VERTICES); + + // EdPoly2 is the first vertex from EdPoly, and the last EdPoly->Vertices.Num() - MAX_NODE_VERTICES + 1. + FPoly *EdPoly2 = new FPoly; + *EdPoly2 = *EdPoly; + EdPoly2->Vertices.RemoveAt(1,FBspNode::MAX_NODE_VERTICES - 2); + + int32 iNode = bspAddNode( Model, iParent, NodePlace, NodeFlags, EdPoly1, BspPoints, BspVectors ); // Add this poly first. + bspAddNode( Model, iNode, NODE_Plane, NodeFlags, EdPoly2, BspPoints, BspVectors ); // Then add other (may be bigger). + + delete EdPoly1; + delete EdPoly2; + + Mark.Pop(); + return iNode; // Return coplanar "parent" node (not coplanar child) + } + else + { + // Add node. + int32 iNode = Model->Nodes.AddZeroed(); + FBspNode& Node = Model->Nodes[iNode]; + + // Tell transaction tracking system that parent is about to be modified. + FBspNode* Parent=NULL; + if( NodePlace!=NODE_Root ) + Parent = &Model->Nodes[iParent]; + + // Set node properties. + Node.iSurf = EdPoly->iLinkSurf; + Node.NodeFlags = NodeFlags; + Node.iCollisionBound = INDEX_NONE; + Node.Plane = FPlane( EdPoly->Vertices[0], EdPoly->Normal ); + Node.iVertPool = Model->Verts.AddUninitialized(EdPoly->Vertices.Num()); + Node.iFront = INDEX_NONE; + Node.iBack = INDEX_NONE; + Node.iPlane = INDEX_NONE; + if( NodePlace==NODE_Root ) + { + Node.iLeaf[0] = INDEX_NONE; + Node.iLeaf[1] = INDEX_NONE; + Node.iZone[0] = 0; + Node.iZone[1] = 0; + } + else if( NodePlace==NODE_Front || NodePlace==NODE_Back ) + { + int32 ZoneFront=NodePlace==NODE_Front; + Node.iLeaf[0] = Parent->iLeaf[ZoneFront]; + Node.iLeaf[1] = Parent->iLeaf[ZoneFront]; + Node.iZone[0] = Parent->iZone[ZoneFront]; + Node.iZone[1] = Parent->iZone[ZoneFront]; + } + else + { + int32 IsFlipped = (Node.Plane|Parent->Plane)<0.0; + Node.iLeaf[0] = Parent->iLeaf[IsFlipped ]; + Node.iLeaf[1] = Parent->iLeaf[1-IsFlipped]; + Node.iZone[0] = Parent->iZone[IsFlipped ]; + Node.iZone[1] = Parent->iZone[1-IsFlipped]; + } + + // Link parent to this node. + if (NodePlace == NODE_Front) + { + Parent->iFront = iNode; + } + else if (NodePlace == NODE_Back) + { + Parent->iBack = iNode; + } + else if (NodePlace == NODE_Plane) + { + Parent->iPlane = iNode; + } + + // Add all points to point table, merging nearly-overlapping polygon points + // with other points in the poly to prevent criscrossing vertices from + // being generated. + + // Must maintain Node->NumVertices on the fly so that bspAddPoint is always + // called with the Bsp in a clean state. + Node.NumVertices = 0; + FVert* VertPool = &Model->Verts[ Node.iVertPool ]; + for( uint8 i=0; iVertices.Num(); i++ ) + { + int32 pVertex = bspAddPoint(Model,&EdPoly->Vertices[i],0, BspPoints); + if( Node.NumVertices==0 || VertPool[Node.NumVertices-1].pVertex!=pVertex ) + { + VertPool[Node.NumVertices].iSide = INDEX_NONE; + VertPool[Node.NumVertices].pVertex = pVertex; + Node.NumVertices++; + } + } + if( Node.NumVertices>=2 && VertPool[0].pVertex==VertPool[Node.NumVertices-1].pVertex ) + { + Node.NumVertices--; + } + if( Node.NumVertices < 3 ) + { + GErrors++; +// UE_LOG(LogBSPOps, Warning, TEXT("bspAddNode: Infinitesimal polygon %i (%i)"), Node.NumVertices, EdPoly->Vertices.Num() ); + Node.NumVertices = 0; + } + + return iNode; + } +} + +/** + * Rebuild some brush internals + */ +void FHBSPOps::RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + Brush->Modify(); + Brush->EmptyModel(1, 0); + + // Build bounding box. + Brush->BuildBound(); + + // Build BSP for the brush. + bspBuild(Brush, BSP_Good, 15, 70, 1, 0, BspPoints, BspVectors); + bspRefresh(Brush, 1); + bspBuildBounds(Brush); +} + +/** + * Rotates the specified brush's vertices. + */ +void FHBSPOps::RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + if(Brush->GetBrushComponent()->Brush && Brush->GetBrushComponent()->Brush->Polys) + { + for( int32 poly = 0 ; poly < Brush->GetBrushComponent()->Brush->Polys->Element.Num() ; poly++ ) + { + FPoly* Poly = &(Brush->GetBrushComponent()->Brush->Polys->Element[poly]); + + // Rotate the vertices. + const FRotationMatrix RotMatrix( Rotation ); + for( int32 vertex = 0 ; vertex < Poly->Vertices.Num() ; vertex++ ) + { + Poly->Vertices[vertex] = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Vertices[vertex] - Brush->GetPivotOffset()); + } + Poly->Base = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Base - Brush->GetPivotOffset()); + + // Rotate the texture vectors. + Poly->TextureU = RotMatrix.TransformVector( Poly->TextureU ); + Poly->TextureV = RotMatrix.TransformVector( Poly->TextureV ); + + // Recalc the normal for the poly. + Poly->Normal = FVector::ZeroVector; + Poly->Finalize(Brush,0); + } + + Brush->GetBrushComponent()->Brush->BuildBound(); + + if( !Brush->IsStaticBrush() ) + { + csgPrepMovingBrush( Brush, BspPoints, BspVectors ); + } + + if ( bClearComponents ) + { + Brush->ReregisterAllComponents(); + } + } +} + + +void FHBSPOps::HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + // The default physics volume doesn't have an associated UModel, so we need to handle that case gracefully. + if(Volume.Brush) + { + FHBSPOps::csgPrepMovingBrush( &Volume, BspPoints, BspVectors); + } +} + +UHBspPointsGrid* UHBspPointsGrid::Create(float InGranularity, float InThreshold, int32 InitialSize) +{ + check(InThreshold / InGranularity <= 0.5f); + + UHBspPointsGrid* Obj = NewObject(GetTransientPackage(), UHBspPointsGrid::StaticClass()); + Obj->OneOverGranularity = 1.0f / InGranularity; + Obj->Threshold = InThreshold; + Obj->Clear(InitialSize); + + return Obj; +} + +void UHBspPointsGrid::Clear(int32 InitialSize) +{ + GridMap.Empty(InitialSize); +} + + +// Given a grid index in one axis, a real position on the grid and a threshold radius, +// return either: +// - the additional grid index it can overlap in that axis, or +// - the original grid index if there is no overlap. +int32 UHBspPointsGrid::GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold) +{ + if (GridPos - GridIndex < GridThreshold) + { + return GridIndex - 1; + } + else if (1.0f - (GridPos - GridIndex) < GridThreshold) + { + return GridIndex + 1; + } + else + { + return GridIndex; + } +} + +int32 UHBspPointsGrid::FindOrAddPoint(const FVector& Point, int32 Index, float PointThreshold) +{ + // Offset applied to the grid coordinates so aligned vertices (the normal case) don't overlap several grid items (taking into account the threshold) + const float GridOffset = 0.12345f; + + const float AdjustedPointX = Point.X - GridOffset; + const float AdjustedPointY = Point.Y - GridOffset; + const float AdjustedPointZ = Point.Z - GridOffset; + + const float GridX = AdjustedPointX * OneOverGranularity; + const float GridY = AdjustedPointY * OneOverGranularity; + const float GridZ = AdjustedPointZ * OneOverGranularity; + + // Get the grid indices corresponding to the point coordinates + const int32 GridIndexX = FMath::FloorToInt(GridX); + const int32 GridIndexY = FMath::FloorToInt(GridY); + const int32 GridIndexZ = FMath::FloorToInt(GridZ); + + // Find grid item in map + FHBspPointsGridItem& GridItem = GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, GridIndexZ)); + + // Iterate through grid item points and return a point if it's close to the threshold + const float PointThresholdSquared = PointThreshold * PointThreshold; + for (const FHBspIndexedPoint& IndexedPoint : GridItem.IndexedPoints) + { + if (FVector::DistSquared(IndexedPoint.Point, Point) <= PointThresholdSquared) + { + return IndexedPoint.Index; + } + } + + // Otherwise, the point is new: add it to the grid item. + GridItem.IndexedPoints.Emplace(Point, Index); + + // The grid has a maximum threshold of a certain radius. If the point is near the edge of a grid cube, it may overlap into other items. + // Add it to all grid items it can be seen from. + const float GridThreshold = Threshold * OneOverGranularity; + const int32 NeighbourX = GetAdjacentIndexIfOverlapping(GridIndexX, GridX, GridThreshold); + const int32 NeighbourY = GetAdjacentIndexIfOverlapping(GridIndexY, GridY, GridThreshold); + const int32 NeighbourZ = GetAdjacentIndexIfOverlapping(GridIndexZ, GridZ, GridThreshold); + + const bool bOverlapsInX = (NeighbourX != GridIndexX); + const bool bOverlapsInY = (NeighbourY != GridIndexY); + const bool bOverlapsInZ = (NeighbourZ != GridIndexZ); + + if (bOverlapsInX) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInY) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else + { + if (bOverlapsInY) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + + return Index; +} diff --git a/Source/HoudiniEngine/Private/HBSPOps.h b/Source/HoudiniEngine/Private/HBSPOps.h index 8e359b070..f5a9a02a7 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.h +++ b/Source/HoudiniEngine/Private/HBSPOps.h @@ -1,181 +1,181 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Engine/Brush.h" -#include "Engine/Polys.h" - -#include "HBSPOps.generated.h" - -class AVolume; -class UModel; - -// This codebase have been localised from UnrealEd/HBSPOps to remove static/global variables. -class FHBSPOps -{ -public: - FHBSPOps(); - - /** Quality level for rebuilding Bsp. */ - enum EBspOptimization - { - BSP_Lame, - BSP_Good, - BSP_Optimal - }; - - /** Possible positions of a child Bsp node relative to its parent (for BspAddToNode) */ - enum ENodePlace - { - NODE_Back = 0, // Node is in back of parent -> Bsp[iParent].iBack. - NODE_Front = 1, // Node is in front of parent -> Bsp[iParent].iFront. - NODE_Plane = 2, // Node is coplanar with parent -> Bsp[iParent].iPlane. - NODE_Root = 3, // Node is the Bsp root and has no parent -> Bsp[0]. - }; - - static void csgPrepMovingBrush( ABrush* Actor, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static void csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static ABrush* csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - static int32 bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ); - static int32 bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ); - static void bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static void bspRefresh( UModel* Model, bool NoRemapSurfs ); - - static void bspBuildBounds( UModel* Model ); - - static void bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ); - static void bspUnlinkPolys( UModel* Brush ); - static int32 bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - /** - * Rebuild some brush internals - */ - static void RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - static FPoly BuildInfiniteFPoly( UModel* Model, int32 iNode ); - - /** - * Rotates the specified brush's vertices. - */ - static void RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - /** Called when an AVolume shape is changed*/ - static void HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - /** Errors encountered in Csg operation. */ - static int32 GErrors; - static bool GFastRebuild; - -protected: - static void SplitPolyList - ( - UModel *Model, - int32 iParent, - FHBSPOps::ENodePlace NodePlace, - int32 NumPolys, - FPoly **PolyList, - EBspOptimization Opt, - int32 Balance, - int32 PortalBias, - int32 RebuildSimplePolys, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); -}; - - -struct FHBspPointsKey -{ - int32 X; - int32 Y; - int32 Z; - - FHBspPointsKey(int32 InX, int32 InY, int32 InZ) - : X(InX) - , Y(InY) - , Z(InZ) - {} - - friend FORCEINLINE bool operator == (const FHBspPointsKey& A, const FHBspPointsKey& B) - { - return A.X == B.X && A.Y == B.Y && A.Z == B.Z; - } - - friend FORCEINLINE uint32 GetTypeHash(const FHBspPointsKey& Key) - { - return HashCombine(static_cast(Key.X), HashCombine(static_cast(Key.Y), static_cast(Key.Z))); - } -}; - -struct FHBspIndexedPoint -{ - FHBspIndexedPoint(const FVector& InPoint, int32 InIndex) - : Point(InPoint) - , Index(InIndex) - {} - - FVector Point; - int32 Index; -}; - - -struct FHBspPointsGridItem -{ - TArray> IndexedPoints; -}; - - -// Represents a sparse granular 3D grid into which points are added for quick (~O(1)) lookup. -// The 3D space is divided into a grid with a given granularity. -// Points are considered to have a given radius (threshold) and are added to the grid cube they fall in, and to up to seven neighbours if they overlap. -UCLASS() -class HOUDINIENGINE_API UHBspPointsGrid : public UObject -{ - GENERATED_BODY() -protected: - - UHBspPointsGrid() {} - -public: - // Create a new instance of this grid with the given arguments. - static UHBspPointsGrid* Create(float InGranularity, float InThreshold, int32 InitialSize = 0); - - void Clear(int32 InitialSize = 0); - - int32 FindOrAddPoint(const FVector& Point, int32 Index, float Threshold); - - static FORCEINLINE int32 GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold); - -private: - float OneOverGranularity; - float Threshold; - - typedef TMap FGridMap; - FGridMap GridMap; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Brush.h" +#include "Engine/Polys.h" + +#include "HBSPOps.generated.h" + +class AVolume; +class UModel; + +// This codebase have been localised from UnrealEd/HBSPOps to remove static/global variables. +class FHBSPOps +{ +public: + FHBSPOps(); + + /** Quality level for rebuilding Bsp. */ + enum EBspOptimization + { + BSP_Lame, + BSP_Good, + BSP_Optimal + }; + + /** Possible positions of a child Bsp node relative to its parent (for BspAddToNode) */ + enum ENodePlace + { + NODE_Back = 0, // Node is in back of parent -> Bsp[iParent].iBack. + NODE_Front = 1, // Node is in front of parent -> Bsp[iParent].iFront. + NODE_Plane = 2, // Node is coplanar with parent -> Bsp[iParent].iPlane. + NODE_Root = 3, // Node is the Bsp root and has no parent -> Bsp[0]. + }; + + static void csgPrepMovingBrush( ABrush* Actor, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static void csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static ABrush* csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + static int32 bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ); + static int32 bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ); + static void bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static void bspRefresh( UModel* Model, bool NoRemapSurfs ); + + static void bspBuildBounds( UModel* Model ); + + static void bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ); + static void bspUnlinkPolys( UModel* Brush ); + static int32 bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + /** + * Rebuild some brush internals + */ + static void RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + static FPoly BuildInfiniteFPoly( UModel* Model, int32 iNode ); + + /** + * Rotates the specified brush's vertices. + */ + static void RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + /** Called when an AVolume shape is changed*/ + static void HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + /** Errors encountered in Csg operation. */ + static int32 GErrors; + static bool GFastRebuild; + +protected: + static void SplitPolyList + ( + UModel *Model, + int32 iParent, + FHBSPOps::ENodePlace NodePlace, + int32 NumPolys, + FPoly **PolyList, + EBspOptimization Opt, + int32 Balance, + int32 PortalBias, + int32 RebuildSimplePolys, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); +}; + + +struct FHBspPointsKey +{ + int32 X; + int32 Y; + int32 Z; + + FHBspPointsKey(int32 InX, int32 InY, int32 InZ) + : X(InX) + , Y(InY) + , Z(InZ) + {} + + friend FORCEINLINE bool operator == (const FHBspPointsKey& A, const FHBspPointsKey& B) + { + return A.X == B.X && A.Y == B.Y && A.Z == B.Z; + } + + friend FORCEINLINE uint32 GetTypeHash(const FHBspPointsKey& Key) + { + return HashCombine(static_cast(Key.X), HashCombine(static_cast(Key.Y), static_cast(Key.Z))); + } +}; + +struct FHBspIndexedPoint +{ + FHBspIndexedPoint(const FVector& InPoint, int32 InIndex) + : Point(InPoint) + , Index(InIndex) + {} + + FVector Point; + int32 Index; +}; + + +struct FHBspPointsGridItem +{ + TArray> IndexedPoints; +}; + + +// Represents a sparse granular 3D grid into which points are added for quick (~O(1)) lookup. +// The 3D space is divided into a grid with a given granularity. +// Points are considered to have a given radius (threshold) and are added to the grid cube they fall in, and to up to seven neighbours if they overlap. +UCLASS() +class HOUDINIENGINE_API UHBspPointsGrid : public UObject +{ + GENERATED_BODY() +protected: + + UHBspPointsGrid() {} + +public: + // Create a new instance of this grid with the given arguments. + static UHBspPointsGrid* Create(float InGranularity, float InThreshold, int32 InitialSize = 0); + + void Clear(int32 InitialSize = 0); + + int32 FindOrAddPoint(const FVector& Point, int32 Index, float Threshold); + + static FORCEINLINE int32 GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold); + +private: + float OneOverGranularity; + float Threshold; + + typedef TMap FGridMap; + FGridMap GridMap; +}; diff --git a/Source/HoudiniEngine/Private/HCsgUtils.cpp b/Source/HoudiniEngine/Private/HCsgUtils.cpp index 9fd98873b..4b7d09a8a 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.cpp +++ b/Source/HoudiniEngine/Private/HCsgUtils.cpp @@ -1,1461 +1,1461 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HCsgUtils.h" - -#include "Engine/Engine.h" -#include "Engine/Polys.h" -#include "Engine/Selection.h" -#include "Materials/Material.h" -#include "Misc/FeedbackContext.h" - -#include "ActorEditorUtils.h" -#include "Misc/ScopedSlowTask.h" - - -DEFINE_LOG_CATEGORY_STATIC(LogHCsgUtils, Log, All); - -#if WITH_EDITOR -#include "Editor.h" -#endif - -// Magic numbers. -#define THRESH_OPTGEOM_COPLANAR (0.25) /* Threshold for Bsp geometry optimization */ -#define THRESH_OPTGEOM_COSIDAL (0.25) /* Threshold for Bsp geometry optimization */ - - -UHCsgUtils::UHCsgUtils() -{ - // A TempModel is allocated for the HCsgUtils instance to avoid reallocation during inner loops. - TempModel = NewObject(); - TempModel->Initialize(nullptr, 1); - - /*GBspPoints = NewObject(); - GBspVectors = NewObject();*/ -} - - -/*---------------------------------------------------------------------------- - CSG leaf filter callbacks. -----------------------------------------------------------------------------*/ - -void UHCsgUtils::AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); - break; - case F_COSPATIAL_FACING_OUT: - if( !(EdPoly->PolyFlags & PF_Semisolid) ) - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_IN: - break; - } -} - -void UHCsgUtils::AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - // Only affect the world poly if it has been cut. - if( EdPoly->PolyFlags & PF_EdCut ) - FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors ); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_IN: - case F_COSPATIAL_FACING_OUT: - // Discard original poly. - GDiscarded++; - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - break; - } -} - -void UHCsgUtils::SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch (Filter) - { - case F_OUTSIDE: - case F_COSPATIAL_FACING_OUT: - case F_COSPATIAL_FACING_IN: - case F_COPLANAR_OUTSIDE: - break; - case F_COPLANAR_INSIDE: - case F_INSIDE: - EdPoly->Reverse(); - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); // Add to Bsp back - EdPoly->Reverse(); - break; - } -} - -void UHCsgUtils::SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - // Only affect the world poly if it has been cut. - if( EdPoly->PolyFlags & PF_EdCut ) - FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - // Discard original poly. - GDiscarded++; - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - break; - } -} - -void UHCsgUtils::IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - case F_COSPATIAL_FACING_OUT: - // Ignore. - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - if( EdPoly->Fix()>=3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - // Ignore. - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - if( EdPoly->Fix() >= 3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - case F_COSPATIAL_FACING_IN: - // Ignore. - break; - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - if( EdPoly->Fix()>=3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_OUT: - // Ignore. - break; - case F_COPLANAR_INSIDE: - case F_INSIDE: - case F_COSPATIAL_FACING_IN: - if( EdPoly->Fix() >= 3 ) - { - EdPoly->Reverse(); - new(GModel->Polys->Element)FPoly(*EdPoly); - EdPoly->Reverse(); - } - break; - } -} - -/*---------------------------------------------------------------------------- - CSG polygon filtering routine (calls the callbacks). -----------------------------------------------------------------------------*/ - -// -// Handle a piece of a polygon that was filtered to a leaf. -// -void UHCsgUtils::FilterLeaf -( - BspFilterFunc FilterFunc, - UModel* Model, - int32 iNode, - FPoly* EdPoly, - FCoplanarInfo CoplanarInfo, - int32 LeafOutside, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - EPolyNodeFilter FilterType; - - if( CoplanarInfo.iOriginalNode == INDEX_NONE ) - { - // Processing regular, non-coplanar polygons. - FilterType = LeafOutside ? F_OUTSIDE : F_INSIDE; - (this->*FilterFunc)( Model, iNode, EdPoly, FilterType, ENodePlace, BspPoints, BspVectors ); - } - else if( CoplanarInfo.ProcessingBack ) - { - // Finished filtering polygon through tree in back of parent coplanar. - DoneFilteringBack: - if ((!LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_INSIDE; - else if (( LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_OUTSIDE; - else if ((!LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_OUT; - else if (( LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_IN; - else - { - UE_LOG(LogHCsgUtils, Fatal, TEXT("FilterLeaf: Bad Locs")); - return; - } - (this->*FilterFunc)( Model, CoplanarInfo.iOriginalNode, EdPoly, FilterType, FHBSPOps::NODE_Plane, BspPoints, BspVectors ); - } - else - { - CoplanarInfo.FrontLeafOutside = LeafOutside; - - if( CoplanarInfo.iBackNode == INDEX_NONE ) - { - // Back tree is empty. - LeafOutside = CoplanarInfo.BackNodeOutside; - goto DoneFilteringBack; - } - else - { - // Call FilterEdPoly to filter through the back. This will result in - // another call to FilterLeaf with iNode = leaf this falls into in the - // back tree and EdPoly = the final EdPoly to insert. - CoplanarInfo.ProcessingBack=1; - FilterEdPoly( FilterFunc, Model, CoplanarInfo.iBackNode, EdPoly,CoplanarInfo, CoplanarInfo.BackNodeOutside, BspPoints, BspVectors ); - } - } -} - -// -// Filter an EdPoly through the Bsp recursively, calling FilterFunc -// for all chunks that fall into leaves. FCoplanarInfo is used to -// handle the tricky case of double-recursion for polys that must be -// filtered through a node's front, then filtered through the node's back, -// in order to handle coplanar CSG properly. -// -void UHCsgUtils::FilterEdPoly -( - BspFilterFunc FilterFunc, - UModel *Model, - int32 iNode, - FPoly *EdPoly, - FCoplanarInfo CoplanarInfo, - int32 Outside, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - int32 SplitResult,iOurFront,iOurBack; - int32 NewFrontOutside,NewBackOutside; - - FilterLoop: - - // Split em. - FPoly TempFrontEdPoly,TempBackEdPoly; - SplitResult = EdPoly->SplitWithPlane - ( - Model->Points [Model->Verts[Model->Nodes[iNode].iVertPool].pVertex], - Model->Vectors[Model->Surfs[Model->Nodes[iNode].iSurf].vNormal], - &TempFrontEdPoly, - &TempBackEdPoly, - 0 - ); - - // Process split results. - if( SplitResult == SP_Front ) - { - Front: - - FBspNode *Node = &Model->Nodes[iNode]; - Outside = Outside || Node->IsCsg(); - - if( Node->iFront == INDEX_NONE ) - { - FilterLeaf(FilterFunc,Model,iNode,EdPoly,CoplanarInfo,Outside,FHBSPOps::NODE_Front, BspPoints, BspVectors); - } - else - { - iNode = Node->iFront; - goto FilterLoop; - } - } - else if( SplitResult == SP_Back ) - { - FBspNode *Node = &Model->Nodes[iNode]; - Outside = Outside && !Node->IsCsg(); - - if( Node->iBack == INDEX_NONE ) - { - FilterLeaf( FilterFunc, Model, iNode, EdPoly, CoplanarInfo, Outside, FHBSPOps::NODE_Back, BspPoints, BspVectors); - } - else - { - iNode=Node->iBack; - goto FilterLoop; - } - } - else if( SplitResult == SP_Coplanar ) - { - if( CoplanarInfo.iOriginalNode != INDEX_NONE ) - { - // This will happen once in a blue moon when a polygon is barely outside the - // coplanar threshold and is split up into a new polygon that is - // is barely inside the coplanar threshold. To handle this, just classify - // it as front and it will be handled propery. - FHBSPOps::GErrors++; -// UE_LOG(LogEditorBsp, Warning, TEXT("FilterEdPoly: Encountered out-of-place coplanar") ); - goto Front; - } - CoplanarInfo.iOriginalNode = iNode; - CoplanarInfo.iBackNode = INDEX_NONE; - CoplanarInfo.ProcessingBack = 0; - CoplanarInfo.BackNodeOutside = Outside; - NewFrontOutside = Outside; - - // See whether Node's iFront or iBack points to the side of the tree on the front - // of this polygon (will be as expected if this polygon is facing the same - // way as first coplanar in link, otherwise opposite). - if( (FVector(Model->Nodes[iNode].Plane) | EdPoly->Normal) >= 0.0 ) - { - iOurFront = Model->Nodes[iNode].iFront; - iOurBack = Model->Nodes[iNode].iBack; - - if( Model->Nodes[iNode].IsCsg() ) - { - CoplanarInfo.BackNodeOutside = 0; - NewFrontOutside = 1; - } - } - else - { - iOurFront = Model->Nodes[iNode].iBack; - iOurBack = Model->Nodes[iNode].iFront; - - if( Model->Nodes[iNode].IsCsg() ) - { - CoplanarInfo.BackNodeOutside = 1; - NewFrontOutside = 0; - } - } - - // Process front and back. - if ((iOurFront==INDEX_NONE)&&(iOurBack==INDEX_NONE)) - { - // No front or back. - CoplanarInfo.ProcessingBack = 1; - CoplanarInfo.FrontLeafOutside = NewFrontOutside; - FilterLeaf - ( - FilterFunc, - Model, - iNode, - EdPoly, - CoplanarInfo, - CoplanarInfo.BackNodeOutside, - FHBSPOps::NODE_Plane, - BspPoints, - BspVectors - ); - } - else if( iOurFront==INDEX_NONE && iOurBack!=INDEX_NONE ) - { - // Back but no front. - CoplanarInfo.ProcessingBack = 1; - CoplanarInfo.iBackNode = iOurBack; - CoplanarInfo.FrontLeafOutside = NewFrontOutside; - - iNode = iOurBack; - Outside = CoplanarInfo.BackNodeOutside; - goto FilterLoop; - } - else - { - // Has a front and maybe a back. - - // Set iOurBack up to process back on next call to FilterLeaf, and loop - // to process front. Next call to FilterLeaf will set FrontLeafOutside. - CoplanarInfo.ProcessingBack = 0; - - // May be a node or may be INDEX_NONE. - CoplanarInfo.iBackNode = iOurBack; - - iNode = iOurFront; - Outside = NewFrontOutside; - goto FilterLoop; - } - } - else if( SplitResult == SP_Split ) - { - // Front half of split. - if( Model->Nodes[iNode].IsCsg() ) - { - NewFrontOutside = 1; - NewBackOutside = 0; - } - else - { - NewFrontOutside = Outside; - NewBackOutside = Outside; - } - - if( Model->Nodes[iNode].iFront==INDEX_NONE ) - { - FilterLeaf - ( - FilterFunc, - Model, - iNode, - &TempFrontEdPoly, - CoplanarInfo, - NewFrontOutside, - FHBSPOps::NODE_Front, - BspPoints, - BspVectors - ); - } - else - { - FilterEdPoly - ( - FilterFunc, - Model, - Model->Nodes[iNode].iFront, - &TempFrontEdPoly, - CoplanarInfo, - NewFrontOutside, - BspPoints, - BspVectors - ); - } - - // Back half of split. - if( Model->Nodes[iNode].iBack==INDEX_NONE ) - { - FilterLeaf - ( - FilterFunc, - Model, - iNode, - &TempBackEdPoly, - CoplanarInfo, - NewBackOutside, - FHBSPOps::NODE_Back, - BspPoints, - BspVectors - ); - } - else - { - FilterEdPoly - ( - FilterFunc, - Model, - Model->Nodes[iNode].iBack, - &TempBackEdPoly, - CoplanarInfo, - NewBackOutside, - BspPoints, - BspVectors - ); - } - } -} - -// -// Regular entry into FilterEdPoly (so higher-level callers don't have to -// deal with unnecessary info). Filters starting at root. -// -void UHCsgUtils::BspFilterFPoly( BspFilterFunc FilterFunc, UModel *Model, FPoly *EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - FCoplanarInfo StartingCoplanarInfo; - StartingCoplanarInfo.iOriginalNode = INDEX_NONE; - if( Model->Nodes.Num() == 0 ) - { - // If Bsp is empty, process at root. - (this->*FilterFunc)( Model, 0, EdPoly, Model->RootOutside ? F_OUTSIDE : F_INSIDE, FHBSPOps::NODE_Root, BspPoints, BspVectors ); - } - else - { - // Filter through Bsp. - FilterEdPoly( FilterFunc, Model, 0, EdPoly, StartingCoplanarInfo, Model->RootOutside, BspPoints, BspVectors ); - } -} - -int UHCsgUtils::bspNodeToFPoly -( - UModel* Model, - int32 iNode, - FPoly* EdPoly -) -{ - FPoly MasterEdPoly; - - FBspNode &Node = Model->Nodes[iNode]; - FBspSurf &Poly = Model->Surfs[Node.iSurf]; - FVert *VertPool = &Model->Verts[ Node.iVertPool ]; - - EdPoly->Base = Model->Points [Poly.pBase]; - EdPoly->Normal = Model->Vectors[Poly.vNormal]; - - EdPoly->PolyFlags = Poly.PolyFlags & ~(PF_EdCut | PF_EdProcessed | PF_Selected | PF_Memorized); - EdPoly->iLinkSurf = Node.iSurf; - EdPoly->Material = Poly.Material; - - EdPoly->Actor = Poly.Actor; - EdPoly->iBrushPoly = Poly.iBrushPoly; - - if( polyFindMaster(Model,Node.iSurf,MasterEdPoly) ) - EdPoly->ItemName = MasterEdPoly.ItemName; - else - EdPoly->ItemName = NAME_None; - - EdPoly->TextureU = Model->Vectors[Poly.vTextureU]; - EdPoly->TextureV = Model->Vectors[Poly.vTextureV]; - - EdPoly->LightMapScale = Poly.LightMapScale; - - EdPoly->LightmassSettings = Model->LightmassSettings[Poly.iLightmassIndex]; - - EdPoly->Vertices.Empty(); - - for(int32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++) - { - new(EdPoly->Vertices) FVector(Model->Points[VertPool[VertexIndex].pVertex]); - } - - if(EdPoly->Vertices.Num() < 3) - { - EdPoly->Vertices.Empty(); - } - else - { - // Remove colinear points and identical points (which will appear - // if T-joints were eliminated). - EdPoly->RemoveColinears(); - } - - return EdPoly->Vertices.Num(); -} - -/*--------------------------------------------------------------------------------------- - World filtering. ----------------------------------------------------------------------------------------*/ - -// -// Filter all relevant world polys through the brush. -// -void UHCsgUtils::FilterWorldThroughBrush -( - UModel* Model, - UModel* Brush, - EBrushType BrushType, - ECsgOper CSGOper, - int32 iNode, - FSphere* BrushSphere, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - // Loop through all coplanars. - while( iNode != INDEX_NONE ) - { - // Get surface. - int32 iSurf = Model->Nodes[iNode].iSurf; - - // Skip new nodes and their children, which are guaranteed new. - if( Model->Nodes[iNode].NodeFlags & NF_IsNew ) - return; - - // Sphere reject. - int DoFront = 1, DoBack = 1; - if( BrushSphere ) - { - float Dist = Model->Nodes[iNode].Plane.PlaneDot( BrushSphere->Center ); - DoFront = (Dist >= -BrushSphere->W); - DoBack = (Dist <= +BrushSphere->W); - } - - // Process only polys that aren't empty. - FPoly TempEdPoly; - if( DoFront && DoBack && (GEditor->bspNodeToFPoly(Model,iNode,&TempEdPoly)>0) ) - { - TempEdPoly.Actor = Model->Surfs[iSurf].Actor; - TempEdPoly.iBrushPoly = Model->Surfs[iSurf].iBrushPoly; - - if( BrushType==Brush_Add || BrushType==Brush_Subtract ) - { - // Add and subtract work the same in this step. - GNode = iNode; - GModel = Model; - GDiscarded = 0; - GNumNodes = Model->Nodes.Num(); - - // Find last coplanar in chain. - GLastCoplanar = iNode; - while( Model->Nodes[GLastCoplanar].iPlane != INDEX_NONE ) - GLastCoplanar = Model->Nodes[GLastCoplanar].iPlane; - - // Do the filter operation. - BspFilterFPoly - ( - BrushType==Brush_Add ? &UHCsgUtils::AddWorldToBrushFunc : &UHCsgUtils::SubtractWorldToBrushFunc, - Brush, - &TempEdPoly, - BspPoints, - BspVectors - ); - - if( GDiscarded == 0 ) - { - // Get rid of all the fragments we added. - Model->Nodes[GLastCoplanar].iPlane = INDEX_NONE; - const bool bAllowShrinking = false; - Model->Nodes.RemoveAt( GNumNodes, Model->Nodes.Num()-GNumNodes, bAllowShrinking ); - } - else - { - // Tag original world poly for deletion; has been deleted or replaced by partial fragments. - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - } - } - else if( CSGOper == CSG_Intersect ) - { - BspFilterFPoly( &UHCsgUtils::IntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); - } - else if( CSGOper == CSG_Deintersect ) - { - BspFilterFPoly( &UHCsgUtils::DeIntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); - } - } - - // Now recurse to filter all of the world's children nodes. - if( DoFront && (Model->Nodes[iNode].iFront != INDEX_NONE)) FilterWorldThroughBrush - ( - Model, - Brush, - BrushType, - CSGOper, - Model->Nodes[iNode].iFront, - BrushSphere, - BspPoints, - BspVectors - ); - if( DoBack && (Model->Nodes[iNode].iBack != INDEX_NONE) ) FilterWorldThroughBrush - ( - Model, - Brush, - BrushType, - CSGOper, - Model->Nodes[iNode].iBack, - BrushSphere, - BspPoints, - BspVectors - ); - iNode = Model->Nodes[iNode].iPlane; - } -} - -void UHCsgUtils::RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic) -{ - if (!IsValid(Model)) - return; - - UHCsgUtils* CsgUtils = NewObject(); - int32 CsgErrors = 0; - - UHBspPointsGrid* BspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* BspVectors = UHBspPointsGrid::Create(1/16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - - // Empty the model out. - const int32 NumPoints = Model->Points.Num(); - const int32 NumNodes = Model->Nodes.Num(); - const int32 NumVerts = Model->Verts.Num(); - const int32 NumVectors = Model->Vectors.Num(); - const int32 NumSurfs = Model->Surfs.Num(); - - Model->Modify(); - Model->EmptyModel(1, 1); - - // Reserve arrays an eighth bigger than the previous allocation - Model->Points.Empty(NumPoints + NumPoints / 8); - Model->Nodes.Empty(NumNodes + NumNodes / 8); - Model->Verts.Empty(NumVerts + NumVerts / 8); - Model->Vectors.Empty(NumVectors + NumVectors / 8); - Model->Surfs.Empty(NumSurfs + NumSurfs / 8); - - // Build list of all static brushes, first structural brushes and portals - TArray StaticBrushes; - for (ABrush* Brush : Brushes) - { - if ((Brush && (Brush->IsStaticBrush() || bTreatMovableBrushesAsStatic) && !FActorEditorUtils::IsABuilderBrush(Brush)) && - (!(Brush->PolyFlags & PF_Semisolid) || (Brush->BrushType != Brush_Add) || (Brush->PolyFlags & PF_Portal))) - { - StaticBrushes.Add(Brush); - - // Treat portals as solids for cutting. - if (Brush->PolyFlags & PF_Portal) - { - Brush->PolyFlags = (Brush->PolyFlags & ~PF_Semisolid) | PF_NotSolid; - } - } - } - - // Next append all detail brushes - for (ABrush* Brush : Brushes) - { - if (Brush && Brush->IsStaticBrush() && !FActorEditorUtils::IsABuilderBrush(Brush) && - (Brush->PolyFlags & PF_Semisolid) && !(Brush->PolyFlags & PF_Portal) && (Brush->BrushType == Brush_Add)) - { - StaticBrushes.Add(Brush); - } - } - - // Build list of dynamic brushes - TArray DynamicBrushes; - if (!bTreatMovableBrushesAsStatic) - { - for (ABrush* DynamicBrush : Brushes) - { - if (DynamicBrush && DynamicBrush->Brush && !DynamicBrush->IsStaticBrush()) - { - DynamicBrushes.Add(DynamicBrush); - } - } - } - - FScopedSlowTask SlowTask(StaticBrushes.Num() + DynamicBrushes.Num()); - SlowTask.MakeDialogDelayed(3.0f); - - // Compose all static brushes - for (ABrush* Brush : StaticBrushes) - { - SlowTask.EnterProgressFrame(1); - Brush->Modify(); - int32 Errors = CsgUtils->ComposeBrushCSG(Brush, Model, Brush->PolyFlags, (EBrushType)Brush->BrushType, CSG_None, false, true, false, false, BspPoints, BspVectors); - if (Errors > 1) - CsgErrors += Errors - 1; - } - - // Rebuild dynamic brush BSP's (if they weren't handled earlier) - for (ABrush* DynamicBrush : DynamicBrushes) - { - SlowTask.EnterProgressFrame(1); - UHBspPointsGrid* LocalBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* LocalBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - - FHBSPOps::csgPrepMovingBrush(DynamicBrush, LocalBspPoints, LocalBspVectors); - } -} - - - -UModel* UHCsgUtils::BuildModelFromBrushes(TArray& Brushes) -{ - // Generally UModels are initialized using ABrush. Here we manually - // initialize using relevant parts from - UModel* OutModel = NewObject(); - OutModel->SetFlags(RF_Transactional); - OutModel->RootOutside = true; - OutModel->EmptyModel(1,1); - OutModel->UpdateVertices(); - - if (!IsValid(OutModel)) - return nullptr; - - // Can we combine the brushes without modifying the actors here ...? - - //FVector Location(0.0f, 0.0f, 0.0f); - //FRotator Rotation(0.0f, 0.0f, 0.0f); - //for(int32 BrushesIdx = 0; BrushesIdx < Brushes.Num(); ++BrushesIdx ) - //{ - // // Cache the location and rotation. - // Location = Brushes[BrushesIdx]->GetActorLocation(); - // Rotation = Brushes[BrushesIdx]->GetActorRotation(); - - - // // Leave the actor's rotation but move it to origin so the Static Mesh will generate correctly. - // Brushes[BrushesIdx]->TeleportTo(Location - InPivotLocation, Rotation, false, true); - //} - - RebuildModelFromBrushes(OutModel, Brushes, true); - //GEditor->bspBuildFPolys(OutModel, true, 0); - - //if (0 < ConversionTempModel->Polys->Element.Num()) - //{ - // UStaticMesh* NewMesh = CreateStaticMeshFromBrush(Pkg, ObjName, NULL, ConversionTempModel); - // NewActor = FActorFactoryAssetProxy::AddActorForAsset( NewMesh ); - - // NewActor->Modify(); - - // NewActor->InvalidateLightingCache(); - // NewActor->PostEditChange(); - // NewActor->PostEditMove( true ); - // NewActor->Modify(); - // ULayersSubsystem* LayersSubsystem = GetEditorSubsystem(); - // LayersSubsystem->InitializeNewActorLayers(NewActor); - - // // Teleport the new actor to the old location but not the old rotation. The static mesh is built to the rotation already. - // NewActor->TeleportTo(InPivotLocation, FRotator(0.0f, 0.0f, 0.0f), false, true); - - // // Destroy the old brushes. - // for( int32 BrushIdx = 0; BrushIdx < InBrushesToConvert.Num(); ++BrushIdx ) - // { - // LayersSubsystem->DisassociateActorFromLayers(InBrushesToConvert[BrushIdx]); - // GWorld->EditorDestroyActor( InBrushesToConvert[BrushIdx], true ); - // } - - // // Notify the asset registry - // FAssetRegistryModule::AssetCreated(NewMesh); - //} - - //ConversionTempModel->EmptyModel(1, 1); - //RebuildAlteredBSP(); - //RedrawLevelEditingViewports(); - - //return NewActor; - - return OutModel; -} - -int UHCsgUtils::ComposeBrushCSG -( - ABrush* Actor, - UModel* Model, - uint32 PolyFlags, - EBrushType BrushType, - ECsgOper CSGOper, - bool bBuildBounds, - bool bMergePolys, - bool bReplaceNULLMaterialRefs, - bool bShowProgressBar, /*=true*/ - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - uint32 NotPolyFlags = 0; - int32 NumPolysFromBrush=0,i,j,ReallyBig; - UModel* Brush = Actor->Brush; - int32 Errors = 0; - - // Make sure we're in an acceptable state. - if( !Brush ) - { - return 0; - } - - // Non-solid and semisolid stuff can only be added. - if( BrushType != Brush_Add ) - { - NotPolyFlags |= (PF_Semisolid | PF_NotSolid); - } - - TempModel->EmptyModel(1,1); - - // Update status. - ReallyBig = (Brush->Polys->Element.Num() > 200) && bShowProgressBar; - if( ReallyBig ) - { - FText Description = NSLOCTEXT("UnrealEd", "PerformingCSGOperation", "Performing CSG operation"); - - if (BrushType != Brush_MAX) - { - if (BrushType == Brush_Add) - { - Description = NSLOCTEXT("UnrealEd", "AddingBrushToWorld", "Adding brush to world"); - } - else if (BrushType == Brush_Subtract) - { - Description = NSLOCTEXT("UnrealEd", "SubtractingBrushFromWorld", "Subtracting brush from world"); - } - } - else if (CSGOper != CSG_None) - { - if (CSGOper == CSG_Intersect) - { - Description = NSLOCTEXT("UnrealEd", "IntersectingBrushWithWorld", "Intersecting brush with world"); - } - else if (CSGOper == CSG_Deintersect) - { - Description = NSLOCTEXT("UnrealEd", "DeintersectingBrushWithWorld", "Deintersecting brush with world"); - } - } - - GWarn->BeginSlowTask( Description, true ); - // Transform original brush poly into same coordinate system as world - // so Bsp filtering operations make sense. - GWarn->StatusUpdate(0, 0, NSLOCTEXT("UnrealEd", "Transforming", "Transforming")); - } - - - //UMaterialInterface* SelectedMaterialInstance = GEditor->GetSelectedObjects()->GetTop(); - UMaterialInterface* SelectedMaterialInstance = nullptr; - - const FVector Scale = Actor->GetActorScale(); - const FRotator Rotation = Actor->GetActorRotation(); - const FVector Location = Actor->GetActorLocation(); - - const bool bIsMirrored = (Scale.X * Scale.Y * Scale.Z < 0.0f); - - // Cache actor transform which is used for the geometry being built - Brush->OwnerLocationWhenLastBuilt = Location; - Brush->OwnerRotationWhenLastBuilt = Rotation; - Brush->OwnerScaleWhenLastBuilt = Scale; - Brush->bCachedOwnerTransformValid = true; - - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly& CurrentPoly = Brush->Polys->Element[i]; - - // Set texture the first time. - if ( bReplaceNULLMaterialRefs ) - { - UMaterialInterface*& PolyMat = CurrentPoly.Material; - if ( !PolyMat || PolyMat == UMaterial::GetDefaultMaterial(MD_Surface) ) - { - PolyMat = SelectedMaterialInstance; - } - } - - // Get the brush poly. - FPoly DestEdPoly = CurrentPoly; - check(CurrentPoly.iLinkPolys->Element.Num()); - - // Set its backward brush link. - DestEdPoly.Actor = Actor; - DestEdPoly.iBrushPoly = i; - - // Update its flags. - DestEdPoly.PolyFlags = (DestEdPoly.PolyFlags | PolyFlags) & ~NotPolyFlags; - - // Set its internal link. - if (DestEdPoly.iLink == INDEX_NONE) - { - DestEdPoly.iLink = i; - } - - // Transform it. - DestEdPoly.Scale( Scale ); - DestEdPoly.Rotate( Rotation ); - DestEdPoly.Transform( Location ); - - // Reverse winding and normal if the parent brush is mirrored - if (bIsMirrored) - { - DestEdPoly.Reverse(); - DestEdPoly.CalcNormal(); - } - - // Add poly to the temp model. - new(TempModel->Polys->Element)FPoly( DestEdPoly ); - } - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringBrush", "Filtering brush") ); - - // Pass the brush polys through the world Bsp. - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - // Empty the brush. - Brush->EmptyModel(1,1); - - // Intersect and deintersect. - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly EdPoly = TempModel->Polys->Element[i]; - GModel = Brush; - // TODO: iLink / iLinkSurf in EdPoly / TempModel->Polys->Element[i] ? - BspFilterFPoly( CSGOper==CSG_Intersect ? &UHCsgUtils::IntersectBrushWithWorldFunc : &UHCsgUtils::DeIntersectBrushWithWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); - } - NumPolysFromBrush = Brush->Polys->Element.Num(); - } - else - { - // Add and subtract. - TMap SurfaceIndexRemap; - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly EdPoly = TempModel->Polys->Element[i]; - - // Mark the polygon as non-cut so that it won't be harmed unless it must - // be split, and set iLink so that BspAddNode will know to add its information - // if a node is added based on this poly. - EdPoly.PolyFlags &= ~(PF_EdCut); - const int32* SurfaceIndexPtr = SurfaceIndexRemap.Find(EdPoly.iLink); - if (SurfaceIndexPtr == nullptr) - { - const int32 NewSurfaceIndex = Model->Surfs.Num(); - SurfaceIndexRemap.Add(EdPoly.iLink, NewSurfaceIndex); - EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = NewSurfaceIndex; - } - else - { - EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = *SurfaceIndexPtr; - } - - // Filter brush through the world. - BspFilterFPoly( BrushType==Brush_Add ? &UHCsgUtils::AddBrushToWorldFunc : &UHCsgUtils::SubtractBrushFromWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); - } - } - if( Model->Nodes.Num() && !(PolyFlags & (PF_NotSolid | PF_Semisolid)) ) - { - // Quickly build a Bsp for the brush, tending to minimize splits rather than balance - // the tree. We only need the cutting planes, though the entire Bsp struct (polys and - // all) is built. - - /*FHBspPointsGrid* LevelModelPointsGrid = FHBspPointsGrid::GBspPoints; - FHBspPointsGrid* LevelModelVectorsGrid = FHBspPointsGrid::GBspVectors;*/ - - // For the bspBuild call, temporarily create a new pair of BspPointsGrids for the TempModel. - UHBspPointsGrid* TempBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* TempBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - /*FHBspPointsGrid::GBspPoints = BspPoints.Get(); - FHBspPointsGrid::GBspVectors = BspVectors.Get();*/ - - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "BuildingBSP", "Building BSP") ); - - FHBSPOps::bspBuild( TempModel, FHBSPOps::BSP_Lame, 0, 70, 1, 0, TempBspPoints, TempBspVectors ); - - // Reinstate the original BspPointsGrids used for building the level Model. - /*FHBspPointsGrid::GBspPoints = LevelModelPointsGrid; - FHBspPointsGrid::GBspVectors = LevelModelVectorsGrid;*/ - - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringWorld", "Filtering world") ); - GModel = Brush; - TempModel->BuildBound(); - - FSphere BrushSphere = TempModel->Bounds.GetSphere(); - FilterWorldThroughBrush( Model, TempModel, BrushType, CSGOper, 0, &BrushSphere, BspPoints, BspVectors); - } - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "AdjustingBrush", "Adjusting brush") ); - - // Link polys obtained from the original brush. - for( i=NumPolysFromBrush-1; i>=0; i-- ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - for( j=0; jiLink == Brush->Polys->Element[j].iLink ) - { - DestEdPoly->iLink = j; - break; - } - } - if( j >= i ) DestEdPoly->iLink = i; - } - - // Link polys obtained from the world. - for( i=Brush->Polys->Element.Num()-1; i>=NumPolysFromBrush; i-- ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - for( j=NumPolysFromBrush; jiLink == Brush->Polys->Element[j].iLink ) - { - DestEdPoly->iLink = j; - break; - } - } - if( j >= i ) DestEdPoly->iLink = i; - } - Brush->Linked = 1; - - // Detransform the obtained brush back into its original coordinate system. - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - DestEdPoly->Transform(-Location); - DestEdPoly->Rotate(Rotation.GetInverse()); - DestEdPoly->Scale(FVector(1.0f) / Scale); - DestEdPoly->Fix(); - DestEdPoly->Actor = NULL; - DestEdPoly->iBrushPoly = i; - } - } - - if( BrushType==Brush_Add || BrushType==Brush_Subtract ) - { - // Clean up nodes, reset node flags. - bspCleanup( Model ); - - // Rebuild bounding volumes. - if( bBuildBounds ) - { - FHBSPOps::bspBuildBounds( Model ); - } - } - - Brush->NumUniqueVertices = TempModel->Points.Num(); - // Release TempModel. - TempModel->EmptyModel(1,1); - - // Merge coplanars if needed. - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - if( ReallyBig ) - { - GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "Merging", "Merging") ); - } - if( bMergePolys ) - { - bspMergeCoplanars( Brush, 1, 0 ); - } - } - if( ReallyBig ) - { - GWarn->EndSlowTask(); - } - - return 1 + FHBSPOps::GErrors; -} - -/*---------------------------------------------------------------------------- - EdPoly building and compacting. -----------------------------------------------------------------------------*/ - -// -// Trys to merge two polygons. If they can be merged, replaces Poly1 and emptys Poly2 -// and returns 1. Otherwise, returns 0. -// -int UHCsgUtils::TryToMerge( FPoly *Poly1, FPoly *Poly2 ) -{ - // Find one overlapping point. - int32 Start1=0, Start2=0; - for( Start1=0; Start1Vertices.Num(); Start1++ ) - for( Start2=0; Start2Vertices.Num(); Start2++ ) - if( FVector::PointsAreSame(Poly1->Vertices[Start1], Poly2->Vertices[Start2]) ) - goto FoundOverlap; - return 0; - FoundOverlap: - - // Wrap around trying to merge. - int32 End1 = Start1; - int32 End2 = Start2; - int32 Test1 = Start1+1; if (Test1>=Poly1->Vertices.Num()) Test1 = 0; - int32 Test2 = Start2-1; if (Test2<0) Test2 = Poly2->Vertices.Num()-1; - if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) - { - End1 = Test1; - Start2 = Test2; - } - else - { - Test1 = Start1-1; if (Test1<0) Test1=Poly1->Vertices.Num()-1; - Test2 = Start2+1; if (Test2>=Poly2->Vertices.Num()) Test2=0; - if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) - { - Start1 = Test1; - End2 = Test2; - } - else return 0; - } - - // Build a new edpoly containing both polygons merged. - FPoly NewPoly = *Poly1; - NewPoly.Vertices.Empty(); - int32 Vertex = End1; - for( int32 i=0; iVertices.Num(); i++ ) - { - new(NewPoly.Vertices) FVector(Poly1->Vertices[Vertex]); - if( ++Vertex >= Poly1->Vertices.Num() ) - Vertex=0; - } - Vertex = End2; - for( int32 i=0; i<(Poly2->Vertices.Num()-2); i++ ) - { - if( ++Vertex >= Poly2->Vertices.Num() ) - Vertex=0; - new(NewPoly.Vertices) FVector(Poly2->Vertices[Vertex]); - } - - // Remove colinear vertices and check convexity. - if( NewPoly.RemoveColinears() ) - { - *Poly1 = NewPoly; - Poly2->Vertices.Empty(); - return true; - } - else return 0; -} - -// -// Merge all polygons in coplanar list that can be merged convexly. -// -void UHCsgUtils::MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ) -{ - int32 MergeAgain = 1; - while( MergeAgain ) - { - MergeAgain = 0; - for( int32 i=0; iPolys->Element[PolyList[i]]; - if( Poly1.Vertices.Num() > 0 ) - { - for( int32 j=i+1; jPolys->Element[PolyList[j]]; - if( Poly2.Vertices.Num() > 0 ) - { - if( TryToMerge( &Poly1, &Poly2 ) ) - MergeAgain=1; - } - } - } - } - } -} - -void UHCsgUtils::bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ) -{ - int32 OriginalNum = Model->Polys->Element.Num(); - - // Mark all polys as unprocessed. - for( int32 i=0; iPolys->Element.Num(); i++ ) - Model->Polys->Element[i].PolyFlags &= ~PF_EdProcessed; - - // Find matching coplanars and merge them. - FMemMark Mark(FMemStack::Get()); - int32* PolyList = new(FMemStack::Get(),Model->Polys->Element.Num())int32; - int32 n=0; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - FPoly* EdPoly = &Model->Polys->Element[i]; - if( EdPoly->Vertices.Num()>0 && !(EdPoly->PolyFlags & PF_EdProcessed) ) - { - int32 PolyCount = 0; - PolyList[PolyCount++] = i; - EdPoly->PolyFlags |= PF_EdProcessed; - for( int32 j=i+1; jPolys->Element.Num(); j++ ) - { - FPoly* OtherPoly = &Model->Polys->Element[j]; - if( OtherPoly->iLink == EdPoly->iLink && OtherPoly->Vertices.Num() ) - { - float Dist = (OtherPoly->Vertices[0] - EdPoly->Vertices[0]) | EdPoly->Normal; - if - ( Dist>-0.001 - && Dist<0.001 - && (OtherPoly->Normal|EdPoly->Normal)>0.9999 - && (MergeDisparateTextures - || ( FVector::PointsAreNear(OtherPoly->TextureU,EdPoly->TextureU,THRESH_VECTORS_ARE_NEAR) - && FVector::PointsAreNear(OtherPoly->TextureV,EdPoly->TextureV,THRESH_VECTORS_ARE_NEAR) ) ) ) - { - OtherPoly->PolyFlags |= PF_EdProcessed; - PolyList[PolyCount++] = j; - } - } - } - if( PolyCount > 1 ) - { - MergeCoplanars( Model, PolyList, PolyCount ); - n++; - } - } - } -// UE_LOG(LogEditorBsp, Log, TEXT("Found %i coplanar sets in %i"), n, Model->Polys->Element.Num() ); - Mark.Pop(); - - // Get rid of empty EdPolys while remapping iLinks. - FMemMark Mark2(FMemStack::Get()); - int32 j=0; - int32* Remap = new(FMemStack::Get(),Model->Polys->Element.Num())int32; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - if( Model->Polys->Element[i].Vertices.Num() ) - { - Remap[i] = j; - Model->Polys->Element[j] = Model->Polys->Element[i]; - j++; - } - } - Model->Polys->Element.RemoveAt( j, Model->Polys->Element.Num()-j ); - if( RemapLinks ) - { - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - if (Model->Polys->Element[i].iLink != INDEX_NONE) - { - CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*Remap'. - Model->Polys->Element[i].iLink = Remap[Model->Polys->Element[i].iLink]; - } - } - } -// UE_LOG(LogEditorBsp, Log, TEXT("BspMergeCoplanars reduced %i->%i"), OriginalNum, Model->Polys->Element.Num() ); - Mark2.Pop(); -} - -bool UHCsgUtils::polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly) -{ - FBspSurf &Surf = InModel->Surfs[iSurf]; - if( !Surf.Actor || !Surf.Actor->Brush->Polys->Element.IsValidIndex(Surf.iBrushPoly) ) - { - return false; - } - else - { - Poly = Surf.Actor->Brush->Polys->Element[Surf.iBrushPoly]; - return true; - } -} - -void UHCsgUtils::CleanupNodes( UModel *Model, int32 iNode, int32 iParent ) -{ - FBspNode *Node = &Model->Nodes[iNode]; - - // Transactionally empty vertices of tag-for-empty nodes. - Node->NodeFlags &= ~(NF_IsNew | NF_IsFront | NF_IsBack); - - // Recursively clean up front, back, and plane nodes. - if( Node->iFront != INDEX_NONE ) CleanupNodes( Model, Node->iFront, iNode ); - if( Node->iBack != INDEX_NONE ) CleanupNodes( Model, Node->iBack , iNode ); - if( Node->iPlane != INDEX_NONE ) CleanupNodes( Model, Node->iPlane, iNode ); - - // Reload Node since the recusive call aliases it. - Node = &Model->Nodes[iNode]; - - // If this is an empty node with a coplanar, replace it with the coplanar. - if( Node->NumVertices==0 && Node->iPlane!=INDEX_NONE ) - { - FBspNode* PlaneNode = &Model->Nodes[ Node->iPlane ]; - - // Stick our front, back, and parent nodes on the coplanar. - if( (Node->Plane | PlaneNode->Plane) >= 0.0 ) - { - PlaneNode->iFront = Node->iFront; - PlaneNode->iBack = Node->iBack; - } - else - { - PlaneNode->iFront = Node->iBack; - PlaneNode->iBack = Node->iFront; - } - - if( iParent == INDEX_NONE ) - { - // This node is the root. - *Node = *PlaneNode; // Replace root. - PlaneNode->NumVertices = 0; // Mark as unused. - } - else - { - // This is a child node. - FBspNode *ParentNode = &Model->Nodes[iParent]; - - if ( ParentNode->iFront == iNode ) ParentNode->iFront = Node->iPlane; - else if ( ParentNode->iBack == iNode ) ParentNode->iBack = Node->iPlane; - else if ( ParentNode->iPlane == iNode ) ParentNode->iPlane = Node->iPlane; - else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); - } - } - else if( Node->NumVertices == 0 && ( Node->iFront==INDEX_NONE || Node->iBack==INDEX_NONE ) ) - { - // Delete empty nodes with no fronts or backs. - // Replace empty nodes with only fronts. - // Replace empty nodes with only backs. - int32 iReplacementNode; - if ( Node->iFront != INDEX_NONE ) iReplacementNode = Node->iFront; - else if( Node->iBack != INDEX_NONE ) iReplacementNode = Node->iBack; - else iReplacementNode = INDEX_NONE; - - if( iParent == INDEX_NONE ) - { - // Root. - if( iReplacementNode == INDEX_NONE ) - { - Model->Nodes.Empty(); - } - else - { - *Node = Model->Nodes[iReplacementNode]; - } - } - else - { - // Regular node. - FBspNode *ParentNode = &Model->Nodes[iParent]; - - if ( ParentNode->iFront == iNode ) ParentNode->iFront = iReplacementNode; - else if( ParentNode->iBack == iNode ) ParentNode->iBack = iReplacementNode; - else if( ParentNode->iPlane == iNode ) ParentNode->iPlane = iReplacementNode; - else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); - } - } -} - - -void UHCsgUtils::bspCleanup( UModel *Model ) -{ - if( Model->Nodes.Num() > 0 ) - CleanupNodes( Model, 0, INDEX_NONE ); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HCsgUtils.h" + +#include "Engine/Engine.h" +#include "Engine/Polys.h" +#include "Engine/Selection.h" +#include "Materials/Material.h" +#include "Misc/FeedbackContext.h" + +#include "ActorEditorUtils.h" +#include "Misc/ScopedSlowTask.h" + + +DEFINE_LOG_CATEGORY_STATIC(LogHCsgUtils, Log, All); + +#if WITH_EDITOR +#include "Editor.h" +#endif + +// Magic numbers. +#define THRESH_OPTGEOM_COPLANAR (0.25) /* Threshold for Bsp geometry optimization */ +#define THRESH_OPTGEOM_COSIDAL (0.25) /* Threshold for Bsp geometry optimization */ + + +UHCsgUtils::UHCsgUtils() +{ + // A TempModel is allocated for the HCsgUtils instance to avoid reallocation during inner loops. + TempModel = NewObject(); + TempModel->Initialize(nullptr, 1); + + /*GBspPoints = NewObject(); + GBspVectors = NewObject();*/ +} + + +/*---------------------------------------------------------------------------- + CSG leaf filter callbacks. +----------------------------------------------------------------------------*/ + +void UHCsgUtils::AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); + break; + case F_COSPATIAL_FACING_OUT: + if( !(EdPoly->PolyFlags & PF_Semisolid) ) + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_IN: + break; + } +} + +void UHCsgUtils::AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + // Only affect the world poly if it has been cut. + if( EdPoly->PolyFlags & PF_EdCut ) + FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors ); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_IN: + case F_COSPATIAL_FACING_OUT: + // Discard original poly. + GDiscarded++; + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + break; + } +} + +void UHCsgUtils::SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch (Filter) + { + case F_OUTSIDE: + case F_COSPATIAL_FACING_OUT: + case F_COSPATIAL_FACING_IN: + case F_COPLANAR_OUTSIDE: + break; + case F_COPLANAR_INSIDE: + case F_INSIDE: + EdPoly->Reverse(); + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); // Add to Bsp back + EdPoly->Reverse(); + break; + } +} + +void UHCsgUtils::SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + // Only affect the world poly if it has been cut. + if( EdPoly->PolyFlags & PF_EdCut ) + FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + // Discard original poly. + GDiscarded++; + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + break; + } +} + +void UHCsgUtils::IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + case F_COSPATIAL_FACING_OUT: + // Ignore. + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + if( EdPoly->Fix()>=3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + // Ignore. + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + if( EdPoly->Fix() >= 3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + case F_COSPATIAL_FACING_IN: + // Ignore. + break; + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + if( EdPoly->Fix()>=3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_OUT: + // Ignore. + break; + case F_COPLANAR_INSIDE: + case F_INSIDE: + case F_COSPATIAL_FACING_IN: + if( EdPoly->Fix() >= 3 ) + { + EdPoly->Reverse(); + new(GModel->Polys->Element)FPoly(*EdPoly); + EdPoly->Reverse(); + } + break; + } +} + +/*---------------------------------------------------------------------------- + CSG polygon filtering routine (calls the callbacks). +----------------------------------------------------------------------------*/ + +// +// Handle a piece of a polygon that was filtered to a leaf. +// +void UHCsgUtils::FilterLeaf +( + BspFilterFunc FilterFunc, + UModel* Model, + int32 iNode, + FPoly* EdPoly, + FCoplanarInfo CoplanarInfo, + int32 LeafOutside, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + EPolyNodeFilter FilterType; + + if( CoplanarInfo.iOriginalNode == INDEX_NONE ) + { + // Processing regular, non-coplanar polygons. + FilterType = LeafOutside ? F_OUTSIDE : F_INSIDE; + (this->*FilterFunc)( Model, iNode, EdPoly, FilterType, ENodePlace, BspPoints, BspVectors ); + } + else if( CoplanarInfo.ProcessingBack ) + { + // Finished filtering polygon through tree in back of parent coplanar. + DoneFilteringBack: + if ((!LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_INSIDE; + else if (( LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_OUTSIDE; + else if ((!LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_OUT; + else if (( LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_IN; + else + { + UE_LOG(LogHCsgUtils, Fatal, TEXT("FilterLeaf: Bad Locs")); + return; + } + (this->*FilterFunc)( Model, CoplanarInfo.iOriginalNode, EdPoly, FilterType, FHBSPOps::NODE_Plane, BspPoints, BspVectors ); + } + else + { + CoplanarInfo.FrontLeafOutside = LeafOutside; + + if( CoplanarInfo.iBackNode == INDEX_NONE ) + { + // Back tree is empty. + LeafOutside = CoplanarInfo.BackNodeOutside; + goto DoneFilteringBack; + } + else + { + // Call FilterEdPoly to filter through the back. This will result in + // another call to FilterLeaf with iNode = leaf this falls into in the + // back tree and EdPoly = the final EdPoly to insert. + CoplanarInfo.ProcessingBack=1; + FilterEdPoly( FilterFunc, Model, CoplanarInfo.iBackNode, EdPoly,CoplanarInfo, CoplanarInfo.BackNodeOutside, BspPoints, BspVectors ); + } + } +} + +// +// Filter an EdPoly through the Bsp recursively, calling FilterFunc +// for all chunks that fall into leaves. FCoplanarInfo is used to +// handle the tricky case of double-recursion for polys that must be +// filtered through a node's front, then filtered through the node's back, +// in order to handle coplanar CSG properly. +// +void UHCsgUtils::FilterEdPoly +( + BspFilterFunc FilterFunc, + UModel *Model, + int32 iNode, + FPoly *EdPoly, + FCoplanarInfo CoplanarInfo, + int32 Outside, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + int32 SplitResult,iOurFront,iOurBack; + int32 NewFrontOutside,NewBackOutside; + + FilterLoop: + + // Split em. + FPoly TempFrontEdPoly,TempBackEdPoly; + SplitResult = EdPoly->SplitWithPlane + ( + Model->Points [Model->Verts[Model->Nodes[iNode].iVertPool].pVertex], + Model->Vectors[Model->Surfs[Model->Nodes[iNode].iSurf].vNormal], + &TempFrontEdPoly, + &TempBackEdPoly, + 0 + ); + + // Process split results. + if( SplitResult == SP_Front ) + { + Front: + + FBspNode *Node = &Model->Nodes[iNode]; + Outside = Outside || Node->IsCsg(); + + if( Node->iFront == INDEX_NONE ) + { + FilterLeaf(FilterFunc,Model,iNode,EdPoly,CoplanarInfo,Outside,FHBSPOps::NODE_Front, BspPoints, BspVectors); + } + else + { + iNode = Node->iFront; + goto FilterLoop; + } + } + else if( SplitResult == SP_Back ) + { + FBspNode *Node = &Model->Nodes[iNode]; + Outside = Outside && !Node->IsCsg(); + + if( Node->iBack == INDEX_NONE ) + { + FilterLeaf( FilterFunc, Model, iNode, EdPoly, CoplanarInfo, Outside, FHBSPOps::NODE_Back, BspPoints, BspVectors); + } + else + { + iNode=Node->iBack; + goto FilterLoop; + } + } + else if( SplitResult == SP_Coplanar ) + { + if( CoplanarInfo.iOriginalNode != INDEX_NONE ) + { + // This will happen once in a blue moon when a polygon is barely outside the + // coplanar threshold and is split up into a new polygon that is + // is barely inside the coplanar threshold. To handle this, just classify + // it as front and it will be handled propery. + FHBSPOps::GErrors++; +// UE_LOG(LogEditorBsp, Warning, TEXT("FilterEdPoly: Encountered out-of-place coplanar") ); + goto Front; + } + CoplanarInfo.iOriginalNode = iNode; + CoplanarInfo.iBackNode = INDEX_NONE; + CoplanarInfo.ProcessingBack = 0; + CoplanarInfo.BackNodeOutside = Outside; + NewFrontOutside = Outside; + + // See whether Node's iFront or iBack points to the side of the tree on the front + // of this polygon (will be as expected if this polygon is facing the same + // way as first coplanar in link, otherwise opposite). + if( (FVector(Model->Nodes[iNode].Plane) | EdPoly->Normal) >= 0.0 ) + { + iOurFront = Model->Nodes[iNode].iFront; + iOurBack = Model->Nodes[iNode].iBack; + + if( Model->Nodes[iNode].IsCsg() ) + { + CoplanarInfo.BackNodeOutside = 0; + NewFrontOutside = 1; + } + } + else + { + iOurFront = Model->Nodes[iNode].iBack; + iOurBack = Model->Nodes[iNode].iFront; + + if( Model->Nodes[iNode].IsCsg() ) + { + CoplanarInfo.BackNodeOutside = 1; + NewFrontOutside = 0; + } + } + + // Process front and back. + if ((iOurFront==INDEX_NONE)&&(iOurBack==INDEX_NONE)) + { + // No front or back. + CoplanarInfo.ProcessingBack = 1; + CoplanarInfo.FrontLeafOutside = NewFrontOutside; + FilterLeaf + ( + FilterFunc, + Model, + iNode, + EdPoly, + CoplanarInfo, + CoplanarInfo.BackNodeOutside, + FHBSPOps::NODE_Plane, + BspPoints, + BspVectors + ); + } + else if( iOurFront==INDEX_NONE && iOurBack!=INDEX_NONE ) + { + // Back but no front. + CoplanarInfo.ProcessingBack = 1; + CoplanarInfo.iBackNode = iOurBack; + CoplanarInfo.FrontLeafOutside = NewFrontOutside; + + iNode = iOurBack; + Outside = CoplanarInfo.BackNodeOutside; + goto FilterLoop; + } + else + { + // Has a front and maybe a back. + + // Set iOurBack up to process back on next call to FilterLeaf, and loop + // to process front. Next call to FilterLeaf will set FrontLeafOutside. + CoplanarInfo.ProcessingBack = 0; + + // May be a node or may be INDEX_NONE. + CoplanarInfo.iBackNode = iOurBack; + + iNode = iOurFront; + Outside = NewFrontOutside; + goto FilterLoop; + } + } + else if( SplitResult == SP_Split ) + { + // Front half of split. + if( Model->Nodes[iNode].IsCsg() ) + { + NewFrontOutside = 1; + NewBackOutside = 0; + } + else + { + NewFrontOutside = Outside; + NewBackOutside = Outside; + } + + if( Model->Nodes[iNode].iFront==INDEX_NONE ) + { + FilterLeaf + ( + FilterFunc, + Model, + iNode, + &TempFrontEdPoly, + CoplanarInfo, + NewFrontOutside, + FHBSPOps::NODE_Front, + BspPoints, + BspVectors + ); + } + else + { + FilterEdPoly + ( + FilterFunc, + Model, + Model->Nodes[iNode].iFront, + &TempFrontEdPoly, + CoplanarInfo, + NewFrontOutside, + BspPoints, + BspVectors + ); + } + + // Back half of split. + if( Model->Nodes[iNode].iBack==INDEX_NONE ) + { + FilterLeaf + ( + FilterFunc, + Model, + iNode, + &TempBackEdPoly, + CoplanarInfo, + NewBackOutside, + FHBSPOps::NODE_Back, + BspPoints, + BspVectors + ); + } + else + { + FilterEdPoly + ( + FilterFunc, + Model, + Model->Nodes[iNode].iBack, + &TempBackEdPoly, + CoplanarInfo, + NewBackOutside, + BspPoints, + BspVectors + ); + } + } +} + +// +// Regular entry into FilterEdPoly (so higher-level callers don't have to +// deal with unnecessary info). Filters starting at root. +// +void UHCsgUtils::BspFilterFPoly( BspFilterFunc FilterFunc, UModel *Model, FPoly *EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + FCoplanarInfo StartingCoplanarInfo; + StartingCoplanarInfo.iOriginalNode = INDEX_NONE; + if( Model->Nodes.Num() == 0 ) + { + // If Bsp is empty, process at root. + (this->*FilterFunc)( Model, 0, EdPoly, Model->RootOutside ? F_OUTSIDE : F_INSIDE, FHBSPOps::NODE_Root, BspPoints, BspVectors ); + } + else + { + // Filter through Bsp. + FilterEdPoly( FilterFunc, Model, 0, EdPoly, StartingCoplanarInfo, Model->RootOutside, BspPoints, BspVectors ); + } +} + +int UHCsgUtils::bspNodeToFPoly +( + UModel* Model, + int32 iNode, + FPoly* EdPoly +) +{ + FPoly MasterEdPoly; + + FBspNode &Node = Model->Nodes[iNode]; + FBspSurf &Poly = Model->Surfs[Node.iSurf]; + FVert *VertPool = &Model->Verts[ Node.iVertPool ]; + + EdPoly->Base = Model->Points [Poly.pBase]; + EdPoly->Normal = Model->Vectors[Poly.vNormal]; + + EdPoly->PolyFlags = Poly.PolyFlags & ~(PF_EdCut | PF_EdProcessed | PF_Selected | PF_Memorized); + EdPoly->iLinkSurf = Node.iSurf; + EdPoly->Material = Poly.Material; + + EdPoly->Actor = Poly.Actor; + EdPoly->iBrushPoly = Poly.iBrushPoly; + + if( polyFindMaster(Model,Node.iSurf,MasterEdPoly) ) + EdPoly->ItemName = MasterEdPoly.ItemName; + else + EdPoly->ItemName = NAME_None; + + EdPoly->TextureU = Model->Vectors[Poly.vTextureU]; + EdPoly->TextureV = Model->Vectors[Poly.vTextureV]; + + EdPoly->LightMapScale = Poly.LightMapScale; + + EdPoly->LightmassSettings = Model->LightmassSettings[Poly.iLightmassIndex]; + + EdPoly->Vertices.Empty(); + + for(int32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++) + { + new(EdPoly->Vertices) FVector(Model->Points[VertPool[VertexIndex].pVertex]); + } + + if(EdPoly->Vertices.Num() < 3) + { + EdPoly->Vertices.Empty(); + } + else + { + // Remove colinear points and identical points (which will appear + // if T-joints were eliminated). + EdPoly->RemoveColinears(); + } + + return EdPoly->Vertices.Num(); +} + +/*--------------------------------------------------------------------------------------- + World filtering. +---------------------------------------------------------------------------------------*/ + +// +// Filter all relevant world polys through the brush. +// +void UHCsgUtils::FilterWorldThroughBrush +( + UModel* Model, + UModel* Brush, + EBrushType BrushType, + ECsgOper CSGOper, + int32 iNode, + FSphere* BrushSphere, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + // Loop through all coplanars. + while( iNode != INDEX_NONE ) + { + // Get surface. + int32 iSurf = Model->Nodes[iNode].iSurf; + + // Skip new nodes and their children, which are guaranteed new. + if( Model->Nodes[iNode].NodeFlags & NF_IsNew ) + return; + + // Sphere reject. + int DoFront = 1, DoBack = 1; + if( BrushSphere ) + { + float Dist = Model->Nodes[iNode].Plane.PlaneDot( BrushSphere->Center ); + DoFront = (Dist >= -BrushSphere->W); + DoBack = (Dist <= +BrushSphere->W); + } + + // Process only polys that aren't empty. + FPoly TempEdPoly; + if( DoFront && DoBack && (GEditor->bspNodeToFPoly(Model,iNode,&TempEdPoly)>0) ) + { + TempEdPoly.Actor = Model->Surfs[iSurf].Actor; + TempEdPoly.iBrushPoly = Model->Surfs[iSurf].iBrushPoly; + + if( BrushType==Brush_Add || BrushType==Brush_Subtract ) + { + // Add and subtract work the same in this step. + GNode = iNode; + GModel = Model; + GDiscarded = 0; + GNumNodes = Model->Nodes.Num(); + + // Find last coplanar in chain. + GLastCoplanar = iNode; + while( Model->Nodes[GLastCoplanar].iPlane != INDEX_NONE ) + GLastCoplanar = Model->Nodes[GLastCoplanar].iPlane; + + // Do the filter operation. + BspFilterFPoly + ( + BrushType==Brush_Add ? &UHCsgUtils::AddWorldToBrushFunc : &UHCsgUtils::SubtractWorldToBrushFunc, + Brush, + &TempEdPoly, + BspPoints, + BspVectors + ); + + if( GDiscarded == 0 ) + { + // Get rid of all the fragments we added. + Model->Nodes[GLastCoplanar].iPlane = INDEX_NONE; + const bool bAllowShrinking = false; + Model->Nodes.RemoveAt( GNumNodes, Model->Nodes.Num()-GNumNodes, bAllowShrinking ); + } + else + { + // Tag original world poly for deletion; has been deleted or replaced by partial fragments. + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + } + } + else if( CSGOper == CSG_Intersect ) + { + BspFilterFPoly( &UHCsgUtils::IntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); + } + else if( CSGOper == CSG_Deintersect ) + { + BspFilterFPoly( &UHCsgUtils::DeIntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); + } + } + + // Now recurse to filter all of the world's children nodes. + if( DoFront && (Model->Nodes[iNode].iFront != INDEX_NONE)) FilterWorldThroughBrush + ( + Model, + Brush, + BrushType, + CSGOper, + Model->Nodes[iNode].iFront, + BrushSphere, + BspPoints, + BspVectors + ); + if( DoBack && (Model->Nodes[iNode].iBack != INDEX_NONE) ) FilterWorldThroughBrush + ( + Model, + Brush, + BrushType, + CSGOper, + Model->Nodes[iNode].iBack, + BrushSphere, + BspPoints, + BspVectors + ); + iNode = Model->Nodes[iNode].iPlane; + } +} + +void UHCsgUtils::RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic) +{ + if (!IsValid(Model)) + return; + + UHCsgUtils* CsgUtils = NewObject(); + int32 CsgErrors = 0; + + UHBspPointsGrid* BspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* BspVectors = UHBspPointsGrid::Create(1/16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + + // Empty the model out. + const int32 NumPoints = Model->Points.Num(); + const int32 NumNodes = Model->Nodes.Num(); + const int32 NumVerts = Model->Verts.Num(); + const int32 NumVectors = Model->Vectors.Num(); + const int32 NumSurfs = Model->Surfs.Num(); + + Model->Modify(); + Model->EmptyModel(1, 1); + + // Reserve arrays an eighth bigger than the previous allocation + Model->Points.Empty(NumPoints + NumPoints / 8); + Model->Nodes.Empty(NumNodes + NumNodes / 8); + Model->Verts.Empty(NumVerts + NumVerts / 8); + Model->Vectors.Empty(NumVectors + NumVectors / 8); + Model->Surfs.Empty(NumSurfs + NumSurfs / 8); + + // Build list of all static brushes, first structural brushes and portals + TArray StaticBrushes; + for (ABrush* Brush : Brushes) + { + if ((Brush && (Brush->IsStaticBrush() || bTreatMovableBrushesAsStatic) && !FActorEditorUtils::IsABuilderBrush(Brush)) && + (!(Brush->PolyFlags & PF_Semisolid) || (Brush->BrushType != Brush_Add) || (Brush->PolyFlags & PF_Portal))) + { + StaticBrushes.Add(Brush); + + // Treat portals as solids for cutting. + if (Brush->PolyFlags & PF_Portal) + { + Brush->PolyFlags = (Brush->PolyFlags & ~PF_Semisolid) | PF_NotSolid; + } + } + } + + // Next append all detail brushes + for (ABrush* Brush : Brushes) + { + if (Brush && Brush->IsStaticBrush() && !FActorEditorUtils::IsABuilderBrush(Brush) && + (Brush->PolyFlags & PF_Semisolid) && !(Brush->PolyFlags & PF_Portal) && (Brush->BrushType == Brush_Add)) + { + StaticBrushes.Add(Brush); + } + } + + // Build list of dynamic brushes + TArray DynamicBrushes; + if (!bTreatMovableBrushesAsStatic) + { + for (ABrush* DynamicBrush : Brushes) + { + if (DynamicBrush && DynamicBrush->Brush && !DynamicBrush->IsStaticBrush()) + { + DynamicBrushes.Add(DynamicBrush); + } + } + } + + FScopedSlowTask SlowTask(StaticBrushes.Num() + DynamicBrushes.Num()); + SlowTask.MakeDialogDelayed(3.0f); + + // Compose all static brushes + for (ABrush* Brush : StaticBrushes) + { + SlowTask.EnterProgressFrame(1); + Brush->Modify(); + int32 Errors = CsgUtils->ComposeBrushCSG(Brush, Model, Brush->PolyFlags, (EBrushType)Brush->BrushType, CSG_None, false, true, false, false, BspPoints, BspVectors); + if (Errors > 1) + CsgErrors += Errors - 1; + } + + // Rebuild dynamic brush BSP's (if they weren't handled earlier) + for (ABrush* DynamicBrush : DynamicBrushes) + { + SlowTask.EnterProgressFrame(1); + UHBspPointsGrid* LocalBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* LocalBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + + FHBSPOps::csgPrepMovingBrush(DynamicBrush, LocalBspPoints, LocalBspVectors); + } +} + + + +UModel* UHCsgUtils::BuildModelFromBrushes(TArray& Brushes) +{ + // Generally UModels are initialized using ABrush. Here we manually + // initialize using relevant parts from + UModel* OutModel = NewObject(); + OutModel->SetFlags(RF_Transactional); + OutModel->RootOutside = true; + OutModel->EmptyModel(1,1); + OutModel->UpdateVertices(); + + if (!IsValid(OutModel)) + return nullptr; + + // Can we combine the brushes without modifying the actors here ...? + + //FVector Location(0.0f, 0.0f, 0.0f); + //FRotator Rotation(0.0f, 0.0f, 0.0f); + //for(int32 BrushesIdx = 0; BrushesIdx < Brushes.Num(); ++BrushesIdx ) + //{ + // // Cache the location and rotation. + // Location = Brushes[BrushesIdx]->GetActorLocation(); + // Rotation = Brushes[BrushesIdx]->GetActorRotation(); + + + // // Leave the actor's rotation but move it to origin so the Static Mesh will generate correctly. + // Brushes[BrushesIdx]->TeleportTo(Location - InPivotLocation, Rotation, false, true); + //} + + RebuildModelFromBrushes(OutModel, Brushes, true); + //GEditor->bspBuildFPolys(OutModel, true, 0); + + //if (0 < ConversionTempModel->Polys->Element.Num()) + //{ + // UStaticMesh* NewMesh = CreateStaticMeshFromBrush(Pkg, ObjName, NULL, ConversionTempModel); + // NewActor = FActorFactoryAssetProxy::AddActorForAsset( NewMesh ); + + // NewActor->Modify(); + + // NewActor->InvalidateLightingCache(); + // NewActor->PostEditChange(); + // NewActor->PostEditMove( true ); + // NewActor->Modify(); + // ULayersSubsystem* LayersSubsystem = GetEditorSubsystem(); + // LayersSubsystem->InitializeNewActorLayers(NewActor); + + // // Teleport the new actor to the old location but not the old rotation. The static mesh is built to the rotation already. + // NewActor->TeleportTo(InPivotLocation, FRotator(0.0f, 0.0f, 0.0f), false, true); + + // // Destroy the old brushes. + // for( int32 BrushIdx = 0; BrushIdx < InBrushesToConvert.Num(); ++BrushIdx ) + // { + // LayersSubsystem->DisassociateActorFromLayers(InBrushesToConvert[BrushIdx]); + // GWorld->EditorDestroyActor( InBrushesToConvert[BrushIdx], true ); + // } + + // // Notify the asset registry + // FAssetRegistryModule::AssetCreated(NewMesh); + //} + + //ConversionTempModel->EmptyModel(1, 1); + //RebuildAlteredBSP(); + //RedrawLevelEditingViewports(); + + //return NewActor; + + return OutModel; +} + +int UHCsgUtils::ComposeBrushCSG +( + ABrush* Actor, + UModel* Model, + uint32 PolyFlags, + EBrushType BrushType, + ECsgOper CSGOper, + bool bBuildBounds, + bool bMergePolys, + bool bReplaceNULLMaterialRefs, + bool bShowProgressBar, /*=true*/ + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + uint32 NotPolyFlags = 0; + int32 NumPolysFromBrush=0,i,j,ReallyBig; + UModel* Brush = Actor->Brush; + int32 Errors = 0; + + // Make sure we're in an acceptable state. + if( !Brush ) + { + return 0; + } + + // Non-solid and semisolid stuff can only be added. + if( BrushType != Brush_Add ) + { + NotPolyFlags |= (PF_Semisolid | PF_NotSolid); + } + + TempModel->EmptyModel(1,1); + + // Update status. + ReallyBig = (Brush->Polys->Element.Num() > 200) && bShowProgressBar; + if( ReallyBig ) + { + FText Description = NSLOCTEXT("UnrealEd", "PerformingCSGOperation", "Performing CSG operation"); + + if (BrushType != Brush_MAX) + { + if (BrushType == Brush_Add) + { + Description = NSLOCTEXT("UnrealEd", "AddingBrushToWorld", "Adding brush to world"); + } + else if (BrushType == Brush_Subtract) + { + Description = NSLOCTEXT("UnrealEd", "SubtractingBrushFromWorld", "Subtracting brush from world"); + } + } + else if (CSGOper != CSG_None) + { + if (CSGOper == CSG_Intersect) + { + Description = NSLOCTEXT("UnrealEd", "IntersectingBrushWithWorld", "Intersecting brush with world"); + } + else if (CSGOper == CSG_Deintersect) + { + Description = NSLOCTEXT("UnrealEd", "DeintersectingBrushWithWorld", "Deintersecting brush with world"); + } + } + + GWarn->BeginSlowTask( Description, true ); + // Transform original brush poly into same coordinate system as world + // so Bsp filtering operations make sense. + GWarn->StatusUpdate(0, 0, NSLOCTEXT("UnrealEd", "Transforming", "Transforming")); + } + + + //UMaterialInterface* SelectedMaterialInstance = GEditor->GetSelectedObjects()->GetTop(); + UMaterialInterface* SelectedMaterialInstance = nullptr; + + const FVector Scale = Actor->GetActorScale(); + const FRotator Rotation = Actor->GetActorRotation(); + const FVector Location = Actor->GetActorLocation(); + + const bool bIsMirrored = (Scale.X * Scale.Y * Scale.Z < 0.0f); + + // Cache actor transform which is used for the geometry being built + Brush->OwnerLocationWhenLastBuilt = Location; + Brush->OwnerRotationWhenLastBuilt = Rotation; + Brush->OwnerScaleWhenLastBuilt = Scale; + Brush->bCachedOwnerTransformValid = true; + + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly& CurrentPoly = Brush->Polys->Element[i]; + + // Set texture the first time. + if ( bReplaceNULLMaterialRefs ) + { + UMaterialInterface*& PolyMat = CurrentPoly.Material; + if ( !PolyMat || PolyMat == UMaterial::GetDefaultMaterial(MD_Surface) ) + { + PolyMat = SelectedMaterialInstance; + } + } + + // Get the brush poly. + FPoly DestEdPoly = CurrentPoly; + check(CurrentPoly.iLinkPolys->Element.Num()); + + // Set its backward brush link. + DestEdPoly.Actor = Actor; + DestEdPoly.iBrushPoly = i; + + // Update its flags. + DestEdPoly.PolyFlags = (DestEdPoly.PolyFlags | PolyFlags) & ~NotPolyFlags; + + // Set its internal link. + if (DestEdPoly.iLink == INDEX_NONE) + { + DestEdPoly.iLink = i; + } + + // Transform it. + DestEdPoly.Scale( Scale ); + DestEdPoly.Rotate( Rotation ); + DestEdPoly.Transform( Location ); + + // Reverse winding and normal if the parent brush is mirrored + if (bIsMirrored) + { + DestEdPoly.Reverse(); + DestEdPoly.CalcNormal(); + } + + // Add poly to the temp model. + new(TempModel->Polys->Element)FPoly( DestEdPoly ); + } + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringBrush", "Filtering brush") ); + + // Pass the brush polys through the world Bsp. + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + // Empty the brush. + Brush->EmptyModel(1,1); + + // Intersect and deintersect. + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly EdPoly = TempModel->Polys->Element[i]; + GModel = Brush; + // TODO: iLink / iLinkSurf in EdPoly / TempModel->Polys->Element[i] ? + BspFilterFPoly( CSGOper==CSG_Intersect ? &UHCsgUtils::IntersectBrushWithWorldFunc : &UHCsgUtils::DeIntersectBrushWithWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); + } + NumPolysFromBrush = Brush->Polys->Element.Num(); + } + else + { + // Add and subtract. + TMap SurfaceIndexRemap; + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly EdPoly = TempModel->Polys->Element[i]; + + // Mark the polygon as non-cut so that it won't be harmed unless it must + // be split, and set iLink so that BspAddNode will know to add its information + // if a node is added based on this poly. + EdPoly.PolyFlags &= ~(PF_EdCut); + const int32* SurfaceIndexPtr = SurfaceIndexRemap.Find(EdPoly.iLink); + if (SurfaceIndexPtr == nullptr) + { + const int32 NewSurfaceIndex = Model->Surfs.Num(); + SurfaceIndexRemap.Add(EdPoly.iLink, NewSurfaceIndex); + EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = NewSurfaceIndex; + } + else + { + EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = *SurfaceIndexPtr; + } + + // Filter brush through the world. + BspFilterFPoly( BrushType==Brush_Add ? &UHCsgUtils::AddBrushToWorldFunc : &UHCsgUtils::SubtractBrushFromWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); + } + } + if( Model->Nodes.Num() && !(PolyFlags & (PF_NotSolid | PF_Semisolid)) ) + { + // Quickly build a Bsp for the brush, tending to minimize splits rather than balance + // the tree. We only need the cutting planes, though the entire Bsp struct (polys and + // all) is built. + + /*FHBspPointsGrid* LevelModelPointsGrid = FHBspPointsGrid::GBspPoints; + FHBspPointsGrid* LevelModelVectorsGrid = FHBspPointsGrid::GBspVectors;*/ + + // For the bspBuild call, temporarily create a new pair of BspPointsGrids for the TempModel. + UHBspPointsGrid* TempBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* TempBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + /*FHBspPointsGrid::GBspPoints = BspPoints.Get(); + FHBspPointsGrid::GBspVectors = BspVectors.Get();*/ + + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "BuildingBSP", "Building BSP") ); + + FHBSPOps::bspBuild( TempModel, FHBSPOps::BSP_Lame, 0, 70, 1, 0, TempBspPoints, TempBspVectors ); + + // Reinstate the original BspPointsGrids used for building the level Model. + /*FHBspPointsGrid::GBspPoints = LevelModelPointsGrid; + FHBspPointsGrid::GBspVectors = LevelModelVectorsGrid;*/ + + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringWorld", "Filtering world") ); + GModel = Brush; + TempModel->BuildBound(); + + FSphere BrushSphere = TempModel->Bounds.GetSphere(); + FilterWorldThroughBrush( Model, TempModel, BrushType, CSGOper, 0, &BrushSphere, BspPoints, BspVectors); + } + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "AdjustingBrush", "Adjusting brush") ); + + // Link polys obtained from the original brush. + for( i=NumPolysFromBrush-1; i>=0; i-- ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + for( j=0; jiLink == Brush->Polys->Element[j].iLink ) + { + DestEdPoly->iLink = j; + break; + } + } + if( j >= i ) DestEdPoly->iLink = i; + } + + // Link polys obtained from the world. + for( i=Brush->Polys->Element.Num()-1; i>=NumPolysFromBrush; i-- ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + for( j=NumPolysFromBrush; jiLink == Brush->Polys->Element[j].iLink ) + { + DestEdPoly->iLink = j; + break; + } + } + if( j >= i ) DestEdPoly->iLink = i; + } + Brush->Linked = 1; + + // Detransform the obtained brush back into its original coordinate system. + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + DestEdPoly->Transform(-Location); + DestEdPoly->Rotate(Rotation.GetInverse()); + DestEdPoly->Scale(FVector(1.0f) / Scale); + DestEdPoly->Fix(); + DestEdPoly->Actor = NULL; + DestEdPoly->iBrushPoly = i; + } + } + + if( BrushType==Brush_Add || BrushType==Brush_Subtract ) + { + // Clean up nodes, reset node flags. + bspCleanup( Model ); + + // Rebuild bounding volumes. + if( bBuildBounds ) + { + FHBSPOps::bspBuildBounds( Model ); + } + } + + Brush->NumUniqueVertices = TempModel->Points.Num(); + // Release TempModel. + TempModel->EmptyModel(1,1); + + // Merge coplanars if needed. + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + if( ReallyBig ) + { + GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "Merging", "Merging") ); + } + if( bMergePolys ) + { + bspMergeCoplanars( Brush, 1, 0 ); + } + } + if( ReallyBig ) + { + GWarn->EndSlowTask(); + } + + return 1 + FHBSPOps::GErrors; +} + +/*---------------------------------------------------------------------------- + EdPoly building and compacting. +----------------------------------------------------------------------------*/ + +// +// Trys to merge two polygons. If they can be merged, replaces Poly1 and emptys Poly2 +// and returns 1. Otherwise, returns 0. +// +int UHCsgUtils::TryToMerge( FPoly *Poly1, FPoly *Poly2 ) +{ + // Find one overlapping point. + int32 Start1=0, Start2=0; + for( Start1=0; Start1Vertices.Num(); Start1++ ) + for( Start2=0; Start2Vertices.Num(); Start2++ ) + if( FVector::PointsAreSame(Poly1->Vertices[Start1], Poly2->Vertices[Start2]) ) + goto FoundOverlap; + return 0; + FoundOverlap: + + // Wrap around trying to merge. + int32 End1 = Start1; + int32 End2 = Start2; + int32 Test1 = Start1+1; if (Test1>=Poly1->Vertices.Num()) Test1 = 0; + int32 Test2 = Start2-1; if (Test2<0) Test2 = Poly2->Vertices.Num()-1; + if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) + { + End1 = Test1; + Start2 = Test2; + } + else + { + Test1 = Start1-1; if (Test1<0) Test1=Poly1->Vertices.Num()-1; + Test2 = Start2+1; if (Test2>=Poly2->Vertices.Num()) Test2=0; + if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) + { + Start1 = Test1; + End2 = Test2; + } + else return 0; + } + + // Build a new edpoly containing both polygons merged. + FPoly NewPoly = *Poly1; + NewPoly.Vertices.Empty(); + int32 Vertex = End1; + for( int32 i=0; iVertices.Num(); i++ ) + { + new(NewPoly.Vertices) FVector(Poly1->Vertices[Vertex]); + if( ++Vertex >= Poly1->Vertices.Num() ) + Vertex=0; + } + Vertex = End2; + for( int32 i=0; i<(Poly2->Vertices.Num()-2); i++ ) + { + if( ++Vertex >= Poly2->Vertices.Num() ) + Vertex=0; + new(NewPoly.Vertices) FVector(Poly2->Vertices[Vertex]); + } + + // Remove colinear vertices and check convexity. + if( NewPoly.RemoveColinears() ) + { + *Poly1 = NewPoly; + Poly2->Vertices.Empty(); + return true; + } + else return 0; +} + +// +// Merge all polygons in coplanar list that can be merged convexly. +// +void UHCsgUtils::MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ) +{ + int32 MergeAgain = 1; + while( MergeAgain ) + { + MergeAgain = 0; + for( int32 i=0; iPolys->Element[PolyList[i]]; + if( Poly1.Vertices.Num() > 0 ) + { + for( int32 j=i+1; jPolys->Element[PolyList[j]]; + if( Poly2.Vertices.Num() > 0 ) + { + if( TryToMerge( &Poly1, &Poly2 ) ) + MergeAgain=1; + } + } + } + } + } +} + +void UHCsgUtils::bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ) +{ + int32 OriginalNum = Model->Polys->Element.Num(); + + // Mark all polys as unprocessed. + for( int32 i=0; iPolys->Element.Num(); i++ ) + Model->Polys->Element[i].PolyFlags &= ~PF_EdProcessed; + + // Find matching coplanars and merge them. + FMemMark Mark(FMemStack::Get()); + int32* PolyList = new(FMemStack::Get(),Model->Polys->Element.Num())int32; + int32 n=0; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + FPoly* EdPoly = &Model->Polys->Element[i]; + if( EdPoly->Vertices.Num()>0 && !(EdPoly->PolyFlags & PF_EdProcessed) ) + { + int32 PolyCount = 0; + PolyList[PolyCount++] = i; + EdPoly->PolyFlags |= PF_EdProcessed; + for( int32 j=i+1; jPolys->Element.Num(); j++ ) + { + FPoly* OtherPoly = &Model->Polys->Element[j]; + if( OtherPoly->iLink == EdPoly->iLink && OtherPoly->Vertices.Num() ) + { + float Dist = (OtherPoly->Vertices[0] - EdPoly->Vertices[0]) | EdPoly->Normal; + if + ( Dist>-0.001 + && Dist<0.001 + && (OtherPoly->Normal|EdPoly->Normal)>0.9999 + && (MergeDisparateTextures + || ( FVector::PointsAreNear(OtherPoly->TextureU,EdPoly->TextureU,THRESH_VECTORS_ARE_NEAR) + && FVector::PointsAreNear(OtherPoly->TextureV,EdPoly->TextureV,THRESH_VECTORS_ARE_NEAR) ) ) ) + { + OtherPoly->PolyFlags |= PF_EdProcessed; + PolyList[PolyCount++] = j; + } + } + } + if( PolyCount > 1 ) + { + MergeCoplanars( Model, PolyList, PolyCount ); + n++; + } + } + } +// UE_LOG(LogEditorBsp, Log, TEXT("Found %i coplanar sets in %i"), n, Model->Polys->Element.Num() ); + Mark.Pop(); + + // Get rid of empty EdPolys while remapping iLinks. + FMemMark Mark2(FMemStack::Get()); + int32 j=0; + int32* Remap = new(FMemStack::Get(),Model->Polys->Element.Num())int32; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + if( Model->Polys->Element[i].Vertices.Num() ) + { + Remap[i] = j; + Model->Polys->Element[j] = Model->Polys->Element[i]; + j++; + } + } + Model->Polys->Element.RemoveAt( j, Model->Polys->Element.Num()-j ); + if( RemapLinks ) + { + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + if (Model->Polys->Element[i].iLink != INDEX_NONE) + { + CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*Remap'. + Model->Polys->Element[i].iLink = Remap[Model->Polys->Element[i].iLink]; + } + } + } +// UE_LOG(LogEditorBsp, Log, TEXT("BspMergeCoplanars reduced %i->%i"), OriginalNum, Model->Polys->Element.Num() ); + Mark2.Pop(); +} + +bool UHCsgUtils::polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly) +{ + FBspSurf &Surf = InModel->Surfs[iSurf]; + if( !Surf.Actor || !Surf.Actor->Brush->Polys->Element.IsValidIndex(Surf.iBrushPoly) ) + { + return false; + } + else + { + Poly = Surf.Actor->Brush->Polys->Element[Surf.iBrushPoly]; + return true; + } +} + +void UHCsgUtils::CleanupNodes( UModel *Model, int32 iNode, int32 iParent ) +{ + FBspNode *Node = &Model->Nodes[iNode]; + + // Transactionally empty vertices of tag-for-empty nodes. + Node->NodeFlags &= ~(NF_IsNew | NF_IsFront | NF_IsBack); + + // Recursively clean up front, back, and plane nodes. + if( Node->iFront != INDEX_NONE ) CleanupNodes( Model, Node->iFront, iNode ); + if( Node->iBack != INDEX_NONE ) CleanupNodes( Model, Node->iBack , iNode ); + if( Node->iPlane != INDEX_NONE ) CleanupNodes( Model, Node->iPlane, iNode ); + + // Reload Node since the recusive call aliases it. + Node = &Model->Nodes[iNode]; + + // If this is an empty node with a coplanar, replace it with the coplanar. + if( Node->NumVertices==0 && Node->iPlane!=INDEX_NONE ) + { + FBspNode* PlaneNode = &Model->Nodes[ Node->iPlane ]; + + // Stick our front, back, and parent nodes on the coplanar. + if( (Node->Plane | PlaneNode->Plane) >= 0.0 ) + { + PlaneNode->iFront = Node->iFront; + PlaneNode->iBack = Node->iBack; + } + else + { + PlaneNode->iFront = Node->iBack; + PlaneNode->iBack = Node->iFront; + } + + if( iParent == INDEX_NONE ) + { + // This node is the root. + *Node = *PlaneNode; // Replace root. + PlaneNode->NumVertices = 0; // Mark as unused. + } + else + { + // This is a child node. + FBspNode *ParentNode = &Model->Nodes[iParent]; + + if ( ParentNode->iFront == iNode ) ParentNode->iFront = Node->iPlane; + else if ( ParentNode->iBack == iNode ) ParentNode->iBack = Node->iPlane; + else if ( ParentNode->iPlane == iNode ) ParentNode->iPlane = Node->iPlane; + else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); + } + } + else if( Node->NumVertices == 0 && ( Node->iFront==INDEX_NONE || Node->iBack==INDEX_NONE ) ) + { + // Delete empty nodes with no fronts or backs. + // Replace empty nodes with only fronts. + // Replace empty nodes with only backs. + int32 iReplacementNode; + if ( Node->iFront != INDEX_NONE ) iReplacementNode = Node->iFront; + else if( Node->iBack != INDEX_NONE ) iReplacementNode = Node->iBack; + else iReplacementNode = INDEX_NONE; + + if( iParent == INDEX_NONE ) + { + // Root. + if( iReplacementNode == INDEX_NONE ) + { + Model->Nodes.Empty(); + } + else + { + *Node = Model->Nodes[iReplacementNode]; + } + } + else + { + // Regular node. + FBspNode *ParentNode = &Model->Nodes[iParent]; + + if ( ParentNode->iFront == iNode ) ParentNode->iFront = iReplacementNode; + else if( ParentNode->iBack == iNode ) ParentNode->iBack = iReplacementNode; + else if( ParentNode->iPlane == iNode ) ParentNode->iPlane = iReplacementNode; + else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); + } + } +} + + +void UHCsgUtils::bspCleanup( UModel *Model ) +{ + if( Model->Nodes.Num() > 0 ) + CleanupNodes( Model, 0, INDEX_NONE ); +} diff --git a/Source/HoudiniEngine/Private/HCsgUtils.h b/Source/HoudiniEngine/Private/HCsgUtils.h index cc48c0b48..c02fe3ed6 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.h +++ b/Source/HoudiniEngine/Private/HCsgUtils.h @@ -1,278 +1,278 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "HBSPOps.h" -#include "Engine/Brush.h" -#include "Model.h" - -#include "HCsgUtils.generated.h" - -//USTRUCT() -//struct FHCsgContext -//{ -// GENERATED_BODY() -// -// int32 Errors; -// -// -// UPROPERTY() -// class UModel* TempModel; -// -// UPROPERTY() -// class UModel* ConversionTempModel; -//}; - -// This HCsgUtils is one big fork of the codebase located UnrealEd/Private/EditorBsp.cpp. -// The main purpose was to remove parts of the code that store state in global/static variables as well -// as dependency on editor state (such as retrieving selected brushes). -UCLASS() -class HOUDINIENGINE_API UHCsgUtils : public UObject -{ - GENERATED_BODY() -public: - - UHCsgUtils(); - - /** - * Builds up a model from a set of brushes. Used by RebuildLevel. - * - * @param Model The model to be rebuilt. - * @param bSelectedBrushesOnly Use all brushes in the current level or just the selected ones?. - * @param bTreatMovableBrushesAsStatic Treat moveable brushes as static?. - */ - static void RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic); - - /** - * Converts passed in brushes into a single static mesh actor. - * Note: This replaces all the brushes with a single actor. This actor will not be attached to anything unless a single brush was converted. - * - * @param InStaticMeshPackageName The name to save the brushes to. - * @param InBrushesToConvert A list of brushes being converted. - * - * @return Returns the newly created actor with the newly created static mesh. - */ - static UModel* BuildModelFromBrushes(TArray& Brushes); - - /** - * Forked version of UEditorEngine::bspBrushCSG() from UnrealEd/Private/EditorBsp.cpp. - * - * Apply the appropriate CSG operation required in order to compose the brush actor onto the given model. - * - * @param Actor The brush actor to apply. - * @param Model The model to apply the CSG operation to; typically the world's model. - * @param PolyFlags PolyFlags to set on brush's polys. - * @param BrushType The type of brush. - * @param CSGOper The CSG operation to perform. - * @param bBuildBounds If true, updates bounding volumes on Model for CSG_Add or CSG_Subtract operations. - * @param bMergePolys If true, coplanar polygons are merged for CSG_Intersect or CSG_Deintersect operations. - * @param bReplaceNULLMaterialRefs If true, replace NULL material references with a reference to the GB-selected material. - * @param bShowProgressBar If true, display progress bar for complex brushes - * @return 0 if nothing happened, 1 if the operation was error-free, or 1+N if N CSG errors occurred. - */ - int ComposeBrushCSG( - ABrush* Actor, - UModel* Model, - uint32 PolyFlags, - EBrushType BrushType, - ECsgOper CSGOper, - bool bBuildBounds, - bool bMergePolys, - bool bReplaceNULLMaterialRefs, - bool bShowProgressBar, /*=true*/ - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - -protected: - // - // Status of filtered polygons: - // - enum EPolyNodeFilter - { - F_OUTSIDE = 0, // Leaf is an exterior leaf (visible to viewers). - F_INSIDE = 1, // Leaf is an interior leaf (non-visible, hidden behind backface). - F_COPLANAR_OUTSIDE = 2, // Poly is coplanar and in the exterior (visible to viewers). - F_COPLANAR_INSIDE = 3, // Poly is coplanar and inside (invisible to viewers). - F_COSPATIAL_FACING_IN = 4, // Poly is coplanar, cospatial, and facing in. - F_COSPATIAL_FACING_OUT = 5, // Poly is coplanar, cospatial, and facing out. - }; - - - // - // Information used by FilterEdPoly. - // - class FCoplanarInfo - { - public: - int32 iOriginalNode; - int32 iBackNode; - int BackNodeOutside; - int FrontLeafOutside; - int ProcessingBack; - }; - - // - // Generic filter function called by BspFilterEdPolys. A and B are pointers - // to any integers that your specific routine requires (or NULL if not needed). - // - typedef void (UHCsgUtils::*BspFilterFunc) - ( - UModel* Model, - int32 iNode, - FPoly* EdPoly, - EPolyNodeFilter Leaf, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // State shared between bspBrushCSG and AddWorldToBrushFunc. These are very - // tightly tied into the function AddWorldToBrush, not for general use. - // - int32 GDiscarded; // Number of polys discarded and not added. - int32 GNode; // Node AddBrushToWorld is adding to. - int32 GLastCoplanar; // Last coplanar beneath GNode at start of AddWorldToBrush. - int32 GNumNodes; // Number of Bsp nodes at start of AddWorldToBrush. - - UPROPERTY() - UModel* GModel; // Level map Model we're adding to. - - UPROPERTY() - class UModel* TempModel; - - //// Globals removed from FBspPointsGrid - //UPROPERTY() - //UHBspPointsGrid* GBspPoints; - - //UPROPERTY() - //UHBspPointsGrid* GBspVectors; - - /*struct BspFilterOp { - void Apply(UHCsgUtils* Obj, UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) {}; - };*/ - - // - // Handle a piece of a polygon that was filtered to a leaf. - // - void FilterLeaf( - BspFilterFunc FilterFunc, - UModel* Model, - int32 iNode, - FPoly* EdPoly, - FCoplanarInfo CoplanarInfo, - int32 LeafOutside, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // Function to filter an EdPoly through the Bsp, calling a callback - // function for all chunks that fall into leaves. - // - void FilterEdPoly - ( - BspFilterFunc FilterFunc, - UModel *Model, - int32 iNode, - FPoly *EdPoly, - FCoplanarInfo CoplanarInfo, - int32 Outside, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // Regular entry into FilterEdPoly (so higher-level callers don't have to - // deal with unnecessary info). Filters starting at root. - // - void BspFilterFPoly - ( - BspFilterFunc FilterFunc, - UModel *Model, - FPoly *EdPoly, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - - int bspNodeToFPoly - ( - UModel* Model, - int32 iNode, - FPoly* EdPoly - ); - - - //---------------------------------------------------------------------------- - // World Filtering - //---------------------------------------------------------------------------- - - // - // Filter all relevant world polys through the brush. - // - void FilterWorldThroughBrush - ( - UModel* Model, - UModel* Brush, - EBrushType BrushType, - ECsgOper CSGOper, - int32 iNode, - FSphere* BrushSphere, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - //---------------------------------------------------------------------------- - // CSG leaf filter callbacks / operations. - // --------------------------------------------------------------------------- - void AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - - //---------------------------------------------------------------------------- - // Forked various functions located in: EditorBsp.cpp, EditorCsg.cpp - //---------------------------------------------------------------------------- - static int TryToMerge( FPoly *Poly1, FPoly *Poly2 ); - static void MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ); - void bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ); - bool polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly); - static void CleanupNodes( UModel *Model, int32 iNode, int32 iParent ); - void bspCleanup( UModel *Model ); - -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HBSPOps.h" +#include "Engine/Brush.h" +#include "Model.h" + +#include "HCsgUtils.generated.h" + +//USTRUCT() +//struct FHCsgContext +//{ +// GENERATED_BODY() +// +// int32 Errors; +// +// +// UPROPERTY() +// class UModel* TempModel; +// +// UPROPERTY() +// class UModel* ConversionTempModel; +//}; + +// This HCsgUtils is one big fork of the codebase located UnrealEd/Private/EditorBsp.cpp. +// The main purpose was to remove parts of the code that store state in global/static variables as well +// as dependency on editor state (such as retrieving selected brushes). +UCLASS() +class HOUDINIENGINE_API UHCsgUtils : public UObject +{ + GENERATED_BODY() +public: + + UHCsgUtils(); + + /** + * Builds up a model from a set of brushes. Used by RebuildLevel. + * + * @param Model The model to be rebuilt. + * @param bSelectedBrushesOnly Use all brushes in the current level or just the selected ones?. + * @param bTreatMovableBrushesAsStatic Treat moveable brushes as static?. + */ + static void RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic); + + /** + * Converts passed in brushes into a single static mesh actor. + * Note: This replaces all the brushes with a single actor. This actor will not be attached to anything unless a single brush was converted. + * + * @param InStaticMeshPackageName The name to save the brushes to. + * @param InBrushesToConvert A list of brushes being converted. + * + * @return Returns the newly created actor with the newly created static mesh. + */ + static UModel* BuildModelFromBrushes(TArray& Brushes); + + /** + * Forked version of UEditorEngine::bspBrushCSG() from UnrealEd/Private/EditorBsp.cpp. + * + * Apply the appropriate CSG operation required in order to compose the brush actor onto the given model. + * + * @param Actor The brush actor to apply. + * @param Model The model to apply the CSG operation to; typically the world's model. + * @param PolyFlags PolyFlags to set on brush's polys. + * @param BrushType The type of brush. + * @param CSGOper The CSG operation to perform. + * @param bBuildBounds If true, updates bounding volumes on Model for CSG_Add or CSG_Subtract operations. + * @param bMergePolys If true, coplanar polygons are merged for CSG_Intersect or CSG_Deintersect operations. + * @param bReplaceNULLMaterialRefs If true, replace NULL material references with a reference to the GB-selected material. + * @param bShowProgressBar If true, display progress bar for complex brushes + * @return 0 if nothing happened, 1 if the operation was error-free, or 1+N if N CSG errors occurred. + */ + int ComposeBrushCSG( + ABrush* Actor, + UModel* Model, + uint32 PolyFlags, + EBrushType BrushType, + ECsgOper CSGOper, + bool bBuildBounds, + bool bMergePolys, + bool bReplaceNULLMaterialRefs, + bool bShowProgressBar, /*=true*/ + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + +protected: + // + // Status of filtered polygons: + // + enum EPolyNodeFilter + { + F_OUTSIDE = 0, // Leaf is an exterior leaf (visible to viewers). + F_INSIDE = 1, // Leaf is an interior leaf (non-visible, hidden behind backface). + F_COPLANAR_OUTSIDE = 2, // Poly is coplanar and in the exterior (visible to viewers). + F_COPLANAR_INSIDE = 3, // Poly is coplanar and inside (invisible to viewers). + F_COSPATIAL_FACING_IN = 4, // Poly is coplanar, cospatial, and facing in. + F_COSPATIAL_FACING_OUT = 5, // Poly is coplanar, cospatial, and facing out. + }; + + + // + // Information used by FilterEdPoly. + // + class FCoplanarInfo + { + public: + int32 iOriginalNode; + int32 iBackNode; + int BackNodeOutside; + int FrontLeafOutside; + int ProcessingBack; + }; + + // + // Generic filter function called by BspFilterEdPolys. A and B are pointers + // to any integers that your specific routine requires (or NULL if not needed). + // + typedef void (UHCsgUtils::*BspFilterFunc) + ( + UModel* Model, + int32 iNode, + FPoly* EdPoly, + EPolyNodeFilter Leaf, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // State shared between bspBrushCSG and AddWorldToBrushFunc. These are very + // tightly tied into the function AddWorldToBrush, not for general use. + // + int32 GDiscarded; // Number of polys discarded and not added. + int32 GNode; // Node AddBrushToWorld is adding to. + int32 GLastCoplanar; // Last coplanar beneath GNode at start of AddWorldToBrush. + int32 GNumNodes; // Number of Bsp nodes at start of AddWorldToBrush. + + UPROPERTY() + UModel* GModel; // Level map Model we're adding to. + + UPROPERTY() + class UModel* TempModel; + + //// Globals removed from FBspPointsGrid + //UPROPERTY() + //UHBspPointsGrid* GBspPoints; + + //UPROPERTY() + //UHBspPointsGrid* GBspVectors; + + /*struct BspFilterOp { + void Apply(UHCsgUtils* Obj, UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) {}; + };*/ + + // + // Handle a piece of a polygon that was filtered to a leaf. + // + void FilterLeaf( + BspFilterFunc FilterFunc, + UModel* Model, + int32 iNode, + FPoly* EdPoly, + FCoplanarInfo CoplanarInfo, + int32 LeafOutside, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // Function to filter an EdPoly through the Bsp, calling a callback + // function for all chunks that fall into leaves. + // + void FilterEdPoly + ( + BspFilterFunc FilterFunc, + UModel *Model, + int32 iNode, + FPoly *EdPoly, + FCoplanarInfo CoplanarInfo, + int32 Outside, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // Regular entry into FilterEdPoly (so higher-level callers don't have to + // deal with unnecessary info). Filters starting at root. + // + void BspFilterFPoly + ( + BspFilterFunc FilterFunc, + UModel *Model, + FPoly *EdPoly, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + + int bspNodeToFPoly + ( + UModel* Model, + int32 iNode, + FPoly* EdPoly + ); + + + //---------------------------------------------------------------------------- + // World Filtering + //---------------------------------------------------------------------------- + + // + // Filter all relevant world polys through the brush. + // + void FilterWorldThroughBrush + ( + UModel* Model, + UModel* Brush, + EBrushType BrushType, + ECsgOper CSGOper, + int32 iNode, + FSphere* BrushSphere, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + //---------------------------------------------------------------------------- + // CSG leaf filter callbacks / operations. + // --------------------------------------------------------------------------- + void AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + + //---------------------------------------------------------------------------- + // Forked various functions located in: EditorBsp.cpp, EditorCsg.cpp + //---------------------------------------------------------------------------- + static int TryToMerge( FPoly *Poly1, FPoly *Poly2 ); + static void MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ); + void bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ); + bool polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly); + static void CleanupNodes( UModel *Model, int32 iNode, int32 iParent ); + void bspCleanup( UModel *Model ); + +}; + diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.cpp b/Source/HoudiniEngine/Private/HoudiniEngine.cpp index ec8be793e..c0ee69d3e 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngine.cpp @@ -1,1283 +1,1283 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngine.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "HoudiniApi.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineScheduler.h" -#include "HoudiniEngineManager.h" -#include "HoudiniEngineTask.h" -#include "HoudiniEngineTaskInfo.h" -#include "HoudiniAssetComponent.h" -#include "HAPI/HAPI_Version.h" - -#include "Modules/ModuleManager.h" -#include "Misc/ScopeLock.h" -#include "Engine/StaticMesh.h" -#include "Materials/Material.h" -#include "ISettingsModule.h" -#include "HAL/PlatformFilemanager.h" -#include "Async/Async.h" -#include "Logging/LogMacros.h" - -#if WITH_EDITOR - #include "Widgets/Notifications/SNotificationList.h" - #include "Framework/Notifications/NotificationManager.h" -#endif - -#define LOCTEXT_NAMESPACE "HoudiniEngine" - -IMPLEMENT_MODULE(FHoudiniEngine, HoudiniEngine) -DEFINE_LOG_CATEGORY( LogHoudiniEngine ); - -FHoudiniEngine * -FHoudiniEngine::HoudiniEngineInstance = nullptr; - -FHoudiniEngine::FHoudiniEngine() - : LicenseType(HAPI_LICENSE_NONE) - , HoudiniEngineSchedulerThread(nullptr) - , HoudiniEngineScheduler(nullptr) - , HoudiniEngineManagerThread(nullptr) - , HoudiniEngineManager(nullptr) - //, bHAPIVersionMismatch(false) - , bEnableCookingGlobal(true) - , UIRefreshCountWhenPauseCooking(0) - , bFirstSessionCreated(false) - , bEnableSessionSync(false) - , bCookUsingHoudiniTime(true) - , bSyncViewport(false) - , bSyncHoudiniViewport(true) - , bSyncUnrealViewport(false) - , HoudiniLogoStaticMesh(nullptr) - , HoudiniDefaultMaterial(nullptr) - , HoudiniTemplateMaterial(nullptr) - , HoudiniLogoBrush(nullptr) - , HoudiniDefaultReferenceMesh(nullptr) - , HoudiniDefaultReferenceMeshMaterial(nullptr) -{ - Session.type = HAPI_SESSION_MAX; - Session.id = -1; - - SetSessionStatus(EHoudiniSessionStatus::Invalid); - -#if WITH_EDITOR - HapiNotificationStarted = 0.0; - TimeSinceLastPersistentNotification = 0.0; -#endif -} - -FHoudiniEngine& -FHoudiniEngine::Get() -{ - check(FHoudiniEngine::HoudiniEngineInstance); - return *FHoudiniEngine::HoudiniEngineInstance; -} - -bool -FHoudiniEngine::IsInitialized() -{ - return FHoudiniEngine::HoudiniEngineInstance != nullptr && FHoudiniEngineUtils::IsInitialized(); -} - -void -FHoudiniEngine::StartupModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine module...")); - -#if WITH_EDITOR - // Register settings. - if (ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings")) - { - SettingsModule->RegisterSettings( - "Project", "Plugins", "HoudiniEngine", - LOCTEXT("RuntimeSettingsName", "Houdini Engine"), - LOCTEXT("RuntimeSettingsDescription", "Configure the HoudiniEngine plugin"), - GetMutableDefault< UHoudiniRuntimeSettings >()); - } -#endif - - // Before starting the module, we need to locate and load HAPI library. - { - void * HAPILibraryHandle = FHoudiniEngineUtils::LoadLibHAPI(LibHAPILocation); - if ( HAPILibraryHandle ) - { - FHoudiniApi::InitializeHAPI( HAPILibraryHandle ); - } - else - { - // Get platform specific name of libHAPI. - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - HOUDINI_LOG_MESSAGE(TEXT("Failed locating or loading %s"), *LibHAPIName); - } - } - - // Create static mesh Houdini logo. - HoudiniLogoStaticMesh = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_LOGO, nullptr, LOAD_None, nullptr); - if (HoudiniLogoStaticMesh.IsValid()) - HoudiniLogoStaticMesh->AddToRoot(); - - // Create default material. - HoudiniDefaultMaterial = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultMaterial.IsValid()) - HoudiniDefaultMaterial->AddToRoot(); - - HoudiniTemplateMaterial = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniTemplateMaterial.IsValid()) - HoudiniTemplateMaterial->AddToRoot(); - - // Houdini Logo Brush - FString Icon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icon128.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) - { - const FName BrushName(*Icon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Logo Brush - FString HEIcon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_logo_128.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) - { - const FName BrushName(*HEIcon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Create Houdini default reference mesh - HoudiniDefaultReferenceMesh = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultReferenceMesh.IsValid()) - HoudiniDefaultReferenceMesh->AddToRoot(); - - // Create Houdini default reference mesh material - HoudiniDefaultReferenceMeshMaterial = LoadObject - (nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultReferenceMeshMaterial.IsValid()) - HoudiniDefaultReferenceMeshMaterial->AddToRoot(); - - // We do not automatically try to start a session when starting up the module now. - bFirstSessionCreated = false; - - // Create HAPI scheduler and processing thread. - HoudiniEngineScheduler = new FHoudiniEngineScheduler(); - HoudiniEngineSchedulerThread = FRunnableThread::Create( - HoudiniEngineScheduler, TEXT("HoudiniSchedulerThread"), 0, TPri_Normal); - - // Create Houdini Asset Manager - HoudiniEngineManager = new FHoudiniEngineManager(); - - // Set the session status to Not Started - SetSessionStatus(EHoudiniSessionStatus::NotStarted); - - // Set the default value for pausing houdini engine cooking - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bEnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; - - // Check if a null session is set - bool bNoneSession = (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None); - if (bNoneSession) - SetSessionStatus(EHoudiniSessionStatus::None); - - // Initialize the singleton with this instance - FHoudiniEngine::HoudiniEngineInstance = this; - - // See if we need to start the manager ticking if needed - // Dont tick if we failed to load HAPI, if cooking is disabled or if we're using a null session - if (FHoudiniApi::IsHAPIInitialized()) - { - if (bEnableCookingGlobal && !bNoneSession) - { - PostEngineInitCallback = FCoreDelegates::OnPostEngineInit.AddLambda([]() - { - FHoudiniEngine& HEngine = FHoudiniEngine::Get(); - HEngine.UnregisterPostEngineInitCallback(); - FHoudiniEngineManager* const Manager = HEngine.GetHoudiniEngineManager(); - if (Manager) - Manager->StartHoudiniTicking(); - }); - } - } -} - -void -FHoudiniEngine::ShutdownModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine module.")); - - // We no longer need the Houdini logo static mesh. - if (HoudiniLogoStaticMesh.IsValid()) - { - HoudiniLogoStaticMesh->RemoveFromRoot(); - HoudiniLogoStaticMesh = nullptr; - } - - // We no longer need the Houdini default material. - if (HoudiniDefaultMaterial.IsValid()) - { - HoudiniDefaultMaterial->RemoveFromRoot(); - HoudiniDefaultMaterial = nullptr; - } - - // We no longer need the Houdini default material. - if (HoudiniTemplateMaterial.IsValid()) - { - HoudiniTemplateMaterial->RemoveFromRoot(); - HoudiniTemplateMaterial = nullptr; - } - - // We no longer need the Houdini default reference mesh - if (HoudiniDefaultReferenceMesh.IsValid()) - { - HoudiniDefaultReferenceMesh->RemoveFromRoot(); - HoudiniDefaultReferenceMesh = nullptr; - } - - // We no longer need the Houdini default reference mesh material - if (HoudiniDefaultReferenceMeshMaterial.IsValid()) - { - HoudiniDefaultReferenceMeshMaterial->RemoveFromRoot(); - HoudiniDefaultReferenceMeshMaterial = nullptr; - } - /* - // We no longer need Houdini digital asset used for loading bgeo files. - if (HoudiniBgeoAsset.IsValid()) - { - HoudiniBgeoAsset->RemoveFromRoot(); - HoudiniBgeoAsset = nullptr; - } - */ - -#if WITH_EDITOR - // Unregister settings. - ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings"); - if (SettingsModule) - SettingsModule->UnregisterSettings("Project", "Plugins", "HoudiniEngine"); -#endif - - // Do scheduler and thread clean up. - if (HoudiniEngineScheduler) - HoudiniEngineScheduler->Stop(); - - if (HoudiniEngineSchedulerThread) - { - //HoudiniEngineSchedulerThread->Kill( true ); - HoudiniEngineSchedulerThread->WaitForCompletion(); - - delete HoudiniEngineSchedulerThread; - HoudiniEngineSchedulerThread = nullptr; - } - - if ( HoudiniEngineScheduler ) - { - delete HoudiniEngineScheduler; - HoudiniEngineScheduler = nullptr; - } - - // Do manager clean up. - if (HoudiniEngineManager) - HoudiniEngineManager->StopHoudiniTicking(); - - if (HoudiniEngineManager) - { - delete HoudiniEngineManager; - HoudiniEngineManager = nullptr; - } - - // Perform HAPI finalization. - if ( FHoudiniApi::IsHAPIInitialized() ) - { - FHoudiniApi::Cleanup(GetSession()); - FHoudiniApi::CloseSession(GetSession()); - SessionStatus = EHoudiniSessionStatus::Invalid; - } - - FHoudiniApi::FinalizeHAPI(); - - FHoudiniEngine::HoudiniEngineInstance = nullptr; -} - -void -FHoudiniEngine::AddTask(const FHoudiniEngineTask & InTask) -{ - if ( HoudiniEngineScheduler ) - HoudiniEngineScheduler->AddTask(InTask); - - FScopeLock ScopeLock(&CriticalSection); - FHoudiniEngineTaskInfo TaskInfo; - TaskInfo.TaskType = InTask.TaskType; - TaskInfo.TaskState = EHoudiniEngineTaskState::Working; - - TaskInfos.Add(InTask.HapiGUID, TaskInfo); -} - -void -FHoudiniEngine::AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo) -{ - FScopeLock ScopeLock(&CriticalSection); - TaskInfos.Add(InHapiGUID, InTaskInfo); -} - -void -FHoudiniEngine::RemoveTaskInfo(const FGuid& InHapiGUID) -{ - FScopeLock ScopeLock(&CriticalSection); - TaskInfos.Remove(InHapiGUID); -} - -bool -FHoudiniEngine::RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo) -{ - FScopeLock ScopeLock(&CriticalSection); - - if (TaskInfos.Contains(InHapiGUID)) - { - OutTaskInfo = TaskInfos[InHapiGUID]; - return true; - } - - return false; -} - -/* -void -FHoudiniEngine::AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - if (HoudiniEngineManager) - HoudiniEngineManager->AddComponent(HAC); -} -*/ - -const FString & -FHoudiniEngine::GetLibHAPILocation() const -{ - return LibHAPILocation; -} - -const HAPI_Session * -FHoudiniEngine::GetSession() const -{ - return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; -} - -const EHoudiniSessionStatus& -FHoudiniEngine::GetSessionStatus() const -{ - return SessionStatus; -} - -void -FHoudiniEngine::SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus) -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None) - { - // Check for none sessions first - SessionStatus = EHoudiniSessionStatus::None; - return; - } - - if (!bFirstSessionCreated) - { - // Don't change the status unless we've attempted to start the session once - SessionStatus = EHoudiniSessionStatus::NotStarted; - return; - } - - switch (InSessionStatus) - { - case EHoudiniSessionStatus::NotStarted: - case EHoudiniSessionStatus::NoLicense: - case EHoudiniSessionStatus::Lost: - case EHoudiniSessionStatus::None: - case EHoudiniSessionStatus::Invalid: - case EHoudiniSessionStatus::Connected: - { - SessionStatus = InSessionStatus; - } - break; - - case EHoudiniSessionStatus::Stopped: - { - // Only set to stop status if the session was valid - if (SessionStatus == EHoudiniSessionStatus::Connected) - SessionStatus = EHoudiniSessionStatus::Stopped; - } - break; - - case EHoudiniSessionStatus::Failed: - { - // Preserve No License / Lost status - if (SessionStatus != EHoudiniSessionStatus::NoLicense && SessionStatus != EHoudiniSessionStatus::Lost) - SessionStatus = EHoudiniSessionStatus::Failed; - } - break; - } -} - -HAPI_CookOptions -FHoudiniEngine::GetDefaultCookOptions() -{ - // Default CookOptions - HAPI_CookOptions CookOptions; - FHoudiniApi::CookOptions_Init(&CookOptions); - - CookOptions.curveRefineLOD = 8.0f; - CookOptions.clearErrorsAndWarnings = false; - CookOptions.maxVerticesPerPrimitive = 3; - CookOptions.splitGeosByGroup = false; - CookOptions.splitGeosByAttribute = false; - CookOptions.splitAttrSH = 0; - CookOptions.refineCurveToLinear = true; - CookOptions.handleBoxPartTypes = false; - CookOptions.handleSpherePartTypes = false; - CookOptions.splitPointsByVertexAttributes = false; - CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; - CookOptions.cookTemplatedGeos = true; - - return CookOptions; -} - -bool -FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, - const bool& StartAutomaticServer, - const float& AutomaticServerTimeout, - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const int32& ServerPort, - const FString& ServerHost) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - // Only start a new Session if we dont already have a valid one - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) - return true; - - // Set the HAPI_CLIENT_NAME environment variable to "unreal" - // We need to do this before starting HARS. - FPlatformMisc::SetEnvironmentVar(TEXT("HAPI_CLIENT_NAME"), TEXT("unreal")); - - HAPI_Result SessionResult = HAPI_RESULT_FAILURE; - - HAPI_ThriftServerOptions ServerOptions; - FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); - ServerOptions.autoClose = true; - ServerOptions.timeoutMs = AutomaticServerTimeout; - - // Unless we automatically start the server, - // consider we're in SessionSync mode - bEnableSessionSync = true; - - auto UpdatePathForServer = [&] - { - // Modify our PATH so that HARC will find HARS.exe - const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); - - FString OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); - - FString ModifiedPath = -#if PLATFORM_MAC - // On Mac our binaries are split between two folders - LibHAPILocation + TEXT("/../Resources/bin") + PathDelimiter + -#endif - LibHAPILocation + PathDelimiter + OrigPathVar; - - FPlatformMisc::SetEnvironmentVar(TEXT("PATH"), *ModifiedPath); - }; - - switch ( SessionType ) - { - case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: - { - // Try to connect to an existing socket session first - SessionResult = FHoudiniApi::CreateThriftSocketSession( - SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort ); - - // Start a session and try to connect to it if we failed - if ( StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS ) - { - UpdatePathForServer(); - FHoudiniApi::StartThriftSocketServer( - &ServerOptions, ServerPort, nullptr); - - // We've started the server manually, disable session sync - bEnableSessionSync = false; - - SessionResult = FHoudiniApi::CreateThriftSocketSession( - SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort); - } - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: - { - // Try to connect to an existing pipe session first - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - SessionPtr, TCHAR_TO_UTF8(*ServerPipeName) ); - - // Start a session and try to connect to it if we failed - if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) - { - UpdatePathForServer(); - FHoudiniApi::StartThriftNamedPipeServer( - &ServerOptions, TCHAR_TO_UTF8(*ServerPipeName), nullptr); - - // We've started the server manually, disable session sync - bEnableSessionSync = false; - - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - SessionPtr, TCHAR_TO_UTF8(*ServerPipeName)); - } - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_None: - { - HOUDINI_LOG_MESSAGE(TEXT("Session type set to None, Cooking is disabled.")); - // Disable session sync - bEnableSessionSync = false; - break; - } - - // As of Unreal 4.19, InProcess sessions are not supported anymore - case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: - default: - HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine session type")); - // Disable session sync - bEnableSessionSync = false; - break; - } - - if(SessionType != EHoudiniRuntimeSettingsSessionType::HRSST_None) - FHoudiniEngine::Get().SetFirstSessionCreated(true); - - if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) - { - // Disable session sync as well? - bEnableSessionSync = false; - return false; - } - - // Update this session's license type - HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( - SessionPtr, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); - - return true; -} - -bool -FHoudiniEngine::SessionSyncConnect( - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const FString& ServerHost, - const int32& ServerPort) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - // Only start a new Session if we dont already have a valid one - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(&Session)) - return true; - - // Consider the session failed as long as we dont connect - SetSessionStatus(EHoudiniSessionStatus::Failed); - - HAPI_Result SessionResult = HAPI_RESULT_FAILURE; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - - HAPI_ThriftServerOptions ServerOptions; - FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); - ServerOptions.autoClose = true; - ServerOptions.timeoutMs = HoudiniRuntimeSettings->AutomaticServerTimeout; - - switch (SessionType) - { - case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: - { - // Try to connect to an existing socket session first - SessionResult = FHoudiniApi::CreateThriftSocketSession( - &Session, TCHAR_TO_UTF8(*ServerHost), ServerPort); - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: - { - // Try to connect to an existing pipe session first - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - &Session, TCHAR_TO_UTF8(*ServerPipeName)); - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_None: - case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: - default: - HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine Session Sync Type!!")); - bEnableSessionSync = false; - break; - } - - if (SessionResult != HAPI_RESULT_SUCCESS) - return false; - - // Enable session sync - bEnableSessionSync = true; - SetSessionStatus(EHoudiniSessionStatus::Connected); - - // Update this session's license type - HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( - &Session, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); - - // Update the default viewport sync settings - bSyncViewport = HoudiniRuntimeSettings->bSyncViewport; - bSyncHoudiniViewport = HoudiniRuntimeSettings->bSyncHoudiniViewport; - bSyncUnrealViewport = HoudiniRuntimeSettings->bSyncUnrealViewport; - - return true; -} - -bool -FHoudiniEngine::InitializeHAPISession() -{ - // The HAPI stubs needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The Houdini API stubs have not been properly initialized.")); - return false; - } - - // We need a Valid Session - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(GetSession())) - { - HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The session is invalid.")); - return false; - } - - // Now, initialize HAPI with the new session - // We need to make sure HAPI version is correct. - int32 RunningEngineMajor = 0; - int32 RunningEngineMinor = 0; - int32 RunningEngineApi = 0; - - // 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); - - // Compare defined and running versions. - if (RunningEngineMajor != HAPI_VERSION_HOUDINI_ENGINE_MAJOR - || RunningEngineMinor != HAPI_VERSION_HOUDINI_ENGINE_MINOR) - { - // Major or minor HAPI version differs, stop here - HOUDINI_LOG_ERROR( - TEXT("Starting up the Houdini Engine module failed: built and running versions do not match.")); - HOUDINI_LOG_ERROR( - TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), - HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, - RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - - // Display an error message - - // - return false; - - } - else if (RunningEngineApi != HAPI_VERSION_HOUDINI_ENGINE_API) - { - // Major/minor HAPIversions match, but only the API version differs, - // Allow the user to continue but warn him of possible instabilities - HOUDINI_LOG_WARNING( - TEXT("Starting up the Houdini Engine module: built and running versions do not match.")); - HOUDINI_LOG_WARNING( - TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), - HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, - RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - HOUDINI_LOG_WARNING( - TEXT("This could cause instabilities and crashes when using the Houdini Engine plugin")); - } - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Default CookOptions - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - - bool bUseCookingThread = true; - HAPI_Result Result = FHoudiniApi::Initialize( - &Session, - &CookOptions, - bUseCookingThread, - HoudiniRuntimeSettings->CookingThreadStackSize, - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->HoudiniEnvironmentFiles), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->OtlSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->DsoSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->ImageDsoSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->AudioDsoSearchPath)); - - if (Result == HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module.")); - } - else if (Result == HAPI_RESULT_ALREADY_INITIALIZED) - { - // Reused session? just notify the user - HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module - HAPI was already initialzed.")); - } - else - { - HOUDINI_LOG_ERROR( - TEXT("Houdini Engine API initialization failed: %s"), - *FHoudiniEngineUtils::GetErrorDescription(Result)); - - return false; - } - - // Let HAPI know we are running inside UE4 - FHoudiniApi::SetServerEnvString(&Session, HAPI_ENV_CLIENT_NAME, HAPI_UNREAL_CLIENT_NAME); - - if (bEnableSessionSync) - { - // Set the session sync infos if needed - UploadSessionSyncInfoToHoudini(); - - // Indicate that Session Sync is enabled - FString Notification = TEXT("Houdini Engine Session Sync enabled."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Session Sync enabled.")); - } - - return true; -} - - -void -FHoudiniEngine::OnSessionLost() -{ - // Mark the session as invalid - Session.id = -1; - Session.type = HAPI_SESSION_MAX; - SetSessionStatus(EHoudiniSessionStatus::Lost); - - bEnableSessionSync = false; - HoudiniEngineManager->StopHoudiniTicking(); - - // This indicates that we likely have lost the session due to a crash in HARS/Houdini - FString Notification = TEXT("Houdini Engine Session lost!"); - FHoudiniEngineUtils::CreateSlateNotification(Notification, 2.0, 4.0); - - HOUDINI_LOG_ERROR(TEXT("Houdini Engine Session lost! This could be caused by a crash in HARS.")); -} - -bool -FHoudiniEngine::StopSession() -{ - HAPI_Session* SessionPtr = &Session; - return StopSession(SessionPtr); -} - -bool -FHoudiniEngine::StopSession(HAPI_Session*& SessionPtr) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) - { - // SessionPtr is valid, clean up and close the session - FHoudiniApi::Cleanup(SessionPtr); - FHoudiniApi::CloseSession(SessionPtr); - } - - Session.id = -1; - Session.type = HAPI_SESSION_MAX; - SetSessionStatus(EHoudiniSessionStatus::Stopped); - bEnableSessionSync = false; - - HoudiniEngineManager->StopHoudiniTicking(); - - return true; -} - -bool -FHoudiniEngine::RestartSession() -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Starting the Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - if (!StopSession(SessionPtr)) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); - } - else - { - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - HoudiniRuntimeSettings->bStartAutomaticServer, - HoudiniRuntimeSettings->AutomaticServerTimeout, - HoudiniRuntimeSettings->SessionType, - HoudiniRuntimeSettings->ServerPipeName, - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to start the new Session")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - bSuccess = true; - SetSessionStatus(EHoudiniSessionStatus::Connected); - } - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -bool -FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName) -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Create the Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - true, - HoudiniRuntimeSettings->AutomaticServerTimeout, - SessionType, - OverrideServerPipeName == NAME_None ? HoudiniRuntimeSettings->ServerPipeName : OverrideServerPipeName.ToString(), - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine Session")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine session - Failed to initialize HAPI")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - bSuccess = true; - SetSessionStatus(EHoudiniSessionStatus::Connected); - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -bool -FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType) -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Connecting to a Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - false, - HoudiniRuntimeSettings->AutomaticServerTimeout, - SessionType, - HoudiniRuntimeSettings->ServerPipeName, - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine Session")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine session - Failed to initialize HAPI")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - bSuccess = true; - SetSessionStatus(EHoudiniSessionStatus::Connected); - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -void -FHoudiniEngine::StartTicking() -{ - // Finish the notification and display the results - FString StatusText = TEXT("Houdini Engine session connected."); - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - - HoudiniEngineManager->StartHoudiniTicking(); -} - -void -FHoudiniEngine::StopTicking() -{ - // Finish the notification and display the results - FString StatusText = TEXT("Failed to start the Houdini Engine session..."); - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - - HoudiniEngineManager->StopHoudiniTicking(); - - HAPI_Session* SessionPtr = &Session; - StopSession(SessionPtr); -} - -bool -FHoudiniEngine::IsCookingEnabled() const -{ - return bEnableCookingGlobal; -} - -void -FHoudiniEngine::SetCookingEnabled(const bool& bInEnableCooking) -{ - bEnableCookingGlobal = bInEnableCooking; -} - -bool -FHoudiniEngine::GetFirstSessionCreated() const -{ - return bFirstSessionCreated; -} - -bool -FHoudiniEngine::CreateTaskSlateNotification( - const FText& InText, const bool& bForceNow, const float& NotificationExpire, const float& NotificationFadeOut) -{ -#if WITH_EDITOR - static double NotificationUpdateFrequency = 2.0f; - - // Check whether we want to display Slate cooking and instantiation notifications. - bool bDisplaySlateCookingNotifications = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (!bDisplaySlateCookingNotifications) - return false; - - if (!bForceNow) - { - if ((FPlatformTime::Seconds() - HapiNotificationStarted) < NotificationUpdateFrequency) - return false; - } - - if (!NotificationPtr.IsValid()) - { - FNotificationInfo Info(InText); - Info.bFireAndForget = false; - Info.FadeOutDuration = NotificationFadeOut; - Info.ExpireDuration = NotificationExpire; - TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); - if (HoudiniBrush.IsValid()) - Info.Image = HoudiniBrush.Get(); - - /* - if (!IsPIEActive()) - */ - - NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); - //FSlateNotificationManager::Get().Tick(); - } -#endif - - return true; -} - -bool -FHoudiniEngine::UpdateTaskSlateNotification(const FText& InText) -{ -#if WITH_EDITOR - // task is till running - // Just update the slate notification - TSharedPtr NotificationItem = NotificationPtr.Pin(); - if (NotificationItem.IsValid()) - NotificationItem->SetText(InText); - - //FSlateNotificationManager::Get().Tick(); -#endif - - return true; -} - -bool -FHoudiniEngine::FinishTaskSlateNotification(const FText& InText) -{ -#if WITH_EDITOR - if (NotificationPtr.IsValid()) - { - TSharedPtr NotificationItem = NotificationPtr.Pin(); - if (NotificationItem.IsValid()) - { - NotificationItem->SetText(InText); - NotificationItem->ExpireAndFadeout(); - - NotificationPtr.Reset(); - } - } -#endif - - return true; -} - -bool FHoudiniEngine::UpdateCookingNotification(const FText& InText, const bool bExpireAndFade) -{ -#if WITH_EDITOR - // Check whether we want to display Slate cooking and instantiation notifications. - bool bDisplaySlateCookingNotifications = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (!bDisplaySlateCookingNotifications) - return false; - - UpdatePersistentNotification(InText, bExpireAndFade); - -#endif - return true; -} - -bool -FHoudiniEngine::UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade) -{ -#if WITH_EDITOR - TimeSinceLastPersistentNotification = 0.0; - - if (!PersistentNotificationPtr.IsValid()) - { - FNotificationInfo Info(InText); - Info.bFireAndForget = false; - Info.FadeOutDuration = HAPI_UNREAL_NOTIFICATION_FADEOUT; - Info.ExpireDuration = HAPI_UNREAL_NOTIFICATION_EXPIRE; - const TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); - if (HoudiniBrush.IsValid()) - Info.Image = HoudiniBrush.Get(); - - - PersistentNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); - //FSlateNotificationManager::Get().Tick(); - } - - TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); - - if (NotificationItem.IsValid()) - { - // Update the persistent notification. - NotificationItem->SetText(InText); - bPersistentAllowExpiry = bExpireAndFade; - } - - //FSlateNotificationManager::Get().Tick(); -#endif - - return true; -} - -void FHoudiniEngine::TickPersistentNotification(const float DeltaTime) -{ - if (PersistentNotificationPtr.IsValid() && DeltaTime > 0.0f) - { - TimeSinceLastPersistentNotification += DeltaTime; - if (bPersistentAllowExpiry && TimeSinceLastPersistentNotification > HAPI_UNREAL_NOTIFICATION_EXPIRE) - { - TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); - if (NotificationItem.IsValid()) - { - NotificationItem->Fadeout(); - PersistentNotificationPtr.Reset(); - } - } - } - - // Tick the notification manager - //FSlateNotificationManager::Get().Tick(); -} - -void -FHoudiniEngine::UpdateSessionSyncInfoFromHoudini() -{ - if (!bEnableSessionSync) - return; - - // Set the Session Sync settings to Houdini - HAPI_SessionSyncInfo SessionSyncInfo; - //FHoudiniApi::SessionSyncInfo_Create(&SessionSyncInfo); - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetSessionSyncInfo(&Session, &SessionSyncInfo)) - { - bCookUsingHoudiniTime = SessionSyncInfo.cookUsingHoudiniTime; - bSyncViewport = SessionSyncInfo.syncViewport; - } -} - -void -FHoudiniEngine::UploadSessionSyncInfoToHoudini() -{ - // No need to set sessionsync info if we're not using session sync - if (!bEnableSessionSync) - return; - - // Set the Session Sync settings to Houdini - HAPI_SessionSyncInfo SessionSyncInfo; - SessionSyncInfo.cookUsingHoudiniTime = bCookUsingHoudiniTime; - SessionSyncInfo.syncViewport = bSyncViewport; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetSessionSyncInfo(&Session, &SessionSyncInfo)) - HOUDINI_LOG_WARNING(TEXT("Failed to set the SessionSync Infos.")); -} - -void -FHoudiniEngine::StartPDGCommandlet() -{ - if (HoudiniEngineManager) - HoudiniEngineManager->StartPDGCommandlet(); -} - -void -FHoudiniEngine::StopPDGCommandlet() -{ - if (HoudiniEngineManager) - HoudiniEngineManager->StopPDGCommandlet(); -} - -bool -FHoudiniEngine::IsPDGCommandletRunningOrConnected() -{ - if (HoudiniEngineManager) - return HoudiniEngineManager->IsPDGCommandletRunningOrConnected(); - return false; -} - -EHoudiniBGEOCommandletStatus -FHoudiniEngine::GetPDGCommandletStatus() -{ - if (HoudiniEngineManager) - return HoudiniEngineManager->GetPDGCommandletStatus(); - return EHoudiniBGEOCommandletStatus::NotStarted; -} - -void -FHoudiniEngine::UnregisterPostEngineInitCallback() -{ - if (PostEngineInitCallback.IsValid()) - FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitCallback); -} - -bool FHoudiniEngine::IsSyncWithHoudiniCookEnabled() const -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - return HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bSyncWithHoudiniCook : false; -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngine.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineScheduler.h" +#include "HoudiniEngineManager.h" +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniAssetComponent.h" +#include "HAPI/HAPI_Version.h" + +#include "Modules/ModuleManager.h" +#include "Misc/ScopeLock.h" +#include "Engine/StaticMesh.h" +#include "Materials/Material.h" +#include "ISettingsModule.h" +#include "HAL/PlatformFilemanager.h" +#include "Async/Async.h" +#include "Logging/LogMacros.h" + +#if WITH_EDITOR + #include "Widgets/Notifications/SNotificationList.h" + #include "Framework/Notifications/NotificationManager.h" +#endif + +#define LOCTEXT_NAMESPACE "HoudiniEngine" + +IMPLEMENT_MODULE(FHoudiniEngine, HoudiniEngine) +DEFINE_LOG_CATEGORY( LogHoudiniEngine ); + +FHoudiniEngine * +FHoudiniEngine::HoudiniEngineInstance = nullptr; + +FHoudiniEngine::FHoudiniEngine() + : LicenseType(HAPI_LICENSE_NONE) + , HoudiniEngineSchedulerThread(nullptr) + , HoudiniEngineScheduler(nullptr) + , HoudiniEngineManagerThread(nullptr) + , HoudiniEngineManager(nullptr) + //, bHAPIVersionMismatch(false) + , bEnableCookingGlobal(true) + , UIRefreshCountWhenPauseCooking(0) + , bFirstSessionCreated(false) + , bEnableSessionSync(false) + , bCookUsingHoudiniTime(true) + , bSyncViewport(false) + , bSyncHoudiniViewport(true) + , bSyncUnrealViewport(false) + , HoudiniLogoStaticMesh(nullptr) + , HoudiniDefaultMaterial(nullptr) + , HoudiniTemplateMaterial(nullptr) + , HoudiniLogoBrush(nullptr) + , HoudiniDefaultReferenceMesh(nullptr) + , HoudiniDefaultReferenceMeshMaterial(nullptr) +{ + Session.type = HAPI_SESSION_MAX; + Session.id = -1; + + SetSessionStatus(EHoudiniSessionStatus::Invalid); + +#if WITH_EDITOR + HapiNotificationStarted = 0.0; + TimeSinceLastPersistentNotification = 0.0; +#endif +} + +FHoudiniEngine& +FHoudiniEngine::Get() +{ + check(FHoudiniEngine::HoudiniEngineInstance); + return *FHoudiniEngine::HoudiniEngineInstance; +} + +bool +FHoudiniEngine::IsInitialized() +{ + return FHoudiniEngine::HoudiniEngineInstance != nullptr && FHoudiniEngineUtils::IsInitialized(); +} + +void +FHoudiniEngine::StartupModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine module...")); + +#if WITH_EDITOR + // Register settings. + if (ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->RegisterSettings( + "Project", "Plugins", "HoudiniEngine", + LOCTEXT("RuntimeSettingsName", "Houdini Engine"), + LOCTEXT("RuntimeSettingsDescription", "Configure the HoudiniEngine plugin"), + GetMutableDefault< UHoudiniRuntimeSettings >()); + } +#endif + + // Before starting the module, we need to locate and load HAPI library. + { + void * HAPILibraryHandle = FHoudiniEngineUtils::LoadLibHAPI(LibHAPILocation); + if ( HAPILibraryHandle ) + { + FHoudiniApi::InitializeHAPI( HAPILibraryHandle ); + } + else + { + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + HOUDINI_LOG_MESSAGE(TEXT("Failed locating or loading %s"), *LibHAPIName); + } + } + + // Create static mesh Houdini logo. + HoudiniLogoStaticMesh = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_LOGO, nullptr, LOAD_None, nullptr); + if (HoudiniLogoStaticMesh.IsValid()) + HoudiniLogoStaticMesh->AddToRoot(); + + // Create default material. + HoudiniDefaultMaterial = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultMaterial.IsValid()) + HoudiniDefaultMaterial->AddToRoot(); + + HoudiniTemplateMaterial = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniTemplateMaterial.IsValid()) + HoudiniTemplateMaterial->AddToRoot(); + + // Houdini Logo Brush + FString Icon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icon128.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) + { + const FName BrushName(*Icon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Logo Brush + FString HEIcon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_logo_128.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) + { + const FName BrushName(*HEIcon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Create Houdini default reference mesh + HoudiniDefaultReferenceMesh = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultReferenceMesh.IsValid()) + HoudiniDefaultReferenceMesh->AddToRoot(); + + // Create Houdini default reference mesh material + HoudiniDefaultReferenceMeshMaterial = LoadObject + (nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultReferenceMeshMaterial.IsValid()) + HoudiniDefaultReferenceMeshMaterial->AddToRoot(); + + // We do not automatically try to start a session when starting up the module now. + bFirstSessionCreated = false; + + // Create HAPI scheduler and processing thread. + HoudiniEngineScheduler = new FHoudiniEngineScheduler(); + HoudiniEngineSchedulerThread = FRunnableThread::Create( + HoudiniEngineScheduler, TEXT("HoudiniSchedulerThread"), 0, TPri_Normal); + + // Create Houdini Asset Manager + HoudiniEngineManager = new FHoudiniEngineManager(); + + // Set the session status to Not Started + SetSessionStatus(EHoudiniSessionStatus::NotStarted); + + // Set the default value for pausing houdini engine cooking + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bEnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; + + // Check if a null session is set + bool bNoneSession = (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None); + if (bNoneSession) + SetSessionStatus(EHoudiniSessionStatus::None); + + // Initialize the singleton with this instance + FHoudiniEngine::HoudiniEngineInstance = this; + + // See if we need to start the manager ticking if needed + // Dont tick if we failed to load HAPI, if cooking is disabled or if we're using a null session + if (FHoudiniApi::IsHAPIInitialized()) + { + if (bEnableCookingGlobal && !bNoneSession) + { + PostEngineInitCallback = FCoreDelegates::OnPostEngineInit.AddLambda([]() + { + FHoudiniEngine& HEngine = FHoudiniEngine::Get(); + HEngine.UnregisterPostEngineInitCallback(); + FHoudiniEngineManager* const Manager = HEngine.GetHoudiniEngineManager(); + if (Manager) + Manager->StartHoudiniTicking(); + }); + } + } +} + +void +FHoudiniEngine::ShutdownModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine module.")); + + // We no longer need the Houdini logo static mesh. + if (HoudiniLogoStaticMesh.IsValid()) + { + HoudiniLogoStaticMesh->RemoveFromRoot(); + HoudiniLogoStaticMesh = nullptr; + } + + // We no longer need the Houdini default material. + if (HoudiniDefaultMaterial.IsValid()) + { + HoudiniDefaultMaterial->RemoveFromRoot(); + HoudiniDefaultMaterial = nullptr; + } + + // We no longer need the Houdini default material. + if (HoudiniTemplateMaterial.IsValid()) + { + HoudiniTemplateMaterial->RemoveFromRoot(); + HoudiniTemplateMaterial = nullptr; + } + + // We no longer need the Houdini default reference mesh + if (HoudiniDefaultReferenceMesh.IsValid()) + { + HoudiniDefaultReferenceMesh->RemoveFromRoot(); + HoudiniDefaultReferenceMesh = nullptr; + } + + // We no longer need the Houdini default reference mesh material + if (HoudiniDefaultReferenceMeshMaterial.IsValid()) + { + HoudiniDefaultReferenceMeshMaterial->RemoveFromRoot(); + HoudiniDefaultReferenceMeshMaterial = nullptr; + } + /* + // We no longer need Houdini digital asset used for loading bgeo files. + if (HoudiniBgeoAsset.IsValid()) + { + HoudiniBgeoAsset->RemoveFromRoot(); + HoudiniBgeoAsset = nullptr; + } + */ + +#if WITH_EDITOR + // Unregister settings. + ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings"); + if (SettingsModule) + SettingsModule->UnregisterSettings("Project", "Plugins", "HoudiniEngine"); +#endif + + // Do scheduler and thread clean up. + if (HoudiniEngineScheduler) + HoudiniEngineScheduler->Stop(); + + if (HoudiniEngineSchedulerThread) + { + //HoudiniEngineSchedulerThread->Kill( true ); + HoudiniEngineSchedulerThread->WaitForCompletion(); + + delete HoudiniEngineSchedulerThread; + HoudiniEngineSchedulerThread = nullptr; + } + + if ( HoudiniEngineScheduler ) + { + delete HoudiniEngineScheduler; + HoudiniEngineScheduler = nullptr; + } + + // Do manager clean up. + if (HoudiniEngineManager) + HoudiniEngineManager->StopHoudiniTicking(); + + if (HoudiniEngineManager) + { + delete HoudiniEngineManager; + HoudiniEngineManager = nullptr; + } + + // Perform HAPI finalization. + if ( FHoudiniApi::IsHAPIInitialized() ) + { + FHoudiniApi::Cleanup(GetSession()); + FHoudiniApi::CloseSession(GetSession()); + SessionStatus = EHoudiniSessionStatus::Invalid; + } + + FHoudiniApi::FinalizeHAPI(); + + FHoudiniEngine::HoudiniEngineInstance = nullptr; +} + +void +FHoudiniEngine::AddTask(const FHoudiniEngineTask & InTask) +{ + if ( HoudiniEngineScheduler ) + HoudiniEngineScheduler->AddTask(InTask); + + FScopeLock ScopeLock(&CriticalSection); + FHoudiniEngineTaskInfo TaskInfo; + TaskInfo.TaskType = InTask.TaskType; + TaskInfo.TaskState = EHoudiniEngineTaskState::Working; + + TaskInfos.Add(InTask.HapiGUID, TaskInfo); +} + +void +FHoudiniEngine::AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo) +{ + FScopeLock ScopeLock(&CriticalSection); + TaskInfos.Add(InHapiGUID, InTaskInfo); +} + +void +FHoudiniEngine::RemoveTaskInfo(const FGuid& InHapiGUID) +{ + FScopeLock ScopeLock(&CriticalSection); + TaskInfos.Remove(InHapiGUID); +} + +bool +FHoudiniEngine::RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo) +{ + FScopeLock ScopeLock(&CriticalSection); + + if (TaskInfos.Contains(InHapiGUID)) + { + OutTaskInfo = TaskInfos[InHapiGUID]; + return true; + } + + return false; +} + +/* +void +FHoudiniEngine::AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + if (HoudiniEngineManager) + HoudiniEngineManager->AddComponent(HAC); +} +*/ + +const FString & +FHoudiniEngine::GetLibHAPILocation() const +{ + return LibHAPILocation; +} + +const HAPI_Session * +FHoudiniEngine::GetSession() const +{ + return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; +} + +const EHoudiniSessionStatus& +FHoudiniEngine::GetSessionStatus() const +{ + return SessionStatus; +} + +void +FHoudiniEngine::SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus) +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None) + { + // Check for none sessions first + SessionStatus = EHoudiniSessionStatus::None; + return; + } + + if (!bFirstSessionCreated) + { + // Don't change the status unless we've attempted to start the session once + SessionStatus = EHoudiniSessionStatus::NotStarted; + return; + } + + switch (InSessionStatus) + { + case EHoudiniSessionStatus::NotStarted: + case EHoudiniSessionStatus::NoLicense: + case EHoudiniSessionStatus::Lost: + case EHoudiniSessionStatus::None: + case EHoudiniSessionStatus::Invalid: + case EHoudiniSessionStatus::Connected: + { + SessionStatus = InSessionStatus; + } + break; + + case EHoudiniSessionStatus::Stopped: + { + // Only set to stop status if the session was valid + if (SessionStatus == EHoudiniSessionStatus::Connected) + SessionStatus = EHoudiniSessionStatus::Stopped; + } + break; + + case EHoudiniSessionStatus::Failed: + { + // Preserve No License / Lost status + if (SessionStatus != EHoudiniSessionStatus::NoLicense && SessionStatus != EHoudiniSessionStatus::Lost) + SessionStatus = EHoudiniSessionStatus::Failed; + } + break; + } +} + +HAPI_CookOptions +FHoudiniEngine::GetDefaultCookOptions() +{ + // Default CookOptions + HAPI_CookOptions CookOptions; + FHoudiniApi::CookOptions_Init(&CookOptions); + + CookOptions.curveRefineLOD = 8.0f; + CookOptions.clearErrorsAndWarnings = false; + CookOptions.maxVerticesPerPrimitive = 3; + CookOptions.splitGeosByGroup = false; + CookOptions.splitGeosByAttribute = false; + CookOptions.splitAttrSH = 0; + CookOptions.refineCurveToLinear = true; + CookOptions.handleBoxPartTypes = false; + CookOptions.handleSpherePartTypes = false; + CookOptions.splitPointsByVertexAttributes = false; + CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; + CookOptions.cookTemplatedGeos = true; + + return CookOptions; +} + +bool +FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, + const bool& StartAutomaticServer, + const float& AutomaticServerTimeout, + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const int32& ServerPort, + const FString& ServerHost) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + // Only start a new Session if we dont already have a valid one + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) + return true; + + // Set the HAPI_CLIENT_NAME environment variable to "unreal" + // We need to do this before starting HARS. + FPlatformMisc::SetEnvironmentVar(TEXT("HAPI_CLIENT_NAME"), TEXT("unreal")); + + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; + + HAPI_ThriftServerOptions ServerOptions; + FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); + ServerOptions.autoClose = true; + ServerOptions.timeoutMs = AutomaticServerTimeout; + + // Unless we automatically start the server, + // consider we're in SessionSync mode + bEnableSessionSync = true; + + auto UpdatePathForServer = [&] + { + // Modify our PATH so that HARC will find HARS.exe + const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); + + FString OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); + + FString ModifiedPath = +#if PLATFORM_MAC + // On Mac our binaries are split between two folders + LibHAPILocation + TEXT("/../Resources/bin") + PathDelimiter + +#endif + LibHAPILocation + PathDelimiter + OrigPathVar; + + FPlatformMisc::SetEnvironmentVar(TEXT("PATH"), *ModifiedPath); + }; + + switch ( SessionType ) + { + case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: + { + // Try to connect to an existing socket session first + SessionResult = FHoudiniApi::CreateThriftSocketSession( + SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort ); + + // Start a session and try to connect to it if we failed + if ( StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS ) + { + UpdatePathForServer(); + FHoudiniApi::StartThriftSocketServer( + &ServerOptions, ServerPort, nullptr); + + // We've started the server manually, disable session sync + bEnableSessionSync = false; + + SessionResult = FHoudiniApi::CreateThriftSocketSession( + SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort); + } + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: + { + // Try to connect to an existing pipe session first + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8(*ServerPipeName) ); + + // Start a session and try to connect to it if we failed + if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) + { + UpdatePathForServer(); + FHoudiniApi::StartThriftNamedPipeServer( + &ServerOptions, TCHAR_TO_UTF8(*ServerPipeName), nullptr); + + // We've started the server manually, disable session sync + bEnableSessionSync = false; + + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8(*ServerPipeName)); + } + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_None: + { + HOUDINI_LOG_MESSAGE(TEXT("Session type set to None, Cooking is disabled.")); + // Disable session sync + bEnableSessionSync = false; + break; + } + + // As of Unreal 4.19, InProcess sessions are not supported anymore + case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + default: + HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine session type")); + // Disable session sync + bEnableSessionSync = false; + break; + } + + if(SessionType != EHoudiniRuntimeSettingsSessionType::HRSST_None) + FHoudiniEngine::Get().SetFirstSessionCreated(true); + + if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) + { + // Disable session sync as well? + bEnableSessionSync = false; + return false; + } + + // Update this session's license type + HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( + SessionPtr, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); + + return true; +} + +bool +FHoudiniEngine::SessionSyncConnect( + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const FString& ServerHost, + const int32& ServerPort) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + // Only start a new Session if we dont already have a valid one + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(&Session)) + return true; + + // Consider the session failed as long as we dont connect + SetSessionStatus(EHoudiniSessionStatus::Failed); + + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + + HAPI_ThriftServerOptions ServerOptions; + FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); + ServerOptions.autoClose = true; + ServerOptions.timeoutMs = HoudiniRuntimeSettings->AutomaticServerTimeout; + + switch (SessionType) + { + case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: + { + // Try to connect to an existing socket session first + SessionResult = FHoudiniApi::CreateThriftSocketSession( + &Session, TCHAR_TO_UTF8(*ServerHost), ServerPort); + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: + { + // Try to connect to an existing pipe session first + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + &Session, TCHAR_TO_UTF8(*ServerPipeName)); + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_None: + case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + default: + HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine Session Sync Type!!")); + bEnableSessionSync = false; + break; + } + + if (SessionResult != HAPI_RESULT_SUCCESS) + return false; + + // Enable session sync + bEnableSessionSync = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); + + // Update this session's license type + HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( + &Session, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); + + // Update the default viewport sync settings + bSyncViewport = HoudiniRuntimeSettings->bSyncViewport; + bSyncHoudiniViewport = HoudiniRuntimeSettings->bSyncHoudiniViewport; + bSyncUnrealViewport = HoudiniRuntimeSettings->bSyncUnrealViewport; + + return true; +} + +bool +FHoudiniEngine::InitializeHAPISession() +{ + // The HAPI stubs needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The Houdini API stubs have not been properly initialized.")); + return false; + } + + // We need a Valid Session + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(GetSession())) + { + HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The session is invalid.")); + return false; + } + + // Now, initialize HAPI with the new session + // We need to make sure HAPI version is correct. + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + // 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); + + // Compare defined and running versions. + if (RunningEngineMajor != HAPI_VERSION_HOUDINI_ENGINE_MAJOR + || RunningEngineMinor != HAPI_VERSION_HOUDINI_ENGINE_MINOR) + { + // Major or minor HAPI version differs, stop here + HOUDINI_LOG_ERROR( + TEXT("Starting up the Houdini Engine module failed: built and running versions do not match.")); + HOUDINI_LOG_ERROR( + TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), + HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, + RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + + // Display an error message + + // + return false; + + } + else if (RunningEngineApi != HAPI_VERSION_HOUDINI_ENGINE_API) + { + // Major/minor HAPIversions match, but only the API version differs, + // Allow the user to continue but warn him of possible instabilities + HOUDINI_LOG_WARNING( + TEXT("Starting up the Houdini Engine module: built and running versions do not match.")); + HOUDINI_LOG_WARNING( + TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), + HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, + RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + HOUDINI_LOG_WARNING( + TEXT("This could cause instabilities and crashes when using the Houdini Engine plugin")); + } + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Default CookOptions + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + + bool bUseCookingThread = true; + HAPI_Result Result = FHoudiniApi::Initialize( + &Session, + &CookOptions, + bUseCookingThread, + HoudiniRuntimeSettings->CookingThreadStackSize, + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->HoudiniEnvironmentFiles), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->OtlSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->DsoSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->ImageDsoSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->AudioDsoSearchPath)); + + if (Result == HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module.")); + } + else if (Result == HAPI_RESULT_ALREADY_INITIALIZED) + { + // Reused session? just notify the user + HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module - HAPI was already initialzed.")); + } + else + { + HOUDINI_LOG_ERROR( + TEXT("Houdini Engine API initialization failed: %s"), + *FHoudiniEngineUtils::GetErrorDescription(Result)); + + return false; + } + + // Let HAPI know we are running inside UE4 + FHoudiniApi::SetServerEnvString(&Session, HAPI_ENV_CLIENT_NAME, HAPI_UNREAL_CLIENT_NAME); + + if (bEnableSessionSync) + { + // Set the session sync infos if needed + UploadSessionSyncInfoToHoudini(); + + // Indicate that Session Sync is enabled + FString Notification = TEXT("Houdini Engine Session Sync enabled."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Session Sync enabled.")); + } + + return true; +} + + +void +FHoudiniEngine::OnSessionLost() +{ + // Mark the session as invalid + Session.id = -1; + Session.type = HAPI_SESSION_MAX; + SetSessionStatus(EHoudiniSessionStatus::Lost); + + bEnableSessionSync = false; + HoudiniEngineManager->StopHoudiniTicking(); + + // This indicates that we likely have lost the session due to a crash in HARS/Houdini + FString Notification = TEXT("Houdini Engine Session lost!"); + FHoudiniEngineUtils::CreateSlateNotification(Notification, 2.0, 4.0); + + HOUDINI_LOG_ERROR(TEXT("Houdini Engine Session lost! This could be caused by a crash in HARS.")); +} + +bool +FHoudiniEngine::StopSession() +{ + HAPI_Session* SessionPtr = &Session; + return StopSession(SessionPtr); +} + +bool +FHoudiniEngine::StopSession(HAPI_Session*& SessionPtr) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) + { + // SessionPtr is valid, clean up and close the session + FHoudiniApi::Cleanup(SessionPtr); + FHoudiniApi::CloseSession(SessionPtr); + } + + Session.id = -1; + Session.type = HAPI_SESSION_MAX; + SetSessionStatus(EHoudiniSessionStatus::Stopped); + bEnableSessionSync = false; + + HoudiniEngineManager->StopHoudiniTicking(); + + return true; +} + +bool +FHoudiniEngine::RestartSession() +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Starting the Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + if (!StopSession(SessionPtr)) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); + } + else + { + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + HoudiniRuntimeSettings->bStartAutomaticServer, + HoudiniRuntimeSettings->AutomaticServerTimeout, + HoudiniRuntimeSettings->SessionType, + HoudiniRuntimeSettings->ServerPipeName, + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to start the new Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); + } + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +bool +FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName) +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Create the Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + true, + HoudiniRuntimeSettings->AutomaticServerTimeout, + SessionType, + OverrideServerPipeName == NAME_None ? HoudiniRuntimeSettings->ServerPipeName : OverrideServerPipeName.ToString(), + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +bool +FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType) +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Connecting to a Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + false, + HoudiniRuntimeSettings->AutomaticServerTimeout, + SessionType, + HoudiniRuntimeSettings->ServerPipeName, + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +void +FHoudiniEngine::StartTicking() +{ + // Finish the notification and display the results + FString StatusText = TEXT("Houdini Engine session connected."); + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + + HoudiniEngineManager->StartHoudiniTicking(); +} + +void +FHoudiniEngine::StopTicking() +{ + // Finish the notification and display the results + FString StatusText = TEXT("Failed to start the Houdini Engine session..."); + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + + HoudiniEngineManager->StopHoudiniTicking(); + + HAPI_Session* SessionPtr = &Session; + StopSession(SessionPtr); +} + +bool +FHoudiniEngine::IsCookingEnabled() const +{ + return bEnableCookingGlobal; +} + +void +FHoudiniEngine::SetCookingEnabled(const bool& bInEnableCooking) +{ + bEnableCookingGlobal = bInEnableCooking; +} + +bool +FHoudiniEngine::GetFirstSessionCreated() const +{ + return bFirstSessionCreated; +} + +bool +FHoudiniEngine::CreateTaskSlateNotification( + const FText& InText, const bool& bForceNow, const float& NotificationExpire, const float& NotificationFadeOut) +{ +#if WITH_EDITOR + static double NotificationUpdateFrequency = 2.0f; + + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return false; + + if (!bForceNow) + { + if ((FPlatformTime::Seconds() - HapiNotificationStarted) < NotificationUpdateFrequency) + return false; + } + + if (!NotificationPtr.IsValid()) + { + FNotificationInfo Info(InText); + Info.bFireAndForget = false; + Info.FadeOutDuration = NotificationFadeOut; + Info.ExpireDuration = NotificationExpire; + TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + /* + if (!IsPIEActive()) + */ + + NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + //FSlateNotificationManager::Get().Tick(); + } +#endif + + return true; +} + +bool +FHoudiniEngine::UpdateTaskSlateNotification(const FText& InText) +{ +#if WITH_EDITOR + // task is till running + // Just update the slate notification + TSharedPtr NotificationItem = NotificationPtr.Pin(); + if (NotificationItem.IsValid()) + NotificationItem->SetText(InText); + + //FSlateNotificationManager::Get().Tick(); +#endif + + return true; +} + +bool +FHoudiniEngine::FinishTaskSlateNotification(const FText& InText) +{ +#if WITH_EDITOR + if (NotificationPtr.IsValid()) + { + TSharedPtr NotificationItem = NotificationPtr.Pin(); + if (NotificationItem.IsValid()) + { + NotificationItem->SetText(InText); + NotificationItem->ExpireAndFadeout(); + + NotificationPtr.Reset(); + } + } +#endif + + return true; +} + +bool FHoudiniEngine::UpdateCookingNotification(const FText& InText, const bool bExpireAndFade) +{ +#if WITH_EDITOR + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return false; + + UpdatePersistentNotification(InText, bExpireAndFade); + +#endif + return true; +} + +bool +FHoudiniEngine::UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade) +{ +#if WITH_EDITOR + TimeSinceLastPersistentNotification = 0.0; + + if (!PersistentNotificationPtr.IsValid()) + { + FNotificationInfo Info(InText); + Info.bFireAndForget = false; + Info.FadeOutDuration = HAPI_UNREAL_NOTIFICATION_FADEOUT; + Info.ExpireDuration = HAPI_UNREAL_NOTIFICATION_EXPIRE; + const TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + + PersistentNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + //FSlateNotificationManager::Get().Tick(); + } + + TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); + + if (NotificationItem.IsValid()) + { + // Update the persistent notification. + NotificationItem->SetText(InText); + bPersistentAllowExpiry = bExpireAndFade; + } + + //FSlateNotificationManager::Get().Tick(); +#endif + + return true; +} + +void FHoudiniEngine::TickPersistentNotification(const float DeltaTime) +{ + if (PersistentNotificationPtr.IsValid() && DeltaTime > 0.0f) + { + TimeSinceLastPersistentNotification += DeltaTime; + if (bPersistentAllowExpiry && TimeSinceLastPersistentNotification > HAPI_UNREAL_NOTIFICATION_EXPIRE) + { + TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); + if (NotificationItem.IsValid()) + { + NotificationItem->Fadeout(); + PersistentNotificationPtr.Reset(); + } + } + } + + // Tick the notification manager + //FSlateNotificationManager::Get().Tick(); +} + +void +FHoudiniEngine::UpdateSessionSyncInfoFromHoudini() +{ + if (!bEnableSessionSync) + return; + + // Set the Session Sync settings to Houdini + HAPI_SessionSyncInfo SessionSyncInfo; + //FHoudiniApi::SessionSyncInfo_Create(&SessionSyncInfo); + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetSessionSyncInfo(&Session, &SessionSyncInfo)) + { + bCookUsingHoudiniTime = SessionSyncInfo.cookUsingHoudiniTime; + bSyncViewport = SessionSyncInfo.syncViewport; + } +} + +void +FHoudiniEngine::UploadSessionSyncInfoToHoudini() +{ + // No need to set sessionsync info if we're not using session sync + if (!bEnableSessionSync) + return; + + // Set the Session Sync settings to Houdini + HAPI_SessionSyncInfo SessionSyncInfo; + SessionSyncInfo.cookUsingHoudiniTime = bCookUsingHoudiniTime; + SessionSyncInfo.syncViewport = bSyncViewport; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetSessionSyncInfo(&Session, &SessionSyncInfo)) + HOUDINI_LOG_WARNING(TEXT("Failed to set the SessionSync Infos.")); +} + +void +FHoudiniEngine::StartPDGCommandlet() +{ + if (HoudiniEngineManager) + HoudiniEngineManager->StartPDGCommandlet(); +} + +void +FHoudiniEngine::StopPDGCommandlet() +{ + if (HoudiniEngineManager) + HoudiniEngineManager->StopPDGCommandlet(); +} + +bool +FHoudiniEngine::IsPDGCommandletRunningOrConnected() +{ + if (HoudiniEngineManager) + return HoudiniEngineManager->IsPDGCommandletRunningOrConnected(); + return false; +} + +EHoudiniBGEOCommandletStatus +FHoudiniEngine::GetPDGCommandletStatus() +{ + if (HoudiniEngineManager) + return HoudiniEngineManager->GetPDGCommandletStatus(); + return EHoudiniBGEOCommandletStatus::NotStarted; +} + +void +FHoudiniEngine::UnregisterPostEngineInitCallback() +{ + if (PostEngineInitCallback.IsValid()) + FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitCallback); +} + +bool FHoudiniEngine::IsSyncWithHoudiniCookEnabled() const +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + return HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bSyncWithHoudiniCook : false; +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.h b/Source/HoudiniEngine/Private/HoudiniEngine.h index 494ca2049..a921b1893 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.h +++ b/Source/HoudiniEngine/Private/HoudiniEngine.h @@ -1,344 +1,344 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineTaskInfo.h" -#include "HoudiniRuntimeSettings.h" - -#include "Modules/ModuleInterface.h" - -class FRunnableThread; -class FHoudiniEngineScheduler; -class FHoudiniEngineManager; -class UHoudiniAssetComponent; -class UStaticMesh; -class UMaterial; - -struct FSlateDynamicImageBrush; - -enum class EHoudiniBGEOCommandletStatus : uint8; - -UENUM() -enum class EHoudiniSessionStatus : int8 -{ - Invalid = -1, - - NotStarted, // Session not initialized yet - Connected, // Session successfully started - None, // Session type set to None - Stopped, // Session stopped - Failed, // Session failed to connect - Lost, // Session Lost (HARS/Houdini Crash?) - NoLicense, // Failed to acquire a license -}; - -// Not using the IHoudiniEngine interface for now -class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface -{ - public: - - FHoudiniEngine(); - - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // Return singleton instance of Houdini Engine, used internally. - static FHoudiniEngine & Get(); - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // Return the location of the currently loaded LibHAPI - virtual const FString & GetLibHAPILocation() const; - - // Session accessor - virtual const HAPI_Session* GetSession() const; - - virtual const EHoudiniSessionStatus& GetSessionStatus() const; - - virtual void SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus); - - // Default cook options - static HAPI_CookOptions GetDefaultCookOptions(); - - // Creates a new session - bool StartSession( - HAPI_Session*& SessionPtr, - const bool& StartAutomaticServer, - const float& AutomaticServerTimeout, - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const int32& ServerPort, - const FString& ServerHost); - - // Stop the current session if it is valid - bool StopSession(HAPI_Session*& SessionPtr); - - // Creates a session sync session - bool SessionSyncConnect( - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const FString& ServerHost, - const int32& ServerPort); - - // Stops the current session - bool StopSession(); - // Stops, then creates a new session - bool RestartSession(); - // Creates a session, start HARS - bool CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName=NAME_None); - // Connect to an existing HE session - bool ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType); - - // Starts the HoudiniEngineManager ticking - void StartTicking(); - // Stops the HoudiniEngineManager ticking and invalidate the session - void StopTicking(); - - // Initialize HAPI - bool InitializeHAPISession(); - - // Indicate to the plugin that the session is now invalid (HAPI has likely crashed...) - void OnSessionLost(); - - bool CreateTaskSlateNotification( - const FText& InText, - const bool& bForceNow = false, - const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, - const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); - - bool UpdateTaskSlateNotification(const FText& InText); - bool FinishTaskSlateNotification(const FText& InText); - - // Only update persistent notification if cooking notification has been enabled in the settings. - bool UpdateCookingNotification(const FText& InText, const bool bExpireAndFade); - - // Update persistent notification irrespective of any notification enable/disable settings. - bool UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade); - - // If the time since last persistent notification has expired, fade out the persistent notification. - void TickPersistentNotification(float DeltaTime); - - void SetHapiNotificationStartedTime(const double& InTime) { HapiNotificationStarted = InTime; }; - - // Register task for execution. - virtual void AddTask(const FHoudiniEngineTask & InTask); - // Register task info. - virtual void AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo); - // Remove task info. - virtual void RemoveTaskInfo(const FGuid& InHapiGUID); - // Remove task info. - virtual bool RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo); - // Register asset to the manager - //virtual void AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC); - - // Indicates whether or not cooking is currently enabled - bool IsCookingEnabled() const; - // Sets whether or not cooking is currently enabled - void SetCookingEnabled(const bool& bInEnableCooking); - - // Check if we need to refresh UI when cooking is paused - bool HasUIFinishRefreshingWhenPausingCooking() const { return UIRefreshCountWhenPauseCooking <= 0; }; - - // Reset number of registered HACs when cooking is paused - void SetUIRefreshCountWhenPauseCooking(const int32& bInCount) { UIRefreshCountWhenPauseCooking = bInCount; }; - // Reduce the count by 1 when an HAC UI is refreshed when cooking is paused - void RefreshUIDisplayedWhenPauseCooking() { UIRefreshCountWhenPauseCooking -= 1; }; - - // Indicates whether or not the first attempt to create a Houdini session was made - bool GetFirstSessionCreated() const; - // Sets whether or not the first attempt to create a Houdini session was made - void SetFirstSessionCreated(const bool& bInStarted) { bFirstSessionCreated = bInStarted; }; - - bool IsSessionSyncEnabled() const { return bEnableSessionSync; }; - - bool IsSyncWithHoudiniCookEnabled() const; - - bool IsCookUsingHoudiniTimeEnabled() const { return bCookUsingHoudiniTime; }; - - bool IsSyncViewportEnabled() const { return bSyncViewport; }; - - bool IsSyncHoudiniViewportEnabled() const { return bSyncHoudiniViewport; }; - - bool IsSyncUnrealViewportEnabled() const { return bSyncUnrealViewport; }; - - // Helper function to update our session sync infos from Houdini's - void UpdateSessionSyncInfoFromHoudini(); - - // Helper function to update Houdini's Session sync infos from ours - void UploadSessionSyncInfoToHoudini(); - - // Sets whether or not viewport sync is enabled - void SetSyncViewportEnabled(const bool& bInSync) { bSyncViewport = bInSync; }; - // Sets whether or not we want to sync the houdini viewport to unreal's - void SetSyncHoudiniViewportEnabled(const bool& bInSync) { bSyncHoudiniViewport = bInSync; }; - // Sets whether or not we want to sync unreal's viewport to Houdini's - void SetSyncUnrealViewportEnabled(const bool& bInSync) { bSyncUnrealViewport = bInSync; }; - - // Returns the default Houdini Logo Static Mesh - virtual TWeakObjectPtr GetHoudiniLogoStaticMesh() const { return HoudiniLogoStaticMesh; }; - - // Returns either the default Houdini material or the default template material - virtual TWeakObjectPtr GetHoudiniDefaultMaterial(const bool& bIsTemplate) const { return bIsTemplate ? HoudiniTemplateMaterial : HoudiniDefaultMaterial; }; - - // Returns the default Houdini material - virtual TWeakObjectPtr GetHoudiniDefaultMaterial() const { return HoudiniDefaultMaterial; }; - // Returns the default template Houdini material - virtual TWeakObjectPtr GetHoudiniTemplatedMaterial() const { return HoudiniTemplateMaterial; }; - // Returns a shared Ptr to the houdini logo - TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; - // Returns a shared Ptr to the houdini engine logo - TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; - - // Returns the default Houdini reference mesh - virtual TWeakObjectPtr GetHoudiniDefaultReferenceMesh() const { return HoudiniDefaultReferenceMesh; }; - // Returns the default Houdini reference mesh material - virtual TWeakObjectPtr GetHoudiniDefaultReferenceMeshMaterial() const { return HoudiniDefaultReferenceMeshMaterial; }; - - const HAPI_License GetLicenseType() const { return LicenseType; }; - - const bool IsLicenseIndie() const { return (LicenseType == HAPI_LICENSE_HOUDINI_ENGINE_INDIE || LicenseType == HAPI_LICENSE_HOUDINI_INDIE); }; - - // Session Sync ProcHandle accessor - FProcHandle GetHESSProcHandle() const { return HESS_ProcHandle; }; - void SetHESSProcHandle(const FProcHandle& InProcHandle) { HESS_ProcHandle = InProcHandle; }; - - void StartPDGCommandlet(); - - void StopPDGCommandlet(); - - bool IsPDGCommandletRunningOrConnected(); - - EHoudiniBGEOCommandletStatus GetPDGCommandletStatus(); - - FHoudiniEngineManager* GetHoudiniEngineManager() { return HoudiniEngineManager; } - - const FHoudiniEngineManager* GetHoudiniEngineManager() const { return HoudiniEngineManager; } - - void UnregisterPostEngineInitCallback(); - - private: - - // Singleton instance of Houdini Engine. - static FHoudiniEngine * HoudiniEngineInstance; - - // Location of libHAPI binary. - FString LibHAPILocation; - - // The Houdini Engine session. - HAPI_Session Session; - - // The Houdini Engine session's status - EHoudiniSessionStatus SessionStatus; - - // The type of HE license used by the current session - HAPI_License LicenseType; - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // Map of task statuses. - TMap TaskInfos; - - // Thread used to execute the scheduler. - FRunnableThread * HoudiniEngineSchedulerThread; - // Scheduler used to schedule HAPI instantiation and cook tasks. - FHoudiniEngineScheduler * HoudiniEngineScheduler; - - // Thread used to execute the manager. - FRunnableThread * HoudiniEngineManagerThread; - // Scheduler used to monitor and process Houdini Asset Components - FHoudiniEngineManager * HoudiniEngineManager; - - // Process Handle for session sync - FProcHandle HESS_ProcHandle; - - // Is set to true when mismatch between defined and running HAPI versions is detected. - //bool bHAPIVersionMismatch; - - // Global cooking flag, used to pause HEngine while using the editor - bool bEnableCookingGlobal; - // Counter of HACs that need to be refreshed when pause cooking - int32 UIRefreshCountWhenPauseCooking; - - // Indicates that the first attempt to create a session has been done - // This is to delay the first "automatic" session creation for the first cook - // or instantiation rather than when the module started. - bool bFirstSessionCreated; - - // Indicates if the current session is a SessionSync one - bool bEnableSessionSync; - - // If true and we're in SessionSync, keeps the assets on the plugin side synchronized with changes on the Houdini side. - //bool bSyncWithHoudiniCook; - - // If true and we're in SessionSync, use the Houdini Timeline time to cook assets. - bool bCookUsingHoudiniTime; - - // If true and we're in Session Sync, the Houdini and Unreal viewport will be synchronized. - bool bSyncViewport; - // If true and we're in Session Sync, the Houdini viewport will be synchronized to Unreal's. - bool bSyncHoudiniViewport; - // If true and we're in Session Sync, the Unreal viewport will be synchronized to Houdini's. - bool bSyncUnrealViewport; - - // Static mesh used for Houdini logo rendering. - TWeakObjectPtr HoudiniLogoStaticMesh; - - // Material used as default material. - TWeakObjectPtr HoudiniDefaultMaterial; - - // Material used as default template material. - TWeakObjectPtr HoudiniTemplateMaterial; - - // Houdini logo brush. - TSharedPtr HoudiniLogoBrush; - // Houdini logo brush. - TSharedPtr HoudiniEngineLogoBrush; - - // Static mesh used for default mesh reference - TWeakObjectPtr HoudiniDefaultReferenceMesh; - - // Material used for default mesh reference - TWeakObjectPtr HoudiniDefaultReferenceMeshMaterial; - - FDelegateHandle PostEngineInitCallback; - -#if WITH_EDITOR - /** Notification used by this component. **/ - TWeakPtr NotificationPtr; - - /** Persistent notification. **/ - bool bPersistentAllowExpiry; - TWeakPtr PersistentNotificationPtr; - float TimeSinceLastPersistentNotification; - - /** Used to delay notification updates for HAPI asynchronous work. **/ - double HapiNotificationStarted; -#endif +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniRuntimeSettings.h" + +#include "Modules/ModuleInterface.h" + +class FRunnableThread; +class FHoudiniEngineScheduler; +class FHoudiniEngineManager; +class UHoudiniAssetComponent; +class UStaticMesh; +class UMaterial; + +struct FSlateDynamicImageBrush; + +enum class EHoudiniBGEOCommandletStatus : uint8; + +UENUM() +enum class EHoudiniSessionStatus : int8 +{ + Invalid = -1, + + NotStarted, // Session not initialized yet + Connected, // Session successfully started + None, // Session type set to None + Stopped, // Session stopped + Failed, // Session failed to connect + Lost, // Session Lost (HARS/Houdini Crash?) + NoLicense, // Failed to acquire a license +}; + +// Not using the IHoudiniEngine interface for now +class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface +{ + public: + + FHoudiniEngine(); + + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // Return singleton instance of Houdini Engine, used internally. + static FHoudiniEngine & Get(); + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // Return the location of the currently loaded LibHAPI + virtual const FString & GetLibHAPILocation() const; + + // Session accessor + virtual const HAPI_Session* GetSession() const; + + virtual const EHoudiniSessionStatus& GetSessionStatus() const; + + virtual void SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus); + + // Default cook options + static HAPI_CookOptions GetDefaultCookOptions(); + + // Creates a new session + bool StartSession( + HAPI_Session*& SessionPtr, + const bool& StartAutomaticServer, + const float& AutomaticServerTimeout, + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const int32& ServerPort, + const FString& ServerHost); + + // Stop the current session if it is valid + bool StopSession(HAPI_Session*& SessionPtr); + + // Creates a session sync session + bool SessionSyncConnect( + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const FString& ServerHost, + const int32& ServerPort); + + // Stops the current session + bool StopSession(); + // Stops, then creates a new session + bool RestartSession(); + // Creates a session, start HARS + bool CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName=NAME_None); + // Connect to an existing HE session + bool ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType); + + // Starts the HoudiniEngineManager ticking + void StartTicking(); + // Stops the HoudiniEngineManager ticking and invalidate the session + void StopTicking(); + + // Initialize HAPI + bool InitializeHAPISession(); + + // Indicate to the plugin that the session is now invalid (HAPI has likely crashed...) + void OnSessionLost(); + + bool CreateTaskSlateNotification( + const FText& InText, + const bool& bForceNow = false, + const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, + const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); + + bool UpdateTaskSlateNotification(const FText& InText); + bool FinishTaskSlateNotification(const FText& InText); + + // Only update persistent notification if cooking notification has been enabled in the settings. + bool UpdateCookingNotification(const FText& InText, const bool bExpireAndFade); + + // Update persistent notification irrespective of any notification enable/disable settings. + bool UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade); + + // If the time since last persistent notification has expired, fade out the persistent notification. + void TickPersistentNotification(float DeltaTime); + + void SetHapiNotificationStartedTime(const double& InTime) { HapiNotificationStarted = InTime; }; + + // Register task for execution. + virtual void AddTask(const FHoudiniEngineTask & InTask); + // Register task info. + virtual void AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo); + // Remove task info. + virtual void RemoveTaskInfo(const FGuid& InHapiGUID); + // Remove task info. + virtual bool RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo); + // Register asset to the manager + //virtual void AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC); + + // Indicates whether or not cooking is currently enabled + bool IsCookingEnabled() const; + // Sets whether or not cooking is currently enabled + void SetCookingEnabled(const bool& bInEnableCooking); + + // Check if we need to refresh UI when cooking is paused + bool HasUIFinishRefreshingWhenPausingCooking() const { return UIRefreshCountWhenPauseCooking <= 0; }; + + // Reset number of registered HACs when cooking is paused + void SetUIRefreshCountWhenPauseCooking(const int32& bInCount) { UIRefreshCountWhenPauseCooking = bInCount; }; + // Reduce the count by 1 when an HAC UI is refreshed when cooking is paused + void RefreshUIDisplayedWhenPauseCooking() { UIRefreshCountWhenPauseCooking -= 1; }; + + // Indicates whether or not the first attempt to create a Houdini session was made + bool GetFirstSessionCreated() const; + // Sets whether or not the first attempt to create a Houdini session was made + void SetFirstSessionCreated(const bool& bInStarted) { bFirstSessionCreated = bInStarted; }; + + bool IsSessionSyncEnabled() const { return bEnableSessionSync; }; + + bool IsSyncWithHoudiniCookEnabled() const; + + bool IsCookUsingHoudiniTimeEnabled() const { return bCookUsingHoudiniTime; }; + + bool IsSyncViewportEnabled() const { return bSyncViewport; }; + + bool IsSyncHoudiniViewportEnabled() const { return bSyncHoudiniViewport; }; + + bool IsSyncUnrealViewportEnabled() const { return bSyncUnrealViewport; }; + + // Helper function to update our session sync infos from Houdini's + void UpdateSessionSyncInfoFromHoudini(); + + // Helper function to update Houdini's Session sync infos from ours + void UploadSessionSyncInfoToHoudini(); + + // Sets whether or not viewport sync is enabled + void SetSyncViewportEnabled(const bool& bInSync) { bSyncViewport = bInSync; }; + // Sets whether or not we want to sync the houdini viewport to unreal's + void SetSyncHoudiniViewportEnabled(const bool& bInSync) { bSyncHoudiniViewport = bInSync; }; + // Sets whether or not we want to sync unreal's viewport to Houdini's + void SetSyncUnrealViewportEnabled(const bool& bInSync) { bSyncUnrealViewport = bInSync; }; + + // Returns the default Houdini Logo Static Mesh + virtual TWeakObjectPtr GetHoudiniLogoStaticMesh() const { return HoudiniLogoStaticMesh; }; + + // Returns either the default Houdini material or the default template material + virtual TWeakObjectPtr GetHoudiniDefaultMaterial(const bool& bIsTemplate) const { return bIsTemplate ? HoudiniTemplateMaterial : HoudiniDefaultMaterial; }; + + // Returns the default Houdini material + virtual TWeakObjectPtr GetHoudiniDefaultMaterial() const { return HoudiniDefaultMaterial; }; + // Returns the default template Houdini material + virtual TWeakObjectPtr GetHoudiniTemplatedMaterial() const { return HoudiniTemplateMaterial; }; + // Returns a shared Ptr to the houdini logo + TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; + // Returns a shared Ptr to the houdini engine logo + TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; + + // Returns the default Houdini reference mesh + virtual TWeakObjectPtr GetHoudiniDefaultReferenceMesh() const { return HoudiniDefaultReferenceMesh; }; + // Returns the default Houdini reference mesh material + virtual TWeakObjectPtr GetHoudiniDefaultReferenceMeshMaterial() const { return HoudiniDefaultReferenceMeshMaterial; }; + + const HAPI_License GetLicenseType() const { return LicenseType; }; + + const bool IsLicenseIndie() const { return (LicenseType == HAPI_LICENSE_HOUDINI_ENGINE_INDIE || LicenseType == HAPI_LICENSE_HOUDINI_INDIE); }; + + // Session Sync ProcHandle accessor + FProcHandle GetHESSProcHandle() const { return HESS_ProcHandle; }; + void SetHESSProcHandle(const FProcHandle& InProcHandle) { HESS_ProcHandle = InProcHandle; }; + + void StartPDGCommandlet(); + + void StopPDGCommandlet(); + + bool IsPDGCommandletRunningOrConnected(); + + EHoudiniBGEOCommandletStatus GetPDGCommandletStatus(); + + FHoudiniEngineManager* GetHoudiniEngineManager() { return HoudiniEngineManager; } + + const FHoudiniEngineManager* GetHoudiniEngineManager() const { return HoudiniEngineManager; } + + void UnregisterPostEngineInitCallback(); + + private: + + // Singleton instance of Houdini Engine. + static FHoudiniEngine * HoudiniEngineInstance; + + // Location of libHAPI binary. + FString LibHAPILocation; + + // The Houdini Engine session. + HAPI_Session Session; + + // The Houdini Engine session's status + EHoudiniSessionStatus SessionStatus; + + // The type of HE license used by the current session + HAPI_License LicenseType; + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // Map of task statuses. + TMap TaskInfos; + + // Thread used to execute the scheduler. + FRunnableThread * HoudiniEngineSchedulerThread; + // Scheduler used to schedule HAPI instantiation and cook tasks. + FHoudiniEngineScheduler * HoudiniEngineScheduler; + + // Thread used to execute the manager. + FRunnableThread * HoudiniEngineManagerThread; + // Scheduler used to monitor and process Houdini Asset Components + FHoudiniEngineManager * HoudiniEngineManager; + + // Process Handle for session sync + FProcHandle HESS_ProcHandle; + + // Is set to true when mismatch between defined and running HAPI versions is detected. + //bool bHAPIVersionMismatch; + + // Global cooking flag, used to pause HEngine while using the editor + bool bEnableCookingGlobal; + // Counter of HACs that need to be refreshed when pause cooking + int32 UIRefreshCountWhenPauseCooking; + + // Indicates that the first attempt to create a session has been done + // This is to delay the first "automatic" session creation for the first cook + // or instantiation rather than when the module started. + bool bFirstSessionCreated; + + // Indicates if the current session is a SessionSync one + bool bEnableSessionSync; + + // If true and we're in SessionSync, keeps the assets on the plugin side synchronized with changes on the Houdini side. + //bool bSyncWithHoudiniCook; + + // If true and we're in SessionSync, use the Houdini Timeline time to cook assets. + bool bCookUsingHoudiniTime; + + // If true and we're in Session Sync, the Houdini and Unreal viewport will be synchronized. + bool bSyncViewport; + // If true and we're in Session Sync, the Houdini viewport will be synchronized to Unreal's. + bool bSyncHoudiniViewport; + // If true and we're in Session Sync, the Unreal viewport will be synchronized to Houdini's. + bool bSyncUnrealViewport; + + // Static mesh used for Houdini logo rendering. + TWeakObjectPtr HoudiniLogoStaticMesh; + + // Material used as default material. + TWeakObjectPtr HoudiniDefaultMaterial; + + // Material used as default template material. + TWeakObjectPtr HoudiniTemplateMaterial; + + // Houdini logo brush. + TSharedPtr HoudiniLogoBrush; + // Houdini logo brush. + TSharedPtr HoudiniEngineLogoBrush; + + // Static mesh used for default mesh reference + TWeakObjectPtr HoudiniDefaultReferenceMesh; + + // Material used for default mesh reference + TWeakObjectPtr HoudiniDefaultReferenceMeshMaterial; + + FDelegateHandle PostEngineInitCallback; + +#if WITH_EDITOR + /** Notification used by this component. **/ + TWeakPtr NotificationPtr; + + /** Persistent notification. **/ + bool bPersistentAllowExpiry; + TWeakPtr PersistentNotificationPtr; + float TimeSinceLastPersistentNotification; + + /** Used to delay notification updates for HAPI asynchronous work. **/ + double HapiNotificationStarted; +#endif }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp index 28deb4131..c6f308dfd 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp @@ -1,1693 +1,1724 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineManager.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniParameterTranslator.h" -#include "HoudiniPDGManager.h" -#include "HoudiniInputTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniHandleTranslator.h" -#include "HoudiniSplineTranslator.h" - -#include "Misc/MessageDialog.h" -#include "Misc/ScopedSlowTask.h" -#include "Containers/Ticker.h" -#include "HAL/IConsoleManager.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "EditorViewportClient.h" - #include "Kismet/KismetMathLibrary.h" - - //#include "UnrealEd.h" - #include "UnrealEdGlobals.h" - #include "Editor/UnrealEdEngine.h" - #include "IPackageAutoSaver.h" -#endif - -static TAutoConsoleVariable CVarHoudiniEngineTickTimeLimit( - TEXT("HoudiniEngine.TickTimeLimit"), - 1.0, - TEXT("Time limit after which HDA processing will be stopped, until the next tick of the Houdini Engine Manager.\n") - TEXT("<= 0.0: No Limit\n") - TEXT("1.0: Default\n") -); - -FHoudiniEngineManager::FHoudiniEngineManager() - : CurrentIndex(0) - , ComponentCount(0) - , bMustStopTicking(false) - , SyncedHoudiniViewportPivotPosition(FVector::ZeroVector) - , SyncedHoudiniViewportQuat(FQuat::Identity) - , SyncedHoudiniViewportOffset(0.0f) - , SyncedUnrealViewportPosition(FVector::ZeroVector) - , SyncedUnrealViewportRotation(FRotator::ZeroRotator) - , SyncedUnrealViewportLookatPosition(FVector::ZeroVector) - , ZeroOffsetValue(0.f) - , bOffsetZeroed(false) -{ - -} - -FHoudiniEngineManager::~FHoudiniEngineManager() -{ - PDGManager.StopBGEOCommandletAndEndpoint(); -} - -void -FHoudiniEngineManager::StartHoudiniTicking() -{ - // If we have no timer delegate spawned, spawn one. - if (!TickerHandle.IsValid() && GEditor) - { - // We use the ticker manager so we get ticked once per frame, no more. - TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FHoudiniEngineManager::Tick)); - - // Grab current time for delayed notification. - FHoudiniEngine::Get().SetHapiNotificationStartedTime(FPlatformTime::Seconds()); - } -} - -void -FHoudiniEngineManager::StopHoudiniTicking() -{ - if (TickerHandle.IsValid() && GEditor) - { - if (IsInGameThread()) - { - FTicker::GetCoreTicker().RemoveTicker(TickerHandle); - TickerHandle.Reset(); - - // Reset time for delayed notification. - FHoudiniEngine::Get().SetHapiNotificationStartedTime(0.0); - - bMustStopTicking = false; - } - else - { - // We can't stop ticking now as we're not in the game Thread, - // and accessing the timer would crash, indicate that we want to stop ticking asap - // This can happen when loosing a session due to a Houdini crash - bMustStopTicking = true; - } - } -} - -bool -FHoudiniEngineManager::Tick(float DeltaTime) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::Tick); - - EnableEditorAutoSave(nullptr); - - FHoudiniEngine::Get().TickPersistentNotification(DeltaTime); - - if (bMustStopTicking) - { - // Ticking should be stopped immediately - StopHoudiniTicking(); - return true; - } - - // Build a set of components that need to be processed - // 1 - selected HACs - // 2 - "Active" HACs - // 3 - The "next" inactive HAC - TArray ComponentsToProcess; - if (FHoudiniEngineRuntime::IsInitialized()) - { - FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); - - //FScopeLock ScopeLock(&CriticalSection); - ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); - - // Wrap around if needed - if (CurrentIndex >= ComponentCount) - CurrentIndex = 0; - - for (uint32 nIdx = 0; nIdx < ComponentCount; nIdx++) - { - UHoudiniAssetComponent * CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(nIdx); - if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) - { - // Invalid component, do not process - continue; - } - else if (CurrentComponent->IsPendingKill() - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) - { - // Component being deleted, do not process - continue; - } - - if (!CurrentComponent->IsFullyLoaded()) - { - // Let the component figure out whether it's fully loaded or not. - CurrentComponent->HoudiniEngineTick(); - if (!CurrentComponent->IsFullyLoaded()) - continue; // We need to wait some more. - } - - if (!CurrentComponent->IsValidComponent()) - { - // This component is no longer valid. Prevent it from being processed, and remove it. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); - continue; - } - - AActor* Owner = CurrentComponent->GetOwner(); - if (Owner && Owner->IsSelectedInEditor()) - { - // 1. Add selected HACs - // If the component's owner is selected, add it to the set - ComponentsToProcess.Add(CurrentComponent); - } - else if (CurrentComponent->GetAssetState() != EHoudiniAssetState::NeedInstantiation - && CurrentComponent->GetAssetState() != EHoudiniAssetState::None) - { - // 2. Add "Active" HACs, the only two non-active states are: - // NeedInstantiation (loaded, not instantiated in H yet, not modified) - // None (no processing currently) - ComponentsToProcess.Add(CurrentComponent); - } - else if(nIdx == CurrentIndex) - { - // 3. Add the "Current" HAC - ComponentsToProcess.Add(CurrentComponent); - } - - // Set the LastTickTime on the "current" HAC to 0 to ensure it's treated first - if (nIdx == CurrentIndex) - { - CurrentComponent->LastTickTime = 0.0; - } - } - - // Increment the current index for the next tick - CurrentIndex++; - } - - // Sort the components by last tick time - ComponentsToProcess.Sort([](const UHoudiniAssetComponent& A, const UHoudiniAssetComponent& B) { return A.LastTickTime < B.LastTickTime; }); - - // Time limit for processing - double dProcessTimeLimit = CVarHoudiniEngineTickTimeLimit.GetValueOnAnyThread(); - double dProcessStartTime = FPlatformTime::Seconds(); - - // Process all the components in the list - for(UHoudiniAssetComponent* CurrentComponent : ComponentsToProcess) - { - // Tick the notification manager - //FHoudiniEngine::Get().TickPersistentNotification(0.0f); - - double dNow = FPlatformTime::Seconds(); - if (dProcessTimeLimit > 0.0 - && dNow - dProcessStartTime > dProcessTimeLimit) - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); - break; - } - - // Update the tick time for this component - CurrentComponent->LastTickTime = dNow; - - // Handle template processing (for BP) first - // We don't want to the template component processing to trigger session creation - if (CurrentComponent->GetAssetState() == EHoudiniAssetState::ProcessTemplate) - { - if (CurrentComponent->IsTemplate() && !CurrentComponent->HasOpenEditor()) - { - // This component template no longer has an open editor and can be deregistered. - // TODO: Replace this polling mechanism with an "On Asset Closed" event if we - // can find one that actually works. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); - continue; - } - - if (CurrentComponent->NeedBlueprintStructureUpdate()) - { - CurrentComponent->OnBlueprintStructureModified(); - } - - if (CurrentComponent->NeedBlueprintUpdate()) - { - CurrentComponent->OnBlueprintModified(); - } - - if (FHoudiniEngine::Get().IsCookingEnabled()) - { - // Only process component template parameter updates when cooking is enabled. - if (CurrentComponent->NeedUpdateParameters() || CurrentComponent->NeedUpdateInputs()) - { - CurrentComponent->OnTemplateParametersChanged(); - } - } - - if (CurrentComponent->NeedOutputUpdate()) - { - // TODO: Transfer template output changes over to the preview instance. - } - continue; - } - - // Process the component - bool bKeepProcessing = true; - while (bKeepProcessing) - { - // Tick the notification manager - FHoudiniEngine::Get().TickPersistentNotification(0.0f); - - // See if we should start the default "first" session - AutoStartFirstSessionIfNeeded(CurrentComponent); - - EHoudiniAssetState PrevState = CurrentComponent->GetAssetState(); - ProcessComponent(CurrentComponent); - EHoudiniAssetState NewState = CurrentComponent->GetAssetState(); - - // In order to process components faster / with less ticks, - // we may continue processing the component if it ends up in certain states - switch (NewState) - { - case EHoudiniAssetState::PreInstantiation: - case EHoudiniAssetState::PreCook: - case EHoudiniAssetState::PostCook: - case EHoudiniAssetState::PreProcess: - case EHoudiniAssetState::Processing: - bKeepProcessing = true; - break; - - case EHoudiniAssetState::NeedInstantiation: - case EHoudiniAssetState::Instantiating: - case EHoudiniAssetState::Cooking: - case EHoudiniAssetState::None: - case EHoudiniAssetState::ProcessTemplate: - case EHoudiniAssetState::NeedRebuild: - case EHoudiniAssetState::NeedDelete: - case EHoudiniAssetState::Deleting: - bKeepProcessing = false; - break; - } - - // Safeguard, useless? - // Stop processing if the state hasn't changed - if (PrevState == NewState) - bKeepProcessing = false; - - dNow = FPlatformTime::Seconds(); - if (dProcessTimeLimit > 0.0 && dNow - dProcessStartTime > dProcessTimeLimit) - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); - break; - } - - // Update the tick time for this component - CurrentComponent->LastTickTime = dNow; - } - } - - // Handle Asset delete - if (FHoudiniEngineRuntime::IsInitialized()) - { - int32 PendingDeleteCount = FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteCount(); - for (int32 DeleteIdx = PendingDeleteCount - 1; DeleteIdx >= 0; DeleteIdx--) - { - HAPI_NodeId NodeIdToDelete = (HAPI_NodeId)FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteAt(DeleteIdx); - FGuid HapiDeletionGUID; - bool bShouldDeleteParent = FHoudiniEngineRuntime::Get().IsParentNodePendingDelete(NodeIdToDelete); - if (StartTaskAssetDelete(NodeIdToDelete, HapiDeletionGUID, bShouldDeleteParent)) - { - FHoudiniEngineRuntime::Get().RemoveNodeIdPendingDeleteAt(DeleteIdx); - if (bShouldDeleteParent) - FHoudiniEngineRuntime::Get().RemoveParentNodePendingDelete(NodeIdToDelete); - } - } - } - - // Update PDG Contexts and asset link if needed - PDGManager.Update(); - - // Session Sync Updates - if (FHoudiniEngine::Get().IsSessionSyncEnabled()) - { - // See if the session sync settings have changed on the houdini side, update ours if they did - FHoudiniEngine::Get().UpdateSessionSyncInfoFromHoudini(); -#if WITH_EDITOR - // Update the Houdini viewport from unreal if needed - if (FHoudiniEngine::Get().IsSyncViewportEnabled()) - { - // Sync the Houdini viewport to Unreal - if (!SyncHoudiniViewportToUnreal()) - { - // If the unreal viewport hasnt changed, - // See if we need to sync the Unreal viewport from Houdini's - SyncUnrealViewportToHoudini(); - } - } -#endif - } - else - { - // reset zero offset variables when session sync is off - if (ZeroOffsetValue != 0.f) - ZeroOffsetValue = 0.f; - - if (bOffsetZeroed) - bOffsetZeroed = false; - } - - // Tick the notification manager - FHoudiniEngine::Get().TickPersistentNotification(0.0f); - - return true; -} - -void -FHoudiniEngineManager::AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC) -{ - // See if we should start the default "first" session - if (FHoudiniEngine::Get().GetSession() - || FHoudiniEngine::Get().GetFirstSessionCreated() - || !InCurrentHAC) - return; - - // Only try to start the default session if we have an "active" HAC - if (InCurrentHAC->GetAssetState() == EHoudiniAssetState::PreInstantiation - || InCurrentHAC->GetAssetState() == EHoudiniAssetState::Instantiating - || InCurrentHAC->GetAssetState() == EHoudiniAssetState::PreCook - || InCurrentHAC->GetAssetState() == EHoudiniAssetState::Cooking) - { - FString StatusText = TEXT("Initializing Houdini Engine..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // We want to yield for a bit. - //FPlatformProcess::Sleep(0.5f); - - // Indicates that we've tried to start the session once no matter if it failed or succeed - FHoudiniEngine::Get().SetFirstSessionCreated(true); - - // Attempt to restart the session - if (!FHoudiniEngine::Get().RestartSession()) - { - // We failed to start the session - // Stop ticking until it's manually restarted - StopHoudiniTicking(); - - StatusText = TEXT("Houdini Engine failed to initialize."); - } - else - { - StatusText = TEXT("Houdini Engine successfully initialized."); - } - - // Finish the notification and display the results - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - } -} - -void -FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::ProcessComponent); - - if (!HAC || HAC->IsPendingKill()) - return; - - // No need to process component not tied to an asset - if (!HAC->GetHoudiniAsset()) - return; - - // If cooking is paused, stay in the current state until cooking's resumed - if (!FHoudiniEngine::Get().IsCookingEnabled()) - { - // We can only handle output updates - if (HAC->GetAssetState() == EHoudiniAssetState::None && HAC->NeedOutputUpdate()) - { - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Refresh UI when pause cooking - if (!FHoudiniEngine::Get().HasUIFinishRefreshingWhenPausingCooking()) - { - // Trigger a details panel update if the Houdini asset actor is selected - if (HAC->IsOwnerSelected()) - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // Finished refreshing UI of one HDA. - FHoudiniEngine::Get().RefreshUIDisplayedWhenPauseCooking(); - } - - // Prevent any other state change to happen - return; - } - - switch (HAC->GetAssetState()) - { - case EHoudiniAssetState::NeedInstantiation: - { - // Do nothing unless the HAC has been updated - if (HAC->NeedUpdate()) - { - HAC->OnPrePreInstantiation(); - HAC->bForceNeedUpdate = false; - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::PreInstantiation; - } - else if (HAC->NeedOutputUpdate()) - { - // Output updates do not recquire the HDA to be instantiated - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Update world input if we have any - FHoudiniInputTranslator::UpdateWorldInputs(HAC); - - break; - } - - case EHoudiniAssetState::PreInstantiation: - { - // Only proceed forward if we don't need to wait for our input HoudiniAssets to finish cooking/instantiating - if (HAC->NeedsToWaitForInputHoudiniAssets()) - break; - - FGuid TaskGuid; - UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (StartTaskAssetInstantiation(HoudiniAsset, HAC->GetDisplayName(), TaskGuid)) - { - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::Instantiating; - - // Update the Task GUID - HAC->HapiGUID = TaskGuid; - } - else - { - // If we couldnt instantiate the asset - // Change the state back to NeedInstantiating - HAC->AssetState = EHoudiniAssetState::NeedInstantiation; - } - break; - } - - case EHoudiniAssetState::Instantiating: - { - EHoudiniAssetState NewState = EHoudiniAssetState::Instantiating; - if (UpdateInstantiating(HAC, NewState)) - { - // We need to update the HAC's state - HAC->AssetState = NewState; - EnableEditorAutoSave(HAC); - } - else - { - DisableEditorAutoSave(HAC); - } - break; - } - - case EHoudiniAssetState::PreCook: - { - // Only proceed forward if we don't need to wait for our input - // HoudiniAssets to finish cooking/instantiating - if (HAC->NeedsToWaitForInputHoudiniAssets()) - break; - - HAC->OnPrePreCook(); - // Update all the HAPI nodes, parameters, inputs etc... - PreCook(HAC); - HAC->OnPostPreCook(); - - // Create a Cooking task only if necessary - bool bCookStarted = false; - if (IsCookingEnabledForHoudiniAsset(HAC)) - { - FGuid TaskGUID = HAC->GetHapiGUID(); - if ( StartTaskAssetCooking(HAC->GetAssetId(), HAC->GetDisplayName(), TaskGUID) ) - { - // Updates the HAC's state - HAC->AssetState = EHoudiniAssetState::Cooking; - HAC->HapiGUID = TaskGUID; - bCookStarted = true; - } - } - - if(!bCookStarted) - { - // Just refresh editor properties? - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // TODO: Check! update state? - HAC->AssetState = EHoudiniAssetState::None; - } - break; - } - - case EHoudiniAssetState::Cooking: - { - EHoudiniAssetState NewState = EHoudiniAssetState::Cooking; - bool state = UpdateCooking(HAC, NewState); - if (state) - { - // We need to update the HAC's state - HAC->AssetState = NewState; - EnableEditorAutoSave(HAC); - } - else - { - DisableEditorAutoSave(HAC); - } - break; - } - - case EHoudiniAssetState::PostCook: - { - // Handle PostCook - EHoudiniAssetState NewState = EHoudiniAssetState::None; - bool bSuccess = HAC->bLastCookSuccess; - HAC->OnPreOutputProcessing(); - if (PostCook(HAC, bSuccess, HAC->GetAssetId())) - { - // Cook was successful, process the results - NewState = EHoudiniAssetState::PreProcess; - } - else - { - // Cook failed, skip output processing - NewState = EHoudiniAssetState::None; - } - HAC->AssetState = NewState; - break; - } - - case EHoudiniAssetState::PreProcess: - { - StartTaskAssetProcess(HAC); - break; - } - - case EHoudiniAssetState::Processing: - { - UpdateProcess(HAC); - - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - HAC->SetAssetCookCount(CookCount); - - HAC->OnPostOutputProcessing(); - FHoudiniEngineUtils::UpdateBlueprintEditor(HAC); - break; - } - - case EHoudiniAssetState::None: - { - // Do nothing unless the HAC has been updated - if (HAC->NeedUpdate()) - { - HAC->bForceNeedUpdate = false; - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::PreCook; - } - else if (HAC->NeedTransformUpdate()) - { - FHoudiniEngineUtils::UploadHACTransform(HAC); - } - else if (HAC->NeedOutputUpdate()) - { - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Update world inputs if we have any - FHoudiniInputTranslator::UpdateWorldInputs(HAC); - - // See if we need to get an update from Session Sync - if(FHoudiniEngine::Get().IsSessionSyncEnabled() - && FHoudiniEngine::Get().IsSyncWithHoudiniCookEnabled() - && HAC->AssetState == EHoudiniAssetState::None) - { - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - if (CookCount >= 0 && CookCount != HAC->GetAssetCookCount()) - { - // The cook count has changed on the Houdini side, - // this indicates that the user has changed something in Houdini so we need to trigger an update - HAC->AssetState = EHoudiniAssetState::PreCook; - } - } - break; - } - - case EHoudiniAssetState::NeedRebuild: - { - StartTaskAssetRebuild(HAC->AssetId, HAC->HapiGUID); - - HAC->MarkAsNeedCook(); - HAC->AssetState = EHoudiniAssetState::PreInstantiation; - break; - } - - case EHoudiniAssetState::NeedDelete: - { - FGuid HapiDeletionGUID; - StartTaskAssetDelete(HAC->GetAssetId(), HapiDeletionGUID, true); - //HAC->AssetId = -1; - - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::Deleting; - break; - } - - case EHoudiniAssetState::Deleting: - { - break; - } - } -} - - - -bool -FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID) -{ - // Make sure we have a valid session before attempting anything - if (!FHoudiniEngine::Get().GetSession()) - return false; - - OutTaskGUID.Invalidate(); - - // Load the HDA file - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - null or invalid Houdini Asset.")); - return false; - } - - HAPI_AssetLibraryId AssetLibraryId = -1; - if (!FHoudiniEngineUtils::LoadHoudiniAsset(HoudiniAsset, AssetLibraryId) ) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - could not load Houdini Asset.")); - return false; - } - - // Handle hda files that contain multiple assets - TArray< HAPI_StringHandle > AssetNames; - if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - unable to retrieve asset names.")); - return false; - } - - // By default, assume we want to load the first Asset - HAPI_StringHandle PickedAssetName = AssetNames[0]; - -#if WITH_EDITOR - // Should we show the multi asset dialog? - bool bShowMultiAssetDialog = false; - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && AssetNames.Num() > 1) - bShowMultiAssetDialog = HoudiniRuntimeSettings->bShowMultiAssetDialog; - - // TODO: Add multi selection dialog - if (bShowMultiAssetDialog ) - { - // TODO: Implement - FHoudiniEngineUtils::OpenSubassetSelectionWindow(AssetNames, PickedAssetName); - } -#endif - - // Give the HAC a new GUID to identify this request. - OutTaskGUID = FGuid::NewGuid(); - - // Create a new instantiation task - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetInstantiation, OutTaskGUID); - Task.Asset = HoudiniAsset; - Task.ActorName = DisplayName; - //Task.bLoadedComponent = bLocalLoadedComponent; - Task.AssetLibraryId = AssetLibraryId; - Task.AssetHapiName = PickedAssetName; - - // Add the task to the stack - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState ) -{ - check(HAC); - - // Will return true if the asset's state need to be updated - NewState = HAC->GetAssetState(); - bool bUpdateState = false; - - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - // Get the current task's progress - FHoudiniEngineTaskInfo TaskInfo; - if ( !UpdateTaskStatus(HAC->HapiGUID, TaskInfo) - || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetInstantiation) - { - // Couldnt get a valid task info - HOUDINI_LOG_ERROR(TEXT(" %s Failed to instantiate - invalid task"), *DisplayName); - NewState = EHoudiniAssetState::NeedInstantiation; - bUpdateState = true; - return bUpdateState; - } - - bool bSuccess = false; - bool bFinished = false; - switch (TaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Success: - { - bSuccess = true; - bFinished = true; - break; - } - - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::FinishedWithError: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - bSuccess = false; - bFinished = true; - break; - } - - case EHoudiniEngineTaskState::None: - case EHoudiniEngineTaskState::Working: - { - bFinished = false; - break; - } - } - - if ( !bFinished ) - { - // Task is still in progress, nothing to do for now - return false; - } - - if ( bSuccess && (TaskInfo.AssetId < 0) ) - { - // Task finished successfully but we received an invalid asset ID, error out - HOUDINI_LOG_ERROR(TEXT(" %s Finished Instantiation but received invalid asset id."), *DisplayName); - bSuccess = false; - } - - if ( bSuccess ) - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedInstantiation."), *DisplayName); - - // Set the new Asset ID - HAC->AssetId = TaskInfo.AssetId; - - // Assign a unique name to the actor if needed - FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(HAC); - - // TODO: Create default preset buffer. - /*TArray< char > DefaultPresetBuffer; - if (!FHoudiniEngineUtils::GetAssetPreset(TaskInfo.AssetId, DefaultPresetBuffer)) - DefaultPresetBuffer.Empty();*/ - - // Reset the cook counter. - HAC->SetAssetCookCount(0); - - // If necessary, set asset transform. - if (HAC->bUploadTransformsToHoudiniEngine) - { - // Retrieve the current component-to-world transform for this component. - if (!FHoudiniEngineUtils::HapiSetAssetTransform(TaskInfo.AssetId, HAC->GetComponentTransform())) - HOUDINI_LOG_MESSAGE(TEXT("Failed to upload the initial Transform back to HAPI.")); - } - - // Only initalize the PDG Asset Link if this Asset is a PDG Asset - // InitializePDGAssetLink may take a while to execute on non PDG HDA, - // So we want to avoid calling it if possible - if (FHoudiniPDGManager::IsPDGAsset(HAC->AssetId)) - { - PDGManager.InitializePDGAssetLink(HAC); - } - - // Update the HAC's state - NewState = EHoudiniAssetState::PreCook; - return true; - } - else - { - 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); - - FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); - - FMessageDialog::Debugf(FText::FromString(WarningMessage), &WarningTitleText); - } - - // Reset the cook counter. - HAC->SetAssetCookCount(0); - - // Make sure the asset ID is invalid - HAC->AssetId = -1; - - // Update the HAC's state - HAC->AssetState = EHoudiniAssetState::NeedInstantiation; - //HAC->AssetStateResult = EHoudiniAssetStateResult::Success; - - return true; - } -} - -bool -FHoudiniEngineManager::StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID) -{ - // Make sure we have a valid session before attempting anything - if (!FHoudiniEngine::Get().GetSession()) - return false; - - // Check we have a valid AssetId - if (AssetId < 0) - return false; - - // Check this HAC doesn't already have a running task - if (OutTaskGUID.IsValid()) - return false; - - // Generate a GUID for our new task. - OutTaskGUID = FGuid::NewGuid(); - - // Add a new cook task - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetCooking, OutTaskGUID); - Task.ActorName = DisplayName; - Task.AssetId = AssetId; - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState) -{ - check(HAC); - - // Will return true if the asset's state need to be updated - NewState = HAC->GetAssetState(); - bool bUpdateState = false; - - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - // Get the current task's progress - FHoudiniEngineTaskInfo TaskInfo; - if (!UpdateTaskStatus(HAC->HapiGUID, TaskInfo) - || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetCooking) - { - // Couldnt get a valid task info - HOUDINI_LOG_ERROR(TEXT(" %s Failed to cook - invalid task"), *DisplayName); - NewState = EHoudiniAssetState::None; - bUpdateState = true; - return bUpdateState; - } - - bool bSuccess = false; - switch (TaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Success: - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking."), *DisplayName); - bSuccess = true; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::FinishedWithError: - { - // We finished with cook error, will still try to process the results - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with errors - will try to process the available results."), *DisplayName); - bSuccess = true; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with fatal errors - aborting."), *DisplayName); - bSuccess = false; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::None: - case EHoudiniEngineTaskState::Working: - { - // Task is still in progress, nothing to do for now - // return false so we do not update the state - bUpdateState = false; - } - break; - } - - // If the task is still in progress, return now - if (!bUpdateState) - return false; - - // Handle PostCook - NewState = EHoudiniAssetState::PostCook; - HAC->bLastCookSuccess = bSuccess; - - //if (PostCook(HAC, bSuccess, TaskInfo.AssetId)) - //{ - // // Cook was successfull, process the results - // NewState = EHoudiniAssetState::PreProcess; - // HAC->BroadcastCookFinished(); - //} - //else - //{ - // // Cook failed, skip output processing - // NewState = EHoudiniAssetState::None; - //} - - return true; -} - -bool -FHoudiniEngineManager::PreCook(UHoudiniAssetComponent* HAC) -{ - // Handle duplicated HAC - // We need to clean/duplicate some of the HAC's output data manually here - if (HAC->HasBeenDuplicated()) - { - HAC->UpdatePostDuplicate(); - } - - FHoudiniParameterTranslator::OnPreCookParameters(HAC); - - // Upload the changed/parameters back to HAPI - // If cooking is disabled, we still try to upload parameters - if (HAC->HasBeenLoaded()) - { - // Handle loaded parameters - FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); - - // Handle loaded inputs - FHoudiniInputTranslator::UpdateLoadedInputs(HAC); - - // Handle loaded outputs - FHoudiniOutputTranslator::UpdateLoadedOutputs(HAC); - - // TODO: Handle loaded curve - // TODO: Handle editable node - // TODO: Restore parameter preset data - } - - // Try to upload changed parameters - FHoudiniParameterTranslator::UploadChangedParameters(HAC); - - // Try to upload changed inputs - FHoudiniInputTranslator::UploadChangedInputs(HAC); - - // Try to upload changed editable nodes - FHoudiniOutputTranslator::UploadChangedEditableOutput(HAC, false); - - // Upload the asset's transform if needed - if (HAC->NeedTransformUpdate()) - FHoudiniEngineUtils::UploadHACTransform(HAC); - - HAC->ClearRefineMeshesTimer(); - - return true; -} - -bool -FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId) -{ - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - bool bCookSuccess = bSuccess; - if (bCookSuccess && (TaskAssetId < 0)) - { - // Task finished successfully but we received an invalid asset ID, error out - HOUDINI_LOG_ERROR(TEXT(" %s received an invalid asset id - aborting."), *DisplayName); - bCookSuccess = false; - } - - // Update the asset cook count using the node infos - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - HAC->SetAssetCookCount(CookCount); - /* - if(CookCount >= 0 ) - HAC->SetAssetCookCount(CookCount); - else - HAC->SetAssetCookCount(HAC->GetAssetCookCount()+1); - */ - - bool bNeedsToTriggerViewportUpdate = false; - if (bCookSuccess) - { - FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Processing outputs..."), false); - - // Set new asset id. - HAC->AssetId = TaskAssetId; - - FHoudiniParameterTranslator::UpdateParameters(HAC); - - FHoudiniInputTranslator::UpdateInputs(HAC); - - bool bHasHoudiniStaticMeshOutput = false; - bool ForceUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); - FHoudiniOutputTranslator::UpdateOutputs(HAC, ForceUpdate, bHasHoudiniStaticMeshOutput); - HAC->SetNoProxyMeshNextCookRequested(false); - - // Handles have to be updated after parameters - FHoudiniHandleTranslator::UpdateHandles(HAC); - - // Clear the HasBeenLoaded flag - if (HAC->HasBeenLoaded()) - { - HAC->SetHasBeenLoaded(false); - } - - // Clear the HasBeenDuplicated flag - if (HAC->HasBeenDuplicated()) - { - HAC->SetHasBeenDuplicated(false); - } - - // Update rendering information. - HAC->UpdateRenderingInformation(); - - // Since we have new asset, we need to update bounds. - HAC->UpdateBounds(); - - FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Finished processing outputs"), true); - - // Trigger a details panel update - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // If any outputs have HoudiniStaticMeshes, and if timer based refinement is enabled on the HAC, - // set the RefineMeshesTimer and ensure BuildStaticMeshesForAllHoudiniStaticMeshes is bound to - // the RefineMeshesTimerFired delegate of the HAC - if (bHasHoudiniStaticMeshOutput && HAC->IsProxyStaticMeshRefinementByTimerEnabled()) - { - if (!HAC->GetOnRefineMeshesTimerDelegate().IsBoundToObject(this)) - HAC->GetOnRefineMeshesTimerDelegate().AddRaw(this, &FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes); - HAC->SetRefineMeshesTimer(); - } - - if (bHasHoudiniStaticMeshOutput) - bNeedsToTriggerViewportUpdate = true; - - UHoudiniAssetComponent::FOnPostCookDelegate& OnPostCookDelegate = HAC->GetOnPostCookDelegate(); - if (OnPostCookDelegate.IsBound()) - { - OnPostCookDelegate.Execute(HAC, true); - } - - UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); - if (OnPostCookBakeDelegate.IsBound()) - { - OnPostCookBakeDelegate.Execute(HAC); - if (!HAC->IsBakeAfterNextCookEnabled()) - OnPostCookBakeDelegate.Unbind(); - } - } - else - { - // TODO: Create parameters inputs and handles inputs. - //CreateParameters(); - //CreateInputs(); - //CreateHandles(); - - UHoudiniAssetComponent::FOnPostCookDelegate& OnPostCookDelegate = HAC->GetOnPostCookDelegate(); - if (OnPostCookDelegate.IsBound()) - { - OnPostCookDelegate.Execute(HAC, false); - } - - // Clear the bake after cook delegate if - UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); - if (OnPostCookBakeDelegate.IsBound() && !HAC->IsBakeAfterNextCookEnabled()) - { - OnPostCookBakeDelegate.Unbind(); - // Notify the user that the bake failed since the cook failed. - FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Cook failed, therefore the bake also failed..."), true); - } - } - - if (HAC->InputPresets.Num() > 0) - { - HAC->ApplyInputPresets(); - } - - // If we have downstream HDAs, we need to tell them we're done cooking - HAC->NotifyCookedToDownstreamAssets(); - - // Notify the PDG manager that the HDA is done cooking - FHoudiniPDGManager::NotifyAssetCooked(HAC->PDGAssetLink, bSuccess); - - if (bNeedsToTriggerViewportUpdate && GEditor) - { - // We need to manually update the vieport with HoudiniMeshProxies - // if not, modification made in H with the two way debugger wont be visible in Unreal until the vieports gets focus - GEditor->RedrawAllViewports(false); - } - - // Clear the rebuild/recook flags - HAC->SetRecookRequested(false); - HAC->SetRebuildRequested(false); - - //HAC->SyncToBlueprintGeneratedClass(); - - return bCookSuccess; -} - -bool -FHoudiniEngineManager::StartTaskAssetProcess(UHoudiniAssetComponent* HAC) -{ - HAC->AssetState = EHoudiniAssetState::Processing; - - return true; -} - -bool -FHoudiniEngineManager::UpdateProcess(UHoudiniAssetComponent* HAC) -{ - HAC->AssetState = EHoudiniAssetState::None; - - return true; -} - -bool -FHoudiniEngineManager::StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID) -{ - // Check this HAC doesn't already have a running task - if (OutTaskGUID.IsValid()) - return false; - - if (InAssetId >= 0) - { - /* TODO: Handle Asset Preset - if (!FHoudiniEngineUtils::GetAssetPreset(AssetId, PresetBuffer)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to get the asset's preset, rebuilt asset may have lost its parameters.")); - } - */ - // Delete the asset - if (!StartTaskAssetDelete(InAssetId, OutTaskGUID, true)) - { - return false; - } - } - - // Create a new task GUID for this asset - OutTaskGUID = FGuid::NewGuid(); - - return true; -} - -bool -FHoudiniEngineManager::StartTaskAssetDelete(const HAPI_NodeId& InNodeId, FGuid& OutTaskGUID, bool bShouldDeleteParent) -{ - if (InNodeId < 0) - return false; - - // Get the Asset's NodeInfo - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, &AssetNodeInfo)); - - HAPI_NodeId OBJNodeToDelete = InNodeId; - if (AssetNodeInfo.type == HAPI_NODETYPE_SOP) - { - // For SOP Asset, we want to delete their parent's OBJ node - if (bShouldDeleteParent) - { - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(OBJNodeToDelete); - OBJNodeToDelete = ParentId != -1 ? ParentId : OBJNodeToDelete; - } - } - - // Generate GUID for our new task. - OutTaskGUID = FGuid::NewGuid(); - - // Create asset deletion task object and submit it for processing. - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetDeletion, OutTaskGUID); - Task.AssetId = OBJNodeToDelete; - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo) -{ - if (!OutTaskGUID.IsValid()) - return false; - - if (!FHoudiniEngine::Get().RetrieveTaskInfo(OutTaskGUID, OutTaskInfo)) - { - // Task information does not exist - OutTaskGUID.Invalidate(); - return false; - } - - // Check whether we want to display Slate cooking and instantiation notifications. - bool bDisplaySlateCookingNotifications = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (EHoudiniEngineTaskState::None != OutTaskInfo.TaskState && bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); - } - - switch (OutTaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::Success: - case EHoudiniEngineTaskState::FinishedWithError: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - // If the current task is finished - // Terminate the slate notification if they exist and delete/invalidate the task - if (bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, true); - } - - FHoudiniEngine::Get().RemoveTaskInfo(OutTaskGUID); - OutTaskGUID.Invalidate(); - } - break; - - case EHoudiniEngineTaskState::Working: - { - // The current task is still running, simply update the current notification - if (bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); - } - } - break; - - case EHoudiniEngineTaskState::None: - default: - { - break; - } - } - - return true; -} - -bool -FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC) -{ - bool bManualRecook = false; - bool bComponentEnable = false; - if (HAC && !HAC->IsPendingKill()) - { - bManualRecook = HAC->HasRecookBeenRequested(); - bComponentEnable = HAC->IsCookingEnabled(); - } - - if (bManualRecook) - return true; - - if (bComponentEnable && FHoudiniEngine::Get().IsCookingEnabled()) - return true; - - return false; -} - -void -FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes called with HAC=nullptr")); - return; - } - -#if WITH_EDITOR - AActor *Owner = HAC->GetOwner(); - FString Name = Owner ? Owner->GetName() : HAC->GetName(); - - FScopedSlowTask Progress(2.0f, FText::FromString(FString::Printf(TEXT("Refining Proxy Mesh to Static Mesh on %s"), *Name))); - Progress.MakeDialog(); - Progress.EnterProgressFrame(1.0f); -#endif - - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); - -#if WITH_EDITOR - Progress.EnterProgressFrame(1.0f); -#endif -} - - -/* Unreal's viewport representation rules: - Viewport location is the actual camera location; - Lookat position is always right in front of the camera, which means the camera is looking at; - The rotator rotates the forward vector to a direction & orientation, and this dir and orientation is the camera's; - The identity direction and orientation of the camera is facing positive X-axis. -*/ - -/* Hapi's viewport representation rules: - The camera is located at a point on the sphere, which the center is the pivot position and the radius is offset; - Quat determines the location on the sphere and which direction the camera is facing towards, as well as the camera orientation; - The identity location, direction and orientation of the camera is facing positive Z-axis (in Hapi coords); -*/ - - -bool -FHoudiniEngineManager::SyncHoudiniViewportToUnreal() -{ - if (!FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled()) - return false; - -#if WITH_EDITOR - // Get the editor viewport LookAt position to spawn the new objects - if (!GEditor || !GEditor->GetActiveViewport()) - return false; - - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (!ViewportClient) - return false; - - // Get the current UE viewport location, lookat position, and rotation - FVector UnrealViewportPosition = ViewportClient->GetViewLocation(); - FRotator UnrealViewportRotation = ViewportClient->GetViewRotation(); - FVector UnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - /* Check if the Unreal viewport has changed */ - if (UnrealViewportPosition.Equals(SyncedUnrealViewportPosition) && - UnrealViewportRotation.Equals(SyncedUnrealViewportRotation) && - UnrealViewportLookatPosition.Equals(SyncedUnrealViewportLookatPosition)) - { - // No need to sync if the viewport camera hasn't changed - return false; - } - - /* Calculate Hapi Quaternion */ - // Initialize Hapi Quat with Unreal Quat. - // Note that rotations are in general non-commutative *** - FQuat HapiQuat = UnrealViewportRotation.Quaternion(); - - // We're in orbit mode, forward vector is Y-axis - if (ViewportClient->bUsingOrbitCamera) - { - // The forward vector is Y-negative direction when on orbiting mode - HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 180.f)); - - // rotations around X and Y axis are reversed - float TempX = HapiQuat.X; - HapiQuat.X = HapiQuat.Y; - HapiQuat.Y = TempX; - HapiQuat.W = -HapiQuat.W; - - } - // We're not in orbiting mode, forward vector is X-axis - else - { - // Rotate the Quat arount Z-axis by 90 degree. - HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 90.f)); - } - - - /* Update Hapi H_View */ - // Note: There are infinte number of H_View representation for current viewport - // Each choice of pivot point determines an equivalent representation. - // We just find an equivalent when the pivot position is the view position, and offset is 0 - - HAPI_Viewport H_View; - H_View.position[0] = UnrealViewportPosition.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - H_View.position[1] = UnrealViewportPosition.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - H_View.position[2] = UnrealViewportPosition.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Set HAPI_Offset always 0 when syncing Houdini to UE viewport - H_View.offset = 0.f; - - H_View.rotationQuaternion[0] = -HapiQuat.X; - H_View.rotationQuaternion[1] = -HapiQuat.Z; - H_View.rotationQuaternion[2] = -HapiQuat.Y; - H_View.rotationQuaternion[3] = HapiQuat.W; - - FHoudiniApi::SetViewport(FHoudiniEngine::Get().GetSession(), &H_View); - - /* Update the Synced viewport values - We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. - Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. */ - - // We need to get the H_Viewport again, since it is possible the value is a different equivalence of what we set. - HAPI_Viewport Cur_H_View; - FHoudiniApi::GetViewport( - FHoudiniEngine::Get().GetSession(), &Cur_H_View); - - // Hapi values are in Houdini coordinate and scale - SyncedHoudiniViewportPivotPosition = FVector(Cur_H_View.position[0], Cur_H_View.position[1], Cur_H_View.position[2]); - SyncedHoudiniViewportQuat = FQuat(Cur_H_View.rotationQuaternion[0], Cur_H_View.rotationQuaternion[1], Cur_H_View.rotationQuaternion[2], Cur_H_View.rotationQuaternion[3]); - SyncedHoudiniViewportOffset = Cur_H_View.offset; - - SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); - SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); - SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - // When sync Houdini to UE, we set offset to be 0. - // So we need to zero out offset for the next time syncing UE to Houdini - bOffsetZeroed = true; - - return true; -#endif - - return false; -} - - -bool -FHoudiniEngineManager::SyncUnrealViewportToHoudini() -{ - if (!FHoudiniEngine::Get().IsSyncUnrealViewportEnabled()) - return false; - -#if WITH_EDITOR - // Get the editor viewport LookAt position to spawn the new objects - if (!GEditor || !GEditor->GetActiveViewport()) - return false; - - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (!ViewportClient) - return false; - - // Get the current HAPI_Viewport - HAPI_Viewport H_View; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetViewport( - FHoudiniEngine::Get().GetSession(), &H_View)) - { - return false; - } - - - // Get Hapi viewport's PivotPosition, Offset and Quat, w.r.t Houdini's coordinate and scale. - FVector HapiViewportPivotPosition = FVector(H_View.position[0], H_View.position[1], H_View.position[2]); - float HapiViewportOffset = H_View.offset; - FQuat HapiViewportQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[1], H_View.rotationQuaternion[2], H_View.rotationQuaternion[3]); - - /* Check if the Houdini viewport has changed */ - if (SyncedHoudiniViewportPivotPosition.Equals(HapiViewportPivotPosition) && - SyncedHoudiniViewportQuat.Equals(HapiViewportQuat) && - SyncedHoudiniViewportOffset == HapiViewportOffset) - { - // Houdini viewport hasn't changed, nothing to do - return false; - } - - // Set zero value of offset when needed - if (bOffsetZeroed) - { - ZeroOffsetValue = H_View.offset; - bOffsetZeroed = false; - } - - - /* Translate the hapi camera transfrom to Unreal's representation system */ - - // Get pivot point in UE's coordinate and scale - FVector UnrealViewportPivotPosition = FVector(H_View.position[0], H_View.position[2], H_View.position[1]) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // HAPI bug? After we set the H_View, offset becomes a lot bigger when move the viewport just a little bit in Houdini. - // But the pivot point doesn't change. Which caused UE viewport jumping far suddenly. - // So we get rid of this problem by setting the first HAPI_offset value after syncing Houdini viewport as the base. - - // Get offset in UE's scale. The actual offset after 'zero out' - float UnrealOffset = (H_View.offset - ZeroOffsetValue) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - /* Calculate Quaternion in UE */ - // Rotate the resulting Quat around Z-axis by -90 degree. - // Note that rotation is in general non-commutative *** - FQuat UnrealQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[2], H_View.rotationQuaternion[1], -H_View.rotationQuaternion[3]); - UnrealQuat = UnrealQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)); - - FVector UnrealBaseVector(1.f, 0.f, 0.f); // Forward vector in Unreal viewport - - /* Get UE viewport location*/ - FVector UnrealViewPosition = - UnrealQuat.RotateVector(UnrealBaseVector) * UnrealOffset + UnrealViewportPivotPosition; - - /* Set the viewport's value */ - ViewportClient->SetViewLocation(UnrealViewPosition); - ViewportClient->SetViewRotation(UnrealQuat.Rotator()); - - // Invalidate the viewport - ViewportClient->Invalidate(); - - /* Update the synced viewport values */ - // We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. - // Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. - - // Hapi values are in Houdini coordinate and scale - SyncedHoudiniViewportPivotPosition = HapiViewportPivotPosition; - SyncedHoudiniViewportQuat = HapiViewportQuat; - SyncedHoudiniViewportOffset = HapiViewportOffset; - - SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); - SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); - SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - return true; -#endif - - return false; -} - - -void -FHoudiniEngineManager::DisableEditorAutoSave(const UHoudiniAssetComponent* HAC) -{ -#if WITH_EDITOR - if (!HAC || HAC->IsPendingKill()) - return; - - if (!GUnrealEd) - return; - - if (DisableAutoSavingHACs.Contains(HAC)) - return; - // Add the HAC to the set - DisableAutoSavingHACs.Add(HAC); - - // Return if auto-saving has been disabled by some other HACs. - if (DisableAutoSavingHACs.Num() > 1) - return; - - // Disable auto-saving by setting min time till auto-save to max float value - IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); - AutoSaver.ForceMinimumTimeTillAutoSave(TNumericLimits::Max()); -#endif -} - - -void -FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = nullptr) -{ -#if WITH_EDITOR - if (!GUnrealEd) - return; - - if (!HAC) - { - // When HAC is nullptr, go through all HACs in the set, - // remove it if the HAC has been deleted. - if (DisableAutoSavingHACs.Num() <= 0) - return; - - TSet ValidComponents; - for (auto& CurHAC : DisableAutoSavingHACs) - { - if (CurHAC && !CurHAC->IsPendingKill()) - { - ValidComponents.Add(CurHAC); - } - } - DisableAutoSavingHACs = MoveTemp(ValidComponents); - } - else - { - // Otherwise, remove the HAC from the set - if (DisableAutoSavingHACs.Contains(HAC)) - DisableAutoSavingHACs.Remove(HAC); - } - - if (DisableAutoSavingHACs.Num() > 0) - return; - - // When no HAC disables cooking, reset min time till auto-save to default value, then reset the timer - IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); - AutoSaver.ResetAutoSaveTimer(); -#endif -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineManager.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniParameterTranslator.h" +#include "HoudiniPDGManager.h" +#include "HoudiniInputTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniHandleTranslator.h" +#include "HoudiniSplineTranslator.h" + +#include "Misc/MessageDialog.h" +#include "Misc/ScopedSlowTask.h" +#include "Containers/Ticker.h" +#include "HAL/IConsoleManager.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "EditorViewportClient.h" + #include "Kismet/KismetMathLibrary.h" + + //#include "UnrealEd.h" + #include "UnrealEdGlobals.h" + #include "Editor/UnrealEdEngine.h" + #include "IPackageAutoSaver.h" +#endif + +static TAutoConsoleVariable CVarHoudiniEngineTickTimeLimit( + TEXT("HoudiniEngine.TickTimeLimit"), + 1.0, + TEXT("Time limit after which HDA processing will be stopped, until the next tick of the Houdini Engine Manager.\n") + TEXT("<= 0.0: No Limit\n") + TEXT("1.0: Default\n") +); + +FHoudiniEngineManager::FHoudiniEngineManager() + : CurrentIndex(0) + , ComponentCount(0) + , bMustStopTicking(false) + , SyncedHoudiniViewportPivotPosition(FVector::ZeroVector) + , SyncedHoudiniViewportQuat(FQuat::Identity) + , SyncedHoudiniViewportOffset(0.0f) + , SyncedUnrealViewportPosition(FVector::ZeroVector) + , SyncedUnrealViewportRotation(FRotator::ZeroRotator) + , SyncedUnrealViewportLookatPosition(FVector::ZeroVector) + , ZeroOffsetValue(0.f) + , bOffsetZeroed(false) +{ + +} + +FHoudiniEngineManager::~FHoudiniEngineManager() +{ + PDGManager.StopBGEOCommandletAndEndpoint(); +} + +void +FHoudiniEngineManager::StartHoudiniTicking() +{ + // If we have no timer delegate spawned, spawn one. + if (!TickerHandle.IsValid() && GEditor) + { + // We use the ticker manager so we get ticked once per frame, no more. + TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FHoudiniEngineManager::Tick)); + + // Grab current time for delayed notification. + FHoudiniEngine::Get().SetHapiNotificationStartedTime(FPlatformTime::Seconds()); + } +} + +void +FHoudiniEngineManager::StopHoudiniTicking() +{ + if (TickerHandle.IsValid() && GEditor) + { + if (IsInGameThread()) + { + FTicker::GetCoreTicker().RemoveTicker(TickerHandle); + TickerHandle.Reset(); + + // Reset time for delayed notification. + FHoudiniEngine::Get().SetHapiNotificationStartedTime(0.0); + + bMustStopTicking = false; + } + else + { + // We can't stop ticking now as we're not in the game Thread, + // and accessing the timer would crash, indicate that we want to stop ticking asap + // This can happen when loosing a session due to a Houdini crash + bMustStopTicking = true; + } + } +} + +bool +FHoudiniEngineManager::Tick(float DeltaTime) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::Tick); + + EnableEditorAutoSave(nullptr); + + FHoudiniEngine::Get().TickPersistentNotification(DeltaTime); + + if (bMustStopTicking) + { + // Ticking should be stopped immediately + StopHoudiniTicking(); + return true; + } + + // Build a set of components that need to be processed + // 1 - selected HACs + // 2 - "Active" HACs + // 3 - The "next" inactive HAC + TArray ComponentsToProcess; + if (FHoudiniEngineRuntime::IsInitialized()) + { + FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); + + //FScopeLock ScopeLock(&CriticalSection); + ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); + + // Wrap around if needed + if (CurrentIndex >= ComponentCount) + CurrentIndex = 0; + + for (uint32 nIdx = 0; nIdx < ComponentCount; nIdx++) + { + UHoudiniAssetComponent * CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(nIdx); + if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) + { + // Invalid component, do not process + continue; + } + else if (CurrentComponent->IsPendingKill() + || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) + { + // Component being deleted, do not process + continue; + } + + if (!CurrentComponent->IsFullyLoaded()) + { + // Let the component figure out whether it's fully loaded or not. + CurrentComponent->HoudiniEngineTick(); + if (!CurrentComponent->IsFullyLoaded()) + continue; // We need to wait some more. + } + + if (!CurrentComponent->IsValidComponent()) + { + // This component is no longer valid. Prevent it from being processed, and remove it. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); + continue; + } + + AActor* Owner = CurrentComponent->GetOwner(); + if (Owner && Owner->IsSelectedInEditor()) + { + // 1. Add selected HACs + // If the component's owner is selected, add it to the set + ComponentsToProcess.Add(CurrentComponent); + } + else if (CurrentComponent->GetAssetState() != EHoudiniAssetState::NeedInstantiation + && CurrentComponent->GetAssetState() != EHoudiniAssetState::None) + { + // 2. Add "Active" HACs, the only two non-active states are: + // NeedInstantiation (loaded, not instantiated in H yet, not modified) + // None (no processing currently) + ComponentsToProcess.Add(CurrentComponent); + } + else if(nIdx == CurrentIndex) + { + // 3. Add the "Current" HAC + ComponentsToProcess.Add(CurrentComponent); + } + + // Set the LastTickTime on the "current" HAC to 0 to ensure it's treated first + if (nIdx == CurrentIndex) + { + CurrentComponent->LastTickTime = 0.0; + } + } + + // Increment the current index for the next tick + CurrentIndex++; + } + + // Sort the components by last tick time + ComponentsToProcess.Sort([](const UHoudiniAssetComponent& A, const UHoudiniAssetComponent& B) { return A.LastTickTime < B.LastTickTime; }); + + // Time limit for processing + double dProcessTimeLimit = CVarHoudiniEngineTickTimeLimit.GetValueOnAnyThread(); + double dProcessStartTime = FPlatformTime::Seconds(); + + // Process all the components in the list + for(UHoudiniAssetComponent* CurrentComponent : ComponentsToProcess) + { + // Tick the notification manager + //FHoudiniEngine::Get().TickPersistentNotification(0.0f); + + double dNow = FPlatformTime::Seconds(); + if (dProcessTimeLimit > 0.0 + && dNow - dProcessStartTime > dProcessTimeLimit) + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); + break; + } + + // Update the tick time for this component + CurrentComponent->LastTickTime = dNow; + + // Handle template processing (for BP) first + // We don't want to the template component processing to trigger session creation + if (CurrentComponent->GetAssetState() == EHoudiniAssetState::ProcessTemplate) + { + if (CurrentComponent->IsTemplate() && !CurrentComponent->HasOpenEditor()) + { + // This component template no longer has an open editor and can be deregistered. + // TODO: Replace this polling mechanism with an "On Asset Closed" event if we + // can find one that actually works. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); + continue; + } + + if (CurrentComponent->NeedBlueprintStructureUpdate()) + { + CurrentComponent->OnBlueprintStructureModified(); + } + + if (CurrentComponent->NeedBlueprintUpdate()) + { + CurrentComponent->OnBlueprintModified(); + } + + if (FHoudiniEngine::Get().IsCookingEnabled()) + { + // Only process component template parameter updates when cooking is enabled. + if (CurrentComponent->NeedUpdateParameters() || CurrentComponent->NeedUpdateInputs()) + { + CurrentComponent->OnTemplateParametersChanged(); + } + } + + if (CurrentComponent->NeedOutputUpdate()) + { + // TODO: Transfer template output changes over to the preview instance. + } + continue; + } + + // Process the component + bool bKeepProcessing = true; + while (bKeepProcessing) + { + // Tick the notification manager + FHoudiniEngine::Get().TickPersistentNotification(0.0f); + + // See if we should start the default "first" session + AutoStartFirstSessionIfNeeded(CurrentComponent); + + EHoudiniAssetState PrevState = CurrentComponent->GetAssetState(); + ProcessComponent(CurrentComponent); + EHoudiniAssetState NewState = CurrentComponent->GetAssetState(); + + // In order to process components faster / with less ticks, + // we may continue processing the component if it ends up in certain states + switch (NewState) + { + case EHoudiniAssetState::NewHDA: + case EHoudiniAssetState::PreInstantiation: + case EHoudiniAssetState::PreCook: + case EHoudiniAssetState::PostCook: + case EHoudiniAssetState::PreProcess: + case EHoudiniAssetState::Processing: + bKeepProcessing = true; + break; + + case EHoudiniAssetState::NeedInstantiation: + case EHoudiniAssetState::Instantiating: + case EHoudiniAssetState::Cooking: + case EHoudiniAssetState::None: + case EHoudiniAssetState::ProcessTemplate: + case EHoudiniAssetState::NeedRebuild: + case EHoudiniAssetState::NeedDelete: + case EHoudiniAssetState::Deleting: + bKeepProcessing = false; + break; + } + + // Safeguard, useless? + // Stop processing if the state hasn't changed + if (PrevState == NewState) + bKeepProcessing = false; + + dNow = FPlatformTime::Seconds(); + if (dProcessTimeLimit > 0.0 && dNow - dProcessStartTime > dProcessTimeLimit) + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); + break; + } + + // Update the tick time for this component + CurrentComponent->LastTickTime = dNow; + } + } + + // Handle Asset delete + if (FHoudiniEngineRuntime::IsInitialized()) + { + int32 PendingDeleteCount = FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteCount(); + for (int32 DeleteIdx = PendingDeleteCount - 1; DeleteIdx >= 0; DeleteIdx--) + { + HAPI_NodeId NodeIdToDelete = (HAPI_NodeId)FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteAt(DeleteIdx); + FGuid HapiDeletionGUID; + bool bShouldDeleteParent = FHoudiniEngineRuntime::Get().IsParentNodePendingDelete(NodeIdToDelete); + if (StartTaskAssetDelete(NodeIdToDelete, HapiDeletionGUID, bShouldDeleteParent)) + { + FHoudiniEngineRuntime::Get().RemoveNodeIdPendingDeleteAt(DeleteIdx); + if (bShouldDeleteParent) + FHoudiniEngineRuntime::Get().RemoveParentNodePendingDelete(NodeIdToDelete); + } + } + } + + // Update PDG Contexts and asset link if needed + PDGManager.Update(); + + // Session Sync Updates + if (FHoudiniEngine::Get().IsSessionSyncEnabled()) + { + // See if the session sync settings have changed on the houdini side, update ours if they did + FHoudiniEngine::Get().UpdateSessionSyncInfoFromHoudini(); +#if WITH_EDITOR + // Update the Houdini viewport from unreal if needed + if (FHoudiniEngine::Get().IsSyncViewportEnabled()) + { + // Sync the Houdini viewport to Unreal + if (!SyncHoudiniViewportToUnreal()) + { + // If the unreal viewport hasnt changed, + // See if we need to sync the Unreal viewport from Houdini's + SyncUnrealViewportToHoudini(); + } + } +#endif + } + else + { + // reset zero offset variables when session sync is off + if (ZeroOffsetValue != 0.f) + ZeroOffsetValue = 0.f; + + if (bOffsetZeroed) + bOffsetZeroed = false; + } + + // Tick the notification manager + FHoudiniEngine::Get().TickPersistentNotification(0.0f); + + return true; +} + +void +FHoudiniEngineManager::AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC) +{ + // See if we should start the default "first" session + if (FHoudiniEngine::Get().GetSession() + || FHoudiniEngine::Get().GetFirstSessionCreated() + || !InCurrentHAC) + return; + + // Only try to start the default session if we have an "active" HAC + const EHoudiniAssetState CurrentState = InCurrentHAC->GetAssetState(); + if (CurrentState == EHoudiniAssetState::NewHDA + || CurrentState == EHoudiniAssetState::PreInstantiation + || CurrentState == EHoudiniAssetState::Instantiating + || CurrentState == EHoudiniAssetState::PreCook + || CurrentState == EHoudiniAssetState::Cooking) + { + FString StatusText = TEXT("Initializing Houdini Engine..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // We want to yield for a bit. + //FPlatformProcess::Sleep(0.5f); + + // Indicates that we've tried to start the session once no matter if it failed or succeed + FHoudiniEngine::Get().SetFirstSessionCreated(true); + + // Attempt to restart the session + if (!FHoudiniEngine::Get().RestartSession()) + { + // We failed to start the session + // Stop ticking until it's manually restarted + StopHoudiniTicking(); + + StatusText = TEXT("Houdini Engine failed to initialize."); + } + else + { + StatusText = TEXT("Houdini Engine successfully initialized."); + } + + // Finish the notification and display the results + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + } +} + +void +FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::ProcessComponent); + + if (!HAC || HAC->IsPendingKill()) + return; + + // No need to process component not tied to an asset + if (!HAC->GetHoudiniAsset()) + return; + + const EHoudiniAssetState AssetStateToProcess = HAC->GetAssetState(); + + // If cooking is paused, stay in the current state until cooking's resumed, unless we are in NewHDA + if (!FHoudiniEngine::Get().IsCookingEnabled() && AssetStateToProcess != EHoudiniAssetState::NewHDA) + { + // We can only handle output updates + if (HAC->GetAssetState() == EHoudiniAssetState::None && HAC->NeedOutputUpdate()) + { + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Refresh UI when pause cooking + if (!FHoudiniEngine::Get().HasUIFinishRefreshingWhenPausingCooking()) + { + // Trigger a details panel update if the Houdini asset actor is selected + if (HAC->IsOwnerSelected()) + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // Finished refreshing UI of one HDA. + FHoudiniEngine::Get().RefreshUIDisplayedWhenPauseCooking(); + } + + // Prevent any other state change to happen + return; + } + + switch (AssetStateToProcess) + { + case EHoudiniAssetState::NeedInstantiation: + { + // Do nothing unless the HAC has been updated + if (HAC->NeedUpdate()) + { + HAC->OnPrePreInstantiation(); + HAC->bForceNeedUpdate = false; + // Update the HAC's state + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + } + else if (HAC->NeedOutputUpdate()) + { + // Output updates do not recquire the HDA to be instantiated + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Update world input if we have any + FHoudiniInputTranslator::UpdateWorldInputs(HAC); + + break; + } + + case EHoudiniAssetState::NewHDA: + { + // Update parameters. Since there is no instantiated node yet, this will only fetch the defaults from + // the asset definition. + FHoudiniParameterTranslator::UpdateParameters(HAC); + // Since the HAC only has the asset definition's default parameter interface, without any asset or node ids, + // we mark it has requiring a parameter definition sync. This will be carried out pre-cook. + HAC->bParameterDefinitionUpdateNeeded = true; + + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + break; + } + + case EHoudiniAssetState::PreInstantiation: + { + // Only proceed forward if we don't need to wait for our input HoudiniAssets to finish cooking/instantiating + if (HAC->NeedsToWaitForInputHoudiniAssets()) + break; + + FGuid TaskGuid; + FString HapiAssetName; + UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); + if (StartTaskAssetInstantiation(HoudiniAsset, HAC->GetDisplayName(), TaskGuid, HapiAssetName)) + { + // Update the HAC's state + HAC->SetAssetState(EHoudiniAssetState::Instantiating); + + // Update the Task GUID + HAC->HapiGUID = TaskGuid; + + // Update the HapiAssetName + HAC->HapiAssetName = HapiAssetName; + } + else + { + // If we couldnt instantiate the asset + // Change the state back to NeedInstantiating + HAC->SetAssetState(EHoudiniAssetState::NeedInstantiation); + } + break; + } + + case EHoudiniAssetState::Instantiating: + { + EHoudiniAssetState NewState = EHoudiniAssetState::Instantiating; + if (UpdateInstantiating(HAC, NewState)) + { + // We need to update the HAC's state + HAC->SetAssetState(NewState); + EnableEditorAutoSave(HAC); + } + else + { + DisableEditorAutoSave(HAC); + } + break; + } + + case EHoudiniAssetState::PreCook: + { + // Only proceed forward if we don't need to wait for our input + // HoudiniAssets to finish cooking/instantiating + if (HAC->NeedsToWaitForInputHoudiniAssets()) + break; + + HAC->OnPrePreCook(); + // Update all the HAPI nodes, parameters, inputs etc... + PreCook(HAC); + HAC->OnPostPreCook(); + + // Create a Cooking task only if necessary + bool bCookStarted = false; + if (IsCookingEnabledForHoudiniAsset(HAC)) + { + FGuid TaskGUID = HAC->GetHapiGUID(); + if ( StartTaskAssetCooking(HAC->GetAssetId(), HAC->GetDisplayName(), TaskGUID) ) + { + // Updates the HAC's state + HAC->SetAssetState(EHoudiniAssetState::Cooking); + HAC->HapiGUID = TaskGUID; + bCookStarted = true; + } + } + + if(!bCookStarted) + { + // Just refresh editor properties? + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // TODO: Check! update state? + HAC->SetAssetState(EHoudiniAssetState::None); + } + break; + } + + case EHoudiniAssetState::Cooking: + { + EHoudiniAssetState NewState = EHoudiniAssetState::Cooking; + bool state = UpdateCooking(HAC, NewState); + if (state) + { + // We need to update the HAC's state + HAC->SetAssetState(NewState); + EnableEditorAutoSave(HAC); + } + else + { + DisableEditorAutoSave(HAC); + } + break; + } + + case EHoudiniAssetState::PostCook: + { + // Handle PostCook + EHoudiniAssetState NewState = EHoudiniAssetState::None; + bool bSuccess = HAC->bLastCookSuccess; + HAC->OnPreOutputProcessing(); + if (PostCook(HAC, bSuccess, HAC->GetAssetId())) + { + // Cook was successful, process the results + NewState = EHoudiniAssetState::PreProcess; + } + else + { + // Cook failed, skip output processing + NewState = EHoudiniAssetState::None; + } + HAC->SetAssetState(NewState); + break; + } + + case EHoudiniAssetState::PreProcess: + { + StartTaskAssetProcess(HAC); + break; + } + + case EHoudiniAssetState::Processing: + { + UpdateProcess(HAC); + + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + HAC->SetAssetCookCount(CookCount); + + HAC->OnPostOutputProcessing(); + FHoudiniEngineUtils::UpdateBlueprintEditor(HAC); + break; + } + + case EHoudiniAssetState::None: + { + // Do nothing unless the HAC has been updated + if (HAC->NeedUpdate()) + { + HAC->bForceNeedUpdate = false; + // Update the HAC's state + HAC->SetAssetState(EHoudiniAssetState::PreCook); + } + else if (HAC->NeedTransformUpdate()) + { + FHoudiniEngineUtils::UploadHACTransform(HAC); + } + else if (HAC->NeedOutputUpdate()) + { + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Update world inputs if we have any + FHoudiniInputTranslator::UpdateWorldInputs(HAC); + + // See if we need to get an update from Session Sync + if(FHoudiniEngine::Get().IsSessionSyncEnabled() + && FHoudiniEngine::Get().IsSyncWithHoudiniCookEnabled() + && HAC->GetAssetState() == EHoudiniAssetState::None) + { + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + if (CookCount >= 0 && CookCount != HAC->GetAssetCookCount()) + { + // The cook count has changed on the Houdini side, + // this indicates that the user has changed something in Houdini so we need to trigger an update + HAC->SetAssetState(EHoudiniAssetState::PreCook); + } + } + break; + } + + case EHoudiniAssetState::NeedRebuild: + { + StartTaskAssetRebuild(HAC->AssetId, HAC->HapiGUID); + + HAC->MarkAsNeedCook(); + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + break; + } + + case EHoudiniAssetState::NeedDelete: + { + FGuid HapiDeletionGUID; + StartTaskAssetDelete(HAC->GetAssetId(), HapiDeletionGUID, true); + //HAC->AssetId = -1; + + // Update the HAC's state + HAC->SetAssetState(EHoudiniAssetState::Deleting); + break; + } + + case EHoudiniAssetState::Deleting: + { + break; + } + } +} + + + +bool +FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID, FString& OutHAPIAssetName) +{ + // Make sure we have a valid session before attempting anything + if (!FHoudiniEngine::Get().GetSession()) + return false; + + OutTaskGUID.Invalidate(); + + // Load the HDA file + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - null or invalid Houdini Asset.")); + return false; + } + + HAPI_AssetLibraryId AssetLibraryId = -1; + if (!FHoudiniEngineUtils::LoadHoudiniAsset(HoudiniAsset, AssetLibraryId) ) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - could not load Houdini Asset.")); + return false; + } + + // Handle hda files that contain multiple assets + TArray< HAPI_StringHandle > AssetNames; + if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - unable to retrieve asset names.")); + return false; + } + + // By default, assume we want to load the first Asset + HAPI_StringHandle PickedAssetName = AssetNames[0]; + +#if WITH_EDITOR + // Should we show the multi asset dialog? + bool bShowMultiAssetDialog = false; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && AssetNames.Num() > 1) + bShowMultiAssetDialog = HoudiniRuntimeSettings->bShowMultiAssetDialog; + + // TODO: Add multi selection dialog + if (bShowMultiAssetDialog ) + { + // TODO: Implement + FHoudiniEngineUtils::OpenSubassetSelectionWindow(AssetNames, PickedAssetName); + } +#endif + + // Give the HAC a new GUID to identify this request. + OutTaskGUID = FGuid::NewGuid(); + + // Create a new instantiation task + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetInstantiation, OutTaskGUID); + Task.Asset = HoudiniAsset; + Task.ActorName = DisplayName; + //Task.bLoadedComponent = bLocalLoadedComponent; + Task.AssetLibraryId = AssetLibraryId; + Task.AssetHapiName = PickedAssetName; + + FHoudiniEngineString(PickedAssetName).ToFString(OutHAPIAssetName); + + // Add the task to the stack + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState ) +{ + check(HAC); + + // Will return true if the asset's state need to be updated + NewState = HAC->GetAssetState(); + bool bUpdateState = false; + + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + // Get the current task's progress + FHoudiniEngineTaskInfo TaskInfo; + if ( !UpdateTaskStatus(HAC->HapiGUID, TaskInfo) + || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetInstantiation) + { + // Couldnt get a valid task info + HOUDINI_LOG_ERROR(TEXT(" %s Failed to instantiate - invalid task"), *DisplayName); + NewState = EHoudiniAssetState::NeedInstantiation; + bUpdateState = true; + return bUpdateState; + } + + bool bSuccess = false; + bool bFinished = false; + switch (TaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Success: + { + bSuccess = true; + bFinished = true; + break; + } + + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::FinishedWithError: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + bSuccess = false; + bFinished = true; + break; + } + + case EHoudiniEngineTaskState::None: + case EHoudiniEngineTaskState::Working: + { + bFinished = false; + break; + } + } + + if ( !bFinished ) + { + // Task is still in progress, nothing to do for now + return false; + } + + if ( bSuccess && (TaskInfo.AssetId < 0) ) + { + // Task finished successfully but we received an invalid asset ID, error out + HOUDINI_LOG_ERROR(TEXT(" %s Finished Instantiation but received invalid asset id."), *DisplayName); + bSuccess = false; + } + + if ( bSuccess ) + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedInstantiation."), *DisplayName); + + // Set the new Asset ID + HAC->AssetId = TaskInfo.AssetId; + + // Assign a unique name to the actor if needed + FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(HAC); + + // TODO: Create default preset buffer. + /*TArray< char > DefaultPresetBuffer; + if (!FHoudiniEngineUtils::GetAssetPreset(TaskInfo.AssetId, DefaultPresetBuffer)) + DefaultPresetBuffer.Empty();*/ + + // Reset the cook counter. + HAC->SetAssetCookCount(0); + + // If necessary, set asset transform. + if (HAC->bUploadTransformsToHoudiniEngine) + { + // Retrieve the current component-to-world transform for this component. + if (!FHoudiniEngineUtils::HapiSetAssetTransform(TaskInfo.AssetId, HAC->GetComponentTransform())) + HOUDINI_LOG_MESSAGE(TEXT("Failed to upload the initial Transform back to HAPI.")); + } + + // Only initalize the PDG Asset Link if this Asset is a PDG Asset + // InitializePDGAssetLink may take a while to execute on non PDG HDA, + // So we want to avoid calling it if possible + if (FHoudiniPDGManager::IsPDGAsset(HAC->AssetId)) + { + PDGManager.InitializePDGAssetLink(HAC); + } + + // Initial update/create of inputs + if (HAC->HasBeenLoaded()) + { + FHoudiniInputTranslator::UpdateLoadedInputs(HAC); + } + else + { + FHoudiniInputTranslator::UpdateInputs(HAC); + } + + // Update the HAC's state + NewState = EHoudiniAssetState::PreCook; + return true; + } + else + { + 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); + + FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); + + FMessageDialog::Debugf(FText::FromString(WarningMessage), &WarningTitleText); + } + + // Reset the cook counter. + HAC->SetAssetCookCount(0); + + // Make sure the asset ID is invalid + HAC->AssetId = -1; + + // Update the HAC's state + HAC->SetAssetState(EHoudiniAssetState::NeedInstantiation); + //HAC->AssetStateResult = EHoudiniAssetStateResult::Success; + + return true; + } +} + +bool +FHoudiniEngineManager::StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID) +{ + // Make sure we have a valid session before attempting anything + if (!FHoudiniEngine::Get().GetSession()) + return false; + + // Check we have a valid AssetId + if (AssetId < 0) + return false; + + // Check this HAC doesn't already have a running task + if (OutTaskGUID.IsValid()) + return false; + + // Generate a GUID for our new task. + OutTaskGUID = FGuid::NewGuid(); + + // Add a new cook task + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetCooking, OutTaskGUID); + Task.ActorName = DisplayName; + Task.AssetId = AssetId; + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState) +{ + check(HAC); + + // Will return true if the asset's state need to be updated + NewState = HAC->GetAssetState(); + bool bUpdateState = false; + + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + // Get the current task's progress + FHoudiniEngineTaskInfo TaskInfo; + if (!UpdateTaskStatus(HAC->HapiGUID, TaskInfo) + || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetCooking) + { + // Couldnt get a valid task info + HOUDINI_LOG_ERROR(TEXT(" %s Failed to cook - invalid task"), *DisplayName); + NewState = EHoudiniAssetState::None; + bUpdateState = true; + return bUpdateState; + } + + bool bSuccess = false; + switch (TaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Success: + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking."), *DisplayName); + bSuccess = true; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::FinishedWithError: + { + // We finished with cook error, will still try to process the results + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with errors - will try to process the available results."), *DisplayName); + bSuccess = true; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with fatal errors - aborting."), *DisplayName); + bSuccess = false; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::None: + case EHoudiniEngineTaskState::Working: + { + // Task is still in progress, nothing to do for now + // return false so we do not update the state + bUpdateState = false; + } + break; + } + + // If the task is still in progress, return now + if (!bUpdateState) + return false; + + // Handle PostCook + NewState = EHoudiniAssetState::PostCook; + HAC->bLastCookSuccess = bSuccess; + + //if (PostCook(HAC, bSuccess, TaskInfo.AssetId)) + //{ + // // Cook was successfull, process the results + // NewState = EHoudiniAssetState::PreProcess; + // HAC->BroadcastCookFinished(); + //} + //else + //{ + // // Cook failed, skip output processing + // NewState = EHoudiniAssetState::None; + //} + + return true; +} + +bool +FHoudiniEngineManager::PreCook(UHoudiniAssetComponent* HAC) +{ + // Handle duplicated HAC + // We need to clean/duplicate some of the HAC's output data manually here + if (HAC->HasBeenDuplicated()) + { + HAC->UpdatePostDuplicate(); + } + + FHoudiniParameterTranslator::OnPreCookParameters(HAC); + + if (HAC->HasBeenLoaded() || HAC->IsParameterDefinitionUpdateNeeded()) + { + // This will sync parameter definitions but not upload values to HAPI or fetch values for existing parameters + // in Unreal. It will creating missing parameters in Unreal. + FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); + HAC->bParameterDefinitionUpdateNeeded = false; + } + + // Upload the changed/parameters back to HAPI + // If cooking is disabled, we still try to upload parameters + if (HAC->HasBeenLoaded()) + { + // // Handle loaded parameters + // FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); + + // Handle loaded inputs + FHoudiniInputTranslator::UpdateLoadedInputs(HAC); + + // Handle loaded outputs + FHoudiniOutputTranslator::UpdateLoadedOutputs(HAC); + + // TODO: Handle loaded curve + // TODO: Handle editable node + // TODO: Restore parameter preset data + } + + // Try to upload changed parameters + FHoudiniParameterTranslator::UploadChangedParameters(HAC); + + // Try to upload changed inputs + FHoudiniInputTranslator::UploadChangedInputs(HAC); + + // Try to upload changed editable nodes + FHoudiniOutputTranslator::UploadChangedEditableOutput(HAC, false); + + // Upload the asset's transform if needed + if (HAC->NeedTransformUpdate()) + FHoudiniEngineUtils::UploadHACTransform(HAC); + + HAC->ClearRefineMeshesTimer(); + + return true; +} + +bool +FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId) +{ + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + bool bCookSuccess = bSuccess; + if (bCookSuccess && (TaskAssetId < 0)) + { + // Task finished successfully but we received an invalid asset ID, error out + HOUDINI_LOG_ERROR(TEXT(" %s received an invalid asset id - aborting."), *DisplayName); + bCookSuccess = false; + } + + // Update the asset cook count using the node infos + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + HAC->SetAssetCookCount(CookCount); + /* + if(CookCount >= 0 ) + HAC->SetAssetCookCount(CookCount); + else + HAC->SetAssetCookCount(HAC->GetAssetCookCount()+1); + */ + + bool bNeedsToTriggerViewportUpdate = false; + if (bCookSuccess) + { + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Processing outputs..."), false); + + // Set new asset id. + HAC->AssetId = TaskAssetId; + + FHoudiniParameterTranslator::UpdateParameters(HAC); + + FHoudiniInputTranslator::UpdateInputs(HAC); + + bool bHasHoudiniStaticMeshOutput = false; + bool ForceUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); + FHoudiniOutputTranslator::UpdateOutputs(HAC, ForceUpdate, bHasHoudiniStaticMeshOutput); + HAC->SetNoProxyMeshNextCookRequested(false); + + // Handles have to be updated after parameters + FHoudiniHandleTranslator::UpdateHandles(HAC); + + // Clear the HasBeenLoaded flag + if (HAC->HasBeenLoaded()) + { + HAC->SetHasBeenLoaded(false); + } + + // Clear the HasBeenDuplicated flag + if (HAC->HasBeenDuplicated()) + { + HAC->SetHasBeenDuplicated(false); + } + + // Update rendering information. + HAC->UpdateRenderingInformation(); + + // Since we have new asset, we need to update bounds. + HAC->UpdateBounds(); + + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Finished processing outputs"), true); + + // Trigger a details panel update + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // If any outputs have HoudiniStaticMeshes, and if timer based refinement is enabled on the HAC, + // set the RefineMeshesTimer and ensure BuildStaticMeshesForAllHoudiniStaticMeshes is bound to + // the RefineMeshesTimerFired delegate of the HAC + if (bHasHoudiniStaticMeshOutput && HAC->IsProxyStaticMeshRefinementByTimerEnabled()) + { + if (!HAC->GetOnRefineMeshesTimerDelegate().IsBoundToObject(this)) + HAC->GetOnRefineMeshesTimerDelegate().AddRaw(this, &FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes); + HAC->SetRefineMeshesTimer(); + } + + if (bHasHoudiniStaticMeshOutput) + bNeedsToTriggerViewportUpdate = true; + + UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); + if (OnPostCookBakeDelegate.IsBound()) + { + OnPostCookBakeDelegate.Execute(HAC); + if (!HAC->IsBakeAfterNextCookEnabled()) + OnPostCookBakeDelegate.Unbind(); + } + } + else + { + // TODO: Create parameters inputs and handles inputs. + //CreateParameters(); + //CreateInputs(); + //CreateHandles(); + + // Clear the bake after cook delegate if + UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); + if (OnPostCookBakeDelegate.IsBound() && !HAC->IsBakeAfterNextCookEnabled()) + { + OnPostCookBakeDelegate.Unbind(); + // Notify the user that the bake failed since the cook failed. + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Cook failed, therefore the bake also failed..."), true); + } + } + + if (HAC->InputPresets.Num() > 0) + { + HAC->ApplyInputPresets(); + } + + // If we have downstream HDAs, we need to tell them we're done cooking + HAC->NotifyCookedToDownstreamAssets(); + + // Notify the PDG manager that the HDA is done cooking + FHoudiniPDGManager::NotifyAssetCooked(HAC->PDGAssetLink, bSuccess); + + if (bNeedsToTriggerViewportUpdate && GEditor) + { + // We need to manually update the vieport with HoudiniMeshProxies + // if not, modification made in H with the two way debugger wont be visible in Unreal until the vieports gets focus + GEditor->RedrawAllViewports(false); + } + + // Clear the rebuild/recook flags + HAC->SetRecookRequested(false); + HAC->SetRebuildRequested(false); + + //HAC->SyncToBlueprintGeneratedClass(); + + return bCookSuccess; +} + +bool +FHoudiniEngineManager::StartTaskAssetProcess(UHoudiniAssetComponent* HAC) +{ + HAC->SetAssetState(EHoudiniAssetState::Processing); + + return true; +} + +bool +FHoudiniEngineManager::UpdateProcess(UHoudiniAssetComponent* HAC) +{ + HAC->SetAssetState(EHoudiniAssetState::None); + + return true; +} + +bool +FHoudiniEngineManager::StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID) +{ + // Check this HAC doesn't already have a running task + if (OutTaskGUID.IsValid()) + return false; + + if (InAssetId >= 0) + { + /* TODO: Handle Asset Preset + if (!FHoudiniEngineUtils::GetAssetPreset(AssetId, PresetBuffer)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get the asset's preset, rebuilt asset may have lost its parameters.")); + } + */ + // Delete the asset + if (!StartTaskAssetDelete(InAssetId, OutTaskGUID, true)) + { + return false; + } + } + + // Create a new task GUID for this asset + OutTaskGUID = FGuid::NewGuid(); + + return true; +} + +bool +FHoudiniEngineManager::StartTaskAssetDelete(const HAPI_NodeId& InNodeId, FGuid& OutTaskGUID, bool bShouldDeleteParent) +{ + if (InNodeId < 0) + return false; + + // Get the Asset's NodeInfo + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, &AssetNodeInfo)); + + HAPI_NodeId OBJNodeToDelete = InNodeId; + if (AssetNodeInfo.type == HAPI_NODETYPE_SOP) + { + // For SOP Asset, we want to delete their parent's OBJ node + if (bShouldDeleteParent) + { + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(OBJNodeToDelete); + OBJNodeToDelete = ParentId != -1 ? ParentId : OBJNodeToDelete; + } + } + + // Generate GUID for our new task. + OutTaskGUID = FGuid::NewGuid(); + + // Create asset deletion task object and submit it for processing. + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetDeletion, OutTaskGUID); + Task.AssetId = OBJNodeToDelete; + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo) +{ + if (!OutTaskGUID.IsValid()) + return false; + + if (!FHoudiniEngine::Get().RetrieveTaskInfo(OutTaskGUID, OutTaskInfo)) + { + // Task information does not exist + OutTaskGUID.Invalidate(); + return false; + } + + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (EHoudiniEngineTaskState::None != OutTaskInfo.TaskState && bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); + } + + switch (OutTaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::Success: + case EHoudiniEngineTaskState::FinishedWithError: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + // If the current task is finished + // Terminate the slate notification if they exist and delete/invalidate the task + if (bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, true); + } + + FHoudiniEngine::Get().RemoveTaskInfo(OutTaskGUID); + OutTaskGUID.Invalidate(); + } + break; + + case EHoudiniEngineTaskState::Working: + { + // The current task is still running, simply update the current notification + if (bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); + } + } + break; + + case EHoudiniEngineTaskState::None: + default: + { + break; + } + } + + return true; +} + +bool +FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC) +{ + bool bManualRecook = false; + bool bComponentEnable = false; + if (HAC && !HAC->IsPendingKill()) + { + bManualRecook = HAC->HasRecookBeenRequested(); + bComponentEnable = HAC->IsCookingEnabled(); + } + + if (bManualRecook) + return true; + + if (bComponentEnable && FHoudiniEngine::Get().IsCookingEnabled()) + return true; + + return false; +} + +void +FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes called with HAC=nullptr")); + return; + } + +#if WITH_EDITOR + AActor *Owner = HAC->GetOwner(); + FString Name = Owner ? Owner->GetName() : HAC->GetName(); + + FScopedSlowTask Progress(2.0f, FText::FromString(FString::Printf(TEXT("Refining Proxy Mesh to Static Mesh on %s"), *Name))); + Progress.MakeDialog(); + Progress.EnterProgressFrame(1.0f); +#endif + + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); + +#if WITH_EDITOR + Progress.EnterProgressFrame(1.0f); +#endif +} + + +/* Unreal's viewport representation rules: + Viewport location is the actual camera location; + Lookat position is always right in front of the camera, which means the camera is looking at; + The rotator rotates the forward vector to a direction & orientation, and this dir and orientation is the camera's; + The identity direction and orientation of the camera is facing positive X-axis. +*/ + +/* Hapi's viewport representation rules: + The camera is located at a point on the sphere, which the center is the pivot position and the radius is offset; + Quat determines the location on the sphere and which direction the camera is facing towards, as well as the camera orientation; + The identity location, direction and orientation of the camera is facing positive Z-axis (in Hapi coords); +*/ + + +bool +FHoudiniEngineManager::SyncHoudiniViewportToUnreal() +{ + if (!FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled()) + return false; + +#if WITH_EDITOR + // Get the editor viewport LookAt position to spawn the new objects + if (!GEditor || !GEditor->GetActiveViewport()) + return false; + + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (!ViewportClient) + return false; + + // Get the current UE viewport location, lookat position, and rotation + FVector UnrealViewportPosition = ViewportClient->GetViewLocation(); + FRotator UnrealViewportRotation = ViewportClient->GetViewRotation(); + FVector UnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + /* Check if the Unreal viewport has changed */ + if (UnrealViewportPosition.Equals(SyncedUnrealViewportPosition) && + UnrealViewportRotation.Equals(SyncedUnrealViewportRotation) && + UnrealViewportLookatPosition.Equals(SyncedUnrealViewportLookatPosition)) + { + // No need to sync if the viewport camera hasn't changed + return false; + } + + /* Calculate Hapi Quaternion */ + // Initialize Hapi Quat with Unreal Quat. + // Note that rotations are in general non-commutative *** + FQuat HapiQuat = UnrealViewportRotation.Quaternion(); + + // We're in orbit mode, forward vector is Y-axis + if (ViewportClient->bUsingOrbitCamera) + { + // The forward vector is Y-negative direction when on orbiting mode + HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 180.f)); + + // rotations around X and Y axis are reversed + float TempX = HapiQuat.X; + HapiQuat.X = HapiQuat.Y; + HapiQuat.Y = TempX; + HapiQuat.W = -HapiQuat.W; + + } + // We're not in orbiting mode, forward vector is X-axis + else + { + // Rotate the Quat arount Z-axis by 90 degree. + HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 90.f)); + } + + + /* Update Hapi H_View */ + // Note: There are infinte number of H_View representation for current viewport + // Each choice of pivot point determines an equivalent representation. + // We just find an equivalent when the pivot position is the view position, and offset is 0 + + HAPI_Viewport H_View; + H_View.position[0] = UnrealViewportPosition.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + H_View.position[1] = UnrealViewportPosition.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + H_View.position[2] = UnrealViewportPosition.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Set HAPI_Offset always 0 when syncing Houdini to UE viewport + H_View.offset = 0.f; + + H_View.rotationQuaternion[0] = -HapiQuat.X; + H_View.rotationQuaternion[1] = -HapiQuat.Z; + H_View.rotationQuaternion[2] = -HapiQuat.Y; + H_View.rotationQuaternion[3] = HapiQuat.W; + + FHoudiniApi::SetViewport(FHoudiniEngine::Get().GetSession(), &H_View); + + /* Update the Synced viewport values + We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. + Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. */ + + // We need to get the H_Viewport again, since it is possible the value is a different equivalence of what we set. + HAPI_Viewport Cur_H_View; + FHoudiniApi::GetViewport( + FHoudiniEngine::Get().GetSession(), &Cur_H_View); + + // Hapi values are in Houdini coordinate and scale + SyncedHoudiniViewportPivotPosition = FVector(Cur_H_View.position[0], Cur_H_View.position[1], Cur_H_View.position[2]); + SyncedHoudiniViewportQuat = FQuat(Cur_H_View.rotationQuaternion[0], Cur_H_View.rotationQuaternion[1], Cur_H_View.rotationQuaternion[2], Cur_H_View.rotationQuaternion[3]); + SyncedHoudiniViewportOffset = Cur_H_View.offset; + + SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); + SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); + SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + // When sync Houdini to UE, we set offset to be 0. + // So we need to zero out offset for the next time syncing UE to Houdini + bOffsetZeroed = true; + + return true; +#endif + + return false; +} + + +bool +FHoudiniEngineManager::SyncUnrealViewportToHoudini() +{ + if (!FHoudiniEngine::Get().IsSyncUnrealViewportEnabled()) + return false; + +#if WITH_EDITOR + // Get the editor viewport LookAt position to spawn the new objects + if (!GEditor || !GEditor->GetActiveViewport()) + return false; + + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (!ViewportClient) + return false; + + // Get the current HAPI_Viewport + HAPI_Viewport H_View; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetViewport( + FHoudiniEngine::Get().GetSession(), &H_View)) + { + return false; + } + + + // Get Hapi viewport's PivotPosition, Offset and Quat, w.r.t Houdini's coordinate and scale. + FVector HapiViewportPivotPosition = FVector(H_View.position[0], H_View.position[1], H_View.position[2]); + float HapiViewportOffset = H_View.offset; + FQuat HapiViewportQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[1], H_View.rotationQuaternion[2], H_View.rotationQuaternion[3]); + + /* Check if the Houdini viewport has changed */ + if (SyncedHoudiniViewportPivotPosition.Equals(HapiViewportPivotPosition) && + SyncedHoudiniViewportQuat.Equals(HapiViewportQuat) && + SyncedHoudiniViewportOffset == HapiViewportOffset) + { + // Houdini viewport hasn't changed, nothing to do + return false; + } + + // Set zero value of offset when needed + if (bOffsetZeroed) + { + ZeroOffsetValue = H_View.offset; + bOffsetZeroed = false; + } + + + /* Translate the hapi camera transfrom to Unreal's representation system */ + + // Get pivot point in UE's coordinate and scale + FVector UnrealViewportPivotPosition = FVector(H_View.position[0], H_View.position[2], H_View.position[1]) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // HAPI bug? After we set the H_View, offset becomes a lot bigger when move the viewport just a little bit in Houdini. + // But the pivot point doesn't change. Which caused UE viewport jumping far suddenly. + // So we get rid of this problem by setting the first HAPI_offset value after syncing Houdini viewport as the base. + + // Get offset in UE's scale. The actual offset after 'zero out' + float UnrealOffset = (H_View.offset - ZeroOffsetValue) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + /* Calculate Quaternion in UE */ + // Rotate the resulting Quat around Z-axis by -90 degree. + // Note that rotation is in general non-commutative *** + FQuat UnrealQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[2], H_View.rotationQuaternion[1], -H_View.rotationQuaternion[3]); + UnrealQuat = UnrealQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)); + + FVector UnrealBaseVector(1.f, 0.f, 0.f); // Forward vector in Unreal viewport + + /* Get UE viewport location*/ + FVector UnrealViewPosition = - UnrealQuat.RotateVector(UnrealBaseVector) * UnrealOffset + UnrealViewportPivotPosition; + + /* Set the viewport's value */ + ViewportClient->SetViewLocation(UnrealViewPosition); + ViewportClient->SetViewRotation(UnrealQuat.Rotator()); + + // Invalidate the viewport + ViewportClient->Invalidate(); + + /* Update the synced viewport values */ + // We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. + // Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. + + // Hapi values are in Houdini coordinate and scale + SyncedHoudiniViewportPivotPosition = HapiViewportPivotPosition; + SyncedHoudiniViewportQuat = HapiViewportQuat; + SyncedHoudiniViewportOffset = HapiViewportOffset; + + SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); + SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); + SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + return true; +#endif + + return false; +} + + +void +FHoudiniEngineManager::DisableEditorAutoSave(const UHoudiniAssetComponent* HAC) +{ +#if WITH_EDITOR + if (!HAC || HAC->IsPendingKill()) + return; + + if (!GUnrealEd) + return; + + if (DisableAutoSavingHACs.Contains(HAC)) + return; + // Add the HAC to the set + DisableAutoSavingHACs.Add(HAC); + + // Return if auto-saving has been disabled by some other HACs. + if (DisableAutoSavingHACs.Num() > 1) + return; + + // Disable auto-saving by setting min time till auto-save to max float value + IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); + AutoSaver.ForceMinimumTimeTillAutoSave(TNumericLimits::Max()); +#endif +} + + +void +FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = nullptr) +{ +#if WITH_EDITOR + if (!GUnrealEd) + return; + + if (!HAC) + { + // When HAC is nullptr, go through all HACs in the set, + // remove it if the HAC has been deleted. + if (DisableAutoSavingHACs.Num() <= 0) + return; + + TSet ValidComponents; + for (auto& CurHAC : DisableAutoSavingHACs) + { + if (CurHAC && !CurHAC->IsPendingKill()) + { + ValidComponents.Add(CurHAC); + } + } + DisableAutoSavingHACs = MoveTemp(ValidComponents); + } + else + { + // Otherwise, remove the HAC from the set + if (DisableAutoSavingHACs.Contains(HAC)) + DisableAutoSavingHACs.Remove(HAC); + } + + if (DisableAutoSavingHACs.Num() > 0) + return; + + // When no HAC disables cooking, reset min time till auto-save to default value, then reset the timer + IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); + AutoSaver.ResetAutoSaveTimer(); +#endif +} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.h b/Source/HoudiniEngine/Private/HoudiniEngineManager.h index 0d7e5aa7b..5d9610f6f 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.h @@ -1,182 +1,182 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "TimerManager.h" - -//#include "HAL/Runnable.h" -//#include "HAL/RunnableThread.h" -//#include "Misc/SingleThreadRunnable.h" - -#include "HoudiniPDGManager.h" - -class UHoudiniAsset; -class UHoudiniAssetComponent; - -struct FHoudiniEngineTaskInfo; -struct FGuid; - -enum class EHoudiniAssetState : uint8; - -class FHoudiniEngineManager -{ -public: - - FHoudiniEngineManager(); - virtual ~FHoudiniEngineManager(); - - void StartHoudiniTicking(); - void StopHoudiniTicking(); - bool Tick(float DeltaTime); - - // Updates / Process a component - void ProcessComponent(UHoudiniAssetComponent* HAC); - - // Build UStaticMesh for all UHoudiniStaticMesh in a HAC. - // This is fired by the OnRefinedMeshesTimerDelegate on a HAC - void BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC); - - void StartPDGCommandlet() - { - if (!IsPDGCommandletRunningOrConnected()) - PDGManager.CreateBGEOCommandletAndEndpoint(); - } - - void StopPDGCommandlet() - { - if (IsPDGCommandletRunningOrConnected()) - PDGManager.StopBGEOCommandletAndEndpoint(); - } - - bool IsPDGCommandletRunningOrConnected() - { - const EHoudiniBGEOCommandletStatus Status = PDGManager.UpdateAndGetBGEOCommandletStatus(); - return Status == EHoudiniBGEOCommandletStatus::Running || Status == EHoudiniBGEOCommandletStatus::Connected; - } - - EHoudiniBGEOCommandletStatus GetPDGCommandletStatus() { return PDGManager.UpdateAndGetBGEOCommandletStatus(); } - - -protected: - - // Updates a given task's status - // Returns true if the given task's status was properly found - bool UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo); - - // Start a task to instantiate the given HoudiniAsset - // Return true if the task was successfully created - bool StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID); - - // Updates progress of the instantiation task - // Returns true if a state change should be made - bool UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); - - // Start a task to instantiate the Houdini Asset with the given node Id - // Returns true if the task was successfully created - bool StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID); - - // Updates progress of the cooking task - // Returns true if a state change should be made - bool UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); - - // Called to update template components. - bool PreCookTemplate(UHoudiniAssetComponent* HAC); - - // Called to update all houdini nodes/params/inputs before a cook has started - bool PreCook(UHoudiniAssetComponent* HAC); - - // Called after a cook has finished - bool PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId); - - bool StartTaskAssetProcess(UHoudiniAssetComponent* HAC); - - bool UpdateProcess(UHoudiniAssetComponent* HAC); - - // Starts a rebuild task (delete then re instantiate) - // The NodeID should be invalidated after a successful call - bool StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID); - - // Starts a node delete task - // The NodeID should be invalidated after a successful call - bool StartTaskAssetDelete(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID, bool bShouldDeleteParent); - - bool IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC); - - // Syncs the houdini viewport to Unreal's viewport - // Returns true if the Houdini viewport has been modified - bool SyncHoudiniViewportToUnreal(); - - // Syncs the unreal viewport to Houdini's viewport - // Returns true if the Unreal viewport has been modified - bool SyncUnrealViewportToHoudini(); - - // Disable auto save by setting min time till auto save to the max value - void DisableEditorAutoSave(const UHoudiniAssetComponent* HAC); - - void EnableEditorAutoSave(const UHoudiniAssetComponent* HAC); - - // Automatically try to start the First HE session if needed - void AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC); - -private: - - // Ticker handle, used for processing HAC. - FDelegateHandle TickerHandle; - - // Current position in the array - uint32 CurrentIndex; - - // Current number of components in the array - uint32 ComponentCount; - - // Stopping flag. - // Indicates that we should stop ticking asap - bool bMustStopTicking; - - // The PDG Manager, handles all registered PDG Asset Links - FHoudiniPDGManager PDGManager; - - // For ViewportSync: The camera transform that Hapi and Unreal currently agree with. - FVector SyncedHoudiniViewportPivotPosition; - FQuat SyncedHoudiniViewportQuat; - float SyncedHoudiniViewportOffset; - - FVector SyncedUnrealViewportPosition; - FRotator SyncedUnrealViewportRotation; - FVector SyncedUnrealViewportLookatPosition; - - // We need these two variables to get rid of a HAPI bug - // Note: When sync Houdini to UE, we set the pivot position to be the view position, and offset to be 0.0. - // but when we switch to control viewport in Houdini, HAPI returns an H_View with a large offset, but pivot unchanged. - // so, we need these two variables to 'zero out' offset. - float ZeroOffsetValue; // in HAPI scale - bool bOffsetZeroed; - - // Indicates which HACs disable auto-saving - TSet DisableAutoSavingHACs; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "TimerManager.h" + +//#include "HAL/Runnable.h" +//#include "HAL/RunnableThread.h" +//#include "Misc/SingleThreadRunnable.h" + +#include "HoudiniPDGManager.h" + +class UHoudiniAsset; +class UHoudiniAssetComponent; + +struct FHoudiniEngineTaskInfo; +struct FGuid; + +enum class EHoudiniAssetState : uint8; + +class FHoudiniEngineManager +{ +public: + + FHoudiniEngineManager(); + virtual ~FHoudiniEngineManager(); + + void StartHoudiniTicking(); + void StopHoudiniTicking(); + bool Tick(float DeltaTime); + + // Updates / Process a component + void ProcessComponent(UHoudiniAssetComponent* HAC); + + // Build UStaticMesh for all UHoudiniStaticMesh in a HAC. + // This is fired by the OnRefinedMeshesTimerDelegate on a HAC + void BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC); + + void StartPDGCommandlet() + { + if (!IsPDGCommandletRunningOrConnected()) + PDGManager.CreateBGEOCommandletAndEndpoint(); + } + + void StopPDGCommandlet() + { + if (IsPDGCommandletRunningOrConnected()) + PDGManager.StopBGEOCommandletAndEndpoint(); + } + + bool IsPDGCommandletRunningOrConnected() + { + const EHoudiniBGEOCommandletStatus Status = PDGManager.UpdateAndGetBGEOCommandletStatus(); + return Status == EHoudiniBGEOCommandletStatus::Running || Status == EHoudiniBGEOCommandletStatus::Connected; + } + + EHoudiniBGEOCommandletStatus GetPDGCommandletStatus() { return PDGManager.UpdateAndGetBGEOCommandletStatus(); } + + +protected: + + // Updates a given task's status + // Returns true if the given task's status was properly found + bool UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo); + + // Start a task to instantiate the given HoudiniAsset + // Return true if the task was successfully created + bool StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID, FString& OutHAPIAssetName); + + // Updates progress of the instantiation task + // Returns true if a state change should be made + bool UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); + + // Start a task to instantiate the Houdini Asset with the given node Id + // Returns true if the task was successfully created + bool StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID); + + // Updates progress of the cooking task + // Returns true if a state change should be made + bool UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); + + // Called to update template components. + bool PreCookTemplate(UHoudiniAssetComponent* HAC); + + // Called to update all houdini nodes/params/inputs before a cook has started + bool PreCook(UHoudiniAssetComponent* HAC); + + // Called after a cook has finished + bool PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId); + + bool StartTaskAssetProcess(UHoudiniAssetComponent* HAC); + + bool UpdateProcess(UHoudiniAssetComponent* HAC); + + // Starts a rebuild task (delete then re instantiate) + // The NodeID should be invalidated after a successful call + bool StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID); + + // Starts a node delete task + // The NodeID should be invalidated after a successful call + bool StartTaskAssetDelete(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID, bool bShouldDeleteParent); + + bool IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC); + + // Syncs the houdini viewport to Unreal's viewport + // Returns true if the Houdini viewport has been modified + bool SyncHoudiniViewportToUnreal(); + + // Syncs the unreal viewport to Houdini's viewport + // Returns true if the Unreal viewport has been modified + bool SyncUnrealViewportToHoudini(); + + // Disable auto save by setting min time till auto save to the max value + void DisableEditorAutoSave(const UHoudiniAssetComponent* HAC); + + void EnableEditorAutoSave(const UHoudiniAssetComponent* HAC); + + // Automatically try to start the First HE session if needed + void AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC); + +private: + + // Ticker handle, used for processing HAC. + FDelegateHandle TickerHandle; + + // Current position in the array + uint32 CurrentIndex; + + // Current number of components in the array + uint32 ComponentCount; + + // Stopping flag. + // Indicates that we should stop ticking asap + bool bMustStopTicking; + + // The PDG Manager, handles all registered PDG Asset Links + FHoudiniPDGManager PDGManager; + + // For ViewportSync: The camera transform that Hapi and Unreal currently agree with. + FVector SyncedHoudiniViewportPivotPosition; + FQuat SyncedHoudiniViewportQuat; + float SyncedHoudiniViewportOffset; + + FVector SyncedUnrealViewportPosition; + FRotator SyncedUnrealViewportRotation; + FVector SyncedUnrealViewportLookatPosition; + + // We need these two variables to get rid of a HAPI bug + // Note: When sync Houdini to UE, we set the pivot position to be the view position, and offset to be 0.0. + // but when we switch to control viewport in Houdini, HAPI returns an H_View with a large offset, but pivot unchanged. + // so, we need these two variables to 'zero out' offset. + float ZeroOffsetValue; // in HAPI scale + bool bOffsetZeroed; + + // Indicates which HACs disable auto-saving + TSet DisableAutoSavingHACs; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp index ce7231f85..957845f18 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp @@ -1,60 +1,60 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineOutputStats.h" - -FHoudiniEngineOutputStats::FHoudiniEngineOutputStats() - : NumPackagesCreated(0) - , NumPackagesUpdated(0) -{ } - -void FHoudiniEngineOutputStats::NotifyPackageCreated(int32 NumCreated) -{ - NumPackagesCreated += NumCreated; -} - -void FHoudiniEngineOutputStats::NotifyPackageUpdated(int32 NumUpdated) -{ - NumPackagesUpdated += NumUpdated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated) -{ - const int32 Count = OutputObjectsCreated.FindOrAdd(ObjectTypeName, 0); - OutputObjectsCreated[ObjectTypeName] = Count + NumCreated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated) -{ - const int32 Count = OutputObjectsUpdated.FindOrAdd(ObjectTypeName, 0); - OutputObjectsUpdated[ObjectTypeName] = Count + NumUpdated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced) -{ - const int32 Count = OutputObjectsReplaced.FindOrAdd(ObjectTypeName, 0); - OutputObjectsReplaced[ObjectTypeName] = Count + NumReplaced; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineOutputStats.h" + +FHoudiniEngineOutputStats::FHoudiniEngineOutputStats() + : NumPackagesCreated(0) + , NumPackagesUpdated(0) +{ } + +void FHoudiniEngineOutputStats::NotifyPackageCreated(int32 NumCreated) +{ + NumPackagesCreated += NumCreated; +} + +void FHoudiniEngineOutputStats::NotifyPackageUpdated(int32 NumUpdated) +{ + NumPackagesUpdated += NumUpdated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated) +{ + const int32 Count = OutputObjectsCreated.FindOrAdd(ObjectTypeName, 0); + OutputObjectsCreated[ObjectTypeName] = Count + NumCreated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated) +{ + const int32 Count = OutputObjectsUpdated.FindOrAdd(ObjectTypeName, 0); + OutputObjectsUpdated[ObjectTypeName] = Count + NumUpdated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced) +{ + const int32 Count = OutputObjectsReplaced.FindOrAdd(ObjectTypeName, 0); + OutputObjectsReplaced[ObjectTypeName] = Count + NumReplaced; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h index 6234dca1b..b973c18d1 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Class.h" - -struct HOUDINIENGINE_API FHoudiniEngineOutputStats -{ - FHoudiniEngineOutputStats(); - - int32 NumPackagesCreated; - int32 NumPackagesUpdated; - - // These FStrings should preferably be EHoudiniOutputType enum - // Move the OUtput enums into a separate header to avoid circular dependencies. - TMap OutputObjectsCreated; - TMap OutputObjectsUpdated; - TMap OutputObjectsReplaced; - - void NotifyPackageCreated(int32 NumCreated); - void NotifyPackageUpdated(int32 NumUpdated); - - // Objects created - void NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated); - template - void NotifyObjectsCreated(EnumT EnumValue, int32 NumCreated) - { - NotifyObjectsCreated( UEnum::GetValueAsString(EnumValue), NumCreated ); - } - - // Object updated - void NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated); - template - void NotifyObjectsUpdated(EnumT EnumValue, int32 NumUpdated) - { - NotifyObjectsUpdated( UEnum::GetValueAsString(EnumValue), NumUpdated ); - } - - // Objects replaced - void NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced); - template - void NotifyObjectsReplaced(EnumT EnumValue, int32 NumReplaced) - { - NotifyObjectsReplaced( UEnum::GetValueAsString(EnumValue), NumReplaced ); - } -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Class.h" + +struct HOUDINIENGINE_API FHoudiniEngineOutputStats +{ + FHoudiniEngineOutputStats(); + + int32 NumPackagesCreated; + int32 NumPackagesUpdated; + + // These FStrings should preferably be EHoudiniOutputType enum + // Move the OUtput enums into a separate header to avoid circular dependencies. + TMap OutputObjectsCreated; + TMap OutputObjectsUpdated; + TMap OutputObjectsReplaced; + + void NotifyPackageCreated(int32 NumCreated); + void NotifyPackageUpdated(int32 NumUpdated); + + // Objects created + void NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated); + template + void NotifyObjectsCreated(EnumT EnumValue, int32 NumCreated) + { + NotifyObjectsCreated( UEnum::GetValueAsString(EnumValue), NumCreated ); + } + + // Object updated + void NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated); + template + void NotifyObjectsUpdated(EnumT EnumValue, int32 NumUpdated) + { + NotifyObjectsUpdated( UEnum::GetValueAsString(EnumValue), NumUpdated ); + } + + // Objects replaced + void NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced); + template + void NotifyObjectsReplaced(EnumT EnumValue, int32 NumReplaced) + { + NotifyObjectsReplaced( UEnum::GetValueAsString(EnumValue), NumReplaced ); + } +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h index 9e0aa9433..a3121280c 100644 --- a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h +++ b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h @@ -1,396 +1,411 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -// Indicate we're in the HoudiniEngine module -#define HOUDINI_ENGINE -#include "HoudiniEngineRuntimePrivatePCH.h" - -// HFS path definition coming from UBT/build.cs files. -#ifndef HOUDINI_ENGINE_HFS_PATH_DEFINE - #define HOUDINI_ENGINE_HFS_PATH "" -#else - #define HOUDINI_ENGINE_STRINGIFY_HELPER(X) #X - #define HOUDINI_ENGINE_STRINGIFY(X) HOUDINI_ENGINE_STRINGIFY_HELPER(X) - #define HOUDINI_ENGINE_HFS_PATH HOUDINI_ENGINE_STRINGIFY(HOUDINI_ENGINE_HFS_PATH_DEFINE) -#endif - -// HFS subfolder containing HAPI lib. -#define HAPI_HFS_SUBFOLDER_WINDOWS TEXT( "bin" ) -#define HAPI_HFS_SUBFOLDER_MAC TEXT( "dsolib" ) -#define HAPI_HFS_SUBFOLDER_LINUX TEXT( "dsolib" ) - -// Unreal HAPI Resources. -#define HAPI_UNREAL_RESOURCE_HOUDINI_LOGO TEXT( "/HoudiniEngine/houdini_logo.houdini_logo" ) -#define HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL TEXT( "/HoudiniEngine/houdini_default_material.houdini_default_material" ) -#define HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL TEXT( "/HoudiniEngine/houdini_templated_material.houdini_templated_material") -#define HAPI_UNREAL_RESOURCE_BGEO_IMPORT TEXT( "/HoudiniEngine/houdini_bgeo_import.houdini_bgeo_import" ) - -#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH TEXT("/HoudiniEngine/default_reference_static_mesh.default_reference_static_mesh") -#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL TEXT("/HoudiniEngine/default_reference_static_mesh_material.default_reference_static_mesh_material") - -// Client name so HAPI knows we're running inside unreal -#define HAPI_UNREAL_CLIENT_NAME "unreal" - -// Error checking - this macro will check the status and return specified parameter. -#define HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HAPI_LOG_ROUTINE ) \ - do \ - { \ - HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ - if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - return HAPI_PARAM_RETURN; \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR_RETURN( HAPI_PARAM_CALL, HAPI_PARAM_RETURN ) \ - HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HOUDINI_LOG_ERROR ) - -// Simple Error checking - this macro will check the status. -#define HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ - do \ - { \ - HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ - if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR( HAPI_PARAM_CALL ) \ - HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) - -// Error checking - this macro will check the status and returns it. -#define HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ - do \ - { \ - *HAPI_PARAM_RESULT = HAPI_PARAM_CALL; \ - if ( *HAPI_PARAM_RESULT != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR_GET( HAPI_PARAM_RESULT, HAPI_PARAM_CALL ) \ - HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) - -// For Transform conversion between UE4 / Houdini -// Set to 0 to stop converting Houdini's coordinate space to unreal's -#define HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM 1 - -#define HAPI_UNREAL_SCALE_FACTOR_POSITION 100.0f -#define HAPI_UNREAL_SCALE_FACTOR_TRANSLATION 100.0f -#define HAPI_UNREAL_SCALE_FACTOR_SCALE 100.0f - -#define HAPI_UNREAL_SCALE_SMALL_VALUE KINDA_SMALL_NUMBER * 2.0f - -#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) - -// Attributes -#define HAPI_UNREAL_ATTRIB_POSITION HAPI_ATTRIB_POSITION -#define HAPI_UNREAL_ATTRIB_ROTATION "rot" -#define HAPI_UNREAL_ATTRIB_SCALE "scale" -#define HAPI_UNREAL_ATTRIB_UNIFORM_SCALE "pscale" -#define HAPI_UNREAL_ATTRIB_COLOR HAPI_ATTRIB_COLOR -#define HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR "unreal_lightmap_color" -#define HAPI_UNREAL_ATTRIB_ALPHA "Alpha" -#define HAPI_UNREAL_ATTRIB_UV HAPI_ATTRIB_UV -#define HAPI_UNREAL_ATTRIB_NORMAL HAPI_ATTRIB_NORMAL -#define HAPI_UNREAL_ATTRIB_TANGENTU HAPI_ATTRIB_TANGENT -#define HAPI_UNREAL_ATTRIB_TANGENTV HAPI_ATTRIB_TANGENT2 - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE "tile" -// Always the name of the main landscape actor. -// Names for landscape tile actors will be taken from 'unreal_output_name'. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME "unreal_landscape_shared_actor_name" -// This tile_actor_type succeeds the 'unreal_landscape_streaming_proxy' (v1) attribute. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE "unreal_landscape_tile_actor_type" -// This attribute is for backwards compatibility only. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN "unreal_landscape_layer_min" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX "unreal_landscape_layer_max" - -// Path to the level in which an actor should be generated or which contained the input data -// "." - (Default) Generate geometry in the the current persistent world -// "Junk/Background" - Path to a Map that is relative to the current persistent world's Map. -// "/Game/Maps/Level01/Junk/Background" - Absolute path to the map in which the primitive should be output -#define HAPI_UNREAL_ATTRIB_LEVEL_PATH "unreal_level_path" - -// Path to the actor that contained the input data/should be generated -#define HAPI_UNREAL_ATTRIB_ACTOR_PATH "unreal_actor_path" - -// Path to the object plugged in a geo in -#define HAPI_UNREAL_ATTRIB_OBJECT_PATH "unreal_object_path" - -// Attributes used for data exchange between UE4 and Houdini -#define HAPI_UNREAL_ATTRIB_MATERIAL "unreal_material" -#define HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK "unreal_face_material" -#define HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE "unreal_material_instance" -#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE "unreal_material_hole" -#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE "unreal_material_hole_instance" -#define HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL "unreal_physical_material" -#define HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK "unreal_face_smoothing_mask" -#define HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION "unreal_lightmap_resolution" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE "lod_screensize" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX "lod" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX "_screensize" -#define HAPI_UNREAL_ATTRIB_TAG_PREFIX "unreal_tag_" - -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX "mesh_socket" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME "mesh_socket_name" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD "unreal_mesh_socket_name" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR "mesh_socket_actor" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD "unreal_mesh_socket_actor" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG "mesh_socket_tag" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD "unreal_mesh_socket_tag" - -#define HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME "unreal_input_mesh_name" -#define HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE "unreal_input_source_file" - -#define HAPI_UNREAL_ATTRIB_INSTANCE "instance" -#define HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE "unreal_instance" -#define HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES "unreal_split_instances" -#define HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER "unreal_foliage" -#define HAPI_UNREAL_ATTRIB_INSTANCE_ROTATION "rot" -#define HAPI_UNREAL_ATTRIB_INSTANCE_SCALE "scale" -#define HAPI_UNREAL_ATTRIB_INSTANCE_POSITION HAPI_ATTRIB_POSITION -#define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" -#define HAPI_UNREAL_ATTRIB_SPLIT_ATTR "unreal_split_attr" -#define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" -#define HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" -#define HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX "unreal_per_instance_custom_data" - - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX "unreal_vertex_index" -#define HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER "unreal_unit_landscape_layer" -#define HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS "unreal_landscape_layer_nonweightblended" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO "unreal_landscape_layer_info" - -#define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" -#define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" - -#define HAPI_UNREAL_ATTRIB_BAKE_FOLDER "unreal_bake_folder" -#define HAPI_UNREAL_ATTRIB_TEMP_FOLDER "unreal_temp_folder" -#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1 "unreal_generated_mesh_name" -#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2 "unreal_output_name" -#define HAPI_UNREAL_ATTRIB_BAKE_ACTOR "unreal_bake_actor" -#define HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER "unreal_bake_outliner_folder" - -// data tables -#define HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX "unreal_data_table_" -#define HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT "unreal_datatable_rowstruct" - -// Attributes for Curve Outputs -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE "unreal_output_curve" -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR "unreal_output_curve_linear" -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED "unreal_output_curve_closed" -// We only support Unreal spline outputs for now -//#define HAPI_UNREAL_ATTRIB_OUTPUT_HOUDINI_CURVE "houdini_output_curve" - -// Geometry Node -#define HAPI_UNREAL_PARAM_TRANSLATE "t" -#define HAPI_UNREAL_PARAM_ROTATE "r" -#define HAPI_UNREAL_PARAM_SCALE "s" -#define HAPI_UNREAL_PARAM_PIVOT "p" -#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" - -// Houdini Curve -#define HAPI_UNREAL_PARAM_CURVE_TYPE "type" -#define HAPI_UNREAL_PARAM_CURVE_METHOD "method" -#define HAPI_UNREAL_PARAM_CURVE_COORDS "coords" -#define HAPI_UNREAL_PARAM_CURVE_CLOSED "close" -#define HAPI_UNREAL_PARAM_CURVE_REVERSED "reverse" - -#define HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT "0.0, 0.0, 3.0 3.0, 0.0, 3.0" - -// String Params tags -#define HOUDINI_PARAMETER_STRING_REF_TAG TEXT("unreal_ref") -#define HOUDINI_PARAMETER_STRING_REF_CLASS_TAG TEXT("unreal_ref_class") -#define HOUDINI_PARAMETER_STRING_MULTILINE_TAG TEXT("editor") - -// Parameter tags -#define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" -#define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" -#define HAPI_PARAM_TAG_UNITS "units" - -// TODO: unused, remove! -#define HAPI_PARAM_TAG_ASSET_REF "asset_ref" - -// Groups -#define HAPI_UNREAL_GROUP_LOD_PREFIX TEXT("lod") -#define HAPI_UNREAL_GROUP_SOCKET_PREFIX TEXT("mesh_socket") -#define HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD TEXT("socket") -//#define HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX TEXT("unreal_split") - -#define HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION TEXT("main_geo") - -#define HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX TEXT("collision_geo") -#define HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX TEXT("rendered_collision_geo") - -#define HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX TEXT("collision_geo_ucx") -#define HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX TEXT("rendered_collision_geo_ucx") - -#define HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX TEXT("collision_geo_simple") -#define HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX TEXT("rendered_collision_geo_simple") - -// Default material name. -#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) - -// Various variable names used to store meta information in generated packages. -#define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE TEXT( "HoudiniGeneratedTextureType" ) -#define HAPI_UNREAL_PACKAGE_META_NODE_PATH TEXT( "HoudiniNodePath" ) -#define HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER TEXT( "HoudiniPackageBakeCounter" ) -#define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) - -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL TEXT( "N" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE TEXT( "C_A" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR TEXT( "S" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS TEXT( "R" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC TEXT( "M" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE TEXT( "E" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK TEXT( "O" ) - -// Texture planes. -#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA "C A" -#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR "C" -#define HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA "A" -#define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" - -// Materials Diffuse. -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL "ogl_diff" -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE "basecolor" - -#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" - -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL "ogl_tex1" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED "ogl_use_tex1" - -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE "basecolor_texture" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED "basecolor_useTexture" - -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" - -// Materials Normal. -#define HAPI_UNREAL_PARAM_MAP_NORMAL_OGL "ogl_normalmap" - -//#define HAPI_UNREAL_PARAM_MAP_NORMAL "normalTexture" -//#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "normalUseTexture" -#define HAPI_UNREAL_PARAM_MAP_NORMAL "baseNormal_texture" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "baseBumpAndNormal_enable" - -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" - -#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" - -// Materials Specular. -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL "ogl_spec" -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR "reflect" - -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL "ogl_specmap" -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED "ogl_use_specmap" - -#define HAPI_UNREAL_PARAM_MAP_SPECULAR "reflect_texture" -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED "reflect_useTexture" - -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" - -// Materials Roughness. -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL "ogl_rough" -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS "rough" - -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL "ogl_roughmap" -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED "ogl_use_roughmap" - -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS "rough_texture" -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED "rough_useTexture" - -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" - -// Materials Metallic. -#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" -#define HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL "ogl_metallic" - -#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL "ogl_metallicmap" -#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED "ogl_use_metallicmap" - -#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" -#define HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED "metallic_useTexture" - -#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" - -// Materials Emissive. -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL "ogl_emit" -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE "emitcolor" - -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL "ogl_emissionmap" -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED "ogl_use_emissionmap" - -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED "emitcolor_useTexture" - -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" - -// Materials Opacity. -#define HAPI_UNREAL_PARAM_ALPHA_OGL "ogl_alpha" -#define HAPI_UNREAL_PARAM_ALPHA "opac" - -#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL "ogl_opacitymap" -#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED "ogl_use_opacitymap" - -#define HAPI_UNREAL_PARAM_MAP_OPACITY "opaccolor_texture" -#define HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED "opaccolor_useTexture" - -// Number of GUID characters to keep for packages -#define PACKAGE_GUID_LENGTH 8 -#define PACKAGE_GUID_COMPONENT_LENGTH 12 - -/** Ramp related defines. **/ -#define HAPI_UNREAL_RAMP_FLOAT_AXIS_X "position" -#define HAPI_UNREAL_RAMP_FLOAT_AXIS_Y "value" -#define HAPI_UNREAL_RAMP_COLOR_AXIS_X "position" -#define HAPI_UNREAL_RAMP_COLOR_AXIS_Y "color" - -/** Handle types. **/ -#define HAPI_UNREAL_HANDLE_TRANSFORM "xform" -#define HAPI_UNREAL_HANDLE_BOUNDER "bound" - -#define HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME "height" - -#define HAPI_UNREAL_NOTIFICATION_FADEOUT 2.0f -#define HAPI_UNREAL_NOTIFICATION_EXPIRE 2.0f - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +// Indicate we're in the HoudiniEngine module +#define HOUDINI_ENGINE +#include "HoudiniEngineRuntimePrivatePCH.h" + +// HFS path definition coming from UBT/build.cs files. +#ifndef HOUDINI_ENGINE_HFS_PATH_DEFINE + #define HOUDINI_ENGINE_HFS_PATH "" +#else + #define HOUDINI_ENGINE_STRINGIFY_HELPER(X) #X + #define HOUDINI_ENGINE_STRINGIFY(X) HOUDINI_ENGINE_STRINGIFY_HELPER(X) + #define HOUDINI_ENGINE_HFS_PATH HOUDINI_ENGINE_STRINGIFY(HOUDINI_ENGINE_HFS_PATH_DEFINE) +#endif + +// HFS subfolder containing HAPI lib. +#define HAPI_HFS_SUBFOLDER_WINDOWS TEXT( "bin" ) +#define HAPI_HFS_SUBFOLDER_MAC TEXT( "dsolib" ) +#define HAPI_HFS_SUBFOLDER_LINUX TEXT( "dsolib" ) + +// Unreal HAPI Resources. +#define HAPI_UNREAL_RESOURCE_HOUDINI_LOGO TEXT( "/HoudiniEngine/houdini_logo.houdini_logo" ) +#define HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL TEXT( "/HoudiniEngine/houdini_default_material.houdini_default_material" ) +#define HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL TEXT( "/HoudiniEngine/houdini_templated_material.houdini_templated_material") +#define HAPI_UNREAL_RESOURCE_BGEO_IMPORT TEXT( "/HoudiniEngine/houdini_bgeo_import.houdini_bgeo_import" ) + +#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH TEXT("/HoudiniEngine/default_reference_static_mesh.default_reference_static_mesh") +#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL TEXT("/HoudiniEngine/default_reference_static_mesh_material.default_reference_static_mesh_material") + +// Client name so HAPI knows we're running inside unreal +#define HAPI_UNREAL_CLIENT_NAME "unreal" + +// Error checking - this macro will check the status and return specified parameter. +#define HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HAPI_LOG_ROUTINE ) \ + do \ + { \ + HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ + if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + return HAPI_PARAM_RETURN; \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR_RETURN( HAPI_PARAM_CALL, HAPI_PARAM_RETURN ) \ + HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HOUDINI_LOG_ERROR ) + +// Simple Error checking - this macro will check the status. +#define HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ + do \ + { \ + HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ + if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR( HAPI_PARAM_CALL ) \ + HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) + +// Error checking - this macro will check the status and returns it. +#define HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ + do \ + { \ + *HAPI_PARAM_RESULT = HAPI_PARAM_CALL; \ + if ( *HAPI_PARAM_RESULT != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR_GET( HAPI_PARAM_RESULT, HAPI_PARAM_CALL ) \ + HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) + +// For Transform conversion between UE4 / Houdini +// Set to 0 to stop converting Houdini's coordinate space to unreal's +#define HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM 1 + +#define HAPI_UNREAL_SCALE_FACTOR_POSITION 100.0f +#define HAPI_UNREAL_SCALE_FACTOR_TRANSLATION 100.0f +#define HAPI_UNREAL_SCALE_FACTOR_SCALE 100.0f + +#define HAPI_UNREAL_SCALE_SMALL_VALUE KINDA_SMALL_NUMBER * 2.0f + +#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) + +// Attributes +#define HAPI_UNREAL_ATTRIB_POSITION HAPI_ATTRIB_POSITION +#define HAPI_UNREAL_ATTRIB_ROTATION "rot" +#define HAPI_UNREAL_ATTRIB_SCALE "scale" +#define HAPI_UNREAL_ATTRIB_UNIFORM_SCALE "pscale" +#define HAPI_UNREAL_ATTRIB_COLOR HAPI_ATTRIB_COLOR +#define HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR "unreal_lightmap_color" +#define HAPI_UNREAL_ATTRIB_ALPHA "Alpha" +#define HAPI_UNREAL_ATTRIB_UV HAPI_ATTRIB_UV +#define HAPI_UNREAL_ATTRIB_NORMAL HAPI_ATTRIB_NORMAL +#define HAPI_UNREAL_ATTRIB_TANGENTU HAPI_ATTRIB_TANGENT +#define HAPI_UNREAL_ATTRIB_TANGENTV HAPI_ATTRIB_TANGENT2 + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE "tile" +// Always the name of the main landscape actor. +// Names for landscape tile actors will be taken from 'unreal_output_name'. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME "unreal_landscape_shared_actor_name" +// This tile_actor_type succeeds the 'unreal_landscape_streaming_proxy' (v1) attribute. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE "unreal_landscape_tile_actor_type" +// This attribute is for backwards compatibility only. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN "unreal_landscape_layer_min" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX "unreal_landscape_layer_max" + +// Path to the level in which an actor should be generated or which contained the input data +// "." - (Default) Generate geometry in the the current persistent world +// "Junk/Background" - Path to a Map that is relative to the current persistent world's Map. +// "/Game/Maps/Level01/Junk/Background" - Absolute path to the map in which the primitive should be output +#define HAPI_UNREAL_ATTRIB_LEVEL_PATH "unreal_level_path" + +// Path to the actor that contained the input data/should be generated +#define HAPI_UNREAL_ATTRIB_ACTOR_PATH "unreal_actor_path" + +// Path to the object plugged in a geo in +#define HAPI_UNREAL_ATTRIB_OBJECT_PATH "unreal_object_path" + +// Attributes used for data exchange between UE4 and Houdini +#define HAPI_UNREAL_ATTRIB_MATERIAL "unreal_material" +#define HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK "unreal_face_material" +#define HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE "unreal_material_instance" +#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE "unreal_material_hole" +#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE "unreal_material_hole_instance" +#define HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL "unreal_physical_material" +#define HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK "unreal_face_smoothing_mask" +#define HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION "unreal_lightmap_resolution" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE "lod_screensize" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX "lod" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX "_screensize" +#define HAPI_UNREAL_ATTRIB_TAG_PREFIX "unreal_tag_" + +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX "mesh_socket" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME "mesh_socket_name" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD "unreal_mesh_socket_name" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR "mesh_socket_actor" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD "unreal_mesh_socket_actor" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG "mesh_socket_tag" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD "unreal_mesh_socket_tag" + +#define HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME "unreal_input_mesh_name" +#define HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE "unreal_input_source_file" + +#define HAPI_UNREAL_ATTRIB_INSTANCE "instance" +#define HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE "unreal_instance" +#define HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES "unreal_split_instances" +#define HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER "unreal_foliage" +#define HAPI_UNREAL_ATTRIB_INSTANCE_ROTATION "rot" +#define HAPI_UNREAL_ATTRIB_INSTANCE_SCALE "scale" +#define HAPI_UNREAL_ATTRIB_INSTANCE_POSITION HAPI_ATTRIB_POSITION +#define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" +#define HAPI_UNREAL_ATTRIB_SPLIT_ATTR "unreal_split_attr" +#define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" +#define HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" +#define HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX "unreal_per_instance_custom_data" + + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX "unreal_vertex_index" +#define HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER "unreal_unit_landscape_layer" +#define HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS "unreal_landscape_layer_nonweightblended" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO "unreal_landscape_layer_info" +// Landscape output mode: +// 0 - Default (Temp) mode +// 1 - Output heightfield to existing landscape editable layer +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_OUTPUT_MODE "unreal_landscape_output_mode" +#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_DEFAULT 0 +#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_EDITABLE_LAYER 1 + +// Edit layer +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME "unreal_landscape_editlayer_name" +// Clear the editlayer before blitting new data +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR "unreal_landscape_editlayer_clear" +// Landscape that is being targeted by "edit layer" outputs +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET "unreal_landscape_editlayer_target" +// Place the output layer "after" the given layer +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER "unreal_landscape_editlayer_after" + +#define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" +#define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" + +#define HAPI_UNREAL_ATTRIB_BAKE_FOLDER "unreal_bake_folder" +#define HAPI_UNREAL_ATTRIB_TEMP_FOLDER "unreal_temp_folder" +#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1 "unreal_generated_mesh_name" +#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2 "unreal_output_name" +#define HAPI_UNREAL_ATTRIB_BAKE_ACTOR "unreal_bake_actor" +#define HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER "unreal_bake_outliner_folder" + +// data tables +#define HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX "unreal_data_table_" +#define HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT "unreal_datatable_rowstruct" + +// Attributes for Curve Outputs +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE "unreal_output_curve" +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR "unreal_output_curve_linear" +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED "unreal_output_curve_closed" +// We only support Unreal spline outputs for now +//#define HAPI_UNREAL_ATTRIB_OUTPUT_HOUDINI_CURVE "houdini_output_curve" + +// Geometry Node +#define HAPI_UNREAL_PARAM_TRANSLATE "t" +#define HAPI_UNREAL_PARAM_ROTATE "r" +#define HAPI_UNREAL_PARAM_SCALE "s" +#define HAPI_UNREAL_PARAM_PIVOT "p" +#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" + +// Houdini Curve +#define HAPI_UNREAL_PARAM_CURVE_TYPE "type" +#define HAPI_UNREAL_PARAM_CURVE_METHOD "method" +#define HAPI_UNREAL_PARAM_CURVE_COORDS "coords" +#define HAPI_UNREAL_PARAM_CURVE_CLOSED "close" +#define HAPI_UNREAL_PARAM_CURVE_REVERSED "reverse" + +#define HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT "0.0, 0.0, 3.0 3.0, 0.0, 3.0" + +// String Params tags +#define HOUDINI_PARAMETER_STRING_REF_TAG TEXT("unreal_ref") +#define HOUDINI_PARAMETER_STRING_REF_CLASS_TAG TEXT("unreal_ref_class") +#define HOUDINI_PARAMETER_STRING_MULTILINE_TAG TEXT("editor") + +// Parameter tags +#define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" +#define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" +#define HAPI_PARAM_TAG_UNITS "units" + +// TODO: unused, remove! +#define HAPI_PARAM_TAG_ASSET_REF "asset_ref" + +// Groups +#define HAPI_UNREAL_GROUP_LOD_PREFIX TEXT("lod") +#define HAPI_UNREAL_GROUP_SOCKET_PREFIX TEXT("mesh_socket") +#define HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD TEXT("socket") +//#define HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX TEXT("unreal_split") + +#define HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION TEXT("main_geo") + +#define HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX TEXT("collision_geo") +#define HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX TEXT("rendered_collision_geo") + +#define HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX TEXT("collision_geo_ucx") +#define HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX TEXT("rendered_collision_geo_ucx") + +#define HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX TEXT("collision_geo_simple") +#define HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX TEXT("rendered_collision_geo_simple") + +// Default material name. +#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) + +// Various variable names used to store meta information in generated packages. +#define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE TEXT( "HoudiniGeneratedTextureType" ) +#define HAPI_UNREAL_PACKAGE_META_NODE_PATH TEXT( "HoudiniNodePath" ) +#define HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER TEXT( "HoudiniPackageBakeCounter" ) +#define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) + +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL TEXT( "N" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE TEXT( "C_A" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR TEXT( "S" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS TEXT( "R" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC TEXT( "M" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE TEXT( "E" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK TEXT( "O" ) + +// Texture planes. +#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA "C A" +#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR "C" +#define HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA "A" +#define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" + +// Materials Diffuse. +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL "ogl_diff" +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE "basecolor" + +#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL "ogl_tex1" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED "ogl_use_tex1" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE "basecolor_texture" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED "basecolor_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" + +// Materials Normal. +#define HAPI_UNREAL_PARAM_MAP_NORMAL_OGL "ogl_normalmap" + +//#define HAPI_UNREAL_PARAM_MAP_NORMAL "normalTexture" +//#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "normalUseTexture" +#define HAPI_UNREAL_PARAM_MAP_NORMAL "baseNormal_texture" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "baseBumpAndNormal_enable" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" + +// Materials Specular. +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL "ogl_spec" +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR "reflect" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL "ogl_specmap" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED "ogl_use_specmap" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR "reflect_texture" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED "reflect_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" + +// Materials Roughness. +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL "ogl_rough" +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS "rough" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL "ogl_roughmap" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED "ogl_use_roughmap" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS "rough_texture" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED "rough_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" + +// Materials Metallic. +#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" +#define HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL "ogl_metallic" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL "ogl_metallicmap" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED "ogl_use_metallicmap" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED "metallic_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" + +// Materials Emissive. +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL "ogl_emit" +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE "emitcolor" + +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL "ogl_emissionmap" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED "ogl_use_emissionmap" + +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED "emitcolor_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" + +// Materials Opacity. +#define HAPI_UNREAL_PARAM_ALPHA_OGL "ogl_alpha" +#define HAPI_UNREAL_PARAM_ALPHA "opac" + +#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL "ogl_opacitymap" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED "ogl_use_opacitymap" + +#define HAPI_UNREAL_PARAM_MAP_OPACITY "opaccolor_texture" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED "opaccolor_useTexture" + +// Number of GUID characters to keep for packages +#define PACKAGE_GUID_LENGTH 8 +#define PACKAGE_GUID_COMPONENT_LENGTH 12 + +/** Ramp related defines. **/ +#define HAPI_UNREAL_RAMP_FLOAT_AXIS_X "position" +#define HAPI_UNREAL_RAMP_FLOAT_AXIS_Y "value" +#define HAPI_UNREAL_RAMP_COLOR_AXIS_X "position" +#define HAPI_UNREAL_RAMP_COLOR_AXIS_Y "color" + +/** Handle types. **/ +#define HAPI_UNREAL_HANDLE_TRANSFORM "xform" +#define HAPI_UNREAL_HANDLE_BOUNDER "bound" + +#define HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME "height" + +#define HAPI_UNREAL_NOTIFICATION_FADEOUT 2.0f +#define HAPI_UNREAL_NOTIFICATION_EXPIRE 2.0f + diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp index 280915c4a..772259c55 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp @@ -1,625 +1,625 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineScheduler.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngineString.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngine.h" - -const uint32 -FHoudiniEngineScheduler::InitialTaskSize = 256u; - -const float -FHoudiniEngineScheduler::UpdateFrequency = 0.1f; - -FHoudiniEngineScheduler::FHoudiniEngineScheduler() - : Tasks(nullptr) - , PositionWrite(0u) - , PositionRead(0u) - , bStopping(false) -{ - // Make sure size is power of two. - TaskCount = FPlatformMath::RoundUpToPowerOfTwo(FHoudiniEngineScheduler::InitialTaskSize); - - if (TaskCount) - { - // Allocate buffer to store all tasks. - Tasks = static_cast(FMemory::Malloc(TaskCount * sizeof(FHoudiniEngineTask))); - - if (Tasks) - { - // Zero memory. - FMemory::Memset(Tasks, 0x0, TaskCount * sizeof(FHoudiniEngineTask)); - } - } -} - -FHoudiniEngineScheduler::~FHoudiniEngineScheduler() -{ - if (TaskCount) - { - FMemory::Free(Tasks); - Tasks = nullptr; - } -} - -void -FHoudiniEngineScheduler::TaskDescription( - FHoudiniEngineTaskInfo & TaskInfo, - const FString & ActorName, - const FString & StatusString) -{ - FFormatNamedArguments Args; - - if (!ActorName.IsEmpty()) - { - Args.Add(TEXT("AssetName"), FText::FromString(ActorName)); - Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); - TaskInfo.StatusText = - FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetName}) : ({AssetStatus})"), Args); - } - else - { - Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); - TaskInfo.StatusText = - FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetStatus})"), Args); - } -} - -void -FHoudiniEngineScheduler::TaskInstantiateAsset(const FHoudiniEngineTask & Task) -{ - FString AssetN; - FHoudiniEngineString(Task.AssetHapiName).ToFString(AssetN); - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Instantiation Started for %s: Asset=%s, HoudiniAsset = 0x%x"), - *Task.ActorName, *AssetN, Task.Asset.Get()); - - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskInstantiateAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - if (!Task.Asset.IsValid()) - { - // Asset is no longer valid, return. - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset is no longer valid.")); - - return; - } - - if (Task.AssetHapiName < 0) - { - // Asset is no longer valid, return. - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset name is invalid.")); - - return; - } - - HAPI_Result Result = HAPI_RESULT_SUCCESS; - int32 AssetCount = 0; - HAPI_NodeId AssetId = -1; - std::string AssetNameString; - double LastUpdateTime; - - FHoudiniEngineString HoudiniEngineString(Task.AssetHapiName); - if (!HoudiniEngineString.ToStdString(AssetNameString)) - { - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Error retrieving asset name.")); - - return; - } - - // Translate asset name into Unreal string. - FString AssetName = ANSI_TO_TCHAR(AssetNameString.c_str()); - - // Initialize last update time. - LastUpdateTime = FPlatformTime::Seconds(); - - // We instantiate without cooking. - Result = FHoudiniApi::CreateNode( - FHoudiniEngine::Get().GetSession(), -1, &AssetNameString[0], nullptr, false, &AssetId); - if (Result != HAPI_RESULT_SUCCESS) - { - AddResponseMessageTaskInfo( - Result, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Error instantiating asset.")); - - return; - } - - // Add processing notification. - FHoudiniEngineTaskInfo TaskInfo( - HAPI_RESULT_SUCCESS, -1, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Working); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - TaskDescription(TaskInfo, Task.ActorName, TEXT("Started Instantiation")); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); - - // We need to spin until instantiation is finished. - while (true) - { - int Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // Cooking has been successful. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Success, AssetId, Task, - TEXT("Finished Instantiation.")); - - break; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // There was an error while instantiating. - FString CookResultString = FHoudiniEngineUtils::GetCookResult(); - int32 CookResult = static_cast(HAPI_RESULT_SUCCESS); - FHoudiniApi::GetStatus(FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_RESULT, &CookResult); - - EHoudiniEngineTaskState TaskStateResult = EHoudiniEngineTaskState::FinishedWithFatalError; - if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - TaskStateResult = EHoudiniEngineTaskState::FinishedWithError; - - AddResponseMessageTaskInfo( - static_cast(CookResult), - EHoudiniEngineTaskType::AssetInstantiation, - TaskStateResult, - AssetId, Task, - FString::Printf(TEXT("Finished Instantiation with Errors: %s"), *CookResultString)); - - break; - } - - static const double NotificationUpdateFrequency = 0.5; - if ((FPlatformTime::Seconds() - LastUpdateTime) >= NotificationUpdateFrequency) - { - // Reset update time. - LastUpdateTime = FPlatformTime::Seconds(); - const FString& CookStateMessage = FHoudiniEngineUtils::GetCookState(); - - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Working, - AssetId, Task, CookStateMessage); - } - - // We want to yield. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } -} - -void -FHoudiniEngineScheduler::TaskCookAsset(const FHoudiniEngineTask & Task) -{ - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskCookAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - // Retrieve asset id. - HAPI_NodeId AssetId = Task.AssetId; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Cooking Started for %s., AssetId = %d"), - *Task.ActorName, AssetId); - - if (AssetId == -1) - { - // We have an invalid asset id. - HOUDINI_LOG_ERROR(TEXT("TaskCookAsset failed for %s: Invalid Asset Id."), *Task.ActorName); - - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset has invalid id.")); - - return; - } - - // Default CookOptions - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - Result = FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), AssetId, &CookOptions); - if (Result != HAPI_RESULT_SUCCESS) - { - AddResponseMessageTaskInfo( - Result, EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - AssetId, Task, TEXT("Error cooking asset.")); - - return; - } - - // Add processing notification. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Working, - AssetId, Task, TEXT("Started Cooking")); - - // Initialize last update time. - double LastUpdateTime = FPlatformTime::Seconds(); - - // We need to spin until cooking is finished. - while (true) - { - int32 Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET( &Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // Cooking has been successful. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Success, - AssetId, Task, TEXT("Finished Cooking")); - - break; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - EHoudiniEngineTaskState TaskResult = EHoudiniEngineTaskState::FinishedWithFatalError; - if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - TaskResult = EHoudiniEngineTaskState::FinishedWithError; - - // There was an error while instantiating. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - TaskResult, - AssetId, Task, - TEXT("Finished Cooking with Errors")); - - break; - } - - static const double NotificationUpdateFrequency = 0.5; - if (FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency) - { - // Reset update time. - LastUpdateTime = FPlatformTime::Seconds(); - - // Retrieve status string. - const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); - - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Working, - AssetId, Task, CookStateMessage); - } - - // We want to yield. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } -} - -void -FHoudiniEngineScheduler::TaskDeleteAsset(const FHoudiniEngineTask & Task) -{ - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Destruction Started for %s. ") - TEXT("AssetId = %d"), - *Task.ActorName, Task.AssetId); - - if (FHoudiniEngineUtils::IsHoudiniNodeValid(Task.AssetId)) - FHoudiniEngineUtils::DestroyHoudiniAsset(Task.AssetId); - - // We do not insert task info as this is a fire and forget operation. - // At this point component most likely does not exist. -} - -void -FHoudiniEngineScheduler::AddResponseTaskInfo( - HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, const FHoudiniEngineTask & Task) -{ - FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); - FString StatusString = FHoudiniEngineUtils::GetErrorDescription(); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - - TaskDescription(TaskInfo, Task.ActorName, StatusString); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); -} - -void -FHoudiniEngineScheduler::AddResponseMessageTaskInfo( - HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, const FString & ErrorMessage) -{ - FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - - TaskDescription(TaskInfo, Task.ActorName, ErrorMessage); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); -} - -void -FHoudiniEngineScheduler::ProcessQueuedTasks() -{ - while (!bStopping) - { - while (true) - { - FHoudiniEngineTask Task; - - { - FScopeLock ScopeLock(&CriticalSection); - - // We have no tasks left. - if (PositionWrite == PositionRead) - break; - - // Retrieve task. - Task = Tasks[PositionRead]; - PositionRead++; - - // Wrap around if required. - PositionRead &= (TaskCount - 1); - } - - bool bTaskProcessed = true; - - switch (Task.TaskType) - { - case EHoudiniEngineTaskType::AssetInstantiation: - { - TaskInstantiateAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetCooking: - { - TaskCookAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetDeletion: - { - TaskDeleteAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetProcess: - { - TaskProccessAsset(Task); - break; - } - - default: - { - bTaskProcessed = false; - break; - } - } - - if (!bTaskProcessed) - break; - } - - if (FPlatformProcess::SupportsMultithreading()) - { - // We want to yield for a bit. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } - else - { - // If we are running in single threaded mode, return so we don't block everything else. - return; - } - } -} - -void -FHoudiniEngineScheduler::TaskProccessAsset(const FHoudiniEngineTask & Task) -{ - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskProccessAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - // Retrieve asset id. - HAPI_NodeId AssetId = Task.AssetId; - if (AssetId == -1) - { - // We have an invalid asset id. - HOUDINI_LOG_ERROR(TEXT("TaskProcessAsset failed for %s: Invalid Asset Id."), *Task.ActorName); - - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset has invalid id.")); - - return; - } - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Processing started for %s., AssetId = %d"), - *Task.ActorName, AssetId); - - // Add processing notification. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::Working, - AssetId, Task, TEXT("Started Cooking")); - - // - - - // TODO: Process results! -} - -bool FHoudiniEngineScheduler::HasPendingTasks() -{ - FScopeLock ScopeLock(&CriticalSection); - return (PositionWrite != PositionRead); -} - -void -FHoudiniEngineScheduler::AddTask(const FHoudiniEngineTask & Task) -{ - FScopeLock ScopeLock(&CriticalSection); - - // Check if we need to grow our circular buffer. - if (PositionWrite + 1 == PositionRead) - { - // Calculate next size (next power of two). - uint32 NextTaskCount = FPlatformMath::RoundUpToPowerOfTwo(TaskCount + 1); - - // Allocate new buffer. - FHoudiniEngineTask * Buffer = static_cast( - FMemory::Malloc(NextTaskCount * sizeof(FHoudiniEngineTask))); - - if (!Buffer) - return; - - // Zero memory. - FMemory::Memset(Buffer, 0x0, NextTaskCount * sizeof(FHoudiniEngineTask)); - - // Copy elements from old buffer to new one. - if (PositionRead < PositionWrite) - { - FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (PositionWrite - PositionRead)); - - // Update index positions. - PositionRead = 0; - PositionWrite = PositionWrite - PositionRead; - } - else - { - FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (TaskCount - PositionRead)); - FMemory::Memcpy(Buffer + TaskCount - PositionRead, Tasks, sizeof(FHoudiniEngineTask) * PositionWrite); - - // Update index positions. - PositionRead = 0; - PositionWrite = TaskCount - PositionRead + PositionWrite; - } - - // Deallocate old buffer. - FMemory::Free(Tasks); - - // Bookkeeping. - Tasks = Buffer; - TaskCount = NextTaskCount; - } - - // Store task. - Tasks[PositionWrite] = Task; - PositionWrite++; - - // Wrap around if required. - PositionWrite &= (TaskCount - 1); -} - -uint32 -FHoudiniEngineScheduler::Run() -{ - ProcessQueuedTasks(); - return 0; -} - -void -FHoudiniEngineScheduler::Stop() -{ - bStopping = true; -} - -void -FHoudiniEngineScheduler::Tick() -{ - ProcessQueuedTasks(); -} - -FSingleThreadRunnable * -FHoudiniEngineScheduler::GetSingleThreadInterface() -{ - return this; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineScheduler.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" + +const uint32 +FHoudiniEngineScheduler::InitialTaskSize = 256u; + +const float +FHoudiniEngineScheduler::UpdateFrequency = 0.1f; + +FHoudiniEngineScheduler::FHoudiniEngineScheduler() + : Tasks(nullptr) + , PositionWrite(0u) + , PositionRead(0u) + , bStopping(false) +{ + // Make sure size is power of two. + TaskCount = FPlatformMath::RoundUpToPowerOfTwo(FHoudiniEngineScheduler::InitialTaskSize); + + if (TaskCount) + { + // Allocate buffer to store all tasks. + Tasks = static_cast(FMemory::Malloc(TaskCount * sizeof(FHoudiniEngineTask))); + + if (Tasks) + { + // Zero memory. + FMemory::Memset(Tasks, 0x0, TaskCount * sizeof(FHoudiniEngineTask)); + } + } +} + +FHoudiniEngineScheduler::~FHoudiniEngineScheduler() +{ + if (TaskCount) + { + FMemory::Free(Tasks); + Tasks = nullptr; + } +} + +void +FHoudiniEngineScheduler::TaskDescription( + FHoudiniEngineTaskInfo & TaskInfo, + const FString & ActorName, + const FString & StatusString) +{ + FFormatNamedArguments Args; + + if (!ActorName.IsEmpty()) + { + Args.Add(TEXT("AssetName"), FText::FromString(ActorName)); + Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); + TaskInfo.StatusText = + FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetName}) : ({AssetStatus})"), Args); + } + else + { + Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); + TaskInfo.StatusText = + FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetStatus})"), Args); + } +} + +void +FHoudiniEngineScheduler::TaskInstantiateAsset(const FHoudiniEngineTask & Task) +{ + FString AssetN; + FHoudiniEngineString(Task.AssetHapiName).ToFString(AssetN); + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Instantiation Started for %s: Asset=%s, HoudiniAsset = 0x%x"), + *Task.ActorName, *AssetN, Task.Asset.Get()); + + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskInstantiateAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + if (!Task.Asset.IsValid()) + { + // Asset is no longer valid, return. + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset is no longer valid.")); + + return; + } + + if (Task.AssetHapiName < 0) + { + // Asset is no longer valid, return. + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset name is invalid.")); + + return; + } + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + int32 AssetCount = 0; + HAPI_NodeId AssetId = -1; + std::string AssetNameString; + double LastUpdateTime; + + FHoudiniEngineString HoudiniEngineString(Task.AssetHapiName); + if (!HoudiniEngineString.ToStdString(AssetNameString)) + { + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Error retrieving asset name.")); + + return; + } + + // Translate asset name into Unreal string. + FString AssetName = ANSI_TO_TCHAR(AssetNameString.c_str()); + + // Initialize last update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // We instantiate without cooking. + Result = FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), -1, &AssetNameString[0], nullptr, false, &AssetId); + if (Result != HAPI_RESULT_SUCCESS) + { + AddResponseMessageTaskInfo( + Result, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Error instantiating asset.")); + + return; + } + + // Add processing notification. + FHoudiniEngineTaskInfo TaskInfo( + HAPI_RESULT_SUCCESS, -1, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Working); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + TaskDescription(TaskInfo, Task.ActorName, TEXT("Started Instantiation")); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); + + // We need to spin until instantiation is finished. + while (true) + { + int Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // Cooking has been successful. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Success, AssetId, Task, + TEXT("Finished Instantiation.")); + + break; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // There was an error while instantiating. + FString CookResultString = FHoudiniEngineUtils::GetCookResult(); + int32 CookResult = static_cast(HAPI_RESULT_SUCCESS); + FHoudiniApi::GetStatus(FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_RESULT, &CookResult); + + EHoudiniEngineTaskState TaskStateResult = EHoudiniEngineTaskState::FinishedWithFatalError; + if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + TaskStateResult = EHoudiniEngineTaskState::FinishedWithError; + + AddResponseMessageTaskInfo( + static_cast(CookResult), + EHoudiniEngineTaskType::AssetInstantiation, + TaskStateResult, + AssetId, Task, + FString::Printf(TEXT("Finished Instantiation with Errors: %s"), *CookResultString)); + + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if ((FPlatformTime::Seconds() - LastUpdateTime) >= NotificationUpdateFrequency) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + const FString& CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Working, + AssetId, Task, CookStateMessage); + } + + // We want to yield. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } +} + +void +FHoudiniEngineScheduler::TaskCookAsset(const FHoudiniEngineTask & Task) +{ + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskCookAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + // Retrieve asset id. + HAPI_NodeId AssetId = Task.AssetId; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Cooking Started for %s., AssetId = %d"), + *Task.ActorName, AssetId); + + if (AssetId == -1) + { + // We have an invalid asset id. + HOUDINI_LOG_ERROR(TEXT("TaskCookAsset failed for %s: Invalid Asset Id."), *Task.ActorName); + + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset has invalid id.")); + + return; + } + + // Default CookOptions + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + Result = FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), AssetId, &CookOptions); + if (Result != HAPI_RESULT_SUCCESS) + { + AddResponseMessageTaskInfo( + Result, EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + AssetId, Task, TEXT("Error cooking asset.")); + + return; + } + + // Add processing notification. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Working, + AssetId, Task, TEXT("Started Cooking")); + + // Initialize last update time. + double LastUpdateTime = FPlatformTime::Seconds(); + + // We need to spin until cooking is finished. + while (true) + { + int32 Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET( &Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // Cooking has been successful. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Success, + AssetId, Task, TEXT("Finished Cooking")); + + break; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + EHoudiniEngineTaskState TaskResult = EHoudiniEngineTaskState::FinishedWithFatalError; + if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + TaskResult = EHoudiniEngineTaskState::FinishedWithError; + + // There was an error while instantiating. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + TaskResult, + AssetId, Task, + TEXT("Finished Cooking with Errors")); + + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if (FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // Retrieve status string. + const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Working, + AssetId, Task, CookStateMessage); + } + + // We want to yield. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } +} + +void +FHoudiniEngineScheduler::TaskDeleteAsset(const FHoudiniEngineTask & Task) +{ + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Destruction Started for %s. ") + TEXT("AssetId = %d"), + *Task.ActorName, Task.AssetId); + + if (FHoudiniEngineUtils::IsHoudiniNodeValid(Task.AssetId)) + FHoudiniEngineUtils::DestroyHoudiniAsset(Task.AssetId); + + // We do not insert task info as this is a fire and forget operation. + // At this point component most likely does not exist. +} + +void +FHoudiniEngineScheduler::AddResponseTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task) +{ + FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); + FString StatusString = FHoudiniEngineUtils::GetErrorDescription(); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + + TaskDescription(TaskInfo, Task.ActorName, StatusString); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); +} + +void +FHoudiniEngineScheduler::AddResponseMessageTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, const FString & ErrorMessage) +{ + FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + + TaskDescription(TaskInfo, Task.ActorName, ErrorMessage); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); +} + +void +FHoudiniEngineScheduler::ProcessQueuedTasks() +{ + while (!bStopping) + { + while (true) + { + FHoudiniEngineTask Task; + + { + FScopeLock ScopeLock(&CriticalSection); + + // We have no tasks left. + if (PositionWrite == PositionRead) + break; + + // Retrieve task. + Task = Tasks[PositionRead]; + PositionRead++; + + // Wrap around if required. + PositionRead &= (TaskCount - 1); + } + + bool bTaskProcessed = true; + + switch (Task.TaskType) + { + case EHoudiniEngineTaskType::AssetInstantiation: + { + TaskInstantiateAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetCooking: + { + TaskCookAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetDeletion: + { + TaskDeleteAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetProcess: + { + TaskProccessAsset(Task); + break; + } + + default: + { + bTaskProcessed = false; + break; + } + } + + if (!bTaskProcessed) + break; + } + + if (FPlatformProcess::SupportsMultithreading()) + { + // We want to yield for a bit. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } + else + { + // If we are running in single threaded mode, return so we don't block everything else. + return; + } + } +} + +void +FHoudiniEngineScheduler::TaskProccessAsset(const FHoudiniEngineTask & Task) +{ + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskProccessAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + // Retrieve asset id. + HAPI_NodeId AssetId = Task.AssetId; + if (AssetId == -1) + { + // We have an invalid asset id. + HOUDINI_LOG_ERROR(TEXT("TaskProcessAsset failed for %s: Invalid Asset Id."), *Task.ActorName); + + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset has invalid id.")); + + return; + } + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Processing started for %s., AssetId = %d"), + *Task.ActorName, AssetId); + + // Add processing notification. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::Working, + AssetId, Task, TEXT("Started Cooking")); + + // + + + // TODO: Process results! +} + +bool FHoudiniEngineScheduler::HasPendingTasks() +{ + FScopeLock ScopeLock(&CriticalSection); + return (PositionWrite != PositionRead); +} + +void +FHoudiniEngineScheduler::AddTask(const FHoudiniEngineTask & Task) +{ + FScopeLock ScopeLock(&CriticalSection); + + // Check if we need to grow our circular buffer. + if (PositionWrite + 1 == PositionRead) + { + // Calculate next size (next power of two). + uint32 NextTaskCount = FPlatformMath::RoundUpToPowerOfTwo(TaskCount + 1); + + // Allocate new buffer. + FHoudiniEngineTask * Buffer = static_cast( + FMemory::Malloc(NextTaskCount * sizeof(FHoudiniEngineTask))); + + if (!Buffer) + return; + + // Zero memory. + FMemory::Memset(Buffer, 0x0, NextTaskCount * sizeof(FHoudiniEngineTask)); + + // Copy elements from old buffer to new one. + if (PositionRead < PositionWrite) + { + FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (PositionWrite - PositionRead)); + + // Update index positions. + PositionRead = 0; + PositionWrite = PositionWrite - PositionRead; + } + else + { + FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (TaskCount - PositionRead)); + FMemory::Memcpy(Buffer + TaskCount - PositionRead, Tasks, sizeof(FHoudiniEngineTask) * PositionWrite); + + // Update index positions. + PositionRead = 0; + PositionWrite = TaskCount - PositionRead + PositionWrite; + } + + // Deallocate old buffer. + FMemory::Free(Tasks); + + // Bookkeeping. + Tasks = Buffer; + TaskCount = NextTaskCount; + } + + // Store task. + Tasks[PositionWrite] = Task; + PositionWrite++; + + // Wrap around if required. + PositionWrite &= (TaskCount - 1); +} + +uint32 +FHoudiniEngineScheduler::Run() +{ + ProcessQueuedTasks(); + return 0; +} + +void +FHoudiniEngineScheduler::Stop() +{ + bStopping = true; +} + +void +FHoudiniEngineScheduler::Tick() +{ + ProcessQueuedTasks(); +} + +FSingleThreadRunnable * +FHoudiniEngineScheduler::GetSingleThreadInterface() +{ + return this; +} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h index 2c9354540..a0540d1ea 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h @@ -1,119 +1,119 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineTask.h" -#include "HoudiniEngineTaskInfo.h" -#include "HAL/Runnable.h" -#include "HAL/RunnableThread.h" -#include "Misc/SingleThreadRunnable.h" - -class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable -{ -public: - - FHoudiniEngineScheduler(); - virtual ~FHoudiniEngineScheduler(); - - // FRunnable methods. - virtual uint32 Run() override; - virtual void Stop() override; - FSingleThreadRunnable * GetSingleThreadInterface() override; - - // FSingleThreadRunnable methods. - virtual void Tick() override; - - // Adds a task. - void AddTask(const FHoudiniEngineTask & Task); - - bool HasPendingTasks(); - - // Adds instantiation response task info. - void AddResponseTaskInfo( - HAPI_Result Result, - EHoudiniEngineTaskType TaskType, - EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, - const FHoudiniEngineTask & Task); - - void AddResponseMessageTaskInfo( - HAPI_Result Result, - EHoudiniEngineTaskType TaskType, - EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, - const FHoudiniEngineTask & Task, - const FString & ErrorMessage); - -protected: - - // Process queued tasks. - void ProcessQueuedTasks(); - - // Task : instantiate an asset. - void TaskInstantiateAsset(const FHoudiniEngineTask & Task); - - // Task : cook an asset. - void TaskCookAsset(const FHoudiniEngineTask & Task); - - // Create description of task's state. - void TaskDescription( - FHoudiniEngineTaskInfo & Task, - const FString & ActorName, - const FString & StatusString); - - // Delete an asset. - void TaskDeleteAsset(const FHoudiniEngineTask & Task); - - // Process the result of a sucesfull cook - void TaskProccessAsset(const FHoudiniEngineTask & Task); - -private: - - // Initial number of tasks in our circular queue. - static const uint32 InitialTaskSize; - - // Frequency update (sleep time between each update) - static const float UpdateFrequency; - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // List of scheduled tasks. - FHoudiniEngineTask* Tasks; - - // Head of the circular queue. - uint32 PositionWrite; - - // Tail of the circular queue. - uint32 PositionRead; - - // Size of the circular queue. - uint32 TaskCount; - - // Stopping flag. - bool bStopping; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HAL/Runnable.h" +#include "HAL/RunnableThread.h" +#include "Misc/SingleThreadRunnable.h" + +class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable +{ +public: + + FHoudiniEngineScheduler(); + virtual ~FHoudiniEngineScheduler(); + + // FRunnable methods. + virtual uint32 Run() override; + virtual void Stop() override; + FSingleThreadRunnable * GetSingleThreadInterface() override; + + // FSingleThreadRunnable methods. + virtual void Tick() override; + + // Adds a task. + void AddTask(const FHoudiniEngineTask & Task); + + bool HasPendingTasks(); + + // Adds instantiation response task info. + void AddResponseTaskInfo( + HAPI_Result Result, + EHoudiniEngineTaskType TaskType, + EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, + const FHoudiniEngineTask & Task); + + void AddResponseMessageTaskInfo( + HAPI_Result Result, + EHoudiniEngineTaskType TaskType, + EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, + const FHoudiniEngineTask & Task, + const FString & ErrorMessage); + +protected: + + // Process queued tasks. + void ProcessQueuedTasks(); + + // Task : instantiate an asset. + void TaskInstantiateAsset(const FHoudiniEngineTask & Task); + + // Task : cook an asset. + void TaskCookAsset(const FHoudiniEngineTask & Task); + + // Create description of task's state. + void TaskDescription( + FHoudiniEngineTaskInfo & Task, + const FString & ActorName, + const FString & StatusString); + + // Delete an asset. + void TaskDeleteAsset(const FHoudiniEngineTask & Task); + + // Process the result of a sucesfull cook + void TaskProccessAsset(const FHoudiniEngineTask & Task); + +private: + + // Initial number of tasks in our circular queue. + static const uint32 InitialTaskSize; + + // Frequency update (sleep time between each update) + static const float UpdateFrequency; + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // List of scheduled tasks. + FHoudiniEngineTask* Tasks; + + // Head of the circular queue. + uint32 PositionWrite; + + // Tail of the circular queue. + uint32 PositionRead; + + // Size of the circular queue. + uint32 TaskCount; + + // Stopping flag. + bool bStopping; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineString.cpp b/Source/HoudiniEngine/Private/HoudiniEngineString.cpp index 74d91d273..69214fbd6 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineString.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineString.cpp @@ -1,215 +1,215 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineString.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include - -FHoudiniEngineString::FHoudiniEngineString() - : StringId(-1) -{} - -FHoudiniEngineString::FHoudiniEngineString(int32 InStringId) - : StringId(InStringId) -{} - -FHoudiniEngineString::FHoudiniEngineString(const FHoudiniEngineString& Other) - : StringId(Other.StringId) -{} - -FHoudiniEngineString & -FHoudiniEngineString::operator=(const FHoudiniEngineString& Other) -{ - if (this != &Other) - StringId = Other.StringId; - - return *this; -} - -bool -FHoudiniEngineString::operator==(const FHoudiniEngineString& Other) const -{ - return Other.StringId == StringId; -} - -bool -FHoudiniEngineString::operator!=(const FHoudiniEngineString& Other) const -{ - return Other.StringId != StringId; -} - -int32 -FHoudiniEngineString::GetId() const -{ - return StringId; -} - -bool -FHoudiniEngineString::HasValidId() const -{ - return StringId > 0; -} - -bool -FHoudiniEngineString::ToStdString(std::string& String) const -{ - String = ""; - - // Null string ID / zero should be considered invalid - // (or we'd get the "null string, should never see this!" text) - if (StringId <= 0) - { - return false; - } - - int32 NameLength = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBufLength( - FHoudiniEngine::Get().GetSession(), StringId, &NameLength)) - { - return false; - } - - if (NameLength <= 0) - return false; - - std::vector< char > NameBuffer(NameLength, '\0'); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( - FHoudiniEngine::Get().GetSession(), - StringId, &NameBuffer[0], NameLength ) ) - { - return false; - } - - String = std::string(NameBuffer.begin(), NameBuffer.end()); - - return true; -} - -bool -FHoudiniEngineString::ToFName(FName& Name) const -{ - Name = NAME_None; - FString NameString = TEXT(""); - if (ToFString(NameString)) - { - Name = FName(*NameString); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToFString(FString& String) const -{ - String = TEXT(""); - std::string NamePlain = ""; - - if (ToStdString(NamePlain)) - { - String = UTF8_TO_TCHAR(NamePlain.c_str()); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToFText(FText& Text) const -{ - Text = FText::GetEmpty(); - FString NameString = TEXT(""); - - if (ToFString(NameString)) - { - Text = FText::FromString(NameString); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToStdString(const int32& InStringId, std::string& OutStdString) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToStdString(OutStdString); -} - -bool -FHoudiniEngineString::ToFName(const int32& InStringId, FName& OutName) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFName(OutName); -} - -bool -FHoudiniEngineString::ToFString(const int32& InStringId, FString& OutString) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFString(OutString); -} - -bool -FHoudiniEngineString::ToFText(const int32& InStringId, FText& OutText) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFText(OutText); -} - -bool -FHoudiniEngineString::SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray) -{ - bool bReturn = true; - OutStringArray.SetNumZeroed(InStringIdArray.Num()); - - // Avoid calling HAPI to resolve the same strings again and again - TMap ResolvedStrings; - for (int32 IdxSH = 0; IdxSH < InStringIdArray.Num(); IdxSH++) - { - const int32* ResolvedString = ResolvedStrings.Find(InStringIdArray[IdxSH]); - if (ResolvedString) - { - // Already resolved earlier, copy the string instead of calling HAPI. - OutStringArray[IdxSH] = OutStringArray[*ResolvedString]; - } - else - { - FString CurrentString = FString(); - if(!FHoudiniEngineString::ToFString(InStringIdArray[IdxSH], CurrentString)) - bReturn = false; - - OutStringArray[IdxSH] = CurrentString; - ResolvedStrings.Add(InStringIdArray[IdxSH], IdxSH); - } - } - - return bReturn; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineString.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include + +FHoudiniEngineString::FHoudiniEngineString() + : StringId(-1) +{} + +FHoudiniEngineString::FHoudiniEngineString(int32 InStringId) + : StringId(InStringId) +{} + +FHoudiniEngineString::FHoudiniEngineString(const FHoudiniEngineString& Other) + : StringId(Other.StringId) +{} + +FHoudiniEngineString & +FHoudiniEngineString::operator=(const FHoudiniEngineString& Other) +{ + if (this != &Other) + StringId = Other.StringId; + + return *this; +} + +bool +FHoudiniEngineString::operator==(const FHoudiniEngineString& Other) const +{ + return Other.StringId == StringId; +} + +bool +FHoudiniEngineString::operator!=(const FHoudiniEngineString& Other) const +{ + return Other.StringId != StringId; +} + +int32 +FHoudiniEngineString::GetId() const +{ + return StringId; +} + +bool +FHoudiniEngineString::HasValidId() const +{ + return StringId > 0; +} + +bool +FHoudiniEngineString::ToStdString(std::string& String) const +{ + String = ""; + + // Null string ID / zero should be considered invalid + // (or we'd get the "null string, should never see this!" text) + if (StringId <= 0) + { + return false; + } + + int32 NameLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBufLength( + FHoudiniEngine::Get().GetSession(), StringId, &NameLength)) + { + return false; + } + + if (NameLength <= 0) + return false; + + std::vector< char > NameBuffer(NameLength, '\0'); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( + FHoudiniEngine::Get().GetSession(), + StringId, &NameBuffer[0], NameLength ) ) + { + return false; + } + + String = std::string(NameBuffer.begin(), NameBuffer.end()); + + return true; +} + +bool +FHoudiniEngineString::ToFName(FName& Name) const +{ + Name = NAME_None; + FString NameString = TEXT(""); + if (ToFString(NameString)) + { + Name = FName(*NameString); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToFString(FString& String) const +{ + String = TEXT(""); + std::string NamePlain = ""; + + if (ToStdString(NamePlain)) + { + String = UTF8_TO_TCHAR(NamePlain.c_str()); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToFText(FText& Text) const +{ + Text = FText::GetEmpty(); + FString NameString = TEXT(""); + + if (ToFString(NameString)) + { + Text = FText::FromString(NameString); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToStdString(const int32& InStringId, std::string& OutStdString) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToStdString(OutStdString); +} + +bool +FHoudiniEngineString::ToFName(const int32& InStringId, FName& OutName) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFName(OutName); +} + +bool +FHoudiniEngineString::ToFString(const int32& InStringId, FString& OutString) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFString(OutString); +} + +bool +FHoudiniEngineString::ToFText(const int32& InStringId, FText& OutText) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFText(OutText); +} + +bool +FHoudiniEngineString::SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray) +{ + bool bReturn = true; + OutStringArray.SetNumZeroed(InStringIdArray.Num()); + + // Avoid calling HAPI to resolve the same strings again and again + TMap ResolvedStrings; + for (int32 IdxSH = 0; IdxSH < InStringIdArray.Num(); IdxSH++) + { + const int32* ResolvedString = ResolvedStrings.Find(InStringIdArray[IdxSH]); + if (ResolvedString) + { + // Already resolved earlier, copy the string instead of calling HAPI. + OutStringArray[IdxSH] = OutStringArray[*ResolvedString]; + } + else + { + FString CurrentString = FString(); + if(!FHoudiniEngineString::ToFString(InStringIdArray[IdxSH], CurrentString)) + bReturn = false; + + OutStringArray[IdxSH] = CurrentString; + ResolvedStrings.Add(InStringIdArray[IdxSH], IdxSH); + } + } + + return bReturn; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineString.h b/Source/HoudiniEngine/Private/HoudiniEngineString.h index 521248393..c1f40fea0 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineString.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineString.h @@ -1,74 +1,74 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include -#include "HoudiniApi.h" - -class FText; -class FString; -class FName; - -class HOUDINIENGINE_API FHoudiniEngineString -{ - public: - - FHoudiniEngineString(); - FHoudiniEngineString(int32 InStringId); - FHoudiniEngineString(const FHoudiniEngineString & Other); - - FHoudiniEngineString & operator=(const FHoudiniEngineString & Other); - - bool operator==(const FHoudiniEngineString & Other) const; - bool operator!=(const FHoudiniEngineString & Other) const; - - // Conversion functions - bool ToStdString(std::string & String) const; - bool ToFName(FName & Name) const; - bool ToFString(FString & String) const; - bool ToFText(FText & Text) const; - - // Static converters - static bool ToStdString(const int32& InStringId, std::string & String); - static bool ToFName(const int32& InStringId, FName & Name); - static bool ToFString(const int32& InStringId, FString & String); - static bool ToFText(const int32& InStringId, FText & Text); - - // Array converter, uses a map to avoid redudant calls to HAPI - static bool SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray); - - // Return id of this string. - int32 GetId() const; - - // Return true if this string has a valid id. - bool HasValidId() const; - - protected: - - // Id of the underlying Houdini Engine string. - int32 StringId; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include +#include "HoudiniApi.h" + +class FText; +class FString; +class FName; + +class HOUDINIENGINE_API FHoudiniEngineString +{ + public: + + FHoudiniEngineString(); + FHoudiniEngineString(int32 InStringId); + FHoudiniEngineString(const FHoudiniEngineString & Other); + + FHoudiniEngineString & operator=(const FHoudiniEngineString & Other); + + bool operator==(const FHoudiniEngineString & Other) const; + bool operator!=(const FHoudiniEngineString & Other) const; + + // Conversion functions + bool ToStdString(std::string & String) const; + bool ToFName(FName & Name) const; + bool ToFString(FString & String) const; + bool ToFText(FText & Text) const; + + // Static converters + static bool ToStdString(const int32& InStringId, std::string & String); + static bool ToFName(const int32& InStringId, FName & Name); + static bool ToFString(const int32& InStringId, FString & String); + static bool ToFText(const int32& InStringId, FText & Text); + + // Array converter, uses a map to avoid redudant calls to HAPI + static bool SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray); + + // Return id of this string. + int32 GetId() const; + + // Return true if this string has a valid id. + bool HasValidId() const; + + protected: + + // Id of the underlying Houdini Engine string. + int32 StringId; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp index f9c98f390..a1d759193 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp @@ -1,48 +1,48 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineTask.h" - -#include "HoudiniApi.h" - -FHoudiniEngineTask::FHoudiniEngineTask() - : TaskType(EHoudiniEngineTaskType::None) - , ActorName(TEXT("")) - , AssetId(-1) - , AssetLibraryId(-1) - , AssetHapiName(-1) -{ - HapiGUID.Invalidate(); -} - -FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID) - : HapiGUID(InHapiGUID) - , TaskType(InTaskType) - , ActorName(TEXT("")) - , AssetId(-1) - , AssetLibraryId(-1) - , AssetHapiName(-1) -{} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineTask.h" + +#include "HoudiniApi.h" + +FHoudiniEngineTask::FHoudiniEngineTask() + : TaskType(EHoudiniEngineTaskType::None) + , ActorName(TEXT("")) + , AssetId(-1) + , AssetLibraryId(-1) + , AssetHapiName(-1) +{ + HapiGUID.Invalidate(); +} + +FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID) + : HapiGUID(InHapiGUID) + , TaskType(InTaskType) + , ActorName(TEXT("")) + , AssetId(-1) + , AssetLibraryId(-1) + , AssetHapiName(-1) +{} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.h b/Source/HoudiniEngine/Private/HoudiniEngineTask.h index 2d620a3c3..f17c9712d 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.h @@ -1,100 +1,100 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniApi.h" -#include "CoreMinimal.h" -#include "Misc/Guid.h" -#include "UObject/WeakObjectPtr.h" - -/* -namespace EHoudiniEngineTaskType -{ - enum Type - { - None, - - // This type corresponds to Houdini asset instantiation (without cooking). - AssetInstantiation, - - // This type corresponds to Houdini asset cooking request. - AssetCooking, - - // This type is used for asynchronous asset deletion. - AssetDeletion - }; -} -*/ - -UENUM() -enum class EHoudiniEngineTaskType : uint8 -{ - None, - - // This type corresponds to Houdini asset instantiation (without cooking). - AssetInstantiation, - - // This type corresponds to Houdini asset cooking request. - AssetCooking, - - // This type is used for asynchronous asset deletion. - AssetDeletion, - - // This type is used when processing the results of a sucessful cook - AssetProcess, -}; - -struct HOUDINIENGINE_API FHoudiniEngineTask -{ - // Constructors. - FHoudiniEngineTask(); - FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID); - - // GUID of this request. - FGuid HapiGUID; - - // Type of this task. - EHoudiniEngineTaskType TaskType; - - // Houdini asset for instantiation. - TWeakObjectPtr< class UHoudiniAsset > Asset; - - // Name of the actor requesting this task. - FString ActorName; - - // Asset Id. - HAPI_NodeId AssetId; - - // Library Id. - HAPI_AssetLibraryId AssetLibraryId; - - // HAPI name of the asset. - int32 AssetHapiName; - - // Is set to true if component has been loaded. - //bool bLoadedComponent; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniApi.h" +#include "CoreMinimal.h" +#include "Misc/Guid.h" +#include "UObject/WeakObjectPtr.h" + +/* +namespace EHoudiniEngineTaskType +{ + enum Type + { + None, + + // This type corresponds to Houdini asset instantiation (without cooking). + AssetInstantiation, + + // This type corresponds to Houdini asset cooking request. + AssetCooking, + + // This type is used for asynchronous asset deletion. + AssetDeletion + }; +} +*/ + +UENUM() +enum class EHoudiniEngineTaskType : uint8 +{ + None, + + // This type corresponds to Houdini asset instantiation (without cooking). + AssetInstantiation, + + // This type corresponds to Houdini asset cooking request. + AssetCooking, + + // This type is used for asynchronous asset deletion. + AssetDeletion, + + // This type is used when processing the results of a sucessful cook + AssetProcess, +}; + +struct HOUDINIENGINE_API FHoudiniEngineTask +{ + // Constructors. + FHoudiniEngineTask(); + FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID); + + // GUID of this request. + FGuid HapiGUID; + + // Type of this task. + EHoudiniEngineTaskType TaskType; + + // Houdini asset for instantiation. + TWeakObjectPtr< class UHoudiniAsset > Asset; + + // Name of the actor requesting this task. + FString ActorName; + + // Asset Id. + HAPI_NodeId AssetId; + + // Library Id. + HAPI_AssetLibraryId AssetLibraryId; + + // HAPI name of the asset. + int32 AssetHapiName; + + // Is set to true if component has been loaded. + //bool bLoadedComponent; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp index f104eacde..d63feab38 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp @@ -1,47 +1,47 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineTaskInfo.h" - -#include "HAPI/HAPI_Common.h" - -FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo() - : Result(HAPI_RESULT_SUCCESS) - , AssetId(-1) - , TaskType(EHoudiniEngineTaskType::None) - , TaskState(EHoudiniEngineTaskState::None) -{} - -FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo( - HAPI_Result InResult, - HAPI_NodeId InAssetId, - EHoudiniEngineTaskType InTaskType, - EHoudiniEngineTaskState InTaskState) - : Result(InResult) - , AssetId(InAssetId) - , TaskType(InTaskType) - , TaskState(InTaskState) +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineTaskInfo.h" + +#include "HAPI/HAPI_Common.h" + +FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo() + : Result(HAPI_RESULT_SUCCESS) + , AssetId(-1) + , TaskType(EHoudiniEngineTaskType::None) + , TaskState(EHoudiniEngineTaskState::None) +{} + +FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo( + HAPI_Result InResult, + HAPI_NodeId InAssetId, + EHoudiniEngineTaskType InTaskType, + EHoudiniEngineTaskState InTaskState) + : Result(InResult) + , AssetId(InAssetId) + , TaskType(InTaskType) + , TaskState(InTaskState) {} \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h index c5831d8a6..79e6aceb1 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h @@ -1,78 +1,78 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineTask.h" - -UENUM() -enum class EHoudiniEngineTaskState : uint8 -{ - None, - - // Indicates the current task is still running - Working, - - // Indicates the task has successfully finished - Success, - - // Indicates the task has finished with non fatal errors - FinishedWithError, - - // Indicates the task has finished with fatal errors and should be terminated - FinishedWithFatalError, - - // Indicates the task has been aborted (unused) - Aborted -}; - -struct HOUDINIENGINE_API FHoudiniEngineTaskInfo -{ - // Constructors. - FHoudiniEngineTaskInfo(); - FHoudiniEngineTaskInfo( - HAPI_Result InResult, HAPI_NodeId InAssetId, - EHoudiniEngineTaskType InTaskType, - EHoudiniEngineTaskState InTaskState); - - // Current HAPI result. - HAPI_Result Result; - - // Current Asset Id. - HAPI_NodeId AssetId; - - // Type of task. - EHoudiniEngineTaskType TaskType; - - // Current status. - EHoudiniEngineTaskState TaskState; - - // String used for status / progress bar. - FText StatusText; - - // Is set to true if corresponding task was issued for loaded component. - //bool bLoadedComponent; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineTask.h" + +UENUM() +enum class EHoudiniEngineTaskState : uint8 +{ + None, + + // Indicates the current task is still running + Working, + + // Indicates the task has successfully finished + Success, + + // Indicates the task has finished with non fatal errors + FinishedWithError, + + // Indicates the task has finished with fatal errors and should be terminated + FinishedWithFatalError, + + // Indicates the task has been aborted (unused) + Aborted +}; + +struct HOUDINIENGINE_API FHoudiniEngineTaskInfo +{ + // Constructors. + FHoudiniEngineTaskInfo(); + FHoudiniEngineTaskInfo( + HAPI_Result InResult, HAPI_NodeId InAssetId, + EHoudiniEngineTaskType InTaskType, + EHoudiniEngineTaskState InTaskState); + + // Current HAPI result. + HAPI_Result Result; + + // Current Asset Id. + HAPI_NodeId AssetId; + + // Type of task. + EHoudiniEngineTaskType TaskType; + + // Current status. + EHoudiniEngineTaskState TaskState; + + // String used for status / progress bar. + FText StatusText; + + // Is set to true if corresponding task was issued for loaded component. + //bool bLoadedComponent; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp index 439fcabbe..112105740 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp @@ -1,5278 +1,5348 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineUtils.h" -#include "Misc/StringFormatArg.h" - -#if PLATFORM_WINDOWS - #include "Windows/WindowsHWrapper.h" - - // Of course, Windows defines its own GetGeoInfo, - // So we need to undefine that before including HoudiniApi.h to avoid collision... - #ifdef GetGeoInfo - #undef GetGeoInfo - #endif -#endif - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniEngineString.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInput.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniEngineRuntime.h" - -#if WITH_EDITOR - #include "SAssetSelectionWidget.h" -#endif - -#include "HAPI/HAPI_Version.h" - -#include "Misc/Paths.h" -#include "Editor/EditorEngine.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" -#include "PropertyEditorModule.h" -#include "Modules/ModuleManager.h" -#include "Engine/StaticMeshSocket.h" -#include "Async/Async.h" -#include "BlueprintEditor.h" -#include "Toolkits/AssetEditorManager.h" -#include "Engine/BlueprintGeneratedClass.h" -#include "UObject/MetaData.h" -#include "RawMesh.h" -#include "Widgets/Notifications/SNotificationList.h" -#include "Framework/Notifications/NotificationManager.h" -#include "Interfaces/IPluginManager.h" -//#include "Kismet/BlueprintEditor.h" -#include "SSCSEditor.h" -#include "Engine/WorldComposition.h" - -#if WITH_EDITOR - #include "Interfaces/IMainFrameModule.h" -#endif - -#include - -#include "AssetRegistryModule.h" -#include "FileHelpers.h" -#include "Factories/WorldFactory.h" -#include "HAL/FileManager.h" - -#if WITH_EDITOR - #include "EditorModeManager.h" - #include "EditorModes.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// HAPI_Result strings -const FString kResultStringSuccess(TEXT("Success")); -const FString kResultStringFailure(TEXT("Generic Failure")); -const FString kResultStringAlreadyInitialized(TEXT("Already Initialized")); -const FString kResultStringNotInitialized(TEXT("Not Initialized")); -const FString kResultStringCannotLoadFile(TEXT("Unable to Load File")); -const FString kResultStringParmSetFailed(TEXT("Failed Setting Parameter")); -const FString kResultStringInvalidArgument(TEXT("Invalid Argument")); -const FString kResultStringCannotLoadGeo(TEXT("Uneable to Load Geometry")); -const FString kResultStringCannotGeneratePreset(TEXT("Uneable to Generate Preset")); -const FString kResultStringCannotLoadPreset(TEXT("Uneable to Load Preset")); -const FString kResultStringAssetDefAlrealdyLoaded(TEXT("Asset definition already loaded")); -const FString kResultStringNoLicenseFound(TEXT("No License Found")); -const FString kResultStringDisallowedNCLicenseFound(TEXT("Disallowed Non Commercial License found")); -const FString kResultStringDisallowedNCAssetWithCLicense(TEXT("Disallowed Non Commercial Asset With Commercial License")); -const FString kResultStringDisallowedNCAssetWithLCLicense(TEXT("Disallowed Non Commercial Asset With Limited Commercial License")); -const FString kResultStringDisallowedLCAssetWithCLicense(TEXT("Disallowed Limited Commercial Asset With Commercial License")); -const FString kResultStringDisallowedHengineIndieWith3PartyPlugin(TEXT("Disallowed Houdini Engine Indie With 3rd Party Plugin")); -const FString kResultStringAssetInvalid(TEXT("Invalid Asset")); -const FString kResultStringNodeInvalid(TEXT("Invalid Node")); -const FString kResultStringUserInterrupted(TEXT("User Interrupt")); -const FString kResultStringInvalidSession(TEXT("Invalid Session")); -const FString kResultStringUnknowFailure(TEXT("Unknown Failure")); - -#define DebugTextLine TEXT("===================================") - -const int32 -FHoudiniEngineUtils::PackageGUIDComponentNameLength = 12; - -const int32 -FHoudiniEngineUtils::PackageGUIDItemNameLength = 8; - -const FString -FHoudiniEngineUtils::GetErrorDescription(HAPI_Result Result) -{ - if (Result == HAPI_RESULT_SUCCESS) - { - return kResultStringSuccess; - } - else - { - switch (Result) - { - case HAPI_RESULT_FAILURE: - { - return kResultStringFailure; - } - - case HAPI_RESULT_ALREADY_INITIALIZED: - { - return kResultStringAlreadyInitialized; - } - - case HAPI_RESULT_NOT_INITIALIZED: - { - return kResultStringNotInitialized; - } - - case HAPI_RESULT_CANT_LOADFILE: - { - return kResultStringCannotLoadFile; - } - - case HAPI_RESULT_PARM_SET_FAILED: - { - return kResultStringParmSetFailed; - } - - case HAPI_RESULT_INVALID_ARGUMENT: - { - return kResultStringInvalidArgument; - } - - case HAPI_RESULT_CANT_LOAD_GEO: - { - return kResultStringCannotLoadGeo; - } - - case HAPI_RESULT_CANT_GENERATE_PRESET: - { - return kResultStringCannotGeneratePreset; - } - - case HAPI_RESULT_CANT_LOAD_PRESET: - { - return kResultStringCannotLoadPreset; - } - - case HAPI_RESULT_ASSET_DEF_ALREADY_LOADED: - { - return kResultStringAssetDefAlrealdyLoaded; - } - - case HAPI_RESULT_NO_LICENSE_FOUND: - { - return kResultStringNoLicenseFound; - } - - case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: - { - return kResultStringDisallowedNCLicenseFound; - } - - case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: - { - return kResultStringDisallowedNCAssetWithCLicense; - } - - case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: - { - return kResultStringDisallowedNCAssetWithLCLicense; - } - - case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: - { - return kResultStringDisallowedLCAssetWithCLicense; - } - - case HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN: - { - return kResultStringDisallowedHengineIndieWith3PartyPlugin; - } - - case HAPI_RESULT_ASSET_INVALID: - { - return kResultStringAssetInvalid; - } - - case HAPI_RESULT_NODE_INVALID: - { - return kResultStringNodeInvalid; - } - - case HAPI_RESULT_USER_INTERRUPTED: - { - return kResultStringUserInterrupted; - } - - case HAPI_RESULT_INVALID_SESSION: - { - return kResultStringInvalidSession; - } - - default: - { - return kResultStringUnknowFailure; - } - }; - } -} - -const FString -FHoudiniEngineUtils::GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity) -{ - const HAPI_Session* SessionPtr = FHoudiniEngine::Get().GetSession(); - if (!SessionPtr) - { - // No valid session - return FString(TEXT("No valid Houdini Engine session.")); - } - - int32 StatusBufferLength = 0; - HAPI_Result Result = FHoudiniApi::GetStatusStringBufLength( - SessionPtr, status_type, verbosity, &StatusBufferLength); - - if (Result == HAPI_RESULT_INVALID_SESSION) - { - // Let FHoudiniEngine know that the sesion is now invalid to "Stop" the invalid session - // and clean things up - FHoudiniEngine::Get().OnSessionLost(); - } - - if (StatusBufferLength > 0) - { - TArray< char > StatusStringBuffer; - StatusStringBuffer.SetNumZeroed(StatusBufferLength); - FHoudiniApi::GetStatusString( - SessionPtr, status_type, &StatusStringBuffer[0], StatusBufferLength); - - return FString(UTF8_TO_TCHAR(&StatusStringBuffer[0])); - } - - return FString(TEXT("")); -} - -const FString -FHoudiniEngineUtils::GetCookResult() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_MESSAGES); -} - -const FString -FHoudiniEngineUtils::GetCookState() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); -} - -const FString -FHoudiniEngineUtils::GetErrorDescription() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS); -} - -const FString -FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId) -{ - int32 NodeErrorLength = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeNodeCookResult( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_StatusVerbosity::HAPI_STATUSVERBOSITY_ALL, &NodeErrorLength)) - { - NodeErrorLength = 0; - } - - FString NodeError; - if (NodeErrorLength > 0) - { - TArray NodeErrorBuffer; - NodeErrorBuffer.SetNumZeroed(NodeErrorLength); - FHoudiniApi::GetComposedNodeCookResult( - FHoudiniEngine::Get().GetSession(), &NodeErrorBuffer[0], NodeErrorLength); - - NodeError = FString(UTF8_TO_TCHAR(&NodeErrorBuffer[0])); - } - - return NodeError; -} - -const FString -FHoudiniEngineUtils::GetCookLog(TArray& InHACs) -{ - FString CookLog; - - // Get fetch cook status. - FString CookResult = FHoudiniEngineUtils::GetCookResult(); - if (!CookResult.IsEmpty()) - CookLog += TEXT("Cook Results:\n") + CookResult + TEXT("\n\n"); - - // Add the cook state - FString CookState = FHoudiniEngineUtils::GetCookState(); - if (!CookState.IsEmpty()) - CookLog += TEXT("Cook State:\n") + CookState + TEXT("\n\n"); - - // Error Description - FString Error = FHoudiniEngineUtils::GetErrorDescription(); - if (!Error.IsEmpty()) - CookLog += TEXT("Error Description:\n") + Error + TEXT("\n\n"); - - // Iterates on all the selected HAC and get their node errors - for (auto& HAC : InHACs) - { - if (!HAC || HAC->IsPendingKill()) - continue; - - // Get the node errors, warnings and messages - FString NodeErrors = FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(HAC->GetAssetId()); - if (NodeErrors.IsEmpty()) - continue; - - CookLog += NodeErrors; - } - - if (CookLog.IsEmpty()) - { - // See if a failed HAPI initialization / invalid session is preventing us from getting the cook log - if (!FHoudiniApi::IsHAPIInitialized()) - { - CookLog += TEXT("\n\nThe Houdini Engine API Library (HAPI) has not been initialized properly.\n\n"); - } - else - { - const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) - { - CookLog += TEXT("\n\nThe current Houdini Engine Session is not valid.\n\n"); - } - else if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) - { - CookLog += TEXT("\n\nThe current Houdini Engine Session has not been initialized properly.\n\n"); - } - } - - if (!CookLog.IsEmpty()) - { - CookLog += TEXT("Please try to restart the current Houdini Engine session via File > Restart Houdini Engine Session.\n\n"); - } - else - { - CookLog = TEXT("\n\nThe cook log is empty...\n\n"); - } - } - - return CookLog; -} - -const FString -FHoudiniEngineUtils::GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - FString HelpString = TEXT(""); - if (!HoudiniAssetComponent) - return HelpString; - - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HAPI_NodeId AssetId = HoudiniAssetComponent->GetAssetId(); - if (AssetId < 0) - return HelpString; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), HelpString); - - if (!FHoudiniEngineString::ToFString(AssetInfo.helpTextSH, HelpString)) - return HelpString; - - if (HelpString.IsEmpty()) - HelpString = TEXT("No Asset Help Found"); - - return HelpString; -} - -void -FHoudiniEngineUtils::ConvertUnrealString(const FString & UnrealString, std::string & String) -{ - String = TCHAR_TO_UTF8(*UnrealString); -} - -UWorld* -FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreateMissingPackage, bool& bOutCreatedPackage) -{ - AActor* Result = nullptr; - UWorld* PackageWorld = nullptr; - - bOutCreatedPackage = false; - - // Try to load existing UWorld from the tile package path. - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!Package) - Package = LoadPackage(nullptr, *PackagePath, LOAD_None); - if (Package) - { - // If the package is not valid (pending kill) rename it - if (Package->IsPendingKill()) - { - if (bCreateMissingPackage) - { - Package->Rename( - *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); - } - } - else - { - PackageWorld = UWorld::FindWorldInPackage(Package); - } - } - - if (!IsValid(PackageWorld) && bCreateMissingPackage) - { - // The map for this tile does not exist. Create one - UWorldFactory* Factory = NewObject(); - Factory->WorldType = EWorldType::Inactive; // World that is being loaded but not currently edited by editor. - PackageWorld = CastChecked(Factory->FactoryCreateNew(UWorld::StaticClass(), Package, NAME_None, RF_Public | RF_Standalone, NULL, GWarn)); - - if (IsValid(PackageWorld)) - { - PackageWorld->PostEditChange(); - PackageWorld->MarkPackageDirty(); - - if(FPackageName::IsValidLongPackageName(PackagePath)) - { - const FString PackageFilename = FPackageName::LongPackageNameToFilename(PackagePath); - bool bSaved = FEditorFileUtils::SaveLevel(PackageWorld->PersistentLevel, *PackageFilename); - } - - FAssetRegistryModule::AssetCreated(PackageWorld); - - bOutCreatedPackage = true; - } - } - - return PackageWorld; -} - -bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - UWorld* CurrentWorld, - const FString& PackagePath, - bool bCreateMissingPackage, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bOutPackageCreated, - bool& bPackageInWorld) -{ - UWorld* PackageWorld = FindWorldInPackage(PackagePath, bCreateMissingPackage, bOutPackageCreated); - if (!IsValid(PackageWorld)) - return false; - - if (PackageWorld->PersistentLevel == CurrentWorld->PersistentLevel) - { - // The loaded world and the package world is one and the same. - OutWorld = CurrentWorld; - OutLevel = CurrentWorld->PersistentLevel; - bPackageInWorld = true; - return true; - } - - if (CurrentWorld->GetLevels().Contains(PackageWorld->PersistentLevel)) - { - // The package level is loaded into CurrentWorld. - OutWorld = CurrentWorld; - OutLevel = PackageWorld->PersistentLevel; - bPackageInWorld = true; - return true; - } - - // The package level is not loaded at all. Send back the on-disk assets. - OutWorld = PackageWorld; - OutLevel = PackageWorld->PersistentLevel; - bPackageInWorld = false; - return true; -} - -void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) -{ - FString WorldPath = FPaths::GetPath(InWorld->GetPathName()); - IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); - TArray Packages; - Packages.Add(WorldPath); - AssetRegistry.ScanPathsSynchronous(Packages, true); -} - -AActor* FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) -{ - // AActor* NamedActor = FindObject(Outer, *InName, false); - // Find ANY actor in the world matching the given name. - AActor* NamedActor = FindActorInWorld(InWorld, FName(InName)); - OutFoundActor = NamedActor; - bool bShouldRename = false; - if (NamedActor) - { - if (NamedActor->GetClass()->IsChildOf(InClass) && !NamedActor->IsPendingKill()) - { - return NamedActor; - } - else - { - FString Suffix; - bool bShouldUpdateLabel = false; - if (NamedActor->IsPendingKill()) - Suffix = "_pendingkill"; - else - Suffix = "_0"; // A previous actor that had the same name. - const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName+Suffix); - } - } - return nullptr; -} - -void FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) -{ - LogPackageInfo( LoadPackage(nullptr, *InLongPackageName, 0) ); -} - -void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) -{ - HOUDINI_LOG_MESSAGE(DebugTextLine); - HOUDINI_LOG_MESSAGE(TEXT("= LogPackageInfo")); - if (!IsValid(InPackage)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Invalid package.")); - HOUDINI_LOG_MESSAGE(DebugTextLine); - return; - } - - HOUDINI_LOG_MESSAGE(TEXT(" = Filename: %s"), *(InPackage->FileName.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = Package Id: %d"), InPackage->GetPackageId().ValueForDebugging()); - HOUDINI_LOG_MESSAGE(TEXT(" = File size: %d"), InPackage->FileSize); - HOUDINI_LOG_MESSAGE(TEXT(" = Contains map: %d"), InPackage->ContainsMap()); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Fully Loaded: %d"), InPackage->IsFullyLoaded()); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Dirty: %d"), InPackage->IsDirty()); - - if (InPackage->WorldTileInfo.IsValid()) - { - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Position: %s"), *(InPackage->WorldTileInfo->Position.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Absolute Position: %s"), *(InPackage->WorldTileInfo->AbsolutePosition.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Bounds: %s"), *(InPackage->WorldTileInfo->Bounds.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - HidInTileView: %d"), InPackage->WorldTileInfo->bHideInTileView); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - ZOrder: %d"), InPackage->WorldTileInfo->ZOrder); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Parent tile package: %s"), *(InPackage->WorldTileInfo->ParentTilePackageName)); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo: NULL")); - } - - HOUDINI_LOG_MESSAGE(DebugTextLine); -} - -void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) -{ - UPackage* Package = LoadPackage(nullptr, *InLongPackageName, 0); - UWorld* World = nullptr; - - if (IsValid(Package)) - { - World = UWorld::FindWorldInPackage(Package); - } - - LogWorldInfo(World); -} - -void FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) -{ - - HOUDINI_LOG_MESSAGE(DebugTextLine); - HOUDINI_LOG_MESSAGE(TEXT("= LogWorldInfo")); - if (!IsValid(InWorld)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Invalid world.")); - HOUDINI_LOG_MESSAGE(DebugTextLine); - return; - } - - // UWorld lacks const-correctness on certain accessors - UWorld* NonConstWorld = const_cast(InWorld); - - HOUDINI_LOG_MESSAGE(TEXT(" = Path Name: %s"), *(InWorld->GetPathName())); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Editor World: %d"), InWorld->IsEditorWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Game World: %d"), InWorld->IsGameWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Preview World: %d"), InWorld->IsPreviewWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Actor Count: %d"), NonConstWorld->GetActorCount() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Num Levels: %d"), InWorld->GetNumLevels() ); - - if (IsValid(InWorld->WorldComposition)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Composition - Num Tiles: %d"), InWorld->WorldComposition->GetTilesList().Num() ); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT(" = World Composition NULL") ); - } - - - - HOUDINI_LOG_MESSAGE(DebugTextLine); -} - -FString -FHoudiniEngineUtils::HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType) -{ - switch (InEventType) - { - case HAPI_PDG_EVENT_NULL: - return TEXT("HAPI_PDG_EVENT_NULL"); - - case HAPI_PDG_EVENT_WORKITEM_ADD: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE"); - case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_DEP"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_PARENT"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT"); - - case HAPI_PDG_EVENT_NODE_CLEAR: - return TEXT("HAPI_PDG_EVENT_NODE_CLEAR"); - - case HAPI_PDG_EVENT_COOK_ERROR: - return TEXT("HAPI_PDG_EVENT_COOK_ERROR"); - case HAPI_PDG_EVENT_COOK_WARNING: - return TEXT("HAPI_PDG_EVENT_COOK_WARNING"); - - case HAPI_PDG_EVENT_COOK_COMPLETE: - return TEXT("HAPI_PDG_EVENT_COOK_COMPLETE"); - - case HAPI_PDG_EVENT_DIRTY_START: - return TEXT("HAPI_PDG_EVENT_DIRTY_START"); - case HAPI_PDG_EVENT_DIRTY_STOP: - return TEXT("HAPI_PDG_EVENT_DIRTY_STOP"); - - case HAPI_PDG_EVENT_DIRTY_ALL: - return TEXT("HAPI_PDG_EVENT_DIRTY_ALL"); - - case HAPI_PDG_EVENT_UI_SELECT: - return TEXT("HAPI_PDG_EVENT_UI_SELECT"); - - case HAPI_PDG_EVENT_NODE_CREATE: - return TEXT("HAPI_PDG_EVENT_NODE_CREATE"); - case HAPI_PDG_EVENT_NODE_REMOVE: - return TEXT("HAPI_PDG_EVENT_NODE_REMOVE"); - case HAPI_PDG_EVENT_NODE_RENAME: - return TEXT("HAPI_PDG_EVENT_NODE_RENAME"); - case HAPI_PDG_EVENT_NODE_CONNECT: - return TEXT("HAPI_PDG_EVENT_NODE_CONNECT"); - case HAPI_PDG_EVENT_NODE_DISCONNECT: - return TEXT("HAPI_PDG_EVENT_NODE_DISCONNECT"); - - case HAPI_PDG_EVENT_WORKITEM_SET_INT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_INT"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FLOAT"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_STRING: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_STRING"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_FILE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FILE"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_MERGE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_MERGE"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_RESULT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_RESULT"); - - case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED - return TEXT("HAPI_PDG_EVENT_WORKITEM_PRIORITY"); - - case HAPI_PDG_EVENT_COOK_START: - return TEXT("HAPI_PDG_EVENT_COOK_START"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR"); - - case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: - return TEXT("HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE"); - - case HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED: - return TEXT("HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED"); - - case HAPI_PDG_EVENT_ALL: - return TEXT("HAPI_PDG_EVENT_ALL"); - case HAPI_PDG_EVENT_LOG: - return TEXT("HAPI_PDG_EVENT_LOG"); - - case HAPI_PDG_EVENT_SCHEDULER_ADDED: - return TEXT("HAPI_PDG_EVENT_SCHEDULER_ADDED"); - case HAPI_PDG_EVENT_SCHEDULER_REMOVED: - return TEXT("HAPI_PDG_EVENT_SCHEDULER_REMOVED"); - case HAPI_PDG_EVENT_SET_SCHEDULER: - return TEXT("HAPI_PDG_EVENT_SET_SCHEDULER"); - - case HAPI_PDG_EVENT_SERVICE_MANAGER_ALL: - return TEXT("HAPI_PDG_EVENT_SERVICE_MANAGER_ALL"); - - case HAPI_PDG_CONTEXT_EVENTS: - return TEXT("HAPI_PDG_CONTEXT_EVENTS"); - default: - break; - } - - return FString::Printf(TEXT("Unknown HAPI_PDG_EventType %d"), InEventType); -} - -FString -FHoudiniEngineUtils::HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState) -{ - switch (InWorkitemState) - { - case HAPI_PDG_WORKITEM_UNDEFINED: - return TEXT("HAPI_PDG_WORKITEM_UNDEFINED"); - case HAPI_PDG_WORKITEM_UNCOOKED: - return TEXT("HAPI_PDG_WORKITEM_UNCOOKED"); - case HAPI_PDG_WORKITEM_WAITING: - return TEXT("HAPI_PDG_WORKITEM_WAITING"); - case HAPI_PDG_WORKITEM_SCHEDULED: - return TEXT("HAPI_PDG_WORKITEM_SCHEDULED"); - case HAPI_PDG_WORKITEM_COOKING: - return TEXT("HAPI_PDG_WORKITEM_COOKING"); - case HAPI_PDG_WORKITEM_COOKED_SUCCESS: - return TEXT("HAPI_PDG_WORKITEM_COOKED_SUCCESS"); - case HAPI_PDG_WORKITEM_COOKED_CACHE: - return TEXT("HAPI_PDG_WORKITEM_COOKED_CACHE"); - case HAPI_PDG_WORKITEM_COOKED_FAIL: - return TEXT("HAPI_PDG_WORKITEM_COOKED_FAIL"); - case HAPI_PDG_WORKITEM_COOKED_CANCEL: - return TEXT("HAPI_PDG_WORKITEM_COOKED_CANCEL"); - case HAPI_PDG_WORKITEM_DIRTY: - return TEXT("HAPI_PDG_WORKITEM_DIRTY"); - default: - break; - } - - return FString::Printf(TEXT("Unknown HAPI_PDG_WorkitemState %d"), InWorkitemState); -} - - -// Centralized call to track renaming of objects -bool FHoudiniEngineUtils::RenameObject(UObject* Object, const TCHAR* NewName /*= nullptr*/, UObject* NewOuter /*= nullptr*/, ERenameFlags Flags /*= REN_None*/) -{ - check(Object); - if (AActor* Actor = Cast(Object)) - { - if (Actor->IsPackageExternal()) - { - // There should be no need to choose a specific name for an actor in Houdini Engine, instead setting its label should be enough. - if (FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, NewName)) - { - HOUDINI_LOG_WARNING(TEXT("Called SetActorLabel(%s) on external actor %s instead of Rename : Explicit naming of an actor that is saved in its own external package is prone to cause name clashes when submitting the file.)"), NewName, *Actor->GetName()); - } - // Force to return false (make sure nothing in Houdini Engine plugin relies on actor being renamed to provided name) - return false; - } - } - return Object->Rename(NewName, NewOuter, Flags); -} - -FName -FHoudiniEngineUtils::RenameToUniqueActor(AActor* InActor, const FString& InName) -{ - const FName NewName = MakeUniqueObjectName(InActor->GetOuter(), InActor->GetClass(), FName(InName)); - - FHoudiniEngineUtils::RenameObject(InActor, *(NewName.ToString())); - FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName.ToString()); - - return NewName; -} - -UObject* -FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) -{ - check(InActor); - - UObject* PrevObj = nullptr; - UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, InActor->GetOuter(), *InName, true); - if (ExistingObject && ExistingObject != InActor) - { - // Rename the existing object - const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName + TEXT("_old"))); - FHoudiniEngineUtils::RenameObject(ExistingObject, *(NewName.ToString())); - PrevObj = ExistingObject; - } - - FHoudiniEngineUtils::RenameObject(InActor, *InName); - - if (UpdateLabel) - { - //InActor->SetActorLabel(InName, true); - FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, InName); - InActor->Modify(true); - } - - return PrevObj; -} - -void -FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - FHoudiniPackageParams& OutPackageParams, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FString &BakeFolder, - const FString &ObjectName, - const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode, - bool bAutomaticallySetAttemptToLoadMissingPackages) -{ - OutPackageParams.GeoId = InIdentifier.GeoId; - OutPackageParams.ObjectId = InIdentifier.ObjectId; - OutPackageParams.PartId = InIdentifier.PartId; - OutPackageParams.BakeFolder = BakeFolder; - OutPackageParams.PackageMode = EPackageMode::Bake; - OutPackageParams.ReplaceMode = InReplaceMode; - OutPackageParams.HoudiniAssetName = HoudiniAssetName; - OutPackageParams.HoudiniAssetActorName = HoudiniAssetName; - OutPackageParams.ObjectName = ObjectName; -} - -void -FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - UWorld* const InWorldContext, - const UHoudiniAssetComponent* HoudiniAssetComponent, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FHoudiniOutputObject& InOutputObject, - const FString &InDefaultObjectName, - const FString &InHoudiniAssetName, - FHoudiniPackageParams& OutPackageParams, - FHoudiniAttributeResolver& OutResolver, - const FString &InDefaultBakeFolder, - EPackageReplaceMode InReplaceMode, - bool bAutomaticallySetAttemptToLoadMissingPackages, - bool bInSkipObjectNameResolutionAndUseDefault, - bool bInSkipBakeFolderResolutionAndUseDefault) -{ - // Configure OutPackageParams with the default (UI value first then fallback to default from settings) object name - // and bake folder. We use the "initial" PackageParams as a helper to populate tokens for the resolver. - // - // User specified attributes (eg unreal_bake_folder) are then resolved, with the defaults being those tokens configured - // from the initial PackageParams. Once resolved, we updated the relevant fields in PackageParams - // (ObjectName and BakeFolder), and update the resolver tokens with these final values. - // - // The resolver is then ready to be used to resolve the rest of the user attributes, such as unreal_level_path. - // - const FString DefaultBakeFolder = !InDefaultBakeFolder.IsEmpty() ? InDefaultBakeFolder : - FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - const bool bHasBakeNameUIOverride = !InOutputObject.BakeName.IsEmpty(); - FillInPackageParamsForBakingOutput( - OutPackageParams, - InIdentifier, - DefaultBakeFolder, - bHasBakeNameUIOverride ? InOutputObject.BakeName : InDefaultObjectName, - InHoudiniAssetName, - InReplaceMode, - bAutomaticallySetAttemptToLoadMissingPackages); - - const TMap& CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); - OutResolver.SetCachedAttributes(CachedAttributes); - OutResolver.SetTokensFromStringMap(Tokens); - -#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING - // Log the cached attributes and tokens for debugging - OutResolver.LogCachedAttributesAndTokens(); -#endif - - if (!bInSkipObjectNameResolutionAndUseDefault) - { - // Resolve the object name - // TODO: currently the UI override is checked first (this should probably change so that attributes are used first) - FString ObjectName; - if (bHasBakeNameUIOverride) - { - ObjectName = InOutputObject.BakeName; - } - else - { - ObjectName = OutResolver.ResolveOutputName(); - if (ObjectName.IsEmpty()) - ObjectName = InDefaultObjectName; - } - // Update the object name in the package params and also update its token - OutPackageParams.ObjectName = ObjectName; - OutResolver.SetToken("object_name", OutPackageParams.ObjectName); - } - - if (!bInSkipBakeFolderResolutionAndUseDefault) - { - // Now resolve the bake folder - const FString BakeFolder = OutResolver.ResolveBakeFolder(); - if (!BakeFolder.IsEmpty()) - OutPackageParams.BakeFolder = BakeFolder; - } - - if (!bInSkipObjectNameResolutionAndUseDefault || !bInSkipBakeFolderResolutionAndUseDefault) - { - // Update the tokens from the package params - OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); - OutResolver.SetTokensFromStringMap(Tokens); - -#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING - // Log the final tokens - OutResolver.LogCachedAttributesAndTokens(); -#endif - } -} - - -bool -FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() -{ - // Update / repopulate the foliage editor mode's mesh list if the foliage editor mode is active. - // TODO: find a better way to do this, the relevant functions are in FEdModeFoliage and FFoliageEdModeToolkit are not API exported - FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); - if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) - { - EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); - EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); - return true; - } - - return false; -} - - -UHoudiniAssetComponent* -FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(const UObject* Obj) -{ - if (!IsValid(Obj)) - return nullptr; - - // Check the direct Outer - UHoudiniAssetComponent* OuterHAC = Cast(Obj->GetOuter()); - if(IsValid(OuterHAC)) - return OuterHAC; - - // Check the whole outer chain - OuterHAC = Obj->GetTypedOuter(); - if (IsValid(OuterHAC)) - return OuterHAC; - - // Finally check if the Object itself is a HaC - UObject* NonConstObj = const_cast(Obj); - OuterHAC = Cast(NonConstObj); - if (IsValid(OuterHAC)) - return OuterHAC; - - return nullptr; -} - - -FString -FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) -{ - // Compute Houdini version string. - FString HoudiniVersionString = FString::Printf( - TEXT("%d.%d.%s%d"), HAPI_VERSION_HOUDINI_MAJOR, - HAPI_VERSION_HOUDINI_MINOR, - (ExtraDigit ? (TEXT("0.")) : TEXT("")), - HAPI_VERSION_HOUDINI_BUILD); - - // If we have a patch version, we need to append it. - if (HAPI_VERSION_HOUDINI_PATCH > 0) - HoudiniVersionString = FString::Printf(TEXT("%s.%d"), *HoudiniVersionString, HAPI_VERSION_HOUDINI_PATCH); - return HoudiniVersionString; -} - - -void * -FHoudiniEngineUtils::LoadLibHAPI(FString & StoredLibHAPILocation) -{ - FString HFSPath = TEXT(""); - void * HAPILibraryHandle = nullptr; - - // Look up HAPI_PATH environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . - FString HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HAPI_PATH")); - if (!HFS_ENV_VAR.IsEmpty()) - HFSPath = HFS_ENV_VAR; - - // Look up environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . - HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HFS")); - if (!HFS_ENV_VAR.IsEmpty()) - HFSPath = HFS_ENV_VAR; - - // Get platform specific name of libHAPI. - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - - // If we have a custom location specified through settings, attempt to use that. - bool bCustomPathFound = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->bUseCustomHoudiniLocation) - { - // Create full path to libHAPI binary. - FString CustomHoudiniLocationPath = HoudiniRuntimeSettings->CustomHoudiniLocation.Path; - if (!CustomHoudiniLocationPath.IsEmpty()) - { - // Convert path to absolute if it is relative. - if (FPaths::IsRelative(CustomHoudiniLocationPath)) - CustomHoudiniLocationPath = FPaths::ConvertRelativePathToFull(CustomHoudiniLocationPath); - - FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPICustomPath)) - { - HFSPath = CustomHoudiniLocationPath; - bCustomPathFound = true; - } - } - } - - // We have HFS environment variable defined (or custom location), attempt to load libHAPI from it. - if (!HFSPath.IsEmpty()) - { - if (!bCustomPathFound) - { -#if PLATFORM_WINDOWS - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_WINDOWS); -#elif PLATFORM_MAC - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_MAC); -#elif PLATFORM_LINUX - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_LINUX); -#endif - } - - // Create full path to libHAPI binary. - FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - // libHAPI binary exists at specified location, attempt to load it. - FPlatformProcess::PushDllDirectory(*HFSPath); -#if PLATFORM_WINDOWS - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); -#elif PLATFORM_MAC || PLATFORM_LINUX - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); -#endif - FPlatformProcess::PopDllDirectory(*HFSPath); - - // If library has been loaded successfully we can stop. - if ( HAPILibraryHandle ) - { - if (bCustomPathFound) - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from custom path %s"), *LibHAPIName, *HFSPath); - else - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from HFS environment path %s"), *LibHAPIName, *HFSPath); - - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - } - - // Otherwise, we will attempt to detect Houdini installation. - FString HoudiniLocation = TEXT(HOUDINI_ENGINE_HFS_PATH); - FString LibHAPIPath; - - // Compute Houdini version string. - FString HoudiniVersionString = ComputeVersionString(false); - -#if PLATFORM_WINDOWS - - // On Windows, we have also hardcoded HFS path in plugin configuration file; attempt to load from it. - HFSPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, HAPI_HFS_SUBFOLDER_WINDOWS); - - // Create full path to libHAPI binary. - LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HFSPath); - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); - FPlatformProcess::PopDllDirectory(*HFSPath); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from Plugin defined HFS path %s"), *LibHAPIName, *HFSPath); - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - - // As a second attempt, on Windows, we try to look up location of Houdini Engine in the registry. - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini Engine"), StoredLibHAPILocation, false); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // As a third attempt, we try to look up location of Houdini installation (not Houdini Engine) in the registry. - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini"), StoredLibHAPILocation, false); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // Do similar registry lookups for the 32 bits registry - // Look for the Houdini Engine registry install path - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini Engine"), StoredLibHAPILocation, true); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // ... and for the Houdini registry install path - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini"), StoredLibHAPILocation, true); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // Finally, try to load from a hardcoded program files path. - HoudiniLocation = FString::Printf( - TEXT("C:\\Program Files\\Side Effects Software\\Houdini %s\\%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_WINDOWS); - -#else - -# if PLATFORM_MAC - - // Attempt to load from standard Mac OS X installation. - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/Current/Libraries"), *HoudiniVersionString); - - // Fallback in case the previous one doesnt exist - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/%s/Libraries"), *HoudiniVersionString, *HoudiniVersionString); - - // Fallback in case we're using the steam version - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); - - // Backup Fallback in case we're using the steam version - // (this could probably be removed as paths have changed) - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); - -# elif PLATFORM_LINUX - - // Attempt to load from standard Linux installation. - HoudiniLocation = FString::Printf( - TEXT("/opt/hfs%s/%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_LINUX); - -# endif - -#endif - - // Create full path to libHAPI binary. - LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HoudiniLocation); - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); - FPlatformProcess::PopDllDirectory(*HoudiniLocation); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from expected installation %s"), *LibHAPIName, *HoudiniLocation); - StoredLibHAPILocation = HoudiniLocation; - return HAPILibraryHandle; - } - } - - StoredLibHAPILocation = TEXT(""); - return HAPILibraryHandle; -} - -bool -FHoudiniEngineUtils::IsInitialized() -{ - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) - return false; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) - return false; - - return true; -} - -bool -FHoudiniEngineUtils::IsHoudiniNodeValid(const HAPI_NodeId& NodeId) -{ - if (NodeId < 0) - return false; - - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - bool ValidationAnswer = 0; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) - { - return false; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsNodeValid( - FHoudiniEngine::Get().GetSession(), NodeId, - NodeInfo.uniqueHoudiniNodeId, &ValidationAnswer)) - { - return false; - } - - return ValidationAnswer; -} - -bool -FHoudiniEngineUtils::HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex) -{ - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), HostAssetId, InputIndex), false); - - return true; -} - -bool -FHoudiniEngineUtils::DestroyHoudiniAsset(const HAPI_NodeId& AssetId) -{ - if (HAPI_RESULT_SUCCESS == FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), AssetId)) - { - return true; - } - - return false; -} - -#if PLATFORM_WINDOWS -void * -FHoudiniEngineUtils::LocateLibHAPIInRegistry( - const FString & HoudiniInstallationType, - FString & StoredLibHAPILocation, - bool LookIn32bitRegistry) -{ - auto FindDll = [&](const FString& InHoudiniInstallationPath) - { - FString HFSPath = FString::Printf(TEXT("%s/%s"), *InHoudiniInstallationPath, HAPI_HFS_SUBFOLDER_WINDOWS); - - // Create full path to libHAPI binary. - FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, HAPI_LIB_OBJECT_WINDOWS); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HFSPath); - void* HAPILibraryHandle = FPlatformProcess::GetDllHandle(HAPI_LIB_OBJECT_WINDOWS); - FPlatformProcess::PopDllDirectory(*HFSPath); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE( - TEXT("Loaded %s from Registry path %s"), HAPI_LIB_OBJECT_WINDOWS, - *HFSPath); - - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - return (void*)0; - }; - - FString HoudiniInstallationPath; - FString HoudiniVersionString = ComputeVersionString(true); - FString RegistryKey = FString::Printf( - TEXT("Software\\%sSide Effects Software\\%s"), - (LookIn32bitRegistry ? TEXT("WOW6432Node\\") : TEXT("")), *HoudiniInstallationType); - - if (FWindowsPlatformMisc::QueryRegKey( - HKEY_LOCAL_MACHINE, *RegistryKey, *HoudiniVersionString, HoudiniInstallationPath)) - { - FPaths::NormalizeDirectoryName(HoudiniInstallationPath); - return FindDll(HoudiniInstallationPath); - } - - return nullptr; -} -#endif - -bool -FHoudiniEngineUtils::LoadHoudiniAsset(UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId& OutAssetLibraryId) -{ - OutAssetLibraryId = -1; - - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return false; - - if (!FHoudiniEngineUtils::IsInitialized()) - { - // If we're not initialized now, it likely means the session has been lost - FHoudiniEngine::Get().OnSessionLost(); - return false; - } - - // Get the preferences - bool bMemoryCopyFirst = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bMemoryCopyFirst = HoudiniRuntimeSettings->bPreferHdaMemoryCopyOverHdaSourceFile; - - // Get the HDA's file path - // We need to convert relative file path to absolute - FString AssetFileName = HoudiniAsset->GetAssetFileName(); - if (FPaths::IsRelative(AssetFileName)) - AssetFileName = FPaths::ConvertRelativePathToFull(AssetFileName); - - // We need to modify the file name for expanded .hdas - FString FileExtension = FPaths::GetExtension(AssetFileName); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) - { - // the .hda directory is what we should be loading - AssetFileName = FPaths::GetPath(AssetFileName); - } - - //Check whether we can Load from file/memory - bool bCanLoadFromMemory = (!HoudiniAsset->IsExpandedHDA() && HoudiniAsset->GetAssetBytesCount() > 0); - - // If the hda file exists, we can simply load it directly - bool bCanLoadFromFile = false; - if ( !AssetFileName.IsEmpty() ) - { - if (FPaths::FileExists(AssetFileName) - || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName))) - { - bCanLoadFromFile = true; - } - } - - HAPI_Result Result = HAPI_RESULT_FAILURE; - - // Lambda to detect license issues - auto CheckLicenseValid = [&AssetFileName](const HAPI_Result& Result) - { - // HoudiniEngine acquires a license when creating/loading a node, not when creating a session - if (Result >= HAPI_RESULT_NO_LICENSE_FOUND && Result < HAPI_RESULT_ASSET_INVALID) - { - FString ErrorDesc = GetErrorDescription(Result); - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: License failed: %s."), *AssetFileName, *ErrorDesc); - - // We must stop the session to prevent further attempts at loading an HDA - // as this could lead to unreal becoming stuck and unresponsive due to license timeout - FHoudiniEngine::Get().StopSession(); - - // Set the HE status to "no license" - FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); - - return false; - } - else - { - return true; - } - }; - - // Lambda to load an HDA from file - auto LoadAssetFromFile = [&Result, &OutAssetLibraryId](const FString& InAssetFileName) - { - // Load the asset from file. - std::string AssetFileNamePlain; - FHoudiniEngineUtils::ConvertUnrealString(InAssetFileName, AssetFileNamePlain); - Result = FHoudiniApi::LoadAssetLibraryFromFile( - FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); - - }; - - // Lambda to load an HDA from memory - auto LoadAssetFromMemory = [&Result, &OutAssetLibraryId](UHoudiniAsset* InHoudiniAsset) - { - // Load the asset from the cached memory buffer - Result = FHoudiniApi::LoadAssetLibraryFromMemory( - FHoudiniEngine::Get().GetSession(), - reinterpret_cast(InHoudiniAsset->GetAssetBytes()), - InHoudiniAsset->GetAssetBytesCount(), - true, - &OutAssetLibraryId); - }; - - if (!bMemoryCopyFirst) - { - // Load from File first - if (bCanLoadFromFile) - { - LoadAssetFromFile(AssetFileName); - - // Detect license issues when loading the HDA - if (!CheckLicenseValid(Result)) - return false; - } - - // If we failed to load from file ... - if (Result != HAPI_RESULT_SUCCESS) - { - // ... warn the user that we will be loading from memory. - HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from Memory: source asset file not found."), *AssetFileName); - - // Attempt to load from memory - if (bCanLoadFromMemory) - { - LoadAssetFromMemory(HoudiniAsset); - - // Detect license issues when loading the HDA - if (!CheckLicenseValid(Result)) - return false; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); - return false; - } - } - } - else - { - // Load from Memory first - if(bCanLoadFromMemory) - { - LoadAssetFromMemory(HoudiniAsset); - - // Detect license issues when loading the HDA - if (!CheckLicenseValid(Result)) - return false; - } - - // If we failed to load from memory ... - if (Result != HAPI_RESULT_SUCCESS) - { - // ... warn the user that we will be loading from file - HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from File: no memory copy available."), *AssetFileName); - - // Attempt to load from file - if (bCanLoadFromFile) - { - LoadAssetFromFile(AssetFileName); - - // Detect license issues when loading the HDA - if (!CheckLicenseValid(Result)) - return false; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); - return false; - } - } - } - - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_MESSAGE(TEXT("Error loading asset library for %s: %s"), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - return true; -} - -bool -FHoudiniEngineUtils::GetSubAssetNames( - const HAPI_AssetLibraryId& AssetLibraryId, - TArray< HAPI_StringHandle >& OutAssetNames) -{ - if (AssetLibraryId < 0) - return false; - - int32 AssetCount = 0; - HAPI_Result Result = HAPI_RESULT_FAILURE; - Result = FHoudiniApi::GetAvailableAssetCount(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetCount); - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Error getting asset count: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - if (AssetCount <= 0) - { - HOUDINI_LOG_ERROR(TEXT("Could not find an asset.")); - return false; - } - - OutAssetNames.SetNumUninitialized(AssetCount); - Result = FHoudiniApi::GetAvailableAssets(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &OutAssetNames[0], AssetCount); - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Unable to retrieve sub asset names: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - if (!AssetCount) - { - HOUDINI_LOG_ERROR(TEXT("No assets found")); - return false; - } - - return true; -} - - -bool -FHoudiniEngineUtils::OpenSubassetSelectionWindow(TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ) -{ - OutPickedAssetName = -1; - - if (AssetNames.Num() <= 0) - return false; - - // Default to the first asset - OutPickedAssetName = AssetNames[0]; - -#if WITH_EDITOR - // Present the user with a dialog for choosing which asset to instantiate. - TSharedPtr ParentWindow; - if (FModuleManager::Get().IsModuleLoaded("MainFrame")) - { - // Check if the main frame is loaded. When using the old main frame it may not be. - IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); - ParentWindow = MainFrame.GetParentWindow(); - } - - if (!ParentWindow.IsValid()) - { - return false; - } - - TSharedPtr AssetSelectionWidget; - TSharedRef 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()) - { - return false; - } - - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - - int32 DialogPickedAssetName = AssetSelectionWidget->GetSelectedAssetName(); - if (DialogPickedAssetName != -1) - { - OutPickedAssetName = DialogPickedAssetName; - return true; - } - else - { - return false; - } -#endif - - return true; -} - -/* -bool -FHoudiniEngineUtils::IsValidNodeId(HAPI_NodeId NodeId) -{ - return NodeId != -1; -} -*/ - -bool -FHoudiniEngineUtils::GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString) -{ - HAPI_AssetInfo AssetInfo; - if (FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo) == HAPI_RESULT_SUCCESS) - { - FHoudiniEngineString HoudiniEngineString(AssetInfo.nameSH); - return HoudiniEngineString.ToFString(NameString); - } - - return false; -} - -bool -FHoudiniEngineUtils::GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer) -{ - PresetBuffer.Empty(); - - HAPI_NodeId NodeId; - HAPI_AssetInfo AssetInfo; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo)) - { - NodeId = AssetInfo.nodeId; - } - else - NodeId = AssetNodeId; - - int32 BufferLength = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPresetBufLength( - FHoudiniEngine::Get().GetSession(), NodeId, - HAPI_PRESETTYPE_BINARY, NULL, &BufferLength), false); - - PresetBuffer.SetNumZeroed(BufferLength); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPreset( - FHoudiniEngine::Get().GetSession(), NodeId, - &PresetBuffer[0], PresetBuffer.Num()), false); - - return true; -} - - -bool -FHoudiniEngineUtils::HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath) -{ - // Retrieve Path to the given Node, relative to the other given Node - if ((InNodeId < 0) || (InRelativeToNodeId < 0)) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InNodeId)) - return false; - - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodePath( - FHoudiniEngine::Get().GetSession(), - InNodeId, InRelativeToNodeId, &StringHandle)) - { - if(FHoudiniEngineString::ToFString(StringHandle, OutPath)) - { - return true; - } - } - return false; -} - -bool -FHoudiniEngineUtils::HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath) -{ - // Do the HAPI query only on first-use - if (!InHGPO.NodePath.IsEmpty()) - return true; - - FString NodePathTemp; - if (InHGPO.AssetId == InHGPO.GeoId) - { - // This is a SOP asset, just return the asset name in this case - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InHGPO.AssetId, &AssetInfo)) - { - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo)) - { - if (FHoudiniEngineString::ToFString(AssetNodeInfo.nameSH, NodePathTemp)) - { - OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); - } - } - } - } - else - { - // This is an OBJ asset, return the path to this geo relative to the asset - if (FHoudiniEngineUtils::HapiGetNodePath(InHGPO.GeoId, InHGPO.AssetId, NodePathTemp)) - { - OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); - } - } - - /*if (OutPath.IsEmpty()) - { - OutPath = TEXT("Empty"); - } - - return NodePath; - */ - - return !OutPath.IsEmpty(); -} - - -bool -FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), - InNodeId, &NodeInfo), false); - - int32 ObjectCount = 0; - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - ObjectCount = 1; - OutObjectInfos.SetNumUninitialized(1); - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( - FHoudiniEngine::Get().GetSession(), - NodeInfo.parentId, &OutObjectInfos[0]), false); - - // Use the identity transform - OutObjectTransforms.SetNumUninitialized(1); - FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); - - OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; - OutObjectTransforms[0].scale[0] = 1.0f; - OutObjectTransforms[0].scale[1] = 1.0f; - OutObjectTransforms[0].scale[2] = 1.0f; - OutObjectTransforms[0].rstOrder = HAPI_SRT; - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( - FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); - - if (ObjectCount <= 0) - { - // This asset is an OBJ that has no object as children, use the object itself - ObjectCount = 1; - OutObjectInfos.SetNumUninitialized(1); - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[0]), false); - - // Use the identity transform - OutObjectTransforms.SetNumUninitialized(1); - FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); - - OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; - OutObjectTransforms[0].scale[0] = 1.0f; - OutObjectTransforms[0].scale[1] = 1.0f; - OutObjectTransforms[0].scale[2] = 1.0f; - OutObjectTransforms[0].rstOrder = HAPI_SRT; - } - else - { - // This OBJ has children - // See if we should add ourself by looking for immediate display SOP - int32 ImmediateSOP = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), NodeInfo.id, - HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_DISPLAY, - false, &ImmediateSOP), false); - - bool bAddSelf = ImmediateSOP > 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( - FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); - - // Increment the object count by one if we should add ourself - OutObjectInfos.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); - OutObjectTransforms.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); - for (int32 Idx = 0; Idx < OutObjectInfos.Num(); Idx++) - { - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[Idx])); - FHoudiniApi::Transform_Init(&(OutObjectTransforms[Idx])); - } - - // Get our object info in 0 if needed - if (bAddSelf) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[0]), false); - - // Use the identity transform - OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; - OutObjectTransforms[0].scale[0] = 1.0f; - OutObjectTransforms[0].scale[1] = 1.0f; - OutObjectTransforms[0].scale[2] = 1.0f; - OutObjectTransforms[0].rstOrder = HAPI_SRT; - } - - // Get the other object infos - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( - FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[bAddSelf ? 1 : 0], 0, ObjectCount), false); - - // Get the composed object transforms for the others (1 - Count) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_SRT, &OutObjectTransforms[bAddSelf ? 1 : 0], 0, ObjectCount), false); - } - } - else - return false; - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, - &NodeInfo), false); - - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - NodeInfo.parentId, -1, HAPI_SRT, &HapiTransform), false); - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InNodeId, -1, HAPI_SRT, &HapiTransform), false); - } - else - return false; - - // Convert HAPI transform to Unreal one. - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, OutTransform); - - return true; -} - -void -FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform) -{ - if ( HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM ) - { - // Swap Y/Z, invert W - FQuat ObjectRotation( - HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[2], - HapiTransform.rotationQuaternion[1], -HapiTransform.rotationQuaternion[3]); - - // Swap Y/Z and scale - FVector ObjectTranslation(HapiTransform.position[0], HapiTransform.position[2], HapiTransform.position[1]); - ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[2], HapiTransform.scale[1]); - - UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); - } - else - { - FQuat ObjectRotation( - HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[1], - HapiTransform.rotationQuaternion[2], HapiTransform.rotationQuaternion[3]); - - FVector ObjectTranslation( - HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2]); - ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[1], HapiTransform.scale[2]); - - UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); - } -} - -void -FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform) -{ - float HapiMatrix[16]; - FHoudiniApi::ConvertTransformEulerToMatrix(FHoudiniEngine::Get().GetSession(), &HapiTransformEuler, HapiMatrix); - - HAPI_Transform HapiTransformQuat; - FMemory::Memzero< HAPI_Transform >(HapiTransformQuat); - FHoudiniApi::ConvertMatrixToQuat(FHoudiniEngine::Get().GetSession(), HapiMatrix, HAPI_SRT, &HapiTransformQuat); - - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransformQuat, UnrealTransform); -} - -void -FHoudiniEngineUtils::TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform) -{ - FMemory::Memzero< HAPI_Transform >(HapiTransform); - HapiTransform.rstOrder = HAPI_SRT; - - FQuat UnrealRotation = UnrealTransform.GetRotation(); - FVector UnrealTranslation = UnrealTransform.GetTranslation(); - FVector UnrealScale = UnrealTransform.GetScale3D(); - - if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) - { - // Swap Y/Z, invert XYZ - HapiTransform.rotationQuaternion[0] = -UnrealRotation.X; - HapiTransform.rotationQuaternion[1] = -UnrealRotation.Z; - HapiTransform.rotationQuaternion[2] = -UnrealRotation.Y; - HapiTransform.rotationQuaternion[3] = UnrealRotation.W; - - // Swap Y/Z, scale - HapiTransform.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransform.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransform.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - HapiTransform.scale[0] = UnrealScale.X; - HapiTransform.scale[1] = UnrealScale.Z; - HapiTransform.scale[2] = UnrealScale.Y; - } - else - { - HapiTransform.rotationQuaternion[0] = UnrealRotation.X; - HapiTransform.rotationQuaternion[1] = UnrealRotation.Y; - HapiTransform.rotationQuaternion[2] = UnrealRotation.Z; - HapiTransform.rotationQuaternion[3] = UnrealRotation.W; - - HapiTransform.position[0] = UnrealTranslation.X; - HapiTransform.position[1] = UnrealTranslation.Y; - HapiTransform.position[2] = UnrealTranslation.Z; - - HapiTransform.scale[0] = UnrealScale.X; - HapiTransform.scale[1] = UnrealScale.Y; - HapiTransform.scale[2] = UnrealScale.Z; - } -} - -void -FHoudiniEngineUtils::TranslateUnrealTransform( - const FTransform & UnrealTransform, - HAPI_TransformEuler & HapiTransformEuler) -{ - FMemory::Memzero< HAPI_TransformEuler >(HapiTransformEuler); - - HapiTransformEuler.rstOrder = HAPI_SRT; - HapiTransformEuler.rotationOrder = HAPI_XYZ; - - FQuat UnrealRotation = UnrealTransform.GetRotation(); - FVector UnrealTranslation = UnrealTransform.GetTranslation(); - FVector UnrealScale = UnrealTransform.GetScale3D(); - - if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) - { - // switch the quaternion to Y-up, LHR by Swapping Y/Z and negating W - Swap(UnrealRotation.Y, UnrealRotation.Z); - UnrealRotation.W = -UnrealRotation.W; - const FRotator Rotator = UnrealRotation.Rotator(); - - // Negate roll and pitch since they are actually RHR - HapiTransformEuler.rotationEuler[0] = -Rotator.Roll; - HapiTransformEuler.rotationEuler[1] = -Rotator.Pitch; - HapiTransformEuler.rotationEuler[2] = Rotator.Yaw; - - // Swap Y/Z, scale - HapiTransformEuler.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransformEuler.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransformEuler.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - HapiTransformEuler.scale[0] = UnrealScale.X; - HapiTransformEuler.scale[1] = UnrealScale.Z; - HapiTransformEuler.scale[2] = UnrealScale.Y; - } - else - { - const FRotator Rotator = UnrealRotation.Rotator(); - HapiTransformEuler.rotationEuler[0] = Rotator.Roll; - HapiTransformEuler.rotationEuler[1] = Rotator.Yaw; - HapiTransformEuler.rotationEuler[2] = Rotator.Pitch; - - HapiTransformEuler.position[0] = UnrealTranslation.X; - HapiTransformEuler.position[1] = UnrealTranslation.Y; - HapiTransformEuler.position[2] = UnrealTranslation.Z; - - HapiTransformEuler.scale[0] = UnrealScale.X; - HapiTransformEuler.scale[1] = UnrealScale.Y; - HapiTransformEuler.scale[2] = UnrealScale.Z; - } -} - -bool -FHoudiniEngineUtils::UploadHACTransform(UHoudiniAssetComponent* HAC) -{ - if (!HAC || !HAC->bUploadTransformsToHoudiniEngine) - return false; - - // Indicates the HAC has been fully loaded - // TODO: Check! (replaces fullyloaded) - if (!HAC->IsFullyLoaded()) - return false; - - if (HAC->GetAssetCookCount() > 0 && HAC->GetAssetId() >= 0) - { - if (!FHoudiniEngineUtils::HapiSetAssetTransform(HAC->GetAssetId(), HAC->GetComponentTransform())) - return false; - } - - HAC->SetHasComponentTransformChanged(false); - - return true; -} - -bool -FHoudiniEngineUtils::HapiSetAssetTransform(const HAPI_NodeId& AssetId, const FTransform & Transform) -{ - if (AssetId < 0) - return false; - - // Translate Unreal transform to HAPI Euler one. - HAPI_TransformEuler TransformEuler; - FMemory::Memzero< HAPI_TransformEuler >(TransformEuler); - FHoudiniEngineUtils::TranslateUnrealTransform(Transform, TransformEuler); - - // Get the NodeInfo - HAPI_NodeInfo LocalAssetNodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetId, - &LocalAssetNodeInfo), false); - - if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - LocalAssetNodeInfo.parentId, - &TransformEuler), false); - } - else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - AssetId, &TransformEuler), false); - } - else - return false; - - return true; -} - -HAPI_NodeId -FHoudiniEngineUtils::HapiGetParentNodeId(const HAPI_NodeId& NodeId) -{ - HAPI_NodeId ParentId = -1; - if (NodeId >= 0) - { - HAPI_NodeInfo NodeInfo; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) - ParentId = NodeInfo.parentId; - } - - return ParentId; -} - - -// Assign a unique Actor Label if needed -void -FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - // TODO: Necessary?? - -#if WITH_EDITOR - HAPI_NodeId AssetId = HAC->GetAssetId(); - if (AssetId < 0) - return; - - AActor* OwnerActor = HAC->GetOwner(); - if (!OwnerActor) - return; - - if (!OwnerActor->GetName().StartsWith(AHoudiniAssetActor::StaticClass()->GetName())) - return; - - // Assign unique actor label based on asset name if it seems to have not been renamed already - FString UniqueName; - if (FHoudiniEngineUtils::GetHoudiniAssetName(AssetId, UniqueName)) - FActorLabelUtilities::SetActorLabelUnique(OwnerActor, UniqueName); -#endif -} - -bool -FHoudiniEngineUtils::GetLicenseType(FString & LicenseType) -{ - LicenseType = TEXT(""); - HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetSessionEnvInt( - FHoudiniEngine::Get().GetSession(), HAPI_SESSIONENVINT_LICENSE, - (int32 *)&LicenseTypeValue), false); - - switch (LicenseTypeValue) - { - case HAPI_LICENSE_NONE: - { - LicenseType = TEXT("No License Acquired"); - break; - } - - case HAPI_LICENSE_HOUDINI_ENGINE: - { - LicenseType = TEXT("Houdini Engine"); - break; - } - - case HAPI_LICENSE_HOUDINI: - { - LicenseType = TEXT("Houdini"); - break; - } - - case HAPI_LICENSE_HOUDINI_FX: - { - LicenseType = TEXT("Houdini FX"); - break; - } - - case HAPI_LICENSE_HOUDINI_ENGINE_INDIE: - { - LicenseType = TEXT("Houdini Engine Indie"); - break; - } - - case HAPI_LICENSE_HOUDINI_INDIE: - { - LicenseType = TEXT("Houdini Indie"); - break; - } - - case HAPI_LICENSE_MAX: - default: - { - return false; - } - } - - return true; -} - -// Check if the Houdini asset component (or parent HAC of a parameter) is being cooked -bool -FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(UObject* InObj) -{ - if (!InObj) - return false; - - UHoudiniAssetComponent* HoudiniAssetComponent = nullptr; - - if (InObj->IsA()) - { - HoudiniAssetComponent = Cast(InObj); - } - else if (InObj->IsA()) - { - UHoudiniParameter* Parameter = Cast(InObj); - if (!Parameter) - return false; - - HoudiniAssetComponent = Cast(Parameter->GetOuter()); - } - - if (!HoudiniAssetComponent) - return false; - - EHoudiniAssetState AssetState = HoudiniAssetComponent->GetAssetState(); - - return AssetState >= EHoudiniAssetState::PreCook && AssetState <= EHoudiniAssetState::PostCook; -} - -void -FHoudiniEngineUtils::UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate) -{ - TArray ObjectsToUpdate; - ObjectsToUpdate.Add(InObjectToUpdate); - - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() - { - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - } -} - -void -FHoudiniEngineUtils::UpdateEditorProperties(TArray ObjectsToUpdate, const bool& InForceFullUpdate) -{ - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() - { - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - } -} - -void FHoudiniEngineUtils::UpdateBlueprintEditor(UHoudiniAssetComponent* HAC) -{ - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [HAC]() - { - FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); - } -} - -void -FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate) -{ - // TODO: Don't use this method. Prefer using IDetailLayoutBuilder::ForceRefreshDetails(). - // Example to correctly update details panel through IDetailCategoryBuilder / IDetailLayoutBuilder - // IDetailCategoryBuilder &CategoryBuilder = StructBuilder.GetParentCategory(); - // IDetailLayoutBuilder &LayoutBuilder = CategoryBuilder.GetParentLayout(); - // LayoutBuilder.ForceRefreshDetails(); - -#if WITH_EDITOR - if (!bInForceFullUpdate) - { - // bNeedFullUpdate is false only when small changes (parameters value) have been made - // We do not reselect the actor to avoid loosing the currently selected parameter - if(GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); - - return; - } - - // We now want to get all the components/actors owning the objects to update - TArray AllSceneComponents; - for (auto CurrentObject : ObjectsToUpdate) - { - if (!CurrentObject || CurrentObject->IsPendingKill()) - continue; - - // In some case, the object itself is the component - USceneComponent* SceneComp = Cast(CurrentObject); - if (!SceneComp) - { - SceneComp = Cast(CurrentObject->GetOuter()); - } - - if (SceneComp && !SceneComp->IsPendingKill()) - { - AllSceneComponents.Add(SceneComp); - continue; - } - } - - TArray AllActors; - for (auto CurrentSceneComp : AllSceneComponents) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill()) - continue; - - AActor* Actor = CurrentSceneComp->GetOwner(); - if (Actor && !Actor->IsPendingKill()) - AllActors.Add(Actor); - } - - // Updating the editor properties can be done in two ways, depending if we're in the BP editor or not - // If we have a parent actor, we're not in the BP Editor, so update via the property editor module - if (AllActors.Num() > 0) - { - // Get the property editor module - FPropertyEditorModule& PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // This will actually force a refresh of all the details view - //PropertyModule.NotifyCustomizationModuleChanged(); - - TArray SelectedActors; - for (auto Actor : AllActors) - { - if (Actor && Actor->IsSelected()) - SelectedActors.Add(Actor); - } - - if (SelectedActors.Num() > 0) - { - PropertyModule.UpdatePropertyViews(SelectedActors); - } - - // 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. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - { - // We have no details panel, nothing to update. - continue; - } - - // Get the selected actors for this details panels and check if one of ours belongs to it - const TArray>& SelectedDetailActors = DetailsView->GetSelectedActors(); - bool bFoundActor = false; - for (int32 ActorIdx = 0; ActorIdx < SelectedDetailActors.Num(); ActorIdx++) - { - TWeakObjectPtr SelectedActor = SelectedDetailActors[ActorIdx]; - if (SelectedActor.IsValid() && AllActors.Contains(SelectedActor.Get())) - { - bFoundActor = true; - break; - } - } - - // None of our actors belongs to this detail panel, no need to update it - if (!bFoundActor) - continue; - - // Refresh that details panels using its current selection - TArray Selection; - for (auto DetailsActor : SelectedDetailActors) - { - if (DetailsActor.IsValid()) - Selection.Add(DetailsActor.Get()); - } - - // Reset selected actors, force refresh and override the lock. - DetailsView->SetObjects(SelectedActors, bInForceFullUpdate, true); - - if (GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); - } - } - else - { - // TODO: Do we need to do Blueprint Editor updates here or can we confine it to "post output processing"? - - } - - /* - // Reset the full update flag - if (bNeedFullUpdate) - HAC->SetEditorPropertiesNeedFullUpdate(false); - */ - - return; -#endif -} - -void FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC) -{ - //UHoudiniAssetComponent* HACTemplate = HAC->GetCachedTemplate(); - //UBlueprintGeneratedClass* OwnerBPClass = Cast(HACTemplate->GetOuter()); - //if (!OwnerBPClass) - // return; - - ///* - //FBlueprintEditor* BlueprintEditor = static_cast(FAssetEditorManager::Get().FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); - //if (!BlueprintEditor) - // return; - //*/ - - //// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built. - //UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - //FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); - //if (!BlueprintEditor) - // return; - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(HAC); - if (!BlueprintEditor) - return; - - TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); - if (SCSEditor.IsValid()) - { - SCSEditor->UpdateTree(true); - SCSEditor->DumpTree(); - } - BlueprintEditor->RefreshMyBlueprint(); - - //BlueprintEditor->RefreshMyBlueprint(); - //BlueprintEditor->RefreshInspector(); - //BlueprintEditor->RefreshEditors(); - - // Also somehow reselect ? -} - -HAPI_Result -FHoudiniEngineUtils::SetAttributeStringData( - const FString& InString, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo) -{ - TArray StringArray; - StringArray.Add(InString); - - return SetAttributeStringData(StringArray, InNodeId, InPartId, InAttributeName, InAttributeInfo); -} - -HAPI_Result -FHoudiniEngineUtils::SetAttributeStringData( - const TArray& InStringArray, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo ) -{ - TArray StringDataArray; - for (auto CurrentString : InStringArray) - { - // Append the converted string to the string array - StringDataArray.Add(FHoudiniEngineUtils::ExtractRawString(CurrentString)); - } - - // Set the attribute's string data - HAPI_Result result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - TCHAR_TO_ANSI(*InAttributeName), &InAttributeInfo, - StringDataArray.GetData(), 0, InAttributeInfo.count); - - // ExtractRawString allocates memory using malloc, free it! - FreeRawStringMemory(StringDataArray); - - return result; -} - -char * -FHoudiniEngineUtils::ExtractRawString(const FString& InString) -{ - if (InString.IsEmpty()) - return nullptr; - - std::string ConvertedString = TCHAR_TO_UTF8(*InString); - - // Allocate space for unique string. - int32 UniqueStringBytes = ConvertedString.size() + 1; - char * UniqueString = static_cast(FMemory::Malloc(UniqueStringBytes)); - - FMemory::Memzero(UniqueString, UniqueStringBytes); - FMemory::Memcpy(UniqueString, ConvertedString.c_str(), ConvertedString.size()); - - return UniqueString; -} - -void -FHoudiniEngineUtils::FreeRawStringMemory(const char*& InRawString) -{ - if (InRawString == nullptr) - return; - - // Do not attempt to free empty strings! - if (!InRawString[0]) - return; - - FMemory::Free((void*)InRawString); - InRawString = nullptr; -} - -void -FHoudiniEngineUtils::FreeRawStringMemory(TArray& InRawStringArray) -{ - // ExtractRawString allocates memory using malloc, free it! - for (auto CurrentStrPtr : InRawStringArray) - { - FreeRawStringMemory(CurrentStrPtr); - } - InRawStringArray.Empty(); -} - -bool -FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // No need to add another component if we already show the logo - if (FHoudiniEngineUtils::HasHoudiniLogo(HAC)) - return true; - - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - UStaticMeshComponent * HoudiniLogoSMC = NewObject< UStaticMeshComponent >( - HAC, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!HoudiniLogoSMC) - return false; - - HoudiniLogoSMC->SetStaticMesh(HoudiniLogoSM); - HoudiniLogoSMC->SetVisibility(true); - HoudiniLogoSMC->SetHiddenInGame(true); - // Attach created static mesh component to our Houdini component. - HoudiniLogoSMC->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniLogoSMC->RegisterComponent(); - - return true; -} - -bool -FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the Houdini Logo SM - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - // Iterate on the HAC's component - for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) - continue; - - // Get the static mesh component - UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) - continue; - - // Check if the SMC is the Houdini Logo - if (SMC->GetStaticMesh() != HoudiniLogoSM) - continue; - - SMC->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SMC->UnregisterComponent(); - SMC->DestroyComponent(); - - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the Houdini Logo SM - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - // Iterate on the HAC's component - for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) - continue; - - // Get the static mesh component - UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) - continue; - - // Check if the SMC is the Houdini Logo - if (SMC->GetStaticMesh() == HoudiniLogoSM) - return true; - } - - return false; -} - -int32 -FHoudiniEngineUtils::HapiGetVertexListForGroup( - const HAPI_NodeId& GeoId, - const HAPI_PartInfo& PartInfo, - const FString& GroupName, - const TArray& FullVertexList, - TArray& NewVertexList, - TArray& AllVertexList, - TArray& AllFaceList, - TArray& AllGroupFaceIndices, - int32& FirstValidVertex, - int32& FirstValidPrim, - const bool& isPackedPrim) -{ - int32 ProcessedWedges = 0; - AllFaceList.Empty(); - FirstValidPrim = 0; - FirstValidVertex = 0; - NewVertexList.Init(-1, FullVertexList.Num()); - - // Get the faces membership for this group - bool bAllEquals = false; - TArray PartGroupMembership; - if (!FHoudiniEngineUtils::HapiGetGroupMembership( - GeoId, PartInfo, HAPI_GROUPTYPE_PRIM, GroupName, PartGroupMembership, bAllEquals)) - return false; - - // Go through all primitives. - for (int32 FaceIdx = 0; FaceIdx < PartGroupMembership.Num(); ++FaceIdx) - { - if (PartGroupMembership[FaceIdx] <= 0) - { - // The face is not in the group, skip - continue; - } - - // Add the face's index. - AllFaceList.Add(FaceIdx); - - // Get the index of this face's vertices - int32 FirstVertexIdx = FaceIdx * 3; - int32 SecondVertexIdx = FirstVertexIdx + 1; - int32 LastVertexIdx = FirstVertexIdx + 2; - - // This face is a member of specified group. - // Add all 3 vertices - if (FullVertexList.IsValidIndex(LastVertexIdx)) - { - NewVertexList[FirstVertexIdx] = FullVertexList[FirstVertexIdx]; - NewVertexList[SecondVertexIdx] = FullVertexList[SecondVertexIdx]; - NewVertexList[LastVertexIdx] = FullVertexList[LastVertexIdx]; - } - - // Mark these vertex indices as used. - if (AllVertexList.IsValidIndex(LastVertexIdx)) - { - AllVertexList[FirstVertexIdx] = 1; - AllVertexList[SecondVertexIdx] = 1; - AllVertexList[LastVertexIdx] = 1; - } - - // Mark this face as used. - if (AllGroupFaceIndices.IsValidIndex(FaceIdx)) - AllGroupFaceIndices[FaceIdx] = 1; - - if (ProcessedWedges == 0) - { - // Keep track of the first valid vertex/face indices for this group - // This will be useful later on when extracting attributes - FirstValidVertex = FirstVertexIdx; - FirstValidPrim = FaceIdx; - } - - ProcessedWedges += 3; - } - - return ProcessedWedges; -} - -bool -FHoudiniEngineUtils::HapiGetGroupNames( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const HAPI_GroupType& GroupType, const bool& isPackedPrim, - TArray& OutGroupNames) -{ - int32 GroupCount = 0; - if (!isPackedPrim) - { - // Get group count on the geo - HAPI_GeoInfo GeoInfo; - FHoudiniApi::GeoInfo_Init(&GeoInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo), false); - - if (GroupType == HAPI_GROUPTYPE_POINT) - GroupCount = GeoInfo.pointGroupCount; - else if (GroupType == HAPI_GROUPTYPE_PRIM) - GroupCount = GeoInfo.primitiveGroupCount; - } - else - { - // We need the group count for this packed prim - int32 PointGroupCount = 0, PrimGroupCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupCountOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PointGroupCount, &PrimGroupCount), false); - - if (GroupType == HAPI_GROUPTYPE_POINT) - GroupCount = PointGroupCount; - else if (GroupType == HAPI_GROUPTYPE_PRIM) - GroupCount = PrimGroupCount; - } - - if (GroupCount <= 0) - return true; - - TArray GroupNameStringHandles; - GroupNameStringHandles.SetNumZeroed(GroupCount); - if (!isPackedPrim) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNames( - FHoudiniEngine::Get().GetSession(), - GeoId, GroupType, &GroupNameStringHandles[0], GroupCount), false); - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNamesOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, GroupType, &GroupNameStringHandles[0], GroupCount), false); - } - - /* - OutGroupNames.SetNum(GroupCount); - for (int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx) - { - FString CurrentGroupName = TEXT(""); - FHoudiniEngineString::ToFString(GroupNameStringHandles[NameIdx], CurrentGroupName); - OutGroupNames[NameIdx] = CurrentGroupName; - } - */ - - FHoudiniEngineString::SHArrayToFStringArray(GroupNameStringHandles, OutGroupNames); - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetGroupMembership( - const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, - const HAPI_GroupType& GroupType, const FString & GroupName, - TArray& OutGroupMembership, bool& OutAllEquals) -{ - int32 ElementCount = (GroupType == HAPI_GROUPTYPE_POINT) ? PartInfo.pointCount : PartInfo.faceCount; - if (ElementCount < 1) - return false; - OutGroupMembership.SetNum(ElementCount); - - OutAllEquals = false; - std::string ConvertedGroupName = TCHAR_TO_UTF8(*GroupName); - if (!PartInfo.isInstanced) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembership( - FHoudiniEngine::Get().GetSession(), - GeoId, PartInfo.id, GroupType,ConvertedGroupName.c_str(), - &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembershipOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), GeoId, PartInfo.id, GroupType, - ConvertedGroupName.c_str(), &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); - } - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize, - HAPI_AttributeOwner InOwner) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniEngineUtils::HapiGetAttributeDataAsFloat")); - - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Allocate sufficient buffer for data. - OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); - - return true; - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Expected Float, found an int, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray IntData; - IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(IntData.Num()); - for (int32 Idx = 0; Idx < IntData.Num(); Idx++) - { - OutData[Idx] = (float)IntData[Idx]; - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from integer."), *FString(InAttribName)); - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - // Expected Float, found a string, try to convert the attribute - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) - { - bool bConversionError = false; - OutData.SetNum(StringData.Num()); - for (int32 Idx = 0; Idx < StringData.Num(); Idx++) - { - if (StringData[Idx].IsNumeric()) - OutData[Idx] = FCString::Atof(*StringData[Idx]); - else - bConversionError = true; - } - - if (!bConversionError) - { - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from string."), *FString(InAttribName)); - return true; - } - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a float attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - const int32& InTupleSize, - const HAPI_AttributeOwner& InOwner) -{ - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Allocate sufficient buffer for data. - OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); - - return true; - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Expected Int, found a float, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray FloatData; - FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the float values - if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(FloatData.Num()); - for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) - { - OutData[Idx] = (int32)FloatData[Idx]; - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from float."), *FString(InAttribName)); - - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - // Expected Int, found a string, try to convert the attribute - TArray StringData; - if(FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) - { - bool bConversionError = false; - OutData.SetNum(StringData.Num()); - for (int32 Idx = 0; Idx < StringData.Num(); Idx++) - { - if (StringData[Idx].IsNumeric()) - OutData[Idx] = FCString::Atoi(*StringData[Idx]); - else - bConversionError = true; - } - - if (!bConversionError) - { - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from string."), *FString(InAttribName)); - return true; - } - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be an integer attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsString( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize, - HAPI_AttributeOwner InOwner) -{ - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - return FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, OutData); - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Expected string, found a float, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray FloatData; - FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the float values - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(FloatData.Num()); - for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) - { - OutData[Idx] = FString::SanitizeFloat(FloatData[Idx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from float."), *FString(InAttribName)); - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Expected String, found an int, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray IntData; - IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(IntData.Num()); - for (int32 Idx = 0; Idx < IntData.Num(); Idx++) - { - OutData[Idx] = FString::FromInt(IntData[Idx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from integer."), *FString(InAttribName)); - return true; - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a string attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& InAttributeInfo, - TArray& OutData) -{ - if (!InAttributeInfo.exists) - return false; - - // Extract the StringHandles - TArray StringHandles; - StringHandles.Init(-1, InAttributeInfo.count * InAttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, &InAttributeInfo, - &StringHandles[0], 0, InAttributeInfo.count), false); - - // Set the output data size - OutData.SetNum(StringHandles.Num()); - - // Convert the StringHandles to FString. - // using a map to minimize the number of HAPI calls - FHoudiniEngineString::SHArrayToFStringArray(StringHandles, OutData); - - return true; -} - - -bool -FHoudiniEngineUtils::HapiCheckAttributeExists( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const char * AttribName, HAPI_AttributeOwner Owner) -{ - if (Owner == HAPI_ATTROWNER_INVALID) - { - for (int32 OwnerIdx = 0; OwnerIdx < HAPI_ATTROWNER_MAX; OwnerIdx++) - { - if (HapiCheckAttributeExists(GeoId, PartId, AttribName, (HAPI_AttributeOwner)OwnerIdx)) - { - return true; - } - } - } - else - { - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, AttribName, Owner, &AttribInfo), false); - - return AttribInfo.exists; - } - - return false; -} - -bool -FHoudiniEngineUtils::IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType) -{ - // Check for - // - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE (unreal_instance) on points/detail - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT)) - { - OutInstancerType = EHoudiniInstancerType::AttributeInstancer; - return true; - } - - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL)) - { - OutInstancerType = EHoudiniInstancerType::AttributeInstancer; - return true; - } - - // - HAPI_UNREAL_ATTRIB_INSTANCE (instance) on points - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT)) - { - OutInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsString( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const FString& DefaultValue, - FString& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParamInfo; - FHoudiniApi::ParmInfo_Init(&FoundParamInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParamInfo), false); - - // .. and value - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), NodeId, false, - &StringHandle, FoundParamInfo.stringValuesIndex, 1), false); - - // Convert the string handle to FString - return FHoudiniEngineString::ToFString(StringHandle, OutValue); -} - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const int32& DefaultValue, - int32& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParmInfo; - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), false); - - // .. and value - int32 Value = DefaultValue; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), NodeId, &Value, - FoundParmInfo.intValuesIndex, 1), false); - - OutValue = Value; - - return true; -} - - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsFloat( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const float& DefaultValue, - float& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParmInfo; - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), false); - - // .. and value - float Value = DefaultValue; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), NodeId, &Value, - FoundParmInfo.floatValuesIndex, 1), false); - - OutValue = Value; - - return true; -} - -HAPI_ParmId -FHoudiniEngineUtils::HapiFindParameterByName(const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo) -{ - // Try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - InNodeId, InParmName.c_str(), &ParmId), -1); - - if (ParmId < 0) - return -1; - - FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmId, &OutFoundParmInfo), -1); - - return ParmId; -} - -HAPI_ParmId -FHoudiniEngineUtils::HapiFindParameterByTag(const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo) -{ - // Try to find the parameter by its tag - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmWithTag( - FHoudiniEngine::Get().GetSession(), - InNodeId, InParmTag.c_str(), &ParmId), -1); - - if (ParmId < 0) - return -1; - - FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmId, &OutFoundParmInfo), -1); - - return ParmId; -} - -int32 -FHoudiniEngineUtils::HapiGetAttributeOfType( - const HAPI_NodeId& GeoId, - const HAPI_NodeId& PartId, - const HAPI_AttributeOwner& AttributeOwner, - const HAPI_AttributeTypeInfo& AttributeType, - TArray& MatchingAttributesInfo, - TArray& MatchingAttributesName) -{ - int32 NumberOfAttributeFound = 0; - - // Get the part infos - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, &PartInfo), NumberOfAttributeFound); - - // Get All attribute names for that part - int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; - - TArray AttribNameSHArray; - AttribNameSHArray.SetNum(nAttribCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, AttributeOwner, - AttribNameSHArray.GetData(), nAttribCount), NumberOfAttributeFound); - - TArray AttribNameArray; - FHoudiniEngineString::SHArrayToFStringArray(AttribNameSHArray, AttribNameArray); - - // Iterate on all the attributes, and get their part infos to get their type - for (int32 Idx = 0; Idx < AttribNameArray.Num(); Idx++) - { - FString HapiString = AttribNameArray[Idx]; - - // ... then the attribute info - HAPI_AttributeInfo AttrInfo; - FHoudiniApi::AttributeInfo_Init(&AttrInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, TCHAR_TO_UTF8(*HapiString), - AttributeOwner, &AttrInfo)) - continue; - - if (!AttrInfo.exists) - continue; - - // ... check the type - if (AttrInfo.typeInfo != AttributeType) - continue; - - MatchingAttributesInfo.Add(AttrInfo); - MatchingAttributesName.Add(HapiString); - - NumberOfAttributeFound++; - } - - return NumberOfAttributeFound; -} - -HAPI_PartInfo -FHoudiniEngineUtils::ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo) -{ - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - - PartInfo.id = InHPartInfo.PartId; - //PartInfo.nameSH = InHPartInfo.Name; - - switch (InHPartInfo.Type) - { - case EHoudiniPartType::Mesh: - PartInfo.type = HAPI_PARTTYPE_MESH; - break; - case EHoudiniPartType::Curve: - PartInfo.type = HAPI_PARTTYPE_CURVE; - break; - case EHoudiniPartType::Instancer: - PartInfo.type = HAPI_PARTTYPE_INSTANCER; - break; - case EHoudiniPartType::Volume: - PartInfo.type = HAPI_PARTTYPE_VOLUME; - break; - default: - case EHoudiniPartType::Invalid: - PartInfo.type = HAPI_PARTTYPE_INVALID; - break; - } - - PartInfo.faceCount = InHPartInfo.FaceCount; - PartInfo.vertexCount = InHPartInfo.VertexCount; - PartInfo.pointCount = InHPartInfo.PointCount; - - PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = InHPartInfo.PointAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX] = InHPartInfo.VertexAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_PRIM] = InHPartInfo.PrimitiveAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL] = InHPartInfo.DetailAttributeCounts; - - PartInfo.isInstanced = InHPartInfo.bIsInstanced; - - PartInfo.instancedPartCount = InHPartInfo.InstancedPartCount; - PartInfo.instanceCount = InHPartInfo.InstanceCount; - - PartInfo.hasChanged = InHPartInfo.bHasChanged; - - return PartInfo; -} - -int32 -FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - TArray< FHoudiniMeshSocket >& AllSockets, - const bool& isPackedPrim) -{ - int32 FoundSocketCount = 0; - - // Attributes we are interested in. - // Position - TArray Positions; - HAPI_AttributeInfo AttribInfoPositions; - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bool bHasRotation = false; - TArray Rotations; - HAPI_AttributeInfo AttribInfoRotations; - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bool bHasScale = false; - TArray Scales; - HAPI_AttributeInfo AttribInfoScales; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // Socket Name - bool bHasNames = false; - TArray Names; - HAPI_AttributeInfo AttribInfoNames; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bool bHasActors = false; - TArray Actors; - HAPI_AttributeInfo AttribInfoActors; - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bool bHasTags = false; - TArray Tags; - HAPI_AttributeInfo AttribInfoTags; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - - // Lambda function for creating the socket and adding it to the array - // Shared between the by Attribute / by Group methods - auto AddSocketToArray = [&](const int32& PointIdx) - { - FHoudiniMeshSocket CurrentSocket; - FVector currentPosition = FVector::ZeroVector; - if (Positions.IsValidIndex(PointIdx * 3 + 2)) - { - currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - FVector currentScale = FVector::OneVector; - if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) - { - currentScale.X = Scales[PointIdx * 3]; - currentScale.Y = Scales[PointIdx * 3 + 2]; - currentScale.Z = Scales[PointIdx * 3 + 1]; - } - - FQuat currentRotation = FQuat::Identity; - if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) - { - currentRotation.X = Rotations[PointIdx * 4]; - currentRotation.Y = Rotations[PointIdx * 4 + 2]; - currentRotation.Z = Rotations[PointIdx * 4 + 1]; - currentRotation.W = -Rotations[PointIdx * 4 + 3]; - } - - if (bHasNames && Names.IsValidIndex(PointIdx)) - CurrentSocket.Name = Names[PointIdx]; - - if (bHasActors && Actors.IsValidIndex(PointIdx)) - CurrentSocket.Actor = Actors[PointIdx]; - - if (bHasTags && Tags.IsValidIndex(PointIdx)) - CurrentSocket.Tag = Tags[PointIdx]; - - // If the scale attribute wasn't set on all socket, we might end up - // with a zero scale socket, avoid that. - if (currentScale == FVector::ZeroVector) - currentScale = FVector::OneVector; - - CurrentSocket.Transform.SetLocation(currentPosition); - CurrentSocket.Transform.SetRotation(currentRotation); - CurrentSocket.Transform.SetScale3D(currentScale); - - // We want to make sure we're not adding the same socket multiple times - AllSockets.AddUnique(CurrentSocket); - - FoundSocketCount++; - - return true; - }; - - - // Lambda function for reseting the arrays/attributes - auto ResetArraysAndAttr = [&]() - { - // Position - Positions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bHasRotation = false; - Rotations.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bHasScale = false; - Scales.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // Socket Name - bHasNames = false; - Names.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bHasActors = false; - Actors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bHasTags = false; - Tags.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - }; - - //------------------------------------------------------------------------- - // FIND SOCKETS BY DETAIL ATTRIBUTES - //------------------------------------------------------------------------- - - int32 SocketIdx = 0; - bool HasSocketAttributes = true; - while (HasSocketAttributes) - { - // Build the current socket's prefix - FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(SocketIdx); - - // Reset the arrays and attributes - ResetArraysAndAttr(); - - // Retrieve position data. - FString SocketPosAttr = SocketAttrPrefix + TEXT("_pos"); - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, TCHAR_TO_ANSI(*SocketPosAttr), - AttribInfoPositions, Positions, 0, HAPI_ATTROWNER_DETAIL)) - break; - - if (!AttribInfoPositions.exists) - { - // No need to keep looking for socket attributes - HasSocketAttributes = false; - break; - } - - // Retrieve rotation data. - FString SocketRotAttr = SocketAttrPrefix + TEXT("_rot"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketRotAttr), AttribInfoRotations, Rotations, 0, HAPI_ATTROWNER_DETAIL)) - bHasRotation = true; - - // Retrieve scale data. - FString SocketScaleAttr = SocketAttrPrefix + TEXT("_scale"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketScaleAttr), AttribInfoScales, Scales, 0, HAPI_ATTROWNER_DETAIL)) - bHasScale = true; - - // Retrieve mesh socket names. - FString SocketNameAttr = SocketAttrPrefix + TEXT("_name"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketNameAttr), AttribInfoNames, Names)) - bHasNames = true; - - // Retrieve mesh socket actor. - FString SocketActorAttr = SocketAttrPrefix + TEXT("_actor"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketActorAttr), AttribInfoActors, Actors)) - bHasActors = true; - - // Retrieve mesh socket tags. - FString SocketTagAttr = SocketAttrPrefix + TEXT("_tag"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketTagAttr), AttribInfoTags, Tags)) - bHasTags = true; - - // Add the socket to the array - AddSocketToArray(0); - - // Try to find the next socket - SocketIdx++; - } - - return FoundSocketCount; -} - - -int32 -FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - TArray& AllSockets, - const bool& isPackedPrim) -{ - // Attributes we are interested in. - // Position - TArray Positions; - HAPI_AttributeInfo AttribInfoPositions; - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bool bHasRotation = false; - TArray Rotations; - HAPI_AttributeInfo AttribInfoRotations; - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bool bHasScale = false; - TArray Scales; - HAPI_AttributeInfo AttribInfoScales; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // We can also get the sockets rotation from the normal - bool bHasNormals = false; - TArray Normals; - HAPI_AttributeInfo AttribInfoNormals; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Socket Name - bool bHasNames = false; - TArray Names; - HAPI_AttributeInfo AttribInfoNames; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bool bHasActors = false; - TArray Actors; - HAPI_AttributeInfo AttribInfoActors; - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bool bHasTags = false; - TArray Tags; - HAPI_AttributeInfo AttribInfoTags; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - - // Lambda function for creating the socket and adding it to the array - // Shared between the by Attribute / by Group methods - int32 FoundSocketCount = 0; - auto AddSocketToArray = [&](const int32& PointIdx) - { - FHoudiniMeshSocket CurrentSocket; - FVector currentPosition = FVector::ZeroVector; - if (Positions.IsValidIndex(PointIdx * 3 + 2)) - { - currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - FVector currentScale = FVector::OneVector; - if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) - { - currentScale.X = Scales[PointIdx * 3]; - currentScale.Y = Scales[PointIdx * 3 + 2]; - currentScale.Z = Scales[PointIdx * 3 + 1]; - } - - FQuat currentRotation = FQuat::Identity; - if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) - { - currentRotation.X = Rotations[PointIdx * 4]; - currentRotation.Y = Rotations[PointIdx * 4 + 2]; - currentRotation.Z = Rotations[PointIdx * 4 + 1]; - currentRotation.W = -Rotations[PointIdx * 4 + 3]; - } - else if (bHasNormals && Normals.IsValidIndex(PointIdx * 3 + 2)) - { - FVector vNormal; - vNormal.X = Normals[PointIdx * 3]; - vNormal.Y = Normals[PointIdx * 3 + 2]; - vNormal.Z = Normals[PointIdx * 3 + 1]; - - if (vNormal != FVector::ZeroVector) - currentRotation = FQuat::FindBetween(FVector::UpVector, vNormal); - } - - if (bHasNames && Names.IsValidIndex(PointIdx)) - CurrentSocket.Name = Names[PointIdx]; - - if (bHasActors && Actors.IsValidIndex(PointIdx)) - CurrentSocket.Actor = Actors[PointIdx]; - - if (bHasTags && Tags.IsValidIndex(PointIdx)) - CurrentSocket.Tag = Tags[PointIdx]; - - // If the scale attribute wasn't set on all socket, we might end up - // with a zero scale socket, avoid that. - if (currentScale == FVector::ZeroVector) - currentScale = FVector::OneVector; - - CurrentSocket.Transform.SetLocation(currentPosition); - CurrentSocket.Transform.SetRotation(currentRotation); - CurrentSocket.Transform.SetScale3D(currentScale); - - // We want to make sure we're not adding the same socket multiple times - AllSockets.AddUnique(CurrentSocket); - - FoundSocketCount++; - - return true; - }; - - - // Lambda function for reseting the arrays/attributes - auto ResetArraysAndAttr = [&]() - { - // Position - Positions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bHasRotation = false; - Rotations.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bHasScale = false; - Scales.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // When using socket groups, we can also get the sockets rotation from the normal - bHasNormals = false; - Normals.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Socket Name - bHasNames = false; - Names.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bHasActors = false; - Actors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bHasTags = false; - Tags.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - }; - - //------------------------------------------------------------------------- - // FIND SOCKETS BY POINT GROUPS - //------------------------------------------------------------------------- - - // Get object / geo group memberships for primitives. - TArray GroupNames; - if (!FHoudiniEngineUtils::HapiGetGroupNames( - GeoId, PartId, HAPI_GROUPTYPE_POINT, isPackedPrim, GroupNames)) - { - HOUDINI_LOG_MESSAGE(TEXT("GetMeshSocketList: Geo [%d] Part [%d] non-fatal error reading point group names"), GeoId, PartId); - } - - // First, we want to make sure we have at least one socket group before continuing - bool bHasSocketGroup = false; - for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) - { - const FString & GroupName = GroupNames[GeoGroupNameIdx]; - if (GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) - || GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) - { - bHasSocketGroup = true; - break; - } - } - - if (!bHasSocketGroup) - return FoundSocketCount; - - // Get the part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PartInfo)) - return false; - - // Reset the data arrays and attributes - ResetArraysAndAttr(); - - // Retrieve position data. - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, Positions)) - return false; - - // Retrieve rotation data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_ROTATION, AttribInfoRotations, Rotations)) - bHasRotation = true; - - // Retrieve normal data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, Normals)) - bHasNormals = true; - - // Retrieve scale data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_SCALE, AttribInfoScales, Scales)) - bHasScale = true; - - // Retrieve mesh socket names. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, AttribInfoNames, Names)) - bHasNames = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD, AttribInfoNames, Names)) - bHasNames = true; - - // Retrieve mesh socket actor. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR, AttribInfoActors, Actors)) - bHasActors = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD, AttribInfoActors, Actors)) - bHasActors = true; - - // Retrieve mesh socket tags. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, AttribInfoTags, Tags)) - bHasTags = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD, AttribInfoTags, Tags)) - bHasTags = true; - - // Extracting Sockets vertices - for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) - { - const FString & GroupName = GroupNames[GeoGroupNameIdx]; - if (!GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) - && !GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) - continue; - - bool AllEquals = false; - TArray< int32 > PointGroupMembership; - FHoudiniEngineUtils::HapiGetGroupMembership( - GeoId, PartInfo, HAPI_GROUPTYPE_POINT, GroupName, PointGroupMembership, AllEquals); - - // Go through all primitives. - for (int32 PointIdx = 0; PointIdx < PointGroupMembership.Num(); ++PointIdx) - { - if (PointGroupMembership[PointIdx] == 0) - { - if (AllEquals) - break; - else - continue; - } - - // Add the corresponding socket to the array - AddSocketToArray(PointIdx); - } - } - - return FoundSocketCount; -} - -bool -FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( - UStaticMesh* StaticMesh, - TArray& AllSockets, - const bool& CleanImportSockets) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Remove the sockets from the previous cook! - if (CleanImportSockets) - { - StaticMesh->Sockets.RemoveAll([=](UStaticMeshSocket* Socket) { return Socket ? Socket->bSocketCreatedAtImport : true; }); - } - - if (AllSockets.Num() <= 0) - return true; - - // Having sockets with empty names can lead to various issues, so we'll create one now - for (int32 Idx = 0; Idx < AllSockets.Num(); ++Idx) - { - // Assign the unnamed sockets with default names - if (AllSockets[Idx].Name.IsEmpty()) - AllSockets[Idx].Name = TEXT("Socket ") + FString::FromInt(Idx); - } - - // ensure the socket names are unique. (Unreal will use the first socket if multiple socket have the same name) - for (int32 Idx_i = 0; Idx_i < AllSockets.Num(); ++Idx_i) - { - int32 Count = 0; - for (int32 Idx_j = Idx_i + 1; Idx_j < AllSockets.Num(); ++Idx_j) - { - if (AllSockets[Idx_i].Name.Equals(AllSockets[Idx_j].Name)) - { - Count += 1; - AllSockets[Idx_j].Name = AllSockets[Idx_j].Name + "_" + FString::FromInt(Count); - } - } - } - - // Clear all the sockets of the output static mesh. - StaticMesh->Sockets.Empty(); - - for (int32 nSocket = 0; nSocket < AllSockets.Num(); nSocket++) - { - // Create a new Socket - UStaticMeshSocket* Socket = NewObject(StaticMesh); - if (!Socket || Socket->IsPendingKill()) - continue; - - Socket->RelativeLocation = AllSockets[nSocket].Transform.GetLocation(); - Socket->RelativeRotation = FRotator(AllSockets[nSocket].Transform.GetRotation()); - Socket->RelativeScale = AllSockets[nSocket].Transform.GetScale3D(); - Socket->SocketName = FName(*AllSockets[nSocket].Name); - - // Socket Tag - FString Tag; - if (!AllSockets[nSocket].Tag.IsEmpty()) - Tag = AllSockets[nSocket].Tag; - - // The actor will be stored temporarily in the socket's Tag as we need a StaticMeshComponent to add an actor to the socket - Tag += TEXT("|") + AllSockets[nSocket].Actor; - - Socket->Tag = Tag; - Socket->bSocketCreatedAtImport = true; - - StaticMesh->Sockets.Add(Socket); - } - - return true; -} - -bool -FHoudiniEngineUtils::CreateAttributesFromTags( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const TArray& Tags ) -{ - if (Tags.Num() <= 0) - return false; - - HAPI_Result Result = HAPI_RESULT_FAILURE; - - // Get the destination part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); - - bool NeedToCommitGeo = false; - for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) - { - FString TagString; - Tags[TagIdx].ToString(TagString); - SanitizeHAPIVariableName(TagString); - - // Create a primitive attribute for the tag - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - AttributeInfo.count = PartInfo.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; - - FString AttributeName = TEXT(HAPI_UNREAL_ATTRIB_TAG_PREFIX) + FString::FromInt(TagIdx); - AttributeName.RemoveSpacesInline(); - - Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo); - - if (Result != HAPI_RESULT_SUCCESS) - continue; - - TArray TagStr; - TagStr.Add(FHoudiniEngineUtils::ExtractRawString(TagString)); - - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo, - TagStr.GetData(), 0, AttributeInfo.count); - - if (HAPI_RESULT_SUCCESS == Result) - NeedToCommitGeo = true; - - // Free memory for allocated by ExtractRawString - FHoudiniEngineUtils::FreeRawStringMemory(TagStr); - } - - return NeedToCommitGeo; -} - -bool -FHoudiniEngineUtils::CreateGroupsFromTags( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const TArray& Tags ) -{ - if (Tags.Num() <= 0) - return true; - - HAPI_Result Result = HAPI_RESULT_FAILURE; - - // Get the destination part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); - - bool NeedToCommitGeo = false; - for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) - { - FString TagString; - Tags[TagIdx].ToString(TagString); - SanitizeHAPIVariableName(TagString); - - const char * TagStr = FHoudiniEngineUtils::ExtractRawString(TagString); - - // Create a primitive group for this tag - if ( HAPI_RESULT_SUCCESS == FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, TagStr) ) - { - // Set the group's Memberships - TArray GroupArray; - GroupArray.Init(1, PartInfo.faceCount); - - if ( HAPI_RESULT_SUCCESS == FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_GROUPTYPE_PRIM, TagStr, - GroupArray.GetData(), 0, PartInfo.faceCount) ) - { - NeedToCommitGeo = true; - } - } - - // Free memory allocated by ExtractRawString() - FHoudiniEngineUtils::FreeRawStringMemory(TagStr); - } - - return NeedToCommitGeo; -} - - -bool -FHoudiniEngineUtils::SanitizeHAPIVariableName(FString& String) -{ - // Only keep alphanumeric characters, underscores - // Also, if the first character is a digit, append an underscore at the beginning - TArray& StrArray = String.GetCharArray(); - if (StrArray.Num() <= 0) - return false; - - for (auto& CurChar : StrArray) - { - const bool bIsValid = (CurChar >= TEXT('A') && CurChar <= TEXT('Z')) - || (CurChar >= TEXT('a') && CurChar <= TEXT('z')) - || (CurChar >= TEXT('0') && CurChar <= TEXT('9')) - || (CurChar == TEXT('_')) || (CurChar == TEXT('\0')); - - if(bIsValid) - continue; - - CurChar = TEXT('_'); - } - - if (StrArray.Num() > 0) - { - TCHAR FirstChar = StrArray[0]; - if (FirstChar >= TEXT('0') && FirstChar <= TEXT('9')) - StrArray.Insert(TEXT('_'), 0); - } - - return true; -} - -bool -FHoudiniEngineUtils::GetUnrealTagAttributes( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags) -{ - FString TagAttribBase = TEXT("unreal_tag_"); - bool bAttributeFound = true; - int32 TagIdx = 0; - while (bAttributeFound) - { - FString CurrentTagAttr = TagAttribBase + FString::FromInt(TagIdx++); - bAttributeFound = HapiCheckAttributeExists(GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), HAPI_ATTROWNER_PRIM); - if (!bAttributeFound) - break; - - // found the unreal_tag_X attribute, get its value and add it to the array - FString TagValue = FString(); - - // Create an AttributeInfo - { - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) - { - TagValue = StringData[0]; - } - } - - FName NameTag = *TagValue; - OutTags.Add(NameTag); - } - - return true; -} - - -int32 -FHoudiniEngineUtils::GetGenericAttributeList( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FString& InGenericAttributePrefix, - TArray& OutFoundAttributes, - const HAPI_AttributeOwner& AttributeOwner, - const int32& InAttribIndex) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineUtils::GetGenericAttributeList); - - // Get the part info to get the attribute counts for the specified owner - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), InGeoNodeId, InPartId, &PartInfo), false); - - int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; - - // Get all attribute names for that part - TArray AttribNameSHArray; - AttribNameSHArray.SetNum(nAttribCount); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, AttributeOwner, - AttribNameSHArray.GetData(), nAttribCount)) - { - return 0; - } - - // For everything but detail attribute, - // if an attribute index was specified, only extract the attribute value for that specific index - // if not, extract all values for the given attribute - bool HandleSplit = false; - int32 AttribIndex = -1; - if ((AttributeOwner != HAPI_ATTROWNER_DETAIL) && (InAttribIndex != -1)) - { - // The index has already been specified so we'll use it - HandleSplit = true; - AttribIndex = InAttribIndex; - } - - int32 FoundCount = 0; - for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) - { - int32 AttribNameSH = (int32)AttribNameSHArray[Idx]; - FString AttribName = TEXT(""); - FHoudiniEngineString::ToFString(AttribNameSH, AttribName); - if (!AttribName.StartsWith(InGenericAttributePrefix, ESearchCase::IgnoreCase)) - continue; - - // Get the Attribute Info - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), AttributeOwner, &AttribInfo)) - { - // failed to get that attribute's info - continue; - } - - int32 AttribStart = 0; - int32 AttribCount = AttribInfo.count; - if (HandleSplit) - { - // For split primitives, we need to only get only one value for the proper split prim - // Make sure that the split index is valid - if (AttribIndex >= 0 && AttribIndex < AttribInfo.count) - { - AttribStart = AttribIndex; - AttribCount = 1; - } - } - - // - FHoudiniGenericAttribute CurrentGenericAttribute; - // Remove the generic attribute prefix - CurrentGenericAttribute.AttributeName = AttribName.Right(AttribName.Len() - InGenericAttributePrefix.Len()); - - CurrentGenericAttribute.AttributeOwner = (EAttribOwner)AttribInfo.owner; - - // Get the attribute type and tuple size - CurrentGenericAttribute.AttributeType = (EAttribStorageType)AttribInfo.storage; - CurrentGenericAttribute.AttributeCount = AttribInfo.count; - CurrentGenericAttribute.AttributeTupleSize = AttribInfo.tupleSize; - - if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT64) - { - // Initialize the value array - CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloat64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, 0, - CurrentGenericAttribute.DoubleValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT) - { - // Initialize the value array - TArray FloatValues; - FloatValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, FloatValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to double - CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - for (int32 n = 0; n < FloatValues.Num(); n++) - CurrentGenericAttribute.DoubleValues[n] = (double)FloatValues[n]; - - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT64) - { -#if PLATFORM_LINUX - // On Linux, we unfortunately cannot guarantee that int64 and HAPI_Int64 - // are of the same type, to properly read the value, we must first check the - // size, then either cast them (if sizes match) or convert the values (if sizes don't match) - if (sizeof(int64) != sizeof(HAPI_Int64)) - { - // int64 and HAPI_Int64 are of different size, we need to cast - TArray HAPIIntValues; - HAPIIntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, HAPIIntValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to int64 - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - for (int32 n = 0; n < HAPIIntValues.Num(); n++) - CurrentGenericAttribute.IntValues[n] = (int64)HAPIIntValues[n]; - } - else - { - // Initialize the value array - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) with a reinterpret_cast since sizes match - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, reinterpret_cast(CurrentGenericAttribute.IntValues.GetData()), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - } -#else - // Initialize the value array - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, CurrentGenericAttribute.IntValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } -#endif - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT) - { - // Initialize the value array - TArray IntValues; - IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, IntValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to int64 - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - for (int32 n = 0; n < IntValues.Num(); n++) - CurrentGenericAttribute.IntValues[n] = (int64)IntValues[n]; - - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - // Initialize a string handle array - TArray HapiSHArray; - HapiSHArray.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the string handle(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - HapiSHArray.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert the String Handles to FStrings - // using a map to minimize the number of HAPI calls - FHoudiniEngineString::SHArrayToFStringArray(HapiSHArray, CurrentGenericAttribute.StringValues); - } - else - { - // Unsupported type, skipping! - continue; - } - - // We can add the UPropertyAttribute to the array - OutFoundAttributes.Add(CurrentGenericAttribute); - FoundCount++; - } - - return FoundCount; -} - - -bool -FHoudiniEngineUtils::GetGenericPropertiesAttributes(const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const bool InbFindDetailAttributes, const int32& InFirstValidPrimIndex, const int32& InFirstValidVertexIndex, const int32& InFirstValidPointIndex, - TArray& OutPropertyAttributes) -{ - int32 FoundCount = 0; - - // List all the generic property detail attributes ... - if (InbFindDetailAttributes) - { - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - } - - // .. then the primitive property attributes for the given prim - if (InFirstValidPrimIndex != INDEX_NONE) - { - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); - } - - if (InFirstValidVertexIndex != INDEX_NONE) - { - // .. then finally, point uprop attributes for the given point - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_VERTEX, InFirstValidVertexIndex); - } - - if (InFirstValidPointIndex != INDEX_NONE) - { - // .. then finally, point uprop attributes for the given point - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidPointIndex); - } - - return FoundCount > 0; -} - -bool -FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(UObject* InObject, - const TArray& InAllPropertyAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (const auto& CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current Property Attribute - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - NumSuccess++; -#if defined(HOUDINI_ENGINE_LOGGING) - const FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - const FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); -#endif - } - - return (NumSuccess > 0); -} - -bool -FHoudiniEngineUtils::SetGenericPropertyAttribute( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FHoudiniGenericAttribute& InPropertyAttribute) -{ - HAPI_AttributeOwner AttribOwner; - switch (InPropertyAttribute.AttributeOwner) - { - case EAttribOwner::Point: - AttribOwner = HAPI_ATTROWNER_POINT; - break; - case EAttribOwner::Vertex: - AttribOwner = HAPI_ATTROWNER_VERTEX; - break; - case EAttribOwner::Prim: - AttribOwner = HAPI_ATTROWNER_PRIM; - break; - case EAttribOwner::Detail: - AttribOwner = HAPI_ATTROWNER_DETAIL; - break; - case EAttribOwner::Invalid: - default: - HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InPropertyAttribute.AttributeOwner); - return false; - } - - // Create the attribute via HAPI - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.tupleSize = InPropertyAttribute.AttributeTupleSize; - AttributeInfo.count = InPropertyAttribute.AttributeCount; - AttributeInfo.exists = true; - AttributeInfo.owner = AttribOwner; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - switch(InPropertyAttribute.AttributeType) - { - case (EAttribStorageType::INT): - AttributeInfo.storage = HAPI_STORAGETYPE_INT; - break; - case (EAttribStorageType::INT64): - AttributeInfo.storage = HAPI_STORAGETYPE_INT64; - break; - case (EAttribStorageType::FLOAT): - AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT; - break; - case (EAttribStorageType::FLOAT64): - AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT64; - break; - case (EAttribStorageType::STRING): - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - break; - case (EAttribStorageType::Invalid): - default: - HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Storage Type: %d"), InPropertyAttribute.AttributeType); - return false; - } - - // Create the new attribute - if (HAPI_RESULT_SUCCESS != FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo)) - { - return false; - } - - // The New attribute has been successfully created, set its value - switch (InPropertyAttribute.AttributeType) - { - case EAttribStorageType::INT: - { - TArray TempArray; - TempArray.Reserve(InPropertyAttribute.IntValues.Num()); - for (auto Value : InPropertyAttribute.IntValues) - { - TempArray.Add(static_cast(Value)); - } - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, - TempArray.GetData(), 0, AttributeInfo.count)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } - break; - } - case EAttribStorageType::INT64: - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, - InPropertyAttribute.IntValues.GetData(), 0, AttributeInfo.count)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } - break; - } - case EAttribStorageType::FLOAT: - { - - TArray TempArray; - TempArray.Reserve(InPropertyAttribute.DoubleValues.Num()); - for (auto Value : InPropertyAttribute.DoubleValues) - { - TempArray.Add(static_cast(Value)); - } - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, - TempArray.GetData(), 0, AttributeInfo.count)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } - break; - } - case EAttribStorageType::FLOAT64: - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloat64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, - InPropertyAttribute.DoubleValues.GetData(), 0, AttributeInfo.count)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } - break; - } - case EAttribStorageType::STRING: - { - if (HAPI_RESULT_SUCCESS != FHoudiniEngineUtils::SetAttributeStringData( - InPropertyAttribute.StringValues, - InGeoNodeId, - InPartId, - InPropertyAttribute.AttributeName, - AttributeInfo)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } - break; - } - default: - // Unsupported storage type - HOUDINI_LOG_WARNING(TEXT("Unsupported storage type: %d"), InPropertyAttribute.AttributeType); - break; - } - - return true; -} - -void -FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const FString& Key, const FString& Value) -{ - if (!Package || Package->IsPendingKill()) - return; - - UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) - MetaData->SetValue(Object, *Key, *Value); -} - - -bool -FHoudiniEngineUtils::AddLevelPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - ULevel* InLevel, - const int32& InCount) -{ - if (InNodeId < 0 || InCount <= 0) - return false; - - if (!InLevel || InLevel->IsPendingKill()) - return false; - - // Extract the level path from the level - FString LevelPath = InLevel->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = InCount; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(InCount); - for (int32 Idx = 0; Idx < InCount; ++Idx) - { - PrimitiveAttrs[Idx] = LevelPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} - - -bool -FHoudiniEngineUtils::AddActorPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - AActor* InActor, - const int32& InCount) -{ - if (InNodeId < 0 || InCount <= 0) - return false; - - if (!InActor || InActor->IsPendingKill()) - return false; - - // Extract the actor path - FString ActorPath = InActor->GetPathName(); - - // Get name of attribute used for Actor path - std::string MarshallingAttributeActorPath = HAPI_UNREAL_ATTRIB_ACTOR_PATH; - - // Marshall in Actor path. - HAPI_AttributeInfo AttributeInfoActorPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoActorPath); - AttributeInfoActorPath.count = InCount; - AttributeInfoActorPath.tupleSize = 1; - AttributeInfoActorPath.exists = true; - AttributeInfoActorPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoActorPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoActorPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string ActorPathCStr = TCHAR_TO_ANSI(*ActorPath); - const char* ActorPathCStrRaw = ActorPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(InCount); - for (int32 Idx = 0; Idx < InCount; ++Idx) - { - PrimitiveAttrs[Idx] = ActorPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, - MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoActorPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_actor_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} - - -bool -FHoudiniEngineUtils::ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx) -{ - const TArray< FVector2D > & LightmapUVs = RawMesh.WedgeTexCoords[LightmapSourceIdx]; - const TArray< uint32 > & Indices = RawMesh.WedgeIndices; - - if (LightmapUVs.Num() != Indices.Num()) - { - // This is invalid raw mesh; by design we consider that it contains invalid lightmap faces. - return true; - } - - for (int32 Idx = 0; Idx < Indices.Num(); Idx += 3) - { - const FVector2D & uv0 = LightmapUVs[Idx + 0]; - const FVector2D & uv1 = LightmapUVs[Idx + 1]; - const FVector2D & uv2 = LightmapUVs[Idx + 2]; - - if (uv0 == uv1 && uv1 == uv2) - { - // Detect invalid lightmap face, can stop. - return true; - } - } - - // Otherwise there are no invalid lightmap faces. - return false; -} - -void -FHoudiniEngineUtils::CreateSlateNotification( - const FString& NotificationString, const float& NotificationExpire, const float& NotificationFadeOut ) -{ -#if WITH_EDITOR - // Trying to display SlateNotifications while in a background thread will crash UE - if (!IsInGameThread() && !IsInSlateThread() && !IsInAsyncLoadingThread()) - return; - - // Check whether we want to display Slate notifications. - bool bDisplaySlateCookingNotifications = true; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (!bDisplaySlateCookingNotifications) - return; - - FText NotificationText = FText::FromString(NotificationString); - FNotificationInfo Info(NotificationText); - - Info.bFireAndForget = true; - Info.FadeOutDuration = NotificationFadeOut; - Info.ExpireDuration = NotificationExpire; - - TSharedPtr HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); - if (HoudiniBrush.IsValid()) - Info.Image = HoudiniBrush.Get(); - - FSlateNotificationManager::Get().AddNotification(Info); -#endif - - return; -} - -FString -FHoudiniEngineUtils::GetHoudiniEnginePluginDir() -{ - FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(EnginePluginDir)) - return EnginePluginDir; - - FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(ProjectPluginDir)) - return ProjectPluginDir; - - TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); - FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; - if (FPaths::DirectoryExists(PluginBaseDir)) - return PluginBaseDir; - - HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); - - return EnginePluginDir; -} - - -HAPI_Result -FHoudiniEngineUtils::CreateNode( - const HAPI_NodeId& InParentNodeId, - const FString& InOperatorName, - const FString& InNodeLabel, - const HAPI_Bool& bInCookOnCreation, - HAPI_NodeId* OutNewNodeId) -{ - // Call HAPI::CreateNode - HAPI_Result Result = FHoudiniApi::CreateNode( - FHoudiniEngine::Get().GetSession(), - InParentNodeId, TCHAR_TO_UTF8(*InOperatorName), TCHAR_TO_UTF8(*InNodeLabel), bInCookOnCreation, OutNewNodeId); - - // Return now if CreateNode fialed - if (Result != HAPI_RESULT_SUCCESS) - return Result; - - // Loop on the cook_state status until it's ready - int CurrentStatus = HAPI_State::HAPI_STATE_STARTING_LOAD; - while (CurrentStatus > HAPI_State::HAPI_STATE_MAX_READY_STATE) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), - HAPI_StatusType::HAPI_STATUS_COOK_STATE, &CurrentStatus)) - { - // Exit the loop if GetStatus somehow fails - break; - } - } - - if (CurrentStatus == HAPI_STATE_READY_WITH_FATAL_ERRORS) - { - // Fatal errors - failed - HOUDINI_LOG_ERROR(TEXT("Failed to create node %s - %s"), *InOperatorName, *InNodeLabel); - return HAPI_RESULT_FAILURE; - } - else if (CurrentStatus == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // Mention the errors - still return success - HOUDINI_LOG_WARNING(TEXT("Cook errors when creating node %s - %s"), *InOperatorName, *InNodeLabel); - } - - return HAPI_RESULT_SUCCESS; -} - - -int32 -FHoudiniEngineUtils::HapiGetCookCount(const HAPI_NodeId& InNodeId) -{ - int32 CookCount = -1; - - FHoudiniApi::GetTotalCookCount( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_ANY, true, &CookCount); - - /* - // TODO: - // Use HAPI_GetCookingTotalCount() when available - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - - int32 CookCount = -1; - HAPI_Result Result = FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), InNodeId, &NodeInfo); - - if (Result != HAPI_RESULT_FAILURE) - { - if (NodeInfo.type != HAPI_NODETYPE_OBJ) - { - // For SOP assets, get the cook count straight from the Asset Node - CookCount = NodeInfo.totalCookCount; - } - else - { - // For OBJ nodes, get the cook count from the display geos - // Retrieve information about each object contained within our asset. - TArray< HAPI_ObjectInfo > ObjectInfos; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(InNodeId, ObjectInfos)) - return false; - - for (auto CurrentHapiObjectInfo : ObjectInfos) - { - // Get the Display Geo's info - HAPI_GeoInfo DisplayHapiGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) - { - continue; - } - - HAPI_NodeInfo DisplayNodeInfo; - FHoudiniApi::NodeInfo_Init(&DisplayNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), DisplayHapiGeoInfo.nodeId, &DisplayNodeInfo)) - { - continue; - } - - CookCount += DisplayNodeInfo.totalCookCount; - } - } - } - */ - - return CookCount; -} - -bool -FHoudiniEngineUtils::GetLevelPathAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutLevelPaths, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_level_path - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_LEVEL_PATH, AttributeInfo, OutLevelPaths, 1, InAttributeOwner)) - { - if (OutLevelPaths.Num() > 0) - return true; - } - - OutLevelPaths.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetOutputNameAttribute(const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutOutputNames) -{ - // --------------------------------------------- - // Attribute: unreal_output_name - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, AttributeInfo, OutOutputNames, 1)) - { - if (OutOutputNames.Num() > 0) - return true; - } - - OutOutputNames.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, AttributeInfo, OutOutputNames, 1)) - { - if (OutOutputNames.Num() > 0) - return true; - } - - OutOutputNames.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetTileAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutTileValues, - const HAPI_AttributeOwner& InAttribOwner) -{ - // --------------------------------------------- - // Attribute: tile - // --------------------------------------------- - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, - AttribInfoTile, - OutTileValues, - 0, - InAttribOwner)) - { - if (OutTileValues.Num() > 0) - return true; - } - - OutTileValues.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - HAPI_AttributeOwner InAttributeOwner, - TArray& OutBakeFolder, - HAPI_PartId InPartId) -{ - OutBakeFolder.Empty(); - - HAPI_AttributeInfo BakeFolderAttribInfo; - FHoudiniApi::AttributeInfo_Init(&BakeFolderAttribInfo); - if (HapiGetAttributeDataAsString( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, InAttributeOwner)) - { - if (OutBakeFolder.Num() > 0) - return true; - } - - OutBakeFolder.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - TArray& OutBakeFolder, - HAPI_PartId InPartId) -{ - OutBakeFolder.Empty(); - - if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_PRIM, OutBakeFolder, InPartId)) - { - if (OutBakeFolder.Num() > 0) - return true; - } - - if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_DETAIL, OutBakeFolder, InPartId)) - { - if (OutBakeFolder.Num() > 0) - return true; - } - - OutBakeFolder.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeActorAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeActorNames, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_bake_actor - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_BAKE_ACTOR, AttributeInfo, OutBakeActorNames, 1, InAttributeOwner)) - { - if (OutBakeActorNames.Num() > 0) - return true; - } - - OutBakeActorNames.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeOutlinerFolders, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_bake_outliner_folder - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, AttributeInfo, OutBakeOutlinerFolders, 1, InAttributeOwner)) - { - if (OutBakeOutlinerFolders.Num() > 0) - return true; - } - - OutBakeOutlinerFolders.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) -{ - if (!InActor || !InDesiredLevel) - return false; - - ULevel* PreviousLevel = InActor->GetLevel(); - if (PreviousLevel == InDesiredLevel) - return true; - - UWorld* CurrentWorld = InActor->GetWorld(); - if(CurrentWorld) - CurrentWorld->RemoveActor(InActor, true); - - //Set the outer of Actor to NewLevel - FHoudiniEngineUtils::RenameObject(InActor, (const TCHAR*)0, InDesiredLevel); - InDesiredLevel->Actors.Add(InActor); - - return true; -} - -bool -FHoudiniEngineUtils::HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions, const bool& bWaitForCompletion) -{ - // Check for an invalid node id - if (InNodeId < 0) - return false; - - // No Cook Options were specified, use the default one - if (InCookOptions == nullptr) - { - // Use the default cook options - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); - } - else - { - // Use the provided CookOptions - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, InCookOptions), false); - } - - // If we don't need to wait for completion, return now - if (!bWaitForCompletion) - return true; - - // Wait for the cook to finish - HAPI_Result Result = HAPI_RESULT_SUCCESS; - while (true) - { - // Get the current cook status - int Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // The cook has been successful. - return true; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // There was an error while cooking the node. - //FString CookResultString = FHoudiniEngineUtils::GetCookResult(); - //HOUDINI_LOG_ERROR(); - return false; - } - - // We want to yield a bit. - FPlatformProcess::Sleep(0.1f); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineUtils.h" +#include "Misc/StringFormatArg.h" + +#if PLATFORM_WINDOWS + #include "Windows/WindowsHWrapper.h" + + // Of course, Windows defines its own GetGeoInfo, + // So we need to undefine that before including HoudiniApi.h to avoid collision... + #ifdef GetGeoInfo + #undef GetGeoInfo + #endif +#endif + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniEngineString.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInput.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntime.h" + +#if WITH_EDITOR + #include "SAssetSelectionWidget.h" +#endif + +#include "HAPI/HAPI_Version.h" + +#include "Misc/Paths.h" +#include "Editor/EditorEngine.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "PropertyEditorModule.h" +#include "Modules/ModuleManager.h" +#include "Engine/StaticMeshSocket.h" +#include "Async/Async.h" +#include "BlueprintEditor.h" +#include "Toolkits/AssetEditorManager.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "UObject/MetaData.h" +#include "RawMesh.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Interfaces/IPluginManager.h" +//#include "Kismet/BlueprintEditor.h" +#include "SSCSEditor.h" +#include "Engine/WorldComposition.h" + +#if WITH_EDITOR + #include "Interfaces/IMainFrameModule.h" +#endif + +#include + +#include "AssetRegistryModule.h" +#include "FileHelpers.h" +#include "Factories/WorldFactory.h" +#include "HAL/FileManager.h" + +#if WITH_EDITOR + #include "EditorModeManager.h" + #include "EditorModes.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// HAPI_Result strings +const FString kResultStringSuccess(TEXT("Success")); +const FString kResultStringFailure(TEXT("Generic Failure")); +const FString kResultStringAlreadyInitialized(TEXT("Already Initialized")); +const FString kResultStringNotInitialized(TEXT("Not Initialized")); +const FString kResultStringCannotLoadFile(TEXT("Unable to Load File")); +const FString kResultStringParmSetFailed(TEXT("Failed Setting Parameter")); +const FString kResultStringInvalidArgument(TEXT("Invalid Argument")); +const FString kResultStringCannotLoadGeo(TEXT("Uneable to Load Geometry")); +const FString kResultStringCannotGeneratePreset(TEXT("Uneable to Generate Preset")); +const FString kResultStringCannotLoadPreset(TEXT("Uneable to Load Preset")); +const FString kResultStringAssetDefAlrealdyLoaded(TEXT("Asset definition already loaded")); +const FString kResultStringNoLicenseFound(TEXT("No License Found")); +const FString kResultStringDisallowedNCLicenseFound(TEXT("Disallowed Non Commercial License found")); +const FString kResultStringDisallowedNCAssetWithCLicense(TEXT("Disallowed Non Commercial Asset With Commercial License")); +const FString kResultStringDisallowedNCAssetWithLCLicense(TEXT("Disallowed Non Commercial Asset With Limited Commercial License")); +const FString kResultStringDisallowedLCAssetWithCLicense(TEXT("Disallowed Limited Commercial Asset With Commercial License")); +const FString kResultStringDisallowedHengineIndieWith3PartyPlugin(TEXT("Disallowed Houdini Engine Indie With 3rd Party Plugin")); +const FString kResultStringAssetInvalid(TEXT("Invalid Asset")); +const FString kResultStringNodeInvalid(TEXT("Invalid Node")); +const FString kResultStringUserInterrupted(TEXT("User Interrupt")); +const FString kResultStringInvalidSession(TEXT("Invalid Session")); +const FString kResultStringUnknowFailure(TEXT("Unknown Failure")); + +#define DebugTextLine TEXT("===================================") + +const int32 +FHoudiniEngineUtils::PackageGUIDComponentNameLength = 12; + +const int32 +FHoudiniEngineUtils::PackageGUIDItemNameLength = 8; + +const FString +FHoudiniEngineUtils::GetErrorDescription(HAPI_Result Result) +{ + if (Result == HAPI_RESULT_SUCCESS) + { + return kResultStringSuccess; + } + else + { + switch (Result) + { + case HAPI_RESULT_FAILURE: + { + return kResultStringFailure; + } + + case HAPI_RESULT_ALREADY_INITIALIZED: + { + return kResultStringAlreadyInitialized; + } + + case HAPI_RESULT_NOT_INITIALIZED: + { + return kResultStringNotInitialized; + } + + case HAPI_RESULT_CANT_LOADFILE: + { + return kResultStringCannotLoadFile; + } + + case HAPI_RESULT_PARM_SET_FAILED: + { + return kResultStringParmSetFailed; + } + + case HAPI_RESULT_INVALID_ARGUMENT: + { + return kResultStringInvalidArgument; + } + + case HAPI_RESULT_CANT_LOAD_GEO: + { + return kResultStringCannotLoadGeo; + } + + case HAPI_RESULT_CANT_GENERATE_PRESET: + { + return kResultStringCannotGeneratePreset; + } + + case HAPI_RESULT_CANT_LOAD_PRESET: + { + return kResultStringCannotLoadPreset; + } + + case HAPI_RESULT_ASSET_DEF_ALREADY_LOADED: + { + return kResultStringAssetDefAlrealdyLoaded; + } + + case HAPI_RESULT_NO_LICENSE_FOUND: + { + return kResultStringNoLicenseFound; + } + + case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: + { + return kResultStringDisallowedNCLicenseFound; + } + + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: + { + return kResultStringDisallowedNCAssetWithCLicense; + } + + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: + { + return kResultStringDisallowedNCAssetWithLCLicense; + } + + case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: + { + return kResultStringDisallowedLCAssetWithCLicense; + } + + case HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN: + { + return kResultStringDisallowedHengineIndieWith3PartyPlugin; + } + + case HAPI_RESULT_ASSET_INVALID: + { + return kResultStringAssetInvalid; + } + + case HAPI_RESULT_NODE_INVALID: + { + return kResultStringNodeInvalid; + } + + case HAPI_RESULT_USER_INTERRUPTED: + { + return kResultStringUserInterrupted; + } + + case HAPI_RESULT_INVALID_SESSION: + { + return kResultStringInvalidSession; + } + + default: + { + return kResultStringUnknowFailure; + } + }; + } +} + +const FString +FHoudiniEngineUtils::GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity) +{ + const HAPI_Session* SessionPtr = FHoudiniEngine::Get().GetSession(); + if (!SessionPtr) + { + // No valid session + return FString(TEXT("No valid Houdini Engine session.")); + } + + int32 StatusBufferLength = 0; + HAPI_Result Result = FHoudiniApi::GetStatusStringBufLength( + SessionPtr, status_type, verbosity, &StatusBufferLength); + + if (Result == HAPI_RESULT_INVALID_SESSION) + { + // Let FHoudiniEngine know that the sesion is now invalid to "Stop" the invalid session + // and clean things up + FHoudiniEngine::Get().OnSessionLost(); + } + + if (StatusBufferLength > 0) + { + TArray< char > StatusStringBuffer; + StatusStringBuffer.SetNumZeroed(StatusBufferLength); + FHoudiniApi::GetStatusString( + SessionPtr, status_type, &StatusStringBuffer[0], StatusBufferLength); + + return FString(UTF8_TO_TCHAR(&StatusStringBuffer[0])); + } + + return FString(TEXT("")); +} + +const FString +FHoudiniEngineUtils::GetCookResult() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_MESSAGES); +} + +const FString +FHoudiniEngineUtils::GetCookState() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); +} + +const FString +FHoudiniEngineUtils::GetErrorDescription() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS); +} + +const FString +FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId) +{ + int32 NodeErrorLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeNodeCookResult( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_StatusVerbosity::HAPI_STATUSVERBOSITY_ALL, &NodeErrorLength)) + { + NodeErrorLength = 0; + } + + FString NodeError; + if (NodeErrorLength > 0) + { + TArray NodeErrorBuffer; + NodeErrorBuffer.SetNumZeroed(NodeErrorLength); + FHoudiniApi::GetComposedNodeCookResult( + FHoudiniEngine::Get().GetSession(), &NodeErrorBuffer[0], NodeErrorLength); + + NodeError = FString(UTF8_TO_TCHAR(&NodeErrorBuffer[0])); + } + + return NodeError; +} + +const FString +FHoudiniEngineUtils::GetCookLog(TArray& InHACs) +{ + FString CookLog; + + // Get fetch cook status. + FString CookResult = FHoudiniEngineUtils::GetCookResult(); + if (!CookResult.IsEmpty()) + CookLog += TEXT("Cook Results:\n") + CookResult + TEXT("\n\n"); + + // Add the cook state + FString CookState = FHoudiniEngineUtils::GetCookState(); + if (!CookState.IsEmpty()) + CookLog += TEXT("Cook State:\n") + CookState + TEXT("\n\n"); + + // Error Description + FString Error = FHoudiniEngineUtils::GetErrorDescription(); + if (!Error.IsEmpty()) + CookLog += TEXT("Error Description:\n") + Error + TEXT("\n\n"); + + // Iterates on all the selected HAC and get their node errors + for (auto& HAC : InHACs) + { + if (!HAC || HAC->IsPendingKill()) + continue; + + // Get the node errors, warnings and messages + FString NodeErrors = FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(HAC->GetAssetId()); + if (NodeErrors.IsEmpty()) + continue; + + CookLog += NodeErrors; + } + + if (CookLog.IsEmpty()) + { + // See if a failed HAPI initialization / invalid session is preventing us from getting the cook log + if (!FHoudiniApi::IsHAPIInitialized()) + { + CookLog += TEXT("\n\nThe Houdini Engine API Library (HAPI) has not been initialized properly.\n\n"); + } + else + { + const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) + { + CookLog += TEXT("\n\nThe current Houdini Engine Session is not valid.\n\n"); + } + else if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) + { + CookLog += TEXT("\n\nThe current Houdini Engine Session has not been initialized properly.\n\n"); + } + } + + if (!CookLog.IsEmpty()) + { + CookLog += TEXT("Please try to restart the current Houdini Engine session via File > Restart Houdini Engine Session.\n\n"); + } + else + { + CookLog = TEXT("\n\nThe cook log is empty...\n\n"); + } + } + + return CookLog; +} + +const FString +FHoudiniEngineUtils::GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + FString HelpString = TEXT(""); + if (!HoudiniAssetComponent) + return HelpString; + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HAPI_NodeId AssetId = HoudiniAssetComponent->GetAssetId(); + if (AssetId < 0) + return HelpString; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), HelpString); + + if (!FHoudiniEngineString::ToFString(AssetInfo.helpTextSH, HelpString)) + return HelpString; + + if (HelpString.IsEmpty()) + HelpString = TEXT("No Asset Help Found"); + + return HelpString; +} + +void +FHoudiniEngineUtils::ConvertUnrealString(const FString & UnrealString, std::string & String) +{ + String = TCHAR_TO_UTF8(*UnrealString); +} + +UWorld* +FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreateMissingPackage, bool& bOutCreatedPackage) +{ + AActor* Result = nullptr; + UWorld* PackageWorld = nullptr; + + bOutCreatedPackage = false; + + // Try to load existing UWorld from the tile package path. + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!Package) + Package = LoadPackage(nullptr, *PackagePath, LOAD_None); + if (Package) + { + // If the package is not valid (pending kill) rename it + if (Package->IsPendingKill()) + { + if (bCreateMissingPackage) + { + Package->Rename( + *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); + } + } + else + { + PackageWorld = UWorld::FindWorldInPackage(Package); + } + } + + if (!IsValid(PackageWorld) && bCreateMissingPackage) + { + // The map for this tile does not exist. Create one + UWorldFactory* Factory = NewObject(); + Factory->WorldType = EWorldType::Inactive; // World that is being loaded but not currently edited by editor. + PackageWorld = CastChecked(Factory->FactoryCreateNew(UWorld::StaticClass(), Package, NAME_None, RF_Public | RF_Standalone, NULL, GWarn)); + + if (IsValid(PackageWorld)) + { + PackageWorld->PostEditChange(); + PackageWorld->MarkPackageDirty(); + + if(FPackageName::IsValidLongPackageName(PackagePath)) + { + const FString PackageFilename = FPackageName::LongPackageNameToFilename(PackagePath); + bool bSaved = FEditorFileUtils::SaveLevel(PackageWorld->PersistentLevel, *PackageFilename); + } + + FAssetRegistryModule::AssetCreated(PackageWorld); + + bOutCreatedPackage = true; + } + } + + return PackageWorld; +} + +bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + UWorld* CurrentWorld, + const FString& PackagePath, + bool bCreateMissingPackage, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bOutPackageCreated, + bool& bPackageInWorld) +{ + UWorld* PackageWorld = FindWorldInPackage(PackagePath, bCreateMissingPackage, bOutPackageCreated); + if (!IsValid(PackageWorld)) + return false; + + if (PackageWorld->PersistentLevel == CurrentWorld->PersistentLevel) + { + // The loaded world and the package world is one and the same. + OutWorld = CurrentWorld; + OutLevel = CurrentWorld->PersistentLevel; + bPackageInWorld = true; + return true; + } + + if (CurrentWorld->GetLevels().Contains(PackageWorld->PersistentLevel)) + { + // The package level is loaded into CurrentWorld. + OutWorld = CurrentWorld; + OutLevel = PackageWorld->PersistentLevel; + bPackageInWorld = true; + return true; + } + + // The package level is not loaded at all. Send back the on-disk assets. + OutWorld = PackageWorld; + OutLevel = PackageWorld->PersistentLevel; + bPackageInWorld = false; + return true; +} + +void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) +{ + FString WorldPath = FPaths::GetPath(InWorld->GetPathName()); + IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); + TArray Packages; + Packages.Add(WorldPath); + AssetRegistry.ScanPathsSynchronous(Packages, true); +} + +AActor* FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) +{ + // AActor* NamedActor = FindObject(Outer, *InName, false); + // Find ANY actor in the world matching the given name. + AActor* NamedActor = FindActorInWorld(InWorld, FName(InName)); + OutFoundActor = NamedActor; + bool bShouldRename = false; + if (NamedActor) + { + if (NamedActor->GetClass()->IsChildOf(InClass) && !NamedActor->IsPendingKill()) + { + return NamedActor; + } + else + { + FString Suffix; + bool bShouldUpdateLabel = false; + if (NamedActor->IsPendingKill()) + Suffix = "_pendingkill"; + else + Suffix = "_0"; // A previous actor that had the same name. + const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName+Suffix); + } + } + return nullptr; +} + +void FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) +{ + LogPackageInfo( LoadPackage(nullptr, *InLongPackageName, 0) ); +} + +void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) +{ + HOUDINI_LOG_MESSAGE(DebugTextLine); + HOUDINI_LOG_MESSAGE(TEXT("= LogPackageInfo")); + if (!IsValid(InPackage)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Invalid package.")); + HOUDINI_LOG_MESSAGE(DebugTextLine); + return; + } + + HOUDINI_LOG_MESSAGE(TEXT(" = Filename: %s"), *(InPackage->FileName.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = Package Id: %d"), InPackage->GetPackageId().ValueForDebugging()); + HOUDINI_LOG_MESSAGE(TEXT(" = File size: %d"), InPackage->FileSize); + HOUDINI_LOG_MESSAGE(TEXT(" = Contains map: %d"), InPackage->ContainsMap()); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Fully Loaded: %d"), InPackage->IsFullyLoaded()); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Dirty: %d"), InPackage->IsDirty()); + + if (InPackage->WorldTileInfo.IsValid()) + { + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Position: %s"), *(InPackage->WorldTileInfo->Position.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Absolute Position: %s"), *(InPackage->WorldTileInfo->AbsolutePosition.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Bounds: %s"), *(InPackage->WorldTileInfo->Bounds.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - HidInTileView: %d"), InPackage->WorldTileInfo->bHideInTileView); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - ZOrder: %d"), InPackage->WorldTileInfo->ZOrder); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Parent tile package: %s"), *(InPackage->WorldTileInfo->ParentTilePackageName)); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo: NULL")); + } + + HOUDINI_LOG_MESSAGE(DebugTextLine); +} + +void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) +{ + UPackage* Package = LoadPackage(nullptr, *InLongPackageName, 0); + UWorld* World = nullptr; + + if (IsValid(Package)) + { + World = UWorld::FindWorldInPackage(Package); + } + + LogWorldInfo(World); +} + +void FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) +{ + + HOUDINI_LOG_MESSAGE(DebugTextLine); + HOUDINI_LOG_MESSAGE(TEXT("= LogWorldInfo")); + if (!IsValid(InWorld)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Invalid world.")); + HOUDINI_LOG_MESSAGE(DebugTextLine); + return; + } + + // UWorld lacks const-correctness on certain accessors + UWorld* NonConstWorld = const_cast(InWorld); + + HOUDINI_LOG_MESSAGE(TEXT(" = Path Name: %s"), *(InWorld->GetPathName())); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Editor World: %d"), InWorld->IsEditorWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Game World: %d"), InWorld->IsGameWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Preview World: %d"), InWorld->IsPreviewWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Actor Count: %d"), NonConstWorld->GetActorCount() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Num Levels: %d"), InWorld->GetNumLevels() ); + + if (IsValid(InWorld->WorldComposition)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Composition - Num Tiles: %d"), InWorld->WorldComposition->GetTilesList().Num() ); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT(" = World Composition NULL") ); + } + + + + HOUDINI_LOG_MESSAGE(DebugTextLine); +} + +FString +FHoudiniEngineUtils::HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType) +{ + switch (InEventType) + { + case HAPI_PDG_EVENT_NULL: + return TEXT("HAPI_PDG_EVENT_NULL"); + + case HAPI_PDG_EVENT_WORKITEM_ADD: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE"); + case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_DEP"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_PARENT"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT"); + + case HAPI_PDG_EVENT_NODE_CLEAR: + return TEXT("HAPI_PDG_EVENT_NODE_CLEAR"); + + case HAPI_PDG_EVENT_COOK_ERROR: + return TEXT("HAPI_PDG_EVENT_COOK_ERROR"); + case HAPI_PDG_EVENT_COOK_WARNING: + return TEXT("HAPI_PDG_EVENT_COOK_WARNING"); + + case HAPI_PDG_EVENT_COOK_COMPLETE: + return TEXT("HAPI_PDG_EVENT_COOK_COMPLETE"); + + case HAPI_PDG_EVENT_DIRTY_START: + return TEXT("HAPI_PDG_EVENT_DIRTY_START"); + case HAPI_PDG_EVENT_DIRTY_STOP: + return TEXT("HAPI_PDG_EVENT_DIRTY_STOP"); + + case HAPI_PDG_EVENT_DIRTY_ALL: + return TEXT("HAPI_PDG_EVENT_DIRTY_ALL"); + + case HAPI_PDG_EVENT_UI_SELECT: + return TEXT("HAPI_PDG_EVENT_UI_SELECT"); + + case HAPI_PDG_EVENT_NODE_CREATE: + return TEXT("HAPI_PDG_EVENT_NODE_CREATE"); + case HAPI_PDG_EVENT_NODE_REMOVE: + return TEXT("HAPI_PDG_EVENT_NODE_REMOVE"); + case HAPI_PDG_EVENT_NODE_RENAME: + return TEXT("HAPI_PDG_EVENT_NODE_RENAME"); + case HAPI_PDG_EVENT_NODE_CONNECT: + return TEXT("HAPI_PDG_EVENT_NODE_CONNECT"); + case HAPI_PDG_EVENT_NODE_DISCONNECT: + return TEXT("HAPI_PDG_EVENT_NODE_DISCONNECT"); + + case HAPI_PDG_EVENT_WORKITEM_SET_INT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_INT"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FLOAT"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_STRING: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_STRING"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FILE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FILE"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_MERGE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_MERGE"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_RESULT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_RESULT"); + + case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED + return TEXT("HAPI_PDG_EVENT_WORKITEM_PRIORITY"); + + case HAPI_PDG_EVENT_COOK_START: + return TEXT("HAPI_PDG_EVENT_COOK_START"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR"); + + case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: + return TEXT("HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE"); + + case HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED: + return TEXT("HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED"); + + case HAPI_PDG_EVENT_ALL: + return TEXT("HAPI_PDG_EVENT_ALL"); + case HAPI_PDG_EVENT_LOG: + return TEXT("HAPI_PDG_EVENT_LOG"); + + case HAPI_PDG_EVENT_SCHEDULER_ADDED: + return TEXT("HAPI_PDG_EVENT_SCHEDULER_ADDED"); + case HAPI_PDG_EVENT_SCHEDULER_REMOVED: + return TEXT("HAPI_PDG_EVENT_SCHEDULER_REMOVED"); + case HAPI_PDG_EVENT_SET_SCHEDULER: + return TEXT("HAPI_PDG_EVENT_SET_SCHEDULER"); + + case HAPI_PDG_EVENT_SERVICE_MANAGER_ALL: + return TEXT("HAPI_PDG_EVENT_SERVICE_MANAGER_ALL"); + + case HAPI_PDG_CONTEXT_EVENTS: + return TEXT("HAPI_PDG_CONTEXT_EVENTS"); + default: + break; + } + + return FString::Printf(TEXT("Unknown HAPI_PDG_EventType %d"), InEventType); +} + +FString +FHoudiniEngineUtils::HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState) +{ + switch (InWorkitemState) + { + case HAPI_PDG_WORKITEM_UNDEFINED: + return TEXT("HAPI_PDG_WORKITEM_UNDEFINED"); + case HAPI_PDG_WORKITEM_UNCOOKED: + return TEXT("HAPI_PDG_WORKITEM_UNCOOKED"); + case HAPI_PDG_WORKITEM_WAITING: + return TEXT("HAPI_PDG_WORKITEM_WAITING"); + case HAPI_PDG_WORKITEM_SCHEDULED: + return TEXT("HAPI_PDG_WORKITEM_SCHEDULED"); + case HAPI_PDG_WORKITEM_COOKING: + return TEXT("HAPI_PDG_WORKITEM_COOKING"); + case HAPI_PDG_WORKITEM_COOKED_SUCCESS: + return TEXT("HAPI_PDG_WORKITEM_COOKED_SUCCESS"); + case HAPI_PDG_WORKITEM_COOKED_CACHE: + return TEXT("HAPI_PDG_WORKITEM_COOKED_CACHE"); + case HAPI_PDG_WORKITEM_COOKED_FAIL: + return TEXT("HAPI_PDG_WORKITEM_COOKED_FAIL"); + case HAPI_PDG_WORKITEM_COOKED_CANCEL: + return TEXT("HAPI_PDG_WORKITEM_COOKED_CANCEL"); + case HAPI_PDG_WORKITEM_DIRTY: + return TEXT("HAPI_PDG_WORKITEM_DIRTY"); + default: + break; + } + + return FString::Printf(TEXT("Unknown HAPI_PDG_WorkitemState %d"), InWorkitemState); +} + + +// Centralized call to track renaming of objects +bool FHoudiniEngineUtils::RenameObject(UObject* Object, const TCHAR* NewName /*= nullptr*/, UObject* NewOuter /*= nullptr*/, ERenameFlags Flags /*= REN_None*/) +{ + check(Object); + if (AActor* Actor = Cast(Object)) + { + if (Actor->IsPackageExternal()) + { + // There should be no need to choose a specific name for an actor in Houdini Engine, instead setting its label should be enough. + if (FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, NewName)) + { + HOUDINI_LOG_WARNING(TEXT("Called SetActorLabel(%s) on external actor %s instead of Rename : Explicit naming of an actor that is saved in its own external package is prone to cause name clashes when submitting the file.)"), NewName, *Actor->GetName()); + } + // Force to return false (make sure nothing in Houdini Engine plugin relies on actor being renamed to provided name) + return false; + } + } + return Object->Rename(NewName, NewOuter, Flags); +} + +FName +FHoudiniEngineUtils::RenameToUniqueActor(AActor* InActor, const FString& InName) +{ + const FName NewName = MakeUniqueObjectName(InActor->GetOuter(), InActor->GetClass(), FName(InName)); + + FHoudiniEngineUtils::RenameObject(InActor, *(NewName.ToString())); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName.ToString()); + + return NewName; +} + +UObject* +FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) +{ + check(InActor); + + UObject* PrevObj = nullptr; + UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, InActor->GetOuter(), *InName, true); + if (ExistingObject && ExistingObject != InActor) + { + // Rename the existing object + const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName + TEXT("_old"))); + FHoudiniEngineUtils::RenameObject(ExistingObject, *(NewName.ToString())); + PrevObj = ExistingObject; + } + + FHoudiniEngineUtils::RenameObject(InActor, *InName); + + if (UpdateLabel) + { + //InActor->SetActorLabel(InName, true); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, InName); + InActor->Modify(true); + } + + return PrevObj; +} + +void +FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + FHoudiniPackageParams& OutPackageParams, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FString &BakeFolder, + const FString &ObjectName, + const FString &HoudiniAssetName, + EPackageReplaceMode InReplaceMode, + bool bAutomaticallySetAttemptToLoadMissingPackages) +{ + OutPackageParams.GeoId = InIdentifier.GeoId; + OutPackageParams.ObjectId = InIdentifier.ObjectId; + OutPackageParams.PartId = InIdentifier.PartId; + OutPackageParams.BakeFolder = BakeFolder; + OutPackageParams.PackageMode = EPackageMode::Bake; + OutPackageParams.ReplaceMode = InReplaceMode; + OutPackageParams.HoudiniAssetName = HoudiniAssetName; + OutPackageParams.HoudiniAssetActorName = HoudiniAssetName; + OutPackageParams.ObjectName = ObjectName; +} + +void +FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + UWorld* const InWorldContext, + const UHoudiniAssetComponent* HoudiniAssetComponent, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FString &InDefaultObjectName, + const FString &InHoudiniAssetName, + FHoudiniPackageParams& OutPackageParams, + FHoudiniAttributeResolver& OutResolver, + const FString &InDefaultBakeFolder, + EPackageReplaceMode InReplaceMode, + bool bAutomaticallySetAttemptToLoadMissingPackages, + bool bInSkipObjectNameResolutionAndUseDefault, + bool bInSkipBakeFolderResolutionAndUseDefault) +{ + // Configure OutPackageParams with the default (UI value first then fallback to default from settings) object name + // and bake folder. We use the "initial" PackageParams as a helper to populate tokens for the resolver. + // + // User specified attributes (eg unreal_bake_folder) are then resolved, with the defaults being those tokens configured + // from the initial PackageParams. Once resolved, we updated the relevant fields in PackageParams + // (ObjectName and BakeFolder), and update the resolver tokens with these final values. + // + // The resolver is then ready to be used to resolve the rest of the user attributes, such as unreal_level_path. + // + const FString DefaultBakeFolder = !InDefaultBakeFolder.IsEmpty() ? InDefaultBakeFolder : + FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + const bool bHasBakeNameUIOverride = !InOutputObject.BakeName.IsEmpty(); + FillInPackageParamsForBakingOutput( + OutPackageParams, + InIdentifier, + DefaultBakeFolder, + bHasBakeNameUIOverride ? InOutputObject.BakeName : InDefaultObjectName, + InHoudiniAssetName, + InReplaceMode, + bAutomaticallySetAttemptToLoadMissingPackages); + + const TMap& CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); + OutResolver.SetCachedAttributes(CachedAttributes); + OutResolver.SetTokensFromStringMap(Tokens); + +#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING + // Log the cached attributes and tokens for debugging + OutResolver.LogCachedAttributesAndTokens(); +#endif + + if (!bInSkipObjectNameResolutionAndUseDefault) + { + // Resolve the object name + // TODO: currently the UI override is checked first (this should probably change so that attributes are used first) + FString ObjectName; + if (bHasBakeNameUIOverride) + { + ObjectName = InOutputObject.BakeName; + } + else + { + ObjectName = OutResolver.ResolveOutputName(); + if (ObjectName.IsEmpty()) + ObjectName = InDefaultObjectName; + } + // Update the object name in the package params and also update its token + OutPackageParams.ObjectName = ObjectName; + OutResolver.SetToken("object_name", OutPackageParams.ObjectName); + } + + if (!bInSkipBakeFolderResolutionAndUseDefault) + { + // Now resolve the bake folder + const FString BakeFolder = OutResolver.ResolveBakeFolder(); + if (!BakeFolder.IsEmpty()) + OutPackageParams.BakeFolder = BakeFolder; + } + + if (!bInSkipObjectNameResolutionAndUseDefault || !bInSkipBakeFolderResolutionAndUseDefault) + { + // Update the tokens from the package params + OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); + OutResolver.SetTokensFromStringMap(Tokens); + +#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING + // Log the final tokens + OutResolver.LogCachedAttributesAndTokens(); +#endif + } +} + + +bool +FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() +{ + // Update / repopulate the foliage editor mode's mesh list if the foliage editor mode is active. + // TODO: find a better way to do this, the relevant functions are in FEdModeFoliage and FFoliageEdModeToolkit are not API exported + FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); + if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) + { + EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); + EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); + return true; + } + + return false; +} + +void +FHoudiniEngineUtils::GatherLandscapeInputs( + UHoudiniAssetComponent* HAC, + TArray& AllInputLandscapes, + TArray& InputLandscapesToUpdate) +{ + if (!IsValid(HAC)) + return; + + int32 NumInputs = HAC->GetNumInputs(); + + for (int32 InputIndex = 0; InputIndex < NumInputs; InputIndex++ ) + { + UHoudiniInput* CurrentInput = HAC->GetInputAt(InputIndex); + if (!CurrentInput) + continue; + + if (CurrentInput->GetInputType() == EHoudiniInputType::World) + { + // Check if we have any landscapes as world inputs. + CurrentInput->ForAllHoudiniInputObjects([&AllInputLandscapes](UHoudiniInputObject* InputObject) + { + UHoudiniInputLandscape* InputLandscape = Cast(InputObject); + if (InputLandscape) + { + ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); + if (IsValid(LandscapeProxy)) + { + AllInputLandscapes.Add(LandscapeProxy); + } + } + }); + } + + if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) + continue; + + // Get the landscape input's landscape + ALandscapeProxy* InputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); + if (!InputLandscape) + continue; + + AllInputLandscapes.Add(InputLandscape); + + if (CurrentInput->GetUpdateInputLandscape()) + InputLandscapesToUpdate.Add(InputLandscape); + } +} + + +UHoudiniAssetComponent* +FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(const UObject* Obj) +{ + if (!IsValid(Obj)) + return nullptr; + + // Check the direct Outer + UHoudiniAssetComponent* OuterHAC = Cast(Obj->GetOuter()); + if(IsValid(OuterHAC)) + return OuterHAC; + + // Check the whole outer chain + OuterHAC = Obj->GetTypedOuter(); + if (IsValid(OuterHAC)) + return OuterHAC; + + // Finally check if the Object itself is a HaC + UObject* NonConstObj = const_cast(Obj); + OuterHAC = Cast(NonConstObj); + if (IsValid(OuterHAC)) + return OuterHAC; + + return nullptr; +} + + +FString +FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) +{ + // Compute Houdini version string. + FString HoudiniVersionString = FString::Printf( + TEXT("%d.%d.%s%d"), HAPI_VERSION_HOUDINI_MAJOR, + HAPI_VERSION_HOUDINI_MINOR, + (ExtraDigit ? (TEXT("0.")) : TEXT("")), + HAPI_VERSION_HOUDINI_BUILD); + + // If we have a patch version, we need to append it. + if (HAPI_VERSION_HOUDINI_PATCH > 0) + HoudiniVersionString = FString::Printf(TEXT("%s.%d"), *HoudiniVersionString, HAPI_VERSION_HOUDINI_PATCH); + return HoudiniVersionString; +} + + +void * +FHoudiniEngineUtils::LoadLibHAPI(FString & StoredLibHAPILocation) +{ + FString HFSPath = TEXT(""); + void * HAPILibraryHandle = nullptr; + + // Look up HAPI_PATH environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . + FString HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HAPI_PATH")); + if (!HFS_ENV_VAR.IsEmpty()) + HFSPath = HFS_ENV_VAR; + + // Look up environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . + HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HFS")); + if (!HFS_ENV_VAR.IsEmpty()) + HFSPath = HFS_ENV_VAR; + + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + + // If we have a custom location specified through settings, attempt to use that. + bool bCustomPathFound = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->bUseCustomHoudiniLocation) + { + // Create full path to libHAPI binary. + FString CustomHoudiniLocationPath = HoudiniRuntimeSettings->CustomHoudiniLocation.Path; + if (!CustomHoudiniLocationPath.IsEmpty()) + { + // Convert path to absolute if it is relative. + if (FPaths::IsRelative(CustomHoudiniLocationPath)) + CustomHoudiniLocationPath = FPaths::ConvertRelativePathToFull(CustomHoudiniLocationPath); + + FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPICustomPath)) + { + HFSPath = CustomHoudiniLocationPath; + bCustomPathFound = true; + } + } + } + + // We have HFS environment variable defined (or custom location), attempt to load libHAPI from it. + if (!HFSPath.IsEmpty()) + { + if (!bCustomPathFound) + { +#if PLATFORM_WINDOWS + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_WINDOWS); +#elif PLATFORM_MAC + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_MAC); +#elif PLATFORM_LINUX + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_LINUX); +#endif + } + + // Create full path to libHAPI binary. + FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + // libHAPI binary exists at specified location, attempt to load it. + FPlatformProcess::PushDllDirectory(*HFSPath); +#if PLATFORM_WINDOWS + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); +#elif PLATFORM_MAC || PLATFORM_LINUX + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); +#endif + FPlatformProcess::PopDllDirectory(*HFSPath); + + // If library has been loaded successfully we can stop. + if ( HAPILibraryHandle ) + { + if (bCustomPathFound) + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from custom path %s"), *LibHAPIName, *HFSPath); + else + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from HFS environment path %s"), *LibHAPIName, *HFSPath); + + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + } + + // Otherwise, we will attempt to detect Houdini installation. + FString HoudiniLocation = TEXT(HOUDINI_ENGINE_HFS_PATH); + FString LibHAPIPath; + + // Compute Houdini version string. + FString HoudiniVersionString = ComputeVersionString(false); + +#if PLATFORM_WINDOWS + + // On Windows, we have also hardcoded HFS path in plugin configuration file; attempt to load from it. + HFSPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, HAPI_HFS_SUBFOLDER_WINDOWS); + + // Create full path to libHAPI binary. + LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HFSPath); + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); + FPlatformProcess::PopDllDirectory(*HFSPath); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from Plugin defined HFS path %s"), *LibHAPIName, *HFSPath); + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + + // As a second attempt, on Windows, we try to look up location of Houdini Engine in the registry. + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini Engine"), StoredLibHAPILocation, false); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // As a third attempt, we try to look up location of Houdini installation (not Houdini Engine) in the registry. + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini"), StoredLibHAPILocation, false); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // Do similar registry lookups for the 32 bits registry + // Look for the Houdini Engine registry install path + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini Engine"), StoredLibHAPILocation, true); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // ... and for the Houdini registry install path + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini"), StoredLibHAPILocation, true); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // Finally, try to load from a hardcoded program files path. + HoudiniLocation = FString::Printf( + TEXT("C:\\Program Files\\Side Effects Software\\Houdini %s\\%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_WINDOWS); + +#else + +# if PLATFORM_MAC + + // Attempt to load from standard Mac OS X installation. + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/Current/Libraries"), *HoudiniVersionString); + + // Fallback in case the previous one doesnt exist + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/%s/Libraries"), *HoudiniVersionString, *HoudiniVersionString); + + // Fallback in case we're using the steam version + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); + + // Backup Fallback in case we're using the steam version + // (this could probably be removed as paths have changed) + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); + +# elif PLATFORM_LINUX + + // Attempt to load from standard Linux installation. + HoudiniLocation = FString::Printf( + TEXT("/opt/hfs%s/%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_LINUX); + +# endif + +#endif + + // Create full path to libHAPI binary. + LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HoudiniLocation); + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); + FPlatformProcess::PopDllDirectory(*HoudiniLocation); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from expected installation %s"), *LibHAPIName, *HoudiniLocation); + StoredLibHAPILocation = HoudiniLocation; + return HAPILibraryHandle; + } + } + + StoredLibHAPILocation = TEXT(""); + return HAPILibraryHandle; +} + +bool +FHoudiniEngineUtils::IsInitialized() +{ + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) + return false; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) + return false; + + return true; +} + +bool +FHoudiniEngineUtils::IsHoudiniNodeValid(const HAPI_NodeId& NodeId) +{ + if (NodeId < 0) + return false; + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + bool ValidationAnswer = 0; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) + { + return false; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsNodeValid( + FHoudiniEngine::Get().GetSession(), NodeId, + NodeInfo.uniqueHoudiniNodeId, &ValidationAnswer)) + { + return false; + } + + return ValidationAnswer; +} + +bool +FHoudiniEngineUtils::HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex) +{ + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), HostAssetId, InputIndex), false); + + return true; +} + +bool +FHoudiniEngineUtils::DestroyHoudiniAsset(const HAPI_NodeId& AssetId) +{ + if (HAPI_RESULT_SUCCESS == FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), AssetId)) + { + return true; + } + + return false; +} + +#if PLATFORM_WINDOWS +void * +FHoudiniEngineUtils::LocateLibHAPIInRegistry( + const FString & HoudiniInstallationType, + FString & StoredLibHAPILocation, + bool LookIn32bitRegistry) +{ + auto FindDll = [&](const FString& InHoudiniInstallationPath) + { + FString HFSPath = FString::Printf(TEXT("%s/%s"), *InHoudiniInstallationPath, HAPI_HFS_SUBFOLDER_WINDOWS); + + // Create full path to libHAPI binary. + FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, HAPI_LIB_OBJECT_WINDOWS); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HFSPath); + void* HAPILibraryHandle = FPlatformProcess::GetDllHandle(HAPI_LIB_OBJECT_WINDOWS); + FPlatformProcess::PopDllDirectory(*HFSPath); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE( + TEXT("Loaded %s from Registry path %s"), HAPI_LIB_OBJECT_WINDOWS, + *HFSPath); + + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + return (void*)0; + }; + + FString HoudiniInstallationPath; + FString HoudiniVersionString = ComputeVersionString(true); + FString RegistryKey = FString::Printf( + TEXT("Software\\%sSide Effects Software\\%s"), + (LookIn32bitRegistry ? TEXT("WOW6432Node\\") : TEXT("")), *HoudiniInstallationType); + + if (FWindowsPlatformMisc::QueryRegKey( + HKEY_LOCAL_MACHINE, *RegistryKey, *HoudiniVersionString, HoudiniInstallationPath)) + { + FPaths::NormalizeDirectoryName(HoudiniInstallationPath); + return FindDll(HoudiniInstallationPath); + } + + return nullptr; +} +#endif + +bool +FHoudiniEngineUtils::LoadHoudiniAsset(const UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId& OutAssetLibraryId) +{ + OutAssetLibraryId = -1; + + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return false; + + if (!FHoudiniEngineUtils::IsInitialized()) + { + // If we're not initialized now, it likely means the session has been lost + FHoudiniEngine::Get().OnSessionLost(); + return false; + } + + // Get the preferences + bool bMemoryCopyFirst = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bMemoryCopyFirst = HoudiniRuntimeSettings->bPreferHdaMemoryCopyOverHdaSourceFile; + + // Get the HDA's file path + // We need to convert relative file path to absolute + FString AssetFileName = HoudiniAsset->GetAssetFileName(); + if (FPaths::IsRelative(AssetFileName)) + AssetFileName = FPaths::ConvertRelativePathToFull(AssetFileName); + + // We need to modify the file name for expanded .hdas + FString FileExtension = FPaths::GetExtension(AssetFileName); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) + { + // the .hda directory is what we should be loading + AssetFileName = FPaths::GetPath(AssetFileName); + } + + //Check whether we can Load from file/memory + bool bCanLoadFromMemory = (!HoudiniAsset->IsExpandedHDA() && HoudiniAsset->GetAssetBytesCount() > 0); + + // If the hda file exists, we can simply load it directly + bool bCanLoadFromFile = false; + if ( !AssetFileName.IsEmpty() ) + { + if (FPaths::FileExists(AssetFileName) + || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName))) + { + bCanLoadFromFile = true; + } + } + + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Lambda to detect license issues + auto CheckLicenseValid = [&AssetFileName](const HAPI_Result& Result) + { + // HoudiniEngine acquires a license when creating/loading a node, not when creating a session + if (Result >= HAPI_RESULT_NO_LICENSE_FOUND && Result < HAPI_RESULT_ASSET_INVALID) + { + FString ErrorDesc = GetErrorDescription(Result); + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: License failed: %s."), *AssetFileName, *ErrorDesc); + + // We must stop the session to prevent further attempts at loading an HDA + // as this could lead to unreal becoming stuck and unresponsive due to license timeout + FHoudiniEngine::Get().StopSession(); + + // Set the HE status to "no license" + FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); + + return false; + } + else + { + return true; + } + }; + + // Lambda to load an HDA from file + auto LoadAssetFromFile = [&Result, &OutAssetLibraryId](const FString& InAssetFileName) + { + // Load the asset from file. + std::string AssetFileNamePlain; + FHoudiniEngineUtils::ConvertUnrealString(InAssetFileName, AssetFileNamePlain); + Result = FHoudiniApi::LoadAssetLibraryFromFile( + FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); + + }; + + // Lambda to load an HDA from memory + auto LoadAssetFromMemory = [&Result, &OutAssetLibraryId](const UHoudiniAsset* InHoudiniAsset) + { + // Load the asset from the cached memory buffer + Result = FHoudiniApi::LoadAssetLibraryFromMemory( + FHoudiniEngine::Get().GetSession(), + reinterpret_cast(InHoudiniAsset->GetAssetBytes()), + InHoudiniAsset->GetAssetBytesCount(), + true, + &OutAssetLibraryId); + }; + + if (!bMemoryCopyFirst) + { + // Load from File first + if (bCanLoadFromFile) + { + LoadAssetFromFile(AssetFileName); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + + // If we failed to load from file ... + if (Result != HAPI_RESULT_SUCCESS) + { + // ... warn the user that we will be loading from memory. + HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from Memory: source asset file not found."), *AssetFileName); + + // Attempt to load from memory + if (bCanLoadFromMemory) + { + LoadAssetFromMemory(HoudiniAsset); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); + return false; + } + } + } + else + { + // Load from Memory first + if(bCanLoadFromMemory) + { + LoadAssetFromMemory(HoudiniAsset); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + + // If we failed to load from memory ... + if (Result != HAPI_RESULT_SUCCESS) + { + // ... warn the user that we will be loading from file + HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from File: no memory copy available."), *AssetFileName); + + // Attempt to load from file + if (bCanLoadFromFile) + { + LoadAssetFromFile(AssetFileName); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); + return false; + } + } + } + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_MESSAGE(TEXT("Error loading asset library for %s: %s"), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + return true; +} + +bool +FHoudiniEngineUtils::GetSubAssetNames( + const HAPI_AssetLibraryId& AssetLibraryId, + TArray< HAPI_StringHandle >& OutAssetNames) +{ + if (AssetLibraryId < 0) + return false; + + int32 AssetCount = 0; + HAPI_Result Result = HAPI_RESULT_FAILURE; + Result = FHoudiniApi::GetAvailableAssetCount(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetCount); + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Error getting asset count: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + if (AssetCount <= 0) + { + HOUDINI_LOG_ERROR(TEXT("Could not find an asset.")); + return false; + } + + OutAssetNames.SetNumUninitialized(AssetCount); + Result = FHoudiniApi::GetAvailableAssets(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &OutAssetNames[0], AssetCount); + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Unable to retrieve sub asset names: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + if (!AssetCount) + { + HOUDINI_LOG_ERROR(TEXT("No assets found")); + return false; + } + + return true; +} + + +bool +FHoudiniEngineUtils::OpenSubassetSelectionWindow(TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ) +{ + OutPickedAssetName = -1; + + if (AssetNames.Num() <= 0) + return false; + + // Default to the first asset + OutPickedAssetName = AssetNames[0]; + +#if WITH_EDITOR + // Present the user with a dialog for choosing which asset to instantiate. + TSharedPtr ParentWindow; + if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + // Check if the main frame is loaded. When using the old main frame it may not be. + IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (!ParentWindow.IsValid()) + { + return false; + } + + TSharedPtr AssetSelectionWidget; + TSharedRef 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()) + { + return false; + } + + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + + int32 DialogPickedAssetName = AssetSelectionWidget->GetSelectedAssetName(); + if (DialogPickedAssetName != -1) + { + OutPickedAssetName = DialogPickedAssetName; + return true; + } + else + { + return false; + } +#endif + + return true; +} + +/* +bool +FHoudiniEngineUtils::IsValidNodeId(HAPI_NodeId NodeId) +{ + return NodeId != -1; +} +*/ + +bool +FHoudiniEngineUtils::GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString) +{ + HAPI_AssetInfo AssetInfo; + if (FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo) == HAPI_RESULT_SUCCESS) + { + FHoudiniEngineString HoudiniEngineString(AssetInfo.nameSH); + return HoudiniEngineString.ToFString(NameString); + } + + return false; +} + +bool +FHoudiniEngineUtils::GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer) +{ + PresetBuffer.Empty(); + + HAPI_NodeId NodeId; + HAPI_AssetInfo AssetInfo; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo)) + { + NodeId = AssetInfo.nodeId; + } + else + NodeId = AssetNodeId; + + int32 BufferLength = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPresetBufLength( + FHoudiniEngine::Get().GetSession(), NodeId, + HAPI_PRESETTYPE_BINARY, NULL, &BufferLength), false); + + PresetBuffer.SetNumZeroed(BufferLength); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPreset( + FHoudiniEngine::Get().GetSession(), NodeId, + &PresetBuffer[0], PresetBuffer.Num()), false); + + return true; +} + + +bool +FHoudiniEngineUtils::HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath) +{ + // Retrieve Path to the given Node, relative to the other given Node + if ((InNodeId < 0) || (InRelativeToNodeId < 0)) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InNodeId)) + return false; + + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodePath( + FHoudiniEngine::Get().GetSession(), + InNodeId, InRelativeToNodeId, &StringHandle)) + { + if(FHoudiniEngineString::ToFString(StringHandle, OutPath)) + { + return true; + } + } + return false; +} + +bool +FHoudiniEngineUtils::HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath) +{ + // Do the HAPI query only on first-use + if (!InHGPO.NodePath.IsEmpty()) + return true; + + FString NodePathTemp; + if (InHGPO.AssetId == InHGPO.GeoId) + { + // This is a SOP asset, just return the asset name in this case + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), InHGPO.AssetId, &AssetInfo)) + { + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo)) + { + if (FHoudiniEngineString::ToFString(AssetNodeInfo.nameSH, NodePathTemp)) + { + OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); + } + } + } + } + else + { + // This is an OBJ asset, return the path to this geo relative to the asset + if (FHoudiniEngineUtils::HapiGetNodePath(InHGPO.GeoId, InHGPO.AssetId, NodePathTemp)) + { + OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); + } + } + + /*if (OutPath.IsEmpty()) + { + OutPath = TEXT("Empty"); + } + + return NodePath; + */ + + return !OutPath.IsEmpty(); +} + + +bool +FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + InNodeId, &NodeInfo), false); + + int32 ObjectCount = 0; + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + ObjectCount = 1; + OutObjectInfos.SetNumUninitialized(1); + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), + NodeInfo.parentId, &OutObjectInfos[0]), false); + + // Use the identity transform + OutObjectTransforms.SetNumUninitialized(1); + FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); + + OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; + OutObjectTransforms[0].scale[0] = 1.0f; + OutObjectTransforms[0].scale[1] = 1.0f; + OutObjectTransforms[0].scale[2] = 1.0f; + OutObjectTransforms[0].rstOrder = HAPI_SRT; + } + else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); + + if (ObjectCount <= 0) + { + // This asset is an OBJ that has no object as children, use the object itself + ObjectCount = 1; + OutObjectInfos.SetNumUninitialized(1); + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[0]), false); + + // Use the identity transform + OutObjectTransforms.SetNumUninitialized(1); + FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); + + OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; + OutObjectTransforms[0].scale[0] = 1.0f; + OutObjectTransforms[0].scale[1] = 1.0f; + OutObjectTransforms[0].scale[2] = 1.0f; + OutObjectTransforms[0].rstOrder = HAPI_SRT; + } + else + { + // This OBJ has children + // See if we should add ourself by looking for immediate display SOP + int32 ImmediateSOP = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), NodeInfo.id, + HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_DISPLAY, + false, &ImmediateSOP), false); + + bool bAddSelf = ImmediateSOP > 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); + + // Increment the object count by one if we should add ourself + OutObjectInfos.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); + OutObjectTransforms.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); + for (int32 Idx = 0; Idx < OutObjectInfos.Num(); Idx++) + { + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[Idx])); + FHoudiniApi::Transform_Init(&(OutObjectTransforms[Idx])); + } + + // Get our object info in 0 if needed + if (bAddSelf) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[0]), false); + + // Use the identity transform + OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; + OutObjectTransforms[0].scale[0] = 1.0f; + OutObjectTransforms[0].scale[1] = 1.0f; + OutObjectTransforms[0].scale[2] = 1.0f; + OutObjectTransforms[0].rstOrder = HAPI_SRT; + } + + // Get the other object infos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[bAddSelf ? 1 : 0], 0, ObjectCount), false); + + // Get the composed object transforms for the others (1 - Count) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_SRT, &OutObjectTransforms[bAddSelf ? 1 : 0], 0, ObjectCount), false); + } + } + else + return false; + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &NodeInfo), false); + + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + NodeInfo.parentId, -1, HAPI_SRT, &HapiTransform), false); + } + else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InNodeId, -1, HAPI_SRT, &HapiTransform), false); + } + else + return false; + + // Convert HAPI transform to Unreal one. + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, OutTransform); + + return true; +} + +void +FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform) +{ + if ( HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM ) + { + // Swap Y/Z, invert W + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[2], + HapiTransform.rotationQuaternion[1], -HapiTransform.rotationQuaternion[3]); + + // Swap Y/Z and scale + FVector ObjectTranslation(HapiTransform.position[0], HapiTransform.position[2], HapiTransform.position[1]); + ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[2], HapiTransform.scale[1]); + + UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); + } + else + { + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[1], + HapiTransform.rotationQuaternion[2], HapiTransform.rotationQuaternion[3]); + + FVector ObjectTranslation( + HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2]); + ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[1], HapiTransform.scale[2]); + + UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); + } +} + +void +FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform) +{ + float HapiMatrix[16]; + FHoudiniApi::ConvertTransformEulerToMatrix(FHoudiniEngine::Get().GetSession(), &HapiTransformEuler, HapiMatrix); + + HAPI_Transform HapiTransformQuat; + FMemory::Memzero< HAPI_Transform >(HapiTransformQuat); + FHoudiniApi::ConvertMatrixToQuat(FHoudiniEngine::Get().GetSession(), HapiMatrix, HAPI_SRT, &HapiTransformQuat); + + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransformQuat, UnrealTransform); +} + +void +FHoudiniEngineUtils::TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform) +{ + FMemory::Memzero< HAPI_Transform >(HapiTransform); + HapiTransform.rstOrder = HAPI_SRT; + + FQuat UnrealRotation = UnrealTransform.GetRotation(); + FVector UnrealTranslation = UnrealTransform.GetTranslation(); + FVector UnrealScale = UnrealTransform.GetScale3D(); + + if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) + { + // Swap Y/Z, invert XYZ + HapiTransform.rotationQuaternion[0] = -UnrealRotation.X; + HapiTransform.rotationQuaternion[1] = -UnrealRotation.Z; + HapiTransform.rotationQuaternion[2] = -UnrealRotation.Y; + HapiTransform.rotationQuaternion[3] = UnrealRotation.W; + + // Swap Y/Z, scale + HapiTransform.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransform.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransform.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + HapiTransform.scale[0] = UnrealScale.X; + HapiTransform.scale[1] = UnrealScale.Z; + HapiTransform.scale[2] = UnrealScale.Y; + } + else + { + HapiTransform.rotationQuaternion[0] = UnrealRotation.X; + HapiTransform.rotationQuaternion[1] = UnrealRotation.Y; + HapiTransform.rotationQuaternion[2] = UnrealRotation.Z; + HapiTransform.rotationQuaternion[3] = UnrealRotation.W; + + HapiTransform.position[0] = UnrealTranslation.X; + HapiTransform.position[1] = UnrealTranslation.Y; + HapiTransform.position[2] = UnrealTranslation.Z; + + HapiTransform.scale[0] = UnrealScale.X; + HapiTransform.scale[1] = UnrealScale.Y; + HapiTransform.scale[2] = UnrealScale.Z; + } +} + +void +FHoudiniEngineUtils::TranslateUnrealTransform( + const FTransform & UnrealTransform, + HAPI_TransformEuler & HapiTransformEuler) +{ + FMemory::Memzero< HAPI_TransformEuler >(HapiTransformEuler); + + HapiTransformEuler.rstOrder = HAPI_SRT; + HapiTransformEuler.rotationOrder = HAPI_XYZ; + + FQuat UnrealRotation = UnrealTransform.GetRotation(); + FVector UnrealTranslation = UnrealTransform.GetTranslation(); + FVector UnrealScale = UnrealTransform.GetScale3D(); + + if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) + { + // switch the quaternion to Y-up, LHR by Swapping Y/Z and negating W + Swap(UnrealRotation.Y, UnrealRotation.Z); + UnrealRotation.W = -UnrealRotation.W; + const FRotator Rotator = UnrealRotation.Rotator(); + + // Negate roll and pitch since they are actually RHR + HapiTransformEuler.rotationEuler[0] = -Rotator.Roll; + HapiTransformEuler.rotationEuler[1] = -Rotator.Pitch; + HapiTransformEuler.rotationEuler[2] = Rotator.Yaw; + + // Swap Y/Z, scale + HapiTransformEuler.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransformEuler.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransformEuler.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + HapiTransformEuler.scale[0] = UnrealScale.X; + HapiTransformEuler.scale[1] = UnrealScale.Z; + HapiTransformEuler.scale[2] = UnrealScale.Y; + } + else + { + const FRotator Rotator = UnrealRotation.Rotator(); + HapiTransformEuler.rotationEuler[0] = Rotator.Roll; + HapiTransformEuler.rotationEuler[1] = Rotator.Yaw; + HapiTransformEuler.rotationEuler[2] = Rotator.Pitch; + + HapiTransformEuler.position[0] = UnrealTranslation.X; + HapiTransformEuler.position[1] = UnrealTranslation.Y; + HapiTransformEuler.position[2] = UnrealTranslation.Z; + + HapiTransformEuler.scale[0] = UnrealScale.X; + HapiTransformEuler.scale[1] = UnrealScale.Y; + HapiTransformEuler.scale[2] = UnrealScale.Z; + } +} + +bool +FHoudiniEngineUtils::UploadHACTransform(UHoudiniAssetComponent* HAC) +{ + if (!HAC || !HAC->bUploadTransformsToHoudiniEngine) + return false; + + // Indicates the HAC has been fully loaded + // TODO: Check! (replaces fullyloaded) + if (!HAC->IsFullyLoaded()) + return false; + + if (HAC->GetAssetCookCount() > 0 && HAC->GetAssetId() >= 0) + { + if (!FHoudiniEngineUtils::HapiSetAssetTransform(HAC->GetAssetId(), HAC->GetComponentTransform())) + return false; + } + + HAC->SetHasComponentTransformChanged(false); + + return true; +} + +bool +FHoudiniEngineUtils::HapiSetAssetTransform(const HAPI_NodeId& AssetId, const FTransform & Transform) +{ + if (AssetId < 0) + return false; + + // Translate Unreal transform to HAPI Euler one. + HAPI_TransformEuler TransformEuler; + FMemory::Memzero< HAPI_TransformEuler >(TransformEuler); + FHoudiniEngineUtils::TranslateUnrealTransform(Transform, TransformEuler); + + // Get the NodeInfo + HAPI_NodeInfo LocalAssetNodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, + &LocalAssetNodeInfo), false); + + if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + LocalAssetNodeInfo.parentId, + &TransformEuler), false); + } + else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + AssetId, &TransformEuler), false); + } + else + return false; + + return true; +} + +HAPI_NodeId +FHoudiniEngineUtils::HapiGetParentNodeId(const HAPI_NodeId& NodeId) +{ + HAPI_NodeId ParentId = -1; + if (NodeId >= 0) + { + HAPI_NodeInfo NodeInfo; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) + ParentId = NodeInfo.parentId; + } + + return ParentId; +} + + +// Assign a unique Actor Label if needed +void +FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + // TODO: Necessary?? + +#if WITH_EDITOR + HAPI_NodeId AssetId = HAC->GetAssetId(); + if (AssetId < 0) + return; + + AActor* OwnerActor = HAC->GetOwner(); + if (!OwnerActor) + return; + + if (!OwnerActor->GetName().StartsWith(AHoudiniAssetActor::StaticClass()->GetName())) + return; + + // Assign unique actor label based on asset name if it seems to have not been renamed already + FString UniqueName; + if (FHoudiniEngineUtils::GetHoudiniAssetName(AssetId, UniqueName)) + FActorLabelUtilities::SetActorLabelUnique(OwnerActor, UniqueName); +#endif +} + +bool +FHoudiniEngineUtils::GetLicenseType(FString & LicenseType) +{ + LicenseType = TEXT(""); + HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetSessionEnvInt( + FHoudiniEngine::Get().GetSession(), HAPI_SESSIONENVINT_LICENSE, + (int32 *)&LicenseTypeValue), false); + + switch (LicenseTypeValue) + { + case HAPI_LICENSE_NONE: + { + LicenseType = TEXT("No License Acquired"); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE: + { + LicenseType = TEXT("Houdini Engine"); + break; + } + + case HAPI_LICENSE_HOUDINI: + { + LicenseType = TEXT("Houdini"); + break; + } + + case HAPI_LICENSE_HOUDINI_FX: + { + LicenseType = TEXT("Houdini FX"); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE_INDIE: + { + LicenseType = TEXT("Houdini Engine Indie"); + break; + } + + case HAPI_LICENSE_HOUDINI_INDIE: + { + LicenseType = TEXT("Houdini Indie"); + break; + } + + case HAPI_LICENSE_MAX: + default: + { + return false; + } + } + + return true; +} + +// Check if the Houdini asset component (or parent HAC of a parameter) is being cooked +bool +FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(UObject* InObj) +{ + if (!InObj) + return false; + + UHoudiniAssetComponent* HoudiniAssetComponent = nullptr; + + if (InObj->IsA()) + { + HoudiniAssetComponent = Cast(InObj); + } + else if (InObj->IsA()) + { + UHoudiniParameter* Parameter = Cast(InObj); + if (!Parameter) + return false; + + HoudiniAssetComponent = Cast(Parameter->GetOuter()); + } + + if (!HoudiniAssetComponent) + return false; + + EHoudiniAssetState AssetState = HoudiniAssetComponent->GetAssetState(); + + return AssetState >= EHoudiniAssetState::PreCook && AssetState <= EHoudiniAssetState::PostCook; +} + +void +FHoudiniEngineUtils::UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate) +{ + TArray ObjectsToUpdate; + ObjectsToUpdate.Add(InObjectToUpdate); + + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() + { + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + } +} + +void +FHoudiniEngineUtils::UpdateEditorProperties(TArray ObjectsToUpdate, const bool& InForceFullUpdate) +{ + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() + { + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + } +} + +void FHoudiniEngineUtils::UpdateBlueprintEditor(UHoudiniAssetComponent* HAC) +{ + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [HAC]() + { + FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); + } +} + +void +FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate) +{ + // TODO: Don't use this method. Prefer using IDetailLayoutBuilder::ForceRefreshDetails(). + // Example to correctly update details panel through IDetailCategoryBuilder / IDetailLayoutBuilder + // IDetailCategoryBuilder &CategoryBuilder = StructBuilder.GetParentCategory(); + // IDetailLayoutBuilder &LayoutBuilder = CategoryBuilder.GetParentLayout(); + // LayoutBuilder.ForceRefreshDetails(); + +#if WITH_EDITOR + if (!bInForceFullUpdate) + { + // bNeedFullUpdate is false only when small changes (parameters value) have been made + // We do not reselect the actor to avoid loosing the currently selected parameter + if(GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); + + return; + } + + // We now want to get all the components/actors owning the objects to update + TArray AllSceneComponents; + for (auto CurrentObject : ObjectsToUpdate) + { + if (!CurrentObject || CurrentObject->IsPendingKill()) + continue; + + // In some case, the object itself is the component + USceneComponent* SceneComp = Cast(CurrentObject); + if (!SceneComp) + { + SceneComp = Cast(CurrentObject->GetOuter()); + } + + if (SceneComp && !SceneComp->IsPendingKill()) + { + AllSceneComponents.Add(SceneComp); + continue; + } + } + + TArray AllActors; + for (auto CurrentSceneComp : AllSceneComponents) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill()) + continue; + + AActor* Actor = CurrentSceneComp->GetOwner(); + if (Actor && !Actor->IsPendingKill()) + AllActors.Add(Actor); + } + + // Updating the editor properties can be done in two ways, depending if we're in the BP editor or not + // If we have a parent actor, we're not in the BP Editor, so update via the property editor module + if (AllActors.Num() > 0) + { + // Get the property editor module + FPropertyEditorModule& PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // This will actually force a refresh of all the details view + //PropertyModule.NotifyCustomizationModuleChanged(); + + TArray SelectedActors; + for (auto Actor : AllActors) + { + if (Actor && Actor->IsSelected()) + SelectedActors.Add(Actor); + } + + if (SelectedActors.Num() > 0) + { + PropertyModule.UpdatePropertyViews(SelectedActors); + } + + // 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. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + { + // We have no details panel, nothing to update. + continue; + } + + // Get the selected actors for this details panels and check if one of ours belongs to it + const TArray>& SelectedDetailActors = DetailsView->GetSelectedActors(); + bool bFoundActor = false; + for (int32 ActorIdx = 0; ActorIdx < SelectedDetailActors.Num(); ActorIdx++) + { + TWeakObjectPtr SelectedActor = SelectedDetailActors[ActorIdx]; + if (SelectedActor.IsValid() && AllActors.Contains(SelectedActor.Get())) + { + bFoundActor = true; + break; + } + } + + // None of our actors belongs to this detail panel, no need to update it + if (!bFoundActor) + continue; + + // Refresh that details panels using its current selection + TArray Selection; + for (auto DetailsActor : SelectedDetailActors) + { + if (DetailsActor.IsValid()) + Selection.Add(DetailsActor.Get()); + } + + // Reset selected actors, force refresh and override the lock. + DetailsView->SetObjects(SelectedActors, bInForceFullUpdate, true); + + if (GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); + } + } + else + { + // TODO: Do we need to do Blueprint Editor updates here or can we confine it to "post output processing"? + + } + + /* + // Reset the full update flag + if (bNeedFullUpdate) + HAC->SetEditorPropertiesNeedFullUpdate(false); + */ + + return; +#endif +} + +void FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC) +{ + //UHoudiniAssetComponent* HACTemplate = HAC->GetCachedTemplate(); + //UBlueprintGeneratedClass* OwnerBPClass = Cast(HACTemplate->GetOuter()); + //if (!OwnerBPClass) + // return; + + ///* + //FBlueprintEditor* BlueprintEditor = static_cast(FAssetEditorManager::Get().FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); + //if (!BlueprintEditor) + // return; + //*/ + + //// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built. + //UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + //FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); + //if (!BlueprintEditor) + // return; + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(HAC); + if (!BlueprintEditor) + return; + + TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); + if (SCSEditor.IsValid()) + { + SCSEditor->UpdateTree(true); + SCSEditor->DumpTree(); + } + BlueprintEditor->RefreshMyBlueprint(); + + //BlueprintEditor->RefreshMyBlueprint(); + //BlueprintEditor->RefreshInspector(); + //BlueprintEditor->RefreshEditors(); + + // Also somehow reselect ? +} + +HAPI_Result +FHoudiniEngineUtils::SetAttributeStringData( + const FString& InString, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo) +{ + TArray StringArray; + StringArray.Add(InString); + + return SetAttributeStringData(StringArray, InNodeId, InPartId, InAttributeName, InAttributeInfo); +} + +HAPI_Result +FHoudiniEngineUtils::SetAttributeStringData( + const TArray& InStringArray, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo ) +{ + TArray StringDataArray; + for (auto CurrentString : InStringArray) + { + // Append the converted string to the string array + StringDataArray.Add(FHoudiniEngineUtils::ExtractRawString(CurrentString)); + } + + // Set the attribute's string data + HAPI_Result result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + TCHAR_TO_ANSI(*InAttributeName), &InAttributeInfo, + StringDataArray.GetData(), 0, InAttributeInfo.count); + + // ExtractRawString allocates memory using malloc, free it! + FreeRawStringMemory(StringDataArray); + + return result; +} + +char * +FHoudiniEngineUtils::ExtractRawString(const FString& InString) +{ + if (InString.IsEmpty()) + return nullptr; + + std::string ConvertedString = TCHAR_TO_UTF8(*InString); + + // Allocate space for unique string. + int32 UniqueStringBytes = ConvertedString.size() + 1; + char * UniqueString = static_cast(FMemory::Malloc(UniqueStringBytes)); + + FMemory::Memzero(UniqueString, UniqueStringBytes); + FMemory::Memcpy(UniqueString, ConvertedString.c_str(), ConvertedString.size()); + + return UniqueString; +} + +void +FHoudiniEngineUtils::FreeRawStringMemory(const char*& InRawString) +{ + if (InRawString == nullptr) + return; + + // Do not attempt to free empty strings! + if (!InRawString[0]) + return; + + FMemory::Free((void*)InRawString); + InRawString = nullptr; +} + +void +FHoudiniEngineUtils::FreeRawStringMemory(TArray& InRawStringArray) +{ + // ExtractRawString allocates memory using malloc, free it! + for (auto CurrentStrPtr : InRawStringArray) + { + FreeRawStringMemory(CurrentStrPtr); + } + InRawStringArray.Empty(); +} + +bool +FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // No need to add another component if we already show the logo + if (FHoudiniEngineUtils::HasHoudiniLogo(HAC)) + return true; + + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + UStaticMeshComponent * HoudiniLogoSMC = NewObject< UStaticMeshComponent >( + HAC, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!HoudiniLogoSMC) + return false; + + HoudiniLogoSMC->SetStaticMesh(HoudiniLogoSM); + HoudiniLogoSMC->SetVisibility(true); + HoudiniLogoSMC->SetHiddenInGame(true); + // Attach created static mesh component to our Houdini component. + HoudiniLogoSMC->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniLogoSMC->RegisterComponent(); + + return true; +} + +bool +FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the Houdini Logo SM + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + // Iterate on the HAC's component + for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + continue; + + // Get the static mesh component + UStaticMeshComponent* SMC = Cast(CurrentSceneComp); + if (!SMC || SMC->IsPendingKill()) + continue; + + // Check if the SMC is the Houdini Logo + if (SMC->GetStaticMesh() != HoudiniLogoSM) + continue; + + SMC->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SMC->UnregisterComponent(); + SMC->DestroyComponent(); + + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the Houdini Logo SM + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + // Iterate on the HAC's component + for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + continue; + + // Get the static mesh component + UStaticMeshComponent* SMC = Cast(CurrentSceneComp); + if (!SMC || SMC->IsPendingKill()) + continue; + + // Check if the SMC is the Houdini Logo + if (SMC->GetStaticMesh() == HoudiniLogoSM) + return true; + } + + return false; +} + +int32 +FHoudiniEngineUtils::HapiGetVertexListForGroup( + const HAPI_NodeId& GeoId, + const HAPI_PartInfo& PartInfo, + const FString& GroupName, + const TArray& FullVertexList, + TArray& NewVertexList, + TArray& AllVertexList, + TArray& AllFaceList, + TArray& AllGroupFaceIndices, + int32& FirstValidVertex, + int32& FirstValidPrim, + const bool& isPackedPrim) +{ + int32 ProcessedWedges = 0; + AllFaceList.Empty(); + FirstValidPrim = 0; + FirstValidVertex = 0; + NewVertexList.Init(-1, FullVertexList.Num()); + + // Get the faces membership for this group + bool bAllEquals = false; + TArray PartGroupMembership; + if (!FHoudiniEngineUtils::HapiGetGroupMembership( + GeoId, PartInfo, HAPI_GROUPTYPE_PRIM, GroupName, PartGroupMembership, bAllEquals)) + return false; + + // Go through all primitives. + for (int32 FaceIdx = 0; FaceIdx < PartGroupMembership.Num(); ++FaceIdx) + { + if (PartGroupMembership[FaceIdx] <= 0) + { + // The face is not in the group, skip + continue; + } + + // Add the face's index. + AllFaceList.Add(FaceIdx); + + // Get the index of this face's vertices + int32 FirstVertexIdx = FaceIdx * 3; + int32 SecondVertexIdx = FirstVertexIdx + 1; + int32 LastVertexIdx = FirstVertexIdx + 2; + + // This face is a member of specified group. + // Add all 3 vertices + if (FullVertexList.IsValidIndex(LastVertexIdx)) + { + NewVertexList[FirstVertexIdx] = FullVertexList[FirstVertexIdx]; + NewVertexList[SecondVertexIdx] = FullVertexList[SecondVertexIdx]; + NewVertexList[LastVertexIdx] = FullVertexList[LastVertexIdx]; + } + + // Mark these vertex indices as used. + if (AllVertexList.IsValidIndex(LastVertexIdx)) + { + AllVertexList[FirstVertexIdx] = 1; + AllVertexList[SecondVertexIdx] = 1; + AllVertexList[LastVertexIdx] = 1; + } + + // Mark this face as used. + if (AllGroupFaceIndices.IsValidIndex(FaceIdx)) + AllGroupFaceIndices[FaceIdx] = 1; + + if (ProcessedWedges == 0) + { + // Keep track of the first valid vertex/face indices for this group + // This will be useful later on when extracting attributes + FirstValidVertex = FirstVertexIdx; + FirstValidPrim = FaceIdx; + } + + ProcessedWedges += 3; + } + + return ProcessedWedges; +} + +bool +FHoudiniEngineUtils::HapiGetGroupNames( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const HAPI_GroupType& GroupType, const bool& isPackedPrim, + TArray& OutGroupNames) +{ + int32 GroupCount = 0; + if (!isPackedPrim) + { + // Get group count on the geo + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo), false); + + if (GroupType == HAPI_GROUPTYPE_POINT) + GroupCount = GeoInfo.pointGroupCount; + else if (GroupType == HAPI_GROUPTYPE_PRIM) + GroupCount = GeoInfo.primitiveGroupCount; + } + else + { + // We need the group count for this packed prim + int32 PointGroupCount = 0, PrimGroupCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupCountOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PointGroupCount, &PrimGroupCount), false); + + if (GroupType == HAPI_GROUPTYPE_POINT) + GroupCount = PointGroupCount; + else if (GroupType == HAPI_GROUPTYPE_PRIM) + GroupCount = PrimGroupCount; + } + + if (GroupCount <= 0) + return true; + + TArray GroupNameStringHandles; + GroupNameStringHandles.SetNumZeroed(GroupCount); + if (!isPackedPrim) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNames( + FHoudiniEngine::Get().GetSession(), + GeoId, GroupType, &GroupNameStringHandles[0], GroupCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNamesOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, GroupType, &GroupNameStringHandles[0], GroupCount), false); + } + + /* + OutGroupNames.SetNum(GroupCount); + for (int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx) + { + FString CurrentGroupName = TEXT(""); + FHoudiniEngineString::ToFString(GroupNameStringHandles[NameIdx], CurrentGroupName); + OutGroupNames[NameIdx] = CurrentGroupName; + } + */ + + FHoudiniEngineString::SHArrayToFStringArray(GroupNameStringHandles, OutGroupNames); + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetGroupMembership( + const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, + const HAPI_GroupType& GroupType, const FString & GroupName, + TArray& OutGroupMembership, bool& OutAllEquals) +{ + int32 ElementCount = (GroupType == HAPI_GROUPTYPE_POINT) ? PartInfo.pointCount : PartInfo.faceCount; + if (ElementCount < 1) + return false; + OutGroupMembership.SetNum(ElementCount); + + OutAllEquals = false; + std::string ConvertedGroupName = TCHAR_TO_UTF8(*GroupName); + if (!PartInfo.isInstanced) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembership( + FHoudiniEngine::Get().GetSession(), + GeoId, PartInfo.id, GroupType,ConvertedGroupName.c_str(), + &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembershipOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), GeoId, PartInfo.id, GroupType, + ConvertedGroupName.c_str(), &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); + } + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize, + HAPI_AttributeOwner InOwner) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniEngineUtils::HapiGetAttributeDataAsFloat")); + + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Allocate sufficient buffer for data. + OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); + + return true; + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Expected Float, found an int, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray IntData; + IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(IntData.Num()); + for (int32 Idx = 0; Idx < IntData.Num(); Idx++) + { + OutData[Idx] = (float)IntData[Idx]; + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from integer."), *FString(InAttribName)); + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + // Expected Float, found a string, try to convert the attribute + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) + { + bool bConversionError = false; + OutData.SetNum(StringData.Num()); + for (int32 Idx = 0; Idx < StringData.Num(); Idx++) + { + if (StringData[Idx].IsNumeric()) + OutData[Idx] = FCString::Atof(*StringData[Idx]); + else + bConversionError = true; + } + + if (!bConversionError) + { + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from string."), *FString(InAttribName)); + return true; + } + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a float attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + const int32& InTupleSize, + const HAPI_AttributeOwner& InOwner) +{ + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Allocate sufficient buffer for data. + OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); + + return true; + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Expected Int, found a float, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray FloatData; + FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the float values + if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(FloatData.Num()); + for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) + { + OutData[Idx] = (int32)FloatData[Idx]; + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from float."), *FString(InAttribName)); + + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + // Expected Int, found a string, try to convert the attribute + TArray StringData; + if(FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) + { + bool bConversionError = false; + OutData.SetNum(StringData.Num()); + for (int32 Idx = 0; Idx < StringData.Num(); Idx++) + { + if (StringData[Idx].IsNumeric()) + OutData[Idx] = FCString::Atoi(*StringData[Idx]); + else + bConversionError = true; + } + + if (!bConversionError) + { + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from string."), *FString(InAttribName)); + return true; + } + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be an integer attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsString( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize, + HAPI_AttributeOwner InOwner) +{ + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + return FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, OutData); + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Expected string, found a float, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray FloatData; + FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the float values + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(FloatData.Num()); + for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) + { + OutData[Idx] = FString::SanitizeFloat(FloatData[Idx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from float."), *FString(InAttribName)); + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Expected String, found an int, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray IntData; + IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); + + // Fetch the values + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) + { + OutData.SetNum(IntData.Num()); + for (int32 Idx = 0; Idx < IntData.Num(); Idx++) + { + OutData[Idx] = FString::FromInt(IntData[Idx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from integer."), *FString(InAttribName)); + return true; + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a string attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& InAttributeInfo, + TArray& OutData) +{ + if (!InAttributeInfo.exists) + return false; + + // Extract the StringHandles + TArray StringHandles; + StringHandles.Init(-1, InAttributeInfo.count * InAttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, &InAttributeInfo, + &StringHandles[0], 0, InAttributeInfo.count), false); + + // Set the output data size + OutData.SetNum(StringHandles.Num()); + + // Convert the StringHandles to FString. + // using a map to minimize the number of HAPI calls + FHoudiniEngineString::SHArrayToFStringArray(StringHandles, OutData); + + return true; +} + + +bool +FHoudiniEngineUtils::HapiCheckAttributeExists( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const char * AttribName, HAPI_AttributeOwner Owner) +{ + if (Owner == HAPI_ATTROWNER_INVALID) + { + for (int32 OwnerIdx = 0; OwnerIdx < HAPI_ATTROWNER_MAX; OwnerIdx++) + { + if (HapiCheckAttributeExists(GeoId, PartId, AttribName, (HAPI_AttributeOwner)OwnerIdx)) + { + return true; + } + } + } + else + { + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, AttribName, Owner, &AttribInfo), false); + + return AttribInfo.exists; + } + + return false; +} + +bool +FHoudiniEngineUtils::IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType) +{ + // Check for + // - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE (unreal_instance) on points/detail + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT)) + { + OutInstancerType = EHoudiniInstancerType::AttributeInstancer; + return true; + } + + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL)) + { + OutInstancerType = EHoudiniInstancerType::AttributeInstancer; + return true; + } + + // - HAPI_UNREAL_ATTRIB_INSTANCE (instance) on points + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT)) + { + OutInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsString( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const FString& DefaultValue, + FString& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParamInfo), false); + + // .. and value + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), NodeId, false, + &StringHandle, FoundParamInfo.stringValuesIndex, 1), false); + + // Convert the string handle to FString + return FHoudiniEngineString::ToFString(StringHandle, OutValue); +} + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const int32& DefaultValue, + int32& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParmInfo; + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo), false); + + // .. and value + int32 Value = DefaultValue; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Value, + FoundParmInfo.intValuesIndex, 1), false); + + OutValue = Value; + + return true; +} + + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsFloat( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const float& DefaultValue, + float& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParmInfo; + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo), false); + + // .. and value + float Value = DefaultValue; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Value, + FoundParmInfo.floatValuesIndex, 1), false); + + OutValue = Value; + + return true; +} + +HAPI_ParmId +FHoudiniEngineUtils::HapiFindParameterByName(const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo) +{ + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + InNodeId, InParmName.c_str(), &ParmId), -1); + + if (ParmId < 0) + return -1; + + FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmId, &OutFoundParmInfo), -1); + + return ParmId; +} + +HAPI_ParmId +FHoudiniEngineUtils::HapiFindParameterByTag(const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo) +{ + // Try to find the parameter by its tag + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmWithTag( + FHoudiniEngine::Get().GetSession(), + InNodeId, InParmTag.c_str(), &ParmId), -1); + + if (ParmId < 0) + return -1; + + FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmId, &OutFoundParmInfo), -1); + + return ParmId; +} + +int32 +FHoudiniEngineUtils::HapiGetAttributeOfType( + const HAPI_NodeId& GeoId, + const HAPI_NodeId& PartId, + const HAPI_AttributeOwner& AttributeOwner, + const HAPI_AttributeTypeInfo& AttributeType, + TArray& MatchingAttributesInfo, + TArray& MatchingAttributesName) +{ + int32 NumberOfAttributeFound = 0; + + // Get the part infos + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, &PartInfo), NumberOfAttributeFound); + + // Get All attribute names for that part + int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; + + TArray AttribNameSHArray; + AttribNameSHArray.SetNum(nAttribCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, AttributeOwner, + AttribNameSHArray.GetData(), nAttribCount), NumberOfAttributeFound); + + TArray AttribNameArray; + FHoudiniEngineString::SHArrayToFStringArray(AttribNameSHArray, AttribNameArray); + + // Iterate on all the attributes, and get their part infos to get their type + for (int32 Idx = 0; Idx < AttribNameArray.Num(); Idx++) + { + FString HapiString = AttribNameArray[Idx]; + + // ... then the attribute info + HAPI_AttributeInfo AttrInfo; + FHoudiniApi::AttributeInfo_Init(&AttrInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, TCHAR_TO_UTF8(*HapiString), + AttributeOwner, &AttrInfo)) + continue; + + if (!AttrInfo.exists) + continue; + + // ... check the type + if (AttrInfo.typeInfo != AttributeType) + continue; + + MatchingAttributesInfo.Add(AttrInfo); + MatchingAttributesName.Add(HapiString); + + NumberOfAttributeFound++; + } + + return NumberOfAttributeFound; +} + +HAPI_PartInfo +FHoudiniEngineUtils::ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo) +{ + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + + PartInfo.id = InHPartInfo.PartId; + //PartInfo.nameSH = InHPartInfo.Name; + + switch (InHPartInfo.Type) + { + case EHoudiniPartType::Mesh: + PartInfo.type = HAPI_PARTTYPE_MESH; + break; + case EHoudiniPartType::Curve: + PartInfo.type = HAPI_PARTTYPE_CURVE; + break; + case EHoudiniPartType::Instancer: + PartInfo.type = HAPI_PARTTYPE_INSTANCER; + break; + case EHoudiniPartType::Volume: + PartInfo.type = HAPI_PARTTYPE_VOLUME; + break; + default: + case EHoudiniPartType::Invalid: + PartInfo.type = HAPI_PARTTYPE_INVALID; + break; + } + + PartInfo.faceCount = InHPartInfo.FaceCount; + PartInfo.vertexCount = InHPartInfo.VertexCount; + PartInfo.pointCount = InHPartInfo.PointCount; + + PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = InHPartInfo.PointAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX] = InHPartInfo.VertexAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_PRIM] = InHPartInfo.PrimitiveAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL] = InHPartInfo.DetailAttributeCounts; + + PartInfo.isInstanced = InHPartInfo.bIsInstanced; + + PartInfo.instancedPartCount = InHPartInfo.InstancedPartCount; + PartInfo.instanceCount = InHPartInfo.InstanceCount; + + PartInfo.hasChanged = InHPartInfo.bHasChanged; + + return PartInfo; +} + +int32 +FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + TArray< FHoudiniMeshSocket >& AllSockets, + const bool& isPackedPrim) +{ + int32 FoundSocketCount = 0; + + // Attributes we are interested in. + // Position + TArray Positions; + HAPI_AttributeInfo AttribInfoPositions; + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bool bHasRotation = false; + TArray Rotations; + HAPI_AttributeInfo AttribInfoRotations; + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bool bHasScale = false; + TArray Scales; + HAPI_AttributeInfo AttribInfoScales; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // Socket Name + bool bHasNames = false; + TArray Names; + HAPI_AttributeInfo AttribInfoNames; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bool bHasActors = false; + TArray Actors; + HAPI_AttributeInfo AttribInfoActors; + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bool bHasTags = false; + TArray Tags; + HAPI_AttributeInfo AttribInfoTags; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + + // Lambda function for creating the socket and adding it to the array + // Shared between the by Attribute / by Group methods + auto AddSocketToArray = [&](const int32& PointIdx) + { + FHoudiniMeshSocket CurrentSocket; + FVector currentPosition = FVector::ZeroVector; + if (Positions.IsValidIndex(PointIdx * 3 + 2)) + { + currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + FVector currentScale = FVector::OneVector; + if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) + { + currentScale.X = Scales[PointIdx * 3]; + currentScale.Y = Scales[PointIdx * 3 + 2]; + currentScale.Z = Scales[PointIdx * 3 + 1]; + } + + FQuat currentRotation = FQuat::Identity; + if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) + { + currentRotation.X = Rotations[PointIdx * 4]; + currentRotation.Y = Rotations[PointIdx * 4 + 2]; + currentRotation.Z = Rotations[PointIdx * 4 + 1]; + currentRotation.W = -Rotations[PointIdx * 4 + 3]; + } + + if (bHasNames && Names.IsValidIndex(PointIdx)) + CurrentSocket.Name = Names[PointIdx]; + + if (bHasActors && Actors.IsValidIndex(PointIdx)) + CurrentSocket.Actor = Actors[PointIdx]; + + if (bHasTags && Tags.IsValidIndex(PointIdx)) + CurrentSocket.Tag = Tags[PointIdx]; + + // If the scale attribute wasn't set on all socket, we might end up + // with a zero scale socket, avoid that. + if (currentScale == FVector::ZeroVector) + currentScale = FVector::OneVector; + + CurrentSocket.Transform.SetLocation(currentPosition); + CurrentSocket.Transform.SetRotation(currentRotation); + CurrentSocket.Transform.SetScale3D(currentScale); + + // We want to make sure we're not adding the same socket multiple times + AllSockets.AddUnique(CurrentSocket); + + FoundSocketCount++; + + return true; + }; + + + // Lambda function for reseting the arrays/attributes + auto ResetArraysAndAttr = [&]() + { + // Position + Positions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bHasRotation = false; + Rotations.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bHasScale = false; + Scales.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // Socket Name + bHasNames = false; + Names.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bHasActors = false; + Actors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bHasTags = false; + Tags.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + }; + + //------------------------------------------------------------------------- + // FIND SOCKETS BY DETAIL ATTRIBUTES + //------------------------------------------------------------------------- + + int32 SocketIdx = 0; + bool HasSocketAttributes = true; + while (HasSocketAttributes) + { + // Build the current socket's prefix + FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(SocketIdx); + + // Reset the arrays and attributes + ResetArraysAndAttr(); + + // Retrieve position data. + FString SocketPosAttr = SocketAttrPrefix + TEXT("_pos"); + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, TCHAR_TO_ANSI(*SocketPosAttr), + AttribInfoPositions, Positions, 0, HAPI_ATTROWNER_DETAIL)) + break; + + if (!AttribInfoPositions.exists) + { + // No need to keep looking for socket attributes + HasSocketAttributes = false; + break; + } + + // Retrieve rotation data. + FString SocketRotAttr = SocketAttrPrefix + TEXT("_rot"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketRotAttr), AttribInfoRotations, Rotations, 0, HAPI_ATTROWNER_DETAIL)) + bHasRotation = true; + + // Retrieve scale data. + FString SocketScaleAttr = SocketAttrPrefix + TEXT("_scale"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketScaleAttr), AttribInfoScales, Scales, 0, HAPI_ATTROWNER_DETAIL)) + bHasScale = true; + + // Retrieve mesh socket names. + FString SocketNameAttr = SocketAttrPrefix + TEXT("_name"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketNameAttr), AttribInfoNames, Names)) + bHasNames = true; + + // Retrieve mesh socket actor. + FString SocketActorAttr = SocketAttrPrefix + TEXT("_actor"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketActorAttr), AttribInfoActors, Actors)) + bHasActors = true; + + // Retrieve mesh socket tags. + FString SocketTagAttr = SocketAttrPrefix + TEXT("_tag"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketTagAttr), AttribInfoTags, Tags)) + bHasTags = true; + + // Add the socket to the array + AddSocketToArray(0); + + // Try to find the next socket + SocketIdx++; + } + + return FoundSocketCount; +} + + +int32 +FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + TArray& AllSockets, + const bool& isPackedPrim) +{ + // Attributes we are interested in. + // Position + TArray Positions; + HAPI_AttributeInfo AttribInfoPositions; + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bool bHasRotation = false; + TArray Rotations; + HAPI_AttributeInfo AttribInfoRotations; + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bool bHasScale = false; + TArray Scales; + HAPI_AttributeInfo AttribInfoScales; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // We can also get the sockets rotation from the normal + bool bHasNormals = false; + TArray Normals; + HAPI_AttributeInfo AttribInfoNormals; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Socket Name + bool bHasNames = false; + TArray Names; + HAPI_AttributeInfo AttribInfoNames; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bool bHasActors = false; + TArray Actors; + HAPI_AttributeInfo AttribInfoActors; + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bool bHasTags = false; + TArray Tags; + HAPI_AttributeInfo AttribInfoTags; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + + // Lambda function for creating the socket and adding it to the array + // Shared between the by Attribute / by Group methods + int32 FoundSocketCount = 0; + auto AddSocketToArray = [&](const int32& PointIdx) + { + FHoudiniMeshSocket CurrentSocket; + FVector currentPosition = FVector::ZeroVector; + if (Positions.IsValidIndex(PointIdx * 3 + 2)) + { + currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + FVector currentScale = FVector::OneVector; + if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) + { + currentScale.X = Scales[PointIdx * 3]; + currentScale.Y = Scales[PointIdx * 3 + 2]; + currentScale.Z = Scales[PointIdx * 3 + 1]; + } + + FQuat currentRotation = FQuat::Identity; + if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) + { + currentRotation.X = Rotations[PointIdx * 4]; + currentRotation.Y = Rotations[PointIdx * 4 + 2]; + currentRotation.Z = Rotations[PointIdx * 4 + 1]; + currentRotation.W = -Rotations[PointIdx * 4 + 3]; + } + else if (bHasNormals && Normals.IsValidIndex(PointIdx * 3 + 2)) + { + FVector vNormal; + vNormal.X = Normals[PointIdx * 3]; + vNormal.Y = Normals[PointIdx * 3 + 2]; + vNormal.Z = Normals[PointIdx * 3 + 1]; + + if (vNormal != FVector::ZeroVector) + currentRotation = FQuat::FindBetween(FVector::UpVector, vNormal); + } + + if (bHasNames && Names.IsValidIndex(PointIdx)) + CurrentSocket.Name = Names[PointIdx]; + + if (bHasActors && Actors.IsValidIndex(PointIdx)) + CurrentSocket.Actor = Actors[PointIdx]; + + if (bHasTags && Tags.IsValidIndex(PointIdx)) + CurrentSocket.Tag = Tags[PointIdx]; + + // If the scale attribute wasn't set on all socket, we might end up + // with a zero scale socket, avoid that. + if (currentScale == FVector::ZeroVector) + currentScale = FVector::OneVector; + + CurrentSocket.Transform.SetLocation(currentPosition); + CurrentSocket.Transform.SetRotation(currentRotation); + CurrentSocket.Transform.SetScale3D(currentScale); + + // We want to make sure we're not adding the same socket multiple times + AllSockets.AddUnique(CurrentSocket); + + FoundSocketCount++; + + return true; + }; + + + // Lambda function for reseting the arrays/attributes + auto ResetArraysAndAttr = [&]() + { + // Position + Positions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bHasRotation = false; + Rotations.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bHasScale = false; + Scales.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // When using socket groups, we can also get the sockets rotation from the normal + bHasNormals = false; + Normals.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Socket Name + bHasNames = false; + Names.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bHasActors = false; + Actors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bHasTags = false; + Tags.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + }; + + //------------------------------------------------------------------------- + // FIND SOCKETS BY POINT GROUPS + //------------------------------------------------------------------------- + + // Get object / geo group memberships for primitives. + TArray GroupNames; + if (!FHoudiniEngineUtils::HapiGetGroupNames( + GeoId, PartId, HAPI_GROUPTYPE_POINT, isPackedPrim, GroupNames)) + { + HOUDINI_LOG_MESSAGE(TEXT("GetMeshSocketList: Geo [%d] Part [%d] non-fatal error reading point group names"), GeoId, PartId); + } + + // First, we want to make sure we have at least one socket group before continuing + bool bHasSocketGroup = false; + for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) + { + const FString & GroupName = GroupNames[GeoGroupNameIdx]; + if (GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) + || GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) + { + bHasSocketGroup = true; + break; + } + } + + if (!bHasSocketGroup) + return FoundSocketCount; + + // Get the part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PartInfo)) + return false; + + // Reset the data arrays and attributes + ResetArraysAndAttr(); + + // Retrieve position data. + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, Positions)) + return false; + + // Retrieve rotation data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_ROTATION, AttribInfoRotations, Rotations)) + bHasRotation = true; + + // Retrieve normal data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, Normals)) + bHasNormals = true; + + // Retrieve scale data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_SCALE, AttribInfoScales, Scales)) + bHasScale = true; + + // Retrieve mesh socket names. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, AttribInfoNames, Names)) + bHasNames = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD, AttribInfoNames, Names)) + bHasNames = true; + + // Retrieve mesh socket actor. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR, AttribInfoActors, Actors)) + bHasActors = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD, AttribInfoActors, Actors)) + bHasActors = true; + + // Retrieve mesh socket tags. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, AttribInfoTags, Tags)) + bHasTags = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD, AttribInfoTags, Tags)) + bHasTags = true; + + // Extracting Sockets vertices + for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) + { + const FString & GroupName = GroupNames[GeoGroupNameIdx]; + if (!GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) + && !GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) + continue; + + bool AllEquals = false; + TArray< int32 > PointGroupMembership; + FHoudiniEngineUtils::HapiGetGroupMembership( + GeoId, PartInfo, HAPI_GROUPTYPE_POINT, GroupName, PointGroupMembership, AllEquals); + + // Go through all primitives. + for (int32 PointIdx = 0; PointIdx < PointGroupMembership.Num(); ++PointIdx) + { + if (PointGroupMembership[PointIdx] == 0) + { + if (AllEquals) + break; + else + continue; + } + + // Add the corresponding socket to the array + AddSocketToArray(PointIdx); + } + } + + return FoundSocketCount; +} + +bool +FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( + UStaticMesh* StaticMesh, + TArray& AllSockets, + const bool& CleanImportSockets) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Remove the sockets from the previous cook! + if (CleanImportSockets) + { + StaticMesh->Sockets.RemoveAll([=](UStaticMeshSocket* Socket) { return Socket ? Socket->bSocketCreatedAtImport : true; }); + } + + if (AllSockets.Num() <= 0) + return true; + + // Having sockets with empty names can lead to various issues, so we'll create one now + for (int32 Idx = 0; Idx < AllSockets.Num(); ++Idx) + { + // Assign the unnamed sockets with default names + if (AllSockets[Idx].Name.IsEmpty()) + AllSockets[Idx].Name = TEXT("Socket ") + FString::FromInt(Idx); + } + + // ensure the socket names are unique. (Unreal will use the first socket if multiple socket have the same name) + for (int32 Idx_i = 0; Idx_i < AllSockets.Num(); ++Idx_i) + { + int32 Count = 0; + for (int32 Idx_j = Idx_i + 1; Idx_j < AllSockets.Num(); ++Idx_j) + { + if (AllSockets[Idx_i].Name.Equals(AllSockets[Idx_j].Name)) + { + Count += 1; + AllSockets[Idx_j].Name = AllSockets[Idx_j].Name + "_" + FString::FromInt(Count); + } + } + } + + // Clear all the sockets of the output static mesh. + StaticMesh->Sockets.Empty(); + + for (int32 nSocket = 0; nSocket < AllSockets.Num(); nSocket++) + { + // Create a new Socket + UStaticMeshSocket* Socket = NewObject(StaticMesh); + if (!Socket || Socket->IsPendingKill()) + continue; + + Socket->RelativeLocation = AllSockets[nSocket].Transform.GetLocation(); + Socket->RelativeRotation = FRotator(AllSockets[nSocket].Transform.GetRotation()); + Socket->RelativeScale = AllSockets[nSocket].Transform.GetScale3D(); + Socket->SocketName = FName(*AllSockets[nSocket].Name); + + // Socket Tag + FString Tag; + if (!AllSockets[nSocket].Tag.IsEmpty()) + Tag = AllSockets[nSocket].Tag; + + // The actor will be stored temporarily in the socket's Tag as we need a StaticMeshComponent to add an actor to the socket + Tag += TEXT("|") + AllSockets[nSocket].Actor; + + Socket->Tag = Tag; + Socket->bSocketCreatedAtImport = true; + + StaticMesh->Sockets.Add(Socket); + } + + return true; +} + +bool +FHoudiniEngineUtils::CreateAttributesFromTags( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const TArray& Tags ) +{ + if (Tags.Num() <= 0) + return false; + + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Get the destination part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); + + bool NeedToCommitGeo = false; + for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) + { + FString TagString; + Tags[TagIdx].ToString(TagString); + SanitizeHAPIVariableName(TagString); + + // Create a primitive attribute for the tag + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + AttributeInfo.count = PartInfo.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; + + FString AttributeName = TEXT(HAPI_UNREAL_ATTRIB_TAG_PREFIX) + FString::FromInt(TagIdx); + AttributeName.RemoveSpacesInline(); + + Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo); + + if (Result != HAPI_RESULT_SUCCESS) + continue; + + TArray TagStr; + TagStr.Add(FHoudiniEngineUtils::ExtractRawString(TagString)); + + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo, + TagStr.GetData(), 0, AttributeInfo.count); + + if (HAPI_RESULT_SUCCESS == Result) + NeedToCommitGeo = true; + + // Free memory for allocated by ExtractRawString + FHoudiniEngineUtils::FreeRawStringMemory(TagStr); + } + + return NeedToCommitGeo; +} + +bool +FHoudiniEngineUtils::CreateGroupsFromTags( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const TArray& Tags ) +{ + if (Tags.Num() <= 0) + return true; + + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Get the destination part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); + + bool NeedToCommitGeo = false; + for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) + { + FString TagString; + Tags[TagIdx].ToString(TagString); + SanitizeHAPIVariableName(TagString); + + const char * TagStr = FHoudiniEngineUtils::ExtractRawString(TagString); + + // Create a primitive group for this tag + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, TagStr) ) + { + // Set the group's Memberships + TArray GroupArray; + GroupArray.Init(1, PartInfo.faceCount); + + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_GROUPTYPE_PRIM, TagStr, + GroupArray.GetData(), 0, PartInfo.faceCount) ) + { + NeedToCommitGeo = true; + } + } + + // Free memory allocated by ExtractRawString() + FHoudiniEngineUtils::FreeRawStringMemory(TagStr); + } + + return NeedToCommitGeo; +} + + +bool +FHoudiniEngineUtils::SanitizeHAPIVariableName(FString& String) +{ + // Only keep alphanumeric characters, underscores + // Also, if the first character is a digit, append an underscore at the beginning + TArray& StrArray = String.GetCharArray(); + if (StrArray.Num() <= 0) + return false; + + for (auto& CurChar : StrArray) + { + const bool bIsValid = (CurChar >= TEXT('A') && CurChar <= TEXT('Z')) + || (CurChar >= TEXT('a') && CurChar <= TEXT('z')) + || (CurChar >= TEXT('0') && CurChar <= TEXT('9')) + || (CurChar == TEXT('_')) || (CurChar == TEXT('\0')); + + if(bIsValid) + continue; + + CurChar = TEXT('_'); + } + + if (StrArray.Num() > 0) + { + TCHAR FirstChar = StrArray[0]; + if (FirstChar >= TEXT('0') && FirstChar <= TEXT('9')) + StrArray.Insert(TEXT('_'), 0); + } + + return true; +} + +bool +FHoudiniEngineUtils::GetUnrealTagAttributes( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags) +{ + FString TagAttribBase = TEXT("unreal_tag_"); + bool bAttributeFound = true; + int32 TagIdx = 0; + while (bAttributeFound) + { + FString CurrentTagAttr = TagAttribBase + FString::FromInt(TagIdx++); + bAttributeFound = HapiCheckAttributeExists(GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), HAPI_ATTROWNER_PRIM); + if (!bAttributeFound) + break; + + // found the unreal_tag_X attribute, get its value and add it to the array + FString TagValue = FString(); + + // Create an AttributeInfo + { + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) + { + TagValue = StringData[0]; + } + } + + FName NameTag = *TagValue; + OutTags.Add(NameTag); + } + + return true; +} + + +int32 +FHoudiniEngineUtils::GetGenericAttributeList( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FString& InGenericAttributePrefix, + TArray& OutFoundAttributes, + const HAPI_AttributeOwner& AttributeOwner, + const int32& InAttribIndex) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineUtils::GetGenericAttributeList); + + // Get the part info to get the attribute counts for the specified owner + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), InGeoNodeId, InPartId, &PartInfo), false); + + int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; + + // Get all attribute names for that part + TArray AttribNameSHArray; + AttribNameSHArray.SetNum(nAttribCount); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, AttributeOwner, + AttribNameSHArray.GetData(), nAttribCount)) + { + return 0; + } + + // For everything but detail attribute, + // if an attribute index was specified, only extract the attribute value for that specific index + // if not, extract all values for the given attribute + bool HandleSplit = false; + int32 AttribIndex = -1; + if ((AttributeOwner != HAPI_ATTROWNER_DETAIL) && (InAttribIndex != -1)) + { + // The index has already been specified so we'll use it + HandleSplit = true; + AttribIndex = InAttribIndex; + } + + int32 FoundCount = 0; + for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) + { + int32 AttribNameSH = (int32)AttribNameSHArray[Idx]; + FString AttribName = TEXT(""); + FHoudiniEngineString::ToFString(AttribNameSH, AttribName); + if (!AttribName.StartsWith(InGenericAttributePrefix, ESearchCase::IgnoreCase)) + continue; + + // Get the Attribute Info + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), AttributeOwner, &AttribInfo)) + { + // failed to get that attribute's info + continue; + } + + int32 AttribStart = 0; + int32 AttribCount = AttribInfo.count; + if (HandleSplit) + { + // For split primitives, we need to only get only one value for the proper split prim + // Make sure that the split index is valid + if (AttribIndex >= 0 && AttribIndex < AttribInfo.count) + { + AttribStart = AttribIndex; + AttribCount = 1; + } + } + + // + FHoudiniGenericAttribute CurrentGenericAttribute; + // Remove the generic attribute prefix + CurrentGenericAttribute.AttributeName = AttribName.Right(AttribName.Len() - InGenericAttributePrefix.Len()); + + CurrentGenericAttribute.AttributeOwner = (EAttribOwner)AttribInfo.owner; + + // Get the attribute type and tuple size + CurrentGenericAttribute.AttributeType = (EAttribStorageType)AttribInfo.storage; + CurrentGenericAttribute.AttributeCount = AttribInfo.count; + CurrentGenericAttribute.AttributeTupleSize = AttribInfo.tupleSize; + + if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT64) + { + // Initialize the value array + CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloat64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, 0, + CurrentGenericAttribute.DoubleValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT) + { + // Initialize the value array + TArray FloatValues; + FloatValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, FloatValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to double + CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < FloatValues.Num(); n++) + CurrentGenericAttribute.DoubleValues[n] = (double)FloatValues[n]; + + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT64) + { +#if PLATFORM_LINUX + // On Linux, we unfortunately cannot guarantee that int64 and HAPI_Int64 + // are of the same type, to properly read the value, we must first check the + // size, then either cast them (if sizes match) or convert the values (if sizes don't match) + if (sizeof(int64) != sizeof(HAPI_Int64)) + { + // int64 and HAPI_Int64 are of different size, we need to cast + TArray HAPIIntValues; + HAPIIntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, HAPIIntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to int64 + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < HAPIIntValues.Num(); n++) + CurrentGenericAttribute.IntValues[n] = (int64)HAPIIntValues[n]; + } + else + { + // Initialize the value array + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) with a reinterpret_cast since sizes match + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, reinterpret_cast(CurrentGenericAttribute.IntValues.GetData()), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + } +#else + // Initialize the value array + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, CurrentGenericAttribute.IntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } +#endif + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT) + { + // Initialize the value array + TArray IntValues; + IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, IntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to int64 + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < IntValues.Num(); n++) + CurrentGenericAttribute.IntValues[n] = (int64)IntValues[n]; + + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + // Initialize a string handle array + TArray HapiSHArray; + HapiSHArray.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the string handle(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + HapiSHArray.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert the String Handles to FStrings + // using a map to minimize the number of HAPI calls + FHoudiniEngineString::SHArrayToFStringArray(HapiSHArray, CurrentGenericAttribute.StringValues); + } + else + { + // Unsupported type, skipping! + continue; + } + + // We can add the UPropertyAttribute to the array + OutFoundAttributes.Add(CurrentGenericAttribute); + FoundCount++; + } + + return FoundCount; +} + + +bool +FHoudiniEngineUtils::GetGenericPropertiesAttributes(const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, + const bool InbFindDetailAttributes, const int32& InFirstValidPrimIndex, const int32& InFirstValidVertexIndex, const int32& InFirstValidPointIndex, + TArray& OutPropertyAttributes) +{ + int32 FoundCount = 0; + + // List all the generic property detail attributes ... + if (InbFindDetailAttributes) + { + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + } + + // .. then the primitive property attributes for the given prim + if (InFirstValidPrimIndex != INDEX_NONE) + { + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); + } + + if (InFirstValidVertexIndex != INDEX_NONE) + { + // .. then finally, point uprop attributes for the given point + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_VERTEX, InFirstValidVertexIndex); + } + + if (InFirstValidPointIndex != INDEX_NONE) + { + // .. then finally, point uprop attributes for the given point + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidPointIndex); + } + + return FoundCount > 0; +} + +bool +FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(UObject* InObject, + const TArray& InAllPropertyAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current Property Attribute + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) + continue; + + // Success! + NumSuccess++; +#if defined(HOUDINI_ENGINE_LOGGING) + const FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + const FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); +#endif + } + + return (NumSuccess > 0); +} + +bool +FHoudiniEngineUtils::SetGenericPropertyAttribute( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FHoudiniGenericAttribute& InPropertyAttribute) +{ + HAPI_AttributeOwner AttribOwner; + switch (InPropertyAttribute.AttributeOwner) + { + case EAttribOwner::Point: + AttribOwner = HAPI_ATTROWNER_POINT; + break; + case EAttribOwner::Vertex: + AttribOwner = HAPI_ATTROWNER_VERTEX; + break; + case EAttribOwner::Prim: + AttribOwner = HAPI_ATTROWNER_PRIM; + break; + case EAttribOwner::Detail: + AttribOwner = HAPI_ATTROWNER_DETAIL; + break; + case EAttribOwner::Invalid: + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InPropertyAttribute.AttributeOwner); + return false; + } + + // Create the attribute via HAPI + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.tupleSize = InPropertyAttribute.AttributeTupleSize; + AttributeInfo.count = InPropertyAttribute.AttributeCount; + AttributeInfo.exists = true; + AttributeInfo.owner = AttribOwner; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + switch(InPropertyAttribute.AttributeType) + { + case (EAttribStorageType::INT): + AttributeInfo.storage = HAPI_STORAGETYPE_INT; + break; + case (EAttribStorageType::INT64): + AttributeInfo.storage = HAPI_STORAGETYPE_INT64; + break; + case (EAttribStorageType::FLOAT): + AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT; + break; + case (EAttribStorageType::FLOAT64): + AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT64; + break; + case (EAttribStorageType::STRING): + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + break; + case (EAttribStorageType::Invalid): + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Storage Type: %d"), InPropertyAttribute.AttributeType); + return false; + } + + // Create the new attribute + if (HAPI_RESULT_SUCCESS != FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo)) + { + return false; + } + + // The New attribute has been successfully created, set its value + switch (InPropertyAttribute.AttributeType) + { + case EAttribStorageType::INT: + { + TArray TempArray; + TempArray.Reserve(InPropertyAttribute.IntValues.Num()); + for (auto Value : InPropertyAttribute.IntValues) + { + TempArray.Add(static_cast(Value)); + } + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + TempArray.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + case EAttribStorageType::INT64: + { +#if PLATFORM_LINUX + // On Linux, we unfortunately cannot guarantee that int64 and HAPI_Int64 are of the same type, + TArray HAPIIntValues; + HAPIIntValues.SetNumZeroed(InPropertyAttribute.IntValues.Num()); + for (int32 n = 0; n < HAPIIntValues.Num(); n++) + HAPIIntValues[n] = (HAPI_Int64)InPropertyAttribute.IntValues[n]; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + HAPIIntValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } +#else + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + InPropertyAttribute.IntValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } +#endif + break; + } + + case EAttribStorageType::FLOAT: + { + + TArray TempArray; + TempArray.Reserve(InPropertyAttribute.DoubleValues.Num()); + for (auto Value : InPropertyAttribute.DoubleValues) + { + TempArray.Add(static_cast(Value)); + } + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + TempArray.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + case EAttribStorageType::FLOAT64: + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloat64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + InPropertyAttribute.DoubleValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + case EAttribStorageType::STRING: + { + if (HAPI_RESULT_SUCCESS != FHoudiniEngineUtils::SetAttributeStringData( + InPropertyAttribute.StringValues, + InGeoNodeId, + InPartId, + InPropertyAttribute.AttributeName, + AttributeInfo)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + default: + // Unsupported storage type + HOUDINI_LOG_WARNING(TEXT("Unsupported storage type: %d"), InPropertyAttribute.AttributeType); + break; + } + + return true; +} + +void +FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const FString& Key, const FString& Value) +{ + if (!Package || Package->IsPendingKill()) + return; + + UMetaData * MetaData = Package->GetMetaData(); + if (MetaData && !MetaData->IsPendingKill()) + MetaData->SetValue(Object, *Key, *Value); +} + + +bool +FHoudiniEngineUtils::AddLevelPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + ULevel* InLevel, + const int32& InCount) +{ + if (InNodeId < 0 || InCount <= 0) + return false; + + if (!InLevel || InLevel->IsPendingKill()) + return false; + + // Extract the level path from the level + FString LevelPath = InLevel->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = InCount; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(InCount); + for (int32 Idx = 0; Idx < InCount; ++Idx) + { + PrimitiveAttrs[Idx] = LevelPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} + + +bool +FHoudiniEngineUtils::AddActorPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + AActor* InActor, + const int32& InCount) +{ + if (InNodeId < 0 || InCount <= 0) + return false; + + if (!InActor || InActor->IsPendingKill()) + return false; + + // Extract the actor path + FString ActorPath = InActor->GetPathName(); + + // Get name of attribute used for Actor path + std::string MarshallingAttributeActorPath = HAPI_UNREAL_ATTRIB_ACTOR_PATH; + + // Marshall in Actor path. + HAPI_AttributeInfo AttributeInfoActorPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoActorPath); + AttributeInfoActorPath.count = InCount; + AttributeInfoActorPath.tupleSize = 1; + AttributeInfoActorPath.exists = true; + AttributeInfoActorPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoActorPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoActorPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string ActorPathCStr = TCHAR_TO_ANSI(*ActorPath); + const char* ActorPathCStrRaw = ActorPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(InCount); + for (int32 Idx = 0; Idx < InCount; ++Idx) + { + PrimitiveAttrs[Idx] = ActorPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, + MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoActorPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_actor_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} + + +bool +FHoudiniEngineUtils::ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx) +{ + const TArray< FVector2D > & LightmapUVs = RawMesh.WedgeTexCoords[LightmapSourceIdx]; + const TArray< uint32 > & Indices = RawMesh.WedgeIndices; + + if (LightmapUVs.Num() != Indices.Num()) + { + // This is invalid raw mesh; by design we consider that it contains invalid lightmap faces. + return true; + } + + for (int32 Idx = 0; Idx < Indices.Num(); Idx += 3) + { + const FVector2D & uv0 = LightmapUVs[Idx + 0]; + const FVector2D & uv1 = LightmapUVs[Idx + 1]; + const FVector2D & uv2 = LightmapUVs[Idx + 2]; + + if (uv0 == uv1 && uv1 == uv2) + { + // Detect invalid lightmap face, can stop. + return true; + } + } + + // Otherwise there are no invalid lightmap faces. + return false; +} + +void +FHoudiniEngineUtils::CreateSlateNotification( + const FString& NotificationString, const float& NotificationExpire, const float& NotificationFadeOut ) +{ +#if WITH_EDITOR + // Trying to display SlateNotifications while in a background thread will crash UE + if (!IsInGameThread() && !IsInSlateThread() && !IsInAsyncLoadingThread()) + return; + + // Check whether we want to display Slate notifications. + bool bDisplaySlateCookingNotifications = true; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return; + + FText NotificationText = FText::FromString(NotificationString); + FNotificationInfo Info(NotificationText); + + Info.bFireAndForget = true; + Info.FadeOutDuration = NotificationFadeOut; + Info.ExpireDuration = NotificationExpire; + + TSharedPtr HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + FSlateNotificationManager::Get().AddNotification(Info); +#endif + + return; +} + +FString +FHoudiniEngineUtils::GetHoudiniEnginePluginDir() +{ + FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(EnginePluginDir)) + return EnginePluginDir; + + FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(ProjectPluginDir)) + return ProjectPluginDir; + + TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); + FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; + if (FPaths::DirectoryExists(PluginBaseDir)) + return PluginBaseDir; + + HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); + + return EnginePluginDir; +} + + +HAPI_Result +FHoudiniEngineUtils::CreateNode( + const HAPI_NodeId& InParentNodeId, + const FString& InOperatorName, + const FString& InNodeLabel, + const HAPI_Bool& bInCookOnCreation, + HAPI_NodeId* OutNewNodeId) +{ + // Call HAPI::CreateNode + HAPI_Result Result = FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), + InParentNodeId, TCHAR_TO_UTF8(*InOperatorName), TCHAR_TO_UTF8(*InNodeLabel), bInCookOnCreation, OutNewNodeId); + + // Return now if CreateNode fialed + if (Result != HAPI_RESULT_SUCCESS) + return Result; + + // Loop on the cook_state status until it's ready + int CurrentStatus = HAPI_State::HAPI_STATE_STARTING_LOAD; + while (CurrentStatus > HAPI_State::HAPI_STATE_MAX_READY_STATE) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), + HAPI_StatusType::HAPI_STATUS_COOK_STATE, &CurrentStatus)) + { + // Exit the loop if GetStatus somehow fails + break; + } + } + + if (CurrentStatus == HAPI_STATE_READY_WITH_FATAL_ERRORS) + { + // Fatal errors - failed + HOUDINI_LOG_ERROR(TEXT("Failed to create node %s - %s"), *InOperatorName, *InNodeLabel); + return HAPI_RESULT_FAILURE; + } + else if (CurrentStatus == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // Mention the errors - still return success + HOUDINI_LOG_WARNING(TEXT("Cook errors when creating node %s - %s"), *InOperatorName, *InNodeLabel); + } + + return HAPI_RESULT_SUCCESS; +} + + +int32 +FHoudiniEngineUtils::HapiGetCookCount(const HAPI_NodeId& InNodeId) +{ + int32 CookCount = -1; + + FHoudiniApi::GetTotalCookCount( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_ANY, true, &CookCount); + + /* + // TODO: + // Use HAPI_GetCookingTotalCount() when available + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + + int32 CookCount = -1; + HAPI_Result Result = FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), InNodeId, &NodeInfo); + + if (Result != HAPI_RESULT_FAILURE) + { + if (NodeInfo.type != HAPI_NODETYPE_OBJ) + { + // For SOP assets, get the cook count straight from the Asset Node + CookCount = NodeInfo.totalCookCount; + } + else + { + // For OBJ nodes, get the cook count from the display geos + // Retrieve information about each object contained within our asset. + TArray< HAPI_ObjectInfo > ObjectInfos; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(InNodeId, ObjectInfos)) + return false; + + for (auto CurrentHapiObjectInfo : ObjectInfos) + { + // Get the Display Geo's info + HAPI_GeoInfo DisplayHapiGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) + { + continue; + } + + HAPI_NodeInfo DisplayNodeInfo; + FHoudiniApi::NodeInfo_Init(&DisplayNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), DisplayHapiGeoInfo.nodeId, &DisplayNodeInfo)) + { + continue; + } + + CookCount += DisplayNodeInfo.totalCookCount; + } + } + } + */ + + return CookCount; +} + +bool +FHoudiniEngineUtils::GetLevelPathAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutLevelPaths, + HAPI_AttributeOwner InAttributeOwner) +{ + // --------------------------------------------- + // Attribute: unreal_level_path + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_LEVEL_PATH, AttributeInfo, OutLevelPaths, 1, InAttributeOwner)) + { + if (OutLevelPaths.Num() > 0) + return true; + } + + OutLevelPaths.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetOutputNameAttribute(const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutOutputNames) +{ + // --------------------------------------------- + // Attribute: unreal_output_name + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, AttributeInfo, OutOutputNames, 1)) + { + if (OutOutputNames.Num() > 0) + return true; + } + + OutOutputNames.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, AttributeInfo, OutOutputNames, 1)) + { + if (OutOutputNames.Num() > 0) + return true; + } + + OutOutputNames.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetTileAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutTileValues, + const HAPI_AttributeOwner& InAttribOwner) +{ + // --------------------------------------------- + // Attribute: tile + // --------------------------------------------- + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, + AttribInfoTile, + OutTileValues, + 0, + InAttribOwner)) + { + if (OutTileValues.Num() > 0) + return true; + } + + OutTileValues.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + HAPI_AttributeOwner InAttributeOwner, + TArray& OutBakeFolder, + HAPI_PartId InPartId) +{ + OutBakeFolder.Empty(); + + HAPI_AttributeInfo BakeFolderAttribInfo; + FHoudiniApi::AttributeInfo_Init(&BakeFolderAttribInfo); + if (HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, InAttributeOwner)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + + OutBakeFolder.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + TArray& OutBakeFolder, + HAPI_PartId InPartId) +{ + OutBakeFolder.Empty(); + + if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_PRIM, OutBakeFolder, InPartId)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + + if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_DETAIL, OutBakeFolder, InPartId)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + + OutBakeFolder.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeActorAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeActorNames, + HAPI_AttributeOwner InAttributeOwner) +{ + // --------------------------------------------- + // Attribute: unreal_bake_actor + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_BAKE_ACTOR, AttributeInfo, OutBakeActorNames, 1, InAttributeOwner)) + { + if (OutBakeActorNames.Num() > 0) + return true; + } + + OutBakeActorNames.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeOutlinerFolders, + HAPI_AttributeOwner InAttributeOwner) +{ + // --------------------------------------------- + // Attribute: unreal_bake_outliner_folder + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, AttributeInfo, OutBakeOutlinerFolders, 1, InAttributeOwner)) + { + if (OutBakeOutlinerFolders.Num() > 0) + return true; + } + + OutBakeOutlinerFolders.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) +{ + if (!InActor || !InDesiredLevel) + return false; + + ULevel* PreviousLevel = InActor->GetLevel(); + if (PreviousLevel == InDesiredLevel) + return true; + + UWorld* CurrentWorld = InActor->GetWorld(); + if(CurrentWorld) + CurrentWorld->RemoveActor(InActor, true); + + //Set the outer of Actor to NewLevel + FHoudiniEngineUtils::RenameObject(InActor, (const TCHAR*)0, InDesiredLevel); + InDesiredLevel->Actors.Add(InActor); + + return true; +} + +bool +FHoudiniEngineUtils::HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions, const bool& bWaitForCompletion) +{ + // Check for an invalid node id + if (InNodeId < 0) + return false; + + // No Cook Options were specified, use the default one + if (InCookOptions == nullptr) + { + // Use the default cook options + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); + } + else + { + // Use the provided CookOptions + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, InCookOptions), false); + } + + // If we don't need to wait for completion, return now + if (!bWaitForCompletion) + return true; + + // Wait for the cook to finish + HAPI_Result Result = HAPI_RESULT_SUCCESS; + while (true) + { + // Get the current cook status + int Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // The cook has been successful. + return true; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // There was an error while cooking the node. + //FString CookResultString = FHoudiniEngineUtils::GetCookResult(); + //HOUDINI_LOG_ERROR(); + return false; + } + + // We want to yield a bit. + FPlatformProcess::Sleep(0.1f); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h index b84a39afb..d5933bc20 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h @@ -1,657 +1,679 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniEnginePrivatePCH.h" -#include "EngineUtils.h" -#include - -#include "HoudiniOutput.h" -#include "HoudiniPackageParams.h" -#include "Containers/UnrealString.h" - -#include "SSCSEditor.h" - - -class FString; -class UStaticMesh; -class UHoudiniAsset; -class UHoudiniAssetComponent; - -struct FHoudiniPartInfo; -struct FHoudiniMeshSocket; -struct FHoudiniGeoPartObject; -struct FHoudiniGenericAttribute; - -struct FRawMesh; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniInstancerType : uint8; - -struct HOUDINIENGINE_API FHoudiniEngineUtils -{ - friend struct FUnrealMeshTranslator; - - public: - // Load libHAPI and return handle to it, also store location of loaded libHAPI in passed argument. - static void* LoadLibHAPI(FString& StoredLibHAPILocation); - - // Return true if module has been properly initialized. - static bool IsInitialized(); - - // Return type of license used. - static bool GetLicenseType(FString & LicenseType); - - // Cook the specified node id - // if the cook options are null, the defualt one will be used - // if bWaitForCompletion is true, this call will be blocking until the cook is finished - static bool HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions = nullptr, const bool& bWaitForCompletion = false); - - // Return a specified HAPI status string. - static const FString GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity); - - // Return a string representing cooking result. - static const FString GetCookResult(); - - // Return a string indicating cook state. - static const FString GetCookState(); - - // Return a string error description. - static const FString GetErrorDescription(); - - // Return a string description of error from a given error code. - static const FString GetErrorDescription(HAPI_Result Result); - - // Return the errors, warning and messages on a specified node - static const FString GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId); - - static const FString GetCookLog(TArray& InHACs); - - static const FString GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent); - - // Updates the Object transform of a Houdini Asset Component - static bool UploadHACTransform(UHoudiniAssetComponent* HAC); - - // Convert FString to std::string - static void ConvertUnrealString(const FString & UnrealString, std::string& String); - - // Wrapper for the CreateNode function - // As HAPI_CreateNode is an async call, this function actually waits for the node creation to be done before returning - static HAPI_Result CreateNode( - const HAPI_NodeId& InParentNodeId, - const FString& InOperatorName, - const FString& InNodeLabel, - const HAPI_Bool& bInCookOnCreation, - HAPI_NodeId* OutNewNodeId); - - static int32 HapiGetCookCount(const HAPI_NodeId& InNodeId); - - // HAPI : Retrieve the asset node's object transform. **/ - static bool HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform); - - // HAPI : Translate HAPI transform to Unreal one. - static void TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform); - - // HAPI : Translate HAPI Euler transform to Unreal one. - static void TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform); - - // HAPI : Translate Unreal transform to HAPI one. - static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform); - - // HAPI : Translate Unreal transform to HAPI Euler one. - static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_TransformEuler & HapiTransformEuler); - - // Return true if asset is valid. - static bool IsHoudiniNodeValid(const HAPI_NodeId& AssetId); - - // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. - static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms); - - // HAPI: Retrieve Path to the given Node, relative to the given Node - static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); - - // HAPI: Retrieve the relative for the given HGPO Node - static bool HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath); - - // HAPI : Return all group names for a given Geo. - static bool HapiGetGroupNames( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const HAPI_GroupType& GroupType, const bool& isPackedPrim, - TArray& OutGroupNames ); - - // HAPI : Retrieve group membership. - static bool HapiGetGroupMembership( - const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, - const HAPI_GroupType& GroupType, const FString & GroupName, - TArray& OutGroupMembership, bool& OutAllEquals); - - // HAPI : Given vertex list, retrieve new vertex list for a specified group. - // Return number of processed valid index vertices for this split. - static int32 HapiGetVertexListForGroup( - const HAPI_NodeId& GeoId, - const HAPI_PartInfo& PartInfo, - const FString& GroupName, - const TArray& FullVertexList, - TArray& NewVertexList, - TArray& AllVertexList, - TArray& AllFaceList, - TArray& AllGroupFaceIndices, - int32& FirstValidVertex, - int32& FirstValidPrim, - const bool& isPackedPrim); - - // HAPI : Get attribute data as float. - static bool HapiGetAttributeDataAsFloat( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize = 0, - HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as Integer. - static bool HapiGetAttributeDataAsInteger( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - const int32& InTupleSize = 0, - const HAPI_AttributeOwner& InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as strings. - static bool HapiGetAttributeDataAsString( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize = 0, - HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as strings. - static bool HapiGetAttributeDataAsStringFromInfo( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& InAttributeInfo, - TArray& OutData); - - // HAPI : Check if given attribute exists. - static bool HapiCheckAttributeExists( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - const char * AttribName, - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID); - - // HAPI: Returns all the attributes of a given type for a given owner - static int32 HapiGetAttributeOfType( - const HAPI_NodeId& GeoId, - const HAPI_NodeId& PartId, - const HAPI_AttributeOwner& AttributeOwner, - const HAPI_AttributeTypeInfo& AttributeType, - TArray& MatchingAttributesInfo, - TArray& MatchingAttributesName); - - // HAPI : Look for a parameter by name and returns its index. Returns -1 if not found. - static HAPI_ParmId HapiFindParameterByName( - const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo); - - // HAPI : Look for a parameter by tag and returns its index. Returns -1 if not found. - static HAPI_ParmId HapiFindParameterByTag( - const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo); - - // Returns true is the given Geo-Part is an attribute instancer - static bool IsAttributeInstancer( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); - - // HAPI : Return a give node's parent ID, -1 if none - static HAPI_NodeId HapiGetParentNodeId(const HAPI_NodeId& NodeId); - - // HAPI : Marshaling, disconnect input asset from a given slot. - static bool HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex); - - // Destroy asset, returns the status. - static bool DestroyHoudiniAsset(const HAPI_NodeId& AssetId); - - // Loads an HDA file and returns its AssetLibraryId - static bool LoadHoudiniAsset( - UHoudiniAsset * HoudiniAsset, - HAPI_AssetLibraryId & OutAssetLibraryId); - - // Returns the name of the available subassets in a loaded HDA - static bool GetSubAssetNames( - const HAPI_AssetLibraryId& AssetLibraryId, - TArray< HAPI_StringHandle > & OutAssetNames); - - static bool OpenSubassetSelectionWindow( - TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ); - - // Returns the name of a Houdini asset. - static bool GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString); - - // Gets preset data for a given asset. - static bool GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer); - - // HAPI : Set asset transform. - static bool HapiSetAssetTransform(const HAPI_NodeId& AssetNodeId, const FTransform & Transform); - - // TODO: Move me somewhere else - static void AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC); - - // Triggers an update the details panel - // Will use an AsyncTask if we're not in the game thread - // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. - static void UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate); - - // Triggers an update the details panel - // Will use an AsyncTask if we're not in the game thread - // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. - static void UpdateEditorProperties(TArray InObjectsToUpdate, const bool& InForceFullUpdate); - - // Triggers an update the details panel - static void UpdateBlueprintEditor(UHoudiniAssetComponent* HAC); - - // Check if the Houdini asset component is being cooked - static bool IsHoudiniAssetComponentCooking(UObject* InObj); - - // Helper function to set attribute string data for a single FString - static HAPI_Result SetAttributeStringData( - const FString& InString, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo); - - // Helper function to set attribute string data for a FString array - static HAPI_Result SetAttributeStringData( - const TArray& InStringArray, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo); - - static bool HapiGetParameterDataAsString( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const FString& DefaultValue, - FString& OutValue); - - static bool HapiGetParameterDataAsInteger( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const int32& DefaultValue, - int32 & OutValue); - - static bool HapiGetParameterDataAsFloat( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const float& DefaultValue, - float& OutValue); - - // Returns a list of all the generic attributes for a given attribute owner - static int32 GetGenericAttributeList( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FString& InGenericAttributePrefix, - TArray& OutFoundAttributes, - const HAPI_AttributeOwner& AttributeOwner, - const int32& InAttribIndex = -1); - - // Helper functions for generic property attributes - static bool GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const bool InFindDetailAttributes, // if true, find default attributes - const int32& InFirstValidPrimIndex, // If not INDEX_NONE, look for primitive attribute - const int32& InFirstValidVertexIndex, // If this is not INDEX_NONE, look for vertex attribute - const int32& InFirstValidPointIndex, // If this is not INDEX_NONE, look for point attribute - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes); - - // Helper function for setting a generic attribute on geo (UE -> HAPI) - static bool SetGenericPropertyAttribute( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FHoudiniGenericAttribute& InPropertyAttribute); - - /* - // Tries to update values for all the UProperty attributes to apply on the object. - static void ApplyUPropertyAttributesOnObject( - UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify ); - */ - /* - static bool TryToFindInStructProperty( - UObject* Object, FString UPropertyNameToFind, UStructProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); - */ - /* - static bool TryToFindInArrayProperty( - UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); - */ - - static void AddHoudiniMetaInformationToPackage( - UPackage* Package, UObject* Object, const FString& Key, const FString& Value); - - // Adds the HoudiniLogo mesh to a Houdini Asset Component - static bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); - - // Removes the default Houdini logo mesh from a HAC - static bool RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC); - - // Indicates if a HAC has the Houdini logo mesh - static bool HasHoudiniLogo(UHoudiniAssetComponent* HAC); - - // - static HAPI_PartInfo ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo); - - // - static int32 AddMeshSocketsToArray_Group( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - TArray& AllSockets, const bool& isPackedPrim); - - // - static int32 AddMeshSocketsToArray_DetailAttribute( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - TArray& AllSockets, const bool& isPackedPrim); - - static bool AddMeshSocketsToStaticMesh( - UStaticMesh* StaticMesh, - TArray& AllSockets, - const bool& CleanImportSockets); - - // - static bool CreateGroupsFromTags( - const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); - - // - static bool CreateAttributesFromTags( - const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); - - static bool GetUnrealTagAttributes(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags); - - // Helper function to access the "unreal_level_path" attribute - static bool GetLevelPathAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutLevelPath, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to access the custom output name attribute - static bool GetOutputNameAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutOutputName); - - // Helper function to access the "tile" attribute - static bool GetTileAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutTileValue, - const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); - - // Helper function to access the "unreal_bake_folder" attribute - static bool GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - HAPI_AttributeOwner InAttributeOwner, - TArray& OutBakeFolder, - HAPI_PartId InPartId=0); - - // Helper function to access the "unreal_bake_folder" attribute - // We check for a primitive attribute first, if the primitive attribute does not exist, we check for a - // detail attribute. - static bool GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - TArray& OutBakeFolder, - HAPI_PartId InPartId=0); - - // Helper function to access the bake output actor attribute (unreal_bake_actor) - static bool GetBakeActorAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeActorNames, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to access the bake output actor attribute (unreal_bake_outliner_folder) - static bool GetBakeOutlinerFolderAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeOutlinerFolders, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Adds the "unreal_level_path" primitive attribute - static bool AddLevelPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - ULevel* InLevel, - const int32& InCount); - - // Adds the "unreal_actor_path" primitive attribute - static bool AddActorPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - AActor* InActor, - const int32& InCount); - - // Helper function used to extract a const char* from a FString - // !! Allocates memory using malloc that will need to be freed afterwards! - static char * ExtractRawString(const FString& Name); - - // Frees memory allocated by ExtractRawString() - static void FreeRawStringMemory(const char*& InRawString); - - // Frees memory allocated by ExtractRawString() - static void FreeRawStringMemory(TArray& InRawStringArray); - - // Make sure a string complies with Houdini's internal variable naming convention (group, attr etc..) - static bool SanitizeHAPIVariableName(FString& String); - - /** How many GUID symbols are used for package component name generation. **/ - static const int32 PackageGUIDComponentNameLength; - - /** How many GUID symbols are used for package item name generation. **/ - static const int32 PackageGUIDItemNameLength; - - /** Helper routine to check invalid lightmap faces. **/ - static bool ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx); - - // Helper function for creating a temporary Slate notification. - static void CreateSlateNotification( - const FString& NotificationString, - const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, - const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); - - static FString GetHoudiniEnginePluginDir(); - - // ------------------------------------------------- - // UWorld and UPackage utilities - // ------------------------------------------------- - - // Find actor in a given world by name - // Note that by default this will return all actors - template - static T* FindActorInWorld(UWorld* InWorld, FName ActorName, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) - { - T* OutActor = nullptr; - for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) - { - OutActor = *ActorIt; - if (!OutActor) - continue; - if (OutActor->GetFName().Compare(ActorName)==0) - return OutActor; - } - return nullptr; - } - - // Find an actor by name - static UWorld* FindWorldInPackage(const FString& PackagePath, bool bCreatedMissingPackage, bool& bOutPackageCreated); - - // Determine the appropriate world and level in which to spawn a new actor. - static bool FindWorldAndLevelForSpawning( - UWorld* CurrentWorld, - const FString& PackagePath, - bool bCreateMissingPackage, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bOutPackageCreated, - bool& bPackageInWorld); - - template - static T* SpawnActorInLevel(UWorld* InWorld, ULevel* InLevel) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = InLevel; - return InWorld->SpawnActor(SpawnParams); - } - - // Force the AssetRegistry to recursively rescan a path for - // any new packages that it may not know about, starting at the directory - // in which the given world package is located. This is typically useful - // for WorldComposition to detect new packages immediately after they - // were created. - static void RescanWorldPath(UWorld* InWorld); - - // ------------------------------------------------- - // Actor Utilities - // ------------------------------------------------- - - // Find in actor that belongs to the given outer matching the specified name. - // If the actor doesn't match the type, or is in a PendingKill state, rename it - // so that a new actor can be created with the given name. - // Note that if an actor with the give name was found, it will be returned via `OutFoundActor`. - static AActor* FindOrRenameInvalidActorGeneric(UClass* Class, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor); - - template - static T* FindOrRenameInvalidActor(UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) - { - return Cast( FindOrRenameInvalidActorGeneric(T::StaticClass(), InWorld, InName, OutFoundActor) ); - } - - // Moves an actor to the specified level - static bool MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel); - - // ------------------------------------------------- - // Debug Utilities - // ------------------------------------------------- - - // Log debug info for the given package - static void LogPackageInfo(const FString& InLongPackageName); - static void LogPackageInfo(const UPackage* InPackage); - - static void LogWorldInfo(const FString& InLongPackageName); - static void LogWorldInfo(const UWorld* InWorld); - - static FString HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType); - static FString HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState); - - // ------------------------------------------------- - // Generic naming / pathing utilities - // ------------------------------------------------- - - static bool RenameObject(UObject* Object, const TCHAR* NewName = nullptr, UObject* NewOuter = nullptr, ERenameFlags Flags = REN_None); - - // Rename the actor to a unique / generated name. - static FName RenameToUniqueActor(AActor* InActor, const FString& InName); - - // Safely rename the actor by ensuring that there aren't any existing objects left - // in the actor's outer with the same name. If an existing object was found, rename it and return it. - static UObject* SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel=true); - - // ------------------------------------------------- - // PackageParam utilities - // ------------------------------------------------- - - // Helper for populating FHoudiniPackageParams. - // If bAutomaticallySetAttemptToLoadMissingPackages is true, then - // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. - static void FillInPackageParamsForBakingOutput( - FHoudiniPackageParams& OutPackageParams, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FString &BakeFolder, - const FString &ObjectName, - const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, - bool bAutomaticallySetAttemptToLoadMissingPackages=true); - - // Helper for populating FHoudiniPackageParams when baking. This includes configuring the resolver to - // resolve the object name and unreal_bake_folder and setting these resolved values on the PackageParams. - // If bAutomaticallySetAttemptToLoadMissingPackages is true, then - // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. - static void FillInPackageParamsForBakingOutputWithResolver( - UWorld* const InWorldContext, - const UHoudiniAssetComponent* HoudiniAssetComponent, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FHoudiniOutputObject& InOutputObject, - const FString &InDefaultObjectName, - const FString &InHoudiniAssetName, - FHoudiniPackageParams& OutPackageParams, - FHoudiniAttributeResolver& OutResolver, - const FString &InDefaultBakeFolder=FString(), - EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, - bool bAutomaticallySetAttemptToLoadMissingPackages=true, - bool bInSkipObjectNameResolutionAndUseDefault=false, - bool bInSkipBakeFolderResolutionAndUseDefault=false); - - // ------------------------------------------------- - // Foliage utilities - // ------------------------------------------------- - - // If the foliage editor mode is active, repopulate the list of foliage types in the UI. - // NOTE: this is a currently a bit of a hack: we deactive and reactive the foliage mode (if it was active), - // since the relevant functions are not API exported. - // Returns true if the list was repopulated. - static bool RepopulateFoliageTypeListInUI(); - - - static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(const UObject* Obj); - - protected: - - // Computes the XX.YY.ZZZ version string using HAPI_Version - static FString ComputeVersionString(bool ExtraDigit); - -#if PLATFORM_WINDOWS - // Attempt to locate libHAPI on Windows in the registry. Return handle if located and return location. - static void* LocateLibHAPIInRegistry( - const FString& HoudiniInstallationType, FString& StoredLibHAPILocation, bool LookIn32bitRegistry); -#endif - - // Triggers an update the details panel - //static void UpdateEditorProperties_Internal(UObject* ObjectToUpdate, const bool& bInForceFullUpdate); - - // Triggers an update the details panel - static void UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate); - - // Trigger an update of the Blueprint Editor on the game thread - static void UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniEnginePrivatePCH.h" +#include "EngineUtils.h" +#include + +#include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" +#include "Containers/UnrealString.h" + +#include "SSCSEditor.h" + + +class FString; +class UStaticMesh; +class UHoudiniAsset; +class UHoudiniAssetComponent; + +struct FHoudiniPartInfo; +struct FHoudiniMeshSocket; +struct FHoudiniGeoPartObject; +struct FHoudiniGenericAttribute; + +struct FRawMesh; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniInstancerType : uint8; + +struct HOUDINIENGINE_API FHoudiniEngineUtils +{ + friend struct FUnrealMeshTranslator; + + public: + // Load libHAPI and return handle to it, also store location of loaded libHAPI in passed argument. + static void* LoadLibHAPI(FString& StoredLibHAPILocation); + + // Return true if module has been properly initialized. + static bool IsInitialized(); + + // Return type of license used. + static bool GetLicenseType(FString & LicenseType); + + // Cook the specified node id + // if the cook options are null, the defualt one will be used + // if bWaitForCompletion is true, this call will be blocking until the cook is finished + static bool HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions = nullptr, const bool& bWaitForCompletion = false); + + // Return a specified HAPI status string. + static const FString GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity); + + // Return a string representing cooking result. + static const FString GetCookResult(); + + // Return a string indicating cook state. + static const FString GetCookState(); + + // Return a string error description. + static const FString GetErrorDescription(); + + // Return a string description of error from a given error code. + static const FString GetErrorDescription(HAPI_Result Result); + + // Return the errors, warning and messages on a specified node + static const FString GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId); + + static const FString GetCookLog(TArray& InHACs); + + static const FString GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent); + + // Updates the Object transform of a Houdini Asset Component + static bool UploadHACTransform(UHoudiniAssetComponent* HAC); + + // Convert FString to std::string + static void ConvertUnrealString(const FString & UnrealString, std::string& String); + + // Wrapper for the CreateNode function + // As HAPI_CreateNode is an async call, this function actually waits for the node creation to be done before returning + static HAPI_Result CreateNode( + const HAPI_NodeId& InParentNodeId, + const FString& InOperatorName, + const FString& InNodeLabel, + const HAPI_Bool& bInCookOnCreation, + HAPI_NodeId* OutNewNodeId); + + static int32 HapiGetCookCount(const HAPI_NodeId& InNodeId); + + // HAPI : Retrieve the asset node's object transform. **/ + static bool HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform); + + // HAPI : Translate HAPI transform to Unreal one. + static void TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform); + + // HAPI : Translate HAPI Euler transform to Unreal one. + static void TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform); + + // HAPI : Translate Unreal transform to HAPI one. + static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform); + + // HAPI : Translate Unreal transform to HAPI Euler one. + static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_TransformEuler & HapiTransformEuler); + + // Return true if asset is valid. + static bool IsHoudiniNodeValid(const HAPI_NodeId& AssetId); + + // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. + static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms); + + // HAPI: Retrieve Path to the given Node, relative to the given Node + static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); + + // HAPI: Retrieve the relative for the given HGPO Node + static bool HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath); + + // HAPI : Return all group names for a given Geo. + static bool HapiGetGroupNames( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const HAPI_GroupType& GroupType, const bool& isPackedPrim, + TArray& OutGroupNames ); + + // HAPI : Retrieve group membership. + static bool HapiGetGroupMembership( + const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, + const HAPI_GroupType& GroupType, const FString & GroupName, + TArray& OutGroupMembership, bool& OutAllEquals); + + // HAPI : Given vertex list, retrieve new vertex list for a specified group. + // Return number of processed valid index vertices for this split. + static int32 HapiGetVertexListForGroup( + const HAPI_NodeId& GeoId, + const HAPI_PartInfo& PartInfo, + const FString& GroupName, + const TArray& FullVertexList, + TArray& NewVertexList, + TArray& AllVertexList, + TArray& AllFaceList, + TArray& AllGroupFaceIndices, + int32& FirstValidVertex, + int32& FirstValidPrim, + const bool& isPackedPrim); + + // HAPI : Get attribute data as float. + static bool HapiGetAttributeDataAsFloat( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize = 0, + HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); + + // HAPI : Get attribute data as Integer. + static bool HapiGetAttributeDataAsInteger( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + const int32& InTupleSize = 0, + const HAPI_AttributeOwner& InOwner = HAPI_ATTROWNER_INVALID); + + // HAPI : Get attribute data as strings. + static bool HapiGetAttributeDataAsString( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize = 0, + HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); + + // HAPI : Get attribute data as strings. + static bool HapiGetAttributeDataAsStringFromInfo( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& InAttributeInfo, + TArray& OutData); + + // HAPI : Check if given attribute exists. + static bool HapiCheckAttributeExists( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + const char * AttribName, + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID); + + // HAPI: Returns all the attributes of a given type for a given owner + static int32 HapiGetAttributeOfType( + const HAPI_NodeId& GeoId, + const HAPI_NodeId& PartId, + const HAPI_AttributeOwner& AttributeOwner, + const HAPI_AttributeTypeInfo& AttributeType, + TArray& MatchingAttributesInfo, + TArray& MatchingAttributesName); + + // HAPI : Look for a parameter by name and returns its index. Returns -1 if not found. + static HAPI_ParmId HapiFindParameterByName( + const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo); + + // HAPI : Look for a parameter by tag and returns its index. Returns -1 if not found. + static HAPI_ParmId HapiFindParameterByTag( + const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo); + + // Returns true is the given Geo-Part is an attribute instancer + static bool IsAttributeInstancer( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); + + // HAPI : Return a give node's parent ID, -1 if none + static HAPI_NodeId HapiGetParentNodeId(const HAPI_NodeId& NodeId); + + // HAPI : Marshaling, disconnect input asset from a given slot. + static bool HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex); + + // Destroy asset, returns the status. + static bool DestroyHoudiniAsset(const HAPI_NodeId& AssetId); + + // Loads an HDA file and returns its AssetLibraryId + static bool LoadHoudiniAsset( + const UHoudiniAsset * HoudiniAsset, + HAPI_AssetLibraryId & OutAssetLibraryId); + + // Returns the name of the available subassets in a loaded HDA + static bool GetSubAssetNames( + const HAPI_AssetLibraryId& AssetLibraryId, + TArray< HAPI_StringHandle > & OutAssetNames); + + static bool OpenSubassetSelectionWindow( + TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ); + + // Returns the name of a Houdini asset. + static bool GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString); + + // Gets preset data for a given asset. + static bool GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer); + + // HAPI : Set asset transform. + static bool HapiSetAssetTransform(const HAPI_NodeId& AssetNodeId, const FTransform & Transform); + + // TODO: Move me somewhere else + static void AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC); + + // Triggers an update the details panel + // Will use an AsyncTask if we're not in the game thread + // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. + static void UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate); + + // Triggers an update the details panel + // Will use an AsyncTask if we're not in the game thread + // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. + static void UpdateEditorProperties(TArray InObjectsToUpdate, const bool& InForceFullUpdate); + + // Triggers an update the details panel + static void UpdateBlueprintEditor(UHoudiniAssetComponent* HAC); + + // Check if the Houdini asset component is being cooked + static bool IsHoudiniAssetComponentCooking(UObject* InObj); + + // Helper function to set attribute string data for a single FString + static HAPI_Result SetAttributeStringData( + const FString& InString, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo); + + // Helper function to set attribute string data for a FString array + static HAPI_Result SetAttributeStringData( + const TArray& InStringArray, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo); + + static bool HapiGetParameterDataAsString( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const FString& DefaultValue, + FString& OutValue); + + static bool HapiGetParameterDataAsInteger( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const int32& DefaultValue, + int32 & OutValue); + + static bool HapiGetParameterDataAsFloat( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const float& DefaultValue, + float& OutValue); + + // Returns a list of all the generic attributes for a given attribute owner + static int32 GetGenericAttributeList( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FString& InGenericAttributePrefix, + TArray& OutFoundAttributes, + const HAPI_AttributeOwner& AttributeOwner, + const int32& InAttribIndex = -1); + + // Helper functions for generic property attributes + static bool GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const bool InFindDetailAttributes, // if true, find default attributes + const int32& InFirstValidPrimIndex, // If not INDEX_NONE, look for primitive attribute + const int32& InFirstValidVertexIndex, // If this is not INDEX_NONE, look for vertex attribute + const int32& InFirstValidPointIndex, // If this is not INDEX_NONE, look for point attribute + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes); + + // Helper function for setting a generic attribute on geo (UE -> HAPI) + static bool SetGenericPropertyAttribute( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FHoudiniGenericAttribute& InPropertyAttribute); + + /* + // Tries to update values for all the UProperty attributes to apply on the object. + static void ApplyUPropertyAttributesOnObject( + UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify ); + */ + /* + static bool TryToFindInStructProperty( + UObject* Object, FString UPropertyNameToFind, UStructProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); + */ + /* + static bool TryToFindInArrayProperty( + UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); + */ + + static void AddHoudiniMetaInformationToPackage( + UPackage* Package, UObject* Object, const FString& Key, const FString& Value); + + // Adds the HoudiniLogo mesh to a Houdini Asset Component + static bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); + + // Removes the default Houdini logo mesh from a HAC + static bool RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC); + + // Indicates if a HAC has the Houdini logo mesh + static bool HasHoudiniLogo(UHoudiniAssetComponent* HAC); + + // + static HAPI_PartInfo ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo); + + // + static int32 AddMeshSocketsToArray_Group( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + TArray& AllSockets, const bool& isPackedPrim); + + // + static int32 AddMeshSocketsToArray_DetailAttribute( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + TArray& AllSockets, const bool& isPackedPrim); + + static bool AddMeshSocketsToStaticMesh( + UStaticMesh* StaticMesh, + TArray& AllSockets, + const bool& CleanImportSockets); + + // + static bool CreateGroupsFromTags( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); + + // + static bool CreateAttributesFromTags( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); + + static bool GetUnrealTagAttributes(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags); + + // Helper function to access the "unreal_level_path" attribute + static bool GetLevelPathAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutLevelPath, + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + + // Helper function to access the custom output name attribute + static bool GetOutputNameAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutOutputName); + + // Helper function to access the "tile" attribute + static bool GetTileAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutTileValue, + const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); + + // Helper function to access the "unreal_bake_folder" attribute + static bool GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + HAPI_AttributeOwner InAttributeOwner, + TArray& OutBakeFolder, + HAPI_PartId InPartId=0); + + // Helper function to access the "unreal_bake_folder" attribute + // We check for a primitive attribute first, if the primitive attribute does not exist, we check for a + // detail attribute. + static bool GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + TArray& OutBakeFolder, + HAPI_PartId InPartId=0); + + // Helper function to access the bake output actor attribute (unreal_bake_actor) + static bool GetBakeActorAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeActorNames, + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + + // Helper function to access the bake output actor attribute (unreal_bake_outliner_folder) + static bool GetBakeOutlinerFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeOutlinerFolders, + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); + + // Adds the "unreal_level_path" primitive attribute + static bool AddLevelPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + ULevel* InLevel, + const int32& InCount); + + // Adds the "unreal_actor_path" primitive attribute + static bool AddActorPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + AActor* InActor, + const int32& InCount); + + // Helper function used to extract a const char* from a FString + // !! Allocates memory using malloc that will need to be freed afterwards! + static char * ExtractRawString(const FString& Name); + + // Frees memory allocated by ExtractRawString() + static void FreeRawStringMemory(const char*& InRawString); + + // Frees memory allocated by ExtractRawString() + static void FreeRawStringMemory(TArray& InRawStringArray); + + // Make sure a string complies with Houdini's internal variable naming convention (group, attr etc..) + static bool SanitizeHAPIVariableName(FString& String); + + /** How many GUID symbols are used for package component name generation. **/ + static const int32 PackageGUIDComponentNameLength; + + /** How many GUID symbols are used for package item name generation. **/ + static const int32 PackageGUIDItemNameLength; + + /** Helper routine to check invalid lightmap faces. **/ + static bool ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx); + + // Helper function for creating a temporary Slate notification. + static void CreateSlateNotification( + const FString& NotificationString, + const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, + const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); + + static FString GetHoudiniEnginePluginDir(); + + // ------------------------------------------------- + // UWorld and UPackage utilities + // ------------------------------------------------- + + // Find actor in a given world by label + template + static T* FindActorInWorldByLabel(UWorld* InWorld, FString ActorLabel, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) + { + T* OutActor = nullptr; + for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) + { + OutActor = *ActorIt; + if (!OutActor) + continue; + if (OutActor->GetActorLabel() == ActorLabel) + return OutActor; + } + return nullptr; + } + + // Find actor in a given world by name + template + static T* FindActorInWorld(UWorld* InWorld, FName ActorName, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) + { + T* OutActor = nullptr; + for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) + { + OutActor = *ActorIt; + if (!OutActor) + continue; + if (OutActor->GetFName().Compare(ActorName)==0) + return OutActor; + } + return nullptr; + } + + // Find an actor by name + static UWorld* FindWorldInPackage(const FString& PackagePath, bool bCreatedMissingPackage, bool& bOutPackageCreated); + + // Determine the appropriate world and level in which to spawn a new actor. + static bool FindWorldAndLevelForSpawning( + UWorld* CurrentWorld, + const FString& PackagePath, + bool bCreateMissingPackage, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bOutPackageCreated, + bool& bPackageInWorld); + + template + static T* SpawnActorInLevel(UWorld* InWorld, ULevel* InLevel) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = InLevel; + return InWorld->SpawnActor(SpawnParams); + } + + // Force the AssetRegistry to recursively rescan a path for + // any new packages that it may not know about, starting at the directory + // in which the given world package is located. This is typically useful + // for WorldComposition to detect new packages immediately after they + // were created. + static void RescanWorldPath(UWorld* InWorld); + + // ------------------------------------------------- + // Actor Utilities + // ------------------------------------------------- + + // Find in actor that belongs to the given outer matching the specified name. + // If the actor doesn't match the type, or is in a PendingKill state, rename it + // so that a new actor can be created with the given name. + // Note that if an actor with the give name was found, it will be returned via `OutFoundActor`. + static AActor* FindOrRenameInvalidActorGeneric(UClass* Class, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor); + + template + static T* FindOrRenameInvalidActor(UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) + { + return Cast( FindOrRenameInvalidActorGeneric(T::StaticClass(), InWorld, InName, OutFoundActor) ); + } + + // Moves an actor to the specified level + static bool MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel); + + // ------------------------------------------------- + // Debug Utilities + // ------------------------------------------------- + + // Log debug info for the given package + static void LogPackageInfo(const FString& InLongPackageName); + static void LogPackageInfo(const UPackage* InPackage); + + static void LogWorldInfo(const FString& InLongPackageName); + static void LogWorldInfo(const UWorld* InWorld); + + static FString HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType); + static FString HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState); + + // ------------------------------------------------- + // Generic naming / pathing utilities + // ------------------------------------------------- + + static bool RenameObject(UObject* Object, const TCHAR* NewName = nullptr, UObject* NewOuter = nullptr, ERenameFlags Flags = REN_None); + + // Rename the actor to a unique / generated name. + static FName RenameToUniqueActor(AActor* InActor, const FString& InName); + + // Safely rename the actor by ensuring that there aren't any existing objects left + // in the actor's outer with the same name. If an existing object was found, rename it and return it. + static UObject* SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel=true); + + // ------------------------------------------------- + // PackageParam utilities + // ------------------------------------------------- + + // Helper for populating FHoudiniPackageParams. + // If bAutomaticallySetAttemptToLoadMissingPackages is true, then + // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. + static void FillInPackageParamsForBakingOutput( + FHoudiniPackageParams& OutPackageParams, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FString &BakeFolder, + const FString &ObjectName, + const FString &HoudiniAssetName, + EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, + bool bAutomaticallySetAttemptToLoadMissingPackages=true); + + // Helper for populating FHoudiniPackageParams when baking. This includes configuring the resolver to + // resolve the object name and unreal_bake_folder and setting these resolved values on the PackageParams. + // If bAutomaticallySetAttemptToLoadMissingPackages is true, then + // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. + static void FillInPackageParamsForBakingOutputWithResolver( + UWorld* const InWorldContext, + const UHoudiniAssetComponent* HoudiniAssetComponent, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FString &InDefaultObjectName, + const FString &InHoudiniAssetName, + FHoudiniPackageParams& OutPackageParams, + FHoudiniAttributeResolver& OutResolver, + const FString &InDefaultBakeFolder=FString(), + EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, + bool bAutomaticallySetAttemptToLoadMissingPackages=true, + bool bInSkipObjectNameResolutionAndUseDefault=false, + bool bInSkipBakeFolderResolutionAndUseDefault=false); + + // ------------------------------------------------- + // Foliage utilities + // ------------------------------------------------- + + // If the foliage editor mode is active, repopulate the list of foliage types in the UI. + // NOTE: this is a currently a bit of a hack: we deactive and reactive the foliage mode (if it was active), + // since the relevant functions are not API exported. + // Returns true if the list was repopulated. + static bool RepopulateFoliageTypeListInUI(); + + // ------------------------------------------------- + // Landscape utilities + // ------------------------------------------------- + + // Iterate over the input objects and gather only the landscape inputs. + static void GatherLandscapeInputs(UHoudiniAssetComponent* HAC, TArray& AllInputLandscapes, TArray& InputLandscapesToUpdate); + + + static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(const UObject* Obj); + + protected: + + // Computes the XX.YY.ZZZ version string using HAPI_Version + static FString ComputeVersionString(bool ExtraDigit); + +#if PLATFORM_WINDOWS + // Attempt to locate libHAPI on Windows in the registry. Return handle if located and return location. + static void* LocateLibHAPIInRegistry( + const FString& HoudiniInstallationType, FString& StoredLibHAPILocation, bool LookIn32bitRegistry); +#endif + + // Triggers an update the details panel + //static void UpdateEditorProperties_Internal(UObject* ObjectToUpdate, const bool& bInForceFullUpdate); + + // Triggers an update the details panel + static void UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate); + + // Trigger an update of the Blueprint Editor on the game thread + static void UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp index 6bcb4c5c2..02a090d77 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp @@ -1,775 +1,775 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoImportCommandlet.h" - -#include "DirectoryWatcherModule.h" -#include "Modules/ModuleManager.h" -#include "Misc/Guid.h" -#include "EditorFramework/AssetImportData.h" - -#include "Editor.h" -#include "FileHelpers.h" - -#include "MessageEndpointBuilder.h" - -#include "PackageTools.h" - -#include "IDirectoryWatcher.h" - -#include "Internationalization/Regex.h" - -#include "Interfaces/ISlateNullRendererModule.h" -#include "Rendering/SlateRenderer.h" -#include "Framework/Application/SlateApplication.h" - -#include "HoudiniPackageParams.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniOutput.h" -#include "HoudiniPDGImporterMessages.h" -#include "HoudiniMeshTranslator.h" -#include "HAL/ThreadManager.h" - - -UHoudiniGeoImportCommandlet::UHoudiniGeoImportCommandlet() -{ - HelpDescription = TEXT("Import BGEOs as UAssets. Includes an option to watch a directories and include new .bgeos created there."); - - HelpUsage = TEXT("HoudiniGeoImport Usage: HoudiniGeoImport {options} [filename.bgeo]"); - // "Options:\n" - // "\t-help or -?\n" - // "\t\tDisplays this help.\n\n" - // "\t-listen=manager_messaging_address\n\n" - // "\t\tListen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager.\n\n" - // "\t-watch=directory\n\n" - // "\t\tA directory to watch for new .bgeo files to import.\n\n" - // "\t-managerpid=owner_pid\n\n" - // "\t\tThe PID of the owner/manager process. If the manager process dies the commandlet also quits.\n\n" - // "\t-bake\n\n" - // "\t\tBake generated assets. Instancers are baked to blueprints. Not supported in -listen mode.\n\n" - // "\t[filename.bgeo]\n" - // "\t\tWhen not using -listen or -watch, the path to a .bgeo file must be specified for import.\n" - //); - - HelpParamNames = { - "help", - "listen", - "guid", - "watch", - "managerpid", - "bake" - }; - - HelpParamDescriptions = { - "Displays this help.", - "Listen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager. Expects the owning process' PID.", - "Specify a GUID for the commandlet. Useful to identify the commandlet when the messaging system is used.", - "A directory to watch for new .bgeo files to import.", - "The PID of the owner/manager process. If the manager process dies the commandlet also quits.", - "Bake generated assets. Instancers are baked to blueprints. Not supported in -listen mode." - }; - - IsClient = false; - IsEditor = true; - IsServer = false; - LogToConsole = true; - ShowProgress = false; - ShowErrorCount = false; - - // LogToConsole = false; - - Mode = EHoudiniGeoImportCommandletMode::None; - bBakeOutputs = false; -} - -void UHoudiniGeoImportCommandlet::PrintUsage() const -{ - HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpDescription); - HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpUsage); - const int32 NumOptions = HelpParamNames.Num(); - for (int32 Idx = 0; Idx < NumOptions; ++Idx) - { - HOUDINI_LOG_DISPLAY(TEXT("-%s\t%s"), *HelpParamNames[Idx], *HelpParamDescriptions[Idx]); - } -} - -void UHoudiniGeoImportCommandlet::PopulatePackageParams(const FString &InBGEOFilename, FHoudiniPackageParams& OutPackageParams) -{ - UObject* InParent = this; - - if (bBakeOutputs) - { - OutPackageParams.PackageMode = EPackageMode::Bake; - } - else - { - OutPackageParams.PackageMode = EPackageMode::CookToTemp; - } - OutPackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - OutPackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - OutPackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - OutPackageParams.HoudiniAssetName = FPaths::GetBaseFilename(InBGEOFilename); - OutPackageParams.HoudiniAssetActorName = FString(); - OutPackageParams.ObjectName = FPaths::GetBaseFilename(InBGEOFilename); - - if (!OutPackageParams.OuterPackage) - { - OutPackageParams.OuterPackage = InParent; - } - - if (!OutPackageParams.ComponentGUID.IsValid()) - { - // TODO: will need to reuse the GUID when reimporting? - OutPackageParams.ComponentGUID = FGuid::NewGuid(); - } -} - -void UHoudiniGeoImportCommandlet::TickDiscoveredFiles() -{ - for (auto &FileDataEntry : DiscoveredFiles) - { - FDiscoveredFileData &FileData = FileDataEntry.Value; - if (FileData.bImportNextTick && !FileData.bImported) - { - FileData.bImportNextTick = false; - FileData.ImportAttempts++; - - FHoudiniPackageParams PackageParams; - PopulatePackageParams(FileData.FileName, PackageParams); - TArray Outputs; - int32 Error = ImportBGEO(FileData.FileName, PackageParams, Outputs); - if (Error == 0) - { - FileData.bImported = true; - HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Done"), *FileData.FileName); - } - else - { - FileData.bImported = false; - HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Failed (%d)"), *FileData.FileName, Error); - } - } - } -} - -int32 UHoudiniGeoImportCommandlet::MainLoop() -{ - GIsRunning = true; - - IDirectoryWatcher* DirectoryWatcher = nullptr; - - if (Mode == EHoudiniGeoImportCommandletMode::Listen) - { - PDGEndpoint = FMessageEndpoint::Builder("PDG/BGEO Commandlet") - .Handling(this, &UHoudiniGeoImportCommandlet::HandleImportBGEOMessage) - .ReceivingOnThread(ENamedThreads::GameThread); - if (!PDGEndpoint.IsValid()) - { - GIsRunning = false; - return 3; - } - // Notify the manager that we are running - HOUDINI_LOG_DISPLAY(TEXT("Notifying the manager (%s) that we are running"), *ManagerAddress.ToString()); - // Try to send directly to the manager - // TODO: this initially direct message does not work, the address looks to be correct, perhaps there is some - // additional set up needed to connect / discover the endpoints? - PDGEndpoint->Send(new FHoudiniPDGImportBGEODiscoverMessage(Guid), ManagerAddress); - } - else if (Mode == EHoudiniGeoImportCommandletMode::Watch) - { - FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::Get().LoadModuleChecked(TEXT("DirectoryWatcher")); - DirectoryWatcher = DirectoryWatcherModule.Get(); - } - - // In UnrealEngine 4.25 and older we cannot tick the editor engine without slate being initialized. - if (!FSlateApplication::IsInitialized()) - { - FSlateApplication::InitHighDPI(false); - FSlateApplication::Create(); - } - - // If slate is initialized, make sure it has a renderer. If we have to create a renderer, create the null renderer. - if (FSlateApplication::IsInitialized() && !FSlateApplication::Get().GetRenderer()) - { - const TSharedPtr SlateRenderer = FModuleManager::Get().LoadModuleChecked("SlateNullRenderer").CreateSlateNullRenderer(); - const TSharedRef SlateRendererSharedRef = SlateRenderer.ToSharedRef(); - FSlateApplication::Get().InitializeRenderer(SlateRendererSharedRef); - } - - // in listen mode broadcast our presence every 60 seconds - // This is an attempt to test if it solves a rare issue where the endpoints appear to get - // "disconnected" and sending a message to a previously valid message address stops working, even though - // both processes are still running (happens especially when debugging with breakpoints) - const float BroadcastIntervalSeconds = 60.0f; - float LastbroadcastTimeSeconds = 0.0f; - - // main loop - while (GIsRunning && !IsEngineExitRequested()) - { - GEngine->UpdateTimeAndHandleMaxTickRate(); - GEngine->Tick(FApp::GetDeltaTime(), false); - - if (FSlateApplication::IsInitialized()) - { - FSlateApplication::Get().PumpMessages(); - FSlateApplication::Get().Tick(); - } - - // Required for FTimerManager to function - as it blocks ticks, if the frame counter doesn't change - GFrameCounter++; - - // update task graph - FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); - - FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()); - FThreadManager::Get().Tick(); - GEngine->TickDeferredCommands(); - - if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) - { - // DirectoryWatcher->Tick(FApp::GetDeltaTime()); - - // Process the discovered files - TickDiscoveredFiles(); - } - - if (OwnerProcHandle.IsValid() && !FPlatformProcess::IsProcRunning(OwnerProcHandle)) - { - // Our once valid owner has disappeared, so quit. - RequestEngineExit(TEXT("OwnerDisappeared")); - } - - if (Mode == EHoudiniGeoImportCommandletMode::Listen && PDGEndpoint.IsValid()) - { - const float TimeSeconds = FPlatformTime::Seconds(); - if (TimeSeconds - LastbroadcastTimeSeconds >= BroadcastIntervalSeconds) - { - LastbroadcastTimeSeconds = TimeSeconds; - // Broadcast a discover message to notify that we are still available - PDGEndpoint->Publish(new FHoudiniPDGImportBGEODiscoverMessage(Guid)); - - HOUDINI_LOG_MESSAGE(TEXT("Publishing FHoudiniPDGImportBGEODiscoverMessage(%s)"), *Guid.ToString()); - } - } - - FPlatformProcess::Sleep(0); - } - - PDGEndpoint.Reset(); - if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) - { - DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(DirectoryToWatch, DirectoryWatcherHandle); - } - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Shutdown(); - - GIsRunning = false; - - return 0; -} - -void UHoudiniGeoImportCommandlet::HandleImportBGEOMessage( - const FHoudiniPDGImportBGEOMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_DISPLAY(TEXT("Received BGEO import request from %s"), *InContext->GetSender().ToString()); - - FHoudiniPackageParams PackageParams; - InMessage.PopulatePackageParams(PackageParams); - - TArray Outputs; - TMap> OutputObjectAttributes; - TMap InstancedOutputPartData; - if (ImportBGEO(InMessage.FilePath, PackageParams, Outputs, &OutputObjectAttributes, &InstancedOutputPartData) == 0) - { - FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); - (*Reply) = InMessage; - // Reply->PopulateFromPackageParams(PackageParams); - Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Success; - - const int32 NumOutputs = Outputs.Num(); - Reply->Outputs.Init(FHoudiniPDGImportNodeOutput(), NumOutputs); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - FHoudiniPDGImportNodeOutput &MessageOutput = Reply->Outputs[Index]; - UHoudiniOutput* Output = Outputs[Index]; - for (const FHoudiniGeoPartObject& HGPO : Output->GetHoudiniGeoPartObjects()) - { - HOUDINI_LOG_WARNING(TEXT("HGPO %d %d %d"), HGPO.ObjectId, HGPO.GeoId, HGPO.PartId); - MessageOutput.HoudiniGeoPartObjects.Add(HGPO); - - // Get instancer data if this is an instancer output - if (Output->GetType() == EHoudiniOutputType::Instancer) - { - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = HGPO.ObjectId; - OutputIdentifier.GeoId = HGPO.GeoId; - OutputIdentifier.PartId = HGPO.PartId; - OutputIdentifier.PartName = HGPO.PartName; - - FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = InstancedOutputPartData.Find(OutputIdentifier); - if (InstancedOutputPartDataPtr) - { - InstancedOutputPartDataPtr->BuildFlatInstancedTransformsAndObjectPaths(); - MessageOutput.InstancedOutputPartData.Add(*InstancedOutputPartDataPtr); - } - else - { - MessageOutput.InstancedOutputPartData.Add(FHoudiniInstancedOutputPartData()); - } - } - } - for (const auto& Entry : Output->GetOutputObjects()) - { - HOUDINI_LOG_WARNING(TEXT("Identifier %d %d %d"), Entry.Key.ObjectId, Entry.Key.GeoId, Entry.Key.PartId); - - MessageOutput.OutputObjects.AddDefaulted(); - FHoudiniPDGImportNodeOutputObject& MessageOutputObject = MessageOutput.OutputObjects.Last(); - - FString PackagePath = IsValid(Entry.Value.OutputObject) ? Entry.Value.OutputObject->GetPathName() : ""; - MessageOutputObject.Identifier = Entry.Key; - MessageOutputObject.PackagePath = PackagePath; - const TArray* PropertyAttributes = OutputObjectAttributes.Find(Entry.Key); - if (PropertyAttributes) - MessageOutputObject.GenericAttributes = *PropertyAttributes; - MessageOutputObject.CachedAttributes = Entry.Value.CachedAttributes; - } - } - - PDGEndpoint->Send(Reply, InContext->GetSender()); - } - else - { - HOUDINI_LOG_WARNING(TEXT("BGEO import failed.")); - FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); - Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Failed; - PDGEndpoint->Send(Reply, InContext->GetSender()); - } - - // Cleanup the outputs (remove from root) - TArray PackagesToUnload; - for (UHoudiniOutput *CurOutput : Outputs) - { - if (!IsValid(CurOutput)) - continue; - - for (const auto& Entry : CurOutput->GetOutputObjects()) - { - if (IsValid(Entry.Value.OutputObject)) - { - UPackage *Outermost = Entry.Value.OutputObject->GetOutermost(); - if (IsValid(Outermost)) - { - PackagesToUnload.Add(Outermost); - } - - Entry.Value.OutputObject->RemoveFromRoot(); - } - } - - CurOutput->RemoveFromRoot(); - } - Outputs.Empty(); - OutputObjectAttributes.Empty(); - - if (PackagesToUnload.Num() > 0) - { - HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ..."), PackagesToUnload.Num()); - FText ErrorMessage; - if (!UPackageTools::UnloadPackages(PackagesToUnload, ErrorMessage)) - { - HOUDINI_LOG_WARNING(TEXT("Unload packages failed: %s"), *ErrorMessage.ToString()); - } - else - { - HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ... Success"), PackagesToUnload.Num()); - } - PackagesToUnload.Empty(); - } - - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); -} - -bool UHoudiniGeoImportCommandlet::StartHoudiniEngineSession() -{ - // Start Houdini Engine session - HOUDINI_LOG_DISPLAY(TEXT("Starting Houdini Engine session...")); - FHoudiniEngine& HoudiniEngine = FHoudiniEngine::Get(); - if (!HoudiniEngine.CreateSession( - EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe, - "hapi_bgeo_cmdlet")) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start Houdini Engine session.")); - return false; - } - - return true; -} - -int32 UHoudiniGeoImportCommandlet::ImportBGEO( - const FString &InFilename, - const FHoudiniPackageParams &InPackageParams, - TArray& OutOutputs, - TMap>* OutGenericAttributes, - TMap* OutInstancedOutputPartData) -{ - if (!IsHoudiniEngineSessionRunning() && !StartHoudiniEngineSession()) - { - return 2; - } - - FHoudiniPackageParams PackageParams = InPackageParams; - UHoudiniGeoImporter* GeoImporter = NewObject(this); - - TArray OldOutputs; - OutOutputs.Empty(); - - // 2. Update the file paths - HOUDINI_LOG_DISPLAY(TEXT("SetFilePath %s"), *InFilename); - if (!GeoImporter->SetFilePath(InFilename)) - return 1; - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - HOUDINI_LOG_DISPLAY(TEXT("LoadBGEOFileInHAPI")); - if (!GeoImporter->LoadBGEOFileInHAPI(NodeId)) - return 1; - - // Look for a bake folder override in the BGEO file - if (PackageParams.PackageMode == EPackageMode::Bake) - { - HOUDINI_LOG_DISPLAY(TEXT("Looking for bake folder override attribute...")); - // Get the geo id for the node id - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), NodeId, &DisplayGeoInfo)) - { - TArray BakeFolderOverrideArray; - FString BakeFolderOverride; - const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderAttribute(DisplayGeoInfo.nodeId, HAPI_ATTROWNER_DETAIL,BakeFolderOverrideArray); - if (bFoundOverride && BakeFolderOverrideArray.Num() > 0) - BakeFolderOverride = BakeFolderOverrideArray[0]; - if (!BakeFolderOverride.IsEmpty()) - { - PackageParams.BakeFolder = BakeFolderOverride; - HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override (detail attrib): %s"), *PackageParams.BakeFolder); - } - else - { - HOUDINI_LOG_DISPLAY(TEXT("No bake folder override, using: %s"), *PackageParams.BakeFolder); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could not find display geo node id (when looking for bake folder override).")); - } - } - - auto CleanUpAndExit = [&OutOutputs, GeoImporter, NodeId](int32 InExitCode) - { - GeoImporter->GetOutputObjects().Empty(); - for (UHoudiniOutput* Output : OutOutputs) - { - Output->RemoveFromRoot(); - } - OutOutputs.Empty(); - - if (NodeId >= 0) - GeoImporter->DeleteCreatedNode(NodeId); - - return InExitCode; - }; - - // 4. Get the output from the file node - HOUDINI_LOG_DISPLAY(TEXT("BuildOutputsForNode %d"), NodeId); - if (!GeoImporter->BuildOutputsForNode(NodeId, OldOutputs, OutOutputs)) - return CleanUpAndExit(1); - - // Create uniquely named packages, commandlet runs in conjunction - // with a main editor instance, so we cannot modify existing files - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - // FString PackageName; - // UPackage* Outer = PackageParams.CreatePackageForObject(PackageName); - UObject* Outer = this; - - // 5. Create the static meshes in the outputs - HOUDINI_LOG_DISPLAY(TEXT("Create Static Meshes")); - if (!GeoImporter->CreateStaticMeshes(OutOutputs, Outer, PackageParams)) - return CleanUpAndExit(1); - - //// 6. Create the landscape in the outputs - //if (!GeoImporter->CreateLandscapes(NewOutputs, Outer, PackageParams)) - // return CleanUpAndExit(1); - - // 7. Create the instancers in the outputs - if (OutInstancedOutputPartData) - { - if (!GeoImporter->CreateInstancerOutputPartData(OutOutputs, *OutInstancedOutputPartData)) - return CleanUpAndExit(1); - } - else - { - if (!GeoImporter->CreateInstancers(OutOutputs, Outer, PackageParams)) - return CleanUpAndExit(1); - } - - if (OutGenericAttributes) - { - // Collect all generic properties from Houdini, we need to pass these - // through to PDG manager - HOUDINI_LOG_DISPLAY(TEXT("Get Generic Attributes for static meshes")); - for (UHoudiniOutput* CurOutput : OutOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (const auto& Entry : CurOutput->GetOutputObjects()) - { - const FHoudiniOutputObjectIdentifier OutputIdentifier = Entry.Key; - TArray PropertyAttributes; - FHoudiniEngineUtils::GetGenericPropertiesAttributes( - OutputIdentifier.GeoId, OutputIdentifier.PartId, - true, OutputIdentifier.PrimitiveIndex, INDEX_NONE, OutputIdentifier.PointIndex, - PropertyAttributes); - OutGenericAttributes->Add(OutputIdentifier, PropertyAttributes); - } - } - } - - // 8. Delete the created node in Houdini - HOUDINI_LOG_DISPLAY(TEXT("DeleteCreatedNode %d"), NodeId); - if (!GeoImporter->DeleteCreatedNode(NodeId)) - { - // Not good, but not fatal.. - //return false; - } - - TArray PackagesToSave; - TArray& OutputObjects = GeoImporter->GetOutputObjects(); - for (UObject* Object : OutputObjects) - { - if (!IsValid(Object)) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("Created object: %s"), *Object->GetFullName()); - - UAssetImportData* AssetImportData = nullptr; - if (Object->IsA()) - { - UStaticMesh* SM = Cast(Object); - AssetImportData = SM->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); - SM->AssetImportData = AssetImportData; - } - } - - if (AssetImportData) - AssetImportData->Update(InFilename); - - Object->MarkPackageDirty(); - Object->PostEditChange(); - - UPackage* Package = Object->GetOutermost(); - if (IsValid(Package)) - { - PackagesToSave.AddUnique(Package); - } - } - - if (PackagesToSave.Num() > 0) - { - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); - } - - PackagesToSave.Empty(); - OutputObjects.Empty(); - - return 0; -} - -void UHoudiniGeoImportCommandlet::HandleDirectoryChanged(const TArray& InFileChangeDatas) -{ - const FRegexPattern BGEOPattern(TEXT(R"((.*)\.(bgeo(\.[^\.]*)?)$)")); - - for (const FFileChangeData& FileChangeData : InFileChangeDatas) - { - HOUDINI_LOG_MESSAGE(TEXT("HandleDirectoryChanged %d %s"), FileChangeData.Action, *FileChangeData.Filename); - - FRegexMatcher BGEOMatcher(BGEOPattern, FileChangeData.Filename.ToLower()); - if (BGEOMatcher.FindNext() && BGEOMatcher.GetCaptureGroup(2).StartsWith(TEXT("bgeo"))) - { - HOUDINI_LOG_DISPLAY(TEXT("Updating entry for %s..."), *FileChangeData.Filename); - const uint32 MaxImportAttempts = 3; - switch(FileChangeData.Action) - { - case FFileChangeData::FCA_Added: - case FFileChangeData::FCA_Modified: - if (DiscoveredFiles.Contains(FileChangeData.Filename)) - { - FDiscoveredFileData &FileData = DiscoveredFiles[FileChangeData.Filename]; - if (!FileData.bImported && FileData.ImportAttempts < MaxImportAttempts) - FileData.bImportNextTick = true; - else if (FileData.ImportAttempts >= MaxImportAttempts) - HOUDINI_LOG_WARNING(TEXT("Not importing %s, max attempts exceeded %d"), *FileData.FileName, FileData.ImportAttempts); - } - else - { - DiscoveredFiles.Add(FileChangeData.Filename, FDiscoveredFileData(FileChangeData.Filename, true)); - } - break; - case FFileChangeData::FCA_Removed: - DiscoveredFiles.Remove(FileChangeData.Filename); - break; - default: - HOUDINI_LOG_WARNING(TEXT("Unknown file change event %d for %s"), FileChangeData.Action, *FileChangeData.Filename); - } - } - } -} - -int32 UHoudiniGeoImportCommandlet::Main(const FString& InParams) -{ - TArray Tokens; - TArray Switches; - TMap Params; - ParseCommandLine(*InParams, Tokens, Switches, Params); - - if (Switches.Contains(TEXT("help")) || Switches.Contains(TEXT("?"))) - { - PrintUsage(); - return 0; - } - - if (Params.Contains(TEXT("guid"))) - { - const FString GuidStr = Params.FindChecked(TEXT("guid")); - FGuid::Parse(GuidStr, Guid); - - HOUDINI_LOG_DISPLAY(TEXT("GUID received: %s"), *Guid.ToString()); - } - else - { - Guid = FGuid::NewGuid(); - } - - // Set bake mode - if (Switches.Contains(TEXT("bake"))) - bBakeOutputs = true; - else - bBakeOutputs = false; - - if (Params.Contains(TEXT("listen"))) - { - Mode = EHoudiniGeoImportCommandletMode::Listen; - - if (!Params.Contains(TEXT("managerpid"))) - { - HOUDINI_LOG_ERROR(TEXT("'managerpid' is required when in -listen mode.")); - return 1; - } - - if (bBakeOutputs) - { - HOUDINI_LOG_ERROR(TEXT("'listen' mode does not support baking outputs (-bake).")); - return 1; - } - - // Get the manager's messaging address from the -listen param - const FString ManagerAddressStr = Params.FindChecked(TEXT("listen")); - if (!FMessageAddress::Parse(ManagerAddressStr, ManagerAddress)) - { - HOUDINI_LOG_ERROR(TEXT("The manager messaging address passed to -listen=%s is invalid."), *ManagerAddressStr); - return 1; - } - - // Get the manager pid and proc handle - uint32 OwnerProcessId = FCString::Strtoi(*Params.FindChecked(TEXT("managerpid")), nullptr, 10); - HOUDINI_LOG_DISPLAY(TEXT("Owner process Id: %d"), OwnerProcessId); - OwnerProcHandle = FPlatformProcess::OpenProcess(OwnerProcessId); - - return MainLoop(); - } - else if (Params.Contains(TEXT("watch"))) - { - Mode = EHoudiniGeoImportCommandletMode::Watch; - - HOUDINI_LOG_DISPLAY(TEXT("directory watch mode")); - FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); - IDirectoryWatcher* DirectoryWatcher = DirectoryWatcherModule.Get(); - if (DirectoryWatcher) - { - DirectoryToWatch = Params.FindChecked(TEXT("watch")); - if (FPaths::IsRelative(DirectoryToWatch)) - DirectoryToWatch = FPaths::ConvertRelativePathToFull(DirectoryToWatch); - - HOUDINI_LOG_DISPLAY(TEXT("Watching %s"), *DirectoryToWatch); - - DirectoryWatcher->RegisterDirectoryChangedCallback_Handle( - DirectoryToWatch, - IDirectoryWatcher::FDirectoryChanged::CreateUObject(this, &UHoudiniGeoImportCommandlet::HandleDirectoryChanged), - DirectoryWatcherHandle); - - return MainLoop(); - } - else - { - return 10; - } - } - else if (Tokens.Num() > 0) - { - Mode = EHoudiniGeoImportCommandletMode::SpecifiedFiles; - - if (!StartHoudiniEngineSession()) - return 2; - - const FString Filename = FPaths::IsRelative(Tokens[0]) ? FPaths::ConvertRelativePathToFull(Tokens[0]) : Tokens[0]; - FHoudiniPackageParams PackageParams; - PopulatePackageParams(Filename, PackageParams); - - TArray Outputs; - const int32 Result = ImportBGEO(Tokens[0], PackageParams, Outputs); - - for (UHoudiniOutput* Output : Outputs) - { - Output->RemoveFromRoot(); - } - Outputs.Empty(); - - return Result; - } - - return 0; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoImportCommandlet.h" + +#include "DirectoryWatcherModule.h" +#include "Modules/ModuleManager.h" +#include "Misc/Guid.h" +#include "EditorFramework/AssetImportData.h" + +#include "Editor.h" +#include "FileHelpers.h" + +#include "MessageEndpointBuilder.h" + +#include "PackageTools.h" + +#include "IDirectoryWatcher.h" + +#include "Internationalization/Regex.h" + +#include "Interfaces/ISlateNullRendererModule.h" +#include "Rendering/SlateRenderer.h" +#include "Framework/Application/SlateApplication.h" + +#include "HoudiniPackageParams.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniOutput.h" +#include "HoudiniPDGImporterMessages.h" +#include "HoudiniMeshTranslator.h" +#include "HAL/ThreadManager.h" + + +UHoudiniGeoImportCommandlet::UHoudiniGeoImportCommandlet() +{ + HelpDescription = TEXT("Import BGEOs as UAssets. Includes an option to watch a directories and include new .bgeos created there."); + + HelpUsage = TEXT("HoudiniGeoImport Usage: HoudiniGeoImport {options} [filename.bgeo]"); + // "Options:\n" + // "\t-help or -?\n" + // "\t\tDisplays this help.\n\n" + // "\t-listen=manager_messaging_address\n\n" + // "\t\tListen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager.\n\n" + // "\t-watch=directory\n\n" + // "\t\tA directory to watch for new .bgeo files to import.\n\n" + // "\t-managerpid=owner_pid\n\n" + // "\t\tThe PID of the owner/manager process. If the manager process dies the commandlet also quits.\n\n" + // "\t-bake\n\n" + // "\t\tBake generated assets. Instancers are baked to blueprints. Not supported in -listen mode.\n\n" + // "\t[filename.bgeo]\n" + // "\t\tWhen not using -listen or -watch, the path to a .bgeo file must be specified for import.\n" + //); + + HelpParamNames = { + "help", + "listen", + "guid", + "watch", + "managerpid", + "bake" + }; + + HelpParamDescriptions = { + "Displays this help.", + "Listen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager. Expects the owning process' PID.", + "Specify a GUID for the commandlet. Useful to identify the commandlet when the messaging system is used.", + "A directory to watch for new .bgeo files to import.", + "The PID of the owner/manager process. If the manager process dies the commandlet also quits.", + "Bake generated assets. Instancers are baked to blueprints. Not supported in -listen mode." + }; + + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = true; + ShowProgress = false; + ShowErrorCount = false; + + // LogToConsole = false; + + Mode = EHoudiniGeoImportCommandletMode::None; + bBakeOutputs = false; +} + +void UHoudiniGeoImportCommandlet::PrintUsage() const +{ + HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpDescription); + HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpUsage); + const int32 NumOptions = HelpParamNames.Num(); + for (int32 Idx = 0; Idx < NumOptions; ++Idx) + { + HOUDINI_LOG_DISPLAY(TEXT("-%s\t%s"), *HelpParamNames[Idx], *HelpParamDescriptions[Idx]); + } +} + +void UHoudiniGeoImportCommandlet::PopulatePackageParams(const FString &InBGEOFilename, FHoudiniPackageParams& OutPackageParams) +{ + UObject* InParent = this; + + if (bBakeOutputs) + { + OutPackageParams.PackageMode = EPackageMode::Bake; + } + else + { + OutPackageParams.PackageMode = EPackageMode::CookToTemp; + } + OutPackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + OutPackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + OutPackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + OutPackageParams.HoudiniAssetName = FPaths::GetBaseFilename(InBGEOFilename); + OutPackageParams.HoudiniAssetActorName = FString(); + OutPackageParams.ObjectName = FPaths::GetBaseFilename(InBGEOFilename); + + if (!OutPackageParams.OuterPackage) + { + OutPackageParams.OuterPackage = InParent; + } + + if (!OutPackageParams.ComponentGUID.IsValid()) + { + // TODO: will need to reuse the GUID when reimporting? + OutPackageParams.ComponentGUID = FGuid::NewGuid(); + } +} + +void UHoudiniGeoImportCommandlet::TickDiscoveredFiles() +{ + for (auto &FileDataEntry : DiscoveredFiles) + { + FDiscoveredFileData &FileData = FileDataEntry.Value; + if (FileData.bImportNextTick && !FileData.bImported) + { + FileData.bImportNextTick = false; + FileData.ImportAttempts++; + + FHoudiniPackageParams PackageParams; + PopulatePackageParams(FileData.FileName, PackageParams); + TArray Outputs; + int32 Error = ImportBGEO(FileData.FileName, PackageParams, Outputs); + if (Error == 0) + { + FileData.bImported = true; + HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Done"), *FileData.FileName); + } + else + { + FileData.bImported = false; + HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Failed (%d)"), *FileData.FileName, Error); + } + } + } +} + +int32 UHoudiniGeoImportCommandlet::MainLoop() +{ + GIsRunning = true; + + IDirectoryWatcher* DirectoryWatcher = nullptr; + + if (Mode == EHoudiniGeoImportCommandletMode::Listen) + { + PDGEndpoint = FMessageEndpoint::Builder("PDG/BGEO Commandlet") + .Handling(this, &UHoudiniGeoImportCommandlet::HandleImportBGEOMessage) + .ReceivingOnThread(ENamedThreads::GameThread); + if (!PDGEndpoint.IsValid()) + { + GIsRunning = false; + return 3; + } + // Notify the manager that we are running + HOUDINI_LOG_DISPLAY(TEXT("Notifying the manager (%s) that we are running"), *ManagerAddress.ToString()); + // Try to send directly to the manager + // TODO: this initially direct message does not work, the address looks to be correct, perhaps there is some + // additional set up needed to connect / discover the endpoints? + PDGEndpoint->Send(new FHoudiniPDGImportBGEODiscoverMessage(Guid), ManagerAddress); + } + else if (Mode == EHoudiniGeoImportCommandletMode::Watch) + { + FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::Get().LoadModuleChecked(TEXT("DirectoryWatcher")); + DirectoryWatcher = DirectoryWatcherModule.Get(); + } + + // In UnrealEngine 4.25 and older we cannot tick the editor engine without slate being initialized. + if (!FSlateApplication::IsInitialized()) + { + FSlateApplication::InitHighDPI(false); + FSlateApplication::Create(); + } + + // If slate is initialized, make sure it has a renderer. If we have to create a renderer, create the null renderer. + if (FSlateApplication::IsInitialized() && !FSlateApplication::Get().GetRenderer()) + { + const TSharedPtr SlateRenderer = FModuleManager::Get().LoadModuleChecked("SlateNullRenderer").CreateSlateNullRenderer(); + const TSharedRef SlateRendererSharedRef = SlateRenderer.ToSharedRef(); + FSlateApplication::Get().InitializeRenderer(SlateRendererSharedRef); + } + + // in listen mode broadcast our presence every 60 seconds + // This is an attempt to test if it solves a rare issue where the endpoints appear to get + // "disconnected" and sending a message to a previously valid message address stops working, even though + // both processes are still running (happens especially when debugging with breakpoints) + const float BroadcastIntervalSeconds = 60.0f; + float LastbroadcastTimeSeconds = 0.0f; + + // main loop + while (GIsRunning && !IsEngineExitRequested()) + { + GEngine->UpdateTimeAndHandleMaxTickRate(); + GEngine->Tick(FApp::GetDeltaTime(), false); + + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().PumpMessages(); + FSlateApplication::Get().Tick(); + } + + // Required for FTimerManager to function - as it blocks ticks, if the frame counter doesn't change + GFrameCounter++; + + // update task graph + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + + FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()); + FThreadManager::Get().Tick(); + GEngine->TickDeferredCommands(); + + if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) + { + // DirectoryWatcher->Tick(FApp::GetDeltaTime()); + + // Process the discovered files + TickDiscoveredFiles(); + } + + if (OwnerProcHandle.IsValid() && !FPlatformProcess::IsProcRunning(OwnerProcHandle)) + { + // Our once valid owner has disappeared, so quit. + RequestEngineExit(TEXT("OwnerDisappeared")); + } + + if (Mode == EHoudiniGeoImportCommandletMode::Listen && PDGEndpoint.IsValid()) + { + const float TimeSeconds = FPlatformTime::Seconds(); + if (TimeSeconds - LastbroadcastTimeSeconds >= BroadcastIntervalSeconds) + { + LastbroadcastTimeSeconds = TimeSeconds; + // Broadcast a discover message to notify that we are still available + PDGEndpoint->Publish(new FHoudiniPDGImportBGEODiscoverMessage(Guid)); + + HOUDINI_LOG_MESSAGE(TEXT("Publishing FHoudiniPDGImportBGEODiscoverMessage(%s)"), *Guid.ToString()); + } + } + + FPlatformProcess::Sleep(0); + } + + PDGEndpoint.Reset(); + if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) + { + DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(DirectoryToWatch, DirectoryWatcherHandle); + } + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Shutdown(); + + GIsRunning = false; + + return 0; +} + +void UHoudiniGeoImportCommandlet::HandleImportBGEOMessage( + const FHoudiniPDGImportBGEOMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_DISPLAY(TEXT("Received BGEO import request from %s"), *InContext->GetSender().ToString()); + + FHoudiniPackageParams PackageParams; + InMessage.PopulatePackageParams(PackageParams); + + TArray Outputs; + TMap> OutputObjectAttributes; + TMap InstancedOutputPartData; + if (ImportBGEO(InMessage.FilePath, PackageParams, Outputs, &OutputObjectAttributes, &InstancedOutputPartData) == 0) + { + FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); + (*Reply) = InMessage; + // Reply->PopulateFromPackageParams(PackageParams); + Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Success; + + const int32 NumOutputs = Outputs.Num(); + Reply->Outputs.Init(FHoudiniPDGImportNodeOutput(), NumOutputs); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + FHoudiniPDGImportNodeOutput &MessageOutput = Reply->Outputs[Index]; + UHoudiniOutput* Output = Outputs[Index]; + for (const FHoudiniGeoPartObject& HGPO : Output->GetHoudiniGeoPartObjects()) + { + HOUDINI_LOG_WARNING(TEXT("HGPO %d %d %d"), HGPO.ObjectId, HGPO.GeoId, HGPO.PartId); + MessageOutput.HoudiniGeoPartObjects.Add(HGPO); + + // Get instancer data if this is an instancer output + if (Output->GetType() == EHoudiniOutputType::Instancer) + { + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = HGPO.ObjectId; + OutputIdentifier.GeoId = HGPO.GeoId; + OutputIdentifier.PartId = HGPO.PartId; + OutputIdentifier.PartName = HGPO.PartName; + + FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = InstancedOutputPartData.Find(OutputIdentifier); + if (InstancedOutputPartDataPtr) + { + InstancedOutputPartDataPtr->BuildFlatInstancedTransformsAndObjectPaths(); + MessageOutput.InstancedOutputPartData.Add(*InstancedOutputPartDataPtr); + } + else + { + MessageOutput.InstancedOutputPartData.Add(FHoudiniInstancedOutputPartData()); + } + } + } + for (const auto& Entry : Output->GetOutputObjects()) + { + HOUDINI_LOG_WARNING(TEXT("Identifier %d %d %d"), Entry.Key.ObjectId, Entry.Key.GeoId, Entry.Key.PartId); + + MessageOutput.OutputObjects.AddDefaulted(); + FHoudiniPDGImportNodeOutputObject& MessageOutputObject = MessageOutput.OutputObjects.Last(); + + FString PackagePath = IsValid(Entry.Value.OutputObject) ? Entry.Value.OutputObject->GetPathName() : ""; + MessageOutputObject.Identifier = Entry.Key; + MessageOutputObject.PackagePath = PackagePath; + const TArray* PropertyAttributes = OutputObjectAttributes.Find(Entry.Key); + if (PropertyAttributes) + MessageOutputObject.GenericAttributes = *PropertyAttributes; + MessageOutputObject.CachedAttributes = Entry.Value.CachedAttributes; + } + } + + PDGEndpoint->Send(Reply, InContext->GetSender()); + } + else + { + HOUDINI_LOG_WARNING(TEXT("BGEO import failed.")); + FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); + Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Failed; + PDGEndpoint->Send(Reply, InContext->GetSender()); + } + + // Cleanup the outputs (remove from root) + TArray PackagesToUnload; + for (UHoudiniOutput *CurOutput : Outputs) + { + if (!IsValid(CurOutput)) + continue; + + for (const auto& Entry : CurOutput->GetOutputObjects()) + { + if (IsValid(Entry.Value.OutputObject)) + { + UPackage *Outermost = Entry.Value.OutputObject->GetOutermost(); + if (IsValid(Outermost)) + { + PackagesToUnload.Add(Outermost); + } + + Entry.Value.OutputObject->RemoveFromRoot(); + } + } + + CurOutput->RemoveFromRoot(); + } + Outputs.Empty(); + OutputObjectAttributes.Empty(); + + if (PackagesToUnload.Num() > 0) + { + HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ..."), PackagesToUnload.Num()); + FText ErrorMessage; + if (!UPackageTools::UnloadPackages(PackagesToUnload, ErrorMessage)) + { + HOUDINI_LOG_WARNING(TEXT("Unload packages failed: %s"), *ErrorMessage.ToString()); + } + else + { + HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ... Success"), PackagesToUnload.Num()); + } + PackagesToUnload.Empty(); + } + + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); +} + +bool UHoudiniGeoImportCommandlet::StartHoudiniEngineSession() +{ + // Start Houdini Engine session + HOUDINI_LOG_DISPLAY(TEXT("Starting Houdini Engine session...")); + FHoudiniEngine& HoudiniEngine = FHoudiniEngine::Get(); + if (!HoudiniEngine.CreateSession( + EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe, + "hapi_bgeo_cmdlet")) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start Houdini Engine session.")); + return false; + } + + return true; +} + +int32 UHoudiniGeoImportCommandlet::ImportBGEO( + const FString &InFilename, + const FHoudiniPackageParams &InPackageParams, + TArray& OutOutputs, + TMap>* OutGenericAttributes, + TMap* OutInstancedOutputPartData) +{ + if (!IsHoudiniEngineSessionRunning() && !StartHoudiniEngineSession()) + { + return 2; + } + + FHoudiniPackageParams PackageParams = InPackageParams; + UHoudiniGeoImporter* GeoImporter = NewObject(this); + + TArray OldOutputs; + OutOutputs.Empty(); + + // 2. Update the file paths + HOUDINI_LOG_DISPLAY(TEXT("SetFilePath %s"), *InFilename); + if (!GeoImporter->SetFilePath(InFilename)) + return 1; + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + HOUDINI_LOG_DISPLAY(TEXT("LoadBGEOFileInHAPI")); + if (!GeoImporter->LoadBGEOFileInHAPI(NodeId)) + return 1; + + // Look for a bake folder override in the BGEO file + if (PackageParams.PackageMode == EPackageMode::Bake) + { + HOUDINI_LOG_DISPLAY(TEXT("Looking for bake folder override attribute...")); + // Get the geo id for the node id + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), NodeId, &DisplayGeoInfo)) + { + TArray BakeFolderOverrideArray; + FString BakeFolderOverride; + const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderAttribute(DisplayGeoInfo.nodeId, HAPI_ATTROWNER_DETAIL,BakeFolderOverrideArray); + if (bFoundOverride && BakeFolderOverrideArray.Num() > 0) + BakeFolderOverride = BakeFolderOverrideArray[0]; + if (!BakeFolderOverride.IsEmpty()) + { + PackageParams.BakeFolder = BakeFolderOverride; + HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override (detail attrib): %s"), *PackageParams.BakeFolder); + } + else + { + HOUDINI_LOG_DISPLAY(TEXT("No bake folder override, using: %s"), *PackageParams.BakeFolder); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could not find display geo node id (when looking for bake folder override).")); + } + } + + auto CleanUpAndExit = [&OutOutputs, GeoImporter, NodeId](int32 InExitCode) + { + GeoImporter->GetOutputObjects().Empty(); + for (UHoudiniOutput* Output : OutOutputs) + { + Output->RemoveFromRoot(); + } + OutOutputs.Empty(); + + if (NodeId >= 0) + GeoImporter->DeleteCreatedNode(NodeId); + + return InExitCode; + }; + + // 4. Get the output from the file node + HOUDINI_LOG_DISPLAY(TEXT("BuildOutputsForNode %d"), NodeId); + if (!GeoImporter->BuildOutputsForNode(NodeId, OldOutputs, OutOutputs)) + return CleanUpAndExit(1); + + // Create uniquely named packages, commandlet runs in conjunction + // with a main editor instance, so we cannot modify existing files + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + // FString PackageName; + // UPackage* Outer = PackageParams.CreatePackageForObject(PackageName); + UObject* Outer = this; + + // 5. Create the static meshes in the outputs + HOUDINI_LOG_DISPLAY(TEXT("Create Static Meshes")); + if (!GeoImporter->CreateStaticMeshes(OutOutputs, Outer, PackageParams)) + return CleanUpAndExit(1); + + //// 6. Create the landscape in the outputs + //if (!GeoImporter->CreateLandscapes(NewOutputs, Outer, PackageParams)) + // return CleanUpAndExit(1); + + // 7. Create the instancers in the outputs + if (OutInstancedOutputPartData) + { + if (!GeoImporter->CreateInstancerOutputPartData(OutOutputs, *OutInstancedOutputPartData)) + return CleanUpAndExit(1); + } + else + { + if (!GeoImporter->CreateInstancers(OutOutputs, Outer, PackageParams)) + return CleanUpAndExit(1); + } + + if (OutGenericAttributes) + { + // Collect all generic properties from Houdini, we need to pass these + // through to PDG manager + HOUDINI_LOG_DISPLAY(TEXT("Get Generic Attributes for static meshes")); + for (UHoudiniOutput* CurOutput : OutOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (const auto& Entry : CurOutput->GetOutputObjects()) + { + const FHoudiniOutputObjectIdentifier OutputIdentifier = Entry.Key; + TArray PropertyAttributes; + FHoudiniEngineUtils::GetGenericPropertiesAttributes( + OutputIdentifier.GeoId, OutputIdentifier.PartId, + true, OutputIdentifier.PrimitiveIndex, INDEX_NONE, OutputIdentifier.PointIndex, + PropertyAttributes); + OutGenericAttributes->Add(OutputIdentifier, PropertyAttributes); + } + } + } + + // 8. Delete the created node in Houdini + HOUDINI_LOG_DISPLAY(TEXT("DeleteCreatedNode %d"), NodeId); + if (!GeoImporter->DeleteCreatedNode(NodeId)) + { + // Not good, but not fatal.. + //return false; + } + + TArray PackagesToSave; + TArray& OutputObjects = GeoImporter->GetOutputObjects(); + for (UObject* Object : OutputObjects) + { + if (!IsValid(Object)) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("Created object: %s"), *Object->GetFullName()); + + UAssetImportData* AssetImportData = nullptr; + if (Object->IsA()) + { + UStaticMesh* SM = Cast(Object); + AssetImportData = SM->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); + SM->AssetImportData = AssetImportData; + } + } + + if (AssetImportData) + AssetImportData->Update(InFilename); + + Object->MarkPackageDirty(); + Object->PostEditChange(); + + UPackage* Package = Object->GetOutermost(); + if (IsValid(Package)) + { + PackagesToSave.AddUnique(Package); + } + } + + if (PackagesToSave.Num() > 0) + { + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); + } + + PackagesToSave.Empty(); + OutputObjects.Empty(); + + return 0; +} + +void UHoudiniGeoImportCommandlet::HandleDirectoryChanged(const TArray& InFileChangeDatas) +{ + const FRegexPattern BGEOPattern(TEXT(R"((.*)\.(bgeo(\.[^\.]*)?)$)")); + + for (const FFileChangeData& FileChangeData : InFileChangeDatas) + { + HOUDINI_LOG_MESSAGE(TEXT("HandleDirectoryChanged %d %s"), FileChangeData.Action, *FileChangeData.Filename); + + FRegexMatcher BGEOMatcher(BGEOPattern, FileChangeData.Filename.ToLower()); + if (BGEOMatcher.FindNext() && BGEOMatcher.GetCaptureGroup(2).StartsWith(TEXT("bgeo"))) + { + HOUDINI_LOG_DISPLAY(TEXT("Updating entry for %s..."), *FileChangeData.Filename); + const uint32 MaxImportAttempts = 3; + switch(FileChangeData.Action) + { + case FFileChangeData::FCA_Added: + case FFileChangeData::FCA_Modified: + if (DiscoveredFiles.Contains(FileChangeData.Filename)) + { + FDiscoveredFileData &FileData = DiscoveredFiles[FileChangeData.Filename]; + if (!FileData.bImported && FileData.ImportAttempts < MaxImportAttempts) + FileData.bImportNextTick = true; + else if (FileData.ImportAttempts >= MaxImportAttempts) + HOUDINI_LOG_WARNING(TEXT("Not importing %s, max attempts exceeded %d"), *FileData.FileName, FileData.ImportAttempts); + } + else + { + DiscoveredFiles.Add(FileChangeData.Filename, FDiscoveredFileData(FileChangeData.Filename, true)); + } + break; + case FFileChangeData::FCA_Removed: + DiscoveredFiles.Remove(FileChangeData.Filename); + break; + default: + HOUDINI_LOG_WARNING(TEXT("Unknown file change event %d for %s"), FileChangeData.Action, *FileChangeData.Filename); + } + } + } +} + +int32 UHoudiniGeoImportCommandlet::Main(const FString& InParams) +{ + TArray Tokens; + TArray Switches; + TMap Params; + ParseCommandLine(*InParams, Tokens, Switches, Params); + + if (Switches.Contains(TEXT("help")) || Switches.Contains(TEXT("?"))) + { + PrintUsage(); + return 0; + } + + if (Params.Contains(TEXT("guid"))) + { + const FString GuidStr = Params.FindChecked(TEXT("guid")); + FGuid::Parse(GuidStr, Guid); + + HOUDINI_LOG_DISPLAY(TEXT("GUID received: %s"), *Guid.ToString()); + } + else + { + Guid = FGuid::NewGuid(); + } + + // Set bake mode + if (Switches.Contains(TEXT("bake"))) + bBakeOutputs = true; + else + bBakeOutputs = false; + + if (Params.Contains(TEXT("listen"))) + { + Mode = EHoudiniGeoImportCommandletMode::Listen; + + if (!Params.Contains(TEXT("managerpid"))) + { + HOUDINI_LOG_ERROR(TEXT("'managerpid' is required when in -listen mode.")); + return 1; + } + + if (bBakeOutputs) + { + HOUDINI_LOG_ERROR(TEXT("'listen' mode does not support baking outputs (-bake).")); + return 1; + } + + // Get the manager's messaging address from the -listen param + const FString ManagerAddressStr = Params.FindChecked(TEXT("listen")); + if (!FMessageAddress::Parse(ManagerAddressStr, ManagerAddress)) + { + HOUDINI_LOG_ERROR(TEXT("The manager messaging address passed to -listen=%s is invalid."), *ManagerAddressStr); + return 1; + } + + // Get the manager pid and proc handle + uint32 OwnerProcessId = FCString::Strtoi(*Params.FindChecked(TEXT("managerpid")), nullptr, 10); + HOUDINI_LOG_DISPLAY(TEXT("Owner process Id: %d"), OwnerProcessId); + OwnerProcHandle = FPlatformProcess::OpenProcess(OwnerProcessId); + + return MainLoop(); + } + else if (Params.Contains(TEXT("watch"))) + { + Mode = EHoudiniGeoImportCommandletMode::Watch; + + HOUDINI_LOG_DISPLAY(TEXT("directory watch mode")); + FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); + IDirectoryWatcher* DirectoryWatcher = DirectoryWatcherModule.Get(); + if (DirectoryWatcher) + { + DirectoryToWatch = Params.FindChecked(TEXT("watch")); + if (FPaths::IsRelative(DirectoryToWatch)) + DirectoryToWatch = FPaths::ConvertRelativePathToFull(DirectoryToWatch); + + HOUDINI_LOG_DISPLAY(TEXT("Watching %s"), *DirectoryToWatch); + + DirectoryWatcher->RegisterDirectoryChangedCallback_Handle( + DirectoryToWatch, + IDirectoryWatcher::FDirectoryChanged::CreateUObject(this, &UHoudiniGeoImportCommandlet::HandleDirectoryChanged), + DirectoryWatcherHandle); + + return MainLoop(); + } + else + { + return 10; + } + } + else if (Tokens.Num() > 0) + { + Mode = EHoudiniGeoImportCommandletMode::SpecifiedFiles; + + if (!StartHoudiniEngineSession()) + return 2; + + const FString Filename = FPaths::IsRelative(Tokens[0]) ? FPaths::ConvertRelativePathToFull(Tokens[0]) : Tokens[0]; + FHoudiniPackageParams PackageParams; + PopulatePackageParams(Filename, PackageParams); + + TArray Outputs; + const int32 Result = ImportBGEO(Tokens[0], PackageParams, Outputs); + + for (UHoudiniOutput* Output : Outputs) + { + Output->RemoveFromRoot(); + } + Outputs.Empty(); + + return Result; + } + + return 0; +} diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h index b6bc33ced..23734611c 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h @@ -1,153 +1,153 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "Commandlets/Commandlet.h" -#include "MessageEndpoint.h" - -#include "HoudiniEngine.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPDGImporterMessages.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniGeoImportCommandlet.generated.h" - -class FSocket; - -class UHoudiniGeoImporter; -class UHoudiniOutput; - -struct FHoudiniPackageParams; - -enum class EHoudiniGeoImportCommandletMode : uint8 -{ - // Unspecified - None, - // Import of specified file - SpecifiedFiles, - // Directory watch mode - Watch, - // Listen mode (via PDGManager) - Listen -}; - -struct FDiscoveredFileData -{ -public: - FDiscoveredFileData() : FileName(), bImportNextTick(false), ImportAttempts(0), bImported(false) {} - - FDiscoveredFileData(const FString& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} - - FDiscoveredFileData(FString&& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} - - // Full/absolute file path - FString FileName; - - // Try to import this file on the next tick - bool bImportNextTick; - - // Number of attempts at importing this file - uint32 ImportAttempts; - - // The file has been imported successfully - bool bImported; -}; - -UCLASS() -class HOUDINIENGINE_API UHoudiniGeoImportCommandlet : public UCommandlet -{ - GENERATED_BODY() - -public: - - UHoudiniGeoImportCommandlet(); - - void PrintUsage() const; - - /** - * Entry point for your commandlet - * - * @param Params the string containing the parameters for the commandlet - */ - virtual int32 Main(const FString& Params) override; - - void HandleImportBGEOMessage( - const struct FHoudiniPDGImportBGEOMessage& InMessage, - const TSharedRef& InContext); - - void HandleDirectoryChanged(const TArray& InFileChangeDatas); - -protected: - - void PopulatePackageParams(const FString& InBGEOFilename, FHoudiniPackageParams& OutPackageParams); - - bool StartHoudiniEngineSession(); - - bool IsHoudiniEngineSessionRunning() { return FHoudiniEngine::Get().GetSession() != nullptr; }; - - int32 MainLoop(); - - int32 ImportBGEO( - const FString& InFilename, - const FHoudiniPackageParams& InPackageParams, - TArray& OutOutputs, - TMap>* OutGenericAttributes=nullptr, - TMap* OutInstancedOutputPartData=nullptr); - - void TickDiscoveredFiles(); - -private: - - // Messaging end point for receiving messages from PDG manager - TSharedPtr PDGEndpoint; - - // The messaging address of the manager - FMessageAddress ManagerAddress; - - // Unique ID of the commandlet. - FGuid Guid; - - // The proc handle of our owner (if in listen mode, quit when the owner stops running). - FProcHandle OwnerProcHandle; - - // TODO: Map so that we can watch multiple directories? - // Directory to watch - FString DirectoryToWatch; - // Handle if we are watching a directory for changes. - FDelegateHandle DirectoryWatcherHandle; - - // Keep track of files discovered by the watcher, and their state - TMap DiscoveredFiles; - - // Mode in which commandlet is running - EHoudiniGeoImportCommandletMode Mode; - - // Bake outputs via FHoudiniEngineBakeUtils - bool bBakeOutputs; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Commandlets/Commandlet.h" +#include "MessageEndpoint.h" + +#include "HoudiniEngine.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPDGImporterMessages.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniGeoImportCommandlet.generated.h" + +class FSocket; + +class UHoudiniGeoImporter; +class UHoudiniOutput; + +struct FHoudiniPackageParams; + +enum class EHoudiniGeoImportCommandletMode : uint8 +{ + // Unspecified + None, + // Import of specified file + SpecifiedFiles, + // Directory watch mode + Watch, + // Listen mode (via PDGManager) + Listen +}; + +struct FDiscoveredFileData +{ +public: + FDiscoveredFileData() : FileName(), bImportNextTick(false), ImportAttempts(0), bImported(false) {} + + FDiscoveredFileData(const FString& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} + + FDiscoveredFileData(FString&& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} + + // Full/absolute file path + FString FileName; + + // Try to import this file on the next tick + bool bImportNextTick; + + // Number of attempts at importing this file + uint32 ImportAttempts; + + // The file has been imported successfully + bool bImported; +}; + +UCLASS() +class HOUDINIENGINE_API UHoudiniGeoImportCommandlet : public UCommandlet +{ + GENERATED_BODY() + +public: + + UHoudiniGeoImportCommandlet(); + + void PrintUsage() const; + + /** + * Entry point for your commandlet + * + * @param Params the string containing the parameters for the commandlet + */ + virtual int32 Main(const FString& Params) override; + + void HandleImportBGEOMessage( + const struct FHoudiniPDGImportBGEOMessage& InMessage, + const TSharedRef& InContext); + + void HandleDirectoryChanged(const TArray& InFileChangeDatas); + +protected: + + void PopulatePackageParams(const FString& InBGEOFilename, FHoudiniPackageParams& OutPackageParams); + + bool StartHoudiniEngineSession(); + + bool IsHoudiniEngineSessionRunning() { return FHoudiniEngine::Get().GetSession() != nullptr; }; + + int32 MainLoop(); + + int32 ImportBGEO( + const FString& InFilename, + const FHoudiniPackageParams& InPackageParams, + TArray& OutOutputs, + TMap>* OutGenericAttributes=nullptr, + TMap* OutInstancedOutputPartData=nullptr); + + void TickDiscoveredFiles(); + +private: + + // Messaging end point for receiving messages from PDG manager + TSharedPtr PDGEndpoint; + + // The messaging address of the manager + FMessageAddress ManagerAddress; + + // Unique ID of the commandlet. + FGuid Guid; + + // The proc handle of our owner (if in listen mode, quit when the owner stops running). + FProcHandle OwnerProcHandle; + + // TODO: Map so that we can watch multiple directories? + // Directory to watch + FString DirectoryToWatch; + // Handle if we are watching a directory for changes. + FDelegateHandle DirectoryWatcherHandle; + + // Keep track of files discovered by the watcher, and their state + TMap DiscoveredFiles; + + // Mode in which commandlet is running + EHoudiniGeoImportCommandletMode Mode; + + // Bake outputs via FHoudiniEngineBakeUtils + bool bBakeOutputs; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp index 8f073376c..a68b6ec68 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp @@ -1,895 +1,895 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoImporter.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniPackageParams.h" -#include "HoudiniOutput.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniSplineComponent.h" - -#include "CoreMinimal.h" -#include "Misc/Paths.h" -#include "Misc/PackageName.h" -#include "UObject/Package.h" -#include "PackageTools.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "Editor.h" - -#include "Materials/MaterialInterface.h" -#include "Materials/Material.h" - - -UHoudiniGeoImporter::UHoudiniGeoImporter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , SourceFilePath() - , AbsoluteFilePath() - , AbsoluteFileDirectory() - , FileName() - , FileExtension() - , BakeRootFolder(TEXT("/Game/HoudiniEngine/Bake/")) -{ - /* - SourceFilePath = FString(); - - AbsoluteFilePath = FString(); - AbsoluteFileDirectory = FString(); - FileName = FString(); - FileExtension = FString(); - - OutputFilename = FString(); - BakeRootFolder = TEXT("/Game/HoudiniEngine/Bake/"); - */ -} - -bool -UHoudiniGeoImporter::SetFilePath(const FString& InFilePath) -{ - SourceFilePath = InFilePath; - if (!FPaths::FileExists(SourceFilePath)) - { - // Cant find BGEO file - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InFilePath); - return false; - } - - // Make sure we're using absolute path! - AbsoluteFilePath = FPaths::ConvertRelativePathToFull(SourceFilePath); - - // Split the file path - FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); - - // Handle .bgeo.sc correctly - if (FileExtension.Equals(TEXT("sc"))) - { - // append the bgeo to .sc - FileExtension = FPaths::GetExtension(FileName) +TEXT(".") + FileExtension; - // update the filename - FileName = FPaths::GetBaseFilename(FileName); - } - - if (FileExtension.IsEmpty()) - FileExtension = TEXT("bgeo"); - - if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) - { - // Not a bgeo file! - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *SourceFilePath); - return false; - } - - //BGEOFilePath = BGEOPath + TEXT("/") + BGEOFileName + TEXT(".") + BGEOExtension; - - // Only use "/" for the output file path - BakeRootFolder.ReplaceInline(TEXT("\\"), TEXT("/")); - // Make sure the output folder ends with a "/" - if (!BakeRootFolder.EndsWith("/")) - BakeRootFolder += TEXT("/"); - - // If we have't specified an outpout file name yet, use the input file name - if (OutputFilename.IsEmpty()) - OutputFilename = FileName; - - return true; -} - -bool -UHoudiniGeoImporter::AutoStartHoudiniEngineSessionIfNeeded() -{ - if (FHoudiniEngine::Get().GetSession()) - return true; - - // Default first session already attempted to be created ? stop here? - /* - if (FHoudiniEngine::Get().GetFirstSessionCreated()) - return false; - */ - - // Indicates that we've tried to start a session once no matter if it failed or succeed - FHoudiniEngine::Get().SetFirstSessionCreated(true); - if (!FHoudiniEngine::Get().RestartSession()) - { - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Couldn't start the default HoudiniEngine session!")); - return false; - } - - return true; -} - -bool -UHoudiniGeoImporter::BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs) -{ - FString Notification = TEXT("BGEO Importer: Getting output geos..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - const bool bInAddOutputsToRootSet = true; - return BuildAllOutputsForNode(InNodeId, this, InOldOutputs, OutNewOutputs, bInAddOutputsToRootSet); -} - -bool -UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - FString Notification = TEXT("BGEO Importer: Creating Static Meshes..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - //FHoudiniMeshTranslator::CreateAllMeshesFromHoudiniOutput(CurOutput, OuterPackage, OuterComponent, OuterAsset); - - TMap NewOutputObjects; - TMap OldOutputObjects = CurOutput->GetOutputObjects(); - TMap& AssignementMaterials = CurOutput->GetAssignementMaterials(); - TMap& ReplacementMaterials = CurOutput->GetReplacementMaterials(); - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - // Not a mesh, skip - if (CurHGPO.Type != EHoudiniPartType::Mesh) - continue; - - // Check for a unreal_output_name if we are in bake mode - FHoudiniPackageParams PackageParams(InPackageParams); - if (PackageParams.PackageMode == EPackageMode::Bake) - { - TArray OutputNames; - if (FHoudiniEngineUtils::GetOutputNameAttribute(CurHGPO.GeoId, CurHGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - PackageParams.ObjectName = OutputNames[0]; - } - } - // Could have prim attribute unreal_bake_folder override - TArray BakeFolderNames; - if (FHoudiniEngineUtils::GetBakeFolderAttribute(CurHGPO.GeoId, BakeFolderNames, CurHGPO.PartId)) - { - if (BakeFolderNames.Num() > 0 && !BakeFolderNames[0].IsEmpty()) - { - PackageParams.BakeFolder = BakeFolderNames[0]; - } - } - } - - FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); - FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); - FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( - CurHGPO, - PackageParams, - OldOutputObjects, - NewOutputObjects, - AssignementMaterials, - ReplacementMaterials, - true, - EHoudiniStaticMeshMethod::RawMesh, - SMGP, - MBS); - } - - // Add all output objects and materials - for (auto CurOutputPair : NewOutputObjects) - { - UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - - // Do the same for materials - for (auto CurAssignmentMatPair : AssignementMaterials) - { - UObject* CurObj = CurAssignmentMatPair.Value; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - - // Also assign to the output objects map as we may need the meshes to create instancers later - CurOutput->SetOutputObjects(NewOutputObjects); - } - - return true; -} - - -bool -UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - TArray CurveOutputs; - CurveOutputs.Reserve(InOutputs.Num()); - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Curve) - continue; - - CurveOutputs.Add(CurOutput); - break; - } - - FString Notification = TEXT("BGEO Importer: Creating Curves..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - // Look for the first unreal_output_name attribute on the curve outputs and use that - // for ObjectName - FHoudiniPackageParams PackageParams(InPackageParams); - for (auto& CurOutput : CurveOutputs) - { - bool bFoundOutputName = false; - bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Curve) - continue; - - if (!bFoundOutputName) - { - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - } - } - } - - if (!bFoundBakeFolder) - { - TArray Strings; - if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.BakeFolder = Strings[0]; - bFoundBakeFolder = true; - } - } - } - - if (bFoundOutputName && bFoundBakeFolder) - break; - } - - if (bFoundOutputName && bFoundBakeFolder) - break; - } - - // Create a Package for the BP - PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - FString PackageName; - UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); - check(BPPackage); - - // Create and init a new Blueprint Actor - UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); - if (!Blueprint) - return false; - - // Create a fake outer component that we'll use as a temporary outer for our curves - UWorld* TempWorld = UWorld::CreateWorld(EWorldType::Inactive, false, TEXT("BGEOImporterTemp"), GetTransientPackage(), false); - const FActorSpawnParameters ActorSpawnParameters; - AActor* OuterActor = TempWorld->SpawnActor(ActorSpawnParameters); - USceneComponent* OuterComponent = - NewObject(OuterActor, USceneComponent::GetDefaultSceneRootVariableName()); - - for (auto& CurOutput : CurveOutputs) - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); - - // Prepare an ActorComponent array for AddComponentsToBlueprint() - TArray OutputComp; - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputComp.Add(CurObj); - } - - // Transfer all the instancer components to the BP - if (OutputComp.Num() > 0) - { - FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; - Params.OptionalNewRootNode = nullptr; - Params.bKeepMobility = false; - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); - } - } - - // Compile the blueprint - FKismetEditorUtilities::CompileBlueprint(Blueprint); - - // Add it to our output objects - OutputObjects.Add(Blueprint); - - return true; -} - - -bool -UHoudiniGeoImporter::CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - HOUDINI_LOG_WARNING(TEXT("Importing a landscape directly from BGEOs is not currently supported.")); - return false; - } - - return true; - - /* - // Before processing any of the output, - // we need to get the min/max value for all Height volumes in this output (if any) - float HoudiniHeightfieldOutputsGlobalMin = 0.f; - float HoudiniHeightfieldOutputsGlobalMax = 0.f; - FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax(InOutputs, HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax); - - UWorld* PersistentWorld = InParent->GetWorld(); - if(!PersistentWorld) - PersistentWorld = GEditor ? GEditor->GetEditorWorldContext(false).World() : nullptr; - - if (!PersistentWorld) - return false; - - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - FString Notification = TEXT("BGEO Importer: Creating Landscapes..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - TArray EmptyInputLandscapes; - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(CurOutput); - - bool bCreatedNewMaps = false; - ERuntimePackageMode RuntimePackageMode = ERuntimePackageMode::CookToTemp; - switch(InPackageParams.PackageMode) - { - - case EPackageMode::Bake: - RuntimePackageMode = ERuntimePackageMode::Bake; - break; - case EPackageMode::CookToLevel: - case EPackageMode::CookToTemp: - default: - RuntimePackageMode = ERuntimePackageMode::CookToTemp; - break; - } - TArray> CreatedUntrackedOutputs; - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - CreatedUntrackedOutputs, - EmptyInputLandscapes, - EmptyInputLandscapes, - HAC, - TEXT("{object_name}_"), - PersistentWorld, - HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax, - InPackageParams, bCreatedNewMaps, - RuntimePackageMode); - - // Add all output objects - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - } - - return true; - */ -} - - -bool -UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - bool HasInstancer = false; - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - HasInstancer = true; - break; - } - - if (!HasInstancer) - return true; - - FString Notification = TEXT("BGEO Importer: Creating Instancers..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - // Look for the first unreal_output_name attribute on the instancer outputs and use that - // for ObjectName - FHoudiniPackageParams PackageParams(InPackageParams); - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - bool bFoundOutputName = false; - bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Instancer) - continue; - - if (!bFoundOutputName) - { - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - break; - } - } - } - - if (!bFoundBakeFolder) - { - TArray Strings; - if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.BakeFolder = Strings[0]; - bFoundBakeFolder = true; - break; - } - } - } - - if (bFoundOutputName && bFoundBakeFolder) - break; - } - - if (bFoundOutputName && bFoundBakeFolder) - break; - } - - // Create a Package for the BP - PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - FString PackageName; - UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); - check(BPPackage); - - // Create and init a new Blueprint Actor - UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); - if (!Blueprint) - return false; - - // Create a fake outer component that we'll use as a temporary outer for our instancers - USceneComponent* OuterComponent = NewObject(); - - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - // Create all the instancers and attach them to a fake outer component - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - CurOutput, InOutputs, OuterComponent); - - // Prepare an ActorComponent array for AddComponentsToBlueprint() - TArray OutputComp; - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputComp.Add(CurObj); - } - - // Transfer all the instancer components to the BP - if (OutputComp.Num() > 0) - { - FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; - Params.OptionalNewRootNode = nullptr; - Params.bKeepMobility = false; - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); - } - } - - // Compile the blueprint - FKismetEditorUtilities::CompileBlueprint(Blueprint); - - // Add it to our output objects - OutputObjects.Add(Blueprint); - - return true; -} - -bool -UHoudiniGeoImporter::CreateInstancerOutputPartData( - TArray& InOutputs, - TMap& OutInstancedOutputPartData) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = HGPO.ObjectId; - OutputIdentifier.GeoId = HGPO.GeoId; - OutputIdentifier.PartId = HGPO.PartId; - OutputIdentifier.PartName = HGPO.PartName; - - OutInstancedOutputPartData.Add(OutputIdentifier, FHoudiniInstancedOutputPartData()); - FHoudiniInstancedOutputPartData *InstancedOutputData = OutInstancedOutputPartData.Find(OutputIdentifier); - // Create all the instancers and attach them to a fake outer component - if (!FHoudiniInstanceTranslator::PopulateInstancedOutputPartData(HGPO, InOutputs, *InstancedOutputData)) - return false; - } - } - - return true; -} - -bool -UHoudiniGeoImporter::DeleteCreatedNode(const HAPI_NodeId& InNodeId) -{ - if (InNodeId < 0) - return false; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), InNodeId)) - { - // Could not delete the bgeo's file sop ! - HOUDINI_LOG_WARNING(TEXT("Houdini GEO Importer: Could not delete HAPI File SOP.")); - return false; - } - - return true; -} - -bool -UHoudiniGeoImporter::ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams) -{ - if (InBGEOFile.IsEmpty()) - return false; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!AutoStartHoudiniEngineSessionIfNeeded()) - return false; - - // 2. Update the file paths - if (!SetFilePath(InBGEOFile)) - return false; - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - if (!LoadBGEOFileInHAPI(NodeId)) - return false; - - // 4. Get the output from the file node - TArray NewOutputs; - TArray OldOutputs; - if (!BuildOutputsForNode(NodeId, OldOutputs, NewOutputs)) - return false; - - // Failure lambda - auto CleanUpAndReturn = [&NewOutputs](const bool& bReturnValue) - { - // Remove the output objects from the root set before returning false - for (auto Out : NewOutputs) - Out->RemoveFromRoot(); - - return bReturnValue; - }; - - // Prepare the package used for creating the mesh, landscape and instancer pacakges - FHoudiniPackageParams PackageParams; - if (InPackageParams) - { - PackageParams = *InPackageParams; - } - else - { - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.HoudiniAssetName = FString(); - PackageParams.HoudiniAssetActorName = FString(); - PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); - } - - if (!PackageParams.OuterPackage) - { - PackageParams.OuterPackage = InParent; - } - - if (!PackageParams.ComponentGUID.IsValid()) - { - // TODO: will need to reuse the GUID when reimporting? - PackageParams.ComponentGUID = FGuid::NewGuid(); - } - - // 5. Create the static meshes in the outputs - if (!CreateStaticMeshes(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 6. Create the static meshes in the outputs - if (!CreateCurves(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 7. Create the landscape in the outputs - if (!CreateLandscapes(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 8. Create the instancers in the outputs - if (!CreateInstancers(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 9. Delete the created node in Houdini - if (!DeleteCreatedNode(NodeId)) - return CleanUpAndReturn(false); - - // Clean up and return true - return CleanUpAndReturn(true); -} - -bool -UHoudiniGeoImporter::OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition) -{ - if (InBGEOFile.IsEmpty()) - return false; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!AutoStartHoudiniEngineSessionIfNeeded()) - return false; - - if (!FPaths::FileExists(InBGEOFile)) - { - // Cant find BGEO file - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InBGEOFile); - return false; - } - - // Make sure we're using absolute path! - const FString AbsoluteFilePath = FPaths::ConvertRelativePathToFull(InBGEOFile); - - if (AbsoluteFilePath.IsEmpty()) - return false; - - FString AbsoluteFileDirectory; - FString FileName; - FString FileExtension; - - // Split the file path - FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); - - // Handle .bgeo.sc correctly - if (FileExtension.Equals(TEXT("sc"))) - { - // append the bgeo to .sc - FileExtension = FPaths::GetExtension(FileName) + TEXT(".") + FileExtension; - // update the filename - FileName = FPaths::GetBaseFilename(FileName); - } - - if (FileExtension.IsEmpty()) - FileExtension = TEXT("bgeo"); - - if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) - { - // Not a bgeo file! - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *InBGEOFile); - return false; - } - - OutNodeId = -1; - - // Check HoudiniEngine / HAPI init? - if (!FHoudiniEngine::IsInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); - return false; - } - - FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); - - // Create a file SOP - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, "SOP/file", "bgeo", true, &OutNodeId), false); - - // Set the file path parameter - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - OutNodeId, "file", &ParmId), false); - - const std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str(), ParmId, 0), false); - - return true; -} - -bool -UHoudiniGeoImporter::CloseBGEOFile(const HAPI_NodeId& InNodeId) -{ - // 8. Delete the created node in Houdini - if (!DeleteCreatedNode(InNodeId)) - return false; - - return true; -} - -bool -UHoudiniGeoImporter::LoadBGEOFileInHAPI(HAPI_NodeId& NodeId) -{ - NodeId = -1; - - if (AbsoluteFilePath.IsEmpty()) - return false; - - // Check HoudiniEngine / HAPI init? - if (!FHoudiniEngine::IsInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); - return false; - } - - FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); - - // Create a file SOP - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, "SOP/file", "bgeo", true, &NodeId), false); - - // Set the file path parameter - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, "file", &ParmId), false); - - std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, 0), false); - - return CookFileNode(NodeId); -} - -bool -UHoudiniGeoImporter::CookFileNode(const HAPI_NodeId& InNodeId) -{ - // Cook the node - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); - - // Wait for the cook to finish - int32 status = HAPI_STATE_MAX_READY_STATE + 1; - while (status > HAPI_STATE_MAX_READY_STATE) - { - // Retrieve the status - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), - HAPI_STATUS_COOK_STATE, &status), false); - - FString StatusString = FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); - HOUDINI_LOG_MESSAGE(TEXT("Still Cooking, current status: %s."), *StatusString); - - // Go to bed.. - if (status > HAPI_STATE_MAX_READY_STATE) - FPlatformProcess::Sleep(0.5f); - } - - if (status != HAPI_STATE_READY) - { - // There was some cook errors - HOUDINI_LOG_ERROR(TEXT("Finished Cooking with errors!")); - return false; - } - - HOUDINI_LOG_MESSAGE(TEXT("Finished Cooking!")); - - return true; -} - -bool -UHoudiniGeoImporter::BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet) -{ - // TArray OldOutputs; - if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, InOldOutputs, OutNewOutputs, false)) - { - // Couldn't create the package - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Failed to process the File SOP's outputs!")); - return false; - } - - if (bInAddOutputsToRootSet) - { - // Add the output objects to the RootSet to prevent them from being GCed - for (auto& Out : OutNewOutputs) - Out->AddToRoot(); - } - - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoImporter.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniPackageParams.h" +#include "HoudiniOutput.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniSplineComponent.h" + +#include "CoreMinimal.h" +#include "Misc/Paths.h" +#include "Misc/PackageName.h" +#include "UObject/Package.h" +#include "PackageTools.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "Editor.h" + +#include "Materials/MaterialInterface.h" +#include "Materials/Material.h" + + +UHoudiniGeoImporter::UHoudiniGeoImporter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , SourceFilePath() + , AbsoluteFilePath() + , AbsoluteFileDirectory() + , FileName() + , FileExtension() + , BakeRootFolder(TEXT("/Game/HoudiniEngine/Bake/")) +{ + /* + SourceFilePath = FString(); + + AbsoluteFilePath = FString(); + AbsoluteFileDirectory = FString(); + FileName = FString(); + FileExtension = FString(); + + OutputFilename = FString(); + BakeRootFolder = TEXT("/Game/HoudiniEngine/Bake/"); + */ +} + +bool +UHoudiniGeoImporter::SetFilePath(const FString& InFilePath) +{ + SourceFilePath = InFilePath; + if (!FPaths::FileExists(SourceFilePath)) + { + // Cant find BGEO file + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InFilePath); + return false; + } + + // Make sure we're using absolute path! + AbsoluteFilePath = FPaths::ConvertRelativePathToFull(SourceFilePath); + + // Split the file path + FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); + + // Handle .bgeo.sc correctly + if (FileExtension.Equals(TEXT("sc"))) + { + // append the bgeo to .sc + FileExtension = FPaths::GetExtension(FileName) +TEXT(".") + FileExtension; + // update the filename + FileName = FPaths::GetBaseFilename(FileName); + } + + if (FileExtension.IsEmpty()) + FileExtension = TEXT("bgeo"); + + if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) + { + // Not a bgeo file! + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *SourceFilePath); + return false; + } + + //BGEOFilePath = BGEOPath + TEXT("/") + BGEOFileName + TEXT(".") + BGEOExtension; + + // Only use "/" for the output file path + BakeRootFolder.ReplaceInline(TEXT("\\"), TEXT("/")); + // Make sure the output folder ends with a "/" + if (!BakeRootFolder.EndsWith("/")) + BakeRootFolder += TEXT("/"); + + // If we have't specified an outpout file name yet, use the input file name + if (OutputFilename.IsEmpty()) + OutputFilename = FileName; + + return true; +} + +bool +UHoudiniGeoImporter::AutoStartHoudiniEngineSessionIfNeeded() +{ + if (FHoudiniEngine::Get().GetSession()) + return true; + + // Default first session already attempted to be created ? stop here? + /* + if (FHoudiniEngine::Get().GetFirstSessionCreated()) + return false; + */ + + // Indicates that we've tried to start a session once no matter if it failed or succeed + FHoudiniEngine::Get().SetFirstSessionCreated(true); + if (!FHoudiniEngine::Get().RestartSession()) + { + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Couldn't start the default HoudiniEngine session!")); + return false; + } + + return true; +} + +bool +UHoudiniGeoImporter::BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs) +{ + FString Notification = TEXT("BGEO Importer: Getting output geos..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + const bool bInAddOutputsToRootSet = true; + return BuildAllOutputsForNode(InNodeId, this, InOldOutputs, OutNewOutputs, bInAddOutputsToRootSet); +} + +bool +UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + FString Notification = TEXT("BGEO Importer: Creating Static Meshes..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + //FHoudiniMeshTranslator::CreateAllMeshesFromHoudiniOutput(CurOutput, OuterPackage, OuterComponent, OuterAsset); + + TMap NewOutputObjects; + TMap OldOutputObjects = CurOutput->GetOutputObjects(); + TMap& AssignementMaterials = CurOutput->GetAssignementMaterials(); + TMap& ReplacementMaterials = CurOutput->GetReplacementMaterials(); + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + // Not a mesh, skip + if (CurHGPO.Type != EHoudiniPartType::Mesh) + continue; + + // Check for a unreal_output_name if we are in bake mode + FHoudiniPackageParams PackageParams(InPackageParams); + if (PackageParams.PackageMode == EPackageMode::Bake) + { + TArray OutputNames; + if (FHoudiniEngineUtils::GetOutputNameAttribute(CurHGPO.GeoId, CurHGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + PackageParams.ObjectName = OutputNames[0]; + } + } + // Could have prim attribute unreal_bake_folder override + TArray BakeFolderNames; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(CurHGPO.GeoId, BakeFolderNames, CurHGPO.PartId)) + { + if (BakeFolderNames.Num() > 0 && !BakeFolderNames[0].IsEmpty()) + { + PackageParams.BakeFolder = BakeFolderNames[0]; + } + } + } + + FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); + FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); + FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( + CurHGPO, + PackageParams, + OldOutputObjects, + NewOutputObjects, + AssignementMaterials, + ReplacementMaterials, + true, + EHoudiniStaticMeshMethod::RawMesh, + SMGP, + MBS); + } + + // Add all output objects and materials + for (auto CurOutputPair : NewOutputObjects) + { + UObject* CurObj = CurOutputPair.Value.OutputObject; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + + // Do the same for materials + for (auto CurAssignmentMatPair : AssignementMaterials) + { + UObject* CurObj = CurAssignmentMatPair.Value; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + + // Also assign to the output objects map as we may need the meshes to create instancers later + CurOutput->SetOutputObjects(NewOutputObjects); + } + + return true; +} + + +bool +UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + TArray CurveOutputs; + CurveOutputs.Reserve(InOutputs.Num()); + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Curve) + continue; + + CurveOutputs.Add(CurOutput); + break; + } + + FString Notification = TEXT("BGEO Importer: Creating Curves..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + // Look for the first unreal_output_name attribute on the curve outputs and use that + // for ObjectName + FHoudiniPackageParams PackageParams(InPackageParams); + for (auto& CurOutput : CurveOutputs) + { + bool bFoundOutputName = false; + bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Curve) + continue; + + if (!bFoundOutputName) + { + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + } + } + } + + if (!bFoundBakeFolder) + { + TArray Strings; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.BakeFolder = Strings[0]; + bFoundBakeFolder = true; + } + } + } + + if (bFoundOutputName && bFoundBakeFolder) + break; + } + + if (bFoundOutputName && bFoundBakeFolder) + break; + } + + // Create a Package for the BP + PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + FString PackageName; + UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); + check(BPPackage); + + // Create and init a new Blueprint Actor + UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); + if (!Blueprint) + return false; + + // Create a fake outer component that we'll use as a temporary outer for our curves + UWorld* TempWorld = UWorld::CreateWorld(EWorldType::Inactive, false, TEXT("BGEOImporterTemp"), GetTransientPackage(), false); + const FActorSpawnParameters ActorSpawnParameters; + AActor* OuterActor = TempWorld->SpawnActor(ActorSpawnParameters); + USceneComponent* OuterComponent = + NewObject(OuterActor, USceneComponent::GetDefaultSceneRootVariableName()); + + for (auto& CurOutput : CurveOutputs) + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); + + // Prepare an ActorComponent array for AddComponentsToBlueprint() + TArray OutputComp; + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputComp.Add(CurObj); + } + + // Transfer all the instancer components to the BP + if (OutputComp.Num() > 0) + { + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; + Params.OptionalNewRootNode = nullptr; + Params.bKeepMobility = false; + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); + } + } + + // Compile the blueprint + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + // Add it to our output objects + OutputObjects.Add(Blueprint); + + return true; +} + + +bool +UHoudiniGeoImporter::CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + HOUDINI_LOG_WARNING(TEXT("Importing a landscape directly from BGEOs is not currently supported.")); + return false; + } + + return true; + + /* + // Before processing any of the output, + // we need to get the min/max value for all Height volumes in this output (if any) + float HoudiniHeightfieldOutputsGlobalMin = 0.f; + float HoudiniHeightfieldOutputsGlobalMax = 0.f; + FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax(InOutputs, HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax); + + UWorld* PersistentWorld = InParent->GetWorld(); + if(!PersistentWorld) + PersistentWorld = GEditor ? GEditor->GetEditorWorldContext(false).World() : nullptr; + + if (!PersistentWorld) + return false; + + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + FString Notification = TEXT("BGEO Importer: Creating Landscapes..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + TArray EmptyInputLandscapes; + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(CurOutput); + + bool bCreatedNewMaps = false; + ERuntimePackageMode RuntimePackageMode = ERuntimePackageMode::CookToTemp; + switch(InPackageParams.PackageMode) + { + + case EPackageMode::Bake: + RuntimePackageMode = ERuntimePackageMode::Bake; + break; + case EPackageMode::CookToLevel: + case EPackageMode::CookToTemp: + default: + RuntimePackageMode = ERuntimePackageMode::CookToTemp; + break; + } + TArray> CreatedUntrackedOutputs; + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + CreatedUntrackedOutputs, + EmptyInputLandscapes, + EmptyInputLandscapes, + HAC, + TEXT("{object_name}_"), + PersistentWorld, + HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax, + InPackageParams, bCreatedNewMaps, + RuntimePackageMode); + + // Add all output objects + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UObject* CurObj = CurOutputPair.Value.OutputObject; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + } + + return true; + */ +} + + +bool +UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + bool HasInstancer = false; + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + HasInstancer = true; + break; + } + + if (!HasInstancer) + return true; + + FString Notification = TEXT("BGEO Importer: Creating Instancers..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + // Look for the first unreal_output_name attribute on the instancer outputs and use that + // for ObjectName + FHoudiniPackageParams PackageParams(InPackageParams); + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + bool bFoundOutputName = false; + bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Instancer) + continue; + + if (!bFoundOutputName) + { + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + break; + } + } + } + + if (!bFoundBakeFolder) + { + TArray Strings; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.BakeFolder = Strings[0]; + bFoundBakeFolder = true; + break; + } + } + } + + if (bFoundOutputName && bFoundBakeFolder) + break; + } + + if (bFoundOutputName && bFoundBakeFolder) + break; + } + + // Create a Package for the BP + PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + FString PackageName; + UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); + check(BPPackage); + + // Create and init a new Blueprint Actor + UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); + if (!Blueprint) + return false; + + // Create a fake outer component that we'll use as a temporary outer for our instancers + USceneComponent* OuterComponent = NewObject(); + + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + // Create all the instancers and attach them to a fake outer component + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + CurOutput, InOutputs, OuterComponent); + + // Prepare an ActorComponent array for AddComponentsToBlueprint() + TArray OutputComp; + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputComp.Add(CurObj); + } + + // Transfer all the instancer components to the BP + if (OutputComp.Num() > 0) + { + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; + Params.OptionalNewRootNode = nullptr; + Params.bKeepMobility = false; + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); + } + } + + // Compile the blueprint + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + // Add it to our output objects + OutputObjects.Add(Blueprint); + + return true; +} + +bool +UHoudiniGeoImporter::CreateInstancerOutputPartData( + TArray& InOutputs, + TMap& OutInstancedOutputPartData) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = HGPO.ObjectId; + OutputIdentifier.GeoId = HGPO.GeoId; + OutputIdentifier.PartId = HGPO.PartId; + OutputIdentifier.PartName = HGPO.PartName; + + OutInstancedOutputPartData.Add(OutputIdentifier, FHoudiniInstancedOutputPartData()); + FHoudiniInstancedOutputPartData *InstancedOutputData = OutInstancedOutputPartData.Find(OutputIdentifier); + // Create all the instancers and attach them to a fake outer component + if (!FHoudiniInstanceTranslator::PopulateInstancedOutputPartData(HGPO, InOutputs, *InstancedOutputData)) + return false; + } + } + + return true; +} + +bool +UHoudiniGeoImporter::DeleteCreatedNode(const HAPI_NodeId& InNodeId) +{ + if (InNodeId < 0) + return false; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), InNodeId)) + { + // Could not delete the bgeo's file sop ! + HOUDINI_LOG_WARNING(TEXT("Houdini GEO Importer: Could not delete HAPI File SOP.")); + return false; + } + + return true; +} + +bool +UHoudiniGeoImporter::ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams) +{ + if (InBGEOFile.IsEmpty()) + return false; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!AutoStartHoudiniEngineSessionIfNeeded()) + return false; + + // 2. Update the file paths + if (!SetFilePath(InBGEOFile)) + return false; + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + if (!LoadBGEOFileInHAPI(NodeId)) + return false; + + // 4. Get the output from the file node + TArray NewOutputs; + TArray OldOutputs; + if (!BuildOutputsForNode(NodeId, OldOutputs, NewOutputs)) + return false; + + // Failure lambda + auto CleanUpAndReturn = [&NewOutputs](const bool& bReturnValue) + { + // Remove the output objects from the root set before returning false + for (auto Out : NewOutputs) + Out->RemoveFromRoot(); + + return bReturnValue; + }; + + // Prepare the package used for creating the mesh, landscape and instancer pacakges + FHoudiniPackageParams PackageParams; + if (InPackageParams) + { + PackageParams = *InPackageParams; + } + else + { + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.HoudiniAssetName = FString(); + PackageParams.HoudiniAssetActorName = FString(); + PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); + } + + if (!PackageParams.OuterPackage) + { + PackageParams.OuterPackage = InParent; + } + + if (!PackageParams.ComponentGUID.IsValid()) + { + // TODO: will need to reuse the GUID when reimporting? + PackageParams.ComponentGUID = FGuid::NewGuid(); + } + + // 5. Create the static meshes in the outputs + if (!CreateStaticMeshes(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 6. Create the static meshes in the outputs + if (!CreateCurves(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 7. Create the landscape in the outputs + if (!CreateLandscapes(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 8. Create the instancers in the outputs + if (!CreateInstancers(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 9. Delete the created node in Houdini + if (!DeleteCreatedNode(NodeId)) + return CleanUpAndReturn(false); + + // Clean up and return true + return CleanUpAndReturn(true); +} + +bool +UHoudiniGeoImporter::OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition) +{ + if (InBGEOFile.IsEmpty()) + return false; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!AutoStartHoudiniEngineSessionIfNeeded()) + return false; + + if (!FPaths::FileExists(InBGEOFile)) + { + // Cant find BGEO file + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InBGEOFile); + return false; + } + + // Make sure we're using absolute path! + const FString AbsoluteFilePath = FPaths::ConvertRelativePathToFull(InBGEOFile); + + if (AbsoluteFilePath.IsEmpty()) + return false; + + FString AbsoluteFileDirectory; + FString FileName; + FString FileExtension; + + // Split the file path + FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); + + // Handle .bgeo.sc correctly + if (FileExtension.Equals(TEXT("sc"))) + { + // append the bgeo to .sc + FileExtension = FPaths::GetExtension(FileName) + TEXT(".") + FileExtension; + // update the filename + FileName = FPaths::GetBaseFilename(FileName); + } + + if (FileExtension.IsEmpty()) + FileExtension = TEXT("bgeo"); + + if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) + { + // Not a bgeo file! + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *InBGEOFile); + return false; + } + + OutNodeId = -1; + + // Check HoudiniEngine / HAPI init? + if (!FHoudiniEngine::IsInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); + return false; + } + + FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); + + // Create a file SOP + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, "SOP/file", "bgeo", true, &OutNodeId), false); + + // Set the file path parameter + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + OutNodeId, "file", &ParmId), false); + + const std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str(), ParmId, 0), false); + + return true; +} + +bool +UHoudiniGeoImporter::CloseBGEOFile(const HAPI_NodeId& InNodeId) +{ + // 8. Delete the created node in Houdini + if (!DeleteCreatedNode(InNodeId)) + return false; + + return true; +} + +bool +UHoudiniGeoImporter::LoadBGEOFileInHAPI(HAPI_NodeId& NodeId) +{ + NodeId = -1; + + if (AbsoluteFilePath.IsEmpty()) + return false; + + // Check HoudiniEngine / HAPI init? + if (!FHoudiniEngine::IsInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); + return false; + } + + FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); + + // Create a file SOP + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, "SOP/file", "bgeo", true, &NodeId), false); + + // Set the file path parameter + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, "file", &ParmId), false); + + std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, 0), false); + + return CookFileNode(NodeId); +} + +bool +UHoudiniGeoImporter::CookFileNode(const HAPI_NodeId& InNodeId) +{ + // Cook the node + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); + + // Wait for the cook to finish + int32 status = HAPI_STATE_MAX_READY_STATE + 1; + while (status > HAPI_STATE_MAX_READY_STATE) + { + // Retrieve the status + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), + HAPI_STATUS_COOK_STATE, &status), false); + + FString StatusString = FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); + HOUDINI_LOG_MESSAGE(TEXT("Still Cooking, current status: %s."), *StatusString); + + // Go to bed.. + if (status > HAPI_STATE_MAX_READY_STATE) + FPlatformProcess::Sleep(0.5f); + } + + if (status != HAPI_STATE_READY) + { + // There was some cook errors + HOUDINI_LOG_ERROR(TEXT("Finished Cooking with errors!")); + return false; + } + + HOUDINI_LOG_MESSAGE(TEXT("Finished Cooking!")); + + return true; +} + +bool +UHoudiniGeoImporter::BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet) +{ + // TArray OldOutputs; + if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, InOldOutputs, OutNewOutputs, false)) + { + // Couldn't create the package + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Failed to process the File SOP's outputs!")); + return false; + } + + if (bInAddOutputsToRootSet) + { + // Add the output objects to the RootSet to prevent them from being GCed + for (auto& Out : OutNewOutputs) + Out->AddToRoot(); + } + + return true; +} diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.h b/Source/HoudiniEngine/Private/HoudiniGeoImporter.h index 0ca0a5965..33ed94820 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.h +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.h @@ -1,122 +1,122 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniGeoImporter.generated.h" - -class UHoudiniOutput; - -struct FHoudiniPackageParams; - -UCLASS() -class HOUDINIENGINE_API UHoudiniGeoImporter : public UObject -{ -public: - - GENERATED_UCLASS_BODY() - - public: - //UHoudiniGeoImporter(); - //~UHoudiniGeoImporter(); - - void SetBakeRootFolder(const FString& InFolder) { BakeRootFolder = InFolder; }; - void SetOutputFilename(const FString& InOutFilename) { OutputFilename = InOutFilename; }; - - TArray& GetOutputObjects() { return OutputObjects; }; - - // BEGIN: Static API - // Open a BGEO file: create a file node in HAPI and cook it - static bool OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition=false); - // Cook the file node specified by the valid NodeId. - static bool CookFileNode(const HAPI_NodeId& InNodeId); - // Extract the outputs for a given node ID - static bool BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet=false); - // Delete the HAPI node and remove InOutputs from the root set. - static bool CloseBGEOFile(const HAPI_NodeId& InNodeId); - // END: Static API - - // Import the BGEO file - bool ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams=nullptr); - - // 1. Start a HE session if needed - static bool AutoStartHoudiniEngineSessionIfNeeded(); - // 2. Update our file members fromn the input file path - bool SetFilePath(const FString& InFilePath); - // 3. Creates a new file node and loads the bgeo file in HAPI - bool LoadBGEOFileInHAPI(HAPI_NodeId& NodeId); - // 4. Extract the outputs for a given node ID - bool BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs); - // 5. Creates the static meshes object found in the output - bool CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 6. Create the output curves - bool CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 7. Create the output landscapes - bool CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 8. Create the output instancers - bool CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 9. Clean up the created node - static bool DeleteCreatedNode(const HAPI_NodeId& InNodeId); - - static bool CreateInstancerOutputPartData( - TArray& InOutputs, - TMap& OutInstancedOutputPartData); - - private: - - // - // Input file - // - // Path how the file we're currently loading - FString SourceFilePath; - // Absolute Path to the file - FString AbsoluteFilePath; - FString AbsoluteFileDirectory; - // File Name / Extension - FString FileName; - FString FileExtension; - - - // - // Output file - // - // Output filename, if empty, will be set to the input filename - FString OutputFilename; - // Root Folder for storing the created files - FString BakeRootFolder; - - // - // Output Objects - // - TArray OutputObjects; - - //TArray OutputStaticMeshes; - //TArray OutputLandscapes; - //TArray OutputInstancers; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniGeoImporter.generated.h" + +class UHoudiniOutput; + +struct FHoudiniPackageParams; + +UCLASS() +class HOUDINIENGINE_API UHoudiniGeoImporter : public UObject +{ +public: + + GENERATED_UCLASS_BODY() + + public: + //UHoudiniGeoImporter(); + //~UHoudiniGeoImporter(); + + void SetBakeRootFolder(const FString& InFolder) { BakeRootFolder = InFolder; }; + void SetOutputFilename(const FString& InOutFilename) { OutputFilename = InOutFilename; }; + + TArray& GetOutputObjects() { return OutputObjects; }; + + // BEGIN: Static API + // Open a BGEO file: create a file node in HAPI and cook it + static bool OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition=false); + // Cook the file node specified by the valid NodeId. + static bool CookFileNode(const HAPI_NodeId& InNodeId); + // Extract the outputs for a given node ID + static bool BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet=false); + // Delete the HAPI node and remove InOutputs from the root set. + static bool CloseBGEOFile(const HAPI_NodeId& InNodeId); + // END: Static API + + // Import the BGEO file + bool ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams=nullptr); + + // 1. Start a HE session if needed + static bool AutoStartHoudiniEngineSessionIfNeeded(); + // 2. Update our file members fromn the input file path + bool SetFilePath(const FString& InFilePath); + // 3. Creates a new file node and loads the bgeo file in HAPI + bool LoadBGEOFileInHAPI(HAPI_NodeId& NodeId); + // 4. Extract the outputs for a given node ID + bool BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs); + // 5. Creates the static meshes object found in the output + bool CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 6. Create the output curves + bool CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 7. Create the output landscapes + bool CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 8. Create the output instancers + bool CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 9. Clean up the created node + static bool DeleteCreatedNode(const HAPI_NodeId& InNodeId); + + static bool CreateInstancerOutputPartData( + TArray& InOutputs, + TMap& OutInstancedOutputPartData); + + private: + + // + // Input file + // + // Path how the file we're currently loading + FString SourceFilePath; + // Absolute Path to the file + FString AbsoluteFilePath; + FString AbsoluteFileDirectory; + // File Name / Extension + FString FileName; + FString FileExtension; + + + // + // Output file + // + // Output filename, if empty, will be set to the input filename + FString OutputFilename; + // Root Folder for storing the created files + FString BakeRootFolder; + + // + // Output Objects + // + TArray OutputObjects; + + //TArray OutputStaticMeshes; + //TArray OutputLandscapes; + //TArray OutputInstancers; + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp index 0c559f453..b50e986b6 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp @@ -1,370 +1,370 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniHandleComponent.h" - - -bool -FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray NewHandles; - - if (FHoudiniHandleTranslator::BuildAllHandles(HAC->GetAssetId(), HAC, HAC->HandleComponents, NewHandles)) - { - - HAC->HandleComponents = NewHandles; - - } - - return true; -} - -bool -FHoudiniHandleTranslator::BuildAllHandles( - const HAPI_NodeId& AssetId, - UHoudiniAssetComponent* OuterObject, - TArray& CurrentHandles, - TArray& NewHandles) -{ - if (AssetId < 0) - return false; - - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo)) - return false; - - TMap CurrentHandlesByName; - - for (auto& Handle : CurrentHandles) - { - if (!Handle) - continue; - - CurrentHandlesByName.Add(Handle->GetHandleName(), Handle); - } - - // If there are handles - if (AssetInfo.handleCount > 0) - { - TArray 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) - { - 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"), - OuterObject ? *(OuterObject->GetName()) : TEXT("?"), *TypeName, *HandleName); - continue; - } - - UHoudiniHandleComponent* HandleComponent = nullptr; - UHoudiniHandleComponent** FoundHandleComponent = CurrentHandlesByName.Find(HandleName); - if (FoundHandleComponent) - { - HandleComponent = *FoundHandleComponent; - - CurrentHandles.Remove(*FoundHandleComponent); - CurrentHandlesByName.Remove(HandleName); - } - else - { - HandleComponent = NewObject( - OuterObject, - UHoudiniHandleComponent::StaticClass(), - NAME_None, RF_Public | RF_Transactional); - - HandleComponent->SetHandleName(HandleName); - HandleComponent->SetHandleType(HandleType); - - // Change the creation method so the component is listed in the details panels - HandleComponent->CreationMethod = EComponentCreationMethod::Instance; - } - - if (!HandleComponent) - continue; - - // If we have no parent, we need to re-attach. - if (!HandleComponent->GetAttachParent()) - { - HandleComponent->AttachToComponent(OuterObject, FAttachmentTransformRules::KeepRelativeTransform); - } - - HandleComponent->SetVisibility(true); - - // If component is not registered, register it - if (!HandleComponent->IsRegistered()) - HandleComponent->RegisterComponent(); - - // Get handle's bindings - TArray BindingInfos; - BindingInfos.SetNumZeroed(HandleInfo.bindingsCount); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetHandleBindingInfo(FHoudiniEngine::Get().GetSession(), - AssetId, HandleIdx, &BindingInfos[0], 0, HandleInfo.bindingsCount)) - continue; - - HAPI_TransformEuler HapiEulerXform; - FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); - HapiEulerXform.position[0] = HapiEulerXform.position[1] = HapiEulerXform.position[2] = 0.0f; - HapiEulerXform.rotationEuler[0] = HapiEulerXform.rotationEuler[1] = HapiEulerXform.rotationEuler[2] = 0.0f; - HapiEulerXform.scale[0] = HapiEulerXform.scale[1] = HapiEulerXform.scale[2] = 1.0f; - - TSharedPtr< FString > RSTOrderStrPtr, XYZOrderStrPtr; - - for (const auto& BindingInfo : BindingInfos) - { - FString HandleParmName = TEXT(""); - FHoudiniEngineString HoudiniEngineString(BindingInfo.handleParmNameSH); - HoudiniEngineString.ToFString(HandleParmName); - - const HAPI_ParmId ParamId = BindingInfo.assetParmId; - - TArray &XformParms = HandleComponent->XformParms; - - UHoudiniParameter* FoundParam = nullptr; - - for (auto Param : OuterObject->Parameters) - { - if (Param->GetParmId() == ParamId) - { - FoundParam = Param; - break; - } - } - - HandleComponent->InitializeHandleParameters(); - - if (!HandleComponent->CheckHandleValid()) - continue; - - (void)(XformParms[int32(EXformParameter::TX)]->Bind(HapiEulerXform.position[0], "tx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::TY)]->Bind(HapiEulerXform.position[1], "ty", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::TZ)]->Bind(HapiEulerXform.position[2], "tz", 2, HandleParmName, FoundParam) - - || XformParms[int32(EXformParameter::RX)]->Bind(HapiEulerXform.rotationEuler[0], "rx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::RY)]->Bind(HapiEulerXform.rotationEuler[1], "ry", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::RZ)]->Bind(HapiEulerXform.rotationEuler[2], "rz", 2, HandleParmName, FoundParam) - - || XformParms[int32(EXformParameter::SX)]->Bind(HapiEulerXform.scale[0], "sx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::SY)]->Bind(HapiEulerXform.scale[1], "sy", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::SZ)]->Bind(HapiEulerXform.scale[2], "sz", 2, HandleParmName, FoundParam) - - || HandleComponent->RSTParm->Bind(RSTOrderStrPtr, "trs_order", 0, HandleParmName, FoundParam) - || HandleComponent->RotOrderParm->Bind(XYZOrderStrPtr, "xyz_order", 0, HandleParmName, FoundParam) - ); - } - - HapiEulerXform.rstOrder = GetHapiRSTOrder(RSTOrderStrPtr); - HapiEulerXform.rotationOrder = GetHapiXYZOrder(XYZOrderStrPtr); - - constexpr float MaxFloat = TNumericLimits::Max(); - constexpr float MinFloat = TNumericLimits::Min(); - - HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); - HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); - HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); - - FTransform UnrealXform; - FHoudiniEngineUtils::TranslateHapiTransform(HapiEulerXform, UnrealXform); - - //HandleComponent->SetRelativeTransform(UnrealXform); - - NewHandles.Add(HandleComponent); - - } - } - - return true; -} - - -void -FHoudiniHandleTranslator::ClearHandles(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - for (auto& HandleComponent : HAC->HandleComponents) - { - if (!HandleComponent) - continue; - - HandleComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - HandleComponent->UnregisterComponent(); - HandleComponent->DestroyComponent(); - } - - HAC->HandleComponents.Empty(); -} - -HAPI_RSTOrder -FHoudiniHandleTranslator::GetHapiRSTOrder(const TSharedPtr & StrPtr) -{ - if (StrPtr.Get()) - { - FString & Str = *StrPtr; - - if (Str == "trs") return HAPI_TRS; - else if (Str == "tsr") return HAPI_TSR; - else if (Str == "rts") return HAPI_RTS; - else if (Str == "rst") return HAPI_RST; - else if (Str == "str") return HAPI_STR; - else if (Str == "srt") return HAPI_SRT; - } - - return HAPI_SRT; -} - -HAPI_XYZOrder -FHoudiniHandleTranslator::GetHapiXYZOrder(const TSharedPtr & StrPtr) -{ - if (StrPtr.Get()) - { - FString & Str = *StrPtr; - - if (Str == "xyz") return HAPI_XYZ; - else if (Str == "xzy") return HAPI_XZY; - else if (Str == "yxz") return HAPI_YXZ; - else if (Str == "yzx") return HAPI_YZX; - else if (Str == "zxy") return HAPI_ZXY; - else if (Str == "zyx") return HAPI_ZYX; - } - - return HAPI_XYZ; -} - - -void -FHoudiniHandleTranslator::UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent) -{ - if (!HandleComponent || HandleComponent->IsPendingKill()) - return; - - if (!HandleComponent->CheckHandleValid()) - return; - - TArray &XformParms = HandleComponent->XformParms; - - if (XformParms.Num() < (int32)EXformParameter::COUNT) - return; - - HAPI_Transform HapiXform; - FMemory::Memzero< HAPI_Transform >(HapiXform); - FHoudiniEngineUtils::TranslateUnrealTransform(HandleComponent->GetRelativeTransform(), HapiXform); - - const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); - - float HapiMatrix[16]; - FHoudiniApi::ConvertTransformQuatToMatrix(Session, &HapiXform, HapiMatrix); - - HAPI_TransformEuler HapiEulerXform; - FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); - FHoudiniApi::ConvertMatrixToEuler( - Session, - HapiMatrix, - GetHapiRSTOrder(HandleComponent->RSTParm->Get(TSharedPtr< FString >())), - GetHapiXYZOrder(HandleComponent->RotOrderParm->Get(TSharedPtr< FString >())), - &HapiEulerXform - ); - - *XformParms[int32(EXformParameter::TX)] = HapiEulerXform.position[0]; - *XformParms[int32(EXformParameter::TY)] = HapiEulerXform.position[1]; - *XformParms[int32(EXformParameter::TZ)] = HapiEulerXform.position[2]; - - *XformParms[int32(EXformParameter::RX)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[0]); - *XformParms[int32(EXformParameter::RY)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[1]); - *XformParms[int32(EXformParameter::RZ)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[2]); - - constexpr float MaxFloat = TNumericLimits::Max(); - constexpr float MinFloat = TNumericLimits::Min(); - HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); - HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); - HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); - - *XformParms[int32(EXformParameter::SX)] = HapiEulerXform.scale[0]; - *XformParms[int32(EXformParameter::SY)] = HapiEulerXform.scale[1]; - *XformParms[int32(EXformParameter::SZ)] = HapiEulerXform.scale[2]; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniHandleComponent.h" + + +bool +FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray NewHandles; + + if (FHoudiniHandleTranslator::BuildAllHandles(HAC->GetAssetId(), HAC, HAC->HandleComponents, NewHandles)) + { + + HAC->HandleComponents = NewHandles; + + } + + return true; +} + +bool +FHoudiniHandleTranslator::BuildAllHandles( + const HAPI_NodeId& AssetId, + UHoudiniAssetComponent* OuterObject, + TArray& CurrentHandles, + TArray& NewHandles) +{ + if (AssetId < 0) + return false; + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo)) + return false; + + TMap CurrentHandlesByName; + + for (auto& Handle : CurrentHandles) + { + if (!Handle) + continue; + + CurrentHandlesByName.Add(Handle->GetHandleName(), Handle); + } + + // If there are handles + if (AssetInfo.handleCount > 0) + { + TArray 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) + { + 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"), + OuterObject ? *(OuterObject->GetName()) : TEXT("?"), *TypeName, *HandleName); + continue; + } + + UHoudiniHandleComponent* HandleComponent = nullptr; + UHoudiniHandleComponent** FoundHandleComponent = CurrentHandlesByName.Find(HandleName); + if (FoundHandleComponent) + { + HandleComponent = *FoundHandleComponent; + + CurrentHandles.Remove(*FoundHandleComponent); + CurrentHandlesByName.Remove(HandleName); + } + else + { + HandleComponent = NewObject( + OuterObject, + UHoudiniHandleComponent::StaticClass(), + NAME_None, RF_Public | RF_Transactional); + + HandleComponent->SetHandleName(HandleName); + HandleComponent->SetHandleType(HandleType); + + // Change the creation method so the component is listed in the details panels + HandleComponent->CreationMethod = EComponentCreationMethod::Instance; + } + + if (!HandleComponent) + continue; + + // If we have no parent, we need to re-attach. + if (!HandleComponent->GetAttachParent()) + { + HandleComponent->AttachToComponent(OuterObject, FAttachmentTransformRules::KeepRelativeTransform); + } + + HandleComponent->SetVisibility(true); + + // If component is not registered, register it + if (!HandleComponent->IsRegistered()) + HandleComponent->RegisterComponent(); + + // Get handle's bindings + TArray BindingInfos; + BindingInfos.SetNumZeroed(HandleInfo.bindingsCount); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetHandleBindingInfo(FHoudiniEngine::Get().GetSession(), + AssetId, HandleIdx, &BindingInfos[0], 0, HandleInfo.bindingsCount)) + continue; + + HAPI_TransformEuler HapiEulerXform; + FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); + HapiEulerXform.position[0] = HapiEulerXform.position[1] = HapiEulerXform.position[2] = 0.0f; + HapiEulerXform.rotationEuler[0] = HapiEulerXform.rotationEuler[1] = HapiEulerXform.rotationEuler[2] = 0.0f; + HapiEulerXform.scale[0] = HapiEulerXform.scale[1] = HapiEulerXform.scale[2] = 1.0f; + + TSharedPtr< FString > RSTOrderStrPtr, XYZOrderStrPtr; + + for (const auto& BindingInfo : BindingInfos) + { + FString HandleParmName = TEXT(""); + FHoudiniEngineString HoudiniEngineString(BindingInfo.handleParmNameSH); + HoudiniEngineString.ToFString(HandleParmName); + + const HAPI_ParmId ParamId = BindingInfo.assetParmId; + + TArray &XformParms = HandleComponent->XformParms; + + UHoudiniParameter* FoundParam = nullptr; + + for (auto Param : OuterObject->Parameters) + { + if (Param->GetParmId() == ParamId) + { + FoundParam = Param; + break; + } + } + + HandleComponent->InitializeHandleParameters(); + + if (!HandleComponent->CheckHandleValid()) + continue; + + (void)(XformParms[int32(EXformParameter::TX)]->Bind(HapiEulerXform.position[0], "tx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::TY)]->Bind(HapiEulerXform.position[1], "ty", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::TZ)]->Bind(HapiEulerXform.position[2], "tz", 2, HandleParmName, FoundParam) + + || XformParms[int32(EXformParameter::RX)]->Bind(HapiEulerXform.rotationEuler[0], "rx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::RY)]->Bind(HapiEulerXform.rotationEuler[1], "ry", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::RZ)]->Bind(HapiEulerXform.rotationEuler[2], "rz", 2, HandleParmName, FoundParam) + + || XformParms[int32(EXformParameter::SX)]->Bind(HapiEulerXform.scale[0], "sx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::SY)]->Bind(HapiEulerXform.scale[1], "sy", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::SZ)]->Bind(HapiEulerXform.scale[2], "sz", 2, HandleParmName, FoundParam) + + || HandleComponent->RSTParm->Bind(RSTOrderStrPtr, "trs_order", 0, HandleParmName, FoundParam) + || HandleComponent->RotOrderParm->Bind(XYZOrderStrPtr, "xyz_order", 0, HandleParmName, FoundParam) + ); + } + + HapiEulerXform.rstOrder = GetHapiRSTOrder(RSTOrderStrPtr); + HapiEulerXform.rotationOrder = GetHapiXYZOrder(XYZOrderStrPtr); + + constexpr float MaxFloat = TNumericLimits::Max(); + constexpr float MinFloat = TNumericLimits::Min(); + + HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); + HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); + HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); + + FTransform UnrealXform; + FHoudiniEngineUtils::TranslateHapiTransform(HapiEulerXform, UnrealXform); + + //HandleComponent->SetRelativeTransform(UnrealXform); + + NewHandles.Add(HandleComponent); + + } + } + + return true; +} + + +void +FHoudiniHandleTranslator::ClearHandles(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + for (auto& HandleComponent : HAC->HandleComponents) + { + if (!HandleComponent) + continue; + + HandleComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + HandleComponent->UnregisterComponent(); + HandleComponent->DestroyComponent(); + } + + HAC->HandleComponents.Empty(); +} + +HAPI_RSTOrder +FHoudiniHandleTranslator::GetHapiRSTOrder(const TSharedPtr & StrPtr) +{ + if (StrPtr.Get()) + { + FString & Str = *StrPtr; + + if (Str == "trs") return HAPI_TRS; + else if (Str == "tsr") return HAPI_TSR; + else if (Str == "rts") return HAPI_RTS; + else if (Str == "rst") return HAPI_RST; + else if (Str == "str") return HAPI_STR; + else if (Str == "srt") return HAPI_SRT; + } + + return HAPI_SRT; +} + +HAPI_XYZOrder +FHoudiniHandleTranslator::GetHapiXYZOrder(const TSharedPtr & StrPtr) +{ + if (StrPtr.Get()) + { + FString & Str = *StrPtr; + + if (Str == "xyz") return HAPI_XYZ; + else if (Str == "xzy") return HAPI_XZY; + else if (Str == "yxz") return HAPI_YXZ; + else if (Str == "yzx") return HAPI_YZX; + else if (Str == "zxy") return HAPI_ZXY; + else if (Str == "zyx") return HAPI_ZYX; + } + + return HAPI_XYZ; +} + + +void +FHoudiniHandleTranslator::UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent) +{ + if (!HandleComponent || HandleComponent->IsPendingKill()) + return; + + if (!HandleComponent->CheckHandleValid()) + return; + + TArray &XformParms = HandleComponent->XformParms; + + if (XformParms.Num() < (int32)EXformParameter::COUNT) + return; + + HAPI_Transform HapiXform; + FMemory::Memzero< HAPI_Transform >(HapiXform); + FHoudiniEngineUtils::TranslateUnrealTransform(HandleComponent->GetRelativeTransform(), HapiXform); + + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + + float HapiMatrix[16]; + FHoudiniApi::ConvertTransformQuatToMatrix(Session, &HapiXform, HapiMatrix); + + HAPI_TransformEuler HapiEulerXform; + FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); + FHoudiniApi::ConvertMatrixToEuler( + Session, + HapiMatrix, + GetHapiRSTOrder(HandleComponent->RSTParm->Get(TSharedPtr< FString >())), + GetHapiXYZOrder(HandleComponent->RotOrderParm->Get(TSharedPtr< FString >())), + &HapiEulerXform + ); + + *XformParms[int32(EXformParameter::TX)] = HapiEulerXform.position[0]; + *XformParms[int32(EXformParameter::TY)] = HapiEulerXform.position[1]; + *XformParms[int32(EXformParameter::TZ)] = HapiEulerXform.position[2]; + + *XformParms[int32(EXformParameter::RX)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[0]); + *XformParms[int32(EXformParameter::RY)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[1]); + *XformParms[int32(EXformParameter::RZ)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[2]); + + constexpr float MaxFloat = TNumericLimits::Max(); + constexpr float MinFloat = TNumericLimits::Min(); + HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); + HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); + HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); + + *XformParms[int32(EXformParameter::SX)] = HapiEulerXform.scale[0]; + *XformParms[int32(EXformParameter::SY)] = HapiEulerXform.scale[1]; + *XformParms[int32(EXformParameter::SZ)] = HapiEulerXform.scale[2]; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h index 93bf71fbe..f362bd7e9 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h @@ -1,55 +1,55 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniApi.h" - -#include "Templates/SharedPointer.h" - -class UHoudiniAssetComponent; -class UHoudiniHandleComponent; - -struct HOUDINIENGINE_API FHoudiniHandleTranslator -{ - static bool UpdateHandles(UHoudiniAssetComponent* HAC); - - - static bool BuildAllHandles(const HAPI_NodeId& AssetId, - UHoudiniAssetComponent* OuterObject, - TArray& CurrentHandles, - TArray& NewHandles); - - static void ClearHandles(UHoudiniAssetComponent* HAC); - - static HAPI_RSTOrder GetHapiRSTOrder(const TSharedPtr & StrPtr); - - static HAPI_XYZOrder GetHapiXYZOrder(const TSharedPtr & StrPtr); - - static void UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniApi.h" + +#include "Templates/SharedPointer.h" + +class UHoudiniAssetComponent; +class UHoudiniHandleComponent; + +struct HOUDINIENGINE_API FHoudiniHandleTranslator +{ + static bool UpdateHandles(UHoudiniAssetComponent* HAC); + + + static bool BuildAllHandles(const HAPI_NodeId& AssetId, + UHoudiniAssetComponent* OuterObject, + TArray& CurrentHandles, + TArray& NewHandles); + + static void ClearHandles(UHoudiniAssetComponent* HAC); + + static HAPI_RSTOrder GetHapiRSTOrder(const TSharedPtr & StrPtr); + + static HAPI_XYZOrder GetHapiXYZOrder(const TSharedPtr & StrPtr); + + static void UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp index 1f8b55ca8..d2ad57df6 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp @@ -1,3045 +1,3047 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInputTranslator.h" - -#include "HoudiniInput.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInputObject.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniAssetActor.h" -#include "HoudiniOutputTranslator.h" -#include "UnrealBrushTranslator.h" -#include "UnrealSplineTranslator.h" -#include "UnrealMeshTranslator.h" -#include "UnrealInstanceTranslator.h" -#include "UnrealLandscapeTranslator.h" -#include "UnrealFoliageTypeTranslator.h" - -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "Engine/Brush.h" -#include "Engine/DataTable.h" -#include "Camera/CameraComponent.h" -#include "FoliageType_InstancedStaticMesh.h" - -#include "Engine/SimpleConstructionScript.h" -#include "Engine/SCS_Node.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Editor/UnrealEdEngine.h" - #include "UnrealEdGlobals.h" -#endif - -#include "HCsgUtils.h" - -#include "Async/Async.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#if WITH_EDITOR -// Allows checking of objects currently being dragged around -struct FHoudiniMoveTracker -{ - FHoudiniMoveTracker() : IsObjectMoving(false) - { - GEditor->OnBeginObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = true; }); - GEditor->OnEndObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - - GEditor->OnBeginCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - GEditor->OnEndCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - } - static FHoudiniMoveTracker& Get() { static FHoudiniMoveTracker Instance; return Instance; } - - bool IsObjectMoving; -}; -#endif - -// -bool -FHoudiniInputTranslator::UpdateInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) - { - // Failed to create the inputs - return false; - } - - return true; -} - -bool -FHoudiniInputTranslator::BuildAllInputs( - const HAPI_NodeId& AssetId, - class UObject* InOuterObject, - TArray& Inputs, - TArray& Parameters) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Start by getting the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - // Get the number of geo (SOP) inputs - int32 InputCount = AssetInfo.geoInputCount; - /* - // It's best to update the input count even if the hda hasnt cooked - // as it can cause loaded geo inputs to disappear upon loading the level - if ( AssetInfo.hasEverCooked ) - { - InputCount = AssetInfo.geoInputCount; - } - */ - // Also look for object path parameters inputs - TArray> InputParameters; - for (auto Param : Parameters) - { - if (Param->GetParameterType() == EHoudiniParameterType::Input) - InputParameters.Add(Param); - } - - InputCount += InputParameters.Num(); - - // Append new inputs as needed - if (InputCount > Inputs.Num()) - { - int32 NumNewInputs = InputCount - Inputs.Num(); - for (int32 InputIdx = Inputs.Num(); InputIdx < InputCount; ++InputIdx) - { - FString InputObjectName = TEXT("Input") + FString::FromInt(InputIdx + 1); - UHoudiniInput * NewInput = NewObject< UHoudiniInput >( - InOuterObject, - UHoudiniInput::StaticClass(), - FName(*InputObjectName), - RF_Transactional); - - if (!NewInput || NewInput->IsPendingKill()) - { - //HOUDINI_LOG_WARNING("Failed to create asset input"); - continue; - } - // Create a default curve object here to avoid Transaction issue - //NewInput->CreateDefaultCurveInputObject(); - - Inputs.Add(NewInput); - } - } - else if (InputCount < Inputs.Num()) - { - // TODO: Properly clean up the input object + created nodes? - for (int32 InputIdx = Inputs.Num() - 1; InputIdx >= InputCount; InputIdx--) - { - UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - FHoudiniInputTranslator::DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); - - // DO NOT MANUALLY DESTROY THE OLD/DANGLING INPUTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - //CurrentInput->ConditionalBeginDestroy(); - //CurrentInput = nullptr; - } - - Inputs.SetNum(InputCount); - } - - // Now, check the inputs in the array match the geo inputs - //for (int32 GeoInIdx = 0; GeoInIdx < AssetInfo.geoInputCount; GeoInIdx++) - bool bBlueprintStructureChanged = false; - for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) - { - UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // Create default Name/Label/Help - FString CurrentInputName = TEXT("Input") + FString::FromInt(InputIdx + 1); - FString CurrentInputLabel = CurrentInputName; - FString CurrentInputHelp; - - // Set the nodeId - CurrentInput->SetAssetNodeId(AssetId); - - // Is this an object path parameter input? - bool bIsObjectPath = InputIdx >= AssetInfo.geoInputCount; - if (!bIsObjectPath) - { - // Mark this input as a SOP input - CurrentInput->SetSOPInput(InputIdx); - - // Get and set the name - HAPI_StringHandle InputStringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInputName( - FHoudiniEngine::Get().GetSession(), - AssetId, InputIdx, &InputStringHandle)) - { - FHoudiniEngineString HoudiniEngineString(InputStringHandle); - HoudiniEngineString.ToFString(CurrentInputLabel); - } - } - else - { - // Get this input's parameter index in the objpath param array - int32 CurrentParmIdx = InputIdx - AssetInfo.geoInputCount; - - UHoudiniParameter* CurrentParm = nullptr; - if (InputParameters.IsValidIndex(CurrentParmIdx)) - { - if (InputParameters[CurrentParmIdx].IsValid()) - CurrentParm = InputParameters[CurrentParmIdx].Get(); - } - - int32 ParmId = -1; - if (CurrentParm && !CurrentParm->IsPendingKill()) - { - ParmId = CurrentParm->GetParmId(); - CurrentInputName = CurrentParm->GetParameterName(); - CurrentInputLabel = CurrentParm->GetParameterLabel(); - CurrentInputHelp = CurrentParm->GetParameterHelp(); - } - - UHoudiniParameterOperatorPath* CurrentObjPathParm = Cast(CurrentParm); - if (CurrentObjPathParm && !CurrentObjPathParm->IsPendingKill()) - { - CurrentObjPathParm->HoudiniInput = CurrentInput; - } - - // Mark this input as an object path parameter input - CurrentInput->SetObjectPathParameter(ParmId); - } - - CurrentInput->SetName(CurrentInputName); - CurrentInput->SetLabel(CurrentInputLabel); - - if ( CurrentInputHelp.IsEmpty() ) - { - CurrentInputHelp = CurrentInputLabel + TEXT("(") + CurrentInputName + TEXT(")"); - } - CurrentInput->SetHelp(CurrentInputHelp); - - // If the input type is invalid, - // We need to initialize its default - if (CurrentInput->GetInputType() == EHoudiniInputType::Invalid) - { - // Initialize it to the default corresponding to its name - CurrentInput->SetInputType(GetDefaultInputTypeFromLabel(CurrentInputLabel), bBlueprintStructureChanged); - - // Preset the default HDA for objpath input - SetDefaultAssetFromHDA(CurrentInput, bBlueprintStructureChanged); - } - - // Update input objects data on UE side for all types of inputs. - switch (CurrentInput->GetInputType()) - { - case EHoudiniInputType::Curve: - FHoudiniSplineTranslator::UpdateHoudiniInputCurves(CurrentInput); - break; - case EHoudiniInputType::Landscape: - //FUnrealLandscapeTranslator::UpdateHoudiniInputLandscapes(CurrentInput); - break; - case EHoudiniInputType::Asset: - break; - case EHoudiniInputType::Geometry: - break; - case EHoudiniInputType::Skeletal: - break; - case EHoudiniInputType::World: - break; - default: - break; - } - } - - return true; -} - -bool -FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - if (!InputToDestroy || InputToDestroy->IsPendingKill()) - return false; - - // Start by disconnecting the input / nullifying the object path parameter - if (InputToDestroy->IsObjectPathParameter()) - { - // Just set the objpath parameter to null - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - InputToDestroy->GetAssetNodeId(), "", - InputToDestroy->GetParameterId(), 0); - } - else - { - // Get the asset / created input node ID - HAPI_NodeId HostAssetId = InputToDestroy->GetAssetNodeId(); - HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); - - // Only disconnect if both are valid - if (HostAssetId >= 0 && CreatedInputId >= 0) - { - FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), - HostAssetId, InputToDestroy->GetInputIndex()); - } - } - - if (InputType == EHoudiniInputType::Asset) - { - // TODO: - // If we're an asset input, just remove us from the downstream connection on the input HDA - // then reset this input's flag - - // TODO: Check this? Clean our DS assets?? why?? likely uneeded - UHoudiniAssetComponent* OuterHAC = Cast(InputToDestroy->GetOuter()); - if (OuterHAC) - OuterHAC->ClearDownstreamHoudiniAsset(); - - InputToDestroy->SetInputNodeId(-1); - } - - return true; -} - -bool -FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - if (!InputToDestroy || InputToDestroy->IsPendingKill()) - return false; - - if (!InputToDestroy->CanDeleteHoudiniNodes()) - return false; - - // If we're destroying an asset input, don't destroy anything as we don't want to destroy the input HDA - // a simple disconnect is sufficient - if (InputType == EHoudiniInputType::Asset) - return true; - - // Destroy the nodes created by all the input objects - TArray CreatedInputDataAssetIds = InputToDestroy->GetCreatedDataNodeIds(); - TArray* InputObjectNodes = InputToDestroy->GetHoudiniInputObjectArray(InputType); - if (InputObjectNodes) - { - for (auto CurInputObject : *InputObjectNodes) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - if (CurInputObject->Type == EHoudiniInputObjectType::HoudiniAssetComponent) - { - // Remove this input object's node Id from the - // CreatedInputDataAssetIds array to avoid its deletion further down - CreatedInputDataAssetIds.Remove(CurInputObject->InputNodeId); - CurInputObject->InputNodeId = -1; - CurInputObject->InputObjectNodeId = -1; - continue; - } - - // For Actor input objects, set the input node id for all component objects to -1, - if (CurInputObject->Type == EHoudiniInputObjectType::Actor) - { - UHoudiniInputActor* CurActorInputObject = Cast(CurInputObject); - if (CurActorInputObject) - { - for (auto & CurActorComponent : CurActorInputObject->GetActorComponents()) - { - if (!CurActorComponent || CurActorComponent->IsPendingKill()) - continue; - - // No need to delete the nodes created for an asset component manually here, - // As they will be deleted when we clean up the CreateNodeIds array - CurActorComponent->InputNodeId = -1; - } - } - } - // No need to delete the nodes created for an asset component manually here, - // As they will be deleted when we clean up the CreateNodeIds array - - if (CurInputObject->InputNodeId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputNodeId); - CurInputObject->InputNodeId = -1; - } - - if(CurInputObject->InputObjectNodeId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputObjectNodeId); - CurInputObject->InputObjectNodeId = -1; - - // TODO: CHECK ME! - HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CurInputObject->InputObjectNodeId); - - // Delete its parent node as well - if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentNodeId)) - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentNodeId); - } - - // Also directly invalidate HoudiniSplineComponent's node IDs. - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast(CurInputObject); - if (IsValid(HoudiniSplineInputObject) && !IsGarbageCollecting()) - { - UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (SplineComponent && !SplineComponent->IsPendingKill()) - { - SplineComponent->SetNodeId(-1); - } - } - - CurInputObject->MarkChanged(true); - } - } - - // Destroy all the input assets - for (HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds) - { - if (AssetNodeId < 0) - continue; - - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), AssetNodeId); - } - CreatedInputDataAssetIds.Empty(); - - // Then simply destroy the input's parent OBJ node - if (InputToDestroy->GetInputNodeId() >= 0) - { - HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedInputId); - - if (CreatedInputId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CreatedInputId); - InputToDestroy->SetInputNodeId(-1); - } - - if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentId)) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentId); - } - } - - return true; -} - -bool -FHoudiniInputTranslator::DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - // Start by disconnecting the input/object merge - bool bSuccess = DisconnectInput(InputToDestroy, InputType); - - // Then destroy the created input nodes - bSuccess &= DestroyInputNodes(InputToDestroy, InputType); - - return bSuccess; -} - - -EHoudiniInputType -FHoudiniInputTranslator::GetDefaultInputTypeFromLabel(const FString& InputName) -{ - // We'll try to find these magic words to try to detect the default input type - //FString geoPrefix = TEXT("geo"); - FString curvePrefix = TEXT("curve"); - - FString landscapePrefix = TEXT("landscape"); - FString landscapePrefix2 = TEXT("terrain"); - FString landscapePrefix3 = TEXT("heightfield"); - - FString worldPrefix = TEXT("world"); - FString worldPrefix2 = TEXT("outliner"); - - FString assetPrefix = TEXT("asset"); - FString assetPrefix2 = TEXT("hda"); - - // By default, geometry input is chosen. - EHoudiniInputType InputType = EHoudiniInputType::Geometry; - - if (InputName.Contains(curvePrefix, ESearchCase::IgnoreCase)) - InputType = EHoudiniInputType::Curve; - - else if ((InputName.Contains(landscapePrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(landscapePrefix2, ESearchCase::IgnoreCase)) - || (InputName.Contains(landscapePrefix3, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::Landscape; - - else if ((InputName.Contains(worldPrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(worldPrefix2, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::World; - - else if ((InputName.Contains(assetPrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(assetPrefix2, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::Asset; - - return InputType; -} - -bool -FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bForce) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (!InInput->HasInputTypeChanged() && !bForce) - return true; - - // - Handle switching AWAY from an input type - DisconnectAndDestroyInput(InInput, InInput->GetPreviousInputType()); - - // Invalidate the previous input type now that we've actually changed - //InInput->SetPreviousInputType(EHoudiniInputType::Invalid); - - //ChangeInputType(InInput, NewType); - - // TODO: - // - Handle updating to the new input type - // downstream asset connection, static mesh update, curve creation... - - // Mark all the objects from this input has changed so they upload themselves - InInput->MarkAllInputObjectsChanged(true); - - return true; -} - -bool -FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified) -{ - // - if (!Input || Input->IsPendingKill()) - return false; - - // Make sure we're linked to a valid object path parameter - if (Input->GetParameterId() < 0) - return false; - - // Get our ParmInfo - HAPI_ParmInfo FoundParamInfo; - FHoudiniApi::ParmInfo_Init(&FoundParamInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - Input->GetAssetNodeId(), Input->GetParameterId(), &FoundParamInfo)) - { - return false; - } - - // Get our string value - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - Input->GetAssetNodeId(), - false, - &StringHandle, - FoundParamInfo.stringValuesIndex, - 1)) - { - return false; - } - - FString ParamValue; - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (!HoudiniEngineString.ToFString(ParamValue)) - { - return false; - } - - if (ParamValue.Len() <= 0) - { - return false; - } - - // Chop the default value using semi-colons as separators - TArray Tokens; - ParamValue.ParseIntoArray(Tokens, TEXT(";"), true); - - // Start by setting geometry input objects - int32 GeoIdx = 0; - for (auto& CurToken : Tokens) - { - if (CurToken.IsEmpty()) - continue; - - // Set default objects on the HDA instance - will override the parameter string - // and apply the object input local-path thing for the HDA cook. - UObject * pObject = LoadObject(nullptr, *CurToken); - if (!pObject) - continue; - - Input->SetInputObjectAt(EHoudiniInputType::Geometry, GeoIdx++, pObject); - } - - // See if we can preset world objects as well - int32 WorldIdx = 0; - int32 LandscapedIdx = 0; - int32 HDAIdx = 0; - for (TActorIterator ActorIt(Input->GetWorld(), AActor::StaticClass(), EActorIteratorFlags::SkipPendingKill); ActorIt; ++ActorIt) - { - AActor* CurActor = *ActorIt; - if (!CurActor) - continue; - - AActor* FoundActor = nullptr; - int32 FoundIdx = Tokens.Find(CurActor->GetFName().ToString()); - if (FoundIdx == INDEX_NONE) - FoundIdx = Tokens.Find(CurActor->GetActorLabel()); - - if(FoundIdx != INDEX_NONE) - FoundActor = CurActor; - - if (!FoundActor) - continue; - - // Select the found actor in the world input - Input->SetInputObjectAt(EHoudiniInputType::World, WorldIdx++, FoundActor); - - if (FoundActor->IsA()) - { - // Select the HDA in the asset input - Input->SetInputObjectAt(EHoudiniInputType::Asset, HDAIdx++, FoundActor); - } - else if (FoundActor->IsA()) - { - // Select the landscape in the landscape input - Input->SetInputObjectAt(EHoudiniInputType::Landscape, LandscapedIdx++, FoundActor); - } - - // Remove the Found Token - Tokens.RemoveAt(FoundIdx); - } - - // See if we should change the default input type - if (Input->GetInputType() == EHoudiniInputType::Geometry && WorldIdx > 0 && GeoIdx == 0) - { - if (LandscapedIdx == WorldIdx) - { - // We've only selected landscapes, set to landscape IN - Input->SetInputType(EHoudiniInputType::Landscape, bOutBlueprintStructureModified); - } - else if (HDAIdx == WorldIdx) - { - // We've only selected Houdini Assets, set to Asset IN - Input->SetInputType(EHoudiniInputType::Asset, bOutBlueprintStructureModified); - } - else - { - // Set to world input - Input->SetInputType(EHoudiniInputType::World, bOutBlueprintStructureModified); - } - } - - return true; -} - -bool -FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - //for (auto CurrentInput : HAC->Inputs) - for(int32 InputIdx = 0; InputIdx < HAC->GetNumInputs(); InputIdx++) - { - UHoudiniInput*& CurrentInput = HAC->Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill() || !CurrentInput->HasChanged()) - continue; - - // First thing, see if we need to change the input type - if (CurrentInput->HasInputTypeChanged()) - { - ChangeInputType(CurrentInput, false); - } - - if (CurrentInput->GetInputType() == EHoudiniInputType::Landscape && CurrentInput->HasLandscapeExportTypeChanged()) - { - DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); - CurrentInput->MarkAllInputObjectsChanged(true); - CurrentInput->SetHasLandscapeExportTypeChanged(false); - } - - bool bSuccess = true; - if (CurrentInput->IsDataUploadNeeded()) - { - bSuccess &= UploadInputData(CurrentInput); - CurrentInput->MarkDataUploadNeeded(!bSuccess); - } - - if (CurrentInput->IsTransformUploadNeeded()) - { - bSuccess &= UploadInputTransform(CurrentInput); - } - - // Update the input properties AFTER eventually uploading it - bSuccess = UpdateInputProperties(CurrentInput); - - if (bSuccess) - { - CurrentInput->MarkChanged(false); - CurrentInput->MarkAllInputObjectsChanged(false); - } - - if (CurrentInput->HasInputTypeChanged()) - CurrentInput->SetPreviousInputType(EHoudiniInputType::Invalid); - - // Even if we failed, no need to try updating again. - CurrentInput->SetNeedsToTriggerUpdate(false); - } - - return true; -} - -bool -FHoudiniInputTranslator::UpdateInputProperties(UHoudiniInput* InInput) -{ - bool bSucess = UpdateTransformType(InInput); - - bSucess &= UpdatePackBeforeMerge(InInput); - - bSucess &= UpdateTransformOffset(InInput); - - return bSucess; -} - -bool -FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - bool nTransformType = InInput->GetKeepWorldTransform(); - - // Geometry inputs are always set to none - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType == EHoudiniInputType::Geometry) - nTransformType = 0; - - // Get the Input node ID from the host ID - HAPI_NodeId InputNodeId = -1; - HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); - - bool bSuccess = true; - const std::string sXformType = "xformtype"; - if (InInput->IsObjectPathParameter()) - { - // Directly change the Parameter xformtype - // (This will only work if the object merge is editable/unlocked) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), - HostAssetId, sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - else - { - // Query the object merge's node ID via the input - if (HAPI_RESULT_SUCCESS == FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - HostAssetId, InInput->GetInputIndex(), &InputNodeId)) - { - // Change its Parameter xformtype - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), - InputNodeId, sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - } - - // Since our input objects are all plugged into a merge node - // We want to also update the transform type on the object merge plugged into the merge node - HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); - if ((ParentNodeId >= 0) && (InputType != EHoudiniInputType::Geometry) && (InputType != EHoudiniInputType::Asset)) - { - HAPI_NodeId InputObjectNodeId = -1; - int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); - for (int n = 0; n < NumberOfInputMeshes; n++) - { - // Get the Input node ID from the host ID - InputObjectNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ParentNodeId, n, &InputObjectNodeId)) - continue; - - if (InputObjectNodeId == -1) - continue; - - // Change the xformtype parameter on the object merge - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), InputObjectNodeId, - sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - // Pack before merge is only available for Geo/World input - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType != EHoudiniInputType::World - && InputType != EHoudiniInputType::Geometry) - { - // Nothing to change - return true; - } - - uint32 nPackValue = InInput->GetPackBeforeMerge() ? 1 : 0; - - // Get the Input node ID from the host ID - HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); - - bool bSuccess = true; - const std::string sPack = "pack"; - - // We'll be going through each input object plugged in the input's merge node - // and change the pack parameter there - HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); - if (ParentNodeId >= 0) - { - HAPI_NodeId InputObjectNodeId = -1; - int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); - for (int n = 0; n < NumberOfInputMeshes; n++) - { - // Get the Input node ID from the host ID - InputObjectNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ParentNodeId, n, &InputObjectNodeId)) - continue; - - if (InputObjectNodeId == -1) - continue; - - // Change the pack parameter on the object merge - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), InputObjectNodeId, - sPack.c_str(), 0, nPackValue)) - bSuccess = false; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - // Transform offsets are only for geometry inputs - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType != EHoudiniInputType::Geometry) - { - // Nothing to change - return true; - } - - // Get the input objects - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Update each object's transform offset - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - // If the Input mesh has a Transform offset - FTransform TransformOffset = CurrentInputObject->Transform; - if (!TransformOffset.Equals(FTransform::Identity)) - { - // Updating the Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CurrentInputObject->InputObjectNodeId, &HapiTransform), false); - } - } - - return true; -} - -bool -FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - EHoudiniInputType InputType = InInput->GetInputType(); - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Iterate on all the input objects and see if they need to be uploaded - bool bSuccess = true; - TArray CreatedNodeIds; - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; - if (!CurrentInputObject->HasChanged() && CurrentInputObjectNodeId >= 0) - { - // If this object hasn't changed, no need to upload it - // but we need to keep its created input node - if (CurrentInputObject->Type == EHoudiniInputObjectType::Actor) - { - // If this input object is an actor, it actually contains other input - // objects for each of his components, keep them as well - UHoudiniInputActor* InputActor = Cast(CurrentInputObject); - if (InputActor && !InputActor->IsPendingKill()) - { - for (auto CurrentComp : InputActor->GetActorComponents()) - { - if (!CurrentComp || CurrentComp->IsPendingKill()) - continue; - - int32& CurrentCompNodeId = CurrentComp->InputObjectNodeId; - if (!CurrentComp->HasChanged() && CurrentCompNodeId >= 0) - { - // If the component hasnt changed and is valid, keep it - CreatedNodeIds.Add(CurrentCompNodeId); - } - else - { - // Upload the component input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentComp, CreatedNodeIds)) - bSuccess = false; - } - } - } - } - else - { - // No changes, keep it - CreatedNodeIds.Add(CurrentInputObjectNodeId); - } - } - else - { - // Upload the current input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentInputObject, CreatedNodeIds)) - bSuccess = false; - } - } - - // If we haven't created any input, invalidate our input node id - if (CreatedNodeIds.Num() == 0) - { - if (!InInput->HasInputTypeChanged()) - { - int32 InputNodeId = InInput->GetInputNodeId(); - TArray PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); - - if (InInput->GetInputType() == EHoudiniInputType::Asset) - { - UHoudiniAssetComponent * OuterHAC = Cast(InInput->GetOuter()); - HAPI_NodeId AssetId = OuterHAC->GetAssetId(); - - // Disconnect the asset input - if (InputNodeId >= 0 && InInput->GetInputIndex() >= 0) - { - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), AssetId, InInput->GetInputIndex())); - } - } - else if (InInput->GetInputType() == EHoudiniInputType::World) - { - // World nodes are handled by InputObjects () (with FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete) - } - else - { - if (InputNodeId >= 0) - { - for (int32 Idx = 0; Idx < PreviousInputObjectNodeIds.Num(); Idx++) - { - - // Get the object merge connected to the merge node - HAPI_NodeId InputObjectMergeId = -1; - HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); - - // Disconnect the two nodes - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); - - // Destroy the object merge node, do not delete other HDA (Asset input type) - HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); - } - } - } - } - InInput->GetCreatedDataNodeIds().Empty(); - InInput->SetInputNodeId(-1); - return bSuccess; - } - - // Get the current input's NodeId - HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); - // Check that the current input's node ID is still valid - if (InputNodeId < 0 || !FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - { - // This input doesn't have a valid NodeId yet, - // we need to create this input's merge node and update this input's node ID - FString MergeName = InInput->GetNodeBaseName() + TEXT("_Merge"); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/merge"), MergeName, true, &InputNodeId), false); - - InInput->SetInputNodeId(InputNodeId); - } - - //TODO: - // Do we want to update the input's transform? - if (false) - { - FTransform ComponentTransform = FTransform::Identity; - USceneComponent* OuterComp = Cast(InInput->GetOuter()); - if (OuterComp && !OuterComp->IsPendingKill()) - ComponentTransform = OuterComp->GetComponentTransform(); - - FHoudiniEngineUtils::HapiSetAssetTransform(InputNodeId, ComponentTransform); - //HapiUpdateInputNodeTransform(InputNodeId, ComponentTransform); - } - - // Connect all the input objects to the merge node now - int32 InputIndex = 0; - for (auto CurrentNodeId : CreatedNodeIds) - { - if (CurrentNodeId < 0) - continue; - - if (InputNodeId == CurrentNodeId) - continue; - - // Connect the current input object to the merge node - HOUDINI_CHECK_ERROR(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - InputNodeId, InputIndex++, CurrentNodeId, 0)); - } - - // Check if we need to disconnect extra input objects nodes from the merge - // This can be needed when the input had more input objects on the previous cook - TArray& PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); - if (!InInput->HasInputTypeChanged()) - { - for (int32 Idx = CreatedNodeIds.Num(); Idx < PreviousInputObjectNodeIds.Num(); Idx++) - { - // Get the object merge connected to the merge node - HAPI_NodeId InputObjectMergeId = -1; - if (InInput->GetInputType() != EHoudiniInputType::Asset) - HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); - - // Disconnect the two nodes - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); - - // Destroy the object merge node, do not destroy other HDA (Asset input type) - if (InInput->GetInputType() != EHoudiniInputType::Asset) - { - HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); - } - } - } - - // Keep track of all the nodes plugged into our input's merge - PreviousInputObjectNodeIds = CreatedNodeIds; - - // Finally, connect our main input node to the asset - bSuccess = ConnectInputNode(InInput); - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - EHoudiniInputType InputType = InInput->GetInputType(); - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Iterate on all the input objects and see if their transform needs to be uploaded - bool bSuccess = true; - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; - if (!CurrentInputObject->HasTransformChanged()) - continue; - - // Upload the current input object's transform to Houdini - if (!UploadHoudiniInputTransform(InInput, CurrentInputObject)) - { - bSuccess = false; - continue; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::ConnectInputNode(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - HAPI_NodeId AssetNodeId = InInput->GetAssetNodeId(); - if (AssetNodeId < 0) - return false; - - HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); - if (InputNodeId < 0) - return false; - - // Helper for connecting our input or setting the object path parameter - if (InInput->IsObjectPathParameter()) - { - // Now we can assign the input node path to the parameter - std::string ParamNameString = TCHAR_TO_UTF8(*(InInput->GetName())); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue( - FHoudiniEngine::Get().GetSession(), AssetNodeId, - ParamNameString.c_str(), InputNodeId), false); - } - else - { - // TODO: CHECK ME! - //if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - // return false; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), AssetNodeId, - InInput->GetInputIndex(), InputNodeId, 0), false); - } - - return true; -} - -bool -FHoudiniInputTranslator::UploadHoudiniInputObject( - UHoudiniInput* InInput, - UHoudiniInputObject* InInputObject, - TArray& OutCreatedNodeIds) -{ - if (!InInput || !InInputObject) - return false; - - FString ObjBaseName = InInput->GetNodeBaseName(); - - bool bSuccess = true; - switch (InInputObject->Type) - { - case EHoudiniInputObjectType::Object: - { - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForObject(ObjBaseName, InInputObject); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::StaticMesh: - { - UHoudiniInputStaticMesh* InputSM = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( - ObjBaseName, InputSM, InInput->GetExportLODs(), InInput->GetExportSockets(), - InInput->GetExportColliders(), InInput->GetImportAsReference()); - - if (bSuccess) - { - // If this SM input object takes in a BP, add all its BP StaticMesh components input object node id to the created id list. - if (InputSM->bIsBlueprint()) - { - for (auto & CurSMObj : InputSM->BlueprintStaticMeshes) - OutCreatedNodeIds.Add(CurSMObj->InputObjectNodeId); - } - else - { - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - } - } - - break; - } - - case EHoudiniInputObjectType::SkeletalMesh: - { - UHoudiniInputSkeletalMesh* InputSkelMes = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(ObjBaseName, InputSkelMes); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::SceneComponent: - { - UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(ObjBaseName, InputSceneComp); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::StaticMeshComponent: - { - UHoudiniInputMeshComponent* InputSMC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( - ObjBaseName, InputSMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders(), InInput->GetImportAsReference()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - { - UHoudiniInputInstancedMeshComponent* InputISMC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( - ObjBaseName, InputISMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::SplineComponent: - { - UHoudiniInputSplineComponent* InputSpline = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(ObjBaseName, InputSpline, InInput->GetUnrealSplineResolution()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::HoudiniSplineComponent: - { - UHoudiniInputHoudiniSplineComponent* InputCurve = Cast(InInputObject); - - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve, InInput->IsAddRotAndScaleAttributesEnabled()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::HoudiniAssetActor: - case EHoudiniInputObjectType::HoudiniAssetComponent: - { - UHoudiniInputHoudiniAsset* InputHAC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetImportAsReference()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Actor: - { - UHoudiniInputActor* InputActor = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, OutCreatedNodeIds); - break; - } - - case EHoudiniInputObjectType::Landscape: - { - UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForLandscape(ObjBaseName, InputLandscape, InInput); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Brush: - { - UHoudiniInputBrush* InputBrush = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForBrush(ObjBaseName, InputBrush, InInput->GetBoundSelectorObjectArray()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::CameraComponent: - { - UHoudiniInputCameraComponent* InputCamera = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForCamera(ObjBaseName, InputCamera); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::DataTable: - { - UHoudiniInputDataTable* InputDT = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(ObjBaseName, InputDT); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: - { - UHoudiniInputFoliageType_InstancedStaticMesh* const InputFoliageTypeSM = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - ObjBaseName, InputFoliageTypeSM, InInput->GetExportLODs(), InInput->GetExportSockets(), - InInput->GetExportColliders(), InInput->GetImportAsReference()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Invalid: - //default: - break; - } - - // Mark that input object as not changed - if (bSuccess) - { - InInputObject->MarkChanged(false); - InInputObject->SetNeedsToTriggerUpdate(false); - } - else - { - // We couldn't update/create that input object, keep it changed but prevent it from trigger updates - InInputObject->SetNeedsToTriggerUpdate(false); - } - - return bSuccess; -} - - -// Upload transform for an input's InputObject -bool -FHoudiniInputTranslator::UploadHoudiniInputTransform( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject) -{ - if (!InInput || !InInputObject) - return false; - - auto UpdateTransform = [](const FTransform& InTransform, const HAPI_NodeId& InNodeId) - { - // Translate the Transform to HAPI - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(InTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InNodeId, &HapiTransform), false); - - return true; - }; - - bool bSuccess = true; - switch (InInputObject->Type) - { - case EHoudiniInputObjectType::StaticMesh: - { - // Simply update the Input mesh's Transform offset - if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) - bSuccess = false; - - break; - } - - case EHoudiniInputObjectType::StaticMeshComponent: - { - // Update using the static mesh component's transform - UHoudiniInputMeshComponent* InSMC = Cast(InInputObject); - if (!InSMC || InSMC->IsPendingKill()) - { - bSuccess = false; - break; - } - - FTransform NewTransform = InSMC->GetStaticMeshComponent() ? InSMC->GetStaticMeshComponent()->GetComponentTransform() : InInputObject->Transform; - if(!UpdateTransform(NewTransform, InInputObject->InputObjectNodeId)) - bSuccess = false; - - // Update the InputObject's transform - InInputObject->Transform = NewTransform; - - break; - } - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - { - // TODO: Only update the instances transform - break; - } - - case EHoudiniInputObjectType::HoudiniSplineComponent: - { - // TODO: Simply update the curve's transform? - break; - } - - case EHoudiniInputObjectType::HoudiniAssetActor: - case EHoudiniInputObjectType::HoudiniAssetComponent: - { - // TODO: Check, nothing to do? - break; - } - - case EHoudiniInputObjectType::Actor: - { - UHoudiniInputActor* InputActor = Cast(InInputObject); - if (!InputActor || InputActor->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Update the actor's transform - // To avoid further updates - if (InputActor->GetActor()) - InputActor->Transform = InputActor->GetActor()->GetTransform(); - - // Iterate on all the actor input objects and see if their transform needs to be uploaded - // TODO? Also update the component's actor transform?? - for (auto& CurrentComponent : InputActor->GetActorComponents()) - { - if (!CurrentComponent || CurrentComponent->IsPendingKill()) - continue; - - if (!CurrentComponent->HasTransformChanged()) - continue; - - // Upload the current input object's transform to Houdini - if (!UploadHoudiniInputTransform(InInput, CurrentComponent)) - { - bSuccess = false; - continue; - } - } - break; - } - - case EHoudiniInputObjectType::SceneComponent: - { - UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); - if (!InputSceneComp || InputSceneComp->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Update the component transform to avoid further updates - if (InputSceneComp->GetSceneComponent()) - InputSceneComp->Transform = InputSceneComp->GetSceneComponent()->GetComponentTransform(); - - break; - } - - case EHoudiniInputObjectType::Landscape: - { - // - UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) - { - bSuccess = false; - break; - } - - // - ALandscapeProxy* Landscape = InputLandscape->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Only apply diff for landscape since the HF's transform is used for value conversion as well - FTransform CurrentTransform = InputLandscape->Transform; - FTransform NewTransform = Landscape->ActorToWorld(); - - // Only handle position/rotation differences - FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); - FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); - - // Now get the HF's current transform - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - - if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) - { - bSuccess = false; - break; - } - - // Convert it to unreal - FTransform HFTransform; - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); - - // Apply the position offset if needed - if (!PosDiff.IsZero()) - HFTransform.AddToTranslation(PosDiff); - - // Apply the rotation offset if needed - if (!RotDiff.IsIdentity()) - HFTransform.ConcatenateRotation(RotDiff); - - // Convert back to a HAPI Transform and update the HF's transform - HAPI_TransformEuler NewHAPITransform; - FHoudiniApi::TransformEuler_Init(&NewHAPITransform); - FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); - NewHAPITransform.position[1] = 0.0f; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InputLandscape->InputObjectNodeId, &NewHAPITransform)) - { - bSuccess = false; - break; - } - - // Update the cached transform - InputLandscape->Transform = NewTransform; - } - - case EHoudiniInputObjectType::Brush: - { - // TODO: Update the Brush's transform - break; - } - - case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: - { - // Simply update the Input mesh's Transform offset - if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) - bSuccess = false; - - break; - } - - // Unsupported - case EHoudiniInputObjectType::Object: - case EHoudiniInputObjectType::SkeletalMesh: - case EHoudiniInputObjectType::SplineComponent: - { - break; - } - - case EHoudiniInputObjectType::Invalid: - default: - break; - } - - // Mark that input object as not changed - if (bSuccess) - { - InInputObject->MarkTransformChanged(false); - InInputObject->SetNeedsToTriggerUpdate(false); - } - else - { - // We couldn't update/create that input object, keep it changed but prevent it from trigger updates - InInputObject->SetNeedsToTriggerUpdate(false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForObject(const FString& InObjNodeName, UHoudiniInputObject* InObject) -{ - if (!InObject) - return false; - - UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) - return true; - - FString NodeName = InObjNodeName + TEXT("_") + Object->GetName(); - - // For UObjects we can't upload much, but can still create an input node - // with a single point, with an attribute pointing to the input object's path - HAPI_NodeId InputNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); - - // Update this input object's NodeId and ObjectNodeId - InObject->InputNodeId = (int32)InputNodeId; - InObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 2; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = 1; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); - - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - FVector ObjectPosition = InObject->Transform.GetLocation(); - TArray Position = - { - ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION - }; - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Position.GetData(), 0, - AttributeInfoPoint.count), false); - } - - { - // Create point attribute info for the path. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); - - // Set the point's path attribute - FString ObjectPathName = Object->GetPathName(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - ObjectPathName, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InputNodeId), false); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( - const FString& InObjNodeName, - UHoudiniInputStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UBlueprint* BP = nullptr; - UStaticMesh* SM = nullptr; - - FString SMName = InObjNodeName + TEXT("_"); - - // Get Blueprint or StaticMesh - if (InObject->bIsBlueprint()) - { - BP = InObject->GetBlueprint(); - if (!BP || BP->IsPendingKill()) - return true; - - SMName += BP->GetName(); - } - else - { - SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - SMName += SM->GetName(); - } - - // Marshall the Static Mesh to Houdini - bool bSuccess = true; - - if (bImportAsReference) - { - // Start by getting the Object's full name - FString AssetReference; - if (SM) - AssetReference += SM->GetFullName(); - - if (BP) - AssetReference += BP->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, SMName, InObject->Transform); - } - else - { - TArray StaticMeshComponents; - - // The input object is a Blueprint, Get all its StaticMeshes - if (BP) - { - USimpleConstructionScript* SCS = BP->SimpleConstructionScript; - if (SCS && !SCS->IsPendingKill()) - { - const TArray& Nodes = SCS->GetAllNodes(); - for (auto & CurNode : Nodes) - { - if (!CurNode || CurNode->IsPendingKill()) - continue; - - UActorComponent * CurComp = CurNode->ComponentTemplate; - if (!CurComp || CurComp->IsPendingKill()) - continue; - - UStaticMeshComponent* CurSMC = Cast(CurComp); - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - UStaticMesh* CurSM = CurSMC->GetStaticMesh(); - if (CurSM && !CurSM->IsPendingKill()) - StaticMeshComponents.Add(CurSMC); - - } - } - } - - // Clear previous Blueprint Static Mesh Comps (if there is any) - InObject->BlueprintStaticMeshes.Empty(); - - // This is a BP, add all the BP SM comps to its BlueprintStaticMeshes list. - if (InObject->bIsBlueprint()) - { - for (auto & CurSMC : StaticMeshComponents) - { - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - UHoudiniInputStaticMesh* SMObject = Cast( - UHoudiniInputObject::CreateTypedInputObject(CurSMC->GetStaticMesh(), InObject, InObject->GetName() + TEXT("_") + CurSMC->GetName())); - - if (!SMObject || SMObject->IsPendingKill()) - continue; - - bSuccess &= FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - CurSMC->GetStaticMesh(), SMObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); - - InObject->SetImportAsReference(false); - - // Update this input object's OBJ NodeId - SMObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(SMObject->InputNodeId); - - // Update the component's transform - FTransform ComponentTransform = CurSMC->GetRelativeTransform(); - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), SMObject->InputObjectNodeId, &HapiTransform), false); - } - - InObject->BlueprintStaticMeshes.Add(SMObject); - } - - return true; - } - // This is a normal static mesh input, process it normally as a static mesh Input Object - else - { - bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, InObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); - } - } - - InObject->SetImportAsReference(bImportAsReference); - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - // If the Input mesh has a Transform offset - FTransform TransformOffset = InObject->Transform; - if (!TransformOffset.Equals(FTransform::Identity)) - { - // Updating the Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USkeletalMesh* SkelMesh = InObject->GetSkeletalMesh(); - if (!SkelMesh || SkelMesh->IsPendingKill()) - return true; - - // Get the SM's transform offset - FTransform TransformOffset = InObject->Transform; - - // TODO - // Support this type of input object - // FUnrealMeshTranslator::CreateInputNodeForSkeletalMesh(...) - - return HapiCreateInputNodeForObject(InObjNodeName, InObject); -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USceneComponent* SceneComp = InObject->GetSceneComponent(); - if (!SceneComp || SceneComp->IsPendingKill()) - return true; - - // Get the Scene Component's transform - FTransform TransformOffset = InObject->Transform; - - // Get the parent Actor's transform - FTransform ParentTransform = InObject->ActorTransform; - - // Dont do that! - return false; - - // TODO - // Support this type of input object - return HapiCreateInputNodeForObject(InObjNodeName, InObject); -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UStaticMeshComponent* SMC = InObject->GetStaticMeshComponent(); - if (!SMC || SMC->IsPendingKill()) - return true; - - // Get the component's Static Mesh - UStaticMesh* SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - // Marshall the Static Mesh to Houdini - FString SMCName = InObjNodeName + TEXT("_") + SMC->GetName(); - - bool bSuccess = true; - if (bImportAsReference) - { - // Start by getting the Object's full name - FString AssetReference = SM->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, AssetReference, SMCName, InObject->Transform); - - } - else - { - bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, InObject->InputNodeId, SMCName, SMC, bExportLODs, bExportSockets, bExportColliders); - } - - InObject->SetImportAsReference(bImportAsReference); - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - // Update this input object's cache data - InObject->Update(SMC); - - // Update the component's transform - FTransform ComponentTransform = InObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputInstancedMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) - return true; - - // Get the ISMC - UInstancedStaticMeshComponent* ISMC = InObject->GetInstancedStaticMeshComponent(); - if (!ISMC || ISMC->IsPendingKill()) - return true; - - HAPI_NodeId NewNodeId = -1; - if (!FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( - ISMC, InObjNodeName, NewNodeId, bExportLODs, bExportSockets, bExportColliders, false)) - return false; - - // Update this input object's node IDs - InObject->InputNodeId = NewNodeId; - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); - - // Update the component's cached instances - InObject->Update(ISMC); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USplineComponent* Spline = InObject->GetSplineComponent(); - if (!Spline || Spline->IsPendingKill()) - return true; - - - int32 NumberOfSplineControlPoints = InObject->NumberOfSplineControlPoints; - - TArray SplineControlPoints = InObject->SplineControlPoints; - - FString NodeName = InObjNodeName + TEXT("_") + InObject->GetName(); - - if (!FUnrealSplineTranslator::CreateInputNodeForSplineComponent(Spline, SplineResolution, InObject->InputNodeId, NodeName)) - return false; - - // Cache the exported curve's data to the input object - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - InObject->MarkChanged(true); - - //if (!HapiCreateInputNodeForObject(InObjNodeName, InObject)) - // return false; - - // Update the component's cached data - InObject->Update(Spline); - - // Update the component's transform - FTransform ComponentTransform = InObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject, bool bInAddRotAndScaleAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UHoudiniSplineComponent* Curve = InObject->GetCurveComponent(); - if (!Curve || Curve->IsPendingKill()) - return true; - - if (!FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(Curve, bInAddRotAndScaleAttributes)) - return false; - - // See if the component needs it node Id invalidated - //if (InObject->InputNodeId < 0) - // Curve->SetNodeId(InObject->InputNodeId); - - // Cache the exported curve's data to the input object - InObject->InputNodeId = Curve->GetNodeId(); - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - //InObject->CurveType = Curve->GetCurveType(); - //InObject->CurveMethod = Curve->GetCurveMethod(); - //InObject->Reversed = Curve->IsReversed(); - InObject->Update(Curve); - - InObject->MarkChanged(true); - - return true; -} - -bool -FHoudiniInputTranslator:: -HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UHoudiniAssetComponent* InputHAC = InObject->GetHoudiniAssetComponent(); - if (!InputHAC || InputHAC->IsPendingKill()) - return true; - - if (!InputHAC->CanDeleteHoudiniNodes()) - return true; - - UHoudiniInput* HoudiniInput = Cast(InObject->GetOuter()); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - return true; - - UHoudiniAssetComponent* OuterHAC = Cast(HoudiniInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return true; - - // Do not allow using ourself as an input, terrible things would happen - if (InputHAC->GetAssetId() == OuterHAC->GetAssetId()) - return false; - - // If previously imported as ref, delete the input node. - if (InObject->InputNodeId > -1 && InObject->GetImportAsReference()) - { - int32 PreviousInputNodeId = InObject->InputNodeId; - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InObject->GetName()); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InObject->GetName()); - } - } - - InObject->SetImportAsReference(bImportAsReference); - - // If this object is in an Asset input, we need to set the InputNodeId directl - // to avoid creating extra merge nodes. World inputs should not do that! - bool bIsAssetInput = HoudiniInput->GetInputType() == EHoudiniInputType::Asset; - - if (bImportAsReference) - { - InObject->InputNodeId = -1; - InObject->InputObjectNodeId = -1; - - if(bIsAssetInput) - HoudiniInput->SetInputNodeId(-1); - - // Start by getting the Object's full name - FString AssetReference = InputHAC->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - if (!FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform)) // do not delete previous node if it was HAC - return false; - - if (bIsAssetInput) - HoudiniInput->SetInputNodeId(InObject->InputNodeId); - } - - InputHAC->AddDownstreamHoudiniAsset(OuterHAC); - - //if (HAC->NeedsInitialization()) - // HAC->MarkAsNeedInstantiation(); - - //HoudiniInput->SetAssetNodeId(HAC->GetAssetId()); - - // TODO: This might be uneeded as this function should only be called - // after we're not wiating on the input asset... - if (InputHAC->AssetState == EHoudiniAssetState::NeedInstantiation) - { - // If the input HAC needs to be instantiated, tell it do so - InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; - // Mark this object's input as changed so we can properly update after the input HDA's done instantiating/cooking - HoudiniInput->MarkChanged(true); - } - - if (InputHAC->NeedsInitialization() || InputHAC->NeedUpdate()) - return false; - - if (!bImportAsReference) - { - if (bIsAssetInput) - HoudiniInput->SetInputNodeId(InputHAC->GetAssetId()); - - InObject->InputNodeId = InputHAC->GetAssetId(); - } - - InObject->InputObjectNodeId = InObject->InputNodeId; - - bool bReturn = InObject->InputNodeId > -1; - - if(bIsAssetInput) - bReturn = FHoudiniInputTranslator::ConnectInputNode(HoudiniInput); - - return bReturn; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (!InObject || InObject->IsPendingKill()) - return false; - - AActor* Actor = InObject->GetActor(); - if (!Actor || Actor->IsPendingKill()) - return true; - - // Check if this is a world input and if this is a HoudiniAssetActor - // If so we need to build static meshes for any proxy meshes - if (InInput->GetInputType() == EHoudiniInputType::World && Actor->IsA()) - { - AHoudiniAssetActor *HAA = Cast(Actor); - UHoudiniAssetComponent *HAC = HAA->GetHoudiniAssetComponent(); - if (HAC && !HAC->IsPendingKill()) - { - if (HAC->HasAnyCurrentProxyOutput()) - { - bool bPendingDeleteOrRebuild = false; - bool bInvalidState = false; - const bool bIsHoudiniCookedDataAvailable = HAC->IsHoudiniCookedDataAvailable(bPendingDeleteOrRebuild, bInvalidState); - if (bIsHoudiniCookedDataAvailable) - { - // Build the static mesh - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); - // Update the input object since a new StaticMeshComponent could have been created - UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) - { - InObject->Update(InputObject); - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - } - } - else if (!bPendingDeleteOrRebuild && !bInvalidState) - { - // Request a cook with no proxy output - HAC->MarkAsNeedCook(); - HAC->SetNoProxyMeshNextCookRequested(true); - } - } - else if (InObject->GetActorComponents().Num() == 0 && HAC->HasAnyOutputComponent()) - { - // The HAC has non-proxy output components, but the InObject does not have any - // actor components. This can arise after a cook if previously there were only - // proxies and the input was created when there were only proxies - // Try to update the input to find new components - UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) - { - InObject->Update(InputObject); - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - } - } - } - } - - // Now, commit all of this actor's component - int32 ComponentIdx = 0; - for (UHoudiniInputSceneComponent* CurComponent : InObject->GetActorComponents()) - { - if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) - ComponentIdx++; - } - - // TODO: We should call Update here... - // needs to be fixed - - // Cache our transformn - InObject->Transform = Actor->GetTransform(); - - // Do something for our actor's transform? - /* - // TODO - // Support this type of input object - FString ObjNodeName = InInput->GetNodeBaseName(); - return HapiCreateInputNodeForObject(ObjNodeName, InObject); - */ - - //TODO? Check - // return true if we have at least uploaded one component - // return (ComponentIdx > 0); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( - const FString& InObjNodeName, UHoudiniInputLandscape* InObject, UHoudiniInput* InInput) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - if (!InInput || InInput->IsPendingKill()) - return false; - - ALandscapeProxy* Landscape = InObject->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) - return true; - - EHoudiniLandscapeExportType ExportType = InInput->GetLandscapeExportType(); - - bool bSucess = false; - if (ExportType == EHoudiniLandscapeExportType::Heightfield) - { - bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); - } - else - { - bool bExportLighting = InInput->bLandscapeExportLighting; - bool bExportMaterials = InInput->bLandscapeExportMaterials; - bool bExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; - bool bExportTileUVs = InInput->bLandscapeExportTileUVs; - bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; - bool bExportAsMesh = InInput->LandscapeExportType == EHoudiniLandscapeExportType::Mesh; - - bSucess = FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( - Landscape, InObject->InputNodeId, InObjNodeName, - bExportAsMesh, bExportTileUVs, bExportNormalizedUVs, bExportLighting, bExportMaterials); - } - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - InObject->Update(Landscape); - - return bSucess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForBrush(const FString& InObjNodeName, UHoudiniInputBrush* InObject, TArray* ExcludeActors) -{ - if (!IsValid(InObject)) - return false; - - ABrush* BrushActor = InObject->GetBrush(); - if (!IsValid(BrushActor)) - return true; - - if (!FUnrealBrushTranslator::CreateInputNodeForBrush(InObject, BrushActor, ExcludeActors, InObject->InputNodeId, InObjNodeName)) - return false; - - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - InObject->Update(BrushActor); - - return true; -} - - - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, UHoudiniInputCameraComponent* InInputObject) -{ - if (!InInputObject || InInputObject->IsPendingKill()) - return false; - - UCameraComponent* Camera = InInputObject->GetCameraComponent(); - if (!Camera || Camera->IsPendingKill()) - return true; - - FString NodeName = InNodeName + TEXT("_") + Camera->GetName(); - - // Create the camera OBJ. - int32 CameraNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( - -1, TEXT("Object/cam"), InNodeName, true, &CameraNodeId), false); - - // set "Pixel Aspect Ratio" (aspect) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "aspect", 0, InInputObject->AspectRatio), false); - - // set "Projection" (projection) (0 persp, 1 ortho) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "projection", 0, InInputObject->bIsOrthographic ? 1 : 0), false); - - // set Ortho Width (orthowidth) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "orthowidth", 0, InInputObject->OrthoWidth), false); - - // set Near Clippin (near) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "near", 0, InInputObject->OrthoNearClipPlane), false); - - // set far clipping (far) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "far", 0, InInputObject->OrthoFarClipPlane), false); - - // Set the transform - HAPI_TransformEuler H_Transform; - FHoudiniApi::TransformEuler_Init(&H_Transform); - FHoudiniEngineUtils::TranslateUnrealTransform(Camera->GetComponentTransform(), H_Transform); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CameraNodeId, &H_Transform), false); - - // Update the component's transform - FTransform ComponentTransform = InInputObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Camera orientation need to be adjusted - HapiTransform.rotationEuler[1] += -90.0f; - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CameraNodeId, &HapiTransform), false); - } - - // Update this input's NodeId and ObjectNodeId - InInputObject->InputNodeId = -1;// (int32)CameraNodeId; - InInputObject->InputObjectNodeId = (int32)CameraNodeId; - - // Update this input object's cache data - InInputObject->Update(Camera); - - return true; -} - -bool -FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // We need to call BuildAllInputs here to update all the inputs, - // and make sure that the object path parameter inputs' parameter ids are up to date - if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) - return false; - - // We need to update the AssetID stored on all the inputs - // and mark all the input objects for this input type as changed - int32 HACAssetId = HAC->GetAssetId(); - for (auto CurrentInput : HAC->Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // - CurrentInput->SetAssetNodeId(HACAssetId); - - // We need to delete the nodes created for the input objects if they are valid - // (since the node IDs are transients, this likely means we're handling a recook/rebuild - // and therefore expect to recreate the input nodes) - DestroyInputNodes(CurrentInput, CurrentInput->GetInputType()); - } - - return true; -} - - - -bool -FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Only tick/cook when in Editor - // This prevents PIE cooks or runtime cooks due to inputs moving - AActor* ActorOwner = HAC->GetOwner(); - if (ActorOwner) - { - if (!ActorOwner->GetWorld() || (ActorOwner->GetWorld()->WorldType != EWorldType::Editor)) - return false; - } - -#if WITH_EDITOR - // Stop outliner objects from causing recooks while input objects are dragged around - if (FHoudiniMoveTracker::Get().IsObjectMoving) - { - //HOUDINI_LOG_MESSAGE(TEXT("Object moving, not updating world inputs!")); - return false; - } -#endif - - for (auto CurrentInput : HAC->Inputs) - { - if (!CurrentInput) - continue; - if (CurrentInput->GetInputType() != EHoudiniInputType::World) - continue; - - UpdateWorldInput(CurrentInput); - } - - return true; -} - -bool -FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (InInput->GetInputType() != EHoudiniInputType::World) - return false; - - TArray* InputObjectsPtr = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjectsPtr) - return false; - - bool bHasChanged = false; - if (InInput->IsWorldInputBoundSelector() && InInput->GetWorldInputBoundSelectorAutoUpdates()) - { - // If the input is in bound selector mode, and auto-update is enabled - // update the actors selected by the bounds first - bHasChanged = InInput->UpdateWorldSelectionFromBoundSelectors(); - } - - // See if we need to update the components for this input - // look for deleted actors/components - TArray ObjectToDeleteIndices; - for(int32 InputObjIdx = 0; InputObjIdx < InputObjectsPtr->Num(); InputObjIdx++) - { - UHoudiniInputActor* ActorObject = Cast((*InputObjectsPtr)[InputObjIdx]); - if (!ActorObject || ActorObject->IsPendingKill()) - continue; - - // Make sure the actor is still valid - AActor* const Actor = ActorObject->GetActor(); - bool bValidActorObject = Actor && !Actor->IsPendingKill(); - - // For BrushActors, the brush and actors must be valid as well - UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); - if (bValidActorObject && BrushActorObject) - { - ABrush* BrushActor = BrushActorObject->GetBrush(); - if (!IsValid(BrushActor)) - bValidActorObject = false; - else if (!IsValid(BrushActor->Brush)) - bValidActorObject = false; - } - - // The actor is no longer valid, mark it for deletion - if (!bValidActorObject) - { - if ((ActorObject->InputNodeId > 0) || (ActorObject->InputObjectNodeId > 0)) - { - ActorObject->InvalidateData(); - // We only need to update the input if the actors nodes were created in Houdini - bHasChanged = true; - } - - // Delete the Actor object - ObjectToDeleteIndices.Add(InputObjIdx); - continue; - } - - if (ActorObject->HasActorTransformChanged()) - { - ActorObject->MarkTransformChanged(true); - bHasChanged = true; - } - - if (ActorObject->HasContentChanged()) - { - ActorObject->MarkChanged(true); - bHasChanged = true; - } - - // Ensure we are aware of all the components of the actor - ActorObject->Update(Actor); - - // Check if any components have content or transform changes - for (auto CurActorComp : ActorObject->GetActorComponents()) - { - if (CurActorComp->HasComponentTransformChanged()) - { - CurActorComp->MarkTransformChanged(true); - bHasChanged = true; - } - - if (CurActorComp->HasComponentChanged()) - { - CurActorComp->MarkChanged(true); - bHasChanged = true; - } - } - - // Check if we added/removed any components in the call to update - if (ActorObject->GetLastUpdateNumComponentsAdded() > 0 || ActorObject->GetLastUpdateNumComponentsRemoved() > 0) - { - bHasChanged = true; - if (ActorObject->GetLastUpdateNumComponentsRemoved() > 0) - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - } - } - - // Delete the actor objects that were marked for deletion - for (int32 ToDeleteIdx = ObjectToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) - InputObjectsPtr->RemoveAt(ObjectToDeleteIndices[ToDeleteIdx]); - - // Mark the input as changed if need so it will trigger an upload - if (bHasChanged) - InInput->MarkChanged(true); - - return true; -} - - -bool -FHoudiniInputTranslator::CreateInputNodeForReference( - HAPI_NodeId& InputNodeId, - const FString& InRef, - const FString& InputNodeName, - const FTransform& InTransform) -{ - HAPI_NodeId NewNodeId = -1; - - // Create a single input node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_UTF8(*InputNodeName)), false); - - /* - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, nullptr), false); - */ - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) - return false; - - // We have now created a valid new input node, delete the previous one - HAPI_NodeId PreviousInputNodeId = InputNodeId; - if (PreviousInputNodeId >= 0) - { - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); - } - } - - // Create and initialize a part containing one point with a point attribute - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - - PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = 1; - PartInfo.vertexCount = 0; - PartInfo.faceCount = 0; - PartInfo.pointCount = 1; - PartInfo.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, &PartInfo), false); - - // Point Position Attribute - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - FVector ObjectPosition = InTransform.GetLocation(); - TArray Position = - { - ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION - }; - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Position.GetData(), 0, - AttributeInfoPoint.count), false); - } - - // String Attribute - { - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint), false); - - // Set string attribute - std::string AttriString = TCHAR_TO_ANSI(*InRef); - const char* AttriStringRaw = AttriString.c_str(); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NewNodeId, 0, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint, - &AttriStringRaw, 0, 1), false); - } - - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NewNodeId), false); - - InputNodeId = NewNodeId; - return true; -} - - - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeName, UHoudiniInputDataTable* InInputObject) -{ - //TODO - if (!InInputObject || InInputObject->IsPendingKill()) - return false; - - UDataTable* DataTable = InInputObject->GetDataTable(); - if (!DataTable || DataTable->IsPendingKill()) - return true; - - // Get the DataTable data as string - TArray> TableData = DataTable->GetTableData(EDataTableExportFlags::None); - if (TableData.Num() <= 1) - return true; - - int32 NumRows = TableData.Num() - 1; - int32 NumAttributes = TableData[0].Num(); - if (NumRows <= 0 || NumAttributes <= 0) - return true; - - // Create the input node - FString NodeName = InNodeName + TEXT("_") + DataTable->GetName(); - HAPI_NodeId InputNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); - - // Update this input object's NodeId and ObjectNodeId - InInputObject->InputNodeId = (int32)InputNodeId; - InInputObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = NumAttributes; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = NumRows; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); - - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - TArray Positions; - Positions.SetNum(NumRows * 3); - for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) - { - Positions[RowIdx * 3] = 0.0f; - Positions[RowIdx * 3 + 1] = (float)RowIdx; - Positions[RowIdx * 3 + 2] = 0.0f; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Positions.GetData(), 0, - AttributeInfoPoint.count), false); - } - - { - // Create point attribute info for the path. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); - - // Get the object path - FString ObjectPathName = DataTable->GetPathName(); - - // Create an array - TArray ObjectPaths; - ObjectPaths.Init(ObjectPathName, NumRows); - - // Set the point's path attribute - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - ObjectPaths, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); - } - - { - // Create point attribute info for data table RowTable class name - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, &AttributeInfoPoint), false); - - // Get the object path - FString RowStructName = DataTable->GetRowStructName().ToString(); - - // Create an array - TArray RowStructNames; - RowStructNames.Init(RowStructName, NumRows); - - // Set the point's path attribute - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - RowStructNames, InputNodeId, 0, - HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, AttributeInfoPoint), false); - } - - // Now set the attributes values for each "point" of the data table - for (int32 ColIdx = 0; ColIdx < NumAttributes; ColIdx++) - { - // attribute name is "unreal_data_table_COL_NAME" - FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + TableData[0][ColIdx]; - - // We need to gt all values for that attribute - TArray AttributeValues; - AttributeValues.SetNum(NumRows); - for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) - { - AttributeValues[RowIdx] = TableData[RowIdx + 1][ColIdx]; - } - - // Create a point attribute info - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = NumRows; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_POINT; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - TCHAR_TO_ANSI(*CurAttrName), &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - AttributeValues, InputNodeId, 0, - CurAttrName, AttributeInfo), false); - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InputNodeId), false); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr), false); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - const FString& InObjNodeName, - UHoudiniInputFoliageType_InstancedStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference) -{ - if (!IsValid(InObject)) - return false; - - FString FTName = InObjNodeName + TEXT("_"); - - UFoliageType_InstancedStaticMesh* FoliageType = Cast(InObject->GetObject()); - if (!IsValid(FoliageType)) - return true; - - UStaticMesh* const SM = FoliageType->GetStaticMesh(); - if (!IsValid(SM)) - return true; - - FTName += FoliageType->GetName(); - - // Marshall the Static Mesh to Houdini - bool bSuccess = true; - - if (bImportAsReference) - { - // Start by getting the Object's full name - FString AssetReference; - AssetReference += SM->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - bSuccess = FUnrealFoliageTypeTranslator::CreateInputNodeForReference( - FoliageType, InObject->InputNodeId, AssetReference, FTName, InObject->Transform); - } - else - { - bSuccess = FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - FoliageType, InObject->InputNodeId, FTName, bExportLODs, bExportSockets, bExportColliders); - } - - InObject->SetImportAsReference(bImportAsReference); - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - // If the Input mesh has a Transform offset - const FTransform TransformOffset = InObject->Transform; - if (!TransformOffset.Equals(FTransform::Identity)) - { - // Updating the Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - return bSuccess; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputTranslator.h" + +#include "HoudiniInput.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInputObject.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniAssetActor.h" +#include "HoudiniOutputTranslator.h" +#include "UnrealBrushTranslator.h" +#include "UnrealSplineTranslator.h" +#include "UnrealMeshTranslator.h" +#include "UnrealInstanceTranslator.h" +#include "UnrealLandscapeTranslator.h" +#include "UnrealFoliageTypeTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "Engine/Brush.h" +#include "Engine/DataTable.h" +#include "Camera/CameraComponent.h" +#include "FoliageType_InstancedStaticMesh.h" + +#include "Engine/SimpleConstructionScript.h" +#include "Engine/SCS_Node.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Editor/UnrealEdEngine.h" + #include "UnrealEdGlobals.h" +#endif + +#include "HCsgUtils.h" + +#include "Async/Async.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#if WITH_EDITOR +// Allows checking of objects currently being dragged around +struct FHoudiniMoveTracker +{ + FHoudiniMoveTracker() : IsObjectMoving(false) + { + GEditor->OnBeginObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = true; }); + GEditor->OnEndObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + + GEditor->OnBeginCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + GEditor->OnEndCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + } + static FHoudiniMoveTracker& Get() { static FHoudiniMoveTracker Instance; return Instance; } + + bool IsObjectMoving; +}; +#endif + +// +bool +FHoudiniInputTranslator::UpdateInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) + { + // Failed to create the inputs + return false; + } + + return true; +} + +bool +FHoudiniInputTranslator::BuildAllInputs( + const HAPI_NodeId& AssetId, + class UObject* InOuterObject, + TArray& Inputs, + TArray& Parameters) +{ + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Start by getting the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // Get the number of geo (SOP) inputs + int32 InputCount = AssetInfo.geoInputCount; + /* + // It's best to update the input count even if the hda hasnt cooked + // as it can cause loaded geo inputs to disappear upon loading the level + if ( AssetInfo.hasEverCooked ) + { + InputCount = AssetInfo.geoInputCount; + } + */ + // Also look for object path parameters inputs + TArray> InputParameters; + for (auto Param : Parameters) + { + if (Param->GetParameterType() == EHoudiniParameterType::Input) + InputParameters.Add(Param); + } + + InputCount += InputParameters.Num(); + + // Append new inputs as needed + if (InputCount > Inputs.Num()) + { + int32 NumNewInputs = InputCount - Inputs.Num(); + for (int32 InputIdx = Inputs.Num(); InputIdx < InputCount; ++InputIdx) + { + FString InputObjectName = TEXT("Input") + FString::FromInt(InputIdx + 1); + UHoudiniInput * NewInput = NewObject< UHoudiniInput >( + InOuterObject, + UHoudiniInput::StaticClass(), + FName(*InputObjectName), + RF_Transactional); + + if (!NewInput || NewInput->IsPendingKill()) + { + //HOUDINI_LOG_WARNING("Failed to create asset input"); + continue; + } + // Create a default curve object here to avoid Transaction issue + //NewInput->CreateDefaultCurveInputObject(); + + Inputs.Add(NewInput); + } + } + else if (InputCount < Inputs.Num()) + { + // TODO: Properly clean up the input object + created nodes? + for (int32 InputIdx = Inputs.Num() - 1; InputIdx >= InputCount; InputIdx--) + { + UHoudiniInput* CurrentInput = Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + FHoudiniInputTranslator::DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); + + // DO NOT MANUALLY DESTROY THE OLD/DANGLING INPUTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + //CurrentInput->ConditionalBeginDestroy(); + //CurrentInput = nullptr; + } + + Inputs.SetNum(InputCount); + } + + // Now, check the inputs in the array match the geo inputs + //for (int32 GeoInIdx = 0; GeoInIdx < AssetInfo.geoInputCount; GeoInIdx++) + bool bBlueprintStructureChanged = false; + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) + { + UHoudiniInput* CurrentInput = Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // Create default Name/Label/Help + FString CurrentInputName = TEXT("Input") + FString::FromInt(InputIdx + 1); + FString CurrentInputLabel = CurrentInputName; + FString CurrentInputHelp; + + // Set the nodeId + CurrentInput->SetAssetNodeId(AssetId); + + // Is this an object path parameter input? + bool bIsObjectPath = InputIdx >= AssetInfo.geoInputCount; + if (!bIsObjectPath) + { + // Mark this input as a SOP input + CurrentInput->SetSOPInput(InputIdx); + + // Get and set the name + HAPI_StringHandle InputStringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInputName( + FHoudiniEngine::Get().GetSession(), + AssetId, InputIdx, &InputStringHandle)) + { + FHoudiniEngineString HoudiniEngineString(InputStringHandle); + HoudiniEngineString.ToFString(CurrentInputLabel); + } + } + else + { + // Get this input's parameter index in the objpath param array + int32 CurrentParmIdx = InputIdx - AssetInfo.geoInputCount; + + UHoudiniParameter* CurrentParm = nullptr; + if (InputParameters.IsValidIndex(CurrentParmIdx)) + { + if (InputParameters[CurrentParmIdx].IsValid()) + CurrentParm = InputParameters[CurrentParmIdx].Get(); + } + + int32 ParmId = -1; + if (CurrentParm && !CurrentParm->IsPendingKill()) + { + ParmId = CurrentParm->GetParmId(); + CurrentInputName = CurrentParm->GetParameterName(); + CurrentInputLabel = CurrentParm->GetParameterLabel(); + CurrentInputHelp = CurrentParm->GetParameterHelp(); + } + + UHoudiniParameterOperatorPath* CurrentObjPathParm = Cast(CurrentParm); + if (CurrentObjPathParm && !CurrentObjPathParm->IsPendingKill()) + { + CurrentObjPathParm->HoudiniInput = CurrentInput; + } + + // Mark this input as an object path parameter input + CurrentInput->SetObjectPathParameter(ParmId); + } + + CurrentInput->SetName(CurrentInputName); + CurrentInput->SetLabel(CurrentInputLabel); + + if ( CurrentInputHelp.IsEmpty() ) + { + CurrentInputHelp = CurrentInputLabel + TEXT("(") + CurrentInputName + TEXT(")"); + } + CurrentInput->SetHelp(CurrentInputHelp); + + // If the input type is invalid, + // We need to initialize its default + if (CurrentInput->GetInputType() == EHoudiniInputType::Invalid) + { + // Initialize it to the default corresponding to its name + CurrentInput->SetInputType(GetDefaultInputTypeFromLabel(CurrentInputLabel), bBlueprintStructureChanged); + + // Preset the default HDA for objpath input + SetDefaultAssetFromHDA(CurrentInput, bBlueprintStructureChanged); + } + + // Update input objects data on UE side for all types of inputs. + switch (CurrentInput->GetInputType()) + { + case EHoudiniInputType::Curve: + FHoudiniSplineTranslator::UpdateHoudiniInputCurves(CurrentInput); + break; + case EHoudiniInputType::Landscape: + //FUnrealLandscapeTranslator::UpdateHoudiniInputLandscapes(CurrentInput); + break; + case EHoudiniInputType::Asset: + break; + case EHoudiniInputType::Geometry: + break; + case EHoudiniInputType::Skeletal: + break; + case EHoudiniInputType::World: + break; + default: + break; + } + } + + return true; +} + +bool +FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + if (!InputToDestroy || InputToDestroy->IsPendingKill()) + return false; + + // Start by disconnecting the input / nullifying the object path parameter + if (InputToDestroy->IsObjectPathParameter()) + { + // Just set the objpath parameter to null + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + InputToDestroy->GetAssetNodeId(), "", + InputToDestroy->GetParameterId(), 0); + } + else + { + // Get the asset / created input node ID + HAPI_NodeId HostAssetId = InputToDestroy->GetAssetNodeId(); + HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); + + // Only disconnect if both are valid + if (HostAssetId >= 0 && CreatedInputId >= 0) + { + FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), + HostAssetId, InputToDestroy->GetInputIndex()); + } + } + + if (InputType == EHoudiniInputType::Asset) + { + // TODO: + // If we're an asset input, just remove us from the downstream connection on the input HDA + // then reset this input's flag + + // TODO: Check this? Clean our DS assets?? why?? likely uneeded + UHoudiniAssetComponent* OuterHAC = Cast(InputToDestroy->GetOuter()); + if (OuterHAC) + OuterHAC->ClearDownstreamHoudiniAsset(); + + InputToDestroy->SetInputNodeId(-1); + } + + return true; +} + +bool +FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + if (!InputToDestroy || InputToDestroy->IsPendingKill()) + return false; + + if (!InputToDestroy->CanDeleteHoudiniNodes()) + return false; + + // If we're destroying an asset input, don't destroy anything as we don't want to destroy the input HDA + // a simple disconnect is sufficient + if (InputType == EHoudiniInputType::Asset) + return true; + + // Destroy the nodes created by all the input objects + TArray CreatedInputDataAssetIds = InputToDestroy->GetCreatedDataNodeIds(); + TArray* InputObjectNodes = InputToDestroy->GetHoudiniInputObjectArray(InputType); + if (InputObjectNodes) + { + for (auto CurInputObject : *InputObjectNodes) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + if (CurInputObject->Type == EHoudiniInputObjectType::HoudiniAssetComponent) + { + // Remove this input object's node Id from the + // CreatedInputDataAssetIds array to avoid its deletion further down + CreatedInputDataAssetIds.Remove(CurInputObject->InputNodeId); + CurInputObject->InputNodeId = -1; + CurInputObject->InputObjectNodeId = -1; + continue; + } + + // For Actor input objects, set the input node id for all component objects to -1, + if (CurInputObject->Type == EHoudiniInputObjectType::Actor) + { + UHoudiniInputActor* CurActorInputObject = Cast(CurInputObject); + if (CurActorInputObject) + { + for (auto & CurActorComponent : CurActorInputObject->GetActorComponents()) + { + if (!CurActorComponent || CurActorComponent->IsPendingKill()) + continue; + + // No need to delete the nodes created for an asset component manually here, + // As they will be deleted when we clean up the CreateNodeIds array + CurActorComponent->InputNodeId = -1; + } + } + } + // No need to delete the nodes created for an asset component manually here, + // As they will be deleted when we clean up the CreateNodeIds array + + if (CurInputObject->InputNodeId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputNodeId); + CurInputObject->InputNodeId = -1; + } + + if(CurInputObject->InputObjectNodeId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputObjectNodeId); + CurInputObject->InputObjectNodeId = -1; + + // TODO: CHECK ME! + HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CurInputObject->InputObjectNodeId); + + // Delete its parent node as well + if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentNodeId)) + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentNodeId); + } + + // Also directly invalidate HoudiniSplineComponent's node IDs. + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast(CurInputObject); + if (IsValid(HoudiniSplineInputObject) && !IsGarbageCollecting()) + { + UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (SplineComponent && !SplineComponent->IsPendingKill()) + { + SplineComponent->SetNodeId(-1); + } + } + + CurInputObject->MarkChanged(true); + } + } + + // Destroy all the input assets + for (HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds) + { + if (AssetNodeId < 0) + continue; + + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), AssetNodeId); + } + CreatedInputDataAssetIds.Empty(); + + // Then simply destroy the input's parent OBJ node + if (InputToDestroy->GetInputNodeId() >= 0) + { + HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedInputId); + + if (CreatedInputId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CreatedInputId); + InputToDestroy->SetInputNodeId(-1); + } + + if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentId)) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentId); + } + } + + return true; +} + +bool +FHoudiniInputTranslator::DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + // Start by disconnecting the input/object merge + bool bSuccess = DisconnectInput(InputToDestroy, InputType); + + // Then destroy the created input nodes + bSuccess &= DestroyInputNodes(InputToDestroy, InputType); + + return bSuccess; +} + + +EHoudiniInputType +FHoudiniInputTranslator::GetDefaultInputTypeFromLabel(const FString& InputName) +{ + // We'll try to find these magic words to try to detect the default input type + //FString geoPrefix = TEXT("geo"); + FString curvePrefix = TEXT("curve"); + + FString landscapePrefix = TEXT("landscape"); + FString landscapePrefix2 = TEXT("terrain"); + FString landscapePrefix3 = TEXT("heightfield"); + + FString worldPrefix = TEXT("world"); + FString worldPrefix2 = TEXT("outliner"); + + FString assetPrefix = TEXT("asset"); + FString assetPrefix2 = TEXT("hda"); + + // By default, geometry input is chosen. + EHoudiniInputType InputType = EHoudiniInputType::Geometry; + + if (InputName.Contains(curvePrefix, ESearchCase::IgnoreCase)) + InputType = EHoudiniInputType::Curve; + + else if ((InputName.Contains(landscapePrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(landscapePrefix2, ESearchCase::IgnoreCase)) + || (InputName.Contains(landscapePrefix3, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::Landscape; + + else if ((InputName.Contains(worldPrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(worldPrefix2, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::World; + + else if ((InputName.Contains(assetPrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(assetPrefix2, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::Asset; + + return InputType; +} + +bool +FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bForce) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (!InInput->HasInputTypeChanged() && !bForce) + return true; + + // - Handle switching AWAY from an input type + DisconnectAndDestroyInput(InInput, InInput->GetPreviousInputType()); + + // Invalidate the previous input type now that we've actually changed + //InInput->SetPreviousInputType(EHoudiniInputType::Invalid); + + //ChangeInputType(InInput, NewType); + + // TODO: + // - Handle updating to the new input type + // downstream asset connection, static mesh update, curve creation... + + // Mark all the objects from this input has changed so they upload themselves + InInput->MarkAllInputObjectsChanged(true); + + return true; +} + +bool +FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified) +{ + // + if (!Input || Input->IsPendingKill()) + return false; + + // Make sure we're linked to a valid object path parameter + if (Input->GetParameterId() < 0) + return false; + + // Get our ParmInfo + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + Input->GetAssetNodeId(), Input->GetParameterId(), &FoundParamInfo)) + { + return false; + } + + // Get our string value + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + Input->GetAssetNodeId(), + false, + &StringHandle, + FoundParamInfo.stringValuesIndex, + 1)) + { + return false; + } + + FString ParamValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (!HoudiniEngineString.ToFString(ParamValue)) + { + return false; + } + + if (ParamValue.Len() <= 0) + { + return false; + } + + // Chop the default value using semi-colons as separators + TArray Tokens; + ParamValue.ParseIntoArray(Tokens, TEXT(";"), true); + + // Start by setting geometry input objects + int32 GeoIdx = 0; + for (auto& CurToken : Tokens) + { + if (CurToken.IsEmpty()) + continue; + + // Set default objects on the HDA instance - will override the parameter string + // and apply the object input local-path thing for the HDA cook. + UObject * pObject = LoadObject(nullptr, *CurToken); + if (!pObject) + continue; + + Input->SetInputObjectAt(EHoudiniInputType::Geometry, GeoIdx++, pObject); + } + + // See if we can preset world objects as well + int32 WorldIdx = 0; + int32 LandscapedIdx = 0; + int32 HDAIdx = 0; + for (TActorIterator ActorIt(Input->GetWorld(), AActor::StaticClass(), EActorIteratorFlags::SkipPendingKill); ActorIt; ++ActorIt) + { + AActor* CurActor = *ActorIt; + if (!CurActor) + continue; + + AActor* FoundActor = nullptr; + int32 FoundIdx = Tokens.Find(CurActor->GetFName().ToString()); + if (FoundIdx == INDEX_NONE) + FoundIdx = Tokens.Find(CurActor->GetActorLabel()); + + if(FoundIdx != INDEX_NONE) + FoundActor = CurActor; + + if (!FoundActor) + continue; + + // Select the found actor in the world input + Input->SetInputObjectAt(EHoudiniInputType::World, WorldIdx++, FoundActor); + + if (FoundActor->IsA()) + { + // Select the HDA in the asset input + Input->SetInputObjectAt(EHoudiniInputType::Asset, HDAIdx++, FoundActor); + } + else if (FoundActor->IsA()) + { + // Select the landscape in the landscape input + Input->SetInputObjectAt(EHoudiniInputType::Landscape, LandscapedIdx++, FoundActor); + } + + // Remove the Found Token + Tokens.RemoveAt(FoundIdx); + } + + // See if we should change the default input type + if (Input->GetInputType() == EHoudiniInputType::Geometry && WorldIdx > 0 && GeoIdx == 0) + { + if (LandscapedIdx == WorldIdx) + { + // We've only selected landscapes, set to landscape IN + Input->SetInputType(EHoudiniInputType::Landscape, bOutBlueprintStructureModified); + } + else if (HDAIdx == WorldIdx) + { + // We've only selected Houdini Assets, set to Asset IN + Input->SetInputType(EHoudiniInputType::Asset, bOutBlueprintStructureModified); + } + else + { + // Set to world input + Input->SetInputType(EHoudiniInputType::World, bOutBlueprintStructureModified); + } + } + + return true; +} + +bool +FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + //for (auto CurrentInput : HAC->Inputs) + for(int32 InputIdx = 0; InputIdx < HAC->GetNumInputs(); InputIdx++) + { + UHoudiniInput*& CurrentInput = HAC->Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill() || !CurrentInput->HasChanged()) + continue; + + // First thing, see if we need to change the input type + if (CurrentInput->HasInputTypeChanged()) + { + ChangeInputType(CurrentInput, false); + } + + if (CurrentInput->GetInputType() == EHoudiniInputType::Landscape && CurrentInput->HasLandscapeExportTypeChanged()) + { + DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); + CurrentInput->MarkAllInputObjectsChanged(true); + CurrentInput->SetHasLandscapeExportTypeChanged(false); + } + + bool bSuccess = true; + if (CurrentInput->IsDataUploadNeeded()) + { + bSuccess &= UploadInputData(CurrentInput); + CurrentInput->MarkDataUploadNeeded(!bSuccess); + } + + if (CurrentInput->IsTransformUploadNeeded()) + { + bSuccess &= UploadInputTransform(CurrentInput); + } + + // Update the input properties AFTER eventually uploading it + bSuccess = UpdateInputProperties(CurrentInput); + + if (bSuccess) + { + CurrentInput->MarkChanged(false); + CurrentInput->MarkAllInputObjectsChanged(false); + } + + if (CurrentInput->HasInputTypeChanged()) + CurrentInput->SetPreviousInputType(EHoudiniInputType::Invalid); + + // Even if we failed, no need to try updating again. + CurrentInput->SetNeedsToTriggerUpdate(false); + } + + return true; +} + +bool +FHoudiniInputTranslator::UpdateInputProperties(UHoudiniInput* InInput) +{ + bool bSucess = UpdateTransformType(InInput); + + bSucess &= UpdatePackBeforeMerge(InInput); + + bSucess &= UpdateTransformOffset(InInput); + + return bSucess; +} + +bool +FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + bool nTransformType = InInput->GetKeepWorldTransform(); + + // Geometry inputs are always set to none + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType == EHoudiniInputType::Geometry) + nTransformType = 0; + + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); + + bool bSuccess = true; + const std::string sXformType = "xformtype"; + if (InInput->IsObjectPathParameter()) + { + // Directly change the Parameter xformtype + // (This will only work if the object merge is editable/unlocked) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + HostAssetId, sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + else + { + // Query the object merge's node ID via the input + if (HAPI_RESULT_SUCCESS == FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + HostAssetId, InInput->GetInputIndex(), &InputNodeId)) + { + // Change its Parameter xformtype + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + InputNodeId, sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + } + + // Since our input objects are all plugged into a merge node + // We want to also update the transform type on the object merge plugged into the merge node + HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); + if ((ParentNodeId >= 0) && (InputType != EHoudiniInputType::Geometry) && (InputType != EHoudiniInputType::Asset)) + { + HAPI_NodeId InputObjectNodeId = -1; + int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); + for (int n = 0; n < NumberOfInputMeshes; n++) + { + // Get the Input node ID from the host ID + InputObjectNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ParentNodeId, n, &InputObjectNodeId)) + continue; + + if (InputObjectNodeId == -1) + continue; + + // Change the xformtype parameter on the object merge + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId, + sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + // Pack before merge is only available for Geo/World input + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType != EHoudiniInputType::World + && InputType != EHoudiniInputType::Geometry) + { + // Nothing to change + return true; + } + + uint32 nPackValue = InInput->GetPackBeforeMerge() ? 1 : 0; + + // Get the Input node ID from the host ID + HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); + + bool bSuccess = true; + const std::string sPack = "pack"; + + // We'll be going through each input object plugged in the input's merge node + // and change the pack parameter there + HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); + if (ParentNodeId >= 0) + { + HAPI_NodeId InputObjectNodeId = -1; + int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); + for (int n = 0; n < NumberOfInputMeshes; n++) + { + // Get the Input node ID from the host ID + InputObjectNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ParentNodeId, n, &InputObjectNodeId)) + continue; + + if (InputObjectNodeId == -1) + continue; + + // Change the pack parameter on the object merge + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId, + sPack.c_str(), 0, nPackValue)) + bSuccess = false; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + // Transform offsets are only for geometry inputs + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType != EHoudiniInputType::Geometry) + { + // Nothing to change + return true; + } + + // Get the input objects + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Update each object's transform offset + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + // If the Input mesh has a Transform offset + FTransform TransformOffset = CurrentInputObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CurrentInputObject->InputObjectNodeId, &HapiTransform), false); + } + } + + return true; +} + +bool +FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + EHoudiniInputType InputType = InInput->GetInputType(); + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Iterate on all the input objects and see if they need to be uploaded + bool bSuccess = true; + TArray CreatedNodeIds; + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; + if (!CurrentInputObject->HasChanged() && CurrentInputObjectNodeId >= 0) + { + // If this object hasn't changed, no need to upload it + // but we need to keep its created input node + if (CurrentInputObject->Type == EHoudiniInputObjectType::Actor) + { + // If this input object is an actor, it actually contains other input + // objects for each of his components, keep them as well + UHoudiniInputActor* InputActor = Cast(CurrentInputObject); + if (InputActor && !InputActor->IsPendingKill()) + { + for (auto CurrentComp : InputActor->GetActorComponents()) + { + if (!CurrentComp || CurrentComp->IsPendingKill()) + continue; + + int32& CurrentCompNodeId = CurrentComp->InputObjectNodeId; + if (!CurrentComp->HasChanged() && CurrentCompNodeId >= 0) + { + // If the component hasnt changed and is valid, keep it + CreatedNodeIds.Add(CurrentCompNodeId); + } + else + { + // Upload the component input object to Houdini + if (!UploadHoudiniInputObject(InInput, CurrentComp, CreatedNodeIds)) + bSuccess = false; + } + } + } + } + else + { + // No changes, keep it + CreatedNodeIds.Add(CurrentInputObjectNodeId); + } + } + else + { + // Upload the current input object to Houdini + if (!UploadHoudiniInputObject(InInput, CurrentInputObject, CreatedNodeIds)) + bSuccess = false; + } + } + + // If we haven't created any input, invalidate our input node id + if (CreatedNodeIds.Num() == 0) + { + if (!InInput->HasInputTypeChanged()) + { + int32 InputNodeId = InInput->GetInputNodeId(); + TArray PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); + + if (InInput->GetInputType() == EHoudiniInputType::Asset) + { + UHoudiniAssetComponent * OuterHAC = Cast(InInput->GetOuter()); + HAPI_NodeId AssetId = OuterHAC->GetAssetId(); + + // Disconnect the asset input + if (InputNodeId >= 0 && InInput->GetInputIndex() >= 0) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), AssetId, InInput->GetInputIndex())); + } + } + else if (InInput->GetInputType() == EHoudiniInputType::World) + { + // World nodes are handled by InputObjects () (with FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete) + } + else + { + if (InputNodeId >= 0) + { + for (int32 Idx = 0; Idx < PreviousInputObjectNodeIds.Num(); Idx++) + { + + // Get the object merge connected to the merge node + HAPI_NodeId InputObjectMergeId = -1; + HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); + + // Disconnect the two nodes + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); + + // Destroy the object merge node, do not delete other HDA (Asset input type) + HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); + } + } + } + } + InInput->GetCreatedDataNodeIds().Empty(); + InInput->SetInputNodeId(-1); + return bSuccess; + } + + // Get the current input's NodeId + HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); + // Check that the current input's node ID is still valid + if (InputNodeId < 0 || !FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + { + // This input doesn't have a valid NodeId yet, + // we need to create this input's merge node and update this input's node ID + FString MergeName = InInput->GetNodeBaseName() + TEXT("_Merge"); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/merge"), MergeName, true, &InputNodeId), false); + + InInput->SetInputNodeId(InputNodeId); + } + + //TODO: + // Do we want to update the input's transform? + if (false) + { + FTransform ComponentTransform = FTransform::Identity; + USceneComponent* OuterComp = Cast(InInput->GetOuter()); + if (OuterComp && !OuterComp->IsPendingKill()) + ComponentTransform = OuterComp->GetComponentTransform(); + + FHoudiniEngineUtils::HapiSetAssetTransform(InputNodeId, ComponentTransform); + //HapiUpdateInputNodeTransform(InputNodeId, ComponentTransform); + } + + // Connect all the input objects to the merge node now + int32 InputIndex = 0; + for (auto CurrentNodeId : CreatedNodeIds) + { + if (CurrentNodeId < 0) + continue; + + if (InputNodeId == CurrentNodeId) + continue; + + // Connect the current input object to the merge node + HOUDINI_CHECK_ERROR(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + InputNodeId, InputIndex++, CurrentNodeId, 0)); + } + + // Check if we need to disconnect extra input objects nodes from the merge + // This can be needed when the input had more input objects on the previous cook + TArray& PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); + if (!InInput->HasInputTypeChanged()) + { + for (int32 Idx = CreatedNodeIds.Num(); Idx < PreviousInputObjectNodeIds.Num(); Idx++) + { + // Get the object merge connected to the merge node + HAPI_NodeId InputObjectMergeId = -1; + if (InInput->GetInputType() != EHoudiniInputType::Asset) + HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); + + // Disconnect the two nodes + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); + + // Destroy the object merge node, do not destroy other HDA (Asset input type) + if (InInput->GetInputType() != EHoudiniInputType::Asset) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); + } + } + } + + // Keep track of all the nodes plugged into our input's merge + PreviousInputObjectNodeIds = CreatedNodeIds; + + // Finally, connect our main input node to the asset + bSuccess = ConnectInputNode(InInput); + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + EHoudiniInputType InputType = InInput->GetInputType(); + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Iterate on all the input objects and see if their transform needs to be uploaded + bool bSuccess = true; + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; + if (!CurrentInputObject->HasTransformChanged()) + continue; + + // Upload the current input object's transform to Houdini + if (!UploadHoudiniInputTransform(InInput, CurrentInputObject)) + { + bSuccess = false; + continue; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::ConnectInputNode(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + HAPI_NodeId AssetNodeId = InInput->GetAssetNodeId(); + if (AssetNodeId < 0) + return false; + + HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); + if (InputNodeId < 0) + return false; + + // Helper for connecting our input or setting the object path parameter + if (InInput->IsObjectPathParameter()) + { + // Now we can assign the input node path to the parameter + std::string ParamNameString = TCHAR_TO_UTF8(*(InInput->GetName())); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue( + FHoudiniEngine::Get().GetSession(), AssetNodeId, + ParamNameString.c_str(), InputNodeId), false); + } + else + { + // TODO: CHECK ME! + //if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + // return false; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), AssetNodeId, + InInput->GetInputIndex(), InputNodeId, 0), false); + } + + return true; +} + +bool +FHoudiniInputTranslator::UploadHoudiniInputObject( + UHoudiniInput* InInput, + UHoudiniInputObject* InInputObject, + TArray& OutCreatedNodeIds) +{ + if (!InInput || !InInputObject) + return false; + + FString ObjBaseName = InInput->GetNodeBaseName(); + + bool bSuccess = true; + switch (InInputObject->Type) + { + case EHoudiniInputObjectType::Object: + { + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForObject(ObjBaseName, InInputObject); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::StaticMesh: + { + UHoudiniInputStaticMesh* InputSM = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( + ObjBaseName, InputSM, InInput->GetExportLODs(), InInput->GetExportSockets(), + InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + { + // If this SM input object takes in a BP, add all its BP StaticMesh components input object node id to the created id list. + if (InputSM->bIsBlueprint()) + { + for (auto & CurSMObj : InputSM->BlueprintStaticMeshes) + OutCreatedNodeIds.Add(CurSMObj->InputObjectNodeId); + } + else + { + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + } + } + + break; + } + + case EHoudiniInputObjectType::SkeletalMesh: + { + UHoudiniInputSkeletalMesh* InputSkelMes = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(ObjBaseName, InputSkelMes); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::SceneComponent: + { + UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(ObjBaseName, InputSceneComp); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::StaticMeshComponent: + { + UHoudiniInputMeshComponent* InputSMC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( + ObjBaseName, InputSMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + { + UHoudiniInputInstancedMeshComponent* InputISMC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( + ObjBaseName, InputISMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::SplineComponent: + { + UHoudiniInputSplineComponent* InputSpline = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(ObjBaseName, InputSpline, InInput->GetUnrealSplineResolution()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::HoudiniSplineComponent: + { + UHoudiniInputHoudiniSplineComponent* InputCurve = Cast(InInputObject); + + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve, InInput->IsAddRotAndScaleAttributesEnabled()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::HoudiniAssetActor: + case EHoudiniInputObjectType::HoudiniAssetComponent: + { + UHoudiniInputHoudiniAsset* InputHAC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Actor: + { + UHoudiniInputActor* InputActor = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, OutCreatedNodeIds); + break; + } + + case EHoudiniInputObjectType::Landscape: + { + UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForLandscape(ObjBaseName, InputLandscape, InInput); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Brush: + { + UHoudiniInputBrush* InputBrush = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForBrush(ObjBaseName, InputBrush, InInput->GetBoundSelectorObjectArray()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::CameraComponent: + { + UHoudiniInputCameraComponent* InputCamera = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForCamera(ObjBaseName, InputCamera); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::DataTable: + { + UHoudiniInputDataTable* InputDT = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(ObjBaseName, InputDT); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + { + UHoudiniInputFoliageType_InstancedStaticMesh* const InputFoliageTypeSM = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + ObjBaseName, InputFoliageTypeSM, InInput->GetExportLODs(), InInput->GetExportSockets(), + InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Invalid: + //default: + break; + } + + // Mark that input object as not changed + if (bSuccess) + { + InInputObject->MarkChanged(false); + InInputObject->SetNeedsToTriggerUpdate(false); + } + else + { + // We couldn't update/create that input object, keep it changed but prevent it from trigger updates + InInputObject->SetNeedsToTriggerUpdate(false); + } + + return bSuccess; +} + + +// Upload transform for an input's InputObject +bool +FHoudiniInputTranslator::UploadHoudiniInputTransform( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject) +{ + if (!InInput || !InInputObject) + return false; + + auto UpdateTransform = [](const FTransform& InTransform, const HAPI_NodeId& InNodeId) + { + // Translate the Transform to HAPI + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(InTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InNodeId, &HapiTransform), false); + + return true; + }; + + bool bSuccess = true; + switch (InInputObject->Type) + { + case EHoudiniInputObjectType::StaticMesh: + { + // Simply update the Input mesh's Transform offset + if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + break; + } + + case EHoudiniInputObjectType::StaticMeshComponent: + { + // Update using the static mesh component's transform + UHoudiniInputMeshComponent* InSMC = Cast(InInputObject); + if (!InSMC || InSMC->IsPendingKill()) + { + bSuccess = false; + break; + } + + FTransform NewTransform = InSMC->GetStaticMeshComponent() ? InSMC->GetStaticMeshComponent()->GetComponentTransform() : InInputObject->Transform; + if(!UpdateTransform(NewTransform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + // Update the InputObject's transform + InInputObject->Transform = NewTransform; + + break; + } + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + { + // TODO: Only update the instances transform + break; + } + + case EHoudiniInputObjectType::HoudiniSplineComponent: + { + // TODO: Simply update the curve's transform? + break; + } + + case EHoudiniInputObjectType::HoudiniAssetActor: + case EHoudiniInputObjectType::HoudiniAssetComponent: + { + // TODO: Check, nothing to do? + break; + } + + case EHoudiniInputObjectType::Actor: + { + UHoudiniInputActor* InputActor = Cast(InInputObject); + if (!InputActor || InputActor->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Update the actor's transform + // To avoid further updates + if (InputActor->GetActor()) + InputActor->Transform = InputActor->GetActor()->GetTransform(); + + // Iterate on all the actor input objects and see if their transform needs to be uploaded + // TODO? Also update the component's actor transform?? + for (auto& CurrentComponent : InputActor->GetActorComponents()) + { + if (!CurrentComponent || CurrentComponent->IsPendingKill()) + continue; + + if (!CurrentComponent->HasTransformChanged()) + continue; + + // Upload the current input object's transform to Houdini + if (!UploadHoudiniInputTransform(InInput, CurrentComponent)) + { + bSuccess = false; + continue; + } + } + break; + } + + case EHoudiniInputObjectType::SceneComponent: + { + UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); + if (!InputSceneComp || InputSceneComp->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Update the component transform to avoid further updates + if (InputSceneComp->GetSceneComponent()) + InputSceneComp->Transform = InputSceneComp->GetSceneComponent()->GetComponentTransform(); + + break; + } + + case EHoudiniInputObjectType::Landscape: + { + // + UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); + if (!InputLandscape || InputLandscape->IsPendingKill()) + { + bSuccess = false; + break; + } + + // + ALandscapeProxy* Landscape = InputLandscape->GetLandscapeProxy(); + if (!Landscape || Landscape->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Only apply diff for landscape since the HF's transform is used for value conversion as well + FTransform CurrentTransform = InputLandscape->Transform; + FTransform NewTransform = Landscape->ActorToWorld(); + + // Only handle position/rotation differences + FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); + FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); + + // Now get the HF's current transform + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) + { + bSuccess = false; + break; + } + + // Convert it to unreal + FTransform HFTransform; + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); + + // Apply the position offset if needed + if (!PosDiff.IsZero()) + HFTransform.AddToTranslation(PosDiff); + + // Apply the rotation offset if needed + if (!RotDiff.IsIdentity()) + HFTransform.ConcatenateRotation(RotDiff); + + // Convert back to a HAPI Transform and update the HF's transform + HAPI_TransformEuler NewHAPITransform; + FHoudiniApi::TransformEuler_Init(&NewHAPITransform); + FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); + NewHAPITransform.position[1] = 0.0f; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InputLandscape->InputObjectNodeId, &NewHAPITransform)) + { + bSuccess = false; + break; + } + + // Update the cached transform + InputLandscape->Transform = NewTransform; + } + + case EHoudiniInputObjectType::Brush: + { + // TODO: Update the Brush's transform + break; + } + + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + { + // Simply update the Input mesh's Transform offset + if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + break; + } + + // Unsupported + case EHoudiniInputObjectType::Object: + case EHoudiniInputObjectType::SkeletalMesh: + case EHoudiniInputObjectType::SplineComponent: + { + break; + } + + case EHoudiniInputObjectType::Invalid: + default: + break; + } + + // Mark that input object as not changed + if (bSuccess) + { + InInputObject->MarkTransformChanged(false); + InInputObject->SetNeedsToTriggerUpdate(false); + } + else + { + // We couldn't update/create that input object, keep it changed but prevent it from trigger updates + InInputObject->SetNeedsToTriggerUpdate(false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForObject(const FString& InObjNodeName, UHoudiniInputObject* InObject) +{ + if (!InObject) + return false; + + UObject* Object = InObject->GetObject(); + if (!Object || Object->IsPendingKill()) + return true; + + FString NodeName = InObjNodeName + TEXT("_") + Object->GetName(); + + // For UObjects we can't upload much, but can still create an input node + // with a single point, with an attribute pointing to the input object's path + HAPI_NodeId InputNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); + + // Update this input object's NodeId and ObjectNodeId + InObject->InputNodeId = (int32)InputNodeId; + InObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 2; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = 1; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); + + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + FVector ObjectPosition = InObject->Transform.GetLocation(); + TArray Position = + { + ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION + }; + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Position.GetData(), 0, + AttributeInfoPoint.count), false); + } + + { + // Create point attribute info for the path. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); + + // Set the point's path attribute + FString ObjectPathName = Object->GetPathName(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + ObjectPathName, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputNodeId), false); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( + const FString& InObjNodeName, + UHoudiniInputStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UBlueprint* BP = nullptr; + UStaticMesh* SM = nullptr; + + FString SMName = InObjNodeName + TEXT("_"); + + // Get Blueprint or StaticMesh + if (InObject->bIsBlueprint()) + { + BP = InObject->GetBlueprint(); + if (!BP || BP->IsPendingKill()) + return true; + + SMName += BP->GetName(); + } + else + { + SM = InObject->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + SMName += SM->GetName(); + } + + // Marshall the Static Mesh to Houdini + bool bSuccess = true; + + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference; + if (SM) + AssetReference += SM->GetFullName(); + + if (BP) + AssetReference += BP->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference( + InObject->InputNodeId, AssetReference, SMName, InObject->Transform); + } + else + { + TArray StaticMeshComponents; + + // The input object is a Blueprint, Get all its StaticMeshes + if (BP) + { + USimpleConstructionScript* SCS = BP->SimpleConstructionScript; + if (SCS && !SCS->IsPendingKill()) + { + const TArray& Nodes = SCS->GetAllNodes(); + for (auto & CurNode : Nodes) + { + if (!CurNode || CurNode->IsPendingKill()) + continue; + + UActorComponent * CurComp = CurNode->ComponentTemplate; + if (!CurComp || CurComp->IsPendingKill()) + continue; + + UStaticMeshComponent* CurSMC = Cast(CurComp); + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + UStaticMesh* CurSM = CurSMC->GetStaticMesh(); + if (CurSM && !CurSM->IsPendingKill()) + StaticMeshComponents.Add(CurSMC); + + } + } + } + + // Clear previous Blueprint Static Mesh Comps (if there is any) + InObject->BlueprintStaticMeshes.Empty(); + + // This is a BP, add all the BP SM comps to its BlueprintStaticMeshes list. + if (InObject->bIsBlueprint()) + { + for (auto & CurSMC : StaticMeshComponents) + { + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + UHoudiniInputStaticMesh* SMObject = Cast( + UHoudiniInputObject::CreateTypedInputObject(CurSMC->GetStaticMesh(), InObject, InObject->GetName() + TEXT("_") + CurSMC->GetName())); + + if (!SMObject || SMObject->IsPendingKill()) + continue; + + bSuccess &= FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + CurSMC->GetStaticMesh(), SMObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); + + InObject->SetImportAsReference(false); + + // Update this input object's OBJ NodeId + SMObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(SMObject->InputNodeId); + + // Update the component's transform + FTransform ComponentTransform = CurSMC->GetRelativeTransform(); + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), SMObject->InputObjectNodeId, &HapiTransform), false); + } + + InObject->BlueprintStaticMeshes.Add(SMObject); + } + + return true; + } + // This is a normal static mesh input, process it normally as a static mesh Input Object + else + { + bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, InObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); + } + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // If the Input mesh has a Transform offset + FTransform TransformOffset = InObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USkeletalMesh* SkelMesh = InObject->GetSkeletalMesh(); + if (!SkelMesh || SkelMesh->IsPendingKill()) + return true; + + // Get the SM's transform offset + FTransform TransformOffset = InObject->Transform; + + // TODO + // Support this type of input object + // FUnrealMeshTranslator::CreateInputNodeForSkeletalMesh(...) + + return HapiCreateInputNodeForObject(InObjNodeName, InObject); +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USceneComponent* SceneComp = InObject->GetSceneComponent(); + if (!SceneComp || SceneComp->IsPendingKill()) + return true; + + // Get the Scene Component's transform + FTransform TransformOffset = InObject->Transform; + + // Get the parent Actor's transform + FTransform ParentTransform = InObject->ActorTransform; + + // Dont do that! + return false; + + // TODO + // Support this type of input object + return HapiCreateInputNodeForObject(InObjNodeName, InObject); +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UStaticMeshComponent* SMC = InObject->GetStaticMeshComponent(); + if (!SMC || SMC->IsPendingKill()) + return true; + + // Get the component's Static Mesh + UStaticMesh* SM = InObject->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + // Marshall the Static Mesh to Houdini + FString SMCName = InObjNodeName + TEXT("_") + SMC->GetName(); + + bool bSuccess = true; + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference = SM->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, AssetReference, SMCName, InObject->Transform); + + } + else + { + bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, InObject->InputNodeId, SMCName, SMC, bExportLODs, bExportSockets, bExportColliders); + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // Update this input object's cache data + InObject->Update(SMC); + + // Update the component's transform + FTransform ComponentTransform = InObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputInstancedMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UObject* Object = InObject->GetObject(); + if (!Object || Object->IsPendingKill()) + return true; + + // Get the ISMC + UInstancedStaticMeshComponent* ISMC = InObject->GetInstancedStaticMeshComponent(); + if (!ISMC || ISMC->IsPendingKill()) + return true; + + HAPI_NodeId NewNodeId = -1; + if (!FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( + ISMC, InObjNodeName, NewNodeId, bExportLODs, bExportSockets, bExportColliders, false)) + return false; + + // Update this input object's node IDs + InObject->InputNodeId = NewNodeId; + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); + + // Update the component's cached instances + InObject->Update(ISMC); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USplineComponent* Spline = InObject->GetSplineComponent(); + if (!Spline || Spline->IsPendingKill()) + return true; + + + int32 NumberOfSplineControlPoints = InObject->NumberOfSplineControlPoints; + + TArray SplineControlPoints = InObject->SplineControlPoints; + + FString NodeName = InObjNodeName + TEXT("_") + InObject->GetName(); + + if (!FUnrealSplineTranslator::CreateInputNodeForSplineComponent(Spline, SplineResolution, InObject->InputNodeId, NodeName)) + return false; + + // Cache the exported curve's data to the input object + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + InObject->MarkChanged(true); + + //if (!HapiCreateInputNodeForObject(InObjNodeName, InObject)) + // return false; + + // Update the component's cached data + InObject->Update(Spline); + + // Update the component's transform + FTransform ComponentTransform = InObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject, bool bInAddRotAndScaleAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UHoudiniSplineComponent* Curve = InObject->GetCurveComponent(); + if (!Curve || Curve->IsPendingKill()) + return true; + + if (!FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(Curve, bInAddRotAndScaleAttributes)) + return false; + + // See if the component needs it node Id invalidated + //if (InObject->InputNodeId < 0) + // Curve->SetNodeId(InObject->InputNodeId); + + // Cache the exported curve's data to the input object + InObject->InputNodeId = Curve->GetNodeId(); + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + //InObject->CurveType = Curve->GetCurveType(); + //InObject->CurveMethod = Curve->GetCurveMethod(); + //InObject->Reversed = Curve->IsReversed(); + InObject->Update(Curve); + + InObject->MarkChanged(true); + + return true; +} + +bool +FHoudiniInputTranslator:: +HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UHoudiniAssetComponent* InputHAC = InObject->GetHoudiniAssetComponent(); + if (!InputHAC || InputHAC->IsPendingKill()) + return true; + + if (!InputHAC->CanDeleteHoudiniNodes()) + return true; + + UHoudiniInput* HoudiniInput = Cast(InObject->GetOuter()); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + return true; + + UHoudiniAssetComponent* OuterHAC = Cast(HoudiniInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return true; + + // Do not allow using ourself as an input, terrible things would happen + if (InputHAC->GetAssetId() == OuterHAC->GetAssetId()) + return false; + + // If previously imported as ref, delete the input node. + if (InObject->InputNodeId > -1 && InObject->GetImportAsReference()) + { + int32 PreviousInputNodeId = InObject->InputNodeId; + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InObject->GetName()); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InObject->GetName()); + } + } + + InObject->SetImportAsReference(bImportAsReference); + + // If this object is in an Asset input, we need to set the InputNodeId directl + // to avoid creating extra merge nodes. World inputs should not do that! + bool bIsAssetInput = HoudiniInput->GetInputType() == EHoudiniInputType::Asset; + + if (bImportAsReference) + { + InObject->InputNodeId = -1; + InObject->InputObjectNodeId = -1; + + if(bIsAssetInput) + HoudiniInput->SetInputNodeId(-1); + + // Start by getting the Object's full name + FString AssetReference = InputHAC->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + if (!FHoudiniInputTranslator::CreateInputNodeForReference( + InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform)) // do not delete previous node if it was HAC + return false; + + if (bIsAssetInput) + HoudiniInput->SetInputNodeId(InObject->InputNodeId); + } + + InputHAC->AddDownstreamHoudiniAsset(OuterHAC); + + //if (HAC->NeedsInitialization()) + // HAC->MarkAsNeedInstantiation(); + + //HoudiniInput->SetAssetNodeId(HAC->GetAssetId()); + + // TODO: This might be uneeded as this function should only be called + // after we're not wiating on the input asset... + if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) + { + // If the input HAC needs to be instantiated, tell it do so + InputHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + // Mark this object's input as changed so we can properly update after the input HDA's done instantiating/cooking + HoudiniInput->MarkChanged(true); + } + + if (InputHAC->NeedsInitialization() || InputHAC->NeedUpdate()) + return false; + + if (!bImportAsReference) + { + if (bIsAssetInput) + HoudiniInput->SetInputNodeId(InputHAC->GetAssetId()); + + InObject->InputNodeId = InputHAC->GetAssetId(); + } + + InObject->InputObjectNodeId = InObject->InputNodeId; + + bool bReturn = InObject->InputNodeId > -1; + + if(bIsAssetInput) + bReturn = FHoudiniInputTranslator::ConnectInputNode(HoudiniInput); + + return bReturn; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForActor( + UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (!InObject || InObject->IsPendingKill()) + return false; + + AActor* Actor = InObject->GetActor(); + if (!Actor || Actor->IsPendingKill()) + return true; + + // Check if this is a world input and if this is a HoudiniAssetActor + // If so we need to build static meshes for any proxy meshes + if (InInput->GetInputType() == EHoudiniInputType::World && Actor->IsA()) + { + AHoudiniAssetActor *HAA = Cast(Actor); + UHoudiniAssetComponent *HAC = HAA->GetHoudiniAssetComponent(); + if (HAC && !HAC->IsPendingKill()) + { + if (HAC->HasAnyCurrentProxyOutput()) + { + bool bPendingDeleteOrRebuild = false; + bool bInvalidState = false; + const bool bIsHoudiniCookedDataAvailable = HAC->IsHoudiniCookedDataAvailable(bPendingDeleteOrRebuild, bInvalidState); + if (bIsHoudiniCookedDataAvailable) + { + // Build the static mesh + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); + // Update the input object since a new StaticMeshComponent could have been created + UObject *InputObject = InObject->GetObject(); + if (InputObject && !InputObject->IsPendingKill()) + { + InObject->Update(InputObject); + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } + } + else if (!bPendingDeleteOrRebuild && !bInvalidState) + { + // Request a cook with no proxy output + HAC->MarkAsNeedCook(); + HAC->SetNoProxyMeshNextCookRequested(true); + } + } + else if (InObject->GetActorComponents().Num() == 0 && HAC->HasAnyOutputComponent()) + { + // The HAC has non-proxy output components, but the InObject does not have any + // actor components. This can arise after a cook if previously there were only + // proxies and the input was created when there were only proxies + // Try to update the input to find new components + UObject *InputObject = InObject->GetObject(); + if (InputObject && !InputObject->IsPendingKill()) + { + InObject->Update(InputObject); + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } + } + } + } + + // Now, commit all of this actor's component + int32 ComponentIdx = 0; + for (UHoudiniInputSceneComponent* CurComponent : InObject->GetActorComponents()) + { + if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) + ComponentIdx++; + } + + // TODO: We should call Update here... + // needs to be fixed + + // Cache our transformn + InObject->Transform = Actor->GetTransform(); + + // Do something for our actor's transform? + /* + // TODO + // Support this type of input object + FString ObjNodeName = InInput->GetNodeBaseName(); + return HapiCreateInputNodeForObject(ObjNodeName, InObject); + */ + + //TODO? Check + // return true if we have at least uploaded one component + // return (ComponentIdx > 0); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( + const FString& InObjNodeName, UHoudiniInputLandscape* InObject, UHoudiniInput* InInput) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + if (!InInput || InInput->IsPendingKill()) + return false; + + ALandscapeProxy* Landscape = InObject->GetLandscapeProxy(); + if (!Landscape || Landscape->IsPendingKill()) + return true; + + EHoudiniLandscapeExportType ExportType = InInput->GetLandscapeExportType(); + + bool bSucess = false; + if (ExportType == EHoudiniLandscapeExportType::Heightfield) + { + // Ensure we destroy any (Houdini) input nodes before clobbering this object with a new heightfield. + //DestroyInputNodes(InInput, InInput->GetInputType()); + bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); + } + else + { + bool bExportLighting = InInput->bLandscapeExportLighting; + bool bExportMaterials = InInput->bLandscapeExportMaterials; + bool bExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; + bool bExportTileUVs = InInput->bLandscapeExportTileUVs; + bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; + bool bExportAsMesh = InInput->LandscapeExportType == EHoudiniLandscapeExportType::Mesh; + + bSucess = FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( + Landscape, InObject->InputNodeId, InObjNodeName, + bExportAsMesh, bExportTileUVs, bExportNormalizedUVs, bExportLighting, bExportMaterials); + } + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + InObject->Update(Landscape); + + return bSucess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForBrush(const FString& InObjNodeName, UHoudiniInputBrush* InObject, TArray* ExcludeActors) +{ + if (!IsValid(InObject)) + return false; + + ABrush* BrushActor = InObject->GetBrush(); + if (!IsValid(BrushActor)) + return true; + + if (!FUnrealBrushTranslator::CreateInputNodeForBrush(InObject, BrushActor, ExcludeActors, InObject->InputNodeId, InObjNodeName)) + return false; + + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + InObject->Update(BrushActor); + + return true; +} + + + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, UHoudiniInputCameraComponent* InInputObject) +{ + if (!InInputObject || InInputObject->IsPendingKill()) + return false; + + UCameraComponent* Camera = InInputObject->GetCameraComponent(); + if (!Camera || Camera->IsPendingKill()) + return true; + + FString NodeName = InNodeName + TEXT("_") + Camera->GetName(); + + // Create the camera OBJ. + int32 CameraNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( + -1, TEXT("Object/cam"), InNodeName, true, &CameraNodeId), false); + + // set "Pixel Aspect Ratio" (aspect) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "aspect", 0, InInputObject->AspectRatio), false); + + // set "Projection" (projection) (0 persp, 1 ortho) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "projection", 0, InInputObject->bIsOrthographic ? 1 : 0), false); + + // set Ortho Width (orthowidth) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "orthowidth", 0, InInputObject->OrthoWidth), false); + + // set Near Clippin (near) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "near", 0, InInputObject->OrthoNearClipPlane), false); + + // set far clipping (far) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "far", 0, InInputObject->OrthoFarClipPlane), false); + + // Set the transform + HAPI_TransformEuler H_Transform; + FHoudiniApi::TransformEuler_Init(&H_Transform); + FHoudiniEngineUtils::TranslateUnrealTransform(Camera->GetComponentTransform(), H_Transform); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CameraNodeId, &H_Transform), false); + + // Update the component's transform + FTransform ComponentTransform = InInputObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Camera orientation need to be adjusted + HapiTransform.rotationEuler[1] += -90.0f; + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CameraNodeId, &HapiTransform), false); + } + + // Update this input's NodeId and ObjectNodeId + InInputObject->InputNodeId = -1;// (int32)CameraNodeId; + InInputObject->InputObjectNodeId = (int32)CameraNodeId; + + // Update this input object's cache data + InInputObject->Update(Camera); + + return true; +} + +bool +FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // We need to call BuildAllInputs here to update all the inputs, + // and make sure that the object path parameter inputs' parameter ids are up to date + if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) + return false; + + // We need to update the AssetID stored on all the inputs + // and mark all the input objects for this input type as changed + int32 HACAssetId = HAC->GetAssetId(); + for (auto CurrentInput : HAC->Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // + CurrentInput->SetAssetNodeId(HACAssetId); + + // We need to delete the nodes created for the input objects if they are valid + // (since the node IDs are transients, this likely means we're handling a recook/rebuild + // and therefore expect to recreate the input nodes) + DestroyInputNodes(CurrentInput, CurrentInput->GetInputType()); + } + + return true; +} + + + +bool +FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Only tick/cook when in Editor + // This prevents PIE cooks or runtime cooks due to inputs moving + AActor* ActorOwner = HAC->GetOwner(); + if (ActorOwner) + { + if (!ActorOwner->GetWorld() || (ActorOwner->GetWorld()->WorldType != EWorldType::Editor)) + return false; + } + +#if WITH_EDITOR + // Stop outliner objects from causing recooks while input objects are dragged around + if (FHoudiniMoveTracker::Get().IsObjectMoving) + { + //HOUDINI_LOG_MESSAGE(TEXT("Object moving, not updating world inputs!")); + return false; + } +#endif + + for (auto CurrentInput : HAC->Inputs) + { + if (!CurrentInput) + continue; + if (CurrentInput->GetInputType() != EHoudiniInputType::World) + continue; + + UpdateWorldInput(CurrentInput); + } + + return true; +} + +bool +FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (InInput->GetInputType() != EHoudiniInputType::World) + return false; + + TArray* InputObjectsPtr = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjectsPtr) + return false; + + bool bHasChanged = false; + if (InInput->IsWorldInputBoundSelector() && InInput->GetWorldInputBoundSelectorAutoUpdates()) + { + // If the input is in bound selector mode, and auto-update is enabled + // update the actors selected by the bounds first + bHasChanged = InInput->UpdateWorldSelectionFromBoundSelectors(); + } + + // See if we need to update the components for this input + // look for deleted actors/components + TArray ObjectToDeleteIndices; + for(int32 InputObjIdx = 0; InputObjIdx < InputObjectsPtr->Num(); InputObjIdx++) + { + UHoudiniInputActor* ActorObject = Cast((*InputObjectsPtr)[InputObjIdx]); + if (!ActorObject || ActorObject->IsPendingKill()) + continue; + + // Make sure the actor is still valid + AActor* const Actor = ActorObject->GetActor(); + bool bValidActorObject = Actor && !Actor->IsPendingKill(); + + // For BrushActors, the brush and actors must be valid as well + UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); + if (bValidActorObject && BrushActorObject) + { + ABrush* BrushActor = BrushActorObject->GetBrush(); + if (!IsValid(BrushActor)) + bValidActorObject = false; + else if (!IsValid(BrushActor->Brush)) + bValidActorObject = false; + } + + // The actor is no longer valid, mark it for deletion + if (!bValidActorObject) + { + if ((ActorObject->InputNodeId > 0) || (ActorObject->InputObjectNodeId > 0)) + { + ActorObject->InvalidateData(); + // We only need to update the input if the actors nodes were created in Houdini + bHasChanged = true; + } + + // Delete the Actor object + ObjectToDeleteIndices.Add(InputObjIdx); + continue; + } + + if (ActorObject->HasActorTransformChanged()) + { + ActorObject->MarkTransformChanged(true); + bHasChanged = true; + } + + if (ActorObject->HasContentChanged()) + { + ActorObject->MarkChanged(true); + bHasChanged = true; + } + + // Ensure we are aware of all the components of the actor + ActorObject->Update(Actor); + + // Check if any components have content or transform changes + for (auto CurActorComp : ActorObject->GetActorComponents()) + { + if (CurActorComp->HasComponentTransformChanged()) + { + CurActorComp->MarkTransformChanged(true); + bHasChanged = true; + } + + if (CurActorComp->HasComponentChanged()) + { + CurActorComp->MarkChanged(true); + bHasChanged = true; + } + } + + // Check if we added/removed any components in the call to update + if (ActorObject->GetLastUpdateNumComponentsAdded() > 0 || ActorObject->GetLastUpdateNumComponentsRemoved() > 0) + { + bHasChanged = true; + if (ActorObject->GetLastUpdateNumComponentsRemoved() > 0) + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } + } + + // Delete the actor objects that were marked for deletion + for (int32 ToDeleteIdx = ObjectToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) + InputObjectsPtr->RemoveAt(ObjectToDeleteIndices[ToDeleteIdx]); + + // Mark the input as changed if need so it will trigger an upload + if (bHasChanged) + InInput->MarkChanged(true); + + return true; +} + + +bool +FHoudiniInputTranslator::CreateInputNodeForReference( + HAPI_NodeId& InputNodeId, + const FString& InRef, + const FString& InputNodeName, + const FTransform& InTransform) +{ + HAPI_NodeId NewNodeId = -1; + + // Create a single input node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_UTF8(*InputNodeName)), false); + + /* + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, nullptr), false); + */ + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) + return false; + + // We have now created a valid new input node, delete the previous one + HAPI_NodeId PreviousInputNodeId = InputNodeId; + if (PreviousInputNodeId >= 0) + { + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); + } + } + + // Create and initialize a part containing one point with a point attribute + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + + PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = 1; + PartInfo.vertexCount = 0; + PartInfo.faceCount = 0; + PartInfo.pointCount = 1; + PartInfo.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, &PartInfo), false); + + // Point Position Attribute + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + FVector ObjectPosition = InTransform.GetLocation(); + TArray Position = + { + ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION + }; + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Position.GetData(), 0, + AttributeInfoPoint.count), false); + } + + // String Attribute + { + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint), false); + + // Set string attribute + std::string AttriString = TCHAR_TO_ANSI(*InRef); + const char* AttriStringRaw = AttriString.c_str(); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint, + &AttriStringRaw, 0, 1), false); + } + + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NewNodeId), false); + + InputNodeId = NewNodeId; + return true; +} + + + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeName, UHoudiniInputDataTable* InInputObject) +{ + //TODO + if (!InInputObject || InInputObject->IsPendingKill()) + return false; + + UDataTable* DataTable = InInputObject->GetDataTable(); + if (!DataTable || DataTable->IsPendingKill()) + return true; + + // Get the DataTable data as string + TArray> TableData = DataTable->GetTableData(EDataTableExportFlags::None); + if (TableData.Num() <= 1) + return true; + + int32 NumRows = TableData.Num() - 1; + int32 NumAttributes = TableData[0].Num(); + if (NumRows <= 0 || NumAttributes <= 0) + return true; + + // Create the input node + FString NodeName = InNodeName + TEXT("_") + DataTable->GetName(); + HAPI_NodeId InputNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); + + // Update this input object's NodeId and ObjectNodeId + InInputObject->InputNodeId = (int32)InputNodeId; + InInputObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = NumAttributes; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = NumRows; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); + + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + TArray Positions; + Positions.SetNum(NumRows * 3); + for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) + { + Positions[RowIdx * 3] = 0.0f; + Positions[RowIdx * 3 + 1] = (float)RowIdx; + Positions[RowIdx * 3 + 2] = 0.0f; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Positions.GetData(), 0, + AttributeInfoPoint.count), false); + } + + { + // Create point attribute info for the path. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); + + // Get the object path + FString ObjectPathName = DataTable->GetPathName(); + + // Create an array + TArray ObjectPaths; + ObjectPaths.Init(ObjectPathName, NumRows); + + // Set the point's path attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + ObjectPaths, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); + } + + { + // Create point attribute info for data table RowTable class name + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, &AttributeInfoPoint), false); + + // Get the object path + FString RowStructName = DataTable->GetRowStructName().ToString(); + + // Create an array + TArray RowStructNames; + RowStructNames.Init(RowStructName, NumRows); + + // Set the point's path attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + RowStructNames, InputNodeId, 0, + HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, AttributeInfoPoint), false); + } + + // Now set the attributes values for each "point" of the data table + for (int32 ColIdx = 0; ColIdx < NumAttributes; ColIdx++) + { + // attribute name is "unreal_data_table_COL_NAME" + FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + TableData[0][ColIdx]; + + // We need to gt all values for that attribute + TArray AttributeValues; + AttributeValues.SetNum(NumRows); + for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) + { + AttributeValues[RowIdx] = TableData[RowIdx + 1][ColIdx]; + } + + // Create a point attribute info + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = NumRows; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_POINT; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + TCHAR_TO_ANSI(*CurAttrName), &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + AttributeValues, InputNodeId, 0, + CurAttrName, AttributeInfo), false); + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputNodeId), false); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr), false); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + const FString& InObjNodeName, + UHoudiniInputFoliageType_InstancedStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!IsValid(InObject)) + return false; + + FString FTName = InObjNodeName + TEXT("_"); + + UFoliageType_InstancedStaticMesh* FoliageType = Cast(InObject->GetObject()); + if (!IsValid(FoliageType)) + return true; + + UStaticMesh* const SM = FoliageType->GetStaticMesh(); + if (!IsValid(SM)) + return true; + + FTName += FoliageType->GetName(); + + // Marshall the Static Mesh to Houdini + bool bSuccess = true; + + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference; + AssetReference += SM->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FUnrealFoliageTypeTranslator::CreateInputNodeForReference( + FoliageType, InObject->InputNodeId, AssetReference, FTName, InObject->Transform); + } + else + { + bSuccess = FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + FoliageType, InObject->InputNodeId, FTName, bExportLODs, bExportSockets, bExportColliders); + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // If the Input mesh has a Transform offset + const FTransform TransformOffset = InObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h index 553a5bb8c..3450c4ae8 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h @@ -1,215 +1,215 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "CoreMinimal.h" - -class AActor; - -class UHoudiniInput; -class UHoudiniParameter; -class UHoudiniAssetComponent; - -class UHoudiniInputObject; -class UHoudiniInputStaticMesh; -class UHoudiniInputSkeletalMesh; -class UHoudiniInputSceneComponent; -class UHoudiniInputMeshComponent; -class UHoudiniInputInstancedMeshComponent; -class UHoudiniInputSplineComponent; -class UHoudiniInputHoudiniSplineComponent; -class UHoudiniInputHoudiniAsset; -class UHoudiniInputActor; -class UHoudiniInputLandscape; -class UHoudiniInputBrush; -class UHoudiniSplineComponent; -class UHoudiniInputCameraComponent; -class UHoudiniInputDataTable; -class UHoudiniInputFoliageType_InstancedStaticMesh; - -class AActor; - -enum class EHoudiniInputType : uint8; -enum class EHoudiniLandscapeExportType : uint8; - -struct HOUDINIENGINE_API FHoudiniInputTranslator -{ - // - static bool UpdateInputs(UHoudiniAssetComponent* HAC); - - // Update inputs from the asset - // @AssetId: NodeId of the digital asset - // @OuterObject: Object to use for transactions and as Outer for new inputs - // @CurrentInputs: pre: current & post: invalid inputs - // @NewParameters: pre: empty & post: new inputs - // On Return: CurrentInputs are the old inputs that are no longer valid, - // NewInputs are new and re-used inputs. - static bool BuildAllInputs( - const HAPI_NodeId& AssetId, - class UObject* OuterObject, - TArray& Inputs, - TArray& Parameters); - - // Update loaded inputs and their input objects so they can be uploaded properly - static bool UpdateLoadedInputs(UHoudiniAssetComponent * HAC); - - // Update all the inputs that have been marked as change - static bool UploadChangedInputs(UHoudiniAssetComponent * HAC); - - // Only update simple input properties - static bool UpdateInputProperties(UHoudiniInput* InInput); - - // Update the KeepWorldTransform / Object merge transform type property - static bool UpdateTransformType(UHoudiniInput* InInput); - - // Update the pack before merge parameter for World/Geometry inputs - static bool UpdatePackBeforeMerge(UHoudiniInput* InInput); - - // Update the transform offset for geometry inputs - static bool UpdateTransformOffset(UHoudiniInput* InInput); - - // Upload all the input's data to Houdini - static bool UploadInputData(UHoudiniInput* InInput); - - // Upload all the input's transforms to Houdini - static bool UploadInputTransform(UHoudiniInput* InInput); - - // Upload data for an input's InputObject - static bool UploadHoudiniInputObject( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, TArray& OutCreatedNodeIds); - - // Upload transform for an input's InputObject - static bool UploadHoudiniInputTransform( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject); - - // Updates/ticks world inputs in the given HAC - static bool UpdateWorldInputs(UHoudiniAssetComponent* HAC); - - // Updates/ticks the given world input - static bool UpdateWorldInput(UHoudiniInput* InInput); - - // Connect an input's nodes to its linked HDA node - static bool ConnectInputNode(UHoudiniInput* InInput); - - // Destroys an input - static bool DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - static bool DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - static bool DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - - static EHoudiniInputType GetDefaultInputTypeFromLabel(const FString& InputName); - - static bool SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified); - - static bool ChangeInputType(UHoudiniInput* Input, const bool& bForce); - - static bool HapiCreateInputNodeForObject( - const FString& InObjNodeName, UHoudiniInputObject* InObject); - - static bool HapiCreateInputNodeForStaticMesh( - const FString& InObjNodeName, - UHoudiniInputStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference = false); - - static bool HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, - UHoudiniInputHoudiniSplineComponent* InObject, - bool bInSetRotAndScaleAttributes); - - static bool HapiCreateInputNodeForLandscape( - const FString& InObjNodeName, - UHoudiniInputLandscape* InObject, - UHoudiniInput* InInput); - - static bool HapiCreateInputNodeForSkeletalMesh( - const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject); - - static bool HapiCreateInputNodeForSceneComponent( - const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject); - - static bool HapiCreateInputNodeForStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference); - - static bool HapiCreateInputNodeForInstancedStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputInstancedMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders); - - static bool HapiCreateInputNodeForSplineComponent( - const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution); - - static bool HapiCreateInputNodeForHoudiniAssetComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference); - - static bool HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds); - - static bool HapiCreateInputNodeForCamera( - const FString& InObjNodeName, UHoudiniInputCameraComponent* InObject); - - // Create input node for Brush. Optionally exclude actors when combining - // brush with other intersecting brushes. This is typically used to - // exclude Selector objects. - static bool HapiCreateInputNodeForBrush( - const FString& InObjNodeName, - UHoudiniInputBrush* InObject, - TArray* ExcludeActors - ); - - static bool HapiCreateInputNodeForDataTable( - const FString& InNodeName, UHoudiniInputDataTable* InInputObject); - - static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - const FString& InObjNodeName, - UHoudiniInputFoliageType_InstancedStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference = false); - - // HAPI: Create an input node for reference - static bool CreateInputNodeForReference( - HAPI_NodeId& InputNodeId, - const FString & InRef, - const FString & InputNodeName, - const FTransform & InTransform); - - //static bool HapiUpdateInputNodeTransform(const HAPI_NodeId InputNodeId, const FTransform& Transform); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "CoreMinimal.h" + +class AActor; + +class UHoudiniInput; +class UHoudiniParameter; +class UHoudiniAssetComponent; + +class UHoudiniInputObject; +class UHoudiniInputStaticMesh; +class UHoudiniInputSkeletalMesh; +class UHoudiniInputSceneComponent; +class UHoudiniInputMeshComponent; +class UHoudiniInputInstancedMeshComponent; +class UHoudiniInputSplineComponent; +class UHoudiniInputHoudiniSplineComponent; +class UHoudiniInputHoudiniAsset; +class UHoudiniInputActor; +class UHoudiniInputLandscape; +class UHoudiniInputBrush; +class UHoudiniSplineComponent; +class UHoudiniInputCameraComponent; +class UHoudiniInputDataTable; +class UHoudiniInputFoliageType_InstancedStaticMesh; + +class AActor; + +enum class EHoudiniInputType : uint8; +enum class EHoudiniLandscapeExportType : uint8; + +struct HOUDINIENGINE_API FHoudiniInputTranslator +{ + // + static bool UpdateInputs(UHoudiniAssetComponent* HAC); + + // Update inputs from the asset + // @AssetId: NodeId of the digital asset + // @OuterObject: Object to use for transactions and as Outer for new inputs + // @CurrentInputs: pre: current & post: invalid inputs + // @NewParameters: pre: empty & post: new inputs + // On Return: CurrentInputs are the old inputs that are no longer valid, + // NewInputs are new and re-used inputs. + static bool BuildAllInputs( + const HAPI_NodeId& AssetId, + class UObject* OuterObject, + TArray& Inputs, + TArray& Parameters); + + // Update loaded inputs and their input objects so they can be uploaded properly + static bool UpdateLoadedInputs(UHoudiniAssetComponent * HAC); + + // Update all the inputs that have been marked as change + static bool UploadChangedInputs(UHoudiniAssetComponent * HAC); + + // Only update simple input properties + static bool UpdateInputProperties(UHoudiniInput* InInput); + + // Update the KeepWorldTransform / Object merge transform type property + static bool UpdateTransformType(UHoudiniInput* InInput); + + // Update the pack before merge parameter for World/Geometry inputs + static bool UpdatePackBeforeMerge(UHoudiniInput* InInput); + + // Update the transform offset for geometry inputs + static bool UpdateTransformOffset(UHoudiniInput* InInput); + + // Upload all the input's data to Houdini + static bool UploadInputData(UHoudiniInput* InInput); + + // Upload all the input's transforms to Houdini + static bool UploadInputTransform(UHoudiniInput* InInput); + + // Upload data for an input's InputObject + static bool UploadHoudiniInputObject( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, TArray& OutCreatedNodeIds); + + // Upload transform for an input's InputObject + static bool UploadHoudiniInputTransform( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject); + + // Updates/ticks world inputs in the given HAC + static bool UpdateWorldInputs(UHoudiniAssetComponent* HAC); + + // Updates/ticks the given world input + static bool UpdateWorldInput(UHoudiniInput* InInput); + + // Connect an input's nodes to its linked HDA node + static bool ConnectInputNode(UHoudiniInput* InInput); + + // Destroys an input + static bool DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + static bool DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + static bool DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + + static EHoudiniInputType GetDefaultInputTypeFromLabel(const FString& InputName); + + static bool SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified); + + static bool ChangeInputType(UHoudiniInput* Input, const bool& bForce); + + static bool HapiCreateInputNodeForObject( + const FString& InObjNodeName, UHoudiniInputObject* InObject); + + static bool HapiCreateInputNodeForStaticMesh( + const FString& InObjNodeName, + UHoudiniInputStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference = false); + + static bool HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, + UHoudiniInputHoudiniSplineComponent* InObject, + bool bInSetRotAndScaleAttributes); + + static bool HapiCreateInputNodeForLandscape( + const FString& InObjNodeName, + UHoudiniInputLandscape* InObject, + UHoudiniInput* InInput); + + static bool HapiCreateInputNodeForSkeletalMesh( + const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject); + + static bool HapiCreateInputNodeForSceneComponent( + const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject); + + static bool HapiCreateInputNodeForStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference); + + static bool HapiCreateInputNodeForInstancedStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputInstancedMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders); + + static bool HapiCreateInputNodeForSplineComponent( + const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution); + + static bool HapiCreateInputNodeForHoudiniAssetComponent( + const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference); + + static bool HapiCreateInputNodeForActor( + UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds); + + static bool HapiCreateInputNodeForCamera( + const FString& InObjNodeName, UHoudiniInputCameraComponent* InObject); + + // Create input node for Brush. Optionally exclude actors when combining + // brush with other intersecting brushes. This is typically used to + // exclude Selector objects. + static bool HapiCreateInputNodeForBrush( + const FString& InObjNodeName, + UHoudiniInputBrush* InObject, + TArray* ExcludeActors + ); + + static bool HapiCreateInputNodeForDataTable( + const FString& InNodeName, UHoudiniInputDataTable* InInputObject); + + static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + const FString& InObjNodeName, + UHoudiniInputFoliageType_InstancedStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference = false); + + // HAPI: Create an input node for reference + static bool CreateInputNodeForReference( + HAPI_NodeId& InputNodeId, + const FString & InRef, + const FString & InputNodeName, + const FTransform & InTransform); + + //static bool HapiUpdateInputNodeTransform(const HAPI_NodeId InputNodeId, const FTransform& Transform); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp index 789dd224f..56f15cdfd 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp @@ -1,3275 +1,3288 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInstanceTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniStaticMesh.h" - -//#include "HAPI/HAPI_Common.h" - -#include "Engine/StaticMesh.h" -#include "ComponentReregisterContext.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#if WITH_EDITOR - //#include "ScopedTransaction.h" - #include "LevelEditorViewport.h" - #include "MeshPaintHelpers.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// Fastrand is a faster alternative to std::rand() -// and doesn't oscillate when looking for 2 values like Unreal's. -inline int fastrand(int& nSeed) -{ - nSeed = (214013 * nSeed + 2531011); - return (nSeed >> 16) & 0x7FFF; -} - -// -bool -FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) -{ - // Get if force to use HISM from attribute - OutInstancedOutputPartData.bForceHISM = HasHISMAttribute(InHGPO.GeoId, InHGPO.PartId); - - // Extract the object and transforms for this instancer - if (!GetInstancerObjectsAndTransforms( - InHGPO, - InAllOutputs, - OutInstancedOutputPartData.OriginalInstancedObjects, - OutInstancedOutputPartData.OriginalInstancedTransforms, - OutInstancedOutputPartData.SplitAttributeName, - OutInstancedOutputPartData.SplitAttributeValues, - OutInstancedOutputPartData.PerSplitAttributes)) - return false; - - // Check if this is a No-Instancers ( unreal_split_instances ) - OutInstancedOutputPartData.bSplitMeshInstancer = IsSplitInstancer(InHGPO.GeoId, InHGPO.PartId); - - OutInstancedOutputPartData.bIsFoliageInstancer = IsFoliageInstancer(InHGPO.GeoId, InHGPO.PartId); - - // Extract the generic attributes - GetGenericPropertiesAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllPropertyAttributes); - - // Check for per instance custom data - GetPerInstanceCustomData(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData); - - //Get the level path attribute on the instancer - if (!FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllLevelPaths)) - { - // No attribute specified - OutInstancedOutputPartData.AllLevelPaths.Empty(); - } - - // Get the output name attribute - if (!FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.OutputNames)) - { - // No attribute specified - OutInstancedOutputPartData.OutputNames.Empty(); - } - - // See if we have a tile attribute - if (!FHoudiniEngineUtils::GetTileAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.TileValues)) - { - // No attribute specified - OutInstancedOutputPartData.TileValues.Empty(); - } - - // Get the bake actor attribute - if (!FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeActorNames)) - { - // No attribute specified - OutInstancedOutputPartData.AllBakeActorNames.Empty(); - } - - // Get the unreal_bake_folder attribute - if (!FHoudiniEngineUtils::GetBakeFolderAttribute(InHGPO.GeoId, OutInstancedOutputPartData.AllBakeFolders, InHGPO.PartId)) - { - // No attribute specified - OutInstancedOutputPartData.AllBakeFolders.Empty(); - } - - // Get the bake outliner folder attribute - if (!FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeOutlinerFolders)) - { - // No attribute specified - OutInstancedOutputPartData.AllBakeOutlinerFolders.Empty(); - } - - // See if we have instancer material overrides - if (!GetMaterialOverridesFromAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.MaterialAttributes)) - OutInstancedOutputPartData.MaterialAttributes.Empty(); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - UHoudiniOutput* InOutput, - const TArray& InAllOutputs, - UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - // Keep track of the previous cook's component to clean them up after - TMap NewOutputObjects; - TMap OldOutputObjects = InOutput->GetOutputObjects(); - - TMap& InstancedOutputs = InOutput->GetInstancedOutputs(); - // Mark all the current instanced output as stale - for (auto& InstOut : InstancedOutputs) - InstOut.Value.bStale = true; - - USceneComponent* ParentComponent = Cast(InOuterComponent); - if (!ParentComponent) - return false; - - // Keep track of if we remove, create or update any foliage, so that we can repopulate the foliage type list in - // the UI (foliage mode) at the end - bool bHaveAnyFoliageInstancers = false; - - // We also need to cleanup the previous foliages instances (if we have any) - for (auto& CurrentPair : OldOutputObjects) - { - // Foliage instancers store a HISMC in the components - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentPair.Value.OutputComponent); - if (!IsValid(FoliageHISMC)) - continue; - - CleanupFoliageInstances(FoliageHISMC, CurrentPair.Value.OutputObject, ParentComponent); - bHaveAnyFoliageInstancers = true; - } - - // The default SM to be used if the instanced object has not been found (when using attribute instancers) - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) - { - // Not an instancer, skip - if (CurHGPO.Type != EHoudiniPartType::Instancer) - continue; - - // Prepare this output object's output identifier - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = CurHGPO.ObjectId; - OutputIdentifier.GeoId = CurHGPO.GeoId; - OutputIdentifier.PartId = CurHGPO.PartId; - OutputIdentifier.PartName = CurHGPO.PartName; - - FHoudiniInstancedOutputPartData InstancedOutputPartDataTmp; - const FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = nullptr; - if (InPreBuiltInstancedOutputPartData) - { - InstancedOutputPartDataPtr = InPreBuiltInstancedOutputPartData->Find(OutputIdentifier); - } - if (!InstancedOutputPartDataPtr) - { - if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs, InstancedOutputPartDataTmp)) - continue; - InstancedOutputPartDataPtr = &InstancedOutputPartDataTmp; - } - - const FHoudiniInstancedOutputPartData& InstancedOutputPartData = *InstancedOutputPartDataPtr; - - TArray InstancerMaterials; - if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes, InstancerMaterials)) - InstancerMaterials.Empty(); - - if (InstancedOutputPartData.bIsFoliageInstancer) - bHaveAnyFoliageInstancers = true; - - // - // TODO: REFACTOR THIS! - // - // We create an instanced output per original object - // These original object can then potentially be replaced by variations - // Each variations will create a instance component / OutputObject - // Currently we process all original objects AND their variations at the same time - // we should instead loop on the original objects - // - get their variations objects/transform - // - create the appropriate instancer - // This means modifying UpdateInstanceVariationsObjects so that it works using - // a single OriginalObject instead of using an array - // Also, apply the same logic to UpdateChangedInstanceOutput - // - - // Array containing all the variations objects for all the original objects - TArray> VariationInstancedObjects; - // Array containing all the variations transforms - TArray> VariationInstancedTransforms; - // Array indicate the original object index for each variation - TArray VariationOriginalObjectIndices; - // Array indicate the variation number for each variation - TArray VariationIndices; - // Update our variations using the instanced outputs - UpdateInstanceVariationObjects( - OutputIdentifier, - InstancedOutputPartData.OriginalInstancedObjects, - InstancedOutputPartData.OriginalInstancedTransforms, - InOutput->GetInstancedOutputs(), - VariationInstancedObjects, - VariationInstancedTransforms, - VariationOriginalObjectIndices, - VariationIndices); - - // Preload objects so we can benefit from async compilation as much as possible - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) - { - VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); - } - - // Create the instancer components now - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) - { - UObject* InstancedObject = VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - continue; - - if (!VariationInstancedTransforms.IsValidIndex(InstanceObjectIdx)) - continue; - - const TArray& InstancedObjectTransforms = VariationInstancedTransforms[InstanceObjectIdx]; - if (InstancedObjectTransforms.Num() <= 0) - continue; - - // Find the matching instance output now - FHoudiniInstancedOutput* FoundInstancedOutput = nullptr; - { - // Instanced output only use the original object index for their split identifier - FHoudiniOutputObjectIdentifier InstancedOutputIdentifier = OutputIdentifier; - InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]); - FoundInstancedOutput = InstancedOutputs.Find(InstancedOutputIdentifier); - } - - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - OutputIdentifier.SplitIdentifier = - FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]) - + TEXT("_") - + FString::FromInt(VariationIndices[InstanceObjectIdx]); - - // Get the OutputObj for this variation - FHoudiniOutputObject* FoundOutputObject = OldOutputObjects.Find(OutputIdentifier); - // See if we can find an preexisting component for this obj to try to reuse it - USceneComponent* OldInstancerComponent = nullptr; - const bool bIsProxyMesh = InstancedObject->IsA(); - if (FoundOutputObject) - { - if (bIsProxyMesh) - { - OldInstancerComponent = Cast(FoundOutputObject->ProxyComponent); - } - else - { - OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); - } - } - - // Extract the material for this variation - TArray VariationMaterials; - if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) - VariationMaterials.Empty(); - - USceneComponent* NewInstancerComponent = nullptr; - if (!CreateOrUpdateInstanceComponent( - InstancedObject, - InstancedObjectTransforms, - InstancedOutputPartData.AllPropertyAttributes, - CurHGPO, - ParentComponent, - OldInstancerComponent, - NewInstancerComponent, - InstancedOutputPartData.bSplitMeshInstancer, - InstancedOutputPartData.bIsFoliageInstancer, - VariationMaterials, - InstancedOutputPartData.bForceHISM)) - { - // TODO?? - continue; - } - - if (!NewInstancerComponent) - continue; - - // Copy the per-instance custom data if we have any - UpdateChangedPerInstanceCustomData( - InstancedOutputPartData.NumCustomFloats, - InstancedOutputPartData.PerInstanceCustomData, - NewInstancerComponent); - - // If the instanced object (by ref) wasn't found, hide the component - if(InstancedObject == DefaultReferenceSM) - NewInstancerComponent->SetHiddenInGame(true); - else - NewInstancerComponent->SetHiddenInGame(false); - - FHoudiniOutputObject& NewOutputObject = NewOutputObjects.FindOrAdd(OutputIdentifier); - if (bIsProxyMesh) - { - NewOutputObject.ProxyComponent = NewInstancerComponent; - NewOutputObject.ProxyObject = InstancedObject; - } - else - { - NewOutputObject.OutputComponent = NewInstancerComponent; - NewOutputObject.OutputObject = InstancedObject; - } - - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - NewOutputObject.CachedAttributes.Empty(); - NewOutputObject.CachedTokens.Empty(); - - // Todo: get the proper attribute value per variation... - // Cache the level path, output name and tile attributes on the output object - // So they can be reused for baking - if(InstancedOutputPartData.AllLevelPaths.Num() > 0 && !InstancedOutputPartData.AllLevelPaths[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[0]); - - if(InstancedOutputPartData.OutputNames.Num() > 0 && !InstancedOutputPartData.OutputNames[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[0]); - - if(InstancedOutputPartData.TileValues.Num() > 0 && InstancedOutputPartData.TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[0])); - } - - if (InstancedOutputPartData.AllBakeActorNames.Num() > 0 && !InstancedOutputPartData.AllBakeActorNames[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[0]); - - if (InstancedOutputPartData.AllBakeFolders.Num() > 0 && !InstancedOutputPartData.AllBakeFolders[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, InstancedOutputPartData.AllBakeFolders[0]); - - if (InstancedOutputPartData.AllBakeOutlinerFolders.Num() > 0 && !InstancedOutputPartData.AllBakeOutlinerFolders[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[0]); - - if (InstancedOutputPartData.SplitAttributeValues.Num() > 0 - && !InstancedOutputPartData.SplitAttributeName.IsEmpty() - && InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalObjectIndices[InstanceObjectIdx])) - { - FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalObjectIndices[InstanceObjectIdx]]; - - // Cache the split attribute both as attribute and token - NewOutputObject.CachedAttributes.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); - NewOutputObject.CachedTokens.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); - - // If we have a split name that is non-empty, override attributes that can differ by split based - // on the split name - if (!SplitValue.IsEmpty()) - { - const FHoudiniInstancedOutputPerSplitAttributes* PerSplitAttributes = InstancedOutputPartData.PerSplitAttributes.Find(SplitValue); - if (PerSplitAttributes) - { - if (!PerSplitAttributes->LevelPath.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, PerSplitAttributes->LevelPath); - if (!PerSplitAttributes->BakeActorName.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, PerSplitAttributes->BakeActorName); - if (!PerSplitAttributes->BakeOutlinerFolder.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, PerSplitAttributes->BakeOutlinerFolder); - if (!PerSplitAttributes->BakeFolder.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, PerSplitAttributes->BakeFolder); - } - } - } - } - } - - // Remove reused components from the old map to avoid their deletion - for (const auto& CurNewPair : NewOutputObjects) - { - // Get the new Identifier / StaticMesh - const FHoudiniOutputObjectIdentifier& OutputIdentifier = CurNewPair.Key; - - // See if we already had that pair in the old map - FHoudiniOutputObject* FoundOldOutputObject = OldOutputObjects.Find(OutputIdentifier); - if (!FoundOldOutputObject) - continue; - - bool bKeep = false; - - UObject* NewComponent = CurNewPair.Value.OutputComponent; - if (NewComponent) - { - UObject* FoundOldComponent = FoundOldOutputObject->OutputComponent; - if (FoundOldComponent && !FoundOldComponent->IsPendingKill()) - { - bKeep = (FoundOldComponent == NewComponent); - } - } - - UObject* NewProxyComponent = CurNewPair.Value.ProxyComponent; - if (NewProxyComponent) - { - UObject* FoundOldProxyComponent = FoundOldOutputObject->ProxyComponent; - if (FoundOldProxyComponent && !FoundOldProxyComponent->IsPendingKill()) - { - bKeep = (FoundOldProxyComponent == NewProxyComponent); - } - } - - if (bKeep) - { - // Remove the reused component from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - - // The Old map now only contains unused/stale components, delete them - for (auto& OldPair : OldOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; - UObject* OldComponent = OldPair.Value.OutputComponent; - if (OldComponent) - { - bool bDestroy = true; - if (OldComponent->IsA()) - { - // When destroying a component, we have to be sure it's not an HISMC owned by an InstanceFoliageActor - UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OldComponent); - if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) - bDestroy = false; - } - - if(bDestroy) - RemoveAndDestroyComponent(OldComponent, OldPair.Value.OutputObject); - - OldPair.Value.OutputComponent = nullptr; - OldPair.Value.OutputObject = nullptr; - } - - UObject* OldProxyComponent = OldPair.Value.ProxyComponent; - if (OldProxyComponent) - { - RemoveAndDestroyComponent(OldProxyComponent, OldPair.Value.ProxyObject); - OldPair.Value.ProxyComponent = nullptr; - OldPair.Value.ProxyObject = nullptr; - } - } - OldOutputObjects.Empty(); - - // Update the output's object map - // Instancer do not create objects, clean the map - InOutput->SetOutputObjects(NewOutputObjects); - - // If we removed, created or updated any foliage instancers, repopulate the list of foliage types in the UI (foliage - // mode) - if (bHaveAnyFoliageInstancers) - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - - return true; -} - - -bool -FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( - FHoudiniInstancedOutput& InInstancedOutput, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - UHoudiniOutput* InParentOutput, - USceneComponent* InParentComponent) -{ - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = InOutputIdentifier.ObjectId; - OutputIdentifier.GeoId = InOutputIdentifier.GeoId; - OutputIdentifier.PartId = InOutputIdentifier.PartId; - OutputIdentifier.SplitIdentifier = InOutputIdentifier.SplitIdentifier; - OutputIdentifier.PartName = InOutputIdentifier.PartName; - - // Get if force using HISM from attribute - bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); - - TArray OriginalInstancedObjects; - OriginalInstancedObjects.Add(InInstancedOutput.OriginalObject.LoadSynchronous()); - - TArray> OriginalInstancedTransforms; - OriginalInstancedTransforms.Add(InInstancedOutput.OriginalTransforms); - - // Update our variations using the changed instancedoutputs objects - TArray> InstancedObjects; - TArray> InstancedTransforms; - TArray VariationOriginalObjectIndices; - TArray VariationIndices; - UpdateInstanceVariationObjects( - OutputIdentifier, - OriginalInstancedObjects, - OriginalInstancedTransforms, - InParentOutput->GetInstancedOutputs(), - InstancedObjects, - InstancedTransforms, - VariationOriginalObjectIndices, - VariationIndices); - - // Find the HGPO for this instanced output - bool FoundHGPO = false; - FHoudiniGeoPartObject HGPO; - for (const auto& curHGPO : InParentOutput->GetHoudiniGeoPartObjects()) - { - if (OutputIdentifier.Matches(curHGPO)) - { - HGPO = curHGPO; - FoundHGPO = true; - break; - } - } - - if (!FoundHGPO) - { - // TODO check failure - ensure(FoundHGPO); - } - - // Extract the generic attributes for that HGPO - TArray AllPropertyAttributes; - GetGenericPropertiesAttributes(OutputIdentifier.GeoId, OutputIdentifier.PartId, AllPropertyAttributes); - - // Check if this is a No-Instancers ( unreal_split_instances ) - bool bSplitMeshInstancer = IsSplitInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); - - bool bIsFoliageInstancer = IsFoliageInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); - - // See if we have instancer material overrides - TArray InstancerMaterials; - if (!GetInstancerMaterials(OutputIdentifier.GeoId, OutputIdentifier.PartId, InstancerMaterials)) - InstancerMaterials.Empty(); - - // Preload objects so we can benefit from async compilation as much as possible - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) - { - InstancedObjects[InstanceObjectIdx].LoadSynchronous(); - } - - // Keep track of the new instancer component in order to be able to clean up the unused/stale ones after. - TMap& OutputObjects = InParentOutput->GetOutputObjects(); - TMap ToDeleteOutputObjects = InParentOutput->GetOutputObjects(); - - // Create the instancer components now - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) - { - UObject* InstancedObject = InstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - continue; - - if (!InstancedTransforms.IsValidIndex(InstanceObjectIdx)) - continue; - - const TArray& InstancedObjectTransforms = InstancedTransforms[InstanceObjectIdx]; - if (InstancedObjectTransforms.Num() <= 0) - continue; - - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - // the original object index is used for the instanced outputs split identifier - OutputIdentifier.SplitIdentifier = - InOutputIdentifier.SplitIdentifier - + TEXT("_") - + FString::FromInt(VariationIndices[InstanceObjectIdx]); - - // See if we can find an preexisting component for this obj to try to reuse it - USceneComponent* OldInstancerComponent = nullptr; - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(OutputIdentifier); - if (FoundOutputObject) - { - OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); - } - - // Extract the material for this variation -// FHoudiniInstancedOutput* FoundInstancedOutput = InstancedOutputs.Find(OutputIdentifier); - TArray VariationMaterials; - if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) - VariationMaterials.Empty(); - - USceneComponent* NewInstancerComponent = nullptr; - if (!CreateOrUpdateInstanceComponent( - InstancedObject, InstancedObjectTransforms, - AllPropertyAttributes, HGPO, - InParentComponent, OldInstancerComponent, NewInstancerComponent, - bSplitMeshInstancer, bIsFoliageInstancer, InstancerMaterials, bForceHISM)) - { - // TODO?? - continue; - } - - if (!NewInstancerComponent) - continue; - - if (OldInstancerComponent != NewInstancerComponent) - { - // Previous component wasn't reused, detach and delete it - RemoveAndDestroyComponent(OldInstancerComponent, nullptr); - - // Replace it with the new component - if (FoundOutputObject) - { - FoundOutputObject->OutputComponent = NewInstancerComponent; - } - else - { - FHoudiniOutputObject& NewOutputObject = OutputObjects.Add(OutputIdentifier); - NewOutputObject.OutputComponent = NewInstancerComponent; - } - } - - // Remove this output object from the todelete map - ToDeleteOutputObjects.Remove(OutputIdentifier); - } - - // Clean up the output objects that are not "reused" by the instanced outs - // The ToDelete map now only contains unused/stale components, delete them - for (auto& ToDeletePair : ToDeleteOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& ToDeleteIdentifier = ToDeletePair.Key; - UObject* OldComponent = ToDeletePair.Value.OutputComponent; - if (OldComponent) - { - RemoveAndDestroyComponent(OldComponent, ToDeletePair.Value.OutputObject); - ToDeletePair.Value.OutputComponent = nullptr; - } - - UObject* OldProxyComponent = ToDeletePair.Value.ProxyComponent; - if (OldProxyComponent) - { - RemoveAndDestroyComponent(OldProxyComponent, ToDeletePair.Value.ProxyObject); - ToDeletePair.Value.ProxyComponent = nullptr; - } - - // Make sure the stale output object is not in the output map anymore - OutputObjects.Remove(ToDeleteIdentifier); - } - ToDeleteOutputObjects.Empty(); - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValues, - TMap& OutPerSplitAttributes) -{ - TArray InstancedObjects; - TArray> InstancedTransforms; - - TArray InstancedHGPOs; - TArray> InstancedHGPOTransforms; - - bool bSuccess = false; - switch (InHGPO.InstancerType) - { - case EHoudiniInstancerType::PackedPrimitive: - { - // Packed primitives instances - bSuccess = GetPackedPrimitiveInstancerHGPOsAndTransforms( - InHGPO, - InstancedHGPOs, - InstancedHGPOTransforms, - OutSplitAttributeName, - OutSplitAttributeValues, - OutPerSplitAttributes); - } - break; - - case EHoudiniInstancerType::AttributeInstancer: - { - // "Modern" attribute instancer - "unreal_instance" - bSuccess = GetAttributeInstancerObjectsAndTransforms( - InHGPO, - InstancedObjects, - InstancedTransforms, - OutSplitAttributeName, - OutSplitAttributeValues, - OutPerSplitAttributes); - } - break; - - case EHoudiniInstancerType::OldSchoolAttributeInstancer: - { - // Old school attribute override instancer - instance attribute w/ a HoudiniPath - bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); - } - break; - - case EHoudiniInstancerType::ObjectInstancer: - { - // Old School object instancer - bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); - } - break; - } - - if (!bSuccess) - return false; - - // Fetch the UOBject that correspond to the instanced parts - // Attribute instancers don't need to do this since they refer UObjects directly - if (InstancedHGPOs.Num() > 0) - { - for (int32 HGPOIdx = 0; HGPOIdx < InstancedHGPOs.Num(); HGPOIdx++) - { - const FHoudiniGeoPartObject& CurrentHGPO = InstancedHGPOs[HGPOIdx]; - - // Get the UObject that was generated for that HGPO - TArray ObjectsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - if (Output->OutputObjects.Num() <= 0) - continue; - - for (const auto& OutObjPair : Output->OutputObjects) - { - if (!OutObjPair.Key.Matches(CurrentHGPO)) - continue; - - const FHoudiniOutputObject& CurrentOutputObject = OutObjPair.Value; - - // In the case of a single-instance we can use the proxy (if it is current) - // FHoudiniOutputTranslator::UpdateOutputs doesn't allow proxies if there is more than one instance in an output - if (InstancedHGPOTransforms[HGPOIdx].Num() <= 1 && CurrentOutputObject.bProxyIsCurrent - && CurrentOutputObject.ProxyObject && !CurrentOutputObject.ProxyObject->IsPendingKill()) - { - ObjectsToInstance.Add(CurrentOutputObject.ProxyObject); - } - else if (CurrentOutputObject.OutputObject && !CurrentOutputObject.OutputObject->IsPendingKill()) - { - ObjectsToInstance.Add(CurrentOutputObject.OutputObject); - } - } - } - - // Add the UObject and the HGPO transforms to the output arrays - for (const auto& MatchingOutputObj : ObjectsToInstance) - { - InstancedObjects.Add(MatchingOutputObj); - InstancedTransforms.Add(InstancedHGPOTransforms[HGPOIdx]); - } - } - } - - // - if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() ) - { - // TODO - // Error / warning - return false; - } - - OutInstancedObjects = InstancedObjects; - OutInstancedTransforms = InstancedTransforms; - - return true; -} - - -void -FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TArray& InOriginalObjects, - const TArray>& InOriginalTransforms, - TMap& InstancedOutputs, - TArray>& OutVariationsInstancedObjects, - TArray>& OutVariationsInstancedTransforms, - TArray& OutVariationOriginalObjectIdx, - TArray& OutVariationIndices) -{ - FHoudiniOutputObjectIdentifier Identifier = InOutputIdentifier; - for (int32 InstObjIdx = 0; InstObjIdx < InOriginalObjects.Num(); InstObjIdx++) - { - UObject* OriginalObj = InOriginalObjects[InstObjIdx]; - if (!OriginalObj || OriginalObj->IsPendingKill()) - continue; - - // Build this output object's split identifier - Identifier.SplitIdentifier = FString::FromInt(InstObjIdx); - - // Do we have an instanced output object for this one? - FHoudiniInstancedOutput * FoundInstancedOutput = nullptr; - for (auto& Iter : InstancedOutputs) - { - FHoudiniOutputObjectIdentifier& FoundIdentifier = Iter.Key; - if (!(FoundIdentifier == Identifier)) - continue; - - // We found an existing instanced output for this identifier - FoundInstancedOutput = &(Iter.Value); - - if (FoundIdentifier.bLoaded) - { - // The output object identifier we found is marked as loaded, - // so uses old node IDs, we must update them, or the next cook - // will fail to locate the output back - FoundIdentifier.ObjectId = Identifier.ObjectId; - FoundIdentifier.GeoId = Identifier.GeoId; - FoundIdentifier.PartId = Identifier.PartId; - } - } - - if (!FoundInstancedOutput) - { - // Create a new one - FHoudiniInstancedOutput CurInstancedOutput; - CurInstancedOutput.OriginalObject = OriginalObj; - CurInstancedOutput.OriginalObjectIndex = InstObjIdx; - CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; - - CurInstancedOutput.VariationObjects.Add(OriginalObj); - CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); - CurInstancedOutput.TransformVariationIndices.SetNumZeroed(InOriginalTransforms[InstObjIdx].Num()); - CurInstancedOutput.MarkChanged(false); - CurInstancedOutput.bStale = false; - - // No variations, simply assign the object/transforms - OutVariationsInstancedObjects.Add(OriginalObj); - OutVariationsInstancedTransforms.Add(InOriginalTransforms[InstObjIdx]); - OutVariationOriginalObjectIdx.Add(InstObjIdx); - OutVariationIndices.Add(0); - - InstancedOutputs.Add(Identifier, CurInstancedOutput); - } - else - { - // Process the potential variations - FHoudiniInstancedOutput& CurInstancedOutput = *FoundInstancedOutput; - UObject *ReplacedOriginalObject = nullptr; - if (CurInstancedOutput.OriginalObject != OriginalObj) - { - ReplacedOriginalObject = CurInstancedOutput.OriginalObject.LoadSynchronous(); - CurInstancedOutput.OriginalObject = OriginalObj; - } - - CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; - - // Shouldnt be needed... - CurInstancedOutput.OriginalObjectIndex = InstObjIdx; - - // Remove any null or deleted variation objects - TArray ObjsToRemove; - for (int32 VarIdx = CurInstancedOutput.VariationObjects.Num() - 1; VarIdx >= 0; --VarIdx) - { - UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill() || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) - { - ObjsToRemove.Add(VarIdx); - } - } - if (ObjsToRemove.Num() > 0) - { - for (const int32 &VarIdx : ObjsToRemove) - { - CurInstancedOutput.VariationObjects.RemoveAt(VarIdx); - CurInstancedOutput.VariationTransformOffsets.RemoveAt(VarIdx); - } - // Force a recompute of variation assignments - CurInstancedOutput.TransformVariationIndices.SetNum(0); - } - - // If we don't have variations, simply use the original object - if (CurInstancedOutput.VariationObjects.Num() <= 0) - { - // No variations? add the original one - CurInstancedOutput.VariationObjects.Add(OriginalObj); - CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); - CurInstancedOutput.TransformVariationIndices.SetNum(0); - } - - // If the number of transforms has changed since the previous cook, - // we need to recompute the variation assignments - if (CurInstancedOutput.TransformVariationIndices.Num() != CurInstancedOutput.OriginalTransforms.Num()) - UpdateVariationAssignements(CurInstancedOutput); - - // Assign variations and their transforms - for (int32 VarIdx = 0; VarIdx < CurInstancedOutput.VariationObjects.Num(); VarIdx++) - { - UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill()) - continue; - - // Get the transforms assigned to that variation - TArray ProcessedTransforms; - ProcessInstanceTransforms(CurInstancedOutput, VarIdx, ProcessedTransforms); - if (ProcessedTransforms.Num() > 0) - { - OutVariationsInstancedObjects.Add(CurrentVariationObject); - OutVariationsInstancedTransforms.Add(ProcessedTransforms); - OutVariationOriginalObjectIdx.Add(InstObjIdx); - OutVariationIndices.Add(VarIdx); - } - } - - CurInstancedOutput.MarkChanged(false); - CurInstancedOutput.bStale = false; - } - } -} - - -void -FHoudiniInstanceTranslator::UpdateVariationAssignements(FHoudiniInstancedOutput& InstancedOutput) -{ - int32 TransformCount = InstancedOutput.OriginalTransforms.Num(); - InstancedOutput.TransformVariationIndices.SetNumZeroed(TransformCount); - - int32 VariationCount = InstancedOutput.VariationObjects.Num(); - if (VariationCount <= 1) - return; - - int nSeed = 1234; - for (int32 Idx = 0; Idx < TransformCount; Idx++) - { - InstancedOutput.TransformVariationIndices[Idx] = fastrand(nSeed) % VariationCount; - } -} - -void -FHoudiniInstanceTranslator::ProcessInstanceTransforms( - FHoudiniInstancedOutput& InstancedOutput, const int32& VariationIdx, TArray& OutProcessedTransforms) -{ - if (!InstancedOutput.VariationObjects.IsValidIndex(VariationIdx)) - return; - - if (!InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx)) - return; - - bool bHasVariations = InstancedOutput.VariationObjects.Num() > 1; - bool bHasTransformOffset = InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx) - ? !InstancedOutput.VariationTransformOffsets[VariationIdx].Equals(FTransform::Identity) - : false; - - if (!bHasVariations && !bHasTransformOffset) - { - // We dont have variations or transform offset, so we can reuse the original transforms as is - OutProcessedTransforms = InstancedOutput.OriginalTransforms; - return; - } - - if (bHasVariations) - { - // We simply need to extract the transforms for this variation - for (int32 TransformIndex = 0; TransformIndex < InstancedOutput.TransformVariationIndices.Num(); TransformIndex++) - { - if (InstancedOutput.TransformVariationIndices[TransformIndex] != VariationIdx) - continue; - - OutProcessedTransforms.Add(InstancedOutput.OriginalTransforms[TransformIndex]); - } - } - else - { - // No variations, we can reuse the original transforms - OutProcessedTransforms = InstancedOutput.OriginalTransforms; - } - - if (bHasTransformOffset) - { - // Get the transform offset for this variation - FVector PositionOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetLocation(); - FQuat RotationOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetRotation(); - FVector ScaleOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetScale3D(); - - FTransform CurrentTransform = FTransform::Identity; - for (int32 TransformIndex = 0; TransformIndex < OutProcessedTransforms.Num(); TransformIndex++) - { - CurrentTransform = OutProcessedTransforms[TransformIndex]; - - // Compute new rotation and scale. - FVector Position = CurrentTransform.GetLocation() + PositionOffset; - FQuat TransformRotation = CurrentTransform.GetRotation() * RotationOffset; - FVector TransformScale3D = CurrentTransform.GetScale3D() * ScaleOffset; - - // Make sure inverse matrix exists - seems to be a bug in Unreal when submitting instances. - // Happens in blueprint as well. - // We want to make sure the scale is not too small, but keep negative values! (Bug 90876) - if (FMath::Abs(TransformScale3D.X) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.X = (TransformScale3D.X > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - if (FMath::Abs(TransformScale3D.Y) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.Y = (TransformScale3D.Y > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - if (FMath::Abs(TransformScale3D.Z) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.Z = (TransformScale3D.Z > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - CurrentTransform.SetLocation(Position); - CurrentTransform.SetRotation(TransformRotation); - CurrentTransform.SetScale3D(TransformScale3D); - - if (CurrentTransform.IsValid()) - OutProcessedTransforms[TransformIndex] = CurrentTransform; - } - } -} - -bool -FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::PackedPrimitive) - return false; - - // Get transforms for each instance - TArray InstancerPartTransforms; - InstancerPartTransforms.SetNumZeroed(InHGPO.PartInfo.InstanceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancerPartTransforms( - FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, - HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, InHGPO.PartInfo.InstanceCount), false); - - // Convert the transform to Unreal's coordinate system - TArray InstancerUnrealTransforms; - InstancerUnrealTransforms.SetNumUninitialized(InstancerPartTransforms.Num()); - for (int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); InstanceIdx++) - { - const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; - FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, InstancerUnrealTransforms[InstanceIdx]); - } - - // Get the part ids for parts being instanced - TArray InstancedPartIds; - InstancedPartIds.SetNumZeroed(InHGPO.PartInfo.InstancedPartCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancedPartIds( - FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, - InstancedPartIds.GetData(), 0, InHGPO.PartInfo.InstancedPartCount), false); - - // See if the user has specified an attribute for splitting the instances - // and get the values - FString SplitAttribName = FString(); - TArray AllSplitAttributeValues; - bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( - InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_PRIM, SplitAttribName, AllSplitAttributeValues); - - // Get the level path attribute on the instancer - TArray AllLevelPaths; - const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( - InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_PRIM); - - // Get the bake actor attribute - TArray AllBakeActorNames; - const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( - InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_PRIM); - - // Get the unreal_bake_folder attribute - TArray AllBakeFolders; - const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( - InHGPO.GeoId, HAPI_ATTROWNER_PRIM, AllBakeFolders, InHGPO.PartId); - - // Get the bake outliner folder attribute - TArray AllBakeOutlinerFolders; - const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_PRIM); - - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; - - for (const auto& InstancedPartId : InstancedPartIds) - { - // Create a GeoPartObject corresponding to the instanced part - FHoudiniGeoPartObject InstancedHGPO; - InstancedHGPO.AssetId = InHGPO.AssetId; - InstancedHGPO.AssetName = InHGPO.AssetName; - InstancedHGPO.ObjectId = InHGPO.ObjectId; - InstancedHGPO.ObjectName = InHGPO.ObjectName; - InstancedHGPO.GeoId = InHGPO.GeoId; - InstancedHGPO.PartId = InstancedPartId; - InstancedHGPO.PartName = InHGPO.PartName; - InstancedHGPO.TransformMatrix = InHGPO.TransformMatrix; - - // TODO: Copy more cached data? - - OutInstancedHGPO.Add(InstancedHGPO); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - } - - // If we don't need to split the instances, we're done - if (!bHasSplitAttribute) - return true; - - // TODO: Optimize this! - // Split the instances using the split attribute's values - - // Move the output arrays to temp arrays - TArray UnsplitInstancedHGPOs = OutInstancedHGPO; - TArray> UnsplitInstancedTransforms = OutInstancedTransforms; - - // Empty the output arrays - OutInstancedHGPO.Empty(); - OutInstancedTransforms.Empty(); - OutSplitAttributeValue.Empty(); - for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedHGPOs.Num(); ObjIdx++) - { - // Map of split values to transform arrays - TMap> SplitTransformMap; - - TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; - - int32 NumInstances = CurrentTransforms.Num(); - if (AllSplitAttributeValues.Num() != NumInstances) - continue; - - // Split the transforms using the split values - for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) - { - const FString& SplitAttrValue = AllSplitAttributeValues[InstIdx]; - SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); - - // Record attributes for any split value we have not yet seen - if (bHasAnyPerSplitAttributes) - { - FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); - if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) - { - PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; - } - if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; - } - if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; - } - if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; - } - } - } - - // Add the objects, transform, split values to the final arrays - for (auto& Iterator : SplitTransformMap) - { - OutSplitAttributeValue.Add(Iterator.Key); - OutInstancedHGPO.Add(UnsplitInstancedHGPOs[ObjIdx]); - OutInstancedTransforms.Add(Iterator.Value); - } - } - - OutSplitAttributeName = SplitAttribName; - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::AttributeInstancer) - return false; - - // Look for the unreal instance attribute - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - - // instance attribute on points - bool is_override_attr = false; - HAPI_Result Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT, &AttribInfo); - - // unreal_instance attribute on points - if (Result != HAPI_RESULT_SUCCESS || AttribInfo.exists == false) - { - is_override_attr = true; - Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT, &AttribInfo); - } - - // unreal_instance attribute on detail - if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) - { - is_override_attr = true; - Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL, &AttribInfo); - } - - // Attribute does not exist. - if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the settings indicating if we want to use a default object when the referenced mesh is invalid - bool bDefaultObjectEnabled = true; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bDefaultObjectEnabled = HoudiniRuntimeSettings->bShowDefaultMesh; - } - - // See if the user has specified an attribute for splitting the instances, and get the values - FString SplitAttribName = FString(); - TArray AllSplitAttributeValues; - bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( - InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_POINT, SplitAttribName, AllSplitAttributeValues); - - // Get the level path attribute on the instancer - TArray AllLevelPaths; - const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( - InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_POINT); - - // Get the bake actor attribute - TArray AllBakeActorNames; - const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( - InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_POINT); - - // Get the unreal_bake_folder attribute - TArray AllBakeFolders; - const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( - InHGPO.GeoId, HAPI_ATTROWNER_POINT, AllBakeFolders, InHGPO.PartId); - - // Get the bake outliner folder attribute - TArray AllBakeOutlinerFolders; - const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_POINT); - - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; - - // Array used to store the split values per objects - // Will only be used if we have a split attribute - TArray> SplitAttributeValuesPerObject; - - if (AttribInfo.owner == HAPI_ATTROWNER_DETAIL) - { - // If the attribute is on the detail, then its value is applied to all points - TArray DetailInstanceValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InHGPO.GeoId, - InHGPO.PartId, - is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, - AttribInfo, - DetailInstanceValues)) - { - // This should not happen - attribute exists, but there was an error retrieving it. - return false; - } - - if (DetailInstanceValues.Num() <= 0) - { - // No values specified. - return false; - } - - // Attempt to load specified asset. - const FString & AssetName = DetailInstanceValues[0]; - UObject * AttributeObject = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetName, nullptr, LOAD_None, nullptr); - - if (!AttributeObject) - { - // See if the ref is a class that we can instantiate - UClass * FoundClass = FindObject(ANY_PACKAGE, *AssetName); - if (FoundClass != nullptr) - { - // TODO: ensure we'll be able to create an actor from this class! - AttributeObject = FoundClass; - } - } - - if (!AttributeObject && bDefaultObjectEnabled) - { - HOUDINI_LOG_WARNING(TEXT("Failed to load instanced object '%s', using default instance mesh (hidden in game)."), *(AssetName)); - - // Couldn't load the referenced object, use the default reference mesh - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (!DefaultReferenceSM || DefaultReferenceSM->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to load the default instance mesh.")); - return false; - } - AttributeObject = DefaultReferenceSM; - } - - // Attach the objectPtr/transforms/bHiddenInGame if the attributeObject is created successfully - // (with either the actual referenced object or the default placeholder object) - if (AttributeObject) - { - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - - if(bHasSplitAttribute) - SplitAttributeValuesPerObject.Add(AllSplitAttributeValues); - } - } - else - { - // Attribute is on points, so we may have different values for each of them - TArray PointInstanceValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InHGPO.GeoId, - InHGPO.PartId, - is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, - AttribInfo, - PointInstanceValues)) - { - // This should not happen - attribute exists, but there was an error retrieving it. - return false; - } - - // The attribute is on points, so the number of points must match number of transforms. - if (!ensure(PointInstanceValues.Num() == InstancerUnrealTransforms.Num())) - { - // This should not happen, we have mismatch between number of instance values and transforms. - return false; - } - - // If instance attribute exists on points, we need to get all the unique values. - // This will give us all the unique object we want to instance - TMap ObjectsToInstance; - for (const auto& Iter : PointInstanceValues) - { - if (!ObjectsToInstance.Contains(Iter)) - { - // To avoid trying to load an object that fails multiple times, - // still add it to the array if null so we can still skip further attempts - UObject * AttributeObject = StaticLoadObject( - UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); - - if (!AttributeObject) - { - // See if the ref is a class that we can instantiate - UClass * FoundClass = FindObject(ANY_PACKAGE, *Iter); - if (FoundClass != nullptr) - { - // TODO: ensure we'll be able to create an actor from this class! - AttributeObject = FoundClass; - } - } - - ObjectsToInstance.Add(Iter, AttributeObject); - } - } - - // Iterates through all the unique objects and get their corresponding transforms - bool Success = false; - for (auto Iter : ObjectsToInstance) - { - bool bHiddenInGame = false; - // Check that we managed to load this object - UObject * AttributeObject = Iter.Value; - if (!AttributeObject && bDefaultObjectEnabled) - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load instanced object '%s', use default mesh (hidden in game)."), *(Iter.Key)); - - // If failed to load this object, add default reference mesh - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) - { - AttributeObject = DefaultReferenceSM; - bHiddenInGame = true; - } - else// Failed to load default reference mesh object - { - HOUDINI_LOG_WARNING(TEXT("Failed to load default mesh.")); - continue; - } - } - - if (!AttributeObject) - continue; - - if (!bHasSplitAttribute) - { - // No Split attribute: - // Extract the transform values that correspond to this object, and add them to the output arrays - const FString & InstancePath = Iter.Key; - TArray ObjectTransforms; - for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) - { - if (InstancePath.Equals(PointInstanceValues[Idx])) - ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); - } - - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(ObjectTransforms); - Success = true; - } - else - { - // We have a split attribute: - // Extract the transform values and split attribute values for this object, - // add them to the output arrays, and we will process the splits after - const FString & InstancePath = Iter.Key; - TArray ObjectTransforms; - TArray ObjectSplitValues; - for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) - { - if (InstancePath.Equals(PointInstanceValues[Idx])) - { - ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); - ObjectSplitValues.Add(AllSplitAttributeValues[Idx]); - } - } - - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(ObjectTransforms); - SplitAttributeValuesPerObject.Add(ObjectSplitValues); - Success = true; - } - } - - if (!Success) - return false; - } - - // If we don't need to split the instances, we're done - if (!bHasSplitAttribute) - return true; - - // Split the instances one more time, this time using the split values - - // Move the output arrays to temp arrays - TArray UnsplitInstancedObjects = OutInstancedObjects; - TArray> UnsplitInstancedTransforms = OutInstancedTransforms; - - // Empty the output arrays - OutInstancedObjects.Empty(); - OutInstancedTransforms.Empty(); - - // TODO: Output the split values as well! - OutSplitAttributeValue.Empty(); - for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedObjects.Num(); ObjIdx++) - { - UObject* InstancedObject = UnsplitInstancedObjects[ObjIdx]; - - // Map of split values to transform arrays - TMap> SplitTransformMap; - - TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; - TArray& CurrentSplits = SplitAttributeValuesPerObject[ObjIdx]; - - int32 NumInstances = CurrentTransforms.Num(); - if (CurrentSplits.Num() != NumInstances) - continue; - - // Split the transforms using the split values - for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) - { - const FString& SplitAttrValue = CurrentSplits[InstIdx]; - SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); - - // Record attributes for any split value we have not yet seen - if (bHasAnyPerSplitAttributes) - { - FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); - if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) - { - PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; - } - if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; - } - if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; - } - if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; - } - } - } - - // Add the objects, transform, split values to the final arrays - for (auto& Iterator : SplitTransformMap) - { - OutSplitAttributeValue.Add(Iterator.Key); - OutInstancedObjects.Add(InstancedObject); - OutInstancedTransforms.Add(Iterator.Value); - } - } - - OutSplitAttributeName = SplitAttribName; - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::OldSchoolAttributeInstancer) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the objects IDs to instanciate - int32 NumPoints = InHGPO.PartInfo.PointCount; - TArray InstancedObjectIds; - InstancedObjectIds.SetNumUninitialized(NumPoints); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedObjectIds( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InstancedObjectIds.GetData(), 0, NumPoints), false); - - // Find the set of instanced object ids and locate the corresponding parts - TSet UniqueInstancedObjectIds(InstancedObjectIds); - - // Locate all the HoudiniGeoPartObject that corresponds to the instanced object IDs - for (int32 InstancedObjectId : UniqueInstancedObjectIds) - { - // Get the parts that correspond to that object Id - TArray PartsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) - { - if (OutHGPO.Type != EHoudiniPartType::Mesh) - continue; - - if (OutHGPO.bIsInstanced) - continue; - - if (InstancedObjectId != OutHGPO.ObjectId) - continue; - - PartsToInstance.Add(OutHGPO); - } - } - - // Extract only the transforms that correspond to that specific object ID - TArray InstanceTransforms; - for (int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix) - { - if ((InstancedObjectIds[Ix] == InstancedObjectId) && (InstancerUnrealTransforms.IsValidIndex(Ix))) - { - InstanceTransforms.Add(InstancerUnrealTransforms[Ix]); - } - } - - // Add the instanced parts and their transforms to the output arrays - for (const auto& PartToInstance : PartsToInstance) - { - OutInstancedHGPO.Add(PartToInstance); - OutInstancedTransforms.Add(InstanceTransforms); - } - } - - if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0) - return true; - - return false; -} - - -bool -FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::ObjectInstancer) - return false; - - if (InHGPO.ObjectInfo.ObjectToInstanceID < 0) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the parts that correspond to that Object Id - TArray PartsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) - { - if (OutHGPO.Type != EHoudiniPartType::Mesh) - continue; - - /* - // But the instanced geo is actually not marked as instanced - if (!OutHGPO.bIsInstanced) - continue; - */ - - if (InHGPO.ObjectInfo.ObjectToInstanceID != OutHGPO.ObjectId) - continue; - - PartsToInstance.Add(OutHGPO); - } - } - - // Add found HGPO and transforms to the output arrays - for (auto& InstanceHGPO : PartsToInstance) - { - InstanceHGPO.TransformMatrix = InHGPO.TransformMatrix; - - // TODO: - //InstanceHGPO.UpdateCustomName(); - - OutInstancedHGPO.Add(InstanceHGPO); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent* OldComponent, - USceneComponent*& NewComponent, - const bool& InIsSplitMeshInstancer, - const bool& InIsFoliageInstancer, - const TArray& InstancerMaterials, - const int32& InstancerObjectIdx, - const bool& bForceHISM) -{ - enum InstancerComponentType - { - Invalid = -1, - InstancedStaticMeshComponent = 0, - HierarchicalInstancedStaticMeshComponent = 1, - MeshSplitInstancerComponent = 2, - HoudiniInstancedActorComponent = 3, - StaticMeshComponent = 4, - HoudiniStaticMeshComponent = 5, - Foliage = 6 - }; - - // See if we can reuse the old component - InstancerComponentType OldType = InstancerComponentType::Invalid; - if (OldComponent/*&& !OldComponent->IsPendingKill()*/) // The old component could be marked as pending kill - { - if(OldComponent->IsA()) - OldType = Foliage; - else if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) - OldType = Foliage; - else if (OldComponent->IsA()) - OldType = HierarchicalInstancedStaticMeshComponent; - else if (OldComponent->IsA()) - OldType = InstancedStaticMeshComponent; - else if (OldComponent->IsA()) - OldType = MeshSplitInstancerComponent; - else if (OldComponent->IsA()) - OldType = HoudiniInstancedActorComponent; - else if (OldComponent->IsA()) - OldType = StaticMeshComponent; - else if (OldComponent->IsA()) - OldType = HoudiniStaticMeshComponent; - } - - // See what type of component we want to create - InstancerComponentType NewType = InstancerComponentType::Invalid; - - UStaticMesh * StaticMesh = Cast(InstancedObject); - UFoliageType * FoliageType = Cast(InstancedObject); - - UHoudiniStaticMesh * HSM = nullptr; - if (!StaticMesh && !FoliageType) - HSM = Cast(InstancedObject); - - if (StaticMesh) - { - if (InstancedObjectTransforms.Num() == 1) - NewType = StaticMeshComponent; - else if (InIsFoliageInstancer) - NewType = Foliage; - else if (InIsSplitMeshInstancer) - NewType = MeshSplitInstancerComponent; - else if(StaticMesh->GetNumLODs() > 1 || bForceHISM) - NewType = HierarchicalInstancedStaticMeshComponent; - else - NewType = InstancedStaticMeshComponent; - } - else if (HSM) - { - if (InstancedObjectTransforms.Num() == 1) - NewType = HoudiniStaticMeshComponent; - else - { - HOUDINI_LOG_ERROR(TEXT("More than one instance transform encountered for UHoudiniStaticMesh: %s"), *(HSM->GetPathName())); - NewType = Invalid; - return false; - } - } - else if (FoliageType) - { - NewType = Foliage; - } - else - { - NewType = HoudiniInstancedActorComponent; - } - - if (OldType == NewType) - NewComponent = OldComponent; - - UMaterialInterface* InstancerMaterial = nullptr; - if (InstancerMaterials.Num() > 0) - { - if (InstancerMaterials.IsValidIndex(InstancerObjectIdx)) - InstancerMaterial = InstancerMaterials[InstancerObjectIdx]; - else - InstancerMaterial = InstancerMaterials[0]; - } - - bool bSuccess = false; - switch (NewType) - { - case InstancedStaticMeshComponent: - case HierarchicalInstancedStaticMeshComponent: - { - // Create an Instanced Static Mesh Component - bSuccess = CreateOrUpdateInstancedStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM); - } - break; - - case MeshSplitInstancerComponent: - { - bSuccess = CreateOrUpdateMeshSplitInstancerComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterials); - } - break; - - case HoudiniInstancedActorComponent: - { - bSuccess = CreateOrUpdateInstancedActorComponent( - InstancedObject, InstancedObjectTransforms, AllPropertyAttributes, ParentComponent, NewComponent); - } - break; - - case StaticMeshComponent: - { - // Create a Static Mesh Component - bSuccess = CreateOrUpdateStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - break; - - case HoudiniStaticMeshComponent: - { - // Create a Houdini Static Mesh Component - bSuccess = CreateOrUpdateHoudiniStaticMeshComponent( - HSM, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - break; - - case Foliage: - { - bSuccess = CreateOrUpdateFoliageInstances( - StaticMesh, FoliageType, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - } - - if (!NewComponent) - return false; - - NewComponent->SetMobility(ParentComponent->Mobility); - NewComponent->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepRelativeTransform); - - // For single instance, that generates a SMC, the transform is already set on the component - // TODO: Should cumulate transform in that case? - if(NewType != StaticMeshComponent && NewType != HoudiniStaticMeshComponent) - NewComponent->SetRelativeTransform(InstancerGeoPartObject.TransformMatrix); - - // Only register if we have a valid component - if (NewComponent->GetOwner() && NewComponent->GetWorld()) - NewComponent->RegisterComponent(); - - // If the old component couldn't be reused, dettach/ destroy it - if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) - { - RemoveAndDestroyComponent(OldComponent, nullptr); - } - - return bSuccess; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial, /*=nullptr*/ - const bool & bForceHISM) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast(CreatedInstancedComponent); - if (!InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill()) - { - if (InstancedStaticMesh->GetNumLODs() > 1 || bForceHISM) - { - // If the mesh has LODs, use Hierarchical ISMC - InstancedStaticMeshComponent = NewObject( - ComponentOuter, UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - } - else - { - // If the mesh doesnt have LOD, we can use a regular ISMC - InstancedStaticMeshComponent = NewObject( - ComponentOuter, UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - } - - // Change the creation method so the component is listed in the details panels - InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!InstancedStaticMeshComponent) - return false; - - InstancedStaticMeshComponent->SetStaticMesh(InstancedStaticMesh); - InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; - - InstancedStaticMeshComponent->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances themselves - // TODO: We should be calling UHoudiniInstancedActorComponent::UpdateInstancerComponentInstances( ... ) - InstancedStaticMeshComponent->ClearInstances(); - InstancedStaticMeshComponent->PreAllocateInstancesMemory(InstancedObjectTransforms.Num()); - for (const FTransform& Transform : InstancedObjectTransforms) - { - InstancedStaticMeshComponent->AddInstance(Transform); - } - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, 0); - - // Assign the new ISMC / HISMC to the output component if we created a new one - if(bCreatedNewComponent) - CreatedInstancedComponent = InstancedStaticMeshComponent; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent) -{ - if (!InstancedObject) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniInstancedActorComponent* InstancedActorComponent = Cast(CreatedInstancedComponent); - if (!InstancedActorComponent || InstancedActorComponent->IsPendingKill()) - { - // If the mesh doesnt have LOD, we can use a regular ISMC - InstancedActorComponent = NewObject( - ComponentOuter, UHoudiniInstancedActorComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - InstancedActorComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!InstancedActorComponent) - return false; - - // See if the instanced object has changed - bool bInstancedObjectHasChanged = (InstancedObject != InstancedActorComponent->GetInstancedObject()); - if (bInstancedObjectHasChanged) - { - // All actors will need to be respawned, invalidate all of them - InstancedActorComponent->ClearAllInstances(); - - // Update the HIAC's instanced asset - InstancedActorComponent->SetInstancedObject(InstancedObject); - } - - // Get the level where we want to spawn the actors - ULevel* SpawnLevel = ParentComponent->GetOwner() ? ParentComponent->GetOwner()->GetLevel() : nullptr; - if (!SpawnLevel) - return false; - - // Set the number of needed instances - InstancedActorComponent->SetNumberOfInstances(InstancedObjectTransforms.Num()); - for (int32 Idx = 0; Idx < InstancedObjectTransforms.Num(); Idx++) - { - // if we already have an actor, we can reuse it - const FTransform& CurTransform = InstancedObjectTransforms[Idx]; - - // Get the current instance - // If null, we need to create a new one, else we can reuse the actor - AActor* CurInstance = InstancedActorComponent->GetInstancedActorAt(Idx); - if (!CurInstance || CurInstance->IsPendingKill()) - { - CurInstance = SpawnInstanceActor(CurTransform, SpawnLevel, InstancedActorComponent); - InstancedActorComponent->SetInstanceAt(Idx, CurTransform, CurInstance); - } - else - { - // We can simply update the actor's transform - InstancedActorComponent->SetInstanceTransformAt(Idx, CurTransform); - } - - // Update the generic properties for that instance if any - // TODO: Handle instance variations w/ Idx - UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, Idx); - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - { - CreatedInstancedComponent = InstancedActorComponent; - } - - return true; -} - -// Create or update a MSIC -bool -FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - const TArray& InInstancerMaterials) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniMeshSplitInstancerComponent* MeshSplitComponent = Cast(CreatedInstancedComponent); - if (!MeshSplitComponent || MeshSplitComponent->IsPendingKill()) - { - // If the mesh doesn't have LOD, we can use a regular ISMC - MeshSplitComponent = NewObject( - ComponentOuter, UHoudiniMeshSplitInstancerComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - MeshSplitComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!MeshSplitComponent) - return false; - - MeshSplitComponent->SetStaticMesh(InstancedStaticMesh); - MeshSplitComponent->SetOverrideMaterials(InInstancerMaterials); - - // Now add the instances - MeshSplitComponent->SetInstanceTransforms(InstancedObjectTransforms); - - // Check for instance colors - TArray InstanceColorOverrides; - bool ColorOverrideAttributeFound = false; - - // Look for the unreal_instance_color attribute on points - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_POINT, &AttributeInfo)) - { - ColorOverrideAttributeFound = AttributeInfo.exists; - } - - // Look for the unreal_instance_color attribute on prims? (why? original code) - if (!ColorOverrideAttributeFound) - { - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_PRIM, &AttributeInfo)) - { - ColorOverrideAttributeFound = AttributeInfo.exists; - } - } - - if (ColorOverrideAttributeFound) - { - if (AttributeInfo.tupleSize == 4) - { - // Allocate sufficient buffer for data. - InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)InstanceColorOverrides.GetData(), 0, AttributeInfo.count)) - { - InstanceColorOverrides.Empty(); - } - } - else if (AttributeInfo.tupleSize == 3) - { - // Allocate sufficient buffer for data. - TArray FloatValues; - FloatValues.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)FloatValues.GetData(), 0, AttributeInfo.count)) - { - - // Allocate sufficient buffer for data. - InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); - - // Convert float to FLinearColors - for (int32 ColorIdx = 0; ColorIdx < InstanceColorOverrides.Num(); ColorIdx++) - { - InstanceColorOverrides[ColorIdx].R = FloatValues[ColorIdx * AttributeInfo.tupleSize + 0]; - InstanceColorOverrides[ColorIdx].G = FloatValues[ColorIdx * AttributeInfo.tupleSize + 1]; - InstanceColorOverrides[ColorIdx].B = FloatValues[ColorIdx * AttributeInfo.tupleSize + 2]; - InstanceColorOverrides[ColorIdx].A = 1.0; - } - FloatValues.Empty(); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_COLOR " must be a float[4] or float[3] prim/point attribute")); - } - } - - // if we have vertex color overrides, apply them now -#if WITH_EDITOR - if (InstanceColorOverrides.Num() > 0) - { - // Convert the color attribute to FColor - TArray InstanceColors; - InstanceColors.SetNumUninitialized(InstanceColorOverrides.Num()); - for (int32 ix = 0; ix < InstanceColors.Num(); ++ix) - { - InstanceColors[ix] = InstanceColorOverrides[ix].GetClamped().ToFColor(false); - } - - // Apply them to the instances - TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); - for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) - { - UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - if (!InstanceColors.IsValidIndex(InstIndex)) - continue; - - MeshPaintHelpers::FillStaticMeshVertexColors(CurSMC, -1, InstanceColors[InstIndex], FColor::White); - - //CurSMC->UnregisterComponent(); - //CurSMC->ReregisterComponent(); - - { - // We're only changing instanced vertices on this specific mesh component, so we - // only need to detach our mesh component - FComponentReregisterContext ComponentReregisterContext(CurSMC); - for (auto& CurLODData : CurSMC->LODData) - { - BeginInitResource(CurLODData.OverrideVertexColors); - } - } - - //FIXME: How to get rid of the warning about fixup vertex colors on load? - //SMC->FixupOverrideColorsIfNecessary(); - } - } -#endif - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - // TODO: Optimize - // Loop on attributes first, then components, - // if failing to find the attrib on a component, skip the rest - if (AllPropertyAttributes.Num() > 0) - { - TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); - for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) - { - UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); - } - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = MeshSplitComponent; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UStaticMeshComponent* SMC = Cast(CreatedInstancedComponent); - if (!SMC || SMC->IsPendingKill()) - { - // Create a new StaticMeshComponent - SMC = NewObject( - ComponentOuter, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - SMC->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!SMC) - return false; - - SMC->SetStaticMesh(InstancedStaticMesh); - SMC->GetBodyInstance()->bAutoWeld = false; - - SMC->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - SMC->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances Transform - if (InstancedObjectTransforms.Num() > 0) - { - SMC->SetRelativeTransform(InstancedObjectTransforms[0]); - } - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, 0); - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = SMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( - UHoudiniStaticMesh* InstancedProxyStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - if (!InstancedProxyStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniStaticMeshComponent* HSMC = Cast(CreatedInstancedComponent); - if (!HSMC || HSMC->IsPendingKill()) - { - // Create a new StaticMeshComponent - HSMC = NewObject( - ComponentOuter, UHoudiniStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - HSMC->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!HSMC) - return false; - - HSMC->SetMesh(InstancedProxyStaticMesh); - - HSMC->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedProxyStaticMesh->GetNumStaticMaterials(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - HSMC->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances Transform - HSMC->SetRelativeTransform(InstancedObjectTransforms[0]); - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, 0); - - // Assign the new HSMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = HSMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - - -bool -FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( - UStaticMesh* InstancedStaticMesh, - UFoliageType* InFoliageType, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& NewInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - // We need either a valid SM or a valid Foliage Type - if ((!InstancedStaticMesh || InstancedStaticMesh->IsPendingKill()) - && (!InFoliageType || InFoliageType->IsPendingKill())) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - AActor* OwnerActor = ParentComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return false; - - // See if we already have a FoliageType for that static mesh - bool bCreatedNew = false; - UFoliageType *FoliageType = InFoliageType; - if (!FoliageType || FoliageType->IsPendingKill()) - { - // Foliage Type wasnt specified, only the mesh, try to find an existing foliage for that SM - FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InstancedStaticMesh); - } - else - { - // Foliage Type was specified, see if we can get its static mesh - UFoliageType_InstancedStaticMesh* FoliageISM = Cast(InFoliageType); - if (FoliageISM) - { - InstancedStaticMesh = FoliageISM->GetStaticMesh(); - } - - // See a component already exist on the actor - // If we cant find Foliage info for that foliage type, a new one will be created. - // when we call FindOrAddMesh - bCreatedNew = InstancedFoliageActor->FindInfo(FoliageType) == nullptr; - } - - if (!FoliageType || FoliageType->IsPendingKill()) - { - // We need to create a new FoliageType for this Static Mesh - // TODO: Add foliage default settings - InstancedFoliageActor->AddMesh(InstancedStaticMesh, &FoliageType); - bCreatedNew = true; - } - - if (!bCreatedNew && NewInstancedComponent) - { - // TODO: Shouldnt be needed anymore - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); - } - - // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it - FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); - if (!FoliageInfo) - return false; - - FTransform HoudiniAssetTransform = ParentComponent->GetComponentTransform(); - FFoliageInstance FoliageInstance; - int32 CurrentInstanceCount = 0; - for (auto CurrentTransform : InstancedObjectTransforms) - { - // Use our parent component for the base component of the instances, - // this will allow us to clean the instances by component - FoliageInstance.BaseComponent = ParentComponent; - - // TODO: FIX ME! - // Somehow, the first time when we create the Foliage type, instances need to be added with relative transform - // On subsequent cooks, they are actually expecting world transform - if (bCreatedNew) - { - FoliageInstance.Location = CurrentTransform.GetLocation(); - FoliageInstance.Rotation = CurrentTransform.GetRotation().Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D(); - } - else - { - FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); - FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); - } - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - CurrentInstanceCount++; - } - - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); - if (IsValid(FoliageHISMC)) - { - // TODO: This was due to a bug in UE4.22-20, check if still needed! - FoliageHISMC->BuildTreeIfOutdated(true, true); - - if (InstancerMaterial) - { - FoliageHISMC->OverrideMaterials.Empty(); - int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->StaticMaterials.Num() : 1; - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - FoliageHISMC->SetMaterial(Idx, InstancerMaterial); - } - } - - // Try to apply generic properties attributes - // either on the instancer, mesh or foliage type - // TODO: Use proper atIndex!! - UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); - UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, 0); - UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, 0); - - if (IsValid(FoliageHISMC)) - NewInstancedComponent = FoliageHISMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::HapiGetInstanceTransforms( - const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancerUnrealTransforms) -{ - // Get the instance transforms - int32 PointCount = InHGPO.PartInfo.PointCount; - if (PointCount <= 0) - return false; - - TArray InstanceTransforms; - InstanceTransforms.SetNum(PointCount); - for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++) - FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx])); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetInstanceTransformsOnPart( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, HAPI_SRT, - &InstanceTransforms[0], 0, PointCount)) - { - InstanceTransforms.SetNum(0); - - // TODO: Warning? error? - return false; - } - - // Convert the transform to Unreal's coordinate system - OutInstancerUnrealTransforms.SetNumZeroed(InstanceTransforms.Num()); - for (int32 InstanceIdx = 0; InstanceIdx < InstanceTransforms.Num(); InstanceIdx++) - { - const auto& InstanceTransform = InstanceTransforms[InstanceIdx]; - FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, OutInstancerUnrealTransforms[InstanceIdx]); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::GetGenericPropertiesAttributes( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then get all the values for the primitive property attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, -1); - - // .. then finally, all values for point uproperty attributes - // TODO: !! get the correct Index here? - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, -1); - - return FoundCount > 0; -} - -bool -FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes, const int32& AtIndex) -{ - if (!IsValid(InObject)) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (const auto& CurrentPropAttribute : InAllPropertyAttributes) - { - if (CurrentPropAttribute.AttributeName.Equals(TEXT("NumCustomDataFloats"), ESearchCase::IgnoreCase)) - { - // Skip, as setting NumCustomDataFloats this way causes Unreal to crash! - HOUDINI_LOG_WARNING( - TEXT("Skipping UProperty %s on %s, custom data floats should be modified via the unreal_num_custom_floats and unreal_per_instance_custom_dataX attributes"), - *CurrentPropAttribute.AttributeName, *InObject->GetName()); - continue; - } - - // Update the current property for the given instance index - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute, AtIndex)) - continue; - - // Success! - NumSuccess++; - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, InObject->GetClass() ? *InObject->GetClass()->GetName() : TEXT("Object"), *InObject->GetName()); - } - - return (NumSuccess > 0); -} - -bool -FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent, UObject* InFoliageObject) -{ - if (!InComponent || InComponent->IsPendingKill()) - return false; - - UFoliageInstancedStaticMeshComponent* FISMC = Cast(InComponent); - if (FISMC && !FISMC->IsPendingKill()) - { - // Make sure foliage our foliage instances have been removed - USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) - CleanupFoliageInstances(FISMC, InFoliageObject, ParentComponent); - - // do not delete FISMC that still have instances left - // as we have cleaned up our instances before, these have been hand-placed - if (FISMC->GetInstanceCount() > 0) - return false; - } - - USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - return true; - } - - return false; -} - - -bool -FHoudiniInstanceTranslator::GetMaterialOverridesFromAttributes( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutMaterialAttributes) -{ - HAPI_AttributeInfo MaterialAttributeInfo; - FHoudiniApi::AttributeInfo_Init(&MaterialAttributeInfo); - - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL, MaterialAttributeInfo, OutMaterialAttributes); - - /* - // TODO: Support material instances on instancers... - // see FHoudiniMaterialTranslator::CreateMaterialInstances() - // If material attribute and fallbacks were not found, check the material instance attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, MaterialAttributeInfo, OutMaterialAttributes); - } - */ - - if (!MaterialAttributeInfo.exists - /*&& MaterialAttributeInfo.owner != HAPI_ATTROWNER_PRIM - && MaterialAttributeInfo.owner != HAPI_ATTROWNER_DETAIL*/) - { - //HOUDINI_LOG_WARNING(TEXT("Instancer: the unreal_material attribute must be a primitive or detail attribute, ignoring the attribute.")); - OutMaterialAttributes.Empty(); - return false; - } - - return true; -} - -bool -FHoudiniInstanceTranslator::GetInstancerMaterials( - const TArray& MaterialAttributes, TArray& OutInstancerMaterials) -{ - // Use a map to avoid attempting to load the object for each instance - TMap MaterialMap; - - bool bHasValidMaterial = false; - for (auto& CurrentMatString : MaterialAttributes) - { - UMaterialInterface* CurrentMaterialInterface = nullptr; - UMaterialInterface** FoundMaterial = MaterialMap.Find(CurrentMatString); - if (!FoundMaterial) - { - // See if we can find a material interface that matches the attribute - CurrentMaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentMatString, nullptr, LOAD_NoWarn, nullptr)); - - // Check validity - if (!CurrentMaterialInterface || CurrentMaterialInterface->IsPendingKill()) - CurrentMaterialInterface = nullptr; - else - bHasValidMaterial = true; - - // Add what we found to the material map to avoid unnecessary loads - MaterialMap.Add(CurrentMatString, CurrentMaterialInterface); - } - else - { - // Reuse what we previously found - CurrentMaterialInterface = *FoundMaterial; - } - - OutInstancerMaterials.Add(CurrentMaterialInterface); - } - - // IF we couldn't find at least one valid material interface, empty the array - if (!bHasValidMaterial) - OutInstancerMaterials.Empty(); - - return true; -} - -bool -FHoudiniInstanceTranslator::GetInstancerMaterials( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutInstancerMaterials) -{ - TArray MaterialAttributes; - if (!GetMaterialOverridesFromAttributes(InGeoNodeId, InPartId, MaterialAttributes)) - MaterialAttributes.Empty(); - - return GetInstancerMaterials(MaterialAttributes, OutInstancerMaterials); -} - -bool -FHoudiniInstanceTranslator::GetVariationMaterials( - FHoudiniInstancedOutput* InInstancedOutput , const int32& InVariationIndex, - const TArray& InInstancerMaterials, TArray& OutVariationMaterials) -{ - if (!InInstancedOutput || InInstancerMaterials.Num() <= 0) - return false; - - // TODO: This also need to be improved and wont work 100%!! - // Use the instancedoutputs original object index? - if(!InInstancedOutput->VariationObjects.IsValidIndex(InVariationIndex)) - return false; - /* - // No variations, reuse the array - if (InInstancedOutput->VariationObjects.Num() == 1) - { - OutVariationMaterials = InInstancerMaterials; - return true; - } - */ - - if (InInstancedOutput->TransformVariationIndices.Num() == InInstancerMaterials.Num()) - { - for (int32 Idx = 0; Idx < InInstancedOutput->TransformVariationIndices.Num(); Idx++) - { - int32 VariationAssignment = InInstancedOutput->TransformVariationIndices[Idx]; - if (VariationAssignment != InVariationIndex) - continue; - - OutVariationMaterials.Add(InInstancerMaterials[Idx]); - } - } - else - { - if (InInstancerMaterials.IsValidIndex(InVariationIndex)) - OutVariationMaterials.Add(InInstancerMaterials[InVariationIndex]); - else - OutVariationMaterials.Add(InInstancerMaterials[0]); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::IsSplitInstancer(const int32& InGeoId, const int32& InPartId) -{ - bool bSplitMeshInstancer = false; - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; - bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); - - if (!bSplitMeshInstancer) - { - // Try on primitive - Owner = HAPI_ATTROWNER_PRIM; - bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); - } - - if (!bSplitMeshInstancer) - return false; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, - Owner, &AttributeInfo), false); - - if (!AttributeInfo.exists || AttributeInfo.count <= 0) - return false; - - TArray IntData; - // Allocate sufficient buffer for data. - IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, - &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); - - return (IntData[0] != 0); -} - -bool -FHoudiniInstanceTranslator::IsFoliageInstancer(const int32& InGeoId, const int32& InPartId) -{ - bool bIsFoliageInstancer = false; - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - - if (!bIsFoliageInstancer) - { - // Try on primitive - Owner = HAPI_ATTROWNER_PRIM; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - } - - if (!bIsFoliageInstancer) - { - // Finally, try on points - Owner = HAPI_ATTROWNER_POINT; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - } - - if (!bIsFoliageInstancer) - return false; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - Owner, &AttributeInfo), false); - - if (!AttributeInfo.exists || AttributeInfo.count <= 0) - return false; - - // We only support int/float attributes - if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - TArray IntData; - // Allocate sufficient buffer for data. - IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); - - return (IntData[0] != 0); - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - TArray FloatData; - // Allocate sufficient buffer for data. - FloatData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - &AttributeInfo, 0, &FloatData[0], 0, AttributeInfo.count), false); - - return (FloatData[0] != 0); - } - - return false; -} - - -AActor* -FHoudiniInstanceTranslator::SpawnInstanceActor( - const FTransform& InTransform, - ULevel* InSpawnLevel, - UHoudiniInstancedActorComponent* InIAC) -{ - if (!InIAC || InIAC->IsPendingKill()) - return nullptr; - - UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - return nullptr; - - AActor* NewActor = nullptr; - -#if WITH_EDITOR - // Try to spawn a new actor for the given transform - GEditor->ClickLocation = InTransform.GetTranslation(); - GEditor->ClickPlane = FPlane(GEditor->ClickLocation, FVector::UpVector); - - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(InSpawnLevel, InstancedObject, false, RF_Transactional, nullptr); - if (NewActors.Num() > 0) - { - if (NewActors[0] && !NewActors[0]->IsPendingKill()) - { - NewActor = NewActors[0]; - } - } -#endif - - // Make sure that the actor was spawned in the proper level - FHoudiniEngineUtils::MoveActorToLevel(NewActor, InSpawnLevel); - - return NewActor; -} - - -void -FHoudiniInstanceTranslator::CleanupFoliageInstances( - UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, - UObject* InInstancedObject, - USceneComponent* InParentComponent) -{ - if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) - return; - - UStaticMesh* FoliageSM = InFoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - return; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(InFoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return; - - // Get the Foliage Type - UFoliageType *FoliageType = Cast(InInstancedObject); - if (!FoliageType || FoliageType->IsPendingKill()) - { - // Try to get the foliage type for the instanced mesh from the actor - FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InInstancedObject); - - if (!FoliageType || FoliageType->IsPendingKill()) - return; - } - - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(InParentComponent, FoliageType); - - // Remove the foliage type if it doesn't have any more instances - if(InFoliageHISMC->GetInstanceCount() == 0) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - return; -} - - -FString -FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(UObject* InObject) -{ - USceneComponent* InComponent = Cast(InObject); - - FString InstancerType = TEXT("Instancer"); - if (InComponent && !InComponent->IsPendingKill()) - { - if (InComponent->IsA()) - { - InstancerType = TEXT("(Split Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Actor Instancer)"); - } - else if (InComponent->IsA()) - { - if (InComponent->GetOwner() && InComponent->GetOwner()->IsA()) - InstancerType = TEXT("(Foliage Instancer)"); - else - InstancerType = TEXT("(Hierarchical Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Mesh Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Static Mesh Component)"); - } - } - - return InstancerType; -} - -bool -FHoudiniInstanceTranslator::GetInstancerSplitAttributesAndValues( - const int32& InGeoId, - const int32& InPartId, - const HAPI_AttributeOwner& InSplitAttributeOwner, - FString& OutSplitAttributeName, - TArray& OutAllSplitAttributeValues) -{ - // See if the user has specified an attribute to split the instancers. - bool bHasSplitAttribute = false; - //FString SplitAttribName = FString(); - OutSplitAttributeName = FString(); - - // Look for the unreal_split_attr attribute - // This attribute indicates the name of the point attribute that we'll use to split the instances further - HAPI_AttributeInfo SplitAttribInfo; - FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); - - TArray StringData; - bHasSplitAttribute = FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_SPLIT_ATTR, SplitAttribInfo, StringData, 1); - - if (!bHasSplitAttribute || !SplitAttribInfo.exists || StringData.Num() <= 0) - return false; - - OutSplitAttributeName = StringData[0]; - - // We have specified a split attribute, try to get its values. - OutAllSplitAttributeValues.Empty(); - if (!OutSplitAttributeName.IsEmpty()) - { - //HAPI_AttributeInfo SplitAttribInfo; - FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); - bool bSplitAttrFound = FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoId, - InPartId, - TCHAR_TO_ANSI(*OutSplitAttributeName), - SplitAttribInfo, - OutAllSplitAttributeValues, - 1, - InSplitAttributeOwner); - - if (!bSplitAttrFound || OutAllSplitAttributeValues.Num() <= 0) - { - // We couldn't properly get the point values, clean up everything - // to ensure that we'll ignore the split attribute - bHasSplitAttribute = false; - OutAllSplitAttributeValues.Empty(); - OutSplitAttributeName = FString(); - } - } - - return bHasSplitAttribute; -} - -bool -FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId) -{ - bool bHISM = false; - HAPI_AttributeInfo AttriInfo; - FHoudiniApi::AttributeInfo_Init(&AttriInfo); - TArray IntData; - IntData.Empty(); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM, AttriInfo, IntData, 1)) - { - if (IntData.Num() > 0) - bHISM = IntData[0] == 1; - } - - return bHISM; -} - -void -FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() -{ - NumInstancedTransformsPerObject.Empty(); - OriginalInstancedTransformsFlat.Empty(); - for (const TArray& Transforms : OriginalInstancedTransforms) - { - NumInstancedTransformsPerObject.Add(Transforms.Num()); - OriginalInstancedTransformsFlat.Append(Transforms); - } - - OriginalInstanceObjectPackagePaths.Empty(); - for (const UObject* Obj : OriginalInstancedObjects) - { - if (IsValid(Obj)) - { - OriginalInstanceObjectPackagePaths.Add(Obj->GetPathName()); - } - else - { - OriginalInstanceObjectPackagePaths.Add(FString()); - } - } -} - -void -FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() -{ - const int32 NumObjects = NumInstancedTransformsPerObject.Num(); - OriginalInstancedTransforms.Init(TArray(), NumObjects); - int32 ObjectIndexOffset = 0; - for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) - { - TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; - const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; - for (int32 Index = 0; Index < NumInstances; ++Index) - { - Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); - } - ObjectIndexOffset += NumInstances; - } - - OriginalInstancedObjects.Empty(); - for (const FString& PackageFullPath : OriginalInstanceObjectPackagePaths) - { - FString PackagePath; - FString PackageName; - const bool bDidSplit = PackageFullPath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); - if (!bDidSplit) - PackagePath = PackageFullPath; - - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!IsValid(Package)) - { - // Editor might have picked up the package yet, try to load it - Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); - } - if (IsValid(Package)) - { - OriginalInstancedObjects.Add(FindObject(Package, *PackageName)); - } - else - { - OriginalInstancedObjects.Add(nullptr); - } - } -} - -bool -FHoudiniInstanceTranslator::GetPerInstanceCustomData( - const int32& InGeoNodeId, - const int32& InPartId, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) -{ - // Initialize sizes to zero - OutInstancedOutputPartData.NumCustomFloats = 0; - OutInstancedOutputPartData.PerInstanceCustomData.SetNum(0); - - // First look for the number of custom floats - // If we dont have the attribute, or it is set to zero, we dont have PerInstanceCustomData - // HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" - HAPI_AttributeInfo AttribInfoNumCustomFloats; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNumCustomFloats); - - TArray CustomFloatsArray; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - InGeoNodeId, InPartId, - HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS, - AttribInfoNumCustomFloats, - CustomFloatsArray)) - { - return false; - } - - if (CustomFloatsArray.Num() <= 0) - return false; - - OutInstancedOutputPartData.NumCustomFloats = CustomFloatsArray[0]; - if (OutInstancedOutputPartData.NumCustomFloats <= 0) - return false; - - // We do have custom float, now read the per instance custom data - // They are stored in attributes that uses the "unreal_per_instance_custom" prefix - // ie, unreal_per_instance_custom0, unreal_per_instance_custom1 etc... - // We do not supprot tuples/arrays attributes for now. - TArray> AllCustomDataAttributeValues; - AllCustomDataAttributeValues.SetNum(OutInstancedOutputPartData.NumCustomFloats); - - // Read the custom data attributes - int32 NumInstance = 0; - for (int32 nIdx = 0; nIdx < OutInstancedOutputPartData.NumCustomFloats; nIdx++) - { - // Build the custom data attribute - FString CurrentAttr = TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX) + FString::FromInt(nIdx); - - // TODO? Tuple values Array attributes? - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - - // Retrieve the custom data values - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - InGeoNodeId, InPartId, - TCHAR_TO_ANSI(*CurrentAttr), - AttribInfo, - AllCustomDataAttributeValues[nIdx], - 1)) - { - // Skip, we'll fill the values with zeros later on - continue; - } - - if (NumInstance < AllCustomDataAttributeValues[nIdx].Num()) - NumInstance = AllCustomDataAttributeValues[nIdx].Num(); - - if (NumInstance != AllCustomDataAttributeValues[nIdx].Num()) - { - HOUDINI_LOG_ERROR(TEXT("Instancer: Invalid number of Per-Instance Custom data attributes, ignoring...")); - OutInstancedOutputPartData.NumCustomFloats = 0; - return false; - } - } - - // Check sizes - if (AllCustomDataAttributeValues.Num() != OutInstancedOutputPartData.NumCustomFloats) - { - HOUDINI_LOG_ERROR(TEXT("Instancer: Number of Per-Instance Custom data attributes don't match the number of custom floats, ignoring...")); - OutInstancedOutputPartData.NumCustomFloats = 0; - return false; - } - - // Now that we have read all the custom data values, we need to "interlace" them - // in the final per-instance custom data array, fill missing values with zeroes - OutInstancedOutputPartData.PerInstanceCustomData.SetNumZeroed(OutInstancedOutputPartData.NumCustomFloats * NumInstance); - - // Fill the custom data array by interlacing the custom float values - for (int32 nCustomIdx = 0; nCustomIdx < OutInstancedOutputPartData.NumCustomFloats; nCustomIdx++) - { - int32 CurrentNumInstance = NumInstance; - if (NumInstance < AllCustomDataAttributeValues[nCustomIdx].Num()) - CurrentNumInstance = AllCustomDataAttributeValues[nCustomIdx].Num(); - - // Copy the attribute value we read into the custom data array - for (int32 nInstanceIdx = 0; nInstanceIdx < CurrentNumInstance; nInstanceIdx++) - { - OutInstancedOutputPartData.PerInstanceCustomData[nInstanceIdx * OutInstancedOutputPartData.NumCustomFloats + nCustomIdx] = AllCustomDataAttributeValues[nCustomIdx][nInstanceIdx]; - } - } - - return true; -} - - -bool -FHoudiniInstanceTranslator::UpdateChangedPerInstanceCustomData( - const int32& InNumCustomFloats, - const TArray& InPerInstanceCustomData, - USceneComponent* InComponentToUpdate) -{ - // Checks - if (InNumCustomFloats < 0) - return false; - - UInstancedStaticMeshComponent* ISMC = Cast(InComponentToUpdate); - if (!IsValid(ISMC)) - return false; - - // No Custom data to add/remove - if (ISMC->NumCustomDataFloats == 0 && InNumCustomFloats == 0) - return false; - - // We can copy the per instance custom data if we have any - // TODO: Properly extract only needed values! - ISMC->NumCustomDataFloats = InNumCustomFloats; - - int32 InstanceCount = ISMC->GetInstanceCount(); - - // Clear out and reinit to 0 the PerInstanceCustomData array - ISMC->PerInstanceSMCustomData.Empty(InstanceCount * InNumCustomFloats); - ISMC->PerInstanceSMCustomData.SetNumZeroed(InstanceCount * InNumCustomFloats); - - // Behaviour copied From UInstancedStaticMeshComponent::SetCustomData() - // except we modify all the instance/custom values at once - ISMC->Modify(); - - // MemCopy - const int32 NumToCopy = FMath::Min(ISMC->PerInstanceSMCustomData.Num(), InPerInstanceCustomData.Num()); - if (NumToCopy > 0) - { - FMemory::Memcpy(&ISMC->PerInstanceSMCustomData[0], InPerInstanceCustomData.GetData(), NumToCopy * InPerInstanceCustomData.GetTypeSize()); - } - - // Force recreation of the render data when proxy is created - //NewISMC->InstanceUpdateCmdBuffer.Edit(); - // Cant call the edit function above because the function is defined in a different cpp file than the .h it is declared in... - ISMC->InstanceUpdateCmdBuffer.NumEdits++; - - ISMC->MarkRenderStateDirty(); - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInstanceTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniStaticMesh.h" + +//#include "HAPI/HAPI_Common.h" + +#include "Engine/StaticMesh.h" +#include "ComponentReregisterContext.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#if WITH_EDITOR + //#include "ScopedTransaction.h" + #include "LevelEditorViewport.h" + #include "MeshPaintHelpers.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// Fastrand is a faster alternative to std::rand() +// and doesn't oscillate when looking for 2 values like Unreal's. +inline int fastrand(int& nSeed) +{ + nSeed = (214013 * nSeed + 2531011); + return (nSeed >> 16) & 0x7FFF; +} + +// +bool +FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) +{ + // Get if force to use HISM from attribute + OutInstancedOutputPartData.bForceHISM = HasHISMAttribute(InHGPO.GeoId, InHGPO.PartId); + + // Extract the object and transforms for this instancer + if (!GetInstancerObjectsAndTransforms( + InHGPO, + InAllOutputs, + OutInstancedOutputPartData.OriginalInstancedObjects, + OutInstancedOutputPartData.OriginalInstancedTransforms, + OutInstancedOutputPartData.SplitAttributeName, + OutInstancedOutputPartData.SplitAttributeValues, + OutInstancedOutputPartData.PerSplitAttributes)) + return false; + + // Check if this is a No-Instancers ( unreal_split_instances ) + OutInstancedOutputPartData.bSplitMeshInstancer = IsSplitInstancer(InHGPO.GeoId, InHGPO.PartId); + + OutInstancedOutputPartData.bIsFoliageInstancer = IsFoliageInstancer(InHGPO.GeoId, InHGPO.PartId); + + // Extract the generic attributes + GetGenericPropertiesAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllPropertyAttributes); + + // Check for per instance custom data + GetPerInstanceCustomData(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData); + + //Get the level path attribute on the instancer + if (!FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllLevelPaths)) + { + // No attribute specified + OutInstancedOutputPartData.AllLevelPaths.Empty(); + } + + // Get the output name attribute + if (!FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.OutputNames)) + { + // No attribute specified + OutInstancedOutputPartData.OutputNames.Empty(); + } + + // See if we have a tile attribute + if (!FHoudiniEngineUtils::GetTileAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.TileValues)) + { + // No attribute specified + OutInstancedOutputPartData.TileValues.Empty(); + } + + // Get the bake actor attribute + if (!FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeActorNames)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeActorNames.Empty(); + } + + // Get the unreal_bake_folder attribute + if (!FHoudiniEngineUtils::GetBakeFolderAttribute(InHGPO.GeoId, OutInstancedOutputPartData.AllBakeFolders, InHGPO.PartId)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeFolders.Empty(); + } + + // Get the bake outliner folder attribute + if (!FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeOutlinerFolders)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeOutlinerFolders.Empty(); + } + + // See if we have instancer material overrides + if (!GetMaterialOverridesFromAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.MaterialAttributes)) + OutInstancedOutputPartData.MaterialAttributes.Empty(); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + UHoudiniOutput* InOutput, + const TArray& InAllOutputs, + UObject* InOuterComponent, + const TMap* InPreBuiltInstancedOutputPartData) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + // Keep track of the previous cook's component to clean them up after + TMap NewOutputObjects; + TMap OldOutputObjects = InOutput->GetOutputObjects(); + + TMap& InstancedOutputs = InOutput->GetInstancedOutputs(); + // Mark all the current instanced output as stale + for (auto& InstOut : InstancedOutputs) + InstOut.Value.bStale = true; + + USceneComponent* ParentComponent = Cast(InOuterComponent); + if (!ParentComponent) + return false; + + // Keep track of if we remove, create or update any foliage, so that we can repopulate the foliage type list in + // the UI (foliage mode) at the end + bool bHaveAnyFoliageInstancers = false; + + // We also need to cleanup the previous foliages instances (if we have any) + for (auto& CurrentPair : OldOutputObjects) + { + // Foliage instancers store a HISMC in the components + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentPair.Value.OutputComponent); + if (!IsValid(FoliageHISMC)) + continue; + + CleanupFoliageInstances(FoliageHISMC, CurrentPair.Value.OutputObject, ParentComponent); + bHaveAnyFoliageInstancers = true; + } + + // The default SM to be used if the instanced object has not been found (when using attribute instancers) + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) + { + // Not an instancer, skip + if (CurHGPO.Type != EHoudiniPartType::Instancer) + continue; + + // Prepare this output object's output identifier + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = CurHGPO.ObjectId; + OutputIdentifier.GeoId = CurHGPO.GeoId; + OutputIdentifier.PartId = CurHGPO.PartId; + OutputIdentifier.PartName = CurHGPO.PartName; + + FHoudiniInstancedOutputPartData InstancedOutputPartDataTmp; + const FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = nullptr; + if (InPreBuiltInstancedOutputPartData) + { + InstancedOutputPartDataPtr = InPreBuiltInstancedOutputPartData->Find(OutputIdentifier); + } + if (!InstancedOutputPartDataPtr) + { + if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs, InstancedOutputPartDataTmp)) + continue; + InstancedOutputPartDataPtr = &InstancedOutputPartDataTmp; + } + + const FHoudiniInstancedOutputPartData& InstancedOutputPartData = *InstancedOutputPartDataPtr; + + TArray InstancerMaterials; + if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes, InstancerMaterials)) + InstancerMaterials.Empty(); + + if (InstancedOutputPartData.bIsFoliageInstancer) + bHaveAnyFoliageInstancers = true; + + // + // TODO: REFACTOR THIS! + // + // We create an instanced output per original object + // These original object can then potentially be replaced by variations + // Each variations will create a instance component / OutputObject + // Currently we process all original objects AND their variations at the same time + // we should instead loop on the original objects + // - get their variations objects/transform + // - create the appropriate instancer + // This means modifying UpdateInstanceVariationsObjects so that it works using + // a single OriginalObject instead of using an array + // Also, apply the same logic to UpdateChangedInstanceOutput + // + + // Array containing all the variations objects for all the original objects + TArray> VariationInstancedObjects; + // Array containing all the variations transforms + TArray> VariationInstancedTransforms; + // Array indicate the original object index for each variation + TArray VariationOriginalObjectIndices; + // Array indicate the variation number for each variation + TArray VariationIndices; + // Update our variations using the instanced outputs + UpdateInstanceVariationObjects( + OutputIdentifier, + InstancedOutputPartData.OriginalInstancedObjects, + InstancedOutputPartData.OriginalInstancedTransforms, + InOutput->GetInstancedOutputs(), + VariationInstancedObjects, + VariationInstancedTransforms, + VariationOriginalObjectIndices, + VariationIndices); + + // Preload objects so we can benefit from async compilation as much as possible + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) + { + VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); + } + + // Create the instancer components now + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) + { + UObject* InstancedObject = VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + continue; + + if (!VariationInstancedTransforms.IsValidIndex(InstanceObjectIdx)) + continue; + + const TArray& InstancedObjectTransforms = VariationInstancedTransforms[InstanceObjectIdx]; + if (InstancedObjectTransforms.Num() <= 0) + continue; + + // Find the matching instance output now + FHoudiniInstancedOutput* FoundInstancedOutput = nullptr; + { + // Instanced output only use the original object index for their split identifier + FHoudiniOutputObjectIdentifier InstancedOutputIdentifier = OutputIdentifier; + InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]); + FoundInstancedOutput = InstancedOutputs.Find(InstancedOutputIdentifier); + } + + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + OutputIdentifier.SplitIdentifier = + FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]) + + TEXT("_") + + FString::FromInt(VariationIndices[InstanceObjectIdx]); + + // Get the OutputObj for this variation + FHoudiniOutputObject* FoundOutputObject = OldOutputObjects.Find(OutputIdentifier); + // See if we can find an preexisting component for this obj to try to reuse it + USceneComponent* OldInstancerComponent = nullptr; + const bool bIsProxyMesh = InstancedObject->IsA(); + if (FoundOutputObject) + { + if (bIsProxyMesh) + { + OldInstancerComponent = Cast(FoundOutputObject->ProxyComponent); + } + else + { + OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); + } + } + + // Extract the material for this variation + TArray VariationMaterials; + if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) + VariationMaterials.Empty(); + + USceneComponent* NewInstancerComponent = nullptr; + if (!CreateOrUpdateInstanceComponent( + InstancedObject, + InstancedObjectTransforms, + InstancedOutputPartData.AllPropertyAttributes, + CurHGPO, + ParentComponent, + OldInstancerComponent, + NewInstancerComponent, + InstancedOutputPartData.bSplitMeshInstancer, + InstancedOutputPartData.bIsFoliageInstancer, + VariationMaterials, + InstanceObjectIdx, + InstancedOutputPartData.bForceHISM)) + { + // TODO?? + continue; + } + + if (!NewInstancerComponent) + continue; + + // Copy the per-instance custom data if we have any + UpdateChangedPerInstanceCustomData( + InstancedOutputPartData.NumCustomFloats, + InstancedOutputPartData.PerInstanceCustomData, + NewInstancerComponent); + + // If the instanced object (by ref) wasn't found, hide the component + if(InstancedObject == DefaultReferenceSM) + NewInstancerComponent->SetHiddenInGame(true); + else + NewInstancerComponent->SetHiddenInGame(false); + + FHoudiniOutputObject& NewOutputObject = NewOutputObjects.FindOrAdd(OutputIdentifier); + if (bIsProxyMesh) + { + NewOutputObject.ProxyComponent = NewInstancerComponent; + NewOutputObject.ProxyObject = InstancedObject; + } + else + { + NewOutputObject.OutputComponent = NewInstancerComponent; + NewOutputObject.OutputObject = InstancedObject; + } + + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + NewOutputObject.CachedAttributes.Empty(); + NewOutputObject.CachedTokens.Empty(); + + // Todo: get the proper attribute value per variation... + // Cache the level path, output name and tile attributes on the output object + // So they can be reused for baking + if(InstancedOutputPartData.AllLevelPaths.Num() > 0 && !InstancedOutputPartData.AllLevelPaths[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[0]); + + if(InstancedOutputPartData.OutputNames.Num() > 0 && !InstancedOutputPartData.OutputNames[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[0]); + + if(InstancedOutputPartData.TileValues.Num() > 0 && InstancedOutputPartData.TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[0])); + } + + if (InstancedOutputPartData.AllBakeActorNames.Num() > 0 && !InstancedOutputPartData.AllBakeActorNames[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[0]); + + if (InstancedOutputPartData.AllBakeFolders.Num() > 0 && !InstancedOutputPartData.AllBakeFolders[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, InstancedOutputPartData.AllBakeFolders[0]); + + if (InstancedOutputPartData.AllBakeOutlinerFolders.Num() > 0 && !InstancedOutputPartData.AllBakeOutlinerFolders[0].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[0]); + + if (InstancedOutputPartData.SplitAttributeValues.Num() > 0 + && !InstancedOutputPartData.SplitAttributeName.IsEmpty() + && InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalObjectIndices[InstanceObjectIdx])) + { + FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalObjectIndices[InstanceObjectIdx]]; + + // Cache the split attribute both as attribute and token + NewOutputObject.CachedAttributes.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); + NewOutputObject.CachedTokens.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); + + // If we have a split name that is non-empty, override attributes that can differ by split based + // on the split name + if (!SplitValue.IsEmpty()) + { + const FHoudiniInstancedOutputPerSplitAttributes* PerSplitAttributes = InstancedOutputPartData.PerSplitAttributes.Find(SplitValue); + if (PerSplitAttributes) + { + if (!PerSplitAttributes->LevelPath.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, PerSplitAttributes->LevelPath); + if (!PerSplitAttributes->BakeActorName.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, PerSplitAttributes->BakeActorName); + if (!PerSplitAttributes->BakeOutlinerFolder.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, PerSplitAttributes->BakeOutlinerFolder); + if (!PerSplitAttributes->BakeFolder.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, PerSplitAttributes->BakeFolder); + } + } + } + } + } + + // Remove reused components from the old map to avoid their deletion + for (const auto& CurNewPair : NewOutputObjects) + { + // Get the new Identifier / StaticMesh + const FHoudiniOutputObjectIdentifier& OutputIdentifier = CurNewPair.Key; + + // See if we already had that pair in the old map + FHoudiniOutputObject* FoundOldOutputObject = OldOutputObjects.Find(OutputIdentifier); + if (!FoundOldOutputObject) + continue; + + bool bKeep = false; + + UObject* NewComponent = CurNewPair.Value.OutputComponent; + if (NewComponent) + { + UObject* FoundOldComponent = FoundOldOutputObject->OutputComponent; + if (FoundOldComponent && !FoundOldComponent->IsPendingKill()) + { + bKeep = (FoundOldComponent == NewComponent); + } + } + + UObject* NewProxyComponent = CurNewPair.Value.ProxyComponent; + if (NewProxyComponent) + { + UObject* FoundOldProxyComponent = FoundOldOutputObject->ProxyComponent; + if (FoundOldProxyComponent && !FoundOldProxyComponent->IsPendingKill()) + { + bKeep = (FoundOldProxyComponent == NewProxyComponent); + } + } + + if (bKeep) + { + // Remove the reused component from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + + // The Old map now only contains unused/stale components, delete them + for (auto& OldPair : OldOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; + UObject* OldComponent = OldPair.Value.OutputComponent; + if (OldComponent) + { + bool bDestroy = true; + if (OldComponent->IsA()) + { + // When destroying a component, we have to be sure it's not an HISMC owned by an InstanceFoliageActor + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OldComponent); + if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) + bDestroy = false; + } + + if(bDestroy) + RemoveAndDestroyComponent(OldComponent, OldPair.Value.OutputObject); + + OldPair.Value.OutputComponent = nullptr; + OldPair.Value.OutputObject = nullptr; + } + + UObject* OldProxyComponent = OldPair.Value.ProxyComponent; + if (OldProxyComponent) + { + RemoveAndDestroyComponent(OldProxyComponent, OldPair.Value.ProxyObject); + OldPair.Value.ProxyComponent = nullptr; + OldPair.Value.ProxyObject = nullptr; + } + } + OldOutputObjects.Empty(); + + // Update the output's object map + // Instancer do not create objects, clean the map + InOutput->SetOutputObjects(NewOutputObjects); + + // If we removed, created or updated any foliage instancers, repopulate the list of foliage types in the UI (foliage + // mode) + if (bHaveAnyFoliageInstancers) + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + + return true; +} + + +bool +FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( + FHoudiniInstancedOutput& InInstancedOutput, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + UHoudiniOutput* InParentOutput, + USceneComponent* InParentComponent) +{ + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = InOutputIdentifier.ObjectId; + OutputIdentifier.GeoId = InOutputIdentifier.GeoId; + OutputIdentifier.PartId = InOutputIdentifier.PartId; + OutputIdentifier.SplitIdentifier = InOutputIdentifier.SplitIdentifier; + OutputIdentifier.PartName = InOutputIdentifier.PartName; + + // Get if force using HISM from attribute + bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); + + TArray OriginalInstancedObjects; + OriginalInstancedObjects.Add(InInstancedOutput.OriginalObject.LoadSynchronous()); + + TArray> OriginalInstancedTransforms; + OriginalInstancedTransforms.Add(InInstancedOutput.OriginalTransforms); + + // Update our variations using the changed instancedoutputs objects + TArray> InstancedObjects; + TArray> InstancedTransforms; + TArray VariationOriginalObjectIndices; + TArray VariationIndices; + UpdateInstanceVariationObjects( + OutputIdentifier, + OriginalInstancedObjects, + OriginalInstancedTransforms, + InParentOutput->GetInstancedOutputs(), + InstancedObjects, + InstancedTransforms, + VariationOriginalObjectIndices, + VariationIndices); + + // Find the HGPO for this instanced output + bool FoundHGPO = false; + FHoudiniGeoPartObject HGPO; + for (const auto& curHGPO : InParentOutput->GetHoudiniGeoPartObjects()) + { + if (OutputIdentifier.Matches(curHGPO)) + { + HGPO = curHGPO; + FoundHGPO = true; + break; + } + } + + if (!FoundHGPO) + { + // TODO check failure + ensure(FoundHGPO); + } + + // Extract the generic attributes for that HGPO + TArray AllPropertyAttributes; + GetGenericPropertiesAttributes(OutputIdentifier.GeoId, OutputIdentifier.PartId, AllPropertyAttributes); + + // Check if this is a No-Instancers ( unreal_split_instances ) + bool bSplitMeshInstancer = IsSplitInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); + + bool bIsFoliageInstancer = IsFoliageInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); + + // See if we have instancer material overrides + TArray InstancerMaterials; + if (!GetInstancerMaterials(OutputIdentifier.GeoId, OutputIdentifier.PartId, InstancerMaterials)) + InstancerMaterials.Empty(); + + // Preload objects so we can benefit from async compilation as much as possible + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) + { + InstancedObjects[InstanceObjectIdx].LoadSynchronous(); + } + + // Keep track of the new instancer component in order to be able to clean up the unused/stale ones after. + TMap& OutputObjects = InParentOutput->GetOutputObjects(); + TMap ToDeleteOutputObjects = InParentOutput->GetOutputObjects(); + + // Create the instancer components now + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) + { + UObject* InstancedObject = InstancedObjects[InstanceObjectIdx].LoadSynchronous(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + continue; + + if (!InstancedTransforms.IsValidIndex(InstanceObjectIdx)) + continue; + + const TArray& InstancedObjectTransforms = InstancedTransforms[InstanceObjectIdx]; + if (InstancedObjectTransforms.Num() <= 0) + continue; + + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + // the original object index is used for the instanced outputs split identifier + OutputIdentifier.SplitIdentifier = + InOutputIdentifier.SplitIdentifier + + TEXT("_") + + FString::FromInt(VariationIndices[InstanceObjectIdx]); + + // See if we can find an preexisting component for this obj to try to reuse it + USceneComponent* OldInstancerComponent = nullptr; + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(OutputIdentifier); + if (FoundOutputObject) + { + OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); + } + + // Extract the material for this variation +// FHoudiniInstancedOutput* FoundInstancedOutput = InstancedOutputs.Find(OutputIdentifier); + TArray VariationMaterials; + if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) + VariationMaterials.Empty(); + + USceneComponent* NewInstancerComponent = nullptr; + if (!CreateOrUpdateInstanceComponent( + InstancedObject, + InstancedObjectTransforms, + AllPropertyAttributes, + HGPO, + InParentComponent, + OldInstancerComponent, + NewInstancerComponent, + bSplitMeshInstancer, + bIsFoliageInstancer, + InstancerMaterials, + InstanceObjectIdx, + bForceHISM)) + { + // TODO?? + continue; + } + + if (!NewInstancerComponent) + continue; + + if (OldInstancerComponent != NewInstancerComponent) + { + // Previous component wasn't reused, detach and delete it + RemoveAndDestroyComponent(OldInstancerComponent, nullptr); + + // Replace it with the new component + if (FoundOutputObject) + { + FoundOutputObject->OutputComponent = NewInstancerComponent; + } + else + { + FHoudiniOutputObject& NewOutputObject = OutputObjects.Add(OutputIdentifier); + NewOutputObject.OutputComponent = NewInstancerComponent; + } + } + + // Remove this output object from the todelete map + ToDeleteOutputObjects.Remove(OutputIdentifier); + } + + // Clean up the output objects that are not "reused" by the instanced outs + // The ToDelete map now only contains unused/stale components, delete them + for (auto& ToDeletePair : ToDeleteOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& ToDeleteIdentifier = ToDeletePair.Key; + UObject* OldComponent = ToDeletePair.Value.OutputComponent; + if (OldComponent) + { + RemoveAndDestroyComponent(OldComponent, ToDeletePair.Value.OutputObject); + ToDeletePair.Value.OutputComponent = nullptr; + } + + UObject* OldProxyComponent = ToDeletePair.Value.ProxyComponent; + if (OldProxyComponent) + { + RemoveAndDestroyComponent(OldProxyComponent, ToDeletePair.Value.ProxyObject); + ToDeletePair.Value.ProxyComponent = nullptr; + } + + // Make sure the stale output object is not in the output map anymore + OutputObjects.Remove(ToDeleteIdentifier); + } + ToDeleteOutputObjects.Empty(); + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValues, + TMap& OutPerSplitAttributes) +{ + TArray InstancedObjects; + TArray> InstancedTransforms; + + TArray InstancedHGPOs; + TArray> InstancedHGPOTransforms; + + bool bSuccess = false; + switch (InHGPO.InstancerType) + { + case EHoudiniInstancerType::PackedPrimitive: + { + // Packed primitives instances + bSuccess = GetPackedPrimitiveInstancerHGPOsAndTransforms( + InHGPO, + InstancedHGPOs, + InstancedHGPOTransforms, + OutSplitAttributeName, + OutSplitAttributeValues, + OutPerSplitAttributes); + } + break; + + case EHoudiniInstancerType::AttributeInstancer: + { + // "Modern" attribute instancer - "unreal_instance" + bSuccess = GetAttributeInstancerObjectsAndTransforms( + InHGPO, + InstancedObjects, + InstancedTransforms, + OutSplitAttributeName, + OutSplitAttributeValues, + OutPerSplitAttributes); + } + break; + + case EHoudiniInstancerType::OldSchoolAttributeInstancer: + { + // Old school attribute override instancer - instance attribute w/ a HoudiniPath + bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); + } + break; + + case EHoudiniInstancerType::ObjectInstancer: + { + // Old School object instancer + bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); + } + break; + } + + if (!bSuccess) + return false; + + // Fetch the UOBject that correspond to the instanced parts + // Attribute instancers don't need to do this since they refer UObjects directly + if (InstancedHGPOs.Num() > 0) + { + for (int32 HGPOIdx = 0; HGPOIdx < InstancedHGPOs.Num(); HGPOIdx++) + { + const FHoudiniGeoPartObject& CurrentHGPO = InstancedHGPOs[HGPOIdx]; + + // Get the UObject that was generated for that HGPO + TArray ObjectsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + if (Output->OutputObjects.Num() <= 0) + continue; + + for (const auto& OutObjPair : Output->OutputObjects) + { + if (!OutObjPair.Key.Matches(CurrentHGPO)) + continue; + + const FHoudiniOutputObject& CurrentOutputObject = OutObjPair.Value; + + // In the case of a single-instance we can use the proxy (if it is current) + // FHoudiniOutputTranslator::UpdateOutputs doesn't allow proxies if there is more than one instance in an output + if (InstancedHGPOTransforms[HGPOIdx].Num() <= 1 && CurrentOutputObject.bProxyIsCurrent + && CurrentOutputObject.ProxyObject && !CurrentOutputObject.ProxyObject->IsPendingKill()) + { + ObjectsToInstance.Add(CurrentOutputObject.ProxyObject); + } + else if (CurrentOutputObject.OutputObject && !CurrentOutputObject.OutputObject->IsPendingKill()) + { + ObjectsToInstance.Add(CurrentOutputObject.OutputObject); + } + } + } + + // Add the UObject and the HGPO transforms to the output arrays + for (const auto& MatchingOutputObj : ObjectsToInstance) + { + InstancedObjects.Add(MatchingOutputObj); + InstancedTransforms.Add(InstancedHGPOTransforms[HGPOIdx]); + } + } + } + + // + if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() ) + { + // TODO + // Error / warning + return false; + } + + OutInstancedObjects = InstancedObjects; + OutInstancedTransforms = InstancedTransforms; + + return true; +} + + +void +FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TArray& InOriginalObjects, + const TArray>& InOriginalTransforms, + TMap& InstancedOutputs, + TArray>& OutVariationsInstancedObjects, + TArray>& OutVariationsInstancedTransforms, + TArray& OutVariationOriginalObjectIdx, + TArray& OutVariationIndices) +{ + FHoudiniOutputObjectIdentifier Identifier = InOutputIdentifier; + for (int32 InstObjIdx = 0; InstObjIdx < InOriginalObjects.Num(); InstObjIdx++) + { + UObject* OriginalObj = InOriginalObjects[InstObjIdx]; + if (!OriginalObj || OriginalObj->IsPendingKill()) + continue; + + // Build this output object's split identifier + Identifier.SplitIdentifier = FString::FromInt(InstObjIdx); + + // Do we have an instanced output object for this one? + FHoudiniInstancedOutput * FoundInstancedOutput = nullptr; + for (auto& Iter : InstancedOutputs) + { + FHoudiniOutputObjectIdentifier& FoundIdentifier = Iter.Key; + if (!(FoundIdentifier == Identifier)) + continue; + + // We found an existing instanced output for this identifier + FoundInstancedOutput = &(Iter.Value); + + if (FoundIdentifier.bLoaded) + { + // The output object identifier we found is marked as loaded, + // so uses old node IDs, we must update them, or the next cook + // will fail to locate the output back + FoundIdentifier.ObjectId = Identifier.ObjectId; + FoundIdentifier.GeoId = Identifier.GeoId; + FoundIdentifier.PartId = Identifier.PartId; + } + } + + if (!FoundInstancedOutput) + { + // Create a new one + FHoudiniInstancedOutput CurInstancedOutput; + CurInstancedOutput.OriginalObject = OriginalObj; + CurInstancedOutput.OriginalObjectIndex = InstObjIdx; + CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + + CurInstancedOutput.VariationObjects.Add(OriginalObj); + CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); + CurInstancedOutput.TransformVariationIndices.SetNumZeroed(InOriginalTransforms[InstObjIdx].Num()); + CurInstancedOutput.MarkChanged(false); + CurInstancedOutput.bStale = false; + + // No variations, simply assign the object/transforms + OutVariationsInstancedObjects.Add(OriginalObj); + OutVariationsInstancedTransforms.Add(InOriginalTransforms[InstObjIdx]); + OutVariationOriginalObjectIdx.Add(InstObjIdx); + OutVariationIndices.Add(0); + + InstancedOutputs.Add(Identifier, CurInstancedOutput); + } + else + { + // Process the potential variations + FHoudiniInstancedOutput& CurInstancedOutput = *FoundInstancedOutput; + UObject *ReplacedOriginalObject = nullptr; + if (CurInstancedOutput.OriginalObject != OriginalObj) + { + ReplacedOriginalObject = CurInstancedOutput.OriginalObject.LoadSynchronous(); + CurInstancedOutput.OriginalObject = OriginalObj; + } + + CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + + // Shouldnt be needed... + CurInstancedOutput.OriginalObjectIndex = InstObjIdx; + + // Remove any null or deleted variation objects + TArray ObjsToRemove; + for (int32 VarIdx = CurInstancedOutput.VariationObjects.Num() - 1; VarIdx >= 0; --VarIdx) + { + UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); + if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill() || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) + { + ObjsToRemove.Add(VarIdx); + } + } + if (ObjsToRemove.Num() > 0) + { + for (const int32 &VarIdx : ObjsToRemove) + { + CurInstancedOutput.VariationObjects.RemoveAt(VarIdx); + CurInstancedOutput.VariationTransformOffsets.RemoveAt(VarIdx); + } + // Force a recompute of variation assignments + CurInstancedOutput.TransformVariationIndices.SetNum(0); + } + + // If we don't have variations, simply use the original object + if (CurInstancedOutput.VariationObjects.Num() <= 0) + { + // No variations? add the original one + CurInstancedOutput.VariationObjects.Add(OriginalObj); + CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); + CurInstancedOutput.TransformVariationIndices.SetNum(0); + } + + // If the number of transforms has changed since the previous cook, + // we need to recompute the variation assignments + if (CurInstancedOutput.TransformVariationIndices.Num() != CurInstancedOutput.OriginalTransforms.Num()) + UpdateVariationAssignements(CurInstancedOutput); + + // Assign variations and their transforms + for (int32 VarIdx = 0; VarIdx < CurInstancedOutput.VariationObjects.Num(); VarIdx++) + { + UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); + if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill()) + continue; + + // Get the transforms assigned to that variation + TArray ProcessedTransforms; + ProcessInstanceTransforms(CurInstancedOutput, VarIdx, ProcessedTransforms); + if (ProcessedTransforms.Num() > 0) + { + OutVariationsInstancedObjects.Add(CurrentVariationObject); + OutVariationsInstancedTransforms.Add(ProcessedTransforms); + OutVariationOriginalObjectIdx.Add(InstObjIdx); + OutVariationIndices.Add(VarIdx); + } + } + + CurInstancedOutput.MarkChanged(false); + CurInstancedOutput.bStale = false; + } + } +} + + +void +FHoudiniInstanceTranslator::UpdateVariationAssignements(FHoudiniInstancedOutput& InstancedOutput) +{ + int32 TransformCount = InstancedOutput.OriginalTransforms.Num(); + InstancedOutput.TransformVariationIndices.SetNumZeroed(TransformCount); + + int32 VariationCount = InstancedOutput.VariationObjects.Num(); + if (VariationCount <= 1) + return; + + int nSeed = 1234; + for (int32 Idx = 0; Idx < TransformCount; Idx++) + { + InstancedOutput.TransformVariationIndices[Idx] = fastrand(nSeed) % VariationCount; + } +} + +void +FHoudiniInstanceTranslator::ProcessInstanceTransforms( + FHoudiniInstancedOutput& InstancedOutput, const int32& VariationIdx, TArray& OutProcessedTransforms) +{ + if (!InstancedOutput.VariationObjects.IsValidIndex(VariationIdx)) + return; + + if (!InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx)) + return; + + bool bHasVariations = InstancedOutput.VariationObjects.Num() > 1; + bool bHasTransformOffset = InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx) + ? !InstancedOutput.VariationTransformOffsets[VariationIdx].Equals(FTransform::Identity) + : false; + + if (!bHasVariations && !bHasTransformOffset) + { + // We dont have variations or transform offset, so we can reuse the original transforms as is + OutProcessedTransforms = InstancedOutput.OriginalTransforms; + return; + } + + if (bHasVariations) + { + // We simply need to extract the transforms for this variation + for (int32 TransformIndex = 0; TransformIndex < InstancedOutput.TransformVariationIndices.Num(); TransformIndex++) + { + if (InstancedOutput.TransformVariationIndices[TransformIndex] != VariationIdx) + continue; + + OutProcessedTransforms.Add(InstancedOutput.OriginalTransforms[TransformIndex]); + } + } + else + { + // No variations, we can reuse the original transforms + OutProcessedTransforms = InstancedOutput.OriginalTransforms; + } + + if (bHasTransformOffset) + { + // Get the transform offset for this variation + FVector PositionOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetLocation(); + FQuat RotationOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetRotation(); + FVector ScaleOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetScale3D(); + + FTransform CurrentTransform = FTransform::Identity; + for (int32 TransformIndex = 0; TransformIndex < OutProcessedTransforms.Num(); TransformIndex++) + { + CurrentTransform = OutProcessedTransforms[TransformIndex]; + + // Compute new rotation and scale. + FVector Position = CurrentTransform.GetLocation() + PositionOffset; + FQuat TransformRotation = CurrentTransform.GetRotation() * RotationOffset; + FVector TransformScale3D = CurrentTransform.GetScale3D() * ScaleOffset; + + // Make sure inverse matrix exists - seems to be a bug in Unreal when submitting instances. + // Happens in blueprint as well. + // We want to make sure the scale is not too small, but keep negative values! (Bug 90876) + if (FMath::Abs(TransformScale3D.X) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.X = (TransformScale3D.X > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + if (FMath::Abs(TransformScale3D.Y) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.Y = (TransformScale3D.Y > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + if (FMath::Abs(TransformScale3D.Z) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.Z = (TransformScale3D.Z > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + CurrentTransform.SetLocation(Position); + CurrentTransform.SetRotation(TransformRotation); + CurrentTransform.SetScale3D(TransformScale3D); + + if (CurrentTransform.IsValid()) + OutProcessedTransforms[TransformIndex] = CurrentTransform; + } + } +} + +bool +FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::PackedPrimitive) + return false; + + // Get transforms for each instance + TArray InstancerPartTransforms; + InstancerPartTransforms.SetNumZeroed(InHGPO.PartInfo.InstanceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancerPartTransforms( + FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, + HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, InHGPO.PartInfo.InstanceCount), false); + + // Convert the transform to Unreal's coordinate system + TArray InstancerUnrealTransforms; + InstancerUnrealTransforms.SetNumUninitialized(InstancerPartTransforms.Num()); + for (int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); InstanceIdx++) + { + const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, InstancerUnrealTransforms[InstanceIdx]); + } + + // Get the part ids for parts being instanced + TArray InstancedPartIds; + InstancedPartIds.SetNumZeroed(InHGPO.PartInfo.InstancedPartCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancedPartIds( + FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, + InstancedPartIds.GetData(), 0, InHGPO.PartInfo.InstancedPartCount), false); + + // See if the user has specified an attribute for splitting the instances + // and get the values + FString SplitAttribName = FString(); + TArray AllSplitAttributeValues; + bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( + InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_PRIM, SplitAttribName, AllSplitAttributeValues); + + // Get the level path attribute on the instancer + TArray AllLevelPaths; + const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( + InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_PRIM); + + // Get the bake actor attribute + TArray AllBakeActorNames; + const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( + InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_PRIM); + + // Get the unreal_bake_folder attribute + TArray AllBakeFolders; + const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( + InHGPO.GeoId, HAPI_ATTROWNER_PRIM, AllBakeFolders, InHGPO.PartId); + + // Get the bake outliner folder attribute + TArray AllBakeOutlinerFolders; + const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_PRIM); + + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; + + for (const auto& InstancedPartId : InstancedPartIds) + { + // Create a GeoPartObject corresponding to the instanced part + FHoudiniGeoPartObject InstancedHGPO; + InstancedHGPO.AssetId = InHGPO.AssetId; + InstancedHGPO.AssetName = InHGPO.AssetName; + InstancedHGPO.ObjectId = InHGPO.ObjectId; + InstancedHGPO.ObjectName = InHGPO.ObjectName; + InstancedHGPO.GeoId = InHGPO.GeoId; + InstancedHGPO.PartId = InstancedPartId; + InstancedHGPO.PartName = InHGPO.PartName; + InstancedHGPO.TransformMatrix = InHGPO.TransformMatrix; + + // TODO: Copy more cached data? + + OutInstancedHGPO.Add(InstancedHGPO); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + } + + // If we don't need to split the instances, we're done + if (!bHasSplitAttribute) + return true; + + // TODO: Optimize this! + // Split the instances using the split attribute's values + + // Move the output arrays to temp arrays + TArray UnsplitInstancedHGPOs = OutInstancedHGPO; + TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + + // Empty the output arrays + OutInstancedHGPO.Empty(); + OutInstancedTransforms.Empty(); + OutSplitAttributeValue.Empty(); + for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedHGPOs.Num(); ObjIdx++) + { + // Map of split values to transform arrays + TMap> SplitTransformMap; + + TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + + int32 NumInstances = CurrentTransforms.Num(); + if (AllSplitAttributeValues.Num() != NumInstances) + continue; + + // Split the transforms using the split values + for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) + { + const FString& SplitAttrValue = AllSplitAttributeValues[InstIdx]; + SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + + // Record attributes for any split value we have not yet seen + if (bHasAnyPerSplitAttributes) + { + FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); + if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) + { + PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; + } + if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; + } + if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; + } + if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; + } + } + } + + // Add the objects, transform, split values to the final arrays + for (auto& Iterator : SplitTransformMap) + { + OutSplitAttributeValue.Add(Iterator.Key); + OutInstancedHGPO.Add(UnsplitInstancedHGPOs[ObjIdx]); + OutInstancedTransforms.Add(Iterator.Value); + } + } + + OutSplitAttributeName = SplitAttribName; + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::AttributeInstancer) + return false; + + // Look for the unreal instance attribute + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + // instance attribute on points + bool is_override_attr = false; + HAPI_Result Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT, &AttribInfo); + + // unreal_instance attribute on points + if (Result != HAPI_RESULT_SUCCESS || AttribInfo.exists == false) + { + is_override_attr = true; + Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT, &AttribInfo); + } + + // unreal_instance attribute on detail + if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) + { + is_override_attr = true; + Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL, &AttribInfo); + } + + // Attribute does not exist. + if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the settings indicating if we want to use a default object when the referenced mesh is invalid + bool bDefaultObjectEnabled = true; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bDefaultObjectEnabled = HoudiniRuntimeSettings->bShowDefaultMesh; + } + + // See if the user has specified an attribute for splitting the instances, and get the values + FString SplitAttribName = FString(); + TArray AllSplitAttributeValues; + bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( + InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_POINT, SplitAttribName, AllSplitAttributeValues); + + // Get the level path attribute on the instancer + TArray AllLevelPaths; + const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( + InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_POINT); + + // Get the bake actor attribute + TArray AllBakeActorNames; + const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( + InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_POINT); + + // Get the unreal_bake_folder attribute + TArray AllBakeFolders; + const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( + InHGPO.GeoId, HAPI_ATTROWNER_POINT, AllBakeFolders, InHGPO.PartId); + + // Get the bake outliner folder attribute + TArray AllBakeOutlinerFolders; + const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_POINT); + + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; + + // Array used to store the split values per objects + // Will only be used if we have a split attribute + TArray> SplitAttributeValuesPerObject; + + if (AttribInfo.owner == HAPI_ATTROWNER_DETAIL) + { + // If the attribute is on the detail, then its value is applied to all points + TArray DetailInstanceValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InHGPO.GeoId, + InHGPO.PartId, + is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, + AttribInfo, + DetailInstanceValues)) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + if (DetailInstanceValues.Num() <= 0) + { + // No values specified. + return false; + } + + // Attempt to load specified asset. + const FString & AssetName = DetailInstanceValues[0]; + UObject * AttributeObject = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetName, nullptr, LOAD_None, nullptr); + + if (!AttributeObject) + { + // See if the ref is a class that we can instantiate + UClass * FoundClass = FindObject(ANY_PACKAGE, *AssetName); + if (FoundClass != nullptr) + { + // TODO: ensure we'll be able to create an actor from this class! + AttributeObject = FoundClass; + } + } + + if (!AttributeObject && bDefaultObjectEnabled) + { + HOUDINI_LOG_WARNING(TEXT("Failed to load instanced object '%s', using default instance mesh (hidden in game)."), *(AssetName)); + + // Couldn't load the referenced object, use the default reference mesh + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (!DefaultReferenceSM || DefaultReferenceSM->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to load the default instance mesh.")); + return false; + } + AttributeObject = DefaultReferenceSM; + } + + // Attach the objectPtr/transforms/bHiddenInGame if the attributeObject is created successfully + // (with either the actual referenced object or the default placeholder object) + if (AttributeObject) + { + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + + if(bHasSplitAttribute) + SplitAttributeValuesPerObject.Add(AllSplitAttributeValues); + } + } + else + { + // Attribute is on points, so we may have different values for each of them + TArray PointInstanceValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InHGPO.GeoId, + InHGPO.PartId, + is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, + AttribInfo, + PointInstanceValues)) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + // The attribute is on points, so the number of points must match number of transforms. + if (!ensure(PointInstanceValues.Num() == InstancerUnrealTransforms.Num())) + { + // This should not happen, we have mismatch between number of instance values and transforms. + return false; + } + + // If instance attribute exists on points, we need to get all the unique values. + // This will give us all the unique object we want to instance + TMap ObjectsToInstance; + for (const auto& Iter : PointInstanceValues) + { + if (!ObjectsToInstance.Contains(Iter)) + { + // To avoid trying to load an object that fails multiple times, + // still add it to the array if null so we can still skip further attempts + UObject * AttributeObject = StaticLoadObject( + UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); + + if (!AttributeObject) + { + // See if the ref is a class that we can instantiate + UClass * FoundClass = FindObject(ANY_PACKAGE, *Iter); + if (FoundClass != nullptr) + { + // TODO: ensure we'll be able to create an actor from this class! + AttributeObject = FoundClass; + } + } + + ObjectsToInstance.Add(Iter, AttributeObject); + } + } + + // Iterates through all the unique objects and get their corresponding transforms + bool Success = false; + for (auto Iter : ObjectsToInstance) + { + bool bHiddenInGame = false; + // Check that we managed to load this object + UObject * AttributeObject = Iter.Value; + if (!AttributeObject && bDefaultObjectEnabled) + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load instanced object '%s', use default mesh (hidden in game)."), *(Iter.Key)); + + // If failed to load this object, add default reference mesh + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + { + AttributeObject = DefaultReferenceSM; + bHiddenInGame = true; + } + else// Failed to load default reference mesh object + { + HOUDINI_LOG_WARNING(TEXT("Failed to load default mesh.")); + continue; + } + } + + if (!AttributeObject) + continue; + + if (!bHasSplitAttribute) + { + // No Split attribute: + // Extract the transform values that correspond to this object, and add them to the output arrays + const FString & InstancePath = Iter.Key; + TArray ObjectTransforms; + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) + { + if (InstancePath.Equals(PointInstanceValues[Idx])) + ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + } + + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(ObjectTransforms); + Success = true; + } + else + { + // We have a split attribute: + // Extract the transform values and split attribute values for this object, + // add them to the output arrays, and we will process the splits after + const FString & InstancePath = Iter.Key; + TArray ObjectTransforms; + TArray ObjectSplitValues; + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) + { + if (InstancePath.Equals(PointInstanceValues[Idx])) + { + ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + ObjectSplitValues.Add(AllSplitAttributeValues[Idx]); + } + } + + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(ObjectTransforms); + SplitAttributeValuesPerObject.Add(ObjectSplitValues); + Success = true; + } + } + + if (!Success) + return false; + } + + // If we don't need to split the instances, we're done + if (!bHasSplitAttribute) + return true; + + // Split the instances one more time, this time using the split values + + // Move the output arrays to temp arrays + TArray UnsplitInstancedObjects = OutInstancedObjects; + TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + + // Empty the output arrays + OutInstancedObjects.Empty(); + OutInstancedTransforms.Empty(); + + // TODO: Output the split values as well! + OutSplitAttributeValue.Empty(); + for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedObjects.Num(); ObjIdx++) + { + UObject* InstancedObject = UnsplitInstancedObjects[ObjIdx]; + + // Map of split values to transform arrays + TMap> SplitTransformMap; + + TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + TArray& CurrentSplits = SplitAttributeValuesPerObject[ObjIdx]; + + int32 NumInstances = CurrentTransforms.Num(); + if (CurrentSplits.Num() != NumInstances) + continue; + + // Split the transforms using the split values + for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) + { + const FString& SplitAttrValue = CurrentSplits[InstIdx]; + SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + + // Record attributes for any split value we have not yet seen + if (bHasAnyPerSplitAttributes) + { + FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); + if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) + { + PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; + } + if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; + } + if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; + } + if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; + } + } + } + + // Add the objects, transform, split values to the final arrays + for (auto& Iterator : SplitTransformMap) + { + OutSplitAttributeValue.Add(Iterator.Key); + OutInstancedObjects.Add(InstancedObject); + OutInstancedTransforms.Add(Iterator.Value); + } + } + + OutSplitAttributeName = SplitAttribName; + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::OldSchoolAttributeInstancer) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the objects IDs to instanciate + int32 NumPoints = InHGPO.PartInfo.PointCount; + TArray InstancedObjectIds; + InstancedObjectIds.SetNumUninitialized(NumPoints); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedObjectIds( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InstancedObjectIds.GetData(), 0, NumPoints), false); + + // Find the set of instanced object ids and locate the corresponding parts + TSet UniqueInstancedObjectIds(InstancedObjectIds); + + // Locate all the HoudiniGeoPartObject that corresponds to the instanced object IDs + for (int32 InstancedObjectId : UniqueInstancedObjectIds) + { + // Get the parts that correspond to that object Id + TArray PartsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) + { + if (OutHGPO.Type != EHoudiniPartType::Mesh) + continue; + + if (OutHGPO.bIsInstanced) + continue; + + if (InstancedObjectId != OutHGPO.ObjectId) + continue; + + PartsToInstance.Add(OutHGPO); + } + } + + // Extract only the transforms that correspond to that specific object ID + TArray InstanceTransforms; + for (int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix) + { + if ((InstancedObjectIds[Ix] == InstancedObjectId) && (InstancerUnrealTransforms.IsValidIndex(Ix))) + { + InstanceTransforms.Add(InstancerUnrealTransforms[Ix]); + } + } + + // Add the instanced parts and their transforms to the output arrays + for (const auto& PartToInstance : PartsToInstance) + { + OutInstancedHGPO.Add(PartToInstance); + OutInstancedTransforms.Add(InstanceTransforms); + } + } + + if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0) + return true; + + return false; +} + + +bool +FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::ObjectInstancer) + return false; + + if (InHGPO.ObjectInfo.ObjectToInstanceID < 0) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the parts that correspond to that Object Id + TArray PartsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) + { + if (OutHGPO.Type != EHoudiniPartType::Mesh) + continue; + + /* + // But the instanced geo is actually not marked as instanced + if (!OutHGPO.bIsInstanced) + continue; + */ + + if (InHGPO.ObjectInfo.ObjectToInstanceID != OutHGPO.ObjectId) + continue; + + PartsToInstance.Add(OutHGPO); + } + } + + // Add found HGPO and transforms to the output arrays + for (auto& InstanceHGPO : PartsToInstance) + { + InstanceHGPO.TransformMatrix = InHGPO.TransformMatrix; + + // TODO: + //InstanceHGPO.UpdateCustomName(); + + OutInstancedHGPO.Add(InstanceHGPO); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent* OldComponent, + USceneComponent*& NewComponent, + const bool& InIsSplitMeshInstancer, + const bool& InIsFoliageInstancer, + const TArray& InstancerMaterials, + const int32& InstancerObjectIdx, + const bool& bForceHISM) +{ + enum InstancerComponentType + { + Invalid = -1, + InstancedStaticMeshComponent = 0, + HierarchicalInstancedStaticMeshComponent = 1, + MeshSplitInstancerComponent = 2, + HoudiniInstancedActorComponent = 3, + StaticMeshComponent = 4, + HoudiniStaticMeshComponent = 5, + Foliage = 6 + }; + + // See if we can reuse the old component + InstancerComponentType OldType = InstancerComponentType::Invalid; + if (OldComponent/*&& !OldComponent->IsPendingKill()*/) // The old component could be marked as pending kill + { + if(OldComponent->IsA()) + OldType = Foliage; + else if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) + OldType = Foliage; + else if (OldComponent->IsA()) + OldType = HierarchicalInstancedStaticMeshComponent; + else if (OldComponent->IsA()) + OldType = InstancedStaticMeshComponent; + else if (OldComponent->IsA()) + OldType = MeshSplitInstancerComponent; + else if (OldComponent->IsA()) + OldType = HoudiniInstancedActorComponent; + else if (OldComponent->IsA()) + OldType = StaticMeshComponent; + else if (OldComponent->IsA()) + OldType = HoudiniStaticMeshComponent; + } + + // See what type of component we want to create + InstancerComponentType NewType = InstancerComponentType::Invalid; + + UStaticMesh * StaticMesh = Cast(InstancedObject); + UFoliageType * FoliageType = Cast(InstancedObject); + + UHoudiniStaticMesh * HSM = nullptr; + if (!StaticMesh && !FoliageType) + HSM = Cast(InstancedObject); + + if (StaticMesh) + { + if (InstancedObjectTransforms.Num() == 1) + NewType = StaticMeshComponent; + else if (InIsFoliageInstancer) + NewType = Foliage; + else if (InIsSplitMeshInstancer) + NewType = MeshSplitInstancerComponent; + else if(StaticMesh->GetNumLODs() > 1 || bForceHISM) + NewType = HierarchicalInstancedStaticMeshComponent; + else + NewType = InstancedStaticMeshComponent; + } + else if (HSM) + { + if (InstancedObjectTransforms.Num() == 1) + NewType = HoudiniStaticMeshComponent; + else + { + HOUDINI_LOG_ERROR(TEXT("More than one instance transform encountered for UHoudiniStaticMesh: %s"), *(HSM->GetPathName())); + NewType = Invalid; + return false; + } + } + else if (FoliageType) + { + NewType = Foliage; + } + else + { + NewType = HoudiniInstancedActorComponent; + } + + if (OldType == NewType) + NewComponent = OldComponent; + + UMaterialInterface* InstancerMaterial = nullptr; + if (InstancerMaterials.Num() > 0) + { + if (InstancerMaterials.IsValidIndex(InstancerObjectIdx)) + InstancerMaterial = InstancerMaterials[InstancerObjectIdx]; + else + InstancerMaterial = InstancerMaterials[0]; + } + + bool bSuccess = false; + switch (NewType) + { + case InstancedStaticMeshComponent: + case HierarchicalInstancedStaticMeshComponent: + { + // Create an Instanced Static Mesh Component + bSuccess = CreateOrUpdateInstancedStaticMeshComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM); + } + break; + + case MeshSplitInstancerComponent: + { + bSuccess = CreateOrUpdateMeshSplitInstancerComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterials); + } + break; + + case HoudiniInstancedActorComponent: + { + bSuccess = CreateOrUpdateInstancedActorComponent( + InstancedObject, InstancedObjectTransforms, AllPropertyAttributes, ParentComponent, NewComponent); + } + break; + + case StaticMeshComponent: + { + // Create a Static Mesh Component + bSuccess = CreateOrUpdateStaticMeshComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + break; + + case HoudiniStaticMeshComponent: + { + // Create a Houdini Static Mesh Component + bSuccess = CreateOrUpdateHoudiniStaticMeshComponent( + HSM, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + break; + + case Foliage: + { + bSuccess = CreateOrUpdateFoliageInstances( + StaticMesh, FoliageType, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + } + + if (!NewComponent) + return false; + + NewComponent->SetMobility(ParentComponent->Mobility); + NewComponent->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepRelativeTransform); + + // For single instance, that generates a SMC, the transform is already set on the component + // TODO: Should cumulate transform in that case? + if(NewType != StaticMeshComponent && NewType != HoudiniStaticMeshComponent) + NewComponent->SetRelativeTransform(InstancerGeoPartObject.TransformMatrix); + + // Only register if we have a valid component + if (NewComponent->GetOwner() && NewComponent->GetWorld()) + NewComponent->RegisterComponent(); + + // If the old component couldn't be reused, dettach/ destroy it + if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) + { + RemoveAndDestroyComponent(OldComponent, nullptr); + } + + return bSuccess; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial, /*=nullptr*/ + const bool & bForceHISM) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast(CreatedInstancedComponent); + if (!InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill()) + { + if (InstancedStaticMesh->GetNumLODs() > 1 || bForceHISM) + { + // If the mesh has LODs, use Hierarchical ISMC + InstancedStaticMeshComponent = NewObject( + ComponentOuter, UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + else + { + // If the mesh doesnt have LOD, we can use a regular ISMC + InstancedStaticMeshComponent = NewObject( + ComponentOuter, UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + + // Change the creation method so the component is listed in the details panels + InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!InstancedStaticMeshComponent) + return false; + + InstancedStaticMeshComponent->SetStaticMesh(InstancedStaticMesh); + InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; + + InstancedStaticMeshComponent->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances themselves + InstancedStaticMeshComponent->ClearInstances(); + InstancedStaticMeshComponent->AddInstances(InstancedObjectTransforms, false); + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, 0); + + // Assign the new ISMC / HISMC to the output component if we created a new one + if(bCreatedNewComponent) + CreatedInstancedComponent = InstancedStaticMeshComponent; + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent) +{ + if (!InstancedObject) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniInstancedActorComponent* InstancedActorComponent = Cast(CreatedInstancedComponent); + if (!InstancedActorComponent || InstancedActorComponent->IsPendingKill()) + { + // If the mesh doesnt have LOD, we can use a regular ISMC + InstancedActorComponent = NewObject( + ComponentOuter, UHoudiniInstancedActorComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + InstancedActorComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!InstancedActorComponent) + return false; + + // See if the instanced object has changed + bool bInstancedObjectHasChanged = (InstancedObject != InstancedActorComponent->GetInstancedObject()); + if (bInstancedObjectHasChanged) + { + // All actors will need to be respawned, invalidate all of them + InstancedActorComponent->ClearAllInstances(); + + // Update the HIAC's instanced asset + InstancedActorComponent->SetInstancedObject(InstancedObject); + } + + // Get the level where we want to spawn the actors + ULevel* SpawnLevel = ParentComponent->GetOwner() ? ParentComponent->GetOwner()->GetLevel() : nullptr; + if (!SpawnLevel) + return false; + + // Set the number of needed instances + InstancedActorComponent->SetNumberOfInstances(InstancedObjectTransforms.Num()); + + for (int32 Idx = 0; Idx < InstancedObjectTransforms.Num(); Idx++) + { + // if we already have an actor, we can reuse it + const FTransform& CurTransform = InstancedObjectTransforms[Idx]; + + // Get the current instance + // If null, we need to create a new one, else we can reuse the actor + AActor* CurInstance = InstancedActorComponent->GetInstancedActorAt(Idx); + if (!CurInstance || CurInstance->IsPendingKill()) + { + CurInstance = SpawnInstanceActor(CurTransform, SpawnLevel, InstancedActorComponent); + InstancedActorComponent->SetInstanceAt(Idx, CurTransform, CurInstance); + } + else + { + // We can simply update the actor's transform + InstancedActorComponent->SetInstanceTransformAt(Idx, CurTransform); + } + + // Update the generic properties for that instance if any + // TODO: Handle instance variations w/ Idx + UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, Idx); + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + { + CreatedInstancedComponent = InstancedActorComponent; + } + + return true; +} + +// Create or update a MSIC +bool +FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + const TArray& InInstancerMaterials) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniMeshSplitInstancerComponent* MeshSplitComponent = Cast(CreatedInstancedComponent); + if (!MeshSplitComponent || MeshSplitComponent->IsPendingKill()) + { + // If the mesh doesn't have LOD, we can use a regular ISMC + MeshSplitComponent = NewObject( + ComponentOuter, UHoudiniMeshSplitInstancerComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + MeshSplitComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!MeshSplitComponent) + return false; + + MeshSplitComponent->SetStaticMesh(InstancedStaticMesh); + MeshSplitComponent->SetOverrideMaterials(InInstancerMaterials); + + // Now add the instances + MeshSplitComponent->SetInstanceTransforms(InstancedObjectTransforms); + + // Check for instance colors + TArray InstanceColorOverrides; + bool ColorOverrideAttributeFound = false; + + // Look for the unreal_instance_color attribute on points + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_POINT, &AttributeInfo)) + { + ColorOverrideAttributeFound = AttributeInfo.exists; + } + + // Look for the unreal_instance_color attribute on prims? (why? original code) + if (!ColorOverrideAttributeFound) + { + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_PRIM, &AttributeInfo)) + { + ColorOverrideAttributeFound = AttributeInfo.exists; + } + } + + if (ColorOverrideAttributeFound) + { + if (AttributeInfo.tupleSize == 4) + { + // Allocate sufficient buffer for data. + InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)InstanceColorOverrides.GetData(), 0, AttributeInfo.count)) + { + InstanceColorOverrides.Empty(); + } + } + else if (AttributeInfo.tupleSize == 3) + { + // Allocate sufficient buffer for data. + TArray FloatValues; + FloatValues.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)FloatValues.GetData(), 0, AttributeInfo.count)) + { + + // Allocate sufficient buffer for data. + InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); + + // Convert float to FLinearColors + for (int32 ColorIdx = 0; ColorIdx < InstanceColorOverrides.Num(); ColorIdx++) + { + InstanceColorOverrides[ColorIdx].R = FloatValues[ColorIdx * AttributeInfo.tupleSize + 0]; + InstanceColorOverrides[ColorIdx].G = FloatValues[ColorIdx * AttributeInfo.tupleSize + 1]; + InstanceColorOverrides[ColorIdx].B = FloatValues[ColorIdx * AttributeInfo.tupleSize + 2]; + InstanceColorOverrides[ColorIdx].A = 1.0; + } + FloatValues.Empty(); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_COLOR " must be a float[4] or float[3] prim/point attribute")); + } + } + + // if we have vertex color overrides, apply them now +#if WITH_EDITOR + if (InstanceColorOverrides.Num() > 0) + { + // Convert the color attribute to FColor + TArray InstanceColors; + InstanceColors.SetNumUninitialized(InstanceColorOverrides.Num()); + for (int32 ix = 0; ix < InstanceColors.Num(); ++ix) + { + InstanceColors[ix] = InstanceColorOverrides[ix].GetClamped().ToFColor(false); + } + + // Apply them to the instances + TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); + for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) + { + UStaticMeshComponent* CurSMC = Instances[InstIndex]; + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + if (!InstanceColors.IsValidIndex(InstIndex)) + continue; + + MeshPaintHelpers::FillStaticMeshVertexColors(CurSMC, -1, InstanceColors[InstIndex], FColor::White); + + //CurSMC->UnregisterComponent(); + //CurSMC->ReregisterComponent(); + + { + // We're only changing instanced vertices on this specific mesh component, so we + // only need to detach our mesh component + FComponentReregisterContext ComponentReregisterContext(CurSMC); + for (auto& CurLODData : CurSMC->LODData) + { + BeginInitResource(CurLODData.OverrideVertexColors); + } + } + + //FIXME: How to get rid of the warning about fixup vertex colors on load? + //SMC->FixupOverrideColorsIfNecessary(); + } + } +#endif + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + // TODO: Optimize + // Loop on attributes first, then components, + // if failing to find the attrib on a component, skip the rest + if (AllPropertyAttributes.Num() > 0) + { + TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); + for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) + { + UStaticMeshComponent* CurSMC = Instances[InstIndex]; + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); + } + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = MeshSplitComponent; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UStaticMeshComponent* SMC = Cast(CreatedInstancedComponent); + if (!SMC || SMC->IsPendingKill()) + { + // Create a new StaticMeshComponent + SMC = NewObject( + ComponentOuter, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + SMC->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!SMC) + return false; + + SMC->SetStaticMesh(InstancedStaticMesh); + SMC->GetBodyInstance()->bAutoWeld = false; + + SMC->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + SMC->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances Transform + if (InstancedObjectTransforms.Num() > 0) + { + SMC->SetRelativeTransform(InstancedObjectTransforms[0]); + } + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, 0); + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = SMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( + UHoudiniStaticMesh* InstancedProxyStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + if (!InstancedProxyStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniStaticMeshComponent* HSMC = Cast(CreatedInstancedComponent); + if (!HSMC || HSMC->IsPendingKill()) + { + // Create a new StaticMeshComponent + HSMC = NewObject( + ComponentOuter, UHoudiniStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + HSMC->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!HSMC) + return false; + + HSMC->SetMesh(InstancedProxyStaticMesh); + + HSMC->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedProxyStaticMesh->GetNumStaticMaterials(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + HSMC->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances Transform + HSMC->SetRelativeTransform(InstancedObjectTransforms[0]); + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, 0); + + // Assign the new HSMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = HSMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + + +bool +FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( + UStaticMesh* InstancedStaticMesh, + UFoliageType* InFoliageType, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& NewInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + // We need either a valid SM or a valid Foliage Type + if ((!InstancedStaticMesh || InstancedStaticMesh->IsPendingKill()) + && (!InFoliageType || InFoliageType->IsPendingKill())) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + AActor* OwnerActor = ParentComponent->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return false; + + // See if we already have a FoliageType for that static mesh + bool bCreatedNew = false; + UFoliageType *FoliageType = InFoliageType; + if (!FoliageType || FoliageType->IsPendingKill()) + { + // Foliage Type wasnt specified, only the mesh, try to find an existing foliage for that SM + FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InstancedStaticMesh); + } + else + { + // Foliage Type was specified, see if we can get its static mesh + UFoliageType_InstancedStaticMesh* FoliageISM = Cast(InFoliageType); + if (FoliageISM) + { + InstancedStaticMesh = FoliageISM->GetStaticMesh(); + } + + // See a component already exist on the actor + // If we cant find Foliage info for that foliage type, a new one will be created. + // when we call FindOrAddMesh + bCreatedNew = InstancedFoliageActor->FindInfo(FoliageType) == nullptr; + } + + if (!FoliageType || FoliageType->IsPendingKill()) + { + // We need to create a new FoliageType for this Static Mesh + // TODO: Add foliage default settings + InstancedFoliageActor->AddMesh(InstancedStaticMesh, &FoliageType); + bCreatedNew = true; + } + + if (!bCreatedNew && NewInstancedComponent) + { + // TODO: Shouldnt be needed anymore + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); + } + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if (!FoliageInfo) + return false; + + FTransform HoudiniAssetTransform = ParentComponent->GetComponentTransform(); + FFoliageInstance FoliageInstance; + int32 CurrentInstanceCount = 0; + + FoliageInfo->ReserveAdditionalInstances(InstancedFoliageActor, FoliageType, InstancedObjectTransforms.Num()); + for (auto CurrentTransform : InstancedObjectTransforms) + { + // Use our parent component for the base component of the instances, + // this will allow us to clean the instances by component + FoliageInstance.BaseComponent = ParentComponent; + + // TODO: FIX ME! + // Somehow, the first time when we create the Foliage type, instances need to be added with relative transform + // On subsequent cooks, they are actually expecting world transform + if (bCreatedNew) + { + FoliageInstance.Location = CurrentTransform.GetLocation(); + FoliageInstance.Rotation = CurrentTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D(); + } + else + { + FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); + FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); + } + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + CurrentInstanceCount++; + } + + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); + if (IsValid(FoliageHISMC)) + { + // TODO: This was due to a bug in UE4.22-20, check if still needed! + FoliageHISMC->BuildTreeIfOutdated(true, true); + + if (InstancerMaterial) + { + FoliageHISMC->OverrideMaterials.Empty(); + int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->StaticMaterials.Num() : 1; + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + FoliageHISMC->SetMaterial(Idx, InstancerMaterial); + } + } + + // Try to apply generic properties attributes + // either on the instancer, mesh or foliage type + // TODO: Use proper atIndex!! + UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, 0); + + if (IsValid(FoliageHISMC)) + NewInstancedComponent = FoliageHISMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::HapiGetInstanceTransforms( + const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancerUnrealTransforms) +{ + // Get the instance transforms + int32 PointCount = InHGPO.PartInfo.PointCount; + if (PointCount <= 0) + return false; + + TArray InstanceTransforms; + InstanceTransforms.SetNum(PointCount); + for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++) + FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx])); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetInstanceTransformsOnPart( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, HAPI_SRT, + &InstanceTransforms[0], 0, PointCount)) + { + InstanceTransforms.SetNum(0); + + // TODO: Warning? error? + return false; + } + + // Convert the transform to Unreal's coordinate system + OutInstancerUnrealTransforms.SetNumZeroed(InstanceTransforms.Num()); + for (int32 InstanceIdx = 0; InstanceIdx < InstanceTransforms.Num(); InstanceIdx++) + { + const auto& InstanceTransform = InstanceTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, OutInstancerUnrealTransforms[InstanceIdx]); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::GetGenericPropertiesAttributes( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutPropertyAttributes) +{ + // List all the generic property detail attributes ... + int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + + // .. then get all the values for the primitive property attributes + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, -1); + + // .. then finally, all values for point uproperty attributes + // TODO: !! get the correct Index here? + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, -1); + + return FoundCount > 0; +} + +bool +FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes, const int32& AtIndex) +{ + if (!IsValid(InObject)) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + if (CurrentPropAttribute.AttributeName.Equals(TEXT("NumCustomDataFloats"), ESearchCase::IgnoreCase)) + { + // Skip, as setting NumCustomDataFloats this way causes Unreal to crash! + HOUDINI_LOG_WARNING( + TEXT("Skipping UProperty %s on %s, custom data floats should be modified via the unreal_num_custom_floats and unreal_per_instance_custom_dataX attributes"), + *CurrentPropAttribute.AttributeName, *InObject->GetName()); + continue; + } + + // Update the current property for the given instance index + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute, AtIndex)) + continue; + + // Success! + NumSuccess++; + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, InObject->GetClass() ? *InObject->GetClass()->GetName() : TEXT("Object"), *InObject->GetName()); + } + + return (NumSuccess > 0); +} + +bool +FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent, UObject* InFoliageObject) +{ + if (!InComponent || InComponent->IsPendingKill()) + return false; + + UFoliageInstancedStaticMeshComponent* FISMC = Cast(InComponent); + if (FISMC && !FISMC->IsPendingKill()) + { + // Make sure foliage our foliage instances have been removed + USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); + if (ParentComponent && !ParentComponent->IsPendingKill()) + CleanupFoliageInstances(FISMC, InFoliageObject, ParentComponent); + + // do not delete FISMC that still have instances left + // as we have cleaned up our instances before, these have been hand-placed + if (FISMC->GetInstanceCount() > 0) + return false; + } + + USceneComponent* SceneComponent = Cast(InComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + return true; + } + + return false; +} + + +bool +FHoudiniInstanceTranslator::GetMaterialOverridesFromAttributes( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutMaterialAttributes) +{ + HAPI_AttributeInfo MaterialAttributeInfo; + FHoudiniApi::AttributeInfo_Init(&MaterialAttributeInfo); + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL, MaterialAttributeInfo, OutMaterialAttributes); + + /* + // TODO: Support material instances on instancers... + // see FHoudiniMaterialTranslator::CreateMaterialInstances() + // If material attribute and fallbacks were not found, check the material instance attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, MaterialAttributeInfo, OutMaterialAttributes); + } + */ + + if (!MaterialAttributeInfo.exists + /*&& MaterialAttributeInfo.owner != HAPI_ATTROWNER_PRIM + && MaterialAttributeInfo.owner != HAPI_ATTROWNER_DETAIL*/) + { + //HOUDINI_LOG_WARNING(TEXT("Instancer: the unreal_material attribute must be a primitive or detail attribute, ignoring the attribute.")); + OutMaterialAttributes.Empty(); + return false; + } + + return true; +} + +bool +FHoudiniInstanceTranslator::GetInstancerMaterials( + const TArray& MaterialAttributes, TArray& OutInstancerMaterials) +{ + // Use a map to avoid attempting to load the object for each instance + TMap MaterialMap; + + bool bHasValidMaterial = false; + for (auto& CurrentMatString : MaterialAttributes) + { + UMaterialInterface* CurrentMaterialInterface = nullptr; + UMaterialInterface** FoundMaterial = MaterialMap.Find(CurrentMatString); + if (!FoundMaterial) + { + // See if we can find a material interface that matches the attribute + CurrentMaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentMatString, nullptr, LOAD_NoWarn, nullptr)); + + // Check validity + if (!CurrentMaterialInterface || CurrentMaterialInterface->IsPendingKill()) + CurrentMaterialInterface = nullptr; + else + bHasValidMaterial = true; + + // Add what we found to the material map to avoid unnecessary loads + MaterialMap.Add(CurrentMatString, CurrentMaterialInterface); + } + else + { + // Reuse what we previously found + CurrentMaterialInterface = *FoundMaterial; + } + + OutInstancerMaterials.Add(CurrentMaterialInterface); + } + + // IF we couldn't find at least one valid material interface, empty the array + if (!bHasValidMaterial) + OutInstancerMaterials.Empty(); + + return true; +} + +bool +FHoudiniInstanceTranslator::GetInstancerMaterials( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutInstancerMaterials) +{ + TArray MaterialAttributes; + if (!GetMaterialOverridesFromAttributes(InGeoNodeId, InPartId, MaterialAttributes)) + MaterialAttributes.Empty(); + + return GetInstancerMaterials(MaterialAttributes, OutInstancerMaterials); +} + +bool +FHoudiniInstanceTranslator::GetVariationMaterials( + FHoudiniInstancedOutput* InInstancedOutput , const int32& InVariationIndex, + const TArray& InInstancerMaterials, TArray& OutVariationMaterials) +{ + if (!InInstancedOutput || InInstancerMaterials.Num() <= 0) + return false; + + // TODO: FIXME This also need to be improved and wont work 100%!! + + // No variations, reuse the full array + if (InInstancedOutput->VariationObjects.Num() == 1) + { + if (InInstancerMaterials.IsValidIndex(InInstancedOutput->OriginalObjectIndex)) + OutVariationMaterials.Add(InInstancerMaterials[InInstancedOutput->OriginalObjectIndex]); + else + OutVariationMaterials.Add(InInstancerMaterials[0]); + return true; + } + + // If we have variations, see if we can use the instancer mat array + // TODO: FIX ME! this wont work if we have split the instancer and added variations at the same time! + if (InInstancedOutput->TransformVariationIndices.Num() == InInstancerMaterials.Num()) + { + for (int32 Idx = 0; Idx < InInstancedOutput->TransformVariationIndices.Num(); Idx++) + { + int32 VariationAssignment = InInstancedOutput->TransformVariationIndices[Idx]; + if (VariationAssignment != InVariationIndex) + continue; + + OutVariationMaterials.Add(InInstancerMaterials[Idx]); + } + } + else + { + if (InInstancerMaterials.IsValidIndex(InInstancedOutput->OriginalObjectIndex)) + OutVariationMaterials.Add(InInstancerMaterials[InInstancedOutput->OriginalObjectIndex]); + else + OutVariationMaterials.Add(InInstancerMaterials[0]); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::IsSplitInstancer(const int32& InGeoId, const int32& InPartId) +{ + bool bSplitMeshInstancer = false; + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; + bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); + + if (!bSplitMeshInstancer) + { + // Try on primitive + Owner = HAPI_ATTROWNER_PRIM; + bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); + } + + if (!bSplitMeshInstancer) + return false; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, + Owner, &AttributeInfo), false); + + if (!AttributeInfo.exists || AttributeInfo.count <= 0) + return false; + + TArray IntData; + // Allocate sufficient buffer for data. + IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, + &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); + + return (IntData[0] != 0); +} + +bool +FHoudiniInstanceTranslator::IsFoliageInstancer(const int32& InGeoId, const int32& InPartId) +{ + bool bIsFoliageInstancer = false; + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + + if (!bIsFoliageInstancer) + { + // Try on primitive + Owner = HAPI_ATTROWNER_PRIM; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + } + + if (!bIsFoliageInstancer) + { + // Finally, try on points + Owner = HAPI_ATTROWNER_POINT; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + } + + if (!bIsFoliageInstancer) + return false; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, + Owner, &AttributeInfo), false); + + if (!AttributeInfo.exists || AttributeInfo.count <= 0) + return false; + + // We only support int/float attributes + if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + TArray IntData; + // Allocate sufficient buffer for data. + IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, + &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); + + return (IntData[0] != 0); + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + TArray FloatData; + // Allocate sufficient buffer for data. + FloatData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, + &AttributeInfo, 0, &FloatData[0], 0, AttributeInfo.count), false); + + return (FloatData[0] != 0); + } + + return false; +} + + +AActor* +FHoudiniInstanceTranslator::SpawnInstanceActor( + const FTransform& InTransform, + ULevel* InSpawnLevel, + UHoudiniInstancedActorComponent* InIAC) +{ + if (!InIAC || InIAC->IsPendingKill()) + return nullptr; + + UObject* InstancedObject = InIAC->GetInstancedObject(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + return nullptr; + + AActor* NewActor = nullptr; + +#if WITH_EDITOR + // Try to spawn a new actor for the given transform + GEditor->ClickLocation = InTransform.GetTranslation(); + GEditor->ClickPlane = FPlane(GEditor->ClickLocation, FVector::UpVector); + + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(InSpawnLevel, InstancedObject, false, RF_Transactional, nullptr); + if (NewActors.Num() > 0) + { + if (NewActors[0] && !NewActors[0]->IsPendingKill()) + { + NewActor = NewActors[0]; + } + } +#endif + + // Make sure that the actor was spawned in the proper level + FHoudiniEngineUtils::MoveActorToLevel(NewActor, InSpawnLevel); + + return NewActor; +} + + +void +FHoudiniInstanceTranslator::CleanupFoliageInstances( + UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, + UObject* InInstancedObject, + USceneComponent* InParentComponent) +{ + if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) + return; + + UStaticMesh* FoliageSM = InFoliageHISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + return; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(InFoliageHISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return; + + // Get the Foliage Type + UFoliageType *FoliageType = Cast(InInstancedObject); + if (!FoliageType || FoliageType->IsPendingKill()) + { + // Try to get the foliage type for the instanced mesh from the actor + FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InInstancedObject); + + if (!FoliageType || FoliageType->IsPendingKill()) + return; + } + + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(InParentComponent, FoliageType); + + // Remove the foliage type if it doesn't have any more instances + if(InFoliageHISMC->GetInstanceCount() == 0) + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + + return; +} + + +FString +FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(UObject* InObject) +{ + USceneComponent* InComponent = Cast(InObject); + + FString InstancerType = TEXT("Instancer"); + if (InComponent && !InComponent->IsPendingKill()) + { + if (InComponent->IsA()) + { + InstancerType = TEXT("(Split Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Actor Instancer)"); + } + else if (InComponent->IsA()) + { + if (InComponent->GetOwner() && InComponent->GetOwner()->IsA()) + InstancerType = TEXT("(Foliage Instancer)"); + else + InstancerType = TEXT("(Hierarchical Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Mesh Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Static Mesh Component)"); + } + } + + return InstancerType; +} + +bool +FHoudiniInstanceTranslator::GetInstancerSplitAttributesAndValues( + const int32& InGeoId, + const int32& InPartId, + const HAPI_AttributeOwner& InSplitAttributeOwner, + FString& OutSplitAttributeName, + TArray& OutAllSplitAttributeValues) +{ + // See if the user has specified an attribute to split the instancers. + bool bHasSplitAttribute = false; + //FString SplitAttribName = FString(); + OutSplitAttributeName = FString(); + + // Look for the unreal_split_attr attribute + // This attribute indicates the name of the point attribute that we'll use to split the instances further + HAPI_AttributeInfo SplitAttribInfo; + FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); + + TArray StringData; + bHasSplitAttribute = FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_SPLIT_ATTR, SplitAttribInfo, StringData, 1); + + if (!bHasSplitAttribute || !SplitAttribInfo.exists || StringData.Num() <= 0) + return false; + + OutSplitAttributeName = StringData[0]; + + // We have specified a split attribute, try to get its values. + OutAllSplitAttributeValues.Empty(); + if (!OutSplitAttributeName.IsEmpty()) + { + //HAPI_AttributeInfo SplitAttribInfo; + FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); + bool bSplitAttrFound = FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, + InPartId, + TCHAR_TO_ANSI(*OutSplitAttributeName), + SplitAttribInfo, + OutAllSplitAttributeValues, + 1, + InSplitAttributeOwner); + + if (!bSplitAttrFound || OutAllSplitAttributeValues.Num() <= 0) + { + // We couldn't properly get the point values + bHasSplitAttribute = false; + } + } + else + { + // We couldn't properly get the split attribute + bHasSplitAttribute = false; + } + + if (!bHasSplitAttribute) + { + // Clean up everything to ensure that we'll ignore the split attribute + OutAllSplitAttributeValues.Empty(); + OutSplitAttributeName = FString(); + } + + return bHasSplitAttribute; +} + +bool +FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId) +{ + bool bHISM = false; + HAPI_AttributeInfo AttriInfo; + FHoudiniApi::AttributeInfo_Init(&AttriInfo); + TArray IntData; + IntData.Empty(); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM, AttriInfo, IntData, 1)) + { + if (IntData.Num() > 0) + bHISM = IntData[0] == 1; + } + + return bHISM; +} + +void +FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() +{ + NumInstancedTransformsPerObject.Empty(); + OriginalInstancedTransformsFlat.Empty(); + for (const TArray& Transforms : OriginalInstancedTransforms) + { + NumInstancedTransformsPerObject.Add(Transforms.Num()); + OriginalInstancedTransformsFlat.Append(Transforms); + } + + OriginalInstanceObjectPackagePaths.Empty(); + for (const UObject* Obj : OriginalInstancedObjects) + { + if (IsValid(Obj)) + { + OriginalInstanceObjectPackagePaths.Add(Obj->GetPathName()); + } + else + { + OriginalInstanceObjectPackagePaths.Add(FString()); + } + } +} + +void +FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() +{ + const int32 NumObjects = NumInstancedTransformsPerObject.Num(); + OriginalInstancedTransforms.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) + { + TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; + const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; + for (int32 Index = 0; Index < NumInstances; ++Index) + { + Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumInstances; + } + + OriginalInstancedObjects.Empty(); + for (const FString& PackageFullPath : OriginalInstanceObjectPackagePaths) + { + FString PackagePath; + FString PackageName; + const bool bDidSplit = PackageFullPath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bDidSplit) + PackagePath = PackageFullPath; + + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!IsValid(Package)) + { + // Editor might have picked up the package yet, try to load it + Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); + } + if (IsValid(Package)) + { + OriginalInstancedObjects.Add(FindObject(Package, *PackageName)); + } + else + { + OriginalInstancedObjects.Add(nullptr); + } + } +} + +bool +FHoudiniInstanceTranslator::GetPerInstanceCustomData( + const int32& InGeoNodeId, + const int32& InPartId, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) +{ + // Initialize sizes to zero + OutInstancedOutputPartData.NumCustomFloats = 0; + OutInstancedOutputPartData.PerInstanceCustomData.SetNum(0); + + // First look for the number of custom floats + // If we dont have the attribute, or it is set to zero, we dont have PerInstanceCustomData + // HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" + HAPI_AttributeInfo AttribInfoNumCustomFloats; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNumCustomFloats); + + TArray CustomFloatsArray; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoNodeId, InPartId, + HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS, + AttribInfoNumCustomFloats, + CustomFloatsArray)) + { + return false; + } + + if (CustomFloatsArray.Num() <= 0) + return false; + + OutInstancedOutputPartData.NumCustomFloats = CustomFloatsArray[0]; + if (OutInstancedOutputPartData.NumCustomFloats <= 0) + return false; + + // We do have custom float, now read the per instance custom data + // They are stored in attributes that uses the "unreal_per_instance_custom" prefix + // ie, unreal_per_instance_custom0, unreal_per_instance_custom1 etc... + // We do not supprot tuples/arrays attributes for now. + TArray> AllCustomDataAttributeValues; + AllCustomDataAttributeValues.SetNum(OutInstancedOutputPartData.NumCustomFloats); + + // Read the custom data attributes + int32 NumInstance = 0; + for (int32 nIdx = 0; nIdx < OutInstancedOutputPartData.NumCustomFloats; nIdx++) + { + // Build the custom data attribute + FString CurrentAttr = TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX) + FString::FromInt(nIdx); + + // TODO? Tuple values Array attributes? + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + // Retrieve the custom data values + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + InGeoNodeId, InPartId, + TCHAR_TO_ANSI(*CurrentAttr), + AttribInfo, + AllCustomDataAttributeValues[nIdx], + 1)) + { + // Skip, we'll fill the values with zeros later on + continue; + } + + if (NumInstance < AllCustomDataAttributeValues[nIdx].Num()) + NumInstance = AllCustomDataAttributeValues[nIdx].Num(); + + if (NumInstance != AllCustomDataAttributeValues[nIdx].Num()) + { + HOUDINI_LOG_ERROR(TEXT("Instancer: Invalid number of Per-Instance Custom data attributes, ignoring...")); + OutInstancedOutputPartData.NumCustomFloats = 0; + return false; + } + } + + // Check sizes + if (AllCustomDataAttributeValues.Num() != OutInstancedOutputPartData.NumCustomFloats) + { + HOUDINI_LOG_ERROR(TEXT("Instancer: Number of Per-Instance Custom data attributes don't match the number of custom floats, ignoring...")); + OutInstancedOutputPartData.NumCustomFloats = 0; + return false; + } + + // Now that we have read all the custom data values, we need to "interlace" them + // in the final per-instance custom data array, fill missing values with zeroes + OutInstancedOutputPartData.PerInstanceCustomData.SetNumZeroed(OutInstancedOutputPartData.NumCustomFloats * NumInstance); + + // Fill the custom data array by interlacing the custom float values + for (int32 nCustomIdx = 0; nCustomIdx < OutInstancedOutputPartData.NumCustomFloats; nCustomIdx++) + { + int32 CurrentNumInstance = NumInstance; + if (NumInstance < AllCustomDataAttributeValues[nCustomIdx].Num()) + CurrentNumInstance = AllCustomDataAttributeValues[nCustomIdx].Num(); + + // Copy the attribute value we read into the custom data array + for (int32 nInstanceIdx = 0; nInstanceIdx < CurrentNumInstance; nInstanceIdx++) + { + OutInstancedOutputPartData.PerInstanceCustomData[nInstanceIdx * OutInstancedOutputPartData.NumCustomFloats + nCustomIdx] = AllCustomDataAttributeValues[nCustomIdx][nInstanceIdx]; + } + } + + return true; +} + + +bool +FHoudiniInstanceTranslator::UpdateChangedPerInstanceCustomData( + const int32& InNumCustomFloats, + const TArray& InPerInstanceCustomData, + USceneComponent* InComponentToUpdate) +{ + // Checks + if (InNumCustomFloats < 0) + return false; + + UInstancedStaticMeshComponent* ISMC = Cast(InComponentToUpdate); + if (!IsValid(ISMC)) + return false; + + // No Custom data to add/remove + if (ISMC->NumCustomDataFloats == 0 && InNumCustomFloats == 0) + return false; + + // We can copy the per instance custom data if we have any + // TODO: Properly extract only needed values! + ISMC->NumCustomDataFloats = InNumCustomFloats; + + int32 InstanceCount = ISMC->GetInstanceCount(); + + // Clear out and reinit to 0 the PerInstanceCustomData array + ISMC->PerInstanceSMCustomData.Empty(InstanceCount * InNumCustomFloats); + ISMC->PerInstanceSMCustomData.SetNumZeroed(InstanceCount * InNumCustomFloats); + + // Behaviour copied From UInstancedStaticMeshComponent::SetCustomData() + // except we modify all the instance/custom values at once + ISMC->Modify(); + + // MemCopy + const int32 NumToCopy = FMath::Min(ISMC->PerInstanceSMCustomData.Num(), InPerInstanceCustomData.Num()); + if (NumToCopy > 0) + { + FMemory::Memcpy(&ISMC->PerInstanceSMCustomData[0], InPerInstanceCustomData.GetData(), NumToCopy * InPerInstanceCustomData.GetTypeSize()); + } + + // Force recreation of the render data when proxy is created + //NewISMC->InstanceUpdateCmdBuffer.Edit(); + // Cant call the edit function above because the function is defined in a different cpp file than the .h it is declared in... + ISMC->InstanceUpdateCmdBuffer.NumEdits++; + + ISMC->MarkRenderStateDirty(); + + return true; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h index c0eb647f2..5268c71f4 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h @@ -1,394 +1,394 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniOutput.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniGenericAttribute.h" - -#include "HoudiniInstanceTranslator.generated.h" - -class UStaticMesh; -class UFoliageType; -class UHoudiniStaticMesh; -class UHoudiniInstancedActorComponent; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes -{ -public: - - GENERATED_BODY() - - // level path attribute value - UPROPERTY() - FString LevelPath; - - // Bake actor name attribute value - UPROPERTY() - FString BakeActorName; - - // bake outliner folder attribute value - UPROPERTY() - FString BakeOutlinerFolder; - - // unreal_bake_folder attribute value - UPROPERTY() - FString BakeFolder; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData -{ -public: - - GENERATED_BODY() - - UPROPERTY() - bool bForceHISM = false; - - UPROPERTY() - TArray OriginalInstancedObjects; - - UPROPERTY() - TArray OriginalInstanceObjectPackagePaths; - - TArray> OriginalInstancedTransforms; - - UPROPERTY() - TArray NumInstancedTransformsPerObject; - - UPROPERTY() - TArray OriginalInstancedTransformsFlat; - - UPROPERTY() - FString SplitAttributeName; - - UPROPERTY() - TArray SplitAttributeValues; - - UPROPERTY() - bool bSplitMeshInstancer = false; - - UPROPERTY() - bool bIsFoliageInstancer = false; - - UPROPERTY() - TArray AllPropertyAttributes; - - // All level path attributes from the first attribute owner we could find - UPROPERTY() - TArray AllLevelPaths; - - // All bake actor name attributes from the first attribute owner we could find - UPROPERTY() - TArray AllBakeActorNames; - - // All unreal_bake_folder attributes (prim attr is checked first then detail) - UPROPERTY() - TArray AllBakeFolders; - - // All bake outliner folder attributes from the first attribute owner we could find - UPROPERTY() - TArray AllBakeOutlinerFolders; - - // A map of split value to attribute values that are valid per split (unreal_bake_actor, unreal_level_path, - // unreal_bake_outliner_folder) - UPROPERTY() - TMap PerSplitAttributes; - - UPROPERTY() - TArray OutputNames; - - UPROPERTY() - TArray TileValues; - - UPROPERTY() - TArray MaterialAttributes; - - // Number of custom floats for the instancer - UPROPERTY() - int32 NumCustomFloats = -1; - - // Custom float array - // Size is NumCustomFloat * NumberOfInstances - UPROPERTY() - TArray PerInstanceCustomData; - - void BuildFlatInstancedTransformsAndObjectPaths(); - - void BuildOriginalInstancedTransformsAndObjectArrays(); -}; - -struct HOUDINIENGINE_API FHoudiniInstanceTranslator -{ - public: - - static bool PopulateInstancedOutputPartData( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); - - static bool CreateAllInstancersFromHoudiniOutput( - UHoudiniOutput* InOutput, - const TArray& InAllOutputs, - UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData = nullptr); - - static bool GetInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValues, - TMap& OutPerSplitAttributes); - - static bool GetPackedPrimitiveInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes); - - static bool GetAttributeInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes); - - static bool GetOldSchoolAttributeInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms); - - static bool GetObjectInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms); - - // Updates the variations array using the instanced outputs - static void UpdateInstanceVariationObjects( - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TArray& InOriginalObjects, - const TArray>& InOriginalTransforms, - TMap& InstancedOutputs, - TArray>& OutVariationsInstancedObjects, - TArray>& OutVariationsInstancedTransforms, - TArray& OutVariationOriginalObjectIdx, - TArray& OutVariationIndices); - - // Recreates the components after an instanced outputs has been changed - static bool UpdateChangedInstancedOutput( - FHoudiniInstancedOutput& InInstancedOutput, - const FHoudiniOutputObjectIdentifier& OutputIdentifier, - UHoudiniOutput* InParentOutput, - USceneComponent* InParentComponent); - - // Recomputes the variation assignements for a given instanced output - static void UpdateVariationAssignements( - FHoudiniInstancedOutput& InstancedOutput); - - // Extracts the final transforms (with the transform offset applied) for a given variation - static void ProcessInstanceTransforms( - FHoudiniInstancedOutput& InstancedOutput, - const int32& VariationIdx, - TArray& OutProcessedTransforms); - - // Creates a new component or updates the previous one if possible - static bool CreateOrUpdateInstanceComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent* OldComponent, - USceneComponent*& NewComponent, - const bool& InIsSplitMeshInstancer, - const bool& InIsFoliageInstancer, - const TArray& InstancerMaterials, - const int32& InstancerObjectIdx = 0, - const bool& bForceHISM = false); - - // Create or update an ISMC / HISMC - static bool CreateOrUpdateInstancedStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr, - const bool& bForceHISM = false); - - // Create or update an IAC - static bool CreateOrUpdateInstancedActorComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent); - - // Create or update a MeshSplitInstancer - static bool CreateOrUpdateMeshSplitInstancerComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - const TArray& InstancerMaterials); - - // Create or update a StaticMeshComponent (when we have only one instance) - static bool CreateOrUpdateStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr); - - // Create or update a HoudiniStaticMeshComponent (when we have only one instance) - static bool CreateOrUpdateHoudiniStaticMeshComponent( - UHoudiniStaticMesh* InstancedProxyStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr); - - // Create or update a Foliage instances - static bool CreateOrUpdateFoliageInstances( - UStaticMesh* InstancedStaticMesh, - UFoliageType* InFoliageType, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& NewInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/); - - // Helper fumction to properly remove/destroy a component - static bool RemoveAndDestroyComponent( - UObject* InComponent, - UObject* InFoliageObject); - - // Utility function - // Fetches instance transforms and convert them to ue4 coordinates - static bool HapiGetInstanceTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancerUnrealTransforms); - - // Helper function used to spawn a new Actor for UHoudiniInstancedActorComponent - // Relies on editor-only functionalities, so this function is not on the IAC itself - static AActor* SpawnInstanceActor( - const FTransform& InTransform, - ULevel* InSpawnLevel, - UHoudiniInstancedActorComponent* InIAC); - - // Helper functions for generic property attributes - static bool GetGenericPropertiesAttributes( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, - const TArray& InAllPropertyAttributes, - const int32& AtIndex); - - static bool GetMaterialOverridesFromAttributes( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutMaterialAttributes); - - static bool GetInstancerMaterials( - const TArray& MaterialAttributes, - TArray& OutInstancerMaterials); - - static bool GetInstancerMaterials( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutInstancerMaterials); - - static bool GetVariationMaterials( - FHoudiniInstancedOutput* InInstancedOutput, - const int32& InVariationIndex, - const TArray& InInstancerMaterials, - TArray& OutVariationMaterials); - - static bool IsSplitInstancer( - const int32& InGeoId, - const int32& InPartId); - - static bool IsFoliageInstancer( - const int32& InGeoId, - const int32& InPartId); - - static void CleanupFoliageInstances( - UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, - UObject* InInstancedObject, - USceneComponent* InParentComponent); - - static FString GetInstancerTypeFromComponent( - UObject* InComponent); - - // Returns the name and values of the attribute that has been specified to split the instances - // returns false if the attribute is invalid or hasn't been specified - static bool GetInstancerSplitAttributesAndValues( - const int32& InGeoId, - const int32& InPartId, - const HAPI_AttributeOwner& InSplitAttributeOwner, - FString& OutSplitAttributeName, - TArray& OutAllSplitAttributeValues); - - // Get if force using HISM from attribute - static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); - - // Checks for PerInstanceCustomData on the instancer part - static bool GetPerInstanceCustomData( - const int32& InGeoNodeId, - const int32& InPartId, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); - - // Update PerInstanceCustom data on the given component if possible - static bool UpdateChangedPerInstanceCustomData( - const int32& InNumCustomFloats, - const TArray& InPerInstanceCustomData, - USceneComponent* InComponentToUpdate); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniOutput.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniGenericAttribute.h" + +#include "HoudiniInstanceTranslator.generated.h" + +class UStaticMesh; +class UFoliageType; +class UHoudiniStaticMesh; +class UHoudiniInstancedActorComponent; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes +{ +public: + + GENERATED_BODY() + + // level path attribute value + UPROPERTY() + FString LevelPath; + + // Bake actor name attribute value + UPROPERTY() + FString BakeActorName; + + // bake outliner folder attribute value + UPROPERTY() + FString BakeOutlinerFolder; + + // unreal_bake_folder attribute value + UPROPERTY() + FString BakeFolder; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData +{ +public: + + GENERATED_BODY() + + UPROPERTY() + bool bForceHISM = false; + + UPROPERTY() + TArray OriginalInstancedObjects; + + UPROPERTY() + TArray OriginalInstanceObjectPackagePaths; + + TArray> OriginalInstancedTransforms; + + UPROPERTY() + TArray NumInstancedTransformsPerObject; + + UPROPERTY() + TArray OriginalInstancedTransformsFlat; + + UPROPERTY() + FString SplitAttributeName; + + UPROPERTY() + TArray SplitAttributeValues; + + UPROPERTY() + bool bSplitMeshInstancer = false; + + UPROPERTY() + bool bIsFoliageInstancer = false; + + UPROPERTY() + TArray AllPropertyAttributes; + + // All level path attributes from the first attribute owner we could find + UPROPERTY() + TArray AllLevelPaths; + + // All bake actor name attributes from the first attribute owner we could find + UPROPERTY() + TArray AllBakeActorNames; + + // All unreal_bake_folder attributes (prim attr is checked first then detail) + UPROPERTY() + TArray AllBakeFolders; + + // All bake outliner folder attributes from the first attribute owner we could find + UPROPERTY() + TArray AllBakeOutlinerFolders; + + // A map of split value to attribute values that are valid per split (unreal_bake_actor, unreal_level_path, + // unreal_bake_outliner_folder) + UPROPERTY() + TMap PerSplitAttributes; + + UPROPERTY() + TArray OutputNames; + + UPROPERTY() + TArray TileValues; + + UPROPERTY() + TArray MaterialAttributes; + + // Number of custom floats for the instancer + UPROPERTY() + int32 NumCustomFloats = -1; + + // Custom float array + // Size is NumCustomFloat * NumberOfInstances + UPROPERTY() + TArray PerInstanceCustomData; + + void BuildFlatInstancedTransformsAndObjectPaths(); + + void BuildOriginalInstancedTransformsAndObjectArrays(); +}; + +struct HOUDINIENGINE_API FHoudiniInstanceTranslator +{ + public: + + static bool PopulateInstancedOutputPartData( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); + + static bool CreateAllInstancersFromHoudiniOutput( + UHoudiniOutput* InOutput, + const TArray& InAllOutputs, + UObject* InOuterComponent, + const TMap* InPreBuiltInstancedOutputPartData = nullptr); + + static bool GetInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValues, + TMap& OutPerSplitAttributes); + + static bool GetPackedPrimitiveInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes); + + static bool GetAttributeInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes); + + static bool GetOldSchoolAttributeInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms); + + static bool GetObjectInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms); + + // Updates the variations array using the instanced outputs + static void UpdateInstanceVariationObjects( + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TArray& InOriginalObjects, + const TArray>& InOriginalTransforms, + TMap& InstancedOutputs, + TArray>& OutVariationsInstancedObjects, + TArray>& OutVariationsInstancedTransforms, + TArray& OutVariationOriginalObjectIdx, + TArray& OutVariationIndices); + + // Recreates the components after an instanced outputs has been changed + static bool UpdateChangedInstancedOutput( + FHoudiniInstancedOutput& InInstancedOutput, + const FHoudiniOutputObjectIdentifier& OutputIdentifier, + UHoudiniOutput* InParentOutput, + USceneComponent* InParentComponent); + + // Recomputes the variation assignements for a given instanced output + static void UpdateVariationAssignements( + FHoudiniInstancedOutput& InstancedOutput); + + // Extracts the final transforms (with the transform offset applied) for a given variation + static void ProcessInstanceTransforms( + FHoudiniInstancedOutput& InstancedOutput, + const int32& VariationIdx, + TArray& OutProcessedTransforms); + + // Creates a new component or updates the previous one if possible + static bool CreateOrUpdateInstanceComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent* OldComponent, + USceneComponent*& NewComponent, + const bool& InIsSplitMeshInstancer, + const bool& InIsFoliageInstancer, + const TArray& InstancerMaterials, + const int32& InstancerObjectIdx = 0, + const bool& bForceHISM = false); + + // Create or update an ISMC / HISMC + static bool CreateOrUpdateInstancedStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr, + const bool& bForceHISM = false); + + // Create or update an IAC + static bool CreateOrUpdateInstancedActorComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent); + + // Create or update a MeshSplitInstancer + static bool CreateOrUpdateMeshSplitInstancerComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + const TArray& InstancerMaterials); + + // Create or update a StaticMeshComponent (when we have only one instance) + static bool CreateOrUpdateStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr); + + // Create or update a HoudiniStaticMeshComponent (when we have only one instance) + static bool CreateOrUpdateHoudiniStaticMeshComponent( + UHoudiniStaticMesh* InstancedProxyStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr); + + // Create or update a Foliage instances + static bool CreateOrUpdateFoliageInstances( + UStaticMesh* InstancedStaticMesh, + UFoliageType* InFoliageType, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& NewInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/); + + // Helper fumction to properly remove/destroy a component + static bool RemoveAndDestroyComponent( + UObject* InComponent, + UObject* InFoliageObject); + + // Utility function + // Fetches instance transforms and convert them to ue4 coordinates + static bool HapiGetInstanceTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancerUnrealTransforms); + + // Helper function used to spawn a new Actor for UHoudiniInstancedActorComponent + // Relies on editor-only functionalities, so this function is not on the IAC itself + static AActor* SpawnInstanceActor( + const FTransform& InTransform, + ULevel* InSpawnLevel, + UHoudiniInstancedActorComponent* InIAC); + + // Helper functions for generic property attributes + static bool GetGenericPropertiesAttributes( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, + const TArray& InAllPropertyAttributes, + const int32& AtIndex); + + static bool GetMaterialOverridesFromAttributes( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutMaterialAttributes); + + static bool GetInstancerMaterials( + const TArray& MaterialAttributes, + TArray& OutInstancerMaterials); + + static bool GetInstancerMaterials( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutInstancerMaterials); + + static bool GetVariationMaterials( + FHoudiniInstancedOutput* InInstancedOutput, + const int32& InVariationIndex, + const TArray& InInstancerMaterials, + TArray& OutVariationMaterials); + + static bool IsSplitInstancer( + const int32& InGeoId, + const int32& InPartId); + + static bool IsFoliageInstancer( + const int32& InGeoId, + const int32& InPartId); + + static void CleanupFoliageInstances( + UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, + UObject* InInstancedObject, + USceneComponent* InParentComponent); + + static FString GetInstancerTypeFromComponent( + UObject* InComponent); + + // Returns the name and values of the attribute that has been specified to split the instances + // returns false if the attribute is invalid or hasn't been specified + static bool GetInstancerSplitAttributesAndValues( + const int32& InGeoId, + const int32& InPartId, + const HAPI_AttributeOwner& InSplitAttributeOwner, + FString& OutSplitAttributeName, + TArray& OutAllSplitAttributeValues); + + // Get if force using HISM from attribute + static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); + + // Checks for PerInstanceCustomData on the instancer part + static bool GetPerInstanceCustomData( + const int32& InGeoNodeId, + const int32& InPartId, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); + + // Update PerInstanceCustom data on the given component if possible + static bool UpdateChangedPerInstanceCustomData( + const int32& InNumCustomFloats, + const TArray& InPerInstanceCustomData, + USceneComponent* InComponentToUpdate); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp index 667e1f122..1dfa7a380 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp @@ -1,3879 +1,4476 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniLandscapeTranslator.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniEngineString.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPackageParams.h" -#include "HoudiniStringResolver.h" -#include "HoudiniInput.h" - -#include "ObjectTools.h" -#include "FileHelpers.h" -#include "Editor.h" -#include "LandscapeLayerInfoObject.h" -#include "LandscapeStreamingProxy.h" -#include "LandscapeInfo.h" -#include "LandscapeEdit.h" -#include "AssetRegistryModule.h" -#include "PackageTools.h" -#include "PhysicalMaterials/PhysicalMaterial.h" -#include "UObject/UnrealType.h" - -#include "GameFramework/WorldSettings.h" -#include "Misc/Paths.h" -#include "Modules/ModuleManager.h" -#include "AssetToolsModule.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Factories/WorldFactory.h" -#include "Misc/Guid.h" -#include "Engine/LevelBounds.h" - -#include "HAL/IConsoleManager.h" -#include "Engine/AssetManager.h" -#include "Misc/ScopedSlowTask.h" - -#if WITH_EDITOR - #include "EditorLevelUtils.h" - #include "LandscapeEditorModule.h" - #include "LandscapeFileFormatInterface.h" -#endif - -static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( - TEXT("HoudiniEngine.ExportLandscapeTextures"), - 0, - TEXT("If enabled, landscape layers and heightmap will be exported as textures in the temp directory when converting a Heightfield to a Landscape.\n") - TEXT("0: Disabled\n") - TEXT("1: Enabled\n") -); - -typedef FHoudiniEngineUtils FHUtils; - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY(); - -bool -FHoudiniLandscapeTranslator::CreateLandscape( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedOutputs, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* InWorld, // Persistent / root world for the landscape - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniLandscapeExtent& LandscapeExtent, - FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, - FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, - FHoudiniPackageParams InPackageParams, - TArray& OutCreatedPackages -) -{ - check(LayerMinimums.Contains(TEXT("height"))); - check(LayerMaximums.Contains(TEXT("height"))); - - float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); - float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); - - if (!InOutput || InOutput->IsPendingKill()) - return false; - - // Get the height map. - const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); - if (!Heightfield) - return false; - - if (Heightfield->Type != EHoudiniPartType::Volume) - return false; - - const HAPI_NodeId GeoId = Heightfield->GeoId; - const HAPI_PartId PartId = Heightfield->PartId; - - // Construct the identifier of the Heightfield geo part. - FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); - HeightfieldIdentifier.PartName = Heightfield->PartName; - - FString NodeNameSuffix = GetActorNameSuffix(InPackageParams.PackageMode); - - const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - - TArray IntData; - TArray StrData; - // Output attributes will be stored on the Output object and will be used again during baking to determine - // where content should be baked to and what they should be named, etc. - // At the end of this function, the output attributes and tokens will be copied to the output object. - TMap OutputAttributes; - TMap OutputTokens; - FHoudiniAttributeResolver Resolver; - InPackageParams.UpdateTokensFromParams(InWorld, HoudiniAssetComponent, OutputTokens); - - bool bHasTile = Heightfield->VolumeTileIndex >= 0; - - // --------------------------------------------- - // Attribute: unreal_landscape_tile_actor_type, unreal_landscape_streaming_proxy (v1) - // --------------------------------------------- - // Determine the actor type for the tile - bool bCreateLandscapeStreamingProxy = false; - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - LandscapeActorType TileActorType = LandscapeActorType::LandscapeActor; - IntData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, AttributeInfo, IntData, 1)) - { - if (IntData.Num() > 0) - { - TileActorType = static_cast(IntData[0]); - } - } - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY, AttributeInfo, IntData, 1)) - { - if (IntData.Num() > 0 && IntData[0] != 0) - TileActorType = LandscapeActorType::LandscapeStreamingProxy; - } - - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, FString::FromInt(static_cast(TileActorType))); - - // --------------------------------------------- - // Attribute: unreal_landscape_actor_name - // --------------------------------------------- - // Retrieve the name of the main Landscape actor to look for - FString SharedLandscapeActorName = DefaultLandscapeActorPrefix + "SharedLandscape"; // If this is an empty string, don't affirm a root landscape actor? - StrData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, AttributeInfo, StrData, 1)) - { - if (StrData.Num() > 0 && !StrData[0].IsEmpty()) - SharedLandscapeActorName = StrData[0]; - } - - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); - - // --------------------------------------------- - // Attribute: unreal_level_path - // --------------------------------------------- - // FString LevelPath = bHasTile ? "{world}/Landscape/Tile{tile}" : "{world}/Landscape"; - FString LevelPath; - TArray LevelPaths; - if (FHoudiniEngineUtils::GetLevelPathAttribute(GeoId, PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - LevelPath = LevelPaths[0]; - } - if (!LevelPath.IsEmpty()) - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); - - // --------------------------------------------- - // Attribute: unreal_output_name - // --------------------------------------------- - FString LandscapeTileActorName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; - TArray AllOutputNames; - if (FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames)) - { - if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) - LandscapeTileActorName = AllOutputNames[0]; - } - OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); - - // --------------------------------------------- - // Attribute: unreal_bake_folder - // --------------------------------------------- - TArray AllBakeFolders; - if (FHoudiniEngineUtils::GetBakeFolderAttribute(GeoId, AllBakeFolders, PartId)) - { - FString BakeFolder; - if (AllBakeFolders.Num() > 0 && !AllBakeFolders[0].IsEmpty()) - BakeFolder = AllBakeFolders[0]; - OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_BAKE_FOLDER), BakeFolder); - } - - // Streaming proxy actors/tiles requires a "main" landscape actor - // that contains the shared landscape state. - bool bRequiresSharedLandscape = false; - if (TileActorType == LandscapeActorType::LandscapeStreamingProxy) - bRequiresSharedLandscape = true; - - // ---------------------------------- - // Inject landscape specific tokens - // ---------------------------------- - if (bHasTile) - { - const FString TileValue = FString::FromInt(Heightfield->VolumeTileIndex); - // Tile value needs to go into Output arguments to be available during the bake. - OutputTokens.Add(TEXT("tile"), TileValue); - } - - // ---------------------------------- - // Expand string arguments for various landscape naming aspects. - // ---------------------------------- - - // Update resolver attributes and tokens before we start resolving attributes. - Resolver.SetCachedAttributes(OutputAttributes); - Resolver.SetTokensFromStringMap(OutputTokens); - - SharedLandscapeActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); - SharedLandscapeActorName += NodeNameSuffix; - - LandscapeTileActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, LandscapeTileActorName); - LandscapeTileActorName += NodeNameSuffix; - - LevelPath = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); - - FString TileName = LandscapeTileActorName; - - // Note that relative level paths are always interpreted as relative to the default output directory (temp / bake). - // FString TilePackagePath = FPaths::Combine(DefaultOutputPath, LevelPath); - FString TilePackagePath = Resolver.ResolveFullLevelPath(); - - // This crashes UE if the package name does not resolve - //FString TileMapFileName = FPackageName::LongPackageNameToFilename(TilePackagePath, FPackageName::GetMapPackageExtension()); - - FText NotValidReason; - bool bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); - if (!bIsValidLongName) - { - // Try a more naive approach - TilePackagePath = FPaths::Combine(InPackageParams.BakeFolder, LevelPath); - bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); - } - - if (!bIsValidLongName) - { - HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] TilePackagePath is not a valid long name. Reason: %s"), *(NotValidReason.ToString())); - return false; - } - - FString TileMapFileName; - if (!FPackageName::TryConvertLongPackageNameToFilename(TilePackagePath, TileMapFileName, FPackageName::GetMapPackageExtension())) - { - // Rather stop here than crash! - HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] Failed to resolve the TilePackagePath: %s"), *(TilePackagePath)); - return false; - } - - // Find the package for both the world and the tile. - // The world should contain the main landscape actor while - // the tile will contain a Landscape, LandscapeProxy or LandscapeStreamingProxy depending on user settings. - - bool bTileisStreamingProxy = (TileActorType == LandscapeActorType::LandscapeStreamingProxy); - UWorld* TileWorld = nullptr; // World from which to spawn tile actor - ULevel* TileLevel = nullptr; // Level in which to spawn tile actor - ALandscapeProxy* TileActor = nullptr; // Spawned tile actor. - - // ---------------------------------- - // Update package parameters for this tile - // ---------------------------------- - - // NOTE: we don't manually inject a tile number in the object name. This should - // already be encoded in the TileName string. - FHoudiniPackageParams TilePackageParams = InPackageParams; - TilePackageParams.ObjectName = TileName; - - FHoudiniPackageParams LayerPackageParams = InPackageParams; - if (bRequiresSharedLandscape) - { - // Note that layers are shared amongst all the tiles for a given landscape. - LayerPackageParams.ObjectName = SharedLandscapeActorName; - } - else - { - // This landscape tile is a standalone landscape and should have its own material layers. - LayerPackageParams.ObjectName = TileName; - } - - // See if the current heightfield has an unreal_material or unreal_hole_material assigned to it - UMaterialInterface* LandscapeMaterial = nullptr; - UMaterialInterface* LandscapeHoleMaterial = nullptr; - UPhysicalMaterial* LandscapePhysicalMaterial = nullptr; - FHoudiniLandscapeTranslator::GetLandscapeMaterials(*Heightfield, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial); - - // Extract the float data from the Heightfield. - const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; - TArray FloatValues; - float FloatMin, FloatMax; - if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) - return false; - - // Heightfield conversions should always use the global float min/max - // since they need to be calculated externally, potentially across multiple tiles. - FloatMin = fGlobalMin; - FloatMax = fGlobalMax; - - // Get the Unreal landscape size - const int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; - const int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; - - if (!LandscapeTileSizeInfo.bIsCached) - { - // Calculate a landscape size info from this heightfield to be - // used by subsequent tiles on the same landscape - if (FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - HoudiniHeightfieldXSize, - HoudiniHeightfieldYSize, - LandscapeTileSizeInfo.UnrealSizeX, - LandscapeTileSizeInfo.UnrealSizeY, - LandscapeTileSizeInfo.NumSectionsPerComponent, - LandscapeTileSizeInfo.NumQuadsPerSection)) - { - LandscapeTileSizeInfo.bIsCached = true; - } - else - { - return false; - } - } - - const int32 UnrealTileSizeX = LandscapeTileSizeInfo.UnrealSizeX; - const int32 UnrealTileSizeY = LandscapeTileSizeInfo.UnrealSizeY; - const int32 NumSectionPerLandscapeComponent = LandscapeTileSizeInfo.NumSectionsPerComponent; - const int32 NumQuadsPerLandscapeSection = LandscapeTileSizeInfo.NumQuadsPerSection; - - // ---------------------------------------------------- - // Export of layer textures - // ---------------------------------------------------- - // Export textures, if enabled. Mostly used for debugging at the moment. - bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; - if (bExportTexture) - { - // Export raw height data to texture - FString TextureName = TilePackageParams.ObjectName + TEXT("_height_raw"); - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - HoudiniHeightfieldXSize, - HoudiniHeightfieldYSize, - FloatValues, - FloatMin, - FloatMax); - } - - // Look for all the layers/masks corresponding to the current heightfield. - TArray< const FHoudiniGeoPartObject* > FoundLayers; - FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); - - // Get the updated layers. - TArray LayerInfos; - - if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, - LayerMinimums, LayerMaximums, LayerInfos, false, - TilePackageParams, - LayerPackageParams, - OutCreatedPackages)) - return false; - - // Convert Houdini's heightfield data to Unreal's landscape data - TArray IntHeightData; - FTransform TileTransform; - if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( - FloatValues, VolumeInfo, - UnrealTileSizeX, UnrealTileSizeY, - FloatMin, FloatMax, - IntHeightData, TileTransform)) - return false; - - // ---------------------------------------------------- - // Property changes that we want to track - // ---------------------------------------------------- - - bool bModifiedLandscapeActor = false; - bool bModifiedSharedLandscapeActor = false; - bool bSharedLandscapeMaterialChanged = false; - bool bSharedLandscapeHoleMaterialChanged = false; - bool bSharedPhysicalMaterialChanged = false; - bool bTileLandscapeMaterialChanged = false; - bool bTileLandscapeHoleMaterialChanged = false; - bool bTilePhysicalMaterialChanged = false; - bool bCreatedMap = false; - bool bCreatedTileActor = false; - bool bHeightLayerDataChanged = false; - bool bCustomLayerDataChanged = false; - - // ---------------------------------------------------- - // Calculate Tile location and landscape offset - // ---------------------------------------------------- - FTransform LandscapeTransform; - FIntPoint TileLoc; - - // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate - // for any landscape shifts due to section base aligment offsets. - CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeReferenceLocation, LandscapeTransform, TileLoc); - - // ---------------------------------------------------- - // Find or create *shared* landscape - // ---------------------------------------------------- - - ALandscape* SharedLandscapeActor = nullptr; - bool bCreatedSharedLandscape = false; - - if (bRequiresSharedLandscape) - { - // Streaming proxy tiles always require a "shared landscape" that contains the - // various landscape properties to be shared amongst all the tiles. - AActor* FoundActor = nullptr; - SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, SharedLandscapeActorName, FoundActor); - - bool bIsValidSharedLandscape = IsValid(SharedLandscapeActor); - - if (bIsValidSharedLandscape) - { - // We have a target landscape. Check whether it is compatible with the Houdini volume. - ULandscapeInfo* LandscapeInfo = SharedLandscapeActor->GetLandscapeInfo(); - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape: %s"), *(SharedLandscapeActor->GetPathName())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape with num proxies: %d"), LandscapeInfo->Proxies.Num()); - - if (!LandscapeExtent.bIsCached) - { - LandscapeInfo->FixupProxiesTransform(); - // Cache the landscape extents. Note that GetLandscapeExtent() will only take into account the currently loaded landscape tiles. - PopulateLandscapeExtents(LandscapeExtent, LandscapeInfo); - } - - bool bIsCompatible = IsLandscapeInfoCompatible( - LandscapeInfo, - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Checking landscape compatibility ...")); - bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); - if (!bIsCompatible) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Shared landscape is incompatible. Cannot reuse.")); - // We can't resize the landscape in-place. We have to create a new one. - DestroyLandscape(SharedLandscapeActor); - SharedLandscapeActor = nullptr; - bIsValidSharedLandscape = false; - } - else - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Existing shared landscape is compatible.")); - } - } - - if (!bIsValidSharedLandscape) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Create new shared landscape...")); - // Create and configure the main landscape actor. - // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos - SharedLandscapeActor = InWorld->SpawnActor(); - if (SharedLandscapeActor) - { - CreatedUntrackedOutputs.Add( SharedLandscapeActor ); - - // NOTE that shared landscape is always located at the origin, but not the tile actors. The - // tiles are properly transformed. - - // If we working with landscape tiles, this actor will become the "Main landscape" actor but - // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. - SharedLandscapeActor->bCanHaveLayersContent = false; - SharedLandscapeActor->ComponentSizeQuads = NumQuadsPerLandscapeSection*NumSectionPerLandscapeComponent; - SharedLandscapeActor->NumSubsections = NumSectionPerLandscapeComponent; - SharedLandscapeActor->SubsectionSizeQuads = NumQuadsPerLandscapeSection; - SharedLandscapeActor->SetLandscapeGuid( FGuid::NewGuid() ); - SharedLandscapeActor->bCastStaticShadow = false; - for (const auto& ImportLayerInfo : LayerInfos) - { - SharedLandscapeActor->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(ImportLayerInfo.LayerInfo)); - } - SharedLandscapeActor->CreateLandscapeInfo(); - bCreatedSharedLandscape = true; - - // NOTE: It is important to set Landscape materials BEFORE blitting layer data. For example, setting - // data in the visibility layer (on tiles) will have no effect until Landscape materials have been applied / processed. - SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; - SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - - // Ensure the landscape actor name and label matches `LandscapeActorName`. - FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, SharedLandscapeActorName); - - SharedLandscapeActor->MarkPackageDirty(); - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(InWorld->GetPathName()) ); - return false; - } - } - // else -- Reusing shared landscape - } - - if (SharedLandscapeActor) - { - // Ensure the existing landscape actor transform is correct. - SharedLandscapeActor->SetActorRelativeTransform(LandscapeTransform); - - bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; - if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) - { - DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - } - - if (bSharedLandscapeMaterialChanged) - { - SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; - - } - if (bSharedLandscapeHoleMaterialChanged) - { - SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - - } - - bSharedPhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? (SharedLandscapeActor->DefaultPhysMaterial != LandscapePhysicalMaterial) : false; - if (bSharedPhysicalMaterialChanged) - { - DoPreEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); - SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - SharedLandscapeActor->ChangedPhysMaterial(); - } - } - - // ---------------------------------------------------- - // Find Landscape actor / tile - // ---------------------------------------------------- - - // Find an actor with the given name. The TileWorld and TileLevel returned should be - // used to spawn the new actor, if the actor itself could not be found. - //bool bCreatedPackage = false; - // TileActor = FindExistingLandscapeActor( - // InWorld, InOutput, ValidLandscapes, - // UnrealLandscapeSizeX, UnrealLandscapeSizeY, LandscapeTileActorName, - // LevelPath, TileWorld, TileLevel, bCreatedPackage); - - // Currently the Temp Cook mode is not concerned with creating packages. This will, at the time of writing, - // exclusively be dealt with during Bake mode so don't bother with searching / creating other packages. - - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (IsValid(HAC)) - { - TileWorld = HAC->GetWorld(); - TileLevel = HAC->GetComponentLevel(); - } - else - { - TileWorld = InWorld; - TileLevel = InWorld->PersistentLevel; - } - - check(TileWorld); - check(TileLevel); - - AActor* FoundActor = nullptr; - if (InPackageParams.PackageMode == EPackageMode::Bake) - { - // When baking, See if we can find any landscape / proxy actors for this tile in the TileLevel. - // If we find any actors that match the name but not the type, or the actors are pending kill, then - // rename them so that we can spawn new actors. - switch (TileActorType) - { - case LandscapeActorType::LandscapeActor: - TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); - break; - case LandscapeActorType::LandscapeStreamingProxy: - TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); - break; - default: - TileActor = nullptr; - } - } - else - { - // In temp mode, only consider our previous output landscapes, - // or our input landscapes that have the "update input landscape" option enabled - ALandscapeProxy* FoundLandscapeProxy = nullptr; - - // Try to see if we have an input landscape that matches the size of the current HGPO - for (int nIdx = 0; nIdx < InputLandscapesToUpdate.Num(); nIdx++) - { - ALandscapeProxy* CurrentInputLandscape = InputLandscapesToUpdate[nIdx]; - if (!CurrentInputLandscape) - continue; - - if (SharedLandscapeActor && CurrentInputLandscape->GetLandscapeActor() != SharedLandscapeActor) - // This tile actor no longer associated with the current shared landscape - continue; - - ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); - if (!CurrentInfo) - continue; - - int32 InputMinX = 0; - int32 InputMinY = 0; - int32 InputMaxX = 0; - int32 InputMaxY = 0; - if (!LandscapeExtent.bIsCached) - { - PopulateLandscapeExtents(LandscapeExtent, CurrentInfo); - } - - if (!LandscapeExtent.bIsCached) - { - HOUDINI_LOG_WARNING(TEXT("Warning: Could not determine landscape extents. Cannot re-use input landscape actor.")); - continue; - } - - InputMinX = LandscapeExtent.MinY; - InputMinY = LandscapeExtent.MinY; - InputMaxX = LandscapeExtent.MaxX; - InputMaxY = LandscapeExtent.MaxY; - - // If the full size matches, we'll update that input landscape - bool SizeMatch = false; - if ((InputMaxX - InputMinX + 1) == UnrealTileSizeX && (InputMaxY - InputMinY + 1) == UnrealTileSizeY) - SizeMatch = true; - - // HF and landscape don't match, try another one - if (!SizeMatch) - continue; - - // Replace FoundLandscape by that input landscape - FoundLandscapeProxy = CurrentInputLandscape; - - // We've found a valid input landscape, remove it from the input array so we don't try to update it multiple times - InputLandscapesToUpdate.RemoveAt(nIdx); - break; - } - - if (!FoundLandscapeProxy) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Could not find input landscape to update. Searching output objects...")); - - // Try to see if we can reuse one of our previous output landscape. - // Keep track of the previous cook's landscapes - TMap& OldOutputObjects = InOutput->GetOutputObjects(); - for (auto& CurrentLandscape : OldOutputObjects) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentLandscape.Value.OutputObject); - if (!LandscapePtr) - continue; - - FoundLandscapeProxy = LandscapePtr->GetRawPtr(); - if (!FoundLandscapeProxy) - { - // We may need to manually load the object - //OldLandscapeProxy = LandscapePtr->GetSoftPtr().LoadSynchronous(); - FoundLandscapeProxy = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); - } - - if (!IsValid(FoundLandscapeProxy)) - continue; - - // We need to make sure that this landscape is not one of our input landscape - // This would happen if we were previously updating it, but just turned the option off - // In that case, the landscape would be in both our inputs and outputs, - // but with the "Update Input Data" option off - if (InAllInputLandscapes.Contains(FoundLandscapeProxy)) - { - FoundLandscapeProxy = nullptr; - continue; - } - - if (SharedLandscapeActor && FoundLandscapeProxy->GetLandscapeActor() != SharedLandscapeActor) - { - // This landscape proxy is no longer part of the shared landscape. - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Output landscape proxy is no longer part of the landscape. Skipping")); - FoundLandscapeProxy = nullptr; - continue; - } - - // If we found a possible candidate, make sure that its size matches ours as we can only update a landscape tile of the same size - if (!IsLandscapeTileCompatible( - FoundLandscapeProxy, - UnrealTileSizeX-1, - UnrealTileSizeY-1, - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection)) - { - FoundLandscapeProxy = nullptr; - continue; - } - - if (SharedLandscapeActor) - { - if (FoundLandscapeProxy->GetLandscapeGuid() != SharedLandscapeActor->GetLandscapeGuid()) - { - FoundLandscapeProxy = nullptr; - continue; - } - } - - // TODO: we probably need to do some more checks with tiled landscapes as well? - - // We found a valid Candidate! - if (FoundLandscapeProxy) - { - break; - } - } - } - - - - if (IsValid(FoundLandscapeProxy)) - { - TileActor = FoundLandscapeProxy; - if (TileActor->GetName() != LandscapeTileActorName) - { - // Ensure the TileActor is named correctly - FHoudiniEngineUtils::SafeRenameActor(TileActor, LandscapeTileActorName); - } - } - } - - // NOTE: We don't need to delete old landscape tiles (FoundActor != TileActor) here. That is an old - // output that should get cleaned up automatically. - - if (IsValid(TileActor)) - { - check(!(TileActor->IsPendingKill())); - - // ---------------------------------------------------- - // Check landscape compatibility - // ---------------------------------------------------- - - bool bIsCompatible = IsLandscapeTileCompatible( - TileActor, - UnrealTileSizeX-1, - UnrealTileSizeY-1, - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection); - - bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(TileActor, TileActorType); - - if (!bIsCompatible) - { - // Can't reuse this tile actor since the landscape dimensions doesn't match or the actor type has changed. - if (TileActor->IsA()) - { - // This landscape tile needs to be unregistered from the landscape info. - ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); - if (IsValid(LandscapeInfo)) - { - LandscapeInfo->UnregisterActor(TileActor); - } - } - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Incompatible tile actor. Destroying: %s"), *(TileActor->GetPathName())); - TileActor->Destroy(); - TileActor = nullptr; - } - else - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape tile is compatible: %s"), *(TileActor->GetPathName())); - } - } - - // ---------------------------------------------------- - // Create or update landscape / tile. - // ---------------------------------------------------- - // Note that a single heightfield generated in Houdini can be treated - // as either a landscape tile (LandscapeStreamingProxy) or a standalone - // landscape (ALandscape). This determination is made purely from user specified - // attributes. No "clever logic" in here, please! - - ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; - ALandscape* CachedLandscapeActor = nullptr; - ULandscapeInfo *LandscapeInfo; - -#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Loc: %d, %d"), TileLoc.X, TileLoc.Y); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Size: %d, %d"), UnrealTileSizeX, UnrealTileSizeY); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Quads/Section: %d"), NumQuadsPerLandscapeSection); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Sections/Component: %d"), NumSectionPerLandscapeComponent); -#endif - - if (!TileActor) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Creating new tile actor: %s"), *(LandscapeTileActorName)); - // Create a new Landscape tile in the TileWorld - TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( - IntHeightData, LayerInfos, TileTransform, TileLoc, - UnrealTileSizeX, UnrealTileSizeY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, - LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, - LandscapeTileActorName, - TileActorType, - SharedLandscapeActor, - TileWorld, - TileLevel, - InPackageParams); - - if (!TileActor || !TileActor->IsValidLowLevel()) - return false; - - LandscapeInfo = TileActor->GetLandscapeInfo(); - - bCreatedTileActor = true; - bTileLandscapeMaterialChanged = true; - bTileLandscapeHoleMaterialChanged = true; - bTilePhysicalMaterialChanged = true; - bHeightLayerDataChanged = true; - bCustomLayerDataChanged = true; - } - else - { - LandscapeInfo = TileActor->GetLandscapeInfo(); - - // Always update the transform, even if the HGPO transform hasn't changed, - // If we change the number of tiles, or switch from outputting single tile to multiple, - // then its fairly likely that the unreal transform has changed even if the - // Houdini Transform remained the same - bool bUpdateTransform = !TileActor->GetActorTransform().Equals(TileTransform); - - // Update existing landscape / tile - if (SharedLandscapeActor && TileActorType == LandscapeActorType::LandscapeStreamingProxy) - { - TileActor->FixupSharedData(SharedLandscapeActor); - if (bUpdateTransform) - { - TileActor->SetAbsoluteSectionBase(TileLoc); - LandscapeInfo->FixupProxiesTransform(); - LandscapeInfo->RecreateLandscapeInfo(InWorld,true); - } - - // This is a tile with a shared landscape. - // Check whether the LandscapeActor changed. If so, update the proxy's shared properties. - CachedStreamingProxyActor = Cast(TileActor); - if (SharedLandscapeActor) - { - if (CachedStreamingProxyActor) - bModifiedLandscapeActor = CachedStreamingProxyActor->LandscapeActor != SharedLandscapeActor; - else - bModifiedLandscapeActor = true; - - if (bModifiedLandscapeActor) - { - CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; - // We need to force a state update through PostEditChangeProperty here in order to initialize - // since we're about to perform additional data updates on this tile. - DoPostEditChangeProperty(CachedStreamingProxyActor, "LandscapeActor"); - } - } - else - { - CachedStreamingProxyActor->LandscapeActor = nullptr; - } - - } - else - { - // This is a standalone tile / landscape actor. - if (bUpdateTransform) - { - TileActor->SetActorRelativeTransform(TileTransform); - TileActor->SetAbsoluteSectionBase(TileLoc); - } - CachedLandscapeActor = Cast(TileActor); - } - - ULandscapeInfo* PreviousInfo = TileActor->GetLandscapeInfo(); - if (!PreviousInfo) - return false; - - FIntRect BoundingRect = TileActor->GetBoundingRect(); - FIntPoint SectionOffset = TileActor->GetSectionBaseOffset(); - - // Landscape region to update - const int32 MinX = TileLoc.X; - const int32 MaxX = TileLoc.X + UnrealTileSizeX - 1; - const int32 MinY = TileLoc.Y; - const int32 MaxY = TileLoc.Y + UnrealTileSizeY - 1; - - // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. - // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools - // though the *Accessors do additional things like update normals and foliage. - - // Update height if it has been changed. - if (Heightfield->bHasGeoChanged) - { - // It is important to update the heightmap through the this since it will properly - // update normals and foliage. - FHeightmapAccessor HeightmapAccessor(LandscapeInfo); - HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); - - bHeightLayerDataChanged = true; - } - - // Update the layers on the landscape. - for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) - { - if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. - FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); - AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - } - else - { - FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); - AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - } - - bCustomLayerDataChanged = true; - } - - bModifiedLandscapeActor = true; - } - - // ---------------------------------------------------- - // Update tile materials - // ---------------------------------------------------- - // TODO: These material updates can possibly be skipped if we have already performed this - // check on a SharedLandscape. - bTileLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (TileActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bTileLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (TileActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); - - if (bTileLandscapeMaterialChanged) - TileActor->LandscapeMaterial = LandscapeMaterial; - - if (bTileLandscapeHoleMaterialChanged) - TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - - bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; - if (bTilePhysicalMaterialChanged) - { - DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); - TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - //TileActor->ChangedPhysMaterial(); - } - - // ---------------------------------------------------- - // Apply actor tags - // ---------------------------------------------------- - - // See if we have unreal_tag_ attribute - TArray Tags; - if (TileActor && FHoudiniEngineUtils::GetUnrealTagAttributes(GeoId, PartId, Tags)) - { - TileActor->Tags = Tags; - } - - // ---------------------------------------------------- - // Update actor states based on data updates - // ---------------------------------------------------- - // Based on ALandscape and ALandscapeStreamingProxy PostEditChangeProperty() implementations, - // effect appropriate state updates based on the property updates that was performed in - // the above code. - - if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) - { - check(SharedLandscapeActor); - DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - } - - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - { - check(TileActor); - // Tile material changes are only processed if it wasn't already done for a shared - // landscape since the shared landscape should have already propagated the changes to associated proxies. - DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); - } - - if (bSharedPhysicalMaterialChanged) - { - check(SharedLandscapeActor); - DoPostEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); - } - - if (bTilePhysicalMaterialChanged) - { - check(TileActor); - DoPostEditChangeProperty(TileActor, "DefaultPhysMaterial"); - } - - if (bModifiedSharedLandscapeActor) - { - SharedLandscapeActor->PostEditChange(); - } - - if (bModifiedLandscapeActor) - { - TileActor->PostEditChange(); - } - - { - FLandscapeEditDataInterface LandscapeEdit(TileActor->GetLandscapeInfo()); - LandscapeEdit.RecalculateNormals(); - } - - if (LandscapeInfo) - { - LandscapeInfo->RecreateLandscapeInfo(InWorld, true); - LandscapeInfo->RecreateCollisionComponents(); - } - - { - // Update UProperties - - // Apply detail attributes to both the Shared Landscape and the Landscape Tile actor - TArray PropertyAttributes; - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - GeoId, PartId, - true, - INDEX_NONE, INDEX_NONE, INDEX_NONE, - PropertyAttributes)) - { - if (IsValid(TileActor)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); - } - if (IsValid(SharedLandscapeActor)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(SharedLandscapeActor, PropertyAttributes); - } - } - - // Apply point attributes only to the Shared Landscape and the Landscape Tile actor - PropertyAttributes.Empty(); - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - GeoId, PartId, - false, - 0, INDEX_NONE, 0, - PropertyAttributes)) - { - if (IsValid(TileActor)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); - } - } - } - - // Add objects to the HAC output. - SetLandscapeActorAsOutput( - InOutput, - InAllInputLandscapes, - OutputAttributes, - OutputTokens, - SharedLandscapeActor, - SharedLandscapeActorParent, - bCreatedSharedLandscape, - HeightfieldIdentifier, - TileActor, - InPackageParams.PackageMode); - -#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) - if (LandscapeInfo) - { - int32 MinX, MinY, MaxX, MaxY; - LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape extent: %d, %d -> %d, %d"), MinX, MinY, MaxX, MaxY); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Cached extent: %d, %d -> %d, %d"), LandscapeExtent.MinX, LandscapeExtent.MinY, LandscapeExtent.MaxX, LandscapeExtent.MaxY); - } - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Ending with num of output objects: %d"), InOutput->GetOutputObjects().Num()); -#endif - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( - const ULandscapeInfo* LandscapeInfo, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection - ) -{ - if (!IsValid(LandscapeInfo)) - return false; - - - if (LandscapeInfo->ComponentNumSubsections != InNumSectionsPerComponent) - return false; - - if (LandscapeInfo->SubsectionSizeQuads != InNumQuadsPerSection) - return false; - - int32 MinX, MinY, MaxX, MaxY; - if (LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - { - const int32 NumComponentsX = (MaxX - MinX) / (InNumQuadsPerSection*InNumSectionsPerComponent); - const int32 NumComponentsY = (MaxY - MinY) / (InNumQuadsPerSection*InNumSectionsPerComponent); - } - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsLandscapeTileCompatible( - const ALandscapeProxy* TileActor, - const int32 InTileSizeX, - const int32 InTileSizeY, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection -) -{ - check(TileActor); - - // NOTE: We can't compare landscape extents here since the Houdini only knows about the size for single tile. - // and LandscapeInfo will only return extents for the *loaded* landscape tiles. - - // TODO: Add more robust checks to determine landscape compatibility. - - if (!IsLandscapeInfoCompatible(TileActor->GetLandscapeInfo(), InNumSectionsPerComponent, InNumQuadsPerSection)) - return false; - - const FIntRect Bounds = TileActor->GetBoundingRect(); - const FIntPoint Size = Bounds.Size(); - if (Size.X != InTileSizeX && Size.Y != InTileSizeY) - return false; - - return true; -} - - -bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, LandscapeActorType ActorType) -{ - if (!IsValid(Actor)) - return false; - - switch (ActorType) - { - case LandscapeActorType::LandscapeActor: - return Actor->IsA(); - break; - case LandscapeActorType::LandscapeStreamingProxy: - return Actor->IsA(); - break; - default: - break; - } - - return false; -} - -bool FHoudiniLandscapeTranslator::PopulateLandscapeExtents(FHoudiniLandscapeExtent& Extent, - const ULandscapeInfo* LandscapeInfo) -{ - if (LandscapeInfo->GetLandscapeExtent(Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY)) - { - Extent.ExtentsX = Extent.MaxX - Extent.MinX; - Extent.ExtentsY = Extent.MaxY - Extent.MinY; - Extent.bIsCached = true; - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Cached extent: %d, %d -> %d, %d"), Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY); - - return true; - } - return false; -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage, - const EPackageMode& InPackageMode) -{ - if (InPackageMode == EPackageMode::Bake) - return FindExistingLandscapeActor_Bake(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); - else - return FindExistingLandscapeActor_Temp(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - bCreatedPackage = false; - - // // Locate landscape proxy actor when running in baked mode - // AActor* FoundActor = nullptr; - ALandscapeProxy* OutActor = nullptr; - // OutActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); - // // OutActor = FHoudiniEngineUtils::FindActorInWorld(InWorld, FName(InActorName)); - // if (FoundActor && FoundActor != OutActor) - // FoundActor->Destroy(); // nuke it! - // - // if (OutActor) - // { - // // TODO: make sure that the found is actor is actually assigned to the level defined by package path. - // // If the found actor is not from that level, it should be moved there. - // - // OutWorld = OutActor->GetWorld(); - // OutLevel = OutActor->GetLevel(); - // } - // else - { - // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. - bool bActorInWorld = false; - const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - InWorld, - InPackagePath, - true, - OutWorld, - OutLevel, - bCreatedPackage, - bActorInWorld); - - if (!bResult) - { - return nullptr; - } - - // if (!bActorInWorld) - // { - // // The OutLevel is not present in the current world which means we might - // // still find the tile actor in OutWorld. - OutActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); - // } - } - - return OutActor; -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - ALandscapeProxy* OutActor = nullptr; - FString ActorName = InActorName + TEXT("_Temp"); - TMap& PrevCookObjects = InOutput->GetOutputObjects(); - - OutWorld = InWorld; - OutLevel = InWorld->PersistentLevel; - - bCreatedPackage = false; - - // Find Landscape proxy for output when running in Temp mode - for(auto& PrevObject : PrevCookObjects) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(PrevObject.Value.OutputObject); - if (!LandscapePtr) - continue; - - OutActor = LandscapePtr->GetRawPtr(); - if (!OutActor) - { - // We may need to manually load the object - OutActor = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); - } - - if (!OutActor) - continue; - - // If we were updating the input landscape before, but arent anymore, - // we could still find it here in the output, ignore them now as we're only looking for previous output - if (ValidLandscapes.Contains(OutActor)) - continue; - - if (OutActor->GetName() != ActorName) - // This is not the droid we're looking for - continue; - - if (OutActor->IsPendingKill()) - { - FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + "_pendingkill"); - continue; - } - - // If we found a possible candidate, make sure that its size matches ours - // as we can only update a landscape of the same size - ULandscapeInfo* PreviousInfo = OutActor->GetLandscapeInfo(); - if (PreviousInfo) - { - int32 PrevMinX = 0; - int32 PrevMinY = 0; - int32 PrevMaxX = 0; - int32 PrevMaxY = 0; - PreviousInfo->GetLandscapeExtent(PrevMinX, PrevMinY, PrevMaxX, PrevMaxY); - - if ((PrevMaxX - PrevMinX + 1) == UnrealLandscapeSizeX && (PrevMaxY - PrevMinY + 1) == UnrealLandscapeSizeY) - { - // The size matches, we can reuse the old landscape. - break; - } - else - { - // We can't reuse this actor. The dimensions does not match. - // We need to rename this actor in order to create a new one with the specified name. - FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + TEXT("_old") ); - OutActor = nullptr; - } - } - } - - return OutActor; -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor, - const EPackageMode InPackageMode) -{ - if (InPackageMode == EPackageMode::Bake) - return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); - else - return SetLandscapeActorAsOutput_Temp(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor) -{ - // We are in bake mode. No outputs to register / add here. - // Do nothing, for now. -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedSharedLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor) -{ - // The main landscape is a special case here. It cannot be registered with the - // output object here, since it is possibly shared by *multiple* outputs so - // we have to deal with the attached and cleanup of the actor manually. - if (bCreatedSharedLandscape && IsValid(SharedLandscapeActorParent)) - { - AttachActorToHAC(InOutput, SharedLandscapeActor); - } - - // TODO: The OutputObject cleanup being performed here should really be part of - // the output object itself (or at the very least be encapsulated in a reusable - // static function somewhere) so that individual output objects can be cleaned - // when necessary without having to duplicate code when its needed. - - // Cleanup any stale output objects - TMap& Outputs = InOutput->GetOutputObjects(); - TArray StaleOutputs; - for (auto& Elem : Outputs) - { - UHoudiniLandscapePtr* LandscapePtr = nullptr; - bool bIsStale = false; - - if (!(Elem.Key == Identifier)) - { - // Identifiers doesn't match so this is definitely a stale output. - StaleOutputs.Add(Elem.Key); - bIsStale = true; - } - - LandscapePtr = Cast(Elem.Value.OutputObject); - if (LandscapePtr) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - - if (LandscapeProxy) - { - // We shouldn't destroy any input landscape, - // or the landscape that we are currently trying to set as output.. - if (!InAllInputLandscapes.Contains(LandscapeProxy) && (LandscapeProxy != LandscapeActor)) - { - // This landscape proxy either doesn't match the landscape identifier - // or it doesn't match the actor we're about to output. Either way, - // get rid of it. - LandscapeProxy->Destroy(); - } - } - } - - if (bIsStale) - { - Elem.Value.OutputObject = nullptr; - } - } - - for (FHoudiniOutputObjectIdentifier& StaleOutput : StaleOutputs) - { - Outputs.Remove(StaleOutput); - } - - - // Send a landscape pointer back to the Output Object for this landscape tile. - FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); - UHoudiniLandscapePtr* LandscapePtr = NewObject(InOutput); - LandscapePtr->SetSoftPtr(LandscapeActor); - OutputObj.OutputObject = LandscapePtr; - OutputObj.CachedAttributes = OutputAttributes; - OutputObj.CachedTokens = OutputTokens; -} - - -bool -FHoudiniLandscapeTranslator::AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor) -{ - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (IsValid(HAC)) - { - InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); - - return true; - } - return false; -} - - -FString -FHoudiniLandscapeTranslator::GetActorNameSuffix(const EPackageMode& InPackageMode) -{ - if(InPackageMode == EPackageMode::CookToTemp) - return "_Temp"; - else - return FString(); -} - -void -FHoudiniLandscapeTranslator::DoPreEditChangeProperty(UObject* Obj, FName PropertyName) -{ - Obj->PreEditChange(FindFProperty(Obj->GetClass(), PropertyName)); -} - -void -FHoudiniLandscapeTranslator::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) -{ - FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); - Obj->PostEditChangeProperty(Evt); -} - -bool -FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( - const TArray< float >& HeightfieldFloatValues, - const FHoudiniVolumeInfo& HeightfieldVolumeInfo, - const int32& FinalXSize, const int32& FinalYSize, - float FloatMin, float FloatMax, - TArray< uint16 >& IntHeightData, - FTransform& LandscapeTransform, - const bool& NoResize) -{ - IntHeightData.Empty(); - LandscapeTransform.SetIdentity(); - - // HF sizes needs an X/Y swap - // NOPE.. not anymore - int32 HoudiniXSize = HeightfieldVolumeInfo.YLength; - int32 HoudiniYSize = HeightfieldVolumeInfo.XLength; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - // Test for potential special cases... - // Just print a warning for now - if (HeightfieldVolumeInfo.MinX != 0) - HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min X is not zero.")); - - if (HeightfieldVolumeInfo.MinY != 0) - HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min Y is not zero.")); - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to uint16 using doubles to get the maximum precision during the conversion - //-------------------------------------------------------------------------------------------------- - - FTransform CurrentVolumeTransform = HeightfieldVolumeInfo.Transform; - - // The ZRange in Houdini (in m) - double MeterZRange = (double)(FloatMax - FloatMin); - - // The corresponding unreal digit range (as unreal uses uint16, max is 65535) - // We may want to not use the full range in order to be able to sculpt the landscape past the min/max values after. - const double dUINT16_MAX = (double)UINT16_MAX; - double DigitZRange = 49152.0; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution) - DigitZRange = dUINT16_MAX - 1.0; - - // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down - double DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); - - // The factor used to convert from Houdini's ZRange to the desired digit range - double ZSpacing = (MeterZRange != 0.0) ? (DigitZRange / MeterZRange) : 0.0; - - // Changes these values if the user wants to loose a lot of precision - // just to keep the same transform as the landscape input - bool bUseDefaultUE4Scaling = false; - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) - bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; - - if (bUseDefaultUE4Scaling) - { - //Check that our values are compatible with UE4's default scale values - if (FloatMin < -256.0f || FloatMin > 256.0f || FloatMax < -256.0f || FloatMax > 256.0f) - { - // Warn the user that the landscape conversion will have issues - // invite him to change that setting - HOUDINI_LOG_WARNING( - TEXT("The heightfield's min and max height values are too large for being used with the \"Use Default UE4 scaling\" option.\n \ - The generated Heightfield will likely be incorrectly converted to landscape unless you disable that option in the project settings and recook the asset.")); - } - - DigitZRange = dUINT16_MAX - 1.0; - DigitCenterOffset = 0; - - // Default unreal landscape scaling is -256m:256m at Scale = 100, - // We need to apply the scale back, and swap Y/Z axis - FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f; - FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f; - MeterZRange = (double)(FloatMax - FloatMin); - - ZSpacing = ((double)DigitZRange) / MeterZRange; - } - - // Converting the data from Houdini to Unreal - // For correct orientation in unreal, the point matrix has to be transposed. - IntHeightData.SetNumUninitialized(SizeInPoints); - - int32 nUnreal = 0; - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y - int32 nHoudini = nY + nX * HoudiniYSize; - - // Get the double values in [0 - ZRange] - double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; - - // Then convert it to [0 - DesiredRange] and center it - DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; - IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); - } - } - - //-------------------------------------------------------------------------------------------------- - // 2. Resample / Pad the int data so that if fits unreal size requirements - //-------------------------------------------------------------------------------------------------- - - // UE has specific size requirements for landscape, - // so we might need to pad/resample the heightfield data - FVector LandscapeResizeFactor = FVector::OneVector; - FVector LandscapePositionOffsetInPixels = FVector::ZeroVector; - if (!NoResize) - { - // Try to resize the data - if (!FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( - IntHeightData, - HoudiniXSize, HoudiniYSize, FinalXSize, FinalYSize, - LandscapeResizeFactor, LandscapePositionOffsetInPixels)) - return false; - } - - //-------------------------------------------------------------------------------------------------- - // 3. Calculating the proper transform for the landscape to be sized and positionned properly - //-------------------------------------------------------------------------------------------------- - - // Scale: - // Calculating the equivalent scale to match Houdini's Terrain Size in Unreal - FVector LandscapeScale; - - // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing - // Swap Y/Z axis from H to UE - LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; - LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Z * 2.0f; - - // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini - // Unreal has a default Z range is 512m for a scale of a 100% - LandscapeScale.Z = (float)((double)(dUINT16_MAX / DigitZRange) * MeterZRange / 512.0); - if (bUseDefaultUE4Scaling) - { - // Swap Y/Z axis from H to UE - LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Y * 2.0f; - } - LandscapeScale *= 100.f; - - // If the data was resized and not expanded, we need to modify the landscape's scale - LandscapeScale *= LandscapeResizeFactor; - - // Don't allow a zero scale, as this results in divide by 0 operations in FMatrix::InverseFast in the landscape component. - if (FMath::IsNearlyZero(LandscapeScale.Z)) - LandscapeScale.Z = 1.0f; - - // We'll use the position from Houdini, but we will need to offset the Z Position to center the - // values properly as the data has been offset by the conversion to uint16 - FVector LandscapePosition = CurrentVolumeTransform.GetLocation(); - //LandscapePosition.Z = 0.0f; - - // We need to calculate the position offset so that Houdini and Unreal have the same Zero position - // In Unreal, zero has a height value of 32768. - // These values are then divided by 128 internally, and then multiplied by the Landscape's Z scale - // ( DIGIT - 32768 ) / 128 * ZScale = ZOffset - - // We need the Digit (Unreal) value of Houdini's zero for the scale calculation - // ( float and int32 are used for this because 0 might be out of the landscape Z range! - // when using the full range, this would cause an overflow for a uint16!! ) - float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); - float ZOffset = -(HoudiniZeroValueInDigit - 32768.0f) / 128.0f * LandscapeScale.Z; - - LandscapePosition.Z += ZOffset; - - // If we have padded the data when resizing the landscape, we need to offset the position because of - // the added values on the topLeft Corner of the Landscape - if (LandscapePositionOffsetInPixels != FVector::ZeroVector) - { - FVector LandscapeOffset = LandscapePositionOffsetInPixels * LandscapeScale; - LandscapeOffset.Z = 0.0f; - - LandscapePosition += LandscapeOffset; - } - - /* - FTransform TempTransform; - TempTransform.SetIdentity(); - { - // Houdini Pivot (center of the Landscape) - FVector HoudiniPivot = FVector((FinalXSize-1) * 100.0f / 2.0f, (FinalYSize-1) * 100.0f / 2.0f, 0.0f); - - // Center the landscape - FVector CenterLocation = LandscapePosition - HoudiniPivot; - - // Rotate the vector using the H rotation - // We need to compensate for the "default" HF Transform - FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); - Rotator.Yaw -= 90.0f; - Rotator.Roll += 90.0f; - FVector RotatedLocation = Rotator.RotateVector(CenterLocation); - - FQuat LandscapeRotation = FQuat(Rotator) * FQuat::Identity; - - // Return to previous origin - FVector Uncentered = RotatedLocation + HoudiniPivot; - TempTransform = FTransform(LandscapeRotation, Uncentered, LandscapeScale); - } - - LandscapeTransform = TempTransform; - */ - - // We can now set the Landscape position - LandscapeTransform.SetLocation(LandscapePosition); - LandscapeTransform.SetScale3D(LandscapeScale); - - // Rotate the vector using the H rotation - FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); - // We need to compensate for the "default" HF Transform - Rotator.Yaw -= 90.0f; - Rotator.Roll += 90.0f; - - // Only rotate if the rotator is far from zero - if(!Rotator.IsNearlyZero()) - LandscapeTransform.SetRotation(FQuat(Rotator)); - - return true; -} - -template -TArray ResampleData(const TArray& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight) -{ - TArray Result; - Result.Empty(NewWidth * NewHeight); - Result.AddUninitialized(NewWidth * NewHeight); - - const float XScale = (float)(OldWidth - 1) / (NewWidth - 1); - const float YScale = (float)(OldHeight - 1) / (NewHeight - 1); - for (int32 Y = 0; Y < NewHeight; ++Y) - { - for (int32 X = 0; X < NewWidth; ++X) - { - const float OldY = Y * YScale; - const float OldX = X * XScale; - const int32 X0 = FMath::FloorToInt(OldX); - const int32 X1 = FMath::Min(FMath::FloorToInt(OldX) + 1, OldWidth - 1); - const int32 Y0 = FMath::FloorToInt(OldY); - const int32 Y1 = FMath::Min(FMath::FloorToInt(OldY) + 1, OldHeight - 1); - const T& Original00 = Data[Y0 * OldWidth + X0]; - const T& Original10 = Data[Y0 * OldWidth + X1]; - const T& Original01 = Data[Y1 * OldWidth + X0]; - const T& Original11 = Data[Y1 * OldWidth + X1]; - Result[Y * NewWidth + X] = FMath::BiLerp(Original00, Original10, Original01, Original11, FMath::Fractional(OldX), FMath::Fractional(OldY)); - } - } - - return Result; -} - -template -void ExpandData(T* OutData, const T* InData, - int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, - int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY) -{ - const int32 OldWidth = OldMaxX - OldMinX + 1; - const int32 OldHeight = OldMaxY - OldMinY + 1; - const int32 NewWidth = NewMaxX - NewMinX + 1; - const int32 NewHeight = NewMaxY - NewMinY + 1; - const int32 OffsetX = NewMinX - OldMinX; - const int32 OffsetY = NewMinY - OldMinY; - - for (int32 Y = 0; Y < NewHeight; ++Y) - { - const int32 OldY = FMath::Clamp(Y + OffsetY, 0, OldHeight - 1); - - // Pad anything to the left - const T PadLeft = InData[OldY * OldWidth + 0]; - for (int32 X = 0; X < -OffsetX; ++X) - { - OutData[Y * NewWidth + X] = PadLeft; - } - - // Copy one row of the old data - { - const int32 X = FMath::Max(0, -OffsetX); - const int32 OldX = FMath::Clamp(X + OffsetX, 0, OldWidth - 1); - FMemory::Memcpy(&OutData[Y * NewWidth + X], &InData[OldY * OldWidth + OldX], FMath::Min(OldWidth, NewWidth) * sizeof(T)); - } - - const T PadRight = InData[OldY * OldWidth + OldWidth - 1]; - for (int32 X = -OffsetX + OldWidth; X < NewWidth; ++X) - { - OutData[Y * NewWidth + X] = PadRight; - } - } -} - -template -TArray ExpandData(const TArray& Data, - int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, - int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY, - int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr) -{ - const int32 NewWidth = NewMaxX - NewMinX + 1; - const int32 NewHeight = NewMaxY - NewMinY + 1; - - TArray Result; - Result.Empty(NewWidth * NewHeight); - Result.AddUninitialized(NewWidth * NewHeight); - - ExpandData(Result.GetData(), Data.GetData(), - OldMinX, OldMinY, OldMaxX, OldMaxY, - NewMinX, NewMinY, NewMaxX, NewMaxY); - - // Return the padding so we can offset the terrain position after - if (PadOffsetX) - *PadOffsetX = NewMinX; - - if (PadOffsetY) - *PadOffsetY = NewMinY; - - return Result; -} - -bool -FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( - TArray& HeightData, - const int32& SizeX, const int32& SizeY, - const int32& NewSizeX, const int32& NewSizeY, - FVector& LandscapeResizeFactor, - FVector& LandscapePositionOffset) -{ - LandscapeResizeFactor = FVector::OneVector; - LandscapePositionOffset = FVector::ZeroVector; - - if (HeightData.Num() <= 4) - return false; - - if ((SizeX < 2) || (SizeY < 2)) - return false; - - // No need to resize anything - if (SizeX == NewSizeX && SizeY == NewSizeY) - return true; - - // Always resample, for now. We may enable padding functionality again at some point via - // a plugin setting. - bool bForceResample = true; - bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); - - TArray NewData; - if (!bResample) - { - // Expanding the data by padding - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - - const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; - const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; - - // Store the offset in pixel due to the padding - int32 PadOffsetX = 0; - int32 PadOffsetY = 0; - - // Expanding the Data - NewData = ExpandData( - HeightData, 0, 0, SizeX - 1, SizeY - 1, - -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1, - &PadOffsetX, &PadOffsetY); - - // We will need to offset the landscape position due to the value added by the padding - LandscapePositionOffset.X = (float)PadOffsetX; - LandscapePositionOffset.Y = (float)PadOffsetY; - - // Notify the user that the data was padded - HOUDINI_LOG_WARNING( - TEXT("Landscape data was padded from ( %d x %d ) to ( %d x %d )."), - SizeX, SizeY, NewSizeX, NewSizeY); - } - else - { - // Resampling the data - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - NewData = ResampleData(HeightData, SizeX, SizeY, NewSizeX, NewSizeY); - - // The landscape has been resized, we'll need to take that into account when sizing it - LandscapeResizeFactor.X = (float)SizeX / (float)NewSizeX; - LandscapeResizeFactor.Y = (float)SizeY / (float)NewSizeY; - LandscapeResizeFactor.Z = 1.0f; - - // Notify the user if the heightfield data was resized - HOUDINI_LOG_WARNING( - TEXT("Landscape data was resized from ( %d x %d ) to ( %d x %d )."), - SizeX, SizeY, NewSizeX, NewSizeY); - } - - // Replaces Old data with the new one - HeightData = NewData; - - return true; -} - - -bool -FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - const int32& HoudiniSizeX, const int32& HoudiniSizeY, - int32& UnrealSizeX, int32& UnrealSizeY, - int32& NumSectionsPerComponent, int32& NumQuadsPerSection) -{ - if ((HoudiniSizeX < 2) || (HoudiniSizeY < 2)) - return false; - - NumSectionsPerComponent = 1; - NumQuadsPerSection = 1; - UnrealSizeX = -1; - UnrealSizeY = -1; - - // Unreal's default sizes - int32 SectionSizes[] = { 7, 15, 31, 63, 127, 255 }; - int32 NumSections[] = { 1, 2 }; - - // Component count used to calculate the final size of the landscape - int32 ComponentsCountX = 1; - int32 ComponentsCountY = 1; - - // Lambda for clamping the number of component in X/Y - auto ClampLandscapeSize = [&]() - { - // Max size is either whole components below 8192 verts, or 32 components - ComponentsCountX = FMath::Clamp(ComponentsCountX, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); - ComponentsCountY = FMath::Clamp(ComponentsCountY, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); - }; - - // Try to find a section size and number of sections that exactly matches the dimensions of the heightfield - bool bFoundMatch = false; - for (int32 SectionSizesIdx = UE_ARRAY_COUNT(SectionSizes) - 1; SectionSizesIdx >= 0; SectionSizesIdx--) - { - for (int32 NumSectionsIdx = UE_ARRAY_COUNT(NumSections) - 1; NumSectionsIdx >= 0; NumSectionsIdx--) - { - int32 ss = SectionSizes[SectionSizesIdx]; - int32 ns = NumSections[NumSectionsIdx]; - - if (((HoudiniSizeX - 1) % (ss * ns)) == 0 && ((HoudiniSizeX - 1) / (ss * ns)) <= 32 && - ((HoudiniSizeY - 1) % (ss * ns)) == 0 && ((HoudiniSizeY - 1) / (ss * ns)) <= 32) - { - bFoundMatch = true; - NumQuadsPerSection = ss; - NumSectionsPerComponent = ns; - ComponentsCountX = (HoudiniSizeX - 1) / (ss * ns); - ComponentsCountY = (HoudiniSizeY - 1) / (ss * ns); - ClampLandscapeSize(); - break; - } - } - if (bFoundMatch) - { - break; - } - } - - if (!bFoundMatch) - { - // if there was no exact match, try increasing the section size until we encompass the whole heightmap - const int32 CurrentSectionSize = NumQuadsPerSection; - const int32 CurrentNumSections = NumSectionsPerComponent; - for (int32 SectionSizesIdx = 0; SectionSizesIdx < UE_ARRAY_COUNT(SectionSizes); SectionSizesIdx++) - { - if (SectionSizes[SectionSizesIdx] < CurrentSectionSize) - { - continue; - } - - const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); - const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); - if (ComponentsX <= 32 && ComponentsY <= 32) - { - bFoundMatch = true; - NumQuadsPerSection = SectionSizes[SectionSizesIdx]; - ComponentsCountX = ComponentsX; - ComponentsCountY = ComponentsY; - ClampLandscapeSize(); - break; - } - } - } - - if (!bFoundMatch) - { - // if the heightmap is very large, fall back to using the largest values we support - const int32 MaxSectionSize = SectionSizes[UE_ARRAY_COUNT(SectionSizes) - 1]; - const int32 MaxNumSubSections = NumSections[UE_ARRAY_COUNT(NumSections) - 1]; - const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), MaxSectionSize * MaxNumSubSections); - const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), MaxSectionSize * MaxNumSubSections); - - bFoundMatch = true; - NumQuadsPerSection = MaxSectionSize; - NumSectionsPerComponent = MaxNumSubSections; - ComponentsCountX = ComponentsX; - ComponentsCountY = ComponentsY; - ClampLandscapeSize(); - } - - if (!bFoundMatch) - { - // Using default size just to not crash.. - UnrealSizeX = 512; - UnrealSizeY = 512; - NumSectionsPerComponent = 1; - NumQuadsPerSection = 511; - ComponentsCountX = 1; - ComponentsCountY = 1; - } - else - { - // Calculating the desired size - int32 QuadsPerComponent = NumSectionsPerComponent * NumQuadsPerSection; - - UnrealSizeX = ComponentsCountX * QuadsPerComponent + 1; - UnrealSizeY = ComponentsCountY * QuadsPerComponent + 1; - } - - return bFoundMatch; -} - -const FHoudiniGeoPartObject* -FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return nullptr; - - if (InOutput->GetHoudiniGeoPartObjects().Num() < 1) - return nullptr; - - for (const FHoudiniGeoPartObject& HGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Volume) - continue; - - FHoudiniVolumeInfo CurVolumeInfo = HGPO.VolumeInfo; - if (!CurVolumeInfo.Name.Contains("height")) - continue; - - // We're only handling single values for now - if (CurVolumeInfo.TupleSize != 1) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume has an invalide tuple size!")); - return nullptr; - } - - // Terrains always have a ZSize of 1. - if (CurVolumeInfo.ZLength != 1) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume's z length is not 1!")); - return nullptr; - } - - // Values should be float - if (!CurVolumeInfo.bIsFloat) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output, the height volume's data is not stored as floats!")); - return nullptr; - } - - return &HGPO; - } - - return nullptr; -} - -void -FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers) -{ - FoundLayers.Empty(); - - // Get node id - HAPI_NodeId HeightFieldNodeId = Heightfield.GeoId; - - // We need the tile attribute if the height has it - bool bParentHeightfieldHasTile = false; - int32 HeightFieldTile = -1; - { - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - TArray< int32 > TileValues; - - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HeightFieldNodeId, Heightfield.PartId, "tile", AttribInfoTile, TileValues); - - if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) - { - HeightFieldTile = TileValues[0]; - bParentHeightfieldHasTile = true; - } - } - - for (TArray< FHoudiniGeoPartObject >::TConstIterator IterLayers(InOutput->GetHoudiniGeoPartObjects()); IterLayers; ++IterLayers) - { - const FHoudiniGeoPartObject & HoudiniGeoPartObject = *IterLayers; - - HAPI_NodeId NodeId = HoudiniGeoPartObject.GeoId; - if (NodeId == -1 || NodeId != HeightFieldNodeId) - continue; - - if (bParentHeightfieldHasTile) - { - int32 CurrentTile = -1; - - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - TArray TileValues; - - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "tile", AttribInfoTile, TileValues); - - - if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) - { - CurrentTile = TileValues[0]; - } - - // Does this layer come from the same tile as the height? - if ((CurrentTile != HeightFieldTile) || (CurrentTile == -1)) - continue; - } - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, HoudiniGeoPartObject.PartId, - &CurrentVolumeInfo)) - continue; - - // We're interesting in anything but height data - FString CurrentVolumeName; - FHoudiniEngineString(CurrentVolumeInfo.nameSH).ToFString(CurrentVolumeName); - if (CurrentVolumeName.Contains("height")) - continue; - - // We're only handling single values for now - if (CurrentVolumeInfo.tupleSize != 1) - continue; - - // Terrains always have a ZSize of 1. - if (CurrentVolumeInfo.zLength != 1) - continue; - - // Values should be float - if (CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - continue; - - FoundLayers.Add(&HoudiniGeoPartObject); - } -} - -bool FHoudiniLandscapeTranslator::GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo) -{ - if (HGPO->Type != EHoudiniPartType::Volume) - return false; - - FHoudiniApi::VolumeInfo_Init(&VolumeInfo); - - HAPI_Result Result = FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - HGPO->GeoId, HGPO->PartId, &VolumeInfo); - - // We're only handling single values for now - if (VolumeInfo.tupleSize != 1) - return false; - - // Terrains always have a ZSize of 1. - if (VolumeInfo.zLength != 1) - return false; - - // Values must be float - if (VolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - return false; - - if ((VolumeInfo.xLength < 2) || (VolumeInfo.yLength < 2)) - return false; - - return true; -} - -bool -FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) -{ - OutFloatArr.Empty(); - OutFloatMin = 0.f; - OutFloatMax = 0.f; - - HAPI_VolumeInfo VolumeInfo; - if (!GetHoudiniHeightfieldVolumeInfo(HGPO, VolumeInfo)) - return false; - - const int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; - - OutFloatArr.SetNum(SizeInPoints); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetHeightFieldData( - FHoudiniEngine::Get().GetSession(), - HGPO->GeoId, HGPO->PartId, - OutFloatArr.GetData(), - 0, SizeInPoints), false); - - OutFloatMin = OutFloatArr[0]; - OutFloatMax = OutFloatMin; - - for (float NextFloatVal : OutFloatArr) - { - if (NextFloatVal > OutFloatMax) - { - OutFloatMax = NextFloatVal; - } - else if (NextFloatVal < OutFloatMin) - OutFloatMin = NextFloatVal; - } - - return true; -} - -bool -FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(const FHoudiniGeoPartObject& InHGPO, TArray& NonWeightBlendedLayerNames) -{ - // Check the attribute exists on primitive or detail - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; - if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_PRIM)) - Owner = HAPI_ATTROWNER_PRIM; - else if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_DETAIL)) - Owner = HAPI_ATTROWNER_DETAIL; - else - return false; - - // Get the values - HAPI_AttributeInfo AttribInfoNonWBLayer; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNonWBLayer); - TArray AttribValues; - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, AttribInfoNonWBLayer, AttribValues, 1, Owner); - - if (AttribValues.Num() <= 0) - return false; - - // Convert them to FString - for (int32 Idx = 0; Idx < AttribValues.Num(); Idx++) - { - TArray Tokens; - AttribValues[Idx].ParseIntoArray(Tokens, TEXT(" "), true); - - for (int32 n = 0; n < Tokens.Num(); n++) - NonWeightBlendedLayerNames.AddUnique(Tokens[n]); - } - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) -{ - // Check the attribute exists on primitive or detail - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; - if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_PRIM)) - Owner = HAPI_ATTROWNER_PRIM; - else if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_DETAIL)) - Owner = HAPI_ATTROWNER_DETAIL; - else - return false; - - // Check the value - HAPI_AttributeInfo AttribInfoUnitLayer; - FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer); - TArray< int32 > AttribValues; - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, AttribInfoUnitLayer, AttribValues, 1, Owner); - - if (AttribValues.Num() > 0 && AttribValues[0] == 1) - return true; - - return false; -} - -bool -FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( - const TArray& FoundLayers, - const FHoudiniGeoPartObject& Heightfield, - const int32& LandscapeXSize, const int32& LandscapeYSize, - const TMap& GlobalMinimums, - const TMap& GlobalMaximums, - TArray& OutLayerInfos, - bool bIsUpdate, - const FHoudiniPackageParams& InTilePackageParams, - const FHoudiniPackageParams& InLayerPackageParams, - TArray& OutCreatedPackages - ) -{ - OutLayerInfos.Empty(); - - // Get the names of all non weight blended layers - TArray NonWeightBlendedLayerNames; - FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(Heightfield, NonWeightBlendedLayerNames); - - // Used for exporting layer info objects (per landscape layer) - FHoudiniPackageParams LayerPackageParams = InLayerPackageParams; - // Used for exporting textures (per landscape tile) - FHoudiniPackageParams TilePackageParams = InTilePackageParams; - - // For Debugging, do we want to export layers as textures? - bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; - - // Try to create all the layers - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - for (TArray::TConstIterator IterLayers(FoundLayers); IterLayers; ++IterLayers) - { - const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers; - if (!LayerGeoPartObject) - continue; - - if (!LayerGeoPartObject->IsValid()) - continue; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerGeoPartObject->AssetId)) - continue; - - if (bIsUpdate && !LayerGeoPartObject->bHasGeoChanged) - { - continue; - } - - TArray FloatLayerData; - float LayerMin = 0; - float LayerMax = 0; - if (!FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(LayerGeoPartObject, FloatLayerData, LayerMin, LayerMax)) - continue; - - // No need to create flat layers as Unreal will remove them afterwards.. - if (LayerMin == LayerMax) - continue; - - const FHoudiniVolumeInfo& LayerVolumeInfo = LayerGeoPartObject->VolumeInfo; - - // Get the layer's name - FString LayerName = LayerVolumeInfo.Name; - const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerName); - - TilePackageParams.ObjectName = InTilePackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; - LayerPackageParams.ObjectName = InLayerPackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; - - if (bExportTexture) - { - // Create a raw texture export of the layer on this tile - FString TextureName = TilePackageParams.ObjectName + "_raw"; - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - LayerVolumeInfo.YLength, // Y and X inverted?? why? - LayerVolumeInfo.XLength, - FloatLayerData, - LayerMin, - LayerMax); - } - - // Check if that landscape layer has been marked as unit (range in [0-1] - if (IsUnitLandscapeLayer(*LayerGeoPartObject)) - { - LayerMin = 0.0f; - LayerMax = 1.0f; - } - else - { - // We want to convert the layer using the global Min/Max - if (GlobalMaximums.Contains(LayerName)) - LayerMax = GlobalMaximums[LayerName]; - - if (GlobalMinimums.Contains(LayerName)) - LayerMin = GlobalMinimums[LayerName]; - } - - // Get the layer package path - // FString LayerNameString = FString::Printf(TEXT("%s_%d"), LayerString.GetCharArray().GetData(), (int32)LayerGeoPartObject->PartId); - // LayerNameString = UPackageTools::SanitizePackageName(LayerNameString); - - // Build an object name for the current layer - LayerPackageParams.SplitStr = SanitizedLayerName; - - // Creating the ImportLayerInfo and LayerInfo objects - FLandscapeImportLayerInfo ImportLayerInfo(*LayerName); - - // See if the user has assigned a layer info object via attribute - UPackage * Package = nullptr; - ULandscapeLayerInfoObject* LayerInfo = GetLandscapeLayerInfoForLayer(*LayerGeoPartObject, *LayerName); - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - // No assignment, try to find or create a landscape layer info object for that layer - LayerInfo = FindOrCreateLandscapeLayerInfoObject(LayerName, LayerPackageParams.GetPackagePath(), LayerPackageParams.GetPackageName(), Package); - } - - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - continue; - } - - // Convert the float data to uint8 - // HF masks need their X/Y sizes swapped - if (!FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( - FloatLayerData, LayerVolumeInfo.YLength, LayerVolumeInfo.XLength, - LayerMin, LayerMax, - LandscapeXSize, LandscapeYSize, - ImportLayerInfo.LayerData)) - continue; - - // We will store the data used to convert from Houdini values to int in the DebugColor - // This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards... - // R = Min, G = Max, B = Spacing, A = ? - LayerInfo->LayerUsageDebugColor.R = LayerMin; - LayerInfo->LayerUsageDebugColor.G = LayerMax; - LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; - LayerInfo->LayerUsageDebugColor.A = PI; - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers] Processing layer: %s"), *(LayerName)); - - // Visibility are by default non weight blended - if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) - LayerInfo->bNoWeightBlend = true; - else - LayerInfo->bNoWeightBlend = false; - - if (!bIsUpdate && Package && !Package->IsPendingKill()) - { - // Mark the package dirty... - Package->MarkPackageDirty(); - OutCreatedPackages.Add(Package); - } - - if (bExportTexture) - { - // Create an export of the converted data to texture - // FString TextureName = LayerString; - // if (LayerGeoPartObject->VolumeTileIndex >= 0) - // TextureName = TEXT("Tile") + FString::FromInt(LayerGeoPartObject->VolumeTileIndex) + TEXT("_") + LayerString; - // TextureName += TEXT("_conv"); - - const FString TextureName = TilePackageParams.ObjectName + TEXT("_conv"); - - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - LandscapeXSize, LandscapeYSize, - ImportLayerInfo.LayerData); - } - - // See if there is a physical material assigned via attribute for that landscape layer - UPhysicalMaterial* PhysMaterial = FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(*LayerGeoPartObject); - if (PhysMaterial && !PhysMaterial->IsPendingKill()) - { - LayerInfo->PhysMaterial = PhysMaterial; - } - - // Assign the layer info object to the import layer infos - ImportLayerInfo.LayerInfo = LayerInfo; - OutLayerInfos.Add(ImportLayerInfo); - } - - // Autosaving the layers prevents them for being deleted with the Asset - // Save the packages created for the LayerInfos - // Do this only for when creating layers. - /* - if (!bIsUpdate) - FEditorFileUtils::PromptForCheckoutAndSave(CreatedLandscapeLayerPackage, true, false); - */ - - return true; -} - -void -FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums, - bool bShouldEmptyMaps) -{ - if (bShouldEmptyMaps) - { - GlobalMinimums.Empty(); - GlobalMaximums.Empty(); - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray FloatData; - - for (const FHoudiniGeoPartObject& CurrentHeightfield: InHeightfieldArray) - { - // Get the current Heightfield GeoPartObject - if ( CurrentHeightfield.VolumeInfo.TupleSize != 1) - continue; - - // Retrieve node id from geo part. - HAPI_NodeId NodeId = CurrentHeightfield.GeoId; - if (NodeId == -1) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - &CurrentVolumeInfo)) - continue; - - // Retrieve the volume name. - FString VolumeName; - FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); - HoudiniEngineStringPartName.ToFString(VolumeName); - - bool bHasMinAttr = false; - bool bHasMaxAttr = false; - - // If this volume has an attribute defining a minimum value use it as is. - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - GlobalMinimums.Add(VolumeName, FloatData[0]); - bHasMinAttr = true; - } - } - - // If this volume has an attribute defining maximum value use it as is. - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - GlobalMaximums.Add(VolumeName, FloatData[0]); - bHasMaxAttr = true; - } - } - - if (!(bHasMinAttr && bHasMaxAttr)) - { - // Unreal's Z values are Y in Houdini - float ymin, ymax; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - nullptr, &ymin, nullptr, - nullptr, &ymax, nullptr, - nullptr, nullptr, nullptr)) - continue; - - - if (!bHasMinAttr) - { - // Read the global min value for this volume - if (!GlobalMinimums.Contains(VolumeName)) - { - GlobalMinimums.Add(VolumeName, ymin); - } - else - { - // Update the min if necessary - if (ymin < GlobalMinimums[VolumeName]) - GlobalMinimums[VolumeName] = ymin; - } - } - - if (!bHasMaxAttr) - { - // Read the global max value for this volume - if (!GlobalMaximums.Contains(VolumeName)) - { - GlobalMaximums.Add(VolumeName, ymax); - } - else - { - // Update the max if necessary - if (ymax > GlobalMaximums[VolumeName]) - GlobalMaximums[VolumeName] = ymax; - } - } - } - } -} - -void -FHoudiniLandscapeTranslator::GetLayersZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums) - -{ - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray FloatData; - - for (const FHoudiniGeoPartObject& CurrentHeightfield : InHeightfieldArray) - { - // Get the current Heightfield GeoPartObject - if (CurrentHeightfield.VolumeInfo.TupleSize != 1) - continue; - - // Retrieve node id from geo part. - HAPI_NodeId NodeId = CurrentHeightfield.GeoId; - if (NodeId == -1) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - &CurrentVolumeInfo)) - continue; - - // Retrieve the volume name. - FString VolumeName; - FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); - HoudiniEngineStringPartName.ToFString(VolumeName); - - // Read the global min value for this volume - - float MinValue; - float MaxValue; - bool bHasMin = false; - bool bHasMax = false; - - if (!GlobalMinimums.Contains(VolumeName)) - { - // Extract min value - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - MinValue = FloatData[0]; - bHasMin = true; - } - } - if (!bHasMin) - { - if (VolumeName == TEXT("height")) - { - MinValue = -1000.f; - } - else - { - MinValue = 0.f; - } - } - GlobalMinimums.Add(VolumeName, MinValue); - } - - if (!GlobalMaximums.Contains(VolumeName)) - { - // Extract max value - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - MaxValue = FloatData[0]; - bHasMax = true; - } - } - if (!bHasMax) - { - if (VolumeName == TEXT("height")) - { - MaxValue = 1000.f; - } - else - { - MaxValue = 1.f; - } - } - GlobalMaximums.Add(VolumeName, MaxValue); - } - - - - } -} - -bool -FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( - const TArray& FloatLayerData, - const int32& HoudiniXSize, const int32& HoudiniYSize, - const float& LayerMin, const float& LayerMax, - const int32& LandscapeXSize, const int32& LandscapeYSize, - TArray& LayerData, const bool& NoResize) -{ - // Convert the float data to uint8 - LayerData.SetNumUninitialized(HoudiniXSize * HoudiniYSize); - - // Calculating the factor used to convert from Houdini's ZRange to [0 255] - double LayerZRange = (LayerMax - LayerMin); - double LayerZSpacing = (LayerZRange != 0.0) ? (255.0 / (double)(LayerZRange)) : 0.0; - - int32 nUnrealIndex = 0; - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y - int32 nHoudini = nY + nX * HoudiniYSize; - - // Get the double values in [0 - ZRange] - double DoubleValue = (double)FMath::Clamp(FloatLayerData[nHoudini], LayerMin, LayerMax) - (double)LayerMin; - - // Then convert it to [0 - 255] - DoubleValue *= LayerZSpacing; - - LayerData[nUnrealIndex++] = FMath::RoundToInt(DoubleValue); - } - } - - // Finally, resize the data to fit with the new landscape size if needed - if (NoResize) - return true; - - return FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( - LayerData, HoudiniXSize, HoudiniYSize, - LandscapeXSize, LandscapeYSize); -} - -bool -FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( - TArray< uint8 >& LayerData, - const int32& SizeX, const int32& SizeY, - const int32& NewSizeX, const int32& NewSizeY) -{ - if ((NewSizeX == SizeX) && (NewSizeY == SizeY)) - return true; - - bool bForceResample = true; - bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); - - TArray NewData; - if (!bResample) - { - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - - const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; - const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; - - // Expanding the Data - NewData = ExpandData( - LayerData, - 0, 0, SizeX - 1, SizeY - 1, - -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1); - } - else - { - // Resampling the data - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - NewData = ResampleData(LayerData, SizeX, SizeY, NewSizeX, NewSizeY); - } - - LayerData = NewData; - - return true; -} - -ALandscapeProxy * -FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( - const TArray< uint16 >& IntHeightData, - const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - const FTransform& TileTransform, - const FIntPoint& TileLocation, - const int32& XSize, - const int32& YSize, - const int32& NumSectionPerLandscapeComponent, - const int32& NumQuadsPerLandscapeSection, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhysicalMaterial, - const FString& LandscapeTileActorName, - LandscapeActorType ActorType, - ALandscape* SharedLandscapeActor, - UWorld* InWorld, - ULevel* InLevel, - FHoudiniPackageParams InPackageParams) -{ - if (!IsValid(InWorld)) - return nullptr; - - // if (!IsValid(MainLandscapeActor)) - // return nullptr; - - if ((XSize < 2) || (YSize < 2)) - return nullptr; - - if (IntHeightData.Num() != (XSize * YSize)) - return nullptr; - - if (!GEditor) - return nullptr; - - ALandscapeProxy* LandscapeTile = nullptr; - UPackage *CreatedPackage = nullptr; - - ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; - ALandscape* CachedLandscapeActor = nullptr; - - UWorld* NewWorld = nullptr; - FString MapFileName; - bool bBroadcastMaterialUpdate = false; - //... Create landscape tile ...// - { - // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos - if (ActorType == LandscapeActorType::LandscapeStreamingProxy) - { - CachedStreamingProxyActor = InWorld->SpawnActor(); - if (CachedStreamingProxyActor) - { - check(SharedLandscapeActor); - CachedStreamingProxyActor->PreEditChange(nullptr); - - // Update landscape tile properties from the main landscape actor. - CachedStreamingProxyActor->GetSharedProperties(SharedLandscapeActor); - CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; - CachedStreamingProxyActor->bCastStaticShadow = false; - - LandscapeTile = CachedStreamingProxyActor; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not spawn ALandscapeStreamingProxy with name: %s"), *(LandscapeTileActorName) ); - return nullptr; - } - } - else - { - // Create a normal landscape actor - CachedLandscapeActor = InWorld->SpawnActor(); - if (CachedLandscapeActor) - { - CachedLandscapeActor->PreEditChange(nullptr); - CachedLandscapeActor->SetLandscapeGuid(FGuid::NewGuid()); - CachedLandscapeActor->LandscapeMaterial = LandscapeMaterial; - CachedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - CachedLandscapeActor->bCastStaticShadow = false; - bBroadcastMaterialUpdate = true; - LandscapeTile = CachedLandscapeActor; - } - } - } - - - if (!LandscapeTile) - return nullptr; - - // Only import non-visibility layers. Visibility will be handled explicitly. - TArray CustomImportLayerInfos; - for (const FLandscapeImportLayerInfo& LayerInfo : ImportLayerInfos) - { - if (LayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - continue; - CustomImportLayerInfos.Add(LayerInfo); - } - - // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. - - // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue - LandscapeTile->bCastStaticShadow = false; - - // TODO: Check me? - //if (LandscapePhsyicalMaterial) - // LandscapeTile->DefaultPhysMaterial = LandscapePhsyicalMaterial; - - // Setting the layer type here. - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - - TMap> HeightmapDataPerLayers; - TMap> MaterialLayerDataPerLayer; - HeightmapDataPerLayers.Add(FGuid(), IntHeightData); - TArray& MaterialImportLayerInfos = MaterialLayerDataPerLayer.Add(FGuid(), CustomImportLayerInfos); - - // Before we import new tile data, remove previous tile data that will be overlapped by the new tile. - TSet OverlappingComponents; - const int32 DestMinX = TileLocation.X; - const int32 DestMinY = TileLocation.Y; - const int32 DestMaxX = TileLocation.X + XSize - 1; - const int32 DestMaxY = TileLocation.Y + YSize - 1; - - ULandscapeInfo* LandscapeInfo = LandscapeTile->GetLandscapeInfo(); - - if (LandscapeInfo) - { - // If there is a preexisting LandscapeInfo object, check for overlapping components. - - // Shrink bounds by 1,1 to avoid GetComponentsInRegion picking up extra components on all sides due to the overlap between components - LandscapeInfo->GetComponentsInRegion(DestMinX+1, DestMinY+1, DestMaxX-1, DestMaxY-1, OverlappingComponents); - TSet StaleActors; - - for (ULandscapeComponent* Component : OverlappingComponents) - { - // Remove the overlapped component from the LandscapeInfo and then from - LandscapeInfo->Modify(); - - ALandscapeProxy* Proxy = Component->GetLandscapeProxy(); - if (!IsValid(Proxy)) - continue; - check(Proxy); - FIntRect Bounds = Proxy->GetBoundingRect(); - // If this landscape proxy has no more components left, remove it from the LandscapeInfo. - LandscapeInfo->UnregisterActor(Proxy); - Proxy->Destroy(); - } - - ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); - } - - // Import tile data - // The Import function will correctly compute the tile section locations. No need to set it explicitly. - // TODO: Verify this with world composition!! - - bool bIsStandalone = LandscapeTile->GetLandscapeActor() == LandscapeTile; - - // We set the actor transform and absolute section base before importing heightfield data. This allows us to - // use the correct (quad-space) blitting region without causing overlaps during import. - - // NOTE: The following SetAbsoluteSectionBase() calls are very important. This tells the Landscape system - // where on the landscape, in quad space, a specific tile is located. This is used by various - // mechanisms and tools that tie into the Landscape system such as World Composition and Landscape editor tools. - // If the Section Offset for a landscape tile is wrong, it will appear in the wrong place in the World Composition - // viewer. Worse than that, none of Landscape Editor tools will work properly because it won't be able to - // locate the correct Landscape component when calculating the "Landscape Component Key" for the given world position. - // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blitting functions use the - // same underlying Landscape Component Key calculation to find the correct landscape component to draw to. So if the - // Section Offsets are wrong ... all manner of chaos will follow. - // It is also super important to call ULandscapeInfo::RecreateLandscapeInfo() after change a LandscapeProxy's - // section offset in order to update the landscape's internal caches (more specifically the component keys, which - // are based on the section offsets) otherwise component key calculations won't work correctly. - - LandscapeTile->SetActorRelativeTransform(TileTransform); - LandscapeTile->SetAbsoluteSectionBase(TileLocation); - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Importing tile for actor: %s "), *(LandscapeTile->GetPathName())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Dest region: %d, %d -> %d, %d"), DestMinX, DestMinY, DestMaxX, DestMaxY); - - LandscapeTile->Import( - LandscapeTile->GetLandscapeGuid(), - DestMinX, DestMinY, DestMaxX, DestMaxY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, - HeightmapDataPerLayers, NULL, - MaterialLayerDataPerLayer, ImportLayerType); - - - if (!LandscapeInfo) - { - LandscapeInfo = LandscapeTile->GetLandscapeInfo(); - } - - check(LandscapeInfo); - - // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so - // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo component keys. - // Only then are we able to "blit" the new alpha data into the correct place on the landscape. - - ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); - LandscapeTile->RecreateComponentsState(); - - // TODO: Verify whether we still need to manually set the LandscapeLightingLOD setting or whether - // calling PostEditChange() will properly fix the state. - - // Copied straight from UE source code to avoid crash after importing the landscape: - // automatically calculate a lighting LOD that won't crash lightmass (hopefully) - // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 - LandscapeTile->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); - - // ---------------------------------------------------- - // Update visibility layer - // ---------------------------------------------------- - - // Update the visibility mask / layer if we have any (TileImport does not update the visibility layer). - for (auto CurLayerInfo : ImportLayerInfos) - { - if (CurLayerInfo.LayerInfo && CurLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); - AlphaAccessor.SetData(DestMinX, DestMinY, DestMaxX, DestMaxY, CurLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - } - } - - // ---------------------------------------------------- - // Rename the actor - // ---------------------------------------------------- - - // NOTE: The LandscapeProxy needs to be properly initialized before renaming (which is why the rename is taking - // place at the end) since the rename will trigger PostEditChange and can crash if the actor has not been - // correctly setup. - FHoudiniEngineUtils::SafeRenameActor(LandscapeTile, LandscapeTileActorName); - - if (!LandscapeTile->MarkPackageDirty()) - { - HOUDINI_LOG_WARNING(TEXT("Editor suppressed MarkPackageDirty() for landscape tile: %s"), *(LandscapeTile->GetPathName())); - } - -#if WITH_EDITOR - GEngine->BroadcastOnActorMoved(LandscapeTile); -#endif - - return LandscapeTile; -} - - -void -FHoudiniLandscapeTranslator::DestroyLandscape(ALandscape* Landscape) -{ - if (!IsValid(Landscape)) - return; - - ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); - if (!IsValid(Info)) - return; - - TArray Proxies = Info->Proxies; - for(ALandscapeStreamingProxy* Proxy : Proxies) - { - Info->UnregisterActor(Proxy); - Proxy->Destroy(); - } - Landscape->Destroy(); -} - - -void -FHoudiniLandscapeTranslator::CalculateTileLocation( - int32 NumSectionsPerComponent, - int32 NumQuadsPerSection, - const FTransform& TileTransformWS, - FHoudiniLandscapeReferenceLocation& RefLoc, - FTransform& OutLandscapeTransform, - FIntPoint& OutTileLocation) -{ - // IMPORTANT: Tile Location (Section Base) needs to be in multiples of the landscape component's quad size - const int32 ComponentSize = NumSectionsPerComponent * NumQuadsPerSection; - - OutLandscapeTransform = FTransform::Identity; - const FTransform TileSR = FTransform(TileTransformWS.GetRotation(), FVector::ZeroVector, TileTransformWS.GetScale3D()); - - const FVector BaseLoc = TileSR.InverseTransformPosition(TileTransformWS.GetLocation()); - - const FVector TileScale = TileTransformWS.GetScale3D(); - const float TileLocX = BaseLoc.X; // / TileScale.X; - const float TileLocY = BaseLoc.Y; // / TileScale.Y; - - if (!RefLoc.bIsCached) - { - // If there is no landscape reference location yet, calculate one now. - - // We cache this tile as a reference point for the other landscape tiles so that they can calculate - // section base offsets in a consistent manner, relative to this tile. - const float NearestMultipleX = FMath::RoundHalfFromZero(TileLocX / ComponentSize) * ComponentSize; - const float NearestMultipleY = FMath::RoundHalfFromZero(TileLocY / ComponentSize) * ComponentSize; - - RefLoc.SectionCoordX = FMath::RoundHalfFromZero(NearestMultipleX); - RefLoc.SectionCoordY = FMath::RoundHalfFromZero(NearestMultipleY); - RefLoc.TileLocationX = TileLocX; - RefLoc.TileLocationY = TileLocY; - } - - // Calculate the section coordinate for this tile - const float DeltaLocX = TileLocX - RefLoc.TileLocationX; - const float DeltaLocY = TileLocY - RefLoc.TileLocationY; - - const float DeltaCoordX = FMath::RoundHalfFromZero(DeltaLocX / ComponentSize) * ComponentSize; - const float DeltaCoordY = FMath::RoundHalfFromZero(DeltaLocY / ComponentSize) * ComponentSize; - - OutTileLocation.X = RefLoc.SectionCoordX + DeltaCoordX; - OutTileLocation.Y = RefLoc.SectionCoordY + DeltaCoordY; - - // Adjust landscape offset to compensate for tile location / section base shifting. - if (!RefLoc.bIsCached) - { - FVector Offset((TileLocX - OutTileLocation.X), (TileLocY - OutTileLocation.Y), BaseLoc.Z); - Offset = TileSR.TransformPosition(Offset); - - RefLoc.MainTransform = TileTransformWS; - RefLoc.MainTransform.SetTranslation(Offset); - // Reference locations are now fully cached. - RefLoc.bIsCached = true; - } - - OutLandscapeTransform = RefLoc.MainTransform; -} - - -void -FHoudiniLandscapeTranslator::GetLandscapeMaterials( - const FHoudiniGeoPartObject& InHeightHGPO, - UMaterialInterface*& OutLandscapeMaterial, - UMaterialInterface*& OutLandscapeHoleMaterial, - UPhysicalMaterial*& OutLandscapePhysicalMaterial) -{ - OutLandscapeMaterial = nullptr; - OutLandscapeHoleMaterial = nullptr; - OutLandscapePhysicalMaterial = nullptr; - - if (InHeightHGPO.Type != EHoudiniPartType::Volume) - return; - - TArray Materials; - HAPI_AttributeInfo AttribMaterials; - FHoudiniApi::AttributeInfo_Init(&AttribMaterials); - - // First, look for landscape material - { - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL, - AttribMaterials, Materials); - - // If the material attribute was not found, check the material instance attribute. - if (!AttribMaterials.exists) - { - Materials.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, - AttribMaterials, Materials); - } - - // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. - //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) - if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) - { - HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a point, primitive or detail attribute, ignoring attribute.")); - AttribMaterials.exists = false; - Materials.Empty(); - } - - if (AttribMaterials.exists && Materials.Num() > 0) - { - // Load the material - OutLandscapeMaterial = Cast(StaticLoadObject( - UMaterialInterface::StaticClass(), - nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); - } - } - - Materials.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribMaterials); - - // Then, for the hole_material - { - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - AttribMaterials, Materials); - - // If the material attribute was not found, check the material instance attribute. - if (!AttribMaterials.exists) - { - Materials.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE, - AttribMaterials, Materials); - } - - // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. - //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) - if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) - { - HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute.")); - AttribMaterials.exists = false; - Materials.Empty(); - } - - if (AttribMaterials.exists && Materials.Num() > 0) - { - // Load the material - OutLandscapeHoleMaterial = Cast< UMaterialInterface >(StaticLoadObject( - UMaterialInterface::StaticClass(), - nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); - } - } - - // Then for the physical material - OutLandscapePhysicalMaterial = GetLandscapePhysicalMaterial(InHeightHGPO); -} - -// Read the landscape component extent attribute from a heightfield -bool -FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( - const FHoudiniGeoPartObject& HoudiniGeoPartObject, - int32& MinX, int32& MaxX, - int32& MinY, int32& MaxY) -{ - // If we dont have minX, we likely dont have the others too - if (!FHoudiniEngineUtils::HapiCheckAttributeExists( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", HAPI_ATTROWNER_PRIM)) - return false; - - // Create an AttributeInfo - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); - - // Get MinX - TArray IntData; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MinX = IntData[0]; - - // Get MaxX - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MaxX = IntData[0]; - - // Get MinY - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MinY = IntData[0]; - - // Get MaxX - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MaxY = IntData[0]; - - return true; -} - -ULandscapeLayerInfoObject * -FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& InLayerName, const FString& InPackagePath, const FString& InPackageName, UPackage*& OutPackage) -{ - FString PackageFullName = InPackagePath + TEXT("/") + InPackageName; - - // See if package exists, if it does, reuse it - bool bCreatedPackage = false; - OutPackage = FindPackage(nullptr, *PackageFullName); - if (!OutPackage || OutPackage->IsPendingKill()) - { - // We need to create a new package - OutPackage = CreatePackage(*PackageFullName); - bCreatedPackage = true; - } - - if (!OutPackage || OutPackage->IsPendingKill()) - return nullptr; - - if (!OutPackage->IsFullyLoaded()) - OutPackage->FullyLoad(); - - ULandscapeLayerInfoObject* LayerInfo = nullptr; - if (!bCreatedPackage) - { - // See if we can load the layer info instead of creating a new one - LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), OutPackage, FName(*InPackageName)); - } - - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - // Create a new LandscapeLayerInfoObject in the package - LayerInfo = NewObject(OutPackage, FName(*InPackageName), RF_Public | RF_Standalone /*| RF_Transactional*/); - - // Notify the asset registry - FAssetRegistryModule::AssetCreated(LayerInfo); - } - - if (LayerInfo && !LayerInfo->IsPendingKill()) - { - LayerInfo->LayerName = FName(*InLayerName); - - // Trigger update of the Layer Info - LayerInfo->PreEditChange(nullptr); - LayerInfo->PostEditChange(); - LayerInfo->MarkPackageDirty(); - - // Mark the package dirty... - OutPackage->MarkPackageDirty(); - } - - return LayerInfo; -} - -bool -FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax( - const TArray& AllOutputs, float& OutGlobalMin, float& OutGlobalMax) -{ - OutGlobalMin = 0.f; - OutGlobalMax = 0.f; - - for (const auto& CurrentOutput : AllOutputs) - { - if (!CurrentOutput) - continue; - - if (CurrentOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - const TArray& HGPOs = CurrentOutput->GetHoudiniGeoPartObjects(); - for (const FHoudiniGeoPartObject& CurrentHGPO : HGPOs) - { - if (CurrentHGPO.Type != EHoudiniPartType::Volume) - continue; - - if (!CurrentHGPO.VolumeInfo.Name.Contains("height")) - continue; - - // We're only handling single values for now - if (CurrentHGPO.VolumeInfo.TupleSize != 1) - continue; - - // Terrains always have a ZSize of 1. - if (CurrentHGPO.VolumeInfo.ZLength != 1) - continue; - - // Values should be float - if (!CurrentHGPO.VolumeInfo.bIsFloat) - continue; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CurrentHGPO.GeoId)) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHGPO.GeoId, CurrentHGPO.PartId, &CurrentVolumeInfo)) - continue; - - // Unreal's Z values are Y in Houdini - float yMin = OutGlobalMin, yMax = OutGlobalMax; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), - CurrentHGPO.GeoId, CurrentHGPO.PartId, - nullptr, &yMin, nullptr, - nullptr, &yMax, nullptr, - nullptr, nullptr, nullptr)) - continue; - - if (yMin < OutGlobalMin) - OutGlobalMin = yMin; - - if (yMax > OutGlobalMax) - OutGlobalMax = yMax; - } - - if (OutGlobalMin > OutGlobalMax) - { - OutGlobalMin = 0.f; - OutGlobalMax = 0.f; - } - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::EnableWorldComposition() -{ - HOUDINI_LOG_WARNING(TEXT("[FHoudiniLandscapeTranslator::EnableWorldComposition] We should never enable world composition from within the plugin.")); - // Get the world - UWorld* MyWorld = nullptr; - { - // We want to create the landscape in the landscape editor mode's world - FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); - MyWorld = EditorWorldContext.World(); - } - - if (!MyWorld) - return false; - - ULevel* CurrentLevel = MyWorld->GetCurrentLevel(); - - if (!CurrentLevel) - return false; - - AWorldSettings* WorldSettings = CurrentLevel->GetWorldSettings(); - if (!WorldSettings) - return false; - - // Enable world composition in WorldSettings - WorldSettings->bEnableWorldComposition = true; - - CurrentLevel->PostEditChange(); - - return true; -} - -bool -FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (const auto& CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current Property Attribute - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); - } - - return (NumSuccess > 0); -} - - -bool -FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(const FString& BaseName, ALandscapeProxy* Landscape) -{ - // We need to cache the input landscape to a file - if (!Landscape) - return false; - - ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Save Height data to file - //FString HeightSave = TEXT("/Game/HoudiniEngine/Temp/HeightCache.png"); - FString HeightSave = BaseName + TEXT("_height.png"); - LandscapeInfo->ExportHeightmap(HeightSave); - Landscape->ReimportHeightmapFilePath = HeightSave; - - // Save each layer to a file - for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) - { - FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); - //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) - continue; - - FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); - LandscapeInfo->ExportLayer(CurrentLayerInfo, LayerSave); - - // Update the file reimport path on the input landscape for this layer - LandscapeInfo->GetLayerEditorSettings(CurrentLayerInfo).ReimportLayerFilePath = LayerSave; - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy) -{ - if (!LandscapeProxy) - return false; - - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Restore Height data from the backup file - FString ReimportFile = LandscapeProxy->ReimportHeightmapFilePath; - if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, TEXT("height"))) - HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); - - // Restore each layer from the backup file - TArray< ULandscapeLayerInfoObject* > SourceLayers; - for (int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++) - { - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) - continue; - - FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); - ReimportFile = LandscapeProxy->EditorLayerSettings[LayerIndex].ReimportLayerFilePath; - - if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, CurrentLayerName, CurrentLayerInfo)) - HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); - - SourceLayers.Add(CurrentLayerInfo); - } - - // Iterate on the landscape info's layer to remove any layer that could have been added by Houdini - for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) - { - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; - if (SourceLayers.Contains(CurrentLayerInfo)) - continue; - - // Delete the added layer - FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName; - LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName); - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::ImportLandscapeData( - ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject) -{ - // - // Code copied/edited from FEdModeLandscape::ImportData as we cannot access that function - // - if (!LandscapeInfo) - return false; - - bool IsHeight = LayerName.Equals(TEXT("height"), ESearchCase::IgnoreCase); - - int32 MinX, MinY, MaxX, MaxY; - if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - { - const FLandscapeFileResolution LandscapeResolution = { (uint32)(1 + MaxX - MinX), (uint32)(1 + MaxY - MinY) }; - - ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); - - if (IsHeight) - { - const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); - - if (!HeightmapFormat) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); - return false; - } - - FLandscapeFileResolution ImportResolution = { 0, 0 }; - - const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename); - - // display error message if there is one, and abort the import - if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); - return false; - } - - // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape - if (HeightmapInfo.PossibleResolutions.Num() > 1) - { - if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined")); - return false; - } - else - { - ImportResolution = LandscapeResolution; - } - } - - // display warning message if there is one and allow user to cancel - if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); - - // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape - // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is - if (HeightmapInfo.PossibleResolutions.Num() == 1) - { - ImportResolution = HeightmapInfo.PossibleResolutions[0]; - if (ImportResolution != LandscapeResolution) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); - } - - FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution); - if (ImportData.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); - return false; - } - - TArray Data; - if (ImportResolution != LandscapeResolution) - { - // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked - // so that reimports behave the same as the initial import :) - - const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; - const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; - - Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16)); - - ExpandData(Data.GetData(), ImportData.Data.GetData(), - 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, - -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); - } - else - { - Data = MoveTemp(ImportData.Data); - } - - //FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap")); - - FHeightmapAccessor HeightmapAccessor(LandscapeInfo); - HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData()); - } - else - { - // We're importing a Landscape layer - if (!LayerInfoObject || LayerInfoObject->IsPendingKill()) - return false; - - const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); - if (!WeightmapFormat) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); - return false; - } - - FLandscapeFileResolution ImportResolution = { 0, 0 }; - - const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName)); - - // display error message if there is one, and abort the import - if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); - return false; - } - - // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape - if (WeightmapInfo.PossibleResolutions.Num() > 1) - { - if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined")); - return false; - } - else - { - ImportResolution = LandscapeResolution; - } - } - - // display warning message if there is one and allow user to cancel - if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); - - // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape - // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is - if (WeightmapInfo.PossibleResolutions.Num() == 1) - { - ImportResolution = WeightmapInfo.PossibleResolutions[0]; - if (ImportResolution != LandscapeResolution) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); - } - - FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution); - - if (ImportData.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); - return false; - } - - TArray Data; - if (ImportResolution != LandscapeResolution) - { - // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked - // so that reimports behave the same as the initial import :) - const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; - const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; - - Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8)); - - ExpandData(Data.GetData(), ImportData.Data.GetData(), - 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, - -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); - } - else - { - Data = MoveTemp(ImportData.Data); - } - - //FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer")); - FAlphamapAccessor AlphamapAccessor(LandscapeInfo, LayerInfoObject); - AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None); - } - } - - return true; -} - -UTexture2D* -FHoudiniLandscapeTranslator::CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& InFloatBuffer, - const float& InMin, - const float& InMax) -{ - - // Convert the float values to uint8 - double Range = (double)InMax - (double)InMin; - TArray IntBuffer; - IntBuffer.SetNum(InFloatBuffer.Num()); - for(int32 i = 0; i < InFloatBuffer.Num(); i++) - { - double dNormalizedValue = ((double)InFloatBuffer[i] - (double)InMin) / (double)Range; - IntBuffer[i] = (uint8)(dNormalizedValue * 255.0); - } - - return FHoudiniLandscapeTranslator::CreateUnrealTexture( - InPackageParams, LayerName, InXSize, InYSize, IntBuffer); -} - -UTexture2D* -FHoudiniLandscapeTranslator::CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& IntBuffer) -{ - FHoudiniPackageParams MyPackageParams = InPackageParams; - MyPackageParams.ObjectName = LayerName; - MyPackageParams.PackageMode = EPackageMode::CookToTemp; - MyPackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - FString CreatedPackageName; - UPackage* Package = MyPackageParams.CreatePackageForObject(CreatedPackageName); - if (!Package || Package->IsPendingKill()) - return nullptr; - - // Create new texture object. - UTexture2D * Texture = NewObject(Package, UTexture2D::StaticClass(), *LayerName, RF_Public | RF_Standalone); - - // Add/Update meta information to package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *LayerName); - - /*// Texture Settings - Texture->PlatformData = new FTexturePlatformData(); - Texture->PlatformData->SizeX = InXSize; - Texture->PlatformData->SizeY = InYSize; - Texture->PlatformData->PixelFormat = PF_R8G8B8A8;*/ - - // Initialize texture source. - Texture->Source.Init(InXSize, InYSize, 1, 1, TSF_BGRA8); - - // Lock the texture. - uint8 * MipData = Texture->Source.LockMip(0); - - // Create base map. - uint8* DestPtr = nullptr; - uint32 SrcWidth = InXSize; - uint32 SrcHeight = InYSize; - const uint8 * SrcData = &IntBuffer[0]; - - for (uint32 y = 0; y < SrcHeight; y++) - { - DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; - - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth + x; - - *DestPtr++ = *(SrcData + DataOffset); // B greyscale, same value 3 times - *DestPtr++ = *(SrcData + DataOffset); // G - *DestPtr++ = *(SrcData + DataOffset); // R - - *DestPtr++ = 0xFF; // A to 1 - } - } - - // Unlock the texture. - Texture->Source.UnlockMip(0); - - // Texture creation parameters. - //Texture->SRGB = TextureParameters.bSRGB; - Texture->CompressionSettings = TC_Grayscale; - Texture->CompressionNoAlpha = true; - Texture->MipGenSettings = TMGS_NoMipmaps; - - // Set the Source Guid/Hash if specified. - /* - if ( TextureParameters.SourceGuidHash.IsValid() ) - { - Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); - } - */ - - // Updating Texture & mark it as unsaved - //Texture->AddToRoot(); - //Texture->UpdateResource(); - Package->MarkPackageDirty(); - - Texture->PostEditChange(); - - FString PathName = Texture->GetPathName(); - HOUDINI_LOG_MESSAGE(TEXT("Created texture when for %s in %s"), *LayerName, *PathName); - - return Texture; -} - -UPhysicalMaterial* -FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO) -{ - // See if we have assigned a physical material to this layer via attribute - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - TArray AttributeValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InLayerHGPO.GeoId, InLayerHGPO.PartId, - HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL, - AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) - return nullptr; - - if (AttributeValues.Num() > 0) - { - return LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); - } - - return nullptr; -} - -ULandscapeLayerInfoObject* -FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName) -{ - // See if we have assigned a landscape layer info object to this layer via attribute - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - TArray AttributeValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InLayerHGPO.GeoId, InLayerHGPO.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO, - AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) - return nullptr; - - if (AttributeValues.Num() > 0) - { - ULandscapeLayerInfoObject* FoundLayerInfo = LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); - if (!FoundLayerInfo || FoundLayerInfo->IsPendingKill()) - return nullptr; - - // The layer info's name must match this layer's name or Unreal will not like this! - if (!FoundLayerInfo->LayerName.IsEqual(InLayerName)) - { - FString NameStr = InLayerName.ToString(); - HOUDINI_LOG_WARNING(TEXT("Failed to use the assigned layer info object for %s by the unreal_landscape_layer_info attribute as the found layer info object's layer name does not match."), *NameStr); - } - - return FoundLayerInfo; - } - - return nullptr; -} - - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniLandscapeTranslator.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniEngineString.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPackageParams.h" +#include "HoudiniStringResolver.h" +#include "HoudiniInput.h" + +#include "ObjectTools.h" +#include "FileHelpers.h" +#include "Editor.h" +#include "LandscapeLayerInfoObject.h" +#include "LandscapeStreamingProxy.h" +#include "LandscapeInfo.h" +#include "LandscapeEdit.h" +#include "AssetRegistryModule.h" +#include "PackageTools.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "UObject/UnrealType.h" + +#include "GameFramework/WorldSettings.h" +#include "Misc/Paths.h" +#include "Modules/ModuleManager.h" +#include "AssetToolsModule.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Factories/WorldFactory.h" +#include "Misc/Guid.h" +#include "Engine/LevelBounds.h" + +#include "HAL/IConsoleManager.h" +#include "Engine/AssetManager.h" +#include "Misc/ScopedSlowTask.h" + +#if WITH_EDITOR + #include "EditorLevelUtils.h" + #include "LandscapeEditorModule.h" + #include "LandscapeFileFormatInterface.h" +#endif + +static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( + TEXT("HoudiniEngine.ExportLandscapeTextures"), + 0, + TEXT("If enabled, landscape layers and heightmap will be exported as textures in the temp directory when converting a Heightfield to a Landscape.\n") + TEXT("0: Disabled\n") + TEXT("1: Enabled\n") +); + +typedef FHoudiniEngineUtils FHUtils; + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY(); + +bool +FHoudiniLandscapeTranslator::CreateLandscape( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedOutputs, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* InWorld, // Persistent / root world for the landscape + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TSet& ClearedLayers, + TArray& OutCreatedPackages +) +{ + // Do the absolute minimum in order to determine which output mode we're dealing with (Temp or Editable Layers). + + if (!InOutput || InOutput->IsPendingKill()) + return false; + + // Get the height map. + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + if (!Heightfield) + return false; + + if (Heightfield->Type != EHoudiniPartType::Volume) + return false; + + const HAPI_NodeId GeoId = Heightfield->GeoId; + const HAPI_PartId PartId = Heightfield->PartId; + + // Check whether we're running in edit layer mode, or the usual temp mode + + TArray IntData; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + // --------------------------------------------- + // Attribute: unreal_landscape_output_mode + // --------------------------------------------- + IntData.Empty(); + int32 LandscapeOutputMode = 0; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_OUTPUT_MODE, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0) + { + LandscapeOutputMode = IntData[0]; + } + } + + switch (LandscapeOutputMode) + { + case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_EDITABLE_LAYER: + { + return OutputLandscape_EditableLayer(InOutput, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + InAllInputLandscapes, + SharedLandscapeActorParent, + DefaultLandscapeActorPrefix, + InWorld, + LayerMinimums, + LayerMaximums, + LandscapeExtent, + LandscapeTileSizeInfo, + LandscapeReferenceLocation, + InPackageParams, + ClearedLayers, + OutCreatedPackages); + } + break; + case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_DEFAULT: + default: + { + return OutputLandscape_Temp(InOutput, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + InAllInputLandscapes, + SharedLandscapeActorParent, + DefaultLandscapeActorPrefix, + InWorld, + LayerMinimums, + LayerMaximums, + LandscapeExtent, + LandscapeTileSizeInfo, + LandscapeReferenceLocation, + InPackageParams, + OutCreatedPackages + ); + } + break; + } +} + +bool +FHoudiniLandscapeTranslator::OutputLandscape_Temp( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedOutputs, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* InWorld, // Persistent / root world for the landscape + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TArray& OutCreatedPackages +) +{ + check(LayerMinimums.Contains(TEXT("height"))); + check(LayerMaximums.Contains(TEXT("height"))); + + float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); + float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); + + if (!InOutput || InOutput->IsPendingKill()) + return false; + + // Get the height map. + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + if (!Heightfield) + return false; + + if (Heightfield->Type != EHoudiniPartType::Volume) + return false; + + const HAPI_NodeId GeoId = Heightfield->GeoId; + const HAPI_PartId PartId = Heightfield->PartId; + + // Construct the identifier of the Heightfield geo part. + FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); + HeightfieldIdentifier.PartName = Heightfield->PartName; + + FString NodeNameSuffix = GetActorNameSuffix(InPackageParams.PackageMode); + bool bAddLandscapeNameSuffix = true; + bool bAddLandscapeTileNameSuffix = true; + + const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + + TArray IntData; + TArray StrData; + // Output attributes will be stored on the Output object and will be used again during baking to determine + // where content should be baked to and what they should be named, etc. + // At the end of this function, the output attributes and tokens will be copied to the output object. + TMap OutputAttributes; + TMap OutputTokens; + FHoudiniAttributeResolver Resolver; + InPackageParams.UpdateTokensFromParams(InWorld, HoudiniAssetComponent, OutputTokens); + + bool bHasTile = Heightfield->VolumeTileIndex >= 0; + + // --------------------------------------------- + // Attribute: unreal_landscape_tile_actor_type, unreal_landscape_streaming_proxy (v1) + // --------------------------------------------- + // Determine the actor type for the tile + bool bCreateLandscapeStreamingProxy = false; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + LandscapeActorType TileActorType = LandscapeActorType::LandscapeActor; + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0) + { + TileActorType = static_cast(IntData[0]); + } + } + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0 && IntData[0] != 0) + TileActorType = LandscapeActorType::LandscapeStreamingProxy; + } + + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, FString::FromInt(static_cast(TileActorType))); + + // --------------------------------------------- + // Attribute: unreal_landscape_actor_name + // --------------------------------------------- + // Retrieve the name of the main Landscape actor to look for + FString SharedLandscapeActorName = DefaultLandscapeActorPrefix + "SharedLandscape"; // If this is an empty string, don't affirm a root landscape actor? + StrData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, AttributeInfo, StrData, 1)) + { + if (StrData.Num() > 0 && !StrData[0].IsEmpty()) + SharedLandscapeActorName = StrData[0]; + } + + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); + + // --------------------------------------------- + // Attribute: unreal_level_path + // --------------------------------------------- + // FString LevelPath = bHasTile ? "{world}/Landscape/Tile{tile}" : "{world}/Landscape"; + FString LevelPath; + TArray LevelPaths; + if (FHoudiniEngineUtils::GetLevelPathAttribute(GeoId, PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + LevelPath = LevelPaths[0]; + } + if (!LevelPath.IsEmpty()) + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); + + // --------------------------------------------- + // Attribute: unreal_output_name + // --------------------------------------------- + FString LandscapeTileActorName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; + TArray AllOutputNames; + if (FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames)) + { + if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) + LandscapeTileActorName = AllOutputNames[0]; + } + OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); + + // --------------------------------------------- + // Attribute: unreal_bake_folder + // --------------------------------------------- + TArray AllBakeFolders; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(GeoId, AllBakeFolders, PartId)) + { + FString BakeFolder; + if (AllBakeFolders.Num() > 0 && !AllBakeFolders[0].IsEmpty()) + BakeFolder = AllBakeFolders[0]; + OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_BAKE_FOLDER), BakeFolder); + } + + // Streaming proxy actors/tiles requires a "main" landscape actor + // that contains the shared landscape state. + bool bRequiresSharedLandscape = false; + if (TileActorType == LandscapeActorType::LandscapeStreamingProxy) + bRequiresSharedLandscape = true; + + // ---------------------------------- + // Inject landscape specific tokens + // ---------------------------------- + if (bHasTile) + { + const FString TileValue = FString::FromInt(Heightfield->VolumeTileIndex); + // Tile value needs to go into Output arguments to be available during the bake. + OutputTokens.Add(TEXT("tile"), TileValue); + } + + // ---------------------------------- + // Expand string arguments for various landscape naming aspects. + // ---------------------------------- + + // Update resolver attributes and tokens before we start resolving attributes. + Resolver.SetCachedAttributes(OutputAttributes); + Resolver.SetTokensFromStringMap(OutputTokens); + + SharedLandscapeActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); + SharedLandscapeActorName += NodeNameSuffix; + + LandscapeTileActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, LandscapeTileActorName); + LandscapeTileActorName += NodeNameSuffix; + + LevelPath = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); + + FString TileName = LandscapeTileActorName; + + // Note that relative level paths are always interpreted as relative to the default output directory (temp / bake). + // FString TilePackagePath = FPaths::Combine(DefaultOutputPath, LevelPath); + FString TilePackagePath = Resolver.ResolveFullLevelPath(); + + // This crashes UE if the package name does not resolve + //FString TileMapFileName = FPackageName::LongPackageNameToFilename(TilePackagePath, FPackageName::GetMapPackageExtension()); + + FText NotValidReason; + bool bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); + if (!bIsValidLongName) + { + // Try a more naive approach + TilePackagePath = FPaths::Combine(InPackageParams.BakeFolder, LevelPath); + bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); + } + + if (!bIsValidLongName) + { + HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] TilePackagePath is not a valid long name. Reason: %s"), *(NotValidReason.ToString())); + return false; + } + + FString TileMapFileName; + if (!FPackageName::TryConvertLongPackageNameToFilename(TilePackagePath, TileMapFileName, FPackageName::GetMapPackageExtension())) + { + // Rather stop here than crash! + HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] Failed to resolve the TilePackagePath: %s"), *(TilePackagePath)); + return false; + } + + // Find the package for both the world and the tile. + // The world should contain the main landscape actor while + // the tile will contain a Landscape, LandscapeProxy or LandscapeStreamingProxy depending on user settings. + + bool bTileisStreamingProxy = (TileActorType == LandscapeActorType::LandscapeStreamingProxy); + UWorld* TileWorld = nullptr; // World from which to spawn tile actor + ULevel* TileLevel = nullptr; // Level in which to spawn tile actor + ALandscapeProxy* TileActor = nullptr; // Spawned tile actor. + + // ---------------------------------- + // Update package parameters for this tile + // ---------------------------------- + + // NOTE: we don't manually inject a tile number in the object name. This should + // already be encoded in the TileName string. + FHoudiniPackageParams TilePackageParams = InPackageParams; + TilePackageParams.ObjectName = TileName; + + FHoudiniPackageParams LayerPackageParams = InPackageParams; + if (bRequiresSharedLandscape) + { + // Note that layers are shared amongst all the tiles for a given landscape. + LayerPackageParams.ObjectName = SharedLandscapeActorName; + } + else + { + // This landscape tile is a standalone landscape and should have its own material layers. + LayerPackageParams.ObjectName = TileName; + } + + // See if the current heightfield has an unreal_material or unreal_hole_material assigned to it + UMaterialInterface* LandscapeMaterial = nullptr; + UMaterialInterface* LandscapeHoleMaterial = nullptr; + UPhysicalMaterial* LandscapePhysicalMaterial = nullptr; + FHoudiniLandscapeTranslator::GetLandscapeMaterials(*Heightfield, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial); + + // Extract the float data from the Heightfield. + const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; + TArray FloatValues; + float FloatMin, FloatMax; + if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) + return false; + + // Heightfield conversions should always use the global float min/max + // since they need to be calculated externally, potentially across multiple tiles. + FloatMin = fGlobalMin; + FloatMax = fGlobalMax; + + // Get the Unreal landscape size + const int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; + const int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; + + if (!LandscapeTileSizeInfo.bIsCached) + { + // Calculate a landscape size info from this heightfield to be + // used by subsequent tiles on the same landscape + if (FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + LandscapeTileSizeInfo.UnrealSizeX, + LandscapeTileSizeInfo.UnrealSizeY, + LandscapeTileSizeInfo.NumSectionsPerComponent, + LandscapeTileSizeInfo.NumQuadsPerSection)) + { + LandscapeTileSizeInfo.bIsCached = true; + } + else + { + return false; + } + } + + const int32 UnrealTileSizeX = LandscapeTileSizeInfo.UnrealSizeX; + const int32 UnrealTileSizeY = LandscapeTileSizeInfo.UnrealSizeY; + const int32 NumSectionPerLandscapeComponent = LandscapeTileSizeInfo.NumSectionsPerComponent; + const int32 NumQuadsPerLandscapeSection = LandscapeTileSizeInfo.NumQuadsPerSection; + + // ---------------------------------------------------- + // Export of layer textures + // ---------------------------------------------------- + // Export textures, if enabled. Mostly used for debugging at the moment. + bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; + if (bExportTexture) + { + // Export raw height data to texture + FString TextureName = TilePackageParams.ObjectName + TEXT("_height_raw"); + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + FloatValues, + FloatMin, + FloatMax); + } + + // Look for all the layers/masks corresponding to the current heightfield. + TArray< const FHoudiniGeoPartObject* > FoundLayers; + FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); + + // Get the updated layers. + TArray LayerInfos; + + if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, + LayerMinimums, LayerMaximums, LayerInfos, false, + TilePackageParams, + LayerPackageParams, + OutCreatedPackages)) + return false; + + // Convert Houdini's heightfield data to Unreal's landscape data + TArray IntHeightData; + FTransform TileTransform; + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + FloatValues, VolumeInfo, + UnrealTileSizeX, UnrealTileSizeY, + FloatMin, FloatMax, + IntHeightData, TileTransform)) + return false; + + // ---------------------------------------------------- + // Property changes that we want to track + // ---------------------------------------------------- + + bool bModifiedLandscapeActor = false; + bool bModifiedSharedLandscapeActor = false; + bool bSharedLandscapeMaterialChanged = false; + bool bSharedLandscapeHoleMaterialChanged = false; + bool bSharedPhysicalMaterialChanged = false; + bool bTileLandscapeMaterialChanged = false; + bool bTileLandscapeHoleMaterialChanged = false; + bool bTilePhysicalMaterialChanged = false; + bool bCreatedMap = false; + bool bCreatedTileActor = false; + bool bHeightLayerDataChanged = false; + bool bCustomLayerDataChanged = false; + + // ---------------------------------------------------- + // Calculate Tile location and landscape offset + // ---------------------------------------------------- + FTransform LandscapeTransform; + FIntPoint TileLoc; + + // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate + // for any landscape shifts due to section base alignment offsets. + CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeReferenceLocation, LandscapeTransform, TileLoc); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Transform: %s"), *TileTransform.ToString()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape Transform: %s"), *LandscapeTransform.ToString()); + + + // ---------------------------------------------------- + // Find or create *shared* landscape + // ---------------------------------------------------- + + ALandscape* SharedLandscapeActor = nullptr; + bool bCreatedSharedLandscape = false; + + if (bRequiresSharedLandscape) + { + // Streaming proxy tiles always require a "shared landscape" that contains the + // various landscape properties to be shared amongst all the tiles. + AActor* FoundActor = nullptr; + SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, SharedLandscapeActorName, FoundActor); + + bool bIsValidSharedLandscape = IsValid(SharedLandscapeActor); + + if (bIsValidSharedLandscape) + { + // We have a target landscape. Check whether it is compatible with the Houdini volume. + ULandscapeInfo* LandscapeInfo = SharedLandscapeActor->GetLandscapeInfo(); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape: %s"), *(SharedLandscapeActor->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape with num proxies: %d"), LandscapeInfo->Proxies.Num()); + + if (!LandscapeExtent.bIsCached) + { + LandscapeInfo->FixupProxiesTransform(); + // Cache the landscape extents. Note that GetLandscapeExtent() will only take into account the currently loaded landscape tiles. + PopulateLandscapeExtents(LandscapeExtent, LandscapeInfo); + } + + bool bIsCompatible = IsLandscapeInfoCompatible( + LandscapeInfo, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Checking landscape compatibility ...")); + bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); + if (!bIsCompatible) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Shared landscape is incompatible. Cannot reuse.")); + // We can't resize the landscape in-place. We have to create a new one. + DestroyLandscape(SharedLandscapeActor); + SharedLandscapeActor = nullptr; + bIsValidSharedLandscape = false; + } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Existing shared landscape is compatible.")); + } + } + + if (!bIsValidSharedLandscape) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Create new shared landscape...")); + // Create and configure the main landscape actor. + // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos + SharedLandscapeActor = InWorld->SpawnActor(); + if (SharedLandscapeActor) + { + CreatedUntrackedOutputs.Add( SharedLandscapeActor ); + + // NOTE that shared landscape is always located at the origin, but not the tile actors. The + // tiles are properly transformed. + + // If we working with landscape tiles, this actor will become the "Main landscape" actor but + // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. + SharedLandscapeActor->bCanHaveLayersContent = false; + SharedLandscapeActor->ComponentSizeQuads = NumQuadsPerLandscapeSection*NumSectionPerLandscapeComponent; + SharedLandscapeActor->NumSubsections = NumSectionPerLandscapeComponent; + SharedLandscapeActor->SubsectionSizeQuads = NumQuadsPerLandscapeSection; + SharedLandscapeActor->SetLandscapeGuid( FGuid::NewGuid() ); + SharedLandscapeActor->bCastStaticShadow = false; + for (const auto& ImportLayerInfo : LayerInfos) + { + SharedLandscapeActor->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(ImportLayerInfo.LayerInfo)); + } + SharedLandscapeActor->CreateLandscapeInfo(); + bCreatedSharedLandscape = true; + + // NOTE: It is important to set Landscape materials BEFORE blitting layer data. For example, setting + // data in the visibility layer (on tiles) will have no effect until Landscape materials have been applied / processed. + SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + + // Ensure the landscape actor name and label matches `LandscapeActorName`. + FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, SharedLandscapeActorName); + + SharedLandscapeActor->MarkPackageDirty(); + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(InWorld->GetPathName()) ); + return false; + } + } + // else -- Reusing shared landscape + } + + if (SharedLandscapeActor) + { + // Ensure the existing landscape actor transform is correct. + SharedLandscapeActor->SetActorRelativeTransform(LandscapeTransform); + + bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; + bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; + if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + { + DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + } + + if (bSharedLandscapeMaterialChanged) + { + SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + + } + if (bSharedLandscapeHoleMaterialChanged) + { + SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + + } + + bSharedPhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? (SharedLandscapeActor->DefaultPhysMaterial != LandscapePhysicalMaterial) : false; + if (bSharedPhysicalMaterialChanged) + { + DoPreEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); + SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; + SharedLandscapeActor->ChangedPhysMaterial(); + } + } + + // ---------------------------------------------------- + // Find Landscape actor / tile + // ---------------------------------------------------- + + // Find an actor with the given name. The TileWorld and TileLevel returned should be + // used to spawn the new actor, if the actor itself could not be found. + //bool bCreatedPackage = false; + // TileActor = FindExistingLandscapeActor( + // InWorld, InOutput, ValidLandscapes, + // UnrealLandscapeSizeX, UnrealLandscapeSizeY, LandscapeTileActorName, + // LevelPath, TileWorld, TileLevel, bCreatedPackage); + + // Currently the Temp Cook mode is not concerned with creating packages. This will, at the time of writing, + // exclusively be dealt with during Bake mode so don't bother with searching / creating other packages. + + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (IsValid(HAC)) + { + TileWorld = HAC->GetWorld(); + TileLevel = HAC->GetComponentLevel(); + } + else + { + TileWorld = InWorld; + TileLevel = InWorld->PersistentLevel; + } + + check(TileWorld); + check(TileLevel); + + AActor* FoundActor = nullptr; + if (InPackageParams.PackageMode == EPackageMode::Bake) + { + // When baking, See if we can find any landscape / proxy actors for this tile in the TileLevel. + // If we find any actors that match the name but not the type, or the actors are pending kill, then + // rename them so that we can spawn new actors. + switch (TileActorType) + { + case LandscapeActorType::LandscapeActor: + TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); + break; + case LandscapeActorType::LandscapeStreamingProxy: + TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); + break; + default: + TileActor = nullptr; + } + } + else + { + // In temp mode, only consider our previous output landscapes, + // or our input landscapes that have the "update input landscape" option enabled + ALandscapeProxy* FoundLandscapeProxy = nullptr; + + // Try to see if we have an input landscape that matches the size of the current HGPO + for (int nIdx = 0; nIdx < InputLandscapesToUpdate.Num(); nIdx++) + { + ALandscapeProxy* CurrentInputLandscape = InputLandscapesToUpdate[nIdx]; + if (!CurrentInputLandscape) + continue; + + if (SharedLandscapeActor && CurrentInputLandscape->GetLandscapeActor() != SharedLandscapeActor) + // This tile actor no longer associated with the current shared landscape + continue; + + ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); + if (!CurrentInfo) + continue; + + int32 InputMinX = 0; + int32 InputMinY = 0; + int32 InputMaxX = 0; + int32 InputMaxY = 0; + if (!LandscapeExtent.bIsCached) + { + PopulateLandscapeExtents(LandscapeExtent, CurrentInfo); + } + + if (!LandscapeExtent.bIsCached) + { + HOUDINI_LOG_WARNING(TEXT("Warning: Could not determine landscape extents. Cannot re-use input landscape actor.")); + continue; + } + + InputMinX = LandscapeExtent.MinY; + InputMinY = LandscapeExtent.MinY; + InputMaxX = LandscapeExtent.MaxX; + InputMaxY = LandscapeExtent.MaxY; + + // If the full size matches, we'll update that input landscape + bool SizeMatch = false; + if ((InputMaxX - InputMinX + 1) == UnrealTileSizeX && (InputMaxY - InputMinY + 1) == UnrealTileSizeY) + SizeMatch = true; + + // HF and landscape don't match, try another one + if (!SizeMatch) + continue; + + // Replace FoundLandscape by that input landscape + FoundLandscapeProxy = CurrentInputLandscape; + + // We've found a valid input landscape, remove it from the input array so we don't try to update it multiple times + InputLandscapesToUpdate.RemoveAt(nIdx); + break; + } + + if (!FoundLandscapeProxy) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Could not find input landscape to update. Searching output objects...")); + + // Try to see if we can reuse one of our previous output landscape. + // Keep track of the previous cook's landscapes + TMap& OldOutputObjects = InOutput->GetOutputObjects(); + for (auto& CurrentLandscape : OldOutputObjects) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentLandscape.Value.OutputObject); + if (!LandscapePtr) + continue; + + FoundLandscapeProxy = LandscapePtr->GetRawPtr(); + if (!FoundLandscapeProxy) + { + // We may need to manually load the object + //OldLandscapeProxy = LandscapePtr->GetSoftPtr().LoadSynchronous(); + FoundLandscapeProxy = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); + } + + if (!IsValid(FoundLandscapeProxy)) + continue; + + // We need to make sure that this landscape is not one of our input landscape + // This would happen if we were previously updating it, but just turned the option off + // In that case, the landscape would be in both our inputs and outputs, + // but with the "Update Input Data" option off + if (InAllInputLandscapes.Contains(FoundLandscapeProxy)) + { + FoundLandscapeProxy = nullptr; + continue; + } + + if (SharedLandscapeActor && FoundLandscapeProxy->GetLandscapeActor() != SharedLandscapeActor) + { + // This landscape proxy is no longer part of the shared landscape. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Output landscape proxy is no longer part of the landscape. Skipping")); + FoundLandscapeProxy = nullptr; + continue; + } + + // If we found a possible candidate, make sure that its size matches ours as we can only update a landscape tile of the same size + if (!IsLandscapeTileCompatible( + FoundLandscapeProxy, + UnrealTileSizeX-1, + UnrealTileSizeY-1, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection)) + { + FoundLandscapeProxy = nullptr; + continue; + } + + if (SharedLandscapeActor) + { + if (FoundLandscapeProxy->GetLandscapeGuid() != SharedLandscapeActor->GetLandscapeGuid()) + { + FoundLandscapeProxy = nullptr; + continue; + } + } + + // TODO: we probably need to do some more checks with tiled landscapes as well? + + // We found a valid Candidate! + if (FoundLandscapeProxy) + { + break; + } + } + } + + + + if (IsValid(FoundLandscapeProxy)) + { + TileActor = FoundLandscapeProxy; + if (TileActor->GetName() != LandscapeTileActorName) + { + // Ensure the TileActor is named correctly + FHoudiniEngineUtils::SafeRenameActor(TileActor, LandscapeTileActorName); + } + } + } + + // NOTE: We don't need to delete old landscape tiles (FoundActor != TileActor) here. That is an old + // output that should get cleaned up automatically. + + if (IsValid(TileActor)) + { + check(!(TileActor->IsPendingKill())); + + // ---------------------------------------------------- + // Check landscape compatibility + // ---------------------------------------------------- + + bool bIsCompatible = IsLandscapeTileCompatible( + TileActor, + UnrealTileSizeX-1, + UnrealTileSizeY-1, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection); + + bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(TileActor, TileActorType); + + if (!bIsCompatible) + { + // Can't reuse this tile actor since the landscape dimensions doesn't match or the actor type has changed. + if (TileActor->IsA()) + { + // This landscape tile needs to be unregistered from the landscape info. + ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); + if (IsValid(LandscapeInfo)) + { + LandscapeInfo->UnregisterActor(TileActor); + } + } + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Incompatible tile actor. Destroying: %s"), *(TileActor->GetPathName())); + TileActor->Destroy(); + TileActor = nullptr; + } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape tile is compatible: %s"), *(TileActor->GetPathName())); + } + } + + // ---------------------------------------------------- + // Create or update landscape / tile. + // ---------------------------------------------------- + // Note that a single heightfield generated in Houdini can be treated + // as either a landscape tile (LandscapeStreamingProxy) or a standalone + // landscape (ALandscape). This determination is made purely from user specified + // attributes. No "clever logic" in here, please! + + ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; + ALandscape* CachedLandscapeActor = nullptr; + ULandscapeInfo *LandscapeInfo; + +#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Loc: %d, %d"), TileLoc.X, TileLoc.Y); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Size: %d, %d"), UnrealTileSizeX, UnrealTileSizeY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Quads/Section: %d"), NumQuadsPerLandscapeSection); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Sections/Component: %d"), NumSectionPerLandscapeComponent); +#endif + + if (!TileActor) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Creating new tile actor: %s"), *(LandscapeTileActorName)); + // Create a new Landscape tile in the TileWorld + TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( + IntHeightData, LayerInfos, TileTransform, TileLoc, + UnrealTileSizeX, UnrealTileSizeY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, + LandscapeTileActorName, + TileActorType, + SharedLandscapeActor, + TileWorld, + TileLevel, + InPackageParams); + + if (!TileActor || !TileActor->IsValidLowLevel()) + return false; + + LandscapeInfo = TileActor->GetLandscapeInfo(); + + bCreatedTileActor = true; + bTileLandscapeMaterialChanged = true; + bTileLandscapeHoleMaterialChanged = true; + bTilePhysicalMaterialChanged = true; + bHeightLayerDataChanged = true; + bCustomLayerDataChanged = true; + } + else + { + LandscapeInfo = TileActor->GetLandscapeInfo(); + + // Always update the transform, even if the HGPO transform hasn't changed, + // If we change the number of tiles, or switch from outputting single tile to multiple, + // then its fairly likely that the unreal transform has changed even if the + // Houdini Transform remained the same + bool bUpdateTransform = !TileActor->GetActorTransform().Equals(TileTransform); + + // Update existing landscape / tile + if (SharedLandscapeActor && TileActorType == LandscapeActorType::LandscapeStreamingProxy) + { + TileActor->FixupSharedData(SharedLandscapeActor); + if (bUpdateTransform) + { + TileActor->SetAbsoluteSectionBase(TileLoc); + LandscapeInfo->FixupProxiesTransform(); + LandscapeInfo->RecreateLandscapeInfo(InWorld,true); + } + + // This is a tile with a shared landscape. + // Check whether the LandscapeActor changed. If so, update the proxy's shared properties. + CachedStreamingProxyActor = Cast(TileActor); + if (SharedLandscapeActor) + { + if (CachedStreamingProxyActor) + bModifiedLandscapeActor = CachedStreamingProxyActor->LandscapeActor != SharedLandscapeActor; + else + bModifiedLandscapeActor = true; + + if (bModifiedLandscapeActor) + { + CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; + // We need to force a state update through PostEditChangeProperty here in order to initialize + // since we're about to perform additional data updates on this tile. + DoPostEditChangeProperty(CachedStreamingProxyActor, "LandscapeActor"); + } + } + else + { + CachedStreamingProxyActor->LandscapeActor = nullptr; + } + + } + else + { + // This is a standalone tile / landscape actor. + if (bUpdateTransform) + { + TileActor->SetActorRelativeTransform(TileTransform); + TileActor->SetAbsoluteSectionBase(TileLoc); + } + } + + CachedLandscapeActor = TileActor->GetLandscapeActor(); + + ULandscapeInfo* PreviousInfo = TileActor->GetLandscapeInfo(); + if (!PreviousInfo) + return false; + + FIntRect BoundingRect = TileActor->GetBoundingRect(); + FIntPoint SectionOffset = TileActor->GetSectionBaseOffset(); + + // Landscape region to update + const int32 MinX = TileLoc.X; + const int32 MaxX = TileLoc.X + UnrealTileSizeX - 1; + const int32 MinY = TileLoc.Y; + const int32 MaxY = TileLoc.Y + UnrealTileSizeY - 1; + + // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. + // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools + // though the *Accessors do additional things like update normals and foliage. + + // Update height if it has been changed. + if (Heightfield->bHasGeoChanged) + { + // It is important to update the heightmap through HeightmapAccessor this since it will properly + // update normals and foliage. + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); + bHeightLayerDataChanged = true; + } + + // Update the layers on the landscape. + for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) + { + if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. + FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + + bCustomLayerDataChanged = true; + } + + bModifiedLandscapeActor = true; + } + + // ---------------------------------------------------- + // Update tile materials + // ---------------------------------------------------- + // TODO: These material updates can possibly be skipped if we have already performed this + // check on a SharedLandscape. + bTileLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (TileActor->GetLandscapeMaterial() != LandscapeMaterial) : false; + bTileLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (TileActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); + + if (bTileLandscapeMaterialChanged) + TileActor->LandscapeMaterial = LandscapeMaterial; + + if (bTileLandscapeHoleMaterialChanged) + TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + + bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; + if (bTilePhysicalMaterialChanged) + { + DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); + TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; + //TileActor->ChangedPhysMaterial(); + } + + // ---------------------------------------------------- + // Apply actor tags + // ---------------------------------------------------- + + // See if we have unreal_tag_ attribute + TArray Tags; + if (TileActor && FHoudiniEngineUtils::GetUnrealTagAttributes(GeoId, PartId, Tags)) + { + TileActor->Tags = Tags; + } + + // ---------------------------------------------------- + // Update actor states based on data updates + // ---------------------------------------------------- + // Based on ALandscape and ALandscapeStreamingProxy PostEditChangeProperty() implementations, + // effect appropriate state updates based on the property updates that was performed in + // the above code. + + if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + { + check(SharedLandscapeActor); + DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + } + + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + { + check(TileActor); + // Tile material changes are only processed if it wasn't already done for a shared + // landscape since the shared landscape should have already propagated the changes to associated proxies. + DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); + } + + if (bSharedPhysicalMaterialChanged) + { + check(SharedLandscapeActor); + DoPostEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); + } + + if (bTilePhysicalMaterialChanged) + { + check(TileActor); + DoPostEditChangeProperty(TileActor, "DefaultPhysMaterial"); + } + + if (bModifiedSharedLandscapeActor) + { + SharedLandscapeActor->PostEditChange(); + } + + if (bModifiedLandscapeActor) + { + TileActor->PostEditChange(); + } + + { + FLandscapeEditDataInterface LandscapeEdit(TileActor->GetLandscapeInfo()); + LandscapeEdit.RecalculateNormals(); + } + + if (LandscapeInfo) + { + LandscapeInfo->RecreateLandscapeInfo(InWorld, true); + LandscapeInfo->RecreateCollisionComponents(); + } + + { + // Update UProperties + + // Apply detail attributes to both the Shared Landscape and the Landscape Tile actor + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + GeoId, PartId, + true, + INDEX_NONE, INDEX_NONE, INDEX_NONE, + PropertyAttributes)) + { + if (IsValid(TileActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); + } + if (IsValid(SharedLandscapeActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(SharedLandscapeActor, PropertyAttributes); + } + } + + // Apply point attributes only to the Shared Landscape and the Landscape Tile actor + PropertyAttributes.Empty(); + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + GeoId, PartId, + false, + 0, INDEX_NONE, 0, + PropertyAttributes)) + { + if (IsValid(TileActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); + } + } + } + + // Add objects to the HAC output. + SetLandscapeActorAsOutput( + InOutput, + InAllInputLandscapes, + OutputAttributes, + OutputTokens, + SharedLandscapeActor, + SharedLandscapeActorParent, + bCreatedSharedLandscape, + HeightfieldIdentifier, + TileActor, + InPackageParams.PackageMode); + +#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + if (LandscapeInfo) + { + int32 MinX, MinY, MaxX, MaxY; + LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape extent: %d, %d -> %d, %d"), MinX, MinY, MaxX, MaxY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Cached extent: %d, %d -> %d, %d"), LandscapeExtent.MinX, LandscapeExtent.MinY, LandscapeExtent.MaxX, LandscapeExtent.MaxY); + } + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Ending with num of output objects: %d"), InOutput->GetOutputObjects().Num()); +#endif + + return true; +} + +bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, UWorld* World, const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, FHoudiniPackageParams InPackageParams, + TSet& ClearedLayers, + TArray& OutCreatedPackages) +{ + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::OutputLandscape_EditableLayer] =======================================================================")); + + check(LayerMinimums.Contains(TEXT("height"))); + check(LayerMaximums.Contains(TEXT("height"))); + + float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); + float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); + + if (!InOutput || InOutput->IsPendingKill()) + return false; + + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (!IsValid(HAC)) + return false; + + // Get the height map. + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + if (!Heightfield) + return false; + + if (Heightfield->Type != EHoudiniPartType::Volume) + return false; + + const HAPI_NodeId GeoId = Heightfield->GeoId; + const HAPI_PartId PartId = Heightfield->PartId; + + TArray StrData; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_name + // --------------------------------------------- + StrData.Empty(); + FString EditableLayerName; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME, AttributeInfo, StrData, 1)) + { + if (StrData.Num() > 0) + { + EditableLayerName = StrData[0]; + } + } + if (EditableLayerName.IsEmpty()) + return false; + + // Construct the identifier of the Heightfield geo part. + FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); + HeightfieldIdentifier.PartName = Heightfield->PartName; + + // Extract the float data from the Heightfield. + const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; + TArray FloatValues; + float FloatMin, FloatMax; + if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) + return false; + + // Get the Unreal landscape size + const int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; + const int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; + + if (!LandscapeTileSizeInfo.bIsCached) + { + // Calculate a landscape size info from this heightfield to be + // used by subsequent tiles on the same landscape + if (FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + LandscapeTileSizeInfo.UnrealSizeX, + LandscapeTileSizeInfo.UnrealSizeY, + LandscapeTileSizeInfo.NumSectionsPerComponent, + LandscapeTileSizeInfo.NumQuadsPerSection)) + { + LandscapeTileSizeInfo.bIsCached = true; + } + else + { + return false; + } + } + + TMap OutputTokens; + FHoudiniAttributeResolver Resolver; + // Update resolver attributes and tokens before we start resolving attributes. + InPackageParams.UpdateTokensFromParams(World, HAC, OutputTokens); + + // --------------------------------------------- + // Attribute: unreal_landscape_actor_name + // --------------------------------------------- + // Retrieve the name of the main Landscape actor to look for + FString TargetLandscapeName = "Input0"; + StrData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, AttributeInfo, StrData, 1)) + { + if (StrData.Num() > 0 && !StrData[0].IsEmpty()) + TargetLandscapeName = StrData[0]; + } + + Resolver.SetAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, TargetLandscapeName); + Resolver.SetTokensFromStringMap(OutputTokens); + TargetLandscapeName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, TargetLandscapeName); + + // --------------------------------------------- + // Find the landscape that we're targeting for output + // --------------------------------------------- + ALandscapeProxy* TargetLandscapeProxy = FindTargetLandscapeProxy(TargetLandscapeName, World, InAllInputLandscapes); + if (!IsValid(TargetLandscapeProxy)) + { + HOUDINI_LOG_WARNING(TEXT("Could not find landscape actor: %s"), *(TargetLandscapeName)); + return false; + } + ALandscape* TargetLandscape = TargetLandscapeProxy->GetLandscapeActor(); + check(TargetLandscape); + + ULandscapeInfo* TargetLandscapeInfo = TargetLandscapeProxy->GetLandscapeInfo(); + const FTransform TargetLandscapeTransform = TargetLandscape->GetActorTransform(); + + const float DestHeightScale = TargetLandscapeProxy->LandscapeActorToWorld().GetScale3D().Z; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Height Scale: %f"), DestHeightScale); + + // Create the layer if it doesn't exist + int32 EditLayerIndex = TargetLandscape->GetLayerIndex(FName(EditableLayerName)); + const FLandscapeLayer* TargetLayer = TargetLandscape->GetLayer(EditLayerIndex); + if (!TargetLayer) + { + // Create new layer + EditLayerIndex = TargetLandscape->CreateLayer(FName(EditableLayerName)); + TargetLayer = TargetLandscape->GetLayer(FName(EditableLayerName)); + } + + if (!TargetLayer) + { + HOUDINI_LOG_WARNING(TEXT("Could not find or create target layer: %s"), *(TargetLandscapeName)); + return false; + } + + { + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_after + // --------------------------------------------- + StrData.Empty(); + FString AfterLayerName; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER, AttributeInfo, StrData, 1)) + { + if (StrData.Num() > 0) + { + AfterLayerName = StrData[0]; + } + } + if (!AfterLayerName.IsEmpty()) + { + // If we have an "after layer", move the output layer into position. + int32 NewLayerIndex = TargetLandscape->GetLayerIndex(FName(AfterLayerName)); + if (NewLayerIndex != INDEX_NONE && EditLayerIndex != NewLayerIndex) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Moving layer from %d to %d"), EditLayerIndex, NewLayerIndex); + if (NewLayerIndex < EditLayerIndex) + { + NewLayerIndex += 1; + } + TargetLandscape->ReorderLayer(EditLayerIndex, NewLayerIndex); + + // Ensure we have the correct layer/index + EditLayerIndex = TargetLandscape->GetLayerIndex(FName(EditableLayerName)); + TargetLayer = TargetLandscape->GetLayer(EditLayerIndex); + } + } + } + + // ---------------------------------------------------- + // Convert Heightfield data + // ---------------------------------------------------- + // Convert Houdini's heightfield data to Unreal's landscape data + TArray IntHeightData; + FTransform TileTransform; + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + FloatValues, VolumeInfo, + LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, + FloatMin, FloatMax, + IntHeightData, TileTransform, + false, true, DestHeightScale)) + return false; + + + // ---------------------------------------------------- + // Calculate Tile location and landscape offset + // ---------------------------------------------------- + FTransform SrcLandscapeTransform, HACTransform; + FIntPoint TileLoc; + + // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate + // for any landscape shifts due to section base alignment offsets. + CalculateTileLocation(LandscapeTileSizeInfo.NumSectionsPerComponent, LandscapeTileSizeInfo.NumQuadsPerSection, TileTransform, LandscapeReferenceLocation, SrcLandscapeTransform, TileLoc); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Landscape Transform: %s"), *(TargetLandscapeTransform.ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Tile Transform: %s"), *(TileTransform.ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Tile Size: %d, %d"), LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY); + + HACTransform = HAC->GetComponentTransform(); + SrcLandscapeTransform = HACTransform; + + // ---------------------------------------------------- + // Convert the tile coordinates to quad space on the target Landscape. + // ---------------------------------------------------- + FVector RelTileLoc = (TileTransform*HACTransform).GetLocation(); + RelTileLoc = TargetLandscapeTransform.InverseTransformPosition(RelTileLoc); + + TileLoc.X = FMath::RoundFromZero(RelTileLoc.X); + TileLoc.Y = FMath::RoundFromZero(RelTileLoc.Y); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Sections per component: %d"), (TargetLandscapeInfo->ComponentNumSubsections)); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Quads per component: %d"), (TargetLandscapeInfo->ComponentSizeQuads)); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Relative Tile Position: %s"), *(RelTileLoc.ToString())); + + FVector TileMin, TileMax; + TileMin.X = TileLoc.X; + TileMin.Y = TileLoc.Y; + TileMax.X = TileLoc.X + LandscapeTileSizeInfo.UnrealSizeX - 1; + TileMax.Y = TileLoc.Y + LandscapeTileSizeInfo.UnrealSizeY - 1; + TileMin.Z = TileMax.Z = 0.f; + + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Src Landscape Transform: %s"), *(SrcLandscapeTransform.ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Src Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); + + FTransform DestLandscapeTransform = TargetLandscapeProxy->LandscapeActorToWorld(); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Landscape Transform: %s"), *(DestLandscapeTransform.ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Actor Transform: %s"), *(TargetLandscape->GetTransform().ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); + + // NOTE: we don't manually inject a tile number in the object name. This should + // already be encoded in the TileName string. + FHoudiniPackageParams TilePackageParams = InPackageParams; + FHoudiniPackageParams LayerPackageParams = InPackageParams; + + TilePackageParams.ObjectName = TargetLandscapeName; + LayerPackageParams.ObjectName = TargetLandscapeName; + + // Look for all the layers/masks corresponding to the current heightfield. + TArray< const FHoudiniGeoPartObject* > FoundLayers; + FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Found %d output layers."), FoundLayers.Num()); + + // Get the updated layers. + TArray LayerInfos; + if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, + LayerMinimums, LayerMaximums, LayerInfos, false, + TilePackageParams, + LayerPackageParams, + OutCreatedPackages)) + return false; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Generated %d layer infos."), LayerInfos.Num()); + + // Collect existing layers on the landscape + TMap ExistingLayers; + int32 NumTargetLayers = TargetLandscape->EditorLayerSettings.Num(); + for(int32 LayerIndex = 0; LayerIndex < NumTargetLayers; LayerIndex++) + { + FLandscapeEditorLayerSettings& Settings = TargetLandscape->EditorLayerSettings[LayerIndex]; + if (!Settings.LayerInfoObj) + continue; + ExistingLayers.Add(Settings.LayerInfoObj->LayerName, LayerIndex); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Found existing landscape material layer: %s"), *(Settings.LayerInfoObj->LayerName.ToString())); + } + + bool bLayerHasChanged = false; + for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) + { + // Ensure weight blending is disabled for all layers coming from Houdini otherwise material layer outputs + // won't blend correctly on landscapes in Editable Layer mode. + InLayerInfo.LayerInfo->bNoWeightBlend = true; + + if (ExistingLayers.Contains(InLayerInfo.LayerName)) + { + + int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); + // NOTE: If we hot-swap existing Layer Info objects here, it leads to errors about landscape drawing resources that can't be released. + // For now, just modify any existing layers in place until we can figure out how to properly swap Layer Info objects. + + // // The landscape already contains this layer. Ensure it is pointing to the correct layer info object. + // bLayerHasChanged = TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj != InLayerInfo.LayerInfo; + // if (bLayerHasChanged) + // { + // HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Updating existing layer: %s"), *(InLayerInfo.LayerName.ToString())); + // TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj = InLayerInfo.LayerInfo; + // } + TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj->bNoWeightBlend = true; + } + else + { + // Landscape does not contain this layer. Add it. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Adding new layer: %s"), *(InLayerInfo.LayerName.ToString())); + TargetLandscape->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(InLayerInfo.LayerInfo)); + bLayerHasChanged = true; + } + } + + // Clear layers + if (!ClearedLayers.Contains(EditableLayerName)) + { + bool bClearLayer = false; + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_clear + // --------------------------------------------- + // Check whether we should clear the target edit layer. + TArray IntData; + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0) + { + bClearLayer = IntData[0] != 0; + } + } + + if (bClearLayer) + { + if (TargetLayer) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Clearing layer heightmap: %s"), *EditableLayerName); + ClearedLayers.Add(EditableLayerName); + + // Clear the heightmap + TargetLandscape->ClearLayer(TargetLayer->Guid, nullptr, ELandscapeClearMode::Clear_Heightmap); + + // Clear the paint layers, but only the ones that are being output from Houdini. + for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) + { + if (!ExistingLayers.Contains(InLayerInfo.LayerName)) + continue; + + int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); + TargetLandscape->ClearPaintLayer(TargetLayer->Guid, InLayerInfo.LayerInfo); + } + } + } + } + + + { + // Scope the Edit Layer before we start drawing on ANY of the layers + FScopedSetLandscapeEditingLayer Scope(TargetLandscape, TargetLayer->Guid, [=] { TargetLandscape->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); }); + FLandscapeEditDataInterface LandscapeEdit(TargetLandscapeInfo); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Drawing heightmap..")); + // Draw Heightmap + FHeightmapAccessor HeightmapAccessor(TargetLandscapeInfo); + HeightmapAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, IntHeightData.GetData()); + + // Draw material layers on the landscape + // Update the layers on the landscape. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target has layers content: %d"), TargetLandscape->HasLayersContent()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] IsEditingLayer? %d"), TargetLandscape->GetEditingLayer().IsValid()); + + for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Trying to draw on layer: %s"), *(InLayerInfo.LayerName.ToString())); + + if (InLayerInfo.LayerInfo && InLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. + FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, InLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + if (!ExistingLayers.Contains(InLayerInfo.LayerName)) + continue; + + int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); + FLandscapeEditorLayerSettings& CurLayer = TargetLandscape->EditorLayerSettings[LayerIndex]; + // Draw on the current layer, if it is valid. + if (CurLayer.LayerInfoObj) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Drawing using Alpha accessor. Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); + FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, CurLayer.LayerInfoObj); + AlphaAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, InLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + } + + } // Landscape layer drawing scope + + // Only keep output the output object that corresponds to this layer. Everything else should be removed. + TMap OutputObjects = InOutput->GetOutputObjects(); + TSet StaleOutputs; + OutputObjects.GetKeys(StaleOutputs); + bool bFoundOutputObject = false; + for(auto& Entry : OutputObjects) + { + if (bFoundOutputObject) + continue; // We already have a matching layer output object. Anything else is stale. + + FHoudiniOutputObjectIdentifier& OutputId = Entry.Key; + FHoudiniOutputObject& Object = Entry.Value; + UHoudiniLandscapeEditLayer* EditLayer = Cast(Object.OutputObject); + if (!IsValid(EditLayer)) + continue; + StaleOutputs.Remove(OutputId); + } + + // Clean up stale outputs + for(FHoudiniOutputObjectIdentifier& StaleId : StaleOutputs) + { + FHoudiniOutputObject& OutputObject = OutputObjects.FindChecked(StaleId); + if (UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject)) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + + if (LandscapeProxy) + { + // We shouldn't destroy any input landscapes + if (!InAllInputLandscapes.Contains(LandscapeProxy)) + { + LandscapeProxy->Destroy(); + } + } + } + + OutputObjects.Remove(StaleId); + } + + // Update the output object + FHoudiniOutputObjectIdentifier OutputObjectIdentifier(Heightfield->ObjectId, GeoId, PartId, "EditableLayer"); + FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(OutputObjectIdentifier); + UHoudiniLandscapeEditLayer* LayerPtr = NewObject(InOutput); + LayerPtr->SetSoftPtr(TargetLandscape); + LayerPtr->LayerName = EditableLayerName; + OutputObj.OutputObject = LayerPtr; + // Editable layers doesn't currently require any attributes / tokens to be cached. + // OutputObj.CachedAttributes = OutputAttributes; + // OutputObj.CachedTokens = OutputTokens; + + return true; +} + + +bool +FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( + const ULandscapeInfo* LandscapeInfo, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection + ) +{ + if (!IsValid(LandscapeInfo)) + return false; + + + if (LandscapeInfo->ComponentNumSubsections != InNumSectionsPerComponent) + return false; + + if (LandscapeInfo->SubsectionSizeQuads != InNumQuadsPerSection) + return false; + + int32 MinX, MinY, MaxX, MaxY; + if (LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + { + const int32 NumComponentsX = (MaxX - MinX) / (InNumQuadsPerSection*InNumSectionsPerComponent); + const int32 NumComponentsY = (MaxY - MinY) / (InNumQuadsPerSection*InNumSectionsPerComponent); + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::IsLandscapeTileCompatible( + const ALandscapeProxy* TileActor, + const int32 InTileSizeX, + const int32 InTileSizeY, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection +) +{ + check(TileActor); + + // NOTE: We can't compare landscape extents here since the Houdini only knows about the size for single tile. + // and LandscapeInfo will only return extents for the *loaded* landscape tiles. + + // TODO: Add more robust checks to determine landscape compatibility. + + if (!IsLandscapeInfoCompatible(TileActor->GetLandscapeInfo(), InNumSectionsPerComponent, InNumQuadsPerSection)) + return false; + + const FIntRect Bounds = TileActor->GetBoundingRect(); + const FIntPoint Size = Bounds.Size(); + if (Size.X != InTileSizeX && Size.Y != InTileSizeY) + return false; + + return true; +} + + +bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, LandscapeActorType ActorType) +{ + if (!IsValid(Actor)) + return false; + + switch (ActorType) + { + case LandscapeActorType::LandscapeActor: + return Actor->IsA(); + break; + case LandscapeActorType::LandscapeStreamingProxy: + return Actor->IsA(); + break; + default: + break; + } + + return false; +} + +bool FHoudiniLandscapeTranslator::PopulateLandscapeExtents(FHoudiniLandscapeExtent& Extent, + const ULandscapeInfo* LandscapeInfo) +{ + if (LandscapeInfo->GetLandscapeExtent(Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY)) + { + Extent.ExtentsX = Extent.MaxX - Extent.MinX; + Extent.ExtentsY = Extent.MaxY - Extent.MinY; + Extent.bIsCached = true; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Cached extent: %d, %d -> %d, %d"), Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY); + + return true; + } + return false; +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage, + const EPackageMode& InPackageMode) +{ + if (InPackageMode == EPackageMode::Bake) + return FindExistingLandscapeActor_Bake(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); + else + return FindExistingLandscapeActor_Temp(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + bCreatedPackage = false; + + // // Locate landscape proxy actor when running in baked mode + // AActor* FoundActor = nullptr; + ALandscapeProxy* OutActor = nullptr; + // OutActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); + // // OutActor = FHoudiniEngineUtils::FindActorInWorld(InWorld, FName(InActorName)); + // if (FoundActor && FoundActor != OutActor) + // FoundActor->Destroy(); // nuke it! + // + // if (OutActor) + // { + // // TODO: make sure that the found is actor is actually assigned to the level defined by package path. + // // If the found actor is not from that level, it should be moved there. + // + // OutWorld = OutActor->GetWorld(); + // OutLevel = OutActor->GetLevel(); + // } + // else + { + // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. + bool bActorInWorld = false; + const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + InWorld, + InPackagePath, + true, + OutWorld, + OutLevel, + bCreatedPackage, + bActorInWorld); + + if (!bResult) + { + return nullptr; + } + + // if (!bActorInWorld) + // { + // // The OutLevel is not present in the current world which means we might + // // still find the tile actor in OutWorld. + OutActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); + // } + } + + return OutActor; +} + + +ALandscapeProxy* FHoudiniLandscapeTranslator::FindTargetLandscapeProxy(const FString& ActorName, UWorld* World, + const TArray& LandscapeInputs) +{ + int32 InputIndex = INDEX_NONE; + if (ActorName.StartsWith(TEXT("Input"))) + { + // Extract the numeric value after 'Input'. + FString IndexStr; + ActorName.Split(TEXT("Input"), nullptr, &IndexStr); + if (IndexStr.IsNumeric()) + { + InputIndex = FPlatformString::Atoi(*IndexStr); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FindTargetLandscapeProxy] Extract index %d from actor name: %s"), InputIndex, *ActorName); + } + } + + if (InputIndex != INDEX_NONE) + { + if (!LandscapeInputs.IsValidIndex(InputIndex)) + return nullptr; + return LandscapeInputs[InputIndex]; + } + + return FHoudiniEngineUtils::FindActorInWorldByLabel(World, ActorName); +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + ALandscapeProxy* OutActor = nullptr; + FString ActorName = InActorName + TEXT("_Temp"); + TMap& PrevCookObjects = InOutput->GetOutputObjects(); + + OutWorld = InWorld; + OutLevel = InWorld->PersistentLevel; + + bCreatedPackage = false; + + // Find Landscape proxy for output when running in Temp mode + for(auto& PrevObject : PrevCookObjects) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(PrevObject.Value.OutputObject); + if (!LandscapePtr) + continue; + + OutActor = LandscapePtr->GetRawPtr(); + if (!OutActor) + { + // We may need to manually load the object + OutActor = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); + } + + if (!OutActor) + continue; + + // If we were updating the input landscape before, but arent anymore, + // we could still find it here in the output, ignore them now as we're only looking for previous output + if (ValidLandscapes.Contains(OutActor)) + continue; + + if (OutActor->GetName() != ActorName) + // This is not the droid we're looking for + continue; + + if (OutActor->IsPendingKill()) + { + FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + "_pendingkill"); + continue; + } + + // If we found a possible candidate, make sure that its size matches ours + // as we can only update a landscape of the same size + ULandscapeInfo* PreviousInfo = OutActor->GetLandscapeInfo(); + if (PreviousInfo) + { + int32 PrevMinX = 0; + int32 PrevMinY = 0; + int32 PrevMaxX = 0; + int32 PrevMaxY = 0; + PreviousInfo->GetLandscapeExtent(PrevMinX, PrevMinY, PrevMaxX, PrevMaxY); + + if ((PrevMaxX - PrevMinX + 1) == UnrealLandscapeSizeX && (PrevMaxY - PrevMinY + 1) == UnrealLandscapeSizeY) + { + // The size matches, we can reuse the old landscape. + break; + } + else + { + // We can't reuse this actor. The dimensions does not match. + // We need to rename this actor in order to create a new one with the specified name. + FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + TEXT("_old") ); + OutActor = nullptr; + } + } + } + + return OutActor; +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor, + const EPackageMode InPackageMode) +{ + if (InPackageMode == EPackageMode::Bake) + return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); + else + return SetLandscapeActorAsOutput_Temp(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor) +{ + // We are in bake mode. No outputs to register / add here. + // Do nothing, for now. +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedSharedLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor) +{ + // The main landscape is a special case here. It cannot be registered with the + // output object here, since it is possibly shared by *multiple* outputs so + // we have to deal with the attached and cleanup of the actor manually. + if (bCreatedSharedLandscape && IsValid(SharedLandscapeActorParent)) + { + AttachActorToHAC(InOutput, SharedLandscapeActor); + } + + // TODO: The OutputObject cleanup being performed here should really be part of + // the output object itself (or at the very least be encapsulated in a reusable + // static function somewhere) so that individual output objects can be cleaned + // when necessary without having to duplicate code when its needed. + + // Cleanup any stale output objects + TMap& Outputs = InOutput->GetOutputObjects(); + TArray StaleOutputs; + for (auto& Elem : Outputs) + { + UHoudiniLandscapePtr* LandscapePtr = nullptr; + bool bIsStale = false; + + if (!(Elem.Key == Identifier)) + { + // Identifiers doesn't match so this is definitely a stale output. + StaleOutputs.Add(Elem.Key); + bIsStale = true; + } + + LandscapePtr = Cast(Elem.Value.OutputObject); + if (LandscapePtr) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + + if (LandscapeProxy) + { + // We shouldn't destroy any input landscape, + // or the landscape that we are currently trying to set as output.. + if (!InAllInputLandscapes.Contains(LandscapeProxy) && (LandscapeProxy != LandscapeActor)) + { + // This landscape proxy either doesn't match the landscape identifier + // or it doesn't match the actor we're about to output. Either way, + // get rid of it. + LandscapeProxy->Destroy(); + } + } + } + + if (bIsStale) + { + Elem.Value.OutputObject = nullptr; + } + } + + for (FHoudiniOutputObjectIdentifier& StaleOutput : StaleOutputs) + { + Outputs.Remove(StaleOutput); + } + + + // Send a landscape pointer back to the Output Object for this landscape tile. + FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); + UHoudiniLandscapePtr* LandscapePtr = NewObject(InOutput); + LandscapePtr->SetSoftPtr(LandscapeActor); + OutputObj.OutputObject = LandscapePtr; + OutputObj.CachedAttributes = OutputAttributes; + OutputObj.CachedTokens = OutputTokens; +} + + +bool +FHoudiniLandscapeTranslator::AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor) +{ + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (IsValid(HAC)) + { + InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); + + return true; + } + return false; +} + + +FString +FHoudiniLandscapeTranslator::GetActorNameSuffix(const EPackageMode& InPackageMode) +{ + if(InPackageMode == EPackageMode::CookToTemp) + return "_Temp"; + else + return FString(); +} + +void +FHoudiniLandscapeTranslator::DoPreEditChangeProperty(UObject* Obj, FName PropertyName) +{ + Obj->PreEditChange(FindFProperty(Obj->GetClass(), PropertyName)); +} + +void +FHoudiniLandscapeTranslator::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) +{ + FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); + Obj->PostEditChangeProperty(Evt); +} + +bool +FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + const TArray< float >& HeightfieldFloatValues, + const FHoudiniVolumeInfo& HeightfieldVolumeInfo, + const int32& FinalXSize, const int32& FinalYSize, + float FloatMin, float FloatMax, + TArray< uint16 >& IntHeightData, + FTransform& LandscapeTransform, + const bool NoResize, + const bool bOverrideZScale, + const float CustomZScale) +{ + IntHeightData.Empty(); + LandscapeTransform.SetIdentity(); + + // HF sizes needs an X/Y swap + // NOPE.. not anymore + int32 HoudiniXSize = HeightfieldVolumeInfo.YLength; + int32 HoudiniYSize = HeightfieldVolumeInfo.XLength; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + // Test for potential special cases... + // Just print a warning for now + if (HeightfieldVolumeInfo.MinX != 0) + HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min X is not zero.")); + + if (HeightfieldVolumeInfo.MinY != 0) + HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min Y is not zero.")); + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to uint16 using doubles to get the maximum precision during the conversion + //-------------------------------------------------------------------------------------------------- + + FTransform CurrentVolumeTransform = HeightfieldVolumeInfo.Transform; + + // The ZRange in Houdini (in m) + double MeterZRange = (double)(FloatMax - FloatMin); + + // The corresponding unreal digit range (as unreal uses uint16, max is 65535) + // We may want to not use the full range in order to be able to sculpt the landscape past the min/max values after. + const double dUINT16_MAX = (double)UINT16_MAX; + double DigitZRange = 49152.0; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution) + DigitZRange = dUINT16_MAX - 1.0; + + // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down + double DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); + + // The factor used to convert from Houdini's ZRange to the desired digit range + double ZSpacing = (MeterZRange != 0.0) ? (DigitZRange / MeterZRange) : 0.0; + + // Changes these values if the user wants to loose a lot of precision + // just to keep the same transform as the landscape input + bool bUseDefaultUE4Scaling = false; + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) + bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + + bUseDefaultUE4Scaling |= bOverrideZScale; + + if (bUseDefaultUE4Scaling) + { + //Check that our values are compatible with UE4's default scale values + if (FloatMin < -256.0f || FloatMin > 256.0f || FloatMax < -256.0f || FloatMax > 256.0f) + { + // Warn the user that the landscape conversion will have issues + // invite him to change that setting + HOUDINI_LOG_WARNING( + TEXT("The heightfield's min and max height values are too large for being used with the \"Use Default UE4 scaling\" option.\n \ + The generated Heightfield will likely be incorrectly converted to landscape unless you disable that option in the project settings and recook the asset.")); + } + + DigitZRange = dUINT16_MAX - 1.0; + DigitCenterOffset = 0; + + // Default unreal landscape scaling is -256m:256m at Scale = 100, + // We need to apply the scale back, and swap Y/Z axis + FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f * CustomZScale/100.f; + FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f * CustomZScale/100.f; + + MeterZRange = (double)(FloatMax - FloatMin); + + ZSpacing = ((double)DigitZRange) / MeterZRange; + } + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] DigitCenterOffset: %f"), DigitZRange); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] DigitZRange: %f"), DigitZRange); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] MeterZRange: %f"), MeterZRange); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] ZSpacing: %f"), ZSpacing); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Volume YScale: %f"), CurrentVolumeTransform.GetScale3D().Y); + + // Converting the data from Houdini to Unreal + // For correct orientation in unreal, the point matrix has to be transposed. + IntHeightData.SetNumUninitialized(SizeInPoints); + + int32 nUnreal = 0; + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y + int32 nHoudini = nY + nX * HoudiniYSize; + + // Get the double values in [0 - ZRange] + double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; + + // Then convert it to [0 - DesiredRange] and center it + DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; + IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); + } + } + + //-------------------------------------------------------------------------------------------------- + // 2. Resample / Pad the int data so that if fits unreal size requirements + //-------------------------------------------------------------------------------------------------- + + // UE has specific size requirements for landscape, + // so we might need to pad/resample the heightfield data + FVector LandscapeResizeFactor = FVector::OneVector; + FVector LandscapePositionOffsetInPixels = FVector::ZeroVector; + if (!NoResize) + { + // Try to resize the data + if (!FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( + IntHeightData, + HoudiniXSize, HoudiniYSize, FinalXSize, FinalYSize, + LandscapeResizeFactor, LandscapePositionOffsetInPixels)) + return false; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Calculating the proper transform for the landscape to be sized and positionned properly + //-------------------------------------------------------------------------------------------------- + + // Scale: + // Calculating the equivalent scale to match Houdini's Terrain Size in Unreal + FVector LandscapeScale; + + // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing + // Swap Y/Z axis from H to UE + LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; + LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Z * 2.0f; + + // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini + // Unreal has a default Z range is 512m for a scale of a 100% + LandscapeScale.Z = (float)((double)(dUINT16_MAX / DigitZRange) * MeterZRange / 512.0); + if (bUseDefaultUE4Scaling) + { + // Swap Y/Z axis from H to UE + LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Y * 2.0f; + } + LandscapeScale *= 100.f; + + // If the data was resized and not expanded, we need to modify the landscape's scale + LandscapeScale *= LandscapeResizeFactor; + + // Don't allow a zero scale, as this results in divide by 0 operations in FMatrix::InverseFast in the landscape component. + if (FMath::IsNearlyZero(LandscapeScale.Z)) + LandscapeScale.Z = 1.0f; + + // We'll use the position from Houdini, but we will need to offset the Z Position to center the + // values properly as the data has been offset by the conversion to uint16 + FVector LandscapePosition = CurrentVolumeTransform.GetLocation(); + //LandscapePosition.Z = 0.0f; + + // We need to calculate the position offset so that Houdini and Unreal have the same Zero position + // In Unreal, zero has a height value of 32768. + // These values are then divided by 128 internally, and then multiplied by the Landscape's Z scale + // ( DIGIT - 32768 ) / 128 * ZScale = ZOffset + + // We need the Digit (Unreal) value of Houdini's zero for the scale calculation + // ( float and int32 are used for this because 0 might be out of the landscape Z range! + // when using the full range, this would cause an overflow for a uint16!! ) + float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); + float ZOffset = -(HoudiniZeroValueInDigit - 32768.0f) / 128.0f * LandscapeScale.Z; + + LandscapePosition.Z += ZOffset; + + // If we have padded the data when resizing the landscape, we need to offset the position because of + // the added values on the topLeft Corner of the Landscape + if (LandscapePositionOffsetInPixels != FVector::ZeroVector) + { + FVector LandscapeOffset = LandscapePositionOffsetInPixels * LandscapeScale; + LandscapeOffset.Z = 0.0f; + + LandscapePosition += LandscapeOffset; + } + + /* + FTransform TempTransform; + TempTransform.SetIdentity(); + { + // Houdini Pivot (center of the Landscape) + FVector HoudiniPivot = FVector((FinalXSize-1) * 100.0f / 2.0f, (FinalYSize-1) * 100.0f / 2.0f, 0.0f); + + // Center the landscape + FVector CenterLocation = LandscapePosition - HoudiniPivot; + + // Rotate the vector using the H rotation + // We need to compensate for the "default" HF Transform + FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); + Rotator.Yaw -= 90.0f; + Rotator.Roll += 90.0f; + FVector RotatedLocation = Rotator.RotateVector(CenterLocation); + + FQuat LandscapeRotation = FQuat(Rotator) * FQuat::Identity; + + // Return to previous origin + FVector Uncentered = RotatedLocation + HoudiniPivot; + TempTransform = FTransform(LandscapeRotation, Uncentered, LandscapeScale); + } + + LandscapeTransform = TempTransform; + */ + + // We can now set the Landscape position + LandscapeTransform.SetLocation(LandscapePosition); + LandscapeTransform.SetScale3D(LandscapeScale); + + // Rotate the vector using the H rotation + FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); + // We need to compensate for the "default" HF Transform + Rotator.Yaw -= 90.0f; + Rotator.Roll += 90.0f; + + // Only rotate if the rotator is far from zero + if(!Rotator.IsNearlyZero()) + LandscapeTransform.SetRotation(FQuat(Rotator)); + + return true; +} + +template +TArray ResampleData(const TArray& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight) +{ + TArray Result; + Result.Empty(NewWidth * NewHeight); + Result.AddUninitialized(NewWidth * NewHeight); + + const float XScale = (float)(OldWidth - 1) / (NewWidth - 1); + const float YScale = (float)(OldHeight - 1) / (NewHeight - 1); + for (int32 Y = 0; Y < NewHeight; ++Y) + { + for (int32 X = 0; X < NewWidth; ++X) + { + const float OldY = Y * YScale; + const float OldX = X * XScale; + const int32 X0 = FMath::FloorToInt(OldX); + const int32 X1 = FMath::Min(FMath::FloorToInt(OldX) + 1, OldWidth - 1); + const int32 Y0 = FMath::FloorToInt(OldY); + const int32 Y1 = FMath::Min(FMath::FloorToInt(OldY) + 1, OldHeight - 1); + const T& Original00 = Data[Y0 * OldWidth + X0]; + const T& Original10 = Data[Y0 * OldWidth + X1]; + const T& Original01 = Data[Y1 * OldWidth + X0]; + const T& Original11 = Data[Y1 * OldWidth + X1]; + Result[Y * NewWidth + X] = FMath::BiLerp(Original00, Original10, Original01, Original11, FMath::Fractional(OldX), FMath::Fractional(OldY)); + } + } + + return Result; +} + +template +void ExpandData(T* OutData, const T* InData, + int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, + int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY) +{ + const int32 OldWidth = OldMaxX - OldMinX + 1; + const int32 OldHeight = OldMaxY - OldMinY + 1; + const int32 NewWidth = NewMaxX - NewMinX + 1; + const int32 NewHeight = NewMaxY - NewMinY + 1; + const int32 OffsetX = NewMinX - OldMinX; + const int32 OffsetY = NewMinY - OldMinY; + + for (int32 Y = 0; Y < NewHeight; ++Y) + { + const int32 OldY = FMath::Clamp(Y + OffsetY, 0, OldHeight - 1); + + // Pad anything to the left + const T PadLeft = InData[OldY * OldWidth + 0]; + for (int32 X = 0; X < -OffsetX; ++X) + { + OutData[Y * NewWidth + X] = PadLeft; + } + + // Copy one row of the old data + { + const int32 X = FMath::Max(0, -OffsetX); + const int32 OldX = FMath::Clamp(X + OffsetX, 0, OldWidth - 1); + FMemory::Memcpy(&OutData[Y * NewWidth + X], &InData[OldY * OldWidth + OldX], FMath::Min(OldWidth, NewWidth) * sizeof(T)); + } + + const T PadRight = InData[OldY * OldWidth + OldWidth - 1]; + for (int32 X = -OffsetX + OldWidth; X < NewWidth; ++X) + { + OutData[Y * NewWidth + X] = PadRight; + } + } +} + +template +TArray ExpandData(const TArray& Data, + int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, + int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY, + int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr) +{ + const int32 NewWidth = NewMaxX - NewMinX + 1; + const int32 NewHeight = NewMaxY - NewMinY + 1; + + TArray Result; + Result.Empty(NewWidth * NewHeight); + Result.AddUninitialized(NewWidth * NewHeight); + + ExpandData(Result.GetData(), Data.GetData(), + OldMinX, OldMinY, OldMaxX, OldMaxY, + NewMinX, NewMinY, NewMaxX, NewMaxY); + + // Return the padding so we can offset the terrain position after + if (PadOffsetX) + *PadOffsetX = NewMinX; + + if (PadOffsetY) + *PadOffsetY = NewMinY; + + return Result; +} + +bool +FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( + TArray& HeightData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY, + FVector& LandscapeResizeFactor, + FVector& LandscapePositionOffset) +{ + LandscapeResizeFactor = FVector::OneVector; + LandscapePositionOffset = FVector::ZeroVector; + + if (HeightData.Num() <= 4) + return false; + + if ((SizeX < 2) || (SizeY < 2)) + return false; + + // No need to resize anything + if (SizeX == NewSizeX && SizeY == NewSizeY) + return true; + + // Always resample, for now. We may enable padding functionality again at some point via + // a plugin setting. + bool bForceResample = true; + bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); + + TArray NewData; + if (!bResample) + { + // Expanding the data by padding + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + + const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; + const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; + + // Store the offset in pixel due to the padding + int32 PadOffsetX = 0; + int32 PadOffsetY = 0; + + // Expanding the Data + NewData = ExpandData( + HeightData, 0, 0, SizeX - 1, SizeY - 1, + -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1, + &PadOffsetX, &PadOffsetY); + + // We will need to offset the landscape position due to the value added by the padding + LandscapePositionOffset.X = (float)PadOffsetX; + LandscapePositionOffset.Y = (float)PadOffsetY; + + // Notify the user that the data was padded + HOUDINI_LOG_WARNING( + TEXT("Landscape data was padded from ( %d x %d ) to ( %d x %d )."), + SizeX, SizeY, NewSizeX, NewSizeY); + } + else + { + // Resampling the data + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + NewData = ResampleData(HeightData, SizeX, SizeY, NewSizeX, NewSizeY); + + // The landscape has been resized, we'll need to take that into account when sizing it + LandscapeResizeFactor.X = (float)SizeX / (float)NewSizeX; + LandscapeResizeFactor.Y = (float)SizeY / (float)NewSizeY; + LandscapeResizeFactor.Z = 1.0f; + + // Notify the user if the heightfield data was resized + HOUDINI_LOG_WARNING( + TEXT("Landscape data was resized from ( %d x %d ) to ( %d x %d )."), + SizeX, SizeY, NewSizeX, NewSizeY); + } + + // Replaces Old data with the new one + HeightData = NewData; + + return true; +} + + +bool +FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + const int32& HoudiniSizeX, const int32& HoudiniSizeY, + int32& UnrealSizeX, int32& UnrealSizeY, + int32& NumSectionsPerComponent, int32& NumQuadsPerSection) +{ + if ((HoudiniSizeX < 2) || (HoudiniSizeY < 2)) + return false; + + NumSectionsPerComponent = 1; + NumQuadsPerSection = 1; + UnrealSizeX = -1; + UnrealSizeY = -1; + + // Unreal's default sizes + int32 SectionSizes[] = { 7, 15, 31, 63, 127, 255 }; + int32 NumSections[] = { 1, 2 }; + + // Component count used to calculate the final size of the landscape + int32 ComponentsCountX = 1; + int32 ComponentsCountY = 1; + + // Lambda for clamping the number of component in X/Y + auto ClampLandscapeSize = [&]() + { + // Max size is either whole components below 8192 verts, or 32 components + ComponentsCountX = FMath::Clamp(ComponentsCountX, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); + ComponentsCountY = FMath::Clamp(ComponentsCountY, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); + }; + + // Try to find a section size and number of sections that exactly matches the dimensions of the heightfield + bool bFoundMatch = false; + for (int32 SectionSizesIdx = UE_ARRAY_COUNT(SectionSizes) - 1; SectionSizesIdx >= 0; SectionSizesIdx--) + { + for (int32 NumSectionsIdx = UE_ARRAY_COUNT(NumSections) - 1; NumSectionsIdx >= 0; NumSectionsIdx--) + { + int32 ss = SectionSizes[SectionSizesIdx]; + int32 ns = NumSections[NumSectionsIdx]; + + if (((HoudiniSizeX - 1) % (ss * ns)) == 0 && ((HoudiniSizeX - 1) / (ss * ns)) <= 32 && + ((HoudiniSizeY - 1) % (ss * ns)) == 0 && ((HoudiniSizeY - 1) / (ss * ns)) <= 32) + { + bFoundMatch = true; + NumQuadsPerSection = ss; + NumSectionsPerComponent = ns; + ComponentsCountX = (HoudiniSizeX - 1) / (ss * ns); + ComponentsCountY = (HoudiniSizeY - 1) / (ss * ns); + ClampLandscapeSize(); + break; + } + } + if (bFoundMatch) + { + break; + } + } + + if (!bFoundMatch) + { + // if there was no exact match, try increasing the section size until we encompass the whole heightmap + const int32 CurrentSectionSize = NumQuadsPerSection; + const int32 CurrentNumSections = NumSectionsPerComponent; + for (int32 SectionSizesIdx = 0; SectionSizesIdx < UE_ARRAY_COUNT(SectionSizes); SectionSizesIdx++) + { + if (SectionSizes[SectionSizesIdx] < CurrentSectionSize) + { + continue; + } + + const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); + const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); + if (ComponentsX <= 32 && ComponentsY <= 32) + { + bFoundMatch = true; + NumQuadsPerSection = SectionSizes[SectionSizesIdx]; + ComponentsCountX = ComponentsX; + ComponentsCountY = ComponentsY; + ClampLandscapeSize(); + break; + } + } + } + + if (!bFoundMatch) + { + // if the heightmap is very large, fall back to using the largest values we support + const int32 MaxSectionSize = SectionSizes[UE_ARRAY_COUNT(SectionSizes) - 1]; + const int32 MaxNumSubSections = NumSections[UE_ARRAY_COUNT(NumSections) - 1]; + const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), MaxSectionSize * MaxNumSubSections); + const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), MaxSectionSize * MaxNumSubSections); + + bFoundMatch = true; + NumQuadsPerSection = MaxSectionSize; + NumSectionsPerComponent = MaxNumSubSections; + ComponentsCountX = ComponentsX; + ComponentsCountY = ComponentsY; + ClampLandscapeSize(); + } + + if (!bFoundMatch) + { + // Using default size just to not crash.. + UnrealSizeX = 512; + UnrealSizeY = 512; + NumSectionsPerComponent = 1; + NumQuadsPerSection = 511; + ComponentsCountX = 1; + ComponentsCountY = 1; + } + else + { + // Calculating the desired size + int32 QuadsPerComponent = NumSectionsPerComponent * NumQuadsPerSection; + + UnrealSizeX = ComponentsCountX * QuadsPerComponent + 1; + UnrealSizeY = ComponentsCountY * QuadsPerComponent + 1; + } + + return bFoundMatch; +} + +const FHoudiniGeoPartObject* +FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return nullptr; + + if (InOutput->GetHoudiniGeoPartObjects().Num() < 1) + return nullptr; + + for (const FHoudiniGeoPartObject& HGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Volume) + continue; + + FHoudiniVolumeInfo CurVolumeInfo = HGPO.VolumeInfo; + if (!CurVolumeInfo.Name.Contains("height")) + continue; + + // We're only handling single values for now + if (CurVolumeInfo.TupleSize != 1) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume has an invalide tuple size!")); + return nullptr; + } + + // Terrains always have a ZSize of 1. + if (CurVolumeInfo.ZLength != 1) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume's z length is not 1!")); + return nullptr; + } + + // Values should be float + if (!CurVolumeInfo.bIsFloat) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output, the height volume's data is not stored as floats!")); + return nullptr; + } + + return &HGPO; + } + + return nullptr; +} + +void +FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers) +{ + FoundLayers.Empty(); + + // Get node id + HAPI_NodeId HeightFieldNodeId = Heightfield.GeoId; + + // We need the tile attribute if the height has it + bool bParentHeightfieldHasTile = false; + int32 HeightFieldTile = -1; + { + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + TArray< int32 > TileValues; + + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HeightFieldNodeId, Heightfield.PartId, "tile", AttribInfoTile, TileValues); + + if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) + { + HeightFieldTile = TileValues[0]; + bParentHeightfieldHasTile = true; + } + } + + for (TArray< FHoudiniGeoPartObject >::TConstIterator IterLayers(InOutput->GetHoudiniGeoPartObjects()); IterLayers; ++IterLayers) + { + const FHoudiniGeoPartObject & HoudiniGeoPartObject = *IterLayers; + + HAPI_NodeId NodeId = HoudiniGeoPartObject.GeoId; + if (NodeId == -1 || NodeId != HeightFieldNodeId) + continue; + + if (bParentHeightfieldHasTile) + { + int32 CurrentTile = -1; + + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + TArray TileValues; + + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "tile", AttribInfoTile, TileValues); + + + if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) + { + CurrentTile = TileValues[0]; + } + + // Does this layer come from the same tile as the height? + if ((CurrentTile != HeightFieldTile) || (CurrentTile == -1)) + continue; + } + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, HoudiniGeoPartObject.PartId, + &CurrentVolumeInfo)) + continue; + + // We're interesting in anything but height data + FString CurrentVolumeName; + FHoudiniEngineString(CurrentVolumeInfo.nameSH).ToFString(CurrentVolumeName); + if (CurrentVolumeName.Contains("height")) + continue; + + // We're only handling single values for now + if (CurrentVolumeInfo.tupleSize != 1) + continue; + + // Terrains always have a ZSize of 1. + if (CurrentVolumeInfo.zLength != 1) + continue; + + // Values should be float + if (CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + continue; + + FoundLayers.Add(&HoudiniGeoPartObject); + } +} + +bool FHoudiniLandscapeTranslator::GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo) +{ + if (HGPO->Type != EHoudiniPartType::Volume) + return false; + + FHoudiniApi::VolumeInfo_Init(&VolumeInfo); + + HAPI_Result Result = FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + HGPO->GeoId, HGPO->PartId, &VolumeInfo); + + // We're only handling single values for now + if (VolumeInfo.tupleSize != 1) + return false; + + // Terrains always have a ZSize of 1. + if (VolumeInfo.zLength != 1) + return false; + + // Values must be float + if (VolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + return false; + + if ((VolumeInfo.xLength < 2) || (VolumeInfo.yLength < 2)) + return false; + + return true; +} + +bool +FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) +{ + OutFloatArr.Empty(); + OutFloatMin = 0.f; + OutFloatMax = 0.f; + + HAPI_VolumeInfo VolumeInfo; + if (!GetHoudiniHeightfieldVolumeInfo(HGPO, VolumeInfo)) + return false; + + const int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; + + OutFloatArr.SetNum(SizeInPoints); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetHeightFieldData( + FHoudiniEngine::Get().GetSession(), + HGPO->GeoId, HGPO->PartId, + OutFloatArr.GetData(), + 0, SizeInPoints), false); + + OutFloatMin = OutFloatArr[0]; + OutFloatMax = OutFloatMin; + + for (float NextFloatVal : OutFloatArr) + { + if (NextFloatVal > OutFloatMax) + { + OutFloatMax = NextFloatVal; + } + else if (NextFloatVal < OutFloatMin) + OutFloatMin = NextFloatVal; + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(const FHoudiniGeoPartObject& InHGPO, TArray& NonWeightBlendedLayerNames) +{ + // Check the attribute exists on primitive or detail + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; + if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_PRIM)) + Owner = HAPI_ATTROWNER_PRIM; + else if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_DETAIL)) + Owner = HAPI_ATTROWNER_DETAIL; + else + return false; + + // Get the values + HAPI_AttributeInfo AttribInfoNonWBLayer; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNonWBLayer); + TArray AttribValues; + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, AttribInfoNonWBLayer, AttribValues, 1, Owner); + + if (AttribValues.Num() <= 0) + return false; + + // Convert them to FString + for (int32 Idx = 0; Idx < AttribValues.Num(); Idx++) + { + TArray Tokens; + AttribValues[Idx].ParseIntoArray(Tokens, TEXT(" "), true); + + for (int32 n = 0; n < Tokens.Num(); n++) + NonWeightBlendedLayerNames.AddUnique(Tokens[n]); + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) +{ + // Check the attribute exists on primitive or detail + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; + if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_PRIM)) + Owner = HAPI_ATTROWNER_PRIM; + else if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_DETAIL)) + Owner = HAPI_ATTROWNER_DETAIL; + else + return false; + + // Check the value + HAPI_AttributeInfo AttribInfoUnitLayer; + FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer); + TArray< int32 > AttribValues; + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, AttribInfoUnitLayer, AttribValues, 1, Owner); + + if (AttribValues.Num() > 0 && AttribValues[0] == 1) + return true; + + return false; +} + +bool +FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( + const TArray& FoundLayers, + const FHoudiniGeoPartObject& Heightfield, + const int32& LandscapeXSize, const int32& LandscapeYSize, + const TMap& GlobalMinimums, + const TMap& GlobalMaximums, + TArray& OutLayerInfos, + bool bIsUpdate, + const FHoudiniPackageParams& InTilePackageParams, + const FHoudiniPackageParams& InLayerPackageParams, + TArray& OutCreatedPackages + ) +{ + OutLayerInfos.Empty(); + + // Get the names of all non weight blended layers + TArray NonWeightBlendedLayerNames; + FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(Heightfield, NonWeightBlendedLayerNames); + + // Used for exporting layer info objects (per landscape layer) + FHoudiniPackageParams LayerPackageParams = InLayerPackageParams; + // Used for exporting textures (per landscape tile) + FHoudiniPackageParams TilePackageParams = InTilePackageParams; + + // For Debugging, do we want to export layers as textures? + bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; + + // Try to create all the layers + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + for (TArray::TConstIterator IterLayers(FoundLayers); IterLayers; ++IterLayers) + { + const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers; + if (!LayerGeoPartObject) + continue; + + if (!LayerGeoPartObject->IsValid()) + continue; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerGeoPartObject->AssetId)) + continue; + + if (bIsUpdate && !LayerGeoPartObject->bHasGeoChanged) + { + continue; + } + + TArray FloatLayerData; + float LayerMin = 0; + float LayerMax = 0; + if (!FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(LayerGeoPartObject, FloatLayerData, LayerMin, LayerMax)) + continue; + + // No need to create flat layers as Unreal will remove them afterwards.. + if (LayerMin == LayerMax) + continue; + + const FHoudiniVolumeInfo& LayerVolumeInfo = LayerGeoPartObject->VolumeInfo; + + // Get the layer's name + FString LayerName = LayerVolumeInfo.Name; + const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerName); + + TilePackageParams.ObjectName = InTilePackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; + LayerPackageParams.ObjectName = InLayerPackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; + + if (bExportTexture) + { + // Create a raw texture export of the layer on this tile + FString TextureName = TilePackageParams.ObjectName + "_raw"; + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + LayerVolumeInfo.YLength, // Y and X inverted?? why? + LayerVolumeInfo.XLength, + FloatLayerData, + LayerMin, + LayerMax); + } + + // Check if that landscape layer has been marked as unit (range in [0-1] + if (IsUnitLandscapeLayer(*LayerGeoPartObject)) + { + LayerMin = 0.0f; + LayerMax = 1.0f; + } + else + { + // We want to convert the layer using the global Min/Max + if (GlobalMaximums.Contains(LayerName)) + LayerMax = GlobalMaximums[LayerName]; + + if (GlobalMinimums.Contains(LayerName)) + LayerMin = GlobalMinimums[LayerName]; + } + + // Get the layer package path + // FString LayerNameString = FString::Printf(TEXT("%s_%d"), LayerString.GetCharArray().GetData(), (int32)LayerGeoPartObject->PartId); + // LayerNameString = UPackageTools::SanitizePackageName(LayerNameString); + + // Build an object name for the current layer + LayerPackageParams.SplitStr = SanitizedLayerName; + + // Creating the ImportLayerInfo and LayerInfo objects + FLandscapeImportLayerInfo ImportLayerInfo(*LayerName); + + // See if the user has assigned a layer info object via attribute + UPackage * Package = nullptr; + ULandscapeLayerInfoObject* LayerInfo = GetLandscapeLayerInfoForLayer(*LayerGeoPartObject, *LayerName); + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + // No assignment, try to find or create a landscape layer info object for that layer + LayerInfo = FindOrCreateLandscapeLayerInfoObject(LayerName, LayerPackageParams.GetPackagePath(), LayerPackageParams.GetPackageName(), Package); + } + + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + continue; + } + + // Convert the float data to uint8 + // HF masks need their X/Y sizes swapped + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( + FloatLayerData, LayerVolumeInfo.YLength, LayerVolumeInfo.XLength, + LayerMin, LayerMax, + LandscapeXSize, LandscapeYSize, + ImportLayerInfo.LayerData)) + continue; + + // We will store the data used to convert from Houdini values to int in the DebugColor + // This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards... + // R = Min, G = Max, B = Spacing, A = ? + LayerInfo->LayerUsageDebugColor.R = LayerMin; + LayerInfo->LayerUsageDebugColor.G = LayerMax; + LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; + LayerInfo->LayerUsageDebugColor.A = PI; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers] Processing layer: %s"), *(LayerName)); + + // Visibility are by default non weight blended + if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) + LayerInfo->bNoWeightBlend = true; + else + LayerInfo->bNoWeightBlend = false; + + if (!bIsUpdate && Package && !Package->IsPendingKill()) + { + // Mark the package dirty... + Package->MarkPackageDirty(); + OutCreatedPackages.Add(Package); + } + + if (bExportTexture) + { + // Create an export of the converted data to texture + // FString TextureName = LayerString; + // if (LayerGeoPartObject->VolumeTileIndex >= 0) + // TextureName = TEXT("Tile") + FString::FromInt(LayerGeoPartObject->VolumeTileIndex) + TEXT("_") + LayerString; + // TextureName += TEXT("_conv"); + + const FString TextureName = TilePackageParams.ObjectName + TEXT("_conv"); + + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + LandscapeXSize, LandscapeYSize, + ImportLayerInfo.LayerData); + } + + // See if there is a physical material assigned via attribute for that landscape layer + UPhysicalMaterial* PhysMaterial = FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(*LayerGeoPartObject); + if (PhysMaterial && !PhysMaterial->IsPendingKill()) + { + LayerInfo->PhysMaterial = PhysMaterial; + } + + // Assign the layer info object to the import layer infos + ImportLayerInfo.LayerInfo = LayerInfo; + OutLayerInfos.Add(ImportLayerInfo); + } + + // Autosaving the layers prevents them for being deleted with the Asset + // Save the packages created for the LayerInfos + // Do this only for when creating layers. + /* + if (!bIsUpdate) + FEditorFileUtils::PromptForCheckoutAndSave(CreatedLandscapeLayerPackage, true, false); + */ + + return true; +} + +void +FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums, + bool bShouldEmptyMaps) +{ + if (bShouldEmptyMaps) + { + GlobalMinimums.Empty(); + GlobalMaximums.Empty(); + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray FloatData; + + for (const FHoudiniGeoPartObject& CurrentHeightfield: InHeightfieldArray) + { + // Get the current Heightfield GeoPartObject + if ( CurrentHeightfield.VolumeInfo.TupleSize != 1) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = CurrentHeightfield.GeoId; + if (NodeId == -1) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + &CurrentVolumeInfo)) + continue; + + // Retrieve the volume name. + FString VolumeName; + FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); + HoudiniEngineStringPartName.ToFString(VolumeName); + + bool bHasMinAttr = false; + bool bHasMaxAttr = false; + + // If this volume has an attribute defining a minimum value use it as is. + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + GlobalMinimums.Add(VolumeName, FloatData[0]); + bHasMinAttr = true; + } + } + + // If this volume has an attribute defining maximum value use it as is. + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + GlobalMaximums.Add(VolumeName, FloatData[0]); + bHasMaxAttr = true; + } + } + + if (!(bHasMinAttr && bHasMaxAttr)) + { + // Unreal's Z values are Y in Houdini + float ymin, ymax; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + nullptr, &ymin, nullptr, + nullptr, &ymax, nullptr, + nullptr, nullptr, nullptr)) + continue; + + + if (!bHasMinAttr) + { + // Read the global min value for this volume + if (!GlobalMinimums.Contains(VolumeName)) + { + GlobalMinimums.Add(VolumeName, ymin); + } + else + { + // Update the min if necessary + if (ymin < GlobalMinimums[VolumeName]) + GlobalMinimums[VolumeName] = ymin; + } + } + + if (!bHasMaxAttr) + { + // Read the global max value for this volume + if (!GlobalMaximums.Contains(VolumeName)) + { + GlobalMaximums.Add(VolumeName, ymax); + } + else + { + // Update the max if necessary + if (ymax > GlobalMaximums[VolumeName]) + GlobalMaximums[VolumeName] = ymax; + } + } + } + } +} + +void +FHoudiniLandscapeTranslator::GetLayersZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums) + +{ + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray FloatData; + + for (const FHoudiniGeoPartObject& CurrentHeightfield : InHeightfieldArray) + { + // Get the current Heightfield GeoPartObject + if (CurrentHeightfield.VolumeInfo.TupleSize != 1) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = CurrentHeightfield.GeoId; + if (NodeId == -1) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + &CurrentVolumeInfo)) + continue; + + // Retrieve the volume name. + FString VolumeName; + FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); + HoudiniEngineStringPartName.ToFString(VolumeName); + + // Read the global min value for this volume + + float MinValue; + float MaxValue; + bool bHasMin = false; + bool bHasMax = false; + + if (!GlobalMinimums.Contains(VolumeName)) + { + // Extract min value + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + MinValue = FloatData[0]; + bHasMin = true; + } + } + if (!bHasMin) + { + if (VolumeName == TEXT("height")) + { + MinValue = -1000.f; + } + else + { + MinValue = 0.f; + } + } + GlobalMinimums.Add(VolumeName, MinValue); + } + + if (!GlobalMaximums.Contains(VolumeName)) + { + // Extract max value + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) + { + if (FloatData.Num() > 0) + { + MaxValue = FloatData[0]; + bHasMax = true; + } + } + if (!bHasMax) + { + if (VolumeName == TEXT("height")) + { + MaxValue = 1000.f; + } + else + { + MaxValue = 1.f; + } + } + GlobalMaximums.Add(VolumeName, MaxValue); + } + + + + } +} + +bool +FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( + const TArray& FloatLayerData, + const int32& HoudiniXSize, const int32& HoudiniYSize, + const float& LayerMin, const float& LayerMax, + const int32& LandscapeXSize, const int32& LandscapeYSize, + TArray& LayerData, const bool& NoResize) +{ + // Convert the float data to uint8 + LayerData.SetNumUninitialized(HoudiniXSize * HoudiniYSize); + + // Calculating the factor used to convert from Houdini's ZRange to [0 255] + double LayerZRange = (LayerMax - LayerMin); + double LayerZSpacing = (LayerZRange != 0.0) ? (255.0 / (double)(LayerZRange)) : 0.0; + + int32 nUnrealIndex = 0; + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y + int32 nHoudini = nY + nX * HoudiniYSize; + + // Get the double values in [0 - ZRange] + double DoubleValue = (double)FMath::Clamp(FloatLayerData[nHoudini], LayerMin, LayerMax) - (double)LayerMin; + + // Then convert it to [0 - 255] + DoubleValue *= LayerZSpacing; + + LayerData[nUnrealIndex++] = FMath::RoundToInt(DoubleValue); + } + } + + // Finally, resize the data to fit with the new landscape size if needed + if (NoResize) + return true; + + return FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( + LayerData, HoudiniXSize, HoudiniYSize, + LandscapeXSize, LandscapeYSize); +} + +bool +FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( + TArray< uint8 >& LayerData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY) +{ + if ((NewSizeX == SizeX) && (NewSizeY == SizeY)) + return true; + + bool bForceResample = true; + bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); + + TArray NewData; + if (!bResample) + { + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + + const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; + const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; + + // Expanding the Data + NewData = ExpandData( + LayerData, + 0, 0, SizeX - 1, SizeY - 1, + -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1); + } + else + { + // Resampling the data + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + NewData = ResampleData(LayerData, SizeX, SizeY, NewSizeX, NewSizeY); + } + + LayerData = NewData; + + return true; +} + +ALandscapeProxy * +FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( + const TArray< uint16 >& IntHeightData, + const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + const FTransform& TileTransform, + const FIntPoint& TileLocation, + const int32& XSize, + const int32& YSize, + const int32& NumSectionPerLandscapeComponent, + const int32& NumQuadsPerLandscapeSection, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* LandscapePhysicalMaterial, + const FString& LandscapeTileActorName, + LandscapeActorType ActorType, + ALandscape* SharedLandscapeActor, + UWorld* InWorld, + ULevel* InLevel, + FHoudiniPackageParams InPackageParams) +{ + if (!IsValid(InWorld)) + return nullptr; + + // if (!IsValid(MainLandscapeActor)) + // return nullptr; + + if ((XSize < 2) || (YSize < 2)) + return nullptr; + + if (IntHeightData.Num() != (XSize * YSize)) + return nullptr; + + if (!GEditor) + return nullptr; + + ALandscapeProxy* LandscapeTile = nullptr; + UPackage *CreatedPackage = nullptr; + + ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; + ALandscape* CachedLandscapeActor = nullptr; + + UWorld* NewWorld = nullptr; + FString MapFileName; + bool bBroadcastMaterialUpdate = false; + //... Create landscape tile ...// + { + // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos + if (ActorType == LandscapeActorType::LandscapeStreamingProxy) + { + CachedStreamingProxyActor = InWorld->SpawnActor(); + if (CachedStreamingProxyActor) + { + check(SharedLandscapeActor); + CachedStreamingProxyActor->PreEditChange(nullptr); + + // Update landscape tile properties from the main landscape actor. + CachedStreamingProxyActor->GetSharedProperties(SharedLandscapeActor); + CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; + CachedStreamingProxyActor->bCastStaticShadow = false; + + LandscapeTile = CachedStreamingProxyActor; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not spawn ALandscapeStreamingProxy with name: %s"), *(LandscapeTileActorName) ); + return nullptr; + } + } + else + { + // Create a normal landscape actor + CachedLandscapeActor = InWorld->SpawnActor(); + if (CachedLandscapeActor) + { + CachedLandscapeActor->PreEditChange(nullptr); + CachedLandscapeActor->SetLandscapeGuid(FGuid::NewGuid()); + CachedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + CachedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + CachedLandscapeActor->bCastStaticShadow = false; + bBroadcastMaterialUpdate = true; + LandscapeTile = CachedLandscapeActor; + } + } + } + + + if (!LandscapeTile) + return nullptr; + + // Only import non-visibility layers. Visibility will be handled explicitly. + TArray CustomImportLayerInfos; + for (const FLandscapeImportLayerInfo& LayerInfo : ImportLayerInfos) + { + if (LayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + continue; + CustomImportLayerInfos.Add(LayerInfo); + } + + // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. + + // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue + LandscapeTile->bCastStaticShadow = false; + + // TODO: Check me? + //if (LandscapePhsyicalMaterial) + // LandscapeTile->DefaultPhysMaterial = LandscapePhsyicalMaterial; + + // Setting the layer type here. + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + + TMap> HeightmapDataPerLayers; + TMap> MaterialLayerDataPerLayer; + HeightmapDataPerLayers.Add(FGuid(), IntHeightData); + TArray& MaterialImportLayerInfos = MaterialLayerDataPerLayer.Add(FGuid(), CustomImportLayerInfos); + + // Before we import new tile data, remove previous tile data that will be overlapped by the new tile. + TSet OverlappingComponents; + const int32 DestMinX = TileLocation.X; + const int32 DestMinY = TileLocation.Y; + const int32 DestMaxX = TileLocation.X + XSize - 1; + const int32 DestMaxY = TileLocation.Y + YSize - 1; + + ULandscapeInfo* LandscapeInfo = LandscapeTile->GetLandscapeInfo(); + + if (LandscapeInfo) + { + // If there is a preexisting LandscapeInfo object, check for overlapping components. + + // Shrink bounds by 1,1 to avoid GetComponentsInRegion picking up extra components on all sides due to the overlap between components + LandscapeInfo->GetComponentsInRegion(DestMinX+1, DestMinY+1, DestMaxX-1, DestMaxY-1, OverlappingComponents); + TSet StaleActors; + + for (ULandscapeComponent* Component : OverlappingComponents) + { + // Remove the overlapped component from the LandscapeInfo and then from + LandscapeInfo->Modify(); + + ALandscapeProxy* Proxy = Component->GetLandscapeProxy(); + if (!IsValid(Proxy)) + continue; + check(Proxy); + FIntRect Bounds = Proxy->GetBoundingRect(); + // If this landscape proxy has no more components left, remove it from the LandscapeInfo. + LandscapeInfo->UnregisterActor(Proxy); + Proxy->Destroy(); + } + + ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); + } + + // Import tile data + // The Import function will correctly compute the tile section locations. No need to set it explicitly. + // TODO: Verify this with world composition!! + + bool bIsStandalone = LandscapeTile->GetLandscapeActor() == LandscapeTile; + + // We set the actor transform and absolute section base before importing heightfield data. This allows us to + // use the correct (quad-space) blitting region without causing overlaps during import. + + // NOTE: The following SetAbsoluteSectionBase() calls are very important. This tells the Landscape system + // where on the landscape, in quad space, a specific tile is located. This is used by various + // mechanisms and tools that tie into the Landscape system such as World Composition and Landscape editor tools. + // If the Section Offset for a landscape tile is wrong, it will appear in the wrong place in the World Composition + // viewer. Worse than that, none of Landscape Editor tools will work properly because it won't be able to + // locate the correct Landscape component when calculating the "Landscape Component Key" for the given world position. + // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blitting functions use the + // same underlying Landscape Component Key calculation to find the correct landscape component to draw to. So if the + // Section Offsets are wrong ... all manner of chaos will follow. + // It is also super important to call ULandscapeInfo::RecreateLandscapeInfo() after change a LandscapeProxy's + // section offset in order to update the landscape's internal caches (more specifically the component keys, which + // are based on the section offsets) otherwise component key calculations won't work correctly. + + LandscapeTile->SetActorRelativeTransform(TileTransform); + LandscapeTile->SetAbsoluteSectionBase(TileLocation); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Importing tile for actor: %s "), *(LandscapeTile->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Dest region: %d, %d -> %d, %d"), DestMinX, DestMinY, DestMaxX, DestMaxY); + + LandscapeTile->Import( + LandscapeTile->GetLandscapeGuid(), + DestMinX, DestMinY, DestMaxX, DestMaxY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + HeightmapDataPerLayers, NULL, + MaterialLayerDataPerLayer, ImportLayerType); + + + if (!LandscapeInfo) + { + LandscapeInfo = LandscapeTile->GetLandscapeInfo(); + } + + check(LandscapeInfo); + + // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so + // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo component keys. + // Only then are we able to "blit" the new alpha data into the correct place on the landscape. + + ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); + LandscapeTile->RecreateComponentsState(); + + // TODO: Verify whether we still need to manually set the LandscapeLightingLOD setting or whether + // calling PostEditChange() will properly fix the state. + + // Copied straight from UE source code to avoid crash after importing the landscape: + // automatically calculate a lighting LOD that won't crash lightmass (hopefully) + // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 + LandscapeTile->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + + // ---------------------------------------------------- + // Update visibility layer + // ---------------------------------------------------- + + // Update the visibility mask / layer if we have any (TileImport does not update the visibility layer). + for (auto CurLayerInfo : ImportLayerInfos) + { + if (CurLayerInfo.LayerInfo && CurLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(DestMinX, DestMinY, DestMaxX, DestMaxY, CurLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + + // ---------------------------------------------------- + // Rename the actor + // ---------------------------------------------------- + + // NOTE: The LandscapeProxy needs to be properly initialized before renaming (which is why the rename is taking + // place at the end) since the rename will trigger PostEditChange and can crash if the actor has not been + // correctly setup. + FHoudiniEngineUtils::SafeRenameActor(LandscapeTile, LandscapeTileActorName); + + if (!LandscapeTile->MarkPackageDirty()) + { + HOUDINI_LOG_WARNING(TEXT("Editor suppressed MarkPackageDirty() for landscape tile: %s"), *(LandscapeTile->GetPathName())); + } + +#if WITH_EDITOR + GEngine->BroadcastOnActorMoved(LandscapeTile); +#endif + + return LandscapeTile; +} + + +void +FHoudiniLandscapeTranslator::DestroyLandscape(ALandscape* Landscape) +{ + if (!IsValid(Landscape)) + return; + + ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); + if (!IsValid(Info)) + return; + + TArray Proxies = Info->Proxies; + for(ALandscapeStreamingProxy* Proxy : Proxies) + { + Info->UnregisterActor(Proxy); + Proxy->Destroy(); + } + Landscape->Destroy(); +} + + +void +FHoudiniLandscapeTranslator::CalculateTileLocation( + int32 NumSectionsPerComponent, + int32 NumQuadsPerSection, + const FTransform& TileTransformWS, + FHoudiniLandscapeReferenceLocation& RefLoc, + FTransform& OutLandscapeTransform, + FIntPoint& OutTileLocation) +{ + // IMPORTANT: Tile Location (Section Base) needs to be in multiples of the landscape component's quad size + const int32 ComponentSize = NumSectionsPerComponent * NumQuadsPerSection; + + OutLandscapeTransform = FTransform::Identity; + const FTransform TileSR = FTransform(TileTransformWS.GetRotation(), FVector::ZeroVector, TileTransformWS.GetScale3D()); + + const FVector BaseLoc = TileSR.InverseTransformPosition(TileTransformWS.GetLocation()); + + const FVector TileScale = TileTransformWS.GetScale3D(); + const float TileLocX = BaseLoc.X; // / TileScale.X; + const float TileLocY = BaseLoc.Y; // / TileScale.Y; + + if (!RefLoc.bIsCached) + { + // If there is no landscape reference location yet, calculate one now. + + // We cache this tile as a reference point for the other landscape tiles so that they can calculate + // section base offsets in a consistent manner, relative to this tile. + const float NearestMultipleX = FMath::RoundHalfFromZero(TileLocX / ComponentSize) * ComponentSize; + const float NearestMultipleY = FMath::RoundHalfFromZero(TileLocY / ComponentSize) * ComponentSize; + + RefLoc.SectionCoordX = FMath::RoundHalfFromZero(NearestMultipleX); + RefLoc.SectionCoordY = FMath::RoundHalfFromZero(NearestMultipleY); + RefLoc.TileLocationX = TileLocX; + RefLoc.TileLocationY = TileLocY; + } + + // Calculate the section coordinate for this tile + const float DeltaLocX = TileLocX - RefLoc.TileLocationX; + const float DeltaLocY = TileLocY - RefLoc.TileLocationY; + + const float DeltaCoordX = FMath::RoundHalfFromZero(DeltaLocX / ComponentSize) * ComponentSize; + const float DeltaCoordY = FMath::RoundHalfFromZero(DeltaLocY / ComponentSize) * ComponentSize; + + OutTileLocation.X = RefLoc.SectionCoordX + DeltaCoordX; + OutTileLocation.Y = RefLoc.SectionCoordY + DeltaCoordY; + + // Adjust landscape offset to compensate for tile location / section base shifting. + if (!RefLoc.bIsCached) + { + FVector Offset((TileLocX - OutTileLocation.X), (TileLocY - OutTileLocation.Y), BaseLoc.Z); + Offset = TileSR.TransformPosition(Offset); + + RefLoc.MainTransform = TileTransformWS; + RefLoc.MainTransform.SetTranslation(Offset); + // Reference locations are now fully cached. + RefLoc.bIsCached = true; + } + + OutLandscapeTransform = RefLoc.MainTransform; +} + + +void +FHoudiniLandscapeTranslator::GetLandscapeMaterials( + const FHoudiniGeoPartObject& InHeightHGPO, + UMaterialInterface*& OutLandscapeMaterial, + UMaterialInterface*& OutLandscapeHoleMaterial, + UPhysicalMaterial*& OutLandscapePhysicalMaterial) +{ + OutLandscapeMaterial = nullptr; + OutLandscapeHoleMaterial = nullptr; + OutLandscapePhysicalMaterial = nullptr; + + if (InHeightHGPO.Type != EHoudiniPartType::Volume) + return; + + TArray Materials; + HAPI_AttributeInfo AttribMaterials; + FHoudiniApi::AttributeInfo_Init(&AttribMaterials); + + // First, look for landscape material + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL, + AttribMaterials, Materials); + + // If the material attribute was not found, check the material instance attribute. + if (!AttribMaterials.exists) + { + Materials.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, + AttribMaterials, Materials); + } + + // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. + //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) + if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) + { + HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a point, primitive or detail attribute, ignoring attribute.")); + AttribMaterials.exists = false; + Materials.Empty(); + } + + if (AttribMaterials.exists && Materials.Num() > 0) + { + // Load the material + OutLandscapeMaterial = Cast(StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); + } + } + + Materials.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribMaterials); + + // Then, for the hole_material + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + AttribMaterials, Materials); + + // If the material attribute was not found, check the material instance attribute. + if (!AttribMaterials.exists) + { + Materials.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE, + AttribMaterials, Materials); + } + + // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. + //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) + if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) + { + HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute.")); + AttribMaterials.exists = false; + Materials.Empty(); + } + + if (AttribMaterials.exists && Materials.Num() > 0) + { + // Load the material + OutLandscapeHoleMaterial = Cast< UMaterialInterface >(StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); + } + } + + // Then for the physical material + OutLandscapePhysicalMaterial = GetLandscapePhysicalMaterial(InHeightHGPO); +} + +// Read the landscape component extent attribute from a heightfield +bool +FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + int32& MinX, int32& MaxX, + int32& MinY, int32& MaxY) +{ + // If we dont have minX, we likely dont have the others too + if (!FHoudiniEngineUtils::HapiCheckAttributeExists( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", HAPI_ATTROWNER_PRIM)) + return false; + + // Create an AttributeInfo + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); + + // Get MinX + TArray IntData; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MinX = IntData[0]; + + // Get MaxX + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MaxX = IntData[0]; + + // Get MinY + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MinY = IntData[0]; + + // Get MaxX + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + return false; + + if (IntData.Num() > 0) + MaxY = IntData[0]; + + return true; +} + +ULandscapeLayerInfoObject * +FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& InLayerName, const FString& InPackagePath, const FString& InPackageName, UPackage*& OutPackage) +{ + FString PackageFullName = InPackagePath + TEXT("/") + InPackageName; + + // See if package exists, if it does, reuse it + bool bCreatedPackage = false; + OutPackage = FindPackage(nullptr, *PackageFullName); + if (!OutPackage || OutPackage->IsPendingKill()) + { + // We need to create a new package + OutPackage = CreatePackage(*PackageFullName); + bCreatedPackage = true; + } + + if (!OutPackage || OutPackage->IsPendingKill()) + return nullptr; + + if (!OutPackage->IsFullyLoaded()) + OutPackage->FullyLoad(); + + ULandscapeLayerInfoObject* LayerInfo = nullptr; + if (!bCreatedPackage) + { + // See if we can load the layer info instead of creating a new one + LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), OutPackage, FName(*InPackageName)); + } + + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + // Create a new LandscapeLayerInfoObject in the package + LayerInfo = NewObject(OutPackage, FName(*InPackageName), RF_Public | RF_Standalone /*| RF_Transactional*/); + + // Notify the asset registry + FAssetRegistryModule::AssetCreated(LayerInfo); + } + + if (LayerInfo && !LayerInfo->IsPendingKill()) + { + LayerInfo->LayerName = FName(*InLayerName); + + // Trigger update of the Layer Info + LayerInfo->PreEditChange(nullptr); + LayerInfo->PostEditChange(); + LayerInfo->MarkPackageDirty(); + + // Mark the package dirty... + OutPackage->MarkPackageDirty(); + } + + return LayerInfo; +} + +bool +FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax( + const TArray& AllOutputs, float& OutGlobalMin, float& OutGlobalMax) +{ + OutGlobalMin = 0.f; + OutGlobalMax = 0.f; + + for (const auto& CurrentOutput : AllOutputs) + { + if (!CurrentOutput) + continue; + + if (CurrentOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + const TArray& HGPOs = CurrentOutput->GetHoudiniGeoPartObjects(); + for (const FHoudiniGeoPartObject& CurrentHGPO : HGPOs) + { + if (CurrentHGPO.Type != EHoudiniPartType::Volume) + continue; + + if (!CurrentHGPO.VolumeInfo.Name.Contains("height")) + continue; + + // We're only handling single values for now + if (CurrentHGPO.VolumeInfo.TupleSize != 1) + continue; + + // Terrains always have a ZSize of 1. + if (CurrentHGPO.VolumeInfo.ZLength != 1) + continue; + + // Values should be float + if (!CurrentHGPO.VolumeInfo.bIsFloat) + continue; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CurrentHGPO.GeoId)) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHGPO.GeoId, CurrentHGPO.PartId, &CurrentVolumeInfo)) + continue; + + // Unreal's Z values are Y in Houdini + float yMin = OutGlobalMin, yMax = OutGlobalMax; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), + CurrentHGPO.GeoId, CurrentHGPO.PartId, + nullptr, &yMin, nullptr, + nullptr, &yMax, nullptr, + nullptr, nullptr, nullptr)) + continue; + + if (yMin < OutGlobalMin) + OutGlobalMin = yMin; + + if (yMax > OutGlobalMax) + OutGlobalMax = yMax; + } + + if (OutGlobalMin > OutGlobalMax) + { + OutGlobalMin = 0.f; + OutGlobalMax = 0.f; + } + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::EnableWorldComposition() +{ + HOUDINI_LOG_WARNING(TEXT("[FHoudiniLandscapeTranslator::EnableWorldComposition] We should never enable world composition from within the plugin.")); + // Get the world + UWorld* MyWorld = nullptr; + { + // We want to create the landscape in the landscape editor mode's world + FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); + MyWorld = EditorWorldContext.World(); + } + + if (!MyWorld) + return false; + + ULevel* CurrentLevel = MyWorld->GetCurrentLevel(); + + if (!CurrentLevel) + return false; + + AWorldSettings* WorldSettings = CurrentLevel->GetWorldSettings(); + if (!WorldSettings) + return false; + + // Enable world composition in WorldSettings + WorldSettings->bEnableWorldComposition = true; + + CurrentLevel->PostEditChange(); + + return true; +} + +bool +FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current Property Attribute + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) + continue; + + // Success! + NumSuccess++; + FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); + } + + return (NumSuccess > 0); +} + + +bool +FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(const FString& BaseName, ALandscapeProxy* Landscape) +{ + // We need to cache the input landscape to a file + if (!Landscape) + return false; + + ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Save Height data to file + //FString HeightSave = TEXT("/Game/HoudiniEngine/Temp/HeightCache.png"); + FString HeightSave = BaseName + TEXT("_height.png"); + LandscapeInfo->ExportHeightmap(HeightSave); + Landscape->ReimportHeightmapFilePath = HeightSave; + + // Save each layer to a file + for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) + { + FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); + //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; + if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + continue; + + FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); + LandscapeInfo->ExportLayer(CurrentLayerInfo, LayerSave); + + // Update the file reimport path on the input landscape for this layer + LandscapeInfo->GetLayerEditorSettings(CurrentLayerInfo).ReimportLayerFilePath = LayerSave; + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy) +{ + if (!LandscapeProxy) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Restore Height data from the backup file + FString ReimportFile = LandscapeProxy->ReimportHeightmapFilePath; + if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, TEXT("height"))) + HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); + + // Restore each layer from the backup file + TArray< ULandscapeLayerInfoObject* > SourceLayers; + for (int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++) + { + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; + if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + continue; + + FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); + ReimportFile = LandscapeProxy->EditorLayerSettings[LayerIndex].ReimportLayerFilePath; + + if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, CurrentLayerName, CurrentLayerInfo)) + HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); + + SourceLayers.Add(CurrentLayerInfo); + } + + // Iterate on the landscape info's layer to remove any layer that could have been added by Houdini + for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) + { + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; + if (SourceLayers.Contains(CurrentLayerInfo)) + continue; + + // Delete the added layer + FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName; + LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName); + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::ImportLandscapeData( + ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject) +{ + // + // Code copied/edited from FEdModeLandscape::ImportData as we cannot access that function + // + if (!LandscapeInfo) + return false; + + bool IsHeight = LayerName.Equals(TEXT("height"), ESearchCase::IgnoreCase); + + int32 MinX, MinY, MaxX, MaxY; + if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + { + const FLandscapeFileResolution LandscapeResolution = { (uint32)(1 + MaxX - MinX), (uint32)(1 + MaxY - MinY) }; + + ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); + + if (IsHeight) + { + const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); + + if (!HeightmapFormat) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); + return false; + } + + FLandscapeFileResolution ImportResolution = { 0, 0 }; + + const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename); + + // display error message if there is one, and abort the import + if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); + return false; + } + + // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape + if (HeightmapInfo.PossibleResolutions.Num() > 1) + { + if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined")); + return false; + } + else + { + ImportResolution = LandscapeResolution; + } + } + + // display warning message if there is one and allow user to cancel + if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); + + // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape + // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is + if (HeightmapInfo.PossibleResolutions.Num() == 1) + { + ImportResolution = HeightmapInfo.PossibleResolutions[0]; + if (ImportResolution != LandscapeResolution) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); + } + + FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution); + if (ImportData.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); + return false; + } + + TArray Data; + if (ImportResolution != LandscapeResolution) + { + // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked + // so that reimports behave the same as the initial import :) + + const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; + const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; + + Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16)); + + ExpandData(Data.GetData(), ImportData.Data.GetData(), + 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, + -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); + } + else + { + Data = MoveTemp(ImportData.Data); + } + + //FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap")); + + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData()); + } + else + { + // We're importing a Landscape layer + if (!LayerInfoObject || LayerInfoObject->IsPendingKill()) + return false; + + const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); + if (!WeightmapFormat) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); + return false; + } + + FLandscapeFileResolution ImportResolution = { 0, 0 }; + + const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName)); + + // display error message if there is one, and abort the import + if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); + return false; + } + + // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape + if (WeightmapInfo.PossibleResolutions.Num() > 1) + { + if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined")); + return false; + } + else + { + ImportResolution = LandscapeResolution; + } + } + + // display warning message if there is one and allow user to cancel + if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); + + // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape + // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is + if (WeightmapInfo.PossibleResolutions.Num() == 1) + { + ImportResolution = WeightmapInfo.PossibleResolutions[0]; + if (ImportResolution != LandscapeResolution) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); + } + + FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution); + + if (ImportData.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); + return false; + } + + TArray Data; + if (ImportResolution != LandscapeResolution) + { + // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked + // so that reimports behave the same as the initial import :) + const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; + const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; + + Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8)); + + ExpandData(Data.GetData(), ImportData.Data.GetData(), + 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, + -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); + } + else + { + Data = MoveTemp(ImportData.Data); + } + + //FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer")); + FAlphamapAccessor AlphamapAccessor(LandscapeInfo, LayerInfoObject); + AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + + return true; +} + +UTexture2D* +FHoudiniLandscapeTranslator::CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& InFloatBuffer, + const float& InMin, + const float& InMax) +{ + + // Convert the float values to uint8 + double Range = (double)InMax - (double)InMin; + TArray IntBuffer; + IntBuffer.SetNum(InFloatBuffer.Num()); + for(int32 i = 0; i < InFloatBuffer.Num(); i++) + { + double dNormalizedValue = ((double)InFloatBuffer[i] - (double)InMin) / (double)Range; + IntBuffer[i] = (uint8)(dNormalizedValue * 255.0); + } + + return FHoudiniLandscapeTranslator::CreateUnrealTexture( + InPackageParams, LayerName, InXSize, InYSize, IntBuffer); +} + +UTexture2D* +FHoudiniLandscapeTranslator::CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& IntBuffer) +{ + FHoudiniPackageParams MyPackageParams = InPackageParams; + MyPackageParams.ObjectName = LayerName; + MyPackageParams.PackageMode = EPackageMode::CookToTemp; + MyPackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + FString CreatedPackageName; + UPackage* Package = MyPackageParams.CreatePackageForObject(CreatedPackageName); + if (!Package || Package->IsPendingKill()) + return nullptr; + + // Create new texture object. + UTexture2D * Texture = NewObject(Package, UTexture2D::StaticClass(), *LayerName, RF_Public | RF_Standalone); + + // Add/Update meta information to package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *LayerName); + + /*// Texture Settings + Texture->PlatformData = new FTexturePlatformData(); + Texture->PlatformData->SizeX = InXSize; + Texture->PlatformData->SizeY = InYSize; + Texture->PlatformData->PixelFormat = PF_R8G8B8A8;*/ + + // Initialize texture source. + Texture->Source.Init(InXSize, InYSize, 1, 1, TSF_BGRA8); + + // Lock the texture. + uint8 * MipData = Texture->Source.LockMip(0); + + // Create base map. + uint8* DestPtr = nullptr; + uint32 SrcWidth = InXSize; + uint32 SrcHeight = InYSize; + const uint8 * SrcData = &IntBuffer[0]; + + for (uint32 y = 0; y < SrcHeight; y++) + { + DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; + + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth + x; + + *DestPtr++ = *(SrcData + DataOffset); // B greyscale, same value 3 times + *DestPtr++ = *(SrcData + DataOffset); // G + *DestPtr++ = *(SrcData + DataOffset); // R + + *DestPtr++ = 0xFF; // A to 1 + } + } + + // Unlock the texture. + Texture->Source.UnlockMip(0); + + // Texture creation parameters. + //Texture->SRGB = TextureParameters.bSRGB; + Texture->CompressionSettings = TC_Grayscale; + Texture->CompressionNoAlpha = true; + Texture->MipGenSettings = TMGS_NoMipmaps; + + // Set the Source Guid/Hash if specified. + /* + if ( TextureParameters.SourceGuidHash.IsValid() ) + { + Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); + } + */ + + // Updating Texture & mark it as unsaved + //Texture->AddToRoot(); + //Texture->UpdateResource(); + Package->MarkPackageDirty(); + + Texture->PostEditChange(); + + FString PathName = Texture->GetPathName(); + HOUDINI_LOG_MESSAGE(TEXT("Created texture when for %s in %s"), *LayerName, *PathName); + + return Texture; +} + +UPhysicalMaterial* +FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO) +{ + // See if we have assigned a physical material to this layer via attribute + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + TArray AttributeValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InLayerHGPO.GeoId, InLayerHGPO.PartId, + HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL, + AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) + return nullptr; + + if (AttributeValues.Num() > 0) + { + return LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); + } + + return nullptr; +} + +ULandscapeLayerInfoObject* +FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName) +{ + // See if we have assigned a landscape layer info object to this layer via attribute + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + TArray AttributeValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InLayerHGPO.GeoId, InLayerHGPO.PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO, + AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) + return nullptr; + + if (AttributeValues.Num() > 0) + { + ULandscapeLayerInfoObject* FoundLayerInfo = LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); + if (!FoundLayerInfo || FoundLayerInfo->IsPendingKill()) + return nullptr; + + // The layer info's name must match this layer's name or Unreal will not like this! + if (!FoundLayerInfo->LayerName.IsEqual(InLayerName)) + { + FString NameStr = InLayerName.ToString(); + HOUDINI_LOG_WARNING(TEXT("Failed to use the assigned layer info object for %s by the unreal_landscape_layer_info attribute as the found layer info object's layer name does not match."), *NameStr); + } + + return FoundLayerInfo; + } + + return nullptr; +} + + + diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h index 73a5275ef..07ffb3c84 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h @@ -1,415 +1,459 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "Landscape.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "Engine/World.h" -#include "EngineUtils.h" -#include "HoudiniEngineOutputStats.h" -#include "HoudiniPackageParams.h" -#include "HoudiniTranslatorTypes.h" - -class UHoudiniAssetComponent; -class ULandscapeLayerInfoObject; -struct FHoudiniGenericAttribute; -struct FHoudiniPackageParams; - -struct HOUDINIENGINE_API FHoudiniLandscapeTranslator -{ - public: - enum class LandscapeActorType : uint8 - { - LandscapeActor = 0, - LandscapeStreamingProxy = 1, - }; - - static bool CreateLandscape( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedActors, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* World, - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniLandscapeExtent& LandscapeExtent, - FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, - FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, - FHoudiniPackageParams InPackageParams, - TArray& OutCreatedPackages); - - static ALandscapeProxy* FindExistingLandscapeActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - protected: - - static bool IsLandscapeInfoCompatible( - const ULandscapeInfo* LandscapeInfo, - const int32 NumSectionsX, - const int32 NumSectionsY); - - static bool IsLandscapeTileCompatible( - const ALandscapeProxy* TileActor, - const int32 InTileSizeX, - const int32 InTileSizeY, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection); - - static bool IsLandscapeTypeCompatible( - const AActor* Actor, - LandscapeActorType ActorType); - - static bool PopulateLandscapeExtents( - FHoudiniLandscapeExtent& Extent, - const ULandscapeInfo* LandscapeInfo - ); - - - /** - * Find a ALandscapeProxy actor that can be reused. It is important - * to note that the request landscape actor could not be found, - * `OutWorld` and `OutLevel` should be used to spawn the - * new landscape actor. - * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. - */ - static ALandscapeProxy* FindExistingLandscapeActor( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage, - const EPackageMode& InPackageMode); - - static ALandscapeProxy* FindExistingLandscapeActor_Temp( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - /** - * Attempt the given ALandscapeActor to the outer HAC. Note - * that certain package modes (such as Bake) may choose not to do so. - * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. - */ - static void SetLandscapeActorAsOutput( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor, - const EPackageMode InPackageMode); - - static void SetLandscapeActorAsOutput_Bake( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor); - - static void SetLandscapeActorAsOutput_Temp( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor); - - /** - * Attach the given actor the HoudiniAssetComponent that - * owns `InOutput`, if any. - * @returns True if the actor was attached. Otherwise, return false. - */ - static bool AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor); - - /** - * Get the actor name suffix to be used in the specific packaging mode. - * @returns Suffix for actor names, return as an FString. - */ - static FString GetActorNameSuffix(const EPackageMode& InPackageMode); - - - // Helpers to get rid of repetitive boilerplate. - static void DoPreEditChangeProperty(UObject* Obj, FName PropertyName); - static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); - - public: - - - static const FHoudiniGeoPartObject* GetHoudiniHeightFieldFromOutput( - UHoudiniOutput* InOutput); - - static void GetHeightfieldsLayersFromOutput( - const UHoudiniOutput* InOutput, - const FHoudiniGeoPartObject& Heightfield, - TArray< const FHoudiniGeoPartObject* >& FoundLayers); - - static bool GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo); - - static bool GetHoudiniHeightfieldFloatData( - const FHoudiniGeoPartObject* HGPO, - TArray &OutFloatArr, - float &OutFloatMin, - float &OutFloatMax); - - static bool CalcLandscapeSizeFromHeightfieldSize( - const int32& HoudiniSizeX, - const int32& HoudiniSizeY, - int32& UnrealSizeX, - int32& UnrealSizeY, - int32& NumSectionsPerComponent, - int32& NumQuadsPerSection); - - static bool ConvertHeightfieldDataToLandscapeData( - const TArray< float >& HeightfieldFloatValues, - const FHoudiniVolumeInfo& HeightfieldVolumeInfo, - const int32& FinalXSize, - const int32& FinalYSize, - float FloatMin, - float FloatMax, - TArray< uint16 >& IntHeightData, - FTransform& LandscapeTransform, - const bool& NoResize = false); - - static bool ResizeHeightDataForLandscape( - TArray& HeightData, - const int32& SizeX, - const int32& SizeY, - const int32& NewSizeX, - const int32& NewSizeY, - FVector& LandscapeResizeFactor, - FVector& LandscapePositionOffset); - - static bool CreateOrUpdateLandscapeLayers( - const TArray& FoundLayers, - const FHoudiniGeoPartObject& HeightField, - const int32& LandscapeXSize, - const int32& LandscapeYSize, - const TMap &GlobalMinimums, - const TMap &GlobalMaximums, - TArray& OutLayerInfos, - bool bIsUpdate, - const FHoudiniPackageParams& InTilePackageParams, - const FHoudiniPackageParams& InLayerPackageParams, - TArray& OutCreatedPackages); - - static bool GetNonWeightBlendedLayerNames( - const FHoudiniGeoPartObject& HeightfieldGeoPartObject, - TArray& NonWeightBlendedLayerNames); - - static bool IsUnitLandscapeLayer( - const FHoudiniGeoPartObject& LayerGeoPartObject); - - // Return the height min/max values for all - static bool CalcHeightGlobalZminZMax( - const TArray& AllOutputs, - float& OutGlobalMin, - float& OutGlobalMax); - - // Returns the min/max values per layer/volume for an array of volumes/heightfields - static void CalcHeightfieldsArrayGlobalZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums, - bool bShouldEmptyMaps=true); - - // Iterate over layers for the heightfields and retrieve min/max values - // from attributes, otherwise return default values. - static void GetLayersZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums); - - static bool ConvertHeightfieldLayerToLandscapeLayer( - const TArray& FloatLayerData, - const int32& HoudiniXSize, - const int32& HoudiniYSize, - const float& LayerMin, - const float& LayerMax, - const int32& LandscapeXSize, - const int32& LandscapeYSize, - TArray& LayerData, - const bool& NoResize = false); - - static bool ResizeLayerDataForLandscape( - TArray< uint8 >& LayerData, - const int32& SizeX, - const int32& SizeY, - const int32& NewSizeX, - const int32& NewSizeY); - - // static ALandscapeProxy * CreateLandscape( - // const TArray< uint16 >& IntHeightData, - // const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - // const FTransform& LandscapeTransform, - // const int32& XSize, - // const int32& YSize, - // const int32& NumSectionPerLandscapeComponent, - // const int32& NumQuadsPerLandscapeSection, - // UMaterialInterface* LandscapeMaterial, - // UMaterialInterface* LandscapeHoleMaterial, - // const bool& CreateLandscapeStreamingProxy, - // bool bNeedCreateNewWorld, - // UWorld* SpawnWorld, - // FHoudiniPackageParams InPackageParams, - // bool& bOutCreatedNewMap); - - static ALandscapeProxy* CreateLandscapeTileInWorld( - const TArray< uint16 >& IntHeightData, - const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - const FTransform& TileTransform, - const FIntPoint& TileLocation, - const int32& XSize, - const int32& YSize, - const int32& NumSectionPerLandscapeComponent, - const int32& NumQuadsPerLandscapeSection, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhysicalMaterial, - const FString& LandscapeTileActorName, - LandscapeActorType ActorType, - ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies - UWorld* InWorld, // World in which to spawn - ULevel* InLevel, // Level, contained in World, in which to spawn. - FHoudiniPackageParams InPackageParams); - - // Destroy the given landscape and all its proxies - static void DestroyLandscape(ALandscape* Landscape); - -protected: - /** - * Calculate the location of a landscape tile. - * This location is typically used in conjunction with ALandscapeProxy::SetAbsoluteSectionBase(). - */ - static void CalculateTileLocation( - int32 NumSectionsPerComponent, - int32 NumQuadsPerSection, - const FTransform& InTileTransform, - FHoudiniLandscapeReferenceLocation& RefLoc, - FTransform& OutLandscapeTransform, - FIntPoint& OutTileLocation); - -public: - - static void GetLandscapeMaterials( - const FHoudiniGeoPartObject& InHeightHGPO, - UMaterialInterface*& OutLandscapeMaterial, - UMaterialInterface*& OutLandscapeHoleMaterial, - UPhysicalMaterial*& OutLandscapePhysicalMaterial); - - static bool GetLandscapeComponentExtentAttributes( - const FHoudiniGeoPartObject& HoudiniGeoPartObject, - int32& MinX, - int32& MaxX, - int32& MinY, - int32& MaxY); - - static ULandscapeLayerInfoObject* FindOrCreateLandscapeLayerInfoObject( - const FString& InLayerName, - const FString& InPackagePath, - const FString& InPackageName, - UPackage*& OutPackage); - - static bool EnableWorldComposition(); - - static bool GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const int32& InPrimIndex, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, - const TArray& InAllPropertyAttributes); - - static bool BackupLandscapeToImageFiles( - const FString& BaseName, ALandscapeProxy* Landscape); - - static bool RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy); - - static UPhysicalMaterial* GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO); - - static ULandscapeLayerInfoObject* GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName); - - private: - - static bool ImportLandscapeData( - ULandscapeInfo* LandscapeInfo, - const FString& Filename, - const FString& LayerName, - ULandscapeLayerInfoObject* LayerInfoObject = nullptr); - - static UTexture2D* CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& InFloatBuffer, - const float& InMin, - const float& InMax); - - static UTexture2D* CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& IntBuffer); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "Landscape.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "Engine/World.h" +#include "EngineUtils.h" +#include "HoudiniEngineOutputStats.h" +#include "HoudiniPackageParams.h" +#include "HoudiniTranslatorTypes.h" + +class UHoudiniAssetComponent; +class ULandscapeLayerInfoObject; +struct FHoudiniGenericAttribute; +struct FHoudiniPackageParams; + +struct HOUDINIENGINE_API FHoudiniLandscapeTranslator +{ + public: + enum class LandscapeActorType : uint8 + { + LandscapeActor = 0, + LandscapeStreamingProxy = 1, + }; + + static bool CreateLandscape( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TSet& ClearedLayers, + TArray& OutCreatedPackages); + + static bool OutputLandscape_Temp( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TArray& OutCreatedPackages); + + // Outputting landscape as "editable layers" differs significantly from + // landscape outputs in "temp mode". To avoid a bigger spaghetti mess, we're + // dealing with editable layers completely separately. + static bool OutputLandscape_EditableLayer( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TSet& ClearedLayers, + TArray& OutCreatedPackages); + + static ALandscapeProxy* FindExistingLandscapeActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + static ALandscapeProxy* FindTargetLandscapeProxy( + const FString& ActorName, + UWorld* World, + const TArray& LandscapeInputs + ); + + protected: + + static bool IsLandscapeInfoCompatible( + const ULandscapeInfo* LandscapeInfo, + const int32 NumSectionsX, + const int32 NumSectionsY); + + static bool IsLandscapeTileCompatible( + const ALandscapeProxy* TileActor, + const int32 InTileSizeX, + const int32 InTileSizeY, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection); + + static bool IsLandscapeTypeCompatible( + const AActor* Actor, + LandscapeActorType ActorType); + + static bool PopulateLandscapeExtents( + FHoudiniLandscapeExtent& Extent, + const ULandscapeInfo* LandscapeInfo + ); + + /** + * Find a ALandscapeProxy actor that can be reused. It is important + * to note that the request landscape actor could not be found, + * `OutWorld` and `OutLevel` should be used to spawn the + * new landscape actor. + * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. + */ + static ALandscapeProxy* FindExistingLandscapeActor( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage, + const EPackageMode& InPackageMode); + + static ALandscapeProxy* FindExistingLandscapeActor_Temp( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + /** + * Attempt the given ALandscapeActor to the outer HAC. Note + * that certain package modes (such as Bake) may choose not to do so. + * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. + */ + static void SetLandscapeActorAsOutput( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor, + const EPackageMode InPackageMode); + + static void SetLandscapeActorAsOutput_Bake( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor); + + static void SetLandscapeActorAsOutput_Temp( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor); + + /** + * Attach the given actor the HoudiniAssetComponent that + * owns `InOutput`, if any. + * @returns True if the actor was attached. Otherwise, return false. + */ + static bool AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor); + + /** + * Get the actor name suffix to be used in the specific packaging mode. + * @returns Suffix for actor names, return as an FString. + */ + static FString GetActorNameSuffix(const EPackageMode& InPackageMode); + + + // Helpers to get rid of repetitive boilerplate. + static void DoPreEditChangeProperty(UObject* Obj, FName PropertyName); + static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); + + public: + + + static const FHoudiniGeoPartObject* GetHoudiniHeightFieldFromOutput( + UHoudiniOutput* InOutput); + + static void GetHeightfieldsLayersFromOutput( + const UHoudiniOutput* InOutput, + const FHoudiniGeoPartObject& Heightfield, + TArray< const FHoudiniGeoPartObject* >& FoundLayers); + + static bool GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo); + + static bool GetHoudiniHeightfieldFloatData( + const FHoudiniGeoPartObject* HGPO, + TArray &OutFloatArr, + float &OutFloatMin, + float &OutFloatMax); + + static bool CalcLandscapeSizeFromHeightfieldSize( + const int32& HoudiniSizeX, + const int32& HoudiniSizeY, + int32& UnrealSizeX, + int32& UnrealSizeY, + int32& NumSectionsPerComponent, + int32& NumQuadsPerSection); + + static bool ConvertHeightfieldDataToLandscapeData( + const TArray< float >& HeightfieldFloatValues, + const FHoudiniVolumeInfo& HeightfieldVolumeInfo, + const int32& FinalXSize, + const int32& FinalYSize, + float FloatMin, + float FloatMax, + TArray< uint16 >& IntHeightData, + FTransform& LandscapeTransform, + const bool NoResize = false, + const bool bOverrideZScale = false, + const float CustomZScale = 100.f); + + static bool ResizeHeightDataForLandscape( + TArray& HeightData, + const int32& SizeX, + const int32& SizeY, + const int32& NewSizeX, + const int32& NewSizeY, + FVector& LandscapeResizeFactor, + FVector& LandscapePositionOffset); + + static bool CreateOrUpdateLandscapeLayers( + const TArray& FoundLayers, + const FHoudiniGeoPartObject& HeightField, + const int32& LandscapeXSize, + const int32& LandscapeYSize, + const TMap &GlobalMinimums, + const TMap &GlobalMaximums, + TArray& OutLayerInfos, + bool bIsUpdate, + const FHoudiniPackageParams& InTilePackageParams, + const FHoudiniPackageParams& InLayerPackageParams, + TArray& OutCreatedPackages); + + static bool GetNonWeightBlendedLayerNames( + const FHoudiniGeoPartObject& HeightfieldGeoPartObject, + TArray& NonWeightBlendedLayerNames); + + static bool IsUnitLandscapeLayer( + const FHoudiniGeoPartObject& LayerGeoPartObject); + + // Return the height min/max values for all + static bool CalcHeightGlobalZminZMax( + const TArray& AllOutputs, + float& OutGlobalMin, + float& OutGlobalMax); + + // Returns the min/max values per layer/volume for an array of volumes/heightfields + static void CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums, + bool bShouldEmptyMaps=true); + + // Iterate over layers for the heightfields and retrieve min/max values + // from attributes, otherwise return default values. + static void GetLayersZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums); + + static bool ConvertHeightfieldLayerToLandscapeLayer( + const TArray& FloatLayerData, + const int32& HoudiniXSize, + const int32& HoudiniYSize, + const float& LayerMin, + const float& LayerMax, + const int32& LandscapeXSize, + const int32& LandscapeYSize, + TArray& LayerData, + const bool& NoResize = false); + + static bool ResizeLayerDataForLandscape( + TArray< uint8 >& LayerData, + const int32& SizeX, + const int32& SizeY, + const int32& NewSizeX, + const int32& NewSizeY); + + // static ALandscapeProxy * CreateLandscape( + // const TArray< uint16 >& IntHeightData, + // const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + // const FTransform& LandscapeTransform, + // const int32& XSize, + // const int32& YSize, + // const int32& NumSectionPerLandscapeComponent, + // const int32& NumQuadsPerLandscapeSection, + // UMaterialInterface* LandscapeMaterial, + // UMaterialInterface* LandscapeHoleMaterial, + // const bool& CreateLandscapeStreamingProxy, + // bool bNeedCreateNewWorld, + // UWorld* SpawnWorld, + // FHoudiniPackageParams InPackageParams, + // bool& bOutCreatedNewMap); + + static ALandscapeProxy* CreateLandscapeTileInWorld( + const TArray< uint16 >& IntHeightData, + const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + const FTransform& TileTransform, + const FIntPoint& TileLocation, + const int32& XSize, + const int32& YSize, + const int32& NumSectionPerLandscapeComponent, + const int32& NumQuadsPerLandscapeSection, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* LandscapePhysicalMaterial, + const FString& LandscapeTileActorName, + LandscapeActorType ActorType, + ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies + UWorld* InWorld, // World in which to spawn + ULevel* InLevel, // Level, contained in World, in which to spawn. + FHoudiniPackageParams InPackageParams); + + // Destroy the given landscape and all its proxies + static void DestroyLandscape(ALandscape* Landscape); + +protected: + /** + * Calculate the location of a landscape tile. + * This location is typically used in conjunction with ALandscapeProxy::SetAbsoluteSectionBase(). + */ + static void CalculateTileLocation( + int32 NumSectionsPerComponent, + int32 NumQuadsPerSection, + const FTransform& InTileTransform, + FHoudiniLandscapeReferenceLocation& RefLoc, + FTransform& OutLandscapeTransform, + FIntPoint& OutTileLocation); + +public: + + static void GetLandscapeMaterials( + const FHoudiniGeoPartObject& InHeightHGPO, + UMaterialInterface*& OutLandscapeMaterial, + UMaterialInterface*& OutLandscapeHoleMaterial, + UPhysicalMaterial*& OutLandscapePhysicalMaterial); + + static bool GetLandscapeComponentExtentAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + int32& MinX, + int32& MaxX, + int32& MinY, + int32& MaxY); + + static ULandscapeLayerInfoObject* FindOrCreateLandscapeLayerInfoObject( + const FString& InLayerName, + const FString& InPackagePath, + const FString& InPackageName, + UPackage*& OutPackage); + + static bool EnableWorldComposition(); + + static bool GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const int32& InPrimIndex, + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, + const TArray& InAllPropertyAttributes); + + static bool BackupLandscapeToImageFiles( + const FString& BaseName, ALandscapeProxy* Landscape); + + static bool RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy); + + static UPhysicalMaterial* GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO); + + static ULandscapeLayerInfoObject* GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName); + + private: + + static bool ImportLandscapeData( + ULandscapeInfo* LandscapeInfo, + const FString& Filename, + const FString& LayerName, + ULandscapeLayerInfoObject* LayerInfoObject = nullptr); + + static UTexture2D* CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& InFloatBuffer, + const float& InMin, + const float& InMax); + + static UTexture2D* CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& IntBuffer); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp index 322646b0f..3189e5440 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp @@ -1,3413 +1,3413 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniMaterialTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPackageParams.h" - -#include "Materials/Material.h" -#include "Materials/MaterialInstance.h" -#include "Materials/MaterialInstanceConstant.h" -#include "PhysicalMaterials/PhysicalMaterial.h" - -#include "Materials/MaterialExpressionTextureSample.h" -#include "Materials/MaterialExpressionTextureCoordinate.h" -#include "Materials/MaterialExpressionConstant4Vector.h" -#include "Materials/MaterialExpressionConstant.h" -#include "Materials/MaterialExpressionMultiply.h" -#include "Materials/MaterialExpressionVertexColor.h" -#include "Materials/MaterialExpressionTextureSampleParameter2D.h" -#include "Materials/MaterialExpressionVectorParameter.h" -#include "Materials/MaterialExpressionScalarParameter.h" -#include "ImageUtils.h" -#include "PackageTools.h" -#include "AssetRegistryModule.h" -#include "UObject/MetaData.h" - -#if WITH_EDITOR - #include "Factories/MaterialFactoryNew.h" - #include "Factories/MaterialInstanceConstantFactoryNew.h" -#endif - -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeX = -400; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeY = -150; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepX = 220; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepY = 220; - -bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( - const HAPI_NodeId& InAssetId, - const FHoudiniPackageParams& InPackageParams, - const TArray& InUniqueMaterialIds, - const TArray& InUniqueMaterialInfos, - const TMap& InMaterials, - TMap& OutMaterials, - TArray& OutPackages, - const bool& bForceRecookAll, - bool bInTreatExistingMaterialsAsUpToDate) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMaterialTranslator::CreateHoudiniMaterials")); - - if (InUniqueMaterialIds.Num() <= 0) - return false; - - if (InUniqueMaterialInfos.Num() != InUniqueMaterialIds.Num()) - return false; - - // Empty returned materials. - OutMaterials.Empty(); - - // Update context for generated materials (will trigger when object goes out of scope). - FMaterialUpdateContext MaterialUpdateContext; - - // Default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - OutMaterials.Add(HAPI_UNREAL_DEFAULT_MATERIAL_NAME, DefaultMaterial); - - // Factory to create materials. - UMaterialFactoryNew * MaterialFactory = NewObject(); - MaterialFactory->AddToRoot(); - - for (int32 MaterialIdx = 0; MaterialIdx < InUniqueMaterialIds.Num(); MaterialIdx++) - { - HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; - - HAPI_MaterialInfo MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; - if (!MaterialInfo.exists) - { - // The material does not exist, - // we will use the default Houdini material in this case. - continue; - } - - // Get the material node's node information. - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &NodeInfo)) - { - continue; - } - - FString MaterialName = TEXT(""); - if (!FHoudiniEngineString::ToFString(NodeInfo.nameSH, MaterialName)) - { - // shouldnt happen, give a generic name - HOUDINI_LOG_WARNING(TEXT("Failed to retrieve material name!")); - MaterialName = TEXT("Material_") + FString::FromInt(MaterialInfo.nodeId); - } - - FString MaterialPathName = TEXT(""); - if (!FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, MaterialInfo, MaterialPathName)) - continue; - - // Check first in the existing material map - UMaterial * Material = nullptr; - UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); - if (FoundMaterial) - { - Material = Cast(*FoundMaterial); - } - - bool bCreatedNewMaterial = false; - if (Material && !Material->IsPendingKill()) - { - // If cached material exists and has not changed, we can reuse it. - if ((bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll) - { - // We found cached material, we can reuse it. - OutMaterials.Add(MaterialPathName, Material); - continue; - } - } - else - { - // Previous Material was not found, we need to create a new one. - EObjectFlags ObjFlags = RF_Public | RF_Standalone; - - // Create material package and get material name. - FString MaterialPackageName; - UPackage * MaterialPackage = FHoudiniMaterialTranslator::CreatePackageForMaterial( - MaterialInfo.nodeId, MaterialName, InPackageParams, MaterialPackageName); - - Material = (UMaterial *)MaterialFactory->FactoryCreateNew( - UMaterial::StaticClass(), MaterialPackage, *MaterialPackageName, ObjFlags, NULL, GWarn); - - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialName); - - bCreatedNewMaterial = true; - } - - if (!Material || Material->IsPendingKill()) - continue; - - // Get the asset name from the package params - FString AssetName = InPackageParams.HoudiniAssetName.IsEmpty() ? TEXT("HoudiniAsset") : InPackageParams.HoudiniAssetName; - - // Get the package and add it to our list - UPackage* Package = Material->GetOutermost(); - OutPackages.AddUnique(Package); - - /* - // TODO: This should be handled in the mesh/instance translator - // If this is an instancer material, enable the instancing flag. - if (UniqueInstancerMaterialIds.Contains(MaterialId)) - Material->bUsedWithInstancedStaticMeshes = true; - */ - - // Reset material expressions. - Material->Expressions.Empty(); - - // Generate various components for this material. - bool bMaterialComponentCreated = false; - int32 MaterialNodeY = FHoudiniMaterialTranslator::MaterialExpressionNodeY; - - // By default we mark material as opaque. Some of component creators can change this. - Material->BlendMode = BLEND_Opaque; - - // Extract diffuse plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract opacity plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract opacity mask plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract normal plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentNormal( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract specular plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract roughness plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract metallic plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract emissive plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Set other material properties. - Material->TwoSided = true; - Material->SetShadingModel(MSM_DefaultLit); - - // Schedule this material for update. - MaterialUpdateContext.AddMaterial(Material); - - // Cache material. - OutMaterials.Add(MaterialPathName, Material); - - // Propagate and trigger material updates. - if (bCreatedNewMaterial) - FAssetRegistryModule::AssetCreated(Material); - - Material->PreEditChange(nullptr); - Material->PostEditChange(); - Material->MarkPackageDirty(); - } - - MaterialFactory->RemoveFromRoot(); - - return true; -} - -// -bool -FHoudiniMaterialTranslator::CreateMaterialInstances( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& UniqueMaterialInstanceOverrides, - const TArray& InPackages, - const TMap& InMaterials, - TMap& OutMaterials, - const bool& bForceRecookAll) -{ - // Check the node ID is valid - if (InHGPO.AssetId < 0) - return false; - - // No material instance attributes - if (UniqueMaterialInstanceOverrides.Num() <= 0) - return false; - - // TODO: Improve! - // Get the material name from the material_instance attribute - // Since the material instance attribute can be set per primitive, it's going to be very difficult to know - // exactly where to look for the nth material instance. In order for the material slot to be created, - // we used the fact that the material instance attribute had to be different - // This is pretty hacky and we should probably require an extra material_instance_index attribute instead. - // as we can only create one instance of the same material, and cant get two slots for the same "source" material. - int32 MaterialIndex = 0; - for (TMap::TConstIterator Iter(UniqueMaterialInstanceOverrides); Iter; ++Iter) - { - FString CurrentSourceMaterial = Iter->Key; - if (CurrentSourceMaterial.IsEmpty()) - continue; - - // Try to find the material we want to create an instance of - UMaterialInterface* CurrentSourceMaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentSourceMaterial, nullptr, LOAD_NoWarn, nullptr)); - - if (!CurrentSourceMaterialInterface || CurrentSourceMaterialInterface->IsPendingKill()) - { - // Couldn't find the source material - HOUDINI_LOG_WARNING(TEXT("Couldn't find the source material %s to create a material instance."), *CurrentSourceMaterial); - continue; - } - - // Create/Retrieve the package for the MI - FString MaterialInstanceName; - FString MaterialInstanceNamePrefix = UPackageTools::SanitizePackageName( - CurrentSourceMaterialInterface->GetName() + TEXT("_instance_") + FString::FromInt(MaterialIndex)); - - // Increase the material index - MaterialIndex++; - - // See if we can find an existing package for that instance - UPackage * MaterialInstancePackage = nullptr; - UMaterialInterface * const * FoundMatPtr = InMaterials.Find(MaterialInstanceNamePrefix); - if (FoundMatPtr && *FoundMatPtr) - { - // We found an already existing MI, get its package - MaterialInstancePackage = Cast((*FoundMatPtr)->GetOuter()); - } - - if (MaterialInstancePackage) - { - MaterialInstanceName = MaterialInstancePackage->GetName(); - } - else - { - // We couldnt find the corresponding M_I package, so create a new one - MaterialInstancePackage = CreatePackageForMaterial(InHGPO.AssetId, MaterialInstanceNamePrefix, InPackageParams, MaterialInstanceName); - } - - // Couldn't create a package for that Material Instance - if (!MaterialInstancePackage) - continue; - - bool bNewMaterialCreated = false; - UMaterialInstanceConstant* NewMaterialInstance = LoadObject(MaterialInstancePackage, *MaterialInstanceName, nullptr, LOAD_None, nullptr); - if (!NewMaterialInstance) - { - // Factory to create materials. - UMaterialInstanceConstantFactoryNew* MaterialInstanceFactory = NewObject< UMaterialInstanceConstantFactoryNew >(); - if (!MaterialInstanceFactory) - continue; - - // Create the new material instance - MaterialInstanceFactory->AddToRoot(); - MaterialInstanceFactory->InitialParent = CurrentSourceMaterialInterface; - NewMaterialInstance = (UMaterialInstanceConstant*)MaterialInstanceFactory->FactoryCreateNew( - UMaterialInstanceConstant::StaticClass(), MaterialInstancePackage, FName(*MaterialInstanceName), - RF_Public | RF_Standalone, NULL, GWarn); - - if (NewMaterialInstance) - bNewMaterialCreated = true; - - MaterialInstanceFactory->RemoveFromRoot(); - } - - if (!NewMaterialInstance) - { - HOUDINI_LOG_WARNING(TEXT("Couldn't access the material instance for %s"), *CurrentSourceMaterial); - continue; - } - - // Update context for generated materials (will trigger when the object goes out of scope). - FMaterialUpdateContext MaterialUpdateContext; - - bool bModifiedMaterialParameters = false; - // See if we need to override some of the material instance's parameters - TArray AllMatParams; - // Get the detail material parameters - int32 ParamCount = FHoudiniEngineUtils::GetGenericAttributeList( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, - AllMatParams, HAPI_ATTROWNER_DETAIL, -1); - - // Then the primitive material parameters - int32 MaterialIndexToAttributeIndex = Iter->Value; - ParamCount += FHoudiniEngineUtils::GetGenericAttributeList( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, - AllMatParams, HAPI_ATTROWNER_PRIM, MaterialIndexToAttributeIndex); - - for (int32 ParamIdx = 0; ParamIdx < AllMatParams.Num(); ParamIdx++) - { - // Try to update the material instance parameter corresponding to the attribute - if (UpdateMaterialInstanceParameter(AllMatParams[ParamIdx], NewMaterialInstance, InPackages)) - bModifiedMaterialParameters = true; - } - - // Schedule this material for update if needed. - if (bNewMaterialCreated || bModifiedMaterialParameters) - MaterialUpdateContext.AddMaterialInstance(NewMaterialInstance); - - if (bNewMaterialCreated) - { - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialInstanceName); - // Notify registry that we have created a new material. - FAssetRegistryModule::AssetCreated(NewMaterialInstance); - } - - if (bNewMaterialCreated || bModifiedMaterialParameters) - { - // Dirty the material - NewMaterialInstance->MarkPackageDirty(); - - // Update the material instance - NewMaterialInstance->InitStaticPermutation(); - NewMaterialInstance->PreEditChange(nullptr); - NewMaterialInstance->PostEditChange(); - /* - // Automatically save the package to avoid further issue - MaterialInstancePackage->SetDirtyFlag( true ); - MaterialInstancePackage->FullyLoad(); - UPackage::SavePackage( - MaterialInstancePackage, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, - *FPackageName::LongPackageNameToFilename( MaterialInstancePackage->GetName(), FPackageName::GetAssetPackageExtension() ) ); - */ - } - - // Add the created material to the output assignement map - // Use the "source" material name as we want the instance to replace it - OutMaterials.Add(CurrentSourceMaterial, NewMaterialInstance); - } - - return true; -} - -bool -FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath) -{ - HAPI_MaterialInfo MaterialInfo; - FHoudiniApi::MaterialInfo_Init(&MaterialInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetMaterialInfo( - FHoudiniEngine::Get().GetSession(), InMaterialNodeId, - &MaterialInfo), false); - - return GetMaterialRelativePath(InAssetId, MaterialInfo, OutRelativePath); -} -bool -FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialInfo, FString& OutRelativePath) -{ - if (InAssetId < 0 || !InMaterialInfo.exists) - return false; - - // We want to get the asset node path so we can remove it from the material name - FString AssetNodeName = TEXT(""); - { - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); - - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo), false); - - FHoudiniEngineString::ToFString(AssetNodeInfo.internalNodePathSH, AssetNodeName); - } - - // Get the material name from the info - FString MaterialNodeName = TEXT(""); - { - HAPI_NodeInfo MaterialNodeInfo; - FHoudiniApi::NodeInfo_Init(&MaterialNodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &MaterialNodeInfo), false); - - FHoudiniEngineString::ToFString(MaterialNodeInfo.internalNodePathSH, MaterialNodeName); - } - - if (AssetNodeName.Len() > 0 && MaterialNodeName.Len() > 0) - { - // Remove AssetNodeName part from MaterialNodeName. Extra position is for separator. - OutRelativePath = MaterialNodeName.Mid(AssetNodeName.Len() + 1); - return true; - } - - return false; -} - - -UPackage* -FHoudiniMaterialTranslator::CreatePackageForMaterial( - const HAPI_NodeId& InMaterialNodeId, - const FString& InMaterialName, - const FHoudiniPackageParams& InPackageParams, - FString& OutMaterialName) -{ - FString MaterialDescriptor = TEXT("_material_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InMaterialName; - - FHoudiniPackageParams MyPackageParams = InPackageParams; - if (!MyPackageParams.ObjectName.IsEmpty()) - { - MyPackageParams.ObjectName += MaterialDescriptor; - } - else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) - { - MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + MaterialDescriptor; - } - else - { - MyPackageParams.ObjectName = MaterialDescriptor; - } - MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); - - return MyPackageParams.CreatePackageForObject(OutMaterialName); -} - - -UPackage* -FHoudiniMaterialTranslator::CreatePackageForTexture( - const HAPI_NodeId& InMaterialNodeId, - const FString& InTextureType, - const FHoudiniPackageParams& InPackageParams, - FString& OutTextureName) -{ - FString TextureInfoDescriptor = TEXT("_texture_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InTextureType; - FHoudiniPackageParams MyPackageParams = InPackageParams; - if (!MyPackageParams.ObjectName.IsEmpty()) - { - MyPackageParams.ObjectName += TextureInfoDescriptor; - } - else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) - { - MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + TextureInfoDescriptor; - } - else - { - MyPackageParams.ObjectName = TextureInfoDescriptor; - } - MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); - - return MyPackageParams.CreatePackageForObject(OutTextureName); -} - - -UTexture2D * -FHoudiniMaterialTranslator::CreateUnrealTexture( - UTexture2D* ExistingTexture, - const HAPI_ImageInfo& ImageInfo, - UPackage* Package, - const FString& TextureName, - const TArray& ImageBuffer, - const FCreateTexture2DParameters& TextureParameters, - const TextureGroup& LODGroup, - const FString& TextureType, - const FString& NodePath) -{ - if (!Package || Package->IsPendingKill()) - return nullptr; - - UTexture2D * Texture = nullptr; - if (ExistingTexture) - { - Texture = ExistingTexture; - } - else - { - // Create new texture object. - Texture = NewObject< UTexture2D >( - Package, UTexture2D::StaticClass(), *TextureName, - RF_Transactional); - - // Assign texture group. - Texture->LODGroup = LODGroup; - } - - // Add/Update meta information to package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *TextureName); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_NODE_PATH, *NodePath); - - // Initialize texture source. - Texture->Source.Init(ImageInfo.xRes, ImageInfo.yRes, 1, 1, TSF_BGRA8); - - // Lock the texture. - uint8 * MipData = Texture->Source.LockMip(0); - - // Create base map. - uint8* DestPtr = nullptr; - uint32 SrcWidth = ImageInfo.xRes; - uint32 SrcHeight = ImageInfo.yRes; - const char * SrcData = &ImageBuffer[0]; - - for (uint32 y = 0; y < SrcHeight; y++) - { - DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; - - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth * 4 + x * 4; - - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 2); // B - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 1); // G - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 0); // R - - if (TextureParameters.bUseAlpha) - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 3); // A - else - *DestPtr++ = 0xFF; - } - } - - bool bHasAlphaValue = false; - if (TextureParameters.bUseAlpha) - { - // See if there is an actual alpha value in the texture or if we can ignore the texture alpha - for (uint32 y = 0; y < SrcHeight; y++) - { - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth * 4 + x * 4; - if (*(uint8*)(SrcData + DataOffset + 3) != 0xFF) - { - bHasAlphaValue = true; - break; - } - } - - if (bHasAlphaValue) - break; - } - } - - // Unlock the texture. - Texture->Source.UnlockMip(0); - - // Texture creation parameters. - Texture->SRGB = TextureParameters.bSRGB; - Texture->CompressionSettings = TextureParameters.CompressionSettings; - Texture->CompressionNoAlpha = !bHasAlphaValue; - Texture->DeferCompression = TextureParameters.bDeferCompression; - - // Set the Source Guid/Hash if specified. - /* - if ( TextureParameters.SourceGuidHash.IsValid() ) - { - Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); - } - */ - - Texture->PostEditChange(); - - return Texture; -} - - - -bool -FHoudiniMaterialTranslator::HapiExtractImage( - const HAPI_ParmId& NodeParmId, - const HAPI_MaterialInfo& MaterialInfo, - const char * PlaneType, - const HAPI_ImageDataFormat& ImageDataFormat, - HAPI_ImagePacking ImagePacking, - bool bRenderToImage, - TArray& OutImageBuffer ) -{ - if (bRenderToImage) - { - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, NodeParmId), false); - } - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImageInfo), false); - - ImageInfo.dataFormat = ImageDataFormat; - ImageInfo.interleaved = true; - ImageInfo.packing = ImagePacking; - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetImageInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImageInfo), false); - - int32 ImageBufferSize = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ExtractImageToMemory( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, HAPI_RAW_FORMAT_NAME, - PlaneType, &ImageBufferSize), false); - - if (ImageBufferSize <= 0) - return false; - - OutImageBuffer.SetNumUninitialized(ImageBufferSize); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImageMemoryBuffer( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &OutImageBuffer[0], - ImageBufferSize), false); - - return true; -} - -bool -FHoudiniMaterialTranslator::HapiGetImagePlanes( - const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes) -{ - OutImagePlanes.Empty(); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, NodeParmId), false); - - int32 ImagePlaneCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlaneCount( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImagePlaneCount), false); - - if (ImagePlaneCount <= 0) - return true; - - TArray ImagePlaneStringHandles; - ImagePlaneStringHandles.SetNumZeroed(ImagePlaneCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlanes( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImagePlaneStringHandles[0], ImagePlaneCount), false); - - FHoudiniEngineString::SHArrayToFStringArray(ImagePlaneStringHandles, OutImagePlanes); - - return true; -} - - -UMaterialExpression * -FHoudiniMaterialTranslator::MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass) -{ - if (!Expression) - return nullptr; - -#if WITH_EDITOR - if (ExpressionClass == Expression->GetClass()) - return Expression; - - // If this is a channel multiply expression, we can recurse. - UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast< UMaterialExpressionMultiply >(Expression); - if (MaterialExpressionMultiply) - { - { - UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->A.Expression; - if (MaterialExpression) - { - if (MaterialExpression->GetClass() == ExpressionClass) - return MaterialExpression; - - MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( - Cast(MaterialExpression), ExpressionClass); - - if (MaterialExpression) - return MaterialExpression; - } - } - - { - UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->B.Expression; - if (MaterialExpression) - { - if (MaterialExpression->GetClass() == ExpressionClass) - return MaterialExpression; - - MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( - Cast(MaterialExpression), ExpressionClass); - - if (MaterialExpression) - return MaterialExpression; - } - } - } -#endif - - return nullptr; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Names of generating Houdini parameters. - FString GeneratingParameterNameDiffuseTexture = TEXT(""); - FString GeneratingParameterNameUniformColor = TEXT(""); - FString GeneratingParameterNameVertexColor = TEXT(HAPI_UNREAL_ATTRIB_COLOR); - - // Diffuse texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Default; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // Attempt to look up previously created expressions. - UMaterialExpression * MaterialExpression = Material->BaseColor.Expression; - - // Locate sampling expression. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureSample = - Cast< UMaterialExpressionTextureSampleParameter2D >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // If texture sampling expression does exist, attempt to look up corresponding texture. - UTexture2D * TextureDiffuse = nullptr; - if (IsValid(ExpressionTextureSample)) - TextureDiffuse = Cast< UTexture2D >(ExpressionTextureSample->Texture); - - // Locate uniform color expression. - UMaterialExpressionVectorParameter * ExpressionConstant4Vector = - Cast< UMaterialExpressionVectorParameter >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionVectorParameter::StaticClass())); - - // If uniform color expression does not exist, create it. - if (!ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill()) - { - ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( - Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); - ExpressionConstant4Vector->DefaultValue = FLinearColor::White; - } - - // Add expression. - Material->Expressions.Add(ExpressionConstant4Vector); - - // Locate vertex color expression. - UMaterialExpressionVertexColor * ExpressionVertexColor = - Cast< UMaterialExpressionVertexColor >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionVertexColor::StaticClass())); - - // If vertex color expression does not exist, create it. - if (!ExpressionVertexColor || ExpressionVertexColor->IsPendingKill()) - { - ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( - Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag); - ExpressionVertexColor->Desc = GeneratingParameterNameVertexColor; - } - - // Add expression. - Material->Expressions.Add(ExpressionVertexColor); - - // Material should have at least one multiply expression. - UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast(MaterialExpression); - if (!MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill()) - MaterialExpressionMultiply = NewObject( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - // Add expression. - Material->Expressions.Add(MaterialExpressionMultiply); - - // See if primary multiplication has secondary multiplication as A input. - UMaterialExpressionMultiply * MaterialExpressionMultiplySecondary = nullptr; - if (MaterialExpressionMultiply->A.Expression) - MaterialExpressionMultiplySecondary = - Cast(MaterialExpressionMultiply->A.Expression); - - // See if a diffuse texture is available. - HAPI_ParmInfo ParmDiffuseTextureInfo; - HAPI_ParmId ParmDiffuseTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, - true, - ParmDiffuseTextureId, - ParmDiffuseTextureInfo)) - { - // Found via OGL tag - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_DIFFUSE, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, - false, - ParmDiffuseTextureId, - ParmDiffuseTextureInfo)) - { - // Found via Parm name - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); - } - else - { - // failed to find the texture - ParmDiffuseTextureId = -1; - } - - // If we have diffuse texture parameter. - if (ParmDiffuseTextureId >= 0) - { - TArray ImageBuffer; - - // Get image planes of diffuse map. - TArray DiffuseImagePlanes; - bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( - ParmDiffuseTextureId, InMaterialInfo, DiffuseImagePlanes); - - HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; - const char * PlaneType = ""; - - if (bFoundImagePlanes && DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))) - { - if (DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA))) - { - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - - // Material does use alpha. - CreateTexture2DParameters.bUseAlpha = true; - } - else - { - // We still need to have the Alpha plane, just not the CreateTexture2DParameters - // alpha option. This is because all texture data from Houdini Engine contains - // the alpha plane by default. - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - } - } - else - { - bFoundImagePlanes = false; - } - - // Retrieve color plane. - if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( - ParmDiffuseTextureId, InMaterialInfo, PlaneType, - HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) - { - UPackage * TextureDiffusePackage = nullptr; - if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) - TextureDiffusePackage = Cast(TextureDiffuse->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureDiffuseName; - bool bCreatedNewTextureDiffuse = false; - - // Create diffuse texture package, if this is a new diffuse texture. - if (!TextureDiffusePackage) - { - TextureDiffusePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, - InPackageParams, - TextureDiffuseName); - } - else if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureDiffuseName = TextureDiffuse->GetName(); - } - else - { - TextureDiffuseName = FPaths::GetBaseFilename(TextureDiffusePackage->GetName(), true); - } - - // Create diffuse texture, if we need to create one. - if (!TextureDiffuse || TextureDiffuse->IsPendingKill()) - bCreatedNewTextureDiffuse = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing diffuse texture, or create new one. - TextureDiffuse = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureDiffuse, - ImageInfo, - TextureDiffusePackage, - TextureDiffuseName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureDiffuse->SetFlags(RF_Public | RF_Standalone); - - // Create diffuse sampling expression, if needed. - if (!ExpressionTextureSample) - { - ExpressionTextureSample = NewObject( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionTextureSample->Desc = GeneratingParameterNameDiffuseTexture; - ExpressionTextureSample->ParameterName = *GeneratingParameterNameDiffuseTexture; - ExpressionTextureSample->Texture = TextureDiffuse; - ExpressionTextureSample->SamplerType = SAMPLERTYPE_Color; - - // Add expression. - Material->Expressions.Add(ExpressionTextureSample); - - // Propagate and trigger diffuse texture updates. - if (bCreatedNewTextureDiffuse) - FAssetRegistryModule::AssetCreated(TextureDiffuse); - - TextureDiffuse->PreEditChange(nullptr); - TextureDiffuse->PostEditChange(); - TextureDiffuse->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureDiffusePackage); - } - } - - // See if uniform color is available. - HAPI_ParmInfo ParmDiffuseColorInfo; - HAPI_ParmId ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL, ParmDiffuseColorInfo); - - if (ParmDiffuseColorId >= 0) - { - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL); - } - else - { - ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE, ParmDiffuseColorInfo); - - if (ParmDiffuseColorId >= 0) - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE); - } - - // If we have uniform color parameter. - if (ParmDiffuseColorId >= 0) - { - FLinearColor Color = FLinearColor::White; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&Color.R, - ParmDiffuseColorInfo.floatValuesIndex, ParmDiffuseColorInfo.size) == HAPI_RESULT_SUCCESS) - { - if (ParmDiffuseColorInfo.size == 3) - Color.A = 1.0f; - - // Record generating parameter. - ExpressionConstant4Vector->Desc = GeneratingParameterNameUniformColor; - ExpressionConstant4Vector->ParameterName = *GeneratingParameterNameUniformColor; - ExpressionConstant4Vector->DefaultValue = Color; - } - } - - // If we have have texture sample expression present, we need a secondary multiplication expression. - if (ExpressionTextureSample) - { - if (!MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary = NewObject( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - // Add expression. - Material->Expressions.Add(MaterialExpressionMultiplySecondary); - } - } - else - { - // If secondary multiplication exists, but we have no sampling, we can free it. - if (MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary->A.Expression = nullptr; - MaterialExpressionMultiplySecondary->B.Expression = nullptr; - MaterialExpressionMultiplySecondary->ConditionalBeginDestroy(); - } - } - - float SecondaryExpressionScale = 1.0f; - if (MaterialExpressionMultiplySecondary) - SecondaryExpressionScale = 1.5f; - - // Create multiplication expression which has uniform color and vertex color. - MaterialExpressionMultiply->A.Expression = ExpressionConstant4Vector; - MaterialExpressionMultiply->B.Expression = ExpressionVertexColor; - - ExpressionConstant4Vector->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionConstant4Vector->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - ExpressionVertexColor->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionVertexColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiply->MaterialExpressionEditorY = - (ExpressionVertexColor->MaterialExpressionEditorY - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; - - // Hook up secondary multiplication expression to first one. - if (MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; - MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; - - if (ExpressionTextureSample) - { - ExpressionTextureSample->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; - } - - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiplySecondary->MaterialExpressionEditorY = - MaterialExpressionMultiply->MaterialExpressionEditorY + FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression. - Material->BaseColor.Expression = MaterialExpressionMultiplySecondary; - } - else - { - // Assign expression. - Material->BaseColor.Expression = MaterialExpressionMultiply; - - MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiply->MaterialExpressionEditorY = - (ExpressionVertexColor->MaterialExpressionEditorY - - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; - } - - return true; -} - - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - // Name of generating Houdini parameters. - FString GeneratingParameterNameTexture = TEXT(""); - - UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Opacity expressions. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; - UTexture2D * TextureOpacity = nullptr; - - // Opacity texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // See if opacity texture is available. - HAPI_ParmInfo ParmOpacityTextureInfo; - HAPI_ParmId ParmOpacityTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_OPACITY_OGL, - HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED, - true, - ParmOpacityTextureId, - ParmOpacityTextureInfo)) - { - // Found via OGL tag - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_OPACITY, - HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED, - false, - ParmOpacityTextureId, - ParmOpacityTextureInfo)) - { - // Found via Parm name - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY); - } - else - { - // failed to find the texture - ParmOpacityTextureId = -1; - } - - // If we have opacity texture parameter. - if (ParmOpacityTextureId >= 0) - { - TArray< char > ImageBuffer; - - // Get image planes of opacity map. - TArray< FString > OpacityImagePlanes; - bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( - ParmOpacityTextureId, InMaterialInfo, OpacityImagePlanes); - - HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; - const char * PlaneType = ""; - - bool bColorAlphaFound = (OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA)) && OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))); - - if (bFoundImagePlanes && bColorAlphaFound) - { - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - CreateTexture2DParameters.bUseAlpha = true; - } - else - { - bFoundImagePlanes = false; - } - - if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( - ParmOpacityTextureId, InMaterialInfo, PlaneType, - HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) - { - // Locate sampling expression. - ExpressionTextureOpacitySample = Cast< UMaterialExpressionTextureSampleParameter2D >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // Locate opacity texture, if valid. - if (ExpressionTextureOpacitySample) - TextureOpacity = Cast< UTexture2D >(ExpressionTextureOpacitySample->Texture); - - UPackage * TextureOpacityPackage = nullptr; - if (TextureOpacity) - TextureOpacityPackage = Cast< UPackage >(TextureOpacity->GetOuter()); - - HAPI_ImageInfo ImageInfo; - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureOpacityName; - bool bCreatedNewTextureOpacity = false; - - // Create opacity texture package, if this is a new opacity texture. - if (!TextureOpacityPackage) - { - TextureOpacityPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, - InPackageParams, - TextureOpacityName); - } - else if (TextureOpacity && !TextureOpacity->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureOpacityName = TextureOpacity->GetName(); - } - else - { - TextureOpacityName = FPaths::GetBaseFilename(TextureOpacityPackage->GetName(), true); - } - - // Create opacity texture, if we need to create one. - if (!TextureOpacity) - bCreatedNewTextureOpacity = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing opacity texture, or create new one. - TextureOpacity = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureOpacity, - ImageInfo, - TextureOpacityPackage, - TextureOpacityName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, - NodePath); - - // if (BakeMode == EBakeMode::CookToTemp) - TextureOpacity->SetFlags(RF_Public | RF_Standalone); - - // Create opacity sampling expression, if needed. - if (!ExpressionTextureOpacitySample) - { - ExpressionTextureOpacitySample = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionTextureOpacitySample->Desc = GeneratingParameterNameTexture; - ExpressionTextureOpacitySample->ParameterName = *GeneratingParameterNameTexture; - ExpressionTextureOpacitySample->Texture = TextureOpacity; - ExpressionTextureOpacitySample->SamplerType = SAMPLERTYPE_Grayscale; - - // Offset node placement. - ExpressionTextureOpacitySample->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionTextureOpacitySample->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Add expression. - Material->Expressions.Add(ExpressionTextureOpacitySample); - - // We need to set material type to masked. - TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput* ExpressionOutput = ExpressionOutputs.GetData(); - - Material->OpacityMask.Expression = ExpressionTextureOpacitySample; - Material->BlendMode = BLEND_Masked; - - Material->OpacityMask.Mask = ExpressionOutput->Mask; - Material->OpacityMask.MaskR = 1; - Material->OpacityMask.MaskG = 0; - Material->OpacityMask.MaskB = 0; - Material->OpacityMask.MaskA = 0; - - // Propagate and trigger opacity texture updates. - if (bCreatedNewTextureOpacity) - FAssetRegistryModule::AssetCreated(TextureOpacity); - - TextureOpacity->PreEditChange(nullptr); - TextureOpacity->PostEditChange(); - TextureOpacity->MarkPackageDirty(); - - bExpressionCreated = true; - } - - // Cache the texture package - OutPackages.AddUnique(TextureOpacityPackage); - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - float OpacityValue = 1.0f; - bool bNeedsTranslucency = false; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameters. - FString GeneratingParameterNameScalar = TEXT(""); - FString GeneratingParameterNameTexture = TEXT(""); - - UMaterialExpression * MaterialExpression = Material->Opacity.Expression; - - // Opacity expressions. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; - UMaterialExpressionScalarParameter * ExpressionScalarOpacity = nullptr; - UTexture2D * TextureOpacity = nullptr; - - // Opacity texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // If opacity sampling expression was not created, check if diffuse contains an alpha plane. - if (!ExpressionTextureOpacitySample) - { - UMaterialExpression * MaterialExpressionDiffuse = Material->BaseColor.Expression; - if (MaterialExpressionDiffuse) - { - // Locate diffuse sampling expression. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureDiffuseSample = - Cast< UMaterialExpressionTextureSampleParameter2D >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpressionDiffuse, - UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // See if there's an alpha plane in this expression's texture. - if (ExpressionTextureDiffuseSample) - { - UTexture2D * DiffuseTexture = Cast< UTexture2D >(ExpressionTextureDiffuseSample->Texture); - if (DiffuseTexture && !DiffuseTexture->CompressionNoAlpha) - { - // The diffuse texture has an alpha channel (that wasn't discarded), so we can use it - ExpressionTextureOpacitySample = ExpressionTextureDiffuseSample; - bNeedsTranslucency = true; - } - } - } - } - - // Retrieve opacity value - HAPI_ParmInfo ParmOpacityValueInfo; - HAPI_ParmId ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_OGL, ParmOpacityValueInfo); - - if (ParmOpacityValueId >= 0) - { - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_OGL); - } - else - { - ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA, ParmOpacityValueInfo); - - if (ParmOpacityValueId >= 0) - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA); - } - - if (ParmOpacityValueId >= 0) - { - if (ParmOpacityValueInfo.size > 0 && ParmOpacityValueInfo.floatValuesIndex >= 0) - { - float OpacityValueRetrieved = 1.0f; - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, - (float *)&OpacityValue, ParmOpacityValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - if (!ExpressionScalarOpacity) - { - ExpressionScalarOpacity = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Clamp retrieved value. - OpacityValueRetrieved = FMath::Clamp< float >(OpacityValueRetrieved, 0.0f, 1.0f); - OpacityValue = OpacityValueRetrieved; - - // Set expression fields. - ExpressionScalarOpacity->DefaultValue = OpacityValue; - ExpressionScalarOpacity->SliderMin = 0.0f; - ExpressionScalarOpacity->SliderMax = 1.0f; - ExpressionScalarOpacity->Desc = GeneratingParameterNameScalar; - ExpressionScalarOpacity->ParameterName = *GeneratingParameterNameScalar; - - // Add expression. - Material->Expressions.Add(ExpressionScalarOpacity); - - // If alpha is less than 1, we need translucency. - bNeedsTranslucency |= (OpacityValue != 1.0f); - } - } - } - - if (bNeedsTranslucency) - Material->BlendMode = BLEND_Translucent; - - if (ExpressionScalarOpacity && ExpressionTextureOpacitySample) - { - // We have both alpha and alpha uniform, attempt to locate multiply expression. - UMaterialExpressionMultiply * ExpressionMultiply = - Cast< UMaterialExpressionMultiply >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, - UMaterialExpressionMultiply::StaticClass())); - - if (!ExpressionMultiply) - ExpressionMultiply = NewObject< UMaterialExpressionMultiply >( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - Material->Expressions.Add(ExpressionMultiply); - - TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); - - ExpressionMultiply->A.Expression = ExpressionTextureOpacitySample; - ExpressionMultiply->B.Expression = ExpressionScalarOpacity; - - Material->Opacity.Expression = ExpressionMultiply; - Material->Opacity.Mask = ExpressionOutput->Mask; - Material->Opacity.MaskR = 0; - Material->Opacity.MaskG = 0; - Material->Opacity.MaskB = 0; - Material->Opacity.MaskA = 1; - - ExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMultiply->MaterialExpressionEditorY = MaterialNodeY; - - ExpressionScalarOpacity->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX; - ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - bExpressionCreated = true; - } - else if (ExpressionScalarOpacity) - { - Material->Opacity.Expression = ExpressionScalarOpacity; - - ExpressionScalarOpacity->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - bExpressionCreated = true; - } - else if (ExpressionTextureOpacitySample) - { - TArray ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); - - Material->Opacity.Expression = ExpressionTextureOpacitySample; - Material->Opacity.Mask = ExpressionOutput->Mask; - Material->Opacity.MaskR = 0; - Material->Opacity.MaskG = 0; - Material->Opacity.MaskB = 0; - Material->Opacity.MaskA = 1; - - bExpressionCreated = true; - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentNormal( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - bool bTangentSpaceNormal = true; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Normal texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Normalmap; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if separate normal texture is available. - HAPI_ParmInfo ParmNormalTextureInfo; - HAPI_ParmId ParmNormalTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_NORMAL, - HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED, - false, - ParmNormalTextureId, - ParmNormalTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_NORMAL_OGL, - "", - true, - ParmNormalTextureId, - ParmNormalTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_OGL); - } - else - { - // failed to find the texture - ParmNormalTextureId = -1; - } - - if (ParmNormalTextureId >= 0) - { - // Retrieve space for this normal texture. - HAPI_ParmInfo ParmInfoNormalType; - int32 ParmNormalTypeId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); - - // Retrieve value for normal type choice list (if exists). - if (ParmNormalTypeId >= 0) - { - FString NormalType = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT); - if (ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0) - { - HAPI_StringHandle StringHandle; - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, false, &StringHandle, ParmInfoNormalType.stringValuesIndex, ParmInfoNormalType.size) == HAPI_RESULT_SUCCESS) - { - // Get the actual string value. - FString NormalTypeString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(NormalTypeString)) - NormalType = NormalTypeString; - } - } - - // Check if we require world space normals. - if (NormalType.Equals(TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD), ESearchCase::IgnoreCase)) - bTangentSpaceNormal = false; - } - - // Retrieve color plane. - TArray ImageBuffer; - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNormalTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); - - UTexture2D * TextureNormal = nullptr; - if (ExpressionNormal) - { - TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Normal.Expression) - { - Material->Normal.Expression->ConditionalBeginDestroy(); - Material->Normal.Expression = nullptr; - } - } - - UPackage * TextureNormalPackage = nullptr; - if (TextureNormal) - TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureNormalName; - bool bCreatedNewTextureNormal = false; - - // Create normal texture package, if this is a new normal texture. - if (!TextureNormalPackage) - { - TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - InPackageParams, - TextureNormalName); - } - else if (TextureNormal && !TextureNormal->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureNormalName = TextureNormal->GetName(); - } - else - { - TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); - } - - // Create normal texture, if we need to create one. - if (!TextureNormal) - bCreatedNewTextureNormal = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing normal texture, or create new one. - TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureNormal, - ImageInfo, - TextureNormalPackage, - TextureNormalName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_WorldNormalMap, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureNormal->SetFlags(RF_Public | RF_Standalone); - - // Create normal sampling expression, if needed. - if (!ExpressionNormal) - ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionNormal->Desc = GeneratingParameterName; - ExpressionNormal->ParameterName = *GeneratingParameterName; - - ExpressionNormal->Texture = TextureNormal; - ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; - - // Offset node placement. - ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Set normal space. - Material->bTangentSpaceNormal = bTangentSpaceNormal; - - // Assign expression to material. - Material->Expressions.Add(ExpressionNormal); - Material->Normal.Expression = ExpressionNormal; - - bExpressionCreated = true; - - // Propagate and trigger normal texture updates. - if (bCreatedNewTextureNormal) - FAssetRegistryModule::AssetCreated(TextureNormal); - - TextureNormal->PreEditChange(nullptr); - TextureNormal->PostEditChange(); - TextureNormal->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureNormalPackage); - } - } - - // If separate normal map was not found, see if normal plane exists in diffuse map. - if (!bExpressionCreated) - { - // See if diffuse texture is available. - HAPI_ParmInfo ParmDiffuseTextureInfo; - HAPI_ParmId ParmDiffuseTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, - true, - ParmDiffuseTextureId, - ParmDiffuseTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_DIFFUSE, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, - false, - ParmDiffuseTextureId, - ParmDiffuseTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); - } - else - { - // failed to find the texture - ParmDiffuseTextureId = -1; - } - - if (ParmDiffuseTextureId >= 0) - { - // Normal plane is available in diffuse map. - TArray ImageBuffer; - - // Retrieve color plane - this will contain normal data. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmDiffuseTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = - Cast(Material->Normal.Expression); - - UTexture2D* TextureNormal = nullptr; - if (ExpressionNormal) - { - TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Normal.Expression) - { - Material->Normal.Expression->ConditionalBeginDestroy(); - Material->Normal.Expression = nullptr; - } - } - - UPackage* TextureNormalPackage = nullptr; - if (TextureNormal) - TextureNormalPackage = Cast(TextureNormal->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureNormalName; - bool bCreatedNewTextureNormal = false; - - // Create normal texture package, if this is a new normal texture. - if (!TextureNormalPackage) - { - TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - InPackageParams, - TextureNormalName); - } - else if (TextureNormal && !TextureNormal->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureNormalName = TextureNormal->GetName(); - } - else - { - TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); - } - - // Create normal texture, if we need to create one. - if (!TextureNormal) - bCreatedNewTextureNormal = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing normal texture, or create new one. - TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureNormal, - ImageInfo, - TextureNormalPackage, - TextureNormalName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_WorldNormalMap, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureNormal->SetFlags(RF_Public | RF_Standalone); - - // Create normal sampling expression, if needed. - if (!ExpressionNormal) - ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionNormal->Desc = GeneratingParameterName; - ExpressionNormal->ParameterName = *GeneratingParameterName; - - ExpressionNormal->Texture = TextureNormal; - ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; - - // Offset node placement. - ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Set normal space. - Material->bTangentSpaceNormal = bTangentSpaceNormal; - - // Assign expression to material. - Material->Expressions.Add(ExpressionNormal); - Material->Normal.Expression = ExpressionNormal; - - // Propagate and trigger diffuse texture updates. - if (bCreatedNewTextureNormal) - FAssetRegistryModule::AssetCreated(TextureNormal); - - TextureNormal->PreEditChange(nullptr); - TextureNormal->PostEditChange(); - TextureNormal->MarkPackageDirty(); - - bExpressionCreated = true; - } - - // Cache the texture package - OutPackages.AddUnique(TextureNormalPackage); - } - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Specular texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if specular texture is available. - HAPI_ParmInfo ParmSpecularTextureInfo; - HAPI_ParmId ParmSpecularTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL, - HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED, - true, - ParmSpecularTextureId, - ParmSpecularTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_SPECULAR, - HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED, - false, - ParmSpecularTextureId, - ParmSpecularTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR); - } - else - { - // failed to find the texture - ParmSpecularTextureId = -1; - } - - if (ParmSpecularTextureId >= 0) - { - TArray ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmSpecularTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Specular.Expression); - - UTexture2D * TextureSpecular = nullptr; - if (ExpressionSpecular) - { - TextureSpecular = Cast< UTexture2D >(ExpressionSpecular->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Specular.Expression) - { - Material->Specular.Expression->ConditionalBeginDestroy(); - Material->Specular.Expression = nullptr; - } - } - - UPackage * TextureSpecularPackage = nullptr; - if (TextureSpecular) - TextureSpecularPackage = Cast< UPackage >(TextureSpecular->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureSpecularName; - bool bCreatedNewTextureSpecular = false; - - // Create specular texture package, if this is a new specular texture. - if (!TextureSpecularPackage) - { - TextureSpecularPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, - InPackageParams, - TextureSpecularName); - } - else if (TextureSpecular && !TextureSpecular->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureSpecularName = TextureSpecular->GetName(); - } - else - { - TextureSpecularName = FPaths::GetBaseFilename(TextureSpecularPackage->GetName(), true); - } - - // Create specular texture, if we need to create one. - if (!TextureSpecular) - bCreatedNewTextureSpecular = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing specular texture, or create new one. - TextureSpecular = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureSpecular, - ImageInfo, - TextureSpecularPackage, - TextureSpecularName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureSpecular->SetFlags(RF_Public | RF_Standalone); - - // Create specular sampling expression, if needed. - if (!ExpressionSpecular) - { - ExpressionSpecular = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionSpecular->Desc = GeneratingParameterName; - ExpressionSpecular->ParameterName = *GeneratingParameterName; - - ExpressionSpecular->Texture = TextureSpecular; - ExpressionSpecular->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionSpecular->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionSpecular->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionSpecular); - Material->Specular.Expression = ExpressionSpecular; - - bExpressionCreated = true; - - // Propagate and trigger specular texture updates. - if (bCreatedNewTextureSpecular) - FAssetRegistryModule::AssetCreated(TextureSpecular); - - TextureSpecular->PreEditChange(nullptr); - TextureSpecular->PostEditChange(); - TextureSpecular->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureSpecularPackage); - } - } - - // See if we have a specular color - HAPI_ParmInfo ParmSpecularColorInfo; - HAPI_ParmId ParmSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL, ParmSpecularColorInfo); - - if (ParmSpecularColorId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL); - } - else - { - ParmSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR, ParmSpecularColorInfo); - - if (ParmSpecularColorId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR); - } - - if (!bExpressionCreated && ParmSpecularColorId >= 0) - { - // Specular color is available. - FLinearColor Color = FLinearColor::White; - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmSpecularColorInfo.floatValuesIndex, ParmSpecularColorInfo.size) == HAPI_RESULT_SUCCESS) - { - if (ParmSpecularColorInfo.size == 3) - Color.A = 1.0f; - - UMaterialExpressionVectorParameter * ExpressionSpecularColor = - Cast< UMaterialExpressionVectorParameter >(Material->Specular.Expression); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionSpecularColor) - { - // Otherwise new expression is of a different type. - if (Material->Specular.Expression) - { - Material->Specular.Expression->ConditionalBeginDestroy(); - Material->Specular.Expression = nullptr; - } - - ExpressionSpecularColor = NewObject< UMaterialExpressionVectorParameter >( - Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionSpecularColor->Desc = GeneratingParameterName; - ExpressionSpecularColor->ParameterName = *GeneratingParameterName; - - ExpressionSpecularColor->DefaultValue = Color; - - // Offset node placement. - ExpressionSpecularColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionSpecularColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionSpecularColor); - Material->Specular.Expression = ExpressionSpecularColor; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Roughness texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if roughness texture is available. - HAPI_ParmInfo ParmRoughnessTextureInfo; - HAPI_ParmId ParmRoughnessTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL, - HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED, - true, - ParmRoughnessTextureId, - ParmRoughnessTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_ROUGHNESS, - HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED, - false, - ParmRoughnessTextureId, - ParmRoughnessTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS); - } - else - { - // failed to find the texture - ParmRoughnessTextureId = -1; - } - - if (ParmRoughnessTextureId >= 0) - { - TArray ImageBuffer; - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmRoughnessTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer ) ) - { - UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Roughness.Expression); - - UTexture2D* TextureRoughness = nullptr; - if (ExpressionRoughness) - { - TextureRoughness = Cast< UTexture2D >(ExpressionRoughness->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Roughness.Expression) - { - Material->Roughness.Expression->ConditionalBeginDestroy(); - Material->Roughness.Expression = nullptr; - } - } - - UPackage * TextureRoughnessPackage = nullptr; - if (TextureRoughness) - TextureRoughnessPackage = Cast< UPackage >(TextureRoughness->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureRoughnessName; - bool bCreatedNewTextureRoughness = false; - - // Create roughness texture package, if this is a new roughness texture. - if (!TextureRoughnessPackage) - { - TextureRoughnessPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, - InPackageParams, - TextureRoughnessName); - } - else if (TextureRoughness && !TextureRoughness->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureRoughnessName = TextureRoughness->GetName(); - } - else - { - TextureRoughnessName = FPaths::GetBaseFilename(TextureRoughnessPackage->GetName(), true); - } - - // Create roughness texture, if we need to create one. - if (!TextureRoughness) - bCreatedNewTextureRoughness = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing roughness texture, or create new one. - TextureRoughness = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureRoughness, - ImageInfo, - TextureRoughnessPackage, - TextureRoughnessName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureRoughness->SetFlags(RF_Public | RF_Standalone); - - // Create roughness sampling expression, if needed. - if (!ExpressionRoughness) - ExpressionRoughness = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionRoughness->Desc = GeneratingParameterName; - ExpressionRoughness->ParameterName = *GeneratingParameterName; - - ExpressionRoughness->Texture = TextureRoughness; - ExpressionRoughness->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionRoughness->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionRoughness->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionRoughness); - Material->Roughness.Expression = ExpressionRoughness; - - bExpressionCreated = true; - - // Propagate and trigger roughness texture updates. - if (bCreatedNewTextureRoughness) - FAssetRegistryModule::AssetCreated(TextureRoughness); - - TextureRoughness->PreEditChange(nullptr); - TextureRoughness->PostEditChange(); - TextureRoughness->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureRoughnessPackage); - } - } - - // See if we have a roughness value - HAPI_ParmInfo ParmRoughnessValueInfo; - HAPI_ParmId ParmRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL, ParmRoughnessValueInfo); - - if (ParmRoughnessValueId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL); - } - else - { - ParmRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS, ParmRoughnessValueInfo); - - if (ParmRoughnessValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS); - } - - if (!bExpressionCreated && ParmRoughnessValueId >= 0) - { - // Roughness value is available. - - float RoughnessValue = 0.0f; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&RoughnessValue, - ParmRoughnessValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - UMaterialExpressionScalarParameter * ExpressionRoughnessValue = - Cast< UMaterialExpressionScalarParameter >(Material->Roughness.Expression); - - // Clamp retrieved value. - RoughnessValue = FMath::Clamp< float >(RoughnessValue, 0.0f, 1.0f); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionRoughnessValue) - { - // Otherwise new expression is of a different type. - if (Material->Roughness.Expression) - { - Material->Roughness.Expression->ConditionalBeginDestroy(); - Material->Roughness.Expression = nullptr; - } - - ExpressionRoughnessValue = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionRoughnessValue->Desc = GeneratingParameterName; - ExpressionRoughnessValue->ParameterName = *GeneratingParameterName; - - ExpressionRoughnessValue->DefaultValue = RoughnessValue; - ExpressionRoughnessValue->SliderMin = 0.0f; - ExpressionRoughnessValue->SliderMax = 1.0f; - - // Offset node placement. - ExpressionRoughnessValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionRoughnessValue->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionRoughnessValue); - Material->Roughness.Expression = ExpressionRoughnessValue; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Metallic texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if metallic texture is available. - HAPI_ParmInfo ParmMetallicTextureInfo; - HAPI_ParmId ParmMetallicTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_METALLIC_OGL, - HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED, - true, - ParmMetallicTextureId, - ParmMetallicTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_METALLIC, - HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED, - false, - ParmMetallicTextureId, - ParmMetallicTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC); - } - else - { - // failed to find the texture - ParmMetallicTextureId = -1; - } - - if (ParmMetallicTextureId >= 0) - { - TArray ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmMetallicTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Metallic.Expression); - - UTexture2D * TextureMetallic = nullptr; - if (ExpressionMetallic) - { - TextureMetallic = Cast(ExpressionMetallic->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Metallic.Expression) - { - Material->Metallic.Expression->ConditionalBeginDestroy(); - Material->Metallic.Expression = nullptr; - } - } - - UPackage * TextureMetallicPackage = nullptr; - if (TextureMetallic) - TextureMetallicPackage = Cast< UPackage >(TextureMetallic->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureMetallicName; - bool bCreatedNewTextureMetallic = false; - - // Create metallic texture package, if this is a new metallic texture. - if (!TextureMetallicPackage) - { - TextureMetallicPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, - InPackageParams, - TextureMetallicName); - } - else if (TextureMetallic && !TextureMetallic->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureMetallicName = TextureMetallic->GetName(); - } - else - { - TextureMetallicName = FPaths::GetBaseFilename(TextureMetallicPackage->GetName(), true); - } - - // Create metallic texture, if we need to create one. - if (!TextureMetallic) - bCreatedNewTextureMetallic = true; - - // Get the node path to add it to the meta data - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing metallic texture, or create new one. - TextureMetallic = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureMetallic, - ImageInfo, - TextureMetallicPackage, - TextureMetallicName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureMetallic->SetFlags(RF_Public | RF_Standalone); - - // Create metallic sampling expression, if needed. - if (!ExpressionMetallic) - ExpressionMetallic = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionMetallic->Desc = GeneratingParameterName; - ExpressionMetallic->ParameterName = *GeneratingParameterName; - - ExpressionMetallic->Texture = TextureMetallic; - ExpressionMetallic->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionMetallic->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMetallic->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionMetallic); - Material->Metallic.Expression = ExpressionMetallic; - - bExpressionCreated = true; - - // Propagate and trigger metallic texture updates. - if (bCreatedNewTextureMetallic) - FAssetRegistryModule::AssetCreated(TextureMetallic); - - TextureMetallic->PreEditChange(nullptr); - TextureMetallic->PostEditChange(); - TextureMetallic->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureMetallicPackage); - } - } - - // Get the metallic value - HAPI_ParmInfo ParmMetallicValueInfo; - HAPI_ParmId ParmMetallicValueId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL, ParmMetallicValueInfo); - - if (ParmMetallicValueId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL); - } - else - { - ParmMetallicValueId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmMetallicValueInfo); - - if (ParmMetallicValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); - } - - if (!bExpressionCreated && ParmMetallicValueId >= 0) - { - // Metallic value is available. - float MetallicValue = 0.0f; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&MetallicValue, - ParmMetallicTextureInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - UMaterialExpressionScalarParameter * ExpressionMetallicValue = - Cast< UMaterialExpressionScalarParameter >(Material->Metallic.Expression); - - // Clamp retrieved value. - MetallicValue = FMath::Clamp< float >(MetallicValue, 0.0f, 1.0f); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionMetallicValue) - { - // Otherwise new expression is of a different type. - if (Material->Metallic.Expression) - { - Material->Metallic.Expression->ConditionalBeginDestroy(); - Material->Metallic.Expression = nullptr; - } - - ExpressionMetallicValue = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionMetallicValue->Desc = GeneratingParameterName; - ExpressionMetallicValue->ParameterName = *GeneratingParameterName; - - ExpressionMetallicValue->DefaultValue = MetallicValue; - ExpressionMetallicValue->SliderMin = 0.0f; - ExpressionMetallicValue->SliderMax = 1.0f; - - // Offset node placement. - ExpressionMetallicValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMetallicValue->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionMetallicValue); - Material->Metallic.Expression = ExpressionMetallicValue; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Emissive texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if emissive texture is available. - HAPI_ParmInfo ParmEmissiveTextureInfo; - HAPI_ParmId ParmEmissiveTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL, - HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED, - true, - ParmEmissiveTextureId, - ParmEmissiveTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_EMISSIVE, - HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED, - false, - ParmEmissiveTextureId, - ParmEmissiveTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE); - } - else - { - // failed to find the texture - ParmEmissiveTextureId = -1; - } - - if (ParmEmissiveTextureId >= 0) - { - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmEmissiveTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->EmissiveColor.Expression); - - UTexture2D * TextureEmissive = nullptr; - if (ExpressionEmissive) - { - TextureEmissive = Cast< UTexture2D >(ExpressionEmissive->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->EmissiveColor.Expression) - { - Material->EmissiveColor.Expression->ConditionalBeginDestroy(); - Material->EmissiveColor.Expression = nullptr; - } - } - - UPackage * TextureEmissivePackage = nullptr; - if (TextureEmissive) - TextureEmissivePackage = Cast< UPackage >(TextureEmissive->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureEmissiveName; - bool bCreatedNewTextureEmissive = false; - - // Create emissive texture package, if this is a new emissive texture. - if (!TextureEmissivePackage) - { - TextureEmissivePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, - InPackageParams, - TextureEmissiveName); - } - else if (TextureEmissive && !TextureEmissive->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureEmissiveName = TextureEmissive->GetName(); - } - else - { - TextureEmissiveName = FPaths::GetBaseFilename(TextureEmissivePackage->GetName(), true); - } - - // Create emissive texture, if we need to create one. - if (!TextureEmissive) - bCreatedNewTextureEmissive = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing emissive texture, or create new one. - TextureEmissive = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureEmissive, - ImageInfo, - TextureEmissivePackage, - TextureEmissiveName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureEmissive->SetFlags(RF_Public | RF_Standalone); - - // Create emissive sampling expression, if needed. - if (!ExpressionEmissive) - ExpressionEmissive = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionEmissive->Desc = GeneratingParameterName; - ExpressionEmissive->ParameterName = *GeneratingParameterName; - - ExpressionEmissive->Texture = TextureEmissive; - ExpressionEmissive->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionEmissive->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionEmissive->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionEmissive); - Material->EmissiveColor.Expression = ExpressionEmissive; - - bExpressionCreated = true; - - // Propagate and trigger metallic texture updates. - if (bCreatedNewTextureEmissive) - FAssetRegistryModule::AssetCreated(TextureEmissive); - - TextureEmissive->PreEditChange(nullptr); - TextureEmissive->PostEditChange(); - TextureEmissive->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureEmissivePackage); - } - } - - HAPI_ParmInfo ParmEmissiveValueInfo; - HAPI_ParmId ParmEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL, ParmEmissiveValueInfo); - - if (ParmEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL); - else - { - ParmEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE, ParmEmissiveValueInfo); - - if (ParmEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE); - } - - if (!bExpressionCreated && ParmEmissiveValueId >= 0) - { - // Emissive color is available. - - FLinearColor Color = FLinearColor::White; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmEmissiveValueInfo.floatValuesIndex, ParmEmissiveValueInfo.size) == HAPI_RESULT_SUCCESS) - { - if (ParmEmissiveValueInfo.size == 3) - Color.A = 1.0f; - - UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = - Cast< UMaterialExpressionConstant4Vector >(Material->EmissiveColor.Expression); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionEmissiveColor) - { - // Otherwise new expression is of a different type. - if (Material->EmissiveColor.Expression) - { - Material->EmissiveColor.Expression->ConditionalBeginDestroy(); - Material->EmissiveColor.Expression = nullptr; - } - - ExpressionEmissiveColor = NewObject< UMaterialExpressionConstant4Vector >( - Material, UMaterialExpressionConstant4Vector::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionEmissiveColor->Desc = GeneratingParameterName; - if (ExpressionEmissiveColor->CanRenameNode()) - ExpressionEmissiveColor->SetEditableName(*GeneratingParameterName); - - ExpressionEmissiveColor->Constant = Color; - - // Offset node placement. - ExpressionEmissiveColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionEmissiveColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionEmissiveColor); - Material->EmissiveColor.Expression = ExpressionEmissiveColor; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - - -bool -FHoudiniMaterialTranslator::UpdateMaterialInstanceParameter( - FHoudiniGenericAttribute MaterialParameter, - UMaterialInstanceConstant* MaterialInstance, - const TArray& InPackages) -{ - bool bParameterUpdated = false; - -#if WITH_EDITOR - if (!MaterialInstance) - return false; - - if (MaterialParameter.AttributeName.IsEmpty()) - return false; - - // The default material instance parameters needs to be handled manually as they cant be changed via generic SetParameters functions - if (MaterialParameter.AttributeName.Compare("CastShadowAsMasked", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideCastShadowAsMasked() && (MaterialInstance->GetCastShadowAsMasked() == Value)) - return false; - - MaterialInstance->SetOverrideCastShadowAsMasked(true); - MaterialInstance->SetCastShadowAsMasked(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("EmissiveBoost", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideEmissiveBoost() && (MaterialInstance->GetEmissiveBoost() == Value)) - return false; - - MaterialInstance->SetOverrideEmissiveBoost(true); - MaterialInstance->SetEmissiveBoost(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("DiffuseBoost", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideDiffuseBoost() && (MaterialInstance->GetDiffuseBoost() == Value)) - return false; - - MaterialInstance->SetOverrideDiffuseBoost(true); - MaterialInstance->SetDiffuseBoost(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("ExportResolutionScale", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideExportResolutionScale() && (MaterialInstance->GetExportResolutionScale() == Value)) - return false; - - MaterialInstance->SetOverrideExportResolutionScale(true); - MaterialInstance->SetExportResolutionScale(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("OpacityMaskClipValue", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue && (MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue = true; - MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("BlendMode", ESearchCase::IgnoreCase) == 0) - { - EBlendMode EnumValue = (EBlendMode)MaterialParameter.GetIntValue(); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - FString StringValue = MaterialParameter.GetStringValue(); - if (StringValue.Compare("Opaque", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Opaque; - else if (StringValue.Compare("Masked", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Masked; - else if (StringValue.Compare("Translucent", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Translucent; - else if (StringValue.Compare("Additive", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Additive; - else if (StringValue.Compare("Modulate", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Modulate; - else if (StringValue.StartsWith("Alpha", ESearchCase::IgnoreCase)) - EnumValue = EBlendMode::BLEND_AlphaComposite; - } - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_BlendMode && (MaterialInstance->BasePropertyOverrides.BlendMode == EnumValue)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = true; - MaterialInstance->BasePropertyOverrides.BlendMode = EnumValue; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("ShadingModel", ESearchCase::IgnoreCase) == 0) - { - EMaterialShadingModel EnumValue = (EMaterialShadingModel)MaterialParameter.GetIntValue(); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - FString StringValue = MaterialParameter.GetStringValue(); - if (StringValue.Compare("Unlit", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Unlit; - else if (StringValue.StartsWith("Default", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_DefaultLit; - else if (StringValue.Compare("Subsurface", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Subsurface; - else if (StringValue.StartsWith("Preintegrated", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_PreintegratedSkin; - else if (StringValue.StartsWith("Clear", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_ClearCoat; - else if (StringValue.Compare("SubsurfaceProfile", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_SubsurfaceProfile; - else if (StringValue.Compare("TwoSidedFoliage", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_TwoSidedFoliage; - else if (StringValue.Compare("Hair", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Hair; - else if (StringValue.Compare("Cloth", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Cloth; - else if (StringValue.Compare("Eye", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Eye; - } - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel && (MaterialInstance->BasePropertyOverrides.ShadingModel == EnumValue)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel = true; - MaterialInstance->BasePropertyOverrides.ShadingModel = EnumValue; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("TwoSided", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_TwoSided && (MaterialInstance->BasePropertyOverrides.TwoSided == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_TwoSided = true; - MaterialInstance->BasePropertyOverrides.TwoSided = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("DitheredLODTransition", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition && (MaterialInstance->BasePropertyOverrides.DitheredLODTransition == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition = true; - MaterialInstance->BasePropertyOverrides.DitheredLODTransition = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("PhysMaterial", ESearchCase::IgnoreCase) == 0) - { - // Try to load a Material corresponding to the parameter value - FString ParamValue = MaterialParameter.GetStringValue(); - UPhysicalMaterial* FoundPhysMaterial = Cast< UPhysicalMaterial >( - StaticLoadObject(UPhysicalMaterial::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); - - // Update the parameter value if necessary - if (!FoundPhysMaterial || (MaterialInstance->PhysMaterial == FoundPhysMaterial)) - return false; - - MaterialInstance->PhysMaterial = FoundPhysMaterial; - bParameterUpdated = true; - } - - if (bParameterUpdated) - return true; - - // Handling custom parameters - FName CurrentMatParamName = FName(*MaterialParameter.AttributeName); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - // String attributes are used for textures parameters - // We need to find the texture corresponding to the param - UTexture* FoundTexture = nullptr; - FString ParamValue = MaterialParameter.GetStringValue(); - - // Texture can either be already existing texture assets in UE4, or a newly generated textures by this asset - // Try to find the texture corresponding to the param value in the existing assets first. - FoundTexture = Cast( - StaticLoadObject(UTexture::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); - - if (!FoundTexture) - { - // We couldn't find a texture corresponding to the parameter in the existing UE4 assets - // Try to find the corresponding texture in the cooked temporary package we just generated - FoundTexture = FHoudiniMaterialTranslator::FindGeneratedTexture(ParamValue, InPackages); - } - - // Do not update if unnecessary - if (FoundTexture) - { - // Do not update if unnecessary - UTexture* OldTexture = nullptr; - bool FoundOldParam = MaterialInstance->GetTextureParameterValue(CurrentMatParamName, OldTexture); - if (FoundOldParam && (OldTexture == FoundTexture)) - return false; - - MaterialInstance->SetTextureParameterValueEditorOnly(CurrentMatParamName, FoundTexture); - bParameterUpdated = true; - } - } - else if (MaterialParameter.AttributeTupleSize == 1) - { - // Single attributes are either for scalar parameters or static switches - float OldValue; - bool FoundOldScalarParam = MaterialInstance->GetScalarParameterValue(CurrentMatParamName, OldValue); - if (FoundOldScalarParam) - { - // The material parameter is a scalar - float NewValue = (float)MaterialParameter.GetDoubleValue(); - - // Do not update if unnecessary - if (OldValue == NewValue) - return false; - - MaterialInstance->SetScalarParameterValueEditorOnly(CurrentMatParamName, NewValue); - bParameterUpdated = true; - } - else - { - // See if the underlying parameter is a static switch - bool NewBoolValue = MaterialParameter.GetBoolValue(); - - // We need to iterate over the material's static parameter set - FStaticParameterSet StaticParameters; - MaterialInstance->GetStaticParameterValues(StaticParameters); - - for (int32 SwitchParameterIdx = 0; SwitchParameterIdx < StaticParameters.StaticSwitchParameters.Num(); ++SwitchParameterIdx) - { - FStaticSwitchParameter& SwitchParameter = StaticParameters.StaticSwitchParameters[SwitchParameterIdx]; - if (SwitchParameter.ParameterInfo.Name != CurrentMatParamName) - continue; - - if (SwitchParameter.Value == NewBoolValue) - return false; - - SwitchParameter.Value = NewBoolValue; - SwitchParameter.bOverride = true; - - MaterialInstance->UpdateStaticPermutation(StaticParameters); - bParameterUpdated = true; - break; - } - } - } - else - { - // Tuple attributes are for vector parameters - FLinearColor NewLinearColor; - // if the attribute is stored in an int, we'll have to convert a color to a linear color - if (MaterialParameter.AttributeType == EAttribStorageType::INT || MaterialParameter.AttributeType == EAttribStorageType::INT64) - { - FColor IntColor; - IntColor.R = (int8)MaterialParameter.GetIntValue(0); - IntColor.G = (int8)MaterialParameter.GetIntValue(1); - IntColor.B = (int8)MaterialParameter.GetIntValue(2); - if (MaterialParameter.AttributeTupleSize >= 4) - IntColor.A = (int8)MaterialParameter.GetIntValue(3); - else - IntColor.A = 1; - - NewLinearColor = FLinearColor(IntColor); - } - else - { - NewLinearColor.R = (float)MaterialParameter.GetDoubleValue(0); - NewLinearColor.G = (float)MaterialParameter.GetDoubleValue(1); - NewLinearColor.B = (float)MaterialParameter.GetDoubleValue(2); - if (MaterialParameter.AttributeTupleSize >= 4) - NewLinearColor.A = (float)MaterialParameter.GetDoubleValue(3); - } - - // Do not update if unnecessary - FLinearColor OldValue; - bool FoundOldParam = MaterialInstance->GetVectorParameterValue(CurrentMatParamName, OldValue); - if (FoundOldParam && (OldValue == NewLinearColor)) - return false; - - MaterialInstance->SetVectorParameterValueEditorOnly(CurrentMatParamName, NewLinearColor); - bParameterUpdated = true; - } -#endif - - return bParameterUpdated; -} - - -UTexture* -FHoudiniMaterialTranslator::FindGeneratedTexture(const FString& TextureString, const TArray& InPackages) -{ - if (TextureString.IsEmpty()) - return nullptr; - - // Try to find the corresponding texture in the cooked temporary package generated by an HDA -UTexture* FoundTexture = nullptr; -for (const auto& CurrentPackage : InPackages) -{ - // Iterate through the cooked packages - if (!CurrentPackage || CurrentPackage->IsPendingKill()) - continue; - - // First, check if the package contains a texture - FString CurrentPackageName = CurrentPackage->GetName(); - UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); - if (!PackageTexture) - continue; - - // Then check if the package's metadata match what we're looking for - // Make sure this texture was generated by Houdini Engine - UMetaData* MetaData = CurrentPackage->GetMetaData(); - if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - continue; - - // Get the texture type from the meta data - // Texture type store has meta data will be C_A, N, S, R etc.. - const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); - if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // Convert the texture type to a "friendly" version - // C_A to diffuse, N to Normal, S to Specular etc... - FString TextureTypeFriendlyString = TextureTypeString; - FString TextureTypeFriendlyAlternateString = TEXT(""); - if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) - { - TextureTypeFriendlyString = TEXT("diffuse"); - TextureTypeFriendlyAlternateString = TEXT("basecolor"); - } - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("normal"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("emissive"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("specular"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("roughness"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("metallic"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("opacity"); - - // See if we have a match between the texture string and the friendly name - if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) - { - FoundTexture = PackageTexture; - break; - } - - // Get the node path from the meta data - const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); - if (NodePath.IsEmpty()) - continue; - - // See if we have a match with the path and texture type - FString PathAndType = NodePath + TEXT("/") + TextureTypeString; - if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // See if we have a match with the friendly path and texture type - FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; - if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // Try the alternate friendly string - if (!TextureTypeFriendlyAlternateString.IsEmpty()) - { - PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; - if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - } -} - -return FoundTexture; -} - - -bool -FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - const HAPI_NodeId& InNodeId, - const std::string& InTextureParmName, - const std::string& InUseTextureParmName, - const bool& bFindByTag, - HAPI_ParmId& OutParmId, - HAPI_ParmInfo& OutParmInfo) -{ - OutParmId = -1; - - if(bFindByTag) - OutParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InTextureParmName, OutParmInfo); - else - OutParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InTextureParmName, OutParmInfo); - - if (OutParmId < 0) - { - // Failed to find the texture - return false; - } - - // We found a valid parameter, check if the matching "use" parameter exists - HAPI_ParmInfo FoundUseParmInfo; - HAPI_ParmId FoundUseParmId = -1; - if(bFindByTag) - FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InUseTextureParmName, FoundUseParmInfo); - else - FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InUseTextureParmName, FoundUseParmInfo); - - if (FoundUseParmId >= 0) - { - // We found a valid "use" parameter, check if it is disabled - // Get the param value - int32 UseValue = 0; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &UseValue, FoundUseParmInfo.intValuesIndex, 1)) - { - if (UseValue == 0) - { - // We found the texture parm, but the "use" param/tag is disabled, so don't use it! - // We still return true as we found the parameter, this will prevent looking for other parms - OutParmId = -1; - return true; - } - } - } - - // Finally, make sure that the found texture Parm is not empty! - FString ParmValue = FString(); - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandle, OutParmInfo.stringValuesIndex, 1)) - { - // Convert the string handle to FString - FHoudiniEngineString::ToFString(StringHandle, ParmValue); - } - - if (ParmValue.IsEmpty()) - { - // We found the parm, but it's empty, don't use it! - // We still return true as we found the parameter, this will prevent looking for other parms - OutParmId = -1; - return true; - } - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniMaterialTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPackageParams.h" + +#include "Materials/Material.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceConstant.h" +#include "PhysicalMaterials/PhysicalMaterial.h" + +#include "Materials/MaterialExpressionTextureSample.h" +#include "Materials/MaterialExpressionTextureCoordinate.h" +#include "Materials/MaterialExpressionConstant4Vector.h" +#include "Materials/MaterialExpressionConstant.h" +#include "Materials/MaterialExpressionMultiply.h" +#include "Materials/MaterialExpressionVertexColor.h" +#include "Materials/MaterialExpressionTextureSampleParameter2D.h" +#include "Materials/MaterialExpressionVectorParameter.h" +#include "Materials/MaterialExpressionScalarParameter.h" +#include "ImageUtils.h" +#include "PackageTools.h" +#include "AssetRegistryModule.h" +#include "UObject/MetaData.h" + +#if WITH_EDITOR + #include "Factories/MaterialFactoryNew.h" + #include "Factories/MaterialInstanceConstantFactoryNew.h" +#endif + +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeX = -400; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeY = -150; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepX = 220; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepY = 220; + +bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( + const HAPI_NodeId& InAssetId, + const FHoudiniPackageParams& InPackageParams, + const TArray& InUniqueMaterialIds, + const TArray& InUniqueMaterialInfos, + const TMap& InMaterials, + TMap& OutMaterials, + TArray& OutPackages, + const bool& bForceRecookAll, + bool bInTreatExistingMaterialsAsUpToDate) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMaterialTranslator::CreateHoudiniMaterials")); + + if (InUniqueMaterialIds.Num() <= 0) + return false; + + if (InUniqueMaterialInfos.Num() != InUniqueMaterialIds.Num()) + return false; + + // Empty returned materials. + OutMaterials.Empty(); + + // Update context for generated materials (will trigger when object goes out of scope). + FMaterialUpdateContext MaterialUpdateContext; + + // Default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + OutMaterials.Add(HAPI_UNREAL_DEFAULT_MATERIAL_NAME, DefaultMaterial); + + // Factory to create materials. + UMaterialFactoryNew * MaterialFactory = NewObject(); + MaterialFactory->AddToRoot(); + + for (int32 MaterialIdx = 0; MaterialIdx < InUniqueMaterialIds.Num(); MaterialIdx++) + { + HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; + + HAPI_MaterialInfo MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; + if (!MaterialInfo.exists) + { + // The material does not exist, + // we will use the default Houdini material in this case. + continue; + } + + // Get the material node's node information. + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &NodeInfo)) + { + continue; + } + + FString MaterialName = TEXT(""); + if (!FHoudiniEngineString::ToFString(NodeInfo.nameSH, MaterialName)) + { + // shouldnt happen, give a generic name + HOUDINI_LOG_WARNING(TEXT("Failed to retrieve material name!")); + MaterialName = TEXT("Material_") + FString::FromInt(MaterialInfo.nodeId); + } + + FString MaterialPathName = TEXT(""); + if (!FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, MaterialInfo, MaterialPathName)) + continue; + + // Check first in the existing material map + UMaterial * Material = nullptr; + UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); + if (FoundMaterial) + { + Material = Cast(*FoundMaterial); + } + + bool bCreatedNewMaterial = false; + if (Material && !Material->IsPendingKill()) + { + // If cached material exists and has not changed, we can reuse it. + if ((bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll) + { + // We found cached material, we can reuse it. + OutMaterials.Add(MaterialPathName, Material); + continue; + } + } + else + { + // Previous Material was not found, we need to create a new one. + EObjectFlags ObjFlags = RF_Public | RF_Standalone; + + // Create material package and get material name. + FString MaterialPackageName; + UPackage * MaterialPackage = FHoudiniMaterialTranslator::CreatePackageForMaterial( + MaterialInfo.nodeId, MaterialName, InPackageParams, MaterialPackageName); + + Material = (UMaterial *)MaterialFactory->FactoryCreateNew( + UMaterial::StaticClass(), MaterialPackage, *MaterialPackageName, ObjFlags, NULL, GWarn); + + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialName); + + bCreatedNewMaterial = true; + } + + if (!Material || Material->IsPendingKill()) + continue; + + // Get the asset name from the package params + FString AssetName = InPackageParams.HoudiniAssetName.IsEmpty() ? TEXT("HoudiniAsset") : InPackageParams.HoudiniAssetName; + + // Get the package and add it to our list + UPackage* Package = Material->GetOutermost(); + OutPackages.AddUnique(Package); + + /* + // TODO: This should be handled in the mesh/instance translator + // If this is an instancer material, enable the instancing flag. + if (UniqueInstancerMaterialIds.Contains(MaterialId)) + Material->bUsedWithInstancedStaticMeshes = true; + */ + + // Reset material expressions. + Material->Expressions.Empty(); + + // Generate various components for this material. + bool bMaterialComponentCreated = false; + int32 MaterialNodeY = FHoudiniMaterialTranslator::MaterialExpressionNodeY; + + // By default we mark material as opaque. Some of component creators can change this. + Material->BlendMode = BLEND_Opaque; + + // Extract diffuse plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract opacity plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract opacity mask plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract normal plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentNormal( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract specular plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract roughness plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract metallic plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract emissive plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Set other material properties. + Material->TwoSided = true; + Material->SetShadingModel(MSM_DefaultLit); + + // Schedule this material for update. + MaterialUpdateContext.AddMaterial(Material); + + // Cache material. + OutMaterials.Add(MaterialPathName, Material); + + // Propagate and trigger material updates. + if (bCreatedNewMaterial) + FAssetRegistryModule::AssetCreated(Material); + + Material->PreEditChange(nullptr); + Material->PostEditChange(); + Material->MarkPackageDirty(); + } + + MaterialFactory->RemoveFromRoot(); + + return true; +} + +// +bool +FHoudiniMaterialTranslator::CreateMaterialInstances( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& UniqueMaterialInstanceOverrides, + const TArray& InPackages, + const TMap& InMaterials, + TMap& OutMaterials, + const bool& bForceRecookAll) +{ + // Check the node ID is valid + if (InHGPO.AssetId < 0) + return false; + + // No material instance attributes + if (UniqueMaterialInstanceOverrides.Num() <= 0) + return false; + + // TODO: Improve! + // Get the material name from the material_instance attribute + // Since the material instance attribute can be set per primitive, it's going to be very difficult to know + // exactly where to look for the nth material instance. In order for the material slot to be created, + // we used the fact that the material instance attribute had to be different + // This is pretty hacky and we should probably require an extra material_instance_index attribute instead. + // as we can only create one instance of the same material, and cant get two slots for the same "source" material. + int32 MaterialIndex = 0; + for (TMap::TConstIterator Iter(UniqueMaterialInstanceOverrides); Iter; ++Iter) + { + FString CurrentSourceMaterial = Iter->Key; + if (CurrentSourceMaterial.IsEmpty()) + continue; + + // Try to find the material we want to create an instance of + UMaterialInterface* CurrentSourceMaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentSourceMaterial, nullptr, LOAD_NoWarn, nullptr)); + + if (!CurrentSourceMaterialInterface || CurrentSourceMaterialInterface->IsPendingKill()) + { + // Couldn't find the source material + HOUDINI_LOG_WARNING(TEXT("Couldn't find the source material %s to create a material instance."), *CurrentSourceMaterial); + continue; + } + + // Create/Retrieve the package for the MI + FString MaterialInstanceName; + FString MaterialInstanceNamePrefix = UPackageTools::SanitizePackageName( + CurrentSourceMaterialInterface->GetName() + TEXT("_instance_") + FString::FromInt(MaterialIndex)); + + // Increase the material index + MaterialIndex++; + + // See if we can find an existing package for that instance + UPackage * MaterialInstancePackage = nullptr; + UMaterialInterface * const * FoundMatPtr = InMaterials.Find(MaterialInstanceNamePrefix); + if (FoundMatPtr && *FoundMatPtr) + { + // We found an already existing MI, get its package + MaterialInstancePackage = Cast((*FoundMatPtr)->GetOuter()); + } + + if (MaterialInstancePackage) + { + MaterialInstanceName = MaterialInstancePackage->GetName(); + } + else + { + // We couldnt find the corresponding M_I package, so create a new one + MaterialInstancePackage = CreatePackageForMaterial(InHGPO.AssetId, MaterialInstanceNamePrefix, InPackageParams, MaterialInstanceName); + } + + // Couldn't create a package for that Material Instance + if (!MaterialInstancePackage) + continue; + + bool bNewMaterialCreated = false; + UMaterialInstanceConstant* NewMaterialInstance = LoadObject(MaterialInstancePackage, *MaterialInstanceName, nullptr, LOAD_None, nullptr); + if (!NewMaterialInstance) + { + // Factory to create materials. + UMaterialInstanceConstantFactoryNew* MaterialInstanceFactory = NewObject< UMaterialInstanceConstantFactoryNew >(); + if (!MaterialInstanceFactory) + continue; + + // Create the new material instance + MaterialInstanceFactory->AddToRoot(); + MaterialInstanceFactory->InitialParent = CurrentSourceMaterialInterface; + NewMaterialInstance = (UMaterialInstanceConstant*)MaterialInstanceFactory->FactoryCreateNew( + UMaterialInstanceConstant::StaticClass(), MaterialInstancePackage, FName(*MaterialInstanceName), + RF_Public | RF_Standalone, NULL, GWarn); + + if (NewMaterialInstance) + bNewMaterialCreated = true; + + MaterialInstanceFactory->RemoveFromRoot(); + } + + if (!NewMaterialInstance) + { + HOUDINI_LOG_WARNING(TEXT("Couldn't access the material instance for %s"), *CurrentSourceMaterial); + continue; + } + + // Update context for generated materials (will trigger when the object goes out of scope). + FMaterialUpdateContext MaterialUpdateContext; + + bool bModifiedMaterialParameters = false; + // See if we need to override some of the material instance's parameters + TArray AllMatParams; + // Get the detail material parameters + int32 ParamCount = FHoudiniEngineUtils::GetGenericAttributeList( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, + AllMatParams, HAPI_ATTROWNER_DETAIL, -1); + + // Then the primitive material parameters + int32 MaterialIndexToAttributeIndex = Iter->Value; + ParamCount += FHoudiniEngineUtils::GetGenericAttributeList( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, + AllMatParams, HAPI_ATTROWNER_PRIM, MaterialIndexToAttributeIndex); + + for (int32 ParamIdx = 0; ParamIdx < AllMatParams.Num(); ParamIdx++) + { + // Try to update the material instance parameter corresponding to the attribute + if (UpdateMaterialInstanceParameter(AllMatParams[ParamIdx], NewMaterialInstance, InPackages)) + bModifiedMaterialParameters = true; + } + + // Schedule this material for update if needed. + if (bNewMaterialCreated || bModifiedMaterialParameters) + MaterialUpdateContext.AddMaterialInstance(NewMaterialInstance); + + if (bNewMaterialCreated) + { + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialInstanceName); + // Notify registry that we have created a new material. + FAssetRegistryModule::AssetCreated(NewMaterialInstance); + } + + if (bNewMaterialCreated || bModifiedMaterialParameters) + { + // Dirty the material + NewMaterialInstance->MarkPackageDirty(); + + // Update the material instance + NewMaterialInstance->InitStaticPermutation(); + NewMaterialInstance->PreEditChange(nullptr); + NewMaterialInstance->PostEditChange(); + /* + // Automatically save the package to avoid further issue + MaterialInstancePackage->SetDirtyFlag( true ); + MaterialInstancePackage->FullyLoad(); + UPackage::SavePackage( + MaterialInstancePackage, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, + *FPackageName::LongPackageNameToFilename( MaterialInstancePackage->GetName(), FPackageName::GetAssetPackageExtension() ) ); + */ + } + + // Add the created material to the output assignement map + // Use the "source" material name as we want the instance to replace it + OutMaterials.Add(CurrentSourceMaterial, NewMaterialInstance); + } + + return true; +} + +bool +FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath) +{ + HAPI_MaterialInfo MaterialInfo; + FHoudiniApi::MaterialInfo_Init(&MaterialInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), InMaterialNodeId, + &MaterialInfo), false); + + return GetMaterialRelativePath(InAssetId, MaterialInfo, OutRelativePath); +} +bool +FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialInfo, FString& OutRelativePath) +{ + if (InAssetId < 0 || !InMaterialInfo.exists) + return false; + + // We want to get the asset node path so we can remove it from the material name + FString AssetNodeName = TEXT(""); + { + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); + + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo), false); + + FHoudiniEngineString::ToFString(AssetNodeInfo.internalNodePathSH, AssetNodeName); + } + + // Get the material name from the info + FString MaterialNodeName = TEXT(""); + { + HAPI_NodeInfo MaterialNodeInfo; + FHoudiniApi::NodeInfo_Init(&MaterialNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &MaterialNodeInfo), false); + + FHoudiniEngineString::ToFString(MaterialNodeInfo.internalNodePathSH, MaterialNodeName); + } + + if (AssetNodeName.Len() > 0 && MaterialNodeName.Len() > 0) + { + // Remove AssetNodeName part from MaterialNodeName. Extra position is for separator. + OutRelativePath = MaterialNodeName.Mid(AssetNodeName.Len() + 1); + return true; + } + + return false; +} + + +UPackage* +FHoudiniMaterialTranslator::CreatePackageForMaterial( + const HAPI_NodeId& InMaterialNodeId, + const FString& InMaterialName, + const FHoudiniPackageParams& InPackageParams, + FString& OutMaterialName) +{ + FString MaterialDescriptor = TEXT("_material_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InMaterialName; + + FHoudiniPackageParams MyPackageParams = InPackageParams; + if (!MyPackageParams.ObjectName.IsEmpty()) + { + MyPackageParams.ObjectName += MaterialDescriptor; + } + else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) + { + MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + MaterialDescriptor; + } + else + { + MyPackageParams.ObjectName = MaterialDescriptor; + } + MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); + + return MyPackageParams.CreatePackageForObject(OutMaterialName); +} + + +UPackage* +FHoudiniMaterialTranslator::CreatePackageForTexture( + const HAPI_NodeId& InMaterialNodeId, + const FString& InTextureType, + const FHoudiniPackageParams& InPackageParams, + FString& OutTextureName) +{ + FString TextureInfoDescriptor = TEXT("_texture_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InTextureType; + FHoudiniPackageParams MyPackageParams = InPackageParams; + if (!MyPackageParams.ObjectName.IsEmpty()) + { + MyPackageParams.ObjectName += TextureInfoDescriptor; + } + else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) + { + MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + TextureInfoDescriptor; + } + else + { + MyPackageParams.ObjectName = TextureInfoDescriptor; + } + MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); + + return MyPackageParams.CreatePackageForObject(OutTextureName); +} + + +UTexture2D * +FHoudiniMaterialTranslator::CreateUnrealTexture( + UTexture2D* ExistingTexture, + const HAPI_ImageInfo& ImageInfo, + UPackage* Package, + const FString& TextureName, + const TArray& ImageBuffer, + const FCreateTexture2DParameters& TextureParameters, + const TextureGroup& LODGroup, + const FString& TextureType, + const FString& NodePath) +{ + if (!Package || Package->IsPendingKill()) + return nullptr; + + UTexture2D * Texture = nullptr; + if (ExistingTexture) + { + Texture = ExistingTexture; + } + else + { + // Create new texture object. + Texture = NewObject< UTexture2D >( + Package, UTexture2D::StaticClass(), *TextureName, + RF_Transactional); + + // Assign texture group. + Texture->LODGroup = LODGroup; + } + + // Add/Update meta information to package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *TextureName); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_NODE_PATH, *NodePath); + + // Initialize texture source. + Texture->Source.Init(ImageInfo.xRes, ImageInfo.yRes, 1, 1, TSF_BGRA8); + + // Lock the texture. + uint8 * MipData = Texture->Source.LockMip(0); + + // Create base map. + uint8* DestPtr = nullptr; + uint32 SrcWidth = ImageInfo.xRes; + uint32 SrcHeight = ImageInfo.yRes; + const char * SrcData = &ImageBuffer[0]; + + for (uint32 y = 0; y < SrcHeight; y++) + { + DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; + + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth * 4 + x * 4; + + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 2); // B + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 1); // G + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 0); // R + + if (TextureParameters.bUseAlpha) + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 3); // A + else + *DestPtr++ = 0xFF; + } + } + + bool bHasAlphaValue = false; + if (TextureParameters.bUseAlpha) + { + // See if there is an actual alpha value in the texture or if we can ignore the texture alpha + for (uint32 y = 0; y < SrcHeight; y++) + { + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth * 4 + x * 4; + if (*(uint8*)(SrcData + DataOffset + 3) != 0xFF) + { + bHasAlphaValue = true; + break; + } + } + + if (bHasAlphaValue) + break; + } + } + + // Unlock the texture. + Texture->Source.UnlockMip(0); + + // Texture creation parameters. + Texture->SRGB = TextureParameters.bSRGB; + Texture->CompressionSettings = TextureParameters.CompressionSettings; + Texture->CompressionNoAlpha = !bHasAlphaValue; + Texture->DeferCompression = TextureParameters.bDeferCompression; + + // Set the Source Guid/Hash if specified. + /* + if ( TextureParameters.SourceGuidHash.IsValid() ) + { + Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); + } + */ + + Texture->PostEditChange(); + + return Texture; +} + + + +bool +FHoudiniMaterialTranslator::HapiExtractImage( + const HAPI_ParmId& NodeParmId, + const HAPI_MaterialInfo& MaterialInfo, + const char * PlaneType, + const HAPI_ImageDataFormat& ImageDataFormat, + HAPI_ImagePacking ImagePacking, + bool bRenderToImage, + TArray& OutImageBuffer ) +{ + if (bRenderToImage) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, NodeParmId), false); + } + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo), false); + + ImageInfo.dataFormat = ImageDataFormat; + ImageInfo.interleaved = true; + ImageInfo.packing = ImagePacking; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo), false); + + int32 ImageBufferSize = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ExtractImageToMemory( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, HAPI_RAW_FORMAT_NAME, + PlaneType, &ImageBufferSize), false); + + if (ImageBufferSize <= 0) + return false; + + OutImageBuffer.SetNumUninitialized(ImageBufferSize); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImageMemoryBuffer( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &OutImageBuffer[0], + ImageBufferSize), false); + + return true; +} + +bool +FHoudiniMaterialTranslator::HapiGetImagePlanes( + const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes) +{ + OutImagePlanes.Empty(); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, NodeParmId), false); + + int32 ImagePlaneCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlaneCount( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImagePlaneCount), false); + + if (ImagePlaneCount <= 0) + return true; + + TArray ImagePlaneStringHandles; + ImagePlaneStringHandles.SetNumZeroed(ImagePlaneCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlanes( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImagePlaneStringHandles[0], ImagePlaneCount), false); + + FHoudiniEngineString::SHArrayToFStringArray(ImagePlaneStringHandles, OutImagePlanes); + + return true; +} + + +UMaterialExpression * +FHoudiniMaterialTranslator::MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass) +{ + if (!Expression) + return nullptr; + +#if WITH_EDITOR + if (ExpressionClass == Expression->GetClass()) + return Expression; + + // If this is a channel multiply expression, we can recurse. + UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast< UMaterialExpressionMultiply >(Expression); + if (MaterialExpressionMultiply) + { + { + UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->A.Expression; + if (MaterialExpression) + { + if (MaterialExpression->GetClass() == ExpressionClass) + return MaterialExpression; + + MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( + Cast(MaterialExpression), ExpressionClass); + + if (MaterialExpression) + return MaterialExpression; + } + } + + { + UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->B.Expression; + if (MaterialExpression) + { + if (MaterialExpression->GetClass() == ExpressionClass) + return MaterialExpression; + + MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( + Cast(MaterialExpression), ExpressionClass); + + if (MaterialExpression) + return MaterialExpression; + } + } + } +#endif + + return nullptr; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Names of generating Houdini parameters. + FString GeneratingParameterNameDiffuseTexture = TEXT(""); + FString GeneratingParameterNameUniformColor = TEXT(""); + FString GeneratingParameterNameVertexColor = TEXT(HAPI_UNREAL_ATTRIB_COLOR); + + // Diffuse texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Default; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // Attempt to look up previously created expressions. + UMaterialExpression * MaterialExpression = Material->BaseColor.Expression; + + // Locate sampling expression. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureSample = + Cast< UMaterialExpressionTextureSampleParameter2D >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // If texture sampling expression does exist, attempt to look up corresponding texture. + UTexture2D * TextureDiffuse = nullptr; + if (IsValid(ExpressionTextureSample)) + TextureDiffuse = Cast< UTexture2D >(ExpressionTextureSample->Texture); + + // Locate uniform color expression. + UMaterialExpressionVectorParameter * ExpressionConstant4Vector = + Cast< UMaterialExpressionVectorParameter >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionVectorParameter::StaticClass())); + + // If uniform color expression does not exist, create it. + if (!ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill()) + { + ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( + Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); + ExpressionConstant4Vector->DefaultValue = FLinearColor::White; + } + + // Add expression. + Material->Expressions.Add(ExpressionConstant4Vector); + + // Locate vertex color expression. + UMaterialExpressionVertexColor * ExpressionVertexColor = + Cast< UMaterialExpressionVertexColor >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionVertexColor::StaticClass())); + + // If vertex color expression does not exist, create it. + if (!ExpressionVertexColor || ExpressionVertexColor->IsPendingKill()) + { + ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( + Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag); + ExpressionVertexColor->Desc = GeneratingParameterNameVertexColor; + } + + // Add expression. + Material->Expressions.Add(ExpressionVertexColor); + + // Material should have at least one multiply expression. + UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast(MaterialExpression); + if (!MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill()) + MaterialExpressionMultiply = NewObject( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + // Add expression. + Material->Expressions.Add(MaterialExpressionMultiply); + + // See if primary multiplication has secondary multiplication as A input. + UMaterialExpressionMultiply * MaterialExpressionMultiplySecondary = nullptr; + if (MaterialExpressionMultiply->A.Expression) + MaterialExpressionMultiplySecondary = + Cast(MaterialExpressionMultiply->A.Expression); + + // See if a diffuse texture is available. + HAPI_ParmInfo ParmDiffuseTextureInfo; + HAPI_ParmId ParmDiffuseTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, + true, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via OGL tag + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, + false, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via Parm name + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); + } + else + { + // failed to find the texture + ParmDiffuseTextureId = -1; + } + + // If we have diffuse texture parameter. + if (ParmDiffuseTextureId >= 0) + { + TArray ImageBuffer; + + // Get image planes of diffuse map. + TArray DiffuseImagePlanes; + bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( + ParmDiffuseTextureId, InMaterialInfo, DiffuseImagePlanes); + + HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; + const char * PlaneType = ""; + + if (bFoundImagePlanes && DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))) + { + if (DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA))) + { + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + + // Material does use alpha. + CreateTexture2DParameters.bUseAlpha = true; + } + else + { + // We still need to have the Alpha plane, just not the CreateTexture2DParameters + // alpha option. This is because all texture data from Houdini Engine contains + // the alpha plane by default. + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + } + } + else + { + bFoundImagePlanes = false; + } + + // Retrieve color plane. + if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( + ParmDiffuseTextureId, InMaterialInfo, PlaneType, + HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) + { + UPackage * TextureDiffusePackage = nullptr; + if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + TextureDiffusePackage = Cast(TextureDiffuse->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureDiffuseName; + bool bCreatedNewTextureDiffuse = false; + + // Create diffuse texture package, if this is a new diffuse texture. + if (!TextureDiffusePackage) + { + TextureDiffusePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, + InPackageParams, + TextureDiffuseName); + } + else if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureDiffuseName = TextureDiffuse->GetName(); + } + else + { + TextureDiffuseName = FPaths::GetBaseFilename(TextureDiffusePackage->GetName(), true); + } + + // Create diffuse texture, if we need to create one. + if (!TextureDiffuse || TextureDiffuse->IsPendingKill()) + bCreatedNewTextureDiffuse = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing diffuse texture, or create new one. + TextureDiffuse = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureDiffuse, + ImageInfo, + TextureDiffusePackage, + TextureDiffuseName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureDiffuse->SetFlags(RF_Public | RF_Standalone); + + // Create diffuse sampling expression, if needed. + if (!ExpressionTextureSample) + { + ExpressionTextureSample = NewObject( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionTextureSample->Desc = GeneratingParameterNameDiffuseTexture; + ExpressionTextureSample->ParameterName = *GeneratingParameterNameDiffuseTexture; + ExpressionTextureSample->Texture = TextureDiffuse; + ExpressionTextureSample->SamplerType = SAMPLERTYPE_Color; + + // Add expression. + Material->Expressions.Add(ExpressionTextureSample); + + // Propagate and trigger diffuse texture updates. + if (bCreatedNewTextureDiffuse) + FAssetRegistryModule::AssetCreated(TextureDiffuse); + + TextureDiffuse->PreEditChange(nullptr); + TextureDiffuse->PostEditChange(); + TextureDiffuse->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureDiffusePackage); + } + } + + // See if uniform color is available. + HAPI_ParmInfo ParmDiffuseColorInfo; + HAPI_ParmId ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL, ParmDiffuseColorInfo); + + if (ParmDiffuseColorId >= 0) + { + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL); + } + else + { + ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE, ParmDiffuseColorInfo); + + if (ParmDiffuseColorId >= 0) + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE); + } + + // If we have uniform color parameter. + if (ParmDiffuseColorId >= 0) + { + FLinearColor Color = FLinearColor::White; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&Color.R, + ParmDiffuseColorInfo.floatValuesIndex, ParmDiffuseColorInfo.size) == HAPI_RESULT_SUCCESS) + { + if (ParmDiffuseColorInfo.size == 3) + Color.A = 1.0f; + + // Record generating parameter. + ExpressionConstant4Vector->Desc = GeneratingParameterNameUniformColor; + ExpressionConstant4Vector->ParameterName = *GeneratingParameterNameUniformColor; + ExpressionConstant4Vector->DefaultValue = Color; + } + } + + // If we have have texture sample expression present, we need a secondary multiplication expression. + if (ExpressionTextureSample) + { + if (!MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary = NewObject( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + // Add expression. + Material->Expressions.Add(MaterialExpressionMultiplySecondary); + } + } + else + { + // If secondary multiplication exists, but we have no sampling, we can free it. + if (MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary->A.Expression = nullptr; + MaterialExpressionMultiplySecondary->B.Expression = nullptr; + MaterialExpressionMultiplySecondary->ConditionalBeginDestroy(); + } + } + + float SecondaryExpressionScale = 1.0f; + if (MaterialExpressionMultiplySecondary) + SecondaryExpressionScale = 1.5f; + + // Create multiplication expression which has uniform color and vertex color. + MaterialExpressionMultiply->A.Expression = ExpressionConstant4Vector; + MaterialExpressionMultiply->B.Expression = ExpressionVertexColor; + + ExpressionConstant4Vector->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionConstant4Vector->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + ExpressionVertexColor->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionVertexColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiply->MaterialExpressionEditorY = + (ExpressionVertexColor->MaterialExpressionEditorY - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; + + // Hook up secondary multiplication expression to first one. + if (MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; + MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; + + if (ExpressionTextureSample) + { + ExpressionTextureSample->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; + } + + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiplySecondary->MaterialExpressionEditorY = + MaterialExpressionMultiply->MaterialExpressionEditorY + FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression. + Material->BaseColor.Expression = MaterialExpressionMultiplySecondary; + } + else + { + // Assign expression. + Material->BaseColor.Expression = MaterialExpressionMultiply; + + MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiply->MaterialExpressionEditorY = + (ExpressionVertexColor->MaterialExpressionEditorY - + ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; + } + + return true; +} + + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + // Name of generating Houdini parameters. + FString GeneratingParameterNameTexture = TEXT(""); + + UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Opacity expressions. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; + UTexture2D * TextureOpacity = nullptr; + + // Opacity texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // See if opacity texture is available. + HAPI_ParmInfo ParmOpacityTextureInfo; + HAPI_ParmId ParmOpacityTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_OPACITY_OGL, + HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED, + true, + ParmOpacityTextureId, + ParmOpacityTextureInfo)) + { + // Found via OGL tag + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_OPACITY, + HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED, + false, + ParmOpacityTextureId, + ParmOpacityTextureInfo)) + { + // Found via Parm name + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY); + } + else + { + // failed to find the texture + ParmOpacityTextureId = -1; + } + + // If we have opacity texture parameter. + if (ParmOpacityTextureId >= 0) + { + TArray< char > ImageBuffer; + + // Get image planes of opacity map. + TArray< FString > OpacityImagePlanes; + bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( + ParmOpacityTextureId, InMaterialInfo, OpacityImagePlanes); + + HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; + const char * PlaneType = ""; + + bool bColorAlphaFound = (OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA)) && OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))); + + if (bFoundImagePlanes && bColorAlphaFound) + { + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + CreateTexture2DParameters.bUseAlpha = true; + } + else + { + bFoundImagePlanes = false; + } + + if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( + ParmOpacityTextureId, InMaterialInfo, PlaneType, + HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) + { + // Locate sampling expression. + ExpressionTextureOpacitySample = Cast< UMaterialExpressionTextureSampleParameter2D >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // Locate opacity texture, if valid. + if (ExpressionTextureOpacitySample) + TextureOpacity = Cast< UTexture2D >(ExpressionTextureOpacitySample->Texture); + + UPackage * TextureOpacityPackage = nullptr; + if (TextureOpacity) + TextureOpacityPackage = Cast< UPackage >(TextureOpacity->GetOuter()); + + HAPI_ImageInfo ImageInfo; + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureOpacityName; + bool bCreatedNewTextureOpacity = false; + + // Create opacity texture package, if this is a new opacity texture. + if (!TextureOpacityPackage) + { + TextureOpacityPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, + InPackageParams, + TextureOpacityName); + } + else if (TextureOpacity && !TextureOpacity->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureOpacityName = TextureOpacity->GetName(); + } + else + { + TextureOpacityName = FPaths::GetBaseFilename(TextureOpacityPackage->GetName(), true); + } + + // Create opacity texture, if we need to create one. + if (!TextureOpacity) + bCreatedNewTextureOpacity = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing opacity texture, or create new one. + TextureOpacity = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureOpacity, + ImageInfo, + TextureOpacityPackage, + TextureOpacityName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, + NodePath); + + // if (BakeMode == EBakeMode::CookToTemp) + TextureOpacity->SetFlags(RF_Public | RF_Standalone); + + // Create opacity sampling expression, if needed. + if (!ExpressionTextureOpacitySample) + { + ExpressionTextureOpacitySample = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionTextureOpacitySample->Desc = GeneratingParameterNameTexture; + ExpressionTextureOpacitySample->ParameterName = *GeneratingParameterNameTexture; + ExpressionTextureOpacitySample->Texture = TextureOpacity; + ExpressionTextureOpacitySample->SamplerType = SAMPLERTYPE_Grayscale; + + // Offset node placement. + ExpressionTextureOpacitySample->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionTextureOpacitySample->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Add expression. + Material->Expressions.Add(ExpressionTextureOpacitySample); + + // We need to set material type to masked. + TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput* ExpressionOutput = ExpressionOutputs.GetData(); + + Material->OpacityMask.Expression = ExpressionTextureOpacitySample; + Material->BlendMode = BLEND_Masked; + + Material->OpacityMask.Mask = ExpressionOutput->Mask; + Material->OpacityMask.MaskR = 1; + Material->OpacityMask.MaskG = 0; + Material->OpacityMask.MaskB = 0; + Material->OpacityMask.MaskA = 0; + + // Propagate and trigger opacity texture updates. + if (bCreatedNewTextureOpacity) + FAssetRegistryModule::AssetCreated(TextureOpacity); + + TextureOpacity->PreEditChange(nullptr); + TextureOpacity->PostEditChange(); + TextureOpacity->MarkPackageDirty(); + + bExpressionCreated = true; + } + + // Cache the texture package + OutPackages.AddUnique(TextureOpacityPackage); + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + float OpacityValue = 1.0f; + bool bNeedsTranslucency = false; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameters. + FString GeneratingParameterNameScalar = TEXT(""); + FString GeneratingParameterNameTexture = TEXT(""); + + UMaterialExpression * MaterialExpression = Material->Opacity.Expression; + + // Opacity expressions. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; + UMaterialExpressionScalarParameter * ExpressionScalarOpacity = nullptr; + UTexture2D * TextureOpacity = nullptr; + + // Opacity texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // If opacity sampling expression was not created, check if diffuse contains an alpha plane. + if (!ExpressionTextureOpacitySample) + { + UMaterialExpression * MaterialExpressionDiffuse = Material->BaseColor.Expression; + if (MaterialExpressionDiffuse) + { + // Locate diffuse sampling expression. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureDiffuseSample = + Cast< UMaterialExpressionTextureSampleParameter2D >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpressionDiffuse, + UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // See if there's an alpha plane in this expression's texture. + if (ExpressionTextureDiffuseSample) + { + UTexture2D * DiffuseTexture = Cast< UTexture2D >(ExpressionTextureDiffuseSample->Texture); + if (DiffuseTexture && !DiffuseTexture->CompressionNoAlpha) + { + // The diffuse texture has an alpha channel (that wasn't discarded), so we can use it + ExpressionTextureOpacitySample = ExpressionTextureDiffuseSample; + bNeedsTranslucency = true; + } + } + } + } + + // Retrieve opacity value + HAPI_ParmInfo ParmOpacityValueInfo; + HAPI_ParmId ParmOpacityValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_OGL, ParmOpacityValueInfo); + + if (ParmOpacityValueId >= 0) + { + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_OGL); + } + else + { + ParmOpacityValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA, ParmOpacityValueInfo); + + if (ParmOpacityValueId >= 0) + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA); + } + + if (ParmOpacityValueId >= 0) + { + if (ParmOpacityValueInfo.size > 0 && ParmOpacityValueInfo.floatValuesIndex >= 0) + { + float OpacityValueRetrieved = 1.0f; + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, + (float *)&OpacityValue, ParmOpacityValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + if (!ExpressionScalarOpacity) + { + ExpressionScalarOpacity = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Clamp retrieved value. + OpacityValueRetrieved = FMath::Clamp< float >(OpacityValueRetrieved, 0.0f, 1.0f); + OpacityValue = OpacityValueRetrieved; + + // Set expression fields. + ExpressionScalarOpacity->DefaultValue = OpacityValue; + ExpressionScalarOpacity->SliderMin = 0.0f; + ExpressionScalarOpacity->SliderMax = 1.0f; + ExpressionScalarOpacity->Desc = GeneratingParameterNameScalar; + ExpressionScalarOpacity->ParameterName = *GeneratingParameterNameScalar; + + // Add expression. + Material->Expressions.Add(ExpressionScalarOpacity); + + // If alpha is less than 1, we need translucency. + bNeedsTranslucency |= (OpacityValue != 1.0f); + } + } + } + + if (bNeedsTranslucency) + Material->BlendMode = BLEND_Translucent; + + if (ExpressionScalarOpacity && ExpressionTextureOpacitySample) + { + // We have both alpha and alpha uniform, attempt to locate multiply expression. + UMaterialExpressionMultiply * ExpressionMultiply = + Cast< UMaterialExpressionMultiply >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, + UMaterialExpressionMultiply::StaticClass())); + + if (!ExpressionMultiply) + ExpressionMultiply = NewObject< UMaterialExpressionMultiply >( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + Material->Expressions.Add(ExpressionMultiply); + + TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); + + ExpressionMultiply->A.Expression = ExpressionTextureOpacitySample; + ExpressionMultiply->B.Expression = ExpressionScalarOpacity; + + Material->Opacity.Expression = ExpressionMultiply; + Material->Opacity.Mask = ExpressionOutput->Mask; + Material->Opacity.MaskR = 0; + Material->Opacity.MaskG = 0; + Material->Opacity.MaskB = 0; + Material->Opacity.MaskA = 1; + + ExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMultiply->MaterialExpressionEditorY = MaterialNodeY; + + ExpressionScalarOpacity->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX; + ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + bExpressionCreated = true; + } + else if (ExpressionScalarOpacity) + { + Material->Opacity.Expression = ExpressionScalarOpacity; + + ExpressionScalarOpacity->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + bExpressionCreated = true; + } + else if (ExpressionTextureOpacitySample) + { + TArray ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); + + Material->Opacity.Expression = ExpressionTextureOpacitySample; + Material->Opacity.Mask = ExpressionOutput->Mask; + Material->Opacity.MaskR = 0; + Material->Opacity.MaskG = 0; + Material->Opacity.MaskB = 0; + Material->Opacity.MaskA = 1; + + bExpressionCreated = true; + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentNormal( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + bool bTangentSpaceNormal = true; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Normal texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Normalmap; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if separate normal texture is available. + HAPI_ParmInfo ParmNormalTextureInfo; + HAPI_ParmId ParmNormalTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_NORMAL, + HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED, + false, + ParmNormalTextureId, + ParmNormalTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_NORMAL_OGL, + "", + true, + ParmNormalTextureId, + ParmNormalTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_OGL); + } + else + { + // failed to find the texture + ParmNormalTextureId = -1; + } + + if (ParmNormalTextureId >= 0) + { + // Retrieve space for this normal texture. + HAPI_ParmInfo ParmInfoNormalType; + int32 ParmNormalTypeId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); + + // Retrieve value for normal type choice list (if exists). + if (ParmNormalTypeId >= 0) + { + FString NormalType = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT); + if (ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0) + { + HAPI_StringHandle StringHandle; + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, false, &StringHandle, ParmInfoNormalType.stringValuesIndex, ParmInfoNormalType.size) == HAPI_RESULT_SUCCESS) + { + // Get the actual string value. + FString NormalTypeString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(NormalTypeString)) + NormalType = NormalTypeString; + } + } + + // Check if we require world space normals. + if (NormalType.Equals(TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD), ESearchCase::IgnoreCase)) + bTangentSpaceNormal = false; + } + + // Retrieve color plane. + TArray ImageBuffer; + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNormalTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); + + UTexture2D * TextureNormal = nullptr; + if (ExpressionNormal) + { + TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Normal.Expression) + { + Material->Normal.Expression->ConditionalBeginDestroy(); + Material->Normal.Expression = nullptr; + } + } + + UPackage * TextureNormalPackage = nullptr; + if (TextureNormal) + TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureNormalName; + bool bCreatedNewTextureNormal = false; + + // Create normal texture package, if this is a new normal texture. + if (!TextureNormalPackage) + { + TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + InPackageParams, + TextureNormalName); + } + else if (TextureNormal && !TextureNormal->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureNormalName = TextureNormal->GetName(); + } + else + { + TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); + } + + // Create normal texture, if we need to create one. + if (!TextureNormal) + bCreatedNewTextureNormal = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing normal texture, or create new one. + TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureNormal, + ImageInfo, + TextureNormalPackage, + TextureNormalName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_WorldNormalMap, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureNormal->SetFlags(RF_Public | RF_Standalone); + + // Create normal sampling expression, if needed. + if (!ExpressionNormal) + ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionNormal->Desc = GeneratingParameterName; + ExpressionNormal->ParameterName = *GeneratingParameterName; + + ExpressionNormal->Texture = TextureNormal; + ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; + + // Offset node placement. + ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Set normal space. + Material->bTangentSpaceNormal = bTangentSpaceNormal; + + // Assign expression to material. + Material->Expressions.Add(ExpressionNormal); + Material->Normal.Expression = ExpressionNormal; + + bExpressionCreated = true; + + // Propagate and trigger normal texture updates. + if (bCreatedNewTextureNormal) + FAssetRegistryModule::AssetCreated(TextureNormal); + + TextureNormal->PreEditChange(nullptr); + TextureNormal->PostEditChange(); + TextureNormal->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureNormalPackage); + } + } + + // If separate normal map was not found, see if normal plane exists in diffuse map. + if (!bExpressionCreated) + { + // See if diffuse texture is available. + HAPI_ParmInfo ParmDiffuseTextureInfo; + HAPI_ParmId ParmDiffuseTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, + true, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, + false, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); + } + else + { + // failed to find the texture + ParmDiffuseTextureId = -1; + } + + if (ParmDiffuseTextureId >= 0) + { + // Normal plane is available in diffuse map. + TArray ImageBuffer; + + // Retrieve color plane - this will contain normal data. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmDiffuseTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = + Cast(Material->Normal.Expression); + + UTexture2D* TextureNormal = nullptr; + if (ExpressionNormal) + { + TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Normal.Expression) + { + Material->Normal.Expression->ConditionalBeginDestroy(); + Material->Normal.Expression = nullptr; + } + } + + UPackage* TextureNormalPackage = nullptr; + if (TextureNormal) + TextureNormalPackage = Cast(TextureNormal->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureNormalName; + bool bCreatedNewTextureNormal = false; + + // Create normal texture package, if this is a new normal texture. + if (!TextureNormalPackage) + { + TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + InPackageParams, + TextureNormalName); + } + else if (TextureNormal && !TextureNormal->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureNormalName = TextureNormal->GetName(); + } + else + { + TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); + } + + // Create normal texture, if we need to create one. + if (!TextureNormal) + bCreatedNewTextureNormal = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing normal texture, or create new one. + TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureNormal, + ImageInfo, + TextureNormalPackage, + TextureNormalName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_WorldNormalMap, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureNormal->SetFlags(RF_Public | RF_Standalone); + + // Create normal sampling expression, if needed. + if (!ExpressionNormal) + ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionNormal->Desc = GeneratingParameterName; + ExpressionNormal->ParameterName = *GeneratingParameterName; + + ExpressionNormal->Texture = TextureNormal; + ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; + + // Offset node placement. + ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Set normal space. + Material->bTangentSpaceNormal = bTangentSpaceNormal; + + // Assign expression to material. + Material->Expressions.Add(ExpressionNormal); + Material->Normal.Expression = ExpressionNormal; + + // Propagate and trigger diffuse texture updates. + if (bCreatedNewTextureNormal) + FAssetRegistryModule::AssetCreated(TextureNormal); + + TextureNormal->PreEditChange(nullptr); + TextureNormal->PostEditChange(); + TextureNormal->MarkPackageDirty(); + + bExpressionCreated = true; + } + + // Cache the texture package + OutPackages.AddUnique(TextureNormalPackage); + } + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Specular texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if specular texture is available. + HAPI_ParmInfo ParmSpecularTextureInfo; + HAPI_ParmId ParmSpecularTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL, + HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED, + true, + ParmSpecularTextureId, + ParmSpecularTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_SPECULAR, + HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED, + false, + ParmSpecularTextureId, + ParmSpecularTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR); + } + else + { + // failed to find the texture + ParmSpecularTextureId = -1; + } + + if (ParmSpecularTextureId >= 0) + { + TArray ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmSpecularTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Specular.Expression); + + UTexture2D * TextureSpecular = nullptr; + if (ExpressionSpecular) + { + TextureSpecular = Cast< UTexture2D >(ExpressionSpecular->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Specular.Expression) + { + Material->Specular.Expression->ConditionalBeginDestroy(); + Material->Specular.Expression = nullptr; + } + } + + UPackage * TextureSpecularPackage = nullptr; + if (TextureSpecular) + TextureSpecularPackage = Cast< UPackage >(TextureSpecular->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureSpecularName; + bool bCreatedNewTextureSpecular = false; + + // Create specular texture package, if this is a new specular texture. + if (!TextureSpecularPackage) + { + TextureSpecularPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, + InPackageParams, + TextureSpecularName); + } + else if (TextureSpecular && !TextureSpecular->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureSpecularName = TextureSpecular->GetName(); + } + else + { + TextureSpecularName = FPaths::GetBaseFilename(TextureSpecularPackage->GetName(), true); + } + + // Create specular texture, if we need to create one. + if (!TextureSpecular) + bCreatedNewTextureSpecular = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing specular texture, or create new one. + TextureSpecular = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureSpecular, + ImageInfo, + TextureSpecularPackage, + TextureSpecularName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureSpecular->SetFlags(RF_Public | RF_Standalone); + + // Create specular sampling expression, if needed. + if (!ExpressionSpecular) + { + ExpressionSpecular = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionSpecular->Desc = GeneratingParameterName; + ExpressionSpecular->ParameterName = *GeneratingParameterName; + + ExpressionSpecular->Texture = TextureSpecular; + ExpressionSpecular->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionSpecular->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionSpecular->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionSpecular); + Material->Specular.Expression = ExpressionSpecular; + + bExpressionCreated = true; + + // Propagate and trigger specular texture updates. + if (bCreatedNewTextureSpecular) + FAssetRegistryModule::AssetCreated(TextureSpecular); + + TextureSpecular->PreEditChange(nullptr); + TextureSpecular->PostEditChange(); + TextureSpecular->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureSpecularPackage); + } + } + + // See if we have a specular color + HAPI_ParmInfo ParmSpecularColorInfo; + HAPI_ParmId ParmSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL, ParmSpecularColorInfo); + + if (ParmSpecularColorId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL); + } + else + { + ParmSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR, ParmSpecularColorInfo); + + if (ParmSpecularColorId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR); + } + + if (!bExpressionCreated && ParmSpecularColorId >= 0) + { + // Specular color is available. + FLinearColor Color = FLinearColor::White; + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, + ParmSpecularColorInfo.floatValuesIndex, ParmSpecularColorInfo.size) == HAPI_RESULT_SUCCESS) + { + if (ParmSpecularColorInfo.size == 3) + Color.A = 1.0f; + + UMaterialExpressionVectorParameter * ExpressionSpecularColor = + Cast< UMaterialExpressionVectorParameter >(Material->Specular.Expression); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionSpecularColor) + { + // Otherwise new expression is of a different type. + if (Material->Specular.Expression) + { + Material->Specular.Expression->ConditionalBeginDestroy(); + Material->Specular.Expression = nullptr; + } + + ExpressionSpecularColor = NewObject< UMaterialExpressionVectorParameter >( + Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionSpecularColor->Desc = GeneratingParameterName; + ExpressionSpecularColor->ParameterName = *GeneratingParameterName; + + ExpressionSpecularColor->DefaultValue = Color; + + // Offset node placement. + ExpressionSpecularColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionSpecularColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionSpecularColor); + Material->Specular.Expression = ExpressionSpecularColor; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Roughness texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if roughness texture is available. + HAPI_ParmInfo ParmRoughnessTextureInfo; + HAPI_ParmId ParmRoughnessTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED, + true, + ParmRoughnessTextureId, + ParmRoughnessTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED, + false, + ParmRoughnessTextureId, + ParmRoughnessTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS); + } + else + { + // failed to find the texture + ParmRoughnessTextureId = -1; + } + + if (ParmRoughnessTextureId >= 0) + { + TArray ImageBuffer; + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmRoughnessTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer ) ) + { + UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Roughness.Expression); + + UTexture2D* TextureRoughness = nullptr; + if (ExpressionRoughness) + { + TextureRoughness = Cast< UTexture2D >(ExpressionRoughness->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Roughness.Expression) + { + Material->Roughness.Expression->ConditionalBeginDestroy(); + Material->Roughness.Expression = nullptr; + } + } + + UPackage * TextureRoughnessPackage = nullptr; + if (TextureRoughness) + TextureRoughnessPackage = Cast< UPackage >(TextureRoughness->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureRoughnessName; + bool bCreatedNewTextureRoughness = false; + + // Create roughness texture package, if this is a new roughness texture. + if (!TextureRoughnessPackage) + { + TextureRoughnessPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, + InPackageParams, + TextureRoughnessName); + } + else if (TextureRoughness && !TextureRoughness->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureRoughnessName = TextureRoughness->GetName(); + } + else + { + TextureRoughnessName = FPaths::GetBaseFilename(TextureRoughnessPackage->GetName(), true); + } + + // Create roughness texture, if we need to create one. + if (!TextureRoughness) + bCreatedNewTextureRoughness = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing roughness texture, or create new one. + TextureRoughness = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureRoughness, + ImageInfo, + TextureRoughnessPackage, + TextureRoughnessName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureRoughness->SetFlags(RF_Public | RF_Standalone); + + // Create roughness sampling expression, if needed. + if (!ExpressionRoughness) + ExpressionRoughness = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionRoughness->Desc = GeneratingParameterName; + ExpressionRoughness->ParameterName = *GeneratingParameterName; + + ExpressionRoughness->Texture = TextureRoughness; + ExpressionRoughness->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionRoughness->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionRoughness->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionRoughness); + Material->Roughness.Expression = ExpressionRoughness; + + bExpressionCreated = true; + + // Propagate and trigger roughness texture updates. + if (bCreatedNewTextureRoughness) + FAssetRegistryModule::AssetCreated(TextureRoughness); + + TextureRoughness->PreEditChange(nullptr); + TextureRoughness->PostEditChange(); + TextureRoughness->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureRoughnessPackage); + } + } + + // See if we have a roughness value + HAPI_ParmInfo ParmRoughnessValueInfo; + HAPI_ParmId ParmRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL, ParmRoughnessValueInfo); + + if (ParmRoughnessValueId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL); + } + else + { + ParmRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS, ParmRoughnessValueInfo); + + if (ParmRoughnessValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS); + } + + if (!bExpressionCreated && ParmRoughnessValueId >= 0) + { + // Roughness value is available. + + float RoughnessValue = 0.0f; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&RoughnessValue, + ParmRoughnessValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + UMaterialExpressionScalarParameter * ExpressionRoughnessValue = + Cast< UMaterialExpressionScalarParameter >(Material->Roughness.Expression); + + // Clamp retrieved value. + RoughnessValue = FMath::Clamp< float >(RoughnessValue, 0.0f, 1.0f); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionRoughnessValue) + { + // Otherwise new expression is of a different type. + if (Material->Roughness.Expression) + { + Material->Roughness.Expression->ConditionalBeginDestroy(); + Material->Roughness.Expression = nullptr; + } + + ExpressionRoughnessValue = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionRoughnessValue->Desc = GeneratingParameterName; + ExpressionRoughnessValue->ParameterName = *GeneratingParameterName; + + ExpressionRoughnessValue->DefaultValue = RoughnessValue; + ExpressionRoughnessValue->SliderMin = 0.0f; + ExpressionRoughnessValue->SliderMax = 1.0f; + + // Offset node placement. + ExpressionRoughnessValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionRoughnessValue->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionRoughnessValue); + Material->Roughness.Expression = ExpressionRoughnessValue; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Metallic texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if metallic texture is available. + HAPI_ParmInfo ParmMetallicTextureInfo; + HAPI_ParmId ParmMetallicTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_METALLIC_OGL, + HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED, + true, + ParmMetallicTextureId, + ParmMetallicTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_METALLIC, + HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED, + false, + ParmMetallicTextureId, + ParmMetallicTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC); + } + else + { + // failed to find the texture + ParmMetallicTextureId = -1; + } + + if (ParmMetallicTextureId >= 0) + { + TArray ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmMetallicTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Metallic.Expression); + + UTexture2D * TextureMetallic = nullptr; + if (ExpressionMetallic) + { + TextureMetallic = Cast(ExpressionMetallic->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Metallic.Expression) + { + Material->Metallic.Expression->ConditionalBeginDestroy(); + Material->Metallic.Expression = nullptr; + } + } + + UPackage * TextureMetallicPackage = nullptr; + if (TextureMetallic) + TextureMetallicPackage = Cast< UPackage >(TextureMetallic->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureMetallicName; + bool bCreatedNewTextureMetallic = false; + + // Create metallic texture package, if this is a new metallic texture. + if (!TextureMetallicPackage) + { + TextureMetallicPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, + InPackageParams, + TextureMetallicName); + } + else if (TextureMetallic && !TextureMetallic->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureMetallicName = TextureMetallic->GetName(); + } + else + { + TextureMetallicName = FPaths::GetBaseFilename(TextureMetallicPackage->GetName(), true); + } + + // Create metallic texture, if we need to create one. + if (!TextureMetallic) + bCreatedNewTextureMetallic = true; + + // Get the node path to add it to the meta data + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing metallic texture, or create new one. + TextureMetallic = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureMetallic, + ImageInfo, + TextureMetallicPackage, + TextureMetallicName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureMetallic->SetFlags(RF_Public | RF_Standalone); + + // Create metallic sampling expression, if needed. + if (!ExpressionMetallic) + ExpressionMetallic = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionMetallic->Desc = GeneratingParameterName; + ExpressionMetallic->ParameterName = *GeneratingParameterName; + + ExpressionMetallic->Texture = TextureMetallic; + ExpressionMetallic->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionMetallic->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMetallic->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionMetallic); + Material->Metallic.Expression = ExpressionMetallic; + + bExpressionCreated = true; + + // Propagate and trigger metallic texture updates. + if (bCreatedNewTextureMetallic) + FAssetRegistryModule::AssetCreated(TextureMetallic); + + TextureMetallic->PreEditChange(nullptr); + TextureMetallic->PostEditChange(); + TextureMetallic->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureMetallicPackage); + } + } + + // Get the metallic value + HAPI_ParmInfo ParmMetallicValueInfo; + HAPI_ParmId ParmMetallicValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL, ParmMetallicValueInfo); + + if (ParmMetallicValueId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL); + } + else + { + ParmMetallicValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmMetallicValueInfo); + + if (ParmMetallicValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); + } + + if (!bExpressionCreated && ParmMetallicValueId >= 0) + { + // Metallic value is available. + float MetallicValue = 0.0f; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&MetallicValue, + ParmMetallicTextureInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + UMaterialExpressionScalarParameter * ExpressionMetallicValue = + Cast< UMaterialExpressionScalarParameter >(Material->Metallic.Expression); + + // Clamp retrieved value. + MetallicValue = FMath::Clamp< float >(MetallicValue, 0.0f, 1.0f); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionMetallicValue) + { + // Otherwise new expression is of a different type. + if (Material->Metallic.Expression) + { + Material->Metallic.Expression->ConditionalBeginDestroy(); + Material->Metallic.Expression = nullptr; + } + + ExpressionMetallicValue = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionMetallicValue->Desc = GeneratingParameterName; + ExpressionMetallicValue->ParameterName = *GeneratingParameterName; + + ExpressionMetallicValue->DefaultValue = MetallicValue; + ExpressionMetallicValue->SliderMin = 0.0f; + ExpressionMetallicValue->SliderMax = 1.0f; + + // Offset node placement. + ExpressionMetallicValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMetallicValue->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionMetallicValue); + Material->Metallic.Expression = ExpressionMetallicValue; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Emissive texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if emissive texture is available. + HAPI_ParmInfo ParmEmissiveTextureInfo; + HAPI_ParmId ParmEmissiveTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED, + true, + ParmEmissiveTextureId, + ParmEmissiveTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_EMISSIVE, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED, + false, + ParmEmissiveTextureId, + ParmEmissiveTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE); + } + else + { + // failed to find the texture + ParmEmissiveTextureId = -1; + } + + if (ParmEmissiveTextureId >= 0) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmEmissiveTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->EmissiveColor.Expression); + + UTexture2D * TextureEmissive = nullptr; + if (ExpressionEmissive) + { + TextureEmissive = Cast< UTexture2D >(ExpressionEmissive->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->EmissiveColor.Expression) + { + Material->EmissiveColor.Expression->ConditionalBeginDestroy(); + Material->EmissiveColor.Expression = nullptr; + } + } + + UPackage * TextureEmissivePackage = nullptr; + if (TextureEmissive) + TextureEmissivePackage = Cast< UPackage >(TextureEmissive->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureEmissiveName; + bool bCreatedNewTextureEmissive = false; + + // Create emissive texture package, if this is a new emissive texture. + if (!TextureEmissivePackage) + { + TextureEmissivePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, + InPackageParams, + TextureEmissiveName); + } + else if (TextureEmissive && !TextureEmissive->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureEmissiveName = TextureEmissive->GetName(); + } + else + { + TextureEmissiveName = FPaths::GetBaseFilename(TextureEmissivePackage->GetName(), true); + } + + // Create emissive texture, if we need to create one. + if (!TextureEmissive) + bCreatedNewTextureEmissive = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing emissive texture, or create new one. + TextureEmissive = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureEmissive, + ImageInfo, + TextureEmissivePackage, + TextureEmissiveName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureEmissive->SetFlags(RF_Public | RF_Standalone); + + // Create emissive sampling expression, if needed. + if (!ExpressionEmissive) + ExpressionEmissive = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionEmissive->Desc = GeneratingParameterName; + ExpressionEmissive->ParameterName = *GeneratingParameterName; + + ExpressionEmissive->Texture = TextureEmissive; + ExpressionEmissive->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionEmissive->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionEmissive->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionEmissive); + Material->EmissiveColor.Expression = ExpressionEmissive; + + bExpressionCreated = true; + + // Propagate and trigger metallic texture updates. + if (bCreatedNewTextureEmissive) + FAssetRegistryModule::AssetCreated(TextureEmissive); + + TextureEmissive->PreEditChange(nullptr); + TextureEmissive->PostEditChange(); + TextureEmissive->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureEmissivePackage); + } + } + + HAPI_ParmInfo ParmEmissiveValueInfo; + HAPI_ParmId ParmEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL, ParmEmissiveValueInfo); + + if (ParmEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL); + else + { + ParmEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE, ParmEmissiveValueInfo); + + if (ParmEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE); + } + + if (!bExpressionCreated && ParmEmissiveValueId >= 0) + { + // Emissive color is available. + + FLinearColor Color = FLinearColor::White; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, + ParmEmissiveValueInfo.floatValuesIndex, ParmEmissiveValueInfo.size) == HAPI_RESULT_SUCCESS) + { + if (ParmEmissiveValueInfo.size == 3) + Color.A = 1.0f; + + UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = + Cast< UMaterialExpressionConstant4Vector >(Material->EmissiveColor.Expression); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionEmissiveColor) + { + // Otherwise new expression is of a different type. + if (Material->EmissiveColor.Expression) + { + Material->EmissiveColor.Expression->ConditionalBeginDestroy(); + Material->EmissiveColor.Expression = nullptr; + } + + ExpressionEmissiveColor = NewObject< UMaterialExpressionConstant4Vector >( + Material, UMaterialExpressionConstant4Vector::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionEmissiveColor->Desc = GeneratingParameterName; + if (ExpressionEmissiveColor->CanRenameNode()) + ExpressionEmissiveColor->SetEditableName(*GeneratingParameterName); + + ExpressionEmissiveColor->Constant = Color; + + // Offset node placement. + ExpressionEmissiveColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionEmissiveColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionEmissiveColor); + Material->EmissiveColor.Expression = ExpressionEmissiveColor; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + + +bool +FHoudiniMaterialTranslator::UpdateMaterialInstanceParameter( + FHoudiniGenericAttribute MaterialParameter, + UMaterialInstanceConstant* MaterialInstance, + const TArray& InPackages) +{ + bool bParameterUpdated = false; + +#if WITH_EDITOR + if (!MaterialInstance) + return false; + + if (MaterialParameter.AttributeName.IsEmpty()) + return false; + + // The default material instance parameters needs to be handled manually as they cant be changed via generic SetParameters functions + if (MaterialParameter.AttributeName.Compare("CastShadowAsMasked", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideCastShadowAsMasked() && (MaterialInstance->GetCastShadowAsMasked() == Value)) + return false; + + MaterialInstance->SetOverrideCastShadowAsMasked(true); + MaterialInstance->SetCastShadowAsMasked(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("EmissiveBoost", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideEmissiveBoost() && (MaterialInstance->GetEmissiveBoost() == Value)) + return false; + + MaterialInstance->SetOverrideEmissiveBoost(true); + MaterialInstance->SetEmissiveBoost(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("DiffuseBoost", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideDiffuseBoost() && (MaterialInstance->GetDiffuseBoost() == Value)) + return false; + + MaterialInstance->SetOverrideDiffuseBoost(true); + MaterialInstance->SetDiffuseBoost(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("ExportResolutionScale", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideExportResolutionScale() && (MaterialInstance->GetExportResolutionScale() == Value)) + return false; + + MaterialInstance->SetOverrideExportResolutionScale(true); + MaterialInstance->SetExportResolutionScale(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("OpacityMaskClipValue", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue && (MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue = true; + MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("BlendMode", ESearchCase::IgnoreCase) == 0) + { + EBlendMode EnumValue = (EBlendMode)MaterialParameter.GetIntValue(); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + FString StringValue = MaterialParameter.GetStringValue(); + if (StringValue.Compare("Opaque", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Opaque; + else if (StringValue.Compare("Masked", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Masked; + else if (StringValue.Compare("Translucent", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Translucent; + else if (StringValue.Compare("Additive", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Additive; + else if (StringValue.Compare("Modulate", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Modulate; + else if (StringValue.StartsWith("Alpha", ESearchCase::IgnoreCase)) + EnumValue = EBlendMode::BLEND_AlphaComposite; + } + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_BlendMode && (MaterialInstance->BasePropertyOverrides.BlendMode == EnumValue)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = true; + MaterialInstance->BasePropertyOverrides.BlendMode = EnumValue; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("ShadingModel", ESearchCase::IgnoreCase) == 0) + { + EMaterialShadingModel EnumValue = (EMaterialShadingModel)MaterialParameter.GetIntValue(); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + FString StringValue = MaterialParameter.GetStringValue(); + if (StringValue.Compare("Unlit", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Unlit; + else if (StringValue.StartsWith("Default", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_DefaultLit; + else if (StringValue.Compare("Subsurface", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Subsurface; + else if (StringValue.StartsWith("Preintegrated", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_PreintegratedSkin; + else if (StringValue.StartsWith("Clear", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_ClearCoat; + else if (StringValue.Compare("SubsurfaceProfile", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_SubsurfaceProfile; + else if (StringValue.Compare("TwoSidedFoliage", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_TwoSidedFoliage; + else if (StringValue.Compare("Hair", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Hair; + else if (StringValue.Compare("Cloth", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Cloth; + else if (StringValue.Compare("Eye", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Eye; + } + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel && (MaterialInstance->BasePropertyOverrides.ShadingModel == EnumValue)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel = true; + MaterialInstance->BasePropertyOverrides.ShadingModel = EnumValue; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("TwoSided", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_TwoSided && (MaterialInstance->BasePropertyOverrides.TwoSided == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_TwoSided = true; + MaterialInstance->BasePropertyOverrides.TwoSided = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("DitheredLODTransition", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition && (MaterialInstance->BasePropertyOverrides.DitheredLODTransition == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition = true; + MaterialInstance->BasePropertyOverrides.DitheredLODTransition = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("PhysMaterial", ESearchCase::IgnoreCase) == 0) + { + // Try to load a Material corresponding to the parameter value + FString ParamValue = MaterialParameter.GetStringValue(); + UPhysicalMaterial* FoundPhysMaterial = Cast< UPhysicalMaterial >( + StaticLoadObject(UPhysicalMaterial::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); + + // Update the parameter value if necessary + if (!FoundPhysMaterial || (MaterialInstance->PhysMaterial == FoundPhysMaterial)) + return false; + + MaterialInstance->PhysMaterial = FoundPhysMaterial; + bParameterUpdated = true; + } + + if (bParameterUpdated) + return true; + + // Handling custom parameters + FName CurrentMatParamName = FName(*MaterialParameter.AttributeName); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + // String attributes are used for textures parameters + // We need to find the texture corresponding to the param + UTexture* FoundTexture = nullptr; + FString ParamValue = MaterialParameter.GetStringValue(); + + // Texture can either be already existing texture assets in UE4, or a newly generated textures by this asset + // Try to find the texture corresponding to the param value in the existing assets first. + FoundTexture = Cast( + StaticLoadObject(UTexture::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); + + if (!FoundTexture) + { + // We couldn't find a texture corresponding to the parameter in the existing UE4 assets + // Try to find the corresponding texture in the cooked temporary package we just generated + FoundTexture = FHoudiniMaterialTranslator::FindGeneratedTexture(ParamValue, InPackages); + } + + // Do not update if unnecessary + if (FoundTexture) + { + // Do not update if unnecessary + UTexture* OldTexture = nullptr; + bool FoundOldParam = MaterialInstance->GetTextureParameterValue(CurrentMatParamName, OldTexture); + if (FoundOldParam && (OldTexture == FoundTexture)) + return false; + + MaterialInstance->SetTextureParameterValueEditorOnly(CurrentMatParamName, FoundTexture); + bParameterUpdated = true; + } + } + else if (MaterialParameter.AttributeTupleSize == 1) + { + // Single attributes are either for scalar parameters or static switches + float OldValue; + bool FoundOldScalarParam = MaterialInstance->GetScalarParameterValue(CurrentMatParamName, OldValue); + if (FoundOldScalarParam) + { + // The material parameter is a scalar + float NewValue = (float)MaterialParameter.GetDoubleValue(); + + // Do not update if unnecessary + if (OldValue == NewValue) + return false; + + MaterialInstance->SetScalarParameterValueEditorOnly(CurrentMatParamName, NewValue); + bParameterUpdated = true; + } + else + { + // See if the underlying parameter is a static switch + bool NewBoolValue = MaterialParameter.GetBoolValue(); + + // We need to iterate over the material's static parameter set + FStaticParameterSet StaticParameters; + MaterialInstance->GetStaticParameterValues(StaticParameters); + + for (int32 SwitchParameterIdx = 0; SwitchParameterIdx < StaticParameters.StaticSwitchParameters.Num(); ++SwitchParameterIdx) + { + FStaticSwitchParameter& SwitchParameter = StaticParameters.StaticSwitchParameters[SwitchParameterIdx]; + if (SwitchParameter.ParameterInfo.Name != CurrentMatParamName) + continue; + + if (SwitchParameter.Value == NewBoolValue) + return false; + + SwitchParameter.Value = NewBoolValue; + SwitchParameter.bOverride = true; + + MaterialInstance->UpdateStaticPermutation(StaticParameters); + bParameterUpdated = true; + break; + } + } + } + else + { + // Tuple attributes are for vector parameters + FLinearColor NewLinearColor; + // if the attribute is stored in an int, we'll have to convert a color to a linear color + if (MaterialParameter.AttributeType == EAttribStorageType::INT || MaterialParameter.AttributeType == EAttribStorageType::INT64) + { + FColor IntColor; + IntColor.R = (int8)MaterialParameter.GetIntValue(0); + IntColor.G = (int8)MaterialParameter.GetIntValue(1); + IntColor.B = (int8)MaterialParameter.GetIntValue(2); + if (MaterialParameter.AttributeTupleSize >= 4) + IntColor.A = (int8)MaterialParameter.GetIntValue(3); + else + IntColor.A = 1; + + NewLinearColor = FLinearColor(IntColor); + } + else + { + NewLinearColor.R = (float)MaterialParameter.GetDoubleValue(0); + NewLinearColor.G = (float)MaterialParameter.GetDoubleValue(1); + NewLinearColor.B = (float)MaterialParameter.GetDoubleValue(2); + if (MaterialParameter.AttributeTupleSize >= 4) + NewLinearColor.A = (float)MaterialParameter.GetDoubleValue(3); + } + + // Do not update if unnecessary + FLinearColor OldValue; + bool FoundOldParam = MaterialInstance->GetVectorParameterValue(CurrentMatParamName, OldValue); + if (FoundOldParam && (OldValue == NewLinearColor)) + return false; + + MaterialInstance->SetVectorParameterValueEditorOnly(CurrentMatParamName, NewLinearColor); + bParameterUpdated = true; + } +#endif + + return bParameterUpdated; +} + + +UTexture* +FHoudiniMaterialTranslator::FindGeneratedTexture(const FString& TextureString, const TArray& InPackages) +{ + if (TextureString.IsEmpty()) + return nullptr; + + // Try to find the corresponding texture in the cooked temporary package generated by an HDA +UTexture* FoundTexture = nullptr; +for (const auto& CurrentPackage : InPackages) +{ + // Iterate through the cooked packages + if (!CurrentPackage || CurrentPackage->IsPendingKill()) + continue; + + // First, check if the package contains a texture + FString CurrentPackageName = CurrentPackage->GetName(); + UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); + if (!PackageTexture) + continue; + + // Then check if the package's metadata match what we're looking for + // Make sure this texture was generated by Houdini Engine + UMetaData* MetaData = CurrentPackage->GetMetaData(); + if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + continue; + + // Get the texture type from the meta data + // Texture type store has meta data will be C_A, N, S, R etc.. + const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); + if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // Convert the texture type to a "friendly" version + // C_A to diffuse, N to Normal, S to Specular etc... + FString TextureTypeFriendlyString = TextureTypeString; + FString TextureTypeFriendlyAlternateString = TEXT(""); + if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) + { + TextureTypeFriendlyString = TEXT("diffuse"); + TextureTypeFriendlyAlternateString = TEXT("basecolor"); + } + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("normal"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("emissive"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("specular"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("roughness"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("metallic"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("opacity"); + + // See if we have a match between the texture string and the friendly name + if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) + { + FoundTexture = PackageTexture; + break; + } + + // Get the node path from the meta data + const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); + if (NodePath.IsEmpty()) + continue; + + // See if we have a match with the path and texture type + FString PathAndType = NodePath + TEXT("/") + TextureTypeString; + if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // See if we have a match with the friendly path and texture type + FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; + if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // Try the alternate friendly string + if (!TextureTypeFriendlyAlternateString.IsEmpty()) + { + PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; + if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + } +} + +return FoundTexture; +} + + +bool +FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + const HAPI_NodeId& InNodeId, + const std::string& InTextureParmName, + const std::string& InUseTextureParmName, + const bool& bFindByTag, + HAPI_ParmId& OutParmId, + HAPI_ParmInfo& OutParmInfo) +{ + OutParmId = -1; + + if(bFindByTag) + OutParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InTextureParmName, OutParmInfo); + else + OutParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InTextureParmName, OutParmInfo); + + if (OutParmId < 0) + { + // Failed to find the texture + return false; + } + + // We found a valid parameter, check if the matching "use" parameter exists + HAPI_ParmInfo FoundUseParmInfo; + HAPI_ParmId FoundUseParmId = -1; + if(bFindByTag) + FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InUseTextureParmName, FoundUseParmInfo); + else + FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InUseTextureParmName, FoundUseParmInfo); + + if (FoundUseParmId >= 0) + { + // We found a valid "use" parameter, check if it is disabled + // Get the param value + int32 UseValue = 0; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &UseValue, FoundUseParmInfo.intValuesIndex, 1)) + { + if (UseValue == 0) + { + // We found the texture parm, but the "use" param/tag is disabled, so don't use it! + // We still return true as we found the parameter, this will prevent looking for other parms + OutParmId = -1; + return true; + } + } + } + + // Finally, make sure that the found texture Parm is not empty! + FString ParmValue = FString(); + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandle, OutParmInfo.stringValuesIndex, 1)) + { + // Convert the string handle to FString + FHoudiniEngineString::ToFString(StringHandle, ParmValue); + } + + if (ParmValue.IsEmpty()) + { + // We found the parm, but it's empty, don't use it! + // We still return true as we found the parameter, this will prevent looking for other parms + OutParmId = -1; + return true; + } + + return true; +} + diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h index 9805049bf..d44ca8e4d 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h @@ -1,230 +1,230 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniGeoPartObject.h" -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "Engine/TextureDefines.h" - -#include - -class UMaterial; -class UMaterialInterface; -class UMaterialExpression; -class UMaterialInstanceConstant; -class UTexture2D; -class UTexture; -class UPackage; - -struct FHoudiniPackageParams; -struct FCreateTexture2DParameters; -struct FHoudiniGenericAttribute; - -// Forward declared enums do not work with 4.24 builds on Linux with the Clang 8.0.1 toolchain: ISO C++ forbids forward references to 'enum' types -// enum TextureGroup; - -struct HOUDINIENGINE_API FHoudiniMaterialTranslator -{ -public: - - // - static bool CreateHoudiniMaterials( - const HAPI_NodeId& InNodeId, - const FHoudiniPackageParams& InPackageParams, - const TArray& InUniqueMaterialIds, - const TArray& InUniqueMaterialInfos, - const TMap& InMaterials, - TMap& OutMaterials, - TArray& OutPackages, - const bool& bForceRecookAll, - bool bInTreatExistingMaterialsAsUpToDate=false); - - // - static bool CreateMaterialInstances( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& UniqueMaterialInstanceOverrides, - const TArray& InPackages, - const TMap& InMaterials, - TMap& OutMaterials, - const bool& bForceRecookAll); - - // - static bool UpdateMaterialInstanceParameter( - FHoudiniGenericAttribute MaterialParameter, - UMaterialInstanceConstant* MaterialInstance, - const TArray& InPackages); - - static UTexture* FindGeneratedTexture( - const FString& TextureString, - const TArray& InPackages); - - // - static UPackage* CreatePackageForTexture( - const HAPI_NodeId& InMaterialNodeId, - const FString& InTextureType, - const FHoudiniPackageParams& InPackageParams, - FString& OutTextureName); - - // - static UPackage* CreatePackageForMaterial( - const HAPI_NodeId& InMaterialNodeId, - const FString& InMaterialName, - const FHoudiniPackageParams& InPackageParams, - FString& OutMaterialName); - - - // Create a texture from given information. - static UTexture2D* CreateUnrealTexture( - UTexture2D* ExistingTexture, - const HAPI_ImageInfo& ImageInfo, - UPackage* Package, - const FString& TextureName, - const TArray& ImageBuffer, - const FCreateTexture2DParameters& TextureParameters, - const TextureGroup& LODGroup, - const FString& TextureType, - const FString& NodePath); - - // HAPI : Retrieve a list of image planes. - static bool HapiExtractImage( - const HAPI_ParmId& NodeParmId, - const HAPI_MaterialInfo& MaterialInfo, - const char * PlaneType, - const HAPI_ImageDataFormat& ImageDataFormat, - HAPI_ImagePacking ImagePacking, - bool bRenderToImage, - TArray& OutImageBuffer); - - // HAPI : Extract image data. - static bool HapiGetImagePlanes( - const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes); - - // Returns a unique name for a given material, its relative path (to the asset) - static bool GetMaterialRelativePath( - const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialNodeInfo, FString& OutRelativePath); - static bool GetMaterialRelativePath( - const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath); - - // Returns true if a texture parameter was found - // Ensures that the texture is not disabled via the "UseTexture" Parm name/tag - static bool FindTextureParamByNameOrTag( - const HAPI_NodeId& InNodeId, - const std::string& InTextureParmName, - const std::string& InUseTextureParmName, - const bool& bFindByTag, - HAPI_ParmId& OutParmId, - HAPI_ParmInfo& OutParmInfo); - -protected: - - // Helper function to locate first Material expression of given class within given expression subgraph. - static UMaterialExpression * MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass); - - // Create various material components. - static bool CreateMaterialComponentDiffuse( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentNormal( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentSpecular( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentRoughness( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentMetallic( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentEmissive( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentOpacity( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentOpacityMask( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - -public: - - // Material node construction offsets. - static const int32 MaterialExpressionNodeX; - static const int32 MaterialExpressionNodeY; - static const int32 MaterialExpressionNodeStepX; - static const int32 MaterialExpressionNodeStepY; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniGeoPartObject.h" +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Engine/TextureDefines.h" + +#include + +class UMaterial; +class UMaterialInterface; +class UMaterialExpression; +class UMaterialInstanceConstant; +class UTexture2D; +class UTexture; +class UPackage; + +struct FHoudiniPackageParams; +struct FCreateTexture2DParameters; +struct FHoudiniGenericAttribute; + +// Forward declared enums do not work with 4.24 builds on Linux with the Clang 8.0.1 toolchain: ISO C++ forbids forward references to 'enum' types +// enum TextureGroup; + +struct HOUDINIENGINE_API FHoudiniMaterialTranslator +{ +public: + + // + static bool CreateHoudiniMaterials( + const HAPI_NodeId& InNodeId, + const FHoudiniPackageParams& InPackageParams, + const TArray& InUniqueMaterialIds, + const TArray& InUniqueMaterialInfos, + const TMap& InMaterials, + TMap& OutMaterials, + TArray& OutPackages, + const bool& bForceRecookAll, + bool bInTreatExistingMaterialsAsUpToDate=false); + + // + static bool CreateMaterialInstances( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& UniqueMaterialInstanceOverrides, + const TArray& InPackages, + const TMap& InMaterials, + TMap& OutMaterials, + const bool& bForceRecookAll); + + // + static bool UpdateMaterialInstanceParameter( + FHoudiniGenericAttribute MaterialParameter, + UMaterialInstanceConstant* MaterialInstance, + const TArray& InPackages); + + static UTexture* FindGeneratedTexture( + const FString& TextureString, + const TArray& InPackages); + + // + static UPackage* CreatePackageForTexture( + const HAPI_NodeId& InMaterialNodeId, + const FString& InTextureType, + const FHoudiniPackageParams& InPackageParams, + FString& OutTextureName); + + // + static UPackage* CreatePackageForMaterial( + const HAPI_NodeId& InMaterialNodeId, + const FString& InMaterialName, + const FHoudiniPackageParams& InPackageParams, + FString& OutMaterialName); + + + // Create a texture from given information. + static UTexture2D* CreateUnrealTexture( + UTexture2D* ExistingTexture, + const HAPI_ImageInfo& ImageInfo, + UPackage* Package, + const FString& TextureName, + const TArray& ImageBuffer, + const FCreateTexture2DParameters& TextureParameters, + const TextureGroup& LODGroup, + const FString& TextureType, + const FString& NodePath); + + // HAPI : Retrieve a list of image planes. + static bool HapiExtractImage( + const HAPI_ParmId& NodeParmId, + const HAPI_MaterialInfo& MaterialInfo, + const char * PlaneType, + const HAPI_ImageDataFormat& ImageDataFormat, + HAPI_ImagePacking ImagePacking, + bool bRenderToImage, + TArray& OutImageBuffer); + + // HAPI : Extract image data. + static bool HapiGetImagePlanes( + const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes); + + // Returns a unique name for a given material, its relative path (to the asset) + static bool GetMaterialRelativePath( + const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialNodeInfo, FString& OutRelativePath); + static bool GetMaterialRelativePath( + const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath); + + // Returns true if a texture parameter was found + // Ensures that the texture is not disabled via the "UseTexture" Parm name/tag + static bool FindTextureParamByNameOrTag( + const HAPI_NodeId& InNodeId, + const std::string& InTextureParmName, + const std::string& InUseTextureParmName, + const bool& bFindByTag, + HAPI_ParmId& OutParmId, + HAPI_ParmInfo& OutParmInfo); + +protected: + + // Helper function to locate first Material expression of given class within given expression subgraph. + static UMaterialExpression * MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass); + + // Create various material components. + static bool CreateMaterialComponentDiffuse( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentNormal( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentSpecular( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentRoughness( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentMetallic( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentEmissive( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentOpacity( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentOpacityMask( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + +public: + + // Material node construction offsets. + static const int32 MaterialExpressionNodeX; + static const int32 MaterialExpressionNodeY; + static const int32 MaterialExpressionNodeStepX; + static const int32 MaterialExpressionNodeStepY; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp index f930813b0..361bf4d9d 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp @@ -1,6729 +1,6733 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniMeshTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniOutput.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniMaterialTranslator.h" -#include "HoudiniAssetActor.h" - -#include "HoudiniStaticMesh.h" -#include "HoudiniStaticMeshComponent.h" -#include "Engine/StaticMeshSocket.h" - -#include "PhysicsEngine/BodySetup.h" -#include "Engine/StaticMesh.h" -#include "PackageTools.h" -#include "RawMesh.h" -#include "Materials/MaterialInterface.h" -#include "Materials/Material.h" -#include "MeshDescription.h" -#include "StaticMeshAttributes.h" -#include "MeshDescriptionOperations.h" - -#include "BSPOps.h" -#include "Model.h" -#include "Engine/Polys.h" -#include "AssetRegistryModule.h" -#include "Interfaces/ITargetPlatform.h" -#include "Interfaces/ITargetPlatformManagerModule.h" -#include "AI/Navigation/NavCollisionBase.h" -#include "ObjectTools.h" - -// #include "Async/ParallelFor.h" - -#include "ProfilingDebugging/CpuProfilerTrace.h" - -#include "EditorSupportDelegates.h" - -#if WITH_EDITOR - #include "UnrealEd/Private/ConvexDecompTool.h" - #include "Editor/UnrealEd/Private/GeomFitUtils.h" - #include "LevelEditorViewport.h" - #include "FileHelpers.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -static TAutoConsoleVariable CVarHoudiniEngineMeshBuildTimer( - TEXT("HoudiniEngine.MeshBuildTimer"), - 0.0, - TEXT("When enabled, the plugin will output timings during the Mesh creation.\n") -); - -// -bool -FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - UHoudiniOutput* InOutput, - const FHoudiniPackageParams& InPackageParams, - const EHoudiniStaticMeshMethod& InStaticMeshMethod, - const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, - const FMeshBuildSettings& InMeshBuildSettings, - UObject* InOuterComponent, - bool bInTreatExistingMaterialsAsUpToDate, - bool bInDestroyProxies) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InPackageParams.OuterPackage || InPackageParams.OuterPackage->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - TMap NewOutputObjects; - TMap OldOutputObjects = InOutput->GetOutputObjects(); - TMap& AssignementMaterials = InOutput->GetAssignementMaterials(); - TMap& ReplacementMaterials = InOutput->GetReplacementMaterials(); - - bool InForceRebuild = false; - if (InOutput->HasAnyCurrentProxy() && InStaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh) - { - // Make sure we're not preventing refinement - InForceRebuild = true; - } - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) - { - // Not a mesh, skip - if (CurHGPO.Type != EHoudiniPartType::Mesh) - continue; - - // See if we have some uproperty attributes to update on - // the outer component (in most case, the HAC) - TArray PropertyAttributes; - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - CurHGPO.GeoId, CurHGPO.PartId, - true, 0, 0, 0, - PropertyAttributes)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( - InOuterComponent, PropertyAttributes); - } - - CreateStaticMeshFromHoudiniGeoPartObject( - CurHGPO, - InPackageParams, - OldOutputObjects, - NewOutputObjects, - AssignementMaterials, - ReplacementMaterials, - InForceRebuild, - InStaticMeshMethod, - InSMGenerationProperties, - InMeshBuildSettings, - bInTreatExistingMaterialsAsUpToDate); - } - - return FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - InOutput, - InOuterComponent, - NewOutputObjects, - bInDestroyProxies); -} - -bool -FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - UHoudiniOutput* InOutput, - UObject* InOuterComponent, - TMap& InNewOutputObjects, - bool bInDestroyProxies, - bool bInApplyGenericProperties) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - TMap OldOutputObjects = InOutput->GetOutputObjects(); - - // Remove Static Meshes and their components from the old map - // to avoid their deletion if new proxies were created for them - for (auto& NewOutputObj : InNewOutputObjects) - { - FHoudiniOutputObjectIdentifier OutputIdentifier = NewOutputObj.Key; - - // See if we already had that pair in the old map of static mesh - FHoudiniOutputObject* FoundOldOutputObj = OldOutputObjects.Find(NewOutputObj.Key); - if (!FoundOldOutputObj) - continue; - - UObject* NewStaticMesh = NewOutputObj.Value.OutputObject; - UObject* NewProxyMesh = NewOutputObj.Value.ProxyObject; - - UObject* OldStaticMesh = FoundOldOutputObj->OutputObject; - if (OldStaticMesh && !OldStaticMesh->IsPendingKill()) - { - // If a proxy was created for an existing static mesh, keep the existing static - // mesh (will be hidden) - if (NewProxyMesh && NewOutputObj.Value.bProxyIsCurrent) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - else if (NewStaticMesh && NewStaticMesh == OldStaticMesh) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - - UObject* OldProxyMesh = FoundOldOutputObj->ProxyObject; - if (OldProxyMesh && !OldProxyMesh->IsPendingKill()) - { - // If a new static mesh was created for a proxy, keep the proxy (will be hidden) - // ... unless we want to explicitly destroy proxies - if (NewStaticMesh && !bInDestroyProxies) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - else if (NewProxyMesh && (NewProxyMesh == OldProxyMesh)) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - } - - // The old map now only contains unused/stale Meshes/Components, delete them - for (auto& OldPair : OldOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; - FHoudiniOutputObject& OldOutputObject = OldPair.Value; - - // Remove the old component from the map - RemoveAndDestroyComponent(OldOutputObject.OutputComponent); - OldOutputObject.OutputComponent = nullptr; - // Remove the old proxy component from the map - RemoveAndDestroyComponent(OldOutputObject.ProxyComponent); - OldOutputObject.ProxyComponent = nullptr; - - if (OldOutputObject.OutputObject && !OldOutputObject.OutputObject->IsPendingKill()) - { - OldOutputObject.OutputObject->MarkPendingKill(); - } - - if (OldOutputObject.ProxyObject && !OldOutputObject.ProxyObject->IsPendingKill()) - { - OldOutputObject.ProxyObject->MarkPendingKill(); - } - } - OldOutputObjects.Empty(); - - /* - // Remove any stale components, these are components with OutputIdentifiers that are not - // in NewOutputObjects. This seems to happen mostly with the first or second cook after a - // "Rebuild Asset" - if (OutputComponents.Num() > 0 || OutputProxyComponents.Num() > 0) - { - TArray> StaleComponents; - const uint32 MaxNumStale = FMath::Max(OutputComponents.Num(), OutputProxyComponents.Num()); - StaleComponents.Reserve(MaxNumStale); - for (auto& ComponentPair : OutputComponents) - { - if (!NewOutputObjects.Contains(ComponentPair.Key) && !OldOutputObjectsReplacedByProxy.Contains(ComponentPair.Key)) - { - StaleComponents.Add(ComponentPair); - } - } - for (auto& ComponentPair : StaleComponents) - { - RemoveAndDestroyComponent(ComponentPair.Key, OutputComponents); - } - StaleComponents.Empty(MaxNumStale); - - for (auto& ComponentPair : OutputProxyComponents) - { - if (!NewOutputProxyObjects.Contains(ComponentPair.Key) && !OldOutputProxyObjectsReplacedByStaticMesh.Contains(ComponentPair.Key)) - { - StaleComponents.Add(ComponentPair); - } - } - for (auto& ComponentPair : StaleComponents) - { - RemoveAndDestroyComponent(ComponentPair.Key, OutputProxyComponents); - } - StaleComponents.Empty(); - } - */ - - // Now create/update the new static mesh components - for (auto& NewPair : InNewOutputObjects) - { - // Get the old Identifier / StaticMesh - const FHoudiniOutputObjectIdentifier& OutputIdentifier = NewPair.Key; - FHoudiniOutputObject& OutputObject = NewPair.Value; - - if (OutputObject.bIsImplicit) - { - // This output is implicit and shouldn't have a representative component/proxy in the scene - // Remove the old component from the map - if (OutputObject.OutputComponent) - { - RemoveAndDestroyComponent(OutputObject.OutputComponent); - OutputObject.OutputComponent = nullptr; - } - - // Remove the old proxy component from the map - RemoveAndDestroyComponent(OutputObject.ProxyComponent); - OutputObject.ProxyComponent = nullptr; - - continue; // Skip any proxy / component creation below - } - - // Check if we should create a Proxy/SMC - if (OutputObject.bProxyIsCurrent) - { - UObject *Mesh = OutputObject.ProxyObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) - { - HOUDINI_LOG_ERROR(TEXT("Proxy Mesh is invalid (wrong type or pending kill)...")); - continue; - } - - // Create or update a new proxy component - TSubclassOf ComponentType = UHoudiniStaticMeshComponent::StaticClass(); - const FHoudiniGeoPartObject *FoundHGPO = nullptr; - bool bCreated = false; - UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); - if (MeshComponent) - { - UHoudiniStaticMeshComponent *HSMC = Cast(MeshComponent); - - if (bCreated) - { - PostCreateHoudiniStaticMeshComponent(HSMC, Mesh); - } - else if (HSMC && !HSMC->IsPendingKill() && HSMC->GetMesh() != Mesh) - { - // We need to reassign the HSM to the component - UHoudiniStaticMesh* HSM = Cast(Mesh); - HSMC->SetMesh(HSM); - } - - UpdateMeshComponent( - MeshComponent, - OutputIdentifier, - FoundHGPO, - InOutput->HoudiniCreatedSocketActors, - InOutput->HoudiniAttachedSocketActors, - bInApplyGenericProperties); - - if (!bCreated) - { - // For proxy meshes: notify that the mesh has been updated - HSMC->NotifyMeshUpdated(); - HSMC->SetHoudiniIconVisible(true); - } - } - - // Now, ensure that meshes replaced by proxies are still kept but hidden - USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent) - { - SceneComponent->SetVisibility(false); - SceneComponent->SetHiddenInGame(true); - } - - // If the proxy mesh we just created is templated, hide it in game - if (FoundHGPO->bIsTemplated) - { - MeshComponent->SetHiddenInGame(true); - } - } - else - { - // Create a new SMC if needed - UObject* Mesh = OutputObject.OutputObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) - { - HOUDINI_LOG_ERROR(TEXT("Mesh is invalid (wrong type or pending kill)...")); - continue; - } - - TSubclassOf ComponentType = UStaticMeshComponent::StaticClass(); - const FHoudiniGeoPartObject *FoundHGPO = nullptr; - bool bCreated = false; - UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); - if (MeshComponent) - { - if (bCreated) - { - PostCreateStaticMeshComponent(Cast(MeshComponent), Mesh); - } - UpdateMeshComponent( - MeshComponent, - OutputIdentifier, - FoundHGPO, - InOutput->HoudiniCreatedSocketActors, - InOutput->HoudiniAttachedSocketActors, - bInApplyGenericProperties); - } - - // Now, ensure that proxies replaced by meshes are still kept but hidden - UHoudiniStaticMeshComponent *HSMC = Cast(OutputObject.ProxyComponent); - if (HSMC) - { - HSMC->SetVisibility(false); - HSMC->SetHiddenInGame(true); - HSMC->SetHoudiniIconVisible(false); - } - - // If the mesh we just created is templated, hide it in game - if (FoundHGPO->bIsTemplated) - { - MeshComponent->SetHiddenInGame(true); - } - } - } - - // Assign the new output objects to the output - InOutput->SetOutputObjects(InNewOutputObjects); - - return true; -} - -void -FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, - const FHoudiniGeoPartObject *InHGPO, TArray &HoudiniCreatedSocketActors, TArray &HoudiniAttachedSocketActors, - bool bInApplyGenericProperties) -{ - // Update collision/visibility - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(InOutputIdentifier.SplitIdentifier); - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider) - { - // Invisible complex collider should not be seen - InMeshComponent->SetVisibility(false); - InMeshComponent->SetHiddenInGame(true); - InMeshComponent->SetCollisionProfileName(FName(TEXT("InvisibleWall"))); - InMeshComponent->SetCastShadow(false); - } - else - { - // Update visiblity - bool bVisible = InHGPO ? InHGPO->bIsVisible : true; - InMeshComponent->SetVisibility(bVisible); - InMeshComponent->SetHiddenInGame(!bVisible); - } - - // TODO: - // Update navmesh? - - // Transform the component by transformation provided by HAPI. - InMeshComponent->SetRelativeTransform(InHGPO->TransformMatrix); - - // If the static mesh had sockets, we can assign the desired actor to them now - UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); - UStaticMesh * StaticMesh = nullptr; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - StaticMesh = StaticMeshComponent->GetStaticMesh(); - - if (StaticMesh && !StaticMesh->IsPendingKill()) - { - 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; - - AddActorsToMeshSocket(StaticMesh->Sockets[nSocket], StaticMeshComponent, HoudiniCreatedSocketActors, HoudiniAttachedSocketActors); - } - - // Iterate all remaining created socket actors, destroy the ones that are not assigned to socket after re-cook - { - for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - continue; - } - - bool bFoundSocket = false; - for (auto & CurSocket : StaticMesh->Sockets) - { - if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) - { - bFoundSocket = true; - break; - } - } - // cur actor's attaching socket is found, skip - if (bFoundSocket) - continue; - - // Destroy the previous created socket actor if not found - HoudiniCreatedSocketActors.RemoveAt(Idx); - CurActor->Destroy(); - } - } - - // Detach the in level actors which is not attached to any socket now - { - for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor* CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniAttachedSocketActors.RemoveAt(Idx); - continue; - } - - bool bFoundSocket = false; - for (auto & CurSocket : StaticMesh->Sockets) - { - if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) - { - bFoundSocket = true; - break; - } - } - - if (bFoundSocket) - continue; - - // If the attached socket name is not found in current socket, detach it and remove from the array - CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - HoudiniAttachedSocketActors.RemoveAt(Idx); - } - } - - } - - if (bInApplyGenericProperties) - { - // Clear the component tags as generic properties only add them - InMeshComponent->ComponentTags.Empty(); - // Update the property attributes on the component - TArray PropertyAttributes; - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - InOutputIdentifier.GeoId, InOutputIdentifier.PartId, - true, - InOutputIdentifier.PrimitiveIndex, - INDEX_NONE, - InOutputIdentifier.PointIndex, - PropertyAttributes)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); - } - } -} - -bool -FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& InOutputObjects, - TMap& OutOutputObjects, - TMap& AssignmentMaterialMap, - TMap& ReplacementMaterialMap, - const bool& InForceRebuild, - const EHoudiniStaticMeshMethod& InStaticMeshMethod, - const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, - const FMeshBuildSettings& InSMBuildSettings, - bool bInTreatExistingMaterialsAsUpToDate) -{ - // If we're not forcing the rebuild - // No need to recreate something that hasn't changed - if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged) && InOutputObjects.Num() > 0) - { - // Simply reuse the existing meshes - OutOutputObjects = InOutputObjects; - return true; - } - - FHoudiniMeshTranslator CurrentTranslator; - CurrentTranslator.ForceRebuild = InForceRebuild; - CurrentTranslator.SetHoudiniGeoPartObject(InHGPO); - CurrentTranslator.SetInputObjects(InOutputObjects); - CurrentTranslator.SetOutputObjects(OutOutputObjects); - CurrentTranslator.SetInputAssignmentMaterials(AssignmentMaterialMap); - CurrentTranslator.SetReplacementMaterials(ReplacementMaterialMap); - CurrentTranslator.SetPackageParams(InPackageParams, true); - CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); - CurrentTranslator.SetStaticMeshGenerationProperties(InSMGenerationProperties); - CurrentTranslator.SetStaticMeshBuildSettings(InSMBuildSettings); - - // TODO: Fetch from settings/HAC - CurrentTranslator.DefaultMeshSmoothing = 1; - if (false) - CurrentTranslator.DefaultMeshSmoothing = 0; - - // TODO: mechanism to determine when to use dynamic mesh for fast updates, and when to switch to - // baking the full static mesh - switch (InStaticMeshMethod) - { - case EHoudiniStaticMeshMethod::RawMesh: - CurrentTranslator.CreateStaticMesh_RawMesh(); - break; - case EHoudiniStaticMeshMethod::FMeshDescription: - CurrentTranslator.CreateStaticMesh_MeshDescription(); - break; - case EHoudiniStaticMeshMethod::UHoudiniStaticMesh: - CurrentTranslator.CreateHoudiniStaticMesh(); - break; - } - - // Copy the output objects/materials - OutOutputObjects = CurrentTranslator.OutputObjects; - AssignmentMaterialMap = CurrentTranslator.OutputAssignmentMaterials; - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartVertexList() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartVertexList")); - - if (HGPO.PartInfo.VertexCount <= 0) - return false; - - // Get the vertex List - PartVertexList.SetNumUninitialized(HGPO.PartInfo.VertexCount); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVertexList( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, &PartVertexList[0], 0, HGPO.PartInfo.VertexCount)) - { - // Error getting the vertex list. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - - return false; - } - - return true; -} - -void -FHoudiniMeshTranslator::SortSplitGroups() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::SortSplitGroups")); - - // Sort the splits in the order that we want to process them: - // Simple/Convex invisible colliders should be treated first as they will need to be attached to the visible meshes - TArray First; - - // The main geo and its LODs should be created after. - TArray Main; - TArray LODs; - - // Finally, visible colliders and invisible complex colliders as they need their own static mesh - TArray Last; - - for (auto& curSplit : HGPO.SplitGroups) - { - EHoudiniSplitType curSplitType = GetSplitTypeFromSplitName(curSplit); - switch (curSplitType) - { - case EHoudiniSplitType::InvisibleSimpleCollider: - case EHoudiniSplitType::InvisibleUCXCollider: - First.Add(curSplit); - break; - - case EHoudiniSplitType::Normal: - Main.Add(curSplit); - break; - - case EHoudiniSplitType::LOD: - LODs.Add(curSplit); - break; - - case EHoudiniSplitType::RenderedSimpleCollider: - case EHoudiniSplitType::RenderedUCXCollider: - case EHoudiniSplitType::RenderedComplexCollider: - case EHoudiniSplitType::InvisibleComplexCollider: - Last.Add(curSplit); - break; - } - } - - // Make sure LODs are order by name - LODs.Sort(); - - // Copy the split names in order - AllSplitGroups.Empty(); - for (auto& splitName : First) - AllSplitGroups.Add(splitName); - - for (auto& splitName : Main) - AllSplitGroups.Add(splitName); - - for (auto& splitName : LODs) - AllSplitGroups.Add(splitName); - - for (auto& splitName : Last) - AllSplitGroups.Add(splitName); -} - -bool -FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices")); - - // Reset the splits faces/indices arrays - AllSplitVertexLists.Empty(); - AllSplitVertexCounts.Empty(); - AllSplitFaceIndices.Empty(); - AllSplitFirstValidVertexIndex.Empty(); - AllSplitFirstValidPrimIndex.Empty(); - - bool bHasSplit = AllSplitGroups.Num() > 0; - if (bHasSplit) - { - HAPI_PartInfo PartInfo = FHoudiniEngineUtils::ToHAPIPartInfo(HGPO.PartInfo); - - // Buffer for all vertex indices used for split groups. - // We need this to figure out all vertex indices that are not part of them. - TArray AllVertexList; - AllVertexList.SetNumZeroed(PartVertexList.Num()); - - // Buffer for all face indices used for split groups. - // We need this to figure out all face indices that are not part of them. - TArray AllGroupFaceIndices; - AllGroupFaceIndices.SetNumZeroed(HGPO.PartInfo.FaceCount); - - // Some of the groups may contain invalid geometry - // Store them here so we can remove them afterwards - TArray InvalidGroupNameIndices; - - // Extract the vertices/faces for each of the split groups - for (int32 SplitIdx = 0; SplitIdx < AllSplitGroups.Num(); SplitIdx++) - { - const FString& GroupName = AllSplitGroups[SplitIdx]; - - // New vertex list just for this group. - TArray< int32 > GroupVertexList; - TArray< int32 > AllFaceList; - - int32 FirstValidPrimIndex = 0; - int32 FirstValidVertexIndex = 0; - // Extract vertex indices for this split. - int32 GroupVertexListCount = FHoudiniEngineUtils::HapiGetVertexListForGroup( - HGPO.GeoId, PartInfo, GroupName, - PartVertexList, GroupVertexList, - AllVertexList, AllFaceList, AllGroupFaceIndices, - FirstValidVertexIndex, FirstValidPrimIndex, - HGPO.PartInfo.bIsInstanced); - - if (GroupVertexListCount <= 0) - { - // This group doesn't have vertices/faces, mark it as invalid - InvalidGroupNameIndices.Add(SplitIdx); - - // Error getting the vertex list. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list for group %s - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, *GroupName); - - continue; - } - - // If list is not empty, we store it for this group - this will define new mesh. - AllSplitVertexLists.Add(GroupName, GroupVertexList); - AllSplitVertexCounts.Add(GroupName, GroupVertexListCount); - AllSplitFaceIndices.Add(GroupName, AllFaceList); - AllSplitFirstValidVertexIndex.Add(GroupName, FirstValidVertexIndex); - AllSplitFirstValidPrimIndex.Add(GroupName, FirstValidPrimIndex); - } - - if (InvalidGroupNameIndices.Num() > 0) - { - // Remove all invalid split groups - for (int32 InvalIdx = InvalidGroupNameIndices.Num() - 1; InvalIdx >= 0; InvalIdx--) - { - int32 Index = InvalidGroupNameIndices[InvalIdx]; - AllSplitGroups.RemoveAt(Index); - } - } - - // We also need to figure out / construct the vertex list for everything that's not in a split group - TArray GroupSplitFacesRemaining; - GroupSplitFacesRemaining.Init(-1, PartVertexList.Num()); - - int32 GroupVertexListCount = 0; - bool bHasMainSplitGroup = false; - TArray< int32 > GroupSplitFaceIndicesRemaining; - int32 FistUnusedVertexIndex = -1; - for (int32 SplitVertexIdx = 0; SplitVertexIdx < AllVertexList.Num(); SplitVertexIdx++) - { - if (AllVertexList[SplitVertexIdx] == 0) - { - // This is an unused index, we need to add it to unused vertex list. - FistUnusedVertexIndex = SplitVertexIdx; - GroupSplitFacesRemaining[SplitVertexIdx] = PartVertexList[SplitVertexIdx]; - bHasMainSplitGroup = true; - GroupVertexListCount++; - } - } - - int32 FistUnusedPrimIndex = -1; - for (int32 SplitFaceIdx = 0; SplitFaceIdx < AllGroupFaceIndices.Num(); SplitFaceIdx++) - { - if (AllGroupFaceIndices[SplitFaceIdx] == 0) - { - // This is unused face, we need to add it to unused faces list. - GroupSplitFaceIndicesRemaining.Add(SplitFaceIdx); - FistUnusedPrimIndex = SplitFaceIdx; - } - } - - // We store the remaining geo vertex list as a special split named "main geo" - // and make sure its treated before the collider meshes - if (bHasMainSplitGroup) - { - static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - AllSplitGroups.Add(RemainingGroupName); - AllSplitVertexLists.Add(RemainingGroupName, GroupSplitFacesRemaining); - AllSplitVertexCounts.Add(RemainingGroupName, GroupVertexListCount); - AllSplitFaceIndices.Add(RemainingGroupName, GroupSplitFaceIndicesRemaining); - AllSplitFirstValidPrimIndex.Add(RemainingGroupName, FistUnusedPrimIndex); - AllSplitFirstValidVertexIndex.Add(RemainingGroupName, FistUnusedVertexIndex); - } - } - else - { - // No splitting required - // Mark everything as the main geo group - static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - AllSplitGroups.Add(RemainingGroupName); - AllSplitVertexLists.Add(RemainingGroupName, PartVertexList); - AllSplitVertexCounts.Add(RemainingGroupName, PartVertexList.Num()); - AllSplitFirstValidPrimIndex.Add(RemainingGroupName, 0); - AllSplitFirstValidVertexIndex.Add(RemainingGroupName, 0); - - TArray AllFaces; - for (int32 FaceIdx = 0; FaceIdx < HGPO.PartInfo.FaceCount; ++FaceIdx) - AllFaces.Add(FaceIdx); - - AllSplitFaceIndices.Add(RemainingGroupName, AllFaces); - } - - return true; -} - -void -FHoudiniMeshTranslator::ResetPartCache() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::ResetPartCache")); - - // Vertex Positions - PartPositions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Vertex Normals - PartNormals.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Vertex TangentU - PartTangentU.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentU); - - // Vertex TangentV - PartTangentV.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentV); - - // Vertex Colors - PartColors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoColors); - - // Vertex Alpha values - PartAlphas.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoAlpha); - - // FaceSmoothing values - PartFaceSmoothingMasks.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceSmoothingMasks); - - // UVs - PartUVSets.Empty(); - AttribInfoUVSets.Empty(); - - // UVs - PartLightMapResolutions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoLightmapResolution); - - // Material IDs per face - PartFaceMaterialIds.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialIds); - // Unique material IDs - PartUniqueMaterialIds.Empty(); - // Material infos for each unique Material - PartUniqueMaterialInfos.Empty(); - // - bOnlyOneFaceMaterial = false; - - // Face Materials override - PartFaceMaterialOverrides.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialOverrides); - bMaterialOverrideNeedsCreateInstance = false; - - // LOD Screensize - PartLODScreensize.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoLODScreensize); -} - -bool -FHoudiniMeshTranslator::UpdatePartPositionIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartPositionIfNeeded")); - - // Only Retrieve the vertices positions if necessary - if (PartPositions.Num() > 0) - return true; - - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve position data") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded")); - - // No need to read the normals if we want unreal to recompute them after - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - bool bReadNormals = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; - if (!bReadNormals) - return true; - - // Only Retrieve the normals if we haven't already - if (PartNormals.Num() > 0) - return true; - - // Retrieve normal data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, PartNormals); - - // There is no normals to fetch - if (!AttribInfoNormals.exists) - return true; - - if (!Success && AttribInfoNormals.exists) - { - // Error retrieving normals. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve normal data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded")) - - bool bReturn = true; - if (PartTangentU.Num() <= 0) - { - // Retrieve TangentU data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_TANGENTU, AttribInfoTangentU, PartTangentU); - - if (!Success && AttribInfoTangentU.exists) - { - // Error retrieving tangent. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentU data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - bReturn = false; - } - } - - if (PartTangentV.Num() <= 0) - { - // Retrieve TangentV data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_TANGENTV, AttribInfoTangentV, PartTangentV); - - if (!Success && AttribInfoTangentV.exists) - { - // Error retrieving tangent. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentV data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - bReturn = false; - } - } - - return bReturn; -} - -bool -FHoudiniMeshTranslator::UpdatePartColorsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartColorsIfNeeded")); - - // Only Retrieve the vertices colors if necessary - if (PartColors.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_COLOR, AttribInfoColors, PartColors); - - if (!Success && AttribInfoColors.exists) - { - // Error retrieving colors. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve color data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded")); - - // Only Retrieve the vertices alphas if necessary - if (PartAlphas.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_ALPHA, AttribInfoAlpha, PartAlphas); - - if (!Success && AttribInfoAlpha.exists) - { - // Error retrieving alpha values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve alpha data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceSmoothingIfNeeded() -{ - // Only Retrieve the vertices FaceSmoothing if necessary - if (PartFaceSmoothingMasks.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, - AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks); - - if (!Success && AttribInfoFaceSmoothingMasks.exists) - { - // Error retrieving FaceSmoothing values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve FaceSmoothing data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded")); - - // Only Retrieve uvs if necessary - if (PartUVSets.Num() > 0) - return true; - - PartUVSets.SetNum(MAX_STATIC_TEXCOORDS); - AttribInfoUVSets.SetNum(MAX_STATIC_TEXCOORDS); - - // The second UV set should be called uv2, but we will still check if need to look for a uv1 set. - // If uv1 exists, we'll look for uv, uv1, uv2 etc.. if not we'll look for uv, uv2, uv3 etc.. - bool bUV1Exists = FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, "uv1"); - - // Retrieve UVs. - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (TexCoordIdx > 0) - UVAttributeName += FString::Printf(TEXT("%d"), bUV1Exists ? TexCoordIdx : TexCoordIdx + 1); - - FHoudiniApi::AttributeInfo_Init(&AttribInfoUVSets[TexCoordIdx]); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*UVAttributeName), - AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], 2); - } - - // Also look for 16.5 uvs (attributes with a Texture type) - // For that, we'll have to iterate through ALL the attributes and check their types - TArray< FString > FoundAttributeNames; - TArray< HAPI_AttributeInfo > FoundAttributeInfos; - - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - FHoudiniEngineUtils::HapiGetAttributeOfType( - HGPO.GeoId, HGPO.PartId, (HAPI_AttributeOwner)AttrIdx, - HAPI_ATTRIBUTE_TYPE_TEXTURE, FoundAttributeInfos, FoundAttributeNames); - } - - if (FoundAttributeInfos.Num() <= 0) - return true; - - // We found some additionnal uv attributes - int32 AvailableIdx = 0; - for (int32 attrIdx = 0; attrIdx < FoundAttributeInfos.Num(); attrIdx++) - { - // Ignore the old uvs - if (FoundAttributeNames[attrIdx] == TEXT("uv") - || FoundAttributeNames[attrIdx] == TEXT("uv1") - || FoundAttributeNames[attrIdx] == TEXT("uv2") - || FoundAttributeNames[attrIdx] == TEXT("uv3") - || FoundAttributeNames[attrIdx] == TEXT("uv4") - || FoundAttributeNames[attrIdx] == TEXT("uv5") - || FoundAttributeNames[attrIdx] == TEXT("uv6") - || FoundAttributeNames[attrIdx] == TEXT("uv7") - || FoundAttributeNames[attrIdx] == TEXT("uv8")) - continue; - - HAPI_AttributeInfo CurrentAttrInfo = FoundAttributeInfos[attrIdx]; - if (!CurrentAttrInfo.exists) - continue; - - // Look for the next available index in the return arrays - for (; AvailableIdx < AttribInfoUVSets.Num(); AvailableIdx++) - { - if (!AttribInfoUVSets[AvailableIdx].exists) - break; - } - - // We are limited to MAX_STATIC_TEXCOORDS uv sets! - // If we already have too many uv sets, skip the rest - if ((AvailableIdx >= MAX_STATIC_TEXCOORDS) || (AvailableIdx >= AttribInfoUVSets.Num())) - { - HOUDINI_LOG_WARNING(TEXT("Too many UV sets found. Unreal only supports %d , skipping the remaining uv sets."), (int32)MAX_STATIC_TEXCOORDS); - break; - } - - // Force the tuple size to 2 ? - CurrentAttrInfo.tupleSize = 2; - - // Add the attribute infos we found - AttribInfoUVSets[AvailableIdx] = CurrentAttrInfo; - - // Allocate sufficient buffer for the attribute's data. - PartUVSets[AvailableIdx].SetNumUninitialized(CurrentAttrInfo.count * CurrentAttrInfo.tupleSize); - - // Get the texture coordinates - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, TCHAR_TO_UTF8(*(FoundAttributeNames[attrIdx])), - &AttribInfoUVSets[AvailableIdx], -1, - &PartUVSets[AvailableIdx][0], 0, CurrentAttrInfo.count)) - { - // Something went wrong when trying to access the uv values, invalidate this set - AttribInfoUVSets[AvailableIdx].exists = false; - } - } - - // Remove unused UV sets - if (bRemoveUnused) - { - for (int32 Idx = PartUVSets.Num() - 1; Idx >= 0; Idx--) - { - if (PartUVSets[Idx].Num() > 0) - continue; - - PartUVSets.RemoveAt(Idx); - } - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartLightmapResolutionsIfNeeded() -{ - // Only Retrieve the vertices lightmap resolution if necessary - if (PartLightMapResolutions.Num() > 0) - return true; - - // Get lightmap resolution (if present). - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, - AttribInfoLightmapResolution, PartLightMapResolutions); - - if (!Success && AttribInfoLightmapResolution.exists) - { - // Error retrieving lightmap resolution values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve lightmap resolution data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded")); - - // Only Retrieve the material IDs if necessary - if (PartFaceMaterialIds.Num() > 0) - return true; - - int32 NumFaces = HGPO.PartInfo.FaceCount; - if (NumFaces <= 0) - return true; - - PartFaceMaterialIds.SetNum(NumFaces); - - // Get the materials IDs per face - HAPI_Bool bSingleFaceMaterial = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, &bSingleFaceMaterial, - &PartFaceMaterialIds[0], 0, NumFaces)) - { - // Error retrieving material face assignments. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material face assignments"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - bOnlyOneFaceMaterial = bSingleFaceMaterial; - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded")); - - // Only Retrieve the material overrides if necessary - if (PartFaceMaterialOverrides.Num() > 0) - return true; - - bMaterialOverrideNeedsCreateInstance = false; - - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - - // If material attribute was not found, check fallback compatibility attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - } - - // If material attribute and fallbacks were not found, check the material instance attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - - // We will we need to create material instances from the override attributes - bMaterialOverrideNeedsCreateInstance = AttribInfoFaceMaterialOverrides.exists; - } - - if (AttribInfoFaceMaterialOverrides.exists - && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_PRIM - && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_DETAIL) - { - HOUDINI_LOG_WARNING(TEXT("Static Mesh [%d %s], Geo [%d], Part [%d %s]: unreal_material must be a primitive or detail attribute, ignoring attribute."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - AttribInfoFaceMaterialOverrides.exists = false; - bMaterialOverrideNeedsCreateInstance = false; - PartFaceMaterialOverrides.Empty(); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartNeededMaterials() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials")); - - // Update the per face material IDs - UpdatePartFaceMaterialIDsIfNeeded(); - - // See if we have some material overides - UpdatePartFaceMaterialOverridesIfNeeded(); - - // If we have houdini materials AND overrides: - // We want to only create the Houdini materials that are not "covered" by overrides - // If we have material instance attributes, create all the houdini material anyway - // as their textures could be referenced by the material instance parameters - if (PartFaceMaterialOverrides.Num() > 0 && !bMaterialOverrideNeedsCreateInstance) - { - // If the material override was set on the detail, no need to look for houdini material IDs, as only the override will be used - if (AttribInfoFaceMaterialOverrides.exists && AttribInfoFaceMaterialOverrides.owner == HAPI_ATTROWNER_PRIM) - { - for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) - { - // Add a material ID to the unique array only if that face is not using the override - if (PartFaceMaterialOverrides[MaterialIdx].IsEmpty()) - PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); - } - } - } - else - { - // No material overrides, simply update the unique material array - for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) - PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); - } - - // Remove the invalid material ID from the unique array - PartUniqueMaterialIds.RemoveSingle(-1); - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials - Get the unique material infos")); - // Get the unique material infos - PartUniqueMaterialInfos.SetNum(PartUniqueMaterialIds.Num()); - for (int32 MaterialIdx = 0; MaterialIdx < PartUniqueMaterialIds.Num(); MaterialIdx++) - { - - FHoudiniApi::MaterialInfo_Init(&PartUniqueMaterialInfos[MaterialIdx]); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialInfo( - FHoudiniEngine::Get().GetSession(), - PartUniqueMaterialIds[MaterialIdx], - &PartUniqueMaterialInfos[MaterialIdx])) - { - // Error retrieving material face assignments. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material info for material %d"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, PartUniqueMaterialIds[MaterialIdx]); - continue; - } - } - } - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartLODScreensizeIfNeeded() -{ - // Only retrieve LOD screensizes if necessary - if (PartLODScreensize.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE, - AttribInfoLODScreensize, PartLODScreensize); - - if (!Success && AttribInfoLODScreensize.exists) - { - // Error retrieving FaceSmoothing values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve LOD screensizes"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - - -UStaticMesh* -FHoudiniMeshTranslator::CreateNewStaticMesh(const FString& InSplitIdentifier) -{ - // Update the current Obj/Geo/Part/Split IDs - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.GeoId; - PackageParams.PartId = HGPO.PartId; - PackageParams.SplitStr = InSplitIdentifier; - - UStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) - return nullptr; - - return NewStaticMesh; -} - -UHoudiniStaticMesh* -FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentifier) -{ - // Update the current Obj/Geo/Part/Split IDs - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.GeoId; - PackageParams.PartId = HGPO.PartId; - // Add _HSM suffix to the split str, to distinguish the temporary HoudiniStaticMesh - // from the UStaticMesh - PackageParams.SplitStr = InSplitIdentifier + "_HSM"; - - UHoudiniStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) - return nullptr; - - return NewStaticMesh; -} - -bool -FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() -{ - // Time limit for processing - bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; - - double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Prepare the object that will store UCX and simple colliders - AllAggregateCollisions.Empty(); - - // We need to know the number of LODs that will be needed for this part - int32 NumberOfLODs = 0; - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) - NumberOfLODs++; - else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - bHasMainGeo = true; - } - - // Update the part's material's IDS and info now - CreateNeededMaterials(); - - // Check now if they were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Get the current target platform for default lod policies - ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); - check(CurrentPlatform); - - // New mesh list - TMap StaticMeshToBuild; - - // Map of Houdini Material IDs to Unreal Material Interface - TMap MapHoudiniMatIdToUnrealInterface; - // Map of Houdini Material Attributes to Unreal Material Interface - TMap MapHoudiniMatAttributesToUnrealInterface; - // Map of Unreal Material Interface to Unreal Material Index, per visible mesh - TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; - - // bool MeshMaterialsHaveBeenReset = false; - - // Mesh Socket array - TArray AllSockets; - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - - double tick = FPlatformTime::Seconds(); - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre Split-Loop in %f seconds."), tick - time_start); - } - - UStaticMesh* MainStaticMesh = nullptr; - bool bAssignedCustomCollisionMesh = false; - ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; - - // Iterate through all detected split groups we care about and split geometry. - // The split are ordered in the following way: - // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - double split_tick = FPlatformTime::Seconds(); - - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], - OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - - // Get/Create the Aggregate Collisions for this mesh identifier - FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); - - // Handle UCX / Convex Hull colliders - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) - { - MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the convex hull colliders and add them to the Aggregate - if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) - continue; - } - else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) - { - MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the simple colliders and add them to the aggregate - if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) - continue; - } - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing SM from a previous cook - UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - // Prepare LOD Group data for this static mesh - FStaticMeshLODGroup LODGroup; - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing static mesh, create a new one - FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - - // Use the platform's default LODGroup policy - // TODO? Add setting for default LOD Group? - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); - } - else - { - // Try to reuse the existing SM's LOD group instead of the default one - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); - } - - if (SplitType == EHoudiniSplitType::Normal && !MainStaticMesh) - { - MainStaticMesh = FoundStaticMesh; - MainStaticMesh->ComplexCollisionMesh = nullptr; - MainStaticMesh->bCustomizedCollision = false; - // NOTE: The main static mesh collision trace flag will be set after all splits have been processed. - } - - if (!FoundOutputObject) - { - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - InputObjects.Remove(OutputObjectIdentifier); - } - else - { - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - FoundOutputObject->CachedAttributes.Empty(); - FoundOutputObject->CachedTokens.Empty(); - } - FoundOutputObject->bProxyIsCurrent = false; - - // TODO: Needed? - // Free any RHI resources for existing mesh before we re-create in place. - FoundStaticMesh->PreEditChange(NULL); - - // Check that the Static Mesh we found has the appropriate number of Source models/LODs - int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); - - // LODs are only for the "main" mesh, not for complex colliders! - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); - - if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) - { - while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) - FoundStaticMesh->AddSourceModel(); - - // We may have to remove excessive LOD levels - if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) - FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); - - // Initialize their default reduction setting - for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) - { - FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); - } - FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); - } - - // By default, always work on the first source model, unless we're a LOD - int32 SrcModelIndex = 0; - int32 LODIndex = 0; - if (SplitType == EHoudiniSplitType::LOD) - { - for (auto& curSplit : AllSplitGroups) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); - if (CurrentSplitType == EHoudiniSplitType::LOD - || CurrentSplitType == EHoudiniSplitType::Normal) - { - LODIndex++; - } - - if (curSplit == SplitGroupName) - break; - } - - // Fix for the case where we don't have a main geo - if(!bHasMainGeo) - LODIndex--; - } - - // Grab the appropriate SourceModel - FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; - if (!SrcModel) - { - HOUDINI_LOG_ERROR( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); - continue; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Load existing raw model. This will be empty as we are constructing a new mesh. - FRawMesh RawMesh; - if (!bRebuildStaticMesh) - { - // We dont need to rebuild the mesh itself: - // the geometry hasn't changed, but the materials have. - // We can just load the old data into the Raw mesh and reuse it. - SrcModel->LoadRawMesh(RawMesh); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - LoadRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - } - else - { - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's normal if needed - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - // Check that the number of normal we retrieved is correct - int32 WedgeNormalCount = SplitNormals.Num() / 3; - if (SplitNormals.Num() < 0 || !SplitNormals.IsValidIndex((WedgeNormalCount - 1) * 3 + 2)) - { - // Ignore normals - WedgeNormalCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); - } - - // Transfer the normals to the raw mesh - RawMesh.WedgeTangentZ.SetNumZeroed(WedgeNormalCount); - for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) - { - // Swap Y/Z for Coordinates conversion - RawMesh.WedgeTangentZ[WedgeTangentZIdx].X = SplitNormals[WedgeTangentZIdx * 3 + 0]; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].Y = SplitNormals[WedgeTangentZIdx * 3 + 2]; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].Z = SplitNormals[WedgeTangentZIdx * 3 + 1]; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Normals in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENTS - //--------------------------------------------------------------------------------------------------------------------- - - // No need to read the tangents if we want unreal to recompute them after - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - TArray< float > SplitTangentU; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - TArray< float > SplitTangentV; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - bool bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - - // Check that the number of tangents read matches the number of normals - int32 WedgeTangentUCount = SplitTangentU.Num() / 3; - int32 WedgeTangentVCount = SplitTangentV.Num() / 3; - if (WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount) - bGenerateTangents = true; - - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - - // Generate the tangents if needed - if (bGenerateTangents) - { - RawMesh.WedgeTangentX.SetNumZeroed(WedgeNormalCount); - RawMesh.WedgeTangentY.SetNumZeroed(WedgeNormalCount); - for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) - { - FVector TangentX, TangentY; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].FindBestAxisVectors(TangentX, TangentY); - - RawMesh.WedgeTangentX[WedgeTangentZIdx] = TangentX; - RawMesh.WedgeTangentY[WedgeTangentZIdx] = TangentY; - } - } - else - { - // Transfer the tangents we have read them and they're valid - RawMesh.WedgeTangentX.SetNumZeroed(WedgeTangentUCount); - for (int32 WedgeTangentUIdx = 0; WedgeTangentUIdx < WedgeTangentUCount; ++WedgeTangentUIdx) - { - // We need to flip Z and Y - RawMesh.WedgeTangentX[WedgeTangentUIdx].X = SplitTangentU[WedgeTangentUIdx * 3 + 0]; - RawMesh.WedgeTangentX[WedgeTangentUIdx].Y = SplitTangentU[WedgeTangentUIdx * 3 + 2]; - RawMesh.WedgeTangentX[WedgeTangentUIdx].Z = SplitTangentU[WedgeTangentUIdx * 3 + 1]; - } - - RawMesh.WedgeTangentY.SetNumZeroed(WedgeTangentVCount); - for (int32 WedgeTangentVIdx = 0; WedgeTangentVIdx < WedgeTangentVCount; ++WedgeTangentVIdx) - { - // We need to flip Z and Y - RawMesh.WedgeTangentY[WedgeTangentVIdx].X = SplitTangentV[WedgeTangentVIdx * 3 + 0]; - RawMesh.WedgeTangentY[WedgeTangentVIdx].Y = SplitTangentV[WedgeTangentVIdx * 3 + 2]; - RawMesh.WedgeTangentY[WedgeTangentVIdx].Z = SplitTangentV[WedgeTangentVIdx * 3 + 1]; - } - } - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Tangents in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // VERTEX COLORS AND ALPHAS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's colors if needed - UpdatePartColorsIfNeeded(); - - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract this part's alpha values if needed - UpdatePartAlphasIfNeeded(); - - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - - // Transfer colors and alphas if possible - int32 WedgeColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; - bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && WedgeColorsCount > 0; - bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == WedgeColorsCount); - if (bSplitColorValid) - { - RawMesh.WedgeColors.SetNumZeroed(WedgeColorsCount); - for (int32 WedgeColorIdx = 0; WedgeColorIdx < WedgeColorsCount; WedgeColorIdx++) - { - FLinearColor WedgeColor; - WedgeColor.R = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); - WedgeColor.G = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); - WedgeColor.B = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); - - if (bSplitAlphaValid) - { - // Use the Alpha attribute value - WedgeColor.A = FMath::Clamp(SplitAlphas[WedgeColorIdx], 0.0f, 1.0f); - } - else if (AttribInfoColors.tupleSize >= 4) - { - // Use the alpha value from the color attribute - WedgeColor.A = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); - } - else - { - WedgeColor.A = 1.0f; - } - - // Convert linear color to fixed color. - RawMesh.WedgeColors[WedgeColorIdx] = WedgeColor.ToFColor(false); - } - } - else - { - // TODO? Needed? New meshes wont have WedgeIndices yet!? - // No Colors or Alphas, init colors to White - FColor DefaultWedgeColor = FLinearColor::White.ToFColor(false); - WedgeColorsCount = RawMesh.WedgeIndices.Num(); - if (WedgeColorsCount > 0) - RawMesh.WedgeColors.Init(DefaultWedgeColor, WedgeColorsCount); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Cd and Alpha in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's FaceSmoothing values if needed - UpdatePartFaceSmoothingIfNeeded(); - - // Get the FaceSmoothing values for this split - TArray SplitFaceSmoothingMasks; - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); - - // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! - RawMesh.FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); - - // Check that the number of face smoothing values we retrieved is correct - int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; - if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) - { - // Ignore our face smoothing values - WedgeFaceSmoothCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); - } - - // Transfer the face smoothing masks to the raw mesh if we have any - for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) - { - RawMesh.FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - FaceSmoothing in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // UVS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's UV sets if needed - UpdatePartUVSetsIfNeeded(); - - // See if we need to transfer uv point attributes to vertex attributes. - TArray> SplitUVSets; - SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - } - - // Transfer UVs to the Raw Mesh - int32 UVChannelCount = 0; - int32 LightMapUVChannel = 0; - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; - - int32 WedgeUVCount = SplitUVs.Num() / 2; - if (SplitUVs.Num() > 0 && SplitUVs.IsValidIndex((WedgeUVCount - 1) * 2 + 1)) - { - RawMesh.WedgeTexCoords[TexCoordIdx].SetNumZeroed(WedgeUVCount); - for (int32 WedgeUVIdx = 0; WedgeUVIdx < WedgeUVCount; ++WedgeUVIdx) - { - // We need to flip V coordinate when it's coming from HAPI. - RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].X = SplitUVs[WedgeUVIdx * 2 + 0]; - RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].Y = 1.0f - SplitUVs[WedgeUVIdx * 2 + 1]; - } - - UVChannelCount++; - if (UVChannelCount <= 2) - LightMapUVChannel = TexCoordIdx; - } - else - { - RawMesh.WedgeTexCoords[TexCoordIdx].Empty(); - } - } - - // We must have at least one UV channel. If there's none, create one filled with zero data. - if (UVChannelCount == 0) - RawMesh.WedgeTexCoords[0].SetNumZeroed(SplitVertexCount); - - // Set the lightmap Coordinate Index - // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention - // If not, the first UV set will be used - FoundStaticMesh->LightMapCoordinateIndex = LightMapUVChannel; - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - UVs in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's LightmapResolution values if needed - UpdatePartLightmapResolutionsIfNeeded(); - - // make sure the mesh has a new lighting guid - FoundStaticMesh->LightingGuid = FGuid::NewGuid(); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Lightmap Resolutions in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" - // So that IndicesMapper[ oldIndex ] => newIndex - TArray IndicesMapper; - IndicesMapper.Init(-1, SplitVertexList.Num()); - int32 CurrentMapperIndex = 0; - - // NeededVertices: - // Array containing the old index of the needed vertices for the current split - // NeededVertices[ newIndex ] => oldIndex - TArray< int32 > NeededVertices; - RawMesh.WedgeIndices.SetNumZeroed(SplitVertexCount); - - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) - || !IndicesMapper.IsValidIndex(WedgeIndices[1]) - || !IndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (IndicesMapper[WedgeIndices[i]] < 0) - { - // This old index has not yet been "converted" to a new index - NeededVertices.Add(WedgeIndices[i]); - IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; - CurrentMapperIndex++; - } - - // Replace the old index with the new one - WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; - } - - if (!RawMesh.WedgeIndices.IsValidIndex(ValidVertexId + 2)) - break; - - // Flip wedge indices to fix the winding order. - RawMesh.WedgeIndices[ValidVertexId + 0] = WedgeIndices[0]; - RawMesh.WedgeIndices[ValidVertexId + 1] = WedgeIndices[2]; - RawMesh.WedgeIndices[ValidVertexId + 2] = WedgeIndices[1]; - - // Check if we need to patch UVs. - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - if (RawMesh.WedgeTexCoords[TexCoordIdx].IsValidIndex(ValidVertexId + 2)) - { - Swap(RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 1], - RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 2]); - } - } - - // Check if we need to patch colors. - if (RawMesh.WedgeColors.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeColors[ValidVertexId + 1], RawMesh.WedgeColors[ValidVertexId + 2]); - - // Check if we need to patch Normals and tangents. - if (RawMesh.WedgeTangentZ.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentZ[ValidVertexId + 1], RawMesh.WedgeTangentZ[ValidVertexId + 2]); - - if (RawMesh.WedgeTangentX.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentX[ValidVertexId + 1], RawMesh.WedgeTangentX[ValidVertexId + 2]); - - if (RawMesh.WedgeTangentY.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentY[ValidVertexId + 1], RawMesh.WedgeTangentY[ValidVertexId + 2]); - - ValidVertexId += 3; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - UpdatePartPositionIfNeeded(); - - // - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - int32 VertexPositionsCount = NeededVertices.Num(); - RawMesh.VertexPositions.SetNumZeroed(VertexPositionsCount); - - for (int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx) - { - int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; - if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // We need to swap Z and Y coordinate here, and convert from m to cm. - RawMesh.VertexPositions[VertexPositionIdx].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - RawMesh.VertexPositions[VertexPositionIdx].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - RawMesh.VertexPositions[VertexPositionIdx].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - /* - // TODO: - // Check if this mesh contains only degenerate triangles. - if (FHoudiniEngineUtils::CountDegenerateTriangles(RawMesh) == SplitGroupFaceCount) - { - // This mesh contains only degenerate triangles, there's nothing we can do. - if (bStaticMeshCreated) - StaticMesh->MarkPendingKill(); - - continue; - } - */ - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL ATTRIBUTE OVERRIDES - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: These are actually per faces, not per vertices... - // Need to update!! - UpdatePartFaceMaterialOverridesIfNeeded(); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Material Overrides in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Handle Materials!!!! - - // Get face indices for this split. - TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; - - // // We need to reset the Static Mesh's materials once per SM: - // // so, for the first lod, or the main geo... - // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) - // { - // FoundStaticMesh->StaticMaterials.Empty(); - // MeshMaterialsHaveBeenReset = true; - // } - // - // // .. or for each visible complex collider - // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - // FoundStaticMesh->StaticMaterials.Empty(); - - // Clear the materials array of the mesh the first time we encounter it - if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) - { - FoundStaticMesh->StaticMaterials.Empty(); - } - TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); - - // Process material overrides first - if (PartFaceMaterialOverrides.Num() > 0) - { - // Array used to avoid constantly attempting to load invalid materials - TArray InvalidMaterials; - - // If the part has material overrides - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - continue; - - UMaterialInterface * MaterialInterface = nullptr; - int32 CurrentFaceMaterialIdx = 0; - const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface) - { - // Try to locate the corresponding material interface - - // Start by looking in our assignment map - FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) - { - // Only try to load a material if has a chance to be valid! - MaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - - if (!MaterialInterface) - InvalidMaterials.Add(MaterialName); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); - } - else - { - // The Attribute Material and its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - if (!MaterialInterface) - { - // If everything fails, we'll use the default material - MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - } - } - - if (MaterialInterface) - { - int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); - } - - // Update the Face Material on the mesh - RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; - } - } - } - else if (PartUniqueMaterialIds.Num() > 0) - { - // The part has houdini materials - if (bOnlyOneFaceMaterial) - { - // We have only one material. - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - else - { - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - // Reset Rawmesh material face assignments. - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - UMaterialInterface* MaterialInterface = nullptr; - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (MaterialInterface) - { - int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; - } - } - else - { - MaterialInterface = Cast(DefaultMaterial); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - - if (MaterialInterface) - { - // Add the material to the Static mesh - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - - // Map the houdini ID to the unreal one - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); - - // Update the face index - RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; - } - } - } - } - else - { - // No materials were found, we need to use default Houdini material. - int32 SplitFaceCount = SplitFaceIndices.Num(); - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceCount); - - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Face Materials in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Update the Build Settings using the default setting values - UpdateMeshBuildSettings( - SrcModel->BuildSettings, - RawMesh.WedgeTangentZ.Num() > 0, - (RawMesh.WedgeTangentX.Num() > 0 && RawMesh.WedgeTangentY.Num() > 0), - RawMesh.WedgeTexCoords->Num() > 0); - - // Check for a lightmap resolution override - int32 LightMapResolutionOverride = -1; - if (PartLightMapResolutions.Num() > 0) - LightMapResolutionOverride = PartLightMapResolutions[0]; - - if (LightMapResolutionOverride > 0) - FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; - else - FoundStaticMesh->LightMapResolution = 64; - - // TODO - //StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; - //StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; - //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings; - - // TODO: - // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - - // This was required due to the impeding deprecation of FRawMesh - // If we dont update this UE4 will crash upon deleting an asset. - //SrcModel->StaticMeshOwner = FoundStaticMesh; - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreSaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Store the new raw mesh if it is valid - if (RawMesh.IsValid()) - { - SrcModel->SaveRawMesh(RawMesh); - } - else - { - HOUDINI_LOG_WARNING( - TEXT("[CreateStaticMesh_RawMesh]: Invalid StaticMesh data for %s LOD %i in cook output! Please check the log."), - *FoundStaticMesh->GetName(), LODIndex); - // Create an "empty" valid raw mesh (single zero-area triangle) - // TODO: is there a cleaner way to do this? Perhaps committing an empty mesh description? Empty RawMesh is - // a no-op on SrcModel->SaveRawMesh (leaves previous data in place). - // TODO: perhaps we can use an alternative "error" mesh? - RawMesh.Empty(); - RawMesh.VertexPositions.Add(FVector::ZeroVector); - RawMesh.WedgeIndices.SetNumZeroed(3); - RawMesh.WedgeTexCoords[0].Init(FVector2D::ZeroVector, RawMesh.WedgeIndices.Num()); - SrcModel->SaveRawMesh(RawMesh); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - SaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // NOTE: This Mesh Description patch causes crashes in certain situations and need to be revised. Until - // this patch has been properly fixed, we're just going to revert back to old (non-crashing) behaviour. - // if (IsValid(FoundStaticMesh)) - // { - // // Patch the MeshDescription data structure that is being output from SaveRawMesh. SaveRawMesh leaves invalid entries - // // in the PolyGroups array that causes issues later when the static mesh is built and LOD material assignments - // // are being done (materials aren't correctly assigned to LODs if LODs use different materials). - // FPolygonGroupArray& PolyGroups = SrcModel->MeshDescription->PolygonGroups(); - // TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = SrcModel->MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - // for(int32 MaterialIndex = 0; MaterialIndex < FoundStaticMesh->StaticMaterials.Num(); ++MaterialIndex) - // { - // FStaticMaterial& Material = FoundStaticMesh->StaticMaterials[MaterialIndex]; - // FPolygonGroupID PolygonGroupID(MaterialIndex); - // if (!PolyGroups.IsValid(PolygonGroupID)) - // { - // PolyGroups.Insert(PolygonGroupID); - // } - // PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = Material.MaterialSlotName; - // } - // } - - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = GetLODSCreensizeForSplit(SplitGroupName); - if (screensize >= 0.0f) - { - // Only apply the LOD screensize if it's valid - SrcModel->ScreenSize = screensize; - //FoundStaticMesh->GetSourceModel(LODIndex).ScreenSize = screensize; - FoundStaticMesh->bAutoComputeLODScreenSize = false; - } - - // TODO: - // SET STATIC MESH GENERATION PARAM - // HANDLE COLLIDERS - // REMOVE OLD COLLIDERS - // CUSTOM BAKE NAME OVERRIDE - - // Update property attributes on the SM - TArray PropertyAttributes; - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - HGPO.GeoId, HGPO.PartId, - true, - AllSplitFirstValidPrimIndex[SplitGroupName], - INDEX_NONE, - AllSplitFirstValidVertexIndex[SplitGroupName], - PropertyAttributes)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( - FoundStaticMesh, PropertyAttributes); - } - - TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - { - // cache the level path attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); - } - } - - TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - // cache the output name attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); - } - } - - TArray TileValues; - if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); - } - } - - TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) - { - if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); - } - } - - TArray BakeFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId)) - { - if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) - { - // cache the unreal_bake_folder attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); - } - } - - TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) - { - if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); - } - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Attributes in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Notify that we created a new Static Mesh if needed - if (bNewStaticMeshCreated) - FAssetRegistryModule::AssetCreated(FoundStaticMesh); - - // Add the Static mesh to the output maps and the build map if we haven't already - if (FoundOutputObject) - { - FoundOutputObject->OutputObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = false; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - - StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Total Split time: %f seconds."), tick - split_tick); - } - } - - // Look if we only have colliders - // If we do, we'll allow attaching sockets to the collider meshes - bool bCollidersOnly = true; - for (auto& Current : StaticMeshToBuild) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) - { - bCollidersOnly = false; - break; - } - } - - FHoudiniScopedGlobalSilence ScopedGlobalSilence; - for (auto& Current : StaticMeshToBuild) - { - tick = FPlatformTime::Seconds(); - - UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) - continue; - - UBodySetup * BodySetup = SM->BodySetup; - if (!BodySetup) - { - SM->CreateBodySetup(); - BodySetup = SM->BodySetup; - } - - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - - // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) - { - // Make sure rendering is done - so we are not changing data being used by collision drawing. - FlushRenderingCommands(); - - // Clean up old colliders from a previous cook - BodySetup->Modify(); - BodySetup->RemoveSimpleCollision(); - - FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; - FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); - if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) - { - BodySetup->AddCollisionFrom(*CurrentAggColl); - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; - } - - // See if we need to enable collisions on the whole mesh - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - // Complex collider, enable collisions for this (collider) static mesh. - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - - // Apply the collider to the Main static mesh, if relevant. - ApplyComplexColliderHelper( - MainStaticMesh, - SM, - SplitType, - bAssignedCustomCollisionMesh, - OutputObjects.Find(Current.Key)); - } - else - { - // TODO - // if the LODForCollision uproperty attribute is set, we need to activate complex collision - // on the static mesh for that lod to be picked up properly as a collider - if (FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, - "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) - { - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - } - } - - // Add the Sockets to the StaticMesh - // We only add them to the main geo, or to the colliders if we only generate colliders - bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; - if (bAddSocket) - { - if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); - } - } - - if (MainStaticMesh) - { - UBodySetup* MainBodySetup = MainStaticMesh->BodySetup; - if (!IsValid(MainBodySetup)) - { - MainStaticMesh->CreateBodySetup(); - MainBodySetup = MainStaticMesh->BodySetup; - } - - check(MainBodySetup); - // Set the main static mesh to whatever the final CTF should be. - MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // BUILD the Static Mesh - // bSilent doesnt add the Build Errors... - double build_start = FPlatformTime::Seconds(); - TArray SMBuildErrors; - SM->Build(true, &SMBuildErrors); - if (bDoTiming) - { - tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - StaticMesh->Build() executed in %f seconds."), tick - build_start); - } - - // This replaces the call to RefreshCollision below, but without CreateNavCollision - // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, - // and can be expensive depending on the vert/poly count of the mesh - // RefreshCollisionChange(*SM); - { - for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) - { - UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); - if (StaticMeshComponent->GetStaticMesh() == SM) - { - // it needs to recreate IF it already has been created - if (StaticMeshComponent->IsPhysicsStateCreated()) - { - StaticMeshComponent->RecreatePhysicsState(); - } - } - } - - FEditorSupportDelegates::RedrawAllViewports.Broadcast(); - } - - SM->GetOnMeshChanged().Broadcast(); - - UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); - } - } - - // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup - // Here as it has already been handled by the StaticMesh Build call - - double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() executed in %f seconds."), time_end - time_start); - - return true; -} - -bool -FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() -{ - // Time limit for processing - bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; - - double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - // Simple colliders first, lods and finally, invisible colliders (that are separate Static Mesh) - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Prepare the object that will store UCX and simple colliders - AllAggregateCollisions.Empty(); - - // We need to know the number of LODs that will be needed for this part - int32 NumberOfLODs = 0; - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) - NumberOfLODs++; - else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - bHasMainGeo = true; - } - - // Update the part's material's IDS and info now - CreateNeededMaterials(); - - // Check if the materials were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Get the current target platform for default lod policies - ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); - check(CurrentPlatform); - - // New mesh list - TMap StaticMeshToBuild; - - // Map of Houdini Material IDs to Unreal Material Interface - TMap MapHoudiniMatIdToUnrealInterface; - // Map of Houdini Material Attributes to Unreal Material Interface - TMap MapHoudiniMatAttributesToUnrealInterface; - // Map of Unreal Material Interface to Unreal Material Index, per visible mesh - TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; - - bool MeshMaterialsHaveBeenReset = false; - - // Mesh Socket array - TArray AllSockets; - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - HGPO.GeoId, HGPO.PartId, AllSockets, HGPO.PartInfo.bIsInstanced); - - double tick = FPlatformTime::Seconds(); - if (bDoTiming) - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); - - UStaticMesh* MainStaticMesh = nullptr; - bool bAssignedCustomCollisionMesh = false; - ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; - - // Iterate through all detected split groups we care about and split geometry. - // The split are ordered in the following way: - // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - double split_tick = FPlatformTime::Seconds(); - - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], - OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - - // Get/Create the Aggregate Collisions for this mesh identifier - FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); - - // Handle UCX / Convex Hull colliders - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) - { - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the convex hull colliders and add them to the Aggregate - if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) - continue; - } - else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) - { - MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the simple colliders and add them to the aggregate - if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) - continue; - } - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing SM from a previous cook - UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - // Prepare LOD Group data for this static mesh - FStaticMeshLODGroup LODGroup; - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing static mesh, create a new one - FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - - // Use the platform's default LODGroup policy - // TODO? Add setting for default LOD Group? - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); - } - else - { - // Try to reuse the existing SM's LOD group instead of the default one - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); - } - - if (SplitType == EHoudiniSplitType::Normal) - { - MainStaticMesh = FoundStaticMesh; - MainStaticMesh->ComplexCollisionMesh = nullptr; - MainStaticMesh->bCustomizedCollision = false; - } - - if (!FoundOutputObject) - { - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - } - else - { - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - FoundOutputObject->CachedAttributes.Empty(); - FoundOutputObject->CachedTokens.Empty(); - } - FoundOutputObject->bProxyIsCurrent = false; - - // TODO: Needed? - // Free any RHI resources for existing mesh before we re-create in place. - FoundStaticMesh->PreEditChange(NULL); - - // Check that the Static Mesh we found has the appropriate number of Source models/LODs - int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); - - // LODs are only for the "main" mesh, not for complex colliders! - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); - - if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) - { - while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) - FoundStaticMesh->AddSourceModel(); - - // We may have to remove excessive LOD levels - if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) - FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); - - // Initialize their default reduction setting - for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) - { - FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); - } - FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); - } - - // By default, always work on the first source model, unless we're a LOD - int32 SrcModelIndex = 0; - int32 LODIndex = 0; - if (SplitType == EHoudiniSplitType::LOD) - { - for (auto& curSplit : AllSplitGroups) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); - if (CurrentSplitType == EHoudiniSplitType::LOD - || CurrentSplitType == EHoudiniSplitType::Normal) - { - LODIndex++; - } - - if (curSplit == SplitGroupName) - break; - } - - // Fix for the case where we don't have a main geo - if(!bHasMainGeo) - LODIndex--; - } - - // Grab the appropriate SourceModel - FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; - if (!SrcModel) - { - HOUDINI_LOG_ERROR( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); - continue; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - bool bHasNormal = false; - bool bHasTangents = false; - - // Load the existing mesh description if we don't need to rebuild the mesh - FMeshDescription* MeshDescription; - if (!bRebuildStaticMesh) - { - // We dont need to rebuild the mesh itself: - // the geometry hasn't changed, but the materials have. - // We can just reuse the old MeshDescription and reuse it. - MeshDescription = FoundStaticMesh->GetMeshDescription(LODIndex); - } - else - { - // Extract all the data needed for this split - // Start by initializing the MeshDescription for this LOD - MeshDescription = FoundStaticMesh->CreateMeshDescription(LODIndex); - FStaticMeshAttributes(*MeshDescription).Register(); - - // Mesh description uses material to create its PolygonGroups, - // so we first need to know how many different materials we have for this split - // and what vertices/indices belong to each material for remapping - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // SplitNeededVertices - // Array containing the (unique) part indices for the vertices that are needed for this split - // SplitNeededVertices[splitIndex] = PartIndex - TArray SplitNeededVertices; - //SplitNeededVertices.SetNumZeroed(SplitVertexCount); - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" so that IndicesMapper[ partIndex ] => splitIndex - TArray PartToSplitIndicesMapper; - PartToSplitIndicesMapper.Init(-1, SplitVertexList.Num()); - //TMap SplitToPartIndicesMapper; - - // SplitIndices - // Array of SplitIndices used to describe this split's polygons - TArray SplitIndices; - SplitIndices.SetNumZeroed(SplitVertexCount); - - int32 CurrentSplitIndex = 0; - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[0]) - || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[1]) - || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (PartToSplitIndicesMapper[WedgeIndices[i]] < 0) - { - // This part index has not yet been "converted" to a new split index - SplitNeededVertices.Add(WedgeIndices[i]); - PartToSplitIndicesMapper[WedgeIndices[i]] = CurrentSplitIndex; - //SplitToPartIndicesMapper.Add(CurrentSplitIndex, WedgeIndices[i]); - CurrentSplitIndex++; - } - - // Replace the old part index with the new split index - WedgeIndices[i] = PartToSplitIndicesMapper[WedgeIndices[i]]; - } - - if (!SplitIndices.IsValidIndex(ValidVertexId + 2)) - break; - - // Flip wedge indices to fix the winding order. - SplitIndices[ValidVertexId + 0] = WedgeIndices[0]; - SplitIndices[ValidVertexId + 1] = WedgeIndices[2]; - SplitIndices[ValidVertexId + 2] = WedgeIndices[1]; - - ValidVertexId += 3; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract position for this part - UpdatePartPositionIfNeeded(); - - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - TVertexAttributesRef VertexPositions = - MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - - MeshDescription->ReserveNewVertices(SplitNeededVertices.Num()); - for ( const int32& NeededVertexIndex : SplitNeededVertices) - { - // Create a new Vertex - FVertexID VertexID = MeshDescription->CreateVertex(); - if (PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexPositions[VertexID].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexPositions[VertexID].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexPositions[VertexID].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - else - { - // Error when retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // // TODO: Check if still needed for MeshDescription - // // We need to reset the Static Mesh's materials once per SM: - // // so, for the first lod, or the main geo... - // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) - // { - // FoundStaticMesh->StaticMaterials.Empty(); - // MeshMaterialsHaveBeenReset = true; - // } - // - // // .. or for each visible complex collider - // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - // FoundStaticMesh->StaticMaterials.Empty(); - - // Clear the materials array of the mesh the first time we encounter it - if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) - { - FoundStaticMesh->StaticMaterials.Empty(); - } - TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); - - // Get this split's faces - TArray& SplitGroupFaceIndices = AllSplitFaceIndices[SplitGroupName]; - // Array holding the materials needed for this split - //TArray SplitMaterials; - // Split Material indices per face, by default all faces are set to use the first Material - TArray SplitFaceMaterialIndices; - SplitFaceMaterialIndices.SetNumZeroed(SplitGroupFaceIndices.Num()); - - bool HasHoudiniMaterials = PartUniqueMaterialIds.Num() > 0; - bool HasMaterialOverrides = PartFaceMaterialOverrides.Num() > 0; - if (!HasHoudiniMaterials && !HasMaterialOverrides) - { - // We don't have any material override or houdini material - // we just need one polygon group using the default Houdini material. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // TODO: ? Add default mat to the assignement map? - } - else if (HasHoudiniMaterials && !HasMaterialOverrides) - { - // We have Houdini Material but no overrides - if (bOnlyOneFaceMaterial || PartUniqueMaterialIds.Num() == 1) - { - // We have only one Houdini material. - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // TODO: ? Add the mat to the assignement map? - } - else - { - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * MaterialDefault = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - // Reset Rawmesh material face assignments. - for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - UMaterialInterface* MaterialInterface = nullptr; - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (MaterialInterface) - { - int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; - } - } - else - { - MaterialInterface = Cast(MaterialDefault); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - - if (MaterialInterface) - { - // Add the material to the Static mesh - //int32 UnrealMatIndex = SplitMaterials.Add(Material); - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // Map the houdini ID to the unreal one - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); - - // Update the face index - SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; - } - } - } - } - else - { - // Array used to avoid constantly attempting to load invalid materials - TArray InvalidMaterials; - - // If we have material overrides - for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; - - UMaterialInterface * MaterialInterface = nullptr; - int32 CurrentFaceMaterialIdx = -1; - if (PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - { - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface) - { - // Try to locate the corresponding material interface - - // Start by looking in our assignment map - FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) - { - // Only try to load a material if has a chance to be valid! - MaterialInterface = Cast< UMaterialInterface >( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - - if (!MaterialInterface) - InvalidMaterials.Add(MaterialName); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add this material to the map - MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); - } - } - - if (!MaterialInterface) - { - // The attribute Material or its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface) - { - // If everything else fails, we'll use the default material - MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - } - } - - int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); - } - - // Update the Face Material on the mesh - SplitFaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; - } - } - - // Create a Polygon Group for each material slot - TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = - MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - - // We must use the number of assignment materials found to reserve the number of material slots - // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials - int32 NumberOfMaterials = OutputAssignmentMaterials.Num(); - if (NumberOfMaterials <= 0) - { - // No materials, create a polygon group for the default one - const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - } - else - { - MeshDescription->ReserveNewPolygonGroups(NumberOfMaterials); - //for (int32 MatIndex = 0; MatIndex < NumberOfMaterials; ++MatIndex) - for (auto& CurrentMatAssignement : OutputAssignmentMaterials) - { - const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = - FName(CurrentMatAssignement.Value ? *(CurrentMatAssignement.Value->GetName()) : *(CurrentMatAssignement.Key)); - } - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // - // VERTEX INSTANCE ATTRIBUTES - // NORMALS, TANGENTS, COLORS, UVS, Alpha - // - - // Extract the normals - UpdatePartNormalsIfNeeded(); - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - - // No need to read the tangents if we want unreal to recompute them after - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; - - // Extract the tangents - TArray SplitTangentU; - TArray SplitTangentV; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - int32 NormalCount = SplitNormals.Num(); - bool bGenerateTangents = (NormalCount > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - // Check that the number of tangents read matches the number of normals - if (SplitTangentU.Num() != NormalCount || SplitTangentV.Num() != NormalCount) - bGenerateTangents = true; - - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - - // Generate the tangents if needed - if (bGenerateTangents) - { - SplitTangentU.SetNumZeroed(NormalCount); - SplitTangentV.SetNumZeroed(NormalCount); - for (int32 Idx = 0; Idx + 2 < NormalCount; Idx += 3) - { - FVector TangentZ; - TangentZ.X = SplitNormals[Idx + 0]; - TangentZ.Y = SplitNormals[Idx + 2]; - TangentZ.Z = SplitNormals[Idx + 1]; - - FVector TangentX, TangentY; - TangentZ.FindBestAxisVectors(TangentX, TangentY); - - SplitTangentU[Idx + 0] = TangentX.X; - SplitTangentU[Idx + 2] = TangentX.Y; - SplitTangentU[Idx + 1] = TangentX.Z; - - SplitTangentV[Idx + 0] = TangentY.X; - SplitTangentV[Idx + 2] = TangentY.Y; - SplitTangentV[Idx + 1] = TangentY.Z; - } - } - } - TVertexInstanceAttributesRef VertexInstanceTangents = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - - // Extract the color values - UpdatePartColorsIfNeeded(); - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract the alpha values - UpdatePartAlphasIfNeeded(); - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - TVertexInstanceAttributesRef VertexInstanceColors = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - - // Extract UVs - UpdatePartUVSetsIfNeeded(true); - // See if we need to transfer uv point attributes to vertex attributes. - int32 UVSetCount = PartUVSets.Num(); - TArray> SplitUVSets; - SplitUVSets.SetNum(UVSetCount); - for (int32 TexCoordIdx = 0; TexCoordIdx < UVSetCount; TexCoordIdx++) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - } - TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - VertexInstanceUVs.SetNumIndices(UVSetCount); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Allocate space for the vertex instances and polygons - MeshDescription->ReserveNewVertexInstances(SplitIndices.Num()); - MeshDescription->ReserveNewPolygons(SplitIndices.Num() / 3); - //Approximately 2.5 edges per polygons - MeshDescription->ReserveNewEdges(SplitIndices.Num() * 2.5f / 3); - - bHasNormal = SplitNormals.Num() > 0; - bHasTangents = SplitTangentU.Num() > 0 && SplitTangentV.Num() > 0; - bool bHasRGB = SplitColors.Num() > 0; - bool bHasRGBA = bHasRGB && AttribInfoColors.tupleSize == 4; - bool bHasAlpha = SplitAlphas.Num() > 0; - - TArray HasUVSets; - HasUVSets.SetNumZeroed(PartUVSets.Num()); - for (int32 Idx = 0; Idx < PartUVSets.Num(); Idx++) - HasUVSets[Idx] = PartUVSets[Idx].Num() > 0; - - uint32 FaceCount = SplitIndices.Num() / 3; - for (uint32 FaceIndex = 0; FaceIndex < FaceCount; FaceIndex++) - { - TArray FaceVertexInstanceIDs; - FaceVertexInstanceIDs.SetNum(3); - - // Ignore degenerate triangles - FVertexID VertexIDs[3]; - for (int32 Corner = 0; Corner < 3; ++Corner) - { - VertexIDs[Corner] = FVertexID(SplitIndices[(FaceIndex * 3) + Corner]); - } - if (VertexIDs[0] == VertexIDs[1] || VertexIDs[0] == VertexIDs[2] || VertexIDs[1] == VertexIDs[2]) - continue; - - //FVertexID FaceVertexIDs[3]; - for (int32 Corner = 0; Corner < 3; Corner++) - { - uint32 SplitIndex = (FaceIndex * 3) + Corner; - uint32 SplitVertexIndex = SplitIndices[SplitIndex]; - const FVertexInstanceID& VertexInstanceID = MeshDescription->CreateVertexInstance(FVertexID(SplitVertexIndex)); - - // Fix the winding order by updating the SplitIndex (invert corner 1 and 2) - // instead of going 0 1 2 go 0 2 1 - // TODO; this slows down StaticMesh->Build() considerably! - Corner == 1 ? SplitIndex++ : Corner == 2 ? SplitIndex-- : SplitIndex; - - const uint32 SplitVertexIndex_X = SplitIndex * 3 + 0; - const uint32 SplitVertexIndex_Y = SplitIndex * 3 + 2; - const uint32 SplitVertexIndex_Z = SplitIndex * 3 + 1; - // Normals - if (bHasNormal) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexInstanceNormals[VertexInstanceID].X = SplitNormals[SplitVertexIndex_X]; - VertexInstanceNormals[VertexInstanceID].Y = SplitNormals[SplitVertexIndex_Y]; - VertexInstanceNormals[VertexInstanceID].Z = SplitNormals[SplitVertexIndex_Z]; - } - - // Tangents and binormals - if (bHasTangents) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexInstanceTangents[VertexInstanceID].X = SplitTangentU[SplitVertexIndex_X]; - VertexInstanceTangents[VertexInstanceID].Y = SplitTangentU[SplitVertexIndex_Y]; - VertexInstanceTangents[VertexInstanceID].Z = SplitTangentU[SplitVertexIndex_Z]; - - FVector TangentY; - TangentY.X = SplitTangentV[SplitVertexIndex_X]; - TangentY.Y = SplitTangentV[SplitVertexIndex_Y]; - TangentY.Z = SplitTangentV[SplitVertexIndex_Z]; - - VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign( - VertexInstanceTangents[VertexInstanceID].GetSafeNormal(), - TangentY.GetSafeNormal(), - VertexInstanceNormals[VertexInstanceID].GetSafeNormal()); - } - - // Color - FLinearColor Color = FLinearColor::White; - if (bHasRGB) - { - Color.R = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); - Color.G = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); - Color.B = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); - } - // Alpha - if (bHasAlpha) - { - Color.A = FMath::Clamp(SplitAlphas[SplitIndex], 0.0f, 1.0f); - } - else if (bHasRGBA) - { - Color.A = FMath::Clamp(SplitColors[SplitIndex * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); - } - VertexInstanceColors[VertexInstanceID] = FVector4(Color); - - // UVs - for (int32 UVIndex = 0; UVIndex < SplitUVSets.Num(); UVIndex++) - { - if (HasUVSets[UVIndex]) - { - // We need to flip V coordinate when it's coming from HAPI. - FVector2D CurrentUV; - CurrentUV.X = SplitUVSets[UVIndex][SplitIndex * 2 + 0]; - CurrentUV.Y = 1.0f - SplitUVSets[UVIndex][SplitIndex * 2 + 1]; - - VertexInstanceUVs.Set(VertexInstanceID, UVIndex, CurrentUV); - } - } - - FaceVertexInstanceIDs[Corner] = VertexInstanceID; - } - - const FPolygonGroupID PolygonGroupID(SplitFaceMaterialIndices[FaceIndex]); - - // Insert a triangle into the mesh - MeshDescription->CreateTriangle(PolygonGroupID, FaceVertexInstanceIDs); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's FaceSmoothing values if needed - UpdatePartFaceSmoothingIfNeeded(); - - // Get the FaceSmoothing values for this split - TArray SplitFaceSmoothingMasks; - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); - - // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! - // TODO: Expose the default FaceSmoothing value - // 0 will make hard face - TArray FaceSmoothingMasks; - FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); - - // Check that the number of face smoothing values we retrieved is correct - int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; - if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) - { - // Ignore our face smoothing values - WedgeFaceSmoothCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); - } - - // Transfer the face smoothing masks to the raw mesh if we have any - for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) - { - FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; - } - - // TODO - // Check - FStaticMeshOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, *MeshDescription); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - // Extract this part's LightmapResolution values if needed - UpdatePartLightmapResolutionsIfNeeded(); - - // make sure the mesh has a new lighting guid - FoundStaticMesh->LightingGuid = FGuid::NewGuid(); - } - - // Update the Build Settings using the default setting values - UpdateMeshBuildSettings( - SrcModel->BuildSettings, - bHasNormal, - bHasTangents, - PartUVSets.Num() > 0); - - // Set the lightmap Coordinate Index - // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention - FoundStaticMesh->LightMapCoordinateIndex = PartUVSets.Num() > 1 ? 1 : 0; - - // Check for a lightmapa resolution override - int32 LightMapResolutionOverride = -1; - if ( PartLightMapResolutions.Num() > 0) - LightMapResolutionOverride = PartLightMapResolutions[0]; - - if (LightMapResolutionOverride > 0) - FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; - else - FoundStaticMesh->LightMapResolution = 64; - - // TODO: - // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - - // RAW MESH CHECKS - - // TODO: Check not needed w/ FMeshDesc - // This is required due to the impeding deprecation of FRawMesh - // If we dont update this UE4 will crash upon deleting an asset. - //SrcModel->StaticMeshOwner = FoundStaticMesh; - - // Check if the mesh has at least one triangle, if not, log a message - if (MeshDescription->Triangles().Num() == 0) - { - HOUDINI_LOG_WARNING( - TEXT("[CreateStaticMesh_MeshDescription]: 0 valid triangles in StaticMesh data for %s LOD %i! Please check the log."), - *FoundStaticMesh->GetName(), LODIndex); - } - - // Store the new MeshDescription - FoundStaticMesh->CommitMeshDescription(LODIndex); - //Set the Imported version before calling the build - FoundStaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; - - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = GetLODSCreensizeForSplit(SplitGroupName); - if (screensize >= 0.0f) - { - // Only apply the LOD screensize if it's valid - SrcModel->ScreenSize = screensize; - //FoundStaticMesh->GetSourceModel(LodIndex).ScreenSize = screensize; - FoundStaticMesh->bAutoComputeLODScreenSize = false; - } - - // SET STATIC MESH GENERATION PARAM - // HANDLE COLLIDERS - // REMOVE OLD COLLIDERS - // CUSTOM BAKE NAME OVERRIDE - - // UPDATE UPROPERTY ATTRIBS - // Update property attributes on the SM - TArray PropertyAttributes; - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - HGPO.GeoId, HGPO.PartId, - true, - AllSplitFirstValidPrimIndex[SplitGroupName], - INDEX_NONE, - AllSplitFirstValidVertexIndex[SplitGroupName], - PropertyAttributes)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( - FoundStaticMesh, PropertyAttributes); - } - - TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - { - // cache the level path attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); - } - } - - TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - // cache the output name attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); - } - } - - TArray TileValues; - if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); - } - } - - TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) - { - if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); - } - } - - TArray BakeFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId)) - { - if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) - { - // cache the unreal_bake_folder attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); - } - } - - TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) - { - if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); - } - } - - // Notify that we created a new Static Mesh if needed - if(bNewStaticMeshCreated) - FAssetRegistryModule::AssetCreated(FoundStaticMesh); - - // Add the Static mesh to the output maps and the build map if we haven't already - if (FoundOutputObject) - { - FoundOutputObject->OutputObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = false; - FoundOutputObject->bIsImplicit = false; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - - StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Total Split time: %f seconds."), tick - split_tick); - } - } - - // Look if we only have colliders - // If we do, we'll allow attaching sockets to the collider meshes - bool bCollidersOnly = true; - for (auto& Current : StaticMeshToBuild) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) - { - bCollidersOnly = false; - break; - } - } - - FHoudiniScopedGlobalSilence ScopedGlobalSilence; - for (auto& Current : StaticMeshToBuild) - { - tick = FPlatformTime::Seconds(); - - UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) - continue; - - UBodySetup * BodySetup = SM->BodySetup; - if (!BodySetup) - { - SM->CreateBodySetup(); - BodySetup = SM->BodySetup; - } - - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - - // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) - { - // Make sure rendering is done - so we are not changing data being used by collision drawing. - FlushRenderingCommands(); - - // Clean up old colliders from a previous cook - BodySetup->Modify(); - BodySetup->RemoveSimpleCollision(); - // Create new GUID - BodySetup->InvalidatePhysicsData(); - - FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; - FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); - if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) - { - BodySetup->AddCollisionFrom(*CurrentAggColl); - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; - } - - // Moved RefreshCollisionChange to after the SM->Build call - // RefreshCollisionChange(*SM); - // SM->bCustomizedCollision = true; - - // See if we need to enable collisions on the whole mesh - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - // Complex collider, enable collisions for this static mesh. - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - ApplyComplexColliderHelper( - MainStaticMesh, - SM, - SplitType, - bAssignedCustomCollisionMesh, - OutputObjects.Find(Current.Key)); - } - else - { - // TODO - // if the LODForCollision uproperty attribute is set, we need to activate complex collision - // on the static mesh for that lod to be picked up properly as a collider - if ( FHoudiniEngineUtils::HapiCheckAttributeExists( HGPO.GeoId, HGPO.PartId, - "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) - { - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - } - } - - // Add the Sockets to the StaticMesh - // We only add them to the main geo, or to the colliders if we only generate colliders - bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; - if (bAddSocket) - { - if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, AllSockets, true)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); - } - } - - if (MainStaticMesh) - { - UBodySetup* MainBodySetup = MainStaticMesh->BodySetup; - if (!IsValid(MainBodySetup)) - { - MainStaticMesh->CreateBodySetup(); - MainBodySetup = MainStaticMesh->BodySetup; - } - - check(MainBodySetup); - // Set the main static mesh to whatever the final CTF should be. - MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; - } - - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // BUILD the Static Mesh - // bSilent doesnt add the Build Errors... - double build_start = FPlatformTime::Seconds(); - TArray SMBuildErrors; - SM->Build(true, &SMBuildErrors); - - if (bDoTiming) - { - tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - StaticMesh->Build() executed in %f seconds."), tick - build_start); - } - - // This replaces the call to RefreshCollision below, but without CreateNavCollision - // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, - // and can be expensive depending on the vert/poly count of the mesh - // RefreshCollisionChange(*SM); - { - for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) - { - UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); - if (StaticMeshComponent->GetStaticMesh() == SM) - { - // it needs to recreate IF it already has been created - if (StaticMeshComponent->IsPhysicsStateCreated()) - { - StaticMeshComponent->RecreatePhysicsState(); - } - } - } - - FEditorSupportDelegates::RedrawAllViewports.Broadcast(); - } - - SM->GetOnMeshChanged().Broadcast(); - - UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); - } - } - - // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup - // Here as it has already been handled by the StaticMesh Build call - - double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() executed in %f seconds."), time_end - time_start); - - return true; -} - -bool -FHoudiniMeshTranslator::CreateHoudiniStaticMesh() -{ - // Time limit for processing - bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; - - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh")); - - const double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Determine if there is "main" geo, if not we'll use the first LOD - // as main geo - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - { - bHasMainGeo = true; - break; - } - } - - // Update the part's material's IDS and info now - //UpdatePartFaceMaterialsIfNeeded(); - CreateNeededMaterials(); - - // Check if the materials were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Map of Houdini Material IDs to Unreal Material Interface - TMap MapHoudiniMatIdToUnrealInterface; - // Map of Houdini Material Attributes to Unreal Material Interface - TMap MapHoudiniMatAttributesToUnrealInterface; - // Map of Unreal Material Interface to Unreal Material Index, per visible mesh - TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; - - // bool MeshMaterialsHaveBeenReset = false; - - double tick = FPlatformTime::Seconds(); - if(bDoTiming) - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); - - // Iterate through all detected split groups we care about and split geometry. - bool bMainGeoOrFirstLODFound = false; - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Per Split")); - - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // We are only interested in the Normal/main geo and visible colliders - if (SplitType != EHoudiniSplitType::Normal && - SplitType != EHoudiniSplitType::LOD && - SplitType != EHoudiniSplitType::RenderedComplexCollider && - SplitType != EHoudiniSplitType::RenderedSimpleCollider && - SplitType != EHoudiniSplitType::RenderedUCXCollider) - { - continue; - } - - // We only use LOD if there is no Normal geo - if (SplitType == EHoudiniSplitType::Normal) - { - bMainGeoOrFirstLODFound = true; - HOUDINI_LOG_MESSAGE(TEXT("Found Normal geo for mesh.")); - } - else if (SplitType == EHoudiniSplitType::LOD) - { - if (bHasMainGeo) - { - HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since the mesh has Normal geo.")); - continue; - } - else if (bMainGeoOrFirstLODFound) - { - HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since we have already processed the first LOD.")); - continue; - } - else - { - bMainGeoOrFirstLODFound = true; - HOUDINI_LOG_MESSAGE(TEXT("Mesh does not have Normal geo, found first LOD.")); - } - } - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName]; - OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing DM from a previous cook - UHoudiniStaticMesh* FoundStaticMesh = FindExistingHoudiniStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing dynamic mesh, create a new one - FoundStaticMesh = CreateNewHoudiniStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - } - - if (!FoundOutputObject) - { - // If we couldnt find a previous output object, create a new one - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - } - FoundOutputObject->bProxyIsCurrent = true; - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - if (bRebuildStaticMesh) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build/Rebuild UHoudiniStaticMesh")); - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" - // So that IndicesMapper[ oldIndex ] => newIndex - TArray IndicesMapper; - IndicesMapper.Init(-1, SplitVertexList.Num()); - int32 CurrentMapperIndex = 0; - - // NeededVertices: - // Array containing the old index of the needed vertices for the current split - // NeededVertices[ newIndex ] => oldIndex - TArray< int32 > NeededVertices; - NeededVertices.Reserve(SplitVertexList.Num() / 3); - TArray< int32 > TriangleIndices; - TriangleIndices.Reserve(SplitVertexList.Num()); - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build IndicesMapper and NeededVertices")); - - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) - || !IndicesMapper.IsValidIndex(WedgeIndices[1]) - || !IndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (IndicesMapper[WedgeIndices[i]] < 0) - { - // This old index has not yet been "converted" to a new index - NeededVertices.Add(WedgeIndices[i]); - IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; - CurrentMapperIndex++; - } - - // Replace the old index with the new one - WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; - } - - // Flip wedge indices to fix the winding order. - TriangleIndices.Add(WedgeIndices[0]); - TriangleIndices.Add(WedgeIndices[2]); - TriangleIndices.Add(WedgeIndices[1]); - - ValidVertexId += 3; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's normal if needed - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - // Check that the number of normal we retrieved is correct - int32 NormalCount = SplitNormals.Num() / 3; - if (NormalCount < 0 || NormalCount < NeededVertices.Num()) - { - // Ignore normals - NormalCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENTS - //--------------------------------------------------------------------------------------------------------------------- - - TArray SplitTangentU; - TArray SplitTangentV; - int32 TangentUCount = 0; - int32 TangentVCount = 0; - // No need to read the tangents if we want unreal to recompute them after - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; - - bool bGenerateTangents = bReadTangents; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - - // Check that the number of tangents read matches the number of normals - TangentUCount = SplitTangentU.Num() / 3; - TangentVCount = SplitTangentV.Num() / 3; - if (TangentUCount != NormalCount || TangentVCount != NormalCount) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh: Generate tangents due to count mismatch (# U Tangents = %d; # V Tangents = %d; # Normals = %d)"), TangentUCount, TangentVCount, NormalCount); - bGenerateTangents = true; - } - - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // VERTEX COLORS AND ALPHAS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's colors if needed - UpdatePartColorsIfNeeded(); - - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract this part's alpha values if needed - UpdatePartAlphasIfNeeded(); - - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - - const int32 ColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; - const bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && ColorsCount > 0; - const bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == ColorsCount); - - //--------------------------------------------------------------------------------------------------------------------- - // UVS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's UV sets if needed - UpdatePartUVSetsIfNeeded(); - - // See if we need to transfer uv point attributes to vertex attributes. - int32 NumUVLayers = 0; - TArray> SplitUVSets; - SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - if (SplitUVSets[TexCoordIdx].Num() > 0) - { - NumUVLayers++; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL ATTRIBUTE OVERRIDES - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: These are actually per faces, not per vertices... - // Need to update!! - UpdatePartFaceMaterialOverridesIfNeeded(); - - // - // Initialize mesh - // - const int32 NumVertexPositions = NeededVertices.Num(); - const int32 NumTriangles = TriangleIndices.Num() / 3; - const bool bHasPerFaceMaterials = PartFaceMaterialOverrides.Num() > 0 || (PartUniqueMaterialIds.Num() > 0 && !bOnlyOneFaceMaterial); - - FoundStaticMesh->Initialize( - NumVertexPositions, - NumTriangles, - NumUVLayers, // NumUVLayers - 0, // InitialNumStaticMaterials - NormalCount > 0, // HasNormals - NormalCount > 0 && bReadTangents, // HasTangents - bSplitColorValid, // HasColors - bHasPerFaceMaterials // HasPerFaceMaterials - ); - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - UpdatePartPositionIfNeeded(); - - // - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Vertex Positions")); - - for (int32 VertexPositionIdx = 0; VertexPositionIdx < NumVertexPositions; ++VertexPositionIdx) - //ParallelFor(NumVertexPositions, [&](uint32 VertexPositionIdx) - { - int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; - if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // We need to swap Z and Y coordinate here, and convert from m to cm. - FoundStaticMesh->SetVertexPosition(VertexPositionIdx, FVector( - PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION, - PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION, - PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION - )); - }//); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACES / TRIS - // Now set Normals, UVs and Colors on mesh points and AttributeSet - //--------------------------------------------------------------------------------------------------------------------- - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Triangle Indices & Per Vertex Instance Attribute Values")); - - // Now add the triangles to the mesh - for (int32 TriangleIdx = 0; TriangleIdx < NumTriangles; ++TriangleIdx) - // ParallelFor(NumTriangles, [&](uint32 TriangleIdx) - { - - const int32 TriVertIdx0 = TriangleIdx * 3; - FoundStaticMesh->SetTriangleVertexIndices(TriangleIdx, FIntVector( - TriangleIndices[TriVertIdx0 + 0], - TriangleIndices[TriVertIdx0 + 1], - TriangleIndices[TriVertIdx0 + 2] - )); - - const int32 TriWindingIndex[3] = { 0, 2, 1 }; - if (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)) - { - // Flip Z and Y coordinate for normal, but don't scale - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - const FVector Normal( - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] - ); - - FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); - - if (bReadTangents) - { - FVector TangentU, TangentV; - if (bGenerateTangents) - { - // Generate the tangents if needed - Normal.FindBestAxisVectors(TangentU, TangentV); - } - else - { - // Transfer the tangents from Houdini - TangentU.X = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; - TangentU.Y = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; - TangentU.Z = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; - - TangentU.X = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; - TangentU.Y = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; - TangentU.Z = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; - } - - FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); - FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); - } - } - } - - if (bSplitColorValid && SplitColors.IsValidIndex(TriVertIdx0 * AttribInfoColors.tupleSize + 3 * AttribInfoColors.tupleSize - 1)) - { - FLinearColor VertexLinearColor; - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - VertexLinearColor.R = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 0], 0.0f, 1.0f); - VertexLinearColor.G = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 1], 0.0f, 1.0f); - VertexLinearColor.B = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 2], 0.0f, 1.0f); - - if (bSplitAlphaValid) - { - VertexLinearColor.A = FMath::Clamp(SplitAlphas[TriVertIdx0 + ElementIdx], 0.0f, 1.0f); - } - else if (AttribInfoColors.tupleSize >= 4) - { - VertexLinearColor.A = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 3], 0.0f, 1.0f); - } - else - { - VertexLinearColor.A = 1.0f; - } - const FColor VertexColor = VertexLinearColor.ToFColor(false); - FoundStaticMesh->SetTriangleVertexColor(TriangleIdx, TriWindingIndex[ElementIdx], VertexColor); - } - } - - if (NumUVLayers > 0) - { - // Dynamic mesh supports only 1 UV layer on the mesh it self. So we set the first layer - // on the mesh itself only, and we set all layers on the AttributeSet - for (int32 TexCoordIdx = 0; TexCoordIdx < NumUVLayers; ++TexCoordIdx) - { - const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; - if (SplitUVs.IsValidIndex(TriVertIdx0 * 2 + 3 * 2 - 1)) - { - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - const int32 UVIdx = TriVertIdx0 * 2 + ElementIdx * 2; - // We need to flip V coordinate when it's coming from HAPI. - const FVector2D UV(SplitUVs[UVIdx + 0], 1.0f - SplitUVs[UVIdx + 1]); - // Set the UV on the vertex instance in the UVLayer - FoundStaticMesh->SetTriangleVertexUV(TriangleIdx, TriWindingIndex[ElementIdx], TexCoordIdx, UV); - } - } - } - } - } - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIALS / FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // Get face indices for this split. - TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; - - // Fetch the FoundMesh's Static Materials array - TArray& FoundStaticMaterials = FoundStaticMesh->GetStaticMaterials(); - - // Clear the materials array of the mesh the first time we encounter it - if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) - { - FoundStaticMaterials.Empty(); - } - TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); - - // Process material overrides first - if (PartFaceMaterialOverrides.Num() > 0) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Material Overrides")); - - // Array used to avoid constantly attempting to load invalid materials - TArray InvalidMaterials; - - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - continue; - - UMaterialInterface * MaterialInterface = nullptr; - int32 CurrentFaceMaterialIdx = 0; - const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface) - { - // Try to locate the corresponding material interface - - // Start by looking in our assignment map - FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - // Only try to load a material if it has a chance to be valid! - if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) - { - MaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - - if (!MaterialInterface) - InvalidMaterials.Add(MaterialName); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add this material to the map - MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); - } - else - { - // The Attribute Material and its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - if (!MaterialInterface) - { - // If everything fails, we'll use the default material - MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - } - } - - if (MaterialInterface) - { - int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); - } - // Update the Face Material on the mesh - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); - } - } - } - else if (PartUniqueMaterialIds.Num() > 0) - { - // The part has houdini materials - if (bOnlyOneFaceMaterial) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Single Material")); - - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMaterials.Empty(); - FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - else - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Materials")); - - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - UMaterialInterface* MaterialInterface = nullptr; - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (MaterialInterface) - { - int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); - continue; - } - } - else - { - MaterialInterface = Cast(DefaultMaterial); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - - if (MaterialInterface) - { - // Add the material to the Static mesh - int32 UnrealMatIndex = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - - // Map the houdini ID to the unreal one - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); - - // Update the face index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); - } - } - } - } - else - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Default Material")); - - // No materials were found, we need to use default Houdini material. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMaterials.Empty(); - FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - - //// Update property attributes on the mesh - //TArray PropertyAttributes; - //if (GetGenericPropertiesAttributes( - // HGPO.GeoId, HGPO.PartId, - // AllSplitFirstValidVertexIndex[SplitGroupName], - // AllSplitFirstValidPrimIndex[SplitGroupName], - // PropertyAttributes)) - //{ - // UpdateGenericPropertiesAttributes( - // FoundStaticMesh, PropertyAttributes); - //} - - FoundStaticMesh->Optimize(); - - // Check if the mesh is valid (check all the counts (vertex, triangles, vertex instances, UVs etc) but skip - // looping over each individual triangle vertex index to check if the value is valid). - const bool bSkipVertexIndicesCheck = true; - if (!FoundStaticMesh->IsValid(bSkipVertexIndicesCheck)) - { - HOUDINI_LOG_WARNING( - TEXT("[CreateHoudiniStaticMesh]: Invalid StaticMesh data for %s in cook output! Please check the log."), - *FoundStaticMesh->GetName()); - } - - //// Try to find the outer package so we can dirty it up - //if (FoundStaticMesh->GetOuter()) - //{ - // FoundStaticMesh->GetOuter()->MarkPackageDirty(); - //} - //else - //{ - // FoundStaticMesh->MarkPackageDirty(); - //} - UPackage *MeshPackage = FoundStaticMesh->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - // Save the created/updated package - FEditorFileUtils::PromptForCheckoutAndSave({ MeshPackage }, false, false); - */ - } - - // Add the Proxy mesh to the output maps - if (FoundOutputObject) - { - FoundOutputObject->ProxyObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = true; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - } - - const double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() executed in %f seconds."), time_end - time_start); - - return true; -} - -void -FHoudiniMeshTranslator::ApplyComplexColliderHelper( - UStaticMesh* TargetStaticMesh, - UStaticMesh* ComplexStaticMesh, - const EHoudiniSplitType SplitType, - bool& bAssignedCustomCollisionMesh, - FHoudiniOutputObject* OutputObject) -{ - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider && TargetStaticMesh) - { - if (!bAssignedCustomCollisionMesh) - { - bAssignedCustomCollisionMesh = true; - TargetStaticMesh->ComplexCollisionMesh = ComplexStaticMesh; - TargetStaticMesh->bCustomizedCollision = true; - bAssignedCustomCollisionMesh = true; - // We don't want an actor/component for this object in the scene, so flag it as an implicit output. - if (OutputObject) - { - OutputObject->bIsImplicit = true; - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("More than one (invisible) complex collision mesh found. Static Mesh assets only support a single complex collision mesh. Creating additional collision geo as Static Mesh Components.")); - } - } -} - - -bool -FHoudiniMeshTranslator::CreateNeededMaterials() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateNeededMaterials")); - - UpdatePartNeededMaterials(); - - TArray MaterialAndTexturePackages; - FHoudiniMaterialTranslator::CreateHoudiniMaterials( - HGPO.AssetId, PackageParams, - PartUniqueMaterialIds, PartUniqueMaterialInfos, - InputAssignmentMaterials, OutputAssignmentMaterials, - MaterialAndTexturePackages, false, bTreatExistingMaterialsAsUpToDate); - - /* - // Save the created packages if needed - // DPT: deactivated, only dirty for now, as we'll save them when saving the world. - if (MaterialAndTexturePackages.Num() > 0) - FEditorFileUtils::PromptForCheckoutAndSave(MaterialAndTexturePackages, true, false); - */ - - if (bMaterialOverrideNeedsCreateInstance && PartFaceMaterialOverrides.Num() > 0) - { - // Map containing unique face materials override attribute - // and their first valid prim index - // We create only one material instance per attribute - TMap UniqueFaceMaterialOverrides; - for (int FaceIdx = 0; FaceIdx < PartFaceMaterialOverrides.Num(); FaceIdx++) - { - FString MatOverrideAttr = PartFaceMaterialOverrides[FaceIdx]; - if (UniqueFaceMaterialOverrides.Contains(MatOverrideAttr)) - continue; - - // Add the material override and face index to the map - UniqueFaceMaterialOverrides.Add(MatOverrideAttr, FaceIdx); - } - - FHoudiniMaterialTranslator::CreateMaterialInstances( - HGPO, PackageParams, - UniqueFaceMaterialOverrides, MaterialAndTexturePackages, - InputAssignmentMaterials, OutputAssignmentMaterials, - false); - } - - return true; -} - -FString -FHoudiniMeshTranslator::GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType) -{ - FString MeshIdentifier = TEXT(""); - switch (InSplitType) - { - case EHoudiniSplitType::Normal: - case EHoudiniSplitType::LOD: - case EHoudiniSplitType::InvisibleUCXCollider: - case EHoudiniSplitType::InvisibleSimpleCollider: - // LODs and Invisible simple colliders use the main mesh - MeshIdentifier = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - break; - - case EHoudiniSplitType::InvisibleComplexCollider: - case EHoudiniSplitType::RenderedComplexCollider: - case EHoudiniSplitType::RenderedUCXCollider: - case EHoudiniSplitType::RenderedSimpleCollider: - // Rendered colliders or invisible complex colliders have their own static mesh - MeshIdentifier = InSplitName; - break; - - default: - break; - } - - return MeshIdentifier; -} - -UStaticMesh* -FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - // See if we already have an input object for that output identifier - FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); - UStaticMesh* FoundStaticMesh = nullptr; - if (FoundOutputObjectPtr) - { - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - FoundStaticMesh = nullptr; - } - - if (!FoundStaticMesh) - { - // No input object matching this identifier, see if we have created an output object that matches - FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); - if (!FoundOutputObjectPtr) - return nullptr; - - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - return nullptr; - } - - if (FoundStaticMesh) - { - UObject* OuterMost = FoundStaticMesh->GetOutermostObject(); - if (OuterMost->IsA()) - { - // The Outermost for this static mesh is a level - // This is likely a SM created by V1, and we should not reuse it. - // This will force the plugin to recreate a "proper" SM in the temp folder. - FoundStaticMesh->MarkPendingKill(); - FoundStaticMesh = nullptr; - } - } - - return FoundStaticMesh; -} - -UHoudiniStaticMesh* -FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - // See if we already have an input object for that output identifier - FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); - UHoudiniStaticMesh* FoundStaticMesh = nullptr; - if (FoundOutputObjectPtr) - { - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - FoundStaticMesh = nullptr; - } - - if (!FoundStaticMesh) - { - // No input object matching this identifier, see if we have created an output object that matches - FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); - if (!FoundOutputObjectPtr) - return nullptr; - - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - return nullptr; - } - - return FoundStaticMesh; -} - -EHoudiniSplitType -FHoudiniMeshTranslator::GetSplitTypeFromSplitName(const FString& InSplitName) -{ - const FString MainGroup = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - if (InSplitName.StartsWith(MainGroup, ESearchCase::IgnoreCase)) - return EHoudiniSplitType::Normal; - - const FString LODGroupPrefix = HAPI_UNREAL_GROUP_LOD_PREFIX; - if (InSplitName.StartsWith(LODGroupPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::LOD; - } - - const FString RenderedCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; - if (InSplitName.StartsWith(RenderedCollisionPrefix, ESearchCase::IgnoreCase)) - { - // Rendered colliders - // See if it is a simple/ucx/complex - const FString RenderedUCXCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX; - const FString RenderedSimpleCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(RenderedUCXCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::RenderedUCXCollider; - } - else if (InSplitName.StartsWith(RenderedSimpleCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::RenderedSimpleCollider; - } - else - { - return EHoudiniSplitType::RenderedComplexCollider; - } - } - - const FString InvisibleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(InvisibleCollisionPrefix, ESearchCase::IgnoreCase)) - { - // Invisible colliders - // See if it is a simple/ucx/complex - const FString InvisibleUCXCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX; - const FString InvisibleSimpleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(InvisibleUCXCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::InvisibleUCXCollider; - } - else if (InSplitName.StartsWith(InvisibleSimpleCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::InvisibleSimpleCollider; - } - else - { - return EHoudiniSplitType::InvisibleComplexCollider; - } - } - - // ? - return EHoudiniSplitType::Invalid; - //return EHoudiniSplitType::Normal; -} - -bool -FHoudiniMeshTranslator::AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) -{ - // Get the vertex indices for the split group - TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; - - // We're only interested in unique vertices - TArray UniqueVertexIndexes; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - UniqueVertexIndexes.AddUnique(Index); - } - - // Extract the collision geo's vertices - TArray< FVector > VertexArray; - VertexArray.SetNum(UniqueVertexIndexes.Num()); - for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) - { - int32 VertexIndex = UniqueVertexIndexes[Idx]; - if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) - continue; - - VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - -#if WITH_EDITOR - // Do we want to create multiple convex hulls? - bool bDoMultiHullDecomp = false; - if (SplitGroupName.Contains(TEXT("ucx_multi"), ESearchCase::IgnoreCase)) - bDoMultiHullDecomp = true; - - uint32 HullCount = 8; - int32 MaxHullVerts = 16; - if (bDoMultiHullDecomp) - { - // TODO: - // Look for extra attributes for the decomposition parameters? (HullCount/MaxHullVerts) - } - - if (bDoMultiHullDecomp && (VertexArray.Num() >= 3 || UniqueVertexIndexes.Num() >= 3)) - { - // creating multiple convex hull collision - // ... this might take a while - - // We're only interested in the valid indices! - TArray Indices; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - Indices.Add(Index); - } - - // But we need all the positions as vertex - TArray< FVector > Vertices; - Vertices.SetNum(PartPositions.Num() / 3); - - for (int32 Idx = 0; Idx < Vertices.Num(); Idx++) - { - Vertices[Idx].X = PartPositions[Idx * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[Idx].Y = PartPositions[Idx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[Idx].Z = PartPositions[Idx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - // We are using Unreal's DecomposeMeshToHulls() - // We need a BodySetup so create a fake/transient one - UBodySetup* BodySetup = NewObject(); - - // Run actual util to do the work (if we have some valid input) - DecomposeMeshToHulls(BodySetup, Vertices, Indices, HullCount, MaxHullVerts); - - // If we succeed, return here - // If not, keep going and we'll try to do a single hull decomposition - if (BodySetup->AggGeom.ConvexElems.Num() > 0) - { - // Copy the convex elem to our aggregate - for (int32 n = 0; n < BodySetup->AggGeom.ConvexElems.Num(); n++) - AggCollisions.ConvexElems.Add(BodySetup->AggGeom.ConvexElems[n]); - - return true; - } - } -#endif - - // Creating a single Convex collision - FKConvexElem ConvexCollision; - ConvexCollision.VertexData = VertexArray; - ConvexCollision.UpdateElemBox(); - - AggCollisions.ConvexElems.Add(ConvexCollision); - - return true; -} - -bool -FHoudiniMeshTranslator::AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) -{ - // Get the vertex indices for the split group - TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; - - // We're only interested in unique vertices - TArray UniqueVertexIndexes; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - UniqueVertexIndexes.AddUnique(Index); - } - - // Extract the collision geo's vertices - TArray< FVector > VertexArray; - VertexArray.SetNum(UniqueVertexIndexes.Num()); - for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) - { - int32 VertexIndex = UniqueVertexIndexes[Idx]; - if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) - continue; - - VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - int32 NewColliders = 0; - if (SplitGroupName.Contains("Box")) - { - NewColliders = FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(VertexArray, AggCollisions); - } - else if (SplitGroupName.Contains("Sphere")) - { - NewColliders = FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(VertexArray, AggCollisions); - } - else if (SplitGroupName.Contains("Capsule")) - { - NewColliders = FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(VertexArray, AggCollisions); - } - else - { - // We need to see what type of collision the user wants - // by default, a kdop26 will be created - uint32 NumDirections = 26; - const FVector* Directions = KDopDir26; - if (SplitGroupName.Contains("kdop10X")) - { - NumDirections = 10; - Directions = KDopDir10X; - } - else if (SplitGroupName.Contains("kdop10Y")) - { - NumDirections = 10; - Directions = KDopDir10Y; - } - else if (SplitGroupName.Contains("kdop10Z")) - { - NumDirections = 10; - Directions = KDopDir10Z; - } - else if (SplitGroupName.Contains("kdop18")) - { - NumDirections = 18; - Directions = KDopDir18; - } - - // Converting the directions to a TArray - TArray DirArray; - DirArray.SetNum(NumDirections); - for (uint32 DirectionIndex = 0; DirectionIndex < NumDirections; DirectionIndex++) - { - DirArray[DirectionIndex] = Directions[DirectionIndex]; - } - - NewColliders = FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(VertexArray, DirArray, AggCollisions); - } - - return (NewColliders > 0); -} - -int32 -FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData) -{ - return FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, OutVertexData); -} - -/* -int32 -FHoudiniMeshTranslator::GetSplitNormals( - const TArray& InSplitVertexList, TArray& OutNormals) -{ - // Extract the normals - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutNormals.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; - OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; - OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; - - OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; - OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; - OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; - - OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; - OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; - OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; - } - - return WedgeCount; -} - -int32 -FHoudiniMeshTranslator::GetSplitUVs( - const TArray& InSplitVertexList, TArray& OutUVs) -{ - // Extract the normals - UpdatePartUVSetsIfNeeded(); - - // Get the normals for this split - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutNormals.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; - OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; - OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; - - OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; - OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; - OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; - - OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; - OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; - OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; - } - - return WedgeCount; -} - - -int32 -FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutData, - const float& ScaleFactor) -{ - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutData.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; - OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; - - OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; - OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; - - OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; - OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; - } - - return WedgeCount; -} - -int32 -FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutData, - const float& ScaleFactor) -{ - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutData.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; - OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; - - OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; - OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; - - OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; - OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; - } - - return WedgeCount; -} -*/ - - -template -int32 FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::TransferPartAttributesToSplit")); - - if (!InAttribInfo.exists || InAttribInfo.tupleSize <= 0) - return 0; - - if (InData.Num() <= 0) - return 0; - - int32 ValidWedgeCount = 0; - - // Future optimization - see if we can do direct vertex transfer. - int32 WedgeCount = InVertexList.Num(); - int32 LastValidWedgeIdx = 0; - if (InAttribInfo.owner == HAPI_ATTROWNER_POINT) - { - // Point attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - int32 VertexIdx = InVertexList[WedgeIdx]; - if (VertexIdx < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[VertexIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_VERTEX) - { - // Vertex attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[WedgeIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_PRIM) - { - // Primitive attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 PrimIdx = WedgeIdx / 3; - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[PrimIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_DETAIL) - { - // Detail attribute transfer - // We have one value to copy for all output split vertices - // if the attribute is a single value (not a tuple) - // then we can simply use the array init function instead of looping - if (InAttribInfo.tupleSize == 1) - { - OutVertexData.Init(InData[0], WedgeCount); - } - else - { - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - } - else - { - // Invalid attribute owner, shouldn't happen - check(false); - } - - OutVertexData.SetNumZeroed(ValidWedgeCount * InAttribInfo.tupleSize); - - return ValidWedgeCount; -} - -float -FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) -{ - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = -1.0f; - - // Start by looking at the lod_screensize primitive attribute - bool bAttribValid = false; - UpdatePartLODScreensizeIfNeeded(); - - if (PartLODScreensize.Num() > 0) - { - // use the "lod_screensize" primitive attribute - int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - if (PartLODScreensize.IsValidIndex(FirstValidPrimIndex)) - screensize = PartLODScreensize[FirstValidPrimIndex]; - } - - if (screensize < 0.0f) - { - // We couldn't find the primitive attribute, look for a "lodX_screensize" detail attribute - FString LODAttributeName = SplitGroupName + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX; - - TArray LODScreenSizes; - HAPI_AttributeInfo AttribInfoScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); - - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*LODAttributeName), - AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_DETAIL); - - if (AttribInfoScreenSize.exists && LODScreenSizes.Num() > 0) - { - screensize = LODScreenSizes[0]; - } - } - - if (screensize < 0.0f) - { - // finally, look for a potential uproperty style attribute - // aka, "unreal_uproperty_screensize" - TArray LODScreenSizes; - HAPI_AttributeInfo AttribInfoScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); - - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, "unreal_uproperty_screensize", - AttribInfoScreenSize, LODScreenSizes); - - if (AttribInfoScreenSize.exists) - { - if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_DETAIL && LODScreenSizes.Num() > 0) - { - screensize = LODScreenSizes[0]; - } - else if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_PRIM) - { - int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - if (LODScreenSizes.IsValidIndex(FirstValidPrimIndex)) - screensize = LODScreenSizes[FirstValidPrimIndex]; - } - } - } - - // Make sure the screensize is in percent, so if its above 1, divide by 100 - if (screensize > 1.0f) - screensize /= 100.0f; - - return screensize; -} - -int32 -FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - // Calculate bounding Box. - FVector Center, Extents; - FVector unitVec = FVector::OneVector;// bs->BuildScale3D; - CalcBoundingBox(InPositionArray, Center, Extents, unitVec); - - FKBoxElem BoxElem; - BoxElem.Center = Center; - BoxElem.X = Extents.X * 2.0f; - BoxElem.Y = Extents.Y * 2.0f; - BoxElem.Z = Extents.Z * 2.0f; - OutAggregateCollisions.BoxElems.Add(BoxElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FBox Box(ForceInit); - for (const FVector& CurPos : PositionArray) - { - Box += CurPos; - } - Box.GetCenterAndExtents(Center, Extents); -} - -int32 -FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FSphere bSphere, bSphere2, bestSphere; - FVector unitVec = FVector::OneVector; - - // Calculate bounding sphere. - CalcBoundingSphere(InPositionArray, bSphere, unitVec); - CalcBoundingSphere2(InPositionArray, bSphere2, unitVec); - - if (bSphere.W < bSphere2.W) - bestSphere = bSphere; - else - bestSphere = bSphere2; - - // Don't use if radius is zero. - if (bestSphere.W <= 0.f) - { - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Sphere collider.")); - return 0; - } - - FKSphereElem SphereElem; - SphereElem.Center = bestSphere.Center; - SphereElem.Radius = bestSphere.W; - OutAggregateCollisions.SphereElems.Add(SphereElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - if (PositionArray.Num() == 0) - return; - - FBox Box; - FVector MinIx[3]; - FVector MaxIx[3]; - - bool bFirstVertex = true; - for (const FVector& CurPosition : PositionArray) - { - FVector p = CurPosition * LimitVec; - if (bFirstVertex) - { - // First, find AABB, remembering furthest points in each dir. - Box.Min = p; - Box.Max = Box.Min; - - MinIx[0] = CurPosition; - MinIx[1] = CurPosition; - MinIx[2] = CurPosition; - - MaxIx[0] = CurPosition; - MaxIx[1] = CurPosition; - MaxIx[2] = CurPosition; - bFirstVertex = false; - continue; - } - - // X // - if (p.X < Box.Min.X) - { - Box.Min.X = p.X; - MinIx[0] = CurPosition; - } - else if (p.X > Box.Max.X) - { - Box.Max.X = p.X; - MaxIx[0] = CurPosition; - } - - // Y // - if (p.Y < Box.Min.Y) - { - Box.Min.Y = p.Y; - MinIx[1] = CurPosition; - } - else if (p.Y > Box.Max.Y) - { - Box.Max.Y = p.Y; - MaxIx[1] = CurPosition; - } - - // Z // - if (p.Z < Box.Min.Z) - { - Box.Min.Z = p.Z; - MinIx[2] = CurPosition; - } - else if (p.Z > Box.Max.Z) - { - Box.Max.Z = p.Z; - MaxIx[2] = CurPosition; - } - } - - const FVector Extremes[3] = { (MaxIx[0] - MinIx[0]) * LimitVec, - (MaxIx[1] - MinIx[1]) * LimitVec, - (MaxIx[2] - MinIx[2]) * LimitVec }; - - // Now find extreme points furthest apart, and initial center and radius of sphere. - float d2 = 0.f; - for (int32 i = 0; i < 3; i++) - { - const float tmpd2 = Extremes[i].SizeSquared(); - if (tmpd2 > d2) - { - d2 = tmpd2; - sphere.Center = (MinIx[i] + (0.5f * Extremes[i])) * LimitVec; - sphere.W = 0.f; - } - } - - const FVector Extents = FVector(Extremes[0].X, Extremes[1].Y, Extremes[2].Z); - - // radius and radius squared - float r = 0.5f * Extents.GetMax(); - float r2 = FMath::Square(r); - - // Now check each point lies within this sphere. If not - expand it a bit. - for (const FVector& curPos : PositionArray) - { - const FVector cToP = (curPos * LimitVec) - sphere.Center; - - const float pr2 = cToP.SizeSquared(); - - // If this point is outside our current bounding sphere's radius - if (pr2 > r2) - { - // ..expand radius just enough to include this point. - const float pr = FMath::Sqrt(pr2); - r = 0.5f * (r + pr); - r2 = FMath::Square(r); - - sphere.Center += ((pr - r) / pr * cToP); - } - } - - sphere.W = r; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FVector Center, Extents; - CalcBoundingBox(PositionArray, Center, Extents, LimitVec); - - sphere.Center = Center; - sphere.W = 0.0f; - - for (const FVector& curPos : PositionArray) - { - float Dist = FVector::DistSquared(curPos * LimitVec, sphere.Center); - if (Dist > sphere.W) - sphere.W = Dist; - } - sphere.W = FMath::Sqrt(sphere.W); -} - -int32 -FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FSphere sphere; - float length; - FRotator rotation; - FVector unitVec = FVector::OneVector; - - // Calculate bounding sphyl. - CalcBoundingSphyl(InPositionArray, sphere, length, rotation, unitVec); - - // Dont use if radius is zero. - if (sphere.W <= 0.f) - { - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Capsule collider.")); - return 0; - } - - // If height is zero, then a sphere would be better (should we just create one instead?) - if (length <= 0.f) - { - length = SMALL_NUMBER; - } - - FKSphylElem SphylElem; - SphylElem.Center = sphere.Center; - SphylElem.Rotation = rotation; - SphylElem.Radius = sphere.W; - SphylElem.Length = length; - OutAggregateCollisions.SphylElems.Add(SphylElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - if (PositionArray.Num() == 0) - return; - - FVector Center, Extents; - CalcBoundingBox(PositionArray, Center, Extents, LimitVec); - - // @todo sphere.Center could perhaps be adjusted to best fit if model is non-symmetric on it's longest axis - sphere.Center = Center; - - // Work out best axis aligned orientation (longest side) - float Extent = Extents.GetMax(); - if (Extent == Extents.X) - { - rotation = FRotator(90.f, 0.f, 0.f); - Extents.X = 0.0f; - } - else if (Extent == Extents.Y) - { - rotation = FRotator(0.f, 0.f, 90.f); - Extents.Y = 0.0f; - } - else - { - rotation = FRotator(0.f, 0.f, 0.f); - Extents.Z = 0.0f; - } - - // Cleared the largest axis above, remaining determines the radius - float r = Extents.GetMax(); - float r2 = FMath::Square(r); - - // Now check each point lies within this the radius. If not - expand it a bit. - for (const FVector& CurPos : PositionArray) - { - FVector cToP = (CurPos * LimitVec) - sphere.Center; - cToP = rotation.UnrotateVector(cToP); - - const float pr2 = cToP.SizeSquared2D(); // Ignore Z here... - - // If this point is outside our current bounding sphere's radius - if (pr2 > r2) - { - // ..expand radius just enough to include this point. - const float pr = FMath::Sqrt(pr2); - r = 0.5f * (r + pr); - r2 = FMath::Square(r); - } - } - - // The length is the longest side minus the radius. - float hl = FMath::Max(0.0f, Extent - r); - - // Now check each point lies within the length. If not - expand it a bit. - for (const FVector& CurPos : PositionArray) - { - FVector cToP = (CurPos * LimitVec) - sphere.Center; - cToP = rotation.UnrotateVector(cToP); - - // If this point is outside our current bounding sphyl's length - if (FMath::Abs(cToP.Z) > hl) - { - const bool bFlip = (cToP.Z < 0.f ? true : false); - const FVector cOrigin(0.f, 0.f, (bFlip ? -hl : hl)); - - const float pr2 = (cOrigin - cToP).SizeSquared(); - - // If this point is outside our current bounding sphyl's radius - if (pr2 > r2) - { - FVector cPoint; - FMath::SphereDistToLine(cOrigin, r, cToP, (bFlip ? FVector(0.f, 0.f, 1.f) : FVector(0.f, 0.f, -1.f)), cPoint); - - // Don't accept zero as a valid diff when we know it's outside the sphere (saves needless retest on further iterations of like points) - hl += FMath::Max(FMath::Abs(cToP.Z - cPoint.Z), 1.e-6f); - } - } - } - - sphere.W = r; - length = hl * 2.0f; -} - -int32 -FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - const float my_flt_max = 3.402823466e+38F; - - // Do k- specific stuff. - int32 kCount = Dirs.Num(); - - TArray maxDist; - maxDist.Init(-my_flt_max, kCount); - /* - for (int32 i = 0; i < kCount; i++) - maxDist.Add(my_flt_max); - */ - - // Construct temporary UModel for kdop creation. We keep no refs to it, so it can be GC'd. - auto TempModel = NewObject(); - TempModel->Initialize(nullptr, 1); - - // For each vertex, project along each kdop direction, to find the max in that direction. - for (int32 i = 0; i < InPositionArray.Num(); i++) - { - for (int32 j = 0; j < kCount; j++) - { - float dist = InPositionArray[i] | Dirs[j]; - maxDist[j] = FMath::Max(dist, maxDist[j]); - } - } - - // Inflate kdop to ensure it is no degenerate - const float MinSize = 0.1f; - for (int32 i = 0; i < kCount; i++) - { - maxDist[i] += MinSize; - } - - // Now we have the planes of the kdop, we work out the face polygons. - TArray planes; - for (int32 i = 0; i < kCount; i++) - planes.Add(FPlane(Dirs[i], maxDist[i])); - - for (int32 i = 0; i < planes.Num(); i++) - { - FPoly* Polygon = new(TempModel->Polys->Element) FPoly(); - FVector Base, AxisX, AxisY; - - Polygon->Init(); - Polygon->Normal = planes[i]; - Polygon->Normal.FindBestAxisVectors(AxisX, AxisY); - - Base = planes[i] * planes[i].W; - - new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); - - for (int32 j = 0; j < planes.Num(); j++) - { - if (i != j) - { - if (!Polygon->Split(-FVector(planes[j]), planes[j] * planes[j].W)) - { - Polygon->Vertices.Empty(); - break; - } - } - } - - if (Polygon->Vertices.Num() < 3) - { - // If poly resulted in no verts, remove from array - TempModel->Polys->Element.RemoveAt(TempModel->Polys->Element.Num() - 1); - } - else - { - // Other stuff... - Polygon->iLink = i; - Polygon->CalcNormal(1); - } - } - - if (TempModel->Polys->Element.Num() < 4) - { - TempModel = NULL; - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple KDOP collider.")); - return 0; - } - - // Build bounding box. - TempModel->BuildBound(); - - // Build BSP for the brush. - FBSPOps::bspBuild(TempModel, FBSPOps::BSP_Good, 15, 70, 1, 0); - FBSPOps::bspRefresh(TempModel, 1); - FBSPOps::bspBuildBounds(TempModel); - - // Now, create a temporary BodySetup to build the colliders - UBodySetup* TempBS = NewObject(); - TempBS->CreateFromModel(TempModel, false); - - // Copy the convex elements back to our aggregate - int32 NewConvexElems = 0; - if (TempBS && TempBS->AggGeom.ConvexElems.Num() > 0) - { - for (const auto& CurConvexElem : TempBS->AggGeom.ConvexElems) - { - OutAggregateCollisions.ConvexElems.Add(CurConvexElem); - NewConvexElems++; - } - } - - return NewConvexElems; -} - - -void -FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO) -{ - PackageParams = InPackageParams; - - if (bUpdateHGPO) - { - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.ObjectId; - PackageParams.PartId = HGPO.ObjectId; - } -} - -bool -FHoudiniMeshTranslator::RemoveAndDestroyComponent(UObject* InComponent) -{ - if (!InComponent || InComponent->IsPendingKill()) - return false; - - USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - return true; - } - - return false; -} - -UMeshComponent* -FHoudiniMeshTranslator::CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf &InComponentType) -{ - // Create a new SMC as we couldn't find an existing one - USceneComponent* OuterSceneComponent = Cast(InOuterComponent); - UObject * Outer = nullptr; - if (OuterSceneComponent && !OuterSceneComponent->IsPendingKill()) - Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); - - UMeshComponent *MeshComponent = NewObject(Outer, InComponentType, NAME_None, RF_Transactional); - - // Initialize it - MeshComponent->SetVisibility(true); - //MeshComponent->SetMobility(Mobility); - - // TODO: - // Property propagation: set the new SMC's properties to the HAC's current settings - //CopyComponentPropertiesTo(MeshComponent); - - // Change the creation method so the component is listed in the details panels - MeshComponent->CreationMethod = EComponentCreationMethod::Instance; - - // Attach created static mesh component to our Houdini component. - MeshComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - MeshComponent->OnComponentCreated(); - MeshComponent->RegisterComponent(); - - return MeshComponent; -} - -bool -FHoudiniMeshTranslator::PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh) -{ - UStaticMesh *Mesh = Cast(InMesh); - if (Mesh) - { - InComponent->SetStaticMesh(Mesh); - - return true; - } - - return false; -} - -bool -FHoudiniMeshTranslator::PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh) -{ - UHoudiniStaticMesh *Mesh = Cast(InMesh); - if (Mesh) - { - InComponent->SetMesh(Mesh); - - return true; - } - - return false; -} - -UMeshComponent* -FHoudiniMeshTranslator::CreateOrUpdateMeshComponent( - const UHoudiniOutput *InOutput, - UObject *InOuterComponent, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TSubclassOf& InComponentType, - FHoudiniOutputObject& OutputObject, - FHoudiniGeoPartObject const *& OutFoundHGPO, - bool& bCreated) -{ - bCreated = false; - OutFoundHGPO = nullptr; - - // Find the HGPO that matches this mesh - for (auto& curHGPO : InOutput->HoudiniGeoPartObjects) - { - if (curHGPO.ObjectId != InOutputIdentifier.ObjectId - || curHGPO.GeoId != InOutputIdentifier.GeoId - || curHGPO.PartId != InOutputIdentifier.PartId) - { - continue; - } - - if (InOutputIdentifier.SplitIdentifier.Equals(HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION) - || curHGPO.SplitGroups.Contains(InOutputIdentifier.SplitIdentifier)) - { - OutFoundHGPO = &curHGPO; - } - } - - // No need to create a component for instanced meshes! - if (OutFoundHGPO && OutFoundHGPO->bIsInstanced) - return nullptr; - - bool bIsProxyComponent = InComponentType == UHoudiniStaticMeshComponent::StaticClass(); - - // See if we already have a component for that mesh - UMeshComponent* MeshComponent = nullptr; - if (bIsProxyComponent) - MeshComponent = Cast(OutputObject.ProxyComponent); - else - MeshComponent = Cast(OutputObject.OutputComponent); - - // If there is an existing component, but it is pending kill, then it was likely - // deleted by some other process, such as by the user in the editor, so don't use it - if (!MeshComponent || MeshComponent->IsPendingKill() || !MeshComponent->IsA(InComponentType)) - { - // If the component is not of type InComponentType, or the found component is pending kill, destroy - // the existing component (a new one is then created below) - RemoveAndDestroyComponent(MeshComponent); - MeshComponent = nullptr; - } - - if (!MeshComponent) - { - // Create a new SMC/HSMC as we couldn't find an existing one - MeshComponent = CreateMeshComponent(InOuterComponent, InComponentType); - - if (MeshComponent) - { - // Add to the output object - if (bIsProxyComponent) - OutputObject.ProxyComponent = MeshComponent; - else - OutputObject.OutputComponent = MeshComponent; - - bCreated = true; - } - } - - return MeshComponent; -} - -bool -FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, - TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors) -{ - if (!Socket || Socket->IsPendingKill() || !StaticMeshComponent || StaticMeshComponent->IsPendingKill()) - return false; - - // The actor to assign is stored is the socket's tag - FString ActorString = Socket->Tag; - if (ActorString.IsEmpty()) - return false; - - // The actor to assign are listed after a | - TArray ActorStringArray; - ActorString.ParseIntoArray(ActorStringArray, TEXT("|"), false); - - // The "real" Tag is the first - if (ActorStringArray.Num() > 0) - Socket->Tag = ActorStringArray[0]; - - // We just add a Tag, no Actor - if (ActorStringArray.Num() == 1) - return false; - - // Extract the parsed actor string to split it further - ActorString = ActorStringArray[1]; - - // Converting the string to a string array using delimiters - const TCHAR* Delims[] = { TEXT(","), TEXT(";") }; - ActorString.ParseIntoArray(ActorStringArray, Delims, 2); - - // And try to find the corresponding HoudiniAssetActor in the editor world - // to avoid finding "deleted" assets with the same name - //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); -#if WITH_EDITOR - UWorld* EditorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; - if (!EditorWorld || EditorWorld->IsPendingKill()) - return false; - - // Remove the previous created actors which were attached to this socket - { - for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - continue; - } - - if (CurActor->GetAttachParentSocketName() == Socket->SocketName) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - CurActor->Destroy(); - } - } - } - - // Detach the previous in level actors which was attached to this socket - { - for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniAttachedSocketActors.RemoveAt(Idx); - continue; - } - - if (CurActor->GetAttachParentSocketName() == Socket->SocketName) - { - CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - HoudiniAttachedSocketActors.RemoveAt(Idx); - } - } - } - - auto CreateDefaultActor = [EditorWorld, StaticMeshComponent, Socket, HoudiniCreatedSocketActors]() - { - AActor * CreatedDefaultActor = nullptr; - - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) - { - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( - EditorWorld->GetCurrentLevel(), DefaultReferenceSM, false, RF_Transactional, nullptr); - - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load default mesh.")); - } - else - { - - // Set the default mesh actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : NewActors[0]->GetComponents()) - { - UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) - CurSMC->SetMobility(OutputSMCMobility); - } - - // Set the default mesh actor hidden in game. - NewActors[0]->SetActorHiddenInGame(true); - - Socket->AttachActor(NewActors[0], StaticMeshComponent); - CreatedDefaultActor = NewActors[0]; - //HoudiniCreatedSocketActors.Add(NewActors[0]); - } - } - else - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load default mesh.")); - } - - return CreatedDefaultActor; - }; - - bool bUseDefaultActor = true; - // Get from the Houdini runtime setting if use default object when the reference is invalid - // true by default if fail to access HoudiniRuntimeSettings - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bUseDefaultActor = HoudiniRuntimeSettings->bShowDefaultMesh; - } - - if (ActorStringArray.Num() <= 0) - { - if (!bUseDefaultActor) - return true; - - HOUDINI_LOG_WARNING( - TEXT("Output static mesh: Socket '%s' actor is not specified. Spawn a default mesh (hidden in game)."), *(Socket->GetName())); - - AActor * DefaultActor = CreateDefaultActor(); - if (DefaultActor && !DefaultActor->IsPendingKill()) - HoudiniCreatedSocketActors.Add(DefaultActor); - - return true; - } - - // try to find the actor in level first - for (TActorIterator ActorItr(EditorWorld); ActorItr; ++ActorItr) - { - // Same as with the Object Iterator, access the subclass instance with the * or -> operators. - AActor *Actor = *ActorItr; - if (!Actor || Actor->IsPendingKillOrUnreachable()) - continue; - - for (int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++) - { - if (Actor->GetName() != ActorStringArray[StringIdx] - && Actor->GetActorLabel() != ActorStringArray[StringIdx]) - continue; - - // Set the actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : Actor->GetComponents()) - { - UStaticMeshComponent * SMC = Cast(CurComp); - if (SMC && !SMC->IsPendingKill()) - SMC->SetMobility(OutputSMCMobility); - } - - Socket->AttachActor(Actor, StaticMeshComponent); - HoudiniAttachedSocketActors.Add(Actor); - - // Remove the string if the actor is found in the editor level - ActorStringArray.RemoveAt(StringIdx); - break; - } - } - - bool bSuccess = true; - // If some of the actors are not found in the level, try to find them in the content browser. Spawn one if existed - for (int32 Idx = ActorStringArray.Num() - 1; Idx>= 0; --Idx) - { - UObject * Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *ActorStringArray[Idx]); - if (!Obj || Obj->IsPendingKill()) - { - bSuccess = false; - continue; - } - - // Spawn a new actor with the found object - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( - EditorWorld->GetCurrentLevel(), Obj, false, RF_Transactional, nullptr); - - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) - { - bSuccess = false; - continue; - } - - // Set the new actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : NewActors[0]->GetComponents()) - { - UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) - CurSMC->SetMobility(OutputSMCMobility); - } - - Socket->AttachActor(NewActors[0], StaticMeshComponent); - HoudiniCreatedSocketActors.Add(NewActors[0]); - - ActorStringArray.RemoveAt(Idx); - } - - // Failed to find actors in both level and content browser - // Spawn default actors if enabled - if (bUseDefaultActor) - { - for (int32 Idx = ActorStringArray.Num() - 1; Idx >= 0; --Idx) - { - HOUDINI_LOG_WARNING( - TEXT("Output static mesh: Failed to attach '%s' to socket '%s', spawn a default mesh (hidden in game)."), *(ActorStringArray[Idx]), *(Socket->GetName())); - - // If failed to load this object, spawn a default mesh - AActor * CurDefaultActor = CreateDefaultActor(); - if (CurDefaultActor && !CurDefaultActor->IsPendingKill()) - HoudiniCreatedSocketActors.Add(CurDefaultActor); - } - } - - if (ActorStringArray.Num() > 0) - return false; -#endif - - return bSuccess; -} - -void -FHoudiniMeshTranslator::UpdateMeshBuildSettings( - FMeshBuildSettings& OutMeshBuildSettings, - const bool& bHasNormals, - const bool& bHasTangents, - const bool& bHasLightmapUVSet) -{ - // Use the values provided to the translator - OutMeshBuildSettings = StaticMeshBuildSettings; - - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - - // Recomputing normals. - EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag : HRSRF_OnlyIfMissing; - if(RecomputeNormalFlag == HRSRF_OnlyIfMissing) - OutMeshBuildSettings.bRecomputeNormals = !bHasNormals; - - // Recomputing tangents. - EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; - if (RecomputeTangentFlag == HRSRF_OnlyIfMissing) - OutMeshBuildSettings.bRecomputeTangents = !bHasTangents; - - // Lightmap UV generation. - EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; - if (GenerateLightmapUVFlag == HRSRF_OnlyIfMissing) - OutMeshBuildSettings.bGenerateLightmapUVs = !bHasLightmapUVSet; -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniMeshTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniOutput.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniMaterialTranslator.h" +#include "HoudiniAssetActor.h" + +#include "HoudiniStaticMesh.h" +#include "HoudiniStaticMeshComponent.h" +#include "Engine/StaticMeshSocket.h" + +#include "PhysicsEngine/BodySetup.h" +#include "Engine/StaticMesh.h" +#include "PackageTools.h" +#include "RawMesh.h" +#include "Materials/MaterialInterface.h" +#include "Materials/Material.h" +#include "MeshDescription.h" +#include "StaticMeshAttributes.h" +#include "MeshDescriptionOperations.h" + +#include "BSPOps.h" +#include "Model.h" +#include "Engine/Polys.h" +#include "AssetRegistryModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "AI/Navigation/NavCollisionBase.h" +#include "ObjectTools.h" + +// #include "Async/ParallelFor.h" + +#include "ProfilingDebugging/CpuProfilerTrace.h" + +#include "EditorSupportDelegates.h" + +#if WITH_EDITOR + #include "UnrealEd/Private/ConvexDecompTool.h" + #include "Editor/UnrealEd/Private/GeomFitUtils.h" + #include "LevelEditorViewport.h" + #include "FileHelpers.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +static TAutoConsoleVariable CVarHoudiniEngineMeshBuildTimer( + TEXT("HoudiniEngine.MeshBuildTimer"), + 0.0, + TEXT("When enabled, the plugin will output timings during the Mesh creation.\n") +); + +// +bool +FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + UHoudiniOutput* InOutput, + const FHoudiniPackageParams& InPackageParams, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, + UObject* InOuterComponent, + bool bInTreatExistingMaterialsAsUpToDate, + bool bInDestroyProxies) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InPackageParams.OuterPackage || InPackageParams.OuterPackage->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + TMap NewOutputObjects; + TMap OldOutputObjects = InOutput->GetOutputObjects(); + TMap& AssignementMaterials = InOutput->GetAssignementMaterials(); + TMap& ReplacementMaterials = InOutput->GetReplacementMaterials(); + + bool InForceRebuild = false; + if (InOutput->HasAnyCurrentProxy() && InStaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh) + { + // Make sure we're not preventing refinement + InForceRebuild = true; + } + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) + { + // Not a mesh, skip + if (CurHGPO.Type != EHoudiniPartType::Mesh) + continue; + + // See if we have some uproperty attributes to update on + // the outer component (in most case, the HAC) + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + CurHGPO.GeoId, CurHGPO.PartId, + true, 0, 0, 0, + PropertyAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + InOuterComponent, PropertyAttributes); + } + + CreateStaticMeshFromHoudiniGeoPartObject( + CurHGPO, + InPackageParams, + OldOutputObjects, + NewOutputObjects, + AssignementMaterials, + ReplacementMaterials, + InForceRebuild, + InStaticMeshMethod, + InSMGenerationProperties, + InMeshBuildSettings, + bInTreatExistingMaterialsAsUpToDate); + } + + return FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + InOutput, + InOuterComponent, + NewOutputObjects, + bInDestroyProxies); +} + +bool +FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + UHoudiniOutput* InOutput, + UObject* InOuterComponent, + TMap& InNewOutputObjects, + bool bInDestroyProxies, + bool bInApplyGenericProperties) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + TMap OldOutputObjects = InOutput->GetOutputObjects(); + + // Remove Static Meshes and their components from the old map + // to avoid their deletion if new proxies were created for them + for (auto& NewOutputObj : InNewOutputObjects) + { + FHoudiniOutputObjectIdentifier OutputIdentifier = NewOutputObj.Key; + + // See if we already had that pair in the old map of static mesh + FHoudiniOutputObject* FoundOldOutputObj = OldOutputObjects.Find(NewOutputObj.Key); + if (!FoundOldOutputObj) + continue; + + UObject* NewStaticMesh = NewOutputObj.Value.OutputObject; + UObject* NewProxyMesh = NewOutputObj.Value.ProxyObject; + + UObject* OldStaticMesh = FoundOldOutputObj->OutputObject; + if (OldStaticMesh && !OldStaticMesh->IsPendingKill()) + { + // If a proxy was created for an existing static mesh, keep the existing static + // mesh (will be hidden) + if (NewProxyMesh && NewOutputObj.Value.bProxyIsCurrent) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + else if (NewStaticMesh && NewStaticMesh == OldStaticMesh) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + + UObject* OldProxyMesh = FoundOldOutputObj->ProxyObject; + if (OldProxyMesh && !OldProxyMesh->IsPendingKill()) + { + // If a new static mesh was created for a proxy, keep the proxy (will be hidden) + // ... unless we want to explicitly destroy proxies + if (NewStaticMesh && !bInDestroyProxies) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + else if (NewProxyMesh && (NewProxyMesh == OldProxyMesh)) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + } + + // The old map now only contains unused/stale Meshes/Components, delete them + for (auto& OldPair : OldOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; + FHoudiniOutputObject& OldOutputObject = OldPair.Value; + + // Remove the old component from the map + RemoveAndDestroyComponent(OldOutputObject.OutputComponent); + OldOutputObject.OutputComponent = nullptr; + // Remove the old proxy component from the map + RemoveAndDestroyComponent(OldOutputObject.ProxyComponent); + OldOutputObject.ProxyComponent = nullptr; + + if (OldOutputObject.OutputObject && !OldOutputObject.OutputObject->IsPendingKill()) + { + OldOutputObject.OutputObject->MarkPendingKill(); + } + + if (OldOutputObject.ProxyObject && !OldOutputObject.ProxyObject->IsPendingKill()) + { + OldOutputObject.ProxyObject->MarkPendingKill(); + } + } + OldOutputObjects.Empty(); + + /* + // Remove any stale components, these are components with OutputIdentifiers that are not + // in NewOutputObjects. This seems to happen mostly with the first or second cook after a + // "Rebuild Asset" + if (OutputComponents.Num() > 0 || OutputProxyComponents.Num() > 0) + { + TArray> StaleComponents; + const uint32 MaxNumStale = FMath::Max(OutputComponents.Num(), OutputProxyComponents.Num()); + StaleComponents.Reserve(MaxNumStale); + for (auto& ComponentPair : OutputComponents) + { + if (!NewOutputObjects.Contains(ComponentPair.Key) && !OldOutputObjectsReplacedByProxy.Contains(ComponentPair.Key)) + { + StaleComponents.Add(ComponentPair); + } + } + for (auto& ComponentPair : StaleComponents) + { + RemoveAndDestroyComponent(ComponentPair.Key, OutputComponents); + } + StaleComponents.Empty(MaxNumStale); + + for (auto& ComponentPair : OutputProxyComponents) + { + if (!NewOutputProxyObjects.Contains(ComponentPair.Key) && !OldOutputProxyObjectsReplacedByStaticMesh.Contains(ComponentPair.Key)) + { + StaleComponents.Add(ComponentPair); + } + } + for (auto& ComponentPair : StaleComponents) + { + RemoveAndDestroyComponent(ComponentPair.Key, OutputProxyComponents); + } + StaleComponents.Empty(); + } + */ + + // Now create/update the new static mesh components + for (auto& NewPair : InNewOutputObjects) + { + // Get the old Identifier / StaticMesh + const FHoudiniOutputObjectIdentifier& OutputIdentifier = NewPair.Key; + FHoudiniOutputObject& OutputObject = NewPair.Value; + + if (OutputObject.bIsImplicit) + { + // This output is implicit and shouldn't have a representative component/proxy in the scene + // Remove the old component from the map + if (OutputObject.OutputComponent) + { + RemoveAndDestroyComponent(OutputObject.OutputComponent); + OutputObject.OutputComponent = nullptr; + } + + // Remove the old proxy component from the map + RemoveAndDestroyComponent(OutputObject.ProxyComponent); + OutputObject.ProxyComponent = nullptr; + + continue; // Skip any proxy / component creation below + } + + // Check if we should create a Proxy/SMC + if (OutputObject.bProxyIsCurrent) + { + UObject *Mesh = OutputObject.ProxyObject; + if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + { + HOUDINI_LOG_ERROR(TEXT("Proxy Mesh is invalid (wrong type or pending kill)...")); + continue; + } + + // Create or update a new proxy component + TSubclassOf ComponentType = UHoudiniStaticMeshComponent::StaticClass(); + const FHoudiniGeoPartObject *FoundHGPO = nullptr; + bool bCreated = false; + UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); + if (MeshComponent) + { + UHoudiniStaticMeshComponent *HSMC = Cast(MeshComponent); + + if (bCreated) + { + PostCreateHoudiniStaticMeshComponent(HSMC, Mesh); + } + else if (HSMC && !HSMC->IsPendingKill() && HSMC->GetMesh() != Mesh) + { + // We need to reassign the HSM to the component + UHoudiniStaticMesh* HSM = Cast(Mesh); + HSMC->SetMesh(HSM); + } + + UpdateMeshComponent( + MeshComponent, + OutputIdentifier, + FoundHGPO, + InOutput->HoudiniCreatedSocketActors, + InOutput->HoudiniAttachedSocketActors, + bInApplyGenericProperties); + + if (!bCreated) + { + // For proxy meshes: notify that the mesh has been updated + HSMC->NotifyMeshUpdated(); + HSMC->SetHoudiniIconVisible(true); + } + } + + // Now, ensure that meshes replaced by proxies are still kept but hidden + USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); + if (SceneComponent) + { + SceneComponent->SetVisibility(false); + SceneComponent->SetHiddenInGame(true); + } + + // If the proxy mesh we just created is templated, hide it in game + if (FoundHGPO->bIsTemplated) + { + MeshComponent->SetHiddenInGame(true); + } + } + else + { + // Create a new SMC if needed + UObject* Mesh = OutputObject.OutputObject; + if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + { + HOUDINI_LOG_ERROR(TEXT("Mesh is invalid (wrong type or pending kill)...")); + continue; + } + + TSubclassOf ComponentType = UStaticMeshComponent::StaticClass(); + const FHoudiniGeoPartObject *FoundHGPO = nullptr; + bool bCreated = false; + UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); + if (MeshComponent) + { + if (bCreated) + { + PostCreateStaticMeshComponent(Cast(MeshComponent), Mesh); + } + UpdateMeshComponent( + MeshComponent, + OutputIdentifier, + FoundHGPO, + InOutput->HoudiniCreatedSocketActors, + InOutput->HoudiniAttachedSocketActors, + bInApplyGenericProperties); + } + + // Now, ensure that proxies replaced by meshes are still kept but hidden + UHoudiniStaticMeshComponent *HSMC = Cast(OutputObject.ProxyComponent); + if (HSMC) + { + HSMC->SetVisibility(false); + HSMC->SetHiddenInGame(true); + HSMC->SetHoudiniIconVisible(false); + } + + // If the mesh we just created is templated, hide it in game + if (FoundHGPO->bIsTemplated) + { + MeshComponent->SetHiddenInGame(true); + } + } + } + + // Assign the new output objects to the output + InOutput->SetOutputObjects(InNewOutputObjects); + + return true; +} + +void +FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, + const FHoudiniGeoPartObject *InHGPO, TArray &HoudiniCreatedSocketActors, TArray &HoudiniAttachedSocketActors, + bool bInApplyGenericProperties) +{ + // Update collision/visibility + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(InOutputIdentifier.SplitIdentifier); + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider) + { + // Invisible complex collider should not be seen + InMeshComponent->SetVisibility(false); + InMeshComponent->SetHiddenInGame(true); + InMeshComponent->SetCollisionProfileName(FName(TEXT("InvisibleWall"))); + InMeshComponent->SetCastShadow(false); + } + else + { + // Update visiblity + bool bVisible = InHGPO ? InHGPO->bIsVisible : true; + InMeshComponent->SetVisibility(bVisible); + InMeshComponent->SetHiddenInGame(!bVisible); + } + + // TODO: + // Update navmesh? + + // Transform the component by transformation provided by HAPI. + InMeshComponent->SetRelativeTransform(InHGPO->TransformMatrix); + + // If the static mesh had sockets, we can assign the desired actor to them now + UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); + UStaticMesh * StaticMesh = nullptr; + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + StaticMesh = StaticMeshComponent->GetStaticMesh(); + + if (StaticMesh && !StaticMesh->IsPendingKill()) + { + 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; + + AddActorsToMeshSocket(StaticMesh->Sockets[nSocket], StaticMeshComponent, HoudiniCreatedSocketActors, HoudiniAttachedSocketActors); + } + + // Iterate all remaining created socket actors, destroy the ones that are not assigned to socket after re-cook + { + for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniCreatedSocketActors[Idx]; + + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + continue; + } + + bool bFoundSocket = false; + for (auto & CurSocket : StaticMesh->Sockets) + { + if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) + { + bFoundSocket = true; + break; + } + } + // cur actor's attaching socket is found, skip + if (bFoundSocket) + continue; + + // Destroy the previous created socket actor if not found + HoudiniCreatedSocketActors.RemoveAt(Idx); + CurActor->Destroy(); + } + } + + // Detach the in level actors which is not attached to any socket now + { + for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor* CurActor = HoudiniAttachedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniAttachedSocketActors.RemoveAt(Idx); + continue; + } + + bool bFoundSocket = false; + for (auto & CurSocket : StaticMesh->Sockets) + { + if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) + { + bFoundSocket = true; + break; + } + } + + if (bFoundSocket) + continue; + + // If the attached socket name is not found in current socket, detach it and remove from the array + CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + HoudiniAttachedSocketActors.RemoveAt(Idx); + } + } + + } + + if (bInApplyGenericProperties) + { + // Clear the component tags as generic properties only add them + InMeshComponent->ComponentTags.Empty(); + // Update the property attributes on the component + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + InOutputIdentifier.GeoId, InOutputIdentifier.PartId, + true, + InOutputIdentifier.PrimitiveIndex, + INDEX_NONE, + InOutputIdentifier.PointIndex, + PropertyAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); + } + } +} + +bool +FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& InOutputObjects, + TMap& OutOutputObjects, + TMap& AssignmentMaterialMap, + TMap& ReplacementMaterialMap, + const bool& InForceRebuild, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InSMBuildSettings, + bool bInTreatExistingMaterialsAsUpToDate) +{ + // If we're not forcing the rebuild + // No need to recreate something that hasn't changed + if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged) && InOutputObjects.Num() > 0) + { + // Simply reuse the existing meshes + OutOutputObjects = InOutputObjects; + return true; + } + + FHoudiniMeshTranslator CurrentTranslator; + CurrentTranslator.ForceRebuild = InForceRebuild; + CurrentTranslator.SetHoudiniGeoPartObject(InHGPO); + CurrentTranslator.SetInputObjects(InOutputObjects); + CurrentTranslator.SetOutputObjects(OutOutputObjects); + CurrentTranslator.SetInputAssignmentMaterials(AssignmentMaterialMap); + CurrentTranslator.SetReplacementMaterials(ReplacementMaterialMap); + CurrentTranslator.SetPackageParams(InPackageParams, true); + CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); + CurrentTranslator.SetStaticMeshGenerationProperties(InSMGenerationProperties); + CurrentTranslator.SetStaticMeshBuildSettings(InSMBuildSettings); + + // TODO: Fetch from settings/HAC + CurrentTranslator.DefaultMeshSmoothing = 1; + if (false) + CurrentTranslator.DefaultMeshSmoothing = 0; + + // TODO: mechanism to determine when to use dynamic mesh for fast updates, and when to switch to + // baking the full static mesh + switch (InStaticMeshMethod) + { + case EHoudiniStaticMeshMethod::RawMesh: + CurrentTranslator.CreateStaticMesh_RawMesh(); + break; + case EHoudiniStaticMeshMethod::FMeshDescription: + CurrentTranslator.CreateStaticMesh_MeshDescription(); + break; + case EHoudiniStaticMeshMethod::UHoudiniStaticMesh: + CurrentTranslator.CreateHoudiniStaticMesh(); + break; + } + + // Copy the output objects/materials + OutOutputObjects = CurrentTranslator.OutputObjects; + AssignmentMaterialMap = CurrentTranslator.OutputAssignmentMaterials; + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartVertexList() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartVertexList")); + + if (HGPO.PartInfo.VertexCount <= 0) + return false; + + // Get the vertex List + PartVertexList.SetNumUninitialized(HGPO.PartInfo.VertexCount); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, &PartVertexList[0], 0, HGPO.PartInfo.VertexCount)) + { + // Error getting the vertex list. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + + return false; + } + + return true; +} + +void +FHoudiniMeshTranslator::SortSplitGroups() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::SortSplitGroups")); + + // Sort the splits in the order that we want to process them: + // Simple/Convex invisible colliders should be treated first as they will need to be attached to the visible meshes + TArray First; + + // The main geo and its LODs should be created after. + TArray Main; + TArray LODs; + + // Finally, visible colliders and invisible complex colliders as they need their own static mesh + TArray Last; + + for (auto& curSplit : HGPO.SplitGroups) + { + EHoudiniSplitType curSplitType = GetSplitTypeFromSplitName(curSplit); + switch (curSplitType) + { + case EHoudiniSplitType::InvisibleSimpleCollider: + case EHoudiniSplitType::InvisibleUCXCollider: + First.Add(curSplit); + break; + + case EHoudiniSplitType::Normal: + Main.Add(curSplit); + break; + + case EHoudiniSplitType::LOD: + LODs.Add(curSplit); + break; + + case EHoudiniSplitType::RenderedSimpleCollider: + case EHoudiniSplitType::RenderedUCXCollider: + case EHoudiniSplitType::RenderedComplexCollider: + case EHoudiniSplitType::InvisibleComplexCollider: + Last.Add(curSplit); + break; + } + } + + // Make sure LODs are order by name + LODs.Sort(); + + // Copy the split names in order + AllSplitGroups.Empty(); + for (auto& splitName : First) + AllSplitGroups.Add(splitName); + + for (auto& splitName : Main) + AllSplitGroups.Add(splitName); + + for (auto& splitName : LODs) + AllSplitGroups.Add(splitName); + + for (auto& splitName : Last) + AllSplitGroups.Add(splitName); +} + +bool +FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices")); + + // Reset the splits faces/indices arrays + AllSplitVertexLists.Empty(); + AllSplitVertexCounts.Empty(); + AllSplitFaceIndices.Empty(); + AllSplitFirstValidVertexIndex.Empty(); + AllSplitFirstValidPrimIndex.Empty(); + + bool bHasSplit = AllSplitGroups.Num() > 0; + if (bHasSplit) + { + HAPI_PartInfo PartInfo = FHoudiniEngineUtils::ToHAPIPartInfo(HGPO.PartInfo); + + // Buffer for all vertex indices used for split groups. + // We need this to figure out all vertex indices that are not part of them. + TArray AllVertexList; + AllVertexList.SetNumZeroed(PartVertexList.Num()); + + // Buffer for all face indices used for split groups. + // We need this to figure out all face indices that are not part of them. + TArray AllGroupFaceIndices; + AllGroupFaceIndices.SetNumZeroed(HGPO.PartInfo.FaceCount); + + // Some of the groups may contain invalid geometry + // Store them here so we can remove them afterwards + TArray InvalidGroupNameIndices; + + // Extract the vertices/faces for each of the split groups + for (int32 SplitIdx = 0; SplitIdx < AllSplitGroups.Num(); SplitIdx++) + { + const FString& GroupName = AllSplitGroups[SplitIdx]; + + // New vertex list just for this group. + TArray< int32 > GroupVertexList; + TArray< int32 > AllFaceList; + + int32 FirstValidPrimIndex = 0; + int32 FirstValidVertexIndex = 0; + // Extract vertex indices for this split. + int32 GroupVertexListCount = FHoudiniEngineUtils::HapiGetVertexListForGroup( + HGPO.GeoId, PartInfo, GroupName, + PartVertexList, GroupVertexList, + AllVertexList, AllFaceList, AllGroupFaceIndices, + FirstValidVertexIndex, FirstValidPrimIndex, + HGPO.PartInfo.bIsInstanced); + + if (GroupVertexListCount <= 0) + { + // This group doesn't have vertices/faces, mark it as invalid + InvalidGroupNameIndices.Add(SplitIdx); + + // Error getting the vertex list. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list for group %s - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, *GroupName); + + continue; + } + + // If list is not empty, we store it for this group - this will define new mesh. + AllSplitVertexLists.Add(GroupName, GroupVertexList); + AllSplitVertexCounts.Add(GroupName, GroupVertexListCount); + AllSplitFaceIndices.Add(GroupName, AllFaceList); + AllSplitFirstValidVertexIndex.Add(GroupName, FirstValidVertexIndex); + AllSplitFirstValidPrimIndex.Add(GroupName, FirstValidPrimIndex); + } + + if (InvalidGroupNameIndices.Num() > 0) + { + // Remove all invalid split groups + for (int32 InvalIdx = InvalidGroupNameIndices.Num() - 1; InvalIdx >= 0; InvalIdx--) + { + int32 Index = InvalidGroupNameIndices[InvalIdx]; + AllSplitGroups.RemoveAt(Index); + } + } + + // We also need to figure out / construct the vertex list for everything that's not in a split group + TArray GroupSplitFacesRemaining; + GroupSplitFacesRemaining.Init(-1, PartVertexList.Num()); + + int32 GroupVertexListCount = 0; + bool bHasMainSplitGroup = false; + TArray< int32 > GroupSplitFaceIndicesRemaining; + int32 FistUnusedVertexIndex = -1; + for (int32 SplitVertexIdx = 0; SplitVertexIdx < AllVertexList.Num(); SplitVertexIdx++) + { + if (AllVertexList[SplitVertexIdx] == 0) + { + // This is an unused index, we need to add it to unused vertex list. + FistUnusedVertexIndex = SplitVertexIdx; + GroupSplitFacesRemaining[SplitVertexIdx] = PartVertexList[SplitVertexIdx]; + bHasMainSplitGroup = true; + GroupVertexListCount++; + } + } + + int32 FistUnusedPrimIndex = -1; + for (int32 SplitFaceIdx = 0; SplitFaceIdx < AllGroupFaceIndices.Num(); SplitFaceIdx++) + { + if (AllGroupFaceIndices[SplitFaceIdx] == 0) + { + // This is unused face, we need to add it to unused faces list. + GroupSplitFaceIndicesRemaining.Add(SplitFaceIdx); + FistUnusedPrimIndex = SplitFaceIdx; + } + } + + // We store the remaining geo vertex list as a special split named "main geo" + // and make sure its treated before the collider meshes + if (bHasMainSplitGroup) + { + static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + AllSplitGroups.Add(RemainingGroupName); + AllSplitVertexLists.Add(RemainingGroupName, GroupSplitFacesRemaining); + AllSplitVertexCounts.Add(RemainingGroupName, GroupVertexListCount); + AllSplitFaceIndices.Add(RemainingGroupName, GroupSplitFaceIndicesRemaining); + AllSplitFirstValidPrimIndex.Add(RemainingGroupName, FistUnusedPrimIndex); + AllSplitFirstValidVertexIndex.Add(RemainingGroupName, FistUnusedVertexIndex); + } + } + else + { + // No splitting required + // Mark everything as the main geo group + static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + AllSplitGroups.Add(RemainingGroupName); + AllSplitVertexLists.Add(RemainingGroupName, PartVertexList); + AllSplitVertexCounts.Add(RemainingGroupName, PartVertexList.Num()); + AllSplitFirstValidPrimIndex.Add(RemainingGroupName, 0); + AllSplitFirstValidVertexIndex.Add(RemainingGroupName, 0); + + TArray AllFaces; + for (int32 FaceIdx = 0; FaceIdx < HGPO.PartInfo.FaceCount; ++FaceIdx) + AllFaces.Add(FaceIdx); + + AllSplitFaceIndices.Add(RemainingGroupName, AllFaces); + } + + return true; +} + +void +FHoudiniMeshTranslator::ResetPartCache() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::ResetPartCache")); + + // Vertex Positions + PartPositions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Vertex Normals + PartNormals.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Vertex TangentU + PartTangentU.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentU); + + // Vertex TangentV + PartTangentV.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentV); + + // Vertex Colors + PartColors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoColors); + + // Vertex Alpha values + PartAlphas.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoAlpha); + + // FaceSmoothing values + PartFaceSmoothingMasks.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceSmoothingMasks); + + // UVs + PartUVSets.Empty(); + AttribInfoUVSets.Empty(); + + // UVs + PartLightMapResolutions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoLightmapResolution); + + // Material IDs per face + PartFaceMaterialIds.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialIds); + // Unique material IDs + PartUniqueMaterialIds.Empty(); + // Material infos for each unique Material + PartUniqueMaterialInfos.Empty(); + // + bOnlyOneFaceMaterial = false; + + // Face Materials override + PartFaceMaterialOverrides.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialOverrides); + bMaterialOverrideNeedsCreateInstance = false; + + // LOD Screensize + PartLODScreensize.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoLODScreensize); +} + +bool +FHoudiniMeshTranslator::UpdatePartPositionIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartPositionIfNeeded")); + + // Only Retrieve the vertices positions if necessary + if (PartPositions.Num() > 0) + return true; + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve position data") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded")); + + // No need to read the normals if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadNormals = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + if (!bReadNormals) + return true; + + // Only Retrieve the normals if we haven't already + if (PartNormals.Num() > 0) + return true; + + // Retrieve normal data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, PartNormals); + + // There is no normals to fetch + if (!AttribInfoNormals.exists) + return true; + + if (!Success && AttribInfoNormals.exists) + { + // Error retrieving normals. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve normal data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded")) + + bool bReturn = true; + if (PartTangentU.Num() <= 0) + { + // Retrieve TangentU data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_TANGENTU, AttribInfoTangentU, PartTangentU); + + if (!Success && AttribInfoTangentU.exists) + { + // Error retrieving tangent. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentU data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + bReturn = false; + } + } + + if (PartTangentV.Num() <= 0) + { + // Retrieve TangentV data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_TANGENTV, AttribInfoTangentV, PartTangentV); + + if (!Success && AttribInfoTangentV.exists) + { + // Error retrieving tangent. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentV data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + bReturn = false; + } + } + + return bReturn; +} + +bool +FHoudiniMeshTranslator::UpdatePartColorsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartColorsIfNeeded")); + + // Only Retrieve the vertices colors if necessary + if (PartColors.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_COLOR, AttribInfoColors, PartColors); + + if (!Success && AttribInfoColors.exists) + { + // Error retrieving colors. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve color data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded")); + + // Only Retrieve the vertices alphas if necessary + if (PartAlphas.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_ALPHA, AttribInfoAlpha, PartAlphas); + + if (!Success && AttribInfoAlpha.exists) + { + // Error retrieving alpha values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve alpha data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceSmoothingIfNeeded() +{ + // Only Retrieve the vertices FaceSmoothing if necessary + if (PartFaceSmoothingMasks.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, + AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks); + + if (!Success && AttribInfoFaceSmoothingMasks.exists) + { + // Error retrieving FaceSmoothing values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve FaceSmoothing data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded")); + + // Only Retrieve uvs if necessary + if (PartUVSets.Num() > 0) + return true; + + PartUVSets.SetNum(MAX_STATIC_TEXCOORDS); + AttribInfoUVSets.SetNum(MAX_STATIC_TEXCOORDS); + + // The second UV set should be called uv2, but we will still check if need to look for a uv1 set. + // If uv1 exists, we'll look for uv, uv1, uv2 etc.. if not we'll look for uv, uv2, uv3 etc.. + bool bUV1Exists = FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, "uv1"); + + // Retrieve UVs. + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (TexCoordIdx > 0) + UVAttributeName += FString::Printf(TEXT("%d"), bUV1Exists ? TexCoordIdx : TexCoordIdx + 1); + + FHoudiniApi::AttributeInfo_Init(&AttribInfoUVSets[TexCoordIdx]); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*UVAttributeName), + AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], 2); + } + + // Also look for 16.5 uvs (attributes with a Texture type) + // For that, we'll have to iterate through ALL the attributes and check their types + TArray< FString > FoundAttributeNames; + TArray< HAPI_AttributeInfo > FoundAttributeInfos; + + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + FHoudiniEngineUtils::HapiGetAttributeOfType( + HGPO.GeoId, HGPO.PartId, (HAPI_AttributeOwner)AttrIdx, + HAPI_ATTRIBUTE_TYPE_TEXTURE, FoundAttributeInfos, FoundAttributeNames); + } + + if (FoundAttributeInfos.Num() <= 0) + return true; + + // We found some additionnal uv attributes + int32 AvailableIdx = 0; + for (int32 attrIdx = 0; attrIdx < FoundAttributeInfos.Num(); attrIdx++) + { + // Ignore the old uvs + if (FoundAttributeNames[attrIdx] == TEXT("uv") + || FoundAttributeNames[attrIdx] == TEXT("uv1") + || FoundAttributeNames[attrIdx] == TEXT("uv2") + || FoundAttributeNames[attrIdx] == TEXT("uv3") + || FoundAttributeNames[attrIdx] == TEXT("uv4") + || FoundAttributeNames[attrIdx] == TEXT("uv5") + || FoundAttributeNames[attrIdx] == TEXT("uv6") + || FoundAttributeNames[attrIdx] == TEXT("uv7") + || FoundAttributeNames[attrIdx] == TEXT("uv8")) + continue; + + HAPI_AttributeInfo CurrentAttrInfo = FoundAttributeInfos[attrIdx]; + if (!CurrentAttrInfo.exists) + continue; + + // Look for the next available index in the return arrays + for (; AvailableIdx < AttribInfoUVSets.Num(); AvailableIdx++) + { + if (!AttribInfoUVSets[AvailableIdx].exists) + break; + } + + // We are limited to MAX_STATIC_TEXCOORDS uv sets! + // If we already have too many uv sets, skip the rest + if ((AvailableIdx >= MAX_STATIC_TEXCOORDS) || (AvailableIdx >= AttribInfoUVSets.Num())) + { + HOUDINI_LOG_WARNING(TEXT("Too many UV sets found. Unreal only supports %d , skipping the remaining uv sets."), (int32)MAX_STATIC_TEXCOORDS); + break; + } + + // Force the tuple size to 2 ? + CurrentAttrInfo.tupleSize = 2; + + // Add the attribute infos we found + AttribInfoUVSets[AvailableIdx] = CurrentAttrInfo; + + // Allocate sufficient buffer for the attribute's data. + PartUVSets[AvailableIdx].SetNumUninitialized(CurrentAttrInfo.count * CurrentAttrInfo.tupleSize); + + // Get the texture coordinates + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, TCHAR_TO_UTF8(*(FoundAttributeNames[attrIdx])), + &AttribInfoUVSets[AvailableIdx], -1, + &PartUVSets[AvailableIdx][0], 0, CurrentAttrInfo.count)) + { + // Something went wrong when trying to access the uv values, invalidate this set + AttribInfoUVSets[AvailableIdx].exists = false; + } + } + + // Remove unused UV sets + if (bRemoveUnused) + { + for (int32 Idx = PartUVSets.Num() - 1; Idx >= 0; Idx--) + { + if (PartUVSets[Idx].Num() > 0) + continue; + + PartUVSets.RemoveAt(Idx); + } + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartLightmapResolutionsIfNeeded() +{ + // Only Retrieve the vertices lightmap resolution if necessary + if (PartLightMapResolutions.Num() > 0) + return true; + + // Get lightmap resolution (if present). + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, + AttribInfoLightmapResolution, PartLightMapResolutions); + + if (!Success && AttribInfoLightmapResolution.exists) + { + // Error retrieving lightmap resolution values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve lightmap resolution data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded")); + + // Only Retrieve the material IDs if necessary + if (PartFaceMaterialIds.Num() > 0) + return true; + + int32 NumFaces = HGPO.PartInfo.FaceCount; + if (NumFaces <= 0) + return true; + + PartFaceMaterialIds.SetNum(NumFaces); + + // Get the materials IDs per face + HAPI_Bool bSingleFaceMaterial = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, &bSingleFaceMaterial, + &PartFaceMaterialIds[0], 0, NumFaces)) + { + // Error retrieving material face assignments. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material face assignments"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + bOnlyOneFaceMaterial = bSingleFaceMaterial; + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded")); + + // Only Retrieve the material overrides if necessary + if (PartFaceMaterialOverrides.Num() > 0) + return true; + + bMaterialOverrideNeedsCreateInstance = false; + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + + // If material attribute was not found, check fallback compatibility attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + } + + // If material attribute and fallbacks were not found, check the material instance attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + + // We will we need to create material instances from the override attributes + bMaterialOverrideNeedsCreateInstance = AttribInfoFaceMaterialOverrides.exists; + } + + if (AttribInfoFaceMaterialOverrides.exists + && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_PRIM + && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_DETAIL) + { + HOUDINI_LOG_WARNING(TEXT("Static Mesh [%d %s], Geo [%d], Part [%d %s]: unreal_material must be a primitive or detail attribute, ignoring attribute."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + AttribInfoFaceMaterialOverrides.exists = false; + bMaterialOverrideNeedsCreateInstance = false; + PartFaceMaterialOverrides.Empty(); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartNeededMaterials() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials")); + + // Update the per face material IDs + UpdatePartFaceMaterialIDsIfNeeded(); + + // See if we have some material overides + UpdatePartFaceMaterialOverridesIfNeeded(); + + // If we have houdini materials AND overrides: + // We want to only create the Houdini materials that are not "covered" by overrides + // If we have material instance attributes, create all the houdini material anyway + // as their textures could be referenced by the material instance parameters + if (PartFaceMaterialOverrides.Num() > 0 && !bMaterialOverrideNeedsCreateInstance) + { + // If the material override was set on the detail, no need to look for houdini material IDs, as only the override will be used + if (AttribInfoFaceMaterialOverrides.exists && AttribInfoFaceMaterialOverrides.owner == HAPI_ATTROWNER_PRIM) + { + for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) + { + // Add a material ID to the unique array only if that face is not using the override + if (PartFaceMaterialOverrides[MaterialIdx].IsEmpty()) + PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); + } + } + } + else + { + // No material overrides, simply update the unique material array + for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) + PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); + } + + // Remove the invalid material ID from the unique array + PartUniqueMaterialIds.RemoveSingle(-1); + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials - Get the unique material infos")); + // Get the unique material infos + PartUniqueMaterialInfos.SetNum(PartUniqueMaterialIds.Num()); + for (int32 MaterialIdx = 0; MaterialIdx < PartUniqueMaterialIds.Num(); MaterialIdx++) + { + + FHoudiniApi::MaterialInfo_Init(&PartUniqueMaterialInfos[MaterialIdx]); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), + PartUniqueMaterialIds[MaterialIdx], + &PartUniqueMaterialInfos[MaterialIdx])) + { + // Error retrieving material face assignments. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material info for material %d"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, PartUniqueMaterialIds[MaterialIdx]); + continue; + } + } + } + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartLODScreensizeIfNeeded() +{ + // Only retrieve LOD screensizes if necessary + if (PartLODScreensize.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE, + AttribInfoLODScreensize, PartLODScreensize); + + if (!Success && AttribInfoLODScreensize.exists) + { + // Error retrieving FaceSmoothing values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve LOD screensizes"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + + +UStaticMesh* +FHoudiniMeshTranslator::CreateNewStaticMesh(const FString& InSplitIdentifier) +{ + // Update the current Obj/Geo/Part/Split IDs + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.GeoId; + PackageParams.PartId = HGPO.PartId; + PackageParams.SplitStr = InSplitIdentifier; + + UStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); + if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + return nullptr; + + return NewStaticMesh; +} + +UHoudiniStaticMesh* +FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentifier) +{ + // Update the current Obj/Geo/Part/Split IDs + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.GeoId; + PackageParams.PartId = HGPO.PartId; + // Add _HSM suffix to the split str, to distinguish the temporary HoudiniStaticMesh + // from the UStaticMesh + PackageParams.SplitStr = InSplitIdentifier + "_HSM"; + + UHoudiniStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); + if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + return nullptr; + + return NewStaticMesh; +} + +bool +FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() +{ + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + + double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Prepare the object that will store UCX and simple colliders + AllAggregateCollisions.Empty(); + + // We need to know the number of LODs that will be needed for this part + int32 NumberOfLODs = 0; + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) + NumberOfLODs++; + else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + bHasMainGeo = true; + } + + // Update the part's material's IDS and info now + CreateNeededMaterials(); + + // Check now if they were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Get the current target platform for default lod policies + ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + check(CurrentPlatform); + + // New mesh list + TMap StaticMeshToBuild; + + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; + + // bool MeshMaterialsHaveBeenReset = false; + + double tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre Split-Loop in %f seconds."), tick - time_start); + } + + UStaticMesh* MainStaticMesh = nullptr; + bool bAssignedCustomCollisionMesh = false; + ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; + + // Iterate through all detected split groups we care about and split geometry. + // The split are ordered in the following way: + // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + double split_tick = FPlatformTime::Seconds(); + + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + + // Get/Create the Aggregate Collisions for this mesh identifier + FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); + + // Handle UCX / Convex Hull colliders + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) + { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the convex hull colliders and add them to the Aggregate + if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) + continue; + } + else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) + { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the simple colliders and add them to the aggregate + if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) + continue; + } + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing SM from a previous cook + UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + // Prepare LOD Group data for this static mesh + FStaticMeshLODGroup LODGroup; + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing static mesh, create a new one + FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + + // Use the platform's default LODGroup policy + // TODO? Add setting for default LOD Group? + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); + } + else + { + // Try to reuse the existing SM's LOD group instead of the default one + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); + } + + if (SplitType == EHoudiniSplitType::Normal && !MainStaticMesh) + { + MainStaticMesh = FoundStaticMesh; + MainStaticMesh->ComplexCollisionMesh = nullptr; + MainStaticMesh->bCustomizedCollision = false; + // NOTE: The main static mesh collision trace flag will be set after all splits have been processed. + } + + if (!FoundOutputObject) + { + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + InputObjects.Remove(OutputObjectIdentifier); + } + else + { + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + FoundOutputObject->CachedAttributes.Empty(); + FoundOutputObject->CachedTokens.Empty(); + } + FoundOutputObject->bProxyIsCurrent = false; + + // TODO: Needed? + // Free any RHI resources for existing mesh before we re-create in place. + FoundStaticMesh->PreEditChange(NULL); + + // Check that the Static Mesh we found has the appropriate number of Source models/LODs + int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); + + // LODs are only for the "main" mesh, not for complex colliders! + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); + + if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) + { + while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) + FoundStaticMesh->AddSourceModel(); + + // We may have to remove excessive LOD levels + if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) + FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); + + // Initialize their default reduction setting + for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) + { + FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); + } + FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); + } + + // By default, always work on the first source model, unless we're a LOD + int32 SrcModelIndex = 0; + int32 LODIndex = 0; + if (SplitType == EHoudiniSplitType::LOD) + { + for (auto& curSplit : AllSplitGroups) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); + if (CurrentSplitType == EHoudiniSplitType::LOD + || CurrentSplitType == EHoudiniSplitType::Normal) + { + LODIndex++; + } + + if (curSplit == SplitGroupName) + break; + } + + // Fix for the case where we don't have a main geo + if(!bHasMainGeo) + LODIndex--; + } + + // Grab the appropriate SourceModel + FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; + if (!SrcModel) + { + HOUDINI_LOG_ERROR( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); + continue; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Load existing raw model. This will be empty as we are constructing a new mesh. + FRawMesh RawMesh; + if (!bRebuildStaticMesh) + { + // We dont need to rebuild the mesh itself: + // the geometry hasn't changed, but the materials have. + // We can just load the old data into the Raw mesh and reuse it. + SrcModel->LoadRawMesh(RawMesh); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - LoadRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + } + else + { + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's normal if needed + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + // Check that the number of normal we retrieved is correct + int32 WedgeNormalCount = SplitNormals.Num() / 3; + if (SplitNormals.Num() < 0 || !SplitNormals.IsValidIndex((WedgeNormalCount - 1) * 3 + 2)) + { + // Ignore normals + WedgeNormalCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); + } + + // Transfer the normals to the raw mesh + RawMesh.WedgeTangentZ.SetNumZeroed(WedgeNormalCount); + for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) + { + // Swap Y/Z for Coordinates conversion + RawMesh.WedgeTangentZ[WedgeTangentZIdx].X = SplitNormals[WedgeTangentZIdx * 3 + 0]; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].Y = SplitNormals[WedgeTangentZIdx * 3 + 2]; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].Z = SplitNormals[WedgeTangentZIdx * 3 + 1]; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Normals in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENTS + //--------------------------------------------------------------------------------------------------------------------- + + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + TArray< float > SplitTangentU; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + TArray< float > SplitTangentV; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + bool bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + + // Check that the number of tangents read matches the number of normals + int32 WedgeTangentUCount = SplitTangentU.Num() / 3; + int32 WedgeTangentVCount = SplitTangentV.Num() / 3; + if (WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount) + bGenerateTangents = true; + + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + + // Generate the tangents if needed + if (bGenerateTangents) + { + RawMesh.WedgeTangentX.SetNumZeroed(WedgeNormalCount); + RawMesh.WedgeTangentY.SetNumZeroed(WedgeNormalCount); + for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) + { + FVector TangentX, TangentY; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].FindBestAxisVectors(TangentX, TangentY); + + RawMesh.WedgeTangentX[WedgeTangentZIdx] = TangentX; + RawMesh.WedgeTangentY[WedgeTangentZIdx] = TangentY; + } + } + else + { + // Transfer the tangents we have read them and they're valid + RawMesh.WedgeTangentX.SetNumZeroed(WedgeTangentUCount); + for (int32 WedgeTangentUIdx = 0; WedgeTangentUIdx < WedgeTangentUCount; ++WedgeTangentUIdx) + { + // We need to flip Z and Y + RawMesh.WedgeTangentX[WedgeTangentUIdx].X = SplitTangentU[WedgeTangentUIdx * 3 + 0]; + RawMesh.WedgeTangentX[WedgeTangentUIdx].Y = SplitTangentU[WedgeTangentUIdx * 3 + 2]; + RawMesh.WedgeTangentX[WedgeTangentUIdx].Z = SplitTangentU[WedgeTangentUIdx * 3 + 1]; + } + + RawMesh.WedgeTangentY.SetNumZeroed(WedgeTangentVCount); + for (int32 WedgeTangentVIdx = 0; WedgeTangentVIdx < WedgeTangentVCount; ++WedgeTangentVIdx) + { + // We need to flip Z and Y + RawMesh.WedgeTangentY[WedgeTangentVIdx].X = SplitTangentV[WedgeTangentVIdx * 3 + 0]; + RawMesh.WedgeTangentY[WedgeTangentVIdx].Y = SplitTangentV[WedgeTangentVIdx * 3 + 2]; + RawMesh.WedgeTangentY[WedgeTangentVIdx].Z = SplitTangentV[WedgeTangentVIdx * 3 + 1]; + } + } + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Tangents in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // VERTEX COLORS AND ALPHAS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's colors if needed + UpdatePartColorsIfNeeded(); + + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract this part's alpha values if needed + UpdatePartAlphasIfNeeded(); + + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + + // Transfer colors and alphas if possible + int32 WedgeColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; + bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && WedgeColorsCount > 0; + bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == WedgeColorsCount); + if (bSplitColorValid) + { + RawMesh.WedgeColors.SetNumZeroed(WedgeColorsCount); + for (int32 WedgeColorIdx = 0; WedgeColorIdx < WedgeColorsCount; WedgeColorIdx++) + { + FLinearColor WedgeColor; + WedgeColor.R = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); + WedgeColor.G = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); + WedgeColor.B = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); + + if (bSplitAlphaValid) + { + // Use the Alpha attribute value + WedgeColor.A = FMath::Clamp(SplitAlphas[WedgeColorIdx], 0.0f, 1.0f); + } + else if (AttribInfoColors.tupleSize >= 4) + { + // Use the alpha value from the color attribute + WedgeColor.A = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); + } + else + { + WedgeColor.A = 1.0f; + } + + // Convert linear color to fixed color. + RawMesh.WedgeColors[WedgeColorIdx] = WedgeColor.ToFColor(false); + } + } + else + { + // TODO? Needed? New meshes wont have WedgeIndices yet!? + // No Colors or Alphas, init colors to White + FColor DefaultWedgeColor = FLinearColor::White.ToFColor(false); + WedgeColorsCount = RawMesh.WedgeIndices.Num(); + if (WedgeColorsCount > 0) + RawMesh.WedgeColors.Init(DefaultWedgeColor, WedgeColorsCount); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Cd and Alpha in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's FaceSmoothing values if needed + UpdatePartFaceSmoothingIfNeeded(); + + // Get the FaceSmoothing values for this split + TArray SplitFaceSmoothingMasks; + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); + + // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! + RawMesh.FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); + + // Check that the number of face smoothing values we retrieved is correct + int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; + if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) + { + // Ignore our face smoothing values + WedgeFaceSmoothCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); + } + + // Transfer the face smoothing masks to the raw mesh if we have any + for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) + { + RawMesh.FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - FaceSmoothing in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // UVS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's UV sets if needed + UpdatePartUVSetsIfNeeded(); + + // See if we need to transfer uv point attributes to vertex attributes. + TArray> SplitUVSets; + SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + } + + // Transfer UVs to the Raw Mesh + int32 UVChannelCount = 0; + int32 LightMapUVChannel = 0; + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; + + int32 WedgeUVCount = SplitUVs.Num() / 2; + if (SplitUVs.Num() > 0 && SplitUVs.IsValidIndex((WedgeUVCount - 1) * 2 + 1)) + { + RawMesh.WedgeTexCoords[TexCoordIdx].SetNumZeroed(WedgeUVCount); + for (int32 WedgeUVIdx = 0; WedgeUVIdx < WedgeUVCount; ++WedgeUVIdx) + { + // We need to flip V coordinate when it's coming from HAPI. + RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].X = SplitUVs[WedgeUVIdx * 2 + 0]; + RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].Y = 1.0f - SplitUVs[WedgeUVIdx * 2 + 1]; + } + + UVChannelCount++; + if (UVChannelCount <= 2) + LightMapUVChannel = TexCoordIdx; + } + else + { + RawMesh.WedgeTexCoords[TexCoordIdx].Empty(); + } + } + + // We must have at least one UV channel. If there's none, create one filled with zero data. + if (UVChannelCount == 0) + RawMesh.WedgeTexCoords[0].SetNumZeroed(SplitVertexCount); + + // Set the lightmap Coordinate Index + // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention + // If not, the first UV set will be used + FoundStaticMesh->LightMapCoordinateIndex = LightMapUVChannel; + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - UVs in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's LightmapResolution values if needed + UpdatePartLightmapResolutionsIfNeeded(); + + // make sure the mesh has a new lighting guid + FoundStaticMesh->LightingGuid = FGuid::NewGuid(); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Lightmap Resolutions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" + // So that IndicesMapper[ oldIndex ] => newIndex + TArray IndicesMapper; + IndicesMapper.Init(-1, SplitVertexList.Num()); + int32 CurrentMapperIndex = 0; + + // NeededVertices: + // Array containing the old index of the needed vertices for the current split + // NeededVertices[ newIndex ] => oldIndex + TArray< int32 > NeededVertices; + RawMesh.WedgeIndices.SetNumZeroed(SplitVertexCount); + + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) + || !IndicesMapper.IsValidIndex(WedgeIndices[1]) + || !IndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (IndicesMapper[WedgeIndices[i]] < 0) + { + // This old index has not yet been "converted" to a new index + NeededVertices.Add(WedgeIndices[i]); + IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; + CurrentMapperIndex++; + } + + // Replace the old index with the new one + WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; + } + + if (!RawMesh.WedgeIndices.IsValidIndex(ValidVertexId + 2)) + break; + + // Flip wedge indices to fix the winding order. + RawMesh.WedgeIndices[ValidVertexId + 0] = WedgeIndices[0]; + RawMesh.WedgeIndices[ValidVertexId + 1] = WedgeIndices[2]; + RawMesh.WedgeIndices[ValidVertexId + 2] = WedgeIndices[1]; + + // Check if we need to patch UVs. + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + if (RawMesh.WedgeTexCoords[TexCoordIdx].IsValidIndex(ValidVertexId + 2)) + { + Swap(RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 1], + RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 2]); + } + } + + // Check if we need to patch colors. + if (RawMesh.WedgeColors.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeColors[ValidVertexId + 1], RawMesh.WedgeColors[ValidVertexId + 2]); + + // Check if we need to patch Normals and tangents. + if (RawMesh.WedgeTangentZ.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentZ[ValidVertexId + 1], RawMesh.WedgeTangentZ[ValidVertexId + 2]); + + if (RawMesh.WedgeTangentX.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentX[ValidVertexId + 1], RawMesh.WedgeTangentX[ValidVertexId + 2]); + + if (RawMesh.WedgeTangentY.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentY[ValidVertexId + 1], RawMesh.WedgeTangentY[ValidVertexId + 2]); + + ValidVertexId += 3; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + UpdatePartPositionIfNeeded(); + + // + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + int32 VertexPositionsCount = NeededVertices.Num(); + RawMesh.VertexPositions.SetNumZeroed(VertexPositionsCount); + + for (int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx) + { + int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; + if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // We need to swap Z and Y coordinate here, and convert from m to cm. + RawMesh.VertexPositions[VertexPositionIdx].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + RawMesh.VertexPositions[VertexPositionIdx].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + RawMesh.VertexPositions[VertexPositionIdx].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + /* + // TODO: + // Check if this mesh contains only degenerate triangles. + if (FHoudiniEngineUtils::CountDegenerateTriangles(RawMesh) == SplitGroupFaceCount) + { + // This mesh contains only degenerate triangles, there's nothing we can do. + if (bStaticMeshCreated) + StaticMesh->MarkPendingKill(); + + continue; + } + */ + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL ATTRIBUTE OVERRIDES + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: These are actually per faces, not per vertices... + // Need to update!! + UpdatePartFaceMaterialOverridesIfNeeded(); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Material Overrides in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Handle Materials!!!! + + // Get face indices for this split. + TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; + + // // We need to reset the Static Mesh's materials once per SM: + // // so, for the first lod, or the main geo... + // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // { + // FoundStaticMesh->StaticMaterials.Empty(); + // MeshMaterialsHaveBeenReset = true; + // } + // + // // .. or for each visible complex collider + // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + // FoundStaticMesh->StaticMaterials.Empty(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) + { + FoundStaticMesh->StaticMaterials.Empty(); + } + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); + + // Process material overrides first + if (PartFaceMaterialOverrides.Num() > 0) + { + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + + // If the part has material overrides + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + continue; + + UMaterialInterface * MaterialInterface = nullptr; + int32 CurrentFaceMaterialIdx = 0; + const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) + { + // Try to locate the corresponding material interface + + // Start by looking in our assignment map + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); + } + else + { + // The Attribute Material and its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + if (!MaterialInterface) + { + // If everything fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + } + } + + if (MaterialInterface) + { + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + + // Update the Face Material on the mesh + RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + } + } + } + else if (PartUniqueMaterialIds.Num() > 0) + { + // The part has houdini materials + if (bOnlyOneFaceMaterial) + { + // We have only one material. + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Empty(); + FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + else + { + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + // Reset Rawmesh material face assignments. + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) + { + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } + } + else + { + MaterialInterface = Cast(DefaultMaterial); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + + if (MaterialInterface) + { + // Add the material to the Static mesh + int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); + + // Update the face index + RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } + } + } + } + else + { + // No materials were found, we need to use default Houdini material. + int32 SplitFaceCount = SplitFaceIndices.Num(); + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceCount); + + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Empty(); + FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Face Materials in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Update the Build Settings using the default setting values + UpdateMeshBuildSettings( + SrcModel->BuildSettings, + RawMesh.WedgeTangentZ.Num() > 0, + (RawMesh.WedgeTangentX.Num() > 0 && RawMesh.WedgeTangentY.Num() > 0), + RawMesh.WedgeTexCoords->Num() > 0); + + // Check for a lightmap resolution override + int32 LightMapResolutionOverride = -1; + if (PartLightMapResolutions.Num() > 0) + LightMapResolutionOverride = PartLightMapResolutions[0]; + + if (LightMapResolutionOverride > 0) + FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; + else + FoundStaticMesh->LightMapResolution = 64; + + // TODO + //StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; + //StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; + //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings; + + // TODO: + // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? + + // This was required due to the impeding deprecation of FRawMesh + // If we dont update this UE4 will crash upon deleting an asset. + //SrcModel->StaticMeshOwner = FoundStaticMesh; + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreSaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Store the new raw mesh if it is valid + if (RawMesh.IsValid()) + { + SrcModel->SaveRawMesh(RawMesh); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("[CreateStaticMesh_RawMesh]: Invalid StaticMesh data for %s LOD %i in cook output! Please check the log."), + *FoundStaticMesh->GetName(), LODIndex); + // Create an "empty" valid raw mesh (single zero-area triangle) + // TODO: is there a cleaner way to do this? Perhaps committing an empty mesh description? Empty RawMesh is + // a no-op on SrcModel->SaveRawMesh (leaves previous data in place). + // TODO: perhaps we can use an alternative "error" mesh? + RawMesh.Empty(); + RawMesh.VertexPositions.Add(FVector::ZeroVector); + RawMesh.WedgeIndices.SetNumZeroed(3); + RawMesh.WedgeTexCoords[0].Init(FVector2D::ZeroVector, RawMesh.WedgeIndices.Num()); + SrcModel->SaveRawMesh(RawMesh); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - SaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + { + // Patch the MeshDescription data structure that is being output from SaveRawMesh. SaveRawMesh leaves invalid entries + // in the PolyGroups / MaterialSlotNames arrays that causes issues later when the static mesh is built and LOD material assignments + // are being done (materials aren't correctly assigned to LODs if LODs use different materials). + + // Create a Polygon Group for each material slot + TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = + SrcModel->MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); + + // We must use the number of assignment materials found to reserve the number of material slots + // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials + int32 NumberOfMaterials = FoundStaticMesh->StaticMaterials.Num(); + if (NumberOfMaterials <= 0) + { + // No materials, create a polygon group for the default one + const FPolygonGroupID& PolygonGroupID = SrcModel->MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + } + else + { + FPolygonGroupArray& PolyGroups = SrcModel->MeshDescription->PolygonGroups(); + for (auto& CurrentMatAssignment : OutputAssignmentMaterials) + { + const FPolygonGroupID& PolygonGroupID = SrcModel->MeshDescription->CreatePolygonGroup(); + + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = + FName(CurrentMatAssignment.Value ? *(CurrentMatAssignment.Value->GetName()) : *(CurrentMatAssignment.Key)); + } + } + } + + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = GetLODSCreensizeForSplit(SplitGroupName); + if (screensize >= 0.0f) + { + // Only apply the LOD screensize if it's valid + SrcModel->ScreenSize = screensize; + //FoundStaticMesh->GetSourceModel(LODIndex).ScreenSize = screensize; + FoundStaticMesh->bAutoComputeLODScreenSize = false; + } + + // TODO: + // SET STATIC MESH GENERATION PARAM + // HANDLE COLLIDERS + // REMOVE OLD COLLIDERS + // CUSTOM BAKE NAME OVERRIDE + + // Update property attributes on the SM + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + HGPO.GeoId, HGPO.PartId, + true, + AllSplitFirstValidPrimIndex[SplitGroupName], + INDEX_NONE, + AllSplitFirstValidVertexIndex[SplitGroupName], + PropertyAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + FoundStaticMesh, PropertyAttributes); + } + + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray TileValues; + if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Attributes in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Notify that we created a new Static Mesh if needed + if (bNewStaticMeshCreated) + FAssetRegistryModule::AssetCreated(FoundStaticMesh); + + // Add the Static mesh to the output maps and the build map if we haven't already + if (FoundOutputObject) + { + FoundOutputObject->OutputObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = false; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + + StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Total Split time: %f seconds."), tick - split_tick); + } + } + + // Look if we only have colliders + // If we do, we'll allow attaching sockets to the collider meshes + bool bCollidersOnly = true; + for (auto& Current : StaticMeshToBuild) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) + { + bCollidersOnly = false; + break; + } + } + + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + for (auto& Current : StaticMeshToBuild) + { + tick = FPlatformTime::Seconds(); + + UStaticMesh* SM = Current.Value; + if (!SM || SM->IsPendingKill()) + continue; + + UBodySetup * BodySetup = SM->BodySetup; + if (!BodySetup) + { + SM->CreateBodySetup(); + BodySetup = SM->BodySetup; + } + + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + + // Handle the Static Mesh's colliders + if (BodySetup && !BodySetup->IsPendingKill()) + { + // Make sure rendering is done - so we are not changing data being used by collision drawing. + FlushRenderingCommands(); + + // Clean up old colliders from a previous cook + BodySetup->Modify(); + BodySetup->RemoveSimpleCollision(); + + FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; + FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); + if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) + { + BodySetup->AddCollisionFrom(*CurrentAggColl); + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; + } + + // See if we need to enable collisions on the whole mesh + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + // Complex collider, enable collisions for this (collider) static mesh. + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + + // Apply the collider to the Main static mesh, if relevant. + ApplyComplexColliderHelper( + MainStaticMesh, + SM, + SplitType, + bAssignedCustomCollisionMesh, + OutputObjects.Find(Current.Key)); + } + else + { + // TODO + // if the LODForCollision uproperty attribute is set, we need to activate complex collision + // on the static mesh for that lod to be picked up properly as a collider + if (FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, + "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) + { + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + } + } + + // Add the Sockets to the StaticMesh + // We only add them to the main geo, or to the colliders if we only generate colliders + bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; + if (bAddSocket) + { + if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, HGPO.AllMeshSockets, true)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); + } + } + + if (MainStaticMesh) + { + UBodySetup* MainBodySetup = MainStaticMesh->BodySetup; + if (!IsValid(MainBodySetup)) + { + MainStaticMesh->CreateBodySetup(); + MainBodySetup = MainStaticMesh->BodySetup; + } + + check(MainBodySetup); + // Set the main static mesh to whatever the final CTF should be. + MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // BUILD the Static Mesh + // bSilent doesnt add the Build Errors... + double build_start = FPlatformTime::Seconds(); + TArray SMBuildErrors; + SM->Build(true, &SMBuildErrors); + if (bDoTiming) + { + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - StaticMesh->Build() executed in %f seconds."), tick - build_start); + } + + // This replaces the call to RefreshCollision below, but without CreateNavCollision + // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, + // and can be expensive depending on the vert/poly count of the mesh + // RefreshCollisionChange(*SM); + { + for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + { + UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); + if (StaticMeshComponent->GetStaticMesh() == SM) + { + // it needs to recreate IF it already has been created + if (StaticMeshComponent->IsPhysicsStateCreated()) + { + StaticMeshComponent->RecreatePhysicsState(); + } + } + } + + FEditorSupportDelegates::RedrawAllViewports.Broadcast(); + } + + SM->GetOnMeshChanged().Broadcast(); + + UPackage* MeshPackage = SM->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + } + } + + // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup + // Here as it has already been handled by the StaticMesh Build call + + double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() executed in %f seconds."), time_end - time_start); + + return true; +} + +bool +FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() +{ + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + + double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + // Simple colliders first, lods and finally, invisible colliders (that are separate Static Mesh) + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Prepare the object that will store UCX and simple colliders + AllAggregateCollisions.Empty(); + + // We need to know the number of LODs that will be needed for this part + int32 NumberOfLODs = 0; + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) + NumberOfLODs++; + else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + bHasMainGeo = true; + } + + // Update the part's material's IDS and info now + CreateNeededMaterials(); + + // Check if the materials were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Get the current target platform for default lod policies + ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + check(CurrentPlatform); + + // New mesh list + TMap StaticMeshToBuild; + + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; + + bool MeshMaterialsHaveBeenReset = false; + + double tick = FPlatformTime::Seconds(); + if (bDoTiming) + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); + + UStaticMesh* MainStaticMesh = nullptr; + bool bAssignedCustomCollisionMesh = false; + ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; + + // Iterate through all detected split groups we care about and split geometry. + // The split are ordered in the following way: + // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + double split_tick = FPlatformTime::Seconds(); + + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + + // Get/Create the Aggregate Collisions for this mesh identifier + FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); + + // Handle UCX / Convex Hull colliders + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) + { + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the convex hull colliders and add them to the Aggregate + if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) + continue; + } + else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) + { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the simple colliders and add them to the aggregate + if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) + continue; + } + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing SM from a previous cook + UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + // Prepare LOD Group data for this static mesh + FStaticMeshLODGroup LODGroup; + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing static mesh, create a new one + FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + + // Use the platform's default LODGroup policy + // TODO? Add setting for default LOD Group? + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); + } + else + { + // Try to reuse the existing SM's LOD group instead of the default one + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); + } + + if (SplitType == EHoudiniSplitType::Normal) + { + MainStaticMesh = FoundStaticMesh; + MainStaticMesh->ComplexCollisionMesh = nullptr; + MainStaticMesh->bCustomizedCollision = false; + } + + if (!FoundOutputObject) + { + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + } + else + { + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + FoundOutputObject->CachedAttributes.Empty(); + FoundOutputObject->CachedTokens.Empty(); + } + FoundOutputObject->bProxyIsCurrent = false; + + // TODO: Needed? + // Free any RHI resources for existing mesh before we re-create in place. + FoundStaticMesh->PreEditChange(NULL); + + // Check that the Static Mesh we found has the appropriate number of Source models/LODs + int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); + + // LODs are only for the "main" mesh, not for complex colliders! + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); + + if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) + { + while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) + FoundStaticMesh->AddSourceModel(); + + // We may have to remove excessive LOD levels + if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) + FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); + + // Initialize their default reduction setting + for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) + { + FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); + } + FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); + } + + // By default, always work on the first source model, unless we're a LOD + int32 SrcModelIndex = 0; + int32 LODIndex = 0; + if (SplitType == EHoudiniSplitType::LOD) + { + for (auto& curSplit : AllSplitGroups) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); + if (CurrentSplitType == EHoudiniSplitType::LOD + || CurrentSplitType == EHoudiniSplitType::Normal) + { + LODIndex++; + } + + if (curSplit == SplitGroupName) + break; + } + + // Fix for the case where we don't have a main geo + if(!bHasMainGeo) + LODIndex--; + } + + // Grab the appropriate SourceModel + FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; + if (!SrcModel) + { + HOUDINI_LOG_ERROR( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); + continue; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + bool bHasNormal = false; + bool bHasTangents = false; + + // Load the existing mesh description if we don't need to rebuild the mesh + FMeshDescription* MeshDescription; + if (!bRebuildStaticMesh) + { + // We dont need to rebuild the mesh itself: + // the geometry hasn't changed, but the materials have. + // We can just reuse the old MeshDescription and reuse it. + MeshDescription = FoundStaticMesh->GetMeshDescription(LODIndex); + } + else + { + // Extract all the data needed for this split + // Start by initializing the MeshDescription for this LOD + MeshDescription = FoundStaticMesh->CreateMeshDescription(LODIndex); + FStaticMeshAttributes(*MeshDescription).Register(); + + // Mesh description uses material to create its PolygonGroups, + // so we first need to know how many different materials we have for this split + // and what vertices/indices belong to each material for remapping + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // SplitNeededVertices + // Array containing the (unique) part indices for the vertices that are needed for this split + // SplitNeededVertices[splitIndex] = PartIndex + TArray SplitNeededVertices; + //SplitNeededVertices.SetNumZeroed(SplitVertexCount); + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" so that IndicesMapper[ partIndex ] => splitIndex + TArray PartToSplitIndicesMapper; + PartToSplitIndicesMapper.Init(-1, SplitVertexList.Num()); + //TMap SplitToPartIndicesMapper; + + // SplitIndices + // Array of SplitIndices used to describe this split's polygons + TArray SplitIndices; + SplitIndices.SetNumZeroed(SplitVertexCount); + + int32 CurrentSplitIndex = 0; + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[0]) + || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[1]) + || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (PartToSplitIndicesMapper[WedgeIndices[i]] < 0) + { + // This part index has not yet been "converted" to a new split index + SplitNeededVertices.Add(WedgeIndices[i]); + PartToSplitIndicesMapper[WedgeIndices[i]] = CurrentSplitIndex; + //SplitToPartIndicesMapper.Add(CurrentSplitIndex, WedgeIndices[i]); + CurrentSplitIndex++; + } + + // Replace the old part index with the new split index + WedgeIndices[i] = PartToSplitIndicesMapper[WedgeIndices[i]]; + } + + if (!SplitIndices.IsValidIndex(ValidVertexId + 2)) + break; + + // Flip wedge indices to fix the winding order. + SplitIndices[ValidVertexId + 0] = WedgeIndices[0]; + SplitIndices[ValidVertexId + 1] = WedgeIndices[2]; + SplitIndices[ValidVertexId + 2] = WedgeIndices[1]; + + ValidVertexId += 3; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract position for this part + UpdatePartPositionIfNeeded(); + + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + TVertexAttributesRef VertexPositions = + MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); + + MeshDescription->ReserveNewVertices(SplitNeededVertices.Num()); + for ( const int32& NeededVertexIndex : SplitNeededVertices) + { + // Create a new Vertex + FVertexID VertexID = MeshDescription->CreateVertex(); + if (PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexPositions[VertexID].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexPositions[VertexID].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexPositions[VertexID].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + else + { + // Error when retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // // TODO: Check if still needed for MeshDescription + // // We need to reset the Static Mesh's materials once per SM: + // // so, for the first lod, or the main geo... + // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // { + // FoundStaticMesh->StaticMaterials.Empty(); + // MeshMaterialsHaveBeenReset = true; + // } + // + // // .. or for each visible complex collider + // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + // FoundStaticMesh->StaticMaterials.Empty(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) + { + FoundStaticMesh->StaticMaterials.Empty(); + } + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); + + // Get this split's faces + TArray& SplitGroupFaceIndices = AllSplitFaceIndices[SplitGroupName]; + // Array holding the materials needed for this split + //TArray SplitMaterials; + // Split Material indices per face, by default all faces are set to use the first Material + TArray SplitFaceMaterialIndices; + SplitFaceMaterialIndices.SetNumZeroed(SplitGroupFaceIndices.Num()); + + bool HasHoudiniMaterials = PartUniqueMaterialIds.Num() > 0; + bool HasMaterialOverrides = PartFaceMaterialOverrides.Num() > 0; + if (!HasHoudiniMaterials && !HasMaterialOverrides) + { + // We don't have any material override or houdini material + // we just need one polygon group using the default Houdini material. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Empty(); + FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + + // TODO: ? Add default mat to the assignement map? + } + else if (HasHoudiniMaterials && !HasMaterialOverrides) + { + // We have Houdini Material but no overrides + if (bOnlyOneFaceMaterial || PartUniqueMaterialIds.Num() == 1) + { + // We have only one Houdini material. + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMesh->StaticMaterials.Empty(); + FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + + // TODO: ? Add the mat to the assignement map? + } + else + { + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * MaterialDefault = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + // Reset Rawmesh material face assignments. + for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) + { + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } + } + else + { + MaterialInterface = Cast(MaterialDefault); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + + if (MaterialInterface) + { + // Add the material to the Static mesh + //int32 UnrealMatIndex = SplitMaterials.Add(Material); + int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(MaterialInterface); + + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); + + // Update the face index + SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } + } + } + } + else + { + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + + // If we have material overrides + for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; + + UMaterialInterface * MaterialInterface = nullptr; + int32 CurrentFaceMaterialIdx = -1; + if (PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + { + const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) + { + // Try to locate the corresponding material interface + + // Start by looking in our assignment map + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast< UMaterialInterface >( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add this material to the map + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); + } + } + + if (!MaterialInterface) + { + // The attribute Material or its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) + { + // If everything else fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + } + } + + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + + // Update the Face Material on the mesh + SplitFaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + } + } + + // Create a Polygon Group for each material slot + TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = + MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); + + // We must use the number of assignment materials found to reserve the number of material slots + // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials + int32 NumberOfMaterials = OutputAssignmentMaterials.Num(); + if (NumberOfMaterials <= 0) + { + // No materials, create a polygon group for the default one + const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + } + else + { + MeshDescription->ReserveNewPolygonGroups(NumberOfMaterials); + //for (int32 MatIndex = 0; MatIndex < NumberOfMaterials; ++MatIndex) + for (auto& CurrentMatAssignement : OutputAssignmentMaterials) + { + const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = + FName(CurrentMatAssignement.Value ? *(CurrentMatAssignement.Value->GetName()) : *(CurrentMatAssignement.Key)); + } + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // + // VERTEX INSTANCE ATTRIBUTES + // NORMALS, TANGENTS, COLORS, UVS, Alpha + // + + // Extract the normals + UpdatePartNormalsIfNeeded(); + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); + + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + + // Extract the tangents + TArray SplitTangentU; + TArray SplitTangentV; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + int32 NormalCount = SplitNormals.Num(); + bool bGenerateTangents = (NormalCount > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + // Check that the number of tangents read matches the number of normals + if (SplitTangentU.Num() != NormalCount || SplitTangentV.Num() != NormalCount) + bGenerateTangents = true; + + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + + // Generate the tangents if needed + if (bGenerateTangents) + { + SplitTangentU.SetNumZeroed(NormalCount); + SplitTangentV.SetNumZeroed(NormalCount); + for (int32 Idx = 0; Idx + 2 < NormalCount; Idx += 3) + { + FVector TangentZ; + TangentZ.X = SplitNormals[Idx + 0]; + TangentZ.Y = SplitNormals[Idx + 2]; + TangentZ.Z = SplitNormals[Idx + 1]; + + FVector TangentX, TangentY; + TangentZ.FindBestAxisVectors(TangentX, TangentY); + + SplitTangentU[Idx + 0] = TangentX.X; + SplitTangentU[Idx + 2] = TangentX.Y; + SplitTangentU[Idx + 1] = TangentX.Z; + + SplitTangentV[Idx + 0] = TangentY.X; + SplitTangentV[Idx + 2] = TangentY.Y; + SplitTangentV[Idx + 1] = TangentY.Z; + } + } + } + TVertexInstanceAttributesRef VertexInstanceTangents = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); + TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); + + // Extract the color values + UpdatePartColorsIfNeeded(); + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract the alpha values + UpdatePartAlphasIfNeeded(); + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + TVertexInstanceAttributesRef VertexInstanceColors = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); + + // Extract UVs + UpdatePartUVSetsIfNeeded(true); + // See if we need to transfer uv point attributes to vertex attributes. + int32 UVSetCount = PartUVSets.Num(); + TArray> SplitUVSets; + SplitUVSets.SetNum(UVSetCount); + for (int32 TexCoordIdx = 0; TexCoordIdx < UVSetCount; TexCoordIdx++) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + } + TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); + VertexInstanceUVs.SetNumIndices(UVSetCount); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Allocate space for the vertex instances and polygons + MeshDescription->ReserveNewVertexInstances(SplitIndices.Num()); + MeshDescription->ReserveNewPolygons(SplitIndices.Num() / 3); + //Approximately 2.5 edges per polygons + MeshDescription->ReserveNewEdges(SplitIndices.Num() * 2.5f / 3); + + bHasNormal = SplitNormals.Num() > 0; + bHasTangents = SplitTangentU.Num() > 0 && SplitTangentV.Num() > 0; + bool bHasRGB = SplitColors.Num() > 0; + bool bHasRGBA = bHasRGB && AttribInfoColors.tupleSize == 4; + bool bHasAlpha = SplitAlphas.Num() > 0; + + TArray HasUVSets; + HasUVSets.SetNumZeroed(PartUVSets.Num()); + for (int32 Idx = 0; Idx < PartUVSets.Num(); Idx++) + HasUVSets[Idx] = PartUVSets[Idx].Num() > 0; + + uint32 FaceCount = SplitIndices.Num() / 3; + for (uint32 FaceIndex = 0; FaceIndex < FaceCount; FaceIndex++) + { + TArray FaceVertexInstanceIDs; + FaceVertexInstanceIDs.SetNum(3); + + // Ignore degenerate triangles + FVertexID VertexIDs[3]; + for (int32 Corner = 0; Corner < 3; ++Corner) + { + VertexIDs[Corner] = FVertexID(SplitIndices[(FaceIndex * 3) + Corner]); + } + if (VertexIDs[0] == VertexIDs[1] || VertexIDs[0] == VertexIDs[2] || VertexIDs[1] == VertexIDs[2]) + continue; + + //FVertexID FaceVertexIDs[3]; + for (int32 Corner = 0; Corner < 3; Corner++) + { + uint32 SplitIndex = (FaceIndex * 3) + Corner; + uint32 SplitVertexIndex = SplitIndices[SplitIndex]; + const FVertexInstanceID& VertexInstanceID = MeshDescription->CreateVertexInstance(FVertexID(SplitVertexIndex)); + + // Fix the winding order by updating the SplitIndex (invert corner 1 and 2) + // instead of going 0 1 2 go 0 2 1 + // TODO; this slows down StaticMesh->Build() considerably! + Corner == 1 ? SplitIndex++ : Corner == 2 ? SplitIndex-- : SplitIndex; + + const uint32 SplitVertexIndex_X = SplitIndex * 3 + 0; + const uint32 SplitVertexIndex_Y = SplitIndex * 3 + 2; + const uint32 SplitVertexIndex_Z = SplitIndex * 3 + 1; + // Normals + if (bHasNormal) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexInstanceNormals[VertexInstanceID].X = SplitNormals[SplitVertexIndex_X]; + VertexInstanceNormals[VertexInstanceID].Y = SplitNormals[SplitVertexIndex_Y]; + VertexInstanceNormals[VertexInstanceID].Z = SplitNormals[SplitVertexIndex_Z]; + } + + // Tangents and binormals + if (bHasTangents) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexInstanceTangents[VertexInstanceID].X = SplitTangentU[SplitVertexIndex_X]; + VertexInstanceTangents[VertexInstanceID].Y = SplitTangentU[SplitVertexIndex_Y]; + VertexInstanceTangents[VertexInstanceID].Z = SplitTangentU[SplitVertexIndex_Z]; + + FVector TangentY; + TangentY.X = SplitTangentV[SplitVertexIndex_X]; + TangentY.Y = SplitTangentV[SplitVertexIndex_Y]; + TangentY.Z = SplitTangentV[SplitVertexIndex_Z]; + + VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign( + VertexInstanceTangents[VertexInstanceID].GetSafeNormal(), + TangentY.GetSafeNormal(), + VertexInstanceNormals[VertexInstanceID].GetSafeNormal()); + } + + // Color + FLinearColor Color = FLinearColor::White; + if (bHasRGB) + { + Color.R = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); + Color.G = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); + Color.B = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); + } + // Alpha + if (bHasAlpha) + { + Color.A = FMath::Clamp(SplitAlphas[SplitIndex], 0.0f, 1.0f); + } + else if (bHasRGBA) + { + Color.A = FMath::Clamp(SplitColors[SplitIndex * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); + } + VertexInstanceColors[VertexInstanceID] = FVector4(Color); + + // UVs + for (int32 UVIndex = 0; UVIndex < SplitUVSets.Num(); UVIndex++) + { + if (HasUVSets[UVIndex]) + { + // We need to flip V coordinate when it's coming from HAPI. + FVector2D CurrentUV; + CurrentUV.X = SplitUVSets[UVIndex][SplitIndex * 2 + 0]; + CurrentUV.Y = 1.0f - SplitUVSets[UVIndex][SplitIndex * 2 + 1]; + + VertexInstanceUVs.Set(VertexInstanceID, UVIndex, CurrentUV); + } + } + + FaceVertexInstanceIDs[Corner] = VertexInstanceID; + } + + const FPolygonGroupID PolygonGroupID(SplitFaceMaterialIndices[FaceIndex]); + + // Insert a triangle into the mesh + MeshDescription->CreateTriangle(PolygonGroupID, FaceVertexInstanceIDs); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's FaceSmoothing values if needed + UpdatePartFaceSmoothingIfNeeded(); + + // Get the FaceSmoothing values for this split + TArray SplitFaceSmoothingMasks; + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); + + // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! + // TODO: Expose the default FaceSmoothing value + // 0 will make hard face + TArray FaceSmoothingMasks; + FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); + + // Check that the number of face smoothing values we retrieved is correct + int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; + if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) + { + // Ignore our face smoothing values + WedgeFaceSmoothCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); + } + + // Transfer the face smoothing masks to the raw mesh if we have any + for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) + { + FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; + } + + // TODO + // Check + FStaticMeshOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, *MeshDescription); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + // Extract this part's LightmapResolution values if needed + UpdatePartLightmapResolutionsIfNeeded(); + + // make sure the mesh has a new lighting guid + FoundStaticMesh->LightingGuid = FGuid::NewGuid(); + } + + // Update the Build Settings using the default setting values + UpdateMeshBuildSettings( + SrcModel->BuildSettings, + bHasNormal, + bHasTangents, + PartUVSets.Num() > 0); + + // Set the lightmap Coordinate Index + // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention + FoundStaticMesh->LightMapCoordinateIndex = PartUVSets.Num() > 1 ? 1 : 0; + + // Check for a lightmapa resolution override + int32 LightMapResolutionOverride = -1; + if ( PartLightMapResolutions.Num() > 0) + LightMapResolutionOverride = PartLightMapResolutions[0]; + + if (LightMapResolutionOverride > 0) + FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; + else + FoundStaticMesh->LightMapResolution = 64; + + // TODO: + // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? + + // RAW MESH CHECKS + + // TODO: Check not needed w/ FMeshDesc + // This is required due to the impeding deprecation of FRawMesh + // If we dont update this UE4 will crash upon deleting an asset. + //SrcModel->StaticMeshOwner = FoundStaticMesh; + + // Check if the mesh has at least one triangle, if not, log a message + if (MeshDescription->Triangles().Num() == 0) + { + HOUDINI_LOG_WARNING( + TEXT("[CreateStaticMesh_MeshDescription]: 0 valid triangles in StaticMesh data for %s LOD %i! Please check the log."), + *FoundStaticMesh->GetName(), LODIndex); + } + + // Store the new MeshDescription + FoundStaticMesh->CommitMeshDescription(LODIndex); + //Set the Imported version before calling the build + FoundStaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; + + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = GetLODSCreensizeForSplit(SplitGroupName); + if (screensize >= 0.0f) + { + // Only apply the LOD screensize if it's valid + SrcModel->ScreenSize = screensize; + //FoundStaticMesh->GetSourceModel(LodIndex).ScreenSize = screensize; + FoundStaticMesh->bAutoComputeLODScreenSize = false; + } + + // SET STATIC MESH GENERATION PARAM + // HANDLE COLLIDERS + // REMOVE OLD COLLIDERS + // CUSTOM BAKE NAME OVERRIDE + + // UPDATE UPROPERTY ATTRIBS + // Update property attributes on the SM + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + HGPO.GeoId, HGPO.PartId, + true, + AllSplitFirstValidPrimIndex[SplitGroupName], + INDEX_NONE, + AllSplitFirstValidVertexIndex[SplitGroupName], + PropertyAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + FoundStaticMesh, PropertyAttributes); + } + + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray TileValues; + if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + // Notify that we created a new Static Mesh if needed + if(bNewStaticMeshCreated) + FAssetRegistryModule::AssetCreated(FoundStaticMesh); + + // Add the Static mesh to the output maps and the build map if we haven't already + if (FoundOutputObject) + { + FoundOutputObject->OutputObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = false; + FoundOutputObject->bIsImplicit = false; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + + StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Total Split time: %f seconds."), tick - split_tick); + } + } + + // Look if we only have colliders + // If we do, we'll allow attaching sockets to the collider meshes + bool bCollidersOnly = true; + for (auto& Current : StaticMeshToBuild) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) + { + bCollidersOnly = false; + break; + } + } + + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + for (auto& Current : StaticMeshToBuild) + { + tick = FPlatformTime::Seconds(); + + UStaticMesh* SM = Current.Value; + if (!SM || SM->IsPendingKill()) + continue; + + UBodySetup * BodySetup = SM->BodySetup; + if (!BodySetup) + { + SM->CreateBodySetup(); + BodySetup = SM->BodySetup; + } + + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + + // Handle the Static Mesh's colliders + if (BodySetup && !BodySetup->IsPendingKill()) + { + // Make sure rendering is done - so we are not changing data being used by collision drawing. + FlushRenderingCommands(); + + // Clean up old colliders from a previous cook + BodySetup->Modify(); + BodySetup->RemoveSimpleCollision(); + // Create new GUID + BodySetup->InvalidatePhysicsData(); + + FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; + FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); + if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) + { + BodySetup->AddCollisionFrom(*CurrentAggColl); + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; + } + + // Moved RefreshCollisionChange to after the SM->Build call + // RefreshCollisionChange(*SM); + // SM->bCustomizedCollision = true; + + // See if we need to enable collisions on the whole mesh + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + // Complex collider, enable collisions for this static mesh. + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + ApplyComplexColliderHelper( + MainStaticMesh, + SM, + SplitType, + bAssignedCustomCollisionMesh, + OutputObjects.Find(Current.Key)); + } + else + { + // TODO + // if the LODForCollision uproperty attribute is set, we need to activate complex collision + // on the static mesh for that lod to be picked up properly as a collider + if ( FHoudiniEngineUtils::HapiCheckAttributeExists( HGPO.GeoId, HGPO.PartId, + "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) + { + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + } + } + + // Add the Sockets to the StaticMesh + // We only add them to the main geo, or to the colliders if we only generate colliders + bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; + if (bAddSocket) + { + if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, HGPO.AllMeshSockets, true)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); + } + } + + if (MainStaticMesh) + { + UBodySetup* MainBodySetup = MainStaticMesh->BodySetup; + if (!IsValid(MainBodySetup)) + { + MainStaticMesh->CreateBodySetup(); + MainBodySetup = MainStaticMesh->BodySetup; + } + + check(MainBodySetup); + // Set the main static mesh to whatever the final CTF should be. + MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; + } + + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // BUILD the Static Mesh + // bSilent doesnt add the Build Errors... + double build_start = FPlatformTime::Seconds(); + TArray SMBuildErrors; + SM->Build(true, &SMBuildErrors); + + if (bDoTiming) + { + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - StaticMesh->Build() executed in %f seconds."), tick - build_start); + } + + // This replaces the call to RefreshCollision below, but without CreateNavCollision + // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, + // and can be expensive depending on the vert/poly count of the mesh + // RefreshCollisionChange(*SM); + { + for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + { + UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); + if (StaticMeshComponent->GetStaticMesh() == SM) + { + // it needs to recreate IF it already has been created + if (StaticMeshComponent->IsPhysicsStateCreated()) + { + StaticMeshComponent->RecreatePhysicsState(); + } + } + } + + FEditorSupportDelegates::RedrawAllViewports.Broadcast(); + } + + SM->GetOnMeshChanged().Broadcast(); + + UPackage* MeshPackage = SM->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + } + } + + // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup + // Here as it has already been handled by the StaticMesh Build call + + double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() executed in %f seconds."), time_end - time_start); + + return true; +} + +bool +FHoudiniMeshTranslator::CreateHoudiniStaticMesh() +{ + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh")); + + const double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Determine if there is "main" geo, if not we'll use the first LOD + // as main geo + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + { + bHasMainGeo = true; + break; + } + } + + // Update the part's material's IDS and info now + //UpdatePartFaceMaterialsIfNeeded(); + CreateNeededMaterials(); + + // Check if the materials were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; + + // bool MeshMaterialsHaveBeenReset = false; + + double tick = FPlatformTime::Seconds(); + if(bDoTiming) + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); + + // Iterate through all detected split groups we care about and split geometry. + bool bMainGeoOrFirstLODFound = false; + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Per Split")); + + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // We are only interested in the Normal/main geo and visible colliders + if (SplitType != EHoudiniSplitType::Normal && + SplitType != EHoudiniSplitType::LOD && + SplitType != EHoudiniSplitType::RenderedComplexCollider && + SplitType != EHoudiniSplitType::RenderedSimpleCollider && + SplitType != EHoudiniSplitType::RenderedUCXCollider) + { + continue; + } + + // We only use LOD if there is no Normal geo + if (SplitType == EHoudiniSplitType::Normal) + { + bMainGeoOrFirstLODFound = true; + HOUDINI_LOG_MESSAGE(TEXT("Found Normal geo for mesh.")); + } + else if (SplitType == EHoudiniSplitType::LOD) + { + if (bHasMainGeo) + { + HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since the mesh has Normal geo.")); + continue; + } + else if (bMainGeoOrFirstLODFound) + { + HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since we have already processed the first LOD.")); + continue; + } + else + { + bMainGeoOrFirstLODFound = true; + HOUDINI_LOG_MESSAGE(TEXT("Mesh does not have Normal geo, found first LOD.")); + } + } + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName]; + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing DM from a previous cook + UHoudiniStaticMesh* FoundStaticMesh = FindExistingHoudiniStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing dynamic mesh, create a new one + FoundStaticMesh = CreateNewHoudiniStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + } + + if (!FoundOutputObject) + { + // If we couldnt find a previous output object, create a new one + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + } + FoundOutputObject->bProxyIsCurrent = true; + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + if (bRebuildStaticMesh) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build/Rebuild UHoudiniStaticMesh")); + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" + // So that IndicesMapper[ oldIndex ] => newIndex + TArray IndicesMapper; + IndicesMapper.Init(-1, SplitVertexList.Num()); + int32 CurrentMapperIndex = 0; + + // NeededVertices: + // Array containing the old index of the needed vertices for the current split + // NeededVertices[ newIndex ] => oldIndex + TArray< int32 > NeededVertices; + NeededVertices.Reserve(SplitVertexList.Num() / 3); + TArray< int32 > TriangleIndices; + TriangleIndices.Reserve(SplitVertexList.Num()); + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build IndicesMapper and NeededVertices")); + + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) + || !IndicesMapper.IsValidIndex(WedgeIndices[1]) + || !IndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (IndicesMapper[WedgeIndices[i]] < 0) + { + // This old index has not yet been "converted" to a new index + NeededVertices.Add(WedgeIndices[i]); + IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; + CurrentMapperIndex++; + } + + // Replace the old index with the new one + WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; + } + + // Flip wedge indices to fix the winding order. + TriangleIndices.Add(WedgeIndices[0]); + TriangleIndices.Add(WedgeIndices[2]); + TriangleIndices.Add(WedgeIndices[1]); + + ValidVertexId += 3; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's normal if needed + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + // Check that the number of normal we retrieved is correct + int32 NormalCount = SplitNormals.Num() / 3; + if (NormalCount < 0 || NormalCount < NeededVertices.Num()) + { + // Ignore normals + NormalCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENTS + //--------------------------------------------------------------------------------------------------------------------- + + TArray SplitTangentU; + TArray SplitTangentV; + int32 TangentUCount = 0; + int32 TangentVCount = 0; + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + + bool bGenerateTangents = bReadTangents; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + + // Check that the number of tangents read matches the number of normals + TangentUCount = SplitTangentU.Num() / 3; + TangentVCount = SplitTangentV.Num() / 3; + if (TangentUCount != NormalCount || TangentVCount != NormalCount) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh: Generate tangents due to count mismatch (# U Tangents = %d; # V Tangents = %d; # Normals = %d)"), TangentUCount, TangentVCount, NormalCount); + bGenerateTangents = true; + } + + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // VERTEX COLORS AND ALPHAS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's colors if needed + UpdatePartColorsIfNeeded(); + + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract this part's alpha values if needed + UpdatePartAlphasIfNeeded(); + + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + + const int32 ColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; + const bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && ColorsCount > 0; + const bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == ColorsCount); + + //--------------------------------------------------------------------------------------------------------------------- + // UVS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's UV sets if needed + UpdatePartUVSetsIfNeeded(); + + // See if we need to transfer uv point attributes to vertex attributes. + int32 NumUVLayers = 0; + TArray> SplitUVSets; + SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + if (SplitUVSets[TexCoordIdx].Num() > 0) + { + NumUVLayers++; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL ATTRIBUTE OVERRIDES + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: These are actually per faces, not per vertices... + // Need to update!! + UpdatePartFaceMaterialOverridesIfNeeded(); + + // + // Initialize mesh + // + const int32 NumVertexPositions = NeededVertices.Num(); + const int32 NumTriangles = TriangleIndices.Num() / 3; + const bool bHasPerFaceMaterials = PartFaceMaterialOverrides.Num() > 0 || (PartUniqueMaterialIds.Num() > 0 && !bOnlyOneFaceMaterial); + + FoundStaticMesh->Initialize( + NumVertexPositions, + NumTriangles, + NumUVLayers, // NumUVLayers + 0, // InitialNumStaticMaterials + NormalCount > 0, // HasNormals + NormalCount > 0 && bReadTangents, // HasTangents + bSplitColorValid, // HasColors + bHasPerFaceMaterials // HasPerFaceMaterials + ); + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + UpdatePartPositionIfNeeded(); + + // + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Vertex Positions")); + + for (int32 VertexPositionIdx = 0; VertexPositionIdx < NumVertexPositions; ++VertexPositionIdx) + //ParallelFor(NumVertexPositions, [&](uint32 VertexPositionIdx) + { + int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; + if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // We need to swap Z and Y coordinate here, and convert from m to cm. + FoundStaticMesh->SetVertexPosition(VertexPositionIdx, FVector( + PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION, + PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION, + PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION + )); + }//); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACES / TRIS + // Now set Normals, UVs and Colors on mesh points and AttributeSet + //--------------------------------------------------------------------------------------------------------------------- + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Triangle Indices & Per Vertex Instance Attribute Values")); + + // Now add the triangles to the mesh + for (int32 TriangleIdx = 0; TriangleIdx < NumTriangles; ++TriangleIdx) + // ParallelFor(NumTriangles, [&](uint32 TriangleIdx) + { + + const int32 TriVertIdx0 = TriangleIdx * 3; + FoundStaticMesh->SetTriangleVertexIndices(TriangleIdx, FIntVector( + TriangleIndices[TriVertIdx0 + 0], + TriangleIndices[TriVertIdx0 + 1], + TriangleIndices[TriVertIdx0 + 2] + )); + + const int32 TriWindingIndex[3] = { 0, 2, 1 }; + if (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)) + { + // Flip Z and Y coordinate for normal, but don't scale + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + const FVector Normal( + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] + ); + + FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); + + if (bReadTangents) + { + FVector TangentU, TangentV; + if (bGenerateTangents) + { + // Generate the tangents if needed + Normal.FindBestAxisVectors(TangentU, TangentV); + } + else + { + // Transfer the tangents from Houdini + TangentU.X = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; + TangentU.Y = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; + TangentU.Z = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; + + TangentU.X = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; + TangentU.Y = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; + TangentU.Z = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; + } + + FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); + FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + } + } + } + + if (bSplitColorValid && SplitColors.IsValidIndex(TriVertIdx0 * AttribInfoColors.tupleSize + 3 * AttribInfoColors.tupleSize - 1)) + { + FLinearColor VertexLinearColor; + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + VertexLinearColor.R = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 0], 0.0f, 1.0f); + VertexLinearColor.G = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 1], 0.0f, 1.0f); + VertexLinearColor.B = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 2], 0.0f, 1.0f); + + if (bSplitAlphaValid) + { + VertexLinearColor.A = FMath::Clamp(SplitAlphas[TriVertIdx0 + ElementIdx], 0.0f, 1.0f); + } + else if (AttribInfoColors.tupleSize >= 4) + { + VertexLinearColor.A = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 3], 0.0f, 1.0f); + } + else + { + VertexLinearColor.A = 1.0f; + } + const FColor VertexColor = VertexLinearColor.ToFColor(false); + FoundStaticMesh->SetTriangleVertexColor(TriangleIdx, TriWindingIndex[ElementIdx], VertexColor); + } + } + + if (NumUVLayers > 0) + { + // Dynamic mesh supports only 1 UV layer on the mesh it self. So we set the first layer + // on the mesh itself only, and we set all layers on the AttributeSet + for (int32 TexCoordIdx = 0; TexCoordIdx < NumUVLayers; ++TexCoordIdx) + { + const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; + if (SplitUVs.IsValidIndex(TriVertIdx0 * 2 + 3 * 2 - 1)) + { + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + const int32 UVIdx = TriVertIdx0 * 2 + ElementIdx * 2; + // We need to flip V coordinate when it's coming from HAPI. + const FVector2D UV(SplitUVs[UVIdx + 0], 1.0f - SplitUVs[UVIdx + 1]); + // Set the UV on the vertex instance in the UVLayer + FoundStaticMesh->SetTriangleVertexUV(TriangleIdx, TriWindingIndex[ElementIdx], TexCoordIdx, UV); + } + } + } + } + } + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIALS / FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // Get face indices for this split. + TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; + + // Fetch the FoundMesh's Static Materials array + TArray& FoundStaticMaterials = FoundStaticMesh->GetStaticMaterials(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) + { + FoundStaticMaterials.Empty(); + } + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); + + // Process material overrides first + if (PartFaceMaterialOverrides.Num() > 0) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Material Overrides")); + + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + continue; + + UMaterialInterface * MaterialInterface = nullptr; + int32 CurrentFaceMaterialIdx = 0; + const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) + { + // Try to locate the corresponding material interface + + // Start by looking in our assignment map + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + // Only try to load a material if it has a chance to be valid! + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) + { + MaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add this material to the map + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); + } + else + { + // The Attribute Material and its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + if (!MaterialInterface) + { + // If everything fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + } + } + + if (MaterialInterface) + { + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + // Update the Face Material on the mesh + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); + } + } + } + else if (PartUniqueMaterialIds.Num() > 0) + { + // The part has houdini materials + if (bOnlyOneFaceMaterial) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Single Material")); + + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + else + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Materials")); + + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) + { + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); + continue; + } + } + else + { + MaterialInterface = Cast(DefaultMaterial); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + + if (MaterialInterface) + { + // Add the material to the Static mesh + int32 UnrealMatIndex = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); + + // Update the face index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); + } + } + } + } + else + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Default Material")); + + // No materials were found, we need to use default Houdini material. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + + //// Update property attributes on the mesh + //TArray PropertyAttributes; + //if (GetGenericPropertiesAttributes( + // HGPO.GeoId, HGPO.PartId, + // AllSplitFirstValidVertexIndex[SplitGroupName], + // AllSplitFirstValidPrimIndex[SplitGroupName], + // PropertyAttributes)) + //{ + // UpdateGenericPropertiesAttributes( + // FoundStaticMesh, PropertyAttributes); + //} + + FoundStaticMesh->Optimize(); + + // Check if the mesh is valid (check all the counts (vertex, triangles, vertex instances, UVs etc) but skip + // looping over each individual triangle vertex index to check if the value is valid). + const bool bSkipVertexIndicesCheck = true; + if (!FoundStaticMesh->IsValid(bSkipVertexIndicesCheck)) + { + HOUDINI_LOG_WARNING( + TEXT("[CreateHoudiniStaticMesh]: Invalid StaticMesh data for %s in cook output! Please check the log."), + *FoundStaticMesh->GetName()); + } + + //// Try to find the outer package so we can dirty it up + //if (FoundStaticMesh->GetOuter()) + //{ + // FoundStaticMesh->GetOuter()->MarkPackageDirty(); + //} + //else + //{ + // FoundStaticMesh->MarkPackageDirty(); + //} + UPackage *MeshPackage = FoundStaticMesh->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + + /* + // DPT: deactivated auto saving mesh/material package + // only dirty for now, as we'll save them when saving the world. + // Save the created/updated package + FEditorFileUtils::PromptForCheckoutAndSave({ MeshPackage }, false, false); + */ + } + + // Add the Proxy mesh to the output maps + if (FoundOutputObject) + { + FoundOutputObject->ProxyObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = true; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + } + + const double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() executed in %f seconds."), time_end - time_start); + + return true; +} + +void +FHoudiniMeshTranslator::ApplyComplexColliderHelper( + UStaticMesh* TargetStaticMesh, + UStaticMesh* ComplexStaticMesh, + const EHoudiniSplitType SplitType, + bool& bAssignedCustomCollisionMesh, + FHoudiniOutputObject* OutputObject) +{ + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider && TargetStaticMesh) + { + if (!bAssignedCustomCollisionMesh) + { + bAssignedCustomCollisionMesh = true; + TargetStaticMesh->ComplexCollisionMesh = ComplexStaticMesh; + TargetStaticMesh->bCustomizedCollision = true; + bAssignedCustomCollisionMesh = true; + // We don't want an actor/component for this object in the scene, so flag it as an implicit output. + if (OutputObject) + { + OutputObject->bIsImplicit = true; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("More than one (invisible) complex collision mesh found. Static Mesh assets only support a single complex collision mesh. Creating additional collision geo as Static Mesh Components.")); + } + } +} + + +bool +FHoudiniMeshTranslator::CreateNeededMaterials() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateNeededMaterials")); + + UpdatePartNeededMaterials(); + + TArray MaterialAndTexturePackages; + FHoudiniMaterialTranslator::CreateHoudiniMaterials( + HGPO.AssetId, PackageParams, + PartUniqueMaterialIds, PartUniqueMaterialInfos, + InputAssignmentMaterials, OutputAssignmentMaterials, + MaterialAndTexturePackages, false, bTreatExistingMaterialsAsUpToDate); + + /* + // Save the created packages if needed + // DPT: deactivated, only dirty for now, as we'll save them when saving the world. + if (MaterialAndTexturePackages.Num() > 0) + FEditorFileUtils::PromptForCheckoutAndSave(MaterialAndTexturePackages, true, false); + */ + + if (bMaterialOverrideNeedsCreateInstance && PartFaceMaterialOverrides.Num() > 0) + { + // Map containing unique face materials override attribute + // and their first valid prim index + // We create only one material instance per attribute + TMap UniqueFaceMaterialOverrides; + for (int FaceIdx = 0; FaceIdx < PartFaceMaterialOverrides.Num(); FaceIdx++) + { + FString MatOverrideAttr = PartFaceMaterialOverrides[FaceIdx]; + if (UniqueFaceMaterialOverrides.Contains(MatOverrideAttr)) + continue; + + // Add the material override and face index to the map + UniqueFaceMaterialOverrides.Add(MatOverrideAttr, FaceIdx); + } + + FHoudiniMaterialTranslator::CreateMaterialInstances( + HGPO, PackageParams, + UniqueFaceMaterialOverrides, MaterialAndTexturePackages, + InputAssignmentMaterials, OutputAssignmentMaterials, + false); + } + + return true; +} + +FString +FHoudiniMeshTranslator::GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType) +{ + FString MeshIdentifier = TEXT(""); + switch (InSplitType) + { + case EHoudiniSplitType::Normal: + case EHoudiniSplitType::LOD: + case EHoudiniSplitType::InvisibleUCXCollider: + case EHoudiniSplitType::InvisibleSimpleCollider: + // LODs and Invisible simple colliders use the main mesh + MeshIdentifier = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + break; + + case EHoudiniSplitType::InvisibleComplexCollider: + case EHoudiniSplitType::RenderedComplexCollider: + case EHoudiniSplitType::RenderedUCXCollider: + case EHoudiniSplitType::RenderedSimpleCollider: + // Rendered colliders or invisible complex colliders have their own static mesh + MeshIdentifier = InSplitName; + break; + + default: + break; + } + + return MeshIdentifier; +} + +UStaticMesh* +FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + // See if we already have an input object for that output identifier + FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); + UStaticMesh* FoundStaticMesh = nullptr; + if (FoundOutputObjectPtr) + { + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + FoundStaticMesh = nullptr; + } + + if (!FoundStaticMesh) + { + // No input object matching this identifier, see if we have created an output object that matches + FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); + if (!FoundOutputObjectPtr) + return nullptr; + + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + return nullptr; + } + + if (FoundStaticMesh) + { + UObject* OuterMost = FoundStaticMesh->GetOutermostObject(); + if (OuterMost->IsA()) + { + // The Outermost for this static mesh is a level + // This is likely a SM created by V1, and we should not reuse it. + // This will force the plugin to recreate a "proper" SM in the temp folder. + FoundStaticMesh->MarkPendingKill(); + FoundStaticMesh = nullptr; + } + } + + return FoundStaticMesh; +} + +UHoudiniStaticMesh* +FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + // See if we already have an input object for that output identifier + FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); + UHoudiniStaticMesh* FoundStaticMesh = nullptr; + if (FoundOutputObjectPtr) + { + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + FoundStaticMesh = nullptr; + } + + if (!FoundStaticMesh) + { + // No input object matching this identifier, see if we have created an output object that matches + FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); + if (!FoundOutputObjectPtr) + return nullptr; + + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + return nullptr; + } + + return FoundStaticMesh; +} + +EHoudiniSplitType +FHoudiniMeshTranslator::GetSplitTypeFromSplitName(const FString& InSplitName) +{ + const FString MainGroup = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + if (InSplitName.StartsWith(MainGroup, ESearchCase::IgnoreCase)) + return EHoudiniSplitType::Normal; + + const FString LODGroupPrefix = HAPI_UNREAL_GROUP_LOD_PREFIX; + if (InSplitName.StartsWith(LODGroupPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::LOD; + } + + const FString RenderedCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; + if (InSplitName.StartsWith(RenderedCollisionPrefix, ESearchCase::IgnoreCase)) + { + // Rendered colliders + // See if it is a simple/ucx/complex + const FString RenderedUCXCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX; + const FString RenderedSimpleCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(RenderedUCXCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::RenderedUCXCollider; + } + else if (InSplitName.StartsWith(RenderedSimpleCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::RenderedSimpleCollider; + } + else + { + return EHoudiniSplitType::RenderedComplexCollider; + } + } + + const FString InvisibleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(InvisibleCollisionPrefix, ESearchCase::IgnoreCase)) + { + // Invisible colliders + // See if it is a simple/ucx/complex + const FString InvisibleUCXCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX; + const FString InvisibleSimpleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(InvisibleUCXCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::InvisibleUCXCollider; + } + else if (InSplitName.StartsWith(InvisibleSimpleCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::InvisibleSimpleCollider; + } + else + { + return EHoudiniSplitType::InvisibleComplexCollider; + } + } + + // ? + return EHoudiniSplitType::Invalid; + //return EHoudiniSplitType::Normal; +} + +bool +FHoudiniMeshTranslator::AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) +{ + // Get the vertex indices for the split group + TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; + + // We're only interested in unique vertices + TArray UniqueVertexIndexes; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + UniqueVertexIndexes.AddUnique(Index); + } + + // Extract the collision geo's vertices + TArray< FVector > VertexArray; + VertexArray.SetNum(UniqueVertexIndexes.Num()); + for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) + { + int32 VertexIndex = UniqueVertexIndexes[Idx]; + if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) + continue; + + VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + +#if WITH_EDITOR + // Do we want to create multiple convex hulls? + bool bDoMultiHullDecomp = false; + if (SplitGroupName.Contains(TEXT("ucx_multi"), ESearchCase::IgnoreCase)) + bDoMultiHullDecomp = true; + + uint32 HullCount = 8; + int32 MaxHullVerts = 16; + if (bDoMultiHullDecomp) + { + // TODO: + // Look for extra attributes for the decomposition parameters? (HullCount/MaxHullVerts) + } + + if (bDoMultiHullDecomp && (VertexArray.Num() >= 3 || UniqueVertexIndexes.Num() >= 3)) + { + // creating multiple convex hull collision + // ... this might take a while + + // We're only interested in the valid indices! + TArray Indices; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + Indices.Add(Index); + } + + // But we need all the positions as vertex + TArray< FVector > Vertices; + Vertices.SetNum(PartPositions.Num() / 3); + + for (int32 Idx = 0; Idx < Vertices.Num(); Idx++) + { + Vertices[Idx].X = PartPositions[Idx * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[Idx].Y = PartPositions[Idx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[Idx].Z = PartPositions[Idx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + // We are using Unreal's DecomposeMeshToHulls() + // We need a BodySetup so create a fake/transient one + UBodySetup* BodySetup = NewObject(); + + // Run actual util to do the work (if we have some valid input) + DecomposeMeshToHulls(BodySetup, Vertices, Indices, HullCount, MaxHullVerts); + + // If we succeed, return here + // If not, keep going and we'll try to do a single hull decomposition + if (BodySetup->AggGeom.ConvexElems.Num() > 0) + { + // Copy the convex elem to our aggregate + for (int32 n = 0; n < BodySetup->AggGeom.ConvexElems.Num(); n++) + AggCollisions.ConvexElems.Add(BodySetup->AggGeom.ConvexElems[n]); + + return true; + } + } +#endif + + // Creating a single Convex collision + FKConvexElem ConvexCollision; + ConvexCollision.VertexData = VertexArray; + ConvexCollision.UpdateElemBox(); + + AggCollisions.ConvexElems.Add(ConvexCollision); + + return true; +} + +bool +FHoudiniMeshTranslator::AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) +{ + // Get the vertex indices for the split group + TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; + + // We're only interested in unique vertices + TArray UniqueVertexIndexes; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + UniqueVertexIndexes.AddUnique(Index); + } + + // Extract the collision geo's vertices + TArray< FVector > VertexArray; + VertexArray.SetNum(UniqueVertexIndexes.Num()); + for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) + { + int32 VertexIndex = UniqueVertexIndexes[Idx]; + if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) + continue; + + VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + int32 NewColliders = 0; + if (SplitGroupName.Contains("Box")) + { + NewColliders = FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(VertexArray, AggCollisions); + } + else if (SplitGroupName.Contains("Sphere")) + { + NewColliders = FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(VertexArray, AggCollisions); + } + else if (SplitGroupName.Contains("Capsule")) + { + NewColliders = FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(VertexArray, AggCollisions); + } + else + { + // We need to see what type of collision the user wants + // by default, a kdop26 will be created + uint32 NumDirections = 26; + const FVector* Directions = KDopDir26; + if (SplitGroupName.Contains("kdop10X")) + { + NumDirections = 10; + Directions = KDopDir10X; + } + else if (SplitGroupName.Contains("kdop10Y")) + { + NumDirections = 10; + Directions = KDopDir10Y; + } + else if (SplitGroupName.Contains("kdop10Z")) + { + NumDirections = 10; + Directions = KDopDir10Z; + } + else if (SplitGroupName.Contains("kdop18")) + { + NumDirections = 18; + Directions = KDopDir18; + } + + // Converting the directions to a TArray + TArray DirArray; + DirArray.SetNum(NumDirections); + for (uint32 DirectionIndex = 0; DirectionIndex < NumDirections; DirectionIndex++) + { + DirArray[DirectionIndex] = Directions[DirectionIndex]; + } + + NewColliders = FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(VertexArray, DirArray, AggCollisions); + } + + return (NewColliders > 0); +} + +int32 +FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData) +{ + return FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, OutVertexData); +} + +/* +int32 +FHoudiniMeshTranslator::GetSplitNormals( + const TArray& InSplitVertexList, TArray& OutNormals) +{ + // Extract the normals + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutNormals.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; + OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; + OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; + + OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; + OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; + OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; + + OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; + OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; + OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; + } + + return WedgeCount; +} + +int32 +FHoudiniMeshTranslator::GetSplitUVs( + const TArray& InSplitVertexList, TArray& OutUVs) +{ + // Extract the normals + UpdatePartUVSetsIfNeeded(); + + // Get the normals for this split + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutNormals.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; + OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; + OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; + + OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; + OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; + OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; + + OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; + OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; + OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; + } + + return WedgeCount; +} + + +int32 +FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutData, + const float& ScaleFactor) +{ + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutData.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; + OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; + + OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; + OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; + + OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; + OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; + } + + return WedgeCount; +} + +int32 +FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutData, + const float& ScaleFactor) +{ + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutData.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; + OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; + + OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; + OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; + + OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; + OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; + } + + return WedgeCount; +} +*/ + + +template +int32 FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::TransferPartAttributesToSplit")); + + if (!InAttribInfo.exists || InAttribInfo.tupleSize <= 0) + return 0; + + if (InData.Num() <= 0) + return 0; + + int32 ValidWedgeCount = 0; + + // Future optimization - see if we can do direct vertex transfer. + int32 WedgeCount = InVertexList.Num(); + int32 LastValidWedgeIdx = 0; + if (InAttribInfo.owner == HAPI_ATTROWNER_POINT) + { + // Point attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + int32 VertexIdx = InVertexList[WedgeIdx]; + if (VertexIdx < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[VertexIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_VERTEX) + { + // Vertex attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[WedgeIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_PRIM) + { + // Primitive attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 PrimIdx = WedgeIdx / 3; + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[PrimIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_DETAIL) + { + // Detail attribute transfer + // We have one value to copy for all output split vertices + // if the attribute is a single value (not a tuple) + // then we can simply use the array init function instead of looping + if (InAttribInfo.tupleSize == 1) + { + OutVertexData.Init(InData[0], WedgeCount); + } + else + { + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + } + else + { + // Invalid attribute owner, shouldn't happen + check(false); + } + + OutVertexData.SetNumZeroed(ValidWedgeCount * InAttribInfo.tupleSize); + + return ValidWedgeCount; +} + +float +FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) +{ + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = -1.0f; + + // Start by looking at the lod_screensize primitive attribute + bool bAttribValid = false; + UpdatePartLODScreensizeIfNeeded(); + + if (PartLODScreensize.Num() > 0) + { + // use the "lod_screensize" primitive attribute + int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + if (PartLODScreensize.IsValidIndex(FirstValidPrimIndex)) + screensize = PartLODScreensize[FirstValidPrimIndex]; + } + + if (screensize < 0.0f) + { + // We couldn't find the primitive attribute, look for a "lodX_screensize" detail attribute + FString LODAttributeName = SplitGroupName + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX; + + TArray LODScreenSizes; + HAPI_AttributeInfo AttribInfoScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); + + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*LODAttributeName), + AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_DETAIL); + + if (AttribInfoScreenSize.exists && LODScreenSizes.Num() > 0) + { + screensize = LODScreenSizes[0]; + } + } + + if (screensize < 0.0f) + { + // finally, look for a potential uproperty style attribute + // aka, "unreal_uproperty_screensize" + TArray LODScreenSizes; + HAPI_AttributeInfo AttribInfoScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); + + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, "unreal_uproperty_screensize", + AttribInfoScreenSize, LODScreenSizes); + + if (AttribInfoScreenSize.exists) + { + if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_DETAIL && LODScreenSizes.Num() > 0) + { + screensize = LODScreenSizes[0]; + } + else if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_PRIM) + { + int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + if (LODScreenSizes.IsValidIndex(FirstValidPrimIndex)) + screensize = LODScreenSizes[FirstValidPrimIndex]; + } + } + } + + // Make sure the screensize is in percent, so if its above 1, divide by 100 + if (screensize > 1.0f) + screensize /= 100.0f; + + return screensize; +} + +int32 +FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + // Calculate bounding Box. + FVector Center, Extents; + FVector unitVec = FVector::OneVector;// bs->BuildScale3D; + CalcBoundingBox(InPositionArray, Center, Extents, unitVec); + + FKBoxElem BoxElem; + BoxElem.Center = Center; + BoxElem.X = Extents.X * 2.0f; + BoxElem.Y = Extents.Y * 2.0f; + BoxElem.Z = Extents.Z * 2.0f; + OutAggregateCollisions.BoxElems.Add(BoxElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FBox Box(ForceInit); + for (const FVector& CurPos : PositionArray) + { + Box += CurPos; + } + Box.GetCenterAndExtents(Center, Extents); +} + +int32 +FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FSphere bSphere, bSphere2, bestSphere; + FVector unitVec = FVector::OneVector; + + // Calculate bounding sphere. + CalcBoundingSphere(InPositionArray, bSphere, unitVec); + CalcBoundingSphere2(InPositionArray, bSphere2, unitVec); + + if (bSphere.W < bSphere2.W) + bestSphere = bSphere; + else + bestSphere = bSphere2; + + // Don't use if radius is zero. + if (bestSphere.W <= 0.f) + { + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Sphere collider.")); + return 0; + } + + FKSphereElem SphereElem; + SphereElem.Center = bestSphere.Center; + SphereElem.Radius = bestSphere.W; + OutAggregateCollisions.SphereElems.Add(SphereElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + if (PositionArray.Num() == 0) + return; + + FBox Box; + FVector MinIx[3]; + FVector MaxIx[3]; + + bool bFirstVertex = true; + for (const FVector& CurPosition : PositionArray) + { + FVector p = CurPosition * LimitVec; + if (bFirstVertex) + { + // First, find AABB, remembering furthest points in each dir. + Box.Min = p; + Box.Max = Box.Min; + + MinIx[0] = CurPosition; + MinIx[1] = CurPosition; + MinIx[2] = CurPosition; + + MaxIx[0] = CurPosition; + MaxIx[1] = CurPosition; + MaxIx[2] = CurPosition; + bFirstVertex = false; + continue; + } + + // X // + if (p.X < Box.Min.X) + { + Box.Min.X = p.X; + MinIx[0] = CurPosition; + } + else if (p.X > Box.Max.X) + { + Box.Max.X = p.X; + MaxIx[0] = CurPosition; + } + + // Y // + if (p.Y < Box.Min.Y) + { + Box.Min.Y = p.Y; + MinIx[1] = CurPosition; + } + else if (p.Y > Box.Max.Y) + { + Box.Max.Y = p.Y; + MaxIx[1] = CurPosition; + } + + // Z // + if (p.Z < Box.Min.Z) + { + Box.Min.Z = p.Z; + MinIx[2] = CurPosition; + } + else if (p.Z > Box.Max.Z) + { + Box.Max.Z = p.Z; + MaxIx[2] = CurPosition; + } + } + + const FVector Extremes[3] = { (MaxIx[0] - MinIx[0]) * LimitVec, + (MaxIx[1] - MinIx[1]) * LimitVec, + (MaxIx[2] - MinIx[2]) * LimitVec }; + + // Now find extreme points furthest apart, and initial center and radius of sphere. + float d2 = 0.f; + for (int32 i = 0; i < 3; i++) + { + const float tmpd2 = Extremes[i].SizeSquared(); + if (tmpd2 > d2) + { + d2 = tmpd2; + sphere.Center = (MinIx[i] + (0.5f * Extremes[i])) * LimitVec; + sphere.W = 0.f; + } + } + + const FVector Extents = FVector(Extremes[0].X, Extremes[1].Y, Extremes[2].Z); + + // radius and radius squared + float r = 0.5f * Extents.GetMax(); + float r2 = FMath::Square(r); + + // Now check each point lies within this sphere. If not - expand it a bit. + for (const FVector& curPos : PositionArray) + { + const FVector cToP = (curPos * LimitVec) - sphere.Center; + + const float pr2 = cToP.SizeSquared(); + + // If this point is outside our current bounding sphere's radius + if (pr2 > r2) + { + // ..expand radius just enough to include this point. + const float pr = FMath::Sqrt(pr2); + r = 0.5f * (r + pr); + r2 = FMath::Square(r); + + sphere.Center += ((pr - r) / pr * cToP); + } + } + + sphere.W = r; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FVector Center, Extents; + CalcBoundingBox(PositionArray, Center, Extents, LimitVec); + + sphere.Center = Center; + sphere.W = 0.0f; + + for (const FVector& curPos : PositionArray) + { + float Dist = FVector::DistSquared(curPos * LimitVec, sphere.Center); + if (Dist > sphere.W) + sphere.W = Dist; + } + sphere.W = FMath::Sqrt(sphere.W); +} + +int32 +FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FSphere sphere; + float length; + FRotator rotation; + FVector unitVec = FVector::OneVector; + + // Calculate bounding sphyl. + CalcBoundingSphyl(InPositionArray, sphere, length, rotation, unitVec); + + // Dont use if radius is zero. + if (sphere.W <= 0.f) + { + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Capsule collider.")); + return 0; + } + + // If height is zero, then a sphere would be better (should we just create one instead?) + if (length <= 0.f) + { + length = SMALL_NUMBER; + } + + FKSphylElem SphylElem; + SphylElem.Center = sphere.Center; + SphylElem.Rotation = rotation; + SphylElem.Radius = sphere.W; + SphylElem.Length = length; + OutAggregateCollisions.SphylElems.Add(SphylElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + if (PositionArray.Num() == 0) + return; + + FVector Center, Extents; + CalcBoundingBox(PositionArray, Center, Extents, LimitVec); + + // @todo sphere.Center could perhaps be adjusted to best fit if model is non-symmetric on it's longest axis + sphere.Center = Center; + + // Work out best axis aligned orientation (longest side) + float Extent = Extents.GetMax(); + if (Extent == Extents.X) + { + rotation = FRotator(90.f, 0.f, 0.f); + Extents.X = 0.0f; + } + else if (Extent == Extents.Y) + { + rotation = FRotator(0.f, 0.f, 90.f); + Extents.Y = 0.0f; + } + else + { + rotation = FRotator(0.f, 0.f, 0.f); + Extents.Z = 0.0f; + } + + // Cleared the largest axis above, remaining determines the radius + float r = Extents.GetMax(); + float r2 = FMath::Square(r); + + // Now check each point lies within this the radius. If not - expand it a bit. + for (const FVector& CurPos : PositionArray) + { + FVector cToP = (CurPos * LimitVec) - sphere.Center; + cToP = rotation.UnrotateVector(cToP); + + const float pr2 = cToP.SizeSquared2D(); // Ignore Z here... + + // If this point is outside our current bounding sphere's radius + if (pr2 > r2) + { + // ..expand radius just enough to include this point. + const float pr = FMath::Sqrt(pr2); + r = 0.5f * (r + pr); + r2 = FMath::Square(r); + } + } + + // The length is the longest side minus the radius. + float hl = FMath::Max(0.0f, Extent - r); + + // Now check each point lies within the length. If not - expand it a bit. + for (const FVector& CurPos : PositionArray) + { + FVector cToP = (CurPos * LimitVec) - sphere.Center; + cToP = rotation.UnrotateVector(cToP); + + // If this point is outside our current bounding sphyl's length + if (FMath::Abs(cToP.Z) > hl) + { + const bool bFlip = (cToP.Z < 0.f ? true : false); + const FVector cOrigin(0.f, 0.f, (bFlip ? -hl : hl)); + + const float pr2 = (cOrigin - cToP).SizeSquared(); + + // If this point is outside our current bounding sphyl's radius + if (pr2 > r2) + { + FVector cPoint; + FMath::SphereDistToLine(cOrigin, r, cToP, (bFlip ? FVector(0.f, 0.f, 1.f) : FVector(0.f, 0.f, -1.f)), cPoint); + + // Don't accept zero as a valid diff when we know it's outside the sphere (saves needless retest on further iterations of like points) + hl += FMath::Max(FMath::Abs(cToP.Z - cPoint.Z), 1.e-6f); + } + } + } + + sphere.W = r; + length = hl * 2.0f; +} + +int32 +FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + const float my_flt_max = 3.402823466e+38F; + + // Do k- specific stuff. + int32 kCount = Dirs.Num(); + + TArray maxDist; + maxDist.Init(-my_flt_max, kCount); + /* + for (int32 i = 0; i < kCount; i++) + maxDist.Add(my_flt_max); + */ + + // Construct temporary UModel for kdop creation. We keep no refs to it, so it can be GC'd. + auto TempModel = NewObject(); + TempModel->Initialize(nullptr, 1); + + // For each vertex, project along each kdop direction, to find the max in that direction. + for (int32 i = 0; i < InPositionArray.Num(); i++) + { + for (int32 j = 0; j < kCount; j++) + { + float dist = InPositionArray[i] | Dirs[j]; + maxDist[j] = FMath::Max(dist, maxDist[j]); + } + } + + // Inflate kdop to ensure it is no degenerate + const float MinSize = 0.1f; + for (int32 i = 0; i < kCount; i++) + { + maxDist[i] += MinSize; + } + + // Now we have the planes of the kdop, we work out the face polygons. + TArray planes; + for (int32 i = 0; i < kCount; i++) + planes.Add(FPlane(Dirs[i], maxDist[i])); + + for (int32 i = 0; i < planes.Num(); i++) + { + FPoly* Polygon = new(TempModel->Polys->Element) FPoly(); + FVector Base, AxisX, AxisY; + + Polygon->Init(); + Polygon->Normal = planes[i]; + Polygon->Normal.FindBestAxisVectors(AxisX, AxisY); + + Base = planes[i] * planes[i].W; + + new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); + + for (int32 j = 0; j < planes.Num(); j++) + { + if (i != j) + { + if (!Polygon->Split(-FVector(planes[j]), planes[j] * planes[j].W)) + { + Polygon->Vertices.Empty(); + break; + } + } + } + + if (Polygon->Vertices.Num() < 3) + { + // If poly resulted in no verts, remove from array + TempModel->Polys->Element.RemoveAt(TempModel->Polys->Element.Num() - 1); + } + else + { + // Other stuff... + Polygon->iLink = i; + Polygon->CalcNormal(1); + } + } + + if (TempModel->Polys->Element.Num() < 4) + { + TempModel = NULL; + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple KDOP collider.")); + return 0; + } + + // Build bounding box. + TempModel->BuildBound(); + + // Build BSP for the brush. + FBSPOps::bspBuild(TempModel, FBSPOps::BSP_Good, 15, 70, 1, 0); + FBSPOps::bspRefresh(TempModel, 1); + FBSPOps::bspBuildBounds(TempModel); + + // Now, create a temporary BodySetup to build the colliders + UBodySetup* TempBS = NewObject(); + TempBS->CreateFromModel(TempModel, false); + + // Copy the convex elements back to our aggregate + int32 NewConvexElems = 0; + if (TempBS && TempBS->AggGeom.ConvexElems.Num() > 0) + { + for (const auto& CurConvexElem : TempBS->AggGeom.ConvexElems) + { + OutAggregateCollisions.ConvexElems.Add(CurConvexElem); + NewConvexElems++; + } + } + + return NewConvexElems; +} + + +void +FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO) +{ + PackageParams = InPackageParams; + + if (bUpdateHGPO) + { + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.ObjectId; + PackageParams.PartId = HGPO.ObjectId; + } +} + +bool +FHoudiniMeshTranslator::RemoveAndDestroyComponent(UObject* InComponent) +{ + if (!InComponent || InComponent->IsPendingKill()) + return false; + + USceneComponent* SceneComponent = Cast(InComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + return true; + } + + return false; +} + +UMeshComponent* +FHoudiniMeshTranslator::CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf &InComponentType) +{ + // Create a new SMC as we couldn't find an existing one + USceneComponent* OuterSceneComponent = Cast(InOuterComponent); + UObject * Outer = nullptr; + if (OuterSceneComponent && !OuterSceneComponent->IsPendingKill()) + Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); + + UMeshComponent *MeshComponent = NewObject(Outer, InComponentType, NAME_None, RF_Transactional); + + // Initialize it + MeshComponent->SetVisibility(true); + //MeshComponent->SetMobility(Mobility); + + // TODO: + // Property propagation: set the new SMC's properties to the HAC's current settings + //CopyComponentPropertiesTo(MeshComponent); + + // Change the creation method so the component is listed in the details panels + MeshComponent->CreationMethod = EComponentCreationMethod::Instance; + + // Attach created static mesh component to our Houdini component. + MeshComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + MeshComponent->OnComponentCreated(); + MeshComponent->RegisterComponent(); + + return MeshComponent; +} + +bool +FHoudiniMeshTranslator::PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh) +{ + UStaticMesh *Mesh = Cast(InMesh); + if (Mesh) + { + InComponent->SetStaticMesh(Mesh); + + return true; + } + + return false; +} + +bool +FHoudiniMeshTranslator::PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh) +{ + UHoudiniStaticMesh *Mesh = Cast(InMesh); + if (Mesh) + { + InComponent->SetMesh(Mesh); + + return true; + } + + return false; +} + +UMeshComponent* +FHoudiniMeshTranslator::CreateOrUpdateMeshComponent( + const UHoudiniOutput *InOutput, + UObject *InOuterComponent, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TSubclassOf& InComponentType, + FHoudiniOutputObject& OutputObject, + FHoudiniGeoPartObject const *& OutFoundHGPO, + bool& bCreated) +{ + bCreated = false; + OutFoundHGPO = nullptr; + + // Find the HGPO that matches this mesh + for (auto& curHGPO : InOutput->HoudiniGeoPartObjects) + { + if (curHGPO.ObjectId != InOutputIdentifier.ObjectId + || curHGPO.GeoId != InOutputIdentifier.GeoId + || curHGPO.PartId != InOutputIdentifier.PartId) + { + continue; + } + + if (InOutputIdentifier.SplitIdentifier.Equals(HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION) + || curHGPO.SplitGroups.Contains(InOutputIdentifier.SplitIdentifier)) + { + OutFoundHGPO = &curHGPO; + } + } + + // No need to create a component for instanced meshes! + if (OutFoundHGPO && OutFoundHGPO->bIsInstanced) + return nullptr; + + bool bIsProxyComponent = InComponentType == UHoudiniStaticMeshComponent::StaticClass(); + + // See if we already have a component for that mesh + UMeshComponent* MeshComponent = nullptr; + if (bIsProxyComponent) + MeshComponent = Cast(OutputObject.ProxyComponent); + else + MeshComponent = Cast(OutputObject.OutputComponent); + + // If there is an existing component, but it is pending kill, then it was likely + // deleted by some other process, such as by the user in the editor, so don't use it + if (!MeshComponent || MeshComponent->IsPendingKill() || !MeshComponent->IsA(InComponentType)) + { + // If the component is not of type InComponentType, or the found component is pending kill, destroy + // the existing component (a new one is then created below) + RemoveAndDestroyComponent(MeshComponent); + MeshComponent = nullptr; + } + + if (!MeshComponent) + { + // Create a new SMC/HSMC as we couldn't find an existing one + MeshComponent = CreateMeshComponent(InOuterComponent, InComponentType); + + if (MeshComponent) + { + // Add to the output object + if (bIsProxyComponent) + OutputObject.ProxyComponent = MeshComponent; + else + OutputObject.OutputComponent = MeshComponent; + + bCreated = true; + } + } + + return MeshComponent; +} + +bool +FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, + TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors) +{ + if (!Socket || Socket->IsPendingKill() || !StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + return false; + + // The actor to assign is stored is the socket's tag + FString ActorString = Socket->Tag; + if (ActorString.IsEmpty()) + return false; + + // The actor to assign are listed after a | + TArray ActorStringArray; + ActorString.ParseIntoArray(ActorStringArray, TEXT("|"), false); + + // The "real" Tag is the first + if (ActorStringArray.Num() > 0) + Socket->Tag = ActorStringArray[0]; + + // We just add a Tag, no Actor + if (ActorStringArray.Num() == 1) + return false; + + // Extract the parsed actor string to split it further + ActorString = ActorStringArray[1]; + + // Converting the string to a string array using delimiters + const TCHAR* Delims[] = { TEXT(","), TEXT(";") }; + ActorString.ParseIntoArray(ActorStringArray, Delims, 2); + + // And try to find the corresponding HoudiniAssetActor in the editor world + // to avoid finding "deleted" assets with the same name + //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); +#if WITH_EDITOR + UWorld* EditorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; + if (!EditorWorld || EditorWorld->IsPendingKill()) + return false; + + // Remove the previously created actors which were attached to this socket + { + for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniCreatedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + continue; + } + + if (CurActor->GetAttachParentSocketName() == Socket->SocketName) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + CurActor->Destroy(); + } + } + } + + // Detach the previous in level actors which was attached to this socket + { + for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniAttachedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniAttachedSocketActors.RemoveAt(Idx); + continue; + } + + if (CurActor->GetAttachParentSocketName() == Socket->SocketName) + { + CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + HoudiniAttachedSocketActors.RemoveAt(Idx); + } + } + } + + auto CreateDefaultActor = [EditorWorld, StaticMeshComponent, Socket, HoudiniCreatedSocketActors]() + { + AActor * CreatedDefaultActor = nullptr; + + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + { + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( + EditorWorld->GetCurrentLevel(), DefaultReferenceSM, false, RF_Transactional, nullptr); + + if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load default mesh.")); + } + else + { + + // Set the default mesh actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : NewActors[0]->GetComponents()) + { + UStaticMeshComponent * CurSMC = Cast(CurComp); + if (CurSMC && !CurSMC->IsPendingKill()) + CurSMC->SetMobility(OutputSMCMobility); + } + + // Set the default mesh actor hidden in game. + NewActors[0]->SetActorHiddenInGame(true); + + Socket->AttachActor(NewActors[0], StaticMeshComponent); + CreatedDefaultActor = NewActors[0]; + //HoudiniCreatedSocketActors.Add(NewActors[0]); + } + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load default mesh.")); + } + + return CreatedDefaultActor; + }; + + // If nothing was specified, we're done + if (ActorStringArray.Num() <= 0) + return true; + + bool bUseDefaultActor = true; + // Get from the Houdini runtime setting if use default object when the reference is invalid + // true by default if fail to access HoudiniRuntimeSettings + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bUseDefaultActor = HoudiniRuntimeSettings->bShowDefaultMesh; + } + + /* + // !! Only use the default mesh if we failed to find/spawn the actor to attach + // not if we didn't specify any actor to attach! + if (ActorStringArray.Num() <= 0) + { + if (!bUseDefaultActor) + return true; + + HOUDINI_LOG_WARNING( + TEXT("Output static mesh: Socket '%s' actor is not specified. Spawn a default mesh (hidden in game)."), *(Socket->GetName())); + + AActor * DefaultActor = CreateDefaultActor(); + if (DefaultActor && !DefaultActor->IsPendingKill()) + HoudiniCreatedSocketActors.Add(DefaultActor); + + return true; + } + */ + + // try to find the actor in level first + for (TActorIterator ActorItr(EditorWorld); ActorItr; ++ActorItr) + { + // Same as with the Object Iterator, access the subclass instance with the * or -> operators. + AActor *Actor = *ActorItr; + if (!Actor || Actor->IsPendingKillOrUnreachable()) + continue; + + for (int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++) + { + if (Actor->GetName() != ActorStringArray[StringIdx] + && Actor->GetActorLabel() != ActorStringArray[StringIdx]) + continue; + + // Set the actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : Actor->GetComponents()) + { + UStaticMeshComponent * SMC = Cast(CurComp); + if (SMC && !SMC->IsPendingKill()) + SMC->SetMobility(OutputSMCMobility); + } + + Socket->AttachActor(Actor, StaticMeshComponent); + HoudiniAttachedSocketActors.Add(Actor); + + // Remove the string if the actor is found in the editor level + ActorStringArray.RemoveAt(StringIdx); + break; + } + } + + bool bSuccess = true; + // If some of the actors are not found in the level, try to find them in the content browser. Spawn one if existed + for (int32 Idx = ActorStringArray.Num() - 1; Idx>= 0; --Idx) + { + UObject * Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *ActorStringArray[Idx]); + if (!Obj || Obj->IsPendingKill()) + { + bSuccess = false; + continue; + } + + // Spawn a new actor with the found object + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( + EditorWorld->GetCurrentLevel(), Obj, false, RF_Transactional, nullptr); + + if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + { + bSuccess = false; + continue; + } + + // Set the new actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : NewActors[0]->GetComponents()) + { + UStaticMeshComponent * CurSMC = Cast(CurComp); + if (CurSMC && !CurSMC->IsPendingKill()) + CurSMC->SetMobility(OutputSMCMobility); + } + + Socket->AttachActor(NewActors[0], StaticMeshComponent); + HoudiniCreatedSocketActors.Add(NewActors[0]); + + ActorStringArray.RemoveAt(Idx); + } + + // Failed to find actors in both level and content browser + // Spawn default actors if enabled + if (bUseDefaultActor) + { + for (int32 Idx = ActorStringArray.Num() - 1; Idx >= 0; --Idx) + { + HOUDINI_LOG_WARNING( + TEXT("Output static mesh: Failed to attach '%s' to socket '%s', spawn a default mesh (hidden in game)."), *(ActorStringArray[Idx]), *(Socket->GetName())); + + // If failed to load this object, spawn a default mesh + AActor * CurDefaultActor = CreateDefaultActor(); + if (CurDefaultActor && !CurDefaultActor->IsPendingKill()) + HoudiniCreatedSocketActors.Add(CurDefaultActor); + } + } + + if (ActorStringArray.Num() > 0) + return false; +#endif + + return bSuccess; +} + +void +FHoudiniMeshTranslator::UpdateMeshBuildSettings( + FMeshBuildSettings& OutMeshBuildSettings, + const bool& bHasNormals, + const bool& bHasTangents, + const bool& bHasLightmapUVSet) +{ + // Use the values provided to the translator + OutMeshBuildSettings = StaticMeshBuildSettings; + + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + + // Recomputing normals. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag : HRSRF_OnlyIfMissing; + if(RecomputeNormalFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bRecomputeNormals = !bHasNormals; + + // Recomputing tangents. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; + if (RecomputeTangentFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bRecomputeTangents = !bHasTangents; + + // Lightmap UV generation. + EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; + if (GenerateLightmapUVFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bGenerateLightmapUVs = !bHasLightmapUVSet; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h index 375a282d8..dd23d3604 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h @@ -1,419 +1,419 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "HoudiniPackageParams.h" -#include "HoudiniAssetComponent.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "PhysicsEngine/AggregateGeom.h" - -//#include "HoudiniMeshTranslator.generated.h" - -class UStaticMesh; -class UStaticMeshSocket; -class UMaterialInterface; -class UMeshComponent; -class UStaticMeshComponent; -class UHoudiniStaticMesh; -class UHoudiniStaticMeshComponent; - -struct FKAggregateGeom; -struct FHoudiniGenericAttribute; - - -UENUM() -enum class EHoudiniSplitType : uint8 -{ - Invalid, - - Normal, - - LOD, - - RenderedComplexCollider, - InvisibleComplexCollider, - - RenderedUCXCollider, - InvisibleUCXCollider, - - RenderedSimpleCollider, - InvisibleSimpleCollider -}; - -struct HOUDINIENGINE_API FHoudiniMeshTranslator -{ - public: - - //----------------------------------------------------------------------------------------------------------------------------- - // HOUDINI TO UNREAL - //----------------------------------------------------------------------------------------------------------------------------- - - // - static bool CreateAllMeshesAndComponentsFromHoudiniOutput( - UHoudiniOutput* InOutput, - const FHoudiniPackageParams& InPackageParams, - const EHoudiniStaticMeshMethod& InStaticMeshMethod, - const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, - const FMeshBuildSettings& InMeshBuildSettings, - UObject* InOuterComponent, - bool bInTreatExistingMaterialsAsUpToDate=false, - bool bInDestroyProxies=false); - - static bool CreateStaticMeshFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& InOutputObjects, - TMap& OutOutputObjects, - TMap& InAssignmentMaterialMap, - TMap& InReplacementMaterialMap, - const bool& InForceRebuild, - const EHoudiniStaticMeshMethod& InStaticMeshMethod, - const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, - const FMeshBuildSettings& InMeshBuildSettings, - bool bInTreatExistingMaterialsAsUpToDate = false); - - static bool CreateOrUpdateAllComponents( - UHoudiniOutput* InOutput, - UObject* InOuterComponent, - TMap& InNewOutputObjects, - bool bInDestroyProxies=false, - bool bInApplyGenericProperties=true); - - - //----------------------------------------------------------------------------------------------------------------------------- - // HELPERS - //----------------------------------------------------------------------------------------------------------------------------- - static EHoudiniSplitType GetSplitTypeFromSplitName(const FString& InSplitName); - - static FString GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType); - - // TODO: Rename me! and template me! float/int/string ? - // TransferPartAttributesToSplitVertices - static int32 TransferRegularPointAttributesToVertices( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData); - - template - static int32 TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutSplitData); - - // Update the MeshBuild Settings using the values from the runtime settings/overrides on the HAC - void UpdateMeshBuildSettings( - FMeshBuildSettings& OutMeshBuildSettings, - const bool& bHasNormals, - const bool& bHasTangents, - const bool& bHasLightmapUVSet); - - - //----------------------------------------------------------------------------------------------------------------------------- - // ACCESSORS - //----------------------------------------------------------------------------------------------------------------------------- - - //----------------------------------------------------------------------------------------------------------------------------- - // MUTATORS - //----------------------------------------------------------------------------------------------------------------------------- - void SetHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) { HGPO = InHGPO; }; - void SetOuterComponent(UObject* InOuter) { OuterComponent = InOuter; }; - void SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO = false); - - void SetInputObjects(const TMap& InInputObjects) { InputObjects = InInputObjects; }; - void SetOutputObjects(TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; - - void SetInputAssignmentMaterials(const TMap& InInputMaterials) { InputAssignmentMaterials = InInputMaterials; }; - void SetReplacementMaterials(const TMap& InReplacementMaterials) { ReplacementMaterials = InReplacementMaterials; }; - - //void SetInputObjectProperties(const TMap& InInputObjectProperties) { InputObjectProperties = InInputObjectProperties; }; - //void SetOutputObjectProperties(TMap& InOutputObjectProperties) { OutputObjectProperties = InOutputObjectProperties; }; - - void SetTreatExistingMaterialsAsUpToDate(bool bInTreatExistingMaterialsAsUpToDate) { bTreatExistingMaterialsAsUpToDate = bInTreatExistingMaterialsAsUpToDate; } - - void SetStaticMeshGenerationProperties(const FHoudiniStaticMeshGenerationProperties& InStaticMeshGenerationProperties) { StaticMeshGenerationProperties = InStaticMeshGenerationProperties; }; - - void SetStaticMeshBuildSettings(const FMeshBuildSettings& InMBS) { StaticMeshBuildSettings = InMBS; }; - - protected: - - // Create a StaticMesh using the MeshDescription format - bool CreateStaticMesh_MeshDescription(); - - // Legacy function using RawMesh for static Mesh creation - bool CreateStaticMesh_RawMesh(); - - // Create a UHoudiniStaticMesh - bool CreateHoudiniStaticMesh(); - - static void ApplyComplexColliderHelper( - UStaticMesh* TargetStaticMesh, - UStaticMesh* ComplexStaticMesh, - const EHoudiniSplitType SplitType, - bool& AssignedCustomCollisionMesh, - FHoudiniOutputObject* OutputObject); - - void ResetPartCache(); - - bool UpdatePartVertexList(); - - void SortSplitGroups(); - - bool UpdateSplitsFacesAndIndices(); - - // Update this part's position cache if we haven't already - bool UpdatePartPositionIfNeeded(); - - // Update this part's normal cache if we haven't already - bool UpdatePartNormalsIfNeeded(); - - // Update this part's tangent and binormal caches if we haven't already - bool UpdatePartTangentsIfNeeded(); - - // Update this part's color cache if we haven't already - bool UpdatePartColorsIfNeeded(); - - // Update this part's alpha if we haven't already - bool UpdatePartAlphasIfNeeded(); - - // Update this part's face smoothing values if we haven't already - bool UpdatePartFaceSmoothingIfNeeded(); - - // Update this part's UV sets if we haven't already - bool UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused = false); - - // Update this part;s lightmap resolution cache if we haven't already - bool UpdatePartLightmapResolutionsIfNeeded(); - - // Update this part's lod screensize attribute cache if we haven't already - bool UpdatePartLODScreensizeIfNeeded(); - - // Update th unique materials ids and infos needed for this part using the face materials and overrides - bool UpdatePartNeededMaterials(); - - // Update this part's face material IDs, unique material IDs and material Infos caches if we haven't already - bool UpdatePartFaceMaterialIDsIfNeeded(); - - // Update this part's material overrides cache if we haven't already - bool UpdatePartFaceMaterialOverridesIfNeeded(); - - // Updates and create the material that are needed for this part - bool CreateNeededMaterials(); - - UStaticMesh* CreateNewStaticMesh(const FString& InMeshIdentifierString); - - UStaticMesh* FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); - - UHoudiniStaticMesh* CreateNewHoudiniStaticMesh(const FString& InMeshIdentifierString); - - UHoudiniStaticMesh* FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); - - float GetLODSCreensizeForSplit(const FString& SplitGroupName); - - // Create convex/UCX collider for a split and add to the aggregate - bool AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); - // Create simple colliders for a split and add to the aggregate - bool AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); - - // Helper functions to generate the simple colliders and add them to the aggregate - static int32 GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions); - - // Helper functions for the simple colliders generation - static void CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec); - static void CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); - static void CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); - static void CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec); - - // Helper functions to remove unused/stale components - static bool RemoveAndDestroyComponent(UObject* InComponent); - - // Helper to create a new mesh component - static UMeshComponent* CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf& InComponentType); - - // Helper to update an existing mesh component - static void UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, - const FHoudiniGeoPartObject *InHGPO, TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors, - bool bInApplyGenericProperties=true); - - // Helper to create or update a mesh component for a UStaticMesh or proxy mesh output - static UMeshComponent* CreateOrUpdateMeshComponent( - const UHoudiniOutput* InOutput, - UObject* InOuterComponent, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TSubclassOf& InComponentType, - FHoudiniOutputObject& OutOutputObject, - FHoudiniGeoPartObject const *& OutFoundHGPO, - bool &bCreated); - - // Helper to initialize a UStaticMeshComponent after it was created. - static bool PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh); - - // Helper to initialize a UHoudiniStaticMeshComponent after it was created. - static bool PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh); - - static bool AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, - TArray& HoudiniCreatedSocketActors, TArray& HoudiniAttachedSocketActors); - - protected: - - // Data cache for this translator - - // The HoudiniGeoPartObject we're working on - FHoudiniGeoPartObject HGPO; - - // Outer object for attaching components to - UObject* OuterComponent; - - // Structure that handles cooking/baking package creation parameters - FHoudiniPackageParams PackageParams; - - - // Previous output objects - TMap InputObjects; - - // New Output objects - TMap OutputObjects; - - - // Input Material Map - TMap InputAssignmentMaterials; - // Output Material Map - TMap OutputAssignmentMaterials; - // Input Replacement Materials maps - TMap ReplacementMaterials; - - // Input mesh properties - //TMap InputObjectProperties; - // Output mesh properties - //TMap OutputObjectProperties; - - // Indicates the update is forced - bool ForceRebuild; - - // The generated simple/UCX colliders - TMap AllAggregateCollisions; - - // Names of the groups used for splitting the geometry - TArray AllSplitGroups; - - // Per-split lists of faces - TMap> AllSplitVertexLists; - - // Per-split number of faces - TMap AllSplitVertexCounts; - - // Per-split indices arrays - TMap> AllSplitFaceIndices; - - // Per-split first valid vertex index - TMap AllSplitFirstValidVertexIndex; - - // Per-split first valid prim index - TMap AllSplitFirstValidPrimIndex; - - // Vertex Indices for the part - TArray PartVertexList; - - // Positions - TArray PartPositions; - HAPI_AttributeInfo AttribInfoPositions; - - // Vertex Normals - TArray PartNormals; - HAPI_AttributeInfo AttribInfoNormals; - - // Vertex TangentU - TArray PartTangentU; - HAPI_AttributeInfo AttribInfoTangentU; - - // Vertex TangentV - TArray PartTangentV; - HAPI_AttributeInfo AttribInfoTangentV; - - // Vertex Colors - TArray PartColors; - HAPI_AttributeInfo AttribInfoColors; - - // Vertex Alpha values - TArray PartAlphas; - HAPI_AttributeInfo AttribInfoAlpha; - - // Face Smoothing masks - TArray PartFaceSmoothingMasks; - HAPI_AttributeInfo AttribInfoFaceSmoothingMasks; - - // UVs - TArray> PartUVSets; - TArray AttribInfoUVSets; - - // Lightmap resolution - TArray PartLightMapResolutions; - HAPI_AttributeInfo AttribInfoLightmapResolution; - - // Material IDs per face - TArray PartFaceMaterialIds; - HAPI_AttributeInfo AttribInfoFaceMaterialIds; - // Unique material IDs - TArray PartUniqueMaterialIds; - //TSet PartUniqueMaterialIds; - // Material infos for each unique Material - TArray PartUniqueMaterialInfos; - //TSet PartUniqueMaterialInfos; - // Indicates we only have a single face material - bool bOnlyOneFaceMaterial; - - // Material Overrides per face - TArray PartFaceMaterialOverrides; - HAPI_AttributeInfo AttribInfoFaceMaterialOverrides; - // Indicates that material overides attributes need an instance to be created - bool bMaterialOverrideNeedsCreateInstance; - - // LOD Screensize - TArray PartLODScreensize; - HAPI_AttributeInfo AttribInfoLODScreensize; - - int32 DefaultMeshSmoothing; - - // When building a mesh, if an associated material already exists, treat - // it as up to date, regardless of the MaterialInfo.bHasChanged flag - bool bTreatExistingMaterialsAsUpToDate; - - // Default properties to be used when generating Static Meshes - FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; - - // Default Mesh Build settings to be used when generating Static Meshes - FMeshBuildSettings StaticMeshBuildSettings; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" +#include "HoudiniAssetComponent.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "PhysicsEngine/AggregateGeom.h" + +//#include "HoudiniMeshTranslator.generated.h" + +class UStaticMesh; +class UStaticMeshSocket; +class UMaterialInterface; +class UMeshComponent; +class UStaticMeshComponent; +class UHoudiniStaticMesh; +class UHoudiniStaticMeshComponent; + +struct FKAggregateGeom; +struct FHoudiniGenericAttribute; + + +UENUM() +enum class EHoudiniSplitType : uint8 +{ + Invalid, + + Normal, + + LOD, + + RenderedComplexCollider, + InvisibleComplexCollider, + + RenderedUCXCollider, + InvisibleUCXCollider, + + RenderedSimpleCollider, + InvisibleSimpleCollider +}; + +struct HOUDINIENGINE_API FHoudiniMeshTranslator +{ + public: + + //----------------------------------------------------------------------------------------------------------------------------- + // HOUDINI TO UNREAL + //----------------------------------------------------------------------------------------------------------------------------- + + // + static bool CreateAllMeshesAndComponentsFromHoudiniOutput( + UHoudiniOutput* InOutput, + const FHoudiniPackageParams& InPackageParams, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, + UObject* InOuterComponent, + bool bInTreatExistingMaterialsAsUpToDate=false, + bool bInDestroyProxies=false); + + static bool CreateStaticMeshFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& InOutputObjects, + TMap& OutOutputObjects, + TMap& InAssignmentMaterialMap, + TMap& InReplacementMaterialMap, + const bool& InForceRebuild, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, + bool bInTreatExistingMaterialsAsUpToDate = false); + + static bool CreateOrUpdateAllComponents( + UHoudiniOutput* InOutput, + UObject* InOuterComponent, + TMap& InNewOutputObjects, + bool bInDestroyProxies=false, + bool bInApplyGenericProperties=true); + + + //----------------------------------------------------------------------------------------------------------------------------- + // HELPERS + //----------------------------------------------------------------------------------------------------------------------------- + static EHoudiniSplitType GetSplitTypeFromSplitName(const FString& InSplitName); + + static FString GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType); + + // TODO: Rename me! and template me! float/int/string ? + // TransferPartAttributesToSplitVertices + static int32 TransferRegularPointAttributesToVertices( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData); + + template + static int32 TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutSplitData); + + // Update the MeshBuild Settings using the values from the runtime settings/overrides on the HAC + void UpdateMeshBuildSettings( + FMeshBuildSettings& OutMeshBuildSettings, + const bool& bHasNormals, + const bool& bHasTangents, + const bool& bHasLightmapUVSet); + + + //----------------------------------------------------------------------------------------------------------------------------- + // ACCESSORS + //----------------------------------------------------------------------------------------------------------------------------- + + //----------------------------------------------------------------------------------------------------------------------------- + // MUTATORS + //----------------------------------------------------------------------------------------------------------------------------- + void SetHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) { HGPO = InHGPO; }; + void SetOuterComponent(UObject* InOuter) { OuterComponent = InOuter; }; + void SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO = false); + + void SetInputObjects(const TMap& InInputObjects) { InputObjects = InInputObjects; }; + void SetOutputObjects(TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; + + void SetInputAssignmentMaterials(const TMap& InInputMaterials) { InputAssignmentMaterials = InInputMaterials; }; + void SetReplacementMaterials(const TMap& InReplacementMaterials) { ReplacementMaterials = InReplacementMaterials; }; + + //void SetInputObjectProperties(const TMap& InInputObjectProperties) { InputObjectProperties = InInputObjectProperties; }; + //void SetOutputObjectProperties(TMap& InOutputObjectProperties) { OutputObjectProperties = InOutputObjectProperties; }; + + void SetTreatExistingMaterialsAsUpToDate(bool bInTreatExistingMaterialsAsUpToDate) { bTreatExistingMaterialsAsUpToDate = bInTreatExistingMaterialsAsUpToDate; } + + void SetStaticMeshGenerationProperties(const FHoudiniStaticMeshGenerationProperties& InStaticMeshGenerationProperties) { StaticMeshGenerationProperties = InStaticMeshGenerationProperties; }; + + void SetStaticMeshBuildSettings(const FMeshBuildSettings& InMBS) { StaticMeshBuildSettings = InMBS; }; + + protected: + + // Create a StaticMesh using the MeshDescription format + bool CreateStaticMesh_MeshDescription(); + + // Legacy function using RawMesh for static Mesh creation + bool CreateStaticMesh_RawMesh(); + + // Create a UHoudiniStaticMesh + bool CreateHoudiniStaticMesh(); + + static void ApplyComplexColliderHelper( + UStaticMesh* TargetStaticMesh, + UStaticMesh* ComplexStaticMesh, + const EHoudiniSplitType SplitType, + bool& AssignedCustomCollisionMesh, + FHoudiniOutputObject* OutputObject); + + void ResetPartCache(); + + bool UpdatePartVertexList(); + + void SortSplitGroups(); + + bool UpdateSplitsFacesAndIndices(); + + // Update this part's position cache if we haven't already + bool UpdatePartPositionIfNeeded(); + + // Update this part's normal cache if we haven't already + bool UpdatePartNormalsIfNeeded(); + + // Update this part's tangent and binormal caches if we haven't already + bool UpdatePartTangentsIfNeeded(); + + // Update this part's color cache if we haven't already + bool UpdatePartColorsIfNeeded(); + + // Update this part's alpha if we haven't already + bool UpdatePartAlphasIfNeeded(); + + // Update this part's face smoothing values if we haven't already + bool UpdatePartFaceSmoothingIfNeeded(); + + // Update this part's UV sets if we haven't already + bool UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused = false); + + // Update this part;s lightmap resolution cache if we haven't already + bool UpdatePartLightmapResolutionsIfNeeded(); + + // Update this part's lod screensize attribute cache if we haven't already + bool UpdatePartLODScreensizeIfNeeded(); + + // Update th unique materials ids and infos needed for this part using the face materials and overrides + bool UpdatePartNeededMaterials(); + + // Update this part's face material IDs, unique material IDs and material Infos caches if we haven't already + bool UpdatePartFaceMaterialIDsIfNeeded(); + + // Update this part's material overrides cache if we haven't already + bool UpdatePartFaceMaterialOverridesIfNeeded(); + + // Updates and create the material that are needed for this part + bool CreateNeededMaterials(); + + UStaticMesh* CreateNewStaticMesh(const FString& InMeshIdentifierString); + + UStaticMesh* FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); + + UHoudiniStaticMesh* CreateNewHoudiniStaticMesh(const FString& InMeshIdentifierString); + + UHoudiniStaticMesh* FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); + + float GetLODSCreensizeForSplit(const FString& SplitGroupName); + + // Create convex/UCX collider for a split and add to the aggregate + bool AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); + // Create simple colliders for a split and add to the aggregate + bool AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); + + // Helper functions to generate the simple colliders and add them to the aggregate + static int32 GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions); + + // Helper functions for the simple colliders generation + static void CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec); + static void CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); + static void CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); + static void CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec); + + // Helper functions to remove unused/stale components + static bool RemoveAndDestroyComponent(UObject* InComponent); + + // Helper to create a new mesh component + static UMeshComponent* CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf& InComponentType); + + // Helper to update an existing mesh component + static void UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, + const FHoudiniGeoPartObject *InHGPO, TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors, + bool bInApplyGenericProperties=true); + + // Helper to create or update a mesh component for a UStaticMesh or proxy mesh output + static UMeshComponent* CreateOrUpdateMeshComponent( + const UHoudiniOutput* InOutput, + UObject* InOuterComponent, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TSubclassOf& InComponentType, + FHoudiniOutputObject& OutOutputObject, + FHoudiniGeoPartObject const *& OutFoundHGPO, + bool &bCreated); + + // Helper to initialize a UStaticMeshComponent after it was created. + static bool PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh); + + // Helper to initialize a UHoudiniStaticMeshComponent after it was created. + static bool PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh); + + static bool AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, + TArray& HoudiniCreatedSocketActors, TArray& HoudiniAttachedSocketActors); + + protected: + + // Data cache for this translator + + // The HoudiniGeoPartObject we're working on + FHoudiniGeoPartObject HGPO; + + // Outer object for attaching components to + UObject* OuterComponent; + + // Structure that handles cooking/baking package creation parameters + FHoudiniPackageParams PackageParams; + + + // Previous output objects + TMap InputObjects; + + // New Output objects + TMap OutputObjects; + + + // Input Material Map + TMap InputAssignmentMaterials; + // Output Material Map + TMap OutputAssignmentMaterials; + // Input Replacement Materials maps + TMap ReplacementMaterials; + + // Input mesh properties + //TMap InputObjectProperties; + // Output mesh properties + //TMap OutputObjectProperties; + + // Indicates the update is forced + bool ForceRebuild; + + // The generated simple/UCX colliders + TMap AllAggregateCollisions; + + // Names of the groups used for splitting the geometry + TArray AllSplitGroups; + + // Per-split lists of faces + TMap> AllSplitVertexLists; + + // Per-split number of faces + TMap AllSplitVertexCounts; + + // Per-split indices arrays + TMap> AllSplitFaceIndices; + + // Per-split first valid vertex index + TMap AllSplitFirstValidVertexIndex; + + // Per-split first valid prim index + TMap AllSplitFirstValidPrimIndex; + + // Vertex Indices for the part + TArray PartVertexList; + + // Positions + TArray PartPositions; + HAPI_AttributeInfo AttribInfoPositions; + + // Vertex Normals + TArray PartNormals; + HAPI_AttributeInfo AttribInfoNormals; + + // Vertex TangentU + TArray PartTangentU; + HAPI_AttributeInfo AttribInfoTangentU; + + // Vertex TangentV + TArray PartTangentV; + HAPI_AttributeInfo AttribInfoTangentV; + + // Vertex Colors + TArray PartColors; + HAPI_AttributeInfo AttribInfoColors; + + // Vertex Alpha values + TArray PartAlphas; + HAPI_AttributeInfo AttribInfoAlpha; + + // Face Smoothing masks + TArray PartFaceSmoothingMasks; + HAPI_AttributeInfo AttribInfoFaceSmoothingMasks; + + // UVs + TArray> PartUVSets; + TArray AttribInfoUVSets; + + // Lightmap resolution + TArray PartLightMapResolutions; + HAPI_AttributeInfo AttribInfoLightmapResolution; + + // Material IDs per face + TArray PartFaceMaterialIds; + HAPI_AttributeInfo AttribInfoFaceMaterialIds; + // Unique material IDs + TArray PartUniqueMaterialIds; + //TSet PartUniqueMaterialIds; + // Material infos for each unique Material + TArray PartUniqueMaterialInfos; + //TSet PartUniqueMaterialInfos; + // Indicates we only have a single face material + bool bOnlyOneFaceMaterial; + + // Material Overrides per face + TArray PartFaceMaterialOverrides; + HAPI_AttributeInfo AttribInfoFaceMaterialOverrides; + // Indicates that material overides attributes need an instance to be created + bool bMaterialOverrideNeedsCreateInstance; + + // LOD Screensize + TArray PartLODScreensize; + HAPI_AttributeInfo AttribInfoLODScreensize; + + int32 DefaultMeshSmoothing; + + // When building a mesh, if an associated material already exists, treat + // it as up to date, regardless of the MaterialInfo.bHasChanged flag + bool bTreatExistingMaterialsAsUpToDate; + + // Default properties to be used when generating Static Meshes + FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; + + // Default Mesh Build settings to be used when generating Static Meshes + FMeshBuildSettings StaticMeshBuildSettings; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp index 5a6f68c73..604134d09 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp @@ -1,2121 +1,2174 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniOutputTranslator.h" - -#include "HoudiniOutput.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" - -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniInput.h" -#include "HoudiniStaticMesh.h" - -#include "HoudiniMeshTranslator.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" - -#include "Editor.h" -#include "EditorSupportDelegates.h" -#include "FileHelpers.h" -#include "LandscapeInfo.h" -#include "HAL/PlatformFilemanager.h" -#include "HAL/FileManager.h" -#include "Engine/WorldComposition.h" -#include "Modules/ModuleManager.h" -#include "WorldBrowserModule.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// -bool -FHoudiniOutputTranslator::UpdateOutputs( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate, - bool& bOutHasHoudiniStaticMeshOutput) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the temp folder override - FHoudiniOutputTranslator::GetTempFolderFromAttribute(HAC); - - // Outputs that should be cleared, but only AFTER new output processing have taken place. - // This is needed for landscape resizing where the new landscape needs to copy data from the original landscape - // before the original landscape gets destroyed. - TArray DeferredClearOutputs; - - // Check if the HDA has been marked as not producing outputs - if (!HAC->bOutputless) - { - // Check if we want to convert legacy v1 data - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility && HAC->Version1CompatibilityHAC) - { - // Do not reuse legacy outputs! - for (auto& OldOutput : HAC->Outputs) - { - ClearOutput(OldOutput); - } - } - - TArray NewOutputs; - if (FHoudiniOutputTranslator::BuildAllOutputs(HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos)) - { - // NOTE: For now we are currently forcing all outputs to be cleared here. There is still an issue where, in some - // circumstances, landscape tiles disappear when clearing outputs after processing. - // The reason we may need to defer landscape clearing is to allow the landscape creation code to - // capture the extent of the landscape. The extent of the landscape can only be calculated if all landscape - // tiles are still present in the map. If we find that we don't need this for updating of Input landscapes, - // we can safely remove this feature. - ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); - // Replace with the new parameters - HAC->Outputs = NewOutputs; - } - } - else - { - // This HDA is marked as not supposed to produce any output - ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); - } - - // Look for details generic property attributes on the outputs, - // and try to apply them to the HAC. - // This can be used to preset some of the HDA's uproperty via attribute - TArray GenericAttributes; - for (auto& CurrentOutput : HAC->Outputs) - { - const TArray& CurrentOutputHGPO = CurrentOutput->GetHoudiniGeoPartObjects(); - for (auto& CurrentHGPO : CurrentOutputHGPO) - { - FHoudiniEngineUtils::GetGenericAttributeList( - CurrentHGPO.GeoId, - CurrentHGPO.PartId, - HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, - GenericAttributes, - HAPI_ATTROWNER_DETAIL); - } - } - - // Attempt to apply the attributes to the HAC if we have any - for (const auto& CurrentPropAttribute : GenericAttributes) - { - // Get the current Property Attribute - const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; - if (CurrentPropertyName.IsEmpty()) - continue; - - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(HAC, CurrentPropAttribute)) - continue; - - // Success! - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on Houdini Asset Component named %s"), *CurrentPropertyName, *HAC->GetName()); - } - - // NOTE: PersistentWorld can be NULL when, for example, working with - // HoudiniAssetComponents in Blueprints. - UWorld* PersistentWorld = HAC->GetWorld(); - UWorldComposition* WorldComposition = nullptr; - if (PersistentWorld) - { - WorldComposition = PersistentWorld->WorldComposition; - } - - if (IsValid(WorldComposition)) - { - // We don't want the origin to shift as we're potentially updating levels. - WorldComposition->bTemporarilyDisableOriginTracking = true; - } - - // "Process" the mesh. - // TODO: Move this to the actual processing stage, - // And see if some of this could be threaded - UObject* OuterComponent = HAC; - - FString HoudiniAssetPath = FPaths::GetPath(HAC->GetPathName()); - FString ComponentGUIDString = HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); - FString HoudiniAssetNameString = HAC->GetDisplayName(); - - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - PackageParams.ObjectName = FString(); - - // ---------------------------------------------------- - // Outputs prepass - // ---------------------------------------------------- - - TArray CreatedWorldCompositionPackages; - bool bCreatedNewMaps = false; - //... for heightfield outputs ...// - - // Collect all the landscape layers' global min/max values. - TMap LandscapeLayerGlobalMinimums; - TMap LandscapeLayerGlobalMaximums; - - // Store the instancer outputs separately so we can process them later, after all mesh output are processed. - // Determine the total number of instances, if we have more than 1 then mesh parts with instanced geo we will not create proxy meshes - // Also if we have object instancer (or oldschool attribute instancers), we won't be creating any proxy at all - TArray InstancerOutputs; - int32 NumInstances = 0; - bool bHasObjectInstancer = false; - - for (auto& CurOutput : HAC->Outputs) - { - if (CurOutput->GetType() == EHoudiniOutputType::Instancer) - { - // InstancerOutputs.Add(CurOutput); - for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type == EHoudiniPartType::Instancer) - { - if (HGPO.InstancerType == EHoudiniInstancerType::PackedPrimitive) - { - NumInstances += HGPO.PartInfo.InstanceCount; - } - else - { - NumInstances += HGPO.PartInfo.PointCount; - } - - if ((HGPO.InstancerType == EHoudiniInstancerType::ObjectInstancer) - || (HGPO.InstancerType == EHoudiniInstancerType::OldSchoolAttributeInstancer)) - { - bHasObjectInstancer = true; - } - } - } - } - else if (CurOutput->GetType() == EHoudiniOutputType::Landscape) - { - FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, false); - } - } - - bOutHasHoudiniStaticMeshOutput = false; - int32 NumVisibleOutputs = 0; - int32 NumOutputs = HAC->Outputs.Num(); - bool bHasLandscape = false; - - // Before processing all the outputs, - // See if we have any landscape input that have "Update Input Landscape" enabled - // And make an array of all our input landscapes - TArray AllInputLandscapes; - TArray InputLandscapesToUpdate; - - for (auto CurrentInput : HAC->Inputs) - { - if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) - continue; - - // Get the landscape input's landscape - ALandscapeProxy* InputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); - if (!InputLandscape) - continue; - - AllInputLandscapes.Add(InputLandscape); - - if (CurrentInput->GetUpdateInputLandscape()) - InputLandscapesToUpdate.Add(InputLandscape); - } - - // ---------------------------------------------------- - // Process outputs - // ---------------------------------------------------- - // Landscape creation will cache the first tile as a reference location - // in this struct to be used by during construction of subsequent tiles. - FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; - // Landscape Size info will be cached by the first tile, similar to LandscapeReferenceLocation - FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; - FHoudiniLandscapeExtent LandscapeExtent; - - TArray CreatedPackages; - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* CurOutput = HAC->GetOutputAt(OutputIdx); - if (!CurOutput || CurOutput->IsPendingKill()) - continue; - - FString Notification = FString::Format(TEXT("Processing output {0} / {1}..."), {FString::FromInt(OutputIdx + 1), FString::FromInt(NumOutputs)}); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - if (!HAC->IsOutputTypeSupported(CurOutput->GetType())) - continue; - - switch (CurOutput->GetType()) - { - case EHoudiniOutputType::Mesh: - { - bool bIsProxyStaticMeshEnabled = ( - HAC->IsProxyStaticMeshEnabled() && - !HAC->HasNoProxyMeshNextCookBeenRequested() && - !HAC->IsBakeAfterNextCookEnabled()); - if (bIsProxyStaticMeshEnabled && NumInstances > 1) - { - if (bHasObjectInstancer) - { - // Completely disable proxies if we have object instancers/old school attribute instancers - // as they rely on having a static mesh created (and the instanced mesh HGPO is not marked as instanced...) - bIsProxyStaticMeshEnabled = false; - } - else - { - // If we dont have proxy instancer, enable proxy only for non-instanced mesh - for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.bIsInstanced && HGPO.Type == EHoudiniPartType::Mesh) - { - bIsProxyStaticMeshEnabled = false; - break; - } - } - } - } - - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - PackageParams, - bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, - HAC->StaticMeshGenerationProperties, - HAC->StaticMeshBuildSettings, - OuterComponent); - - NumVisibleOutputs++; - - // Look for UHoudiniStaticMesh in the output, and set bOutHasHoudiniStaticMeshOutput accordingly - if (bIsProxyStaticMeshEnabled && !bOutHasHoudiniStaticMeshOutput) - { - bOutHasHoudiniStaticMeshOutput &= CurOutput->HasAnyCurrentProxy(); - } - - break; - } - - case EHoudiniOutputType::Curve: - { - const TArray &GeoPartObjects = CurOutput->GetHoudiniGeoPartObjects(); - - if (GeoPartObjects.Num() <= 0) - continue; - - const FHoudiniGeoPartObject & CurHGPO = GeoPartObjects[0]; - - if (CurOutput->IsEditableNode()) - { - if (!CurOutput->HasEditableNodeBuilt()) - { - // Editable curve, only need to be built once. - UHoudiniSplineComponent* HoudiniSplineComponent = FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode( - CurHGPO.GeoId, - CurHGPO.PartName, - HAC); - - HoudiniSplineComponent->SetIsEditableOutputCurve(true); - - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; - EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; - EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; - EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; - - TMap& OutputObjects = CurOutput->GetOutputObjects(); - FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - FoundOutputObject.OutputComponent = HoudiniSplineComponent; - - CurOutput->SetHasEditableNodeBuilt(true); - } - } - else - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); - NumVisibleOutputs += CurOutput->GetOutputObjects().Num(); - break; - } - } - break; - - case EHoudiniOutputType::Instancer: - InstancerOutputs.Add(CurOutput); - break; - - case EHoudiniOutputType::Landscape: - { - NumVisibleOutputs++; - - // This gets called for each heightfield primitive from Houdini, i.e., each "tile". - bool bNewMapCreated = false; - // Registering of untracked actors is not currently used in the HDA - // workflow. HDA cleanup will manually search for shared landscapes - // and remove them. That aforementioned behaviour should really be updated to - // make use of untracked actors on the HAC (similar to PDG Asset Link). - TArray> UntrackedActors; - - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - UntrackedActors, - InputLandscapesToUpdate, - AllInputLandscapes, - HAC, - TEXT("{hda_actor_name}_"), - PersistentWorld, - LandscapeLayerGlobalMinimums, - LandscapeLayerGlobalMaximums, - LandscapeExtent, - LandscapeSizeInfo, - LandscapeReferenceLocation, - PackageParams, - CreatedPackages); - - bHasLandscape = true; - - // Attach the created landscape to the parent HAC. - ALandscapeProxy* OutputLandscape = nullptr; - for (auto& Pair : CurOutput->GetOutputObjects()) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(Pair.Value.OutputObject); - OutputLandscape = LandscapePtr->GetRawPtr(); - break; - } - - if (OutputLandscape) - { - // Attach the created landscapes to HAC - // Output Transforms are always relative to the HDA - HAC->SetMobility(EComponentMobility::Static); - OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); - // Note that the above attach will cause the collision components to crap out. This manifests - // itself via the Landscape editor tools not being able to trace Landscape collision components. - // By recreating collision components here, it appears to put things back into working order. - OutputLandscape->GetLandscapeInfo()->FixupProxiesTransform(); - OutputLandscape->GetLandscapeInfo()->RecreateLandscapeInfo(PersistentWorld, true); - OutputLandscape->RecreateCollisionComponents(); - } - - bCreatedNewMaps |= bNewMapCreated; - break; - } - default: - // Do Nothing for now - break; - } - } - - // Now that all meshes have been created, process the instancers - for (auto& CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); - NumVisibleOutputs++; - } - - if (NumVisibleOutputs > 0) - { - // If we have valid outputs, we don't need to display the houdini logo anymore... - FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(HAC); - } - else - { - // ... if we don't have any valid outputs however, we should - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HAC); - } - - // Clear any old outputs that was marked as "Should Defer Clear". - // This should happen before SharedLandscapeActor cleanup - // since this needs to remove old landscape proxies so that empty SharedLandscapeActors - // can be removed afterward. - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniOutputTranslator::UpdateOutputs] Clearing old outputs: %d"), DeferredClearOutputs.Num()); - for(UHoudiniOutput* OldOutput : DeferredClearOutputs) - { - ClearOutput(OldOutput); - } - - // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) - // { - // LandscapeExtents.IntermediateResizeLandscape->Destroy(); - // LandscapeExtents.IntermediateResizeLandscape = nullptr; - // } - - if (bHasLandscape) - { - // ---------------------------------------------------- - // Cleanup untracked shared landscape actors - // ---------------------------------------------------- - // This is a nasty hack to clean up SharedLandscape actors generated by the - // Landscape translator but aren't tracked by an HoudiniOutputObject, since the - // translators can't dynamically create outputs. - - { - // First collect all the landscapes that is being tracked by the HAC. - TSet TrackedLandscapes; - for(UHoudiniOutput* Output : HAC->Outputs) - { - if (Output->GetType() == EHoudiniOutputType::Landscape) - { - for(auto& Elem : Output->GetOutputObjects()) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(Elem.Value.OutputObject); - if (!IsValid(LandscapePtr)) - continue; - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - if (IsValid(LandscapeProxy)) - { - TrackedLandscapes.Add(LandscapeProxy); - - // We need to recreate component states for landscapes if a tile was created, moved, or resized - // otherwise the landscape will exhibit render artifacts (such as only rendering every other - // component.) - LandscapeProxy->RecreateComponentsState(); - } - } - } - } - - // Iterate over Houdini asset child assets in order to find dangling Landscape actors - TArray AttachedComponents = HAC->GetAttachChildren(); - for(USceneComponent* Component : AttachedComponents) - { - if (!IsValid(Component)) - continue; - AActor* Actor = Component->GetOwner(); - ALandscape* Landscape = Cast(Actor); - if (!Landscape) - continue; - if (TrackedLandscapes.Contains(Landscape)) - continue; - - if (Landscape->GetLandscapeInfo()->Proxies.Num() == 0) - Landscape->Destroy(); - } - } - - // Recreate Landscape Info calls WorldChange, so no need to do it manually. - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - } - - // Destroy the intermediate resize landscape, if there is one. - // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) - // { - // FHoudiniLandscapeTranslator::DestroyLandscape(LandscapeExtents.IntermediateResizeLandscape); - // } - - if (IsValid(WorldComposition)) - { - // Disable the flag that we set before starting the import process. - WorldComposition->bTemporarilyDisableOriginTracking = false; - } - - // If the owner component was marked as loaded, unmark all outputs - if (HAC->HasBeenLoaded()) - { - for (auto& CurrentOutput : HAC->Outputs) - { - CurrentOutput->MarkAsLoaded(false); - } - } - - if (bCreatedNewMaps) - { - // Force the asset registry to update its cache of packages paths - // recursively for this world, otherwise world composition won't - // pick them up during the WorldComposition::Rescan(). - FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); - - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - - FHoudiniEngineUtils::LogWorldInfo(PersistentWorld); - if (WorldComposition) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); - } - - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); - } - - if (CreatedPackages.Num() > 0) - { - // Save created packages. For example, we don't want landscape layers deleted - // along with the HDA. - FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); - } - - return true; -} - -bool -FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - UObject* OuterComponent = HAC; - - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - PackageParams.ObjectName = FString(); - - bool bFoundProxies = false; - TArray InstancerOutputs; - for (auto& CurOutput : HAC->Outputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType == EHoudiniOutputType::Mesh) - { - if (CurOutput->HasAnyCurrentProxy()) - { - bFoundProxies = true; - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - PackageParams, - HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, - HAC->StaticMeshGenerationProperties, - HAC->StaticMeshBuildSettings, - OuterComponent, - true, // bInTreatExistingMaterialsAsUpToDate - bInDestroyProxies - ); - } - } - else if (OutputType == EHoudiniOutputType::Instancer) - { - InstancerOutputs.Add(CurOutput); - } - } - - // Rebuild instancers if we built any static meshes from proxies - if (bFoundProxies) - { - for (auto& CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); - } - } - - return true; -} - -// -bool -FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) -{ - HAPI_NodeId & AssetId = HAC->AssetId; - // Get the AssetInfo - 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 ObjectInfos; - TArray ObjectTransforms; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) - return false; - - TArray EditableCurveObjIds; - TArray EditableCurveGeoIds; - TArray EditableCurvePartIds; - TArray EditableCurvePartNames; - - // Iterate through all objects to get all editable curve's object geo and part Ids. - - for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) - { - // Retrieve the object info - const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; - - // Cache/convert them - FHoudiniObjectInfo CurrentObjectInfo; - CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); - - // Start by getting the number of editable nodes - int32 EditableNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, - true, &EditableNodeCount)); - - if (EditableNodeCount > 0) - { - TArray< HAPI_NodeId > EditableNodeIds; - EditableNodeIds.SetNumUninitialized(EditableNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - - for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) - { - HAPI_GeoInfo CurrentEditableGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentEditableGeoInfo.isDisplayGeo) - continue; - - // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GeoType::HAPI_GEOTYPE_CURVE) - continue; - - // Check if the curve is closed (-1 unknown, could not find parameter on node). A closed curve will - // be returned as a mesh by HAPI instead of a curve - int32 CurveClosed = -1; - if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - EditableNodeIds[nEditable], HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) - { - CurveClosed = -1; - } - else - { - if (CurveClosed) - CurveClosed = 1; - else - CurveClosed = 0; - } - - // Cook the editable node to get its parts - if (CurrentEditableGeoInfo.partCount <= 0) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentEditableGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentEditableGeoInfo.nodeId, - &CurrentEditableGeoInfo)); - } - - // Iterate on this geo's parts - for (int32 PartId = 0; PartId < CurrentEditableGeoInfo.partCount; ++PartId) - { - // Get part information. - HAPI_PartInfo CurrentHapiPartInfo; - FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - continue; - - // A closed curve will be returned as a mesh in HAPI - if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE && - (CurveClosed <= 0 || CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_MESH)) - continue; - - // Get the editable curve's part name - FHoudiniEngineString hapiSTR(CurrentHapiPartInfo.nameSH); - FString PartName; - hapiSTR.ToFString(PartName); - - EditableCurveObjIds.Add(CurrentHapiObjectInfo.nodeId); - EditableCurveGeoIds.Add(CurrentEditableGeoInfo.nodeId); - EditableCurvePartIds.Add(CurrentHapiPartInfo.id); - EditableCurvePartNames.Add(PartName); - } - } - } - } - - int32 Idx = 0; - for (auto& CurrentOutput : HAC->Outputs) - { - if (CurrentOutput->IsEditableNode()) - { - // The HAC is Loaded, re-assign node id to its editable curves - if (CurrentOutput->HasEditableNodeBuilt()) - { - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - for (auto& Pair : OutputObjects) - { - if (Idx >= EditableCurvePartIds.Num()) - break; - - UHoudiniSplineComponent * HoudiniSplineComponent = Cast(Pair.Value.OutputComponent); - if (HoudiniSplineComponent && !HoudiniSplineComponent->IsPendingKill()) - { - HoudiniSplineComponent->SetNodeId(EditableCurveGeoIds[Idx]); - - Pair.Key.ObjectId = EditableCurveObjIds[Idx]; - Pair.Key.GeoId = EditableCurveGeoIds[Idx]; - Pair.Key.PartId = EditableCurvePartIds[Idx]; - Pair.Key.PartName = EditableCurvePartNames[Idx]; - - Idx += 1; - } - } - } - // The HAC is a Duplication, re-construct output objects with attached duplicated editable curves, matching by part name - else - { - const TArray &Children = HAC->GetAttachChildren(); - for (auto & CurAttachedComp : Children) - { - if (!CurAttachedComp || CurAttachedComp->IsPendingKill()) - continue; - - if (!CurAttachedComp->IsA()) - continue; - - UHoudiniSplineComponent * CurAttachedSplineComp = Cast(CurAttachedComp); - if (!CurAttachedSplineComp) - continue; - - if (!CurAttachedSplineComp->IsEditableOutputCurve()) - continue; - - if (Idx >= EditableCurvePartIds.Num()) - break; - - // Found a match - if (CurAttachedSplineComp->GetGeoPartName().Equals(EditableCurvePartNames[Idx])) - { - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = EditableCurveObjIds[Idx]; - EditableSplineComponentIdentifier.GeoId = EditableCurveGeoIds[Idx]; - EditableSplineComponentIdentifier.PartId = EditableCurvePartIds[Idx]; - EditableSplineComponentIdentifier.PartName = EditableCurvePartNames[Idx]; - - CurAttachedSplineComp->SetNodeId(EditableSplineComponentIdentifier.GeoId); - - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - FHoudiniOutputObject& NewOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - NewOutputObject.OutputComponent = CurAttachedSplineComp; - - CurrentOutput->SetHasEditableNodeBuilt(true); - - // Never add additional rot/scale attributes on editable curves as this crashes HAPI - FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( - CurAttachedSplineComp, false); - - Idx += 1; - break; - } - } - } - } - else - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); - } - - // Mark our outputs as loaded so they can be matched for potential reuse - // This indicates that the HGPO's ids are invalid and that HGPO should be matched using partnames instead - CurrentOutput->MarkAsLoaded(true); - } - - return true; -} - -// -bool -FHoudiniOutputTranslator::UploadChangedEditableOutput( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray &Outputs = HAC->Outputs; - - // Iterate through the outputs array of HAC. - for (auto& CurrentOutput : HAC->Outputs) - { - if (!CurrentOutput) - continue; - - // Only update the editable nodes that have been built before. - if (!CurrentOutput->IsEditableNode() || !CurrentOutput->HasEditableNodeBuilt()) - continue; - - for (auto& CurrentOutputObj : CurrentOutput->GetOutputObjects()) - { - UHoudiniSplineComponent* HoudiniSplineComponent = Cast(CurrentOutputObj.Value.OutputComponent); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (!HoudiniSplineComponent->HasChanged()) - continue; - - // Dont add rot/scale on editable curves as this crashes HAPI - if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( - HoudiniSplineComponent, false)) - HoudiniSplineComponent->MarkChanged(false); - else - HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); - } - } - - return true; -} - - -bool -FHoudiniOutputTranslator::BuildAllOutputs( - const HAPI_NodeId& AssetId, - UObject* InOuterObject, - TArray& InOldOutputs, - TArray& OutNewOutputs, - const bool& InOutputTemplatedGeos) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Get the AssetInfo - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - FString CurrentAssetName; - { - FHoudiniEngineString hapiSTR(AssetInfo.nameSH); - hapiSTR.ToFString(CurrentAssetName); - } - - // Retrieve the asset's transform. - // TODO: Unused?! - //FTransform AssetUnrealTransform; - //if (!FHoudiniEngineUtils::HapiGetAssetTransform(AssetId, AssetUnrealTransform)) - // return false; - - // Retrieve information about each object contained within our asset. - TArray ObjectInfos; - TArray ObjectTransforms; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) - return false; - - // Mark all the previous HGPOs on the outputs as stale - // This indicates that they were from a previous cook and should then be deleted - for (auto& CurOutput : InOldOutputs) - { - if (CurOutput) - CurOutput->MarkAllHGPOsAsStale(true); - } - - // For HF / Volumes, we only create new Outputs for height volume - // Store all the other volumes (masks etc) on the side and we will - // match them with theit corresponding height volume after - TArray UnassignedVolumeParts; - - TArray AllSockets; - - // Iterate through all objects. - int32 OutputIdx = 1; - for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) - { - // Retrieve the object info - const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; - - // Cache/convert them - FHoudiniObjectInfo CurrentObjectInfo; - CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); - - // Retrieve object name. - FString CurrentObjectName = CurrentObjectInfo.Name; - - // Get transformation for this object. - const HAPI_Transform & ObjectTransform = ObjectTransforms[ObjectId]; - FTransform TransformMatrix; - FHoudiniEngineUtils::TranslateHapiTransform(ObjectTransform, TransformMatrix); - - // TODO: Check transforms?? - - // Build an array of the geos we'll need to process - // In most case, it will only be the display geo, - // but we may also want to process editable geos as well - TArray GeoInfos; - - // Get the Display Geo's info - HAPI_GeoInfo DisplayHapiGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName); - } - else - { - // Add the display geo info to the array - GeoInfos.Add(DisplayHapiGeoInfo); - } - - // Handle the editable nodes for this geo - // Start by getting the number of editable nodes - int32 EditableNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, - true, &EditableNodeCount)); - - if (EditableNodeCount > 0) - { - TArray< HAPI_NodeId > EditableNodeIds; - EditableNodeIds.SetNumUninitialized(EditableNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - - for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) - { - HAPI_GeoInfo CurrentEditableGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentEditableGeoInfo.isDisplayGeo) - continue; - - // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) - continue; - - // Add this geo to the geo info array - GeoInfos.Add(CurrentEditableGeoInfo); - } - } - - // Handle the templated nodes if desired - if (InOutputTemplatedGeos) - { - // Start by getting the number of templated nodes - int32 TemplatedNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, - HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, - true, &TemplatedNodeCount)); - - if (TemplatedNodeCount > 0) - { - TArray TemplatedNodeIds; - TemplatedNodeIds.SetNumUninitialized(TemplatedNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, TemplatedNodeIds.GetData(), TemplatedNodeCount)); - - for (int32 nTemplated = 0; nTemplated < TemplatedNodeCount; nTemplated++) - { - HAPI_GeoInfo CurrentTemplatedGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentTemplatedGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - TemplatedNodeIds[nTemplated], &CurrentTemplatedGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentTemplatedGeoInfo.isDisplayGeo) - continue; - - // We don't want all the nested template node IDs, - // as our HDA could potentially be using other HDAs with nested template flags - // Make sure the parent of the templated node is either the HDA, the current OBJ or the Display SOP - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurrentTemplatedGeoInfo.nodeId); - if (ParentId != CurrentHapiObjectInfo.nodeId - && ParentId != DisplayHapiGeoInfo.nodeId - && ParentId != AssetId) - { - continue; - } - - // Add this geo to the geo info array - GeoInfos.Add(CurrentTemplatedGeoInfo); - } - } - } - - // Iterates through the geos we want to process - for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) - { - // Cook editable/templated nodes to get their parts. - const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; - if ((CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) - || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0)) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, - &GeoInfos[GeoIdx])); - } - - // Cache/convert the display geo's info - FHoudiniGeoInfo CurrentGeoInfo; - CacheGeoInfo(CurrentHapiGeoInfo, CurrentGeoInfo); - - // Simply create an empty array for this geo's group names - // We might need it later for splitting - TArray GeoGroupNames; - bool HasSocketGroups = false; - - // Iterate on this geo's parts - for (int32 PartId = 0; PartId < CurrentGeoInfo.PartCount; ++PartId) - { - // Get part information. - HAPI_PartInfo CurrentHapiPartInfo; - FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); - - // If the geo is templated, cook it manually - if (CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - } - - bool bPartInfoFailed = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - { - bPartInfoFailed = true; - - // If the geo is templated, attempt to cook it manually - if(CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, nullptr); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, - &GeoInfos[GeoIdx])); - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - { - // We managed to get the templated part infos after cooking - bPartInfoFailed = false; - } - } - } - - if (bPartInfoFailed) - { - // Error retrieving part info. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d] unable to retrieve PartInfo - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId); - continue; - } - - // Convert/cache the part info - FHoudiniPartInfo CurrentPartInfo; - CachePartInfo(CurrentHapiPartInfo, CurrentPartInfo); - - // Retrieve part name. - FString CurrentPartName = CurrentPartInfo.Name; - - // Unsupported/Invalid part - if (CurrentPartInfo.Type == EHoudiniPartType::Invalid) - continue; - - // Update part/instancer type from the part infos - EHoudiniPartType CurrentPartType = EHoudiniPartType::Invalid; - EHoudiniInstancerType CurrentInstancerType = EHoudiniInstancerType::Invalid; - switch (CurrentHapiPartInfo.type) - { - case HAPI_PARTTYPE_BOX: - case HAPI_PARTTYPE_SPHERE: - case HAPI_PARTTYPE_MESH: - { - if (CurrentHapiGeoInfo.type == HAPI_GEOTYPE_CURVE) - { - // Closed curve will be seen as mesh - CurrentPartType = EHoudiniPartType::Curve; - } - else - { - CurrentPartType = EHoudiniPartType::Mesh; - - if (CurrentHapiObjectInfo.isInstancer) - { - if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // That part is actually an attribute instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - } - else - { - // That part is actually an instancer - CurrentPartType = EHoudiniPartType::Instancer; - CurrentInstancerType = EHoudiniInstancerType::ObjectInstancer; - } - - } - else if (CurrentHapiPartInfo.vertexCount <= 0 && CurrentHapiPartInfo.pointCount <= 0) - { - // No points, no vertices, we're likely invalid - CurrentPartType = EHoudiniPartType::Invalid; - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a mesh with no points or vertices - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - } - else if (CurrentHapiPartInfo.vertexCount <= 0) - { - // This is not an instancer, we do not have vertices, but we have points - // Maybe this is a point cloud with attribute override instancing - if(FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // Mark it as an instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - } - else - { - // No vertices, not an instancer, just a point cloud, consider ourself as invalid - CurrentPartType = EHoudiniPartType::Invalid; - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a point cloud mesh - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - } - } - } - } - break; - - case HAPI_PARTTYPE_CURVE: - { - // Make sure that this curve is not an an attribute instancer! - if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // Mark the part as an instancer it as an instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - } - else - { - // The curve is a curve! - CurrentPartType = EHoudiniPartType::Curve; - } - } - break; - - case HAPI_PARTTYPE_INSTANCER: - // This is a packed primitive instancer - CurrentPartType = EHoudiniPartType::Instancer; - CurrentInstancerType = EHoudiniInstancerType::PackedPrimitive; - break; - - case HAPI_PARTTYPE_VOLUME: - // Volume data, likely a Heightfield height / mask - CurrentPartType = EHoudiniPartType::Volume; - break; - - default: - // Unsupported Part Type - break; - } - - // There are no vertices AND no points and this part is not a packed prim instancer - if ((CurrentPartInfo.VertexCount <= 0 && CurrentPartInfo.PointCount <= 0) - && (CurrentPartType != EHoudiniPartType::Instancer || CurrentInstancerType != EHoudiniInstancerType::PackedPrimitive)) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] no points or vertices found - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - continue; - } - - // This is an instancer with no points. - if (CurrentHapiObjectInfo.isInstancer && CurrentHapiPartInfo.pointCount <= 0) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is instancer but has 0 points - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - continue; - } - - // Ignore invalid parts - if (CurrentPartType == EHoudiniPartType::Invalid) - continue; - - // Build the HGPO corresponding to this part - FHoudiniGeoPartObject currentHGPO; - currentHGPO.AssetId = AssetId; - currentHGPO.AssetName = CurrentAssetName; - - currentHGPO.ObjectId = CurrentHapiObjectInfo.nodeId; - currentHGPO.ObjectName = CurrentObjectName; - - currentHGPO.GeoId = CurrentHapiGeoInfo.nodeId; - - currentHGPO.PartId = CurrentHapiPartInfo.id; - - currentHGPO.Type = CurrentPartType; - currentHGPO.InstancerType = CurrentInstancerType; - - currentHGPO.TransformMatrix = TransformMatrix; - - currentHGPO.NodePath = TEXT(""); - - currentHGPO.bIsVisible = CurrentHapiObjectInfo.isVisible && !CurrentHapiPartInfo.isInstanced; - currentHGPO.bIsEditable = CurrentHapiGeoInfo.isEditable; - currentHGPO.bIsInstanced = CurrentHapiPartInfo.isInstanced; - // Never consider a display geo as templated! - currentHGPO.bIsTemplated = CurrentHapiGeoInfo.isDisplayGeo ? false : CurrentHapiGeoInfo.isTemplated; - - currentHGPO.bHasGeoChanged = CurrentHapiGeoInfo.hasGeoChanged; - currentHGPO.bHasPartChanged = CurrentHapiPartInfo.hasChanged; - currentHGPO.bHasMaterialsChanged = CurrentHapiGeoInfo.hasMaterialChanged; - currentHGPO.bHasTransformChanged = CurrentHapiObjectInfo.hasTransformChanged; - - // Copy the HAPI info caches - currentHGPO.ObjectInfo = CurrentObjectInfo; - currentHGPO.GeoInfo = CurrentGeoInfo; - currentHGPO.PartInfo = CurrentPartInfo; - - // We only support meshes for templated geos - if (currentHGPO.bIsTemplated && (CurrentPartType != EHoudiniPartType::Mesh)) - continue; - - // Update the HGPO's node path - FHoudiniEngineUtils::HapiGetNodePath(currentHGPO, currentHGPO.NodePath); - - // Try to get the custom part name from attribute - FString CustomPartName; - if (FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CustomPartName)) - currentHGPO.SetCustomPartName(CustomPartName); - else - currentHGPO.PartName = CurrentPartName; - - // - // Mesh Only - Extract split groups - // - // Extract the group names used by this part to see if it will require splitting - // Only meshes can be split, via their primitive groups - TArray< FString > SplitGroupNames; - if (CurrentPartType == EHoudiniPartType::Mesh) - { - if (!CurrentHapiPartInfo.isInstanced && GeoGroupNames.Num() > 0) - { - // We are not instanced and already have extracted the geo's group names - // We can simply reuse the Geo group names / socket groups - currentHGPO.SplitGroups = GeoGroupNames; - } - else - { - // We need to get the primitive group names from HAPI - int32 GroupCount = 0; - TArray GroupNames; - if (!FHoudiniEngineUtils::HapiGetGroupNames( - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - HAPI_GROUPTYPE_PRIM, CurrentHapiPartInfo.isInstanced, - GroupNames)) - { - GroupCount = 0; - GroupNames.Empty(); - } - - // Convert the string handles to FStrings - for (const FString& GroupName : GroupNames) - { - FString LodGroup = HAPI_UNREAL_GROUP_LOD_PREFIX; - FString CollisionGroup = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; - FString RenderedCollisionGroup = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; - if (GroupName.StartsWith(LodGroup, ESearchCase::IgnoreCase) - || GroupName.StartsWith(CollisionGroup, ESearchCase::IgnoreCase) - || GroupName.StartsWith(RenderedCollisionGroup, ESearchCase::IgnoreCase)) - //|| GroupName.StartsWith(HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX, ESearchCase::IgnoreCase)) - { - // Split by collisions / lods - currentHGPO.SplitGroups.Add(GroupName); - } - } - - // Sort the Group name array by name, - // this will order the LODs and other incremental group names - currentHGPO.SplitGroups.Sort(); - - // If this part is not instanced, we can copy the geo - // group names so we can reuse them for another part - if (!CurrentHapiPartInfo.isInstanced) - { - GeoGroupNames = currentHGPO.SplitGroups; - } - } - } - - // - // Volume Only - Extract volume name/tile index - // - // Extract the volume's name, and see if a tile attribute is present - FHoudiniVolumeInfo CurrentVolumeInfo; - if (CurrentPartType == EHoudiniPartType::Volume) - { - // Get this volume's info - HAPI_VolumeInfo CurrentHapiVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentHapiVolumeInfo); - - bool bVolumeValid = true; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - &CurrentHapiVolumeInfo)) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.tupleSize != 1) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.zLength != 1) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - { - bVolumeValid = false; - } - - // Only cache valid volumes - if (bVolumeValid) - { - // Convert/Cache the volume info - CacheVolumeInfo(CurrentHapiVolumeInfo, CurrentVolumeInfo); - - // Get the volume's name - currentHGPO.VolumeName = CurrentVolumeInfo.Name; - - // Now see if this volume has a tile attribute - TArray TileValues; - if (FHoudiniEngineUtils::GetTileAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, TileValues, HAPI_ATTROWNER_PRIM)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - currentHGPO.VolumeTileIndex = TileValues[0]; - else - currentHGPO.VolumeTileIndex = -1; - } - } - } - currentHGPO.VolumeInfo = CurrentVolumeInfo; - - // Cache the curve info as well - // !!! Only call GetCurveInfo if the PartType is Curve - // !!! Closed curves are actually Meshes, and calling GetCurveInfo on a Mesh will crash HAPI! - FHoudiniCurveInfo CurrentCurveInfo; - if (CurrentPartType == EHoudiniPartType::Curve && CurrentPartInfo.Type == EHoudiniPartType::Curve) - { - HAPI_CurveInfo CurrentHapiCurveInfo; - FHoudiniApi::CurveInfo_Init(&CurrentHapiCurveInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - &CurrentHapiCurveInfo)) - { - // Cache/Convert this part's curve info - CacheCurveInfo(CurrentHapiCurveInfo, CurrentCurveInfo); - } - } - currentHGPO.CurveInfo = CurrentCurveInfo; - - - // TODO: - // DONE? bake folders are handled out of this loop? - // See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute - //TArray BakeFolderOverrides; - - // Extract socket points - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - currentHGPO.GeoId, currentHGPO.PartId, AllSockets, CurrentHapiPartInfo.isInstanced); - - // See if we have an existing output that matches this HGPO or if we need to create a new one - bool IsFoundOutputValid = false; - UHoudiniOutput ** FoundHoudiniOutput = nullptr; - // We handle volumes differently than other outputs types, as a single HF output has multiple HGPOs - if (currentHGPO.Type != EHoudiniPartType::Volume) - { - // Look in the previous output if we have a match - FoundHoudiniOutput = InOldOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(currentHGPO) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - - } - else - { - // Look in the previous outputs if we have a match - FoundHoudiniOutput = InOldOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, true) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - - // If we dont have a match in the old maps, also look in the newly created outputs - if (!IsFoundOutputValid) - { - FoundHoudiniOutput = OutNewOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, false) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - } - } - - UHoudiniOutput * HoudiniOutput = nullptr; - if (IsFoundOutputValid) - { - // We can reuse the existing output - HoudiniOutput = *FoundHoudiniOutput; - HoudiniOutput->SetIsUpdating(true); - // Transfer this output from the old array to the new one - InOldOutputs.Remove(HoudiniOutput); - } - else - { - // We couldn't find a valid output object, so create a new one - - // If the current part is a volume, only create a new output object - // if the volume's name is "height", if not store the HGPO aside - if (currentHGPO.Type == EHoudiniPartType::Volume - && !currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) - { - UnassignedVolumeParts.Add(currentHGPO); - continue; - } - - // Create a new output object - //FString OutputName = TEXT("Output") + FString::FromInt(OutputIdx++); - HoudiniOutput = NewObject( - InOuterObject, - UHoudiniOutput::StaticClass(), - NAME_None,//FName(*OutputName), - RF_NoFlags); - - // Make sure the created object is valid - if (!HoudiniOutput || HoudiniOutput->IsPendingKill()) - { - //HOUDINI_LOG_WARNING("Failed to create asset output"); - continue; - } - - // Mark if the HoudiniOutput is editable - HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); - } - - // Add the HGPO to the output - HoudiniOutput->AddNewHGPO(currentHGPO); - // Add this output object to the new ouput array - OutNewOutputs.AddUnique(HoudiniOutput); - } - } - } - - // Update the output/HGPO associations from the map - // Clear the old HGPO since we don't need them anymore - for (auto& CurrentOuput : OutNewOutputs) - { - if (!CurrentOuput || CurrentOuput->IsPendingKill()) - continue; - - CurrentOuput->DeleteAllStaleHGPOs(); - } - - // If we have unassigned volumes, - // try to find their corresponding output - if (UnassignedVolumeParts.Num() > 0) - { - for (auto& currentVolumeHGPO : UnassignedVolumeParts) - { - UHoudiniOutput ** FoundHoudiniOutput = OutNewOutputs.FindByPredicate( - [currentVolumeHGPO](UHoudiniOutput* Output) - { - return Output ? Output->HeightfieldMatch(currentVolumeHGPO, false) : false; - }); - - if (!FoundHoudiniOutput || !(*FoundHoudiniOutput) || (*FoundHoudiniOutput)->IsPendingKill()) - { - // Skip - consider this volume as invalid - continue; - } - - // Add this HGPO to the output - (*FoundHoudiniOutput)->AddNewHGPO(currentVolumeHGPO); - } - } - - // All our output objects now have their HGPO assigned - // We can now parse them to update the output type - for (auto& Output : OutNewOutputs) - { - Output->UpdateOutputType(); - } - - return true; -} - -bool -FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray& Outputs = HAC->Outputs; - - // Iterate through the outputs array of HAC. - for (int32 Index = 0; Index < HAC->GetNumOutputs(); ++Index) - { - UHoudiniOutput* CurrentOutput = HAC->GetOutputAt(Index); - if (!CurrentOutput) - continue; - - if (!HAC->IsOutputTypeSupported(CurrentOutput->GetType())) - continue; - - switch (CurrentOutput->GetType()) - { - case EHoudiniOutputType::Instancer: - { - bool bNeedToRecreateInstancers = false; - for (auto& Iter : CurrentOutput->GetInstancedOutputs()) - { - FHoudiniInstancedOutput& InstOutput = Iter.Value; - if (!InstOutput.bChanged) - continue; - - /* - FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( - InstOutput, Iter.Key, CurrentOutput, HAC); - */ - - // TODO: - // UpdateChangedInstancedOutput needs some improvements - // as it currently destroy too many components. - // For now, we'll update all the instancers - bNeedToRecreateInstancers = true; - - InstOutput.MarkChanged(false); - } - - if (bNeedToRecreateInstancers) - { - if (HAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation || HAC->HasBeenLoaded()) - { - // Instantiate the HDA if it's not been - // This is because CreateAllInstancersFromHoudiniOutput() actually reads the transform from HAPI - // Calling it on a HDA not yet instantiated causes a crash... - HAC->AssetState = EHoudiniAssetState::PreInstantiation; - } - else - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurrentOutput, Outputs, HAC); - } - } - } - break; - - case EHoudiniOutputType::Curve: - { - //FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); - } - break; - - default: - break; - } - } - - return true; -} - -void -FHoudiniOutputTranslator::CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache) -{ - FHoudiniEngineString hapiSTR(InObjInfo.nameSH); - hapiSTR.ToFString(OutObjInfoCache.Name); - //OutObjInfoCache.Name = InObjInfo.nameSH; - - OutObjInfoCache.NodeId = InObjInfo.nodeId; - OutObjInfoCache.ObjectToInstanceID = InObjInfo.objectToInstanceId; - - OutObjInfoCache.bHasTransformChanged = InObjInfo.hasTransformChanged; - OutObjInfoCache.bHaveGeosChanged = InObjInfo.haveGeosChanged; - - OutObjInfoCache.bIsVisible = InObjInfo.isVisible; - OutObjInfoCache.bIsInstancer = InObjInfo.isInstancer; - OutObjInfoCache.bIsInstanced = InObjInfo.isInstanced; - - OutObjInfoCache.GeoCount = InObjInfo.geoCount; -}; - -EHoudiniGeoType -FHoudiniOutputTranslator::ConvertHapiGeoType(const HAPI_GeoType& InType) -{ - EHoudiniGeoType OutType = EHoudiniGeoType::Invalid; - switch (InType) - { - case HAPI_GEOTYPE_DEFAULT: - OutType = EHoudiniGeoType::Default; - break; - - case HAPI_GEOTYPE_INTERMEDIATE: - OutType = EHoudiniGeoType::Intermediate; - break; - - case HAPI_GEOTYPE_INPUT: - OutType = EHoudiniGeoType::Input; - break; - - case HAPI_GEOTYPE_CURVE: - OutType = EHoudiniGeoType::Curve; - break; - - default: - OutType = EHoudiniGeoType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache) -{ - OutGeoInfoCache.Type = ConvertHapiGeoType(InGeoInfo.type); - - FHoudiniEngineString hapiSTR(InGeoInfo.nameSH); - hapiSTR.ToFString(OutGeoInfoCache.Name); - - OutGeoInfoCache.NodeId = InGeoInfo.nodeId; - - OutGeoInfoCache.bIsEditable = InGeoInfo.isEditable; - OutGeoInfoCache.bIsTemplated = InGeoInfo.isTemplated; - OutGeoInfoCache.bIsDisplayGeo = InGeoInfo.isDisplayGeo; - OutGeoInfoCache.bHasGeoChanged = InGeoInfo.hasGeoChanged; - OutGeoInfoCache.bHasMaterialChanged = InGeoInfo.hasMaterialChanged; - - OutGeoInfoCache.PartCount = InGeoInfo.partCount; - OutGeoInfoCache.PointGroupCount = InGeoInfo.pointGroupCount; - OutGeoInfoCache.PrimitiveGroupCount = InGeoInfo.primitiveGroupCount; -}; - - -EHoudiniPartType -FHoudiniOutputTranslator::ConvertHapiPartType(const HAPI_PartType& InType) -{ - EHoudiniPartType OutType = EHoudiniPartType::Invalid; - switch (InType) - { - case HAPI_PARTTYPE_BOX: - case HAPI_PARTTYPE_SPHERE: - case HAPI_PARTTYPE_MESH: - OutType = EHoudiniPartType::Mesh; - break; - - case HAPI_PARTTYPE_CURVE: - OutType = EHoudiniPartType::Curve; - break; - - case HAPI_PARTTYPE_INSTANCER: - OutType = EHoudiniPartType::Instancer; - break; - - case HAPI_PARTTYPE_VOLUME: - OutType = EHoudiniPartType::Volume; - break; - - default: - OutType = EHoudiniPartType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache) -{ - OutPartInfoCache.PartId = InPartInfo.id; - - FHoudiniEngineString hapiSTR(InPartInfo.nameSH); - hapiSTR.ToFString(OutPartInfoCache.Name); - - OutPartInfoCache.Type = ConvertHapiPartType(InPartInfo.type); - - OutPartInfoCache.FaceCount = InPartInfo.faceCount; - OutPartInfoCache.VertexCount = InPartInfo.vertexCount; - OutPartInfoCache.PointCount = InPartInfo.pointCount; - - OutPartInfoCache.PointAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_POINT]; - OutPartInfoCache.VertexAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX]; - OutPartInfoCache.PrimitiveAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_PRIM]; - OutPartInfoCache.DetailAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL]; - - OutPartInfoCache.bIsInstanced = InPartInfo.isInstanced; - - OutPartInfoCache.InstancedPartCount = InPartInfo.instancedPartCount; - OutPartInfoCache.InstanceCount = InPartInfo.instanceCount; - - OutPartInfoCache.bHasChanged = InPartInfo.hasChanged; -}; - -void -FHoudiniOutputTranslator::CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache) -{ - FHoudiniEngineString hapiSTR(InVolumeInfo.nameSH); - hapiSTR.ToFString(OutVolumeInfoCache.Name); - - OutVolumeInfoCache.bIsVDB = (InVolumeInfo.type == HAPI_VOLUMETYPE_VDB); // replaces VolumeType Type; - - OutVolumeInfoCache.TupleSize = InVolumeInfo.tupleSize; - OutVolumeInfoCache.bIsFloat = (InVolumeInfo.storage == HAPI_STORAGETYPE_FLOAT); // replaces StorageType StorageType; - OutVolumeInfoCache.TileSize = InVolumeInfo.tileSize; - - FHoudiniEngineUtils::TranslateHapiTransform(InVolumeInfo.transform, OutVolumeInfoCache.Transform); - OutVolumeInfoCache.bHasTaper = InVolumeInfo.hasTaper; - - OutVolumeInfoCache.XLength = InVolumeInfo.xLength; - OutVolumeInfoCache.YLength = InVolumeInfo.yLength; - OutVolumeInfoCache.ZLength = InVolumeInfo.zLength; - - OutVolumeInfoCache.MinX = InVolumeInfo.minX; - OutVolumeInfoCache.MinY = InVolumeInfo.minY; - OutVolumeInfoCache.MinZ = InVolumeInfo.minZ; - - OutVolumeInfoCache.XTaper = InVolumeInfo.xTaper; - OutVolumeInfoCache.YTaper = InVolumeInfo.yTaper; -}; - -EHoudiniCurveType -FHoudiniOutputTranslator::ConvertHapiCurveType(const HAPI_CurveType& InType) -{ - EHoudiniCurveType OutType = EHoudiniCurveType::Invalid; - switch (InType) - { - case HAPI_CURVETYPE_LINEAR: - OutType = EHoudiniCurveType::Polygon; - break; - - case HAPI_CURVETYPE_NURBS: - OutType = EHoudiniCurveType::Nurbs; - break; - - case HAPI_CURVETYPE_BEZIER: - OutType = EHoudiniCurveType::Bezier; - break; - - case HAPI_CURVETYPE_MAX: - OutType = EHoudiniCurveType::Points; - break; - - default: - OutType = EHoudiniCurveType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache) -{ - OutCurveInfoCache.Type = ConvertHapiCurveType(InCurveInfo.curveType); - - OutCurveInfoCache.CurveCount = InCurveInfo.curveCount; - OutCurveInfoCache.VertexCount = InCurveInfo.vertexCount; - OutCurveInfoCache.KnotCount = InCurveInfo.knotCount; - - OutCurveInfoCache.bIsPeriodic = InCurveInfo.isPeriodic; - OutCurveInfoCache.bIsRational = InCurveInfo.isRational; - - OutCurveInfoCache.Order = InCurveInfo.order; - - OutCurveInfoCache.bHasKnots = InCurveInfo.hasKnots; -}; - - -void -FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll) -{ - if (!IsValid(InHAC)) - return; - - // DO NOT MANUALLY DESTROY THE OLD/DANGLING OUTPUTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - // Simply clearing the array is enough - for (auto& OldOutput : InHAC->Outputs) - { - if (OldOutput->ShouldDeferClear() && !bForceClearAll) - { - OutputsPendingClear.Add(OldOutput); - } - else - { - ClearOutput(OldOutput); - } - } - - InHAC->Outputs.Empty(); -} - -void -FHoudiniOutputTranslator::ClearOutput(UHoudiniOutput* Output) -{ - switch (Output->GetType()) - { - case EHoudiniOutputType::Landscape: - { - for (auto& OutputObject : Output->GetOutputObjects()) - { - // Currently, any Landscape managed by an HDA is always present in the current level. - // Only when it gets baked will Landscapes be serialized to other levels so for now - // assume that a landscape should be available, unless explicitly deleted the user. - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.Value.OutputObject); - if (!LandscapePtr) - continue; - - ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); - - if (!IsValid(Landscape) || !Landscape->IsValidLowLevel()) - continue; - - Landscape->UnregisterAllComponents(); - Landscape->Destroy(); - - // if (Output->IsLandscapeWorldComposition()) - // { - // TSoftObjectPtr LandscapeSoftPtr = LandscapePtr->GetSoftPtr(); - // - // FString SoftPtrPath = LandscapeSoftPtr.ToSoftObjectPath().ToString(); - // - // FString FileName = FPaths::GetBaseFilename(SoftPtrPath); - // FString FileDirectory = FPaths::GetPath(SoftPtrPath); - // - // FString ContentPath = FPaths::ProjectContentDir(); - // FString ContentFullPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*ContentPath); - // - // FString AbsoluteFilePath = ContentFullPath + FileDirectory.Mid(5, FileDirectory.Len() - 5) + "/" + FPaths::GetBaseFilename(FileName) + ".umap"; - // - // FPlatformFileManager::Get().GetPlatformFile().FileExists(*(AbsoluteFilePath)); - // - // FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*(AbsoluteFilePath)); - // } - // else - // { - - // } - } - } - break; - - case EHoudiniOutputType::Instancer: - { - for (auto& OutputObject : Output->GetOutputObjects()) - { - // Is this a foliage instancer? Check if the component is owned by an AInstancedFoliageActor - UActorComponent* const Component = Cast(OutputObject.Value.OutputComponent); - if (!IsValid(Component)) - continue; - AActor* const OwnerActor = Component->GetOwner(); - if (!IsValid(OwnerActor) || !OwnerActor->IsA()) - continue; - - UHierarchicalInstancedStaticMeshComponent* const FoliageHISMC = Cast(Component); - if (IsValid(FoliageHISMC)) - { - // Find the parent component: the output is typically owned by an HAC. - USceneComponent* ParentComponent = nullptr; - UObject* const OutputOuter = Output->GetOuter(); - if (IsValid(OutputOuter)) - { - if (OutputOuter->IsA()) - { - ParentComponent = Cast(OutputOuter); - } - // other possibilities? - } - - // fallback to trying the owner of the HISMC - if (!ParentComponent) - { - ParentComponent = Cast(FoliageHISMC); - } - - if (IsValid(ParentComponent)) - { - FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, OutputObject.Value.OutputObject, ParentComponent); - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - } - } - } - } - break; - // ... Other output types ...// - - default: - break; - - } - - Output->Clear(); -} - - -bool -FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName) -{ - HAPI_AttributeInfo CustomPartNameInfo; - FHoudiniApi::AttributeInfo_Init(&CustomPartNameInfo); - - bool bHasCustomName = false; - TArray CustomNames; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, CustomPartNameInfo, CustomNames)) - { - // Look for the v2 attribute (unreal_output_name) - bHasCustomName = true; - } - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, CustomPartNameInfo, CustomNames)) - { - // If we couldnt find the new attribute, use the legacy v1 attribute (unreal_generated_mesh_name) - bHasCustomName = true; - } - - if (!bHasCustomName) - return false; - - if (CustomNames.Num() <= 0) - return false; - - OutCustomPartName = CustomNames[0]; - - if (OutCustomPartName.IsEmpty()) - return false; - - return true; -} - -void -FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) - return; - - FString TempFolderOverride = FString(); - - HAPI_AttributeInfo TempFolderAttriInfo; - FHoudiniApi::AttributeInfo_Init(&TempFolderAttriInfo); - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_DETAIL)) - { - TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - else - { - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) - { - TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - } - - if (TempFolderOverride.StartsWith("Game/")) - { - TempFolderOverride = "/" + TempFolderOverride; - } - - FString AbsoluteOverridePath; - if (TempFolderOverride.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + TempFolderOverride.Mid(6, TempFolderOverride.Len() - 6); - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - if (!TempFolderOverride.IsEmpty()) - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*TempFolderOverride); - } - - // Check Validity of the path - if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) - { - // Only display a warning if the path is invalid, empty is fine - if(!AbsoluteOverridePath.IsEmpty()) - HOUDINI_LOG_WARNING(TEXT("Invalid override temporary cook path: %s"), *TempFolderOverride); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - TempFolderOverride = HoudiniRuntimeSettings->DefaultTemporaryCookFolder; - } - - // If the TempCookFolder of the HAC is non-empty and is different from the override path. - // do not override it if the current temp cook path is valid. (it was user specified) - if (!HAC->TemporaryCookFolder.Path.IsEmpty() && !HAC->TemporaryCookFolder.Path.Equals(TempFolderOverride)) - return; - - HAC->TemporaryCookFolder.Path = TempFolderOverride; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniOutputTranslator.h" + +#include "HoudiniOutput.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" + +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniInput.h" +#include "HoudiniStaticMesh.h" + +#include "HoudiniMeshTranslator.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" + +#include "Editor.h" +#include "EditorSupportDelegates.h" +#include "FileHelpers.h" +#include "LandscapeInfo.h" +#include "HAL/PlatformFilemanager.h" +#include "HAL/FileManager.h" +#include "Engine/WorldComposition.h" +#include "Modules/ModuleManager.h" +#include "WorldBrowserModule.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// +bool +FHoudiniOutputTranslator::UpdateOutputs( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate, + bool& bOutHasHoudiniStaticMeshOutput) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the temp folder override + FHoudiniOutputTranslator::GetTempFolderFromAttribute(HAC); + + // Outputs that should be cleared, but only AFTER new output processing have taken place. + // This is needed for landscape resizing where the new landscape needs to copy data from the original landscape + // before the original landscape gets destroyed. + TArray DeferredClearOutputs; + + // Check if the HDA has been marked as not producing outputs + if (!HAC->bOutputless) + { + // Check if we want to convert legacy v1 data + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility && HAC->Version1CompatibilityHAC) + { + // Do not reuse legacy outputs! + for (auto& OldOutput : HAC->Outputs) + { + ClearOutput(OldOutput); + } + } + + TArray NewOutputs; + if (FHoudiniOutputTranslator::BuildAllOutputs(HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos)) + { + // NOTE: For now we are currently forcing all outputs to be cleared here. There is still an issue where, in some + // circumstances, landscape tiles disappear when clearing outputs after processing. + // The reason we may need to defer landscape clearing is to allow the landscape creation code to + // capture the extent of the landscape. The extent of the landscape can only be calculated if all landscape + // tiles are still present in the map. If we find that we don't need this for updating of Input landscapes, + // we can safely remove this feature. + ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); + // Replace with the new parameters + HAC->Outputs = NewOutputs; + } + } + else + { + // This HDA is marked as not supposed to produce any output + ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); + } + + // Look for details generic property attributes on the outputs, + // and try to apply them to the HAC. + // This can be used to preset some of the HDA's uproperty via attribute + TArray GenericAttributes; + for (auto& CurrentOutput : HAC->Outputs) + { + const TArray& CurrentOutputHGPO = CurrentOutput->GetHoudiniGeoPartObjects(); + for (auto& CurrentHGPO : CurrentOutputHGPO) + { + FHoudiniEngineUtils::GetGenericAttributeList( + CurrentHGPO.GeoId, + CurrentHGPO.PartId, + HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, + GenericAttributes, + HAPI_ATTROWNER_DETAIL); + } + } + + // Attempt to apply the attributes to the HAC if we have any + for (const auto& CurrentPropAttribute : GenericAttributes) + { + // Get the current Property Attribute + const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; + if (CurrentPropertyName.IsEmpty()) + continue; + + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(HAC, CurrentPropAttribute)) + continue; + + // Success! + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on Houdini Asset Component named %s"), *CurrentPropertyName, *HAC->GetName()); + } + + // NOTE: PersistentWorld can be NULL when, for example, working with + // HoudiniAssetComponents in Blueprints. + UWorld* PersistentWorld = HAC->GetWorld(); + UWorldComposition* WorldComposition = nullptr; + if (PersistentWorld) + { + WorldComposition = PersistentWorld->WorldComposition; + } + + if (IsValid(WorldComposition)) + { + // We don't want the origin to shift as we're potentially updating levels. + WorldComposition->bTemporarilyDisableOriginTracking = true; + } + + // "Process" the mesh. + // TODO: Move this to the actual processing stage, + // And see if some of this could be threaded + UObject* OuterComponent = HAC; + + FString HoudiniAssetPath = FPaths::GetPath(HAC->GetPathName()); + FString ComponentGUIDString = HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); + FString HoudiniAssetNameString = HAC->GetDisplayName(); + + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + PackageParams.ObjectName = FString(); + + // ---------------------------------------------------- + // Outputs prepass + // ---------------------------------------------------- + + TArray CreatedWorldCompositionPackages; + bool bCreatedNewMaps = false; + //... for heightfield outputs ...// + + // Collect all the landscape layers' global min/max values. + TMap LandscapeLayerGlobalMinimums; + TMap LandscapeLayerGlobalMaximums; + + // Store the instancer outputs separately so we can process them later, after all mesh output are processed. + // Determine the total number of instances, if we have more than 1 then mesh parts with instanced geo we will not create proxy meshes + // Also if we have object instancer (or oldschool attribute instancers), we won't be creating any proxy at all + TArray InstancerOutputs; + int32 NumInstances = 0; + bool bHasObjectInstancer = false; + + for (auto& CurOutput : HAC->Outputs) + { + if (CurOutput->GetType() == EHoudiniOutputType::Instancer) + { + // InstancerOutputs.Add(CurOutput); + for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type == EHoudiniPartType::Instancer) + { + if (HGPO.InstancerType == EHoudiniInstancerType::PackedPrimitive) + { + NumInstances += HGPO.PartInfo.InstanceCount; + } + else + { + NumInstances += HGPO.PartInfo.PointCount; + } + + if ((HGPO.InstancerType == EHoudiniInstancerType::ObjectInstancer) + || (HGPO.InstancerType == EHoudiniInstancerType::OldSchoolAttributeInstancer)) + { + bHasObjectInstancer = true; + } + } + } + } + else if (CurOutput->GetType() == EHoudiniOutputType::Landscape) + { + FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, false); + } + } + + bOutHasHoudiniStaticMeshOutput = false; + int32 NumVisibleOutputs = 0; + int32 NumOutputs = HAC->Outputs.Num(); + bool bHasLandscape = false; + + // Before processing all the outputs, + // See if we have any landscape input that have "Update Input Landscape" enabled + // And make an array of all our input landscapes as well. + TArray AllInputLandscapes; + TArray InputLandscapesToUpdate; + + FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); + + // ---------------------------------------------------- + // Process outputs + // ---------------------------------------------------- + // Landscape creation will cache the first tile as a reference location + // in this struct to be used by during construction of subsequent tiles. + FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; + // Landscape Size info will be cached by the first tile, similar to LandscapeReferenceLocation + FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; + FHoudiniLandscapeExtent LandscapeExtent; + TSet ClearedLandscapeLayers; + + TArray CreatedPackages; + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) + { + UHoudiniOutput* CurOutput = HAC->GetOutputAt(OutputIdx); + if (!CurOutput || CurOutput->IsPendingKill()) + continue; + + FString Notification = FString::Format(TEXT("Processing output {0} / {1}..."), {FString::FromInt(OutputIdx + 1), FString::FromInt(NumOutputs)}); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + if (!HAC->IsOutputTypeSupported(CurOutput->GetType())) + continue; + + switch (CurOutput->GetType()) + { + case EHoudiniOutputType::Mesh: + { + bool bIsProxyStaticMeshEnabled = ( + HAC->IsProxyStaticMeshEnabled() && + !HAC->HasNoProxyMeshNextCookBeenRequested() && + !HAC->IsBakeAfterNextCookEnabled()); + if (bIsProxyStaticMeshEnabled && NumInstances > 1) + { + if (bHasObjectInstancer) + { + // Completely disable proxies if we have object instancers/old school attribute instancers + // as they rely on having a static mesh created (and the instanced mesh HGPO is not marked as instanced...) + bIsProxyStaticMeshEnabled = false; + } + else + { + // If we dont have proxy instancer, enable proxy only for non-instanced mesh + for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.bIsInstanced && HGPO.Type == EHoudiniPartType::Mesh) + { + bIsProxyStaticMeshEnabled = false; + break; + } + } + } + } + + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + PackageParams, + bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, + HAC->StaticMeshGenerationProperties, + HAC->StaticMeshBuildSettings, + OuterComponent); + + NumVisibleOutputs++; + + // Look for UHoudiniStaticMesh in the output, and set bOutHasHoudiniStaticMeshOutput accordingly + if (bIsProxyStaticMeshEnabled && !bOutHasHoudiniStaticMeshOutput) + { + bOutHasHoudiniStaticMeshOutput &= CurOutput->HasAnyCurrentProxy(); + } + + break; + } + + case EHoudiniOutputType::Curve: + { + const TArray &GeoPartObjects = CurOutput->GetHoudiniGeoPartObjects(); + + if (GeoPartObjects.Num() <= 0) + continue; + + const FHoudiniGeoPartObject & CurHGPO = GeoPartObjects[0]; + + if (CurOutput->IsEditableNode()) + { + if (!CurOutput->HasEditableNodeBuilt()) + { + // Editable curve, only need to be built once. + UHoudiniSplineComponent* HoudiniSplineComponent = FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode( + CurHGPO.GeoId, + CurHGPO.PartName, + HAC); + + HoudiniSplineComponent->SetIsEditableOutputCurve(true); + + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; + EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; + EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; + EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; + + TMap& OutputObjects = CurOutput->GetOutputObjects(); + FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + FoundOutputObject.OutputComponent = HoudiniSplineComponent; + + CurOutput->SetHasEditableNodeBuilt(true); + } + } + else + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); + NumVisibleOutputs += CurOutput->GetOutputObjects().Num(); + break; + } + } + break; + + case EHoudiniOutputType::Instancer: + InstancerOutputs.Add(CurOutput); + break; + + case EHoudiniOutputType::Landscape: + { + NumVisibleOutputs++; + + // This gets called for each heightfield primitive from Houdini, i.e., each "tile". + bool bNewMapCreated = false; + // Registering of untracked actors is not currently used in the HDA + // workflow. HDA cleanup will manually search for shared landscapes + // and remove them. That aforementioned behaviour should really be updated to + // make use of untracked actors on the HAC (similar to PDG Asset Link). + TArray> UntrackedActors; + + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + UntrackedActors, + InputLandscapesToUpdate, + AllInputLandscapes, + HAC, + TEXT("{hda_actor_name}_"), + PersistentWorld, + LandscapeLayerGlobalMinimums, + LandscapeLayerGlobalMaximums, + LandscapeExtent, + LandscapeSizeInfo, + LandscapeReferenceLocation, + PackageParams, + ClearedLandscapeLayers, + CreatedPackages); + + bHasLandscape = true; + + // Attach the created landscape to the parent HAC. + ALandscapeProxy* OutputLandscape = nullptr; + for (auto& Pair : CurOutput->GetOutputObjects()) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(Pair.Value.OutputObject); + if (IsValid(LandscapePtr)) + { + OutputLandscape = LandscapePtr->GetRawPtr(); + } + break; + } + + if (OutputLandscape) + { + // Attach the created landscapes to HAC + // Output Transforms are always relative to the HDA + HAC->SetMobility(EComponentMobility::Static); + OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); + // Note that the above attach will cause the collision components to crap out. This manifests + // itself via the Landscape editor tools not being able to trace Landscape collision components. + // By recreating collision components here, it appears to put things back into working order. + OutputLandscape->GetLandscapeInfo()->FixupProxiesTransform(); + OutputLandscape->GetLandscapeInfo()->RecreateLandscapeInfo(PersistentWorld, true); + OutputLandscape->RecreateCollisionComponents(); + FEditorDelegates::PostLandscapeLayerUpdated.Broadcast(); + } + + bCreatedNewMaps |= bNewMapCreated; + break; + } + default: + // Do Nothing for now + break; + } + } + + // Now that all meshes have been created, process the instancers + for (auto& CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); + NumVisibleOutputs++; + } + + if (NumVisibleOutputs > 0) + { + // If we have valid outputs, we don't need to display the houdini logo anymore... + FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(HAC); + } + else + { + // ... if we don't have any valid outputs however, we should + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HAC); + } + + // Clear any old outputs that was marked as "Should Defer Clear". + // This should happen before SharedLandscapeActor cleanup + // since this needs to remove old landscape proxies so that empty SharedLandscapeActors + // can be removed afterward. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniOutputTranslator::UpdateOutputs] Clearing old outputs: %d"), DeferredClearOutputs.Num()); + for(UHoudiniOutput* OldOutput : DeferredClearOutputs) + { + ClearOutput(OldOutput); + } + + // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) + // { + // LandscapeExtents.IntermediateResizeLandscape->Destroy(); + // LandscapeExtents.IntermediateResizeLandscape = nullptr; + // } + + if (bHasLandscape) + { + // ---------------------------------------------------- + // Cleanup untracked shared landscape actors + // ---------------------------------------------------- + // This is a nasty hack to clean up SharedLandscape actors generated by the + // Landscape translator but aren't tracked by an HoudiniOutputObject, since the + // translators can't dynamically create outputs. + + { + // First collect all the landscapes that is being tracked by the HAC. + TSet TrackedLandscapes; + for(UHoudiniOutput* Output : HAC->Outputs) + { + if (Output->GetType() == EHoudiniOutputType::Landscape) + { + for(auto& Elem : Output->GetOutputObjects()) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(Elem.Value.OutputObject); + if (!IsValid(LandscapePtr)) + continue; + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapeProxy)) + { + TrackedLandscapes.Add(LandscapeProxy); + + // We need to recreate component states for landscapes if a tile was created, moved, or resized + // otherwise the landscape will exhibit render artifacts (such as only rendering every other + // component.) + LandscapeProxy->RecreateComponentsState(); + } + } + } + } + + // Iterate over Houdini asset child assets in order to find dangling Landscape actors + TArray AttachedComponents = HAC->GetAttachChildren(); + for(USceneComponent* Component : AttachedComponents) + { + if (!IsValid(Component)) + continue; + AActor* Actor = Component->GetOwner(); + ALandscape* Landscape = Cast(Actor); + if (!Landscape) + continue; + if (TrackedLandscapes.Contains(Landscape)) + continue; + + if (Landscape->GetLandscapeInfo()->Proxies.Num() == 0) + Landscape->Destroy(); + } + } + + // Recreate Landscape Info calls WorldChange, so no need to do it manually. + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + +#if WITH_EDITOR + if (GEditor) + { + // We force a viewport refresh since some actions, such as updating landscape + // edit layers will not reflect until the user moves the viewport camera. + GEditor->RedrawLevelEditingViewports(true); + } +#endif + } + + // Destroy the intermediate resize landscape, if there is one. + // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) + // { + // FHoudiniLandscapeTranslator::DestroyLandscape(LandscapeExtents.IntermediateResizeLandscape); + // } + + if (IsValid(WorldComposition)) + { + // Disable the flag that we set before starting the import process. + WorldComposition->bTemporarilyDisableOriginTracking = false; + } + + // If the owner component was marked as loaded, unmark all outputs + if (HAC->HasBeenLoaded()) + { + for (auto& CurrentOutput : HAC->Outputs) + { + CurrentOutput->MarkAsLoaded(false); + } + } + + if (bCreatedNewMaps) + { + // Force the asset registry to update its cache of packages paths + // recursively for this world, otherwise world composition won't + // pick them up during the WorldComposition::Rescan(). + FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); + + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + + FHoudiniEngineUtils::LogWorldInfo(PersistentWorld); + if (WorldComposition) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); + } + + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); + } + + if (CreatedPackages.Num() > 0) + { + // Save created packages. For example, we don't want landscape layers deleted + // along with the HDA. + FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); + } + + return true; +} + +bool +FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + UObject* OuterComponent = HAC; + + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + PackageParams.ObjectName = FString(); + + bool bFoundProxies = false; + TArray InstancerOutputs; + for (auto& CurOutput : HAC->Outputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType == EHoudiniOutputType::Mesh) + { + if (CurOutput->HasAnyCurrentProxy()) + { + bFoundProxies = true; + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + PackageParams, + HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, + HAC->StaticMeshGenerationProperties, + HAC->StaticMeshBuildSettings, + OuterComponent, + true, // bInTreatExistingMaterialsAsUpToDate + bInDestroyProxies + ); + } + } + else if (OutputType == EHoudiniOutputType::Instancer) + { + InstancerOutputs.Add(CurOutput); + } + } + + // Rebuild instancers if we built any static meshes from proxies + if (bFoundProxies) + { + for (auto& CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); + } + } + + return true; +} + +// +bool +FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) +{ + HAPI_NodeId & AssetId = HAC->AssetId; + // Get the AssetInfo + 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 ObjectInfos; + TArray ObjectTransforms; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) + return false; + + TArray EditableCurveObjIds; + TArray EditableCurveGeoIds; + TArray EditableCurvePartIds; + TArray EditableCurvePartNames; + + // Iterate through all objects to get all editable curve's object geo and part Ids. + + for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; + + // Cache/convert them + FHoudiniObjectInfo CurrentObjectInfo; + CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); + + // Start by getting the number of editable nodes + int32 EditableNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + + if (EditableNodeCount > 0) + { + TArray< HAPI_NodeId > EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GeoType::HAPI_GEOTYPE_CURVE) + continue; + + // Check if the curve is closed (-1 unknown, could not find parameter on node). A closed curve will + // be returned as a mesh by HAPI instead of a curve + int32 CurveClosed = -1; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + EditableNodeIds[nEditable], HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) + { + CurveClosed = -1; + } + else + { + if (CurveClosed) + CurveClosed = 1; + else + CurveClosed = 0; + } + + // Cook the editable node to get its parts + if (CurrentEditableGeoInfo.partCount <= 0) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentEditableGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentEditableGeoInfo.nodeId, + &CurrentEditableGeoInfo)); + } + + // Iterate on this geo's parts + for (int32 PartId = 0; PartId < CurrentEditableGeoInfo.partCount; ++PartId) + { + // Get part information. + HAPI_PartInfo CurrentHapiPartInfo; + FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + continue; + + // A closed curve will be returned as a mesh in HAPI + if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE && + (CurveClosed <= 0 || CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_MESH)) + continue; + + // Get the editable curve's part name + FHoudiniEngineString hapiSTR(CurrentHapiPartInfo.nameSH); + FString PartName; + hapiSTR.ToFString(PartName); + + EditableCurveObjIds.Add(CurrentHapiObjectInfo.nodeId); + EditableCurveGeoIds.Add(CurrentEditableGeoInfo.nodeId); + EditableCurvePartIds.Add(CurrentHapiPartInfo.id); + EditableCurvePartNames.Add(PartName); + } + } + } + } + + int32 Idx = 0; + for (auto& CurrentOutput : HAC->Outputs) + { + if (CurrentOutput->IsEditableNode()) + { + // The HAC is Loaded, re-assign node id to its editable curves + if (CurrentOutput->HasEditableNodeBuilt()) + { + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + for (auto& Pair : OutputObjects) + { + if (Idx >= EditableCurvePartIds.Num()) + break; + + UHoudiniSplineComponent * HoudiniSplineComponent = Cast(Pair.Value.OutputComponent); + if (HoudiniSplineComponent && !HoudiniSplineComponent->IsPendingKill()) + { + HoudiniSplineComponent->SetNodeId(EditableCurveGeoIds[Idx]); + + Pair.Key.ObjectId = EditableCurveObjIds[Idx]; + Pair.Key.GeoId = EditableCurveGeoIds[Idx]; + Pair.Key.PartId = EditableCurvePartIds[Idx]; + Pair.Key.PartName = EditableCurvePartNames[Idx]; + + Idx += 1; + } + } + } + // The HAC is a Duplication, re-construct output objects with attached duplicated editable curves, matching by part name + else + { + const TArray &Children = HAC->GetAttachChildren(); + for (auto & CurAttachedComp : Children) + { + if (!CurAttachedComp || CurAttachedComp->IsPendingKill()) + continue; + + if (!CurAttachedComp->IsA()) + continue; + + UHoudiniSplineComponent * CurAttachedSplineComp = Cast(CurAttachedComp); + if (!CurAttachedSplineComp) + continue; + + if (!CurAttachedSplineComp->IsEditableOutputCurve()) + continue; + + if (Idx >= EditableCurvePartIds.Num()) + break; + + // Found a match + if (CurAttachedSplineComp->GetGeoPartName().Equals(EditableCurvePartNames[Idx])) + { + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = EditableCurveObjIds[Idx]; + EditableSplineComponentIdentifier.GeoId = EditableCurveGeoIds[Idx]; + EditableSplineComponentIdentifier.PartId = EditableCurvePartIds[Idx]; + EditableSplineComponentIdentifier.PartName = EditableCurvePartNames[Idx]; + + CurAttachedSplineComp->SetNodeId(EditableSplineComponentIdentifier.GeoId); + + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + FHoudiniOutputObject& NewOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + NewOutputObject.OutputComponent = CurAttachedSplineComp; + + CurrentOutput->SetHasEditableNodeBuilt(true); + + // Never add additional rot/scale attributes on editable curves as this crashes HAPI + FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + CurAttachedSplineComp, false); + + Idx += 1; + break; + } + } + } + } + else + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); + } + + // Mark our outputs as loaded so they can be matched for potential reuse + // This indicates that the HGPO's ids are invalid and that HGPO should be matched using partnames instead + CurrentOutput->MarkAsLoaded(true); + } + + return true; +} + +// +bool +FHoudiniOutputTranslator::UploadChangedEditableOutput( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray &Outputs = HAC->Outputs; + + // Iterate through the outputs array of HAC. + for (auto& CurrentOutput : HAC->Outputs) + { + if (!CurrentOutput) + continue; + + // Only update the editable nodes that have been built before. + if (!CurrentOutput->IsEditableNode() || !CurrentOutput->HasEditableNodeBuilt()) + continue; + + for (auto& CurrentOutputObj : CurrentOutput->GetOutputObjects()) + { + UHoudiniSplineComponent* HoudiniSplineComponent = Cast(CurrentOutputObj.Value.OutputComponent); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (!HoudiniSplineComponent->HasChanged()) + continue; + + // Dont add rot/scale on editable curves as this crashes HAPI + if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + HoudiniSplineComponent, false)) + HoudiniSplineComponent->MarkChanged(false); + else + HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); + } + } + + return true; +} + + +bool +FHoudiniOutputTranslator::BuildAllOutputs( + const HAPI_NodeId& AssetId, + UObject* InOuterObject, + TArray& InOldOutputs, + TArray& OutNewOutputs, + const bool& InOutputTemplatedGeos) +{ + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Get the AssetInfo + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + FString CurrentAssetName; + { + FHoudiniEngineString hapiSTR(AssetInfo.nameSH); + hapiSTR.ToFString(CurrentAssetName); + } + + // Retrieve the asset's transform. + // TODO: Unused?! + //FTransform AssetUnrealTransform; + //if (!FHoudiniEngineUtils::HapiGetAssetTransform(AssetId, AssetUnrealTransform)) + // return false; + + // Retrieve information about each object contained within our asset. + TArray ObjectInfos; + TArray ObjectTransforms; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) + return false; + + // Mark all the previous HGPOs on the outputs as stale + // This indicates that they were from a previous cook and should then be deleted + for (auto& CurOutput : InOldOutputs) + { + if (CurOutput) + CurOutput->MarkAllHGPOsAsStale(true); + } + + // For HF / Volumes, we only create new Outputs for height volume + // Store all the other volumes (masks etc) on the side and we will + // match them with theit corresponding height volume after + TArray UnassignedVolumeParts; + + // Iterate through all objects. + int32 OutputIdx = 1; + for (int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ObjectIdx++) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectIdx]; + + // Cache/convert them + FHoudiniObjectInfo CurrentObjectInfo; + CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); + + // Retrieve object name. + FString CurrentObjectName = CurrentObjectInfo.Name; + + // Get transformation for this object. + FTransform TransformMatrix = FTransform::Identity; + if (ObjectTransforms.IsValidIndex(ObjectIdx)) + { + const HAPI_Transform & ObjectTransform = ObjectTransforms[ObjectIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(ObjectTransform, TransformMatrix); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: No HAPI transform for Object [%d %s] - using identity."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName); + } + + // Build an array of the geos we'll need to process + // In most case, it will only be the display geo, + // but we may also want to process editable geos as well + TArray GeoInfos; + + // Get the Display Geo's info + HAPI_GeoInfo DisplayHapiGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName); + } + else + { + // Add the display geo info to the array + GeoInfos.Add(DisplayHapiGeoInfo); + } + + // Handle the editable nodes for this geo + // Start by getting the number of editable nodes + int32 EditableNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + + if (EditableNodeCount > 0) + { + TArray< HAPI_NodeId > EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) + continue; + + // Add this geo to the geo info array + GeoInfos.Add(CurrentEditableGeoInfo); + } + } + + // Handle the templated nodes if desired + if (InOutputTemplatedGeos) + { + // Start by getting the number of templated nodes + int32 TemplatedNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, + HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, + true, &TemplatedNodeCount)); + + if (TemplatedNodeCount > 0) + { + TArray TemplatedNodeIds; + TemplatedNodeIds.SetNumUninitialized(TemplatedNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, TemplatedNodeIds.GetData(), TemplatedNodeCount)); + + for (int32 nTemplated = 0; nTemplated < TemplatedNodeCount; nTemplated++) + { + HAPI_GeoInfo CurrentTemplatedGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentTemplatedGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + TemplatedNodeIds[nTemplated], &CurrentTemplatedGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentTemplatedGeoInfo.isDisplayGeo) + continue; + + // We don't want all the nested template node IDs, + // as our HDA could potentially be using other HDAs with nested template flags + // Make sure the parent of the templated node is either the HDA, the current OBJ or the Display SOP + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurrentTemplatedGeoInfo.nodeId); + if (ParentId != CurrentHapiObjectInfo.nodeId + && ParentId != DisplayHapiGeoInfo.nodeId + && ParentId != AssetId) + { + continue; + } + + // Add this geo to the geo info array + GeoInfos.Add(CurrentTemplatedGeoInfo); + } + } + } + + // Iterates through the geos we want to process + for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) + { + // Cook editable/templated nodes to get their parts. + const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; + if ((CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) + || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0)) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, + &GeoInfos[GeoIdx])); + } + + // Cache/convert the display geo's info + FHoudiniGeoInfo CurrentGeoInfo; + CacheGeoInfo(CurrentHapiGeoInfo, CurrentGeoInfo); + + // Simply create an empty array for this geo's group names + // We might need it later for splitting + TArray GeoGroupNames; + + // Store all the sockets found for this geo's part + TArray GeoMeshSockets; + + // Iterate on this geo's parts + for (int32 PartId = 0; PartId < CurrentGeoInfo.PartCount; ++PartId) + { + // Get part information. + HAPI_PartInfo CurrentHapiPartInfo; + FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); + + // If the geo is templated, cook it manually + if (CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + } + + bool bPartInfoFailed = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + { + bPartInfoFailed = true; + + // If the geo is templated, attempt to cook it manually + if(CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, nullptr); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, + &GeoInfos[GeoIdx])); + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + { + // We managed to get the templated part infos after cooking + bPartInfoFailed = false; + } + } + } + + if (bPartInfoFailed) + { + // Error retrieving part info. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d] unable to retrieve PartInfo - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId); + continue; + } + + // Convert/cache the part info + FHoudiniPartInfo CurrentPartInfo; + CachePartInfo(CurrentHapiPartInfo, CurrentPartInfo); + + // Retrieve part name. + FString CurrentPartName = CurrentPartInfo.Name; + + // Unsupported/Invalid part + if (CurrentPartInfo.Type == EHoudiniPartType::Invalid) + continue; + + // Update part/instancer type from the part infos + EHoudiniPartType CurrentPartType = EHoudiniPartType::Invalid; + EHoudiniInstancerType CurrentInstancerType = EHoudiniInstancerType::Invalid; + switch (CurrentHapiPartInfo.type) + { + case HAPI_PARTTYPE_BOX: + case HAPI_PARTTYPE_SPHERE: + case HAPI_PARTTYPE_MESH: + { + if (CurrentHapiGeoInfo.type == HAPI_GEOTYPE_CURVE) + { + // Closed curve will be seen as mesh + CurrentPartType = EHoudiniPartType::Curve; + } + else + { + CurrentPartType = EHoudiniPartType::Mesh; + + if (CurrentHapiObjectInfo.isInstancer) + { + if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // That part is actually an attribute instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + } + else + { + // That part is actually an instancer + CurrentPartType = EHoudiniPartType::Instancer; + CurrentInstancerType = EHoudiniInstancerType::ObjectInstancer; + } + + } + else if (CurrentHapiPartInfo.vertexCount <= 0 && CurrentHapiPartInfo.pointCount <= 0) + { + // No points, no vertices, we're likely invalid + CurrentPartType = EHoudiniPartType::Invalid; + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a mesh with no points or vertices - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + } + else if (CurrentHapiPartInfo.vertexCount <= 0) + { + // This is not an instancer, we do not have vertices, but we have points + // Maybe this is a point cloud with attribute override instancing + if(FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // Mark it as an instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + } + else + { + // No vertices, not an instancer, just a point cloud, consider ourself as invalid + CurrentPartType = EHoudiniPartType::Invalid; + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a point cloud mesh - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + } + } + } + } + break; + + case HAPI_PARTTYPE_CURVE: + { + // Make sure that this curve is not an an attribute instancer! + if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // Mark the part as an instancer it as an instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + } + else + { + // The curve is a curve! + CurrentPartType = EHoudiniPartType::Curve; + } + } + break; + + case HAPI_PARTTYPE_INSTANCER: + // This is a packed primitive instancer + CurrentPartType = EHoudiniPartType::Instancer; + CurrentInstancerType = EHoudiniInstancerType::PackedPrimitive; + break; + + case HAPI_PARTTYPE_VOLUME: + // Volume data, likely a Heightfield height / mask + CurrentPartType = EHoudiniPartType::Volume; + break; + + default: + // Unsupported Part Type + break; + } + + // There are no vertices AND no points and this part is not a packed prim instancer + if ((CurrentPartInfo.VertexCount <= 0 && CurrentPartInfo.PointCount <= 0) + && (CurrentPartType != EHoudiniPartType::Instancer || CurrentInstancerType != EHoudiniInstancerType::PackedPrimitive)) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] no points or vertices found - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + continue; + } + + // This is an instancer with no points. + if (CurrentHapiObjectInfo.isInstancer && CurrentHapiPartInfo.pointCount <= 0) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is instancer but has 0 points - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + continue; + } + + // Extract Mesh sockets + // Do this before ignoring invalid parts, as socket groups/attributes could be set on parts + // that don't have any mesh, just points! Those would be be considered "invalid" parts but + // could still have valid sockets! + TArray PartMeshSockets; + FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, PartMeshSockets, CurrentHapiPartInfo.isInstanced); + FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, PartMeshSockets, CurrentHapiPartInfo.isInstanced); + + // Ignore invalid parts + if (CurrentPartType == EHoudiniPartType::Invalid) + { + if(PartMeshSockets.Num() > 0) + { + // Store these Part sockets for the Geo + // We'll copy them to the outputs produced by this Geo later + GeoMeshSockets.Append(PartMeshSockets); + } + + continue; + } + + // Build the HGPO corresponding to this part + FHoudiniGeoPartObject currentHGPO; + currentHGPO.AssetId = AssetId; + currentHGPO.AssetName = CurrentAssetName; + + currentHGPO.ObjectId = CurrentHapiObjectInfo.nodeId; + currentHGPO.ObjectName = CurrentObjectName; + + currentHGPO.GeoId = CurrentHapiGeoInfo.nodeId; + + currentHGPO.PartId = CurrentHapiPartInfo.id; + + currentHGPO.Type = CurrentPartType; + currentHGPO.InstancerType = CurrentInstancerType; + + currentHGPO.TransformMatrix = TransformMatrix; + + currentHGPO.NodePath = TEXT(""); + + currentHGPO.bIsVisible = CurrentHapiObjectInfo.isVisible && !CurrentHapiPartInfo.isInstanced; + currentHGPO.bIsEditable = CurrentHapiGeoInfo.isEditable; + currentHGPO.bIsInstanced = CurrentHapiPartInfo.isInstanced; + // Never consider a display geo as templated! + currentHGPO.bIsTemplated = CurrentHapiGeoInfo.isDisplayGeo ? false : CurrentHapiGeoInfo.isTemplated; + + currentHGPO.bHasGeoChanged = CurrentHapiGeoInfo.hasGeoChanged; + currentHGPO.bHasPartChanged = CurrentHapiPartInfo.hasChanged; + currentHGPO.bHasMaterialsChanged = CurrentHapiGeoInfo.hasMaterialChanged; + currentHGPO.bHasTransformChanged = CurrentHapiObjectInfo.hasTransformChanged; + + // Copy the HAPI info caches + currentHGPO.ObjectInfo = CurrentObjectInfo; + currentHGPO.GeoInfo = CurrentGeoInfo; + currentHGPO.PartInfo = CurrentPartInfo; + + currentHGPO.AllMeshSockets = PartMeshSockets; + + // We only support meshes for templated geos + if (currentHGPO.bIsTemplated && (CurrentPartType != EHoudiniPartType::Mesh)) + continue; + + // Update the HGPO's node path + FHoudiniEngineUtils::HapiGetNodePath(currentHGPO, currentHGPO.NodePath); + + // Try to get the custom part name from attribute + FString CustomPartName; + if (FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CustomPartName)) + currentHGPO.SetCustomPartName(CustomPartName); + else + currentHGPO.PartName = CurrentPartName; + + // + // Mesh Only - Extract split groups + // + // Extract the group names used by this part to see if it will require splitting + // Only meshes can be split, via their primitive groups + TArray SplitGroupNames; + if (CurrentPartType == EHoudiniPartType::Mesh) + { + if (!CurrentHapiPartInfo.isInstanced && GeoGroupNames.Num() > 0) + { + // We are not instanced and already have extracted the geo's group names + // We can simply reuse the Geo group names / socket groups + currentHGPO.SplitGroups = GeoGroupNames; + } + else + { + // We need to get the primitive group names from HAPI + int32 GroupCount = 0; + TArray GroupNames; + if (!FHoudiniEngineUtils::HapiGetGroupNames( + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + HAPI_GROUPTYPE_PRIM, CurrentHapiPartInfo.isInstanced, + GroupNames)) + { + GroupCount = 0; + GroupNames.Empty(); + } + + // Convert the string handles to FStrings + for (const FString& GroupName : GroupNames) + { + FString LodGroup = HAPI_UNREAL_GROUP_LOD_PREFIX; + FString CollisionGroup = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; + FString RenderedCollisionGroup = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; + if (GroupName.StartsWith(LodGroup, ESearchCase::IgnoreCase) + || GroupName.StartsWith(CollisionGroup, ESearchCase::IgnoreCase) + || GroupName.StartsWith(RenderedCollisionGroup, ESearchCase::IgnoreCase)) + //|| GroupName.StartsWith(HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX, ESearchCase::IgnoreCase)) + { + // Split by collisions / lods + currentHGPO.SplitGroups.Add(GroupName); + } + } + + // Sort the Group name array by name, + // this will order the LODs and other incremental group names + currentHGPO.SplitGroups.Sort(); + + // If this part is not instanced, we can copy the geo + // group names so we can reuse them for another part + if (!CurrentHapiPartInfo.isInstanced) + { + GeoGroupNames = currentHGPO.SplitGroups; + } + } + } + + // + // Volume Only - Extract volume name/tile index + // + // Extract the volume's name, and see if a tile attribute is present + FHoudiniVolumeInfo CurrentVolumeInfo; + if (CurrentPartType == EHoudiniPartType::Volume) + { + // Get this volume's info + HAPI_VolumeInfo CurrentHapiVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentHapiVolumeInfo); + + bool bVolumeValid = true; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + &CurrentHapiVolumeInfo)) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.tupleSize != 1) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.zLength != 1) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + { + bVolumeValid = false; + } + + // Only cache valid volumes + if (bVolumeValid) + { + // Convert/Cache the volume info + CacheVolumeInfo(CurrentHapiVolumeInfo, CurrentVolumeInfo); + + // Get the volume's name + currentHGPO.VolumeName = CurrentVolumeInfo.Name; + + // Now see if this volume has a tile attribute + TArray TileValues; + if (FHoudiniEngineUtils::GetTileAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, TileValues, HAPI_ATTROWNER_PRIM)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + currentHGPO.VolumeTileIndex = TileValues[0]; + else + currentHGPO.VolumeTileIndex = -1; + } + } + } + currentHGPO.VolumeInfo = CurrentVolumeInfo; + + // Cache the curve info as well + // !!! Only call GetCurveInfo if the PartType is Curve + // !!! Closed curves are actually Meshes, and calling GetCurveInfo on a Mesh will crash HAPI! + FHoudiniCurveInfo CurrentCurveInfo; + if (CurrentPartType == EHoudiniPartType::Curve && CurrentPartInfo.Type == EHoudiniPartType::Curve) + { + HAPI_CurveInfo CurrentHapiCurveInfo; + FHoudiniApi::CurveInfo_Init(&CurrentHapiCurveInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + &CurrentHapiCurveInfo)) + { + // Cache/Convert this part's curve info + CacheCurveInfo(CurrentHapiCurveInfo, CurrentCurveInfo); + } + } + currentHGPO.CurveInfo = CurrentCurveInfo; + + + // TODO: + // DONE? bake folders are handled out of this loop? + // See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute + //TArray BakeFolderOverrides; + + // See if we have an existing output that matches this HGPO or if we need to create a new one + bool IsFoundOutputValid = false; + UHoudiniOutput ** FoundHoudiniOutput = nullptr; + // We handle volumes differently than other outputs types, as a single HF output has multiple HGPOs + if (currentHGPO.Type != EHoudiniPartType::Volume) + { + // Look in the previous output if we have a match + FoundHoudiniOutput = InOldOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(currentHGPO) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + + } + else + { + // Look in the previous outputs if we have a match + FoundHoudiniOutput = InOldOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, true) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + + // If we dont have a match in the old maps, also look in the newly created outputs + if (!IsFoundOutputValid) + { + FoundHoudiniOutput = OutNewOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, false) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + } + } + + UHoudiniOutput * HoudiniOutput = nullptr; + if (IsFoundOutputValid) + { + // We can reuse the existing output + HoudiniOutput = *FoundHoudiniOutput; + HoudiniOutput->SetIsUpdating(true); + // Transfer this output from the old array to the new one + InOldOutputs.Remove(HoudiniOutput); + } + else + { + // We couldn't find a valid output object, so create a new one + + // If the current part is a volume, only create a new output object + // if the volume's name is "height", if not store the HGPO aside + if (currentHGPO.Type == EHoudiniPartType::Volume + && !currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) + { + UnassignedVolumeParts.Add(currentHGPO); + continue; + } + + // Create a new output object + //FString OutputName = TEXT("Output") + FString::FromInt(OutputIdx++); + HoudiniOutput = NewObject( + InOuterObject, + UHoudiniOutput::StaticClass(), + NAME_None,//FName(*OutputName), + RF_NoFlags); + + // Make sure the created object is valid + if (!HoudiniOutput || HoudiniOutput->IsPendingKill()) + { + //HOUDINI_LOG_WARNING("Failed to create asset output"); + continue; + } + + // Mark if the HoudiniOutput is editable + HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); + } + + // Add the HGPO to the output + HoudiniOutput->AddNewHGPO(currentHGPO); + // Add this output object to the new ouput array + OutNewOutputs.AddUnique(HoudiniOutput); + } + // END: for Part + + if (GeoMeshSockets.Num() > 0) + { + // If we have any mesh socket, assign them to the HGPO for this geo + for (auto& CurNewOutput : OutNewOutputs) + { + if (!IsValid(CurNewOutput)) + continue; + + int32 FirstValidIdx = CurNewOutput->StaleCount; + if (!CurNewOutput->HoudiniGeoPartObjects.IsValidIndex(FirstValidIdx)) + FirstValidIdx = 0; + + for (int32 Idx = FirstValidIdx; Idx < CurNewOutput->HoudiniGeoPartObjects.Num(); Idx++) + { + // Only add sockets to valid/non stale HGPOs + FHoudiniGeoPartObject& CurHGPO = CurNewOutput->HoudiniGeoPartObjects[Idx]; + if (CurHGPO.ObjectId != CurrentHapiObjectInfo.nodeId) + continue; + + if (CurHGPO.GeoId != CurrentHapiGeoInfo.nodeId) + continue; + + CurHGPO.AllMeshSockets.Append(GeoMeshSockets); + } + } + } + } + // END: for GEO + } + // END: for OBJ + + // Update the output/HGPO associations from the map + // Clear the old HGPO since we don't need them anymore + for (auto& CurrentOuput : OutNewOutputs) + { + if (!IsValid(CurrentOuput)) + continue; + + CurrentOuput->DeleteAllStaleHGPOs(); + } + + // If we have unassigned volumes, + // try to find their corresponding output + if (UnassignedVolumeParts.Num() > 0) + { + for (auto& currentVolumeHGPO : UnassignedVolumeParts) + { + UHoudiniOutput ** FoundHoudiniOutput = OutNewOutputs.FindByPredicate( + [currentVolumeHGPO](UHoudiniOutput* Output) + { + return Output ? Output->HeightfieldMatch(currentVolumeHGPO, false) : false; + }); + + if (!FoundHoudiniOutput || !(*FoundHoudiniOutput) || (*FoundHoudiniOutput)->IsPendingKill()) + { + // Skip - consider this volume as invalid + continue; + } + + // Add this HGPO to the output + (*FoundHoudiniOutput)->AddNewHGPO(currentVolumeHGPO); + } + } + + // All our output objects now have their HGPO assigned + // We can now parse them to update the output type + for (auto& Output : OutNewOutputs) + { + Output->UpdateOutputType(); + } + + return true; +} + +bool +FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray& Outputs = HAC->Outputs; + + // Iterate through the outputs array of HAC. + for (int32 Index = 0; Index < HAC->GetNumOutputs(); ++Index) + { + UHoudiniOutput* CurrentOutput = HAC->GetOutputAt(Index); + if (!CurrentOutput) + continue; + + if (!HAC->IsOutputTypeSupported(CurrentOutput->GetType())) + continue; + + switch (CurrentOutput->GetType()) + { + case EHoudiniOutputType::Instancer: + { + bool bNeedToRecreateInstancers = false; + for (auto& Iter : CurrentOutput->GetInstancedOutputs()) + { + FHoudiniInstancedOutput& InstOutput = Iter.Value; + if (!InstOutput.bChanged) + continue; + + /* + FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( + InstOutput, Iter.Key, CurrentOutput, HAC); + */ + + // TODO: + // UpdateChangedInstancedOutput needs some improvements + // as it currently destroy too many components. + // For now, we'll update all the instancers + bNeedToRecreateInstancers = true; + + InstOutput.MarkChanged(false); + } + + if (bNeedToRecreateInstancers) + { + if (HAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation || HAC->HasBeenLoaded()) + { + // Instantiate the HDA if it's not been + // This is because CreateAllInstancersFromHoudiniOutput() actually reads the transform from HAPI + // Calling it on a HDA not yet instantiated causes a crash... + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + } + else + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurrentOutput, Outputs, HAC); + } + } + } + break; + + case EHoudiniOutputType::Curve: + { + //FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); + } + break; + + default: + break; + } + } + + return true; +} + +void +FHoudiniOutputTranslator::CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache) +{ + FHoudiniEngineString hapiSTR(InObjInfo.nameSH); + hapiSTR.ToFString(OutObjInfoCache.Name); + //OutObjInfoCache.Name = InObjInfo.nameSH; + + OutObjInfoCache.NodeId = InObjInfo.nodeId; + OutObjInfoCache.ObjectToInstanceID = InObjInfo.objectToInstanceId; + + OutObjInfoCache.bHasTransformChanged = InObjInfo.hasTransformChanged; + OutObjInfoCache.bHaveGeosChanged = InObjInfo.haveGeosChanged; + + OutObjInfoCache.bIsVisible = InObjInfo.isVisible; + OutObjInfoCache.bIsInstancer = InObjInfo.isInstancer; + OutObjInfoCache.bIsInstanced = InObjInfo.isInstanced; + + OutObjInfoCache.GeoCount = InObjInfo.geoCount; +}; + +EHoudiniGeoType +FHoudiniOutputTranslator::ConvertHapiGeoType(const HAPI_GeoType& InType) +{ + EHoudiniGeoType OutType = EHoudiniGeoType::Invalid; + switch (InType) + { + case HAPI_GEOTYPE_DEFAULT: + OutType = EHoudiniGeoType::Default; + break; + + case HAPI_GEOTYPE_INTERMEDIATE: + OutType = EHoudiniGeoType::Intermediate; + break; + + case HAPI_GEOTYPE_INPUT: + OutType = EHoudiniGeoType::Input; + break; + + case HAPI_GEOTYPE_CURVE: + OutType = EHoudiniGeoType::Curve; + break; + + default: + OutType = EHoudiniGeoType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache) +{ + OutGeoInfoCache.Type = ConvertHapiGeoType(InGeoInfo.type); + + FHoudiniEngineString hapiSTR(InGeoInfo.nameSH); + hapiSTR.ToFString(OutGeoInfoCache.Name); + + OutGeoInfoCache.NodeId = InGeoInfo.nodeId; + + OutGeoInfoCache.bIsEditable = InGeoInfo.isEditable; + OutGeoInfoCache.bIsTemplated = InGeoInfo.isTemplated; + OutGeoInfoCache.bIsDisplayGeo = InGeoInfo.isDisplayGeo; + OutGeoInfoCache.bHasGeoChanged = InGeoInfo.hasGeoChanged; + OutGeoInfoCache.bHasMaterialChanged = InGeoInfo.hasMaterialChanged; + + OutGeoInfoCache.PartCount = InGeoInfo.partCount; + OutGeoInfoCache.PointGroupCount = InGeoInfo.pointGroupCount; + OutGeoInfoCache.PrimitiveGroupCount = InGeoInfo.primitiveGroupCount; +}; + + +EHoudiniPartType +FHoudiniOutputTranslator::ConvertHapiPartType(const HAPI_PartType& InType) +{ + EHoudiniPartType OutType = EHoudiniPartType::Invalid; + switch (InType) + { + case HAPI_PARTTYPE_BOX: + case HAPI_PARTTYPE_SPHERE: + case HAPI_PARTTYPE_MESH: + OutType = EHoudiniPartType::Mesh; + break; + + case HAPI_PARTTYPE_CURVE: + OutType = EHoudiniPartType::Curve; + break; + + case HAPI_PARTTYPE_INSTANCER: + OutType = EHoudiniPartType::Instancer; + break; + + case HAPI_PARTTYPE_VOLUME: + OutType = EHoudiniPartType::Volume; + break; + + default: + OutType = EHoudiniPartType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache) +{ + OutPartInfoCache.PartId = InPartInfo.id; + + FHoudiniEngineString hapiSTR(InPartInfo.nameSH); + hapiSTR.ToFString(OutPartInfoCache.Name); + + OutPartInfoCache.Type = ConvertHapiPartType(InPartInfo.type); + + OutPartInfoCache.FaceCount = InPartInfo.faceCount; + OutPartInfoCache.VertexCount = InPartInfo.vertexCount; + OutPartInfoCache.PointCount = InPartInfo.pointCount; + + OutPartInfoCache.PointAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_POINT]; + OutPartInfoCache.VertexAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX]; + OutPartInfoCache.PrimitiveAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_PRIM]; + OutPartInfoCache.DetailAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL]; + + OutPartInfoCache.bIsInstanced = InPartInfo.isInstanced; + + OutPartInfoCache.InstancedPartCount = InPartInfo.instancedPartCount; + OutPartInfoCache.InstanceCount = InPartInfo.instanceCount; + + OutPartInfoCache.bHasChanged = InPartInfo.hasChanged; +}; + +void +FHoudiniOutputTranslator::CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache) +{ + FHoudiniEngineString hapiSTR(InVolumeInfo.nameSH); + hapiSTR.ToFString(OutVolumeInfoCache.Name); + + OutVolumeInfoCache.bIsVDB = (InVolumeInfo.type == HAPI_VOLUMETYPE_VDB); // replaces VolumeType Type; + + OutVolumeInfoCache.TupleSize = InVolumeInfo.tupleSize; + OutVolumeInfoCache.bIsFloat = (InVolumeInfo.storage == HAPI_STORAGETYPE_FLOAT); // replaces StorageType StorageType; + OutVolumeInfoCache.TileSize = InVolumeInfo.tileSize; + + FHoudiniEngineUtils::TranslateHapiTransform(InVolumeInfo.transform, OutVolumeInfoCache.Transform); + OutVolumeInfoCache.bHasTaper = InVolumeInfo.hasTaper; + + OutVolumeInfoCache.XLength = InVolumeInfo.xLength; + OutVolumeInfoCache.YLength = InVolumeInfo.yLength; + OutVolumeInfoCache.ZLength = InVolumeInfo.zLength; + + OutVolumeInfoCache.MinX = InVolumeInfo.minX; + OutVolumeInfoCache.MinY = InVolumeInfo.minY; + OutVolumeInfoCache.MinZ = InVolumeInfo.minZ; + + OutVolumeInfoCache.XTaper = InVolumeInfo.xTaper; + OutVolumeInfoCache.YTaper = InVolumeInfo.yTaper; +}; + +EHoudiniCurveType +FHoudiniOutputTranslator::ConvertHapiCurveType(const HAPI_CurveType& InType) +{ + EHoudiniCurveType OutType = EHoudiniCurveType::Invalid; + switch (InType) + { + case HAPI_CURVETYPE_LINEAR: + OutType = EHoudiniCurveType::Polygon; + break; + + case HAPI_CURVETYPE_NURBS: + OutType = EHoudiniCurveType::Nurbs; + break; + + case HAPI_CURVETYPE_BEZIER: + OutType = EHoudiniCurveType::Bezier; + break; + + case HAPI_CURVETYPE_MAX: + OutType = EHoudiniCurveType::Points; + break; + + default: + OutType = EHoudiniCurveType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache) +{ + OutCurveInfoCache.Type = ConvertHapiCurveType(InCurveInfo.curveType); + + OutCurveInfoCache.CurveCount = InCurveInfo.curveCount; + OutCurveInfoCache.VertexCount = InCurveInfo.vertexCount; + OutCurveInfoCache.KnotCount = InCurveInfo.knotCount; + + OutCurveInfoCache.bIsPeriodic = InCurveInfo.isPeriodic; + OutCurveInfoCache.bIsRational = InCurveInfo.isRational; + + OutCurveInfoCache.Order = InCurveInfo.order; + + OutCurveInfoCache.bHasKnots = InCurveInfo.hasKnots; +}; + + +void +FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll) +{ + if (!IsValid(InHAC)) + return; + + // DO NOT MANUALLY DESTROY THE OLD/DANGLING OUTPUTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + // Simply clearing the array is enough + for (auto& OldOutput : InHAC->Outputs) + { + if (OldOutput->ShouldDeferClear() && !bForceClearAll) + { + OutputsPendingClear.Add(OldOutput); + } + else + { + ClearOutput(OldOutput); + } + } + + InHAC->Outputs.Empty(); +} + +void +FHoudiniOutputTranslator::ClearOutput(UHoudiniOutput* Output) +{ + switch (Output->GetType()) + { + case EHoudiniOutputType::Landscape: + { + for (auto& OutputObject : Output->GetOutputObjects()) + { + // Currently, any Landscape managed by an HDA is always present in the current level. + // Only when it gets baked will Landscapes be serialized to other levels so for now + // assume that a landscape should be available, unless explicitly deleted the user. + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.Value.OutputObject); + if (!LandscapePtr) + continue; + + ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); + + if (!IsValid(Landscape) || !Landscape->IsValidLowLevel()) + continue; + + Landscape->UnregisterAllComponents(); + Landscape->Destroy(); + + // if (Output->IsLandscapeWorldComposition()) + // { + // TSoftObjectPtr LandscapeSoftPtr = LandscapePtr->GetSoftPtr(); + // + // FString SoftPtrPath = LandscapeSoftPtr.ToSoftObjectPath().ToString(); + // + // FString FileName = FPaths::GetBaseFilename(SoftPtrPath); + // FString FileDirectory = FPaths::GetPath(SoftPtrPath); + // + // FString ContentPath = FPaths::ProjectContentDir(); + // FString ContentFullPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*ContentPath); + // + // FString AbsoluteFilePath = ContentFullPath + FileDirectory.Mid(5, FileDirectory.Len() - 5) + "/" + FPaths::GetBaseFilename(FileName) + ".umap"; + // + // FPlatformFileManager::Get().GetPlatformFile().FileExists(*(AbsoluteFilePath)); + // + // FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*(AbsoluteFilePath)); + // } + // else + // { + + // } + } + } + break; + + case EHoudiniOutputType::Instancer: + { + for (auto& OutputObject : Output->GetOutputObjects()) + { + // Is this a foliage instancer? Check if the component is owned by an AInstancedFoliageActor + UActorComponent* const Component = Cast(OutputObject.Value.OutputComponent); + if (!IsValid(Component)) + continue; + AActor* const OwnerActor = Component->GetOwner(); + if (!IsValid(OwnerActor) || !OwnerActor->IsA()) + continue; + + UHierarchicalInstancedStaticMeshComponent* const FoliageHISMC = Cast(Component); + if (IsValid(FoliageHISMC)) + { + // Find the parent component: the output is typically owned by an HAC. + USceneComponent* ParentComponent = nullptr; + UObject* const OutputOuter = Output->GetOuter(); + if (IsValid(OutputOuter)) + { + if (OutputOuter->IsA()) + { + ParentComponent = Cast(OutputOuter); + } + // other possibilities? + } + + // fallback to trying the owner of the HISMC + if (!ParentComponent) + { + ParentComponent = Cast(FoliageHISMC); + } + + if (IsValid(ParentComponent)) + { + FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, OutputObject.Value.OutputObject, ParentComponent); + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + } + } + } + } + break; + // ... Other output types ...// + + default: + break; + + } + + Output->Clear(); +} + + +bool +FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName) +{ + HAPI_AttributeInfo CustomPartNameInfo; + FHoudiniApi::AttributeInfo_Init(&CustomPartNameInfo); + + bool bHasCustomName = false; + TArray CustomNames; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, CustomPartNameInfo, CustomNames)) + { + // Look for the v2 attribute (unreal_output_name) + bHasCustomName = true; + } + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, CustomPartNameInfo, CustomNames)) + { + // If we couldnt find the new attribute, use the legacy v1 attribute (unreal_generated_mesh_name) + bHasCustomName = true; + } + + if (!bHasCustomName) + return false; + + if (CustomNames.Num() <= 0) + return false; + + OutCustomPartName = CustomNames[0]; + + if (OutCustomPartName.IsEmpty()) + return false; + + return true; +} + +void +FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) + return; + + FString TempFolderOverride = FString(); + + HAPI_AttributeInfo TempFolderAttriInfo; + FHoudiniApi::AttributeInfo_Init(&TempFolderAttriInfo); + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_DETAIL)) + { + TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); + } + else + { + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) + { + TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); + } + } + + if (TempFolderOverride.StartsWith("Game/")) + { + TempFolderOverride = "/" + TempFolderOverride; + } + + FString AbsoluteOverridePath; + if (TempFolderOverride.StartsWith("/Game/")) + { + FString RelativePath = FPaths::ProjectContentDir() + TempFolderOverride.Mid(6, TempFolderOverride.Len() - 6); + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + if (!TempFolderOverride.IsEmpty()) + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*TempFolderOverride); + } + + // Check Validity of the path + if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) + { + // Only display a warning if the path is invalid, empty is fine + if(!AbsoluteOverridePath.IsEmpty()) + HOUDINI_LOG_WARNING(TEXT("Invalid override temporary cook path: %s"), *TempFolderOverride); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + TempFolderOverride = HoudiniRuntimeSettings->DefaultTemporaryCookFolder; + } + + // If the TempCookFolder of the HAC is non-empty and is different from the override path. + // do not override it if the current temp cook path is valid. (it was user specified) + if (!HAC->TemporaryCookFolder.Path.IsEmpty() && !HAC->TemporaryCookFolder.Path.Equals(TempFolderOverride)) + return; + + HAC->TemporaryCookFolder.Path = TempFolderOverride; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h index 791b74cc8..a0ee54472 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h @@ -1,104 +1,104 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" - -class UHoudiniOutput; -class UHoudiniAssetComponent; - -struct FHoudiniObjectInfo; -struct FHoudiniGeoInfo; -struct FHoudiniPartInfo; -struct FHoudiniVolumeInfo; -struct FHoudiniCurveInfo; - -enum class EHoudiniOutputType : uint8; -enum class EHoudiniGeoType : uint8; -enum class EHoudiniPartType : uint8; -enum class EHoudiniCurveType : int8; - -struct HOUDINIENGINE_API FHoudiniOutputTranslator -{ - // - static bool UpdateOutputs( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate, - bool& bOutHasHoudiniStaticMeshOutput); - - // - static bool BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies=false); - - // - static bool UpdateLoadedOutputs(UHoudiniAssetComponent* HAC); - - // - static bool UploadChangedEditableOutput( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate); - // - static bool BuildAllOutputs( - const HAPI_NodeId& AssetId, - UObject* InOuterObject, - TArray& InOldOutputs, - TArray& OutNewOutputs, - const bool& InOutputTemplatedGeos); - - static bool UpdateChangedOutputs( - UHoudiniAssetComponent* HAC); - - // Helpers functions used to convert HAPI types - static EHoudiniGeoType ConvertHapiGeoType(const HAPI_GeoType& InType); - static EHoudiniPartType ConvertHapiPartType(const HAPI_PartType& InType); - static EHoudiniCurveType ConvertHapiCurveType(const HAPI_CurveType& InType); - - // Helper functions used to cache HAPI infos - static void CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache); - static void CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache); - static void CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache); - static void CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache); - static void CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache); - - /** - * Helper to clear the outputs of the houdini asset component - * - * Some outputs (such as landscapes) need "deferred clearing". This means that - * these outputs should only be destroyed AFTER the new outputs have been processed. - * - * @param InHAC All outputs for this Houdini Asset Component will be cleared. - * @param OutputsPendingClear Any outputs that is "pending" clear. These outputs should typically be cleared AFTER the new outputs have been fully processed. - * @param bForceClearAll Setting this flag will force outputs to be cleared here and not take into account outputs requested a deferred clear. - */ - static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll = false); - // Helper to clear an individual UHoudiniOutput - static void ClearOutput(UHoudiniOutput* Output); - - static bool GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName); - static void GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" + +class UHoudiniOutput; +class UHoudiniAssetComponent; + +struct FHoudiniObjectInfo; +struct FHoudiniGeoInfo; +struct FHoudiniPartInfo; +struct FHoudiniVolumeInfo; +struct FHoudiniCurveInfo; + +enum class EHoudiniOutputType : uint8; +enum class EHoudiniGeoType : uint8; +enum class EHoudiniPartType : uint8; +enum class EHoudiniCurveType : int8; + +struct HOUDINIENGINE_API FHoudiniOutputTranslator +{ + // + static bool UpdateOutputs( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate, + bool& bOutHasHoudiniStaticMeshOutput); + + // + static bool BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies=false); + + // + static bool UpdateLoadedOutputs(UHoudiniAssetComponent* HAC); + + // + static bool UploadChangedEditableOutput( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate); + // + static bool BuildAllOutputs( + const HAPI_NodeId& AssetId, + UObject* InOuterObject, + TArray& InOldOutputs, + TArray& OutNewOutputs, + const bool& InOutputTemplatedGeos); + + static bool UpdateChangedOutputs( + UHoudiniAssetComponent* HAC); + + // Helpers functions used to convert HAPI types + static EHoudiniGeoType ConvertHapiGeoType(const HAPI_GeoType& InType); + static EHoudiniPartType ConvertHapiPartType(const HAPI_PartType& InType); + static EHoudiniCurveType ConvertHapiCurveType(const HAPI_CurveType& InType); + + // Helper functions used to cache HAPI infos + static void CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache); + static void CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache); + static void CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache); + static void CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache); + static void CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache); + + /** + * Helper to clear the outputs of the houdini asset component + * + * Some outputs (such as landscapes) need "deferred clearing". This means that + * these outputs should only be destroyed AFTER the new outputs have been processed. + * + * @param InHAC All outputs for this Houdini Asset Component will be cleared. + * @param OutputsPendingClear Any outputs that is "pending" clear. These outputs should typically be cleared AFTER the new outputs have been fully processed. + * @param bForceClearAll Setting this flag will force outputs to be cleared here and not take into account outputs requested a deferred clear. + */ + static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll = false); + // Helper to clear an individual UHoudiniOutput + static void ClearOutput(UHoudiniOutput* Output); + + static bool GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName); + static void GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp index c3c416a4b..53b89fbfd 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp @@ -1,106 +1,106 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGImporterMessages.h" - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage() - : FilePath() - , Name() - , TOPNodeId(-1) - , WorkItemId(-1) -{ - -} - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams -) - : FilePath(InFilePath) - , Name(InName) - , TOPNodeId(-1) - , WorkItemId(-1) -{ - SetPackageParams(InPackageParams); -} - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - HAPI_NodeId InTOPNodeId, - HAPI_PDG_WorkitemId InWorkItemId -) - : FilePath(InFilePath) - , Name(InName) - , TOPNodeId(InTOPNodeId) - , WorkItemId(InWorkItemId) -{ - SetPackageParams(InPackageParams); -} - -void FHoudiniPDGImportBGEOMessage::SetPackageParams(const FHoudiniPackageParams& InPackageParams) -{ - PackageParams = InPackageParams; - PackageParams.OuterPackage = nullptr; -} - -void FHoudiniPDGImportBGEOMessage::PopulatePackageParams(FHoudiniPackageParams& OutPackageParams) const -{ - UObject* KeepOuter = OutPackageParams.OuterPackage; - OutPackageParams = PackageParams; - OutPackageParams.OuterPackage = KeepOuter; -} - -FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage() - : ImportResult(EHoudiniPDGImportBGEOResult::HPIBR_Failed) -{ - -} - -FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - const EHoudiniPDGImportBGEOResult& InImportResult -) - : FHoudiniPDGImportBGEOMessage(InFilePath, InName, InPackageParams) - , ImportResult(InImportResult) -{ -} - -FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage() - : CommandletGuid() -{ - -} - -FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid) - : CommandletGuid(InCommandletGuid) -{ - -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGImporterMessages.h" + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage() + : FilePath() + , Name() + , TOPNodeId(-1) + , WorkItemId(-1) +{ + +} + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams +) + : FilePath(InFilePath) + , Name(InName) + , TOPNodeId(-1) + , WorkItemId(-1) +{ + SetPackageParams(InPackageParams); +} + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + HAPI_NodeId InTOPNodeId, + HAPI_PDG_WorkitemId InWorkItemId +) + : FilePath(InFilePath) + , Name(InName) + , TOPNodeId(InTOPNodeId) + , WorkItemId(InWorkItemId) +{ + SetPackageParams(InPackageParams); +} + +void FHoudiniPDGImportBGEOMessage::SetPackageParams(const FHoudiniPackageParams& InPackageParams) +{ + PackageParams = InPackageParams; + PackageParams.OuterPackage = nullptr; +} + +void FHoudiniPDGImportBGEOMessage::PopulatePackageParams(FHoudiniPackageParams& OutPackageParams) const +{ + UObject* KeepOuter = OutPackageParams.OuterPackage; + OutPackageParams = PackageParams; + OutPackageParams.OuterPackage = KeepOuter; +} + +FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage() + : ImportResult(EHoudiniPDGImportBGEOResult::HPIBR_Failed) +{ + +} + +FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + const EHoudiniPDGImportBGEOResult& InImportResult +) + : FHoudiniPDGImportBGEOMessage(InFilePath, InName, InPackageParams) + , ImportResult(InImportResult) +{ +} + +FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage() + : CommandletGuid() +{ + +} + +FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid) + : CommandletGuid(InCommandletGuid) +{ + +} diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h index df3b58f73..70c60ad7a 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h @@ -1,187 +1,187 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Misc/Guid.h" - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniPackageParams.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInstanceTranslator.h" - -#include "HoudiniPDGImporterMessages.generated.h" - -// Message used to find/discover running commandlets -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEODiscoverMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEODiscoverMessage(); - - FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid); - - // The GUID of the commandlet we are looking for - UPROPERTY() - FGuid CommandletGuid; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEOMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEOMessage(); - - FHoudiniPDGImportBGEOMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams); - - FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - HAPI_NodeId InTOPNodeId, - HAPI_PDG_WorkitemId InWorkItemId); - - void SetPackageParams(const FHoudiniPackageParams& InPackageParams); - - void PopulatePackageParams(FHoudiniPackageParams &OutPackageParams) const; - - // BGEO file path - UPROPERTY() - FString FilePath; - - // PDG work item name - UPROPERTY() - FString Name; - - // TOP/PDG info - // TOP node ID - UPROPERTY() - // HAPI_NodeId TOPNodeId; - int32 TOPNodeId; - - // Work item id - UPROPERTY() - // HAPI_PDG_WorkitemId WorkItemId; - int32 WorkItemId; - - // Package params for the asset - UPROPERTY() - FHoudiniPackageParams PackageParams; -}; - - -UENUM() -enum class EHoudiniPDGImportBGEOResult : uint8 -{ - // Create uassets from the bgeo completely failed. - HPIBR_Failed UMETA(DisplayName="Failed"), - - // Successfully created uassets for all content in the bgeo - HPIBR_Success UMETA(DisplayName = "Success"), - - // Some uassets were created, but there were unsupported objects in the bgeo as well - HPIBR_PartialSuccess UMETA(DisplayName = "Partial Success"), - - HIBPR_MAX -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniGenericAttributes -{ -public: - GENERATED_BODY() - - FHoudiniGenericAttributes() {}; - FHoudiniGenericAttributes(const TArray& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; - FHoudiniGenericAttributes(TArray&& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; - - UPROPERTY() - TArray PropertyAttributes; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutputObject -{ -public: - GENERATED_BODY(); - - UPROPERTY() - FHoudiniOutputObjectIdentifier Identifier; - - UPROPERTY() - FString PackagePath; - - UPROPERTY() - FHoudiniGenericAttributes GenericAttributes; - - UPROPERTY() - TMap CachedAttributes; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutput -{ -public: - GENERATED_BODY(); - - UPROPERTY() - TArray HoudiniGeoPartObjects; - - UPROPERTY() - TArray OutputObjects; - - UPROPERTY() - TArray InstancedOutputPartData; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEOResultMessage : public FHoudiniPDGImportBGEOMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEOResultMessage(); - - FHoudiniPDGImportBGEOResultMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams, const EHoudiniPDGImportBGEOResult& InImportResult); - - void operator=(const FHoudiniPDGImportBGEOMessage& InRHS) { (*static_cast(this)) = InRHS; } - - // Result of the bgeo import -> uassets - UPROPERTY() - EHoudiniPDGImportBGEOResult ImportResult; - - UPROPERTY() - TArray Outputs; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Guid.h" + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniPackageParams.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInstanceTranslator.h" + +#include "HoudiniPDGImporterMessages.generated.h" + +// Message used to find/discover running commandlets +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEODiscoverMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEODiscoverMessage(); + + FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid); + + // The GUID of the commandlet we are looking for + UPROPERTY() + FGuid CommandletGuid; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEOMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEOMessage(); + + FHoudiniPDGImportBGEOMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams); + + FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + HAPI_NodeId InTOPNodeId, + HAPI_PDG_WorkitemId InWorkItemId); + + void SetPackageParams(const FHoudiniPackageParams& InPackageParams); + + void PopulatePackageParams(FHoudiniPackageParams &OutPackageParams) const; + + // BGEO file path + UPROPERTY() + FString FilePath; + + // PDG work item name + UPROPERTY() + FString Name; + + // TOP/PDG info + // TOP node ID + UPROPERTY() + // HAPI_NodeId TOPNodeId; + int32 TOPNodeId; + + // Work item id + UPROPERTY() + // HAPI_PDG_WorkitemId WorkItemId; + int32 WorkItemId; + + // Package params for the asset + UPROPERTY() + FHoudiniPackageParams PackageParams; +}; + + +UENUM() +enum class EHoudiniPDGImportBGEOResult : uint8 +{ + // Create uassets from the bgeo completely failed. + HPIBR_Failed UMETA(DisplayName="Failed"), + + // Successfully created uassets for all content in the bgeo + HPIBR_Success UMETA(DisplayName = "Success"), + + // Some uassets were created, but there were unsupported objects in the bgeo as well + HPIBR_PartialSuccess UMETA(DisplayName = "Partial Success"), + + HIBPR_MAX +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniGenericAttributes +{ +public: + GENERATED_BODY() + + FHoudiniGenericAttributes() {}; + FHoudiniGenericAttributes(const TArray& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; + FHoudiniGenericAttributes(TArray&& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; + + UPROPERTY() + TArray PropertyAttributes; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutputObject +{ +public: + GENERATED_BODY(); + + UPROPERTY() + FHoudiniOutputObjectIdentifier Identifier; + + UPROPERTY() + FString PackagePath; + + UPROPERTY() + FHoudiniGenericAttributes GenericAttributes; + + UPROPERTY() + TMap CachedAttributes; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutput +{ +public: + GENERATED_BODY(); + + UPROPERTY() + TArray HoudiniGeoPartObjects; + + UPROPERTY() + TArray OutputObjects; + + UPROPERTY() + TArray InstancedOutputPartData; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEOResultMessage : public FHoudiniPDGImportBGEOMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEOResultMessage(); + + FHoudiniPDGImportBGEOResultMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams, const EHoudiniPDGImportBGEOResult& InImportResult); + + void operator=(const FHoudiniPDGImportBGEOMessage& InRHS) { (*static_cast(this)) = InRHS; } + + // Result of the bgeo import -> uassets + UPROPERTY() + EHoudiniPDGImportBGEOResult ImportResult; + + UPROPERTY() + TArray Outputs; + +}; diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp index 1a496e0b4..66639e72c 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp @@ -1,2251 +1,2264 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGManager.h" - -#include "Modules/ModuleManager.h" -#include "MessageEndpointBuilder.h" -#include "HAL/FileManager.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPackageParams.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniPDGTranslator.h" -#include "HoudiniPDGImporterMessages.h" - -#include "HAPI/HAPI_Common.h" - -HOUDINI_PDG_DEFINE_LOG_CATEGORY(); - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniPDGManager::FHoudiniPDGManager() -{ -} - -FHoudiniPDGManager::~FHoudiniPDGManager() -{ -} - -bool -FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) -{ - if (!InHAC || InHAC->IsPendingKill()) - return false; - - int32 AssetId = InHAC->GetAssetId(); - if (AssetId < 0) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) - return false; - - // Create a new PDG Asset Link Object - bool bRegisterPDGAssetLink = false; - UHoudiniPDGAssetLink* PDGAssetLink = InHAC->GetPDGAssetLink(); - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - { - PDGAssetLink = NewObject(InHAC, UHoudiniPDGAssetLink::StaticClass(), NAME_None, RF_Transactional); - bRegisterPDGAssetLink = true; - } - - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - PDGAssetLink->AssetID = AssetId; - - // Get the HDA's info - HAPI_NodeInfo AssetInfo; - FHoudiniApi::NodeInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, &AssetInfo), false); - - // Get the node path - FString AssetNodePath; - FHoudiniEngineString::ToFString(AssetInfo.internalNodePathSH, PDGAssetLink->AssetNodePath); - - // Get the node name - FString AssetName; - FHoudiniEngineString::ToFString(AssetInfo.nameSH, PDGAssetLink->AssetName); - - const bool bZeroWorkItemTallys = true; - if (!FHoudiniPDGManager::PopulateTOPNetworks(PDGAssetLink, bZeroWorkItemTallys)) - { - // We couldn't find any valid TOPNet/TOPNode, this is not a PDG Asset - // Make sure the HDA doesn't have a PDGAssetLink - InHAC->SetPDGAssetLink(nullptr); - return false; - } - - // If the PDG asset link comes from a loaded asset, we also need to register it - if (InHAC->HasBeenLoaded()) - { - bRegisterPDGAssetLink = true; - } - - // We have found valid TOPNetworks and TOPNodes, - // This is a PDG HDA, so add the AssetLink to it - PDGAssetLink->LinkState = EPDGLinkState::Linked; - - if (PDGAssetLink->SelectedTOPNetworkIndex < 0) - PDGAssetLink->SelectedTOPNetworkIndex = 0; - - InHAC->SetPDGAssetLink(PDGAssetLink); - - if (bRegisterPDGAssetLink) - { - // Register this PDG Asset Link to the PDG Manager - TWeakObjectPtr AssetLinkPtr(PDGAssetLink); - PDGAssetLinks.Add(AssetLinkPtr); - } - - // If the commandlet is enabled, check if we have started and established communication with the commandlet yet - // if not, try to start the commandlet - bool bCommandletIsEnabled = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (IsValid(HoudiniRuntimeSettings)) - { - bCommandletIsEnabled = HoudiniRuntimeSettings->bPDGAsyncCommandletImportEnabled; - } - - if (bCommandletIsEnabled) - { - const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); - if (CommandletStatus == EHoudiniBGEOCommandletStatus::NotStarted && bCommandletIsEnabled) - { - CreateBGEOCommandletAndEndpoint(); - } - } - - return true; -} - -bool -FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) -{ - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - // If the PDG Asset link is inactive, indicate that our HDA must be instantiated - if (PDGAssetLink->LinkState == EPDGLinkState::Inactive) - { - UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); - if(!ParentHAC) - { - // No valid parent HAC, error! - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("No valid Houdini Asset Component parent for PDG Asset Link!")); - } - else if (ParentHAC && ParentHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) - { - PDGAssetLink->LinkState = EPDGLinkState::Linking; - ParentHAC->AssetState = EHoudiniAssetState::PreInstantiation; - } - else - { - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("Unable to link the PDG Asset link! Try to rebuild the HDA.")); - } - } - - if (PDGAssetLink->LinkState == EPDGLinkState::Linking) - { - return true; - } - - if (PDGAssetLink->LinkState != EPDGLinkState::Linked) - { - UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); - int32 AssetId = ParentHAC->GetAssetId(); - if (AssetId < 0) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) - return false; - - PDGAssetLink->AssetID = AssetId; - } - - if(!PopulateTOPNetworks(PDGAssetLink)) - { - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("Failed to populte the PDG Asset Link.")); - return false; - } - - return true; -} - - -bool -FHoudiniPDGManager::PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys) -{ - // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - // Get all the network nodes within the asset, recursively. - // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type - int32 NetworkNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); - - if (NetworkNodeCount <= 0) - return false; - - TArray AllNetworkNodeIDs; - AllNetworkNodeIDs.SetNum(NetworkNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); - - // There is currently no way to only get non bypassed nodes via HAPI - // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... - TArray AllBypassedTOPNetNodeIDs; - { - int32 BypassedTOPNetNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); - - if (BypassedTOPNetNodeCount > 0) - { - // Get the list of all bypassed TOP Net... - AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); - - // ... and remove them from the network list - for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) - AllNetworkNodeIDs.RemoveAt(Idx); - } - } - } - - - // For each Network we found earlier, only add those with TOP child nodes - // Therefore guaranteeing that we only add TOP networks - TArray AllTOPNetworks; - for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) - { - if (CurrentNodeId < 0) - { - continue; - } - - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) - { - continue; - } - - // Skip non TOP or SOP networks - if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP - && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) - { - continue; - } - - // Check that this TOP Net is not nested in another TOP Net... - // This will happen with ROP Geometry TOPs for example... - bool bIsNestedInTOPNet = false; - HAPI_NodeId CurrentParentId = CurrentNodeInfo.parentId; - while (CurrentParentId > 0) - { - if (AllNetworkNodeIDs.Contains(CurrentParentId)) - { - bIsNestedInTOPNet = true; - break; - } - - if(AllBypassedTOPNetNodeIDs.Contains(CurrentParentId)) - { - bIsNestedInTOPNet = true; - break; - } - - HAPI_NodeInfo ParentNodeInfo; - FHoudiniApi::NodeInfo_Init(&ParentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentParentId, &ParentNodeInfo)) - { - break; - } - - // Get our parent's parent - CurrentParentId = ParentNodeInfo.parentId; - } - - // Skip nested TOP nets - if (bIsNestedInTOPNet) - continue; - - // Get the list of all TOP nodes within the current network (ignoring schedulers) - int32 TOPNodeCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) - { - continue; - } - - TArray AllTOPNodeIDs; - AllTOPNodeIDs.SetNum(TOPNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, AllTOPNodeIDs.GetData(), TOPNodeCount), false); - - // Skip networks without TOP nodes - if (AllTOPNodeIDs.Num() <= 0) - { - continue; - } - - // Since there is currently no way to get only non-bypassed nodes via HAPI - // we need to get a list of all the bypassed top nodes to remove them from the previous list - { - int32 BypassedTOPNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_TOP_NONSCHEDULER | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNodeCount), false); - - if (BypassedTOPNodeCount > 0) - { - // Get the list of all bypassed TOP Nodes... - TArray AllBypassedTOPNodes; - AllBypassedTOPNodes.SetNum(BypassedTOPNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - AllBypassedTOPNodes.GetData(), BypassedTOPNodeCount), false); - - // ... and remove them from the top node list - for (int32 Idx = AllTOPNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNodes.Contains(AllTOPNodeIDs[Idx])) - AllTOPNodeIDs.RemoveAt(Idx); - } - } - } - - // TODO: - // Apply the show and output filter on that node - bool bShow = true; - - // Get the node path - FString CurrentNodePath; - FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, PDGAssetLink->AssetID, CurrentNodePath); - - // Get the node name - FString CurrentNodeName; - FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, CurrentNodeName); - - UTOPNetwork* CurrentTOPNetwork = nullptr; - int32 FoundTOPNetIndex = INDEX_NONE; - UTOPNetwork* FoundTOPNet = PDGAssetLink->GetTOPNetworkByNodePath(CurrentNodeName, PDGAssetLink->AllTOPNetworks, FoundTOPNetIndex); - if (IsValid(FoundTOPNet)) - { - // Reuse the existing corresponding TOP NET - CurrentTOPNetwork = FoundTOPNet; - PDGAssetLink->AllTOPNetworks.RemoveAt(FoundTOPNetIndex); - } - else - { - // Create a new instance for the TOP NET - CurrentTOPNetwork = NewObject(PDGAssetLink, UTOPNetwork::StaticClass(), NAME_None, RF_Transactional); - } - - // Update the TOP NET - CurrentTOPNetwork->NodeId = CurrentNodeId; - CurrentTOPNetwork->NodeName = CurrentNodeName; - CurrentTOPNetwork->NodePath = CurrentNodePath; - CurrentTOPNetwork->ParentName = PDGAssetLink->AssetName; - CurrentTOPNetwork->bShowResults = bShow; - - // Only add network that have valid TOP Nodes - if (PopulateTOPNodes(AllTOPNodeIDs, CurrentTOPNetwork, PDGAssetLink, bInZeroWorkItemTallys)) - { - // See if we need to select a new TOP node - bool bReselectValidTOP = false; - if (CurrentTOPNetwork->SelectedTOPIndex < 0) - bReselectValidTOP = true; - else if (!CurrentTOPNetwork->AllTOPNodes.IsValidIndex(CurrentTOPNetwork->SelectedTOPIndex)) - bReselectValidTOP = true; - else if (!IsValid(CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]) || - CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]->bHidden) - bReselectValidTOP = true; - - if (bReselectValidTOP) - { - // Select the first valid TOP node (not hidden) - for (int Idx = 0; Idx < CurrentTOPNetwork->AllTOPNodes.Num(); Idx++) - { - UTOPNode *TOPNode = CurrentTOPNetwork->AllTOPNodes[Idx]; - if (!IsValid(TOPNode) || TOPNode->bHidden) - continue; - - CurrentTOPNetwork->SelectedTOPIndex = Idx; - break; - } - } - - AllTOPNetworks.Add(CurrentTOPNetwork); - } - } - - // Clear previous TOP networks, nodes and generated data - for (UTOPNetwork* CurrentTOPNet : PDGAssetLink->AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - PDGAssetLink->ClearTOPNetworkWorkItemResults(CurrentTOPNet); - } - //PDGAssetLink->ClearAllTOPData(); - PDGAssetLink->AllTOPNetworks = AllTOPNetworks; - - return (AllTOPNetworks.Num() > 0); -} - - -bool -FHoudiniPDGManager::PopulateTOPNodes( - const TArray& InTopNodeIDs, UTOPNetwork* InTOPNetwork, UHoudiniPDGAssetLink* InPDGAssetLink, bool bInZeroWorkItemTallys) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InTOPNetwork)) - return false; - - // - int32 TOPNodeCount = 0; - - // Holds list of found TOP nodes - TArray AllTOPNodes; - for(const HAPI_NodeId& CurrentTOPNodeID : InTopNodeIDs) - { - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentTOPNodeID, &CurrentNodeInfo)) - { - continue; - } - - // Increase the number of valid TOP Node - // (before applying the node filter) - TOPNodeCount++; - - // Get the node path - FString NodePath; - FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, InTOPNetwork->NodeId, NodePath); - - // Get the node name - FString NodeName; - FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, NodeName); - - // See if we can find an existing version of this TOPNOde - UTOPNode* CurrentTOPNode = nullptr; - int32 FoundNodeIndex = INDEX_NONE; - UTOPNode* FoundNode = InPDGAssetLink->GetTOPNodeByNodePath(NodePath, InTOPNetwork->AllTOPNodes, FoundNodeIndex); - if (IsValid(FoundNode)) - { - CurrentTOPNode = FoundNode; - InTOPNetwork->AllTOPNodes.RemoveAt(FoundNodeIndex); - - if (bInZeroWorkItemTallys) - { - CurrentTOPNode->ZeroWorkItemTally(); - CurrentTOPNode->NodeState = EPDGNodeState::None; - } - } - else - { - // Didn't find an existing UTOPNode for this node, create a new UTOPNode instance - CurrentTOPNode = NewObject(InPDGAssetLink, UTOPNode::StaticClass(), NAME_None, RF_Transactional); - } - - CurrentTOPNode->NodeId = CurrentTOPNodeID; - CurrentTOPNode->NodeName = NodeName; - CurrentTOPNode->NodePath = NodePath; - CurrentTOPNode->ParentName = InTOPNetwork->ParentName + TEXT("_") + InTOPNetwork->NodeName; - CurrentTOPNode->bHasChildNodes = CurrentNodeInfo.childNodeCount > 0; - - // Filter display/autoload using name - CurrentTOPNode->bHidden = false; - if (InPDGAssetLink->bUseTOPNodeFilter && !InPDGAssetLink->TOPNodeFilter.IsEmpty()) - { - // Only display nodes that matches the filter - if (!NodeName.StartsWith(InPDGAssetLink->TOPNodeFilter)) - CurrentTOPNode->bHidden = true; - } - - // Automatically load results for nodes that match the filter - if (InPDGAssetLink->bUseTOPOutputFilter) - { - bool bAutoLoad = false; - if (InPDGAssetLink->TOPOutputFilter.IsEmpty()) - bAutoLoad = true; - else if (NodeName.StartsWith(InPDGAssetLink->TOPOutputFilter)) - bAutoLoad = true; - - CurrentTOPNode->bAutoLoad = bAutoLoad; - - // Show autoloaded results by default - CurrentTOPNode->SetVisibleInLevel(bAutoLoad); - } - - AllTOPNodes.Add(CurrentTOPNode); - } - - for (UTOPNode* CurTOPNode : InTOPNetwork->AllTOPNodes) - { - if (!IsValid(CurTOPNode)) - continue; - - InPDGAssetLink->ClearTOPNodeWorkItemResults(CurTOPNode); - } - - InTOPNetwork->AllTOPNodes = AllTOPNodes; - - return (TOPNodeCount > 0); -} - - -void -FHoudiniPDGManager::DirtyTOPNode(UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - // Dirty the specified TOP node... - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( - FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, true)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *(InTOPNode->NodeName)); - } - - // ... and clear its work item results. - UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(InTOPNode); -} - -// void -// FHoudiniPDGManager::DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode) -// { -// // Dirty the specified TOP node... -// if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( -// FHoudiniEngine::Get().GetSession(), InTOPNode.NodeId, true)) -// { -// HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *InTOPNode.NodeName); -// } -// } - -void -FHoudiniPDGManager::CookTOPNode(UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - if (InTOPNode->NodeState == EPDGNodeState::Cooking || InTOPNode->AnyWorkItemsPending()) - { - HOUDINI_LOG_WARNING(TEXT("PDG Cook TOP Node - %s is already/still cooking, ignoring 'Cook TOP Node' request."), *(InTOPNode->NodePath)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( - FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, 0, 0)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook TOP Node - Failed to cook %s!"), *(InTOPNode->NodeName)); - } -} - - -void -FHoudiniPDGManager::DirtyAll(UTOPNetwork* InTOPNet) -{ - if (!IsValid(InTOPNet)) - return; - - // Dirty the specified TOP network... - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, true)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Dirty All - Failed to dirty all of %s's TOP nodes!"), *(InTOPNet->NodeName)); - return; - } - - // ... and clear its work item results. - UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(InTOPNet); -} - - -void -FHoudiniPDGManager::CookOutput(UTOPNetwork* InTOPNet) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::CookOutput); - - // Cook the output TOP node of the currently selected TOP network. - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - bool bAlreadyCooking = InTOPNet->AnyWorkItemsPending(); - - if (!bAlreadyCooking) - { - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - int32 PDGState = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( - FHoudiniEngine::Get().GetSession(), GraphContextId, &PDGState)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's PDG state."), *(InTOPNet->NodeName)); - return; - } - bAlreadyCooking = ((HAPI_PDG_State) PDGState == HAPI_PDG_STATE_COOKING); - } - - if (bAlreadyCooking) - { - HOUDINI_LOG_WARNING(TEXT("PDG Cook Output - %s is already/still cooking, ignoring 'Cook Output' request."), *(InTOPNet->NodeName)); - return; - } - - // TODO: ??? - // Cancel all cooks. This is required as otherwise the graph gets into an infinite cook state (bug?) - if(HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, 0, 0)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to cook %s's output!"), *(InTOPNet->NodeName)); - } -} - - -void -FHoudiniPDGManager::PauseCook(UTOPNetwork* InTOPNet) -{ - // Pause the PDG cook of the currently selected TOP network - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::PausePDGCook( - FHoudiniEngine::Get().GetSession(), GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to pause %s!"), *(InTOPNet->NodeName)); - return; - } -} - - -void -FHoudiniPDGManager::CancelCook(UTOPNetwork* InTOPNet) -{ - // Cancel the PDG cook of the currently selected TOP network - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CancelPDGCook( - FHoudiniEngine::Get().GetSession(), GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to cancel cook for %s!"), *(InTOPNet->NodeName)); - return; - } -} - -void -FHoudiniPDGManager::Update() -{ - // Clean up registered PDG Asset Links - for(int32 Idx = PDGAssetLinks.Num() - 1; Idx >= 0; Idx--) - { - TWeakObjectPtr Ptr = PDGAssetLinks[Idx]; - if (!Ptr.IsValid() || Ptr.IsStale()) - { - PDGAssetLinks.RemoveAt(Idx); - continue; - } - - UHoudiniPDGAssetLink* CurPDGAssetLink = PDGAssetLinks[Idx].Get(); - if (!CurPDGAssetLink || CurPDGAssetLink->IsPendingKill()) - { - PDGAssetLinks.RemoveAt(Idx); - continue; - } - } - - // Do nothing if we dont have any valid PDG asset Link - if (PDGAssetLinks.Num() <= 0) - return; - - // Update the PDG contexts and handle all pdg events and work item status updates - UpdatePDGContexts(); - - // Prcoess any workitem result if we have any - ProcessWorkItemResults(); -} - -// Query all the PDG graph context in the current Houdini Engine session. -// Handle PDG events, work item status updates. -// Forward relevant events to PDGAssetLink objects. -void -FHoudiniPDGManager::UpdatePDGContexts() -{ - // Get current PDG graph contexts - ReinitializePDGContext(); - - // Process next set of events for each graph context - if (PDGContextIDs.Num() > 0) - { - // Only initialize event array if not valid, or user resized max size - if(PDGEventInfos.Num() != MaxNumberOfPDGEvents) - PDGEventInfos.SetNum(MaxNumberOfPDGEvents); - - // TODO: member? - //HAPI_PDG_State PDGState; - for(const HAPI_PDG_GraphContextId& CurrentContextID : PDGContextIDs) - { - /* - // TODO: No need to reset events at each tick - int32 PDGStateInt; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( - FHoudiniEngine::Get().GetSession(), CurrentContextID, &PDGStateInt)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get PDG state")); - continue; - } - - PDGState = (HAPI_PDG_State)PDGStateInt; - - for (int32 Idx = 0; Idx < PDGEventInfos.Num(); Idx++) - { - ResetPDGEventInfo(PDGEventInfos[Idx]); - } - */ - - int32 PDGEventCount = 0; - int32 RemainingPDGEventCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGEvents( - FHoudiniEngine::Get().GetSession(), CurrentContextID, PDGEventInfos.GetData(), - MaxNumberOfPDGEvents, &PDGEventCount, &RemainingPDGEventCount)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get PDG events")); - continue; - } - - if (PDGEventCount < 1) - continue; - - for (int32 EventIdx = 0; EventIdx < PDGEventCount; EventIdx++) - { - ProcessPDGEvent(CurrentContextID, PDGEventInfos[EventIdx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("PDG: Tick processed %d events, %d remaining."), PDGEventCount, RemainingPDGEventCount); - } - } - - // Refresh UI if necessary - for (auto CurAssetLink : PDGAssetLinks) - { - UHoudiniPDGAssetLink* AssetLink = CurAssetLink.Get(); - if (AssetLink) - { - if (AssetLink->bNeedsUIRefresh) - { - FHoudiniPDGManager::RefreshPDGAssetLinkUI(AssetLink); - AssetLink->bNeedsUIRefresh = false; - } - else - { - AssetLink->UpdateWorkItemTally(); - } - } - } -} - -// Query the currently active PDG graph contexts in the Houdini Engine session. -// Should be done each time to get latest set of graph contexts. -void -FHoudiniPDGManager::ReinitializePDGContext() -{ - int32 NumContexts = 0; - - PDGContextNames.SetNum(MaxNumberOPDGContexts); - PDGContextIDs.SetNum(MaxNumberOPDGContexts); - - if(HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContexts( - FHoudiniEngine::Get().GetSession(), - &NumContexts, PDGContextNames.GetData(), PDGContextIDs.GetData(), MaxNumberOPDGContexts) || NumContexts <= 0) - { - PDGContextNames.SetNum(0); - PDGContextIDs.SetNum(0); - return; - } - - if(PDGContextIDs.Num() != NumContexts) - PDGContextIDs.SetNum(NumContexts); - - if (PDGContextNames.Num() != NumContexts) - PDGContextNames.SetNum(NumContexts); -} - -// Process a PDG event. Notify the relevant PDGAssetLink object. -void -FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo) -{ - UHoudiniPDGAssetLink* PDGAssetLink = nullptr; - UTOPNode* TOPNode = nullptr; - - HAPI_PDG_EventType EventType = (HAPI_PDG_EventType)EventInfo.eventType; - HAPI_PDG_WorkitemState CurrentWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.currentState; - HAPI_PDG_WorkitemState LastWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.lastState; - - // Debug: get the HAPI_PDG_EventType as a string - const FString EventName = FHoudiniEngineUtils::HapiGetEventTypeAsString(EventType); - const FString CurrentWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(CurrentWorkItemState); - const FString LastWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(LastWorkItemState); - - if(!GetTOPAssetLinkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNode) - || PDGAssetLink == nullptr || PDGAssetLink->IsPendingKill() - || TOPNode == nullptr || TOPNode->IsPendingKill() - || TOPNode->NodeId != EventInfo.nodeId) - { - - HOUDINI_LOG_WARNING(TEXT("[ProcessPDGEvent]: Could not find matching TOPNode for event %s, workitem id %d, node id %d"), *EventName, EventInfo.workitemId, EventInfo.nodeId); - return; - } - - HOUDINI_PDG_MESSAGE( - TEXT("[ProcessPDGEvent]: TOPNode: %s, WorkItem ID: %d, Event Type: %s, Current State: %s, Last State %s"), - *(TOPNode->NodePath), EventInfo.workitemId, *EventName, *CurrentWorkitemStateName, *LastWorkitemStateName); - - FLinearColor MsgColor = FLinearColor::White; - - bool bUpdatePDGNodeState = false; - switch (EventType) - { - case HAPI_PDG_EVENT_NULL: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::None); - break; - - case HAPI_PDG_EVENT_NODE_CLEAR: - NotifyTOPNodePDGStateClear(PDGAssetLink, TOPNode); - break; - - case HAPI_PDG_EVENT_WORKITEM_ADD: - CreateOrRelinkWorkItem(TOPNode, InContextID, EventInfo.workitemId); - bUpdatePDGNodeState = true; - NotifyTOPNodeCreatedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - break; - - case HAPI_PDG_EVENT_WORKITEM_REMOVE: - RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, TOPNode); - bUpdatePDGNodeState = true; - NotifyTOPNodeRemovedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - break; - - case HAPI_PDG_EVENT_COOK_WARNING: - MsgColor = FLinearColor::Yellow; - break; - - case HAPI_PDG_EVENT_COOK_ERROR: - MsgColor = FLinearColor::Red; - break; - - case HAPI_PDG_EVENT_COOK_COMPLETE: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); - break; - - case HAPI_PDG_EVENT_DIRTY_START: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtying); - break; - - case HAPI_PDG_EVENT_DIRTY_STOP: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtied); - break; - - case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: - { - // Last states - bUpdatePDGNodeState = true; - if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) - { - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) - { - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) - { - } - else if ( (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE || LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) - && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) - { - // Handled previously cooked WI - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) - { - } - else - { - // TODO: - // unhandled state change - HOUDINI_PDG_WARNING(TEXT("Unhandled PDG state change! Node: %s, WorkItemID %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId); - } - - if (LastWorkItemState == CurrentWorkItemState) - { - // TODO: - // Not a change!! shouldnt happen! - HOUDINI_PDG_WARNING(TEXT("Last state == current state! Node: %s, WorkItemID %d, state %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId, EventInfo.lastState); - } - - // New states - if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) - { - NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNCOOKED) - { - - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_DIRTY) - { - // ClearWorkItemResult(InContextID, EventInfo, *TOPNode); - ClearWorkItemResult(PDGAssetLink, EventInfo.workitemId, TOPNode); - // RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, *TOPNode); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) - { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) - { - NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS - || CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE) - { - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - - // On cook success, handle results - CreateOrRelinkWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) - { - // TODO: on cook failure, get log path? - NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - MsgColor = FLinearColor::Red; - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CANCEL) - { - NotifyTOPNodeCookCancelledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - } - } - break; - - // Unhandled events - case HAPI_PDG_EVENT_DIRTY_ALL: - case HAPI_PDG_EVENT_COOK_START: - case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: - case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: - case HAPI_PDG_EVENT_UI_SELECT: - case HAPI_PDG_EVENT_NODE_CREATE: - case HAPI_PDG_EVENT_NODE_REMOVE: - case HAPI_PDG_EVENT_NODE_RENAME: - case HAPI_PDG_EVENT_NODE_CONNECT: - case HAPI_PDG_EVENT_NODE_DISCONNECT: - case HAPI_PDG_EVENT_WORKITEM_SET_INT: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_STRING: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_FILE: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_RESULT: - case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: - case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: - case HAPI_PDG_EVENT_ALL: - case HAPI_PDG_EVENT_LOG: - case HAPI_PDG_CONTEXT_EVENTS: - break; - } - - if (bUpdatePDGNodeState) - { - // Work item events - EPDGNodeState CurrentTOPNodeState = TOPNode->NodeState; - if (CurrentTOPNodeState == EPDGNodeState::Cooking) - { - if (TOPNode->AreAllWorkItemsComplete()) - { - // At the end of a node/net cook, ensure that the work items are in sync with HAPI and remove any - // work items with invalid ids or that don't exist on the HAPI side anymore. - SyncAndPruneWorkItems(TOPNode); - // Check that all work items are still complete after the sync - if (TOPNode->AreAllWorkItemsComplete()) - { - if (TOPNode->AnyWorkItemsFailed()) - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); - } - else - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); - } - } - } - } - else if (TOPNode->AnyWorkItemsPending()) - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cooking); - } - } - - if (EventInfo.msgSH >= 0) - { - FString EventMsg; - FHoudiniEngineString::ToFString(EventInfo.msgSH, EventMsg); - if (!EventMsg.IsEmpty()) - { - // TODO: Event MSG? - // Somehow update the PDG event msg UI ?? - // Simply log for now... - if (MsgColor == FLinearColor::Red) - { - HOUDINI_LOG_ERROR(TEXT("%s"), *EventMsg); - } - else if (MsgColor == FLinearColor::Yellow) - { - HOUDINI_LOG_WARNING(TEXT("%s"), *EventMsg); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT("%s"), *EventMsg); - } - } - } -} - -void -FHoudiniPDGManager::ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo) -{ - InEventInfo.nodeId = -1; - InEventInfo.workitemId = -1; - InEventInfo.dependencyId = -1; - InEventInfo.currentState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; - InEventInfo.lastState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; - InEventInfo.eventType = HAPI_PDG_EventType::HAPI_PDG_EVENT_NULL; -} - - -bool -FHoudiniPDGManager::GetTOPAssetLinkAndNode( - const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode) -{ - // Returns the PDGAssetLink and FTOPNode data associated with this TOP node ID - OutAssetLink = nullptr; - OutTOPNode = nullptr; - for (TWeakObjectPtr& CurAssetLinkPtr : PDGAssetLinks) - { - if (!CurAssetLinkPtr.IsValid() || CurAssetLinkPtr.IsStale()) - continue; - - UHoudiniPDGAssetLink* CurAssetLink = CurAssetLinkPtr.Get(); - if (!CurAssetLink || CurAssetLink->IsPendingKill()) - continue; - - OutTOPNode = CurAssetLink->GetTOPNode((int32)InNodeID); - - if (OutTOPNode != nullptr) - { - OutAssetLink = CurAssetLink; - return true; - } - } - - return false; -} - -void -FHoudiniPDGManager::SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->NodeState = InPDGState; - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - //Debug.LogFormat("NotifyTOPNodePDGStateClear:: {0}", topNode._nodeName); - InTOPNode->NodeState = EPDGNodeState::None; - InTOPNode->ZeroWorkItemTally(); - InTOPNode->OnDirtyNode(); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ZeroAll"), *(InTOPNode->NodePath)); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); - -} - -void -FHoudiniPDGManager::NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemCreated(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Created WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemRemoved(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Removed WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemCooked(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookedWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemErrored(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemWaiting(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemScheduled(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumScheduledWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemCooking(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookingWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemCookCancelled(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookCancelledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookCancelledWorkItems()); -} - -void -FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // TODO!!! - // Clear all work items' results for the specified TOP node. - // This destroys any loaded results (geometry etc). - //session.LogErrorOverride = false; - InAssetLink->ClearWorkItemResultByID(InWorkItemID, InTOPNode); - // session.LogErrorOverride = true; -} - -void -FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // Clear all of the work item's results for the specified TOP node and also remove the work item itself from - // the TOP node. - InAssetLink->DestroyWorkItemByID(InWorkItemID, InTOPNode); -} - -void -FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // Only update the editor properties if the PDG asset link's Actor is selected - // else, just update the workitemtally - InAssetLink->UpdateWorkItemTally(); - - UHoudiniAssetComponent* HAC = Cast(InAssetLink->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor* ActorOwner = HAC->GetOwner(); - if (ActorOwner != nullptr && ActorOwner->IsSelected()) - { - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - } -} - -void -FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - if (bSuccess) - { - if (InAssetLink->LinkState == EPDGLinkState::Linked) - { - if (InAssetLink->bAutoCook) - { - FHoudiniPDGManager::CookOutput(InAssetLink->GetSelectedTOPNetwork()); - } - } - else - { - UpdatePDGAssetLink(InAssetLink); - } - } - else - { - InAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - } -} - -int32 -FHoudiniPDGManager::CreateOrRelinkWorkItem( - UTOPNode* InTOPNode, - const HAPI_PDG_GraphContextId& InContextID, - HAPI_PDG_WorkitemId InWorkItemID) -{ - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); - return INDEX_NONE; - } - - HAPI_PDG_WorkitemInfo WorkItemInfo; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( - FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); - // TODO? continue? - return INDEX_NONE; - } - - // Try to find the existing WorkItem by ID. - int32 Index = InTOPNode->IndexOfWorkResultByID(InWorkItemID); - if (Index == INDEX_NONE) - { - // Try to find an entry with WorkItemID == INDEX_NONE but where workItemIndex matches. The WorkItemIDs are - // transient, so not saved when the map / asset link is saved. So when loading a map containing the asset - // link all the IDs are INDEX_NONE and the WorkItemIndex is the best way to find an entry to re-use. - const bool bWithInvalidWorkItemID = true; - Index = InTOPNode->IndexOfWorkResultByHAPIIndex(WorkItemInfo.index, bWithInvalidWorkItemID); - if (Index == INDEX_NONE) - { - // If we couldn't find an existing entry, or a stale entry to re-use, create a new one - FTOPWorkResult LocalWorkResult; - LocalWorkResult.WorkItemID = InWorkItemID; - LocalWorkResult.WorkItemIndex = WorkItemInfo.index; - Index = InTOPNode->WorkResult.Add(LocalWorkResult); - } - else - { - InTOPNode->WorkResult[Index].WorkItemID = InWorkItemID; - } - } - - return Index; -} - -bool -FHoudiniPDGManager::CreateOrRelinkWorkItemResult( - UTOPNode* InTOPNode, - const HAPI_PDG_GraphContextId& InContextID, - HAPI_PDG_WorkitemId InWorkItemID, - bool bInLoadResultObjects) -{ - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); - return false; - } - - HAPI_PDG_WorkitemInfo WorkItemInfo; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( - FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); - // TODO? continue? - return false; - } - - // Try to find the existing WorkResult by ID. - FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); - if (!WorkResult) - { - // TODO: This shouldn't really happen, it means a work item finished cooking and generated a result before - // we received an event that the work item was added/generated. - int32 ArrayIndex = CreateOrRelinkWorkItem(InTOPNode, InContextID, InWorkItemID); - if (ArrayIndex != INDEX_NONE) - { - WorkResult = InTOPNode->GetWorkResultByArrayIndex(ArrayIndex); - } - } - - if (!WorkResult) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get or add a FTOPWorkResult for WorkItemID %d for %s"), InWorkItemID, *(InTOPNode->NodeName)); - return false; - } - - TArray NewResultObjects; - TSet ResultIndicesThatWereReused; - if (WorkItemInfo.numResults > 0) - { - TArray ResultInfos; - ResultInfos.SetNum(WorkItemInfo.numResults); - const int32 resultCount = WorkItemInfo.numResults; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemResultInfo( - FHoudiniEngine::Get().GetSession(), - InTOPNode->NodeId, InWorkItemID, ResultInfos.GetData(), resultCount)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d result info for %s"), InWorkItemID, *(InTOPNode->NodeName)); - // TODO? continue? - return false; - } - - FString WorkItemName; - FHoudiniEngineString::ToFString(WorkItemInfo.nameSH, WorkItemName); - - // Load each result geometry - const int32 NumResults = ResultInfos.Num(); - for (int32 Idx = 0; Idx < NumResults; Idx++) - { - const HAPI_PDG_WorkitemResultInfo& ResultInfo = ResultInfos[Idx]; - if (ResultInfo.resultTagSH <= 0 || ResultInfo.resultSH <= 0) - continue; - - FString CurrentTag; - FHoudiniEngineString::ToFString(ResultInfo.resultTagSH, CurrentTag); - if(CurrentTag.IsEmpty() || !CurrentTag.StartsWith(TEXT("file"))) - continue; - - FString CurrentPath = FString(); - FHoudiniEngineString::ToFString(ResultInfo.resultSH, CurrentPath); - - // Construct the name and look for an existing work result, re-use it if found, otherwise create a new one - const FString WorkResultName = FString::Printf( - TEXT("%s_%s_%d_%d"), - *(InTOPNode->ParentName), - *WorkItemName, - WorkItemInfo.index, - Idx); - - // int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) - // { - // return InResultObject.Name == WorkResultName; - // }); - int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([Idx](const FTOPWorkResultObject& InResultObject) - { - return InResultObject.WorkItemResultInfoIndex == Idx; - }); - if (WorkResult->ResultObjects.IsValidIndex(ExistingObjectIndex)) - { - FTOPWorkResultObject& ExistingResultObject = WorkResult->ResultObjects[ExistingObjectIndex]; - - ExistingResultObject.Name = WorkResultName; - ExistingResultObject.FilePath = CurrentPath; - ExistingResultObject.SetAutoBakedSinceLastLoad(false); - if (ExistingResultObject.State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) - { - ExistingResultObject.State = EPDGWorkResultState::ToDelete; - } - else - { - // When the commandlet is not being used, we could leave the outputs in place and have - // translators try to re-use objects/components. When the commandlet is being used, the packages - // are always saved and standalone, so if we want to automatically clean up old results then we - // need to destroy the existing outputs - if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - ExistingResultObject.DestroyResultOutputs(); - - if ((ExistingResultObject.State == EPDGWorkResultState::Loaded || - ExistingResultObject.State == EPDGWorkResultState::ToDelete || - ExistingResultObject.State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) - { - ExistingResultObject.State = EPDGWorkResultState::ToLoad; - } - else - { - ExistingResultObject.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; - } - } - - NewResultObjects.Add(ExistingResultObject); - ResultIndicesThatWereReused.Add(ExistingObjectIndex); - } - else - { - FTOPWorkResultObject ResultObj; - ResultObj.Name = WorkResultName; - ResultObj.FilePath = CurrentPath; - ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; - ResultObj.WorkItemResultInfoIndex = Idx; - ResultObj.SetAutoBakedSinceLastLoad(false); - - NewResultObjects.Add(ResultObj); - } - } - } - // Destroy any old ResultObjects that were not re-used - const int32 NumPrevResultObjects = WorkResult->ResultObjects.Num(); - for (int32 ResultObjectIndex = 0; ResultObjectIndex < NumPrevResultObjects; ++ResultObjectIndex) - { - if (ResultIndicesThatWereReused.Contains(ResultObjectIndex)) - continue; - FTOPWorkResultObject& ResultObject = WorkResult->ResultObjects[ResultObjectIndex]; - ResultObject.DestroyResultOutputsAndRemoveOutputActor(); - } - WorkResult->ResultObjects = NewResultObjects; - - return true; -} - -int32 -FHoudiniPDGManager::SyncAndPruneWorkItems(UTOPNode* InTOPNode) -{ - TSet WorkItemIDSet; - TArray WorkItemIDs; - int NumWorkItems = -1; - - if (!IsValid(InTOPNode)) - return -1; - - HAPI_Session const * const HAPISession = FHoudiniEngine::Get().GetSession(); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNumWorkitems(HAPISession, InTOPNode->NodeId, &NumWorkItems)) - { - HOUDINI_LOG_WARNING(TEXT("GetNumWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); - return -1; - } - - WorkItemIDs.SetNum(NumWorkItems); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitems(HAPISession, InTOPNode->NodeId, WorkItemIDs.GetData(), NumWorkItems)) - { - HOUDINI_LOG_WARNING(TEXT("GetWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); - return -1; - } - - HAPI_PDG_GraphContextId ContextId; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId(HAPISession, InTOPNode->NodeId, &ContextId)) - { - HOUDINI_LOG_WARNING(TEXT("GetPDGGraphContextId call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); - return -1; - } - - for (const HAPI_PDG_WorkitemId& WorkItemID : WorkItemIDs) - { - WorkItemIDSet.Add(static_cast(WorkItemID)); - - // If the WorkItemID is not present in FTOPWorkResult array, sync from HAPI - if (InTOPNode->GetWorkResultByID(WorkItemID) == nullptr) - { - CreateOrRelinkWorkItemResult(InTOPNode, ContextId, WorkItemID); - InTOPNode->OnWorkItemCreated(WorkItemID); - } - } - - // TODO: refactor functions that access the TOPNode's array and properties directly to rather be functions on the - // UTOPNode and make access to these arrays protected/private - - // Remove any work result entries with invalid IDs or where the WorkItemID is not in the set of ids returned by - // HAPI (only if we could get the IDs from HAPI). - int32 NumRemoved = 0; - const int32 NumWorkItemsInArray = InTOPNode->WorkResult.Num(); - for (int32 Index = NumWorkItemsInArray - 1; Index >= 0; --Index) - { - FTOPWorkResult& WorkResult = InTOPNode->WorkResult[Index]; - if (WorkResult.WorkItemID == INDEX_NONE || !WorkItemIDSet.Contains(WorkResult.WorkItemID)) - { - HOUDINI_PDG_WARNING( - TEXT("Pruning a FTOPWorkResult entry from TOP Node %d, WorkItemID %d, WorkItemIndex %d, Array Index %d"), - InTOPNode->NodeId, WorkResult.WorkItemID, WorkResult.WorkItemIndex, Index); - WorkResult.ClearAndDestroyResultObjects(); - InTOPNode->WorkResult.RemoveAt(Index); - InTOPNode->OnWorkItemRemoved(WorkResult.WorkItemID); - NumRemoved++; - } - } - - return NumRemoved; -} - -void -FHoudiniPDGManager::ProcessWorkItemResults() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::ProcessWorkItemResults); - - const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); - for (auto& CurrentPDGAssetLink : PDGAssetLinks) - { - // Iterate through all PDG Asset Link - UHoudiniPDGAssetLink* AssetLink = CurrentPDGAssetLink.Get(); - if (!AssetLink) - continue; - - // Set up package parameters to: - // Cook to temp houdini engine directory - // and if the PDG asset link is associated with a Houdini Asset Component (HAC): - // set the outer package to the HAC - // set the HoudiniAssetName according to the HAC - // set the ComponentGUID according to the HAC - // otherwise we set the outer to the asset link's parent and leave naming and GUID blank - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - // AActor* ParentActor = nullptr; - UObject* AssetLinkParent = AssetLink->GetOuter(); - UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; - if (HAC) - { - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - - // ParentActor = HAC->GetOwner(); - } - else - { - PackageParams.OuterPackage = AssetLinkParent ? AssetLinkParent->GetOutermost() : nullptr; - PackageParams.HoudiniAssetName = FString(); - PackageParams.HoudiniAssetActorName = FString(); - // PackageParams.ComponentGUID = HAC->GetComponentGUID(); - - // // Try to find a parent actor - // UObject* Parent = AssetLinkParent; - // while (Parent && !ParentActor) - // { - // ParentActor = Cast(Parent); - // if (!ParentActor) - // Parent = ParentActor->GetOuter(); - // } - } - PackageParams.ObjectName = FString(); - - // UWorld *World = ParentActor ? ParentActor->GetWorld() : AssetLink->GetWorld(); - UWorld *World = AssetLink->GetWorld(); - - // .. All TOP Nets - for (UTOPNetwork* CurrentTOPNet : AssetLink->AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - // .. All TOP Nodes - for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - // ... All WorkResult - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; - CurrentTOPNode->bCachedHaveLoadedWorkResults = false; - for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) - { - // ... All WorkResultObjects - for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) - { - if (CurrentWorkResultObj.State == EPDGWorkResultState::ToLoad) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Loading; - - // Load this WRObj - PackageParams.PDGTOPNetworkName = CurrentTOPNet->NodeName; - PackageParams.PDGTOPNodeName = CurrentTOPNode->NodeName; - PackageParams.PDGWorkItemIndex = CurrentWorkResult.WorkItemIndex; - - if (CommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - { - BGEOCommandletEndpoint->Send(new FHoudiniPDGImportBGEOMessage( - CurrentWorkResultObj.FilePath, - CurrentWorkResultObj.Name, - PackageParams, - CurrentTOPNode->NodeId, - CurrentWorkResult.WorkItemID - ), BGEOCommandletAddress); - } - else - { - if (FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - AssetLink, - CurrentTOPNode, - CurrentWorkResultObj, - PackageParams)) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Loaded; - CurrentWorkResultObj.SetAutoBakedSinceLastLoad(false); - CurrentTOPNode->bCachedHaveLoadedWorkResults = true; - - // Broadcast that we have loaded the work result object to those interested - AssetLink->OnWorkResultObjectLoaded.Broadcast( - AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemIndex, - CurrentWorkResultObj.WorkItemResultInfoIndex); - } - else - { - CurrentWorkResultObj.State = EPDGWorkResultState::None; - } - } - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::Loaded) - { - // If the work item result obj is in the "Loaded" state, confirm that the output actor - // is still valid (the user could have manually deleted the output - if (!IsValid(CurrentWorkResultObj.GetOutputActorOwner().GetOutputActor())) - { - // If the output actor is invalid, set the state to ToDelete to complete the - // unload/deletion process - CurrentWorkResultObj.State = EPDGWorkResultState::ToDelete; - } - else - { - CurrentTOPNode->bCachedHaveLoadedWorkResults = true; - } - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::ToDelete) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Deleting; - - // Delete and clean up that WRObj - CurrentWorkResultObj.DestroyResultOutputs(); - CurrentWorkResultObj.GetOutputActorOwner().DestroyOutputActor(); - CurrentWorkResultObj.State = EPDGWorkResultState::Deleted; - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::Deleted) - { - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::NotLoaded) - { - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - } - } - } - } - } -} - -void FHoudiniPDGManager::HandleImportBGEODiscoverMessage( - const FHoudiniPDGImportBGEODiscoverMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_DISPLAY(TEXT("Received Discover from %s"), *InContext->GetSender().ToString()); - // Ignore any discover acks received if we already have a valid local address - // for the commandlet - if (BGEOCommandletAddress.IsValid()) - return; - - if (BGEOCommandletProcHandle.IsValid() && InMessage.CommandletGuid.IsValid() && BGEOCommandletGuid == InMessage.CommandletGuid) - { - BGEOCommandletAddress = InContext->GetSender(); - } -} - -void FHoudiniPDGManager::HandleImportBGEOResultMessage( - const FHoudiniPDGImportBGEOResultMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_MESSAGE(TEXT("Received BGEO import result message")); - if (InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_Success || InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_PartialSuccess) - { - FHoudiniPackageParams PackageParams; - InMessage.PopulatePackageParams(PackageParams); - - // Find asset link and work result object - UHoudiniPDGAssetLink *AssetLink = nullptr; - UTOPNode *TOPNode = nullptr; - if (!GetTOPAssetLinkAndNode(InMessage.TOPNodeId, AssetLink, TOPNode) || - !IsValid(AssetLink) || !IsValid(TOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP node with id %d, aborting output object creation."), InMessage.TOPNodeId); - return; - } - - FTOPWorkResult* WorkResult = AssetLink->GetWorkResultByID(InMessage.WorkItemId, TOPNode); - if (WorkResult == nullptr) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result with id %d, aborting output object creation."), InMessage.WorkItemId); - return; - } - const FString& WorkResultObjectName = InMessage.Name; - FTOPWorkResultObject* WorkResultObject = WorkResult->ResultObjects.FindByPredicate( - [&WorkResultObjectName](const FTOPWorkResultObject& WorkResultObject) - { - return WorkResultObject.Name == WorkResultObjectName; - } - ); - if (WorkResultObject == nullptr) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result object with name %s, aborting output object creation."), *InMessage.Name); - return; - } - - if (WorkResultObject->State != EPDGWorkResultState::Loading) - { - HOUDINI_LOG_WARNING(TEXT("TOP work result object (%s) not in Loading state, aborting output object creation."), *InMessage.Name); - return; - } - - // Set package params outer - UObject* AssetLinkParent = AssetLink->GetOuter(); - UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; - if (HAC) - { - PackageParams.OuterPackage = HAC->GetComponentLevel(); - } - else - { - PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); - } - - // Construct UHoudiniOutputs - bool bHasUnsupportedOutputs = false; - TArray NewOutputs; - TMap InstancedOutputPartData; - NewOutputs.Reserve(InMessage.Outputs.Num()); - for (const FHoudiniPDGImportNodeOutput& Output : InMessage.Outputs) - { - UHoudiniOutput* NewOutput = NewObject( - AssetLink, - UHoudiniOutput::StaticClass(), - NAME_None,//FName(*OutputName), - RF_NoFlags); - NewOutputs.Add(NewOutput); - const int32 NumHGPO = Output.HoudiniGeoPartObjects.Num(); - for (int32 Index = 0; Index < NumHGPO; ++Index) - { - const FHoudiniGeoPartObject& HGPO = Output.HoudiniGeoPartObjects[Index]; - NewOutput->AddNewHGPO(HGPO); - - if (Output.InstancedOutputPartData.IsValidIndex(Index)) - { - FHoudiniOutputObjectIdentifier Identifier; - Identifier.ObjectId = HGPO.ObjectId; - Identifier.GeoId = HGPO.GeoId; - Identifier.PartId = HGPO.PartId; - Identifier.PartName = HGPO.PartName; - FHoudiniInstancedOutputPartData InstancedPartData = Output.InstancedOutputPartData[Index]; - InstancedPartData.BuildOriginalInstancedTransformsAndObjectArrays(); - InstancedOutputPartData.Add(Identifier, InstancedPartData); - } - } - const int32 NumObjects = Output.OutputObjects.Num(); - for (int32 Index = 0; Index < NumObjects; ++Index) - { - const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; - FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; - - const FString& FullPackagePath = ImportOutputObject.PackagePath; - FString PackagePath; - FString PackageName; - const bool bDidSplit = FullPackagePath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); - if (!bDidSplit) - PackagePath = FullPackagePath; - - FHoudiniOutputObject OutputObject; - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!IsValid(Package)) - { - // Editor might have picked up the package yet, try to load it - Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); - } - if (IsValid(Package)) - { - OutputObject.OutputObject = FindObject(Package, *PackageName); - } - Identifier.bLoaded = true; - NewOutput->GetOutputObjects().Add(Identifier, OutputObject); - } - NewOutput->UpdateOutputType(); - const EHoudiniOutputType OutputType = NewOutput->GetType(); - if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) - { - bHasUnsupportedOutputs = true; - } - } - - bool bSuccess = true; - if (bHasUnsupportedOutputs) - { - HOUDINI_LOG_MESSAGE(TEXT("Processing output types not supported by commandlet for %s"), *InMessage.Name); - bSuccess = FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - AssetLink, TOPNode, *WorkResultObject, PackageParams, - { - EHoudiniOutputType::Landscape, - EHoudiniOutputType::Curve, - EHoudiniOutputType::Skeletal - } - ); - - if (bSuccess) - { - // Clear/remove the outputs on WorkResultObject that are supported by the commandlet, since we - // are going to replace them with NewOutputs now - TArray& CurrentOutputs = WorkResultObject->GetResultOutputs(); - const int32 NumCurrentOutputs = CurrentOutputs.Num(); - for (int32 Index = 0; Index < NumCurrentOutputs; ++Index) - { - UHoudiniOutput* CurOutput = CurrentOutputs[Index]; - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) - { - // Was created in editor, override the dummy one in NewOutputs with CurOutput - if (NewOutputs.IsValidIndex(Index)) - { - if (OutputType == NewOutputs[Index]->GetType()) - { - UHoudiniOutput* TempOutput = NewOutputs[Index]; - FHoudiniOutputTranslator::ClearOutput(TempOutput); - NewOutputs[Index] = CurOutput; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Unexpected commandlet output type at index %d!"), Index); - } - } - else - { - HOUDINI_LOG_ERROR(TEXT("Expected output index %d from commandlet to be exist!"), Index); - } - } - } - } - } - - if (bSuccess && FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( - AssetLink, - TOPNode, - *WorkResultObject, - PackageParams, - NewOutputs, - {EHoudiniOutputType::Mesh, EHoudiniOutputType::Instancer}, - &InstancedOutputPartData)) - { - const int32 NumOutputs = NewOutputs.Num(); - for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex) - { - UHoudiniOutput *NewOutput = NewOutputs[OutputIndex]; - - if (NewOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - const FHoudiniPDGImportNodeOutput& Output = InMessage.Outputs[OutputIndex]; - int32 NumObjects = Output.OutputObjects.Num(); - for (int32 Index = 0; Index < NumObjects; ++Index) - { - const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; - FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; - FHoudiniOutputObject *OutputObject = NewOutput->GetOutputObjects().Find(Identifier); - if (OutputObject) - { - if (IsValid(OutputObject->OutputComponent)) - { - // Update generic property attributes - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( - OutputObject->OutputComponent, - ImportOutputObject.GenericAttributes.PropertyAttributes); - } - - // Copy cached attributes - OutputObject->CachedAttributes.Append(ImportOutputObject.CachedAttributes); - } - } - } - } - else - { - bSuccess = false; - } - - if (bSuccess) - { - WorkResultObject->State = EPDGWorkResultState::Loaded; - WorkResultObject->SetAutoBakedSinceLastLoad(false); - HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); - // Broadcast that we have loaded the work result object to those interested - AssetLink->OnWorkResultObjectLoaded.Broadcast( - AssetLink, TOPNode, WorkResult->WorkItemIndex, WorkResultObject->WorkItemResultInfoIndex); - } - else - { - WorkResultObject->State = EPDGWorkResultState::None; - HOUDINI_LOG_WARNING(TEXT("Failed to process loaded assets for %s"), *InMessage.Name); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Commandlet failed to import bgeo for %s"), *InMessage.Name); - } -} - -bool FHoudiniPDGManager::CreateBGEOCommandletAndEndpoint() -{ - if (!BGEOCommandletEndpoint.IsValid()) - { - BGEOCommandletAddress.Invalidate(); - BGEOCommandletEndpoint = FMessageEndpoint::Builder(TEXT("Houdini BGEO Commandlet")) - .Handling(this, &FHoudiniPDGManager::HandleImportBGEOResultMessage) - .Handling(this, &FHoudiniPDGManager::HandleImportBGEODiscoverMessage) - .ReceivingOnThread(ENamedThreads::GameThread); - - if (!BGEOCommandletEndpoint.IsValid()) - { - HOUDINI_LOG_WARNING(TEXT("Could not set up messaging end point for BGEO commandlet")); - return false; - } - - BGEOCommandletEndpoint->Subscribe(); - } - - if (!BGEOCommandletProcHandle.IsValid() || !FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - { - // Start the bgeo commandlet - static const FString BGEOCommandletName = TEXT("HoudiniGeoImport"); - BGEOCommandletGuid = FGuid::NewGuid(); - BGEOCommandletAddress.Invalidate(); - - // Get the absolute path to the project file, if known, otherwise get - // the project name. For the path: quote it for the command line. - IFileManager& FileManager = IFileManager::Get(); - FString ProjectPathOrName = FApp::GetProjectName(); - if (FPaths::IsProjectFilePathSet()) - { - const FString ProjectPath = FPaths::GetProjectFilePath(); - if (!ProjectPath.IsEmpty()) - { - ProjectPathOrName = FString::Printf( - TEXT("\"%s\""), - *FileManager.ConvertToAbsolutePathForExternalAppForRead(*ProjectPath) - ); - } - } - - if (ProjectPathOrName.IsEmpty()) - return false; - - // Get the executable path for the app/editor - FString ExePath = FPlatformProcess::GenerateApplicationPath(FApp::GetName(), FApp::GetBuildConfiguration()); - if (!ExePath.IsEmpty()) - ExePath = FileManager.ConvertToAbsolutePathForExternalAppForRead(*ExePath); - - if (ExePath.IsEmpty()) - return false; - - const FString CommandLineParameters = FString::Printf( - TEXT("%s -messaging -run=%s -guid=%s -listen=%s -managerpid=%d"), - *ProjectPathOrName, - *BGEOCommandletName, - *BGEOCommandletGuid.ToString(), - *BGEOCommandletEndpoint->GetAddress().ToString(), - FPlatformProcess::GetCurrentProcessId()); - - BGEOCommandletProcHandle = FPlatformProcess::CreateProc( - *ExePath, - *CommandLineParameters, - false, - true, - false, - &BGEOCommandletProcessId, - 0, - NULL, - NULL); - if (!BGEOCommandletProcHandle.IsValid()) - { - return false; - } - } - - return true; -} - -void FHoudiniPDGManager::StopBGEOCommandletAndEndpoint() -{ - BGEOCommandletEndpoint.Reset(); - BGEOCommandletAddress.Invalidate(); - BGEOCommandletGuid.Invalidate(); - - if (BGEOCommandletProcHandle.IsValid() && FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - { - FPlatformProcess::TerminateProc(BGEOCommandletProcHandle, true); - if (BGEOCommandletProcHandle.IsValid()) - { - FPlatformProcess::WaitForProc(BGEOCommandletProcHandle); - FPlatformProcess::CloseProc(BGEOCommandletProcHandle); - } - } -} - -EHoudiniBGEOCommandletStatus FHoudiniPDGManager::UpdateAndGetBGEOCommandletStatus() -{ - if (BGEOCommandletProcHandle.IsValid()) - { - if (!FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Crashed; - else if (BGEOCommandletAddress.IsValid()) - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Connected; - else - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Running; - } - else - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::NotStarted; - - return BGEOCommandletStatus; -} - - -bool -FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) -{ - if (InAssetId < 0) - return false; - - // Get all the network nodes within the asset, recursively. - // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type - int32 NetworkNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); - - if (NetworkNodeCount <= 0) - return false; - - TArray AllNetworkNodeIDs; - AllNetworkNodeIDs.SetNum(NetworkNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); - - // There is currently no way to only get non bypassed nodes via HAPI - // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... - TArray AllBypassedTOPNetNodeIDs; - { - int32 BypassedTOPNetNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); - - if (BypassedTOPNetNodeCount > 0) - { - // Get the list of all bypassed TOP Net... - AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); - - // ... and remove them from the network list - for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) - AllNetworkNodeIDs.RemoveAt(Idx); - } - } - } - - // For each Network we found earlier, only consider those with TOP child nodes - // If we find TOP nodes in a valid network, then consider this HDA a PDG HDA - for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) - { - if (CurrentNodeId < 0) - { - continue; - } - - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) - { - continue; - } - - // Skip non TOP or SOP networks - if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP - && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) - { - continue; - } - - // Get the list of all TOP nodes within the current network (ignoring schedulers) - int32 TOPNodeCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) - { - continue; - } - - // We found valid TOP Nodes, this is a PDG HDA - if (TOPNodeCount > 0) - return true; - } - - // No valid TOP node found in SOP/TOP Networks, this is not a PDG HDA - return false; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGManager.h" + +#include "Modules/ModuleManager.h" +#include "MessageEndpointBuilder.h" +#include "HAL/FileManager.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPackageParams.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniPDGTranslator.h" +#include "HoudiniPDGImporterMessages.h" + +#include "HAPI/HAPI_Common.h" + +HOUDINI_PDG_DEFINE_LOG_CATEGORY(); + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniPDGManager::FHoudiniPDGManager() +{ +} + +FHoudiniPDGManager::~FHoudiniPDGManager() +{ +} + +bool +FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) +{ + if (!InHAC || InHAC->IsPendingKill()) + return false; + + int32 AssetId = InHAC->GetAssetId(); + if (AssetId < 0) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) + return false; + + // Create a new PDG Asset Link Object + bool bRegisterPDGAssetLink = false; + UHoudiniPDGAssetLink* PDGAssetLink = InHAC->GetPDGAssetLink(); + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + { + PDGAssetLink = NewObject(InHAC, UHoudiniPDGAssetLink::StaticClass(), NAME_None, RF_Transactional); + bRegisterPDGAssetLink = true; + } + + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + PDGAssetLink->AssetID = AssetId; + + // Get the HDA's info + HAPI_NodeInfo AssetInfo; + FHoudiniApi::NodeInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, &AssetInfo), false); + + // Get the node path + FString AssetNodePath; + FHoudiniEngineString::ToFString(AssetInfo.internalNodePathSH, PDGAssetLink->AssetNodePath); + + // Get the node name + FString AssetName; + FHoudiniEngineString::ToFString(AssetInfo.nameSH, PDGAssetLink->AssetName); + + const bool bZeroWorkItemTallys = true; + if (!FHoudiniPDGManager::PopulateTOPNetworks(PDGAssetLink, bZeroWorkItemTallys)) + { + // We couldn't find any valid TOPNet/TOPNode, this is not a PDG Asset + // Make sure the HDA doesn't have a PDGAssetLink + InHAC->SetPDGAssetLink(nullptr); + return false; + } + + // If the PDG asset link comes from a loaded asset, we also need to register it + if (InHAC->HasBeenLoaded()) + { + bRegisterPDGAssetLink = true; + } + + // We have found valid TOPNetworks and TOPNodes, + // This is a PDG HDA, so add the AssetLink to it + PDGAssetLink->LinkState = EPDGLinkState::Linked; + + if (PDGAssetLink->SelectedTOPNetworkIndex < 0) + PDGAssetLink->SelectedTOPNetworkIndex = 0; + + InHAC->SetPDGAssetLink(PDGAssetLink); + + if (bRegisterPDGAssetLink) + { + // Register this PDG Asset Link to the PDG Manager + TWeakObjectPtr AssetLinkPtr(PDGAssetLink); + PDGAssetLinks.Add(AssetLinkPtr); + } + + // If the commandlet is enabled, check if we have started and established communication with the commandlet yet + // if not, try to start the commandlet + bool bCommandletIsEnabled = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (IsValid(HoudiniRuntimeSettings)) + { + bCommandletIsEnabled = HoudiniRuntimeSettings->bPDGAsyncCommandletImportEnabled; + } + + if (bCommandletIsEnabled) + { + const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); + if (CommandletStatus == EHoudiniBGEOCommandletStatus::NotStarted && bCommandletIsEnabled) + { + CreateBGEOCommandletAndEndpoint(); + } + } + + return true; +} + +bool +FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) +{ + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + // If the PDG Asset link is inactive, indicate that our HDA must be instantiated + if (PDGAssetLink->LinkState == EPDGLinkState::Inactive) + { + UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); + if(!ParentHAC) + { + // No valid parent HAC, error! + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("No valid Houdini Asset Component parent for PDG Asset Link!")); + } + else if (ParentHAC && ParentHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) + { + PDGAssetLink->LinkState = EPDGLinkState::Linking; + ParentHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + } + else + { + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("Unable to link the PDG Asset link! Try to rebuild the HDA.")); + } + } + + if (PDGAssetLink->LinkState == EPDGLinkState::Linking) + { + return true; + } + + if (PDGAssetLink->LinkState != EPDGLinkState::Linked) + { + UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); + int32 AssetId = ParentHAC->GetAssetId(); + if (AssetId < 0) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) + return false; + + PDGAssetLink->AssetID = AssetId; + } + + if(!PopulateTOPNetworks(PDGAssetLink)) + { + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("Failed to populte the PDG Asset Link.")); + return false; + } + + return true; +} + + +bool +FHoudiniPDGManager::PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys) +{ + // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + // Get all the network nodes within the asset, recursively. + // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type + int32 NetworkNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); + + if (NetworkNodeCount <= 0) + return false; + + TArray AllNetworkNodeIDs; + AllNetworkNodeIDs.SetNum(NetworkNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); + + // There is currently no way to only get non bypassed nodes via HAPI + // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... + TArray AllBypassedTOPNetNodeIDs; + { + int32 BypassedTOPNetNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); + + if (BypassedTOPNetNodeCount > 0) + { + // Get the list of all bypassed TOP Net... + AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); + + // ... and remove them from the network list + for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) + AllNetworkNodeIDs.RemoveAt(Idx); + } + } + } + + + // For each Network we found earlier, only add those with TOP child nodes + // Therefore guaranteeing that we only add TOP networks + TArray AllTOPNetworks; + for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) + { + if (CurrentNodeId < 0) + { + continue; + } + + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) + { + continue; + } + + // Skip non TOP or SOP networks + if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP + && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) + { + continue; + } + + // Check that this TOP Net is not nested in another TOP Net... + // This will happen with ROP Geometry TOPs for example... + bool bIsNestedInTOPNet = false; + HAPI_NodeId CurrentParentId = CurrentNodeInfo.parentId; + while (CurrentParentId > 0) + { + if (AllNetworkNodeIDs.Contains(CurrentParentId)) + { + bIsNestedInTOPNet = true; + break; + } + + if(AllBypassedTOPNetNodeIDs.Contains(CurrentParentId)) + { + bIsNestedInTOPNet = true; + break; + } + + HAPI_NodeInfo ParentNodeInfo; + FHoudiniApi::NodeInfo_Init(&ParentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentParentId, &ParentNodeInfo)) + { + break; + } + + // Get our parent's parent + CurrentParentId = ParentNodeInfo.parentId; + } + + // Skip nested TOP nets + if (bIsNestedInTOPNet) + continue; + + // Get the list of all TOP nodes within the current network (ignoring schedulers) + int32 TOPNodeCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) + { + continue; + } + + TArray AllTOPNodeIDs; + AllTOPNodeIDs.SetNum(TOPNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, AllTOPNodeIDs.GetData(), TOPNodeCount), false); + + // Skip networks without TOP nodes + if (AllTOPNodeIDs.Num() <= 0) + { + continue; + } + + // Since there is currently no way to get only non-bypassed nodes via HAPI + // we need to get a list of all the bypassed top nodes to remove them from the previous list + { + int32 BypassedTOPNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_TOP_NONSCHEDULER | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNodeCount), false); + + if (BypassedTOPNodeCount > 0) + { + // Get the list of all bypassed TOP Nodes... + TArray AllBypassedTOPNodes; + AllBypassedTOPNodes.SetNum(BypassedTOPNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + AllBypassedTOPNodes.GetData(), BypassedTOPNodeCount), false); + + // ... and remove them from the top node list + for (int32 Idx = AllTOPNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNodes.Contains(AllTOPNodeIDs[Idx])) + AllTOPNodeIDs.RemoveAt(Idx); + } + } + } + + // TODO: + // Apply the show and output filter on that node + bool bShow = true; + + // Get the node path + FString CurrentNodePath; + FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, PDGAssetLink->AssetID, CurrentNodePath); + + // Get the node name + FString CurrentNodeName; + FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, CurrentNodeName); + + UTOPNetwork* CurrentTOPNetwork = nullptr; + int32 FoundTOPNetIndex = INDEX_NONE; + UTOPNetwork* FoundTOPNet = PDGAssetLink->GetTOPNetworkByNodePath(CurrentNodeName, PDGAssetLink->AllTOPNetworks, FoundTOPNetIndex); + if (IsValid(FoundTOPNet)) + { + // Reuse the existing corresponding TOP NET + CurrentTOPNetwork = FoundTOPNet; + PDGAssetLink->AllTOPNetworks.RemoveAt(FoundTOPNetIndex); + } + else + { + // Create a new instance for the TOP NET + CurrentTOPNetwork = NewObject(PDGAssetLink, UTOPNetwork::StaticClass(), NAME_None, RF_Transactional); + } + + // Update the TOP NET + CurrentTOPNetwork->NodeId = CurrentNodeId; + CurrentTOPNetwork->NodeName = CurrentNodeName; + CurrentTOPNetwork->NodePath = CurrentNodePath; + CurrentTOPNetwork->ParentName = PDGAssetLink->AssetName; + CurrentTOPNetwork->bShowResults = bShow; + + // Only add network that have valid TOP Nodes + if (PopulateTOPNodes(AllTOPNodeIDs, CurrentTOPNetwork, PDGAssetLink, bInZeroWorkItemTallys)) + { + // See if we need to select a new TOP node + bool bReselectValidTOP = false; + if (CurrentTOPNetwork->SelectedTOPIndex < 0) + bReselectValidTOP = true; + else if (!CurrentTOPNetwork->AllTOPNodes.IsValidIndex(CurrentTOPNetwork->SelectedTOPIndex)) + bReselectValidTOP = true; + else if (!IsValid(CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]) || + CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]->bHidden) + bReselectValidTOP = true; + + if (bReselectValidTOP) + { + // Select the first valid TOP node (not hidden) + for (int Idx = 0; Idx < CurrentTOPNetwork->AllTOPNodes.Num(); Idx++) + { + UTOPNode *TOPNode = CurrentTOPNetwork->AllTOPNodes[Idx]; + if (!IsValid(TOPNode) || TOPNode->bHidden) + continue; + + CurrentTOPNetwork->SelectedTOPIndex = Idx; + break; + } + } + + AllTOPNetworks.Add(CurrentTOPNetwork); + } + } + + // Clear previous TOP networks, nodes and generated data + for (UTOPNetwork* CurrentTOPNet : PDGAssetLink->AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + PDGAssetLink->ClearTOPNetworkWorkItemResults(CurrentTOPNet); + } + //PDGAssetLink->ClearAllTOPData(); + PDGAssetLink->AllTOPNetworks = AllTOPNetworks; + + return (AllTOPNetworks.Num() > 0); +} + + +bool +FHoudiniPDGManager::PopulateTOPNodes( + const TArray& InTopNodeIDs, UTOPNetwork* InTOPNetwork, UHoudiniPDGAssetLink* InPDGAssetLink, bool bInZeroWorkItemTallys) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InTOPNetwork)) + return false; + + // + int32 TOPNodeCount = 0; + + // Holds list of found TOP nodes + TArray AllTOPNodes; + for(const HAPI_NodeId& CurrentTOPNodeID : InTopNodeIDs) + { + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentTOPNodeID, &CurrentNodeInfo)) + { + continue; + } + + // Increase the number of valid TOP Node + // (before applying the node filter) + TOPNodeCount++; + + // Get the node path + FString NodePath; + FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, InTOPNetwork->NodeId, NodePath); + + // Get the node name + FString NodeName; + FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, NodeName); + + // See if we can find an existing version of this TOPNOde + UTOPNode* CurrentTOPNode = nullptr; + int32 FoundNodeIndex = INDEX_NONE; + UTOPNode* FoundNode = InPDGAssetLink->GetTOPNodeByNodePath(NodePath, InTOPNetwork->AllTOPNodes, FoundNodeIndex); + if (IsValid(FoundNode)) + { + CurrentTOPNode = FoundNode; + InTOPNetwork->AllTOPNodes.RemoveAt(FoundNodeIndex); + + if (bInZeroWorkItemTallys) + { + CurrentTOPNode->ZeroWorkItemTally(); + CurrentTOPNode->NodeState = EPDGNodeState::None; + } + } + else + { + // Didn't find an existing UTOPNode for this node, create a new UTOPNode instance + CurrentTOPNode = NewObject(InPDGAssetLink, UTOPNode::StaticClass(), NAME_None, RF_Transactional); + } + + CurrentTOPNode->NodeId = CurrentTOPNodeID; + CurrentTOPNode->NodeName = NodeName; + CurrentTOPNode->NodePath = NodePath; + CurrentTOPNode->ParentName = InTOPNetwork->ParentName + TEXT("_") + InTOPNetwork->NodeName; + CurrentTOPNode->bHasChildNodes = CurrentNodeInfo.childNodeCount > 0; + + // Filter display/autoload using name + CurrentTOPNode->bHidden = false; + if (InPDGAssetLink->bUseTOPNodeFilter && !InPDGAssetLink->TOPNodeFilter.IsEmpty()) + { + // Only display nodes that matches the filter + if (!NodeName.StartsWith(InPDGAssetLink->TOPNodeFilter)) + CurrentTOPNode->bHidden = true; + } + + // Automatically load results for nodes that match the filter + if (InPDGAssetLink->bUseTOPOutputFilter) + { + bool bAutoLoad = false; + if (InPDGAssetLink->TOPOutputFilter.IsEmpty()) + bAutoLoad = true; + else if (NodeName.StartsWith(InPDGAssetLink->TOPOutputFilter)) + bAutoLoad = true; + + CurrentTOPNode->bAutoLoad = bAutoLoad; + + // Show autoloaded results by default + CurrentTOPNode->SetVisibleInLevel(bAutoLoad); + } + + AllTOPNodes.Add(CurrentTOPNode); + } + + for (UTOPNode* CurTOPNode : InTOPNetwork->AllTOPNodes) + { + if (!IsValid(CurTOPNode)) + continue; + + InPDGAssetLink->ClearTOPNodeWorkItemResults(CurTOPNode); + } + + InTOPNetwork->AllTOPNodes = AllTOPNodes; + + return (TOPNodeCount > 0); +} + + +void +FHoudiniPDGManager::DirtyTOPNode(UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + // Dirty the specified TOP node... + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( + FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, true)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *(InTOPNode->NodeName)); + } + + // ... and clear its work item results. + UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(InTOPNode); +} + +// void +// FHoudiniPDGManager::DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode) +// { +// // Dirty the specified TOP node... +// if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( +// FHoudiniEngine::Get().GetSession(), InTOPNode.NodeId, true)) +// { +// HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *InTOPNode.NodeName); +// } +// } + +void +FHoudiniPDGManager::CookTOPNode(UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + if (InTOPNode->NodeState == EPDGNodeState::Cooking || InTOPNode->AnyWorkItemsPending()) + { + HOUDINI_LOG_WARNING(TEXT("PDG Cook TOP Node - %s is already/still cooking, ignoring 'Cook TOP Node' request."), *(InTOPNode->NodePath)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( + FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, 0, 0)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook TOP Node - Failed to cook %s!"), *(InTOPNode->NodeName)); + } +} + + +void +FHoudiniPDGManager::DirtyAll(UTOPNetwork* InTOPNet) +{ + if (!IsValid(InTOPNet)) + return; + + // Dirty the specified TOP network... + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, true)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Dirty All - Failed to dirty all of %s's TOP nodes!"), *(InTOPNet->NodeName)); + return; + } + + // ... and clear its work item results. + UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(InTOPNet); +} + + +void +FHoudiniPDGManager::CookOutput(UTOPNetwork* InTOPNet) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::CookOutput); + + // Cook the output TOP node of the currently selected TOP network. + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + bool bAlreadyCooking = InTOPNet->AnyWorkItemsPending(); + + if (!bAlreadyCooking) + { + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + int32 PDGState = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( + FHoudiniEngine::Get().GetSession(), GraphContextId, &PDGState)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's PDG state."), *(InTOPNet->NodeName)); + return; + } + bAlreadyCooking = ((HAPI_PDG_State) PDGState == HAPI_PDG_STATE_COOKING); + } + + if (bAlreadyCooking) + { + HOUDINI_LOG_WARNING(TEXT("PDG Cook Output - %s is already/still cooking, ignoring 'Cook Output' request."), *(InTOPNet->NodeName)); + return; + } + + // TODO: ??? + // Cancel all cooks. This is required as otherwise the graph gets into an infinite cook state (bug?) + if(HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, 0, 0)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to cook %s's output!"), *(InTOPNet->NodeName)); + } +} + + +void +FHoudiniPDGManager::PauseCook(UTOPNetwork* InTOPNet) +{ + // Pause the PDG cook of the currently selected TOP network + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::PausePDGCook( + FHoudiniEngine::Get().GetSession(), GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to pause %s!"), *(InTOPNet->NodeName)); + return; + } +} + + +void +FHoudiniPDGManager::CancelCook(UTOPNetwork* InTOPNet) +{ + // Cancel the PDG cook of the currently selected TOP network + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CancelPDGCook( + FHoudiniEngine::Get().GetSession(), GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to cancel cook for %s!"), *(InTOPNet->NodeName)); + return; + } +} + +void +FHoudiniPDGManager::Update() +{ + // Clean up registered PDG Asset Links + for(int32 Idx = PDGAssetLinks.Num() - 1; Idx >= 0; Idx--) + { + TWeakObjectPtr Ptr = PDGAssetLinks[Idx]; + if (!Ptr.IsValid() || Ptr.IsStale()) + { + PDGAssetLinks.RemoveAt(Idx); + continue; + } + + UHoudiniPDGAssetLink* CurPDGAssetLink = PDGAssetLinks[Idx].Get(); + if (!CurPDGAssetLink || CurPDGAssetLink->IsPendingKill()) + { + PDGAssetLinks.RemoveAt(Idx); + continue; + } + } + + // Do nothing if we dont have any valid PDG asset Link + if (PDGAssetLinks.Num() <= 0) + return; + + // Update the PDG contexts and handle all pdg events and work item status updates + UpdatePDGContexts(); + + // Prcoess any workitem result if we have any + ProcessWorkItemResults(); +} + +// Query all the PDG graph context in the current Houdini Engine session. +// Handle PDG events, work item status updates. +// Forward relevant events to PDGAssetLink objects. +void +FHoudiniPDGManager::UpdatePDGContexts() +{ + // Get current PDG graph contexts + ReinitializePDGContext(); + + // Process next set of events for each graph context + if (PDGContextIDs.Num() > 0) + { + // Only initialize event array if not valid, or user resized max size + if(PDGEventInfos.Num() != MaxNumberOfPDGEvents) + PDGEventInfos.SetNum(MaxNumberOfPDGEvents); + + // TODO: member? + //HAPI_PDG_State PDGState; + for(const HAPI_PDG_GraphContextId& CurrentContextID : PDGContextIDs) + { + /* + // TODO: No need to reset events at each tick + int32 PDGStateInt; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( + FHoudiniEngine::Get().GetSession(), CurrentContextID, &PDGStateInt)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get PDG state")); + continue; + } + + PDGState = (HAPI_PDG_State)PDGStateInt; + + for (int32 Idx = 0; Idx < PDGEventInfos.Num(); Idx++) + { + ResetPDGEventInfo(PDGEventInfos[Idx]); + } + */ + + int32 PDGEventCount = 0; + int32 RemainingPDGEventCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGEvents( + FHoudiniEngine::Get().GetSession(), CurrentContextID, PDGEventInfos.GetData(), + MaxNumberOfPDGEvents, &PDGEventCount, &RemainingPDGEventCount)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get PDG events")); + continue; + } + + if (PDGEventCount < 1) + continue; + + for (int32 EventIdx = 0; EventIdx < PDGEventCount; EventIdx++) + { + ProcessPDGEvent(CurrentContextID, PDGEventInfos[EventIdx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("PDG: Tick processed %d events, %d remaining."), PDGEventCount, RemainingPDGEventCount); + } + } + + // Refresh UI if necessary + for (auto CurAssetLink : PDGAssetLinks) + { + UHoudiniPDGAssetLink* AssetLink = CurAssetLink.Get(); + if (AssetLink) + { + if (AssetLink->bNeedsUIRefresh) + { + FHoudiniPDGManager::RefreshPDGAssetLinkUI(AssetLink); + AssetLink->bNeedsUIRefresh = false; + } + else + { + AssetLink->UpdateWorkItemTally(); + } + } + } +} + +// Query the currently active PDG graph contexts in the Houdini Engine session. +// Should be done each time to get latest set of graph contexts. +void +FHoudiniPDGManager::ReinitializePDGContext() +{ + int32 NumContexts = 0; + + PDGContextNames.SetNum(MaxNumberOPDGContexts); + PDGContextIDs.SetNum(MaxNumberOPDGContexts); + + if(HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContexts( + FHoudiniEngine::Get().GetSession(), + &NumContexts, PDGContextNames.GetData(), PDGContextIDs.GetData(), MaxNumberOPDGContexts) || NumContexts <= 0) + { + PDGContextNames.SetNum(0); + PDGContextIDs.SetNum(0); + return; + } + + if(PDGContextIDs.Num() != NumContexts) + PDGContextIDs.SetNum(NumContexts); + + if (PDGContextNames.Num() != NumContexts) + PDGContextNames.SetNum(NumContexts); +} + +// Process a PDG event. Notify the relevant PDGAssetLink object. +void +FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo) +{ + UHoudiniPDGAssetLink* PDGAssetLink = nullptr; + UTOPNetwork* TOPNetwork = nullptr; + UTOPNode* TOPNode = nullptr; + + HAPI_PDG_EventType EventType = (HAPI_PDG_EventType)EventInfo.eventType; + HAPI_PDG_WorkitemState CurrentWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.currentState; + HAPI_PDG_WorkitemState LastWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.lastState; + + // Debug: get the HAPI_PDG_EventType as a string + const FString EventName = FHoudiniEngineUtils::HapiGetEventTypeAsString(EventType); + const FString CurrentWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(CurrentWorkItemState); + const FString LastWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(LastWorkItemState); + + if(!GetTOPAssetLinkNetworkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNetwork, TOPNode) + || PDGAssetLink == nullptr || PDGAssetLink->IsPendingKill() + || TOPNetwork == nullptr || TOPNetwork->IsPendingKill() + || TOPNode == nullptr || TOPNode->IsPendingKill() + || TOPNode->NodeId != EventInfo.nodeId) + { + + HOUDINI_LOG_WARNING(TEXT("[ProcessPDGEvent]: Could not find matching TOPNode for event %s, workitem id %d, node id %d"), *EventName, EventInfo.workitemId, EventInfo.nodeId); + return; + } + + HOUDINI_PDG_MESSAGE( + TEXT("[ProcessPDGEvent]: TOPNode: %s, WorkItem ID: %d, Event Type: %s, Current State: %s, Last State %s"), + *(TOPNode->NodePath), EventInfo.workitemId, *EventName, *CurrentWorkitemStateName, *LastWorkitemStateName); + + FLinearColor MsgColor = FLinearColor::White; + + bool bUpdatePDGNodeState = false; + switch (EventType) + { + case HAPI_PDG_EVENT_NULL: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::None); + break; + + case HAPI_PDG_EVENT_NODE_CLEAR: + NotifyTOPNodePDGStateClear(PDGAssetLink, TOPNode); + break; + + case HAPI_PDG_EVENT_WORKITEM_ADD: + CreateOrRelinkWorkItem(TOPNode, InContextID, EventInfo.workitemId); + bUpdatePDGNodeState = true; + NotifyTOPNodeCreatedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + break; + + case HAPI_PDG_EVENT_WORKITEM_REMOVE: + RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, TOPNode); + bUpdatePDGNodeState = true; + NotifyTOPNodeRemovedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + break; + + case HAPI_PDG_EVENT_COOK_WARNING: + MsgColor = FLinearColor::Yellow; + break; + + case HAPI_PDG_EVENT_COOK_ERROR: + MsgColor = FLinearColor::Red; + break; + + case HAPI_PDG_EVENT_COOK_COMPLETE: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + TOPNode->HandleOnPDGEventCookComplete(); + TOPNetwork->HandleOnPDGEventCookCompleteReceivedByChildNode(PDGAssetLink, TOPNode); + break; + + case HAPI_PDG_EVENT_DIRTY_START: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtying); + break; + + case HAPI_PDG_EVENT_DIRTY_STOP: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtied); + break; + + case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: + { + // Last states + bUpdatePDGNodeState = true; + if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) + { + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) + { + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) + { + } + else if ( (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE || LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) + && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) + { + // Handled previously cooked WI + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) + { + } + else + { + // TODO: + // unhandled state change + HOUDINI_PDG_WARNING(TEXT("Unhandled PDG state change! Node: %s, WorkItemID %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId); + } + + if (LastWorkItemState == CurrentWorkItemState) + { + // TODO: + // Not a change!! shouldnt happen! + HOUDINI_PDG_WARNING(TEXT("Last state == current state! Node: %s, WorkItemID %d, state %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId, EventInfo.lastState); + } + + // New states + if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) + { + NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNCOOKED) + { + + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_DIRTY) + { + // ClearWorkItemResult(InContextID, EventInfo, *TOPNode); + ClearWorkItemResult(PDGAssetLink, EventInfo.workitemId, TOPNode); + // RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, *TOPNode); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) + { + NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) + { + NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS + || CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE) + { + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + + // On cook success, handle results + CreateOrRelinkWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) + { + // TODO: on cook failure, get log path? + NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + MsgColor = FLinearColor::Red; + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CANCEL) + { + NotifyTOPNodeCookCancelledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + } + } + break; + + case HAPI_PDG_EVENT_COOK_START: + TOPNode->HandleOnPDGEventCookStart(); + break; + // Unhandled events + case HAPI_PDG_EVENT_DIRTY_ALL: + case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: + case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: + case HAPI_PDG_EVENT_UI_SELECT: + case HAPI_PDG_EVENT_NODE_CREATE: + case HAPI_PDG_EVENT_NODE_REMOVE: + case HAPI_PDG_EVENT_NODE_RENAME: + case HAPI_PDG_EVENT_NODE_CONNECT: + case HAPI_PDG_EVENT_NODE_DISCONNECT: + case HAPI_PDG_EVENT_WORKITEM_SET_INT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_STRING: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FILE: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_RESULT: + case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: + case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: + case HAPI_PDG_EVENT_ALL: + case HAPI_PDG_EVENT_LOG: + case HAPI_PDG_CONTEXT_EVENTS: + break; + } + + if (bUpdatePDGNodeState) + { + // Work item events + EPDGNodeState CurrentTOPNodeState = TOPNode->NodeState; + if (CurrentTOPNodeState == EPDGNodeState::Cooking) + { + if (TOPNode->AreAllWorkItemsComplete()) + { + // At the end of a node/net cook, ensure that the work items are in sync with HAPI and remove any + // work items with invalid ids or that don't exist on the HAPI side anymore. + SyncAndPruneWorkItems(TOPNode); + // Check that all work items are still complete after the sync + if (TOPNode->AreAllWorkItemsComplete()) + { + if (TOPNode->AnyWorkItemsFailed()) + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); + } + else + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + } + } + } + } + else if (TOPNode->AnyWorkItemsPending()) + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cooking); + } + } + + if (EventInfo.msgSH >= 0) + { + FString EventMsg; + FHoudiniEngineString::ToFString(EventInfo.msgSH, EventMsg); + if (!EventMsg.IsEmpty()) + { + // TODO: Event MSG? + // Somehow update the PDG event msg UI ?? + // Simply log for now... + if (MsgColor == FLinearColor::Red) + { + HOUDINI_LOG_ERROR(TEXT("%s"), *EventMsg); + } + else if (MsgColor == FLinearColor::Yellow) + { + HOUDINI_LOG_WARNING(TEXT("%s"), *EventMsg); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT("%s"), *EventMsg); + } + } + } +} + +void +FHoudiniPDGManager::ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo) +{ + InEventInfo.nodeId = -1; + InEventInfo.workitemId = -1; + InEventInfo.dependencyId = -1; + InEventInfo.currentState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; + InEventInfo.lastState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; + InEventInfo.eventType = HAPI_PDG_EventType::HAPI_PDG_EVENT_NULL; +} + + +bool +FHoudiniPDGManager::GetTOPAssetLinkNetworkAndNode( + const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNetwork*& OutTOPNetwork, UTOPNode*& OutTOPNode) +{ + // Returns the PDGAssetLink and FTOPNode data associated with this TOP node ID + OutAssetLink = nullptr; + OutTOPNetwork = nullptr; + OutTOPNode = nullptr; + for (TWeakObjectPtr& CurAssetLinkPtr : PDGAssetLinks) + { + if (!CurAssetLinkPtr.IsValid() || CurAssetLinkPtr.IsStale()) + continue; + + UHoudiniPDGAssetLink* CurAssetLink = CurAssetLinkPtr.Get(); + if (!CurAssetLink || CurAssetLink->IsPendingKill()) + continue; + + if (CurAssetLink->GetTOPNodeAndNetworkByNodeId((int32)InNodeID, OutTOPNetwork, OutTOPNode)) + { + if (OutTOPNetwork != nullptr && OutTOPNode != nullptr) + { + OutAssetLink = CurAssetLink; + return true; + } + } + } + + OutAssetLink = nullptr; + OutTOPNetwork = nullptr; + OutTOPNode = nullptr; + + return false; +} + +void +FHoudiniPDGManager::SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->NodeState = InPDGState; + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + //Debug.LogFormat("NotifyTOPNodePDGStateClear:: {0}", topNode._nodeName); + InTOPNode->NodeState = EPDGNodeState::None; + InTOPNode->ZeroWorkItemTally(); + InTOPNode->OnDirtyNode(); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ZeroAll"), *(InTOPNode->NodePath)); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); + +} + +void +FHoudiniPDGManager::NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemCreated(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Created WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemRemoved(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Removed WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemCooked(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookedWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemErrored(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemWaiting(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemScheduled(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumScheduledWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemCooking(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookingWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemCookCancelled(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookCancelledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookCancelledWorkItems()); +} + +void +FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // TODO!!! + // Clear all work items' results for the specified TOP node. + // This destroys any loaded results (geometry etc). + //session.LogErrorOverride = false; + InAssetLink->ClearWorkItemResultByID(InWorkItemID, InTOPNode); + // session.LogErrorOverride = true; +} + +void +FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // Clear all of the work item's results for the specified TOP node and also remove the work item itself from + // the TOP node. + InAssetLink->DestroyWorkItemByID(InWorkItemID, InTOPNode); +} + +void +FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // Only update the editor properties if the PDG asset link's Actor is selected + // else, just update the workitemtally + InAssetLink->UpdateWorkItemTally(); + + UHoudiniAssetComponent* HAC = Cast(InAssetLink->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor* ActorOwner = HAC->GetOwner(); + if (ActorOwner != nullptr && ActorOwner->IsSelected()) + { + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + } +} + +void +FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + if (bSuccess) + { + if (InAssetLink->LinkState == EPDGLinkState::Linked) + { + if (InAssetLink->bAutoCook) + { + FHoudiniPDGManager::CookOutput(InAssetLink->GetSelectedTOPNetwork()); + } + } + else + { + UpdatePDGAssetLink(InAssetLink); + } + } + else + { + InAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + } +} + +int32 +FHoudiniPDGManager::CreateOrRelinkWorkItem( + UTOPNode* InTOPNode, + const HAPI_PDG_GraphContextId& InContextID, + HAPI_PDG_WorkitemId InWorkItemID) +{ + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); + return INDEX_NONE; + } + + HAPI_PDG_WorkitemInfo WorkItemInfo; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( + FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return INDEX_NONE; + } + + // Try to find the existing WorkItem by ID. + int32 Index = InTOPNode->IndexOfWorkResultByID(InWorkItemID); + if (Index == INDEX_NONE) + { + // Try to find an entry with WorkItemID == INDEX_NONE but where workItemIndex matches. The WorkItemIDs are + // transient, so not saved when the map / asset link is saved. So when loading a map containing the asset + // link all the IDs are INDEX_NONE and the WorkItemIndex is the best way to find an entry to re-use. + const bool bWithInvalidWorkItemID = true; + Index = InTOPNode->IndexOfWorkResultByHAPIIndex(WorkItemInfo.index, bWithInvalidWorkItemID); + if (Index == INDEX_NONE) + { + // If we couldn't find an existing entry, or a stale entry to re-use, create a new one + FTOPWorkResult LocalWorkResult; + LocalWorkResult.WorkItemID = InWorkItemID; + LocalWorkResult.WorkItemIndex = WorkItemInfo.index; + Index = InTOPNode->WorkResult.Add(LocalWorkResult); + } + else + { + InTOPNode->WorkResult[Index].WorkItemID = InWorkItemID; + } + } + + return Index; +} + +bool +FHoudiniPDGManager::CreateOrRelinkWorkItemResult( + UTOPNode* InTOPNode, + const HAPI_PDG_GraphContextId& InContextID, + HAPI_PDG_WorkitemId InWorkItemID, + bool bInLoadResultObjects) +{ + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); + return false; + } + + HAPI_PDG_WorkitemInfo WorkItemInfo; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( + FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return false; + } + + // Try to find the existing WorkResult by ID. + FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); + if (!WorkResult) + { + // TODO: This shouldn't really happen, it means a work item finished cooking and generated a result before + // we received an event that the work item was added/generated. + int32 ArrayIndex = CreateOrRelinkWorkItem(InTOPNode, InContextID, InWorkItemID); + if (ArrayIndex != INDEX_NONE) + { + WorkResult = InTOPNode->GetWorkResultByArrayIndex(ArrayIndex); + } + } + + if (!WorkResult) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get or add a FTOPWorkResult for WorkItemID %d for %s"), InWorkItemID, *(InTOPNode->NodeName)); + return false; + } + + TArray NewResultObjects; + TSet ResultIndicesThatWereReused; + if (WorkItemInfo.numResults > 0) + { + TArray ResultInfos; + ResultInfos.SetNum(WorkItemInfo.numResults); + const int32 resultCount = WorkItemInfo.numResults; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemResultInfo( + FHoudiniEngine::Get().GetSession(), + InTOPNode->NodeId, InWorkItemID, ResultInfos.GetData(), resultCount)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d result info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return false; + } + + FString WorkItemName; + FHoudiniEngineString::ToFString(WorkItemInfo.nameSH, WorkItemName); + + // Load each result geometry + const int32 NumResults = ResultInfos.Num(); + for (int32 Idx = 0; Idx < NumResults; Idx++) + { + const HAPI_PDG_WorkitemResultInfo& ResultInfo = ResultInfos[Idx]; + if (ResultInfo.resultTagSH <= 0 || ResultInfo.resultSH <= 0) + continue; + + FString CurrentTag; + FHoudiniEngineString::ToFString(ResultInfo.resultTagSH, CurrentTag); + if(CurrentTag.IsEmpty() || !CurrentTag.StartsWith(TEXT("file"))) + continue; + + FString CurrentPath = FString(); + FHoudiniEngineString::ToFString(ResultInfo.resultSH, CurrentPath); + + // Construct the name and look for an existing work result, re-use it if found, otherwise create a new one + const FString WorkResultName = FString::Printf( + TEXT("%s_%s_%d_%d"), + *(InTOPNode->ParentName), + *WorkItemName, + WorkItemInfo.index, + Idx); + + // int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) + // { + // return InResultObject.Name == WorkResultName; + // }); + int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([Idx](const FTOPWorkResultObject& InResultObject) + { + return InResultObject.WorkItemResultInfoIndex == Idx; + }); + if (WorkResult->ResultObjects.IsValidIndex(ExistingObjectIndex)) + { + FTOPWorkResultObject& ExistingResultObject = WorkResult->ResultObjects[ExistingObjectIndex]; + + ExistingResultObject.Name = WorkResultName; + ExistingResultObject.FilePath = CurrentPath; + ExistingResultObject.SetAutoBakedSinceLastLoad(false); + if (ExistingResultObject.State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) + { + ExistingResultObject.State = EPDGWorkResultState::ToDelete; + } + else + { + // When the commandlet is not being used, we could leave the outputs in place and have + // translators try to re-use objects/components. When the commandlet is being used, the packages + // are always saved and standalone, so if we want to automatically clean up old results then we + // need to destroy the existing outputs + if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) + ExistingResultObject.DestroyResultOutputs(); + + if ((ExistingResultObject.State == EPDGWorkResultState::Loaded || + ExistingResultObject.State == EPDGWorkResultState::ToDelete || + ExistingResultObject.State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) + { + ExistingResultObject.State = EPDGWorkResultState::ToLoad; + } + else + { + ExistingResultObject.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + } + } + + NewResultObjects.Add(ExistingResultObject); + ResultIndicesThatWereReused.Add(ExistingObjectIndex); + } + else + { + FTOPWorkResultObject ResultObj; + ResultObj.Name = WorkResultName; + ResultObj.FilePath = CurrentPath; + ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + ResultObj.WorkItemResultInfoIndex = Idx; + ResultObj.SetAutoBakedSinceLastLoad(false); + + NewResultObjects.Add(ResultObj); + } + } + } + // Destroy any old ResultObjects that were not re-used + const int32 NumPrevResultObjects = WorkResult->ResultObjects.Num(); + for (int32 ResultObjectIndex = 0; ResultObjectIndex < NumPrevResultObjects; ++ResultObjectIndex) + { + if (ResultIndicesThatWereReused.Contains(ResultObjectIndex)) + continue; + FTOPWorkResultObject& ResultObject = WorkResult->ResultObjects[ResultObjectIndex]; + ResultObject.DestroyResultOutputsAndRemoveOutputActor(); + } + WorkResult->ResultObjects = NewResultObjects; + + return true; +} + +int32 +FHoudiniPDGManager::SyncAndPruneWorkItems(UTOPNode* InTOPNode) +{ + TSet WorkItemIDSet; + TArray WorkItemIDs; + int NumWorkItems = -1; + + if (!IsValid(InTOPNode)) + return -1; + + HAPI_Session const * const HAPISession = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNumWorkitems(HAPISession, InTOPNode->NodeId, &NumWorkItems)) + { + HOUDINI_LOG_WARNING(TEXT("GetNumWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + WorkItemIDs.SetNum(NumWorkItems); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitems(HAPISession, InTOPNode->NodeId, WorkItemIDs.GetData(), NumWorkItems)) + { + HOUDINI_LOG_WARNING(TEXT("GetWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + HAPI_PDG_GraphContextId ContextId; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId(HAPISession, InTOPNode->NodeId, &ContextId)) + { + HOUDINI_LOG_WARNING(TEXT("GetPDGGraphContextId call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + for (const HAPI_PDG_WorkitemId& WorkItemID : WorkItemIDs) + { + WorkItemIDSet.Add(static_cast(WorkItemID)); + + // If the WorkItemID is not present in FTOPWorkResult array, sync from HAPI + if (InTOPNode->GetWorkResultByID(WorkItemID) == nullptr) + { + CreateOrRelinkWorkItemResult(InTOPNode, ContextId, WorkItemID); + InTOPNode->OnWorkItemCreated(WorkItemID); + } + } + + // TODO: refactor functions that access the TOPNode's array and properties directly to rather be functions on the + // UTOPNode and make access to these arrays protected/private + + // Remove any work result entries with invalid IDs or where the WorkItemID is not in the set of ids returned by + // HAPI (only if we could get the IDs from HAPI). + int32 NumRemoved = 0; + const int32 NumWorkItemsInArray = InTOPNode->WorkResult.Num(); + for (int32 Index = NumWorkItemsInArray - 1; Index >= 0; --Index) + { + FTOPWorkResult& WorkResult = InTOPNode->WorkResult[Index]; + if (WorkResult.WorkItemID == INDEX_NONE || !WorkItemIDSet.Contains(WorkResult.WorkItemID)) + { + HOUDINI_PDG_WARNING( + TEXT("Pruning a FTOPWorkResult entry from TOP Node %d, WorkItemID %d, WorkItemIndex %d, Array Index %d"), + InTOPNode->NodeId, WorkResult.WorkItemID, WorkResult.WorkItemIndex, Index); + WorkResult.ClearAndDestroyResultObjects(); + InTOPNode->WorkResult.RemoveAt(Index); + InTOPNode->OnWorkItemRemoved(WorkResult.WorkItemID); + NumRemoved++; + } + } + + return NumRemoved; +} + +void +FHoudiniPDGManager::ProcessWorkItemResults() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::ProcessWorkItemResults); + + const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); + for (auto& CurrentPDGAssetLink : PDGAssetLinks) + { + // Iterate through all PDG Asset Link + UHoudiniPDGAssetLink* AssetLink = CurrentPDGAssetLink.Get(); + if (!AssetLink) + continue; + + // Set up package parameters to: + // Cook to temp houdini engine directory + // and if the PDG asset link is associated with a Houdini Asset Component (HAC): + // set the outer package to the HAC + // set the HoudiniAssetName according to the HAC + // set the ComponentGUID according to the HAC + // otherwise we set the outer to the asset link's parent and leave naming and GUID blank + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + // AActor* ParentActor = nullptr; + UObject* AssetLinkParent = AssetLink->GetOuter(); + UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; + if (HAC) + { + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + + // ParentActor = HAC->GetOwner(); + } + else + { + PackageParams.OuterPackage = AssetLinkParent ? AssetLinkParent->GetOutermost() : nullptr; + PackageParams.HoudiniAssetName = FString(); + PackageParams.HoudiniAssetActorName = FString(); + // PackageParams.ComponentGUID = HAC->GetComponentGUID(); + + // // Try to find a parent actor + // UObject* Parent = AssetLinkParent; + // while (Parent && !ParentActor) + // { + // ParentActor = Cast(Parent); + // if (!ParentActor) + // Parent = ParentActor->GetOuter(); + // } + } + PackageParams.ObjectName = FString(); + + // UWorld *World = ParentActor ? ParentActor->GetWorld() : AssetLink->GetWorld(); + UWorld *World = AssetLink->GetWorld(); + + // .. All TOP Nets + for (UTOPNetwork* CurrentTOPNet : AssetLink->AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + // .. All TOP Nodes + for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + // ... All WorkResult + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; + CurrentTOPNode->bCachedHaveLoadedWorkResults = false; + for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) + { + // ... All WorkResultObjects + for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) + { + if (CurrentWorkResultObj.State == EPDGWorkResultState::ToLoad) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Loading; + + // Load this WRObj + PackageParams.PDGTOPNetworkName = CurrentTOPNet->NodeName; + PackageParams.PDGTOPNodeName = CurrentTOPNode->NodeName; + PackageParams.PDGWorkItemIndex = CurrentWorkResult.WorkItemIndex; + + if (CommandletStatus == EHoudiniBGEOCommandletStatus::Connected) + { + BGEOCommandletEndpoint->Send(new FHoudiniPDGImportBGEOMessage( + CurrentWorkResultObj.FilePath, + CurrentWorkResultObj.Name, + PackageParams, + CurrentTOPNode->NodeId, + CurrentWorkResult.WorkItemID + ), BGEOCommandletAddress); + } + else + { + if (FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + AssetLink, + CurrentTOPNode, + CurrentWorkResultObj, + PackageParams)) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Loaded; + CurrentWorkResultObj.SetAutoBakedSinceLastLoad(false); + CurrentTOPNode->bCachedHaveLoadedWorkResults = true; + + // Broadcast that we have loaded the work result object to those interested + AssetLink->OnWorkResultObjectLoaded.Broadcast( + AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemIndex, + CurrentWorkResultObj.WorkItemResultInfoIndex); + } + else + { + CurrentWorkResultObj.State = EPDGWorkResultState::None; + } + } + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::Loaded) + { + // If the work item result obj is in the "Loaded" state, confirm that the output actor + // is still valid (the user could have manually deleted the output + if (!IsValid(CurrentWorkResultObj.GetOutputActorOwner().GetOutputActor())) + { + // If the output actor is invalid, set the state to ToDelete to complete the + // unload/deletion process + CurrentWorkResultObj.State = EPDGWorkResultState::ToDelete; + } + else + { + CurrentTOPNode->bCachedHaveLoadedWorkResults = true; + } + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::ToDelete) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Deleting; + + // Delete and clean up that WRObj + CurrentWorkResultObj.DestroyResultOutputs(); + CurrentWorkResultObj.GetOutputActorOwner().DestroyOutputActor(); + CurrentWorkResultObj.State = EPDGWorkResultState::Deleted; + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::Deleted) + { + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::NotLoaded) + { + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + } + } + } + } + } +} + +void FHoudiniPDGManager::HandleImportBGEODiscoverMessage( + const FHoudiniPDGImportBGEODiscoverMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_DISPLAY(TEXT("Received Discover from %s"), *InContext->GetSender().ToString()); + // Ignore any discover acks received if we already have a valid local address + // for the commandlet + if (BGEOCommandletAddress.IsValid()) + return; + + if (BGEOCommandletProcHandle.IsValid() && InMessage.CommandletGuid.IsValid() && BGEOCommandletGuid == InMessage.CommandletGuid) + { + BGEOCommandletAddress = InContext->GetSender(); + } +} + +void FHoudiniPDGManager::HandleImportBGEOResultMessage( + const FHoudiniPDGImportBGEOResultMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_MESSAGE(TEXT("Received BGEO import result message")); + if (InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_Success || InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_PartialSuccess) + { + FHoudiniPackageParams PackageParams; + InMessage.PopulatePackageParams(PackageParams); + + // Find asset link and work result object + UHoudiniPDGAssetLink *AssetLink = nullptr; + UTOPNetwork *TOPNetwork = nullptr; + UTOPNode *TOPNode = nullptr; + if (!GetTOPAssetLinkNetworkAndNode(InMessage.TOPNodeId, AssetLink, TOPNetwork, TOPNode) || + !IsValid(AssetLink) || !IsValid(TOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP node with id %d, aborting output object creation."), InMessage.TOPNodeId); + return; + } + + FTOPWorkResult* WorkResult = AssetLink->GetWorkResultByID(InMessage.WorkItemId, TOPNode); + if (WorkResult == nullptr) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result with id %d, aborting output object creation."), InMessage.WorkItemId); + return; + } + const FString& WorkResultObjectName = InMessage.Name; + FTOPWorkResultObject* WorkResultObject = WorkResult->ResultObjects.FindByPredicate( + [&WorkResultObjectName](const FTOPWorkResultObject& WorkResultObject) + { + return WorkResultObject.Name == WorkResultObjectName; + } + ); + if (WorkResultObject == nullptr) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result object with name %s, aborting output object creation."), *InMessage.Name); + return; + } + + if (WorkResultObject->State != EPDGWorkResultState::Loading) + { + HOUDINI_LOG_WARNING(TEXT("TOP work result object (%s) not in Loading state, aborting output object creation."), *InMessage.Name); + return; + } + + // Set package params outer + UObject* AssetLinkParent = AssetLink->GetOuter(); + UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; + if (HAC) + { + PackageParams.OuterPackage = HAC->GetComponentLevel(); + } + else + { + PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); + } + + // Construct UHoudiniOutputs + bool bHasUnsupportedOutputs = false; + TArray NewOutputs; + TMap InstancedOutputPartData; + NewOutputs.Reserve(InMessage.Outputs.Num()); + for (const FHoudiniPDGImportNodeOutput& Output : InMessage.Outputs) + { + UHoudiniOutput* NewOutput = NewObject( + AssetLink, + UHoudiniOutput::StaticClass(), + NAME_None,//FName(*OutputName), + RF_NoFlags); + NewOutputs.Add(NewOutput); + const int32 NumHGPO = Output.HoudiniGeoPartObjects.Num(); + for (int32 Index = 0; Index < NumHGPO; ++Index) + { + const FHoudiniGeoPartObject& HGPO = Output.HoudiniGeoPartObjects[Index]; + NewOutput->AddNewHGPO(HGPO); + + if (Output.InstancedOutputPartData.IsValidIndex(Index)) + { + FHoudiniOutputObjectIdentifier Identifier; + Identifier.ObjectId = HGPO.ObjectId; + Identifier.GeoId = HGPO.GeoId; + Identifier.PartId = HGPO.PartId; + Identifier.PartName = HGPO.PartName; + FHoudiniInstancedOutputPartData InstancedPartData = Output.InstancedOutputPartData[Index]; + InstancedPartData.BuildOriginalInstancedTransformsAndObjectArrays(); + InstancedOutputPartData.Add(Identifier, InstancedPartData); + } + } + const int32 NumObjects = Output.OutputObjects.Num(); + for (int32 Index = 0; Index < NumObjects; ++Index) + { + const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; + FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; + + const FString& FullPackagePath = ImportOutputObject.PackagePath; + FString PackagePath; + FString PackageName; + const bool bDidSplit = FullPackagePath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bDidSplit) + PackagePath = FullPackagePath; + + FHoudiniOutputObject OutputObject; + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!IsValid(Package)) + { + // Editor might have picked up the package yet, try to load it + Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); + } + if (IsValid(Package)) + { + OutputObject.OutputObject = FindObject(Package, *PackageName); + } + Identifier.bLoaded = true; + NewOutput->GetOutputObjects().Add(Identifier, OutputObject); + } + NewOutput->UpdateOutputType(); + const EHoudiniOutputType OutputType = NewOutput->GetType(); + if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) + { + bHasUnsupportedOutputs = true; + } + } + + bool bSuccess = true; + if (bHasUnsupportedOutputs) + { + HOUDINI_LOG_MESSAGE(TEXT("Processing output types not supported by commandlet for %s"), *InMessage.Name); + bSuccess = FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + AssetLink, TOPNode, *WorkResultObject, PackageParams, + { + EHoudiniOutputType::Landscape, + EHoudiniOutputType::Curve, + EHoudiniOutputType::Skeletal + } + ); + + if (bSuccess) + { + // Clear/remove the outputs on WorkResultObject that are supported by the commandlet, since we + // are going to replace them with NewOutputs now + TArray& CurrentOutputs = WorkResultObject->GetResultOutputs(); + const int32 NumCurrentOutputs = CurrentOutputs.Num(); + for (int32 Index = 0; Index < NumCurrentOutputs; ++Index) + { + UHoudiniOutput* CurOutput = CurrentOutputs[Index]; + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) + { + // Was created in editor, override the dummy one in NewOutputs with CurOutput + if (NewOutputs.IsValidIndex(Index)) + { + if (OutputType == NewOutputs[Index]->GetType()) + { + UHoudiniOutput* TempOutput = NewOutputs[Index]; + FHoudiniOutputTranslator::ClearOutput(TempOutput); + NewOutputs[Index] = CurOutput; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Unexpected commandlet output type at index %d!"), Index); + } + } + else + { + HOUDINI_LOG_ERROR(TEXT("Expected output index %d from commandlet to be exist!"), Index); + } + } + } + } + } + + if (bSuccess && FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( + AssetLink, + TOPNode, + *WorkResultObject, + PackageParams, + NewOutputs, + {EHoudiniOutputType::Mesh, EHoudiniOutputType::Instancer}, + &InstancedOutputPartData)) + { + const int32 NumOutputs = NewOutputs.Num(); + for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex) + { + UHoudiniOutput *NewOutput = NewOutputs[OutputIndex]; + + if (NewOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + const FHoudiniPDGImportNodeOutput& Output = InMessage.Outputs[OutputIndex]; + int32 NumObjects = Output.OutputObjects.Num(); + for (int32 Index = 0; Index < NumObjects; ++Index) + { + const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; + FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; + FHoudiniOutputObject *OutputObject = NewOutput->GetOutputObjects().Find(Identifier); + if (OutputObject) + { + if (IsValid(OutputObject->OutputComponent)) + { + // Update generic property attributes + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + OutputObject->OutputComponent, + ImportOutputObject.GenericAttributes.PropertyAttributes); + } + + // Copy cached attributes + OutputObject->CachedAttributes.Append(ImportOutputObject.CachedAttributes); + } + } + } + } + else + { + bSuccess = false; + } + + if (bSuccess) + { + WorkResultObject->State = EPDGWorkResultState::Loaded; + WorkResultObject->SetAutoBakedSinceLastLoad(false); + HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); + // Broadcast that we have loaded the work result object to those interested + AssetLink->OnWorkResultObjectLoaded.Broadcast( + AssetLink, TOPNode, WorkResult->WorkItemIndex, WorkResultObject->WorkItemResultInfoIndex); + } + else + { + WorkResultObject->State = EPDGWorkResultState::None; + HOUDINI_LOG_WARNING(TEXT("Failed to process loaded assets for %s"), *InMessage.Name); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Commandlet failed to import bgeo for %s"), *InMessage.Name); + } +} + +bool FHoudiniPDGManager::CreateBGEOCommandletAndEndpoint() +{ + if (!BGEOCommandletEndpoint.IsValid()) + { + BGEOCommandletAddress.Invalidate(); + BGEOCommandletEndpoint = FMessageEndpoint::Builder(TEXT("Houdini BGEO Commandlet")) + .Handling(this, &FHoudiniPDGManager::HandleImportBGEOResultMessage) + .Handling(this, &FHoudiniPDGManager::HandleImportBGEODiscoverMessage) + .ReceivingOnThread(ENamedThreads::GameThread); + + if (!BGEOCommandletEndpoint.IsValid()) + { + HOUDINI_LOG_WARNING(TEXT("Could not set up messaging end point for BGEO commandlet")); + return false; + } + + BGEOCommandletEndpoint->Subscribe(); + } + + if (!BGEOCommandletProcHandle.IsValid() || !FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + { + // Start the bgeo commandlet + static const FString BGEOCommandletName = TEXT("HoudiniGeoImport"); + BGEOCommandletGuid = FGuid::NewGuid(); + BGEOCommandletAddress.Invalidate(); + + // Get the absolute path to the project file, if known, otherwise get + // the project name. For the path: quote it for the command line. + IFileManager& FileManager = IFileManager::Get(); + FString ProjectPathOrName = FApp::GetProjectName(); + if (FPaths::IsProjectFilePathSet()) + { + const FString ProjectPath = FPaths::GetProjectFilePath(); + if (!ProjectPath.IsEmpty()) + { + ProjectPathOrName = FString::Printf( + TEXT("\"%s\""), + *FileManager.ConvertToAbsolutePathForExternalAppForRead(*ProjectPath) + ); + } + } + + if (ProjectPathOrName.IsEmpty()) + return false; + + // Get the executable path for the app/editor + FString ExePath = FPlatformProcess::GenerateApplicationPath(FApp::GetName(), FApp::GetBuildConfiguration()); + if (!ExePath.IsEmpty()) + ExePath = FileManager.ConvertToAbsolutePathForExternalAppForRead(*ExePath); + + if (ExePath.IsEmpty()) + return false; + + const FString CommandLineParameters = FString::Printf( + TEXT("%s -messaging -run=%s -guid=%s -listen=%s -managerpid=%d"), + *ProjectPathOrName, + *BGEOCommandletName, + *BGEOCommandletGuid.ToString(), + *BGEOCommandletEndpoint->GetAddress().ToString(), + FPlatformProcess::GetCurrentProcessId()); + + BGEOCommandletProcHandle = FPlatformProcess::CreateProc( + *ExePath, + *CommandLineParameters, + false, + true, + false, + &BGEOCommandletProcessId, + 0, + NULL, + NULL); + if (!BGEOCommandletProcHandle.IsValid()) + { + return false; + } + } + + return true; +} + +void FHoudiniPDGManager::StopBGEOCommandletAndEndpoint() +{ + BGEOCommandletEndpoint.Reset(); + BGEOCommandletAddress.Invalidate(); + BGEOCommandletGuid.Invalidate(); + + if (BGEOCommandletProcHandle.IsValid() && FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + { + FPlatformProcess::TerminateProc(BGEOCommandletProcHandle, true); + if (BGEOCommandletProcHandle.IsValid()) + { + FPlatformProcess::WaitForProc(BGEOCommandletProcHandle); + FPlatformProcess::CloseProc(BGEOCommandletProcHandle); + } + } +} + +EHoudiniBGEOCommandletStatus FHoudiniPDGManager::UpdateAndGetBGEOCommandletStatus() +{ + if (BGEOCommandletProcHandle.IsValid()) + { + if (!FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Crashed; + else if (BGEOCommandletAddress.IsValid()) + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Connected; + else + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Running; + } + else + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::NotStarted; + + return BGEOCommandletStatus; +} + + +bool +FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) +{ + if (InAssetId < 0) + return false; + + // Get all the network nodes within the asset, recursively. + // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type + int32 NetworkNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); + + if (NetworkNodeCount <= 0) + return false; + + TArray AllNetworkNodeIDs; + AllNetworkNodeIDs.SetNum(NetworkNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); + + // There is currently no way to only get non bypassed nodes via HAPI + // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... + TArray AllBypassedTOPNetNodeIDs; + { + int32 BypassedTOPNetNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); + + if (BypassedTOPNetNodeCount > 0) + { + // Get the list of all bypassed TOP Net... + AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); + + // ... and remove them from the network list + for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) + AllNetworkNodeIDs.RemoveAt(Idx); + } + } + } + + // For each Network we found earlier, only consider those with TOP child nodes + // If we find TOP nodes in a valid network, then consider this HDA a PDG HDA + for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) + { + if (CurrentNodeId < 0) + { + continue; + } + + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) + { + continue; + } + + // Skip non TOP or SOP networks + if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP + && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) + { + continue; + } + + // Get the list of all TOP nodes within the current network (ignoring schedulers) + int32 TOPNodeCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) + { + continue; + } + + // We found valid TOP Nodes, this is a PDG HDA + if (TOPNodeCount > 0) + return true; + } + + // No valid TOP node found in SOP/TOP Networks, this is not a PDG HDA + return false; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.h b/Source/HoudiniEngine/Private/HoudiniPDGManager.h index a48064977..0b0b9ba1d 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.h @@ -1,215 +1,215 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HAL/PlatformProcess.h" - -#include "MessageEndpoint.h" - -class UHoudiniAssetComponent; -class UHoudiniPDGAssetLink; -class UTOPNetwork; -class UTOPNode; -class FSocket; - -enum class EPDGNodeState : uint8; - -// BGEO commandlet status -enum class HOUDINIENGINE_API EHoudiniBGEOCommandletStatus : uint8 -{ - // PDG manager has not tried to start the commandlet - NotStarted, - // PDG manager has PID for the commandlet and the host OS indicates it is running, but no message has been - // received from it yet - Running, - // PDG manager has PID for the commandlet, the host OS indicates it is running, and a discover message has been - // received - Connected, - // After being in the Connected state, the commandlet stopped running (host OS indicates PID is not valid) - Crashed -}; - -struct HOUDINIENGINE_API FHoudiniPDGManager -{ - -public: - - FHoudiniPDGManager(); - - virtual ~FHoudiniPDGManager(); - - // Initialize the PDG Asset Link for a HoudiniAssetComponent - // returns true if the HAC uses a PDG asset, and a PDGAssetLink was successfully created - bool InitializePDGAssetLink(UHoudiniAssetComponent* InHAC); - - // Updates an existing PDG AssetLink - static bool UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink); - - // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. - static bool PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys=false); - - static void RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink); - - // Indicates if the Asset is a PDG Asset - // This will look for TOP nodes in all SOP/TOP net in the HDA. - static bool IsPDGAsset(const HAPI_NodeId& InAssetId); - - // Given TOP nodes from a TOP network, populate internal state from each TOP node. - static bool PopulateTOPNodes( - const TArray& InTopNodeIDs, - UTOPNetwork* InTOPNetwork, - UHoudiniPDGAssetLink* InPDGAssetLink, - bool bInZeroWorkItemTallys=false); - - // Cook the specified TOP node. - static void CookTOPNode(UTOPNode* InTOPNode); - - // Dirty the specified TOP node and clear its work item results. - static void DirtyTOPNode(UTOPNode* InTOPNode); - - // // Dirty all the tasks/work items of the specified TOP node. Does not - // // clear its work item results. - // static void DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode); - - // Dirty the TOP network and clear all work item results. - static void DirtyAll(UTOPNetwork* InTOPNet); - - // Cook the output TOP node of the currently selected TOP network. - static void CookOutput(UTOPNetwork* InTOPNet); - - // Pause the PDG cook of the currently selected TOP network - static void PauseCook(UTOPNetwork* InTOPNet); - - // Cancel the PDG cook of the currently selected TOP network - static void CancelCook(UTOPNetwork* InTOPNet); - - static void NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess); - - // Update all registered PDG Asset links - void Update(); - - void ReinitializePDGContext(); - - // Clear all of the specified work item's results from the specified TOP node. This destroys any loaded results - // (geometry etc), but keeps the work item struct. - //void ClearWorkItemResult(const HAPI_PDG_GraphContextId& InContextID, const HAPI_PDG_EventInfo& InEventInfo, FTOPNode& TOPNode); - void ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); - - // Clear the specified work item's results from the specified TOP node and remove the work item struct from the TOP - // node. This destroys any loaded results (geometry etc), and the work item struct. - void RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); - - // Create a (or re-use an existing) FTOPWorkResult for a given TOPNode and the specified work item ID, without - // creating its FTOPWorkResultObjects. - // Returns INDEX_NONE if an entry could not be created or data could not be retrieved from HAPI. - int32 CreateOrRelinkWorkItem(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID); - - // Ensure that FTOPWorkResult exists, and create its FTOPWorkResultObjects for a given TOP node and work item id, - // and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. - // Geometry is not directly loaded by this function, the FTOPWorkResultObjects' states will be set to ToLoad and - // the ProcessWorkItemResults function will take care of loading the geo. - // Results must be tagged with 'file', and must have a file path, otherwise will not included. - bool CreateOrRelinkWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); - - // First Create or re-link FTOPWorkResults based on the work items that exist on InTOPNode in HAPI. - // Then remove any FTOPWorkResults (and clean up their output) from the WorkResults of InTOPNode if: - // WorkResult.WorkItemID is INDEX_NONE - // WorkResult.WorkItemID is not in the list of work item ids that HAPI returns for this node - int32 SyncAndPruneWorkItems(UTOPNode* InTOPNode); - - // Handles replies from commandlets in response to a FHoudiniPDGImportBGEODiscoverMessage - void HandleImportBGEODiscoverMessage( - const struct FHoudiniPDGImportBGEODiscoverMessage& InMessage, - const TSharedRef& InContext); - - // Handles messages sent by the commandlet once an import of a bgeo is complete, and uassets have been created. - void HandleImportBGEOResultMessage( - const struct FHoudiniPDGImportBGEOResultMessage& InMessage, - const TSharedRef& InContext); - - // Create the bgeo commandlet endpoint and start the commandlet (if not already running). - bool CreateBGEOCommandletAndEndpoint(); - - void StopBGEOCommandletAndEndpoint(); - - // Updates and returns the BGEO commandlet status - EHoudiniBGEOCommandletStatus UpdateAndGetBGEOCommandletStatus(); - -private: - - void UpdatePDGContexts(); - - void ProcessWorkItemResults(); - - void ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo); - - static void ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo); - - // Returns the PDGAssetLink and FTOPNode associated with this TOP node ID - bool GetTOPAssetLinkAndNode(const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNode*& OutTOPNode); - - void SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState); - - void NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode); - - void NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - -private: - - TArray PDGContextNames; - TArray PDGContextIDs; - TArray PDGEventInfos; - - TArray> PDGAssetLinks; - - int32 MaxNumberOfPDGEvents = 20; - int32 MaxNumberOPDGContexts = 20; - - TSharedPtr BGEOCommandletEndpoint; - FMessageAddress BGEOCommandletAddress; - FProcHandle BGEOCommandletProcHandle; - FGuid BGEOCommandletGuid; - uint32 BGEOCommandletProcessId; - // Keep track of the BGEO commandlet status - EHoudiniBGEOCommandletStatus BGEOCommandletStatus; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HAL/PlatformProcess.h" + +#include "MessageEndpoint.h" + +class UHoudiniAssetComponent; +class UHoudiniPDGAssetLink; +class UTOPNetwork; +class UTOPNode; +class FSocket; + +enum class EPDGNodeState : uint8; + +// BGEO commandlet status +enum class HOUDINIENGINE_API EHoudiniBGEOCommandletStatus : uint8 +{ + // PDG manager has not tried to start the commandlet + NotStarted, + // PDG manager has PID for the commandlet and the host OS indicates it is running, but no message has been + // received from it yet + Running, + // PDG manager has PID for the commandlet, the host OS indicates it is running, and a discover message has been + // received + Connected, + // After being in the Connected state, the commandlet stopped running (host OS indicates PID is not valid) + Crashed +}; + +struct HOUDINIENGINE_API FHoudiniPDGManager +{ + +public: + + FHoudiniPDGManager(); + + virtual ~FHoudiniPDGManager(); + + // Initialize the PDG Asset Link for a HoudiniAssetComponent + // returns true if the HAC uses a PDG asset, and a PDGAssetLink was successfully created + bool InitializePDGAssetLink(UHoudiniAssetComponent* InHAC); + + // Updates an existing PDG AssetLink + static bool UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink); + + // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. + static bool PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys=false); + + static void RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink); + + // Indicates if the Asset is a PDG Asset + // This will look for TOP nodes in all SOP/TOP net in the HDA. + static bool IsPDGAsset(const HAPI_NodeId& InAssetId); + + // Given TOP nodes from a TOP network, populate internal state from each TOP node. + static bool PopulateTOPNodes( + const TArray& InTopNodeIDs, + UTOPNetwork* InTOPNetwork, + UHoudiniPDGAssetLink* InPDGAssetLink, + bool bInZeroWorkItemTallys=false); + + // Cook the specified TOP node. + static void CookTOPNode(UTOPNode* InTOPNode); + + // Dirty the specified TOP node and clear its work item results. + static void DirtyTOPNode(UTOPNode* InTOPNode); + + // // Dirty all the tasks/work items of the specified TOP node. Does not + // // clear its work item results. + // static void DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode); + + // Dirty the TOP network and clear all work item results. + static void DirtyAll(UTOPNetwork* InTOPNet); + + // Cook the output TOP node of the currently selected TOP network. + static void CookOutput(UTOPNetwork* InTOPNet); + + // Pause the PDG cook of the currently selected TOP network + static void PauseCook(UTOPNetwork* InTOPNet); + + // Cancel the PDG cook of the currently selected TOP network + static void CancelCook(UTOPNetwork* InTOPNet); + + static void NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess); + + // Update all registered PDG Asset links + void Update(); + + void ReinitializePDGContext(); + + // Clear all of the specified work item's results from the specified TOP node. This destroys any loaded results + // (geometry etc), but keeps the work item struct. + //void ClearWorkItemResult(const HAPI_PDG_GraphContextId& InContextID, const HAPI_PDG_EventInfo& InEventInfo, FTOPNode& TOPNode); + void ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); + + // Clear the specified work item's results from the specified TOP node and remove the work item struct from the TOP + // node. This destroys any loaded results (geometry etc), and the work item struct. + void RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); + + // Create a (or re-use an existing) FTOPWorkResult for a given TOPNode and the specified work item ID, without + // creating its FTOPWorkResultObjects. + // Returns INDEX_NONE if an entry could not be created or data could not be retrieved from HAPI. + int32 CreateOrRelinkWorkItem(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID); + + // Ensure that FTOPWorkResult exists, and create its FTOPWorkResultObjects for a given TOP node and work item id, + // and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. + // Geometry is not directly loaded by this function, the FTOPWorkResultObjects' states will be set to ToLoad and + // the ProcessWorkItemResults function will take care of loading the geo. + // Results must be tagged with 'file', and must have a file path, otherwise will not included. + bool CreateOrRelinkWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); + + // First Create or re-link FTOPWorkResults based on the work items that exist on InTOPNode in HAPI. + // Then remove any FTOPWorkResults (and clean up their output) from the WorkResults of InTOPNode if: + // WorkResult.WorkItemID is INDEX_NONE + // WorkResult.WorkItemID is not in the list of work item ids that HAPI returns for this node + int32 SyncAndPruneWorkItems(UTOPNode* InTOPNode); + + // Handles replies from commandlets in response to a FHoudiniPDGImportBGEODiscoverMessage + void HandleImportBGEODiscoverMessage( + const struct FHoudiniPDGImportBGEODiscoverMessage& InMessage, + const TSharedRef& InContext); + + // Handles messages sent by the commandlet once an import of a bgeo is complete, and uassets have been created. + void HandleImportBGEOResultMessage( + const struct FHoudiniPDGImportBGEOResultMessage& InMessage, + const TSharedRef& InContext); + + // Create the bgeo commandlet endpoint and start the commandlet (if not already running). + bool CreateBGEOCommandletAndEndpoint(); + + void StopBGEOCommandletAndEndpoint(); + + // Updates and returns the BGEO commandlet status + EHoudiniBGEOCommandletStatus UpdateAndGetBGEOCommandletStatus(); + +private: + + void UpdatePDGContexts(); + + void ProcessWorkItemResults(); + + void ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo); + + static void ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo); + + // Returns the PDGAssetLink and FTOPNode associated with this TOP node ID + bool GetTOPAssetLinkNetworkAndNode(const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNetwork*& OutTOPNetwork, UTOPNode*& OutTOPNode); + + void SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState); + + void NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode); + + void NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + +private: + + TArray PDGContextNames; + TArray PDGContextIDs; + TArray PDGEventInfos; + + TArray> PDGAssetLinks; + + int32 MaxNumberOfPDGEvents = 20; + int32 MaxNumberOPDGContexts = 20; + + TSharedPtr BGEOCommandletEndpoint; + FMessageAddress BGEOCommandletAddress; + FProcHandle BGEOCommandletProcHandle; + FGuid BGEOCommandletGuid; + uint32 BGEOCommandletProcessId; + // Keep track of the BGEO commandlet status + EHoudiniBGEOCommandletStatus BGEOCommandletStatus; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp index 0407edba1..18a4ee85b 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp @@ -1,510 +1,506 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGTranslator.h" - - -#include "Editor.h" -#include "Containers/Array.h" -#include "FileHelpers.h" -#include "LandscapeInfo.h" -// #include "Engine/WorldComposition.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniPackageParams.h" -#include "HoudiniOutput.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniTranslatorTypes.h" - -#define LOCTEXT_NAMESPACE "HoudiniEngine" - -bool -FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray InOutputTypesToProcess, - bool bInTreatExistingMaterialsAsUpToDate) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem); - - if (!IsValid(InAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InAssetLink is null.")); - return false; - } - - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InTOPNode is null.")); - return false; - } - - TArray OldTOPOutputs = InWorkResultObject.GetResultOutputs(); - TArray NewTOPOutputs; - - FHoudiniEngine::Get().CreateTaskSlateNotification(LOCTEXT("LoadPDGBGEO", "Loading PDG Output BGEO File...")); - - bool bResult = false; - // Create a new file node in HAPI for the bgeo and cook it - HAPI_NodeId FileNodeId = -1; - bResult = UHoudiniGeoImporter::OpenBGEOFile(InWorkResultObject.FilePath, FileNodeId); - if (bResult) - bResult = UHoudiniGeoImporter::CookFileNode(FileNodeId); - - // If the cook was successful, build outputs - if (bResult) - { - FHoudiniEngine::Get().UpdateTaskSlateNotification( - LOCTEXT("BuildPDGBGEOOutputs", "Building Ouputs from BGEO File...")); - - const bool bAddOutputsToRootSet = false; - bResult = UHoudiniGeoImporter::BuildAllOutputsForNode( - FileNodeId, - InAssetLink, - OldTOPOutputs, - NewTOPOutputs, - bAddOutputsToRootSet); - } - - if (bResult) - { - FHoudiniEngine::Get().UpdateTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); - - // If we successfully received outputs from the BGEO file, process the outputs - FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); - AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - if (!IsValid(WorkItemOutputActor)) - { - UWorld* World = InAssetLink->GetWorld(); - FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); - AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - if (!IsValid(TOPNodeOutputActor)) - { - if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) - TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - } - if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) - WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - } - - for (auto& OldOutput : OldTOPOutputs) - { - FHoudiniOutputTranslator::ClearOutput(OldOutput); - } - OldTOPOutputs.Empty(); - InWorkResultObject.GetResultOutputs().Empty(); - InWorkResultObject.SetResultOutputs(NewTOPOutputs); - - bResult = CreateAllResultObjectsFromPDGOutputs( - NewTOPOutputs, - InPackageParams, - WorkItemOutputActor->GetRootComponent(), - InTOPNode->GetLandscapeExtent(), - InTOPNode->GetLandscapeReferenceLocation(), - InTOPNode->GetLandscapeSizeInfo(), - InOutputTypesToProcess, - bInTreatExistingMaterialsAsUpToDate); - - if (!bResult) - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); - else - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); - - InTOPNode->UpdateOutputVisibilityInLevel(); - } - else - { - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("BuildPDGBGEOOutputsFail", "Failed building outputs from BGEO file...")); - } - - // Delete the file node used to load the BGEO via HAPI - if (FileNodeId >= 0) - { - UHoudiniGeoImporter::CloseBGEOFile(FileNodeId); - FileNodeId = -1; - } - - return bResult; -} - -bool -FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray& InOutputs, - TArray InOutputTypesToProcess, - const TMap* InPreBuiltInstancedOutputPartData) -{ - if (!IsValid(InAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InAssetLink is null.")); - return false; - } - - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InTOPNode is null.")); - return false; - } - - FHoudiniEngine::Get().CreateTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); - - // If we successfully received outputs from the BGEO file, process the outputs - FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); - AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - if (!IsValid(WorkItemOutputActor)) - { - UWorld* World = InAssetLink->GetWorld(); - FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); - AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - if (!IsValid(TOPNodeOutputActor)) - { - if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) - TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - } - if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) - WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - } - - InWorkResultObject.SetResultOutputs(InOutputs); - - const bool bInTreatExistingMaterialsAsUpToDate = true; - const bool bOnlyUseExistingAssets = true; - const bool bResult = CreateAllResultObjectsFromPDGOutputs( - InOutputs, - InPackageParams, - WorkItemOutputActor->GetRootComponent(), - InTOPNode->GetLandscapeExtent(), - InTOPNode->GetLandscapeReferenceLocation(), - InTOPNode->GetLandscapeSizeInfo(), - InOutputTypesToProcess, - bInTreatExistingMaterialsAsUpToDate, - bOnlyUseExistingAssets, - InPreBuiltInstancedOutputPartData); - - if (!bResult) - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); - else - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); - - InTOPNode->UpdateOutputVisibilityInLevel(); - - return bResult; -} - -bool -FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( - TArray& InOutputs, - const FHoudiniPackageParams& InPackageParams, - UObject* InOuterComponent, - FHoudiniLandscapeExtent& CachedLandscapeExtent, - FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, - FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, - TArray InOutputTypesToProcess, - bool bInTreatExistingMaterialsAsUpToDate, - bool bInOnlyUseExistingAssets, - const TMap* InPreBuiltInstancedOutputPartData - ) -{ - // Process the new/updated outputs via the various translators - // We try to maintain as much parity with the existing HoudiniAssetComponent workflow - // as possible. - - // // For world composition landscapes - // FString WorldCompositionPath = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - // if (bInUseWorldComposition) - // { - // FHoudiniLandscapeTranslator::EnableWorldComposition(); - // - // // Save the current map as well if world composition is enabled. - // FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); - // UWorld* CurrentWorld = EditorWorldContext.World(); - // - // if (CurrentWorld) - // { - // // Save the current map - // FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); - // UPackage* CurrentWorldPackage = CreatePackage(nullptr, *CurrentWorldPath); - // if (CurrentWorldPackage) - // { - // CurrentWorldPackage->MarkPackageDirty(); - // - // TArray CurrentWorldToSave; - // CurrentWorldToSave.Add(CurrentWorldPackage); - // - // FEditorFileUtils::PromptForCheckoutAndSave(CurrentWorldToSave, true, false); - // - // WorldCompositionPath = FPackageName::GetLongPackagePath(CurrentWorldPackage->GetName()); - // } - // } - // } - - // // Before processing any of the output, - // // we need to get the min/max value for all Height volumes in this output (if any) - TMap LandscapeLayerGlobalMinimums; - TMap LandscapeLayerGlobalMaximums; - - for (UHoudiniOutput* CurOutput : InOutputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType == EHoudiniOutputType::Landscape) - { - // Populate all layer minimums/maximums with default values since, in PDG mode, - // we can't collect the values across all tiles. The user would have to do this - // manually in the Topnet. - FHoudiniLandscapeTranslator::GetLayersZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums); - } - } - - TArray InstancerOutputs; - TArray LandscapeOutputs; - TArray CreatedPackages; - - //bool bCreatedNewMaps = false; - UWorld* PersistentWorld = InOuterComponent->GetTypedOuter(); - check(PersistentWorld); - - for (UHoudiniOutput* CurOutput : InOutputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (InOutputTypesToProcess.Num() > 0 && !InOutputTypesToProcess.Contains(OutputType)) - { - continue; - } - switch (OutputType) - { - case EHoudiniOutputType::Mesh: - { - const bool bInDestroyProxies = false; - if (bInOnlyUseExistingAssets) - { - const bool bInApplyGenericProperties = false; - TMap NewOutputObjects(CurOutput->GetOutputObjects()); - FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - CurOutput, - InOuterComponent, - NewOutputObjects, - bInDestroyProxies, - bInApplyGenericProperties - ); - } - else - { - // TODO: If Outer is an HAC, get SMGP/MBS from it ?? - FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); - FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - InPackageParams, - EHoudiniStaticMeshMethod::RawMesh, - SMGP, - MBS, - InOuterComponent, - bInTreatExistingMaterialsAsUpToDate, - bInDestroyProxies - ); - } - } - break; - - case EHoudiniOutputType::Curve: - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, InOuterComponent); - break; - } - - case EHoudiniOutputType::Instancer: - { - InstancerOutputs.Add(CurOutput); - } - break; - - case EHoudiniOutputType::Landscape: - { - TArray EmptyInputLandscapes; - // Retrieve the topnet parent to which Sharedlandscapes will be attached. - AActor* WorkItemActor = InOuterComponent->GetTypedOuter(); - USceneComponent* TopnetParent = nullptr; - if (WorkItemActor) - { - AActor* TopnetParentActor = WorkItemActor->GetAttachParentActor(); - if (TopnetParentActor) - { - TopnetParent = TopnetParentActor->GetRootComponent(); - } - } - TArray> CreatedUntrackedOutputs; - - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - CreatedUntrackedOutputs, - EmptyInputLandscapes, - EmptyInputLandscapes, - TopnetParent, - TEXT("{hda_actor_name}_{pdg_topnet_name}_"), - PersistentWorld, - LandscapeLayerGlobalMinimums, - LandscapeLayerGlobalMaximums, - CachedLandscapeExtent, - CachedLandscapeSizeInfo, - CachedLandscapeRefLoc, - InPackageParams, - //bCreatedNewMaps, - CreatedPackages); - // Attach any landscape actors to InOuterComponent - LandscapeOutputs.Add(CurOutput); - } - break; - - default: - { - HOUDINI_LOG_WARNING(TEXT("[FTOPWorkResultObject::UpdateResultOutputs]: Unsupported output type: %s"), *UHoudiniOutput::OutputTypeToString(OutputType)); - } - break; - } - } - - // Process instancer outputs after all other outputs have been processed, since it - // might depend on meshes etc from other outputs - if (InstancerOutputs.Num() > 0) - { - for (UHoudiniOutput* CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - CurOutput, - InOutputs, - InOuterComponent, - InPreBuiltInstancedOutputPartData - ); - } - } - - - USceneComponent* ParentComponent = Cast(InOuterComponent); - - if (ParentComponent) - { - AActor* ParentActor = ParentComponent->GetOwner(); - for (UHoudiniOutput* LandscapeOutput : LandscapeOutputs) - { - for (auto& Pair : LandscapeOutput->GetOutputObjects()) - { - FHoudiniOutputObject &OutputObject = Pair.Value; - - // If the OutputObject has an OutputComponent, try to attach it to ParentComponent - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) - { - USceneComponent* Component = Cast(OutputObject.OutputComponent); - if (IsValid(Component) && !Component->IsAttachedTo(ParentComponent)) - { - Component->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepWorldTransform); - continue; - } - } - - // If OutputObject does not have an OutputComponent, check if OutputObject is an AActor and attach - // it to ParentComponent - if (IsValid(OutputObject.OutputObject)) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - if (IsValid(LandscapePtr)) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - if (IsValid(LandscapeProxy)) - { - if (!LandscapeProxy->IsAttachedTo(ParentActor)) - { - LandscapeProxy->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform); - LandscapeProxy->RecreateCollisionComponents(); - } - - if (LandscapeProxy) - { - // We need to recreate component states for landscapes if a tile was created, moved, or resized - // otherwise the landscape will exhibit render artifacts (such as only rendering every other - // component.) - LandscapeProxy->RecreateComponentsState(); - } - } - - } - } - } - } - } - - /* - if (bCreatedNewMaps && PersistentWorld) - { - // Force the asset registry to update its cache of packages paths - // recursively for this world, otherwise world composition won't - // pick them up during the WorldComposition::Rescan(). - FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); - - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - - if (IsValid(PersistentWorld->WorldComposition)) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); - } - - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); - } - */ - - if (CreatedPackages.Num() > 0) - { - // Save created packages. For example, we don't want landscape layers deleted - // along with the HDA. - FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); - } - - return true; -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGTranslator.h" + + +#include "Editor.h" +#include "Containers/Array.h" +#include "FileHelpers.h" +#include "LandscapeInfo.h" +// #include "Engine/WorldComposition.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniPackageParams.h" +#include "HoudiniOutput.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniTranslatorTypes.h" + +#define LOCTEXT_NAMESPACE "HoudiniEngine" + +bool +FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray InOutputTypesToProcess, + bool bInTreatExistingMaterialsAsUpToDate) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem); + + if (!IsValid(InAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InAssetLink is null.")); + return false; + } + + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InTOPNode is null.")); + return false; + } + + TArray OldTOPOutputs = InWorkResultObject.GetResultOutputs(); + TArray NewTOPOutputs; + + FHoudiniEngine::Get().CreateTaskSlateNotification(LOCTEXT("LoadPDGBGEO", "Loading PDG Output BGEO File...")); + + bool bResult = false; + // Create a new file node in HAPI for the bgeo and cook it + HAPI_NodeId FileNodeId = -1; + bResult = UHoudiniGeoImporter::OpenBGEOFile(InWorkResultObject.FilePath, FileNodeId); + if (bResult) + bResult = UHoudiniGeoImporter::CookFileNode(FileNodeId); + + // If the cook was successful, build outputs + if (bResult) + { + FHoudiniEngine::Get().UpdateTaskSlateNotification( + LOCTEXT("BuildPDGBGEOOutputs", "Building Ouputs from BGEO File...")); + + const bool bAddOutputsToRootSet = false; + bResult = UHoudiniGeoImporter::BuildAllOutputsForNode( + FileNodeId, + InAssetLink, + OldTOPOutputs, + NewTOPOutputs, + bAddOutputsToRootSet); + } + + if (bResult) + { + FHoudiniEngine::Get().UpdateTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); + + // If we successfully received outputs from the BGEO file, process the outputs + FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); + AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + if (!IsValid(WorkItemOutputActor)) + { + UWorld* World = InAssetLink->GetWorld(); + FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); + AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + if (!IsValid(TOPNodeOutputActor)) + { + if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) + TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + } + if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) + WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + } + + for (auto& OldOutput : OldTOPOutputs) + { + FHoudiniOutputTranslator::ClearOutput(OldOutput); + } + OldTOPOutputs.Empty(); + InWorkResultObject.GetResultOutputs().Empty(); + InWorkResultObject.SetResultOutputs(NewTOPOutputs); + + // Gather landscape actors from inputs. + // NOTE: If performance becomes a problem, cache these on the TOPNode along with all the other cached landscape + // data. + TArray AllInputLandscapes; + TArray InputLandscapesToUpdate; + UHoudiniAssetComponent* HAC = InAssetLink->GetOuterHoudiniAssetComponent(); + FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); + + bResult = CreateAllResultObjectsFromPDGOutputs( + NewTOPOutputs, + InPackageParams, + WorkItemOutputActor->GetRootComponent(), + InTOPNode->GetLandscapeExtent(), + InTOPNode->GetLandscapeReferenceLocation(), + InTOPNode->GetLandscapeSizeInfo(), + InTOPNode->ClearedLandscapeLayers, + AllInputLandscapes, + InputLandscapesToUpdate, + InOutputTypesToProcess, + bInTreatExistingMaterialsAsUpToDate); + + if (!bResult) + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); + else + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); + + InTOPNode->UpdateOutputVisibilityInLevel(); + } + else + { + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("BuildPDGBGEOOutputsFail", "Failed building outputs from BGEO file...")); + } + + // Delete the file node used to load the BGEO via HAPI + if (FileNodeId >= 0) + { + UHoudiniGeoImporter::CloseBGEOFile(FileNodeId); + FileNodeId = -1; + } + + return bResult; +} + +bool +FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray& InOutputs, + TArray InOutputTypesToProcess, + const TMap* InPreBuiltInstancedOutputPartData) +{ + if (!IsValid(InAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InAssetLink is null.")); + return false; + } + + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InTOPNode is null.")); + return false; + } + + FHoudiniEngine::Get().CreateTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); + + // If we successfully received outputs from the BGEO file, process the outputs + FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); + AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + if (!IsValid(WorkItemOutputActor)) + { + UWorld* World = InAssetLink->GetWorld(); + FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); + AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + if (!IsValid(TOPNodeOutputActor)) + { + if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) + TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + } + if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) + WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + } + + InWorkResultObject.SetResultOutputs(InOutputs); + + // Gather landscape actors from inputs. + // NOTE: If performance becomes a problem, cache these on the TOPNode along with all the other cached landscape + // data. + TArray AllInputLandscapes; + TArray InputLandscapesToUpdate; + UHoudiniAssetComponent* HAC = InAssetLink->GetOuterHoudiniAssetComponent(); + FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); + + const bool bInTreatExistingMaterialsAsUpToDate = true; + const bool bOnlyUseExistingAssets = true; + const bool bResult = CreateAllResultObjectsFromPDGOutputs( + InOutputs, + InPackageParams, + WorkItemOutputActor->GetRootComponent(), + InTOPNode->GetLandscapeExtent(), + InTOPNode->GetLandscapeReferenceLocation(), + InTOPNode->GetLandscapeSizeInfo(), + InTOPNode->ClearedLandscapeLayers, + AllInputLandscapes, + InputLandscapesToUpdate, + InOutputTypesToProcess, + bInTreatExistingMaterialsAsUpToDate, + bOnlyUseExistingAssets, + InPreBuiltInstancedOutputPartData); + + if (!bResult) + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); + else + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); + + InTOPNode->UpdateOutputVisibilityInLevel(); + + return bResult; +} + +bool +FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( + TArray& InOutputs, + const FHoudiniPackageParams& InPackageParams, + UObject* InOuterComponent, + FHoudiniLandscapeExtent& CachedLandscapeExtent, + FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, + FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, + TSet& ClearedLandscapeLayers, + TArray AllInputLandscapes, + TArray InputLandscapesToUpdate, + TArray InOutputTypesToProcess, + bool bInTreatExistingMaterialsAsUpToDate, + bool bInOnlyUseExistingAssets, + const TMap* InPreBuiltInstancedOutputPartData + ) +{ + // Process the new/updated outputs via the various translators + // We try to maintain as much parity with the existing HoudiniAssetComponent workflow + // as possible. + + // // Before processing any of the output, + // // we need to get the min/max value for all Height volumes in this output (if any) + TMap LandscapeLayerGlobalMinimums; + TMap LandscapeLayerGlobalMaximums; + + for (UHoudiniOutput* CurOutput : InOutputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType == EHoudiniOutputType::Landscape) + { + // Populate all layer minimums/maximums with default values since, in PDG mode, + // we can't collect the values across all tiles. The user would have to do this + // manually in the Topnet. + FHoudiniLandscapeTranslator::GetLayersZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums); + } + } + + TArray InstancerOutputs; + TArray LandscapeOutputs; + TArray CreatedPackages; + + //bool bCreatedNewMaps = false; + UWorld* PersistentWorld = InOuterComponent->GetTypedOuter(); + check(PersistentWorld); + + for (UHoudiniOutput* CurOutput : InOutputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (InOutputTypesToProcess.Num() > 0 && !InOutputTypesToProcess.Contains(OutputType)) + { + continue; + } + switch (OutputType) + { + case EHoudiniOutputType::Mesh: + { + const bool bInDestroyProxies = false; + if (bInOnlyUseExistingAssets) + { + const bool bInApplyGenericProperties = false; + TMap NewOutputObjects(CurOutput->GetOutputObjects()); + FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + CurOutput, + InOuterComponent, + NewOutputObjects, + bInDestroyProxies, + bInApplyGenericProperties + ); + } + else + { + // TODO: If Outer is an HAC, get SMGP/MBS from it ?? + FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); + FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + InPackageParams, + EHoudiniStaticMeshMethod::RawMesh, + SMGP, + MBS, + InOuterComponent, + bInTreatExistingMaterialsAsUpToDate, + bInDestroyProxies + ); + } + } + break; + + case EHoudiniOutputType::Curve: + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, InOuterComponent); + break; + } + + case EHoudiniOutputType::Instancer: + { + InstancerOutputs.Add(CurOutput); + } + break; + + case EHoudiniOutputType::Landscape: + { + // Retrieve the topnet parent to which Sharedlandscapes will be attached. + AActor* WorkItemActor = InOuterComponent->GetTypedOuter(); + USceneComponent* TopnetParent = nullptr; + if (WorkItemActor) + { + AActor* TopnetParentActor = WorkItemActor->GetAttachParentActor(); + if (TopnetParentActor) + { + TopnetParent = TopnetParentActor->GetRootComponent(); + } + } + TArray> CreatedUntrackedOutputs; + + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + AllInputLandscapes, + TopnetParent, + TEXT("{hda_actor_name}_{pdg_topnet_name}_"), + PersistentWorld, + LandscapeLayerGlobalMinimums, + LandscapeLayerGlobalMaximums, + CachedLandscapeExtent, + CachedLandscapeSizeInfo, + CachedLandscapeRefLoc, + InPackageParams, + //bCreatedNewMaps, + ClearedLandscapeLayers, + CreatedPackages); + // Attach any landscape actors to InOuterComponent + LandscapeOutputs.Add(CurOutput); + } + break; + + default: + { + HOUDINI_LOG_WARNING(TEXT("[FTOPWorkResultObject::UpdateResultOutputs]: Unsupported output type: %s"), *UHoudiniOutput::OutputTypeToString(OutputType)); + } + break; + } + } + + // Process instancer outputs after all other outputs have been processed, since it + // might depend on meshes etc from other outputs + if (InstancerOutputs.Num() > 0) + { + for (UHoudiniOutput* CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + CurOutput, + InOutputs, + InOuterComponent, + InPreBuiltInstancedOutputPartData + ); + } + } + + + USceneComponent* ParentComponent = Cast(InOuterComponent); + + if (ParentComponent) + { + AActor* ParentActor = ParentComponent->GetOwner(); + for (UHoudiniOutput* LandscapeOutput : LandscapeOutputs) + { + for (auto& Pair : LandscapeOutput->GetOutputObjects()) + { + FHoudiniOutputObject &OutputObject = Pair.Value; + + // If the OutputObject has an OutputComponent, try to attach it to ParentComponent + if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) + { + USceneComponent* Component = Cast(OutputObject.OutputComponent); + if (IsValid(Component) && !Component->IsAttachedTo(ParentComponent)) + { + Component->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepWorldTransform); + continue; + } + } + + // If OutputObject does not have an OutputComponent, check if OutputObject is an AActor and attach + // it to ParentComponent + if (IsValid(OutputObject.OutputObject)) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + if (IsValid(LandscapePtr)) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapeProxy)) + { + if (!LandscapeProxy->IsAttachedTo(ParentActor)) + { + LandscapeProxy->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform); + LandscapeProxy->RecreateCollisionComponents(); + } + + if (LandscapeProxy) + { + // We need to recreate component states for landscapes if a tile was created, moved, or resized + // otherwise the landscape will exhibit render artifacts (such as only rendering every other + // component.) + LandscapeProxy->RecreateComponentsState(); + } + } + + } + } + } + } + } + + /* + if (bCreatedNewMaps && PersistentWorld) + { + // Force the asset registry to update its cache of packages paths + // recursively for this world, otherwise world composition won't + // pick them up during the WorldComposition::Rescan(). + FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); + + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + + if (IsValid(PersistentWorld->WorldComposition)) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); + } + + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); + } + */ + + if (CreatedPackages.Num() > 0) + { + // Save created packages. For example, we don't want landscape layers deleted + // along with the HDA. + FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); + } + + return true; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h index f3957ae80..5909c5b69 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h @@ -1,82 +1,86 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -class UHoudiniPDGAssetLink; -class UHoudiniOutput; -class AActor; -class UTOPNode; - -enum class EHoudiniOutputType : uint8; - -struct FHoudiniPackageParams; -struct FTOPWorkResultObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniInstancedOutputPartData; -struct FHoudiniLandscapeExtent; -struct FHoudiniLandscapeReferenceLocation; -struct FHoudiniLandscapeTileSizeInfo; - -struct HOUDINIENGINE_API FHoudiniPDGTranslator -{ - public: - // Create/update assets/geometry for all PDG outputs of InWorkResultObject. This will use - // InWorkResultObject.FilePath to load the BGEO file and update/build outputs. - static bool CreateAllResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray InOutputTypesToProcess={}, - bool bInTreatExistingMaterialsAsUpToDate=false); - - static bool LoadExistingAssetsAsResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray& InOutputs, - TArray InOutputTypesToProcess={}, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); - - // Use the relevant translators to create assets/geometry for all PDG outputs (InOutputs). - // InOuterComponent is the component to attach the created output objects/components to. - static bool CreateAllResultObjectsFromPDGOutputs( - TArray& InOutputs, - const FHoudiniPackageParams& InPackageParams, - UObject* InOuterComponent, - FHoudiniLandscapeExtent& CachedLandscapeExtent, - FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, - FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, - TArray InOutputTypesToProcess={}, - bool bInTreatExistingMaterialsAsUpToDate=false, - bool bInOnlyUseExistingAssets=false, - const TMap* InPreBuiltInstancedOutputPartData=nullptr - ); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +class UHoudiniPDGAssetLink; +class UHoudiniOutput; +class AActor; +class UTOPNode; +class ALandscapeProxy; + +enum class EHoudiniOutputType : uint8; + +struct FHoudiniPackageParams; +struct FTOPWorkResultObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniInstancedOutputPartData; +struct FHoudiniLandscapeExtent; +struct FHoudiniLandscapeReferenceLocation; +struct FHoudiniLandscapeTileSizeInfo; + +struct HOUDINIENGINE_API FHoudiniPDGTranslator +{ + public: + // Create/update assets/geometry for all PDG outputs of InWorkResultObject. This will use + // InWorkResultObject.FilePath to load the BGEO file and update/build outputs. + static bool CreateAllResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray InOutputTypesToProcess={}, + bool bInTreatExistingMaterialsAsUpToDate=false); + + static bool LoadExistingAssetsAsResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray& InOutputs, + TArray InOutputTypesToProcess={}, + const TMap* InPreBuiltInstancedOutputPartData=nullptr); + + // Use the relevant translators to create assets/geometry for all PDG outputs (InOutputs). + // InOuterComponent is the component to attach the created output objects/components to. + static bool CreateAllResultObjectsFromPDGOutputs( + TArray& InOutputs, + const FHoudiniPackageParams& InPackageParams, + UObject* InOuterComponent, + FHoudiniLandscapeExtent& CachedLandscapeExtent, + FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, + FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, + TSet& ClearedLandscapeLayers, + TArray AllInputLandscapes, + TArray InputLandscapesToUpdate, + TArray InOutputTypesToProcess={}, + bool bInTreatExistingMaterialsAsUpToDate=false, + bool bInOnlyUseExistingAssets=false, + const TMap* InPreBuiltInstancedOutputPartData=nullptr + ); +}; diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp index 81960c089..70be4b32e 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp @@ -1,437 +1,437 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPackageParams.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniStringResolver.h" - -#include "PackageTools.h" -#include "ObjectTools.h" -#include "Engine/StaticMesh.h" -#include "UObject/MetaData.h" - -// -FHoudiniPackageParams::FHoudiniPackageParams() -{ - PackageMode = EPackageMode::CookToTemp; - ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - OuterPackage = nullptr; - ObjectName = FString(); - HoudiniAssetName = FString(); - HoudiniAssetActorName = FString(); - - ObjectId = 0; - GeoId = 0; - PartId = 0; - SplitStr = 0; - - ComponentGUID.Invalidate(); - - PDGTOPNetworkName.Empty(); - PDGTOPNodeName.Empty(); - PDGWorkItemIndex = INDEX_NONE; -} - - -// -FHoudiniPackageParams::~FHoudiniPackageParams() -{ - - -} - - -// Returns the object flags corresponding to the current package mode -EObjectFlags -FHoudiniPackageParams::GetObjectFlags() const -{ - if (PackageMode == EPackageMode::CookToTemp) - return RF_Public | RF_Standalone; - else if (PackageMode == EPackageMode::Bake) - return RF_Public | RF_Standalone; - else - return RF_NoFlags; -} - -FString -FHoudiniPackageParams::GetPackageName() const -{ - if (!ObjectName.IsEmpty()) - return ObjectName; - - // If we have PDG infos, generate a name including them - if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkItemIndex >= 0) - { - return FString::Printf( - TEXT("%s_%s_%s_%d_%d_%s"), - *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkItemIndex, PartId, *SplitStr); - } - else - { - // Generate an object name using the HGPO IDs and the HDA name - return FString::Printf(TEXT("%s_%d_%d_%d_%s"), *HoudiniAssetName, ObjectId, GeoId, PartId, *SplitStr); - } -} - -FString -FHoudiniPackageParams::GetPackagePath() const -{ - FString PackagePath = FString(); - switch (PackageMode) - { - case EPackageMode::CookToLevel: - { - // Path to the persistent level - //PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()); - - // In this mode, we'll use the persistent level as our package's outer - // simply use the hda + component guid for the path - // Add a subdir for the HDA - if (!HoudiniAssetName.IsEmpty()) - PackagePath += TEXT("/") + HoudiniAssetName; - // Add a subdir using the owner component GUID if possible - if(ComponentGUID.IsValid()) - PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); - - // TODO: FIX ME!!! - // Old version - // Build the package name - PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()) + - TEXT("/") + - HoudiniAssetName; - } - break; - - case EPackageMode::CookToTemp: - { - // Temporary Folder - PackagePath = TempCookFolder; - // Add a subdir for the HDA - if (!HoudiniAssetName.IsEmpty()) - PackagePath += TEXT("/") + HoudiniAssetName; - // Add a subdir using the owner component GUID if possible - if (ComponentGUID.IsValid()) - PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); - } - break; - - case EPackageMode::Bake: - { - PackagePath = BakeFolder; - } - break; - } - - return PackagePath; -} - -bool -FHoudiniPackageParams::GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter) -{ - OutBakeCounter = 0; - - if (!IsValid(InAsset)) - return false; - - UPackage* Package = InAsset->GetPackage(); - // const FString PackagePathName = Package->GetPathName(); - // FString PackagePathNamePrefix; - // FString BakeCountOrGUID; - // if (!GetPackageNameWithoutBakeCounterOrGUIDSuffix(PackagePathName, PackagePathNamePrefix, BakeCountOrGUID)) - // PackagePathNamePrefix = PackagePathName; - // - // const FString ThisPackageNameBase = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); - // if (!PackagePathNamePrefix.Equals(ThisPackageNameBase)) - // return false; - // - // // Not a valid counter suffix, could be a GUID suffix. Return true since the prefixes match. - // if (BakeCountOrGUID.IsNumeric()) - // OutBakeCounter = FCString::Atoi(*BakeCountOrGUID); - // - // return true; - - if (!IsValid(Package)) - return false; - - UMetaData* MetaData = Package->GetMetaData(); - if (!IsValid(MetaData)) - return false; - - if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER)) - { - FString BakeCounterStr = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER); - BakeCounterStr.TrimStartAndEndInline(); - if (BakeCounterStr.IsNumeric()) - { - OutBakeCounter = FCString::Atoi(*BakeCounterStr); - return true; - } - } - - return false; -} - -bool -FHoudiniPackageParams::GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID) -{ - if (!InAsset) - return false; - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return false; - - UMetaData* MetaData = Package->GetMetaData(); - if (!IsValid(MetaData)) - return false; - - if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) - { - OutGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); - OutGUID.TrimStartAndEndInline(); - if (!OutGUID.IsEmpty()) - return true; - } - - return false; -} - -FString -FHoudiniPackageParams::GetPackageNameExcludingBakeCounter(const UObject* InAsset) -{ - if (!IsValid(InAsset)) - return FString(); - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return FString(); - - FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); - int32 BakeCounter = 0; - if (GetBakeCounterFromBakedAsset(InAsset, BakeCounter)) - { - const FString BakeCounterSuffix = FString::Printf(TEXT("_%d"), BakeCounter); - if (PackageName.EndsWith(BakeCounterSuffix)) - PackageName = PackageName.Mid(0, PackageName.Len() - BakeCounterSuffix.Len()); - } - - return PackageName; -} - -bool -FHoudiniPackageParams::MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const -{ - if (!IsValid(InAsset)) - return false; - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return false; - - const FString InAssetPackagePathName = FPaths::GetPath(Package->GetPathName()) + TEXT("/") + GetPackageNameExcludingBakeCounter(InAsset); - const FString ThisPackagePathName = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); - return InAssetPackagePathName.Equals(ThisPackagePathName); -} - -FString -FHoudiniPackageParams::GetPackageNameExcludingGUID(const UObject* InAsset) -{ - if (!IsValid(InAsset)) - return FString(); - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return FString(); - - FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); - FString GUIDStr; - if (GetGUIDFromTempAsset(InAsset, GUIDStr)) - { - if (PackageName.EndsWith(TEXT("_") + GUIDStr)) - PackageName = PackageName.Mid(0, PackageName.Len() - GUIDStr.Len() - 1); - } - - return PackageName; -} - -UPackage* -FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart) const -{ - // GUID/counter used to differentiate with existing package - int32 BakeCounter = InBakeCounterStart; - FGuid CurrentGuid = FGuid::NewGuid(); - - // Get the appropriate package path/name for this object - FString PackageName = GetPackageName(); - FString PackagePath = GetPackagePath(); - - // Iterate until we find a suitable name for the package - UPackage * NewPackage = nullptr; - while (true) - { - OutPackageName = PackageName; - - // Append the Bake guid/counter to the object name if needed - if (BakeCounter > 0) - { - OutPackageName += (PackageMode == EPackageMode::Bake) - ? TEXT("_") + FString::FromInt(BakeCounter) - : TEXT("_") + CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); - } - - // Build the final package name - FString FinalPackageName = PackagePath + TEXT("/") + OutPackageName; - // Sanitize package name. - FinalPackageName = UPackageTools::SanitizePackageName(FinalPackageName); - - UObject * PackageOuter = nullptr; - /* - // As of UE4.26, it is not possible anymore to create package with a non null outer - // CookToLevel is, anyway, no logner supported in v2. - if (PackageMode == EPackageMode::CookToLevel) - { - // If we are not baking, then use outermost package, since objects within our package - // need to be visible to external operations, such as copy paste. - PackageOuter = OuterPackage; - } - */ - - // If we are set to create new assets, check if a package named similarly already exists - if (ReplaceMode == EPackageReplaceMode::CreateNewAssets) - { - UPackage* FoundPackage = FindPackage(PackageOuter, *FinalPackageName); - if (FoundPackage == nullptr) - { - // Package might not be in memory, check if it exists on disk - FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_Verify | LOAD_NoWarn); - } - - if (FoundPackage && !FoundPackage->IsPendingKill()) - { - // we need to generate a new name for it - CurrentGuid = FGuid::NewGuid(); - BakeCounter++; - continue; - } - } - - // Create actual package. - NewPackage = CreatePackage(*FinalPackageName); - if (IsValid(NewPackage)) - { - // Record bake counter / temp GUID in package metadata - UMetaData* MetaData = NewPackage->GetMetaData(); - if (IsValid(MetaData)) - { - if (PackageMode == EPackageMode::Bake) - { - // HOUDINI_LOG_MESSAGE(TEXT("Recording bake counter in package metadata: %d"), BakeCounter); - MetaData->RootMetaDataMap.Add( - HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER, FString::FromInt(BakeCounter)); - } - else if (CurrentGuid.IsValid()) - { - const FString GuidStr = CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); - // HOUDINI_LOG_MESSAGE(TEXT("Recording temp guid in package metadata: %s"), *GuidStr); - MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_TEMP_GUID, GuidStr); - } - } - } - - break; - } - - return NewPackage; -} - - -// Fixes link error with the template function under -void TemplateFixer() -{ - FHoudiniPackageParams PP; - UStaticMesh* SM = PP.CreateObjectAndPackage(); - UHoudiniStaticMesh* HSM = PP.CreateObjectAndPackage(); - //UMaterial* Mat = PP.CreateObjectAndPackage(); - //UTexture2D* Text = PP.CreateObjectAndPackage(); -} - -template -T* FHoudiniPackageParams::CreateObjectAndPackage() -{ - // Create the package for the object - FString NewObjectName; - UPackage* Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) - return nullptr; - - const FString SanitizedObjectName = ObjectTools::SanitizeObjectName(NewObjectName); - - T* ExistingTypedObject = FindObject(Package, *NewObjectName); - UObject* ExistingObject = FindObject(Package, *NewObjectName); - - if (ExistingTypedObject != nullptr && !ExistingTypedObject->IsPendingKill()) - { - // An object of the appropriate type already exists, update it! - ExistingTypedObject->PreEditChange(nullptr); - } - else if (ExistingObject != nullptr) - { - // Replacing an object of a different type, Delete it first. - const bool bDeleteSucceeded = ObjectTools::DeleteSingleObject(ExistingObject); - if (bDeleteSucceeded) - { - // Force GC so we can cleanly create a new asset (and not do an 'in place' replacement) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - // Create a package for each mesh - Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) - return nullptr; - } - else - { - // failed to delete - return nullptr; - } - } - - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); - - return NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPackageParams.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniStringResolver.h" + +#include "PackageTools.h" +#include "ObjectTools.h" +#include "Engine/StaticMesh.h" +#include "UObject/MetaData.h" + +// +FHoudiniPackageParams::FHoudiniPackageParams() +{ + PackageMode = EPackageMode::CookToTemp; + ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + OuterPackage = nullptr; + ObjectName = FString(); + HoudiniAssetName = FString(); + HoudiniAssetActorName = FString(); + + ObjectId = 0; + GeoId = 0; + PartId = 0; + SplitStr = 0; + + ComponentGUID.Invalidate(); + + PDGTOPNetworkName.Empty(); + PDGTOPNodeName.Empty(); + PDGWorkItemIndex = INDEX_NONE; +} + + +// +FHoudiniPackageParams::~FHoudiniPackageParams() +{ + + +} + + +// Returns the object flags corresponding to the current package mode +EObjectFlags +FHoudiniPackageParams::GetObjectFlags() const +{ + if (PackageMode == EPackageMode::CookToTemp) + return RF_Public | RF_Standalone; + else if (PackageMode == EPackageMode::Bake) + return RF_Public | RF_Standalone; + else + return RF_NoFlags; +} + +FString +FHoudiniPackageParams::GetPackageName() const +{ + if (!ObjectName.IsEmpty()) + return ObjectName; + + // If we have PDG infos, generate a name including them + if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkItemIndex >= 0) + { + return FString::Printf( + TEXT("%s_%s_%s_%d_%d_%s"), + *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkItemIndex, PartId, *SplitStr); + } + else + { + // Generate an object name using the HGPO IDs and the HDA name + return FString::Printf(TEXT("%s_%d_%d_%d_%s"), *HoudiniAssetName, ObjectId, GeoId, PartId, *SplitStr); + } +} + +FString +FHoudiniPackageParams::GetPackagePath() const +{ + FString PackagePath = FString(); + switch (PackageMode) + { + case EPackageMode::CookToLevel: + { + // Path to the persistent level + //PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()); + + // In this mode, we'll use the persistent level as our package's outer + // simply use the hda + component guid for the path + // Add a subdir for the HDA + if (!HoudiniAssetName.IsEmpty()) + PackagePath += TEXT("/") + HoudiniAssetName; + // Add a subdir using the owner component GUID if possible + if(ComponentGUID.IsValid()) + PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); + + // TODO: FIX ME!!! + // Old version + // Build the package name + PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()) + + TEXT("/") + + HoudiniAssetName; + } + break; + + case EPackageMode::CookToTemp: + { + // Temporary Folder + PackagePath = TempCookFolder; + // Add a subdir for the HDA + if (!HoudiniAssetName.IsEmpty()) + PackagePath += TEXT("/") + HoudiniAssetName; + // Add a subdir using the owner component GUID if possible + if (ComponentGUID.IsValid()) + PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); + } + break; + + case EPackageMode::Bake: + { + PackagePath = BakeFolder; + } + break; + } + + return PackagePath; +} + +bool +FHoudiniPackageParams::GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter) +{ + OutBakeCounter = 0; + + if (!IsValid(InAsset)) + return false; + + UPackage* Package = InAsset->GetPackage(); + // const FString PackagePathName = Package->GetPathName(); + // FString PackagePathNamePrefix; + // FString BakeCountOrGUID; + // if (!GetPackageNameWithoutBakeCounterOrGUIDSuffix(PackagePathName, PackagePathNamePrefix, BakeCountOrGUID)) + // PackagePathNamePrefix = PackagePathName; + // + // const FString ThisPackageNameBase = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); + // if (!PackagePathNamePrefix.Equals(ThisPackageNameBase)) + // return false; + // + // // Not a valid counter suffix, could be a GUID suffix. Return true since the prefixes match. + // if (BakeCountOrGUID.IsNumeric()) + // OutBakeCounter = FCString::Atoi(*BakeCountOrGUID); + // + // return true; + + if (!IsValid(Package)) + return false; + + UMetaData* MetaData = Package->GetMetaData(); + if (!IsValid(MetaData)) + return false; + + if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER)) + { + FString BakeCounterStr = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER); + BakeCounterStr.TrimStartAndEndInline(); + if (BakeCounterStr.IsNumeric()) + { + OutBakeCounter = FCString::Atoi(*BakeCounterStr); + return true; + } + } + + return false; +} + +bool +FHoudiniPackageParams::GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID) +{ + if (!InAsset) + return false; + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return false; + + UMetaData* MetaData = Package->GetMetaData(); + if (!IsValid(MetaData)) + return false; + + if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) + { + OutGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); + OutGUID.TrimStartAndEndInline(); + if (!OutGUID.IsEmpty()) + return true; + } + + return false; +} + +FString +FHoudiniPackageParams::GetPackageNameExcludingBakeCounter(const UObject* InAsset) +{ + if (!IsValid(InAsset)) + return FString(); + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return FString(); + + FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); + int32 BakeCounter = 0; + if (GetBakeCounterFromBakedAsset(InAsset, BakeCounter)) + { + const FString BakeCounterSuffix = FString::Printf(TEXT("_%d"), BakeCounter); + if (PackageName.EndsWith(BakeCounterSuffix)) + PackageName = PackageName.Mid(0, PackageName.Len() - BakeCounterSuffix.Len()); + } + + return PackageName; +} + +bool +FHoudiniPackageParams::MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const +{ + if (!IsValid(InAsset)) + return false; + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return false; + + const FString InAssetPackagePathName = FPaths::GetPath(Package->GetPathName()) + TEXT("/") + GetPackageNameExcludingBakeCounter(InAsset); + const FString ThisPackagePathName = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); + return InAssetPackagePathName.Equals(ThisPackagePathName); +} + +FString +FHoudiniPackageParams::GetPackageNameExcludingGUID(const UObject* InAsset) +{ + if (!IsValid(InAsset)) + return FString(); + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return FString(); + + FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); + FString GUIDStr; + if (GetGUIDFromTempAsset(InAsset, GUIDStr)) + { + if (PackageName.EndsWith(TEXT("_") + GUIDStr)) + PackageName = PackageName.Mid(0, PackageName.Len() - GUIDStr.Len() - 1); + } + + return PackageName; +} + +UPackage* +FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart) const +{ + // GUID/counter used to differentiate with existing package + int32 BakeCounter = InBakeCounterStart; + FGuid CurrentGuid = FGuid::NewGuid(); + + // Get the appropriate package path/name for this object + FString PackageName = GetPackageName(); + FString PackagePath = GetPackagePath(); + + // Iterate until we find a suitable name for the package + UPackage * NewPackage = nullptr; + while (true) + { + OutPackageName = PackageName; + + // Append the Bake guid/counter to the object name if needed + if (BakeCounter > 0) + { + OutPackageName += (PackageMode == EPackageMode::Bake) + ? TEXT("_") + FString::FromInt(BakeCounter) + : TEXT("_") + CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); + } + + // Build the final package name + FString FinalPackageName = PackagePath + TEXT("/") + OutPackageName; + // Sanitize package name. + FinalPackageName = UPackageTools::SanitizePackageName(FinalPackageName); + + UObject * PackageOuter = nullptr; + /* + // As of UE4.26, it is not possible anymore to create package with a non null outer + // CookToLevel is, anyway, no logner supported in v2. + if (PackageMode == EPackageMode::CookToLevel) + { + // If we are not baking, then use outermost package, since objects within our package + // need to be visible to external operations, such as copy paste. + PackageOuter = OuterPackage; + } + */ + + // If we are set to create new assets, check if a package named similarly already exists + if (ReplaceMode == EPackageReplaceMode::CreateNewAssets) + { + UPackage* FoundPackage = FindPackage(PackageOuter, *FinalPackageName); + if (FoundPackage == nullptr) + { + // Package might not be in memory, check if it exists on disk + FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_Verify | LOAD_NoWarn); + } + + if (FoundPackage && !FoundPackage->IsPendingKill()) + { + // we need to generate a new name for it + CurrentGuid = FGuid::NewGuid(); + BakeCounter++; + continue; + } + } + + // Create actual package. + NewPackage = CreatePackage(*FinalPackageName); + if (IsValid(NewPackage)) + { + // Record bake counter / temp GUID in package metadata + UMetaData* MetaData = NewPackage->GetMetaData(); + if (IsValid(MetaData)) + { + if (PackageMode == EPackageMode::Bake) + { + // HOUDINI_LOG_MESSAGE(TEXT("Recording bake counter in package metadata: %d"), BakeCounter); + MetaData->RootMetaDataMap.Add( + HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER, FString::FromInt(BakeCounter)); + } + else if (CurrentGuid.IsValid()) + { + const FString GuidStr = CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); + // HOUDINI_LOG_MESSAGE(TEXT("Recording temp guid in package metadata: %s"), *GuidStr); + MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_TEMP_GUID, GuidStr); + } + } + } + + break; + } + + return NewPackage; +} + + +// Fixes link error with the template function under +void TemplateFixer() +{ + FHoudiniPackageParams PP; + UStaticMesh* SM = PP.CreateObjectAndPackage(); + UHoudiniStaticMesh* HSM = PP.CreateObjectAndPackage(); + //UMaterial* Mat = PP.CreateObjectAndPackage(); + //UTexture2D* Text = PP.CreateObjectAndPackage(); +} + +template +T* FHoudiniPackageParams::CreateObjectAndPackage() +{ + // Create the package for the object + FString NewObjectName; + UPackage* Package = CreatePackageForObject(NewObjectName); + if (!Package || Package->IsPendingKill()) + return nullptr; + + const FString SanitizedObjectName = ObjectTools::SanitizeObjectName(NewObjectName); + + T* ExistingTypedObject = FindObject(Package, *NewObjectName); + UObject* ExistingObject = FindObject(Package, *NewObjectName); + + if (ExistingTypedObject != nullptr && !ExistingTypedObject->IsPendingKill()) + { + // An object of the appropriate type already exists, update it! + ExistingTypedObject->PreEditChange(nullptr); + } + else if (ExistingObject != nullptr) + { + // Replacing an object of a different type, Delete it first. + const bool bDeleteSucceeded = ObjectTools::DeleteSingleObject(ExistingObject); + if (bDeleteSucceeded) + { + // Force GC so we can cleanly create a new asset (and not do an 'in place' replacement) + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Create a package for each mesh + Package = CreatePackageForObject(NewObjectName); + if (!Package || Package->IsPendingKill()) + return nullptr; + } + else + { + // failed to delete + return nullptr; + } + } + + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); + + return NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); +} + diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.h b/Source/HoudiniEngine/Private/HoudiniPackageParams.h index 380035125..d4b947635 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.h +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.h @@ -1,240 +1,240 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" -#include "UObject/ObjectMacros.h" -#include "Engine/World.h" -#include "Misc/Paths.h" - -#include "HoudiniStringResolver.h" - -#include "HoudiniPackageParams.generated.h" - -class UStaticMesh; - -UENUM() -enum class EPackageMode : int8 -{ - CookToLevel, - CookToTemp, - Bake -}; - -UENUM() -enum class EPackageReplaceMode : int8 -{ - CreateNewAssets, - ReplaceExistingAssets -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPackageParams -{ -public: - GENERATED_BODY(); - - // - FHoudiniPackageParams(); - // - ~FHoudiniPackageParams(); - - // Helper functions returning the default behavior expected when cooking mesh - static EPackageMode GetDefaultStaticMeshesCookMode() { return EPackageMode::CookToTemp; }; - // Helper functions returning the default behavior expected when cooking materials or textures - static EPackageMode GetDefaultMaterialAndTextureCookMode() { return EPackageMode::CookToTemp; }; - // Helper functions returning the default behavior for replacing existing package - static EPackageReplaceMode GetDefaultReplaceMode() { return EPackageReplaceMode::ReplaceExistingAssets; }; - - // Returns the name for the package depending on the mode - FString GetPackageName() const; - // Returns the package's path depending on the mode - FString GetPackagePath() const; - // Returns the object flags corresponding to the current package mode - EObjectFlags GetObjectFlags() const; - - // Get the bake counter for InAsset's package metadata. Return true if the counter was found, false otherwise. - static bool GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter); - - // Get the GUID for a temp asset. - static bool GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID); - - // Get package name without bake counter - static FString GetPackageNameExcludingBakeCounter(const UObject* InAsset); - - // Get package name without temp GUID suffix - static FString GetPackageNameExcludingGUID(const UObject* InAsset); - - // Returns true if these package params generate the same package path and name as InAsset's package path name (with - // any potential bake counters stripped during comparison) - bool MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const; - - // Helper function to create a Package for a given object - UPackage* CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart=0) const; - - // Helper function to create an object and its package - template T* CreateObjectAndPackage(); - - - // The current cook/baking mode - UPROPERTY() - EPackageMode PackageMode; - // How to handle existing assets? replace or rename? - UPROPERTY() - EPackageReplaceMode ReplaceMode; - - // When cooking in bake mode - folder to create assets in - UPROPERTY() - FString BakeFolder; - // When cooking in temp mode - folder to create assets in - UPROPERTY() - FString TempCookFolder; - - // Package to save to - UPROPERTY() - UObject* OuterPackage; - - // Name of the package we want to create - // If null, we'll generate one from: - // (without PDG) ASSET_OBJ_GEO_PART_SPLIT, - // (with PDG) ASSET_TOPNET_TOPNODE_WORKITEMINDEX_PART_SPLIT - UPROPERTY() - FString ObjectName; - - // Name of the HDA - UPROPERTY() - FString HoudiniAssetName; - - // Name of actor that is managing an instance of the HDA - UPROPERTY() - FString HoudiniAssetActorName; - - // - UPROPERTY() - int32 ObjectId; - // - UPROPERTY() - int32 GeoId; - // - UPROPERTY() - int32 PartId; - // - UPROPERTY() - FString SplitStr; - - // GUID used for the owner - UPROPERTY() - FGuid ComponentGUID; - - // For PDG temporary outputs: the TOP network name - UPROPERTY() - FString PDGTOPNetworkName; - // For PDG temporary outputs: the TOP node name - UPROPERTY() - FString PDGTOPNodeName; - // For PDG temporary outputs: the work item index of the TOP node - UPROPERTY() - int32 PDGWorkItemIndex; - - ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. - //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; - //FString GetBakeFolderArgument(ERuntimePackageMode PackageMode) const; - - //// Return the output path as either the temp or bake path, depending on the package mode. - //FString GetOutputFolderForPackageMode(ERuntimePackageMode PackageMode) const; - - /* - * Build a "standard" set of string formatting arguments that - * is typically used across HoudiniEngine path naming outputs. - * Note that each output type may contain additional named arguments - * that are not listed here. - * {out} - The output directory (varies depending on the package mode). - * {pkg} - The path to the destination package (varies depending on the package mode). - * {world} - Path the directory that contains the world. - * {hda_name} - Name of the HDA - * {guid} - guid of the HDA component - * @param PackageParams The output path for the current build mode (Temp / Bake). - * @param HACWorld The world in which the HDA component lives (typically Editor world). - * @param OutArgs The generated named arguments to be used for string formatting. - */ - - // Populate a map of named arguments from this FHoudiniPackageParams. - template - void UpdateTokensFromParams( - const UWorld* WorldContext, - const UHoudiniAssetComponent* HAC, - TMap& OutTokens) const - { - UpdateOutputPathTokens(PackageMode, OutTokens); - - if(IsValid(WorldContext)) - OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); - - if(HAC && HAC->GetOutermost()) - OutTokens.Add("hda_level", ValueT( HAC->GetOutermost()->GetPathName() )); - - OutTokens.Add("object_name", ValueT( ObjectName )); - OutTokens.Add("object_id", ValueT( FString::FromInt(ObjectId) )); - OutTokens.Add("geo_id", ValueT( FString::FromInt(GeoId) )); - OutTokens.Add("part_id", ValueT( FString::FromInt(PartId) )); - OutTokens.Add("split_str", ValueT( SplitStr)); - OutTokens.Add("hda_name", ValueT( HoudiniAssetName )); - OutTokens.Add("hda_actor_name", ValueT( HoudiniAssetActorName )); - OutTokens.Add("pdg_topnet_name", ValueT( PDGTOPNetworkName )); - OutTokens.Add("pdg_topnode_name", ValueT( PDGTOPNodeName )); - OutTokens.Add("pdg_workitem_index", ValueT( FString::FromInt(PDGWorkItemIndex) )); - OutTokens.Add("guid", ValueT( ComponentGUID.ToString() )); - } - - template - void UpdateOutputPathTokens(EPackageMode InPackageMode, TMap& OutTokens) const - { - const FString PackagePath = GetPackagePath(); - - OutTokens.Add("temp", ValueT(TempCookFolder)); - OutTokens.Add("bake", ValueT(BakeFolder)); - - // `out_basepath` is useful if users want to organize their cook/bake assets - // different to the convention defined by GetPackagePath(). This would typically - // be combined with `unreal_level_path` during level path resolves. - switch (InPackageMode) - { - case EPackageMode::CookToTemp: - case EPackageMode::CookToLevel: - OutTokens.Add("out_basepath", ValueT(TempCookFolder)); - break; - case EPackageMode::Bake: - OutTokens.Add("out_basepath", ValueT(BakeFolder)); - break; - } - - OutTokens.Add("out", ValueT(PackagePath)); - } - -}; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" +#include "UObject/ObjectMacros.h" +#include "Engine/World.h" +#include "Misc/Paths.h" + +#include "HoudiniStringResolver.h" + +#include "HoudiniPackageParams.generated.h" + +class UStaticMesh; + +UENUM() +enum class EPackageMode : int8 +{ + CookToLevel, + CookToTemp, + Bake +}; + +UENUM() +enum class EPackageReplaceMode : int8 +{ + CreateNewAssets, + ReplaceExistingAssets +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPackageParams +{ +public: + GENERATED_BODY(); + + // + FHoudiniPackageParams(); + // + ~FHoudiniPackageParams(); + + // Helper functions returning the default behavior expected when cooking mesh + static EPackageMode GetDefaultStaticMeshesCookMode() { return EPackageMode::CookToTemp; }; + // Helper functions returning the default behavior expected when cooking materials or textures + static EPackageMode GetDefaultMaterialAndTextureCookMode() { return EPackageMode::CookToTemp; }; + // Helper functions returning the default behavior for replacing existing package + static EPackageReplaceMode GetDefaultReplaceMode() { return EPackageReplaceMode::ReplaceExistingAssets; }; + + // Returns the name for the package depending on the mode + FString GetPackageName() const; + // Returns the package's path depending on the mode + FString GetPackagePath() const; + // Returns the object flags corresponding to the current package mode + EObjectFlags GetObjectFlags() const; + + // Get the bake counter for InAsset's package metadata. Return true if the counter was found, false otherwise. + static bool GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter); + + // Get the GUID for a temp asset. + static bool GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID); + + // Get package name without bake counter + static FString GetPackageNameExcludingBakeCounter(const UObject* InAsset); + + // Get package name without temp GUID suffix + static FString GetPackageNameExcludingGUID(const UObject* InAsset); + + // Returns true if these package params generate the same package path and name as InAsset's package path name (with + // any potential bake counters stripped during comparison) + bool MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const; + + // Helper function to create a Package for a given object + UPackage* CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart=0) const; + + // Helper function to create an object and its package + template T* CreateObjectAndPackage(); + + + // The current cook/baking mode + UPROPERTY() + EPackageMode PackageMode; + // How to handle existing assets? replace or rename? + UPROPERTY() + EPackageReplaceMode ReplaceMode; + + // When cooking in bake mode - folder to create assets in + UPROPERTY() + FString BakeFolder; + // When cooking in temp mode - folder to create assets in + UPROPERTY() + FString TempCookFolder; + + // Package to save to + UPROPERTY() + UObject* OuterPackage; + + // Name of the package we want to create + // If null, we'll generate one from: + // (without PDG) ASSET_OBJ_GEO_PART_SPLIT, + // (with PDG) ASSET_TOPNET_TOPNODE_WORKITEMINDEX_PART_SPLIT + UPROPERTY() + FString ObjectName; + + // Name of the HDA + UPROPERTY() + FString HoudiniAssetName; + + // Name of actor that is managing an instance of the HDA + UPROPERTY() + FString HoudiniAssetActorName; + + // + UPROPERTY() + int32 ObjectId; + // + UPROPERTY() + int32 GeoId; + // + UPROPERTY() + int32 PartId; + // + UPROPERTY() + FString SplitStr; + + // GUID used for the owner + UPROPERTY() + FGuid ComponentGUID; + + // For PDG temporary outputs: the TOP network name + UPROPERTY() + FString PDGTOPNetworkName; + // For PDG temporary outputs: the TOP node name + UPROPERTY() + FString PDGTOPNodeName; + // For PDG temporary outputs: the work item index of the TOP node + UPROPERTY() + int32 PDGWorkItemIndex; + + ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. + //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; + //FString GetBakeFolderArgument(ERuntimePackageMode PackageMode) const; + + //// Return the output path as either the temp or bake path, depending on the package mode. + //FString GetOutputFolderForPackageMode(ERuntimePackageMode PackageMode) const; + + /* + * Build a "standard" set of string formatting arguments that + * is typically used across HoudiniEngine path naming outputs. + * Note that each output type may contain additional named arguments + * that are not listed here. + * {out} - The output directory (varies depending on the package mode). + * {pkg} - The path to the destination package (varies depending on the package mode). + * {world} - Path the directory that contains the world. + * {hda_name} - Name of the HDA + * {guid} - guid of the HDA component + * @param PackageParams The output path for the current build mode (Temp / Bake). + * @param HACWorld The world in which the HDA component lives (typically Editor world). + * @param OutArgs The generated named arguments to be used for string formatting. + */ + + // Populate a map of named arguments from this FHoudiniPackageParams. + template + void UpdateTokensFromParams( + const UWorld* WorldContext, + const UHoudiniAssetComponent* HAC, + TMap& OutTokens) const + { + UpdateOutputPathTokens(PackageMode, OutTokens); + + if(IsValid(WorldContext)) + OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); + + if(HAC && HAC->GetOutermost()) + OutTokens.Add("hda_level", ValueT( HAC->GetOutermost()->GetPathName() )); + + OutTokens.Add("object_name", ValueT( ObjectName )); + OutTokens.Add("object_id", ValueT( FString::FromInt(ObjectId) )); + OutTokens.Add("geo_id", ValueT( FString::FromInt(GeoId) )); + OutTokens.Add("part_id", ValueT( FString::FromInt(PartId) )); + OutTokens.Add("split_str", ValueT( SplitStr)); + OutTokens.Add("hda_name", ValueT( HoudiniAssetName )); + OutTokens.Add("hda_actor_name", ValueT( HoudiniAssetActorName )); + OutTokens.Add("pdg_topnet_name", ValueT( PDGTOPNetworkName )); + OutTokens.Add("pdg_topnode_name", ValueT( PDGTOPNodeName )); + OutTokens.Add("pdg_workitem_index", ValueT( FString::FromInt(PDGWorkItemIndex) )); + OutTokens.Add("guid", ValueT( ComponentGUID.ToString() )); + } + + template + void UpdateOutputPathTokens(EPackageMode InPackageMode, TMap& OutTokens) const + { + const FString PackagePath = GetPackagePath(); + + OutTokens.Add("temp", ValueT(TempCookFolder)); + OutTokens.Add("bake", ValueT(BakeFolder)); + + // `out_basepath` is useful if users want to organize their cook/bake assets + // different to the convention defined by GetPackagePath(). This would typically + // be combined with `unreal_level_path` during level path resolves. + switch (InPackageMode) + { + case EPackageMode::CookToTemp: + case EPackageMode::CookToLevel: + OutTokens.Add("out_basepath", ValueT(TempCookFolder)); + break; + case EPackageMode::Bake: + OutTokens.Add("out_basepath", ValueT(BakeFolder)); + break; + } + + OutTokens.Add("out", ValueT(PackagePath)); + } + +}; + + diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp index b95d8bb84..d9871822e 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp @@ -1,3039 +1,3481 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniInput.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniParameter.h" -#include "HoudiniAssetComponent.h" - - -// Default values for certain UI min and max parameter values -#define HAPI_UNREAL_PARAM_INT_UI_MIN 0 -#define HAPI_UNREAL_PARAM_INT_UI_MAX 10 -#define HAPI_UNREAL_PARAM_FLOAT_UI_MIN 0.0f -#define HAPI_UNREAL_PARAM_FLOAT_UI_MAX 10.0f - -// Some default parameter name -#define HAPI_UNREAL_PARAM_TRANSLATE "t" -#define HAPI_UNREAL_PARAM_ROTATE "r" -#define HAPI_UNREAL_PARAM_SCALE "s" -#define HAPI_UNREAL_PARAM_PIVOT "p" -#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" - -// -bool -FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // When recooking/rebuilding the HDA, force a full update of all params - bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); - - TArray NewParameters; - if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, true, bForceFullUpdate)) - { - /* - // DO NOT MANUALLY DESTROY THE OLD/DANGLING PARAMETERS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - - // Destroy old/dangling parameters - for (auto& OldParm : HAC->Parameters) - { - if (!OldParm || OldParm->IsPendingKill()) - continue; - - OldParm->ConditionalBeginDestroy(); - OldParm = nullptr; - } - */ - - // Replace with the new parameters - HAC->Parameters = NewParameters; - } - - - return true; -} - -bool -FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) -{ - // Call OnPreCook for all parameters. - // Parameters can use this to ensure that any cached / non-cooking state is properly - // synced before the cook starts (Looking at you, ramp parameters!) - for (UHoudiniParameter* Param : HAC->Parameters) - { - if (!Param || Param->IsPendingKill()) - continue; - - Param->OnPreCook(); - } - - return true; -} - -// -bool -FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Update all the parameters using the loaded parameter object - // We set "UpdateValues" to false because we do not want to "read" the parameter value - // from Houdini but keep the loaded value - - // Share AssetInfo if needed - bool bNeedToFetchAssetInfo = true; - HAPI_AssetInfo AssetInfo; - - // This is the first cook on loading after a save or duplication - for (int32 Idx = 0; Idx < HAC->Parameters.Num(); ++Idx) - { - UHoudiniParameter* Param = HAC->Parameters[Idx]; - - if (!Param || Param->IsPendingKill()) - continue; - - switch(Param->GetParameterType()) - { - case EHoudiniParameterType::ColorRamp: - case EHoudiniParameterType::FloatRamp: - case EHoudiniParameterType::MultiParm: - { - // We need to sync the Ramp parameters first, so that their child parameters can be kept - if (bNeedToFetchAssetInfo) - { - FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &AssetInfo); - bNeedToFetchAssetInfo = false; - } - - // TODO: Simplify this, should be handled in BuildAllParameters - SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, AssetInfo); - } - break; - - case EHoudiniParameterType::Button: - case EHoudiniParameterType::ButtonStrip: - { - // Do not trigger buttons upon loading - Param->MarkChanged(false); - } - break; - - default: - break; - } - } - - // When recooking/rebuilding the HDA, force a full update of all params - bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); - - // This call to BuildAllParameters will keep all the loaded parameters (in the HAC's Parameters array) - // that are still present in the HDA, and keep their loaded value. - TArray NewParameters; - if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, false, bForceFullUpdate)) - { - /* - // DO NOT DESTROY OLD PARAMS MANUALLY HERE - // This causes crashes upon duplication due to uncollected zombie objects... - // GC is supposed to handle this by itself - // Destroy old/dangling parameters - for (auto& OldParm : HAC->Parameters) - { - if (!OldParm || OldParm->IsPendingKill()) - continue; - - OldParm->ConditionalBeginDestroy(); - OldParm = nullptr; - } - */ - - // Simply replace with the new parameters - HAC->Parameters = NewParameters; - } - - return true; -} - -bool -FHoudiniParameterTranslator::BuildAllParameters( - const HAPI_NodeId& AssetId, - class UObject* Outer, - TArray& CurrentParameters, - TArray& NewParameters, - const bool& bUpdateValues, - const bool& InForceFullUpdate) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Get the asset's info - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HAPI_Result Result = FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo); - - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); - - NewParameters.Empty(); - if (NodeInfo.parmCount == 0) - { - // The asset doesnt have any parameter, we're done. - return true; - } - else if (NodeInfo.parmCount < 0) - { - // Invalid parm count - return false; - } - - - - // Retrieve all the parameter infos. - TArray ParmInfos; - ParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); - - - // Create a name lookup cache for the current parameters - // Use an array has in some cases, multiple parameters can have the same name! - TMap> CurrentParametersByName; - CurrentParametersByName.Reserve(CurrentParameters.Num()); - for (const auto& Parm : CurrentParameters) - { - if (!IsValid(Parm)) - continue; - - FString ParmName = Parm->GetParameterName(); - TArray* FoundParmArray = CurrentParametersByName.Find(ParmName); - if (!FoundParmArray) - { - // Create a new array - TArray ParmArray; - ParmArray.Add(Parm); - - // add the new array to the map - CurrentParametersByName.Add(ParmName, ParmArray); - } - else - { - // add this parameter to the existing array - FoundParmArray->Add(Parm); - } - } - - // Create properties for parameters. - TArray NewParmIds; - TArray AllMultiParams; - for (int32 ParamIdx = 0; ParamIdx < NodeInfo.parmCount; ++ParamIdx) - { - // Retrieve param info at this index. - const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; - - // If the parameter is corrupt, skip it - if (ParmInfo.id < 0 || ParmInfo.childIndex < 0) - { - HOUDINI_LOG_WARNING(TEXT("Corrupt parameter %d detected, skipping."), ParamIdx); - continue; - } - - // If the parameter is invisible, skip it. - //if (ParmInfo.invisible) - // continue; - - // Check if any parent folder of this parameter is invisible - bool SkipParm = false; - HAPI_ParmId ParentId = ParmInfo.parentId; - while (ParentId > 0 && !SkipParm) - { - if (const HAPI_ParmInfo* ParentInfoPtr = ParmInfos.FindByPredicate([=](const HAPI_ParmInfo& Info) { - return Info.id == ParentId; - })) - { - if (ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER) - SkipParm = true; - ParentId = ParentInfoPtr->parentId; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not find parent of parameter %d"), ParmInfo.id); - SkipParm = true; - } - } - - if (SkipParm) - continue; - - // See if this parameter has already been created. - // We can't use the HAPI_ParmId because it is not unique to parameter instances, - // so instead, try to find the existing parameter by name using the lookup table - FString NewParmName; - FHoudiniEngineString(ParmInfo.nameSH).ToFString(NewParmName); - - EHoudiniParameterType ParmType = EHoudiniParameterType::Invalid; - FHoudiniParameterTranslator::GetParmTypeFromParmInfo(ParmInfo, ParmType); - - // Not using the name lookup map! - UHoudiniParameter* FoundHoudiniParameter = nullptr; - TArray* MatchingParameters = CurrentParametersByName.Find(NewParmName); - if ((ParmType != EHoudiniParameterType::Invalid) && MatchingParameters) - { - //for (auto& CurrentParm : *MatchingParameters) - for(int32 Idx = MatchingParameters->Num() - 1; Idx >= 0; Idx--) - { - UHoudiniParameter* CurrentParm = (*MatchingParameters)[Idx]; - if (!CurrentParm) - continue; - - // First Check the parameter types match - if (ParmType != CurrentParm->GetParameterType()) - { - // Types do not match - continue; - } - - // Then, make sure the tuple size hasn't changed - if (CurrentParm->GetTupleSize() != ParmInfo.size) - { - // Tuple do not match - continue; - } - - if (!CheckParameterTypeAndClassMatch(CurrentParm, ParmType)) - { - // Wrong class - continue; - } - - // We can reuse this parameter - FoundHoudiniParameter = CurrentParm; - - // Remove it from the array/map - MatchingParameters->RemoveAt(Idx); - if (MatchingParameters->Num() <= 0) - CurrentParametersByName.Remove(NewParmName); - - break; - } - } - - UHoudiniParameter * HoudiniAssetParameter = nullptr; - if (FoundHoudiniParameter) - { - // We can reuse the parameter we found - HoudiniAssetParameter = FoundHoudiniParameter; - - // Transfer param object from current map to new map - CurrentParameters.Remove(HoudiniAssetParameter); - - // Do a fast update of this parameter - if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, InForceFullUpdate, bUpdateValues)) - continue; - - // Reset the states of ramp parameters. - switch (HoudiniAssetParameter->GetParameterType()) - { - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); - if (FloatRampParam) - { - UHoudiniAssetComponent* ParentHAC = Cast(FloatRampParam->GetOuter()); - if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) - FloatRampParam->bCaching = false; - } - - break; - } - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); - if (ColorRampParam) - { - UHoudiniAssetComponent* ParentHAC = Cast(ColorRampParam->GetOuter()); - if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) - ColorRampParam->bCaching = false; - } - - break; - } - } - - } - else - { - // Create a new parameter object of the appropriate type - HoudiniAssetParameter = CreateTypedParameter(Outer, ParmType, NewParmName); - // Fully update this parameter - if (!FHoudiniParameterTranslator::UpdateParameterFromInfo(HoudiniAssetParameter, AssetInfo.nodeId, ParmInfo, true, true)) - continue; - } - - // Add the new parameters - NewParameters.Add(HoudiniAssetParameter); - NewParmIds.Add(ParmInfo.id); - - // Check if the parameter is a direct child of a multiparam. - if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::MultiParm) - AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); - - if (AllMultiParams.Contains(HoudiniAssetParameter->GetParentParmId())) - { - HoudiniAssetParameter->SetIsDirectChildOfMultiParm(true); - - // Treat the folderlist whose direct parent is a multi param as a multi param too. - if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::FolderList) - AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); - } - } - - // Assign folder type to all folderlists, - // if the first child of the folderlist is Tab or Radio button, set the bIsTabMenu of the folderlistParam to be true, otherwise false - for (int32 Idx = 0; Idx < NewParameters.Num(); ++Idx) - { - UHoudiniParameter * CurParam = NewParameters[Idx]; - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (CurParam->GetParameterType() == EHoudiniParameterType::FolderList) - { - UHoudiniParameterFolderList* CurFolderList = Cast(CurParam); - if (!CurFolderList || CurFolderList->IsPendingKill()) - continue; - - int32 FirstChildIdx = Idx + 1; - if (!NewParameters.IsValidIndex(FirstChildIdx)) - continue; - - UHoudiniParameterFolder* FirstChildFolder = Cast(NewParameters[FirstChildIdx]); - if (!FirstChildFolder || FirstChildFolder->IsPendingKill()) - continue; - - if (FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Radio || - FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Tabs) - { - // If this is the first time build - if (!CurFolderList->IsTabMenu()) - { - // Set the folderlist to be tabs - CurFolderList->SetIsTabMenu(true); - // Select the first child tab folder by default. - FirstChildFolder->SetChosen(true); - } - } - else - CurFolderList->SetIsTabMenu(false); - } - } - - FHoudiniEngineUtils::UpdateEditorProperties(Outer, true); - - return true; -} - - -void -FHoudiniParameterTranslator::GetParmTypeFromParmInfo( - const HAPI_ParmInfo& ParmInfo, - EHoudiniParameterType& ParmType) -{ - ParmType = EHoudiniParameterType::Invalid; - //ParmValueType = EHoudiniParameterValueType::Invalid; - - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_BUTTON: - ParmType = EHoudiniParameterType::Button; - //ParmValueType = EHoudiniParameterValueType::Int; - break; - - case HAPI_PARMTYPE_STRING: - { - if (ParmInfo.choiceCount > 0) - { - ParmType = EHoudiniParameterType::StringChoice; - //ParmValueType = EHoudiniParameterValueType::String; - } - else - { - ParmType = EHoudiniParameterType::String; - //ParmValueType = EHoudiniParameterValueType::String; - } - break; - } - - case HAPI_PARMTYPE_INT: - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_BUTTONSTRIP) - { - ParmType = EHoudiniParameterType::ButtonStrip; - break; - } - - if (ParmInfo.choiceCount > 0) - { - ParmType = EHoudiniParameterType::IntChoice; - //ParmValueType = EHoudiniParameterValueType::Int; - } - else - { - ParmType = EHoudiniParameterType::Int; - //ParmValueType = EHoudiniParameterValueType::Int; - } - break; - } - - case HAPI_PARMTYPE_FLOAT: - { - ParmType = EHoudiniParameterType::Float; - //ParmValueType = EHoudiniParameterValueType::Float; - break; - } - - case HAPI_PARMTYPE_TOGGLE: - { - ParmType = EHoudiniParameterType::Toggle; - //ParmValueType = EHoudiniParameterValueType::Int; - break; - } - - case HAPI_PARMTYPE_COLOR: - { - ParmType = EHoudiniParameterType::Color; - //ParmValueType = EHoudiniParameterValueType::Float; - break; - } - - case HAPI_PARMTYPE_LABEL: - { - ParmType = EHoudiniParameterType::Label; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_SEPARATOR: - { - ParmType = EHoudiniParameterType::Separator; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - case HAPI_PARMTYPE_FOLDERLIST: - { - ParmType = EHoudiniParameterType::FolderList; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - // Treat radio folders as tab folders for now - case HAPI_PARMTYPE_FOLDERLIST_RADIO: - { - ParmType = EHoudiniParameterType::FolderList; - break; - } - - case HAPI_PARMTYPE_FOLDER: - { - ParmType = EHoudiniParameterType::Folder; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - case HAPI_PARMTYPE_MULTIPARMLIST: - { - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType) - { - ParmType = EHoudiniParameterType::FloatRamp; - //ParmValueType = EHoudiniParameterValueType::Float; - } - else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - { - ParmType = EHoudiniParameterType::ColorRamp; - //ParmValueType = EHoudiniParameterValueType::Float; - } - else - { - ParmType = EHoudiniParameterType::MultiParm; - //ParmValueType = EHoudiniParameterValueType::Int; - } - break; - } - - case HAPI_PARMTYPE_PATH_FILE: - { - ParmType = EHoudiniParameterType::File; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_DIR: - { - ParmType = EHoudiniParameterType::FileDir; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_GEO: - { - ParmType = EHoudiniParameterType::FileGeo; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - { - ParmType = EHoudiniParameterType::FileImage; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_NODE: - { - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - ParmType = EHoudiniParameterType::Input; - } - else - { - ParmType = EHoudiniParameterType::String; - } - break; - } - - default: - { - // Just ignore unsupported types for now. - HOUDINI_LOG_WARNING(TEXT("Parameter Type (%d) is unsupported"), static_cast(ParmInfo.type)); - break; - } - } -} - -UClass* -FHoudiniParameterTranslator::GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo) -{ - UClass* FoundClass = nullptr; - - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_STRING: - if (!ParmInfo.choiceCount) - FoundClass = UHoudiniParameterString::StaticClass(); - else - FoundClass = UHoudiniParameterChoice ::StaticClass(); - break; - - case HAPI_PARMTYPE_INT: - if (!ParmInfo.choiceCount) - FoundClass = UHoudiniParameterInt::StaticClass(); - else - FoundClass = UHoudiniParameterChoice::StaticClass(); - break; - - case HAPI_PARMTYPE_FLOAT: - FoundClass = UHoudiniParameterFloat::StaticClass(); - break; - - case HAPI_PARMTYPE_TOGGLE: - FoundClass = UHoudiniParameterToggle::StaticClass(); - break; - - case HAPI_PARMTYPE_COLOR: - FoundClass = UHoudiniParameterColor::StaticClass(); - break; - - case HAPI_PARMTYPE_LABEL: - FoundClass = UHoudiniParameterLabel::StaticClass(); - break; - - case HAPI_PARMTYPE_BUTTON: - FoundClass = UHoudiniParameterButton::StaticClass(); - break; - - case HAPI_PARMTYPE_SEPARATOR: - FoundClass = UHoudiniParameterSeparator::StaticClass(); - break; - - case HAPI_PARMTYPE_FOLDERLIST: - FoundClass = UHoudiniParameterFolderList::StaticClass(); - break; - - case HAPI_PARMTYPE_FOLDER: - FoundClass = UHoudiniParameterFolder::StaticClass(); - break; - - case HAPI_PARMTYPE_MULTIPARMLIST: - { - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - FoundClass = UHoudiniParameterRampFloat::StaticClass(); - else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - FoundClass = UHoudiniParameterRampColor::StaticClass(); - } - break; - - case HAPI_PARMTYPE_PATH_FILE: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_DIR: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_GEO: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - - case HAPI_PARMTYPE_NODE: - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - FoundClass = UHoudiniParameter::StaticClass(); - } - else - { - FoundClass = UHoudiniParameterString::StaticClass(); - } - break; - } - - if (FoundClass == nullptr) - return UHoudiniParameter::StaticClass(); - - return FoundClass; -} - -bool -FHoudiniParameterTranslator::CheckParameterTypeAndClassMatch(UHoudiniParameter* Parameter, const EHoudiniParameterType& ParmType) -{ - UClass* FoundClass = Parameter->GetClass(); - bool FailedTypeCheck = true; - - switch (ParmType) - { - case EHoudiniParameterType::Invalid: - { - FailedTypeCheck = true; - break; - } - - case EHoudiniParameterType::Button: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterButton >(); - break; - } - - case EHoudiniParameterType::Color: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterColor >(); - break; - } - - case EHoudiniParameterType::ColorRamp: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampColor >(); - break; - } - case EHoudiniParameterType::FloatRamp: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampFloat >(); - break; - } - - case EHoudiniParameterType::File: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileDir: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileGeo: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileImage: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - - case EHoudiniParameterType::Float: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFloat >(); - break; - } - - case EHoudiniParameterType::Folder: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolder >(); - break; - } - - case EHoudiniParameterType::FolderList: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolderList >(); - break; - } - - case EHoudiniParameterType::Input: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterOperatorPath >(); - break; - } - - case EHoudiniParameterType::Int: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterInt >(); - break; - } - - case EHoudiniParameterType::IntChoice: - case EHoudiniParameterType::StringChoice: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); - break; - } - - case EHoudiniParameterType::Label: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterLabel >(); - break; - } - - case EHoudiniParameterType::MultiParm: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterMultiParm >(); - break; - } - - case EHoudiniParameterType::Separator: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterSeparator >(); - break; - } - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); - break; - } - - case EHoudiniParameterType::Toggle: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterToggle >(); - break; - } - }; - - return !FailedTypeCheck; -} -/* -bool -FHoudiniParameterTranslator::CheckParameterClassAndInfoMatch(UHoudiniParameter* Parameter, const HAPI_ParmInfo& ParmInfo) -{ - if (!Parameter || Parameter->IsPendingKill()) - return false; - - UClass* FoundClass = Parameter->GetClass(); - bool FailedTypeCheck = true; - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_STRING: - if (!ParmInfo.choiceCount) - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); - else - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); - break; - - case HAPI_PARMTYPE_INT: - if (!ParmInfo.choiceCount) - FailedTypeCheck &= !FoundClass->IsChildOf(); - else - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FLOAT: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_TOGGLE: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_COLOR: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_LABEL: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_BUTTON: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_SEPARATOR: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FOLDERLIST: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FOLDER: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_MULTIPARMLIST: - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - break; - - case HAPI_PARMTYPE_PATH_FILE: - case HAPI_PARMTYPE_PATH_FILE_DIR: - case HAPI_PARMTYPE_PATH_FILE_GEO: - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_NODE: - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - else - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - break; - } - - return FailedTypeCheck; -} -*/ - -UHoudiniParameter * -FHoudiniParameterTranslator::CreateTypedParameter(UObject * Outer, const EHoudiniParameterType& ParmType, const FString& ParmName) -{ - UHoudiniParameter* HoudiniParameter = nullptr; - // Create a parameter of the desired type - switch (ParmType) - { - case EHoudiniParameterType::Button: - HoudiniParameter = UHoudiniParameterButton::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::ButtonStrip: - HoudiniParameter = UHoudiniParameterButtonStrip::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Color: - HoudiniParameter = UHoudiniParameterColor::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::ColorRamp: - HoudiniParameter = UHoudiniParameterRampColor::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FloatRamp: - HoudiniParameter = UHoudiniParameterRampFloat::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::File: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FileDir: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileDir); - break; - - case EHoudiniParameterType::FileGeo: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileGeo); - break; - - case EHoudiniParameterType::FileImage: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileImage); - break; - - case EHoudiniParameterType::Float: - HoudiniParameter = UHoudiniParameterFloat::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Folder: - HoudiniParameter = UHoudiniParameterFolder::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FolderList: - HoudiniParameter = UHoudiniParameterFolderList::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Input: - // Input parameter simply use the base class as all the processingsince is handled by UHoudiniInput - HoudiniParameter = UHoudiniParameterOperatorPath::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(ParmType); - break; - - case EHoudiniParameterType::Int: - HoudiniParameter = UHoudiniParameterInt::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::IntChoice: - HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::IntChoice); - break; - - case EHoudiniParameterType::StringChoice: - HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::StringChoice); - break; - - case EHoudiniParameterType::Label: - HoudiniParameter = UHoudiniParameterLabel::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::MultiParm: - HoudiniParameter = UHoudiniParameterMultiParm::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Separator: - HoudiniParameter = UHoudiniParameterSeparator::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - HoudiniParameter = UHoudiniParameterString::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Toggle: - HoudiniParameter = UHoudiniParameterToggle::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Invalid: - // TODO handle invalid params - HoudiniParameter = UHoudiniParameter::Create(Outer, ParmName); - break; - } - - return HoudiniParameter; -} - -bool -FHoudiniParameterTranslator::UpdateParameterFromInfo( - UHoudiniParameter * HoudiniParameter, const HAPI_NodeId& InNodeId, const HAPI_ParmInfo& ParmInfo, - const bool& bFullUpdate, const bool& bUpdateValue) -{ - if (!HoudiniParameter || HoudiniParameter->IsPendingKill()) - return false; - - // Copy values from the ParmInfos - HoudiniParameter->SetNodeId(InNodeId); - HoudiniParameter->SetParmId(ParmInfo.id); - HoudiniParameter->SetParentParmId(ParmInfo.parentId); - - HoudiniParameter->SetChildIndex(ParmInfo.childIndex); - HoudiniParameter->SetTagCount(ParmInfo.tagCount); - HoudiniParameter->SetTupleSize(ParmInfo.size); - - HoudiniParameter->SetVisible(!ParmInfo.invisible); - HoudiniParameter->SetDisabled(ParmInfo.disabled); - HoudiniParameter->SetSpare(ParmInfo.spare); - HoudiniParameter->SetJoinNext(ParmInfo.joinNext); - - HoudiniParameter->SetTagCount(ParmInfo.tagCount); - HoudiniParameter->SetIsChildOfMultiParm(ParmInfo.isChildOfMultiParm); - - UHoudiniParameterMultiParm* MultiParm = Cast(HoudiniParameter); - if(MultiParm) - MultiParm->InstanceStartOffset = ParmInfo.instanceStartOffset; - - - - // Get the parameter type - EHoudiniParameterType ParmType = HoudiniParameter->GetParameterType(); - - // We need to set string values from the parmInfo - if (bFullUpdate) - { - FString Name; - { - // Name - FHoudiniEngineString HoudiniEngineStringName(ParmInfo.nameSH); - if (HoudiniEngineStringName.ToFString(Name)) - HoudiniParameter->SetParameterName(Name); - } - - { - // Label - FString Label; - FHoudiniEngineString HoudiniEngineStringLabel(ParmInfo.labelSH); - if (HoudiniEngineStringLabel.ToFString(Label)) - HoudiniParameter->SetParameterLabel(Label); - } - - { - // Help - FString Help; - FHoudiniEngineString HoudiniEngineStringHelp(ParmInfo.helpSH); - if (HoudiniEngineStringHelp.ToFString(Help)) - HoudiniParameter->SetParameterHelp(Help); - } - - if (ParmType == EHoudiniParameterType::String - || ParmType == EHoudiniParameterType::Int - || ParmType == EHoudiniParameterType::Float - || ParmType == EHoudiniParameterType::Toggle - || ParmType == EHoudiniParameterType::Color) - { - // See if the parm has an expression - int32 TupleIdx = ParmInfo.intValuesIndex; - bool bHasExpression = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ParmHasExpression( - FHoudiniEngine::Get().GetSession(), InNodeId, - TCHAR_TO_UTF8(*Name), TupleIdx, &bHasExpression)) - { - // ? - } - - FString ParmExprString = TEXT(""); - if (bHasExpression) - { - // Try to get the expression's value - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmExpression( - FHoudiniEngine::Get().GetSession(), InNodeId, - TCHAR_TO_UTF8(*Name), TupleIdx, &StringHandle)) - { - FHoudiniEngineString HoudiniEngineString(StringHandle); - HoudiniEngineString.ToFString(ParmExprString); - } - - // Check if we actually have an expression - // String parameters return true even if they do not have one - bHasExpression = ParmExprString.Len() > 0; - - } - - HoudiniParameter->SetHasExpression(bHasExpression); - HoudiniParameter->SetExpression(ParmExprString); - } - else - { - HoudiniParameter->SetHasExpression(false); - HoudiniParameter->SetExpression(FString()); - } - - // Get parameter tags. - int32 TagCount = HoudiniParameter->GetTagCount(); - for (int32 Idx = 0; Idx < TagCount; ++Idx) - { - HAPI_StringHandle TagNameSH; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagName( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmInfo.id, Idx, &TagNameSH)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); - continue; - } - - FString NameString = TEXT(""); - FHoudiniEngineString::ToFString(TagNameSH, NameString); - if (NameString.IsEmpty()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); - continue; - } - - HAPI_StringHandle TagValueSH; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagValue( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmInfo.id, TCHAR_TO_ANSI(*NameString), &TagValueSH)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag value: parmId: %d, tag: %s"), ParmInfo.id, *NameString); - } - - FString ValueString = TEXT(""); - FHoudiniEngineString::ToFString(TagValueSH, ValueString); - - HoudiniParameter->GetTags().Add(NameString, ValueString); - } - } - - // - // Update properties specific to parameter classes - switch (ParmType) - { - case EHoudiniParameterType::Button: - { - UHoudiniParameterButton* HoudiniParameterButton = Cast(HoudiniParameter); - if (HoudiniParameterButton && !HoudiniParameterButton->IsPendingKill()) - { - HoudiniParameterButton->SetValueIndex(ParmInfo.intValuesIndex); - } - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - UHoudiniParameterButtonStrip* HoudiniParameterButtonStrip = Cast(HoudiniParameter); - if (HoudiniParameterButtonStrip && !HoudiniParameterButtonStrip->IsPendingKill()) - { - HoudiniParameterButtonStrip->SetValueIndex(ParmInfo.intValuesIndex); - HoudiniParameterButtonStrip->Count = ParmInfo.choiceCount; - } - - if (bFullUpdate) - { - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNum(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - - HoudiniParameterButtonStrip->InitializeLabels(ParmInfo.choiceCount); - - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ButtonLabel = HoudiniParameterButtonStrip->GetStringLabelAt(ChoiceIdx); - if (ButtonLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ButtonLabel)) - return false; - } - } - - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterButtonStrip->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.choiceCount) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - } - break; - - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* HoudiniParameterColor = Cast(HoudiniParameter); - if (HoudiniParameterColor && !HoudiniParameterColor->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterColor->SetValueIndex(ParmInfo.floatValuesIndex); - - // Update the Parameter value if we want to - if (bUpdateValue) - { - // Get the actual value for this property. - FLinearColor Color = FLinearColor::White; - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - (float *)&Color.R, ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - - HoudiniParameterColor->SetColorValue(Color); - } - - if (bFullUpdate) - { - // Set the default value at parameter created. - HoudiniParameterColor->SetDefaultValue(); - } - } - } - break; - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* HoudiniParameterRampColor = Cast(HoudiniParameter); - if (HoudiniParameterRampColor && !HoudiniParameterRampColor->IsPendingKill()) - { - HoudiniParameterRampColor->SetInstanceCount(ParmInfo.instanceCount); - HoudiniParameterRampColor->MultiParmInstanceLength = ParmInfo.instanceLength; - } - } - break; - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* HoudiniParameterRampFloat = Cast(HoudiniParameter); - if (HoudiniParameterRampFloat && !HoudiniParameterRampFloat->IsPendingKill()) - { - HoudiniParameterRampFloat->SetInstanceCount(ParmInfo.instanceCount); - HoudiniParameterRampFloat->MultiParmInstanceLength = ParmInfo.instanceLength; - } - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - UHoudiniParameterFile* HoudiniParameterFile = Cast(HoudiniParameter); - if (HoudiniParameterFile && !HoudiniParameterFile->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFile->SetValueIndex(ParmInfo.stringValuesIndex); - - // Update the file filter and read only tag only for full updates - if (bFullUpdate) - { - // Check if we are read-only - bool bIsReadOnly = false; - FString FileChooserTag; - if (FHoudiniParameterTranslator::HapiGetParameterTagValue(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_FILE_READONLY, FileChooserTag)) - { - if (FileChooserTag.Equals(TEXT("read"), ESearchCase::IgnoreCase)) - bIsReadOnly = true; - } - HoudiniParameterFile->SetReadOnly(bIsReadOnly); - - // Update the file type using the typeInfo string. - if (ParmInfo.typeInfoSH > 0) - { - FString Filters; - FHoudiniEngineString HoudiniEngineString(ParmInfo.typeInfoSH); - if (HoudiniEngineString.ToFString(Filters)) - { - if (!Filters.IsEmpty()) - HoudiniParameterFile->SetFileFilters(Filters); - } - } - } - - if (bUpdateValue) - { - // Get the actual values for this property. - TArray< HAPI_StringHandle > StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), InNodeId, false, - &StringHandles[0], ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - - // Convert HAPI string handles to Unreal strings. - HoudiniParameterFile->SetNumberOfValues(ParmInfo.size); - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - - // Update the parameter value - HoudiniParameterFile->SetValueAt(ValueString, Idx); - } - } - - if (bFullUpdate) - { - HoudiniParameterFile->SetDefaultValues(); - } - } - } - break; - - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* HoudiniParameterFloat = Cast(HoudiniParameter); - if (HoudiniParameterFloat && !HoudiniParameterFloat->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFloat->SetValueIndex(ParmInfo.floatValuesIndex); - - if (bUpdateValue) - { - // Update the parameter's value - HoudiniParameterFloat->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterFloat->GetValuesPtr(), - ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - - if (bFullUpdate) - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_FLOAT_LOG) - { - HoudiniParameterFloat->SetIsLogarithmic(true); - } - - // set the default float values. - HoudiniParameterFloat->SetDefaultValues(); - - // Only update Unit, no swap, and Min/Max values when doing a full update - - // Get the parameter's unit from the "unit" tag - FString ParamUnit; - FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); - HoudiniParameterFloat->SetUnit(ParamUnit); - - // Get the parameter's no swap tag (hengine_noswap) - HoudiniParameterFloat->SetNoSwap(HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_NOSWAP)); - - // Set the min and max for this parameter - if (ParmInfo.hasMin) - { - HoudiniParameterFloat->SetHasMin(true); - HoudiniParameterFloat->SetMin(ParmInfo.min); - } - else - { - HoudiniParameterFloat->SetHasMin(false); - HoudiniParameterFloat->SetMin(TNumericLimits::Lowest()); - } - - if (ParmInfo.hasMax) - { - HoudiniParameterFloat->SetHasMax(true); - HoudiniParameterFloat->SetMax(ParmInfo.max); - } - else - { - HoudiniParameterFloat->SetHasMax(false); - HoudiniParameterFloat->SetMax(TNumericLimits::Max()); - } - - // Set min and max for UI for this property. - bool bUsesDefaultMin = false; - if (ParmInfo.hasUIMin) - { - HoudiniParameterFloat->SetHasUIMin(true); - HoudiniParameterFloat->SetUIMin(ParmInfo.UIMin); - } - else if (ParmInfo.hasMin) - { - // If it is not set, use supplied min. - HoudiniParameterFloat->SetUIMin(ParmInfo.min); - } - else - { - // Min value Houdini uses by default. - HoudiniParameterFloat->SetUIMin(HAPI_UNREAL_PARAM_FLOAT_UI_MIN); - bUsesDefaultMin = true; - } - - bool bUsesDefaultMax = false; - if (ParmInfo.hasUIMax) - { - HoudiniParameterFloat->SetHasUIMax(true); - HoudiniParameterFloat->SetUIMax(ParmInfo.UIMax); - } - else if (ParmInfo.hasMax) - { - // If it is not set, use supplied max. - HoudiniParameterFloat->SetUIMax(ParmInfo.max); - } - else - { - // Max value Houdini uses by default. - HoudiniParameterFloat->SetUIMax(HAPI_UNREAL_PARAM_FLOAT_UI_MAX); - bUsesDefaultMax = true; - } - - if (bUsesDefaultMin || bUsesDefaultMax) - { - // If we are using defaults, we can detect some most common parameter names and alter defaults. - FString LocalParameterName = HoudiniParameterFloat->GetParameterName(); - FHoudiniEngineString HoudiniEngineString(ParmInfo.nameSH); - HoudiniEngineString.ToFString(LocalParameterName); - - static const FString ParameterNameTranslate(TEXT(HAPI_UNREAL_PARAM_TRANSLATE)); - static const FString ParameterNameRotate(TEXT(HAPI_UNREAL_PARAM_ROTATE)); - static const FString ParameterNameScale(TEXT(HAPI_UNREAL_PARAM_SCALE)); - static const FString ParameterNamePivot(TEXT(HAPI_UNREAL_PARAM_PIVOT)); - - if (!LocalParameterName.IsEmpty()) - { - if (LocalParameterName.Equals(ParameterNameTranslate) - || LocalParameterName.Equals(ParameterNameScale) - || LocalParameterName.Equals(ParameterNamePivot)) - { - if (bUsesDefaultMin) - { - HoudiniParameterFloat->SetUIMin(-1.0f); - } - if (bUsesDefaultMax) - { - HoudiniParameterFloat->SetUIMax(1.0f); - } - } - else if (LocalParameterName.Equals(ParameterNameRotate)) - { - if (bUsesDefaultMin) - { - HoudiniParameterFloat->SetUIMin(0.0f); - } - if (bUsesDefaultMax) - { - HoudiniParameterFloat->SetUIMax(360.0f); - } - } - } - } - } - } - } - break; - - case EHoudiniParameterType::Folder: - { - UHoudiniParameterFolder* HoudiniParameterFolder = Cast(HoudiniParameter); - if (HoudiniParameterFolder && !HoudiniParameterFolder->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFolder->SetValueIndex(ParmInfo.intValuesIndex); - HoudiniParameterFolder->SetFolderType(GetFolderTypeFromParamInfo(&ParmInfo)); - } - } - break; - - case EHoudiniParameterType::FolderList: - { - UHoudiniParameterFolderList* HoudiniParameterFolderList = Cast(HoudiniParameter); - if (HoudiniParameterFolderList && !HoudiniParameterFolderList->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFolderList->SetValueIndex(ParmInfo.intValuesIndex); - } - } - break; - - case EHoudiniParameterType::Input: - { - // Inputs parameters are just stored, and handled separately by UHoudiniInputs - UHoudiniParameterOperatorPath* HoudiniParameterOperatorPath = Cast(HoudiniParameter); - if (HoudiniParameterOperatorPath && !HoudiniParameterOperatorPath->IsPendingKill()) - { - /* - // DO NOT CREATE A DUPLICATE INPUT HERE! - // Inputs are created by the input translator, and will be tied to this parameter there - UHoudiniInput * NewInput = NewObject< UHoudiniInput >( - HoudiniParameterOperatorPath, - UHoudiniInput::StaticClass()); - - UHoudiniAssetComponent *ParentHAC = Cast(HoudiniParameterOperatorPath->GetOuter()); - - if (!ParentHAC) - return false; - - if (!NewInput || NewInput->IsPendingKill()) - return false; - - // Set the nodeId - NewInput->SetAssetNodeId(ParentHAC->GetAssetId()); - NewInput->SetInputType(EHoudiniInputType::Geometry); - HoudiniParameterOperatorPath->HoudiniInputs.Add(NewInput); - */ - // Set the valueIndex - HoudiniParameterOperatorPath->SetValueIndex(ParmInfo.stringValuesIndex); - } - } - break; - - case EHoudiniParameterType::Int: - { - UHoudiniParameterInt* HoudiniParameterInt = Cast(HoudiniParameter); - if (HoudiniParameterInt && !HoudiniParameterInt->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterInt->SetValueIndex(ParmInfo.intValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - HoudiniParameterInt->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterInt->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - - if (bFullUpdate) - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_INT_LOG) - { - HoudiniParameterInt->SetIsLogarithmic(true); - } - - // Set the default int values at created - HoudiniParameterInt->SetDefaultValues(); - // Only update unit and Min/Max values for a full update - - // Get the parameter's unit from the "unit" tag - FString ParamUnit; - FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); - HoudiniParameterInt->SetUnit(ParamUnit); - - // Set the min and max for this parameter - if (ParmInfo.hasMin) - { - HoudiniParameterInt->SetHasMin(true); - HoudiniParameterInt->SetMin((int32)ParmInfo.min); - } - else - { - HoudiniParameterInt->SetHasMin(false); - HoudiniParameterInt->SetMin(TNumericLimits::Lowest()); - } - - if (ParmInfo.hasMax) - { - HoudiniParameterInt->SetHasMax(true); - HoudiniParameterInt->SetMax((int32)ParmInfo.max); - } - else - { - HoudiniParameterInt->SetHasMax(false); - HoudiniParameterInt->SetMax(TNumericLimits::Max()); - } - - // Set min and max for UI for this property. - if (ParmInfo.hasUIMin) - { - HoudiniParameterInt->SetHasUIMin(true); - HoudiniParameterInt->SetUIMin((int32)ParmInfo.UIMin); - } - else if (ParmInfo.hasMin) - { - // If it is not set, use supplied min. - HoudiniParameterInt->SetUIMin((int32)ParmInfo.min); - } - else - { - // Min value Houdini uses by default. - HoudiniParameterInt->SetUIMin(HAPI_UNREAL_PARAM_INT_UI_MIN); - } - - if (ParmInfo.hasUIMax) - { - HoudiniParameterInt->SetHasUIMax(true); - HoudiniParameterInt->SetUIMax((int32)ParmInfo.UIMax); - } - else if (ParmInfo.hasMax) - { - // If it is not set, use supplied max. - HoudiniParameterInt->SetUIMax((int32)ParmInfo.max); - } - else - { - // Max value Houdini uses by default. - HoudiniParameterInt->SetUIMax(HAPI_UNREAL_PARAM_INT_UI_MAX); - } - } - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* HoudiniParameterIntChoice = Cast(HoudiniParameter); - if (HoudiniParameterIntChoice && !HoudiniParameterIntChoice->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterIntChoice->SetValueIndex(ParmInfo.intValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - int32 CurrentIntValue = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &CurrentIntValue, - ParmInfo.intValuesIndex, ParmInfo.size), false); - - // Check the value is valid - if (CurrentIntValue >= ParmInfo.choiceCount) - { - HOUDINI_LOG_WARNING(TEXT("parm '%s' has an invalid value %d, menu tokens are not supported for choice menus"), - *HoudiniParameterIntChoice->GetParameterName(), CurrentIntValue); - CurrentIntValue = 0; - } - - HoudiniParameterIntChoice->SetIntValue(CurrentIntValue); - } - - // Get the choice descriptors - if (bFullUpdate) - { - // Set the default value at created - HoudiniParameterIntChoice->SetDefaultIntValue(); - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - - // Set the array sizes - HoudiniParameterIntChoice->SetNumChoices(ParmInfo.choiceCount); - - bool bMatchedSelectionLabel = false; - int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValue(); - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ChoiceLabel = HoudiniParameterIntChoice->GetStringChoiceLabelAt(ChoiceIdx); - if (ChoiceLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ChoiceLabel)) - return false; - //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); - } - - // Match our string value to the corresponding selection label. - if (ChoiceIdx == CurrentIntValue) - { - HoudiniParameterIntChoice->SetStringValue(*ChoiceLabel); - } - } - } - else if (bUpdateValue) - { - // We still need to match the string value to the label - HoudiniParameterIntChoice->UpdateStringValueFromInt(); - } - } - } - break; - - case EHoudiniParameterType::StringChoice: - { - UHoudiniParameterChoice* HoudiniParameterStringChoice = Cast(HoudiniParameter); - if (HoudiniParameterStringChoice && !HoudiniParameterStringChoice->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterStringChoice->SetValueIndex(ParmInfo.stringValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandle, - ParmInfo.stringValuesIndex, ParmInfo.size), false); - - // Get the string value - FString StringValue; - FHoudiniEngineString HoudiniEngineString(StringHandle); - HoudiniEngineString.ToFString(StringValue); - - HoudiniParameterStringChoice->SetStringValue(StringValue); - } - - // Get the choice descriptors - if (bFullUpdate) - { - // Set default value at created. - HoudiniParameterStringChoice->SetDefaultStringValue(); - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - ParmChoices.SetNumUninitialized(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - - // Set the array sizes - HoudiniParameterStringChoice->SetNumChoices(ParmInfo.choiceCount); - - bool bMatchedSelectionLabel = false; - FString CurrentStringValue = HoudiniParameterStringChoice->GetStringValue(); - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ChoiceValue = HoudiniParameterStringChoice->GetStringChoiceValueAt(ChoiceIdx); - if (ChoiceValue) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].valueSH); - if (!HoudiniEngineString.ToFString(*ChoiceValue)) - return false; - //StringChoiceValues.Add(TSharedPtr< FString >(ChoiceValue)); - } - - FString * ChoiceLabel = HoudiniParameterStringChoice->GetStringChoiceLabelAt(ChoiceIdx); - if (ChoiceLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ChoiceLabel)) - return false; - //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); - } - - // If this is a string choice list, we need to match name with corresponding selection label. - if (!bMatchedSelectionLabel && ChoiceValue->Equals(CurrentStringValue)) - { - bMatchedSelectionLabel = true; - HoudiniParameterStringChoice->SetIntValue(ChoiceIdx); - } - } - } - else if (bUpdateValue) - { - // We still need to match the string value to the label - HoudiniParameterStringChoice->UpdateIntValueFromString(); - } - } - } - break; - - case EHoudiniParameterType::Label: - { - UHoudiniParameterLabel* HoudiniParameterLabel = Cast(HoudiniParameter); - if (HoudiniParameterLabel && !HoudiniParameterLabel->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_LABEL) - return false; - - // Set the valueIndex - HoudiniParameterLabel->SetValueIndex(ParmInfo.stringValuesIndex); - - // Get the actual value for this property. - TArray StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandles[0], - ParmInfo.stringValuesIndex, ParmInfo.size); - - HoudiniParameterLabel->EmptyLabelString(); - - // Convert HAPI string handles to Unreal strings. - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - HoudiniParameterLabel->AddLabelString(ValueString); - } - } - } - break; - - case EHoudiniParameterType::MultiParm: - { - UHoudiniParameterMultiParm* HoudiniParameterMulti = Cast(HoudiniParameter); - if (HoudiniParameterMulti && !HoudiniParameterMulti->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST) - return false; - - // Set the valueIndex - HoudiniParameterMulti->SetValueIndex(ParmInfo.intValuesIndex); - - // Set the multiparm value - int32 MultiParmValue = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &MultiParmValue, ParmInfo.intValuesIndex, 1), false); - - HoudiniParameterMulti->SetValue(MultiParmValue); - HoudiniParameterMulti->MultiParmInstanceCount = ParmInfo.instanceCount; - HoudiniParameterMulti->MultiParmInstanceLength = ParmInfo.instanceLength; - - } - - if (bFullUpdate) - { - HoudiniParameterMulti->SetDefaultInstanceCount(ParmInfo.instanceCount); - } - } - break; - - case EHoudiniParameterType::Separator: - { - UHoudiniParameterSeparator* HoudiniParameterSeparator = Cast(HoudiniParameter); - if (HoudiniParameterSeparator && !HoudiniParameterSeparator->IsPendingKill()) - { - // We can only handle separator type. - if (ParmInfo.type != HAPI_PARMTYPE_SEPARATOR) - return false; - - // Set the valueIndex - HoudiniParameterSeparator->SetValueIndex(ParmInfo.stringValuesIndex); - } - } - break; - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - { - UHoudiniParameterString* HoudiniParameterString = Cast(HoudiniParameter); - if (HoudiniParameterString && !HoudiniParameterString->IsPendingKill()) - { - // We can only handle string type. - if (ParmInfo.type != HAPI_PARMTYPE_STRING && ParmInfo.type != HAPI_PARMTYPE_NODE) - return false; - - // Set the valueIndex - HoudiniParameterString->SetValueIndex(ParmInfo.stringValuesIndex); - - // Stop if we don't want to update the value - if (bUpdateValue) - { - // Get the actual value for this property. - TArray< HAPI_StringHandle > StringHandles; - StringHandles.SetNumZeroed(ParmInfo.size); - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandles[0], - ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - - // Convert HAPI string handles to Unreal strings. - HoudiniParameterString->SetNumberOfValues(ParmInfo.size); - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - HoudiniParameterString->SetValueAt(ValueString, Idx); - } - } - - if (bFullUpdate) - { - // Set default string values on created - HoudiniParameterString->SetDefaultValues(); - // Check if the parameter has the "asset_ref" tag - HoudiniParameterString->SetIsAssetRef( - FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_ASSET_REF)); - } - } - } - break; - - case EHoudiniParameterType::Toggle: - { - UHoudiniParameterToggle* HoudiniParameterToggle = Cast(HoudiniParameter); - if (HoudiniParameterToggle && !HoudiniParameterToggle->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_TOGGLE) - return false; - - // Set the valueIndex - HoudiniParameterToggle->SetValueIndex(ParmInfo.intValuesIndex); - - // Stop if we don't want to update the value - if (bUpdateValue) - { - // Get the actual values for this property. - HoudiniParameterToggle->SetNumberOfValues(ParmInfo.size); - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterToggle->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - } - - if (bFullUpdate) - { - HoudiniParameterToggle->SetDefaultValues(); - } - } - break; - - case EHoudiniParameterType::Invalid: - { - // TODO - } - break; - } - - return true; -} - -bool -FHoudiniParameterTranslator::HapiGetParameterTagValue(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag, FString& TagValue) -{ - // Default - TagValue = FString(); - - // Does the parameter has the tag? - bool HasTag = false; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ParmHasTag( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &HasTag), false); - - if (!HasTag) - return false; - - // Get the tag string value - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmTagValue( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &StringHandle), false); - - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(TagValue)) - { - return true; - } - - return false; -} - - -bool -FHoudiniParameterTranslator::HapiGetParameterUnit(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString) -{ - // - OutUnitString = TEXT(""); - - // We're looking for the parameter unit tag - //FString UnitTag = "units"; - - // Get the actual string value. - FString UnitString = TEXT(""); - if (!FHoudiniParameterTranslator::HapiGetParameterTagValue(NodeId, ParmId, "units", UnitString)) - return false; - - // We need to do some replacement in the string here in order to be able to get the - // proper unit type when calling FUnitConversion::UnitFromString(...) after. - - // Per second and per hour are the only "per" unit that unreal recognize - UnitString.ReplaceInline(TEXT("s-1"), TEXT("/s")); - UnitString.ReplaceInline(TEXT("h-1"), TEXT("/h")); - - // Houdini likes to add '1' on all the unit, so we'll remove all of them - // except the '-1' that still needs to remain. - UnitString.ReplaceInline(TEXT("-1"), TEXT("--")); - UnitString.ReplaceInline(TEXT("1"), TEXT("")); - UnitString.ReplaceInline(TEXT("--"), TEXT("-1")); - - OutUnitString = UnitString; - - return true; -} - -bool -FHoudiniParameterTranslator::HapiGetParameterHasTag(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag) -{ - // Does the parameter has the tag we're looking for? - bool HasTag = false; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &HasTag), false); - - return HasTag; -} - - -bool -FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * HAC ) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniParameterTranslator::UploadChangedParameters); - - if (!HAC || HAC->IsPendingKill()) - return false; - - TMap RampsToRevert; - - for (int32 ParmIdx = 0; ParmIdx < HAC->GetNumParameters(); ParmIdx++) - { - UHoudiniParameter*& CurrentParm = HAC->Parameters[ParmIdx]; - if (!CurrentParm || CurrentParm->IsPendingKill() || !CurrentParm->HasChanged()) - continue; - - bool bSuccess = false; - - if (CurrentParm->IsPendingRevertToDefault()) - { - bSuccess = RevertParameterToDefault(CurrentParm); - - if (CurrentParm->GetParameterType() == EHoudiniParameterType::FloatRamp || - CurrentParm->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - RampsToRevert.Add(CurrentParm->GetParameterName(), CurrentParm); - } - } - else - { - bSuccess = UploadParameterValue(CurrentParm); - } - - - if (bSuccess) - { - CurrentParm->MarkChanged(false); - //CurrentParm->SetNeedsToTriggerUpdate(false); - } - else - { - // Keep this param marked as changed but prevent it from generating updates - CurrentParm->SetNeedsToTriggerUpdate(false); - } - } - - FHoudiniParameterTranslator::RevertRampParameters(RampsToRevert, HAC->GetAssetId()); - - return true; -} - -bool -FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return false; - - switch (InParam->GetParameterType()) - { - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParam = Cast(InParam); - if (!FloatParam || FloatParam->IsPendingKill()) - { - return false; - } - - float* DataPtr = FloatParam->GetValuesPtr(); - if (!DataPtr) - { - return false; - } - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - FloatParam->GetNodeId(), DataPtr, FloatParam->GetValueIndex(), FloatParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::Int: - { - UHoudiniParameterInt* IntParam = Cast(InParam); - if (!IntParam || IntParam->IsPendingKill()) - { - return false; - } - - int32* DataPtr = IntParam->GetValuesPtr(); - if (!DataPtr) - { - return false; - } - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - IntParam->GetNodeId(), DataPtr, IntParam->GetValueIndex(), IntParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::String: - { - UHoudiniParameterString* StringParam = Cast(InParam); - if (!StringParam || StringParam->IsPendingKill()) - { - return false; - } - - int32 NumValues = StringParam->GetNumberOfValues(); - if (NumValues <= 0) - { - return false; - } - - for (int32 Idx = 0; Idx < NumValues; Idx++) - { - std::string ConvertedString = TCHAR_TO_UTF8(*(StringParam->GetValueAt(Idx))); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - StringParam->GetNodeId(), ConvertedString.c_str(), StringParam->GetParmId(), Idx), false); - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) - return false; - - // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValue(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); - } - break; - case EHoudiniParameterType::StringChoice: - { - UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) - { - return false; - } - - if (ChoiceParam->IsStringChoice()) - { - // Set the parameter's string value. - std::string ConvertedString = TCHAR_TO_UTF8(*(ChoiceParam->GetStringValue())); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), ConvertedString.c_str(), ChoiceParam->GetParmId(), 0), false); - } - else - { - // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValue(); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); - } - } - break; - - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* ColorParam = Cast(InParam); - if (!ColorParam || ColorParam->IsPendingKill()) - return false; - - bool bHasAlpha = ColorParam->GetTupleSize() == 4 ? true : false; - FLinearColor Color = ColorParam->GetColorValue(); - - // Set the color value - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - ColorParam->GetNodeId(), - (float*)(&Color.R), ColorParam->GetValueIndex(), bHasAlpha ? 4 : 3), false); - - } - break; - - case EHoudiniParameterType::Button: - { - UHoudiniParameterButton* ButtonParam = Cast(InParam); - if (!ButtonParam) - return false; - - TArray DataArray; - DataArray.Add(1); - - // Set the button parameter value to 1, (setting button param to any value will call the callback function.) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ButtonParam->GetNodeId(), - DataArray.GetData(), - ButtonParam->GetValueIndex(), 1), false); - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - UHoudiniParameterButtonStrip* ButtonStripParam = Cast(InParam); - if (!ButtonStripParam) - return false; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ButtonStripParam->GetNodeId(), - ButtonStripParam->Values.GetData(), - ButtonStripParam->GetValueIndex(), ButtonStripParam->Count), false); - } - break; - - case EHoudiniParameterType::Toggle: - { - UHoudiniParameterToggle* ToggleParam = Cast(InParam); - if (!ToggleParam) - return false; - - // Set the toggle parameter values. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ToggleParam->GetNodeId(), - ToggleParam->GetValuesPtr(), - ToggleParam->GetValueIndex(), ToggleParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - UHoudiniParameterFile* FileParam = Cast(InParam); - - if (!UploadDirectoryPath(FileParam)) - return false; - } - break; - - case EHoudiniParameterType::MultiParm: - { - if (!UploadMultiParmValues(InParam)) - return false; - } - - break; - - case EHoudiniParameterType::FloatRamp: - { - if (!UploadRampParameter(InParam)) - return false; - } - break; - - case EHoudiniParameterType::ColorRamp: - { - if (!UploadRampParameter(InParam)) - return false; - } - break; - - default: - { - // TODO: implement other parameter types! - return false; - } - break; - } - - // The parameter is no longer considered as changed - InParam->MarkChanged(false); - - return true; -} - -bool -FHoudiniParameterTranslator::RevertParameterToDefault(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return false; - - if (!InParam->IsPendingRevertToDefault()) - return false; - - TArray TupleToRevert; - InParam->GetTuplePendingRevertToDefault(TupleToRevert); - if (TupleToRevert.Num() <= 0) - return false; - - FString ParameterName = InParam->GetParameterName(); - - bool bReverted = true; - for (auto CurrentIdx : TupleToRevert ) - { - if (!TupleToRevert.IsValidIndex(CurrentIdx)) - { - // revert the whole parameter to its default value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefaults( - FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName))) - { - HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s to its default value."), *ParameterName); - bReverted = false; - } - } - else - { - // revert a tuple to its default value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefault( - FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName), CurrentIdx)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s - %d to its default value."), *ParameterName, CurrentIdx); - bReverted = false; - } - } - } - - if (!bReverted) - return false; - - // The parameter no longer needs to be reverted - InParam->MarkDefault(true); - - return true; -} - -EHoudiniFolderParameterType -FHoudiniParameterTranslator::GetFolderTypeFromParamInfo(const HAPI_ParmInfo* ParamInfo) -{ - EHoudiniFolderParameterType Type = EHoudiniFolderParameterType::Invalid; - - switch (ParamInfo->scriptType) - { - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPSIMPLE: - Type = EHoudiniFolderParameterType::Simple; - break; - - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPCOLLAPSIBLE: - Type = EHoudiniFolderParameterType::Collapsible; - break; - - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUP: - Type = EHoudiniFolderParameterType::Tabs; - break; - - // Treat Radio folders as tabs for now - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPRADIO: - Type = EHoudiniFolderParameterType::Radio; - break; - - default: - Type = EHoudiniFolderParameterType::Other; - break; - - } - - return Type; - -} - -bool -FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad( - UHoudiniParameter* InParam, TArray& OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo) -{ - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam || MultiParam->IsPendingKill()) - return false; - - UHoudiniParameterRampFloat* FloatRampParameter = nullptr; - UHoudiniParameterRampColor* ColorRampParameter = nullptr; - - if (MultiParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - FloatRampParameter = Cast(MultiParam); - else if (MultiParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - ColorRampParameter = Cast(MultiParam); - - HAPI_NodeId NodeId = AssetInfo.nodeId; - - int32 Idx = 0; - int32 InstanceCount = -1; - HAPI_ParmId ParmId = -1; - TArray ParmInfos; - if (!GetMultiParmInstanceStartIdx(AssetInfo, MultiParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - const int32 InstanceCountInUnreal = FMath::Max(MultiParam->GetInstanceCount(), 0); - if (InstanceCount > InstanceCountInUnreal) - { - // The multiparm has more instances on the Houdini side, remove instances from the end until it has the same - // number as in Unreal. - // NOTE: Initially this code always removed the first instance. But that causes an issue if HAPI/Houdini does - // not immediately update the parameter names (param1, param2, param3, when param1 is removed, 2 -> 1, - // 3 -> 2) so that could result in GetParameters returning parameters with unique IDs, but where the names - // are not up to date, so in the above example, the last param could still be named param3 when it should - // be named param2. - const int32 Delta = InstanceCount - InstanceCountInUnreal; - for (int32 n = 0; n < Delta; ++n) - { - FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset + InstanceCount - 1 - n); - } - } - else if (InstanceCountInUnreal > InstanceCount) - { - // The multiparm has fewer instances on the Houdini side, add instances at the end until it has the same - // number as in Unreal. - // NOTE: Initially this code always inserted before the first instance. But that causes an issue if HAPI/Houdini - // does not immediately update the parameter names (param1, param2, param3, when a param is inserted - // before 1, then 1->2, 2->3, 3->4 so that could result in GetParameters returning parameters with unique - // IDs, but where the names are not up to date, so in the above example, the now second param could still - // be named param1 when it should be named param2. - const int32 Delta = InstanceCountInUnreal - InstanceCount; - for (int32 n = 0; n < Delta; ++n) - { - FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset + InstanceCount + n); - } - } - - // We are going to Sync nested multi-params recursively - int32 MyParmId = InParam->GetParmId(); - // First, we need to manually look for our index in the old map - // Since there is a possibility that the parameter interface has changed since our previous cook - int32 MyIndex = -1; - for (int32 ParamIdx = 0; ParamIdx < OldParams.Num(); ParamIdx++) - { - UHoudiniParameter* CurrentOldParm = OldParams[ParamIdx]; - if (!IsValid(CurrentOldParm)) - continue; - - if (CurrentOldParm->GetParmId() != MyParmId) - continue; - - // We found ourself, exit now - MyIndex = ParamIdx; - break; - } - - if (MyIndex >= 0) - { - // Now Sync nested multi-params recursively - for (int32 ParamIdx = MyIndex + 1; ParamIdx < OldParams.Num(); ParamIdx++) - { - UHoudiniParameter* NextParm = OldParams[ParamIdx]; - if (!IsValid(NextParm)) - continue; - - if (NextParm->GetParentParmId() != ParmId) - continue; - - if (NextParm->GetParameterType() != EHoudiniParameterType::MultiParm) - continue; - - // Always make sure to NOT recurse on ourselves! - // This could happen if parms have been deleted... - if (NextParm->GetParmId() == MyParmId) - continue; - - SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, AssetInfo); - } - } - - // The multiparm is a ramp, Get the param infos again, since the number of param instances is changed - if (!GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - // Step 3: Set values of the inserted points - if (FloatRampParameter) - { - for (auto & Point : FloatRampParameter->Points) - { - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); - - // 2: update float value at param Idx + 1 - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Value), ParmInfos[Idx + 1].floatValuesIndex, 1); - - // 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Point->Interpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - else if (ColorRampParameter) - { - for (auto& Point : ColorRampParameter->Points) - { - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); - - // 2: update color value at param Idx + 1 - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, (float*)(&Point->Value.R), ParmInfos[Idx + 1].floatValuesIndex, 3); - - // 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Point->Interpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - - return true; -} - - -bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam) -{ - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam || MultiParam->IsPendingKill()) - return false; - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InParam->GetOuter()); - if (!HoudiniAssetComponent) - return false; - - int32 InsertIndexStart = -1; - UHoudiniParameterRampFloat* RampFloatParam = Cast(InParam); - UHoudiniParameterRampColor* RampColorParam = Cast(InParam); - - TArray *Events = nullptr; - if (RampFloatParam) - { - Events = &(RampFloatParam->ModificationEvents); - InsertIndexStart = RampFloatParam->GetInstanceCount(); - } - else if (RampColorParam) - { - Events = &(RampColorParam->ModificationEvents); - InsertIndexStart = RampColorParam->GetInstanceCount(); - } - else - return false; - - // Handle All Events - Events->Sort([](const UHoudiniParameterRampModificationEvent& A, const UHoudiniParameterRampModificationEvent& B) - { - return A.DeleteInstanceIndex > B.DeleteInstanceIndex; - }); - - - // Step 1: Handle all delete events first - for (auto& Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsDeleteEvent()) - continue; - - FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Event->DeleteInstanceIndex + MultiParam->InstanceStartOffset); - - InsertIndexStart -= 1; - } - - int32 InsertIndex = InsertIndexStart; - - - // Step 2: Handle all insert events - for (auto& Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsInsertEvent()) - continue; - - FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), InsertIndex + MultiParam->InstanceStartOffset); - - InsertIndex += 1; - } - - // Step 3: Set inserted parameter values (only if there are instances inserted) - if (InsertIndex > InsertIndexStart) - { - if (HoudiniAssetComponent) - { - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), HoudiniAssetComponent->AssetId, &AssetInfo), false); - - int32 Idx = 0; - int32 InstanceCount = -1; - HAPI_ParmId ParmId = -1; - TArray ParmInfos; - if (!FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), - Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - if (InstanceCount < 0) - return false; - - // Instance count doesn't match, - if (InsertIndex != InstanceCount) - return false; - - // Starting index of parameters which we just inserted - Idx += 3 * InsertIndexStart; - - if (!ParmInfos.IsValidIndex(Idx + 2)) - return false; - - for (auto & Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsInsertEvent()) - continue; - - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &(Event->InsertPosition), ParmInfos[Idx].floatValuesIndex, 1); - - // step 2: update value at param Idx + 1 - if (Event->IsFloatRampEvent()) - { - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &(Event->InsertFloat), ParmInfos[Idx + 1].floatValuesIndex, 1); - } - else - { - // color value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, (float*)(&Event->InsertColor.R), ParmInfos[Idx + 1].floatValuesIndex, 3); - } - - // step 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Event->InsertInterpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - } - - // Step 4: clear all events - Events->Empty(); - - return true; -} - -bool FHoudiniParameterTranslator::UploadMultiParmValues(UHoudiniParameter* InParam) -{ - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam) - return false; - - TArray &LastModificationArray = MultiParam->MultiParmInstanceLastModifyArray; - - int32 Size = MultiParam->MultiParmInstanceLastModifyArray.Num(); - - for (int32 Index = 0; Index < Size; ++Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Inserted) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) - return false; - - } - } - - for (int32 Index = Size - 1; Index >= 0; --Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) - return false; - } - } - - // Remove all removal events. - for (int32 Index = Size - 1; Index >= 0; --Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) - LastModificationArray.RemoveAt(Index); - } - - // The last modification array is resized. - Size = LastModificationArray.Num(); - - // Reset the last modification array - for (int32 Itr =Size - 1; Itr >= 0; --Itr) - { - LastModificationArray[Itr] = EHoudiniMultiParmModificationType::None; - } - - MultiParam->MultiParmInstanceCount = Size; - - return true; -} - -bool -FHoudiniParameterTranslator::UploadDirectoryPath(UHoudiniParameterFile* InParam) -{ - if(!InParam) - return false; - - for (int32 Index = 0; Index < InParam->GetNumValues(); ++Index) - { - std::string ConvertedString = TCHAR_TO_UTF8(*(InParam->GetValueAt(Index))); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue(FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), ConvertedString.c_str(), InParam->GetParmId(), Index), false); - } - - return true; -} - -bool -FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, - int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos) -{ - // TODO: FIX/IMPROVE THIS! - // This is bad, that function can be called recursively, fetches all parameters, - // iterates on them, and fetches their name!! WTF! - // TODO: Slightly better now, at least we dont fetch every parameter's name! - - // Reset outputs - OutStartIdx = -1; - OutInstanceCount = -1; - OutParmId = -1; - OutParmInfos.Empty(); - - // Try to find the parameter by its name - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, TCHAR_TO_UTF8(*InParmName), &OutParmId), false); - - if (OutParmId < 0) - return false; - - // Get the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &NodeInfo), false); - - // Get all parameters - OutParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &OutParmInfos[0], 0, NodeInfo.parmCount), false); - - OutStartIdx = 0; - for (const auto& CurrentParmInfo : OutParmInfos) - { - if (OutParmId == CurrentParmInfo.id) - { - OutInstanceCount = OutParmInfos[OutStartIdx].instanceCount; - - // Increment, to get the Start index of the ramp children parameters - OutStartIdx++; - return true; - } - - OutStartIdx++; - } - - // We failed to find the parm - OutStartIdx = -1; - - return false; -} - -bool -FHoudiniParameterTranslator::RevertRampParameters(TMap & InRampParams, const int32 & AssetId) -{ - if (InRampParams.Num() <= 0) - return true; - - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); - - // Retrieve all the parameter infos. - TArray< HAPI_ParmInfo > ParmInfos; - ParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); - - int32 ParamIdx = 0; - while (ParamIdx < ParmInfos.Num()) - { - const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; - FString ParmName; - FHoudiniEngineString(ParmInfo.nameSH).ToFString(ParmName); - - if (InRampParams.Contains(ParmName)) - { - if (!InRampParams[ParmName]) - { - ParamIdx += 1; - continue; - } - - if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat * FloatRamp = Cast(InRampParams[ParmName]); - if (!FloatRamp) - { - ParamIdx += 1; - continue; - } - - if (ParmInfo.instanceCount != FloatRamp->NumDefaultPoints) - { - ParamIdx += 1; - continue; - } - - for (int32 PtIdx = 0; PtIdx < FloatRamp->NumDefaultPoints; ++PtIdx) - { - const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; - const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; - const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultValues.GetData() + PtIdx, ValueParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); - - ParamIdx += 3; - } - } - - if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor * ColorRamp = Cast(InRampParams[ParmName]); - if (!ColorRamp) - { - ParamIdx += 1; - continue; - } - - if (ParmInfo.instanceCount != ColorRamp->NumDefaultPoints) - { - ParamIdx += 1; - continue; - } - - for (int32 PtIdx = 0; PtIdx < ColorRamp->NumDefaultPoints; ++PtIdx) - { - const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; - const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; - const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, ColorRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, (float*)(&ColorRamp->DefaultValues[PtIdx].R), ValueParmInfo.floatValuesIndex, 3); - - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, ColorRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); - - ParamIdx += 3; - } - } - } - - ParamIdx += 1; - } - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "HoudiniAsset.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniInput.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniParameter.h" +#include "HoudiniAssetComponent.h" + + +// Default values for certain UI min and max parameter values +#define HAPI_UNREAL_PARAM_INT_UI_MIN 0 +#define HAPI_UNREAL_PARAM_INT_UI_MAX 10 +#define HAPI_UNREAL_PARAM_FLOAT_UI_MIN 0.0f +#define HAPI_UNREAL_PARAM_FLOAT_UI_MAX 10.0f + +// Some default parameter name +#define HAPI_UNREAL_PARAM_TRANSLATE "t" +#define HAPI_UNREAL_PARAM_ROTATE "r" +#define HAPI_UNREAL_PARAM_SCALE "s" +#define HAPI_UNREAL_PARAM_PIVOT "p" +#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" + +// +bool +FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // When recooking/rebuilding the HDA, force a full update of all params + const bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested() || HAC->IsParameterDefinitionUpdateNeeded(); + + TArray NewParameters; + if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, true, bForceFullUpdate, HAC->GetHoudiniAsset(), HAC->GetHapiAssetName())) + { + /* + // DO NOT MANUALLY DESTROY THE OLD/DANGLING PARAMETERS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + + // Destroy old/dangling parameters + for (auto& OldParm : HAC->Parameters) + { + if (!OldParm || OldParm->IsPendingKill()) + continue; + + OldParm->ConditionalBeginDestroy(); + OldParm = nullptr; + } + */ + + // Replace with the new parameters + HAC->Parameters = NewParameters; + + // Update the details panel after the parameter changes/updates + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + } + + + return true; +} + +bool +FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) +{ + // Call OnPreCook for all parameters. + // Parameters can use this to ensure that any cached / non-cooking state is properly + // synced before the cook starts (Looking at you, ramp parameters!) + for (UHoudiniParameter* Param : HAC->Parameters) + { + if (!Param || Param->IsPendingKill()) + continue; + + Param->OnPreCook(); + } + + return true; +} + +// +bool +FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Update all the parameters using the loaded parameter object + // We set "UpdateValues" to false because we do not want to "read" the parameter value + // from Houdini but keep the loaded value + + // Share AssetInfo if needed + bool bNeedToFetchAssetInfo = true; + HAPI_AssetInfo AssetInfo; + + // This is the first cook on loading after a save or duplication + for (int32 Idx = 0; Idx < HAC->Parameters.Num(); ++Idx) + { + UHoudiniParameter* Param = HAC->Parameters[Idx]; + + if (!Param || Param->IsPendingKill()) + continue; + + switch(Param->GetParameterType()) + { + case EHoudiniParameterType::ColorRamp: + case EHoudiniParameterType::FloatRamp: + case EHoudiniParameterType::MultiParm: + { + // We need to sync the Ramp parameters first, so that their child parameters can be kept + if (bNeedToFetchAssetInfo) + { + FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &AssetInfo); + bNeedToFetchAssetInfo = false; + } + + // TODO: Simplify this, should be handled in BuildAllParameters + SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, AssetInfo); + } + break; + + case EHoudiniParameterType::Button: + case EHoudiniParameterType::ButtonStrip: + { + // Do not trigger buttons upon loading + Param->MarkChanged(false); + } + break; + + default: + break; + } + } + + // When recooking/rebuilding the HDA, force a full update of all params + const bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested() || HAC->IsParameterDefinitionUpdateNeeded(); + + // This call to BuildAllParameters will keep all the loaded parameters (in the HAC's Parameters array) + // that are still present in the HDA, and keep their loaded value. + TArray NewParameters; + // We don't need to fetch defaults from the asset definition for a loaded HAC + const UHoudiniAsset* const HoudiniAsset = nullptr; + const FString HoudiniAssetName = FString(); + if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, false, bForceFullUpdate, HoudiniAsset, HoudiniAssetName)) + { + /* + // DO NOT DESTROY OLD PARAMS MANUALLY HERE + // This causes crashes upon duplication due to uncollected zombie objects... + // GC is supposed to handle this by itself + // Destroy old/dangling parameters + for (auto& OldParm : HAC->Parameters) + { + if (!OldParm || OldParm->IsPendingKill()) + continue; + + OldParm->ConditionalBeginDestroy(); + OldParm = nullptr; + } + */ + + // Simply replace with the new parameters + HAC->Parameters = NewParameters; + + // Update the details panel after the parameter changes/updates + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + } + + return true; +} + +bool +FHoudiniParameterTranslator::BuildAllParameters( + const HAPI_NodeId& AssetId, + class UObject* Outer, + TArray& CurrentParameters, + TArray& NewParameters, + const bool& bUpdateValues, + const bool& InForceFullUpdate, + const UHoudiniAsset* InHoudiniAsset, + const FString& InHoudiniAssetName) +{ + const bool bIsAssetValid = IsValid(InHoudiniAsset); + + // Ensure the asset has a valid node ID + if (AssetId < 0 && !bIsAssetValid) + { + return false; + } + + int32 ParmCount = 0; + + // Default value counts and arrays for if we need to fetch those from Houdini. + int DefaultIntValueCount = 0; + int DefaultFloatValueCount = 0; + int DefaultStringValueCount = 0; + int DefaultChoiceValueCount = 0; + TArray DefaultIntValues; + TArray DefaultFloatValues; + TArray DefaultStringValues; + TArray DefaultChoiceValues; + + HAPI_NodeId NodeId = -1; + HAPI_AssetLibraryId AssetLibraryId = -1; + FString HoudiniAssetName; + + if (AssetId >= 0) + { + // Get the asset's info + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HAPI_Result Result = FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo); + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + NodeId = AssetInfo.nodeId; + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); + + ParmCount = NodeInfo.parmCount; + } + else + { + if (!FHoudiniEngineUtils::LoadHoudiniAsset(InHoudiniAsset, AssetLibraryId) ) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - could not load Houdini Asset.")); + return false; + } + + // Handle hda files that contain multiple assets + TArray AssetNames; + if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - unable to retrieve asset names.")); + return false; + } + + if (AssetNames.Num() == 0) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - unable to retrieve asset names.")); + return false; + } + + // If no InHoudiniAssetName was specified, pick the first asset from the library + if (InHoudiniAssetName.IsEmpty()) + { + const FHoudiniEngineString HoudiniEngineString(AssetNames[0]); + HoudiniEngineString.ToFString(HoudiniAssetName); + } + else + { + // Ensure that the specified asset name is in the library + for (const HAPI_StringHandle& Handle : AssetNames) + { + const FHoudiniEngineString HoudiniEngineString(Handle); + FString AssetNameStr; + HoudiniEngineString.ToFString(AssetNameStr); + if (AssetNameStr == InHoudiniAssetName) + { + HoudiniAssetName = AssetNameStr; + break; + } + } + } + + if (HoudiniAssetName.IsEmpty()) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParametersFromAssetDefinition - could not find asset in library.")); + return false; + } + + HAPI_Result Result = FHoudiniApi::GetAssetDefinitionParmCounts( + FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), &ParmCount, + &DefaultIntValueCount, &DefaultFloatValueCount, &DefaultStringValueCount, &DefaultChoiceValueCount); + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + if (ParmCount > 0) + { + // Allocate space in the default value arrays + // Fetch default values from HAPI + DefaultIntValues.SetNumZeroed(DefaultIntValueCount); + DefaultFloatValues.SetNumZeroed(DefaultFloatValueCount); + DefaultStringValues.SetNumZeroed(DefaultStringValueCount); + DefaultChoiceValues.SetNumZeroed(DefaultChoiceValueCount); + + Result = FHoudiniApi::GetAssetDefinitionParmValues( + FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), + DefaultIntValues.GetData(), 0, DefaultIntValueCount, + DefaultFloatValues.GetData(), 0, DefaultFloatValueCount, + false, DefaultStringValues.GetData(), 0, DefaultStringValueCount, + DefaultChoiceValues.GetData(), 0, DefaultChoiceValueCount); + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + } + } + + NewParameters.Empty(); + if (ParmCount == 0) + { + // The asset doesnt have any parameter, we're done. + return true; + } + else if (ParmCount < 0) + { + // Invalid parm count + return false; + } + + // Retrieve all the parameter infos either from instantiated node or from asset definition. + TArray ParmInfos; + ParmInfos.SetNumUninitialized(ParmCount); + + if (AssetId >= 0) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), NodeId, &ParmInfos[0], 0, ParmCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetDefinitionParmInfos( + FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), &ParmInfos[0], 0, ParmCount), false); + } + + // Create a name lookup cache for the current parameters + // Use an array has in some cases, multiple parameters can have the same name! + TMap> CurrentParametersByName; + CurrentParametersByName.Reserve(CurrentParameters.Num()); + for (const auto& Parm : CurrentParameters) + { + if (!IsValid(Parm)) + continue; + + FString ParmName = Parm->GetParameterName(); + TArray* FoundParmArray = CurrentParametersByName.Find(ParmName); + if (!FoundParmArray) + { + // Create a new array + TArray ParmArray; + ParmArray.Add(Parm); + + // add the new array to the map + CurrentParametersByName.Add(ParmName, ParmArray); + } + else + { + // add this parameter to the existing array + FoundParmArray->Add(Parm); + } + } + + // Create properties for parameters. + TMap FloatRampsToIndex; + TMap ColorRampsToIndex; + TArray NewParmIds; + TArray AllMultiParams; + for (int32 ParamIdx = 0; ParamIdx < ParmCount; ++ParamIdx) + { + // Retrieve param info at this index. + const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; + + // If the parameter is corrupt, skip it + if (ParmInfo.id < 0 || ParmInfo.childIndex < 0) + { + HOUDINI_LOG_WARNING(TEXT("Corrupt parameter %d detected, skipping."), ParamIdx); + continue; + } + + // If the parameter is invisible, skip it. + //if (ParmInfo.invisible) + // continue; + + // Check if any parent folder of this parameter is invisible + bool SkipParm = false; + HAPI_ParmId ParentId = ParmInfo.parentId; + while (ParentId > 0 && !SkipParm) + { + if (const HAPI_ParmInfo* ParentInfoPtr = ParmInfos.FindByPredicate([=](const HAPI_ParmInfo& Info) { + return Info.id == ParentId; + })) + { + if (ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER) + SkipParm = true; + ParentId = ParentInfoPtr->parentId; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not find parent of parameter %d"), ParmInfo.id); + SkipParm = true; + } + } + + if (SkipParm) + continue; + + // See if this parameter has already been created. + // We can't use the HAPI_ParmId because it is not unique to parameter instances, + // so instead, try to find the existing parameter by name using the lookup table + FString NewParmName; + FHoudiniEngineString(ParmInfo.nameSH).ToFString(NewParmName); + + EHoudiniParameterType ParmType = EHoudiniParameterType::Invalid; + FHoudiniParameterTranslator::GetParmTypeFromParmInfo(ParmInfo, ParmType); + + // Not using the name lookup map! + UHoudiniParameter* FoundHoudiniParameter = nullptr; + TArray* MatchingParameters = CurrentParametersByName.Find(NewParmName); + if ((ParmType != EHoudiniParameterType::Invalid) && MatchingParameters) + { + //for (auto& CurrentParm : *MatchingParameters) + for(int32 Idx = MatchingParameters->Num() - 1; Idx >= 0; Idx--) + { + UHoudiniParameter* CurrentParm = (*MatchingParameters)[Idx]; + if (!CurrentParm) + continue; + + // First Check the parameter types match + if (ParmType != CurrentParm->GetParameterType()) + { + // Types do not match + continue; + } + + // Then, make sure the tuple size hasn't changed + if (CurrentParm->GetTupleSize() != ParmInfo.size) + { + // Tuple do not match + continue; + } + + if (!CheckParameterTypeAndClassMatch(CurrentParm, ParmType)) + { + // Wrong class + continue; + } + + // We can reuse this parameter + FoundHoudiniParameter = CurrentParm; + + // Remove it from the array/map + MatchingParameters->RemoveAt(Idx); + if (MatchingParameters->Num() <= 0) + CurrentParametersByName.Remove(NewParmName); + + break; + } + } + + UHoudiniParameter * HoudiniAssetParameter = nullptr; + if (FoundHoudiniParameter) + { + // We can reuse the parameter we found + HoudiniAssetParameter = FoundHoudiniParameter; + + // Transfer param object from current map to new map + CurrentParameters.Remove(HoudiniAssetParameter); + + // Do a fast update of this parameter + if (!FHoudiniParameterTranslator::UpdateParameterFromInfo( + HoudiniAssetParameter, NodeId, ParmInfo, InForceFullUpdate, bUpdateValues, + AssetId >= 0 ? nullptr : &DefaultIntValues, + AssetId >= 0 ? nullptr : &DefaultFloatValues, + AssetId >= 0 ? nullptr : &DefaultStringValues, + AssetId >= 0 ? nullptr : &DefaultChoiceValues)) + continue; + + // Reset the states of ramp parameters. + switch (HoudiniAssetParameter->GetParameterType()) + { + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); + if (FloatRampParam) + { + // Record float and color ramps for further processing (creating their Points arrays) + FloatRampsToIndex.Add(FloatRampParam, NewParameters.Num()); + UHoudiniAssetComponent* ParentHAC = Cast(FloatRampParam->GetOuter()); + if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) + FloatRampParam->bCaching = false; + } + + break; + } + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); + if (ColorRampParam) + { + // Record float and color ramps for further processing (creating their Points arrays) + ColorRampsToIndex.Add(ColorRampParam, NewParameters.Num()); + UHoudiniAssetComponent* ParentHAC = Cast(ColorRampParam->GetOuter()); + if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) + ColorRampParam->bCaching = false; + } + + break; + } + } + + } + else + { + // Create a new parameter object of the appropriate type + HoudiniAssetParameter = CreateTypedParameter(Outer, ParmType, NewParmName); + // Fully update this parameter + if (!FHoudiniParameterTranslator::UpdateParameterFromInfo( + HoudiniAssetParameter, NodeId, ParmInfo, true, true, + AssetId >= 0 ? nullptr : &DefaultIntValues, + AssetId >= 0 ? nullptr : &DefaultFloatValues, + AssetId >= 0 ? nullptr : &DefaultStringValues, + AssetId >= 0 ? nullptr : &DefaultChoiceValues)) + continue; + + // Record float and color ramps for further processing (creating their Points arrays) + const EHoudiniParameterType NewParamType = HoudiniAssetParameter->GetParameterType(); + if (NewParamType == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); + if (FloatRampParam) + FloatRampsToIndex.Add(FloatRampParam, NewParameters.Num()); + } + else if (NewParamType == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); + if (ColorRampParam) + ColorRampsToIndex.Add(ColorRampParam, NewParameters.Num()); + } + } + + // Add the new parameters + NewParameters.Add(HoudiniAssetParameter); + NewParmIds.Add(ParmInfo.id); + + // Check if the parameter is a direct child of a multiparam. + if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::MultiParm) + AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); + + if (AllMultiParams.Contains(HoudiniAssetParameter->GetParentParmId())) + { + HoudiniAssetParameter->SetIsDirectChildOfMultiParm(true); + + // Treat the folderlist whose direct parent is a multi param as a multi param too. + if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::FolderList) + AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); + } + } + + // Assign folder type to all folderlists, + // if the first child of the folderlist is Tab or Radio button, set the bIsTabMenu of the folderlistParam to be true, otherwise false + for (int32 Idx = 0; Idx < NewParameters.Num(); ++Idx) + { + UHoudiniParameter * CurParam = NewParameters[Idx]; + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (CurParam->GetParameterType() == EHoudiniParameterType::FolderList) + { + UHoudiniParameterFolderList* CurFolderList = Cast(CurParam); + if (!CurFolderList || CurFolderList->IsPendingKill()) + continue; + + int32 FirstChildIdx = Idx + 1; + if (!NewParameters.IsValidIndex(FirstChildIdx)) + continue; + + UHoudiniParameterFolder* FirstChildFolder = Cast(NewParameters[FirstChildIdx]); + if (!FirstChildFolder || FirstChildFolder->IsPendingKill()) + continue; + + if (FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Radio || + FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Tabs) + { + // If this is the first time build + if (!CurFolderList->IsTabMenu()) + { + // Set the folderlist to be tabs + CurFolderList->SetIsTabMenu(true); + // Select the first child tab folder by default. + FirstChildFolder->SetChosen(true); + } + } + else + CurFolderList->SetIsTabMenu(false); + } + } + + // Create / update the Points arrays for the ramp parameters + if (FloatRampsToIndex.Num() > 0) + { + for (TPair const& Entry : FloatRampsToIndex) + { + UHoudiniParameterRampFloat* const RampFloatParam = Entry.Key; + const int32 ParamIndex = Entry.Value; + if (!IsValid(RampFloatParam)) + continue; + + RampFloatParam->UpdatePointsArray(NewParameters, ParamIndex + 1); + } + } + if (ColorRampsToIndex.Num() > 0) + { + for (TPair const& Entry : ColorRampsToIndex) + { + UHoudiniParameterRampColor* const RampColorParam = Entry.Key; + const int32 ParamIndex = Entry.Value; + if (!IsValid(RampColorParam)) + continue; + + RampColorParam->UpdatePointsArray(NewParameters, ParamIndex + 1); + } + } + + return true; +} + + +void +FHoudiniParameterTranslator::GetParmTypeFromParmInfo( + const HAPI_ParmInfo& ParmInfo, + EHoudiniParameterType& ParmType) +{ + ParmType = EHoudiniParameterType::Invalid; + //ParmValueType = EHoudiniParameterValueType::Invalid; + + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_BUTTON: + ParmType = EHoudiniParameterType::Button; + //ParmValueType = EHoudiniParameterValueType::Int; + break; + + case HAPI_PARMTYPE_STRING: + { + if (ParmInfo.choiceCount > 0) + { + ParmType = EHoudiniParameterType::StringChoice; + //ParmValueType = EHoudiniParameterValueType::String; + } + else + { + ParmType = EHoudiniParameterType::String; + //ParmValueType = EHoudiniParameterValueType::String; + } + break; + } + + case HAPI_PARMTYPE_INT: + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_BUTTONSTRIP) + { + ParmType = EHoudiniParameterType::ButtonStrip; + break; + } + + if (ParmInfo.choiceCount > 0) + { + ParmType = EHoudiniParameterType::IntChoice; + //ParmValueType = EHoudiniParameterValueType::Int; + } + else + { + ParmType = EHoudiniParameterType::Int; + //ParmValueType = EHoudiniParameterValueType::Int; + } + break; + } + + case HAPI_PARMTYPE_FLOAT: + { + ParmType = EHoudiniParameterType::Float; + //ParmValueType = EHoudiniParameterValueType::Float; + break; + } + + case HAPI_PARMTYPE_TOGGLE: + { + ParmType = EHoudiniParameterType::Toggle; + //ParmValueType = EHoudiniParameterValueType::Int; + break; + } + + case HAPI_PARMTYPE_COLOR: + { + ParmType = EHoudiniParameterType::Color; + //ParmValueType = EHoudiniParameterValueType::Float; + break; + } + + case HAPI_PARMTYPE_LABEL: + { + ParmType = EHoudiniParameterType::Label; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_SEPARATOR: + { + ParmType = EHoudiniParameterType::Separator; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + case HAPI_PARMTYPE_FOLDERLIST: + { + ParmType = EHoudiniParameterType::FolderList; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + // Treat radio folders as tab folders for now + case HAPI_PARMTYPE_FOLDERLIST_RADIO: + { + ParmType = EHoudiniParameterType::FolderList; + break; + } + + case HAPI_PARMTYPE_FOLDER: + { + ParmType = EHoudiniParameterType::Folder; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + case HAPI_PARMTYPE_MULTIPARMLIST: + { + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType) + { + ParmType = EHoudiniParameterType::FloatRamp; + //ParmValueType = EHoudiniParameterValueType::Float; + } + else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + { + ParmType = EHoudiniParameterType::ColorRamp; + //ParmValueType = EHoudiniParameterValueType::Float; + } + else + { + ParmType = EHoudiniParameterType::MultiParm; + //ParmValueType = EHoudiniParameterValueType::Int; + } + break; + } + + case HAPI_PARMTYPE_PATH_FILE: + { + ParmType = EHoudiniParameterType::File; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_DIR: + { + ParmType = EHoudiniParameterType::FileDir; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_GEO: + { + ParmType = EHoudiniParameterType::FileGeo; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + { + ParmType = EHoudiniParameterType::FileImage; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_NODE: + { + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + ParmType = EHoudiniParameterType::Input; + } + else + { + ParmType = EHoudiniParameterType::String; + } + break; + } + + default: + { + // Just ignore unsupported types for now. + HOUDINI_LOG_WARNING(TEXT("Parameter Type (%d) is unsupported"), static_cast(ParmInfo.type)); + break; + } + } +} + +UClass* +FHoudiniParameterTranslator::GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo) +{ + UClass* FoundClass = nullptr; + + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_STRING: + if (!ParmInfo.choiceCount) + FoundClass = UHoudiniParameterString::StaticClass(); + else + FoundClass = UHoudiniParameterChoice ::StaticClass(); + break; + + case HAPI_PARMTYPE_INT: + if (!ParmInfo.choiceCount) + FoundClass = UHoudiniParameterInt::StaticClass(); + else + FoundClass = UHoudiniParameterChoice::StaticClass(); + break; + + case HAPI_PARMTYPE_FLOAT: + FoundClass = UHoudiniParameterFloat::StaticClass(); + break; + + case HAPI_PARMTYPE_TOGGLE: + FoundClass = UHoudiniParameterToggle::StaticClass(); + break; + + case HAPI_PARMTYPE_COLOR: + FoundClass = UHoudiniParameterColor::StaticClass(); + break; + + case HAPI_PARMTYPE_LABEL: + FoundClass = UHoudiniParameterLabel::StaticClass(); + break; + + case HAPI_PARMTYPE_BUTTON: + FoundClass = UHoudiniParameterButton::StaticClass(); + break; + + case HAPI_PARMTYPE_SEPARATOR: + FoundClass = UHoudiniParameterSeparator::StaticClass(); + break; + + case HAPI_PARMTYPE_FOLDERLIST: + FoundClass = UHoudiniParameterFolderList::StaticClass(); + break; + + case HAPI_PARMTYPE_FOLDER: + FoundClass = UHoudiniParameterFolder::StaticClass(); + break; + + case HAPI_PARMTYPE_MULTIPARMLIST: + { + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + FoundClass = UHoudiniParameterRampFloat::StaticClass(); + else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + FoundClass = UHoudiniParameterRampColor::StaticClass(); + } + break; + + case HAPI_PARMTYPE_PATH_FILE: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_DIR: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_GEO: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + + case HAPI_PARMTYPE_NODE: + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + FoundClass = UHoudiniParameter::StaticClass(); + } + else + { + FoundClass = UHoudiniParameterString::StaticClass(); + } + break; + } + + if (FoundClass == nullptr) + return UHoudiniParameter::StaticClass(); + + return FoundClass; +} + +bool +FHoudiniParameterTranslator::CheckParameterTypeAndClassMatch(UHoudiniParameter* Parameter, const EHoudiniParameterType& ParmType) +{ + UClass* FoundClass = Parameter->GetClass(); + bool FailedTypeCheck = true; + + switch (ParmType) + { + case EHoudiniParameterType::Invalid: + { + FailedTypeCheck = true; + break; + } + + case EHoudiniParameterType::Button: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterButton >(); + break; + } + + case EHoudiniParameterType::Color: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterColor >(); + break; + } + + case EHoudiniParameterType::ColorRamp: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampColor >(); + break; + } + case EHoudiniParameterType::FloatRamp: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampFloat >(); + break; + } + + case EHoudiniParameterType::File: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileDir: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileGeo: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileImage: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + + case EHoudiniParameterType::Float: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFloat >(); + break; + } + + case EHoudiniParameterType::Folder: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolder >(); + break; + } + + case EHoudiniParameterType::FolderList: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolderList >(); + break; + } + + case EHoudiniParameterType::Input: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterOperatorPath >(); + break; + } + + case EHoudiniParameterType::Int: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterInt >(); + break; + } + + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::StringChoice: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); + break; + } + + case EHoudiniParameterType::Label: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterLabel >(); + break; + } + + case EHoudiniParameterType::MultiParm: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterMultiParm >(); + break; + } + + case EHoudiniParameterType::Separator: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterSeparator >(); + break; + } + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); + break; + } + + case EHoudiniParameterType::Toggle: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterToggle >(); + break; + } + }; + + return !FailedTypeCheck; +} +/* +bool +FHoudiniParameterTranslator::CheckParameterClassAndInfoMatch(UHoudiniParameter* Parameter, const HAPI_ParmInfo& ParmInfo) +{ + if (!Parameter || Parameter->IsPendingKill()) + return false; + + UClass* FoundClass = Parameter->GetClass(); + bool FailedTypeCheck = true; + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_STRING: + if (!ParmInfo.choiceCount) + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); + else + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); + break; + + case HAPI_PARMTYPE_INT: + if (!ParmInfo.choiceCount) + FailedTypeCheck &= !FoundClass->IsChildOf(); + else + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FLOAT: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_TOGGLE: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_COLOR: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_LABEL: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_BUTTON: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_SEPARATOR: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FOLDERLIST: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FOLDER: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_MULTIPARMLIST: + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + break; + + case HAPI_PARMTYPE_PATH_FILE: + case HAPI_PARMTYPE_PATH_FILE_DIR: + case HAPI_PARMTYPE_PATH_FILE_GEO: + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_NODE: + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + else + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + break; + } + + return FailedTypeCheck; +} +*/ + +UHoudiniParameter * +FHoudiniParameterTranslator::CreateTypedParameter(UObject * Outer, const EHoudiniParameterType& ParmType, const FString& ParmName) +{ + UHoudiniParameter* HoudiniParameter = nullptr; + // Create a parameter of the desired type + switch (ParmType) + { + case EHoudiniParameterType::Button: + HoudiniParameter = UHoudiniParameterButton::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::ButtonStrip: + HoudiniParameter = UHoudiniParameterButtonStrip::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Color: + HoudiniParameter = UHoudiniParameterColor::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::ColorRamp: + HoudiniParameter = UHoudiniParameterRampColor::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FloatRamp: + HoudiniParameter = UHoudiniParameterRampFloat::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::File: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FileDir: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileDir); + break; + + case EHoudiniParameterType::FileGeo: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileGeo); + break; + + case EHoudiniParameterType::FileImage: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileImage); + break; + + case EHoudiniParameterType::Float: + HoudiniParameter = UHoudiniParameterFloat::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Folder: + HoudiniParameter = UHoudiniParameterFolder::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FolderList: + HoudiniParameter = UHoudiniParameterFolderList::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Input: + // Input parameter simply use the base class as all the processingsince is handled by UHoudiniInput + HoudiniParameter = UHoudiniParameterOperatorPath::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(ParmType); + break; + + case EHoudiniParameterType::Int: + HoudiniParameter = UHoudiniParameterInt::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::IntChoice: + HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::IntChoice); + break; + + case EHoudiniParameterType::StringChoice: + HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::StringChoice); + break; + + case EHoudiniParameterType::Label: + HoudiniParameter = UHoudiniParameterLabel::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::MultiParm: + HoudiniParameter = UHoudiniParameterMultiParm::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Separator: + HoudiniParameter = UHoudiniParameterSeparator::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + HoudiniParameter = UHoudiniParameterString::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Toggle: + HoudiniParameter = UHoudiniParameterToggle::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Invalid: + // TODO handle invalid params + HoudiniParameter = UHoudiniParameter::Create(Outer, ParmName); + break; + } + + return HoudiniParameter; +} + +bool +FHoudiniParameterTranslator::UpdateParameterFromInfo( + UHoudiniParameter * HoudiniParameter, const HAPI_NodeId& InNodeId, const HAPI_ParmInfo& ParmInfo, + const bool& bFullUpdate, const bool& bUpdateValue, + const TArray* DefaultIntValues, + const TArray* DefaultFloatValues, + const TArray* DefaultStringValues, + const TArray* DefaultChoiceValues) +{ + if (!HoudiniParameter || HoudiniParameter->IsPendingKill()) + return false; + + // Copy values from the ParmInfos + const bool bHasValidNodeId = InNodeId >= 0; + if (bHasValidNodeId) + HoudiniParameter->SetNodeId(InNodeId); + HoudiniParameter->SetParmId(ParmInfo.id); + HoudiniParameter->SetParentParmId(ParmInfo.parentId); + + HoudiniParameter->SetChildIndex(ParmInfo.childIndex); + HoudiniParameter->SetTagCount(ParmInfo.tagCount); + HoudiniParameter->SetTupleSize(ParmInfo.size); + + HoudiniParameter->SetVisible(!ParmInfo.invisible); + HoudiniParameter->SetDisabled(ParmInfo.disabled); + HoudiniParameter->SetSpare(ParmInfo.spare); + HoudiniParameter->SetJoinNext(ParmInfo.joinNext); + + HoudiniParameter->SetTagCount(ParmInfo.tagCount); + HoudiniParameter->SetIsChildOfMultiParm(ParmInfo.isChildOfMultiParm); + + UHoudiniParameterMultiParm* MultiParm = Cast(HoudiniParameter); + if(MultiParm) + MultiParm->InstanceStartOffset = ParmInfo.instanceStartOffset; + + + + // Get the parameter type + EHoudiniParameterType ParmType = HoudiniParameter->GetParameterType(); + + // We need to set string values from the parmInfo + if (bFullUpdate) + { + FString Name; + { + // Name + FHoudiniEngineString HoudiniEngineStringName(ParmInfo.nameSH); + if (HoudiniEngineStringName.ToFString(Name)) + HoudiniParameter->SetParameterName(Name); + } + + { + // Label + FString Label; + FHoudiniEngineString HoudiniEngineStringLabel(ParmInfo.labelSH); + if (HoudiniEngineStringLabel.ToFString(Label)) + HoudiniParameter->SetParameterLabel(Label); + } + + { + // Help + FString Help; + FHoudiniEngineString HoudiniEngineStringHelp(ParmInfo.helpSH); + if (HoudiniEngineStringHelp.ToFString(Help)) + HoudiniParameter->SetParameterHelp(Help); + } + + if (bHasValidNodeId && + (ParmType == EHoudiniParameterType::String + || ParmType == EHoudiniParameterType::Int + || ParmType == EHoudiniParameterType::Float + || ParmType == EHoudiniParameterType::Toggle + || ParmType == EHoudiniParameterType::Color)) + { + // See if the parm has an expression + int32 TupleIdx = ParmInfo.intValuesIndex; + bool bHasExpression = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ParmHasExpression( + FHoudiniEngine::Get().GetSession(), InNodeId, + TCHAR_TO_UTF8(*Name), TupleIdx, &bHasExpression)) + { + // ? + } + + FString ParmExprString = TEXT(""); + if (bHasExpression) + { + // Try to get the expression's value + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmExpression( + FHoudiniEngine::Get().GetSession(), InNodeId, + TCHAR_TO_UTF8(*Name), TupleIdx, &StringHandle)) + { + FHoudiniEngineString HoudiniEngineString(StringHandle); + HoudiniEngineString.ToFString(ParmExprString); + } + + // Check if we actually have an expression + // String parameters return true even if they do not have one + bHasExpression = ParmExprString.Len() > 0; + + } + + HoudiniParameter->SetHasExpression(bHasExpression); + HoudiniParameter->SetExpression(ParmExprString); + } + else + { + HoudiniParameter->SetHasExpression(false); + HoudiniParameter->SetExpression(FString()); + } + + // Get parameter tags. + if (bHasValidNodeId) + { + int32 TagCount = HoudiniParameter->GetTagCount(); + for (int32 Idx = 0; Idx < TagCount; ++Idx) + { + HAPI_StringHandle TagNameSH; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagName( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmInfo.id, Idx, &TagNameSH)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); + continue; + } + + FString NameString = TEXT(""); + FHoudiniEngineString::ToFString(TagNameSH, NameString); + if (NameString.IsEmpty()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); + continue; + } + + HAPI_StringHandle TagValueSH; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmInfo.id, TCHAR_TO_ANSI(*NameString), &TagValueSH)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag value: parmId: %d, tag: %s"), ParmInfo.id, *NameString); + } + + FString ValueString = TEXT(""); + FHoudiniEngineString::ToFString(TagValueSH, ValueString); + + HoudiniParameter->GetTags().Add(NameString, ValueString); + } + } + } + + // + // Update properties specific to parameter classes + switch (ParmType) + { + case EHoudiniParameterType::Button: + { + UHoudiniParameterButton* HoudiniParameterButton = Cast(HoudiniParameter); + if (HoudiniParameterButton && !HoudiniParameterButton->IsPendingKill()) + { + HoudiniParameterButton->SetValueIndex(ParmInfo.intValuesIndex); + } + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + UHoudiniParameterButtonStrip* HoudiniParameterButtonStrip = Cast(HoudiniParameter); + if (HoudiniParameterButtonStrip && !HoudiniParameterButtonStrip->IsPendingKill()) + { + HoudiniParameterButtonStrip->SetValueIndex(ParmInfo.intValuesIndex); + HoudiniParameterButtonStrip->Count = ParmInfo.choiceCount; + } + + if (bFullUpdate) + { + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + + ParmChoices.SetNum(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + } + else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && + DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) + { + FPlatformMemory::Memcpy( + ParmChoices.GetData(), + DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, + sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); + } + else + { + return false; + } + + HoudiniParameterButtonStrip->InitializeLabels(ParmInfo.choiceCount); + + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ButtonLabel = HoudiniParameterButtonStrip->GetStringLabelAt(ChoiceIdx); + if (ButtonLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ButtonLabel)) + return false; + } + } + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterButtonStrip->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.choiceCount) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && + DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.choiceCount - 1)) + { + for (int32 Index = 0; Index < ParmInfo.choiceCount; ++Index) + { + HoudiniParameterButtonStrip->SetValueAt( + Index, (*DefaultIntValues)[ParmInfo.intValuesIndex + Index]); + } + } + else + { + return false; + } + } + } + break; + + case EHoudiniParameterType::Color: + { + UHoudiniParameterColor* HoudiniParameterColor = Cast(HoudiniParameter); + if (HoudiniParameterColor && !HoudiniParameterColor->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterColor->SetValueIndex(ParmInfo.floatValuesIndex); + + // Update the Parameter value if we want to + if (bUpdateValue) + { + // Get the actual value for this property. + FLinearColor Color = FLinearColor::White; + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + (float *)&Color.R, ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultFloatValues && DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex) && + DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex + ParmInfo.size - 1)) + { + FPlatformMemory::Memcpy( + &Color.R, + DefaultFloatValues->GetData() + ParmInfo.floatValuesIndex, + sizeof(float) * ParmInfo.size); + } + else + { + return false; + } + + HoudiniParameterColor->SetColorValue(Color); + } + + if (bFullUpdate) + { + // Set the default value at parameter created. + HoudiniParameterColor->SetDefaultValue(); + } + } + } + break; + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* HoudiniParameterRampColor = Cast(HoudiniParameter); + if (HoudiniParameterRampColor && !HoudiniParameterRampColor->IsPendingKill()) + { + HoudiniParameterRampColor->SetInstanceCount(ParmInfo.instanceCount); + HoudiniParameterRampColor->MultiParmInstanceLength = ParmInfo.instanceLength; + } + } + break; + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* HoudiniParameterRampFloat = Cast(HoudiniParameter); + if (HoudiniParameterRampFloat && !HoudiniParameterRampFloat->IsPendingKill()) + { + HoudiniParameterRampFloat->SetInstanceCount(ParmInfo.instanceCount); + HoudiniParameterRampFloat->MultiParmInstanceLength = ParmInfo.instanceLength; + } + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + UHoudiniParameterFile* HoudiniParameterFile = Cast(HoudiniParameter); + if (HoudiniParameterFile && !HoudiniParameterFile->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFile->SetValueIndex(ParmInfo.stringValuesIndex); + + // Update the file filter and read only tag only for full updates + if (bFullUpdate) + { + // Check if we are read-only + bool bIsReadOnly = false; + FString FileChooserTag; + if (bHasValidNodeId && FHoudiniParameterTranslator::HapiGetParameterTagValue(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_FILE_READONLY, FileChooserTag)) + { + if (FileChooserTag.Equals(TEXT("read"), ESearchCase::IgnoreCase)) + bIsReadOnly = true; + } + HoudiniParameterFile->SetReadOnly(bIsReadOnly); + + // Update the file type using the typeInfo string. + if (ParmInfo.typeInfoSH > 0) + { + FString Filters; + FHoudiniEngineString HoudiniEngineString(ParmInfo.typeInfoSH); + if (HoudiniEngineString.ToFString(Filters)) + { + if (!Filters.IsEmpty()) + HoudiniParameterFile->SetFileFilters(Filters); + } + } + } + + if (bUpdateValue) + { + // Get the actual values for this property. + TArray< HAPI_StringHandle > StringHandles; + + if (bHasValidNodeId) + { + StringHandles.SetNumZeroed(ParmInfo.size); + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), InNodeId, false, + &StringHandles[0], ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && + DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FPlatformMemory::Memcpy( + &StringHandles[0], + DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, + sizeof(HAPI_StringHandle) * ParmInfo.size); + } + else + { + return false; + } + + // Convert HAPI string handles to Unreal strings. + HoudiniParameterFile->SetNumberOfValues(ParmInfo.size); + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + + // Update the parameter value + HoudiniParameterFile->SetValueAt(ValueString, Idx); + } + } + + if (bFullUpdate) + { + HoudiniParameterFile->SetDefaultValues(); + } + } + } + break; + + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* HoudiniParameterFloat = Cast(HoudiniParameter); + if (HoudiniParameterFloat && !HoudiniParameterFloat->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFloat->SetValueIndex(ParmInfo.floatValuesIndex); + + if (bUpdateValue) + { + // Update the parameter's value + HoudiniParameterFloat->SetNumberOfValues(ParmInfo.size); + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterFloat->GetValuesPtr(), + ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultFloatValues && DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex) && + DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex + ParmInfo.size - 1)) + { + FPlatformMemory::Memcpy( + HoudiniParameterFloat->GetValuesPtr(), + DefaultFloatValues->GetData() + ParmInfo.floatValuesIndex, + sizeof(float) * ParmInfo.size); + } + else + { + return false; + } + } + + if (bFullUpdate) + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_FLOAT_LOG) + { + HoudiniParameterFloat->SetIsLogarithmic(true); + } + + // set the default float values. + HoudiniParameterFloat->SetDefaultValues(); + + // Only update Unit, no swap, and Min/Max values when doing a full update + + // Get the parameter's unit from the "unit" tag + FString ParamUnit; + if (bHasValidNodeId) + { + FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); + HoudiniParameterFloat->SetUnit(ParamUnit); + // Get the parameter's no swap tag (hengine_noswap) + HoudiniParameterFloat->SetNoSwap(HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_NOSWAP)); + } + + // Set the min and max for this parameter + if (ParmInfo.hasMin) + { + HoudiniParameterFloat->SetHasMin(true); + HoudiniParameterFloat->SetMin(ParmInfo.min); + } + else + { + HoudiniParameterFloat->SetHasMin(false); + HoudiniParameterFloat->SetMin(TNumericLimits::Lowest()); + } + + if (ParmInfo.hasMax) + { + HoudiniParameterFloat->SetHasMax(true); + HoudiniParameterFloat->SetMax(ParmInfo.max); + } + else + { + HoudiniParameterFloat->SetHasMax(false); + HoudiniParameterFloat->SetMax(TNumericLimits::Max()); + } + + // Set min and max for UI for this property. + bool bUsesDefaultMin = false; + if (ParmInfo.hasUIMin) + { + HoudiniParameterFloat->SetHasUIMin(true); + HoudiniParameterFloat->SetUIMin(ParmInfo.UIMin); + } + else if (ParmInfo.hasMin) + { + // If it is not set, use supplied min. + HoudiniParameterFloat->SetUIMin(ParmInfo.min); + } + else + { + // Min value Houdini uses by default. + HoudiniParameterFloat->SetUIMin(HAPI_UNREAL_PARAM_FLOAT_UI_MIN); + bUsesDefaultMin = true; + } + + bool bUsesDefaultMax = false; + if (ParmInfo.hasUIMax) + { + HoudiniParameterFloat->SetHasUIMax(true); + HoudiniParameterFloat->SetUIMax(ParmInfo.UIMax); + } + else if (ParmInfo.hasMax) + { + // If it is not set, use supplied max. + HoudiniParameterFloat->SetUIMax(ParmInfo.max); + } + else + { + // Max value Houdini uses by default. + HoudiniParameterFloat->SetUIMax(HAPI_UNREAL_PARAM_FLOAT_UI_MAX); + bUsesDefaultMax = true; + } + + if (bUsesDefaultMin || bUsesDefaultMax) + { + // If we are using defaults, we can detect some most common parameter names and alter defaults. + FString LocalParameterName = HoudiniParameterFloat->GetParameterName(); + FHoudiniEngineString HoudiniEngineString(ParmInfo.nameSH); + HoudiniEngineString.ToFString(LocalParameterName); + + static const FString ParameterNameTranslate(TEXT(HAPI_UNREAL_PARAM_TRANSLATE)); + static const FString ParameterNameRotate(TEXT(HAPI_UNREAL_PARAM_ROTATE)); + static const FString ParameterNameScale(TEXT(HAPI_UNREAL_PARAM_SCALE)); + static const FString ParameterNamePivot(TEXT(HAPI_UNREAL_PARAM_PIVOT)); + + if (!LocalParameterName.IsEmpty()) + { + if (LocalParameterName.Equals(ParameterNameTranslate) + || LocalParameterName.Equals(ParameterNameScale) + || LocalParameterName.Equals(ParameterNamePivot)) + { + if (bUsesDefaultMin) + { + HoudiniParameterFloat->SetUIMin(-1.0f); + } + if (bUsesDefaultMax) + { + HoudiniParameterFloat->SetUIMax(1.0f); + } + } + else if (LocalParameterName.Equals(ParameterNameRotate)) + { + if (bUsesDefaultMin) + { + HoudiniParameterFloat->SetUIMin(0.0f); + } + if (bUsesDefaultMax) + { + HoudiniParameterFloat->SetUIMax(360.0f); + } + } + } + } + } + } + } + break; + + case EHoudiniParameterType::Folder: + { + UHoudiniParameterFolder* HoudiniParameterFolder = Cast(HoudiniParameter); + if (HoudiniParameterFolder && !HoudiniParameterFolder->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFolder->SetValueIndex(ParmInfo.intValuesIndex); + HoudiniParameterFolder->SetFolderType(GetFolderTypeFromParamInfo(&ParmInfo)); + } + } + break; + + case EHoudiniParameterType::FolderList: + { + UHoudiniParameterFolderList* HoudiniParameterFolderList = Cast(HoudiniParameter); + if (HoudiniParameterFolderList && !HoudiniParameterFolderList->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFolderList->SetValueIndex(ParmInfo.intValuesIndex); + } + } + break; + + case EHoudiniParameterType::Input: + { + // Inputs parameters are just stored, and handled separately by UHoudiniInputs + UHoudiniParameterOperatorPath* HoudiniParameterOperatorPath = Cast(HoudiniParameter); + if (HoudiniParameterOperatorPath && !HoudiniParameterOperatorPath->IsPendingKill()) + { + /* + // DO NOT CREATE A DUPLICATE INPUT HERE! + // Inputs are created by the input translator, and will be tied to this parameter there + UHoudiniInput * NewInput = NewObject< UHoudiniInput >( + HoudiniParameterOperatorPath, + UHoudiniInput::StaticClass()); + + UHoudiniAssetComponent *ParentHAC = Cast(HoudiniParameterOperatorPath->GetOuter()); + + if (!ParentHAC) + return false; + + if (!NewInput || NewInput->IsPendingKill()) + return false; + + // Set the nodeId + NewInput->SetAssetNodeId(ParentHAC->GetAssetId()); + NewInput->SetInputType(EHoudiniInputType::Geometry); + HoudiniParameterOperatorPath->HoudiniInputs.Add(NewInput); + */ + // Set the valueIndex + HoudiniParameterOperatorPath->SetValueIndex(ParmInfo.stringValuesIndex); + } + } + break; + + case EHoudiniParameterType::Int: + { + UHoudiniParameterInt* HoudiniParameterInt = Cast(HoudiniParameter); + if (HoudiniParameterInt && !HoudiniParameterInt->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterInt->SetValueIndex(ParmInfo.intValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + HoudiniParameterInt->SetNumberOfValues(ParmInfo.size); + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterInt->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && + DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.size - 1)) + { + for (int32 Index = 0; Index < ParmInfo.size; ++Index) + { + // TODO: cannot use SetValueAt: Min/Max has not yet been configured and defaults to 0,0 + // so the value is clamped to 0 + // HoudiniParameterInt->SetValueAt( + // (*DefaultIntValues)[ParmInfo.intValuesIndex + Index], Index); + *(HoudiniParameterInt->GetValuesPtr() + Index) = (*DefaultIntValues)[ParmInfo.intValuesIndex + Index]; + } + } + else + { + return false; + } + } + + if (bFullUpdate) + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_INT_LOG) + { + HoudiniParameterInt->SetIsLogarithmic(true); + } + + // Set the default int values at created + HoudiniParameterInt->SetDefaultValues(); + // Only update unit and Min/Max values for a full update + + // Get the parameter's unit from the "unit" tag + FString ParamUnit; + if (bHasValidNodeId) + { + FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); + HoudiniParameterInt->SetUnit(ParamUnit); + } + + // Set the min and max for this parameter + if (ParmInfo.hasMin) + { + HoudiniParameterInt->SetHasMin(true); + HoudiniParameterInt->SetMin((int32)ParmInfo.min); + } + else + { + HoudiniParameterInt->SetHasMin(false); + HoudiniParameterInt->SetMin(TNumericLimits::Lowest()); + } + + if (ParmInfo.hasMax) + { + HoudiniParameterInt->SetHasMax(true); + HoudiniParameterInt->SetMax((int32)ParmInfo.max); + } + else + { + HoudiniParameterInt->SetHasMax(false); + HoudiniParameterInt->SetMax(TNumericLimits::Max()); + } + + // Set min and max for UI for this property. + if (ParmInfo.hasUIMin) + { + HoudiniParameterInt->SetHasUIMin(true); + HoudiniParameterInt->SetUIMin((int32)ParmInfo.UIMin); + } + else if (ParmInfo.hasMin) + { + // If it is not set, use supplied min. + HoudiniParameterInt->SetUIMin((int32)ParmInfo.min); + } + else + { + // Min value Houdini uses by default. + HoudiniParameterInt->SetUIMin(HAPI_UNREAL_PARAM_INT_UI_MIN); + } + + if (ParmInfo.hasUIMax) + { + HoudiniParameterInt->SetHasUIMax(true); + HoudiniParameterInt->SetUIMax((int32)ParmInfo.UIMax); + } + else if (ParmInfo.hasMax) + { + // If it is not set, use supplied max. + HoudiniParameterInt->SetUIMax((int32)ParmInfo.max); + } + else + { + // Max value Houdini uses by default. + HoudiniParameterInt->SetUIMax(HAPI_UNREAL_PARAM_INT_UI_MAX); + } + } + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* HoudiniParameterIntChoice = Cast(HoudiniParameter); + if (HoudiniParameterIntChoice && !HoudiniParameterIntChoice->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterIntChoice->SetValueIndex(ParmInfo.intValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + int32 CurrentIntValue = 0; + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &CurrentIntValue, + ParmInfo.intValuesIndex, 1/*ParmInfo.size*/), false); + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex)) + { + CurrentIntValue = (*DefaultIntValues)[ParmInfo.intValuesIndex]; + } + else + { + return false; + } + + // Check the value is valid + if (CurrentIntValue >= ParmInfo.choiceCount) + { + HOUDINI_LOG_WARNING(TEXT("parm '%s' has an invalid value %d, menu tokens are not supported for choice menus"), + *HoudiniParameterIntChoice->GetParameterName(), CurrentIntValue); + CurrentIntValue = 0; + } + + HoudiniParameterIntChoice->SetIntValue(CurrentIntValue); + } + + // Get the choice descriptors + if (bFullUpdate) + { + // Set the default value at created + HoudiniParameterIntChoice->SetDefaultIntValue(); + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + + ParmChoices.SetNum(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + } + else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && + DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) + { + FPlatformMemory::Memcpy( + ParmChoices.GetData(), + DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, + sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); + } + else + { + return false; + } + + // Set the array sizes + HoudiniParameterIntChoice->SetNumChoices(ParmInfo.choiceCount); + + bool bMatchedSelectionLabel = false; + int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValue(); + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ChoiceLabel = HoudiniParameterIntChoice->GetStringChoiceLabelAt(ChoiceIdx); + if (ChoiceLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ChoiceLabel)) + return false; + //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); + } + + // Match our string value to the corresponding selection label. + if (ChoiceIdx == CurrentIntValue) + { + HoudiniParameterIntChoice->SetStringValue(*ChoiceLabel); + } + } + } + else if (bUpdateValue) + { + // We still need to match the string value to the label + HoudiniParameterIntChoice->UpdateStringValueFromInt(); + } + } + } + break; + + case EHoudiniParameterType::StringChoice: + { + UHoudiniParameterChoice* HoudiniParameterStringChoice = Cast(HoudiniParameter); + if (HoudiniParameterStringChoice && !HoudiniParameterStringChoice->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterStringChoice->SetValueIndex(ParmInfo.stringValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + HAPI_StringHandle StringHandle; + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandle, + ParmInfo.stringValuesIndex, 1/*ParmInfo.size*/), false); + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex)) + { + StringHandle = (*DefaultStringValues)[ParmInfo.stringValuesIndex]; + } + else + { + return false; + } + + // Get the string value + FString StringValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + HoudiniEngineString.ToFString(StringValue); + + HoudiniParameterStringChoice->SetStringValue(StringValue); + } + + // Get the choice descriptors + if (bFullUpdate) + { + // Set default value at created. + HoudiniParameterStringChoice->SetDefaultStringValue(); + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + + ParmChoices.SetNum(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + } + else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && + DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) + { + FPlatformMemory::Memcpy( + ParmChoices.GetData(), + DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, + sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); + } + else + { + return false; + } + + // Set the array sizes + HoudiniParameterStringChoice->SetNumChoices(ParmInfo.choiceCount); + + bool bMatchedSelectionLabel = false; + FString CurrentStringValue = HoudiniParameterStringChoice->GetStringValue(); + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ChoiceValue = HoudiniParameterStringChoice->GetStringChoiceValueAt(ChoiceIdx); + if (ChoiceValue) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].valueSH); + if (!HoudiniEngineString.ToFString(*ChoiceValue)) + return false; + //StringChoiceValues.Add(TSharedPtr< FString >(ChoiceValue)); + } + + FString * ChoiceLabel = HoudiniParameterStringChoice->GetStringChoiceLabelAt(ChoiceIdx); + if (ChoiceLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ChoiceLabel)) + return false; + //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); + } + + // If this is a string choice list, we need to match name with corresponding selection label. + if (!bMatchedSelectionLabel && ChoiceValue->Equals(CurrentStringValue)) + { + bMatchedSelectionLabel = true; + HoudiniParameterStringChoice->SetIntValue(ChoiceIdx); + } + } + } + else if (bUpdateValue) + { + // We still need to match the string value to the label + HoudiniParameterStringChoice->UpdateIntValueFromString(); + } + } + } + break; + + case EHoudiniParameterType::Label: + { + UHoudiniParameterLabel* HoudiniParameterLabel = Cast(HoudiniParameter); + if (HoudiniParameterLabel && !HoudiniParameterLabel->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_LABEL) + return false; + + // Set the valueIndex + HoudiniParameterLabel->SetValueIndex(ParmInfo.stringValuesIndex); + + // Get the actual value for this property. + TArray StringHandles; + + if (bHasValidNodeId) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandles[0], + ParmInfo.stringValuesIndex, ParmInfo.size); + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && + DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FPlatformMemory::Memcpy( + StringHandles.GetData(), + DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, + sizeof(HAPI_StringHandle) * ParmInfo.size); + } + else + { + return false; + } + + HoudiniParameterLabel->EmptyLabelString(); + + // Convert HAPI string handles to Unreal strings. + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + HoudiniParameterLabel->AddLabelString(ValueString); + } + } + } + break; + + case EHoudiniParameterType::MultiParm: + { + UHoudiniParameterMultiParm* HoudiniParameterMulti = Cast(HoudiniParameter); + if (HoudiniParameterMulti && !HoudiniParameterMulti->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST) + return false; + + // Set the valueIndex + HoudiniParameterMulti->SetValueIndex(ParmInfo.intValuesIndex); + + // Set the multiparm value + int32 MultiParmValue = 0; + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &MultiParmValue, ParmInfo.intValuesIndex, 1), false); + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex)) + { + MultiParmValue = (*DefaultIntValues)[ParmInfo.intValuesIndex]; + } + else + { + return false; + } + + HoudiniParameterMulti->SetValue(MultiParmValue); + HoudiniParameterMulti->MultiParmInstanceCount = ParmInfo.instanceCount; + HoudiniParameterMulti->MultiParmInstanceLength = ParmInfo.instanceLength; + + } + + if (bFullUpdate) + { + HoudiniParameterMulti->SetDefaultInstanceCount(ParmInfo.instanceCount); + } + } + break; + + case EHoudiniParameterType::Separator: + { + UHoudiniParameterSeparator* HoudiniParameterSeparator = Cast(HoudiniParameter); + if (HoudiniParameterSeparator && !HoudiniParameterSeparator->IsPendingKill()) + { + // We can only handle separator type. + if (ParmInfo.type != HAPI_PARMTYPE_SEPARATOR) + return false; + + // Set the valueIndex + HoudiniParameterSeparator->SetValueIndex(ParmInfo.stringValuesIndex); + } + } + break; + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + { + UHoudiniParameterString* HoudiniParameterString = Cast(HoudiniParameter); + if (HoudiniParameterString && !HoudiniParameterString->IsPendingKill()) + { + // We can only handle string type. + if (ParmInfo.type != HAPI_PARMTYPE_STRING && ParmInfo.type != HAPI_PARMTYPE_NODE) + return false; + + // Set the valueIndex + HoudiniParameterString->SetValueIndex(ParmInfo.stringValuesIndex); + + // Stop if we don't want to update the value + if (bUpdateValue) + { + // Get the actual value for this property. + TArray< HAPI_StringHandle > StringHandles; + + if (bHasValidNodeId) + { + StringHandles.SetNumZeroed(ParmInfo.size); + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandles[0], + ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && + DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FPlatformMemory::Memcpy( + StringHandles.GetData(), + DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, + sizeof(HAPI_StringHandle) * ParmInfo.size); + } + else + { + return false; + } + + // Convert HAPI string handles to Unreal strings. + HoudiniParameterString->SetNumberOfValues(ParmInfo.size); + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + HoudiniParameterString->SetValueAt(ValueString, Idx); + } + } + + if (bFullUpdate) + { + // Set default string values on created + HoudiniParameterString->SetDefaultValues(); + // Check if the parameter has the "asset_ref" tag + if (bHasValidNodeId) + { + HoudiniParameterString->SetIsAssetRef( + FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_ASSET_REF)); + } + } + } + } + break; + + case EHoudiniParameterType::Toggle: + { + UHoudiniParameterToggle* HoudiniParameterToggle = Cast(HoudiniParameter); + if (HoudiniParameterToggle && !HoudiniParameterToggle->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_TOGGLE) + return false; + + // Set the valueIndex + HoudiniParameterToggle->SetValueIndex(ParmInfo.intValuesIndex); + + // Stop if we don't want to update the value + if (bUpdateValue) + { + // Get the actual values for this property. + HoudiniParameterToggle->SetNumberOfValues(ParmInfo.size); + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterToggle->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && + DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.size - 1)) + { + for (int32 Index = 0; Index < ParmInfo.size; ++Index) + { + HoudiniParameterToggle->SetValueAt( + (*DefaultIntValues)[ParmInfo.intValuesIndex + Index] != 0, Index); + } + } + else + { + return false; + } + } + } + + if (bFullUpdate) + { + HoudiniParameterToggle->SetDefaultValues(); + } + } + break; + + case EHoudiniParameterType::Invalid: + { + // TODO + } + break; + } + + return true; +} + +bool +FHoudiniParameterTranslator::HapiGetParameterTagValue(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag, FString& TagValue) +{ + // Default + TagValue = FString(); + + // Does the parameter has the tag? + bool HasTag = false; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &HasTag), false); + + if (!HasTag) + return false; + + // Get the tag string value + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &StringHandle), false); + + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(TagValue)) + { + return true; + } + + return false; +} + + +bool +FHoudiniParameterTranslator::HapiGetParameterUnit(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString) +{ + // + OutUnitString = TEXT(""); + + // We're looking for the parameter unit tag + //FString UnitTag = "units"; + + // Get the actual string value. + FString UnitString = TEXT(""); + if (!FHoudiniParameterTranslator::HapiGetParameterTagValue(NodeId, ParmId, "units", UnitString)) + return false; + + // We need to do some replacement in the string here in order to be able to get the + // proper unit type when calling FUnitConversion::UnitFromString(...) after. + + // Per second and per hour are the only "per" unit that unreal recognize + UnitString.ReplaceInline(TEXT("s-1"), TEXT("/s")); + UnitString.ReplaceInline(TEXT("h-1"), TEXT("/h")); + + // Houdini likes to add '1' on all the unit, so we'll remove all of them + // except the '-1' that still needs to remain. + UnitString.ReplaceInline(TEXT("-1"), TEXT("--")); + UnitString.ReplaceInline(TEXT("1"), TEXT("")); + UnitString.ReplaceInline(TEXT("--"), TEXT("-1")); + + OutUnitString = UnitString; + + return true; +} + +bool +FHoudiniParameterTranslator::HapiGetParameterHasTag(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag) +{ + // Does the parameter has the tag we're looking for? + bool HasTag = false; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &HasTag), false); + + return HasTag; +} + + +bool +FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * HAC ) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniParameterTranslator::UploadChangedParameters); + + if (!HAC || HAC->IsPendingKill()) + return false; + + TMap RampsToRevert; + // First upload all parameters, including the current child parameters/points of ramps, and then process + // the ramp parameters themselves (delete and insert operations of ramp points) + // This is so that the initial upload of parameter values use the correct parameter value/tuple array indices + // (which will change after potential insert/delete operations). Insert operations will upload their new + // parameter values after the insert. + TArray RampsToUpload; + + for (int32 ParmIdx = 0; ParmIdx < HAC->GetNumParameters(); ParmIdx++) + { + UHoudiniParameter*& CurrentParm = HAC->Parameters[ParmIdx]; + if (!CurrentParm || CurrentParm->IsPendingKill() || !CurrentParm->HasChanged()) + continue; + + bool bSuccess = false; + + const EHoudiniParameterType CurrentParmType = CurrentParm->GetParameterType(); + if (CurrentParm->IsPendingRevertToDefault()) + { + bSuccess = RevertParameterToDefault(CurrentParm); + + if (CurrentParmType == EHoudiniParameterType::FloatRamp || + CurrentParmType == EHoudiniParameterType::ColorRamp) + { + RampsToRevert.Add(CurrentParm->GetParameterName(), CurrentParm); + } + } + else + { + if (CurrentParmType == EHoudiniParameterType::FloatRamp || + CurrentParmType == EHoudiniParameterType::ColorRamp) + { + RampsToUpload.Add(CurrentParm); + } + else + { + bSuccess = UploadParameterValue(CurrentParm); + } + } + + + if (bSuccess) + { + CurrentParm->MarkChanged(false); + //CurrentParm->SetNeedsToTriggerUpdate(false); + } + else + { + // Keep this param marked as changed but prevent it from generating updates + CurrentParm->SetNeedsToTriggerUpdate(false); + } + } + + FHoudiniParameterTranslator::RevertRampParameters(RampsToRevert, HAC->GetAssetId()); + + for (UHoudiniParameter* const RampParam : RampsToUpload) + { + if (!IsValid(RampParam)) + continue; + + if (UploadParameterValue(RampParam)) + RampParam->MarkChanged(false); + } + + return true; +} + +bool +FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return false; + + switch (InParam->GetParameterType()) + { + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* FloatParam = Cast(InParam); + if (!FloatParam || FloatParam->IsPendingKill()) + { + return false; + } + + float* DataPtr = FloatParam->GetValuesPtr(); + if (!DataPtr) + { + return false; + } + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + FloatParam->GetNodeId(), DataPtr, FloatParam->GetValueIndex(), FloatParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::Int: + { + UHoudiniParameterInt* IntParam = Cast(InParam); + if (!IntParam || IntParam->IsPendingKill()) + { + return false; + } + + int32* DataPtr = IntParam->GetValuesPtr(); + if (!DataPtr) + { + return false; + } + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + IntParam->GetNodeId(), DataPtr, IntParam->GetValueIndex(), IntParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::String: + { + UHoudiniParameterString* StringParam = Cast(InParam); + if (!StringParam || StringParam->IsPendingKill()) + { + return false; + } + + int32 NumValues = StringParam->GetNumberOfValues(); + if (NumValues <= 0) + { + return false; + } + + for (int32 Idx = 0; Idx < NumValues; Idx++) + { + std::string ConvertedString = TCHAR_TO_UTF8(*(StringParam->GetValueAt(Idx))); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + StringParam->GetNodeId(), ConvertedString.c_str(), StringParam->GetParmId(), Idx), false); + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* ChoiceParam = Cast(InParam); + if (!ChoiceParam || ChoiceParam->IsPendingKill()) + return false; + + // Set the parameter's int value. + int32 IntValue = ChoiceParam->GetIntValue(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); + } + break; + case EHoudiniParameterType::StringChoice: + { + UHoudiniParameterChoice* ChoiceParam = Cast(InParam); + if (!ChoiceParam || ChoiceParam->IsPendingKill()) + { + return false; + } + + if (ChoiceParam->IsStringChoice()) + { + // Set the parameter's string value. + std::string ConvertedString = TCHAR_TO_UTF8(*(ChoiceParam->GetStringValue())); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), ConvertedString.c_str(), ChoiceParam->GetParmId(), 0), false); + } + else + { + // Set the parameter's int value. + int32 IntValue = ChoiceParam->GetIntValue(); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); + } + } + break; + + case EHoudiniParameterType::Color: + { + UHoudiniParameterColor* ColorParam = Cast(InParam); + if (!ColorParam || ColorParam->IsPendingKill()) + return false; + + bool bHasAlpha = ColorParam->GetTupleSize() == 4 ? true : false; + FLinearColor Color = ColorParam->GetColorValue(); + + // Set the color value + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + ColorParam->GetNodeId(), + (float*)(&Color.R), ColorParam->GetValueIndex(), bHasAlpha ? 4 : 3), false); + + } + break; + + case EHoudiniParameterType::Button: + { + UHoudiniParameterButton* ButtonParam = Cast(InParam); + if (!ButtonParam) + return false; + + TArray DataArray; + DataArray.Add(1); + + // Set the button parameter value to 1, (setting button param to any value will call the callback function.) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ButtonParam->GetNodeId(), + DataArray.GetData(), + ButtonParam->GetValueIndex(), 1), false); + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + UHoudiniParameterButtonStrip* ButtonStripParam = Cast(InParam); + if (!ButtonStripParam) + return false; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ButtonStripParam->GetNodeId(), + ButtonStripParam->Values.GetData(), + ButtonStripParam->GetValueIndex(), ButtonStripParam->Count), false); + } + break; + + case EHoudiniParameterType::Toggle: + { + UHoudiniParameterToggle* ToggleParam = Cast(InParam); + if (!ToggleParam) + return false; + + // Set the toggle parameter values. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ToggleParam->GetNodeId(), + ToggleParam->GetValuesPtr(), + ToggleParam->GetValueIndex(), ToggleParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + UHoudiniParameterFile* FileParam = Cast(InParam); + + if (!UploadDirectoryPath(FileParam)) + return false; + } + break; + + case EHoudiniParameterType::MultiParm: + { + if (!UploadMultiParmValues(InParam)) + return false; + } + + break; + + case EHoudiniParameterType::FloatRamp: + { + if (!UploadRampParameter(InParam)) + return false; + } + break; + + case EHoudiniParameterType::ColorRamp: + { + if (!UploadRampParameter(InParam)) + return false; + } + break; + + default: + { + // TODO: implement other parameter types! + return false; + } + break; + } + + // The parameter is no longer considered as changed + InParam->MarkChanged(false); + + return true; +} + +bool +FHoudiniParameterTranslator::RevertParameterToDefault(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return false; + + if (!InParam->IsPendingRevertToDefault()) + return false; + + TArray TupleToRevert; + InParam->GetTuplePendingRevertToDefault(TupleToRevert); + if (TupleToRevert.Num() <= 0) + return false; + + FString ParameterName = InParam->GetParameterName(); + + bool bReverted = true; + for (auto CurrentIdx : TupleToRevert ) + { + if (!TupleToRevert.IsValidIndex(CurrentIdx)) + { + // revert the whole parameter to its default value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefaults( + FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName))) + { + HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s to its default value."), *ParameterName); + bReverted = false; + } + } + else + { + // revert a tuple to its default value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefault( + FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName), CurrentIdx)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s - %d to its default value."), *ParameterName, CurrentIdx); + bReverted = false; + } + } + } + + if (!bReverted) + return false; + + // The parameter no longer needs to be reverted + InParam->MarkDefault(true); + + return true; +} + +EHoudiniFolderParameterType +FHoudiniParameterTranslator::GetFolderTypeFromParamInfo(const HAPI_ParmInfo* ParamInfo) +{ + EHoudiniFolderParameterType Type = EHoudiniFolderParameterType::Invalid; + + switch (ParamInfo->scriptType) + { + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPSIMPLE: + Type = EHoudiniFolderParameterType::Simple; + break; + + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPCOLLAPSIBLE: + Type = EHoudiniFolderParameterType::Collapsible; + break; + + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUP: + Type = EHoudiniFolderParameterType::Tabs; + break; + + // Treat Radio folders as tabs for now + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPRADIO: + Type = EHoudiniFolderParameterType::Radio; + break; + + default: + Type = EHoudiniFolderParameterType::Other; + break; + + } + + return Type; + +} + +bool +FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad( + UHoudiniParameter* InParam, TArray& OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo) +{ + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + if (!MultiParam || MultiParam->IsPendingKill()) + return false; + + UHoudiniParameterRampFloat* FloatRampParameter = nullptr; + UHoudiniParameterRampColor* ColorRampParameter = nullptr; + + if (MultiParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + FloatRampParameter = Cast(MultiParam); + else if (MultiParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + ColorRampParameter = Cast(MultiParam); + + HAPI_NodeId NodeId = AssetInfo.nodeId; + + int32 Idx = 0; + int32 InstanceCount = -1; + HAPI_ParmId ParmId = -1; + TArray ParmInfos; + if (!GetMultiParmInstanceStartIdx(AssetInfo, MultiParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + const int32 InstanceCountInUnreal = FMath::Max(MultiParam->GetInstanceCount(), 0); + if (InstanceCount > InstanceCountInUnreal) + { + // The multiparm has more instances on the Houdini side, remove instances from the end until it has the same + // number as in Unreal. + // NOTE: Initially this code always removed the first instance. But that causes an issue if HAPI/Houdini does + // not immediately update the parameter names (param1, param2, param3, when param1 is removed, 2 -> 1, + // 3 -> 2) so that could result in GetParameters returning parameters with unique IDs, but where the names + // are not up to date, so in the above example, the last param could still be named param3 when it should + // be named param2. + const int32 Delta = InstanceCount - InstanceCountInUnreal; + for (int32 n = 0; n < Delta; ++n) + { + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset + InstanceCount - 1 - n); + } + } + else if (InstanceCountInUnreal > InstanceCount) + { + // The multiparm has fewer instances on the Houdini side, add instances at the end until it has the same + // number as in Unreal. + // NOTE: Initially this code always inserted before the first instance. But that causes an issue if HAPI/Houdini + // does not immediately update the parameter names (param1, param2, param3, when a param is inserted + // before 1, then 1->2, 2->3, 3->4 so that could result in GetParameters returning parameters with unique + // IDs, but where the names are not up to date, so in the above example, the now second param could still + // be named param1 when it should be named param2. + const int32 Delta = InstanceCountInUnreal - InstanceCount; + for (int32 n = 0; n < Delta; ++n) + { + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset + InstanceCount + n); + } + } + + // We are going to Sync nested multi-params recursively + int32 MyParmId = InParam->GetParmId(); + // First, we need to manually look for our index in the old map + // Since there is a possibility that the parameter interface has changed since our previous cook + int32 MyIndex = -1; + for (int32 ParamIdx = 0; ParamIdx < OldParams.Num(); ParamIdx++) + { + UHoudiniParameter* CurrentOldParm = OldParams[ParamIdx]; + if (!IsValid(CurrentOldParm)) + continue; + + if (CurrentOldParm->GetParmId() != MyParmId) + continue; + + // We found ourself, exit now + MyIndex = ParamIdx; + break; + } + + if (MyIndex >= 0) + { + // Now Sync nested multi-params recursively + for (int32 ParamIdx = MyIndex + 1; ParamIdx < OldParams.Num(); ParamIdx++) + { + UHoudiniParameter* NextParm = OldParams[ParamIdx]; + if (!IsValid(NextParm)) + continue; + + if (NextParm->GetParentParmId() != MyParmId) + continue; + + if (NextParm->GetParameterType() != EHoudiniParameterType::MultiParm) + continue; + + // Always make sure to NOT recurse on ourselves! + // This could happen if parms have been deleted... + if (NextParm->GetParmId() == MyParmId) + continue; + + SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, AssetInfo); + } + } + + // The multiparm is a ramp, Get the param infos again, since the number of param instances is changed + if (!GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + // Step 3: Set values of the inserted points + if (FloatRampParameter) + { + for (auto & Point : FloatRampParameter->Points) + { + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); + + // 2: update float value at param Idx + 1 + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Value), ParmInfos[Idx + 1].floatValuesIndex, 1); + + // 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Point->Interpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + else if (ColorRampParameter) + { + for (auto& Point : ColorRampParameter->Points) + { + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); + + // 2: update color value at param Idx + 1 + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, (float*)(&Point->Value.R), ParmInfos[Idx + 1].floatValuesIndex, 3); + + // 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Point->Interpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + + return true; +} + + +bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam) +{ + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + if (!MultiParam || MultiParam->IsPendingKill()) + return false; + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InParam->GetOuter()); + if (!HoudiniAssetComponent) + return false; + + int32 InsertIndexStart = -1; + UHoudiniParameterRampFloat* RampFloatParam = Cast(InParam); + UHoudiniParameterRampColor* RampColorParam = Cast(InParam); + + TArray *Events = nullptr; + if (RampFloatParam) + { + Events = &(RampFloatParam->ModificationEvents); + InsertIndexStart = RampFloatParam->GetInstanceCount(); + } + else if (RampColorParam) + { + Events = &(RampColorParam->ModificationEvents); + InsertIndexStart = RampColorParam->GetInstanceCount(); + } + else + return false; + + // Handle All Events + Events->Sort([](const UHoudiniParameterRampModificationEvent& A, const UHoudiniParameterRampModificationEvent& B) + { + return A.DeleteInstanceIndex > B.DeleteInstanceIndex; + }); + + + // Step 1: Handle all delete events first + for (auto& Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsDeleteEvent()) + continue; + + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Event->DeleteInstanceIndex + MultiParam->InstanceStartOffset); + + InsertIndexStart -= 1; + } + + int32 InsertIndex = InsertIndexStart; + + + // Step 2: Handle all insert events + for (auto& Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsInsertEvent()) + continue; + + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), InsertIndex + MultiParam->InstanceStartOffset); + + InsertIndex += 1; + } + + // Step 3: Set inserted parameter values (only if there are instances inserted) + if (InsertIndex > InsertIndexStart) + { + if (HoudiniAssetComponent) + { + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), HoudiniAssetComponent->AssetId, &AssetInfo), false); + + int32 Idx = 0; + int32 InstanceCount = -1; + HAPI_ParmId ParmId = -1; + TArray ParmInfos; + if (!FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), + Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + if (InstanceCount < 0) + return false; + + // Instance count doesn't match, + if (InsertIndex != InstanceCount) + return false; + + // Starting index of parameters which we just inserted + Idx += 3 * InsertIndexStart; + + if (!ParmInfos.IsValidIndex(Idx + 2)) + return false; + + for (auto & Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsInsertEvent()) + continue; + + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &(Event->InsertPosition), ParmInfos[Idx].floatValuesIndex, 1); + + // step 2: update value at param Idx + 1 + if (Event->IsFloatRampEvent()) + { + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &(Event->InsertFloat), ParmInfos[Idx + 1].floatValuesIndex, 1); + } + else + { + // color value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, (float*)(&Event->InsertColor.R), ParmInfos[Idx + 1].floatValuesIndex, 3); + } + + // step 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Event->InsertInterpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + } + + // Step 4: clear all events + Events->Empty(); + + return true; +} + +bool FHoudiniParameterTranslator::UploadMultiParmValues(UHoudiniParameter* InParam) +{ + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + if (!MultiParam) + return false; + + TArray &LastModificationArray = MultiParam->MultiParmInstanceLastModifyArray; + + int32 Size = MultiParam->MultiParmInstanceLastModifyArray.Num(); + + for (int32 Index = 0; Index < Size; ++Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Inserted) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) + return false; + + } + } + + for (int32 Index = Size - 1; Index >= 0; --Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) + return false; + } + } + + // Remove all removal events. + for (int32 Index = Size - 1; Index >= 0; --Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) + LastModificationArray.RemoveAt(Index); + } + + // The last modification array is resized. + Size = LastModificationArray.Num(); + + // Reset the last modification array + for (int32 Itr =Size - 1; Itr >= 0; --Itr) + { + LastModificationArray[Itr] = EHoudiniMultiParmModificationType::None; + } + + MultiParam->MultiParmInstanceCount = Size; + + return true; +} + +bool +FHoudiniParameterTranslator::UploadDirectoryPath(UHoudiniParameterFile* InParam) +{ + if(!InParam) + return false; + + for (int32 Index = 0; Index < InParam->GetNumValues(); ++Index) + { + std::string ConvertedString = TCHAR_TO_UTF8(*(InParam->GetValueAt(Index))); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue(FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), ConvertedString.c_str(), InParam->GetParmId(), Index), false); + } + + return true; +} + +bool +FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, + int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos) +{ + // TODO: FIX/IMPROVE THIS! + // This is bad, that function can be called recursively, fetches all parameters, + // iterates on them, and fetches their name!! WTF! + // TODO: Slightly better now, at least we dont fetch every parameter's name! + + // Reset outputs + OutStartIdx = -1; + OutInstanceCount = -1; + OutParmId = -1; + OutParmInfos.Empty(); + + // Try to find the parameter by its name + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, TCHAR_TO_UTF8(*InParmName), &OutParmId), false); + + if (OutParmId < 0) + return false; + + // Get the asset's node info + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &NodeInfo), false); + + // Get all parameters + OutParmInfos.SetNumUninitialized(NodeInfo.parmCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &OutParmInfos[0], 0, NodeInfo.parmCount), false); + + OutStartIdx = 0; + for (const auto& CurrentParmInfo : OutParmInfos) + { + if (OutParmId == CurrentParmInfo.id) + { + OutInstanceCount = OutParmInfos[OutStartIdx].instanceCount; + + // Increment, to get the Start index of the ramp children parameters + OutStartIdx++; + return true; + } + + OutStartIdx++; + } + + // We failed to find the parm + OutStartIdx = -1; + + return false; +} + +bool +FHoudiniParameterTranslator::RevertRampParameters(TMap & InRampParams, const int32 & AssetId) +{ + if (InRampParams.Num() <= 0) + return true; + + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); + + // Retrieve all the parameter infos. + TArray< HAPI_ParmInfo > ParmInfos; + ParmInfos.SetNumUninitialized(NodeInfo.parmCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); + + int32 ParamIdx = 0; + while (ParamIdx < ParmInfos.Num()) + { + const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; + FString ParmName; + FHoudiniEngineString(ParmInfo.nameSH).ToFString(ParmName); + + if (InRampParams.Contains(ParmName)) + { + if (!InRampParams[ParmName]) + { + ParamIdx += 1; + continue; + } + + if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat * FloatRamp = Cast(InRampParams[ParmName]); + if (!FloatRamp) + { + ParamIdx += 1; + continue; + } + + if (ParmInfo.instanceCount != FloatRamp->NumDefaultPoints) + { + ParamIdx += 1; + continue; + } + + for (int32 PtIdx = 0; PtIdx < FloatRamp->NumDefaultPoints; ++PtIdx) + { + const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; + const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; + const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultValues.GetData() + PtIdx, ValueParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); + + ParamIdx += 3; + } + } + + if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor * ColorRamp = Cast(InRampParams[ParmName]); + if (!ColorRamp) + { + ParamIdx += 1; + continue; + } + + if (ParmInfo.instanceCount != ColorRamp->NumDefaultPoints) + { + ParamIdx += 1; + continue; + } + + for (int32 PtIdx = 0; PtIdx < ColorRamp->NumDefaultPoints; ++PtIdx) + { + const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; + const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; + const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, ColorRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, (float*)(&ColorRamp->DefaultValues[PtIdx].R), ValueParmInfo.floatValuesIndex, 3); + + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, ColorRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); + + ParamIdx += 3; + } + } + } + + ParamIdx += 1; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h index 0cdba1cd1..aadbf85f7 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h @@ -1,151 +1,158 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -class UHoudiniAssetComponent; -class UHoudiniParameter; -class UHoudiniParameterFile; - -enum class EHoudiniFolderParameterType : uint8; -enum class EHoudiniParameterType : uint8; - -struct HOUDINIENGINE_API FHoudiniParameterTranslator -{ - // - static bool UpdateParameters(UHoudiniAssetComponent* HAC); - - static bool OnPreCookParameters(UHoudiniAssetComponent* HAC); - - // - static bool UpdateLoadedParameters(UHoudiniAssetComponent* HAC); - - // - static bool UploadChangedParameters(UHoudiniAssetComponent* HAC); - - // - static bool UploadParameterValue(UHoudiniParameter* InParam); - - // - static bool UploadMultiParmValues(UHoudiniParameter* InParam); - - // - static bool UploadRampParameter(UHoudiniParameter* InParam); - - // - static bool UploadDirectoryPath(UHoudiniParameterFile* InParam); - - // - static bool RevertParameterToDefault(UHoudiniParameter* InParam); - - // - static bool SyncMultiParmValuesAtLoad( - UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo); - - // - static bool GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, - int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos); - - /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. - @AssetId: Id of the digital asset - @PrimaryObject: Object to use for transactions and as Outer for new top-level parameters - @CurrentParameters: pre: current & post: invalid parameters - @NewParameters: new params added to this - - On Return: CurrentParameters are the old parameters that are no longer valid, - NewParameters are new and re-used parameters. - */ - static bool BuildAllParameters( - const HAPI_NodeId& AssetId, - class UObject* OuterObject, - TArray& CurrentParameters, - TArray& NewParameters, - const bool& bUpdateValues, - const bool& InForceFullUpdate); - - // Parameter creation - static UHoudiniParameter * CreateTypedParameter( - class UObject * Outer, - const EHoudiniParameterType& ParmType, - const FString& ParmName ); - - // Parameter update - // bFullUpdate should be set to false after a minor update (change/recook) of the parameter - // and set to true when creating a new parameter - // bUpdateValue should be set to false when updating loaded parameters - // as the internal parameter's value from HAPI - static bool UpdateParameterFromInfo( - UHoudiniParameter * HoudiniParameter, - const HAPI_NodeId& InNodeId, - const HAPI_ParmInfo& ParmInfo, - const bool& bFullUpdate = true, - const bool& bUpdateValue = true); - - static UClass* GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo); - - static void GetParmTypeFromParmInfo( - const HAPI_ParmInfo& ParmInfo, - EHoudiniParameterType& ParmType); - - static bool CheckParameterTypeAndClassMatch( - UHoudiniParameter* Parameter, - const EHoudiniParameterType& ParmType); - - /* - static bool CheckParameterClassAndInfoMatch( - UHoudiniParameter* Parameter, - const HAPI_ParmInfo& ParmInfo ); - */ - - // HAPI: Get a parameter's tag value. - static bool HapiGetParameterTagValue( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - const FString& Tag, - FString& TagValue); - - // HAPI: Get a parameter's unit. - static bool HapiGetParameterUnit( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - FString& OutUnitString ); - - // HAPI: Indicates if a parameter has a given tag - static bool HapiGetParameterHasTag( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - const FString& Tag); - - // Get folder parameter type from HAPI_ParmInfo struct - static EHoudiniFolderParameterType GetFolderTypeFromParamInfo( - const HAPI_ParmInfo* ParamInfo); - - static bool RevertRampParameters(TMap & InRampParams, const int32 & AssetId); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +class UHoudiniAsset; +class UHoudiniAssetComponent; +class UHoudiniParameter; +class UHoudiniParameterFile; + +enum class EHoudiniFolderParameterType : uint8; +enum class EHoudiniParameterType : uint8; + +struct HOUDINIENGINE_API FHoudiniParameterTranslator +{ + // + static bool UpdateParameters(UHoudiniAssetComponent* HAC); + + static bool OnPreCookParameters(UHoudiniAssetComponent* HAC); + + // + static bool UpdateLoadedParameters(UHoudiniAssetComponent* HAC); + + // + static bool UploadChangedParameters(UHoudiniAssetComponent* HAC); + + // + static bool UploadParameterValue(UHoudiniParameter* InParam); + + // + static bool UploadMultiParmValues(UHoudiniParameter* InParam); + + // + static bool UploadRampParameter(UHoudiniParameter* InParam); + + // + static bool UploadDirectoryPath(UHoudiniParameterFile* InParam); + + // + static bool RevertParameterToDefault(UHoudiniParameter* InParam); + + // + static bool SyncMultiParmValuesAtLoad( + UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo); + + // + static bool GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, + int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos); + + /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. + @AssetId: Id of the digital asset + @PrimaryObject: Object to use for transactions and as Outer for new top-level parameters + @CurrentParameters: pre: current & post: invalid parameters + @NewParameters: new params added to this + + On Return: CurrentParameters are the old parameters that are no longer valid, + NewParameters are new and re-used parameters. + */ + static bool BuildAllParameters( + const HAPI_NodeId& AssetId, + class UObject* OuterObject, + TArray& CurrentParameters, + TArray& NewParameters, + const bool& bUpdateValues, + const bool& InForceFullUpdate, + const UHoudiniAsset* InHoudiniAsset, + const FString& InHoudiniAssetName); + + // Parameter creation + static UHoudiniParameter * CreateTypedParameter( + class UObject * Outer, + const EHoudiniParameterType& ParmType, + const FString& ParmName ); + + // Parameter update + // bFullUpdate should be set to false after a minor update (change/recook) of the parameter + // and set to true when creating a new parameter + // bUpdateValue should be set to false when updating loaded parameters + // as the internal parameter's value from HAPI + static bool UpdateParameterFromInfo( + UHoudiniParameter * HoudiniParameter, + const HAPI_NodeId& InNodeId, + const HAPI_ParmInfo& ParmInfo, + const bool& bFullUpdate = true, + const bool& bUpdateValue = true, + const TArray* DefaultIntValues = nullptr, + const TArray* DefaultFloatValues = nullptr, + const TArray* DefaultStringValues = nullptr, + const TArray* DefaultChoiceValues = nullptr); + + static UClass* GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo); + + static void GetParmTypeFromParmInfo( + const HAPI_ParmInfo& ParmInfo, + EHoudiniParameterType& ParmType); + + static bool CheckParameterTypeAndClassMatch( + UHoudiniParameter* Parameter, + const EHoudiniParameterType& ParmType); + + /* + static bool CheckParameterClassAndInfoMatch( + UHoudiniParameter* Parameter, + const HAPI_ParmInfo& ParmInfo ); + */ + + // HAPI: Get a parameter's tag value. + static bool HapiGetParameterTagValue( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + const FString& Tag, + FString& TagValue); + + // HAPI: Get a parameter's unit. + static bool HapiGetParameterUnit( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + FString& OutUnitString ); + + // HAPI: Indicates if a parameter has a given tag + static bool HapiGetParameterHasTag( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + const FString& Tag); + + // Get folder parameter type from HAPI_ParmInfo struct + static EHoudiniFolderParameterType GetFolderTypeFromParamInfo( + const HAPI_ParmInfo* ParamInfo); + + static bool RevertRampParameters(TMap & InRampParams, const int32 & AssetId); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp index e67b3b415..9b7f01f20 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp @@ -1,1714 +1,1714 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniGeoPartObject.h" -#include "Components/SplineComponent.h" - -#include "EditorViewportClient.h" -#include "Engine/Selection.h" - -#include "HoudiniEnginePrivatePCH.h" - -void -FHoudiniSplineTranslator::ExtractStringPositions(const FString& Positions, TArray& OutPositions) -{ - TArray< FString > PointStrings; - static const TCHAR * PositionSeparators[] = - { - TEXT(" "), - TEXT(","), - }; - - int32 NumCoords = Positions.ParseIntoArray(PointStrings, PositionSeparators, 2); - OutPositions.SetNum(NumCoords / 3); - for (int32 OutIndex = 0; OutIndex < OutPositions.Num(); OutIndex++) - { - const int32& CoordIndex = OutIndex * 3; - OutPositions[OutIndex].X = FCString::Atof(*(PointStrings[CoordIndex + 0])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutPositions[OutIndex].Y = FCString::Atof(*(PointStrings[CoordIndex + 2])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutPositions[OutIndex].Z = FCString::Atof(*(PointStrings[CoordIndex + 1])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } -} - -void -FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData) -{ - OutVectorData.SetNum(InRawData.Num() / 3); - - for (int32 OutIndex = 0; OutIndex < OutVectorData.Num(); OutIndex++) - { - const int32& InIndex = OutIndex * 3; - OutVectorData[OutIndex].X = InRawData[InIndex + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutVectorData[OutIndex].Y = InRawData[InIndex + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutVectorData[OutIndex].Z = InRawData[InIndex + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } -} - -void -FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts) -{ - OutVectorData.SetNum(CurveCounts.Num()); - - int32 TotalNumPoints = 0; - for (const int32 & NextCount : CurveCounts) - TotalNumPoints += NextCount; - - // Do not fill the output array, if the total number of points does not match - if (InRawData.Num() < TotalNumPoints * 3) - return; - - - int32 Itr = 0; - - for (int32 n = 0; n < CurveCounts.Num(); ++n) - { - TArray & NextVectorDataArray = OutVectorData[n]; - NextVectorDataArray.SetNumZeroed(CurveCounts[n]); - - for (int32 PtIdx = 0; PtIdx < CurveCounts[n]; ++PtIdx) - { - if (Itr + 2 >= InRawData.Num()) - return; - - NextVectorDataArray[PtIdx].X = InRawData[Itr] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - NextVectorDataArray[PtIdx].Y = InRawData[Itr + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - NextVectorDataArray[PtIdx].Z = InRawData[Itr + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - - Itr += 3; - } - } -} -void -FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC) -{ - for (UHoudiniInput * NextInput : HAC->Inputs) - UpdateHoudiniInputCurves(NextInput); -} - -void -FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniInput* Input) -{ - if (!Input || Input->GetInputType() != EHoudiniInputType::Curve) - return; - - TArray *InputObjectArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!InputObjectArray) - return; - - for (UHoudiniInputObject * NextInputObject : *InputObjectArray) - { - UHoudiniInputHoudiniSplineComponent * HoudiniSplineInput = Cast(NextInputObject); - if (!HoudiniSplineInput) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInput->GetCurveComponent(); - FHoudiniSplineTranslator::UpdateHoudiniCurve(HoudiniSplineComponent); - } -} - -bool -FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent* HoudiniSplineComponent) -{ - if (!IsValid(HoudiniSplineComponent)) - return false; - - int32 CurveNode_id = HoudiniSplineComponent->GetNodeId(); - if (CurveNode_id < 0) - return false; - - FString CurvePointsString = FString(); - if (!FHoudiniEngineUtils::HapiGetParameterDataAsString( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString)) - { - return false; - } - - int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; - if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue)) - { - return false; - } - HoudiniSplineComponent->SetCurveType((EHoudiniCurveType)CurveTypeValue); - - int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; - if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue)) - { - return false; - } - HoudiniSplineComponent->SetCurveMethod((EHoudiniCurveMethod)CurveMethodValue); - - int32 CurveClosed = 0; - if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) - { - return false; - } - HoudiniSplineComponent->SetClosedCurve(CurveClosed == 1); - - int32 CurveReversed = 0; - if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed)) - { - return false; - } - HoudiniSplineComponent->SetReversed(CurveReversed == 1); - - // We need to get the NodeInfo to get the parent id - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurveNode_id, &NodeInfo), false); - - TArray RefinedCurvePositions; - HAPI_AttributeInfo AttributeRefinedCurvePositions; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions)) - { - return false; - } - - // Process coords string and extract positions. - TArray CurvePoints; - FHoudiniSplineTranslator::ExtractStringPositions(CurvePointsString, CurvePoints); - - TArray CurveDisplayPoints; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurveDisplayPoints); - - // Build curve points for editable curves. - if (HoudiniSplineComponent->CurvePoints.Num() != CurvePoints.Num()) - { - HoudiniSplineComponent->CurvePoints.SetNum(CurvePoints.Num()); - for(int32 Idx = 0; Idx < CurvePoints.Num(); Idx++) - { - FTransform Transform = FTransform::Identity; - Transform.SetLocation(CurvePoints[Idx]); - HoudiniSplineComponent->CurvePoints[Idx] = Transform; - } - } - - // Update the display point on the curve - HoudiniSplineComponent->Construct(CurveDisplayPoints); - - HoudiniSplineComponent->MarkChanged(false); - - return true; -} - - -bool -FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( - UHoudiniSplineComponent* HoudiniSplineComponent, - bool bInAddRotAndScaleAttributes) -{ - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return true; - - TArray PositionArray; - TArray RotationArray; - TArray Scales3dArray; - for (FTransform& CurrentTransform : HoudiniSplineComponent->CurvePoints) - { - PositionArray.Add(CurrentTransform.GetLocation()); - if (bInAddRotAndScaleAttributes) - { - RotationArray.Add(CurrentTransform.GetRotation()); - Scales3dArray.Add(CurrentTransform.GetScale3D()); - } - } - - HAPI_NodeId CurveNode_id = HoudiniSplineComponent->GetNodeId(); - FTransform ParentTransform = HoudiniSplineComponent->GetComponentTransform(); - - FString InputNodeNameString = HoudiniSplineComponent->GetName(); - UHoudiniInputHoudiniSplineComponent* InputObject = Cast(HoudiniSplineComponent->GetOuter()); - if (InputObject) - { - UHoudiniInput* Input = Cast(InputObject->GetOuter()); - if (Input) - { - InputNodeNameString = Input->GetNodeBaseName(); - } - } - InputNodeNameString += TEXT("_curve"); - - bool Success = FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( - CurveNode_id, - InputNodeNameString, - &PositionArray, - bInAddRotAndScaleAttributes ? &RotationArray : nullptr, - bInAddRotAndScaleAttributes ? &Scales3dArray : nullptr, - HoudiniSplineComponent->GetCurveType(), - HoudiniSplineComponent->GetCurveMethod(), - HoudiniSplineComponent->IsClosedCurve(), - HoudiniSplineComponent->IsReversed(), - false, - ParentTransform); - - HoudiniSplineComponent->SetNodeId(CurveNode_id); - Success &= UpdateHoudiniCurve(HoudiniSplineComponent); - - return Success; -} - -bool -FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( - HAPI_NodeId& CurveNodeId, - const FString& InputNodeName, - TArray* Positions, - TArray* Rotations, - TArray* Scales3d, - EHoudiniCurveType InCurveType, - EHoudiniCurveMethod InCurveMethod, - const bool& InClosed, - const bool& InReversed, - const bool& InForceClose, - const FTransform& ParentTransform ) -{ -#if WITH_EDITOR - // Positions are required - if (!Positions) - return false; - - // We also need a valid host asset and 2 points to make a curve - int32 NumberOfCVs = Positions->Num(); - if (NumberOfCVs < 2) - return false; - - // Check if connected asset id is valid, if it is not, we need to create an input asset. - if (CurveNodeId < 0) - { - HAPI_NodeId NodeId = -1; - // Create the curve SOP Node - if (!FHoudiniSplineTranslator::HapiCreateCurveInputNode(NodeId, InputNodeName)) - return false; - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NodeId)) - return false; - - // We now have a valid id. - CurveNodeId = NodeId; - } - else - { - // We have to revert the Geo to its original state so we can use the Curve SOP: - // adding parameters to the Curve SOP locked it, preventing its parameters (type, method, isClosed) from working - FHoudiniApi::RevertGeo(FHoudiniEngine::Get().GetSession(), CurveNodeId); - } - - // - // In order to be able to add rotations and scale attributes to the curve SOP, we need to cook it twice: - // - // - First, we send the positions string to it, and cook it without refinement. - // this will allow us to get the proper curve CVs, part attributes and curve info to create the desired curve. - // - // - We then need to send back all the info extracted from the curve SOP to it, and add the rotation - // and scale attributes to it. This will lock the curve SOP, and prevent the curve type and method - // parameters from functioning properly (hence why we needed the first cook to set that up) - // - - // Set the curve type and curve method parameters for the curve node - int32 CurveTypeValue = (int32)InCurveType; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - - int32 CurveMethodValue = (int32)InCurveMethod; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - - int32 CurveClosed = InClosed ? 1 : 0; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - - int32 CurveReversed = InReversed ? 1 : 0; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); - - // Reading the curve parameters - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); - - if (InForceClose) - { - // We need to update the closed parameter - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); - - CurveClosed = 1; - } - - // For closed NURBS (CVs and Breakpoints), we have to close the curve manually, by duplicating its last point - // in order to be able to set the rotations and scales attributes properly. - bool bCloseCurveManually = false; - if (CurveClosed && (CurveTypeValue == HAPI_CURVETYPE_NURBS) && (CurveMethodValue != 2)) - { - // The curve is not closed anymore - if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 0)) - { - bCloseCurveManually = true; - - // Duplicating the first point to the end of the curve - // This needs to be done before sending the position string - FVector pos = (*Positions)[0]; - Positions->Add(pos); - - CurveClosed = false; - } - } - - // Creating the position string - FString PositionString = TEXT(""); - FHoudiniSplineTranslator::CreatePositionsString(*Positions, PositionString); - - // Get param id for the PositionString and modify it - HAPI_ParmId ParmId = -1; - if (FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId) != HAPI_RESULT_SUCCESS) - { - return false; - } - - std::string ConvertedString = TCHAR_TO_UTF8(*PositionString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - ConvertedString.c_str(), ParmId, 0), false); - - // If we don't want to add rotations or scale attributes to the curve, - // we can just cook the node normally and stop here. - bool bAddRotations = (Rotations != nullptr); - bool bAddScales3d = (Scales3d != nullptr); - if (!bAddRotations && !bAddScales3d) - { - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); - */ - - // Cook the node, no need to wait for completion - return FHoudiniEngineUtils::HapiCookNode(CurveNodeId, nullptr, false); - } - - // Setting up the first cook, without the curve refinement - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - CookOptions.maxVerticesPerPrimitive = -1; - CookOptions.refineCurveToLinear = false; - if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, true)) - return false; - - // We can now read back the Part infos from the cooked curve. - HAPI_PartInfo PartInfos; - FHoudiniApi::PartInfo_Init(&PartInfos); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurveNodeId, 0, &PartInfos), false); - - // - // Depending on the curve type and method, additionnal control points might have been created. - // We now have to interpolate the rotations and scale attributes for these. - // - - // Lambda function that interpolates rotation, scale and uniform scales values - // between two points using fCoeff as a weight, and insert the interpolated value at nInsertIndex - auto InterpolateRotScaleUScale = [&](const int32& nIndex1, const int32& nIndex2, const float& fCoeff, const int32& nInsertIndex) - { - if (Rotations && Rotations->IsValidIndex(nIndex1) && Rotations->IsValidIndex(nIndex2)) - { - FQuat interpolation = FQuat::Slerp((*Rotations)[nIndex1], (*Rotations)[nIndex2], fCoeff); - if (Rotations->IsValidIndex(nInsertIndex)) - Rotations->Insert(interpolation, nInsertIndex); - else - Rotations->Add(interpolation); - } - - if (Scales3d && Scales3d->IsValidIndex(nIndex1) && Scales3d->IsValidIndex(nIndex2)) - { - FVector interpolation = fCoeff * (*Scales3d)[nIndex1] + (1.0f - fCoeff) * (*Scales3d)[nIndex2]; - if (Scales3d->IsValidIndex(nInsertIndex)) - Scales3d->Insert(interpolation, nInsertIndex); - else - Scales3d->Add(interpolation); - } - }; - - // Lambda function that duplicates rotation and scale values - // at nIndex and insert/adds it at nInsertIndex - auto DuplicateRotScale = [&](const int32& nIndex, const int32& nInsertIndex) - { - if (Rotations && Rotations->IsValidIndex(nIndex)) - { - FQuat value = (*Rotations)[nIndex]; - if (Rotations->IsValidIndex(nInsertIndex)) - Rotations->Insert(value, nInsertIndex); - else - Rotations->Add(value); - } - - if (Scales3d && Scales3d->IsValidIndex(nIndex)) - { - FVector value = (*Scales3d)[nIndex]; - if (Scales3d->IsValidIndex(nInsertIndex)) - Scales3d->Insert(value, nInsertIndex); - else - Scales3d->Add(value); - } - }; - - // Do we want to close the curve by ourselves? - if (bCloseCurveManually) - { - // We need to duplicate the info of the first point to the last - DuplicateRotScale(0, NumberOfCVs++); - - // We need to update the closed parameter - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); - } - - // INTERPOLATION - if (CurveTypeValue == HAPI_CURVETYPE_NURBS) - { - // Closed NURBS have additional points reproducing the first ones - if (InClosed) - { - // Only the first one if the method is freehand ... - DuplicateRotScale(0, NumberOfCVs++); - - if (CurveMethodValue != 2) - { - // ... but also the 2nd and 3rd if the method is CVs or Breakpoints. - DuplicateRotScale(1, NumberOfCVs++); - DuplicateRotScale(2, NumberOfCVs++); - } - } - else if (CurveMethodValue == 1) - { - // Open NURBS have 2 new points if the method is breakpoint: - // One between the 1st and 2nd ... - InterpolateRotScaleUScale(0, 1, 0.5f, 1); - - // ... and one before the last one. - InterpolateRotScaleUScale(NumberOfCVs, NumberOfCVs - 1, 0.5f, NumberOfCVs); - NumberOfCVs += 2; - } - } - else if (CurveTypeValue == HAPI_CURVETYPE_BEZIER) - { - // Bezier curves requires additional point if the method is Breakpoints - if (CurveMethodValue == 1) - { - // 2 interpolated control points are added per points (except the last one) - int32 nOffset = 0; - for (int32 n = 0; n < NumberOfCVs - 1; n++) - { - int nIndex1 = n + nOffset; - int nIndex2 = n + nOffset + 1; - - InterpolateRotScaleUScale(nIndex1, nIndex2, 0.33f, nIndex2); - nIndex2++; - InterpolateRotScaleUScale(nIndex1, nIndex2, 0.66f, nIndex2); - - nOffset += 2; - } - NumberOfCVs += nOffset; - - if (CurveClosed) - { - // If the curve is closed, we need to add 2 points after the last, - // interpolated between the last and the first one - int nIndex = NumberOfCVs - 1; - InterpolateRotScaleUScale(nIndex, 0, 0.33f, NumberOfCVs++); - InterpolateRotScaleUScale(nIndex, 0, 0.66f, NumberOfCVs++); - - // and finally, the last point is the first.. - DuplicateRotScale(0, NumberOfCVs++); - } - } - else if (CurveClosed) - { - // For the other methods, if the bezier curve is closed, the last point is the 1st - DuplicateRotScale(0, NumberOfCVs++); - } - } - - // Even after interpolation, additional points might still be missing: - // Bezier curves require a certain number of points regarding their order, - // if points are lacking then HAPI duplicates the last one. - if (NumberOfCVs < PartInfos.pointCount) - { - int nToAdd = PartInfos.pointCount - NumberOfCVs; - for (int n = 0; n < nToAdd; n++) - { - DuplicateRotScale(NumberOfCVs - 1, NumberOfCVs); - NumberOfCVs++; - } - } - - // To avoid crashes, attributes will only be added if we now have the correct number of them - bAddRotations = bAddRotations && (Rotations->Num() == PartInfos.pointCount); - bAddScales3d = bAddScales3d && (Scales3d->Num() == PartInfos.pointCount); - - // We need to increase the point attributes count for points in the Part Infos - HAPI_AttributeOwner NewAttributesOwner = HAPI_ATTROWNER_POINT; - HAPI_AttributeOwner OriginalAttributesOwner = HAPI_ATTROWNER_POINT; - - int OriginalPointParametersCount = PartInfos.attributeCounts[NewAttributesOwner]; - if (bAddRotations) - PartInfos.attributeCounts[NewAttributesOwner] += 1; - if (bAddScales3d) - PartInfos.attributeCounts[NewAttributesOwner] += 1; - - // Sending the updated PartInfos - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, &PartInfos), false); - - // We need now to reproduce ALL the curves attributes for ALL the Owners.. - for (int nOwner = 0; nOwner < HAPI_ATTROWNER_MAX; nOwner++) - { - int nOwnerAttributeCount = nOwner == NewAttributesOwner ? OriginalPointParametersCount : PartInfos.attributeCounts[nOwner]; - if (nOwnerAttributeCount == 0) - continue; - - TArray AttributeNamesSH; - AttributeNamesSH.SetNum(nOwnerAttributeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, (HAPI_AttributeOwner)nOwner, - AttributeNamesSH.GetData(), AttributeNamesSH.Num()), false); - - for (int nAttribute = 0; nAttribute < AttributeNamesSH.Num(); nAttribute++) - { - const HAPI_StringHandle sh = AttributeNamesSH[nAttribute]; - if (sh == 0) - continue; - - // Get the attribute name - std::string attr_name; - FHoudiniEngineString::ToStdString(sh, attr_name); - if (strcmp(attr_name.c_str(), "__topology") == 0) - continue; - - // and the attribute infos - HAPI_AttributeInfo attr_info; - FHoudiniApi::AttributeInfo_Init(&attr_info); - //FMemory::Memzero< HAPI_AttributeInfo >( attr_info ); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, attr_name.c_str(), - (HAPI_AttributeOwner)nOwner, &attr_info), false); - - switch (attr_info.storage) - { - case HAPI_STORAGETYPE_INT: - { - // Storing IntData - TArray< int > IntData; - IntData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, -1, - IntData.GetData(), 0, attr_info.count), false); - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info, IntData.GetData(), - 0, attr_info.count), false); - } - break; - - case HAPI_STORAGETYPE_INT64: - { - // Storing IntData - TArray Int64Data; - Int64Data.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, -1, - Int64Data.GetData(), 0, attr_info.count), false); - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info, Int64Data.GetData(), - 0, attr_info.count), false); - } - break; - - case HAPI_STORAGETYPE_FLOAT: - { - // Storing Float Data - TArray< float > FloatData; - FloatData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, -1, - FloatData.GetData(), - 0, attr_info.count), false); - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - FloatData.GetData(), - 0, attr_info.count), false); - } - break; - - case HAPI_STORAGETYPE_STRING: - { - // Storing String Data - TArray StringHandleData; - StringHandleData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - StringHandleData.GetData(), - 0, attr_info.count), false); - - // Convert the SH to const char * - TArray StringData; - StringData.SetNumUninitialized(attr_info.count); - for (int n = 0; n < StringHandleData.Num(); n++) - { - // Converting the string - std::string strSTD; - FHoudiniEngineString::ToStdString(sh, strSTD); - - StringData[n] = strSTD.c_str(); - } - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - StringData.GetData(), - 0, attr_info.count), false); - } - break; - - default: - { - // Unhandled attribute type - warn - HOUDINI_LOG_WARNING( - TEXT("HapiCreateCurveInputNodeForData() - Unhandled attribute type - skipping") - TEXT("- consider disabling additionnal rot/scale if having issues with the HDA")); - continue; - } - - } - } - } - - // Only GET/SET curve infos if the part is a curve... - // (Closed linear curves are actually not considered as curves...) - if (PartInfos.type == HAPI_PARTTYPE_CURVE) - { - // We need to read the curve infos ... - HAPI_CurveInfo CurveInfo; - FHoudiniApi::CurveInfo_Init(&CurveInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - &CurveInfo), false); - - // ... the curve counts - TArray< int > CurveCounts; - CurveCounts.SetNumUninitialized(CurveInfo.curveCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveCounts.GetData(), - 0, CurveInfo.curveCount), false); - - // .. the curve orders - TArray< int > CurveOrders; - CurveOrders.SetNumUninitialized(CurveInfo.curveCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveOrders( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveOrders.GetData(), - 0, CurveInfo.curveCount), false); - - // .. And the Knots if they exist. - TArray< float > KnotsArray; - if (CurveInfo.hasKnots) - { - KnotsArray.SetNumUninitialized(CurveInfo.knotCount); - HOUDINI_CHECK_ERROR_RETURN( - FHoudiniApi::GetCurveKnots( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - KnotsArray.GetData(), - 0, CurveInfo.knotCount), false); - } - - // To set them back in HAPI - // CurveInfo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - &CurveInfo), false); - - // CurveCounts - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveCounts.GetData(), - 0, CurveInfo.curveCount), false); - - // CurveOrders - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveOrders( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveOrders.GetData(), - 0, CurveInfo.curveCount), false); - - // And Knots if they exist - if (CurveInfo.hasKnots) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveKnots( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - KnotsArray.GetData(), - 0, CurveInfo.knotCount), false); - } - } - - if (PartInfos.faceCount > 0) - { - // getting the face counts - TArray< int > FaceCounts; - FaceCounts.SetNumUninitialized(PartInfos.faceCount); - - if (FHoudiniApi::GetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - FaceCounts.GetData(), 0, - PartInfos.faceCount) == HAPI_RESULT_SUCCESS) - { - // Set the face count - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - FaceCounts.GetData(), - 0, PartInfos.faceCount), false); - } - } - - if (PartInfos.vertexCount > 0) - { - // the vertex list - TArray< int > VertexList; - VertexList.SetNumUninitialized(PartInfos.vertexCount); - - if (FHoudiniApi::GetVertexList( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - VertexList.GetData(), - 0, PartInfos.vertexCount) == HAPI_RESULT_SUCCESS) - { - // setting the vertex list - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - VertexList.GetData(), - 0, PartInfos.vertexCount), false); - } - } - - // We can add attributes to the curve now that all the curves attributes - // and properties have been reset. - if (bAddRotations) - { - // Create ROTATION attribute info - HAPI_AttributeInfo AttributeInfoRotation; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); - AttributeInfoRotation.count = NumberOfCVs; - AttributeInfoRotation.tupleSize = 4; - AttributeInfoRotation.exists = true; - AttributeInfoRotation.owner = NewAttributesOwner; - AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRotation.originalOwner = OriginalAttributesOwner; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_ROTATION, - &AttributeInfoRotation), false); - - // Convert the rotation infos - TArray< float > CurveRotations; - CurveRotations.SetNumZeroed(NumberOfCVs * 4); - for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) - { - // Get current quaternion - const FQuat& RotationQuaternion = (*Rotations)[Idx]; - - CurveRotations[Idx * 4 + 0] = RotationQuaternion.X; - CurveRotations[Idx * 4 + 1] = RotationQuaternion.Z; - CurveRotations[Idx * 4 + 2] = RotationQuaternion.Y; - CurveRotations[Idx * 4 + 3] = -RotationQuaternion.W; - } - - //we can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_ROTATION, - &AttributeInfoRotation, - CurveRotations.GetData(), - 0, AttributeInfoRotation.count), false); - } - - // Create SCALE attribute info. - if (bAddScales3d) - { - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = NumberOfCVs; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = NewAttributesOwner; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = OriginalAttributesOwner; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_SCALE, - &AttributeInfoScale), false); - - // Convert the scale - TArray< float > CurveScales; - CurveScales.SetNumZeroed(NumberOfCVs * 3); - for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) - { - // Get current scale - FVector ScaleVector = (*Scales3d)[Idx]; - CurveScales[Idx * 3 + 0] = ScaleVector.X; - CurveScales[Idx * 3 + 1] = ScaleVector.Z; - CurveScales[Idx * 3 + 2] = ScaleVector.Y; - } - - // We can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_SCALE, - &AttributeInfoScale, - CurveScales.GetData(), - 0, AttributeInfoScale.count), false); - } - - // Finally, commit the geo ... - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), CurveNodeId), false); - - // And cook it with refinement enabled - CookOptions.refineCurveToLinear = true; - if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, false)) - return false; -#endif - - return true; -} - -void -FHoudiniSplineTranslator::CreatePositionsString(const TArray& InPositions, FString& OutPositionString) -{ - OutPositionString = TEXT(""); - for (int32 Idx = 0; Idx < InPositions.Num(); ++Idx) - { - FVector Position = InPositions[Idx]; - // Convert to meters - Position /= HAPI_UNREAL_SCALE_FACTOR_POSITION; - // Swap Y/Z - OutPositionString += FString::Printf(TEXT("%f, %f, %f "), Position.X, Position.Z, Position.Y); - } -} - -bool -FHoudiniSplineTranslator::HapiCreateCurveInputNode(HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName) -{ - // Create the curve SOP Node - HAPI_NodeId NewNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/curve"), InputNodeName, false, &NewNodeId), false); - - OutCurveNodeId = NewNodeId; - - // Submit default points to curve. - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), NewNodeId, - HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), NewNodeId, - HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT, ParmId, 0), false); - - // Cook the newly created node - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); - */ - - return FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true); -} - -UHoudiniSplineComponent* -FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent) -{ - if (GeoId < 0) - return nullptr; - - if (!OuterComponent || OuterComponent->IsPendingKill()) - return nullptr; - - USceneComponent* const SceneComponent = Cast(OuterComponent); - if (!IsValid(SceneComponent)) - return nullptr; - - // Create a HoudiniSplineComponent for the editable curve. - UHoudiniSplineComponent* HoudiniSplineComponent = NewObject( - OuterComponent, - UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Public | RF_Transactional); - - HoudiniSplineComponent->SetNodeId(GeoId); - HoudiniSplineComponent->SetGeoPartName(PartName); - - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(SceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - - // Delete the curve points so that UpdateHoudiniCurves initializes them from HAPI - HoudiniSplineComponent->CurvePoints.Empty(); - HoudiniSplineComponent->DisplayPoints.Empty(); - UpdateHoudiniCurve(HoudiniSplineComponent); - - ReselectSelectedActors(); - - return HoudiniSplineComponent; - -} - -UHoudiniSplineComponent* -FHoudiniSplineTranslator::CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC) -{ - if (!OuterHAC || OuterHAC->IsPendingKill()) - return nullptr; - - UObject* Outer = nullptr; - if (OuterHAC && !OuterHAC->IsPendingKill()) - Outer = OuterHAC->GetOwner() ? OuterHAC->GetOwner() : OuterHAC->GetOuter(); - - UHoudiniSplineComponent *NewHoudiniSplineComponent = NewObject(Outer, UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!NewHoudiniSplineComponent) - return nullptr; - - NewHoudiniSplineComponent->Construct(CurvePoints); - - bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); - bool bHasScales = CurveScales.Num() == CurvePoints.Num(); - - TArray Transforms; - for (int32 n = 0; n < CurvePoints.Num(); ++n) - { - FTransform NextTransform = FTransform::Identity; - NextTransform.SetLocation(CurvePoints[n]); - - if (bHasRotations) - NextTransform.SetRotation(CurveRotations[n].Rotation().Quaternion()); - - if (bHasScales) - NextTransform.SetScale3D(CurveScales[n]); - - Transforms.Add(NextTransform); - } - - NewHoudiniSplineComponent->CurveType = EHoudiniCurveType::Polygon; - NewHoudiniSplineComponent->bIsOutputCurve = true; - - NewHoudiniSplineComponent->AttachToComponent(OuterHAC, FAttachmentTransformRules::KeepRelativeTransform); - NewHoudiniSplineComponent->RegisterComponent(); - - ReselectSelectedActors(); - - return NewHoudiniSplineComponent; -} - -USplineComponent* -FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, - UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed) -{ - if (!OuterComponent || OuterComponent->IsPendingKill()) - return nullptr; - - USceneComponent* OuterSceneComponent = Cast(OuterComponent); - if (!IsValid(OuterSceneComponent)) - return nullptr; - - UObject* Outer = nullptr; - Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); - - USplineComponent* NewSplineComponent = NewObject(Outer, USplineComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!NewSplineComponent) - return nullptr; - - // Clear default USplineComponent's points - NewSplineComponent->ClearSplinePoints(); - NewSplineComponent->bEditableWhenInherited = false; - - //bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); - //bool bHasScales = CurveScales.Num() == CurvePoints.Num(); - - for (int32 n = 0; n < CurvePoints.Num(); ++n) - { - NewSplineComponent->AddSplinePoint(CurvePoints[n], ESplineCoordinateSpace::Local); - - //FSplinePoint NewSplinePoint; - //NewSplinePoint.Position = CurvePoints[n]; - //if (bHasRotations) - // NewSplinePoint.Rotation = CurveRotations[n].Rotation(); - - //if (bHasScales) - // NewSplinePoint.Scale = CurveScales[n]; - //NewSplineComponent->AddPoint(NewSplinePoint, false); - } - - if (bIsLinear) - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - else - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); - } - - - NewSplineComponent->SetClosedLoop(bIsClosed); - - /* - NewSplineComponent->SetClosedLoop(bClosed); - - if (Type == int32(EHoudiniCurveType::Linear)) - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - else - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); - } - */ - - NewSplineComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - NewSplineComponent->RegisterComponent(); - AActor *OwnerActor = Cast(Outer); - if (IsValid(OwnerActor)) - OwnerActor->AddInstanceComponent(NewSplineComponent); - - ReselectSelectedActors(); - - return NewSplineComponent; -} - -bool -FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed) -{ - if (!EditedSplineComponent || EditedSplineComponent->IsPendingKill()) - return false; - - if (CurvePoints.Num() < 2) - return false; - - int MinCount = FMath::Min(CurvePoints.Num(), EditedSplineComponent->GetNumberOfSplinePoints()); - - for (int32 Idx = EditedSplineComponent->GetNumberOfSplinePoints() - 1; Idx >= 0; --Idx) - { - EditedSplineComponent->RemoveSplinePoint(Idx, false); - } - - for (int32 Idx = 0; Idx < CurvePoints.Num(); ++Idx) - { - EditedSplineComponent->AddSplinePoint(CurvePoints[Idx], ESplineCoordinateSpace::Local, false); - - if (CurveType == EHoudiniCurveType::Polygon) - EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Linear, false); - else - EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Curve, false); - } - - EditedSplineComponent->SetClosedLoop(bClosed, true); - - return true; -} - -bool -FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent) -{ - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - if (CurvePoints.Num() < 2) - return false; - - int MinCount = FMath::Min(CurvePoints.Num(), EditedHoudiniSplineComponent->CurvePoints.Num()); - - int Idx = 0; - // modify existing points - for (; Idx < MinCount; ++Idx) - { - FTransform CurTrans = EditedHoudiniSplineComponent->CurvePoints[Idx]; - if (CurTrans.GetLocation() == CurvePoints[Idx]) - continue; - - CurTrans.SetLocation(CurvePoints[Idx]); - } - - // remove extra points - if (Idx < EditedHoudiniSplineComponent->CurvePoints.Num()-1) - { - for (int32 n = EditedHoudiniSplineComponent->CurvePoints.Num() - 1; n >= Idx; --n) - { - EditedHoudiniSplineComponent->RemovePointAtIndex(n); - } - } - - - // append extra points - for (; Idx < CurvePoints.Num(); ++Idx) - { - FTransform NewPoint = FTransform::Identity; - NewPoint.SetLocation(CurvePoints[Idx]); - EditedHoudiniSplineComponent->CurvePoints.Add(NewPoint); - } - - return true; -} - - -bool -FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - UObject* InOuterComponent, - TMap& InSplines, - TMap& OutSplines, - const bool& InForceRebuild, - const EHoudiniCurveOutputType& OutputCurveType, - const bool& bIsLinear, - const bool& bIsClosed) -{ - // If we're not forcing the rebuild - // No need to recreate something that hasn't changed - if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged)) - { - // Simply reuse the existing meshes - OutSplines = InSplines; - return true; - } - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - int32 CurveNodeId = InHGPO.GeoId; - int32 CurvePartId = InHGPO.PartId; - if (CurveNodeId < 0 || CurvePartId < 0) - return false; - - // Extract all curve points from this HGPO - TArray RefinedCurvePositions; - HAPI_AttributeInfo AttributeRefinedCurvePositions; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); - - TArray RefinedCurveRotations; - HAPI_AttributeInfo AttributeRefinedCurveRotations; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveRotations); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_ROTATION, AttributeRefinedCurveRotations, RefinedCurveRotations); - - TArray RefinedCurveScales; - HAPI_AttributeInfo AttributeRefinedCurveScales; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveScales); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_SCALE, AttributeRefinedCurveScales, RefinedCurveScales); - - HAPI_CurveInfo CurveInfo; - FHoudiniApi::CurveInfo_Init(&CurveInfo); - FHoudiniApi::GetCurveInfo(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, &CurveInfo); - - int32 NumOfCurves = CurveInfo.curveCount; - TArray CurvePointsCounts; - CurvePointsCounts.SetNumZeroed(NumOfCurves); - FHoudiniApi::GetCurveCounts(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, CurvePointsCounts.GetData(), 0, NumOfCurves); - - TArray> CurvesDisplayPoints; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurvesDisplayPoints, CurvePointsCounts); - - TArray> CurvesRotations; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveRotations, CurvesRotations, CurvePointsCounts); - - TArray> CurvesScales; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveScales, CurvesScales, CurvePointsCounts); - - // Extract all curve points from this HGPO - FString GeoName = InHGPO.PartName; - int32 CurveIdx = 1; - - // Iterate through all curves found in this HGPO - for (int32 n = 0; n < CurvesDisplayPoints.Num(); ++n) - { - FString CurveName = FString::Printf(TEXT("%s curve %d"), *GeoName, CurveIdx); - CurveIdx += 1; - - if (CurvePointsCounts[n] < 2) - { - // Invalid vertex count, skip this curve. - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d] invalid vertex count.") - TEXT("- skipping."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx); - continue; - } - - FHoudiniOutputObjectIdentifier CurveIdentifier(InHGPO.ObjectId, InHGPO.GeoId, InHGPO.PartId, CurveName); - FHoudiniOutputObject* FoundOutputObject = InSplines.Find(CurveIdentifier); - - bool bNeedToRebuildSpline = false; - if (!FoundOutputObject) - bNeedToRebuildSpline = true; - - USceneComponent* FoundComponent = Cast(FoundOutputObject ? FoundOutputObject->OutputComponent : nullptr); - if (FoundComponent && !FoundComponent->IsPendingKill()) - { - // Only support output to Unreal Spline for now... - //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::UnrealSpline) - // bNeedToRebuildSpline = true; - - //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) - // bNeedToRebuildSpline = true; - - if (InHGPO.bHasGeoChanged || InHGPO.PartInfo.bHasChanged || InForceRebuild) - bNeedToRebuildSpline = true; - } - else - { - bNeedToRebuildSpline = true; - } - - // The curve has not changed, no need to go through the rest - if (!bNeedToRebuildSpline) - { - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - continue; - } - - bool bReusedPreviousOutput = false; - if (!FoundOutputObject) - { - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline (default): Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - // If not found (at initialize), create an Unreal spline - // We only support unreal spline for now.. - // May support Houdini spline too later - USplineComponent* CreatedSplineComponent = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); - if (!CreatedSplineComponent) - continue; - - // Create a new output object - FHoudiniOutputObject NewOutputObject; - NewOutputObject.OutputComponent = CreatedSplineComponent; - - NewOutputObject.CurveOutputProperty.CurveOutputType = OutputCurveType; - NewOutputObject.CurveOutputProperty.NumPoints = CurvePointsCounts[n]; - - // TODO: Need a way to access info of the output curve - NewOutputObject.CurveOutputProperty.CurveMethod = EHoudiniCurveMethod::Breakpoints; - NewOutputObject.CurveOutputProperty.CurveType = bIsLinear ? EHoudiniCurveType::Polygon : EHoudiniCurveType::Bezier; - NewOutputObject.CurveOutputProperty.bClosed = false; - // Fill in the rest of output curve properties - - OutSplines.Add(CurveIdentifier, NewOutputObject); - - // Update FOundOutputObject so we can cache attributes after - FoundOutputObject = OutSplines.Find(CurveIdentifier); - } - else - { - // - if (FoundOutputObject->CurveOutputProperty.CurveOutputType == EHoudiniCurveOutputType::UnrealSpline) - { - // See if we can simply update the previous Spline Component - bool bCanUpdateUnrealSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); - if (bCanUpdateUnrealSpline) - { - // Update the existing unreal spline component - bReusedPreviousOutput = true; - HOUDINI_LOG_WARNING( - TEXT("Updating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - USplineComponent* FoundUnrealSpline = Cast(FoundOutputObject->OutputComponent); - if (!FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(CurvesDisplayPoints[n], FoundUnrealSpline, FoundOutputObject->CurveOutputProperty.CurveType, FoundOutputObject->CurveOutputProperty.bClosed)) - continue; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - else - { - // Create a new Unreal spline component - // We support unreal spline only for now... - bReusedPreviousOutput = false; - FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::UnrealSpline; - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - USplineComponent* NewUnrealSpline = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); - if (!NewUnrealSpline) - continue; - - FoundOutputObject->OutputComponent = NewUnrealSpline; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - } - // We current support Unreal Spline output only... - /* - else - { - // We want to output a Houdini Spline Component - // See if we can simply update the previous Houdini Spline Component - bool bCanUpdateHoudiniSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); - if (bCanUpdateHoudiniSpline) - { - // Update the existing houdini spline component - bReusedPreviousOutput = true; - HOUDINI_LOG_WARNING( - TEXT("Changing Houdini Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - UHoudiniSplineComponent* FoundHoudiniSpline = Cast(FoundOutputObject->OutputComponent); - if (!FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], FoundHoudiniSpline)) - continue; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - else - { - // Create a new Houdini spline component - bReusedPreviousOutput = false; - FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - UHoudiniSplineComponent* NewHoudiniSpline = CreateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuter); - if (!NewHoudiniSpline) - continue; - - FoundOutputObject->OutputComponent = NewHoudiniSpline; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - } - */ - } - - // Cache commonly supported Houdini attributes on the OutputAttributes - TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - { - // cache the level path attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); - } - } - - TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - // cache the output name attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); - } - } - - TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, BakeOutputActorNames)) - { - if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); - } - } - - TArray BakeFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(InHGPO.GeoId, BakeFolders, InHGPO.PartId)) - { - if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) - { - // cache the unreal_bake_folder attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); - } - } - - TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, BakeOutlinerFolders)) - { - if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); - } - } - - // Update generic properties attributes on the spline component - TArray GenericAttributes; - if (FoundOutputObject && FHoudiniEngineUtils::GetGenericPropertiesAttributes( - InHGPO.GeoId, InHGPO.PartId, true, 0, 0, 0, GenericAttributes)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(FoundOutputObject->OutputComponent, GenericAttributes); - } - - if (bReusedPreviousOutput) - { - // Remove the reused output unreal spline from the old map to avoid its deletion - InSplines.Remove(CurveIdentifier); - } - - HOUDINI_LOG_WARNING( - TEXT("Finished Generating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - } - - return true; -} - - -bool -FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - // ONLY DO THIS ON CURVES!!!! - if (InOutput->GetType() != EHoudiniOutputType::Curve) - return false; - - // UHoudiniAssetComponent* OuterHAC = Cast(InOuterComponent); - // - // if (!OuterHAC || OuterHAC->IsPendingKill()) - // return false; - - TMap NewOutputObjects; - TMap& OldOutputObjects = InOutput->GetOutputObjects(); - - // Iterate on all the output's HGPO - for (const FHoudiniGeoPartObject & CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - // not a curve, skip - if (CurHGPO.Type != EHoudiniPartType::Curve) - continue; - - // Check if we want to create a houdini output curve from corresponding attribute - HAPI_AttributeInfo CurveOutputAttriInfo; - FHoudiniApi::AttributeInfo_Init(&CurveOutputAttriInfo); - TArray IntData; - IntData.Empty(); - - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE, CurveOutputAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - continue; - - if (IntData.Num() <= 0) - continue; - else - { - if (IntData[0] == 0) - continue; - } - - HAPI_AttributeInfo LinearAttriInfo; - FHoudiniApi::AttributeInfo_Init(&LinearAttriInfo); - IntData.Empty(); - - bool bIsLinear = false; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR, LinearAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - { - if (IntData.Num() > 0) - bIsLinear = IntData[0] == 1; - } - - HAPI_AttributeInfo ClosedAttriInfo; - FHoudiniApi::AttributeInfo_Init(&ClosedAttriInfo); - IntData.Empty(); - - bool bIsClosed = false; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED, ClosedAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - { - if (IntData.Num() > 0) - bIsClosed = IntData[0] == 1; - } - - // We output curve to Unreal Spline only for now - // May support output to Houdini Spline later - CreateOutputSplinesFromHoudiniGeoPartObject(CurHGPO, InOuterComponent, OldOutputObjects, NewOutputObjects, false, EHoudiniCurveOutputType::UnrealSpline, bIsLinear, bIsClosed); - } - - // TODO: FIX ME!!! This literally nukes all the output objects, even if they are not curves! - - // The old map now only contains unused/stale output curves destroy them - for (auto& OldPair : OldOutputObjects) - { - USceneComponent* OldSplineSceneComponent = Cast(OldPair.Value.OutputComponent); - - if (!OldSplineSceneComponent || OldSplineSceneComponent->IsPendingKill()) - continue; - - // The output object is supposed to be a spline - if (!OldSplineSceneComponent->IsA() && !OldSplineSceneComponent->IsA()) - continue; - - OldSplineSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - OldSplineSceneComponent->UnregisterComponent(); - OldSplineSceneComponent->DestroyComponent(); - } - OldOutputObjects.Empty(); - - InOutput->SetOutputObjects(NewOutputObjects); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - - return true; -} - -void -FHoudiniSplineTranslator::ReselectSelectedActors() -{ - // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? - USelection* Selection = GEditor->GetSelectedActors(); - TArray SelectedActors; - SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); - Selection->GetSelectedObjects(SelectedActors); - - GEditor->SelectNone(false, false, false); - - for (AActor* NextSelected : SelectedActors) - { - GEditor->SelectActor(NextSelected, true, true, true, true); - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniGeoPartObject.h" +#include "Components/SplineComponent.h" + +#include "EditorViewportClient.h" +#include "Engine/Selection.h" + +#include "HoudiniEnginePrivatePCH.h" + +void +FHoudiniSplineTranslator::ExtractStringPositions(const FString& Positions, TArray& OutPositions) +{ + TArray< FString > PointStrings; + static const TCHAR * PositionSeparators[] = + { + TEXT(" "), + TEXT(","), + }; + + int32 NumCoords = Positions.ParseIntoArray(PointStrings, PositionSeparators, 2); + OutPositions.SetNum(NumCoords / 3); + for (int32 OutIndex = 0; OutIndex < OutPositions.Num(); OutIndex++) + { + const int32& CoordIndex = OutIndex * 3; + OutPositions[OutIndex].X = FCString::Atof(*(PointStrings[CoordIndex + 0])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutPositions[OutIndex].Y = FCString::Atof(*(PointStrings[CoordIndex + 2])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutPositions[OutIndex].Z = FCString::Atof(*(PointStrings[CoordIndex + 1])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } +} + +void +FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData) +{ + OutVectorData.SetNum(InRawData.Num() / 3); + + for (int32 OutIndex = 0; OutIndex < OutVectorData.Num(); OutIndex++) + { + const int32& InIndex = OutIndex * 3; + OutVectorData[OutIndex].X = InRawData[InIndex + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutVectorData[OutIndex].Y = InRawData[InIndex + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutVectorData[OutIndex].Z = InRawData[InIndex + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } +} + +void +FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts) +{ + OutVectorData.SetNum(CurveCounts.Num()); + + int32 TotalNumPoints = 0; + for (const int32 & NextCount : CurveCounts) + TotalNumPoints += NextCount; + + // Do not fill the output array, if the total number of points does not match + if (InRawData.Num() < TotalNumPoints * 3) + return; + + + int32 Itr = 0; + + for (int32 n = 0; n < CurveCounts.Num(); ++n) + { + TArray & NextVectorDataArray = OutVectorData[n]; + NextVectorDataArray.SetNumZeroed(CurveCounts[n]); + + for (int32 PtIdx = 0; PtIdx < CurveCounts[n]; ++PtIdx) + { + if (Itr + 2 >= InRawData.Num()) + return; + + NextVectorDataArray[PtIdx].X = InRawData[Itr] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + NextVectorDataArray[PtIdx].Y = InRawData[Itr + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + NextVectorDataArray[PtIdx].Z = InRawData[Itr + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + + Itr += 3; + } + } +} +void +FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC) +{ + for (UHoudiniInput * NextInput : HAC->Inputs) + UpdateHoudiniInputCurves(NextInput); +} + +void +FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniInput* Input) +{ + if (!Input || Input->GetInputType() != EHoudiniInputType::Curve) + return; + + TArray *InputObjectArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!InputObjectArray) + return; + + for (UHoudiniInputObject * NextInputObject : *InputObjectArray) + { + UHoudiniInputHoudiniSplineComponent * HoudiniSplineInput = Cast(NextInputObject); + if (!HoudiniSplineInput) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInput->GetCurveComponent(); + FHoudiniSplineTranslator::UpdateHoudiniCurve(HoudiniSplineComponent); + } +} + +bool +FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent* HoudiniSplineComponent) +{ + if (!IsValid(HoudiniSplineComponent)) + return false; + + int32 CurveNode_id = HoudiniSplineComponent->GetNodeId(); + if (CurveNode_id < 0) + return false; + + FString CurvePointsString = FString(); + if (!FHoudiniEngineUtils::HapiGetParameterDataAsString( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString)) + { + return false; + } + + int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue)) + { + return false; + } + HoudiniSplineComponent->SetCurveType((EHoudiniCurveType)CurveTypeValue); + + int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue)) + { + return false; + } + HoudiniSplineComponent->SetCurveMethod((EHoudiniCurveMethod)CurveMethodValue); + + int32 CurveClosed = 0; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) + { + return false; + } + HoudiniSplineComponent->SetClosedCurve(CurveClosed == 1); + + int32 CurveReversed = 0; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed)) + { + return false; + } + HoudiniSplineComponent->SetReversed(CurveReversed == 1); + + // We need to get the NodeInfo to get the parent id + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurveNode_id, &NodeInfo), false); + + TArray RefinedCurvePositions; + HAPI_AttributeInfo AttributeRefinedCurvePositions; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions)) + { + return false; + } + + // Process coords string and extract positions. + TArray CurvePoints; + FHoudiniSplineTranslator::ExtractStringPositions(CurvePointsString, CurvePoints); + + TArray CurveDisplayPoints; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurveDisplayPoints); + + // Build curve points for editable curves. + if (HoudiniSplineComponent->CurvePoints.Num() != CurvePoints.Num()) + { + HoudiniSplineComponent->CurvePoints.SetNum(CurvePoints.Num()); + for(int32 Idx = 0; Idx < CurvePoints.Num(); Idx++) + { + FTransform Transform = FTransform::Identity; + Transform.SetLocation(CurvePoints[Idx]); + HoudiniSplineComponent->CurvePoints[Idx] = Transform; + } + } + + // Update the display point on the curve + HoudiniSplineComponent->Construct(CurveDisplayPoints); + + HoudiniSplineComponent->MarkChanged(false); + + return true; +} + + +bool +FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + UHoudiniSplineComponent* HoudiniSplineComponent, + bool bInAddRotAndScaleAttributes) +{ + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return true; + + TArray PositionArray; + TArray RotationArray; + TArray Scales3dArray; + for (FTransform& CurrentTransform : HoudiniSplineComponent->CurvePoints) + { + PositionArray.Add(CurrentTransform.GetLocation()); + if (bInAddRotAndScaleAttributes) + { + RotationArray.Add(CurrentTransform.GetRotation()); + Scales3dArray.Add(CurrentTransform.GetScale3D()); + } + } + + HAPI_NodeId CurveNode_id = HoudiniSplineComponent->GetNodeId(); + FTransform ParentTransform = HoudiniSplineComponent->GetComponentTransform(); + + FString InputNodeNameString = HoudiniSplineComponent->GetName(); + UHoudiniInputHoudiniSplineComponent* InputObject = Cast(HoudiniSplineComponent->GetOuter()); + if (InputObject) + { + UHoudiniInput* Input = Cast(InputObject->GetOuter()); + if (Input) + { + InputNodeNameString = Input->GetNodeBaseName(); + } + } + InputNodeNameString += TEXT("_curve"); + + bool Success = FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( + CurveNode_id, + InputNodeNameString, + &PositionArray, + bInAddRotAndScaleAttributes ? &RotationArray : nullptr, + bInAddRotAndScaleAttributes ? &Scales3dArray : nullptr, + HoudiniSplineComponent->GetCurveType(), + HoudiniSplineComponent->GetCurveMethod(), + HoudiniSplineComponent->IsClosedCurve(), + HoudiniSplineComponent->IsReversed(), + false, + ParentTransform); + + HoudiniSplineComponent->SetNodeId(CurveNode_id); + Success &= UpdateHoudiniCurve(HoudiniSplineComponent); + + return Success; +} + +bool +FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( + HAPI_NodeId& CurveNodeId, + const FString& InputNodeName, + TArray* Positions, + TArray* Rotations, + TArray* Scales3d, + EHoudiniCurveType InCurveType, + EHoudiniCurveMethod InCurveMethod, + const bool& InClosed, + const bool& InReversed, + const bool& InForceClose, + const FTransform& ParentTransform ) +{ +#if WITH_EDITOR + // Positions are required + if (!Positions) + return false; + + // We also need a valid host asset and 2 points to make a curve + int32 NumberOfCVs = Positions->Num(); + if (NumberOfCVs < 2) + return false; + + // Check if connected asset id is valid, if it is not, we need to create an input asset. + if (CurveNodeId < 0) + { + HAPI_NodeId NodeId = -1; + // Create the curve SOP Node + if (!FHoudiniSplineTranslator::HapiCreateCurveInputNode(NodeId, InputNodeName)) + return false; + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NodeId)) + return false; + + // We now have a valid id. + CurveNodeId = NodeId; + } + else + { + // We have to revert the Geo to its original state so we can use the Curve SOP: + // adding parameters to the Curve SOP locked it, preventing its parameters (type, method, isClosed) from working + FHoudiniApi::RevertGeo(FHoudiniEngine::Get().GetSession(), CurveNodeId); + } + + // + // In order to be able to add rotations and scale attributes to the curve SOP, we need to cook it twice: + // + // - First, we send the positions string to it, and cook it without refinement. + // this will allow us to get the proper curve CVs, part attributes and curve info to create the desired curve. + // + // - We then need to send back all the info extracted from the curve SOP to it, and add the rotation + // and scale attributes to it. This will lock the curve SOP, and prevent the curve type and method + // parameters from functioning properly (hence why we needed the first cook to set that up) + // + + // Set the curve type and curve method parameters for the curve node + int32 CurveTypeValue = (int32)InCurveType; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); + + int32 CurveMethodValue = (int32)InCurveMethod; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); + + int32 CurveClosed = InClosed ? 1 : 0; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); + + int32 CurveReversed = InReversed ? 1 : 0; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + + // Reading the curve parameters + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + + if (InForceClose) + { + // We need to update the closed parameter + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); + + CurveClosed = 1; + } + + // For closed NURBS (CVs and Breakpoints), we have to close the curve manually, by duplicating its last point + // in order to be able to set the rotations and scales attributes properly. + bool bCloseCurveManually = false; + if (CurveClosed && (CurveTypeValue == HAPI_CURVETYPE_NURBS) && (CurveMethodValue != 2)) + { + // The curve is not closed anymore + if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 0)) + { + bCloseCurveManually = true; + + // Duplicating the first point to the end of the curve + // This needs to be done before sending the position string + FVector pos = (*Positions)[0]; + Positions->Add(pos); + + CurveClosed = false; + } + } + + // Creating the position string + FString PositionString = TEXT(""); + FHoudiniSplineTranslator::CreatePositionsString(*Positions, PositionString); + + // Get param id for the PositionString and modify it + HAPI_ParmId ParmId = -1; + if (FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId) != HAPI_RESULT_SUCCESS) + { + return false; + } + + std::string ConvertedString = TCHAR_TO_UTF8(*PositionString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + ConvertedString.c_str(), ParmId, 0), false); + + // If we don't want to add rotations or scale attributes to the curve, + // we can just cook the node normally and stop here. + bool bAddRotations = (Rotations != nullptr); + bool bAddScales3d = (Scales3d != nullptr); + if (!bAddRotations && !bAddScales3d) + { + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); + */ + + // Cook the node, no need to wait for completion + return FHoudiniEngineUtils::HapiCookNode(CurveNodeId, nullptr, false); + } + + // Setting up the first cook, without the curve refinement + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + CookOptions.maxVerticesPerPrimitive = -1; + CookOptions.refineCurveToLinear = false; + if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, true)) + return false; + + // We can now read back the Part infos from the cooked curve. + HAPI_PartInfo PartInfos; + FHoudiniApi::PartInfo_Init(&PartInfos); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurveNodeId, 0, &PartInfos), false); + + // + // Depending on the curve type and method, additionnal control points might have been created. + // We now have to interpolate the rotations and scale attributes for these. + // + + // Lambda function that interpolates rotation, scale and uniform scales values + // between two points using fCoeff as a weight, and insert the interpolated value at nInsertIndex + auto InterpolateRotScaleUScale = [&](const int32& nIndex1, const int32& nIndex2, const float& fCoeff, const int32& nInsertIndex) + { + if (Rotations && Rotations->IsValidIndex(nIndex1) && Rotations->IsValidIndex(nIndex2)) + { + FQuat interpolation = FQuat::Slerp((*Rotations)[nIndex1], (*Rotations)[nIndex2], fCoeff); + if (Rotations->IsValidIndex(nInsertIndex)) + Rotations->Insert(interpolation, nInsertIndex); + else + Rotations->Add(interpolation); + } + + if (Scales3d && Scales3d->IsValidIndex(nIndex1) && Scales3d->IsValidIndex(nIndex2)) + { + FVector interpolation = fCoeff * (*Scales3d)[nIndex1] + (1.0f - fCoeff) * (*Scales3d)[nIndex2]; + if (Scales3d->IsValidIndex(nInsertIndex)) + Scales3d->Insert(interpolation, nInsertIndex); + else + Scales3d->Add(interpolation); + } + }; + + // Lambda function that duplicates rotation and scale values + // at nIndex and insert/adds it at nInsertIndex + auto DuplicateRotScale = [&](const int32& nIndex, const int32& nInsertIndex) + { + if (Rotations && Rotations->IsValidIndex(nIndex)) + { + FQuat value = (*Rotations)[nIndex]; + if (Rotations->IsValidIndex(nInsertIndex)) + Rotations->Insert(value, nInsertIndex); + else + Rotations->Add(value); + } + + if (Scales3d && Scales3d->IsValidIndex(nIndex)) + { + FVector value = (*Scales3d)[nIndex]; + if (Scales3d->IsValidIndex(nInsertIndex)) + Scales3d->Insert(value, nInsertIndex); + else + Scales3d->Add(value); + } + }; + + // Do we want to close the curve by ourselves? + if (bCloseCurveManually) + { + // We need to duplicate the info of the first point to the last + DuplicateRotScale(0, NumberOfCVs++); + + // We need to update the closed parameter + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); + } + + // INTERPOLATION + if (CurveTypeValue == HAPI_CURVETYPE_NURBS) + { + // Closed NURBS have additional points reproducing the first ones + if (InClosed) + { + // Only the first one if the method is freehand ... + DuplicateRotScale(0, NumberOfCVs++); + + if (CurveMethodValue != 2) + { + // ... but also the 2nd and 3rd if the method is CVs or Breakpoints. + DuplicateRotScale(1, NumberOfCVs++); + DuplicateRotScale(2, NumberOfCVs++); + } + } + else if (CurveMethodValue == 1) + { + // Open NURBS have 2 new points if the method is breakpoint: + // One between the 1st and 2nd ... + InterpolateRotScaleUScale(0, 1, 0.5f, 1); + + // ... and one before the last one. + InterpolateRotScaleUScale(NumberOfCVs, NumberOfCVs - 1, 0.5f, NumberOfCVs); + NumberOfCVs += 2; + } + } + else if (CurveTypeValue == HAPI_CURVETYPE_BEZIER) + { + // Bezier curves requires additional point if the method is Breakpoints + if (CurveMethodValue == 1) + { + // 2 interpolated control points are added per points (except the last one) + int32 nOffset = 0; + for (int32 n = 0; n < NumberOfCVs - 1; n++) + { + int nIndex1 = n + nOffset; + int nIndex2 = n + nOffset + 1; + + InterpolateRotScaleUScale(nIndex1, nIndex2, 0.33f, nIndex2); + nIndex2++; + InterpolateRotScaleUScale(nIndex1, nIndex2, 0.66f, nIndex2); + + nOffset += 2; + } + NumberOfCVs += nOffset; + + if (CurveClosed) + { + // If the curve is closed, we need to add 2 points after the last, + // interpolated between the last and the first one + int nIndex = NumberOfCVs - 1; + InterpolateRotScaleUScale(nIndex, 0, 0.33f, NumberOfCVs++); + InterpolateRotScaleUScale(nIndex, 0, 0.66f, NumberOfCVs++); + + // and finally, the last point is the first.. + DuplicateRotScale(0, NumberOfCVs++); + } + } + else if (CurveClosed) + { + // For the other methods, if the bezier curve is closed, the last point is the 1st + DuplicateRotScale(0, NumberOfCVs++); + } + } + + // Even after interpolation, additional points might still be missing: + // Bezier curves require a certain number of points regarding their order, + // if points are lacking then HAPI duplicates the last one. + if (NumberOfCVs < PartInfos.pointCount) + { + int nToAdd = PartInfos.pointCount - NumberOfCVs; + for (int n = 0; n < nToAdd; n++) + { + DuplicateRotScale(NumberOfCVs - 1, NumberOfCVs); + NumberOfCVs++; + } + } + + // To avoid crashes, attributes will only be added if we now have the correct number of them + bAddRotations = bAddRotations && (Rotations->Num() == PartInfos.pointCount); + bAddScales3d = bAddScales3d && (Scales3d->Num() == PartInfos.pointCount); + + // We need to increase the point attributes count for points in the Part Infos + HAPI_AttributeOwner NewAttributesOwner = HAPI_ATTROWNER_POINT; + HAPI_AttributeOwner OriginalAttributesOwner = HAPI_ATTROWNER_POINT; + + int OriginalPointParametersCount = PartInfos.attributeCounts[NewAttributesOwner]; + if (bAddRotations) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + if (bAddScales3d) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + + // Sending the updated PartInfos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, &PartInfos), false); + + // We need now to reproduce ALL the curves attributes for ALL the Owners.. + for (int nOwner = 0; nOwner < HAPI_ATTROWNER_MAX; nOwner++) + { + int nOwnerAttributeCount = nOwner == NewAttributesOwner ? OriginalPointParametersCount : PartInfos.attributeCounts[nOwner]; + if (nOwnerAttributeCount == 0) + continue; + + TArray AttributeNamesSH; + AttributeNamesSH.SetNum(nOwnerAttributeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, (HAPI_AttributeOwner)nOwner, + AttributeNamesSH.GetData(), AttributeNamesSH.Num()), false); + + for (int nAttribute = 0; nAttribute < AttributeNamesSH.Num(); nAttribute++) + { + const HAPI_StringHandle sh = AttributeNamesSH[nAttribute]; + if (sh == 0) + continue; + + // Get the attribute name + std::string attr_name; + FHoudiniEngineString::ToStdString(sh, attr_name); + if (strcmp(attr_name.c_str(), "__topology") == 0) + continue; + + // and the attribute infos + HAPI_AttributeInfo attr_info; + FHoudiniApi::AttributeInfo_Init(&attr_info); + //FMemory::Memzero< HAPI_AttributeInfo >( attr_info ); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, attr_name.c_str(), + (HAPI_AttributeOwner)nOwner, &attr_info), false); + + switch (attr_info.storage) + { + case HAPI_STORAGETYPE_INT: + { + // Storing IntData + TArray< int > IntData; + IntData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + IntData.GetData(), 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info, IntData.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_INT64: + { + // Storing IntData + TArray Int64Data; + Int64Data.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + Int64Data.GetData(), 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info, Int64Data.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_FLOAT: + { + // Storing Float Data + TArray< float > FloatData; + FloatData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + FloatData.GetData(), + 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + FloatData.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_STRING: + { + // Storing String Data + TArray StringHandleData; + StringHandleData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + StringHandleData.GetData(), + 0, attr_info.count), false); + + // Convert the SH to const char * + TArray StringData; + StringData.SetNumUninitialized(attr_info.count); + for (int n = 0; n < StringHandleData.Num(); n++) + { + // Converting the string + std::string strSTD; + FHoudiniEngineString::ToStdString(sh, strSTD); + + StringData[n] = strSTD.c_str(); + } + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + StringData.GetData(), + 0, attr_info.count), false); + } + break; + + default: + { + // Unhandled attribute type - warn + HOUDINI_LOG_WARNING( + TEXT("HapiCreateCurveInputNodeForData() - Unhandled attribute type - skipping") + TEXT("- consider disabling additionnal rot/scale if having issues with the HDA")); + continue; + } + + } + } + } + + // Only GET/SET curve infos if the part is a curve... + // (Closed linear curves are actually not considered as curves...) + if (PartInfos.type == HAPI_PARTTYPE_CURVE) + { + // We need to read the curve infos ... + HAPI_CurveInfo CurveInfo; + FHoudiniApi::CurveInfo_Init(&CurveInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + &CurveInfo), false); + + // ... the curve counts + TArray< int > CurveCounts; + CurveCounts.SetNumUninitialized(CurveInfo.curveCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveCounts.GetData(), + 0, CurveInfo.curveCount), false); + + // .. the curve orders + TArray< int > CurveOrders; + CurveOrders.SetNumUninitialized(CurveInfo.curveCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveOrders( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveOrders.GetData(), + 0, CurveInfo.curveCount), false); + + // .. And the Knots if they exist. + TArray< float > KnotsArray; + if (CurveInfo.hasKnots) + { + KnotsArray.SetNumUninitialized(CurveInfo.knotCount); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetCurveKnots( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + KnotsArray.GetData(), + 0, CurveInfo.knotCount), false); + } + + // To set them back in HAPI + // CurveInfo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + &CurveInfo), false); + + // CurveCounts + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveCounts.GetData(), + 0, CurveInfo.curveCount), false); + + // CurveOrders + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveOrders( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveOrders.GetData(), + 0, CurveInfo.curveCount), false); + + // And Knots if they exist + if (CurveInfo.hasKnots) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveKnots( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + KnotsArray.GetData(), + 0, CurveInfo.knotCount), false); + } + } + + if (PartInfos.faceCount > 0) + { + // getting the face counts + TArray< int > FaceCounts; + FaceCounts.SetNumUninitialized(PartInfos.faceCount); + + if (FHoudiniApi::GetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + FaceCounts.GetData(), 0, + PartInfos.faceCount) == HAPI_RESULT_SUCCESS) + { + // Set the face count + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + FaceCounts.GetData(), + 0, PartInfos.faceCount), false); + } + } + + if (PartInfos.vertexCount > 0) + { + // the vertex list + TArray< int > VertexList; + VertexList.SetNumUninitialized(PartInfos.vertexCount); + + if (FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + VertexList.GetData(), + 0, PartInfos.vertexCount) == HAPI_RESULT_SUCCESS) + { + // setting the vertex list + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + VertexList.GetData(), + 0, PartInfos.vertexCount), false); + } + } + + // We can add attributes to the curve now that all the curves attributes + // and properties have been reset. + if (bAddRotations) + { + // Create ROTATION attribute info + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + AttributeInfoRotation.count = NumberOfCVs; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = NewAttributesOwner; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation), false); + + // Convert the rotation infos + TArray< float > CurveRotations; + CurveRotations.SetNumZeroed(NumberOfCVs * 4); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + // Get current quaternion + const FQuat& RotationQuaternion = (*Rotations)[Idx]; + + CurveRotations[Idx * 4 + 0] = RotationQuaternion.X; + CurveRotations[Idx * 4 + 1] = RotationQuaternion.Z; + CurveRotations[Idx * 4 + 2] = RotationQuaternion.Y; + CurveRotations[Idx * 4 + 3] = -RotationQuaternion.W; + } + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation, + CurveRotations.GetData(), + 0, AttributeInfoRotation.count), false); + } + + // Create SCALE attribute info. + if (bAddScales3d) + { + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = NumberOfCVs; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = NewAttributesOwner; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale), false); + + // Convert the scale + TArray< float > CurveScales; + CurveScales.SetNumZeroed(NumberOfCVs * 3); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + // Get current scale + FVector ScaleVector = (*Scales3d)[Idx]; + CurveScales[Idx * 3 + 0] = ScaleVector.X; + CurveScales[Idx * 3 + 1] = ScaleVector.Z; + CurveScales[Idx * 3 + 2] = ScaleVector.Y; + } + + // We can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale, + CurveScales.GetData(), + 0, AttributeInfoScale.count), false); + } + + // Finally, commit the geo ... + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), CurveNodeId), false); + + // And cook it with refinement enabled + CookOptions.refineCurveToLinear = true; + if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, false)) + return false; +#endif + + return true; +} + +void +FHoudiniSplineTranslator::CreatePositionsString(const TArray& InPositions, FString& OutPositionString) +{ + OutPositionString = TEXT(""); + for (int32 Idx = 0; Idx < InPositions.Num(); ++Idx) + { + FVector Position = InPositions[Idx]; + // Convert to meters + Position /= HAPI_UNREAL_SCALE_FACTOR_POSITION; + // Swap Y/Z + OutPositionString += FString::Printf(TEXT("%f, %f, %f "), Position.X, Position.Z, Position.Y); + } +} + +bool +FHoudiniSplineTranslator::HapiCreateCurveInputNode(HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName) +{ + // Create the curve SOP Node + HAPI_NodeId NewNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/curve"), InputNodeName, false, &NewNodeId), false); + + OutCurveNodeId = NewNodeId; + + // Submit default points to curve. + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), NewNodeId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NewNodeId, + HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT, ParmId, 0), false); + + // Cook the newly created node + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); + */ + + return FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true); +} + +UHoudiniSplineComponent* +FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent) +{ + if (GeoId < 0) + return nullptr; + + if (!OuterComponent || OuterComponent->IsPendingKill()) + return nullptr; + + USceneComponent* const SceneComponent = Cast(OuterComponent); + if (!IsValid(SceneComponent)) + return nullptr; + + // Create a HoudiniSplineComponent for the editable curve. + UHoudiniSplineComponent* HoudiniSplineComponent = NewObject( + OuterComponent, + UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Public | RF_Transactional); + + HoudiniSplineComponent->SetNodeId(GeoId); + HoudiniSplineComponent->SetGeoPartName(PartName); + + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(SceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + + // Delete the curve points so that UpdateHoudiniCurves initializes them from HAPI + HoudiniSplineComponent->CurvePoints.Empty(); + HoudiniSplineComponent->DisplayPoints.Empty(); + UpdateHoudiniCurve(HoudiniSplineComponent); + + ReselectSelectedActors(); + + return HoudiniSplineComponent; + +} + +UHoudiniSplineComponent* +FHoudiniSplineTranslator::CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC) +{ + if (!OuterHAC || OuterHAC->IsPendingKill()) + return nullptr; + + UObject* Outer = nullptr; + if (OuterHAC && !OuterHAC->IsPendingKill()) + Outer = OuterHAC->GetOwner() ? OuterHAC->GetOwner() : OuterHAC->GetOuter(); + + UHoudiniSplineComponent *NewHoudiniSplineComponent = NewObject(Outer, UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!NewHoudiniSplineComponent) + return nullptr; + + NewHoudiniSplineComponent->Construct(CurvePoints); + + bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); + bool bHasScales = CurveScales.Num() == CurvePoints.Num(); + + TArray Transforms; + for (int32 n = 0; n < CurvePoints.Num(); ++n) + { + FTransform NextTransform = FTransform::Identity; + NextTransform.SetLocation(CurvePoints[n]); + + if (bHasRotations) + NextTransform.SetRotation(CurveRotations[n].Rotation().Quaternion()); + + if (bHasScales) + NextTransform.SetScale3D(CurveScales[n]); + + Transforms.Add(NextTransform); + } + + NewHoudiniSplineComponent->CurveType = EHoudiniCurveType::Polygon; + NewHoudiniSplineComponent->bIsOutputCurve = true; + + NewHoudiniSplineComponent->AttachToComponent(OuterHAC, FAttachmentTransformRules::KeepRelativeTransform); + NewHoudiniSplineComponent->RegisterComponent(); + + ReselectSelectedActors(); + + return NewHoudiniSplineComponent; +} + +USplineComponent* +FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, + UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed) +{ + if (!OuterComponent || OuterComponent->IsPendingKill()) + return nullptr; + + USceneComponent* OuterSceneComponent = Cast(OuterComponent); + if (!IsValid(OuterSceneComponent)) + return nullptr; + + UObject* Outer = nullptr; + Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); + + USplineComponent* NewSplineComponent = NewObject(Outer, USplineComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!NewSplineComponent) + return nullptr; + + // Clear default USplineComponent's points + NewSplineComponent->ClearSplinePoints(); + NewSplineComponent->bEditableWhenInherited = false; + + //bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); + //bool bHasScales = CurveScales.Num() == CurvePoints.Num(); + + for (int32 n = 0; n < CurvePoints.Num(); ++n) + { + NewSplineComponent->AddSplinePoint(CurvePoints[n], ESplineCoordinateSpace::Local); + + //FSplinePoint NewSplinePoint; + //NewSplinePoint.Position = CurvePoints[n]; + //if (bHasRotations) + // NewSplinePoint.Rotation = CurveRotations[n].Rotation(); + + //if (bHasScales) + // NewSplinePoint.Scale = CurveScales[n]; + //NewSplineComponent->AddPoint(NewSplinePoint, false); + } + + if (bIsLinear) + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + else + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); + } + + + NewSplineComponent->SetClosedLoop(bIsClosed); + + /* + NewSplineComponent->SetClosedLoop(bClosed); + + if (Type == int32(EHoudiniCurveType::Linear)) + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + else + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); + } + */ + + NewSplineComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + NewSplineComponent->RegisterComponent(); + AActor *OwnerActor = Cast(Outer); + if (IsValid(OwnerActor)) + OwnerActor->AddInstanceComponent(NewSplineComponent); + + ReselectSelectedActors(); + + return NewSplineComponent; +} + +bool +FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed) +{ + if (!EditedSplineComponent || EditedSplineComponent->IsPendingKill()) + return false; + + if (CurvePoints.Num() < 2) + return false; + + int MinCount = FMath::Min(CurvePoints.Num(), EditedSplineComponent->GetNumberOfSplinePoints()); + + for (int32 Idx = EditedSplineComponent->GetNumberOfSplinePoints() - 1; Idx >= 0; --Idx) + { + EditedSplineComponent->RemoveSplinePoint(Idx, false); + } + + for (int32 Idx = 0; Idx < CurvePoints.Num(); ++Idx) + { + EditedSplineComponent->AddSplinePoint(CurvePoints[Idx], ESplineCoordinateSpace::Local, false); + + if (CurveType == EHoudiniCurveType::Polygon) + EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Linear, false); + else + EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Curve, false); + } + + EditedSplineComponent->SetClosedLoop(bClosed, true); + + return true; +} + +bool +FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent) +{ + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + if (CurvePoints.Num() < 2) + return false; + + int MinCount = FMath::Min(CurvePoints.Num(), EditedHoudiniSplineComponent->CurvePoints.Num()); + + int Idx = 0; + // modify existing points + for (; Idx < MinCount; ++Idx) + { + FTransform CurTrans = EditedHoudiniSplineComponent->CurvePoints[Idx]; + if (CurTrans.GetLocation() == CurvePoints[Idx]) + continue; + + CurTrans.SetLocation(CurvePoints[Idx]); + } + + // remove extra points + if (Idx < EditedHoudiniSplineComponent->CurvePoints.Num()-1) + { + for (int32 n = EditedHoudiniSplineComponent->CurvePoints.Num() - 1; n >= Idx; --n) + { + EditedHoudiniSplineComponent->RemovePointAtIndex(n); + } + } + + + // append extra points + for (; Idx < CurvePoints.Num(); ++Idx) + { + FTransform NewPoint = FTransform::Identity; + NewPoint.SetLocation(CurvePoints[Idx]); + EditedHoudiniSplineComponent->CurvePoints.Add(NewPoint); + } + + return true; +} + + +bool +FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + UObject* InOuterComponent, + TMap& InSplines, + TMap& OutSplines, + const bool& InForceRebuild, + const EHoudiniCurveOutputType& OutputCurveType, + const bool& bIsLinear, + const bool& bIsClosed) +{ + // If we're not forcing the rebuild + // No need to recreate something that hasn't changed + if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged)) + { + // Simply reuse the existing meshes + OutSplines = InSplines; + return true; + } + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + int32 CurveNodeId = InHGPO.GeoId; + int32 CurvePartId = InHGPO.PartId; + if (CurveNodeId < 0 || CurvePartId < 0) + return false; + + // Extract all curve points from this HGPO + TArray RefinedCurvePositions; + HAPI_AttributeInfo AttributeRefinedCurvePositions; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); + + TArray RefinedCurveRotations; + HAPI_AttributeInfo AttributeRefinedCurveRotations; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveRotations); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_ROTATION, AttributeRefinedCurveRotations, RefinedCurveRotations); + + TArray RefinedCurveScales; + HAPI_AttributeInfo AttributeRefinedCurveScales; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveScales); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_SCALE, AttributeRefinedCurveScales, RefinedCurveScales); + + HAPI_CurveInfo CurveInfo; + FHoudiniApi::CurveInfo_Init(&CurveInfo); + FHoudiniApi::GetCurveInfo(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, &CurveInfo); + + int32 NumOfCurves = CurveInfo.curveCount; + TArray CurvePointsCounts; + CurvePointsCounts.SetNumZeroed(NumOfCurves); + FHoudiniApi::GetCurveCounts(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, CurvePointsCounts.GetData(), 0, NumOfCurves); + + TArray> CurvesDisplayPoints; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurvesDisplayPoints, CurvePointsCounts); + + TArray> CurvesRotations; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveRotations, CurvesRotations, CurvePointsCounts); + + TArray> CurvesScales; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveScales, CurvesScales, CurvePointsCounts); + + // Extract all curve points from this HGPO + FString GeoName = InHGPO.PartName; + int32 CurveIdx = 1; + + // Iterate through all curves found in this HGPO + for (int32 n = 0; n < CurvesDisplayPoints.Num(); ++n) + { + FString CurveName = FString::Printf(TEXT("%s curve %d"), *GeoName, CurveIdx); + CurveIdx += 1; + + if (CurvePointsCounts[n] < 2) + { + // Invalid vertex count, skip this curve. + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d] invalid vertex count.") + TEXT("- skipping."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx); + continue; + } + + FHoudiniOutputObjectIdentifier CurveIdentifier(InHGPO.ObjectId, InHGPO.GeoId, InHGPO.PartId, CurveName); + FHoudiniOutputObject* FoundOutputObject = InSplines.Find(CurveIdentifier); + + bool bNeedToRebuildSpline = false; + if (!FoundOutputObject) + bNeedToRebuildSpline = true; + + USceneComponent* FoundComponent = Cast(FoundOutputObject ? FoundOutputObject->OutputComponent : nullptr); + if (FoundComponent && !FoundComponent->IsPendingKill()) + { + // Only support output to Unreal Spline for now... + //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::UnrealSpline) + // bNeedToRebuildSpline = true; + + //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + // bNeedToRebuildSpline = true; + + if (InHGPO.bHasGeoChanged || InHGPO.PartInfo.bHasChanged || InForceRebuild) + bNeedToRebuildSpline = true; + } + else + { + bNeedToRebuildSpline = true; + } + + // The curve has not changed, no need to go through the rest + if (!bNeedToRebuildSpline) + { + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + continue; + } + + bool bReusedPreviousOutput = false; + if (!FoundOutputObject) + { + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline (default): Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + // If not found (at initialize), create an Unreal spline + // We only support unreal spline for now.. + // May support Houdini spline too later + USplineComponent* CreatedSplineComponent = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); + if (!CreatedSplineComponent) + continue; + + // Create a new output object + FHoudiniOutputObject NewOutputObject; + NewOutputObject.OutputComponent = CreatedSplineComponent; + + NewOutputObject.CurveOutputProperty.CurveOutputType = OutputCurveType; + NewOutputObject.CurveOutputProperty.NumPoints = CurvePointsCounts[n]; + + // TODO: Need a way to access info of the output curve + NewOutputObject.CurveOutputProperty.CurveMethod = EHoudiniCurveMethod::Breakpoints; + NewOutputObject.CurveOutputProperty.CurveType = bIsLinear ? EHoudiniCurveType::Polygon : EHoudiniCurveType::Bezier; + NewOutputObject.CurveOutputProperty.bClosed = false; + // Fill in the rest of output curve properties + + OutSplines.Add(CurveIdentifier, NewOutputObject); + + // Update FOundOutputObject so we can cache attributes after + FoundOutputObject = OutSplines.Find(CurveIdentifier); + } + else + { + // + if (FoundOutputObject->CurveOutputProperty.CurveOutputType == EHoudiniCurveOutputType::UnrealSpline) + { + // See if we can simply update the previous Spline Component + bool bCanUpdateUnrealSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); + if (bCanUpdateUnrealSpline) + { + // Update the existing unreal spline component + bReusedPreviousOutput = true; + HOUDINI_LOG_WARNING( + TEXT("Updating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + USplineComponent* FoundUnrealSpline = Cast(FoundOutputObject->OutputComponent); + if (!FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(CurvesDisplayPoints[n], FoundUnrealSpline, FoundOutputObject->CurveOutputProperty.CurveType, FoundOutputObject->CurveOutputProperty.bClosed)) + continue; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + else + { + // Create a new Unreal spline component + // We support unreal spline only for now... + bReusedPreviousOutput = false; + FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::UnrealSpline; + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + USplineComponent* NewUnrealSpline = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); + if (!NewUnrealSpline) + continue; + + FoundOutputObject->OutputComponent = NewUnrealSpline; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + } + // We current support Unreal Spline output only... + /* + else + { + // We want to output a Houdini Spline Component + // See if we can simply update the previous Houdini Spline Component + bool bCanUpdateHoudiniSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); + if (bCanUpdateHoudiniSpline) + { + // Update the existing houdini spline component + bReusedPreviousOutput = true; + HOUDINI_LOG_WARNING( + TEXT("Changing Houdini Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + UHoudiniSplineComponent* FoundHoudiniSpline = Cast(FoundOutputObject->OutputComponent); + if (!FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], FoundHoudiniSpline)) + continue; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + else + { + // Create a new Houdini spline component + bReusedPreviousOutput = false; + FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + UHoudiniSplineComponent* NewHoudiniSpline = CreateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuter); + if (!NewHoudiniSpline) + continue; + + FoundOutputObject->OutputComponent = NewHoudiniSpline; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + } + */ + } + + // Cache commonly supported Houdini attributes on the OutputAttributes + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, LevelPaths)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutputNames)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, BakeOutputActorNames)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(InHGPO.GeoId, BakeFolders, InHGPO.PartId)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, BakeOutlinerFolders)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + // Update generic properties attributes on the spline component + TArray GenericAttributes; + if (FoundOutputObject && FHoudiniEngineUtils::GetGenericPropertiesAttributes( + InHGPO.GeoId, InHGPO.PartId, true, 0, 0, 0, GenericAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(FoundOutputObject->OutputComponent, GenericAttributes); + } + + if (bReusedPreviousOutput) + { + // Remove the reused output unreal spline from the old map to avoid its deletion + InSplines.Remove(CurveIdentifier); + } + + HOUDINI_LOG_WARNING( + TEXT("Finished Generating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + } + + return true; +} + + +bool +FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + // ONLY DO THIS ON CURVES!!!! + if (InOutput->GetType() != EHoudiniOutputType::Curve) + return false; + + // UHoudiniAssetComponent* OuterHAC = Cast(InOuterComponent); + // + // if (!OuterHAC || OuterHAC->IsPendingKill()) + // return false; + + TMap NewOutputObjects; + TMap& OldOutputObjects = InOutput->GetOutputObjects(); + + // Iterate on all the output's HGPO + for (const FHoudiniGeoPartObject & CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + // not a curve, skip + if (CurHGPO.Type != EHoudiniPartType::Curve) + continue; + + // Check if we want to create a houdini output curve from corresponding attribute + HAPI_AttributeInfo CurveOutputAttriInfo; + FHoudiniApi::AttributeInfo_Init(&CurveOutputAttriInfo); + TArray IntData; + IntData.Empty(); + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE, CurveOutputAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + continue; + + if (IntData.Num() <= 0) + continue; + else + { + if (IntData[0] == 0) + continue; + } + + HAPI_AttributeInfo LinearAttriInfo; + FHoudiniApi::AttributeInfo_Init(&LinearAttriInfo); + IntData.Empty(); + + bool bIsLinear = false; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR, LinearAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + { + if (IntData.Num() > 0) + bIsLinear = IntData[0] == 1; + } + + HAPI_AttributeInfo ClosedAttriInfo; + FHoudiniApi::AttributeInfo_Init(&ClosedAttriInfo); + IntData.Empty(); + + bool bIsClosed = false; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED, ClosedAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) + { + if (IntData.Num() > 0) + bIsClosed = IntData[0] == 1; + } + + // We output curve to Unreal Spline only for now + // May support output to Houdini Spline later + CreateOutputSplinesFromHoudiniGeoPartObject(CurHGPO, InOuterComponent, OldOutputObjects, NewOutputObjects, false, EHoudiniCurveOutputType::UnrealSpline, bIsLinear, bIsClosed); + } + + // TODO: FIX ME!!! This literally nukes all the output objects, even if they are not curves! + + // The old map now only contains unused/stale output curves destroy them + for (auto& OldPair : OldOutputObjects) + { + USceneComponent* OldSplineSceneComponent = Cast(OldPair.Value.OutputComponent); + + if (!OldSplineSceneComponent || OldSplineSceneComponent->IsPendingKill()) + continue; + + // The output object is supposed to be a spline + if (!OldSplineSceneComponent->IsA() && !OldSplineSceneComponent->IsA()) + continue; + + OldSplineSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + OldSplineSceneComponent->UnregisterComponent(); + OldSplineSceneComponent->DestroyComponent(); + } + OldOutputObjects.Empty(); + + InOutput->SetOutputObjects(NewOutputObjects); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + + return true; +} + +void +FHoudiniSplineTranslator::ReselectSelectedActors() +{ + // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? + USelection* Selection = GEditor->GetSelectedActors(); + TArray SelectedActors; + SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); + Selection->GetSelectedObjects(SelectedActors); + + GEditor->SelectNone(false, false, false); + + for (AActor* NextSelected : SelectedActors) + { + GEditor->SelectActor(NextSelected, true, true, true, true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h index 2c90a3ffe..e7f566b75 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h @@ -1,112 +1,112 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" - -class UHoudiniInput; -class UHoudiniOutput; -class UHoudiniAssetComponent; -class UHoudiniSplineComponent; -class USceneComponent; -class USplineComponent; - -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniOutputObject; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniCurveOutputType : uint8; - -struct HOUDINIENGINE_API FHoudiniSplineTranslator -{ - // Get the cooked Houdini curve. - static bool UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent); - - // Get all cooked Houdini curves of an input. - static void UpdateHoudiniInputCurves(UHoudiniInput* Input); - - // Get all cooked Houdini curves of inputs in an HAC. - static void UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC); - - // Upload Houdini spline component data to the curve node, and then sync the Houdini Spline Component with the curve node. - static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent, bool bInSetRotAndScaleAttributes); - - // Update the curve node data, or create a new curve node if the CurveNodeId is valid. - static bool HapiCreateCurveInputNodeForData( - HAPI_NodeId& CurveNodeId, - const FString& InputNodeName, - TArray* Positions, - TArray* Rotations, - TArray* Scales3d, - EHoudiniCurveType InCurveType, - EHoudiniCurveMethod InCurveMethod, - const bool& InClosed, - const bool& InReversed, - const bool& InForceClose = false, - const FTransform& ParentTransform = FTransform::Identity); - - // Create a default curve node. - static bool HapiCreateCurveInputNode( - HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName); - - // Create a Houdini spline component from a given editable node. (Only called once when first build the editable node.) - static UHoudiniSplineComponent* CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent); - - // Helper functions. - static void ExtractStringPositions(const FString& Positions, TArray& OutPositions); - - static void ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData); - - static void ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts); - - static void CreatePositionsString(const TArray& InPositions, FString& OutPositionString); - - static bool CreateOutputSplinesFromHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO, UObject* InOuterComponent, - TMap& InSplines, - TMap& OutSplines, - const bool& InForceRebuild, - const EHoudiniCurveOutputType& OutputCurveType, - const bool& bIsLinear, - const bool& bIsclosed); - - static bool CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent); - - static USplineComponent* CreateOutputUnrealSplineComponent(const TArray& CurvePoints, - const TArray& CurveRotations, const TArray& CurveScales, UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed); - - static UHoudiniSplineComponent* CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC); - - static bool UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed); - - static bool UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent); - - static void ReselectSelectedActors(); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" + +class UHoudiniInput; +class UHoudiniOutput; +class UHoudiniAssetComponent; +class UHoudiniSplineComponent; +class USceneComponent; +class USplineComponent; + +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniOutputObject; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniCurveOutputType : uint8; + +struct HOUDINIENGINE_API FHoudiniSplineTranslator +{ + // Get the cooked Houdini curve. + static bool UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent); + + // Get all cooked Houdini curves of an input. + static void UpdateHoudiniInputCurves(UHoudiniInput* Input); + + // Get all cooked Houdini curves of inputs in an HAC. + static void UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC); + + // Upload Houdini spline component data to the curve node, and then sync the Houdini Spline Component with the curve node. + static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent, bool bInSetRotAndScaleAttributes); + + // Update the curve node data, or create a new curve node if the CurveNodeId is valid. + static bool HapiCreateCurveInputNodeForData( + HAPI_NodeId& CurveNodeId, + const FString& InputNodeName, + TArray* Positions, + TArray* Rotations, + TArray* Scales3d, + EHoudiniCurveType InCurveType, + EHoudiniCurveMethod InCurveMethod, + const bool& InClosed, + const bool& InReversed, + const bool& InForceClose = false, + const FTransform& ParentTransform = FTransform::Identity); + + // Create a default curve node. + static bool HapiCreateCurveInputNode( + HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName); + + // Create a Houdini spline component from a given editable node. (Only called once when first build the editable node.) + static UHoudiniSplineComponent* CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent); + + // Helper functions. + static void ExtractStringPositions(const FString& Positions, TArray& OutPositions); + + static void ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData); + + static void ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts); + + static void CreatePositionsString(const TArray& InPositions, FString& OutPositionString); + + static bool CreateOutputSplinesFromHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO, UObject* InOuterComponent, + TMap& InSplines, + TMap& OutSplines, + const bool& InForceRebuild, + const EHoudiniCurveOutputType& OutputCurveType, + const bool& bIsLinear, + const bool& bIsclosed); + + static bool CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent); + + static USplineComponent* CreateOutputUnrealSplineComponent(const TArray& CurvePoints, + const TArray& CurveRotations, const TArray& CurveScales, UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed); + + static UHoudiniSplineComponent* CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC); + + static bool UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed); + + static bool UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent); + + static void ReselectSelectedActors(); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp index b4133609e..c6f97412a 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp @@ -1,219 +1,219 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStringResolver.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniEngineRuntime.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HAL/FileManager.h" - -void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutTokens) const -{ - for (auto& Elem : CachedTokens) - { - OutTokens.Add(Elem.Key, Elem.Value.StringValue); - } -} - -FString FHoudiniStringResolver::SanitizeTokenValue(const FString& InValue) -{ - // Replace {} characters with __ - FString OutString = InValue; - OutString.ReplaceInline(ANSI_TO_TCHAR("{"), ANSI_TO_TCHAR("__")); - OutString.ReplaceInline(ANSI_TO_TCHAR("}"), ANSI_TO_TCHAR("__")); - - return OutString; -} - -void FHoudiniStringResolver::SetToken(const FString& InName, const FString& InValue) -{ - CachedTokens.Add(InName, SanitizeTokenValue(InValue)); -} - -void FHoudiniStringResolver::SetTokensFromStringMap(const TMap& InTokens, bool bClearTokens) -{ - if (bClearTokens) - { - CachedTokens.Empty(); - } - - for (auto& Elem : InTokens) - { - CachedTokens.Add(Elem.Key, SanitizeTokenValue(Elem.Value)); - } -} - - - -FString FHoudiniStringResolver::ResolveString( - const FString& InString) const -{ - const FString Result = FString::Format(*InString, CachedTokens); - return Result; -} - -//void FHoudiniStringResolver::SetCurrentWorld(UWorld* InWorld) -//{ -// SetAttribute("world", InWorld->GetPathName()); -//} - -FString FHoudiniAttributeResolver::ResolveAttribute( - const FString& InAttrName, - const FString& InDefaultValue) const -{ - if (!CachedAttributes.Contains(InAttrName)) - { - return ResolveString(InDefaultValue); - } - FString AttrStr = CachedAttributes.FindChecked(InAttrName); - return ResolveString(AttrStr); -} - -//FString FHoudiniStringResolver::GetTempFolderArgument() const -//{ -// // The actual temp directory should have been supplied externally -// if (Tokens.Contains(TEXT("temp"))) -// return Tokens.FindChecked(TEXT("temp")); -// -// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'temp' argument. Using fallback value.")); -// return TEXT("/Game/Content/HoudiniEngine/Temp"); // Fallback value -//} -// -//FString FHoudiniStringResolver::GetBakeFolderArgument() const -//{ -// // The actual bake directory should have been supplied externally -// if (Tokens.Contains(TEXT("bake"))) -// return Tokens.FindChecked(TEXT("bake")); -// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'bake' argument. Using fallback value.")); -// return TEXT("/Game/Content/HoudiniEngine/Bake"); // Fallback value -//} -// -//FString FHoudiniStringResolver::GetOutputFolderForPackageMode(EPackageMode PackageMode) const -//{ -// switch (PackageMode) -// { -// case EPackageMode::Bake: -// return GetBakeFolderArgument(); -// case EPackageMode::CookToLevel: -// case EPackageMode::CookToTemp: -// return GetTempFolderArgument(); -// } -// return ""; -//} - - void FHoudiniAttributeResolver::SetCachedAttributes(const TMap& Attributes) - { - CachedAttributes = Attributes; - } - -void FHoudiniAttributeResolver::SetAttribute(const FString& InName, const FString& InValue) -{ - CachedAttributes.Add(InName, InValue); -} - -FString FHoudiniAttributeResolver::ResolveFullLevelPath() const -{ - FString OutputFolder = TEXT("/Game/Content/HoudiniEngine/Temp"); - - const FStringFormatArg* BaseDir = CachedTokens.Find(TEXT("out_basedir")); - if (BaseDir) - OutputFolder = BaseDir->StringValue; - - FString LevelPathAttr = ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, TEXT("{out}")); - if (LevelPathAttr.IsEmpty()) - return OutputFolder; - - return FHoudiniEngineRuntimeUtils::JoinPaths(OutputFolder, LevelPathAttr); -} - -FString FHoudiniAttributeResolver::ResolveOutputName() const -{ - FString OutputAttribName; - - if (CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2)) - OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2; - else - OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1; - - return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); -} - -FString FHoudiniAttributeResolver::ResolveBakeFolder() const -{ - const FString DefaultBakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - FString BakeFolder = ResolveAttribute(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, TEXT("{bake}")); - if (BakeFolder.IsEmpty()) - return DefaultBakeFolder; - - //if (BakeFolder.StartsWith("Game/")) - //{ - // BakeFolder = "/" + BakeFolder; - //} - - //FString AbsoluteOverridePath; - //if (BakeFolder.StartsWith("/Game/")) - //{ - // const FString RelativePath = FPaths::ProjectContentDir() + BakeFolder.Mid(6, BakeFolder.Len() - 6); - // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - //} - //else - //{ - // if (!BakeFolder.IsEmpty()) - // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolder); - //} - - //// Check Validity of the path - //if (AbsoluteOverridePath.IsEmpty()) - //{ - // return DefaultBakeFolder; - //} - - return BakeFolder; -} - -void FHoudiniAttributeResolver::LogCachedAttributesAndTokens() const -{ - TArray Lines; - Lines.Add(TEXT("==================")); - Lines.Add(TEXT("Cached Attributes:")); - Lines.Add(TEXT("==================")); - for (const auto& Entry : CachedAttributes) - { - Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value))); - } - - Lines.Add(TEXT("==============")); - Lines.Add(TEXT("Cached Tokens:")); - Lines.Add(TEXT("==============")); - for (const auto& Entry : CachedTokens) - { - Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value.StringValue))); - } - - HOUDINI_LOG_DISPLAY(TEXT("%s"), *FString::Join(Lines, TEXT("\n"))); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStringResolver.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntime.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HAL/FileManager.h" + +void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutTokens) const +{ + for (auto& Elem : CachedTokens) + { + OutTokens.Add(Elem.Key, Elem.Value.StringValue); + } +} + +FString FHoudiniStringResolver::SanitizeTokenValue(const FString& InValue) +{ + // Replace {} characters with __ + FString OutString = InValue; + OutString.ReplaceInline(ANSI_TO_TCHAR("{"), ANSI_TO_TCHAR("__")); + OutString.ReplaceInline(ANSI_TO_TCHAR("}"), ANSI_TO_TCHAR("__")); + + return OutString; +} + +void FHoudiniStringResolver::SetToken(const FString& InName, const FString& InValue) +{ + CachedTokens.Add(InName, SanitizeTokenValue(InValue)); +} + +void FHoudiniStringResolver::SetTokensFromStringMap(const TMap& InTokens, bool bClearTokens) +{ + if (bClearTokens) + { + CachedTokens.Empty(); + } + + for (auto& Elem : InTokens) + { + CachedTokens.Add(Elem.Key, SanitizeTokenValue(Elem.Value)); + } +} + + + +FString FHoudiniStringResolver::ResolveString( + const FString& InString) const +{ + const FString Result = FString::Format(*InString, CachedTokens); + return Result; +} + +//void FHoudiniStringResolver::SetCurrentWorld(UWorld* InWorld) +//{ +// SetAttribute("world", InWorld->GetPathName()); +//} + +FString FHoudiniAttributeResolver::ResolveAttribute( + const FString& InAttrName, + const FString& InDefaultValue) const +{ + if (!CachedAttributes.Contains(InAttrName)) + { + return ResolveString(InDefaultValue); + } + FString AttrStr = CachedAttributes.FindChecked(InAttrName); + return ResolveString(AttrStr); +} + +//FString FHoudiniStringResolver::GetTempFolderArgument() const +//{ +// // The actual temp directory should have been supplied externally +// if (Tokens.Contains(TEXT("temp"))) +// return Tokens.FindChecked(TEXT("temp")); +// +// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'temp' argument. Using fallback value.")); +// return TEXT("/Game/Content/HoudiniEngine/Temp"); // Fallback value +//} +// +//FString FHoudiniStringResolver::GetBakeFolderArgument() const +//{ +// // The actual bake directory should have been supplied externally +// if (Tokens.Contains(TEXT("bake"))) +// return Tokens.FindChecked(TEXT("bake")); +// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'bake' argument. Using fallback value.")); +// return TEXT("/Game/Content/HoudiniEngine/Bake"); // Fallback value +//} +// +//FString FHoudiniStringResolver::GetOutputFolderForPackageMode(EPackageMode PackageMode) const +//{ +// switch (PackageMode) +// { +// case EPackageMode::Bake: +// return GetBakeFolderArgument(); +// case EPackageMode::CookToLevel: +// case EPackageMode::CookToTemp: +// return GetTempFolderArgument(); +// } +// return ""; +//} + + void FHoudiniAttributeResolver::SetCachedAttributes(const TMap& Attributes) + { + CachedAttributes = Attributes; + } + +void FHoudiniAttributeResolver::SetAttribute(const FString& InName, const FString& InValue) +{ + CachedAttributes.Add(InName, InValue); +} + +FString FHoudiniAttributeResolver::ResolveFullLevelPath() const +{ + FString OutputFolder = TEXT("/Game/Content/HoudiniEngine/Temp"); + + const FStringFormatArg* BaseDir = CachedTokens.Find(TEXT("out_basedir")); + if (BaseDir) + OutputFolder = BaseDir->StringValue; + + FString LevelPathAttr = ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, TEXT("{out}")); + if (LevelPathAttr.IsEmpty()) + return OutputFolder; + + return FHoudiniEngineRuntimeUtils::JoinPaths(OutputFolder, LevelPathAttr); +} + +FString FHoudiniAttributeResolver::ResolveOutputName() const +{ + FString OutputAttribName; + + if (CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2)) + OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2; + else + OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1; + + return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); +} + +FString FHoudiniAttributeResolver::ResolveBakeFolder() const +{ + const FString DefaultBakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + FString BakeFolder = ResolveAttribute(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, TEXT("{bake}")); + if (BakeFolder.IsEmpty()) + return DefaultBakeFolder; + + //if (BakeFolder.StartsWith("Game/")) + //{ + // BakeFolder = "/" + BakeFolder; + //} + + //FString AbsoluteOverridePath; + //if (BakeFolder.StartsWith("/Game/")) + //{ + // const FString RelativePath = FPaths::ProjectContentDir() + BakeFolder.Mid(6, BakeFolder.Len() - 6); + // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + //} + //else + //{ + // if (!BakeFolder.IsEmpty()) + // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolder); + //} + + //// Check Validity of the path + //if (AbsoluteOverridePath.IsEmpty()) + //{ + // return DefaultBakeFolder; + //} + + return BakeFolder; +} + +void FHoudiniAttributeResolver::LogCachedAttributesAndTokens() const +{ + TArray Lines; + Lines.Add(TEXT("==================")); + Lines.Add(TEXT("Cached Attributes:")); + Lines.Add(TEXT("==================")); + for (const auto& Entry : CachedAttributes) + { + Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value))); + } + + Lines.Add(TEXT("==============")); + Lines.Add(TEXT("Cached Tokens:")); + Lines.Add(TEXT("==============")); + for (const auto& Entry : CachedTokens) + { + Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value.StringValue))); + } + + HOUDINI_LOG_DISPLAY(TEXT("%s"), *FString::Join(Lines, TEXT("\n"))); +} diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.h b/Source/HoudiniEngine/Private/HoudiniStringResolver.h index bedf2768d..e43fcc0b9 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.h +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.h @@ -1,112 +1,112 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "HoudiniStringResolver.generated.h" - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniStringResolver -{ - - GENERATED_USTRUCT_BODY(); - -protected: - - // Named arguments that will be substituted into attribute values upon retrieval. - TMap CachedTokens; - - -public: - // ---------------------------------- - // Named argument accessors - // ---------------------------------- - - TMap& GetCachedTokens() { return CachedTokens; } - - - // Set a named argument that will be used for argument replacement during GetAttribute calls. - void SetToken(const FString& InName, const FString& InValue); - - // Sanitize a token value. Currently only replaces { and } with __. - static FString SanitizeTokenValue(const FString& InValue); - - void GetTokensAsStringMap(TMap& OutTokens) const; - - void SetTokensFromStringMap(const TMap& InValue, bool bClearTokens=true); - - // Resolve a string by substituting `Tokens` as named arguments during string formatting. - FString ResolveString(const FString& InStr) const; - -}; - - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolver -{ - GENERATED_USTRUCT_BODY(); - -protected: - TMap CachedAttributes; - -public: - - void SetCachedAttributes(const TMap& Attributes); - - // Return a mutable reference to the cached attributes. - TMap& GetCachedAttributes() { return CachedAttributes; } - - // Return immutable reference to cached attributes. - const TMap& GetCachedAttributes() const { return CachedAttributes; } - - // Set an attribute with the given name and value in the attribute cache. - void SetAttribute(const FString& InName, const FString& InValue); - - // Try to resolve an attribute with the given name. If the attribute could not be - // found, use DefaultValue as a fallback. - FString ResolveAttribute(const FString& InAttrName, const FString& InDefaultValue) const; - - // ---------------------------------- - // Helpers - // ---------------------------------- - - // Helper for resolving the `unreal_level_path` attribute. - FString ResolveFullLevelPath() const; - - // Helper for resolver custom output name attributes. - FString ResolveOutputName() const; - - // Helper for resolving the unreal_bake_folder attribute. Converts to an absolute path. - FString ResolveBakeFolder() const; - - // ---------------------------------- - // Debug - // ---------------------------------- - // Logs the resolver's cached attributes and tokens - void LogCachedAttributesAndTokens() const; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniStringResolver.generated.h" + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniStringResolver +{ + + GENERATED_USTRUCT_BODY(); + +protected: + + // Named arguments that will be substituted into attribute values upon retrieval. + TMap CachedTokens; + + +public: + // ---------------------------------- + // Named argument accessors + // ---------------------------------- + + TMap& GetCachedTokens() { return CachedTokens; } + + + // Set a named argument that will be used for argument replacement during GetAttribute calls. + void SetToken(const FString& InName, const FString& InValue); + + // Sanitize a token value. Currently only replaces { and } with __. + static FString SanitizeTokenValue(const FString& InValue); + + void GetTokensAsStringMap(TMap& OutTokens) const; + + void SetTokensFromStringMap(const TMap& InValue, bool bClearTokens=true); + + // Resolve a string by substituting `Tokens` as named arguments during string formatting. + FString ResolveString(const FString& InStr) const; + +}; + + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolver +{ + GENERATED_USTRUCT_BODY(); + +protected: + TMap CachedAttributes; + +public: + + void SetCachedAttributes(const TMap& Attributes); + + // Return a mutable reference to the cached attributes. + TMap& GetCachedAttributes() { return CachedAttributes; } + + // Return immutable reference to cached attributes. + const TMap& GetCachedAttributes() const { return CachedAttributes; } + + // Set an attribute with the given name and value in the attribute cache. + void SetAttribute(const FString& InName, const FString& InValue); + + // Try to resolve an attribute with the given name. If the attribute could not be + // found, use DefaultValue as a fallback. + FString ResolveAttribute(const FString& InAttrName, const FString& InDefaultValue) const; + + // ---------------------------------- + // Helpers + // ---------------------------------- + + // Helper for resolving the `unreal_level_path` attribute. + FString ResolveFullLevelPath() const; + + // Helper for resolver custom output name attributes. + FString ResolveOutputName() const; + + // Helper for resolving the unreal_bake_folder attribute. Converts to an absolute path. + FString ResolveBakeFolder() const; + + // ---------------------------------- + // Debug + // ---------------------------------- + // Logs the resolver's cached attributes and tokens + void LogCachedAttributesAndTokens() const; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp b/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp index 1fd5162eb..28eded86a 100644 --- a/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp +++ b/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp @@ -1,159 +1,159 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "SAssetSelectionWidget.h" - -#include "HoudiniEngineString.h" - -#if WITH_EDITOR - -#include "EditorStyleSet.h" -#include "Widgets/Layout/SBorder.h" -#include "Widgets/Input/SButton.h" - - -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 WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - -FReply -SAssetSelectionWidget::OnButtonOk() -{ - if (TSharedPtr WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - -FReply -SAssetSelectionWidget::OnButtonCancel() -{ - bIsCancelled = true; - - if (TSharedPtr WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "SAssetSelectionWidget.h" + +#include "HoudiniEngineString.h" + +#if WITH_EDITOR + +#include "EditorStyleSet.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Input/SButton.h" + + +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 WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply +SAssetSelectionWidget::OnButtonOk() +{ + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply +SAssetSelectionWidget::OnButtonCancel() +{ + bIsCancelled = true; + + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + #endif \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/SAssetSelectionWidget.h b/Source/HoudiniEngine/Private/SAssetSelectionWidget.h index 66f2c39e6..2d6cd3e5d 100644 --- a/Source/HoudiniEngine/Private/SAssetSelectionWidget.h +++ b/Source/HoudiniEngine/Private/SAssetSelectionWidget.h @@ -1,100 +1,100 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#if WITH_EDITOR - -#include "CoreMinimal.h" -//#include "Misc/Attribute.h" - -#include "Widgets/SWidget.h" -#include "Widgets/SCompoundWidget.h" -#include "Widgets/SWindow.h" -#include "Widgets/DeclarativeSyntaxSupport.h" - -#include "HAPI/HAPI_Common.h" - -/** 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, 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; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +//#include "Misc/Attribute.h" + +#include "Widgets/SWidget.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWindow.h" +#include "Widgets/DeclarativeSyntaxSupport.h" + +#include "HAPI/HAPI_Common.h" + +/** 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, 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; +}; + #endif \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp index 75d4f1dc2..0cfc6e9ce 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp @@ -1,448 +1,448 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealBrushTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniInputObject.h" - -#include "HoudiniGeoPartObject.h" -#include "Model.h" -#include "Engine/Polys.h" - -#include "HoudiniEngineRuntimeUtils.h" - -// Includes for Brush building code. Remove when the code is in the correct place. -#include "HCsgUtils.h" -#include "ActorEditorUtils.h" -#include "Misc/ScopedSlowTask.h" - -#include "Engine/Level.h" - -// TODO: Fix this -// This is currently being included to get access to the CreateFaceMaterialArray / DeleteFaceMaterialArray methods. -#include "UnrealMeshTranslator.h" - -DEFINE_LOG_CATEGORY_STATIC(LogBrushTranslator, Log, All); - -bool FUnrealBrushTranslator::CreateInputNodeForBrush( - UHoudiniInputBrush* InputBrushObject, - ABrush* BrushActor, - const TArray* ExcludeActors, - HAPI_NodeId &CreatedNodeId, - const FString& NodeName -) -{ - if (!IsValid(BrushActor)) - return false; - - if (!IsValid(BrushActor->Brush)) - return false; - - if (InputBrushObject->ShouldIgnoreThisInput()) - return true; - - //-------------------------------------------------------------------------------------------------- - // Create an input node - //-------------------------------------------------------------------------------------------------- - - HAPI_NodeId ParentNodeId = -1; - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CreatedNodeId)) - { - HAPI_NodeId InputNodeId = -1; - //FString BrushNodeName = NodeName + TEXT("_") + BrushActor->GetName(); - FString BrushNodeName = BrushActor->GetName(); - // Create Brush SOP node - std::string NodeNameRawString; - FHoudiniEngineUtils::ConvertUnrealString(BrushNodeName, NodeNameRawString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - return false; - - // We now have a valid id. - CreatedNodeId = InputNodeId; - ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); - - // Add a clean node - HAPI_NodeId CleanNodeId; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - ParentNodeId, TEXT("clean"), TEXT("clean"), true, &CleanNodeId), false); - - // Connect input node to the clean node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CleanNodeId, 0, CreatedNodeId, 0), false); - - // Set display flag on the clean node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetNodeDisplay( - FHoudiniEngine::Get().GetSession(), CleanNodeId, 1), false); - } - else - { - ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); - } - - - // Transform for positions - const FTransform ActorTransform = BrushActor->GetActorTransform(); - const FTransform ActorTransformInverse = ActorTransform.Inverse(); - // Precompute matrices for Normal transformations (see FPlane::TransformBy) - const FMatrix NmlInvXform = ActorTransformInverse.ToMatrixWithScale().TransposeAdjoint(); - float NScale = ActorTransformInverse.GetDeterminant() < 0 ? -1.f : 1.f; - - //-------------------------------------------------------------------------------------------------- - // Find actors that intersect with the given brush. - //-------------------------------------------------------------------------------------------------- - TArray BrushActors; - UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(InputBrushObject, BrushActors); - - UModel* BrushModel = UHCsgUtils::BuildModelFromBrushes(BrushActors); - InputBrushObject->UpdateCachedData(BrushModel, BrushActors); - - // DEBUG: Upload the level model (baked by UE) to Houdini - // ULevel* Level = BrushActor->GetTypedOuter(); - // BrushModel = Level->Model; - - - int NumPoints = BrushModel->Points.Num(); - if (NumPoints == 0) - { - // The content has changed and now we don't have geo to output. - // Be sure to clean up existing nodes in Houdini. - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), ParentNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *(BrushActor->GetName())); - } - CreatedNodeId = -1; - return true; - } - - //-------------------------------------------------------------------------------------------------- - // Construct the face count buffer. Also count the vertex indices in required to define the Part. - //-------------------------------------------------------------------------------------------------- - - int NumIndices = 0; - TArray FaceCountBuffer; - - { - // Calculate the size of the vertex buffer and the base vertex index of each node. - TArray& Nodes = BrushModel->Nodes; - //TArray& Surfs = BrushModel->Surfs; - TArray& Verts = BrushModel->Verts; - - int32 NumNodes = Nodes.Num(); - - FaceCountBuffer.SetNumUninitialized(NumNodes); - // Build the face counts buffer by iterating over the BSP nodes. - for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) - { - FBspNode& Node = Nodes[NodeIndex]; - FaceCountBuffer[NodeIndex] = Node.NumVertices; - NumIndices += Node.NumVertices; - } - } - - //-------------------------------------------------------------------------------------------------- - // Apply actor transform - //-------------------------------------------------------------------------------------------------- - - if (!ActorTransform.Equals(FTransform::Identity)) - { - - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ActorTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), ParentNodeId, &HapiTransform), false); - } - - //-------------------------------------------------------------------------------------------------- - // Start processing the geo and add it to the input node - //-------------------------------------------------------------------------------------------------- - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumIndices; - Part.faceCount = FaceCountBuffer.Num(); - Part.pointCount = NumPoints; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, &Part), false); - - // ----------------------------- - // Vector - Point Attribute Info - // ----------------------------- - HAPI_AttributeInfo AttributeInfoPointVector; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointVector); - AttributeInfoPointVector.count = NumPoints; - AttributeInfoPointVector.tupleSize = 3; - AttributeInfoPointVector.exists = true; - AttributeInfoPointVector.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointVector.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointVector.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector), false); - - // ----------------------------- - // Vector - Vertex Attribute Info - // ----------------------------- - HAPI_AttributeInfo AttributeInfoVertexVector; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertexVector); - AttributeInfoVertexVector.count = NumIndices; - AttributeInfoVertexVector.tupleSize = 3; - AttributeInfoVertexVector.exists = true; - AttributeInfoVertexVector.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertexVector.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertexVector.originalOwner = HAPI_ATTROWNER_INVALID; - - // ----------------------------- - // POSITION (P) - // ----------------------------- - - { - TArray< FVector > OutPosition; - FVector Scale = FVector(1.f, 1.f, 1.f); // TODO: Extract from actor transform. - OutPosition.SetNumUninitialized(NumPoints); - - for (int32 PosIndex = 0; PosIndex < NumPoints; ++PosIndex) - { - FVector Point = BrushModel->Points[PosIndex]; - Point = ActorTransformInverse.TransformPosition(Point); - FVector Pos(Point.X, Point.Z, Point.Y); - OutPosition[PosIndex] = Pos/HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - // Upload point positions. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector, - (const float *)OutPosition.GetData(), 0, AttributeInfoPointVector.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES (VertexList), NORMALS, UVS - //--------------------------------------------------------------------------------------------------------------------- - // Vertex buffer processing logic based on UModel::BuildVertexBuffers(). - { - // Calculate the size of the vertex buffer and the base vertex index of each node. - TArray& Positions = BrushModel->Points; - TArray& Nodes = BrushModel->Nodes; - TArray& Surfs = BrushModel->Surfs; - TArray& Verts = BrushModel->Verts; - TArray& Vectors = BrushModel->Vectors; - - int32 NumNodes = Nodes.Num(); - TArray Indices; - TArray OutNormals; - TArray OutUV; - TArray MaterialIndices; - TMap MaterialMap; - - Indices.SetNumUninitialized(NumIndices); - OutNormals.SetNumUninitialized(NumIndices); - OutUV.SetNumUninitialized(NumIndices); - - MaterialIndices.SetNumUninitialized(NumNodes); - - // Populate the vertex index buffer. - int32 iVertex = 0; - for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) - { - FBspNode& Node = Nodes[NodeIndex]; - FBspSurf& Surf = Surfs[Node.iSurf]; - for (int32 NodeVertexIndex = 0; NodeVertexIndex < Node.NumVertices; ++NodeVertexIndex) - { - // Vertex Index - Indices[iVertex] = Verts[Node.iVertPool + NodeVertexIndex].pVertex; - // Normal - FVector N = Vectors[Surf.vNormal]; - N = NmlInvXform.TransformVector(N).GetSafeNormal(); - - OutNormals[iVertex] = FVector(N.X, N.Z, N.Y); - // UVs - FVector& vU = Vectors[Surf.vTextureU]; - FVector& vV = Vectors[Surf.vTextureV]; - FVector deltaVtx = (Positions[Indices[iVertex]] - Positions[Surf.pBase]); - float U = FVector::DotProduct(deltaVtx, vU) / UModel::GetGlobalBSPTexelScale(); - float V = -FVector::DotProduct(deltaVtx, vV) / UModel::GetGlobalBSPTexelScale(); - OutUV[iVertex] = FVector(U, V, 0.f); - ++iVertex; - } - // Face Material - // Construct a material index array for the faces - int32 MaterialIndex = MaterialMap.FindOrAdd(Surf.Material, MaterialMap.Num()); - MaterialIndices[NodeIndex] = MaterialIndex; - } - - // Set the vertex index buffer - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, Indices.GetData(), 0, NumIndices), false); - - // Set the face counts as per the BSP nodes. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, FaceCountBuffer.GetData(), 0, FaceCountBuffer.Num()), false); - - // ----------------------------- - // Normal attribute - // ----------------------------- - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertexVector), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertexVector, (const float *)OutNormals.GetData(), - 0, AttributeInfoVertexVector.count), false); - - // ----------------------------- - // UV attribute - // ----------------------------- - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_UV, &AttributeInfoVertexVector), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_UV, - &AttributeInfoVertexVector, (const float *)OutUV.GetData(), - 0, AttributeInfoVertexVector.count), false); - - // ----------------------------- - // Material attribute - // ----------------------------- - - TArray Materials; - - if (MaterialMap.Num() > 0) - { - // Sort the output material in the correct order (by material index) - MaterialMap.ValueSort([](int32 A, int32 B) { return A < B; }); - - // Set the value in the correct order - // Do not reduce the array of materials, this could cause crahses in some weird cases.. - /*if (MaterialMap.Num() > MaterialInterfaces.Num()) - MaterialInterfaces.SetNumZeroed(MaterialMap.Num());*/ - Materials.SetNumUninitialized(MaterialMap.Num()); - - int32 MaterialIndex = 0; - for (auto Kvp : MaterialMap) - { - Materials[MaterialIndex++] = Kvp.Key; - } - } - - // List of materials, one for each face. - TArray OutMaterials; - - //Lists of material parameters - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - bool bAttributeSuccess = false; - bool bAddMaterialParametersAsAttributes = false; - - if (bAddMaterialParametersAsAttributes) - { - // Create attributes for the material and all its parameters - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - Materials, MaterialIndices, OutMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - CreatedNodeId, - 0, - NumNodes, - OutMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - else - { - // Create attributes only for the materials - // Only get the material attribute data - FUnrealMeshTranslator::CreateFaceMaterialArray( - Materials, MaterialIndices, OutMaterials); - - // Create attribute for materials - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - CreatedNodeId, - 0, - NumNodes, - OutMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(OutMaterials); - - // Delete texture material parameter names. - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedNodeId), false ); - - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealBrushTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniInputObject.h" + +#include "HoudiniGeoPartObject.h" +#include "Model.h" +#include "Engine/Polys.h" + +#include "HoudiniEngineRuntimeUtils.h" + +// Includes for Brush building code. Remove when the code is in the correct place. +#include "HCsgUtils.h" +#include "ActorEditorUtils.h" +#include "Misc/ScopedSlowTask.h" + +#include "Engine/Level.h" + +// TODO: Fix this +// This is currently being included to get access to the CreateFaceMaterialArray / DeleteFaceMaterialArray methods. +#include "UnrealMeshTranslator.h" + +DEFINE_LOG_CATEGORY_STATIC(LogBrushTranslator, Log, All); + +bool FUnrealBrushTranslator::CreateInputNodeForBrush( + UHoudiniInputBrush* InputBrushObject, + ABrush* BrushActor, + const TArray* ExcludeActors, + HAPI_NodeId &CreatedNodeId, + const FString& NodeName +) +{ + if (!IsValid(BrushActor)) + return false; + + if (!IsValid(BrushActor->Brush)) + return false; + + if (InputBrushObject->ShouldIgnoreThisInput()) + return true; + + //-------------------------------------------------------------------------------------------------- + // Create an input node + //-------------------------------------------------------------------------------------------------- + + HAPI_NodeId ParentNodeId = -1; + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CreatedNodeId)) + { + HAPI_NodeId InputNodeId = -1; + //FString BrushNodeName = NodeName + TEXT("_") + BrushActor->GetName(); + FString BrushNodeName = BrushActor->GetName(); + // Create Brush SOP node + std::string NodeNameRawString; + FHoudiniEngineUtils::ConvertUnrealString(BrushNodeName, NodeNameRawString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + return false; + + // We now have a valid id. + CreatedNodeId = InputNodeId; + ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); + + // Add a clean node + HAPI_NodeId CleanNodeId; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + ParentNodeId, TEXT("clean"), TEXT("clean"), true, &CleanNodeId), false); + + // Connect input node to the clean node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CleanNodeId, 0, CreatedNodeId, 0), false); + + // Set display flag on the clean node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetNodeDisplay( + FHoudiniEngine::Get().GetSession(), CleanNodeId, 1), false); + } + else + { + ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); + } + + + // Transform for positions + const FTransform ActorTransform = BrushActor->GetActorTransform(); + const FTransform ActorTransformInverse = ActorTransform.Inverse(); + // Precompute matrices for Normal transformations (see FPlane::TransformBy) + const FMatrix NmlInvXform = ActorTransformInverse.ToMatrixWithScale().TransposeAdjoint(); + float NScale = ActorTransformInverse.GetDeterminant() < 0 ? -1.f : 1.f; + + //-------------------------------------------------------------------------------------------------- + // Find actors that intersect with the given brush. + //-------------------------------------------------------------------------------------------------- + TArray BrushActors; + UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(InputBrushObject, BrushActors); + + UModel* BrushModel = UHCsgUtils::BuildModelFromBrushes(BrushActors); + InputBrushObject->UpdateCachedData(BrushModel, BrushActors); + + // DEBUG: Upload the level model (baked by UE) to Houdini + // ULevel* Level = BrushActor->GetTypedOuter(); + // BrushModel = Level->Model; + + + int NumPoints = BrushModel->Points.Num(); + if (NumPoints == 0) + { + // The content has changed and now we don't have geo to output. + // Be sure to clean up existing nodes in Houdini. + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), ParentNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *(BrushActor->GetName())); + } + CreatedNodeId = -1; + return true; + } + + //-------------------------------------------------------------------------------------------------- + // Construct the face count buffer. Also count the vertex indices in required to define the Part. + //-------------------------------------------------------------------------------------------------- + + int NumIndices = 0; + TArray FaceCountBuffer; + + { + // Calculate the size of the vertex buffer and the base vertex index of each node. + TArray& Nodes = BrushModel->Nodes; + //TArray& Surfs = BrushModel->Surfs; + TArray& Verts = BrushModel->Verts; + + int32 NumNodes = Nodes.Num(); + + FaceCountBuffer.SetNumUninitialized(NumNodes); + // Build the face counts buffer by iterating over the BSP nodes. + for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) + { + FBspNode& Node = Nodes[NodeIndex]; + FaceCountBuffer[NodeIndex] = Node.NumVertices; + NumIndices += Node.NumVertices; + } + } + + //-------------------------------------------------------------------------------------------------- + // Apply actor transform + //-------------------------------------------------------------------------------------------------- + + if (!ActorTransform.Equals(FTransform::Identity)) + { + + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ActorTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), ParentNodeId, &HapiTransform), false); + } + + //-------------------------------------------------------------------------------------------------- + // Start processing the geo and add it to the input node + //-------------------------------------------------------------------------------------------------- + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumIndices; + Part.faceCount = FaceCountBuffer.Num(); + Part.pointCount = NumPoints; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, &Part), false); + + // ----------------------------- + // Vector - Point Attribute Info + // ----------------------------- + HAPI_AttributeInfo AttributeInfoPointVector; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointVector); + AttributeInfoPointVector.count = NumPoints; + AttributeInfoPointVector.tupleSize = 3; + AttributeInfoPointVector.exists = true; + AttributeInfoPointVector.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointVector.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointVector.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector), false); + + // ----------------------------- + // Vector - Vertex Attribute Info + // ----------------------------- + HAPI_AttributeInfo AttributeInfoVertexVector; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertexVector); + AttributeInfoVertexVector.count = NumIndices; + AttributeInfoVertexVector.tupleSize = 3; + AttributeInfoVertexVector.exists = true; + AttributeInfoVertexVector.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertexVector.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertexVector.originalOwner = HAPI_ATTROWNER_INVALID; + + // ----------------------------- + // POSITION (P) + // ----------------------------- + + { + TArray< FVector > OutPosition; + FVector Scale = FVector(1.f, 1.f, 1.f); // TODO: Extract from actor transform. + OutPosition.SetNumUninitialized(NumPoints); + + for (int32 PosIndex = 0; PosIndex < NumPoints; ++PosIndex) + { + FVector Point = BrushModel->Points[PosIndex]; + Point = ActorTransformInverse.TransformPosition(Point); + FVector Pos(Point.X, Point.Z, Point.Y); + OutPosition[PosIndex] = Pos/HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + // Upload point positions. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector, + (const float *)OutPosition.GetData(), 0, AttributeInfoPointVector.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES (VertexList), NORMALS, UVS + //--------------------------------------------------------------------------------------------------------------------- + // Vertex buffer processing logic based on UModel::BuildVertexBuffers(). + { + // Calculate the size of the vertex buffer and the base vertex index of each node. + TArray& Positions = BrushModel->Points; + TArray& Nodes = BrushModel->Nodes; + TArray& Surfs = BrushModel->Surfs; + TArray& Verts = BrushModel->Verts; + TArray& Vectors = BrushModel->Vectors; + + int32 NumNodes = Nodes.Num(); + TArray Indices; + TArray OutNormals; + TArray OutUV; + TArray MaterialIndices; + TMap MaterialMap; + + Indices.SetNumUninitialized(NumIndices); + OutNormals.SetNumUninitialized(NumIndices); + OutUV.SetNumUninitialized(NumIndices); + + MaterialIndices.SetNumUninitialized(NumNodes); + + // Populate the vertex index buffer. + int32 iVertex = 0; + for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) + { + FBspNode& Node = Nodes[NodeIndex]; + FBspSurf& Surf = Surfs[Node.iSurf]; + for (int32 NodeVertexIndex = 0; NodeVertexIndex < Node.NumVertices; ++NodeVertexIndex) + { + // Vertex Index + Indices[iVertex] = Verts[Node.iVertPool + NodeVertexIndex].pVertex; + // Normal + FVector N = Vectors[Surf.vNormal]; + N = NmlInvXform.TransformVector(N).GetSafeNormal(); + + OutNormals[iVertex] = FVector(N.X, N.Z, N.Y); + // UVs + FVector& vU = Vectors[Surf.vTextureU]; + FVector& vV = Vectors[Surf.vTextureV]; + FVector deltaVtx = (Positions[Indices[iVertex]] - Positions[Surf.pBase]); + float U = FVector::DotProduct(deltaVtx, vU) / UModel::GetGlobalBSPTexelScale(); + float V = -FVector::DotProduct(deltaVtx, vV) / UModel::GetGlobalBSPTexelScale(); + OutUV[iVertex] = FVector(U, V, 0.f); + ++iVertex; + } + // Face Material + // Construct a material index array for the faces + int32 MaterialIndex = MaterialMap.FindOrAdd(Surf.Material, MaterialMap.Num()); + MaterialIndices[NodeIndex] = MaterialIndex; + } + + // Set the vertex index buffer + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, Indices.GetData(), 0, NumIndices), false); + + // Set the face counts as per the BSP nodes. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, FaceCountBuffer.GetData(), 0, FaceCountBuffer.Num()), false); + + // ----------------------------- + // Normal attribute + // ----------------------------- + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertexVector), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertexVector, (const float *)OutNormals.GetData(), + 0, AttributeInfoVertexVector.count), false); + + // ----------------------------- + // UV attribute + // ----------------------------- + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_UV, &AttributeInfoVertexVector), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_UV, + &AttributeInfoVertexVector, (const float *)OutUV.GetData(), + 0, AttributeInfoVertexVector.count), false); + + // ----------------------------- + // Material attribute + // ----------------------------- + + TArray Materials; + + if (MaterialMap.Num() > 0) + { + // Sort the output material in the correct order (by material index) + MaterialMap.ValueSort([](int32 A, int32 B) { return A < B; }); + + // Set the value in the correct order + // Do not reduce the array of materials, this could cause crahses in some weird cases.. + /*if (MaterialMap.Num() > MaterialInterfaces.Num()) + MaterialInterfaces.SetNumZeroed(MaterialMap.Num());*/ + Materials.SetNumUninitialized(MaterialMap.Num()); + + int32 MaterialIndex = 0; + for (auto Kvp : MaterialMap) + { + Materials[MaterialIndex++] = Kvp.Key; + } + } + + // List of materials, one for each face. + TArray OutMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + Materials, MaterialIndices, OutMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + CreatedNodeId, + 0, + NumNodes, + OutMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + Materials, MaterialIndices, OutMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + CreatedNodeId, + 0, + NumNodes, + OutMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(OutMaterials); + + // Delete texture material parameter names. + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedNodeId), false ); + + + return true; +} + diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.h b/Source/HoudiniEngine/Private/UnrealBrushTranslator.h index 1d285570f..b98197392 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.h @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "UObject/NameTypes.h" - -class ABrush; -class AActor; -class UModel; -class UHoudiniInputBrush; -class ABrush; -class AActor; - -struct HOUDINIENGINE_API FUnrealBrushTranslator -{ -public: - static bool CreateInputNodeForBrush( - UHoudiniInputBrush* InputBrushObject, - ABrush* BrushActor, - const TArray* ExcludeActors, - HAPI_NodeId &CreatedNodeId, - const FString& NodeName - ); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "UObject/NameTypes.h" + +class ABrush; +class AActor; +class UModel; +class UHoudiniInputBrush; +class ABrush; +class AActor; + +struct HOUDINIENGINE_API FUnrealBrushTranslator +{ +public: + static bool CreateInputNodeForBrush( + UHoudiniInputBrush* InputBrushObject, + ABrush* BrushActor, + const TArray* ExcludeActors, + HAPI_NodeId &CreatedNodeId, + const FString& NodeName + ); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp index 9b3ce7d63..b3c464e20 100644 --- a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp @@ -1,289 +1,289 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealFoliageTypeTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "FoliageType_InstancedStaticMesh.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInputTranslator.h" - -#include "Engine/StaticMesh.h" -#include "Components/StaticMeshComponent.h" - - -bool -FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - UFoliageType_InstancedStaticMesh* InFoliageType, - HAPI_NodeId& InputObjectNodeId, - const FString& InputNodeName, - const bool& ExportAllLODs, - const bool& ExportSockets, - const bool& ExportColliders) -{ - if (!IsValid(InFoliageType)) - return false; - - UStaticMesh* const InputSM = InFoliageType->GetStaticMesh(); - if (!IsValid(InputSM)) - return false; - - UStaticMeshComponent* const StaticMeshComponent = nullptr; - bool bSuccess = HapiCreateInputNodeForStaticMesh( - InputSM, - InputObjectNodeId, - InputNodeName, - StaticMeshComponent, - ExportAllLODs, - ExportSockets, - ExportColliders); - - if (bSuccess) - { - const int32 PartId = 0; - CreateHoudiniFoliageTypeAttributes(InFoliageType, InputObjectNodeId, PartId, HAPI_ATTROWNER_DETAIL); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InputObjectNodeId), false); - } - - return bSuccess; -} - -bool FUnrealFoliageTypeTranslator::CreateInputNodeForReference( - UFoliageType* InFoliageType, - HAPI_NodeId& InInputNodeId, - const FString& InRef, - const FString& InInputNodeName, - const FTransform& InTransform) -{ - bool bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InInputNodeId, InRef, InInputNodeName, InTransform); - if (!bSuccess) - return false; - - const int32 PartId = 0; - if (CreateHoudiniFoliageTypeAttributes(InFoliageType, InInputNodeId, PartId, HAPI_ATTROWNER_POINT)) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InInputNodeId), false); - return true; - } - - return false; -} - -bool -FUnrealFoliageTypeTranslator::CreateHoudiniFoliageTypeAttributes(UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner) -{ - if (InNodeId < 0) - return false; - - bool bSuccess = true; - - // Create attribute for unreal_foliage - HAPI_AttributeInfo AttributeInfoUnrealFoliage; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoUnrealFoliage); - AttributeInfoUnrealFoliage.tupleSize = 1; - AttributeInfoUnrealFoliage.count = 1; - AttributeInfoUnrealFoliage.exists = true; - AttributeInfoUnrealFoliage.owner = InAttributeOwner; - AttributeInfoUnrealFoliage.storage = HAPI_STORAGETYPE_INT; - AttributeInfoUnrealFoliage.originalOwner = HAPI_ATTROWNER_INVALID; - - // Create the new attribute - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage)) - { - // The New attribute has been successfully created, set its value - int UnrealFoliage = 1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage, - &UnrealFoliage, 0, 1)) - { - bSuccess = false; - } - } - - if (!bSuccess) - return false; - - // Foliage type properties that should be sent to Houdini as unreal_uproperty_ attributes. - static TArray FoliageTypePropertyNames({ - // float - // FName("Density"), - // FName("DensityAdjustmentFactor"), - // FName("Radius"), - // FName("SingleInstanceModeRadius"), - FName("AlignMaxAngle"), - FName("RandomPitchAngle"), - FName("MinimumLayerWeight"), - FName("MinimumExclusionLayerWeight"), - FName("CollisionRadius"), - FName("ShadeRadius"), - FName("InitialSeedDensity"), - FName("AverageSpreadDistance"), - FName("SpreadVariance"), - FName("MaxInitialSeedOffset"), - FName("MaxInitialAge"), - FName("MaxAge"), - FName("OverlapPriority"), - - // bool - // FName("bSingleInstanceModeOverrideRadius"), - FName("bCanGrowInShade"), - FName("bSpawnsInShade"), - - // int32 - FName("OverriddenLightMapRes"), - FName("CustomDepthStencilValue"), - FName("TranslucencySortPriority"), - FName("NumSteps"), - FName("SeedsPerStep"), - FName("DistributionSeed"), - FName("ChangeCount"), - FName("VirtualTextureCullMips"), - - // uint32 - FName("AlignToNormal"), - FName("RandomYaw"), - FName("CollisionWithWorld"), - FName("CastShadow"), - FName("bAffectDynamicIndirectLighting"), - FName("bAffectDistanceFieldLighting"), - FName("bCastDynamicShadow"), - FName("bCastStaticShadow"), - FName("bCastShadowAsTwoSided"), - FName("bReceivesDecals"), - FName("bOverrideLightMapRes"), - FName("bUseAsOccluder"), - FName("bRenderCustomDepth"), - FName("ReapplyDensity"), - FName("ReapplyRadius"), - FName("ReapplyAlignToNormal"), - FName("ReapplyRandomYaw"), - FName("ReapplyScaling"), - FName("ReapplyScaleX"), - FName("ReapplyScaleY"), - FName("ReapplyScaleZ"), - FName("ReapplyRandomPitchAngle"), - FName("ReapplyGroundSlope"), - FName("ReapplyHeight"), - FName("ReapplyLandscapeLayers"), - FName("ReapplyZOffset"), - FName("ReapplyCollisionWithWorld"), - FName("ReapplyVertexColorMask"), - FName("bEnableDensityScaling"), - FName("bEnableDiscardOnLoad"), - - // enums - // FName("Scaling"), - FName("LightmapType"), - - // FFloatInterval - // FName("ScaleX"), - // FName("ScaleY"), - // FName("ScaleZ"), - FName("ZOffset"), - FName("GroundSlopeAngle"), - FName("Height"), - FName("ProceduralScale"), - - // FVector - FName("CollisionScale"), - FName("LowBoundOriginRadius")}); - - EAttribOwner AttribOwner; - switch (InAttributeOwner) - { - case HAPI_ATTROWNER_POINT: - AttribOwner = EAttribOwner::Point; - break; - case HAPI_ATTROWNER_VERTEX: - AttribOwner = EAttribOwner::Vertex; - break; - case HAPI_ATTROWNER_PRIM: - AttribOwner = EAttribOwner::Prim; - break; - case HAPI_ATTROWNER_DETAIL: - AttribOwner = EAttribOwner::Detail; - break; - case HAPI_ATTROWNER_INVALID: - case HAPI_ATTROWNER_MAX: - default: - HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InAttributeOwner); - return false; - } - FHoudiniGenericAttribute GenericAttribute; - GenericAttribute.AttributeCount = 1; - GenericAttribute.AttributeOwner = AttribOwner; - - // Reserve enough space in the arrays (we only have a single point (or all are detail attributes), so attribute - // count is 1, but the tuple size could be up to 10 for transforms - GenericAttribute.DoubleValues.Reserve(10); - GenericAttribute.IntValues.Reserve(10); - GenericAttribute.StringValues.Reserve(10); - - for (const FName& PropertyName : FoliageTypePropertyNames) - { - const FString PropertyNameStr = PropertyName.ToString(); - GenericAttribute.AttributeName = FString::Printf(TEXT("unreal_uproperty_%s"), *PropertyNameStr); - // Find the property on the foliage type instance - FProperty* FoundProperty = nullptr; - UObject* FoundPropertyObject = nullptr; - void* Container = nullptr; - if (!FHoudiniGenericAttribute::FindPropertyOnObject( - InFoliageType, - PropertyNameStr, - FoundProperty, - FoundPropertyObject, - Container)) - continue; - - if (!FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( - InFoliageType, - FoundProperty, - Container, - GenericAttribute.AttributeTupleSize, - GenericAttribute.AttributeType)) - continue; - - const int32 AtIndex = 0; - if (!FHoudiniGenericAttribute::GetPropertyValueFromObject( - InFoliageType, - FoundProperty, - Container, - GenericAttribute, - AtIndex)) - continue; - - FHoudiniEngineUtils::SetGenericPropertyAttribute(InNodeId, InPartId, GenericAttribute); - } - - return bSuccess; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealFoliageTypeTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "FoliageType_InstancedStaticMesh.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInputTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" + + +bool +FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + UFoliageType_InstancedStaticMesh* InFoliageType, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + const bool& ExportAllLODs, + const bool& ExportSockets, + const bool& ExportColliders) +{ + if (!IsValid(InFoliageType)) + return false; + + UStaticMesh* const InputSM = InFoliageType->GetStaticMesh(); + if (!IsValid(InputSM)) + return false; + + UStaticMeshComponent* const StaticMeshComponent = nullptr; + bool bSuccess = HapiCreateInputNodeForStaticMesh( + InputSM, + InputObjectNodeId, + InputNodeName, + StaticMeshComponent, + ExportAllLODs, + ExportSockets, + ExportColliders); + + if (bSuccess) + { + const int32 PartId = 0; + CreateHoudiniFoliageTypeAttributes(InFoliageType, InputObjectNodeId, PartId, HAPI_ATTROWNER_DETAIL); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId), false); + } + + return bSuccess; +} + +bool FUnrealFoliageTypeTranslator::CreateInputNodeForReference( + UFoliageType* InFoliageType, + HAPI_NodeId& InInputNodeId, + const FString& InRef, + const FString& InInputNodeName, + const FTransform& InTransform) +{ + bool bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InInputNodeId, InRef, InInputNodeName, InTransform); + if (!bSuccess) + return false; + + const int32 PartId = 0; + if (CreateHoudiniFoliageTypeAttributes(InFoliageType, InInputNodeId, PartId, HAPI_ATTROWNER_POINT)) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InInputNodeId), false); + return true; + } + + return false; +} + +bool +FUnrealFoliageTypeTranslator::CreateHoudiniFoliageTypeAttributes(UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner) +{ + if (InNodeId < 0) + return false; + + bool bSuccess = true; + + // Create attribute for unreal_foliage + HAPI_AttributeInfo AttributeInfoUnrealFoliage; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoUnrealFoliage); + AttributeInfoUnrealFoliage.tupleSize = 1; + AttributeInfoUnrealFoliage.count = 1; + AttributeInfoUnrealFoliage.exists = true; + AttributeInfoUnrealFoliage.owner = InAttributeOwner; + AttributeInfoUnrealFoliage.storage = HAPI_STORAGETYPE_INT; + AttributeInfoUnrealFoliage.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage)) + { + // The New attribute has been successfully created, set its value + int UnrealFoliage = 1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage, + &UnrealFoliage, 0, 1)) + { + bSuccess = false; + } + } + + if (!bSuccess) + return false; + + // Foliage type properties that should be sent to Houdini as unreal_uproperty_ attributes. + static TArray FoliageTypePropertyNames({ + // float + // FName("Density"), + // FName("DensityAdjustmentFactor"), + // FName("Radius"), + // FName("SingleInstanceModeRadius"), + FName("AlignMaxAngle"), + FName("RandomPitchAngle"), + FName("MinimumLayerWeight"), + FName("MinimumExclusionLayerWeight"), + FName("CollisionRadius"), + FName("ShadeRadius"), + FName("InitialSeedDensity"), + FName("AverageSpreadDistance"), + FName("SpreadVariance"), + FName("MaxInitialSeedOffset"), + FName("MaxInitialAge"), + FName("MaxAge"), + FName("OverlapPriority"), + + // bool + // FName("bSingleInstanceModeOverrideRadius"), + FName("bCanGrowInShade"), + FName("bSpawnsInShade"), + + // int32 + FName("OverriddenLightMapRes"), + FName("CustomDepthStencilValue"), + FName("TranslucencySortPriority"), + FName("NumSteps"), + FName("SeedsPerStep"), + FName("DistributionSeed"), + FName("ChangeCount"), + FName("VirtualTextureCullMips"), + + // uint32 + FName("AlignToNormal"), + FName("RandomYaw"), + FName("CollisionWithWorld"), + FName("CastShadow"), + FName("bAffectDynamicIndirectLighting"), + FName("bAffectDistanceFieldLighting"), + FName("bCastDynamicShadow"), + FName("bCastStaticShadow"), + FName("bCastShadowAsTwoSided"), + FName("bReceivesDecals"), + FName("bOverrideLightMapRes"), + FName("bUseAsOccluder"), + FName("bRenderCustomDepth"), + FName("ReapplyDensity"), + FName("ReapplyRadius"), + FName("ReapplyAlignToNormal"), + FName("ReapplyRandomYaw"), + FName("ReapplyScaling"), + FName("ReapplyScaleX"), + FName("ReapplyScaleY"), + FName("ReapplyScaleZ"), + FName("ReapplyRandomPitchAngle"), + FName("ReapplyGroundSlope"), + FName("ReapplyHeight"), + FName("ReapplyLandscapeLayers"), + FName("ReapplyZOffset"), + FName("ReapplyCollisionWithWorld"), + FName("ReapplyVertexColorMask"), + FName("bEnableDensityScaling"), + FName("bEnableDiscardOnLoad"), + + // enums + // FName("Scaling"), + FName("LightmapType"), + + // FFloatInterval + // FName("ScaleX"), + // FName("ScaleY"), + // FName("ScaleZ"), + FName("ZOffset"), + FName("GroundSlopeAngle"), + FName("Height"), + FName("ProceduralScale"), + + // FVector + FName("CollisionScale"), + FName("LowBoundOriginRadius")}); + + EAttribOwner AttribOwner; + switch (InAttributeOwner) + { + case HAPI_ATTROWNER_POINT: + AttribOwner = EAttribOwner::Point; + break; + case HAPI_ATTROWNER_VERTEX: + AttribOwner = EAttribOwner::Vertex; + break; + case HAPI_ATTROWNER_PRIM: + AttribOwner = EAttribOwner::Prim; + break; + case HAPI_ATTROWNER_DETAIL: + AttribOwner = EAttribOwner::Detail; + break; + case HAPI_ATTROWNER_INVALID: + case HAPI_ATTROWNER_MAX: + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InAttributeOwner); + return false; + } + FHoudiniGenericAttribute GenericAttribute; + GenericAttribute.AttributeCount = 1; + GenericAttribute.AttributeOwner = AttribOwner; + + // Reserve enough space in the arrays (we only have a single point (or all are detail attributes), so attribute + // count is 1, but the tuple size could be up to 10 for transforms + GenericAttribute.DoubleValues.Reserve(10); + GenericAttribute.IntValues.Reserve(10); + GenericAttribute.StringValues.Reserve(10); + + for (const FName& PropertyName : FoliageTypePropertyNames) + { + const FString PropertyNameStr = PropertyName.ToString(); + GenericAttribute.AttributeName = FString::Printf(TEXT("unreal_uproperty_%s"), *PropertyNameStr); + // Find the property on the foliage type instance + FProperty* FoundProperty = nullptr; + UObject* FoundPropertyObject = nullptr; + void* Container = nullptr; + if (!FHoudiniGenericAttribute::FindPropertyOnObject( + InFoliageType, + PropertyNameStr, + FoundProperty, + FoundPropertyObject, + Container)) + continue; + + if (!FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( + InFoliageType, + FoundProperty, + Container, + GenericAttribute.AttributeTupleSize, + GenericAttribute.AttributeType)) + continue; + + const int32 AtIndex = 0; + if (!FHoudiniGenericAttribute::GetPropertyValueFromObject( + InFoliageType, + FoundProperty, + Container, + GenericAttribute, + AtIndex)) + continue; + + FHoudiniEngineUtils::SetGenericPropertyAttribute(InNodeId, InPartId, GenericAttribute); + } + + return bSuccess; +} diff --git a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h index 2e0d14c81..a5a1ed6d3 100644 --- a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h @@ -1,66 +1,66 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "UnrealMeshTranslator.h" - -class UFoliageType; -class UFoliageType_InstancedStaticMesh; -class UStaticMeshComponent; - -struct HOUDINIENGINE_API FUnrealFoliageTypeTranslator : public FUnrealMeshTranslator -{ -public: - // HAPI : Marshaling, extract geometry and create input asset for it - return true on success - static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - UFoliageType_InstancedStaticMesh* InFoliageType, - HAPI_NodeId& InputObjectNodeId, - const FString& InputNodeName, - const bool& ExportAllLODs = false, - const bool& ExportSockets = false, - const bool& ExportColliders = false); - - // Create an input node that references the asset via InRef (unreal_instance). - // Also calls CreateHoudiniFoliageTypeAttributes, to create the unreal_foliage attribute, as well as - // unreal_uproperty_ attributes for the foliage type settings. - static bool CreateInputNodeForReference( - UFoliageType* InFoliageType, - HAPI_NodeId& InInputNodeId, - const FString& InRef, - const FString& InInputNodeName, - const FTransform& InTransform); - -protected: - // Creates the unreal_foliage and unreal_uproperty_ attributes for the foliage type. - static bool CreateHoudiniFoliageTypeAttributes( - UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "UnrealMeshTranslator.h" + +class UFoliageType; +class UFoliageType_InstancedStaticMesh; +class UStaticMeshComponent; + +struct HOUDINIENGINE_API FUnrealFoliageTypeTranslator : public FUnrealMeshTranslator +{ +public: + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + UFoliageType_InstancedStaticMesh* InFoliageType, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false, + const bool& ExportColliders = false); + + // Create an input node that references the asset via InRef (unreal_instance). + // Also calls CreateHoudiniFoliageTypeAttributes, to create the unreal_foliage attribute, as well as + // unreal_uproperty_ attributes for the foliage type settings. + static bool CreateInputNodeForReference( + UFoliageType* InFoliageType, + HAPI_NodeId& InInputNodeId, + const FString& InRef, + const FString& InInputNodeName, + const FTransform& InTransform); + +protected: + // Creates the unreal_foliage and unreal_uproperty_ attributes for the foliage type. + static bool CreateHoudiniFoliageTypeAttributes( + UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner); +}; diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp index 3857d0416..b3b969dc1 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp @@ -1,212 +1,212 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealInstanceTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "UnrealMeshTranslator.h" - -#include "Engine/StaticMesh.h" -#include "Components/InstancedStaticMeshComponent.h" - -bool -FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( - UInstancedStaticMeshComponent* ISMC, - const FString& InNodeName, - HAPI_NodeId& OutCreatedNodeId, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bExportAsAttributeInstancer) -{ - int32 InstanceCount = ISMC->GetInstanceCount(); - if (InstanceCount < 1) - return true; - - // Get the Static Mesh instanced by the component - UStaticMesh* SM = ISMC->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - // Marshall the Static Mesh to Houdini - int32 SMNodeId = -1; - FString ISMCName = InNodeName + TEXT("_") + ISMC->GetName(); - bool bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, SMNodeId, InNodeName, ISMC, bExportLODs, bExportSockets, bExportColliders); - - if (!bSuccess) - return false; - - // To create the instance properly (via packed prim), we need to: - // - create a copytopoints (with pack and instance enable - // - an inputnode containing all of the instances transform as points - // - plug the input node and the static mesh node in the copytopoints - - // Create the copytopoints SOP. - int32 CopyNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/copytopoints"), InNodeName, true, &CopyNodeId), false); - - // set "Pack And Instance" (pack) to true - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CopyNodeId, "pack", 0, 1), false); - - // Get the copytopoints parent OBJ NodeID - HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CopyNodeId); - - // Now create an input node for the instance transforms - int32 InstancesNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - ParentNodeId, TEXT("null"), "instances", false, &InstancesNodeId), false); - - // MARSHALL THE INSTANCE TRANSFORM - { - // Get the instance transform and convert them to Position/Rotation/Scale array - TArray Positions; - Positions.SetNumZeroed(InstanceCount * 3); - TArray Rotations; - Rotations.SetNumZeroed(InstanceCount * 4); - TArray Scales; - Scales.SetNumZeroed(InstanceCount * 3); - for (int32 InstanceIdx = 0; InstanceIdx < InstanceCount; InstanceIdx++) - { - FTransform CurTransform; - ISMC->GetInstanceTransform(InstanceIdx, CurTransform); - - // Convert Unreal Position to Houdini - FVector PositionVector = CurTransform.GetLocation(); - Positions[InstanceIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Positions[InstanceIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Positions[InstanceIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - - // Convert Unreal Rotation to Houdini - FQuat RotationQuaternion = CurTransform.GetRotation(); - Rotations[InstanceIdx * 4 + 0] = RotationQuaternion.X; - Rotations[InstanceIdx * 4 + 1] = RotationQuaternion.Z; - Rotations[InstanceIdx * 4 + 2] = RotationQuaternion.Y; - Rotations[InstanceIdx * 4 + 3] = -RotationQuaternion.W; - - // Convert Unreal Scale to Houdini - FVector ScaleVector = CurTransform.GetScale3D(); - Scales[InstanceIdx * 3 + 0] = ScaleVector.X; - Scales[InstanceIdx * 3 + 1] = ScaleVector.Z; - Scales[InstanceIdx * 3 + 2] = ScaleVector.Y; - } - - // Create a part for the instance points. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = InstanceCount; - Part.type = HAPI_PARTTYPE_MESH; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InstancesNodeId, 0, &Part), false); - - // Create position (P) attribute - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = InstanceCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Positions.GetData(), 0, AttributeInfoPoint.count), false); - - // Create Rotation (rot) attribute - HAPI_AttributeInfo AttributeInfoRotation; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); - AttributeInfoRotation.count = InstanceCount; - AttributeInfoRotation.tupleSize = 4; - AttributeInfoRotation.exists = true; - AttributeInfoRotation.owner = HAPI_ATTROWNER_POINT; - AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRotation.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation, - Rotations.GetData(), 0, AttributeInfoRotation.count), false); - - // Create scale attribute - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = InstanceCount; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, - Scales.GetData(), 0, AttributeInfoScale.count), false); - - // Commit the instance point geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InstancesNodeId), false); - } - - // Connect the mesh to the copytopoints node's second input - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CopyNodeId, 0, SMNodeId, 0), false); - - // Connect the instances to the copytopoints node's second input - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CopyNodeId, 1, InstancesNodeId, 0), false); - - // Update this input object's node IDs - OutCreatedNodeId = CopyNodeId; - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealInstanceTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "UnrealMeshTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Components/InstancedStaticMeshComponent.h" + +bool +FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( + UInstancedStaticMeshComponent* ISMC, + const FString& InNodeName, + HAPI_NodeId& OutCreatedNodeId, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bExportAsAttributeInstancer) +{ + int32 InstanceCount = ISMC->GetInstanceCount(); + if (InstanceCount < 1) + return true; + + // Get the Static Mesh instanced by the component + UStaticMesh* SM = ISMC->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + // Marshall the Static Mesh to Houdini + int32 SMNodeId = -1; + FString ISMCName = InNodeName + TEXT("_") + ISMC->GetName(); + bool bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, SMNodeId, InNodeName, ISMC, bExportLODs, bExportSockets, bExportColliders); + + if (!bSuccess) + return false; + + // To create the instance properly (via packed prim), we need to: + // - create a copytopoints (with pack and instance enable + // - an inputnode containing all of the instances transform as points + // - plug the input node and the static mesh node in the copytopoints + + // Create the copytopoints SOP. + int32 CopyNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/copytopoints"), InNodeName, true, &CopyNodeId), false); + + // set "Pack And Instance" (pack) to true + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CopyNodeId, "pack", 0, 1), false); + + // Get the copytopoints parent OBJ NodeID + HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CopyNodeId); + + // Now create an input node for the instance transforms + int32 InstancesNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + ParentNodeId, TEXT("null"), "instances", false, &InstancesNodeId), false); + + // MARSHALL THE INSTANCE TRANSFORM + { + // Get the instance transform and convert them to Position/Rotation/Scale array + TArray Positions; + Positions.SetNumZeroed(InstanceCount * 3); + TArray Rotations; + Rotations.SetNumZeroed(InstanceCount * 4); + TArray Scales; + Scales.SetNumZeroed(InstanceCount * 3); + for (int32 InstanceIdx = 0; InstanceIdx < InstanceCount; InstanceIdx++) + { + FTransform CurTransform; + ISMC->GetInstanceTransform(InstanceIdx, CurTransform); + + // Convert Unreal Position to Houdini + FVector PositionVector = CurTransform.GetLocation(); + Positions[InstanceIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Positions[InstanceIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Positions[InstanceIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + + // Convert Unreal Rotation to Houdini + FQuat RotationQuaternion = CurTransform.GetRotation(); + Rotations[InstanceIdx * 4 + 0] = RotationQuaternion.X; + Rotations[InstanceIdx * 4 + 1] = RotationQuaternion.Z; + Rotations[InstanceIdx * 4 + 2] = RotationQuaternion.Y; + Rotations[InstanceIdx * 4 + 3] = -RotationQuaternion.W; + + // Convert Unreal Scale to Houdini + FVector ScaleVector = CurTransform.GetScale3D(); + Scales[InstanceIdx * 3 + 0] = ScaleVector.X; + Scales[InstanceIdx * 3 + 1] = ScaleVector.Z; + Scales[InstanceIdx * 3 + 2] = ScaleVector.Y; + } + + // Create a part for the instance points. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = InstanceCount; + Part.type = HAPI_PARTTYPE_MESH; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InstancesNodeId, 0, &Part), false); + + // Create position (P) attribute + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = InstanceCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Positions.GetData(), 0, AttributeInfoPoint.count), false); + + // Create Rotation (rot) attribute + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + AttributeInfoRotation.count = InstanceCount; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation, + Rotations.GetData(), 0, AttributeInfoRotation.count), false); + + // Create scale attribute + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = InstanceCount; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, + Scales.GetData(), 0, AttributeInfoScale.count), false); + + // Commit the instance point geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InstancesNodeId), false); + } + + // Connect the mesh to the copytopoints node's second input + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CopyNodeId, 0, SMNodeId, 0), false); + + // Connect the instances to the copytopoints node's second input + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CopyNodeId, 1, InstancesNodeId, 0), false); + + // Update this input object's node IDs + OutCreatedNodeId = CopyNodeId; + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h index c7a6a5ef9..e025a23e7 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h @@ -1,49 +1,49 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -class UInstancedStaticMeshComponent; - -struct HOUDINIENGINE_API FUnrealInstanceTranslator -{ - public: - - // HAPI : Marshaling, extract geometry and create input asset for it - return true on success - static bool HapiCreateInputNodeForInstancer( - UInstancedStaticMeshComponent* ISMC, - const FString& InNodeName, - HAPI_NodeId& OutCreatedNodeId, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bExportAsAttributeInstancer); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +class UInstancedStaticMeshComponent; + +struct HOUDINIENGINE_API FUnrealInstanceTranslator +{ + public: + + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForInstancer( + UInstancedStaticMeshComponent* ISMC, + const FString& InNodeName, + HAPI_NodeId& OutCreatedNodeId, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bExportAsAttributeInstancer); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp index 0e187ca0d..6ec026186 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp @@ -1,1991 +1,2201 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniApi.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" - -#include "UnrealLandscapeTranslator.h" -#include "HoudiniGeoPartObject.h" - -#include "Landscape.h" -#include "LandscapeDataAccess.h" -#include "LandscapeEdit.h" -#include "LightMap.h" -#include "Engine/MapBuildDataRegistry.h" -#include "PhysicalMaterials/PhysicalMaterial.h" - - -bool -FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( - ALandscapeProxy* LandscapeProxy, - HAPI_NodeId& CreatedNodeId, - const FString& InputNodeNameString, - const bool& bExportGeometryAsMesh, - const bool& bExportTileUVs, - const bool bExportNormalizedUVs, - const bool bExportLighting, - const bool bExportMaterials ) -{ - //-------------------------------------------------------------------------------------------------- - // 1. Create an input node - //-------------------------------------------------------------------------------------------------- - HAPI_NodeId InputNodeId = -1; - // Create the curve SOP Node - std::string NodeNameRawString; - FHoudiniEngineUtils::ConvertUnrealString(InputNodeNameString, NodeNameRawString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - return false; - - // We now have a valid id. - CreatedNodeId = InputNodeId; - - if(!FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true)) - return false; - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); - */ - //-------------------------------------------------------------------------------------------------- - // 2. Set the part info - //-------------------------------------------------------------------------------------------------- - int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; - float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; - - //int32 NumComponents = bExportOnlySelected ? SelectedComponents.Num() : LandscapeProxy->LandscapeComponents.Num(); - int32 NumComponents = LandscapeProxy->LandscapeComponents.Num(); - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - int32 VertexCount = NumComponents * VertexCountPerComponent; - if (!VertexCount) - return false; - - int32 TriangleCount = NumComponents * FMath::Square(ComponentSizeQuads) * 2; - int32 QuadCount = NumComponents * FMath::Square(ComponentSizeQuads); - int32 IndexCount = QuadCount * 4; - - // Create part info - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - //FMemory::Memzero< HAPI_PartInfo >(Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = VertexCount; - Part.type = HAPI_PARTTYPE_MESH; - - // If we are exporting to a mesh, we need vertices and faces - if (bExportGeometryAsMesh) - { - Part.vertexCount = IndexCount; - Part.faceCount = QuadCount; - } - - // Set the part infos - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, &DisplayGeoInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part), false); - - //-------------------------------------------------------------------------------------------------- - // 3. Extract the landscape data - //-------------------------------------------------------------------------------------------------- - // Array for the position data - TArray LandscapePositionArray; - // Array for the normals - TArray LandscapeNormalArray; - // Array for the UVs - TArray LandscapeUVArray; - // Array for the vertex index of each point in its component - TArray LandscapeComponentVertexIndicesArray; - // Array for the tile names per point - TArray LandscapeComponentNameArray; - // Array for the lightmap values - TArray LandscapeLightmapValues; - // Selected components set to all components in current landscape proxy - TSet SelectedComponents; - SelectedComponents.Append(LandscapeProxy->LandscapeComponents); - - // Extract all the data from the landscape to the arrays - if (!ExtractLandscapeData( - LandscapeProxy, SelectedComponents, - bExportLighting, bExportTileUVs, bExportNormalizedUVs, - LandscapePositionArray, LandscapeNormalArray, - LandscapeUVArray, LandscapeComponentVertexIndicesArray, - LandscapeComponentNameArray, LandscapeLightmapValues)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 3. Set the corresponding attributes in Houdini - //-------------------------------------------------------------------------------------------------- - - // Create point attribute info containing positions. - if (!AddLandscapePositionAttribute(DisplayGeoInfo.nodeId, LandscapePositionArray)) - return false; - - // Create point attribute info containing normals. - if (!AddLandscapeNormalAttribute(DisplayGeoInfo.nodeId, LandscapeNormalArray)) - return false; - - // Create point attribute info containing UVs. - if (!AddLandscapeUVAttribute(DisplayGeoInfo.nodeId, LandscapeUVArray)) - return false; - - // Create point attribute containing landscape component vertex indices (indices of vertices within the grid - x,y). - if (!AddLandscapeComponentVertexIndicesAttribute(DisplayGeoInfo.nodeId, LandscapeComponentVertexIndicesArray)) - return false; - - // Create point attribute containing landscape component name. - if (!AddLandscapeComponentNameAttribute(DisplayGeoInfo.nodeId, LandscapeComponentNameArray)) - return false; - - // Create point attribute info containing lightmap information. - if (bExportLighting) - { - if (!AddLandscapeLightmapColorAttribute(DisplayGeoInfo.nodeId, LandscapeLightmapValues)) - return false; - } - - // Set indices if we are exporting full geometry. - if (bExportGeometryAsMesh) - { - if (!AddLandscapeMeshIndicesAndMaterialsAttribute( - DisplayGeoInfo.nodeId, - bExportMaterials, - ComponentSizeQuads, - QuadCount, - LandscapeProxy, - SelectedComponents)) - return false; - } - - // If we are marshalling material information. - if (bExportMaterials) - { - if (!AddLandscapeGlobalMaterialAttribute(DisplayGeoInfo.nodeId, LandscapeProxy)) - return false; - } - - /* - // TODO: Move this to ExtractLandscapeData() - //-------------------------------------------------------------------------------------------------- - // 4. Extract and convert all the layers - //-------------------------------------------------------------------------------------------------- - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Get the landscape X/Y Size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - return false; - - // Calc the X/Y size in points - int32 XSize = (MaxX - MinX + 1); - int32 YSize = (MaxY - MinY + 1); - if ((XSize < 2) || (YSize < 2)) - return false; - - bool MaskInitialized = false; - int32 NumLayers = LandscapeInfo->Layers.Num(); - for (int32 n = 0; n < NumLayers; n++) - { - // 1. Extract the uint8 values from the layer - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!GetLandscapeLayerData( - LandscapeInfo, n, - MinX, MinY, MaxX, MaxY, - CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - // 2. Convert unreal uint8 values to floats - // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float - HAPI_VolumeInfo CurrentLayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); - TArray CurrentLayerFloatData; - if (!ConvertLandscapeLayerDataToHeightfieldData( - CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, - CurrentLayerFloatData, CurrentLayerVolumeInfo)) - continue; - - if (!AddLandscapeLayerAttribute( - DisplayGeoInfo.nodeId, CurrentLayerFloatData, LayerName)) - continue; - } - */ - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId), false); - - // TODO: Remove me! - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); - */ - - return FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true); -} - -bool -FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( - ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId, const FString& InputNodeNameStr) -{ - if (!LandscapeProxy) - return false; - - // Export the whole landscape and its layer as a single heightfield. - - //-------------------------------------------------------------------------------------------------- - // 1. Extracting the height data - //-------------------------------------------------------------------------------------------------- - TArray HeightData; - int32 XSize, YSize; - FVector Min, Max; - if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 2. Convert the height uint16 data to float - //-------------------------------------------------------------------------------------------------- - TArray HeightfieldFloatValues; - HAPI_VolumeInfo HeightfieldVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); - FTransform LandscapeTransform = LandscapeProxy->ActorToWorld(); - FVector CenterOffset = FVector::ZeroVector; - if (!ConvertLandscapeDataToHeightfieldData( - HeightData, XSize, YSize, Min, Max, LandscapeTransform, - HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 3. Create the Heightfield Input Node - //-------------------------------------------------------------------------------------------------- - HAPI_NodeId HeightFieldId = -1; - HAPI_NodeId HeightId = -1; - HAPI_NodeId MaskId = -1; - HAPI_NodeId MergeId = -1; - if (!CreateHeightfieldInputNode(InputNodeNameStr, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 4. Set the HeightfieldData in Houdini - //-------------------------------------------------------------------------------------------------- - // Set the Height volume's data - HAPI_PartId PartId = 0; - if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) - return false; - - // Add the materials used - UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); - UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); - UPhysicalMaterial* LandscapePhysMat = LandscapeProxy->DefaultPhysMaterial; - AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(HeightId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(HeightId, PartId, LandscapeProxy, 1); - - // Add the unreal_level_path attribute - ULevel* Level = LandscapeProxy->GetLevel(); - if (Level) - { - FHoudiniEngineUtils::AddLevelPathAttribute(HeightId, PartId, Level, 1); - /* - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToVolume(HeightId, PartId, LevelPath); - */ - } - - // Commit the height volume - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), HeightId), false); - - //-------------------------------------------------------------------------------------------------- - // 5. Extract and convert all the layers - //-------------------------------------------------------------------------------------------------- - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - bool MaskInitialized = false; - int32 MergeInputIndex = 2; - int32 NumLayers = LandscapeInfo->Layers.Num(); - for (int32 n = 0; n < NumLayers; n++) - { - // 1. Extract the uint8 values from the layer - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!GetLandscapeLayerData(LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - // 2. Convert unreal uint8 values to floats - // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float - HAPI_VolumeInfo CurrentLayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); - TArray CurrentLayerFloatData; - if (!ConvertLandscapeLayerDataToHeightfieldData( - CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, - CurrentLayerFloatData, CurrentLayerVolumeInfo)) - continue; - - // We reuse the height layer's transform - CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; - - // 3. See if we need to create an input volume, or can reuse the HF's default mask volume - bool IsMask = false; - if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) - IsMask = true; - - HAPI_NodeId LayerVolumeNodeId = -1; - if (!IsMask) - { - // Current layer is not mask, so we need to create a new input volume - std::string LayerNameStr; - FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); - - FHoudiniApi::CreateHeightfieldInputVolumeNode( - FHoudiniEngine::Get().GetSession(), - HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); - } - else - { - // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node - LayerVolumeNodeId = MaskId; - } - - // Check if we have a valid id for the input volume. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) - continue; - - // 4. Set the layer/mask heighfield data in Houdini - HAPI_PartId CurrentPartId = 0; - if (!SetHeightfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) - continue; - - // Get the physical material used by that layer - UPhysicalMaterial* LayerPhysicalMat = LandscapePhysMat; - { - FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; - ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; - if (LayerInfo) - LayerPhysicalMat = LayerInfo->PhysMaterial; - } - - // Also add the material attributes to the layer volumes - AddLandscapeMaterialAttributesToVolume(LayerVolumeNodeId, PartId, LandscapeMat, LandscapeHoleMat, LayerPhysicalMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(LayerVolumeNodeId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(LayerVolumeNodeId, PartId, LandscapeProxy, 1); - - // Also add the level path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(LayerVolumeNodeId, PartId, Level, 1); - //AddLevelPathAttributeToVolume(LayerVolumeNodeId, PartId, LevelPath); - - // Commit the volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); - - if (!IsMask) - { - // We had to create a new volume for this layer, so we need to connect it to the HF's merge node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - MergeId, MergeInputIndex, LayerVolumeNodeId, 0), false); - - MergeInputIndex++; - } - else - { - MaskInitialized = true; - } - } - - // We need to have a mask layer as it is required for proper heightfield functionalities - // Setting the volume info on the mask is needed for the HF to have proper transform in H! - // If we didn't create a mask volume before, send a default one now - if (!MaskInitialized) - { - MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); - - // Add the materials used - AddLandscapeMaterialAttributesToVolume(MaskId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(MaskId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(MaskId, PartId, LandscapeProxy, 1); - - // Also add the level path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(MaskId, PartId, Level, 1); - //AddLevelPathAttributeToVolume(MaskId, PartId, LevelPath); - - // Commit the mask volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), MaskId), false); - } - - HAPI_TransformEuler HAPIObjectTransform; - FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); - //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); - LandscapeTransform.SetScale3D(FVector::OneVector); - FHoudiniEngineUtils::TranslateUnrealTransform(LandscapeTransform, HAPIObjectTransform); - HAPIObjectTransform.position[1] = 0.0f; - - HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(HeightFieldId); - FHoudiniApi::SetObjectTransform(FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform); - - // Since HF are centered but landscape aren't, we need to set the HF's center parameter - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); - - // Finally, cook the Heightfield node - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), HeightFieldId, &CookOptions), false); - */ - if(!FHoudiniEngineUtils::HapiCookNode(HeightFieldId, nullptr, true)) - return false; - - CreatedHeightfieldNodeId = HeightFieldId; - - return true; -} - -// Converts Unreal uint16 values to Houdini Float -bool -FUnrealLandscapeTranslator::ConvertLandscapeLayerDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - const FLinearColor& LayerUsageDebugColor, - TArray& LayerFloatValues, - HAPI_VolumeInfo& LayerVolumeInfo) -{ - LayerFloatValues.Empty(); - - int32 HoudiniXSize = YSize; - int32 HoudiniYSize = XSize; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - if (IntHeightData.Num() != SizeInPoints) - return false; - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to float - //-------------------------------------------------------------------------------------------------- - - // By default, values are converted from unreal [0 255] uint8 to Houdini [0 1] float - // uint8 min/max - uint8 IntMin = 0; - uint8 IntMax = UINT8_MAX; - // The range in Digits - double DigitRange = (double)UINT8_MAX; - - // By default, the values will be converted to [0, 1] - float LayerMin = 0.0f; - float LayerMax = 1.0f; - float LayerSpacing = 1.0f / DigitRange; - - // If this layer came from Houdini, its alpha value should be PI - // This indicates that we can extract additional infos stored its debug usage color - // so we can reconstruct the original source values (float) more accurately - if (LayerUsageDebugColor.A == PI) - { - // We need the ZMin / ZMax uint8 values - IntMin = IntHeightData[0]; - IntMax = IntMin; - for (int n = 0; n < IntHeightData.Num(); n++) - { - if (IntHeightData[n] < IntMin) - IntMin = IntHeightData[n]; - if (IntHeightData[n] > IntMax) - IntMax = IntHeightData[n]; - } - - DigitRange = (double)IntMax - (double)IntMin; - - // Read the original min/max and spacing stored in the debug color - LayerMin = LayerUsageDebugColor.R; - LayerMax = LayerUsageDebugColor.G; - LayerSpacing = LayerUsageDebugColor.B; - } - - // Convert the Int data to Float - LayerFloatValues.SetNumUninitialized(SizeInPoints); - - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // We need to invert X/Y when reading the value from Unreal - int32 nHoudini = nX + nY * HoudiniXSize; - int32 nUnreal = nY + nX * XSize; - - // Convert the int values to meter - // Unreal's digit value have a zero value of 32768 - double DoubleValue = ((double)IntHeightData[nUnreal] - (double)IntMin) * LayerSpacing + LayerMin; - LayerFloatValues[nHoudini] = (float)DoubleValue; - } - } - - /* - // Verifying the converted ZMin / ZMax - float FloatMin = LayerFloatValues[0]; - float FloatMax = FloatMin; - for (int32 n = 0; n < LayerFloatValues.Num(); n++) - { - if (LayerFloatValues[n] < FloatMin) - FloatMin = LayerFloatValues[n]; - if (LayerFloatValues[n] > FloatMax) - FloatMax = LayerFloatValues[n]; - } - */ - - //-------------------------------------------------------------------------------------------------- - // 2. Fill the volume info - //-------------------------------------------------------------------------------------------------- - LayerVolumeInfo.xLength = HoudiniXSize; - LayerVolumeInfo.yLength = HoudiniYSize; - LayerVolumeInfo.zLength = 1; - - LayerVolumeInfo.minX = 0; - LayerVolumeInfo.minY = 0; - LayerVolumeInfo.minZ = 0; - - LayerVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; - LayerVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; - LayerVolumeInfo.tupleSize = 1; - LayerVolumeInfo.tileSize = 1; - - LayerVolumeInfo.hasTaper = false; - LayerVolumeInfo.xTaper = 0.0; - LayerVolumeInfo.yTaper = 0.0; - - // The layer transform will have to be copied from the main heightfield's transform - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeData( - ALandscapeProxy* LandscapeProxy, - TArray& HeightData, - int32& XSize, int32& YSize, - FVector& Min, FVector& Max) -{ - if (!LandscapeProxy) - return false; - - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Get the landscape extents to get its size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - - // To handle streaming proxies correctly, get the extents via all the components, - // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. - for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) - { - Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); - } - - if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize)) - return false; - - // Get the landscape Min/Max values - // Do not use Landscape->GetActorBounds() here as instanced geo - // (due to grass layers for example) can cause it to return incorrect bounds! - FVector Origin, Extent; - GetLandscapeProxyBounds(LandscapeProxy, Origin, Extent); - - // Get the landscape Min/Max values - Min = Origin - Extent; - Max = Origin + Extent; - - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeData( - ULandscapeInfo* LandscapeInfo, - const int32& MinX, const int32& MinY, - const int32& MaxX, const int32& MaxY, - TArray& HeightData, - int32& XSize, int32& YSize) -{ - if (!LandscapeInfo) - return false; - - // Get the X/Y size in points - XSize = (MaxX - MinX + 1); - YSize = (MaxY - MinY + 1); - - if ((XSize < 2) || (YSize < 2)) - return false; - - // Extracting the uint16 values from the landscape - FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); - HeightData.AddZeroed(XSize * YSize); - LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0); - - return true; -} - - -void -FUnrealLandscapeTranslator::GetLandscapeProxyBounds( - ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents) -{ - // Iterate only on the landscape components - FBox Bounds(ForceInit); - for (const UActorComponent* ActorComponent : LandscapeProxy->GetComponents()) - { - const ULandscapeComponent* LandscapeComp = Cast(ActorComponent); - if (LandscapeComp && LandscapeComp->IsRegistered()) - Bounds += LandscapeComp->Bounds.GetBox(); - } - - // Convert the bounds to origin/offset vectors - Bounds.GetCenterAndExtents(Origin, Extents); -} - -bool -FUnrealLandscapeTranslator::ConvertLandscapeDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - FVector Min, FVector Max, - const FTransform& LandscapeTransform, - TArray& HeightfieldFloatValues, - HAPI_VolumeInfo& HeightfieldVolumeInfo, - FVector& CenterOffset) -{ - HeightfieldFloatValues.Empty(); - - int32 HoudiniXSize = YSize; - int32 HoudiniYSize = XSize; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - if (IntHeightData.Num() != SizeInPoints) - return false; - - // Use default unreal scaling for marshalling landscapes - // A lot of precision will be lost in order to keep the same transform as the landscape input - bool bUseDefaultUE4Scaling = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) - bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to float - //-------------------------------------------------------------------------------------------------- - - - // Convert the min/max values from cm to meters - Min /= 100.0; - Max /= 100.0; - - // Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0 - // To convert the uint16 values to float "metric" values, offset the int by 32768 to center it, - // then scale it - - // Spacing used to convert from uint16 to meters - double ZSpacing = 512.0 / ((double)UINT16_MAX); - ZSpacing *= ((double)LandscapeTransform.GetScale3D().Z / 100.0); - - // Center value in meters (Landscape ranges from [-255:257] meters at default scale - double ZCenterOffset = 32767; - double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f; - // Convert the Int data to Float - HeightfieldFloatValues.SetNumUninitialized(SizeInPoints); - - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // We need to invert X/Y when reading the value from Unreal - int32 nHoudini = nX + nY * HoudiniXSize; - int32 nUnreal = nY + nX * XSize; - - // Convert the int values to meter - // Unreal's digit value have a zero value of 32768 - double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset; - HeightfieldFloatValues[nHoudini] = (float)DoubleValue; - } - } - - //-------------------------------------------------------------------------------------------------- - // 2. Convert the Unreal Transform to a HAPI_transform - //-------------------------------------------------------------------------------------------------- - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - //FMemory::Memzero< HAPI_Transform >( HapiTransform ); - { - FQuat Rotation = LandscapeTransform.GetRotation(); - if (Rotation != FQuat::Identity) - { - //Swap(ObjectRotation.Y, ObjectRotation.Z); - HapiTransform.rotationQuaternion[0] = Rotation.X; - HapiTransform.rotationQuaternion[1] = Rotation.Z; - HapiTransform.rotationQuaternion[2] = Rotation.Y; - HapiTransform.rotationQuaternion[3] = -Rotation.W; - } - else - { - HapiTransform.rotationQuaternion[0] = 0; - HapiTransform.rotationQuaternion[1] = 0; - HapiTransform.rotationQuaternion[2] = 0; - HapiTransform.rotationQuaternion[3] = 1; - } - - // Heightfield are centered, landscapes are not - CenterOffset = (Max - Min) * 0.5f; - - // Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform) - //FVector Position = LandscapeTransform.GetLocation() / 100.0f; - HapiTransform.position[1] = 0.0f;//Position.X + CenterOffset.X; - HapiTransform.position[0] = 0.0f;//Position.Y + CenterOffset.Y; - HapiTransform.position[2] = 0.0f; - - FVector Scale = LandscapeTransform.GetScale3D() / 100.0f; - HapiTransform.scale[0] = Scale.X * 0.5f * HoudiniXSize; - HapiTransform.scale[1] = Scale.Y * 0.5f * HoudiniYSize; - HapiTransform.scale[2] = 0.5f; - if (bUseDefaultUE4Scaling) - HapiTransform.scale[2] *= Scale.Z; - - HapiTransform.shear[0] = 0.0f; - HapiTransform.shear[1] = 0.0f; - HapiTransform.shear[2] = 0.0f; - } - - //-------------------------------------------------------------------------------------------------- - // 3. Fill the volume info - //-------------------------------------------------------------------------------------------------- - HeightfieldVolumeInfo.xLength = HoudiniXSize; - HeightfieldVolumeInfo.yLength = HoudiniYSize; - HeightfieldVolumeInfo.zLength = 1; - - HeightfieldVolumeInfo.minX = 0; - HeightfieldVolumeInfo.minY = 0; - HeightfieldVolumeInfo.minZ = 0; - - HeightfieldVolumeInfo.transform = HapiTransform; - - HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; - HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; - HeightfieldVolumeInfo.tupleSize = 1; - HeightfieldVolumeInfo.tileSize = 1; - - HeightfieldVolumeInfo.hasTaper = false; - HeightfieldVolumeInfo.xTaper = 0.0; - HeightfieldVolumeInfo.yTaper = 0.0; - - return true; -} - -bool -FUnrealLandscapeTranslator::CreateHeightfieldInputNode( - const FString& NodeName, - const int32& XSize, - const int32& YSize, - HAPI_NodeId& HeightfieldNodeId, - HAPI_NodeId& HeightNodeId, - HAPI_NodeId& MaskNodeId, - HAPI_NodeId& MergeNodeId) -{ - // Make sure the Heightfield node doesnt already exists - if (HeightfieldNodeId != -1) - return false; - - // Convert the node's name - std::string NameStr; - FHoudiniEngineUtils::ConvertUnrealString(NodeName, NameStr); - - // Create the heigthfield node via HAPI - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightFieldInput( - FHoudiniEngine::Get().GetSession(), - -1, NameStr.c_str(), XSize, YSize, 1.0f, HAPI_HeightFieldSampling::HAPI_HEIGHTFIELD_SAMPLING_CORNER, - &HeightfieldNodeId, &HeightNodeId, &MaskNodeId, &MergeNodeId), false); - - // Cook it - return FHoudiniEngineUtils::HapiCookNode(HeightfieldNodeId, nullptr, true); - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), HeightfieldNodeId, &CookOptions), false); - - return true; - */ -} - -bool -FUnrealLandscapeTranslator::SetHeightfieldData( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - TArray& FloatValues, - const HAPI_VolumeInfo& VolumeInfo, - const FString& HeightfieldName) -{ - // Cook the node to get proper infos on it - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, &CookOptions), false); - */ - if(!FHoudiniEngineUtils::HapiCookNode(VolumeNodeId, nullptr, true)) - return false; - - // Read the geo/part/volume info from the volume node - HAPI_GeoInfo GeoInfo; - FHoudiniApi::GeoInfo_Init(&GeoInfo); - //FMemory::Memset< HAPI_GeoInfo >(GeoInfo, 0); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, &GeoInfo), false); - - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - //FMemory::Memset< HAPI_PartInfo >(PartInfo, 0); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), - GeoInfo.nodeId, PartId, &PartInfo), false); - - // Update the volume infos - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, PartInfo.id, &VolumeInfo), false); - - // Volume name - std::string NameStr; - FHoudiniEngineUtils::ConvertUnrealString(HeightfieldName, NameStr); - - // Set the Heighfield data on the volume - float * HeightData = FloatValues.GetData(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetHeightFieldData( - FHoudiniEngine::Get().GetSession(), - GeoInfo.nodeId, PartInfo.id, NameStr.c_str(), HeightData, 0, FloatValues.Num()), false); - - return true; -} - -bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - UMaterialInterface* InLandscapeMaterial, - UMaterialInterface* InLandscapeHoleMaterial, - UPhysicalMaterial* InPhysicalMaterial) -{ - if (VolumeNodeId == -1) - return false; - - // LANDSCAPE MATERIAL - if (InLandscapeMaterial && !InLandscapeMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InLandscapeMaterialString = InLandscapeMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - // HOLE MATERIAL - if (InLandscapeHoleMaterial && !InLandscapeHoleMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InLandscapeMaterialString = InLandscapeHoleMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (Result == HAPI_RESULT_SUCCESS) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_hole_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - // PHYSICAL MATERIAL - if (InPhysicalMaterial && !InPhysicalMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InPhysMatlString = InPhysicalMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (Result == HAPI_RESULT_SUCCESS) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InPhysMatlString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_physical_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - return true; -} - -/* -bool -FUnrealLandscapeTranslator::AddLevelPathAttributeToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - const FString& LevelPath) -{ - if (VolumeNodeId == -1) - return false; - - // LANDSCAPE MATERIAL - if (LevelPath.IsEmpty()) - return false; - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = 1; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to cont char * - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray LevelPathArr; - LevelPathArr.Add(LevelPathCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - LevelPathArr.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} -*/ - -bool -FUnrealLandscapeTranslator::GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, const int32& LayerIndex, - TArray& LayerData, FLinearColor& LayerUsageDebugColor, - FString& LayerName) -{ - if (!LandscapeInfo) - return false; - - // Get the landscape X/Y Size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - return false; - - if (!GetLandscapeLayerData( - LandscapeInfo, LayerIndex, - MinX, MinY, MaxX, MaxY, - LayerData, LayerUsageDebugColor, LayerName)) - return false; - - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - const int32& MinX, const int32& MinY, - const int32& MaxX, const int32& MaxY, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName) -{ - if (!LandscapeInfo) - return false; - - if (!LandscapeInfo->Layers.IsValidIndex(LayerIndex)) - return false; - - FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[LayerIndex]; - ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; - if (!LayerInfo) - return false; - - // Calc the X/Y size in points - int32 XSize = (MaxX - MinX + 1); - int32 YSize = (MaxY - MinY + 1); - if ((XSize < 2) || (YSize < 2)) - return false; - - // extracting the uint8 values from the layer - FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); - LayerData.AddZeroed(XSize * YSize); - LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, LayerData.GetData(), 0); - - LayerUsageDebugColor = LayerInfo->LayerUsageDebugColor; - - LayerName = LayersSetting.GetLayerName().ToString(); - - return true; -} - -bool -FUnrealLandscapeTranslator::InitDefaultHeightfieldMask( - const HAPI_VolumeInfo& HeightVolumeInfo, - const HAPI_NodeId& MaskVolumeNodeId) -{ - // We need to have a mask layer as it is required for proper heightfield functionalities - - // Creating an array filled with 0.0 - TArray< float > MaskFloatData; - MaskFloatData.Init(0.0f, HeightVolumeInfo.xLength * HeightVolumeInfo.yLength); - - // Creating the volume infos - HAPI_VolumeInfo MaskVolumeInfo = HeightVolumeInfo; - - // Set the heighfield data in Houdini - FString MaskName = TEXT("mask"); - HAPI_PartId PartId = 0; - if (!SetHeightfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) - return false; - - return true; -} - -bool -FUnrealLandscapeTranslator::DestroyLandscapeAssetNode(HAPI_NodeId& ConnectedAssetId, TArray& CreatedInputAssetIds) -{ - HAPI_AssetInfo NodeAssetInfo; - FHoudiniApi::AssetInfo_Init(&NodeAssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeAssetInfo), false); - - FHoudiniEngineString AssetOpName(NodeAssetInfo.fullOpNameSH); - FString OpName; - if (!AssetOpName.ToFString(OpName)) - return false; - - if (!OpName.Contains(TEXT("xform"))) - { - // Not a transform node, so not a Heightfield - // We just need to destroy the landscape asset node - return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); - } - - // The landscape was marshalled as a heightfield, so we need to destroy and disconnect - // the volvis nodes, all the merge node's input (each merge input is a volume for one - // of the layer/mask of the landscape ) - - // Query the volvis node id - // The volvis node is the fist input of the xform node - HAPI_NodeId VolvisNodeId = -1; - FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ConnectedAssetId, 0, &VolvisNodeId); - - // First, destroy the merge node and its inputs - // The merge node is in the first input of the volvis node - HAPI_NodeId MergeNodeId = -1; - FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - VolvisNodeId, 0, &MergeNodeId); - - if (MergeNodeId != -1) - { - // Get the merge node info - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), MergeNodeId, &NodeInfo), false); - - for (int32 n = 0; n < NodeInfo.inputCount; n++) - { - // Get the Input node ID from the host ID - HAPI_NodeId InputNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - MergeNodeId, n, &InputNodeId)) - break; - - if (InputNodeId == -1) - break; - - // Disconnect and Destroy that input - FHoudiniEngineUtils::HapiDisconnectAsset(MergeNodeId, n); - FHoudiniEngineUtils::DestroyHoudiniAsset(InputNodeId); - } - } - - // Second step, destroy all the volumes GEO assets - for (HAPI_NodeId AssetNodeId : CreatedInputAssetIds) - { - FHoudiniEngineUtils::DestroyHoudiniAsset(AssetNodeId); - } - CreatedInputAssetIds.Empty(); - - // Finally disconnect and destroy the xform, volvis and merge nodes, then destroy them - FHoudiniEngineUtils::HapiDisconnectAsset(ConnectedAssetId, 0); - FHoudiniEngineUtils::HapiDisconnectAsset(VolvisNodeId, 0); - FHoudiniEngineUtils::DestroyHoudiniAsset(MergeNodeId); - FHoudiniEngineUtils::DestroyHoudiniAsset(VolvisNodeId); - - return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); -} - - -bool -FUnrealLandscapeTranslator::ExtractLandscapeData( - ALandscapeProxy * LandscapeProxy, TSet& SelectedComponents, - const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, - TArray& LandscapePositionArray, - TArray& LandscapeNormalArray, - TArray& LandscapeUVArray, - TArray& LandscapeComponentVertexIndicesArray, - TArray& LandscapeComponentNameArray, - TArray& LandscapeLightmapValues) -{ - if (!LandscapeProxy) - return false; - - if (SelectedComponents.Num() < 1) - return false; - - // Get runtime settings. - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Calc all the needed sizes - int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; - float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; - - int32 NumComponents = SelectedComponents.Num(); - bool bExportOnlySelected = NumComponents != LandscapeProxy->LandscapeComponents.Num(); - - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - int32 VertexCount = NumComponents * VertexCountPerComponent; - if (!VertexCount) - return false; - - // Initialize the data arrays - LandscapePositionArray.SetNumUninitialized(VertexCount); - LandscapeNormalArray.SetNumUninitialized(VertexCount); - LandscapeUVArray.SetNumUninitialized(VertexCount); - LandscapeComponentNameArray.SetNumUninitialized(VertexCount); - LandscapeComponentVertexIndicesArray.SetNumUninitialized(VertexCount); - if (bExportLighting) - LandscapeLightmapValues.SetNumUninitialized(VertexCount); - - //----------------------------------------------------------------------------------------------------------------- - // EXTRACT THE LANDSCAPE DATA - //----------------------------------------------------------------------------------------------------------------- - FIntPoint IntPointMax = FIntPoint::ZeroValue; - - int32 AllPositionsIdx = 0; - for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) - { - ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; - if (bExportOnlySelected && !SelectedComponents.Contains(LandscapeComponent)) - continue; - - TArray64< uint8 > LightmapMipData; - int32 LightmapMipSizeX = 0; - int32 LightmapMipSizeY = 0; - - // See if we need to export lighting information. - if (bExportLighting) - { - const FMeshMapBuildData* MapBuildData = LandscapeComponent->GetMeshMapBuildData(); - FLightMap2D* LightMap2D = MapBuildData && MapBuildData->LightMap ? MapBuildData->LightMap->GetLightMap2D() : nullptr; - if (LightMap2D && LightMap2D->IsValid(0)) - { - UTexture2D * TextureLightmap = LightMap2D->GetTexture(0); - if (TextureLightmap) - { - if (TextureLightmap->Source.GetMipData(LightmapMipData, 0, 0, 0, nullptr)) - { - LightmapMipSizeX = TextureLightmap->Source.GetSizeX(); - LightmapMipSizeY = TextureLightmap->Source.GetSizeY(); - } - else - { - LightmapMipData.Empty(); - } - } - } - } - - // Construct landscape component data interface to access raw data. - FLandscapeComponentDataInterface CDI(LandscapeComponent, LandscapeProxy->ExportLOD); - - // Get name of this landscape component. - const char * LandscapeComponentNameStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->GetName()); - for (int32 VertexIdx = 0; VertexIdx < VertexCountPerComponent; VertexIdx++) - { - int32 VertX = 0; - int32 VertY = 0; - CDI.VertexIndexToXY(VertexIdx, VertX, VertY); - - // Get position. - FVector PositionVector = CDI.GetWorldVertex(VertX, VertY); - - // Get normal / tangent / binormal. - FVector Normal = FVector::ZeroVector; - FVector TangentX = FVector::ZeroVector; - FVector TangentY = FVector::ZeroVector; - CDI.GetLocalTangentVectors(VertX, VertY, TangentX, TangentY, Normal); - - // Export UVs. - FVector TextureUV = FVector::ZeroVector; - if (bExportTileUVs) - { - // We want to export uvs per tile. - TextureUV = FVector(VertX, VertY, 0.0f); - - // If we need to normalize UV space. - if (bExportNormalizedUVs) - TextureUV /= ComponentSizeQuads; - } - else - { - // We want to export global uvs (default). - FIntPoint IntPoint = LandscapeComponent->GetSectionBase(); - TextureUV = FVector(VertX * ScaleFactor + IntPoint.X, VertY * ScaleFactor + IntPoint.Y, 0.0f); - - // Keep track of max offset. - IntPointMax = IntPointMax.ComponentMax(IntPoint); - } - - if (bExportLighting) - { - FLinearColor VertexLightmapColor(0.0f, 0.0f, 0.0f, 1.0f); - if (LightmapMipData.Num() > 0) - { - FVector2D UVCoord(VertX, VertY); - UVCoord /= (ComponentSizeQuads + 1); - - FColor LightmapColorRaw = PickVertexColorFromTextureMip( - LightmapMipData.GetData(), UVCoord, LightmapMipSizeX, LightmapMipSizeY); - - VertexLightmapColor = LightmapColorRaw.ReinterpretAsLinear(); - } - - LandscapeLightmapValues[AllPositionsIdx] = VertexLightmapColor; - } - - // Retrieve component transform. - const FTransform & ComponentTransform = LandscapeComponent->GetComponentTransform(); - - // Retrieve component scale. - const FVector & ScaleVector = ComponentTransform.GetScale3D(); - - // Perform normalization. - Normal /= ScaleVector; - Normal.Normalize(); - - TangentX /= ScaleVector; - TangentX.Normalize(); - - TangentY /= ScaleVector; - TangentY.Normalize(); - - // Perform position scaling. - FVector PositionTransformed = PositionVector / HAPI_UNREAL_SCALE_FACTOR_POSITION; - LandscapePositionArray[AllPositionsIdx].X = PositionTransformed.X; - LandscapePositionArray[AllPositionsIdx].Y = PositionTransformed.Z; - LandscapePositionArray[AllPositionsIdx].Z = PositionTransformed.Y; - - Swap(Normal.Y, Normal.Z); - - // Store landscape component name for this point. - LandscapeComponentNameArray[AllPositionsIdx] = LandscapeComponentNameStr; - - // Store vertex index (x,y) for this point. - LandscapeComponentVertexIndicesArray[AllPositionsIdx].X = VertX; - LandscapeComponentVertexIndicesArray[AllPositionsIdx].Y = VertY; - - // Store point normal. - LandscapeNormalArray[AllPositionsIdx] = Normal; - - // Store uv. - LandscapeUVArray[AllPositionsIdx] = TextureUV; - - AllPositionsIdx++; - } - - // Free the memory allocated for LandscapeComponentNameStr - FHoudiniEngineUtils::FreeRawStringMemory(LandscapeComponentNameStr); - } - - // If we need to normalize UV space and we are doing global UVs. - if (!bExportTileUVs && bExportNormalizedUVs) - { - IntPointMax += FIntPoint(ComponentSizeQuads, ComponentSizeQuads); - IntPointMax = IntPointMax.ComponentMax(FIntPoint(1, 1)); - - for (int32 UVIdx = 0; UVIdx < VertexCount; ++UVIdx) - { - FVector & PositionUV = LandscapeUVArray[UVIdx]; - PositionUV.X /= IntPointMax.X; - PositionUV.Y /= IntPointMax.Y; - } - } - - return true; -} - -FColor -FUnrealLandscapeTranslator::PickVertexColorFromTextureMip( - const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight) -{ - check(MipBytes); - - FColor ResultColor(0, 0, 0, 255); - - if (UVCoord.X >= 0.0f && UVCoord.X < 1.0f && UVCoord.Y >= 0.0f && UVCoord.Y < 1.0f) - { - const int32 X = MipWidth * UVCoord.X; - const int32 Y = MipHeight * UVCoord.Y; - - const int32 Index = ((Y * MipWidth) + X) * 4; - - ResultColor.B = MipBytes[Index + 0]; - ResultColor.G = MipBytes[Index + 1]; - ResultColor.R = MipBytes[Index + 2]; - ResultColor.A = MipBytes[Index + 3]; - } - - return ResultColor; -} - -bool -FUnrealLandscapeTranslator::AddLandscapePositionAttribute(const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray) -{ - int32 VertexCount = LandscapePositionArray.Num(); - if (VertexCount < 3) - return false; - - // Create point attribute info containing positions. - HAPI_AttributeInfo AttributeInfoPointPosition; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointPosition); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointPosition ); - AttributeInfoPointPosition.count = VertexCount; - AttributeInfoPointPosition.tupleSize = 3; - AttributeInfoPointPosition.exists = true; - AttributeInfoPointPosition.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointPosition.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointPosition.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition), false); - - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition, - (const float *)LandscapePositionArray.GetData(), - 0, AttributeInfoPointPosition.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeNormalAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeNormalArray) -{ - int32 VertexCount = LandscapeNormalArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointNormal; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointNormal); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointNormal ); - AttributeInfoPointNormal.count = VertexCount; - AttributeInfoPointNormal.tupleSize = 3; - AttributeInfoPointNormal.exists = true; - AttributeInfoPointNormal.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointNormal.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointNormal.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal, - (const float *)LandscapeNormalArray.GetData(), 0, VertexCount), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeUVAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeUVArray) -{ - int32 VertexCount = LandscapeUVArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointUV; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointUV); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointUV ); - AttributeInfoPointUV.count = VertexCount; - AttributeInfoPointUV.tupleSize = 3; - AttributeInfoPointUV.exists = true; - AttributeInfoPointUV.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointUV.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointUV.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV, - (const float *)LandscapeUVArray.GetData(), 0, AttributeInfoPointUV.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeComponentVertexIndicesAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentVertexIndicesArray) -{ - int32 VertexCount = LandscapeComponentVertexIndicesArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointLandscapeComponentVertexIndices; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentVertexIndices); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentVertexIndices ); - AttributeInfoPointLandscapeComponentVertexIndices.count = VertexCount; - AttributeInfoPointLandscapeComponentVertexIndices.tupleSize = 2; - AttributeInfoPointLandscapeComponentVertexIndices.exists = true; - AttributeInfoPointLandscapeComponentVertexIndices.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLandscapeComponentVertexIndices.storage = HAPI_STORAGETYPE_INT; - AttributeInfoPointLandscapeComponentVertexIndices.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, - &AttributeInfoPointLandscapeComponentVertexIndices), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, - &AttributeInfoPointLandscapeComponentVertexIndices, - (const int *)LandscapeComponentVertexIndicesArray.GetData(), 0, - AttributeInfoPointLandscapeComponentVertexIndices.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeComponentNameAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray) -{ - int32 VertexCount = LandscapeComponentNameArray.Num(); - if (VertexCount < 3) - return false; - - // Create point attribute containing landscape component name. - HAPI_AttributeInfo AttributeInfoPointLandscapeComponentNames; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentNames); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentNames ); - AttributeInfoPointLandscapeComponentNames.count = VertexCount; - AttributeInfoPointLandscapeComponentNames.tupleSize = 1; - AttributeInfoPointLandscapeComponentNames.exists = true; - AttributeInfoPointLandscapeComponentNames.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLandscapeComponentNames.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPointLandscapeComponentNames.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, - &AttributeInfoPointLandscapeComponentNames), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, - &AttributeInfoPointLandscapeComponentNames, - (const char **)LandscapeComponentNameArray.GetData(), - 0, AttributeInfoPointLandscapeComponentNames.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeLightmapColorAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues) -{ - int32 VertexCount = LandscapeLightmapValues.Num(); - - HAPI_AttributeInfo AttributeInfoPointLightmapColor; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLightmapColor); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLightmapColor ); - AttributeInfoPointLightmapColor.count = VertexCount; - AttributeInfoPointLightmapColor.tupleSize = 4; - AttributeInfoPointLightmapColor.exists = true; - AttributeInfoPointLightmapColor.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLightmapColor.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointLightmapColor.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor, - (const float *)LandscapeLightmapValues.GetData(), 0, - AttributeInfoPointLightmapColor.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeMeshIndicesAndMaterialsAttribute( - const HAPI_NodeId& NodeId, const bool& bExportMaterials, - const int32& ComponentSizeQuads, const int32& QuadCount, - ALandscapeProxy * LandscapeProxy, - const TSet< ULandscapeComponent * >& SelectedComponents) -{ - if (!LandscapeProxy) - return false; - - // Compute number of necessary indices. - int32 IndexCount = QuadCount * 4; - if (IndexCount < 0) - return false; - - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - - // Array holding indices data. - TArray LandscapeIndices; - LandscapeIndices.SetNumUninitialized(IndexCount); - - // Allocate space for face names. - // The LandscapeMaterial and HoleMaterial per point - TArray FaceMaterials; - TArray FaceHoleMaterials; - FaceMaterials.SetNumUninitialized(QuadCount); - FaceHoleMaterials.SetNumUninitialized(QuadCount); - - int32 VertIdx = 0; - int32 QuadIdx = 0; - - const char * MaterialRawStr = nullptr; - const char * MaterialHoleRawStr = nullptr; - - // Lambda for freeing the memory allocated by ExtractRawString and returning - auto FreeMemoryReturn = [&MaterialRawStr, &MaterialHoleRawStr](const bool& bReturn) - { - FHoudiniEngineUtils::FreeRawStringMemory(MaterialRawStr); - FHoudiniEngineUtils::FreeRawStringMemory(MaterialHoleRawStr); - - return bReturn; - }; - - const int32 QuadComponentCount = ComponentSizeQuads + 1; - for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) - { - ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; - if (!SelectedComponents.Contains(LandscapeComponent)) - continue; - - if (bExportMaterials) - { - // If component has an override material, we need to get the raw name (if exporting materials). - if (LandscapeComponent->OverrideMaterial) - { - MaterialRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideMaterial->GetName()); - } - - // If component has an override hole material, we need to get the raw name (if exporting materials). - if (LandscapeComponent->OverrideHoleMaterial) - { - MaterialHoleRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideHoleMaterial->GetName()); - } - } - - int32 BaseVertIndex = ComponentIdx * VertexCountPerComponent; - for (int32 YIdx = 0; YIdx < ComponentSizeQuads; YIdx++) - { - for (int32 XIdx = 0; XIdx < ComponentSizeQuads; XIdx++) - { - LandscapeIndices[VertIdx + 0] = BaseVertIndex + (XIdx + 0) + (YIdx + 0) * QuadComponentCount; - LandscapeIndices[VertIdx + 1] = BaseVertIndex + (XIdx + 1) + (YIdx + 0) * QuadComponentCount; - LandscapeIndices[VertIdx + 2] = BaseVertIndex + (XIdx + 1) + (YIdx + 1) * QuadComponentCount; - LandscapeIndices[VertIdx + 3] = BaseVertIndex + (XIdx + 0) + (YIdx + 1) * QuadComponentCount; - - // Store override materials (if exporting materials). - if (bExportMaterials) - { - FaceMaterials[QuadIdx] = MaterialRawStr; - FaceHoleMaterials[QuadIdx] = MaterialHoleRawStr; - } - - VertIdx += 4; - QuadIdx++; - } - } - } - - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, LandscapeIndices.GetData(), 0, LandscapeIndices.Num()), - FreeMemoryReturn(false)); - - // We need to generate array of face counts. - TArray LandscapeFaces; - LandscapeFaces.Init(4, QuadCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, LandscapeFaces.GetData(), 0, LandscapeFaces.Num()), - FreeMemoryReturn(false)); - - if (bExportMaterials) - { - if (!FaceMaterials.Contains(nullptr)) - { - // Marshall in override primitive material names. - HAPI_AttributeInfo AttributeInfoPrimitiveMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterial ); - AttributeInfoPrimitiveMaterial.count = FaceMaterials.Num(); - AttributeInfoPrimitiveMaterial.tupleSize = 1; - AttributeInfoPrimitiveMaterial.exists = true; - AttributeInfoPrimitiveMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoPrimitiveMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPrimitiveMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial, - (const char **)FaceMaterials.GetData(), 0, AttributeInfoPrimitiveMaterial.count), - FreeMemoryReturn(false)); - } - - if (!FaceHoleMaterials.Contains(nullptr)) - { - // Marshall in override primitive material hole names. - HAPI_AttributeInfo AttributeInfoPrimitiveMaterialHole; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterialHole); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterialHole ); - AttributeInfoPrimitiveMaterialHole.count = FaceHoleMaterials.Num(); - AttributeInfoPrimitiveMaterialHole.tupleSize = 1; - AttributeInfoPrimitiveMaterialHole.exists = true; - AttributeInfoPrimitiveMaterialHole.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoPrimitiveMaterialHole.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPrimitiveMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoPrimitiveMaterialHole), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoPrimitiveMaterialHole, (const char **)FaceHoleMaterials.GetData(), 0, - AttributeInfoPrimitiveMaterialHole.count), - FreeMemoryReturn(false)); - } - } - - // Free the memory and return true - return FreeMemoryReturn(true); -} - -bool -FUnrealLandscapeTranslator::AddLandscapeGlobalMaterialAttribute( - const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy) -{ - if (!LandscapeProxy) - return false; - - // If there's a global landscape material, we marshall it as detail. - UMaterialInterface * MaterialInterface = LandscapeProxy->GetLandscapeMaterial(); - const char * MaterialNameStr = ""; - if (MaterialInterface) - { - FString FullMaterialName = MaterialInterface->GetPathName(); - MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); - } - - HAPI_AttributeInfo AttributeInfoDetailMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterial ); - AttributeInfoDetailMaterial.count = 1; - AttributeInfoDetailMaterial.tupleSize = 1; - AttributeInfoDetailMaterial.exists = true; - AttributeInfoDetailMaterial.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoDetailMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoDetailMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial, - (const char**)&MaterialNameStr, 0, AttributeInfoDetailMaterial.count), false); - - // If there's a global landscape hole material, we marshall it as detail. - UMaterialInterface * HoleMaterialInterface = LandscapeProxy->GetLandscapeHoleMaterial(); - const char * HoleMaterialNameStr = ""; - if (HoleMaterialInterface) - { - FString FullMaterialName = HoleMaterialInterface->GetPathName(); - MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); - } - - HAPI_AttributeInfo AttributeInfoDetailMaterialHole; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterialHole); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterialHole ); - AttributeInfoDetailMaterialHole.count = 1; - AttributeInfoDetailMaterialHole.tupleSize = 1; - AttributeInfoDetailMaterialHole.exists = true; - AttributeInfoDetailMaterialHole.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoDetailMaterialHole.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoDetailMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoDetailMaterialHole), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoDetailMaterialHole, (const char **)&HoleMaterialNameStr, 0, - AttributeInfoDetailMaterialHole.count), false); - - return true; -} - - -bool -FUnrealLandscapeTranslator::AddLandscapeLayerAttribute( - const HAPI_NodeId& NodeId, const TArray& LandscapeLayerArray, const FString& LayerName) -{ - int32 VertexCount = LandscapeLayerArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoLayer; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLayer); - AttributeInfoLayer.count = VertexCount; - AttributeInfoLayer.tupleSize = 1; - AttributeInfoLayer.exists = true; - AttributeInfoLayer.owner = HAPI_ATTROWNER_POINT; - AttributeInfoLayer.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLayer.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, - TCHAR_TO_ANSI(*LayerName), - &AttributeInfoLayer), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, - TCHAR_TO_ANSI(*LayerName), - &AttributeInfoLayer, - (const float *)LandscapeLayerArray.GetData(), - 0, AttributeInfoLayer.count), false); - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "UnrealLandscapeTranslator.h" +#include "HoudiniGeoPartObject.h" + +#include "Landscape.h" +#include "LandscapeDataAccess.h" +#include "LandscapeEdit.h" +#include "LightMap.h" +#include "Engine/MapBuildDataRegistry.h" +#include "PhysicalMaterials/PhysicalMaterial.h" + + +bool +FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( + ALandscapeProxy* LandscapeProxy, + HAPI_NodeId& CreatedNodeId, + const FString& InputNodeNameString, + const bool& bExportGeometryAsMesh, + const bool& bExportTileUVs, + const bool bExportNormalizedUVs, + const bool bExportLighting, + const bool bExportMaterials ) +{ + //-------------------------------------------------------------------------------------------------- + // 1. Create an input node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId InputNodeId = -1; + // Create the curve SOP Node + std::string NodeNameRawString; + FHoudiniEngineUtils::ConvertUnrealString(InputNodeNameString, NodeNameRawString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + return false; + + // We now have a valid id. + CreatedNodeId = InputNodeId; + + if(!FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true)) + return false; + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); + */ + //-------------------------------------------------------------------------------------------------- + // 2. Set the part info + //-------------------------------------------------------------------------------------------------- + int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; + float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; + + //int32 NumComponents = bExportOnlySelected ? SelectedComponents.Num() : LandscapeProxy->LandscapeComponents.Num(); + int32 NumComponents = LandscapeProxy->LandscapeComponents.Num(); + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + int32 VertexCount = NumComponents * VertexCountPerComponent; + if (!VertexCount) + return false; + + int32 TriangleCount = NumComponents * FMath::Square(ComponentSizeQuads) * 2; + int32 QuadCount = NumComponents * FMath::Square(ComponentSizeQuads); + int32 IndexCount = QuadCount * 4; + + // Create part info + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + //FMemory::Memzero< HAPI_PartInfo >(Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = VertexCount; + Part.type = HAPI_PARTTYPE_MESH; + + // If we are exporting to a mesh, we need vertices and faces + if (bExportGeometryAsMesh) + { + Part.vertexCount = IndexCount; + Part.faceCount = QuadCount; + } + + // Set the part infos + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, &DisplayGeoInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part), false); + + //-------------------------------------------------------------------------------------------------- + // 3. Extract the landscape data + //-------------------------------------------------------------------------------------------------- + // Array for the position data + TArray LandscapePositionArray; + // Array for the normals + TArray LandscapeNormalArray; + // Array for the UVs + TArray LandscapeUVArray; + // Array for the vertex index of each point in its component + TArray LandscapeComponentVertexIndicesArray; + // Array for the tile names per point + TArray LandscapeComponentNameArray; + // Array for the lightmap values + TArray LandscapeLightmapValues; + // Selected components set to all components in current landscape proxy + TSet SelectedComponents; + SelectedComponents.Append(LandscapeProxy->LandscapeComponents); + + // Extract all the data from the landscape to the arrays + if (!ExtractLandscapeData( + LandscapeProxy, SelectedComponents, + bExportLighting, bExportTileUVs, bExportNormalizedUVs, + LandscapePositionArray, LandscapeNormalArray, + LandscapeUVArray, LandscapeComponentVertexIndicesArray, + LandscapeComponentNameArray, LandscapeLightmapValues)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Set the corresponding attributes in Houdini + //-------------------------------------------------------------------------------------------------- + + // Create point attribute info containing positions. + if (!AddLandscapePositionAttribute(DisplayGeoInfo.nodeId, LandscapePositionArray)) + return false; + + // Create point attribute info containing normals. + if (!AddLandscapeNormalAttribute(DisplayGeoInfo.nodeId, LandscapeNormalArray)) + return false; + + // Create point attribute info containing UVs. + if (!AddLandscapeUVAttribute(DisplayGeoInfo.nodeId, LandscapeUVArray)) + return false; + + // Create point attribute containing landscape component vertex indices (indices of vertices within the grid - x,y). + if (!AddLandscapeComponentVertexIndicesAttribute(DisplayGeoInfo.nodeId, LandscapeComponentVertexIndicesArray)) + return false; + + // Create point attribute containing landscape component name. + if (!AddLandscapeComponentNameAttribute(DisplayGeoInfo.nodeId, LandscapeComponentNameArray)) + return false; + + // Create point attribute info containing lightmap information. + if (bExportLighting) + { + if (!AddLandscapeLightmapColorAttribute(DisplayGeoInfo.nodeId, LandscapeLightmapValues)) + return false; + } + + // Set indices if we are exporting full geometry. + if (bExportGeometryAsMesh) + { + if (!AddLandscapeMeshIndicesAndMaterialsAttribute( + DisplayGeoInfo.nodeId, + bExportMaterials, + ComponentSizeQuads, + QuadCount, + LandscapeProxy, + SelectedComponents)) + return false; + } + + // If we are marshalling material information. + if (bExportMaterials) + { + if (!AddLandscapeGlobalMaterialAttribute(DisplayGeoInfo.nodeId, LandscapeProxy)) + return false; + } + + /* + // TODO: Move this to ExtractLandscapeData() + //-------------------------------------------------------------------------------------------------- + // 4. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Get the landscape X/Y Size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + return false; + + // Calc the X/Y size in points + int32 XSize = (MaxX - MinX + 1); + int32 YSize = (MaxY - MinY + 1); + if ((XSize < 2) || (YSize < 2)) + return false; + + bool MaskInitialized = false; + int32 NumLayers = LandscapeInfo->Layers.Num(); + for (int32 n = 0; n < NumLayers; n++) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!GetLandscapeLayerData( + LandscapeInfo, n, + MinX, MinY, MaxX, MaxY, + CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray CurrentLayerFloatData; + if (!ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo)) + continue; + + if (!AddLandscapeLayerAttribute( + DisplayGeoInfo.nodeId, CurrentLayerFloatData, LayerName)) + continue; + } + */ + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId), false); + + // TODO: Remove me! + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); + */ + + return FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true); +} + +bool +FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( + ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId, const FString& InputNodeNameStr) +{ + if (!LandscapeProxy) + return false; + + // Export the whole landscape and its layer as a single heightfield. + FString NodeName = InputNodeNameStr + TEXT("_") + LandscapeProxy->GetName(); + + //-------------------------------------------------------------------------------------------------- + // 1. Extracting the height data + //-------------------------------------------------------------------------------------------------- + TArray HeightData; + int32 XSize, YSize; + FVector Min, Max; + if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + HAPI_VolumeInfo HeightfieldVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + //FTransform LandscapeTransform = LandscapeProxy->LandscapeActorToWorld();// LandscapeProxy->ActorToWorld(); + + // Get the actual transform of this proxy, not the landscape actor's transform! + FTransform LandscapeTM = LandscapeProxy->LandscapeActorToWorld(); + FTransform ProxyRelativeTM(FVector(LandscapeProxy->LandscapeSectionOffset)); + FTransform LandscapeTransform = ProxyRelativeTM * LandscapeTM; + + FVector CenterOffset = FVector::ZeroVector; + if (!ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId HeightFieldId = -1; + HAPI_NodeId HeightId = -1; + HAPI_NodeId MaskId = -1; + HAPI_NodeId MergeId = -1; + if (!CreateHeightfieldInputNode(NodeName, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + HAPI_PartId PartId = 0; + if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) + return false; + + // Apply attributes to the heightfield + ApplyAttributesToHeightfieldNode(HeightId, PartId, LandscapeProxy); + + // Commit the height volume + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), HeightId), false); + + //-------------------------------------------------------------------------------------------------- + // 5. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + bool MaskInitialized = false; + int32 MergeInputIndex = 2; + + auto MergeInputFn = [&MergeInputIndex] (const HAPI_NodeId MergeId, const HAPI_NodeId NodeId) -> HAPI_Result + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HAPI_Result Result = FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex, NodeId, 0); + + if (Result == HAPI_RESULT_SUCCESS) + { + MergeInputIndex++; + } + return Result; + }; + + int32 NumLayers = LandscapeInfo->Layers.Num(); + for (int32 n = 0; n < NumLayers; n++) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!GetLandscapeLayerData(LandscapeProxy, LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray CurrentLayerFloatData; + if (!ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo)) + continue; + + // We reuse the height layer's transform + CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; + + // 3. See if we need to create an input volume, or can reuse the HF's default mask volume + bool IsMask = false; + if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) + IsMask = true; + + HAPI_NodeId LayerVolumeNodeId = -1; + if (!IsMask) + { + // Current layer is not mask, so we need to create a new input volume + std::string LayerNameStr; + FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); + } + else + { + // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node + LayerVolumeNodeId = MaskId; + } + + // Check if we have a valid id for the input volume. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) + continue; + + // 4. Set the layer/mask heighfield data in Houdini + HAPI_PartId CurrentPartId = 0; + if (!SetHeightfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) + continue; + + // Get the physical material used by that layer + UPhysicalMaterial* LayerPhysicalMat = LandscapeProxy->DefaultPhysMaterial; + { + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (LayerInfo) + LayerPhysicalMat = LayerInfo->PhysMaterial; + } + + // Apply attributes to the heightfield input node + ApplyAttributesToHeightfieldNode(LayerVolumeNodeId, PartId, LandscapeProxy); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); + + if (!IsMask) + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN(MergeInputFn(MergeId, LayerVolumeNodeId), false); + } + else + { + MaskInitialized = true; + } + } + + // We need to have a mask layer as it is required for proper heightfield functionalities + // Setting the volume info on the mask is needed for the HF to have proper transform in H! + // If we didn't create a mask volume before, send a default one now + if (!MaskInitialized) + { + MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); + + ApplyAttributesToHeightfieldNode(MaskId, PartId, LandscapeProxy); + + // Commit the mask volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), MaskId), false); + } + + // We need a valid landscape actor to get the edit layers + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if(IsValid(Landscape)) + { + //-------------------------------------------------------------------------------------------------- + // Create heightfield input for each editable landscape layer + //-------------------------------------------------------------------------------------------------- + HAPI_VolumeInfo LayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + + for(FLandscapeLayer& Layer : Landscape->LandscapeLayers) + { + const FString LayerVolumeName = FString::Format(TEXT("landscapelayer_{0}"), {Layer.Name.ToString()}); + + HAPI_NodeId LandscapeLayerNodeId = -1; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape] Creating input node for editable landscape layer: %s"), *LayerVolumeName); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, + &LandscapeLayerNodeId, + TCHAR_TO_UTF8(*LayerVolumeName), + XSize, YSize, + 1.f + ), false); + + // Create a volume visualization node + const FString VisualizationName = FString::Format(TEXT("visualization_{0}"), {Layer.Name.ToString()}); + HAPI_NodeId VisualizationNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, + "volumevisualization", + TCHAR_TO_UTF8(*VisualizationName), + false, + &VisualizationNodeId + ), false); + + // Set Visualization Mode to Height Field + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, + "vismode", + 0, 2 + ), false); + + // Set Density Field to '*'. + HAPI_ParmId DensityFieldParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, + "densityfield", + &DensityFieldParmId + ), false); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, + "*", + DensityFieldParmId, 0 + ), false); + + // Create a visibility node + const FString VisibilityName = FString::Format(TEXT("visibility_{0}"), {Layer.Name.ToString()}); + HAPI_NodeId VisibilityNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, + "visibility", + TCHAR_TO_UTF8(*VisibilityName), + false, + &VisibilityNodeId + ), false); + + // Connect landscape layer to visualization + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, 0, LandscapeLayerNodeId, 0), false); + + // Connect visualization to visibility + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + VisibilityNodeId, 0, VisualizationNodeId, 0), false); + + // Connect the visibility node to the merge input + HOUDINI_CHECK_ERROR_RETURN(MergeInputFn(MergeId, VisibilityNodeId), false); + + FScopedSetLandscapeEditingLayer Scope(Landscape, Layer.Guid ); // Scope landscape access to the current layer + + TArray LayerHeightData; + TArray LayerHeightFloatData; + //-------------------------------------------------------------------------------------------------- + // Extracting height data + //-------------------------------------------------------------------------------------------------- + if (!GetLandscapeData(LandscapeProxy, LayerHeightData, XSize, YSize, Min, Max)) + return false; + + //-------------------------------------------------------------------------------------------------- + // Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + if (!ConvertLandscapeDataToHeightfieldData( + LayerHeightData, XSize, YSize, Min, Max, LandscapeTransform, + LayerHeightFloatData, LayerVolumeInfo, CenterOffset)) + return false; + + HAPI_PartId LayerPartId = 0; + SetHeightfieldData(LandscapeLayerNodeId, LayerPartId, LayerHeightFloatData, LayerVolumeInfo, LayerVolumeName); + + // Apply attributes to the heightfield input node + ApplyAttributesToHeightfieldNode(LandscapeLayerNodeId, 0, LandscapeProxy); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LandscapeLayerNodeId), false); + } + } + + HAPI_TransformEuler HAPIObjectTransform; + FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); + LandscapeTransform.SetScale3D(FVector::OneVector); + FHoudiniEngineUtils::TranslateUnrealTransform(LandscapeTransform, HAPIObjectTransform); + HAPIObjectTransform.position[1] = 0.0f; + + HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(HeightFieldId); + FHoudiniApi::SetObjectTransform(FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform); + + // Since HF are centered but landscape aren't, we need to set the HF's center parameter + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); + + // Finally, cook the Heightfield node + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightFieldId, &CookOptions), false); + */ + if(!FHoudiniEngineUtils::HapiCookNode(HeightFieldId, nullptr, true)) + return false; + + CreatedHeightfieldNodeId = HeightFieldId; + + return true; +} + +bool +FUnrealLandscapeTranslator::CreateInputNodeForLandscape( + ALandscapeProxy* LandscapeProxy, + const FString& InputNodeNameStr, + const FString& HeightFieldName, + const FTransform& LandscapeTransform, + FVector& CenterOffset, + HAPI_NodeId& HeightId, + HAPI_PartId& PartId, + HAPI_NodeId& HeightFieldId, + HAPI_NodeId& MaskId, + HAPI_NodeId& MergeId, + TArray& HeightData, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + int32& XSize, int32& YSize + ) +{ + //-------------------------------------------------------------------------------------------------- + // 1. Extracting the height data + //-------------------------------------------------------------------------------------------------- + + FVector Min, Max; + + if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + + if (!ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + if (!CreateHeightfieldInputNode(InputNodeNameStr, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, HeightFieldName)) + return false; + + return true; +} + +// Converts Unreal uint16 values to Houdini Float +bool +FUnrealLandscapeTranslator::ConvertLandscapeLayerDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + const FLinearColor& LayerUsageDebugColor, + TArray& LayerFloatValues, + HAPI_VolumeInfo& LayerVolumeInfo) +{ + LayerFloatValues.Empty(); + + int32 HoudiniXSize = YSize; + int32 HoudiniYSize = XSize; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + if (IntHeightData.Num() != SizeInPoints) + return false; + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to float + //-------------------------------------------------------------------------------------------------- + + // By default, values are converted from unreal [0 255] uint8 to Houdini [0 1] float + // uint8 min/max + uint8 IntMin = 0; + uint8 IntMax = UINT8_MAX; + // The range in Digits + double DigitRange = (double)UINT8_MAX; + + // By default, the values will be converted to [0, 1] + float LayerMin = 0.0f; + float LayerMax = 1.0f; + float LayerSpacing = 1.0f / DigitRange; + + // If this layer came from Houdini, its alpha value should be PI + // This indicates that we can extract additional infos stored its debug usage color + // so we can reconstruct the original source values (float) more accurately + if (LayerUsageDebugColor.A == PI) + { + // We need the ZMin / ZMax uint8 values + IntMin = IntHeightData[0]; + IntMax = IntMin; + for (int n = 0; n < IntHeightData.Num(); n++) + { + if (IntHeightData[n] < IntMin) + IntMin = IntHeightData[n]; + if (IntHeightData[n] > IntMax) + IntMax = IntHeightData[n]; + } + + DigitRange = (double)IntMax - (double)IntMin; + + // Read the original min/max and spacing stored in the debug color + LayerMin = LayerUsageDebugColor.R; + LayerMax = LayerUsageDebugColor.G; + LayerSpacing = LayerUsageDebugColor.B; + } + + // Convert the Int data to Float + LayerFloatValues.SetNumUninitialized(SizeInPoints); + + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // We need to invert X/Y when reading the value from Unreal + int32 nHoudini = nX + nY * HoudiniXSize; + int32 nUnreal = nY + nX * XSize; + + // Convert the int values to meter + // Unreal's digit value have a zero value of 32768 + double DoubleValue = ((double)IntHeightData[nUnreal] - (double)IntMin) * LayerSpacing + LayerMin; + LayerFloatValues[nHoudini] = (float)DoubleValue; + } + } + + /* + // Verifying the converted ZMin / ZMax + float FloatMin = LayerFloatValues[0]; + float FloatMax = FloatMin; + for (int32 n = 0; n < LayerFloatValues.Num(); n++) + { + if (LayerFloatValues[n] < FloatMin) + FloatMin = LayerFloatValues[n]; + if (LayerFloatValues[n] > FloatMax) + FloatMax = LayerFloatValues[n]; + } + */ + + //-------------------------------------------------------------------------------------------------- + // 2. Fill the volume info + //-------------------------------------------------------------------------------------------------- + LayerVolumeInfo.xLength = HoudiniXSize; + LayerVolumeInfo.yLength = HoudiniYSize; + LayerVolumeInfo.zLength = 1; + + LayerVolumeInfo.minX = 0; + LayerVolumeInfo.minY = 0; + LayerVolumeInfo.minZ = 0; + + LayerVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; + LayerVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; + LayerVolumeInfo.tupleSize = 1; + LayerVolumeInfo.tileSize = 1; + + LayerVolumeInfo.hasTaper = false; + LayerVolumeInfo.xTaper = 0.0; + LayerVolumeInfo.yTaper = 0.0; + + // The layer transform will have to be copied from the main heightfield's transform + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeData( + ALandscapeProxy* LandscapeProxy, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max) +{ + if (!LandscapeProxy) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Get the landscape extents to get its size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (LandscapeProxy == Landscape) + { + // The proxy is a landscape actor, so we have to use the landscape extent (landscape components + // may have been moved to proxies and may not be present on this actor). + LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); + } + else + { + // We only want to get the data for this landscape proxy. + // To handle streaming proxies correctly, get the extents via all the components, + // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. + for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) + { + Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); + } + } + + if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize)) + return false; + + // Get the landscape Min/Max values + // Do not use Landscape->GetActorBounds() here as instanced geo + // (due to grass layers for example) can cause it to return incorrect bounds! + FVector Origin, Extent; + GetLandscapeProxyBounds(LandscapeProxy, Origin, Extent); + + // Get the landscape Min/Max values + Min = Origin - Extent; + Max = Origin + Extent; + + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeData( + ULandscapeInfo* LandscapeInfo, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& HeightData, + int32& XSize, int32& YSize) +{ + if (!LandscapeInfo) + return false; + + // Get the X/Y size in points + XSize = (MaxX - MinX + 1); + YSize = (MaxY - MinY + 1); + + if ((XSize < 2) || (YSize < 2)) + return false; + + // Extracting the uint16 values from the landscape + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + HeightData.AddZeroed(XSize * YSize); + LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0); + + return true; +} + + +void +FUnrealLandscapeTranslator::GetLandscapeProxyBounds( + ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents) +{ + // Iterate only on the landscape components + FBox Bounds(ForceInit); + for (const UActorComponent* ActorComponent : LandscapeProxy->GetComponents()) + { + const ULandscapeComponent* LandscapeComp = Cast(ActorComponent); + if (LandscapeComp && LandscapeComp->IsRegistered()) + Bounds += LandscapeComp->Bounds.GetBox(); + } + + // Convert the bounds to origin/offset vectors + Bounds.GetCenterAndExtents(Origin, Extents); +} + +void +FUnrealLandscapeTranslator::ApplyAttributesToHeightfieldNode( + const HAPI_NodeId HeightId, + const HAPI_PartId PartId, + ALandscapeProxy* LandscapeProxy) +{ + UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); + UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); + UPhysicalMaterial* LandscapePhysMat = LandscapeProxy->DefaultPhysMaterial; + + AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateAttributesFromTags(HeightId, PartId, LandscapeProxy->Tags); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(HeightId, PartId, LandscapeProxy, 1); + + // Add the unreal_level_path attribute + ULevel* Level = LandscapeProxy->GetLevel(); + if (Level) + { + FHoudiniEngineUtils::AddLevelPathAttribute(HeightId, PartId, Level, 1); + /* + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToVolume(HeightId, PartId, LevelPath); + */ + } +} + + +bool +FUnrealLandscapeTranslator::ConvertLandscapeDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + FVector Min, FVector Max, + const FTransform& LandscapeTransform, + TArray& HeightfieldFloatValues, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + FVector& CenterOffset) +{ + HeightfieldFloatValues.Empty(); + + int32 HoudiniXSize = YSize; + int32 HoudiniYSize = XSize; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + if (IntHeightData.Num() != SizeInPoints) + return false; + + // Use default unreal scaling for marshalling landscapes + // A lot of precision will be lost in order to keep the same transform as the landscape input + bool bUseDefaultUE4Scaling = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) + bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to float + //-------------------------------------------------------------------------------------------------- + + + // Convert the min/max values from cm to meters + Min /= 100.0; + Max /= 100.0; + + // Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0 + // To convert the uint16 values to float "metric" values, offset the int by 32768 to center it, + // then scale it + + // Spacing used to convert from uint16 to meters + double ZSpacing = 512.0 / ((double)UINT16_MAX); + ZSpacing *= ((double)LandscapeTransform.GetScale3D().Z / 100.0); + + // Center value in meters (Landscape ranges from [-255:257] meters at default scale + double ZCenterOffset = 32767; + double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f; + // Convert the Int data to Float + HeightfieldFloatValues.SetNumUninitialized(SizeInPoints); + + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // We need to invert X/Y when reading the value from Unreal + int32 nHoudini = nX + nY * HoudiniXSize; + int32 nUnreal = nY + nX * XSize; + + // Convert the int values to meter + // Unreal's digit value have a zero value of 32768 + double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset; + HeightfieldFloatValues[nHoudini] = (float)DoubleValue; + } + } + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the Unreal Transform to a HAPI_transform + //-------------------------------------------------------------------------------------------------- + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + //FMemory::Memzero< HAPI_Transform >( HapiTransform ); + { + FQuat Rotation = LandscapeTransform.GetRotation(); + if (Rotation != FQuat::Identity) + { + //Swap(ObjectRotation.Y, ObjectRotation.Z); + HapiTransform.rotationQuaternion[0] = Rotation.X; + HapiTransform.rotationQuaternion[1] = Rotation.Z; + HapiTransform.rotationQuaternion[2] = Rotation.Y; + HapiTransform.rotationQuaternion[3] = -Rotation.W; + } + else + { + HapiTransform.rotationQuaternion[0] = 0; + HapiTransform.rotationQuaternion[1] = 0; + HapiTransform.rotationQuaternion[2] = 0; + HapiTransform.rotationQuaternion[3] = 1; + } + + // Heightfield are centered, landscapes are not + CenterOffset = (Max - Min) * 0.5f; + + // Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform) + //FVector Position = LandscapeTransform.GetLocation() / 100.0f; + HapiTransform.position[1] = 0.0f;//Position.X + CenterOffset.X; + HapiTransform.position[0] = 0.0f;//Position.Y + CenterOffset.Y; + HapiTransform.position[2] = 0.0f; + + FVector Scale = LandscapeTransform.GetScale3D() / 100.0f; + HapiTransform.scale[0] = Scale.X * 0.5f * HoudiniXSize; + HapiTransform.scale[1] = Scale.Y * 0.5f * HoudiniYSize; + HapiTransform.scale[2] = 0.5f; + if (bUseDefaultUE4Scaling) + HapiTransform.scale[2] *= Scale.Z; + + HapiTransform.shear[0] = 0.0f; + HapiTransform.shear[1] = 0.0f; + HapiTransform.shear[2] = 0.0f; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Fill the volume info + //-------------------------------------------------------------------------------------------------- + HeightfieldVolumeInfo.xLength = HoudiniXSize; + HeightfieldVolumeInfo.yLength = HoudiniYSize; + HeightfieldVolumeInfo.zLength = 1; + + HeightfieldVolumeInfo.minX = 0; + HeightfieldVolumeInfo.minY = 0; + HeightfieldVolumeInfo.minZ = 0; + + HeightfieldVolumeInfo.transform = HapiTransform; + + HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; + HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; + HeightfieldVolumeInfo.tupleSize = 1; + HeightfieldVolumeInfo.tileSize = 1; + + HeightfieldVolumeInfo.hasTaper = false; + HeightfieldVolumeInfo.xTaper = 0.0; + HeightfieldVolumeInfo.yTaper = 0.0; + + return true; +} + +bool +FUnrealLandscapeTranslator::CreateHeightfieldInputNode( + const FString& NodeName, + const int32& XSize, + const int32& YSize, + HAPI_NodeId& HeightfieldNodeId, + HAPI_NodeId& HeightNodeId, + HAPI_NodeId& MaskNodeId, + HAPI_NodeId& MergeNodeId) +{ + // Make sure the Heightfield node doesnt already exists + if (HeightfieldNodeId != -1) + return false; + + // Convert the node's name + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString(NodeName, NameStr); + + // Create the heigthfield node via HAPI + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightFieldInput( + FHoudiniEngine::Get().GetSession(), + -1, NameStr.c_str(), XSize, YSize, 1.0f, HAPI_HeightFieldSampling::HAPI_HEIGHTFIELD_SAMPLING_CORNER, + &HeightfieldNodeId, &HeightNodeId, &MaskNodeId, &MergeNodeId), false); + + // Cook it + return FHoudiniEngineUtils::HapiCookNode(HeightfieldNodeId, nullptr, true); + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightfieldNodeId, &CookOptions), false); + + return true; + */ +} + +bool +FUnrealLandscapeTranslator::SetHeightfieldData( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + TArray& FloatValues, + const HAPI_VolumeInfo& VolumeInfo, + const FString& HeightfieldName) +{ + // Cook the node to get proper infos on it + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, &CookOptions), false); + */ + if(!FHoudiniEngineUtils::HapiCookNode(VolumeNodeId, nullptr, true)) + return false; + + // Read the geo/part/volume info from the volume node + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + //FMemory::Memset< HAPI_GeoInfo >(GeoInfo, 0); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, &GeoInfo), false); + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + //FMemory::Memset< HAPI_PartInfo >(PartInfo, 0); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoInfo.nodeId, PartId, &PartInfo), false); + + // Update the volume infos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, PartInfo.id, &VolumeInfo), false); + + // Volume name + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString(HeightfieldName, NameStr); + + // Set the Heighfield data on the volume + float * HeightData = FloatValues.GetData(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetHeightFieldData( + FHoudiniEngine::Get().GetSession(), + GeoInfo.nodeId, PartInfo.id, NameStr.c_str(), HeightData, 0, FloatValues.Num()), false); + + return true; +} + +bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + UMaterialInterface* InLandscapeMaterial, + UMaterialInterface* InLandscapeHoleMaterial, + UPhysicalMaterial* InPhysicalMaterial) +{ + if (VolumeNodeId == -1) + return false; + + // LANDSCAPE MATERIAL + if (InLandscapeMaterial && !InLandscapeMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InLandscapeMaterialString = InLandscapeMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + // HOLE MATERIAL + if (InLandscapeHoleMaterial && !InLandscapeHoleMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InLandscapeMaterialString = InLandscapeHoleMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (Result == HAPI_RESULT_SUCCESS) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_hole_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + // PHYSICAL MATERIAL + if (InPhysicalMaterial && !InPhysicalMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InPhysMatlString = InPhysicalMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (Result == HAPI_RESULT_SUCCESS) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InPhysMatlString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_physical_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + return true; +} + +/* +bool +FUnrealLandscapeTranslator::AddLevelPathAttributeToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + const FString& LevelPath) +{ + if (VolumeNodeId == -1) + return false; + + // LANDSCAPE MATERIAL + if (LevelPath.IsEmpty()) + return false; + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = 1; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to cont char * + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray LevelPathArr; + LevelPathArr.Add(LevelPathCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + LevelPathArr.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} +*/ + +bool +FUnrealLandscapeTranslator::GetLandscapeLayerData( + ALandscapeProxy* LandscapeProxy, + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName) +{ + if (!IsValid(LandscapeInfo) || !IsValid(LandscapeProxy)) + return false; + + // Get the landscape X/Y Size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (LandscapeProxy == Landscape) + { + // The proxy is a landscape actor, so we have to use the landscape extent (landscape components + // may have been moved to proxies and may not be present on this actor). + LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); + } + else + { + // We only want to get the data for this landscape proxy. + // To handle streaming proxies correctly, get the extents via all the components, + // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. + for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) + { + Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); + } + } + + if(MinX == MAX_int32 || MinY == MAX_int32 || MaxX == -MAX_int32 || MaxY == -MAX_int32) + return false; + + if (!GetLandscapeLayerData( + LandscapeInfo, LayerIndex, + MinX, MinY, MaxX, MaxY, + LayerData, LayerUsageDebugColor, LayerName)) + return false; + + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName) +{ + if (!LandscapeInfo) + return false; + + if (!LandscapeInfo->Layers.IsValidIndex(LayerIndex)) + return false; + + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[LayerIndex]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (!LayerInfo) + return false; + + // Calc the X/Y size in points + int32 XSize = (MaxX - MinX + 1); + int32 YSize = (MaxY - MinY + 1); + if ((XSize < 2) || (YSize < 2)) + return false; + + // extracting the uint8 values from the layer + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + LayerData.AddZeroed(XSize * YSize); + LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, LayerData.GetData(), 0); + + LayerUsageDebugColor = LayerInfo->LayerUsageDebugColor; + + LayerName = LayersSetting.GetLayerName().ToString(); + + return true; +} + +bool +FUnrealLandscapeTranslator::InitDefaultHeightfieldMask( + const HAPI_VolumeInfo& HeightVolumeInfo, + const HAPI_NodeId& MaskVolumeNodeId) +{ + // We need to have a mask layer as it is required for proper heightfield functionalities + + // Creating an array filled with 0.0 + TArray< float > MaskFloatData; + MaskFloatData.Init(0.0f, HeightVolumeInfo.xLength * HeightVolumeInfo.yLength); + + // Creating the volume infos + HAPI_VolumeInfo MaskVolumeInfo = HeightVolumeInfo; + + // Set the heighfield data in Houdini + FString MaskName = TEXT("mask"); + HAPI_PartId PartId = 0; + if (!SetHeightfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) + return false; + + return true; +} + +bool +FUnrealLandscapeTranslator::DestroyLandscapeAssetNode(HAPI_NodeId& ConnectedAssetId, TArray& CreatedInputAssetIds) +{ + HAPI_AssetInfo NodeAssetInfo; + FHoudiniApi::AssetInfo_Init(&NodeAssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeAssetInfo), false); + + FHoudiniEngineString AssetOpName(NodeAssetInfo.fullOpNameSH); + FString OpName; + if (!AssetOpName.ToFString(OpName)) + return false; + + if (!OpName.Contains(TEXT("xform"))) + { + // Not a transform node, so not a Heightfield + // We just need to destroy the landscape asset node + return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); + } + + // The landscape was marshalled as a heightfield, so we need to destroy and disconnect + // the volvis nodes, all the merge node's input (each merge input is a volume for one + // of the layer/mask of the landscape ) + + // Query the volvis node id + // The volvis node is the fist input of the xform node + HAPI_NodeId VolvisNodeId = -1; + FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, &VolvisNodeId); + + // First, destroy the merge node and its inputs + // The merge node is in the first input of the volvis node + HAPI_NodeId MergeNodeId = -1; + FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + VolvisNodeId, 0, &MergeNodeId); + + if (MergeNodeId != -1) + { + // Get the merge node info + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), MergeNodeId, &NodeInfo), false); + + for (int32 n = 0; n < NodeInfo.inputCount; n++) + { + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeNodeId, n, &InputNodeId)) + break; + + if (InputNodeId == -1) + break; + + // Disconnect and Destroy that input + FHoudiniEngineUtils::HapiDisconnectAsset(MergeNodeId, n); + FHoudiniEngineUtils::DestroyHoudiniAsset(InputNodeId); + } + } + + // Second step, destroy all the volumes GEO assets + for (HAPI_NodeId AssetNodeId : CreatedInputAssetIds) + { + FHoudiniEngineUtils::DestroyHoudiniAsset(AssetNodeId); + } + CreatedInputAssetIds.Empty(); + + // Finally disconnect and destroy the xform, volvis and merge nodes, then destroy them + FHoudiniEngineUtils::HapiDisconnectAsset(ConnectedAssetId, 0); + FHoudiniEngineUtils::HapiDisconnectAsset(VolvisNodeId, 0); + FHoudiniEngineUtils::DestroyHoudiniAsset(MergeNodeId); + FHoudiniEngineUtils::DestroyHoudiniAsset(VolvisNodeId); + + return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); +} + + +bool +FUnrealLandscapeTranslator::ExtractLandscapeData( + ALandscapeProxy * LandscapeProxy, TSet& SelectedComponents, + const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, + TArray& LandscapePositionArray, + TArray& LandscapeNormalArray, + TArray& LandscapeUVArray, + TArray& LandscapeComponentVertexIndicesArray, + TArray& LandscapeComponentNameArray, + TArray& LandscapeLightmapValues) +{ + if (!LandscapeProxy) + return false; + + if (SelectedComponents.Num() < 1) + return false; + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Calc all the needed sizes + int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; + float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; + + int32 NumComponents = SelectedComponents.Num(); + bool bExportOnlySelected = NumComponents != LandscapeProxy->LandscapeComponents.Num(); + + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + int32 VertexCount = NumComponents * VertexCountPerComponent; + if (!VertexCount) + return false; + + // Initialize the data arrays + LandscapePositionArray.SetNumUninitialized(VertexCount); + LandscapeNormalArray.SetNumUninitialized(VertexCount); + LandscapeUVArray.SetNumUninitialized(VertexCount); + LandscapeComponentNameArray.SetNumUninitialized(VertexCount); + LandscapeComponentVertexIndicesArray.SetNumUninitialized(VertexCount); + if (bExportLighting) + LandscapeLightmapValues.SetNumUninitialized(VertexCount); + + //----------------------------------------------------------------------------------------------------------------- + // EXTRACT THE LANDSCAPE DATA + //----------------------------------------------------------------------------------------------------------------- + FIntPoint IntPointMax = FIntPoint::ZeroValue; + + int32 AllPositionsIdx = 0; + for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; + if (bExportOnlySelected && !SelectedComponents.Contains(LandscapeComponent)) + continue; + + TArray64< uint8 > LightmapMipData; + int32 LightmapMipSizeX = 0; + int32 LightmapMipSizeY = 0; + + // See if we need to export lighting information. + if (bExportLighting) + { + const FMeshMapBuildData* MapBuildData = LandscapeComponent->GetMeshMapBuildData(); + FLightMap2D* LightMap2D = MapBuildData && MapBuildData->LightMap ? MapBuildData->LightMap->GetLightMap2D() : nullptr; + if (LightMap2D && LightMap2D->IsValid(0)) + { + UTexture2D * TextureLightmap = LightMap2D->GetTexture(0); + if (TextureLightmap) + { + if (TextureLightmap->Source.GetMipData(LightmapMipData, 0, 0, 0, nullptr)) + { + LightmapMipSizeX = TextureLightmap->Source.GetSizeX(); + LightmapMipSizeY = TextureLightmap->Source.GetSizeY(); + } + else + { + LightmapMipData.Empty(); + } + } + } + } + + // Construct landscape component data interface to access raw data. + FLandscapeComponentDataInterface CDI(LandscapeComponent, LandscapeProxy->ExportLOD); + + // Get name of this landscape component. + const char * LandscapeComponentNameStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->GetName()); + for (int32 VertexIdx = 0; VertexIdx < VertexCountPerComponent; VertexIdx++) + { + int32 VertX = 0; + int32 VertY = 0; + CDI.VertexIndexToXY(VertexIdx, VertX, VertY); + + // Get position. + FVector PositionVector = CDI.GetWorldVertex(VertX, VertY); + + // Get normal / tangent / binormal. + FVector Normal = FVector::ZeroVector; + FVector TangentX = FVector::ZeroVector; + FVector TangentY = FVector::ZeroVector; + CDI.GetLocalTangentVectors(VertX, VertY, TangentX, TangentY, Normal); + + // Export UVs. + FVector TextureUV = FVector::ZeroVector; + if (bExportTileUVs) + { + // We want to export uvs per tile. + TextureUV = FVector(VertX, VertY, 0.0f); + + // If we need to normalize UV space. + if (bExportNormalizedUVs) + TextureUV /= ComponentSizeQuads; + } + else + { + // We want to export global uvs (default). + FIntPoint IntPoint = LandscapeComponent->GetSectionBase(); + TextureUV = FVector(VertX * ScaleFactor + IntPoint.X, VertY * ScaleFactor + IntPoint.Y, 0.0f); + + // Keep track of max offset. + IntPointMax = IntPointMax.ComponentMax(IntPoint); + } + + if (bExportLighting) + { + FLinearColor VertexLightmapColor(0.0f, 0.0f, 0.0f, 1.0f); + if (LightmapMipData.Num() > 0) + { + FVector2D UVCoord(VertX, VertY); + UVCoord /= (ComponentSizeQuads + 1); + + FColor LightmapColorRaw = PickVertexColorFromTextureMip( + LightmapMipData.GetData(), UVCoord, LightmapMipSizeX, LightmapMipSizeY); + + VertexLightmapColor = LightmapColorRaw.ReinterpretAsLinear(); + } + + LandscapeLightmapValues[AllPositionsIdx] = VertexLightmapColor; + } + + // Retrieve component transform. + const FTransform & ComponentTransform = LandscapeComponent->GetComponentTransform(); + + // Retrieve component scale. + const FVector & ScaleVector = ComponentTransform.GetScale3D(); + + // Perform normalization. + Normal /= ScaleVector; + Normal.Normalize(); + + TangentX /= ScaleVector; + TangentX.Normalize(); + + TangentY /= ScaleVector; + TangentY.Normalize(); + + // Perform position scaling. + FVector PositionTransformed = PositionVector / HAPI_UNREAL_SCALE_FACTOR_POSITION; + LandscapePositionArray[AllPositionsIdx].X = PositionTransformed.X; + LandscapePositionArray[AllPositionsIdx].Y = PositionTransformed.Z; + LandscapePositionArray[AllPositionsIdx].Z = PositionTransformed.Y; + + Swap(Normal.Y, Normal.Z); + + // Store landscape component name for this point. + LandscapeComponentNameArray[AllPositionsIdx] = LandscapeComponentNameStr; + + // Store vertex index (x,y) for this point. + LandscapeComponentVertexIndicesArray[AllPositionsIdx].X = VertX; + LandscapeComponentVertexIndicesArray[AllPositionsIdx].Y = VertY; + + // Store point normal. + LandscapeNormalArray[AllPositionsIdx] = Normal; + + // Store uv. + LandscapeUVArray[AllPositionsIdx] = TextureUV; + + AllPositionsIdx++; + } + + // Free the memory allocated for LandscapeComponentNameStr + FHoudiniEngineUtils::FreeRawStringMemory(LandscapeComponentNameStr); + } + + // If we need to normalize UV space and we are doing global UVs. + if (!bExportTileUVs && bExportNormalizedUVs) + { + IntPointMax += FIntPoint(ComponentSizeQuads, ComponentSizeQuads); + IntPointMax = IntPointMax.ComponentMax(FIntPoint(1, 1)); + + for (int32 UVIdx = 0; UVIdx < VertexCount; ++UVIdx) + { + FVector & PositionUV = LandscapeUVArray[UVIdx]; + PositionUV.X /= IntPointMax.X; + PositionUV.Y /= IntPointMax.Y; + } + } + + return true; +} + +FColor +FUnrealLandscapeTranslator::PickVertexColorFromTextureMip( + const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight) +{ + check(MipBytes); + + FColor ResultColor(0, 0, 0, 255); + + if (UVCoord.X >= 0.0f && UVCoord.X < 1.0f && UVCoord.Y >= 0.0f && UVCoord.Y < 1.0f) + { + const int32 X = MipWidth * UVCoord.X; + const int32 Y = MipHeight * UVCoord.Y; + + const int32 Index = ((Y * MipWidth) + X) * 4; + + ResultColor.B = MipBytes[Index + 0]; + ResultColor.G = MipBytes[Index + 1]; + ResultColor.R = MipBytes[Index + 2]; + ResultColor.A = MipBytes[Index + 3]; + } + + return ResultColor; +} + +bool +FUnrealLandscapeTranslator::AddLandscapePositionAttribute(const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray) +{ + int32 VertexCount = LandscapePositionArray.Num(); + if (VertexCount < 3) + return false; + + // Create point attribute info containing positions. + HAPI_AttributeInfo AttributeInfoPointPosition; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointPosition); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointPosition ); + AttributeInfoPointPosition.count = VertexCount; + AttributeInfoPointPosition.tupleSize = 3; + AttributeInfoPointPosition.exists = true; + AttributeInfoPointPosition.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointPosition.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointPosition.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition), false); + + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition, + (const float *)LandscapePositionArray.GetData(), + 0, AttributeInfoPointPosition.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeNormalAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeNormalArray) +{ + int32 VertexCount = LandscapeNormalArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointNormal; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointNormal); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointNormal ); + AttributeInfoPointNormal.count = VertexCount; + AttributeInfoPointNormal.tupleSize = 3; + AttributeInfoPointNormal.exists = true; + AttributeInfoPointNormal.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointNormal.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointNormal.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal, + (const float *)LandscapeNormalArray.GetData(), 0, VertexCount), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeUVAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeUVArray) +{ + int32 VertexCount = LandscapeUVArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointUV; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointUV); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointUV ); + AttributeInfoPointUV.count = VertexCount; + AttributeInfoPointUV.tupleSize = 3; + AttributeInfoPointUV.exists = true; + AttributeInfoPointUV.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointUV.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointUV.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV, + (const float *)LandscapeUVArray.GetData(), 0, AttributeInfoPointUV.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeComponentVertexIndicesAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentVertexIndicesArray) +{ + int32 VertexCount = LandscapeComponentVertexIndicesArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointLandscapeComponentVertexIndices; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentVertexIndices); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentVertexIndices ); + AttributeInfoPointLandscapeComponentVertexIndices.count = VertexCount; + AttributeInfoPointLandscapeComponentVertexIndices.tupleSize = 2; + AttributeInfoPointLandscapeComponentVertexIndices.exists = true; + AttributeInfoPointLandscapeComponentVertexIndices.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLandscapeComponentVertexIndices.storage = HAPI_STORAGETYPE_INT; + AttributeInfoPointLandscapeComponentVertexIndices.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, + &AttributeInfoPointLandscapeComponentVertexIndices), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, + &AttributeInfoPointLandscapeComponentVertexIndices, + (const int *)LandscapeComponentVertexIndicesArray.GetData(), 0, + AttributeInfoPointLandscapeComponentVertexIndices.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeComponentNameAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray) +{ + int32 VertexCount = LandscapeComponentNameArray.Num(); + if (VertexCount < 3) + return false; + + // Create point attribute containing landscape component name. + HAPI_AttributeInfo AttributeInfoPointLandscapeComponentNames; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentNames); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentNames ); + AttributeInfoPointLandscapeComponentNames.count = VertexCount; + AttributeInfoPointLandscapeComponentNames.tupleSize = 1; + AttributeInfoPointLandscapeComponentNames.exists = true; + AttributeInfoPointLandscapeComponentNames.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLandscapeComponentNames.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPointLandscapeComponentNames.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, + &AttributeInfoPointLandscapeComponentNames), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, + &AttributeInfoPointLandscapeComponentNames, + (const char **)LandscapeComponentNameArray.GetData(), + 0, AttributeInfoPointLandscapeComponentNames.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeLightmapColorAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues) +{ + int32 VertexCount = LandscapeLightmapValues.Num(); + + HAPI_AttributeInfo AttributeInfoPointLightmapColor; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLightmapColor); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLightmapColor ); + AttributeInfoPointLightmapColor.count = VertexCount; + AttributeInfoPointLightmapColor.tupleSize = 4; + AttributeInfoPointLightmapColor.exists = true; + AttributeInfoPointLightmapColor.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLightmapColor.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointLightmapColor.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor, + (const float *)LandscapeLightmapValues.GetData(), 0, + AttributeInfoPointLightmapColor.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeMeshIndicesAndMaterialsAttribute( + const HAPI_NodeId& NodeId, const bool& bExportMaterials, + const int32& ComponentSizeQuads, const int32& QuadCount, + ALandscapeProxy * LandscapeProxy, + const TSet< ULandscapeComponent * >& SelectedComponents) +{ + if (!LandscapeProxy) + return false; + + // Compute number of necessary indices. + int32 IndexCount = QuadCount * 4; + if (IndexCount < 0) + return false; + + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + + // Array holding indices data. + TArray LandscapeIndices; + LandscapeIndices.SetNumUninitialized(IndexCount); + + // Allocate space for face names. + // The LandscapeMaterial and HoleMaterial per point + TArray FaceMaterials; + TArray FaceHoleMaterials; + FaceMaterials.SetNumUninitialized(QuadCount); + FaceHoleMaterials.SetNumUninitialized(QuadCount); + + int32 VertIdx = 0; + int32 QuadIdx = 0; + + const char * MaterialRawStr = nullptr; + const char * MaterialHoleRawStr = nullptr; + + // Lambda for freeing the memory allocated by ExtractRawString and returning + auto FreeMemoryReturn = [&MaterialRawStr, &MaterialHoleRawStr](const bool& bReturn) + { + FHoudiniEngineUtils::FreeRawStringMemory(MaterialRawStr); + FHoudiniEngineUtils::FreeRawStringMemory(MaterialHoleRawStr); + + return bReturn; + }; + + const int32 QuadComponentCount = ComponentSizeQuads + 1; + for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; + if (!SelectedComponents.Contains(LandscapeComponent)) + continue; + + if (bExportMaterials) + { + // If component has an override material, we need to get the raw name (if exporting materials). + if (LandscapeComponent->OverrideMaterial) + { + MaterialRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideMaterial->GetName()); + } + + // If component has an override hole material, we need to get the raw name (if exporting materials). + if (LandscapeComponent->OverrideHoleMaterial) + { + MaterialHoleRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideHoleMaterial->GetName()); + } + } + + int32 BaseVertIndex = ComponentIdx * VertexCountPerComponent; + for (int32 YIdx = 0; YIdx < ComponentSizeQuads; YIdx++) + { + for (int32 XIdx = 0; XIdx < ComponentSizeQuads; XIdx++) + { + LandscapeIndices[VertIdx + 0] = BaseVertIndex + (XIdx + 0) + (YIdx + 0) * QuadComponentCount; + LandscapeIndices[VertIdx + 1] = BaseVertIndex + (XIdx + 1) + (YIdx + 0) * QuadComponentCount; + LandscapeIndices[VertIdx + 2] = BaseVertIndex + (XIdx + 1) + (YIdx + 1) * QuadComponentCount; + LandscapeIndices[VertIdx + 3] = BaseVertIndex + (XIdx + 0) + (YIdx + 1) * QuadComponentCount; + + // Store override materials (if exporting materials). + if (bExportMaterials) + { + FaceMaterials[QuadIdx] = MaterialRawStr; + FaceHoleMaterials[QuadIdx] = MaterialHoleRawStr; + } + + VertIdx += 4; + QuadIdx++; + } + } + } + + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, LandscapeIndices.GetData(), 0, LandscapeIndices.Num()), + FreeMemoryReturn(false)); + + // We need to generate array of face counts. + TArray LandscapeFaces; + LandscapeFaces.Init(4, QuadCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, LandscapeFaces.GetData(), 0, LandscapeFaces.Num()), + FreeMemoryReturn(false)); + + if (bExportMaterials) + { + if (!FaceMaterials.Contains(nullptr)) + { + // Marshall in override primitive material names. + HAPI_AttributeInfo AttributeInfoPrimitiveMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterial ); + AttributeInfoPrimitiveMaterial.count = FaceMaterials.Num(); + AttributeInfoPrimitiveMaterial.tupleSize = 1; + AttributeInfoPrimitiveMaterial.exists = true; + AttributeInfoPrimitiveMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoPrimitiveMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPrimitiveMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial, + (const char **)FaceMaterials.GetData(), 0, AttributeInfoPrimitiveMaterial.count), + FreeMemoryReturn(false)); + } + + if (!FaceHoleMaterials.Contains(nullptr)) + { + // Marshall in override primitive material hole names. + HAPI_AttributeInfo AttributeInfoPrimitiveMaterialHole; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterialHole); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterialHole ); + AttributeInfoPrimitiveMaterialHole.count = FaceHoleMaterials.Num(); + AttributeInfoPrimitiveMaterialHole.tupleSize = 1; + AttributeInfoPrimitiveMaterialHole.exists = true; + AttributeInfoPrimitiveMaterialHole.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoPrimitiveMaterialHole.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPrimitiveMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoPrimitiveMaterialHole), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoPrimitiveMaterialHole, (const char **)FaceHoleMaterials.GetData(), 0, + AttributeInfoPrimitiveMaterialHole.count), + FreeMemoryReturn(false)); + } + } + + // Free the memory and return true + return FreeMemoryReturn(true); +} + +bool +FUnrealLandscapeTranslator::AddLandscapeGlobalMaterialAttribute( + const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy) +{ + if (!LandscapeProxy) + return false; + + // If there's a global landscape material, we marshall it as detail. + UMaterialInterface * MaterialInterface = LandscapeProxy->GetLandscapeMaterial(); + const char * MaterialNameStr = ""; + if (MaterialInterface) + { + FString FullMaterialName = MaterialInterface->GetPathName(); + MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); + } + + HAPI_AttributeInfo AttributeInfoDetailMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterial ); + AttributeInfoDetailMaterial.count = 1; + AttributeInfoDetailMaterial.tupleSize = 1; + AttributeInfoDetailMaterial.exists = true; + AttributeInfoDetailMaterial.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoDetailMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoDetailMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial, + (const char**)&MaterialNameStr, 0, AttributeInfoDetailMaterial.count), false); + + // If there's a global landscape hole material, we marshall it as detail. + UMaterialInterface * HoleMaterialInterface = LandscapeProxy->GetLandscapeHoleMaterial(); + const char * HoleMaterialNameStr = ""; + if (HoleMaterialInterface) + { + FString FullMaterialName = HoleMaterialInterface->GetPathName(); + MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); + } + + HAPI_AttributeInfo AttributeInfoDetailMaterialHole; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterialHole); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterialHole ); + AttributeInfoDetailMaterialHole.count = 1; + AttributeInfoDetailMaterialHole.tupleSize = 1; + AttributeInfoDetailMaterialHole.exists = true; + AttributeInfoDetailMaterialHole.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoDetailMaterialHole.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoDetailMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoDetailMaterialHole), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoDetailMaterialHole, (const char **)&HoleMaterialNameStr, 0, + AttributeInfoDetailMaterialHole.count), false); + + return true; +} + + +bool +FUnrealLandscapeTranslator::AddLandscapeLayerAttribute( + const HAPI_NodeId& NodeId, const TArray& LandscapeLayerArray, const FString& LayerName) +{ + int32 VertexCount = LandscapeLayerArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoLayer; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLayer); + AttributeInfoLayer.count = VertexCount; + AttributeInfoLayer.tupleSize = 1; + AttributeInfoLayer.exists = true; + AttributeInfoLayer.owner = HAPI_ATTROWNER_POINT; + AttributeInfoLayer.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLayer.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, + TCHAR_TO_ANSI(*LayerName), + &AttributeInfoLayer), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, + TCHAR_TO_ANSI(*LayerName), + &AttributeInfoLayer, + (const float *)LandscapeLayerArray.GetData(), + 0, AttributeInfoLayer.count), false); + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h index ea5803f41..4b7542d16 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h @@ -1,234 +1,258 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Landscape.h" -#include "HAPI/HAPI_Common.h" - -class ALandscapeProxy; -class UHoudiniInputLandscape; - -struct HOUDINIENGINE_API FUnrealLandscapeTranslator -{ - public: - - // ------------------------------------------------------------------------------------------ - // Unreal Landscape to Houdini Heightfield - // ------------------------------------------------------------------------------------------ - static bool CreateHeightfieldFromLandscape( - ALandscapeProxy* LandcapeProxy, - HAPI_NodeId& CreatedHeightfieldNodeId, - const FString &InputNodeNameStr); - - // Extracts the uint16 values of a given landscape - static bool GetLandscapeData( - ALandscapeProxy* LandscapeProxy, - TArray& HeightData, - int32& XSize, int32& YSize, - FVector& Min, FVector& Max); - - static bool GetLandscapeData( - ULandscapeInfo* LandscapeInfo, - const int32& MinX, - const int32& MinY, - const int32& MaxX, - const int32& MaxY, - TArray& HeightData, - int32& XSize, int32& YSize); - - static void GetLandscapeProxyBounds( - ALandscapeProxy* LandscapeProxy, - FVector& Origin, FVector& Extents); - - // Converts Unreal uint16 values to Houdini Float - static bool ConvertLandscapeDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, - const int32& YSize, - FVector Min, - FVector Max, - const FTransform& LandscapeTransform, - TArray& HeightfieldFloatValues, - HAPI_VolumeInfo& HeightfieldVolumeInfo, - FVector& CenterOffset); - - // Converts Unreal uint8 values to Houdini Float - static bool ConvertLandscapeLayerDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - const FLinearColor& LayerUsageDebugColor, - TArray& LayerFloatValues, - HAPI_VolumeInfo& LayerVolumeInfo); - - // Creates an unlocked heightfield input node - static bool CreateHeightfieldInputNode( - const FString& NodeName, - const int32& XSize, - const int32& YSize, - HAPI_NodeId& HeightfieldNodeId, - HAPI_NodeId& HeightNodeId, - HAPI_NodeId& MaskNodeId, - HAPI_NodeId& MergeNodeId ); - - // Set the volume float value for a heightfield - static bool SetHeightfieldData( - const HAPI_NodeId& AssetId, - const HAPI_PartId& PartId, - TArray< float >& FloatValues, - const HAPI_VolumeInfo& VolumeInfo, - const FString& HeightfieldName); - - static bool AddLandscapeMaterialAttributesToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* InPhysicalMaterial); - - /* - static bool AddLevelPathAttributeToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - const FString& LevelPath); - */ - - // Extracts the uint8 values of a given landscape - static bool GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName); - - static bool GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - const int32& MinX, - const int32& MinY, - const int32& MaxX, - const int32& MaxY, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName); - - // Initialise the Heightfield Mask with default values - static bool InitDefaultHeightfieldMask( - const HAPI_VolumeInfo& HeightVolumeInfo, - const HAPI_NodeId& MaskVolumeNodeId); - - // Landscape nodes clean up - static bool DestroyLandscapeAssetNode( - HAPI_NodeId& ConnectedAssetId, - TArray& CreatedInputAssetIds); - - - //-------------------------------------------------------------------------------------------------- - // Unreal to Houdini - MESH / POINTS - //-------------------------------------------------------------------------------------------------- - - static bool CreateMeshOrPointsFromLandscape( - ALandscapeProxy* InLandscape, - HAPI_NodeId& InputNodeId, - const FString& InInputNodeNameString, - const bool& bExportGeometryAsMesh, - const bool& bExportTileUVs, - const bool bExportNormalizedUVs, - const bool bExportLighting, - const bool bExportMaterials); - - // Extract data from the landscape - static bool ExtractLandscapeData( - ALandscapeProxy * LandscapeProxy, - TSet& SelectedComponents, - const bool& bExportLighting, - const bool& bExportTileUVs, - const bool& bExportNormalizedUVs, - TArray& LandscapePositionArray, - TArray& LandscapeNormalArray, - TArray& LandscapeUVArray, - TArray& LandscapeComponentVertexIndicesArray, - TArray& LandscapeComponentNameArray, - TArray& LandscapeLightmapValues); - - // Helper functions to extract color from a texture - static FColor PickVertexColorFromTextureMip( - const uint8 * MipBytes, - FVector2D & UVCoord, - int32 MipWidth, - int32 MipHeight); - - // Add the Position attribute extracted from a landscape - static bool AddLandscapePositionAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapePositionArray); - - // Add the Normal attribute extracted from a landscape - static bool AddLandscapeNormalAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapeNormalArray); - - // Add the UV attribute extracted from a landscape - static bool AddLandscapeUVAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapeUVArray); - - // Add the Component Vertex Index attribute extracted from a landscape - static bool AddLandscapeComponentVertexIndicesAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeComponentVertexIndicesArray); - - // Add the Component Name attribute extracted from a landscape - static bool AddLandscapeComponentNameAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeComponentNameArray); - - // Add the lightmap color attribute extracted from a landscape - static bool AddLandscapeLightmapColorAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeLightmapValues); - - // Creates and add the vertex indices and face materials attribute from a landscape - static bool AddLandscapeMeshIndicesAndMaterialsAttribute( - const HAPI_NodeId& NodeId, - const bool& bExportMaterials, - const int32& ComponentSizeQuads, - const int32& QuadCount, - ALandscapeProxy * LandscapeProxy, - const TSet& SelectedComponents); - - // Add the global (detail) material and hole material attribute from a landscape - static bool AddLandscapeGlobalMaterialAttribute( - const HAPI_NodeId& NodeId, - ALandscapeProxy * LandscapeProxy); - - // Add landscape layer values as point attributes - static bool AddLandscapeLayerAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeLayerArray, - const FString& LayerName); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Landscape.h" +#include "HAPI/HAPI_Common.h" + +class ALandscapeProxy; +class UHoudiniInputLandscape; + +struct HOUDINIENGINE_API FUnrealLandscapeTranslator +{ + public: + + // ------------------------------------------------------------------------------------------ + // Unreal Landscape to Houdini Heightfield + // ------------------------------------------------------------------------------------------ + static bool CreateHeightfieldFromLandscape( + ALandscapeProxy* LandcapeProxy, + HAPI_NodeId& CreatedHeightfieldNodeId, + const FString &InputNodeNameStr); + + static bool CreateInputNodeForLandscape( + ALandscapeProxy* LandscapeProxy, + const FString& InputNodeNameStr, + const FString& HeightFieldName, + const FTransform& LandscapeTransform, + FVector& CenterOffset, + HAPI_NodeId& HeightId, + HAPI_PartId& PartId, + HAPI_NodeId& HeightFieldId, + HAPI_NodeId& MaskId, + HAPI_NodeId& MergeId, + TArray& HeightData, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + int32& XSize, int32& YSize + + ); + + static void ApplyAttributesToHeightfieldNode( + const HAPI_NodeId HeightId, + const HAPI_PartId PartId, + ALandscapeProxy* LandscapeProxy + ); + + // Extracts the uint16 values of a given landscape + static bool GetLandscapeData( + ALandscapeProxy* LandscapeProxy, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max); + + static bool GetLandscapeData( + ULandscapeInfo* LandscapeInfo, + const int32& MinX, + const int32& MinY, + const int32& MaxX, + const int32& MaxY, + TArray& HeightData, + int32& XSize, int32& YSize); + + static void GetLandscapeProxyBounds( + ALandscapeProxy* LandscapeProxy, + FVector& Origin, FVector& Extents); + + // Converts Unreal uint16 values to Houdini Float + static bool ConvertLandscapeDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, + const int32& YSize, + FVector Min, + FVector Max, + const FTransform& LandscapeTransform, + TArray& HeightfieldFloatValues, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + FVector& CenterOffset); + + // Converts Unreal uint8 values to Houdini Float + static bool ConvertLandscapeLayerDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + const FLinearColor& LayerUsageDebugColor, + TArray& LayerFloatValues, + HAPI_VolumeInfo& LayerVolumeInfo); + + // Creates an unlocked heightfield input node + static bool CreateHeightfieldInputNode( + const FString& NodeName, + const int32& XSize, + const int32& YSize, + HAPI_NodeId& HeightfieldNodeId, + HAPI_NodeId& HeightNodeId, + HAPI_NodeId& MaskNodeId, + HAPI_NodeId& MergeNodeId ); + + // Set the volume float value for a heightfield + static bool SetHeightfieldData( + const HAPI_NodeId& AssetId, + const HAPI_PartId& PartId, + TArray< float >& FloatValues, + const HAPI_VolumeInfo& VolumeInfo, + const FString& HeightfieldName); + + static bool AddLandscapeMaterialAttributesToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* InPhysicalMaterial); + + /* + static bool AddLevelPathAttributeToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + const FString& LevelPath); + */ + + // Extracts the uint8 values of a given landscape + static bool GetLandscapeLayerData( + ALandscapeProxy* LandscapeProxy, + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName); + + static bool GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + const int32& MinX, + const int32& MinY, + const int32& MaxX, + const int32& MaxY, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName); + + // Initialise the Heightfield Mask with default values + static bool InitDefaultHeightfieldMask( + const HAPI_VolumeInfo& HeightVolumeInfo, + const HAPI_NodeId& MaskVolumeNodeId); + + // Landscape nodes clean up + static bool DestroyLandscapeAssetNode( + HAPI_NodeId& ConnectedAssetId, + TArray& CreatedInputAssetIds); + + + //-------------------------------------------------------------------------------------------------- + // Unreal to Houdini - MESH / POINTS + //-------------------------------------------------------------------------------------------------- + + static bool CreateMeshOrPointsFromLandscape( + ALandscapeProxy* InLandscape, + HAPI_NodeId& InputNodeId, + const FString& InInputNodeNameString, + const bool& bExportGeometryAsMesh, + const bool& bExportTileUVs, + const bool bExportNormalizedUVs, + const bool bExportLighting, + const bool bExportMaterials); + + // Extract data from the landscape + static bool ExtractLandscapeData( + ALandscapeProxy * LandscapeProxy, + TSet& SelectedComponents, + const bool& bExportLighting, + const bool& bExportTileUVs, + const bool& bExportNormalizedUVs, + TArray& LandscapePositionArray, + TArray& LandscapeNormalArray, + TArray& LandscapeUVArray, + TArray& LandscapeComponentVertexIndicesArray, + TArray& LandscapeComponentNameArray, + TArray& LandscapeLightmapValues); + + // Helper functions to extract color from a texture + static FColor PickVertexColorFromTextureMip( + const uint8 * MipBytes, + FVector2D & UVCoord, + int32 MipWidth, + int32 MipHeight); + + // Add the Position attribute extracted from a landscape + static bool AddLandscapePositionAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapePositionArray); + + // Add the Normal attribute extracted from a landscape + static bool AddLandscapeNormalAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapeNormalArray); + + // Add the UV attribute extracted from a landscape + static bool AddLandscapeUVAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapeUVArray); + + // Add the Component Vertex Index attribute extracted from a landscape + static bool AddLandscapeComponentVertexIndicesAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeComponentVertexIndicesArray); + + // Add the Component Name attribute extracted from a landscape + static bool AddLandscapeComponentNameAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeComponentNameArray); + + // Add the lightmap color attribute extracted from a landscape + static bool AddLandscapeLightmapColorAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeLightmapValues); + + // Creates and add the vertex indices and face materials attribute from a landscape + static bool AddLandscapeMeshIndicesAndMaterialsAttribute( + const HAPI_NodeId& NodeId, + const bool& bExportMaterials, + const int32& ComponentSizeQuads, + const int32& QuadCount, + ALandscapeProxy * LandscapeProxy, + const TSet& SelectedComponents); + + // Add the global (detail) material and hole material attribute from a landscape + static bool AddLandscapeGlobalMaterialAttribute( + const HAPI_NodeId& NodeId, + ALandscapeProxy * LandscapeProxy); + + // Add landscape layer values as point attributes + static bool AddLandscapeLayerAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeLayerArray, + const FString& LayerName); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp index 4ddad9501..aac5ab692 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp @@ -1,4259 +1,4259 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealMeshTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "RawMesh.h" -#include "MeshDescription.h" -#include "MeshDescriptionOperations.h" -#include "Engine/StaticMesh.h" -#include "PhysicsEngine/BodySetup.h" -#include "Engine/StaticMeshSocket.h" -#include "Components/StaticMeshComponent.h" -#include "Materials/Material.h" -#include "Materials/MaterialInterface.h" -#include "MeshAttributes.h" -#include "StaticMeshAttributes.h" - -#if WITH_EDITOR - #include "EditorFramework/AssetImportData.h" -#endif - -bool -FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - UStaticMesh* StaticMesh, - HAPI_NodeId& InputNodeId, - const FString& InputNodeName, - UStaticMeshComponent* StaticMeshComponent /* = nullptr */, - const bool& ExportAllLODs /* = false */, - const bool& ExportSockets /* = false */, - const bool& ExportColliders /* = false */) -{ - // If we don't have a static mesh there's nothing to do. - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Node ID for the newly created node - HAPI_NodeId NewNodeId = -1; - - // Export sockets if there are some - bool DoExportSockets = ExportSockets && (StaticMesh->Sockets.Num() > 0); - - // Export LODs if there are some - bool DoExportLODs = ExportAllLODs && (StaticMesh->GetNumLODs() > 1); - - // Export colliders if there are some - bool DoExportColliders = ExportColliders && StaticMesh->BodySetup != nullptr; - if (DoExportColliders) - { - if (!StaticMesh->BodySetup) - { - DoExportColliders = false; - } - else - { - if (StaticMesh->BodySetup->AggGeom.GetElementCount() <= 0) - DoExportColliders = false; - } - } - - // We need to use a merge node if we export lods OR sockets - bool UseMergeNode = DoExportLODs || DoExportSockets || DoExportColliders; - if (UseMergeNode) - { - // TODO: - // What if OutInputNodeId already exists? - // Delete previous merge?/input? - - // Create a merge SOP asset. This will be our "InputNodeId" - // as all the different LOD meshes and sockets will be plugged into it - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/merge"), InputNodeName, true, &NewNodeId), false); - } - else - { - // No LODs/Sockets, we just need a single input node - // If InputNodeId is invalid, we need to create an input node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_ANSI(*InputNodeName)), false); - - if (!FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true)) - return false; - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); - */ - } - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) - return false; - - HAPI_NodeId PreviousInputNodeId = InputNodeId; - - // Update our input NodeId - InputNodeId = NewNodeId; - // Get our parent OBJ NodeID - HAPI_NodeId InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); - - // We have now created a valid new input node, delete the previous one - if (PreviousInputNodeId >= 0) - { - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); - } - } - - // TODO: - // Setting for lightmap resolution? - //const uint8 ExportMethod = 0; // Raw mesh - //const uint8 ExportMethod = 1; // Mesh description - const uint8 ExportMethod = 2; // LODResources (render mesh) - //bool bExportViaRawMesh = false; - - int32 NumLODsToExport = DoExportLODs ? StaticMesh->GetNumLODs() : 1; - for (int32 LODIndex = 0; LODIndex < NumLODsToExport; LODIndex++) - { - // Grab the LOD level. - FStaticMeshSourceModel & SrcModel = StaticMesh->GetSourceModel(LODIndex); - - // If we're using a merge node, we need to create a new input null - HAPI_NodeId CurrentLODNodeId = -1; - if (UseMergeNode) - { - // Create a new input node for the current LOD - const char * LODName = ""; - { - FString LOD = TEXT("lod") + FString::FromInt(LODIndex); - LODName = TCHAR_TO_UTF8(*LOD); - } - - // Create the node in this input object's OBJ node - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InputObjectNodeId, TEXT("null"), LODName, false, &CurrentLODNodeId), false); - } - else - { - // No merge node, just use the input node we created before - CurrentLODNodeId = NewNodeId; - } - - // Either export the current LOD Mesh by using RawMEsh or MeshDescription (legacy) - FMeshDescription* MeshDesc = nullptr; - // if (!bExportViaRawMesh) - if (ExportMethod == 1) - { - // This will either fetch the mesh description that is cached on the SrcModel - // or load it from bulk data / DDC once - if (SrcModel.MeshDescription.IsValid()) - { - MeshDesc = SrcModel.MeshDescription.Get(); - } - else - { - const double StartTime = FPlatformTime::Seconds(); - MeshDesc = StaticMesh->GetMeshDescription(LODIndex); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->GetMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - } - - bool bMeshSuccess = false; - if (ExportMethod == 1 && MeshDesc) - { - // Convert the Mesh using FMeshDescription - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForMeshDescription( - CurrentLODNodeId, - *MeshDesc, - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - else if (ExportMethod == 2) - { - // Convert the LOD Mesh using FStaticMeshLODResources - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( - CurrentLODNodeId, - StaticMesh->GetLODForExport(LODIndex), - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - else - { - // Convert the LOD Mesh using FRawMesh - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForRawMesh( - CurrentLODNodeId, - SrcModel, - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForRawMesh completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - - if (!bMeshSuccess) - continue; - - if (UseMergeNode) - { - // Connect the LOD node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, LODIndex, CurrentLODNodeId, 0), false); - } - } - - // next Index for adding nodes to the merge - int32 NextMergeIndex = NumLODsToExport; - if (DoExportColliders) - { - FKAggregateGeom SimpleColliders = StaticMesh->BodySetup->AggGeom; - - // Export BOX colliders - for (auto& CurBox : SimpleColliders.BoxElems) - { - FVector BoxCenter = CurBox.Center; - FVector BoxExtent = FVector(CurBox.X, CurBox.Y, CurBox.Z); - FRotator BoxRotation = CurBox.Rotation; - - HAPI_NodeId BoxNodeId = -1; - if (!CreateInputNodeForBox( - BoxNodeId, InputObjectNodeId, NextMergeIndex, - BoxCenter, BoxExtent, BoxRotation)) - continue; - - if (BoxNodeId < 0) - continue; - - // Connect the Box node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, BoxNodeId, 0), false); - - NextMergeIndex++; - } - - // Export SPHERE colliders - for (auto& CurSphere : SimpleColliders.SphereElems) - { - HAPI_NodeId SphereNodeId = -1; - if (!CreateInputNodeForSphere( - SphereNodeId, InputObjectNodeId, NextMergeIndex, - CurSphere.Center, CurSphere.Radius)) - continue; - - if (SphereNodeId < 0) - continue; - - // Connect the Sphere node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, SphereNodeId, 0), false); - - NextMergeIndex++; - } - - // Export CAPSULE colliders - for (auto& CurSphyl : SimpleColliders.SphylElems) - { - HAPI_NodeId SphylNodeId = -1; - if (!CreateInputNodeForSphyl( - SphylNodeId, InputObjectNodeId, NextMergeIndex, - CurSphyl.Center, CurSphyl.Rotation, CurSphyl.Radius, CurSphyl.Length)) - continue; - - if (SphylNodeId < 0) - continue; - - // Connect the capsule node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, SphylNodeId, 0), false); - - NextMergeIndex++; - } - - // Export CONVEX colliders - for (auto& CurConvex : SimpleColliders.ConvexElems) - { - HAPI_NodeId ConvexNodeId = -1; - if (!CreateInputNodeForConvex( - ConvexNodeId, InputObjectNodeId, NextMergeIndex, CurConvex)) - continue; - - if (ConvexNodeId < 0) - continue; - - // Connect the capsule node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, ConvexNodeId, 0), false); - - NextMergeIndex++; - } - } - - if (DoExportSockets && StaticMesh->Sockets.Num() > 0) - { - // Create an input node for the mesh sockets - HAPI_NodeId SocketsNodeId = -1; - if (CreateInputNodeForMeshSockets(StaticMesh->Sockets, InputObjectNodeId, SocketsNodeId)) - { - // We can connect the socket node to the merge node's last input. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), NewNodeId, NextMergeIndex, SocketsNodeId, 0), false); - - NextMergeIndex++; - } - else if (SocketsNodeId != -1) - { - // If we failed to properly export the sockets, clean up the created node - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), SocketsNodeId); - } - } - - // - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForMeshSockets( - const TArray& InMeshSocket, const HAPI_NodeId& InParentNodeId, HAPI_NodeId& OutSocketsNodeId) -{ - int32 NumSockets = InMeshSocket.Num(); - if (NumSockets <= 0) - return false; - - // Create a new input node for the sockets - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeId, TEXT("null"), "sockets", false, &OutSocketsNodeId), false); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.pointCount = NumSockets; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, &Part), false); - - // Create POS point attribute info. - HAPI_AttributeInfo AttributeInfoPos; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); - AttributeInfoPos.count = NumSockets; - AttributeInfoPos.tupleSize = 3; - AttributeInfoPos.exists = true; - AttributeInfoPos.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos), false); - - // Create Rot point attribute Info - HAPI_AttributeInfo AttributeInfoRot; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); - AttributeInfoRot.count = NumSockets; - AttributeInfoRot.tupleSize = 4; - AttributeInfoRot.exists = true; - AttributeInfoRot.owner = HAPI_ATTROWNER_POINT; - AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot), false); - - // Create scale point attribute Info - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = NumSockets; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); - - // Create the name attrib info - HAPI_AttributeInfo AttributeInfoName; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); - AttributeInfoName.count = NumSockets; - AttributeInfoName.tupleSize = 1; - AttributeInfoName.exists = true; - AttributeInfoName.owner = HAPI_ATTROWNER_POINT; - AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName), false); - - // Create the tag attrib info - HAPI_AttributeInfo AttributeInfoTag; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); - AttributeInfoTag.count = NumSockets; - AttributeInfoTag.tupleSize = 1; - AttributeInfoTag.exists = true; - AttributeInfoTag.owner = HAPI_ATTROWNER_POINT; - AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag), false); - - // Extract the sockets transform values - TArray SocketPos; - SocketPos.SetNumZeroed(NumSockets * 3); - TArray SocketRot; - SocketRot.SetNumZeroed(NumSockets * 4); - TArray SocketScale; - SocketScale.SetNumZeroed(NumSockets * 3); - - // raw string array for names and tag, will need to be free before returning - TArray SocketNames; - TArray SocketTags; - - // Lambda for freeing the const char array's memory and returning - auto FreeMemoryReturn = [&SocketNames, &SocketTags](const bool& bReturn) - { - // Frees the memory allocated by ExtractRawString for the names and tags - FHoudiniEngineUtils::FreeRawStringMemory(SocketNames); - FHoudiniEngineUtils::FreeRawStringMemory(SocketTags); - - return bReturn; - }; - - for (int32 Idx = 0; Idx < NumSockets; ++Idx) - { - UStaticMeshSocket* CurrentSocket = InMeshSocket[Idx]; - if (!CurrentSocket || CurrentSocket->IsPendingKill()) - continue; - - // Get the socket's transform and convert it to HapiTransform - FTransform SocketTransform(CurrentSocket->RelativeRotation, CurrentSocket->RelativeLocation, CurrentSocket->RelativeScale); - HAPI_Transform HapiSocketTransform; - FHoudiniApi::Transform_Init(&HapiSocketTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(SocketTransform, HapiSocketTransform); - - // Fill the attribute values - SocketPos[3 * Idx + 0] = HapiSocketTransform.position[0]; - SocketPos[3 * Idx + 1] = HapiSocketTransform.position[1]; - SocketPos[3 * Idx + 2] = HapiSocketTransform.position[2]; - - SocketRot[4 * Idx + 0] = HapiSocketTransform.rotationQuaternion[0]; - SocketRot[4 * Idx + 1] = HapiSocketTransform.rotationQuaternion[1]; - SocketRot[4 * Idx + 2] = HapiSocketTransform.rotationQuaternion[2]; - SocketRot[4 * Idx + 3] = HapiSocketTransform.rotationQuaternion[3]; - - SocketScale[3 * Idx + 0] = HapiSocketTransform.scale[0]; - SocketScale[3 * Idx + 1] = HapiSocketTransform.scale[1]; - SocketScale[3 * Idx + 2] = HapiSocketTransform.scale[2]; - - FString CurrentSocketName; - if (!CurrentSocket->SocketName.IsNone()) - CurrentSocketName = CurrentSocket->SocketName.ToString(); - else - CurrentSocketName = TEXT("Socket") + FString::FromInt(Idx); - SocketNames.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocketName)); - - if (!CurrentSocket->Tag.IsEmpty()) - SocketTags.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocket->Tag)); - else - SocketTags.Add(""); - } - - //we can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos, - SocketPos.GetData(), 0, AttributeInfoPos.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot, - SocketRot.GetData(), 0, AttributeInfoRot.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, - SocketScale.GetData(), 0, AttributeInfoScale.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName, - SocketNames.GetData(), 0, AttributeInfoName.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag, - SocketTags.GetData(), 0, AttributeInfoTag.count), - FreeMemoryReturn(false)); - - // We will also create the socket_details attributes - for (int32 Idx = 0; Idx < NumSockets; ++Idx) - { - // Build the current socket's prefix - FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(Idx); - - // Create mesh_socketX_pos attribute info. - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); - AttributeInfoPos.count = 1; - AttributeInfoPos.tupleSize = 3; - AttributeInfoPos.exists = true; - AttributeInfoPos.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; - - FString PosAttr = SocketAttrPrefix + TEXT("_pos"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos, - &(SocketPos[3 * Idx]), 0, AttributeInfoPos.count), - FreeMemoryReturn(false)); - - // Create mesh_socketX_rot point attribute Info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); - AttributeInfoRot.count = 1; - AttributeInfoRot.tupleSize = 4; - AttributeInfoRot.exists = true; - AttributeInfoRot.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; - - FString RotAttr = SocketAttrPrefix + TEXT("_rot"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot, - &(SocketRot[4 * Idx]), 0, AttributeInfoRot.count), - FreeMemoryReturn(false)); - - // Create mesh_socketX_scale point attribute Info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = 1; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - FString ScaleAttr = SocketAttrPrefix + TEXT("_scale"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale, - &(SocketScale[3 * Idx]), 0, AttributeInfoScale.count), - FreeMemoryReturn(false)); - - // Create the mesh_socketX_name attrib info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); - AttributeInfoName.count = 1; - AttributeInfoName.tupleSize = 1; - AttributeInfoName.exists = true; - AttributeInfoName.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; - - FString NameAttr = SocketAttrPrefix + TEXT("_name"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName, - &(SocketNames[Idx]), 0, AttributeInfoName.count), - FreeMemoryReturn(false)); - - // Create the mesh_socketX_tag attrib info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); - AttributeInfoTag.count = 1; - AttributeInfoTag.tupleSize = 1; - AttributeInfoTag.exists = true; - AttributeInfoTag.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; - - FString TagAttr = SocketAttrPrefix + TEXT("_tag"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag, - &(SocketTags[Idx]), 0, AttributeInfoTag.count), - FreeMemoryReturn(false)); - } - - // Now add the sockets group - const char * SocketGroupStr = "socket_imported"; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr), - FreeMemoryReturn(false)); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, NumSockets); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr, GroupArray.GetData(), 0, NumSockets), - FreeMemoryReturn(false)); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), OutSocketsNodeId), - FreeMemoryReturn(false)); - - return FreeMemoryReturn(true); -} - -bool -FUnrealMeshTranslator::CreateInputNodeForRawMesh( - const HAPI_NodeId& NodeId, - const FStaticMeshSourceModel& SourceModel, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent ) -{ - // Convert the Mesh using FRawMesh - FRawMesh RawMesh; - SourceModel.LoadRawMesh(RawMesh); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = RawMesh.WedgeIndices.Num(); - Part.faceCount = RawMesh.WedgeIndices.Num() / 3; - Part.pointCount = RawMesh.VertexPositions.Num(); - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = RawMesh.VertexPositions.Num(); - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Grab the build scale - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.VertexPositions.Num() > 3) - { - TArray StaticMeshVertices; - StaticMeshVertices.SetNumZeroed(RawMesh.VertexPositions.Num() * 3); - for (int32 VertexIdx = 0; VertexIdx < RawMesh.VertexPositions.Num(); ++VertexIdx) - { - // Convert Unreal to Houdini - const FVector & PositionVector = RawMesh.VertexPositions[VertexIdx]; - StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; - StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; - StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - for (int32 MeshTexCoordIdx = 0; MeshTexCoordIdx < MAX_STATIC_TEXCOORDS; MeshTexCoordIdx++) - { - int32 StaticMeshUVCount = RawMesh.WedgeTexCoords[MeshTexCoordIdx].Num(); - if (StaticMeshUVCount > 0) - { - const TArray & RawMeshUVs = RawMesh.WedgeTexCoords[MeshTexCoordIdx]; - TArray StaticMeshUVs; - StaticMeshUVs.Reserve(StaticMeshUVCount); - - // Transfer UV data. - for (int32 UVIdx = 0; UVIdx < StaticMeshUVCount; UVIdx++) - StaticMeshUVs.Emplace(RawMeshUVs[UVIdx].X, 1.0 - RawMeshUVs[UVIdx].Y, 0); - - // Convert Unreal to Houdini - // We need to re-index UVs for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - // We do not touch wedge 0 of this triangle, Swap 2 and 3 to reverse the winding order. - StaticMeshUVs.SwapMemory(WedgeIdx + 1, WedgeIdx + 2); - } - - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (MeshTexCoordIdx > 0) - UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = StaticMeshUVCount; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, (const float *)StaticMeshUVs.GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentZ.Num() > 0) - { - TArray ChangedNormals(RawMesh.WedgeTangentZ); - - // We need to re-index normals for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentZ1 = ChangedNormals[WedgeIdx + 1]; - FVector TangentZ2 = ChangedNormals[WedgeIdx + 2]; - - ChangedNormals[WedgeIdx + 1] = TangentZ2; - ChangedNormals[WedgeIdx + 2] = TangentZ1; - } - - // We also need to swap the vector's Y and Z components - for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedNormals[WedgeIdx].Y, ChangedNormals[WedgeIdx].Z); - - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedNormals.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, (const float *)ChangedNormals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentX.Num() > 0) - { - TArray ChangedTangentU(RawMesh.WedgeTangentX); - - // We need to re-index tangents for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentU1 = ChangedTangentU[WedgeIdx + 1]; - FVector TangentU2 = ChangedTangentU[WedgeIdx + 2]; - - ChangedTangentU[WedgeIdx + 1] = TangentU2; - ChangedTangentU[WedgeIdx + 2] = TangentU1; - } - - // We also need to swap the vector's Y and Z components - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedTangentU[WedgeIdx].Y, ChangedTangentU[WedgeIdx].Z); - - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedTangentU.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - (const float *)ChangedTangentU.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentY.Num() > 0) - { - TArray ChangedTangentV(RawMesh.WedgeTangentY); - // We need to re-index normals for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentV1 = ChangedTangentV[WedgeIdx + 1]; - FVector TangentV2 = ChangedTangentV[WedgeIdx + 2]; - - ChangedTangentV[WedgeIdx + 1] = TangentV2; - ChangedTangentV[WedgeIdx + 2] = TangentV1; - } - - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedTangentV[WedgeIdx].Y, ChangedTangentV[WedgeIdx].Z); - - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedTangentV.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - (const float *)ChangedTangentV.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - { - // If we have instance override vertex colors on the StaticMeshComponent, - // we first need to propagate them to our copy of the RawMesh Vert Colors - TArray ChangedColors; - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && - StaticMesh->RenderData && - StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; - FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) - { - // Use the wedge map if it is available as it is lossless. - int32 NumWedges = RawMesh.WedgeIndices.Num(); - if (RenderModel.WedgeMap.Num() == NumWedges) - { - int32 NumExistingColors = RawMesh.WedgeColors.Num(); - if (NumExistingColors < NumWedges) - { - RawMesh.WedgeColors.AddUninitialized(NumWedges - NumExistingColors); - } - - // Replace mesh colors with override colors - for (int32 i = 0; i < NumWedges; i++) - { - FColor WedgeColor = FColor::White; - int32 Index = RenderModel.WedgeMap[i]; - if (Index != INDEX_NONE) - { - WedgeColor = ColorVertexBuffer.VertexColor(Index); - } - RawMesh.WedgeColors[i] = WedgeColor; - } - } - } - } - - // See if we have colors to upload. - if (RawMesh.WedgeColors.Num() > 0) - { - ChangedColors.SetNumUninitialized(RawMesh.WedgeColors.Num()); - - // Convert Unreal to Houdini - // We need to re-index colors for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - ChangedColors[WedgeIdx + 0] = RawMesh.WedgeColors[WedgeIdx + 0].ReinterpretAsLinear(); - ChangedColors[WedgeIdx + 1] = RawMesh.WedgeColors[WedgeIdx + 2].ReinterpretAsLinear(); - ChangedColors[WedgeIdx + 2] = RawMesh.WedgeColors[WedgeIdx + 1].ReinterpretAsLinear(); - } - } - - if (ChangedColors.Num() > 0) - { - // Extract the RGB colors - TArray ColorValues; - ColorValues.SetNum(ChangedColors.Num() * 3); - for (int32 colorIndex = 0; colorIndex < ChangedColors.Num(); colorIndex++) - { - ColorValues[colorIndex * 3] = ChangedColors[colorIndex].R; - ColorValues[colorIndex * 3 + 1] = ChangedColors[colorIndex].G; - ColorValues[colorIndex * 3 + 2] = ChangedColors[colorIndex].B; - } - - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedColors.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - ColorValues.GetData(), 0, AttributeInfoVertex.count), false); - - // Create the attribute for Alpha - TArray AlphaValues; - AlphaValues.SetNum(ChangedColors.Num()); - for (int32 alphaIndex = 0; alphaIndex < ChangedColors.Num(); alphaIndex++) - AlphaValues[alphaIndex] = ChangedColors[alphaIndex].A; - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.count = AlphaValues.Num(); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - AlphaValues.GetData(), 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES (VertexList) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeIndices.Num() > 0) - { - TArray StaticMeshIndices; - StaticMeshIndices.SetNumUninitialized(RawMesh.WedgeIndices.Num()); - - // Convert Unreal to Houdini - for (int32 IndexIdx = 0; IndexIdx < RawMesh.WedgeIndices.Num(); IndexIdx += 3) - { - // Swap indices to fix winding order. - StaticMeshIndices[IndexIdx + 0] = RawMesh.WedgeIndices[IndexIdx + 0]; - StaticMeshIndices[IndexIdx + 1] = RawMesh.WedgeIndices[IndexIdx + 2]; - StaticMeshIndices[IndexIdx + 2] = RawMesh.WedgeIndices[IndexIdx + 1]; - } - - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, StaticMeshIndices.GetData(), 0, StaticMeshIndices.Num()), false); - - // We need to generate array of face counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, StaticMeshFaceCounts.GetData(), 0, StaticMeshFaceCounts.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - // Marshall face material indices. - if (RawMesh.FaceMaterialIndices.Num() > 0) - { - // Create an array of Material Interfaces - TArray MaterialInterfaces; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()) - { - // StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); - - int32 NumMeshBasedMaterials = StaticMeshComponent->GetNumMaterials(); - TArray MeshBasedMaterialInterfaces; - MeshBasedMaterialInterfaces.SetNumUninitialized(NumMeshBasedMaterials); - for (int32 i = 0; i < NumMeshBasedMaterials; i++) - MeshBasedMaterialInterfaces[i] = StaticMeshComponent->GetMaterial(i); - - int32 NumSections = StaticMesh->GetNumSections(InLODIndex); - MaterialInterfaces.SetNumUninitialized(NumSections); - for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) - { - FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex); - check(Info.MaterialIndex < NumMeshBasedMaterials); - MaterialInterfaces[SectionIndex] = MeshBasedMaterialInterfaces[Info.MaterialIndex]; - } - } - else - { - // Query the Static mesh's materials - for (int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++) - { - MaterialInterfaces.Add(StaticMesh->GetMaterial(MatIdx)); - } - - // Try to fix up inconsistencies between the RawMesh / StaticMesh material indexes - // by using the meshes sections... - // TODO: Fix me properly! - // Proper fix would be to export the meshes via the FStaticMeshLODResources obtained - // by GetLODForExport(), and then export the mesh by sections. - if (StaticMesh->RenderData && StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - TMap MapOfMaterials; - FStaticMeshLODResources& LODResources = StaticMesh->RenderData->LODResources[InLODIndex]; - for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) - { - // Get the material for each element at the current lod index - int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; - if (!MapOfMaterials.Contains(MaterialIndex)) - { - MapOfMaterials.Add(MaterialIndex, StaticMesh->GetMaterial(MaterialIndex)); - } - } - - if (MapOfMaterials.Num() > 0) - { - // Sort the output material in the correct order (by material index) - MapOfMaterials.KeySort([](int32 A, int32 B) { return A < B; }); - - // Set the value in the correct order - // Do not reduce the array of materials, this could cause crahses in some weird cases.. - if (MapOfMaterials.Num() > MaterialInterfaces.Num()) - MaterialInterfaces.SetNumZeroed(MapOfMaterials.Num()); - - int32 MaterialIndex = 0; - for (auto Kvp : MapOfMaterials) - { - MaterialInterfaces[MaterialIndex++] = Kvp.Value; - } - } - } - } - - // List of materials, one for each face. - TArray StaticMeshFaceMaterials; - - //Lists of material parameters - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - bool bAttributeSuccess = false; - bool bAddMaterialParametersAsAttributes = false; - - if (bAddMaterialParametersAsAttributes) - { - // Create attributes for the material and all its parameters - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - RawMesh.FaceMaterialIndices.Num(), - StaticMeshFaceMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - else - { - // Create attributes only for the materials - // Only get the material attribute data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials); - - // Create attribute for materials - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - RawMesh.FaceMaterialIndices.Num(), - StaticMeshFaceMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(StaticMeshFaceMaterials); - - // Delete texture material parameter names - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING MASKS - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.FaceSmoothingMasks.Num() > 0) - { - HAPI_AttributeInfo AttributeInfoSmoothingMasks; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - AttributeInfoSmoothingMasks.count = RawMesh.FaceSmoothingMasks.Num(); - AttributeInfoSmoothingMasks.tupleSize = 1; - AttributeInfoSmoothingMasks.exists = true; - AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - (const int32 *)RawMesh.FaceSmoothingMasks.GetData(), 0, RawMesh.FaceSmoothingMasks.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - TArray AllTags; - for (auto& ComponentTag : StaticMeshComponent->ComponentTags) - AllTags.AddUnique(ComponentTag); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - for (auto& ActorTag : ParentActor->Tags) - AllTags.AddUnique(ActorTag); - } - - // Try to create groups for the tags - if (!FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, AllTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups for the Static Mesh Component and Actor tags!")); - - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - /* - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - -bool -FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( - const HAPI_NodeId& NodeId, - const FStaticMeshLODResources& LODResources, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent) -{ - // Convert the Mesh using FStaticMeshLODResources - - // Check that the mesh is not empty - if (LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0) - { - HOUDINI_LOG_ERROR(TEXT("No vertices in mesh!")); - return false; - } - - if (LODResources.Sections.Num() == 0) - { - HOUDINI_LOG_ERROR(TEXT("No triangles in mesh!")); - return false; - } - - // Vertex instance and triangle counts - const uint32 OrigNumVertexInstances = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); - const uint32 NumTriangles = LODResources.GetNumTriangles(); - const uint32 NumVertexInstances = NumTriangles * 3; - const uint32 NumSections = LODResources.Sections.Num(); - - // Grab the build scale - const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - // In FStaticMeshLODResources each vertex instances stores its position, even if the positions are not unique (in other - // words, in Houdini terminology, the number of points and vertices are the same. We'll do the same thing that Epic - // does in FBX export: we'll run through all vertex instances and use a hash to determine which instances share a - // position, so that we can a smaller number of points than vertices, and vertices share point positions - TArray UEVertexInstanceIdxToPointIdx; - UEVertexInstanceIdxToPointIdx.Reserve(OrigNumVertexInstances); - - TMap PositionToPointIndexMap; - PositionToPointIndexMap.Reserve(OrigNumVertexInstances); - - TArray StaticMeshVertices; - StaticMeshVertices.Reserve(OrigNumVertexInstances * 3); - for (uint32 VertexInstanceIndex = 0; VertexInstanceIndex < OrigNumVertexInstances; ++VertexInstanceIndex) - { - // Convert Unreal to Houdini - const FVector &PositionVector = LODResources.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexInstanceIndex); - const int32 *FoundPointIndexPtr = PositionToPointIndexMap.Find(PositionVector); - if (!FoundPointIndexPtr) - { - const int32 NewPointIndex = StaticMeshVertices.Add(PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X) / 3; - StaticMeshVertices.Add(PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z); - StaticMeshVertices.Add(PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y); - - PositionToPointIndexMap.Add(PositionVector, NewPointIndex); - UEVertexInstanceIdxToPointIdx.Add(NewPointIndex); - } - else - { - UEVertexInstanceIdxToPointIdx.Add(*FoundPointIndexPtr); - } - } - - StaticMeshVertices.Shrink(); - const uint32 NumVertices = StaticMeshVertices.Num() / 3; - - // Now that we know how many vertices (points), vertex instances (vertices) and triagnles we have, - // we can create the part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumVertexInstances; - Part.faceCount = NumTriangles; - Part.pointCount = NumVertices; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = Part.pointCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - - // Determine which attributes we have - const bool bIsVertexInstanceNormalsValid = true; - const bool bIsVertexInstanceTangentsValid = true; - const bool bIsVertexInstanceBinormalsValid = true; - const bool bIsVertexInstanceColorsValid = LODResources.bHasColorVertexData; - const uint32 NumUVLayers = FMath::Min(LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(), MAX_STATIC_TEXCOORDS); - const bool bIsVertexInstanceUVsValid = NumUVLayers > 0; - - bool bUseComponentOverrideColors = false; - // Determine if have override colors on the static mesh component, if so prefer to use those - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (ColorVertexBuffer.GetNumVertices() == LODResources.GetNumVertices()) - { - bUseComponentOverrideColors = true; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL INDEX -> MATERIAL INTERFACE - //--------------------------------------------------------------------------------------------------------------------- - TArray MaterialInterfaces; - TArray TriangleMaterialIndices; - - // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); - const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); - // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, - // then we will set UEDefaultMaterial at the invalid index - int32 UEDefaultMaterialIndex = INDEX_NONE; - UMaterialInterface *UEDefaultMaterial = nullptr; - if (NumStaticMaterials > 0) - { - MaterialInterfaces.Reserve(NumStaticMaterials); - for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) - { - const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; - UMaterialInterface *Material = nullptr; - if (bIsStaticMeshComponentValid) - { - Material = StaticMeshComponent->GetMaterial(MaterialIndex); - } - else - { - Material = MaterialInfo.MaterialInterface; - } - // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - UEDefaultMaterialIndex = MaterialIndex; - } - Material = UEDefaultMaterial; - HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); - } - // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); - MaterialInterfaces.Add(Material); - } - - TriangleMaterialIndices.Reserve(NumTriangles); - } - - // If we haven't created UEDefaultMaterial yet, check that all the sections' MaterialIndex - // is valid, if not, create UEDefaultMaterial and add to MaterialInterfaces to get UEDefaultMaterialIndex - if (!UEDefaultMaterial || UEDefaultMaterialIndex == INDEX_NONE) - { - for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) - { - // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial - // up to and including MaterialIndex and log a warning - const int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; - if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - // Add the UEDefaultMaterial to MaterialInterfaces - UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); - } - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); - } - } - } - - // Determine the final number of materials we have, with default for missing/invalid indices - const int32 NumMaterials = MaterialInterfaces.Num(); - - // Now we deal with vertex instance attributes. - if (NumTriangles > 0) - { - // UV layer array. Each layer has an array of floats, 3 floats per vertex instance - TArray> UVs; - // Normals: 3 floats per vertex instance - TArray Normals; - // Tangents: 3 floats per vertex instance - TArray Tangents; - // Binormals: 3 floats per vertex instance - TArray Binormals; - // RGBColors: 3 floats per vertex instance - TArray RGBColors; - // Alphas: 1 float per vertex instance - TArray Alphas; - - // Initialize the arrays for the attributes that are valid - if (bIsVertexInstanceUVsValid) - { - UVs.SetNum(NumUVLayers); - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); - } - } - - if (bIsVertexInstanceNormalsValid) - { - Normals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceTangentsValid) - { - Tangents.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceBinormalsValid) - { - Binormals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - RGBColors.SetNumUninitialized(NumVertexInstances * 3); - Alphas.SetNumUninitialized(NumVertexInstances); - } - - // Array of vertex (point position) indices per triangle - TArray MeshTriangleVertexIndices; - MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); - // Array of vertex counts per triangle/face - TArray MeshTriangleVertexCounts; - MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); - - int32 TriangleIdx = 0; - int32 HoudiniVertexIdx = 0; - FIndexArrayView TriangleVertexIndices = LODResources.IndexBuffer.GetArrayView(); - for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) - { - const FStaticMeshSection& Section = LODResources.Sections[SectionIndex]; - for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < Section.NumTriangles; ++SectionTriangleIndex) - { - MeshTriangleVertexCounts[TriangleIdx] = 3; - for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) - { - // Reverse the winding order for Houdini (but still start at 0) - const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; - const uint32 UEVertexIndex = TriangleVertexIndices[Section.FirstIndex + SectionTriangleIndex * 3 + WindingIdx]; - - // Calculate the index of the first component of a vertex instance's value in an inline float array - // representing vectors (3 float) per vertex instance - const int32 Float3Index = HoudiniVertexIdx * 3; - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - const FVector2D &UV = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(UEVertexIndex, UVLayerIndex); - UVs[UVLayerIndex][Float3Index + 0] = UV.X; - UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; - UVs[UVLayerIndex][Float3Index + 2] = 0; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - const FVector &Normal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(UEVertexIndex); - Normals[Float3Index + 0] = Normal.X; - Normals[Float3Index + 1] = Normal.Z; - Normals[Float3Index + 2] = Normal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - const FVector &Tangent = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(UEVertexIndex); - Tangents[Float3Index + 0] = Tangent.X; - Tangents[Float3Index + 1] = Tangent.Z; - Tangents[Float3Index + 2] = Tangent.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - // In order to calculate the binormal we also need the tangent and normal - if (bIsVertexInstanceBinormalsValid) - { - FVector Binormal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(UEVertexIndex); - Binormals[Float3Index + 0] = Binormal.X; - Binormals[Float3Index + 1] = Binormal.Z; - Binormals[Float3Index + 2] = Binormal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - FVector4 Color = FLinearColor::White; - if (bUseComponentOverrideColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - Color = ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); - } - else - { - Color = LODResources.VertexBuffers.ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); - } - RGBColors[Float3Index + 0] = Color[0]; - RGBColors[Float3Index + 1] = Color[1]; - RGBColors[Float3Index + 2] = Color[2]; - Alphas[HoudiniVertexIdx] = Color[3]; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - if (UEVertexInstanceIdxToPointIdx.IsValidIndex(UEVertexIndex)) - { - MeshTriangleVertexIndices[HoudiniVertexIdx] = UEVertexInstanceIdxToPointIdx[UEVertexIndex]; - } - - HoudiniVertexIdx++; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - if (MaterialInterfaces.IsValidIndex(Section.MaterialIndex)) - { - TriangleMaterialIndices.Add(Section.MaterialIndex); - } - else - { - TriangleMaterialIndices.Add(UEDefaultMaterialIndex); - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, Section.MaterialIndex, *(UEDefaultMaterial->GetPathName())); - } - - TriangleIdx++; - } - } - - // Now transfer valid vertex instance attributes to Houdini vertex attributes - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) - { - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (UVLayerIndex > 0) - UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = NumVertexInstances; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, Normals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - Tangents.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceBinormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - Binormals.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - RGBColors.GetData(), 0, AttributeInfoVertex.count), false); - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.count = Alphas.Num(); - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - Alphas.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); - - // Send the array of face vertex counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); - - // Send material assignments to Houdini - if (NumMaterials > 0) - { - // List of materials, one for each face. - TArray TriangleMaterials; - - //Lists of material parameters - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - bool bAttributeSuccess = false; - bool bAddMaterialParametersAsAttributes = false; - - if (bAddMaterialParametersAsAttributes) - { - // Create attributes for the material and all its parameters - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - else - { - // Create attributes only for the materials - // Only get the material attribute data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); - - // Create attribute for materials - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); - - // Delete texture material parameter names - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - } - - // TODO: The render mesh (LODResources) does not have face smoothing information, and the raw mesh triangle order is - // potentially different (see also line 4152 TODO_FBX in Engine\Source\Editor\UnrealEd\Private\Fbx\FbxMainExport.cpp - ////--------------------------------------------------------------------------------------------------------------------- - //// TRIANGLE SMOOTHING MASKS - ////--------------------------------------------------------------------------------------------------------------------- - //TArray TriangleSmoothingMasks; - //TriangleSmoothingMasks.SetNumZeroed(NumTriangles); - //FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); - //if (TriangleSmoothingMasks.Num() > 0) - //{ - // HAPI_AttributeInfo AttributeInfoSmoothingMasks; - // FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - // AttributeInfoSmoothingMasks.tupleSize = 1; - // AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); - // AttributeInfoSmoothingMasks.exists = true; - // AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - // AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - // AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - // FHoudiniEngine::Get().GetSession(), - // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - // FHoudiniEngine::Get().GetSession(), - // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - // (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); - //} - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - // Try to create groups for the static mesh component's tags - if (StaticMeshComponent->ComponentTags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Try to create groups for the parent Actor's tags - if (ParentActor->Tags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - - /* - // Add the unreal_level_path attribute - FString LevelPath = ActorPath;// FString(); - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - -bool -FUnrealMeshTranslator::CreateInputNodeForMeshDescription( - const HAPI_NodeId& NodeId, - const FMeshDescription& MeshDescription, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent) -{ - // Convert the Mesh using FMeshDescription - // Get references to the attributes we are interested in - // before sending to Houdini we'll check if each attribute is valid - FStaticMeshConstAttributes MeshDescriptionAttributes(MeshDescription); - - TVertexAttributesConstRef VertexPositions = MeshDescriptionAttributes.GetVertexPositions(); - TVertexInstanceAttributesConstRef VertexInstanceNormals = MeshDescriptionAttributes.GetVertexInstanceNormals(); - TVertexInstanceAttributesConstRef VertexInstanceTangents = MeshDescriptionAttributes.GetVertexInstanceTangents(); - TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = MeshDescriptionAttributes.GetVertexInstanceBinormalSigns(); - TVertexInstanceAttributesConstRef VertexInstanceColors = MeshDescriptionAttributes.GetVertexInstanceColors(); - TVertexInstanceAttributesConstRef VertexInstanceUVs = MeshDescriptionAttributes.GetVertexInstanceUVs(); - //TPolygonGroupAttributesConstRef PolygonGroupMaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames(); - - // Get the vertex and triangle arrays - const FVertexArray &MDVertices = MeshDescription.Vertices(); - const FPolygonGroupArray &MDPolygonGroups = MeshDescription.PolygonGroups(); - const FPolygonArray &MDPolygons = MeshDescription.Polygons(); - const FTriangleArray &MDTriangles = MeshDescription.Triangles(); - - // Determine point, vertex and polygon counts - const uint32 NumVertices = MDVertices.Num(); - const uint32 NumTriangles = MDTriangles.Num(); - const uint32 NumVertexInstances = NumTriangles * 3; - - // Some checks: we expect triangulated meshes - if (MeshDescription.VertexInstances().Num() != NumTriangles * 3) - { - HOUDINI_LOG_ERROR(TEXT("Expected a triangulated mesh, but # VertexInstances (%d) != # Triangles * 3 (%d)"), MeshDescription.VertexInstances().Num(), NumTriangles * 3); - return false; - } - - // Determine which attributes we have - const bool bIsVertexPositionsValid = VertexPositions.IsValid(); - const bool bIsVertexInstanceNormalsValid = VertexInstanceNormals.IsValid(); - const bool bIsVertexInstanceTangentsValid = VertexInstanceTangents.IsValid(); - const bool bIsVertexInstanceBinormalSignsValid = VertexInstanceBinormalSigns.IsValid(); - const bool bIsVertexInstanceColorsValid = VertexInstanceColors.IsValid(); - const bool bIsVertexInstanceUVsValid = VertexInstanceUVs.IsValid(); - //const bool bIsPolygonGroupImportedMaterialSlotNamesValid = PolygonGroupMaterialSlotNames.IsValid(); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumVertexInstances; - Part.faceCount = NumTriangles; - Part.pointCount = NumVertices; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = Part.pointCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Grab the build scale - const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - // The mesh element arrays are sparse: the max index/ID value can be larger than the number of elements - 1 - // so we have to maintain a lookup of VertexID (UE) to PointIndex (Houdini) - TArray VertexIDToHIndex; - if (bIsVertexPositionsValid && VertexPositions.GetNumElements() >= 3) - { - TArray StaticMeshVertices; - StaticMeshVertices.SetNumUninitialized(NumVertices * 3); - - int32 VertexIdx = 0; - VertexIDToHIndex.Init(INDEX_NONE, MDVertices.GetArraySize()); - - for (const FVertexID& VertexID : MDVertices.GetElementIDs()) - { - // Convert Unreal to Houdini - const FVector &PositionVector = VertexPositions.Get(VertexID); - StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; - StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; - StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; - - // Record the UE Vertex ID to Houdini Point Index lookup - VertexIDToHIndex[VertexID.GetValue()] = VertexIdx; - VertexIdx++; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - } - - bool bUseComponentOverrideColors = false; - // Determine if have override colors on the static mesh component, if so prefer to use those - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && - StaticMesh->RenderData && - StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; - FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) - { - // Use the wedge map if it is available as it is lossless. - if (RenderModel.WedgeMap.Num() == NumVertexInstances) - { - bUseComponentOverrideColors = true; - } - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL SLOT -> MATERIAL INTERFACE - //--------------------------------------------------------------------------------------------------------------------- - // In theory the ImportedMaterialSlotName attribute on PolygonGroups should tell us which material slot is used by - // that group, and thus which MaterialIndex we should assign to triangles in that group. Unfortunately we have - // encountered cases where the ImportedMaterialSlotName name attribute does not match any of the MaterialSlotName or - // ImportedMaterialSlotNames in the StaticMesh->StaticMaterials array. Therefore we have no choice but to rely - // on the PolygonGroup order vs Section order to determine the MaterialIndex for a group. We do what Epic does - // when building a static mesh: Sections are created in the same order as iterating over PolygonGroups, but importantly, - // empty PolygonGroups are skipped - - // // Get material slot name to material index - // and the UMaterialInterface array - // TMap MaterialSlotToInterface; - TArray MaterialInterfaces; - TArray TriangleMaterialIndices; - - // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); - const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); - // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, - // then we will set UEDefaultMaterial at the invalid index - int32 UEDefaultMaterialIndex = INDEX_NONE; - UMaterialInterface *UEDefaultMaterial = nullptr; - if (NumStaticMaterials > 0) - { - MaterialInterfaces.Reserve(NumStaticMaterials); - for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) - { - const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; - UMaterialInterface *Material = nullptr; - if (bIsStaticMeshComponentValid) - { - Material = StaticMeshComponent->GetMaterial(MaterialIndex); - } - else - { - Material = MaterialInfo.MaterialInterface; - } - // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - UEDefaultMaterialIndex = MaterialIndex; - } - Material = UEDefaultMaterial; - HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); - } - // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); - MaterialInterfaces.Add(Material); - } - - TriangleMaterialIndices.Reserve(NumTriangles); - } - // SectionIndex: Looking at Epic's StaticMesh build code, Sections are created in the same - // order as iterating over PolygonGroups, but skipping empty PolygonGroups - TMap PolygonGroupToMaterialIndex; - PolygonGroupToMaterialIndex.Reserve(MeshDescription.PolygonGroups().Num()); - int32 SectionIndex = 0; - for (const FPolygonGroupID &PolygonGroupID : MDPolygonGroups.GetElementIDs()) - { - // Skip empty polygon groups - if (MeshDescription.GetNumPolygonGroupPolygons(PolygonGroupID) == 0) - { - continue; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - // // Get the material index for the material slot for this polygon group - //int32 MaterialIndex = INDEX_NONE; - //if (bIsPolygonGroupImportedMaterialSlotNamesValid) - //{ - // const FName &MaterialSlotName = PolygonGroupMaterialSlotNames.Get(PolygonGroupID); - // const int32 *MaterialIndexPtr = MaterialSlotToInterface.Find(MaterialSlotName); - // if (MaterialIndexPtr != nullptr) - // { - // MaterialIndex = *MaterialIndexPtr; - // } - //} - - // Get the material for the LOD and section via the section info map - if (StaticMesh->GetNumSections(InLODIndex) <= SectionIndex) - { - HOUDINI_LOG_ERROR(TEXT("Found more non-empty polygon groups in the mesh description for LOD %d than sections in the static mesh..."), InLODIndex); - return false; - } - - // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial - // up to and including MaterialIndex and log a warning - int32 MaterialIndex = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex).MaterialIndex; - if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - // Add the UEDefaultMaterial to MaterialInterfaces - UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); - } - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); - MaterialIndex = UEDefaultMaterialIndex; - } - - PolygonGroupToMaterialIndex.Add(PolygonGroupID, MaterialIndex); - SectionIndex++; - } - - // Determine the final number of materials we have, with defaults for missing/invalid indices - const int32 NumMaterials = MaterialInterfaces.Num(); - - // Now we deal with vertex instance attributes. - // // First we must also record a UE VertexInstanceID to Houdini Vertex Index lookup, - // // and then get and convert all valid and supported vertex instance attributes from UE - // TArray VertexInstanceIDToHIndex; - - if (NumTriangles > 0) - { - // UV layer array. Each layer has an array of floats, 3 floats per vertex instance - TArray> UVs; - const int32 NumUVLayers = bIsVertexInstanceUVsValid ? FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_STATIC_TEXCOORDS) : 0; - // Normals: 3 floats per vertex instance - TArray Normals; - // Tangents: 3 floats per vertex instance - TArray Tangents; - // Binormals: 3 floats per vertex instance - TArray Binormals; - // RGBColors: 3 floats per vertex instance - TArray RGBColors; - // Alphas: 1 float per vertex instance - TArray Alphas; - - // Initialize the arrays for the attributes that are valid - if (bIsVertexInstanceUVsValid) - { - UVs.SetNum(NumUVLayers); - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); - } - } - - if (bIsVertexInstanceNormalsValid) - { - Normals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceTangentsValid) - { - Tangents.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceBinormalSignsValid) - { - Binormals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - RGBColors.SetNumUninitialized(NumVertexInstances * 3); - Alphas.SetNumUninitialized(NumVertexInstances); - } - - // Array of material index per triangle/face - TArray MeshTriangleVertexIndices; - MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); - // Array of vertex counts per triangle/face - TArray MeshTriangleVertexCounts; - MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); - - int32 TriangleIdx = 0; - int32 VertexInstanceIdx = 0; - // VertexInstanceIDToHIndex.Init(-1, MDVertexInstances.GetArraySize()); - - for (const FPolygonID &PolygonID : MDPolygons.GetElementIDs()) - { - for (const FTriangleID &TriangleID : MeshDescription.GetPolygonTriangleIDs(PolygonID)) - { - MeshTriangleVertexCounts[TriangleIdx] = 3; - for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) - { - // Reverse the winding order for Houdini (but still start at 0) - const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; - const FVertexInstanceID &VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, WindingIdx); - - // // UE Vertex Instance ID to Houdini Vertex Index look up - // VertexInstanceIDToHIndex[VertexInstanceID.GetValue()] = VertexInstanceIdx; - - // Calculate the index of the first component of a vertex instance's value in an inline float array - // representing vectors (3 float) per vertex instance - const int32 Float3Index = VertexInstanceIdx * 3; - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - const FVector2D &UV = VertexInstanceUVs.Get(VertexInstanceID, UVLayerIndex); - UVs[UVLayerIndex][Float3Index + 0] = UV.X; - UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; - UVs[UVLayerIndex][Float3Index + 2] = 0; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - const FVector &Normal = VertexInstanceNormals.Get(VertexInstanceID); - Normals[Float3Index + 0] = Normal.X; - Normals[Float3Index + 1] = Normal.Z; - Normals[Float3Index + 2] = Normal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - const FVector &Tangent = VertexInstanceTangents.Get(VertexInstanceID); - Tangents[Float3Index + 0] = Tangent.X; - Tangents[Float3Index + 1] = Tangent.Z; - Tangents[Float3Index + 2] = Tangent.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - // In order to calculate the binormal we also need the tangent and normal - if (bIsVertexInstanceBinormalSignsValid && bIsVertexInstanceTangentsValid && bIsVertexInstanceNormalsValid) - { - const float &BinormalSign = VertexInstanceBinormalSigns.Get(VertexInstanceID); - FVector Binormal = FVector::CrossProduct( - FVector(Tangents[Float3Index + 0], Tangents[Float3Index + 1], Tangents[Float3Index + 2]), - FVector(Normals[Float3Index + 0], Normals[Float3Index + 1], Normals[Float3Index + 2]) - ) * BinormalSign; - Binormals[Float3Index + 0] = Binormal.X; - Binormals[Float3Index + 1] = Binormal.Y; - Binormals[Float3Index + 2] = Binormal.Z; - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - FVector4 Color = FLinearColor::White; - if (bUseComponentOverrideColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshLODResources& RenderModel = StaticMesh->RenderData->LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - int32 Index = RenderModel.WedgeMap[VertexInstanceIdx]; - if (Index != INDEX_NONE) - { - Color = ColorVertexBuffer.VertexColor(Index).ReinterpretAsLinear(); - } - } - else - { - Color = VertexInstanceColors.Get(VertexInstanceID); - } - RGBColors[Float3Index + 0] = Color[0]; - RGBColors[Float3Index + 1] = Color[1]; - RGBColors[Float3Index + 2] = Color[2]; - Alphas[VertexInstanceIdx] = Color[3]; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - const FVertexID& VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID); - const int32 UEVertexIdx = VertexID.GetValue(); - if (VertexIDToHIndex.IsValidIndex(UEVertexIdx)) - { - MeshTriangleVertexIndices[VertexInstanceIdx] = VertexIDToHIndex[UEVertexIdx]; - } - - VertexInstanceIdx++; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - const FPolygonGroupID &PolygonGroupID = MeshDescription.GetPolygonPolygonGroup(PolygonID); - const int32 MaterialIndex = PolygonGroupToMaterialIndex.FindChecked(PolygonGroupID); - TriangleMaterialIndices.Add(MaterialIndex); - - TriangleIdx++; - } - } - - // Now transfer valid vertex instance attributes to Houdini vertex attributes - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) - { - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (UVLayerIndex > 0) - UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = NumVertexInstances; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, Normals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - Tangents.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceBinormalSignsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - Binormals.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - RGBColors.GetData(), 0, AttributeInfoVertex.count), false); - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.count = Alphas.Num(); - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - Alphas.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); - - // Send the array of face vertex counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); - - // Send material assignments to Houdini - if (NumMaterials > 0) - { - // List of materials, one for each face. - TArray TriangleMaterials; - - //Lists of material parameters - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - bool bAttributeSuccess = false; - bool bAddMaterialParametersAsAttributes = false; - - if (bAddMaterialParametersAsAttributes) - { - // Create attributes for the material and all its parameters - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - else - { - // Create attributes only for the materials - // Only get the material attribute data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); - - // Create attribute for materials - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); - - // Delete texture material parameter names - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (bAttributeSuccess) - { - check(0); - return false; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE SMOOTHING MASKS - //--------------------------------------------------------------------------------------------------------------------- - TArray TriangleSmoothingMasks; - TriangleSmoothingMasks.SetNumZeroed(NumTriangles); - FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); - if (TriangleSmoothingMasks.Num() > 0) - { - HAPI_AttributeInfo AttributeInfoSmoothingMasks; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - AttributeInfoSmoothingMasks.tupleSize = 1; - AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); - AttributeInfoSmoothingMasks.exists = true; - AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - // Try to create groups for the static mesh component's tags - if (StaticMeshComponent->ComponentTags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Try to create groups for the parent Actor's tags - if (ParentActor->Tags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - - /* - FString LevelPath = FString(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - -void -FUnrealMeshTranslator::CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray& OutStaticMeshFaceMaterials) -{ - // We need to create list of unique materials. - TArray UniqueMaterialList; - - UMaterialInterface * MaterialInterface = nullptr; - char* UniqueName = nullptr; - - UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); - - if (Materials.Num()) - { - // We have materials. - for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) - { - UniqueName = nullptr; - MaterialInterface = Materials[MaterialIdx]; - if (!MaterialInterface) - { - // Null material interface found, add default instead. - UniqueMaterialList.Add(DefaultMaterialName); - - // No need to collect material parameters on the default material - continue; - } - - // We found a material, get its name and material parameters - FString FullMaterialName = MaterialInterface->GetPathName(); - UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); - UniqueMaterialList.Add(UniqueName); - } - } - else - { - // We do not have any materials, add default. - UniqueMaterialList.Add(DefaultMaterialName); - } - - // TODO: Needs to be improved! - // We shouldnt be testing for each face, but only for each unique facematerial value... - for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) - { - int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; - if (UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) - { - OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); - } - else - { - OutStaticMeshFaceMaterials.Add(DefaultMaterialName); - } - } -} - - -void -FUnrealMeshTranslator::CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray& OutStaticMeshFaceMaterials, - TMap> & OutScalarMaterialParameters, - TMap> & OutVectorMaterialParameters, - TMap> & OutTextureMaterialParameters) -{ - // We need to create list of unique materials. - TArray UniqueMaterialList; - - UMaterialInterface * MaterialInterface = nullptr; - char* UniqueName = nullptr; - - UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); - - // Initialize material parameter arrays - TMap> ScalarParams; - TMap> VectorParams; - TMap> TextureParams; - - if (Materials.Num()) - { - // We have materials. - for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) - { - UniqueName = nullptr; - MaterialInterface = Materials[MaterialIdx]; - if (!MaterialInterface) - { - // Null material interface found, add default instead. - UniqueMaterialList.Add(DefaultMaterialName); - - // No need to collect material parameters on the default material - continue; - } - - // We found a material, get its name and material parameters - FString FullMaterialName = MaterialInterface->GetPathName(); - UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); - UniqueMaterialList.Add(UniqueName); - - // Collect all scalar parameters in all materials - { - TArray MaterialScalarParamInfos; - TArray MaterialScalarParamGuids; - MaterialInterface->GetAllScalarParameterInfo(MaterialScalarParamInfos, MaterialScalarParamGuids); - - for (auto & CurScalarParam : MaterialScalarParamInfos) - { - FString CurScalarParamName = CurScalarParam.Name.ToString(); - float CurScalarVal; - MaterialInterface->GetScalarParameterValue(CurScalarParam, CurScalarVal); - if (!ScalarParams.Contains(CurScalarParamName)) - { - TArray CurArray; - CurArray.SetNumUninitialized(Materials.Num()); - // Initialize the array with the Min float value - for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) - CurArray[ArrIdx] = FLT_MIN; - - ScalarParams.Add(CurScalarParamName, CurArray); - OutScalarMaterialParameters.Add(CurScalarParamName); - } - - ScalarParams[CurScalarParamName][MaterialIdx] = CurScalarVal; - } - } - - // Collect all vector parameters in all materials - { - TArray MaterialVectorParamInfos; - TArray MaterialVectorParamGuids; - MaterialInterface->GetAllVectorParameterInfo(MaterialVectorParamInfos, MaterialVectorParamGuids); - - for (auto & CurVectorParam : MaterialVectorParamInfos) - { - FString CurVectorParamName = CurVectorParam.Name.ToString(); - FLinearColor CurVectorValue; - MaterialInterface->GetVectorParameterValue(CurVectorParam, CurVectorValue); - if (!VectorParams.Contains(CurVectorParamName)) - { - TArray CurArray; - CurArray.SetNumUninitialized(Materials.Num()); - FLinearColor MinColor(FLT_MIN, FLT_MIN, FLT_MIN, FLT_MIN); - for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) - CurArray[ArrIdx] = MinColor; - - VectorParams.Add(CurVectorParamName, CurArray); - OutVectorMaterialParameters.Add(CurVectorParamName); - } - - VectorParams[CurVectorParamName][MaterialIdx] = CurVectorValue; - } - } - - // Collect all texture parameters in all materials - { - TArray MaterialTextureParamInfos; - TArray MaterialTextureParamGuids; - MaterialInterface->GetAllTextureParameterInfo(MaterialTextureParamInfos, MaterialTextureParamGuids); - - for (auto & CurTextureParam : MaterialTextureParamInfos) - { - FString CurTextureParamName = CurTextureParam.Name.ToString(); - UTexture * CurTexture = nullptr; - MaterialInterface->GetTextureParameterValue(CurTextureParam, CurTexture); - - if (!CurTexture || CurTexture->IsPendingKill()) - continue; - - FString TexturePath = CurTexture->GetPathName(); - if (!TextureParams.Contains(CurTextureParamName)) - { - TArray CurArray; - CurArray.SetNumZeroed(Materials.Num()); - - TextureParams.Add(CurTextureParamName, CurArray); - OutTextureMaterialParameters.Add(CurTextureParamName); - } - - char * TexturePathRawStr = UniqueName = FHoudiniEngineUtils::ExtractRawString(TexturePath); - TextureParams[CurTextureParamName][MaterialIdx] = TexturePathRawStr; - } - } - - } - } - else - { - // We do not have any materials, add default. - UniqueMaterialList.Add(DefaultMaterialName); - } - - // TODO: Needs to be improved! - // We shouldnt be testing for each face, but only for each unique facematerial value... - for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) - { - int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; - if(UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) - { - OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); - - for (auto & Pair : ScalarParams) - { - OutScalarMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); - } - - for (auto & Pair : VectorParams) - { - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].R); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].G); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].B); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].A); - } - - for (auto & Pair : TextureParams) - { - OutTextureMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); - } - } - else - { - OutStaticMeshFaceMaterials.Add(DefaultMaterialName); - } - } -} - - -void -FUnrealMeshTranslator::DeleteFaceMaterialArray(TArray& OutStaticMeshFaceMaterials) -{ - // Clean up the memory allocated by CreateFaceMaterialArray() - TSet UniqueMaterials(OutStaticMeshFaceMaterials); - for (TSet::TIterator Iter = UniqueMaterials.CreateIterator(); Iter; ++Iter) - { - char* MaterialName = *Iter; - FMemory::Free(MaterialName); - } - - OutStaticMeshFaceMaterials.Empty(); -} - -bool -FUnrealMeshTranslator::CreateInputNodeForBox( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& BoxCenter, - const FVector& BoxExtent, - const FRotator& BoxRotation) -{ - // Create a new input node for the box collider - FString BoxName = TEXT("box") + FString::FromInt(ColliderIndex); - - // Create the node in this input object's OBJ node - HAPI_NodeId BoxNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("box"), BoxName, false, &BoxNodeId), false); - - // Set the box parameters - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 0, BoxExtent.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 1, BoxExtent.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 2, BoxExtent.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 0, BoxCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 1, BoxCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 2, BoxCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 0, BoxRotation.Roll); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 2, BoxRotation.Pitch); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 1, BoxRotation.Yaw); - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), BoxNodeId, &CookOptions); - */ - if (!FHoudiniEngineUtils::HapiCookNode(BoxNodeId, nullptr, true)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmInfo ParmInfo; - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_box") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, BoxNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForSphere( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphereCenter, - const float& SphereRadius) -{ - // Create a new input node for the sphere collider - const char * SphereName = ""; - { - FString SPH = TEXT("Sphere") + FString::FromInt(ColliderIndex); - SphereName = TCHAR_TO_UTF8(*SPH); - } - - // Create the node in this input object's OBJ node - HAPI_NodeId SphereNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "sphere", SphereName, false, &SphereNodeId), false); - - // Set the box parameters - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 1, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 2, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 0, SphereCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 1, SphereCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 2, SphereCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "type", 0, 1); - /* - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "scale", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - */ - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), SphereNodeId, &CookOptions); - */ - if (!FHoudiniEngineUtils::HapiCookNode(SphereNodeId, nullptr, true)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmInfo ParmInfo; - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_sphere") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphereNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForSphyl( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphylCenter, - const FRotator& SphylRotation, - const float& SphylRadius, - const float& SphereLength) -{ - // - // Get the Sphyl's vertices and indices - // (code drived from FKSphylElem::GetElemSolid) - // - - // TODO: - // Rotation? - - const int32 NumSides = 6; - const int32 NumRings = (NumSides / 2) + 1; - - // The first/last arc are on top of each other. - const int32 NumVerts = (NumSides + 1) * (NumRings + 1); - - // Calculate the vertices for one arc - TArray ArcVertices; - ArcVertices.SetNum(NumRings + 1); - for (int32 RingIdx = 0; RingIdx < NumRings + 1; RingIdx++) - { - float Angle; - float ZOffset; - if (RingIdx <= NumSides / 4) - { - Angle = ((float)RingIdx / (NumRings - 1)) * PI; - ZOffset = 0.5 * SphereLength; - } - else - { - Angle = ((float)(RingIdx - 1) / (NumRings - 1)) * PI; - ZOffset = -0.5 * SphereLength; - } - - // Note- unit sphere, so position always has mag of one. We can just use it for normal! - FVector SpherePos; - SpherePos.X = 0.0f; - SpherePos.Y = SphylRadius * FMath::Sin(Angle); - SpherePos.Z = SphylRadius * FMath::Cos(Angle); - - ArcVertices[RingIdx] = SpherePos + FVector(0, 0, ZOffset); - } - - // Get the transform matrix for the rotation - FRotationMatrix SphylRotMatrix(SphylRotation); - - // Get the Sphyl's vertices by rotating the arc NumSides+1 times. - TArray Vertices; - Vertices.SetNum(NumVerts * 3); - for (int32 SideIdx = 0; SideIdx < NumSides + 1; SideIdx++) - { - const FRotator ArcRotator(0, 360.f * ((float)SideIdx / NumSides), 0); - const FRotationMatrix ArcRot(ArcRotator); - const float XTexCoord = ((float)SideIdx / NumSides); - - for (int32 VertIdx = 0; VertIdx < NumRings + 1; VertIdx++) - { - int32 VIx = (NumRings + 1)*SideIdx + VertIdx; - - FVector CurPosition = SphylCenter + ArcRot.TransformPosition(ArcVertices[VertIdx]); - CurPosition = SphylRotMatrix.TransformPosition(CurPosition); - - // Convert the UE4 position to Houdini - Vertices[VIx * 3 + 0] = CurPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[VIx * 3 + 1] = CurPosition.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[VIx * 3 + 2] = CurPosition.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - } - - // Add all of the indices to the mesh. - int32 NumIndices = NumSides * NumRings * 6; - TArray Indices; - Indices.SetNum(NumIndices); - - int32 CurIndex = 0; - for (int32 SideIdx = 0; SideIdx < NumSides; SideIdx++) - { - const int32 a0start = (SideIdx + 0) * (NumRings + 1); - const int32 a1start = (SideIdx + 1) * (NumRings + 1); - for (int32 RingIdx = 0; RingIdx < NumRings; RingIdx++) - { - // First Tri (reverse winding) - Indices[CurIndex+0] = a0start + RingIdx + 0; - Indices[CurIndex+2] = a1start + RingIdx + 0; - Indices[CurIndex+1] = a0start + RingIdx + 1; - CurIndex += 3; - // Second Tri (reverse winding) - Indices[CurIndex+0] = a1start + RingIdx + 0; - Indices[CurIndex+2] = a1start + RingIdx + 1; - Indices[CurIndex+1] = a0start + RingIdx + 1; - CurIndex += 3; - } - } - - // - // Create the Sphyl Mesh in houdini - // - HAPI_NodeId SphylNodeId = -1; - FString SphylName = TEXT("Sphyl") + FString::FromInt(ColliderIndex); - if(!CreateInputNodeForCollider(SphylNodeId, InParentNodeID, ColliderIndex, SphylName, Vertices, Indices)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmInfo ParmInfo; - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_capsule") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphylNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForConvex( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FKConvexElem& ConvexCollider) -{ - TArray Vertices; - TArray Indices; - -#if PHYSICS_INTERFACE_PHYSX - if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) -#elif WITH_CHAOS - //if (ConvexCollider.GetChaosConvexMesh().IsValid()) - if (ConvexCollider.IndexData.Num() > 0 && ConvexCollider.IndexData.Num() % 3 == 0) -#else - if(false) -#endif - { - // Get the convex colliders vertices and indices from the mesh - TArray VertexBuffer; - TArray IndexBuffer; - ConvexCollider.AddCachedSolidConvexGeom(VertexBuffer, IndexBuffer, FColor::White); - - Vertices.SetNum(VertexBuffer.Num() * 3); - int32 CurIndex = 0; - for (auto& CurVert : VertexBuffer) - { - Vertices[CurIndex * 3 + 0] = CurVert.Position.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 1] = CurVert.Position.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 2] = CurVert.Position.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - CurIndex++; - } - - Indices.SetNum(IndexBuffer.Num()); - for (int Idx = 0; (Idx + 2) < IndexBuffer.Num(); Idx += 3) - { - // Reverse winding - Indices[Idx + 0] = Indices[Idx + 0]; - Indices[Idx + 2] = Indices[Idx + 1]; - Indices[Idx + 1] = Indices[Idx + 2]; - } - } - else - { - int32 NumVert = ConvexCollider.VertexData.Num(); - Vertices.SetNum(NumVert * 3); - //Indices.SetNum(NumVert); - int32 CurIndex = 0; - for (auto& CurVert : ConvexCollider.VertexData) - { - Vertices[CurIndex * 3 + 0] = CurVert.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 1] = CurVert.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 2] = CurVert.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - - /* - // TODO: Get proper polygons... - Indices[CurIndex] = CurIndex; - */ - CurIndex++; - } - - // TODO: Get Proper polygons - for (int32 Idx = 0; Idx + 2 < NumVert; Idx++) - { - Indices.Add(Idx + 0); - Indices.Add(Idx + 1); - Indices.Add(Idx + 2); - } - - /* - for (int32 Idx = 0; Idx + 3 < NumVert; Idx+= 4) - { - Indices.Add(Idx + 0); - Indices.Add(Idx + 1); - Indices.Add(Idx + 2); - - Indices.Add(Idx + 2); - Indices.Add(Idx + 1); - Indices.Add(Idx + 3); - } - */ - } - - // - // Create the Convex Mesh in houdini - // - HAPI_NodeId ConvexNodeId = -1; - FString ConvexName = TEXT("Convex") + FString::FromInt(ColliderIndex); - if (!CreateInputNodeForCollider(ConvexNodeId, InParentNodeID, ColliderIndex, ConvexName, Vertices, Indices)) - return false; - - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), ColliderNodeId, &CookOptions); - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "groupcreate", GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_ucx - HAPI_ParmInfo ParmInfo; - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_ucx") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Create a convex hull (shrinkwrap::2.0) node to fix the lack of proper indices - HAPI_NodeId ConvexHullNodeId = -1; - FString ConvexHullName = TEXT("ConvexHull") + FString::FromInt(ColliderIndex); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "shrinkwrap::2.0", ConvexHullName, false, &ConvexHullNodeId), false); - - if (ConvexHullNodeId > 0) - { - // Connect the collider to the convex hull - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), ConvexHullNodeId, 0, ConvexNodeId, 0), false); - - // Connect the convex hull to the group - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexHullNodeId, 0), false); - } - else - { - // Connect the collider to the group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexNodeId, 0), false); - - } - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForCollider( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FString& ColliderName, - const TArray& ColliderVertices, - const TArray& ColliderIndices) -{ - // Create a new input node for the collider - const char * ColliderNameStr = TCHAR_TO_UTF8(*ColliderName); - - // Create the node in this input object's OBJ node - HAPI_NodeId ColliderNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "null", ColliderNameStr, false, &ColliderNodeId), false); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = ColliderIndices.Num(); - Part.faceCount = ColliderIndices.Num() / 3; - Part.pointCount = ColliderVertices.Num() / 3; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), ColliderNodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = ColliderVertices.Num() / 3; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Upload the positions - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - ColliderVertices.GetData(), 0, AttributeInfoPoint.count), false); - - // Upload the indices - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, ColliderIndices.GetData(), 0, ColliderIndices.Num()), false); - - // Generate the array of face counts. - TArray ColldierFaceCounts; - ColldierFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, ColldierFaceCounts.GetData(), 0, ColldierFaceCounts.Num()), false); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), ColliderNodeId), false); - - OutNodeId = ColliderNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - const int32 & NodeId, - const int32 & PartId, - const int32 & Count, - const TArray & TriangleMaterials, - const TMap> & ScalarMaterialParameters, - const TMap> & VectorMaterialParameters, - const TMap> & TextureMaterialParameters) -{ - if (NodeId < 0) - return false; - - bool bSuccess = true; - - // Create attribute for materials. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.count = Count; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - // Create the new attribute - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial, - (const char **)TriangleMaterials.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - - // Add scalar material parameter attributes - for (auto & Pair : ScalarMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 1; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - // Create the new attribute - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - Pair.Value.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - // Add vector material parameters - for (auto & Pair : VectorMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 4; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - Pair.Value.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - // Add texture material parameter attributes - for (auto & Pair : TextureMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 1; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // Replace null strings by empty strings to prevent crashes when setting the attribute. - char* EmptyString = nullptr; - TArray StringData = Pair.Value; - for (auto& CurValue : StringData) - { - if (CurValue != nullptr) - continue; - - if (!EmptyString) - { - // Allocate the empty string the first time it is needed. This is free'd along with - // the other strings in the arrays in TextureMaterialParameters by calls to DeleteFaceMaterialArray - EmptyString = FHoudiniEngineUtils::ExtractRawString(FString(TEXT(""))); - } - CurValue = EmptyString; - } - - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - (const char **)StringData.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - return bSuccess; -} - -/* -bool -FUnrealMeshTranslator::AddLevelPathAttributeToMesh( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const FString& LevelPath, - const int32& Count) -{ - if (NodeId == -1 || LevelPath.IsEmpty() || Count <= 0) - return false; - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = Count; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Count); - for (int32 Idx = 0; Idx < Count; ++Idx) - { - PrimitiveAttrs[Idx] = LevelPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealMeshTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "RawMesh.h" +#include "MeshDescription.h" +#include "MeshDescriptionOperations.h" +#include "Engine/StaticMesh.h" +#include "PhysicsEngine/BodySetup.h" +#include "Engine/StaticMeshSocket.h" +#include "Components/StaticMeshComponent.h" +#include "Materials/Material.h" +#include "Materials/MaterialInterface.h" +#include "MeshAttributes.h" +#include "StaticMeshAttributes.h" + +#if WITH_EDITOR + #include "EditorFramework/AssetImportData.h" +#endif + +bool +FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + UStaticMesh* StaticMesh, + HAPI_NodeId& InputNodeId, + const FString& InputNodeName, + UStaticMeshComponent* StaticMeshComponent /* = nullptr */, + const bool& ExportAllLODs /* = false */, + const bool& ExportSockets /* = false */, + const bool& ExportColliders /* = false */) +{ + // If we don't have a static mesh there's nothing to do. + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Node ID for the newly created node + HAPI_NodeId NewNodeId = -1; + + // Export sockets if there are some + bool DoExportSockets = ExportSockets && (StaticMesh->Sockets.Num() > 0); + + // Export LODs if there are some + bool DoExportLODs = ExportAllLODs && (StaticMesh->GetNumLODs() > 1); + + // Export colliders if there are some + bool DoExportColliders = ExportColliders && StaticMesh->BodySetup != nullptr; + if (DoExportColliders) + { + if (!StaticMesh->BodySetup) + { + DoExportColliders = false; + } + else + { + if (StaticMesh->BodySetup->AggGeom.GetElementCount() <= 0) + DoExportColliders = false; + } + } + + // We need to use a merge node if we export lods OR sockets + bool UseMergeNode = DoExportLODs || DoExportSockets || DoExportColliders; + if (UseMergeNode) + { + // TODO: + // What if OutInputNodeId already exists? + // Delete previous merge?/input? + + // Create a merge SOP asset. This will be our "InputNodeId" + // as all the different LOD meshes and sockets will be plugged into it + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/merge"), InputNodeName, true, &NewNodeId), false); + } + else + { + // No LODs/Sockets, we just need a single input node + // If InputNodeId is invalid, we need to create an input node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_ANSI(*InputNodeName)), false); + + if (!FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true)) + return false; + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); + */ + } + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) + return false; + + HAPI_NodeId PreviousInputNodeId = InputNodeId; + + // Update our input NodeId + InputNodeId = NewNodeId; + // Get our parent OBJ NodeID + HAPI_NodeId InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); + + // We have now created a valid new input node, delete the previous one + if (PreviousInputNodeId >= 0) + { + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); + } + } + + // TODO: + // Setting for lightmap resolution? + //const uint8 ExportMethod = 0; // Raw mesh + //const uint8 ExportMethod = 1; // Mesh description + const uint8 ExportMethod = 2; // LODResources (render mesh) + //bool bExportViaRawMesh = false; + + int32 NumLODsToExport = DoExportLODs ? StaticMesh->GetNumLODs() : 1; + for (int32 LODIndex = 0; LODIndex < NumLODsToExport; LODIndex++) + { + // Grab the LOD level. + FStaticMeshSourceModel & SrcModel = StaticMesh->GetSourceModel(LODIndex); + + // If we're using a merge node, we need to create a new input null + HAPI_NodeId CurrentLODNodeId = -1; + if (UseMergeNode) + { + // Create a new input node for the current LOD + const char * LODName = ""; + { + FString LOD = TEXT("lod") + FString::FromInt(LODIndex); + LODName = TCHAR_TO_UTF8(*LOD); + } + + // Create the node in this input object's OBJ node + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InputObjectNodeId, TEXT("null"), LODName, false, &CurrentLODNodeId), false); + } + else + { + // No merge node, just use the input node we created before + CurrentLODNodeId = NewNodeId; + } + + // Either export the current LOD Mesh by using RawMEsh or MeshDescription (legacy) + FMeshDescription* MeshDesc = nullptr; + // if (!bExportViaRawMesh) + if (ExportMethod == 1) + { + // This will either fetch the mesh description that is cached on the SrcModel + // or load it from bulk data / DDC once + if (SrcModel.MeshDescription.IsValid()) + { + MeshDesc = SrcModel.MeshDescription.Get(); + } + else + { + const double StartTime = FPlatformTime::Seconds(); + MeshDesc = StaticMesh->GetMeshDescription(LODIndex); + HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->GetMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + } + + bool bMeshSuccess = false; + if (ExportMethod == 1 && MeshDesc) + { + // Convert the Mesh using FMeshDescription + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForMeshDescription( + CurrentLODNodeId, + *MeshDesc, + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + else if (ExportMethod == 2) + { + // Convert the LOD Mesh using FStaticMeshLODResources + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( + CurrentLODNodeId, + StaticMesh->GetLODForExport(LODIndex), + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + else + { + // Convert the LOD Mesh using FRawMesh + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForRawMesh( + CurrentLODNodeId, + SrcModel, + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForRawMesh completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + + if (!bMeshSuccess) + continue; + + if (UseMergeNode) + { + // Connect the LOD node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, LODIndex, CurrentLODNodeId, 0), false); + } + } + + // next Index for adding nodes to the merge + int32 NextMergeIndex = NumLODsToExport; + if (DoExportColliders) + { + FKAggregateGeom SimpleColliders = StaticMesh->BodySetup->AggGeom; + + // Export BOX colliders + for (auto& CurBox : SimpleColliders.BoxElems) + { + FVector BoxCenter = CurBox.Center; + FVector BoxExtent = FVector(CurBox.X, CurBox.Y, CurBox.Z); + FRotator BoxRotation = CurBox.Rotation; + + HAPI_NodeId BoxNodeId = -1; + if (!CreateInputNodeForBox( + BoxNodeId, InputObjectNodeId, NextMergeIndex, + BoxCenter, BoxExtent, BoxRotation)) + continue; + + if (BoxNodeId < 0) + continue; + + // Connect the Box node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, BoxNodeId, 0), false); + + NextMergeIndex++; + } + + // Export SPHERE colliders + for (auto& CurSphere : SimpleColliders.SphereElems) + { + HAPI_NodeId SphereNodeId = -1; + if (!CreateInputNodeForSphere( + SphereNodeId, InputObjectNodeId, NextMergeIndex, + CurSphere.Center, CurSphere.Radius)) + continue; + + if (SphereNodeId < 0) + continue; + + // Connect the Sphere node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, SphereNodeId, 0), false); + + NextMergeIndex++; + } + + // Export CAPSULE colliders + for (auto& CurSphyl : SimpleColliders.SphylElems) + { + HAPI_NodeId SphylNodeId = -1; + if (!CreateInputNodeForSphyl( + SphylNodeId, InputObjectNodeId, NextMergeIndex, + CurSphyl.Center, CurSphyl.Rotation, CurSphyl.Radius, CurSphyl.Length)) + continue; + + if (SphylNodeId < 0) + continue; + + // Connect the capsule node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, SphylNodeId, 0), false); + + NextMergeIndex++; + } + + // Export CONVEX colliders + for (auto& CurConvex : SimpleColliders.ConvexElems) + { + HAPI_NodeId ConvexNodeId = -1; + if (!CreateInputNodeForConvex( + ConvexNodeId, InputObjectNodeId, NextMergeIndex, CurConvex)) + continue; + + if (ConvexNodeId < 0) + continue; + + // Connect the capsule node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, ConvexNodeId, 0), false); + + NextMergeIndex++; + } + } + + if (DoExportSockets && StaticMesh->Sockets.Num() > 0) + { + // Create an input node for the mesh sockets + HAPI_NodeId SocketsNodeId = -1; + if (CreateInputNodeForMeshSockets(StaticMesh->Sockets, InputObjectNodeId, SocketsNodeId)) + { + // We can connect the socket node to the merge node's last input. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), NewNodeId, NextMergeIndex, SocketsNodeId, 0), false); + + NextMergeIndex++; + } + else if (SocketsNodeId != -1) + { + // If we failed to properly export the sockets, clean up the created node + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), SocketsNodeId); + } + } + + // + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForMeshSockets( + const TArray& InMeshSocket, const HAPI_NodeId& InParentNodeId, HAPI_NodeId& OutSocketsNodeId) +{ + int32 NumSockets = InMeshSocket.Num(); + if (NumSockets <= 0) + return false; + + // Create a new input node for the sockets + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeId, TEXT("null"), "sockets", false, &OutSocketsNodeId), false); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.pointCount = NumSockets; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, &Part), false); + + // Create POS point attribute info. + HAPI_AttributeInfo AttributeInfoPos; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); + AttributeInfoPos.count = NumSockets; + AttributeInfoPos.tupleSize = 3; + AttributeInfoPos.exists = true; + AttributeInfoPos.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos), false); + + // Create Rot point attribute Info + HAPI_AttributeInfo AttributeInfoRot; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); + AttributeInfoRot.count = NumSockets; + AttributeInfoRot.tupleSize = 4; + AttributeInfoRot.exists = true; + AttributeInfoRot.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot), false); + + // Create scale point attribute Info + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = NumSockets; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); + + // Create the name attrib info + HAPI_AttributeInfo AttributeInfoName; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); + AttributeInfoName.count = NumSockets; + AttributeInfoName.tupleSize = 1; + AttributeInfoName.exists = true; + AttributeInfoName.owner = HAPI_ATTROWNER_POINT; + AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName), false); + + // Create the tag attrib info + HAPI_AttributeInfo AttributeInfoTag; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); + AttributeInfoTag.count = NumSockets; + AttributeInfoTag.tupleSize = 1; + AttributeInfoTag.exists = true; + AttributeInfoTag.owner = HAPI_ATTROWNER_POINT; + AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag), false); + + // Extract the sockets transform values + TArray SocketPos; + SocketPos.SetNumZeroed(NumSockets * 3); + TArray SocketRot; + SocketRot.SetNumZeroed(NumSockets * 4); + TArray SocketScale; + SocketScale.SetNumZeroed(NumSockets * 3); + + // raw string array for names and tag, will need to be free before returning + TArray SocketNames; + TArray SocketTags; + + // Lambda for freeing the const char array's memory and returning + auto FreeMemoryReturn = [&SocketNames, &SocketTags](const bool& bReturn) + { + // Frees the memory allocated by ExtractRawString for the names and tags + FHoudiniEngineUtils::FreeRawStringMemory(SocketNames); + FHoudiniEngineUtils::FreeRawStringMemory(SocketTags); + + return bReturn; + }; + + for (int32 Idx = 0; Idx < NumSockets; ++Idx) + { + UStaticMeshSocket* CurrentSocket = InMeshSocket[Idx]; + if (!CurrentSocket || CurrentSocket->IsPendingKill()) + continue; + + // Get the socket's transform and convert it to HapiTransform + FTransform SocketTransform(CurrentSocket->RelativeRotation, CurrentSocket->RelativeLocation, CurrentSocket->RelativeScale); + HAPI_Transform HapiSocketTransform; + FHoudiniApi::Transform_Init(&HapiSocketTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(SocketTransform, HapiSocketTransform); + + // Fill the attribute values + SocketPos[3 * Idx + 0] = HapiSocketTransform.position[0]; + SocketPos[3 * Idx + 1] = HapiSocketTransform.position[1]; + SocketPos[3 * Idx + 2] = HapiSocketTransform.position[2]; + + SocketRot[4 * Idx + 0] = HapiSocketTransform.rotationQuaternion[0]; + SocketRot[4 * Idx + 1] = HapiSocketTransform.rotationQuaternion[1]; + SocketRot[4 * Idx + 2] = HapiSocketTransform.rotationQuaternion[2]; + SocketRot[4 * Idx + 3] = HapiSocketTransform.rotationQuaternion[3]; + + SocketScale[3 * Idx + 0] = HapiSocketTransform.scale[0]; + SocketScale[3 * Idx + 1] = HapiSocketTransform.scale[1]; + SocketScale[3 * Idx + 2] = HapiSocketTransform.scale[2]; + + FString CurrentSocketName; + if (!CurrentSocket->SocketName.IsNone()) + CurrentSocketName = CurrentSocket->SocketName.ToString(); + else + CurrentSocketName = TEXT("Socket") + FString::FromInt(Idx); + SocketNames.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocketName)); + + if (!CurrentSocket->Tag.IsEmpty()) + SocketTags.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocket->Tag)); + else + SocketTags.Add(""); + } + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos, + SocketPos.GetData(), 0, AttributeInfoPos.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot, + SocketRot.GetData(), 0, AttributeInfoRot.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, + SocketScale.GetData(), 0, AttributeInfoScale.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName, + SocketNames.GetData(), 0, AttributeInfoName.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag, + SocketTags.GetData(), 0, AttributeInfoTag.count), + FreeMemoryReturn(false)); + + // We will also create the socket_details attributes + for (int32 Idx = 0; Idx < NumSockets; ++Idx) + { + // Build the current socket's prefix + FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(Idx); + + // Create mesh_socketX_pos attribute info. + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); + AttributeInfoPos.count = 1; + AttributeInfoPos.tupleSize = 3; + AttributeInfoPos.exists = true; + AttributeInfoPos.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; + + FString PosAttr = SocketAttrPrefix + TEXT("_pos"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos, + &(SocketPos[3 * Idx]), 0, AttributeInfoPos.count), + FreeMemoryReturn(false)); + + // Create mesh_socketX_rot point attribute Info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); + AttributeInfoRot.count = 1; + AttributeInfoRot.tupleSize = 4; + AttributeInfoRot.exists = true; + AttributeInfoRot.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; + + FString RotAttr = SocketAttrPrefix + TEXT("_rot"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot, + &(SocketRot[4 * Idx]), 0, AttributeInfoRot.count), + FreeMemoryReturn(false)); + + // Create mesh_socketX_scale point attribute Info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = 1; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + FString ScaleAttr = SocketAttrPrefix + TEXT("_scale"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale, + &(SocketScale[3 * Idx]), 0, AttributeInfoScale.count), + FreeMemoryReturn(false)); + + // Create the mesh_socketX_name attrib info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); + AttributeInfoName.count = 1; + AttributeInfoName.tupleSize = 1; + AttributeInfoName.exists = true; + AttributeInfoName.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; + + FString NameAttr = SocketAttrPrefix + TEXT("_name"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName, + &(SocketNames[Idx]), 0, AttributeInfoName.count), + FreeMemoryReturn(false)); + + // Create the mesh_socketX_tag attrib info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); + AttributeInfoTag.count = 1; + AttributeInfoTag.tupleSize = 1; + AttributeInfoTag.exists = true; + AttributeInfoTag.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; + + FString TagAttr = SocketAttrPrefix + TEXT("_tag"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag, + &(SocketTags[Idx]), 0, AttributeInfoTag.count), + FreeMemoryReturn(false)); + } + + // Now add the sockets group + const char * SocketGroupStr = "socket_imported"; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr), + FreeMemoryReturn(false)); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, NumSockets); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr, GroupArray.GetData(), 0, NumSockets), + FreeMemoryReturn(false)); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), OutSocketsNodeId), + FreeMemoryReturn(false)); + + return FreeMemoryReturn(true); +} + +bool +FUnrealMeshTranslator::CreateInputNodeForRawMesh( + const HAPI_NodeId& NodeId, + const FStaticMeshSourceModel& SourceModel, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent ) +{ + // Convert the Mesh using FRawMesh + FRawMesh RawMesh; + SourceModel.LoadRawMesh(RawMesh); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = RawMesh.WedgeIndices.Num(); + Part.faceCount = RawMesh.WedgeIndices.Num() / 3; + Part.pointCount = RawMesh.VertexPositions.Num(); + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = RawMesh.VertexPositions.Num(); + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Grab the build scale + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.VertexPositions.Num() > 3) + { + TArray StaticMeshVertices; + StaticMeshVertices.SetNumZeroed(RawMesh.VertexPositions.Num() * 3); + for (int32 VertexIdx = 0; VertexIdx < RawMesh.VertexPositions.Num(); ++VertexIdx) + { + // Convert Unreal to Houdini + const FVector & PositionVector = RawMesh.VertexPositions[VertexIdx]; + StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; + StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; + StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + for (int32 MeshTexCoordIdx = 0; MeshTexCoordIdx < MAX_STATIC_TEXCOORDS; MeshTexCoordIdx++) + { + int32 StaticMeshUVCount = RawMesh.WedgeTexCoords[MeshTexCoordIdx].Num(); + if (StaticMeshUVCount > 0) + { + const TArray & RawMeshUVs = RawMesh.WedgeTexCoords[MeshTexCoordIdx]; + TArray StaticMeshUVs; + StaticMeshUVs.Reserve(StaticMeshUVCount); + + // Transfer UV data. + for (int32 UVIdx = 0; UVIdx < StaticMeshUVCount; UVIdx++) + StaticMeshUVs.Emplace(RawMeshUVs[UVIdx].X, 1.0 - RawMeshUVs[UVIdx].Y, 0); + + // Convert Unreal to Houdini + // We need to re-index UVs for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + // We do not touch wedge 0 of this triangle, Swap 2 and 3 to reverse the winding order. + StaticMeshUVs.SwapMemory(WedgeIdx + 1, WedgeIdx + 2); + } + + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (MeshTexCoordIdx > 0) + UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = StaticMeshUVCount; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, (const float *)StaticMeshUVs.GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentZ.Num() > 0) + { + TArray ChangedNormals(RawMesh.WedgeTangentZ); + + // We need to re-index normals for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentZ1 = ChangedNormals[WedgeIdx + 1]; + FVector TangentZ2 = ChangedNormals[WedgeIdx + 2]; + + ChangedNormals[WedgeIdx + 1] = TangentZ2; + ChangedNormals[WedgeIdx + 2] = TangentZ1; + } + + // We also need to swap the vector's Y and Z components + for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedNormals[WedgeIdx].Y, ChangedNormals[WedgeIdx].Z); + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedNormals.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, (const float *)ChangedNormals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentX.Num() > 0) + { + TArray ChangedTangentU(RawMesh.WedgeTangentX); + + // We need to re-index tangents for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentU1 = ChangedTangentU[WedgeIdx + 1]; + FVector TangentU2 = ChangedTangentU[WedgeIdx + 2]; + + ChangedTangentU[WedgeIdx + 1] = TangentU2; + ChangedTangentU[WedgeIdx + 2] = TangentU1; + } + + // We also need to swap the vector's Y and Z components + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedTangentU[WedgeIdx].Y, ChangedTangentU[WedgeIdx].Z); + + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedTangentU.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + (const float *)ChangedTangentU.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentY.Num() > 0) + { + TArray ChangedTangentV(RawMesh.WedgeTangentY); + // We need to re-index normals for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentV1 = ChangedTangentV[WedgeIdx + 1]; + FVector TangentV2 = ChangedTangentV[WedgeIdx + 2]; + + ChangedTangentV[WedgeIdx + 1] = TangentV2; + ChangedTangentV[WedgeIdx + 2] = TangentV1; + } + + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedTangentV[WedgeIdx].Y, ChangedTangentV[WedgeIdx].Z); + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedTangentV.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + (const float *)ChangedTangentV.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + { + // If we have instance override vertex colors on the StaticMeshComponent, + // we first need to propagate them to our copy of the RawMesh Vert Colors + TArray ChangedColors; + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && + StaticMesh->RenderData && + StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; + FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) + { + // Use the wedge map if it is available as it is lossless. + int32 NumWedges = RawMesh.WedgeIndices.Num(); + if (RenderModel.WedgeMap.Num() == NumWedges) + { + int32 NumExistingColors = RawMesh.WedgeColors.Num(); + if (NumExistingColors < NumWedges) + { + RawMesh.WedgeColors.AddUninitialized(NumWedges - NumExistingColors); + } + + // Replace mesh colors with override colors + for (int32 i = 0; i < NumWedges; i++) + { + FColor WedgeColor = FColor::White; + int32 Index = RenderModel.WedgeMap[i]; + if (Index != INDEX_NONE) + { + WedgeColor = ColorVertexBuffer.VertexColor(Index); + } + RawMesh.WedgeColors[i] = WedgeColor; + } + } + } + } + + // See if we have colors to upload. + if (RawMesh.WedgeColors.Num() > 0) + { + ChangedColors.SetNumUninitialized(RawMesh.WedgeColors.Num()); + + // Convert Unreal to Houdini + // We need to re-index colors for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + ChangedColors[WedgeIdx + 0] = RawMesh.WedgeColors[WedgeIdx + 0].ReinterpretAsLinear(); + ChangedColors[WedgeIdx + 1] = RawMesh.WedgeColors[WedgeIdx + 2].ReinterpretAsLinear(); + ChangedColors[WedgeIdx + 2] = RawMesh.WedgeColors[WedgeIdx + 1].ReinterpretAsLinear(); + } + } + + if (ChangedColors.Num() > 0) + { + // Extract the RGB colors + TArray ColorValues; + ColorValues.SetNum(ChangedColors.Num() * 3); + for (int32 colorIndex = 0; colorIndex < ChangedColors.Num(); colorIndex++) + { + ColorValues[colorIndex * 3] = ChangedColors[colorIndex].R; + ColorValues[colorIndex * 3 + 1] = ChangedColors[colorIndex].G; + ColorValues[colorIndex * 3 + 2] = ChangedColors[colorIndex].B; + } + + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedColors.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + ColorValues.GetData(), 0, AttributeInfoVertex.count), false); + + // Create the attribute for Alpha + TArray AlphaValues; + AlphaValues.SetNum(ChangedColors.Num()); + for (int32 alphaIndex = 0; alphaIndex < ChangedColors.Num(); alphaIndex++) + AlphaValues[alphaIndex] = ChangedColors[alphaIndex].A; + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.count = AlphaValues.Num(); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + AlphaValues.GetData(), 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES (VertexList) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeIndices.Num() > 0) + { + TArray StaticMeshIndices; + StaticMeshIndices.SetNumUninitialized(RawMesh.WedgeIndices.Num()); + + // Convert Unreal to Houdini + for (int32 IndexIdx = 0; IndexIdx < RawMesh.WedgeIndices.Num(); IndexIdx += 3) + { + // Swap indices to fix winding order. + StaticMeshIndices[IndexIdx + 0] = RawMesh.WedgeIndices[IndexIdx + 0]; + StaticMeshIndices[IndexIdx + 1] = RawMesh.WedgeIndices[IndexIdx + 2]; + StaticMeshIndices[IndexIdx + 2] = RawMesh.WedgeIndices[IndexIdx + 1]; + } + + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, StaticMeshIndices.GetData(), 0, StaticMeshIndices.Num()), false); + + // We need to generate array of face counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, StaticMeshFaceCounts.GetData(), 0, StaticMeshFaceCounts.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + // Marshall face material indices. + if (RawMesh.FaceMaterialIndices.Num() > 0) + { + // Create an array of Material Interfaces + TArray MaterialInterfaces; + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()) + { + // StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); + + int32 NumMeshBasedMaterials = StaticMeshComponent->GetNumMaterials(); + TArray MeshBasedMaterialInterfaces; + MeshBasedMaterialInterfaces.SetNumUninitialized(NumMeshBasedMaterials); + for (int32 i = 0; i < NumMeshBasedMaterials; i++) + MeshBasedMaterialInterfaces[i] = StaticMeshComponent->GetMaterial(i); + + int32 NumSections = StaticMesh->GetNumSections(InLODIndex); + MaterialInterfaces.SetNumUninitialized(NumSections); + for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) + { + FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex); + check(Info.MaterialIndex < NumMeshBasedMaterials); + MaterialInterfaces[SectionIndex] = MeshBasedMaterialInterfaces[Info.MaterialIndex]; + } + } + else + { + // Query the Static mesh's materials + for (int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++) + { + MaterialInterfaces.Add(StaticMesh->GetMaterial(MatIdx)); + } + + // Try to fix up inconsistencies between the RawMesh / StaticMesh material indexes + // by using the meshes sections... + // TODO: Fix me properly! + // Proper fix would be to export the meshes via the FStaticMeshLODResources obtained + // by GetLODForExport(), and then export the mesh by sections. + if (StaticMesh->RenderData && StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + { + TMap MapOfMaterials; + FStaticMeshLODResources& LODResources = StaticMesh->RenderData->LODResources[InLODIndex]; + for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) + { + // Get the material for each element at the current lod index + int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; + if (!MapOfMaterials.Contains(MaterialIndex)) + { + MapOfMaterials.Add(MaterialIndex, StaticMesh->GetMaterial(MaterialIndex)); + } + } + + if (MapOfMaterials.Num() > 0) + { + // Sort the output material in the correct order (by material index) + MapOfMaterials.KeySort([](int32 A, int32 B) { return A < B; }); + + // Set the value in the correct order + // Do not reduce the array of materials, this could cause crahses in some weird cases.. + if (MapOfMaterials.Num() > MaterialInterfaces.Num()) + MaterialInterfaces.SetNumZeroed(MapOfMaterials.Num()); + + int32 MaterialIndex = 0; + for (auto Kvp : MapOfMaterials) + { + MaterialInterfaces[MaterialIndex++] = Kvp.Value; + } + } + } + } + + // List of materials, one for each face. + TArray StaticMeshFaceMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + RawMesh.FaceMaterialIndices.Num(), + StaticMeshFaceMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + RawMesh.FaceMaterialIndices.Num(), + StaticMeshFaceMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(StaticMeshFaceMaterials); + + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING MASKS + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.FaceSmoothingMasks.Num() > 0) + { + HAPI_AttributeInfo AttributeInfoSmoothingMasks; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + AttributeInfoSmoothingMasks.count = RawMesh.FaceSmoothingMasks.Num(); + AttributeInfoSmoothingMasks.tupleSize = 1; + AttributeInfoSmoothingMasks.exists = true; + AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + (const int32 *)RawMesh.FaceSmoothingMasks.GetData(), 0, RawMesh.FaceSmoothingMasks.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->LightMapResolution); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + TArray AllTags; + for (auto& ComponentTag : StaticMeshComponent->ComponentTags) + AllTags.AddUnique(ComponentTag); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + for (auto& ActorTag : ParentActor->Tags) + AllTags.AddUnique(ActorTag); + } + + // Try to create groups for the tags + if (!FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, AllTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups for the Static Mesh Component and Actor tags!")); + + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + /* + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +bool +FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( + const HAPI_NodeId& NodeId, + const FStaticMeshLODResources& LODResources, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent) +{ + // Convert the Mesh using FStaticMeshLODResources + + // Check that the mesh is not empty + if (LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0) + { + HOUDINI_LOG_ERROR(TEXT("No vertices in mesh!")); + return false; + } + + if (LODResources.Sections.Num() == 0) + { + HOUDINI_LOG_ERROR(TEXT("No triangles in mesh!")); + return false; + } + + // Vertex instance and triangle counts + const uint32 OrigNumVertexInstances = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); + const uint32 NumTriangles = LODResources.GetNumTriangles(); + const uint32 NumVertexInstances = NumTriangles * 3; + const uint32 NumSections = LODResources.Sections.Num(); + + // Grab the build scale + const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + // In FStaticMeshLODResources each vertex instances stores its position, even if the positions are not unique (in other + // words, in Houdini terminology, the number of points and vertices are the same. We'll do the same thing that Epic + // does in FBX export: we'll run through all vertex instances and use a hash to determine which instances share a + // position, so that we can a smaller number of points than vertices, and vertices share point positions + TArray UEVertexInstanceIdxToPointIdx; + UEVertexInstanceIdxToPointIdx.Reserve(OrigNumVertexInstances); + + TMap PositionToPointIndexMap; + PositionToPointIndexMap.Reserve(OrigNumVertexInstances); + + TArray StaticMeshVertices; + StaticMeshVertices.Reserve(OrigNumVertexInstances * 3); + for (uint32 VertexInstanceIndex = 0; VertexInstanceIndex < OrigNumVertexInstances; ++VertexInstanceIndex) + { + // Convert Unreal to Houdini + const FVector &PositionVector = LODResources.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexInstanceIndex); + const int32 *FoundPointIndexPtr = PositionToPointIndexMap.Find(PositionVector); + if (!FoundPointIndexPtr) + { + const int32 NewPointIndex = StaticMeshVertices.Add(PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X) / 3; + StaticMeshVertices.Add(PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z); + StaticMeshVertices.Add(PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y); + + PositionToPointIndexMap.Add(PositionVector, NewPointIndex); + UEVertexInstanceIdxToPointIdx.Add(NewPointIndex); + } + else + { + UEVertexInstanceIdxToPointIdx.Add(*FoundPointIndexPtr); + } + } + + StaticMeshVertices.Shrink(); + const uint32 NumVertices = StaticMeshVertices.Num() / 3; + + // Now that we know how many vertices (points), vertex instances (vertices) and triagnles we have, + // we can create the part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumVertexInstances; + Part.faceCount = NumTriangles; + Part.pointCount = NumVertices; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = Part.pointCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + + // Determine which attributes we have + const bool bIsVertexInstanceNormalsValid = true; + const bool bIsVertexInstanceTangentsValid = true; + const bool bIsVertexInstanceBinormalsValid = true; + const bool bIsVertexInstanceColorsValid = LODResources.bHasColorVertexData; + const uint32 NumUVLayers = FMath::Min(LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(), MAX_STATIC_TEXCOORDS); + const bool bIsVertexInstanceUVsValid = NumUVLayers > 0; + + bool bUseComponentOverrideColors = false; + // Determine if have override colors on the static mesh component, if so prefer to use those + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (ColorVertexBuffer.GetNumVertices() == LODResources.GetNumVertices()) + { + bUseComponentOverrideColors = true; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL INDEX -> MATERIAL INTERFACE + //--------------------------------------------------------------------------------------------------------------------- + TArray MaterialInterfaces; + TArray TriangleMaterialIndices; + + // If the static mesh component is valid, get the materials via the component to account for overrides + const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); + const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); + // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, + // then we will set UEDefaultMaterial at the invalid index + int32 UEDefaultMaterialIndex = INDEX_NONE; + UMaterialInterface *UEDefaultMaterial = nullptr; + if (NumStaticMaterials > 0) + { + MaterialInterfaces.Reserve(NumStaticMaterials); + for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) + { + const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; + UMaterialInterface *Material = nullptr; + if (bIsStaticMeshComponentValid) + { + Material = StaticMeshComponent->GetMaterial(MaterialIndex); + } + else + { + Material = MaterialInfo.MaterialInterface; + } + // If the Material is NULL or invalid, fallback to the default material + if (!Material || Material->IsPendingKill()) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + UEDefaultMaterialIndex = MaterialIndex; + } + Material = UEDefaultMaterial; + HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); + } + // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); + MaterialInterfaces.Add(Material); + } + + TriangleMaterialIndices.Reserve(NumTriangles); + } + + // If we haven't created UEDefaultMaterial yet, check that all the sections' MaterialIndex + // is valid, if not, create UEDefaultMaterial and add to MaterialInterfaces to get UEDefaultMaterialIndex + if (!UEDefaultMaterial || UEDefaultMaterialIndex == INDEX_NONE) + { + for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + { + // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial + // up to and including MaterialIndex and log a warning + const int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; + if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + // Add the UEDefaultMaterial to MaterialInterfaces + UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); + } + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); + } + } + } + + // Determine the final number of materials we have, with default for missing/invalid indices + const int32 NumMaterials = MaterialInterfaces.Num(); + + // Now we deal with vertex instance attributes. + if (NumTriangles > 0) + { + // UV layer array. Each layer has an array of floats, 3 floats per vertex instance + TArray> UVs; + // Normals: 3 floats per vertex instance + TArray Normals; + // Tangents: 3 floats per vertex instance + TArray Tangents; + // Binormals: 3 floats per vertex instance + TArray Binormals; + // RGBColors: 3 floats per vertex instance + TArray RGBColors; + // Alphas: 1 float per vertex instance + TArray Alphas; + + // Initialize the arrays for the attributes that are valid + if (bIsVertexInstanceUVsValid) + { + UVs.SetNum(NumUVLayers); + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); + } + } + + if (bIsVertexInstanceNormalsValid) + { + Normals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceTangentsValid) + { + Tangents.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceBinormalsValid) + { + Binormals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + RGBColors.SetNumUninitialized(NumVertexInstances * 3); + Alphas.SetNumUninitialized(NumVertexInstances); + } + + // Array of vertex (point position) indices per triangle + TArray MeshTriangleVertexIndices; + MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); + // Array of vertex counts per triangle/face + TArray MeshTriangleVertexCounts; + MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); + + int32 TriangleIdx = 0; + int32 HoudiniVertexIdx = 0; + FIndexArrayView TriangleVertexIndices = LODResources.IndexBuffer.GetArrayView(); + for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + { + const FStaticMeshSection& Section = LODResources.Sections[SectionIndex]; + for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < Section.NumTriangles; ++SectionTriangleIndex) + { + MeshTriangleVertexCounts[TriangleIdx] = 3; + for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) + { + // Reverse the winding order for Houdini (but still start at 0) + const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; + const uint32 UEVertexIndex = TriangleVertexIndices[Section.FirstIndex + SectionTriangleIndex * 3 + WindingIdx]; + + // Calculate the index of the first component of a vertex instance's value in an inline float array + // representing vectors (3 float) per vertex instance + const int32 Float3Index = HoudiniVertexIdx * 3; + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + const FVector2D &UV = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(UEVertexIndex, UVLayerIndex); + UVs[UVLayerIndex][Float3Index + 0] = UV.X; + UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; + UVs[UVLayerIndex][Float3Index + 2] = 0; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + const FVector &Normal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(UEVertexIndex); + Normals[Float3Index + 0] = Normal.X; + Normals[Float3Index + 1] = Normal.Z; + Normals[Float3Index + 2] = Normal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + const FVector &Tangent = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(UEVertexIndex); + Tangents[Float3Index + 0] = Tangent.X; + Tangents[Float3Index + 1] = Tangent.Z; + Tangents[Float3Index + 2] = Tangent.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + // In order to calculate the binormal we also need the tangent and normal + if (bIsVertexInstanceBinormalsValid) + { + FVector Binormal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(UEVertexIndex); + Binormals[Float3Index + 0] = Binormal.X; + Binormals[Float3Index + 1] = Binormal.Z; + Binormals[Float3Index + 2] = Binormal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + FVector4 Color = FLinearColor::White; + if (bUseComponentOverrideColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + Color = ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); + } + else + { + Color = LODResources.VertexBuffers.ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); + } + RGBColors[Float3Index + 0] = Color[0]; + RGBColors[Float3Index + 1] = Color[1]; + RGBColors[Float3Index + 2] = Color[2]; + Alphas[HoudiniVertexIdx] = Color[3]; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + if (UEVertexInstanceIdxToPointIdx.IsValidIndex(UEVertexIndex)) + { + MeshTriangleVertexIndices[HoudiniVertexIdx] = UEVertexInstanceIdxToPointIdx[UEVertexIndex]; + } + + HoudiniVertexIdx++; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + if (MaterialInterfaces.IsValidIndex(Section.MaterialIndex)) + { + TriangleMaterialIndices.Add(Section.MaterialIndex); + } + else + { + TriangleMaterialIndices.Add(UEDefaultMaterialIndex); + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, Section.MaterialIndex, *(UEDefaultMaterial->GetPathName())); + } + + TriangleIdx++; + } + } + + // Now transfer valid vertex instance attributes to Houdini vertex attributes + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) + { + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (UVLayerIndex > 0) + UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = NumVertexInstances; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, Normals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + Tangents.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceBinormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + Binormals.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + RGBColors.GetData(), 0, AttributeInfoVertex.count), false); + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.count = Alphas.Num(); + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + Alphas.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); + + // Send the array of face vertex counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); + + // Send material assignments to Houdini + if (NumMaterials > 0) + { + // List of materials, one for each face. + TArray TriangleMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); + + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + } + + // TODO: The render mesh (LODResources) does not have face smoothing information, and the raw mesh triangle order is + // potentially different (see also line 4152 TODO_FBX in Engine\Source\Editor\UnrealEd\Private\Fbx\FbxMainExport.cpp + ////--------------------------------------------------------------------------------------------------------------------- + //// TRIANGLE SMOOTHING MASKS + ////--------------------------------------------------------------------------------------------------------------------- + //TArray TriangleSmoothingMasks; + //TriangleSmoothingMasks.SetNumZeroed(NumTriangles); + //FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); + //if (TriangleSmoothingMasks.Num() > 0) + //{ + // HAPI_AttributeInfo AttributeInfoSmoothingMasks; + // FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + // AttributeInfoSmoothingMasks.tupleSize = 1; + // AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); + // AttributeInfoSmoothingMasks.exists = true; + // AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + // AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + // AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + // FHoudiniEngine::Get().GetSession(), + // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + // FHoudiniEngine::Get().GetSession(), + // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + // (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); + //} + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->LightMapResolution); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + // Try to create groups for the static mesh component's tags + if (StaticMeshComponent->ComponentTags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Try to create groups for the parent Actor's tags + if (ParentActor->Tags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + + /* + // Add the unreal_level_path attribute + FString LevelPath = ActorPath;// FString(); + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +bool +FUnrealMeshTranslator::CreateInputNodeForMeshDescription( + const HAPI_NodeId& NodeId, + const FMeshDescription& MeshDescription, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent) +{ + // Convert the Mesh using FMeshDescription + // Get references to the attributes we are interested in + // before sending to Houdini we'll check if each attribute is valid + FStaticMeshConstAttributes MeshDescriptionAttributes(MeshDescription); + + TVertexAttributesConstRef VertexPositions = MeshDescriptionAttributes.GetVertexPositions(); + TVertexInstanceAttributesConstRef VertexInstanceNormals = MeshDescriptionAttributes.GetVertexInstanceNormals(); + TVertexInstanceAttributesConstRef VertexInstanceTangents = MeshDescriptionAttributes.GetVertexInstanceTangents(); + TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = MeshDescriptionAttributes.GetVertexInstanceBinormalSigns(); + TVertexInstanceAttributesConstRef VertexInstanceColors = MeshDescriptionAttributes.GetVertexInstanceColors(); + TVertexInstanceAttributesConstRef VertexInstanceUVs = MeshDescriptionAttributes.GetVertexInstanceUVs(); + //TPolygonGroupAttributesConstRef PolygonGroupMaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames(); + + // Get the vertex and triangle arrays + const FVertexArray &MDVertices = MeshDescription.Vertices(); + const FPolygonGroupArray &MDPolygonGroups = MeshDescription.PolygonGroups(); + const FPolygonArray &MDPolygons = MeshDescription.Polygons(); + const FTriangleArray &MDTriangles = MeshDescription.Triangles(); + + // Determine point, vertex and polygon counts + const uint32 NumVertices = MDVertices.Num(); + const uint32 NumTriangles = MDTriangles.Num(); + const uint32 NumVertexInstances = NumTriangles * 3; + + // Some checks: we expect triangulated meshes + if (MeshDescription.VertexInstances().Num() != NumTriangles * 3) + { + HOUDINI_LOG_ERROR(TEXT("Expected a triangulated mesh, but # VertexInstances (%d) != # Triangles * 3 (%d)"), MeshDescription.VertexInstances().Num(), NumTriangles * 3); + return false; + } + + // Determine which attributes we have + const bool bIsVertexPositionsValid = VertexPositions.IsValid(); + const bool bIsVertexInstanceNormalsValid = VertexInstanceNormals.IsValid(); + const bool bIsVertexInstanceTangentsValid = VertexInstanceTangents.IsValid(); + const bool bIsVertexInstanceBinormalSignsValid = VertexInstanceBinormalSigns.IsValid(); + const bool bIsVertexInstanceColorsValid = VertexInstanceColors.IsValid(); + const bool bIsVertexInstanceUVsValid = VertexInstanceUVs.IsValid(); + //const bool bIsPolygonGroupImportedMaterialSlotNamesValid = PolygonGroupMaterialSlotNames.IsValid(); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumVertexInstances; + Part.faceCount = NumTriangles; + Part.pointCount = NumVertices; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = Part.pointCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Grab the build scale + const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + // The mesh element arrays are sparse: the max index/ID value can be larger than the number of elements - 1 + // so we have to maintain a lookup of VertexID (UE) to PointIndex (Houdini) + TArray VertexIDToHIndex; + if (bIsVertexPositionsValid && VertexPositions.GetNumElements() >= 3) + { + TArray StaticMeshVertices; + StaticMeshVertices.SetNumUninitialized(NumVertices * 3); + + int32 VertexIdx = 0; + VertexIDToHIndex.Init(INDEX_NONE, MDVertices.GetArraySize()); + + for (const FVertexID& VertexID : MDVertices.GetElementIDs()) + { + // Convert Unreal to Houdini + const FVector &PositionVector = VertexPositions.Get(VertexID); + StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; + StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; + StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; + + // Record the UE Vertex ID to Houdini Point Index lookup + VertexIDToHIndex[VertexID.GetValue()] = VertexIdx; + VertexIdx++; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + } + + bool bUseComponentOverrideColors = false; + // Determine if have override colors on the static mesh component, if so prefer to use those + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && + StaticMesh->RenderData && + StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; + FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) + { + // Use the wedge map if it is available as it is lossless. + if (RenderModel.WedgeMap.Num() == NumVertexInstances) + { + bUseComponentOverrideColors = true; + } + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL SLOT -> MATERIAL INTERFACE + //--------------------------------------------------------------------------------------------------------------------- + // In theory the ImportedMaterialSlotName attribute on PolygonGroups should tell us which material slot is used by + // that group, and thus which MaterialIndex we should assign to triangles in that group. Unfortunately we have + // encountered cases where the ImportedMaterialSlotName name attribute does not match any of the MaterialSlotName or + // ImportedMaterialSlotNames in the StaticMesh->StaticMaterials array. Therefore we have no choice but to rely + // on the PolygonGroup order vs Section order to determine the MaterialIndex for a group. We do what Epic does + // when building a static mesh: Sections are created in the same order as iterating over PolygonGroups, but importantly, + // empty PolygonGroups are skipped + + // // Get material slot name to material index + // and the UMaterialInterface array + // TMap MaterialSlotToInterface; + TArray MaterialInterfaces; + TArray TriangleMaterialIndices; + + // If the static mesh component is valid, get the materials via the component to account for overrides + const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); + const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); + // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, + // then we will set UEDefaultMaterial at the invalid index + int32 UEDefaultMaterialIndex = INDEX_NONE; + UMaterialInterface *UEDefaultMaterial = nullptr; + if (NumStaticMaterials > 0) + { + MaterialInterfaces.Reserve(NumStaticMaterials); + for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) + { + const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; + UMaterialInterface *Material = nullptr; + if (bIsStaticMeshComponentValid) + { + Material = StaticMeshComponent->GetMaterial(MaterialIndex); + } + else + { + Material = MaterialInfo.MaterialInterface; + } + // If the Material is NULL or invalid, fallback to the default material + if (!Material || Material->IsPendingKill()) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + UEDefaultMaterialIndex = MaterialIndex; + } + Material = UEDefaultMaterial; + HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); + } + // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); + MaterialInterfaces.Add(Material); + } + + TriangleMaterialIndices.Reserve(NumTriangles); + } + // SectionIndex: Looking at Epic's StaticMesh build code, Sections are created in the same + // order as iterating over PolygonGroups, but skipping empty PolygonGroups + TMap PolygonGroupToMaterialIndex; + PolygonGroupToMaterialIndex.Reserve(MeshDescription.PolygonGroups().Num()); + int32 SectionIndex = 0; + for (const FPolygonGroupID &PolygonGroupID : MDPolygonGroups.GetElementIDs()) + { + // Skip empty polygon groups + if (MeshDescription.GetNumPolygonGroupPolygons(PolygonGroupID) == 0) + { + continue; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + // // Get the material index for the material slot for this polygon group + //int32 MaterialIndex = INDEX_NONE; + //if (bIsPolygonGroupImportedMaterialSlotNamesValid) + //{ + // const FName &MaterialSlotName = PolygonGroupMaterialSlotNames.Get(PolygonGroupID); + // const int32 *MaterialIndexPtr = MaterialSlotToInterface.Find(MaterialSlotName); + // if (MaterialIndexPtr != nullptr) + // { + // MaterialIndex = *MaterialIndexPtr; + // } + //} + + // Get the material for the LOD and section via the section info map + if (StaticMesh->GetNumSections(InLODIndex) <= SectionIndex) + { + HOUDINI_LOG_ERROR(TEXT("Found more non-empty polygon groups in the mesh description for LOD %d than sections in the static mesh..."), InLODIndex); + return false; + } + + // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial + // up to and including MaterialIndex and log a warning + int32 MaterialIndex = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex).MaterialIndex; + if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + // Add the UEDefaultMaterial to MaterialInterfaces + UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); + } + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); + MaterialIndex = UEDefaultMaterialIndex; + } + + PolygonGroupToMaterialIndex.Add(PolygonGroupID, MaterialIndex); + SectionIndex++; + } + + // Determine the final number of materials we have, with defaults for missing/invalid indices + const int32 NumMaterials = MaterialInterfaces.Num(); + + // Now we deal with vertex instance attributes. + // // First we must also record a UE VertexInstanceID to Houdini Vertex Index lookup, + // // and then get and convert all valid and supported vertex instance attributes from UE + // TArray VertexInstanceIDToHIndex; + + if (NumTriangles > 0) + { + // UV layer array. Each layer has an array of floats, 3 floats per vertex instance + TArray> UVs; + const int32 NumUVLayers = bIsVertexInstanceUVsValid ? FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_STATIC_TEXCOORDS) : 0; + // Normals: 3 floats per vertex instance + TArray Normals; + // Tangents: 3 floats per vertex instance + TArray Tangents; + // Binormals: 3 floats per vertex instance + TArray Binormals; + // RGBColors: 3 floats per vertex instance + TArray RGBColors; + // Alphas: 1 float per vertex instance + TArray Alphas; + + // Initialize the arrays for the attributes that are valid + if (bIsVertexInstanceUVsValid) + { + UVs.SetNum(NumUVLayers); + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); + } + } + + if (bIsVertexInstanceNormalsValid) + { + Normals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceTangentsValid) + { + Tangents.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceBinormalSignsValid) + { + Binormals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + RGBColors.SetNumUninitialized(NumVertexInstances * 3); + Alphas.SetNumUninitialized(NumVertexInstances); + } + + // Array of material index per triangle/face + TArray MeshTriangleVertexIndices; + MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); + // Array of vertex counts per triangle/face + TArray MeshTriangleVertexCounts; + MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); + + int32 TriangleIdx = 0; + int32 VertexInstanceIdx = 0; + // VertexInstanceIDToHIndex.Init(-1, MDVertexInstances.GetArraySize()); + + for (const FPolygonID &PolygonID : MDPolygons.GetElementIDs()) + { + for (const FTriangleID &TriangleID : MeshDescription.GetPolygonTriangleIDs(PolygonID)) + { + MeshTriangleVertexCounts[TriangleIdx] = 3; + for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) + { + // Reverse the winding order for Houdini (but still start at 0) + const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; + const FVertexInstanceID &VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, WindingIdx); + + // // UE Vertex Instance ID to Houdini Vertex Index look up + // VertexInstanceIDToHIndex[VertexInstanceID.GetValue()] = VertexInstanceIdx; + + // Calculate the index of the first component of a vertex instance's value in an inline float array + // representing vectors (3 float) per vertex instance + const int32 Float3Index = VertexInstanceIdx * 3; + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + const FVector2D &UV = VertexInstanceUVs.Get(VertexInstanceID, UVLayerIndex); + UVs[UVLayerIndex][Float3Index + 0] = UV.X; + UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; + UVs[UVLayerIndex][Float3Index + 2] = 0; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + const FVector &Normal = VertexInstanceNormals.Get(VertexInstanceID); + Normals[Float3Index + 0] = Normal.X; + Normals[Float3Index + 1] = Normal.Z; + Normals[Float3Index + 2] = Normal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + const FVector &Tangent = VertexInstanceTangents.Get(VertexInstanceID); + Tangents[Float3Index + 0] = Tangent.X; + Tangents[Float3Index + 1] = Tangent.Z; + Tangents[Float3Index + 2] = Tangent.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + // In order to calculate the binormal we also need the tangent and normal + if (bIsVertexInstanceBinormalSignsValid && bIsVertexInstanceTangentsValid && bIsVertexInstanceNormalsValid) + { + const float &BinormalSign = VertexInstanceBinormalSigns.Get(VertexInstanceID); + FVector Binormal = FVector::CrossProduct( + FVector(Tangents[Float3Index + 0], Tangents[Float3Index + 1], Tangents[Float3Index + 2]), + FVector(Normals[Float3Index + 0], Normals[Float3Index + 1], Normals[Float3Index + 2]) + ) * BinormalSign; + Binormals[Float3Index + 0] = Binormal.X; + Binormals[Float3Index + 1] = Binormal.Y; + Binormals[Float3Index + 2] = Binormal.Z; + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + FVector4 Color = FLinearColor::White; + if (bUseComponentOverrideColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshLODResources& RenderModel = StaticMesh->RenderData->LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + int32 Index = RenderModel.WedgeMap[VertexInstanceIdx]; + if (Index != INDEX_NONE) + { + Color = ColorVertexBuffer.VertexColor(Index).ReinterpretAsLinear(); + } + } + else + { + Color = VertexInstanceColors.Get(VertexInstanceID); + } + RGBColors[Float3Index + 0] = Color[0]; + RGBColors[Float3Index + 1] = Color[1]; + RGBColors[Float3Index + 2] = Color[2]; + Alphas[VertexInstanceIdx] = Color[3]; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + const FVertexID& VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID); + const int32 UEVertexIdx = VertexID.GetValue(); + if (VertexIDToHIndex.IsValidIndex(UEVertexIdx)) + { + MeshTriangleVertexIndices[VertexInstanceIdx] = VertexIDToHIndex[UEVertexIdx]; + } + + VertexInstanceIdx++; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + const FPolygonGroupID &PolygonGroupID = MeshDescription.GetPolygonPolygonGroup(PolygonID); + const int32 MaterialIndex = PolygonGroupToMaterialIndex.FindChecked(PolygonGroupID); + TriangleMaterialIndices.Add(MaterialIndex); + + TriangleIdx++; + } + } + + // Now transfer valid vertex instance attributes to Houdini vertex attributes + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) + { + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (UVLayerIndex > 0) + UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = NumVertexInstances; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, Normals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + Tangents.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceBinormalSignsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + Binormals.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + RGBColors.GetData(), 0, AttributeInfoVertex.count), false); + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.count = Alphas.Num(); + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + Alphas.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); + + // Send the array of face vertex counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); + + // Send material assignments to Houdini + if (NumMaterials > 0) + { + // List of materials, one for each face. + TArray TriangleMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); + + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (bAttributeSuccess) + { + check(0); + return false; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE SMOOTHING MASKS + //--------------------------------------------------------------------------------------------------------------------- + TArray TriangleSmoothingMasks; + TriangleSmoothingMasks.SetNumZeroed(NumTriangles); + FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); + if (TriangleSmoothingMasks.Num() > 0) + { + HAPI_AttributeInfo AttributeInfoSmoothingMasks; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + AttributeInfoSmoothingMasks.tupleSize = 1; + AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); + AttributeInfoSmoothingMasks.exists = true; + AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->LightMapResolution); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + // Try to create groups for the static mesh component's tags + if (StaticMeshComponent->ComponentTags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Try to create groups for the parent Actor's tags + if (ParentActor->Tags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + + /* + FString LevelPath = FString(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +void +FUnrealMeshTranslator::CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials) +{ + // We need to create list of unique materials. + TArray UniqueMaterialList; + + UMaterialInterface * MaterialInterface = nullptr; + char* UniqueName = nullptr; + + UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); + + if (Materials.Num()) + { + // We have materials. + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) + { + UniqueName = nullptr; + MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface) + { + // Null material interface found, add default instead. + UniqueMaterialList.Add(DefaultMaterialName); + + // No need to collect material parameters on the default material + continue; + } + + // We found a material, get its name and material parameters + FString FullMaterialName = MaterialInterface->GetPathName(); + UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); + UniqueMaterialList.Add(UniqueName); + } + } + else + { + // We do not have any materials, add default. + UniqueMaterialList.Add(DefaultMaterialName); + } + + // TODO: Needs to be improved! + // We shouldnt be testing for each face, but only for each unique facematerial value... + for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) + { + int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; + if (UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) + { + OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); + } + else + { + OutStaticMeshFaceMaterials.Add(DefaultMaterialName); + } + } +} + + +void +FUnrealMeshTranslator::CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials, + TMap> & OutScalarMaterialParameters, + TMap> & OutVectorMaterialParameters, + TMap> & OutTextureMaterialParameters) +{ + // We need to create list of unique materials. + TArray UniqueMaterialList; + + UMaterialInterface * MaterialInterface = nullptr; + char* UniqueName = nullptr; + + UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); + + // Initialize material parameter arrays + TMap> ScalarParams; + TMap> VectorParams; + TMap> TextureParams; + + if (Materials.Num()) + { + // We have materials. + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) + { + UniqueName = nullptr; + MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface) + { + // Null material interface found, add default instead. + UniqueMaterialList.Add(DefaultMaterialName); + + // No need to collect material parameters on the default material + continue; + } + + // We found a material, get its name and material parameters + FString FullMaterialName = MaterialInterface->GetPathName(); + UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); + UniqueMaterialList.Add(UniqueName); + + // Collect all scalar parameters in all materials + { + TArray MaterialScalarParamInfos; + TArray MaterialScalarParamGuids; + MaterialInterface->GetAllScalarParameterInfo(MaterialScalarParamInfos, MaterialScalarParamGuids); + + for (auto & CurScalarParam : MaterialScalarParamInfos) + { + FString CurScalarParamName = CurScalarParam.Name.ToString(); + float CurScalarVal; + MaterialInterface->GetScalarParameterValue(CurScalarParam, CurScalarVal); + if (!ScalarParams.Contains(CurScalarParamName)) + { + TArray CurArray; + CurArray.SetNumUninitialized(Materials.Num()); + // Initialize the array with the Min float value + for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) + CurArray[ArrIdx] = FLT_MIN; + + ScalarParams.Add(CurScalarParamName, CurArray); + OutScalarMaterialParameters.Add(CurScalarParamName); + } + + ScalarParams[CurScalarParamName][MaterialIdx] = CurScalarVal; + } + } + + // Collect all vector parameters in all materials + { + TArray MaterialVectorParamInfos; + TArray MaterialVectorParamGuids; + MaterialInterface->GetAllVectorParameterInfo(MaterialVectorParamInfos, MaterialVectorParamGuids); + + for (auto & CurVectorParam : MaterialVectorParamInfos) + { + FString CurVectorParamName = CurVectorParam.Name.ToString(); + FLinearColor CurVectorValue; + MaterialInterface->GetVectorParameterValue(CurVectorParam, CurVectorValue); + if (!VectorParams.Contains(CurVectorParamName)) + { + TArray CurArray; + CurArray.SetNumUninitialized(Materials.Num()); + FLinearColor MinColor(FLT_MIN, FLT_MIN, FLT_MIN, FLT_MIN); + for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) + CurArray[ArrIdx] = MinColor; + + VectorParams.Add(CurVectorParamName, CurArray); + OutVectorMaterialParameters.Add(CurVectorParamName); + } + + VectorParams[CurVectorParamName][MaterialIdx] = CurVectorValue; + } + } + + // Collect all texture parameters in all materials + { + TArray MaterialTextureParamInfos; + TArray MaterialTextureParamGuids; + MaterialInterface->GetAllTextureParameterInfo(MaterialTextureParamInfos, MaterialTextureParamGuids); + + for (auto & CurTextureParam : MaterialTextureParamInfos) + { + FString CurTextureParamName = CurTextureParam.Name.ToString(); + UTexture * CurTexture = nullptr; + MaterialInterface->GetTextureParameterValue(CurTextureParam, CurTexture); + + if (!CurTexture || CurTexture->IsPendingKill()) + continue; + + FString TexturePath = CurTexture->GetPathName(); + if (!TextureParams.Contains(CurTextureParamName)) + { + TArray CurArray; + CurArray.SetNumZeroed(Materials.Num()); + + TextureParams.Add(CurTextureParamName, CurArray); + OutTextureMaterialParameters.Add(CurTextureParamName); + } + + char * TexturePathRawStr = UniqueName = FHoudiniEngineUtils::ExtractRawString(TexturePath); + TextureParams[CurTextureParamName][MaterialIdx] = TexturePathRawStr; + } + } + + } + } + else + { + // We do not have any materials, add default. + UniqueMaterialList.Add(DefaultMaterialName); + } + + // TODO: Needs to be improved! + // We shouldnt be testing for each face, but only for each unique facematerial value... + for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) + { + int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; + if(UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) + { + OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); + + for (auto & Pair : ScalarParams) + { + OutScalarMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); + } + + for (auto & Pair : VectorParams) + { + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].R); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].G); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].B); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].A); + } + + for (auto & Pair : TextureParams) + { + OutTextureMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); + } + } + else + { + OutStaticMeshFaceMaterials.Add(DefaultMaterialName); + } + } +} + + +void +FUnrealMeshTranslator::DeleteFaceMaterialArray(TArray& OutStaticMeshFaceMaterials) +{ + // Clean up the memory allocated by CreateFaceMaterialArray() + TSet UniqueMaterials(OutStaticMeshFaceMaterials); + for (TSet::TIterator Iter = UniqueMaterials.CreateIterator(); Iter; ++Iter) + { + char* MaterialName = *Iter; + FMemory::Free(MaterialName); + } + + OutStaticMeshFaceMaterials.Empty(); +} + +bool +FUnrealMeshTranslator::CreateInputNodeForBox( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& BoxCenter, + const FVector& BoxExtent, + const FRotator& BoxRotation) +{ + // Create a new input node for the box collider + FString BoxName = TEXT("box") + FString::FromInt(ColliderIndex); + + // Create the node in this input object's OBJ node + HAPI_NodeId BoxNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("box"), BoxName, false, &BoxNodeId), false); + + // Set the box parameters + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 0, BoxExtent.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 1, BoxExtent.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 2, BoxExtent.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 0, BoxCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 1, BoxCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 2, BoxCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 0, BoxRotation.Roll); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 2, BoxRotation.Pitch); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 1, BoxRotation.Yaw); + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), BoxNodeId, &CookOptions); + */ + if (!FHoudiniEngineUtils::HapiCookNode(BoxNodeId, nullptr, true)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_box") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, BoxNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForSphere( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphereCenter, + const float& SphereRadius) +{ + // Create a new input node for the sphere collider + const char * SphereName = ""; + { + FString SPH = TEXT("Sphere") + FString::FromInt(ColliderIndex); + SphereName = TCHAR_TO_UTF8(*SPH); + } + + // Create the node in this input object's OBJ node + HAPI_NodeId SphereNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "sphere", SphereName, false, &SphereNodeId), false); + + // Set the box parameters + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 1, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 2, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 0, SphereCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 1, SphereCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 2, SphereCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "type", 0, 1); + /* + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "scale", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + */ + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), SphereNodeId, &CookOptions); + */ + if (!FHoudiniEngineUtils::HapiCookNode(SphereNodeId, nullptr, true)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_sphere") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphereNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForSphyl( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphylCenter, + const FRotator& SphylRotation, + const float& SphylRadius, + const float& SphereLength) +{ + // + // Get the Sphyl's vertices and indices + // (code drived from FKSphylElem::GetElemSolid) + // + + // TODO: + // Rotation? + + const int32 NumSides = 6; + const int32 NumRings = (NumSides / 2) + 1; + + // The first/last arc are on top of each other. + const int32 NumVerts = (NumSides + 1) * (NumRings + 1); + + // Calculate the vertices for one arc + TArray ArcVertices; + ArcVertices.SetNum(NumRings + 1); + for (int32 RingIdx = 0; RingIdx < NumRings + 1; RingIdx++) + { + float Angle; + float ZOffset; + if (RingIdx <= NumSides / 4) + { + Angle = ((float)RingIdx / (NumRings - 1)) * PI; + ZOffset = 0.5 * SphereLength; + } + else + { + Angle = ((float)(RingIdx - 1) / (NumRings - 1)) * PI; + ZOffset = -0.5 * SphereLength; + } + + // Note- unit sphere, so position always has mag of one. We can just use it for normal! + FVector SpherePos; + SpherePos.X = 0.0f; + SpherePos.Y = SphylRadius * FMath::Sin(Angle); + SpherePos.Z = SphylRadius * FMath::Cos(Angle); + + ArcVertices[RingIdx] = SpherePos + FVector(0, 0, ZOffset); + } + + // Get the transform matrix for the rotation + FRotationMatrix SphylRotMatrix(SphylRotation); + + // Get the Sphyl's vertices by rotating the arc NumSides+1 times. + TArray Vertices; + Vertices.SetNum(NumVerts * 3); + for (int32 SideIdx = 0; SideIdx < NumSides + 1; SideIdx++) + { + const FRotator ArcRotator(0, 360.f * ((float)SideIdx / NumSides), 0); + const FRotationMatrix ArcRot(ArcRotator); + const float XTexCoord = ((float)SideIdx / NumSides); + + for (int32 VertIdx = 0; VertIdx < NumRings + 1; VertIdx++) + { + int32 VIx = (NumRings + 1)*SideIdx + VertIdx; + + FVector CurPosition = SphylCenter + ArcRot.TransformPosition(ArcVertices[VertIdx]); + CurPosition = SphylRotMatrix.TransformPosition(CurPosition); + + // Convert the UE4 position to Houdini + Vertices[VIx * 3 + 0] = CurPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[VIx * 3 + 1] = CurPosition.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[VIx * 3 + 2] = CurPosition.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + } + + // Add all of the indices to the mesh. + int32 NumIndices = NumSides * NumRings * 6; + TArray Indices; + Indices.SetNum(NumIndices); + + int32 CurIndex = 0; + for (int32 SideIdx = 0; SideIdx < NumSides; SideIdx++) + { + const int32 a0start = (SideIdx + 0) * (NumRings + 1); + const int32 a1start = (SideIdx + 1) * (NumRings + 1); + for (int32 RingIdx = 0; RingIdx < NumRings; RingIdx++) + { + // First Tri (reverse winding) + Indices[CurIndex+0] = a0start + RingIdx + 0; + Indices[CurIndex+2] = a1start + RingIdx + 0; + Indices[CurIndex+1] = a0start + RingIdx + 1; + CurIndex += 3; + // Second Tri (reverse winding) + Indices[CurIndex+0] = a1start + RingIdx + 0; + Indices[CurIndex+2] = a1start + RingIdx + 1; + Indices[CurIndex+1] = a0start + RingIdx + 1; + CurIndex += 3; + } + } + + // + // Create the Sphyl Mesh in houdini + // + HAPI_NodeId SphylNodeId = -1; + FString SphylName = TEXT("Sphyl") + FString::FromInt(ColliderIndex); + if(!CreateInputNodeForCollider(SphylNodeId, InParentNodeID, ColliderIndex, SphylName, Vertices, Indices)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_capsule") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphylNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForConvex( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FKConvexElem& ConvexCollider) +{ + TArray Vertices; + TArray Indices; + +#if PHYSICS_INTERFACE_PHYSX + if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) +#elif WITH_CHAOS + //if (ConvexCollider.GetChaosConvexMesh().IsValid()) + if (ConvexCollider.IndexData.Num() > 0 && ConvexCollider.IndexData.Num() % 3 == 0) +#else + if(false) +#endif + { + // Get the convex colliders vertices and indices from the mesh + TArray VertexBuffer; + TArray IndexBuffer; + ConvexCollider.AddCachedSolidConvexGeom(VertexBuffer, IndexBuffer, FColor::White); + + Vertices.SetNum(VertexBuffer.Num() * 3); + int32 CurIndex = 0; + for (auto& CurVert : VertexBuffer) + { + Vertices[CurIndex * 3 + 0] = CurVert.Position.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 1] = CurVert.Position.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 2] = CurVert.Position.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + CurIndex++; + } + + Indices.SetNum(IndexBuffer.Num()); + for (int Idx = 0; (Idx + 2) < IndexBuffer.Num(); Idx += 3) + { + // Reverse winding + Indices[Idx + 0] = Indices[Idx + 0]; + Indices[Idx + 2] = Indices[Idx + 1]; + Indices[Idx + 1] = Indices[Idx + 2]; + } + } + else + { + int32 NumVert = ConvexCollider.VertexData.Num(); + Vertices.SetNum(NumVert * 3); + //Indices.SetNum(NumVert); + int32 CurIndex = 0; + for (auto& CurVert : ConvexCollider.VertexData) + { + Vertices[CurIndex * 3 + 0] = CurVert.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 1] = CurVert.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 2] = CurVert.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + + /* + // TODO: Get proper polygons... + Indices[CurIndex] = CurIndex; + */ + CurIndex++; + } + + // TODO: Get Proper polygons + for (int32 Idx = 0; Idx + 2 < NumVert; Idx++) + { + Indices.Add(Idx + 0); + Indices.Add(Idx + 1); + Indices.Add(Idx + 2); + } + + /* + for (int32 Idx = 0; Idx + 3 < NumVert; Idx+= 4) + { + Indices.Add(Idx + 0); + Indices.Add(Idx + 1); + Indices.Add(Idx + 2); + + Indices.Add(Idx + 2); + Indices.Add(Idx + 1); + Indices.Add(Idx + 3); + } + */ + } + + // + // Create the Convex Mesh in houdini + // + HAPI_NodeId ConvexNodeId = -1; + FString ConvexName = TEXT("Convex") + FString::FromInt(ColliderIndex); + if (!CreateInputNodeForCollider(ConvexNodeId, InParentNodeID, ColliderIndex, ConvexName, Vertices, Indices)) + return false; + + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), ColliderNodeId, &CookOptions); + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "groupcreate", GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_ucx + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_ucx") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Create a convex hull (shrinkwrap::2.0) node to fix the lack of proper indices + HAPI_NodeId ConvexHullNodeId = -1; + FString ConvexHullName = TEXT("ConvexHull") + FString::FromInt(ColliderIndex); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "shrinkwrap::2.0", ConvexHullName, false, &ConvexHullNodeId), false); + + if (ConvexHullNodeId > 0) + { + // Connect the collider to the convex hull + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), ConvexHullNodeId, 0, ConvexNodeId, 0), false); + + // Connect the convex hull to the group + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexHullNodeId, 0), false); + } + else + { + // Connect the collider to the group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexNodeId, 0), false); + + } + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForCollider( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FString& ColliderName, + const TArray& ColliderVertices, + const TArray& ColliderIndices) +{ + // Create a new input node for the collider + const char * ColliderNameStr = TCHAR_TO_UTF8(*ColliderName); + + // Create the node in this input object's OBJ node + HAPI_NodeId ColliderNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "null", ColliderNameStr, false, &ColliderNodeId), false); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = ColliderIndices.Num(); + Part.faceCount = ColliderIndices.Num() / 3; + Part.pointCount = ColliderVertices.Num() / 3; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), ColliderNodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = ColliderVertices.Num() / 3; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Upload the positions + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + ColliderVertices.GetData(), 0, AttributeInfoPoint.count), false); + + // Upload the indices + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, ColliderIndices.GetData(), 0, ColliderIndices.Num()), false); + + // Generate the array of face counts. + TArray ColldierFaceCounts; + ColldierFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, ColldierFaceCounts.GetData(), 0, ColldierFaceCounts.Num()), false); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), ColliderNodeId), false); + + OutNodeId = ColliderNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + const int32 & NodeId, + const int32 & PartId, + const int32 & Count, + const TArray & TriangleMaterials, + const TMap> & ScalarMaterialParameters, + const TMap> & VectorMaterialParameters, + const TMap> & TextureMaterialParameters) +{ + if (NodeId < 0) + return false; + + bool bSuccess = true; + + // Create attribute for materials. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.count = Count; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial, + (const char **)TriangleMaterials.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + + // Add scalar material parameter attributes + for (auto & Pair : ScalarMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 1; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + Pair.Value.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + // Add vector material parameters + for (auto & Pair : VectorMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 4; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + Pair.Value.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + // Add texture material parameter attributes + for (auto & Pair : TextureMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 1; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // Replace null strings by empty strings to prevent crashes when setting the attribute. + char* EmptyString = nullptr; + TArray StringData = Pair.Value; + for (auto& CurValue : StringData) + { + if (CurValue != nullptr) + continue; + + if (!EmptyString) + { + // Allocate the empty string the first time it is needed. This is free'd along with + // the other strings in the arrays in TextureMaterialParameters by calls to DeleteFaceMaterialArray + EmptyString = FHoudiniEngineUtils::ExtractRawString(FString(TEXT(""))); + } + CurValue = EmptyString; + } + + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + (const char **)StringData.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + return bSuccess; +} + +/* +bool +FUnrealMeshTranslator::AddLevelPathAttributeToMesh( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const FString& LevelPath, + const int32& Count) +{ + if (NodeId == -1 || LevelPath.IsEmpty() || Count <= 0) + return false; + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = Count; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Count); + for (int32 Idx = 0; Idx < Count; ++Idx) + { + PrimitiveAttrs[Idx] = LevelPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} */ \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.h b/Source/HoudiniEngine/Private/UnrealMeshTranslator.h index b524ea308..571b2e895 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.h @@ -1,176 +1,176 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -class UStaticMesh; -class UStaticMeshComponent; -class UMaterialInterface; -class UStaticMeshSocket; - -struct FStaticMeshSourceModel; -struct FStaticMeshLODResources; -struct FMeshDescription; -struct FKConvexElem; - -struct HOUDINIENGINE_API FUnrealMeshTranslator -{ - public: - - // HAPI : Marshaling, extract geometry and create input asset for it - return true on success - static bool HapiCreateInputNodeForStaticMesh( - UStaticMesh * Mesh, - HAPI_NodeId& InputObjectNodeId, - const FString& InputNodeName, - class UStaticMeshComponent* StaticMeshComponent = nullptr, - const bool& ExportAllLODs = false, - const bool& ExportSockets = false, - const bool& ExportColliders = false); - - // Convert the Mesh using FStaticMeshLODResources - static bool CreateInputNodeForStaticMeshLODResources( - const HAPI_NodeId& NodeId, - const FStaticMeshLODResources& LODResources, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - // Convert the Mesh using FMeshDescription - static bool CreateInputNodeForMeshDescription( - const HAPI_NodeId& NodeId, - const FMeshDescription& MeshDescription, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - // Convert the Mesh using FRawMesh - - static bool CreateInputNodeForRawMesh( - const HAPI_NodeId& NodeId, - const FStaticMeshSourceModel& SourceModel, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - static bool CreateInputNodeForBox( - HAPI_NodeId& OutBoxNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& BoxCenter, - const FVector& BoxExtent, - const FRotator& BoxRotation); - - static bool CreateInputNodeForSphere( - HAPI_NodeId& OutSphereNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphereCenter, - const float& SphereRadius); - - static bool CreateInputNodeForSphyl( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphylCenter, - const FRotator& SphylRotation, - const float& SphylRadius, - const float& SphereLength); - - static bool CreateInputNodeForConvex( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FKConvexElem& ConvexCollider); - - static bool CreateInputNodeForCollider( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FString& ColliderName, - const TArray& ColliderVertices, - const TArray& ColliderIndices); - - static bool CreateInputNodeForMeshSockets( - const TArray& InMeshSocket, - const HAPI_NodeId& InParentNodeId, - HAPI_NodeId& OutSocketsNodeId); - - - // Helper function to extract the array of material names used by a given mesh - // This is used for marshalling static mesh's materials. - // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() - static void CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray& OutStaticMeshFaceMaterials); - - // Helper function to extract the array of material names used by a given mesh - // Also extracts all scalar/vector/texture parameter in the materials - // This is used for marshalling static mesh's materials. - // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() - // The texture parameter array also needs to be cleared. - static void CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray & OutStaticMeshFaceMaterials, - TMap> & OutScalarMaterialParameters, - TMap> & OutVectorMaterialParameters, - TMap> & OutTextureMaterialParameters); - - // Delete helper array of material names. - // Clean up the memory allocated by CreateFaceMaterialArray() - static void DeleteFaceMaterialArray(TArray & OutStaticMeshFaceMaterials); - - // Create and set mesh material attribute and material (scalar, vector and texture) parameters attributes - static bool CreateHoudiniMeshAttributes( - const int32 & NodeId, - const int32 & PartId, - const int32 & Count, - const TArray & TriangleMaterials, - const TMap> & ScalarMaterialParameters, - const TMap> & VectorMaterialParameters, - const TMap> & TextureMaterialParameters); - - /* - // Creates the unreal_level_path attribute on the input mesh - static bool AddLevelPathAttributeToMesh( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const FString& LevelPath, - const int32& Count); - */ - - private: - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +class UStaticMesh; +class UStaticMeshComponent; +class UMaterialInterface; +class UStaticMeshSocket; + +struct FStaticMeshSourceModel; +struct FStaticMeshLODResources; +struct FMeshDescription; +struct FKConvexElem; + +struct HOUDINIENGINE_API FUnrealMeshTranslator +{ + public: + + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForStaticMesh( + UStaticMesh * Mesh, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + class UStaticMeshComponent* StaticMeshComponent = nullptr, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false, + const bool& ExportColliders = false); + + // Convert the Mesh using FStaticMeshLODResources + static bool CreateInputNodeForStaticMeshLODResources( + const HAPI_NodeId& NodeId, + const FStaticMeshLODResources& LODResources, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + // Convert the Mesh using FMeshDescription + static bool CreateInputNodeForMeshDescription( + const HAPI_NodeId& NodeId, + const FMeshDescription& MeshDescription, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + // Convert the Mesh using FRawMesh + + static bool CreateInputNodeForRawMesh( + const HAPI_NodeId& NodeId, + const FStaticMeshSourceModel& SourceModel, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + static bool CreateInputNodeForBox( + HAPI_NodeId& OutBoxNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& BoxCenter, + const FVector& BoxExtent, + const FRotator& BoxRotation); + + static bool CreateInputNodeForSphere( + HAPI_NodeId& OutSphereNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphereCenter, + const float& SphereRadius); + + static bool CreateInputNodeForSphyl( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphylCenter, + const FRotator& SphylRotation, + const float& SphylRadius, + const float& SphereLength); + + static bool CreateInputNodeForConvex( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FKConvexElem& ConvexCollider); + + static bool CreateInputNodeForCollider( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FString& ColliderName, + const TArray& ColliderVertices, + const TArray& ColliderIndices); + + static bool CreateInputNodeForMeshSockets( + const TArray& InMeshSocket, + const HAPI_NodeId& InParentNodeId, + HAPI_NodeId& OutSocketsNodeId); + + + // Helper function to extract the array of material names used by a given mesh + // This is used for marshalling static mesh's materials. + // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() + static void CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials); + + // Helper function to extract the array of material names used by a given mesh + // Also extracts all scalar/vector/texture parameter in the materials + // This is used for marshalling static mesh's materials. + // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() + // The texture parameter array also needs to be cleared. + static void CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray & OutStaticMeshFaceMaterials, + TMap> & OutScalarMaterialParameters, + TMap> & OutVectorMaterialParameters, + TMap> & OutTextureMaterialParameters); + + // Delete helper array of material names. + // Clean up the memory allocated by CreateFaceMaterialArray() + static void DeleteFaceMaterialArray(TArray & OutStaticMeshFaceMaterials); + + // Create and set mesh material attribute and material (scalar, vector and texture) parameters attributes + static bool CreateHoudiniMeshAttributes( + const int32 & NodeId, + const int32 & PartId, + const int32 & Count, + const TArray & TriangleMaterials, + const TMap> & ScalarMaterialParameters, + const TMap> & VectorMaterialParameters, + const TMap> & TextureMaterialParameters); + + /* + // Creates the unreal_level_path attribute on the input mesh + static bool AddLevelPathAttributeToMesh( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const FString& LevelPath, + const int32& Count); + */ + + private: + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp index 268476eba..844259113 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp @@ -1,124 +1,124 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealSplineTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "Components/SplineComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniSplineTranslator.h" - -bool -FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId& CreatedInputNodeId, const FString& NodeName) -{ - if (!SplineComponent || SplineComponent->IsPendingKill()) - return false; - - int32 NumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); - float SplineLength = SplineComponent->GetSplineLength(); - - // Calculate the number of refined point we want - int32 NumberOfRefinedSplinePoints = SplineResolution > 0.0f ? ceil(SplineLength / SplineResolution) + 1 : NumberOfControlPoints; - - TArray RefinedSplinePositions; - TArray RefinedSplineRotations; - TArray RefinedSplineScales; - - if (NumberOfRefinedSplinePoints <= NumberOfControlPoints) - { - // There's not enough refined points, so we'll use the control points instead - RefinedSplinePositions.SetNumZeroed(NumberOfControlPoints); - RefinedSplineRotations.SetNumZeroed(NumberOfControlPoints); - RefinedSplineScales.SetNumZeroed(NumberOfControlPoints); - - for (int32 n = 0; n < NumberOfControlPoints; ++n) - { - RefinedSplinePositions[n] = SplineComponent->GetLocationAtSplinePoint(n, ESplineCoordinateSpace::Local); - RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World); - RefinedSplineScales[n] = SplineComponent->GetScaleAtSplinePoint(n); - } - } - else - { - // Calculate the refined spline component - RefinedSplinePositions.SetNumZeroed(NumberOfRefinedSplinePoints); - RefinedSplineRotations.SetNumZeroed(NumberOfRefinedSplinePoints); - RefinedSplineScales.SetNumZeroed(NumberOfRefinedSplinePoints); - - float CurrentDistance = 0.0f; - for (int32 n = 0; n < NumberOfRefinedSplinePoints; ++n) - { - RefinedSplinePositions[n] = SplineComponent->GetLocationAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::Local); - RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::World); - RefinedSplineScales[n] = SplineComponent->GetScaleAtDistanceAlongSpline(CurrentDistance); - - CurrentDistance += SplineResolution; - } - } - - - if (!FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData(CreatedInputNodeId, NodeName, - &RefinedSplinePositions, &RefinedSplineRotations, &RefinedSplineScales, - EHoudiniCurveType::Polygon, EHoudiniCurveMethod::Breakpoints, false, SplineComponent->IsClosedLoop())) - return false; - - // Add spline component tags if it has any - bool NeedToCommit = FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, SplineComponent->ComponentTags); - - // Add the parent actor's tag if it has any - AActor* ParentActor = SplineComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - if (FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, ParentActor->Tags)) - NeedToCommit = true; - - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - FHoudiniApi::GetPartInfo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId, 0, &PartInfo); - - // Add the unreal_actor_path attribute - if(FHoudiniEngineUtils::AddActorPathAttribute(CreatedInputNodeId, 0, ParentActor, PartInfo.faceCount)) - NeedToCommit = true; - - // Add the unreal_level_path attribute - if(FHoudiniEngineUtils::AddLevelPathAttribute(CreatedInputNodeId, 0, ParentActor->GetLevel(), PartInfo.faceCount)) - NeedToCommit = true; - } - - if (NeedToCommit) - { - // We successfully added tags to the geo, so we need to commit the changes - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups for the spline input's tags!")); - } - - - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealSplineTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "Components/SplineComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniSplineTranslator.h" + +bool +FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId& CreatedInputNodeId, const FString& NodeName) +{ + if (!SplineComponent || SplineComponent->IsPendingKill()) + return false; + + int32 NumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); + float SplineLength = SplineComponent->GetSplineLength(); + + // Calculate the number of refined point we want + int32 NumberOfRefinedSplinePoints = SplineResolution > 0.0f ? ceil(SplineLength / SplineResolution) + 1 : NumberOfControlPoints; + + TArray RefinedSplinePositions; + TArray RefinedSplineRotations; + TArray RefinedSplineScales; + + if (NumberOfRefinedSplinePoints <= NumberOfControlPoints) + { + // There's not enough refined points, so we'll use the control points instead + RefinedSplinePositions.SetNumZeroed(NumberOfControlPoints); + RefinedSplineRotations.SetNumZeroed(NumberOfControlPoints); + RefinedSplineScales.SetNumZeroed(NumberOfControlPoints); + + for (int32 n = 0; n < NumberOfControlPoints; ++n) + { + RefinedSplinePositions[n] = SplineComponent->GetLocationAtSplinePoint(n, ESplineCoordinateSpace::Local); + RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World); + RefinedSplineScales[n] = SplineComponent->GetScaleAtSplinePoint(n); + } + } + else + { + // Calculate the refined spline component + RefinedSplinePositions.SetNumZeroed(NumberOfRefinedSplinePoints); + RefinedSplineRotations.SetNumZeroed(NumberOfRefinedSplinePoints); + RefinedSplineScales.SetNumZeroed(NumberOfRefinedSplinePoints); + + float CurrentDistance = 0.0f; + for (int32 n = 0; n < NumberOfRefinedSplinePoints; ++n) + { + RefinedSplinePositions[n] = SplineComponent->GetLocationAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::Local); + RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::World); + RefinedSplineScales[n] = SplineComponent->GetScaleAtDistanceAlongSpline(CurrentDistance); + + CurrentDistance += SplineResolution; + } + } + + + if (!FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData(CreatedInputNodeId, NodeName, + &RefinedSplinePositions, &RefinedSplineRotations, &RefinedSplineScales, + EHoudiniCurveType::Polygon, EHoudiniCurveMethod::Breakpoints, false, SplineComponent->IsClosedLoop())) + return false; + + // Add spline component tags if it has any + bool NeedToCommit = FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, SplineComponent->ComponentTags); + + // Add the parent actor's tag if it has any + AActor* ParentActor = SplineComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + if (FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, ParentActor->Tags)) + NeedToCommit = true; + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + FHoudiniApi::GetPartInfo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId, 0, &PartInfo); + + // Add the unreal_actor_path attribute + if(FHoudiniEngineUtils::AddActorPathAttribute(CreatedInputNodeId, 0, ParentActor, PartInfo.faceCount)) + NeedToCommit = true; + + // Add the unreal_level_path attribute + if(FHoudiniEngineUtils::AddLevelPathAttribute(CreatedInputNodeId, 0, ParentActor->GetLevel(), PartInfo.faceCount)) + NeedToCommit = true; + } + + if (NeedToCommit) + { + // We successfully added tags to the geo, so we need to commit the changes + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups for the spline input's tags!")); + } + + + return true; +} diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.h b/Source/HoudiniEngine/Private/UnrealSplineTranslator.h index e6d302233..f4d8adeb5 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.h @@ -1,39 +1,39 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "UObject/NameTypes.h" - -class USplineComponent; - -struct HOUDINIENGINE_API FUnrealSplineTranslator -{ -public: - static bool CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId &CreatedInputNodeId, const FString& NodeName); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "UObject/NameTypes.h" + +class USplineComponent; + +struct HOUDINIENGINE_API FUnrealSplineTranslator +{ +public: + static bool CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId &CreatedInputNodeId, const FString& NodeName); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI.h b/Source/HoudiniEngine/Public/HAPI/HAPI.h index 0f10baad9..ead7ca823 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI.h @@ -1205,6 +1205,36 @@ HAPI_DECL HAPI_SetTimelineOptions( const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options ); +/// @brief Gets the global compositor options. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[out] compositor_options +/// The compositor options struct. +/// +HAPI_DECL HAPI_GetCompositorOptions( + const HAPI_Session * session, + HAPI_CompositorOptions * compositor_options); + +/// @brief Sets the global compositor options. +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] compositor_options +/// The compositor options. +/// +HAPI_DECL HAPI_SetCompositorOptions( + const HAPI_Session * session, + const HAPI_CompositorOptions * compositor_options); + /// @defgroup Assets /// Functions for managing asset libraries diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h index c02bbb5f8..6d89a2be0 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h @@ -1937,4 +1937,15 @@ struct HAPI_API HAPI_SessionSyncInfo }; HAPI_C_STRUCT_TYPEDEF( HAPI_SessionSyncInfo ) +/// Configuration options for Houdini's compositing context +struct HAPI_API HAPI_CompositorOptions +{ + /// Specifies the maximum allowed width of an image in the compositor + int maximumResolutionX; + + /// Specifies the maximum allowed height of an image in the compositor + int maximumResolutionY; +}; +HAPI_C_STRUCT_TYPEDEF( HAPI_CompositorOptions ) + #endif // __HAPI_COMMON_h__ diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Helpers.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Helpers.h index f9dcd46ea..d30e978de 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Helpers.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Helpers.h @@ -26,6 +26,13 @@ HAPI_DECL_RETURN( void ) HAPI_DECL_RETURN( HAPI_TimelineOptions ) HAPI_TimelineOptions_Create(); +// COMPOSITOR SETTINGS ------------------------------------------------------ + +HAPI_DECL_RETURN( void) + HAPI_CompositorOptions_Init( HAPI_CompositorOptions * in ); +HAPI_DECL_RETURN( HAPI_CompositorOptions ) + HAPI_CompositorOptions_Create(); + // ASSETS ------------------------------------------------------------------- HAPI_DECL_RETURN( void ) diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h index c172cfd27..aa75b641a 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h @@ -27,7 +27,7 @@ // expecting to compile against. #define HAPI_VERSION_HOUDINI_MAJOR 18 #define HAPI_VERSION_HOUDINI_MINOR 5 -#define HAPI_VERSION_HOUDINI_BUILD 563 +#define HAPI_VERSION_HOUDINI_BUILD 596 #define HAPI_VERSION_HOUDINI_PATCH 0 // The two components of the Houdini Engine (marketed) version. @@ -40,6 +40,6 @@ // might no longer compile. Semantic changes to the methods will also // cause this version to increase. This number will be reset to 0 // every time the Houdini Engine version is bumped. -#define HAPI_VERSION_HOUDINI_ENGINE_API 1 +#define HAPI_VERSION_HOUDINI_ENGINE_API 2 #endif // __HAPI_VERSION_h__ diff --git a/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs b/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs index 91c671201..07457e320 100644 --- a/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs +++ b/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs @@ -1,119 +1,119 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using UnrealBuildTool; -using System; -using System.IO; - -public class HoudiniEngineEditor : ModuleRules -{ - public HoudiniEngineEditor( ReadOnlyTargetRules Target ) : base( Target ) - { - bPrecompile = true; - PCHUsage = PCHUsageMode.NoSharedPCHs; - PrivatePCHHeaderFile = "Private/HoudiniEngineEditorPrivatePCH.h"; - - // Check if we are compiling on unsupported platforms. - if ( Target.Platform != UnrealTargetPlatform.Win64 && - Target.Platform != UnrealTargetPlatform.Mac && - Target.Platform != UnrealTargetPlatform.Linux ) - { - string Err = string.Format( "Houdini Engine Editor: Compiling for unsupported platform." ); - System.Console.WriteLine( Err ); - throw new BuildException( Err ); - } - - PublicIncludePaths.AddRange( - new string[] { - Path.Combine(ModuleDirectory, "Public") - } - ); - - PrivateIncludePaths.AddRange( - new string[] { - "HoudiniEngine/Private", - "HoudiniEngineRuntime/Private" - } - ); - - PrivateIncludePathModuleNames.AddRange( - new string[] { - "PlacementMode" - } - ); - - // Add common dependencies. - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "HoudiniEngine", - "HoudiniEngineRuntime", - "Slate", - "SlateCore", - "Landscape", - "Foliage" - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "AppFramework", - "AssetTools", - "ContentBrowser", - "DesktopWidgets", - "EditorStyle", - "EditorWidgets", - "Engine", - "InputCore", - "LevelEditor", - "MainFrame", - "Projects", - "PropertyEditor", - "RHI", - "RawMesh", - "RenderCore", - "TargetPlatform", - "UnrealEd", - "ApplicationCore", - "CurveEditor", - "Json", - "SceneOutliner", - "PropertyPath", - "MaterialEditor" - } - ); - - DynamicallyLoadedModuleNames.AddRange( - new string[] - { - "PlacementMode", - } - ); - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using UnrealBuildTool; +using System; +using System.IO; + +public class HoudiniEngineEditor : ModuleRules +{ + public HoudiniEngineEditor( ReadOnlyTargetRules Target ) : base( Target ) + { + bPrecompile = true; + PCHUsage = PCHUsageMode.NoSharedPCHs; + PrivatePCHHeaderFile = "Private/HoudiniEngineEditorPrivatePCH.h"; + + // Check if we are compiling on unsupported platforms. + if ( Target.Platform != UnrealTargetPlatform.Win64 && + Target.Platform != UnrealTargetPlatform.Mac && + Target.Platform != UnrealTargetPlatform.Linux ) + { + string Err = string.Format( "Houdini Engine Editor: Compiling for unsupported platform." ); + System.Console.WriteLine( Err ); + throw new BuildException( Err ); + } + + PublicIncludePaths.AddRange( + new string[] { + Path.Combine(ModuleDirectory, "Public") + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "HoudiniEngine/Private", + "HoudiniEngineRuntime/Private" + } + ); + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "PlacementMode" + } + ); + + // Add common dependencies. + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "HoudiniEngine", + "HoudiniEngineRuntime", + "Slate", + "SlateCore", + "Landscape", + "Foliage" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "AppFramework", + "AssetTools", + "ContentBrowser", + "DesktopWidgets", + "EditorStyle", + "EditorWidgets", + "Engine", + "InputCore", + "LevelEditor", + "MainFrame", + "Projects", + "PropertyEditor", + "RHI", + "RawMesh", + "RenderCore", + "TargetPlatform", + "UnrealEd", + "ApplicationCore", + "CurveEditor", + "Json", + "SceneOutliner", + "PropertyPath", + "MaterialEditor" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + "PlacementMode", + } + ); + } +} diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp index 36dd0f577..200856334 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp @@ -1,459 +1,459 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "AssetTypeActions_HoudiniAsset.h" -#include "HoudiniAsset.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniTool.h" -#include "HoudiniEngineEditorUtils.h" - -#include "EditorReimportHandler.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "HAL/FileManager.h" -#include "EditorFramework/AssetImportData.h" -#include "LevelEditor.h" -#include "Modules/ModuleManager.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FText -FAssetTypeActions_HoudiniAsset::GetName() const -{ - return LOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset"); -} - -FColor -FAssetTypeActions_HoudiniAsset::GetTypeColor() const -{ - return FColor(255, 165, 0); -} - -UClass * -FAssetTypeActions_HoudiniAsset::GetSupportedClass() const -{ - return UHoudiniAsset::StaticClass(); -} - -uint32 -FAssetTypeActions_HoudiniAsset::GetCategories() -{ - return EAssetTypeCategories::Misc; -} - -/* -UThumbnailInfo * -FAssetTypeActions_HoudiniAsset::GetThumbnailInfo(UObject * Asset) const -{ - if (!Asset || Asset->IsPendingKill()) - return nullptr; - - UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >(Asset); - UThumbnailInfo * ThumbnailInfo = HoudiniAsset->ThumbnailInfo; - if (!ThumbnailInfo) - { - // If we have no thumbnail information, construct it. - ThumbnailInfo = NewObject< USceneThumbnailInfo >(HoudiniAsset, USceneThumbnailInfo::StaticClass()); - HoudiniAsset->ThumbnailInfo = ThumbnailInfo; - } - - return ThumbnailInfo; -} -*/ - -bool -FAssetTypeActions_HoudiniAsset::HasActions(const TArray< UObject * > & InObjects) const -{ - return true; -} - -void -FAssetTypeActions_HoudiniAsset::GetActions(const TArray & InObjects, class FMenuBuilder & MenuBuilder) -{ - bool ValidObjects = false; - TArray> HoudiniAssets; - if (InObjects.Num() > 0) - { - HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); - ValidObjects = true; - } - - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Reimport", "Reimport"), - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ReimportTooltip", "Reimport selected Houdini Assets."), - FSlateIcon(StyleSetName, "HoudiniEngine._Reset"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteReimport, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAll", "Rebuild All Instances"), - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAllTooltip", "Reimports and rebuild all instances of the selected Houdini Assets."), - FSlateIcon(StyleSetName, "HoudiniEngine._RebuildAll"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorer", "Find Source"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorerTooltip", - "Opens explorer at the location of this asset."), - FSlateIcon(StyleSetName, "HoudiniEngine.Hou_OpenInHoudinidiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudini", "Open in Houdini"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudiniTooltip", - "Opens the selected asset in Houdini."), - FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuSeparator(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Instantiate", "Instantiate"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateTooltip", - "Instantiate the selected asset in the current world."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiate, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOrigin", "Instantiate at the origin"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOriginTooltip", - "Instantiate the selected asset in the current world. The Houdini Asset Actor will be created at the origin of the level (0,0,0)."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuSeparator(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingle", "Apply to the current selection (single input)"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingleTooltip", - "Applies the selected asset to the current world selection. All the selected object will be assigned to the first input."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMulti", "Apply to the current selection (multiple inputs )"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMultiTooltip", - "Applies the selected asset to the current world selection. Each selected object will be assigned to its own input (one object per input)."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatch", "Batch Apply to the current selection"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatchTooltip", - "Batch apply the selected asset to the current world selection. An instance of the selected Houdini asset will be created for each selected object."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); -} - - -bool -FAssetTypeActions_HoudiniAsset::AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) -{ - if (ActivationType == EAssetTypeActivationMethod::DoubleClicked) - { - bool ValidObjects = false; - TArray> HoudiniAssets; - if (InObjects.Num() > 0) - { - HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); - ValidObjects = true; - } - - if (ValidObjects) - { - ExecuteInstantiate(HoudiniAssets); - return true; - } - } - - return false; -} - - -TSharedRef -FAssetTypeActions_HoudiniAsset::AddLevelEditorMenuExtenders(TArray> HoudiniAssets) -{ - FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); - TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); - - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - TSharedRef Extender = MakeShareable(new FExtender); - Extender->AddMenuExtension( - "ActorAsset", - EExtensionHook::After, - LevelEditorCommandBindings, - FMenuExtensionDelegate::CreateLambda([this, HoudiniAssets, StyleSetName](FMenuBuilder& MenuBuilder) - { - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorer", "Find Source HDA"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorerTooltip", "Opens an explorer at the location of this actor's source HDA file."), - FSlateIcon(StyleSetName, "HoudiniEngine.DigitalAsset"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudini", "Open HDA in Houdini"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudiniTooltip", "Opens the selected asset's source HDA file in Houdini."), - FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - }) - ); - - return Extender; -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteReimport(TArray> InHoudiniAssetPtrs) -{ - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (HoudiniAsset) - FReimportManager::Instance()->Reimport(HoudiniAsset, true); - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs) -{ - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (HoudiniAsset && HoudiniAsset->AssetImportData) - { - const FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); - if (SourceFilePath.Len() && IFileManager::Get().FileSize(*SourceFilePath) != INDEX_NONE) - return FPlatformProcess::ExploreFolder(*SourceFilePath); - } - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs) -{ - if (!FHoudiniEngine::IsInitialized()) - return; - - if (InHoudiniAssetPtrs.Num() != 1) - return; - - UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - return; - - FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); - if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) - return; - - if (!FPaths::FileExists(SourceFilePath)) - return; - - // We'll need to modify the file name for expanded .hda - FString FileExtension = FPaths::GetExtension(SourceFilePath); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) - { - // the .hda directory is what we're actually interested in loading - SourceFilePath = FPaths::GetPath(SourceFilePath); - } - - if (FPaths::IsRelative(SourceFilePath)) - FPaths::ConvertRelativePathToFull(SourceFilePath); - - // Then open the HDA file in Houdini - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + "/hview"; - - FPlatformProcess::CreateProc( - HoudiniLocation.GetCharArray().GetData(), - SourceFilePath.GetCharArray().GetData(), - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); -} - - -void -FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs) -{ - // Reimports and then rebuild all instances of the asset - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (!HoudiniAsset) - continue; - - // Reimports the asset - FReimportManager::Instance()->Reimport(HoudiniAsset, true); - - // Rebuilds all instances of that asset in the scene - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * Component = *Itr; - if (Component && (Component->GetHoudiniAsset() == HoudiniAsset)) - { - Component->MarkAsNeedRebuild(); - } - } - } -} - - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE); -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI); -} -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH); -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType) -{ - if (InHoudiniAssetPtrs.Num() != 1) - return; - - UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - return; - - - FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(HoudiniAsset, InType, EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY); - /* - // Creating a temporary tool for the selected asset - TSoftObjectPtr HoudiniAssetPtr(HoudiniAsset); - FHoudiniTool HoudiniTool( - HoudiniAssetPtr, - FText::FromString(HoudiniAsset->GetName()), - Type, - EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY, - FText(), - NULL, - FString(), - false, - FFilePath(), - FHoudiniToolDirectory(), - FString()); - - SHoudiniToolPalette::InstantiateHoudiniTool(&HoudiniTool); - */ -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs) -{ - for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) - { - UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - continue; - - FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, FTransform::Identity); - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteInstantiate(TArray> InHoudiniAssetPtrs) -{ - FTransform DefaultTransform = FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform(); - for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) - { - UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - continue; - - FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, DefaultTransform); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "AssetTypeActions_HoudiniAsset.h" +#include "HoudiniAsset.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniTool.h" +#include "HoudiniEngineEditorUtils.h" + +#include "EditorReimportHandler.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "HAL/FileManager.h" +#include "EditorFramework/AssetImportData.h" +#include "LevelEditor.h" +#include "Modules/ModuleManager.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FText +FAssetTypeActions_HoudiniAsset::GetName() const +{ + return LOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset"); +} + +FColor +FAssetTypeActions_HoudiniAsset::GetTypeColor() const +{ + return FColor(255, 165, 0); +} + +UClass * +FAssetTypeActions_HoudiniAsset::GetSupportedClass() const +{ + return UHoudiniAsset::StaticClass(); +} + +uint32 +FAssetTypeActions_HoudiniAsset::GetCategories() +{ + return EAssetTypeCategories::Misc; +} + +/* +UThumbnailInfo * +FAssetTypeActions_HoudiniAsset::GetThumbnailInfo(UObject * Asset) const +{ + if (!Asset || Asset->IsPendingKill()) + return nullptr; + + UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >(Asset); + UThumbnailInfo * ThumbnailInfo = HoudiniAsset->ThumbnailInfo; + if (!ThumbnailInfo) + { + // If we have no thumbnail information, construct it. + ThumbnailInfo = NewObject< USceneThumbnailInfo >(HoudiniAsset, USceneThumbnailInfo::StaticClass()); + HoudiniAsset->ThumbnailInfo = ThumbnailInfo; + } + + return ThumbnailInfo; +} +*/ + +bool +FAssetTypeActions_HoudiniAsset::HasActions(const TArray< UObject * > & InObjects) const +{ + return true; +} + +void +FAssetTypeActions_HoudiniAsset::GetActions(const TArray & InObjects, class FMenuBuilder & MenuBuilder) +{ + bool ValidObjects = false; + TArray> HoudiniAssets; + if (InObjects.Num() > 0) + { + HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); + ValidObjects = true; + } + + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Reimport", "Reimport"), + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ReimportTooltip", "Reimport selected Houdini Assets."), + FSlateIcon(StyleSetName, "HoudiniEngine._Reset"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteReimport, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAll", "Rebuild All Instances"), + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAllTooltip", "Reimports and rebuild all instances of the selected Houdini Assets."), + FSlateIcon(StyleSetName, "HoudiniEngine._RebuildAll"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorer", "Find Source"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorerTooltip", + "Opens explorer at the location of this asset."), + FSlateIcon(StyleSetName, "HoudiniEngine.Hou_OpenInHoudinidiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudini", "Open in Houdini"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudiniTooltip", + "Opens the selected asset in Houdini."), + FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuSeparator(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Instantiate", "Instantiate"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateTooltip", + "Instantiate the selected asset in the current world."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiate, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOrigin", "Instantiate at the origin"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOriginTooltip", + "Instantiate the selected asset in the current world. The Houdini Asset Actor will be created at the origin of the level (0,0,0)."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuSeparator(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingle", "Apply to the current selection (single input)"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingleTooltip", + "Applies the selected asset to the current world selection. All the selected object will be assigned to the first input."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMulti", "Apply to the current selection (multiple inputs )"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMultiTooltip", + "Applies the selected asset to the current world selection. Each selected object will be assigned to its own input (one object per input)."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatch", "Batch Apply to the current selection"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatchTooltip", + "Batch apply the selected asset to the current world selection. An instance of the selected Houdini asset will be created for each selected object."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); +} + + +bool +FAssetTypeActions_HoudiniAsset::AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) +{ + if (ActivationType == EAssetTypeActivationMethod::DoubleClicked) + { + bool ValidObjects = false; + TArray> HoudiniAssets; + if (InObjects.Num() > 0) + { + HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); + ValidObjects = true; + } + + if (ValidObjects) + { + ExecuteInstantiate(HoudiniAssets); + return true; + } + } + + return false; +} + + +TSharedRef +FAssetTypeActions_HoudiniAsset::AddLevelEditorMenuExtenders(TArray> HoudiniAssets) +{ + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); + + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + TSharedRef Extender = MakeShareable(new FExtender); + Extender->AddMenuExtension( + "ActorAsset", + EExtensionHook::After, + LevelEditorCommandBindings, + FMenuExtensionDelegate::CreateLambda([this, HoudiniAssets, StyleSetName](FMenuBuilder& MenuBuilder) + { + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorer", "Find Source HDA"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorerTooltip", "Opens an explorer at the location of this actor's source HDA file."), + FSlateIcon(StyleSetName, "HoudiniEngine.DigitalAsset"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudini", "Open HDA in Houdini"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudiniTooltip", "Opens the selected asset's source HDA file in Houdini."), + FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + }) + ); + + return Extender; +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteReimport(TArray> InHoudiniAssetPtrs) +{ + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (HoudiniAsset) + FReimportManager::Instance()->Reimport(HoudiniAsset, true); + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs) +{ + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (HoudiniAsset && HoudiniAsset->AssetImportData) + { + const FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); + if (SourceFilePath.Len() && IFileManager::Get().FileSize(*SourceFilePath) != INDEX_NONE) + return FPlatformProcess::ExploreFolder(*SourceFilePath); + } + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs) +{ + if (!FHoudiniEngine::IsInitialized()) + return; + + if (InHoudiniAssetPtrs.Num() != 1) + return; + + UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + return; + + FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); + if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) + return; + + if (!FPaths::FileExists(SourceFilePath)) + return; + + // We'll need to modify the file name for expanded .hda + FString FileExtension = FPaths::GetExtension(SourceFilePath); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) + { + // the .hda directory is what we're actually interested in loading + SourceFilePath = FPaths::GetPath(SourceFilePath); + } + + if (FPaths::IsRelative(SourceFilePath)) + FPaths::ConvertRelativePathToFull(SourceFilePath); + + // Then open the HDA file in Houdini + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + "/hview"; + + FPlatformProcess::CreateProc( + HoudiniLocation.GetCharArray().GetData(), + SourceFilePath.GetCharArray().GetData(), + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); +} + + +void +FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs) +{ + // Reimports and then rebuild all instances of the asset + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (!HoudiniAsset) + continue; + + // Reimports the asset + FReimportManager::Instance()->Reimport(HoudiniAsset, true); + + // Rebuilds all instances of that asset in the scene + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * Component = *Itr; + if (Component && (Component->GetHoudiniAsset() == HoudiniAsset)) + { + Component->MarkAsNeedRebuild(); + } + } + } +} + + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE); +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI); +} +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH); +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType) +{ + if (InHoudiniAssetPtrs.Num() != 1) + return; + + UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + return; + + + FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(HoudiniAsset, InType, EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY); + /* + // Creating a temporary tool for the selected asset + TSoftObjectPtr HoudiniAssetPtr(HoudiniAsset); + FHoudiniTool HoudiniTool( + HoudiniAssetPtr, + FText::FromString(HoudiniAsset->GetName()), + Type, + EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY, + FText(), + NULL, + FString(), + false, + FFilePath(), + FHoudiniToolDirectory(), + FString()); + + SHoudiniToolPalette::InstantiateHoudiniTool(&HoudiniTool); + */ +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs) +{ + for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) + { + UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + continue; + + FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, FTransform::Identity); + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteInstantiate(TArray> InHoudiniAssetPtrs) +{ + FTransform DefaultTransform = FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform(); + for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) + { + UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + continue; + + FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, DefaultTransform); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h index fe798135a..b0aeb658a 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h @@ -1,84 +1,84 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "AssetTypeActions_Base.h" - -class UClass; -class UObject; -class UHoudiniAsset; - -enum class EHoudiniToolType : uint8; - -class FAssetTypeActions_HoudiniAsset : public FAssetTypeActions_Base -{ - public: - - // FAssetTypeActions_Base methods. - virtual FText GetName() const override; - virtual FColor GetTypeColor() const override; - virtual UClass* GetSupportedClass() const override; - virtual uint32 GetCategories() override; - //virtual UThumbnailInfo * GetThumbnailInfo(UObject * Asset) const override; - virtual bool HasActions(const TArray< UObject * > & InObjects) const override; - virtual void GetActions(const TArray< UObject * > & InObjects, class FMenuBuilder & MenuBuilder) override; - - virtual bool AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) override; - - TSharedRef AddLevelEditorMenuExtenders(TArray> InHoudiniAssetPtrs); - - protected: - - // Handler for reimport option. - void ExecuteReimport(TArray> InHoudiniAssetPtrs); - - // Handler for rebuild all option - void ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs); - - // Handler for find in explorer option - void ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs); - - // Handler for the open in Houdini option - void ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs); - - // Handler to apply the current hda to the current world selection (single input) - void ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs); - - // Handler to apply the current hda to the current world selection (multi input) - void ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs); - - // Handler to batch apply the current hda to the current world selection - void ExecuteApplyBatch(TArray> InHoudiniAssetPtrs ); - - // Handler to instantiate the HDA in the world - void ExecuteInstantiate(TArray> InHoudiniAssetPtrs); - - // Handler to instantiate the HDA in the world, actor is placed at the origin - void ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs); - - void ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "AssetTypeActions_Base.h" + +class UClass; +class UObject; +class UHoudiniAsset; + +enum class EHoudiniToolType : uint8; + +class FAssetTypeActions_HoudiniAsset : public FAssetTypeActions_Base +{ + public: + + // FAssetTypeActions_Base methods. + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + //virtual UThumbnailInfo * GetThumbnailInfo(UObject * Asset) const override; + virtual bool HasActions(const TArray< UObject * > & InObjects) const override; + virtual void GetActions(const TArray< UObject * > & InObjects, class FMenuBuilder & MenuBuilder) override; + + virtual bool AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) override; + + TSharedRef AddLevelEditorMenuExtenders(TArray> InHoudiniAssetPtrs); + + protected: + + // Handler for reimport option. + void ExecuteReimport(TArray> InHoudiniAssetPtrs); + + // Handler for rebuild all option + void ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs); + + // Handler for find in explorer option + void ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs); + + // Handler for the open in Houdini option + void ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs); + + // Handler to apply the current hda to the current world selection (single input) + void ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs); + + // Handler to apply the current hda to the current world selection (multi input) + void ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs); + + // Handler to batch apply the current hda to the current world selection + void ExecuteApplyBatch(TArray> InHoudiniAssetPtrs ); + + // Handler to instantiate the HDA in the world + void ExecuteInstantiate(TArray> InHoudiniAssetPtrs); + + // Handler to instantiate the HDA in the world, actor is placed at the origin + void ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs); + + void ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp index 8f7e86ba7..933ba8bf2 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp @@ -1,117 +1,117 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetActorFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineUtils.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniAssetActorFactory::UHoudiniAssetActorFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - DisplayName = LOCTEXT("HoudiniAssetDisplayName", "Houdini Engine Asset"); - NewActorClass = AHoudiniAssetActor::StaticClass(); -} - -bool -UHoudiniAssetActorFactory::CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) -{ - if (!AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UHoudiniAsset::StaticClass())) - { - OutErrorMsg = NSLOCTEXT("CanCreateActor", "NoHoudiniAsset", "A valid Houdini Engine asset must be specified."); - return false; - } - - return true; -} - -UObject * -UHoudiniAssetActorFactory::GetAssetFromActorInstance(AActor * Instance) -{ - check(Instance->IsA(NewActorClass)); - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(Instance); - - check(HoudiniAssetActor->GetHoudiniAssetComponent()); - return HoudiniAssetActor->GetHoudiniAssetComponent()->HoudiniAsset; -} - -void -UHoudiniAssetActorFactory::PostSpawnActor(UObject * Asset, AActor * NewActor) -{ - HOUDINI_LOG_MESSAGE(TEXT("PostSpawnActor %s, supplied Asset = 0x%0.8p"), *NewActor->GetName(), Asset); - - UHoudiniAsset * HoudiniAsset = CastChecked(Asset); - if (HoudiniAsset) - { - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(NewActor); - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - check(HoudiniAssetComponent); - - //HoudiniAssetComponent->UnregisterComponent(); - //HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - //HoudiniAssetComponent->RegisterComponent(); - - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); - - if (!HoudiniAssetActor->IsUsedForPreview()) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); - } - } -} - -void -UHoudiniAssetActorFactory::PostCreateBlueprint(UObject * Asset, AActor * CDO) -{ - HOUDINI_LOG_MESSAGE(TEXT("PostCreateBlueprint, supplied Asset = 0x%0.8p"), Asset); - - UHoudiniAsset * HoudiniAsset = CastChecked(Asset); - if (HoudiniAsset) - { - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(CDO); - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - check(HoudiniAssetComponent); - - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); - - if (!HoudiniAssetActor->IsUsedForPreview()) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); - } - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetActorFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineUtils.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetActorFactory::UHoudiniAssetActorFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + DisplayName = LOCTEXT("HoudiniAssetDisplayName", "Houdini Engine Asset"); + NewActorClass = AHoudiniAssetActor::StaticClass(); +} + +bool +UHoudiniAssetActorFactory::CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) +{ + if (!AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UHoudiniAsset::StaticClass())) + { + OutErrorMsg = NSLOCTEXT("CanCreateActor", "NoHoudiniAsset", "A valid Houdini Engine asset must be specified."); + return false; + } + + return true; +} + +UObject * +UHoudiniAssetActorFactory::GetAssetFromActorInstance(AActor * Instance) +{ + check(Instance->IsA(NewActorClass)); + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(Instance); + + check(HoudiniAssetActor->GetHoudiniAssetComponent()); + return HoudiniAssetActor->GetHoudiniAssetComponent()->HoudiniAsset; +} + +void +UHoudiniAssetActorFactory::PostSpawnActor(UObject * Asset, AActor * NewActor) +{ + HOUDINI_LOG_MESSAGE(TEXT("PostSpawnActor %s, supplied Asset = 0x%0.8p"), *NewActor->GetName(), Asset); + + UHoudiniAsset * HoudiniAsset = CastChecked(Asset); + if (HoudiniAsset) + { + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(NewActor); + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + check(HoudiniAssetComponent); + + //HoudiniAssetComponent->UnregisterComponent(); + //HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + //HoudiniAssetComponent->RegisterComponent(); + + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); + + if (!HoudiniAssetActor->IsUsedForPreview()) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); + } + } +} + +void +UHoudiniAssetActorFactory::PostCreateBlueprint(UObject * Asset, AActor * CDO) +{ + HOUDINI_LOG_MESSAGE(TEXT("PostCreateBlueprint, supplied Asset = 0x%0.8p"), Asset); + + UHoudiniAsset * HoudiniAsset = CastChecked(Asset); + if (HoudiniAsset) + { + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(CDO); + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + check(HoudiniAssetComponent); + + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); + + if (!HoudiniAssetActor->IsUsedForPreview()) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h index 286b7586a..0ea4eb1be 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h @@ -1,58 +1,58 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ActorFactories/ActorFactory.h" -#include "HoudiniAssetActorFactory.generated.h" - -class FText; -class AActor; -class UObject; -class UHoudiniAssetComponent; - -struct FAssetData; - -UCLASS(config = Editor) -class UHoudiniAssetActorFactory : public UActorFactory -{ - GENERATED_UCLASS_BODY() - -public: - // UActorFactory methods: - // Return true if Actor can be created from a given asset. - virtual bool CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) override; - // Given an instance of an actor pertaining to this factory, find the asset that should be used to create a new actor. - virtual UObject * GetAssetFromActorInstance(AActor * Instance) override; - // Modify the actor after it has been spawned. - virtual void PostSpawnActor(UObject * Asset, AActor * NewActor) override; - // Called after a blueprint is created by this factory to update the blueprint's CDO properties - // with state from the asset for this factory. - virtual void PostCreateBlueprint(UObject * Asset, AActor * CDO) override; - -protected: - bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ActorFactories/ActorFactory.h" +#include "HoudiniAssetActorFactory.generated.h" + +class FText; +class AActor; +class UObject; +class UHoudiniAssetComponent; + +struct FAssetData; + +UCLASS(config = Editor) +class UHoudiniAssetActorFactory : public UActorFactory +{ + GENERATED_UCLASS_BODY() + +public: + // UActorFactory methods: + // Return true if Actor can be created from a given asset. + virtual bool CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) override; + // Given an instance of an actor pertaining to this factory, find the asset that should be used to create a new actor. + virtual UObject * GetAssetFromActorInstance(AActor * Instance) override; + // Modify the actor after it has been spawned. + virtual void PostSpawnActor(UObject * Asset, AActor * NewActor) override; + // Called after a blueprint is created by this factory to update the blueprint's CDO properties + // with state from the asset for this factory. + virtual void PostCreateBlueprint(UObject * Asset, AActor * CDO) override; + +protected: + bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp index 895b17c06..c1c515bdc 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp @@ -1,72 +1,72 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetBroker.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAsset.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniAssetBroker::~FHoudiniAssetBroker() -{ - -} - -UClass * -FHoudiniAssetBroker::GetSupportedAssetClass() -{ - return UHoudiniAsset::StaticClass(); -} - -bool -FHoudiniAssetBroker::AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) -{ - if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) - { - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(InAsset); - if (HoudiniAsset || !InAsset) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - return true; - } - } - - return false; -} - -UObject * -FHoudiniAssetBroker::GetAssetFromComponent(UActorComponent * InComponent) -{ - if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) - { - return HoudiniAssetComponent->GetHoudiniAsset(); - } - - return nullptr; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetBroker.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAsset.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniAssetBroker::~FHoudiniAssetBroker() +{ + +} + +UClass * +FHoudiniAssetBroker::GetSupportedAssetClass() +{ + return UHoudiniAsset::StaticClass(); +} + +bool +FHoudiniAssetBroker::AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) +{ + if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) + { + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(InAsset); + if (HoudiniAsset || !InAsset) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + return true; + } + } + + return false; +} + +UObject * +FHoudiniAssetBroker::GetAssetFromComponent(UActorComponent * InComponent) +{ + if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) + { + return HoudiniAssetComponent->GetHoudiniAsset(); + } + + return nullptr; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h index 3357d841f..9c0183fcd 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h @@ -1,49 +1,49 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ComponentAssetBroker.h" - -class UObject; -class UActorComponent; - -class FHoudiniAssetBroker : public IComponentAssetBroker -{ -public: - - virtual ~FHoudiniAssetBroker(); - - // IComponentAssetBroker methods. - // Reports the asset class this broker knows how to handle. - UClass * GetSupportedAssetClass() override; - - // Assign the assigned asset to the supplied component. - bool AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) override; - - // Get the currently assigned asset from the component. - UObject * GetAssetFromComponent(UActorComponent * InComponent) override; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ComponentAssetBroker.h" + +class UObject; +class UActorComponent; + +class FHoudiniAssetBroker : public IComponentAssetBroker +{ +public: + + virtual ~FHoudiniAssetBroker(); + + // IComponentAssetBroker methods. + // Reports the asset class this broker knows how to handle. + UClass * GetSupportedAssetClass() override; + + // Assign the assigned asset to the supplied component. + bool AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) override; + + // Get the currently assigned asset from the component. + UObject * GetAssetFromComponent(UActorComponent * InComponent) override; +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp index 6499e571c..24c6fb453 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp @@ -1,593 +1,593 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetComponentDetails.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAsset.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniParameter.h" -#include "HoudiniHandleComponent.h" -#include "HoudiniParameterDetails.h" -#include "HoudiniInput.h" -#include "HoudiniInputDetails.h" -#include "HoudiniHandleDetails.h" -#include "HoudiniOutput.h" -#include "HoudiniOutputDetails.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Layout/SSeparator.h" -#include "Widgets/Layout/SBox.h" -#include "Widgets/Images/SImage.h" - -#include "PropertyCustomizationHelpers.h" -#include "DetailLayoutBuilder.h" -#include "DetailCategoryBuilder.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -TSharedRef< IDetailCustomization > -FHoudiniAssetComponentDetails::MakeInstance() -{ - return MakeShareable(new FHoudiniAssetComponentDetails); -} - -FHoudiniAssetComponentDetails::FHoudiniAssetComponentDetails() -{ - OutputDetails = MakeShared(); - ParameterDetails = MakeShared(); - PDGDetails = MakeShared(); - HoudiniEngineDetails = MakeShared(); -} - - -FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() -{ - // The ramp param's curves are added to root to avoid garbage collection - // We need to remove those curves from the root when the details classes are destroyed. - if (ParameterDetails.IsValid()) - { - FHoudiniParameterDetails* ParamDetailsPtr = ParameterDetails.Get(); - - for (auto& CurFloatRampCurveEditor : ParamDetailsPtr->CreatedFloatCurveEditors) - { - if (CurFloatRampCurveEditor.IsValid()) - { - CurFloatRampCurveEditor->HoudiniFloatRampCurve = nullptr; - CurFloatRampCurveEditor->SetCurveOwner(nullptr); - } - } - for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) - { - if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) - continue; - - CurFloatRampCurve->RemoveFromRoot(); - } - - for (auto& CurColorRampCurveEditor : ParamDetailsPtr->CreatedColorGradientEditors) - { - if (CurColorRampCurveEditor.IsValid()) - { - CurColorRampCurveEditor->HoudiniColorRampCurve = nullptr; - CurColorRampCurveEditor->SetCurveOwner(nullptr); - } - } - for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) - { - if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) - continue; - - CurColorRampCurve->RemoveFromRoot(); - } - - ParamDetailsPtr->CreatedFloatCurveEditors.Empty(); - ParamDetailsPtr->CreatedColorGradientEditors.Empty(); - ParamDetailsPtr->CreatedFloatRampCurves.Empty(); - ParamDetailsPtr->CreatedColorRampCurves.Empty(); - } -} - -void -FHoudiniAssetComponentDetails::AddIndieLicenseRow(IDetailCategoryBuilder& InCategory) -{ - FText IndieText = - FText::FromString(TEXT("Houdini Engine Indie - For Limited Commercial Use Only")); - - FSlateFontInfo LargeDetailsFont = IDetailLayoutBuilder::GetDetailFontBold(); - LargeDetailsFont.Size += 2; - - FSlateColor LabelColor = FLinearColor(1.0f, 1.0f, 0.0f, 1.0f); - - InCategory.AddCustomRow(FText::GetEmpty()) - [ - SNew(STextBlock) - .Text(IndieText) - .ToolTipText(IndieText) - .Font(LargeDetailsFont) - .Justification(ETextJustify::Center) - .ColorAndOpacity(LabelColor) - ]; - - InCategory.AddCustomRow(FText::GetEmpty()) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .Padding(0, 0, 5, 0) - [ - SNew(SSeparator) - .Thickness(2.0f) - ] - ]; -} - - -void -FHoudiniAssetComponentDetails::AddSessionStatusRow(IDetailCategoryBuilder& InCategory) -{ - FDetailWidgetRow& PDGStatusRow = InCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetSessionStatusAndColor(StatusString, StatusColor); - return FText::FromString(StatusString); - }) - .ColorAndOpacity_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetSessionStatusAndColor(StatusString, StatusColor); - return FSlateColor(StatusColor); - }) - ] - ]; -} - -bool -FHoudiniAssetComponentDetails::GetSessionStatusAndColor( - FString& OutStatusString, FLinearColor& OutStatusColor) -{ - OutStatusString = FString(); - OutStatusColor = FLinearColor::White; - - const EHoudiniSessionStatus& SessionStatus = FHoudiniEngine::Get().GetSessionStatus(); - - switch (SessionStatus) - { - case EHoudiniSessionStatus::NotStarted: - // Session not initialized yet - OutStatusString = TEXT("Houdini Engine Session - Not Started"); - OutStatusColor = FLinearColor::White; - break; - - case EHoudiniSessionStatus::Connected: - // Session successfully started - OutStatusString = TEXT("Houdini Engine Session READY"); - OutStatusColor = FLinearColor::Green; - break; - case EHoudiniSessionStatus::Stopped: - // Session stopped - OutStatusString = TEXT("Houdini Engine Session STOPPED"); - OutStatusColor = FLinearColor(1.0f, 0.5f, 0.0f); - break; - case EHoudiniSessionStatus::Failed: - // Session failed to be created/connected - OutStatusString = TEXT("Houdini Engine Session FAILED"); - OutStatusColor = FLinearColor::Red; - break; - case EHoudiniSessionStatus::Lost: - // Session Lost (HARS/Houdini Crash?) - OutStatusString = TEXT("Houdini Engine Session LOST"); - OutStatusColor = FLinearColor::Red; - break; - case EHoudiniSessionStatus::NoLicense: - // Failed to acquire a license - OutStatusString = TEXT("Houdini Engine Session FAILED - No License"); - OutStatusColor = FLinearColor::Red; - break; - case EHoudiniSessionStatus::None: - // Session type set to None - OutStatusString = TEXT("Houdini Engine Session DISABLED"); - OutStatusColor = FLinearColor::White; - break; - default: - case EHoudiniSessionStatus::Invalid: - OutStatusString = TEXT("Houdini Engine Session INVALID"); - OutStatusColor = FLinearColor::Red; - break; - } - - // Handle a few specific case for active session - if (SessionStatus == EHoudiniSessionStatus::Connected) - { - bool bPaused = !FHoudiniEngine::Get().IsCookingEnabled(); - bool bSSync = FHoudiniEngine::Get().IsSessionSyncEnabled(); - if (bPaused) - { - OutStatusString = TEXT("Houdini Engine Session PAUSED"); - OutStatusColor = FLinearColor::Yellow; - } - /* - else if (bSSync) - { - OutStatusString = TEXT("Houdini Engine Session Sync READY"); - OutStatusColor = FLinearColor::Blue; - } - */ - } - - return true; -} - -void -FHoudiniAssetComponentDetails::AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC) -{ - FString CategoryName = "Bake"; - InCategory.AddGroup(FName(*CategoryName), FText::FromString(CategoryName), false, false); - -} - -void -FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) -{ - // Get all components which are being customized. - TArray< TWeakObjectPtr< UObject > > ObjectsCustomized; - DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); - - // Extract the Houdini Asset Component to detail - for (int32 i = 0; i < ObjectsCustomized.Num(); ++i) - { - if (ObjectsCustomized[i].IsValid()) - { - UObject * Object = ObjectsCustomized[i].Get(); - if (Object) - { - UHoudiniAssetComponent * HAC = Cast< UHoudiniAssetComponent >(Object); - if (HAC && !HAC->IsPendingKill()) - HoudiniAssetComponents.Add(HAC); - } - } - } - - // Check if we'll need to add indie license labels - bool bIsIndieLicense = FHoudiniEngine::Get().IsLicenseIndie(); - - // To handle multiselection parameter edit, we try to group the selected components by their houdini assets - // TODO? ignore multiselection if all are not the same HDA? - // TODO do the same for inputs - TMap, TArray>> HoudiniAssetToHACs; - for (auto HAC : HoudiniAssetComponents) - { - TWeakObjectPtr HoudiniAsset = HAC->GetHoudiniAsset(); - if (!HoudiniAsset.IsValid()) - continue; - - TArray>& ValueRef = HoudiniAssetToHACs.FindOrAdd(HoudiniAsset); - ValueRef.Add(HAC); - } - - for (auto Iter : HoudiniAssetToHACs) - { - TArray> HACs = Iter.Value; - if (HACs.Num() < 1) - continue; - - TWeakObjectPtr MainComponent = HACs[0]; - if (!MainComponent.IsValid()) - continue; - - // If we have selected more than one component that have different HDAs, - // we'll want to separate the param/input/output category for each HDA - FString MultiSelectionIdentifier = FString(); - if (HoudiniAssetToHACs.Num() > 1) - { - MultiSelectionIdentifier = TEXT("("); - if (MainComponent->GetHoudiniAsset()) - MultiSelectionIdentifier += MainComponent->GetHoudiniAsset()->GetName(); - MultiSelectionIdentifier += TEXT(")"); - } - - /* - // Handled by the UPROPERTIES on the component in v2! - // Edit the Houdini details category - IDetailCategoryBuilder & HoudiniAssetCategory = - DetailBuilder.EditCategory("HoudiniAsset", FText::GetEmpty(), ECategoryPriority::Important); - */ - - // - // 0. HOUDINI ASSET DETAILS - // - - { - FString HoudiniEngineCategoryName = "Houdini Engine"; - HoudiniEngineCategoryName += MultiSelectionIdentifier; - - // Create Houdini Engine details category - IDetailCategoryBuilder & HouEngineCategory = - DetailBuilder.EditCategory(*HoudiniEngineCategoryName, FText::FromString("Houdini Engine"), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouEngineCategory); - - TArray MultiSelectedHACs; - for (auto& NextHACWeakPtr : HACs) - { - if (NextHACWeakPtr.IsValid()) - MultiSelectedHACs.Add(NextHACWeakPtr.Get()); - } - - HoudiniEngineDetails->CreateWidget(HouEngineCategory, MultiSelectedHACs); - } - - // - // 1. PDG ASSET LINK (if available) - // - if (MainComponent->GetPDGAssetLink()) - { - FString PDGCatName = "HoudiniPDGAssetLink"; - PDGCatName += MultiSelectionIdentifier; - - // Create the PDG Asset Link details category - IDetailCategoryBuilder & HouPDGCategory = - DetailBuilder.EditCategory(*PDGCatName, FText::FromString("Houdini - PDG Asset Link"), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouPDGCategory); - - // TODO: Handle multi selection of outputs like params/inputs? - - - PDGDetails->CreateWidget(HouPDGCategory, MainComponent->GetPDGAssetLink()/*, MainComponent*/); - } - - - // - // 2. PARAMETER DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString ParamCatName = "HoudiniParameters"; - ParamCatName += MultiSelectionIdentifier; - - // Create the Parameters details category - IDetailCategoryBuilder & HouParameterCategory = - DetailBuilder.EditCategory(*ParamCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if(bIsIndieLicense) - AddIndieLicenseRow(HouParameterCategory); - - // Iterate through the component's parameters - for (int32 ParamIdx = 0; ParamIdx < MainComponent->GetNumParameters(); ParamIdx++) - { - // We only want to create root parameters here, they will recursively create child parameters. - UHoudiniParameter* CurrentParam = MainComponent->GetParameterAt(ParamIdx); - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - // TODO: remove ? unneeded? - // ensure the parameter is actually owned by a HAC - /*const TWeakObjectPtr Owner = Cast(CurrentParam->GetOuter()); - if (!Owner.IsValid()) - continue;*/ - - // Build an array of edited parameter for multi edit - TArray EditedParams; - EditedParams.Add(CurrentParam); - - // Add the corresponding params in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniParameter* LinkedParam = HACs[LinkedIdx]->GetParameterAt(ParamIdx); - if (!LinkedParam || LinkedParam->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if ( !LinkedParam->Matches(*CurrentParam) ) - { - LinkedParam = MainComponent->FindMatchingParameter(CurrentParam); - if (!LinkedParam || LinkedParam->IsPendingKill() || LinkedParam->IsChildParameter()) - continue; - } - - EditedParams.Add(LinkedParam); - } - - ParameterDetails->CreateWidget(HouParameterCategory, EditedParams); - } - - /*** HOUDINI HANDLE DETAILS ***/ - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString HandleCatName = "HoudiniHandles"; - HandleCatName += MultiSelectionIdentifier; - - // Create the Parameters details category - IDetailCategoryBuilder & HouHandleCategory = - DetailBuilder.EditCategory(*HandleCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouHandleCategory); - - // Iterate through the component's Houdini handles - for (int32 HandleIdx = 0; HandleIdx < MainComponent->GetNumHandles(); ++HandleIdx) - { - UHoudiniHandleComponent* CurrentHandleComponent = MainComponent->GetHandleComponentAt(HandleIdx); - - if (!CurrentHandleComponent || CurrentHandleComponent->IsPendingKill()) - continue; - - TArray EditedHandles; - EditedHandles.Add(CurrentHandleComponent); - - // Add the corresponding params in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); ++LinkedIdx) - { - UHoudiniHandleComponent* LinkedHandle = HACs[LinkedIdx]->GetHandleComponentAt(HandleIdx); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) - continue; - - // Linked handles should match the main param, if not try to find one that matches - if (!LinkedHandle->Matches(*CurrentHandleComponent)) - { - LinkedHandle = MainComponent->FindMatchingHandle(CurrentHandleComponent); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) - continue; - } - - EditedHandles.Add(LinkedHandle); - } - - FHoudiniHandleDetails::CreateWidget(HouHandleCategory, EditedHandles); - } - - - // - // 3. INPUT DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString InputCatName = "HoudiniInputs"; - InputCatName += MultiSelectionIdentifier; - - // Create the input details category - IDetailCategoryBuilder & HouInputCategory = - DetailBuilder.EditCategory(*InputCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouInputCategory); - - // Iterate through the component's inputs - for (int32 InputIdx = 0; InputIdx < MainComponent->GetNumInputs(); InputIdx++) - { - UHoudiniInput* CurrentInput = MainComponent->GetInputAt(InputIdx); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (!MainComponent->IsInputTypeSupported(CurrentInput->GetInputType())) - continue; - - // Object path parameter inputs are displayed by the ParameterDetails - skip them - if (CurrentInput->IsObjectPathParameter()) - continue; - - // Build an array of edited inputs for multi edit - TArray EditedInputs; - EditedInputs.Add(CurrentInput); - - // Add the corresponding inputs in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniInput* LinkedInput = HACs[LinkedIdx]->GetInputAt(InputIdx); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if (!LinkedInput->Matches(*CurrentInput)) - { - LinkedInput = MainComponent->FindMatchingInput(CurrentInput); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - } - - EditedInputs.Add(LinkedInput); - } - - FHoudiniInputDetails::CreateWidget(HouInputCategory, EditedInputs); - } - - // - // 4. OUTPUT DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString OutputCatName = "HoudiniOutputs"; - OutputCatName += MultiSelectionIdentifier; - - // Create the output details category - IDetailCategoryBuilder & HouOutputCategory = - DetailBuilder.EditCategory(*OutputCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // Iterate through the component's outputs - for (int32 OutputIdx = 0; OutputIdx < MainComponent->GetNumOutputs(); OutputIdx++) - { - UHoudiniOutput* CurrentOutput = MainComponent->GetOutputAt(OutputIdx); - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - // Build an array of edited inpoutputs for multi edit - TArray EditedOutputs; - EditedOutputs.Add(CurrentOutput); - - // Add the corresponding outputs in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniOutput* LinkedOutput = HACs[LinkedIdx]->GetOutputAt(OutputIdx); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) - continue; - - /* - // Linked output should match the main output! If not try to find one that matches - if (!LinkedOutput->Matches(*CurrentOutput)) - { - LinkedOutput = MainComponent->FindMatchingInput(CurrentOutput); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) - continue; - } - */ - - EditedOutputs.Add(LinkedOutput); - } - - // TODO: Handle multi selection of outputs like params/inputs? - OutputDetails->CreateWidget(HouOutputCategory, EditedOutputs); - } - } -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetComponentDetails.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAsset.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniParameter.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniParameterDetails.h" +#include "HoudiniInput.h" +#include "HoudiniInputDetails.h" +#include "HoudiniHandleDetails.h" +#include "HoudiniOutput.h" +#include "HoudiniOutputDetails.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Images/SImage.h" + +#include "PropertyCustomizationHelpers.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +TSharedRef< IDetailCustomization > +FHoudiniAssetComponentDetails::MakeInstance() +{ + return MakeShareable(new FHoudiniAssetComponentDetails); +} + +FHoudiniAssetComponentDetails::FHoudiniAssetComponentDetails() +{ + OutputDetails = MakeShared(); + ParameterDetails = MakeShared(); + PDGDetails = MakeShared(); + HoudiniEngineDetails = MakeShared(); +} + + +FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() +{ + // The ramp param's curves are added to root to avoid garbage collection + // We need to remove those curves from the root when the details classes are destroyed. + if (ParameterDetails.IsValid()) + { + FHoudiniParameterDetails* ParamDetailsPtr = ParameterDetails.Get(); + + for (auto& CurFloatRampCurveEditor : ParamDetailsPtr->CreatedFloatCurveEditors) + { + if (CurFloatRampCurveEditor.IsValid()) + { + CurFloatRampCurveEditor->HoudiniFloatRampCurve = nullptr; + CurFloatRampCurveEditor->SetCurveOwner(nullptr); + } + } + for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) + { + if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) + continue; + + CurFloatRampCurve->RemoveFromRoot(); + } + + for (auto& CurColorRampCurveEditor : ParamDetailsPtr->CreatedColorGradientEditors) + { + if (CurColorRampCurveEditor.IsValid()) + { + CurColorRampCurveEditor->HoudiniColorRampCurve = nullptr; + CurColorRampCurveEditor->SetCurveOwner(nullptr); + } + } + for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) + { + if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) + continue; + + CurColorRampCurve->RemoveFromRoot(); + } + + ParamDetailsPtr->CreatedFloatCurveEditors.Empty(); + ParamDetailsPtr->CreatedColorGradientEditors.Empty(); + ParamDetailsPtr->CreatedFloatRampCurves.Empty(); + ParamDetailsPtr->CreatedColorRampCurves.Empty(); + } +} + +void +FHoudiniAssetComponentDetails::AddIndieLicenseRow(IDetailCategoryBuilder& InCategory) +{ + FText IndieText = + FText::FromString(TEXT("Houdini Engine Indie - For Limited Commercial Use Only")); + + FSlateFontInfo LargeDetailsFont = IDetailLayoutBuilder::GetDetailFontBold(); + LargeDetailsFont.Size += 2; + + FSlateColor LabelColor = FLinearColor(1.0f, 1.0f, 0.0f, 1.0f); + + InCategory.AddCustomRow(FText::GetEmpty()) + [ + SNew(STextBlock) + .Text(IndieText) + .ToolTipText(IndieText) + .Font(LargeDetailsFont) + .Justification(ETextJustify::Center) + .ColorAndOpacity(LabelColor) + ]; + + InCategory.AddCustomRow(FText::GetEmpty()) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(0, 0, 5, 0) + [ + SNew(SSeparator) + .Thickness(2.0f) + ] + ]; +} + + +void +FHoudiniAssetComponentDetails::AddSessionStatusRow(IDetailCategoryBuilder& InCategory) +{ + FDetailWidgetRow& PDGStatusRow = InCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetSessionStatusAndColor(StatusString, StatusColor); + return FText::FromString(StatusString); + }) + .ColorAndOpacity_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetSessionStatusAndColor(StatusString, StatusColor); + return FSlateColor(StatusColor); + }) + ] + ]; +} + +bool +FHoudiniAssetComponentDetails::GetSessionStatusAndColor( + FString& OutStatusString, FLinearColor& OutStatusColor) +{ + OutStatusString = FString(); + OutStatusColor = FLinearColor::White; + + const EHoudiniSessionStatus& SessionStatus = FHoudiniEngine::Get().GetSessionStatus(); + + switch (SessionStatus) + { + case EHoudiniSessionStatus::NotStarted: + // Session not initialized yet + OutStatusString = TEXT("Houdini Engine Session - Not Started"); + OutStatusColor = FLinearColor::White; + break; + + case EHoudiniSessionStatus::Connected: + // Session successfully started + OutStatusString = TEXT("Houdini Engine Session READY"); + OutStatusColor = FLinearColor::Green; + break; + case EHoudiniSessionStatus::Stopped: + // Session stopped + OutStatusString = TEXT("Houdini Engine Session STOPPED"); + OutStatusColor = FLinearColor(1.0f, 0.5f, 0.0f); + break; + case EHoudiniSessionStatus::Failed: + // Session failed to be created/connected + OutStatusString = TEXT("Houdini Engine Session FAILED"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::Lost: + // Session Lost (HARS/Houdini Crash?) + OutStatusString = TEXT("Houdini Engine Session LOST"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::NoLicense: + // Failed to acquire a license + OutStatusString = TEXT("Houdini Engine Session FAILED - No License"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::None: + // Session type set to None + OutStatusString = TEXT("Houdini Engine Session DISABLED"); + OutStatusColor = FLinearColor::White; + break; + default: + case EHoudiniSessionStatus::Invalid: + OutStatusString = TEXT("Houdini Engine Session INVALID"); + OutStatusColor = FLinearColor::Red; + break; + } + + // Handle a few specific case for active session + if (SessionStatus == EHoudiniSessionStatus::Connected) + { + bool bPaused = !FHoudiniEngine::Get().IsCookingEnabled(); + bool bSSync = FHoudiniEngine::Get().IsSessionSyncEnabled(); + if (bPaused) + { + OutStatusString = TEXT("Houdini Engine Session PAUSED"); + OutStatusColor = FLinearColor::Yellow; + } + /* + else if (bSSync) + { + OutStatusString = TEXT("Houdini Engine Session Sync READY"); + OutStatusColor = FLinearColor::Blue; + } + */ + } + + return true; +} + +void +FHoudiniAssetComponentDetails::AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC) +{ + FString CategoryName = "Bake"; + InCategory.AddGroup(FName(*CategoryName), FText::FromString(CategoryName), false, false); + +} + +void +FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + // Get all components which are being customized. + TArray< TWeakObjectPtr< UObject > > ObjectsCustomized; + DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); + + // Extract the Houdini Asset Component to detail + for (int32 i = 0; i < ObjectsCustomized.Num(); ++i) + { + if (ObjectsCustomized[i].IsValid()) + { + UObject * Object = ObjectsCustomized[i].Get(); + if (Object) + { + UHoudiniAssetComponent * HAC = Cast< UHoudiniAssetComponent >(Object); + if (HAC && !HAC->IsPendingKill()) + HoudiniAssetComponents.Add(HAC); + } + } + } + + // Check if we'll need to add indie license labels + bool bIsIndieLicense = FHoudiniEngine::Get().IsLicenseIndie(); + + // To handle multiselection parameter edit, we try to group the selected components by their houdini assets + // TODO? ignore multiselection if all are not the same HDA? + // TODO do the same for inputs + TMap, TArray>> HoudiniAssetToHACs; + for (auto HAC : HoudiniAssetComponents) + { + TWeakObjectPtr HoudiniAsset = HAC->GetHoudiniAsset(); + if (!HoudiniAsset.IsValid()) + continue; + + TArray>& ValueRef = HoudiniAssetToHACs.FindOrAdd(HoudiniAsset); + ValueRef.Add(HAC); + } + + for (auto Iter : HoudiniAssetToHACs) + { + TArray> HACs = Iter.Value; + if (HACs.Num() < 1) + continue; + + TWeakObjectPtr MainComponent = HACs[0]; + if (!MainComponent.IsValid()) + continue; + + // If we have selected more than one component that have different HDAs, + // we'll want to separate the param/input/output category for each HDA + FString MultiSelectionIdentifier = FString(); + if (HoudiniAssetToHACs.Num() > 1) + { + MultiSelectionIdentifier = TEXT("("); + if (MainComponent->GetHoudiniAsset()) + MultiSelectionIdentifier += MainComponent->GetHoudiniAsset()->GetName(); + MultiSelectionIdentifier += TEXT(")"); + } + + /* + // Handled by the UPROPERTIES on the component in v2! + // Edit the Houdini details category + IDetailCategoryBuilder & HoudiniAssetCategory = + DetailBuilder.EditCategory("HoudiniAsset", FText::GetEmpty(), ECategoryPriority::Important); + */ + + // + // 0. HOUDINI ASSET DETAILS + // + + { + FString HoudiniEngineCategoryName = "Houdini Engine"; + HoudiniEngineCategoryName += MultiSelectionIdentifier; + + // Create Houdini Engine details category + IDetailCategoryBuilder & HouEngineCategory = + DetailBuilder.EditCategory(*HoudiniEngineCategoryName, FText::FromString("Houdini Engine"), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouEngineCategory); + + TArray MultiSelectedHACs; + for (auto& NextHACWeakPtr : HACs) + { + if (NextHACWeakPtr.IsValid()) + MultiSelectedHACs.Add(NextHACWeakPtr.Get()); + } + + HoudiniEngineDetails->CreateWidget(HouEngineCategory, MultiSelectedHACs); + } + + // + // 1. PDG ASSET LINK (if available) + // + if (MainComponent->GetPDGAssetLink()) + { + FString PDGCatName = "HoudiniPDGAssetLink"; + PDGCatName += MultiSelectionIdentifier; + + // Create the PDG Asset Link details category + IDetailCategoryBuilder & HouPDGCategory = + DetailBuilder.EditCategory(*PDGCatName, FText::FromString("Houdini - PDG Asset Link"), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouPDGCategory); + + // TODO: Handle multi selection of outputs like params/inputs? + + + PDGDetails->CreateWidget(HouPDGCategory, MainComponent->GetPDGAssetLink()/*, MainComponent*/); + } + + + // + // 2. PARAMETER DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString ParamCatName = "HoudiniParameters"; + ParamCatName += MultiSelectionIdentifier; + + // Create the Parameters details category + IDetailCategoryBuilder & HouParameterCategory = + DetailBuilder.EditCategory(*ParamCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if(bIsIndieLicense) + AddIndieLicenseRow(HouParameterCategory); + + // Iterate through the component's parameters + for (int32 ParamIdx = 0; ParamIdx < MainComponent->GetNumParameters(); ParamIdx++) + { + // We only want to create root parameters here, they will recursively create child parameters. + UHoudiniParameter* CurrentParam = MainComponent->GetParameterAt(ParamIdx); + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + // TODO: remove ? unneeded? + // ensure the parameter is actually owned by a HAC + /*const TWeakObjectPtr Owner = Cast(CurrentParam->GetOuter()); + if (!Owner.IsValid()) + continue;*/ + + // Build an array of edited parameter for multi edit + TArray EditedParams; + EditedParams.Add(CurrentParam); + + // Add the corresponding params in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniParameter* LinkedParam = HACs[LinkedIdx]->GetParameterAt(ParamIdx); + if (!LinkedParam || LinkedParam->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if ( !LinkedParam->Matches(*CurrentParam) ) + { + LinkedParam = MainComponent->FindMatchingParameter(CurrentParam); + if (!LinkedParam || LinkedParam->IsPendingKill() || LinkedParam->IsChildParameter()) + continue; + } + + EditedParams.Add(LinkedParam); + } + + ParameterDetails->CreateWidget(HouParameterCategory, EditedParams); + } + + /*** HOUDINI HANDLE DETAILS ***/ + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString HandleCatName = "HoudiniHandles"; + HandleCatName += MultiSelectionIdentifier; + + // Create the Parameters details category + IDetailCategoryBuilder & HouHandleCategory = + DetailBuilder.EditCategory(*HandleCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouHandleCategory); + + // Iterate through the component's Houdini handles + for (int32 HandleIdx = 0; HandleIdx < MainComponent->GetNumHandles(); ++HandleIdx) + { + UHoudiniHandleComponent* CurrentHandleComponent = MainComponent->GetHandleComponentAt(HandleIdx); + + if (!CurrentHandleComponent || CurrentHandleComponent->IsPendingKill()) + continue; + + TArray EditedHandles; + EditedHandles.Add(CurrentHandleComponent); + + // Add the corresponding params in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); ++LinkedIdx) + { + UHoudiniHandleComponent* LinkedHandle = HACs[LinkedIdx]->GetHandleComponentAt(HandleIdx); + if (!LinkedHandle || LinkedHandle->IsPendingKill()) + continue; + + // Linked handles should match the main param, if not try to find one that matches + if (!LinkedHandle->Matches(*CurrentHandleComponent)) + { + LinkedHandle = MainComponent->FindMatchingHandle(CurrentHandleComponent); + if (!LinkedHandle || LinkedHandle->IsPendingKill()) + continue; + } + + EditedHandles.Add(LinkedHandle); + } + + FHoudiniHandleDetails::CreateWidget(HouHandleCategory, EditedHandles); + } + + + // + // 3. INPUT DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString InputCatName = "HoudiniInputs"; + InputCatName += MultiSelectionIdentifier; + + // Create the input details category + IDetailCategoryBuilder & HouInputCategory = + DetailBuilder.EditCategory(*InputCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouInputCategory); + + // Iterate through the component's inputs + for (int32 InputIdx = 0; InputIdx < MainComponent->GetNumInputs(); InputIdx++) + { + UHoudiniInput* CurrentInput = MainComponent->GetInputAt(InputIdx); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (!MainComponent->IsInputTypeSupported(CurrentInput->GetInputType())) + continue; + + // Object path parameter inputs are displayed by the ParameterDetails - skip them + if (CurrentInput->IsObjectPathParameter()) + continue; + + // Build an array of edited inputs for multi edit + TArray EditedInputs; + EditedInputs.Add(CurrentInput); + + // Add the corresponding inputs in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniInput* LinkedInput = HACs[LinkedIdx]->GetInputAt(InputIdx); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if (!LinkedInput->Matches(*CurrentInput)) + { + LinkedInput = MainComponent->FindMatchingInput(CurrentInput); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + } + + EditedInputs.Add(LinkedInput); + } + + FHoudiniInputDetails::CreateWidget(HouInputCategory, EditedInputs); + } + + // + // 4. OUTPUT DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString OutputCatName = "HoudiniOutputs"; + OutputCatName += MultiSelectionIdentifier; + + // Create the output details category + IDetailCategoryBuilder & HouOutputCategory = + DetailBuilder.EditCategory(*OutputCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // Iterate through the component's outputs + for (int32 OutputIdx = 0; OutputIdx < MainComponent->GetNumOutputs(); OutputIdx++) + { + UHoudiniOutput* CurrentOutput = MainComponent->GetOutputAt(OutputIdx); + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + // Build an array of edited inpoutputs for multi edit + TArray EditedOutputs; + EditedOutputs.Add(CurrentOutput); + + // Add the corresponding outputs in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniOutput* LinkedOutput = HACs[LinkedIdx]->GetOutputAt(OutputIdx); + if (!LinkedOutput || LinkedOutput->IsPendingKill()) + continue; + + /* + // Linked output should match the main output! If not try to find one that matches + if (!LinkedOutput->Matches(*CurrentOutput)) + { + LinkedOutput = MainComponent->FindMatchingInput(CurrentOutput); + if (!LinkedOutput || LinkedOutput->IsPendingKill()) + continue; + } + */ + + EditedOutputs.Add(LinkedOutput); + } + + // TODO: Handle multi selection of outputs like params/inputs? + OutputDetails->CreateWidget(HouOutputCategory, EditedOutputs); + } + } +} + + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h index f640fb21b..cfcc9980c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h @@ -1,91 +1,91 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "IDetailCustomization.h" -#include "HoudiniPDGDetails.h" -#include "HoudiniOutputDetails.h" -#include "HoudiniParameterDetails.h" -#include "HoudiniEngineDetails.h" - -class UHoudiniAssetComponent; -class UStaticMesh; - -class FHoudiniAssetComponentDetails : public IDetailCustomization -{ -public: - - // Constructor. - FHoudiniAssetComponentDetails(); - - // Destructor. - virtual ~FHoudiniAssetComponentDetails(); - - // IDetailCustomization methods. - virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; - - // Create an instance of this detail layout class. - static TSharedRef MakeInstance(); - - // Adds a text row that indicate the status of the Houdini Session - static void AddSessionStatusRow(IDetailCategoryBuilder& InCategory); - - static bool GetSessionStatusAndColor(FString& OutStatusString, FLinearColor& OutStatusColor); - -private: - - // Adds a text row indicate we're using a Houdini indie license - void AddIndieLicenseRow(IDetailCategoryBuilder& InCategory); - - // Adds a category for baking options - void AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC); - - // Handler for double clicking the static mesh thumbnail, opens the editor. - FReply OnThumbnailDoubleClick( - const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent, UObject* Object); - - -private: - - // Components which are being customized. - TArray> HoudiniAssetComponents; - - // Structure holding the output's details - TSharedPtr OutputDetails; - - // Structure holding the parameter's details - TSharedPtr ParameterDetails; - - // Structure holding the PDG Asset Link's details - TSharedPtr PDGDetails; - - // Structure holding the HoudiniAsset details - TSharedPtr HoudiniEngineDetails; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" +#include "HoudiniPDGDetails.h" +#include "HoudiniOutputDetails.h" +#include "HoudiniParameterDetails.h" +#include "HoudiniEngineDetails.h" + +class UHoudiniAssetComponent; +class UStaticMesh; + +class FHoudiniAssetComponentDetails : public IDetailCustomization +{ +public: + + // Constructor. + FHoudiniAssetComponentDetails(); + + // Destructor. + virtual ~FHoudiniAssetComponentDetails(); + + // IDetailCustomization methods. + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + + // Create an instance of this detail layout class. + static TSharedRef MakeInstance(); + + // Adds a text row that indicate the status of the Houdini Session + static void AddSessionStatusRow(IDetailCategoryBuilder& InCategory); + + static bool GetSessionStatusAndColor(FString& OutStatusString, FLinearColor& OutStatusColor); + +private: + + // Adds a text row indicate we're using a Houdini indie license + void AddIndieLicenseRow(IDetailCategoryBuilder& InCategory); + + // Adds a category for baking options + void AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC); + + // Handler for double clicking the static mesh thumbnail, opens the editor. + FReply OnThumbnailDoubleClick( + const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent, UObject* Object); + + +private: + + // Components which are being customized. + TArray> HoudiniAssetComponents; + + // Structure holding the output's details + TSharedPtr OutputDetails; + + // Structure holding the parameter's details + TSharedPtr ParameterDetails; + + // Structure holding the PDG Asset Link's details + TSharedPtr PDGDetails; + + // Structure holding the HoudiniAsset details + TSharedPtr HoudiniEngineDetails; + + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp index 4b1961337..6b0ad57a0 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp @@ -1,209 +1,209 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniAsset.h" - -#include "EditorFramework/AssetImportData.h" -#include "Misc/FileHelper.h" -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniAssetFactory::UHoudiniAssetFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - // This factory is responsible for manufacturing HoudiniEngine assets. - SupportedClass = UHoudiniAsset::StaticClass(); - - // This factory does not manufacture new objects from scratch. - bCreateNew = false; - - // This factory will not open the editor for each new object. - bEditAfterNew = false; - - // This factory will import objects from files. - bEditorImport = true; - - // Factory does not import objects from text. - bText = false; - - // Add supported formats. - Formats.Add(TEXT("otl;Houdini Engine Asset")); - Formats.Add(TEXT("otllc;Houdini Engine Limited Commercial Asset")); - Formats.Add(TEXT("otlnc;Houdini Engine Non-Commercial Asset")); - Formats.Add(TEXT("hda;Houdini Engine Asset")); - Formats.Add(TEXT("hdalc;Houdini Engine Limited Commercial Asset")); - Formats.Add(TEXT("hdanc;Houdini Engine Non-Commercial Asset")); - Formats.Add(TEXT("hdalibrary;Houdini Engine Expanded Asset")); -} - -bool -UHoudiniAssetFactory::DoesSupportClass(UClass * Class) -{ - return Class == SupportedClass; -} - -FText -UHoudiniAssetFactory::GetDisplayName() const -{ - return LOCTEXT("HoudiniAssetFactoryDescription", "Houdini Engine Asset"); -} - -UObject * -UHoudiniAssetFactory::FactoryCreateBinary( - UClass * InClass, UObject* InParent, FName InName, EObjectFlags Flags, - UObject * Context, const TCHAR * Type, const uint8 *& Buffer, - const uint8 * BufferEnd, FFeedbackContext * Warn ) -{ - // Broadcast notification that a new asset is being imported. - GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type); - - // Create a new asset. - UHoudiniAsset * HoudiniAsset = NewObject< UHoudiniAsset >(InParent, InName, Flags); - HoudiniAsset->CreateAsset(Buffer, BufferEnd, UFactory::GetCurrentFilename()); - - // Create reimport information. - UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(HoudiniAsset, UAssetImportData::StaticClass()); - HoudiniAsset->AssetImportData = AssetImportData; - } - - AssetImportData->Update(UFactory::GetCurrentFilename()); - - // Broadcast notification that the new asset has been imported. - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, HoudiniAsset); - - return HoudiniAsset; -} - -UObject* -UHoudiniAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) -{ - // "houdini.hdalibrary" files (expanded hda / hda folder) need a special treatment, - // but ".hda" files can be loaded normally - FString FileExtension = FPaths::GetExtension(Filename); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) != 0) - return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); - - // Make sure the file name is sections.list - FString NameOfFile = FPaths::GetBaseFilename(Filename); - if (NameOfFile.Compare(TEXT("houdini"), ESearchCase::IgnoreCase) != 0) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); - return nullptr; - } - - // Make sure that the proper .list file is loaded - FString PathToFile = FPaths::GetPath(Filename); - if (PathToFile.Find(TEXT(".hda")) != (PathToFile.Len() - 4)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); - return nullptr; - } - - FString NewFilename = PathToFile; - FString NewFileNameNoHDA = FPaths::GetBaseFilename(PathToFile); - FName NewIname = FName(*NewFileNameNoHDA); - FString NewFileExtension = FPaths::GetExtension(NewFilename); - - // load as binary - TArray Data; - if (!FFileHelper::LoadFileToArray(Data, *Filename)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s' to array"), *Filename); - return nullptr; - } - - Data.Add(0); - ParseParms(Parms); - const uint8* Ptr = &Data[0]; - - return FactoryCreateBinary(InClass, InParent, NewIname, Flags, nullptr, *NewFileExtension, Ptr, Ptr + Data.Num() - 1, Warn); -} - -bool -UHoudiniAssetFactory::CanReimport(UObject * Obj, TArray< FString > & OutFilenames) -{ - UHoudiniAsset * HoudiniAsset = Cast(Obj); - if (HoudiniAsset) - { - UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; - if (AssetImportData) - OutFilenames.Add(AssetImportData->GetFirstFilename()); - else - OutFilenames.Add(TEXT("")); - - return true; - } - - return false; -} - -void -UHoudiniAssetFactory::SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) -{ - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); - if (HoudiniAsset && (1 == NewReimportPaths.Num())) - HoudiniAsset->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); -} - -EReimportResult::Type -UHoudiniAssetFactory::Reimport(UObject * Obj) -{ - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); - if (HoudiniAsset && HoudiniAsset->AssetImportData) - { - // Make sure file is valid and exists. - const FString & Filename = HoudiniAsset->AssetImportData->GetFirstFilename(); - - if (!Filename.Len() || IFileManager::Get().FileSize(*Filename) == INDEX_NONE) - return EReimportResult::Failed; - - if (UFactory::StaticImportObject( - HoudiniAsset->GetClass(), HoudiniAsset->GetOuter(), *HoudiniAsset->GetName(), - RF_Public | RF_Standalone, *Filename, NULL, this)) - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimported successfully.")); - - if (HoudiniAsset->GetOuter()) - HoudiniAsset->GetOuter()->MarkPackageDirty(); - else - HoudiniAsset->MarkPackageDirty(); - - return EReimportResult::Succeeded; - } - } - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); - return EReimportResult::Failed; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniAsset.h" + +#include "EditorFramework/AssetImportData.h" +#include "Misc/FileHelper.h" +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetFactory::UHoudiniAssetFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + // This factory is responsible for manufacturing HoudiniEngine assets. + SupportedClass = UHoudiniAsset::StaticClass(); + + // This factory does not manufacture new objects from scratch. + bCreateNew = false; + + // This factory will not open the editor for each new object. + bEditAfterNew = false; + + // This factory will import objects from files. + bEditorImport = true; + + // Factory does not import objects from text. + bText = false; + + // Add supported formats. + Formats.Add(TEXT("otl;Houdini Engine Asset")); + Formats.Add(TEXT("otllc;Houdini Engine Limited Commercial Asset")); + Formats.Add(TEXT("otlnc;Houdini Engine Non-Commercial Asset")); + Formats.Add(TEXT("hda;Houdini Engine Asset")); + Formats.Add(TEXT("hdalc;Houdini Engine Limited Commercial Asset")); + Formats.Add(TEXT("hdanc;Houdini Engine Non-Commercial Asset")); + Formats.Add(TEXT("hdalibrary;Houdini Engine Expanded Asset")); +} + +bool +UHoudiniAssetFactory::DoesSupportClass(UClass * Class) +{ + return Class == SupportedClass; +} + +FText +UHoudiniAssetFactory::GetDisplayName() const +{ + return LOCTEXT("HoudiniAssetFactoryDescription", "Houdini Engine Asset"); +} + +UObject * +UHoudiniAssetFactory::FactoryCreateBinary( + UClass * InClass, UObject* InParent, FName InName, EObjectFlags Flags, + UObject * Context, const TCHAR * Type, const uint8 *& Buffer, + const uint8 * BufferEnd, FFeedbackContext * Warn ) +{ + // Broadcast notification that a new asset is being imported. + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type); + + // Create a new asset. + UHoudiniAsset * HoudiniAsset = NewObject< UHoudiniAsset >(InParent, InName, Flags); + HoudiniAsset->CreateAsset(Buffer, BufferEnd, UFactory::GetCurrentFilename()); + + // Create reimport information. + UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(HoudiniAsset, UAssetImportData::StaticClass()); + HoudiniAsset->AssetImportData = AssetImportData; + } + + AssetImportData->Update(UFactory::GetCurrentFilename()); + + // Broadcast notification that the new asset has been imported. + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, HoudiniAsset); + + return HoudiniAsset; +} + +UObject* +UHoudiniAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) +{ + // "houdini.hdalibrary" files (expanded hda / hda folder) need a special treatment, + // but ".hda" files can be loaded normally + FString FileExtension = FPaths::GetExtension(Filename); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) != 0) + return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); + + // Make sure the file name is sections.list + FString NameOfFile = FPaths::GetBaseFilename(Filename); + if (NameOfFile.Compare(TEXT("houdini"), ESearchCase::IgnoreCase) != 0) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); + return nullptr; + } + + // Make sure that the proper .list file is loaded + FString PathToFile = FPaths::GetPath(Filename); + if (PathToFile.Find(TEXT(".hda")) != (PathToFile.Len() - 4)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); + return nullptr; + } + + FString NewFilename = PathToFile; + FString NewFileNameNoHDA = FPaths::GetBaseFilename(PathToFile); + FName NewIname = FName(*NewFileNameNoHDA); + FString NewFileExtension = FPaths::GetExtension(NewFilename); + + // load as binary + TArray Data; + if (!FFileHelper::LoadFileToArray(Data, *Filename)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s' to array"), *Filename); + return nullptr; + } + + Data.Add(0); + ParseParms(Parms); + const uint8* Ptr = &Data[0]; + + return FactoryCreateBinary(InClass, InParent, NewIname, Flags, nullptr, *NewFileExtension, Ptr, Ptr + Data.Num() - 1, Warn); +} + +bool +UHoudiniAssetFactory::CanReimport(UObject * Obj, TArray< FString > & OutFilenames) +{ + UHoudiniAsset * HoudiniAsset = Cast(Obj); + if (HoudiniAsset) + { + UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; + if (AssetImportData) + OutFilenames.Add(AssetImportData->GetFirstFilename()); + else + OutFilenames.Add(TEXT("")); + + return true; + } + + return false; +} + +void +UHoudiniAssetFactory::SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); + if (HoudiniAsset && (1 == NewReimportPaths.Num())) + HoudiniAsset->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); +} + +EReimportResult::Type +UHoudiniAssetFactory::Reimport(UObject * Obj) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); + if (HoudiniAsset && HoudiniAsset->AssetImportData) + { + // Make sure file is valid and exists. + const FString & Filename = HoudiniAsset->AssetImportData->GetFirstFilename(); + + if (!Filename.Len() || IFileManager::Get().FileSize(*Filename) == INDEX_NONE) + return EReimportResult::Failed; + + if (UFactory::StaticImportObject( + HoudiniAsset->GetClass(), HoudiniAsset->GetOuter(), *HoudiniAsset->GetName(), + RF_Public | RF_Standalone, *Filename, NULL, this)) + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimported successfully.")); + + if (HoudiniAsset->GetOuter()) + HoudiniAsset->GetOuter()->MarkPackageDirty(); + else + HoudiniAsset->MarkPackageDirty(); + + return EReimportResult::Succeeded; + } + } + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); + return EReimportResult::Failed; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h index c0c1cf333..d07ff8061 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "EditorReimportHandler.h" -#include "Factories/Factory.h" -#include "HoudiniAssetFactory.generated.h" - -class UClass; -class UObject; -class FFeedbackContext; - -UCLASS(config = Editor) -class UHoudiniAssetFactory : public UFactory, public FReimportHandler -{ - GENERATED_UCLASS_BODY() - - public: - - // UFactory methods. - // return true if it supports this class - virtual bool DoesSupportClass(UClass * Class) override; - - // Returns the name of the factory for menus - virtual FText GetDisplayName() const override; - - // Create a new object by importing it from a binary buffer. - virtual UObject * FactoryCreateBinary( - UClass * InClass, UObject * InParent, FName InName, EObjectFlags Flags, - UObject * Context, const TCHAR * Type, const uint8 *& Buffer, const uint8 * BufferEnd, - FFeedbackContext * Warn) override; - - // Create a new object by importing it from a file name. - virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, - EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, - FFeedbackContext* Warn, bool& bOutOperationCanceled) override; - - // FReimportHandler methods. - // Check to see if we have a handler to manage the reimporting of the object - virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; - - // Sets the reimport path(s) for the specified object - virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; - - // Attempt to reimport the specified object from its source - virtual EReimportResult::Type Reimport(UObject * Obj) override; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "EditorReimportHandler.h" +#include "Factories/Factory.h" +#include "HoudiniAssetFactory.generated.h" + +class UClass; +class UObject; +class FFeedbackContext; + +UCLASS(config = Editor) +class UHoudiniAssetFactory : public UFactory, public FReimportHandler +{ + GENERATED_UCLASS_BODY() + + public: + + // UFactory methods. + // return true if it supports this class + virtual bool DoesSupportClass(UClass * Class) override; + + // Returns the name of the factory for menus + virtual FText GetDisplayName() const override; + + // Create a new object by importing it from a binary buffer. + virtual UObject * FactoryCreateBinary( + UClass * InClass, UObject * InParent, FName InName, EObjectFlags Flags, + UObject * Context, const TCHAR * Type, const uint8 *& Buffer, const uint8 * BufferEnd, + FFeedbackContext * Warn) override; + + // Create a new object by importing it from a file name. + virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, + EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, + FFeedbackContext* Warn, bool& bOutOperationCanceled) override; + + // FReimportHandler methods. + // Check to see if we have a handler to manage the reimporting of the object + virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; + + // Sets the reimport path(s) for the specified object + virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; + + // Attempt to reimport the specified object from its source + virtual EReimportResult::Type Reimport(UObject * Obj) override; +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp index 269ff3fc0..149911518 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp @@ -1,5591 +1,5647 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineBakeUtils.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngineUtils.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutput.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineUtils.h" -#include "UnrealLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniStringResolver.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniEngineRuntimeUtils.h" - -#include "Engine/StaticMesh.h" -#include "Engine/World.h" -#include "RawMesh.h" -#include "UObject/Package.h" -#include "PackageTools.h" -#include "UObject/MetaData.h" -#include "AssetRegistryModule.h" -#include "Materials/Material.h" -#include "LandscapeProxy.h" -#include "LandscapeStreamingProxy.h" -#include "LandscapeInfo.h" -#include "Factories/WorldFactory.h" -#include "AssetToolsModule.h" -#include "InstancedFoliageActor.h" -#include "Components/SplineComponent.h" -#include "GameFramework/Actor.h" -#include "Engine/StaticMeshActor.h" -#include "Components/StaticMeshComponent.h" -#include "PhysicsEngine/BodySetup.h" -#include "ActorFactories/ActorFactoryStaticMesh.h" -#include "ActorFactories/ActorFactoryEmptyActor.h" -#include "BusyCursor.h" -#include "Editor.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "FileHelpers.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngine.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "Editor/EditorEngine.h" -#include "Factories/BlueprintFactory.h" -#include "Engine/SimpleConstructionScript.h" -#include "Misc/Paths.h" -#include "HAL/FileManager.h" -#include "LandscapeEdit.h" -#include "Containers/UnrealString.h" -#include "Components/AudioComponent.h" -#include "Engine/WorldComposition.h" -#include "Kismet2/BlueprintEditorUtils.h" -#include "MaterialEditor/Public/MaterialEditingLibrary.h" -#include "MaterialGraph/MaterialGraph.h" -#include "Particles/ParticleSystemComponent.h" -#include "Sound/SoundBase.h" -#include "UObject/UnrealType.h" -#include "Math/Box.h" -#include "Misc/ScopedSlowTask.h" - -HOUDINI_BAKING_DEFINE_LOG_CATEGORY(); - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() - : Actor(nullptr) - , OutputIndex(INDEX_NONE) - , OutputObjectIdentifier() - , ActorBakeName(NAME_None) - , BakedObject(nullptr) - , SourceObject(nullptr) - , BakeFolderPath() - , bInstancerOutput(false) - , bPostBakeProcessPostponed(false) -{ -} - -FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( - AActor* InActor, - FName InActorBakeName, - FName InWorldOutlinerFolder, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - UObject* InBakedObject, - UObject* InSourceObject, - UObject* InBakedComponent, - const FString& InBakeFolderPath, - const FHoudiniPackageParams& InBakedObjectPackageParams) - : Actor(InActor) - , OutputIndex(InOutputIndex) - , OutputObjectIdentifier(InOutputObjectIdentifier) - , ActorBakeName(InActorBakeName) - , WorldOutlinerFolder(InWorldOutlinerFolder) - , BakedObject(InBakedObject) - , SourceObject(InSourceObject) - , BakedComponent(InBakedComponent) - , BakeFolderPath(InBakeFolderPath) - , BakedObjectPackageParams(InBakedObjectPackageParams) - , bInstancerOutput(false) - , bPostBakeProcessPostponed(false) -{ -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - UHoudiniAssetComponent* InHACToBake, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess) -{ - if (!IsValid(InHACToBake)) - return false; - - // Handle proxies: if the output has any current proxies, first refine them - bool bHACNeedsToReCook; - if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bHACNeedsToReCook)) - { - // Either the component is invalid, or needs a recook to refine a proxy mesh - return false; - } - - bool bSuccess = false; - switch (InBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake); - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake); - } - break; - - case EHoudiniEngineBakeOption::ToFoliage: - { - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake); - } - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - { - //Todo - bSuccess = false; - } - break; - - } - - if (bSuccess && bInRemoveHACOutputOnSuccess) - { - TArray DeferredClearOutputs; - FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake, DeferredClearOutputs, true); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - TArray NewActors; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - - if (!FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats)) - { - // TODO ? - HOUDINI_LOG_WARNING(TEXT("Errors when baking")); - } - - // Save the created packages - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Recenter and select the baked actors - if (GEditor && NewActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : NewActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (HoudiniAssetComponent->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && NewActors.Num() > 0) - GEditor->NoteSelectionChange(); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!IsValid(OwnerActor)) - return false; - - const FString HoudiniAssetName = OwnerActor->GetName(); - - // Get an array of the outputs - const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); - TArray Outputs; - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - Outputs.Add(HoudiniAssetComponent->GetOutputAt(OutputIdx)); - } - - // Get the previous bake objects and grow/shrink to match asset outputs - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - // Ensure we have an entry for each output - if (BakedOutputs.Num() != NumOutputs) - BakedOutputs.SetNum(NumOutputs); - - return BakeHoudiniOutputsToActors( - HoudiniAssetComponent, - Outputs, - BakedOutputs, - HoudiniAssetName, - HoudiniAssetComponent->GetComponentTransform(), - HoudiniAssetComponent->BakeFolder, - HoudiniAssetComponent->TemporaryCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutNewActors, - OutPackagesToSave, - OutBakeStats, - InOutputTypesToBake, - InInstancerComponentTypesToBake, - InFallbackActor, - InFallbackWorldOutlinerFolder); -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - const TArray& InOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FTransform& InParentTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - const int32 NumOutputs = InOutputs.Num(); - - const FString MsgTemplate = TEXT("Baking output: {0}/{1}."); - FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs }); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg)); - - TArray BakedActors; - - // Ensure that InBakedOutputs is the same size as InOutputs - if (InBakedOutputs.Num() != NumOutputs) - InBakedOutputs.SetNum(NumOutputs); - - // First bake everything except instancers, then bake instancers. Since instancers might use meshes in - // from the other outputs. - bool bHasAnyInstancers = false; - int32 NumProcessedOutputs = 0; - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) - { - NumProcessedOutputs++; - continue; - } - - Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); - - const EHoudiniOutputType OutputType = Output->GetType(); - // Check if we should skip this output type - if (InOutputTypesToBake && InOutputTypesToBake->Find(OutputType) == INDEX_NONE) - { - NumProcessedOutputs++; - continue; - } - - switch (OutputType) - { - case EHoudiniOutputType::Mesh: - { - FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( - HoudiniAssetComponent, - OutputIdx, - InOutputs, - InBakedOutputs, - InHoudiniAssetName, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - break; - - case EHoudiniOutputType::Instancer: - { - if (!bHasAnyInstancers) - bHasAnyInstancers = true; - NumProcessedOutputs--; - } - break; - - case EHoudiniOutputType::Landscape: - { - const bool bResult = BakeLandscape( - HoudiniAssetComponent, - OutputIdx, - InOutputs, - InBakedOutputs, - bInReplaceActors, - bInReplaceAssets, - InBakeFolder.Path, - InHoudiniAssetName, - OutBakeStats); - } - break; - - case EHoudiniOutputType::Skeletal: - break; - - case EHoudiniOutputType::Curve: - { - FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - HoudiniAssetComponent, - OutputIdx, - InOutputs, - InBakedOutputs, - InHoudiniAssetName, - InBakeFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - break; - - case EHoudiniOutputType::Invalid: - break; - } - - NumProcessedOutputs++; - } - - if (bHasAnyInstancers) - { - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) - { - NumProcessedOutputs++; - continue; - } - - Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); - - if (Output->GetType() == EHoudiniOutputType::Instancer) - { - FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( - HoudiniAssetComponent, - OutputIdx, - InOutputs, - InBakedOutputs, - InParentTransform, - InHoudiniAssetName, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - OutPackagesToSave, - InInstancerComponentTypesToBake, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - - NumProcessedOutputs++; - } - } - - // Only do the post bake post-process once per Actor - TSet UniqueActors; - for (FHoudiniEngineBakedActor& BakedActor : BakedActors) - { - if (BakedActor.bPostBakeProcessPostponed && BakedActor.Actor) - { - BakedActor.bPostBakeProcessPostponed = false; - AActor* Actor = BakedActor.Actor; - bool bIsAlreadyInSet = false; - UniqueActors.Add(Actor, &bIsAlreadyInSet); - if (!bIsAlreadyInSet) - { - Actor->InvalidateLightingCache(); - Actor->PostEditMove(true); - Actor->MarkPackageDirty(); - } - } - } - - OutNewActors.Append(BakedActors); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave) -{ - UHoudiniOutput* Output = InAllOutputs[InOutputIndex]; - if (!Output || Output->IsPendingKill()) - return false; - - if (Output->GetType() != EHoudiniOutputType::Instancer) - return false; - - if (!IsValid(InOutputObject.OutputComponent)) - return false; - - UStaticMeshComponent* SMC = Cast(InOutputObject.OutputComponent); - if (!IsValid(SMC)) - { - HOUDINI_LOG_WARNING( - TEXT("Unsupported component for foliage: %s"),*(InOutputObject.OutputComponent->GetClass()->GetName())); - return false; - } - - UStaticMesh* InstancedStaticMesh = SMC->GetStaticMesh(); - if (!IsValid(InstancedStaticMesh)) - { - // No mesh, skip this instancer - return false; - } - - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets - ? EPackageReplaceMode::ReplaceExistingAssets - : EPackageReplaceMode::CreateNewAssets; - UWorld* DesiredWorld = Output ? Output->GetWorld() : GWorld; - - // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params - // for baking from it. - // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) - FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = nullptr; - int32 MeshOutputIndex = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - FHoudiniAttributeResolver MeshResolver; - FHoudiniPackageParams MeshPackageParams; - const bool bFoundMeshOutput = FindOutputObject(InstancedStaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); - if (bFoundMeshOutput) - { - // Found the mesh in the mesh outputs, is temporary - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - InHoudiniAssetName, MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - // Update with resolved object name - ObjectName = MeshPackageParams.ObjectName; - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); - } - else - { - BakedStaticMesh = InstancedStaticMesh; - } - - // Update the baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // const FString InstancerName = FString::Printf(TEXT("%s_foliage_%s"), *ObjectName, *(InOutputObjectIdentifier.SplitIdentifier)); - // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone - // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the - // package params. - FHoudiniPackageParams InstancerPackageParams; - FHoudiniAttributeResolver InstancerResolver; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - InHoudiniAssetName, InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - HOUDINI_LOG_ERROR(TEXT("Could not find or create a level: %s"), *LevelPackagePath); - return false; - } - - // If we have created a new level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // Get foliage actor for the level - const bool bCreateIfNone = true; - AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, bCreateIfNone); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Could not find or create an instanced foliage actor for level %s"), *(DesiredLevel->GetPathName())); - return false; - } - - // Get the previous bake data for this instancer - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // Foliage type is replaced in replacement mode if: - // the previous baked object is this foliage type - // and we haven't bake this foliage type during this bake (BakeResults) - // NOTE: foliage type is only recorded as the previous bake object if we created the foliage type - // TODO: replacement mode should probably only affect the instances themselves and not the foliage type - // since the foliage type is already linked to whatever mesh we are using (which will be replaced - // incremented already). To track instances it looks like we would have to use the locations of the - // baked instances (likely cannot use the indices, since the user might modify/add/remove instances - // after the bake). - - // See if we already have a FoliageType for that static mesh - UFoliageType* FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(BakedStaticMesh); - if (!FoliageType || FoliageType->IsPendingKill()) - { - // We need to create a new FoliageType for this Static Mesh - // TODO: Add foliage default settings - InstancedFoliageActor->AddMesh(BakedStaticMesh, &FoliageType); - // Update the previous bake results with the foliage type we created - InBakedOutputObject.BakedComponent = FSoftObjectPath(FoliageType).ToString(); - } - else - { - const FString FoliageTypePath = FSoftObjectPath(FoliageType).ToString(); - if (bInReplaceAssets && InBakedOutputObject.BakedComponent == FoliageTypePath && - !OutActors.FindByPredicate([FoliageType](const FHoudiniEngineBakedActor& Entry) { return Entry.BakedComponent == FoliageType; })) - { - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - // Update the previous bake results with the foliage type - InBakedOutputObject.BakedComponent = FoliageTypePath; - } - else - { - // If we didn't create the foliage type, don't set the baked component - InBakedOutputObject.BakedComponent.Empty(); - } - } - - // Record the foliage bake in the current results - FHoudiniEngineBakedActor& NewResult = OutActors.Add_GetRef(FHoudiniEngineBakedActor()); - NewResult.OutputIndex = InOutputIndex; - NewResult.OutputObjectIdentifier = InOutputObjectIdentifier; - NewResult.SourceObject = InstancedStaticMesh; - NewResult.BakedObject = BakedStaticMesh; - NewResult.BakedComponent = FoliageType; - - // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it - FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); - if (!FoliageInfo) - return false; - - int32 CurrentInstanceCount = 0; - if (SMC->IsA()) - { - UInstancedStaticMeshComponent* ISMC = Cast(SMC); - const int32 NumInstances = ISMC->GetInstanceCount(); - for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex) - { - FTransform InstanceTransform; - const bool bWorldSpace = true; - if (ISMC->GetInstanceTransform(InstanceIndex, InstanceTransform, bWorldSpace)) - { - FFoliageInstance FoliageInstance; - FoliageInstance.Location = InstanceTransform.GetLocation(); - FoliageInstance.Rotation = InstanceTransform.GetRotation().Rotator(); - FoliageInstance.DrawScale3D = InstanceTransform.GetScale3D(); - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - - CurrentInstanceCount++; - } - } - } - else - { - const FTransform ComponentToWorldTransform = SMC->GetComponentToWorld(); - FFoliageInstance FoliageInstance; - FoliageInstance.Location = ComponentToWorldTransform.GetLocation(); - FoliageInstance.Rotation = ComponentToWorldTransform.GetRotation().Rotator(); - FoliageInstance.DrawScale3D = ComponentToWorldTransform.GetScale3D(); - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - - CurrentInstanceCount++; - } - - // TODO: This was due to a bug in UE4.22-20, check if still needed! - if (FoliageInfo->GetComponent()) - FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); - - // Notify the user that we succesfully bake the instances to foliage - FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + BakedStaticMesh->GetName() + TEXT(" to Foliage"); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - InstancedFoliageActor->RegisterAllComponents(); - - // Update / repopulate the foliage editor mode's mesh list - if (CurrentInstanceCount > 0) - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - - return true; -} - -bool -FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n) - { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n); - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Instancer) - continue; - - if (Output->GetInstancedOutputs().Num() > 0) - return true; - /* - // TODO: Is this needed? check we have components to bake? - for (auto& OutputObjectPair : Output->GetOutputObjects()) - { - if (OutputObjectPair.Value.OutputCompoent!= nullpt) - return true; - } - */ - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor * OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - TArray PackagesToSave; - TArray BakedResults; - - FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); - const FString HoudiniAssetName = OwnerActor->GetName(); - - // Build an array of the outputs so that we can search for meshes/previous baked meshes - TArray Outputs; - HoudiniAssetComponent->GetOutputs(Outputs); - const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); - - // Get the previous bake outputs and match the output array size - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - if (BakedOutputs.Num() != NumOutputs) - BakedOutputs.SetNum(NumOutputs); - - bool bSuccess = true; - // Map storing original and baked Static Meshes - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* Output = Outputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Instancer) - continue; - - TMap& OutputObjects = Output->GetOutputObjects(); - const TMap& OldBakedOutputObjects = BakedOutputs[OutputIdx].BakedOutputObjects; - TMap NewBakedOutputObjects; - - for (auto & Pair : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - FHoudiniOutputObject& OutputObject = Pair.Value; - - FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); - if (OldBakedOutputObjects.Contains(Identifier)) - BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); - - const bool bInReplaceActors = false; - bSuccess &= BakeInstancerOutputToFoliage( - HoudiniAssetComponent, - OutputIdx, - Outputs, - Identifier, - OutputObject, - BakedOutputObject, - HoudiniAssetName, - HoudiniAssetComponent->BakeFolder, - HoudiniAssetComponent->TemporaryCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedResults, - PackagesToSave); - } - - // Update the cached baked output data - BakedOutputs[OutputIdx].BakedOutputObjects = NewBakedOutputObjects; - } - - if (PackagesToSave.Num() > 0) - { - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - } - - return bSuccess; -} - - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FTransform& InTransform, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) - return false; - - // Ensure we have the same number of baked outputs and asset outputs - if (InBakedOutputs.Num() != InAllOutputs.Num()) - InBakedOutputs.SetNum(InAllOutputs.Num()); - - TMap& OutputObjects = InOutput->GetOutputObjects(); - const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; - TMap NewBakedOutputObjects; - - // Iterate on the output objects, baking their object/component as we go - for (auto& Pair : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - FHoudiniOutputObject& CurrentOutputObject = Pair.Value; - - FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); - if (OldBakedOutputObjects.Contains(Identifier)) - BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); - - if (CurrentOutputObject.bProxyIsCurrent) - { - // TODO: we need to refine the SM first! - // ?? - } - - if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill()) - continue; - - if (CurrentOutputObject.OutputComponent->IsA()) - { - // Bake foliage as foliage - if (!InInstancerComponentTypesToBake || - InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageInstancedStaticMeshComponent)) - { - BakeInstancerOutputToFoliage( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InHoudiniAssetName, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave); - } - else if (!InInstancerComponentTypesToBake || - InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent)) - { - BakeInstancerOutputToActors_ISMC( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent))) - { - BakeInstancerOutputToActors_ISMC( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent))) - { - BakeInstancerOutputToActors_IAC( - HoudiniAssetComponent, - InOutputIndex, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InBakeFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) - { - BakeInstancerOutputToActors_MSIC( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) - { - BakeInstancerOutputToActors_SMC( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else - { - // Unsupported component! - } - - } - - // Update the cached baked output data - InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UInstancedStaticMeshComponent * InISMC = Cast(InOutputObject.OutputComponent); - if (!InISMC || InISMC->IsPendingKill()) - return false; - - AActor * OwnerActor = InISMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh * StaticMesh = InISMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params - // for baking from it. - // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) - FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = nullptr; - int32 MeshOutputIndex = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - FHoudiniAttributeResolver MeshResolver; - FHoudiniPackageParams MeshPackageParams; - const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); - if (bFoundMeshOutput) - { - // Found the mesh in the mesh outputs, is temporary - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - OwnerActor->GetName(), MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - // Update with resolved object name - ObjectName = MeshPackageParams.ObjectName; - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); - } - else - { - BakedStaticMesh = StaticMesh; - } - - // Update the baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone - // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the - // package params. - FHoudiniPackageParams InstancerPackageParams; - FHoudiniAttributeResolver InstancerResolver; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a new level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if(!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - /* - // TODO: Get the bake name! - // Bake override, the output name - // The bake name override has priority - FString InstancerName = InOutputObject.BakeName; - if (InstancerName.IsEmpty()) - { - // .. then use the output name - InstancerName = Resolver.ResolveOutputName(); - } - */ - - // Should we create one actor with an ISMC or multiple actors with one SMC? - bool bSpawnMultipleSMC = false; - if (bSpawnMultipleSMC) - { - // TODO: Double check, Has a crash here! - - // Get the StaticMesh ActorFactory - UActorFactory* SMFactory = nullptr; - - if (!FoundActor) - { - SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!SMFactory) - return false; - } - - // Split the instances to multiple StaticMeshActors - for (int32 InstanceIdx = 0; InstanceIdx < InISMC->GetInstanceCount(); InstanceIdx++) - { - FTransform InstanceTransform; - InISMC->GetInstanceTransform(InstanceIdx, InstanceTransform, true); - - if (!FoundActor) - { - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform, RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) - continue; - } - - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName.ToString(), FoundActor); - RenameAndRelabelActor(FoundActor, NewNameStr, false); - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - continue; - - // Copy properties from the existing component - CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC); - - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh, - SMActor->GetStaticMeshComponent(), - bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), - MeshPackageParams)); - OutputEntry.bInstancerOutput = true; - OutputEntry.InstancerPackageParams = InstancerPackageParams; - } - } - else - { - bool bSpawnedActor = false; - if (!FoundActor) - { - // Only create one actor - FActorSpawnParameters SpawnInfo; - SpawnInfo.OverrideLevel = DesiredLevel; - SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); - SpawnInfo.bDeferConstruction = true; - - // Spawn the new Actor - FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - bSpawnedActor = true; - - FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); - FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); - } - else - { - // If there is a previously baked component, and we are in replace mode, remove it - if (bInReplaceAssets) - { - USceneComponent* InPrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(InPrevComponent) && InPrevComponent->GetOwner() == FoundActor) - RemovePreviouslyBakedComponent(InPrevComponent); - } - - const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); - } - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Get/create the actor's root component - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - if (bSpawnedActor && IsValid(RootComponent)) - RootComponent->SetWorldTransform(InTransform); - - // Duplicate the instancer component, create a Hierarchical ISMC if needed - UInstancedStaticMeshComponent* NewISMC = nullptr; - UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast(InISMC); - if (InHISMC) - { - // Handle foliage: don't duplicate foliage component, create a new hierarchical one and copy what we can - // from the foliage component - if (InHISMC->IsA()) - { - NewISMC = NewObject( - FoundActor, - FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); - CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); - } - else - { - NewISMC = DuplicateObject( - InHISMC, - FoundActor, - FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); - } - } - else - { - NewISMC = DuplicateObject( - InISMC, - FoundActor, - FName(MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetName()))); - } - - if (!NewISMC) - { - //DesiredLevel->OwningWorld-> - return false; - } - - InBakedOutputObject.BakedComponent = FSoftObjectPath(NewISMC).ToString(); - - NewISMC->RegisterComponent(); - // NewISMC->SetupAttachment(nullptr); - NewISMC->SetStaticMesh(BakedStaticMesh); - FoundActor->AddInstanceComponent(NewISMC); - // NewActor->SetRootComponent(NewISMC); - if (IsValid(RootComponent)) - NewISMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - NewISMC->SetWorldTransform(InISMC->GetComponentTransform()); - - // TODO: do we need to copy properties here, we duplicated the component - // // Copy properties from the existing component - // CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); - - if (bSpawnedActor) - FoundActor->FinishSpawning(InTransform); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh, - NewISMC, - bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), - MeshPackageParams)); - OutputEntry.bInstancerOutput = true; - OutputEntry.InstancerPackageParams = InstancerPackageParams; - - // Postpone post-bake calls to do them once per actor - OutActors.Last().bPostBakeProcessPostponed = true; - } - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UStaticMeshComponent* InSMC = Cast(InOutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) - return false; - - AActor* OwnerActor = InSMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh* StaticMesh = InSMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - - // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params - // for baking from it. - // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) - FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = nullptr; - int32 MeshOutputIndex = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - FHoudiniAttributeResolver MeshResolver; - FHoudiniPackageParams MeshPackageParams; - const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); - if (bFoundMeshOutput) - { - // Found the mesh in the mesh outputs, is temporary - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - OwnerActor->GetName(), MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - // Update with resolved object name - ObjectName = MeshPackageParams.ObjectName; - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); - } - else - { - BakedStaticMesh = StaticMesh; - } - - // Update the previous baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // BaseName holds the Actor / HDA name - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams InstancerPackageParams; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver InstancerResolver; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - UStaticMeshComponent* StaticMeshComponent = nullptr; - // Create an actor if we didn't find one - if (!FoundActor) - { - // Get the StaticMesh ActorFactory - UActorFactory* SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!SMFactory) - return false; - - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - return false; - - StaticMeshComponent = SMActor->GetStaticMeshComponent(); - } - else - { - USceneComponent* RootComponent = GetActorRootComponent(FoundActor); - if (!IsValid(RootComponent)) - return false; - - if (bInReplaceAssets) - { - // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it - UStaticMeshComponent* PrevSMC = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) - { - StaticMeshComponent = PrevSMC; - } - } - - if (!IsValid(StaticMeshComponent)) - { - // Create a new static mesh component - StaticMeshComponent = NewObject(FoundActor, NAME_None, RF_Transactional); - - FoundActor->AddInstanceComponent(StaticMeshComponent); - StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - StaticMeshComponent->RegisterComponent(); - } - } - - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName.ToString(), FoundActor); - RenameAndRelabelActor(FoundActor, NewNameStr, false); - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Update the previous baked component - InBakedOutputObject.BakedComponent = FSoftObjectPath(StaticMeshComponent).ToString(); - - if (!IsValid(StaticMeshComponent)) - return false; - - // Copy properties from the existing component - const bool bCopyWorldTransform = true; - CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC, bCopyWorldTransform); - StaticMeshComponent->SetStaticMesh(BakedStaticMesh); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh, - StaticMeshComponent, - MeshPackageParams.BakeFolder, - MeshPackageParams)); - OutputEntry.bInstancerOutput = true; - OutputEntry.InstancerPackageParams = InstancerPackageParams; - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave) -{ - UHoudiniInstancedActorComponent* InIAC = Cast(InOutputObject.OutputComponent); - if (!InIAC || InIAC->IsPendingKill()) - return false; - - AActor * OwnerActor = InIAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - // BaseName holds the Actor / HDA name - const FName BaseName = FName(OwnerActor->GetName()); - - // Get the object instanced by this IAC - UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - return false; - - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver Resolver; - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, BaseName.ToString(), - OwnerActor->GetName(), PackageParams, Resolver, - InBakeFolder.Path, AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // If we are baking in actor replacement mode, remove any previously baked instanced actors for this output - if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0) - { - UWorld* LevelWorld = DesiredLevel->GetWorld(); - if (IsValid(LevelWorld)) - { - for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) - { - const FSoftObjectPath ActorPath(ActorPathStr); - - if (!ActorPath.IsValid()) - continue; - - AActor* Actor = Cast(ActorPath.TryLoad()); - // Destroy Actor if it is valid and part of DesiredLevel - if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel) - { -#if WITH_EDITOR - LevelWorld->EditorDestroyActor(Actor, true); -#else - LevelWorld->DestroyActor(Actor); -#endif - } - } - } - } - - // Empty and reserve enough space for new instanced actors - InBakedOutputObject.InstancedActors.Empty(InIAC->GetInstancedActors().Num()); - - // Iterates on all the instances of the IAC - for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors()) - { - if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) - continue; - - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName.ToString()); - - FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); - AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); - if (!NewActor || NewActor->IsPendingKill()) - continue; - - const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) - (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); - - EditorUtilities::CopyActorProperties(CurrentInstancedActor, NewActor); - - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); - - FHoudiniEngineRuntimeUtils::SetActorLabel(NewActor, NewNameStr); - - SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); - NewActor->SetActorTransform(CurrentTransform); - - InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); - - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( - NewActor, - BaseName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - nullptr, - InstancedObject, - nullptr, - PackageParams.BakeFolder, - PackageParams)); - OutputEntry.bInstancerOutput = true; - OutputEntry.InstancerPackageParams = PackageParams; - } - - // TODO: - // Move Actors to DesiredLevel if needed?? - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = true; - const bool bInDestroyBakedInstancedActors = false; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UHoudiniMeshSplitInstancerComponent * InMSIC = Cast(InOutputObject.OutputComponent); - if (!InMSIC || InMSIC->IsPendingKill()) - return false; - - AActor * OwnerActor = InMSIC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh * StaticMesh = InMSIC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - - // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params - // for baking from it. - // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) - FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = nullptr; - int32 MeshOutputIndex = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - FHoudiniAttributeResolver MeshResolver; - FHoudiniPackageParams MeshPackageParams; - const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); - if (bFoundMeshOutput) - { - // Found the mesh in the mesh outputs, is temporary - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - OwnerActor->GetName(), MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - // Update with resolved object name - ObjectName = MeshPackageParams.ObjectName; - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); - } - else - { - BakedStaticMesh = StaticMesh; - } - - // Update the baked output - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams InstancerPackageParams; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver InstancerResolver; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - bool bSpawnedActor = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - if (!FoundActor) - { - // This is a split mesh instancer component - we will create a generic AActor with a bunch of SMC - FActorSpawnParameters SpawnInfo; - SpawnInfo.OverrideLevel = DesiredLevel; - SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); - SpawnInfo.bDeferConstruction = true; - - // Spawn the new Actor - FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - bSpawnedActor = true; - - FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); - - FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); - } - else - { - // If we are baking in replacement mode, remove the previous components (if they belong to FoundActor) - for (const FString& PrevComponentPathStr : InBakedOutputObject.InstancedComponents) - { - const FSoftObjectPath PrevComponentPath(PrevComponentPathStr); - - if (!PrevComponentPath.IsValid()) - continue; - - UActorComponent* PrevComponent = Cast(PrevComponentPath.TryLoad()); - if (!IsValid(PrevComponent) || PrevComponent->GetOwner() != FoundActor) - continue; - - RemovePreviouslyBakedComponent(PrevComponent); - } - - const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); - } - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Get/create the actor's root component - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - if (bSpawnedActor && IsValid(RootComponent)) - RootComponent->SetWorldTransform(InTransform); - - // Empty and reserve enough space in the baked components array for the new components - InBakedOutputObject.InstancedComponents.Empty(InMSIC->GetInstances().Num()); - - // Now add s SMC component for each of the SMC's instance - for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances()) - { - if (!CurrentSMC || CurrentSMC->IsPendingKill()) - continue; - - UStaticMeshComponent* NewSMC = DuplicateObject( - CurrentSMC, - FoundActor, - FName(MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetName()))); - if (!NewSMC || NewSMC->IsPendingKill()) - continue; - - InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString()); - - NewSMC->RegisterComponent(); - // NewSMC->SetupAttachment(nullptr); - NewSMC->SetStaticMesh(BakedStaticMesh); - FoundActor->AddInstanceComponent(NewSMC); - NewSMC->SetWorldTransform(CurrentSMC->GetComponentTransform()); - if (IsValid(RootComponent)) - NewSMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); - - // TODO: Do we need to copy properties here, we duplicated the component - // // Copy properties from the existing component - // CopyPropertyToNewActorAndComponent(FoundActor, NewSMC, CurrentSMC); - } - - if (bSpawnedActor) - FoundActor->FinishSpawning(InTransform); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh, - nullptr, - MeshPackageParams.BakeFolder, - MeshPackageParams)); - OutputEntry.bInstancerOutput = true; - OutputEntry.InstancerPackageParams = InstancerPackageParams; - - // Postpone these calls to do them once per actor - OutActors.Last().bPostBakeProcessPostponed = true; - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = true; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = false; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::FindHGPO( - const FHoudiniOutputObjectIdentifier& InIdentifier, - const TArray& InHGPOs, - FHoudiniGeoPartObject const*& OutHGPO) -{ - // Find the HGPO that matches this output identifier - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - for (auto & NextHGPO : InHGPOs) - { - // We use Matches() here as it handles the case where the HDA was loaded, - // which likely means that the the obj/geo/part ids dont match the output identifier - if(InIdentifier.Matches(NextHGPO)) - { - FoundHGPO = &NextHGPO; - break; - } - } - - OutHGPO = FoundHGPO; - return !OutHGPO; -} - -void -FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const FHoudiniOutputObject& InMeshOutputObject, - FString& OutBakeName) -{ - // The bake name override has priority - OutBakeName = InMeshOutputObject.BakeName; - if (OutBakeName.IsEmpty()) - { - FHoudiniAttributeResolver Resolver; - Resolver.SetCachedAttributes(InMeshOutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(InMeshOutputObject.CachedTokens); - const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InObject); - // The default output name (if not set via attributes) is {object_name}, which look for an object_name - // key-value token - if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) - Resolver.SetToken(TEXT("object_name"), DefaultObjectName); - OutBakeName = Resolver.ResolveOutputName(); - // const TArray& HGPOs = InAllOutputs[MeshOutputIdx]->GetHoudiniGeoPartObjects(); - // const FHoudiniGeoPartObject* FoundHGPO = nullptr; - // FindHGPO(MeshIdentifier, HGPOs, FoundHGPO); - // // ... finally the part name - // if (FoundHGPO && FoundHGPO->bHasCustomPartName) - // OutBakeName = FoundHGPO->PartName; - if (OutBakeName.IsEmpty()) - OutBakeName = DefaultObjectName; - } -} - -bool -FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( - const UObject* InObject, - EHoudiniOutputType InOutputType, - const TArray& InAllOutputs, - FString& OutBakeName) -{ - if (!IsValid(InObject)) - return false; - - OutBakeName.Empty(); - - int32 MeshOutputIdx = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - if (FindOutputObject(InObject, InOutputType, InAllOutputs, MeshOutputIdx, MeshIdentifier)) - { - // Found the mesh, get its name - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier); - GetTemporaryOutputObjectBakeName(InObject, MeshOutputObject, OutBakeName); - - return true; - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - // Check that index is not negative - if (InOutputIndex < 0) - return false; - - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) - return false; - - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!Factory) - return false; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); - - // Get the previous bake objects - if (!InBakedOutputs.IsValidIndex(InOutputIndex)) - InBakedOutputs.SetNum(InOutputIndex + 1); - - const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; - TMap NewBakedOutputObjects; - - for (auto& Pair : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - const FHoudiniOutputObject& OutputObject = Pair.Value; - - // Add a new baked output object entry and update it with the previous bake's data, if available - FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); - if (OldBakedOutputObjects.Contains(Identifier)) - BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); - - UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) - continue; - - UStaticMeshComponent* InSMC = Cast(OutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) - continue; - - // Find the HGPO that matches this output identifier - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - FindHGPO(Identifier, HGPOs, FoundHGPO); - - // We do not bake templated geos - if (FoundHGPO && FoundHGPO->bIsTemplated) - continue; - - FHoudiniAttributeResolver Resolver; - const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - - UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, - InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); - - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); - - // See if this output object has an unreal_level_path attribute specified - // In which case, we need to create/find the desired level for baking instead of using the current one - bool bHasLevelPathAttribute = OutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - continue; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add the level to the packages to save? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - // Bake the static mesh if it is still temporary - UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, - Cast(BakedOutputObject.GetBakedObjectIfValid()), - PackageParams, - InAllOutputs, - OutActors, - InTempCookFolder.Path, - OutPackagesToSave); - - if (!BakedSM || BakedSM->IsPendingKill()) - continue; - - // Record the baked object - BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString(); - - // Make sure we have a level to spawn to - if (!DesiredLevel || DesiredLevel->IsPendingKill()) - continue; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - UStaticMeshComponent* SMC = nullptr; - if (!FoundActor) - { - // Spawn the new actor - FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); - if (!FoundActor || FoundActor->IsPendingKill()) - continue; - - // Copy properties to new actor - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - continue; - - SMC = SMActor->GetStaticMeshComponent(); - } - else - { - if (bInReplaceAssets) - { - // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it - UStaticMeshComponent* PrevSMC = Cast(BakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) - { - SMC = PrevSMC; - } - } - - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - - if (!IsValid(SMC)) - { - // Create a new static mesh component on the existing actor - SMC = NewObject(FoundActor, NAME_None, RF_Transactional); - - FoundActor->AddInstanceComponent(SMC); - if (IsValid(RootComponent)) - SMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - else - FoundActor->SetRootComponent(SMC); - SMC->RegisterComponent(); - } - } - - // We need to make a unique name for the actor, renaming an object on top of another is a fatal error - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName.ToString(), FoundActor); - RenameAndRelabelActor(FoundActor, NewNameStr, false); - SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath); - - if (IsValid(SMC)) - { - const bool bCopyWorldTransform = true; - CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC, bCopyWorldTransform); - SMC->SetStaticMesh(BakedSM); - BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString(); - } - - BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh, SMC, - PackageParams.BakeFolder, PackageParams)); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - BakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - } - - // Update the cached baked output data - InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - // Check that index is not negative - if (InOutputIndex < 0) - return false; - - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; - if (!Output || Output->IsPendingKill()) - return false; - - TArray PackagesToSave; - - // Find the previous baked output data for this output index. If an entry - // does not exist, create entries up to and including this output index - if (!InBakedOutputs.IsValidIndex(InOutputIndex)) - InBakedOutputs.SetNum(InOutputIndex + 1); - - TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; - const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; - TMap NewBakedOutputObjects; - - const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); - - for (auto & Pair : OutputObjects) - { - FHoudiniOutputObject& OutputObject = Pair.Value; - USplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); - if (OldBakedOutputObjects.Contains(Identifier)) - BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); - - // TODO: FIX ME!! May not work 100% - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - for (auto & NextHGPO : HGPOs) - { - if (Identifier.GeoId == NextHGPO.GeoId && - Identifier.ObjectId == NextHGPO.ObjectId && - Identifier.PartId == NextHGPO.PartId) - { - FoundHGPO = &NextHGPO; - break; - } - } - - if (!FoundHGPO) - continue; - - const FString DefaultObjectName = InHoudiniAssetName + "_" + SplineComponent->GetName(); - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver Resolver; - UWorld* const DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, - InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); - - BakeCurve( - OutputObject, BakedOutputObject, PackageParams, Resolver, bInReplaceActors, bInReplaceAssets, - OutActors, PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); - } - - // Update the cached bake output results - BakedOutput.BakedOutputObjects = NewBakedOutputObjects; - - SaveBakedPackages(PackagesToSave); - - return true; -} - -bool -FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint) -{ - if (!InActor || InActor->IsPendingKill()) - return false; - - if (!OutBlueprint || OutBlueprint->IsPendingKill()) - return false; - - if (InActor->GetInstanceComponents().Num() > 0) - FKismetEditorUtilities::AddComponentsToBlueprint( - OutBlueprint, - InActor->GetInstanceComponents()); - - if (OutBlueprint->GeneratedClass) - { - AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject()); - if (!CDO || CDO->IsPendingKill()) - return false; - - const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) - (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); - - EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions); - - USceneComponent * Scene = CDO->GetRootComponent(); - if (Scene && !Scene->IsPendingKill()) - { - Scene->SetRelativeLocation(FVector::ZeroVector); - Scene->SetRelativeRotation(FRotator::ZeroRotator); - - // Clear out the attachment info after having copied the properties from the source actor - Scene->SetupAttachment(nullptr); - while (true) - { - const int32 ChildCount = Scene->GetAttachChildren().Num(); - if (ChildCount < 1) - break; - - USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; - if (Component && !Component->IsPendingKill()) - Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - } - check(Scene->GetAttachChildren().Num() == 0); - - // Ensure the light mass information is cleaned up - Scene->InvalidateLightingCache(); - - // Copy relative scale from source to target. - if (USceneComponent* SrcSceneRoot = InActor->GetRootComponent()) - { - Scene->SetRelativeScale3D_Direct(SrcSceneRoot->GetRelativeScale3D()); - } - } - } - - // Compile our blueprint and notify asset system about blueprint. - //FKismetEditorUtilities::CompileBlueprint(OutBlueprint); - //FAssetRegistryModule::AssetCreated(OutBlueprint); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) -{ - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, BakeStats, Blueprints, PackagesToSave); - if (!bSuccess) - { - // TODO: ? - HOUDINI_LOG_WARNING(TEXT("Errors while baking to blueprints.")); - } - - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprints( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceAssets, - FHoudiniEngineOutputStats& InBakeStats, - TArray& OutBlueprints, - TArray& OutPackagesToSave) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); - const bool bIsOwnerActorValid = IsValid(OwnerActor); - - TArray Actors; - - // Don't process outputs that are not supported in blueprints - TArray OutputsToBake = { - EHoudiniOutputType::Mesh, - EHoudiniOutputType::Instancer, - EHoudiniOutputType::Curve - }; - TArray InstancerComponentTypesToBake = { - EHoudiniInstancerComponentType::StaticMeshComponent, - EHoudiniInstancerComponentType::InstancedStaticMeshComponent, - EHoudiniInstancerComponentType::MeshSplitInstancerComponent, - EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent - }; - // When baking blueprints we always create new actors since they are deleted from the world once copied into the - // blueprint - const bool bReplaceActors = false; - bool bBakeSuccess = BakeHoudiniActorToActors( - HoudiniAssetComponent, - bReplaceActors, - bInReplaceAssets, - Actors, - OutPackagesToSave, - InBakeStats, - &OutputsToBake, - &InstancerComponentTypesToBake); - if (!bBakeSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Could not create output actors for baking to blueprint.")); - return false; - } - - // Get the previous baked outputs - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - - bBakeSuccess = BakeBlueprintsFromBakedActors( - Actors, - HoudiniAssetComponent->bRecenterBakedActors, - bInReplaceAssets, - bIsOwnerActorValid ? OwnerActor->GetName() : FString(), - HoudiniAssetComponent->BakeFolder, - &BakedOutputs, - nullptr, - OutBlueprints, - OutPackagesToSave); - - return bBakeSuccess; -} - -UStaticMesh* -FHoudiniEngineBakeUtils::BakeStaticMesh( - UStaticMesh * StaticMesh, - const FHoudiniPackageParams& PackageParams, - const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return nullptr; - - TArray PackagesToSave; - TArray Outputs; - const TArray BakedResults; - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave); - - if (BakedStaticMesh) - { - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor) - { - TArray Objects; - Objects.Add(BakedStaticMesh); - GEditor->SyncBrowserToObjects(Objects); - } - } - - return BakedStaticMesh; -} - -bool -FHoudiniEngineBakeUtils::BakeLandscape( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - bool bInReplaceActors, - bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, - FHoudiniEngineOutputStats& BakeStats - ) -{ - // Check that index is not negative - if (InOutputIndex < 0) - return false; - - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; - if (!IsValid(Output)) - return false; - - // Find the previous baked output data for this output index. If an entry - // does not exist, create entries up to and including this output index - if (!InBakedOutputs.IsValidIndex(InOutputIndex)) - InBakedOutputs.SetNum(InOutputIndex + 1); - - TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; - const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; - TMap NewBakedOutputObjects; - TArray PackagesToSave; - TArray LandscapeWorldsToUpdate; - - FHoudiniPackageParams PackageParams; - - for (auto& Elem : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key; - FHoudiniOutputObject& OutputObject = Elem.Value; - FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(ObjectIdentifier); - if (OldBakedOutputObjects.Contains(ObjectIdentifier)) - BakedOutputObject = OldBakedOutputObjects.FindChecked(ObjectIdentifier); - - // Populate the package params for baking this output object. - if (!IsValid(OutputObject.OutputObject)) - continue; - - if (!OutputObject.OutputObject->IsA()) - continue; - - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); - if (!IsValid(Landscape)) - continue; - - FString ObjectName = Landscape->GetName(); - - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver Resolver; - UWorld* const DesiredWorld = Landscape ? Landscape->GetWorld() : GWorld; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, ObjectIdentifier, OutputObject, ObjectName, - HoudiniAssetName, PackageParams, Resolver, BakePath, AssetPackageReplaceMode); - - BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, - PackageParams, Resolver, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); - } - - // Update the cached baked output data - BakedOutput.BakedOutputObjects = NewBakedOutputObjects; - - if (PackagesToSave.Num() > 0) - { - FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); - } - - for(UWorld* LandscapeWorld : LandscapeWorldsToUpdate) - { - if (!LandscapeWorld) - continue; - FHoudiniEngineUtils::RescanWorldPath(LandscapeWorld); - ULandscapeInfo::RecreateLandscapeInfo(LandscapeWorld, true); - if (LandscapeWorld->WorldComposition) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(LandscapeWorld); - } - } - - if (PackagesToSave.Num() > 0) - { - // These packages were either created during the Bake process or they weren't - // loaded in the first place so be sure to unload them again to preserve their "state". - - TArray PackagesToUnload; - for (UPackage* Package : PackagesToSave) - { - if (!Package->IsDirty()) - PackagesToUnload.Add(Package); - } - UPackageTools::UnloadPackages(PackagesToUnload); - } - -#if WITH_EDITOR - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); -#endif - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeLandscapeObject( - FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInReplaceActors, - bool bInReplaceAssets, - FHoudiniPackageParams& PackageParams, - FHoudiniAttributeResolver& InResolver, - TArray& WorldsToUpdate, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& BakeStats) -{ - UHoudiniLandscapePtr* LandscapePointer = Cast(InOutputObject.OutputObject); - if (!LandscapePointer) - return false; - - ALandscapeProxy* TileActor = LandscapePointer->GetRawPtr(); - if (!TileActor) - return false; - - // Fetch the previous bake's pointer and proxy (if available) - ALandscapeProxy* PreviousTileActor = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - - UWorld* TileWorld = TileActor->GetWorld(); - ULevel* TileLevel = TileActor->GetLevel(); - - ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true); - - // If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC - // and has the appropriate name. - ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor(); - check(SharedLandscapeActor); - - // Fetch the previous bake's shared landscape actor (if available) - ALandscape* PreviousSharedLandscapeActor = nullptr; - if (IsValid(PreviousTileActor)) - PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor(); - - const bool bHasSharedLandscape = SharedLandscapeActor != TileActor; - const bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; - bool bLandscapeReplaced = false; - if (bHasSharedLandscape) - { - // If we are baking in replace mode and we have a previous shared landscape actor, use the name of that - // actor - const FString DesiredSharedLandscapeName = bHasPreviousSharedLandscape && bInReplaceActors - ? PreviousSharedLandscapeActor->GetName() - : InResolver.ResolveAttribute( - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, - SharedLandscapeActor->GetName()); - - // If we are not baking in replacement mode, create a unique name if the name is already in use - const FString SharedLandscapeName = !bInReplaceActors - ? MakeUniqueObjectNameIfNeeded(SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *DesiredSharedLandscapeName) - : DesiredSharedLandscapeName; - - if (SharedLandscapeActor->GetName() != SharedLandscapeName) - { - AActor* FoundActor = nullptr; - ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); - if (ExistingLandscape && bInReplaceActors) - { - // Even though we found an existing landscape with the desired type, we're just going to destroy/replace - // it for now. - FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); - ExistingLandscape->Destroy(); - bLandscapeReplaced = true; - } - - // Fix name of shared landscape - FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); - } - - SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); - } - - // Find the world where the landscape tile should be placed. - - TArray ValidLandscapes; - - FString ActorName = InResolver.ResolveOutputName(); - - // If the unreal_level_path was not specified, then fallback to the tile world's package - FString PackagePath = TileWorld->GetPackage() ? TileWorld->GetPackage()->GetPathName() : FString(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - PackagePath = InResolver.ResolveFullLevelPath(); - - if (bInReplaceActors) - { - // If we are baking in replace mode: get the previous baked actor (if available) name, but only if it is in the - // same target level - if (IsValid(PreviousTileActor)) - { - UPackage* PreviousPackage = PreviousTileActor->GetPackage(); - if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath) - { - ActorName = PreviousTileActor->GetName(); - } - } - } - - bool bCreatedPackage = false; - UWorld* TargetWorld = nullptr; - ULevel* TargetLevel = nullptr; - ALandscapeProxy* TargetActor = FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( - TileActor->GetWorld(), - nullptr, //unused in bake mode - ValidLandscapes,//unused in bake mode - -1, //unused in bake mode - -1, //unused in bake mode - ActorName, - PackagePath, - TargetWorld, - TargetLevel, - bCreatedPackage - ); - - check(TargetLevel) - check(TargetWorld) - - if (TargetActor && TargetActor != TileActor) - { - if (bInReplaceActors && (!PreviousTileActor || PreviousTileActor == TargetActor)) - { - // We found an target matching the name that we want. For now, rename it and then nuke it, so that - // at the very least we can spawn a new actor with the desired name. At a later stage we'll implement - // a content update, if possible. - FHoudiniEngineUtils::RenameToUniqueActor(TargetActor, ActorName + TEXT("_0")); - TargetActor->Destroy(); - } - else - { - // incremental, keep existing actor and create a unique name for the new one - ActorName = MakeUniqueObjectName(TargetActor->GetOuter(), TargetActor->GetClass(), *ActorName).ToString(); - } - TargetActor = nullptr; - } - - if (TargetLevel != TileActor->GetLevel()) - { - bool bLevelInWorld = TileWorld->ContainsLevel(TargetLevel); - ALandscape* SharedLandscape = TileActor->GetLandscapeActor(); - ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); - - check(LandscapeInfo); - - // We can now move the current landscape to the new world / level - // if (TileActor->GetClass()->IsChildOf()) - { - // We can only move streaming proxies to sublevels for now. - TArray ActorsToMove = {TileActor}; - - ALandscapeProxy* NewLandscapeProxy = LandscapeInfo->MoveComponentsToLevel(TileActor->LandscapeComponents, TargetLevel); - // We have now moved the landscape components into the new level. We can (hopefully) safely delete the - // old tile actor. - TileActor->Destroy(); - - TargetLevel->MarkPackageDirty(); - - TileActor = NewLandscapeProxy; - } - } - else - { - // Ensure the landscape actor is detached. - TileActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - } - - // Ensure the tile actor has the desired name. - FHoudiniEngineUtils::SafeRenameActor(TileActor, ActorName); - - if (TileActor->GetClass()->IsChildOf(ALandscape::StaticClass())) - { - // This is not a shared landscape. Be sure to update this landscape's world when - // baking is done. - WorldsToUpdate.AddUnique(TileActor->GetWorld()); - } - - if (bCreatedPackage) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(TargetLevel->GetOutermost()); - } - - // Record the landscape in the baked output object via a new UHoudiniLandscapePtr - // UHoudiniLandscapePtr* BakedLandscapePtr = NewObject(LandscapePointer->GetOuter()); - // if (IsValid(BakedLandscapePtr)) - // { - // BakedLandscapePtr->SetSoftPtr(TileActor); - InBakedOutputObject.BakedObject = FSoftObjectPath(TileActor).ToString(); - // } - // else - // { - // InBakedOutputObject.BakedObject = nullptr; - // } - - // Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks - InOutputObject.OutputObject = nullptr; - - DestroyPreviousBakeOutput(InBakedOutputObject, true, true, true); - - // ---------------------------------------------------- - // Collect baking stats - // ---------------------------------------------------- - if (bLandscapeReplaced) - BakeStats.NotifyObjectsReplaced(EHoudiniOutputType::Landscape, 1); - else - BakeStats.NotifyObjectsCreated(EHoudiniOutputType::Landscape, 1); - - if (bCreatedPackage) - BakeStats.NotifyPackageCreated(1); - else - if (TileLevel != TargetLevel) - BakeStats.NotifyPackageUpdated(1); - - return true; -} - -UStaticMesh * -FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - UStaticMesh * InStaticMesh, - UStaticMesh * InPreviousBakeStaticMesh, - const FHoudiniPackageParams &PackageParams, - const TArray& InParentOutputs, - const TArray& InCurrentBakedActors, - const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages) -{ - if (!InStaticMesh || InStaticMesh->IsPendingKill()) - return nullptr; - - const bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, EHoudiniOutputType::Mesh, InParentOutputs, InTemporaryCookFolder); - if (!bIsTemporaryStaticMesh) - { - // The Static Mesh is not a temporary one/already baked, we can simply reuse it - // instead of duplicating it - return InStaticMesh; - } - - // Look for InStaticMesh as the SourceObject in InCurrentBakedActors (it could have already been baked along with - // a previous output: instancers etc) - for (const FHoudiniEngineBakedActor& BakedActor : InCurrentBakedActors) - { - if (BakedActor.SourceObject == InStaticMesh && IsValid(BakedActor.BakedObject) - && BakedActor.BakedObject->IsA(InStaticMesh->GetClass())) - { - // We have found a bake result where InStaticMesh was the source object and we have a valid BakedObject - // of a compatible class - return Cast(BakedActor.BakedObject); - } - } - - // InStaticMesh is temporary and we didn't find a baked version of it in our current bake output, we need to bake it - - // If we have a previously baked static mesh, get the bake counter from it so that both replace and increment - // is consistent with the bake counter - int32 BakeCounter = 0; - bool bPreviousBakeStaticMeshValid = IsValid(InPreviousBakeStaticMesh); - TArray PreviousBakeMaterials; - if (bPreviousBakeStaticMeshValid) - { - bPreviousBakeStaticMeshValid = PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBakeStaticMesh); - if (bPreviousBakeStaticMeshValid) - { - PackageParams.GetBakeCounterFromBakedAsset(InPreviousBakeStaticMesh, BakeCounter); - PreviousBakeMaterials = InPreviousBakeStaticMesh->StaticMaterials;//GetStaticMaterials(); - } - } - FString CreatedPackageName; - UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter); - if (!MeshPackage || MeshPackage->IsPendingKill()) - return nullptr; - - OutCreatedPackages.Add(MeshPackage); - - // We need to be sure the package has been fully loaded before calling DuplicateObject - if (!MeshPackage->IsFullyLoaded()) - { - FlushAsyncLoading(); - if (!MeshPackage->GetOuter()) - { - MeshPackage->FullyLoad(); - } - else - { - MeshPackage->GetOutermost()->FullyLoad(); - } - } - - // If the a UStaticMesh with that name already exists then detach it from all of its components before replacing - // it so that its render resources can be safely replaced/updated, and then reattach it - UStaticMesh * DuplicatedStaticMesh = nullptr; - UStaticMesh* ExistingMesh = FindObject(MeshPackage, *CreatedPackageName); - bool bFoundExistingMesh = false; - if (IsValid(ExistingMesh)) - { - FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); - DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); - bFoundExistingMesh = true; - } - else - { - DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); - } - - if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MeshPackage, DuplicatedStaticMesh, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MeshPackage, DuplicatedStaticMesh, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedPackageName); - - // See if we need to duplicate materials and textures. - TArrayDuplicatedMaterials; - TArray& Materials = DuplicatedStaticMesh->StaticMaterials; - for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) - { - UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - continue; - - // Only duplicate the material if it is temporary - if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InParentOutputs, InTemporaryCookFolder)) - { - UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); - if (MaterialPackage && !MaterialPackage->IsPendingKill()) - { - FString MaterialName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( - MeshPackage, DuplicatedStaticMesh, MaterialName)) - { - MaterialName = MaterialName + "_Material" + FString::FromInt(MaterialIdx + 1); - - // We only deal with materials. - UMaterial * Material = Cast< UMaterial >(MaterialInterface); - if (Material && !Material->IsPendingKill()) - { - // Look for a previous bake material at this index - UMaterial* PreviousBakeMaterial = nullptr; - if (bPreviousBakeStaticMeshValid && PreviousBakeMaterials.IsValidIndex(MaterialIdx)) - { - PreviousBakeMaterial = Cast(PreviousBakeMaterials[MaterialIdx].MaterialInterface); - } - // Duplicate material resource. - UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages); - - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) - continue; - - // Store duplicated material. - FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx]; - DupeStaticMaterial.MaterialInterface = DuplicatedMaterial; - DuplicatedMaterials.Add(DupeStaticMaterial); - continue; - } - } - } - } - - // We can simply reuse the source material - DuplicatedMaterials.Add(Materials[MaterialIdx]); - } - - // Assign duplicated materials. - DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; - - // Notify registry that we have created a new duplicate mesh. - if (!bFoundExistingMesh) - FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); - - // Dirty the static mesh package. - DuplicatedStaticMesh->MarkPackageDirty(); - - return DuplicatedStaticMesh; -} - -ALandscapeProxy* -FHoudiniEngineBakeUtils::BakeHeightfield( - ALandscapeProxy * InLandscapeProxy, - const FHoudiniPackageParams & PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType) -{ - if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill()) - return nullptr; - - const FString & BakeFolder = PackageParams.BakeFolder; - const FString & AssetName = PackageParams.HoudiniAssetName; - - switch (LandscapeOutputBakeType) - { - case EHoudiniLandscapeOutputBakeType::Detachment: - { - // Detach the landscape from the Houdini Asset Actor - InLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - } - break; - case EHoudiniLandscapeOutputBakeType::BakeToImage: - { - // Create heightmap image to the bake folder - ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) - return nullptr; - - // bake to image must use absoluate path, - // and the file name has a file extension (.png) - FString BakeFolderInFullPath = BakeFolder; - - // Figure absolute path, - if (!BakeFolderInFullPath.EndsWith("/")) - BakeFolderInFullPath += "/"; - - if (BakeFolderInFullPath.StartsWith("/Game")) - BakeFolderInFullPath = BakeFolderInFullPath.Mid(5, BakeFolderInFullPath.Len() - 5); - - if (BakeFolderInFullPath.StartsWith("/")) - BakeFolderInFullPath = BakeFolderInFullPath.Mid(1, BakeFolderInFullPath.Len() - 1); - - FString FullPath = FPaths::ProjectContentDir() + BakeFolderInFullPath + AssetName + "_" + InLandscapeProxy->GetName() + ".png"; - - InLandscapeInfo->ExportHeightmap(FullPath); - - // TODO: - // We should update this to have an asset/package.. - } - break; - case EHoudiniLandscapeOutputBakeType::BakeToWorld: - { - ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) - return nullptr; - - // 0. Get Landscape Data // - - // Extract landscape height data - TArray InLandscapeHeightData; - int32 XSize, YSize; - FVector Min, Max; - if (!FUnrealLandscapeTranslator::GetLandscapeData(InLandscapeProxy, InLandscapeHeightData, XSize, YSize, Min, Max)) - return nullptr; - - // Extract landscape Layers data - TArray InLandscapeImportLayerInfos; - for (int32 n = 0; n < InLandscapeInfo->Layers.Num(); ++n) - { - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - FLandscapeImportLayerInfo CurrentLayerInfo; - CurrentLayerInfo.LayerName = FName(LayerName); - CurrentLayerInfo.LayerInfo = InLandscapeInfo->Layers[n].LayerInfoObj; - CurrentLayerInfo.LayerData = CurrentLayerIntData; - - CurrentLayerInfo.LayerInfo->LayerUsageDebugColor = LayerUsageDebugColor; - - InLandscapeImportLayerInfos.Add(CurrentLayerInfo); - } - - // 1. Create package // - - FString PackagePath = PackageParams.GetPackagePath(); - FString PackageName = PackageParams.GetPackageName(); - - UPackage *CreatedPackage = nullptr; - FString CreatedPackageName; - - CreatedPackage = PackageParams.CreatePackageForObject(CreatedPackageName); - - if (!CreatedPackage) - return nullptr; - - // 2. Create a new world asset with dialog // - UWorldFactory* Factory = NewObject(); - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - UObject* Asset = AssetToolsModule.Get().CreateAssetWithDialog( - PackageName, PackagePath, - UWorld::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - - - UWorld* NewWorld = Cast(Asset); - if (!NewWorld) - return nullptr; - - NewWorld->SetCurrentLevel(NewWorld->PersistentLevel); - - // 4. Spawn a landscape proxy actor in the created world - ALandscapeStreamingProxy * BakedLandscapeProxy = NewWorld->SpawnActor(); - if (!BakedLandscapeProxy) - return nullptr; - - // Create a new GUID - FGuid currentGUID = FGuid::NewGuid(); - BakedLandscapeProxy->SetLandscapeGuid(currentGUID); - - // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue - BakedLandscapeProxy->bCastStaticShadow = false; - - - // 5. Import data to the created landscape proxy - TMap> HeightmapDataPerLayers; - TMap> MaterialLayerDataPerLayer; - - HeightmapDataPerLayers.Add(FGuid(), InLandscapeHeightData); - MaterialLayerDataPerLayer.Add(FGuid(), InLandscapeImportLayerInfos); - - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - - BakedLandscapeProxy->Import( - currentGUID, - 0, 0, XSize-1, YSize-1, - InLandscapeInfo->ComponentNumSubsections, InLandscapeInfo->SubsectionSizeQuads, - HeightmapDataPerLayers, NULL, - MaterialLayerDataPerLayer, ImportLayerType); - - BakedLandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); - - - if (BakedLandscapeProxy->LandscapeMaterial) - BakedLandscapeProxy->LandscapeMaterial = InLandscapeProxy->LandscapeMaterial; - - if (BakedLandscapeProxy->LandscapeHoleMaterial) - BakedLandscapeProxy->LandscapeHoleMaterial = InLandscapeProxy->LandscapeHoleMaterial; - - // 6. Register all the landscape components, and set landscape actor transform - BakedLandscapeProxy->RegisterAllComponents(); - BakedLandscapeProxy->SetActorTransform(InLandscapeProxy->GetTransform()); - - // 7. Save Package - TArray PackagesToSave; - PackagesToSave.Add(CreatedPackage); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor) - { - TArray Objects; - Objects.Add(NewWorld); - GEditor->SyncBrowserToObjects(Objects); - } - } - break; - } - - return InLandscapeProxy; -} - -bool -FHoudiniEngineBakeUtils::BakeCurve( - USplineComponent* InSplineComponent, - ULevel* InLevel, - const FHoudiniPackageParams &PackageParams, - AActor*& OutActor, - USplineComponent*& OutSplineComponent, - FName InOverrideFolderPath, - AActor* InActor) -{ - if (!IsValid(InActor)) - { - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; - if (!Factory) - return false; - - OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform(), RF_Transactional); - } - else - { - OutActor = InActor; - } - - // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FName BaseActorName(PackageParams.ObjectName); - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName.ToString(), OutActor); - RenameAndRelabelActor(OutActor, NewNameStr, false); - OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); - - USplineComponent* DuplicatedSplineComponent = DuplicateObject( - InSplineComponent, - OutActor, - FName(MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), PackageParams.ObjectName))); - OutActor->AddInstanceComponent(DuplicatedSplineComponent); - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); - DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - - // We duplicated the InSplineComponent, so we don't have to copy all of its properties, but we must set the - // world transform - DuplicatedSplineComponent->SetWorldTransform(InSplineComponent->GetComponentTransform()); - - FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent); - DuplicatedSplineComponent->RegisterComponent(); - - OutSplineComponent = DuplicatedSplineComponent; - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeCurve( - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - // const TArray& InAllBakedOutputs, - const FHoudiniPackageParams &PackageParams, - FHoudiniAttributeResolver& InResolver, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - USplineComponent* SplineComponent = Cast(InOutputObject.OutputComponent); - if (!IsValid(SplineComponent)) - return false; - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = InResolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a new level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if(!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - // If we are baking in replace mode, remove the previous bake component - if (bInReplaceAssets && !InBakedOutputObject.BakedComponent.IsEmpty()) - { - UActorComponent* PrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (PrevComponent && PrevComponent->GetOwner() == FoundActor) - { - RemovePreviouslyBakedComponent(PrevComponent); - } - } - - FHoudiniPackageParams CurvePackageParams = PackageParams; - CurvePackageParams.ObjectName = BakeActorName.ToString(); - USplineComponent* NewSplineComponent = nullptr; - const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName)); - if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor)) - return false; - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - InBakedOutputObject.BakedComponent = FSoftObjectPath(NewSplineComponent).ToString(); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - FHoudiniEngineBakedActor Result; - Result.Actor = FoundActor; - Result.ActorBakeName = BakeActorName; - Result.BakeFolderPath = PackageParams.BakeFolder; - Result.BakedObjectPackageParams = PackageParams; - OutActors.Add(Result); - - return true; -} - -AActor* -FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PackageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform) -{ - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) - return nullptr; - - TArray & DisplayPoints = InHoudiniSplineComponent->DisplayPoints; - if (DisplayPoints.Num() < 2) - return nullptr; - - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; - if (!Factory) - return nullptr; - - // Remove the actor if it exists - for (auto & Actor : DesiredLevel->Actors) - { - if (!Actor) - continue; - - if (Actor->GetName() == PackageParams.ObjectName) - { - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - Actor->RemoveFromRoot(); - Actor->ConditionalBeginDestroy(); - World->EditorDestroyActor(Actor, true); - - break; - } - } - - AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform(), RF_Transactional); - - USplineComponent* BakedUnrealSplineComponent = NewObject(NewActor); - if (!BakedUnrealSplineComponent) - return nullptr; - - // add display points to created unreal spline component - for (int32 n = 0; n < DisplayPoints.Num(); ++n) - { - FVector & NextPoint = DisplayPoints[n]; - BakedUnrealSplineComponent->AddSplinePoint(NextPoint, ESplineCoordinateSpace::Local); - // Set the curve point type to be linear, since we are using display points - BakedUnrealSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - NewActor->AddInstanceComponent(BakedUnrealSplineComponent); - - BakedUnrealSplineComponent->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); - - FAssetRegistryModule::AssetCreated(NewActor); - FAssetRegistryModule::AssetCreated(BakedUnrealSplineComponent); - BakedUnrealSplineComponent->RegisterComponent(); - - // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); - RenameAndRelabelActor(NewActor, NewNameStr, false); - NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName)); - - return NewActor; -} - -UBlueprint* -FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PackageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform) -{ - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) - return nullptr; - - FGuid BakeGUID = FGuid::NewGuid(); - - if (!BakeGUID.IsValid()) - BakeGUID = FGuid::NewGuid(); - - // We only want half of generated guid string. - FString BakeGUIDString = BakeGUID.ToString().Left(FHoudiniEngineUtils::PackageGUIDItemNameLength); - - // Generate Blueprint name. - FString BlueprintName = PackageParams.ObjectName + "_BP"; - - // Generate unique package name. - FString PackageName = PackageParams.BakeFolder + "/" + BlueprintName; - PackageName = UPackageTools::SanitizePackageName(PackageName); - - // See if package exists, if it does, we need to regenerate the name. - UPackage * Package = FindPackage(nullptr, *PackageName); - - if (Package && !Package->IsPendingKill()) - { - // Package does exist, there's a collision, we need to generate a new name. - BakeGUID.Invalidate(); - } - else - { - // Create actual package. - Package = CreatePackage(*PackageName); - } - - AActor * CreatedHoudiniSplineActor = FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - InHoudiniSplineComponent, PackageParams, WorldToSpawn, SpawnTransform); - - TArray PackagesToSave; - - UBlueprint * Blueprint = nullptr; - if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill()) - { - - UObject* Asset = nullptr; - - Asset = StaticFindObjectFast(UObject::StaticClass(), Package, FName(*BlueprintName)); - if (!Asset) - { - UBlueprintFactory* Factory = NewObject(); - - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - Asset = AssetToolsModule.Get().CreateAsset( - BlueprintName, PackageParams.BakeFolder, - UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - } - - TArray Components; - for (auto & Next : CreatedHoudiniSplineActor->GetComponents()) - { - Components.Add(Next); - } - - Blueprint = Cast(Asset); - - // Clear old Blueprint Node tree - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - - int32 NodeSize = SCS->GetAllNodes().Num(); - for (int32 n = NodeSize - 1; n >= 0; --n) - SCS->RemoveNode(SCS->GetAllNodes()[n]); - - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components); - - CreatedHoudiniSplineActor->RemoveFromRoot(); - CreatedHoudiniSplineActor->ConditionalBeginDestroy(); - - GWorld->EditorDestroyActor(CreatedHoudiniSplineActor, true); - - Package->MarkPackageDirty(); - PackagesToSave.Add(Package); - } - - // Save the created BP package. - FHoudiniEngineBakeUtils::SaveBakedPackages - (PackagesToSave); - - return Blueprint; -} - - -void -FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const TCHAR * Key, - const TCHAR * Value) -{ - if (!Package || Package->IsPendingKill()) - return; - - UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) - MetaData->SetValue(Object, Key, Value); -} - - -bool -FHoudiniEngineBakeUtils:: -GetHoudiniGeneratedNameFromMetaInformation( - UPackage * Package, UObject * Object, FString & HoudiniName) -{ - if (!Package || Package->IsPendingKill()) - return false; - - UMetaData * MetaData = Package->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return false; - - if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - { - // Retrieve name used for package generation. - const FString NameFull = MetaData->GetValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME); - - //HoudiniName = NameFull.Left(FMath::Min(NameFull.Len(), FHoudiniEngineUtils::PackageGUIDItemNameLength)); - HoudiniName = NameFull; - return true; - } - - return false; -} - -UMaterial * -FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - UMaterial * Material, UMaterial* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutGeneratedPackages) -{ - UMaterial * DuplicatedMaterial = nullptr; - - FString CreatedMaterialName; - // Create material package. Use the same package params as static mesh, but with the material's name - FHoudiniPackageParams MaterialPackageParams = ObjectPackageParams; - MaterialPackageParams.ObjectName = MaterialName; - - // Check if there is a valid previous material. If so, get the bake counter for consistency in - // replace or iterative package naming - bool bIsPreviousBakeMaterialValid = IsValid(PreviousBakeMaterial); - int32 BakeCounter = 0; - TArray PreviousBakeMaterialExpressions; - if (bIsPreviousBakeMaterialValid) - { - bIsPreviousBakeMaterialValid = MaterialPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeMaterial); - if (bIsPreviousBakeMaterialValid) - { - MaterialPackageParams.GetBakeCounterFromBakedAsset(PreviousBakeMaterial, BakeCounter); - PreviousBakeMaterialExpressions = PreviousBakeMaterial->Expressions; - } - } - - UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter); - - if (!MaterialPackage || MaterialPackage->IsPendingKill()) - return nullptr; - - // Clone material. - DuplicatedMaterial = DuplicateObject< UMaterial >(Material, MaterialPackage, *CreatedMaterialName); - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, DuplicatedMaterial, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, DuplicatedMaterial, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedMaterialName); - - // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. - const int32 NumExpressions = DuplicatedMaterial->Expressions.Num(); - for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx) - { - UMaterialExpression* Expression = DuplicatedMaterial->Expressions[ExpressionIdx]; - UMaterialExpression* PreviousBakeExpression = nullptr; - if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx)) - { - PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; - } - FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( - Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages); - } - - // Notify registry that we have created a new duplicate material. - FAssetRegistryModule::AssetCreated(DuplicatedMaterial); - - // Dirty the material package. - DuplicatedMaterial->MarkPackageDirty(); - - // Recompile the baked material - // DuplicatedMaterial->ForceRecompileForRendering(); - // Use UMaterialEditingLibrary::RecompileMaterial since it correctly updates texture references in the material - // which ForceRecompileForRendering does not do - UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterial); - - OutGeneratedPackages.Add(MaterialPackage); - - return DuplicatedMaterial; -} - -void -FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( - UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, - const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages) -{ - UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression); - if (!TextureSample || TextureSample->IsPendingKill()) - return; - - UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture); - if (!Texture || Texture->IsPendingKill()) - return; - - UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) - return; - - // Try to get the previous bake's texture - UTexture2D* PreviousBakeTexture = nullptr; - if (IsValid(PreviousBakeMaterialExpression)) - { - UMaterialExpressionTextureSample* PreviousBakeTextureSample = Cast< UMaterialExpressionTextureSample >(PreviousBakeMaterialExpression); - if (IsValid(PreviousBakeTextureSample)) - PreviousBakeTexture = Cast< UTexture2D >(PreviousBakeTextureSample->Texture); - } - - FString GeneratedTextureName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( - TexturePackage, Texture, GeneratedTextureName)) - { - // Duplicate texture. - UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( - Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages); - - // Re-assign generated texture. - TextureSample->Texture = DuplicatedTexture; - } -} - -UTexture2D * -FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( - UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages) -{ - UTexture2D* DuplicatedTexture = nullptr; -#if WITH_EDITOR - // Retrieve original package of this texture. - UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) - return nullptr; - - FString GeneratedTextureName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName)) - { - UMetaData * MetaData = TexturePackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return nullptr; - - // Retrieve texture type. - const FString & TextureType = - MetaData->GetValue(Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); - - FString CreatedTextureName; - - // Create texture package. Use the same package params as material's, but with object name appended by generated texture's name - FHoudiniPackageParams TexturePackageParams = PackageParams; - TexturePackageParams.ObjectName = TexturePackageParams.ObjectName + "_" + GeneratedTextureName; - - // Determine the bake counter of the previous bake's texture (if exists/valid) for naming consistency when - // replacing/iterating - bool bIsPreviousBakeTextureValid = IsValid(PreviousBakeTexture); - int32 BakeCounter = 0; - if (bIsPreviousBakeTextureValid) - { - bIsPreviousBakeTextureValid = TexturePackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeTexture); - if (bIsPreviousBakeTextureValid) - { - TexturePackageParams.GetBakeCounterFromBakedAsset(PreviousBakeTexture, BakeCounter); - } - } - - UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter); - - if (!NewTexturePackage || NewTexturePackage->IsPendingKill()) - return nullptr; - - // Clone texture. - DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName); - if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedTextureName); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); - - // Notify registry that we have created a new duplicate texture. - FAssetRegistryModule::AssetCreated(DuplicatedTexture); - - // Dirty the texture package. - DuplicatedTexture->MarkPackageDirty(); - - OutCreatedPackages.Add(NewTexturePackage); - } -#endif - return DuplicatedTexture; -} - - -bool -FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); - - if (!ActorOwner || ActorOwner->IsPendingKill()) - return false; - - UWorld* World = ActorOwner->GetWorld(); - if (!World) - World = GWorld; - - World->EditorDestroyActor(ActorOwner, false); - - return true; -} - - -void -FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld) -{ - UWorld * CurrentWorld = nullptr; - if (bSaveCurrentWorld && GEditor) - CurrentWorld = GEditor->GetEditorWorldContext().World(); - - if (CurrentWorld) - { - // Save the current map - FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); - UPackage* CurrentWorldPackage = CreatePackage(*CurrentWorldPath); - - if (CurrentWorldPackage) - { - CurrentWorldPackage->MarkPackageDirty(); - PackagesToSave.Add(CurrentWorldPackage); - } - } - - FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); -} - -bool -FHoudiniEngineBakeUtils::FindOutputObject(const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) -{ - if (!InObjectToFind || InObjectToFind->IsPendingKill()) - return false; - - const int32 NumOutputs = InOutputs.Num(); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - const UHoudiniOutput* CurOutput = InOutputs[OutputIdx]; - if (!IsValid(CurOutput)) - continue; - - if (CurOutput->GetType() != InOutputType) - continue; - - for (auto& CurOutputObject : CurOutput->GetOutputObjects()) - { - if (CurOutputObject.Value.OutputObject == InObjectToFind - || CurOutputObject.Value.OutputComponent == InObjectToFind - || CurOutputObject.Value.ProxyObject == InObjectToFind - || CurOutputObject.Value.ProxyComponent == InObjectToFind) - { - OutOutputIndex = OutputIdx; - OutIdentifier = CurOutputObject.Key; - return true; - } - } - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - FString TempPath = FString(); - - // TODO: Get the HAC outputs in a better way? - TArray Outputs; - if (InHAC && !InHAC->IsPendingKill()) - { - const int32 NumOutputs = InHAC->GetNumOutputs(); - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - Outputs.Add(InHAC->GetOutputAt(OutputIdx)); - } - - TempPath = InHAC->TemporaryCookFolder.Path; - } - - return IsObjectTemporary(InObject, InOutputType, Outputs, TempPath); -} - -bool FHoudiniEngineBakeUtils::IsObjectTemporary( - UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - int32 ParentOutputIndex = -1; - FHoudiniOutputObjectIdentifier Identifier; - if (FindOutputObject(InObject, InOutputType, InParentOutputs, ParentOutputIndex, Identifier)) - return true; - - // Check the package path for this object - // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated - UPackage* ObjectPackage = InObject->GetOutermost(); - if (ObjectPackage && !ObjectPackage->IsPendingKill()) - { - const FString PathName = ObjectPackage->GetPathName(); - if (PathName.StartsWith(InTemporaryCookFolder)) - return true; - - // Also check the default temp folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder)) - return true; - - /* - // TODO: this just indicates that the object was generated by H - // it could as well have been baked before... - // we should probably add a "temp" metadata - // Look in the meta info as well?? - UMetaData * MetaData = ObjectPackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return false; - - if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - return true; - */ - } - - return false; -} - -void -FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent( - AActor* NewActor, - UStaticMeshComponent* NewSMC, - UStaticMeshComponent* InSMC, - bool bInCopyWorldTransform) -{ - if (!NewSMC || NewSMC->IsPendingKill()) - return; - - if (!InSMC || InSMC->IsPendingKill()) - return; - - // Copy properties to new actor - //UStaticMeshComponent* OtherSMC_NonConst = const_cast(StaticMeshComponent); - NewSMC->SetCollisionProfileName(InSMC->GetCollisionProfileName()); - NewSMC->SetCollisionEnabled(InSMC->GetCollisionEnabled()); - NewSMC->LightmassSettings = InSMC->LightmassSettings; - NewSMC->CastShadow = InSMC->CastShadow; - NewSMC->SetMobility(InSMC->Mobility); - - UBodySetup* InBodySetup = InSMC->GetBodySetup(); - UBodySetup* NewBodySetup = NewSMC->GetBodySetup(); - if (InBodySetup && NewBodySetup) - { - // Copy the BodySetup - NewBodySetup->CopyBodyPropertiesFrom(InBodySetup); - - // We need to recreate the physics mesh for the new body setup - NewBodySetup->InvalidatePhysicsData(); - NewBodySetup->CreatePhysicsMeshes(); - - // Only copy the physical material if it's different from the default one, - // As this was causing crashes on BakeToActors in some cases - if (GEngine != NULL && NewBodySetup->GetPhysMaterial() != GEngine->DefaultPhysMaterial) - NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial()); - } - - if (NewActor && !NewActor->IsPendingKill()) - NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame); - - NewSMC->SetVisibility(InSMC->IsVisible()); - - // TODO: - // // Reapply the uproperties modified by attributes on the new component - // FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO); - - // The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another - UClass* ComponentClass = nullptr; - if (InSMC->GetClass()->IsChildOf(NewSMC->GetClass())) - { - ComponentClass = NewSMC->GetClass(); - } - else if (NewSMC->GetClass()->IsChildOf(InSMC->GetClass())) - { - ComponentClass = InSMC->GetClass(); - } - else - { - HOUDINI_LOG_WARNING( - TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), - *(ComponentClass->GetName()), - *(NewSMC->GetClass()->GetName())); - - NewSMC->PostEditChange(); - return; - } - - TSet SourceUCSModifiedProperties; - InSMC->GetUCSModifiedProperties(SourceUCSModifiedProperties); - - AActor* SourceActor = InSMC->GetOwner(); - if (!IsValid(SourceActor)) - { - NewSMC->PostEditChange(); - return; - } - - TArray ModifiedObjects; - const EditorUtilities::FCopyOptions Options(EditorUtilities::ECopyOptions::CallPostEditChangeProperty); - // Copy component properties - for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) - { - const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); - const bool bIsIdentical = Property->Identical_InContainer( InSMC, NewSMC ); - const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); - const bool bIsTransform = - Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); - - // auto SourceComponentIsRoot = [&]() - // { - // USceneComponent* RootComponent = SourceActor->GetRootComponent(); - // if (InSMC == RootComponent) - // { - // return true; - // } - // return false; - // }; - - // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - // && ( !bIsTransform || !SourceComponentIsRoot() ) ) - if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - && !bIsTransform ) - { - // const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) - // && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); - const bool bIsSafeToCopy = true; - if( bIsSafeToCopy ) - { - if (!Options.CanCopyProperty(*Property, *SourceActor)) - { - continue; - } - - if( !ModifiedObjects.Contains(NewSMC) ) - { - NewSMC->SetFlags(RF_Transactional); - NewSMC->Modify(); - ModifiedObjects.Add(NewSMC); - } - - if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) - { - // @todo simulate: Should we be calling this on the component instead? - NewActor->PreEditChange( Property ); - } - - EditorUtilities::CopySingleProperty(InSMC, NewSMC, Property); - - if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) - { - FPropertyChangedEvent PropertyChangedEvent( Property ); - NewActor->PostEditChangeProperty( PropertyChangedEvent ); - } - } - } - } - - if (bInCopyWorldTransform) - { - NewSMC->SetWorldTransform(InSMC->GetComponentTransform()); - } - - NewSMC->PostEditChange(); -}; - -bool -FHoudiniEngineBakeUtils::RemovePreviouslyBakedActor( - AActor* InNewBakedActor, - ULevel* InLevel, - const FHoudiniPackageParams& InPackageParams) -{ - // Remove a previous bake actor if it exists - for (auto & Actor : InLevel->Actors) - { - if (!Actor) - continue; - - if (Actor != InNewBakedActor && Actor->GetName() == InPackageParams.ObjectName) - { - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - Actor->RemoveFromRoot(); - Actor->ConditionalBeginDestroy(); - World->EditorDestroyActor(Actor, true); - - return true; - } - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::RemovePreviouslyBakedComponent(UActorComponent* InComponent) -{ - if (!IsValid(InComponent)) - return false; - - // Remove from its actor first - if (InComponent->GetOwner()) - InComponent->GetOwner()->RemoveOwnedComponent(InComponent); - - // Detach from its parent component if attached - USceneComponent* SceneComponent = Cast(InComponent); - if (IsValid(SceneComponent)) - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - InComponent->UnregisterComponent(); - InComponent->DestroyComponent(); - - return true; -} - -FName -FHoudiniEngineBakeUtils::GetOutputFolderPath(UObject* InOutputOwner) -{ - // Get an output folder path for PDG outputs generated by InOutputOwner. - // The folder path is: / - FString FolderName; - FName FolderDirName; - AActor* OuterActor = Cast(InOutputOwner); - if (OuterActor) - { - FolderName = OuterActor->GetActorLabel(); - FolderDirName = OuterActor->GetFolderPath(); - } - else - { - FolderName = InOutputOwner->GetName(); - } - if (!FolderDirName.IsNone()) - return FName(FString::Printf(TEXT("%s/%s"), *FolderDirName.ToString(), *FolderName)); - else - return FName(FolderName); -} - -void -FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique) -{ - FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); - - const FSoftObjectPath OldPath = FSoftObjectPath(InAsset); - - FString NewName; - if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetPackage(), InAsset->GetClass(), InNewName, InAsset); - else - NewName = InNewName; - - FHoudiniEngineUtils::RenameObject(InAsset, *NewName); - - const FSoftObjectPath NewPath = FSoftObjectPath(InAsset); - if (OldPath != NewPath) - { - TArray RenameData; - RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); - AssetToolsModule.Get().RenameAssets(RenameData); - } -} - -void -FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique) -{ - if (!IsValid(InActor)) - return; - - FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); - - const FSoftObjectPath OldPath = FSoftObjectPath(InActor); - - FString NewName; - if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), InNewName, InActor); - else - NewName = InNewName; - - FHoudiniEngineUtils::RenameObject(InActor, *NewName); - FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName); - - const FSoftObjectPath NewPath = FSoftObjectPath(InActor); - if (OldPath != NewPath) - { - TArray RenameData; - RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); - AssetToolsModule.Get().RenameAssets(RenameData); - } -} - -bool -FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor( - AActor* InActor, - const FString& InNewName, - const FName& InFolderPath) -{ - if (!IsValid(InActor)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: InActor is null.")); - return false; - } - - if (InNewName.TrimStartAndEnd().IsEmpty()) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: A valid actor name must be specified.")); - return false; - } - - // Detach from parent - InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - // Rename - const bool bMakeUniqueIfNotUnique = true; - RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique); - - InActor->SetFolderPath(InFolderPath); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultArrayIndex, - int32 InWorkResultObjectArrayIndex, - bool bInReplaceActors, - bool bInReplaceAssets, - bool bInBakeToWorkResultActor, - bool bInIsAutoBake, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!IsValid(InPDGAssetLink)) - return false; - - if (!IsValid(InNode)) - return false; - - if (!InNode->WorkResult.IsValidIndex(InWorkResultArrayIndex)) - return false; - - FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultArrayIndex]; - if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) - return false; - - FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectArrayIndex]; - TArray& Outputs = WorkResultObject.GetResultOutputs(); - if (Outputs.Num() == 0) - return true; - - if (WorkResultObject.State != EPDGWorkResultState::Loaded) - { - if (bInIsAutoBake && WorkResultObject.AutoBakedSinceLastLoad()) - { - HOUDINI_LOG_MESSAGE(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded but was auto-baked since its last load."), *WorkResultObject.Name); - return true; - } - - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded, cannot bake it."), *WorkResultObject.Name); - return false; - } - - AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor(); - if (!IsValid(WorkResultObjectActor)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObjectActor (%s) is null (unexpected since # Outputs > 0)"), *WorkResultObject.Name); - return false; - } - - // OutBakedActors contains each actor that contains baked PDG results. Actors may - // appear in the array more than once if they have more than one baked result/component associated with - // them - // TArray BakedActorsForWorkResultObject; - const int32 NumBakedPre = OutBakedActors.Num(); - const FString HoudiniAssetName(WorkResultObject.Name); - - // Find the previous bake output for this work result object - FString Key; - InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key); - FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key); - - const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InPDGAssetLink); - check(IsValid(HoudiniAssetComponent)); - - BakeHoudiniOutputsToActors( - HoudiniAssetComponent, - Outputs, - BakedOutputContainer.BakedOutputs, - HoudiniAssetName, - WorkResultObjectActor->GetActorTransform(), - InPDGAssetLink->BakeFolder, - InPDGAssetLink->GetTemporaryCookFolder(), - bInReplaceActors, - bInReplaceAssets, - OutBakedActors, - OutPackagesToSave, - OutBakeStats, - InOutputTypesToBake, - InInstancerComponentTypesToBake, - bInBakeToWorkResultActor ? WorkResultObjectActor : nullptr, - InFallbackWorldOutlinerFolder); - - // Set the PDG indices on the output baked actor entries - FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); - AActor* const WROActor = OutputActorOwner.GetOutputActor(); - FHoudiniEngineBakedActor const * BakedWROActorEntry = nullptr; - const int32 NumBakedPost = OutBakedActors.Num(); - if (NumBakedPost > NumBakedPre) - { - for (int32 Index = FMath::Max(0, NumBakedPre); Index < NumBakedPost; ++Index) - { - FHoudiniEngineBakedActor& BakedActorEntry = OutBakedActors[Index]; - BakedActorEntry.PDGWorkResultArrayIndex = InWorkResultArrayIndex; - BakedActorEntry.PDGWorkItemIndex = WorkResult.WorkItemIndex; - BakedActorEntry.PDGWorkResultObjectArrayIndex = InWorkResultObjectArrayIndex; - - if (WROActor && BakedActorEntry.Actor == WROActor) - { - BakedWROActorEntry = &BakedActorEntry; - } - } - } - - // If anything was baked to WorkResultObjectActor, detach it from its parent - if (bInBakeToWorkResultActor) - { - // if we re-used the temp actor as a bake actor, then remove its temp outputs - WorkResultObject.DestroyResultOutputs(); - if (WROActor) - { - if (BakedWROActorEntry) - { - OutputActorOwner.SetOutputActor(nullptr); - const FString OldActorPath = FSoftObjectPath(WROActor).ToString(); - DetachAndRenameBakedPDGOutputActor( - WROActor, BakedWROActorEntry->ActorBakeName.ToString(), BakedWROActorEntry->WorldOutlinerFolder); - const FString NewActorPath = FSoftObjectPath(WROActor).ToString(); - if (OldActorPath != NewActorPath) - { - // Fix cached string reference in baked outputs to WROActor - for (FHoudiniBakedOutput& BakedOutput : BakedOutputContainer.BakedOutputs) - { - for (auto& Entry : BakedOutput.BakedOutputObjects) - { - if (Entry.Value.Actor == OldActorPath) - Entry.Value.Actor = NewActorPath; - } - } - } - } - else - { - OutputActorOwner.DestroyOutputActor(); - } - } - } - - if (bInIsAutoBake) - WorkResultObject.SetAutoBakedSinceLastLoad(true); - // OutBakedActors.Append(BakedActorsForWorkResultObject); - return true; -} - -void -FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkItemHAPIIndex, - int32 InWorkItemResultInfoIndex) -{ - if (!IsValid(InPDGAssetLink)) - return; - - if (!InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded) - return; - - if (!IsValid(InNode)) - return; - - // Check if the node is ready for baking: all work items must be complete - if (!InNode->AreAllWorkItemsComplete() || InNode->AnyWorkItemsFailed()) - return; - - // Check if the node is ready for baking: all work items must be loaded - for (const FTOPWorkResult& WorkResult : InNode->WorkResult) - { - for (const FTOPWorkResultObject& WRO : WorkResult.ResultObjects) - { - if (WRO.State != EPDGWorkResultState::Loaded && !WRO.AutoBakedSinceLastLoad()) - return; - } - } - - // Check which outputs are selected for baking: selected node, selected network or all - // And only bake if the node falls within the criteria - UTOPNetwork const * const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - UTOPNode const * const SelectedTOPNode = InPDGAssetLink->GetSelectedTOPNode(); - switch (InPDGAssetLink->PDGBakeSelectionOption) - { - case EPDGBakeSelectionOption::SelectedNetwork: - if (!IsValid(SelectedTOPNetwork) || !InNode->IsParentTOPNetwork(SelectedTOPNetwork)) - { - HOUDINI_LOG_WARNING( - TEXT("Not baking Node %s (Net %s): not in selected network"), - InNode ? *InNode->GetName() : TEXT(""), - SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); - return; - } - break; - case EPDGBakeSelectionOption::SelectedNode: - if (InNode != SelectedTOPNode) - { - HOUDINI_LOG_WARNING( - TEXT("Not baking Node %s (Net %s): not the selected node"), - InNode ? *InNode->GetName() : TEXT(""), - SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); - return; - } - break; - case EPDGBakeSelectionOption::All: - default: - break; - } - - const bool bIsAutoBake = true; - switch (InPDGAssetLink->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InNode, bIsAutoBake); - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(InPDGAssetLink, InNode, bIsAutoBake); - break; - - default: - HOUDINI_LOG_WARNING(TEXT("Unsupported HoudiniEngineBakeOption %i"), InPDGAssetLink->HoudiniEngineBakeOption); - } -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInBakeForBlueprint, - bool bInIsAutoBake, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNode)) - return false; - - // Determine the output world outliner folder path via the PDG asset link's - // owner's folder path and name - UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); - if (!PDGOwner) - PDGOwner = InPDGAssetLink->GetOuter(); - const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); - - // Determine the actor/package replacement settings - const bool bReplaceActors = !bInBakeForBlueprint && InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Determine the output types to bake: don't bake landscapes in blueprint baking mode - TArray OutputTypesToBake; - TArray InstancerComponentTypesToBake; - if (bInBakeForBlueprint) - { - OutputTypesToBake.Add(EHoudiniOutputType::Mesh); - OutputTypesToBake.Add(EHoudiniOutputType::Instancer); - OutputTypesToBake.Add(EHoudiniOutputType::Curve); - - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent); - } - - const int32 NumWorkResults = InNode->WorkResult.Num(); - FScopedSlowTask Progress(NumWorkResults, FText::FromString(FString::Printf(TEXT("Baking PDG Node Output %s ..."), *InNode->GetName()))); - Progress.MakeDialog(); - for (int32 WorkResultArrayIdx = 0; WorkResultArrayIdx < NumWorkResults; ++WorkResultArrayIdx) - { - FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultArrayIdx]; - const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); - for (int32 WorkResultObjectArrayIdx = 0; WorkResultObjectArrayIdx < NumWorkResultObjects; ++WorkResultObjectArrayIdx) - { - Progress.EnterProgressFrame(1.0f); - - BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - WorkResultArrayIdx, - WorkResultObjectArrayIdx, - bReplaceActors, - bReplaceAssets, - !bInBakeForBlueprint, - bInIsAutoBake, - OutBakedActors, - OutPackagesToSave, - OutBakeStats, - OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, - InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, - FallbackWorldOutlinerFolderPath.ToString() - ); - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake) -{ - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - TArray BakedActors; - - const bool bBakeBlueprints = false; - - bool bSuccess = BakePDGTOPNodeOutputsKeepActors( - InPDGAssetLink, InTOPNode, bBakeBlueprints, bInIsAutoBake, BakedActors, PackagesToSave, BakeStats); - - SaveBakedPackages(PackagesToSave); - - // Recenter and select the baked actors - if (GEditor && BakedActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : BakedActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (InPDGAssetLink->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && BakedActors.Num() > 0) - GEditor->NoteSelectionChange(); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - bool bInBakeForBlueprint, - bool bInIsAutoBake, - TArray& BakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNetwork)) - return false; - - bool bSuccess = true; - for (UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, bInIsAutoBake, BakedActors, OutPackagesToSave, OutBakeStats); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - TArray BakedActors; - - const bool bBakeBlueprints = false; - const bool bIsAutoBake = false; - - bool bSuccess = true; - switch(InPDGAssetLink->PDGBakeSelectionOption) - { - case EPDGBakeSelectionOption::All: - for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) - { - if (!IsValid(Network)) - continue; - - for (UTOPNode* Node : Network->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, bIsAutoBake, BakedActors, PackagesToSave, BakeStats); - } - } - break; - case EPDGBakeSelectionOption::SelectedNetwork: - bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, bIsAutoBake, BakedActors, PackagesToSave, BakeStats); - case EPDGBakeSelectionOption::SelectedNode: - bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, bIsAutoBake, BakedActors, PackagesToSave, BakeStats); - } - - SaveBakedPackages(PackagesToSave); - - // Recenter and select the baked actors - if (GEditor && BakedActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : BakedActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (InPDGAssetLink->bRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && BakedActors.Num() > 0) - GEditor->NoteSelectionChange(); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( - const TArray& InBakedActors, - bool bInRecenterBakedActors, - bool bInReplaceAssets, - const FString& InAssetName, - const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOutputs, - TMap* const InPDGBakedOutputs, - TArray& OutBlueprints, - TArray& OutPackagesToSave) -{ - // // Clear selection - // if (GEditor) - // { - // GEditor->SelectNone(false, true); - // GEditor->NoteSelectionChange(); - // } - - // Iterate over the baked actors. An actor might appear multiple times if multiple OutputComponents were - // baked to the same actor, so keep track of actors we have already processed in BakedActorSet - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem); - TArray AssetsToReOpenEditors; - TSet BakedActorSet; - - for (const FHoudiniEngineBakedActor& Entry : InBakedActors) - { - AActor *Actor = Entry.Actor; - - if (!Actor || Actor->IsPendingKill()) - continue; - - if (BakedActorSet.Contains(Actor)) - continue; - - BakedActorSet.Add(Actor); - - UObject* Asset = nullptr; - - // Recenter the actor to its bounding box center - if (bInRecenterBakedActors) - CenterActorToBoundingBoxCenter(Actor); - - // Create package for out Blueprint - FString BlueprintName; - - // For instancers we determine the bake folder from the instancer, - // for everything else we use the baked object's bake folder - // If all of that is blank, we fall back to InBakeFolder. - FString BakeFolderPath; - if (Entry.bInstancerOutput) - BakeFolderPath = Entry.InstancerPackageParams.BakeFolder; - else - BakeFolderPath = Entry.BakeFolderPath; - if (BakeFolderPath.IsEmpty()) - BakeFolderPath = InBakeFolder.Path; - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - FHoudiniOutputObjectIdentifier(), - BakeFolderPath, - Entry.ActorBakeName.ToString() + "_BP", - InAssetName, - AssetPackageReplaceMode); - - // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment - // is consistent with the bake counter - int32 BakeCounter = 0; - UBlueprint* InPreviousBlueprint = nullptr; - FHoudiniBakedOutputObject* BakedOutputObject = nullptr; - FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; - // Get the baked output object - if (Entry.PDGWorkResultArrayIndex >= 0 && Entry.PDGWorkItemIndex >= 0 && Entry.PDGWorkResultObjectArrayIndex >= 0 && InPDGBakedOutputs) - { - const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkItemIndex, Entry.PDGWorkResultObjectArrayIndex); - WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); - if (WorkResultObjectBakedOutput) - { - if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } - } - else if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs) - { - if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs->IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = (*InNonPDGBakedOutputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } - if (BakedOutputObject) - { - InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); - if (IsValid(InPreviousBlueprint)) - { - if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint)) - { - PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter); - } - } - } - - UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter); - - if (!Package || Package->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName())); - continue; - } - - if (!Package->IsFullyLoaded()) - Package->FullyLoad(); - - //Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false); - // Find existing asset first (only relevant if we are in replacement mode). If the existing asset has a - // different base class than the incoming actor, we reparent the blueprint to the new base class before - // clearing the SCS graph and repopulating it from the temp actor. - Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName)); - if (IsValid(Asset)) - { - UBlueprint* Blueprint = Cast(Asset); - if (IsValid(Blueprint)) - { - if (Blueprint->GeneratedClass && Blueprint->GeneratedClass != Actor->GetClass()) - { - // Close editors opened on existing asset if applicable - if (Asset && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Asset, false) != nullptr) - { - AssetEditorSubsystem->CloseAllEditorsForAsset(Asset); - AssetsToReOpenEditors.Add(Asset); - } - - Blueprint->ParentClass = Actor->GetClass(); - - FBlueprintEditorUtils::RefreshAllNodes(Blueprint); - FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); - FKismetEditorUtilities::CompileBlueprint(Blueprint); - } - } - } - else if (Asset && Asset->IsPendingKill()) - { - // Rename to pending kill so that we can use the desired name - const FString AssetPendingKillName(BlueprintName + "_PENDING_KILL"); - // Asset->Rename(*MakeUniqueObjectNameIfNeeded(Package, UBlueprint::StaticClass(), AssetPendingKillName).ToString()); - RenameAsset(Asset, AssetPendingKillName, true); - Asset = nullptr; - } - - if (!Asset) - { - UBlueprintFactory* Factory = NewObject(); - Factory->ParentClass = Actor->GetClass(); - - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - Asset = AssetToolsModule.Get().CreateAsset( - BlueprintName, PackageParams.GetPackagePath(), - UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - } - - UBlueprint* Blueprint = Cast(Asset); - - if (!Blueprint || Blueprint->IsPendingKill()) - { - HOUDINI_LOG_WARNING( - TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."), - *(InBakeFolder.Path), *BlueprintName); - - continue; - } - - // Close editors opened on existing asset if applicable - if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr) - { - AssetEditorSubsystem->CloseAllEditorsForAsset(Blueprint); - AssetsToReOpenEditors.Add(Blueprint); - } - - // Record the blueprint as the previous bake blueprint - if (BakedOutputObject) - BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString(); - - OutBlueprints.Add(Blueprint); - - // Clear old Blueprint Node tree - { - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - - int32 NodeSize = SCS->GetAllNodes().Num(); - for (int32 n = NodeSize - 1; n >= 0; --n) - SCS->RemoveNode(SCS->GetAllNodes()[n]); - } - - FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint); - - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - World->EditorDestroyActor(Actor, true); - - // Save the created BP package. - Package->MarkPackageDirty(); - OutPackagesToSave.Add(Package); - } - - // Re-open asset editors for updated blueprints that were open in editors - if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0) - { - for (UObject* Asset : AssetsToReOpenEditors) - { - if (IsValid(Asset)) - { - AssetEditorSubsystem->OpenEditorForAsset(Asset); - } - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInIsAutoBake, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - TArray BPActors; - - if (!IsValid(InPDGAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InPDGAssetLink is null")); - return false; - } - - if (!IsValid(InNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InNode is null")); - return false; - } - - const bool bReplaceAssets = InPDGAssetLink->PDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Bake PDG output to new actors - // bInBakeForBlueprint == true will skip landscapes and instanced actor components - const bool bInBakeForBlueprint = true; - TArray BakedActors; - bool bSuccess = BakePDGTOPNodeOutputsKeepActors( - InPDGAssetLink, - InNode, - bInBakeForBlueprint, - bInIsAutoBake, - BakedActors, - OutPackagesToSave, - OutBakeStats - ); - - if (bSuccess) - { - bSuccess = BakeBlueprintsFromBakedActors( - BakedActors, - InPDGAssetLink->bRecenterBakedActors, - bReplaceAssets, - InPDGAssetLink->AssetName, - InPDGAssetLink->BakeFolder, - nullptr, - &InNode->GetBakedWorkResultObjectsOutputs(), - OutBlueprints, - OutPackagesToSave); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake) -{ - TArray Blueprints; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - const bool bSuccess = BakePDGTOPNodeBlueprints( - InPDGAssetLink, - InTOPNode, - bInIsAutoBake, - Blueprints, - PackagesToSave, - BakeStats); - - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNetwork)) - return false; - - const bool bIsAutoBake = false; - bool bSuccess = true; - for (UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, OutBlueprints, OutPackagesToSave, OutBakeStats); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - TArray Blueprints; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - const bool bIsAutoBake = false; - bool bSuccess = true; - switch(InPDGAssetLink->PDGBakeSelectionOption) - { - case EPDGBakeSelectionOption::All: - for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) - { - if (!IsValid(Network)) - continue; - - for (UTOPNode* Node : Network->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, Blueprints, PackagesToSave, BakeStats); - } - } - break; - case EPDGBakeSelectionOption::SelectedNetwork: - bSuccess &= BakePDGTOPNetworkBlueprints( - InPDGAssetLink, - InPDGAssetLink->GetSelectedTOPNetwork(), - Blueprints, - PackagesToSave, - BakeStats); - case EPDGBakeSelectionOption::SelectedNode: - bSuccess &= BakePDGTOPNodeBlueprints( - InPDGAssetLink, - InPDGAssetLink->GetSelectedTOPNode(), - bIsAutoBake, - Blueprints, - PackagesToSave, - BakeStats); - } - - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - const FString& InLevelPath, - ULevel*& OutDesiredLevel, - UWorld*& OutDesiredWorld, - bool& OutCreatedPackage) -{ - OutDesiredLevel = nullptr; - OutDesiredWorld = nullptr; - if (InLevelPath.IsEmpty()) - { - OutDesiredWorld = GWorld; - OutDesiredLevel = GWorld->GetCurrentLevel(); - } - else - { - OutCreatedPackage = false; - - UWorld* FoundWorld = nullptr; - ULevel* FoundLevel = nullptr; - bool bActorInWorld = false; - if (FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - GWorld, - InLevelPath, - true, - FoundWorld, - FoundLevel, - OutCreatedPackage, - bActorInWorld)) - { - OutDesiredLevel = FoundLevel; - OutDesiredWorld = FoundWorld; - } - } - - return ((OutDesiredWorld != nullptr) && (OutDesiredLevel != nullptr)); -} - - -bool -FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( - const FString& InBakeActorName, - ULevel* InLevel, - AActor*& OutActor, - bool bInNoPendingKillActors, - bool bRenamePendingKillActor) -{ - OutActor = nullptr; - - if (!IsValid(InLevel)) - return false; - - UWorld* const World = InLevel->GetWorld(); - if (!IsValid(World)) - return false; - - // Look for an actor with the given name in the world - const FName BakeActorFName(InBakeActorName); - AActor* FoundActor = Cast(StaticFindObjectFast(AActor::StaticClass(), InLevel, BakeActorFName)); - // for (TActorIterator Iter(World, AActor::StaticClass(), EActorIteratorFlags::AllActors); Iter; ++Iter) - // { - // AActor* const Actor = *Iter; - // if (Actor->GetFName() == BakeActorFName && Actor->GetLevel() == InLevel) - // { - // // Found the actor - // FoundActor = Actor; - // break; - // } - // } - - // If we found an actor and it is pending kill, rename it and don't use it - if (FoundActor) - { - if (FoundActor->IsPendingKill()) - { - if (bRenamePendingKillActor) - { - // FoundActor->Rename( - // *MakeUniqueObjectNameIfNeeded( - // FoundActor->GetOuter(), - // FoundActor->GetClass(), - // FName(FoundActor->GetName() + "_Pending_Kill")).ToString()); - RenameAndRelabelActor( - FoundActor, - *MakeUniqueObjectNameIfNeeded( - FoundActor->GetOuter(), - FoundActor->GetClass(), - FoundActor->GetName() + "_Pending_Kill", - FoundActor), - false); - } - if (bInNoPendingKillActors) - FoundActor = nullptr; - else - OutActor = FoundActor; - } - else - { - OutActor = FoundActor; - } - } - - return true; -} - -bool FHoudiniEngineBakeUtils::FindUnrealBakeActor( - const FHoudiniOutputObject& InOutputObject, - const FHoudiniBakedOutputObject& InBakedOutputObject, - const TArray& InAllBakedActors, - ULevel* InLevel, - FName InDefaultActorName, - bool bInReplaceActorBakeMode, - AActor* InFallbackActor, - AActor*& OutFoundActor, - bool& bOutHasBakeActorName, - FName& OutBakeActorName) -{ - // Determine desired actor name via unreal_output_actor, fallback to InDefaultActorName - OutBakeActorName = NAME_None; - OutFoundActor = nullptr; - bOutHasBakeActorName = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_ACTOR); - if (bOutHasBakeActorName) - { - const FString& BakeActorNameStr = InOutputObject.CachedAttributes[HAPI_UNREAL_ATTRIB_BAKE_ACTOR]; - if (BakeActorNameStr.IsEmpty()) - { - OutBakeActorName = NAME_None; - bOutHasBakeActorName = false; - } - else - { - OutBakeActorName = FName(BakeActorNameStr, NAME_NO_NUMBER_INTERNAL); - // We have a bake actor name, look for the actor - AActor* BakeNameActor = nullptr; - if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor)) - { - // Found an actor with that name, check that we "own" it (we created in during baking previously) - AActor* IncrementedBakedActor = nullptr; - for (const FHoudiniEngineBakedActor& BakedActor : InAllBakedActors) - { - if (!IsValid(BakedActor.Actor)) - continue; - if (BakedActor.Actor == BakeNameActor) - { - OutFoundActor = BakeNameActor; - break; - } - else if (!IncrementedBakedActor && BakedActor.ActorBakeName == OutBakeActorName) - { - // Found an actor we have baked named OutBakeActorName_# (incremental version of our desired name) - IncrementedBakedActor = BakedActor.Actor; - } - } - if (!OutFoundActor && IncrementedBakedActor) - OutFoundActor = IncrementedBakedActor; - } - } - } - - // If unreal_actor_name is not set, or is blank, fallback to InDefaultActorName - if (!bOutHasBakeActorName || (OutBakeActorName.IsNone() || OutBakeActorName.ToString().TrimStartAndEnd().IsEmpty())) - OutBakeActorName = InDefaultActorName; - - if (!OutFoundActor) - { - // If in replace mode, use previous bake actor if valid and in InLevel - if (bInReplaceActorBakeMode) - { - const FSoftObjectPath PrevActorPath(InBakedOutputObject.Actor); - const FString ActorPath = PrevActorPath.IsSubobject() - ? PrevActorPath.GetAssetPathString() + ":" + PrevActorPath.GetSubPathString() - : PrevActorPath.GetAssetPathString(); - const FString LevelPath = IsValid(InLevel) ? InLevel->GetPathName() : ""; - if (PrevActorPath.IsValid() && (LevelPath.IsEmpty() || ActorPath.StartsWith(LevelPath))) - OutFoundActor = InBakedOutputObject.GetActorIfValid(); - } - - // Fallback to InFallbackActor if valid and in InLevel - if (!OutFoundActor && IsValid(InFallbackActor) && (!InLevel || InFallbackActor->GetLevel() == InLevel)) - OutFoundActor = InFallbackActor; - } - - return true; -} - -AActor* -FHoudiniEngineBakeUtils::FindExistingActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - bCreatedPackage = false; - - // Try to Locate a previous actor - AActor* FoundActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); - if (FoundActor) - FoundActor->Destroy(); // nuke it! - - if (FoundActor) - { - // TODO: make sure that the found is actor is actually assigned to the level defined by package path. - // If the found actor is not from that level, it should be moved there. - - OutWorld = FoundActor->GetWorld(); - OutLevel = FoundActor->GetLevel(); - } - else - { - // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. - bool bActorInWorld = false; - const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - InWorld, - InPackagePath, - true, - OutWorld, - OutLevel, - bCreatedPackage, - bActorInWorld); - - if (!bResult) - { - return nullptr; - } - - if (!bActorInWorld) - { - // The OutLevel is not present in the current world which means we might - // still find the tile actor in OutWorld. - FoundActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); - } - } - - return FoundActor; -} - -bool -FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh( - UHoudiniAssetComponent* InHoudiniAssetComponent, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess, - bool& bOutNeedsReCook) -{ - if (!IsValid(InHoudiniAssetComponent)) - { - return false; - } - - // Handle proxies: if the output has any current proxies, first refine them - bOutNeedsReCook = false; - if (InHoudiniAssetComponent->HasAnyCurrentProxyOutput()) - { - bool bNeedsRebuildOrDelete; - bool bInvalidState; - const bool bCookedDataAvailable = InHoudiniAssetComponent->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bInvalidState); - - if (bCookedDataAvailable) - { - // Cook data is available, refine the mesh - AHoudiniAssetActor* HoudiniActor = Cast(InHoudiniAssetComponent->GetOwner()); - if (IsValid(HoudiniActor)) - { - FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ HoudiniActor }); - } - } - else if (!bNeedsRebuildOrDelete && !bInvalidState) - { - // A cook is needed: request the cook, but with no proxy and with a bake after cook - InHoudiniAssetComponent->SetNoProxyMeshNextCookRequested(true); - // Only - if (!InHoudiniAssetComponent->IsBakeAfterNextCookEnabled() || !InHoudiniAssetComponent->GetOnPostCookBakeDelegate().IsBound()) - { - InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess](UHoudiniAssetComponent* InHAC) { - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess); - }); - } - InHoudiniAssetComponent->MarkAsNeedCook(); - - bOutNeedsReCook = true; - - // The cook has to complete first (asynchronously) before the bake can happen - // The SetBakeAfterNextCookEnabled flag will result in a bake after cook - return false; - } - else - { - // The HAC is in an unsupported state - const EHoudiniAssetState AssetState = InHoudiniAssetComponent->GetAssetState(); - HOUDINI_LOG_ERROR(TEXT("Could not refine (in order to bake) %s, the asset is in an unsupported state: %s"), *(InHoudiniAssetComponent->GetPathName()), *(UEnum::GetValueAsString(AssetState))); - return false; - } - } - - return true; -} - -void -FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor) -{ - if (!IsValid(InActor)) - return; - - USceneComponent * const RootComponent = InActor->GetRootComponent(); - if (!IsValid(RootComponent)) - return; - - // If the root component does not have any child components, then there is nothing to recenter - if (RootComponent->GetNumChildrenComponents() <= 0) - return; - - const bool bOnlyCollidingComponents = false; - const bool bIncludeFromChildActors = true; - FVector Origin; - FVector BoxExtent; - // InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); - FBox Box(ForceInit); - - InActor->ForEachComponent(bIncludeFromChildActors, [&](const UPrimitiveComponent* InPrimComp) - { - // Only use non-editor-only components for the bounds calculation (to exclude things like editor only sprite/billboard components) - if (InPrimComp->IsRegistered() && !InPrimComp->IsEditorOnly() && - (!bOnlyCollidingComponents || InPrimComp->IsCollisionEnabled())) - { - Box += InPrimComp->Bounds.GetBox(); - } - }); - Box.GetCenterAndExtents(Origin, BoxExtent); - - const FVector Delta = Origin - RootComponent->GetComponentLocation(); - // Actor->SetActorLocation(Origin); - RootComponent->SetWorldLocation(Origin); - - for (USceneComponent* SceneComponent : RootComponent->GetAttachChildren()) - { - if (!IsValid(SceneComponent)) - continue; - - SceneComponent->SetWorldLocation(SceneComponent->GetComponentLocation() - Delta); - } -} - -void -FHoudiniEngineBakeUtils::CenterActorsToBoundingBoxCenter(const TArray& InActors) -{ - for (AActor* Actor : InActors) - { - if (!IsValid(Actor)) - continue; - - CenterActorToBoundingBoxCenter(Actor); - } -} - -USceneComponent* -FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMissing, EComponentMobility::Type InMobilityIfCreated) -{ - USceneComponent* RootComponent = InActor->GetRootComponent(); - if (!IsValid(RootComponent)) - { - RootComponent = NewObject(InActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); - - // Change the creation method so the component is listed in the details panels - InActor->SetRootComponent(RootComponent); - InActor->AddInstanceComponent(RootComponent); - RootComponent->RegisterComponent(); - RootComponent->SetMobility(InMobilityIfCreated); - } - - return RootComponent; -} - -FString -FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed) -{ - if (IsValid(InObjectThatWouldBeRenamed)) - { - const FName CurrentName = InObjectThatWouldBeRenamed->GetFName(); - if (CurrentName.ToString() == InName) - return InName; - - // Check if the prefix matches (without counter suffix) the new name - // In other words, if InName is 'my_actor' and the object is already an increment of it, 'my_actor_5' then - // don't we can just keep the current name - if (CurrentName.GetPlainNameString() == InName) - return CurrentName.ToString(); - } - - UObject* ExistingObject = nullptr; - FName CandidateName(InName); - bool bAppendedNumber = false; - // Do our own loop for generate suffixes as sequentially as possible. If this turns out to be expensive we can - // revert to MakeUniqueObjectName. - // return MakeUniqueObjectName(InOuter, InClass, CandidateName).ToString(); - do - { - if (InOuter == ANY_PACKAGE) - { - ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *(CandidateName.ToString())); - } - else - { - ExistingObject = StaticFindObjectFast(nullptr, InOuter, CandidateName); - } - - if (ExistingObject) - { - if (!bAppendedNumber) - { - const bool bSplitName = false; - CandidateName = FName(*InName, NAME_EXTERNAL_TO_INTERNAL(1), FNAME_Add, bSplitName); - bAppendedNumber = true; - } - else - { - CandidateName.SetNumber(CandidateName.GetNumber() + 1); - } - // CandidateName = FString::Printf(TEXT("%s_%d"), *InName, ++Counter); - } - } while (ExistingObject); - - return CandidateName.ToString(); -} - -FName -FHoudiniEngineBakeUtils::GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) -{ - const FString* FolderPathPtr = InOutputObject.CachedAttributes.Find(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER); - if (FolderPathPtr && !FolderPathPtr->IsEmpty()) - return FName(*FolderPathPtr); - else - return InDefaultFolder; -} - -bool -FHoudiniEngineBakeUtils::SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) -{ - if (!IsValid(InActor)) - return false; - - InActor->SetFolderPath(GetOutlinerFolderPath(InOutputObject, InDefaultFolder)); - return true; -} - -uint32 -FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInDestroyBakedComponent, - bool bInDestroyBakedInstancedActors, - bool bInDestroyBakedInstancedComponents) -{ - uint32 NumDeleted = 0; - - if (bInDestroyBakedComponent) - { - UActorComponent* Component = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (Component) - { - if (RemovePreviouslyBakedComponent(Component)) - { - InBakedOutputObject.BakedComponent = nullptr; - NumDeleted++; - } - } - } - - if (bInDestroyBakedInstancedActors) - { - for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) - { - const FSoftObjectPath ActorPath(ActorPathStr); - - if (!ActorPath.IsValid()) - continue; - - AActor* Actor = Cast(ActorPath.TryLoad()); - if (IsValid(Actor)) - { - UWorld* World = Actor->GetWorld(); - if (IsValid(World)) - { -#if WITH_EDITOR - World->EditorDestroyActor(Actor, true); -#else - World->DestroyActor(Actor); -#endif - NumDeleted++; - } - } - } - InBakedOutputObject.InstancedActors.Empty(); - } - - if (bInDestroyBakedInstancedComponents) - { - for (const FString& ComponentPathStr : InBakedOutputObject.InstancedComponents) - { - const FSoftObjectPath ComponentPath(ComponentPathStr); - - if (!ComponentPath.IsValid()) - continue; - - UActorComponent* Component = Cast(ComponentPath.TryLoad()); - if (IsValid(Component)) - { - if (RemovePreviouslyBakedComponent(Component)) - NumDeleted++; - } - } - InBakedOutputObject.InstancedComponents.Empty(); - } - - return NumDeleted; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineBakeUtils.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngineUtils.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutput.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineUtils.h" +#include "UnrealLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniStringResolver.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniEngineRuntimeUtils.h" + +#include "Engine/StaticMesh.h" +#include "Engine/World.h" +#include "RawMesh.h" +#include "UObject/Package.h" +#include "PackageTools.h" +#include "UObject/MetaData.h" +#include "AssetRegistryModule.h" +#include "Materials/Material.h" +#include "LandscapeProxy.h" +#include "LandscapeStreamingProxy.h" +#include "LandscapeInfo.h" +#include "Factories/WorldFactory.h" +#include "AssetToolsModule.h" +#include "InstancedFoliageActor.h" +#include "Components/SplineComponent.h" +#include "GameFramework/Actor.h" +#include "Engine/StaticMeshActor.h" +#include "Components/StaticMeshComponent.h" +#include "PhysicsEngine/BodySetup.h" +#include "ActorFactories/ActorFactoryStaticMesh.h" +#include "ActorFactories/ActorFactoryEmptyActor.h" +#include "BusyCursor.h" +#include "Editor.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "FileHelpers.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngine.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "Editor/EditorEngine.h" +#include "Factories/BlueprintFactory.h" +#include "Engine/SimpleConstructionScript.h" +#include "Misc/Paths.h" +#include "HAL/FileManager.h" +#include "LandscapeEdit.h" +#include "Containers/UnrealString.h" +#include "Components/AudioComponent.h" +#include "Engine/WorldComposition.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "MaterialEditor/Public/MaterialEditingLibrary.h" +#include "MaterialGraph/MaterialGraph.h" +#include "Particles/ParticleSystemComponent.h" +#include "Sound/SoundBase.h" +#include "UObject/UnrealType.h" +#include "Math/Box.h" +#include "Misc/ScopedSlowTask.h" + +HOUDINI_BAKING_DEFINE_LOG_CATEGORY(); + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() + : Actor(nullptr) + , OutputIndex(INDEX_NONE) + , OutputObjectIdentifier() + , ActorBakeName(NAME_None) + , BakedObject(nullptr) + , SourceObject(nullptr) + , BakeFolderPath() + , bInstancerOutput(false) + , bPostBakeProcessPostponed(false) +{ +} + +FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( + AActor* InActor, + FName InActorBakeName, + FName InWorldOutlinerFolder, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + UObject* InBakedObject, + UObject* InSourceObject, + UObject* InBakedComponent, + const FString& InBakeFolderPath, + const FHoudiniPackageParams& InBakedObjectPackageParams) + : Actor(InActor) + , OutputIndex(InOutputIndex) + , OutputObjectIdentifier(InOutputObjectIdentifier) + , ActorBakeName(InActorBakeName) + , WorldOutlinerFolder(InWorldOutlinerFolder) + , BakedObject(InBakedObject) + , SourceObject(InSourceObject) + , BakedComponent(InBakedComponent) + , BakeFolderPath(InBakeFolderPath) + , BakedObjectPackageParams(InBakedObjectPackageParams) + , bInstancerOutput(false) + , bPostBakeProcessPostponed(false) +{ +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + UHoudiniAssetComponent* InHACToBake, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors) +{ + if (!IsValid(InHACToBake)) + return false; + + // Handle proxies: if the output has any current proxies, first refine them + bool bHACNeedsToReCook; + if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors, bHACNeedsToReCook)) + { + // Either the component is invalid, or needs a recook to refine a proxy mesh + return false; + } + + bool bSuccess = false; + switch (InBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake, bInRecenterBakedActors); + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake, bInRecenterBakedActors); + } + break; + + case EHoudiniEngineBakeOption::ToFoliage: + { + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake); + } + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + { + //Todo + bSuccess = false; + } + break; + + } + + if (bSuccess && bInRemoveHACOutputOnSuccess) + { + TArray DeferredClearOutputs; + FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake, DeferredClearOutputs, true); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActors) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + TArray NewActors; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + const bool bBakedWithErrors = !FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats); + if (bBakedWithErrors) + { + // TODO ? + HOUDINI_LOG_WARNING(TEXT("Errors when baking")); + } + + // Save the created packages + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && NewActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : NewActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && NewActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + // Broadcast that the bake is complete + HoudiniAssetComponent->HandleOnPostBake(!bBakedWithErrors); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); + if (!IsValid(OwnerActor)) + return false; + + const FString HoudiniAssetName = OwnerActor->GetName(); + + // Get an array of the outputs + const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); + TArray Outputs; + Outputs.Reserve(NumOutputs); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + Outputs.Add(HoudiniAssetComponent->GetOutputAt(OutputIdx)); + } + + // Get the previous bake objects and grow/shrink to match asset outputs + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + // Ensure we have an entry for each output + if (BakedOutputs.Num() != NumOutputs) + BakedOutputs.SetNum(NumOutputs); + + return BakeHoudiniOutputsToActors( + HoudiniAssetComponent, + Outputs, + BakedOutputs, + HoudiniAssetName, + HoudiniAssetComponent->GetComponentTransform(), + HoudiniAssetComponent->BakeFolder, + HoudiniAssetComponent->TemporaryCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutNewActors, + OutPackagesToSave, + OutBakeStats, + InOutputTypesToBake, + InInstancerComponentTypesToBake, + InFallbackActor, + InFallbackWorldOutlinerFolder); +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + const TArray& InOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FTransform& InParentTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + const int32 NumOutputs = InOutputs.Num(); + + const FString MsgTemplate = TEXT("Baking output: {0}/{1}."); + FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs }); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg)); + + TArray BakedActors; + + // Ensure that InBakedOutputs is the same size as InOutputs + if (InBakedOutputs.Num() != NumOutputs) + InBakedOutputs.SetNum(NumOutputs); + + // First bake everything except instancers, then bake instancers. Since instancers might use meshes in + // from the other outputs. + bool bHasAnyInstancers = false; + int32 NumProcessedOutputs = 0; + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + UHoudiniOutput* Output = InOutputs[OutputIdx]; + if (!Output || Output->IsPendingKill()) + { + NumProcessedOutputs++; + continue; + } + + Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); + + const EHoudiniOutputType OutputType = Output->GetType(); + // Check if we should skip this output type + if (InOutputTypesToBake && InOutputTypesToBake->Find(OutputType) == INDEX_NONE) + { + NumProcessedOutputs++; + continue; + } + + switch (OutputType) + { + case EHoudiniOutputType::Mesh: + { + FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + HoudiniAssetComponent, + OutputIdx, + InOutputs, + InBakedOutputs, + InHoudiniAssetName, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + break; + + case EHoudiniOutputType::Instancer: + { + if (!bHasAnyInstancers) + bHasAnyInstancers = true; + NumProcessedOutputs--; + } + break; + + case EHoudiniOutputType::Landscape: + { + const bool bResult = BakeLandscape( + HoudiniAssetComponent, + OutputIdx, + InOutputs, + InBakedOutputs, + bInReplaceActors, + bInReplaceAssets, + InBakeFolder.Path, + InHoudiniAssetName, + OutBakeStats); + } + break; + + case EHoudiniOutputType::Skeletal: + break; + + case EHoudiniOutputType::Curve: + { + FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( + HoudiniAssetComponent, + OutputIdx, + InOutputs, + InBakedOutputs, + InHoudiniAssetName, + InBakeFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + break; + + case EHoudiniOutputType::Invalid: + break; + } + + NumProcessedOutputs++; + } + + if (bHasAnyInstancers) + { + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + UHoudiniOutput* Output = InOutputs[OutputIdx]; + if (!Output || Output->IsPendingKill()) + { + NumProcessedOutputs++; + continue; + } + + Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); + + if (Output->GetType() == EHoudiniOutputType::Instancer) + { + FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + HoudiniAssetComponent, + OutputIdx, + InOutputs, + InBakedOutputs, + InParentTransform, + InHoudiniAssetName, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + OutPackagesToSave, + InInstancerComponentTypesToBake, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + + NumProcessedOutputs++; + } + } + + // Only do the post bake post-process once per Actor + TSet UniqueActors; + for (FHoudiniEngineBakedActor& BakedActor : BakedActors) + { + if (BakedActor.bPostBakeProcessPostponed && BakedActor.Actor) + { + BakedActor.bPostBakeProcessPostponed = false; + AActor* Actor = BakedActor.Actor; + bool bIsAlreadyInSet = false; + UniqueActors.Add(Actor, &bIsAlreadyInSet); + if (!bIsAlreadyInSet) + { + Actor->InvalidateLightingCache(); + Actor->PostEditMove(true); + Actor->MarkPackageDirty(); + } + } + } + + OutNewActors.Append(BakedActors); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave) +{ + UHoudiniOutput* Output = InAllOutputs[InOutputIndex]; + if (!Output || Output->IsPendingKill()) + return false; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + return false; + + if (!IsValid(InOutputObject.OutputComponent)) + return false; + + UStaticMeshComponent* SMC = Cast(InOutputObject.OutputComponent); + if (!IsValid(SMC)) + { + HOUDINI_LOG_WARNING( + TEXT("Unsupported component for foliage: %s"),*(InOutputObject.OutputComponent->GetClass()->GetName())); + return false; + } + + UStaticMesh* InstancedStaticMesh = SMC->GetStaticMesh(); + if (!IsValid(InstancedStaticMesh)) + { + // No mesh, skip this instancer + return false; + } + + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets + ? EPackageReplaceMode::ReplaceExistingAssets + : EPackageReplaceMode::CreateNewAssets; + UWorld* DesiredWorld = Output ? Output->GetWorld() : GWorld; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + const bool bFoundMeshOutput = FindOutputObject(InstancedStaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + InHoudiniAssetName, MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + } + else + { + BakedStaticMesh = InstancedStaticMesh; + } + + // Update the baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // const FString InstancerName = FString::Printf(TEXT("%s_foliage_%s"), *ObjectName, *(InOutputObjectIdentifier.SplitIdentifier)); + // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone + // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the + // package params. + FHoudiniPackageParams InstancerPackageParams; + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + InHoudiniAssetName, InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + HOUDINI_LOG_ERROR(TEXT("Could not find or create a level: %s"), *LevelPackagePath); + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Get foliage actor for the level + const bool bCreateIfNone = true; + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, bCreateIfNone); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Could not find or create an instanced foliage actor for level %s"), *(DesiredLevel->GetPathName())); + return false; + } + + // Get the previous bake data for this instancer + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // Foliage type is replaced in replacement mode if: + // the previous baked object is this foliage type + // and we haven't bake this foliage type during this bake (BakeResults) + // NOTE: foliage type is only recorded as the previous bake object if we created the foliage type + // TODO: replacement mode should probably only affect the instances themselves and not the foliage type + // since the foliage type is already linked to whatever mesh we are using (which will be replaced + // incremented already). To track instances it looks like we would have to use the locations of the + // baked instances (likely cannot use the indices, since the user might modify/add/remove instances + // after the bake). + + // See if we already have a FoliageType for that static mesh + UFoliageType* FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(BakedStaticMesh); + if (!FoliageType || FoliageType->IsPendingKill()) + { + // We need to create a new FoliageType for this Static Mesh + // TODO: Add foliage default settings + InstancedFoliageActor->AddMesh(BakedStaticMesh, &FoliageType); + // Update the previous bake results with the foliage type we created + InBakedOutputObject.BakedComponent = FSoftObjectPath(FoliageType).ToString(); + } + else + { + const FString FoliageTypePath = FSoftObjectPath(FoliageType).ToString(); + if (bInReplaceAssets && InBakedOutputObject.BakedComponent == FoliageTypePath && + !OutActors.FindByPredicate([FoliageType](const FHoudiniEngineBakedActor& Entry) { return Entry.BakedComponent == FoliageType; })) + { + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + // Update the previous bake results with the foliage type + InBakedOutputObject.BakedComponent = FoliageTypePath; + } + else + { + // If we didn't create the foliage type, don't set the baked component + InBakedOutputObject.BakedComponent.Empty(); + } + } + + // Record the foliage bake in the current results + FHoudiniEngineBakedActor& NewResult = OutActors.Add_GetRef(FHoudiniEngineBakedActor()); + NewResult.OutputIndex = InOutputIndex; + NewResult.OutputObjectIdentifier = InOutputObjectIdentifier; + NewResult.SourceObject = InstancedStaticMesh; + NewResult.BakedObject = BakedStaticMesh; + NewResult.BakedComponent = FoliageType; + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if (!FoliageInfo) + return false; + + int32 CurrentInstanceCount = 0; + if (SMC->IsA()) + { + UInstancedStaticMeshComponent* ISMC = Cast(SMC); + const int32 NumInstances = ISMC->GetInstanceCount(); + for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex) + { + FTransform InstanceTransform; + const bool bWorldSpace = true; + if (ISMC->GetInstanceTransform(InstanceIndex, InstanceTransform, bWorldSpace)) + { + FFoliageInstance FoliageInstance; + FoliageInstance.Location = InstanceTransform.GetLocation(); + FoliageInstance.Rotation = InstanceTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = InstanceTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + + CurrentInstanceCount++; + } + } + } + else + { + const FTransform ComponentToWorldTransform = SMC->GetComponentToWorld(); + FFoliageInstance FoliageInstance; + FoliageInstance.Location = ComponentToWorldTransform.GetLocation(); + FoliageInstance.Rotation = ComponentToWorldTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = ComponentToWorldTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + + CurrentInstanceCount++; + } + + // TODO: This was due to a bug in UE4.22-20, check if still needed! + if (FoliageInfo->GetComponent()) + FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); + + // Notify the user that we succesfully bake the instances to foliage + FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + BakedStaticMesh->GetName() + TEXT(" to Foliage"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + InstancedFoliageActor->RegisterAllComponents(); + + // Update / repopulate the foliage editor mode's mesh list + if (CurrentInstanceCount > 0) + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + + return true; +} + +bool +FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n) + { + UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n); + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + continue; + + if (Output->GetInstancedOutputs().Num() > 0) + return true; + /* + // TODO: Is this needed? check we have components to bake? + for (auto& OutputObjectPair : Output->GetOutputObjects()) + { + if (OutputObjectPair.Value.OutputCompoent!= nullpt) + return true; + } + */ + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor * OwnerActor = HoudiniAssetComponent->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + TArray PackagesToSave; + TArray BakedResults; + + FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); + const FString HoudiniAssetName = OwnerActor->GetName(); + + // Build an array of the outputs so that we can search for meshes/previous baked meshes + TArray Outputs; + HoudiniAssetComponent->GetOutputs(Outputs); + const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); + + // Get the previous bake outputs and match the output array size + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + if (BakedOutputs.Num() != NumOutputs) + BakedOutputs.SetNum(NumOutputs); + + bool bSuccess = true; + // Map storing original and baked Static Meshes + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) + { + UHoudiniOutput* Output = Outputs[OutputIdx]; + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + continue; + + TMap& OutputObjects = Output->GetOutputObjects(); + const TMap& OldBakedOutputObjects = BakedOutputs[OutputIdx].BakedOutputObjects; + TMap NewBakedOutputObjects; + + for (auto & Pair : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniOutputObject& OutputObject = Pair.Value; + + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); + + const bool bInReplaceActors = false; + bSuccess &= BakeInstancerOutputToFoliage( + HoudiniAssetComponent, + OutputIdx, + Outputs, + Identifier, + OutputObject, + BakedOutputObject, + HoudiniAssetName, + HoudiniAssetComponent->BakeFolder, + HoudiniAssetComponent->TemporaryCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedResults, + PackagesToSave); + } + + // Update the cached baked output data + BakedOutputs[OutputIdx].BakedOutputObjects = NewBakedOutputObjects; + } + + if (PackagesToSave.Num() > 0) + { + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + } + + // Broadcast that the bake is complete + HoudiniAssetComponent->HandleOnPostBake(bSuccess); + + return bSuccess; +} + + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FTransform& InTransform, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; + if (!InOutput || InOutput->IsPendingKill()) + return false; + + // Ensure we have the same number of baked outputs and asset outputs + if (InBakedOutputs.Num() != InAllOutputs.Num()) + InBakedOutputs.SetNum(InAllOutputs.Num()); + + TMap& OutputObjects = InOutput->GetOutputObjects(); + const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + TMap NewBakedOutputObjects; + + // Iterate on the output objects, baking their object/component as we go + for (auto& Pair : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniOutputObject& CurrentOutputObject = Pair.Value; + + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); + + if (CurrentOutputObject.bProxyIsCurrent) + { + // TODO: we need to refine the SM first! + // ?? + } + + if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill()) + continue; + + if (CurrentOutputObject.OutputComponent->IsA()) + { + // Bake foliage as foliage + if (!InInstancerComponentTypesToBake || + InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageInstancedStaticMeshComponent)) + { + BakeInstancerOutputToFoliage( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InHoudiniAssetName, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave); + } + else if (!InInstancerComponentTypesToBake || + InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent)) + { + BakeInstancerOutputToActors_ISMC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent))) + { + BakeInstancerOutputToActors_ISMC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent))) + { + BakeInstancerOutputToActors_IAC( + HoudiniAssetComponent, + InOutputIndex, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) + { + BakeInstancerOutputToActors_MSIC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) + { + BakeInstancerOutputToActors_SMC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else + { + // Unsupported component! + } + + } + + // Update the cached baked output data + InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UInstancedStaticMeshComponent * InISMC = Cast(InOutputObject.OutputComponent); + if (!InISMC || InISMC->IsPendingKill()) + return false; + + AActor * OwnerActor = InISMC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh * StaticMesh = InISMC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + OwnerActor->GetName(), MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + } + else + { + BakedStaticMesh = StaticMesh; + } + + // Update the baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone + // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the + // package params. + FHoudiniPackageParams InstancerPackageParams; + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if(!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + /* + // TODO: Get the bake name! + // Bake override, the output name + // The bake name override has priority + FString InstancerName = InOutputObject.BakeName; + if (InstancerName.IsEmpty()) + { + // .. then use the output name + InstancerName = Resolver.ResolveOutputName(); + } + */ + + // Should we create one actor with an ISMC or multiple actors with one SMC? + bool bSpawnMultipleSMC = false; + if (bSpawnMultipleSMC) + { + // TODO: Double check, Has a crash here! + + // Get the StaticMesh ActorFactory + UActorFactory* SMFactory = nullptr; + + if (!FoundActor) + { + SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!SMFactory) + return false; + } + + // Split the instances to multiple StaticMeshActors + for (int32 InstanceIdx = 0; InstanceIdx < InISMC->GetInstanceCount(); InstanceIdx++) + { + FTransform InstanceTransform; + InISMC->GetInstanceTransform(InstanceIdx, InstanceTransform, true); + + if (!FoundActor) + { + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform, RF_Transactional); + if (!FoundActor || FoundActor->IsPendingKill()) + continue; + } + + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, NewNameStr, false); + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + continue; + + // Copy properties from the existing component + CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC); + + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh, + SMActor->GetStaticMeshComponent(), + bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + } + } + else + { + bool bSpawnedActor = false; + if (!FoundActor) + { + // Only create one actor + FActorSpawnParameters SpawnInfo; + SpawnInfo.OverrideLevel = DesiredLevel; + SpawnInfo.ObjectFlags = RF_Transactional; + SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); + SpawnInfo.bDeferConstruction = true; + + // Spawn the new Actor + FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + bSpawnedActor = true; + + FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); + FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); + } + else + { + // If there is a previously baked component, and we are in replace mode, remove it + if (bInReplaceAssets) + { + USceneComponent* InPrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(InPrevComponent) && InPrevComponent->GetOwner() == FoundActor) + RemovePreviouslyBakedComponent(InPrevComponent); + } + + const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); + } + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Get/create the actor's root component + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + if (bSpawnedActor && IsValid(RootComponent)) + RootComponent->SetWorldTransform(InTransform); + + // Duplicate the instancer component, create a Hierarchical ISMC if needed + UInstancedStaticMeshComponent* NewISMC = nullptr; + UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast(InISMC); + if (InHISMC) + { + // Handle foliage: don't duplicate foliage component, create a new hierarchical one and copy what we can + // from the foliage component + if (InHISMC->IsA()) + { + NewISMC = NewObject( + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); + CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); + } + else + { + NewISMC = DuplicateObject( + InHISMC, + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); + } + } + else + { + NewISMC = DuplicateObject( + InISMC, + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetName()))); + } + + if (!NewISMC) + { + //DesiredLevel->OwningWorld-> + return false; + } + + InBakedOutputObject.BakedComponent = FSoftObjectPath(NewISMC).ToString(); + + NewISMC->RegisterComponent(); + // NewISMC->SetupAttachment(nullptr); + NewISMC->SetStaticMesh(BakedStaticMesh); + FoundActor->AddInstanceComponent(NewISMC); + // NewActor->SetRootComponent(NewISMC); + if (IsValid(RootComponent)) + NewISMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + NewISMC->SetWorldTransform(InISMC->GetComponentTransform()); + + // TODO: do we need to copy properties here, we duplicated the component + // // Copy properties from the existing component + // CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); + + if (bSpawnedActor) + FoundActor->FinishSpawning(InTransform); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh, + NewISMC, + bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + + // Postpone post-bake calls to do them once per actor + OutActors.Last().bPostBakeProcessPostponed = true; + } + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UStaticMeshComponent* InSMC = Cast(InOutputObject.OutputComponent); + if (!InSMC || InSMC->IsPendingKill()) + return false; + + AActor* OwnerActor = InSMC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh* StaticMesh = InSMC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + OwnerActor->GetName(), MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + } + else + { + BakedStaticMesh = StaticMesh; + } + + // Update the previous baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // BaseName holds the Actor / HDA name + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + // See if the instanced static mesh is still a temporary Houdini created Static Mesh + // If it is, we need to bake the StaticMesh first + FHoudiniPackageParams InstancerPackageParams; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + UStaticMeshComponent* StaticMeshComponent = nullptr; + // Create an actor if we didn't find one + if (!FoundActor) + { + // Get the StaticMesh ActorFactory + UActorFactory* SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!SMFactory) + return false; + + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + return false; + + StaticMeshComponent = SMActor->GetStaticMeshComponent(); + } + else + { + USceneComponent* RootComponent = GetActorRootComponent(FoundActor); + if (!IsValid(RootComponent)) + return false; + + if (bInReplaceAssets) + { + // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it + UStaticMeshComponent* PrevSMC = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) + { + StaticMeshComponent = PrevSMC; + } + } + + if (!IsValid(StaticMeshComponent)) + { + // Create a new static mesh component + StaticMeshComponent = NewObject(FoundActor, NAME_None, RF_Transactional); + + FoundActor->AddInstanceComponent(StaticMeshComponent); + StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + StaticMeshComponent->RegisterComponent(); + } + } + + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, NewNameStr, false); + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Update the previous baked component + InBakedOutputObject.BakedComponent = FSoftObjectPath(StaticMeshComponent).ToString(); + + if (!IsValid(StaticMeshComponent)) + return false; + + // Copy properties from the existing component + const bool bCopyWorldTransform = true; + CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC, bCopyWorldTransform); + StaticMeshComponent->SetStaticMesh(BakedStaticMesh); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh, + StaticMeshComponent, + MeshPackageParams.BakeFolder, + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave) +{ + UHoudiniInstancedActorComponent* InIAC = Cast(InOutputObject.OutputComponent); + if (!InIAC || InIAC->IsPendingKill()) + return false; + + AActor * OwnerActor = InIAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + // BaseName holds the Actor / HDA name + const FName BaseName = FName(OwnerActor->GetName()); + + // Get the object instanced by this IAC + UObject* InstancedObject = InIAC->GetInstancedObject(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + return false; + + FHoudiniPackageParams PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, BaseName.ToString(), + OwnerActor->GetName(), PackageParams, Resolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // If we are baking in actor replacement mode, remove any previously baked instanced actors for this output + if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0) + { + UWorld* LevelWorld = DesiredLevel->GetWorld(); + if (IsValid(LevelWorld)) + { + for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) + { + const FSoftObjectPath ActorPath(ActorPathStr); + + if (!ActorPath.IsValid()) + continue; + + AActor* Actor = Cast(ActorPath.TryLoad()); + // Destroy Actor if it is valid and part of DesiredLevel + if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel) + { +#if WITH_EDITOR + LevelWorld->EditorDestroyActor(Actor, true); +#else + LevelWorld->DestroyActor(Actor); +#endif + } + } + } + } + + // Empty and reserve enough space for new instanced actors + InBakedOutputObject.InstancedActors.Empty(InIAC->GetInstancedActors().Num()); + + // Iterates on all the instances of the IAC + for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors()) + { + if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) + continue; + + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName.ToString()); + + FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); + AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); + if (!NewActor || NewActor->IsPendingKill()) + continue; + + const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) + (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); + + EditorUtilities::CopyActorProperties(CurrentInstancedActor, NewActor); + + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); + + FHoudiniEngineRuntimeUtils::SetActorLabel(NewActor, NewNameStr); + + SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); + NewActor->SetActorTransform(CurrentTransform); + + InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); + + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + NewActor, + BaseName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + nullptr, + InstancedObject, + nullptr, + PackageParams.BakeFolder, + PackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = PackageParams; + } + + // TODO: + // Move Actors to DesiredLevel if needed?? + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = true; + const bool bInDestroyBakedInstancedActors = false; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UHoudiniMeshSplitInstancerComponent * InMSIC = Cast(InOutputObject.OutputComponent); + if (!InMSIC || InMSIC->IsPendingKill()) + return false; + + AActor * OwnerActor = InMSIC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh * StaticMesh = InMSIC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + OwnerActor->GetName(), MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + } + else + { + BakedStaticMesh = StaticMesh; + } + + // Update the baked output + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + // See if the instanced static mesh is still a temporary Houdini created Static Mesh + // If it is, we need to bake the StaticMesh first + FHoudiniPackageParams InstancerPackageParams; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + bool bSpawnedActor = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + if (!FoundActor) + { + // This is a split mesh instancer component - we will create a generic AActor with a bunch of SMC + FActorSpawnParameters SpawnInfo; + SpawnInfo.OverrideLevel = DesiredLevel; + SpawnInfo.ObjectFlags = RF_Transactional; + SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); + SpawnInfo.bDeferConstruction = true; + + // Spawn the new Actor + FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + bSpawnedActor = true; + + FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); + + FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); + } + else + { + // If we are baking in replacement mode, remove the previous components (if they belong to FoundActor) + for (const FString& PrevComponentPathStr : InBakedOutputObject.InstancedComponents) + { + const FSoftObjectPath PrevComponentPath(PrevComponentPathStr); + + if (!PrevComponentPath.IsValid()) + continue; + + UActorComponent* PrevComponent = Cast(PrevComponentPath.TryLoad()); + if (!IsValid(PrevComponent) || PrevComponent->GetOwner() != FoundActor) + continue; + + RemovePreviouslyBakedComponent(PrevComponent); + } + + const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); + } + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Get/create the actor's root component + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + if (bSpawnedActor && IsValid(RootComponent)) + RootComponent->SetWorldTransform(InTransform); + + // Empty and reserve enough space in the baked components array for the new components + InBakedOutputObject.InstancedComponents.Empty(InMSIC->GetInstances().Num()); + + // Now add s SMC component for each of the SMC's instance + for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances()) + { + if (!CurrentSMC || CurrentSMC->IsPendingKill()) + continue; + + UStaticMeshComponent* NewSMC = DuplicateObject( + CurrentSMC, + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetName()))); + if (!NewSMC || NewSMC->IsPendingKill()) + continue; + + InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString()); + + NewSMC->RegisterComponent(); + // NewSMC->SetupAttachment(nullptr); + NewSMC->SetStaticMesh(BakedStaticMesh); + FoundActor->AddInstanceComponent(NewSMC); + NewSMC->SetWorldTransform(CurrentSMC->GetComponentTransform()); + if (IsValid(RootComponent)) + NewSMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); + + // TODO: Do we need to copy properties here, we duplicated the component + // // Copy properties from the existing component + // CopyPropertyToNewActorAndComponent(FoundActor, NewSMC, CurrentSMC); + } + + if (bSpawnedActor) + FoundActor->FinishSpawning(InTransform); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh, + nullptr, + MeshPackageParams.BakeFolder, + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + + // Postpone these calls to do them once per actor + OutActors.Last().bPostBakeProcessPostponed = true; + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = true; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = false; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::FindHGPO( + const FHoudiniOutputObjectIdentifier& InIdentifier, + const TArray& InHGPOs, + FHoudiniGeoPartObject const*& OutHGPO) +{ + // Find the HGPO that matches this output identifier + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + for (auto & NextHGPO : InHGPOs) + { + // We use Matches() here as it handles the case where the HDA was loaded, + // which likely means that the the obj/geo/part ids dont match the output identifier + if(InIdentifier.Matches(NextHGPO)) + { + FoundHGPO = &NextHGPO; + break; + } + } + + OutHGPO = FoundHGPO; + return !OutHGPO; +} + +void +FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const FHoudiniOutputObject& InMeshOutputObject, + FString& OutBakeName) +{ + // The bake name override has priority + OutBakeName = InMeshOutputObject.BakeName; + if (OutBakeName.IsEmpty()) + { + FHoudiniAttributeResolver Resolver; + Resolver.SetCachedAttributes(InMeshOutputObject.CachedAttributes); + Resolver.SetTokensFromStringMap(InMeshOutputObject.CachedTokens); + const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InObject); + // The default output name (if not set via attributes) is {object_name}, which look for an object_name + // key-value token + if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) + Resolver.SetToken(TEXT("object_name"), DefaultObjectName); + OutBakeName = Resolver.ResolveOutputName(); + // const TArray& HGPOs = InAllOutputs[MeshOutputIdx]->GetHoudiniGeoPartObjects(); + // const FHoudiniGeoPartObject* FoundHGPO = nullptr; + // FindHGPO(MeshIdentifier, HGPOs, FoundHGPO); + // // ... finally the part name + // if (FoundHGPO && FoundHGPO->bHasCustomPartName) + // OutBakeName = FoundHGPO->PartName; + if (OutBakeName.IsEmpty()) + OutBakeName = DefaultObjectName; + } +} + +bool +FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( + const UObject* InObject, + EHoudiniOutputType InOutputType, + const TArray& InAllOutputs, + FString& OutBakeName) +{ + if (!IsValid(InObject)) + return false; + + OutBakeName.Empty(); + + int32 MeshOutputIdx = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + if (FindOutputObject(InObject, InOutputType, InAllOutputs, MeshOutputIdx, MeshIdentifier)) + { + // Found the mesh, get its name + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier); + GetTemporaryOutputObjectBakeName(InObject, MeshOutputObject, OutBakeName); + + return true; + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + // Check that index is not negative + if (InOutputIndex < 0) + return false; + + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; + if (!InOutput || InOutput->IsPendingKill()) + return false; + + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!Factory) + return false; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); + + // Get the previous bake objects + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + + const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + TMap NewBakedOutputObjects; + + for (auto& Pair : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + const FHoudiniOutputObject& OutputObject = Pair.Value; + + // Add a new baked output object entry and update it with the previous bake's data, if available + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); + + UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); + if (!StaticMesh || StaticMesh->IsPendingKill()) + continue; + + UStaticMeshComponent* InSMC = Cast(OutputObject.OutputComponent); + if (!InSMC || InSMC->IsPendingKill()) + continue; + + // Find the HGPO that matches this output identifier + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + FindHGPO(Identifier, HGPOs, FoundHGPO); + + // We do not bake templated geos + if (FoundHGPO && FoundHGPO->bIsTemplated) + continue; + + FHoudiniAttributeResolver Resolver; + const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + + UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, + InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); + + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); + + // See if this output object has an unreal_level_path attribute specified + // In which case, we need to create/find the desired level for baking instead of using the current one + bool bHasLevelPathAttribute = OutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + continue; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add the level to the packages to save? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + // Bake the static mesh if it is still temporary + UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, + Cast(BakedOutputObject.GetBakedObjectIfValid()), + PackageParams, + InAllOutputs, + OutActors, + InTempCookFolder.Path, + OutPackagesToSave); + + if (!BakedSM || BakedSM->IsPendingKill()) + continue; + + // Record the baked object + BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString(); + + // Make sure we have a level to spawn to + if (!DesiredLevel || DesiredLevel->IsPendingKill()) + continue; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + UStaticMeshComponent* SMC = nullptr; + if (!FoundActor) + { + // Spawn the new actor + FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); + if (!FoundActor || FoundActor->IsPendingKill()) + continue; + + // Copy properties to new actor + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + continue; + + SMC = SMActor->GetStaticMeshComponent(); + } + else + { + if (bInReplaceAssets) + { + // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it + UStaticMeshComponent* PrevSMC = Cast(BakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) + { + SMC = PrevSMC; + } + } + + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + + if (!IsValid(SMC)) + { + // Create a new static mesh component on the existing actor + SMC = NewObject(FoundActor, NAME_None, RF_Transactional); + + FoundActor->AddInstanceComponent(SMC); + if (IsValid(RootComponent)) + SMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + else + FoundActor->SetRootComponent(SMC); + SMC->RegisterComponent(); + } + } + + // We need to make a unique name for the actor, renaming an object on top of another is a fatal error + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, NewNameStr, false); + SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath); + + if (IsValid(SMC)) + { + const bool bCopyWorldTransform = true; + CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC, bCopyWorldTransform); + SMC->SetStaticMesh(BakedSM); + BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString(); + } + + BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh, SMC, + PackageParams.BakeFolder, PackageParams)); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + BakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + } + + // Update the cached baked output data + InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + // Check that index is not negative + if (InOutputIndex < 0) + return false; + + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; + if (!Output || Output->IsPendingKill()) + return false; + + TArray PackagesToSave; + + // Find the previous baked output data for this output index. If an entry + // does not exist, create entries up to and including this output index + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; + const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; + TMap NewBakedOutputObjects; + + const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); + + for (auto & Pair : OutputObjects) + { + FHoudiniOutputObject& OutputObject = Pair.Value; + USplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); + + // TODO: FIX ME!! May not work 100% + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + for (auto & NextHGPO : HGPOs) + { + if (Identifier.GeoId == NextHGPO.GeoId && + Identifier.ObjectId == NextHGPO.ObjectId && + Identifier.PartId == NextHGPO.PartId) + { + FoundHGPO = &NextHGPO; + break; + } + } + + if (!FoundHGPO) + continue; + + const FString DefaultObjectName = InHoudiniAssetName + "_" + SplineComponent->GetName(); + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* const DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, + InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); + + BakeCurve( + OutputObject, BakedOutputObject, PackageParams, Resolver, bInReplaceActors, bInReplaceAssets, + OutActors, PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); + } + + // Update the cached bake output results + BakedOutput.BakedOutputObjects = NewBakedOutputObjects; + + SaveBakedPackages(PackagesToSave); + + return true; +} + +bool +FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint) +{ + if (!InActor || InActor->IsPendingKill()) + return false; + + if (!OutBlueprint || OutBlueprint->IsPendingKill()) + return false; + + if (InActor->GetInstanceComponents().Num() > 0) + FKismetEditorUtilities::AddComponentsToBlueprint( + OutBlueprint, + InActor->GetInstanceComponents()); + + if (OutBlueprint->GeneratedClass) + { + AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject()); + if (!CDO || CDO->IsPendingKill()) + return false; + + const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) + (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); + + EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions); + + USceneComponent * Scene = CDO->GetRootComponent(); + if (Scene && !Scene->IsPendingKill()) + { + Scene->SetRelativeLocation(FVector::ZeroVector); + Scene->SetRelativeRotation(FRotator::ZeroRotator); + + // Clear out the attachment info after having copied the properties from the source actor + Scene->SetupAttachment(nullptr); + while (true) + { + const int32 ChildCount = Scene->GetAttachChildren().Num(); + if (ChildCount < 1) + break; + + USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; + if (Component && !Component->IsPendingKill()) + Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + } + check(Scene->GetAttachChildren().Num() == 0); + + // Ensure the light mass information is cleaned up + Scene->InvalidateLightingCache(); + + // Copy relative scale from source to target. + if (USceneComponent* SrcSceneRoot = InActor->GetRootComponent()) + { + Scene->SetRelativeScale3D_Direct(SrcSceneRoot->GetRelativeScale3D()); + } + } + } + + // Compile our blueprint and notify asset system about blueprint. + //FKismetEditorUtilities::CompileBlueprint(OutBlueprint); + //FAssetRegistryModule::AssetCreated(OutBlueprint); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, bool bInRecenterBakedActors) +{ + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, bInRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); + if (!bSuccess) + { + // TODO: ? + HOUDINI_LOG_WARNING(TEXT("Errors while baking to blueprints.")); + } + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Broadcast that the bake is complete + HoudiniAssetComponent->HandleOnPostBake(bSuccess); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprints( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceAssets, + bool bInRecenterBakedActors, + FHoudiniEngineOutputStats& InBakeStats, + TArray& OutBlueprints, + TArray& OutPackagesToSave) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); + const bool bIsOwnerActorValid = IsValid(OwnerActor); + + TArray Actors; + + // Don't process outputs that are not supported in blueprints + TArray OutputsToBake = { + EHoudiniOutputType::Mesh, + EHoudiniOutputType::Instancer, + EHoudiniOutputType::Curve + }; + TArray InstancerComponentTypesToBake = { + EHoudiniInstancerComponentType::StaticMeshComponent, + EHoudiniInstancerComponentType::InstancedStaticMeshComponent, + EHoudiniInstancerComponentType::MeshSplitInstancerComponent, + EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent + }; + // When baking blueprints we always create new actors since they are deleted from the world once copied into the + // blueprint + const bool bReplaceActors = false; + bool bBakeSuccess = BakeHoudiniActorToActors( + HoudiniAssetComponent, + bReplaceActors, + bInReplaceAssets, + Actors, + OutPackagesToSave, + InBakeStats, + &OutputsToBake, + &InstancerComponentTypesToBake); + if (!bBakeSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Could not create output actors for baking to blueprint.")); + return false; + } + + // Get the previous baked outputs + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + + bBakeSuccess = BakeBlueprintsFromBakedActors( + Actors, + bInRecenterBakedActors, + bInReplaceAssets, + bIsOwnerActorValid ? OwnerActor->GetName() : FString(), + HoudiniAssetComponent->BakeFolder, + &BakedOutputs, + nullptr, + OutBlueprints, + OutPackagesToSave); + + return bBakeSuccess; +} + +UStaticMesh* +FHoudiniEngineBakeUtils::BakeStaticMesh( + UStaticMesh * StaticMesh, + const FHoudiniPackageParams& PackageParams, + const TArray& InAllOutputs, + const FDirectoryPath& InTempCookFolder) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return nullptr; + + TArray PackagesToSave; + TArray Outputs; + const TArray BakedResults; + UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave); + + if (BakedStaticMesh) + { + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor) + { + TArray Objects; + Objects.Add(BakedStaticMesh); + GEditor->SyncBrowserToObjects(Objects); + } + } + + return BakedStaticMesh; +} + +bool +FHoudiniEngineBakeUtils::BakeLandscape( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + bool bInReplaceActors, + bool bInReplaceAssets, + FString BakePath, + FString HoudiniAssetName, + FHoudiniEngineOutputStats& BakeStats + ) +{ + // Check that index is not negative + if (InOutputIndex < 0) + return false; + + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; + if (!IsValid(Output)) + return false; + + // Find the previous baked output data for this output index. If an entry + // does not exist, create entries up to and including this output index + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; + const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; + TMap NewBakedOutputObjects; + TArray PackagesToSave; + TArray LandscapeWorldsToUpdate; + + FHoudiniPackageParams PackageParams; + + for (auto& Elem : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key; + FHoudiniOutputObject& OutputObject = Elem.Value; + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(ObjectIdentifier); + if (OldBakedOutputObjects.Contains(ObjectIdentifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(ObjectIdentifier); + + // Populate the package params for baking this output object. + if (!IsValid(OutputObject.OutputObject)) + continue; + + if (!OutputObject.OutputObject->IsA()) + continue; + + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); + if (!IsValid(Landscape)) + continue; + + FString ObjectName = Landscape->GetName(); + + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* const DesiredWorld = Landscape ? Landscape->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, ObjectIdentifier, OutputObject, ObjectName, + HoudiniAssetName, PackageParams, Resolver, BakePath, AssetPackageReplaceMode); + + BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, + PackageParams, Resolver, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); + } + + // Update the cached baked output data + BakedOutput.BakedOutputObjects = NewBakedOutputObjects; + + if (PackagesToSave.Num() > 0) + { + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); + } + + for(UWorld* LandscapeWorld : LandscapeWorldsToUpdate) + { + if (!LandscapeWorld) + continue; + FHoudiniEngineUtils::RescanWorldPath(LandscapeWorld); + ULandscapeInfo::RecreateLandscapeInfo(LandscapeWorld, true); + if (LandscapeWorld->WorldComposition) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(LandscapeWorld); + } + } + + if (PackagesToSave.Num() > 0) + { + // These packages were either created during the Bake process or they weren't + // loaded in the first place so be sure to unload them again to preserve their "state". + + TArray PackagesToUnload; + for (UPackage* Package : PackagesToSave) + { + if (!Package->IsDirty()) + PackagesToUnload.Add(Package); + } + UPackageTools::UnloadPackages(PackagesToUnload); + } + +#if WITH_EDITOR + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); +#endif + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeLandscapeObject( + FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInReplaceActors, + bool bInReplaceAssets, + FHoudiniPackageParams& PackageParams, + FHoudiniAttributeResolver& InResolver, + TArray& WorldsToUpdate, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& BakeStats) +{ + UHoudiniLandscapePtr* LandscapePointer = Cast(InOutputObject.OutputObject); + if (!LandscapePointer) + return false; + + ALandscapeProxy* TileActor = LandscapePointer->GetRawPtr(); + if (!TileActor) + return false; + + // Fetch the previous bake's pointer and proxy (if available) + ALandscapeProxy* PreviousTileActor = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + + UWorld* TileWorld = TileActor->GetWorld(); + ULevel* TileLevel = TileActor->GetLevel(); + + ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true); + + // If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC + // and has the appropriate name. + ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor(); + check(SharedLandscapeActor); + + // Fetch the previous bake's shared landscape actor (if available) + ALandscape* PreviousSharedLandscapeActor = nullptr; + if (IsValid(PreviousTileActor)) + PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor(); + + const bool bHasSharedLandscape = SharedLandscapeActor != TileActor; + const bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; + bool bLandscapeReplaced = false; + if (bHasSharedLandscape) + { + // If we are baking in replace mode and we have a previous shared landscape actor, use the name of that + // actor + const FString DesiredSharedLandscapeName = bHasPreviousSharedLandscape && bInReplaceActors + ? PreviousSharedLandscapeActor->GetName() + : InResolver.ResolveAttribute( + HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + SharedLandscapeActor->GetName()); + + // If we are not baking in replacement mode, create a unique name if the name is already in use + const FString SharedLandscapeName = !bInReplaceActors + ? MakeUniqueObjectNameIfNeeded(SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *DesiredSharedLandscapeName) + : DesiredSharedLandscapeName; + + if (SharedLandscapeActor->GetName() != SharedLandscapeName) + { + AActor* FoundActor = nullptr; + ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); + if (ExistingLandscape && bInReplaceActors) + { + // Even though we found an existing landscape with the desired type, we're just going to destroy/replace + // it for now. + FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); + ExistingLandscape->Destroy(); + bLandscapeReplaced = true; + } + + // Fix name of shared landscape + FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); + } + + SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); + } + + // Find the world where the landscape tile should be placed. + + TArray ValidLandscapes; + + FString ActorName = InResolver.ResolveOutputName(); + + // If the unreal_level_path was not specified, then fallback to the tile world's package + FString PackagePath = TileWorld->GetPackage() ? TileWorld->GetPackage()->GetPathName() : FString(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + PackagePath = InResolver.ResolveFullLevelPath(); + + if (bInReplaceActors) + { + // If we are baking in replace mode: get the previous baked actor (if available) name, but only if it is in the + // same target level + if (IsValid(PreviousTileActor)) + { + UPackage* PreviousPackage = PreviousTileActor->GetPackage(); + if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath) + { + ActorName = PreviousTileActor->GetName(); + } + } + } + + bool bCreatedPackage = false; + UWorld* TargetWorld = nullptr; + ULevel* TargetLevel = nullptr; + ALandscapeProxy* TargetActor = FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( + TileActor->GetWorld(), + nullptr, //unused in bake mode + ValidLandscapes,//unused in bake mode + -1, //unused in bake mode + -1, //unused in bake mode + ActorName, + PackagePath, + TargetWorld, + TargetLevel, + bCreatedPackage + ); + + check(TargetLevel) + check(TargetWorld) + + if (TargetActor && TargetActor != TileActor) + { + if (bInReplaceActors && (!PreviousTileActor || PreviousTileActor == TargetActor)) + { + // We found an target matching the name that we want. For now, rename it and then nuke it, so that + // at the very least we can spawn a new actor with the desired name. At a later stage we'll implement + // a content update, if possible. + FHoudiniEngineUtils::RenameToUniqueActor(TargetActor, ActorName + TEXT("_0")); + TargetActor->Destroy(); + } + else + { + // incremental, keep existing actor and create a unique name for the new one + ActorName = MakeUniqueObjectName(TargetActor->GetOuter(), TargetActor->GetClass(), *ActorName).ToString(); + } + TargetActor = nullptr; + } + + if (TargetLevel != TileActor->GetLevel()) + { + bool bLevelInWorld = TileWorld->ContainsLevel(TargetLevel); + ALandscape* SharedLandscape = TileActor->GetLandscapeActor(); + ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); + + check(LandscapeInfo); + + // We can now move the current landscape to the new world / level + // if (TileActor->GetClass()->IsChildOf()) + { + // We can only move streaming proxies to sublevels for now. + TArray ActorsToMove = {TileActor}; + + ALandscapeProxy* NewLandscapeProxy = LandscapeInfo->MoveComponentsToLevel(TileActor->LandscapeComponents, TargetLevel); + // We have now moved the landscape components into the new level. We can (hopefully) safely delete the + // old tile actor. + TileActor->Destroy(); + + TargetLevel->MarkPackageDirty(); + + TileActor = NewLandscapeProxy; + } + } + else + { + // Ensure the landscape actor is detached. + TileActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + } + + // Ensure the tile actor has the desired name. + FHoudiniEngineUtils::SafeRenameActor(TileActor, ActorName); + + if (TileActor->GetClass()->IsChildOf(ALandscape::StaticClass())) + { + // This is not a shared landscape. Be sure to update this landscape's world when + // baking is done. + WorldsToUpdate.AddUnique(TileActor->GetWorld()); + } + + if (bCreatedPackage) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(TargetLevel->GetOutermost()); + } + + // Record the landscape in the baked output object via a new UHoudiniLandscapePtr + // UHoudiniLandscapePtr* BakedLandscapePtr = NewObject(LandscapePointer->GetOuter()); + // if (IsValid(BakedLandscapePtr)) + // { + // BakedLandscapePtr->SetSoftPtr(TileActor); + InBakedOutputObject.BakedObject = FSoftObjectPath(TileActor).ToString(); + // } + // else + // { + // InBakedOutputObject.BakedObject = nullptr; + // } + + // Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks + InOutputObject.OutputObject = nullptr; + + DestroyPreviousBakeOutput(InBakedOutputObject, true, true, true); + + // ---------------------------------------------------- + // Collect baking stats + // ---------------------------------------------------- + if (bLandscapeReplaced) + BakeStats.NotifyObjectsReplaced(EHoudiniOutputType::Landscape, 1); + else + BakeStats.NotifyObjectsCreated(EHoudiniOutputType::Landscape, 1); + + if (bCreatedPackage) + BakeStats.NotifyPackageCreated(1); + else + if (TileLevel != TargetLevel) + BakeStats.NotifyPackageUpdated(1); + + return true; +} + +UStaticMesh * +FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + UStaticMesh * InStaticMesh, + UStaticMesh * InPreviousBakeStaticMesh, + const FHoudiniPackageParams &PackageParams, + const TArray& InParentOutputs, + const TArray& InCurrentBakedActors, + const FString& InTemporaryCookFolder, + TArray & OutCreatedPackages) +{ + if (!InStaticMesh || InStaticMesh->IsPendingKill()) + return nullptr; + + const bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, EHoudiniOutputType::Mesh, InParentOutputs, InTemporaryCookFolder); + if (!bIsTemporaryStaticMesh) + { + // The Static Mesh is not a temporary one/already baked, we can simply reuse it + // instead of duplicating it + return InStaticMesh; + } + + // Look for InStaticMesh as the SourceObject in InCurrentBakedActors (it could have already been baked along with + // a previous output: instancers etc) + for (const FHoudiniEngineBakedActor& BakedActor : InCurrentBakedActors) + { + if (BakedActor.SourceObject == InStaticMesh && IsValid(BakedActor.BakedObject) + && BakedActor.BakedObject->IsA(InStaticMesh->GetClass())) + { + // We have found a bake result where InStaticMesh was the source object and we have a valid BakedObject + // of a compatible class + return Cast(BakedActor.BakedObject); + } + } + + // InStaticMesh is temporary and we didn't find a baked version of it in our current bake output, we need to bake it + + // If we have a previously baked static mesh, get the bake counter from it so that both replace and increment + // is consistent with the bake counter + int32 BakeCounter = 0; + bool bPreviousBakeStaticMeshValid = IsValid(InPreviousBakeStaticMesh); + TArray PreviousBakeMaterials; + if (bPreviousBakeStaticMeshValid) + { + bPreviousBakeStaticMeshValid = PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBakeStaticMesh); + if (bPreviousBakeStaticMeshValid) + { + PackageParams.GetBakeCounterFromBakedAsset(InPreviousBakeStaticMesh, BakeCounter); + PreviousBakeMaterials = InPreviousBakeStaticMesh->StaticMaterials;//GetStaticMaterials(); + } + } + FString CreatedPackageName; + UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter); + if (!MeshPackage || MeshPackage->IsPendingKill()) + return nullptr; + + OutCreatedPackages.Add(MeshPackage); + + // We need to be sure the package has been fully loaded before calling DuplicateObject + if (!MeshPackage->IsFullyLoaded()) + { + FlushAsyncLoading(); + if (!MeshPackage->GetOuter()) + { + MeshPackage->FullyLoad(); + } + else + { + MeshPackage->GetOutermost()->FullyLoad(); + } + } + + // If the a UStaticMesh with that name already exists then detach it from all of its components before replacing + // it so that its render resources can be safely replaced/updated, and then reattach it + UStaticMesh * DuplicatedStaticMesh = nullptr; + UStaticMesh* ExistingMesh = FindObject(MeshPackage, *CreatedPackageName); + bool bFoundExistingMesh = false; + if (IsValid(ExistingMesh)) + { + FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); + DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + bFoundExistingMesh = true; + } + else + { + DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + } + + if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, DuplicatedStaticMesh, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, DuplicatedStaticMesh, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedPackageName); + + // See if we need to duplicate materials and textures. + TArrayDuplicatedMaterials; + TArray& Materials = DuplicatedStaticMesh->StaticMaterials; + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + continue; + + // Only duplicate the material if it is temporary + if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InParentOutputs, InTemporaryCookFolder)) + { + UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); + if (MaterialPackage && !MaterialPackage->IsPendingKill()) + { + FString MaterialName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + MeshPackage, DuplicatedStaticMesh, MaterialName)) + { + MaterialName = MaterialName + "_Material" + FString::FromInt(MaterialIdx + 1); + + // We only deal with materials. + UMaterial * Material = Cast< UMaterial >(MaterialInterface); + if (Material && !Material->IsPendingKill()) + { + // Look for a previous bake material at this index + UMaterial* PreviousBakeMaterial = nullptr; + if (bPreviousBakeStaticMeshValid && PreviousBakeMaterials.IsValidIndex(MaterialIdx)) + { + PreviousBakeMaterial = Cast(PreviousBakeMaterials[MaterialIdx].MaterialInterface); + } + // Duplicate material resource. + UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages); + + if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + continue; + + // Store duplicated material. + FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx]; + DupeStaticMaterial.MaterialInterface = DuplicatedMaterial; + DuplicatedMaterials.Add(DupeStaticMaterial); + continue; + } + } + } + } + + // We can simply reuse the source material + DuplicatedMaterials.Add(Materials[MaterialIdx]); + } + + // Assign duplicated materials. + DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; + + // Notify registry that we have created a new duplicate mesh. + if (!bFoundExistingMesh) + FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); + + // Dirty the static mesh package. + DuplicatedStaticMesh->MarkPackageDirty(); + + return DuplicatedStaticMesh; +} + +ALandscapeProxy* +FHoudiniEngineBakeUtils::BakeHeightfield( + ALandscapeProxy * InLandscapeProxy, + const FHoudiniPackageParams & PackageParams, + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType) +{ + if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill()) + return nullptr; + + const FString & BakeFolder = PackageParams.BakeFolder; + const FString & AssetName = PackageParams.HoudiniAssetName; + + switch (LandscapeOutputBakeType) + { + case EHoudiniLandscapeOutputBakeType::Detachment: + { + // Detach the landscape from the Houdini Asset Actor + InLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + } + break; + case EHoudiniLandscapeOutputBakeType::BakeToImage: + { + // Create heightmap image to the bake folder + ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); + if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + return nullptr; + + // bake to image must use absoluate path, + // and the file name has a file extension (.png) + FString BakeFolderInFullPath = BakeFolder; + + // Figure absolute path, + if (!BakeFolderInFullPath.EndsWith("/")) + BakeFolderInFullPath += "/"; + + if (BakeFolderInFullPath.StartsWith("/Game")) + BakeFolderInFullPath = BakeFolderInFullPath.Mid(5, BakeFolderInFullPath.Len() - 5); + + if (BakeFolderInFullPath.StartsWith("/")) + BakeFolderInFullPath = BakeFolderInFullPath.Mid(1, BakeFolderInFullPath.Len() - 1); + + FString FullPath = FPaths::ProjectContentDir() + BakeFolderInFullPath + AssetName + "_" + InLandscapeProxy->GetName() + ".png"; + + InLandscapeInfo->ExportHeightmap(FullPath); + + // TODO: + // We should update this to have an asset/package.. + } + break; + case EHoudiniLandscapeOutputBakeType::BakeToWorld: + { + ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); + if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + return nullptr; + + // 0. Get Landscape Data // + + // Extract landscape height data + TArray InLandscapeHeightData; + int32 XSize, YSize; + FVector Min, Max; + if (!FUnrealLandscapeTranslator::GetLandscapeData(InLandscapeProxy, InLandscapeHeightData, XSize, YSize, Min, Max)) + return nullptr; + + // Extract landscape Layers data + TArray InLandscapeImportLayerInfos; + for (int32 n = 0; n < InLandscapeInfo->Layers.Num(); ++n) + { + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeProxy, InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + FLandscapeImportLayerInfo CurrentLayerInfo; + CurrentLayerInfo.LayerName = FName(LayerName); + CurrentLayerInfo.LayerInfo = InLandscapeInfo->Layers[n].LayerInfoObj; + CurrentLayerInfo.LayerData = CurrentLayerIntData; + + CurrentLayerInfo.LayerInfo->LayerUsageDebugColor = LayerUsageDebugColor; + + InLandscapeImportLayerInfos.Add(CurrentLayerInfo); + } + + // 1. Create package // + + FString PackagePath = PackageParams.GetPackagePath(); + FString PackageName = PackageParams.GetPackageName(); + + UPackage *CreatedPackage = nullptr; + FString CreatedPackageName; + + CreatedPackage = PackageParams.CreatePackageForObject(CreatedPackageName); + + if (!CreatedPackage) + return nullptr; + + // 2. Create a new world asset with dialog // + UWorldFactory* Factory = NewObject(); + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + UObject* Asset = AssetToolsModule.Get().CreateAssetWithDialog( + PackageName, PackagePath, + UWorld::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + + + UWorld* NewWorld = Cast(Asset); + if (!NewWorld) + return nullptr; + + NewWorld->SetCurrentLevel(NewWorld->PersistentLevel); + + // 4. Spawn a landscape proxy actor in the created world + ALandscapeStreamingProxy * BakedLandscapeProxy = NewWorld->SpawnActor(); + if (!BakedLandscapeProxy) + return nullptr; + + // Create a new GUID + FGuid currentGUID = FGuid::NewGuid(); + BakedLandscapeProxy->SetLandscapeGuid(currentGUID); + + // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue + BakedLandscapeProxy->bCastStaticShadow = false; + + + // 5. Import data to the created landscape proxy + TMap> HeightmapDataPerLayers; + TMap> MaterialLayerDataPerLayer; + + HeightmapDataPerLayers.Add(FGuid(), InLandscapeHeightData); + MaterialLayerDataPerLayer.Add(FGuid(), InLandscapeImportLayerInfos); + + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + + BakedLandscapeProxy->Import( + currentGUID, + 0, 0, XSize-1, YSize-1, + InLandscapeInfo->ComponentNumSubsections, InLandscapeInfo->SubsectionSizeQuads, + HeightmapDataPerLayers, NULL, + MaterialLayerDataPerLayer, ImportLayerType); + + BakedLandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + + + if (BakedLandscapeProxy->LandscapeMaterial) + BakedLandscapeProxy->LandscapeMaterial = InLandscapeProxy->LandscapeMaterial; + + if (BakedLandscapeProxy->LandscapeHoleMaterial) + BakedLandscapeProxy->LandscapeHoleMaterial = InLandscapeProxy->LandscapeHoleMaterial; + + // 6. Register all the landscape components, and set landscape actor transform + BakedLandscapeProxy->RegisterAllComponents(); + BakedLandscapeProxy->SetActorTransform(InLandscapeProxy->GetTransform()); + + // 7. Save Package + TArray PackagesToSave; + PackagesToSave.Add(CreatedPackage); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor) + { + TArray Objects; + Objects.Add(NewWorld); + GEditor->SyncBrowserToObjects(Objects); + } + } + break; + } + + return InLandscapeProxy; +} + +bool +FHoudiniEngineBakeUtils::BakeCurve( + USplineComponent* InSplineComponent, + ULevel* InLevel, + const FHoudiniPackageParams &PackageParams, + AActor*& OutActor, + USplineComponent*& OutSplineComponent, + FName InOverrideFolderPath, + AActor* InActor) +{ + if (!IsValid(InActor)) + { + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; + if (!Factory) + return false; + + OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform(), RF_Transactional); + } + else + { + OutActor = InActor; + } + + // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset + const FName BaseActorName(PackageParams.ObjectName); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName.ToString(), OutActor); + RenameAndRelabelActor(OutActor, NewNameStr, false); + OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); + + USplineComponent* DuplicatedSplineComponent = DuplicateObject( + InSplineComponent, + OutActor, + FName(MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), PackageParams.ObjectName))); + OutActor->AddInstanceComponent(DuplicatedSplineComponent); + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); + DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + + // We duplicated the InSplineComponent, so we don't have to copy all of its properties, but we must set the + // world transform + DuplicatedSplineComponent->SetWorldTransform(InSplineComponent->GetComponentTransform()); + + FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent); + DuplicatedSplineComponent->RegisterComponent(); + + OutSplineComponent = DuplicatedSplineComponent; + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeCurve( + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + // const TArray& InAllBakedOutputs, + const FHoudiniPackageParams &PackageParams, + FHoudiniAttributeResolver& InResolver, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + USplineComponent* SplineComponent = Cast(InOutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + return false; + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = InResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if(!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + // If we are baking in replace mode, remove the previous bake component + if (bInReplaceAssets && !InBakedOutputObject.BakedComponent.IsEmpty()) + { + UActorComponent* PrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (PrevComponent && PrevComponent->GetOwner() == FoundActor) + { + RemovePreviouslyBakedComponent(PrevComponent); + } + } + + FHoudiniPackageParams CurvePackageParams = PackageParams; + CurvePackageParams.ObjectName = BakeActorName.ToString(); + USplineComponent* NewSplineComponent = nullptr; + const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName)); + if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor)) + return false; + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + InBakedOutputObject.BakedComponent = FSoftObjectPath(NewSplineComponent).ToString(); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + FHoudiniEngineBakedActor Result; + Result.Actor = FoundActor; + Result.ActorBakeName = BakeActorName; + Result.BakeFolderPath = PackageParams.BakeFolder; + Result.BakedObjectPackageParams = PackageParams; + OutActors.Add(Result); + + return true; +} + +AActor* +FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PackageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform) +{ + if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + return nullptr; + + TArray & DisplayPoints = InHoudiniSplineComponent->DisplayPoints; + if (DisplayPoints.Num() < 2) + return nullptr; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; + if (!Factory) + return nullptr; + + // Remove the actor if it exists + for (auto & Actor : DesiredLevel->Actors) + { + if (!Actor) + continue; + + if (Actor->GetName() == PackageParams.ObjectName) + { + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + Actor->RemoveFromRoot(); + Actor->ConditionalBeginDestroy(); + World->EditorDestroyActor(Actor, true); + + break; + } + } + + AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform(), RF_Transactional); + + USplineComponent* BakedUnrealSplineComponent = NewObject(NewActor); + if (!BakedUnrealSplineComponent) + return nullptr; + + // add display points to created unreal spline component + for (int32 n = 0; n < DisplayPoints.Num(); ++n) + { + FVector & NextPoint = DisplayPoints[n]; + BakedUnrealSplineComponent->AddSplinePoint(NextPoint, ESplineCoordinateSpace::Local); + // Set the curve point type to be linear, since we are using display points + BakedUnrealSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + NewActor->AddInstanceComponent(BakedUnrealSplineComponent); + + BakedUnrealSplineComponent->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); + + FAssetRegistryModule::AssetCreated(NewActor); + FAssetRegistryModule::AssetCreated(BakedUnrealSplineComponent); + BakedUnrealSplineComponent->RegisterComponent(); + + // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); + RenameAndRelabelActor(NewActor, NewNameStr, false); + NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName)); + + return NewActor; +} + +UBlueprint* +FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PackageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform) +{ + if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + return nullptr; + + FGuid BakeGUID = FGuid::NewGuid(); + + if (!BakeGUID.IsValid()) + BakeGUID = FGuid::NewGuid(); + + // We only want half of generated guid string. + FString BakeGUIDString = BakeGUID.ToString().Left(FHoudiniEngineUtils::PackageGUIDItemNameLength); + + // Generate Blueprint name. + FString BlueprintName = PackageParams.ObjectName + "_BP"; + + // Generate unique package name. + FString PackageName = PackageParams.BakeFolder + "/" + BlueprintName; + PackageName = UPackageTools::SanitizePackageName(PackageName); + + // See if package exists, if it does, we need to regenerate the name. + UPackage * Package = FindPackage(nullptr, *PackageName); + + if (Package && !Package->IsPendingKill()) + { + // Package does exist, there's a collision, we need to generate a new name. + BakeGUID.Invalidate(); + } + else + { + // Create actual package. + Package = CreatePackage(*PackageName); + } + + AActor * CreatedHoudiniSplineActor = FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + InHoudiniSplineComponent, PackageParams, WorldToSpawn, SpawnTransform); + + TArray PackagesToSave; + + UBlueprint * Blueprint = nullptr; + if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill()) + { + + UObject* Asset = nullptr; + + Asset = StaticFindObjectFast(UObject::StaticClass(), Package, FName(*BlueprintName)); + if (!Asset) + { + UBlueprintFactory* Factory = NewObject(); + + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + Asset = AssetToolsModule.Get().CreateAsset( + BlueprintName, PackageParams.BakeFolder, + UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + } + + TArray Components; + for (auto & Next : CreatedHoudiniSplineActor->GetComponents()) + { + Components.Add(Next); + } + + Blueprint = Cast(Asset); + + // Clear old Blueprint Node tree + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + + int32 NodeSize = SCS->GetAllNodes().Num(); + for (int32 n = NodeSize - 1; n >= 0; --n) + SCS->RemoveNode(SCS->GetAllNodes()[n]); + + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components); + + CreatedHoudiniSplineActor->RemoveFromRoot(); + CreatedHoudiniSplineActor->ConditionalBeginDestroy(); + + GWorld->EditorDestroyActor(CreatedHoudiniSplineActor, true); + + Package->MarkPackageDirty(); + PackagesToSave.Add(Package); + } + + // Save the created BP package. + FHoudiniEngineBakeUtils::SaveBakedPackages + (PackagesToSave); + + return Blueprint; +} + + +void +FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const TCHAR * Key, + const TCHAR * Value) +{ + if (!Package || Package->IsPendingKill()) + return; + + UMetaData * MetaData = Package->GetMetaData(); + if (MetaData && !MetaData->IsPendingKill()) + MetaData->SetValue(Object, Key, Value); +} + + +bool +FHoudiniEngineBakeUtils:: +GetHoudiniGeneratedNameFromMetaInformation( + UPackage * Package, UObject * Object, FString & HoudiniName) +{ + if (!Package || Package->IsPendingKill()) + return false; + + UMetaData * MetaData = Package->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return false; + + if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + { + // Retrieve name used for package generation. + const FString NameFull = MetaData->GetValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME); + + //HoudiniName = NameFull.Left(FMath::Min(NameFull.Len(), FHoudiniEngineUtils::PackageGUIDItemNameLength)); + HoudiniName = NameFull; + return true; + } + + return false; +} + +UMaterial * +FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + UMaterial * Material, UMaterial* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, + TArray & OutGeneratedPackages) +{ + UMaterial * DuplicatedMaterial = nullptr; + + FString CreatedMaterialName; + // Create material package. Use the same package params as static mesh, but with the material's name + FHoudiniPackageParams MaterialPackageParams = ObjectPackageParams; + MaterialPackageParams.ObjectName = MaterialName; + + // Check if there is a valid previous material. If so, get the bake counter for consistency in + // replace or iterative package naming + bool bIsPreviousBakeMaterialValid = IsValid(PreviousBakeMaterial); + int32 BakeCounter = 0; + TArray PreviousBakeMaterialExpressions; + if (bIsPreviousBakeMaterialValid) + { + bIsPreviousBakeMaterialValid = MaterialPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeMaterial); + if (bIsPreviousBakeMaterialValid) + { + MaterialPackageParams.GetBakeCounterFromBakedAsset(PreviousBakeMaterial, BakeCounter); + PreviousBakeMaterialExpressions = PreviousBakeMaterial->Expressions; + } + } + + UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter); + + if (!MaterialPackage || MaterialPackage->IsPendingKill()) + return nullptr; + + // Clone material. + DuplicatedMaterial = DuplicateObject< UMaterial >(Material, MaterialPackage, *CreatedMaterialName); + if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, DuplicatedMaterial, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, DuplicatedMaterial, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedMaterialName); + + // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. + const int32 NumExpressions = DuplicatedMaterial->Expressions.Num(); + for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx) + { + UMaterialExpression* Expression = DuplicatedMaterial->Expressions[ExpressionIdx]; + UMaterialExpression* PreviousBakeExpression = nullptr; + if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx)) + { + PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; + } + FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages); + } + + // Notify registry that we have created a new duplicate material. + FAssetRegistryModule::AssetCreated(DuplicatedMaterial); + + // Dirty the material package. + DuplicatedMaterial->MarkPackageDirty(); + + // Recompile the baked material + // DuplicatedMaterial->ForceRecompileForRendering(); + // Use UMaterialEditingLibrary::RecompileMaterial since it correctly updates texture references in the material + // which ForceRecompileForRendering does not do + UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterial); + + OutGeneratedPackages.Add(MaterialPackage); + + return DuplicatedMaterial; +} + +void +FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, + const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages) +{ + UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression); + if (!TextureSample || TextureSample->IsPendingKill()) + return; + + UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture); + if (!Texture || Texture->IsPendingKill()) + return; + + UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); + if (!TexturePackage || TexturePackage->IsPendingKill()) + return; + + // Try to get the previous bake's texture + UTexture2D* PreviousBakeTexture = nullptr; + if (IsValid(PreviousBakeMaterialExpression)) + { + UMaterialExpressionTextureSample* PreviousBakeTextureSample = Cast< UMaterialExpressionTextureSample >(PreviousBakeMaterialExpression); + if (IsValid(PreviousBakeTextureSample)) + PreviousBakeTexture = Cast< UTexture2D >(PreviousBakeTextureSample->Texture); + } + + FString GeneratedTextureName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + TexturePackage, Texture, GeneratedTextureName)) + { + // Duplicate texture. + UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( + Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages); + + // Re-assign generated texture. + TextureSample->Texture = DuplicatedTexture; + } +} + +UTexture2D * +FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( + UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages) +{ + UTexture2D* DuplicatedTexture = nullptr; +#if WITH_EDITOR + // Retrieve original package of this texture. + UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); + if (!TexturePackage || TexturePackage->IsPendingKill()) + return nullptr; + + FString GeneratedTextureName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName)) + { + UMetaData * MetaData = TexturePackage->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return nullptr; + + // Retrieve texture type. + const FString & TextureType = + MetaData->GetValue(Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); + + FString CreatedTextureName; + + // Create texture package. Use the same package params as material's, but with object name appended by generated texture's name + FHoudiniPackageParams TexturePackageParams = PackageParams; + TexturePackageParams.ObjectName = TexturePackageParams.ObjectName + "_" + GeneratedTextureName; + + // Determine the bake counter of the previous bake's texture (if exists/valid) for naming consistency when + // replacing/iterating + bool bIsPreviousBakeTextureValid = IsValid(PreviousBakeTexture); + int32 BakeCounter = 0; + if (bIsPreviousBakeTextureValid) + { + bIsPreviousBakeTextureValid = TexturePackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeTexture); + if (bIsPreviousBakeTextureValid) + { + TexturePackageParams.GetBakeCounterFromBakedAsset(PreviousBakeTexture, BakeCounter); + } + } + + UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter); + + if (!NewTexturePackage || NewTexturePackage->IsPendingKill()) + return nullptr; + + // Clone texture. + DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName); + if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedTextureName); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); + + // Notify registry that we have created a new duplicate texture. + FAssetRegistryModule::AssetCreated(DuplicatedTexture); + + // Dirty the texture package. + DuplicatedTexture->MarkPackageDirty(); + + OutCreatedPackages.Add(NewTexturePackage); + } +#endif + return DuplicatedTexture; +} + + +bool +FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); + + if (!ActorOwner || ActorOwner->IsPendingKill()) + return false; + + UWorld* World = ActorOwner->GetWorld(); + if (!World) + World = GWorld; + + World->EditorDestroyActor(ActorOwner, false); + + return true; +} + + +void +FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld) +{ + UWorld * CurrentWorld = nullptr; + if (bSaveCurrentWorld && GEditor) + CurrentWorld = GEditor->GetEditorWorldContext().World(); + + if (CurrentWorld) + { + // Save the current map + FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); + UPackage* CurrentWorldPackage = CreatePackage(*CurrentWorldPath); + + if (CurrentWorldPackage) + { + CurrentWorldPackage->MarkPackageDirty(); + PackagesToSave.Add(CurrentWorldPackage); + } + } + + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); +} + +bool +FHoudiniEngineBakeUtils::FindOutputObject(const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) +{ + if (!InObjectToFind || InObjectToFind->IsPendingKill()) + return false; + + const int32 NumOutputs = InOutputs.Num(); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + const UHoudiniOutput* CurOutput = InOutputs[OutputIdx]; + if (!IsValid(CurOutput)) + continue; + + if (CurOutput->GetType() != InOutputType) + continue; + + for (auto& CurOutputObject : CurOutput->GetOutputObjects()) + { + if (CurOutputObject.Value.OutputObject == InObjectToFind + || CurOutputObject.Value.OutputComponent == InObjectToFind + || CurOutputObject.Value.ProxyObject == InObjectToFind + || CurOutputObject.Value.ProxyComponent == InObjectToFind) + { + OutOutputIndex = OutputIdx; + OutIdentifier = CurOutputObject.Key; + return true; + } + } + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + FString TempPath = FString(); + + // TODO: Get the HAC outputs in a better way? + TArray Outputs; + if (InHAC && !InHAC->IsPendingKill()) + { + const int32 NumOutputs = InHAC->GetNumOutputs(); + Outputs.Reserve(NumOutputs); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + Outputs.Add(InHAC->GetOutputAt(OutputIdx)); + } + + TempPath = InHAC->TemporaryCookFolder.Path; + } + + return IsObjectTemporary(InObject, InOutputType, Outputs, TempPath); +} + +bool FHoudiniEngineBakeUtils::IsObjectTemporary( + UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + int32 ParentOutputIndex = -1; + FHoudiniOutputObjectIdentifier Identifier; + if (FindOutputObject(InObject, InOutputType, InParentOutputs, ParentOutputIndex, Identifier)) + return true; + + // Check the package path for this object + // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated + UPackage* ObjectPackage = InObject->GetOutermost(); + if (ObjectPackage && !ObjectPackage->IsPendingKill()) + { + const FString PathName = ObjectPackage->GetPathName(); + if (PathName.StartsWith(InTemporaryCookFolder)) + return true; + + // Also check the default temp folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder)) + return true; + + /* + // TODO: this just indicates that the object was generated by H + // it could as well have been baked before... + // we should probably add a "temp" metadata + // Look in the meta info as well?? + UMetaData * MetaData = ObjectPackage->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return false; + + if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + return true; + */ + } + + return false; +} + +void +FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent( + AActor* NewActor, + UStaticMeshComponent* NewSMC, + UStaticMeshComponent* InSMC, + bool bInCopyWorldTransform) +{ + if (!NewSMC || NewSMC->IsPendingKill()) + return; + + if (!InSMC || InSMC->IsPendingKill()) + return; + + // Copy properties to new actor + //UStaticMeshComponent* OtherSMC_NonConst = const_cast(StaticMeshComponent); + NewSMC->SetCollisionProfileName(InSMC->GetCollisionProfileName()); + NewSMC->SetCollisionEnabled(InSMC->GetCollisionEnabled()); + NewSMC->LightmassSettings = InSMC->LightmassSettings; + NewSMC->CastShadow = InSMC->CastShadow; + NewSMC->SetMobility(InSMC->Mobility); + + UBodySetup* InBodySetup = InSMC->GetBodySetup(); + UBodySetup* NewBodySetup = NewSMC->GetBodySetup(); + if (InBodySetup && NewBodySetup) + { + // Copy the BodySetup + NewBodySetup->CopyBodyPropertiesFrom(InBodySetup); + + // We need to recreate the physics mesh for the new body setup + NewBodySetup->InvalidatePhysicsData(); + NewBodySetup->CreatePhysicsMeshes(); + + // Only copy the physical material if it's different from the default one, + // As this was causing crashes on BakeToActors in some cases + if (GEngine != NULL && NewBodySetup->GetPhysMaterial() != GEngine->DefaultPhysMaterial) + NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial()); + } + + if (NewActor && !NewActor->IsPendingKill()) + NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame); + + NewSMC->SetVisibility(InSMC->IsVisible()); + + // TODO: + // // Reapply the uproperties modified by attributes on the new component + // FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO); + + // The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another + UClass* ComponentClass = nullptr; + if (InSMC->GetClass()->IsChildOf(NewSMC->GetClass())) + { + ComponentClass = NewSMC->GetClass(); + } + else if (NewSMC->GetClass()->IsChildOf(InSMC->GetClass())) + { + ComponentClass = InSMC->GetClass(); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), + *(ComponentClass->GetName()), + *(NewSMC->GetClass()->GetName())); + + NewSMC->PostEditChange(); + return; + } + + TSet SourceUCSModifiedProperties; + InSMC->GetUCSModifiedProperties(SourceUCSModifiedProperties); + + AActor* SourceActor = InSMC->GetOwner(); + if (!IsValid(SourceActor)) + { + NewSMC->PostEditChange(); + return; + } + + TArray ModifiedObjects; + const EditorUtilities::FCopyOptions Options(EditorUtilities::ECopyOptions::CallPostEditChangeProperty); + // Copy component properties + for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) + { + const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); + const bool bIsIdentical = Property->Identical_InContainer( InSMC, NewSMC ); + const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); + const bool bIsTransform = + Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); + + // auto SourceComponentIsRoot = [&]() + // { + // USceneComponent* RootComponent = SourceActor->GetRootComponent(); + // if (InSMC == RootComponent) + // { + // return true; + // } + // return false; + // }; + + // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + // && ( !bIsTransform || !SourceComponentIsRoot() ) ) + if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + && !bIsTransform ) + { + // const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) + // && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); + const bool bIsSafeToCopy = true; + if( bIsSafeToCopy ) + { + if (!Options.CanCopyProperty(*Property, *SourceActor)) + { + continue; + } + + if( !ModifiedObjects.Contains(NewSMC) ) + { + NewSMC->SetFlags(RF_Transactional); + NewSMC->Modify(); + ModifiedObjects.Add(NewSMC); + } + + if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) + { + // @todo simulate: Should we be calling this on the component instead? + NewActor->PreEditChange( Property ); + } + + EditorUtilities::CopySingleProperty(InSMC, NewSMC, Property); + + if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) + { + FPropertyChangedEvent PropertyChangedEvent( Property ); + NewActor->PostEditChangeProperty( PropertyChangedEvent ); + } + } + } + } + + if (bInCopyWorldTransform) + { + NewSMC->SetWorldTransform(InSMC->GetComponentTransform()); + } + + NewSMC->PostEditChange(); +}; + +bool +FHoudiniEngineBakeUtils::RemovePreviouslyBakedActor( + AActor* InNewBakedActor, + ULevel* InLevel, + const FHoudiniPackageParams& InPackageParams) +{ + // Remove a previous bake actor if it exists + for (auto & Actor : InLevel->Actors) + { + if (!Actor) + continue; + + if (Actor != InNewBakedActor && Actor->GetName() == InPackageParams.ObjectName) + { + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + Actor->RemoveFromRoot(); + Actor->ConditionalBeginDestroy(); + World->EditorDestroyActor(Actor, true); + + return true; + } + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::RemovePreviouslyBakedComponent(UActorComponent* InComponent) +{ + if (!IsValid(InComponent)) + return false; + + // Remove from its actor first + if (InComponent->GetOwner()) + InComponent->GetOwner()->RemoveOwnedComponent(InComponent); + + // Detach from its parent component if attached + USceneComponent* SceneComponent = Cast(InComponent); + if (IsValid(SceneComponent)) + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + InComponent->UnregisterComponent(); + InComponent->DestroyComponent(); + + return true; +} + +FName +FHoudiniEngineBakeUtils::GetOutputFolderPath(UObject* InOutputOwner) +{ + // Get an output folder path for PDG outputs generated by InOutputOwner. + // The folder path is: / + FString FolderName; + FName FolderDirName; + AActor* OuterActor = Cast(InOutputOwner); + if (OuterActor) + { + FolderName = OuterActor->GetActorLabel(); + FolderDirName = OuterActor->GetFolderPath(); + } + else + { + FolderName = InOutputOwner->GetName(); + } + if (!FolderDirName.IsNone()) + return FName(FString::Printf(TEXT("%s/%s"), *FolderDirName.ToString(), *FolderName)); + else + return FName(FolderName); +} + +void +FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique) +{ + FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); + + const FSoftObjectPath OldPath = FSoftObjectPath(InAsset); + + FString NewName; + if (bMakeUniqueIfNotUnique) + NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetPackage(), InAsset->GetClass(), InNewName, InAsset); + else + NewName = InNewName; + + FHoudiniEngineUtils::RenameObject(InAsset, *NewName); + + const FSoftObjectPath NewPath = FSoftObjectPath(InAsset); + if (OldPath != NewPath) + { + TArray RenameData; + RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); + AssetToolsModule.Get().RenameAssets(RenameData); + } +} + +void +FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique) +{ + if (!IsValid(InActor)) + return; + + FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); + + const FSoftObjectPath OldPath = FSoftObjectPath(InActor); + + FString NewName; + if (bMakeUniqueIfNotUnique) + NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), InNewName, InActor); + else + NewName = InNewName; + + FHoudiniEngineUtils::RenameObject(InActor, *NewName); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName); + + const FSoftObjectPath NewPath = FSoftObjectPath(InActor); + if (OldPath != NewPath) + { + TArray RenameData; + RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); + AssetToolsModule.Get().RenameAssets(RenameData); + } +} + +bool +FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor( + AActor* InActor, + const FString& InNewName, + const FName& InFolderPath) +{ + if (!IsValid(InActor)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: InActor is null.")); + return false; + } + + if (InNewName.TrimStartAndEnd().IsEmpty()) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: A valid actor name must be specified.")); + return false; + } + + // Detach from parent + InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + // Rename + const bool bMakeUniqueIfNotUnique = true; + RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique); + + InActor->SetFolderPath(InFolderPath); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultArrayIndex, + int32 InWorkResultObjectArrayIndex, + bool bInReplaceActors, + bool bInReplaceAssets, + bool bInBakeToWorkResultActor, + bool bInIsAutoBake, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!IsValid(InPDGAssetLink)) + return false; + + if (!IsValid(InNode)) + return false; + + if (!InNode->WorkResult.IsValidIndex(InWorkResultArrayIndex)) + return false; + + FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultArrayIndex]; + if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) + return false; + + FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectArrayIndex]; + TArray& Outputs = WorkResultObject.GetResultOutputs(); + if (Outputs.Num() == 0) + return true; + + if (WorkResultObject.State != EPDGWorkResultState::Loaded) + { + if (bInIsAutoBake && WorkResultObject.AutoBakedSinceLastLoad()) + { + HOUDINI_LOG_MESSAGE(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded but was auto-baked since its last load."), *WorkResultObject.Name); + return true; + } + + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded, cannot bake it."), *WorkResultObject.Name); + return false; + } + + AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor(); + if (!IsValid(WorkResultObjectActor)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObjectActor (%s) is null (unexpected since # Outputs > 0)"), *WorkResultObject.Name); + return false; + } + + // OutBakedActors contains each actor that contains baked PDG results. Actors may + // appear in the array more than once if they have more than one baked result/component associated with + // them + // TArray BakedActorsForWorkResultObject; + const int32 NumBakedPre = OutBakedActors.Num(); + const FString HoudiniAssetName(WorkResultObject.Name); + + // Find the previous bake output for this work result object + FString Key; + InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key); + FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key); + + const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InPDGAssetLink); + check(IsValid(HoudiniAssetComponent)); + + BakeHoudiniOutputsToActors( + HoudiniAssetComponent, + Outputs, + BakedOutputContainer.BakedOutputs, + HoudiniAssetName, + WorkResultObjectActor->GetActorTransform(), + InPDGAssetLink->BakeFolder, + InPDGAssetLink->GetTemporaryCookFolder(), + bInReplaceActors, + bInReplaceAssets, + OutBakedActors, + OutPackagesToSave, + OutBakeStats, + InOutputTypesToBake, + InInstancerComponentTypesToBake, + bInBakeToWorkResultActor ? WorkResultObjectActor : nullptr, + InFallbackWorldOutlinerFolder); + + // Set the PDG indices on the output baked actor entries + FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); + AActor* const WROActor = OutputActorOwner.GetOutputActor(); + FHoudiniEngineBakedActor const * BakedWROActorEntry = nullptr; + const int32 NumBakedPost = OutBakedActors.Num(); + if (NumBakedPost > NumBakedPre) + { + for (int32 Index = FMath::Max(0, NumBakedPre); Index < NumBakedPost; ++Index) + { + FHoudiniEngineBakedActor& BakedActorEntry = OutBakedActors[Index]; + BakedActorEntry.PDGWorkResultArrayIndex = InWorkResultArrayIndex; + BakedActorEntry.PDGWorkItemIndex = WorkResult.WorkItemIndex; + BakedActorEntry.PDGWorkResultObjectArrayIndex = InWorkResultObjectArrayIndex; + + if (WROActor && BakedActorEntry.Actor == WROActor) + { + BakedWROActorEntry = &BakedActorEntry; + } + } + } + + // If anything was baked to WorkResultObjectActor, detach it from its parent + if (bInBakeToWorkResultActor) + { + // if we re-used the temp actor as a bake actor, then remove its temp outputs + WorkResultObject.DestroyResultOutputs(); + if (WROActor) + { + if (BakedWROActorEntry) + { + OutputActorOwner.SetOutputActor(nullptr); + const FString OldActorPath = FSoftObjectPath(WROActor).ToString(); + DetachAndRenameBakedPDGOutputActor( + WROActor, BakedWROActorEntry->ActorBakeName.ToString(), BakedWROActorEntry->WorldOutlinerFolder); + const FString NewActorPath = FSoftObjectPath(WROActor).ToString(); + if (OldActorPath != NewActorPath) + { + // Fix cached string reference in baked outputs to WROActor + for (FHoudiniBakedOutput& BakedOutput : BakedOutputContainer.BakedOutputs) + { + for (auto& Entry : BakedOutput.BakedOutputObjects) + { + if (Entry.Value.Actor == OldActorPath) + Entry.Value.Actor = NewActorPath; + } + } + } + } + else + { + OutputActorOwner.DestroyOutputActor(); + } + } + } + + if (bInIsAutoBake) + WorkResultObject.SetAutoBakedSinceLastLoad(true); + // OutBakedActors.Append(BakedActorsForWorkResultObject); + return true; +} + +void +FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkItemHAPIIndex, + int32 InWorkItemResultInfoIndex) +{ + if (!IsValid(InPDGAssetLink)) + return; + + if (!InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded) + return; + + if (!IsValid(InNode)) + return; + + // Check if the node is ready for baking: all work items must be complete + bool bDoNotBake = false; + bool bPendingBakeItems = false; + if (!InNode->AreAllWorkItemsComplete() || InNode->AnyWorkItemsFailed()) + bDoNotBake = true; + + // Check if the node is ready for baking: all work items must be loaded + if (!bDoNotBake) + { + for (const FTOPWorkResult& WorkResult : InNode->WorkResult) + { + for (const FTOPWorkResultObject& WRO : WorkResult.ResultObjects) + { + if (WRO.State != EPDGWorkResultState::Loaded && !WRO.AutoBakedSinceLastLoad()) + { + bDoNotBake = true; + break; + } + } + if (bDoNotBake) + break; + } + } + + if (!bDoNotBake) + { + // Check which outputs are selected for baking: selected node, selected network or all + // And only bake if the node falls within the criteria + UTOPNetwork const * const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + UTOPNode const * const SelectedTOPNode = InPDGAssetLink->GetSelectedTOPNode(); + switch (InPDGAssetLink->PDGBakeSelectionOption) + { + case EPDGBakeSelectionOption::SelectedNetwork: + if (!IsValid(SelectedTOPNetwork) || !InNode->IsParentTOPNetwork(SelectedTOPNetwork)) + { + HOUDINI_LOG_WARNING( + TEXT("Not baking Node %s (Net %s): not in selected network"), + InNode ? *InNode->GetName() : TEXT(""), + SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); + bDoNotBake = true; + } + break; + case EPDGBakeSelectionOption::SelectedNode: + if (InNode != SelectedTOPNode) + { + HOUDINI_LOG_WARNING( + TEXT("Not baking Node %s (Net %s): not the selected node"), + InNode ? *InNode->GetName() : TEXT(""), + SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); + bDoNotBake = true; + } + break; + case EPDGBakeSelectionOption::All: + default: + break; + } + } + + // If there are no nodes left to auto-bake, broadcast the onpostbake delegate + if (bDoNotBake && !InPDGAssetLink->AnyRemainingAutoBakeNodes()) + InPDGAssetLink->HandleOnPostBake(true); + + if (bDoNotBake) + return; + + bool bSuccess = false; + const bool bIsAutoBake = true; + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + bSuccess = FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InNode, bIsAutoBake, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + bSuccess = FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(InPDGAssetLink, InNode, bIsAutoBake, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); + break; + + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported HoudiniEngineBakeOption %i"), InPDGAssetLink->HoudiniEngineBakeOption); + } + + if (!InPDGAssetLink->AnyRemainingAutoBakeNodes()) + InPDGAssetLink->HandleOnPostBake(bSuccess); +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNode)) + return false; + + // Determine the output world outliner folder path via the PDG asset link's + // owner's folder path and name + UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); + if (!PDGOwner) + PDGOwner = InPDGAssetLink->GetOuter(); + const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); + + // Determine the actor/package replacement settings + const bool bReplaceActors = !bInBakeForBlueprint && InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + const bool bReplaceAssets = InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + // Determine the output types to bake: don't bake landscapes in blueprint baking mode + TArray OutputTypesToBake; + TArray InstancerComponentTypesToBake; + if (bInBakeForBlueprint) + { + OutputTypesToBake.Add(EHoudiniOutputType::Mesh); + OutputTypesToBake.Add(EHoudiniOutputType::Instancer); + OutputTypesToBake.Add(EHoudiniOutputType::Curve); + + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent); + } + + const int32 NumWorkResults = InNode->WorkResult.Num(); + FScopedSlowTask Progress(NumWorkResults, FText::FromString(FString::Printf(TEXT("Baking PDG Node Output %s ..."), *InNode->GetName()))); + Progress.MakeDialog(); + for (int32 WorkResultArrayIdx = 0; WorkResultArrayIdx < NumWorkResults; ++WorkResultArrayIdx) + { + FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultArrayIdx]; + const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); + for (int32 WorkResultObjectArrayIdx = 0; WorkResultObjectArrayIdx < NumWorkResultObjects; ++WorkResultObjectArrayIdx) + { + Progress.EnterProgressFrame(1.0f); + + BakePDGWorkResultObject( + InPDGAssetLink, + InNode, + WorkResultArrayIdx, + WorkResultObjectArrayIdx, + bReplaceActors, + bReplaceAssets, + !bInBakeForBlueprint, + bInIsAutoBake, + OutBakedActors, + OutPackagesToSave, + OutBakeStats, + OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, + InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, + FallbackWorldOutlinerFolderPath.ToString() + ); + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) +{ + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + TArray BakedActors; + + const bool bBakeBlueprints = false; + + bool bSuccess = BakePDGTOPNodeOutputsKeepActors( + InPDGAssetLink, InTOPNode, bBakeBlueprints, bInIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); + + SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && BakedActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : BakedActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && BakedActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + TArray& BakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNetwork)) + return false; + + bool bSuccess = true; + for (UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, bInIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, OutPackagesToSave, OutBakeStats); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + TArray BakedActors; + + const bool bBakeBlueprints = false; + const bool bIsAutoBake = false; + + bool bSuccess = true; + switch(InBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) + { + if (!IsValid(Network)) + continue; + + for (UTOPNode* Node : Network->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); + } + } + break; + case EPDGBakeSelectionOption::SelectedNetwork: + bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); + case EPDGBakeSelectionOption::SelectedNode: + bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); + } + + SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && BakedActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : BakedActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && BakedActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + // Broadcast that the bake is complete + InPDGAssetLink->HandleOnPostBake(bSuccess); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( + const TArray& InBakedActors, + bool bInRecenterBakedActors, + bool bInReplaceAssets, + const FString& InAssetName, + const FDirectoryPath& InBakeFolder, + TArray* const InNonPDGBakedOutputs, + TMap* const InPDGBakedOutputs, + TArray& OutBlueprints, + TArray& OutPackagesToSave) +{ + // // Clear selection + // if (GEditor) + // { + // GEditor->SelectNone(false, true); + // GEditor->NoteSelectionChange(); + // } + + // Iterate over the baked actors. An actor might appear multiple times if multiple OutputComponents were + // baked to the same actor, so keep track of actors we have already processed in BakedActorSet + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem); + TArray AssetsToReOpenEditors; + TSet BakedActorSet; + + for (const FHoudiniEngineBakedActor& Entry : InBakedActors) + { + AActor *Actor = Entry.Actor; + + if (!Actor || Actor->IsPendingKill()) + continue; + + if (BakedActorSet.Contains(Actor)) + continue; + + BakedActorSet.Add(Actor); + + UObject* Asset = nullptr; + + // Recenter the actor to its bounding box center + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Actor); + + // Create package for out Blueprint + FString BlueprintName; + + // For instancers we determine the bake folder from the instancer, + // for everything else we use the baked object's bake folder + // If all of that is blank, we fall back to InBakeFolder. + FString BakeFolderPath; + if (Entry.bInstancerOutput) + BakeFolderPath = Entry.InstancerPackageParams.BakeFolder; + else + BakeFolderPath = Entry.BakeFolderPath; + if (BakeFolderPath.IsEmpty()) + BakeFolderPath = InBakeFolder.Path; + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + FHoudiniOutputObjectIdentifier(), + BakeFolderPath, + Entry.ActorBakeName.ToString() + "_BP", + InAssetName, + AssetPackageReplaceMode); + + // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment + // is consistent with the bake counter + int32 BakeCounter = 0; + UBlueprint* InPreviousBlueprint = nullptr; + FHoudiniBakedOutputObject* BakedOutputObject = nullptr; + FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; + // Get the baked output object + if (Entry.PDGWorkResultArrayIndex >= 0 && Entry.PDGWorkItemIndex >= 0 && Entry.PDGWorkResultObjectArrayIndex >= 0 && InPDGBakedOutputs) + { + const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkItemIndex, Entry.PDGWorkResultObjectArrayIndex); + WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); + if (WorkResultObjectBakedOutput) + { + if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + } + else if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs) + { + if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs->IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = (*InNonPDGBakedOutputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + if (BakedOutputObject) + { + InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); + if (IsValid(InPreviousBlueprint)) + { + if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint)) + { + PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter); + } + } + } + + UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter); + + if (!Package || Package->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName())); + continue; + } + + if (!Package->IsFullyLoaded()) + Package->FullyLoad(); + + //Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false); + // Find existing asset first (only relevant if we are in replacement mode). If the existing asset has a + // different base class than the incoming actor, we reparent the blueprint to the new base class before + // clearing the SCS graph and repopulating it from the temp actor. + Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName)); + if (IsValid(Asset)) + { + UBlueprint* Blueprint = Cast(Asset); + if (IsValid(Blueprint)) + { + if (Blueprint->GeneratedClass && Blueprint->GeneratedClass != Actor->GetClass()) + { + // Close editors opened on existing asset if applicable + if (Asset && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Asset, false) != nullptr) + { + AssetEditorSubsystem->CloseAllEditorsForAsset(Asset); + AssetsToReOpenEditors.Add(Asset); + } + + Blueprint->ParentClass = Actor->GetClass(); + + FBlueprintEditorUtils::RefreshAllNodes(Blueprint); + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + FKismetEditorUtilities::CompileBlueprint(Blueprint); + } + } + } + else if (Asset && Asset->IsPendingKill()) + { + // Rename to pending kill so that we can use the desired name + const FString AssetPendingKillName(BlueprintName + "_PENDING_KILL"); + // Asset->Rename(*MakeUniqueObjectNameIfNeeded(Package, UBlueprint::StaticClass(), AssetPendingKillName).ToString()); + RenameAsset(Asset, AssetPendingKillName, true); + Asset = nullptr; + } + + if (!Asset) + { + UBlueprintFactory* Factory = NewObject(); + Factory->ParentClass = Actor->GetClass(); + + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + Asset = AssetToolsModule.Get().CreateAsset( + BlueprintName, PackageParams.GetPackagePath(), + UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + } + + UBlueprint* Blueprint = Cast(Asset); + + if (!Blueprint || Blueprint->IsPendingKill()) + { + HOUDINI_LOG_WARNING( + TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."), + *(InBakeFolder.Path), *BlueprintName); + + continue; + } + + // Close editors opened on existing asset if applicable + if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr) + { + AssetEditorSubsystem->CloseAllEditorsForAsset(Blueprint); + AssetsToReOpenEditors.Add(Blueprint); + } + + // Record the blueprint as the previous bake blueprint + if (BakedOutputObject) + BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString(); + + OutBlueprints.Add(Blueprint); + + // Clear old Blueprint Node tree + { + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + + int32 NodeSize = SCS->GetAllNodes().Num(); + for (int32 n = NodeSize - 1; n >= 0; --n) + SCS->RemoveNode(SCS->GetAllNodes()[n]); + } + + FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint); + + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + World->EditorDestroyActor(Actor, true); + + // Save the created BP package. + Package->MarkPackageDirty(); + OutPackagesToSave.Add(Package); + } + + // Re-open asset editors for updated blueprints that were open in editors + if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0) + { + for (UObject* Asset : AssetsToReOpenEditors) + { + if (IsValid(Asset)) + { + AssetEditorSubsystem->OpenEditorForAsset(Asset); + } + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + TArray BPActors; + + if (!IsValid(InPDGAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InPDGAssetLink is null")); + return false; + } + + if (!IsValid(InNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InNode is null")); + return false; + } + + const bool bReplaceAssets = InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + // Bake PDG output to new actors + // bInBakeForBlueprint == true will skip landscapes and instanced actor components + const bool bInBakeForBlueprint = true; + TArray BakedActors; + bool bSuccess = BakePDGTOPNodeOutputsKeepActors( + InPDGAssetLink, + InNode, + bInBakeForBlueprint, + bInIsAutoBake, + InPDGBakePackageReplaceMode, + BakedActors, + OutPackagesToSave, + OutBakeStats + ); + + if (bSuccess) + { + bSuccess = BakeBlueprintsFromBakedActors( + BakedActors, + bInRecenterBakedActors, + bReplaceAssets, + InPDGAssetLink->AssetName, + InPDGAssetLink->BakeFolder, + nullptr, + &InNode->GetBakedWorkResultObjectsOutputs(), + OutBlueprints, + OutPackagesToSave); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) +{ + TArray Blueprints; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + const bool bSuccess = BakePDGTOPNodeBlueprints( + InPDGAssetLink, + InTOPNode, + bInIsAutoBake, + InPDGBakePackageReplaceMode, + bInRecenterBakedActors, + Blueprints, + PackagesToSave, + BakeStats); + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNetwork)) + return false; + + const bool bIsAutoBake = false; + bool bSuccess = true; + for (UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, InPDGBakePackageReplaceMode, bInRecenterBakedActors, OutBlueprints, OutPackagesToSave, OutBakeStats); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) +{ + TArray Blueprints; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + const bool bIsAutoBake = false; + bool bSuccess = true; + switch(InBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) + { + if (!IsValid(Network)) + continue; + + for (UTOPNode* Node : Network->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, InPDGBakePackageReplaceMode, bInRecenterBakedActors, Blueprints, PackagesToSave, BakeStats); + } + } + break; + case EPDGBakeSelectionOption::SelectedNetwork: + bSuccess &= BakePDGTOPNetworkBlueprints( + InPDGAssetLink, + InPDGAssetLink->GetSelectedTOPNetwork(), + InPDGBakePackageReplaceMode, + bInRecenterBakedActors, + Blueprints, + PackagesToSave, + BakeStats); + case EPDGBakeSelectionOption::SelectedNode: + bSuccess &= BakePDGTOPNodeBlueprints( + InPDGAssetLink, + InPDGAssetLink->GetSelectedTOPNode(), + bIsAutoBake, + InPDGBakePackageReplaceMode, + bInRecenterBakedActors, + Blueprints, + PackagesToSave, + BakeStats); + } + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Broadcast that the bake is complete + InPDGAssetLink->HandleOnPostBake(bSuccess); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + const FString& InLevelPath, + ULevel*& OutDesiredLevel, + UWorld*& OutDesiredWorld, + bool& OutCreatedPackage) +{ + OutDesiredLevel = nullptr; + OutDesiredWorld = nullptr; + if (InLevelPath.IsEmpty()) + { + OutDesiredWorld = GWorld; + OutDesiredLevel = GWorld->GetCurrentLevel(); + } + else + { + OutCreatedPackage = false; + + UWorld* FoundWorld = nullptr; + ULevel* FoundLevel = nullptr; + bool bActorInWorld = false; + if (FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + GWorld, + InLevelPath, + true, + FoundWorld, + FoundLevel, + OutCreatedPackage, + bActorInWorld)) + { + OutDesiredLevel = FoundLevel; + OutDesiredWorld = FoundWorld; + } + } + + return ((OutDesiredWorld != nullptr) && (OutDesiredLevel != nullptr)); +} + + +bool +FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( + const FString& InBakeActorName, + ULevel* InLevel, + AActor*& OutActor, + bool bInNoPendingKillActors, + bool bRenamePendingKillActor) +{ + OutActor = nullptr; + + if (!IsValid(InLevel)) + return false; + + UWorld* const World = InLevel->GetWorld(); + if (!IsValid(World)) + return false; + + // Look for an actor with the given name in the world + const FName BakeActorFName(InBakeActorName); + AActor* FoundActor = Cast(StaticFindObjectFast(AActor::StaticClass(), InLevel, BakeActorFName)); + // for (TActorIterator Iter(World, AActor::StaticClass(), EActorIteratorFlags::AllActors); Iter; ++Iter) + // { + // AActor* const Actor = *Iter; + // if (Actor->GetFName() == BakeActorFName && Actor->GetLevel() == InLevel) + // { + // // Found the actor + // FoundActor = Actor; + // break; + // } + // } + + // If we found an actor and it is pending kill, rename it and don't use it + if (FoundActor) + { + if (FoundActor->IsPendingKill()) + { + if (bRenamePendingKillActor) + { + // FoundActor->Rename( + // *MakeUniqueObjectNameIfNeeded( + // FoundActor->GetOuter(), + // FoundActor->GetClass(), + // FName(FoundActor->GetName() + "_Pending_Kill")).ToString()); + RenameAndRelabelActor( + FoundActor, + *MakeUniqueObjectNameIfNeeded( + FoundActor->GetOuter(), + FoundActor->GetClass(), + FoundActor->GetName() + "_Pending_Kill", + FoundActor), + false); + } + if (bInNoPendingKillActors) + FoundActor = nullptr; + else + OutActor = FoundActor; + } + else + { + OutActor = FoundActor; + } + } + + return true; +} + +bool FHoudiniEngineBakeUtils::FindUnrealBakeActor( + const FHoudiniOutputObject& InOutputObject, + const FHoudiniBakedOutputObject& InBakedOutputObject, + const TArray& InAllBakedActors, + ULevel* InLevel, + FName InDefaultActorName, + bool bInReplaceActorBakeMode, + AActor* InFallbackActor, + AActor*& OutFoundActor, + bool& bOutHasBakeActorName, + FName& OutBakeActorName) +{ + // Determine desired actor name via unreal_output_actor, fallback to InDefaultActorName + OutBakeActorName = NAME_None; + OutFoundActor = nullptr; + bOutHasBakeActorName = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_ACTOR); + if (bOutHasBakeActorName) + { + const FString& BakeActorNameStr = InOutputObject.CachedAttributes[HAPI_UNREAL_ATTRIB_BAKE_ACTOR]; + if (BakeActorNameStr.IsEmpty()) + { + OutBakeActorName = NAME_None; + bOutHasBakeActorName = false; + } + else + { + OutBakeActorName = FName(BakeActorNameStr, NAME_NO_NUMBER_INTERNAL); + // We have a bake actor name, look for the actor + AActor* BakeNameActor = nullptr; + if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor)) + { + // Found an actor with that name, check that we "own" it (we created in during baking previously) + AActor* IncrementedBakedActor = nullptr; + for (const FHoudiniEngineBakedActor& BakedActor : InAllBakedActors) + { + if (!IsValid(BakedActor.Actor)) + continue; + if (BakedActor.Actor == BakeNameActor) + { + OutFoundActor = BakeNameActor; + break; + } + else if (!IncrementedBakedActor && BakedActor.ActorBakeName == OutBakeActorName) + { + // Found an actor we have baked named OutBakeActorName_# (incremental version of our desired name) + IncrementedBakedActor = BakedActor.Actor; + } + } + if (!OutFoundActor && IncrementedBakedActor) + OutFoundActor = IncrementedBakedActor; + } + } + } + + // If unreal_actor_name is not set, or is blank, fallback to InDefaultActorName + if (!bOutHasBakeActorName || (OutBakeActorName.IsNone() || OutBakeActorName.ToString().TrimStartAndEnd().IsEmpty())) + OutBakeActorName = InDefaultActorName; + + if (!OutFoundActor) + { + // If in replace mode, use previous bake actor if valid and in InLevel + if (bInReplaceActorBakeMode) + { + const FSoftObjectPath PrevActorPath(InBakedOutputObject.Actor); + const FString ActorPath = PrevActorPath.IsSubobject() + ? PrevActorPath.GetAssetPathString() + ":" + PrevActorPath.GetSubPathString() + : PrevActorPath.GetAssetPathString(); + const FString LevelPath = IsValid(InLevel) ? InLevel->GetPathName() : ""; + if (PrevActorPath.IsValid() && (LevelPath.IsEmpty() || ActorPath.StartsWith(LevelPath))) + OutFoundActor = InBakedOutputObject.GetActorIfValid(); + } + + // Fallback to InFallbackActor if valid and in InLevel + if (!OutFoundActor && IsValid(InFallbackActor) && (!InLevel || InFallbackActor->GetLevel() == InLevel)) + OutFoundActor = InFallbackActor; + } + + return true; +} + +AActor* +FHoudiniEngineBakeUtils::FindExistingActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + bCreatedPackage = false; + + // Try to Locate a previous actor + AActor* FoundActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); + if (FoundActor) + FoundActor->Destroy(); // nuke it! + + if (FoundActor) + { + // TODO: make sure that the found is actor is actually assigned to the level defined by package path. + // If the found actor is not from that level, it should be moved there. + + OutWorld = FoundActor->GetWorld(); + OutLevel = FoundActor->GetLevel(); + } + else + { + // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. + bool bActorInWorld = false; + const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + InWorld, + InPackagePath, + true, + OutWorld, + OutLevel, + bCreatedPackage, + bActorInWorld); + + if (!bResult) + { + return nullptr; + } + + if (!bActorInWorld) + { + // The OutLevel is not present in the current world which means we might + // still find the tile actor in OutWorld. + FoundActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); + } + } + + return FoundActor; +} + +bool +FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh( + UHoudiniAssetComponent* InHoudiniAssetComponent, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors, + bool& bOutNeedsReCook) +{ + if (!IsValid(InHoudiniAssetComponent)) + { + return false; + } + + // Handle proxies: if the output has any current proxies, first refine them + bOutNeedsReCook = false; + if (InHoudiniAssetComponent->HasAnyCurrentProxyOutput()) + { + bool bNeedsRebuildOrDelete; + bool bInvalidState; + const bool bCookedDataAvailable = InHoudiniAssetComponent->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bInvalidState); + + if (bCookedDataAvailable) + { + // Cook data is available, refine the mesh + AHoudiniAssetActor* HoudiniActor = Cast(InHoudiniAssetComponent->GetOwner()); + if (IsValid(HoudiniActor)) + { + FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ HoudiniActor }); + } + } + else if (!bNeedsRebuildOrDelete && !bInvalidState) + { + // A cook is needed: request the cook, but with no proxy and with a bake after cook + InHoudiniAssetComponent->SetNoProxyMeshNextCookRequested(true); + // Only + if (!InHoudiniAssetComponent->IsBakeAfterNextCookEnabled() || !InHoudiniAssetComponent->GetOnPostCookBakeDelegate().IsBound()) + { + InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors](UHoudiniAssetComponent* InHAC) { + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors); + }); + } + InHoudiniAssetComponent->MarkAsNeedCook(); + + bOutNeedsReCook = true; + + // The cook has to complete first (asynchronously) before the bake can happen + // The SetBakeAfterNextCookEnabled flag will result in a bake after cook + return false; + } + else + { + // The HAC is in an unsupported state + const EHoudiniAssetState AssetState = InHoudiniAssetComponent->GetAssetState(); + HOUDINI_LOG_ERROR(TEXT("Could not refine (in order to bake) %s, the asset is in an unsupported state: %s"), *(InHoudiniAssetComponent->GetPathName()), *(UEnum::GetValueAsString(AssetState))); + return false; + } + } + + return true; +} + +void +FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor) +{ + if (!IsValid(InActor)) + return; + + USceneComponent * const RootComponent = InActor->GetRootComponent(); + if (!IsValid(RootComponent)) + return; + + // If the root component does not have any child components, then there is nothing to recenter + if (RootComponent->GetNumChildrenComponents() <= 0) + return; + + const bool bOnlyCollidingComponents = false; + const bool bIncludeFromChildActors = true; + FVector Origin; + FVector BoxExtent; + // InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); + FBox Box(ForceInit); + + InActor->ForEachComponent(bIncludeFromChildActors, [&](const UPrimitiveComponent* InPrimComp) + { + // Only use non-editor-only components for the bounds calculation (to exclude things like editor only sprite/billboard components) + if (InPrimComp->IsRegistered() && !InPrimComp->IsEditorOnly() && + (!bOnlyCollidingComponents || InPrimComp->IsCollisionEnabled())) + { + Box += InPrimComp->Bounds.GetBox(); + } + }); + Box.GetCenterAndExtents(Origin, BoxExtent); + + const FVector Delta = Origin - RootComponent->GetComponentLocation(); + // Actor->SetActorLocation(Origin); + RootComponent->SetWorldLocation(Origin); + + for (USceneComponent* SceneComponent : RootComponent->GetAttachChildren()) + { + if (!IsValid(SceneComponent)) + continue; + + SceneComponent->SetWorldLocation(SceneComponent->GetComponentLocation() - Delta); + } +} + +void +FHoudiniEngineBakeUtils::CenterActorsToBoundingBoxCenter(const TArray& InActors) +{ + for (AActor* Actor : InActors) + { + if (!IsValid(Actor)) + continue; + + CenterActorToBoundingBoxCenter(Actor); + } +} + +USceneComponent* +FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMissing, EComponentMobility::Type InMobilityIfCreated) +{ + USceneComponent* RootComponent = InActor->GetRootComponent(); + if (!IsValid(RootComponent)) + { + RootComponent = NewObject(InActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); + + // Change the creation method so the component is listed in the details panels + InActor->SetRootComponent(RootComponent); + InActor->AddInstanceComponent(RootComponent); + RootComponent->RegisterComponent(); + RootComponent->SetMobility(InMobilityIfCreated); + } + + return RootComponent; +} + +FString +FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed) +{ + if (IsValid(InObjectThatWouldBeRenamed)) + { + const FName CurrentName = InObjectThatWouldBeRenamed->GetFName(); + if (CurrentName.ToString() == InName) + return InName; + + // Check if the prefix matches (without counter suffix) the new name + // In other words, if InName is 'my_actor' and the object is already an increment of it, 'my_actor_5' then + // don't we can just keep the current name + if (CurrentName.GetPlainNameString() == InName) + return CurrentName.ToString(); + } + + UObject* ExistingObject = nullptr; + FName CandidateName(InName); + bool bAppendedNumber = false; + // Do our own loop for generate suffixes as sequentially as possible. If this turns out to be expensive we can + // revert to MakeUniqueObjectName. + // return MakeUniqueObjectName(InOuter, InClass, CandidateName).ToString(); + do + { + if (InOuter == ANY_PACKAGE) + { + ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *(CandidateName.ToString())); + } + else + { + ExistingObject = StaticFindObjectFast(nullptr, InOuter, CandidateName); + } + + if (ExistingObject) + { + if (!bAppendedNumber) + { + const bool bSplitName = false; + CandidateName = FName(*InName, NAME_EXTERNAL_TO_INTERNAL(1), FNAME_Add, bSplitName); + bAppendedNumber = true; + } + else + { + CandidateName.SetNumber(CandidateName.GetNumber() + 1); + } + // CandidateName = FString::Printf(TEXT("%s_%d"), *InName, ++Counter); + } + } while (ExistingObject); + + return CandidateName.ToString(); +} + +FName +FHoudiniEngineBakeUtils::GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) +{ + const FString* FolderPathPtr = InOutputObject.CachedAttributes.Find(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER); + if (FolderPathPtr && !FolderPathPtr->IsEmpty()) + return FName(*FolderPathPtr); + else + return InDefaultFolder; +} + +bool +FHoudiniEngineBakeUtils::SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) +{ + if (!IsValid(InActor)) + return false; + + InActor->SetFolderPath(GetOutlinerFolderPath(InOutputObject, InDefaultFolder)); + return true; +} + +uint32 +FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInDestroyBakedComponent, + bool bInDestroyBakedInstancedActors, + bool bInDestroyBakedInstancedComponents) +{ + uint32 NumDeleted = 0; + + if (bInDestroyBakedComponent) + { + UActorComponent* Component = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (Component) + { + if (RemovePreviouslyBakedComponent(Component)) + { + InBakedOutputObject.BakedComponent = nullptr; + NumDeleted++; + } + } + } + + if (bInDestroyBakedInstancedActors) + { + for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) + { + const FSoftObjectPath ActorPath(ActorPathStr); + + if (!ActorPath.IsValid()) + continue; + + AActor* Actor = Cast(ActorPath.TryLoad()); + if (IsValid(Actor)) + { + UWorld* World = Actor->GetWorld(); + if (IsValid(World)) + { +#if WITH_EDITOR + World->EditorDestroyActor(Actor, true); +#else + World->DestroyActor(Actor); +#endif + NumDeleted++; + } + } + } + InBakedOutputObject.InstancedActors.Empty(); + } + + if (bInDestroyBakedInstancedComponents) + { + for (const FString& ComponentPathStr : InBakedOutputObject.InstancedComponents) + { + const FSoftObjectPath ComponentPath(ComponentPathStr); + + if (!ComponentPath.IsValid()) + continue; + + UActorComponent* Component = Cast(ComponentPath.TryLoad()); + if (IsValid(Component)) + { + if (RemovePreviouslyBakedComponent(Component)) + NumDeleted++; + } + } + InBakedOutputObject.InstancedComponents.Empty(); + } + + return NumDeleted; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h index d51cb36d6..d03c95e1f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h @@ -1,694 +1,708 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniPDGAssetLink.h" -#include "HoudiniOutput.h" -#include "HoudiniPackageParams.h" - -class UHoudiniAssetComponent; -class UHoudiniOutput; -class ALandscapeProxy; -class UStaticMesh; -class USplineComponent; -class UPackage; -class UWorld; -class AActor; -class UHoudiniSplineComponent; -class UStaticMeshComponent; -class UHoudiniPDGAssetLink; -class UTOPNetwork; -class UTOPNode; - -struct FHoudiniPackageParams; -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniEngineOutputStats; -struct FHoudiniBakedOutputObject; -struct FHoudiniAttributeResolver; - -enum class EHoudiniLandscapeOutputBakeType : uint8; - -// An enum of the different types for instancer component/bake types -UENUM() -enum class EHoudiniInstancerComponentType : uint8 -{ - // Single static mesh component - StaticMeshComponent, - // (Hierarichal)InstancedStaticMeshComponent - InstancedStaticMeshComponent, - MeshSplitInstancerComponent, - InstancedActorComponent, - // For baking foliage as foliage - FoliageInstancedStaticMeshComponent, - // Baking foliage as HISMC - FoliageAsHierarchicalInstancedStaticMeshComponent -}; - -// Helper struct to track actors created/used when baking, with -// the intended bake name (before making it unique), and their -// output index and output object identifier. -struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor -{ - FHoudiniEngineBakedActor(); - - FHoudiniEngineBakedActor( - AActor* InActor, - FName InActorBakeName, - FName InWorldOutlinerFolder, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - UObject* InBakedObject, - UObject* InSourceObject, - UObject* InBakedComponent, - const FString& InBakeFolderPath, - const FHoudiniPackageParams& InBakedObjectPackageParams); - - // The actor that the baked output was associated with - AActor* Actor = nullptr; - - // The output index on the HAC for the baked object - int32 OutputIndex = INDEX_NONE; - - // The output object identifier for the baked object - FHoudiniOutputObjectIdentifier OutputObjectIdentifier; - - // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. - FName ActorBakeName = NAME_None; - - // The world outliner folder the actor is placed in - FName WorldOutlinerFolder = NAME_None; - - // The array index of the work result when baking PDG - int32 PDGWorkResultArrayIndex = INDEX_NONE; - - // The work item index (as returned by HAPI) for the work item/work result, used when baking PDG - int32 PDGWorkItemIndex = INDEX_NONE; - - // The array index of the work result object of the work result when baking PDG - int32 PDGWorkResultObjectArrayIndex = INDEX_NONE; - - // The baked primary asset (such as static mesh) - UObject* BakedObject = nullptr; - - // The temp asset that was baked to BakedObject - UObject* SourceObject = nullptr; - - // The baked component or foliage type in the case of foliage - UObject* BakedComponent = nullptr; - - // The bake folder path to where BakedObject was baked - FString BakeFolderPath; - - // The package params for the BakedObject - FHoudiniPackageParams BakedObjectPackageParams; - - // True if this entry was created by an instancer output. - bool bInstancerOutput; - - // The package params built for the instancer part of the output, if this was an instancer. - // This would mostly be useful in situations for we later need the resolver and/or cached attributes and - // tokens, such as for blueprint baking. - FHoudiniPackageParams InstancerPackageParams; - - // Used to delay all post bake calls so they are done only once per baked actor - bool bPostBakeProcessPostponed = false; - -}; - -struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils -{ -public: - - /** Bake static mesh. **/ - - /*static UStaticMesh * BakeStaticMesh( - UHoudiniAssetComponent * HoudiniAssetComponent, - UStaticMesh * InStaticMesh, - const FHoudiniPackageParams &PackageParams);*/ - - static ALandscapeProxy* BakeHeightfield( - ALandscapeProxy * InLandscapeProxy, - const FHoudiniPackageParams &PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType); - - static bool BakeCurve( - USplineComponent* InSplineComponent, - ULevel* InLevel, - const FHoudiniPackageParams &PackageParams, - AActor*& OutActor, - USplineComponent*& OutSplineComponent, - FName InOverrideFolderPath=NAME_None, - AActor* InActor=nullptr); - - static bool BakeCurve( - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - // const TArray& InAllBakedOutputs, - const FHoudiniPackageParams &PackageParams, - FHoudiniAttributeResolver& InResolver, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static AActor* BakeInputHoudiniCurveToActor( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PakcageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform); - - static UBlueprint* BakeInputHoudiniCurveToBlueprint( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PakcageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform); - - static UStaticMesh* BakeStaticMesh( - UStaticMesh * StaticMesh, - const FHoudiniPackageParams & PackageParams, - const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder); - - static bool BakeLandscape( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - bool bInReplaceActors, - bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, - FHoudiniEngineOutputStats& BakeStats); - - static bool BakeLandscapeObject( - FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInReplaceActors, - bool bInReplaceAssets, - FHoudiniPackageParams& PackageParams, - FHoudiniAttributeResolver& InResolver, - TArray& WorldsToUpdate, - TArray& OutPackagesToUnload, - FHoudiniEngineOutputStats& BakeStats); - - static bool BakeInstancerOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetcomponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FTransform& InTransform, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_ISMC( - const UHoudiniAssetComponent* HoudiniAssetcomponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_IAC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave); - - static bool BakeInstancerOutputToActors_MSIC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_SMC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static UStaticMesh * DuplicateStaticMeshAndCreatePackageIfNeeded( - UStaticMesh * InStaticMesh, - UStaticMesh * InPreviousBakeStaticMesh, - const FHoudiniPackageParams &PackageParams, - const TArray& InParentOutputs, - const TArray& InCurrentBakedActors, - const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages); - - static UMaterial * DuplicateMaterialAndCreatePackage( - UMaterial * Material, - UMaterial* PreviousBakeMaterial, - const FString & SubMaterialName, - const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutCreatedPackages); - - static void ReplaceDuplicatedMaterialTextureSample( - UMaterialExpression * MaterialExpression, - UMaterialExpression* PreviousBakeMaterialExpression, - const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); - - static UTexture2D * DuplicateTextureAndCreatePackage( - UTexture2D * Texture, - UTexture2D* PreviousBakeTexture, - const FString & SubTextureName, - const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); - - // Bake a Houdini asset component (InHACToBake) based on the bInReplace and BakeOption arguments. - // Returns true if the underlying bake function (for example, BakeHoudiniActorToActors, returns true (or a valid UObject*)) - static bool BakeHoudiniAssetComponent( - UHoudiniAssetComponent* InHACToBake, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess); - - static bool BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets); - - static bool BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeHoudiniOutputsToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - const TArray& InOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FTransform& InParentTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToFoliage( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave); - - static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); - - static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); - - static bool BakeStaticMeshOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeHoudiniCurveOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeBlueprintsFromBakedActors( - const TArray& InBakedActors, - bool bInRecenterBakedActors, - bool bInReplaceAssets, - const FString& InAssetName, - const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOutputs, - TMap* const InPDGBakedOutputs, - TArray& OutBlueprints, - TArray& OutPackagesToSave); - - static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); - - static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, FHoudiniEngineOutputStats& InBakeStats, TArray& OutBlueprints, TArray& OutPackagesToSave); - - static bool CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint); - - static void AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const TCHAR * Key, - const TCHAR * Value); - - static bool GetHoudiniGeneratedNameFromMetaInformation( - UPackage * Package, UObject * Object, FString & HoudiniName); - - static bool DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent); - - static void SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld = false); - - // Look for InObjectToFind among InOutputs. Return true if found and set OutOutputIndex and OutIdentifier. - static bool FindOutputObject( - const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); - - static bool IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC); - - static bool IsObjectTemporary( - UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); - - // Function used to copy properties from the source Static Mesh Component to the new (baked) one - static void CopyPropertyToNewActorAndComponent( - AActor* NewActor, - UStaticMeshComponent* NewSMC, - UStaticMeshComponent* InSMC, - bool bInCopyWorldTransform=false); - - // Finds the world/level indicated by the package path. - // If the level doesn't exists, it will be created. - // If InLevelPath is empty, outputs the editor world and current level - // Returns true if the world/level were found, false otherwise - static bool FindOrCreateDesiredLevelFromLevelPath( - const FString& InLevelPath, - ULevel*& OutDesiredLevel, - UWorld*& OutDesiredWorld, - bool& OutCreatedPackage); - - // Finds the actor indicated by InBakeActorName in InLevel. - // Returns false if any input was invalid (InLevel is invalid for example), true otherwise - // If an actor was found OutActor is set - // If bInNoPendingKillActors is true, then if an actor called InBakeActorName is found but is pending kill, then - // it is not set in OutActor - // If bRenamePendingKillActor is true, then if a pending kill actor call InBakeActorName is found it is renamed - // (uniquely) with a _Pending_Kill suffix (regardless of bInNoPendingKillActors). - static bool FindDesiredBakeActorFromBakeActorName( - const FString& InBakeActorName, - ULevel* InLevel, - AActor*& OutActor, - bool bInNoPendingKillActors=true, - bool bRenamePendingKillActor=true); - - // Helper that determines the desired bake actor name with unreal_bake_actor attribute, falling - // back to InDefaultActorName if the attribute is not set. - // If unreal_bake_actor is set, we look for such in InLevel, and use it *if* it is present in InAlLBakedOutputs. - // Otherwise if we are baking in replace mode, and the previous bake actor is available and in InLevel, return it - // as OutFoundActor. Otherwise return InFallbackActor as OutFoundActor. - // bOutHasBakeActorName indicates if the output has the unreal_bake_actor attribute set. - // OutFoundActor is the actor that was found (if one was found) - static bool FindUnrealBakeActor( - const FHoudiniOutputObject& InOutputObject, - const FHoudiniBakedOutputObject& InBakedOutputObject, - const TArray& InAllBakedActors, - ULevel* InLevel, - FName InDefaultActorName, - bool bInReplaceActorBakeMode, - AActor* InFallbackActor, - AActor*& OutFoundActor, - bool& bOutHasBakeActorName, - FName& OutBakeActorName); - - // Try to find an actor that we can use for baking. - // If the requested actor could not be found, then `OutWorld` and `OutLevel` - // should be used to spawn the new bake actor. - // @returns AActor* if found. Otherwise, returns nullptr. - static AActor* FindExistingActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - // Remove a previously baked actor - static bool RemovePreviouslyBakedActor( - AActor* InNewBakedActor, - ULevel* InLevel, - const FHoudiniPackageParams& InPackageParams); - - static bool RemovePreviouslyBakedComponent(UActorComponent* InComponent); - - // Get the world outliner folder path for output generated by InOutputOwner - static FName GetOutputFolderPath(UObject* InOutputOwner); - - static void RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); - - // Helper function for renaming and relabelling an actor - static void RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); - - // Start: PDG Baking - - // Detach InActor from its parent, and rename to InNewName (attaches a numeric suffix to make it unique via - // MakeUniqueObjectName). Place it in the world outliner folder InFolderPath. - static bool DetachAndRenameBakedPDGOutputActor(AActor* InActor, const FString& InNewName, const FName& InFolderPath); - - static bool BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultArrayIndex, - int32 InWorkResultObjectArrayIndex, - bool bInReplaceActors, - bool bInReplaceAssets, - bool bInBakeToWorkResultActor, - bool bInIsAutoBake, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - // Checks if auto-bake is enabled on InPDGAssetLink, and if it is, calls BakePDGWorkResultObject. - static void CheckPDGAutoBakeAfterResultObjectLoaded( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkItemHAPIIndex, - int32 InWorkItemResultInfoIndex); - - // Bake PDG output. This bakes all assets from all work items in the specified InNode (FTOPNode). - // It uses the existing output actors in the level, but breaks any links from these actors to the PDG link and - // moves the actors out of the parent Folder/ detaches from the parent PDG output actor. - static bool BakePDGTOPNodeOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInBakeForBlueprint, - bool bInIsAutoBake, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Helper function to bake only a specific PDG TOP node's outputs to actors. - static bool BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake); - - // Bake PDG output. This bakes all assets from all work items in the specified TOP network. - // It uses the existing output actors in the level, but breaks any links - // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent - // PDG output actor. - static bool BakePDGTOPNetworkOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - bool bInBakeForBlueprint, - bool bInIsAutoBake, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes assets from TOP networks and nodes according to - // InPDGAssetLink->PDGBakeSelectionOption. It uses the existing output actors in the level, but breaks any links - // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent - // PDG output actor. - static bool BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink); - - // Bake PDG output. This bakes all supported assets from all work items in the specified InNode (FTOPNode). - // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from - // PDG output actors. - static bool BakePDGTOPNodeBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInIsAutoBake, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Helper to bake only a specific PDG TOP node's outputs to blueprint(s). - static bool BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake); - - // Bake PDG output. This bakes all supported assets from all work items in the specified TOP network. - // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from - // PDG output actors. - static bool BakePDGTOPNetworkBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes assets from TOP networks and nodes according to - // InPDGAssetLink->PDGBakeSelectionOption. It duplicates the output actors and bakes them to blueprints. Assets - // that were baked are removed from PDG output actors. - static bool BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink); - - // End: PDG Baking - -protected: - - // Find the HGPO with matching identifier. Returns true if the HGPO was found. - static bool FindHGPO( - const FHoudiniOutputObjectIdentifier& InIdentifier, - const TArray& InHGPOs, - FHoudiniGeoPartObject const*& OutHGPO); - - // Set OutBakeName to the resolved output name of InMeshOutputObject / InObject. OutBakeName is set to the object's - // BakeName (the BakeName on the InMeshOutputObject, or if that is not set, the custom part name or finally the - // package name. - static void GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const FHoudiniOutputObject& InMeshOutputObject, - FString& OutBakeName); - - // Look for InObject in InAllOutputs. If found the function returns true and OutBakeName is set to the object's - // BakeName (the BakeName on the OutputObject, or if that is not set, the custom part name or finally the package - // name. - static bool GetTemporaryOutputObjectBakeName( - const UObject* InObject, - EHoudiniOutputType InOutputType, - const TArray& InAllOutputs, - FString& OutBakeName); - - // Checks if InHoudiniAssetComponent has any current proxy mesh. Refines if it possible. Returns true - // if baking can continue, false otherwise. If the component has a proxy, but no cook data, then false is - // returned, the component is set to recook without a proxy and with bake after cook, and bOutNeedsReCook is set - // to true. - // bInReplace and BakeOption represents the baking settings to use if a delayed bake (post-cook) needs to be triggered. - static bool CheckForAndRefineHoudiniProxyMesh( - UHoudiniAssetComponent* InHoudiniAssetComponent, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption BakeOption, - bool bInRemoveHACOutputOnSuccess, - bool& bOutNeedsReCook); - - // Position InActor at its bounding box center (keep components' world location) - static void CenterActorToBoundingBoxCenter(AActor* InActor); - - // Position each of the actors in InActors at its bounding box center (keep components' world location) - static void CenterActorsToBoundingBoxCenter(const TArray& InActors); - - // Helper to get or optionally create a RootComponent for an actor - static USceneComponent* GetActorRootComponent( - AActor* InActor, bool bCreateIfMissing=true, EComponentMobility::Type InMobilityIfCreated=EComponentMobility::Static); - - // Helper function to return a unique object name if the given is already in use - static FString MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed=nullptr); - - // Helper for getting the actor folder path for the world outliner, based unreal_bake_outliner_folder - static FName GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); - - // Helper for setting the actor folder path in the world outliner - static bool SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); - - // Helper for destroying previous bake components/actors - static uint32 DestroyPreviousBakeOutput( - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInDestroyBakedComponent, - bool bInDestroyBakedInstancedActors, - bool bInDestroyBakedInstancedComponents); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniPDGAssetLink.h" +#include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" + +class UHoudiniAssetComponent; +class UHoudiniOutput; +class ALandscapeProxy; +class UStaticMesh; +class USplineComponent; +class UPackage; +class UWorld; +class AActor; +class UHoudiniSplineComponent; +class UStaticMeshComponent; +class UHoudiniPDGAssetLink; +class UTOPNetwork; +class UTOPNode; + +struct FHoudiniPackageParams; +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniEngineOutputStats; +struct FHoudiniBakedOutputObject; +struct FHoudiniAttributeResolver; + +enum class EHoudiniLandscapeOutputBakeType : uint8; + +// An enum of the different types for instancer component/bake types +UENUM() +enum class EHoudiniInstancerComponentType : uint8 +{ + // Single static mesh component + StaticMeshComponent, + // (Hierarichal)InstancedStaticMeshComponent + InstancedStaticMeshComponent, + MeshSplitInstancerComponent, + InstancedActorComponent, + // For baking foliage as foliage + FoliageInstancedStaticMeshComponent, + // Baking foliage as HISMC + FoliageAsHierarchicalInstancedStaticMeshComponent +}; + +// Helper struct to track actors created/used when baking, with +// the intended bake name (before making it unique), and their +// output index and output object identifier. +struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor +{ + FHoudiniEngineBakedActor(); + + FHoudiniEngineBakedActor( + AActor* InActor, + FName InActorBakeName, + FName InWorldOutlinerFolder, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + UObject* InBakedObject, + UObject* InSourceObject, + UObject* InBakedComponent, + const FString& InBakeFolderPath, + const FHoudiniPackageParams& InBakedObjectPackageParams); + + // The actor that the baked output was associated with + AActor* Actor = nullptr; + + // The output index on the HAC for the baked object + int32 OutputIndex = INDEX_NONE; + + // The output object identifier for the baked object + FHoudiniOutputObjectIdentifier OutputObjectIdentifier; + + // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. + FName ActorBakeName = NAME_None; + + // The world outliner folder the actor is placed in + FName WorldOutlinerFolder = NAME_None; + + // The array index of the work result when baking PDG + int32 PDGWorkResultArrayIndex = INDEX_NONE; + + // The work item index (as returned by HAPI) for the work item/work result, used when baking PDG + int32 PDGWorkItemIndex = INDEX_NONE; + + // The array index of the work result object of the work result when baking PDG + int32 PDGWorkResultObjectArrayIndex = INDEX_NONE; + + // The baked primary asset (such as static mesh) + UObject* BakedObject = nullptr; + + // The temp asset that was baked to BakedObject + UObject* SourceObject = nullptr; + + // The baked component or foliage type in the case of foliage + UObject* BakedComponent = nullptr; + + // The bake folder path to where BakedObject was baked + FString BakeFolderPath; + + // The package params for the BakedObject + FHoudiniPackageParams BakedObjectPackageParams; + + // True if this entry was created by an instancer output. + bool bInstancerOutput; + + // The package params built for the instancer part of the output, if this was an instancer. + // This would mostly be useful in situations for we later need the resolver and/or cached attributes and + // tokens, such as for blueprint baking. + FHoudiniPackageParams InstancerPackageParams; + + // Used to delay all post bake calls so they are done only once per baked actor + bool bPostBakeProcessPostponed = false; + +}; + +struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils +{ +public: + + /** Bake static mesh. **/ + + /*static UStaticMesh * BakeStaticMesh( + UHoudiniAssetComponent * HoudiniAssetComponent, + UStaticMesh * InStaticMesh, + const FHoudiniPackageParams &PackageParams);*/ + + static ALandscapeProxy* BakeHeightfield( + ALandscapeProxy * InLandscapeProxy, + const FHoudiniPackageParams &PackageParams, + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType); + + static bool BakeCurve( + USplineComponent* InSplineComponent, + ULevel* InLevel, + const FHoudiniPackageParams &PackageParams, + AActor*& OutActor, + USplineComponent*& OutSplineComponent, + FName InOverrideFolderPath=NAME_None, + AActor* InActor=nullptr); + + static bool BakeCurve( + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + // const TArray& InAllBakedOutputs, + const FHoudiniPackageParams &PackageParams, + FHoudiniAttributeResolver& InResolver, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static AActor* BakeInputHoudiniCurveToActor( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PakcageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform); + + static UBlueprint* BakeInputHoudiniCurveToBlueprint( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PakcageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform); + + static UStaticMesh* BakeStaticMesh( + UStaticMesh * StaticMesh, + const FHoudiniPackageParams & PackageParams, + const TArray& InAllOutputs, + const FDirectoryPath& InTempCookFolder); + + static bool BakeLandscape( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + bool bInReplaceActors, + bool bInReplaceAssets, + FString BakePath, + FString HoudiniAssetName, + FHoudiniEngineOutputStats& BakeStats); + + static bool BakeLandscapeObject( + FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInReplaceActors, + bool bInReplaceAssets, + FHoudiniPackageParams& PackageParams, + FHoudiniAttributeResolver& InResolver, + TArray& WorldsToUpdate, + TArray& OutPackagesToUnload, + FHoudiniEngineOutputStats& BakeStats); + + static bool BakeInstancerOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetcomponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FTransform& InTransform, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_ISMC( + const UHoudiniAssetComponent* HoudiniAssetcomponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_IAC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave); + + static bool BakeInstancerOutputToActors_MSIC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_SMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static UStaticMesh * DuplicateStaticMeshAndCreatePackageIfNeeded( + UStaticMesh * InStaticMesh, + UStaticMesh * InPreviousBakeStaticMesh, + const FHoudiniPackageParams &PackageParams, + const TArray& InParentOutputs, + const TArray& InCurrentBakedActors, + const FString& InTemporaryCookFolder, + TArray & OutCreatedPackages); + + static UMaterial * DuplicateMaterialAndCreatePackage( + UMaterial * Material, + UMaterial* PreviousBakeMaterial, + const FString & SubMaterialName, + const FHoudiniPackageParams& ObjectPackageParams, + TArray & OutCreatedPackages); + + static void ReplaceDuplicatedMaterialTextureSample( + UMaterialExpression * MaterialExpression, + UMaterialExpression* PreviousBakeMaterialExpression, + const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages); + + static UTexture2D * DuplicateTextureAndCreatePackage( + UTexture2D * Texture, + UTexture2D* PreviousBakeTexture, + const FString & SubTextureName, + const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages); + + // Bake a Houdini asset component (InHACToBake) based on the bInReplace and BakeOption arguments. + // Returns true if the underlying bake function (for example, BakeHoudiniActorToActors, returns true (or a valid UObject*)) + static bool BakeHoudiniAssetComponent( + UHoudiniAssetComponent* InHACToBake, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors); + + static bool BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActors); + + static bool BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeHoudiniOutputsToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + const TArray& InOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FTransform& InParentTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToFoliage( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave); + + static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); + + static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); + + static bool BakeStaticMeshOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeHoudiniCurveOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeBlueprintsFromBakedActors( + const TArray& InBakedActors, + bool bInRecenterBakedActors, + bool bInReplaceAssets, + const FString& InAssetName, + const FDirectoryPath& InBakeFolder, + TArray* const InNonPDGBakedOutputs, + TMap* const InPDGBakedOutputs, + TArray& OutBlueprints, + TArray& OutPackagesToSave); + + static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, bool bInRecenterBakedActors); + + static bool BakeBlueprints( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceAssets, + bool bInRecenterBakedActors, + FHoudiniEngineOutputStats& InBakeStats, + TArray& OutBlueprints, + TArray& OutPackagesToSave); + + static bool CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint); + + static void AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const TCHAR * Key, + const TCHAR * Value); + + static bool GetHoudiniGeneratedNameFromMetaInformation( + UPackage * Package, UObject * Object, FString & HoudiniName); + + static bool DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent); + + static void SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld = false); + + // Look for InObjectToFind among InOutputs. Return true if found and set OutOutputIndex and OutIdentifier. + static bool FindOutputObject( + const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); + + static bool IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC); + + static bool IsObjectTemporary( + UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); + + // Function used to copy properties from the source Static Mesh Component to the new (baked) one + static void CopyPropertyToNewActorAndComponent( + AActor* NewActor, + UStaticMeshComponent* NewSMC, + UStaticMeshComponent* InSMC, + bool bInCopyWorldTransform=false); + + // Finds the world/level indicated by the package path. + // If the level doesn't exists, it will be created. + // If InLevelPath is empty, outputs the editor world and current level + // Returns true if the world/level were found, false otherwise + static bool FindOrCreateDesiredLevelFromLevelPath( + const FString& InLevelPath, + ULevel*& OutDesiredLevel, + UWorld*& OutDesiredWorld, + bool& OutCreatedPackage); + + // Finds the actor indicated by InBakeActorName in InLevel. + // Returns false if any input was invalid (InLevel is invalid for example), true otherwise + // If an actor was found OutActor is set + // If bInNoPendingKillActors is true, then if an actor called InBakeActorName is found but is pending kill, then + // it is not set in OutActor + // If bRenamePendingKillActor is true, then if a pending kill actor call InBakeActorName is found it is renamed + // (uniquely) with a _Pending_Kill suffix (regardless of bInNoPendingKillActors). + static bool FindDesiredBakeActorFromBakeActorName( + const FString& InBakeActorName, + ULevel* InLevel, + AActor*& OutActor, + bool bInNoPendingKillActors=true, + bool bRenamePendingKillActor=true); + + // Helper that determines the desired bake actor name with unreal_bake_actor attribute, falling + // back to InDefaultActorName if the attribute is not set. + // If unreal_bake_actor is set, we look for such in InLevel, and use it *if* it is present in InAlLBakedOutputs. + // Otherwise if we are baking in replace mode, and the previous bake actor is available and in InLevel, return it + // as OutFoundActor. Otherwise return InFallbackActor as OutFoundActor. + // bOutHasBakeActorName indicates if the output has the unreal_bake_actor attribute set. + // OutFoundActor is the actor that was found (if one was found) + static bool FindUnrealBakeActor( + const FHoudiniOutputObject& InOutputObject, + const FHoudiniBakedOutputObject& InBakedOutputObject, + const TArray& InAllBakedActors, + ULevel* InLevel, + FName InDefaultActorName, + bool bInReplaceActorBakeMode, + AActor* InFallbackActor, + AActor*& OutFoundActor, + bool& bOutHasBakeActorName, + FName& OutBakeActorName); + + // Try to find an actor that we can use for baking. + // If the requested actor could not be found, then `OutWorld` and `OutLevel` + // should be used to spawn the new bake actor. + // @returns AActor* if found. Otherwise, returns nullptr. + static AActor* FindExistingActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + // Remove a previously baked actor + static bool RemovePreviouslyBakedActor( + AActor* InNewBakedActor, + ULevel* InLevel, + const FHoudiniPackageParams& InPackageParams); + + static bool RemovePreviouslyBakedComponent(UActorComponent* InComponent); + + // Get the world outliner folder path for output generated by InOutputOwner + static FName GetOutputFolderPath(UObject* InOutputOwner); + + static void RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); + + // Helper function for renaming and relabelling an actor + static void RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); + + // Start: PDG Baking + + // Detach InActor from its parent, and rename to InNewName (attaches a numeric suffix to make it unique via + // MakeUniqueObjectName). Place it in the world outliner folder InFolderPath. + static bool DetachAndRenameBakedPDGOutputActor(AActor* InActor, const FString& InNewName, const FName& InFolderPath); + + static bool BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultArrayIndex, + int32 InWorkResultObjectArrayIndex, + bool bInReplaceActors, + bool bInReplaceAssets, + bool bInBakeToWorkResultActor, + bool bInIsAutoBake, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + // Checks if auto-bake is enabled on InPDGAssetLink, and if it is, calls BakePDGWorkResultObject. + static void CheckPDGAutoBakeAfterResultObjectLoaded( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkItemHAPIIndex, + int32 InWorkItemResultInfoIndex); + + // Bake PDG output. This bakes all assets from all work items in the specified InNode (FTOPNode). + // It uses the existing output actors in the level, but breaks any links from these actors to the PDG link and + // moves the actors out of the parent Folder/ detaches from the parent PDG output actor. + static bool BakePDGTOPNodeOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Helper function to bake only a specific PDG TOP node's outputs to actors. + static bool BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); + + // Bake PDG output. This bakes all assets from all work items in the specified TOP network. + // It uses the existing output actors in the level, but breaks any links + // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent + // PDG output actor. + static bool BakePDGTOPNetworkOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes assets from TOP networks and nodes according to + // InPDGAssetLink->PDGBakeSelectionOption. It uses the existing output actors in the level, but breaks any links + // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent + // PDG output actor. + static bool BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); + + // Bake PDG output. This bakes all supported assets from all work items in the specified InNode (FTOPNode). + // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from + // PDG output actors. + static bool BakePDGTOPNodeBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Helper to bake only a specific PDG TOP node's outputs to blueprint(s). + static bool BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); + + // Bake PDG output. This bakes all supported assets from all work items in the specified TOP network. + // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from + // PDG output actors. + static bool BakePDGTOPNetworkBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes assets from TOP networks and nodes according to + // InPDGAssetLink->PDGBakeSelectionOption. It duplicates the output actors and bakes them to blueprints. Assets + // that were baked are removed from PDG output actors. + static bool BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); + + // End: PDG Baking + +protected: + + // Find the HGPO with matching identifier. Returns true if the HGPO was found. + static bool FindHGPO( + const FHoudiniOutputObjectIdentifier& InIdentifier, + const TArray& InHGPOs, + FHoudiniGeoPartObject const*& OutHGPO); + + // Set OutBakeName to the resolved output name of InMeshOutputObject / InObject. OutBakeName is set to the object's + // BakeName (the BakeName on the InMeshOutputObject, or if that is not set, the custom part name or finally the + // package name. + static void GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const FHoudiniOutputObject& InMeshOutputObject, + FString& OutBakeName); + + // Look for InObject in InAllOutputs. If found the function returns true and OutBakeName is set to the object's + // BakeName (the BakeName on the OutputObject, or if that is not set, the custom part name or finally the package + // name. + static bool GetTemporaryOutputObjectBakeName( + const UObject* InObject, + EHoudiniOutputType InOutputType, + const TArray& InAllOutputs, + FString& OutBakeName); + + // Checks if InHoudiniAssetComponent has any current proxy mesh. Refines if it possible. Returns true + // if baking can continue, false otherwise. If the component has a proxy, but no cook data, then false is + // returned, the component is set to recook without a proxy and with bake after cook, and bOutNeedsReCook is set + // to true. + // bInReplace and BakeOption represents the baking settings to use if a delayed bake (post-cook) needs to be triggered. + static bool CheckForAndRefineHoudiniProxyMesh( + UHoudiniAssetComponent* InHoudiniAssetComponent, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption BakeOption, + bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors, + bool& bOutNeedsReCook); + + // Position InActor at its bounding box center (keep components' world location) + static void CenterActorToBoundingBoxCenter(AActor* InActor); + + // Position each of the actors in InActors at its bounding box center (keep components' world location) + static void CenterActorsToBoundingBoxCenter(const TArray& InActors); + + // Helper to get or optionally create a RootComponent for an actor + static USceneComponent* GetActorRootComponent( + AActor* InActor, bool bCreateIfMissing=true, EComponentMobility::Type InMobilityIfCreated=EComponentMobility::Static); + + // Helper function to return a unique object name if the given is already in use + static FString MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed=nullptr); + + // Helper for getting the actor folder path for the world outliner, based unreal_bake_outliner_folder + static FName GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); + + // Helper for setting the actor folder path in the world outliner + static bool SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); + + // Helper for destroying previous bake components/actors + static uint32 DestroyPreviousBakeOutput( + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInDestroyBakedComponent, + bool bInDestroyBakedInstancedActors, + bool bInDestroyBakedInstancedComponents); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp index ed3be6fdf..08ab3dfaa 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp @@ -1,1753 +1,1820 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineCommands.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniOutput.h" - -#include "DesktopPlatformModule.h" -#include "Interfaces/IMainFrameModule.h" -#include "EditorDirectories.h" -#include "Misc/ScopedSlowTask.h" -#include "Async/Async.h" -#include "FileHelpers.h" -#include "AssetRegistryModule.h" -#include "Engine/ObjectLibrary.h" -#include "ObjectTools.h" -#include "CoreGlobals.h" -#include "HoudiniEngineOutputStats.h" -#include "Misc/FeedbackContext.h" -#include "HAL/FileManager.h" -#include "Modules/ModuleManager.h" -#include "ISettingsModule.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FDelegateHandle FHoudiniEngineCommands::OnPostSaveWorldRefineProxyMeshesHandle = FDelegateHandle(); - -void -FHoudiniEngineCommands::RegisterCommands() -{ - UI_COMMAND(_CreateSession, "Create Session", "Creates a new Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_ConnectSession, "Connect Session", "Connects to an existing Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_StopSession, "Stop Session", "Stops the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RestartSession, "Restart Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_OpenSessionSync, "Open Houdini Session Sync", "Opens Houdini with Session Sync and connect to it.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_CloseSessionSync, "Close Houdini Session Sync", "Close the Session Sync Houdini.", EUserInterfaceActionType::Button, FInputChord()); - - // Viewport Sync - UI_COMMAND(_ViewportSyncNone, "Disabled", "Do not sync viewports.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncUnreal, "Sync Unreal to Houdini.", "Sync the Unreal viewport to Houdini's.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncHoudini, "Sync Houdini to Unreal", "Sync the Houdini viewport to Unreal's.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncBoth, "Both", "Sync both Unreal and Houdini's viewport.", EUserInterfaceActionType::Check, FInputChord()); - - // PDG Import Commandlet - UI_COMMAND(_StartPDGCommandlet, "Start Async Importer", "Start the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_StopPDGCommandlet, "Stop Async Importer", "Stops the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_IsPDGCommandletEnabled, "Enable Async Importer", "Enables the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Check, FInputChord()); - - UI_COMMAND(_InstallInfo, "Installation Info", "Display information on the current Houdini Engine installation", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_PluginSettings, "PluginSettings", "Displays the Houdini Engine plugin settings", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_OpenInHoudini, "Open scene in Houdini", "Opens the current Houdini scene in Houdini.", EUserInterfaceActionType::Button, FInputChord(EKeys::O, EModifierKey::Control | EModifierKey::Alt)); - UI_COMMAND(_SaveHIPFile, "Save Houdini scene (HIP)", "Saves a .hip file of the current Houdini scene.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_OnlineDoc, "Online Documentation", "Go to the plugin's online documentation.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_OnlineForum, "Online Forum", "Go to the plugin's online forum.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_ReportBug, "Report a bug", "Report a bug for Houdini Engine for Unreal plugin.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_CookAll, "Recook All", "Recooks all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_CookSelected, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::C, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_RebuildAll, "Rebuild All", "Rebuilds all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RebuildSelected, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::R, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_BakeAll, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_BakeSelected, "Bake And Replace Selection", "Bakes and replaces with blueprints selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::B, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_RefineAll, "Refine all Houdini Proxy Meshes To Static Meshes", "Builds and replaces all Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RefineSelected, "Refine selected Houdini Proxy Meshes To Static Meshes", "Builds and replaces selected Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_CleanUpTempFolder, "Clean Houdini Engine Temp Folder", "Deletes the unused temporary files in the Temporary Cook Folder.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_PauseAssetCooking, "Pause Houdini Engine Cooking", "When activated, prevents Houdini Engine from cooking assets until unpaused.", EUserInterfaceActionType::Check, FInputChord(EKeys::P, EModifierKey::Control | EModifierKey::Alt)); -} - -void -FHoudiniEngineCommands::SaveHIPFile() -{ - if (!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) - { - HOUDINI_LOG_ERROR(TEXT("Cannot save the Houdini scene, the Houdini Engine session hasn't been started.")); - return; - } - - IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get(); - if (!DesktopPlatform || !FHoudiniEngineUtils::IsInitialized()) - return; - - TArray< FString > SaveFilenames; - bool bSaved = false; - void * ParentWindowWindowHandle = NULL; - - IMainFrameModule & MainFrameModule = FModuleManager::LoadModuleChecked< IMainFrameModule >(TEXT("MainFrame")); - const TSharedPtr< SWindow > & MainFrameParentWindow = MainFrameModule.GetParentWindow(); - if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) - ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); - - bSaved = DesktopPlatform->SaveFileDialog( - ParentWindowWindowHandle, - NSLOCTEXT("SaveHIPFile", "SaveHIPFile", "Saves a .hip file of the current Houdini scene.").ToString(), - *(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT)), - TEXT(""), - TEXT("Houdini HIP file|*.hip"), - EFileDialogFlags::None, - SaveFilenames); - - if (bSaved && SaveFilenames.Num()) - { - // Add a slate notification - FString Notification = TEXT("Saving internal Houdini scene..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Saved Houdini scene to %s"), *SaveFilenames[0]); - - // Get first path. - std::string HIPPathConverted(TCHAR_TO_UTF8(*SaveFilenames[0])); - - // Save HIP file through Engine. - FHoudiniApi::SaveHIPFile(FHoudiniEngine::Get().GetSession(), HIPPathConverted.c_str(), false); - } -} - -void -FHoudiniEngineCommands::OpenInHoudini() -{ - if(!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) - { - HOUDINI_LOG_ERROR(TEXT("Cannot open the scene in Houdini, the Houdini Engine session hasn't been started.")); - return; - } - - // First, saves the current scene as a hip file - // Creates a proper temporary file name - FString UserTempPath = FPaths::CreateTempFilename( - FPlatformProcess::UserTempDir(), - TEXT("HoudiniEngine"), TEXT(".hip")); - - // Save HIP file through Engine. - std::string TempPathConverted(TCHAR_TO_UTF8(*UserTempPath)); - FHoudiniApi::SaveHIPFile( - FHoudiniEngine::Get().GetSession(), - TempPathConverted.c_str(), false); - - if (!FPaths::FileExists(UserTempPath)) - return; - - // Add a slate notification - FString Notification = TEXT("Opening scene in Houdini..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Add quotes to the path to avoid issues with spaces - UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); - // Then open the hip file in Houdini - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); - - FProcHandle ProcHandle = FPlatformProcess::CreateProc( - *HoudiniLocation, - *UserTempPath, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - if (!ProcHandle.IsValid()) - { - // Try with the steam version executable instead - HoudiniLocation = LibHAPILocation + TEXT("//hindie.steam"); - - ProcHandle = FPlatformProcess::CreateProc( - *HoudiniLocation, - *UserTempPath, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - if (!ProcHandle.IsValid()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to open scene in Houdini.")); - } - } - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini.")); -} - -void -FHoudiniEngineCommands::ReportBug() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::ShowInstallInfo() -{ - // TODO -} - -void -FHoudiniEngineCommands::ShowPluginSettings() -{ - FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Plugins"), FName("HoudiniEngine")); -} - -void -FHoudiniEngineCommands::OnlineDocumentation() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_DOC_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::OnlineForum() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_FORUM_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::CleanUpTempFolder() -{ - // TODO: Improve me! slow now that we also have SM saved in the temp directory - // Due to the ref, we probably iterate a little too much, and should maybe do passes following the order of refs: - // mesh first, then materials, then textures. - // have a look at UWrangleContentCommandlet as well - - // Add a slate notification - FString Notification = TEXT("Cleaning up Houdini Engine temporary folder..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - GWarn->BeginSlowTask(LOCTEXT("CleanUpTemp", "Cleaning up the Houdini Engine Temp Folder"), false, false); - - // Get the default temp cook folder - FString TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - TArray TempCookFolders; - TempCookFolders.Add(FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder()); - for (TObjectIterator It; It; ++It) - { - FString CookFolder = It->TemporaryCookFolder.Path; - if (CookFolder.IsEmpty()) - continue; - - TempCookFolders.AddUnique(CookFolder); - } - - // The Asset registry will help us finding if the content of the asset is referenced - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - int32 DeletedCount = 0; - bool bDidDeleteAsset = true; - while (bDidDeleteAsset) - { - // To correctly clean the temp folder, we need to iterate multiple times, because some of the temp assets - // might be referenced by other temp assets.. (ie Textures are referenced by Materials) - // We'll stop looking for assets to delete when no deletion occured. - bDidDeleteAsset = false; - - TArray AssetDataList; - for (auto& TempFolder : TempCookFolders) - { - // The Object library will list all UObjects found in the TempFolder - auto ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, true); - ObjectLibrary->LoadAssetDataFromPath(TempFolder); - - // Get all the assets found in the TEMP folder - TArray CurrentAssetDataList; - ObjectLibrary->GetAssetDataList(CurrentAssetDataList); - - AssetDataList.Append(CurrentAssetDataList); - } - - // All the assets we're going to delete - TArray AssetDataToDelete; - for (FAssetData Data : AssetDataList) - { - UPackage* CurrentPackage = Data.GetPackage(); - if (!CurrentPackage || CurrentPackage->IsPendingKill()) - continue; - - // Do not try to delete the package if it's referenced anywhere - TArray ReferenceNames; - AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, UE::AssetRegistry::EDependencyCategory::All); - if (ReferenceNames.Num() > 0) - continue; - - bool bAssetDataSafeToDelete = true; - TArray AssetsInPackage; - AssetRegistryModule.Get().GetAssetsByPackageName(CurrentPackage->GetFName(), AssetsInPackage); - for (const auto& AssetInfo : AssetsInPackage) - { - // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) - UObject* AssetInPackage = AssetInfo.GetAsset(); - if (!AssetInPackage || AssetInPackage->IsPendingKill()) - continue; - - FReferencerInformationList ReferencesIncludingUndo; - bool bReferencedInMemoryOrUndoStack = IsReferenced(AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo); - if (!bReferencedInMemoryOrUndoStack) - continue; - - // We do have external references, check if the external references are in our ObjectToDelete list - // If they are, we can delete the asset because its references are going to be deleted as well. - for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences) - { - UObject* Outer = ExtRef.Referencer->GetOuter(); - if (!Outer || Outer->IsPendingKill()) - continue; - - bool bOuterFound = false; - for (auto DataToDelete : AssetDataToDelete) - { - if (DataToDelete.GetPackage() == Outer) - { - bOuterFound = true; - break; - } - else if (DataToDelete.GetAsset() == Outer) - { - bOuterFound = true; - break; - } - } - - // We have at least one reference that's not going to be deleted, we have to keep the asset - if (!bOuterFound) - { - bAssetDataSafeToDelete = false; - break; - } - } - } - - if (bAssetDataSafeToDelete) - AssetDataToDelete.Add(Data); - } - - // Nothing to delete - if (AssetDataToDelete.Num() <= 0) - break; - - int32 CurrentDeleted = ObjectTools::DeleteAssets(AssetDataToDelete, false); - - if (CurrentDeleted > 0) - { - DeletedCount += CurrentDeleted; - bDidDeleteAsset = true; - } - } - - - // Now, go through all the directories in the temp directories and delete all the empty ones - IFileManager& FM = IFileManager::Get(); - // Lambda that parses a directory recursively and returns true if it is empty - auto IsEmptyFolder = [&FM](FString PathToDeleteOnDisk) - { - struct FEmptyFolderVisitor : public IPlatformFile::FDirectoryVisitor - { - bool bIsEmpty; - FEmptyFolderVisitor() - : bIsEmpty(true) - { - } - - virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override - { - if (!bIsDirectory) - { - bIsEmpty = false; - return false; // abort searching - } - - return true; // continue searching - } - }; - - // Look for files on disk in case the folder contains things not tracked by the asset registry - FEmptyFolderVisitor EmptyFolderVisitor; - IFileManager::Get().IterateDirectoryRecursively(*PathToDeleteOnDisk, EmptyFolderVisitor); - return EmptyFolderVisitor.bIsEmpty; - }; - - // Iterates on all the temporary cook directories recursively, - // And keep not of all the empty directories - FString TempCookPathOnDisk; - TArray FoldersToDelete; - if (FPackageName::TryConvertLongPackageNameToFilename(TempCookFolder, TempCookPathOnDisk)) - { - FM.IterateDirectoryRecursively(*TempCookPathOnDisk, [&FM, &FoldersToDelete, &IsEmptyFolder](const TCHAR* InFilenameOrDirectory, const bool InIsDirectory) -> bool - { - // Skip Files - if (!InIsDirectory) - return true; - - FString CurrentDirectoryPath = FString(InFilenameOrDirectory); - if (IsEmptyFolder(CurrentDirectoryPath)) - FoldersToDelete.Add(CurrentDirectoryPath); - - // keep iterating - return true; - }); - } - - int32 DeletedDirectories = 0; - for (auto& FolderPath : FoldersToDelete) - { - FString PathToDelete; - if (!FPackageName::TryConvertFilenameToLongPackageName(FolderPath, PathToDelete)) - continue; - - if (IFileManager::Get().DeleteDirectory(*FolderPath, false, true)) - { - AssetRegistryModule.Get().RemovePath(PathToDelete); - DeletedDirectories++; - } - } - - GWarn->EndSlowTask(); - - // Add a slate notification - Notification = TEXT("Deleted ") + FString::FromInt(DeletedCount) + TEXT(" temporary files and ") + FString::FromInt(DeletedDirectories) + TEXT(" directories."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Deleted %d temporary files and %d directories."), DeletedCount, DeletedDirectories); -} - -void -FHoudiniEngineCommands::BakeAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Baking all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 BakedCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component")); - continue; - } - - if (!HoudiniAssetComponent->IsComponentValid()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - if (AssetName != "Default__HoudiniAssetActor") - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is invalid"), *AssetName); - continue; - } - - // If component is not cooking or instancing, we can bake blueprint. - if (HoudiniAssetComponent->IsInstantiatingOrCooking()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is actively instantiating or cooking"), *AssetName); - continue; - } - - bool bSuccess = false; - bool BakeToBlueprints = true; - if (BakeToBlueprints) - { - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // bSuccess = true; - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - if (bSuccess) - { - // Instantiate blueprints in component's level, then remove houdini asset actor - bSuccess = false; - ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); - if (IsValid(Level)) - { - UWorld* World = Level->GetWorld(); - if (IsValid(World)) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = Level; - FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); - for (UBlueprint* Blueprint : Blueprints) - { - if (!IsValid(Blueprint)) - continue; - World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); - } - - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - bSuccess = true; - } - } - } - } - else - { - // TODO: this used to have a way to not select in v1 - // if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent)) - // bSuccess = true; - if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, true, true)) - { - bSuccess = true; - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - } - } - - if (bSuccess) - BakedCount++; - } - - // Add a slate notification - Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); -} - -void -FHoudiniEngineCommands::PauseAssetCooking() -{ - // Revert the global flag - bool bCurrentCookingEnabled = !FHoudiniEngine::Get().IsCookingEnabled(); - FHoudiniEngine::Get().SetCookingEnabled(bCurrentCookingEnabled); - - // We need to refresh UI when pause cooking. Set refresh UI counter to be the number of current registered HACs. - if (!bCurrentCookingEnabled) - FHoudiniEngine::Get().SetUIRefreshCountWhenPauseCooking( FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount() ); - - // Add a slate notification - FString Notification = TEXT("Houdini Engine cooking paused"); - if (bCurrentCookingEnabled) - Notification = TEXT("Houdini Engine cooking resumed"); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - if (bCurrentCookingEnabled) - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking resumed.")); - else - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking paused.")); - - if (!bCurrentCookingEnabled) - return; - - /* - // If we are unpausing, tick each asset component to "update" them - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!")); - continue; - } - - HoudiniAssetComponent->StartHoudiniTicking(); - } - */ -} - -bool -FHoudiniEngineCommands::IsAssetCookingPaused() -{ - return !FHoudiniEngine::Get().IsCookingEnabled(); -} - -void -FHoudiniEngineCommands::RecookSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Cooking selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and cook the assets if they're in a valid state - int32 CookedCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedCook(); - CookedCount++; - } - - // Add a slate notification - Notification = TEXT("Re-cooking ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-cooking %d selected Houdini assets."), CookedCount); -} - -void -FHoudiniEngineCommands::RecookAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Cooking all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 CookedCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedCook(); - CookedCount++; - } - - // Add a slate notification - Notification = TEXT("Re-cooked ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-cooked %d Houdini assets in the current level."), CookedCount); -} - -void -FHoudiniEngineCommands::RebuildAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Re-building all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 RebuiltCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedRebuild(); - RebuiltCount++; - } - - // Add a slate notification - Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d Houdini assets in the current level."), RebuiltCount); -} - -void -FHoudiniEngineCommands::RebuildSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Rebuilding selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and rebuilds the assets if they're in a valid state - int32 RebuiltCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid()) - continue; - - HoudiniAssetComponent->MarkAsNeedRebuild(); - RebuiltCount++; - } - - // Add a slate notification - Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d selected Houdini assets."), RebuiltCount); -} - -void -FHoudiniEngineCommands::BakeSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Baking selected Houdini Asset Actors in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and rebuilds the assets if they're in a valid state - int32 BakedCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); - continue; - } - - if (!HoudiniAssetComponent->IsComponentValid()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - HOUDINI_LOG_ERROR(TEXT("Failed to export Houdini Asset: %s in the scene!"), *AssetName); - continue; - } - - // If component is not cooking or instancing, we can bake blueprint. - if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) - { - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // BakedCount++; - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // bSuccess = true; - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, true, BakeStats, Blueprints, PackagesToSave); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - if (bSuccess) - { - // Instantiate blueprints in component's level, then remove houdini asset actor - ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); - if (IsValid(Level)) - { - UWorld* World = Level->GetWorld(); - if (IsValid(World)) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = Level; - FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); - for (UBlueprint* Blueprint : Blueprints) - { - if (!IsValid(Blueprint)) - continue; - World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); - } - - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - BakedCount++; - } - } - } - } - } - - // Add a slate notification - Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); -} - -// Recentre HoudiniAsset actors' pivots to their input / cooked static-mesh average centre. -void FHoudiniEngineCommands::RecentreSelection() -{ - /* -#if WITH_EDITOR - //Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Recentering selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and cook the assets if they're in a valid state - int32 RecentreCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || !HoudiniAssetComponent->IsComponentValid()) - continue; - - // Get the average centre of all the created Static Meshes - FVector AverageBoundsCentre = FVector::ZeroVector; - int32 NumBounds = 0; - const FVector CurrentLocation = HoudiniAssetComponent->GetComponentLocation(); - { - //Check Static Meshes - TArray StaticMeshes; - StaticMeshes.Reserve(16); - HoudiniAssetComponent->GetAllUsedStaticMeshes(StaticMeshes); - - //Get average centre of all the static meshes. - for (const UStaticMesh* pMesh : StaticMeshes) - { - if (!pMesh) - continue; - - //to world space - AverageBoundsCentre += (pMesh->GetBounds().Origin + CurrentLocation); - NumBounds++; - } - } - - //Check Inputs - if (0 == NumBounds) - { - const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs; - for (const UHoudiniInput* pInput : AssetInputs) - { - if (!pInput || pInput->IsPendingKill()) - continue; - - // to world space - FBox Bounds = pInput->GetInputBounds(CurrentLocation); - if (Bounds.IsValid) - { - AverageBoundsCentre += Bounds.GetCenter(); - NumBounds++; - } - } - } - - //if we have more than one, get the average centre - if (NumBounds > 1) - { - AverageBoundsCentre /= (float)NumBounds; - } - - //if we need to move... - float fDist = FVector::DistSquared(CurrentLocation, AverageBoundsCentre); - if (NumBounds && fDist > 1.0f) - { - // Move actor to average centre and recook - // This will refresh the static mesh under the HoudiniAssestComponent ( undoing the translation ). - HoudiniAssetActor->SetActorLocation(AverageBoundsCentre, false, nullptr, ETeleportType::TeleportPhysics); - - // Recook now the houdini-static-mesh has a new origin - HoudiniAssetComponent->StartTaskAssetCookingManual(); - RecentreCount++; - } - } - - if (RecentreCount) - { - // UE4 Editor doesn't refresh the translation-handles until they are re-selected, confusing the user, deselect the objects. - GEditor->SelectNone(true, false); - } - - // Add a slate notification - Notification = TEXT("Re-centred ") + FString::FromInt(RecentreCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-centred %d selected Houdini assets."), RecentreCount); - -#endif //WITH_EDITOR - */ -} - -void -FHoudiniEngineCommands::OpenSessionSync() -{ - //if (!FHoudiniEngine::IsInitialized()) - // return; - - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - HAPI Not initialized")); - return; - } - - // Get the runtime settings to get the session/type and settings - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - - EHoudiniRuntimeSettingsSessionType SessionType = HoudiniRuntimeSettings->SessionType; - FString ServerPipeName = HoudiniRuntimeSettings->ServerPipeName; - int32 ServerPort = HoudiniRuntimeSettings->ServerPort; - - FString SessionSyncArgs = TEXT("-hess="); - if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe) - { - // Add the -hess=pipe:hapi argument - SessionSyncArgs += TEXT("pipe:") + ServerPipeName; - } - else if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_Socket) - { - // Add the -hess=port:9090 argument - SessionSyncArgs += TEXT("port:") + FString::FromInt(ServerPort); - } - else - { - // Invalid session type - HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - Invalid session type")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Opening Houdini Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Opening Houdini Session Sync.")); - - // Only launch Houdini in Session sync if we havent started it already! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - if (!FPlatformProcess::IsProcRunning(PreviousHESS)) - { - // Start houdini with the -hess commandline args - const FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); -# if PLATFORM_MAC - const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../Resources/bin"); -# elif PLATFORM_LINUX - const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../bin"); -# elif PLATFORM_WINDOWS - const FString HoudiniExeLocationRelativeToLibHAPI; -# else - // Treat an unknown platform the same as Windows for now - const FString HoudiniExeLocationRelativeToLibHAPI; -# endif - FString HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/houdini"); - HOUDINI_LOG_MESSAGE(TEXT("Path to houdini executable: %s"), *HoudiniLocation); - FProcHandle HESSHandle = FPlatformProcess::CreateProc( - *HoudiniLocation, - *SessionSyncArgs, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - if (!HESSHandle.IsValid()) - { - // Try with the steam version executable instead - HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/hindie.steam"); - HOUDINI_LOG_MESSAGE(TEXT("Path to hindie.steam executable: %s"), *HoudiniLocation); - - HESSHandle = FPlatformProcess::CreateProc( - *HoudiniLocation, - *SessionSyncArgs, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - if (!HESSHandle.IsValid()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to launch Houdini in Session Sync mode.")); - return; - } - } - - // Keep track of the SessionSync ProcHandle - FHoudiniEngine::Get().SetHESSProcHandle(HESSHandle); - } - - // Start an Async task to connect to Session Sync - Async(EAsyncExecution::TaskGraphMainThread, [SessionType, ServerPipeName, ServerPort]() - { - // Use a timeout to avoid waiting indefinitely for H to start in session sync mode - const double Timeout = 180.0; // 3min - const double StartTimestamp = FPlatformTime::Seconds(); - - FString ServerHost = TEXT("localhost"); - while (!FHoudiniEngine::Get().SessionSyncConnect(SessionType, ServerPipeName, ServerHost, ServerPort)) - { - // Houdini might not be done loading, sleep for one second - FPlatformProcess::Sleep(1); - - // Check for the timeout - if (FPlatformTime::Seconds() - StartTimestamp > Timeout) - { - // ... and a log message - HOUDINI_LOG_ERROR(TEXT("Failed to start SessionSync - Timeout...")); - return false; - } - } - - // Initialize HAPI with this session - if (!FHoudiniEngine::Get().InitializeHAPISession()) - { - FHoudiniEngine::Get().StopTicking(); - return false; - } - - // Notify all HACs that they need to instantiate in the new session - MarkAllHACsAsNeedInstantiation(); - - // Start ticking - FHoudiniEngine::Get().StartTicking(); - - // Add a slate notification - FString Notification = TEXT("Succesfully connected to Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Succesfully connected to Session Sync...")); - - return true; - }); -} - -void -FHoudiniEngineCommands::CloseSessionSync() -{ - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to stop Session Sync - HAPI Not initialized")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Stopping Houdini Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Stopping Houdini Session Sync.")); - - // Stop Houdini Session sync if it is still running! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - if (FPlatformProcess::IsProcRunning(PreviousHESS)) - { - FPlatformProcess::TerminateProc(PreviousHESS, true); - } -} - -void -FHoudiniEngineCommands::SetViewportSync(const int32& ViewportSync) -{ - if (ViewportSync == 1) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); - } - else if (ViewportSync == 2) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); - } - else if (ViewportSync == 3) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); - } - else - { - FHoudiniEngine::Get().SetSyncViewportEnabled(false); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); - } -} - -int32 -FHoudiniEngineCommands::GetViewportSync() -{ - if(!FHoudiniEngine::Get().IsSyncViewportEnabled()) - return 0; - - bool bSyncH = FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled(); - bool bSyncU = FHoudiniEngine::Get().IsSyncUnrealViewportEnabled(); - if (bSyncH && !bSyncU) - return 1; - else if (!bSyncH && bSyncU) - return 2; - else if (bSyncH && bSyncU) - return 3; - else - return 0; -} - -void -FHoudiniEngineCommands::RestartSession() -{ - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().RestartSession()) - return; - - // We've successfully restarted the Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::CreateSession() -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().CreateSession(HoudiniRuntimeSettings->SessionType)) - return; - - // We've successfully created the Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::ConnectSession() -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().ConnectSession(HoudiniRuntimeSettings->SessionType)) - return; - - // We've successfully connected to a Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation() -{ - // Notify all the HoudiniAssetComponents that they need to re instantiate themselves in the new Houdini engine session. - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedInstantiation(); - } -} - -bool -FHoudiniEngineCommands::IsSessionValid() -{ - return FHoudiniEngine::IsInitialized(); -} - -bool -FHoudiniEngineCommands::IsSessionSyncProcessValid() -{ - // Only launch Houdini in Session sync if we havent started it already! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - return FPlatformProcess::IsProcRunning(PreviousHESS); -} - -void -FHoudiniEngineCommands::StopSession() -{ - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine session stopped.")); - } -} - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE) -{ - // Get current world selection - TArray WorldSelection; - int32 NumSelectedHoudiniAssets = 0; - if (bOnlySelectedActors) - { - NumSelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (NumSelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - } - - // Add a slate notification - FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // First find the components that have meshes that we must refine - TArray ComponentsToRefine; - TArray ComponentsToCook; - // Components that would be candidates for refinement/cooking, but have errors - TArray SkippedComponents; - if (bOnlySelectedActors) - { - for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Index]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - } - else - { - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - } - - RefineTriagedHoudiniProxyMesehesToStaticMeshes( - ComponentsToRefine, - ComponentsToCook, - SkippedComponents, - bSilent, - bRefineAll, - bOnPreSaveWorld, - OnPreSaveWorld, - bOnPreBeginPIE - ); -} - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent) -{ - const bool bRefineAll = true; - const bool bOnPreSaveWorld = false; - UWorld* OnPreSaveWorld = nullptr; - const bool bOnPreBeginPIE = false; - - // First find the components that have meshes that we must refine - TArray ComponentsToRefine; - TArray ComponentsToCook; - // Components that would be candidates for refinement/cooking, but have errors - TArray SkippedComponents; - for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine) - { - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - - RefineTriagedHoudiniProxyMesehesToStaticMeshes( - ComponentsToRefine, - ComponentsToCook, - SkippedComponents, - bSilent, - bRefineAll, - bOnPreSaveWorld, - OnPreSaveWorld, - bOnPreBeginPIE - ); -} - -void -FHoudiniEngineCommands::StartPDGCommandlet() -{ - FHoudiniEngine::Get().StartPDGCommandlet(); -} - -void -FHoudiniEngineCommands::StopPDGCommandlet() -{ - FHoudiniEngine::Get().StopPDGCommandlet(); -} - -bool -FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected() -{ - return FHoudiniEngine::Get().IsPDGCommandletRunningOrConnected(); -} - -bool -FHoudiniEngineCommands::IsPDGCommandletEnabled() -{ - const UHoudiniRuntimeSettings* const Settings = GetDefault(); - if (IsValid(Settings)) - { - return Settings->bPDGAsyncCommandletImportEnabled; - } - - return false; -} - -bool -FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled) -{ - UHoudiniRuntimeSettings* const Settings = GetMutableDefault(); - if (IsValid(Settings)) - { - Settings->bPDGAsyncCommandletImportEnabled = InEnabled; - return true; - } - - return false; -} - -void -FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped) -{ - if (!InHAC || InHAC->IsPendingKill()) - return; - - // Make sure that the component's World and Owner are valid - AActor *Owner = InHAC->GetOwner(); - if (!Owner || Owner->IsPendingKill()) - return; - - UWorld *World = InHAC->GetWorld(); - if (!World || World->IsPendingKill()) - return; - - if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World) - return; - - // Check if we should consider this component for proxy mesh refinement based on its settings and - // flags passed to the function - if (bRefineAll || - (bOnPreSaveWorld && InHAC->IsProxyStaticMeshRefinementOnPreSaveWorldEnabled()) || - (bOnPreBeginPIE && InHAC->IsProxyStaticMeshRefinementOnPreBeginPIEEnabled())) - { - TArray ProxyMeshPackagesToSave; - TArray ComponentsWithProxiesToSave; - - if (InHAC->HasAnyCurrentProxyOutput()) - { - // Get the state of the asset and check if it is cooked - // If it is not cook, request a cook. We can only build the UStaticMesh - // if the data from the cook is available - // If the state is not pre-cook, or None (cooked), then the state is invalid, - // log an error and skip the component - bool bNeedsRebuildOrDelete = false; - bool bUnsupportedState = false; - const bool bCookedDataAvailable = InHAC->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bUnsupportedState); - if (bCookedDataAvailable) - { - OutToRefine.Add(InHAC); - ComponentsWithProxiesToSave.Add(InHAC); - } - else if (!bUnsupportedState && !bNeedsRebuildOrDelete) - { - InHAC->MarkAsNeedCook(); - // Force the output of the cook to be directly created as a UStaticMesh and not a proxy - InHAC->SetNoProxyMeshNextCookRequested(true); - OutToCook.Add(InHAC); - ComponentsWithProxiesToSave.Add(InHAC); - } - else - { - OutSkipped.Add(InHAC); - const EHoudiniAssetState AssetState = InHAC->GetAssetState(); - HOUDINI_LOG_ERROR(TEXT("Could not refine %s, the asset is in an unsupported state: %s"), *(InHAC->GetPathName()), *(UEnum::GetValueAsString(AssetState))); - } - } - else if (InHAC->HasAnyProxyOutput()) - { - // If the HAC has non-current proxies, destroy them - // TODO: Make this its own command? - const uint32 NumOutputs = InHAC->GetNumOutputs(); - for (uint32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = InHAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - TMap& OutputObjects = Output->GetOutputObjects(); - for (auto& CurrentPair : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; - if (!CurrentOutputObject.bProxyIsCurrent) - { - // The proxy is not current, delete it and its component - USceneComponent* FoundProxyComponent = Cast(CurrentOutputObject.ProxyComponent); - if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (FoundProxyComponent->GetOwner()) - FoundProxyComponent->GetOwner()->RemoveOwnedComponent(FoundProxyComponent); - - FoundProxyComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - FoundProxyComponent->UnregisterComponent(); - FoundProxyComponent->DestroyComponent(); - } - - UObject* ProxyObject = CurrentOutputObject.ProxyObject; - if (!ProxyObject || ProxyObject->IsPendingKill()) - continue; - - ProxyObject->MarkPendingKill(); - ProxyObject->MarkPackageDirty(); - UPackage* const Package = ProxyObject->GetPackage(); - if (IsValid(Package)) - ProxyMeshPackagesToSave.Add(Package); - } - } - } - } - - for (UHoudiniAssetComponent* const HAC : ComponentsWithProxiesToSave) - { - const uint32 NumOutputs = HAC->GetNumOutputs(); - for (uint32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - TMap& OutputObjects = Output->GetOutputObjects(); - for (auto& CurrentPair : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; - if (CurrentOutputObject.bProxyIsCurrent && CurrentOutputObject.ProxyObject) - { - UPackage* const Package = CurrentOutputObject.ProxyObject->GetPackage(); - if (IsValid(Package) && Package->IsDirty()) - ProxyMeshPackagesToSave.Add(Package); - } - } - } - } - - if (ProxyMeshPackagesToSave.Num() > 0) - { - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - FEditorFileUtils::PromptForCheckoutAndSave(ProxyMeshPackagesToSave, true, false); - } - } -} - -void -FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( - const TArray& InComponentsToRefine, - const TArray& InComponentsToCook, - const TArray& InSkippedComponents, - bool bInSilent, - bool bInRefineAll, - bool bInOnPreSaveWorld, - UWorld* InOnPreSaveWorld, - bool bInOnPrePIEBeginPlay) -{ - // Slate notification text - FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); - - const uint32 NumComponentsToCook = InComponentsToCook.Num(); - const uint32 NumComponentsToRefine = InComponentsToRefine.Num(); - const uint32 NumComponentsToProcess = NumComponentsToCook + NumComponentsToRefine; - TArray SuccessfulComponents; - uint32 NumSkippedComponents = InSkippedComponents.Num(); - if (NumComponentsToProcess > 0) - { - // The task progress pointer is potentially going to be shared with a background thread and tasks - // on the main thread, so make it thread safe - TSharedPtr TaskProgress = MakeShareable(new FSlowTask((float)NumComponentsToProcess, FText::FromString(Notification))); - TaskProgress->Initialize(); - if (!bInSilent) - TaskProgress->MakeDialog(/*bShowCancelButton=*/true); - - // Iterate over the components for which we can build UStaticMesh, and build the meshes - bool bCancelled = false; - for (uint32 ComponentIndex = 0; ComponentIndex < NumComponentsToRefine; ++ComponentIndex) - { - UHoudiniAssetComponent* HoudiniAssetComponent = InComponentsToRefine[ComponentIndex]; - TaskProgress->EnterProgressFrame(1.0f); - const bool bDestroyProxies = true; - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HoudiniAssetComponent, bDestroyProxies); - - SuccessfulComponents.Add(HoudiniAssetComponent); - - bCancelled = TaskProgress->ShouldCancel(); - if (bCancelled) - { - NumSkippedComponents += NumComponentsToRefine - ComponentIndex - 1; - break; - } - } - - if (NumComponentsToCook > 0 && !bCancelled) - { - // Now use an async task to check on the progress of the cooking components - Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { - RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(InComponentsToCook, TaskProgress, NumComponentsToProcess, NumSkippedComponents, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); - }); - } - else - { - RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(NumComponentsToProcess, NumSkippedComponents, 0, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); - } - } -} - - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray& InComponentsToCook, TSharedPtr InTaskProgress, uint32 InNumComponentsToProcess, uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) -{ - // Copy to a double linked list so that we can loop through - // to check progress of each component and remove it easily - // if it has completed/failed - TDoubleLinkedList CookList; - for (UHoudiniAssetComponent *HAC : InComponentsToCook) - { - CookList.AddTail(HAC); - } - - // Add the successfully cooked compoments to the incoming successful components (previously refined) - TArray SuccessfulComponents(InSuccessfulComponents); - - bool bCancelled = false; - uint32 NumFailedToCook = 0; - while (CookList.Num() > 0 && !bCancelled) - { - TDoubleLinkedList::TDoubleLinkedListNode *Node = CookList.GetHead(); - while (Node && !bCancelled) - { - TDoubleLinkedList::TDoubleLinkedListNode *Next = Node->GetNextNode(); - UHoudiniAssetComponent* HAC = Node->GetValue(); - - if (HAC && !HAC->IsPendingKill()) - { - const EHoudiniAssetState State = HAC->GetAssetState(); - const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult(); - bool bUpdateProgress = false; - if (State == EHoudiniAssetState::None) - { - // Cooked, count as success, remove node - CookList.RemoveNode(Node); - SuccessfulComponents.Add(Node->GetValue()); - bUpdateProgress = true; - } - else if (ResultState != EHoudiniAssetStateResult::None && ResultState != EHoudiniAssetStateResult::Working) - { - // Failed, remove node - HOUDINI_LOG_ERROR(TEXT("Failed to cook %s to obtain static mesh."), *(HAC->GetPathName())); - CookList.RemoveNode(Node); - bUpdateProgress = true; - NumFailedToCook++; - } - - if (bUpdateProgress && InTaskProgress.IsValid()) - { - // Update progress only on the main thread, and check for cancellation request - bCancelled = Async(EAsyncExecution::TaskGraphMainThread, [InTaskProgress]() { - InTaskProgress->EnterProgressFrame(1.0f); - return InTaskProgress->ShouldCancel(); - }).Get(); - } - } - - Node = Next; - } - FPlatformProcess::Sleep(0.01f); - } - - if (bCancelled) - { - HOUDINI_LOG_WARNING(TEXT("Mesh refinement cancelled while waiting for %d components to cook."), CookList.Num()); - } - - // Cooking is done, or failed, display the notifications on the main thread - const uint32 NumRemaining = CookList.Num(); - Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InNumSkippedComponents, NumFailedToCook, NumRemaining, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents]() { - RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InNumSkippedComponents + NumRemaining, NumFailedToCook, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents); - }); -} - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents) -{ - FString Notification; - if (InNumSkippedComponents + InNumFailedToCook > 0) - { - if (bCancelled) - { - Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); - } - else - { - Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), InNumSkippedComponents + InNumFailedToCook, InNumTotalComponents); - } - FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_ERROR(TEXT("%s"), *Notification); - } - else if (InNumTotalComponents > 0) - { - Notification = TEXT("Done: Refining Houdini proxy meshes to static meshes."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_MESSAGE(TEXT("%s"), *Notification); - } - if (InTaskProgress) - { - InTaskProgress->Destroy(); - } - if (bOnPreSaveWorld && InSuccessfulComponents.Num() > 0) - { - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - - // Save the dirty static meshes in InSuccessfulComponents OnPostSaveWorld - // TODO: Remove? This may not be necessary now as we save all dirty temporary cook data in PostSaveWorld() already (Static Meshes, Materials...) - OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([InSuccessfulComponents, bOnPreSaveWorld, InOnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) { - if (bOnPreSaveWorld && InOnPreSaveWorld && InOnPreSaveWorld != InWorld) - return; - - RefineProxyMeshesHandleOnPostSaveWorld(InSuccessfulComponents, InSaveFlags, InWorld, bInSuccess); - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - }); - } -} - -void -FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) -{ - TArray PackagesToSave; - - for (UHoudiniAssetComponent* HAC : InSuccessfulComponents) - { - if (!HAC || HAC->IsPendingKill()) - continue; - - const int32 NumOutputs = HAC->GetNumOutputs(); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (auto &OutputObjectPair : Output->GetOutputObjects()) - { - UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) - continue; - - UStaticMesh *SM = Cast(Obj); - if (!SM) - continue; - - UPackage *Package = SM->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - } - } - - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineCommands.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniOutput.h" + +#include "DesktopPlatformModule.h" +#include "Interfaces/IMainFrameModule.h" +#include "EditorDirectories.h" +#include "Misc/ScopedSlowTask.h" +#include "Async/Async.h" +#include "FileHelpers.h" +#include "AssetRegistryModule.h" +#include "Engine/ObjectLibrary.h" +#include "ObjectTools.h" +#include "CoreGlobals.h" +#include "HoudiniEngineOutputStats.h" +#include "Misc/FeedbackContext.h" +#include "HAL/FileManager.h" +#include "Modules/ModuleManager.h" +#include "ISettingsModule.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FDelegateHandle FHoudiniEngineCommands::OnPostSaveWorldRefineProxyMeshesHandle = FDelegateHandle(); +FHoudiniEngineCommands::FOnHoudiniProxyMeshesRefinedDelegate FHoudiniEngineCommands::OnHoudiniProxyMeshesRefinedDelegate = FHoudiniEngineCommands::FOnHoudiniProxyMeshesRefinedDelegate(); + +void +FHoudiniEngineCommands::RegisterCommands() +{ + UI_COMMAND(_CreateSession, "Create Session", "Creates a new Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_ConnectSession, "Connect Session", "Connects to an existing Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_StopSession, "Stop Session", "Stops the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RestartSession, "Restart Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_OpenSessionSync, "Open Houdini Session Sync", "Opens Houdini with Session Sync and connect to it.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_CloseSessionSync, "Close Houdini Session Sync", "Close the Session Sync Houdini.", EUserInterfaceActionType::Button, FInputChord()); + + // Viewport Sync + UI_COMMAND(_ViewportSyncNone, "Disabled", "Do not sync viewports.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncUnreal, "Sync Unreal to Houdini.", "Sync the Unreal viewport to Houdini's.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncHoudini, "Sync Houdini to Unreal", "Sync the Houdini viewport to Unreal's.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncBoth, "Both", "Sync both Unreal and Houdini's viewport.", EUserInterfaceActionType::Check, FInputChord()); + + // PDG Import Commandlet + UI_COMMAND(_StartPDGCommandlet, "Start Async Importer", "Start the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_StopPDGCommandlet, "Stop Async Importer", "Stops the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_IsPDGCommandletEnabled, "Enable Async Importer", "Enables the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Check, FInputChord()); + + UI_COMMAND(_InstallInfo, "Installation Info", "Display information on the current Houdini Engine installation", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_PluginSettings, "PluginSettings", "Displays the Houdini Engine plugin settings", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_OpenInHoudini, "Open scene in Houdini", "Opens the current Houdini scene in Houdini.", EUserInterfaceActionType::Button, FInputChord(EKeys::O, EModifierKey::Control | EModifierKey::Alt)); + UI_COMMAND(_SaveHIPFile, "Save Houdini scene (HIP)", "Saves a .hip file of the current Houdini scene.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_OnlineDoc, "Online Documentation", "Go to the plugin's online documentation.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_OnlineForum, "Online Forum", "Go to the plugin's online forum.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_ReportBug, "Report a bug", "Report a bug for Houdini Engine for Unreal plugin.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_CookAll, "Recook All", "Recooks all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_CookSelected, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::C, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_RebuildAll, "Rebuild All", "Rebuilds all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RebuildSelected, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::R, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_BakeAll, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_BakeSelected, "Bake And Replace Selection", "Bakes and replaces with blueprints selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::B, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_RefineAll, "Refine all Houdini Proxy Meshes To Static Meshes", "Builds and replaces all Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RefineSelected, "Refine selected Houdini Proxy Meshes To Static Meshes", "Builds and replaces selected Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_CleanUpTempFolder, "Clean Houdini Engine Temp Folder", "Deletes the unused temporary files in the Temporary Cook Folder.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_PauseAssetCooking, "Pause Houdini Engine Cooking", "When activated, prevents Houdini Engine from cooking assets until unpaused.", EUserInterfaceActionType::Check, FInputChord(EKeys::P, EModifierKey::Control | EModifierKey::Alt)); +} + +void +FHoudiniEngineCommands::SaveHIPFile() +{ + if (!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) + { + HOUDINI_LOG_ERROR(TEXT("Cannot save the Houdini scene, the Houdini Engine session hasn't been started.")); + return; + } + + IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get(); + if (!DesktopPlatform || !FHoudiniEngineUtils::IsInitialized()) + return; + + TArray< FString > SaveFilenames; + bool bSaved = false; + void * ParentWindowWindowHandle = NULL; + + IMainFrameModule & MainFrameModule = FModuleManager::LoadModuleChecked< IMainFrameModule >(TEXT("MainFrame")); + const TSharedPtr< SWindow > & MainFrameParentWindow = MainFrameModule.GetParentWindow(); + if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) + ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); + + bSaved = DesktopPlatform->SaveFileDialog( + ParentWindowWindowHandle, + NSLOCTEXT("SaveHIPFile", "SaveHIPFile", "Saves a .hip file of the current Houdini scene.").ToString(), + *(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT)), + TEXT(""), + TEXT("Houdini HIP file|*.hip"), + EFileDialogFlags::None, + SaveFilenames); + + if (bSaved && SaveFilenames.Num()) + { + // Add a slate notification + FString Notification = TEXT("Saving internal Houdini scene..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Saved Houdini scene to %s"), *SaveFilenames[0]); + + // Get first path. + std::string HIPPathConverted(TCHAR_TO_UTF8(*SaveFilenames[0])); + + // Save HIP file through Engine. + FHoudiniApi::SaveHIPFile(FHoudiniEngine::Get().GetSession(), HIPPathConverted.c_str(), false); + } +} + +void +FHoudiniEngineCommands::OpenInHoudini() +{ + if(!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) + { + HOUDINI_LOG_ERROR(TEXT("Cannot open the scene in Houdini, the Houdini Engine session hasn't been started.")); + return; + } + + // First, saves the current scene as a hip file + // Creates a proper temporary file name + FString UserTempPath = FPaths::CreateTempFilename( + FPlatformProcess::UserTempDir(), + TEXT("HoudiniEngine"), TEXT(".hip")); + + // Save HIP file through Engine. + std::string TempPathConverted(TCHAR_TO_UTF8(*UserTempPath)); + FHoudiniApi::SaveHIPFile( + FHoudiniEngine::Get().GetSession(), + TempPathConverted.c_str(), false); + + if (!FPaths::FileExists(UserTempPath)) + return; + + // Add a slate notification + FString Notification = TEXT("Opening scene in Houdini..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Add quotes to the path to avoid issues with spaces + UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); + // Then open the hip file in Houdini + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); + + FProcHandle ProcHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *UserTempPath, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!ProcHandle.IsValid()) + { + // Try with the steam version executable instead + HoudiniLocation = LibHAPILocation + TEXT("//hindie.steam"); + + ProcHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *UserTempPath, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!ProcHandle.IsValid()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to open scene in Houdini.")); + } + } + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini.")); +} + +void +FHoudiniEngineCommands::ReportBug() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::ShowInstallInfo() +{ + // TODO +} + +void +FHoudiniEngineCommands::ShowPluginSettings() +{ + FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Plugins"), FName("HoudiniEngine")); +} + +void +FHoudiniEngineCommands::OnlineDocumentation() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_DOC_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::OnlineForum() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_FORUM_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::CleanUpTempFolder() +{ + // TODO: Improve me! slow now that we also have SM saved in the temp directory + // Due to the ref, we probably iterate a little too much, and should maybe do passes following the order of refs: + // mesh first, then materials, then textures. + // have a look at UWrangleContentCommandlet as well + + // Add a slate notification + FString Notification = TEXT("Cleaning up Houdini Engine temporary folder..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + GWarn->BeginSlowTask(LOCTEXT("CleanUpTemp", "Cleaning up the Houdini Engine Temp Folder"), false, false); + + // Get the default temp cook folder + FString TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + TArray TempCookFolders; + TempCookFolders.Add(FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder()); + for (TObjectIterator It; It; ++It) + { + FString CookFolder = It->TemporaryCookFolder.Path; + if (CookFolder.IsEmpty()) + continue; + + TempCookFolders.AddUnique(CookFolder); + } + + // The Asset registry will help us finding if the content of the asset is referenced + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + int32 DeletedCount = 0; + bool bDidDeleteAsset = true; + while (bDidDeleteAsset) + { + // To correctly clean the temp folder, we need to iterate multiple times, because some of the temp assets + // might be referenced by other temp assets.. (ie Textures are referenced by Materials) + // We'll stop looking for assets to delete when no deletion occured. + bDidDeleteAsset = false; + + TArray AssetDataList; + for (auto& TempFolder : TempCookFolders) + { + // The Object library will list all UObjects found in the TempFolder + auto ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, true); + ObjectLibrary->LoadAssetDataFromPath(TempFolder); + + // Get all the assets found in the TEMP folder + TArray CurrentAssetDataList; + ObjectLibrary->GetAssetDataList(CurrentAssetDataList); + + AssetDataList.Append(CurrentAssetDataList); + } + + // All the assets we're going to delete + TArray AssetDataToDelete; + for (FAssetData Data : AssetDataList) + { + UPackage* CurrentPackage = Data.GetPackage(); + if (!CurrentPackage || CurrentPackage->IsPendingKill()) + continue; + + // Do not try to delete the package if it's referenced anywhere + TArray ReferenceNames; + AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, UE::AssetRegistry::EDependencyCategory::All); + if (ReferenceNames.Num() > 0) + continue; + + bool bAssetDataSafeToDelete = true; + TArray AssetsInPackage; + AssetRegistryModule.Get().GetAssetsByPackageName(CurrentPackage->GetFName(), AssetsInPackage); + for (const auto& AssetInfo : AssetsInPackage) + { + // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) + UObject* AssetInPackage = AssetInfo.GetAsset(); + if (!AssetInPackage || AssetInPackage->IsPendingKill()) + continue; + + FReferencerInformationList ReferencesIncludingUndo; + bool bReferencedInMemoryOrUndoStack = IsReferenced(AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo); + if (!bReferencedInMemoryOrUndoStack) + continue; + + // We do have external references, check if the external references are in our ObjectToDelete list + // If they are, we can delete the asset because its references are going to be deleted as well. + for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences) + { + UObject* Outer = ExtRef.Referencer->GetOuter(); + if (!Outer || Outer->IsPendingKill()) + continue; + + bool bOuterFound = false; + for (auto DataToDelete : AssetDataToDelete) + { + if (DataToDelete.GetPackage() == Outer) + { + bOuterFound = true; + break; + } + else if (DataToDelete.GetAsset() == Outer) + { + bOuterFound = true; + break; + } + } + + // We have at least one reference that's not going to be deleted, we have to keep the asset + if (!bOuterFound) + { + bAssetDataSafeToDelete = false; + break; + } + } + } + + if (bAssetDataSafeToDelete) + AssetDataToDelete.Add(Data); + } + + // Nothing to delete + if (AssetDataToDelete.Num() <= 0) + break; + + int32 CurrentDeleted = ObjectTools::DeleteAssets(AssetDataToDelete, false); + + if (CurrentDeleted > 0) + { + DeletedCount += CurrentDeleted; + bDidDeleteAsset = true; + } + } + + + // Now, go through all the directories in the temp directories and delete all the empty ones + IFileManager& FM = IFileManager::Get(); + // Lambda that parses a directory recursively and returns true if it is empty + auto IsEmptyFolder = [&FM](FString PathToDeleteOnDisk) + { + struct FEmptyFolderVisitor : public IPlatformFile::FDirectoryVisitor + { + bool bIsEmpty; + FEmptyFolderVisitor() + : bIsEmpty(true) + { + } + + virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override + { + if (!bIsDirectory) + { + bIsEmpty = false; + return false; // abort searching + } + + return true; // continue searching + } + }; + + // Look for files on disk in case the folder contains things not tracked by the asset registry + FEmptyFolderVisitor EmptyFolderVisitor; + IFileManager::Get().IterateDirectoryRecursively(*PathToDeleteOnDisk, EmptyFolderVisitor); + return EmptyFolderVisitor.bIsEmpty; + }; + + // Iterates on all the temporary cook directories recursively, + // And keep not of all the empty directories + FString TempCookPathOnDisk; + TArray FoldersToDelete; + if (FPackageName::TryConvertLongPackageNameToFilename(TempCookFolder, TempCookPathOnDisk)) + { + FM.IterateDirectoryRecursively(*TempCookPathOnDisk, [&FM, &FoldersToDelete, &IsEmptyFolder](const TCHAR* InFilenameOrDirectory, const bool InIsDirectory) -> bool + { + // Skip Files + if (!InIsDirectory) + return true; + + FString CurrentDirectoryPath = FString(InFilenameOrDirectory); + if (IsEmptyFolder(CurrentDirectoryPath)) + FoldersToDelete.Add(CurrentDirectoryPath); + + // keep iterating + return true; + }); + } + + int32 DeletedDirectories = 0; + for (auto& FolderPath : FoldersToDelete) + { + FString PathToDelete; + if (!FPackageName::TryConvertFilenameToLongPackageName(FolderPath, PathToDelete)) + continue; + + if (IFileManager::Get().DeleteDirectory(*FolderPath, false, true)) + { + AssetRegistryModule.Get().RemovePath(PathToDelete); + DeletedDirectories++; + } + } + + GWarn->EndSlowTask(); + + // Add a slate notification + Notification = TEXT("Deleted ") + FString::FromInt(DeletedCount) + TEXT(" temporary files and ") + FString::FromInt(DeletedDirectories) + TEXT(" directories."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Deleted %d temporary files and %d directories."), DeletedCount, DeletedDirectories); +} + +void +FHoudiniEngineCommands::BakeAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Baking all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 BakedCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component")); + continue; + } + + if (!HoudiniAssetComponent->IsComponentValid()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + if (AssetName != "Default__HoudiniAssetActor") + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is invalid"), *AssetName); + continue; + } + + // If component is not cooking or instancing, we can bake blueprint. + if (HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is actively instantiating or cooking"), *AssetName); + continue; + } + + bool bSuccess = false; + bool BakeToBlueprints = true; + if (BakeToBlueprints) + { + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // bSuccess = true; + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + const bool bInReplaceAssets = true; + bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + if (bSuccess) + { + // Instantiate blueprints in component's level, then remove houdini asset actor + bSuccess = false; + ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); + if (IsValid(Level)) + { + UWorld* World = Level->GetWorld(); + if (IsValid(World)) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = Level; + FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); + for (UBlueprint* Blueprint : Blueprints) + { + if (!IsValid(Blueprint)) + continue; + World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); + } + + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + bSuccess = true; + } + } + } + } + else + { + // TODO: this used to have a way to not select in v1 + // if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent)) + // bSuccess = true; + const bool bReplaceActors = true; + const bool bReplaceAssets = true; + if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, bReplaceActors, bReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors)) + { + bSuccess = true; + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + } + } + + if (bSuccess) + BakedCount++; + } + + // Add a slate notification + Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); +} + +void +FHoudiniEngineCommands::PauseAssetCooking() +{ + // Revert the global flag + bool bCurrentCookingEnabled = !FHoudiniEngine::Get().IsCookingEnabled(); + FHoudiniEngine::Get().SetCookingEnabled(bCurrentCookingEnabled); + + // We need to refresh UI when pause cooking. Set refresh UI counter to be the number of current registered HACs. + if (!bCurrentCookingEnabled) + FHoudiniEngine::Get().SetUIRefreshCountWhenPauseCooking( FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount() ); + + // Add a slate notification + FString Notification = TEXT("Houdini Engine cooking paused"); + if (bCurrentCookingEnabled) + Notification = TEXT("Houdini Engine cooking resumed"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + if (bCurrentCookingEnabled) + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking resumed.")); + else + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking paused.")); + + if (!bCurrentCookingEnabled) + return; + + /* + // If we are unpausing, tick each asset component to "update" them + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!")); + continue; + } + + HoudiniAssetComponent->StartHoudiniTicking(); + } + */ +} + +bool +FHoudiniEngineCommands::IsAssetCookingPaused() +{ + return !FHoudiniEngine::Get().IsCookingEnabled(); +} + +void +FHoudiniEngineCommands::RecookSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Cooking selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and cook the assets if they're in a valid state + int32 CookedCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedCook(); + CookedCount++; + } + + // Add a slate notification + Notification = TEXT("Re-cooking ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-cooking %d selected Houdini assets."), CookedCount); +} + +void +FHoudiniEngineCommands::RecookAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Cooking all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 CookedCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedCook(); + CookedCount++; + } + + // Add a slate notification + Notification = TEXT("Re-cooked ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-cooked %d Houdini assets in the current level."), CookedCount); +} + +void +FHoudiniEngineCommands::RebuildAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Re-building all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 RebuiltCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedRebuild(); + RebuiltCount++; + } + + // Add a slate notification + Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d Houdini assets in the current level."), RebuiltCount); +} + +void +FHoudiniEngineCommands::RebuildSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Rebuilding selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and rebuilds the assets if they're in a valid state + int32 RebuiltCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid()) + continue; + + HoudiniAssetComponent->MarkAsNeedRebuild(); + RebuiltCount++; + } + + // Add a slate notification + Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d selected Houdini assets."), RebuiltCount); +} + +void +FHoudiniEngineCommands::BakeSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Baking selected Houdini Asset Actors in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and rebuilds the assets if they're in a valid state + int32 BakedCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); + continue; + } + + if (!HoudiniAssetComponent->IsComponentValid()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + HOUDINI_LOG_ERROR(TEXT("Failed to export Houdini Asset: %s in the scene!"), *AssetName); + continue; + } + + // If component is not cooking or instancing, we can bake blueprint. + if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // BakedCount++; + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // bSuccess = true; + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + const bool bReplaceAssets = true; + const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, bReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + if (bSuccess) + { + // Instantiate blueprints in component's level, then remove houdini asset actor + ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); + if (IsValid(Level)) + { + UWorld* World = Level->GetWorld(); + if (IsValid(World)) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = Level; + FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); + for (UBlueprint* Blueprint : Blueprints) + { + if (!IsValid(Blueprint)) + continue; + World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); + } + + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + BakedCount++; + } + } + } + } + } + + // Add a slate notification + Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); +} + +// Recentre HoudiniAsset actors' pivots to their input / cooked static-mesh average centre. +void FHoudiniEngineCommands::RecentreSelection() +{ + /* +#if WITH_EDITOR + //Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Recentering selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and cook the assets if they're in a valid state + int32 RecentreCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || !HoudiniAssetComponent->IsComponentValid()) + continue; + + // Get the average centre of all the created Static Meshes + FVector AverageBoundsCentre = FVector::ZeroVector; + int32 NumBounds = 0; + const FVector CurrentLocation = HoudiniAssetComponent->GetComponentLocation(); + { + //Check Static Meshes + TArray StaticMeshes; + StaticMeshes.Reserve(16); + HoudiniAssetComponent->GetAllUsedStaticMeshes(StaticMeshes); + + //Get average centre of all the static meshes. + for (const UStaticMesh* pMesh : StaticMeshes) + { + if (!pMesh) + continue; + + //to world space + AverageBoundsCentre += (pMesh->GetBounds().Origin + CurrentLocation); + NumBounds++; + } + } + + //Check Inputs + if (0 == NumBounds) + { + const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs; + for (const UHoudiniInput* pInput : AssetInputs) + { + if (!pInput || pInput->IsPendingKill()) + continue; + + // to world space + FBox Bounds = pInput->GetInputBounds(CurrentLocation); + if (Bounds.IsValid) + { + AverageBoundsCentre += Bounds.GetCenter(); + NumBounds++; + } + } + } + + //if we have more than one, get the average centre + if (NumBounds > 1) + { + AverageBoundsCentre /= (float)NumBounds; + } + + //if we need to move... + float fDist = FVector::DistSquared(CurrentLocation, AverageBoundsCentre); + if (NumBounds && fDist > 1.0f) + { + // Move actor to average centre and recook + // This will refresh the static mesh under the HoudiniAssestComponent ( undoing the translation ). + HoudiniAssetActor->SetActorLocation(AverageBoundsCentre, false, nullptr, ETeleportType::TeleportPhysics); + + // Recook now the houdini-static-mesh has a new origin + HoudiniAssetComponent->StartTaskAssetCookingManual(); + RecentreCount++; + } + } + + if (RecentreCount) + { + // UE4 Editor doesn't refresh the translation-handles until they are re-selected, confusing the user, deselect the objects. + GEditor->SelectNone(true, false); + } + + // Add a slate notification + Notification = TEXT("Re-centred ") + FString::FromInt(RecentreCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-centred %d selected Houdini assets."), RecentreCount); + +#endif //WITH_EDITOR + */ +} + +void +FHoudiniEngineCommands::OpenSessionSync() +{ + //if (!FHoudiniEngine::IsInitialized()) + // return; + + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - HAPI Not initialized")); + return; + } + + // Get the runtime settings to get the session/type and settings + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + + EHoudiniRuntimeSettingsSessionType SessionType = HoudiniRuntimeSettings->SessionType; + FString ServerPipeName = HoudiniRuntimeSettings->ServerPipeName; + int32 ServerPort = HoudiniRuntimeSettings->ServerPort; + + FString SessionSyncArgs = TEXT("-hess="); + if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe) + { + // Add the -hess=pipe:hapi argument + SessionSyncArgs += TEXT("pipe:") + ServerPipeName; + } + else if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_Socket) + { + // Add the -hess=port:9090 argument + SessionSyncArgs += TEXT("port:") + FString::FromInt(ServerPort); + } + else + { + // Invalid session type + HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - Invalid session type")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Opening Houdini Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Opening Houdini Session Sync.")); + + // Only launch Houdini in Session sync if we havent started it already! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + if (!FPlatformProcess::IsProcRunning(PreviousHESS)) + { + // Start houdini with the -hess commandline args + const FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); +# if PLATFORM_MAC + const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../Resources/bin"); +# elif PLATFORM_LINUX + const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../bin"); +# elif PLATFORM_WINDOWS + const FString HoudiniExeLocationRelativeToLibHAPI; +# else + // Treat an unknown platform the same as Windows for now + const FString HoudiniExeLocationRelativeToLibHAPI; +# endif + FString HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/houdini"); + HOUDINI_LOG_MESSAGE(TEXT("Path to houdini executable: %s"), *HoudiniLocation); + FProcHandle HESSHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *SessionSyncArgs, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!HESSHandle.IsValid()) + { + // Try with the steam version executable instead + HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/hindie.steam"); + HOUDINI_LOG_MESSAGE(TEXT("Path to hindie.steam executable: %s"), *HoudiniLocation); + + HESSHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *SessionSyncArgs, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!HESSHandle.IsValid()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to launch Houdini in Session Sync mode.")); + return; + } + } + + // Keep track of the SessionSync ProcHandle + FHoudiniEngine::Get().SetHESSProcHandle(HESSHandle); + } + + // Start an Async task to connect to Session Sync + Async(EAsyncExecution::TaskGraphMainThread, [SessionType, ServerPipeName, ServerPort]() + { + // Use a timeout to avoid waiting indefinitely for H to start in session sync mode + const double Timeout = 180.0; // 3min + const double StartTimestamp = FPlatformTime::Seconds(); + + FString ServerHost = TEXT("localhost"); + while (!FHoudiniEngine::Get().SessionSyncConnect(SessionType, ServerPipeName, ServerHost, ServerPort)) + { + // Houdini might not be done loading, sleep for one second + FPlatformProcess::Sleep(1); + + // Check for the timeout + if (FPlatformTime::Seconds() - StartTimestamp > Timeout) + { + // ... and a log message + HOUDINI_LOG_ERROR(TEXT("Failed to start SessionSync - Timeout...")); + return false; + } + } + + // Initialize HAPI with this session + if (!FHoudiniEngine::Get().InitializeHAPISession()) + { + FHoudiniEngine::Get().StopTicking(); + return false; + } + + // Notify all HACs that they need to instantiate in the new session + MarkAllHACsAsNeedInstantiation(); + + // Start ticking + FHoudiniEngine::Get().StartTicking(); + + // Add a slate notification + FString Notification = TEXT("Succesfully connected to Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Succesfully connected to Session Sync...")); + + return true; + }); +} + +void +FHoudiniEngineCommands::CloseSessionSync() +{ + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to stop Session Sync - HAPI Not initialized")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Stopping Houdini Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Stopping Houdini Session Sync.")); + + // Stop Houdini Session sync if it is still running! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + if (FPlatformProcess::IsProcRunning(PreviousHESS)) + { + FPlatformProcess::TerminateProc(PreviousHESS, true); + } +} + +void +FHoudiniEngineCommands::SetViewportSync(const int32& ViewportSync) +{ + if (ViewportSync == 1) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); + } + else if (ViewportSync == 2) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); + } + else if (ViewportSync == 3) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); + } + else + { + FHoudiniEngine::Get().SetSyncViewportEnabled(false); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); + } +} + +int32 +FHoudiniEngineCommands::GetViewportSync() +{ + if(!FHoudiniEngine::Get().IsSyncViewportEnabled()) + return 0; + + bool bSyncH = FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled(); + bool bSyncU = FHoudiniEngine::Get().IsSyncUnrealViewportEnabled(); + if (bSyncH && !bSyncU) + return 1; + else if (!bSyncH && bSyncU) + return 2; + else if (bSyncH && bSyncU) + return 3; + else + return 0; +} + +void +FHoudiniEngineCommands::RestartSession() +{ + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().RestartSession()) + return; + + // We've successfully restarted the Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::CreateSession() +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().CreateSession(HoudiniRuntimeSettings->SessionType)) + return; + + // We've successfully created the Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::ConnectSession() +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().ConnectSession(HoudiniRuntimeSettings->SessionType)) + return; + + // We've successfully connected to a Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation() +{ + // Notify all the HoudiniAssetComponents that they need to re instantiate themselves in the new Houdini engine session. + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedInstantiation(); + } +} + +bool +FHoudiniEngineCommands::IsSessionValid() +{ + return FHoudiniEngine::IsInitialized(); +} + +bool +FHoudiniEngineCommands::IsSessionSyncProcessValid() +{ + // Only launch Houdini in Session sync if we havent started it already! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + return FPlatformProcess::IsProcRunning(PreviousHESS); +} + +void +FHoudiniEngineCommands::StopSession() +{ + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine session stopped.")); + } +} + +EHoudiniProxyRefineRequestResult +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE) +{ + // Get current world selection + TArray WorldSelection; + int32 NumSelectedHoudiniAssets = 0; + if (bOnlySelectedActors) + { + NumSelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (NumSelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return EHoudiniProxyRefineRequestResult::Invalid; + } + } + + // Add a slate notification + FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // First find the components that have meshes that we must refine + TArray ComponentsToRefine; + TArray ComponentsToCook; + // Components that would be candidates for refinement/cooking, but have errors + TArray SkippedComponents; + if (bOnlySelectedActors) + { + for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Index]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + } + else + { + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + } + + return RefineTriagedHoudiniProxyMesehesToStaticMeshes( + ComponentsToRefine, + ComponentsToCook, + SkippedComponents, + bSilent, + bRefineAll, + bOnPreSaveWorld, + OnPreSaveWorld, + bOnPreBeginPIE + ); +} + +EHoudiniProxyRefineRequestResult +FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent) +{ + const bool bRefineAll = true; + const bool bOnPreSaveWorld = false; + UWorld* OnPreSaveWorld = nullptr; + const bool bOnPreBeginPIE = false; + + // First find the components that have meshes that we must refine + TArray ComponentsToRefine; + TArray ComponentsToCook; + // Components that would be candidates for refinement/cooking, but have errors + TArray SkippedComponents; + for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine) + { + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + + return RefineTriagedHoudiniProxyMesehesToStaticMeshes( + ComponentsToRefine, + ComponentsToCook, + SkippedComponents, + bSilent, + bRefineAll, + bOnPreSaveWorld, + OnPreSaveWorld, + bOnPreBeginPIE + ); +} + +void +FHoudiniEngineCommands::StartPDGCommandlet() +{ + FHoudiniEngine::Get().StartPDGCommandlet(); +} + +void +FHoudiniEngineCommands::StopPDGCommandlet() +{ + FHoudiniEngine::Get().StopPDGCommandlet(); +} + +bool +FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected() +{ + return FHoudiniEngine::Get().IsPDGCommandletRunningOrConnected(); +} + +bool +FHoudiniEngineCommands::IsPDGCommandletEnabled() +{ + const UHoudiniRuntimeSettings* const Settings = GetDefault(); + if (IsValid(Settings)) + { + return Settings->bPDGAsyncCommandletImportEnabled; + } + + return false; +} + +bool +FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled) +{ + UHoudiniRuntimeSettings* const Settings = GetMutableDefault(); + if (IsValid(Settings)) + { + Settings->bPDGAsyncCommandletImportEnabled = InEnabled; + return true; + } + + return false; +} + +void +FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped) +{ + if (!InHAC || InHAC->IsPendingKill()) + return; + + // Make sure that the component's World and Owner are valid + AActor *Owner = InHAC->GetOwner(); + if (!Owner || Owner->IsPendingKill()) + return; + + UWorld *World = InHAC->GetWorld(); + if (!World || World->IsPendingKill()) + return; + + if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World) + return; + + // Check if we should consider this component for proxy mesh refinement based on its settings and + // flags passed to the function + if (bRefineAll || + (bOnPreSaveWorld && InHAC->IsProxyStaticMeshRefinementOnPreSaveWorldEnabled()) || + (bOnPreBeginPIE && InHAC->IsProxyStaticMeshRefinementOnPreBeginPIEEnabled())) + { + TArray ProxyMeshPackagesToSave; + TArray ComponentsWithProxiesToSave; + + if (InHAC->HasAnyCurrentProxyOutput()) + { + // Get the state of the asset and check if it is cooked + // If it is not cook, request a cook. We can only build the UStaticMesh + // if the data from the cook is available + // If the state is not pre-cook, or None (cooked), then the state is invalid, + // log an error and skip the component + bool bNeedsRebuildOrDelete = false; + bool bUnsupportedState = false; + const bool bCookedDataAvailable = InHAC->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bUnsupportedState); + if (bCookedDataAvailable) + { + OutToRefine.Add(InHAC); + ComponentsWithProxiesToSave.Add(InHAC); + } + else if (!bUnsupportedState && !bNeedsRebuildOrDelete) + { + InHAC->MarkAsNeedCook(); + // Force the output of the cook to be directly created as a UStaticMesh and not a proxy + InHAC->SetNoProxyMeshNextCookRequested(true); + OutToCook.Add(InHAC); + ComponentsWithProxiesToSave.Add(InHAC); + } + else + { + OutSkipped.Add(InHAC); + const EHoudiniAssetState AssetState = InHAC->GetAssetState(); + HOUDINI_LOG_ERROR(TEXT("Could not refine %s, the asset is in an unsupported state: %s"), *(InHAC->GetPathName()), *(UEnum::GetValueAsString(AssetState))); + } + } + else if (InHAC->HasAnyProxyOutput()) + { + // If the HAC has non-current proxies, destroy them + // TODO: Make this its own command? + const uint32 NumOutputs = InHAC->GetNumOutputs(); + for (uint32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = InHAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + TMap& OutputObjects = Output->GetOutputObjects(); + for (auto& CurrentPair : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; + if (!CurrentOutputObject.bProxyIsCurrent) + { + // The proxy is not current, delete it and its component + USceneComponent* FoundProxyComponent = Cast(CurrentOutputObject.ProxyComponent); + if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (FoundProxyComponent->GetOwner()) + FoundProxyComponent->GetOwner()->RemoveOwnedComponent(FoundProxyComponent); + + FoundProxyComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + FoundProxyComponent->UnregisterComponent(); + FoundProxyComponent->DestroyComponent(); + } + + UObject* ProxyObject = CurrentOutputObject.ProxyObject; + if (!ProxyObject || ProxyObject->IsPendingKill()) + continue; + + ProxyObject->MarkPendingKill(); + ProxyObject->MarkPackageDirty(); + UPackage* const Package = ProxyObject->GetPackage(); + if (IsValid(Package)) + ProxyMeshPackagesToSave.Add(Package); + } + } + } + } + + for (UHoudiniAssetComponent* const HAC : ComponentsWithProxiesToSave) + { + const uint32 NumOutputs = HAC->GetNumOutputs(); + for (uint32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + TMap& OutputObjects = Output->GetOutputObjects(); + for (auto& CurrentPair : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; + if (CurrentOutputObject.bProxyIsCurrent && CurrentOutputObject.ProxyObject) + { + UPackage* const Package = CurrentOutputObject.ProxyObject->GetPackage(); + if (IsValid(Package) && Package->IsDirty()) + ProxyMeshPackagesToSave.Add(Package); + } + } + } + } + + if (ProxyMeshPackagesToSave.Num() > 0) + { + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + FEditorFileUtils::PromptForCheckoutAndSave(ProxyMeshPackagesToSave, true, false); + } + } +} + +EHoudiniProxyRefineRequestResult +FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( + const TArray& InComponentsToRefine, + const TArray& InComponentsToCook, + const TArray& InSkippedComponents, + bool bInSilent, + bool bInRefineAll, + bool bInOnPreSaveWorld, + UWorld* InOnPreSaveWorld, + bool bInOnPrePIEBeginPlay) +{ + // Slate notification text + FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); + + const uint32 NumComponentsToCook = InComponentsToCook.Num(); + const uint32 NumComponentsToRefine = InComponentsToRefine.Num(); + const uint32 NumComponentsToProcess = NumComponentsToCook + NumComponentsToRefine; + + TArray SuccessfulComponents; + TArray FailedComponents; + TArray SkippedComponents(InSkippedComponents); + + if (NumComponentsToProcess > 0) + { + // The task progress pointer is potentially going to be shared with a background thread and tasks + // on the main thread, so make it thread safe + TSharedPtr TaskProgress = MakeShareable(new FSlowTask((float)NumComponentsToProcess, FText::FromString(Notification))); + TaskProgress->Initialize(); + if (!bInSilent) + TaskProgress->MakeDialog(/*bShowCancelButton=*/true); + + // Iterate over the components for which we can build UStaticMesh, and build the meshes + bool bCancelled = false; + for (uint32 ComponentIndex = 0; ComponentIndex < NumComponentsToRefine; ++ComponentIndex) + { + UHoudiniAssetComponent* HoudiniAssetComponent = InComponentsToRefine[ComponentIndex]; + TaskProgress->EnterProgressFrame(1.0f); + const bool bDestroyProxies = true; + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HoudiniAssetComponent, bDestroyProxies); + + SuccessfulComponents.Add(HoudiniAssetComponent); + + bCancelled = TaskProgress->ShouldCancel(); + if (bCancelled) + { + for (uint32 SkippedIndex = ComponentIndex + 1; SkippedIndex < NumComponentsToRefine; ++SkippedIndex) + { + SkippedComponents.Add(InComponentsToRefine[ComponentIndex]); + } + break; + } + } + + if (bCancelled && NumComponentsToCook > 0) + { + for (UHoudiniAssetComponent* const HAC : InComponentsToCook) + { + SkippedComponents.Add(HAC); + } + } + + if (NumComponentsToCook > 0 && !bCancelled) + { + // Now use an async task to check on the progress of the cooking components + Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents]() { + RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread( + InComponentsToCook, TaskProgress, NumComponentsToProcess, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); + }); + + // We have to wait for cook(s) before completing refinement + return EHoudiniProxyRefineRequestResult::PendingCooks; + } + else + { + RefineHoudiniProxyMeshesToStaticMeshesNotifyDone( + NumComponentsToProcess, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); + + // We didn't have to cook anything, so refinement is complete. + return EHoudiniProxyRefineRequestResult::Refined; + } + } + + // Nothing to refine + return EHoudiniProxyRefineRequestResult::None; +} + + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray& InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumComponentsToProcess, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents) +{ + // Copy to a double linked list so that we can loop through + // to check progress of each component and remove it easily + // if it has completed/failed + TDoubleLinkedList CookList; + for (UHoudiniAssetComponent *HAC : InComponentsToCook) + { + CookList.AddTail(HAC); + } + + // Add the successfully cooked components to the incoming successful components (previously refined) + TArray SuccessfulComponents(InSuccessfulComponents); + TArray FailedComponents(InFailedComponents); + TArray SkippedComponents(InSkippedComponents); + + bool bCancelled = false; + uint32 NumFailedToCook = 0; + while (CookList.Num() > 0 && !bCancelled) + { + TDoubleLinkedList::TDoubleLinkedListNode *Node = CookList.GetHead(); + while (Node && !bCancelled) + { + TDoubleLinkedList::TDoubleLinkedListNode *Next = Node->GetNextNode(); + UHoudiniAssetComponent* HAC = Node->GetValue(); + + if (HAC && !HAC->IsPendingKill()) + { + const EHoudiniAssetState State = HAC->GetAssetState(); + const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult(); + bool bUpdateProgress = false; + if (State == EHoudiniAssetState::None) + { + // Cooked, count as success, remove node + CookList.RemoveNode(Node); + SuccessfulComponents.Add(HAC); + bUpdateProgress = true; + } + else if (ResultState != EHoudiniAssetStateResult::None && ResultState != EHoudiniAssetStateResult::Working) + { + // Failed, remove node + HOUDINI_LOG_ERROR(TEXT("Failed to cook %s to obtain static mesh."), *(HAC->GetPathName())); + CookList.RemoveNode(Node); + FailedComponents.Add(HAC); + bUpdateProgress = true; + NumFailedToCook++; + } + + if (bUpdateProgress && InTaskProgress.IsValid()) + { + // Update progress only on the main thread, and check for cancellation request + bCancelled = Async(EAsyncExecution::TaskGraphMainThread, [InTaskProgress]() { + InTaskProgress->EnterProgressFrame(1.0f); + return InTaskProgress->ShouldCancel(); + }).Get(); + } + } + else + { + SkippedComponents.Add(HAC); + CookList.RemoveNode(Node); + } + + Node = Next; + } + FPlatformProcess::Sleep(0.01f); + } + + if (bCancelled) + { + HOUDINI_LOG_WARNING(TEXT("Mesh refinement cancelled while waiting for %d components to cook."), CookList.Num()); + // Mark any remaining HACs in the cook list as skipped + TDoubleLinkedList::TDoubleLinkedListNode* Node = CookList.GetHead(); + while (Node) + { + TDoubleLinkedList::TDoubleLinkedListNode* const Next = Node->GetNextNode(); + UHoudiniAssetComponent* HAC = Node->GetValue(); + if (HAC) + SkippedComponents.Add(HAC); + CookList.RemoveNode(Node); + Node = Next; + } + } + + // Cooking is done, or failed, display the notifications on the main thread + Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents]() { + RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); + }); +} + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(const uint32 InNumTotalComponents, FSlowTask* const InTaskProgress, const bool bCancelled, const bool bOnPreSaveWorld, UWorld* const InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents) +{ + FString Notification; + const uint32 NumSkippedComponents = InSkippedComponents.Num(); + const uint32 NumFailedToCook = InFailedComponents.Num(); + if (NumSkippedComponents + NumFailedToCook > 0) + { + if (bCancelled) + { + Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), NumSkippedComponents + NumFailedToCook, InNumTotalComponents); + } + else + { + Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), NumSkippedComponents + NumFailedToCook, InNumTotalComponents); + } + FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_ERROR(TEXT("%s"), *Notification); + } + else if (InNumTotalComponents > 0) + { + Notification = TEXT("Done: Refining Houdini proxy meshes to static meshes."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_MESSAGE(TEXT("%s"), *Notification); + } + if (InTaskProgress) + { + InTaskProgress->Destroy(); + } + if (bOnPreSaveWorld && InSuccessfulComponents.Num() > 0) + { + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + + // Save the dirty static meshes in InSuccessfulComponents OnPostSaveWorld + // TODO: Remove? This may not be necessary now as we save all dirty temporary cook data in PostSaveWorld() already (Static Meshes, Materials...) + OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([InSuccessfulComponents, bOnPreSaveWorld, InOnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) { + if (bOnPreSaveWorld && InOnPreSaveWorld && InOnPreSaveWorld != InWorld) + return; + + RefineProxyMeshesHandleOnPostSaveWorld(InSuccessfulComponents, InSaveFlags, InWorld, bInSuccess); + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + }); + } + + // Broadcast refinement result per HAC + for (UHoudiniAssetComponent* const HAC : InSuccessfulComponents) + { + if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) + OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Success); + } + for (UHoudiniAssetComponent* const HAC : InFailedComponents) + { + if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) + OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Failed); + } + for (UHoudiniAssetComponent* const HAC : InSkippedComponents) + { + if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) + OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Skipped); + } +} + +void +FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) +{ + TArray PackagesToSave; + + for (UHoudiniAssetComponent* HAC : InSuccessfulComponents) + { + if (!HAC || HAC->IsPendingKill()) + continue; + + const int32 NumOutputs = HAC->GetNumOutputs(); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (auto &OutputObjectPair : Output->GetOutputObjects()) + { + UObject *Obj = OutputObjectPair.Value.OutputObject; + if (!Obj || Obj->IsPendingKill()) + continue; + + UStaticMesh *SM = Cast(Obj); + if (!SM) + continue; + + UPackage *Package = SM->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + } + } + + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h index 6cd08f21f..b8b1516a2 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h @@ -1,263 +1,305 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineStyle.h" - -#include "Framework/Commands/Commands.h" -#include "Misc/SlowTask.h" -#include "Delegates/IDelegateInstance.h" - -class UHoudiniAssetComponent; -class AHoudiniAssetActor; -struct FSlowTask; - -// Class containing commands for Houdini Engine actions -class FHoudiniEngineCommands : public TCommands -{ -public: - FHoudiniEngineCommands() - : TCommands - ( - TEXT("HoudiniEngine"), // Context name for fast lookup - NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), // Localized context name for displaying - NAME_None, // Parent context name. - FHoudiniEngineStyle::GetStyleSetName() // Icon Style Set - ) - { - } - - // TCommand<> interface - virtual void RegisterCommands() override; - -public: - - // Menu action called to save a HIP file. - static void SaveHIPFile(); - - // Menu action called to report a bug. - static void ReportBug(); - - // Menu action called to open the current scene in Houdini. - static void OpenInHoudini(); - - // Menu action called to clean up all unused files in the cook temp folder - static void CleanUpTempFolder(); - - // Menu action to bake/replace all current Houdini Assets with blueprints - static void BakeAllAssets(); - - // Helper function for baking/replacing the current select Houdini Assets with blueprints - static void BakeSelection(); - - // Helper function for restarting the current Houdini Engine session. - static void RestartSession(); - - // Menu action to pause cooking for all Houdini Assets - static void PauseAssetCooking(); - - // Helper delegate used to get the current state of PauseAssetCooking. - static bool IsAssetCookingPaused(); - - // Helper function for recooking all assets in the current level - static void RecookAllAssets(); - - // Helper function for rebuilding all assets in the current level - static void RebuildAllAssets(); - - // Helper function for recooking selected assets - static void RecookSelection(); - - // Helper function for rebuilding selected assets - static void RebuildSelection(); - - // Helper function for rebuilding selected assets - static void RecentreSelection(); - - // Helper function for starting Houdini in Sesion Sync mode - static void OpenSessionSync(); - - // Helper function for closing the current Houdini Sesion Sync - static void CloseSessionSync(); - - // returns true if the current HE session is valid - static bool IsSessionValid(); - - // Returns true if the current Session Sync process is still running - static bool IsSessionSyncProcessValid(); - - static int32 GetViewportSync(); - - static void SetViewportSync(const int32& ViewportSync); - - static void CreateSession(); - - static void ConnectSession(); - - static void StopSession(); - - static void ShowInstallInfo(); - - static void ShowPluginSettings(); - - static void OnlineDocumentation(); - - static void OnlineForum(); - - // Helper function for building static meshes for all assets using HoudiniStaticMesh - // If bSilent is false, show a progress dialog. - // If bRefineAll is true, then all components with HoudiniStaticMesh meshes will be - // refined to UStaticMesh. Otherwise, bOnPreSaveWorld and bOnPrePIEBeginPlay is checked - // against the settings of the component to determine if refinement should take place. - // If bOnPreSaveWorld is true, then OnPreSaveWorld should be the World that is being saved. In - // that case, only proxy meshes attached to components from that world will be refined. - static void RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent=false, bool bRefineAll=true, bool bOnPreSaveWorld=false, UWorld *PreSaveWorld=nullptr, bool bOnPrePIEBeginPlay=false); - - // Refine all proxy meshes on UHoudiniAssetCompoments of InActorsToRefine. - static void RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent=false); - - static void StartPDGCommandlet(); - - static void StopPDGCommandlet(); - - static bool IsPDGCommandletRunningOrConnected(); - - // Returns true if the commandlet is enabled in the settings - static bool IsPDGCommandletEnabled(); - - // Set the bPDGAsyncCommandletImportEnabled in the settings - static bool SetPDGCommandletEnabled(bool InEnabled); - - static FDelegateHandle& GetOnPostSaveWorldRefineProxyMeshesHandle() { return OnPostSaveWorldRefineProxyMeshesHandle; } - -public: - - // UI Action to create a Houdini Engine Session - TSharedPtr _CreateSession; - // UI Action to connect to a Houdini Engine Session - TSharedPtr _ConnectSession; - // UI Action to stop the current Houdini Engine Session - TSharedPtr _StopSession; - // UI Action to restart the current Houdini Engine Session - TSharedPtr _RestartSession; - // UI Action to open Houdini Session Sync - TSharedPtr _OpenSessionSync; - // UI Action to open Houdini Session Sync - TSharedPtr _CloseSessionSync; - - // UI Action to disable viewport sync - TSharedPtr _ViewportSyncNone; - // UI Action to enable unreal viewport sync - TSharedPtr _ViewportSyncUnreal; - // UI Action to enable houdini viewport sync - TSharedPtr _ViewportSyncHoudini; - // UI Action to enable bidirectionnal viewport sync - TSharedPtr _ViewportSyncBoth; - - // - TSharedPtr _InstallInfo; - // - TSharedPtr _PluginSettings; - - // Menu action called to open the current scene in Houdini. - TSharedPtr _OpenInHoudini; - // Menu action called to save a HIP file. - TSharedPtr _SaveHIPFile; - // Menu action called to clean up all unused files in the cook temp folder - TSharedPtr _CleanUpTempFolder; - - // - TSharedPtr _OnlineDoc; - // - TSharedPtr _OnlineForum; - // Menu action called to report a bug. - TSharedPtr _ReportBug; - - // UI Action to recook all HDA - TSharedPtr _CookAll; - // UI Action to recook the current world selection - TSharedPtr _CookSelected; - // Menu action to bake/replace all current Houdini Assets with blueprints - TSharedPtr _BakeAll; - // UI Action to bake and replace the current world selection - TSharedPtr _BakeSelected; - // UI Action to rebuild all HDA - TSharedPtr _RebuildAll; - // UI Action to rebuild the current world selection - TSharedPtr _RebuildSelected; - // UI Action for building static meshes for all assets using HoudiniStaticMesh - TSharedPtr _RefineAll; - // UI Action for building static meshes for selected assets using HoudiniStaticMesh - TSharedPtr _RefineSelected; - // Menu action to pause cooking for all Houdini Assets - TSharedPtr _PauseAssetCooking; - - // UI Action to recentre the current selection - TSharedPtr _RecentreSelected; - - // Start PDG/BGEO commandlet - TSharedPtr _StartPDGCommandlet; - // Stop PDG/BGEO commandlet - TSharedPtr _StopPDGCommandlet; - // Is PDG/BGEO commandlet enabled - TSharedPtr _IsPDGCommandletEnabled; - -protected: - - // Triage a HoudiniAssetComponent with UHoudiniStaticMesh as needing cooking or if a UStaticMesh can be immediately built - static void TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped); - - static void RefineTriagedHoudiniProxyMesehesToStaticMeshes( - const TArray& InComponentsToRefine, - const TArray& InComponentsToCook, - const TArray& InSkippedComponents, - bool bInSilent=false, - bool bInRefineAll=true, - bool bInOnPreSaveWorld=false, - UWorld* InOnPreSaveWorld=nullptr, - bool bInOnPrePIEBeginPlay=false); - - // Called in a background thread by RefineHoudiniProxyMeshesToStaticMeshes when some components need to be cooked to generate UStaticMeshes. Checks and waits for - // cooking of each component to complete, and then calls RefineHoudiniProxyMeshesToStaticMeshesNotifyDone on the main thread. - static void RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray &InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumComponentsToProcess, const uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); - - // Display a notification / end/close progress dialog, when refining mesh proxies to static meshes is complete - static void RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(uint32 InNumTotalComponents, uint32 InNumSkippedComponents, uint32 InNumFailedToCook, FSlowTask *InTaskProgress, bool bCancelled, bool bOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents); - - // Handle OnPostSaveWorld for refining proxy meshes: this saves all the dirty UPackages of the UStaticMeshes that were created during RefineHoudiniProxyMeshesToStaticMeshes - // if it was called as a result of a PreSaveWorld. - static void RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess); - - // Helper function used to indicate to all HAC that they need to be instantiated in the new HE session - // Needs to be call after starting/restarting/connecting/session syncing a HE session.. - static void MarkAllHACsAsNeedInstantiation(); - - // Delegate that is set up to refined proxy meshes post save world (it removes itself afterwards) - static FDelegateHandle OnPostSaveWorldRefineProxyMeshesHandle; - -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineStyle.h" + +#include "Framework/Commands/Commands.h" +#include "Misc/SlowTask.h" +#include "Delegates/IDelegateInstance.h" + +class UHoudiniAssetComponent; +class AHoudiniAssetActor; +struct FSlowTask; + +// When attempting to refine proxy mesh outputs it is a possible that a cook is needed. The enum +// defines the possible return values on a request to refine proxies. +UENUM() +enum class EHoudiniProxyRefineRequestResult : uint8 +{ + Invalid, + + // No refinement is needed + None, + // A cook is needed, refinement will commence automatically after the cook + PendingCooks, + // Successfully refined + Refined, + + Max, +}; + +// When attempting to refine proxy mesh outputs it is a possible that a cook is needed. The enum +// defines the possible return values on a request to refine proxies. +UENUM() +enum class EHoudiniProxyRefineResult : uint8 +{ + Invalid, + + // Refinement (or cook if needed) failed + Failed, + // Refinement completed successfully + Success, + // Refinement was skipped, either it was not necessary or the operation was cancelled by the user + Skipped, + + Max, +}; + + +// Class containing commands for Houdini Engine actions +class FHoudiniEngineCommands : public TCommands +{ +public: + // Multi-cast delegate type for broadcasting when proxy mesh refinement of a HAC is complete. + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHoudiniProxyMeshesRefinedDelegate, UHoudiniAssetComponent* const, const EHoudiniProxyRefineResult); + + FHoudiniEngineCommands() + : TCommands + ( + TEXT("HoudiniEngine"), // Context name for fast lookup + NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), // Localized context name for displaying + NAME_None, // Parent context name. + FHoudiniEngineStyle::GetStyleSetName() // Icon Style Set + ) + { + } + + // TCommand<> interface + virtual void RegisterCommands() override; + +public: + + // Menu action called to save a HIP file. + static void SaveHIPFile(); + + // Menu action called to report a bug. + static void ReportBug(); + + // Menu action called to open the current scene in Houdini. + static void OpenInHoudini(); + + // Menu action called to clean up all unused files in the cook temp folder + static void CleanUpTempFolder(); + + // Menu action to bake/replace all current Houdini Assets with blueprints + static void BakeAllAssets(); + + // Helper function for baking/replacing the current select Houdini Assets with blueprints + static void BakeSelection(); + + // Helper function for restarting the current Houdini Engine session. + static void RestartSession(); + + // Menu action to pause cooking for all Houdini Assets + static void PauseAssetCooking(); + + // Helper delegate used to get the current state of PauseAssetCooking. + static bool IsAssetCookingPaused(); + + // Helper function for recooking all assets in the current level + static void RecookAllAssets(); + + // Helper function for rebuilding all assets in the current level + static void RebuildAllAssets(); + + // Helper function for recooking selected assets + static void RecookSelection(); + + // Helper function for rebuilding selected assets + static void RebuildSelection(); + + // Helper function for rebuilding selected assets + static void RecentreSelection(); + + // Helper function for starting Houdini in Sesion Sync mode + static void OpenSessionSync(); + + // Helper function for closing the current Houdini Sesion Sync + static void CloseSessionSync(); + + // returns true if the current HE session is valid + static bool IsSessionValid(); + + // Returns true if the current Session Sync process is still running + static bool IsSessionSyncProcessValid(); + + static int32 GetViewportSync(); + + static void SetViewportSync(const int32& ViewportSync); + + static void CreateSession(); + + static void ConnectSession(); + + static void StopSession(); + + static void ShowInstallInfo(); + + static void ShowPluginSettings(); + + static void OnlineDocumentation(); + + static void OnlineForum(); + + // Helper function for building static meshes for all assets using HoudiniStaticMesh + // If bSilent is false, show a progress dialog. + // If bRefineAll is true, then all components with HoudiniStaticMesh meshes will be + // refined to UStaticMesh. Otherwise, bOnPreSaveWorld and bOnPrePIEBeginPlay is checked + // against the settings of the component to determine if refinement should take place. + // If bOnPreSaveWorld is true, then OnPreSaveWorld should be the World that is being saved. In + // that case, only proxy meshes attached to components from that world will be refined. + static EHoudiniProxyRefineRequestResult RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent=false, bool bRefineAll=true, bool bOnPreSaveWorld=false, UWorld *PreSaveWorld=nullptr, bool bOnPrePIEBeginPlay=false); + + // Refine all proxy meshes on UHoudiniAssetCompoments of InActorsToRefine. + static EHoudiniProxyRefineRequestResult RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent=false); + + static void StartPDGCommandlet(); + + static void StopPDGCommandlet(); + + static bool IsPDGCommandletRunningOrConnected(); + + // Returns true if the commandlet is enabled in the settings + static bool IsPDGCommandletEnabled(); + + // Set the bPDGAsyncCommandletImportEnabled in the settings + static bool SetPDGCommandletEnabled(bool InEnabled); + + static FDelegateHandle& GetOnPostSaveWorldRefineProxyMeshesHandle() { return OnPostSaveWorldRefineProxyMeshesHandle; } + + static FOnHoudiniProxyMeshesRefinedDelegate& GetOnHoudiniProxyMeshesRefinedDelegate() { return OnHoudiniProxyMeshesRefinedDelegate; } + +public: + + // UI Action to create a Houdini Engine Session + TSharedPtr _CreateSession; + // UI Action to connect to a Houdini Engine Session + TSharedPtr _ConnectSession; + // UI Action to stop the current Houdini Engine Session + TSharedPtr _StopSession; + // UI Action to restart the current Houdini Engine Session + TSharedPtr _RestartSession; + // UI Action to open Houdini Session Sync + TSharedPtr _OpenSessionSync; + // UI Action to open Houdini Session Sync + TSharedPtr _CloseSessionSync; + + // UI Action to disable viewport sync + TSharedPtr _ViewportSyncNone; + // UI Action to enable unreal viewport sync + TSharedPtr _ViewportSyncUnreal; + // UI Action to enable houdini viewport sync + TSharedPtr _ViewportSyncHoudini; + // UI Action to enable bidirectionnal viewport sync + TSharedPtr _ViewportSyncBoth; + + // + TSharedPtr _InstallInfo; + // + TSharedPtr _PluginSettings; + + // Menu action called to open the current scene in Houdini. + TSharedPtr _OpenInHoudini; + // Menu action called to save a HIP file. + TSharedPtr _SaveHIPFile; + // Menu action called to clean up all unused files in the cook temp folder + TSharedPtr _CleanUpTempFolder; + + // + TSharedPtr _OnlineDoc; + // + TSharedPtr _OnlineForum; + // Menu action called to report a bug. + TSharedPtr _ReportBug; + + // UI Action to recook all HDA + TSharedPtr _CookAll; + // UI Action to recook the current world selection + TSharedPtr _CookSelected; + // Menu action to bake/replace all current Houdini Assets with blueprints + TSharedPtr _BakeAll; + // UI Action to bake and replace the current world selection + TSharedPtr _BakeSelected; + // UI Action to rebuild all HDA + TSharedPtr _RebuildAll; + // UI Action to rebuild the current world selection + TSharedPtr _RebuildSelected; + // UI Action for building static meshes for all assets using HoudiniStaticMesh + TSharedPtr _RefineAll; + // UI Action for building static meshes for selected assets using HoudiniStaticMesh + TSharedPtr _RefineSelected; + // Menu action to pause cooking for all Houdini Assets + TSharedPtr _PauseAssetCooking; + + // UI Action to recentre the current selection + TSharedPtr _RecentreSelected; + + // Start PDG/BGEO commandlet + TSharedPtr _StartPDGCommandlet; + // Stop PDG/BGEO commandlet + TSharedPtr _StopPDGCommandlet; + // Is PDG/BGEO commandlet enabled + TSharedPtr _IsPDGCommandletEnabled; + +protected: + + // Triage a HoudiniAssetComponent with UHoudiniStaticMesh as needing cooking or if a UStaticMesh can be immediately built + static void TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped); + + static EHoudiniProxyRefineRequestResult RefineTriagedHoudiniProxyMesehesToStaticMeshes( + const TArray& InComponentsToRefine, + const TArray& InComponentsToCook, + const TArray& InSkippedComponents, + bool bInSilent=false, + bool bInRefineAll=true, + bool bInOnPreSaveWorld=false, + UWorld* InOnPreSaveWorld=nullptr, + bool bInOnPrePIEBeginPlay=false); + + // Called in a background thread by RefineHoudiniProxyMeshesToStaticMeshes when some components need to be cooked to generate UStaticMeshes. Checks and waits for + // cooking of each component to complete, and then calls RefineHoudiniProxyMeshesToStaticMeshesNotifyDone on the main thread. + static void RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray &InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents); + + // Display a notification / end/close progress dialog, when refining mesh proxies to static meshes is complete + static void RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(const uint32 InNumTotalComponents, FSlowTask* const InTaskProgress, const bool bCancelled, const bool bOnPreSaveWorld, UWorld* const InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents); + + // Handle OnPostSaveWorld for refining proxy meshes: this saves all the dirty UPackages of the UStaticMeshes that were created during RefineHoudiniProxyMeshesToStaticMeshes + // if it was called as a result of a PreSaveWorld. + static void RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess); + + // Helper function used to indicate to all HAC that they need to be instantiated in the new HE session + // Needs to be call after starting/restarting/connecting/session syncing a HE session.. + static void MarkAllHACsAsNeedInstantiation(); + + // Delegate that is set up to refined proxy meshes post save world (it removes itself afterwards) + static FDelegateHandle OnPostSaveWorldRefineProxyMeshesHandle; + + // Delegate for broadcasting when proxy mesh refinement of a HAC's output is complete. + static FOnHoudiniProxyMeshesRefinedDelegate OnHoudiniProxyMeshesRefinedDelegate; +}; + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp index 790fadaf1..87d733797 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp @@ -1,1940 +1,1942 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetComponentDetails.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" - -#include "CoreMinimal.h" -#include "DetailCategoryBuilder.h" -#include "IDetailGroup.h" -#include "DetailWidgetRow.h" -#include "Widgets/SBoxPanel.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Layout/SScrollBox.h" -#include "Widgets/Input/SMultiLineEditableTextBox.h" -#include "Widgets/Layout/SUniformGridPanel.h" -#include "Brushes/SlateImageBrush.h" -#include "Widgets/Input/SComboBox.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "ActorPickerMode.h" -#include "SceneOutlinerModule.h" -#include "Modules/ModuleManager.h" -#include "Interfaces/IMainFrameModule.h" -#include "AssetThumbnail.h" -#include "DetailLayoutBuilder.h" -#include "SAssetDropTarget.h" -#include "PropertyCustomizationHelpers.h" -#include "ScopedTransaction.h" -#include "SEnumCombobox.h" -#include "HAL/FileManager.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define HOUDINI_ENGINE_UI_SECTION_GENERATE 1 -#define HOUDINI_ENGINE_UI_SECTION_BAKE 2 -#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS 3 -#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG 4 - -#define HOUDINI_ENGINE_UI_BUTTON_WIDTH 135.0f - -#define HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT "Generate" -#define HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT "Bake" -#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT "Asset Options" -#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT "Help and Debug" - - -void -SHoudiniAssetLogWidget::Construct(const FArguments & InArgs) -{ - this->ChildSlot - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background"))) - .Content() - [ - SNew(SScrollBox) - + SScrollBox::Slot() - [ - SNew(SMultiLineEditableTextBox) - .Text(FText::FromString(InArgs._LogText)) - .AutoWrapText(true) - .IsReadOnly(true) - ] - ] - ]; -} - - -void -FHoudiniEngineDetails::CreateWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // 0. Houdini Engine Icon - FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs); - - // 1. Houdini Engine Session Status - FHoudiniAssetComponentDetails::AddSessionStatusRow(HoudiniEngineCategoryBuilder); - - // 2. Create Generate Category - FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 3. Create Bake Category - FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 4. Create Asset Options Category - FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 5. Create Help and Debug Category - FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs); - -} - -void -FHoudiniEngineDetails::CreateHoudiniEngineIconWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Skip drawing the icon if the icon image is not loaded correctly. - TSharedPtr HoudiniEngineUIIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIIconBrush(); - if (!HoudiniEngineUIIconBrush.IsValid()) - return; - - FDetailWidgetRow & Row = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef Box = SNew(SHorizontalBox); - TSharedPtr Image; - - Box->AddSlot() - .AutoWidth() - .Padding(0.0f, 5.0f, 5.0f, 10.0f) - .HAlign(HAlign_Left) - [ - SNew(SBox) - .HeightOverride(30) - .WidthOverride(208) - [ - SAssignNew(Image, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - Image->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIIconBrush]() { - return HoudiniEngineUIIconBrush.Get(); - }))); - - Row.WholeRowWidget.Widget = Box; - Row.IsEnabled(false); -} - -void -FHoudiniEngineDetails::CreateGenerateWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - auto OnReBuildClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->MarkAsNeedRebuild(); - } - - return FReply::Handled(); - }; - - auto OnRecookClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->MarkAsNeedCook(); - } - - return FReply::Handled(); - }; - - auto ShouldEnableResetParametersButtonLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - // Reset parameters to default values? - for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) - { - UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); - - if (NextParm && !NextParm->IsDefault()) - return true; - } - } - - return false; - }; - - auto OnResetParametersClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - // Reset parameters to default values? - for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) - { - UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); - - if (NextParm && !NextParm->IsDefault()) - { - NextParm->RevertToDefault(); - } - } - } - - return FReply::Handled(); - }; - - auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) - { - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FString NewPathStr = Val.ToString(); - - if (NewPathStr.IsEmpty()) - return; - - if (NewPathStr.StartsWith("Game/")) - { - NewPathStr = "/" + NewPathStr; - } - - FString AbsolutePath; - if (NewPathStr.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); - } - - if (!FPaths::DirectoryExists(AbsolutePath)) - { - HOUDINI_LOG_WARNING(TEXT("Invalid path")); - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - return; - } - - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr)) - continue; - - NextHAC->TemporaryCookFolder.Path = NewPathStr; - } - }; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_GENERATE); - - // Button Row (draw only if expanded) - if (!MainHAC->bGenerateMenuExpanded) - return; - - TSharedPtr HoudiniEngineUIRebuildIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRebuildIconBrush(); - TSharedPtr HoudiniEngineUIRecookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - TSharedPtr HoudiniEngineUIResetParametersIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIResetParametersIconBrush(); - - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef ButtonHorizontalBox = SNew(SHorizontalBox); - - // Recook button - TSharedPtr RecookButton; - TSharedPtr RecookButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(RecookButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) - //.Text(FText::FromString("Recook")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnRecookClickedLambda) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) - ] - ] - ] - ]; - - if (HoudiniEngineUIRecookIconBrush.IsValid()) - { - TSharedPtr RecookImage; - RecookButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RecookImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - RecookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { - return HoudiniEngineUIRecookIconBrush.Get(); - }))); - } - - RecookButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Recook")) - ]; - - // Rebuild button - TSharedPtr RebuildButton; - TSharedPtr RebuildButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(15.0f, 0.0f, 0.0f, 2.0f) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(RebuildButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) - //.Text(FText::FromString("Rebuild")) - .Visibility(EVisibility::Visible) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) - ] - ] - .OnClicked_Lambda(OnReBuildClickedLambda) - ] - ]; - - if (HoudiniEngineUIRebuildIconBrush.IsValid()) - { - TSharedPtr RebuildImage; - RebuildButtonHorizontalBox->AddSlot() - //.Padding(25.0f, 0.0f, 3.0f, 0.0f) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RebuildImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - RebuildImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { - return HoudiniEngineUIRebuildIconBrush.Get(); - }))); - } - - RebuildButtonHorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .AutoWidth() - .Padding(5.0, 0.0, 0.0, 0.0) - [ - SNew(STextBlock) - .Text(FText::FromString("Rebuild")) - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; - ButtonRow.IsEnabled(false); - - // Reset Parameters button - TSharedPtr ResetParametersButton; - TSharedPtr ResetParametersButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(ResetParametersButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsResetParametersAssetButton", "Reset the selected Houdini Asset's parameters to their default values.")) - //.Text(FText::FromString("Reset Parameters")) - .IsEnabled_Lambda(ShouldEnableResetParametersButtonLambda) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnResetParametersClickedLambda) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(ResetParametersButtonHorizontalBox, SHorizontalBox) - ] - ] - ] - ]; - - if (HoudiniEngineUIResetParametersIconBrush.IsValid()) - { - TSharedPtr ResetParametersImage; - ResetParametersButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(0.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(ResetParametersImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - ResetParametersImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIResetParametersIconBrush]() { - return HoudiniEngineUIResetParametersIconBrush.Get(); - }))); - } - - ResetParametersButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - //.FillWidth(4.2f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - //.MinDesiredWidth(160.f) - .Text(FText::FromString("Reset Parameters")) - ]; - - - // Temp Cook Folder Row - FDetailWidgetRow & TempCookFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef TempCookFolderRowHorizontalBox = SNew(SHorizontalBox); - - TempCookFolderRowHorizontalBox->AddSlot() - //.Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineTemporaryCookFolderLabel", "Temporary Cook Folder")) - .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, MAterials, Textures..) that are generated by Houdini Assets when they cook.")) - ] - ]; - - TempCookFolderRowHorizontalBox->AddSlot() - .MaxWidth(235.0f) - [ - SNew(SBox) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, Materials, Textures..) that are generated by Houdini Assets when they cook.")) - .HintText(LOCTEXT("HoudiniEngineTempCookFolderHintText", "Input to set temporary cook folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainHAC->TemporaryCookFolder.Path)) - .OnTextCommitted_Lambda(OnCookFolderTextCommittedLambda) - ] - ]; - - TempCookFolderRow.WholeRowWidget.Widget = TempCookFolderRowHorizontalBox; -} - -void -FHoudiniEngineDetails::OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC) -{ - if (!IsValid(InHAC)) - return; - - if (!bInState) - { - if (InHAC->GetOnPostCookBakeDelegate().IsBound()) - InHAC->GetOnPostCookBakeDelegate().Unbind(); - } - else - { - InHAC->GetOnPostCookBakeDelegate().BindLambda([](UHoudiniAssetComponent* HAC) - { - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - HAC, - HAC->bReplacePreviousBake, - HAC->HoudiniEngineBakeOption, - HAC->bRemoveOutputAfterBake); - }); - } -} - -void -FHoudiniEngineDetails::CreateBakeWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE); - - if (!MainHAC->bBakeMenuExpanded) - return; - - auto OnBakeButtonClickedLambda = [InHACs, MainHAC]() - { - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - NextHAC, - MainHAC->bReplacePreviousBake, - MainHAC->HoudiniEngineBakeOption, - MainHAC->bRemoveOutputAfterBake); - } - - return FReply::Handled(); - }; - - auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) - { - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FString NewPathStr = Val.ToString(); - - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - if (NextHAC->BakeFolder.Path.Equals(NewPathStr)) - continue; - - NextHAC->BakeFolder.Path = NewPathStr; - } - }; - - // Button Row - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - - // Bake Button - TSharedPtr BakeButton; - TSharedPtr BakeButtonHorizontalBox; - - ButtonRowHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(15.f, 0.0f, 0.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(BakeButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsBakeButton", "Bake the Houdini Asset Component(s).")) - //.Text(FText::FromString("Recook")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnBakeButtonClickedLambda) - .Content() - [ - SAssignNew(BakeButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); - if (BakeIconBrush.IsValid()) - { - TSharedPtr BakeImage; - BakeButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(BakeImage, SImage) - ] - ]; - - BakeImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([BakeIconBrush]() { - return BakeIconBrush.Get(); - }))); - } - - BakeButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Bake")) - ]; - - // Bake Type ComboBox - TSharedPtr>> TypeComboBox; - - TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEngineBakeTypeOptionsLabels(); - TSharedPtr IntialSelec; - if (OptionSource) - { - IntialSelec = (*OptionSource)[(int)MainHAC->HoudiniEngineBakeOption]; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - //.MaxWidth(103.f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - //.WidthOverride(103.f) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [MainHAC, InHACs](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EHoudiniEngineBakeOption NewOption = - FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - MainHAC->HoudiniEngineBakeOption = NewOption; - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - [ - SNew(STextBlock) - .Text_Lambda([MainHAC]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(MainHAC->HoudiniEngineBakeOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; - - // Clear Output After Baking Row - FDetailWidgetRow & ClearOutputAfterBakingRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); - - // Remove Output Checkbox - TSharedPtr CheckBoxRemoveOutput; - TSharedPtr CheckBoxAutoBake; - TSharedPtr CheckBoxRecenterBakedActors; - TSharedPtr CheckBoxReplacePreviousBake; - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRemoveOutput, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBox", "Remove HDA Output After Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBoxToolTip", "After baking the existing output of this Houdini Asset Actor will be removed.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bRemoveOutputAfterBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bRemoveOutputAfterBake = bNewState; - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bRecenterBakedActors = bNewState; - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - // TODO: find a better way to manage the initial binding/unbinding of the post cook bake delegate - // We do this here to ensure the delegate is bound/unbound correctly when the UI is initially drawn - // Currently we have the problem that the HoudiniEngineRuntime and HoudiniEngine modules cannot access - // the FHoudiniEngineBakeUtils code that is in HoudiniEngineEditor (this is the primary reason for the delegate and - // managing the delegate in this way). - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - const bool bState = NextHAC->IsBakeAfterNextCookEnabled(); - NextHAC->SetBakeAfterNextCookEnabled(bState); - OnBakeAfterCookChangedHelper(bState, NextHAC); - } - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxAutoBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake the next cook.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->IsBakeAfterNextCookEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->SetBakeAfterNextCookEnabled(bNewState); - OnBakeAfterCookChangedHelper(bNewState, NextHAC); - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - // Replace Checkbox - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxReplacePreviousBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBox", "Replace Previous Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBoxToolTip", "When baking replace the previous bake's output instead of creating additional output actors/components/objects.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bReplacePreviousBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - MainHAC->bReplacePreviousBake = bNewState; - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - ClearOutputAfterBakingRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; - - // Bake Folder Row - FDetailWidgetRow & BakeFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT( - "HoudiniEngineBakeFolderTooltip", - "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " - "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " - "plugin settings is used.")) - ] - ]; - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .MaxWidth(235.0) - [ - SNew(SBox) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT( - "HoudiniEngineBakeFolderTooltip", - "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " - "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " - "plugin settings is used.")) - .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text_Lambda([MainHAC]() - { - if (!IsValid(MainHAC)) - return FText(); - return FText::FromString(MainHAC->BakeFolder.Path); - }) - .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) - ] - ]; - - BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; - - switch (MainHAC->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToActorToolTip", - "Bake this Houdini Asset Actor and its components to native unreal actors and components, replacing the previous baked result.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToActorToolTip", - "Bake this Houdini Asset Actor and its components to native unreal actors and components.")); - } - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToBlueprintToolTip", - "Bake this Houdini Asset Actor to a blueprint.")); - } - break; - - case EHoudiniEngineBakeOption::ToFoliage: - { - if (!FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(MainHAC)) - { - // If the HAC does not have instanced output, disable Bake to Foliage - BakeButton->SetEnabled(false); - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonNoInstancedOutputToolTip", - "The Houdini Asset must be outputing at least one instancer in order to be able to bake to Foliage.")); - } - else - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToFoliageToolTip", - "Add this Houdini Asset Actor's instancers to the current level's Foliage, replacing the previously baked foliage instancers from this actor.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToFoliageToolTip", - "Add this Houdini Asset Actor's instancers to the current level's Foliage.")); - } - } - } - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToWorldOutlinerToolTip", - "Not implemented.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToWorldOutlinerToolTip", - "Not implemented.")); - } - } - break; - } - - // Todo: remove me! - if (MainHAC->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToWorldOutliner) - BakeButton->SetEnabled(false); - -} - -void -FHoudiniEngineDetails::CreateAssetOptionsWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Header Row - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS); - - if (!MainHAC->bAssetOptionMenuExpanded) - return; - - auto IsCheckedParameterChangedLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateParameterChangedLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnParameterChange = bChecked; - } - }; - - auto IsCheckedTransformChangeLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedTransformChangeLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnTransformChange = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedAssetInputCookLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedAssetInputCookLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnAssetInputCook = bChecked; - } - }; - - auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedPushTransformToHoudiniLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bUploadTransformsToHoudiniEngine = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedDoNotGenerateOutputsLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bOutputless = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedOutputTemplatedGeosLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bOutputTemplateGeos = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - // Checkboxes row - FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef CheckBoxesHorizontalBox = SNew(SHorizontalBox); - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - CheckBoxesHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - CheckBoxesHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineCookTriggersLabel", "Cook Triggers")) - ] - ]; - - // Parameter change check box - FText TooltipText = LOCTEXT("HoudiniEngineParameterChangeTooltip", "If enabled, modifying a parameter or input on this Houdini Asset will automatically trigger a cook of the HDA in Houdini."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineParameterChangeCheckBoxLabel", "On Parameter/Input Change")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateParameterChangedLambda) - .IsChecked_Lambda(IsCheckedParameterChangedLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Transform change check box - TooltipText = LOCTEXT("HoudiniEngineTransformChangeTooltip", "If enabled, changing the Houdini Asset Actor's transform in Unreal will also update its HDA's node transform in Houdini, and trigger a recook of the HDA with the updated transform."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineTransformChangeCheckBoxLabel", "On Transform Change")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedTransformChangeLambda) - .IsChecked_Lambda(IsCheckedTransformChangeLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Triggers Downstream cook checkbox - TooltipText = LOCTEXT("HoudiniEngineAssetInputCookTooltip", "When enabled, this asset will automatically re-cook after one its asset input has finished cooking."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineAssetInputCheckBoxLabel", "On Asset Input Cook")) - .ToolTipText(TooltipText) - ] - - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedAssetInputCookLambda) - .IsChecked_Lambda(IsCheckedAssetInputCookLambda) - .ToolTipText(TooltipText) - ] - ]; - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous")) - ]; - - // Push Transform to Houdini check box - TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini")) - .ToolTipText(TooltipText) - ] - - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda) - .IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Do not generate output check box - TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda) - .IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Output templated geos check box - TooltipText = LOCTEXT("HoudiniEnginOutputTemplatesTooltip", "If enabled, Geometry nodes in the asset that have the template flag will be outputed."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Output Templated Geos")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedOutputTemplatedGeosLambda) - .IsChecked_Lambda(IsCheckedOutputTemplatedGeosLambda) - .ToolTipText(TooltipText) - ] - ]; - - CheckBoxesRow.WholeRowWidget.Widget = CheckBoxesHorizontalBox; -} - -void -FHoudiniEngineDetails::CreateHelpAndDebugWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Header Row - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG); - - if (!MainHAC->bHelpAndDebugMenuExpanded) - return; - - auto OnFetchCookLogButtonClickedLambda = [InHACs]() - { - return ShowCookLog(InHACs); - }; - - auto OnHelpButtonClickedLambda = [MainHAC]() - { - return ShowAssetHelp(MainHAC); - }; - - // Button Row - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - TSharedPtr CookLogButtonHorizontalBox = SNew(SHorizontalBox); - - // Fetch Cook Log button - ButtonRowHorizontalBox->AddSlot() - //.Padding(15.0f, 0.0f, 0.0f, 0.0f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(FText::FromString("Fetch and display all cook logs available for this Houdini Asset Actor.")) - //.Text(FText::FromString("Fetch Cook Log")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnFetchCookLogButtonClickedLambda) - .Content() - [ - SAssignNew(CookLogButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr CookLogIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUICookLogIconBrush(); - if (CookLogIconBrush.IsValid()) - { - TSharedPtr CookImage; - CookLogButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookImage, SImage) - ] - ]; - - CookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookLogIconBrush]() { - return CookLogIconBrush.Get(); - }))); - } - - CookLogButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Show Cook Logs")) - ]; - - // Asset Help Button - TSharedPtr AssetHelpButtonHorizontalBox; - ButtonRowHorizontalBox->AddSlot() - //.Padding(4.0, 0.0f, 0.0f, 0.0f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(FText::FromString("Display this Houdini Asset Actor's HDA help.")) - //.Text(FText::FromString("Asset Help")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnHelpButtonClickedLambda) - .Content() - [ - SAssignNew(AssetHelpButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr AssetHelpIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIAssetHelpIconBrush(); - if (AssetHelpIconBrush.IsValid()) - { - TSharedPtr AssetHelpImage; - AssetHelpButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(AssetHelpImage, SImage) - ] - ]; - - AssetHelpImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([AssetHelpIconBrush]() { - return AssetHelpIconBrush.Get(); - }))); - } - - AssetHelpButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Asset Help")) - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; -} - -FMenuBuilder -FHoudiniEngineDetails::Helper_CreateHoudiniAssetPicker() -{ - auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - return true; - }; - - auto OnActorSelected = [](AActor* Actor) - { - UE_LOG(LogTemp, Warning, TEXT("Actor Selected")); - - return; - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterHoudiniAssetLambda); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("HoudiniEngineDetailsAssetPicker", "Asset")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -const FSlateBrush * -FHoudiniEngineDetails::GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const -{ - if (HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - -/* -TSharedRef< SWidget > -FHoudiniEngineDetails::OnGetHoudiniAssetMenuContent(TArray InHACs) -{ - TArray< const UClass * > AllowedClasses; - AllowedClasses.Add(UHoudiniAsset::StaticClass()); - - TArray< UFactory * > NewAssetFactories; - - UHoudiniAsset * HoudiniAsset = nullptr; - if (InHACs.Num() > 0) - { - UHoudiniAssetComponent * HoudiniAssetComponent = InHACs[0]; - HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; - } - - auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - return true; - }; - - // Delegate for filtering Houdini assets. - FOnShouldFilterAsset OnShouldFilterHoudiniAsset = FOnShouldFilterAsset::CreateLambda(OnShouldFilterHoudiniAssetLambda); - - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(HoudiniAsset), true, - AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset, - FOnAssetSelected::CreateLambda([](const FAssetData & AssetData) {}), - FSimpleDelegate::CreateLambda([]() { }) - ); -} -*/ - -FReply -FHoudiniEngineDetails::ShowCookLog(TArray InHACS) -{ - TSharedPtr< SWindow > ParentWindow; - FString CookLog = FHoudiniEngineUtils::GetCookLog(InHACS); - - // 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("MainFrame"); - ParentWindow = MainFrame.GetParentWindow(); - } - - if (ParentWindow.IsValid()) - { - TSharedPtr HoudiniAssetCookLog; - - TSharedRef Window = - SNew(SWindow) - .Title(LOCTEXT("WindowTitle", "Houdini Cook Log")) - .ClientSize(FVector2D(640, 480)); - - Window->SetContent( - SAssignNew(HoudiniAssetCookLog, SHoudiniAssetLogWidget) - .LogText(CookLog)); - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - } - - return FReply::Handled(); -} - -FReply -FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC) -{ - if (!InHAC) - return FReply::Handled(); - - FString AssetHelp = FHoudiniEngineUtils::GetAssetHelp(InHAC); - - 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 HoudiniAssetHelpLog; - - TSharedRef Window = - SNew(SWindow) - .Title(LOCTEXT("WindowTitle", "Houdini Asset Help")) - .ClientSize(FVector2D(640, 480)); - - Window->SetContent( - SAssignNew(HoudiniAssetHelpLog, SHoudiniAssetLogWidget) - .LogText(AssetHelp)); - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - } - return FReply::Handled(); -} - -void -FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return; - - FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - HoudiniAssetComponent->bGenerateMenuExpanded = !HoudiniAssetComponent->bGenerateMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - HoudiniAssetComponent->bBakeMenuExpanded = !HoudiniAssetComponent->bBakeMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - HoudiniAssetComponent->bAssetOptionMenuExpanded = !HoudiniAssetComponent->bAssetOptionMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - HoudiniAssetComponent->bHelpAndDebugMenuExpanded = !HoudiniAssetComponent->bHelpAndDebugMenuExpanded; - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniAssetComponent, true); - - return FReply::Handled(); - }); - - TFunction GetText = [MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT); - break; - } - return FText::FromString(""); - }; - - TFunction GetExpanderBrush = [HoudiniAssetComponent, MenuSection](SButton* InExpanderArrow) - { - FName ResourceName; - bool bMenuExpanded = false; - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - bMenuExpanded = HoudiniAssetComponent->bGenerateMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - bMenuExpanded = HoudiniAssetComponent->bBakeMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - bMenuExpanded = HoudiniAssetComponent->bAssetOptionMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - bMenuExpanded = HoudiniAssetComponent->bHelpAndDebugMenuExpanded; - } - - if (bMenuExpanded) - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - }; - - return AddHeaderRow(HoudiniEngineCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); -} - -void -FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]() - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - InPDGAssetLink->Modify(); - InPDGAssetLink->bBakeMenuExpanded = !InPDGAssetLink->bBakeMenuExpanded; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded), InPDGAssetLink); - break; - } - - //FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, true); - - return FReply::Handled(); - }); - - TFunction GetText = [MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); - break; - } - return FText::FromString(""); - }; - - TFunction GetExpanderBrush = [InPDGAssetLink, MenuSection](SButton* InExpanderArrow) - { - FName ResourceName; - bool bMenuExpanded = false; - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - bMenuExpanded = InPDGAssetLink->bBakeMenuExpanded; - break; - } - - if (bMenuExpanded) - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - }; - - return AddHeaderRow(PDGCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); -} - -void -FHoudiniEngineDetails::AddHeaderRow( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - FOnClicked& InOnExpanderClick, - TFunction& InGetText, - TFunction& InGetExpanderBrush) -{ - // Header Row - FDetailWidgetRow & HeaderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedPtr HeaderHorizontalBox; - HeaderRow.WholeRowWidget.Widget = SAssignNew(HeaderHorizontalBox, SHorizontalBox); - - TSharedPtr ExpanderImage; - TSharedPtr ExpanderArrow; - HeaderHorizontalBox->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth() - [ - SAssignNew(ExpanderArrow, SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - .OnClicked(InOnExpanderClick) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - HeaderHorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SNew(STextBlock) - .Text_Lambda([InGetText](){return InGetText(); }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - ExpanderImage->SetImage( - TAttribute::Create( - [ExpanderArrow, InGetExpanderBrush]() - { - return InGetExpanderBrush(ExpanderArrow.Get()); - })); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" + +#include "CoreMinimal.h" +#include "DetailCategoryBuilder.h" +#include "IDetailGroup.h" +#include "DetailWidgetRow.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Brushes/SlateImageBrush.h" +#include "Widgets/Input/SComboBox.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "ActorPickerMode.h" +#include "SceneOutlinerModule.h" +#include "Modules/ModuleManager.h" +#include "Interfaces/IMainFrameModule.h" +#include "AssetThumbnail.h" +#include "DetailLayoutBuilder.h" +#include "SAssetDropTarget.h" +#include "PropertyCustomizationHelpers.h" +#include "ScopedTransaction.h" +#include "SEnumCombobox.h" +#include "HAL/FileManager.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define HOUDINI_ENGINE_UI_SECTION_GENERATE 1 +#define HOUDINI_ENGINE_UI_SECTION_BAKE 2 +#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS 3 +#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG 4 + +#define HOUDINI_ENGINE_UI_BUTTON_WIDTH 135.0f + +#define HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT "Generate" +#define HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT "Bake" +#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT "Asset Options" +#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT "Help and Debug" + + +void +SHoudiniAssetLogWidget::Construct(const FArguments & InArgs) +{ + this->ChildSlot + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background"))) + .Content() + [ + SNew(SScrollBox) + + SScrollBox::Slot() + [ + SNew(SMultiLineEditableTextBox) + .Text(FText::FromString(InArgs._LogText)) + .AutoWrapText(true) + .IsReadOnly(true) + ] + ] + ]; +} + + +void +FHoudiniEngineDetails::CreateWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // 0. Houdini Engine Icon + FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs); + + // 1. Houdini Engine Session Status + FHoudiniAssetComponentDetails::AddSessionStatusRow(HoudiniEngineCategoryBuilder); + + // 2. Create Generate Category + FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 3. Create Bake Category + FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 4. Create Asset Options Category + FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 5. Create Help and Debug Category + FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs); + +} + +void +FHoudiniEngineDetails::CreateHoudiniEngineIconWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Skip drawing the icon if the icon image is not loaded correctly. + TSharedPtr HoudiniEngineUIIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIIconBrush(); + if (!HoudiniEngineUIIconBrush.IsValid()) + return; + + FDetailWidgetRow & Row = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef Box = SNew(SHorizontalBox); + TSharedPtr Image; + + Box->AddSlot() + .AutoWidth() + .Padding(0.0f, 5.0f, 5.0f, 10.0f) + .HAlign(HAlign_Left) + [ + SNew(SBox) + .HeightOverride(30) + .WidthOverride(208) + [ + SAssignNew(Image, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + Image->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIIconBrush]() { + return HoudiniEngineUIIconBrush.Get(); + }))); + + Row.WholeRowWidget.Widget = Box; + Row.IsEnabled(false); +} + +void +FHoudiniEngineDetails::CreateGenerateWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + auto OnReBuildClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->MarkAsNeedRebuild(); + } + + return FReply::Handled(); + }; + + auto OnRecookClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->MarkAsNeedCook(); + } + + return FReply::Handled(); + }; + + auto ShouldEnableResetParametersButtonLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + // Reset parameters to default values? + for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) + { + UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); + + if (NextParm && !NextParm->IsDefault()) + return true; + } + } + + return false; + }; + + auto OnResetParametersClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + // Reset parameters to default values? + for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) + { + UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); + + if (NextParm && !NextParm->IsDefault()) + { + NextParm->RevertToDefault(); + } + } + } + + return FReply::Handled(); + }; + + auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) + { + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FString NewPathStr = Val.ToString(); + + if (NewPathStr.IsEmpty()) + return; + + if (NewPathStr.StartsWith("Game/")) + { + NewPathStr = "/" + NewPathStr; + } + + FString AbsolutePath; + if (NewPathStr.StartsWith("/Game/")) + { + FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); + } + + if (!FPaths::DirectoryExists(AbsolutePath)) + { + HOUDINI_LOG_WARNING(TEXT("Invalid path")); + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + return; + } + + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr)) + continue; + + NextHAC->TemporaryCookFolder.Path = NewPathStr; + } + }; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_GENERATE); + + // Button Row (draw only if expanded) + if (!MainHAC->bGenerateMenuExpanded) + return; + + TSharedPtr HoudiniEngineUIRebuildIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRebuildIconBrush(); + TSharedPtr HoudiniEngineUIRecookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + TSharedPtr HoudiniEngineUIResetParametersIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIResetParametersIconBrush(); + + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef ButtonHorizontalBox = SNew(SHorizontalBox); + + // Recook button + TSharedPtr RecookButton; + TSharedPtr RecookButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(RecookButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) + //.Text(FText::FromString("Recook")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnRecookClickedLambda) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) + ] + ] + ] + ]; + + if (HoudiniEngineUIRecookIconBrush.IsValid()) + { + TSharedPtr RecookImage; + RecookButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RecookImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + RecookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { + return HoudiniEngineUIRecookIconBrush.Get(); + }))); + } + + RecookButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Recook")) + ]; + + // Rebuild button + TSharedPtr RebuildButton; + TSharedPtr RebuildButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(15.0f, 0.0f, 0.0f, 2.0f) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(RebuildButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) + //.Text(FText::FromString("Rebuild")) + .Visibility(EVisibility::Visible) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) + ] + ] + .OnClicked_Lambda(OnReBuildClickedLambda) + ] + ]; + + if (HoudiniEngineUIRebuildIconBrush.IsValid()) + { + TSharedPtr RebuildImage; + RebuildButtonHorizontalBox->AddSlot() + //.Padding(25.0f, 0.0f, 3.0f, 0.0f) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RebuildImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + RebuildImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { + return HoudiniEngineUIRebuildIconBrush.Get(); + }))); + } + + RebuildButtonHorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) + [ + SNew(STextBlock) + .Text(FText::FromString("Rebuild")) + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; + ButtonRow.IsEnabled(false); + + // Reset Parameters button + TSharedPtr ResetParametersButton; + TSharedPtr ResetParametersButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(ResetParametersButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsResetParametersAssetButton", "Reset the selected Houdini Asset's parameters to their default values.")) + //.Text(FText::FromString("Reset Parameters")) + .IsEnabled_Lambda(ShouldEnableResetParametersButtonLambda) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnResetParametersClickedLambda) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(ResetParametersButtonHorizontalBox, SHorizontalBox) + ] + ] + ] + ]; + + if (HoudiniEngineUIResetParametersIconBrush.IsValid()) + { + TSharedPtr ResetParametersImage; + ResetParametersButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(0.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(ResetParametersImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + ResetParametersImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIResetParametersIconBrush]() { + return HoudiniEngineUIResetParametersIconBrush.Get(); + }))); + } + + ResetParametersButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + //.FillWidth(4.2f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + //.MinDesiredWidth(160.f) + .Text(FText::FromString("Reset Parameters")) + ]; + + + // Temp Cook Folder Row + FDetailWidgetRow & TempCookFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef TempCookFolderRowHorizontalBox = SNew(SHorizontalBox); + + TempCookFolderRowHorizontalBox->AddSlot() + //.Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineTemporaryCookFolderLabel", "Temporary Cook Folder")) + .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, MAterials, Textures..) that are generated by Houdini Assets when they cook.")) + ] + ]; + + TempCookFolderRowHorizontalBox->AddSlot() + .MaxWidth(235.0f) + [ + SNew(SBox) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, Materials, Textures..) that are generated by Houdini Assets when they cook.")) + .HintText(LOCTEXT("HoudiniEngineTempCookFolderHintText", "Input to set temporary cook folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainHAC->TemporaryCookFolder.Path)) + .OnTextCommitted_Lambda(OnCookFolderTextCommittedLambda) + ] + ]; + + TempCookFolderRow.WholeRowWidget.Widget = TempCookFolderRowHorizontalBox; +} + +void +FHoudiniEngineDetails::OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC) +{ + if (!IsValid(InHAC)) + return; + + if (!bInState) + { + if (InHAC->GetOnPostCookBakeDelegate().IsBound()) + InHAC->GetOnPostCookBakeDelegate().Unbind(); + } + else + { + InHAC->GetOnPostCookBakeDelegate().BindLambda([](UHoudiniAssetComponent* HAC) + { + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + HAC, + HAC->bReplacePreviousBake, + HAC->HoudiniEngineBakeOption, + HAC->bRemoveOutputAfterBake, + HAC->bRecenterBakedActors); + }); + } +} + +void +FHoudiniEngineDetails::CreateBakeWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE); + + if (!MainHAC->bBakeMenuExpanded) + return; + + auto OnBakeButtonClickedLambda = [InHACs, MainHAC]() + { + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + NextHAC, + MainHAC->bReplacePreviousBake, + MainHAC->HoudiniEngineBakeOption, + MainHAC->bRemoveOutputAfterBake, + MainHAC->bRecenterBakedActors); + } + + return FReply::Handled(); + }; + + auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) + { + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FString NewPathStr = Val.ToString(); + + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + if (NextHAC->BakeFolder.Path.Equals(NewPathStr)) + continue; + + NextHAC->BakeFolder.Path = NewPathStr; + } + }; + + // Button Row + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + + // Bake Button + TSharedPtr BakeButton; + TSharedPtr BakeButtonHorizontalBox; + + ButtonRowHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(15.f, 0.0f, 0.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(BakeButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsBakeButton", "Bake the Houdini Asset Component(s).")) + //.Text(FText::FromString("Recook")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnBakeButtonClickedLambda) + .Content() + [ + SAssignNew(BakeButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); + if (BakeIconBrush.IsValid()) + { + TSharedPtr BakeImage; + BakeButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(BakeImage, SImage) + ] + ]; + + BakeImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([BakeIconBrush]() { + return BakeIconBrush.Get(); + }))); + } + + BakeButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Bake")) + ]; + + // Bake Type ComboBox + TSharedPtr>> TypeComboBox; + + TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEngineBakeTypeOptionsLabels(); + TSharedPtr IntialSelec; + if (OptionSource) + { + IntialSelec = (*OptionSource)[(int)MainHAC->HoudiniEngineBakeOption]; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + //.MaxWidth(103.f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + //.WidthOverride(103.f) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [MainHAC, InHACs](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EHoudiniEngineBakeOption NewOption = + FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + MainHAC->HoudiniEngineBakeOption = NewOption; + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + [ + SNew(STextBlock) + .Text_Lambda([MainHAC]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(MainHAC->HoudiniEngineBakeOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; + + // Clear Output After Baking Row + FDetailWidgetRow & ClearOutputAfterBakingRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); + + // Remove Output Checkbox + TSharedPtr CheckBoxRemoveOutput; + TSharedPtr CheckBoxAutoBake; + TSharedPtr CheckBoxRecenterBakedActors; + TSharedPtr CheckBoxReplacePreviousBake; + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRemoveOutput, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBox", "Remove HDA Output After Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBoxToolTip", "After baking the existing output of this Houdini Asset Actor will be removed.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bRemoveOutputAfterBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bRemoveOutputAfterBake = bNewState; + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bRecenterBakedActors = bNewState; + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + // TODO: find a better way to manage the initial binding/unbinding of the post cook bake delegate + // We do this here to ensure the delegate is bound/unbound correctly when the UI is initially drawn + // Currently we have the problem that the HoudiniEngineRuntime and HoudiniEngine modules cannot access + // the FHoudiniEngineBakeUtils code that is in HoudiniEngineEditor (this is the primary reason for the delegate and + // managing the delegate in this way). + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + const bool bState = NextHAC->IsBakeAfterNextCookEnabled(); + NextHAC->SetBakeAfterNextCookEnabled(bState); + OnBakeAfterCookChangedHelper(bState, NextHAC); + } + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxAutoBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake the next cook.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->IsBakeAfterNextCookEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->SetBakeAfterNextCookEnabled(bNewState); + OnBakeAfterCookChangedHelper(bNewState, NextHAC); + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + // Replace Checkbox + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxReplacePreviousBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBox", "Replace Previous Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBoxToolTip", "When baking replace the previous bake's output instead of creating additional output actors/components/objects.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bReplacePreviousBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + MainHAC->bReplacePreviousBake = bNewState; + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + ClearOutputAfterBakingRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; + + // Bake Folder Row + FDetailWidgetRow & BakeFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) + .ToolTipText(LOCTEXT( + "HoudiniEngineBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) + ] + ]; + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .MaxWidth(235.0) + [ + SNew(SBox) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT( + "HoudiniEngineBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) + .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text_Lambda([MainHAC]() + { + if (!IsValid(MainHAC)) + return FText(); + return FText::FromString(MainHAC->BakeFolder.Path); + }) + .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) + ] + ]; + + BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; + + switch (MainHAC->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToActorToolTip", + "Bake this Houdini Asset Actor and its components to native unreal actors and components, replacing the previous baked result.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToActorToolTip", + "Bake this Houdini Asset Actor and its components to native unreal actors and components.")); + } + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToBlueprintToolTip", + "Bake this Houdini Asset Actor to a blueprint.")); + } + break; + + case EHoudiniEngineBakeOption::ToFoliage: + { + if (!FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(MainHAC)) + { + // If the HAC does not have instanced output, disable Bake to Foliage + BakeButton->SetEnabled(false); + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonNoInstancedOutputToolTip", + "The Houdini Asset must be outputing at least one instancer in order to be able to bake to Foliage.")); + } + else + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToFoliageToolTip", + "Add this Houdini Asset Actor's instancers to the current level's Foliage, replacing the previously baked foliage instancers from this actor.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToFoliageToolTip", + "Add this Houdini Asset Actor's instancers to the current level's Foliage.")); + } + } + } + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToWorldOutlinerToolTip", + "Not implemented.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToWorldOutlinerToolTip", + "Not implemented.")); + } + } + break; + } + + // Todo: remove me! + if (MainHAC->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToWorldOutliner) + BakeButton->SetEnabled(false); + +} + +void +FHoudiniEngineDetails::CreateAssetOptionsWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Header Row + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS); + + if (!MainHAC->bAssetOptionMenuExpanded) + return; + + auto IsCheckedParameterChangedLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateParameterChangedLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnParameterChange = bChecked; + } + }; + + auto IsCheckedTransformChangeLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedTransformChangeLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnTransformChange = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedAssetInputCookLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedAssetInputCookLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnAssetInputCook = bChecked; + } + }; + + auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedPushTransformToHoudiniLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bUploadTransformsToHoudiniEngine = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedDoNotGenerateOutputsLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bOutputless = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedOutputTemplatedGeosLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bOutputTemplateGeos = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + // Checkboxes row + FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef CheckBoxesHorizontalBox = SNew(SHorizontalBox); + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + CheckBoxesHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + CheckBoxesHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineCookTriggersLabel", "Cook Triggers")) + ] + ]; + + // Parameter change check box + FText TooltipText = LOCTEXT("HoudiniEngineParameterChangeTooltip", "If enabled, modifying a parameter or input on this Houdini Asset will automatically trigger a cook of the HDA in Houdini."); + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineParameterChangeCheckBoxLabel", "On Parameter/Input Change")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateParameterChangedLambda) + .IsChecked_Lambda(IsCheckedParameterChangedLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Transform change check box + TooltipText = LOCTEXT("HoudiniEngineTransformChangeTooltip", "If enabled, changing the Houdini Asset Actor's transform in Unreal will also update its HDA's node transform in Houdini, and trigger a recook of the HDA with the updated transform."); + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineTransformChangeCheckBoxLabel", "On Transform Change")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedTransformChangeLambda) + .IsChecked_Lambda(IsCheckedTransformChangeLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Triggers Downstream cook checkbox + TooltipText = LOCTEXT("HoudiniEngineAssetInputCookTooltip", "When enabled, this asset will automatically re-cook after one its asset input has finished cooking."); + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineAssetInputCheckBoxLabel", "On Asset Input Cook")) + .ToolTipText(TooltipText) + ] + + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedAssetInputCookLambda) + .IsChecked_Lambda(IsCheckedAssetInputCookLambda) + .ToolTipText(TooltipText) + ] + ]; + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous")) + ]; + + // Push Transform to Houdini check box + TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini."); + RightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini")) + .ToolTipText(TooltipText) + ] + + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda) + .IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Do not generate output check box + TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs."); + RightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda) + .IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Output templated geos check box + TooltipText = LOCTEXT("HoudiniEnginOutputTemplatesTooltip", "If enabled, Geometry nodes in the asset that have the template flag will be outputed."); + RightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Output Templated Geos")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedOutputTemplatedGeosLambda) + .IsChecked_Lambda(IsCheckedOutputTemplatedGeosLambda) + .ToolTipText(TooltipText) + ] + ]; + + CheckBoxesRow.WholeRowWidget.Widget = CheckBoxesHorizontalBox; +} + +void +FHoudiniEngineDetails::CreateHelpAndDebugWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Header Row + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG); + + if (!MainHAC->bHelpAndDebugMenuExpanded) + return; + + auto OnFetchCookLogButtonClickedLambda = [InHACs]() + { + return ShowCookLog(InHACs); + }; + + auto OnHelpButtonClickedLambda = [MainHAC]() + { + return ShowAssetHelp(MainHAC); + }; + + // Button Row + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + TSharedPtr CookLogButtonHorizontalBox = SNew(SHorizontalBox); + + // Fetch Cook Log button + ButtonRowHorizontalBox->AddSlot() + //.Padding(15.0f, 0.0f, 0.0f, 0.0f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(FText::FromString("Fetch and display all cook logs available for this Houdini Asset Actor.")) + //.Text(FText::FromString("Fetch Cook Log")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnFetchCookLogButtonClickedLambda) + .Content() + [ + SAssignNew(CookLogButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr CookLogIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUICookLogIconBrush(); + if (CookLogIconBrush.IsValid()) + { + TSharedPtr CookImage; + CookLogButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookImage, SImage) + ] + ]; + + CookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookLogIconBrush]() { + return CookLogIconBrush.Get(); + }))); + } + + CookLogButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Show Cook Logs")) + ]; + + // Asset Help Button + TSharedPtr AssetHelpButtonHorizontalBox; + ButtonRowHorizontalBox->AddSlot() + //.Padding(4.0, 0.0f, 0.0f, 0.0f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(FText::FromString("Display this Houdini Asset Actor's HDA help.")) + //.Text(FText::FromString("Asset Help")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnHelpButtonClickedLambda) + .Content() + [ + SAssignNew(AssetHelpButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr AssetHelpIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIAssetHelpIconBrush(); + if (AssetHelpIconBrush.IsValid()) + { + TSharedPtr AssetHelpImage; + AssetHelpButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(AssetHelpImage, SImage) + ] + ]; + + AssetHelpImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([AssetHelpIconBrush]() { + return AssetHelpIconBrush.Get(); + }))); + } + + AssetHelpButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Asset Help")) + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; +} + +FMenuBuilder +FHoudiniEngineDetails::Helper_CreateHoudiniAssetPicker() +{ + auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + return true; + }; + + auto OnActorSelected = [](AActor* Actor) + { + UE_LOG(LogTemp, Warning, TEXT("Actor Selected")); + + return; + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterHoudiniAssetLambda); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("HoudiniEngineDetailsAssetPicker", "Asset")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +const FSlateBrush * +FHoudiniEngineDetails::GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const +{ + if (HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + +/* +TSharedRef< SWidget > +FHoudiniEngineDetails::OnGetHoudiniAssetMenuContent(TArray InHACs) +{ + TArray< const UClass * > AllowedClasses; + AllowedClasses.Add(UHoudiniAsset::StaticClass()); + + TArray< UFactory * > NewAssetFactories; + + UHoudiniAsset * HoudiniAsset = nullptr; + if (InHACs.Num() > 0) + { + UHoudiniAssetComponent * HoudiniAssetComponent = InHACs[0]; + HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; + } + + auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + return true; + }; + + // Delegate for filtering Houdini assets. + FOnShouldFilterAsset OnShouldFilterHoudiniAsset = FOnShouldFilterAsset::CreateLambda(OnShouldFilterHoudiniAssetLambda); + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(HoudiniAsset), true, + AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset, + FOnAssetSelected::CreateLambda([](const FAssetData & AssetData) {}), + FSimpleDelegate::CreateLambda([]() { }) + ); +} +*/ + +FReply +FHoudiniEngineDetails::ShowCookLog(TArray InHACS) +{ + TSharedPtr< SWindow > ParentWindow; + FString CookLog = FHoudiniEngineUtils::GetCookLog(InHACS); + + // 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("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (ParentWindow.IsValid()) + { + TSharedPtr HoudiniAssetCookLog; + + TSharedRef Window = + SNew(SWindow) + .Title(LOCTEXT("WindowTitle", "Houdini Cook Log")) + .ClientSize(FVector2D(640, 480)); + + Window->SetContent( + SAssignNew(HoudiniAssetCookLog, SHoudiniAssetLogWidget) + .LogText(CookLog)); + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + } + + return FReply::Handled(); +} + +FReply +FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC) +{ + if (!InHAC) + return FReply::Handled(); + + FString AssetHelp = FHoudiniEngineUtils::GetAssetHelp(InHAC); + + 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 HoudiniAssetHelpLog; + + TSharedRef Window = + SNew(SWindow) + .Title(LOCTEXT("WindowTitle", "Houdini Asset Help")) + .ClientSize(FVector2D(640, 480)); + + Window->SetContent( + SAssignNew(HoudiniAssetHelpLog, SHoudiniAssetLogWidget) + .LogText(AssetHelp)); + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + } + return FReply::Handled(); +} + +void +FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return; + + FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + HoudiniAssetComponent->bGenerateMenuExpanded = !HoudiniAssetComponent->bGenerateMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + HoudiniAssetComponent->bBakeMenuExpanded = !HoudiniAssetComponent->bBakeMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + HoudiniAssetComponent->bAssetOptionMenuExpanded = !HoudiniAssetComponent->bAssetOptionMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + HoudiniAssetComponent->bHelpAndDebugMenuExpanded = !HoudiniAssetComponent->bHelpAndDebugMenuExpanded; + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniAssetComponent, true); + + return FReply::Handled(); + }); + + TFunction GetText = [MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT); + break; + } + return FText::FromString(""); + }; + + TFunction GetExpanderBrush = [HoudiniAssetComponent, MenuSection](SButton* InExpanderArrow) + { + FName ResourceName; + bool bMenuExpanded = false; + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + bMenuExpanded = HoudiniAssetComponent->bGenerateMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + bMenuExpanded = HoudiniAssetComponent->bBakeMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + bMenuExpanded = HoudiniAssetComponent->bAssetOptionMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + bMenuExpanded = HoudiniAssetComponent->bHelpAndDebugMenuExpanded; + } + + if (bMenuExpanded) + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + }; + + return AddHeaderRow(HoudiniEngineCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); +} + +void +FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]() + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + InPDGAssetLink->Modify(); + InPDGAssetLink->bBakeMenuExpanded = !InPDGAssetLink->bBakeMenuExpanded; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded), InPDGAssetLink); + break; + } + + //FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, true); + + return FReply::Handled(); + }); + + TFunction GetText = [MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); + break; + } + return FText::FromString(""); + }; + + TFunction GetExpanderBrush = [InPDGAssetLink, MenuSection](SButton* InExpanderArrow) + { + FName ResourceName; + bool bMenuExpanded = false; + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + bMenuExpanded = InPDGAssetLink->bBakeMenuExpanded; + break; + } + + if (bMenuExpanded) + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + }; + + return AddHeaderRow(PDGCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); +} + +void +FHoudiniEngineDetails::AddHeaderRow( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + FOnClicked& InOnExpanderClick, + TFunction& InGetText, + TFunction& InGetExpanderBrush) +{ + // Header Row + FDetailWidgetRow & HeaderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedPtr HeaderHorizontalBox; + HeaderRow.WholeRowWidget.Widget = SAssignNew(HeaderHorizontalBox, SHorizontalBox); + + TSharedPtr ExpanderImage; + TSharedPtr ExpanderArrow; + HeaderHorizontalBox->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth() + [ + SAssignNew(ExpanderArrow, SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + .OnClicked(InOnExpanderClick) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + HeaderHorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SNew(STextBlock) + .Text_Lambda([InGetText](){return InGetText(); }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + ExpanderImage->SetImage( + TAttribute::Create( + [ExpanderArrow, InGetExpanderBrush]() + { + return InGetExpanderBrush(ExpanderArrow.Get()); + })); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h index bd6b0b568..29da133e7 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h @@ -1,122 +1,122 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SCompoundWidget.h" -#include "Framework/SlateDelegates.h" -#include "Styling/SlateBrush.h" -#include "Widgets/Layout/SBorder.h" -#include "Framework/SlateDelegates.h" -#include "Widgets/Input/SButton.h" - -class IDetailCategoryBuilder; -class UHoudiniAssetComponent; -class UHoudiniPDGAssetLink; -class FMenuBuilder; -class SBorder; -class SButton; - -class SHoudiniAssetLogWidget : public SCompoundWidget -{ -public: - - SLATE_BEGIN_ARGS(SHoudiniAssetLogWidget) - : _LogText(TEXT("")) - {} - - SLATE_ARGUMENT(FString, LogText) - SLATE_END_ARGS() - - /** Widget construct. **/ - void Construct(const FArguments & InArgs); -}; - -class FHoudiniEngineDetails : public TSharedFromThis -{ -public: - static void CreateWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateHoudiniEngineIconWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateGenerateWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateBakeWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreatePDGBakeWidgets( - IDetailCategoryBuilder& InPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink); - - static void CreateAssetOptionsWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateHelpAndDebugWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static FReply ShowCookLog(TArray InHACS); - - static FReply ShowAssetHelp(UHoudiniAssetComponent * InHAC); - - static FMenuBuilder Helper_CreateHoudiniAssetPicker(); - - const FSlateBrush * GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const; - - /** Construct drop down menu content for Houdini asset. **/ - //static TSharedRef< SWidget > OnGetHoudiniAssetMenuContent(TArray InHACs); - - static void AddHeaderRowForHoudiniAssetComponent( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - UHoudiniAssetComponent* HoudiniAssetComponent, - int32 MenuSection); - - static void AddHeaderRowForHoudiniPDGAssetLink( - IDetailCategoryBuilder& PDGCategoryBuilder, - UHoudiniPDGAssetLink* InPDGAssetLink, - int32 MenuSection); - - static void AddHeaderRow( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - FOnClicked& InOnExpanderClick, - TFunction& InGetText, - TFunction& InGetExpanderBrush); - - // Helper for binding/unbinding the post cook bake delegate - static void OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC); -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Framework/SlateDelegates.h" +#include "Styling/SlateBrush.h" +#include "Widgets/Layout/SBorder.h" +#include "Framework/SlateDelegates.h" +#include "Widgets/Input/SButton.h" + +class IDetailCategoryBuilder; +class UHoudiniAssetComponent; +class UHoudiniPDGAssetLink; +class FMenuBuilder; +class SBorder; +class SButton; + +class SHoudiniAssetLogWidget : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SHoudiniAssetLogWidget) + : _LogText(TEXT("")) + {} + + SLATE_ARGUMENT(FString, LogText) + SLATE_END_ARGS() + + /** Widget construct. **/ + void Construct(const FArguments & InArgs); +}; + +class FHoudiniEngineDetails : public TSharedFromThis +{ +public: + static void CreateWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateHoudiniEngineIconWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateGenerateWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateBakeWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreatePDGBakeWidgets( + IDetailCategoryBuilder& InPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink); + + static void CreateAssetOptionsWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateHelpAndDebugWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static FReply ShowCookLog(TArray InHACS); + + static FReply ShowAssetHelp(UHoudiniAssetComponent * InHAC); + + static FMenuBuilder Helper_CreateHoudiniAssetPicker(); + + const FSlateBrush * GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const; + + /** Construct drop down menu content for Houdini asset. **/ + //static TSharedRef< SWidget > OnGetHoudiniAssetMenuContent(TArray InHACs); + + static void AddHeaderRowForHoudiniAssetComponent( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + UHoudiniAssetComponent* HoudiniAssetComponent, + int32 MenuSection); + + static void AddHeaderRowForHoudiniPDGAssetLink( + IDetailCategoryBuilder& PDGCategoryBuilder, + UHoudiniPDGAssetLink* InPDGAssetLink, + int32 MenuSection); + + static void AddHeaderRow( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + FOnClicked& InOnExpanderClick, + TFunction& InGetText, + TFunction& InGetExpanderBrush); + + // Helper for binding/unbinding the post cook bake delegate + static void OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC); +}; + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp index 49f96aa4c..ddcb6c849 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp @@ -1,1543 +1,1543 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineEditor.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetBroker.h" -#include "HoudiniAssetActorFactory.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetComponentDetails.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniRuntimeSettingsDetails.h" -#include "HoudiniSplineComponentVisualizer.h" -#include "HoudiniHandleComponentVisualizer.h" -#include "AssetTypeActions_HoudiniAsset.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPackageParams.h" - -#include "Modules/ModuleManager.h" -#include "Interfaces/IPluginManager.h" -#include "HAL/PlatformFilemanager.h" -#include "Misc/MessageDialog.h" -#include "Misc/Paths.h" -#include "AssetRegistryModule.h" -#include "PropertyEditorModule.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "LevelEditor.h" -#include "Templates/SharedPointer.h" -#include "Framework/Application/SlateApplication.h" -#include "HAL/ConsoleManager.h" -#include "Editor/UnrealEdEngine.h" -#include "Editor.h" -#include "UnrealEdGlobals.h" -#include "Engine/Selection.h" -#include "Widgets/Input/SCheckBox.h" -#include "Logging/LogMacros.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FHoudiniEngineEditor, HoudiniEngineEditor); -DEFINE_LOG_CATEGORY(LogHoudiniEngineEditor); - -FHoudiniEngineEditor * -FHoudiniEngineEditor::HoudiniEngineEditorInstance = nullptr; - -FHoudiniEngineEditor & -FHoudiniEngineEditor::Get() -{ - return *HoudiniEngineEditorInstance; -} - -bool -FHoudiniEngineEditor::IsInitialized() -{ - return FHoudiniEngineEditor::HoudiniEngineEditorInstance != nullptr; -} - -FHoudiniEngineEditor::FHoudiniEngineEditor() -{ -} - -void FHoudiniEngineEditor::StartupModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine Editor module.")); - - // Create style set. - FHoudiniEngineStyle::Initialize(); - - // Initilizes various resources used by our editor UI widgets - InitializeWidgetResource(); - - // Register asset type actions. - RegisterAssetTypeActions(); - - // Register asset brokers. - RegisterAssetBrokers(); - - // Register component visualizers. - RegisterComponentVisualizers(); - - // Register detail presenters. - RegisterDetails(); - - // Register actor factories. - RegisterActorFactories(); - - // Extends the file menu. - ExtendMenu(); - - // Extend the World Outliner Menu - AddLevelViewportMenuExtender(); - - // Adds the custom console commands - RegisterConsoleCommands(); - - // Register global undo / redo callbacks. - //RegisterForUndo(); - - //RegisterPlacementModeExtensions(); - - // Register for any FEditorDelegates that we are interested in, such as - // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds - RegisterEditorDelegates(); - - // Store the instance. - FHoudiniEngineEditor::HoudiniEngineEditorInstance = this; - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module startup complete.")); -} - -void FHoudiniEngineEditor::ShutdownModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine Editor module.")); - - // Deregister editor delegates - UnregisterEditorDelegates(); - - // Deregister console commands - UnregisterConsoleCommands(); - - // Remove the level viewport Menu extender - RemoveLevelViewportMenuExtender(); - - // Unregister asset type actions. - UnregisterAssetTypeActions(); - - // Unregister asset brokers. - //UnregisterAssetBrokers(); - - // Unregister detail presenters. - UnregisterDetails(); - - // Unregister our component visualizers. - //UnregisterComponentVisualizers(); - - // Unregister global undo / redo callbacks. - //UnregisterForUndo(); - - //UnregisterPlacementModeExtensions(); - - // Unregister the styleset - FHoudiniEngineStyle::Shutdown(); - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module shutdown complete.")); -} - -FString -FHoudiniEngineEditor::GetHoudiniEnginePluginDir() -{ - FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(EnginePluginDir)) - return EnginePluginDir; - - FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(ProjectPluginDir)) - return ProjectPluginDir; - - TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); - FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; - if (FPaths::DirectoryExists(PluginBaseDir)) - return PluginBaseDir; - - HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); - - return EnginePluginDir; -} - -void -FHoudiniEngineEditor::RegisterDetails() -{ - FPropertyEditorModule & PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Register details presenter for our component type and runtime settings. - PropertyModule.RegisterCustomClassLayout( - TEXT("HoudiniAssetComponent"), - FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniAssetComponentDetails::MakeInstance)); - - PropertyModule.RegisterCustomClassLayout( - TEXT("HoudiniRuntimeSettings"), - FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniRuntimeSettingsDetails::MakeInstance)); -} - -void -FHoudiniEngineEditor::UnregisterDetails() -{ - if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) - { - FPropertyEditorModule & PropertyModule = - FModuleManager::LoadModuleChecked("PropertyEditor"); - - PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniAssetComponent")); - PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniRuntimeSettings")); - } -} - -void -FHoudiniEngineEditor::RegisterComponentVisualizers() -{ - if (GUnrealEd) - { - // Register Houdini spline visualizer - SplineComponentVisualizer = MakeShareable(new FHoudiniSplineComponentVisualizer); - if (SplineComponentVisualizer.IsValid()) - { - GUnrealEd->RegisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName(), SplineComponentVisualizer); - SplineComponentVisualizer->OnRegister(); - } - - // Register Houdini handle visualizer - HandleComponentVisualizer = MakeShareable(new FHoudiniHandleComponentVisualizer); - if (HandleComponentVisualizer.IsValid()) - { - GUnrealEd->RegisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName(), HandleComponentVisualizer); - HandleComponentVisualizer->OnRegister(); - } - } -} - -void -FHoudiniEngineEditor::UnregisterComponentVisualizers() -{ - if (GUnrealEd) - { - // Unregister Houdini spline visualizer - if(SplineComponentVisualizer.IsValid()) - GUnrealEd->UnregisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); - - // Unregister Houdini handle visualizer - if (HandleComponentVisualizer.IsValid()) - GUnrealEd->UnregisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - } -} - -void -FHoudiniEngineEditor::RegisterAssetTypeAction(IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action) -{ - AssetTools.RegisterAssetTypeActions(Action); - AssetTypeActions.Add(Action); -} - -void -FHoudiniEngineEditor::RegisterAssetTypeActions() -{ - // Create and register asset type actions for Houdini asset. - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools").Get(); - RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_HoudiniAsset())); -} - -void -FHoudiniEngineEditor::UnregisterAssetTypeActions() -{ - // Unregister asset type actions we have previously registered. - if (FModuleManager::Get().IsModuleLoaded("AssetTools")) - { - IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >("AssetTools").Get(); - - for (int32 Index = 0; Index < AssetTypeActions.Num(); ++Index) - AssetTools.UnregisterAssetTypeActions(AssetTypeActions[Index].ToSharedRef()); - - AssetTypeActions.Empty(); - } -} - -void -FHoudiniEngineEditor::RegisterAssetBrokers() -{ - // Create and register broker for Houdini asset. - HoudiniAssetBroker = MakeShareable(new FHoudiniAssetBroker()); - FComponentAssetBrokerage::RegisterBroker( HoudiniAssetBroker, UHoudiniAssetComponent::StaticClass(), true, true ); -} - -void -FHoudiniEngineEditor::UnregisterAssetBrokers() -{ - if (UObjectInitialized()) - { - // Unregister broker. - FComponentAssetBrokerage::UnregisterBroker( HoudiniAssetBroker ); - } -} - -void -FHoudiniEngineEditor::RegisterActorFactories() -{ - if (GEditor) - { - UHoudiniAssetActorFactory * HoudiniAssetActorFactory = - NewObject< UHoudiniAssetActorFactory >(GetTransientPackage(), UHoudiniAssetActorFactory::StaticClass()); - - GEditor->ActorFactories.Add(HoudiniAssetActorFactory); - } -} - -void -FHoudiniEngineEditor::BindMenuCommands() -{ - HEngineCommands = MakeShareable(new FUICommandList); - - FHoudiniEngineCommands::Register(); - const FHoudiniEngineCommands& Commands = FHoudiniEngineCommands::Get(); - - // Session - - HEngineCommands->MapAction( - Commands._CreateSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CreateSession(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._ConnectSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ConnectSession(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._StopSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopSession(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RestartSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RestartSession(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OpenSessionSync, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OpenSessionSync(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); - - HEngineCommands->MapAction( - Commands._CloseSessionSync, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CloseSessionSync(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); - - HEngineCommands->MapAction( - Commands._ViewportSyncNone, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(0); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 0); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncHoudini, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(1); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 1); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncUnreal, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(2); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 2); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncBoth, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(3); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 3); }) - ); - - // PDG commandlet - HEngineCommands->MapAction( - Commands._IsPDGCommandletEnabled, - FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::SetPDGCommandletEnabled(!FHoudiniEngineCommands::IsPDGCommandletEnabled()); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletEnabled(); }) - ); - - HEngineCommands->MapAction( - Commands._StartPDGCommandlet, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StartPDGCommandlet(); }), - FCanExecuteAction::CreateLambda([]() - { - return FHoudiniEngineCommands::IsPDGCommandletEnabled() && !FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); - }) - ); - - HEngineCommands->MapAction( - Commands._StopPDGCommandlet, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopPDGCommandlet(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); })); - - // Plugin - - HEngineCommands->MapAction( - Commands._InstallInfo, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowInstallInfo(); }), - FCanExecuteAction::CreateLambda([]() { return false; })); - - HEngineCommands->MapAction( - Commands._PluginSettings, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowPluginSettings(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Files - - HEngineCommands->MapAction( - Commands._OpenInHoudini, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::OpenInHoudini(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._SaveHIPFile, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::SaveHIPFile(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._CleanUpTempFolder, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CleanUpTempFolder(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Help and support - - HEngineCommands->MapAction( - Commands._ReportBug, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::ReportBug(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OnlineDoc, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineDocumentation(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OnlineForum, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineForum(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Actions - - HEngineCommands->MapAction( - Commands._CookAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookAllAssets(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._CookSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RebuildAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildAllAssets(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RebuildSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._BakeAll, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::BakeAllAssets(); }), - FCanExecuteAction::CreateLambda([](){ return true; })); - - HEngineCommands->MapAction( - Commands._BakeSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::BakeSelection(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._RefineAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._RefineSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._PauseAssetCooking, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::PauseAssetCooking(); }), - FCanExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::IsSessionValid(); }), - FIsActionChecked::CreateLambda([](){ return FHoudiniEngineCommands::IsAssetCookingPaused(); })); - - // Non menu command (used for shortcuts only) - - // Append the command to the editor module - FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >("LevelEditor"); - LevelEditorModule.GetGlobalLevelEditorActions()->Append(HEngineCommands.ToSharedRef()); -} - -void -FHoudiniEngineEditor::ExtendMenu() -{ - if (IsRunningCommandlet()) - return; - - // We need to add/bind the UI Commands to their functions first - BindMenuCommands(); - - MainMenuExtender = MakeShareable(new FExtender); - - // Extend File menu, we will add Houdini section. - MainMenuExtender->AddMenuExtension( - "FileLoadAndSave", - EExtensionHook::After, - HEngineCommands, - FMenuExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniFileMenuExtension)); - - MainMenuExtender->AddMenuBarExtension( - "Edit", - EExtensionHook::After, - HEngineCommands, - FMenuBarExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniEditorMenu)); - - // Add our menu extender - FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); - LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); -} - -void -FHoudiniEngineEditor::AddHoudiniFileMenuExtension(FMenuBuilder & MenuBuilder) -{ - MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); - - // Icons used by the commands are defined in the HoudiniEngineStyle - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - - MenuBuilder.EndSection(); -} - -void -FHoudiniEngineEditor::AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder) -{ - // View - MenuBarBuilder.AddPullDownMenu( - LOCTEXT("HoudiniLabel", "Houdini Engine"), - LOCTEXT("HoudiniMenu_ToolTip", "Open the Houdini Engine menu"), - FNewMenuDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniMainMenuExtension), - "View"); -} - -void -FHoudiniEngineEditor::AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder) -{ - /* - MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); - // Icons used by the commands are defined in the HoudiniEngineStyle - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - MenuBuilder.EndSection(); - */ - - MenuBuilder.BeginSection("Session", LOCTEXT("SessionLabel", "Session")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CreateSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ConnectSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenSessionSync); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CloseSessionSync); - - // Viewport sync menu - struct FLocalMenuBuilder - { - static void FillViewportSyncMenu(FMenuBuilder& InSubMenuBuilder) - { - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncNone); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncHoudini); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncUnreal); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncBoth); - } - }; - - MenuBuilder.AddSubMenu( - LOCTEXT("SyncViewport", "Sync Viewport"), - LOCTEXT("SyncViewport_ToolTip", "Sync Viewport"), - FNewMenuDelegate::CreateStatic(&FLocalMenuBuilder::FillViewportSyncMenu), - false, - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._SyncViewport")); - - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("PDG", LOCTEXT("PDGLabel", "PDG")); - struct FLocalPDGMenuBuilder - { - static void FillPDGMenu(FMenuBuilder& InSubMenuBuilder) - { - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._IsPDGCommandletEnabled); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StartPDGCommandlet); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopPDGCommandlet); - } - }; - MenuBuilder.AddSubMenu( - LOCTEXT("PDGSubMenu", "Work Item Import Settings"), - LOCTEXT("PDGSubmenu_ToolTip", "PDG Work Item Import Settings"), - FNewMenuDelegate::CreateStatic(&FLocalPDGMenuBuilder::FillPDGMenu), - false, - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.PDGLink")); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Plugin", LOCTEXT("PluginLabel", "Plugin")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._InstallInfo); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PluginSettings); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("File", LOCTEXT("FileLabel", "File")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Help", LOCTEXT("HelpLabel", "Help And Support")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineDoc); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineForum); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Actions", LOCTEXT("ActionsLabel", "Actions")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - - - MenuBuilder.EndSection(); -} - -void -FHoudiniEngineEditor::RegisterForUndo() -{ - /* - if (GUnrealEd) - GUnrealEd->RegisterForUndo(this); - */ -} - -void -FHoudiniEngineEditor::UnregisterForUndo() -{ - /* - if (GUnrealEd) - GUnrealEd->UnregisterForUndo(this); - */ -} - -void -FHoudiniEngineEditor::RegisterPlacementModeExtensions() -{ - // Load custom houdini tools - /* - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - check(HoudiniRuntimeSettings); - - if (HoudiniRuntimeSettings->bHidePlacementModeHoudiniTools) - return; - - FPlacementCategoryInfo Info( - LOCTEXT("HoudiniCategoryName", "Houdini Engine"), - "HoudiniEngine", - TEXT("PMHoudiniEngine"), - 25 - ); - Info.CustomGenerator = []() -> TSharedRef { return SNew(SHoudiniToolPalette); }; - - IPlacementModeModule::Get().RegisterPlacementCategory(Info); - */ -} - -void -FHoudiniEngineEditor::UnregisterPlacementModeExtensions() -{ - /* - if (IPlacementModeModule::IsAvailable()) - { - IPlacementModeModule::Get().UnregisterPlacementCategory("HoudiniEngine"); - } - - HoudiniTools.Empty(); - */ -} - -void -FHoudiniEngineEditor::InitializeWidgetResource() -{ - // Choice labels for all the input types - //TArray> InputTypeChoiceLabels; - InputTypeChoiceLabels.Reset(); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Asset)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Landscape)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::World)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Skeletal)))); - - BlueprintInputTypeChoiceLabels.Reset(); - BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); - BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); - - // Choice labels for all Houdini curve types - HoudiniCurveTypeChoiceLabels.Reset(); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Polygon)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Nurbs)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Bezier)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Points)))); - - // Choice labels for all Houdini curve methods - HoudiniCurveMethodChoiceLabels.Reset(); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::CVs)))); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Breakpoints)))); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Freehand)))); - - // Choice labels for all Houdini ramp parameter interpolation methods - HoudiniParameterRampInterpolationLabels.Reset(); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CONSTANT)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::LINEAR)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CATMULL_ROM)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::MONOTONE_CUBIC)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BEZIER)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BSPLINE)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::HERMITE)))); - - // Choice labels for all Houdini curve output export types - HoudiniCurveOutputExportTypeLabels.Reset(); - HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Unreal Spline")))); - HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Houdini Spline")))); - - // Choice labels for all Unreal curve output curve types - //(for temporary, we need to figure out a way to access the output curve's info later) - UnrealCurveOutputCurveTypeLabels.Reset(); - UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Linear")))); - UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Curve")))); - - // Option labels for all landscape outputs bake options - HoudiniLandscapeOutputBakeOptionLabels.Reset(); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Current Level")))); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Image")))); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To New World")))); - - // Option labels for Houdini Engine PDG bake options - HoudiniEnginePDGBakeTypeOptionLabels.Reset(); - HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); - HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); - - // Option labels for Houdini Engine bake options - HoudiniEngineBakeTypeOptionLabels.Reset(); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToFoliage)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToWorldOutliner)))); - - // Option labels for Houdini Engine PDG bake options - HoudiniEnginePDGBakeSelectionOptionLabels.Reset(); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::All)))); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNetwork)))); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNode)))); - - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Reset(); - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::ReplaceExistingAssets)))); - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::CreateNewAssets)))); - - - static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); - - // Houdini Logo Brush - FString Icon128FilePath = IconsDir + TEXT("icon_houdini_logo_128"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) - { - const FName BrushName(*Icon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Logo Brush - FString HEIcon128FilePath = IconsDir + TEXT("icon_hengine_logo_128"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) - { - const FName BrushName(*HEIcon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Banner - FString HoudiniEngineUIIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_banner_d.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Rebuild Icon Brush - FString HoudiniEngineUIRebuildIconFilePath = IconsDir + TEXT("rebuild_all16x16.png"); - //FString HoudiniEngineUIRebuildIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_reload_icon.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRebuildIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIRebuildIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIRebuildIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Recook Icon Brush - //FString HoudiniEngineUIRecookIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_recook_icon.png"); - FString HoudiniEngineUIRecookIconFilePath = IconsDir + TEXT("cook_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRecookIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIRecookIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIRecookIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Reset Parameters Icon Brush - //FString HoudiniEngineUIResetParametersIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_resetparameters_icon.png"); - FString HoudiniEngineUIResetParametersIconFilePath = IconsDir + TEXT("reset_parameters16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIResetParametersIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIResetParametersIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIResetParametersIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Bake - FString BakeIconFilePath = IconsDir + TEXT("bake_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*BakeIconFilePath)) - { - const FName BrushName(*BakeIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIBakeIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // CookLog - FString CookLogIconFilePath = IconsDir + TEXT("cook_log16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*CookLogIconFilePath)) - { - const FName BrushName(*CookLogIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUICookLogIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // AssetHelp - FString AssetHelpIconFilePath = IconsDir + TEXT("asset_help16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*AssetHelpIconFilePath)) - { - const FName BrushName(*AssetHelpIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIAssetHelpIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - - // PDG Asset Link - // PDG - FString PDGIconFilePath = IconsDir + TEXT("pdg_link16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGIconFilePath)) - { - const FName BrushName(*PDGIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Cancel - // PDGCancel - FString PDGCancelIconFilePath = IconsDir + TEXT("pdg_cancel16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGCancelIconFilePath)) - { - const FName BrushName(*PDGCancelIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGCancelIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Dirty All - // PDGDirtyAll - FString PDGDirtyAllIconFilePath = IconsDir + TEXT("pdg_dirty_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyAllIconFilePath)) - { - const FName BrushName(*PDGDirtyAllIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGDirtyAllIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Dirty Node - // PDGDirtyNode - FString PDGDirtyNodeIconFilePath = IconsDir + TEXT("pdg_dirty_node16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyNodeIconFilePath)) - { - const FName BrushName(*PDGDirtyNodeIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGDirtyNodeIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Pause - // PDGReset - FString PDGPauseIconFilePath = IconsDir + TEXT("pdg_pause16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGPauseIconFilePath)) - { - const FName BrushName(*PDGPauseIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGPauseIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Reset - // PDGReset - FString PDGResetIconFilePath = IconsDir + TEXT("pdg_reset16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGResetIconFilePath)) - { - const FName BrushName(*PDGResetIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGResetIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Refresh - // PDGRefresh - FString PDGRefreshIconFilePath = IconsDir + TEXT("pdg_refresh16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGRefreshIconFilePath)) - { - const FName BrushName(*PDGRefreshIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGRefreshIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } -} - -void -FHoudiniEngineEditor::AddLevelViewportMenuExtender() -{ - FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked("LevelEditor"); - auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); - - MenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender)); - LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle(); -} - -void -FHoudiniEngineEditor::RemoveLevelViewportMenuExtender() -{ - if (LevelViewportExtenderHandle.IsValid()) - { - FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr("LevelEditor"); - if (LevelEditorModule) - { - typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType; - LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll( - [=](const DelegateType& In) { return In.GetHandle() == LevelViewportExtenderHandle; }); - } - } -} - -TSharedRef -FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef CommandList, const TArray InActors) -{ - TSharedRef Extender = MakeShareable(new FExtender); - - // Build an array of the HoudiniAssets corresponding to the selected actors - TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets; - TArray< TWeakObjectPtr< AHoudiniAssetActor > > HoudiniAssetActors; - for (auto CurrentActor : InActors) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(CurrentActor); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - HoudiniAssetActors.Add(HoudiniAssetActor); - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssets.AddUnique(HoudiniAssetComponent->GetHoudiniAsset()); - } - - if (HoudiniAssets.Num() > 0) - { - // Add the Asset menu extension - if (AssetTypeActions.Num() > 0) - { - // Add the menu extensions via our HoudiniAssetTypeActions - FAssetTypeActions_HoudiniAsset * HATA = static_cast(AssetTypeActions[0].Get()); - if (HATA) - Extender = HATA->AddLevelEditorMenuExtenders(HoudiniAssets); - } - } - - if (HoudiniAssetActors.Num() > 0) - { - // Add some actor menu extensions - FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); - TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); - Extender->AddMenuExtension( - "ActorControl", - EExtensionHook::After, - LevelEditorCommandBindings, - FMenuExtensionDelegate::CreateLambda([this, HoudiniAssetActors](FMenuBuilder& MenuBuilder) - { - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recentre", "Recentre selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecentreTooltip", "Recentres the selected Houdini Asset Actors pivots to their input/cooked static mesh average centre."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecentreSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recook", "Recook selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecookTooltip", "Forces a recook on the selected Houdini Asset Actors."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._CookSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Rebuild", "Rebuild selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RebuildTooltip", "Rebuilds selected Houdini Asset Actors in the current level."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RebuildSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshes", "Refine Houdini Proxy Meshes"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshesTooltip", "Build and replace Houdini Proxy Meshes with Static Meshes."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RefineSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - }) - ); - } - - return Extender; -} - -void -FHoudiniEngineEditor::RegisterConsoleCommands() -{ - // Register corresponding console commands - static FAutoConsoleCommand CCmdOpen = FAutoConsoleCommand( - TEXT("Houdini.Open"), - TEXT("Open the scene in Houdini."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::OpenInHoudini)); - - static FAutoConsoleCommand CCmdSave = FAutoConsoleCommand( - TEXT("Houdini.Save"), - TEXT("Save the current Houdini scene to a hip file."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::SaveHIPFile)); - - static FAutoConsoleCommand CCmdBake = FAutoConsoleCommand( - TEXT("Houdini.BakeAll"), - TEXT("Bakes and replaces with blueprints all Houdini Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::BakeAllAssets)); - - static FAutoConsoleCommand CCmdClean = FAutoConsoleCommand( - TEXT("Houdini.Clean"), - TEXT("Cleans up unused/unreferenced Houdini Engine temporary files."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::CleanUpTempFolder)); - - static FAutoConsoleCommand CCmdPause = FAutoConsoleCommand( - TEXT("Houdini.Pause"), - TEXT("Pauses Houdini Engine Asset cooking."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::PauseAssetCooking)); - - // Additional console only commands - static FAutoConsoleCommand CCmdCookAll = FAutoConsoleCommand( - TEXT("Houdini.CookAll"), - TEXT("Re-cooks all Houdini Engine Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RecookAllAssets)); - - static FAutoConsoleCommand CCmdRebuildAll = FAutoConsoleCommand( - TEXT("Houdini.RebuildAll"), - TEXT("Rebuilds all Houdini Engine Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RebuildAllAssets)); - - static FAutoConsoleCommand CCmdCookSelec = FAutoConsoleCommand( - TEXT("Houdini.Cook"), - TEXT("Re-cooks selected Houdini Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RecookSelection)); - - static FAutoConsoleCommand CCmdRebuildSelec = FAutoConsoleCommand( - TEXT("Houdini.Rebuild"), - TEXT("Rebuilds selected Houdini Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RebuildSelection)); - - static FAutoConsoleCommand CCmdBakeSelec = FAutoConsoleCommand( - TEXT("Houdini.Bake"), - TEXT("Bakes and replaces with blueprints selected Houdini Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::BakeSelection)); - - static FAutoConsoleCommand CCmdRestartSession = FAutoConsoleCommand( - TEXT("Houdini.RestartSession"), - TEXT("Restart the current Houdini Session."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RestartSession)); - - /* - IConsoleManager &ConsoleManager = IConsoleManager::Get(); - const TCHAR *CommandName = TEXT("HoudiniEngine.RefineHoudiniProxyMeshesToStaticMeshes"); - IConsoleCommand *Command = ConsoleManager.RegisterConsoleCommand( - CommandName, - TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), - FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); - if (Command) - { - ConsoleCommands.Add(Command); - } - else - { - HOUDINI_LOG_ERROR(TEXT("Failed to register the '%s' console command."), CommandName); - } - */ - - static FAutoConsoleCommand CCmdRefine = FAutoConsoleCommand( - TEXT("Houdini.RefineAll"), - TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), - FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); - - static FAutoConsoleCommand CCmdOpenSessionSync = FAutoConsoleCommand( - TEXT("Houdini.OpenSessionSync"), - TEXT("Stops the current session, opens Houdini and automnatically start and connect a Session Sync."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::OpenSessionSync)); -} - -void -FHoudiniEngineEditor::UnregisterConsoleCommands() -{ - IConsoleManager &ConsoleManager = IConsoleManager::Get(); - for (IConsoleCommand *Command : ConsoleCommands) - { - if (Command) - { - ConsoleManager.UnregisterConsoleObject(Command); - } - } - ConsoleCommands.Empty(); -} - -void -FHoudiniEngineEditor::RegisterEditorDelegates() -{ - PreSaveWorldEditorDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([](uint32 SaveFlags, UWorld* World) - { - // Skip if this is a game world or an autosave, only refine meshes when the user manually saves - if (!World->IsGameWorld() && (SaveFlags & ESaveFlags::SAVE_FromAutosave) == 0) - { - const bool bSelectedOnly = false; - const bool bSilent = false; - const bool bRefineAll = false; - const bool bOnPreSaveWorld = true; - UWorld * const OnPreSaveWorld = World; - const bool bOnPreBeginPIE = false; - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); - } - - if (!World->IsGameWorld()) - { - UWorld * const OnPreSaveWorld = World; - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - - // Save all dirty temporary cook package OnPostSaveWorld - OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([OnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) - { - if (OnPreSaveWorld && OnPreSaveWorld != InWorld) - return; - - FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(InWorld); - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - }); - } - }); - - PreBeginPIEEditorDelegateHandle = FEditorDelegates::PreBeginPIE.AddLambda([](const bool bIsSimulating) - { - const bool bSelectedOnly = false; - const bool bSilent = false; - const bool bRefineAll = false; - const bool bOnPreSaveWorld = false; - UWorld * const OnPreSaveWorld = nullptr; - const bool bOnPreBeginPIE = true; - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); - }); - - OnDeleteActorsBegin = FEditorDelegates::OnDeleteActorsBegin.AddLambda([this](){ this->HandleOnDeleteActorsBegin(); }); - OnDeleteActorsEnd = FEditorDelegates::OnDeleteActorsEnd.AddLambda([this](){ this-> HandleOnDeleteActorsEnd(); }); -} - -void -FHoudiniEngineEditor::UnregisterEditorDelegates() -{ - if (PreSaveWorldEditorDelegateHandle.IsValid()) - FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldEditorDelegateHandle); - - if (PreBeginPIEEditorDelegateHandle.IsValid()) - FEditorDelegates::PreBeginPIE.Remove(PreBeginPIEEditorDelegateHandle); - - if (OnDeleteActorsBegin.IsValid()) - FEditorDelegates::OnDeleteActorsBegin.Remove(OnDeleteActorsBegin); - - if (OnDeleteActorsEnd.IsValid()) - FEditorDelegates::OnDeleteActorsEnd.Remove(OnDeleteActorsEnd); -} - -FString -FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption) -{ - FString Str; - switch (BakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - Str = "Actor"; - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - Str = "Blueprint"; - break; - - case EHoudiniEngineBakeOption::ToFoliage: - Str = "Foliage"; - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - Str = "World Outliner"; - break; - } - - return Str; -} - -FString -FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption) -{ - FString Str; - switch (BakeOption) - { - case EPDGBakeSelectionOption::All: - Str = "All Outputs"; - break; - - case EPDGBakeSelectionOption::SelectedNetwork: - Str = "Selected Network (All Outputs)"; - break; - - case EPDGBakeSelectionOption::SelectedNode: - Str = "Selected Node (All Outputs)"; - break; - } - - return Str; -} - -FString -FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption) -{ - FString Str; - switch (InOption) - { - case EPDGBakePackageReplaceModeOption::CreateNewAssets: - Str = "Create New Assets"; - break; - case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: - Str = "Replace Existing Assets"; - break; - } - - return Str; -} - -const EHoudiniEngineBakeOption -FHoudiniEngineEditor::StringToHoudiniEngineBakeOption(const FString & InString) -{ - if (InString == "Actor") - return EHoudiniEngineBakeOption::ToActor; - - if (InString == "Blueprint") - return EHoudiniEngineBakeOption::ToBlueprint; - - if (InString == "Foliage") - return EHoudiniEngineBakeOption::ToFoliage; - - if (InString == "World Outliner") - return EHoudiniEngineBakeOption::ToWorldOutliner; - - return EHoudiniEngineBakeOption::ToActor; -} - -const EPDGBakeSelectionOption -FHoudiniEngineEditor::StringToPDGBakeSelectionOption(const FString& InString) -{ - if (InString == "All Outputs") - return EPDGBakeSelectionOption::All; - - if (InString == "Selected Network (All Outputs)") - return EPDGBakeSelectionOption::SelectedNetwork; - - if (InString == "Selected Node (All Outputs)") - return EPDGBakeSelectionOption::SelectedNode; - - return EPDGBakeSelectionOption::All; -} - -const EPDGBakePackageReplaceModeOption -FHoudiniEngineEditor::StringToPDGBakePackageReplaceModeOption(const FString & InString) -{ - if (InString == "Create New Assets") - return EPDGBakePackageReplaceModeOption::CreateNewAssets; - - if (InString == "Replace Existing Assets") - return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; -} - -const EPackageReplaceMode -FHoudiniEngineEditor::PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode) -{ - EPackageReplaceMode Mode; - switch (InReplaceMode) - { - case EPDGBakePackageReplaceModeOption::CreateNewAssets: - Mode = EPackageReplaceMode::CreateNewAssets; - break; - case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: - Mode = EPackageReplaceMode::ReplaceExistingAssets; - break; - default: - { - Mode = FHoudiniPackageParams::GetDefaultReplaceMode(); - HOUDINI_LOG_WARNING(TEXT("Unsupported value for EPDGBakePackageReplaceModeOption %d, using " - "FHoudiniPackageParams::GetDefaultReplaceMode() for resulting EPackageReplaceMode %d"), - InReplaceMode, Mode); - } - } - - return Mode; -} - -void -FHoudiniEngineEditor::HandleOnDeleteActorsBegin() -{ - if (!GEditor) - return; - - TArray AssetActorsWithTempPDGOutput; - // Iterate over all selected actors - for(FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) - { - AActor* SelectedActor = Cast(*It); - if (IsValid(SelectedActor)) - { - // If the class is a AHoudiniAssetActor check if it has temporary PDG outputs - AHoudiniAssetActor* AssetActor = Cast(SelectedActor); - if (IsValid(AssetActor)) - { - UHoudiniPDGAssetLink* AssetLink = AssetActor->GetPDGAssetLink(); - if (IsValid(AssetLink) && AssetLink->HasTemporaryOutputs()) - { - AssetActorsWithTempPDGOutput.Add(AssetActor); - } - } - } - } - - if (AssetActorsWithTempPDGOutput.Num() > 0) - { - const FText DialogTitle = LOCTEXT( - "PDGAssetLink_DeleteWithTemporaryOutputs_Title", - "Warning: PDG Asset Link(s) With Temporary Outputs"); - const EAppReturnType::Type Choice = FMessageDialog::Open( - EAppMsgType::YesNo, - EAppReturnType::No, - LOCTEXT( - "PDGAssetLink_DeleteWithTemporaryOutputs", - "One or more PDG Asset Links in the selection still have temporary outputs. Are you sure you want to " - "delete these PDG Asset Links and their actors?"), - &DialogTitle); - - const bool bKeepAssetLinkActors = (Choice == EAppReturnType::No); - for (AHoudiniAssetActor* AssetActor : AssetActorsWithTempPDGOutput) - { - if (bKeepAssetLinkActors) - { - GEditor->SelectActor(AssetActor, false, false); - ActorsToReselectOnDeleteActorsEnd.Add(AssetActor); - } - } - } -} - -void -FHoudiniEngineEditor::HandleOnDeleteActorsEnd() -{ - if (!GEditor) - return; - - for (AActor* Actor : ActorsToReselectOnDeleteActorsEnd) - { - if (IsValid(Actor)) - GEditor->SelectActor(Actor, true, false); - } - GEditor->NoteSelectionChange(); - ActorsToReselectOnDeleteActorsEnd.Empty(); -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineEditor.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetBroker.h" +#include "HoudiniAssetActorFactory.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniRuntimeSettingsDetails.h" +#include "HoudiniSplineComponentVisualizer.h" +#include "HoudiniHandleComponentVisualizer.h" +#include "AssetTypeActions_HoudiniAsset.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPackageParams.h" + +#include "Modules/ModuleManager.h" +#include "Interfaces/IPluginManager.h" +#include "HAL/PlatformFilemanager.h" +#include "Misc/MessageDialog.h" +#include "Misc/Paths.h" +#include "AssetRegistryModule.h" +#include "PropertyEditorModule.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "LevelEditor.h" +#include "Templates/SharedPointer.h" +#include "Framework/Application/SlateApplication.h" +#include "HAL/ConsoleManager.h" +#include "Editor/UnrealEdEngine.h" +#include "Editor.h" +#include "UnrealEdGlobals.h" +#include "Engine/Selection.h" +#include "Widgets/Input/SCheckBox.h" +#include "Logging/LogMacros.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHoudiniEngineEditor, HoudiniEngineEditor); +DEFINE_LOG_CATEGORY(LogHoudiniEngineEditor); + +FHoudiniEngineEditor * +FHoudiniEngineEditor::HoudiniEngineEditorInstance = nullptr; + +FHoudiniEngineEditor & +FHoudiniEngineEditor::Get() +{ + return *HoudiniEngineEditorInstance; +} + +bool +FHoudiniEngineEditor::IsInitialized() +{ + return FHoudiniEngineEditor::HoudiniEngineEditorInstance != nullptr; +} + +FHoudiniEngineEditor::FHoudiniEngineEditor() +{ +} + +void FHoudiniEngineEditor::StartupModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine Editor module.")); + + // Create style set. + FHoudiniEngineStyle::Initialize(); + + // Initilizes various resources used by our editor UI widgets + InitializeWidgetResource(); + + // Register asset type actions. + RegisterAssetTypeActions(); + + // Register asset brokers. + RegisterAssetBrokers(); + + // Register component visualizers. + RegisterComponentVisualizers(); + + // Register detail presenters. + RegisterDetails(); + + // Register actor factories. + RegisterActorFactories(); + + // Extends the file menu. + ExtendMenu(); + + // Extend the World Outliner Menu + AddLevelViewportMenuExtender(); + + // Adds the custom console commands + RegisterConsoleCommands(); + + // Register global undo / redo callbacks. + //RegisterForUndo(); + + //RegisterPlacementModeExtensions(); + + // Register for any FEditorDelegates that we are interested in, such as + // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds + RegisterEditorDelegates(); + + // Store the instance. + FHoudiniEngineEditor::HoudiniEngineEditorInstance = this; + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module startup complete.")); +} + +void FHoudiniEngineEditor::ShutdownModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine Editor module.")); + + // Deregister editor delegates + UnregisterEditorDelegates(); + + // Deregister console commands + UnregisterConsoleCommands(); + + // Remove the level viewport Menu extender + RemoveLevelViewportMenuExtender(); + + // Unregister asset type actions. + UnregisterAssetTypeActions(); + + // Unregister asset brokers. + //UnregisterAssetBrokers(); + + // Unregister detail presenters. + UnregisterDetails(); + + // Unregister our component visualizers. + //UnregisterComponentVisualizers(); + + // Unregister global undo / redo callbacks. + //UnregisterForUndo(); + + //UnregisterPlacementModeExtensions(); + + // Unregister the styleset + FHoudiniEngineStyle::Shutdown(); + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module shutdown complete.")); +} + +FString +FHoudiniEngineEditor::GetHoudiniEnginePluginDir() +{ + FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(EnginePluginDir)) + return EnginePluginDir; + + FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(ProjectPluginDir)) + return ProjectPluginDir; + + TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); + FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; + if (FPaths::DirectoryExists(PluginBaseDir)) + return PluginBaseDir; + + HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); + + return EnginePluginDir; +} + +void +FHoudiniEngineEditor::RegisterDetails() +{ + FPropertyEditorModule & PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Register details presenter for our component type and runtime settings. + PropertyModule.RegisterCustomClassLayout( + TEXT("HoudiniAssetComponent"), + FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniAssetComponentDetails::MakeInstance)); + + PropertyModule.RegisterCustomClassLayout( + TEXT("HoudiniRuntimeSettings"), + FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniRuntimeSettingsDetails::MakeInstance)); +} + +void +FHoudiniEngineEditor::UnregisterDetails() +{ + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + FPropertyEditorModule & PropertyModule = + FModuleManager::LoadModuleChecked("PropertyEditor"); + + PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniAssetComponent")); + PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniRuntimeSettings")); + } +} + +void +FHoudiniEngineEditor::RegisterComponentVisualizers() +{ + if (GUnrealEd) + { + // Register Houdini spline visualizer + SplineComponentVisualizer = MakeShareable(new FHoudiniSplineComponentVisualizer); + if (SplineComponentVisualizer.IsValid()) + { + GUnrealEd->RegisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName(), SplineComponentVisualizer); + SplineComponentVisualizer->OnRegister(); + } + + // Register Houdini handle visualizer + HandleComponentVisualizer = MakeShareable(new FHoudiniHandleComponentVisualizer); + if (HandleComponentVisualizer.IsValid()) + { + GUnrealEd->RegisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName(), HandleComponentVisualizer); + HandleComponentVisualizer->OnRegister(); + } + } +} + +void +FHoudiniEngineEditor::UnregisterComponentVisualizers() +{ + if (GUnrealEd) + { + // Unregister Houdini spline visualizer + if(SplineComponentVisualizer.IsValid()) + GUnrealEd->UnregisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); + + // Unregister Houdini handle visualizer + if (HandleComponentVisualizer.IsValid()) + GUnrealEd->UnregisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + } +} + +void +FHoudiniEngineEditor::RegisterAssetTypeAction(IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action) +{ + AssetTools.RegisterAssetTypeActions(Action); + AssetTypeActions.Add(Action); +} + +void +FHoudiniEngineEditor::RegisterAssetTypeActions() +{ + // Create and register asset type actions for Houdini asset. + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools").Get(); + RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_HoudiniAsset())); +} + +void +FHoudiniEngineEditor::UnregisterAssetTypeActions() +{ + // Unregister asset type actions we have previously registered. + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >("AssetTools").Get(); + + for (int32 Index = 0; Index < AssetTypeActions.Num(); ++Index) + AssetTools.UnregisterAssetTypeActions(AssetTypeActions[Index].ToSharedRef()); + + AssetTypeActions.Empty(); + } +} + +void +FHoudiniEngineEditor::RegisterAssetBrokers() +{ + // Create and register broker for Houdini asset. + HoudiniAssetBroker = MakeShareable(new FHoudiniAssetBroker()); + FComponentAssetBrokerage::RegisterBroker( HoudiniAssetBroker, UHoudiniAssetComponent::StaticClass(), true, true ); +} + +void +FHoudiniEngineEditor::UnregisterAssetBrokers() +{ + if (UObjectInitialized()) + { + // Unregister broker. + FComponentAssetBrokerage::UnregisterBroker( HoudiniAssetBroker ); + } +} + +void +FHoudiniEngineEditor::RegisterActorFactories() +{ + if (GEditor) + { + UHoudiniAssetActorFactory * HoudiniAssetActorFactory = + NewObject< UHoudiniAssetActorFactory >(GetTransientPackage(), UHoudiniAssetActorFactory::StaticClass()); + + GEditor->ActorFactories.Add(HoudiniAssetActorFactory); + } +} + +void +FHoudiniEngineEditor::BindMenuCommands() +{ + HEngineCommands = MakeShareable(new FUICommandList); + + FHoudiniEngineCommands::Register(); + const FHoudiniEngineCommands& Commands = FHoudiniEngineCommands::Get(); + + // Session + + HEngineCommands->MapAction( + Commands._CreateSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CreateSession(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._ConnectSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ConnectSession(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._StopSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopSession(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RestartSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RestartSession(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OpenSessionSync, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OpenSessionSync(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); + + HEngineCommands->MapAction( + Commands._CloseSessionSync, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CloseSessionSync(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); + + HEngineCommands->MapAction( + Commands._ViewportSyncNone, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(0); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 0); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncHoudini, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(1); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 1); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncUnreal, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(2); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 2); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncBoth, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(3); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 3); }) + ); + + // PDG commandlet + HEngineCommands->MapAction( + Commands._IsPDGCommandletEnabled, + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::SetPDGCommandletEnabled(!FHoudiniEngineCommands::IsPDGCommandletEnabled()); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletEnabled(); }) + ); + + HEngineCommands->MapAction( + Commands._StartPDGCommandlet, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StartPDGCommandlet(); }), + FCanExecuteAction::CreateLambda([]() + { + return FHoudiniEngineCommands::IsPDGCommandletEnabled() && !FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); + }) + ); + + HEngineCommands->MapAction( + Commands._StopPDGCommandlet, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopPDGCommandlet(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); })); + + // Plugin + + HEngineCommands->MapAction( + Commands._InstallInfo, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowInstallInfo(); }), + FCanExecuteAction::CreateLambda([]() { return false; })); + + HEngineCommands->MapAction( + Commands._PluginSettings, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowPluginSettings(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Files + + HEngineCommands->MapAction( + Commands._OpenInHoudini, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::OpenInHoudini(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._SaveHIPFile, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::SaveHIPFile(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._CleanUpTempFolder, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CleanUpTempFolder(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Help and support + + HEngineCommands->MapAction( + Commands._ReportBug, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::ReportBug(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OnlineDoc, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineDocumentation(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OnlineForum, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineForum(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Actions + + HEngineCommands->MapAction( + Commands._CookAll, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookAllAssets(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._CookSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RebuildAll, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildAllAssets(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RebuildSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._BakeAll, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::BakeAllAssets(); }), + FCanExecuteAction::CreateLambda([](){ return true; })); + + HEngineCommands->MapAction( + Commands._BakeSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::BakeSelection(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._RefineAll, + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._RefineSelected, + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._PauseAssetCooking, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::PauseAssetCooking(); }), + FCanExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::IsSessionValid(); }), + FIsActionChecked::CreateLambda([](){ return FHoudiniEngineCommands::IsAssetCookingPaused(); })); + + // Non menu command (used for shortcuts only) + + // Append the command to the editor module + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >("LevelEditor"); + LevelEditorModule.GetGlobalLevelEditorActions()->Append(HEngineCommands.ToSharedRef()); +} + +void +FHoudiniEngineEditor::ExtendMenu() +{ + if (IsRunningCommandlet()) + return; + + // We need to add/bind the UI Commands to their functions first + BindMenuCommands(); + + MainMenuExtender = MakeShareable(new FExtender); + + // Extend File menu, we will add Houdini section. + MainMenuExtender->AddMenuExtension( + "FileLoadAndSave", + EExtensionHook::After, + HEngineCommands, + FMenuExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniFileMenuExtension)); + + MainMenuExtender->AddMenuBarExtension( + "Edit", + EExtensionHook::After, + HEngineCommands, + FMenuBarExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniEditorMenu)); + + // Add our menu extender + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); +} + +void +FHoudiniEngineEditor::AddHoudiniFileMenuExtension(FMenuBuilder & MenuBuilder) +{ + MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); + + // Icons used by the commands are defined in the HoudiniEngineStyle + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + + MenuBuilder.EndSection(); +} + +void +FHoudiniEngineEditor::AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder) +{ + // View + MenuBarBuilder.AddPullDownMenu( + LOCTEXT("HoudiniLabel", "Houdini Engine"), + LOCTEXT("HoudiniMenu_ToolTip", "Open the Houdini Engine menu"), + FNewMenuDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniMainMenuExtension), + "View"); +} + +void +FHoudiniEngineEditor::AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder) +{ + /* + MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); + // Icons used by the commands are defined in the HoudiniEngineStyle + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + MenuBuilder.EndSection(); + */ + + MenuBuilder.BeginSection("Session", LOCTEXT("SessionLabel", "Session")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CreateSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ConnectSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenSessionSync); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CloseSessionSync); + + // Viewport sync menu + struct FLocalMenuBuilder + { + static void FillViewportSyncMenu(FMenuBuilder& InSubMenuBuilder) + { + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncNone); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncHoudini); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncUnreal); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncBoth); + } + }; + + MenuBuilder.AddSubMenu( + LOCTEXT("SyncViewport", "Sync Viewport"), + LOCTEXT("SyncViewport_ToolTip", "Sync Viewport"), + FNewMenuDelegate::CreateStatic(&FLocalMenuBuilder::FillViewportSyncMenu), + false, + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._SyncViewport")); + + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("PDG", LOCTEXT("PDGLabel", "PDG")); + struct FLocalPDGMenuBuilder + { + static void FillPDGMenu(FMenuBuilder& InSubMenuBuilder) + { + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._IsPDGCommandletEnabled); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StartPDGCommandlet); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopPDGCommandlet); + } + }; + MenuBuilder.AddSubMenu( + LOCTEXT("PDGSubMenu", "Work Item Import Settings"), + LOCTEXT("PDGSubmenu_ToolTip", "PDG Work Item Import Settings"), + FNewMenuDelegate::CreateStatic(&FLocalPDGMenuBuilder::FillPDGMenu), + false, + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.PDGLink")); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Plugin", LOCTEXT("PluginLabel", "Plugin")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._InstallInfo); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PluginSettings); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("File", LOCTEXT("FileLabel", "File")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Help", LOCTEXT("HelpLabel", "Help And Support")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineDoc); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineForum); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Actions", LOCTEXT("ActionsLabel", "Actions")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + + + MenuBuilder.EndSection(); +} + +void +FHoudiniEngineEditor::RegisterForUndo() +{ + /* + if (GUnrealEd) + GUnrealEd->RegisterForUndo(this); + */ +} + +void +FHoudiniEngineEditor::UnregisterForUndo() +{ + /* + if (GUnrealEd) + GUnrealEd->UnregisterForUndo(this); + */ +} + +void +FHoudiniEngineEditor::RegisterPlacementModeExtensions() +{ + // Load custom houdini tools + /* + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check(HoudiniRuntimeSettings); + + if (HoudiniRuntimeSettings->bHidePlacementModeHoudiniTools) + return; + + FPlacementCategoryInfo Info( + LOCTEXT("HoudiniCategoryName", "Houdini Engine"), + "HoudiniEngine", + TEXT("PMHoudiniEngine"), + 25 + ); + Info.CustomGenerator = []() -> TSharedRef { return SNew(SHoudiniToolPalette); }; + + IPlacementModeModule::Get().RegisterPlacementCategory(Info); + */ +} + +void +FHoudiniEngineEditor::UnregisterPlacementModeExtensions() +{ + /* + if (IPlacementModeModule::IsAvailable()) + { + IPlacementModeModule::Get().UnregisterPlacementCategory("HoudiniEngine"); + } + + HoudiniTools.Empty(); + */ +} + +void +FHoudiniEngineEditor::InitializeWidgetResource() +{ + // Choice labels for all the input types + //TArray> InputTypeChoiceLabels; + InputTypeChoiceLabels.Reset(); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Asset)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Landscape)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::World)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Skeletal)))); + + BlueprintInputTypeChoiceLabels.Reset(); + BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); + BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); + + // Choice labels for all Houdini curve types + HoudiniCurveTypeChoiceLabels.Reset(); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Polygon)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Nurbs)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Bezier)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Points)))); + + // Choice labels for all Houdini curve methods + HoudiniCurveMethodChoiceLabels.Reset(); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::CVs)))); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Breakpoints)))); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Freehand)))); + + // Choice labels for all Houdini ramp parameter interpolation methods + HoudiniParameterRampInterpolationLabels.Reset(); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CONSTANT)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::LINEAR)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CATMULL_ROM)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::MONOTONE_CUBIC)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BEZIER)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BSPLINE)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::HERMITE)))); + + // Choice labels for all Houdini curve output export types + HoudiniCurveOutputExportTypeLabels.Reset(); + HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Unreal Spline")))); + HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Houdini Spline")))); + + // Choice labels for all Unreal curve output curve types + //(for temporary, we need to figure out a way to access the output curve's info later) + UnrealCurveOutputCurveTypeLabels.Reset(); + UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Linear")))); + UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Curve")))); + + // Option labels for all landscape outputs bake options + HoudiniLandscapeOutputBakeOptionLabels.Reset(); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Current Level")))); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Image")))); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To New World")))); + + // Option labels for Houdini Engine PDG bake options + HoudiniEnginePDGBakeTypeOptionLabels.Reset(); + HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); + HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); + + // Option labels for Houdini Engine bake options + HoudiniEngineBakeTypeOptionLabels.Reset(); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToFoliage)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToWorldOutliner)))); + + // Option labels for Houdini Engine PDG bake options + HoudiniEnginePDGBakeSelectionOptionLabels.Reset(); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::All)))); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNetwork)))); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNode)))); + + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Reset(); + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::ReplaceExistingAssets)))); + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::CreateNewAssets)))); + + + static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); + + // Houdini Logo Brush + FString Icon128FilePath = IconsDir + TEXT("icon_houdini_logo_128"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) + { + const FName BrushName(*Icon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Logo Brush + FString HEIcon128FilePath = IconsDir + TEXT("icon_hengine_logo_128"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) + { + const FName BrushName(*HEIcon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Banner + FString HoudiniEngineUIIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_banner_d.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Rebuild Icon Brush + FString HoudiniEngineUIRebuildIconFilePath = IconsDir + TEXT("rebuild_all16x16.png"); + //FString HoudiniEngineUIRebuildIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_reload_icon.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRebuildIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIRebuildIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIRebuildIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Recook Icon Brush + //FString HoudiniEngineUIRecookIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_recook_icon.png"); + FString HoudiniEngineUIRecookIconFilePath = IconsDir + TEXT("cook_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRecookIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIRecookIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIRecookIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Reset Parameters Icon Brush + //FString HoudiniEngineUIResetParametersIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_resetparameters_icon.png"); + FString HoudiniEngineUIResetParametersIconFilePath = IconsDir + TEXT("reset_parameters16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIResetParametersIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIResetParametersIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIResetParametersIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Bake + FString BakeIconFilePath = IconsDir + TEXT("bake_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*BakeIconFilePath)) + { + const FName BrushName(*BakeIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIBakeIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // CookLog + FString CookLogIconFilePath = IconsDir + TEXT("cook_log16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*CookLogIconFilePath)) + { + const FName BrushName(*CookLogIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUICookLogIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // AssetHelp + FString AssetHelpIconFilePath = IconsDir + TEXT("asset_help16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*AssetHelpIconFilePath)) + { + const FName BrushName(*AssetHelpIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIAssetHelpIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + + // PDG Asset Link + // PDG + FString PDGIconFilePath = IconsDir + TEXT("pdg_link16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGIconFilePath)) + { + const FName BrushName(*PDGIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Cancel + // PDGCancel + FString PDGCancelIconFilePath = IconsDir + TEXT("pdg_cancel16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGCancelIconFilePath)) + { + const FName BrushName(*PDGCancelIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGCancelIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Dirty All + // PDGDirtyAll + FString PDGDirtyAllIconFilePath = IconsDir + TEXT("pdg_dirty_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyAllIconFilePath)) + { + const FName BrushName(*PDGDirtyAllIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGDirtyAllIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Dirty Node + // PDGDirtyNode + FString PDGDirtyNodeIconFilePath = IconsDir + TEXT("pdg_dirty_node16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyNodeIconFilePath)) + { + const FName BrushName(*PDGDirtyNodeIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGDirtyNodeIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Pause + // PDGReset + FString PDGPauseIconFilePath = IconsDir + TEXT("pdg_pause16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGPauseIconFilePath)) + { + const FName BrushName(*PDGPauseIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGPauseIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Reset + // PDGReset + FString PDGResetIconFilePath = IconsDir + TEXT("pdg_reset16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGResetIconFilePath)) + { + const FName BrushName(*PDGResetIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGResetIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Refresh + // PDGRefresh + FString PDGRefreshIconFilePath = IconsDir + TEXT("pdg_refresh16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGRefreshIconFilePath)) + { + const FName BrushName(*PDGRefreshIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGRefreshIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } +} + +void +FHoudiniEngineEditor::AddLevelViewportMenuExtender() +{ + FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked("LevelEditor"); + auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); + + MenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender)); + LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle(); +} + +void +FHoudiniEngineEditor::RemoveLevelViewportMenuExtender() +{ + if (LevelViewportExtenderHandle.IsValid()) + { + FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr("LevelEditor"); + if (LevelEditorModule) + { + typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType; + LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll( + [=](const DelegateType& In) { return In.GetHandle() == LevelViewportExtenderHandle; }); + } + } +} + +TSharedRef +FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef CommandList, const TArray InActors) +{ + TSharedRef Extender = MakeShareable(new FExtender); + + // Build an array of the HoudiniAssets corresponding to the selected actors + TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets; + TArray< TWeakObjectPtr< AHoudiniAssetActor > > HoudiniAssetActors; + for (auto CurrentActor : InActors) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(CurrentActor); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + HoudiniAssetActors.Add(HoudiniAssetActor); + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssets.AddUnique(HoudiniAssetComponent->GetHoudiniAsset()); + } + + if (HoudiniAssets.Num() > 0) + { + // Add the Asset menu extension + if (AssetTypeActions.Num() > 0) + { + // Add the menu extensions via our HoudiniAssetTypeActions + FAssetTypeActions_HoudiniAsset * HATA = static_cast(AssetTypeActions[0].Get()); + if (HATA) + Extender = HATA->AddLevelEditorMenuExtenders(HoudiniAssets); + } + } + + if (HoudiniAssetActors.Num() > 0) + { + // Add some actor menu extensions + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); + Extender->AddMenuExtension( + "ActorControl", + EExtensionHook::After, + LevelEditorCommandBindings, + FMenuExtensionDelegate::CreateLambda([this, HoudiniAssetActors](FMenuBuilder& MenuBuilder) + { + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recentre", "Recentre selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecentreTooltip", "Recentres the selected Houdini Asset Actors pivots to their input/cooked static mesh average centre."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecentreSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recook", "Recook selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecookTooltip", "Forces a recook on the selected Houdini Asset Actors."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._CookSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Rebuild", "Rebuild selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RebuildTooltip", "Rebuilds selected Houdini Asset Actors in the current level."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RebuildSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshes", "Refine Houdini Proxy Meshes"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshesTooltip", "Build and replace Houdini Proxy Meshes with Static Meshes."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RefineSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + }) + ); + } + + return Extender; +} + +void +FHoudiniEngineEditor::RegisterConsoleCommands() +{ + // Register corresponding console commands + static FAutoConsoleCommand CCmdOpen = FAutoConsoleCommand( + TEXT("Houdini.Open"), + TEXT("Open the scene in Houdini."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::OpenInHoudini)); + + static FAutoConsoleCommand CCmdSave = FAutoConsoleCommand( + TEXT("Houdini.Save"), + TEXT("Save the current Houdini scene to a hip file."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::SaveHIPFile)); + + static FAutoConsoleCommand CCmdBake = FAutoConsoleCommand( + TEXT("Houdini.BakeAll"), + TEXT("Bakes and replaces with blueprints all Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::BakeAllAssets)); + + static FAutoConsoleCommand CCmdClean = FAutoConsoleCommand( + TEXT("Houdini.Clean"), + TEXT("Cleans up unused/unreferenced Houdini Engine temporary files."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::CleanUpTempFolder)); + + static FAutoConsoleCommand CCmdPause = FAutoConsoleCommand( + TEXT("Houdini.Pause"), + TEXT("Pauses Houdini Engine Asset cooking."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::PauseAssetCooking)); + + // Additional console only commands + static FAutoConsoleCommand CCmdCookAll = FAutoConsoleCommand( + TEXT("Houdini.CookAll"), + TEXT("Re-cooks all Houdini Engine Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RecookAllAssets)); + + static FAutoConsoleCommand CCmdRebuildAll = FAutoConsoleCommand( + TEXT("Houdini.RebuildAll"), + TEXT("Rebuilds all Houdini Engine Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RebuildAllAssets)); + + static FAutoConsoleCommand CCmdCookSelec = FAutoConsoleCommand( + TEXT("Houdini.Cook"), + TEXT("Re-cooks selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RecookSelection)); + + static FAutoConsoleCommand CCmdRebuildSelec = FAutoConsoleCommand( + TEXT("Houdini.Rebuild"), + TEXT("Rebuilds selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RebuildSelection)); + + static FAutoConsoleCommand CCmdBakeSelec = FAutoConsoleCommand( + TEXT("Houdini.Bake"), + TEXT("Bakes and replaces with blueprints selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::BakeSelection)); + + static FAutoConsoleCommand CCmdRestartSession = FAutoConsoleCommand( + TEXT("Houdini.RestartSession"), + TEXT("Restart the current Houdini Session."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RestartSession)); + + /* + IConsoleManager &ConsoleManager = IConsoleManager::Get(); + const TCHAR *CommandName = TEXT("HoudiniEngine.RefineHoudiniProxyMeshesToStaticMeshes"); + IConsoleCommand *Command = ConsoleManager.RegisterConsoleCommand( + CommandName, + TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), + FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); + if (Command) + { + ConsoleCommands.Add(Command); + } + else + { + HOUDINI_LOG_ERROR(TEXT("Failed to register the '%s' console command."), CommandName); + } + */ + + static FAutoConsoleCommand CCmdRefine = FAutoConsoleCommand( + TEXT("Houdini.RefineAll"), + TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), + FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); + + static FAutoConsoleCommand CCmdOpenSessionSync = FAutoConsoleCommand( + TEXT("Houdini.OpenSessionSync"), + TEXT("Stops the current session, opens Houdini and automnatically start and connect a Session Sync."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::OpenSessionSync)); +} + +void +FHoudiniEngineEditor::UnregisterConsoleCommands() +{ + IConsoleManager &ConsoleManager = IConsoleManager::Get(); + for (IConsoleCommand *Command : ConsoleCommands) + { + if (Command) + { + ConsoleManager.UnregisterConsoleObject(Command); + } + } + ConsoleCommands.Empty(); +} + +void +FHoudiniEngineEditor::RegisterEditorDelegates() +{ + PreSaveWorldEditorDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([](uint32 SaveFlags, UWorld* World) + { + // Skip if this is a game world or an autosave, only refine meshes when the user manually saves + if (!World->IsGameWorld() && (SaveFlags & ESaveFlags::SAVE_FromAutosave) == 0) + { + const bool bSelectedOnly = false; + const bool bSilent = false; + const bool bRefineAll = false; + const bool bOnPreSaveWorld = true; + UWorld * const OnPreSaveWorld = World; + const bool bOnPreBeginPIE = false; + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); + } + + if (!World->IsGameWorld()) + { + UWorld * const OnPreSaveWorld = World; + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + + // Save all dirty temporary cook package OnPostSaveWorld + OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([OnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) + { + if (OnPreSaveWorld && OnPreSaveWorld != InWorld) + return; + + FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(InWorld); + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + }); + } + }); + + PreBeginPIEEditorDelegateHandle = FEditorDelegates::PreBeginPIE.AddLambda([](const bool bIsSimulating) + { + const bool bSelectedOnly = false; + const bool bSilent = false; + const bool bRefineAll = false; + const bool bOnPreSaveWorld = false; + UWorld * const OnPreSaveWorld = nullptr; + const bool bOnPreBeginPIE = true; + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); + }); + + OnDeleteActorsBegin = FEditorDelegates::OnDeleteActorsBegin.AddLambda([this](){ this->HandleOnDeleteActorsBegin(); }); + OnDeleteActorsEnd = FEditorDelegates::OnDeleteActorsEnd.AddLambda([this](){ this-> HandleOnDeleteActorsEnd(); }); +} + +void +FHoudiniEngineEditor::UnregisterEditorDelegates() +{ + if (PreSaveWorldEditorDelegateHandle.IsValid()) + FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldEditorDelegateHandle); + + if (PreBeginPIEEditorDelegateHandle.IsValid()) + FEditorDelegates::PreBeginPIE.Remove(PreBeginPIEEditorDelegateHandle); + + if (OnDeleteActorsBegin.IsValid()) + FEditorDelegates::OnDeleteActorsBegin.Remove(OnDeleteActorsBegin); + + if (OnDeleteActorsEnd.IsValid()) + FEditorDelegates::OnDeleteActorsEnd.Remove(OnDeleteActorsEnd); +} + +FString +FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption) +{ + FString Str; + switch (BakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + Str = "Actor"; + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + Str = "Blueprint"; + break; + + case EHoudiniEngineBakeOption::ToFoliage: + Str = "Foliage"; + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + Str = "World Outliner"; + break; + } + + return Str; +} + +FString +FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption) +{ + FString Str; + switch (BakeOption) + { + case EPDGBakeSelectionOption::All: + Str = "All Outputs"; + break; + + case EPDGBakeSelectionOption::SelectedNetwork: + Str = "Selected Network (All Outputs)"; + break; + + case EPDGBakeSelectionOption::SelectedNode: + Str = "Selected Node (All Outputs)"; + break; + } + + return Str; +} + +FString +FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption) +{ + FString Str; + switch (InOption) + { + case EPDGBakePackageReplaceModeOption::CreateNewAssets: + Str = "Create New Assets"; + break; + case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: + Str = "Replace Existing Assets"; + break; + } + + return Str; +} + +const EHoudiniEngineBakeOption +FHoudiniEngineEditor::StringToHoudiniEngineBakeOption(const FString & InString) +{ + if (InString == "Actor") + return EHoudiniEngineBakeOption::ToActor; + + if (InString == "Blueprint") + return EHoudiniEngineBakeOption::ToBlueprint; + + if (InString == "Foliage") + return EHoudiniEngineBakeOption::ToFoliage; + + if (InString == "World Outliner") + return EHoudiniEngineBakeOption::ToWorldOutliner; + + return EHoudiniEngineBakeOption::ToActor; +} + +const EPDGBakeSelectionOption +FHoudiniEngineEditor::StringToPDGBakeSelectionOption(const FString& InString) +{ + if (InString == "All Outputs") + return EPDGBakeSelectionOption::All; + + if (InString == "Selected Network (All Outputs)") + return EPDGBakeSelectionOption::SelectedNetwork; + + if (InString == "Selected Node (All Outputs)") + return EPDGBakeSelectionOption::SelectedNode; + + return EPDGBakeSelectionOption::All; +} + +const EPDGBakePackageReplaceModeOption +FHoudiniEngineEditor::StringToPDGBakePackageReplaceModeOption(const FString & InString) +{ + if (InString == "Create New Assets") + return EPDGBakePackageReplaceModeOption::CreateNewAssets; + + if (InString == "Replace Existing Assets") + return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; +} + +const EPackageReplaceMode +FHoudiniEngineEditor::PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode) +{ + EPackageReplaceMode Mode; + switch (InReplaceMode) + { + case EPDGBakePackageReplaceModeOption::CreateNewAssets: + Mode = EPackageReplaceMode::CreateNewAssets; + break; + case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: + Mode = EPackageReplaceMode::ReplaceExistingAssets; + break; + default: + { + Mode = FHoudiniPackageParams::GetDefaultReplaceMode(); + HOUDINI_LOG_WARNING(TEXT("Unsupported value for EPDGBakePackageReplaceModeOption %d, using " + "FHoudiniPackageParams::GetDefaultReplaceMode() for resulting EPackageReplaceMode %d"), + InReplaceMode, Mode); + } + } + + return Mode; +} + +void +FHoudiniEngineEditor::HandleOnDeleteActorsBegin() +{ + if (!GEditor) + return; + + TArray AssetActorsWithTempPDGOutput; + // Iterate over all selected actors + for(FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) + { + AActor* SelectedActor = Cast(*It); + if (IsValid(SelectedActor)) + { + // If the class is a AHoudiniAssetActor check if it has temporary PDG outputs + AHoudiniAssetActor* AssetActor = Cast(SelectedActor); + if (IsValid(AssetActor)) + { + UHoudiniPDGAssetLink* AssetLink = AssetActor->GetPDGAssetLink(); + if (IsValid(AssetLink) && AssetLink->HasTemporaryOutputs()) + { + AssetActorsWithTempPDGOutput.Add(AssetActor); + } + } + } + } + + if (AssetActorsWithTempPDGOutput.Num() > 0) + { + const FText DialogTitle = LOCTEXT( + "PDGAssetLink_DeleteWithTemporaryOutputs_Title", + "Warning: PDG Asset Link(s) With Temporary Outputs"); + const EAppReturnType::Type Choice = FMessageDialog::Open( + EAppMsgType::YesNo, + EAppReturnType::No, + LOCTEXT( + "PDGAssetLink_DeleteWithTemporaryOutputs", + "One or more PDG Asset Links in the selection still have temporary outputs. Are you sure you want to " + "delete these PDG Asset Links and their actors?"), + &DialogTitle); + + const bool bKeepAssetLinkActors = (Choice == EAppReturnType::No); + for (AHoudiniAssetActor* AssetActor : AssetActorsWithTempPDGOutput) + { + if (bKeepAssetLinkActors) + { + GEditor->SelectActor(AssetActor, false, false); + ActorsToReselectOnDeleteActorsEnd.Add(AssetActor); + } + } + } +} + +void +FHoudiniEngineEditor::HandleOnDeleteActorsEnd() +{ + if (!GEditor) + return; + + for (AActor* Actor : ActorsToReselectOnDeleteActorsEnd) + { + if (IsValid(Actor)) + GEditor->SelectActor(Actor, true, false); + } + GEditor->NoteSelectionChange(); + ActorsToReselectOnDeleteActorsEnd.Empty(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h index 451d781dd..71e3cc81e 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h @@ -1,350 +1,350 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "IHoudiniEngineEditor.h" -#include "HoudiniInputTypes.h" - -#include "CoreTypes.h" -#include "Templates/SharedPointer.h" -#include "Framework/Commands/UICommandList.h" -#include "Brushes/SlateDynamicImageBrush.h" - - -class FExtender; -class IAssetTools; -class IAssetTypeActions; -class IComponentAssetBroker; -class FComponentVisualizer; -class FMenuBuilder; -class FMenuBarBuilder; -class FUICommandList; -class AActor; - -struct IConsoleCommand; -struct FSlateDynamicImageBrush; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod: int8; -enum class EHoudiniLandscapeOutputBakeType: uint8; -enum class EHoudiniEngineBakeOption : uint8; -enum class EPDGBakeSelectionOption : uint8; -enum class EPDGBakePackageReplaceModeOption : uint8; -enum class EPackageReplaceMode : int8; - -class HOUDINIENGINEEDITOR_API FHoudiniEngineEditor : public IHoudiniEngineEditor -{ - public: - FHoudiniEngineEditor(); - - // IModuleInterface methods. - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // IHoudiniEngineEditor methods - virtual void RegisterComponentVisualizers() override; - virtual void UnregisterComponentVisualizers() override; - virtual void RegisterDetails() override; - virtual void UnregisterDetails() override; - virtual void RegisterAssetTypeActions() override; - virtual void UnregisterAssetTypeActions() override; - virtual void RegisterAssetBrokers() override; - virtual void UnregisterAssetBrokers() override; - virtual void RegisterActorFactories() override; - virtual void ExtendMenu() override; - virtual void RegisterForUndo() override; - virtual void UnregisterForUndo() override; - virtual void RegisterPlacementModeExtensions() override; - virtual void UnregisterPlacementModeExtensions() override; - - // Return singleton instance of Houdini Engine Editor, used internally. - static FHoudiniEngineEditor & Get(); - - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // Returns the plugin's directory - static FString GetHoudiniEnginePluginDir(); - - // Initializes Widget resources - void InitializeWidgetResource(); - - // Menu action to pause cooking for all Houdini Assets - void PauseAssetCooking(); - - // Helper delegate used to determine if PauseAssetCooking can be executed. - bool CanPauseAssetCooking(); - - // Helper delegate used to get the current state of PauseAssetCooking. - bool IsAssetCookingPaused(); - - // Returns a pointer to the input choice types - TArray>* GetInputTypeChoiceLabels() { return &InputTypeChoiceLabels; }; - TArray>* GetBlueprintInputTypeChoiceLabels() { return &BlueprintInputTypeChoiceLabels; }; - - // Returns a pointer to the Houdini curve types - TArray>* GetHoudiniCurveTypeChoiceLabels() { return &HoudiniCurveTypeChoiceLabels; }; - - // Returns a pointer to the Houdini curve methods - TArray>* GetHoudiniCurveMethodChoiceLabels() { return &HoudiniCurveMethodChoiceLabels; }; - - // Returns a pointer to the Houdini ramp parameter interpolation methods - TArray>* GetHoudiniParameterRampInterpolationMethodLabels() {return &HoudiniParameterRampInterpolationLabels;} - - // Returns a pointer to the Houdini curve output export types - TArray>* GetHoudiniCurveOutputExportTypeLabels() { return &HoudiniCurveOutputExportTypeLabels; }; - - TArray>* GetHoudiniLandscapeOutputBakeOptionsLabels() { return &HoudiniLandscapeOutputBakeOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Type labels - TArray>* GetHoudiniEnginePDGBakeTypeOptionsLabels() { return &HoudiniEnginePDGBakeTypeOptionLabels; }; - - // Returns a pointer to the Houdini Engine Bake Type labels - TArray>* GetHoudiniEngineBakeTypeOptionsLabels() { return &HoudiniEngineBakeTypeOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Target labels - TArray>* GetHoudiniEnginePDGBakeSelectionOptionsLabels() { return &HoudiniEnginePDGBakeSelectionOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Package Replace Mode labels - TArray>* GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels() { return &HoudiniEnginePDGBakePackageReplaceModeOptionLabels; }; - - // Returns a shared Ptr to the Houdini logo - TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; - TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; - - // Functions Return a shared Ptr to the Houdini Engine UI Icon - TSharedPtr GetHoudiniEngineUIIconBrush() const { return HoudiniEngineUIIconBrush; } - TSharedPtr GetHoudiniEngineUIRebuildIconBrush() const { return HoudiniEngineUIRebuildIconBrush; } - TSharedPtr GetHoudiniEngineUIRecookIconBrush() const { return HoudiniEngineUIRecookIconBrush; } - TSharedPtr GetHoudiniEngineUIResetParametersIconBrush() const { return HoudiniEngineUIResetParametersIconBrush; } - - TSharedPtr GetHoudiniEngineUIBakeIconBrush() const { return HoudiniEngineUIBakeIconBrush; } - TSharedPtr GetHoudiniEngineUICookLogIconBrush() const { return HoudiniEngineUICookLogIconBrush; } - TSharedPtr GetHoudiniEngineUIAssetHelpIconBrush() const { return HoudiniEngineUIAssetHelpIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGIconBrush() const { return HoudiniEngineUIPDGIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGCancelIconBrush() const { return HoudiniEngineUIPDGCancelIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGDirtyAllIconBrush() const { return HoudiniEngineUIPDGDirtyAllIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGDirtyNodeIconBrush() const { return HoudiniEngineUIPDGDirtyNodeIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGPauseIconBrush() const { return HoudiniEngineUIPDGPauseIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGResetIconBrush() const { return HoudiniEngineUIPDGResetIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGRefreshIconBrush() const { return HoudiniEngineUIPDGRefreshIconBrush; } - - // Returns a pointer to Unreal output curve types (for temporary) - TArray>* GetUnrealOutputCurveTypeLabels() { return &UnrealCurveOutputCurveTypeLabels; }; - - // returns string from Houdini Engine Bake Option - FString GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption); - - // returns string from Houdini Engine PDG Bake Target Option - FString GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption); - - // returns string from PDG package replace mode option - FString GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption); - - // Return HoudiniEngineBakeOption from FString - const EHoudiniEngineBakeOption StringToHoudiniEngineBakeOption(const FString & InString); - - // Return EPDGBakeSelectionOption from FString - const EPDGBakeSelectionOption StringToPDGBakeSelectionOption(const FString& InString); - - // Return EPDGBakePackageReplaceModeOption from FString - const EPDGBakePackageReplaceModeOption StringToPDGBakePackageReplaceModeOption(const FString & InString); - - // Convert EPDGBakePackageReplaceModeOption to EPackageReplaceMode - // TODO: perhaps EPackageReplaceMode can be moved to HoudiniEngineRuntime to avoid having both - // TODO: EPDGBakePackageReplaceModeOption and EPackageReplaceMode? - const EPackageReplaceMode PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode); - - // Get the reference of the radio button folder circle point arrays reference - TArray & GetHoudiniParameterRadioButtonPointsOuter() { return HoudiniParameterRadioButtonPointsOuter; }; - TArray & GetHoudiniParameterRadioButtonPointsInner() { return HoudiniParameterRadioButtonPointsInner; }; - - // Gets the PostSaveWorldOnceHandle - FDelegateHandle& GetOnPostSaveWorldOnceHandle() { return PostSaveWorldOnceHandle; } - - protected: - - // Binds the commands used by the menus - void BindMenuCommands(); - - // Register AssetType action. - void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef< IAssetTypeActions > Action); - - // Add menu extension for our module. - void AddHoudiniFileMenuExtension(FMenuBuilder& MenuBuilder); - - // Add the Houdini Engine editor menu - void AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder); - - // Add menu extension for our module. - void AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder); - - // Adds the custom Houdini Engine commands to the world outliner context menu - void AddLevelViewportMenuExtender(); - - // Removes the custom Houdini Engine commands to the world outliner context menu - void RemoveLevelViewportMenuExtender(); - - // Returns all the custom Houdini Engine commands for the world outliner context menu - TSharedRef GetLevelViewportContextMenuExtender( - const TSharedRef CommandList, const TArray InActors); - - // Register all console commands provided by this module - void RegisterConsoleCommands(); - - // Unregister all registered console commands provided by this module - void UnregisterConsoleCommands(); - - // Register for any FEditorDelegates that we are interested in, such as - // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds - void RegisterEditorDelegates(); - - // Deregister editor delegates - void UnregisterEditorDelegates(); - - // Process the OnDeleteActorsBegin call received from FEditorDelegates. - // Check if any AHoudiniAssetActors with PDG links are selected for deletion. If so, - // check if these still have temporary outputs and give the user to option to skip - // deleting the ones with temporary output. - void HandleOnDeleteActorsBegin(); - - // Re-select AHoudiniAssetActors that were deselected (to avoid deletion) by HandleOnDeleteActorsBegin - void HandleOnDeleteActorsEnd(); - - private: - - // Singleton instance of Houdini Engine Editor. - static FHoudiniEngineEditor * HoudiniEngineEditorInstance; - - // AssetType actions associated with Houdini asset. - TArray> AssetTypeActions; - - // Broker associated with Houdini asset. - TSharedPtr HoudiniAssetBroker; - - // Widget resources: Input Type combo box labels - TArray> InputTypeChoiceLabels; - TArray> BlueprintInputTypeChoiceLabels; - - // Widget resources: Houdini Curve Type combo box labels - TArray> HoudiniCurveTypeChoiceLabels; - - // Widget resources: Houdini Curve Method combo box labels - TArray> HoudiniCurveMethodChoiceLabels; - - // Widget resources: Houdini Ramp Interpolation method combo box labels - TArray> HoudiniParameterRampInterpolationLabels; - - // Widget resources: Houdini Curve Output type labels - TArray> HoudiniCurveOutputExportTypeLabels; - - // Widget resources: Unreal Curve type labels (for temporary, we need to figure out a way to access the output curve's info later) - TArray> UnrealCurveOutputCurveTypeLabels; - - // Widget resources: Landscape output Bake type labels - TArray> HoudiniLandscapeOutputBakeOptionLabels; - - // Widget resources: PDG Bake type labels - TArray> HoudiniEnginePDGBakeTypeOptionLabels; - - // Widget resources: Bake type labels - TArray> HoudiniEngineBakeTypeOptionLabels; - - // Widget resources: PDG Bake target labels - TArray> HoudiniEnginePDGBakeSelectionOptionLabels; - - // Widget resources: PDG Bake package replace mode labels - TArray> HoudiniEnginePDGBakePackageReplaceModeOptionLabels; - - // List of UI commands used by the various menus - TSharedPtr HEngineCommands; - - // Houdini logo brush. - TSharedPtr HoudiniLogoBrush; - // Houdini Engine logo brush - TSharedPtr HoudiniEngineLogoBrush; - - // houdini Engine UI Brushes - TSharedPtr HoudiniEngineUIIconBrush; - TSharedPtr HoudiniEngineUIRebuildIconBrush; - TSharedPtr HoudiniEngineUIRecookIconBrush; - TSharedPtr HoudiniEngineUIResetParametersIconBrush; - - TSharedPtr HoudiniEngineUIBakeIconBrush; - TSharedPtr HoudiniEngineUICookLogIconBrush; - TSharedPtr HoudiniEngineUIAssetHelpIconBrush; - TSharedPtr HoudiniEngineUIPDGIconBrush; - TSharedPtr HoudiniEngineUIPDGCancelIconBrush; - TSharedPtr HoudiniEngineUIPDGDirtyAllIconBrush; - TSharedPtr HoudiniEngineUIPDGDirtyNodeIconBrush; - TSharedPtr HoudiniEngineUIPDGPauseIconBrush; - TSharedPtr HoudiniEngineUIPDGResetIconBrush; - TSharedPtr HoudiniEngineUIPDGRefreshIconBrush; - - // The extender to pass to the level editor to extend it's File menu. - TSharedPtr MainMenuExtender; - - // The extender to pass to the level editor to extend it's Main menu. - //TSharedPtr FileMenuExtender; - - // DelegateHandle for the viewport's context menu extender - FDelegateHandle LevelViewportExtenderHandle; - - // SplineComponentVisualizer - TSharedPtr SplineComponentVisualizer; - - TSharedPtr HandleComponentVisualizer; - - // Array of HoudiniEngine console commands - TArray ConsoleCommands; - - // Delegate handle for the PreSaveWorld editor delegate - FDelegateHandle PreSaveWorldEditorDelegateHandle; - - // Delegate handle for the PostSaveWorld editor delegate: this - // is bound on PreSaveWorld with specific captures and then unbound - // by itself - FDelegateHandle PostSaveWorldOnceHandle; - - // Delegate handle for the PreBeginPIE editor delegate - FDelegateHandle PreBeginPIEEditorDelegateHandle; - - // Delegate handle for OnDeleteActorsBegin - FDelegateHandle OnDeleteActorsBegin; - - // Delegate handle for OnDeleteActorsEnd - FDelegateHandle OnDeleteActorsEnd; - - // List of actors that HandleOnDeleteActorsBegin marked to _not_ be deleted. This - // is used to re-select these actors in HandleOnDeleteActorsEnd. - TArray ActorsToReselectOnDeleteActorsEnd; - - // Cache the points of radio button folder circle points to avoid huge amount of repeat computation. - // (Computing points are time consuming since it uses trigonometric functions) - TArray HoudiniParameterRadioButtonPointsOuter; - TArray HoudiniParameterRadioButtonPointsInner; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "IHoudiniEngineEditor.h" +#include "HoudiniInputTypes.h" + +#include "CoreTypes.h" +#include "Templates/SharedPointer.h" +#include "Framework/Commands/UICommandList.h" +#include "Brushes/SlateDynamicImageBrush.h" + + +class FExtender; +class IAssetTools; +class IAssetTypeActions; +class IComponentAssetBroker; +class FComponentVisualizer; +class FMenuBuilder; +class FMenuBarBuilder; +class FUICommandList; +class AActor; + +struct IConsoleCommand; +struct FSlateDynamicImageBrush; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod: int8; +enum class EHoudiniLandscapeOutputBakeType: uint8; +enum class EHoudiniEngineBakeOption : uint8; +enum class EPDGBakeSelectionOption : uint8; +enum class EPDGBakePackageReplaceModeOption : uint8; +enum class EPackageReplaceMode : int8; + +class HOUDINIENGINEEDITOR_API FHoudiniEngineEditor : public IHoudiniEngineEditor +{ + public: + FHoudiniEngineEditor(); + + // IModuleInterface methods. + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // IHoudiniEngineEditor methods + virtual void RegisterComponentVisualizers() override; + virtual void UnregisterComponentVisualizers() override; + virtual void RegisterDetails() override; + virtual void UnregisterDetails() override; + virtual void RegisterAssetTypeActions() override; + virtual void UnregisterAssetTypeActions() override; + virtual void RegisterAssetBrokers() override; + virtual void UnregisterAssetBrokers() override; + virtual void RegisterActorFactories() override; + virtual void ExtendMenu() override; + virtual void RegisterForUndo() override; + virtual void UnregisterForUndo() override; + virtual void RegisterPlacementModeExtensions() override; + virtual void UnregisterPlacementModeExtensions() override; + + // Return singleton instance of Houdini Engine Editor, used internally. + static FHoudiniEngineEditor & Get(); + + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // Returns the plugin's directory + static FString GetHoudiniEnginePluginDir(); + + // Initializes Widget resources + void InitializeWidgetResource(); + + // Menu action to pause cooking for all Houdini Assets + void PauseAssetCooking(); + + // Helper delegate used to determine if PauseAssetCooking can be executed. + bool CanPauseAssetCooking(); + + // Helper delegate used to get the current state of PauseAssetCooking. + bool IsAssetCookingPaused(); + + // Returns a pointer to the input choice types + TArray>* GetInputTypeChoiceLabels() { return &InputTypeChoiceLabels; }; + TArray>* GetBlueprintInputTypeChoiceLabels() { return &BlueprintInputTypeChoiceLabels; }; + + // Returns a pointer to the Houdini curve types + TArray>* GetHoudiniCurveTypeChoiceLabels() { return &HoudiniCurveTypeChoiceLabels; }; + + // Returns a pointer to the Houdini curve methods + TArray>* GetHoudiniCurveMethodChoiceLabels() { return &HoudiniCurveMethodChoiceLabels; }; + + // Returns a pointer to the Houdini ramp parameter interpolation methods + TArray>* GetHoudiniParameterRampInterpolationMethodLabels() {return &HoudiniParameterRampInterpolationLabels;} + + // Returns a pointer to the Houdini curve output export types + TArray>* GetHoudiniCurveOutputExportTypeLabels() { return &HoudiniCurveOutputExportTypeLabels; }; + + TArray>* GetHoudiniLandscapeOutputBakeOptionsLabels() { return &HoudiniLandscapeOutputBakeOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Type labels + TArray>* GetHoudiniEnginePDGBakeTypeOptionsLabels() { return &HoudiniEnginePDGBakeTypeOptionLabels; }; + + // Returns a pointer to the Houdini Engine Bake Type labels + TArray>* GetHoudiniEngineBakeTypeOptionsLabels() { return &HoudiniEngineBakeTypeOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Target labels + TArray>* GetHoudiniEnginePDGBakeSelectionOptionsLabels() { return &HoudiniEnginePDGBakeSelectionOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Package Replace Mode labels + TArray>* GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels() { return &HoudiniEnginePDGBakePackageReplaceModeOptionLabels; }; + + // Returns a shared Ptr to the Houdini logo + TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; + TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; + + // Functions Return a shared Ptr to the Houdini Engine UI Icon + TSharedPtr GetHoudiniEngineUIIconBrush() const { return HoudiniEngineUIIconBrush; } + TSharedPtr GetHoudiniEngineUIRebuildIconBrush() const { return HoudiniEngineUIRebuildIconBrush; } + TSharedPtr GetHoudiniEngineUIRecookIconBrush() const { return HoudiniEngineUIRecookIconBrush; } + TSharedPtr GetHoudiniEngineUIResetParametersIconBrush() const { return HoudiniEngineUIResetParametersIconBrush; } + + TSharedPtr GetHoudiniEngineUIBakeIconBrush() const { return HoudiniEngineUIBakeIconBrush; } + TSharedPtr GetHoudiniEngineUICookLogIconBrush() const { return HoudiniEngineUICookLogIconBrush; } + TSharedPtr GetHoudiniEngineUIAssetHelpIconBrush() const { return HoudiniEngineUIAssetHelpIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGIconBrush() const { return HoudiniEngineUIPDGIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGCancelIconBrush() const { return HoudiniEngineUIPDGCancelIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGDirtyAllIconBrush() const { return HoudiniEngineUIPDGDirtyAllIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGDirtyNodeIconBrush() const { return HoudiniEngineUIPDGDirtyNodeIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGPauseIconBrush() const { return HoudiniEngineUIPDGPauseIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGResetIconBrush() const { return HoudiniEngineUIPDGResetIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGRefreshIconBrush() const { return HoudiniEngineUIPDGRefreshIconBrush; } + + // Returns a pointer to Unreal output curve types (for temporary) + TArray>* GetUnrealOutputCurveTypeLabels() { return &UnrealCurveOutputCurveTypeLabels; }; + + // returns string from Houdini Engine Bake Option + FString GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption); + + // returns string from Houdini Engine PDG Bake Target Option + FString GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption); + + // returns string from PDG package replace mode option + FString GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption); + + // Return HoudiniEngineBakeOption from FString + const EHoudiniEngineBakeOption StringToHoudiniEngineBakeOption(const FString & InString); + + // Return EPDGBakeSelectionOption from FString + const EPDGBakeSelectionOption StringToPDGBakeSelectionOption(const FString& InString); + + // Return EPDGBakePackageReplaceModeOption from FString + const EPDGBakePackageReplaceModeOption StringToPDGBakePackageReplaceModeOption(const FString & InString); + + // Convert EPDGBakePackageReplaceModeOption to EPackageReplaceMode + // TODO: perhaps EPackageReplaceMode can be moved to HoudiniEngineRuntime to avoid having both + // TODO: EPDGBakePackageReplaceModeOption and EPackageReplaceMode? + const EPackageReplaceMode PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode); + + // Get the reference of the radio button folder circle point arrays reference + TArray & GetHoudiniParameterRadioButtonPointsOuter() { return HoudiniParameterRadioButtonPointsOuter; }; + TArray & GetHoudiniParameterRadioButtonPointsInner() { return HoudiniParameterRadioButtonPointsInner; }; + + // Gets the PostSaveWorldOnceHandle + FDelegateHandle& GetOnPostSaveWorldOnceHandle() { return PostSaveWorldOnceHandle; } + + protected: + + // Binds the commands used by the menus + void BindMenuCommands(); + + // Register AssetType action. + void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef< IAssetTypeActions > Action); + + // Add menu extension for our module. + void AddHoudiniFileMenuExtension(FMenuBuilder& MenuBuilder); + + // Add the Houdini Engine editor menu + void AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder); + + // Add menu extension for our module. + void AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder); + + // Adds the custom Houdini Engine commands to the world outliner context menu + void AddLevelViewportMenuExtender(); + + // Removes the custom Houdini Engine commands to the world outliner context menu + void RemoveLevelViewportMenuExtender(); + + // Returns all the custom Houdini Engine commands for the world outliner context menu + TSharedRef GetLevelViewportContextMenuExtender( + const TSharedRef CommandList, const TArray InActors); + + // Register all console commands provided by this module + void RegisterConsoleCommands(); + + // Unregister all registered console commands provided by this module + void UnregisterConsoleCommands(); + + // Register for any FEditorDelegates that we are interested in, such as + // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds + void RegisterEditorDelegates(); + + // Deregister editor delegates + void UnregisterEditorDelegates(); + + // Process the OnDeleteActorsBegin call received from FEditorDelegates. + // Check if any AHoudiniAssetActors with PDG links are selected for deletion. If so, + // check if these still have temporary outputs and give the user to option to skip + // deleting the ones with temporary output. + void HandleOnDeleteActorsBegin(); + + // Re-select AHoudiniAssetActors that were deselected (to avoid deletion) by HandleOnDeleteActorsBegin + void HandleOnDeleteActorsEnd(); + + private: + + // Singleton instance of Houdini Engine Editor. + static FHoudiniEngineEditor * HoudiniEngineEditorInstance; + + // AssetType actions associated with Houdini asset. + TArray> AssetTypeActions; + + // Broker associated with Houdini asset. + TSharedPtr HoudiniAssetBroker; + + // Widget resources: Input Type combo box labels + TArray> InputTypeChoiceLabels; + TArray> BlueprintInputTypeChoiceLabels; + + // Widget resources: Houdini Curve Type combo box labels + TArray> HoudiniCurveTypeChoiceLabels; + + // Widget resources: Houdini Curve Method combo box labels + TArray> HoudiniCurveMethodChoiceLabels; + + // Widget resources: Houdini Ramp Interpolation method combo box labels + TArray> HoudiniParameterRampInterpolationLabels; + + // Widget resources: Houdini Curve Output type labels + TArray> HoudiniCurveOutputExportTypeLabels; + + // Widget resources: Unreal Curve type labels (for temporary, we need to figure out a way to access the output curve's info later) + TArray> UnrealCurveOutputCurveTypeLabels; + + // Widget resources: Landscape output Bake type labels + TArray> HoudiniLandscapeOutputBakeOptionLabels; + + // Widget resources: PDG Bake type labels + TArray> HoudiniEnginePDGBakeTypeOptionLabels; + + // Widget resources: Bake type labels + TArray> HoudiniEngineBakeTypeOptionLabels; + + // Widget resources: PDG Bake target labels + TArray> HoudiniEnginePDGBakeSelectionOptionLabels; + + // Widget resources: PDG Bake package replace mode labels + TArray> HoudiniEnginePDGBakePackageReplaceModeOptionLabels; + + // List of UI commands used by the various menus + TSharedPtr HEngineCommands; + + // Houdini logo brush. + TSharedPtr HoudiniLogoBrush; + // Houdini Engine logo brush + TSharedPtr HoudiniEngineLogoBrush; + + // houdini Engine UI Brushes + TSharedPtr HoudiniEngineUIIconBrush; + TSharedPtr HoudiniEngineUIRebuildIconBrush; + TSharedPtr HoudiniEngineUIRecookIconBrush; + TSharedPtr HoudiniEngineUIResetParametersIconBrush; + + TSharedPtr HoudiniEngineUIBakeIconBrush; + TSharedPtr HoudiniEngineUICookLogIconBrush; + TSharedPtr HoudiniEngineUIAssetHelpIconBrush; + TSharedPtr HoudiniEngineUIPDGIconBrush; + TSharedPtr HoudiniEngineUIPDGCancelIconBrush; + TSharedPtr HoudiniEngineUIPDGDirtyAllIconBrush; + TSharedPtr HoudiniEngineUIPDGDirtyNodeIconBrush; + TSharedPtr HoudiniEngineUIPDGPauseIconBrush; + TSharedPtr HoudiniEngineUIPDGResetIconBrush; + TSharedPtr HoudiniEngineUIPDGRefreshIconBrush; + + // The extender to pass to the level editor to extend it's File menu. + TSharedPtr MainMenuExtender; + + // The extender to pass to the level editor to extend it's Main menu. + //TSharedPtr FileMenuExtender; + + // DelegateHandle for the viewport's context menu extender + FDelegateHandle LevelViewportExtenderHandle; + + // SplineComponentVisualizer + TSharedPtr SplineComponentVisualizer; + + TSharedPtr HandleComponentVisualizer; + + // Array of HoudiniEngine console commands + TArray ConsoleCommands; + + // Delegate handle for the PreSaveWorld editor delegate + FDelegateHandle PreSaveWorldEditorDelegateHandle; + + // Delegate handle for the PostSaveWorld editor delegate: this + // is bound on PreSaveWorld with specific captures and then unbound + // by itself + FDelegateHandle PostSaveWorldOnceHandle; + + // Delegate handle for the PreBeginPIE editor delegate + FDelegateHandle PreBeginPIEEditorDelegateHandle; + + // Delegate handle for OnDeleteActorsBegin + FDelegateHandle OnDeleteActorsBegin; + + // Delegate handle for OnDeleteActorsEnd + FDelegateHandle OnDeleteActorsEnd; + + // List of actors that HandleOnDeleteActorsBegin marked to _not_ be deleted. This + // is used to re-select these actors in HandleOnDeleteActorsEnd. + TArray ActorsToReselectOnDeleteActorsEnd; + + // Cache the points of radio button folder circle points to avoid huge amount of repeat computation. + // (Computing points are time consuming since it uses trigonometric functions) + TArray HoudiniParameterRadioButtonPointsOuter; + TArray HoudiniParameterRadioButtonPointsInner; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h index b6765739d..a92998038 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h @@ -1,149 +1,149 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#define HOUDINI_ENGINE_EDITOR - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "Editor.h" - -// Details panel desired sizes. -#define HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH 270 -#define HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH 310 -#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH 350 -#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_FULL_WIDGET_WIDTH 400 - - // URL used for bug reporting. -#define HAPI_UNREAL_BUG_REPORT_URL TEXT("https://www.sidefx.com/bugs/submit/") -#define HAPI_UNREAL_ONLINE_DOC_URL TEXT("https://www.sidefx.com/docs/unreal/") -#define HAPI_UNREAL_ONLINE_FORUM_URL TEXT("https://www.sidefx.com/forum/51/") - - -// -// Parameter UI constants -// - -// Constants for parameter UI indentation - -// Change this constant to change the overall indentation width -#define INDENTATION_UNIT_WIDTH 20.0f -// Do not change this width unless the folder triangle arrow is customized. -#define NON_FOLDER_OFFSET_WIDTH 22.0f - - -// Houdini parameter UI row margin heights -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON 8.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP 2.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR 5.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP 57.20f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3 7.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP 51.70f -#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER 2.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST 2.05f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY 62.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE 49.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM 36.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE 41.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE 68.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET 177.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE 235.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH 275.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD 219.35f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL 18.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3 7.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE 7.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL 4.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR 1.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING 6.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF 6.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE 7.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE 10.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE 5.60f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID 0.0f - - - -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER 4.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER 2.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER 2.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_MULTIPARMHEADER 57.20f -#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE_MULTIPARMHEADER 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER 2.60f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER 3.75f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_MULTIPARMHEADER 51.70f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE_MULTIPARMHEADER 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER 1.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER 58.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER 49.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER 40.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER 37.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER 68.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER 173.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER 231.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER 266.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER 215.05f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER 14.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER 2.57f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER 4.12f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER 4.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER 0.75f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER 1.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER 2.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER 2.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER 4.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER 10.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER 2.30f - -// Radio button UI constants -#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER 18 -#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER 8 -#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER 4.5f -#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER 1.0f -#define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X 7.0f +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#define HOUDINI_ENGINE_EDITOR + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Editor.h" + +// Details panel desired sizes. +#define HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH 270 +#define HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH 310 +#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH 350 +#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_FULL_WIDGET_WIDTH 400 + + // URL used for bug reporting. +#define HAPI_UNREAL_BUG_REPORT_URL TEXT("https://www.sidefx.com/bugs/submit/") +#define HAPI_UNREAL_ONLINE_DOC_URL TEXT("https://www.sidefx.com/docs/unreal/") +#define HAPI_UNREAL_ONLINE_FORUM_URL TEXT("https://www.sidefx.com/forum/51/") + + +// +// Parameter UI constants +// + +// Constants for parameter UI indentation + +// Change this constant to change the overall indentation width +#define INDENTATION_UNIT_WIDTH 20.0f +// Do not change this width unless the folder triangle arrow is customized. +#define NON_FOLDER_OFFSET_WIDTH 22.0f + + +// Houdini parameter UI row margin heights +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON 8.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP 2.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR 5.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP 57.20f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3 7.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP 51.70f +#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER 2.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST 2.05f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY 62.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE 49.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM 36.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE 41.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE 68.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET 177.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE 235.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH 275.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD 219.35f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL 18.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3 7.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE 7.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL 4.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR 1.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING 6.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF 6.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE 7.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE 10.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE 5.60f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID 0.0f + + + +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER 4.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER 2.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER 2.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_MULTIPARMHEADER 57.20f +#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE_MULTIPARMHEADER 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER 2.60f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER 3.75f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_MULTIPARMHEADER 51.70f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE_MULTIPARMHEADER 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER 1.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER 58.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER 49.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER 40.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER 37.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER 68.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER 173.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER 231.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER 266.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER 215.05f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER 14.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER 2.57f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER 4.12f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER 4.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER 0.75f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER 1.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER 2.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER 2.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER 4.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER 10.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER 2.30f + +// Radio button UI constants +#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER 18 +#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER 8 +#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER 4.5f +#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER 1.0f +#define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X 7.0f #define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_Y 13.2f \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp index c22bc9227..b1f3e3212 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp @@ -1,652 +1,666 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineEditorUtils.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniAssetActor.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniAsset.h" -#include "HoudiniOutput.h" -#include "HoudiniTool.h" - -#include "ContentBrowserModule.h" -#include "IContentBrowserSingleton.h" -#include "Engine/Selection.h" -#include "AssetRegistryModule.h" -#include "EditorViewportClient.h" -#include "ActorFactories/ActorFactory.h" -#include "FileHelpers.h" -#include "PropertyPathHelpers.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -int32 -FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection) -{ - ContentBrowserSelection.Empty(); - - // Get the current Content browser selection - FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >("ContentBrowser"); - TArray SelectedAssets; - ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets); - - for (int32 n = 0; n < SelectedAssets.Num(); n++) - { - // Get the current object - UObject * Object = SelectedAssets[n].GetAsset(); - if (!Object || Object->IsPendingKill()) - continue; - - // Only static meshes are supported - if (Object->GetClass() != UStaticMesh::StaticClass()) - continue; - - ContentBrowserSelection.Add(Object); - } - - return ContentBrowserSelection.Num(); -} - -int32 -FHoudiniEngineEditorUtils::GetWorldSelection(TArray& WorldSelection, bool bHoudiniAssetActorsOnly) -{ - WorldSelection.Empty(); - - // Get the current editor selection - if (GEditor) - { - USelection* SelectedActors = GEditor->GetSelectedActors(); - if (SelectedActors && !SelectedActors->IsPendingKill()) - { - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor * Actor = Cast(*It); - if (!IsValid(Actor)) - continue; - - // Ignore the SkySphere? - FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); - if (ClassName == TEXT("BP_Sky_Sphere_C")) - continue; - - // We're normally only selecting actors with StaticMeshComponents and SplineComponents - // Heightfields? Filter here or later? also allow HoudiniAssets? - WorldSelection.Add(Actor); - } - } - } - - // If we only want Houdini Actors... - if (bHoudiniAssetActorsOnly) - { - // ... remove all but them - for (int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx--) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - WorldSelection.RemoveAt(Idx); - } - } - - return WorldSelection.Num(); -} - - -FString -FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType) -{ - FString HoudiniCurveTypeStr; - switch (HoudiniCurveType) - { - case EHoudiniCurveType::Invalid: - { - HoudiniCurveTypeStr = TEXT("Invalid"); - } - break; - - case EHoudiniCurveType::Polygon: - { - HoudiniCurveTypeStr = TEXT("Polygon"); - } - break; - - case EHoudiniCurveType::Nurbs: - { - HoudiniCurveTypeStr = TEXT("Nurbs"); - } - break; - - case EHoudiniCurveType::Bezier: - { - HoudiniCurveTypeStr = TEXT("Bezier"); - } - break; - - case EHoudiniCurveType::Points: - { - HoudiniCurveTypeStr = TEXT("Points"); - } - break; - } - - return HoudiniCurveTypeStr; -} - -FString -FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(const EHoudiniCurveMethod& CurveMethod) -{ - FString HoudiniCurveMethodStr; - switch (CurveMethod) - { - case EHoudiniCurveMethod::Invalid: - { - HoudiniCurveMethodStr = TEXT("Invalid"); - } - break; - case EHoudiniCurveMethod::CVs: - { - HoudiniCurveMethodStr = TEXT("CVs"); - } - break; - case EHoudiniCurveMethod::Breakpoints: - { - HoudiniCurveMethodStr = TEXT("Breakpoints"); - } - break; - case EHoudiniCurveMethod::Freehand: - { - HoudiniCurveMethodStr = TEXT("Freehand"); - } - break; - } - - return HoudiniCurveMethodStr; -} - - -FString -FHoudiniEngineEditorUtils::HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType) -{ - // Temporary, we need to figure out a way to access the output curve's info later - FString UnrealCurveType; - switch (HoudiniCurveType) - { - case EHoudiniCurveType::Polygon: - case EHoudiniCurveType::Points: - { - UnrealCurveType = TEXT("Linear"); - } - break; - - case EHoudiniCurveType::Nurbs: - case EHoudiniCurveType::Bezier: - { - UnrealCurveType = TEXT("Curve"); - } - break; - } - - return UnrealCurveType; -} - -FString -FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType) -{ - FString LandscapeBakeTypeString; - switch (LandscapeBakeType) - { - case EHoudiniLandscapeOutputBakeType::Detachment: - LandscapeBakeTypeString = "To Current Level"; - break; - - case EHoudiniLandscapeOutputBakeType::BakeToImage: - LandscapeBakeTypeString = "To Image"; - break; - - case EHoudiniLandscapeOutputBakeType::BakeToWorld: - LandscapeBakeTypeString = "To New World"; - break; - - } - - return LandscapeBakeTypeString; -} - - -FTransform -FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform() -{ - FTransform SpawnTransform = FTransform::Identity; - - // Get the editor viewport LookAt position to spawn the new objects - if (GEditor && GEditor->GetActiveViewport()) - { - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (ViewportClient) - { - // We need to toggle the orbit camera on to get the proper LookAtLocation to spawn our asset - ViewportClient->ToggleOrbitCamera(true); - SpawnTransform.SetLocation(ViewportClient->GetLookAtLocation()); - ViewportClient->ToggleOrbitCamera(false); - } - } - - return SpawnTransform; -} - -FTransform -FHoudiniEngineEditorUtils::GetMeanWorldSelectionTransform() -{ - FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); - - if (GEditor && (GEditor->GetSelectedActorCount() > 0)) - { - // Get the current Level Editor Selection - USelection* SelectedActors = GEditor->GetSelectedActors(); - - int NumAppliedTransform = 0; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor * Actor = Cast< AActor >(*It); - if (!Actor) - continue; - - // Just Ignore the SkySphere... - FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); - if (ClassName == TEXT("BP_Sky_Sphere_C")) - continue; - - FTransform CurrentTransform = Actor->GetTransform(); - - ALandscapeProxy* Landscape = Cast< ALandscapeProxy >(Actor); - if (Landscape) - { - // We need to offset Landscape's transform in X/Y to center them properly - FVector Origin, Extent; - Actor->GetActorBounds(false, Origin, Extent); - - // Use the origin's XY Position - FVector Location = CurrentTransform.GetLocation(); - Location.X = Origin.X; - Location.Y = Origin.Y; - CurrentTransform.SetLocation(Location); - } - - // Accumulate all the actor transforms... - if (NumAppliedTransform == 0) - SpawnTransform = CurrentTransform; - else - SpawnTransform.Accumulate(CurrentTransform); - - NumAppliedTransform++; - } - - if (NumAppliedTransform > 0) - { - // "Mean" all the accumulated Transform - SpawnTransform.SetScale3D(FVector::OneVector); - SpawnTransform.NormalizeRotation(); - - if (NumAppliedTransform > 1) - SpawnTransform.SetLocation(SpawnTransform.GetLocation() / (float)NumAppliedTransform); - } - } - - return SpawnTransform; -} - -void -FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType) -{ - if (!InHoudiniAsset) - return; - - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - // Load the asset - UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); - if (!AssetObj) - return; - - // Get the asset Factory - UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); - if (!Factory) - return; - - // Get the current Level Editor Selection - TArray WorldSelection; - int32 WorldSelectionCount = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection); - - // Get the current Content browser selection - TArray ContentBrowserSelection; - int32 ContentBrowserSelectionCount = FHoudiniEngineEditorUtils::GetContentBrowserSelection(ContentBrowserSelection); - - // By default, Content browser selection has a priority over the world selection - bool UseCBSelection = ContentBrowserSelectionCount > 0; - if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY) - UseCBSelection = true; - else if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY) - UseCBSelection = false; - - // Modify the created actor's position from the current editor world selection - FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); - if (WorldSelectionCount > 0) - { - // Get the "mean" transform of all the selected actors - SpawnTransform = GetMeanWorldSelectionTransform(); - } - - // If the current tool is a batch one, we'll need to create multiple instances of the HDA - if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH) - { - // Unselect the current selection to select the created actor after - if (GEditor) - GEditor->SelectNone(true, true, false); - - // An instance of the asset will be created for each selected object - for (int32 SelecIndex = 0; SelecIndex < (UseCBSelection ? ContentBrowserSelectionCount : WorldSelectionCount); SelecIndex++) - { - // Get the current object - UObject* CurrentSelectedObject = nullptr; - if (UseCBSelection && ContentBrowserSelection.IsValidIndex(SelecIndex)) - CurrentSelectedObject = ContentBrowserSelection[SelecIndex]; - - if (!UseCBSelection && WorldSelection.IsValidIndex(SelecIndex)) - CurrentSelectedObject = WorldSelection[SelecIndex]; - - if (!CurrentSelectedObject) - continue; - - // If it's an actor, use its Transform to spawn the HDA - AActor* CurrentSelectedActor = Cast(CurrentSelectedObject); - if (CurrentSelectedActor) - SpawnTransform = CurrentSelectedActor->GetTransform(); - else - SpawnTransform = GetDefaulAssetSpawnTransform(); - - // Create the actor for the HDA - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); - if (!CreatedActor) - continue; - - // Get the HoudiniAssetActor / HoudiniAssetComponent we just created - AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; - if (!HoudiniAssetActor) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent) - continue; - - // Create and set the input preset for this HDA and selected Object - TMap InputPreset; - InputPreset.Add(CurrentSelectedObject, 0); - HoudiniAssetComponent->SetInputPresets(InputPreset); - - // Select the Actor we just created - if (GEditor && GEditor->CanSelectActor(CreatedActor, true, false)) - GEditor->SelectActor(CreatedActor, true, true, true); - } - } - else - { - // We only need to create a single instance of the asset, regarding of the selection - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); - if (!CreatedActor) - return; - - // Generator tools don't need to preset their input - if (InType != EHoudiniToolType::HTOOLTYPE_GENERATOR) - { - TMap InputPresets; - AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor ? HoudiniAssetActor->GetHoudiniAssetComponent() : nullptr; - if (HoudiniAssetComponent) - { - // Build the preset map - int InputIndex = 0; - for (auto CurrentObject : (UseCBSelection ? ContentBrowserSelection : WorldSelection)) - { - if (!CurrentObject) - continue; - - if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI) - { - // The selection will be applied individually to multiple inputs - // (first object to first input, second object to second input etc...) - InputPresets.Add(CurrentObject, InputIndex++); - } - else - { - // All the selection will be applied to the asset's first input - InputPresets.Add(CurrentObject, 0); - } - } - - // Set the input preset on the HoudiniAssetComponent - if (InputPresets.Num() > 0) - HoudiniAssetComponent->SetInputPresets(InputPresets); - } - } - - // Select the Actor we just created - if (GEditor->CanSelectActor(CreatedActor, true, true)) - { - GEditor->SelectNone(true, true, false); - GEditor->SelectActor(CreatedActor, true, true, true); - } - } -} - -void -FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform) -{ - if (!InHoudiniAsset) - return; - - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - // Load the asset - UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); - if (!AssetObj) - return; - - // Get the asset Factory - UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); - if (!Factory) - return; - - // We only need to create a single instance of the asset, regarding of the selection - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), InTransform); - if (!CreatedActor) - return; - - // Select the Actor we just created - if (GEditor->CanSelectActor(CreatedActor, true, true)) - { - GEditor->SelectNone(true, true, false); - GEditor->SelectActor(CreatedActor, true, true, true); - } -} - - -void -FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) -{ - // Add a slate notification - FString Notification = TEXT("Saving all Houdini temporary cook data..."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - - TArray PackagesToSave; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HAC = *Itr; - if (!HAC || HAC->IsPendingKill()) - continue; - - if (InSaveWorld && InSaveWorld != HAC->GetWorld()) - continue; - - const int32 NumOutputs = HAC->GetNumOutputs(); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - // TODO: Also save landscape layer info objects? - if (Output->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (auto &OutputObjectPair : Output->GetOutputObjects()) - { - UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) - continue; - - UStaticMesh *SM = Cast(Obj); - if (!SM) - continue; - - UPackage *Package = SM->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - - for (auto& MaterialAssignementPair : Output->GetAssignementMaterials()) - { - UMaterialInterface* MatInterface = MaterialAssignementPair.Value; - if (!MatInterface || MatInterface->IsPendingKill()) - continue; - - UPackage *Package = MatInterface->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - } - } - - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); -} - -void -FHoudiniEngineEditorUtils::ReselectSelectedActors() -{ - // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? - USelection* Selection = GEditor->GetSelectedActors(); - TArray SelectedActors; - SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); - Selection->GetSelectedObjects(SelectedActors); - - GEditor->SelectNone(false, false, false); - - for (AActor* NextSelected : SelectedActors) - { - GEditor->SelectActor(NextSelected, true, true, true, true); - } -} - -FString -FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding, const TCHAR PathSep) -{ - int32 Depth = 0; - for (const TCHAR Char : InNodePath) - { - if (Char == PathSep) - Depth++; - } - FString Trimmed = InNodeName; - Trimmed.TrimStartInline(); - return Trimmed.LeftPad(Trimmed.Len() + (Depth * Padding)); -} - -void -FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject) -{ - if (!IsValid(InRootObject)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty]: InRootObject is null.")); - return; - } - - const FCachedPropertyPath CachedPath(InPropertyPath.ToString()); - if (CachedPath.Resolve(InRootObject)) - { - // Notify that we have changed the property - // FPropertyChangedEvent Evt = CachedPath.ToPropertyChangedEvent(EPropertyChangeType::Unspecified); - // Construct FPropertyChangedEvent from the cached property path - const int32 NumSegments = CachedPath.GetNumSegments(); - FPropertyChangedEvent Evt( - CastFieldChecked(CachedPath.GetLastSegment().GetField().ToField()), - EPropertyChangeType::Unspecified, - { InRootObject }); - - if(NumSegments > 1) - { - Evt.SetActiveMemberProperty(CastFieldChecked(CachedPath.GetSegment(NumSegments - 2).GetField().ToField())); - } - - // Set the array of indices to the changed property - TArray> ArrayIndicesPerObject; - ArrayIndicesPerObject.AddDefaulted(1); - for (int32 SegmentIdx = 0; SegmentIdx < NumSegments; ++SegmentIdx) - { - const FPropertyPathSegment& Segment = CachedPath.GetSegment(SegmentIdx); - const int32 ArrayIndex = Segment.GetArrayIndex(); - if (ArrayIndex != INDEX_NONE) - { - ArrayIndicesPerObject[0].Add(Segment.GetName().ToString(), ArrayIndex); - } - } - Evt.SetArrayIndexPerObject(ArrayIndicesPerObject); - - FEditPropertyChain Chain; - CachedPath.ToEditPropertyChain(Chain); - FPropertyChangedChainEvent ChainEvent(Chain, Evt); - ChainEvent.ObjectIteratorIndex = 0; - InRootObject->PostEditChangeChainProperty(ChainEvent); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could not resolve property path '%s' on %s."), *InPropertyPath.ToString(), *(InRootObject->GetFullName())); - } -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineEditorUtils.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniAssetActor.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniAsset.h" +#include "HoudiniOutput.h" +#include "HoudiniTool.h" + +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" +#include "Engine/Selection.h" +#include "AssetRegistryModule.h" +#include "EditorViewportClient.h" +#include "ActorFactories/ActorFactory.h" +#include "FileHelpers.h" +#include "PropertyPathHelpers.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +int32 +FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection) +{ + ContentBrowserSelection.Empty(); + + // Get the current Content browser selection + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >("ContentBrowser"); + TArray SelectedAssets; + ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets); + + for (int32 n = 0; n < SelectedAssets.Num(); n++) + { + // Get the current object + UObject * Object = SelectedAssets[n].GetAsset(); + if (!Object || Object->IsPendingKill()) + continue; + + // Only static meshes are supported + if (Object->GetClass() != UStaticMesh::StaticClass()) + continue; + + ContentBrowserSelection.Add(Object); + } + + return ContentBrowserSelection.Num(); +} + +int32 +FHoudiniEngineEditorUtils::GetWorldSelection(TArray& WorldSelection, bool bHoudiniAssetActorsOnly) +{ + WorldSelection.Empty(); + + // Get the current editor selection + if (GEditor) + { + USelection* SelectedActors = GEditor->GetSelectedActors(); + if (SelectedActors && !SelectedActors->IsPendingKill()) + { + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor * Actor = Cast(*It); + if (!IsValid(Actor)) + continue; + + // Ignore the SkySphere? + FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); + if (ClassName == TEXT("BP_Sky_Sphere_C")) + continue; + + // We're normally only selecting actors with StaticMeshComponents and SplineComponents + // Heightfields? Filter here or later? also allow HoudiniAssets? + WorldSelection.Add(Actor); + } + } + } + + // If we only want Houdini Actors... + if (bHoudiniAssetActorsOnly) + { + // ... remove all but them + for (int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx--) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + WorldSelection.RemoveAt(Idx); + } + } + + return WorldSelection.Num(); +} + + +FString +FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType) +{ + FString HoudiniCurveTypeStr; + switch (HoudiniCurveType) + { + case EHoudiniCurveType::Invalid: + { + HoudiniCurveTypeStr = TEXT("Invalid"); + } + break; + + case EHoudiniCurveType::Polygon: + { + HoudiniCurveTypeStr = TEXT("Polygon"); + } + break; + + case EHoudiniCurveType::Nurbs: + { + HoudiniCurveTypeStr = TEXT("Nurbs"); + } + break; + + case EHoudiniCurveType::Bezier: + { + HoudiniCurveTypeStr = TEXT("Bezier"); + } + break; + + case EHoudiniCurveType::Points: + { + HoudiniCurveTypeStr = TEXT("Points"); + } + break; + } + + return HoudiniCurveTypeStr; +} + +FString +FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(const EHoudiniCurveMethod& CurveMethod) +{ + FString HoudiniCurveMethodStr; + switch (CurveMethod) + { + case EHoudiniCurveMethod::Invalid: + { + HoudiniCurveMethodStr = TEXT("Invalid"); + } + break; + case EHoudiniCurveMethod::CVs: + { + HoudiniCurveMethodStr = TEXT("CVs"); + } + break; + case EHoudiniCurveMethod::Breakpoints: + { + HoudiniCurveMethodStr = TEXT("Breakpoints"); + } + break; + case EHoudiniCurveMethod::Freehand: + { + HoudiniCurveMethodStr = TEXT("Freehand"); + } + break; + } + + return HoudiniCurveMethodStr; +} + + +FString +FHoudiniEngineEditorUtils::HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType) +{ + // Temporary, we need to figure out a way to access the output curve's info later + FString UnrealCurveType; + switch (HoudiniCurveType) + { + case EHoudiniCurveType::Polygon: + case EHoudiniCurveType::Points: + { + UnrealCurveType = TEXT("Linear"); + } + break; + + case EHoudiniCurveType::Nurbs: + case EHoudiniCurveType::Bezier: + { + UnrealCurveType = TEXT("Curve"); + } + break; + } + + return UnrealCurveType; +} + +FString +FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType) +{ + FString LandscapeBakeTypeString; + switch (LandscapeBakeType) + { + case EHoudiniLandscapeOutputBakeType::Detachment: + LandscapeBakeTypeString = "To Current Level"; + break; + + case EHoudiniLandscapeOutputBakeType::BakeToImage: + LandscapeBakeTypeString = "To Image"; + break; + + case EHoudiniLandscapeOutputBakeType::BakeToWorld: + LandscapeBakeTypeString = "To New World"; + break; + + } + + return LandscapeBakeTypeString; +} + + +FTransform +FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform() +{ + FTransform SpawnTransform = FTransform::Identity; + + // Get the editor viewport LookAt position to spawn the new objects + if (GEditor && GEditor->GetActiveViewport()) + { + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (ViewportClient) + { + // We need to toggle the orbit camera on to get the proper LookAtLocation to spawn our asset + ViewportClient->ToggleOrbitCamera(true); + SpawnTransform.SetLocation(ViewportClient->GetLookAtLocation()); + ViewportClient->ToggleOrbitCamera(false); + } + } + + return SpawnTransform; +} + +FTransform +FHoudiniEngineEditorUtils::GetMeanWorldSelectionTransform() +{ + FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); + + if (GEditor && (GEditor->GetSelectedActorCount() > 0)) + { + // Get the current Level Editor Selection + USelection* SelectedActors = GEditor->GetSelectedActors(); + + int NumAppliedTransform = 0; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor * Actor = Cast< AActor >(*It); + if (!Actor) + continue; + + // Just Ignore the SkySphere... + FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); + if (ClassName == TEXT("BP_Sky_Sphere_C")) + continue; + + FTransform CurrentTransform = Actor->GetTransform(); + + ALandscapeProxy* Landscape = Cast< ALandscapeProxy >(Actor); + if (Landscape) + { + // We need to offset Landscape's transform in X/Y to center them properly + FVector Origin, Extent; + Actor->GetActorBounds(false, Origin, Extent); + + // Use the origin's XY Position + FVector Location = CurrentTransform.GetLocation(); + Location.X = Origin.X; + Location.Y = Origin.Y; + CurrentTransform.SetLocation(Location); + } + + // Accumulate all the actor transforms... + if (NumAppliedTransform == 0) + SpawnTransform = CurrentTransform; + else + SpawnTransform.Accumulate(CurrentTransform); + + NumAppliedTransform++; + } + + if (NumAppliedTransform > 0) + { + // "Mean" all the accumulated Transform + SpawnTransform.SetScale3D(FVector::OneVector); + SpawnTransform.NormalizeRotation(); + + if (NumAppliedTransform > 1) + SpawnTransform.SetLocation(SpawnTransform.GetLocation() / (float)NumAppliedTransform); + } + } + + return SpawnTransform; +} + +void +FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType) +{ + if (!InHoudiniAsset) + return; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + // Load the asset + UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); + if (!AssetObj) + return; + + // Get the asset Factory + UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); + if (!Factory) + return; + + // Get the current Level Editor Selection + TArray WorldSelection; + int32 WorldSelectionCount = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection); + + // Get the current Content browser selection + TArray ContentBrowserSelection; + int32 ContentBrowserSelectionCount = FHoudiniEngineEditorUtils::GetContentBrowserSelection(ContentBrowserSelection); + + // By default, Content browser selection has a priority over the world selection + bool UseCBSelection = ContentBrowserSelectionCount > 0; + if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY) + UseCBSelection = true; + else if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY) + UseCBSelection = false; + + // Modify the created actor's position from the current editor world selection + FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); + if (WorldSelectionCount > 0) + { + // Get the "mean" transform of all the selected actors + SpawnTransform = GetMeanWorldSelectionTransform(); + } + + // If the current tool is a batch one, we'll need to create multiple instances of the HDA + if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH) + { + // Unselect the current selection to select the created actor after + if (GEditor) + GEditor->SelectNone(true, true, false); + + // An instance of the asset will be created for each selected object + for (int32 SelecIndex = 0; SelecIndex < (UseCBSelection ? ContentBrowserSelectionCount : WorldSelectionCount); SelecIndex++) + { + // Get the current object + UObject* CurrentSelectedObject = nullptr; + if (UseCBSelection && ContentBrowserSelection.IsValidIndex(SelecIndex)) + CurrentSelectedObject = ContentBrowserSelection[SelecIndex]; + + if (!UseCBSelection && WorldSelection.IsValidIndex(SelecIndex)) + CurrentSelectedObject = WorldSelection[SelecIndex]; + + if (!CurrentSelectedObject) + continue; + + // If it's an actor, use its Transform to spawn the HDA + AActor* CurrentSelectedActor = Cast(CurrentSelectedObject); + if (CurrentSelectedActor) + SpawnTransform = CurrentSelectedActor->GetTransform(); + else + SpawnTransform = GetDefaulAssetSpawnTransform(); + + // Create the actor for the HDA + AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); + if (!CreatedActor) + continue; + + // Get the HoudiniAssetActor / HoudiniAssetComponent we just created + AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; + if (!HoudiniAssetActor) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent) + continue; + + // Create and set the input preset for this HDA and selected Object + TMap InputPreset; + InputPreset.Add(CurrentSelectedObject, 0); + HoudiniAssetComponent->SetInputPresets(InputPreset); + + // Select the Actor we just created + if (GEditor && GEditor->CanSelectActor(CreatedActor, true, false)) + GEditor->SelectActor(CreatedActor, true, true, true); + } + } + else + { + // We only need to create a single instance of the asset, regarding of the selection + AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); + if (!CreatedActor) + return; + + // Generator tools don't need to preset their input + if (InType != EHoudiniToolType::HTOOLTYPE_GENERATOR) + { + TMap InputPresets; + AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor ? HoudiniAssetActor->GetHoudiniAssetComponent() : nullptr; + if (HoudiniAssetComponent) + { + // Build the preset map + int InputIndex = 0; + for (auto CurrentObject : (UseCBSelection ? ContentBrowserSelection : WorldSelection)) + { + if (!CurrentObject) + continue; + + if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI) + { + // The selection will be applied individually to multiple inputs + // (first object to first input, second object to second input etc...) + InputPresets.Add(CurrentObject, InputIndex++); + } + else + { + // All the selection will be applied to the asset's first input + InputPresets.Add(CurrentObject, 0); + } + } + + // Set the input preset on the HoudiniAssetComponent + if (InputPresets.Num() > 0) + HoudiniAssetComponent->SetInputPresets(InputPresets); + } + } + + // Select the Actor we just created + if (GEditor->CanSelectActor(CreatedActor, true, true)) + { + GEditor->SelectNone(true, true, false); + GEditor->SelectActor(CreatedActor, true, true, true); + } + } +} + +AActor* +FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform, UWorld* InSpawnInWorld, ULevel* InSpawnInLevelOverride) +{ + if (!InHoudiniAsset) + return nullptr; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + // Load the asset + UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); + if (!AssetObj) + return nullptr; + + // Get the asset Factory + UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); + if (!Factory) + return nullptr; + + // Determine the level to spawn in from the supplied parameters + // InSpawnInLevelOverride if valid, else if InSpawnInWorld is valid its current level + // lastly, get the editor world's current level + ULevel* LevelToSpawnIn = InSpawnInLevelOverride; + if (!IsValid(LevelToSpawnIn)) + { + if (IsValid(InSpawnInWorld)) + LevelToSpawnIn = InSpawnInWorld->GetCurrentLevel(); + else + LevelToSpawnIn = GEditor->GetEditorWorldContext().World()->GetCurrentLevel(); + } + + // We only need to create a single instance of the asset, regarding of the selection + AActor* CreatedActor = Factory->CreateActor(AssetObj, LevelToSpawnIn, InTransform); + if (!CreatedActor) + return nullptr; + + // Select the Actor we just created + if (GEditor->CanSelectActor(CreatedActor, true, true)) + { + GEditor->SelectNone(true, true, false); + GEditor->SelectActor(CreatedActor, true, true, true); + } + + return CreatedActor; +} + + +void +FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) +{ + // Add a slate notification + FString Notification = TEXT("Saving all Houdini temporary cook data..."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + + TArray PackagesToSave; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HAC = *Itr; + if (!HAC || HAC->IsPendingKill()) + continue; + + if (InSaveWorld && InSaveWorld != HAC->GetWorld()) + continue; + + const int32 NumOutputs = HAC->GetNumOutputs(); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + // TODO: Also save landscape layer info objects? + if (Output->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (auto &OutputObjectPair : Output->GetOutputObjects()) + { + UObject *Obj = OutputObjectPair.Value.OutputObject; + if (!Obj || Obj->IsPendingKill()) + continue; + + UStaticMesh *SM = Cast(Obj); + if (!SM) + continue; + + UPackage *Package = SM->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + + for (auto& MaterialAssignementPair : Output->GetAssignementMaterials()) + { + UMaterialInterface* MatInterface = MaterialAssignementPair.Value; + if (!MatInterface || MatInterface->IsPendingKill()) + continue; + + UPackage *Package = MatInterface->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + } + } + + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); +} + +void +FHoudiniEngineEditorUtils::ReselectSelectedActors() +{ + // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? + USelection* Selection = GEditor->GetSelectedActors(); + TArray SelectedActors; + SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); + Selection->GetSelectedObjects(SelectedActors); + + GEditor->SelectNone(false, false, false); + + for (AActor* NextSelected : SelectedActors) + { + GEditor->SelectActor(NextSelected, true, true, true, true); + } +} + +FString +FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding, const TCHAR PathSep) +{ + int32 Depth = 0; + for (const TCHAR Char : InNodePath) + { + if (Char == PathSep) + Depth++; + } + FString Trimmed = InNodeName; + Trimmed.TrimStartInline(); + return Trimmed.LeftPad(Trimmed.Len() + (Depth * Padding)); +} + +void +FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject) +{ + if (!IsValid(InRootObject)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty]: InRootObject is null.")); + return; + } + + const FCachedPropertyPath CachedPath(InPropertyPath.ToString()); + if (CachedPath.Resolve(InRootObject)) + { + // Notify that we have changed the property + // FPropertyChangedEvent Evt = CachedPath.ToPropertyChangedEvent(EPropertyChangeType::Unspecified); + // Construct FPropertyChangedEvent from the cached property path + const int32 NumSegments = CachedPath.GetNumSegments(); + FPropertyChangedEvent Evt( + CastFieldChecked(CachedPath.GetLastSegment().GetField().ToField()), + EPropertyChangeType::Unspecified, + { InRootObject }); + + if(NumSegments > 1) + { + Evt.SetActiveMemberProperty(CastFieldChecked(CachedPath.GetSegment(NumSegments - 2).GetField().ToField())); + } + + // Set the array of indices to the changed property + TArray> ArrayIndicesPerObject; + ArrayIndicesPerObject.AddDefaulted(1); + for (int32 SegmentIdx = 0; SegmentIdx < NumSegments; ++SegmentIdx) + { + const FPropertyPathSegment& Segment = CachedPath.GetSegment(SegmentIdx); + const int32 ArrayIndex = Segment.GetArrayIndex(); + if (ArrayIndex != INDEX_NONE) + { + ArrayIndicesPerObject[0].Add(Segment.GetName().ToString(), ArrayIndex); + } + } + Evt.SetArrayIndexPerObject(ArrayIndicesPerObject); + + FEditPropertyChain Chain; + CachedPath.ToEditPropertyChain(Chain); + FPropertyChangedChainEvent ChainEvent(Chain, Evt); + ChainEvent.ObjectIteratorIndex = 0; + InRootObject->PostEditChangeChainProperty(ChainEvent); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could not resolve property path '%s' on %s."), *InPropertyPath.ToString(), *(InRootObject->GetFullName())); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h index ef51b9b2a..f1a03b69b 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h @@ -1,86 +1,88 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -class FString; -class UObject; -class UHoudiniAsset; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniLandscapeOutputBakeType : uint8; -enum class EHoudiniToolType : uint8; -enum class EHoudiniToolSelectionType : uint8; - -struct FHoudiniEngineEditorUtils -{ -public: - - // Triggers an update the details panel - //static void UpdateEditorProperties(UObject* ObjectToUpdate); - - // Helper function for accessing the current CB selection - static int32 GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection); - - // Helper function for accessing the current world selection - static int32 GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly = false); - - static FString HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType); - - static FString HoudiniCurveMethodToString(const EHoudiniCurveMethod& HoudinCurveMethod); - - static FString HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType); - - // (for temporary, we need to figure out a way to access the output curve's info later) - static FString HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType); - - static FTransform GetDefaulAssetSpawnTransform(); - - static FTransform GetMeanWorldSelectionTransform(); - - // Instantiate a HoudiniAsset at a given position - static void InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform); - - // Instantiate the given HDA, and handles the current CB/World selection - static void InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType); - - // Helper function used to save all temporary packages when the level is saved - static void SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld); - - // Deselect and reselect all selected actors to get rid of component not showing bug after create. - static void ReselectSelectedActors(); - - // Gets the node name indent from the left with the specified number of spaces based on the path depth. - static FString GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding=4, const TCHAR PathSep='/'); - - // Property change notifications - // Call PostEditChangeChainProperty on InRootObject for the property at InPropertyPath relative to - // InRootObject. - static void NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +class FString; +class UObject; +class UHoudiniAsset; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniLandscapeOutputBakeType : uint8; +enum class EHoudiniToolType : uint8; +enum class EHoudiniToolSelectionType : uint8; + +struct FHoudiniEngineEditorUtils +{ +public: + + // Triggers an update the details panel + //static void UpdateEditorProperties(UObject* ObjectToUpdate); + + // Helper function for accessing the current CB selection + static int32 GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection); + + // Helper function for accessing the current world selection + static int32 GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly = false); + + static FString HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType); + + static FString HoudiniCurveMethodToString(const EHoudiniCurveMethod& HoudinCurveMethod); + + static FString HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType); + + // (for temporary, we need to figure out a way to access the output curve's info later) + static FString HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType); + + static FTransform GetDefaulAssetSpawnTransform(); + + static FTransform GetMeanWorldSelectionTransform(); + + // Instantiate a HoudiniAsset at a given position. If InSpawnInLevelOverride is non-null, spawns in that level. + // Otherwise if InSpawnInWorld, spawns in the current level of InSpawnInWorld. Lastly, if both are null, spawns + // in the current level of the editor context world. + static AActor* InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform, UWorld* InSpawnInWorld=nullptr, ULevel* InSpawnInLevelOverride=nullptr); + + // Instantiate the given HDA, and handles the current CB/World selection + static void InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType); + + // Helper function used to save all temporary packages when the level is saved + static void SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld); + + // Deselect and reselect all selected actors to get rid of component not showing bug after create. + static void ReselectSelectedActors(); + + // Gets the node name indent from the left with the specified number of spaces based on the path depth. + static FString GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding=4, const TCHAR PathSep='/'); + + // Property change notifications + // Call PostEditChangeChainProperty on InRootObject for the property at InPropertyPath relative to + // InRootObject. + static void NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp index a8c45c221..1ebf3ed4c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp @@ -1,311 +1,311 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineStyle.h" - -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineUtils.h" - -#include "EditorStyleSet.h" -#include "Styling/SlateStyleRegistry.h" -#include "Styling/SlateTypes.h" -#include "SlateOptMacros.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) -#define TTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) -#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) -#define OTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) - -TSharedPtr FHoudiniEngineStyle::StyleSet = nullptr; - -TSharedPtr -FHoudiniEngineStyle::Get() -{ - return StyleSet; -} - -FName -FHoudiniEngineStyle::GetStyleSetName() -{ - static FName HoudiniStyleName(TEXT("HoudiniEngineStyle")); - return HoudiniStyleName; -} - -BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION - -void -FHoudiniEngineStyle::Initialize() -{ - // Only register the StyleSet once - if (StyleSet.IsValid()) - return; - - StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName())); - StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); - StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); - - // Note, these sizes are in Slate Units. - // Slate Units do NOT have to map to pixels. - const FVector2D Icon5x16(5.0f, 16.0f); - const FVector2D Icon8x4(8.0f, 4.0f); - const FVector2D Icon8x8(8.0f, 8.0f); - const FVector2D Icon10x10(10.0f, 10.0f); - const FVector2D Icon12x12(12.0f, 12.0f); - const FVector2D Icon12x16(12.0f, 16.0f); - const FVector2D Icon14x14(14.0f, 14.0f); - const FVector2D Icon16x16(16.0f, 16.0f); - const FVector2D Icon20x20(20.0f, 20.0f); - const FVector2D Icon22x22(22.0f, 22.0f); - const FVector2D Icon24x24(24.0f, 24.0f); - const FVector2D Icon25x25(25.0f, 25.0f); - const FVector2D Icon32x32(32.0f, 32.0f); - const FVector2D Icon40x40(40.0f, 40.0f); - const FVector2D Icon64x64(64.0f, 64.0f); - const FVector2D Icon36x24(36.0f, 24.0f); - const FVector2D Icon128x128(128.0f, 128.0f); - - static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); - StyleSet->Set( - "HoudiniEngine.HoudiniEngineLogo", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set( - "ClassIcon.HoudiniAssetActor", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set( - "HoudiniEngine.HoudiniEngineLogo40", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); - - StyleSet->Set( - "ClassIcon.HoudiniAsset", - new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset.png"), Icon16x16)); - - StyleSet->Set( - "ClassThumbnail.HoudiniAsset", - new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset_128.png"), Icon64x64)); - - static FString ResourcesDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/"); - - FString AssetHelpIcon = IconsDir + TEXT("asset_help16x16.png"); - FString BakeAllIcon = IconsDir + TEXT("bake_all16x16.png"); - FString BakeSelIcon = IconsDir + TEXT("bake_selected16x16.png"); - FString CleanTempIcon = IconsDir + TEXT("clean_temp16x16.png"); - FString CookAllIcon = IconsDir + TEXT("cook_all16x16.png"); - FString CookLogIcon = IconsDir + TEXT("cook_log16x16.png"); - FString CookSelIcon = IconsDir + TEXT("cook_selected16x16.png"); - FString DigitalAssetIcon = IconsDir + TEXT("digital_asset16x16.png"); - FString OnlineForumIcon = IconsDir + TEXT("online_forum16x16.png"); - FString OnlineHelpIcon = IconsDir + TEXT("online_help16x16.png"); - FString OpenInHIcon = IconsDir + TEXT("open_in_houdini16x16.png"); - FString PauseIcon = IconsDir + TEXT("pause16x16.png"); - FString PDGCancelIcon = IconsDir + TEXT("pdg_cancel16x16.png"); - FString PDGDirtyAllIcon = IconsDir + TEXT("pdg_dirty_all16x16.png"); - FString PDGDirtyNodeIcon = IconsDir + TEXT("pdg_dirty_node16x16.png"); - FString PDGLinkIcon = IconsDir + TEXT("pdg_link16x16.png"); - FString PDGPauseIcon = IconsDir + TEXT("pdg_pause16x16.png"); - FString PDGRefreshIcon = IconsDir + TEXT("pdg_refresh16x16.png"); - FString PDGResetIcon = IconsDir + TEXT("pdg_reset16x16.png"); - FString RebuildAllIcon = IconsDir + TEXT("rebuild_all16x16.png"); - FString RebuildSelIcon = IconsDir + TEXT("rebuild_selected16x16.png"); - FString RefineAllIcon = IconsDir + TEXT("refine_all16x16.png"); - FString RefineSelIcon = IconsDir + TEXT("refine_selected16x16.png"); - FString ReportBugIcon = IconsDir + TEXT("report_bug16x16.png"); - FString ResetIcon = IconsDir + TEXT("reset16x16.png"); - FString ResetParamIcon = IconsDir + TEXT("reset_parameters16x16.png"); - FString SaveToHipIcon = IconsDir + TEXT("save_to_hip16x16.png"); - FString SessionConnectIcon = IconsDir + TEXT("session_connect16x16.png"); - FString SessionCreateIcon = IconsDir + TEXT("session_create16x16.png"); - FString SessionRestartIcon = IconsDir + TEXT("session_restart16x16.png"); - FString SessionStopIcon = IconsDir + TEXT("session_stop16x16.png"); - FString SessionSyncIcon = IconsDir + TEXT("session_sync16x16.png"); - FString SessionSyncStartIcon = IconsDir + TEXT("session_sync_start16x16.png"); - FString SessionSyncStopIcon = IconsDir + TEXT("session_sync_stop16x16.png"); - FString ViewportSyncIcon = IconsDir + TEXT("viewport_sync16x16.png"); - FString ViewportSyncBothIcon = IconsDir + TEXT("viewport_sync_both16x16.png"); - FString ViewportSyncHoudiniIcon = IconsDir + TEXT("viewport_sync_houdini16x16.png"); - FString ViewportSyncOffIcon = IconsDir + TEXT("viewport_sync_off16x16.png"); - FString ViewportSyncUnrealIcon = IconsDir + TEXT("viewport_sync_unreal16x16.png"); - - FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); - FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); - - StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(SessionCreateIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(SessionConnectIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(SessionStopIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(SessionRestartIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._SessionSync", new FSlateImageBrush(SessionSyncIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(SessionSyncStartIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(SessionSyncStopIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._SyncViewport", new FSlateImageBrush(ViewportSyncIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncNone", new FSlateImageBrush(ViewportSyncOffIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncBoth", new FSlateImageBrush(ViewportSyncBothIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncUnreal", new FSlateImageBrush(ViewportSyncUnrealIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncHoudini", new FSlateImageBrush(ViewportSyncHoudiniIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(OpenInHIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(SaveToHipIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(CleanTempIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(OnlineHelpIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(OnlineForumIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(ReportBugIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(CookAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(CookSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(BakeSelIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(BakeAllIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(RebuildAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(RebuildSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(RefineAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(RefineSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(PauseIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._Reset", new FSlateImageBrush(ResetIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine.DigitalAsset", new FSlateImageBrush(DigitalAssetIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine.PDGLink", new FSlateImageBrush(PDGLinkIcon, Icon16x16)); - - /* - FString StopIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Clear")->GetResourceName().ToString(); - FString RestartIcon = FEditorStyle::GetBrush("Tutorials.Browser.RestartButton")->GetResourceName().ToString(); - FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); - FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); - FString ClearIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Delete")->GetResourceName().ToString(); - FString HelpIcon = FEditorStyle::GetBrush("Icons.Help")->GetResourceName().ToString(); - FString WarningIcon = FEditorStyle::GetBrush("Icons.Warning")->GetResourceName().ToString(); - FString BPIcon = FEditorStyle::GetBrush("PropertyWindow.Button_CreateNewBlueprint")->GetResourceName().ToString(); - FString PauseIcon = FEditorStyle::GetBrush("Profiler.Pause")->GetResourceName().ToString(); - - StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(StopIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(RestartIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(StopIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); - - - StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(HelpIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(WarningIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - */ - - // We need some colors from Editor Style & this is the only way to do this at the moment - const FSlateColor DefaultForeground = FEditorStyle::GetSlateColor("DefaultForeground"); - const FSlateColor InvertedForeground = FEditorStyle::GetSlateColor("InvertedForeground"); - const FSlateColor SelectorColor = FEditorStyle::GetSlateColor("SelectorColor"); - const FSlateColor SelectionColor = FEditorStyle::GetSlateColor("SelectionColor"); - const FSlateColor SelectionColor_Inactive = FEditorStyle::GetSlateColor("SelectionColor_Inactive"); - - const FTableRowStyle &NormalTableRowStyle = FEditorStyle::Get().GetWidgetStyle("TableView.Row"); - StyleSet->Set( - "HoudiniEngine.TableRow", FTableRowStyle(NormalTableRowStyle) - .SetEvenRowBackgroundBrush(FSlateNoResource()) - .SetEvenRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) - .SetOddRowBackgroundBrush(FSlateNoResource()) - .SetOddRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) - .SetSelectorFocusedBrush(BORDER_BRUSH("Common/Selector", FMargin(4.f / 16.f), SelectorColor)) - .SetActiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) - .SetActiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) - .SetInactiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) - .SetInactiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) - .SetTextColor(DefaultForeground) - .SetSelectedTextColor(InvertedForeground) - ); - - // Normal Text - const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle("NormalText"); - StyleSet->Set( - "HoudiniEngine.ThumbnailText", FTextBlockStyle(NormalText) - .SetFont(TTF_CORE_FONT("Fonts/Roboto-Regular", 9)) - .SetColorAndOpacity(FSlateColor::UseForeground()) - .SetShadowOffset(FVector2D::ZeroVector) - .SetShadowColorAndOpacity(FLinearColor::Black) - .SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f)) - .SetHighlightShape(BOX_BRUSH("Common/TextBlockHighlightShape", FMargin(3.f / 8.f))) - ); - - StyleSet->Set("HoudiniEngine.ThumbnailShadow", new BOX_BRUSH("ContentBrowser/ThumbnailShadow", FMargin(4.0f / 64.0f))); - StyleSet->Set("HoudiniEngine.ThumbnailBackground", new IMAGE_BRUSH("Common/ClassBackground_64x", FVector2D(64.f, 64.f), FLinearColor(0.75f, 0.75f, 0.75f, 1.0f))); - - // Register Slate style. - FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); -}; - -END_SLATE_FUNCTION_BUILD_OPTIMIZATION - -#undef IMAGE_BRUSH -#undef BOX_BRUSH -#undef BORDER_BRUSH -#undef TTF_FONT -#undef TTF_CORE_FONT -#undef OTF_FONT -#undef OTF_CORE_FONT - -void -FHoudiniEngineStyle::Shutdown() -{ - if (StyleSet.IsValid()) - { - FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); - ensure(StyleSet.IsUnique()); - StyleSet.Reset(); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineStyle.h" + +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineUtils.h" + +#include "EditorStyleSet.h" +#include "Styling/SlateStyleRegistry.h" +#include "Styling/SlateTypes.h" +#include "SlateOptMacros.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define TTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) +#define OTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +TSharedPtr FHoudiniEngineStyle::StyleSet = nullptr; + +TSharedPtr +FHoudiniEngineStyle::Get() +{ + return StyleSet; +} + +FName +FHoudiniEngineStyle::GetStyleSetName() +{ + static FName HoudiniStyleName(TEXT("HoudiniEngineStyle")); + return HoudiniStyleName; +} + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void +FHoudiniEngineStyle::Initialize() +{ + // Only register the StyleSet once + if (StyleSet.IsValid()) + return; + + StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName())); + StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); + StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); + + // Note, these sizes are in Slate Units. + // Slate Units do NOT have to map to pixels. + const FVector2D Icon5x16(5.0f, 16.0f); + const FVector2D Icon8x4(8.0f, 4.0f); + const FVector2D Icon8x8(8.0f, 8.0f); + const FVector2D Icon10x10(10.0f, 10.0f); + const FVector2D Icon12x12(12.0f, 12.0f); + const FVector2D Icon12x16(12.0f, 16.0f); + const FVector2D Icon14x14(14.0f, 14.0f); + const FVector2D Icon16x16(16.0f, 16.0f); + const FVector2D Icon20x20(20.0f, 20.0f); + const FVector2D Icon22x22(22.0f, 22.0f); + const FVector2D Icon24x24(24.0f, 24.0f); + const FVector2D Icon25x25(25.0f, 25.0f); + const FVector2D Icon32x32(32.0f, 32.0f); + const FVector2D Icon40x40(40.0f, 40.0f); + const FVector2D Icon64x64(64.0f, 64.0f); + const FVector2D Icon36x24(36.0f, 24.0f); + const FVector2D Icon128x128(128.0f, 128.0f); + + static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); + StyleSet->Set( + "HoudiniEngine.HoudiniEngineLogo", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( + "ClassIcon.HoudiniAssetActor", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( + "HoudiniEngine.HoudiniEngineLogo40", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); + + StyleSet->Set( + "ClassIcon.HoudiniAsset", + new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset.png"), Icon16x16)); + + StyleSet->Set( + "ClassThumbnail.HoudiniAsset", + new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset_128.png"), Icon64x64)); + + static FString ResourcesDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/"); + + FString AssetHelpIcon = IconsDir + TEXT("asset_help16x16.png"); + FString BakeAllIcon = IconsDir + TEXT("bake_all16x16.png"); + FString BakeSelIcon = IconsDir + TEXT("bake_selected16x16.png"); + FString CleanTempIcon = IconsDir + TEXT("clean_temp16x16.png"); + FString CookAllIcon = IconsDir + TEXT("cook_all16x16.png"); + FString CookLogIcon = IconsDir + TEXT("cook_log16x16.png"); + FString CookSelIcon = IconsDir + TEXT("cook_selected16x16.png"); + FString DigitalAssetIcon = IconsDir + TEXT("digital_asset16x16.png"); + FString OnlineForumIcon = IconsDir + TEXT("online_forum16x16.png"); + FString OnlineHelpIcon = IconsDir + TEXT("online_help16x16.png"); + FString OpenInHIcon = IconsDir + TEXT("open_in_houdini16x16.png"); + FString PauseIcon = IconsDir + TEXT("pause16x16.png"); + FString PDGCancelIcon = IconsDir + TEXT("pdg_cancel16x16.png"); + FString PDGDirtyAllIcon = IconsDir + TEXT("pdg_dirty_all16x16.png"); + FString PDGDirtyNodeIcon = IconsDir + TEXT("pdg_dirty_node16x16.png"); + FString PDGLinkIcon = IconsDir + TEXT("pdg_link16x16.png"); + FString PDGPauseIcon = IconsDir + TEXT("pdg_pause16x16.png"); + FString PDGRefreshIcon = IconsDir + TEXT("pdg_refresh16x16.png"); + FString PDGResetIcon = IconsDir + TEXT("pdg_reset16x16.png"); + FString RebuildAllIcon = IconsDir + TEXT("rebuild_all16x16.png"); + FString RebuildSelIcon = IconsDir + TEXT("rebuild_selected16x16.png"); + FString RefineAllIcon = IconsDir + TEXT("refine_all16x16.png"); + FString RefineSelIcon = IconsDir + TEXT("refine_selected16x16.png"); + FString ReportBugIcon = IconsDir + TEXT("report_bug16x16.png"); + FString ResetIcon = IconsDir + TEXT("reset16x16.png"); + FString ResetParamIcon = IconsDir + TEXT("reset_parameters16x16.png"); + FString SaveToHipIcon = IconsDir + TEXT("save_to_hip16x16.png"); + FString SessionConnectIcon = IconsDir + TEXT("session_connect16x16.png"); + FString SessionCreateIcon = IconsDir + TEXT("session_create16x16.png"); + FString SessionRestartIcon = IconsDir + TEXT("session_restart16x16.png"); + FString SessionStopIcon = IconsDir + TEXT("session_stop16x16.png"); + FString SessionSyncIcon = IconsDir + TEXT("session_sync16x16.png"); + FString SessionSyncStartIcon = IconsDir + TEXT("session_sync_start16x16.png"); + FString SessionSyncStopIcon = IconsDir + TEXT("session_sync_stop16x16.png"); + FString ViewportSyncIcon = IconsDir + TEXT("viewport_sync16x16.png"); + FString ViewportSyncBothIcon = IconsDir + TEXT("viewport_sync_both16x16.png"); + FString ViewportSyncHoudiniIcon = IconsDir + TEXT("viewport_sync_houdini16x16.png"); + FString ViewportSyncOffIcon = IconsDir + TEXT("viewport_sync_off16x16.png"); + FString ViewportSyncUnrealIcon = IconsDir + TEXT("viewport_sync_unreal16x16.png"); + + FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); + FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); + + StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(SessionCreateIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(SessionConnectIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(SessionStopIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(SessionRestartIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._SessionSync", new FSlateImageBrush(SessionSyncIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(SessionSyncStartIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(SessionSyncStopIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._SyncViewport", new FSlateImageBrush(ViewportSyncIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncNone", new FSlateImageBrush(ViewportSyncOffIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncBoth", new FSlateImageBrush(ViewportSyncBothIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncUnreal", new FSlateImageBrush(ViewportSyncUnrealIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncHoudini", new FSlateImageBrush(ViewportSyncHoudiniIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(OpenInHIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(SaveToHipIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(CleanTempIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(OnlineHelpIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(OnlineForumIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(ReportBugIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(CookAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(CookSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(BakeSelIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(BakeAllIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(RebuildAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(RebuildSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(RefineAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(RefineSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(PauseIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._Reset", new FSlateImageBrush(ResetIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine.DigitalAsset", new FSlateImageBrush(DigitalAssetIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine.PDGLink", new FSlateImageBrush(PDGLinkIcon, Icon16x16)); + + /* + FString StopIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Clear")->GetResourceName().ToString(); + FString RestartIcon = FEditorStyle::GetBrush("Tutorials.Browser.RestartButton")->GetResourceName().ToString(); + FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); + FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); + FString ClearIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Delete")->GetResourceName().ToString(); + FString HelpIcon = FEditorStyle::GetBrush("Icons.Help")->GetResourceName().ToString(); + FString WarningIcon = FEditorStyle::GetBrush("Icons.Warning")->GetResourceName().ToString(); + FString BPIcon = FEditorStyle::GetBrush("PropertyWindow.Button_CreateNewBlueprint")->GetResourceName().ToString(); + FString PauseIcon = FEditorStyle::GetBrush("Profiler.Pause")->GetResourceName().ToString(); + + StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(StopIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(RestartIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(StopIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); + + + StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(HelpIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(WarningIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + */ + + // We need some colors from Editor Style & this is the only way to do this at the moment + const FSlateColor DefaultForeground = FEditorStyle::GetSlateColor("DefaultForeground"); + const FSlateColor InvertedForeground = FEditorStyle::GetSlateColor("InvertedForeground"); + const FSlateColor SelectorColor = FEditorStyle::GetSlateColor("SelectorColor"); + const FSlateColor SelectionColor = FEditorStyle::GetSlateColor("SelectionColor"); + const FSlateColor SelectionColor_Inactive = FEditorStyle::GetSlateColor("SelectionColor_Inactive"); + + const FTableRowStyle &NormalTableRowStyle = FEditorStyle::Get().GetWidgetStyle("TableView.Row"); + StyleSet->Set( + "HoudiniEngine.TableRow", FTableRowStyle(NormalTableRowStyle) + .SetEvenRowBackgroundBrush(FSlateNoResource()) + .SetEvenRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) + .SetOddRowBackgroundBrush(FSlateNoResource()) + .SetOddRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) + .SetSelectorFocusedBrush(BORDER_BRUSH("Common/Selector", FMargin(4.f / 16.f), SelectorColor)) + .SetActiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) + .SetActiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) + .SetInactiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) + .SetInactiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) + .SetTextColor(DefaultForeground) + .SetSelectedTextColor(InvertedForeground) + ); + + // Normal Text + const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle("NormalText"); + StyleSet->Set( + "HoudiniEngine.ThumbnailText", FTextBlockStyle(NormalText) + .SetFont(TTF_CORE_FONT("Fonts/Roboto-Regular", 9)) + .SetColorAndOpacity(FSlateColor::UseForeground()) + .SetShadowOffset(FVector2D::ZeroVector) + .SetShadowColorAndOpacity(FLinearColor::Black) + .SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f)) + .SetHighlightShape(BOX_BRUSH("Common/TextBlockHighlightShape", FMargin(3.f / 8.f))) + ); + + StyleSet->Set("HoudiniEngine.ThumbnailShadow", new BOX_BRUSH("ContentBrowser/ThumbnailShadow", FMargin(4.0f / 64.0f))); + StyleSet->Set("HoudiniEngine.ThumbnailBackground", new IMAGE_BRUSH("Common/ClassBackground_64x", FVector2D(64.f, 64.f), FLinearColor(0.75f, 0.75f, 0.75f, 1.0f))); + + // Register Slate style. + FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); +}; + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef TTF_CORE_FONT +#undef OTF_FONT +#undef OTF_CORE_FONT + +void +FHoudiniEngineStyle::Shutdown() +{ + if (StyleSet.IsValid()) + { + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); + ensure(StyleSet.IsUnique()); + StyleSet.Reset(); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h index ac64f0faa..de25f3b67 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h @@ -1,42 +1,42 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Styling/SlateStyle.h" - -class FHoudiniEngineStyle -{ - public: - static void Initialize(); - static void Shutdown(); - static TSharedPtr Get(); - static FName GetStyleSetName(); - - private: - //static FString InContent(const FString &RelativePath, const ANSICHAR *Extension); - static TSharedPtr StyleSet; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Styling/SlateStyle.h" + +class FHoudiniEngineStyle +{ + public: + static void Initialize(); + static void Shutdown(); + static TSharedPtr Get(); + static FName GetStyleSetName(); + + private: + //static FString InContent(const FString &RelativePath, const ANSICHAR *Extension); + static TSharedPtr StyleSet; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp index 5541b05bf..6799aaff6 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp @@ -1,382 +1,382 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniOutput.h" -#include "HoudiniEngine.h" - -#include "Engine/StaticMesh.h" -//#include "Engine/SkeletalMesh.h" - -#include "EditorFramework/AssetImportData.h" -#include "Misc/FileHelper.h" -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniGeoFactory::UHoudiniGeoFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - // This factory is responsible for manufacturing HoudiniEngine assets. - SupportedClass = nullptr;// UHoudiniAsset::StaticClass(); - - // This factory does not manufacture new objects from scratch. - bCreateNew = false; - - // This factory will not open the editor for each new object. - bEditAfterNew = false; - - // This factory will import objects from files. - bEditorImport = true; - - // Factory does not import objects from text. - bText = false; - - // Add supported formats. - Formats.Add(TEXT("bgeo;Houdini Geometry")); - Formats.Add(TEXT("bgeo.sc;Houdini Geometry (compressed)")); - Formats.Add(TEXT("sc;Houdini Geometry (compressed)")); -} - -bool -UHoudiniGeoFactory::FactoryCanImport(const FString& Filename) -{ - const FString Extension = FPaths::GetExtension(Filename); - if(FPaths::GetExtension(Filename) == TEXT("bgeo")) - return true; - if (FPaths::GetExtension(Filename) == TEXT("bgeo.sc")) - return true; - if (FPaths::GetExtension(Filename) == TEXT("sc")) - return true; - - return false; -} - -bool -UHoudiniGeoFactory::DoesSupportClass(UClass * Class) -{ - return Class == UStaticMesh::StaticClass(); //|| Class == USkeletalMesh::StaticClass()); -} - -UClass* -UHoudiniGeoFactory::ResolveSupportedClass() -{ - return UStaticMesh::StaticClass(); -} - -FText -UHoudiniGeoFactory::GetDisplayName() const -{ - return LOCTEXT("HoudiniGeoFactoryDescription", "Houdini Engine Geo"); -} - -UObject* -UHoudiniGeoFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) -{ - // Make sure we're loading bgeo / bgeo.sc files - FString FileExtension = FPaths::GetExtension(Filename); - if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) - return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); - - // - // TODO: - // Handle import settings here? - // - UObject* Success = Import(InClass, Cast(InParent), InName.ToString(), Filename, Flags, false); - if (!Success) - { - FString Notification = TEXT("BGEO Importer: Failed to load the BGEO file."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - return nullptr; - } - - // Notifiy we're done loading the bgeo - FString Notification = TEXT("BGEO Importer: BGEO file imported succesfully."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - return Success; -} - -bool -UHoudiniGeoFactory::CanReimport(UObject * Obj, TArray& OutFilenames) -{ - UAssetImportData* ImportData = nullptr; - if (Obj->GetClass() == UStaticMesh::StaticClass()) - { - UStaticMesh* Mesh = Cast(Obj); - ImportData = Mesh->AssetImportData; - } - /* - else if (Obj->GetClass() == USkeletalMesh::StaticClass()) - { - USkeletalMesh* Cache = Cast(Obj); - ImportData = Cache->AssetImportData; - } - */ - - if (ImportData) - { - if (FPaths::GetExtension(ImportData->GetFirstFilename()).Contains(TEXT("bgeo"))) - { - ImportData->ExtractFilenames(OutFilenames); - return true; - } - } - return false; -} - -void -UHoudiniGeoFactory::SetReimportPaths(UObject * Obj, const TArray& NewReimportPaths) -{ - UStaticMesh* Mesh = Cast(Obj); - if (Mesh && Mesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) - { - Mesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); - } - - /* - USkeletalMesh* SkeletalMesh = Cast(Obj); - if (SkeletalMesh && SkeletalMesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) - { - SkeletalMesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); - } - */ -} - - -UObject* -UHoudiniGeoFactory::Import(UClass* InClass, UPackage* InParent, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport) -{ - // Broadcast PreImport - GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, FName(FileName), TEXT("Houdini GEO")); - - // Create a new Geo importer - TArray DummyOldOutputs; - TArray NewOutputs; - UHoudiniGeoImporter* BGEOImporter = NewObject(this); - BGEOImporter->AddToRoot(); - - // Clean up lambda - auto CleanUp = [&NewOutputs, &BGEOImporter]() - { - // Remove the importer and output objects from the root set - BGEOImporter->RemoveFromRoot(); - for (auto Out : NewOutputs) - Out->RemoveFromRoot(); - }; - - // Failure lambda - auto FailImportAndReturnNull = [this, &CleanUp, &NewOutputs, &BGEOImporter]() - { - CleanUp(); - - // Failed to read the file info, fail the import - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, nullptr); - - return nullptr; - }; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!BGEOImporter->AutoStartHoudiniEngineSessionIfNeeded()) - return FailImportAndReturnNull(); - - // 2. Update the file paths - if (!BGEOImporter->SetFilePath(AbsoluteFilePath)) - return FailImportAndReturnNull(); - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - if (!BGEOImporter->LoadBGEOFileInHAPI(NodeId)) - return FailImportAndReturnNull(); - - // Prepare the package used for creating the mesh, landscape and instancer pacakges - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - PackageParams.HoudiniAssetName = FString(); - PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); - PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); - - if (bReimport) - { - PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - } - else - { - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - } - - // 4. Get the output from the file node - if (!BGEOImporter->BuildOutputsForNode(NodeId, DummyOldOutputs, NewOutputs)) - return FailImportAndReturnNull(); - - // 5. Create the static meshes in the outputs - if (!BGEOImporter->CreateStaticMeshes(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 6. Create the curves in the outputs - if (!BGEOImporter->CreateCurves(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 7. Create the landscape in the outputs - if (!BGEOImporter->CreateLandscapes(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 8. Create the instancers in the outputs - if (!BGEOImporter->CreateInstancers(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 9. Delete the created node in Houdini - if (!BGEOImporter->DeleteCreatedNode(NodeId)) - { - // Not good, but not fatal.. - //return false; - } - - // Get our result object and "finalize" them - TArray Results = BGEOImporter->GetOutputObjects(); - for (UObject* Object : Results) - { - if (!Object || Object->IsPendingKill()) - continue; - - Object->SetFlags(Flags); - - UAssetImportData * AssetImportData = nullptr; - if (Object->IsA()) - { - UStaticMesh* SM = Cast(Object); - AssetImportData = SM->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); - SM->AssetImportData = AssetImportData; - } - } - /* - else if (Object->IsA()) - { - USkeletalMesh * SkeletalMesh = Cast(Object); - AssetImportData = SkeletalMesh->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject(SkeletalMesh, USkeletalMesh::StaticClass()); - SkeletalMesh->AssetImportData = AssetImportData; - } - } - */ - - if (AssetImportData) - AssetImportData->Update(AbsoluteFilePath); - - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, Object); - Object->MarkPackageDirty(); - Object->PostEditChange(); - } - - CleanUp(); - - // Determine out parent according to the generated assets outer - UObject* OutParent = (Results.Num() > 0 && InParent != Results[0]->GetOutermost()) ? Results[0]->GetOutermost() : InParent; - return (Results.Num() > 0) ? OutParent : nullptr; -} - -EReimportResult::Type -UHoudiniGeoFactory::Reimport(UObject * Obj) -{ - auto FailReimport = []() - { - // Notifiy we failed to load the bgeo - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); - return EReimportResult::Failed; - }; - - if (!Obj || Obj->IsPendingKill()) - return FailReimport(); - - UPackage* Package = Cast(Obj->GetOuter()); - if (!Package || Package->IsPendingKill()) - return FailReimport(); - - UAssetImportData* ImportData = nullptr; - if (Obj->GetClass() == UStaticMesh::StaticClass()) - { - UStaticMesh* StaticMesh = Cast(Obj); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return FailReimport(); - - ImportData = StaticMesh->AssetImportData; - } - /* - else if(Obj->GetClass() == USkeletalMesh::StaticClass()) - { - USkeletalMesh* SkeletalMesh = Cast(Obj); - if (!SkeletalMesh || SkeletalMesh->IsPendingKill()) - return FailReimport(); - - ImportData = SkeletalMesh->AssetImportData; - } - */ - if (!ImportData || ImportData->IsPendingKill()) - return FailReimport(); - - if (ImportData->GetSourceFileCount() <= 0) - return FailReimport(); - - const FString RelativeFileName = ImportData->SourceData.SourceFiles[0].RelativeFilename; - const FString FileExtension = FPaths::GetExtension(RelativeFileName); - FString FileName = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativeFileName); - if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) - return FailReimport(); - - if (!Import(Obj->GetClass(), Package, Obj->GetName(), FileName, Obj->GetFlags(), true)) - return FailReimport(); - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has Succeed.")); - - // Notifiy we're done loading the bgeo - FString Notification = TEXT("BGEO Importer: BGEO file re-imported succesfully."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - return EReimportResult::Succeeded; -} - -int32 -UHoudiniGeoFactory::GetPriority() const -{ - return ImportPriority; -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniOutput.h" +#include "HoudiniEngine.h" + +#include "Engine/StaticMesh.h" +//#include "Engine/SkeletalMesh.h" + +#include "EditorFramework/AssetImportData.h" +#include "Misc/FileHelper.h" +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniGeoFactory::UHoudiniGeoFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + // This factory is responsible for manufacturing HoudiniEngine assets. + SupportedClass = nullptr;// UHoudiniAsset::StaticClass(); + + // This factory does not manufacture new objects from scratch. + bCreateNew = false; + + // This factory will not open the editor for each new object. + bEditAfterNew = false; + + // This factory will import objects from files. + bEditorImport = true; + + // Factory does not import objects from text. + bText = false; + + // Add supported formats. + Formats.Add(TEXT("bgeo;Houdini Geometry")); + Formats.Add(TEXT("bgeo.sc;Houdini Geometry (compressed)")); + Formats.Add(TEXT("sc;Houdini Geometry (compressed)")); +} + +bool +UHoudiniGeoFactory::FactoryCanImport(const FString& Filename) +{ + const FString Extension = FPaths::GetExtension(Filename); + if(FPaths::GetExtension(Filename) == TEXT("bgeo")) + return true; + if (FPaths::GetExtension(Filename) == TEXT("bgeo.sc")) + return true; + if (FPaths::GetExtension(Filename) == TEXT("sc")) + return true; + + return false; +} + +bool +UHoudiniGeoFactory::DoesSupportClass(UClass * Class) +{ + return Class == UStaticMesh::StaticClass(); //|| Class == USkeletalMesh::StaticClass()); +} + +UClass* +UHoudiniGeoFactory::ResolveSupportedClass() +{ + return UStaticMesh::StaticClass(); +} + +FText +UHoudiniGeoFactory::GetDisplayName() const +{ + return LOCTEXT("HoudiniGeoFactoryDescription", "Houdini Engine Geo"); +} + +UObject* +UHoudiniGeoFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) +{ + // Make sure we're loading bgeo / bgeo.sc files + FString FileExtension = FPaths::GetExtension(Filename); + if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) + return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); + + // + // TODO: + // Handle import settings here? + // + UObject* Success = Import(InClass, Cast(InParent), InName.ToString(), Filename, Flags, false); + if (!Success) + { + FString Notification = TEXT("BGEO Importer: Failed to load the BGEO file."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + return nullptr; + } + + // Notifiy we're done loading the bgeo + FString Notification = TEXT("BGEO Importer: BGEO file imported succesfully."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + return Success; +} + +bool +UHoudiniGeoFactory::CanReimport(UObject * Obj, TArray& OutFilenames) +{ + UAssetImportData* ImportData = nullptr; + if (Obj->GetClass() == UStaticMesh::StaticClass()) + { + UStaticMesh* Mesh = Cast(Obj); + ImportData = Mesh->AssetImportData; + } + /* + else if (Obj->GetClass() == USkeletalMesh::StaticClass()) + { + USkeletalMesh* Cache = Cast(Obj); + ImportData = Cache->AssetImportData; + } + */ + + if (ImportData) + { + if (FPaths::GetExtension(ImportData->GetFirstFilename()).Contains(TEXT("bgeo"))) + { + ImportData->ExtractFilenames(OutFilenames); + return true; + } + } + return false; +} + +void +UHoudiniGeoFactory::SetReimportPaths(UObject * Obj, const TArray& NewReimportPaths) +{ + UStaticMesh* Mesh = Cast(Obj); + if (Mesh && Mesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) + { + Mesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); + } + + /* + USkeletalMesh* SkeletalMesh = Cast(Obj); + if (SkeletalMesh && SkeletalMesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) + { + SkeletalMesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); + } + */ +} + + +UObject* +UHoudiniGeoFactory::Import(UClass* InClass, UPackage* InParent, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport) +{ + // Broadcast PreImport + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, FName(FileName), TEXT("Houdini GEO")); + + // Create a new Geo importer + TArray DummyOldOutputs; + TArray NewOutputs; + UHoudiniGeoImporter* BGEOImporter = NewObject(this); + BGEOImporter->AddToRoot(); + + // Clean up lambda + auto CleanUp = [&NewOutputs, &BGEOImporter]() + { + // Remove the importer and output objects from the root set + BGEOImporter->RemoveFromRoot(); + for (auto Out : NewOutputs) + Out->RemoveFromRoot(); + }; + + // Failure lambda + auto FailImportAndReturnNull = [this, &CleanUp, &NewOutputs, &BGEOImporter]() + { + CleanUp(); + + // Failed to read the file info, fail the import + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, nullptr); + + return nullptr; + }; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!BGEOImporter->AutoStartHoudiniEngineSessionIfNeeded()) + return FailImportAndReturnNull(); + + // 2. Update the file paths + if (!BGEOImporter->SetFilePath(AbsoluteFilePath)) + return FailImportAndReturnNull(); + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + if (!BGEOImporter->LoadBGEOFileInHAPI(NodeId)) + return FailImportAndReturnNull(); + + // Prepare the package used for creating the mesh, landscape and instancer pacakges + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + PackageParams.HoudiniAssetName = FString(); + PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); + PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); + + if (bReimport) + { + PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + } + else + { + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + } + + // 4. Get the output from the file node + if (!BGEOImporter->BuildOutputsForNode(NodeId, DummyOldOutputs, NewOutputs)) + return FailImportAndReturnNull(); + + // 5. Create the static meshes in the outputs + if (!BGEOImporter->CreateStaticMeshes(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 6. Create the curves in the outputs + if (!BGEOImporter->CreateCurves(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 7. Create the landscape in the outputs + if (!BGEOImporter->CreateLandscapes(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 8. Create the instancers in the outputs + if (!BGEOImporter->CreateInstancers(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 9. Delete the created node in Houdini + if (!BGEOImporter->DeleteCreatedNode(NodeId)) + { + // Not good, but not fatal.. + //return false; + } + + // Get our result object and "finalize" them + TArray Results = BGEOImporter->GetOutputObjects(); + for (UObject* Object : Results) + { + if (!Object || Object->IsPendingKill()) + continue; + + Object->SetFlags(Flags); + + UAssetImportData * AssetImportData = nullptr; + if (Object->IsA()) + { + UStaticMesh* SM = Cast(Object); + AssetImportData = SM->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); + SM->AssetImportData = AssetImportData; + } + } + /* + else if (Object->IsA()) + { + USkeletalMesh * SkeletalMesh = Cast(Object); + AssetImportData = SkeletalMesh->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject(SkeletalMesh, USkeletalMesh::StaticClass()); + SkeletalMesh->AssetImportData = AssetImportData; + } + } + */ + + if (AssetImportData) + AssetImportData->Update(AbsoluteFilePath); + + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, Object); + Object->MarkPackageDirty(); + Object->PostEditChange(); + } + + CleanUp(); + + // Determine out parent according to the generated assets outer + UObject* OutParent = (Results.Num() > 0 && InParent != Results[0]->GetOutermost()) ? Results[0]->GetOutermost() : InParent; + return (Results.Num() > 0) ? OutParent : nullptr; +} + +EReimportResult::Type +UHoudiniGeoFactory::Reimport(UObject * Obj) +{ + auto FailReimport = []() + { + // Notifiy we failed to load the bgeo + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); + return EReimportResult::Failed; + }; + + if (!Obj || Obj->IsPendingKill()) + return FailReimport(); + + UPackage* Package = Cast(Obj->GetOuter()); + if (!Package || Package->IsPendingKill()) + return FailReimport(); + + UAssetImportData* ImportData = nullptr; + if (Obj->GetClass() == UStaticMesh::StaticClass()) + { + UStaticMesh* StaticMesh = Cast(Obj); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return FailReimport(); + + ImportData = StaticMesh->AssetImportData; + } + /* + else if(Obj->GetClass() == USkeletalMesh::StaticClass()) + { + USkeletalMesh* SkeletalMesh = Cast(Obj); + if (!SkeletalMesh || SkeletalMesh->IsPendingKill()) + return FailReimport(); + + ImportData = SkeletalMesh->AssetImportData; + } + */ + if (!ImportData || ImportData->IsPendingKill()) + return FailReimport(); + + if (ImportData->GetSourceFileCount() <= 0) + return FailReimport(); + + const FString RelativeFileName = ImportData->SourceData.SourceFiles[0].RelativeFilename; + const FString FileExtension = FPaths::GetExtension(RelativeFileName); + FString FileName = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativeFileName); + if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) + return FailReimport(); + + if (!Import(Obj->GetClass(), Package, Obj->GetName(), FileName, Obj->GetFlags(), true)) + return FailReimport(); + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has Succeed.")); + + // Notifiy we're done loading the bgeo + FString Notification = TEXT("BGEO Importer: BGEO file re-imported succesfully."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + return EReimportResult::Succeeded; +} + +int32 +UHoudiniGeoFactory::GetPriority() const +{ + return ImportPriority; +} + + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h index 8dd0a42cb..c8a03b737 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h @@ -1,83 +1,83 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "EditorReimportHandler.h" -#include "Factories/Factory.h" -#include "HoudiniGeoFactory.generated.h" - -class UClass; -class UObject; -class FFeedbackContext; - -UCLASS(config = Editor) -class UHoudiniGeoFactory : public UFactory, public FReimportHandler -{ - GENERATED_UCLASS_BODY() - - public: - - // - // UFactory Interface - // - // Returns the name of the factory for menus - virtual FText GetDisplayName() const override; - // return true if it supports this class - virtual bool DoesSupportClass(UClass * Class) override; - // - virtual UClass* ResolveSupportedClass() override; - // - //virtual UClass* ResolveSupportedClass() override; - // Return true if we can import the file - virtual bool FactoryCanImport(const FString& Filename) override; - - // Create a new object by importing it from a file name. - virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, - EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, - FFeedbackContext* Warn, bool& bOutOperationCanceled) override; - - // - // FReimportHandler Interface - // - UObject* Import(UClass* InClass, UPackage* OuterObject, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport); - - // Check to see if we have a handler to manage the reimporting of the object - virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; - - // Sets the reimport path(s) for the specified object - virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; - // Attempt to reimport the specified object from its source - virtual EReimportResult::Type Reimport(UObject * Obj) override; - - //void ShowImportOptionsWindow(TSharedPtr& Options, FString FilePath, const UHoudiniGeoImporter& Importer); - - virtual int32 GetPriority() const override; - - // - // - // -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "EditorReimportHandler.h" +#include "Factories/Factory.h" +#include "HoudiniGeoFactory.generated.h" + +class UClass; +class UObject; +class FFeedbackContext; + +UCLASS(config = Editor) +class UHoudiniGeoFactory : public UFactory, public FReimportHandler +{ + GENERATED_UCLASS_BODY() + + public: + + // + // UFactory Interface + // + // Returns the name of the factory for menus + virtual FText GetDisplayName() const override; + // return true if it supports this class + virtual bool DoesSupportClass(UClass * Class) override; + // + virtual UClass* ResolveSupportedClass() override; + // + //virtual UClass* ResolveSupportedClass() override; + // Return true if we can import the file + virtual bool FactoryCanImport(const FString& Filename) override; + + // Create a new object by importing it from a file name. + virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, + EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, + FFeedbackContext* Warn, bool& bOutOperationCanceled) override; + + // + // FReimportHandler Interface + // + UObject* Import(UClass* InClass, UPackage* OuterObject, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport); + + // Check to see if we have a handler to manage the reimporting of the object + virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; + + // Sets the reimport path(s) for the specified object + virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; + // Attempt to reimport the specified object from its source + virtual EReimportResult::Type Reimport(UObject * Obj) override; + + //void ShowImportOptionsWindow(TSharedPtr& Options, FString FilePath, const UHoudiniGeoImporter& Importer); + + virtual int32 GetPriority() const override; + + // + // + // +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp index 13b4057bd..12681e4ca 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp @@ -1,260 +1,260 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleComponentVisualizer.h" - -#include "EditorViewportClient.h" - -#include "HoudiniHandleTranslator.h" -#include "HoudiniAssetComponent.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_HIT_PROXY(HHoudiniHandleVisProxy, HComponentVisProxy); - -HHoudiniHandleVisProxy::HHoudiniHandleVisProxy(const UActorComponent * InComponent) - : HComponentVisProxy(InComponent, HPP_Wireframe) -{} - - -FHoudiniHandleComponentVisualizerCommands::FHoudiniHandleComponentVisualizerCommands() - : TCommands< FHoudiniHandleComponentVisualizerCommands >( - "HoudiniHandleComponentVisualizer", - FText::FromString("Houdini handle Component Visualizer"), - NAME_None, - FEditorStyle::GetStyleSetName()) -{} - -void -FHoudiniHandleComponentVisualizerCommands::RegisterCommands() -{} - - -FHoudiniHandleComponentVisualizer::FHoudiniHandleComponentVisualizer() - : FComponentVisualizer() - , EditedComponent(nullptr) - , bEditing(false) -{ - FHoudiniHandleComponentVisualizerCommands::Register(); - VisualizerActions = MakeShareable(new FUICommandList); -} - -FHoudiniHandleComponentVisualizer::~FHoudiniHandleComponentVisualizer() -{ - FHoudiniHandleComponentVisualizerCommands::Unregister(); -} - -void -FHoudiniHandleComponentVisualizer::DrawVisualization(const UActorComponent * Component, - const FSceneView * View, FPrimitiveDrawInterface * PDI) -{ - const UHoudiniHandleComponent* HandleComponent = Cast(Component); - - if (!HandleComponent) - return; - - UHoudiniAssetComponent* HAC = Cast(HandleComponent->GetOuter()); - - if (!HAC) - return; - - - static TMap ColorMapActive; - static TMap ColorMapInactive; - - - int32 AssetId = HAC->GetAssetId(); - - if (!ColorMapActive.Contains(AssetId) || !ColorMapInactive.Contains(AssetId)) - { - FLinearColor NewActiveColor = FLinearColor::MakeRandomColor(); - FLinearColor NewInactiveColor = NewActiveColor.CopyWithNewOpacity(0.1)/2.5f; - - ColorMapActive.Add(AssetId, NewActiveColor); - ColorMapInactive.Add(AssetId, NewInactiveColor); - } - - - const FLinearColor& ActiveColor = ColorMapActive[AssetId]; - const FLinearColor& InactiveColor = ColorMapInactive[AssetId]; - - bool IsActive = EditedComponent != nullptr; - - if (IsActive) - { - UHoudiniAssetComponent* EditedComponentParent = Cast(EditedComponent->GetOuter()); - IsActive &= EditedComponentParent && EditedComponentParent->GetAssetId() == HAC->GetAssetId(); - } - - // Draw point and set hit box for it. - PDI->SetHitProxy(new HHoudiniHandleVisProxy(HandleComponent)); - { - static const float GrabHandleSizeActive = 24.0f; - static const float GrabHandleSizeInactive = 18.0f; - - PDI->DrawPoint(HandleComponent->GetComponentTransform().GetLocation(), IsActive ? ActiveColor : InactiveColor, IsActive ? GrabHandleSizeActive : GrabHandleSizeInactive, SDPG_Foreground); - } - - if (HandleComponent->HandleType == EHoudiniHandleType::Bounder) - { - // draw the scale box - FTransform BoxTransform = HandleComponent->GetComponentTransform(); - const float BoxRad = 50.f; - const FBox Box(FVector(-BoxRad, -BoxRad, -BoxRad), FVector(BoxRad, BoxRad, BoxRad)); - DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), Box, IsActive ? ActiveColor : InactiveColor, SDPG_Foreground); - } - - PDI->SetHitProxy(nullptr); -} - -bool -FHoudiniHandleComponentVisualizer::VisProxyHandleClick( - FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) -{ - bEditing = false; - - bAllowTranslate = false; - bAllowRotation = false; - bAllowScale = false; - - - if (VisProxy && VisProxy->Component.IsValid()) - { - const UHoudiniHandleComponent * Component = - CastChecked< const UHoudiniHandleComponent >(VisProxy->Component.Get()); - - const TArray &XformParms = Component->XformParms; - - if (!Component->CheckHandleValid()) - return bEditing; - - EditedComponent = const_cast(Component); - - if (Component) - { - if (VisProxy->IsA(HHoudiniHandleVisProxy::StaticGetType())) - bEditing = true; - - bAllowTranslate = - XformParms[int32(EXformParameter::TX)]->AssetParameter || - XformParms[int32(EXformParameter::TY)]->AssetParameter || - XformParms[int32(EXformParameter::TZ)]->AssetParameter; - - bAllowRotation = - XformParms[int32(EXformParameter::RX)]->AssetParameter || - XformParms[int32(EXformParameter::RY)]->AssetParameter || - XformParms[int32(EXformParameter::RZ)]->AssetParameter; - - bAllowScale = - XformParms[int32(EXformParameter::SX)]->AssetParameter || - XformParms[int32(EXformParameter::SY)]->AssetParameter || - XformParms[int32(EXformParameter::SZ)]->AssetParameter; - } - } - - return bEditing; -} - -void -FHoudiniHandleComponentVisualizer::EndEditing() -{ - EditedComponent = nullptr; -} - -bool -FHoudiniHandleComponentVisualizer::GetWidgetLocation( - const FEditorViewportClient * ViewportClient, - FVector & OutLocation) const -{ - if (EditedComponent) - { - OutLocation = EditedComponent->GetComponentTransform().GetLocation(); - return true; - } - - return false; -} - -bool -FHoudiniHandleComponentVisualizer::GetCustomInputCoordinateSystem( - const FEditorViewportClient * ViewportClient, - FMatrix & OutMatrix) const -{ - if (EditedComponent && ViewportClient->GetWidgetMode() == FWidget::WM_Scale) - { - OutMatrix = FRotationMatrix::Make(EditedComponent->GetComponentTransform().GetRotation()); - return true; - } - else - { - return false; - } -} - -bool -FHoudiniHandleComponentVisualizer::HandleInputDelta( - FEditorViewportClient * ViewportClient, FViewport * Viewport, - FVector& DeltaTranslate, FRotator & DeltaRotate, FVector & DeltaScale) -{ - if (!EditedComponent) - return false; - - if (!DeltaTranslate.IsZero() && bAllowTranslate) - { - EditedComponent->SetRelativeLocation(EditedComponent->GetRelativeTransform().GetLocation() + DeltaTranslate); - } - - if (!DeltaRotate.IsZero() && bAllowRotation) - { - EditedComponent->SetRelativeRotation(DeltaRotate.Quaternion() * EditedComponent->GetRelativeTransform().GetRotation()); - } - - if (!DeltaScale.IsZero() && bAllowScale) - { - EditedComponent->SetRelativeScale3D(EditedComponent->GetRelativeTransform().GetScale3D() + DeltaScale); - } - - return true; -} - -bool -FHoudiniHandleComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) -{ - if (EditedComponent) - { - if (Key == EKeys::LeftMouseButton && Event == IE_Released) - { - if (GEditor) - GEditor->RedrawLevelEditingViewports(true); - - FHoudiniHandleTranslator::UpdateTransformParameters(EditedComponent); - } - - } - return false; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleComponentVisualizer.h" + +#include "EditorViewportClient.h" + +#include "HoudiniHandleTranslator.h" +#include "HoudiniAssetComponent.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_HIT_PROXY(HHoudiniHandleVisProxy, HComponentVisProxy); + +HHoudiniHandleVisProxy::HHoudiniHandleVisProxy(const UActorComponent * InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) +{} + + +FHoudiniHandleComponentVisualizerCommands::FHoudiniHandleComponentVisualizerCommands() + : TCommands< FHoudiniHandleComponentVisualizerCommands >( + "HoudiniHandleComponentVisualizer", + FText::FromString("Houdini handle Component Visualizer"), + NAME_None, + FEditorStyle::GetStyleSetName()) +{} + +void +FHoudiniHandleComponentVisualizerCommands::RegisterCommands() +{} + + +FHoudiniHandleComponentVisualizer::FHoudiniHandleComponentVisualizer() + : FComponentVisualizer() + , EditedComponent(nullptr) + , bEditing(false) +{ + FHoudiniHandleComponentVisualizerCommands::Register(); + VisualizerActions = MakeShareable(new FUICommandList); +} + +FHoudiniHandleComponentVisualizer::~FHoudiniHandleComponentVisualizer() +{ + FHoudiniHandleComponentVisualizerCommands::Unregister(); +} + +void +FHoudiniHandleComponentVisualizer::DrawVisualization(const UActorComponent * Component, + const FSceneView * View, FPrimitiveDrawInterface * PDI) +{ + const UHoudiniHandleComponent* HandleComponent = Cast(Component); + + if (!HandleComponent) + return; + + UHoudiniAssetComponent* HAC = Cast(HandleComponent->GetOuter()); + + if (!HAC) + return; + + + static TMap ColorMapActive; + static TMap ColorMapInactive; + + + int32 AssetId = HAC->GetAssetId(); + + if (!ColorMapActive.Contains(AssetId) || !ColorMapInactive.Contains(AssetId)) + { + FLinearColor NewActiveColor = FLinearColor::MakeRandomColor(); + FLinearColor NewInactiveColor = NewActiveColor.CopyWithNewOpacity(0.1)/2.5f; + + ColorMapActive.Add(AssetId, NewActiveColor); + ColorMapInactive.Add(AssetId, NewInactiveColor); + } + + + const FLinearColor& ActiveColor = ColorMapActive[AssetId]; + const FLinearColor& InactiveColor = ColorMapInactive[AssetId]; + + bool IsActive = EditedComponent != nullptr; + + if (IsActive) + { + UHoudiniAssetComponent* EditedComponentParent = Cast(EditedComponent->GetOuter()); + IsActive &= EditedComponentParent && EditedComponentParent->GetAssetId() == HAC->GetAssetId(); + } + + // Draw point and set hit box for it. + PDI->SetHitProxy(new HHoudiniHandleVisProxy(HandleComponent)); + { + static const float GrabHandleSizeActive = 24.0f; + static const float GrabHandleSizeInactive = 18.0f; + + PDI->DrawPoint(HandleComponent->GetComponentTransform().GetLocation(), IsActive ? ActiveColor : InactiveColor, IsActive ? GrabHandleSizeActive : GrabHandleSizeInactive, SDPG_Foreground); + } + + if (HandleComponent->HandleType == EHoudiniHandleType::Bounder) + { + // draw the scale box + FTransform BoxTransform = HandleComponent->GetComponentTransform(); + const float BoxRad = 50.f; + const FBox Box(FVector(-BoxRad, -BoxRad, -BoxRad), FVector(BoxRad, BoxRad, BoxRad)); + DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), Box, IsActive ? ActiveColor : InactiveColor, SDPG_Foreground); + } + + PDI->SetHitProxy(nullptr); +} + +bool +FHoudiniHandleComponentVisualizer::VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) +{ + bEditing = false; + + bAllowTranslate = false; + bAllowRotation = false; + bAllowScale = false; + + + if (VisProxy && VisProxy->Component.IsValid()) + { + const UHoudiniHandleComponent * Component = + CastChecked< const UHoudiniHandleComponent >(VisProxy->Component.Get()); + + const TArray &XformParms = Component->XformParms; + + if (!Component->CheckHandleValid()) + return bEditing; + + EditedComponent = const_cast(Component); + + if (Component) + { + if (VisProxy->IsA(HHoudiniHandleVisProxy::StaticGetType())) + bEditing = true; + + bAllowTranslate = + XformParms[int32(EXformParameter::TX)]->AssetParameter || + XformParms[int32(EXformParameter::TY)]->AssetParameter || + XformParms[int32(EXformParameter::TZ)]->AssetParameter; + + bAllowRotation = + XformParms[int32(EXformParameter::RX)]->AssetParameter || + XformParms[int32(EXformParameter::RY)]->AssetParameter || + XformParms[int32(EXformParameter::RZ)]->AssetParameter; + + bAllowScale = + XformParms[int32(EXformParameter::SX)]->AssetParameter || + XformParms[int32(EXformParameter::SY)]->AssetParameter || + XformParms[int32(EXformParameter::SZ)]->AssetParameter; + } + } + + return bEditing; +} + +void +FHoudiniHandleComponentVisualizer::EndEditing() +{ + EditedComponent = nullptr; +} + +bool +FHoudiniHandleComponentVisualizer::GetWidgetLocation( + const FEditorViewportClient * ViewportClient, + FVector & OutLocation) const +{ + if (EditedComponent) + { + OutLocation = EditedComponent->GetComponentTransform().GetLocation(); + return true; + } + + return false; +} + +bool +FHoudiniHandleComponentVisualizer::GetCustomInputCoordinateSystem( + const FEditorViewportClient * ViewportClient, + FMatrix & OutMatrix) const +{ + if (EditedComponent && ViewportClient->GetWidgetMode() == FWidget::WM_Scale) + { + OutMatrix = FRotationMatrix::Make(EditedComponent->GetComponentTransform().GetRotation()); + return true; + } + else + { + return false; + } +} + +bool +FHoudiniHandleComponentVisualizer::HandleInputDelta( + FEditorViewportClient * ViewportClient, FViewport * Viewport, + FVector& DeltaTranslate, FRotator & DeltaRotate, FVector & DeltaScale) +{ + if (!EditedComponent) + return false; + + if (!DeltaTranslate.IsZero() && bAllowTranslate) + { + EditedComponent->SetRelativeLocation(EditedComponent->GetRelativeTransform().GetLocation() + DeltaTranslate); + } + + if (!DeltaRotate.IsZero() && bAllowRotation) + { + EditedComponent->SetRelativeRotation(DeltaRotate.Quaternion() * EditedComponent->GetRelativeTransform().GetRotation()); + } + + if (!DeltaScale.IsZero() && bAllowScale) + { + EditedComponent->SetRelativeScale3D(EditedComponent->GetRelativeTransform().GetScale3D() + DeltaScale); + } + + return true; +} + +bool +FHoudiniHandleComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) +{ + if (EditedComponent) + { + if (Key == EKeys::LeftMouseButton && Event == IE_Released) + { + if (GEditor) + GEditor->RedrawLevelEditingViewports(true); + + FHoudiniHandleTranslator::UpdateTransformParameters(EditedComponent); + } + + } + return false; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h index 5eed6461b..55fe29a57 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h @@ -1,114 +1,114 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ComponentVisualizer.h" -#include "Framework/Commands/Commands.h" -#include "Framework/Commands/UICommandList.h" - -#include "Components/ActorComponent.h" -#include "HoudiniHandleComponent.h" - -/** Base class for clickable editing proxies. **/ -struct HHoudiniHandleVisProxy : public HComponentVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniHandleVisProxy(const UActorComponent * InComponent); -}; - -/** Define commands for our component visualizer */ -class FHoudiniHandleComponentVisualizerCommands : public TCommands< FHoudiniHandleComponentVisualizerCommands > -{ -public: - - /** Constructor. **/ - FHoudiniHandleComponentVisualizerCommands(); - - /** Register commands. **/ - virtual void RegisterCommands() override; - -public: - - /** Command for adding a control point. **/ - TSharedPtr< FUICommandInfo > CommandAddControlPoint; - - /** Command for deleting a control point. **/ - TSharedPtr< FUICommandInfo > CommandDeleteControlPoint; -}; - - -/** Our handle visualizer. **/ -class FHoudiniHandleComponentVisualizer : public FComponentVisualizer -{ -public: - FHoudiniHandleComponentVisualizer(); - - virtual ~FHoudiniHandleComponentVisualizer(); - - /** FComponentVisualizer methods. **/ - - /** Draw visualization for the given component. **/ - virtual void DrawVisualization( - const UActorComponent * Component, const FSceneView * View, - FPrimitiveDrawInterface * PDI) override; - - /** Handle a click on a registered hit box. **/ - virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; - - virtual void EndEditing(); - - /** Returns location of a gizmo widget. **/ - virtual bool GetWidgetLocation( - const FEditorViewportClient *, FVector & OutLocation) const override; - - virtual bool GetCustomInputCoordinateSystem( - const FEditorViewportClient * ViewportClient, FMatrix & OutMatrix) const override; - - /** Handle input change. **/ - virtual bool HandleInputDelta( - FEditorViewportClient *, FViewport *, FVector & DeltaTranslate, - FRotator & DeltaRotate, FVector & DeltaScale) override; - - virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; - - void SetEditedComponent(UHoudiniHandleComponent* InComponent) { EditedComponent = InComponent; }; - void ClearEditedComponent() { EditedComponent = nullptr; }; - - -protected: - /** Visualizer actions. **/ - TSharedPtr< FUICommandList > VisualizerActions; - - /** Houdini component which is being edited. **/ - UHoudiniHandleComponent* EditedComponent; - - /** Is set to true if we are editing. **/ - uint32 bEditing : 1; - uint32 bAllowTranslate : 1; - uint32 bAllowRotation : 1; - uint32 bAllowScale : 1; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ComponentVisualizer.h" +#include "Framework/Commands/Commands.h" +#include "Framework/Commands/UICommandList.h" + +#include "Components/ActorComponent.h" +#include "HoudiniHandleComponent.h" + +/** Base class for clickable editing proxies. **/ +struct HHoudiniHandleVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniHandleVisProxy(const UActorComponent * InComponent); +}; + +/** Define commands for our component visualizer */ +class FHoudiniHandleComponentVisualizerCommands : public TCommands< FHoudiniHandleComponentVisualizerCommands > +{ +public: + + /** Constructor. **/ + FHoudiniHandleComponentVisualizerCommands(); + + /** Register commands. **/ + virtual void RegisterCommands() override; + +public: + + /** Command for adding a control point. **/ + TSharedPtr< FUICommandInfo > CommandAddControlPoint; + + /** Command for deleting a control point. **/ + TSharedPtr< FUICommandInfo > CommandDeleteControlPoint; +}; + + +/** Our handle visualizer. **/ +class FHoudiniHandleComponentVisualizer : public FComponentVisualizer +{ +public: + FHoudiniHandleComponentVisualizer(); + + virtual ~FHoudiniHandleComponentVisualizer(); + + /** FComponentVisualizer methods. **/ + + /** Draw visualization for the given component. **/ + virtual void DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI) override; + + /** Handle a click on a registered hit box. **/ + virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; + + virtual void EndEditing(); + + /** Returns location of a gizmo widget. **/ + virtual bool GetWidgetLocation( + const FEditorViewportClient *, FVector & OutLocation) const override; + + virtual bool GetCustomInputCoordinateSystem( + const FEditorViewportClient * ViewportClient, FMatrix & OutMatrix) const override; + + /** Handle input change. **/ + virtual bool HandleInputDelta( + FEditorViewportClient *, FViewport *, FVector & DeltaTranslate, + FRotator & DeltaRotate, FVector & DeltaScale) override; + + virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; + + void SetEditedComponent(UHoudiniHandleComponent* InComponent) { EditedComponent = InComponent; }; + void ClearEditedComponent() { EditedComponent = nullptr; }; + + +protected: + /** Visualizer actions. **/ + TSharedPtr< FUICommandList > VisualizerActions; + + /** Houdini component which is being edited. **/ + UHoudiniHandleComponent* EditedComponent; + + /** Is set to true if we are editing. **/ + uint32 bEditing : 1; + uint32 bAllowTranslate : 1; + uint32 bAllowRotation : 1; + uint32 bAllowScale : 1; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp index 613c1f094..656eeb082 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp @@ -1,397 +1,397 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleDetails.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniHandleComponent.h" -#include "HoudiniHandleTranslator.h" -#include "HoudiniHandleComponentVisualizer.h" - -#include "DetailCategoryBuilder.h" -#include "DetailWidgetRow.h" -#include "IDetailGroup.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Images/SImage.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -void -FHoudiniHandleDetails::CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles) -{ - - if (InHandles.Num() <= 0) - return; - - UHoudiniHandleComponent* MainHandle = InHandles[0]; - - if (!MainHandle || MainHandle->IsPendingKill()) - return; - - - FString HandleNameStr = MainHandle->GetHandleName() + TEXT(" (") + GetHandleTypeString(MainHandle->GetHandleType()) + TEXT(" )"); - FName HandleName = FName(*HandleNameStr); - IDetailGroup& Group = HouHandleCategory.AddGroup(HandleName, FText::FromString(HandleNameStr), false, false); - - // Create a widget row for this handle - FDetailWidgetRow& Row = Group.AddWidgetRow(); - - CreateNameWidget(Row); - - // Create value widget - - TSharedRef ValueWidgetVerticalBox = SNew(SVerticalBox); - - // Translate - auto OnLocationChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FVector Location = MainHandle->GetRelativeTransform().GetLocation(); - - if (Axis == 0) - Location.X = Val; - else if (Axis == 1) - Location.Y = Val; - else - Location.Z = Val; - - MainHandle->SetRelativeLocation(Location); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertLocationToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - FVector DefaultLocation = FVector(0.f, 0.f, 0.f); - MainHandle->SetRelativeLocation(DefaultLocation); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Z; }) - .OnXCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 0); - }) - .OnYCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 1); - }) - .OnZCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeLocation() == FVector::ZeroVector) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertLocationToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - // Rotation - auto OnRotationChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FQuat Rotation = MainHandle->GetRelativeTransform().GetRotation(); - - if (Axis == 0) - Rotation.X = Val; - else if (Axis == 1) - Rotation.Y = Val; - else - Rotation.Z = Val; - - MainHandle->SetRelativeRotation(Rotation); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertRotationToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - MainHandle->SetRelativeRotation(FQuat::Identity); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Z; }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 0); - }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 1); - }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeTransform().GetRotation() == FQuat::Identity) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertRotationToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - // Scale - auto OnScaleChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FVector Scale = MainHandle->GetRelativeTransform().GetScale3D(); - - if (Axis == 0) - Scale.X = Val; - else if (Axis == 1) - Scale.Y = Val; - else - Scale.Z = Val; - - MainHandle->SetRelativeScale3D(Scale); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertScaleToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - MainHandle->SetRelativeScale3D(FVector::OneVector); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Z; }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 0); - }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 1); - }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeTransform().GetScale3D() == FVector::OneVector) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertScaleToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - Row.ValueWidget.Widget = ValueWidgetVerticalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - - auto OnMouseEnterLambda = [MainHandle](const FGeometry& Geometry, const FPointerEvent& Event) - { - if (!MainHandle) - return; - - TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); - - if (HandleVisualizer.IsValid()) - { - HandleVisualizer->SetEditedComponent(MainHandle); - } - }; - - auto OnMouseLeaveLambda = [MainHandle](const FPointerEvent& Event) - { - if (!MainHandle) - return; - - TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); - - if (HandleVisualizer.IsValid()) - { - HandleVisualizer->SetEditedComponent(nullptr); - } - }; - - // Set on mouse leave UI widget callback functions - Row.NameWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); - Row.ValueWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); - - Row.NameWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); - Row.ValueWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); -} - -void -FHoudiniHandleDetails::CreateNameWidget(FDetailWidgetRow& Row) -{ - TSharedRef VerticalBox = SNew(SVerticalBox); - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Translate")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Rotation")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Scale")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - - Row.NameWidget.Widget = VerticalBox; -} - -FString -FHoudiniHandleDetails::GetHandleTypeString(const EHoudiniHandleType& HandleType) -{ - switch (HandleType) - { - case EHoudiniHandleType::Bounder: - return FString("Bounder"); - case EHoudiniHandleType::Xform: - return FString("Xform"); - case EHoudiniHandleType::Unsupported: - return FString("Unsupported"); - - default: - break; - } - - return FString(""); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleDetails.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniHandleTranslator.h" +#include "HoudiniHandleComponentVisualizer.h" + +#include "DetailCategoryBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailGroup.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Images/SImage.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +void +FHoudiniHandleDetails::CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles) +{ + + if (InHandles.Num() <= 0) + return; + + UHoudiniHandleComponent* MainHandle = InHandles[0]; + + if (!MainHandle || MainHandle->IsPendingKill()) + return; + + + FString HandleNameStr = MainHandle->GetHandleName() + TEXT(" (") + GetHandleTypeString(MainHandle->GetHandleType()) + TEXT(" )"); + FName HandleName = FName(*HandleNameStr); + IDetailGroup& Group = HouHandleCategory.AddGroup(HandleName, FText::FromString(HandleNameStr), false, false); + + // Create a widget row for this handle + FDetailWidgetRow& Row = Group.AddWidgetRow(); + + CreateNameWidget(Row); + + // Create value widget + + TSharedRef ValueWidgetVerticalBox = SNew(SVerticalBox); + + // Translate + auto OnLocationChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FVector Location = MainHandle->GetRelativeTransform().GetLocation(); + + if (Axis == 0) + Location.X = Val; + else if (Axis == 1) + Location.Y = Val; + else + Location.Z = Val; + + MainHandle->SetRelativeLocation(Location); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertLocationToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + FVector DefaultLocation = FVector(0.f, 0.f, 0.f); + MainHandle->SetRelativeLocation(DefaultLocation); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Z; }) + .OnXCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 0); + }) + .OnYCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 1); + }) + .OnZCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeLocation() == FVector::ZeroVector) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertLocationToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + // Rotation + auto OnRotationChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FQuat Rotation = MainHandle->GetRelativeTransform().GetRotation(); + + if (Axis == 0) + Rotation.X = Val; + else if (Axis == 1) + Rotation.Y = Val; + else + Rotation.Z = Val; + + MainHandle->SetRelativeRotation(Rotation); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertRotationToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + MainHandle->SetRelativeRotation(FQuat::Identity); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Z; }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 0); + }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 1); + }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeTransform().GetRotation() == FQuat::Identity) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertRotationToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + // Scale + auto OnScaleChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FVector Scale = MainHandle->GetRelativeTransform().GetScale3D(); + + if (Axis == 0) + Scale.X = Val; + else if (Axis == 1) + Scale.Y = Val; + else + Scale.Z = Val; + + MainHandle->SetRelativeScale3D(Scale); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertScaleToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + MainHandle->SetRelativeScale3D(FVector::OneVector); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Z; }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 0); + }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 1); + }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeTransform().GetScale3D() == FVector::OneVector) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertScaleToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + Row.ValueWidget.Widget = ValueWidgetVerticalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + + auto OnMouseEnterLambda = [MainHandle](const FGeometry& Geometry, const FPointerEvent& Event) + { + if (!MainHandle) + return; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); + + if (HandleVisualizer.IsValid()) + { + HandleVisualizer->SetEditedComponent(MainHandle); + } + }; + + auto OnMouseLeaveLambda = [MainHandle](const FPointerEvent& Event) + { + if (!MainHandle) + return; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); + + if (HandleVisualizer.IsValid()) + { + HandleVisualizer->SetEditedComponent(nullptr); + } + }; + + // Set on mouse leave UI widget callback functions + Row.NameWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); + Row.ValueWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); + + Row.NameWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); + Row.ValueWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); +} + +void +FHoudiniHandleDetails::CreateNameWidget(FDetailWidgetRow& Row) +{ + TSharedRef VerticalBox = SNew(SVerticalBox); + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Translate")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Rotation")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Scale")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + + Row.NameWidget.Widget = VerticalBox; +} + +FString +FHoudiniHandleDetails::GetHandleTypeString(const EHoudiniHandleType& HandleType) +{ + switch (HandleType) + { + case EHoudiniHandleType::Bounder: + return FString("Bounder"); + case EHoudiniHandleType::Xform: + return FString("Xform"); + case EHoudiniHandleType::Unsupported: + return FString("Unsupported"); + + default: + break; + } + + return FString(""); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h index 14dfd7c8c..b56257c67 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "DetailWidgetRow.h" - -class UHoudiniHandleComponent; -class IDetailCategoryBuilder; -enum class EHoudiniHandleType : uint8; - -class FHoudiniHandleDetails : public TSharedFromThis -{ -public: - static void CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles); - - static void CreateNameWidget(FDetailWidgetRow& Row); - - static FString GetHandleTypeString(const EHoudiniHandleType& HandleType); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "DetailWidgetRow.h" + +class UHoudiniHandleComponent; +class IDetailCategoryBuilder; +enum class EHoudiniHandleType : uint8; + +class FHoudiniHandleDetails : public TSharedFromThis +{ +public: + static void CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles); + + static void CreateNameWidget(FDetailWidgetRow& Row); + + static FString GetHandleTypeString(const EHoudiniHandleType& HandleType); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp index 78d89d155..1efc1ec3e 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp @@ -1,5024 +1,5024 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInputDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniInput.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetBlueprintComponent.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniPackageParams.h" - -#include "Editor.h" -#include "DetailLayoutBuilder.h" -#include "DetailCategoryBuilder.h" -#include "IDetailCustomization.h" -#include "DetailWidgetRow.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SRotatorInputBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SNumericEntryBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SEditableText.h" -#include "Widgets/Layout/SUniformGridPanel.h" -#include "Editor/UnrealEd/Public/AssetThumbnail.h" -#include "Editor/PropertyEditor/Private/SDetailsViewBase.h" -#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" -#include "SAssetDropTarget.h" -#include "ScopedTransaction.h" -#include "Engine/SkeletalMesh.h" -#include "Engine/Selection.h" -#include "EngineUtils.h" -#include "AssetData.h" -#include "Framework/SlateDelegates.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "Modules/ModuleManager.h" -#include "SceneOutlinerModule.h" - -#include "Editor/UnrealEdEngine.h" -#include "HoudiniSplineComponentVisualizer.h" -#include "UnrealEdGlobals.h" -#include "Widgets/SWidget.h" - -#include "HoudiniEngineRuntimeUtils.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// Customized TextBlock to show 'editing...' text if this Houdini Spline Component is being edited -class SCurveEditingTextBlock : public STextBlock -{ -public: - UHoudiniSplineComponent* HoudiniSplineComponent; - TSharedPtr HoudiniSplineComponentVisualizer; -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override - { - if (!HoudiniSplineComponentVisualizer.IsValid() || !HoudiniSplineComponent) - return LayerId; - - if (HoudiniSplineComponentVisualizer->GetEditedHoudiniSplineComponent() != HoudiniSplineComponent) - return LayerId; - - return STextBlock::OnPaint(Args, AllottedGeometry, MyClippingRect, - OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - } -}; - -void -FHoudiniInputDetails::CreateWidget( - IDetailCategoryBuilder& HouInputCategory, - TArray InInputs, - FDetailWidgetRow* InputRow) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouInputCategory.GetParentLayout().GetThumbnailPool(); - - EHoudiniInputType MainInputType = MainInput->GetInputType(); - UHoudiniAssetComponent* HAC = MainInput->GetTypedOuter(); - - // Create a widget row, or get the given row. - FDetailWidgetRow* Row = InputRow; - Row = InputRow == nullptr ? &(HouInputCategory.AddCustomRow(FText::GetEmpty())) : InputRow; - if (!Row) - return; - - // Create the standard input name widget if this is not a operator path parameter. - // Operator path parameter's name widget is handled by HoudiniParameterDetails. - if (!InputRow) - CreateNameWidget(MainInput, *Row, true, InInputs.Num()); - - // Create a vertical Box for storing the UI - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - // ComboBox : Input Type - const IDetailsView* DetailsView = HouInputCategory.GetParentLayout().GetDetailsView(); - AddInputTypeComboBox(HouInputCategory, VerticalBox, InInputs, DetailsView); - - // Checkbox : Keep World Transform - AddKeepWorldTransformCheckBox(VerticalBox, InInputs); - - - // Checkbox : CurveInput trigger cook on curve changed - AddCurveInputCookOnChangeCheckBox(VerticalBox, InInputs); - - - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) - { - // Checkbox : Pack before merging - AddPackBeforeMergeCheckbox(VerticalBox, InInputs); - } - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World || MainInputType == EHoudiniInputType::Asset) - { - AddImportAsReferenceCheckbox(VerticalBox, InInputs); - } - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) - { - // Checkboxes : Export LODs / Sockets / Collisions - AddExportCheckboxes(VerticalBox, InInputs); - } - - switch (MainInput->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - AddGeometryInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); - } - break; - - case EHoudiniInputType::Asset: - { - AddAssetInputUI(VerticalBox, InInputs); - } - break; - - case EHoudiniInputType::Curve: - { - AddCurveInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); - } - break; - - case EHoudiniInputType::Landscape: - { - AddLandscapeInputUI(VerticalBox, InInputs); - } - break; - - case EHoudiniInputType::World: - { - AddWorldInputUI(HouInputCategory, VerticalBox, InInputs, DetailsView); - } - break; - - case EHoudiniInputType::Skeletal: - { - AddSkeletalInputUI(VerticalBox, InInputs, AssetThumbnailPool); - } - break; - } - - - Row->ValueWidget.Widget = VerticalBox; - - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - //Row.ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniInputDetails::CreateNameWidget( - UHoudiniInput* InInput, FDetailWidgetRow & Row, bool bLabel, int32 InInputCount) -{ - if (!InInput || InInput->IsPendingKill()) - return; - - FString InputLabelStr = InInput->GetLabel(); - if (InInputCount > 1) - { - InputLabelStr += TEXT(" (") + FString::FromInt(InInputCount) + TEXT(")"); - } - - const FText & FinalInputLabelText = bLabel ? FText::FromString(InputLabelStr) : FText::GetEmpty(); - FText InputTooltip = GetInputTooltip(InInput); - { - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(FinalInputLabelText) - .ToolTipText(InputTooltip) - .Font(FEditorStyle::GetFontStyle(!InInput->HasChanged() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))); - } -} - -FText -FHoudiniInputDetails::GetInputTooltip(UHoudiniInput* InParam) -{ - // TODO - return FText(); -} - -void -FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuilder, TSharedRef VerticalBox, TArray& InInputs, const IDetailsView* DetailsView) -{ - // Get the details view name and locked status - bool bDetailsLocked = false; - FName DetailsPanelName = "LevelEditorSelectionDetails"; - if (DetailsView) - { - DetailsPanelName = DetailsView->GetIdentifier(); - if (DetailsView->IsLocked()) - bDetailsLocked = true; - } - - // Lambda return a FText correpsonding to an input's current type - auto GetInputText = [](UHoudiniInput* InInput) - { - return FText::FromString(InInput->GetInputTypeAsString()); - }; - - // Lambda for changing inputs type - auto OnSelChanged = [DetailsPanelName, &CategoryBuilder](TArray InInputsToUpdate, TSharedPtr InNewChoice) - { - if (!InNewChoice.IsValid()) - return; - - EHoudiniInputType NewInputType = UHoudiniInput::StringToInputType(*InNewChoice.Get()); - if (NewInputType != EHoudiniInputType::World) - { - Helper_CancelWorldSelection(InInputsToUpdate, DetailsPanelName); - } - - if (InInputsToUpdate.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputsToUpdate[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetBlueprintComponent* HAB = MainInput->GetTypedOuter(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Input Type"), - MainInput->GetOuter()); - - bool bBlueprintStructureModified = false; - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetInputType() == NewInputType) - continue; - - /* This causes multiple issues. It does not set reset the previous type variable to Invalid sometimes - and it causes re-cook infinitely after few undo changing type. - { - CurInput->SetInputType(NewInputType); - CurInput->Modify(); - } - */ - - { - // Cache the current input type for undo type changing (since new type becomes previous type after undo) - EHoudiniInputType PrevType = CurInput->GetPreviousInputType(); - CurInput->SetPreviousInputType(NewInputType); - - CurInput->Modify(); - CurInput->SetPreviousInputType(PrevType); - CurInput->SetInputType(NewInputType, bBlueprintStructureModified); // pass in false for 2nd parameter in order to avoid creating default curve if empty - } - CurInput->MarkChanged(true); - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - - } - - if (HAB) - { - if (bBlueprintStructureModified) - HAB->MarkAsBlueprintStructureModified(); - } - - }; - - UHoudiniInput* MainInput = InInputs[0]; - TArray>* SupportedChoices = nullptr; - UHoudiniAssetBlueprintComponent* HAC = MainInput->GetTypedOuter(); - if (HAC) - { - SupportedChoices = FHoudiniEngineEditor::Get().GetBlueprintInputTypeChoiceLabels(); - } - else - { - SupportedChoices = FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels(); - } - - // ComboBox : Input Type - TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBoxInputType; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(ComboBoxInputType, SComboBox>) - .OptionsSource(SupportedChoices) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels())[((int32)MainInput->GetInputType() - 1)]) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([=](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnSelChanged(InInputs, NewChoice); - }) - [ - SNew( STextBlock ) - .Text_Lambda([=]() - { - return GetInputText(MainInput); - }) - .Font( FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; -} - -void -FHoudiniInputDetails:: AddCurveInputCookOnChangeCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - - if (!MainInput || MainInput->GetInputType() != EHoudiniInputType::Curve) - return; - - auto IsCheckedCookOnChange = [MainInput]() - { - if (!MainInput) - return ECheckBoxState::Checked; - - return MainInput->GetCookOnCurveChange() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto CheckStateChangedCookOnChange = [InInputs](ECheckBoxState NewState) - { - bool bChecked = NewState == ECheckBoxState::Checked; - for (auto & NextInput : InInputs) - { - if (!NextInput) - continue; - - NextInput->SetCookOnCurveChange(bChecked); - } - }; - - // Checkbox : Trigger cook on input curve changed - TSharedPtr< SCheckBox > CheckBoxCookOnCurveChanged; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxCookOnCurveChanged, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookOnCurveChangedCheckbox", "Auto-update")) - .ToolTipText(LOCTEXT("CookOnCurveChangeCheckboxTip", "When checked, cook is triggered automatically when the curve is modified.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedCookOnChange, MainInput]() - { - return IsCheckedCookOnChange(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedCookOnChange](ECheckBoxState NewState) - { - return CheckStateChangedCookOnChange( NewState); - }) - ]; - -} - -void -FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current KeepWorldTransform state - auto IsCheckedKeepWorldTransform = [&](UHoudiniInput* InInput) - { - return InInput->GetKeepWorldTransform() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing KeepWorldTransform state - auto CheckStateChangedKeepWorldTransform = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (MainInput->GetKeepWorldTransform() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Keep World Transform"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetKeepWorldTransform() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetKeepWorldTransform(bNewState); - CurInput->MarkChanged(true); - } - }; - - - // Checkbox : Keep World Transform - TSharedPtr< SCheckBox > CheckBoxTranformType; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxTranformType, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("KeepWorldTransformCheckbox", "Keep World Transform")) - .ToolTipText(LOCTEXT("KeepWorldTransformCheckboxTip", "Set this Input's object_merge Transform Type to INTO_THIS_OBJECT. If unchecked, it will be set to NONE.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedKeepWorldTransform(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedKeepWorldTransform(InInputs, NewState); - }) - ]; - - // the checkbox is read only for geo inputs - if (MainInput->GetInputType() == EHoudiniInputType::Geometry) - CheckBoxTranformType->SetEnabled(false); - - // Checkbox is read only if the input is an object-path parameter - //if (MainInput->IsObjectPathParameter() ) - // CheckBoxTranformType->SetEnabled(false); -} - -void -FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current PackBeforeMerge state - auto IsCheckedPackBeforeMerge = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetPackBeforeMerge() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing PackBeforeMerge state - auto CheckStateChangedPackBeforeMerge = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetPackBeforeMerge() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetPackBeforeMerge() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetPackBeforeMerge(bNewState); - CurInput->MarkChanged(true); - } - }; - - TSharedPtr< SCheckBox > CheckBoxPackBeforeMerge; - VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() - [ - SAssignNew( CheckBoxPackBeforeMerge, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "PackBeforeMergeCheckbox", "Pack Geometry before merging" ) ) - .ToolTipText( LOCTEXT( "PackBeforeMergeCheckboxTip", "Pack each separate piece of geometry before merging them into the input." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedPackBeforeMerge(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedPackBeforeMerge(InInputs, NewState); - }) - ]; -} - -void -FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current PackBeforeMerge state - auto IsCheckedImportAsReference= [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetImportAsReference() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing PackBeforeMerge state - auto CheckStateChangedImportAsReference= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetImportAsReference() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetImportAsReference() == bNewState) - continue; - - TArray * InputObjs = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (InputObjs) - { - // Mark all its input objects as changed to trigger recook. - for (auto CurInputObj : *InputObjs) - { - if (!CurInputObj || CurInputObj->IsPendingKill()) - continue; - - if (CurInputObj->GetImportAsReference() != bNewState) - { - CurInputObj->SetImportAsReference(bNewState); - CurInputObj->MarkChanged(true); - } - } - } - - CurInput->Modify(); - CurInput->SetImportAsReference(bNewState); - } - }; - - TSharedPtr< SCheckBox > CheckBoxImportAsReference; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxImportAsReference, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("ImportInputAsRefCheckbox", "Import input as references")) - .ToolTipText(LOCTEXT("ImportInputAsRefCheckboxTip", "Import input objects as references. (Geometry, World and Asset input types only)")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedImportAsReference(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedImportAsReference(InInputs, NewState); - }) - ]; -} -void -FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current ExportLODs state - auto IsCheckedExportLODs = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportLODs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda returning a CheckState from the input's current ExportSockets state - auto IsCheckedExportSockets = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportSockets() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda returning a CheckState from the input's current ExportColliders state - auto IsCheckedExportColliders = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportColliders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing ExportLODs state - auto CheckStateChangedExportLODs = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportLODs() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export LODs"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportLODs() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportLODs(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - // Lambda for changing ExportSockets state - auto CheckStateChangedExportSockets = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportSockets() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Sockets"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportSockets() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportSockets(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - // Lambda for changing ExportColliders state - auto CheckStateChangedExportColliders = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportColliders() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Colliders"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportColliders() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportColliders(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - TSharedPtr< SCheckBox > CheckBoxExportLODs; - TSharedPtr< SCheckBox > CheckBoxExportSockets; - TSharedPtr< SCheckBox > CheckBoxExportColliders; - VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew(CheckBoxExportLODs, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportAllLOD", "Export LODs" ) ) - .ToolTipText( LOCTEXT( "ExportAllLODCheckboxTip", "If enabled, all LOD Meshes in this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportLODs(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportLODs(InInputs, NewState); - }) - ] - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( CheckBoxExportSockets, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportSockets", "Export Sockets" ) ) - .ToolTipText( LOCTEXT( "ExportSocketsTip", "If enabled, all Mesh Sockets in this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportSockets(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportSockets(InInputs, NewState); - }) - ] - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( CheckBoxExportColliders, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportColliders", "Export Colliders" ) ) - .ToolTipText( LOCTEXT( "ExportCollidersTip", "If enabled, collision geometry for this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportColliders(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportColliders(InInputs, NewState); - }) - ] - ]; -} - -void -FHoudiniInputDetails::AddGeometryInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool ) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - - // Lambda for changing ExportColliders state - auto SetGeometryInputObjectsCount = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& NewInputCount) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing the number of Geometry Input Objects"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry) == NewInputCount) - continue; - - CurInput->Modify(); - - CurInput->SetInputObjectsNumber(EHoudiniInputType::Geometry, NewInputCount); - CurInput->MarkChanged(true); - - // - if (GEditor) - GEditor->RedrawAllViewports(); - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - }; - - InVerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs, NumInputObjects]() - { - return SetGeometryInputObjectsCount(InInputs, NumInputObjects + 1); - }), - LOCTEXT("AddInput", "Adds a Geometry Input"), true) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs]() - { - return SetGeometryInputObjectsCount(InInputs, 0); - }), - LOCTEXT("EmptyInputs", "Removes All Inputs"), true) - ] - ]; - - for (int32 GeometryObjectIdx = 0; GeometryObjectIdx < NumInputObjects; GeometryObjectIdx++) - { - //UObject* InputObject = InParam.GetInputObject(Idx); - Helper_CreateGeometryWidget(CategoryBuilder, InInputs, GeometryObjectIdx, AssetThumbnailPool, InVerticalBox); - } -} - - - -// Create a single geometry widget for the given input object -void -FHoudiniInputDetails::Helper_CreateGeometryWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InGeometryObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox ) -{ - UHoudiniInput* MainInput = InInputs[0]; - - // Access the object used in the corresponding geometry input - UHoudiniInputObject* HoudiniInputObject = MainInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - UObject* InputObject = HoudiniInputObject ? HoudiniInputObject->GetObject() : nullptr; - - // Create thumbnail for this static mesh. - TSharedPtr StaticMeshThumbnail = MakeShareable( - new FAssetThumbnail(InputObject, 64, 64, AssetThumbnailPool)); - - // Lambda for adding new geometry input objects - auto UpdateGeometryObjectAt = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& AtIndex, UObject* InObject) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - if (!InObject || InObject->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing a Geometry Input Object"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - UObject* InputObject = CurInput->GetInputObjectAt(EHoudiniInputType::Geometry, AtIndex); - if (InObject == InputObject) - continue; - - UHoudiniInputObject* CurrentInputObjectWrapper = CurInput->GetHoudiniInputObjectAt(AtIndex); - if (CurrentInputObjectWrapper) - CurrentInputObjectWrapper->Modify(); - - CurInput->Modify(); - - CurInput->SetInputObjectAt(EHoudiniInputType::Geometry, AtIndex, InObject); - CurInput->MarkChanged(true); - - // TODO: Not needed? - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - }; - - // Drop Target: Static/Skeletal Mesh - TSharedPtr HorizontalBox = NULL; - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop_Lambda([]( const UObject* InObject) - { - return UHoudiniInput::IsObjectAcceptable(EHoudiniInputType::Geometry, InObject); - }) - .OnAssetDropped_Lambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt](UObject* InObject) - { - return UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, InObject); - }) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - // Thumbnail : Static Mesh - FText ParameterLabelText = FText::FromString(MainInput->GetLabel()); - - TSharedPtr< SBorder > StaticMeshThumbnailBorder; - HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(StaticMeshThumbnailBorder, SBorder) - .Padding(5.0f) - .OnMouseDoubleClick_Lambda([MainInput, InGeometryObjectIdx](const FGeometry&, const FPointerEvent&) - { - UObject* InputObject = MainInput->GetInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - if (GEditor && InputObject) - GEditor->EditObject(InputObject); - - return FReply::Handled(); - }) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(ParameterLabelText) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - TWeakPtr WeakStaticMeshThumbnailBorder(StaticMeshThumbnailBorder); - StaticMeshThumbnailBorder->SetBorderImage(TAttribute::Create( - TAttribute::FGetter::CreateLambda([WeakStaticMeshThumbnailBorder]() - { - TSharedPtr ThumbnailBorder = WeakStaticMeshThumbnailBorder.Pin(); - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ))); - - FText MeshNameText = FText::GetEmpty(); - if (InputObject) - MeshNameText = FText::FromString(InputObject->GetName()); - - - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add Combo box : Static Mesh - TSharedPtr StaticMeshComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SAssignNew(StaticMeshComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(MeshNameText) - ] - ] - ]; - - - TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); - StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( - [MainInput, InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt]() - { - TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); - UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); - - TArray< UFactory * > NewAssetFactories; - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(DefaultObj), - true, - AllowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda( - [InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) - { - TSharedPtr ComboButton = WeakStaticMeshComboButton.Pin(); - if (ComboButton.IsValid()) - { - ComboButton->SetIsOpen(false); - UObject * Object = AssetData.GetAsset(); - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); - } - } - ), - FSimpleDelegate::CreateLambda([]() {}) - ); - } - )); - - - // Add buttons - TSharedPtr ButtonHorizontalBox; - ComboAndButtonBox->AddSlot() - .FillHeight(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ButtonHorizontalBox, SHorizontalBox) - ]; - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add( TEXT( "Asset" ), MeshNameText ); - FText StaticMeshTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", - "Browse to '{Asset}' in Content Browser" ), Args ); - - // Button : Use selected in content browser - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton(FSimpleDelegate::CreateLambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt]() - { - if (GEditor) - { - TArray CBSelections; - GEditor->GetContentBrowserSelections(CBSelections); - - // Get the first selected static mesh object - UObject* Object = nullptr; - for (auto & CurAssetData : CBSelections) - { - if (CurAssetData.AssetClass != UStaticMesh::StaticClass()->GetFName()) - continue; - - Object = CurAssetData.GetAsset(); - break; - } - - if (Object && !Object->IsPendingKill()) - { - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); - } - } - }), TAttribute< FText >(LOCTEXT("GeometryInputUseSelectedAssetFromCB", "Use Selected Asset from Content Browser"))) - ]; - - // Button : Browse Static Mesh - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateLambda([MainInput, InGeometryObjectIdx]() - { - UObject* InputObject = MainInput->GetInputObjectAt(InGeometryObjectIdx); - if (GEditor && InputObject) - { - TArray Objects; - Objects.Add(InputObject); - GEditor->SyncBrowserToObjects(Objects); - } - }), - TAttribute< FText >( StaticMeshTooltip ) - ) - ]; - - // ButtonBox : Reset - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default static mesh" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked_Lambda( [UpdateGeometryObjectAt, InInputs, InGeometryObjectIdx]() - { - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, nullptr); - return FReply::Handled(); - }) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ]; - - // Insert/Delete/Duplicate - ButtonHorizontalBox->AddSlot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( - FExecuteAction::CreateLambda( [ InInputs, InGeometryObjectIdx, MainInput ]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: insert a Geometry Input Object"), - MainInput->GetOuter()); - // Insert - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->InsertInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - } - } ), - FExecuteAction::CreateLambda([MainInput, InInputs, InGeometryObjectIdx]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: delete a Geometry Input Object"), - MainInput->GetOuter()); - - // Delete - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->DeleteInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - - if (GEditor) - GEditor->RedrawAllViewports(); - } - } ), - FExecuteAction::CreateLambda([InInputs, InGeometryObjectIdx, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), - MainInput->GetOuter()); - - // Duplicate - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->DuplicateInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - } - } ) ) - ]; - - // TRANSFORM OFFSET EXPANDER - { - TSharedPtr ExpanderArrow; - TSharedPtr ExpanderImage; - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( ExpanderArrow, SButton ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ClickMethod( EButtonClickMethod::MouseDown ) - .Visibility( EVisibility::Visible ) - .OnClicked(FOnClicked::CreateLambda([InInputs, InGeometryObjectIdx, MainInput, &CategoryBuilder]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled();; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), - MainInput->GetOuter()); - - // Expand transform - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->OnTransformUIExpand(InGeometryObjectIdx); - } - - // TODO: Not needed? - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - })) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ] - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( STextBlock ) - .Text( LOCTEXT("GeoInputTransform", "Transform Offset") ) - .ToolTipText( LOCTEXT( "GeoInputTransformTooltip", "Transform offset used for correction before sending the asset to Houdini" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - ]; - - TWeakPtr WeakExpanderArrow(ExpanderArrow); - // Set delegate for image - ExpanderImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([InGeometryObjectIdx, MainInput, WeakExpanderArrow]() - { - FName ResourceName; - TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); - if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) - { - ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - return FEditorStyle::GetBrush(ResourceName); - } - ))); - } - - // Lambda for changing the transform values - auto ChangeTransformOffsetAt = [&](const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex, const bool& DoChange, TArray InInputs) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputTransformChange", "Houdini Input: Changing Transform offset"), - InInputs[0]->GetOuter()); - - bool bChanged = true; - for (int Idx = 0; Idx < InInputs.Num(); Idx++) - { - if (!InInputs[Idx] || InInputs[Idx]->IsPendingKill()) - continue; - - UHoudiniInputObject* InputObject = InInputs[Idx]->GetHoudiniInputObjectAt(AtIndex); - if (InputObject) - InputObject->Modify(); - - bChanged &= InInputs[Idx]->SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); - } - - if (bChanged && DoChange) - { - // Mark the values as changed to trigger an update - for (int Idx = 0; Idx < InInputs.Num(); Idx++) - { - InInputs[Idx]->MarkChanged(true); - } - } - else - { - // Cancel the transaction - Transaction.Cancel(); - } - }; - - // Get Visibility of reset buttons - bool bResetButtonVisiblePosition = false; - bool bResetButtonVisibleRotation = false; - bool bResetButtonVisibleScale = false; - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - FTransform* CurTransform = CurInput->GetTransformOffset(InGeometryObjectIdx); - if (!CurTransform) - continue; - - if (CurTransform->GetLocation() != FVector::ZeroVector) - bResetButtonVisiblePosition = true; - - FRotator Rotator = CurTransform->Rotator(); - if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) - bResetButtonVisibleRotation = true; - - if (CurTransform->GetScale3D() != FVector::OneVector) - bResetButtonVisibleScale = true; - } - - auto ChangeTransformOffsetUniformlyAt = [InGeometryObjectIdx, InInputs, ChangeTransformOffsetAt](const float & Val, const int32& PosRotScaleIndex) - { - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 0, true, InInputs); - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 1, true, InInputs); - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 2, true, InInputs); - }; - - // TRANSFORM OFFSET - if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) - { - // Position - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew(STextBlock) - .Text( LOCTEXT("GeoInputTranslate", "T") ) - .ToolTipText( LOCTEXT( "GeoInputTranslateTooltip", "Translate" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SVectorInputBox ) - .bColorAxisLabels( true ) - .AllowSpin(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetX, InGeometryObjectIdx))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetY, InGeometryObjectIdx))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetZ, InGeometryObjectIdx))) - .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 0, true, InInputs); }) - .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 1, true, InInputs); }) - .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 2, true, InInputs); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([MainInput, ChangeTransformOffsetUniformlyAt, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 0); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - - // Rotation - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text( LOCTEXT("GeoInputRotate", "R") ) - .ToolTipText( LOCTEXT( "GeoInputRotateTooltip", "Rotate" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SRotatorInputBox ) - .AllowSpin( true ) - .bColorAxisLabels( true ) - .Roll(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetRoll, InGeometryObjectIdx))) - .Pitch(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetPitch, InGeometryObjectIdx))) - .Yaw(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetYaw, InGeometryObjectIdx))) - .OnRollCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 0, true, InInputs); }) - .OnPitchCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 1, true, InInputs); }) - .OnYawCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 2, true, InInputs); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (Not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 1); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - - // Scale - bool bLocked = false; - if (HoudiniInputObject) - bLocked = HoudiniInputObject->IsUniformScaleLocked(); - - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "GeoInputScale", "S" ) ) - .ToolTipText( LOCTEXT( "GeoInputScaleTooltip", "Scale" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SVectorInputBox ) - .bColorAxisLabels( true ) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetX, InGeometryObjectIdx))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetY, InGeometryObjectIdx))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetZ, InGeometryObjectIdx))) - .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 0, true, InInputs); - }) - .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 1, true, InInputs); - }) - .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 2, true, InInputs); - }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - SNew(SHorizontalBox) - // Lock Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ToolTipText(HoudiniInputObject ? - LOCTEXT("GeoInputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the input object maintains its shape in each direction when scaled") : - LOCTEXT("GeoInputLockButtonToolTipNoObject", "No input object selected")) - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(bLocked ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([InInputs, MainInput, InGeometryObjectIdx, HoudiniInputObject, &CategoryBuilder]() - { - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - UHoudiniInputObject*CurInputObject = CurInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - CurInputObject->SwitchUniformScaleLock(); - } - - if (HoudiniInputObject) - { - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); - }) - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(1.0f, 2); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - } -} - -void -FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Houdini Asset Picker Widget - { - FMenuBuilder MenuBuilder = Helper_CreateHoudiniAssetPickerWidget(InInputs); - - VerticalBox->AddSlot() - .Padding(2.0f, 2.0f, 5.0f, 2.0f) - .AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - // Button : Clear Selection - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - auto IsClearButtonEnabled = [MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return false; - - TArray* AssetInputObjectsArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - - if (!AssetInputObjectsArray) - return false; - - if (AssetInputObjectsArray->Num() <= 0) - return false; - - return true; - }; - - FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChangeClear", "Houdini Input: Clearing asset input selection"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - TArray* AssetInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - if (!AssetInputObjectsArray) - continue; - - CurrentInput->Modify(); - - AssetInputObjectsArray->Empty(); - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }); - - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - // Button : Clear Selection - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("ClearSelection", "Clear Selection")) - .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) - .IsEnabled_Lambda(IsClearButtonEnabled) - .OnClicked(OnClearSelect) - ] - ]; - - // Do not enable select all/clear select when selection has been started and details are locked - //HorizontalBox->SetEnabled(!bDetailsLocked); - } - - -} - -void -FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs, TSharedPtr AssetThumbnailPool) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Curve); - - // lambda for inserting an input Houdini curve. - auto InsertAnInputCurve = [MainInput, &CategoryBuilder](const int32& NewInputCount) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetComponent* OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - // Do not insert input object when the HAC does not finish cooking - EHoudiniAssetState CurrentHACState = OuterHAC->GetAssetState(); - if (CurrentHACState >= EHoudiniAssetState::PreCook && CurrentHACState<= EHoudiniAssetState::Processing) - return; - - // Clear the to be inserted object array, which records the pointers of the input objects to be inserted. - MainInput->LastInsertedInputs.Empty(); - // Record the pointer of the object to be inserted before transaction for undo the insert action. - bool bBlueprintStructureModified = false; - UHoudiniInputHoudiniSplineComponent* NewInput = MainInput->CreateHoudiniSplineInput(nullptr, true, false, bBlueprintStructureModified); - MainInput->LastInsertedInputs.Add(NewInput); - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction(FText::FromString("Modifying Houdini input: Adding curve input.")); - MainInput->Modify(); - - // Modify the MainInput. - MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType())->Add(NewInput); - - MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, NewInputCount); - - if (bBlueprintStructureModified) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - // Add Rot/Scale attribute checkbox - FText TooltipText = LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesTooltip", "If enabled, rot and scale attributes will be added per to the input curve on each control points."); - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesLabel", "Add rot & scale Attributes")) - .ToolTipText(TooltipText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - //.MinDesiredWidth(160.f) - ] - .OnCheckStateChanged_Lambda([InInputs](ECheckBoxState NewState) - { - const bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& CurrentInput : InInputs) - { - if (!IsValid(CurrentInput)) - continue; - - CurrentInput->SetAddRotAndScaleAttributes(bChecked); - } - }) - .IsChecked_Lambda([MainInput]() - { - if (!IsValid(MainInput)) - return ECheckBoxState::Unchecked; - - return MainInput->IsAddRotAndScaleAttributesEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .ToolTipText(TooltipText) - ]; - - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([InsertAnInputCurve, NumInputObjects]() - { - return InsertAnInputCurve(NumInputObjects+1); - //return SetCurveInputObjectsCount(NumInputObjects+1); - }), - - LOCTEXT("AddInputCurve", "Adds a Curve Input"), true) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateLambda([InInputs, MainInput, &CategoryBuilder]() - { - TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - - // Detach all curves before deleting. - for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) - { - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - } - - // Clear the insert objects buffer before transaction. - MainInput->LastInsertedInputs.Empty(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction(FText::FromString("Modifying Houdini Input: Delete curve inputs.")); - MainInput->Modify(); - - bool bBlueprintStructureModified = false; - - // actual delete. - for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) - { - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - MainInput->RemoveSplineFromInputObject(HoudiniInput, bBlueprintStructureModified); - - MainInput->DeleteInputObjectAt(EHoudiniInputType::Curve, n); - } - - MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, 0); - - if (bBlueprintStructureModified) - { - UActorComponent* OuterComponent = Cast(MainInput->GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterComponent); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - }), - LOCTEXT("EmptyInputsCurve", "Removes All Curve Inputs"), true) - ] - + SHorizontalBox::Slot().FillWidth(80.f).MaxWidth(80.f) - [ - SNew(SButton) - .Text(LOCTEXT("ResetCurveOffsetStr", "Reset Offset")) - .OnClicked_Lambda([MainInput]()->FReply - { - MainInput->ResetDefaultCurveOffset(); - return FReply::Handled(); - }) - ] - ]; - - //UHoudiniSplineComponent* SplineCompBeingEdited = nullptr; - TSharedPtr HouSplineComponentVisualizer; - if (GUnrealEd) - { - TSharedPtr Visualizer = - GUnrealEd->FindComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); - - HouSplineComponentVisualizer = StaticCastSharedPtr(Visualizer); - } - - - for (int n = 0; n < NumInputObjects; n++) - { - Helper_CreateCurveWidget(CategoryBuilder, InInputs, n, AssetThumbnailPool ,VerticalBox, HouSplineComponentVisualizer); - } -} - -void -FHoudiniInputDetails::Helper_CreateCurveWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InCurveObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox, - TSharedPtr HouSplineComponentVisualizer) -{ - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetComponent * OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - auto GetHoudiniSplineComponentAtIndex = [](UHoudiniInput * Input, int32 Index) - { - UHoudiniSplineComponent* FoundHoudiniSplineComponent = nullptr; - if (!Input || Input->IsPendingKill()) - return FoundHoudiniSplineComponent; - - // Get the TArray ptr to the curve objects in this input - TArray * CurveInputComponentArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - return FoundHoudiniSplineComponent; - - if (!CurveInputComponentArray->IsValidIndex(Index)) - return FoundHoudiniSplineComponent; - - // Access the object used in the corresponding Houdini curve input - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - FoundHoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - - return FoundHoudiniSplineComponent; - }; - - - // Get the TArray ptr to the curve objects in this input - TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - return; - - if (!CurveInputComponentArray->IsValidIndex(InCurveObjectIdx)) - return; - - // Access the object used in the corresponding Houdini curve input - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[InCurveObjectIdx]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent) - return; - - FString HoudiniSplineName = HoudiniSplineComponent->GetHoudiniSplineName(); - - // Editable label for the current Houdini curve - TSharedPtr LabelHorizontalBox; - VerticalBox->AddSlot() - .Padding(0, 2) - .AutoHeight() - [ - SAssignNew(LabelHorizontalBox, SHorizontalBox) - ]; - - - TSharedPtr LabelBlock; - LabelHorizontalBox->AddSlot() - .Padding(0, 15, 0, 2) - .MaxWidth(150.f) - .FillWidth(150.f) - .VAlign(VAlign_Bottom) - .HAlign(HAlign_Left) - [ - SNew(SBox).HeightOverride(20.f).WidthOverride(200.f).VAlign(VAlign_Center) - [ - SAssignNew(LabelBlock, SEditableText).Text(FText::FromString(HoudiniSplineName)) - .OnTextCommitted_Lambda([HoudiniSplineComponent](FText NewText, ETextCommit::Type CommitType) - { - if (CommitType == ETextCommit::Type::OnEnter) - { - HoudiniSplineComponent->SetHoudiniSplineName(NewText.ToString()); - } - }) - ] - ]; - - // 'Editing...' TextBlock showing if this component is being edited - TSharedPtr EditingTextBlock; - LabelHorizontalBox->AddSlot() - .Padding(0, 15, 0, 2) - .MaxWidth(55.f) - .FillWidth(55.f) - .VAlign(VAlign_Bottom) - .HAlign(HAlign_Left) - [ - SNew(SBox).HeightOverride(20.f).WidthOverride(75.f).VAlign(VAlign_Center) - [ - SAssignNew(EditingTextBlock, SCurveEditingTextBlock).Text(LOCTEXT("HoudiniCurveInputEditingLabel", "(editing...)")) - ] - ]; - - EditingTextBlock->HoudiniSplineComponent = HoudiniSplineComponent; - EditingTextBlock->HoudiniSplineComponentVisualizer = HouSplineComponentVisualizer; - - // Lambda for deleting the current curve input - auto DeleteHoudiniCurveAtIndex = [InInputs, InCurveObjectIdx, OuterHAC, CurveInputComponentArray, &CategoryBuilder]() - { - if (!OuterHAC|| OuterHAC->IsPendingKill()) - return; - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeDeleteACurve", "Houdini Input: Deleting a curve input"), - OuterHAC); - - int MainInputCurveArraySize = CurveInputComponentArray->Num(); - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - Input->Modify(); - - TArray* InputObjectArr = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!InputObjectArr) - continue; - - if (!InputObjectArr->IsValidIndex(InCurveObjectIdx)) - continue; - - if (MainInputCurveArraySize != InputObjectArr->Num()) - continue; - - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast((*InputObjectArr)[InCurveObjectIdx]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - return; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent) - return; - - // Detach the spline component before delete. - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - - // This input is marked changed when an input component is deleted. - Input->DeleteInputObjectAt(EHoudiniInputType::Curve, InCurveObjectIdx); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - // Add delete button UI - LabelHorizontalBox->AddSlot().Padding(0, 2, 0, 2).HAlign(HAlign_Right).VAlign(VAlign_Bottom).AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([DeleteHoudiniCurveAtIndex]() - { - return DeleteHoudiniCurveAtIndex(); - })) - ]; - - - TSharedPtr HorizontalBox = NULL; - VerticalBox->AddSlot().Padding(0, 2).AutoHeight()[SAssignNew(HorizontalBox, SHorizontalBox)]; - - // Closed check box - // Lambda returning a closed state - auto IsCheckedClosedCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsClosedCurve() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing Closed state - auto CheckStateChangedClosedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeClosed", "Houdini Input: Changing Curve Closed"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->IsClosedCurve() == bNewState) - continue; - - HoudiniSplineComponent->Modify(); - - HoudiniSplineComponent->SetClosedCurve(bNewState); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Add Closed check box UI - TSharedPtr CheckBoxClosed = NULL; - HorizontalBox->AddSlot().Padding(0, 2).AutoWidth() - [ - SAssignNew(CheckBoxClosed, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("ClosedCurveCheckBox", "Closed")) - .ToolTipText(LOCTEXT("ClosedCurveCheckboxTip", "Close this input curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedClosedCurve]() - { - return IsCheckedClosedCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedClosedCurve](ECheckBoxState NewState) - { - return CheckStateChangedClosedCurve(NewState); - }) - ]; - - // Reversed check box - // Lambda returning a reversed state - auto IsCheckedReversedCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsReversed() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing reversed state - auto CheckStateChangedReversedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeReversed", "Houdini Input: Changing Curve Reversed"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->IsReversed() == bNewState) - continue; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetReversed(bNewState); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Add reversed check box UI - TSharedPtr CheckBoxReversed = NULL; - HorizontalBox->AddSlot() - .Padding(2, 2) - .AutoWidth() - [ - SAssignNew(CheckBoxReversed, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("ReversedCurveCheckBox", "Reversed")) - .ToolTipText(LOCTEXT("ReversedCurveCheckboxTip", "Reverse this input curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedReversedCurve]() - { - return IsCheckedReversedCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedReversedCurve](ECheckBoxState NewState) - { - return CheckStateChangedReversedCurve(NewState); - }) - ]; - - // Visible check box - // Lambda returning a visible state - auto IsCheckedVisibleCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsHoudiniSplineVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing visible state - auto CheckStateChangedVisibleCurve = [GetHoudiniSplineComponentAtIndex, InInputs, OuterHAC, InCurveObjectIdx](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent) - continue; - - if (HoudiniSplineComponent->IsHoudiniSplineVisible() == bNewState) - return; - - HoudiniSplineComponent->SetHoudiniSplineVisible(bNewState); - } - - if (GEditor) - GEditor->RedrawAllViewports(); - - }; - - // Add visible check box UI - TSharedPtr CheckBoxVisible = NULL; - HorizontalBox->AddSlot().Padding(2, 2).AutoWidth() - [ - SAssignNew(CheckBoxVisible, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("VisibleCurveCheckBox", "Visible")) - .ToolTipText(LOCTEXT("VisibleCurveCheckboxTip", "Set the visibility of this curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedVisibleCurve]() - { - return IsCheckedVisibleCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedVisibleCurve](ECheckBoxState NewState) - { - return CheckStateChangedVisibleCurve(NewState); - }) - ]; - - // Curve type comboBox - // Lambda for changing Houdini curve type - auto OnCurveTypeChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - if (!InNewChoice.IsValid()) - return; - - EHoudiniCurveType NewInputType = UHoudiniInput::StringToHoudiniCurveType(*InNewChoice.Get()); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeType", "Houdini Input: Changing Curve Type"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->GetCurveType() == NewInputType) - continue; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetCurveType(NewInputType); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Lambda for getting Houdini curve type - auto GetCurveTypeText = [HoudiniSplineComponent]() - { - return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(HoudiniSplineComponent->GetCurveType())); - }; - - // Add curve type combo box UI - TSharedPtr CurveTypeHorizontalBox; - VerticalBox->AddSlot() - .Padding(0, 2, 2, 0) - .AutoHeight() - [ - SAssignNew(CurveTypeHorizontalBox, SHorizontalBox) - ]; - - // Add curve type label UI - CurveTypeHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() - [ - SNew(STextBlock).Text(LOCTEXT("CurveTypeText", "Curve Type ")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveType; - CurveTypeHorizontalBox->AddSlot() - .Padding(2, 2, 5, 2) - .FillWidth(150.f) - .MaxWidth(150.f) - [ - SAssignNew(ComboBoxCurveType, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels())[(int)HoudiniSplineComponent->GetCurveType()]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnCurveTypeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnCurveTypeChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([GetCurveTypeText]() - { - return GetCurveTypeText(); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - // Houdini curve method combo box - // Lambda for changing Houdini curve method - auto OnCurveMethodChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - if (!InNewChoice.IsValid()) - return; - - EHoudiniCurveMethod NewInputMethod = UHoudiniInput::StringToHoudiniCurveMethod(*InNewChoice.Get()); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeMethod", "Houdini Input: Changing Curve Method"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->GetCurveMethod() == NewInputMethod) - return; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetCurveMethod(NewInputMethod); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Lambda for getting Houdini curve method - auto GetCurveMethodText = [HoudiniSplineComponent]() - { - return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(HoudiniSplineComponent->GetCurveMethod())); - }; - - // Add curve method combo box UI - TSharedPtr< SHorizontalBox > CurveMethodHorizontalBox; - VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(CurveMethodHorizontalBox, SHorizontalBox)]; - - // Add curve method label UI - CurveMethodHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() - [ - SNew(STextBlock).Text(LOCTEXT("CurveMethodText", "Curve Method ")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveMethod; - CurveMethodHorizontalBox->AddSlot().Padding(2, 2, 5, 2).FillWidth(150.f).MaxWidth(150.f) - [ - SAssignNew(ComboBoxCurveMethod, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels())[(int)HoudiniSplineComponent->GetCurveMethod()]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnCurveMethodChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnCurveMethodChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([GetCurveMethodText]() - { - return GetCurveMethodText(); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - auto BakeInputCurveLambda = [](TArray Inputs, int32 Index, bool bBakeToBlueprint) - { - for (auto & NextInput : Inputs) - { - if (!NextInput || NextInput->IsPendingKill()) - continue; - - UHoudiniAssetComponent * OuterHAC = Cast(NextInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - continue; - - AActor * OwnerActor = OuterHAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - continue; - - TArray * CurveInputComponentArray = NextInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - continue; - - if (!CurveInputComponentArray->IsValidIndex(Index)) - continue; - - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - FHoudiniPackageParams PackageParams; - PackageParams.BakeFolder = OuterHAC->BakeFolder.Path; - PackageParams.HoudiniAssetName = OuterHAC->GetName(); - PackageParams.GeoId = NextInput->GetAssetNodeId(); - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.ObjectId = Index; - PackageParams.ObjectName = OwnerActor->GetName() + "InputHoudiniSpline" + FString::FromInt(Index); - - if (bBakeToBlueprint) - { - FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( - HoudiniSplineComponent, - PackageParams, - OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); - } - else - { - FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - HoudiniSplineComponent, - PackageParams, - OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); - } - } - - return FReply::Handled(); - }; - - // Add input curve bake button - TSharedPtr< SHorizontalBox > InputCurveBakeHorizontalBox; - VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(InputCurveBakeHorizontalBox, SHorizontalBox)]; - VerticalBox->AddSlot() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(110.f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("HoudiniInputCurveBakeToActorButton", "Bake to Actor")) - .IsEnabled(true) - .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() - { - return BakeInputCurveLambda(InInputs, InCurveObjectIdx, false); - }) - .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToActorButtonToolTip", "Bake this input curve to Actor")) - ] - - + SHorizontalBox::Slot().MaxWidth(110.f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("HoudiniInputCurveBakeToBPButton", "Bake to Blueprint")) - .IsEnabled(true) - .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() - { - return BakeInputCurveLambda(InInputs, InCurveObjectIdx, true); - }) - .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToBPButtonToolTip", "Bake this input curve to Blueprint")) - ] - ]; - - // Do we actually need to set enable the UI components? - if (MainInput->GetInputType() == EHoudiniInputType::Curve) - { - LabelBlock->SetEnabled(true); - CheckBoxClosed->SetEnabled(true); - CheckBoxReversed->SetEnabled(true); - CheckBoxVisible->SetEnabled(true); - ComboBoxCurveType->SetEnabled(true); - ComboBoxCurveMethod->SetEnabled(true); - } - else - { - LabelBlock->SetEnabled(false); - CheckBoxClosed->SetEnabled(false); - CheckBoxReversed->SetEnabled(false); - CheckBoxVisible->SetEnabled(false); - ComboBoxCurveType->SetEnabled(false); - ComboBoxCurveMethod->SetEnabled(false); - } -} - -void -FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - // Lambda returning a CheckState from the input's current KeepWorldTransform state - auto IsCheckedUpdateInputLandscape = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetUpdateInputLandscape() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing KeepWorldTransform state - auto CheckStateChangedUpdateInputLandscape = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangedUpdate", "Houdini Input: Changing Keep World Transform"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (bNewState == CurInput->GetUpdateInputLandscape()) - continue; - - CurInput->Modify(); - - UHoudiniAssetComponent* HAC = Cast(CurInput->GetOuter()); - if (!HAC) - continue; - - TArray* LandscapeInputObjects = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (!LandscapeInputObjects) - continue; - - for (UHoudiniInputObject* NextInputObj : *LandscapeInputObjects) - { - UHoudiniInputLandscape* CurrentInputLandscape = Cast(NextInputObj); - if (!CurrentInputLandscape) - continue; - - ALandscapeProxy* CurrentInputLandscapeProxy = CurrentInputLandscape->GetLandscapeProxy(); - if (!CurrentInputLandscapeProxy) - continue; - - if (bNewState) - { - // We want to update this landscape data directly, start by backing it up to image files in the temp folder - FString BackupBaseName = HAC->TemporaryCookFolder.Path - + TEXT("/") - + CurrentInputLandscapeProxy->GetName() - + TEXT("_") - + HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); - - // We need to cache the input landscape to a file - FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(BackupBaseName, CurrentInputLandscapeProxy); - - // Cache its transform on the input - CurrentInputLandscape->CachedInputLandscapeTraqnsform = CurrentInputLandscapeProxy->ActorToWorld(); - - HAC->SetMobility(EComponentMobility::Static); - CurrentInputLandscapeProxy->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); - } - else - { - // We are not updating this input landscape anymore, detach it and restore its backed-up values - CurrentInputLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - - // Restore the input landscape's backup data - FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(CurrentInputLandscapeProxy); - - // Reapply the source Landscape's transform - CurrentInputLandscapeProxy->SetActorTransform(CurrentInputLandscape->CachedInputLandscapeTraqnsform); - - // TODO: - // Clear the input obj map? - } - } - - CurInput->bUpdateInputLandscape = (NewState == ECheckBoxState::Checked); - CurInput->MarkChanged(true); - } - }; - - // CheckBox : Update Input Landscape Data - TSharedPtr< SCheckBox > CheckBoxUpdateInput; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew( CheckBoxUpdateInput, SCheckBox).Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) - .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedUpdateInputLandscape, MainInput]() - { - return IsCheckedUpdateInputLandscape(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedUpdateInputLandscape, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedUpdateInputLandscape(InInputs, NewState); - }) - ]; - - // Actor picker: Landscape. - FMenuBuilder MenuBuilder = Helper_CreateLandscapePickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - - // Checkboxes : Export landscape as Heightfield/Mesh/Points - { - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAs", "Export Landscape As")) - .ToolTipText(LOCTEXT("LandscapeExportAsToolTip", "Choose the type of data you want the ladscape to be exported to:\n * Heightfield\n * Mesh\n * Points")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr ButtonOptionsPanel; - VerticalBox->AddSlot().Padding(5, 2, 5, 2).AutoHeight() - [ - SAssignNew(ButtonOptionsPanel, SUniformGridPanel) - ]; - - auto IsCheckedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) - { - if (!Input || Input->IsPendingKill()) - return ECheckBoxState::Unchecked; - - if (Input->GetLandscapeExportType() == LandscapeExportType) - return ECheckBoxState::Checked; - else - return ECheckBoxState::Unchecked; - }; - - auto CheckStateChangedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) - { - if (!Input || Input->IsPendingKill()) - return false; - - if (Input->GetLandscapeExportType() == LandscapeExportType) - return false; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changed Landscape export type."), - Input->GetOuter()); - Input->Modify(); - - Input->SetLandscapeExportType(LandscapeExportType); - Input->SetHasLandscapeExportTypeChanged(true); - Input->MarkChanged(true); - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return true; - - for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) - { - if (!NextInputObj) - continue; - NextInputObj->MarkChanged(true); - } - - return true; - }; - - // Heightfield - FText HeightfieldTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a heightfield."); - ButtonOptionsPanel->AddSlot(0, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Start") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Heightfield); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for(auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Heightfield); - }) - .ToolTipText(HeightfieldTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("ClassIcon.LandscapeComponent")) - ] - + SHorizontalBox::Slot() - .FillWidth(1.f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsHeightfieldCheckbox", "Heightfield")) - .ToolTipText(HeightfieldTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - - // Mesh - FText MeshTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a mesh."); - ButtonOptionsPanel->AddSlot(1, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Mesh); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for (auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Mesh); - }) - .ToolTipText(MeshTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("ClassIcon.StaticMeshComponent")) - ] - - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsMeshCheckbox", "Mesh")) - .ToolTipText(MeshTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - - // Points - FText PointsTooltip = LOCTEXT("LandscapeExportAsPointsTooltip", "If enabled, the landscape will be exported to Houdini as points."); - ButtonOptionsPanel->AddSlot(2, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.End") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Points); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for (auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Points); - }) - .ToolTipText(PointsTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("Mobility.Static")) - ] - - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsPointsCheckbox", "Points")) - .ToolTipText(PointsTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - } - - // CheckBox : Export selected components only - { - TSharedPtr< SCheckBox > CheckBoxExportSelected; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportSelected, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeSelectedCheckbox", "Export Selected Landscape Components Only")) - .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, only the selected Landscape Components will be exported.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportSelectionOnly ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportSelectionOnly", "Houdini Input: Changing Landscape export only selected component."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportSelectionOnly) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportSelectionOnly = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - } - - // Checkbox: auto select components - { - TSharedPtr< SCheckBox > CheckBoxAutoSelectComponents; - VerticalBox->AddSlot().Padding(10, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxAutoSelectComponents, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("AutoSelectComponentCheckbox", "Auto-select component in asset bounds")) - .ToolTipText(LOCTEXT("AutoSelectComponentCheckboxTooltip", "If enabled, when no Landscape components are curremtly selected, the one within the asset's bounding box will be exported.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeAutoSelectComponent ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeAutoSelectComponent", "Houdini Input: Changing Landscape input auto-selects components."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeAutoSelectComponent) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeAutoSelectComponent = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - // Enable only when exporting selection or when exporting heighfield (for now) - bool bEnable = false; - for (auto CurrentInput : InInputs) - { - if (!MainInput->bLandscapeExportSelectionOnly) - continue; - - bEnable = true; - break; - } - CheckBoxAutoSelectComponents->SetEnabled(bEnable); - } - - - // The following checkbox are only added when not in heightfield mode - if (MainInput->LandscapeExportType != EHoudiniLandscapeExportType::Heightfield) - { - // Checkbox : Export materials - { - TSharedPtr< SCheckBox > CheckBoxExportMaterials; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportMaterials, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeMaterialsCheckbox", "Export Landscape Materials")) - .ToolTipText(LOCTEXT("LandscapeMaterialsTooltip", "If enabled, the landscape materials will be exported with it.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportMaterials", "Houdini Input: Changing Landscape input export materials."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportMaterials) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportMaterials = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportMaterials->SetEnabled(false); - */ - } - - // Checkbox : Export Tile UVs - { - TSharedPtr< SCheckBox > CheckBoxExportTileUVs; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportTileUVs, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeTileUVsCheckbox", "Export Landscape Tile UVs")) - .ToolTipText(LOCTEXT("LandscapeTileUVsTooltip", "If enabled, UVs will be exported separately for each Landscape tile.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportTileUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportTileUVs", "Houdini Input: Changing Landscape export tile UVs."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportTileUVs) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportTileUVs = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportTileUVs->SetEnabled(false); - */ - } - - // Checkbox : Export normalized UVs - { - TSharedPtr< SCheckBox > CheckBoxExportNormalizedUVs; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportNormalizedUVs, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeNormalizedUVsCheckbox", "Export Landscape Normalized UVs")) - .ToolTipText(LOCTEXT("LandscapeNormalizedUVsTooltip", "If enabled, landscape UVs will be exported in [0, 1].")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportNormalizedUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Changing Landscape export normalized UVs."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportNormalizedUVs) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportNormalizedUVs = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportNormalizedUVs->SetEnabled(false); - */ - } - - // Checkbox : Export lighting - { - TSharedPtr< SCheckBox > CheckBoxExportLighting; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportLighting, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeLightingCheckbox", "Export Landscape Lighting")) - .ToolTipText(LOCTEXT("LandscapeLightingTooltip", "If enabled, lightmap information will be exported with the landscape.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportLighting", "Houdini Input: Changing Landscape export lighting."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportLighting) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportLighting = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportLighting->SetEnabled(false); - */ - } - - } - - // Button : Recommit - { - auto OnButtonRecommitClicked = [InInputs]() - { - for (auto CurrentInput : InInputs) - { - TArray* LandscapeInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); - if (!LandscapeInputObjectsArray) - continue; - - for (UHoudiniInputObject* NextLandscapeInput : *LandscapeInputObjectsArray) - { - if (!NextLandscapeInput) - continue; - - NextLandscapeInput->MarkChanged(true); - } - - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1, 2, 4, 2) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("LandscapeInputRecommit", "Recommit Landscape")) - .ToolTipText(LOCTEXT("LandscapeInputRecommitTooltip", "Recommits the Landscape to Houdini.")) - .OnClicked_Lambda(OnButtonRecommitClicked) - ] - ]; - } - - - // Button : Clear Selection - { - auto IsClearButtonEnabled = [MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return false; - - if (MainInput->GetInputType() != EHoudiniInputType::Landscape) - return false; - - TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); - if (!MainInputObjectsArray) - return false; - - if (MainInputObjectsArray->Num() <= 0) - return false; - - return true; - }; - - auto OnButtonClearClicked = [InInputs]() - { - if (InInputs.Num() <= 0) - return FReply::Handled(); - - UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - if (MainInput->GetInputType() != EHoudiniInputType::Landscape) - return FReply::Handled(); - - TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); - if (!MainInputObjectsArray) - return FReply::Handled(); - - if (MainInputObjectsArray->Num() <= 0) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Clearing landscape input."), - MainInput->GetOuter()); - - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - TArray* LandscapeInputObjectsArray = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (!LandscapeInputObjectsArray) - continue; - - if (LandscapeInputObjectsArray->Num() <= 0) - continue; - - CurInput->MarkChanged(true); - CurInput->Modify(); - - LandscapeInputObjectsArray->Empty(); - } - - return FReply::Handled(); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().Padding(1, 2, 4, 2) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("ClearSelection", "Clear Selection")) - .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) - .IsEnabled_Lambda(IsClearButtonEnabled) - .OnClicked_Lambda(OnButtonClearClicked) - ] - ]; - } -} - -/* -FMenuBuilder -FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArray& InInputs, const TAttribute& HeadingText, const bool& bShowCurrentSelectionSection) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - - // Filters are only based on the MainInput - auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - if (!Actor->IsA()) - return false; - - ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); - if (!LandscapeProxy) - return false; - - // Get the landscape's actor - AActor* OwnerActor = LandscapeProxy->GetOwner(); - - // Get our Actor - UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); - AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; - - // TODO: FIX ME! - // IF the landscape is owned by ourself, skip it! - if (OwnerActor == MyOwner) - return false; - - return true; - }; - - auto OnShouldFilterWorld = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - const TArray* InputObjects = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurInputObject : *InputObjects) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - auto OnShouldFilterHoudiniAsset = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - // But not our own Asset Actor - if (const USceneComponent* RootComp = Cast(InInput->GetOuter())) - { - if (RootComp && Cast(RootComp->GetOwner()) != Actor) - return true; - } - - return false; - }; - - auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape, OnShouldFilterWorld, OnShouldFilterHoudiniAsset](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - switch (MainInput->GetInputType()) - { - case EHoudiniInputType::Landscape: - return OnShouldFilterLandscape(Actor, MainInput); - case EHoudiniInputType::World: - return OnShouldFilterWorld(Actor, MainInput); - case EHoudiniInputType::Asset: - return OnShouldFilterHoudiniAsset(Actor, MainInput); - default: - return true; - } - - return false; - }; - - - // Selection uses the input arrays - auto OnLandscapeSelected = [](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - ALandscapeProxy* LandscapeProxy = Cast(Actor); - if (!LandscapeProxy) - return; - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return; - - LandscapeInputObjectsArray->Empty(); - - FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); - - // Create a Houdini Input Object. - UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( - LandscapeProxy, Input, LandscapeName.ToString()); - - UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); - LandscapeInput->MarkChanged(true); - - LandscapeInputObjectsArray->Add(LandscapeInput); - Input->MarkChanged(true); - }; - - auto OnHoudiniAssetActorSelected = [](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); - if (!HoudiniAssetActor) - return; - - TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!AssetInputObjectsArray) - return; - - AssetInputObjectsArray->Empty(); - - FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); - - // Create a Houdini Asset Input Object - UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); - - UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); - AssetInput->MarkChanged(true); - - AssetInputObjectsArray->Add(AssetInput); - Input->MarkChanged(true); - }; - - auto OnWorldSelected = [](AActor* Actor, UHoudiniInput* Input) - { - // Do Nothing - }; - - auto OnActorSelected = [OnLandscapeSelected, OnWorldSelected, OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) - { - for (auto& CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - return; - - switch (CurInput->GetInputType()) - { - case EHoudiniInputType::Landscape: - return OnLandscapeSelected(Actor, CurInput); - case EHoudiniInputType::World: - return OnWorldSelected(Actor, CurInput); - case EHoudiniInputType::Asset: - return OnHoudiniAssetActorSelected(Actor, CurInput); - default: - return; - } - } - - return; - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - if (bShowCurrentSelectionSection) - { - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - } - - - MenuBuilder.BeginSection(NAME_None, HeadingText); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} -*/ - - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterHoudiniAsset = [InInputs](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - // But not our selected Asset Actor - for (auto & NextSelectedInput : InInputs) - { - if (!NextSelectedInput) - continue; - - const USceneComponent* RootComp = Cast(NextSelectedInput->GetOuter()); - if (RootComp && Cast(RootComp->GetOwner()) == Actor) - return false; - - } - - return true; - }; - - // Filters are only based on the MainInput - auto OnShouldFilterActor = [MainInput, OnShouldFilterHoudiniAsset](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - return OnShouldFilterHoudiniAsset(Actor); - }; - - auto OnHoudiniAssetActorSelected = [OnShouldFilterHoudiniAsset](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || Actor->IsPendingKill() || !Input || Input->IsPendingKill()) - return; - - AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); - if (!HoudiniAssetActor) - return; - - // Make sure that the actor is valid for this input - if (!OnShouldFilterHoudiniAsset(Actor)) - return; - - TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - if (!AssetInputObjectsArray) - return; - - FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); - - // Create a Houdini Asset Input Object - UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); - - UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); - AssetInput->MarkChanged(true); - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniAssetInputChange", "Houdini Input: Selecting an asset input"), - Input->GetOuter()); - - Input->Modify(); - - AssetInputObjectsArray->Empty(); - AssetInputObjectsArray->Add(AssetInput); - Input->MarkChanged(true); - }; - - auto OnActorSelected = [OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) - { - for (auto& CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - return; - - OnHoudiniAssetActorSelected(Actor, CurInput); - } - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - // Show current selection - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("AssetInputSelectableActors", "Houdini Assets")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - if (!Actor->IsA()) - return false; - - ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); - if (!LandscapeProxy) - return false; - - // Get the landscape's parent actor - // We need to get the AttachParent on the root componet, GteOwner will not return the parent actor! - AActor* OwnerActor = nullptr; - USceneComponent* RootComponent = LandscapeProxy->GetRootComponent(); - if (RootComponent && !RootComponent->IsPendingKill()) - OwnerActor = RootComponent->GetAttachParent() ? RootComponent->GetAttachParent()->GetOwner() : LandscapeProxy->GetOwner(); - - // Get our Actor - UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); - AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; - - // IF the landscape is owned by ourself, skip it! - if (OwnerActor && OwnerActor == MyOwner) - { - // ... buuuut we dont want to filter input landscapes that have the "Update Input Landscape Data" option enabled - // (and are, therefore, outputs as well) - for (int32 Idx = 0; Idx < MyHAC->GetNumInputs(); Idx++) - { - UHoudiniInput* CurrentInput = MyHAC->GetInputAt(Idx); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) - continue; - - if (!CurrentInput->GetUpdateInputLandscape()) - continue; - - // Don't filter our input landscapes - ALandscapeProxy* UpdatedInputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); - if (LandscapeProxy == UpdatedInputLandscape) - return true; - } - - return false; - } - - return true; - }; - - // Filters are only based on the MainInput - auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - return OnShouldFilterLandscape(Actor, MainInput); - }; - - // Selection uses the input arrays - auto OnLandscapeSelected = [OnShouldFilterLandscape](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - // Make sure that the actor is valid for this input - if (!OnShouldFilterLandscape(Actor, Input)) - return; - - ALandscapeProxy* LandscapeProxy = Cast(Actor); - if (!LandscapeProxy) - return; - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return; - - LandscapeInputObjectsArray->Empty(); - - FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); - - // Create a Houdini Input Object. - UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( - LandscapeProxy, Input, LandscapeName.ToString()); - - UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); - LandscapeInput->MarkChanged(true); - - LandscapeInputObjectsArray->Add(LandscapeInput); - Input->MarkChanged(true); - }; - - auto OnActorSelected = [OnLandscapeSelected](AActor* Actor, TArray InInputs) - { - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeSelections", "Houdini Input: Selecting input landscape."), - MainInput->GetOuter()); - - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - OnLandscapeSelected(Actor, CurInput); - } - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - // Show current selection - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("LandscapeInputSelectableActors", "Landscapes")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterWorld = [MainInput](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - const TArray* InputObjects = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurInputObject : *InputObjects) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) - { - // See if the input object is a HAC, if it is, get its parent actor - UHoudiniAssetComponent* CurHAC = Cast(CurInputObject->GetObject()); - if (CurHAC && !CurHAC->IsPendingKill()) - CurActor = CurHAC->GetOwner(); - } - - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - auto OnWorldSelected = [](AActor* Actor) - { - // Do Nothing - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterWorld); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputSelectedActors", "Currently Selected Actors")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnWorldSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilter = [MainInput](const AActor* const Actor) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - const TArray* BoundObjects = MainInput->GetBoundSelectorObjectArray(); - if (!BoundObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurActor : *BoundObjects) - { - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - - auto OnSelected = [](AActor* Actor) - { - // Do Nothing - }; - - FMenuBuilder MenuBuilder(true, nullptr); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputBoundSelectors", "Bound Selectors")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(FOnShouldFilterActor::CreateLambda(OnShouldFilter)); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -void -FHoudiniInputDetails::AddWorldInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - const IDetailsView* DetailsView) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); - - // Get the details view name and locked status - bool bDetailsLocked = false; - FName DetailsPanelName = "LevelEditorSelectionDetails"; - if (DetailsView) - { - DetailsPanelName = DetailsView->GetIdentifier(); - if (DetailsView->IsLocked()) - bDetailsLocked = true; - } - - // Check of we're in bound selector mode - bool bIsBoundSelector = MainInput->IsWorldInputBoundSelector(); - - // Button : Start Selection / Use current selection + refresh - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - //auto ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); - //auto ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); - FText ButtonLabel; - FText ButtonTooltip; - if (!bIsBoundSelector) - { - // Button : Start Selection / Use current selection - if (bDetailsLocked) - { - ButtonLabel = LOCTEXT("WorldInputUseCurrentSelection", "Use Current Selection (Unlocks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputUseCurrentSelectionTooltip", "Fill the asset's input with the current selection and unlocks the Details Panel."); - } - else - { - ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); - } - /* - FOnClicked OnSelectActors = FOnClicked::CreateStatic( - &FHoudiniInputDetails::Helper_OnButtonClickSelectActors, InInputs, DetailsPanelName); - */ - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ButtonLabel) - .ToolTipText(ButtonTooltip) - //.OnClicked(OnSelectActors) - .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() - { - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName); - }) - - ] - ]; - } - else - { - // Button : Start Selection / Use current selection as Bound selector - if (bDetailsLocked) - { - ButtonLabel = LOCTEXT("WorldInputUseSelectionAsBoundSelector", "Use Selection as Bound Selector (Unlocks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputUseSelectionAsBoundSelectorTooltip", "Fill the asset's input with all the actors contains in the current's selection bound, and unlocks the Details Panel."); - } - else - { - ButtonLabel = LOCTEXT("WorldInputStartBoundSelection", "Start Bound Selection (Locks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputStartBoundSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that will be used as bounds."); - } - - /* - FOnClicked OnSelectBounds = FOnClicked::CreateStatic( - &FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector, InInputs, DetailsPanelName); - */ - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ButtonLabel) - .ToolTipText(ButtonTooltip) - //.OnClicked(OnSelectBounds) - .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() - { - return Helper_OnButtonClickUseSelectionAsBoundSelector(CategoryBuilder, InInputs, DetailsPanelName); - }) - ] - ]; - } - } - - // Button : Select All + Clear Selection - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FOnClicked OnSelectAll = FOnClicked::CreateLambda([InInputs, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputSelectedAll", "Houdini Input: Selecting all actor in the current world"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // Get the parent component/actor/world of the current input - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - UWorld* MyWorld = CurrentInput->GetWorld(); - - TArray NewSelectedActors; - for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) - { - AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - NewSelectedActors.Add(CurrentActor); - } - - CurrentInput->Modify(); - - bool bHasChanged = CurrentInput->UpdateWorldSelection(NewSelectedActors); - } - - return FReply::Handled(); - }); - - FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput, &CategoryBuilder]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - const bool bMainInputBoundSelection = MainInput->IsWorldInputBoundSelector(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputClear", "Houdini Input: Clearing world input selection"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - // Do nothing if the current input has different selector settings from the main input - if (CurrentInput->IsWorldInputBoundSelector() != bMainInputBoundSelection) - continue; - - CurrentInput->Modify(); - - if (CurrentInput->IsWorldInputBoundSelector()) - { - CurrentInput->SetBoundSelectorObjectsNumber(0); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - else - { - TArray EmptySelection; - bool bHasChanged = CurrentInput->UpdateWorldSelection(EmptySelection); - } - } - - return FReply::Handled(); - }); - - FText ClearSelectionLabel; - FText ClearSelectionTooltip; - if (bIsBoundSelector) - { - ClearSelectionLabel = LOCTEXT("ClearBoundSelection", "Clear Bound Selection"); - ClearSelectionTooltip = LOCTEXT("ClearBoundSelectionTooltip", "Clear the input's current bound selection."); - } - else - { - ClearSelectionLabel = LOCTEXT("ClearSelection", "Clear Selection"); - ClearSelectionTooltip = LOCTEXT("ClearSelectionTooltip", "Clear the input's current selection."); - } - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - // Button : SelectAll - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("WorldInputSelectAll", "Select All")) - .ToolTipText(LOCTEXT("WorldInputSelectAll", "Fill the asset's input with all actors.")) - .OnClicked(OnSelectAll) - .IsEnabled(!bIsBoundSelector) - ] - + SHorizontalBox::Slot() - [ - // Button : Clear Selection - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ClearSelectionLabel) - .ToolTipText(ClearSelectionTooltip) - .OnClicked(OnClearSelect) - ] - ]; - - // Do not enable select all/clear select when selection has been started and details are locked - HorizontalBox->SetEnabled(!bDetailsLocked); - } - - // Checkbox: Bound Selector - { - // Lambda returning a CheckState from the input's current bound selector state - auto IsCheckedBoundSelector = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->IsWorldInputBoundSelector() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing bound selector state - auto CheckStateChangedIsBoundSelector = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeBoungSelector", "Houdini Input: Changing world input to bound selector"), - MainInput->GetOuter()); - - bool bNewState = (NewState == ECheckBoxState::Checked); - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->IsWorldInputBoundSelector() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetWorldInputBoundSelector(bNewState); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - // Checkbox : Is Bound Selector - TSharedPtr< SCheckBox > CheckBoxBoundSelector; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxBoundSelector, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("BoundSelector", "Bound Selector")) - .ToolTipText(LOCTEXT("BoundSelectorTip", "When enabled, this world input works as a bound selector, sending all the objects contained in the bound selector bounding boxes.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedBoundSelector, MainInput]() - { - return IsCheckedBoundSelector(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedIsBoundSelector, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedIsBoundSelector(InInputs, NewState); - }) - ]; - } - - // Checkbox: Bound Selector Auto update - { - // Lambda returning a CheckState from the input's current auto update state - auto IsCheckedAutoUpdate = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetWorldInputBoundSelectorAutoUpdates() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing the auto update state - auto CheckStateChangedBoundAutoUpdates = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeAutoUpdate", "Houdini Input: Changing bound selector auto-update state."), - MainInput->GetOuter()); - - bool bNewState = (NewState == ECheckBoxState::Checked); - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetWorldInputBoundSelectorAutoUpdates() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetWorldInputBoundSelectorAutoUpdates(bNewState); - CurInput->MarkChanged(true); - } - }; - - // Checkbox : Is Bound Selector - TSharedPtr< SCheckBox > CheckBoxBoundAutoUpdate; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxBoundAutoUpdate, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("BoundAutoUpdate", "Update bound selection automatically")) - .ToolTipText(LOCTEXT("BoundAutoUpdateTip", "If enabled and if this world input is set as a bound selector, the objects selected by the bounds will update automatically.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedAutoUpdate, MainInput]() - { - return IsCheckedAutoUpdate(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedBoundAutoUpdates, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedBoundAutoUpdates(InInputs, NewState); - }) - ]; - - CheckBoxBoundAutoUpdate->SetEnabled(MainInput->IsWorldInputBoundSelector()); - } - - // ActorPicker : Bound Selector - if(bIsBoundSelector) - { - FMenuBuilder MenuBuilder = Helper_CreateBoundSelectorPickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - // ActorPicker : World Outliner - { - FMenuBuilder MenuBuilder = Helper_CreateWorldActorPickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - { - // Spline Resolution - TSharedPtr> NumericEntryBox; - int32 Idx = 0; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(LOCTEXT("SplineRes", "Unreal Spline Resolution")) - .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm betweem control points)\nSet this to 0 to only export the control points.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SNumericEntryBox) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .MinValue(-1.0f) - .MaxValue(1000.0f) - .MinSliderValue(0.0f) - .MaxSliderValue(1000.0f) - .Value(MainInput->GetUnrealSplineResolution()) - .OnValueChanged_Lambda([MainInput, InInputs](float Val) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeSplineResolution", "Houdini Input: Changing world input spline resolution"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetUnrealSplineResolution() == Val) - continue; - - CurrentInput->Modify(); - - CurrentInput->SetUnrealSplineResolution(Val); - CurrentInput->MarkChanged(true); - } - }) - /* - .Value(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject( - &InParam, &UHoudiniAssetInput::GetSplineResolutionValue))) - .OnValueChanged(SNumericEntryBox< float >::FOnValueChanged::CreateUObject( - &InParam, &UHoudiniAssetInput::SetSplineResolutionValue)) - .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateUObject( - &InParam, &UHoudiniAssetInput::IsSplineResolutionEnabled))) - */ - .SliderExponent(1.0f) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("SplineResToDefault", "Reset to default value.")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - // TODO: FINISH ME! - //.OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) - .OnClicked_Lambda([MainInput, InInputs]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputRevertSplineResolution", "Houdini Input: Reverting world input spline resolution to default"), - MainInput->GetOuter()); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - float DefaultSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetUnrealSplineResolution() == DefaultSplineResolution) - continue; - - CurrentInput->Modify(); - - CurrentInput->SetUnrealSplineResolution(DefaultSplineResolution); - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - } -} - -void -FHoudiniInputDetails::AddSkeletalInputUI( - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool ) -{ -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) -{ - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, false); -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) -{ - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, true); -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName, const bool& bUseWorldInAsWorldSelector) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // There's no undo operation for button. - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Locate the details panel. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - return FReply::Handled(); - - class SLocalDetailsView : public SDetailsViewBase - { - public: - void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } - void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } - }; - auto * LocalDetailsView = static_cast(DetailsView.Get()); - - if (!DetailsView->IsLocked()) - { - // - // START SELECTION - // Locks the details view and select our currently selected actors - // - LocalDetailsView->LockDetailsView(); - check(DetailsView->IsLocked()); - - // Force refresh of details view. - TArray InputOuters; - for (auto CurIn : InInputs) - InputOuters.Add(CurIn->GetOuter()); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - //ReselectSelectedActors(); - - if (bUseWorldInAsWorldSelector) - { - // Bound Selection - // Select back the previously chosen bound selectors - GEditor->SelectNone(false, true); - int32 NumBoundSelectors = MainInput->GetNumberOfBoundSelectorObjects(); - for (int32 Idx = 0; Idx < NumBoundSelectors; Idx++) - { - AActor* Actor = MainInput->GetBoundSelectorObjectAt(Idx); - if (!Actor || Actor->IsPendingKill()) - continue; - - GEditor->SelectActor(Actor, true, true); - } - } - else - { - // Regular selection - // Select the already chosen input Actors from the World Outliner. - GEditor->SelectNone(false, true); - int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); - for (int32 Idx = 0; Idx < NumInputObjects; Idx++) - { - UHoudiniInputObject* CurInputObject = MainInput->GetHoudiniInputObjectAt(Idx); - if (!CurInputObject) - continue; - - AActor* Actor = nullptr; - UHoudiniInputActor* InputActor = Cast(CurInputObject); - if (InputActor && !InputActor->IsPendingKill()) - { - // Get the input actor - Actor = InputActor->GetActor(); - } - else - { - // See if the input object is a HAC - UHoudiniInputHoudiniAsset* InputHAC = Cast(CurInputObject); - if (InputHAC && !InputHAC->IsPendingKill()) - { - Actor = InputHAC->GetHoudiniAssetComponent() ? InputHAC->GetHoudiniAssetComponent()->GetOwner() : nullptr; - } - } - - if (!Actor || Actor->IsPendingKill()) - continue; - - GEditor->SelectActor(Actor, true, true); - } - } - - return FReply::Handled(); - } - else - { - // - // UPDATE SELECTION - // Unlocks the input's selection and select the HDA back. - // - - if (!GEditor || !GEditor->GetSelectedObjects()) - return FReply::Handled(); - - USelection * SelectedActors = GEditor->GetSelectedActors(); - if (!SelectedActors) - return FReply::Handled(); - - // Create a transaction - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniWorldInputSelectionChanged", "Changing Houdini world outliner input objects"), - MainInput->GetOuter()); - - - TArray AllActors; - for (auto CurrentInput : InInputs) - { - CurrentInput->Modify(); - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - AllActors.Add(ParentActor); - - bool bHasChanged = true; - if (bUseWorldInAsWorldSelector) - { - // - // Update bound selectors - - // Clean up the selected actors - TArray ValidBoundSelectedActors; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor* CurrentBoundActor = Cast(*It); - if (!CurrentBoundActor) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentBoundActor == ParentActor)) - continue; - - ValidBoundSelectedActors.Add(CurrentBoundActor); - } - - // See if the bound selector have changed - int32 PreviousBoundSelectorCount = CurrentInput->GetNumberOfBoundSelectorObjects(); - if (PreviousBoundSelectorCount == ValidBoundSelectedActors.Num()) - { - // Same number of BoundSelectors, see if they have changed - bHasChanged = false; - for (int32 BoundIdx = 0; BoundIdx < PreviousBoundSelectorCount; BoundIdx++) - { - AActor* PreviousBound = CurrentInput->GetBoundSelectorObjectAt(BoundIdx); - if (!PreviousBound) - continue; - - if (!ValidBoundSelectedActors.Contains(PreviousBound)) - { - bHasChanged = true; - break; - } - } - } - - if (bHasChanged) - { - // Only update the bound selector objects on the input if they have changed - CurrentInput->SetBoundSelectorObjectsNumber(ValidBoundSelectedActors.Num()); - int32 InputObjectIdx = 0; - for (auto CurActor : ValidBoundSelectedActors) - { - CurrentInput->SetBoundSelectorObjectAt(InputObjectIdx++, CurActor); - } - - // Update the current selection from the BoundSelectors - CurrentInput->UpdateWorldSelectionFromBoundSelectors(); - } - } - else - { - // - // Update our selection directly with the currently selected actors - // - - TArray ValidSelectedActors; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor* CurrentActor = Cast(*It); - if (!CurrentActor) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - ValidSelectedActors.Add(CurrentActor); - } - - // Update the input objects from the valid selected actors array - // Only new/remove input objects will be marked as changed - bHasChanged = CurrentInput->UpdateWorldSelection(ValidSelectedActors); - } - - // If we didnt change the selection, cancel the transaction - if (!bHasChanged) - Transaction.Cancel(); - } - - // We can now unlock the details view... - LocalDetailsView->UnlockDetailsView(); - check(!DetailsView->IsLocked()); - - // .. reset the selected actors, force refresh and override the lock. - DetailsView->SetObjects(AllActors, true, true); - - // We now need to reselect all our Asset Actors. - // If we don't do this, our Asset parameters will stop refreshing and the user will be very confused. - // It is also resetting the state of the selection before the input actor selection process was started. - GEditor->SelectNone(false, true); - for (auto CurrentActor : AllActors) - { - AActor* ParentActor = Cast(CurrentActor); - if (!ParentActor) - continue; - - GEditor->SelectActor(ParentActor, true, true); - } - - // Update the input details layout. - // if (CategoryBuilder.IsParentLayoutValid()) - // CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); -} - - -bool -FHoudiniInputDetails::Helper_CancelWorldSelection(TArray& InInputs, const FName& DetailsPanelName) -{ - if (InInputs.Num() <= 0) - return false; - - // Get the property module to access the details view - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Locate the details panel. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - return false; - - if (!DetailsView->IsLocked()) - return false; - - class SLocalDetailsView : public SDetailsViewBase - { - public: - void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } - void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } - }; - auto * LocalDetailsView = static_cast(DetailsView.Get()); - - // Get all our parent components / actors - TArray AllComponents; - TArray AllActors; - for (auto CurrentInput : InInputs) - { - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - if (!ParentComponent) - continue; - - AllComponents.Add(ParentComponent); - - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - if (!ParentActor) - continue; - - AllActors.Add(ParentActor); - } - - // Unlock the detail view and re-select our parent actors - { - LocalDetailsView->UnlockDetailsView(); - check(!DetailsView->IsLocked()); - - // Reset selected actor to itself, force refresh and override the lock. - DetailsView->SetObjects(AllActors, true, true); - } - - // Reselect the Asset Actor. If we don't do this, our Asset parameters will stop - // refreshing and the user will be very confused. It is also resetting the state - // of the selection before the input actor selection process was started. - GEditor->SelectNone(false, true); - for (auto ParentActorObj : AllActors) - { - AActor* ParentActor = Cast(ParentActorObj); - if (!ParentActor) - continue; - - GEditor->SelectActor(ParentActor, true, true); - } - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniInput.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetBlueprintComponent.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniPackageParams.h" + +#include "Editor.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SRotatorInputBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SEditableText.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Editor/UnrealEd/Public/AssetThumbnail.h" +#include "Editor/PropertyEditor/Private/SDetailsViewBase.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" +#include "SAssetDropTarget.h" +#include "ScopedTransaction.h" +#include "Engine/SkeletalMesh.h" +#include "Engine/Selection.h" +#include "EngineUtils.h" +#include "AssetData.h" +#include "Framework/SlateDelegates.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Modules/ModuleManager.h" +#include "SceneOutlinerModule.h" + +#include "Editor/UnrealEdEngine.h" +#include "HoudiniSplineComponentVisualizer.h" +#include "UnrealEdGlobals.h" +#include "Widgets/SWidget.h" + +#include "HoudiniEngineRuntimeUtils.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// Customized TextBlock to show 'editing...' text if this Houdini Spline Component is being edited +class SCurveEditingTextBlock : public STextBlock +{ +public: + UHoudiniSplineComponent* HoudiniSplineComponent; + TSharedPtr HoudiniSplineComponentVisualizer; +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override + { + if (!HoudiniSplineComponentVisualizer.IsValid() || !HoudiniSplineComponent) + return LayerId; + + if (HoudiniSplineComponentVisualizer->GetEditedHoudiniSplineComponent() != HoudiniSplineComponent) + return LayerId; + + return STextBlock::OnPaint(Args, AllottedGeometry, MyClippingRect, + OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + } +}; + +void +FHoudiniInputDetails::CreateWidget( + IDetailCategoryBuilder& HouInputCategory, + TArray InInputs, + FDetailWidgetRow* InputRow) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouInputCategory.GetParentLayout().GetThumbnailPool(); + + EHoudiniInputType MainInputType = MainInput->GetInputType(); + UHoudiniAssetComponent* HAC = MainInput->GetTypedOuter(); + + // Create a widget row, or get the given row. + FDetailWidgetRow* Row = InputRow; + Row = InputRow == nullptr ? &(HouInputCategory.AddCustomRow(FText::GetEmpty())) : InputRow; + if (!Row) + return; + + // Create the standard input name widget if this is not a operator path parameter. + // Operator path parameter's name widget is handled by HoudiniParameterDetails. + if (!InputRow) + CreateNameWidget(MainInput, *Row, true, InInputs.Num()); + + // Create a vertical Box for storing the UI + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + // ComboBox : Input Type + const IDetailsView* DetailsView = HouInputCategory.GetParentLayout().GetDetailsView(); + AddInputTypeComboBox(HouInputCategory, VerticalBox, InInputs, DetailsView); + + // Checkbox : Keep World Transform + AddKeepWorldTransformCheckBox(VerticalBox, InInputs); + + + // Checkbox : CurveInput trigger cook on curve changed + AddCurveInputCookOnChangeCheckBox(VerticalBox, InInputs); + + + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) + { + // Checkbox : Pack before merging + AddPackBeforeMergeCheckbox(VerticalBox, InInputs); + } + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World || MainInputType == EHoudiniInputType::Asset) + { + AddImportAsReferenceCheckbox(VerticalBox, InInputs); + } + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) + { + // Checkboxes : Export LODs / Sockets / Collisions + AddExportCheckboxes(VerticalBox, InInputs); + } + + switch (MainInput->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + AddGeometryInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); + } + break; + + case EHoudiniInputType::Asset: + { + AddAssetInputUI(VerticalBox, InInputs); + } + break; + + case EHoudiniInputType::Curve: + { + AddCurveInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); + } + break; + + case EHoudiniInputType::Landscape: + { + AddLandscapeInputUI(VerticalBox, InInputs); + } + break; + + case EHoudiniInputType::World: + { + AddWorldInputUI(HouInputCategory, VerticalBox, InInputs, DetailsView); + } + break; + + case EHoudiniInputType::Skeletal: + { + AddSkeletalInputUI(VerticalBox, InInputs, AssetThumbnailPool); + } + break; + } + + + Row->ValueWidget.Widget = VerticalBox; + + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + //Row.ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniInputDetails::CreateNameWidget( + UHoudiniInput* InInput, FDetailWidgetRow & Row, bool bLabel, int32 InInputCount) +{ + if (!InInput || InInput->IsPendingKill()) + return; + + FString InputLabelStr = InInput->GetLabel(); + if (InInputCount > 1) + { + InputLabelStr += TEXT(" (") + FString::FromInt(InInputCount) + TEXT(")"); + } + + const FText & FinalInputLabelText = bLabel ? FText::FromString(InputLabelStr) : FText::GetEmpty(); + FText InputTooltip = GetInputTooltip(InInput); + { + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(FinalInputLabelText) + .ToolTipText(InputTooltip) + .Font(FEditorStyle::GetFontStyle(!InInput->HasChanged() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))); + } +} + +FText +FHoudiniInputDetails::GetInputTooltip(UHoudiniInput* InParam) +{ + // TODO + return FText(); +} + +void +FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuilder, TSharedRef VerticalBox, TArray& InInputs, const IDetailsView* DetailsView) +{ + // Get the details view name and locked status + bool bDetailsLocked = false; + FName DetailsPanelName = "LevelEditorSelectionDetails"; + if (DetailsView) + { + DetailsPanelName = DetailsView->GetIdentifier(); + if (DetailsView->IsLocked()) + bDetailsLocked = true; + } + + // Lambda return a FText correpsonding to an input's current type + auto GetInputText = [](UHoudiniInput* InInput) + { + return FText::FromString(InInput->GetInputTypeAsString()); + }; + + // Lambda for changing inputs type + auto OnSelChanged = [DetailsPanelName, &CategoryBuilder](TArray InInputsToUpdate, TSharedPtr InNewChoice) + { + if (!InNewChoice.IsValid()) + return; + + EHoudiniInputType NewInputType = UHoudiniInput::StringToInputType(*InNewChoice.Get()); + if (NewInputType != EHoudiniInputType::World) + { + Helper_CancelWorldSelection(InInputsToUpdate, DetailsPanelName); + } + + if (InInputsToUpdate.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputsToUpdate[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetBlueprintComponent* HAB = MainInput->GetTypedOuter(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Input Type"), + MainInput->GetOuter()); + + bool bBlueprintStructureModified = false; + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetInputType() == NewInputType) + continue; + + /* This causes multiple issues. It does not set reset the previous type variable to Invalid sometimes + and it causes re-cook infinitely after few undo changing type. + { + CurInput->SetInputType(NewInputType); + CurInput->Modify(); + } + */ + + { + // Cache the current input type for undo type changing (since new type becomes previous type after undo) + EHoudiniInputType PrevType = CurInput->GetPreviousInputType(); + CurInput->SetPreviousInputType(NewInputType); + + CurInput->Modify(); + CurInput->SetPreviousInputType(PrevType); + CurInput->SetInputType(NewInputType, bBlueprintStructureModified); // pass in false for 2nd parameter in order to avoid creating default curve if empty + } + CurInput->MarkChanged(true); + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + + } + + if (HAB) + { + if (bBlueprintStructureModified) + HAB->MarkAsBlueprintStructureModified(); + } + + }; + + UHoudiniInput* MainInput = InInputs[0]; + TArray>* SupportedChoices = nullptr; + UHoudiniAssetBlueprintComponent* HAC = MainInput->GetTypedOuter(); + if (HAC) + { + SupportedChoices = FHoudiniEngineEditor::Get().GetBlueprintInputTypeChoiceLabels(); + } + else + { + SupportedChoices = FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels(); + } + + // ComboBox : Input Type + TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBoxInputType; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(ComboBoxInputType, SComboBox>) + .OptionsSource(SupportedChoices) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels())[((int32)MainInput->GetInputType() - 1)]) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([=](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnSelChanged(InInputs, NewChoice); + }) + [ + SNew( STextBlock ) + .Text_Lambda([=]() + { + return GetInputText(MainInput); + }) + .Font( FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; +} + +void +FHoudiniInputDetails:: AddCurveInputCookOnChangeCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + + if (!MainInput || MainInput->GetInputType() != EHoudiniInputType::Curve) + return; + + auto IsCheckedCookOnChange = [MainInput]() + { + if (!MainInput) + return ECheckBoxState::Checked; + + return MainInput->GetCookOnCurveChange() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto CheckStateChangedCookOnChange = [InInputs](ECheckBoxState NewState) + { + bool bChecked = NewState == ECheckBoxState::Checked; + for (auto & NextInput : InInputs) + { + if (!NextInput) + continue; + + NextInput->SetCookOnCurveChange(bChecked); + } + }; + + // Checkbox : Trigger cook on input curve changed + TSharedPtr< SCheckBox > CheckBoxCookOnCurveChanged; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxCookOnCurveChanged, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookOnCurveChangedCheckbox", "Auto-update")) + .ToolTipText(LOCTEXT("CookOnCurveChangeCheckboxTip", "When checked, cook is triggered automatically when the curve is modified.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedCookOnChange, MainInput]() + { + return IsCheckedCookOnChange(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedCookOnChange](ECheckBoxState NewState) + { + return CheckStateChangedCookOnChange( NewState); + }) + ]; + +} + +void +FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current KeepWorldTransform state + auto IsCheckedKeepWorldTransform = [&](UHoudiniInput* InInput) + { + return InInput->GetKeepWorldTransform() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing KeepWorldTransform state + auto CheckStateChangedKeepWorldTransform = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (MainInput->GetKeepWorldTransform() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Keep World Transform"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetKeepWorldTransform() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetKeepWorldTransform(bNewState); + CurInput->MarkChanged(true); + } + }; + + + // Checkbox : Keep World Transform + TSharedPtr< SCheckBox > CheckBoxTranformType; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxTranformType, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("KeepWorldTransformCheckbox", "Keep World Transform")) + .ToolTipText(LOCTEXT("KeepWorldTransformCheckboxTip", "Set this Input's object_merge Transform Type to INTO_THIS_OBJECT. If unchecked, it will be set to NONE.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedKeepWorldTransform(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedKeepWorldTransform(InInputs, NewState); + }) + ]; + + // the checkbox is read only for geo inputs + if (MainInput->GetInputType() == EHoudiniInputType::Geometry) + CheckBoxTranformType->SetEnabled(false); + + // Checkbox is read only if the input is an object-path parameter + //if (MainInput->IsObjectPathParameter() ) + // CheckBoxTranformType->SetEnabled(false); +} + +void +FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current PackBeforeMerge state + auto IsCheckedPackBeforeMerge = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetPackBeforeMerge() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing PackBeforeMerge state + auto CheckStateChangedPackBeforeMerge = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetPackBeforeMerge() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetPackBeforeMerge() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetPackBeforeMerge(bNewState); + CurInput->MarkChanged(true); + } + }; + + TSharedPtr< SCheckBox > CheckBoxPackBeforeMerge; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxPackBeforeMerge, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "PackBeforeMergeCheckbox", "Pack Geometry before merging" ) ) + .ToolTipText( LOCTEXT( "PackBeforeMergeCheckboxTip", "Pack each separate piece of geometry before merging them into the input." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedPackBeforeMerge(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedPackBeforeMerge(InInputs, NewState); + }) + ]; +} + +void +FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current PackBeforeMerge state + auto IsCheckedImportAsReference= [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetImportAsReference() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing PackBeforeMerge state + auto CheckStateChangedImportAsReference= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetImportAsReference() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetImportAsReference() == bNewState) + continue; + + TArray * InputObjs = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (InputObjs) + { + // Mark all its input objects as changed to trigger recook. + for (auto CurInputObj : *InputObjs) + { + if (!CurInputObj || CurInputObj->IsPendingKill()) + continue; + + if (CurInputObj->GetImportAsReference() != bNewState) + { + CurInputObj->SetImportAsReference(bNewState); + CurInputObj->MarkChanged(true); + } + } + } + + CurInput->Modify(); + CurInput->SetImportAsReference(bNewState); + } + }; + + TSharedPtr< SCheckBox > CheckBoxImportAsReference; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxImportAsReference, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ImportInputAsRefCheckbox", "Import input as references")) + .ToolTipText(LOCTEXT("ImportInputAsRefCheckboxTip", "Import input objects as references. (Geometry, World and Asset input types only)")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedImportAsReference(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedImportAsReference(InInputs, NewState); + }) + ]; +} +void +FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current ExportLODs state + auto IsCheckedExportLODs = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportLODs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda returning a CheckState from the input's current ExportSockets state + auto IsCheckedExportSockets = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportSockets() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda returning a CheckState from the input's current ExportColliders state + auto IsCheckedExportColliders = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportColliders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing ExportLODs state + auto CheckStateChangedExportLODs = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportLODs() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export LODs"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportLODs() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportLODs(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + // Lambda for changing ExportSockets state + auto CheckStateChangedExportSockets = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportSockets() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Sockets"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportSockets() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportSockets(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + // Lambda for changing ExportColliders state + auto CheckStateChangedExportColliders = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportColliders() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Colliders"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportColliders() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportColliders(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + TSharedPtr< SCheckBox > CheckBoxExportLODs; + TSharedPtr< SCheckBox > CheckBoxExportSockets; + TSharedPtr< SCheckBox > CheckBoxExportColliders; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew(CheckBoxExportLODs, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportAllLOD", "Export LODs" ) ) + .ToolTipText( LOCTEXT( "ExportAllLODCheckboxTip", "If enabled, all LOD Meshes in this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportLODs(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportLODs(InInputs, NewState); + }) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( CheckBoxExportSockets, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportSockets", "Export Sockets" ) ) + .ToolTipText( LOCTEXT( "ExportSocketsTip", "If enabled, all Mesh Sockets in this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportSockets(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportSockets(InInputs, NewState); + }) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( CheckBoxExportColliders, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportColliders", "Export Colliders" ) ) + .ToolTipText( LOCTEXT( "ExportCollidersTip", "If enabled, collision geometry for this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportColliders(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportColliders(InInputs, NewState); + }) + ] + ]; +} + +void +FHoudiniInputDetails::AddGeometryInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool ) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + + // Lambda for changing ExportColliders state + auto SetGeometryInputObjectsCount = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& NewInputCount) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing the number of Geometry Input Objects"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry) == NewInputCount) + continue; + + CurInput->Modify(); + + CurInput->SetInputObjectsNumber(EHoudiniInputType::Geometry, NewInputCount); + CurInput->MarkChanged(true); + + // + if (GEditor) + GEditor->RedrawAllViewports(); + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + }; + + InVerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs, NumInputObjects]() + { + return SetGeometryInputObjectsCount(InInputs, NumInputObjects + 1); + }), + LOCTEXT("AddInput", "Adds a Geometry Input"), true) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs]() + { + return SetGeometryInputObjectsCount(InInputs, 0); + }), + LOCTEXT("EmptyInputs", "Removes All Inputs"), true) + ] + ]; + + for (int32 GeometryObjectIdx = 0; GeometryObjectIdx < NumInputObjects; GeometryObjectIdx++) + { + //UObject* InputObject = InParam.GetInputObject(Idx); + Helper_CreateGeometryWidget(CategoryBuilder, InInputs, GeometryObjectIdx, AssetThumbnailPool, InVerticalBox); + } +} + + + +// Create a single geometry widget for the given input object +void +FHoudiniInputDetails::Helper_CreateGeometryWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InGeometryObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox ) +{ + UHoudiniInput* MainInput = InInputs[0]; + + // Access the object used in the corresponding geometry input + UHoudiniInputObject* HoudiniInputObject = MainInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + UObject* InputObject = HoudiniInputObject ? HoudiniInputObject->GetObject() : nullptr; + + // Create thumbnail for this static mesh. + TSharedPtr StaticMeshThumbnail = MakeShareable( + new FAssetThumbnail(InputObject, 64, 64, AssetThumbnailPool)); + + // Lambda for adding new geometry input objects + auto UpdateGeometryObjectAt = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& AtIndex, UObject* InObject) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + if (!InObject || InObject->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing a Geometry Input Object"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + UObject* InputObject = CurInput->GetInputObjectAt(EHoudiniInputType::Geometry, AtIndex); + if (InObject == InputObject) + continue; + + UHoudiniInputObject* CurrentInputObjectWrapper = CurInput->GetHoudiniInputObjectAt(AtIndex); + if (CurrentInputObjectWrapper) + CurrentInputObjectWrapper->Modify(); + + CurInput->Modify(); + + CurInput->SetInputObjectAt(EHoudiniInputType::Geometry, AtIndex, InObject); + CurInput->MarkChanged(true); + + // TODO: Not needed? + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + }; + + // Drop Target: Static/Skeletal Mesh + TSharedPtr HorizontalBox = NULL; + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop_Lambda([]( const UObject* InObject) + { + return UHoudiniInput::IsObjectAcceptable(EHoudiniInputType::Geometry, InObject); + }) + .OnAssetDropped_Lambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt](UObject* InObject) + { + return UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, InObject); + }) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + // Thumbnail : Static Mesh + FText ParameterLabelText = FText::FromString(MainInput->GetLabel()); + + TSharedPtr< SBorder > StaticMeshThumbnailBorder; + HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(StaticMeshThumbnailBorder, SBorder) + .Padding(5.0f) + .OnMouseDoubleClick_Lambda([MainInput, InGeometryObjectIdx](const FGeometry&, const FPointerEvent&) + { + UObject* InputObject = MainInput->GetInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + if (GEditor && InputObject) + GEditor->EditObject(InputObject); + + return FReply::Handled(); + }) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(ParameterLabelText) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + TWeakPtr WeakStaticMeshThumbnailBorder(StaticMeshThumbnailBorder); + StaticMeshThumbnailBorder->SetBorderImage(TAttribute::Create( + TAttribute::FGetter::CreateLambda([WeakStaticMeshThumbnailBorder]() + { + TSharedPtr ThumbnailBorder = WeakStaticMeshThumbnailBorder.Pin(); + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ))); + + FText MeshNameText = FText::GetEmpty(); + if (InputObject) + MeshNameText = FText::FromString(InputObject->GetName()); + + + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add Combo box : Static Mesh + TSharedPtr StaticMeshComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SAssignNew(StaticMeshComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(MeshNameText) + ] + ] + ]; + + + TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); + StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( + [MainInput, InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt]() + { + TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); + UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); + + TArray< UFactory * > NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(DefaultObj), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( + [InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) + { + TSharedPtr ComboButton = WeakStaticMeshComboButton.Pin(); + if (ComboButton.IsValid()) + { + ComboButton->SetIsOpen(false); + UObject * Object = AssetData.GetAsset(); + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); + } + } + ), + FSimpleDelegate::CreateLambda([]() {}) + ); + } + )); + + + // Add buttons + TSharedPtr ButtonHorizontalBox; + ComboAndButtonBox->AddSlot() + .FillHeight(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ButtonHorizontalBox, SHorizontalBox) + ]; + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add( TEXT( "Asset" ), MeshNameText ); + FText StaticMeshTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", + "Browse to '{Asset}' in Content Browser" ), Args ); + + // Button : Use selected in content browser + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton(FSimpleDelegate::CreateLambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt]() + { + if (GEditor) + { + TArray CBSelections; + GEditor->GetContentBrowserSelections(CBSelections); + + // Get the first selected static mesh object + UObject* Object = nullptr; + for (auto & CurAssetData : CBSelections) + { + if (CurAssetData.AssetClass != UStaticMesh::StaticClass()->GetFName()) + continue; + + Object = CurAssetData.GetAsset(); + break; + } + + if (Object && !Object->IsPendingKill()) + { + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); + } + } + }), TAttribute< FText >(LOCTEXT("GeometryInputUseSelectedAssetFromCB", "Use Selected Asset from Content Browser"))) + ]; + + // Button : Browse Static Mesh + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateLambda([MainInput, InGeometryObjectIdx]() + { + UObject* InputObject = MainInput->GetInputObjectAt(InGeometryObjectIdx); + if (GEditor && InputObject) + { + TArray Objects; + Objects.Add(InputObject); + GEditor->SyncBrowserToObjects(Objects); + } + }), + TAttribute< FText >( StaticMeshTooltip ) + ) + ]; + + // ButtonBox : Reset + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default static mesh" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked_Lambda( [UpdateGeometryObjectAt, InInputs, InGeometryObjectIdx]() + { + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, nullptr); + return FReply::Handled(); + }) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + // Insert/Delete/Duplicate + ButtonHorizontalBox->AddSlot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( + FExecuteAction::CreateLambda( [ InInputs, InGeometryObjectIdx, MainInput ]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: insert a Geometry Input Object"), + MainInput->GetOuter()); + // Insert + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->InsertInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + } + } ), + FExecuteAction::CreateLambda([MainInput, InInputs, InGeometryObjectIdx]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: delete a Geometry Input Object"), + MainInput->GetOuter()); + + // Delete + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->DeleteInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + + if (GEditor) + GEditor->RedrawAllViewports(); + } + } ), + FExecuteAction::CreateLambda([InInputs, InGeometryObjectIdx, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), + MainInput->GetOuter()); + + // Duplicate + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->DuplicateInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + } + } ) ) + ]; + + // TRANSFORM OFFSET EXPANDER + { + TSharedPtr ExpanderArrow; + TSharedPtr ExpanderImage; + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( ExpanderArrow, SButton ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ClickMethod( EButtonClickMethod::MouseDown ) + .Visibility( EVisibility::Visible ) + .OnClicked(FOnClicked::CreateLambda([InInputs, InGeometryObjectIdx, MainInput, &CategoryBuilder]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled();; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), + MainInput->GetOuter()); + + // Expand transform + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->OnTransformUIExpand(InGeometryObjectIdx); + } + + // TODO: Not needed? + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + })) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ] + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT("GeoInputTransform", "Transform Offset") ) + .ToolTipText( LOCTEXT( "GeoInputTransformTooltip", "Transform offset used for correction before sending the asset to Houdini" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + TWeakPtr WeakExpanderArrow(ExpanderArrow); + // Set delegate for image + ExpanderImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([InGeometryObjectIdx, MainInput, WeakExpanderArrow]() + { + FName ResourceName; + TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); + if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + return FEditorStyle::GetBrush(ResourceName); + } + ))); + } + + // Lambda for changing the transform values + auto ChangeTransformOffsetAt = [&](const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex, const bool& DoChange, TArray InInputs) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputTransformChange", "Houdini Input: Changing Transform offset"), + InInputs[0]->GetOuter()); + + bool bChanged = true; + for (int Idx = 0; Idx < InInputs.Num(); Idx++) + { + if (!InInputs[Idx] || InInputs[Idx]->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObject = InInputs[Idx]->GetHoudiniInputObjectAt(AtIndex); + if (InputObject) + InputObject->Modify(); + + bChanged &= InInputs[Idx]->SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); + } + + if (bChanged && DoChange) + { + // Mark the values as changed to trigger an update + for (int Idx = 0; Idx < InInputs.Num(); Idx++) + { + InInputs[Idx]->MarkChanged(true); + } + } + else + { + // Cancel the transaction + Transaction.Cancel(); + } + }; + + // Get Visibility of reset buttons + bool bResetButtonVisiblePosition = false; + bool bResetButtonVisibleRotation = false; + bool bResetButtonVisibleScale = false; + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + FTransform* CurTransform = CurInput->GetTransformOffset(InGeometryObjectIdx); + if (!CurTransform) + continue; + + if (CurTransform->GetLocation() != FVector::ZeroVector) + bResetButtonVisiblePosition = true; + + FRotator Rotator = CurTransform->Rotator(); + if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) + bResetButtonVisibleRotation = true; + + if (CurTransform->GetScale3D() != FVector::OneVector) + bResetButtonVisibleScale = true; + } + + auto ChangeTransformOffsetUniformlyAt = [InGeometryObjectIdx, InInputs, ChangeTransformOffsetAt](const float & Val, const int32& PosRotScaleIndex) + { + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 0, true, InInputs); + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 1, true, InInputs); + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 2, true, InInputs); + }; + + // TRANSFORM OFFSET + if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) + { + // Position + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew(STextBlock) + .Text( LOCTEXT("GeoInputTranslate", "T") ) + .ToolTipText( LOCTEXT( "GeoInputTranslateTooltip", "Translate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .AllowSpin(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetX, InGeometryObjectIdx))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetY, InGeometryObjectIdx))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetZ, InGeometryObjectIdx))) + .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 0, true, InInputs); }) + .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 1, true, InInputs); }) + .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 2, true, InInputs); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([MainInput, ChangeTransformOffsetUniformlyAt, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 0); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + + // Rotation + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text( LOCTEXT("GeoInputRotate", "R") ) + .ToolTipText( LOCTEXT( "GeoInputRotateTooltip", "Rotate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SRotatorInputBox ) + .AllowSpin( true ) + .bColorAxisLabels( true ) + .Roll(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetRoll, InGeometryObjectIdx))) + .Pitch(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetPitch, InGeometryObjectIdx))) + .Yaw(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetYaw, InGeometryObjectIdx))) + .OnRollCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 0, true, InInputs); }) + .OnPitchCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 1, true, InInputs); }) + .OnYawCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 2, true, InInputs); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (Not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 1); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + + // Scale + bool bLocked = false; + if (HoudiniInputObject) + bLocked = HoudiniInputObject->IsUniformScaleLocked(); + + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "GeoInputScale", "S" ) ) + .ToolTipText( LOCTEXT( "GeoInputScaleTooltip", "Scale" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetX, InGeometryObjectIdx))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetY, InGeometryObjectIdx))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetZ, InGeometryObjectIdx))) + .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 0, true, InInputs); + }) + .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 1, true, InInputs); + }) + .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 2, true, InInputs); + }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + SNew(SHorizontalBox) + // Lock Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ToolTipText(HoudiniInputObject ? + LOCTEXT("GeoInputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the input object maintains its shape in each direction when scaled") : + LOCTEXT("GeoInputLockButtonToolTipNoObject", "No input object selected")) + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(bLocked ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([InInputs, MainInput, InGeometryObjectIdx, HoudiniInputObject, &CategoryBuilder]() + { + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + UHoudiniInputObject*CurInputObject = CurInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + CurInputObject->SwitchUniformScaleLock(); + } + + if (HoudiniInputObject) + { + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); + }) + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(1.0f, 2); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + } +} + +void +FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Houdini Asset Picker Widget + { + FMenuBuilder MenuBuilder = Helper_CreateHoudiniAssetPickerWidget(InInputs); + + VerticalBox->AddSlot() + .Padding(2.0f, 2.0f, 5.0f, 2.0f) + .AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + // Button : Clear Selection + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + auto IsClearButtonEnabled = [MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return false; + + TArray* AssetInputObjectsArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + + if (!AssetInputObjectsArray) + return false; + + if (AssetInputObjectsArray->Num() <= 0) + return false; + + return true; + }; + + FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChangeClear", "Houdini Input: Clearing asset input selection"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + TArray* AssetInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + if (!AssetInputObjectsArray) + continue; + + CurrentInput->Modify(); + + AssetInputObjectsArray->Empty(); + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }); + + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + // Button : Clear Selection + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("ClearSelection", "Clear Selection")) + .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) + .IsEnabled_Lambda(IsClearButtonEnabled) + .OnClicked(OnClearSelect) + ] + ]; + + // Do not enable select all/clear select when selection has been started and details are locked + //HorizontalBox->SetEnabled(!bDetailsLocked); + } + + +} + +void +FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs, TSharedPtr AssetThumbnailPool) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Curve); + + // lambda for inserting an input Houdini curve. + auto InsertAnInputCurve = [MainInput, &CategoryBuilder](const int32& NewInputCount) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetComponent* OuterHAC = Cast(MainInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + // Do not insert input object when the HAC does not finish cooking + EHoudiniAssetState CurrentHACState = OuterHAC->GetAssetState(); + if (CurrentHACState >= EHoudiniAssetState::PreCook && CurrentHACState<= EHoudiniAssetState::Processing) + return; + + // Clear the to be inserted object array, which records the pointers of the input objects to be inserted. + MainInput->LastInsertedInputs.Empty(); + // Record the pointer of the object to be inserted before transaction for undo the insert action. + bool bBlueprintStructureModified = false; + UHoudiniInputHoudiniSplineComponent* NewInput = MainInput->CreateHoudiniSplineInput(nullptr, true, false, bBlueprintStructureModified); + MainInput->LastInsertedInputs.Add(NewInput); + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction(FText::FromString("Modifying Houdini input: Adding curve input.")); + MainInput->Modify(); + + // Modify the MainInput. + MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType())->Add(NewInput); + + MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, NewInputCount); + + if (bBlueprintStructureModified) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + // Add Rot/Scale attribute checkbox + FText TooltipText = LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesTooltip", "If enabled, rot and scale attributes will be added per to the input curve on each control points."); + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesLabel", "Add rot & scale Attributes")) + .ToolTipText(TooltipText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + //.MinDesiredWidth(160.f) + ] + .OnCheckStateChanged_Lambda([InInputs](ECheckBoxState NewState) + { + const bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& CurrentInput : InInputs) + { + if (!IsValid(CurrentInput)) + continue; + + CurrentInput->SetAddRotAndScaleAttributes(bChecked); + } + }) + .IsChecked_Lambda([MainInput]() + { + if (!IsValid(MainInput)) + return ECheckBoxState::Unchecked; + + return MainInput->IsAddRotAndScaleAttributesEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .ToolTipText(TooltipText) + ]; + + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([InsertAnInputCurve, NumInputObjects]() + { + return InsertAnInputCurve(NumInputObjects+1); + //return SetCurveInputObjectsCount(NumInputObjects+1); + }), + + LOCTEXT("AddInputCurve", "Adds a Curve Input"), true) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateLambda([InInputs, MainInput, &CategoryBuilder]() + { + TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + + // Detach all curves before deleting. + for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) + { + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast ((*CurveInputComponentArray)[n]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + } + + // Clear the insert objects buffer before transaction. + MainInput->LastInsertedInputs.Empty(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction(FText::FromString("Modifying Houdini Input: Delete curve inputs.")); + MainInput->Modify(); + + bool bBlueprintStructureModified = false; + + // actual delete. + for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) + { + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast ((*CurveInputComponentArray)[n]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + MainInput->RemoveSplineFromInputObject(HoudiniInput, bBlueprintStructureModified); + + MainInput->DeleteInputObjectAt(EHoudiniInputType::Curve, n); + } + + MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, 0); + + if (bBlueprintStructureModified) + { + UActorComponent* OuterComponent = Cast(MainInput->GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterComponent); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + }), + LOCTEXT("EmptyInputsCurve", "Removes All Curve Inputs"), true) + ] + + SHorizontalBox::Slot().FillWidth(80.f).MaxWidth(80.f) + [ + SNew(SButton) + .Text(LOCTEXT("ResetCurveOffsetStr", "Reset Offset")) + .OnClicked_Lambda([MainInput]()->FReply + { + MainInput->ResetDefaultCurveOffset(); + return FReply::Handled(); + }) + ] + ]; + + //UHoudiniSplineComponent* SplineCompBeingEdited = nullptr; + TSharedPtr HouSplineComponentVisualizer; + if (GUnrealEd) + { + TSharedPtr Visualizer = + GUnrealEd->FindComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); + + HouSplineComponentVisualizer = StaticCastSharedPtr(Visualizer); + } + + + for (int n = 0; n < NumInputObjects; n++) + { + Helper_CreateCurveWidget(CategoryBuilder, InInputs, n, AssetThumbnailPool ,VerticalBox, HouSplineComponentVisualizer); + } +} + +void +FHoudiniInputDetails::Helper_CreateCurveWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InCurveObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox, + TSharedPtr HouSplineComponentVisualizer) +{ + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetComponent * OuterHAC = Cast(MainInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + auto GetHoudiniSplineComponentAtIndex = [](UHoudiniInput * Input, int32 Index) + { + UHoudiniSplineComponent* FoundHoudiniSplineComponent = nullptr; + if (!Input || Input->IsPendingKill()) + return FoundHoudiniSplineComponent; + + // Get the TArray ptr to the curve objects in this input + TArray * CurveInputComponentArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + return FoundHoudiniSplineComponent; + + if (!CurveInputComponentArray->IsValidIndex(Index)) + return FoundHoudiniSplineComponent; + + // Access the object used in the corresponding Houdini curve input + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + FoundHoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + + return FoundHoudiniSplineComponent; + }; + + + // Get the TArray ptr to the curve objects in this input + TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + return; + + if (!CurveInputComponentArray->IsValidIndex(InCurveObjectIdx)) + return; + + // Access the object used in the corresponding Houdini curve input + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[InCurveObjectIdx]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent) + return; + + FString HoudiniSplineName = HoudiniSplineComponent->GetHoudiniSplineName(); + + // Editable label for the current Houdini curve + TSharedPtr LabelHorizontalBox; + VerticalBox->AddSlot() + .Padding(0, 2) + .AutoHeight() + [ + SAssignNew(LabelHorizontalBox, SHorizontalBox) + ]; + + + TSharedPtr LabelBlock; + LabelHorizontalBox->AddSlot() + .Padding(0, 15, 0, 2) + .MaxWidth(150.f) + .FillWidth(150.f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Left) + [ + SNew(SBox).HeightOverride(20.f).WidthOverride(200.f).VAlign(VAlign_Center) + [ + SAssignNew(LabelBlock, SEditableText).Text(FText::FromString(HoudiniSplineName)) + .OnTextCommitted_Lambda([HoudiniSplineComponent](FText NewText, ETextCommit::Type CommitType) + { + if (CommitType == ETextCommit::Type::OnEnter) + { + HoudiniSplineComponent->SetHoudiniSplineName(NewText.ToString()); + } + }) + ] + ]; + + // 'Editing...' TextBlock showing if this component is being edited + TSharedPtr EditingTextBlock; + LabelHorizontalBox->AddSlot() + .Padding(0, 15, 0, 2) + .MaxWidth(55.f) + .FillWidth(55.f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Left) + [ + SNew(SBox).HeightOverride(20.f).WidthOverride(75.f).VAlign(VAlign_Center) + [ + SAssignNew(EditingTextBlock, SCurveEditingTextBlock).Text(LOCTEXT("HoudiniCurveInputEditingLabel", "(editing...)")) + ] + ]; + + EditingTextBlock->HoudiniSplineComponent = HoudiniSplineComponent; + EditingTextBlock->HoudiniSplineComponentVisualizer = HouSplineComponentVisualizer; + + // Lambda for deleting the current curve input + auto DeleteHoudiniCurveAtIndex = [InInputs, InCurveObjectIdx, OuterHAC, CurveInputComponentArray, &CategoryBuilder]() + { + if (!OuterHAC|| OuterHAC->IsPendingKill()) + return; + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeDeleteACurve", "Houdini Input: Deleting a curve input"), + OuterHAC); + + int MainInputCurveArraySize = CurveInputComponentArray->Num(); + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + Input->Modify(); + + TArray* InputObjectArr = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!InputObjectArr) + continue; + + if (!InputObjectArr->IsValidIndex(InCurveObjectIdx)) + continue; + + if (MainInputCurveArraySize != InputObjectArr->Num()) + continue; + + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast((*InputObjectArr)[InCurveObjectIdx]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + return; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent) + return; + + // Detach the spline component before delete. + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + + // This input is marked changed when an input component is deleted. + Input->DeleteInputObjectAt(EHoudiniInputType::Curve, InCurveObjectIdx); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + // Add delete button UI + LabelHorizontalBox->AddSlot().Padding(0, 2, 0, 2).HAlign(HAlign_Right).VAlign(VAlign_Bottom).AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([DeleteHoudiniCurveAtIndex]() + { + return DeleteHoudiniCurveAtIndex(); + })) + ]; + + + TSharedPtr HorizontalBox = NULL; + VerticalBox->AddSlot().Padding(0, 2).AutoHeight()[SAssignNew(HorizontalBox, SHorizontalBox)]; + + // Closed check box + // Lambda returning a closed state + auto IsCheckedClosedCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsClosedCurve() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing Closed state + auto CheckStateChangedClosedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeClosed", "Houdini Input: Changing Curve Closed"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->IsClosedCurve() == bNewState) + continue; + + HoudiniSplineComponent->Modify(); + + HoudiniSplineComponent->SetClosedCurve(bNewState); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Add Closed check box UI + TSharedPtr CheckBoxClosed = NULL; + HorizontalBox->AddSlot().Padding(0, 2).AutoWidth() + [ + SAssignNew(CheckBoxClosed, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("ClosedCurveCheckBox", "Closed")) + .ToolTipText(LOCTEXT("ClosedCurveCheckboxTip", "Close this input curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedClosedCurve]() + { + return IsCheckedClosedCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedClosedCurve](ECheckBoxState NewState) + { + return CheckStateChangedClosedCurve(NewState); + }) + ]; + + // Reversed check box + // Lambda returning a reversed state + auto IsCheckedReversedCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsReversed() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing reversed state + auto CheckStateChangedReversedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeReversed", "Houdini Input: Changing Curve Reversed"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->IsReversed() == bNewState) + continue; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetReversed(bNewState); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Add reversed check box UI + TSharedPtr CheckBoxReversed = NULL; + HorizontalBox->AddSlot() + .Padding(2, 2) + .AutoWidth() + [ + SAssignNew(CheckBoxReversed, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("ReversedCurveCheckBox", "Reversed")) + .ToolTipText(LOCTEXT("ReversedCurveCheckboxTip", "Reverse this input curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedReversedCurve]() + { + return IsCheckedReversedCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedReversedCurve](ECheckBoxState NewState) + { + return CheckStateChangedReversedCurve(NewState); + }) + ]; + + // Visible check box + // Lambda returning a visible state + auto IsCheckedVisibleCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsHoudiniSplineVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing visible state + auto CheckStateChangedVisibleCurve = [GetHoudiniSplineComponentAtIndex, InInputs, OuterHAC, InCurveObjectIdx](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent) + continue; + + if (HoudiniSplineComponent->IsHoudiniSplineVisible() == bNewState) + return; + + HoudiniSplineComponent->SetHoudiniSplineVisible(bNewState); + } + + if (GEditor) + GEditor->RedrawAllViewports(); + + }; + + // Add visible check box UI + TSharedPtr CheckBoxVisible = NULL; + HorizontalBox->AddSlot().Padding(2, 2).AutoWidth() + [ + SAssignNew(CheckBoxVisible, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("VisibleCurveCheckBox", "Visible")) + .ToolTipText(LOCTEXT("VisibleCurveCheckboxTip", "Set the visibility of this curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedVisibleCurve]() + { + return IsCheckedVisibleCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedVisibleCurve](ECheckBoxState NewState) + { + return CheckStateChangedVisibleCurve(NewState); + }) + ]; + + // Curve type comboBox + // Lambda for changing Houdini curve type + auto OnCurveTypeChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + if (!InNewChoice.IsValid()) + return; + + EHoudiniCurveType NewInputType = UHoudiniInput::StringToHoudiniCurveType(*InNewChoice.Get()); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeType", "Houdini Input: Changing Curve Type"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->GetCurveType() == NewInputType) + continue; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetCurveType(NewInputType); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Lambda for getting Houdini curve type + auto GetCurveTypeText = [HoudiniSplineComponent]() + { + return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(HoudiniSplineComponent->GetCurveType())); + }; + + // Add curve type combo box UI + TSharedPtr CurveTypeHorizontalBox; + VerticalBox->AddSlot() + .Padding(0, 2, 2, 0) + .AutoHeight() + [ + SAssignNew(CurveTypeHorizontalBox, SHorizontalBox) + ]; + + // Add curve type label UI + CurveTypeHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() + [ + SNew(STextBlock).Text(LOCTEXT("CurveTypeText", "Curve Type ")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveType; + CurveTypeHorizontalBox->AddSlot() + .Padding(2, 2, 5, 2) + .FillWidth(150.f) + .MaxWidth(150.f) + [ + SAssignNew(ComboBoxCurveType, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels())[(int)HoudiniSplineComponent->GetCurveType()]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnCurveTypeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnCurveTypeChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([GetCurveTypeText]() + { + return GetCurveTypeText(); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + // Houdini curve method combo box + // Lambda for changing Houdini curve method + auto OnCurveMethodChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + if (!InNewChoice.IsValid()) + return; + + EHoudiniCurveMethod NewInputMethod = UHoudiniInput::StringToHoudiniCurveMethod(*InNewChoice.Get()); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeMethod", "Houdini Input: Changing Curve Method"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->GetCurveMethod() == NewInputMethod) + return; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetCurveMethod(NewInputMethod); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Lambda for getting Houdini curve method + auto GetCurveMethodText = [HoudiniSplineComponent]() + { + return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(HoudiniSplineComponent->GetCurveMethod())); + }; + + // Add curve method combo box UI + TSharedPtr< SHorizontalBox > CurveMethodHorizontalBox; + VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(CurveMethodHorizontalBox, SHorizontalBox)]; + + // Add curve method label UI + CurveMethodHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() + [ + SNew(STextBlock).Text(LOCTEXT("CurveMethodText", "Curve Method ")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveMethod; + CurveMethodHorizontalBox->AddSlot().Padding(2, 2, 5, 2).FillWidth(150.f).MaxWidth(150.f) + [ + SAssignNew(ComboBoxCurveMethod, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels())[(int)HoudiniSplineComponent->GetCurveMethod()]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnCurveMethodChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnCurveMethodChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([GetCurveMethodText]() + { + return GetCurveMethodText(); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + auto BakeInputCurveLambda = [](TArray Inputs, int32 Index, bool bBakeToBlueprint) + { + for (auto & NextInput : Inputs) + { + if (!NextInput || NextInput->IsPendingKill()) + continue; + + UHoudiniAssetComponent * OuterHAC = Cast(NextInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + continue; + + AActor * OwnerActor = OuterHAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + continue; + + TArray * CurveInputComponentArray = NextInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + continue; + + if (!CurveInputComponentArray->IsValidIndex(Index)) + continue; + + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + FHoudiniPackageParams PackageParams; + PackageParams.BakeFolder = OuterHAC->BakeFolder.Path; + PackageParams.HoudiniAssetName = OuterHAC->GetName(); + PackageParams.GeoId = NextInput->GetAssetNodeId(); + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.ObjectId = Index; + PackageParams.ObjectName = OwnerActor->GetName() + "InputHoudiniSpline" + FString::FromInt(Index); + + if (bBakeToBlueprint) + { + FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( + HoudiniSplineComponent, + PackageParams, + OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); + } + else + { + FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + HoudiniSplineComponent, + PackageParams, + OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); + } + } + + return FReply::Handled(); + }; + + // Add input curve bake button + TSharedPtr< SHorizontalBox > InputCurveBakeHorizontalBox; + VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(InputCurveBakeHorizontalBox, SHorizontalBox)]; + VerticalBox->AddSlot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(110.f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("HoudiniInputCurveBakeToActorButton", "Bake to Actor")) + .IsEnabled(true) + .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() + { + return BakeInputCurveLambda(InInputs, InCurveObjectIdx, false); + }) + .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToActorButtonToolTip", "Bake this input curve to Actor")) + ] + + + SHorizontalBox::Slot().MaxWidth(110.f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("HoudiniInputCurveBakeToBPButton", "Bake to Blueprint")) + .IsEnabled(true) + .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() + { + return BakeInputCurveLambda(InInputs, InCurveObjectIdx, true); + }) + .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToBPButtonToolTip", "Bake this input curve to Blueprint")) + ] + ]; + + // Do we actually need to set enable the UI components? + if (MainInput->GetInputType() == EHoudiniInputType::Curve) + { + LabelBlock->SetEnabled(true); + CheckBoxClosed->SetEnabled(true); + CheckBoxReversed->SetEnabled(true); + CheckBoxVisible->SetEnabled(true); + ComboBoxCurveType->SetEnabled(true); + ComboBoxCurveMethod->SetEnabled(true); + } + else + { + LabelBlock->SetEnabled(false); + CheckBoxClosed->SetEnabled(false); + CheckBoxReversed->SetEnabled(false); + CheckBoxVisible->SetEnabled(false); + ComboBoxCurveType->SetEnabled(false); + ComboBoxCurveMethod->SetEnabled(false); + } +} + +void +FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + // Lambda returning a CheckState from the input's current KeepWorldTransform state + auto IsCheckedUpdateInputLandscape = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetUpdateInputLandscape() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing KeepWorldTransform state + auto CheckStateChangedUpdateInputLandscape = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangedUpdate", "Houdini Input: Changing Keep World Transform"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (bNewState == CurInput->GetUpdateInputLandscape()) + continue; + + CurInput->Modify(); + + UHoudiniAssetComponent* HAC = Cast(CurInput->GetOuter()); + if (!HAC) + continue; + + TArray* LandscapeInputObjects = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (!LandscapeInputObjects) + continue; + + for (UHoudiniInputObject* NextInputObj : *LandscapeInputObjects) + { + UHoudiniInputLandscape* CurrentInputLandscape = Cast(NextInputObj); + if (!CurrentInputLandscape) + continue; + + ALandscapeProxy* CurrentInputLandscapeProxy = CurrentInputLandscape->GetLandscapeProxy(); + if (!CurrentInputLandscapeProxy) + continue; + + if (bNewState) + { + // We want to update this landscape data directly, start by backing it up to image files in the temp folder + FString BackupBaseName = HAC->TemporaryCookFolder.Path + + TEXT("/") + + CurrentInputLandscapeProxy->GetName() + + TEXT("_") + + HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); + + // We need to cache the input landscape to a file + FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(BackupBaseName, CurrentInputLandscapeProxy); + + // Cache its transform on the input + CurrentInputLandscape->CachedInputLandscapeTraqnsform = CurrentInputLandscapeProxy->ActorToWorld(); + + HAC->SetMobility(EComponentMobility::Static); + CurrentInputLandscapeProxy->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + } + else + { + // We are not updating this input landscape anymore, detach it and restore its backed-up values + CurrentInputLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + // Restore the input landscape's backup data + FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(CurrentInputLandscapeProxy); + + // Reapply the source Landscape's transform + CurrentInputLandscapeProxy->SetActorTransform(CurrentInputLandscape->CachedInputLandscapeTraqnsform); + + // TODO: + // Clear the input obj map? + } + } + + CurInput->bUpdateInputLandscape = (NewState == ECheckBoxState::Checked); + CurInput->MarkChanged(true); + } + }; + + // CheckBox : Update Input Landscape Data + TSharedPtr< SCheckBox > CheckBoxUpdateInput; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew( CheckBoxUpdateInput, SCheckBox).Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) + .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedUpdateInputLandscape, MainInput]() + { + return IsCheckedUpdateInputLandscape(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedUpdateInputLandscape, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedUpdateInputLandscape(InInputs, NewState); + }) + ]; + + // Actor picker: Landscape. + FMenuBuilder MenuBuilder = Helper_CreateLandscapePickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + + // Checkboxes : Export landscape as Heightfield/Mesh/Points + { + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAs", "Export Landscape As")) + .ToolTipText(LOCTEXT("LandscapeExportAsToolTip", "Choose the type of data you want the ladscape to be exported to:\n * Heightfield\n * Mesh\n * Points")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr ButtonOptionsPanel; + VerticalBox->AddSlot().Padding(5, 2, 5, 2).AutoHeight() + [ + SAssignNew(ButtonOptionsPanel, SUniformGridPanel) + ]; + + auto IsCheckedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) + { + if (!Input || Input->IsPendingKill()) + return ECheckBoxState::Unchecked; + + if (Input->GetLandscapeExportType() == LandscapeExportType) + return ECheckBoxState::Checked; + else + return ECheckBoxState::Unchecked; + }; + + auto CheckStateChangedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) + { + if (!Input || Input->IsPendingKill()) + return false; + + if (Input->GetLandscapeExportType() == LandscapeExportType) + return false; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changed Landscape export type."), + Input->GetOuter()); + Input->Modify(); + + Input->SetLandscapeExportType(LandscapeExportType); + Input->SetHasLandscapeExportTypeChanged(true); + Input->MarkChanged(true); + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return true; + + for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) + { + if (!NextInputObj) + continue; + NextInputObj->MarkChanged(true); + } + + return true; + }; + + // Heightfield + FText HeightfieldTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a heightfield."); + ButtonOptionsPanel->AddSlot(0, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Start") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Heightfield); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for(auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Heightfield); + }) + .ToolTipText(HeightfieldTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("ClassIcon.LandscapeComponent")) + ] + + SHorizontalBox::Slot() + .FillWidth(1.f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsHeightfieldCheckbox", "Heightfield")) + .ToolTipText(HeightfieldTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + + // Mesh + FText MeshTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a mesh."); + ButtonOptionsPanel->AddSlot(1, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Mesh); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for (auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Mesh); + }) + .ToolTipText(MeshTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("ClassIcon.StaticMeshComponent")) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsMeshCheckbox", "Mesh")) + .ToolTipText(MeshTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + + // Points + FText PointsTooltip = LOCTEXT("LandscapeExportAsPointsTooltip", "If enabled, the landscape will be exported to Houdini as points."); + ButtonOptionsPanel->AddSlot(2, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.End") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Points); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for (auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Points); + }) + .ToolTipText(PointsTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("Mobility.Static")) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsPointsCheckbox", "Points")) + .ToolTipText(PointsTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + } + + // CheckBox : Export selected components only + { + TSharedPtr< SCheckBox > CheckBoxExportSelected; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportSelected, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeSelectedCheckbox", "Export Selected Landscape Components Only")) + .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, only the selected Landscape Components will be exported.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportSelectionOnly ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportSelectionOnly", "Houdini Input: Changing Landscape export only selected component."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportSelectionOnly) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportSelectionOnly = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + } + + // Checkbox: auto select components + { + TSharedPtr< SCheckBox > CheckBoxAutoSelectComponents; + VerticalBox->AddSlot().Padding(10, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxAutoSelectComponents, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AutoSelectComponentCheckbox", "Auto-select component in asset bounds")) + .ToolTipText(LOCTEXT("AutoSelectComponentCheckboxTooltip", "If enabled, when no Landscape components are curremtly selected, the one within the asset's bounding box will be exported.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeAutoSelectComponent ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeAutoSelectComponent", "Houdini Input: Changing Landscape input auto-selects components."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeAutoSelectComponent) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeAutoSelectComponent = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + // Enable only when exporting selection or when exporting heighfield (for now) + bool bEnable = false; + for (auto CurrentInput : InInputs) + { + if (!MainInput->bLandscapeExportSelectionOnly) + continue; + + bEnable = true; + break; + } + CheckBoxAutoSelectComponents->SetEnabled(bEnable); + } + + + // The following checkbox are only added when not in heightfield mode + if (MainInput->LandscapeExportType != EHoudiniLandscapeExportType::Heightfield) + { + // Checkbox : Export materials + { + TSharedPtr< SCheckBox > CheckBoxExportMaterials; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportMaterials, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeMaterialsCheckbox", "Export Landscape Materials")) + .ToolTipText(LOCTEXT("LandscapeMaterialsTooltip", "If enabled, the landscape materials will be exported with it.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportMaterials", "Houdini Input: Changing Landscape input export materials."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportMaterials) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportMaterials = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportMaterials->SetEnabled(false); + */ + } + + // Checkbox : Export Tile UVs + { + TSharedPtr< SCheckBox > CheckBoxExportTileUVs; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportTileUVs, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeTileUVsCheckbox", "Export Landscape Tile UVs")) + .ToolTipText(LOCTEXT("LandscapeTileUVsTooltip", "If enabled, UVs will be exported separately for each Landscape tile.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportTileUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportTileUVs", "Houdini Input: Changing Landscape export tile UVs."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportTileUVs) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportTileUVs = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportTileUVs->SetEnabled(false); + */ + } + + // Checkbox : Export normalized UVs + { + TSharedPtr< SCheckBox > CheckBoxExportNormalizedUVs; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportNormalizedUVs, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeNormalizedUVsCheckbox", "Export Landscape Normalized UVs")) + .ToolTipText(LOCTEXT("LandscapeNormalizedUVsTooltip", "If enabled, landscape UVs will be exported in [0, 1].")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportNormalizedUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Changing Landscape export normalized UVs."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportNormalizedUVs) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportNormalizedUVs = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportNormalizedUVs->SetEnabled(false); + */ + } + + // Checkbox : Export lighting + { + TSharedPtr< SCheckBox > CheckBoxExportLighting; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportLighting, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeLightingCheckbox", "Export Landscape Lighting")) + .ToolTipText(LOCTEXT("LandscapeLightingTooltip", "If enabled, lightmap information will be exported with the landscape.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportLighting", "Houdini Input: Changing Landscape export lighting."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportLighting) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportLighting = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportLighting->SetEnabled(false); + */ + } + + } + + // Button : Recommit + { + auto OnButtonRecommitClicked = [InInputs]() + { + for (auto CurrentInput : InInputs) + { + TArray* LandscapeInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); + if (!LandscapeInputObjectsArray) + continue; + + for (UHoudiniInputObject* NextLandscapeInput : *LandscapeInputObjectsArray) + { + if (!NextLandscapeInput) + continue; + + NextLandscapeInput->MarkChanged(true); + } + + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1, 2, 4, 2) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("LandscapeInputRecommit", "Recommit Landscape")) + .ToolTipText(LOCTEXT("LandscapeInputRecommitTooltip", "Recommits the Landscape to Houdini.")) + .OnClicked_Lambda(OnButtonRecommitClicked) + ] + ]; + } + + + // Button : Clear Selection + { + auto IsClearButtonEnabled = [MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return false; + + if (MainInput->GetInputType() != EHoudiniInputType::Landscape) + return false; + + TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); + if (!MainInputObjectsArray) + return false; + + if (MainInputObjectsArray->Num() <= 0) + return false; + + return true; + }; + + auto OnButtonClearClicked = [InInputs]() + { + if (InInputs.Num() <= 0) + return FReply::Handled(); + + UHoudiniInput * MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + if (MainInput->GetInputType() != EHoudiniInputType::Landscape) + return FReply::Handled(); + + TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); + if (!MainInputObjectsArray) + return FReply::Handled(); + + if (MainInputObjectsArray->Num() <= 0) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Clearing landscape input."), + MainInput->GetOuter()); + + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + TArray* LandscapeInputObjectsArray = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (!LandscapeInputObjectsArray) + continue; + + if (LandscapeInputObjectsArray->Num() <= 0) + continue; + + CurInput->MarkChanged(true); + CurInput->Modify(); + + LandscapeInputObjectsArray->Empty(); + } + + return FReply::Handled(); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1, 2, 4, 2) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("ClearSelection", "Clear Selection")) + .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) + .IsEnabled_Lambda(IsClearButtonEnabled) + .OnClicked_Lambda(OnButtonClearClicked) + ] + ]; + } +} + +/* +FMenuBuilder +FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArray& InInputs, const TAttribute& HeadingText, const bool& bShowCurrentSelectionSection) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + + // Filters are only based on the MainInput + auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + if (!Actor->IsA()) + return false; + + ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); + if (!LandscapeProxy) + return false; + + // Get the landscape's actor + AActor* OwnerActor = LandscapeProxy->GetOwner(); + + // Get our Actor + UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); + AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; + + // TODO: FIX ME! + // IF the landscape is owned by ourself, skip it! + if (OwnerActor == MyOwner) + return false; + + return true; + }; + + auto OnShouldFilterWorld = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + const TArray* InputObjects = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurInputObject : *InputObjects) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + AActor* CurActor = Cast(CurInputObject->GetObject()); + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + auto OnShouldFilterHoudiniAsset = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + // But not our own Asset Actor + if (const USceneComponent* RootComp = Cast(InInput->GetOuter())) + { + if (RootComp && Cast(RootComp->GetOwner()) != Actor) + return true; + } + + return false; + }; + + auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape, OnShouldFilterWorld, OnShouldFilterHoudiniAsset](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + switch (MainInput->GetInputType()) + { + case EHoudiniInputType::Landscape: + return OnShouldFilterLandscape(Actor, MainInput); + case EHoudiniInputType::World: + return OnShouldFilterWorld(Actor, MainInput); + case EHoudiniInputType::Asset: + return OnShouldFilterHoudiniAsset(Actor, MainInput); + default: + return true; + } + + return false; + }; + + + // Selection uses the input arrays + auto OnLandscapeSelected = [](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + ALandscapeProxy* LandscapeProxy = Cast(Actor); + if (!LandscapeProxy) + return; + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return; + + LandscapeInputObjectsArray->Empty(); + + FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); + + // Create a Houdini Input Object. + UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( + LandscapeProxy, Input, LandscapeName.ToString()); + + UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); + LandscapeInput->MarkChanged(true); + + LandscapeInputObjectsArray->Add(LandscapeInput); + Input->MarkChanged(true); + }; + + auto OnHoudiniAssetActorSelected = [](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); + if (!HoudiniAssetActor) + return; + + TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!AssetInputObjectsArray) + return; + + AssetInputObjectsArray->Empty(); + + FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); + + // Create a Houdini Asset Input Object + UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); + + UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); + AssetInput->MarkChanged(true); + + AssetInputObjectsArray->Add(AssetInput); + Input->MarkChanged(true); + }; + + auto OnWorldSelected = [](AActor* Actor, UHoudiniInput* Input) + { + // Do Nothing + }; + + auto OnActorSelected = [OnLandscapeSelected, OnWorldSelected, OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) + { + for (auto& CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + return; + + switch (CurInput->GetInputType()) + { + case EHoudiniInputType::Landscape: + return OnLandscapeSelected(Actor, CurInput); + case EHoudiniInputType::World: + return OnWorldSelected(Actor, CurInput); + case EHoudiniInputType::Asset: + return OnHoudiniAssetActorSelected(Actor, CurInput); + default: + return; + } + } + + return; + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + if (bShowCurrentSelectionSection) + { + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + } + + + MenuBuilder.BeginSection(NAME_None, HeadingText); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} +*/ + + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterHoudiniAsset = [InInputs](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + // But not our selected Asset Actor + for (auto & NextSelectedInput : InInputs) + { + if (!NextSelectedInput) + continue; + + const USceneComponent* RootComp = Cast(NextSelectedInput->GetOuter()); + if (RootComp && Cast(RootComp->GetOwner()) == Actor) + return false; + + } + + return true; + }; + + // Filters are only based on the MainInput + auto OnShouldFilterActor = [MainInput, OnShouldFilterHoudiniAsset](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + return OnShouldFilterHoudiniAsset(Actor); + }; + + auto OnHoudiniAssetActorSelected = [OnShouldFilterHoudiniAsset](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || Actor->IsPendingKill() || !Input || Input->IsPendingKill()) + return; + + AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); + if (!HoudiniAssetActor) + return; + + // Make sure that the actor is valid for this input + if (!OnShouldFilterHoudiniAsset(Actor)) + return; + + TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + if (!AssetInputObjectsArray) + return; + + FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); + + // Create a Houdini Asset Input Object + UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); + + UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); + AssetInput->MarkChanged(true); + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniAssetInputChange", "Houdini Input: Selecting an asset input"), + Input->GetOuter()); + + Input->Modify(); + + AssetInputObjectsArray->Empty(); + AssetInputObjectsArray->Add(AssetInput); + Input->MarkChanged(true); + }; + + auto OnActorSelected = [OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) + { + for (auto& CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + return; + + OnHoudiniAssetActorSelected(Actor, CurInput); + } + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + // Show current selection + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("AssetInputSelectableActors", "Houdini Assets")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + if (!Actor->IsA()) + return false; + + ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); + if (!LandscapeProxy) + return false; + + // Get the landscape's parent actor + // We need to get the AttachParent on the root componet, GteOwner will not return the parent actor! + AActor* OwnerActor = nullptr; + USceneComponent* RootComponent = LandscapeProxy->GetRootComponent(); + if (RootComponent && !RootComponent->IsPendingKill()) + OwnerActor = RootComponent->GetAttachParent() ? RootComponent->GetAttachParent()->GetOwner() : LandscapeProxy->GetOwner(); + + // Get our Actor + UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); + AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; + + // IF the landscape is owned by ourself, skip it! + if (OwnerActor && OwnerActor == MyOwner) + { + // ... buuuut we dont want to filter input landscapes that have the "Update Input Landscape Data" option enabled + // (and are, therefore, outputs as well) + for (int32 Idx = 0; Idx < MyHAC->GetNumInputs(); Idx++) + { + UHoudiniInput* CurrentInput = MyHAC->GetInputAt(Idx); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) + continue; + + if (!CurrentInput->GetUpdateInputLandscape()) + continue; + + // Don't filter our input landscapes + ALandscapeProxy* UpdatedInputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); + if (LandscapeProxy == UpdatedInputLandscape) + return true; + } + + return false; + } + + return true; + }; + + // Filters are only based on the MainInput + auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + return OnShouldFilterLandscape(Actor, MainInput); + }; + + // Selection uses the input arrays + auto OnLandscapeSelected = [OnShouldFilterLandscape](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + // Make sure that the actor is valid for this input + if (!OnShouldFilterLandscape(Actor, Input)) + return; + + ALandscapeProxy* LandscapeProxy = Cast(Actor); + if (!LandscapeProxy) + return; + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return; + + LandscapeInputObjectsArray->Empty(); + + FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); + + // Create a Houdini Input Object. + UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( + LandscapeProxy, Input, LandscapeName.ToString()); + + UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); + LandscapeInput->MarkChanged(true); + + LandscapeInputObjectsArray->Add(LandscapeInput); + Input->MarkChanged(true); + }; + + auto OnActorSelected = [OnLandscapeSelected](AActor* Actor, TArray InInputs) + { + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeSelections", "Houdini Input: Selecting input landscape."), + MainInput->GetOuter()); + + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + OnLandscapeSelected(Actor, CurInput); + } + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + // Show current selection + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("LandscapeInputSelectableActors", "Landscapes")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterWorld = [MainInput](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + const TArray* InputObjects = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurInputObject : *InputObjects) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + AActor* CurActor = Cast(CurInputObject->GetObject()); + if (!CurActor || CurActor->IsPendingKill()) + { + // See if the input object is a HAC, if it is, get its parent actor + UHoudiniAssetComponent* CurHAC = Cast(CurInputObject->GetObject()); + if (CurHAC && !CurHAC->IsPendingKill()) + CurActor = CurHAC->GetOwner(); + } + + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + auto OnWorldSelected = [](AActor* Actor) + { + // Do Nothing + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterWorld); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputSelectedActors", "Currently Selected Actors")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnWorldSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilter = [MainInput](const AActor* const Actor) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + const TArray* BoundObjects = MainInput->GetBoundSelectorObjectArray(); + if (!BoundObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurActor : *BoundObjects) + { + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + + auto OnSelected = [](AActor* Actor) + { + // Do Nothing + }; + + FMenuBuilder MenuBuilder(true, nullptr); + MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputBoundSelectors", "Bound Selectors")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(FOnShouldFilterActor::CreateLambda(OnShouldFilter)); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +void +FHoudiniInputDetails::AddWorldInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + const IDetailsView* DetailsView) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); + + // Get the details view name and locked status + bool bDetailsLocked = false; + FName DetailsPanelName = "LevelEditorSelectionDetails"; + if (DetailsView) + { + DetailsPanelName = DetailsView->GetIdentifier(); + if (DetailsView->IsLocked()) + bDetailsLocked = true; + } + + // Check of we're in bound selector mode + bool bIsBoundSelector = MainInput->IsWorldInputBoundSelector(); + + // Button : Start Selection / Use current selection + refresh + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + //auto ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); + //auto ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); + FText ButtonLabel; + FText ButtonTooltip; + if (!bIsBoundSelector) + { + // Button : Start Selection / Use current selection + if (bDetailsLocked) + { + ButtonLabel = LOCTEXT("WorldInputUseCurrentSelection", "Use Current Selection (Unlocks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputUseCurrentSelectionTooltip", "Fill the asset's input with the current selection and unlocks the Details Panel."); + } + else + { + ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); + } + /* + FOnClicked OnSelectActors = FOnClicked::CreateStatic( + &FHoudiniInputDetails::Helper_OnButtonClickSelectActors, InInputs, DetailsPanelName); + */ + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ButtonLabel) + .ToolTipText(ButtonTooltip) + //.OnClicked(OnSelectActors) + .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() + { + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName); + }) + + ] + ]; + } + else + { + // Button : Start Selection / Use current selection as Bound selector + if (bDetailsLocked) + { + ButtonLabel = LOCTEXT("WorldInputUseSelectionAsBoundSelector", "Use Selection as Bound Selector (Unlocks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputUseSelectionAsBoundSelectorTooltip", "Fill the asset's input with all the actors contains in the current's selection bound, and unlocks the Details Panel."); + } + else + { + ButtonLabel = LOCTEXT("WorldInputStartBoundSelection", "Start Bound Selection (Locks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputStartBoundSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that will be used as bounds."); + } + + /* + FOnClicked OnSelectBounds = FOnClicked::CreateStatic( + &FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector, InInputs, DetailsPanelName); + */ + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ButtonLabel) + .ToolTipText(ButtonTooltip) + //.OnClicked(OnSelectBounds) + .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() + { + return Helper_OnButtonClickUseSelectionAsBoundSelector(CategoryBuilder, InInputs, DetailsPanelName); + }) + ] + ]; + } + } + + // Button : Select All + Clear Selection + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FOnClicked OnSelectAll = FOnClicked::CreateLambda([InInputs, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputSelectedAll", "Houdini Input: Selecting all actor in the current world"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // Get the parent component/actor/world of the current input + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + UWorld* MyWorld = CurrentInput->GetWorld(); + + TArray NewSelectedActors; + for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) + { + AActor *CurrentActor = *ActorItr; + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + NewSelectedActors.Add(CurrentActor); + } + + CurrentInput->Modify(); + + bool bHasChanged = CurrentInput->UpdateWorldSelection(NewSelectedActors); + } + + return FReply::Handled(); + }); + + FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput, &CategoryBuilder]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + const bool bMainInputBoundSelection = MainInput->IsWorldInputBoundSelector(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputClear", "Houdini Input: Clearing world input selection"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + // Do nothing if the current input has different selector settings from the main input + if (CurrentInput->IsWorldInputBoundSelector() != bMainInputBoundSelection) + continue; + + CurrentInput->Modify(); + + if (CurrentInput->IsWorldInputBoundSelector()) + { + CurrentInput->SetBoundSelectorObjectsNumber(0); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + else + { + TArray EmptySelection; + bool bHasChanged = CurrentInput->UpdateWorldSelection(EmptySelection); + } + } + + return FReply::Handled(); + }); + + FText ClearSelectionLabel; + FText ClearSelectionTooltip; + if (bIsBoundSelector) + { + ClearSelectionLabel = LOCTEXT("ClearBoundSelection", "Clear Bound Selection"); + ClearSelectionTooltip = LOCTEXT("ClearBoundSelectionTooltip", "Clear the input's current bound selection."); + } + else + { + ClearSelectionLabel = LOCTEXT("ClearSelection", "Clear Selection"); + ClearSelectionTooltip = LOCTEXT("ClearSelectionTooltip", "Clear the input's current selection."); + } + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + // Button : SelectAll + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("WorldInputSelectAll", "Select All")) + .ToolTipText(LOCTEXT("WorldInputSelectAll", "Fill the asset's input with all actors.")) + .OnClicked(OnSelectAll) + .IsEnabled(!bIsBoundSelector) + ] + + SHorizontalBox::Slot() + [ + // Button : Clear Selection + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ClearSelectionLabel) + .ToolTipText(ClearSelectionTooltip) + .OnClicked(OnClearSelect) + ] + ]; + + // Do not enable select all/clear select when selection has been started and details are locked + HorizontalBox->SetEnabled(!bDetailsLocked); + } + + // Checkbox: Bound Selector + { + // Lambda returning a CheckState from the input's current bound selector state + auto IsCheckedBoundSelector = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->IsWorldInputBoundSelector() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing bound selector state + auto CheckStateChangedIsBoundSelector = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeBoungSelector", "Houdini Input: Changing world input to bound selector"), + MainInput->GetOuter()); + + bool bNewState = (NewState == ECheckBoxState::Checked); + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->IsWorldInputBoundSelector() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetWorldInputBoundSelector(bNewState); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + // Checkbox : Is Bound Selector + TSharedPtr< SCheckBox > CheckBoxBoundSelector; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxBoundSelector, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("BoundSelector", "Bound Selector")) + .ToolTipText(LOCTEXT("BoundSelectorTip", "When enabled, this world input works as a bound selector, sending all the objects contained in the bound selector bounding boxes.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedBoundSelector, MainInput]() + { + return IsCheckedBoundSelector(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedIsBoundSelector, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedIsBoundSelector(InInputs, NewState); + }) + ]; + } + + // Checkbox: Bound Selector Auto update + { + // Lambda returning a CheckState from the input's current auto update state + auto IsCheckedAutoUpdate = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetWorldInputBoundSelectorAutoUpdates() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing the auto update state + auto CheckStateChangedBoundAutoUpdates = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeAutoUpdate", "Houdini Input: Changing bound selector auto-update state."), + MainInput->GetOuter()); + + bool bNewState = (NewState == ECheckBoxState::Checked); + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetWorldInputBoundSelectorAutoUpdates() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetWorldInputBoundSelectorAutoUpdates(bNewState); + CurInput->MarkChanged(true); + } + }; + + // Checkbox : Is Bound Selector + TSharedPtr< SCheckBox > CheckBoxBoundAutoUpdate; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxBoundAutoUpdate, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("BoundAutoUpdate", "Update bound selection automatically")) + .ToolTipText(LOCTEXT("BoundAutoUpdateTip", "If enabled and if this world input is set as a bound selector, the objects selected by the bounds will update automatically.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedAutoUpdate, MainInput]() + { + return IsCheckedAutoUpdate(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedBoundAutoUpdates, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedBoundAutoUpdates(InInputs, NewState); + }) + ]; + + CheckBoxBoundAutoUpdate->SetEnabled(MainInput->IsWorldInputBoundSelector()); + } + + // ActorPicker : Bound Selector + if(bIsBoundSelector) + { + FMenuBuilder MenuBuilder = Helper_CreateBoundSelectorPickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + // ActorPicker : World Outliner + { + FMenuBuilder MenuBuilder = Helper_CreateWorldActorPickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + { + // Spline Resolution + TSharedPtr> NumericEntryBox; + int32 Idx = 0; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("SplineRes", "Unreal Spline Resolution")) + .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm between control points)\nSet this to 0 to only export the control points.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .MinValue(-1.0f) + .MaxValue(1000.0f) + .MinSliderValue(0.0f) + .MaxSliderValue(1000.0f) + .Value(MainInput->GetUnrealSplineResolution()) + .OnValueChanged_Lambda([MainInput, InInputs](float Val) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeSplineResolution", "Houdini Input: Changing world input spline resolution"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetUnrealSplineResolution() == Val) + continue; + + CurrentInput->Modify(); + + CurrentInput->SetUnrealSplineResolution(Val); + CurrentInput->MarkChanged(true); + } + }) + /* + .Value(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetSplineResolutionValue))) + .OnValueChanged(SNumericEntryBox< float >::FOnValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetSplineResolutionValue)) + .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsSplineResolutionEnabled))) + */ + .SliderExponent(1.0f) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("SplineResToDefault", "Reset to default value.")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + // TODO: FINISH ME! + //.OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) + .OnClicked_Lambda([MainInput, InInputs]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputRevertSplineResolution", "Houdini Input: Reverting world input spline resolution to default"), + MainInput->GetOuter()); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + float DefaultSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetUnrealSplineResolution() == DefaultSplineResolution) + continue; + + CurrentInput->Modify(); + + CurrentInput->SetUnrealSplineResolution(DefaultSplineResolution); + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + } +} + +void +FHoudiniInputDetails::AddSkeletalInputUI( + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool ) +{ +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) +{ + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, false); +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) +{ + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, true); +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName, const bool& bUseWorldInAsWorldSelector) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // There's no undo operation for button. + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Locate the details panel. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + return FReply::Handled(); + + class SLocalDetailsView : public SDetailsViewBase + { + public: + void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } + void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } + }; + auto * LocalDetailsView = static_cast(DetailsView.Get()); + + if (!DetailsView->IsLocked()) + { + // + // START SELECTION + // Locks the details view and select our currently selected actors + // + LocalDetailsView->LockDetailsView(); + check(DetailsView->IsLocked()); + + // Force refresh of details view. + TArray InputOuters; + for (auto CurIn : InInputs) + InputOuters.Add(CurIn->GetOuter()); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + //ReselectSelectedActors(); + + if (bUseWorldInAsWorldSelector) + { + // Bound Selection + // Select back the previously chosen bound selectors + GEditor->SelectNone(false, true); + int32 NumBoundSelectors = MainInput->GetNumberOfBoundSelectorObjects(); + for (int32 Idx = 0; Idx < NumBoundSelectors; Idx++) + { + AActor* Actor = MainInput->GetBoundSelectorObjectAt(Idx); + if (!Actor || Actor->IsPendingKill()) + continue; + + GEditor->SelectActor(Actor, true, true); + } + } + else + { + // Regular selection + // Select the already chosen input Actors from the World Outliner. + GEditor->SelectNone(false, true); + int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); + for (int32 Idx = 0; Idx < NumInputObjects; Idx++) + { + UHoudiniInputObject* CurInputObject = MainInput->GetHoudiniInputObjectAt(Idx); + if (!CurInputObject) + continue; + + AActor* Actor = nullptr; + UHoudiniInputActor* InputActor = Cast(CurInputObject); + if (InputActor && !InputActor->IsPendingKill()) + { + // Get the input actor + Actor = InputActor->GetActor(); + } + else + { + // See if the input object is a HAC + UHoudiniInputHoudiniAsset* InputHAC = Cast(CurInputObject); + if (InputHAC && !InputHAC->IsPendingKill()) + { + Actor = InputHAC->GetHoudiniAssetComponent() ? InputHAC->GetHoudiniAssetComponent()->GetOwner() : nullptr; + } + } + + if (!Actor || Actor->IsPendingKill()) + continue; + + GEditor->SelectActor(Actor, true, true); + } + } + + return FReply::Handled(); + } + else + { + // + // UPDATE SELECTION + // Unlocks the input's selection and select the HDA back. + // + + if (!GEditor || !GEditor->GetSelectedObjects()) + return FReply::Handled(); + + USelection * SelectedActors = GEditor->GetSelectedActors(); + if (!SelectedActors) + return FReply::Handled(); + + // Create a transaction + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniWorldInputSelectionChanged", "Changing Houdini world outliner input objects"), + MainInput->GetOuter()); + + + TArray AllActors; + for (auto CurrentInput : InInputs) + { + CurrentInput->Modify(); + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + AllActors.Add(ParentActor); + + bool bHasChanged = true; + if (bUseWorldInAsWorldSelector) + { + // + // Update bound selectors + + // Clean up the selected actors + TArray ValidBoundSelectedActors; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor* CurrentBoundActor = Cast(*It); + if (!CurrentBoundActor) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentBoundActor == ParentActor)) + continue; + + ValidBoundSelectedActors.Add(CurrentBoundActor); + } + + // See if the bound selector have changed + int32 PreviousBoundSelectorCount = CurrentInput->GetNumberOfBoundSelectorObjects(); + if (PreviousBoundSelectorCount == ValidBoundSelectedActors.Num()) + { + // Same number of BoundSelectors, see if they have changed + bHasChanged = false; + for (int32 BoundIdx = 0; BoundIdx < PreviousBoundSelectorCount; BoundIdx++) + { + AActor* PreviousBound = CurrentInput->GetBoundSelectorObjectAt(BoundIdx); + if (!PreviousBound) + continue; + + if (!ValidBoundSelectedActors.Contains(PreviousBound)) + { + bHasChanged = true; + break; + } + } + } + + if (bHasChanged) + { + // Only update the bound selector objects on the input if they have changed + CurrentInput->SetBoundSelectorObjectsNumber(ValidBoundSelectedActors.Num()); + int32 InputObjectIdx = 0; + for (auto CurActor : ValidBoundSelectedActors) + { + CurrentInput->SetBoundSelectorObjectAt(InputObjectIdx++, CurActor); + } + + // Update the current selection from the BoundSelectors + CurrentInput->UpdateWorldSelectionFromBoundSelectors(); + } + } + else + { + // + // Update our selection directly with the currently selected actors + // + + TArray ValidSelectedActors; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor* CurrentActor = Cast(*It); + if (!CurrentActor) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + ValidSelectedActors.Add(CurrentActor); + } + + // Update the input objects from the valid selected actors array + // Only new/remove input objects will be marked as changed + bHasChanged = CurrentInput->UpdateWorldSelection(ValidSelectedActors); + } + + // If we didnt change the selection, cancel the transaction + if (!bHasChanged) + Transaction.Cancel(); + } + + // We can now unlock the details view... + LocalDetailsView->UnlockDetailsView(); + check(!DetailsView->IsLocked()); + + // .. reset the selected actors, force refresh and override the lock. + DetailsView->SetObjects(AllActors, true, true); + + // We now need to reselect all our Asset Actors. + // If we don't do this, our Asset parameters will stop refreshing and the user will be very confused. + // It is also resetting the state of the selection before the input actor selection process was started. + GEditor->SelectNone(false, true); + for (auto CurrentActor : AllActors) + { + AActor* ParentActor = Cast(CurrentActor); + if (!ParentActor) + continue; + + GEditor->SelectActor(ParentActor, true, true); + } + + // Update the input details layout. + // if (CategoryBuilder.IsParentLayoutValid()) + // CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); +} + + +bool +FHoudiniInputDetails::Helper_CancelWorldSelection(TArray& InInputs, const FName& DetailsPanelName) +{ + if (InInputs.Num() <= 0) + return false; + + // Get the property module to access the details view + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Locate the details panel. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + return false; + + if (!DetailsView->IsLocked()) + return false; + + class SLocalDetailsView : public SDetailsViewBase + { + public: + void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } + void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } + }; + auto * LocalDetailsView = static_cast(DetailsView.Get()); + + // Get all our parent components / actors + TArray AllComponents; + TArray AllActors; + for (auto CurrentInput : InInputs) + { + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + if (!ParentComponent) + continue; + + AllComponents.Add(ParentComponent); + + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + if (!ParentActor) + continue; + + AllActors.Add(ParentActor); + } + + // Unlock the detail view and re-select our parent actors + { + LocalDetailsView->UnlockDetailsView(); + check(!DetailsView->IsLocked()); + + // Reset selected actor to itself, force refresh and override the lock. + DetailsView->SetObjects(AllActors, true, true); + } + + // Reselect the Asset Actor. If we don't do this, our Asset parameters will stop + // refreshing and the user will be very confused. It is also resetting the state + // of the selection before the input actor selection process was started. + GEditor->SelectNone(false, true); + for (auto ParentActorObj : AllActors) + { + AActor* ParentActor = Cast(ParentActorObj); + if (!ParentActor) + continue; + + GEditor->SelectActor(ParentActor, true, true); + } + + return true; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h index b6be86b23..3153a1417 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h @@ -1,168 +1,168 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Widgets/SBoxPanel.h" -#include "IDetailsView.h" - -class UHoudiniInput; -class UHoudiniSplineComponent; - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class FMenuBuilder; -class SVerticalBox; -class IDetailsView; -class FReply; -class FAssetThumbnailPool; - -class FHoudiniInputDetails : public TSharedFromThis -{ - public: - static void CreateWidget( - IDetailCategoryBuilder& HouInputCategoryBuilder, - TArray InInputs, FDetailWidgetRow* InputRow = nullptr); - - static void CreateNameWidget( - UHoudiniInput* InParam, - FDetailWidgetRow & Row, - bool bLabel, - int32 InInputCount); - - static FText GetInputTooltip( UHoudiniInput* InInput ); - - // ComboBox : Input Type - static void AddInputTypeComboBox( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - const IDetailsView* InDetailsView); - - // Checkbox : Keep World Transform - static void AddKeepWorldTransformCheckBox( - TSharedRef InVerticalBox, - TArray& InInputs); - - static void AddCurveInputCookOnChangeCheckBox( - TSharedRef< SVerticalBox > VerticalBox, - TArray& InInputs); - - // Checkbox : Pack before merging - static void AddPackBeforeMergeCheckbox( - TSharedRef InVerticalBox, - TArray& InInputs); - - static void AddImportAsReferenceCheckbox( - TSharedRef< SVerticalBox > VerticalBox, - TArray& InInputs); - - // Checkboxes : Export LODs / Sockets / Collisions - static void AddExportCheckboxes( - TSharedRef InVerticalBox, - TArray& InInputs); - - // Add Geometry Inputs UI Widgets - static void AddGeometryInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - - // Create a single geometry widget for the given input object - static void Helper_CreateGeometryWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const FPlatformTypes::int32& InGeometryObjectIdx, - TSharedPtr AssetThumbnailPool, TSharedRef VerticalBox); - - static void Helper_CreateCurveWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InCurveObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox, - TSharedPtr HouSplineComponentVisualizer); - - // Add Asset Inputs UI Widgets - static void AddAssetInputUI( - TSharedRef VerticalBox, - TArray& InInputs); - - // Add Curve Inputs UI Widgets - static void AddCurveInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - - // Add Landscape Inputs UI Widgets - static void AddLandscapeInputUI( - TSharedRef VerticalBox, - TArray& InInputs); - - // Add World Inputs UI Widgets - static void AddWorldInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - const IDetailsView* InDetailsView); - - // Add Skeletal Inputs UI Widgets - static void AddSkeletalInputUI( - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - /* - static FMenuBuilder Helper_CreateCustomActorPickerWidget( - UHoudiniInput* InParam, - const TAttribute& HeadingText, - const bool& bShowCurrentSelectionSection) - */ - - static FMenuBuilder Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateLandscapePickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateWorldActorPickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateBoundSelectorPickerWidget(TArray& InInputs); - - static FReply Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); - - static FReply Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); - - static FReply Helper_OnButtonClickSelectActors( - IDetailCategoryBuilder& CategoryBuilder, - TArray InInputs, - const FName& InDetailsPanelName, - const bool& bUseWorldInAsWorldSelector); - - static bool Helper_CancelWorldSelection( - TArray& InInputs, const FName& DetailsPanelName); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Widgets/SBoxPanel.h" +#include "IDetailsView.h" + +class UHoudiniInput; +class UHoudiniSplineComponent; + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class FMenuBuilder; +class SVerticalBox; +class IDetailsView; +class FReply; +class FAssetThumbnailPool; + +class FHoudiniInputDetails : public TSharedFromThis +{ + public: + static void CreateWidget( + IDetailCategoryBuilder& HouInputCategoryBuilder, + TArray InInputs, FDetailWidgetRow* InputRow = nullptr); + + static void CreateNameWidget( + UHoudiniInput* InParam, + FDetailWidgetRow & Row, + bool bLabel, + int32 InInputCount); + + static FText GetInputTooltip( UHoudiniInput* InInput ); + + // ComboBox : Input Type + static void AddInputTypeComboBox( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + const IDetailsView* InDetailsView); + + // Checkbox : Keep World Transform + static void AddKeepWorldTransformCheckBox( + TSharedRef InVerticalBox, + TArray& InInputs); + + static void AddCurveInputCookOnChangeCheckBox( + TSharedRef< SVerticalBox > VerticalBox, + TArray& InInputs); + + // Checkbox : Pack before merging + static void AddPackBeforeMergeCheckbox( + TSharedRef InVerticalBox, + TArray& InInputs); + + static void AddImportAsReferenceCheckbox( + TSharedRef< SVerticalBox > VerticalBox, + TArray& InInputs); + + // Checkboxes : Export LODs / Sockets / Collisions + static void AddExportCheckboxes( + TSharedRef InVerticalBox, + TArray& InInputs); + + // Add Geometry Inputs UI Widgets + static void AddGeometryInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + + // Create a single geometry widget for the given input object + static void Helper_CreateGeometryWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const FPlatformTypes::int32& InGeometryObjectIdx, + TSharedPtr AssetThumbnailPool, TSharedRef VerticalBox); + + static void Helper_CreateCurveWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InCurveObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox, + TSharedPtr HouSplineComponentVisualizer); + + // Add Asset Inputs UI Widgets + static void AddAssetInputUI( + TSharedRef VerticalBox, + TArray& InInputs); + + // Add Curve Inputs UI Widgets + static void AddCurveInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + + // Add Landscape Inputs UI Widgets + static void AddLandscapeInputUI( + TSharedRef VerticalBox, + TArray& InInputs); + + // Add World Inputs UI Widgets + static void AddWorldInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + const IDetailsView* InDetailsView); + + // Add Skeletal Inputs UI Widgets + static void AddSkeletalInputUI( + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + /* + static FMenuBuilder Helper_CreateCustomActorPickerWidget( + UHoudiniInput* InParam, + const TAttribute& HeadingText, + const bool& bShowCurrentSelectionSection) + */ + + static FMenuBuilder Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateLandscapePickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateWorldActorPickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateBoundSelectorPickerWidget(TArray& InInputs); + + static FReply Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); + + static FReply Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); + + static FReply Helper_OnButtonClickSelectActors( + IDetailCategoryBuilder& CategoryBuilder, + TArray InInputs, + const FName& InDetailsPanelName, + const bool& bUseWorldInAsWorldSelector); + + static bool Helper_CancelWorldSelection( + TArray& InInputs, const FName& DetailsPanelName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp index ea1fdae46..1e737864c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp @@ -1,3256 +1,3655 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniOutputDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniAssetComponentDetails.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAsset.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniEngineCommands.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "DetailWidgetRow.h" -#include "IDetailGroup.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SRotatorInputBox.h" -#include "Widgets/Layout/SBox.h" -#include "Widgets/Text/STextBlock.h" -#include "Editor/UnrealEd/Public/AssetThumbnail.h" -#include "SAssetDropTarget.h" -#include "Engine/StaticMesh.h" -#include "Components/SplineComponent.h" -#include "Materials/Material.h" -#include "Materials/MaterialInstance.h" -#include "Materials/MaterialInstanceConstant.h" -#include "Sound/SoundBase.h" -#include "Engine/SkeletalMesh.h" -#include "Particles/ParticleSystem.h" -//#include "Landscape.h" -#include "LandscapeProxy.h" -#include "ScopedTransaction.h" -#include "PhysicsEngine/BodySetup.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" -#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -void -FHoudiniOutputDetails::CreateWidget( - IDetailCategoryBuilder& HouOutputCategory, - TArray InOutputs) -{ - if (InOutputs.Num() <= 0) - return; - - UHoudiniOutput* MainOutput = InOutputs[0]; - if (!IsValid(MainOutput)) - return; - - // Don't create UI for editable curve. - if (MainOutput->IsEditableNode() && MainOutput->GetType() == EHoudiniOutputType::Curve) - return; - - // Get thumbnail pool for this builder. - TSharedPtr AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); - - switch (MainOutput->GetType()) - { - case EHoudiniOutputType::Mesh: - { - FHoudiniOutputDetails::CreateMeshOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Landscape: - { - FHoudiniOutputDetails::CreateLandscapeOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Instancer: - { - FHoudiniOutputDetails::CreateInstancerOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Curve: - { - FHoudiniOutputDetails::CreateCurveOutputWidget(HouOutputCategory, MainOutput); - break; - } - case EHoudiniOutputType::Skeletal: - default: - { - FHoudiniOutputDetails::CreateDefaultOutputWidget(HouOutputCategory, MainOutput); - break; - } - } -} - - -void -FHoudiniOutputDetails::CreateLandscapeOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // Go through this output's objects - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& CurrentOutputObj : OutputObjects) - { - UHoudiniLandscapePtr* LandscapePointer = Cast(CurrentOutputObj.Value.OutputObject); - if (!LandscapePointer) - continue; - - FHoudiniOutputObjectIdentifier& Identifier = CurrentOutputObj.Key; - const FHoudiniGeoPartObject *HGPO = nullptr; - for (const auto& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!Identifier.Matches(CurHGPO)) - continue; - - HGPO = &CurHGPO; - break; - } - - if (!HGPO) - continue; - - CreateLandscapeOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapePointer, Identifier); - } -} - -void -FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - const FHoudiniGeoPartObject& HGPO, - UHoudiniLandscapePtr* LandscapePointer, - const FHoudiniOutputObjectIdentifier & OutputIdentifier) -{ - if (!LandscapePointer || LandscapePointer->IsPendingKill() || !LandscapePointer->LandscapeSoftPtr.IsValid()) - return; - - if (!InOutput || InOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return; - - ALandscapeProxy * Landscape = LandscapePointer->LandscapeSoftPtr.Get(); - if (!Landscape || Landscape->IsPendingKill()) - return; - - // TODO: Get bake base name - FString Label = Landscape->GetName(); - - EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType = LandscapePointer->BakeType; - - // Get thumbnail pool for this builder - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - TArray>* BakeOptionString = FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels(); - - // Create bake mesh name textfield. - IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - LandscapeGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(Label)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - // Create the thumbnail for the landscape output object. - TSharedPtr< FAssetThumbnail > LandscapeThumbnail = - MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); - - TSharedPtr< SBorder > LandscapeThumbnailBorder; - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - LandscapeGrp.AddWidgetRow() - .NameContent() - [ - SNew(SSpacer) - .Size(FVector2D(250, 64)) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot().Padding(0, 2).AutoHeight() - [ - SNew(SBox).WidthOverride(175) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(LandscapeThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(Landscape->GetPathName())) - [ - LandscapeThumbnail->MakeThumbnailWidget() - ] - ] - ] - - + SHorizontalBox::Slot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SBox).WidthOverride(40.0f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("Bake", "Bake")) - .IsEnabled(true) - .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() - { - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - if (FoundOutputObject) - { - TArray AllOutputs; - AllOutputs.Reserve(HAC->GetNumOutputs()); - HAC->GetOutputs(AllOutputs); - FHoudiniOutputDetails::OnBakeOutputObject( - FoundOutputObject->BakeName, - Landscape, - OutputIdentifier, - *FoundOutputObject, - HGPO, - HAC, - OwnerActor->GetName(), - HAC->BakeFolder.Path, - HAC->TemporaryCookFolder.Path, - InOutput->GetType(), - LandscapeOutputBakeType, - AllOutputs); - } - - // TODO: Remove the output landscape if the landscape bake type is Detachment? - return FReply::Handled(); - }) - .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) - ] - ] - + SHorizontalBox::Slot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SBox).WidthOverride(120.f) - [ - SNew(SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - if (SelectType != ESelectInfo::Type::OnMouseClick) - return; - - FString *NewChoiceStr = NewChoice.Get(); - if (!NewChoiceStr) - return; - - if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); - } - else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); - } - else - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); - } - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - [ - SNew(STextBlock) - .Text_Lambda([LandscapePointer]() - { - FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); - return FText::FromString(BakeTypeString); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ] - ] - ]; - - // Store thumbnail for this landscape. - OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); - - // We need to add material box for each the landscape and landscape hole materials - for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) - { - UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); - TSharedPtr MaterialThumbnailBorder; - TSharedPtr HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if (MaterialInterface) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - - // Create thumbnail for this material. - TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = - MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(STextBlock) - .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().Padding(0, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) - .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(MaterialThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(MaterialPathName)) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this landscape and material index. - { - TPairInitializer Pair(Landscape, MaterialIdx); - MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); - } - - // Combox Box and Button Box - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Combo row - TSharedPtr< SComboButton > AssetComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) - [ - SAssignNew(AssetComboButton, SComboButton) - //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - // Buttons row - TSharedPtr ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Add use Content Browser selection arrow - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)Landscape, InOutput, MaterialIdx), - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) - ]; - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), - TAttribute< FText >(MaterialTooltip)) - ]; - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - - // Store combo button for this mesh and index. - { - TPairInitializer Pair(Landscape, MaterialIdx); - MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); - } - } -} - -void -FHoudiniOutputDetails::CreateMeshOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent* HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - FString HoudiniAssetName; - if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) - { - HoudiniAssetName = HAC->GetOwner()->GetName(); - } - else if (HAC->GetHoudiniAsset()) - { - HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); - } - else - { - HoudiniAssetName = HAC->GetName(); - } - - // Go through this output's object - int32 OutputObjIdx = 0; - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& IterObject : OutputObjects) - { - UStaticMesh* StaticMesh = Cast(IterObject.Value.OutputObject); - UHoudiniStaticMesh* ProxyMesh = Cast(IterObject.Value.ProxyObject); - - if ((!StaticMesh || StaticMesh->IsPendingKill()) - && (!ProxyMesh || ProxyMesh->IsPendingKill())) - continue; - - FHoudiniOutputObjectIdentifier & OutputIdentifier = IterObject.Key; - - // Find the corresponding HGPO in the output - FHoudiniGeoPartObject HoudiniGeoPartObject; - for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!OutputIdentifier.Matches(curHGPO)) - continue; - - HoudiniGeoPartObject = curHGPO; - break; - } - - if (StaticMesh && !StaticMesh->IsPendingKill()) - { - bool bIsProxyMeshCurrent = IterObject.Value.bProxyIsCurrent; - - // If we have a static mesh, alway display its widget even if the proxy is more recent - CreateStaticMeshAndMaterialWidgets( - HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); - } - else - { - // If we only have a proxy mesh, then show the proxy widget - CreateProxyMeshAndMaterialWidgets( - HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject); - } - } -} - -void -FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - int32 OutputObjIdx = 0; - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& IterObject : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = IterObject.Value; - USceneComponent* SplineComponent = Cast(IterObject.Value.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - FHoudiniOutputObjectIdentifier& OutputIdentifier = IterObject.Key; - FHoudiniGeoPartObject HoudiniGeoPartObject; - for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!OutputIdentifier.Matches(curHGPO)) - continue; - - HoudiniGeoPartObject = curHGPO; - break; - } - - CreateCurveWidgets(HouOutputCategory, InOutput, SplineComponent, CurrentOutputObject, OutputIdentifier, HoudiniGeoPartObject); - } -} - -void -FHoudiniOutputDetails::CreateCurveWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - USceneComponent* SplineComponent, - FHoudiniOutputObject& OutputObject, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - FHoudiniGeoPartObject& HoudiniGeoPartObject) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // We support Unreal Spline out only for now - USplineComponent* SplineOutput = Cast(SplineComponent); - if (!SplineOutput || SplineOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return; - - FHoudiniCurveOutputProperties* OutputProperty = &(OutputObject.CurveOutputProperty); - EHoudiniCurveType OutputCurveType = OutputObject.CurveOutputProperty.CurveType; - - FString Label = SplineComponent->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - //Label += FString("_") + OutputIdentifier.SplitIdentifier; - - FString OutputCurveName = OutputObject.BakeName; - if(OutputCurveName.IsEmpty()) - OutputCurveName = OwnerActor->GetName() + "_" + Label; - - const FText& LabelText = FText::FromString("Unreal Spline"); - - IDetailGroup& CurveOutputGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label), false, false); - - // Bake name row UI - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(OutputObject.BakeName)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - ] - - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("OutputCurveSplineType", "Spline Type")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText_Lambda([SplineOutput, Label, OutputCurveType]() - { - FString ToolTipStr = FString::Printf(TEXT(" curve: %s\n Export type: Unreal Spline\n num points: %d\n curve type: %s\n closed: %s"), - *Label, - SplineOutput->GetNumberOfSplinePoints(), - *FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(OutputCurveType), - SplineOutput->IsClosedLoop() ? *(FString("yes")) : *(FString("no"))); - - return FText::FromString(ToolTipStr); - }) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(STextBlock) - // We support Unreal Spline output only for now... - .Text(LOCTEXT("OutputCurveSplineTypeUnreal", "Unreal Spline")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ]; - - //if (bIsUnrealSpline) - //{ - USplineComponent* UnrealSpline = Cast(SplineComponent); - - // Curve type combo box UI - auto InitialSelectionLambda = [OutputProperty]() - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - { - return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[0]; - } - else - { - return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[1]; - } - }; - - TSharedPtr>> UnrealCurveTypeComboBox; - - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Text(LOCTEXT("OutputCurveUnrealSplinePointType", "Spline Point Type")) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(UnrealCurveTypeComboBox, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels()) - .InitiallySelectedItem(InitialSelectionLambda()) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [OutputProperty, InOutput, SplineComponent](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - // Set the curve point type locally - USplineComponent* Spline = Cast(SplineComponent); - if (!Spline || Spline->IsPendingKill()) - return; - - FString *NewChoiceStr = NewChoice.Get(); - if (!NewChoiceStr) - return; - - if (*NewChoiceStr == "Linear") - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - return; - - OutputProperty->CurveType = EHoudiniCurveType::Polygon; - - for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) - { - Spline->SetSplinePointType(PtIdx, ESplinePointType::Linear); - } - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - } - else if (*NewChoiceStr == "Curve") - { - if (OutputProperty->CurveType != EHoudiniCurveType::Polygon) - return; - - OutputProperty->CurveType = EHoudiniCurveType::Bezier; - - for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) - { - Spline->SetSplinePointType(PtIdx, ESplinePointType::Curve); - } - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([OutputProperty]() - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - return FText::FromString(TEXT("Linear")); - else - return FText::FromString(TEXT("Curve")); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - // Add closed curve checkbox UI - TSharedPtr ClosedCheckBox; - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Text(LOCTEXT("OutputCurveUnrealSplineClosed", "Closed")) - ] - .ValueContent() - [ - SAssignNew(ClosedCheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([UnrealSpline, InOutput](ECheckBoxState NewState) - { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) - return; - - UnrealSpline->SetClosedLoop(NewState == ECheckBoxState::Checked); - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - .IsChecked_Lambda([UnrealSpline]() - { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return UnrealSpline->IsClosedLoop() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - ]; - //} - - // Add Bake Button UI - TSharedPtr BakeButton; - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - ] - .ValueContent() - [ - SAssignNew(BakeButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) - .IsEnabled(true) - .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) - .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName, OutputObject]() - { - TArray AllOutputs; - AllOutputs.Reserve(HAC->GetNumOutputs()); - HAC->GetOutputs(AllOutputs); - FHoudiniOutputDetails::OnBakeOutputObject( - OutputCurveName, - SplineComponent, - OutputIdentifier, - OutputObject, - HoudiniGeoPartObject, - HAC, - OwnerActor->GetName(), - HAC->BakeFolder.Path, - HAC->TemporaryCookFolder.Path, - InOutput->GetType(), - EHoudiniLandscapeOutputBakeType::InValid, - AllOutputs); - - return FReply::Handled(); - }) - ]; -} - -void -FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UStaticMesh * StaticMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject, - const bool& bIsProxyMeshCurrent) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return; - - UHoudiniAssetComponent* OwningHAC = Cast(InOutput->GetOuter()); - - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); - - // Get thumbnail pool for this builder. - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // TODO: GetBakingBaseName! - FString Label = StaticMesh->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - // Create thumbnail for this mesh. - TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = - MakeShareable(new FAssetThumbnail(StaticMesh, 64, 64, AssetThumbnailPool)); - TSharedPtr StaticMeshThumbnailBorder; - - TSharedRef VerticalBox = SNew(SVerticalBox); - - IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(BakeName)) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - }) - .ToolTipText( LOCTEXT( "BakeNameTip", "The base name of the baked asset") ) - ] - - +SHorizontalBox::Slot() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "RevertNameOverride", "Revert bake name override" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ] - ]; - - // Add details on the SM colliders - EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); - FString MeshLabel = TEXT( "Static Mesh" ); - - // If the Proxy mesh is more recent, indicate it in the details - if (bIsProxyMeshCurrent) - { - MeshLabel += TEXT("\n(unrefined)"); - } - - // Indicate that this mesh is instanced - if (HoudiniGeoPartObject.bIsInstanced) - { - MeshLabel += TEXT("\n(instanced)"); - } - - if (HoudiniGeoPartObject.bIsTemplated) - { - MeshLabel += TEXT("\n(templated)"); - } - - int32 NumSimpleColliders = 0; - if (StaticMesh->BodySetup && !StaticMesh->BodySetup->IsPendingKill()) - NumSimpleColliders = StaticMesh->BodySetup->AggGeom.GetElementCount(); - - if(NumSimpleColliders > 0) - { - MeshLabel += TEXT( "\n(") + FString::FromInt(NumSimpleColliders) + TEXT(" Simple Collider" ); - if (NumSimpleColliders > 1 ) - MeshLabel += TEXT("s"); - MeshLabel += TEXT(")"); - } - else if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - MeshLabel += TEXT( "\n(Rendered Complex Collider)" ); - } - else if(SplitType == EHoudiniSplitType::InvisibleComplexCollider ) - { - MeshLabel += TEXT( "\n(Invisible Complex Collider)" ); - } - - if ( StaticMesh->GetNumLODs() > 1 ) - MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)"); - - if ( StaticMesh->Sockets.Num() > 0 ) - MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->Sockets.Num() ) + TEXT(" sockets)"); - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew( STextBlock ) - .Text( FText::FromString(MeshLabel) ) - .Font( IDetailLayoutBuilder::GetDetailFont() ) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot() - .Padding( 0, 2 ) - .AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) - .AutoWidth() - [ - SAssignNew( StaticMeshThumbnailBorder, SBorder ) - .Padding( 5.0f ) - .BorderImage( this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)StaticMesh ) - .OnMouseDoubleClick( this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *) StaticMesh ) - [ - SNew( SBox ) - .WidthOverride( 64 ) - .HeightOverride( 64 ) - .ToolTipText( FText::FromString( StaticMesh->GetPathName() ) ) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ] - - +SHorizontalBox::Slot() - .FillWidth( 1.0f ) - .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SVerticalBox ) - +SVerticalBox::Slot() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .MaxWidth( 80.0f ) - [ - SNew( SButton ) - .VAlign( VAlign_Center ) - .HAlign( HAlign_Center ) - .Text( LOCTEXT( "Bake", "Bake" ) ) - .IsEnabled(true) - .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC, FoundOutputObject]() - { - if (FoundOutputObject) - { - TArray AllOutputs; - FString TempCookFolder; - if (IsValid(OwningHAC)) - { - AllOutputs.Reserve(OwningHAC->GetNumOutputs()); - OwningHAC->GetOutputs(AllOutputs); - - TempCookFolder = OwningHAC->TemporaryCookFolder.Path; - } - FHoudiniOutputDetails::OnBakeOutputObject( - BakeName, - StaticMesh, - OutputIdentifier, - *FoundOutputObject, - HoudiniGeoPartObject, - OwningHAC, - HoudiniAssetName, - BakeFolder, - TempCookFolder, - InOutput->GetType(), - EHoudiniLandscapeOutputBakeType::InValid, - AllOutputs); - } - - return FReply::Handled(); - }) - .ToolTipText( LOCTEXT( "HoudiniStaticMeshBakeButton", "Bake this generated static mesh" ) ) - ] - +SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)StaticMesh), - TAttribute(LOCTEXT("HoudiniStaticMeshBrowseButton", "Browse to this generated static mesh in the content browser"))) - ] - ] - ] - ]; - - // Store thumbnail for this mesh. - OutputObjectThumbnailBorders.Add((UObject*)StaticMesh, StaticMeshThumbnailBorder); - - // We need to add material box for each material present in this static mesh. - auto & StaticMeshMaterials = StaticMesh->StaticMaterials; - for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) - { - UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface; - TSharedPtr< SBorder > MaterialThumbnailBorder; - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if ( MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - else - { - MaterialInterface = nullptr; - MaterialName = TEXT("Material (invalid)") + FString::FromInt( MaterialIdx ) ; - MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - } - - // Create thumbnail for this material. - TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = - MakeShareable( new FAssetThumbnail( MaterialInterface, 64, 64, AssetThumbnailPool ) ); - - VerticalBox->AddSlot().Padding( 0, 2 ) - [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop( this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver ) - .OnAssetDropped( - this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx ) - [ - SAssignNew( HorizontalBox, SHorizontalBox ) - ] - ]; - - HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() - [ - SAssignNew( MaterialThumbnailBorder, SBorder ) - .Padding( 5.0f ) - .BorderImage( - this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject *)StaticMesh, MaterialIdx ) - .OnMouseDoubleClick( - this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface ) - [ - SNew( SBox ) - .WidthOverride( 64 ) - .HeightOverride( 64 ) - .ToolTipText( FText::FromString( MaterialPathName ) ) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this mesh and material index. - { - TPairInitializer Pair( StaticMesh, MaterialIdx ); - MaterialInterfaceThumbnailBorders.Add( Pair, MaterialThumbnailBorder ); - } - - // ComboBox and buttons - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add Combo box - TSharedPtr< SComboButton > AssetComboButton; - ComboAndButtonBox->AddSlot().VAlign(VAlign_Center).FillHeight(1.0f) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().VAlign(VAlign_Center).FillHeight(1.0f) - [ - SAssignNew(AssetComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, - MaterialInterface, (UObject*)StaticMesh, InOutput, MaterialIdx) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - - // Add buttons - TSharedPtr< SHorizontalBox > ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Use CB selection arrow button - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)StaticMesh, InOutput, MaterialIdx), - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) - ]; - - // Browse CB button - ButtonBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface ), TAttribute< FText >( MaterialTooltip ) ) - ]; - - // Reset button - ButtonBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "ResetToBaseMaterial", "Reset to base material" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked( - this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ]; - - // Store combo button for this mesh and index. - { - TPairInitializer Pair( StaticMesh, MaterialIdx ); - MaterialInterfaceComboButtons.Add( Pair, AssetComboButton ); - } - } -} - -void -FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UHoudiniStaticMesh * ProxyMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject) -{ - if (!ProxyMesh || ProxyMesh->IsPendingKill()) - return; - - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); - - // Get thumbnail pool for this builder. - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // TODO: GetBakingBaseName! - FString Label = ProxyMesh->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - // Create thumbnail for this mesh. - TSharedPtr MeshThumbnail = MakeShareable(new FAssetThumbnail(ProxyMesh, 64, 64, AssetThumbnailPool)); - TSharedPtr MeshThumbnailBorder; - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(BakeName)) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - }) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - // Add details on the Proxy Mesh - EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); - FString MeshLabel = TEXT("Proxy Mesh"); - - // Indicate that this mesh is instanced - if (HoudiniGeoPartObject.bIsInstanced) - { - MeshLabel += TEXT("\n(instanced)"); - } - - if (HoudiniGeoPartObject.bIsTemplated) - { - MeshLabel += TEXT("\n(templated)"); - } - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(FText::FromString(MeshLabel)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot() - .Padding(0, 2) - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(MeshThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)ProxyMesh) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)ProxyMesh) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(ProxyMesh->GetPathName())) - [ - MeshThumbnail->MakeThumbnailWidget() - ] - ] - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .MaxWidth(80.0f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("Refine", "Refine")) - .IsEnabled(true) - .OnClicked(this, &FHoudiniOutputDetails::OnRefineClicked, (UObject *)ProxyMesh, InOutput) - .ToolTipText(LOCTEXT("RefineTooltip", "Refine this Proxy Mesh to a Static Mesh")) - ] - ] - ] - ]; - - // Store thumbnail for this mesh. - OutputObjectThumbnailBorders.Add(ProxyMesh, MeshThumbnailBorder); - - // We need to add material box for each material present in this static mesh. - auto & ProxyMeshMaterials = ProxyMesh->GetStaticMaterials(); - for (int32 MaterialIdx = 0; MaterialIdx < ProxyMeshMaterials.Num(); ++MaterialIdx) - { - UMaterialInterface * MaterialInterface = ProxyMeshMaterials[MaterialIdx].MaterialInterface; - TSharedPtr< SBorder > MaterialThumbnailBorder; - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if (MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill()) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - else - { - MaterialInterface = nullptr; - MaterialName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - } - - // Create thumbnail for this material. - TSharedPtr MaterialInterfaceThumbnail = - MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); - - // No drop target - VerticalBox->AddSlot() - .Padding(0, 2) - [ - SNew(SAssetDropTarget) - //.OnIsAssetAcceptableForDrop(false) - //.OnAssetDropped( - // this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - HorizontalBox->AddSlot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(MaterialThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage( - this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)ProxyMesh, MaterialIdx) - .OnMouseDoubleClick( - this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(MaterialPathName)) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this mesh and material index. - { - TPairInitializer Pair((UObject*)ProxyMesh, MaterialIdx); - MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); - } - - // Combo box and buttons - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add combo box - TSharedPtr AssetComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SAssignNew(AssetComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - /*.OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, - MaterialInterface, StaticMesh, InOutput, MaterialIdx)*/ - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - - TSharedPtr ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Disable the combobutton for proxies - AssetComboButton->SetEnabled(false); - - // Add use selection form content browser array - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - /*FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)ProxyMesh, InOutput, MaterialIdx),*/ - FSimpleDelegate::CreateLambda([]() {}), // Do nothing for proxies - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser")), false) - // Disable the use CB selection button for proxies - ]; - - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP(this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), TAttribute(MaterialTooltip)) - ]; - - /* - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked( - this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - */ - - // Store combo button for this mesh and index. - { - TPairInitializer Pair(ProxyMesh, MaterialIdx); - MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); - } - } -} - -FText -FHoudiniOutputDetails::GetOutputDebugName(UHoudiniOutput* InOutput) -{ - // Get the name and type - FString OutputNameStr = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); - - // Then add the number of parts - OutputNameStr += TEXT(" (") + FString::FromInt(InOutput->GetHoudiniGeoPartObjects().Num()) + TEXT(" Part(s))\n"); - - return FText::FromString(OutputNameStr); -} -FText -FHoudiniOutputDetails::GetOutputDebugDescription(UHoudiniOutput* InOutput) -{ - const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); - - FString OutputValStr; - OutputValStr += TEXT("HGPOs:\n"); - for (auto& HGPO : HGPOs) - { - OutputValStr += TEXT(" - ") + HGPO.PartName + TEXT(" (") + FHoudiniGeoPartObject::HoudiniPartTypeToString(HGPO.Type) + TEXT(")"); - - if (HGPO.SplitGroups.Num() > 0) - { - OutputValStr += TEXT("( ") + FString::FromInt(HGPO.SplitGroups.Num()) + TEXT(" splits:"); - for (auto& split : HGPO.SplitGroups) - { - OutputValStr += TEXT(" ") + split; - } - OutputValStr += TEXT(")"); - } - - if (!HGPO.VolumeName.IsEmpty()) - { - OutputValStr += TEXT("( ") + HGPO.VolumeName; - if (HGPO.VolumeTileIndex >= 0) - OutputValStr += TEXT(" tile ") + FString::FromInt(HGPO.VolumeTileIndex); - OutputValStr += TEXT(" )"); - } - - OutputValStr += TEXT("\n"); - } - - // Add output objects if any - TMap AllOutputObj = InOutput->GetOutputObjects(); - if (AllOutputObj.Num() > 0) - { - bool TitleAdded = false; - for (const auto& Iter : AllOutputObj) - { - UObject* OutObject = Iter.Value.OutputObject; - if (OutObject) - { - OutputValStr += OutObject->GetFullName() + TEXT(" (obj)\n"); - } - - UObject* OutComp = Iter.Value.OutputComponent; - if (OutComp) - { - OutputValStr += OutObject->GetFullName() + TEXT(" (comp)\n"); - } - } - } - - return FText::FromString(OutputValStr); -} - -FText -FHoudiniOutputDetails::GetOutputTooltip(UHoudiniOutput* InOutput) -{ - // TODO - return FText(); -} - - -const FSlateBrush * -FHoudiniOutputDetails::GetThumbnailBorder(UObject* Mesh) const -{ - TSharedPtr ThumbnailBorder = OutputObjectThumbnailBorders[Mesh]; - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - - -const FSlateBrush * -FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(UObject* OutputObject, int32 MaterialIdx) const -{ - if (!OutputObject) - return nullptr; - - TPairInitializer Pair(OutputObject, MaterialIdx); - TSharedPtr ThumbnailBorder = MaterialInterfaceThumbnailBorders[Pair]; - - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - -/* -const FSlateBrush * -FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx) const -{ - if (!Landscape) - return nullptr; - - TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); - TSharedPtr< SBorder > ThumbnailBorder = LandscapeMaterialInterfaceThumbnailBorders[Pair]; - - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} -*/ - -FReply -FHoudiniOutputDetails::OnThumbnailDoubleClick( - const FGeometry & InMyGeometry, - const FPointerEvent & InMouseEvent, UObject * Object) -{ - if (Object && GEditor) - GEditor->EditObject(Object); - - return FReply::Handled(); -} - -/* -FReply -FHoudiniOutputDetails::OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject) -{ - if (HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill()) - { - FHoudiniPackageParams PackageParms; - - - FHoudiniEngineBakeUtils::BakeStaticMesh(HoudiniAssetComponent, GeoPartObject, StaticMesh, PackageParms); - // TODO: Bake the SM - - - // We need to locate corresponding geo part object in component. - const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(StaticMesh); - - // (void)FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( - // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); - - } - - return FReply::Handled(); -} -*/ - -bool -FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver(const UObject * InObject) const -{ - return (InObject && InObject->IsA(UMaterialInterface::StaticClass())); -} - - -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - UStaticMesh * StaticMesh, - UHoudiniOutput * HoudiniOutput, - int32 MaterialIdx) -{ - FReply RetValue = FReply::Handled(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return RetValue; - - if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) - return RetValue; - - // Retrieve material interface which is being replaced. - UMaterialInterface * MaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; - if (!MaterialInterface) - return RetValue; - - // Find the string corresponding to the material that is being replaced - const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); - if (!FoundString ) - { - // This material was not replaced, no need to reset it - return RetValue; - } - - // This material has been replaced previously. - FString MaterialString = *FoundString; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), HoudiniOutput); - - // Remove the replacement - HoudiniOutput->Modify(); - HoudiniOutput->GetReplacementMaterials().Remove(MaterialString); - - bool bViewportNeedsUpdate = true; - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - UMaterialInterface * const * FoundMat = HoudiniOutput->GetAssignementMaterials().Find(MaterialString); - if (FoundMat && (*FoundMat)) - AssignMaterial = *FoundMat; - - // Replace material on static mesh. - StaticMesh->Modify(); - StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = AssignMaterial; - - // Replace the material on any component (SMC/ISMC) that uses the above SM - // TODO: ?? Replace for all? - for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) - { - // Only look at MeshComponents - UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); - if (!SMC) - continue; - - if (SMC->GetStaticMesh() != StaticMesh) - continue; - - SMC->Modify(); - SMC->SetMaterial(MaterialIdx, AssignMaterial); - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - return RetValue; -} - -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - ALandscapeProxy* InLandscape, - UHoudiniOutput * InHoudiniOutput, - int32 InMaterialIdx) -{ - FReply RetValue = FReply::Handled(); - if (!InLandscape || InLandscape->IsPendingKill()) - return RetValue; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * MaterialInterface = InMaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); - UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - - // Find the string corresponding to the material that is being replaced - const FString* FoundString = InHoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); - if (!FoundString) - { - // This material was not replaced, no need to reset it - return RetValue; - } - - // This material has been replaced previously. - FString MaterialString = *FoundString; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), InHoudiniOutput); - - // Remove the replacement - InHoudiniOutput->Modify(); - InHoudiniOutput->GetReplacementMaterials().Remove(MaterialString); - - bool bViewportNeedsUpdate = true; - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - UMaterialInterface * const * FoundMat = InHoudiniOutput->GetAssignementMaterials().Find(MaterialString); - if (FoundMat && (*FoundMat)) - AssignMaterial = *FoundMat; - - // Replace material on Landscape - InLandscape->Modify(); - if (InMaterialIdx == 0) - InLandscape->LandscapeMaterial = AssignMaterial; - else - InLandscape->LandscapeHoleMaterial = AssignMaterial; - - InLandscape->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); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - */ - - FHoudiniEngineUtils::UpdateEditorProperties(InHoudiniOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - return RetValue; -} -/* -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - ALandscapeProxy * Landscape, UHoudiniOutput * InOutput, int32 MaterialIdx) -{ - bool bViewportNeedsUpdate = false; - - // TODO: Handle me! - for (TArray< UHoudiniAssetComponent * >::TIterator - IterComponents(HoudiniAssetComponents); IterComponents; ++IterComponents) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; - if (!HoudiniAssetComponent) - continue; - - TWeakObjectPtr* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find(*HoudiniGeoPartObject); - if (!FoundLandscapePtr) - continue; - - ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get(); - if (!FoundLandscape || !FoundLandscape->IsValidLowLevel()) - continue; - - if (FoundLandscape != Landscape) - continue; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); - UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - - bool bMaterialRestored = false; - FString MaterialShopName; - if (!HoudiniAssetComponent->GetReplacementMaterialShopName(*HoudiniGeoPartObject, MaterialInterface, MaterialShopName)) - { - // This material was not replaced so there's no need to reset it - continue; - } - - // Remove the replacement - HoudiniAssetComponent->RemoveReplacementMaterial(*HoudiniGeoPartObject, MaterialShopName); - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial(MaterialShopName); - if (AssignedMaterial) - MaterialInterfaceReplacement = AssignedMaterial; - - // Replace material on the landscape - Landscape->Modify(); - - if (MaterialIdx == 0) - Landscape->LandscapeMaterial = MaterialInterfaceReplacement; - else - Landscape->LandscapeHoleMaterial = MaterialInterfaceReplacement; - - //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); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - - HoudiniAssetComponent->UpdateEditorProperties(false); - bViewportNeedsUpdate = true; - } - - if (GEditor && bViewportNeedsUpdate) - { - GEditor->RedrawAllViewports(); - } - - return FReply::Handled(); -} -*/ - -void -FHoudiniOutputDetails::OnBrowseTo(UObject* InObject) -{ - if (GEditor) - { - TArray Objects; - Objects.Add(InObject); - GEditor->SyncBrowserToObjects(Objects); - } -} - -TSharedRef -FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent( - UMaterialInterface* MaterialInterface, - UObject* OutputObject, - UHoudiniOutput* InOutput, - int32 MaterialIdx) -{ - TArray AllowedClasses; - AllowedClasses.Add(UMaterialInterface::StaticClass()); - - TArray NewAssetFactories; - - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(MaterialInterface), - true, - AllowedClasses, - NewAssetFactories, - OnShouldFilterMaterialInterface, - FOnAssetSelected::CreateSP( - this, &FHoudiniOutputDetails::OnMaterialInterfaceSelected, OutputObject, InOutput, MaterialIdx), - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::CloseMaterialInterfaceComboButton)); -} - - -void -FHoudiniOutputDetails::CloseMaterialInterfaceComboButton() -{ - -} - -void -FHoudiniOutputDetails::OnMaterialInterfaceDropped( - UObject * InObject, - UStaticMesh * StaticMesh, - UHoudiniOutput * HoudiniOutput, - int32 MaterialIdx) -{ - UMaterialInterface * MaterialInterface = Cast(InObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - return; - - if (!StaticMesh || StaticMesh->IsPendingKill()) - return; - - if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) - return; - - bool bViewportNeedsUpdate = false; - - // Retrieve material interface which is being replaced. - UMaterialInterface * OldMaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; - if (OldMaterialInterface == MaterialInterface) - return; - - // Find the string corresponding to the material that is being replaced - FString MaterialString = FString(); - const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been replaced previously. - MaterialString = *FoundString; - } - else - { - // We have no previous replacement for this material, - // see if we can find it the material assignment list. - FoundString = HoudiniOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been assigned previously. - MaterialString = *FoundString; - } - else - { - UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - if (OldMaterialInterface == DefaultMaterial) - { - // This is replacement for default material. - MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - } - else - { - // External Material? - MaterialString = OldMaterialInterface->GetName(); - } - } - } - - if (MaterialString.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), HoudiniOutput); - - // Add a new material replacement entry. - HoudiniOutput->Modify(); - HoudiniOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); - - // Replace material on static mesh. - StaticMesh->Modify(); - StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = MaterialInterface; - - // Replace the material on any component (SMC/ISMC) that uses the above SM - for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) - { - // Only look at MeshComponents - UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); - if (SMC && !SMC->IsPendingKill()) - { - if (SMC->GetStaticMesh() == StaticMesh) - { - SMC->Modify(); - SMC->SetMaterial(MaterialIdx, MaterialInterface); - } - } - else - { - UStaticMesh* SM = Cast(OutputObject.Value.OutputObject); - if (SM && !SM->IsPendingKill()) - { - SM->Modify(); - SM->SetMaterial(MaterialIdx, MaterialInterface); - } - } - - - - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); - - /* - if(GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); -*/ - if (GEditor) - GEditor->RedrawAllViewports(); -} - -// Delegate used when a valid material has been drag and dropped on a landscape. -void -FHoudiniOutputDetails::OnMaterialInterfaceDropped( - UObject* InDroppedObject, - ALandscapeProxy* InLandscape, - UHoudiniOutput* InOutput, - int32 MaterialIdx) -{ - UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >(InDroppedObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - return; - - if (!InLandscape || InLandscape->IsPendingKill()) - return; - - bool bViewportNeedsUpdate = false; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * OldMaterialInterface = MaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); - if (OldMaterialInterface == MaterialInterface) - return; - - // Find the string corresponding to the material that is being replaced - FString MaterialString = FString(); - const FString* FoundString = InOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been replaced previously. - MaterialString = *FoundString; - } - else - { - // We have no previous replacement for this material, - // see if we can find it the material assignment list. - FoundString = InOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been assigned previously. - MaterialString = *FoundString; - } - else - { - UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - if (OldMaterialInterface == DefaultMaterial) - { - // This is replacement for default material. - MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - } - else - { - // External Material? - if (OldMaterialInterface && !OldMaterialInterface->IsPendingKill()) - MaterialString = OldMaterialInterface->GetName(); - } - } - } - - if (MaterialString.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), InOutput); - - // Add a new material replacement entry. - InOutput->Modify(); - InOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); - - // Replace material on the landscape - InLandscape->Modify(); - - if (MaterialIdx == 0) - InLandscape->LandscapeMaterial = MaterialInterface; - else - InLandscape->LandscapeHoleMaterial = MaterialInterface; - - // Update the landscape components Material instances - InLandscape->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 >(InLandscape->GetClass(), (MaterialIdx == 0) ? TEXT("LandscapeMaterial") : TEXT("LandscapeHoleMaterial")); - if (FoundProperty) - { - FPropertyChangedEvent PropChanged(FoundProperty, EPropertyChangeType::ValueSet); - InLandscape->PostEditChangeProperty(PropChanged); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - */ - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); -} - -void -FHoudiniOutputDetails::OnMaterialInterfaceSelected( - const FAssetData & AssetData, - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - TPairInitializer Pair(OutputObject, MaterialIdx); - TSharedPtr AssetComboButton = MaterialInterfaceComboButtons[Pair]; - if (AssetComboButton.IsValid()) - { - AssetComboButton->SetIsOpen(false); - - UObject * Object = AssetData.GetAsset(); - - UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); - } - - ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } - } -} - -void -FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - if (!OutputObject || OutputObject->IsPendingKill()) - return; - - if (!InOutput || InOutput->IsPendingKill()) - return; - - if (GEditor) - { - TArray CBSelections; - GEditor->GetContentBrowserSelections(CBSelections); - - // Get the first selected material object - UObject* Object = nullptr; - for (auto & CurAssetData : CBSelections) - { - if (CurAssetData.AssetClass != UMaterial::StaticClass()->GetFName() && - CurAssetData.AssetClass != UMaterialInstance::StaticClass()->GetFName() && - CurAssetData.AssetClass != UMaterialInstanceConstant::StaticClass()->GetFName()) - continue; - - Object = CurAssetData.GetAsset(); - break; - } - - if (Object && !Object->IsPendingKill()) - { - UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); - } - - ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } - } - } -} - -void -FHoudiniOutputDetails::CreateInstancerOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // Do not display instancer UI for one-instance instancers - bool OnlyOneInstanceInstancers = true; - for (auto& Iter : InOutput->GetInstancedOutputs()) - { - FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); - if (CurInstanceOutput.OriginalTransforms.Num() <= 1) - continue; - - OnlyOneInstanceInstancers = false; - break; - } - - // This output only has one-instance instancers (SMC), no need to display the instancer UI. - if (OnlyOneInstanceInstancers) - return; - - // Classes allowed for instance variations. - const TArray AllowedClasses = - { - UStaticMesh::StaticClass(), USkeletalMesh::StaticClass(), - AActor::StaticClass(), UBlueprint::StaticClass(), - UFXSystemAsset::StaticClass(), USoundBase::StaticClass() - }; - - // Classes not allowed for instances variations (useless?) - TArray DisallowedClasses = - { - UClass::StaticClass(), ULevel::StaticClass(), - UMaterial::StaticClass(), UTexture::StaticClass() - }; - - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // Lambda for adding new variation objects - auto AddObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) - { - // TODO: undo/redo? - InOutputToUpdate.VariationObjects.Insert(InObject, AtIndex); - InOutputToUpdate.VariationTransformOffsets.Insert(FTransform::Identity, AtIndex); - FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for adding new geometry input objects - auto RemoveObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex) - { - // Also keep one instance object - if (AtIndex < 0 || AtIndex >= InOutputToUpdate.VariationObjects.Num()) - return; - - if (InOutputToUpdate.VariationObjects.Num() == 1) - return; - - // TODO: undo/redo? - InOutputToUpdate.VariationObjects.RemoveAt(AtIndex); - InOutputToUpdate.VariationTransformOffsets.RemoveAt( AtIndex); - FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for updating a variation - auto SetObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) - { - if (!InOutputToUpdate.VariationObjects.IsValidIndex(AtIndex)) - return; - - InOutputToUpdate.VariationObjects[AtIndex] = InObject; - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for changing the transform offset values - auto ChangeTransformOffsetAt = [InOutput]( - FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, - const float& Value, const int32& PosRotScaleIndex, const int32& XYZIndex) - { - bool bChanged = InOutputToUpdate.SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); - if (!bChanged) - return; - - InOutputToUpdate.MarkChanged(true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Get this output's OutputObject - const TMap& OutputObjects = InOutput->GetOutputObjects(); - - // Iterate on all of the output's HGPO - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - // Not an instancer, skip - if (CurHGPO.Type != EHoudiniPartType::Instancer) - continue; - - // Get the label for that instancer - FString InstancerLabel = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); - if (CurHGPO.bHasCustomPartName) - InstancerLabel = CurHGPO.PartName; - - TSharedRef InstancerVerticalBox = SNew(SVerticalBox); - TSharedPtr InstancerHorizontalBox = nullptr; - - // Create a new Group for that instancer - IDetailGroup& InstancerGroup = HouOutputCategory.AddGroup(FName(*InstancerLabel), FText::FromString(InstancerLabel)); - - // Now iterate and display the instance outputs that matches this HGPO - for (auto& Iter : InOutput->GetInstancedOutputs()) - { - FHoudiniOutputObjectIdentifier& CurOutputObjectIdentifier = Iter.Key; - if (!CurOutputObjectIdentifier.Matches(CurHGPO)) - continue; - - FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); - - // Dont display instancer UI for one-instance instancers (SMC) - if (CurInstanceOutput.OriginalTransforms.Num() <= 1) - continue; - - for( int32 VariationIdx = 0; VariationIdx < CurInstanceOutput.VariationObjects.Num(); VariationIdx++ ) - { - UObject * InstancedObject = CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous(); - if ( !InstancedObject || InstancedObject->IsPendingKill() ) - { - HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); - continue; - } - - // Create thumbnail for this object. - TSharedPtr VariationThumbnail = - MakeShareable(new FAssetThumbnail(InstancedObject, 64, 64, AssetThumbnailPool)); - TSharedRef PickerVerticalBox = SNew(SVerticalBox); - TSharedPtr PickerHorizontalBox = nullptr; - TSharedPtr VariationThumbnailBorder; - - // For the variation name, reuse the instancer label and append the variation index if we have more than one variation - FString InstanceOutputLabel = InstancerLabel; - if(CurInstanceOutput.VariationObjects.Num() > 1) - InstanceOutputLabel += TEXT(" [") + FString::FromInt(VariationIdx) + TEXT("]"); - - IDetailGroup* DetailGroup = &InstancerGroup; - if (CurInstanceOutput.VariationObjects.Num() > 1) - { - // If we have more than one variation, add a new group for each variation - DetailGroup = &InstancerGroup.AddGroup(FName(*InstanceOutputLabel), FText::FromString(InstanceOutputLabel), true); - } - - // See if we can find the corresponding component to get its type - FString InstancerType = TEXT("(Instancer)"); - FHoudiniOutputObjectIdentifier CurVariationIdentifier = CurOutputObjectIdentifier; - CurVariationIdentifier.SplitIdentifier += TEXT("_") + FString::FromInt(VariationIdx); - const FHoudiniOutputObject* VariationOutputObject = OutputObjects.Find(CurVariationIdentifier); - if(VariationOutputObject) - InstancerType = FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(VariationOutputObject->OutputComponent); - - DetailGroup->AddWidgetRow() - .NameContent() - [ - //SNew(SSpacer) - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(InstancerType)) - //.Size(FVector2D(250, 64)) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - PickerVerticalBox - ]; - - // Add an asset drop target - PickerVerticalBox->AddSlot() - .Padding(0, 2) - .AutoHeight() - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop(SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( - [DisallowedClasses](const UObject* Obj) - { - for (auto Klass : DisallowedClasses) - { - if (Obj && Obj->IsA(Klass)) - return false; - } - return true; - }) - ) - .OnAssetDropped_Lambda([&CurInstanceOutput, VariationIdx, SetObjectAt](UObject* InObject) - { - return SetObjectAt(CurInstanceOutput, VariationIdx, InObject); - }) - [ - SAssignNew(PickerHorizontalBox, SHorizontalBox) - ] - ]; - - PickerHorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(VariationThumbnailBorder, SBorder) - .Padding( 5.0f ) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, InstancedObject) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(InstancedObject->GetPathName())) - [ - VariationThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - TWeakPtr WeakVariationThumbnailBorder(VariationThumbnailBorder); - VariationThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush *>::Create( - TAttribute::FGetter::CreateLambda([WeakVariationThumbnailBorder]() - { - TSharedPtr ThumbnailBorder = WeakVariationThumbnailBorder.Pin(); - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ))); - - PickerHorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 28.0f, 0.0f, 28.0f) - [ - PropertyCustomizationHelpers::MakeAddButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, AddObjectAt]() - { - UObject* ObjToAdd = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? - CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() - : nullptr; - - return AddObjectAt(CurInstanceOutput, VariationIdx, ObjToAdd); - }), - LOCTEXT("AddAnotherInstanceToolTip", "Add Another Instance")) - ]; - - PickerHorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 28.0f, 4.0f, 28.0f ) - [ - PropertyCustomizationHelpers::MakeRemoveButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, RemoveObjectAt]() - { - return RemoveObjectAt(CurInstanceOutput, VariationIdx); - }), - LOCTEXT("RemoveLastInstanceToolTip", "Remove Last Instance")) - ]; - - TSharedPtr AssetComboButton; - TSharedPtr ButtonBox; - PickerHorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .HAlign(HAlign_Fill) - [ - SAssignNew(ButtonBox, SHorizontalBox) - +SHorizontalBox::Slot() - [ - SAssignNew(AssetComboButton, SComboButton) - //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) - /* TODO: Update UI - .OnMenuOpenChanged( FOnIsOpenChanged::CreateUObject( - &InParam, &UHoudiniAssetInstanceInput::ChangedStaticMeshComboButton, - CurInstanceOutput, InstOutIdx, VariationIdx ) ) - */ - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(InstancedObject->GetName())) - ] - ] - ] - ]; - - // Create asset picker for this combo button. - { - TWeakPtr WeakAssetComboButton(AssetComboButton); - TArray NewAssetFactories; - TSharedRef PropertyMenuAssetPicker = PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(InstancedObject), - true, - AllowedClasses, - DisallowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda( - [&CurInstanceOutput, VariationIdx, SetObjectAt, WeakAssetComboButton](const FAssetData& AssetData) - { - TSharedPtr AssetComboButtonPtr = WeakAssetComboButton.Pin(); - if (AssetComboButtonPtr.IsValid()) - { - AssetComboButtonPtr->SetIsOpen(false); - UObject * Object = AssetData.GetAsset(); - SetObjectAt(CurInstanceOutput, VariationIdx, Object); - } - } - ), - // Nothing to do on close - FSimpleDelegate::CreateLambda([](){}) - ); - - AssetComboButton->SetMenuContent(PropertyMenuAssetPicker); - } - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(InstancedObject->GetName())); - FText StaticMeshTooltip = - FText::Format(LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx]() - { - UObject* InputObject = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? - CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() - : nullptr; - - if (GEditor && InputObject) - { - TArray Objects; - Objects.Add(InputObject); - GEditor->SyncBrowserToObjects(Objects); - } - }), - TAttribute< FText >( StaticMeshTooltip ) ) - ]; - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f ) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT( "ResetToBase", "Reset to default static mesh")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([SetObjectAt, &CurInstanceOutput, VariationIdx]() - { - SetObjectAt(CurInstanceOutput, VariationIdx, CurInstanceOutput.OriginalObject.LoadSynchronous()); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - - - // Get Visibility of reset buttons - bool bResetButtonVisiblePosition = false; - bool bResetButtonVisibleRotation = false; - bool bResetButtonVisibleScale = false; - - FTransform CurTransform = CurInstanceOutput.VariationTransformOffsets[VariationIdx]; - - if (CurTransform.GetLocation() != FVector::ZeroVector) - bResetButtonVisiblePosition = true; - - FRotator Rotator = CurTransform.Rotator(); - if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) - bResetButtonVisibleRotation = true; - - if (CurTransform.GetScale3D() != FVector::OneVector) - bResetButtonVisibleScale = true; - - auto ChangeTransformOffsetUniformlyAt = [ChangeTransformOffsetAt, VariationIdx, &CurInstanceOutput](const float& Val, const int32& PosRotScaleIndex) - { - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 0); - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 1); - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 2); - }; - - TSharedRef OffsetVerticalBox = SNew(SVerticalBox); - FText LabelPositionText = LOCTEXT("HoudiniPositionOffset", "Position Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelPositionText) - .ToolTipText(LabelPositionText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .AllowSpin(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 0); } - ))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 1); } - ))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 2); } - ))) - .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 0); }) - .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 1); }) - .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 2); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("InstancerOutputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, CurInstanceOutput, InOutput]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 0); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - ]; - - FText LabelRotationText = LOCTEXT("HoudiniRotationOffset", "Rotation Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelRotationText) - .ToolTipText(LabelRotationText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SRotatorInputBox) - .AllowSpin(true) - .bColorAxisLabels(true) - .Roll(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 0); } - ))) - .Pitch(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 1); } - ))) - .Yaw(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 2); } - ))) - .OnRollCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 0); }) - .OnPitchCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 1); }) - .OnYawCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 2); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 1); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - ]; - - FText LabelScaleText = LOCTEXT("HoudiniScaleOffset", "Scale Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelScaleText) - .ToolTipText(LabelScaleText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 0); } - ))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 1); } - ))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 2); } - ))) - .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 0); - }) - .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 1); - }) - .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 2); - }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("InstancerOutputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the output object maintains its shape in each direction when scaled")) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(CurInstanceOutput.IsUnformScaleLocked() ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([&CurInstanceOutput, InOutput]() - { - CurInstanceOutput.SwitchUniformScaleLock(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() - { - ChangeTransformOffsetUniformlyAt(1.0f, 2); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - /* - // TODO: Add support for this back - + SHorizontalBox::Slot().AutoWidth() - [ - // Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "TransparentCheckBox") - .ToolTipText(LOCTEXT("PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled")) - *//* - .OnCheckStateChanged(FOnCheckStateChanged::CreateLambda([=](ECheckBoxState NewState) - { - if ( MyParam.IsValid() && InputFieldPtr.IsValid() ) - MyParam->CheckStateChanged( NewState == ECheckBoxState::Checked, InputFieldPtr.Get(), VariationIdx ); - })) - .IsChecked( TAttribute< ECheckBoxState >::Create( - TAttribute::FGetter::CreateLambda( [=]() - { - if (InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly(VariationIdx)) - return ECheckBoxState::Checked; - return ECheckBoxState::Unchecked; - } - ))) - *//* - [ - SNew(SImage) - *//*.Image(TAttribute::Create( - TAttribute::FGetter::CreateLambda( [=]() - { - if ( InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly( VariationIdx ) ) - { - return FEditorStyle::GetBrush( TEXT( "GenericLock" ) ); - } - return FEditorStyle::GetBrush( TEXT( "GenericUnlock" ) ); - } - ))) - *//* - .ColorAndOpacity( FSlateColor::UseForeground() ) - ] - ] - */ - ]; - } - } - } -} - -/* -void -FHoudiniOutputDetails::OnMaterialInterfaceSelected( - const FAssetData & AssetData, - ALandscapeProxy* Landscape, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); - TSharedPtr< SComboButton > AssetComboButton = LandscapeMaterialInterfaceComboButtons[Pair]; - if (AssetComboButton.IsValid()) - { - AssetComboButton->SetIsOpen(false); - - UObject * Object = AssetData.GetAsset(); - OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } -} -*/ - -void -FHoudiniOutputDetails::CreateDefaultOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput) - return; - - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); - - // TODO - // This is just a temporary placeholder displaying name/output type - { - FString OutputNameStr = InOutput->GetName(); - FText OutputTooltip = GetOutputTooltip(InOutput); - - // Create a new detail row - // Name - FText OutputNameTxt = GetOutputDebugName(InOutput); - FDetailWidgetRow & Row = HouOutputCategory.AddCustomRow(FText::GetEmpty()); - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(OutputNameTxt) - .ToolTipText(OutputTooltip) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - - // Value - FText OutputTypeTxt = GetOutputDebugDescription(InOutput); - Row.ValueWidget.Widget = - SNew(STextBlock) - .Text(OutputTypeTxt) - .ToolTipText(OutputTooltip) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - } -} - -void -FHoudiniOutputDetails::OnBakeOutputObject( - const FString& InBakeName, - UObject * BakedOutputObject, - const FHoudiniOutputObjectIdentifier & OutputIdentifier, - const FHoudiniOutputObject& InOutputObject, - const FHoudiniGeoPartObject & HGPO, - const UObject* OutputOwner, - const FString & HoudiniAssetName, - const FString & BakeFolder, - const FString & TempCookFolder, - const EHoudiniOutputType & Type, - const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, - const TArray& InAllOutputs) -{ - if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) - return; - - // Fill in the package params - FHoudiniPackageParams PackageParams; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver Resolver; - // Determine the relevant WorldContext based on the output owner - UWorld* WorldContext = OutputOwner ? OutputOwner->GetWorld() : GWorld; - const UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(OutputOwner); - check(IsValid(HAC)); - const bool bAutomaticallySetAttemptToLoadMissingPackages = true; - const bool bSkipObjectNameResolutionAndUseDefault = !InBakeName.IsEmpty(); // If InBakeName is set use it as is for the object name - const bool bSkipBakeFolderResolutionAndUseDefault = false; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - WorldContext, HAC, OutputIdentifier, InOutputObject, BakedOutputObject->GetName(), - HoudiniAssetName, PackageParams, Resolver, - BakeFolder, EPackageReplaceMode::ReplaceExistingAssets, - bAutomaticallySetAttemptToLoadMissingPackages, bSkipObjectNameResolutionAndUseDefault, - bSkipBakeFolderResolutionAndUseDefault); - - switch (Type) - { - case EHoudiniOutputType::Mesh: - { - UStaticMesh* StaticMesh = Cast(BakedOutputObject); - if (StaticMesh) - { - FDirectoryPath TempCookFolderPath; - TempCookFolderPath.Path = TempCookFolder; - UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( - StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath); - } - } - break; - case EHoudiniOutputType::Curve: - { - USplineComponent* SplineComponent = Cast(BakedOutputObject); - if (SplineComponent) - { - AActor* BakedActor; - USplineComponent* BakedSplineComponent; - FHoudiniEngineBakeUtils::BakeCurve(SplineComponent, GWorld->GetCurrentLevel(), PackageParams, BakedActor, BakedSplineComponent); - } - } - break; - case EHoudiniOutputType::Landscape: - { - ALandscapeProxy* Landscape = Cast(BakedOutputObject); - if (Landscape) - { - FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType); - } - } - break; - } -} - -FReply -FHoudiniOutputDetails::OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput) -{ - // TODO: Actually refine only the selected ProxyMesh - // For now, refine all the selection - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true, true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); -} - -void -FHoudiniOutputDetails::OnBakeNameCommitted( - const FText& Val, ETextCommit::Type TextCommitType, - UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - if (!InOutput) - return; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - - if (!FoundOutputObject) - return; - - FoundOutputObject->BakeName = Val.ToString(); -} - -void -FHoudiniOutputDetails::OnRevertBakeNameToDefault(UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier & InIdentifier) -{ - if (!InOutput) - return; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - - if (!FoundOutputObject) - return; - - FoundOutputObject->BakeName = FString(); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniOutputDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAsset.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniEngineCommands.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailGroup.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SRotatorInputBox.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Text/STextBlock.h" +#include "Editor/UnrealEd/Public/AssetThumbnail.h" +#include "SAssetDropTarget.h" +#include "Engine/StaticMesh.h" +#include "Components/SplineComponent.h" +#include "Materials/Material.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceConstant.h" +#include "Sound/SoundBase.h" +#include "Engine/SkeletalMesh.h" +#include "Particles/ParticleSystem.h" +//#include "Landscape.h" +#include "LandscapeProxy.h" +#include "ScopedTransaction.h" +#include "PhysicsEngine/BodySetup.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +void +FHoudiniOutputDetails::CreateWidget( + IDetailCategoryBuilder& HouOutputCategory, + TArray InOutputs) +{ + if (InOutputs.Num() <= 0) + return; + + UHoudiniOutput* MainOutput = InOutputs[0]; + if (!IsValid(MainOutput)) + return; + + // Don't create UI for editable curve. + if (MainOutput->IsEditableNode() && MainOutput->GetType() == EHoudiniOutputType::Curve) + return; + + // Get thumbnail pool for this builder. + TSharedPtr AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); + + switch (MainOutput->GetType()) + { + case EHoudiniOutputType::Mesh: + { + FHoudiniOutputDetails::CreateMeshOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Landscape: + { + FHoudiniOutputDetails::CreateLandscapeOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Instancer: + { + FHoudiniOutputDetails::CreateInstancerOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Curve: + { + FHoudiniOutputDetails::CreateCurveOutputWidget(HouOutputCategory, MainOutput); + break; + } + case EHoudiniOutputType::Skeletal: + default: + { + FHoudiniOutputDetails::CreateDefaultOutputWidget(HouOutputCategory, MainOutput); + break; + } + } +} + + +void +FHoudiniOutputDetails::CreateLandscapeOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // Go through this output's objects + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& CurrentOutputObj : OutputObjects) + { + FHoudiniOutputObjectIdentifier& Identifier = CurrentOutputObj.Key; + const FHoudiniGeoPartObject *HGPO = nullptr; + for (const auto& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!Identifier.Matches(CurHGPO)) + continue; + + HGPO = &CurHGPO; + break; + } + + if (!HGPO) + continue; + + + if (UHoudiniLandscapePtr* LandscapePointer = Cast(CurrentOutputObj.Value.OutputObject)) + { + CreateLandscapeOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapePointer, Identifier); + } + else if (UHoudiniLandscapeEditLayer* LandscapeLayer = Cast(CurrentOutputObj.Value.OutputObject)) + { + // TODO: Create widget for landscape editlayer output + CreateLandscapeEditLayerOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapeLayer, Identifier); + } + + + + } +} + +void +FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + const FHoudiniGeoPartObject& HGPO, + UHoudiniLandscapePtr* LandscapePointer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier) +{ + if (!LandscapePointer || LandscapePointer->IsPendingKill() || !LandscapePointer->LandscapeSoftPtr.IsValid()) + return; + + if (!InOutput || InOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return; + + ALandscapeProxy * Landscape = LandscapePointer->LandscapeSoftPtr.Get(); + if (!Landscape || Landscape->IsPendingKill()) + return; + + // TODO: Get bake base name + FString Label = Landscape->GetName(); + + EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType = LandscapePointer->BakeType; + + // Get thumbnail pool for this builder + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + TArray>* BakeOptionString = FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels(); + + // Create bake mesh name textfield. + IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(Label)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + // Create the thumbnail for the landscape output object. + TSharedPtr< FAssetThumbnail > LandscapeThumbnail = + MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); + + TSharedPtr< SBorder > LandscapeThumbnailBorder; + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(SSpacer) + .Size(FVector2D(250, 64)) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot().Padding(0, 2).AutoHeight() + [ + SNew(SBox).WidthOverride(175) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(LandscapeThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(Landscape->GetPathName())) + [ + LandscapeThumbnail->MakeThumbnailWidget() + ] + ] + ] + + + SHorizontalBox::Slot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SBox).WidthOverride(40.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("Bake", "Bake")) + .IsEnabled(true) + .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() + { + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + if (FoundOutputObject) + { + TArray AllOutputs; + AllOutputs.Reserve(HAC->GetNumOutputs()); + HAC->GetOutputs(AllOutputs); + FHoudiniOutputDetails::OnBakeOutputObject( + FoundOutputObject->BakeName, + Landscape, + OutputIdentifier, + *FoundOutputObject, + HGPO, + HAC, + OwnerActor->GetName(), + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + InOutput->GetType(), + LandscapeOutputBakeType, + AllOutputs); + } + + // TODO: Remove the output landscape if the landscape bake type is Detachment? + return FReply::Handled(); + }) + .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) + ] + ] + + SHorizontalBox::Slot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SBox).WidthOverride(120.f) + [ + SNew(SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + if (SelectType != ESelectInfo::Type::OnMouseClick) + return; + + FString *NewChoiceStr = NewChoice.Get(); + if (!NewChoiceStr) + return; + + if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); + } + else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); + } + else + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); + } + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + [ + SNew(STextBlock) + .Text_Lambda([LandscapePointer]() + { + FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); + return FText::FromString(BakeTypeString); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ] + ] + ]; + + // Store thumbnail for this landscape. + OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); + + // We need to add material box for each the landscape and landscape hole materials + for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) + { + UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + TSharedPtr MaterialThumbnailBorder; + TSharedPtr HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if (MaterialInterface) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + + // Create thumbnail for this material. + TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(STextBlock) + .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().Padding(0, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) + .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(MaterialThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(MaterialPathName)) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this landscape and material index. + { + TPairInitializer Pair(Landscape, MaterialIdx); + MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + } + + // Combox Box and Button Box + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Combo row + TSharedPtr< SComboButton > AssetComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) + [ + SAssignNew(AssetComboButton, SComboButton) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + // Buttons row + TSharedPtr ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Add use Content Browser selection arrow + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)Landscape, InOutput, MaterialIdx), + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + ]; + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), + TAttribute< FText >(MaterialTooltip)) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + + // Store combo button for this mesh and index. + { + TPairInitializer Pair(Landscape, MaterialIdx); + MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + } + } +} + +void FHoudiniOutputDetails::CreateLandscapeEditLayerOutputWidget_Helper(IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& HGPO, UHoudiniLandscapeEditLayer* LandscapeEditLayer, + const FHoudiniOutputObjectIdentifier& OutputIdentifier) +{ + if (!LandscapeEditLayer || LandscapeEditLayer->IsPendingKill() || !LandscapeEditLayer->LandscapeSoftPtr.IsValid()) + return; + + if (!InOutput || InOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return; + + ALandscapeProxy * Landscape = LandscapeEditLayer->LandscapeSoftPtr.Get(); + if (!Landscape || Landscape->IsPendingKill()) + return; + + const FString Label = Landscape->GetName(); + const FString LayerName = LandscapeEditLayer->LayerName; + + // Get thumbnail pool for this builder + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // Create labels to display the edit layer name. + IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeEditLayerName", "Edit Layer Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(STextBlock) + .Text(FText::AsCultureInvariant(LayerName)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + + // SNew(SHorizontalBox) + // + SHorizontalBox::Slot() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // .FillWidth(1) + // [ + // SNew(SEditableTextBox) + // .Text(FText::FromString(Label)) + // .Font(IDetailLayoutBuilder::GetDetailFont()) + // .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + // .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + // .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) + // { + // FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + // FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + // }) + // ] + // + SHorizontalBox::Slot() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // .AutoWidth() + // [ + // SNew(SButton) + // .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + // .ButtonStyle(FEditorStyle::Get(), "NoBorder") + // .ContentPadding(0) + // .Visibility(EVisibility::Visible) + // .OnClicked_Lambda([InOutput, OutputIdentifier]() + // { + // FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + // return FReply::Handled(); + // }) + // [ + // SNew(SImage) + // .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + // ] + // ] + ]; + + // // Create the thumbnail for the landscape output object. + // TSharedPtr< FAssetThumbnail > LandscapeThumbnail = + // MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); + // + // TSharedPtr< SBorder > LandscapeThumbnailBorder; + // TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + // + // LandscapeGrp.AddWidgetRow() + // .NameContent() + // [ + // SNew(SSpacer) + // .Size(FVector2D(250, 64)) + // ] + // .ValueContent() + // .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + // [ + // VerticalBox + // ]; + // + // VerticalBox->AddSlot().Padding(0, 2).AutoHeight() + // [ + // SNew(SBox).WidthOverride(175) + // [ + // SNew(SHorizontalBox) + // + SHorizontalBox::Slot() + // .Padding(0.0f, 0.0f, 2.0f, 0.0f) + // .AutoWidth() + // [ + // SAssignNew(LandscapeThumbnailBorder, SBorder) + // .Padding(5.0f) + // .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) + // .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) + // [ + // SNew(SBox) + // .WidthOverride(64) + // .HeightOverride(64) + // .ToolTipText(FText::FromString(Landscape->GetPathName())) + // [ + // LandscapeThumbnail->MakeThumbnailWidget() + // ] + // ] + // ] + // + // + SHorizontalBox::Slot() + // .Padding(0.0f, 4.0f, 4.0f, 4.0f) + // .VAlign(VAlign_Center) + // [ + // SNew(SBox).WidthOverride(40.0f) + // [ + // SNew(SButton) + // .VAlign(VAlign_Center) + // .HAlign(HAlign_Center) + // .Text(LOCTEXT("Bake", "Bake")) + // .IsEnabled(true) + // .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() + // { + // FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + // if (FoundOutputObject) + // { + // TArray AllOutputs; + // AllOutputs.Reserve(HAC->GetNumOutputs()); + // HAC->GetOutputs(AllOutputs); + // FHoudiniOutputDetails::OnBakeOutputObject( + // FoundOutputObject->BakeName, + // Landscape, + // OutputIdentifier, + // *FoundOutputObject, + // HGPO, + // HAC, + // OwnerActor->GetName(), + // HAC->BakeFolder.Path, + // HAC->TemporaryCookFolder.Path, + // InOutput->GetType(), + // LandscapeOutputBakeType, + // AllOutputs); + // } + // + // // TODO: Remove the output landscape if the landscape bake type is Detachment? + // return FReply::Handled(); + // }) + // .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) + // ] + // ] + // + SHorizontalBox::Slot() + // .Padding(0.0f, 4.0f, 4.0f, 4.0f) + // .VAlign(VAlign_Center) + // [ + // SNew(SBox).WidthOverride(120.f) + // [ + // SNew(SComboBox>) + // .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) + // .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) + // .OnGenerateWidget_Lambda( + // [](TSharedPtr< FString > InItem) + // { + // return SNew(STextBlock).Text(FText::FromString(*InItem)); + // }) + // .OnSelectionChanged_Lambda( + // [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + // { + // if (SelectType != ESelectInfo::Type::OnMouseClick) + // return; + // + // FString *NewChoiceStr = NewChoice.Get(); + // if (!NewChoiceStr) + // return; + // + // if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) + // { + // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); + // } + // else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) + // { + // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); + // } + // else + // { + // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); + // } + // + // FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + // }) + // [ + // SNew(STextBlock) + // .Text_Lambda([LandscapePointer]() + // { + // FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); + // return FText::FromString(BakeTypeString); + // }) + // .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + // ] + // ] + // ] + // ] + // ]; + // + // // Store thumbnail for this landscape. + // OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); + // + // // We need to add material box for each the landscape and landscape hole materials + // for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) + // { + // UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + // TSharedPtr MaterialThumbnailBorder; + // TSharedPtr HorizontalBox = NULL; + // + // FString MaterialName, MaterialPathName; + // if (MaterialInterface) + // { + // MaterialName = MaterialInterface->GetName(); + // MaterialPathName = MaterialInterface->GetPathName(); + // } + // + // // Create thumbnail for this material. + // TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + // MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + // + // VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + // [ + // SNew(STextBlock) + // .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) + // .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + // ]; + // + // VerticalBox->AddSlot().Padding(0, 2) + // [ + // SNew(SAssetDropTarget) + // .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) + // .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) + // [ + // SAssignNew(HorizontalBox, SHorizontalBox) + // ] + // ]; + // + // HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + // [ + // SAssignNew(MaterialThumbnailBorder, SBorder) + // .Padding(5.0f) + // .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) + // .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + // [ + // SNew(SBox) + // .WidthOverride(64) + // .HeightOverride(64) + // .ToolTipText(FText::FromString(MaterialPathName)) + // [ + // MaterialInterfaceThumbnail->MakeThumbnailWidget() + // ] + // ] + // ]; + // + // // Store thumbnail for this landscape and material index. + // { + // TPairInitializer Pair(Landscape, MaterialIdx); + // MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + // } + // + // // Combox Box and Button Box + // TSharedPtr ComboAndButtonBox; + // HorizontalBox->AddSlot() + // .FillWidth(1.0f) + // .Padding(0.0f, 4.0f, 4.0f, 4.0f) + // .VAlign(VAlign_Center) + // [ + // SAssignNew(ComboAndButtonBox, SVerticalBox) + // ]; + // + // // Combo row + // TSharedPtr< SComboButton > AssetComboButton; + // ComboAndButtonBox->AddSlot().FillHeight(1.0f) + // [ + // SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) + // [ + // SAssignNew(AssetComboButton, SComboButton) + // //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + // .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + // .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + // .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) + // .ContentPadding(2.0f) + // .ButtonContent() + // [ + // SNew(STextBlock) + // .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + // .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + // .Text(FText::FromString(MaterialName)) + // ] + // ] + // ]; + // + // // Buttons row + // TSharedPtr ButtonBox; + // ComboAndButtonBox->AddSlot().FillHeight(1.0f) + // [ + // SAssignNew(ButtonBox, SHorizontalBox) + // ]; + // + // // Add use Content Browser selection arrow + // ButtonBox->AddSlot() + // .AutoWidth() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // [ + // PropertyCustomizationHelpers::MakeUseSelectedButton( + // FSimpleDelegate::CreateSP( + // this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + // (UObject*)Landscape, InOutput, MaterialIdx), + // TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + // ]; + // + // // Create tooltip. + // FFormatNamedArguments Args; + // Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + // FText MaterialTooltip = FText::Format( + // LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + // + // ButtonBox->AddSlot() + // .AutoWidth() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // [ + // PropertyCustomizationHelpers::MakeBrowseButton( + // FSimpleDelegate::CreateSP( + // this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), + // TAttribute< FText >(MaterialTooltip)) + // ]; + // + // ButtonBox->AddSlot() + // .AutoWidth() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // [ + // SNew(SButton) + // .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + // .ButtonStyle(FEditorStyle::Get(), "NoBorder") + // .ContentPadding(0) + // .Visibility(EVisibility::Visible) + // .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) + // [ + // SNew(SImage) + // .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + // ] + // ]; + // + // // Store combo button for this mesh and index. + // { + // TPairInitializer Pair(Landscape, MaterialIdx); + // MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + // } + // } +} + +void +FHoudiniOutputDetails::CreateMeshOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent* HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + FString HoudiniAssetName; + if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) + { + HoudiniAssetName = HAC->GetOwner()->GetName(); + } + else if (HAC->GetHoudiniAsset()) + { + HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); + } + else + { + HoudiniAssetName = HAC->GetName(); + } + + // Go through this output's object + int32 OutputObjIdx = 0; + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& IterObject : OutputObjects) + { + UStaticMesh* StaticMesh = Cast(IterObject.Value.OutputObject); + UHoudiniStaticMesh* ProxyMesh = Cast(IterObject.Value.ProxyObject); + + if ((!StaticMesh || StaticMesh->IsPendingKill()) + && (!ProxyMesh || ProxyMesh->IsPendingKill())) + continue; + + FHoudiniOutputObjectIdentifier & OutputIdentifier = IterObject.Key; + + // Find the corresponding HGPO in the output + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!OutputIdentifier.Matches(curHGPO)) + continue; + + HoudiniGeoPartObject = curHGPO; + break; + } + + if (StaticMesh && !StaticMesh->IsPendingKill()) + { + bool bIsProxyMeshCurrent = IterObject.Value.bProxyIsCurrent; + + // If we have a static mesh, alway display its widget even if the proxy is more recent + CreateStaticMeshAndMaterialWidgets( + HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); + } + else + { + // If we only have a proxy mesh, then show the proxy widget + CreateProxyMeshAndMaterialWidgets( + HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject); + } + } +} + +void +FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + int32 OutputObjIdx = 0; + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& IterObject : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = IterObject.Value; + USceneComponent* SplineComponent = Cast(IterObject.Value.OutputComponent); + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + FHoudiniOutputObjectIdentifier& OutputIdentifier = IterObject.Key; + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!OutputIdentifier.Matches(curHGPO)) + continue; + + HoudiniGeoPartObject = curHGPO; + break; + } + + CreateCurveWidgets(HouOutputCategory, InOutput, SplineComponent, CurrentOutputObject, OutputIdentifier, HoudiniGeoPartObject); + } +} + +void +FHoudiniOutputDetails::CreateCurveWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + USceneComponent* SplineComponent, + FHoudiniOutputObject& OutputObject, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + FHoudiniGeoPartObject& HoudiniGeoPartObject) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // We support Unreal Spline out only for now + USplineComponent* SplineOutput = Cast(SplineComponent); + if (!SplineOutput || SplineOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return; + + FHoudiniCurveOutputProperties* OutputProperty = &(OutputObject.CurveOutputProperty); + EHoudiniCurveType OutputCurveType = OutputObject.CurveOutputProperty.CurveType; + + FString Label = SplineComponent->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + //Label += FString("_") + OutputIdentifier.SplitIdentifier; + + FString OutputCurveName = OutputObject.BakeName; + if(OutputCurveName.IsEmpty()) + OutputCurveName = OwnerActor->GetName() + "_" + Label; + + const FText& LabelText = FText::FromString("Unreal Spline"); + + IDetailGroup& CurveOutputGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label), false, false); + + // Bake name row UI + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(OutputObject.BakeName)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + ] + + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("OutputCurveSplineType", "Spline Type")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText_Lambda([SplineOutput, Label, OutputCurveType]() + { + FString ToolTipStr = FString::Printf(TEXT(" curve: %s\n Export type: Unreal Spline\n num points: %d\n curve type: %s\n closed: %s"), + *Label, + SplineOutput->GetNumberOfSplinePoints(), + *FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(OutputCurveType), + SplineOutput->IsClosedLoop() ? *(FString("yes")) : *(FString("no"))); + + return FText::FromString(ToolTipStr); + }) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(STextBlock) + // We support Unreal Spline output only for now... + .Text(LOCTEXT("OutputCurveSplineTypeUnreal", "Unreal Spline")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + + //if (bIsUnrealSpline) + //{ + USplineComponent* UnrealSpline = Cast(SplineComponent); + + // Curve type combo box UI + auto InitialSelectionLambda = [OutputProperty]() + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + { + return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[0]; + } + else + { + return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[1]; + } + }; + + TSharedPtr>> UnrealCurveTypeComboBox; + + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(LOCTEXT("OutputCurveUnrealSplinePointType", "Spline Point Type")) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(UnrealCurveTypeComboBox, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels()) + .InitiallySelectedItem(InitialSelectionLambda()) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [OutputProperty, InOutput, SplineComponent](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + // Set the curve point type locally + USplineComponent* Spline = Cast(SplineComponent); + if (!Spline || Spline->IsPendingKill()) + return; + + FString *NewChoiceStr = NewChoice.Get(); + if (!NewChoiceStr) + return; + + if (*NewChoiceStr == "Linear") + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + return; + + OutputProperty->CurveType = EHoudiniCurveType::Polygon; + + for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) + { + Spline->SetSplinePointType(PtIdx, ESplinePointType::Linear); + } + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + } + else if (*NewChoiceStr == "Curve") + { + if (OutputProperty->CurveType != EHoudiniCurveType::Polygon) + return; + + OutputProperty->CurveType = EHoudiniCurveType::Bezier; + + for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) + { + Spline->SetSplinePointType(PtIdx, ESplinePointType::Curve); + } + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([OutputProperty]() + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + return FText::FromString(TEXT("Linear")); + else + return FText::FromString(TEXT("Curve")); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + // Add closed curve checkbox UI + TSharedPtr ClosedCheckBox; + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(LOCTEXT("OutputCurveUnrealSplineClosed", "Closed")) + ] + .ValueContent() + [ + SAssignNew(ClosedCheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([UnrealSpline, InOutput](ECheckBoxState NewState) + { + if (!UnrealSpline || UnrealSpline->IsPendingKill()) + return; + + UnrealSpline->SetClosedLoop(NewState == ECheckBoxState::Checked); + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + .IsChecked_Lambda([UnrealSpline]() + { + if (!UnrealSpline || UnrealSpline->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return UnrealSpline->IsClosedLoop() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + ]; + //} + + // Add Bake Button UI + TSharedPtr BakeButton; + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + ] + .ValueContent() + [ + SAssignNew(BakeButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) + .IsEnabled(true) + .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) + .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName, OutputObject]() + { + TArray AllOutputs; + AllOutputs.Reserve(HAC->GetNumOutputs()); + HAC->GetOutputs(AllOutputs); + FHoudiniOutputDetails::OnBakeOutputObject( + OutputCurveName, + SplineComponent, + OutputIdentifier, + OutputObject, + HoudiniGeoPartObject, + HAC, + OwnerActor->GetName(), + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + InOutput->GetType(), + EHoudiniLandscapeOutputBakeType::InValid, + AllOutputs); + + return FReply::Handled(); + }) + ]; +} + +void +FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UStaticMesh * StaticMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + const bool& bIsProxyMeshCurrent) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return; + + UHoudiniAssetComponent* OwningHAC = Cast(InOutput->GetOuter()); + + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // TODO: GetBakingBaseName! + FString Label = StaticMesh->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + // Create thumbnail for this mesh. + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = + MakeShareable(new FAssetThumbnail(StaticMesh, 64, 64, AssetThumbnailPool)); + TSharedPtr StaticMeshThumbnailBorder; + + TSharedRef VerticalBox = SNew(SVerticalBox); + + IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(BakeName)) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + }) + .ToolTipText( LOCTEXT( "BakeNameTip", "The base name of the baked asset") ) + ] + + +SHorizontalBox::Slot() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "RevertNameOverride", "Revert bake name override" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ] + ]; + + // Add details on the SM colliders + EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); + FString MeshLabel = TEXT( "Static Mesh" ); + + // If the Proxy mesh is more recent, indicate it in the details + if (bIsProxyMeshCurrent) + { + MeshLabel += TEXT("\n(unrefined)"); + } + + // Indicate that this mesh is instanced + if (HoudiniGeoPartObject.bIsInstanced) + { + MeshLabel += TEXT("\n(instanced)"); + } + + if (HoudiniGeoPartObject.bIsTemplated) + { + MeshLabel += TEXT("\n(templated)"); + } + + int32 NumSimpleColliders = 0; + if (StaticMesh->BodySetup && !StaticMesh->BodySetup->IsPendingKill()) + NumSimpleColliders = StaticMesh->BodySetup->AggGeom.GetElementCount(); + + if(NumSimpleColliders > 0) + { + MeshLabel += TEXT( "\n(") + FString::FromInt(NumSimpleColliders) + TEXT(" Simple Collider" ); + if (NumSimpleColliders > 1 ) + MeshLabel += TEXT("s"); + MeshLabel += TEXT(")"); + } + else if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + MeshLabel += TEXT( "\n(Rendered Complex Collider)" ); + } + else if(SplitType == EHoudiniSplitType::InvisibleComplexCollider ) + { + MeshLabel += TEXT( "\n(Invisible Complex Collider)" ); + } + + if ( StaticMesh->GetNumLODs() > 1 ) + MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)"); + + if (HoudiniGeoPartObject.AllMeshSockets.Num() > 0) + { + if (bIsProxyMeshCurrent) + { + // Proxy is current, show the number of sockets on the HGPO + MeshLabel += TEXT("\n(") + FString::FromInt(HoudiniGeoPartObject.AllMeshSockets.Num()) + TEXT(" sockets)"); + } + else + { + // Show the number of sockets on the SM + MeshLabel += TEXT("\n(") + FString::FromInt(StaticMesh->Sockets.Num()) + TEXT(" sockets)"); + } + } + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew( STextBlock ) + .Text( FText::FromString(MeshLabel) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot() + .Padding( 0, 2 ) + .AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) + .AutoWidth() + [ + SAssignNew( StaticMeshThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)StaticMesh ) + .OnMouseDoubleClick( this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *) StaticMesh ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( StaticMesh->GetPathName() ) ) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ] + + +SHorizontalBox::Slot() + .FillWidth( 1.0f ) + .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SVerticalBox ) + +SVerticalBox::Slot() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .MaxWidth( 80.0f ) + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .Text( LOCTEXT( "Bake", "Bake" ) ) + .IsEnabled(true) + .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC, FoundOutputObject]() + { + if (FoundOutputObject) + { + TArray AllOutputs; + FString TempCookFolder; + if (IsValid(OwningHAC)) + { + AllOutputs.Reserve(OwningHAC->GetNumOutputs()); + OwningHAC->GetOutputs(AllOutputs); + + TempCookFolder = OwningHAC->TemporaryCookFolder.Path; + } + FHoudiniOutputDetails::OnBakeOutputObject( + BakeName, + StaticMesh, + OutputIdentifier, + *FoundOutputObject, + HoudiniGeoPartObject, + OwningHAC, + HoudiniAssetName, + BakeFolder, + TempCookFolder, + InOutput->GetType(), + EHoudiniLandscapeOutputBakeType::InValid, + AllOutputs); + } + + return FReply::Handled(); + }) + .ToolTipText( LOCTEXT( "HoudiniStaticMeshBakeButton", "Bake this generated static mesh" ) ) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)StaticMesh), + TAttribute(LOCTEXT("HoudiniStaticMeshBrowseButton", "Browse to this generated static mesh in the content browser"))) + ] + ] + ] + ]; + + // Store thumbnail for this mesh. + OutputObjectThumbnailBorders.Add((UObject*)StaticMesh, StaticMeshThumbnailBorder); + + // We need to add material box for each material present in this static mesh. + auto & StaticMeshMaterials = StaticMesh->StaticMaterials; + for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) + { + UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface; + TSharedPtr< SBorder > MaterialThumbnailBorder; + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if ( MaterialInterface && !MaterialInterface->IsPendingKill() + && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + else + { + MaterialInterface = nullptr; + MaterialName = TEXT("Material (invalid)") + FString::FromInt( MaterialIdx ) ; + MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + } + + // Create thumbnail for this material. + TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + MakeShareable( new FAssetThumbnail( MaterialInterface, 64, 64, AssetThumbnailPool ) ); + + VerticalBox->AddSlot().Padding( 0, 2 ) + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver ) + .OnAssetDropped( + this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx ) + [ + SAssignNew( HorizontalBox, SHorizontalBox ) + ] + ]; + + HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() + [ + SAssignNew( MaterialThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( + this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject *)StaticMesh, MaterialIdx ) + .OnMouseDoubleClick( + this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( MaterialPathName ) ) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this mesh and material index. + { + TPairInitializer Pair( StaticMesh, MaterialIdx ); + MaterialInterfaceThumbnailBorders.Add( Pair, MaterialThumbnailBorder ); + } + + // ComboBox and buttons + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add Combo box + TSharedPtr< SComboButton > AssetComboButton; + ComboAndButtonBox->AddSlot().VAlign(VAlign_Center).FillHeight(1.0f) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().VAlign(VAlign_Center).FillHeight(1.0f) + [ + SAssignNew(AssetComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, + MaterialInterface, (UObject*)StaticMesh, InOutput, MaterialIdx) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + + // Add buttons + TSharedPtr< SHorizontalBox > ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Use CB selection arrow button + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)StaticMesh, InOutput, MaterialIdx), + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + ]; + + // Browse CB button + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface ), TAttribute< FText >( MaterialTooltip ) ) + ]; + + // Reset button + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBaseMaterial", "Reset to base material" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked( + this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + // Store combo button for this mesh and index. + { + TPairInitializer Pair( StaticMesh, MaterialIdx ); + MaterialInterfaceComboButtons.Add( Pair, AssetComboButton ); + } + } +} + +void +FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UHoudiniStaticMesh * ProxyMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject) +{ + if (!ProxyMesh || ProxyMesh->IsPendingKill()) + return; + + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // TODO: GetBakingBaseName! + FString Label = ProxyMesh->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + // Create thumbnail for this mesh. + TSharedPtr MeshThumbnail = MakeShareable(new FAssetThumbnail(ProxyMesh, 64, 64, AssetThumbnailPool)); + TSharedPtr MeshThumbnailBorder; + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(BakeName)) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + }) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + // Add details on the Proxy Mesh + EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); + FString MeshLabel = TEXT("Proxy Mesh"); + + // Indicate that this mesh is instanced + if (HoudiniGeoPartObject.bIsInstanced) + { + MeshLabel += TEXT("\n(instanced)"); + } + + if (HoudiniGeoPartObject.bIsTemplated) + { + MeshLabel += TEXT("\n(templated)"); + } + + if (HoudiniGeoPartObject.AllMeshSockets.Num() > 0) + { + MeshLabel += TEXT("\n(") + FString::FromInt(HoudiniGeoPartObject.AllMeshSockets.Num()) + TEXT(" sockets)"); + } + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(FText::FromString(MeshLabel)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot() + .Padding(0, 2) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(MeshThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)ProxyMesh) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)ProxyMesh) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(ProxyMesh->GetPathName())) + [ + MeshThumbnail->MakeThumbnailWidget() + ] + ] + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .MaxWidth(80.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("Refine", "Refine")) + .IsEnabled(true) + .OnClicked(this, &FHoudiniOutputDetails::OnRefineClicked, (UObject *)ProxyMesh, InOutput) + .ToolTipText(LOCTEXT("RefineTooltip", "Refine this Proxy Mesh to a Static Mesh")) + ] + ] + ] + ]; + + // Store thumbnail for this mesh. + OutputObjectThumbnailBorders.Add(ProxyMesh, MeshThumbnailBorder); + + // We need to add material box for each material present in this static mesh. + auto & ProxyMeshMaterials = ProxyMesh->GetStaticMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < ProxyMeshMaterials.Num(); ++MaterialIdx) + { + UMaterialInterface * MaterialInterface = ProxyMeshMaterials[MaterialIdx].MaterialInterface; + TSharedPtr< SBorder > MaterialThumbnailBorder; + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if (MaterialInterface && !MaterialInterface->IsPendingKill() + && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill()) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + else + { + MaterialInterface = nullptr; + MaterialName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + } + + // Create thumbnail for this material. + TSharedPtr MaterialInterfaceThumbnail = + MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + + // No drop target + VerticalBox->AddSlot() + .Padding(0, 2) + [ + SNew(SAssetDropTarget) + //.OnIsAssetAcceptableForDrop(false) + //.OnAssetDropped( + // this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + HorizontalBox->AddSlot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(MaterialThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage( + this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)ProxyMesh, MaterialIdx) + .OnMouseDoubleClick( + this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(MaterialPathName)) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this mesh and material index. + { + TPairInitializer Pair((UObject*)ProxyMesh, MaterialIdx); + MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + } + + // Combo box and buttons + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add combo box + TSharedPtr AssetComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SAssignNew(AssetComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + /*.OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, + MaterialInterface, StaticMesh, InOutput, MaterialIdx)*/ + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + + TSharedPtr ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Disable the combobutton for proxies + AssetComboButton->SetEnabled(false); + + // Add use selection form content browser array + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + /*FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)ProxyMesh, InOutput, MaterialIdx),*/ + FSimpleDelegate::CreateLambda([]() {}), // Do nothing for proxies + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser")), false) + // Disable the use CB selection button for proxies + ]; + + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP(this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), TAttribute(MaterialTooltip)) + ]; + + /* + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked( + this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + */ + + // Store combo button for this mesh and index. + { + TPairInitializer Pair(ProxyMesh, MaterialIdx); + MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + } + } +} + +FText +FHoudiniOutputDetails::GetOutputDebugName(UHoudiniOutput* InOutput) +{ + // Get the name and type + FString OutputNameStr = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); + + // Then add the number of parts + OutputNameStr += TEXT(" (") + FString::FromInt(InOutput->GetHoudiniGeoPartObjects().Num()) + TEXT(" Part(s))\n"); + + return FText::FromString(OutputNameStr); +} +FText +FHoudiniOutputDetails::GetOutputDebugDescription(UHoudiniOutput* InOutput) +{ + const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); + + FString OutputValStr; + OutputValStr += TEXT("HGPOs:\n"); + for (auto& HGPO : HGPOs) + { + OutputValStr += TEXT(" - ") + HGPO.PartName + TEXT(" (") + FHoudiniGeoPartObject::HoudiniPartTypeToString(HGPO.Type) + TEXT(")"); + + if (HGPO.SplitGroups.Num() > 0) + { + OutputValStr += TEXT("( ") + FString::FromInt(HGPO.SplitGroups.Num()) + TEXT(" splits:"); + for (auto& split : HGPO.SplitGroups) + { + OutputValStr += TEXT(" ") + split; + } + OutputValStr += TEXT(")"); + } + + if (!HGPO.VolumeName.IsEmpty()) + { + OutputValStr += TEXT("( ") + HGPO.VolumeName; + if (HGPO.VolumeTileIndex >= 0) + OutputValStr += TEXT(" tile ") + FString::FromInt(HGPO.VolumeTileIndex); + OutputValStr += TEXT(" )"); + } + + OutputValStr += TEXT("\n"); + } + + // Add output objects if any + TMap AllOutputObj = InOutput->GetOutputObjects(); + if (AllOutputObj.Num() > 0) + { + bool TitleAdded = false; + for (const auto& Iter : AllOutputObj) + { + UObject* OutObject = Iter.Value.OutputObject; + if (OutObject) + { + OutputValStr += OutObject->GetFullName() + TEXT(" (obj)\n"); + } + + UObject* OutComp = Iter.Value.OutputComponent; + if (OutComp) + { + OutputValStr += OutObject->GetFullName() + TEXT(" (comp)\n"); + } + } + } + + return FText::FromString(OutputValStr); +} + +FText +FHoudiniOutputDetails::GetOutputTooltip(UHoudiniOutput* InOutput) +{ + // TODO + return FText(); +} + + +const FSlateBrush * +FHoudiniOutputDetails::GetThumbnailBorder(UObject* Mesh) const +{ + TSharedPtr ThumbnailBorder = OutputObjectThumbnailBorders[Mesh]; + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + + +const FSlateBrush * +FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(UObject* OutputObject, int32 MaterialIdx) const +{ + if (!OutputObject) + return nullptr; + + TPairInitializer Pair(OutputObject, MaterialIdx); + TSharedPtr ThumbnailBorder = MaterialInterfaceThumbnailBorders[Pair]; + + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + +/* +const FSlateBrush * +FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx) const +{ + if (!Landscape) + return nullptr; + + TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); + TSharedPtr< SBorder > ThumbnailBorder = LandscapeMaterialInterfaceThumbnailBorders[Pair]; + + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} +*/ + +FReply +FHoudiniOutputDetails::OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, + const FPointerEvent & InMouseEvent, UObject * Object) +{ + if (Object && GEditor) + GEditor->EditObject(Object); + + return FReply::Handled(); +} + +/* +FReply +FHoudiniOutputDetails::OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject) +{ + if (HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill()) + { + FHoudiniPackageParams PackageParms; + + + FHoudiniEngineBakeUtils::BakeStaticMesh(HoudiniAssetComponent, GeoPartObject, StaticMesh, PackageParms); + // TODO: Bake the SM + + + // We need to locate corresponding geo part object in component. + const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(StaticMesh); + + // (void)FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( + // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); + + } + + return FReply::Handled(); +} +*/ + +bool +FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver(const UObject * InObject) const +{ + return (InObject && InObject->IsA(UMaterialInterface::StaticClass())); +} + + +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + UStaticMesh * StaticMesh, + UHoudiniOutput * HoudiniOutput, + int32 MaterialIdx) +{ + FReply RetValue = FReply::Handled(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return RetValue; + + if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) + return RetValue; + + // Retrieve material interface which is being replaced. + UMaterialInterface * MaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; + if (!MaterialInterface) + return RetValue; + + // Find the string corresponding to the material that is being replaced + const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); + if (!FoundString ) + { + // This material was not replaced, no need to reset it + return RetValue; + } + + // This material has been replaced previously. + FString MaterialString = *FoundString; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), HoudiniOutput); + + // Remove the replacement + HoudiniOutput->Modify(); + HoudiniOutput->GetReplacementMaterials().Remove(MaterialString); + + bool bViewportNeedsUpdate = true; + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + UMaterialInterface * const * FoundMat = HoudiniOutput->GetAssignementMaterials().Find(MaterialString); + if (FoundMat && (*FoundMat)) + AssignMaterial = *FoundMat; + + // Replace material on static mesh. + StaticMesh->Modify(); + StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = AssignMaterial; + + // Replace the material on any component (SMC/ISMC) that uses the above SM + // TODO: ?? Replace for all? + for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) + { + // Only look at MeshComponents + UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); + if (!SMC) + continue; + + if (SMC->GetStaticMesh() != StaticMesh) + continue; + + SMC->Modify(); + SMC->SetMaterial(MaterialIdx, AssignMaterial); + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + return RetValue; +} + +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + ALandscapeProxy* InLandscape, + UHoudiniOutput * InHoudiniOutput, + int32 InMaterialIdx) +{ + FReply RetValue = FReply::Handled(); + if (!InLandscape || InLandscape->IsPendingKill()) + return RetValue; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * MaterialInterface = InMaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); + UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + // Find the string corresponding to the material that is being replaced + const FString* FoundString = InHoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); + if (!FoundString) + { + // This material was not replaced, no need to reset it + return RetValue; + } + + // This material has been replaced previously. + FString MaterialString = *FoundString; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), InHoudiniOutput); + + // Remove the replacement + InHoudiniOutput->Modify(); + InHoudiniOutput->GetReplacementMaterials().Remove(MaterialString); + + bool bViewportNeedsUpdate = true; + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + UMaterialInterface * const * FoundMat = InHoudiniOutput->GetAssignementMaterials().Find(MaterialString); + if (FoundMat && (*FoundMat)) + AssignMaterial = *FoundMat; + + // Replace material on Landscape + InLandscape->Modify(); + if (InMaterialIdx == 0) + InLandscape->LandscapeMaterial = AssignMaterial; + else + InLandscape->LandscapeHoleMaterial = AssignMaterial; + + InLandscape->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); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + */ + + FHoudiniEngineUtils::UpdateEditorProperties(InHoudiniOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + return RetValue; +} +/* +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + ALandscapeProxy * Landscape, UHoudiniOutput * InOutput, int32 MaterialIdx) +{ + bool bViewportNeedsUpdate = false; + + // TODO: Handle me! + for (TArray< UHoudiniAssetComponent * >::TIterator + IterComponents(HoudiniAssetComponents); IterComponents; ++IterComponents) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if (!HoudiniAssetComponent) + continue; + + TWeakObjectPtr* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find(*HoudiniGeoPartObject); + if (!FoundLandscapePtr) + continue; + + ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get(); + if (!FoundLandscape || !FoundLandscape->IsValidLowLevel()) + continue; + + if (FoundLandscape != Landscape) + continue; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + bool bMaterialRestored = false; + FString MaterialShopName; + if (!HoudiniAssetComponent->GetReplacementMaterialShopName(*HoudiniGeoPartObject, MaterialInterface, MaterialShopName)) + { + // This material was not replaced so there's no need to reset it + continue; + } + + // Remove the replacement + HoudiniAssetComponent->RemoveReplacementMaterial(*HoudiniGeoPartObject, MaterialShopName); + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial(MaterialShopName); + if (AssignedMaterial) + MaterialInterfaceReplacement = AssignedMaterial; + + // Replace material on the landscape + Landscape->Modify(); + + if (MaterialIdx == 0) + Landscape->LandscapeMaterial = MaterialInterfaceReplacement; + else + Landscape->LandscapeHoleMaterial = MaterialInterfaceReplacement; + + //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); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + + HoudiniAssetComponent->UpdateEditorProperties(false); + bViewportNeedsUpdate = true; + } + + if (GEditor && bViewportNeedsUpdate) + { + GEditor->RedrawAllViewports(); + } + + return FReply::Handled(); +} +*/ + +void +FHoudiniOutputDetails::OnBrowseTo(UObject* InObject) +{ + if (GEditor) + { + TArray Objects; + Objects.Add(InObject); + GEditor->SyncBrowserToObjects(Objects); + } +} + +TSharedRef +FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent( + UMaterialInterface* MaterialInterface, + UObject* OutputObject, + UHoudiniOutput* InOutput, + int32 MaterialIdx) +{ + TArray AllowedClasses; + AllowedClasses.Add(UMaterialInterface::StaticClass()); + + TArray NewAssetFactories; + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(MaterialInterface), + true, + AllowedClasses, + NewAssetFactories, + OnShouldFilterMaterialInterface, + FOnAssetSelected::CreateSP( + this, &FHoudiniOutputDetails::OnMaterialInterfaceSelected, OutputObject, InOutput, MaterialIdx), + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::CloseMaterialInterfaceComboButton)); +} + + +void +FHoudiniOutputDetails::CloseMaterialInterfaceComboButton() +{ + +} + +void +FHoudiniOutputDetails::OnMaterialInterfaceDropped( + UObject * InObject, + UStaticMesh * StaticMesh, + UHoudiniOutput * HoudiniOutput, + int32 MaterialIdx) +{ + UMaterialInterface * MaterialInterface = Cast(InObject); + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + return; + + if (!StaticMesh || StaticMesh->IsPendingKill()) + return; + + if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) + return; + + bool bViewportNeedsUpdate = false; + + // Retrieve material interface which is being replaced. + UMaterialInterface * OldMaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; + if (OldMaterialInterface == MaterialInterface) + return; + + // Find the string corresponding to the material that is being replaced + FString MaterialString = FString(); + const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been replaced previously. + MaterialString = *FoundString; + } + else + { + // We have no previous replacement for this material, + // see if we can find it the material assignment list. + FoundString = HoudiniOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been assigned previously. + MaterialString = *FoundString; + } + else + { + UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + if (OldMaterialInterface == DefaultMaterial) + { + // This is replacement for default material. + MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + } + else + { + // External Material? + MaterialString = OldMaterialInterface->GetName(); + } + } + } + + if (MaterialString.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), HoudiniOutput); + + // Add a new material replacement entry. + HoudiniOutput->Modify(); + HoudiniOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); + + // Replace material on static mesh. + StaticMesh->Modify(); + StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = MaterialInterface; + + // Replace the material on any component (SMC/ISMC) that uses the above SM + for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) + { + // Only look at MeshComponents + UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); + if (SMC && !SMC->IsPendingKill()) + { + if (SMC->GetStaticMesh() == StaticMesh) + { + SMC->Modify(); + SMC->SetMaterial(MaterialIdx, MaterialInterface); + } + } + else + { + UStaticMesh* SM = Cast(OutputObject.Value.OutputObject); + if (SM && !SM->IsPendingKill()) + { + SM->Modify(); + SM->SetMaterial(MaterialIdx, MaterialInterface); + } + } + + + + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); + + /* + if(GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); +*/ + if (GEditor) + GEditor->RedrawAllViewports(); +} + +// Delegate used when a valid material has been drag and dropped on a landscape. +void +FHoudiniOutputDetails::OnMaterialInterfaceDropped( + UObject* InDroppedObject, + ALandscapeProxy* InLandscape, + UHoudiniOutput* InOutput, + int32 MaterialIdx) +{ + UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >(InDroppedObject); + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + return; + + if (!InLandscape || InLandscape->IsPendingKill()) + return; + + bool bViewportNeedsUpdate = false; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * OldMaterialInterface = MaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); + if (OldMaterialInterface == MaterialInterface) + return; + + // Find the string corresponding to the material that is being replaced + FString MaterialString = FString(); + const FString* FoundString = InOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been replaced previously. + MaterialString = *FoundString; + } + else + { + // We have no previous replacement for this material, + // see if we can find it the material assignment list. + FoundString = InOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been assigned previously. + MaterialString = *FoundString; + } + else + { + UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + if (OldMaterialInterface == DefaultMaterial) + { + // This is replacement for default material. + MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + } + else + { + // External Material? + if (OldMaterialInterface && !OldMaterialInterface->IsPendingKill()) + MaterialString = OldMaterialInterface->GetName(); + } + } + } + + if (MaterialString.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), InOutput); + + // Add a new material replacement entry. + InOutput->Modify(); + InOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); + + // Replace material on the landscape + InLandscape->Modify(); + + if (MaterialIdx == 0) + InLandscape->LandscapeMaterial = MaterialInterface; + else + InLandscape->LandscapeHoleMaterial = MaterialInterface; + + // Update the landscape components Material instances + InLandscape->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 >(InLandscape->GetClass(), (MaterialIdx == 0) ? TEXT("LandscapeMaterial") : TEXT("LandscapeHoleMaterial")); + if (FoundProperty) + { + FPropertyChangedEvent PropChanged(FoundProperty, EPropertyChangeType::ValueSet); + InLandscape->PostEditChangeProperty(PropChanged); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + */ + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); +} + +void +FHoudiniOutputDetails::OnMaterialInterfaceSelected( + const FAssetData & AssetData, + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + TPairInitializer Pair(OutputObject, MaterialIdx); + TSharedPtr AssetComboButton = MaterialInterfaceComboButtons[Pair]; + if (AssetComboButton.IsValid()) + { + AssetComboButton->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + + UStaticMesh* SM = Cast(OutputObject); + if (SM && !SM->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); + } + + ALandscapeProxy* Landscape = Cast(OutputObject); + if (Landscape && !Landscape->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } + } +} + +void +FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + if (!OutputObject || OutputObject->IsPendingKill()) + return; + + if (!InOutput || InOutput->IsPendingKill()) + return; + + if (GEditor) + { + TArray CBSelections; + GEditor->GetContentBrowserSelections(CBSelections); + + // Get the first selected material object + UObject* Object = nullptr; + for (auto & CurAssetData : CBSelections) + { + if (CurAssetData.AssetClass != UMaterial::StaticClass()->GetFName() && + CurAssetData.AssetClass != UMaterialInstance::StaticClass()->GetFName() && + CurAssetData.AssetClass != UMaterialInstanceConstant::StaticClass()->GetFName()) + continue; + + Object = CurAssetData.GetAsset(); + break; + } + + if (Object && !Object->IsPendingKill()) + { + UStaticMesh* SM = Cast(OutputObject); + if (SM && !SM->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); + } + + ALandscapeProxy* Landscape = Cast(OutputObject); + if (Landscape && !Landscape->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } + } + } +} + +void +FHoudiniOutputDetails::CreateInstancerOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // Do not display instancer UI for one-instance instancers + bool OnlyOneInstanceInstancers = true; + for (auto& Iter : InOutput->GetInstancedOutputs()) + { + FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); + if (CurInstanceOutput.OriginalTransforms.Num() <= 1) + continue; + + OnlyOneInstanceInstancers = false; + break; + } + + // This output only has one-instance instancers (SMC), no need to display the instancer UI. + if (OnlyOneInstanceInstancers) + return; + + // Classes allowed for instance variations. + const TArray AllowedClasses = + { + UStaticMesh::StaticClass(), USkeletalMesh::StaticClass(), + AActor::StaticClass(), UBlueprint::StaticClass(), + UFXSystemAsset::StaticClass(), USoundBase::StaticClass() + }; + + // Classes not allowed for instances variations (useless?) + TArray DisallowedClasses = + { + UClass::StaticClass(), ULevel::StaticClass(), + UMaterial::StaticClass(), UTexture::StaticClass() + }; + + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // Lambda for adding new variation objects + auto AddObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) + { + // TODO: undo/redo? + InOutputToUpdate.VariationObjects.Insert(InObject, AtIndex); + InOutputToUpdate.VariationTransformOffsets.Insert(FTransform::Identity, AtIndex); + FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for adding new geometry input objects + auto RemoveObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex) + { + // Also keep one instance object + if (AtIndex < 0 || AtIndex >= InOutputToUpdate.VariationObjects.Num()) + return; + + if (InOutputToUpdate.VariationObjects.Num() == 1) + return; + + // TODO: undo/redo? + InOutputToUpdate.VariationObjects.RemoveAt(AtIndex); + InOutputToUpdate.VariationTransformOffsets.RemoveAt( AtIndex); + FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for updating a variation + auto SetObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) + { + if (!InOutputToUpdate.VariationObjects.IsValidIndex(AtIndex)) + return; + + InOutputToUpdate.VariationObjects[AtIndex] = InObject; + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for changing the transform offset values + auto ChangeTransformOffsetAt = [InOutput]( + FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, + const float& Value, const int32& PosRotScaleIndex, const int32& XYZIndex) + { + bool bChanged = InOutputToUpdate.SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); + if (!bChanged) + return; + + InOutputToUpdate.MarkChanged(true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Get this output's OutputObject + const TMap& OutputObjects = InOutput->GetOutputObjects(); + + // Iterate on all of the output's HGPO + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + // Not an instancer, skip + if (CurHGPO.Type != EHoudiniPartType::Instancer) + continue; + + // Get the label for that instancer + FString InstancerLabel = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); + if (CurHGPO.bHasCustomPartName) + InstancerLabel = CurHGPO.PartName; + + TSharedRef InstancerVerticalBox = SNew(SVerticalBox); + TSharedPtr InstancerHorizontalBox = nullptr; + + // Create a new Group for that instancer + IDetailGroup& InstancerGroup = HouOutputCategory.AddGroup(FName(*InstancerLabel), FText::FromString(InstancerLabel)); + + // Now iterate and display the instance outputs that matches this HGPO + for (auto& Iter : InOutput->GetInstancedOutputs()) + { + FHoudiniOutputObjectIdentifier& CurOutputObjectIdentifier = Iter.Key; + if (!CurOutputObjectIdentifier.Matches(CurHGPO)) + continue; + + FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); + + // Dont display instancer UI for one-instance instancers (SMC) + if (CurInstanceOutput.OriginalTransforms.Num() <= 1) + continue; + + for( int32 VariationIdx = 0; VariationIdx < CurInstanceOutput.VariationObjects.Num(); VariationIdx++ ) + { + UObject * InstancedObject = CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous(); + if ( !InstancedObject || InstancedObject->IsPendingKill() ) + { + HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); + continue; + } + + // Create thumbnail for this object. + TSharedPtr VariationThumbnail = + MakeShareable(new FAssetThumbnail(InstancedObject, 64, 64, AssetThumbnailPool)); + TSharedRef PickerVerticalBox = SNew(SVerticalBox); + TSharedPtr PickerHorizontalBox = nullptr; + TSharedPtr VariationThumbnailBorder; + + // For the variation name, reuse the instancer label and append the variation index if we have more than one variation + FString InstanceOutputLabel = InstancerLabel; + if(CurInstanceOutput.VariationObjects.Num() > 1) + InstanceOutputLabel += TEXT(" [") + FString::FromInt(VariationIdx) + TEXT("]"); + + IDetailGroup* DetailGroup = &InstancerGroup; + if (CurInstanceOutput.VariationObjects.Num() > 1) + { + // If we have more than one variation, add a new group for each variation + DetailGroup = &InstancerGroup.AddGroup(FName(*InstanceOutputLabel), FText::FromString(InstanceOutputLabel), true); + } + + // See if we can find the corresponding component to get its type + FString InstancerType = TEXT("(Instancer)"); + FHoudiniOutputObjectIdentifier CurVariationIdentifier = CurOutputObjectIdentifier; + CurVariationIdentifier.SplitIdentifier += TEXT("_") + FString::FromInt(VariationIdx); + const FHoudiniOutputObject* VariationOutputObject = OutputObjects.Find(CurVariationIdentifier); + if(VariationOutputObject) + InstancerType = FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(VariationOutputObject->OutputComponent); + + DetailGroup->AddWidgetRow() + .NameContent() + [ + //SNew(SSpacer) + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(InstancerType)) + //.Size(FVector2D(250, 64)) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + PickerVerticalBox + ]; + + // Add an asset drop target + PickerVerticalBox->AddSlot() + .Padding(0, 2) + .AutoHeight() + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop(SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( + [DisallowedClasses](const UObject* Obj) + { + for (auto Klass : DisallowedClasses) + { + if (Obj && Obj->IsA(Klass)) + return false; + } + return true; + }) + ) + .OnAssetDropped_Lambda([&CurInstanceOutput, VariationIdx, SetObjectAt](UObject* InObject) + { + return SetObjectAt(CurInstanceOutput, VariationIdx, InObject); + }) + [ + SAssignNew(PickerHorizontalBox, SHorizontalBox) + ] + ]; + + PickerHorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(VariationThumbnailBorder, SBorder) + .Padding( 5.0f ) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, InstancedObject) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(InstancedObject->GetPathName())) + [ + VariationThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + TWeakPtr WeakVariationThumbnailBorder(VariationThumbnailBorder); + VariationThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush *>::Create( + TAttribute::FGetter::CreateLambda([WeakVariationThumbnailBorder]() + { + TSharedPtr ThumbnailBorder = WeakVariationThumbnailBorder.Pin(); + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ))); + + PickerHorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 28.0f, 0.0f, 28.0f) + [ + PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, AddObjectAt]() + { + UObject* ObjToAdd = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? + CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() + : nullptr; + + return AddObjectAt(CurInstanceOutput, VariationIdx, ObjToAdd); + }), + LOCTEXT("AddAnotherInstanceToolTip", "Add Another Instance")) + ]; + + PickerHorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 28.0f, 4.0f, 28.0f ) + [ + PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, RemoveObjectAt]() + { + return RemoveObjectAt(CurInstanceOutput, VariationIdx); + }), + LOCTEXT("RemoveLastInstanceToolTip", "Remove Last Instance")) + ]; + + TSharedPtr AssetComboButton; + TSharedPtr ButtonBox; + PickerHorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .HAlign(HAlign_Fill) + [ + SAssignNew(ButtonBox, SHorizontalBox) + +SHorizontalBox::Slot() + [ + SAssignNew(AssetComboButton, SComboButton) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) + /* TODO: Update UI + .OnMenuOpenChanged( FOnIsOpenChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::ChangedStaticMeshComboButton, + CurInstanceOutput, InstOutIdx, VariationIdx ) ) + */ + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(InstancedObject->GetName())) + ] + ] + ] + ]; + + // Create asset picker for this combo button. + { + TWeakPtr WeakAssetComboButton(AssetComboButton); + TArray NewAssetFactories; + TSharedRef PropertyMenuAssetPicker = PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(InstancedObject), + true, + AllowedClasses, + DisallowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( + [&CurInstanceOutput, VariationIdx, SetObjectAt, WeakAssetComboButton](const FAssetData& AssetData) + { + TSharedPtr AssetComboButtonPtr = WeakAssetComboButton.Pin(); + if (AssetComboButtonPtr.IsValid()) + { + AssetComboButtonPtr->SetIsOpen(false); + UObject * Object = AssetData.GetAsset(); + SetObjectAt(CurInstanceOutput, VariationIdx, Object); + } + } + ), + // Nothing to do on close + FSimpleDelegate::CreateLambda([](){}) + ); + + AssetComboButton->SetMenuContent(PropertyMenuAssetPicker); + } + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(InstancedObject->GetName())); + FText StaticMeshTooltip = + FText::Format(LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx]() + { + UObject* InputObject = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? + CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() + : nullptr; + + if (GEditor && InputObject) + { + TArray Objects; + Objects.Add(InputObject); + GEditor->SyncBrowserToObjects(Objects); + } + }), + TAttribute< FText >( StaticMeshTooltip ) ) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f ) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT( "ResetToBase", "Reset to default static mesh")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([SetObjectAt, &CurInstanceOutput, VariationIdx]() + { + SetObjectAt(CurInstanceOutput, VariationIdx, CurInstanceOutput.OriginalObject.LoadSynchronous()); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + + + // Get Visibility of reset buttons + bool bResetButtonVisiblePosition = false; + bool bResetButtonVisibleRotation = false; + bool bResetButtonVisibleScale = false; + + FTransform CurTransform = CurInstanceOutput.VariationTransformOffsets[VariationIdx]; + + if (CurTransform.GetLocation() != FVector::ZeroVector) + bResetButtonVisiblePosition = true; + + FRotator Rotator = CurTransform.Rotator(); + if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) + bResetButtonVisibleRotation = true; + + if (CurTransform.GetScale3D() != FVector::OneVector) + bResetButtonVisibleScale = true; + + auto ChangeTransformOffsetUniformlyAt = [ChangeTransformOffsetAt, VariationIdx, &CurInstanceOutput](const float& Val, const int32& PosRotScaleIndex) + { + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 0); + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 1); + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 2); + }; + + TSharedRef OffsetVerticalBox = SNew(SVerticalBox); + FText LabelPositionText = LOCTEXT("HoudiniPositionOffset", "Position Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelPositionText) + .ToolTipText(LabelPositionText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .AllowSpin(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 0); } + ))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 1); } + ))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 2); } + ))) + .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 0); }) + .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 1); }) + .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 2); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("InstancerOutputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, CurInstanceOutput, InOutput]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 0); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + ]; + + FText LabelRotationText = LOCTEXT("HoudiniRotationOffset", "Rotation Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelRotationText) + .ToolTipText(LabelRotationText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SRotatorInputBox) + .AllowSpin(true) + .bColorAxisLabels(true) + .Roll(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 0); } + ))) + .Pitch(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 1); } + ))) + .Yaw(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 2); } + ))) + .OnRollCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 0); }) + .OnPitchCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 1); }) + .OnYawCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 2); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 1); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + ]; + + FText LabelScaleText = LOCTEXT("HoudiniScaleOffset", "Scale Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelScaleText) + .ToolTipText(LabelScaleText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 0); } + ))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 1); } + ))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 2); } + ))) + .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 0); + }) + .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 1); + }) + .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 2); + }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("InstancerOutputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the output object maintains its shape in each direction when scaled")) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(CurInstanceOutput.IsUnformScaleLocked() ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([&CurInstanceOutput, InOutput]() + { + CurInstanceOutput.SwitchUniformScaleLock(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() + { + ChangeTransformOffsetUniformlyAt(1.0f, 2); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + /* + // TODO: Add support for this back + + SHorizontalBox::Slot().AutoWidth() + [ + // Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "TransparentCheckBox") + .ToolTipText(LOCTEXT("PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled")) + *//* + .OnCheckStateChanged(FOnCheckStateChanged::CreateLambda([=](ECheckBoxState NewState) + { + if ( MyParam.IsValid() && InputFieldPtr.IsValid() ) + MyParam->CheckStateChanged( NewState == ECheckBoxState::Checked, InputFieldPtr.Get(), VariationIdx ); + })) + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute::FGetter::CreateLambda( [=]() + { + if (InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly(VariationIdx)) + return ECheckBoxState::Checked; + return ECheckBoxState::Unchecked; + } + ))) + *//* + [ + SNew(SImage) + *//*.Image(TAttribute::Create( + TAttribute::FGetter::CreateLambda( [=]() + { + if ( InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly( VariationIdx ) ) + { + return FEditorStyle::GetBrush( TEXT( "GenericLock" ) ); + } + return FEditorStyle::GetBrush( TEXT( "GenericUnlock" ) ); + } + ))) + *//* + .ColorAndOpacity( FSlateColor::UseForeground() ) + ] + ] + */ + ]; + } + } + } +} + +/* +void +FHoudiniOutputDetails::OnMaterialInterfaceSelected( + const FAssetData & AssetData, + ALandscapeProxy* Landscape, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); + TSharedPtr< SComboButton > AssetComboButton = LandscapeMaterialInterfaceComboButtons[Pair]; + if (AssetComboButton.IsValid()) + { + AssetComboButton->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } +} +*/ + +void +FHoudiniOutputDetails::CreateDefaultOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput) + return; + + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); + + // TODO + // This is just a temporary placeholder displaying name/output type + { + FString OutputNameStr = InOutput->GetName(); + FText OutputTooltip = GetOutputTooltip(InOutput); + + // Create a new detail row + // Name + FText OutputNameTxt = GetOutputDebugName(InOutput); + FDetailWidgetRow & Row = HouOutputCategory.AddCustomRow(FText::GetEmpty()); + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(OutputNameTxt) + .ToolTipText(OutputTooltip) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + + // Value + FText OutputTypeTxt = GetOutputDebugDescription(InOutput); + Row.ValueWidget.Widget = + SNew(STextBlock) + .Text(OutputTypeTxt) + .ToolTipText(OutputTooltip) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + } +} + +void +FHoudiniOutputDetails::OnBakeOutputObject( + const FString& InBakeName, + UObject * BakedOutputObject, + const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FHoudiniGeoPartObject & HGPO, + const UObject* OutputOwner, + const FString & HoudiniAssetName, + const FString & BakeFolder, + const FString & TempCookFolder, + const EHoudiniOutputType & Type, + const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, + const TArray& InAllOutputs) +{ + if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) + return; + + // Fill in the package params + FHoudiniPackageParams PackageParams; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + // Determine the relevant WorldContext based on the output owner + UWorld* WorldContext = OutputOwner ? OutputOwner->GetWorld() : GWorld; + const UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(OutputOwner); + check(IsValid(HAC)); + const bool bAutomaticallySetAttemptToLoadMissingPackages = true; + const bool bSkipObjectNameResolutionAndUseDefault = !InBakeName.IsEmpty(); // If InBakeName is set use it as is for the object name + const bool bSkipBakeFolderResolutionAndUseDefault = false; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + WorldContext, HAC, OutputIdentifier, InOutputObject, BakedOutputObject->GetName(), + HoudiniAssetName, PackageParams, Resolver, + BakeFolder, EPackageReplaceMode::ReplaceExistingAssets, + bAutomaticallySetAttemptToLoadMissingPackages, bSkipObjectNameResolutionAndUseDefault, + bSkipBakeFolderResolutionAndUseDefault); + + switch (Type) + { + case EHoudiniOutputType::Mesh: + { + UStaticMesh* StaticMesh = Cast(BakedOutputObject); + if (StaticMesh) + { + FDirectoryPath TempCookFolderPath; + TempCookFolderPath.Path = TempCookFolder; + UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( + StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath); + } + } + break; + case EHoudiniOutputType::Curve: + { + USplineComponent* SplineComponent = Cast(BakedOutputObject); + if (SplineComponent) + { + AActor* BakedActor; + USplineComponent* BakedSplineComponent; + FHoudiniEngineBakeUtils::BakeCurve(SplineComponent, GWorld->GetCurrentLevel(), PackageParams, BakedActor, BakedSplineComponent); + } + } + break; + case EHoudiniOutputType::Landscape: + { + ALandscapeProxy* Landscape = Cast(BakedOutputObject); + if (Landscape) + { + FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType); + } + } + break; + } +} + +FReply +FHoudiniOutputDetails::OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput) +{ + // TODO: Actually refine only the selected ProxyMesh + // For now, refine all the selection + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true, true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); +} + +void +FHoudiniOutputDetails::OnBakeNameCommitted( + const FText& Val, ETextCommit::Type TextCommitType, + UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + if (!InOutput) + return; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + + if (!FoundOutputObject) + return; + + FoundOutputObject->BakeName = Val.ToString(); +} + +void +FHoudiniOutputDetails::OnRevertBakeNameToDefault(UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier & InIdentifier) +{ + if (!InOutput) + return; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + + if (!FoundOutputObject) + return; + + FoundOutputObject->BakeName = FString(); +} #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h index a380d94bf..55f37c1d6 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h @@ -1,218 +1,226 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "ContentBrowserDelegates.h" -#include "Materials/MaterialInterface.h" -#include "Components/Border.h" -#include "Components/ComboBox.h" - - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class UHoudiniOutput; -class UHoudiniAssetComponent; -class FAssetThumbnailPool; -class ALandscapeProxy; -class USplineComponent; -class UHoudiniLandscapePtr; -class UHoudiniStaticMesh; -class UMaterialInterface; -class SBorder; -class SComboButton; - -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniOutputObject; - -enum class EHoudiniOutputType : uint8; -enum class EHoudiniLandscapeOutputBakeType : uint8; - -class FHoudiniOutputDetails : public TSharedFromThis -{ -public: - void CreateWidget( - IDetailCategoryBuilder& HouInputCategoryBuilder, - TArray InOutputs); - - void CreateMeshOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateCurveOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateStaticMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UStaticMesh * StaticMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject, - const bool& bIsProxyMeshCurrent); - - void CreateProxyMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UHoudiniStaticMesh * ProxyMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject); - - void CreateCurveWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - USceneComponent* SplineComponent, - FHoudiniOutputObject& OutputObject, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - FHoudiniGeoPartObject& HoudiniGeoPartObject); - - void CreateLandscapeOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateLandscapeOutputWidget_Helper( - IDetailCategoryBuilder & HouOutputCategory, - UHoudiniOutput * InOutput, - const FHoudiniGeoPartObject & HGPO, - UHoudiniLandscapePtr * LandscapePointer, - const FHoudiniOutputObjectIdentifier & OutputIdentifier); - - void CreateInstancerOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput * InOutput); - - void CreateDefaultOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - static FText GetOutputTooltip(UHoudiniOutput* MainOutput); - static FText GetOutputDebugName(UHoudiniOutput* InOutput); - static FText GetOutputDebugDescription(UHoudiniOutput* InOutput); - - static void OnBakeNameCommitted( - const FText& Val, ETextCommit::Type TextCommitType, - UHoudiniOutput * InOutput, - const FHoudiniOutputObjectIdentifier & InIdentifier); - - static void OnRevertBakeNameToDefault( - UHoudiniOutput * InOutput, - const FHoudiniOutputObjectIdentifier & InIdentifier); - - static void OnBakeOutputObject( - const FString& InBakeName, - UObject * BakedOutputObject, - const FHoudiniOutputObjectIdentifier & OutputIdentifier, - const FHoudiniOutputObject& InOutputObject, - const FHoudiniGeoPartObject & HGPO, - const UObject* OutputOwner, - const FString & HoudiniAssetName, - const FString & BakeFolder, - const FString & TempCookFolder, - const EHoudiniOutputType & Type, - const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, - const TArray& InAllOutputs); - - FReply OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput); - - // Gets the border brush to show around thumbnails, changes when the user hovers on it. - const FSlateBrush * GetThumbnailBorder(UObject* Mesh) const; - const FSlateBrush * GetMaterialInterfaceThumbnailBorder(UObject* Mesh, int32 MaterialIdx) const; - - // Delegate used to detect if valid object has been dragged and dropped. - bool OnMaterialInterfaceDraggedOver(const UObject * InObject) const; - - // Delegate used when a valid material has been drag and dropped on a mesh. - void OnMaterialInterfaceDropped( - UObject* InDroppedObject, - UStaticMesh* InStaticMesh, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Delegate used when a valid material has been drag and dropped on a landscape. - void OnMaterialInterfaceDropped( - UObject* InDroppedObject, - ALandscapeProxy* InLandscape, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Construct drop down menu content for material. - TSharedRef OnGetMaterialInterfaceMenuContent( - UMaterialInterface* MaterialInterface, - UObject* OutputObject, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Delegate for handling selection in content browser. - void OnMaterialInterfaceSelected( - const FAssetData & AssetData, - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx); - - // Delegate for handling Use CB selection arrow button clicked. - void OnUseContentBrowserSelectedMaterialInterface( - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx); - - // Closes the combo button. - void CloseMaterialInterfaceComboButton(); - - // Browse to material interface. - void OnBrowseTo(UObject* InObject); - - // Handler for reset material interface button. - FReply OnResetMaterialInterfaceClicked( - UStaticMesh * StaticMesh, UHoudiniOutput * InOutput, int32 MaterialIdx); - - FReply OnResetMaterialInterfaceClicked( - ALandscapeProxy* InLandscape, UHoudiniOutput * InHoudiniOutput, int32 InMaterialIdx); - - // Handler for when static mesh thumbnail is double clicked. We open editor in this case. - FReply OnThumbnailDoubleClick( - const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object); - - // Handler for bake individual static mesh action. - // static FReply OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject); - -private: - - // Map of meshes and corresponding thumbnail borders. - TMap> OutputObjectThumbnailBorders; - // Map of meshes / material indices to thumbnail borders. - TMap, TSharedPtr> MaterialInterfaceThumbnailBorders; - // Map of meshes / material indices to combo elements. - TMap, TSharedPtr> MaterialInterfaceComboButtons; - - /** Delegate for filtering material interfaces. **/ - FOnShouldFilterAsset OnShouldFilterMaterialInterface; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "ContentBrowserDelegates.h" +#include "Materials/MaterialInterface.h" +#include "Components/Border.h" +#include "Components/ComboBox.h" + + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class UHoudiniOutput; +class UHoudiniAssetComponent; +class FAssetThumbnailPool; +class ALandscapeProxy; +class USplineComponent; +class UHoudiniLandscapePtr; +class UHoudiniLandscapeEditLayer; +class UHoudiniStaticMesh; +class UMaterialInterface; +class SBorder; +class SComboButton; + +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniOutputObject; + +enum class EHoudiniOutputType : uint8; +enum class EHoudiniLandscapeOutputBakeType : uint8; + +class FHoudiniOutputDetails : public TSharedFromThis +{ +public: + void CreateWidget( + IDetailCategoryBuilder& HouInputCategoryBuilder, + TArray InOutputs); + + void CreateMeshOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateCurveOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateStaticMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UStaticMesh * StaticMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + const bool& bIsProxyMeshCurrent); + + void CreateProxyMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UHoudiniStaticMesh * ProxyMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject); + + void CreateCurveWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + USceneComponent* SplineComponent, + FHoudiniOutputObject& OutputObject, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + FHoudiniGeoPartObject& HoudiniGeoPartObject); + + void CreateLandscapeOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateLandscapeOutputWidget_Helper( + IDetailCategoryBuilder & HouOutputCategory, + UHoudiniOutput * InOutput, + const FHoudiniGeoPartObject & HGPO, + UHoudiniLandscapePtr * LandscapePointer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier); + + void CreateLandscapeEditLayerOutputWidget_Helper( + IDetailCategoryBuilder & HouOutputCategory, + UHoudiniOutput * InOutput, + const FHoudiniGeoPartObject & HGPO, + UHoudiniLandscapeEditLayer * LandscapeEditLayer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier); + + void CreateInstancerOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput * InOutput); + + void CreateDefaultOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + static FText GetOutputTooltip(UHoudiniOutput* MainOutput); + static FText GetOutputDebugName(UHoudiniOutput* InOutput); + static FText GetOutputDebugDescription(UHoudiniOutput* InOutput); + + static void OnBakeNameCommitted( + const FText& Val, ETextCommit::Type TextCommitType, + UHoudiniOutput * InOutput, + const FHoudiniOutputObjectIdentifier & InIdentifier); + + static void OnRevertBakeNameToDefault( + UHoudiniOutput * InOutput, + const FHoudiniOutputObjectIdentifier & InIdentifier); + + static void OnBakeOutputObject( + const FString& InBakeName, + UObject * BakedOutputObject, + const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FHoudiniGeoPartObject & HGPO, + const UObject* OutputOwner, + const FString & HoudiniAssetName, + const FString & BakeFolder, + const FString & TempCookFolder, + const EHoudiniOutputType & Type, + const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, + const TArray& InAllOutputs); + + FReply OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput); + + // Gets the border brush to show around thumbnails, changes when the user hovers on it. + const FSlateBrush * GetThumbnailBorder(UObject* Mesh) const; + const FSlateBrush * GetMaterialInterfaceThumbnailBorder(UObject* Mesh, int32 MaterialIdx) const; + + // Delegate used to detect if valid object has been dragged and dropped. + bool OnMaterialInterfaceDraggedOver(const UObject * InObject) const; + + // Delegate used when a valid material has been drag and dropped on a mesh. + void OnMaterialInterfaceDropped( + UObject* InDroppedObject, + UStaticMesh* InStaticMesh, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Delegate used when a valid material has been drag and dropped on a landscape. + void OnMaterialInterfaceDropped( + UObject* InDroppedObject, + ALandscapeProxy* InLandscape, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Construct drop down menu content for material. + TSharedRef OnGetMaterialInterfaceMenuContent( + UMaterialInterface* MaterialInterface, + UObject* OutputObject, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Delegate for handling selection in content browser. + void OnMaterialInterfaceSelected( + const FAssetData & AssetData, + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx); + + // Delegate for handling Use CB selection arrow button clicked. + void OnUseContentBrowserSelectedMaterialInterface( + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx); + + // Closes the combo button. + void CloseMaterialInterfaceComboButton(); + + // Browse to material interface. + void OnBrowseTo(UObject* InObject); + + // Handler for reset material interface button. + FReply OnResetMaterialInterfaceClicked( + UStaticMesh * StaticMesh, UHoudiniOutput * InOutput, int32 MaterialIdx); + + FReply OnResetMaterialInterfaceClicked( + ALandscapeProxy* InLandscape, UHoudiniOutput * InHoudiniOutput, int32 InMaterialIdx); + + // Handler for when static mesh thumbnail is double clicked. We open editor in this case. + FReply OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object); + + // Handler for bake individual static mesh action. + // static FReply OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject); + +private: + + // Map of meshes and corresponding thumbnail borders. + TMap> OutputObjectThumbnailBorders; + // Map of meshes / material indices to thumbnail borders. + TMap, TSharedPtr> MaterialInterfaceThumbnailBorders; + // Map of meshes / material indices to combo elements. + TMap, TSharedPtr> MaterialInterfaceComboButtons; + + /** Delegate for filtering material interfaces. **/ + FOnShouldFilterAsset OnShouldFilterMaterialInterface; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp index ef908d776..411857626 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp @@ -1,2636 +1,2636 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPDGManager.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAssetActor.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniEngineDetails.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "IDetailGroup.h" -#include "IDetailCustomization.h" -#include "PropertyCustomizationHelpers.h" -#include "DetailWidgetRow.h" -#include "ScopedTransaction.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/SBoxPanel.h" -#include "Widgets/Layout/SSpacer.h" -#include "Framework/SlateDelegates.h" -#include "Templates/SharedPointer.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define HOUDINI_ENGINE_UI_SECTION_PDG_BAKE 2 - -void -FHoudiniPDGDetails::CreateWidget( - IDetailCategoryBuilder& HouPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // PDG ASSET - FHoudiniPDGDetails::AddPDGAssetWidget(HouPDGCategory, InPDGAssetLink); - - // TOP NETWORKS - FHoudiniPDGDetails::AddTOPNetworkWidget(HouPDGCategory, InPDGAssetLink); - - // PDG EVENT MESSAGES -} - - -void -FHoudiniPDGDetails::AddPDGAssetWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // PDG STATUS ROW - AddPDGAssetStatus(InPDGCategory, InPDGAssetLink); - - // Commandlet Status row - AddPDGCommandletStatus(InPDGCategory, FHoudiniEngine::Get().GetPDGCommandletStatus()); - - // REFRESH / RESET Buttons - { - TSharedRef RefreshHBox = SNew(SHorizontalBox); - TSharedPtr ResetHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGRefreshResetRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Refresh", "Refresh")) - .ToolTipText(LOCTEXT("RefreshTooltip", "Refreshes infos displayed by the the PDG Asset Link")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .OnClicked_Lambda([InPDGAssetLink]() - { - FHoudiniPDGDetails::RefreshPDGAssetLink(InPDGAssetLink); - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(RefreshHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Reset", "Reset")) - .ToolTipText(LOCTEXT("ResetTooltip", "Resets the PDG Asset Link")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .OnClicked_Lambda([InPDGAssetLink]() - { - // TODO: RESET USELESS? - FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(ResetHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr RefreshIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGRefreshIconBrush(); - if (RefreshIconBrush.IsValid()) - { - TSharedPtr RefreshImage; - RefreshHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RefreshImage, SImage) - ] - ]; - - RefreshImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([RefreshIconBrush]() { return RefreshIconBrush.Get(); }))); - } - - RefreshHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Refresh", "Refresh")) - ]; - - TSharedPtr ResetIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGResetIconBrush(); - if (ResetIconBrush.IsValid()) - { - TSharedPtr ResetImage; - ResetHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(ResetImage, SImage) - ] - ]; - - ResetImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([ResetIconBrush]() { return ResetIconBrush.Get(); }))); - } - - ResetHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Reset", "Reset")) - ]; - } - - // TOP NODE FILTER - { - FText Tooltip = FText::FromString(TEXT("When enabled, the TOP Node Filter will only display the TOP Nodes found in the current network that start with the filter prefix. Disabling the Filter will display all of the TOP Network's TOP Nodes.")); - // Lambda for changing the filter value - auto ChangeTOPNodeFilter = [InPDGAssetLink](const FString& NewValue) - { - if (InPDGAssetLink->TOPNodeFilter.Equals(NewValue)) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->TOPNodeFilter = NewValue; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter), InPDGAssetLink); - }; - - FDetailWidgetRow& PDGFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGFilterRow, InPDGAssetLink); - PDGFilterRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox enable filter - SNew(SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bUseTOPNodeFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (InPDGAssetLink->bUseTOPNodeFilter == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bUseTOPNodeFilter = bNewState; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node Filter"))) - .ToolTipText(Tooltip) - ]; - - PDGFilterRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .ToolTipText(Tooltip) - .Text_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return FText(); - return FText::FromString(InPDGAssetLink->TOPNodeFilter); - }) - .OnTextCommitted_Lambda([ChangeTOPNodeFilter](const FText& Val, ETextCommit::Type TextCommitType) - { - ChangeTOPNodeFilter(Val.ToString()); - }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([=]() - { - FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER); - ChangeTOPNodeFilter(DefaultFilter); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - } - - // TOP OUTPUT FILTER - { - // Lambda for changing the filter value - FText Tooltip = FText::FromString(TEXT("When enabled, the Work Item Output Files created for the TOP Nodes found in the current network that start with the filter prefix will be automatically loaded int the world after being cooked.")); - auto ChangeTOPOutputFilter = [InPDGAssetLink](const FString& NewValue) - { - if (InPDGAssetLink->TOPOutputFilter.Equals(NewValue)) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->TOPOutputFilter = NewValue; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter), InPDGAssetLink); - }; - - FDetailWidgetRow& PDGOutputFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGOutputFilterRow, InPDGAssetLink); - - PDGOutputFilterRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox enable filter - SNew(SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bUseTOPOutputFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (InPDGAssetLink->bUseTOPOutputFilter == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bUseTOPOutputFilter = bNewState; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Output Filter"))) - .ToolTipText(Tooltip) - ]; - - PDGOutputFilterRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return FText(); - return FText::FromString(InPDGAssetLink->TOPOutputFilter); - }) - .OnTextCommitted_Lambda([ChangeTOPOutputFilter](const FText& Val, ETextCommit::Type TextCommitType) - { - ChangeTOPOutputFilter(Val.ToString()); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([ChangeTOPOutputFilter]() - { - FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER); - ChangeTOPOutputFilter(DefaultFilter); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - } - - // Checkbox: Autocook - { - FText Tooltip = FText::FromString(TEXT("When enabled, the selected TOP Network's output will automatically cook after succesfully cooking the PDG Asset Link HDA.")); - FDetailWidgetRow& PDGAutocookRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGAutocookRow, InPDGAssetLink); - PDGAutocookRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Auto-cook"))) - .ToolTipText(Tooltip) - ]; - - TSharedPtr AutoCookCheckBox; - PDGAutocookRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(AutoCookCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bAutoCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (!InPDGAssetLink || InPDGAssetLink->bAutoCook == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bAutoCook = bNewState; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bAutoCook), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ]; - } - // Output parent actor selector - { - IDetailPropertyRow* PDGOutputParentActorRow = InPDGCategory.AddExternalObjectProperty({ InPDGAssetLink }, "OutputParentActor"); - if (PDGOutputParentActorRow) - { - TAttribute PDGOutputParentActorRowEnabled; - BindDisableIfPDGNotLinked(PDGOutputParentActorRowEnabled, InPDGAssetLink); - PDGOutputParentActorRow->IsEnabled(PDGOutputParentActorRowEnabled); - TSharedPtr NameWidget; - TSharedPtr ValueWidget; - PDGOutputParentActorRow->GetDefaultWidgets(NameWidget, ValueWidget); - PDGOutputParentActorRow->DisplayName(FText::FromString(TEXT("Output Parent Actor"))); - PDGOutputParentActorRow->ToolTip(FText::FromString( - TEXT("The PDG Output Actors will be created under this parent actor. If not set, then the PDG Output Actors will be created under a new folder."))); - } - } - - // Add bake widgets for PDG output - CreatePDGBakeWidgets(InPDGCategory, InPDGAssetLink); - - // TODO: move this to a better place: the baking code is in HoudiniEngineEditor, the PDG manager (that knows about - // when work object results are loaded is in HoudiniEngine and the PDGAssetLink is in HoudiniEngineRuntime). So - // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? - if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) - InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); - InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded); - - // WORK ITEM STATUS - { - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGStatusRow, InPDGAssetLink); - FHoudiniPDGDetails::AddWorkItemStatusWidget( - PDGStatusRow, TEXT("Asset Work Item Status"), InPDGAssetLink, false); - } -} - -bool -FHoudiniPDGDetails::GetPDGStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor) -{ - OutPDGStatusString = FString(); - OutPDGStatusColor = FLinearColor::White; - - if (!IsValid(InPDGAssetLink)) - return false; - - switch (InPDGAssetLink->LinkState) - { - case EPDGLinkState::Linked: - OutPDGStatusString = TEXT("PDG is READY"); - OutPDGStatusColor = FLinearColor::Green; - break; - case EPDGLinkState::Linking: - OutPDGStatusString = TEXT("PDG is Linking"); - OutPDGStatusColor = FLinearColor::Yellow; - break; - case EPDGLinkState::Error_Not_Linked: - OutPDGStatusString = TEXT("PDG is ERRORED"); - OutPDGStatusColor = FLinearColor::Red; - break; - case EPDGLinkState::Inactive: - OutPDGStatusString = TEXT("PDG is INACTIVE"); - OutPDGStatusColor = FLinearColor::White; - break; - default: - return false; - } - - return true; -} - -void -FHoudiniPDGDetails::AddPDGAssetStatus( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink) -{ - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - FString PDGStatusString; - FLinearColor PDGStatusColor; - GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); - return FText::FromString(PDGStatusString); - }) - .ColorAndOpacity_Lambda([InPDGAssetLink]() - { - FString PDGStatusString; - FLinearColor PDGStatusColor; - GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); - return FSlateColor(PDGStatusColor); - }) - ] - ]; -} - -void -FHoudiniPDGDetails::GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor) -{ - OutStatusString = FString(); - OutStatusColor = FLinearColor::White; - switch (FHoudiniEngine::Get().GetPDGCommandletStatus()) - { - case EHoudiniBGEOCommandletStatus::Connected: - OutStatusString = TEXT("Async importer is CONNECTED"); - OutStatusColor = FLinearColor::Green; - break; - case EHoudiniBGEOCommandletStatus::Running: - OutStatusString = TEXT("Async importer is Running"); - OutStatusColor = FLinearColor::Yellow; - break; - case EHoudiniBGEOCommandletStatus::Crashed: - OutStatusString = TEXT("Async importer has CRASHED"); - OutStatusColor = FLinearColor::Red; - break; - case EHoudiniBGEOCommandletStatus::NotStarted: - OutStatusString = TEXT("Async importer is NOT STARTED"); - OutStatusColor = FLinearColor::White; - break; - } -} - -void -FHoudiniPDGDetails::AddPDGCommandletStatus( - IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus) -{ - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Visibility_Lambda([]() - { - const UHoudiniRuntimeSettings* Settings = GetDefault(); - if (IsValid(Settings)) - { - return FHoudiniEngineCommands::IsPDGCommandletEnabled() ? EVisibility::Visible : EVisibility::Collapsed; - } - - return EVisibility::Visible; - }) - .Text_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetPDGCommandletStatus(StatusString, StatusColor); - return FText::FromString(StatusString); - }) - .ColorAndOpacity_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetPDGCommandletStatus(StatusString, StatusColor); - return FSlateColor(StatusColor); - }) - ] - ]; -} - -bool -FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( - UHoudiniPDGAssetLink* InAssetLink, - bool bInForSelectedNode, - const FString& InTallyItemString, - int32& OutValue, - FLinearColor& OutColor) -{ - OutValue = 0; - OutColor = FLinearColor::White; - - if (!IsValid(InAssetLink)) - return false; - - bool bFound = false; - const FWorkItemTallyBase* TallyPtr = nullptr; - if (bInForSelectedNode) - { - UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); - if (TOPNode && !TOPNode->bHidden) - TallyPtr = &(TOPNode->GetWorkItemTally()); - } - else - TallyPtr = &(InAssetLink->WorkItemTally); - - if (TallyPtr) - { - if (InTallyItemString == TEXT("WAITING")) - { - // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI - OutValue = TallyPtr->NumWaitingWorkItems() + TallyPtr->NumScheduledWorkItems(); - OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("COOKING")) - { - OutValue = TallyPtr->NumCookingWorkItems(); - OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("COOKED")) - { - OutValue = TallyPtr->NumCookedWorkItems(); - OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("FAILED")) - { - OutValue = TallyPtr->NumErroredWorkItems(); - OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; - bFound = true; - } - } - - return bFound; -} - -void -FHoudiniPDGDetails::AddWorkItemStatusWidget( - FDetailWidgetRow& InRow, const FString& InTitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode) -{ - auto AddGridBox = [InAssetLink, bInForSelectedNode](const FString& Title) -> SHorizontalBox::FSlot& - { - return SHorizontalBox::Slot() - .MaxWidth(500.0f) - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .AutoHeight() - .Padding(FMargin(1.0f, 2.0f)) - [ - SNew(SBorder) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) - .BorderBackgroundColor(FSlateColor(FLinearColor(0.6, 0.6, 0.6))) - .Padding(FMargin(1.0f, 5.0f)) - [ - SNew(SBox) - .WidthOverride(95.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString(Title)) - .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FSlateColor(Color); - }) - ] - ] - ] - + SVerticalBox::Slot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .AutoHeight() - .Padding(FMargin(1.0f, 2.0f)) - [ - SNew(SBorder) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) - .BorderBackgroundColor(FSlateColor(FLinearColor(0.8, 0.8, 0.8))) - .Padding(FMargin(1.0f, 5.0f)) - [ - SNew(SBox) - .WidthOverride(95.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FText::AsNumber(Value); - }) - .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FSlateColor(Color); - }) - ] - ] - ] - ]; - }; - - InRow.WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f) - .AutoWidth() - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - [ - SNew(SSpacer) - ] - + SVerticalBox::Slot() - .AutoHeight() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(FMargin(0.0f, 2.0f)) - [ - SNew(STextBlock) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .Text(FText::FromString(InTitleString)) - - ] - + SVerticalBox::Slot() - .AutoHeight() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(FMargin(0.0f, 2.0f)) - [ - SNew(SHorizontalBox) - + AddGridBox(TEXT("WAITING")) - + AddGridBox(TEXT("COOKING")) - + AddGridBox(TEXT("COOKED")) - + AddGridBox(TEXT("FAILED")) - ] - + SVerticalBox::Slot() - [ - SNew(SSpacer) - ] - ] - ]; -} - - -void -FHoudiniPDGDetails::AddTOPNetworkWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink ) -{ - if (!InPDGAssetLink->GetSelectedTOPNetwork()) - return; - - if (InPDGAssetLink->AllTOPNetworks.Num() <= 0) - return; - - TOPNetworksPtr.Reset(); - - FString GroupLabel = TEXT("TOP Networks"); - IDetailGroup& TOPNetWorkGrp = InPDGCategory.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), false, true); - - // Combobox: TOP Network - { - FDetailWidgetRow& PDGTOPNetRow = TOPNetWorkGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGTOPNetRow, InPDGAssetLink); - PDGTOPNetRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Network"))) - ]; - - // Fill the TOP Networks SharedString array - TOPNetworksPtr.SetNum(InPDGAssetLink->AllTOPNetworks.Num()); - for(int32 Idx = 0; Idx < InPDGAssetLink->AllTOPNetworks.Num(); Idx++) - { - const UTOPNetwork* Network = InPDGAssetLink->AllTOPNetworks[Idx]; - if (!IsValid(Network)) - { - TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( - Idx, - TEXT("Invalid"), - TEXT("Invalid") - )); - } - else - { - TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( - Idx, - FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Network->NodeName, Network->NodePath), - Network->NodePath - )); - } - } - - if(TOPNetworksPtr.Num() <= 0) - TOPNetworksPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, "----"))); - - // Lambda for selecting another TOPNet - auto OnTOPNetChanged = [InPDGAssetLink](TSharedPtr InNewChoice) - { - if (!InNewChoice.IsValid()) - return; - - const int32 NewChoice = InNewChoice->Value; - int32 NewSelectedIndex = -1; - if (InPDGAssetLink->AllTOPNetworks.IsValidIndex(NewChoice)) - NewSelectedIndex = NewChoice; - - if (InPDGAssetLink->SelectedTOPNetworkIndex == NewSelectedIndex) - return; - - if (NewSelectedIndex < 0) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->SelectedTOPNetworkIndex = NewSelectedIndex; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex), InPDGAssetLink); - }; - - TSharedPtr HorizontalBoxTOPNet; - TSharedPtr>> ComboBoxTOPNet; - int32 SelectedIndex = TOPNetworksPtr.IndexOfByPredicate([InPDGAssetLink](const TSharedPtr& InEntry) - { - return InEntry.IsValid() && InEntry->Value == InPDGAssetLink->SelectedTOPNetworkIndex; - }); - if (SelectedIndex < 0) - SelectedIndex = 0; - - PDGTOPNetRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .FillWidth(300.f) - .MaxWidth(300.f) - [ - SAssignNew(ComboBoxTOPNet, SComboBox>) - .OptionsSource(&TOPNetworksPtr) - .InitiallySelectedItem(TOPNetworksPtr[SelectedIndex]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); - const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryToolTip) - .Margin(2.0f) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnTOPNetChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnTOPNetChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString(InPDGAssetLink->GetSelectedTOPNetworkName()); - }) - .ToolTipText_Lambda([InPDGAssetLink]() - { - UTOPNetwork const * const Network = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(Network)) - { - if (!Network->NodePath.IsEmpty()) - return FText::FromString(Network->NodePath); - else - return FText::FromString(Network->NodeName); - } - else - { - return FText(); - } - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - } - - // Buttons: DIRTY ALL / COOK OUTPUT - { - TSharedRef DirtyAllHBox = SNew(SHorizontalBox); - TSharedPtr CookOutHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("DirtyAll", "Dirty All")) - .ToolTipText(LOCTEXT("DirtyAllTooltip", "Dirty all TOP nodes in the selected TOP network and clears all of its work item results.")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork()); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(TOPNetwork)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - FHoudiniPDGManager::DirtyAll(TOPNetwork); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - else - { - UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(TOPNetwork); - } - } - } - - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(DirtyAllHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("CookOut", "Cook Output")) - .ToolTipText(LOCTEXT("CookOutTooltip", "Cooks the output nodes of the selected TOP network")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!IsValid(SelectedTOPNet)) - return false; - - // Disable if there any nodes in the network that are already cooking - return !SelectedTOPNet->AnyWorkItemsPending(); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::CookOutput(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CookOutHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr DirtyAllIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyAllIconBrush(); - if (DirtyAllIconBrush.IsValid()) - { - TSharedPtr DirtyAllImage; - DirtyAllHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(DirtyAllImage, SImage) - ] - ]; - - DirtyAllImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([DirtyAllIconBrush]() { return DirtyAllIconBrush.Get(); }))); - } - - DirtyAllHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("DirtyAll", "Dirty All")) - ]; - - TSharedPtr CookOutIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - if (CookOutIconBrush.IsValid()) - { - TSharedPtr CookOutImage; - CookOutHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookOutImage, SImage) - ] - ]; - - CookOutImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookOutIconBrush]() { return CookOutIconBrush.Get(); }))); - } - - CookOutHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookOut", "Cook Output")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: PAUSE COOK / CANCEL COOK - { - TSharedRef PauseHBox = SNew(SHorizontalBox); - TSharedPtr CancelHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Pause", "Pause Cook")) - .ToolTipText(LOCTEXT("PauseTooltip", "Pauses cooking for the selected TOP Network")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::PauseCook(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(PauseHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Cancel", "Cancel Cook")) - .ToolTipText(LOCTEXT("CancelTooltip", "Cancels cooking the selected TOP network")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::CancelCook(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CancelHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr PauseIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGPauseIconBrush(); - if (PauseIconBrush.IsValid()) - { - TSharedPtr PauseImage; - PauseHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(PauseImage, SImage) - ] - ]; - - PauseImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([PauseIconBrush]() { return PauseIconBrush.Get(); }))); - } - - PauseHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Pause", "Pause Cook")) - ]; - - TSharedPtr CancelIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGCancelIconBrush(); - if (CancelIconBrush.IsValid()) - { - TSharedPtr CancelImage; - CancelHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CancelImage, SImage) - ] - ]; - - CancelImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CancelIconBrush]() { return CancelIconBrush.Get(); }))); - } - - CancelHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Cancel", "Cancel Cook")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: Unload Work Item Objects - { - FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork(); }) - .WidthOverride(200.0f) - [ - SNew(SButton) - .Text(LOCTEXT("UnloadWorkItemsForNetwork", "Unload All Work Item Objects")) - .ToolTipText(LOCTEXT("UnloadWorkItemsForNetworkTooltip", "Unloads / removes loaded work item results from level for all nodes in this network. Not undoable: use the \"Load Work Item Objects\" button on the individual TOP nodes to reload work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNetwork* const SelectedNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!IsValid(SelectedNet) || - INDEX_NONE == SelectedNet->AllTOPNodes.IndexOfByPredicate([](const UTOPNode* InNode) { return IsValid(InNode) && InNode->bCachedHaveLoadedWorkResults; })) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNetwork* const TOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(TOPNet)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - // Set the state to ToDelete, PDGManager will delete it when processing work items - TOPNet->SetLoadedWorkResultsToDelete(); - } - else - { - // Delete and unload the result objects and actors now - TOPNet->DeleteWorkResultOutputObjects(); - } - } - } - - return FReply::Handled(); - }) - ] - ] - ]; - } - - // TOP NODE WIDGETS - FHoudiniPDGDetails::AddTOPNodeWidget(TOPNetWorkGrp, InPDGAssetLink); -} - -bool -FHoudiniPDGDetails::GetSelectedTOPNodeStatusAndColor(UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor) -{ - OutTOPNodeStatus = FString(); - OutTOPNodeStatusColor = FLinearColor::White; - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode) && !TOPNode->bHidden) - { - OutTOPNodeStatus = UHoudiniPDGAssetLink::GetTOPNodeStatus(TOPNode); - OutTOPNodeStatusColor = UHoudiniPDGAssetLink::GetTOPNodeStatusColor(TOPNode); - - return true; - } - } - - return false; -} - -void -FHoudiniPDGDetails::AddTOPNodeWidget( - IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink ) -{ - if (!InPDGAssetLink->GetSelectedTOPNetwork()) - return; - - FString GroupLabel = TEXT("TOP Nodes"); - IDetailGroup& TOPNodesGrp = InGroup.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), true); - - // Combobox: TOP Node - { - FDetailWidgetRow& PDGTOPNodeRow = TOPNodesGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGTOPNodeRow, InPDGAssetLink); - PDGTOPNodeRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node"))) - ]; - - // Update the TOP Node SharedString - TOPNodesPtr.Reset(); - TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, LOCTEXT("ComboBoxEntryNoSelectedTOPNode", "- Select -").ToString()))); - const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNet)) - { - const int32 NumTOPNodes = SelectedTOPNet->AllTOPNodes.Num(); - for (int32 Idx = 0; Idx < NumTOPNodes; Idx++) - { - const UTOPNode* const Node = SelectedTOPNet->AllTOPNodes[Idx]; - if (!IsValid(Node) || Node->bHidden) - continue; - - TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip( - Idx, - FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Node->NodeName, Node->NodePath), - Node->NodePath - ))); - } - } - - FString NodeErrorText = FString(); - FString NodeErrorTooltip = FString(); - FLinearColor NodeErrorColor = FLinearColor::White; - if (!IsValid(SelectedTOPNet) || SelectedTOPNet->AllTOPNodes.Num() <= 0) - { - NodeErrorText = TEXT("No valid TOP Node found!"); - NodeErrorTooltip = TEXT("There is no valid TOP Node found in the selected TOP Network!"); - NodeErrorColor = FLinearColor::Red; - } - else if(TOPNodesPtr.Num() <= 0) - { - NodeErrorText = TEXT("No visible TOP Node found!"); - NodeErrorTooltip = TEXT("No visible TOP Node found, all nodes in this network are hidden. Please update your TOP Node Filter."); - NodeErrorColor = FLinearColor::Yellow; - } - - // Lambda for selecting a TOPNode - auto OnTOPNodeChanged = [InPDGAssetLink](TSharedPtr InNewChoice) - { - UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!InNewChoice.IsValid() || !IsValid(TOPNetwork)) - return; - - const int32 NewChoice = InNewChoice->Value; - int32 NewSelectedIndex = INDEX_NONE; - if (TOPNetwork->AllTOPNodes.IsValidIndex(NewChoice)) - NewSelectedIndex = NewChoice; - - if (TOPNetwork->SelectedTOPIndex != NewSelectedIndex) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNetwork); - - TOPNetwork->Modify(); - TOPNetwork->SelectedTOPIndex = NewSelectedIndex; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UTOPNetwork, SelectedTOPIndex), TOPNetwork); - } - }; - - TSharedPtr HorizontalBoxTOPNode; - TSharedPtr>> ComboBoxTOPNode; - int32 SelectedIndex = 0; - UTOPNetwork* const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNetwork) && SelectedTOPNetwork->SelectedTOPIndex >= 0) - { - //SelectedIndex = InPDGAssetLink->GetSelectedTOPNetwork()->SelectedTOPIndex; - - // We need to match the selection by the index in the AllTopNodes array - // Because of the nodefilter, it is possible that the selected index does not match the index in TOPNodesPtr - const int32 SelectedTOPNodeIndex = SelectedTOPNetwork->SelectedTOPIndex; - // Find the matching UI index - for (int32 UIIndex = 0; UIIndex < TOPNodesPtr.Num(); UIIndex++) - { - if (TOPNodesPtr[UIIndex] && TOPNodesPtr[UIIndex]->Value == SelectedTOPNodeIndex) - { - // We found the UI Index that matches the current TOP Node! - SelectedIndex = UIIndex; - break; - } - } - } - - TSharedPtr ErrorText; - - PDGTOPNodeRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .FillWidth(300.f) - .MaxWidth(300.f) - [ - SAssignNew(ComboBoxTOPNode, SComboBox>) - .OptionsSource(&TOPNodesPtr) - .InitiallySelectedItem(TOPNodesPtr[SelectedIndex]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); - const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryToolTip) - .Margin(2.0f) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnTOPNodeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnTOPNodeChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink, ComboBoxTOPNode, Options = TOPNodesPtr]() - { - if (IsValid(InPDGAssetLink)) - return FText::FromString(InPDGAssetLink->GetSelectedTOPNodeName()); - else - return FText(); - }) - .ToolTipText_Lambda([InPDGAssetLink]() - { - UTOPNode const * const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (!TOPNode->NodePath.IsEmpty()) - return FText::FromString(TOPNode->NodePath); - else - return FText::FromString(TOPNode->NodeName); - } - else - { - return FText(); - } - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .AutoWidth() - [ - SAssignNew(ErrorText, STextBlock) - .Text(FText::FromString(NodeErrorText)) - .ToolTipText(FText::FromString(NodeErrorText)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .ColorAndOpacity(FLinearColor::Red) - //.ShadowColorAndOpacity(FLinearColor::Black) - ]; - - // Update the error text if needed - ErrorText->SetText(FText::FromString(NodeErrorText)); - ErrorText->SetToolTipText(FText::FromString(NodeErrorTooltip)); - ErrorText->SetColorAndOpacity(NodeErrorColor); - - // Hide the combobox if we have an error - ComboBoxTOPNode->SetVisibility(NodeErrorText.IsEmpty() ? EVisibility::Visible : EVisibility::Hidden); - } - - // TOP Node State - { - FDetailWidgetRow& PDGNodeStateResultRow = TOPNodesGrp.AddWidgetRow(); - DisableIfPDGNotLinked(PDGNodeStateResultRow, InPDGAssetLink); - PDGNodeStateResultRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node State"))) - ]; - - PDGNodeStateResultRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - FString TOPNodeStatus = FString(); - FLinearColor TOPNodeStatusColor = FLinearColor::White; - GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); - return FText::FromString(TOPNodeStatus); - }) - .ColorAndOpacity_Lambda([InPDGAssetLink]() - { - FString TOPNodeStatus = FString(); - FLinearColor TOPNodeStatusColor = FLinearColor::White; - GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); - return FSlateColor(TOPNodeStatusColor); - }) - ]; - } - - // Checkbox: Load Work Item Output Files - { - auto ToolTipLambda = [InPDGAssetLink]() - { - bool bDisabled = false; - if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) - { - bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; - } - - return bDisabled - ? FText::FromString(TEXT("This node has child nodes, the auto-load setting must be set on the child nodes individually.")) - : FText::FromString(TEXT("When enabled, Output files produced by this TOP Node's Work Items will automatically be loaded when cooked.")); - }; - FDetailWidgetRow& PDGNodeAutoLoadRow = TOPNodesGrp.AddWidgetRow(); - - DisableIfPDGNotLinked(PDGNodeAutoLoadRow, InPDGAssetLink); - PDGNodeAutoLoadRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) - return true; - return false; - })); - - PDGNodeAutoLoadRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Auto-Load Work Item Output Files"))) - .ToolTipText_Lambda(ToolTipLambda) - ]; - - TSharedPtr AutoLoadCheckBox; - - PDGNodeAutoLoadRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(AutoLoadCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->GetSelectedTOPNode() - ? (InPDGAssetLink->GetSelectedTOPNode()->bAutoLoad ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - UTOPNode* TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(TOPNode) || TOPNode->bAutoLoad == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNode); - - TOPNode->Modify(); - TOPNode->bAutoLoad = bNewState; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UTOPNode, bAutoLoad), TOPNode); - - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - }) - .ToolTipText_Lambda(ToolTipLambda) - ]; - } - - // Checkbox: Work Item Output Files Visible - { - auto ToolTipLambda = [InPDGAssetLink]() - { - bool bDisabled = false; - if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) - { - bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; - } - - return bDisabled - ? FText::FromString(TEXT("This node has child nodes, visibility of work item outputs must be set on the child nodes individually.")) - : FText::FromString(TEXT("Toggles the visibility of the actors created from this TOP Node's Work Item File Outputs.")); - }; - - FDetailWidgetRow& PDGNodeShowResultRow = TOPNodesGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGNodeShowResultRow, InPDGAssetLink); - PDGNodeShowResultRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) - return true; - - return false; - })); - PDGNodeShowResultRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Work Item Output Files Visible"))) - .ToolTipText_Lambda(ToolTipLambda) - ]; - - TSharedPtr ShowResCheckBox; - PDGNodeShowResultRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(ShowResCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->GetSelectedTOPNode() - ? (InPDGAssetLink->GetSelectedTOPNode()->IsVisibleInLevel() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(TOPNode) || TOPNode->IsVisibleInLevel() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNode); - - TOPNode->Modify(); - TOPNode->SetVisibleInLevel(bNewState); - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(TEXT("bShow"), TOPNode); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - }) - .ToolTipText_Lambda(ToolTipLambda) - ]; - } - - // Buttons: DIRTY NODE / COOK NODE - { - TSharedRef DirtyHBox = SNew(SHorizontalBox); - TSharedPtr CookHBox = SNew(SHorizontalBox); - - TSharedPtr DirtyButton; - TSharedPtr CookButton; - - FDetailWidgetRow& PDGDirtyCookRow = TOPNodesGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); - }) - .WidthOverride(200.0f) - [ - SAssignNew(DirtyButton, SButton) - //.Text(LOCTEXT("DirtyNode", "Dirty Node")) - .ToolTipText(LOCTEXT("DirtyNodeTooltip", "Dirties the selected TOP node and clears its work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - FHoudiniPDGManager::DirtyTOPNode(TOPNode); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - else - { - UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(TOPNode); - } - } - } - - return FReply::Handled(); - }) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNode()) && !InPDGAssetLink->GetSelectedTOPNode()->bHidden) - return true; - return false; - }) - .Content() - [ - SAssignNew(DirtyHBox, SHorizontalBox) - ] - ] - ] - // + SHorizontalBox::Slot() - // .AutoWidth() - // [ - // SNew(SBox) - // .WidthOverride(200.0f) - // [ - // SAssignNew(DirtyButton, SButton) - // .Text(LOCTEXT("DirtyAllTasks", "Dirty All Tasks")) - // .ToolTipText(LOCTEXT("DirtyAllTasksTooltip", "Dirties all tasks/work items on the selected TOP node.")) - // .ContentPadding(FMargin(5.0f, 2.0f)) - // .VAlign(VAlign_Center) - // .HAlign(HAlign_Center) - // .OnClicked_Lambda([InPDGAssetLink]() - // { - // if(InPDGAssetLink->GetSelectedTOPNode()) - // { - // FHoudiniPDGManager::DirtyAllTasksOfTOPNode(*(InPDGAssetLink->GetSelectedTOPNode())); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - // } - // - // return FReply::Handled(); - // }) - // ] - // ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(200.0f) - [ - SAssignNew(CookButton, SButton) - //.Text(LOCTEXT("CookNode", "Cook Node")) - .ToolTipText(LOCTEXT("CookNodeTooltip", "Cooks the selected TOP Node.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode)) - return false; - // Disable Cook Node button if the node is already cooking - return !SelectedNode->bHidden && SelectedNode->NodeState != EPDGNodeState::Cooking && !SelectedNode->AnyWorkItemsPending(); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node)) - { - FHoudiniPDGManager::CookTOPNode(Node); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CookHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr DirtyIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyNodeIconBrush(); - if (DirtyIconBrush.IsValid()) - { - TSharedPtr DirtyImage; - DirtyHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(DirtyImage, SImage) - ] - ]; - - DirtyImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([DirtyIconBrush]() { return DirtyIconBrush.Get(); }))); - } - - DirtyHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("DirtyNode", "Dirty Node")) - ]; - - TSharedPtr CookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - if (CookIconBrush.IsValid()) - { - TSharedPtr CookImage; - CookHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookImage, SImage) - ] - ]; - - CookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookIconBrush]() { return CookIconBrush.Get(); }))); - } - - CookHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookNode", "Cook Node")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: Load Work Item Objects / Unload Work Item Objects - { - TSharedPtr UnloadWorkItemsButton; - TSharedPtr LoadWorkItemsButton; - - FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNodesGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode()); - }) - .WidthOverride(200.0f) - [ - SAssignNew(UnloadWorkItemsButton, SButton) - .Text(LOCTEXT("UnloadWorkItemsForNode", "Unload Work Item Objects")) - .ToolTipText(LOCTEXT("UnloadWorkItemsForNodeTooltip", "Unloads / removes loaded work item results from level. Not undoable: use the \"Load Work Item Objects\" button to reload the results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveLoadedWorkResults) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - // Set the state to ToDelete, PDGManager will delete it when processing work items - TOPNode->SetLoadedWorkResultsToDelete(); - } - else - { - // Delete and unload the result objects and actors now - TOPNode->DeleteWorkResultOutputObjects(); - } - } - } - - return FReply::Handled(); - }) - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(200.0f) - [ - SAssignNew(LoadWorkItemsButton, SButton) - .Text(LOCTEXT("LoadWorkItems", "Load Work Item Objects")) - .ToolTipText(LOCTEXT("LoadWorkItemsForNodeTooltip", "Loads any available but not loaded work items objects (this could include items from a previous cook). Creates output actors. Not undoable: use the \"Unload Work Item Objects\" button to unload/remove loaded work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveNotLoadedWorkResults) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(SelectedNode)) - { - SelectedNode->SetNotLoadedWorkResultsToLoad(true); - } - } - return FReply::Handled(); - }) - ] - ] - ]; - } - - // TOP Node WorkItem Status - { - if (InPDGAssetLink->GetSelectedTOPNode()) - { - FDetailWidgetRow& PDGNodeWorkItemStatsRow = TOPNodesGrp.AddWidgetRow(); - DisableIfPDGNotLinked(PDGNodeWorkItemStatsRow, InPDGAssetLink); - FHoudiniPDGDetails::AddWorkItemStatusWidget( - PDGNodeWorkItemStatsRow, TEXT("TOP Node Work Item Status"), InPDGAssetLink, true); - } - } -} - -void -FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // Repopulate the network and nodes for the assetlink - if (!FHoudiniPDGManager::UpdatePDGAssetLink(InPDGAssetLink)) - return; - - FHoudiniPDGDetails::RefreshUI(InPDGAssetLink, true); -} - -void -FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // Update the workitem stats - InPDGAssetLink->UpdateWorkItemTally(); - - // Update the editor properties - FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, InFullUpdate); -} - -void -FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); - - if (!InPDGAssetLink->bBakeMenuExpanded) - return; - - auto OnBakeButtonClickedLambda = [InPDGAssetLink]() - { - switch (InPDGAssetLink->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(InPDGAssetLink); - // else - FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink); - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceWithBlueprint(InPDGAssetLink); - // else - FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink); - } - break; - // - // case EHoudiniEngineBakeOption::ToFoliage: - // { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(InPDGAssetLink); - // else - // FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InPDGAssetLink); - // } - // break; - // - // case EHoudiniEngineBakeOption::ToWorldOutliner: - // { - // if (InPDGAssetLink->bIsReplace) - // { - // // Todo - // } - // else - // { - // //Todo - // } - // } - // break; - } - - return FReply::Handled(); - }; - - auto OnBakeFolderTextCommittedLambda = [InPDGAssetLink](const FText& Val, ETextCommit::Type TextCommitType) - { - FString NewPathStr = Val.ToString(); - if (NewPathStr.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - //Todo? Check if the new Bake folder path is valid - InPDGAssetLink->Modify(); - InPDGAssetLink->BakeFolder.Path = NewPathStr; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, BakeFolder), InPDGAssetLink); - }; - - // Button Row - FDetailWidgetRow & ButtonRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(ButtonRow, InPDGAssetLink); - - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - - // Bake Button - TSharedRef BakeHBox = SNew(SHorizontalBox); - TSharedPtr BakeButton; - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(15.f, 0.0f, 0.0f, 0.0f) - .MaxWidth(75.0f) - [ - SNew(SBox) - .WidthOverride(75.0f) - [ - SAssignNew(BakeButton, SButton) - //.Text(FText::FromString("Bake")) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - //.ToolTipText(LOCTEXT("HoudiniPDGDetailsBakeButton", "Bake the Houdini PDG TOP Node(s)")) - .ToolTipText_Lambda([InPDGAssetLink]() - { - switch (InPDGAssetLink->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - return LOCTEXT( - "HoudiniEnginePDGBakeButtonBakeToActorToolTip", - "Bake this Houdini PDG Asset's output assets and seperate the output actors from the PDG asset link."); - } - break; - case EHoudiniEngineBakeOption::ToBlueprint: - { - return LOCTEXT( - "HoudiniEnginePDGBakeButtonBakeToBlueprintToolTip", - "Bake this Houdini PDG Asset's output assets to blueprints and remove temporary output actors that no " - "longer has output components from the PDG asset link."); - } - break; - default: - { - return FText(); - } - } - }) - .Visibility(EVisibility::Visible) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda(OnBakeButtonClickedLambda) - .Content() - [ - SAssignNew(BakeHBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); - if (BakeIconBrush.IsValid()) - { - TSharedPtr BakeImage; - BakeHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(BakeImage, SImage) - ] - ]; - - BakeImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([BakeIconBrush]() { return BakeIconBrush.Get(); }))); - } - - BakeHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Bake")) - ]; - - // bake Type ComboBox - TSharedPtr>> TypeComboBox; - - TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeTypeOptionsLabels(); - TSharedPtr IntialSelec; - if (OptionSource) - { - // IntialSelec = (*OptionSource)[(int)InPDGAssetLink->HoudiniEngineBakeOption]; - const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption); - const TSharedPtr* DefaultOption = OptionSource->FindByPredicate( - [DefaultStr](TSharedPtr InStringPtr) - { - return InStringPtr.IsValid() && *InStringPtr == DefaultStr; - } - ); - if (DefaultOption) - IntialSelec = *DefaultOption; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(93.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(93.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid() || SelectType == ESelectInfo::Type::Direct) - return; - - const EHoudiniEngineBakeOption NewOption = - FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->HoudiniEngineBakeOption) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->HoudiniEngineBakeOption = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink, TypeComboBox, OptionSource]() - { - return FText::FromString(FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - // bake selection ComboBox - TSharedPtr>> BakeSelectionComboBox; - - TArray>* PDGBakeSelectionOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeSelectionOptionsLabels(); - TSharedPtr PDGBakeSelectionIntialSelec; - if (PDGBakeSelectionOptionSource) - { - PDGBakeSelectionIntialSelec = (*PDGBakeSelectionOptionSource)[(int)InPDGAssetLink->PDGBakeSelectionOption]; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(163.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(163.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(PDGBakeSelectionOptionSource) - .InitiallySelectedItem(PDGBakeSelectionIntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EPDGBakeSelectionOption NewOption = - FHoudiniEngineEditor::Get().StringToPDGBakeSelectionOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->PDGBakeSelectionOption) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->PDGBakeSelectionOption = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromPDGBakeTargetOption(InPDGAssetLink->PDGBakeSelectionOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; - - // Bake package replacement mode row - FDetailWidgetRow & BakePackageReplaceRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(BakePackageReplaceRow, InPDGAssetLink); - - TSharedRef BakePackageReplaceRowHorizontalBox = SNew(SHorizontalBox); - - BakePackageReplaceRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeLabel", "Replace Mode")) - .ToolTipText( - LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeTooltip", "Replacement mode " - "during baking. Create new assets, using numerical suffixes in package names when necessary, or " - "replace existing assets with matching names. Also replaces the previous bake's output actors in " - "replacement mode vs creating incremental ones.")) - ] - ]; - - // bake package replace mode ComboBox - TSharedPtr>> BakePackageReplaceModeComboBox; - - TArray>* PDGBakePackageReplaceModeOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels(); - TSharedPtr PDGBakePackageReplaceModeInitialSelec; - if (PDGBakePackageReplaceModeOptionSource) - { - const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode); - const TSharedPtr* DefaultOption = PDGBakePackageReplaceModeOptionSource->FindByPredicate( - [DefaultStr](TSharedPtr InStringPtr) - { - return InStringPtr.IsValid() && *InStringPtr == DefaultStr; - } - ); - if (DefaultOption) - PDGBakePackageReplaceModeInitialSelec = *DefaultOption; - } - - BakePackageReplaceRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(163.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(163.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(PDGBakePackageReplaceModeOptionSource) - .InitiallySelectedItem(PDGBakePackageReplaceModeInitialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - const FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EPDGBakePackageReplaceModeOption NewOption = - FHoudiniEngineEditor::Get().StringToPDGBakePackageReplaceModeOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->PDGBakePackageReplaceMode) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->PDGBakePackageReplaceMode = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - BakePackageReplaceRow.WholeRowWidget.Widget = BakePackageReplaceRowHorizontalBox; - - // Bake Folder Row - FDetailWidgetRow & BakeFolderRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(BakeFolderRow, InPDGAssetLink); - - TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT( - "HoudiniEnginePDGBakeFolderTooltip", - "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " - "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " - "plugin settings is used.")) - ] - ]; - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .MaxWidth(235.0) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT( - "HoudiniEnginePDGBakeFolderTooltip", - "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " - "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " - "plugin settings is used.")) - .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) - .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) - ] - ]; - - BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; - - // Add additional bake options - FDetailWidgetRow & AdditionalBakeSettingsRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); - - TSharedPtr CheckBoxAutoBake; - TSharedPtr CheckBoxRecenterBakedActors; - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bRecenterBakedActors = bNewState; - - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bRecenterBakedActors), InPDGAssetLink); - }) - ] - ]; - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxAutoBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake work result object as they are loaded.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded = bNewState; - - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterAllWorkResultObjectsLoaded), InPDGAssetLink); - }) - ] - ]; - - AdditionalBakeSettingsRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText) - : Text(InText) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText, const FString &InToolTip) - : Text(InText) - , ToolTip(InToolTip) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText) - : Text(InText) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText, FString&& InToolTip) - : Text(InText) - , ToolTip(InToolTip) - , Value(InValue) -{ -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPDGManager.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetActor.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniEngineDetails.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "IDetailGroup.h" +#include "IDetailCustomization.h" +#include "PropertyCustomizationHelpers.h" +#include "DetailWidgetRow.h" +#include "ScopedTransaction.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SSpacer.h" +#include "Framework/SlateDelegates.h" +#include "Templates/SharedPointer.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define HOUDINI_ENGINE_UI_SECTION_PDG_BAKE 2 + +void +FHoudiniPDGDetails::CreateWidget( + IDetailCategoryBuilder& HouPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // PDG ASSET + FHoudiniPDGDetails::AddPDGAssetWidget(HouPDGCategory, InPDGAssetLink); + + // TOP NETWORKS + FHoudiniPDGDetails::AddTOPNetworkWidget(HouPDGCategory, InPDGAssetLink); + + // PDG EVENT MESSAGES +} + + +void +FHoudiniPDGDetails::AddPDGAssetWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // PDG STATUS ROW + AddPDGAssetStatus(InPDGCategory, InPDGAssetLink); + + // Commandlet Status row + AddPDGCommandletStatus(InPDGCategory, FHoudiniEngine::Get().GetPDGCommandletStatus()); + + // REFRESH / RESET Buttons + { + TSharedRef RefreshHBox = SNew(SHorizontalBox); + TSharedPtr ResetHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGRefreshResetRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Refresh", "Refresh")) + .ToolTipText(LOCTEXT("RefreshTooltip", "Refreshes infos displayed by the the PDG Asset Link")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked_Lambda([InPDGAssetLink]() + { + FHoudiniPDGDetails::RefreshPDGAssetLink(InPDGAssetLink); + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(RefreshHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Reset", "Reset")) + .ToolTipText(LOCTEXT("ResetTooltip", "Resets the PDG Asset Link")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked_Lambda([InPDGAssetLink]() + { + // TODO: RESET USELESS? + FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(ResetHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr RefreshIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGRefreshIconBrush(); + if (RefreshIconBrush.IsValid()) + { + TSharedPtr RefreshImage; + RefreshHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RefreshImage, SImage) + ] + ]; + + RefreshImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([RefreshIconBrush]() { return RefreshIconBrush.Get(); }))); + } + + RefreshHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Refresh", "Refresh")) + ]; + + TSharedPtr ResetIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGResetIconBrush(); + if (ResetIconBrush.IsValid()) + { + TSharedPtr ResetImage; + ResetHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(ResetImage, SImage) + ] + ]; + + ResetImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([ResetIconBrush]() { return ResetIconBrush.Get(); }))); + } + + ResetHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Reset", "Reset")) + ]; + } + + // TOP NODE FILTER + { + FText Tooltip = FText::FromString(TEXT("When enabled, the TOP Node Filter will only display the TOP Nodes found in the current network that start with the filter prefix. Disabling the Filter will display all of the TOP Network's TOP Nodes.")); + // Lambda for changing the filter value + auto ChangeTOPNodeFilter = [InPDGAssetLink](const FString& NewValue) + { + if (InPDGAssetLink->TOPNodeFilter.Equals(NewValue)) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->TOPNodeFilter = NewValue; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter), InPDGAssetLink); + }; + + FDetailWidgetRow& PDGFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGFilterRow, InPDGAssetLink); + PDGFilterRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox enable filter + SNew(SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bUseTOPNodeFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (InPDGAssetLink->bUseTOPNodeFilter == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bUseTOPNodeFilter = bNewState; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node Filter"))) + .ToolTipText(Tooltip) + ]; + + PDGFilterRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .ToolTipText(Tooltip) + .Text_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return FText(); + return FText::FromString(InPDGAssetLink->TOPNodeFilter); + }) + .OnTextCommitted_Lambda([ChangeTOPNodeFilter](const FText& Val, ETextCommit::Type TextCommitType) + { + ChangeTOPNodeFilter(Val.ToString()); + }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([=]() + { + FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER); + ChangeTOPNodeFilter(DefaultFilter); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + } + + // TOP OUTPUT FILTER + { + // Lambda for changing the filter value + FText Tooltip = FText::FromString(TEXT("When enabled, the Work Item Output Files created for the TOP Nodes found in the current network that start with the filter prefix will be automatically loaded int the world after being cooked.")); + auto ChangeTOPOutputFilter = [InPDGAssetLink](const FString& NewValue) + { + if (InPDGAssetLink->TOPOutputFilter.Equals(NewValue)) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->TOPOutputFilter = NewValue; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter), InPDGAssetLink); + }; + + FDetailWidgetRow& PDGOutputFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGOutputFilterRow, InPDGAssetLink); + + PDGOutputFilterRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox enable filter + SNew(SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bUseTOPOutputFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (InPDGAssetLink->bUseTOPOutputFilter == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bUseTOPOutputFilter = bNewState; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Output Filter"))) + .ToolTipText(Tooltip) + ]; + + PDGOutputFilterRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return FText(); + return FText::FromString(InPDGAssetLink->TOPOutputFilter); + }) + .OnTextCommitted_Lambda([ChangeTOPOutputFilter](const FText& Val, ETextCommit::Type TextCommitType) + { + ChangeTOPOutputFilter(Val.ToString()); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([ChangeTOPOutputFilter]() + { + FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER); + ChangeTOPOutputFilter(DefaultFilter); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + } + + // Checkbox: Autocook + { + FText Tooltip = FText::FromString(TEXT("When enabled, the selected TOP Network's output will automatically cook after succesfully cooking the PDG Asset Link HDA.")); + FDetailWidgetRow& PDGAutocookRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGAutocookRow, InPDGAssetLink); + PDGAutocookRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Auto-cook"))) + .ToolTipText(Tooltip) + ]; + + TSharedPtr AutoCookCheckBox; + PDGAutocookRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(AutoCookCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bAutoCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (!InPDGAssetLink || InPDGAssetLink->bAutoCook == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bAutoCook = bNewState; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bAutoCook), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ]; + } + // Output parent actor selector + { + IDetailPropertyRow* PDGOutputParentActorRow = InPDGCategory.AddExternalObjectProperty({ InPDGAssetLink }, "OutputParentActor"); + if (PDGOutputParentActorRow) + { + TAttribute PDGOutputParentActorRowEnabled; + BindDisableIfPDGNotLinked(PDGOutputParentActorRowEnabled, InPDGAssetLink); + PDGOutputParentActorRow->IsEnabled(PDGOutputParentActorRowEnabled); + TSharedPtr NameWidget; + TSharedPtr ValueWidget; + PDGOutputParentActorRow->GetDefaultWidgets(NameWidget, ValueWidget); + PDGOutputParentActorRow->DisplayName(FText::FromString(TEXT("Output Parent Actor"))); + PDGOutputParentActorRow->ToolTip(FText::FromString( + TEXT("The PDG Output Actors will be created under this parent actor. If not set, then the PDG Output Actors will be created under a new folder."))); + } + } + + // Add bake widgets for PDG output + CreatePDGBakeWidgets(InPDGCategory, InPDGAssetLink); + + // TODO: move this to a better place: the baking code is in HoudiniEngineEditor, the PDG manager (that knows about + // when work object results are loaded is in HoudiniEngine and the PDGAssetLink is in HoudiniEngineRuntime). So + // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? + if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) + InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); + InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded); + + // WORK ITEM STATUS + { + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGStatusRow, InPDGAssetLink); + FHoudiniPDGDetails::AddWorkItemStatusWidget( + PDGStatusRow, TEXT("Asset Work Item Status"), InPDGAssetLink, false); + } +} + +bool +FHoudiniPDGDetails::GetPDGStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor) +{ + OutPDGStatusString = FString(); + OutPDGStatusColor = FLinearColor::White; + + if (!IsValid(InPDGAssetLink)) + return false; + + switch (InPDGAssetLink->LinkState) + { + case EPDGLinkState::Linked: + OutPDGStatusString = TEXT("PDG is READY"); + OutPDGStatusColor = FLinearColor::Green; + break; + case EPDGLinkState::Linking: + OutPDGStatusString = TEXT("PDG is Linking"); + OutPDGStatusColor = FLinearColor::Yellow; + break; + case EPDGLinkState::Error_Not_Linked: + OutPDGStatusString = TEXT("PDG is ERRORED"); + OutPDGStatusColor = FLinearColor::Red; + break; + case EPDGLinkState::Inactive: + OutPDGStatusString = TEXT("PDG is INACTIVE"); + OutPDGStatusColor = FLinearColor::White; + break; + default: + return false; + } + + return true; +} + +void +FHoudiniPDGDetails::AddPDGAssetStatus( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink) +{ + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + FString PDGStatusString; + FLinearColor PDGStatusColor; + GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); + return FText::FromString(PDGStatusString); + }) + .ColorAndOpacity_Lambda([InPDGAssetLink]() + { + FString PDGStatusString; + FLinearColor PDGStatusColor; + GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); + return FSlateColor(PDGStatusColor); + }) + ] + ]; +} + +void +FHoudiniPDGDetails::GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor) +{ + OutStatusString = FString(); + OutStatusColor = FLinearColor::White; + switch (FHoudiniEngine::Get().GetPDGCommandletStatus()) + { + case EHoudiniBGEOCommandletStatus::Connected: + OutStatusString = TEXT("Async importer is CONNECTED"); + OutStatusColor = FLinearColor::Green; + break; + case EHoudiniBGEOCommandletStatus::Running: + OutStatusString = TEXT("Async importer is Running"); + OutStatusColor = FLinearColor::Yellow; + break; + case EHoudiniBGEOCommandletStatus::Crashed: + OutStatusString = TEXT("Async importer has CRASHED"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniBGEOCommandletStatus::NotStarted: + OutStatusString = TEXT("Async importer is NOT STARTED"); + OutStatusColor = FLinearColor::White; + break; + } +} + +void +FHoudiniPDGDetails::AddPDGCommandletStatus( + IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus) +{ + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Visibility_Lambda([]() + { + const UHoudiniRuntimeSettings* Settings = GetDefault(); + if (IsValid(Settings)) + { + return FHoudiniEngineCommands::IsPDGCommandletEnabled() ? EVisibility::Visible : EVisibility::Collapsed; + } + + return EVisibility::Visible; + }) + .Text_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetPDGCommandletStatus(StatusString, StatusColor); + return FText::FromString(StatusString); + }) + .ColorAndOpacity_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetPDGCommandletStatus(StatusString, StatusColor); + return FSlateColor(StatusColor); + }) + ] + ]; +} + +bool +FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( + UHoudiniPDGAssetLink* InAssetLink, + bool bInForSelectedNode, + const FString& InTallyItemString, + int32& OutValue, + FLinearColor& OutColor) +{ + OutValue = 0; + OutColor = FLinearColor::White; + + if (!IsValid(InAssetLink)) + return false; + + bool bFound = false; + const FWorkItemTallyBase* TallyPtr = nullptr; + if (bInForSelectedNode) + { + UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); + if (TOPNode && !TOPNode->bHidden) + TallyPtr = &(TOPNode->GetWorkItemTally()); + } + else + TallyPtr = &(InAssetLink->WorkItemTally); + + if (TallyPtr) + { + if (InTallyItemString == TEXT("WAITING")) + { + // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI + OutValue = TallyPtr->NumWaitingWorkItems() + TallyPtr->NumScheduledWorkItems(); + OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("COOKING")) + { + OutValue = TallyPtr->NumCookingWorkItems(); + OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("COOKED")) + { + OutValue = TallyPtr->NumCookedWorkItems(); + OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("FAILED")) + { + OutValue = TallyPtr->NumErroredWorkItems(); + OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; + bFound = true; + } + } + + return bFound; +} + +void +FHoudiniPDGDetails::AddWorkItemStatusWidget( + FDetailWidgetRow& InRow, const FString& InTitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode) +{ + auto AddGridBox = [InAssetLink, bInForSelectedNode](const FString& Title) -> SHorizontalBox::FSlot& + { + return SHorizontalBox::Slot() + .MaxWidth(500.0f) + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(FMargin(1.0f, 2.0f)) + [ + SNew(SBorder) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .BorderBackgroundColor(FSlateColor(FLinearColor(0.6, 0.6, 0.6))) + .Padding(FMargin(1.0f, 5.0f)) + [ + SNew(SBox) + .WidthOverride(95.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(Title)) + .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FSlateColor(Color); + }) + ] + ] + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(FMargin(1.0f, 2.0f)) + [ + SNew(SBorder) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .BorderBackgroundColor(FSlateColor(FLinearColor(0.8, 0.8, 0.8))) + .Padding(FMargin(1.0f, 5.0f)) + [ + SNew(SBox) + .WidthOverride(95.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FText::AsNumber(Value); + }) + .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FSlateColor(Color); + }) + ] + ] + ] + ]; + }; + + InRow.WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f) + .AutoWidth() + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + [ + SNew(SSpacer) + ] + + SVerticalBox::Slot() + .AutoHeight() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(FMargin(0.0f, 2.0f)) + [ + SNew(STextBlock) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .Text(FText::FromString(InTitleString)) + + ] + + SVerticalBox::Slot() + .AutoHeight() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(FMargin(0.0f, 2.0f)) + [ + SNew(SHorizontalBox) + + AddGridBox(TEXT("WAITING")) + + AddGridBox(TEXT("COOKING")) + + AddGridBox(TEXT("COOKED")) + + AddGridBox(TEXT("FAILED")) + ] + + SVerticalBox::Slot() + [ + SNew(SSpacer) + ] + ] + ]; +} + + +void +FHoudiniPDGDetails::AddTOPNetworkWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink ) +{ + if (!InPDGAssetLink->GetSelectedTOPNetwork()) + return; + + if (InPDGAssetLink->AllTOPNetworks.Num() <= 0) + return; + + TOPNetworksPtr.Reset(); + + FString GroupLabel = TEXT("TOP Networks"); + IDetailGroup& TOPNetWorkGrp = InPDGCategory.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), false, true); + + // Combobox: TOP Network + { + FDetailWidgetRow& PDGTOPNetRow = TOPNetWorkGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGTOPNetRow, InPDGAssetLink); + PDGTOPNetRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Network"))) + ]; + + // Fill the TOP Networks SharedString array + TOPNetworksPtr.SetNum(InPDGAssetLink->AllTOPNetworks.Num()); + for(int32 Idx = 0; Idx < InPDGAssetLink->AllTOPNetworks.Num(); Idx++) + { + const UTOPNetwork* Network = InPDGAssetLink->AllTOPNetworks[Idx]; + if (!IsValid(Network)) + { + TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( + Idx, + TEXT("Invalid"), + TEXT("Invalid") + )); + } + else + { + TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( + Idx, + FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Network->NodeName, Network->NodePath), + Network->NodePath + )); + } + } + + if(TOPNetworksPtr.Num() <= 0) + TOPNetworksPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, "----"))); + + // Lambda for selecting another TOPNet + auto OnTOPNetChanged = [InPDGAssetLink](TSharedPtr InNewChoice) + { + if (!InNewChoice.IsValid()) + return; + + const int32 NewChoice = InNewChoice->Value; + int32 NewSelectedIndex = -1; + if (InPDGAssetLink->AllTOPNetworks.IsValidIndex(NewChoice)) + NewSelectedIndex = NewChoice; + + if (InPDGAssetLink->SelectedTOPNetworkIndex == NewSelectedIndex) + return; + + if (NewSelectedIndex < 0) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->SelectedTOPNetworkIndex = NewSelectedIndex; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex), InPDGAssetLink); + }; + + TSharedPtr HorizontalBoxTOPNet; + TSharedPtr>> ComboBoxTOPNet; + int32 SelectedIndex = TOPNetworksPtr.IndexOfByPredicate([InPDGAssetLink](const TSharedPtr& InEntry) + { + return InEntry.IsValid() && InEntry->Value == InPDGAssetLink->SelectedTOPNetworkIndex; + }); + if (SelectedIndex < 0) + SelectedIndex = 0; + + PDGTOPNetRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .FillWidth(300.f) + .MaxWidth(300.f) + [ + SAssignNew(ComboBoxTOPNet, SComboBox>) + .OptionsSource(&TOPNetworksPtr) + .InitiallySelectedItem(TOPNetworksPtr[SelectedIndex]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); + const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryToolTip) + .Margin(2.0f) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnTOPNetChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnTOPNetChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString(InPDGAssetLink->GetSelectedTOPNetworkName()); + }) + .ToolTipText_Lambda([InPDGAssetLink]() + { + UTOPNetwork const * const Network = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(Network)) + { + if (!Network->NodePath.IsEmpty()) + return FText::FromString(Network->NodePath); + else + return FText::FromString(Network->NodeName); + } + else + { + return FText(); + } + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + // Buttons: DIRTY ALL / COOK OUTPUT + { + TSharedRef DirtyAllHBox = SNew(SHorizontalBox); + TSharedPtr CookOutHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("DirtyAll", "Dirty All")) + .ToolTipText(LOCTEXT("DirtyAllTooltip", "Dirty all TOP nodes in the selected TOP network and clears all of its work item results.")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork()); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(TOPNetwork)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + FHoudiniPDGManager::DirtyAll(TOPNetwork); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + else + { + UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(TOPNetwork); + } + } + } + + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(DirtyAllHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("CookOut", "Cook Output")) + .ToolTipText(LOCTEXT("CookOutTooltip", "Cooks the output nodes of the selected TOP network")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNet)) + return false; + + // Disable if there any nodes in the network that are already cooking + return !SelectedTOPNet->AnyWorkItemsPending(); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::CookOutput(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CookOutHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr DirtyAllIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyAllIconBrush(); + if (DirtyAllIconBrush.IsValid()) + { + TSharedPtr DirtyAllImage; + DirtyAllHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(DirtyAllImage, SImage) + ] + ]; + + DirtyAllImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([DirtyAllIconBrush]() { return DirtyAllIconBrush.Get(); }))); + } + + DirtyAllHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("DirtyAll", "Dirty All")) + ]; + + TSharedPtr CookOutIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + if (CookOutIconBrush.IsValid()) + { + TSharedPtr CookOutImage; + CookOutHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookOutImage, SImage) + ] + ]; + + CookOutImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookOutIconBrush]() { return CookOutIconBrush.Get(); }))); + } + + CookOutHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookOut", "Cook Output")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: PAUSE COOK / CANCEL COOK + { + TSharedRef PauseHBox = SNew(SHorizontalBox); + TSharedPtr CancelHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Pause", "Pause Cook")) + .ToolTipText(LOCTEXT("PauseTooltip", "Pauses cooking for the selected TOP Network")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::PauseCook(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(PauseHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Cancel", "Cancel Cook")) + .ToolTipText(LOCTEXT("CancelTooltip", "Cancels cooking the selected TOP network")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::CancelCook(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CancelHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr PauseIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGPauseIconBrush(); + if (PauseIconBrush.IsValid()) + { + TSharedPtr PauseImage; + PauseHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(PauseImage, SImage) + ] + ]; + + PauseImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([PauseIconBrush]() { return PauseIconBrush.Get(); }))); + } + + PauseHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Pause", "Pause Cook")) + ]; + + TSharedPtr CancelIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGCancelIconBrush(); + if (CancelIconBrush.IsValid()) + { + TSharedPtr CancelImage; + CancelHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CancelImage, SImage) + ] + ]; + + CancelImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CancelIconBrush]() { return CancelIconBrush.Get(); }))); + } + + CancelHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Cancel", "Cancel Cook")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: Unload Work Item Objects + { + FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork(); }) + .WidthOverride(200.0f) + [ + SNew(SButton) + .Text(LOCTEXT("UnloadWorkItemsForNetwork", "Unload All Work Item Objects")) + .ToolTipText(LOCTEXT("UnloadWorkItemsForNetworkTooltip", "Unloads / removes loaded work item results from level for all nodes in this network. Not undoable: use the \"Load Work Item Objects\" button on the individual TOP nodes to reload work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNetwork* const SelectedNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!IsValid(SelectedNet) || + INDEX_NONE == SelectedNet->AllTOPNodes.IndexOfByPredicate([](const UTOPNode* InNode) { return IsValid(InNode) && InNode->bCachedHaveLoadedWorkResults; })) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNetwork* const TOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(TOPNet)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + // Set the state to ToDelete, PDGManager will delete it when processing work items + TOPNet->SetLoadedWorkResultsToDelete(); + } + else + { + // Delete and unload the result objects and actors now + TOPNet->DeleteWorkResultOutputObjects(); + } + } + } + + return FReply::Handled(); + }) + ] + ] + ]; + } + + // TOP NODE WIDGETS + FHoudiniPDGDetails::AddTOPNodeWidget(TOPNetWorkGrp, InPDGAssetLink); +} + +bool +FHoudiniPDGDetails::GetSelectedTOPNodeStatusAndColor(UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor) +{ + OutTOPNodeStatus = FString(); + OutTOPNodeStatusColor = FLinearColor::White; + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode) && !TOPNode->bHidden) + { + OutTOPNodeStatus = UHoudiniPDGAssetLink::GetTOPNodeStatus(TOPNode); + OutTOPNodeStatusColor = UHoudiniPDGAssetLink::GetTOPNodeStatusColor(TOPNode); + + return true; + } + } + + return false; +} + +void +FHoudiniPDGDetails::AddTOPNodeWidget( + IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink ) +{ + if (!InPDGAssetLink->GetSelectedTOPNetwork()) + return; + + FString GroupLabel = TEXT("TOP Nodes"); + IDetailGroup& TOPNodesGrp = InGroup.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), true); + + // Combobox: TOP Node + { + FDetailWidgetRow& PDGTOPNodeRow = TOPNodesGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGTOPNodeRow, InPDGAssetLink); + PDGTOPNodeRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node"))) + ]; + + // Update the TOP Node SharedString + TOPNodesPtr.Reset(); + TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, LOCTEXT("ComboBoxEntryNoSelectedTOPNode", "- Select -").ToString()))); + const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNet)) + { + const int32 NumTOPNodes = SelectedTOPNet->AllTOPNodes.Num(); + for (int32 Idx = 0; Idx < NumTOPNodes; Idx++) + { + const UTOPNode* const Node = SelectedTOPNet->AllTOPNodes[Idx]; + if (!IsValid(Node) || Node->bHidden) + continue; + + TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip( + Idx, + FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Node->NodeName, Node->NodePath), + Node->NodePath + ))); + } + } + + FString NodeErrorText = FString(); + FString NodeErrorTooltip = FString(); + FLinearColor NodeErrorColor = FLinearColor::White; + if (!IsValid(SelectedTOPNet) || SelectedTOPNet->AllTOPNodes.Num() <= 0) + { + NodeErrorText = TEXT("No valid TOP Node found!"); + NodeErrorTooltip = TEXT("There is no valid TOP Node found in the selected TOP Network!"); + NodeErrorColor = FLinearColor::Red; + } + else if(TOPNodesPtr.Num() <= 0) + { + NodeErrorText = TEXT("No visible TOP Node found!"); + NodeErrorTooltip = TEXT("No visible TOP Node found, all nodes in this network are hidden. Please update your TOP Node Filter."); + NodeErrorColor = FLinearColor::Yellow; + } + + // Lambda for selecting a TOPNode + auto OnTOPNodeChanged = [InPDGAssetLink](TSharedPtr InNewChoice) + { + UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!InNewChoice.IsValid() || !IsValid(TOPNetwork)) + return; + + const int32 NewChoice = InNewChoice->Value; + int32 NewSelectedIndex = INDEX_NONE; + if (TOPNetwork->AllTOPNodes.IsValidIndex(NewChoice)) + NewSelectedIndex = NewChoice; + + if (TOPNetwork->SelectedTOPIndex != NewSelectedIndex) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNetwork); + + TOPNetwork->Modify(); + TOPNetwork->SelectedTOPIndex = NewSelectedIndex; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UTOPNetwork, SelectedTOPIndex), TOPNetwork); + } + }; + + TSharedPtr HorizontalBoxTOPNode; + TSharedPtr>> ComboBoxTOPNode; + int32 SelectedIndex = 0; + UTOPNetwork* const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNetwork) && SelectedTOPNetwork->SelectedTOPIndex >= 0) + { + //SelectedIndex = InPDGAssetLink->GetSelectedTOPNetwork()->SelectedTOPIndex; + + // We need to match the selection by the index in the AllTopNodes array + // Because of the nodefilter, it is possible that the selected index does not match the index in TOPNodesPtr + const int32 SelectedTOPNodeIndex = SelectedTOPNetwork->SelectedTOPIndex; + // Find the matching UI index + for (int32 UIIndex = 0; UIIndex < TOPNodesPtr.Num(); UIIndex++) + { + if (TOPNodesPtr[UIIndex] && TOPNodesPtr[UIIndex]->Value == SelectedTOPNodeIndex) + { + // We found the UI Index that matches the current TOP Node! + SelectedIndex = UIIndex; + break; + } + } + } + + TSharedPtr ErrorText; + + PDGTOPNodeRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .FillWidth(300.f) + .MaxWidth(300.f) + [ + SAssignNew(ComboBoxTOPNode, SComboBox>) + .OptionsSource(&TOPNodesPtr) + .InitiallySelectedItem(TOPNodesPtr[SelectedIndex]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); + const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryToolTip) + .Margin(2.0f) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnTOPNodeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnTOPNodeChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink, ComboBoxTOPNode, Options = TOPNodesPtr]() + { + if (IsValid(InPDGAssetLink)) + return FText::FromString(InPDGAssetLink->GetSelectedTOPNodeName()); + else + return FText(); + }) + .ToolTipText_Lambda([InPDGAssetLink]() + { + UTOPNode const * const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (!TOPNode->NodePath.IsEmpty()) + return FText::FromString(TOPNode->NodePath); + else + return FText::FromString(TOPNode->NodeName); + } + else + { + return FText(); + } + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .AutoWidth() + [ + SAssignNew(ErrorText, STextBlock) + .Text(FText::FromString(NodeErrorText)) + .ToolTipText(FText::FromString(NodeErrorText)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .ColorAndOpacity(FLinearColor::Red) + //.ShadowColorAndOpacity(FLinearColor::Black) + ]; + + // Update the error text if needed + ErrorText->SetText(FText::FromString(NodeErrorText)); + ErrorText->SetToolTipText(FText::FromString(NodeErrorTooltip)); + ErrorText->SetColorAndOpacity(NodeErrorColor); + + // Hide the combobox if we have an error + ComboBoxTOPNode->SetVisibility(NodeErrorText.IsEmpty() ? EVisibility::Visible : EVisibility::Hidden); + } + + // TOP Node State + { + FDetailWidgetRow& PDGNodeStateResultRow = TOPNodesGrp.AddWidgetRow(); + DisableIfPDGNotLinked(PDGNodeStateResultRow, InPDGAssetLink); + PDGNodeStateResultRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node State"))) + ]; + + PDGNodeStateResultRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + FString TOPNodeStatus = FString(); + FLinearColor TOPNodeStatusColor = FLinearColor::White; + GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); + return FText::FromString(TOPNodeStatus); + }) + .ColorAndOpacity_Lambda([InPDGAssetLink]() + { + FString TOPNodeStatus = FString(); + FLinearColor TOPNodeStatusColor = FLinearColor::White; + GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); + return FSlateColor(TOPNodeStatusColor); + }) + ]; + } + + // Checkbox: Load Work Item Output Files + { + auto ToolTipLambda = [InPDGAssetLink]() + { + bool bDisabled = false; + if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) + { + bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; + } + + return bDisabled + ? FText::FromString(TEXT("This node has child nodes, the auto-load setting must be set on the child nodes individually.")) + : FText::FromString(TEXT("When enabled, Output files produced by this TOP Node's Work Items will automatically be loaded when cooked.")); + }; + FDetailWidgetRow& PDGNodeAutoLoadRow = TOPNodesGrp.AddWidgetRow(); + + DisableIfPDGNotLinked(PDGNodeAutoLoadRow, InPDGAssetLink); + PDGNodeAutoLoadRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) + return true; + return false; + })); + + PDGNodeAutoLoadRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Auto-Load Work Item Output Files"))) + .ToolTipText_Lambda(ToolTipLambda) + ]; + + TSharedPtr AutoLoadCheckBox; + + PDGNodeAutoLoadRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(AutoLoadCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->GetSelectedTOPNode() + ? (InPDGAssetLink->GetSelectedTOPNode()->bAutoLoad ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + UTOPNode* TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(TOPNode) || TOPNode->bAutoLoad == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNode); + + TOPNode->Modify(); + TOPNode->bAutoLoad = bNewState; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UTOPNode, bAutoLoad), TOPNode); + + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + }) + .ToolTipText_Lambda(ToolTipLambda) + ]; + } + + // Checkbox: Work Item Output Files Visible + { + auto ToolTipLambda = [InPDGAssetLink]() + { + bool bDisabled = false; + if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) + { + bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; + } + + return bDisabled + ? FText::FromString(TEXT("This node has child nodes, visibility of work item outputs must be set on the child nodes individually.")) + : FText::FromString(TEXT("Toggles the visibility of the actors created from this TOP Node's Work Item File Outputs.")); + }; + + FDetailWidgetRow& PDGNodeShowResultRow = TOPNodesGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGNodeShowResultRow, InPDGAssetLink); + PDGNodeShowResultRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) + return true; + + return false; + })); + PDGNodeShowResultRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Work Item Output Files Visible"))) + .ToolTipText_Lambda(ToolTipLambda) + ]; + + TSharedPtr ShowResCheckBox; + PDGNodeShowResultRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(ShowResCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->GetSelectedTOPNode() + ? (InPDGAssetLink->GetSelectedTOPNode()->IsVisibleInLevel() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(TOPNode) || TOPNode->IsVisibleInLevel() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNode); + + TOPNode->Modify(); + TOPNode->SetVisibleInLevel(bNewState); + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(TEXT("bShow"), TOPNode); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + }) + .ToolTipText_Lambda(ToolTipLambda) + ]; + } + + // Buttons: DIRTY NODE / COOK NODE + { + TSharedRef DirtyHBox = SNew(SHorizontalBox); + TSharedPtr CookHBox = SNew(SHorizontalBox); + + TSharedPtr DirtyButton; + TSharedPtr CookButton; + + FDetailWidgetRow& PDGDirtyCookRow = TOPNodesGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); + }) + .WidthOverride(200.0f) + [ + SAssignNew(DirtyButton, SButton) + //.Text(LOCTEXT("DirtyNode", "Dirty Node")) + .ToolTipText(LOCTEXT("DirtyNodeTooltip", "Dirties the selected TOP node and clears its work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + FHoudiniPDGManager::DirtyTOPNode(TOPNode); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + else + { + UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(TOPNode); + } + } + } + + return FReply::Handled(); + }) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNode()) && !InPDGAssetLink->GetSelectedTOPNode()->bHidden) + return true; + return false; + }) + .Content() + [ + SAssignNew(DirtyHBox, SHorizontalBox) + ] + ] + ] + // + SHorizontalBox::Slot() + // .AutoWidth() + // [ + // SNew(SBox) + // .WidthOverride(200.0f) + // [ + // SAssignNew(DirtyButton, SButton) + // .Text(LOCTEXT("DirtyAllTasks", "Dirty All Tasks")) + // .ToolTipText(LOCTEXT("DirtyAllTasksTooltip", "Dirties all tasks/work items on the selected TOP node.")) + // .ContentPadding(FMargin(5.0f, 2.0f)) + // .VAlign(VAlign_Center) + // .HAlign(HAlign_Center) + // .OnClicked_Lambda([InPDGAssetLink]() + // { + // if(InPDGAssetLink->GetSelectedTOPNode()) + // { + // FHoudiniPDGManager::DirtyAllTasksOfTOPNode(*(InPDGAssetLink->GetSelectedTOPNode())); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + // } + // + // return FReply::Handled(); + // }) + // ] + // ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(200.0f) + [ + SAssignNew(CookButton, SButton) + //.Text(LOCTEXT("CookNode", "Cook Node")) + .ToolTipText(LOCTEXT("CookNodeTooltip", "Cooks the selected TOP Node.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode)) + return false; + // Disable Cook Node button if the node is already cooking + return !SelectedNode->bHidden && SelectedNode->NodeState != EPDGNodeState::Cooking && !SelectedNode->AnyWorkItemsPending(); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node)) + { + FHoudiniPDGManager::CookTOPNode(Node); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CookHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr DirtyIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyNodeIconBrush(); + if (DirtyIconBrush.IsValid()) + { + TSharedPtr DirtyImage; + DirtyHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(DirtyImage, SImage) + ] + ]; + + DirtyImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([DirtyIconBrush]() { return DirtyIconBrush.Get(); }))); + } + + DirtyHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("DirtyNode", "Dirty Node")) + ]; + + TSharedPtr CookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + if (CookIconBrush.IsValid()) + { + TSharedPtr CookImage; + CookHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookImage, SImage) + ] + ]; + + CookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookIconBrush]() { return CookIconBrush.Get(); }))); + } + + CookHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookNode", "Cook Node")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: Load Work Item Objects / Unload Work Item Objects + { + TSharedPtr UnloadWorkItemsButton; + TSharedPtr LoadWorkItemsButton; + + FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNodesGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode()); + }) + .WidthOverride(200.0f) + [ + SAssignNew(UnloadWorkItemsButton, SButton) + .Text(LOCTEXT("UnloadWorkItemsForNode", "Unload Work Item Objects")) + .ToolTipText(LOCTEXT("UnloadWorkItemsForNodeTooltip", "Unloads / removes loaded work item results from level. Not undoable: use the \"Load Work Item Objects\" button to reload the results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveLoadedWorkResults) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + // Set the state to ToDelete, PDGManager will delete it when processing work items + TOPNode->SetLoadedWorkResultsToDelete(); + } + else + { + // Delete and unload the result objects and actors now + TOPNode->DeleteWorkResultOutputObjects(); + } + } + } + + return FReply::Handled(); + }) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(200.0f) + [ + SAssignNew(LoadWorkItemsButton, SButton) + .Text(LOCTEXT("LoadWorkItems", "Load Work Item Objects")) + .ToolTipText(LOCTEXT("LoadWorkItemsForNodeTooltip", "Loads any available but not loaded work items objects (this could include items from a previous cook). Creates output actors. Not undoable: use the \"Unload Work Item Objects\" button to unload/remove loaded work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveNotLoadedWorkResults) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(SelectedNode)) + { + SelectedNode->SetNotLoadedWorkResultsToLoad(true); + } + } + return FReply::Handled(); + }) + ] + ] + ]; + } + + // TOP Node WorkItem Status + { + if (InPDGAssetLink->GetSelectedTOPNode()) + { + FDetailWidgetRow& PDGNodeWorkItemStatsRow = TOPNodesGrp.AddWidgetRow(); + DisableIfPDGNotLinked(PDGNodeWorkItemStatsRow, InPDGAssetLink); + FHoudiniPDGDetails::AddWorkItemStatusWidget( + PDGNodeWorkItemStatsRow, TEXT("TOP Node Work Item Status"), InPDGAssetLink, true); + } + } +} + +void +FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // Repopulate the network and nodes for the assetlink + if (!FHoudiniPDGManager::UpdatePDGAssetLink(InPDGAssetLink)) + return; + + FHoudiniPDGDetails::RefreshUI(InPDGAssetLink, true); +} + +void +FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // Update the workitem stats + InPDGAssetLink->UpdateWorkItemTally(); + + // Update the editor properties + FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, InFullUpdate); +} + +void +FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); + + if (!InPDGAssetLink->bBakeMenuExpanded) + return; + + auto OnBakeButtonClickedLambda = [InPDGAssetLink]() + { + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(InPDGAssetLink); + // else + FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->PDGBakeSelectionOption, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceWithBlueprint(InPDGAssetLink); + // else + FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink, InPDGAssetLink->PDGBakeSelectionOption, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); + } + break; + // + // case EHoudiniEngineBakeOption::ToFoliage: + // { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(InPDGAssetLink); + // else + // FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InPDGAssetLink); + // } + // break; + // + // case EHoudiniEngineBakeOption::ToWorldOutliner: + // { + // if (InPDGAssetLink->bIsReplace) + // { + // // Todo + // } + // else + // { + // //Todo + // } + // } + // break; + } + + return FReply::Handled(); + }; + + auto OnBakeFolderTextCommittedLambda = [InPDGAssetLink](const FText& Val, ETextCommit::Type TextCommitType) + { + FString NewPathStr = Val.ToString(); + if (NewPathStr.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + //Todo? Check if the new Bake folder path is valid + InPDGAssetLink->Modify(); + InPDGAssetLink->BakeFolder.Path = NewPathStr; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, BakeFolder), InPDGAssetLink); + }; + + // Button Row + FDetailWidgetRow & ButtonRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(ButtonRow, InPDGAssetLink); + + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + + // Bake Button + TSharedRef BakeHBox = SNew(SHorizontalBox); + TSharedPtr BakeButton; + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(15.f, 0.0f, 0.0f, 0.0f) + .MaxWidth(75.0f) + [ + SNew(SBox) + .WidthOverride(75.0f) + [ + SAssignNew(BakeButton, SButton) + //.Text(FText::FromString("Bake")) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + //.ToolTipText(LOCTEXT("HoudiniPDGDetailsBakeButton", "Bake the Houdini PDG TOP Node(s)")) + .ToolTipText_Lambda([InPDGAssetLink]() + { + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + return LOCTEXT( + "HoudiniEnginePDGBakeButtonBakeToActorToolTip", + "Bake this Houdini PDG Asset's output assets and seperate the output actors from the PDG asset link."); + } + break; + case EHoudiniEngineBakeOption::ToBlueprint: + { + return LOCTEXT( + "HoudiniEnginePDGBakeButtonBakeToBlueprintToolTip", + "Bake this Houdini PDG Asset's output assets to blueprints and remove temporary output actors that no " + "longer has output components from the PDG asset link."); + } + break; + default: + { + return FText(); + } + } + }) + .Visibility(EVisibility::Visible) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda(OnBakeButtonClickedLambda) + .Content() + [ + SAssignNew(BakeHBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); + if (BakeIconBrush.IsValid()) + { + TSharedPtr BakeImage; + BakeHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(BakeImage, SImage) + ] + ]; + + BakeImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([BakeIconBrush]() { return BakeIconBrush.Get(); }))); + } + + BakeHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Bake")) + ]; + + // bake Type ComboBox + TSharedPtr>> TypeComboBox; + + TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeTypeOptionsLabels(); + TSharedPtr IntialSelec; + if (OptionSource) + { + // IntialSelec = (*OptionSource)[(int)InPDGAssetLink->HoudiniEngineBakeOption]; + const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption); + const TSharedPtr* DefaultOption = OptionSource->FindByPredicate( + [DefaultStr](TSharedPtr InStringPtr) + { + return InStringPtr.IsValid() && *InStringPtr == DefaultStr; + } + ); + if (DefaultOption) + IntialSelec = *DefaultOption; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(93.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(93.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid() || SelectType == ESelectInfo::Type::Direct) + return; + + const EHoudiniEngineBakeOption NewOption = + FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->HoudiniEngineBakeOption) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->HoudiniEngineBakeOption = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink, TypeComboBox, OptionSource]() + { + return FText::FromString(FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + // bake selection ComboBox + TSharedPtr>> BakeSelectionComboBox; + + TArray>* PDGBakeSelectionOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeSelectionOptionsLabels(); + TSharedPtr PDGBakeSelectionIntialSelec; + if (PDGBakeSelectionOptionSource) + { + PDGBakeSelectionIntialSelec = (*PDGBakeSelectionOptionSource)[(int)InPDGAssetLink->PDGBakeSelectionOption]; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(163.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(163.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(PDGBakeSelectionOptionSource) + .InitiallySelectedItem(PDGBakeSelectionIntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EPDGBakeSelectionOption NewOption = + FHoudiniEngineEditor::Get().StringToPDGBakeSelectionOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->PDGBakeSelectionOption) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->PDGBakeSelectionOption = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromPDGBakeTargetOption(InPDGAssetLink->PDGBakeSelectionOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; + + // Bake package replacement mode row + FDetailWidgetRow & BakePackageReplaceRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(BakePackageReplaceRow, InPDGAssetLink); + + TSharedRef BakePackageReplaceRowHorizontalBox = SNew(SHorizontalBox); + + BakePackageReplaceRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeLabel", "Replace Mode")) + .ToolTipText( + LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeTooltip", "Replacement mode " + "during baking. Create new assets, using numerical suffixes in package names when necessary, or " + "replace existing assets with matching names. Also replaces the previous bake's output actors in " + "replacement mode vs creating incremental ones.")) + ] + ]; + + // bake package replace mode ComboBox + TSharedPtr>> BakePackageReplaceModeComboBox; + + TArray>* PDGBakePackageReplaceModeOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels(); + TSharedPtr PDGBakePackageReplaceModeInitialSelec; + if (PDGBakePackageReplaceModeOptionSource) + { + const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode); + const TSharedPtr* DefaultOption = PDGBakePackageReplaceModeOptionSource->FindByPredicate( + [DefaultStr](TSharedPtr InStringPtr) + { + return InStringPtr.IsValid() && *InStringPtr == DefaultStr; + } + ); + if (DefaultOption) + PDGBakePackageReplaceModeInitialSelec = *DefaultOption; + } + + BakePackageReplaceRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(163.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(163.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(PDGBakePackageReplaceModeOptionSource) + .InitiallySelectedItem(PDGBakePackageReplaceModeInitialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + const FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EPDGBakePackageReplaceModeOption NewOption = + FHoudiniEngineEditor::Get().StringToPDGBakePackageReplaceModeOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->PDGBakePackageReplaceMode) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->PDGBakePackageReplaceMode = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + BakePackageReplaceRow.WholeRowWidget.Widget = BakePackageReplaceRowHorizontalBox; + + // Bake Folder Row + FDetailWidgetRow & BakeFolderRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(BakeFolderRow, InPDGAssetLink); + + TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) + .ToolTipText(LOCTEXT( + "HoudiniEnginePDGBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) + ] + ]; + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .MaxWidth(235.0) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT( + "HoudiniEnginePDGBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) + .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) + .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) + ] + ]; + + BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; + + // Add additional bake options + FDetailWidgetRow & AdditionalBakeSettingsRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); + + TSharedPtr CheckBoxAutoBake; + TSharedPtr CheckBoxRecenterBakedActors; + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bRecenterBakedActors = bNewState; + + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bRecenterBakedActors), InPDGAssetLink); + }) + ] + ]; + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxAutoBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake work result object as they are loaded.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded = bNewState; + + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterAllWorkResultObjectsLoaded), InPDGAssetLink); + }) + ] + ]; + + AdditionalBakeSettingsRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText) + : Text(InText) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText, const FString &InToolTip) + : Text(InText) + , ToolTip(InToolTip) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText) + : Text(InText) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText, FString&& InToolTip) + : Text(InText) + , ToolTip(InToolTip) + , Value(InValue) +{ +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h index 00a8b3629..de9d43d6a 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h @@ -1,140 +1,140 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Templates/SharedPointer.h" -#include "DetailWidgetRow.h" - -#include "HoudiniPDGAssetLink.h" - -class IDetailGroup; -class IDetailCategoryBuilder; - -struct FWorkItemTally; -enum class EPDGLinkState : uint8; -enum class EHoudiniBGEOCommandletStatus : uint8; - -// Convenience struct to hold a label and tooltip for widgets. -struct FTextAndTooltip -{ -public: - FTextAndTooltip(int32 InValue, const FString& InText); - FTextAndTooltip(int32 InValue, const FString& InText, const FString &InTooltip); - FTextAndTooltip(int32 InValue, FString&& InText); - FTextAndTooltip(int32 InValue, FString&& InText, FString&& InTooltip); - - FString Text; - - FString ToolTip; - - int32 Value; -}; - -class FHoudiniPDGDetails : public TSharedFromThis -{ - public: - - void CreateWidget( - IDetailCategoryBuilder & HouPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink); - //UHoudiniAssetComponent* InHAC); - - void AddPDGAssetWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - - void AddWorkItemStatusWidget( - FDetailWidgetRow& InRow, const FString& TitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode); - - void AddPDGAssetStatus( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink); - - void AddPDGCommandletStatus( - IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus); - - void AddTOPNetworkWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - - void AddTOPNodeWidget( - IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink); - - static void RefreshPDGAssetLink( - UHoudiniPDGAssetLink* InPDGAssetLink); - - static void RefreshUI( - UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate = true); - - static void - CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - protected: - // Helper function for getting the work item tally and color - static bool GetWorkItemTallyValueAndColor( - UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode, const FString& InTallyItemString, - int32& OutValue, FLinearColor& OutColor); - - // Helper to get the status text for the selected TOP node, and the color with which to display it on the UI. - // Returns false if the InPDGAssetLink is invalid, or there is no selected TOP node. - static bool GetSelectedTOPNodeStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor); - - // Helper to get asset link status and status color for UI - static bool GetPDGStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor); - - // Helper for getting the commandlet status text and color for the UI - static void GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor); - - // Helper to check if the asset link state is Linked - static FORCEINLINE bool IsPDGLinked(UHoudiniPDGAssetLink* InPDGAssetLink) - { - return IsValid(InPDGAssetLink) && InPDGAssetLink->LinkState == EPDGLinkState::Linked; - } - - // Helper for binding IsPDGLinked to a TAttribute - static FORCEINLINE void BindDisableIfPDGNotLinked(TAttribute &InAttrToBind, UHoudiniPDGAssetLink* InPDGAssetLink) - { - InAttrToBind.Bind( - TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink); - }) - ); - } - - // Helper to disable a UI row if InPDGAssetLink is not linked - static FORCEINLINE void DisableIfPDGNotLinked(FDetailWidgetRow& InRow, UHoudiniPDGAssetLink* InPDGAssetLink) - { - BindDisableIfPDGNotLinked(InRow.IsEnabledAttr, InPDGAssetLink); - } - - private: - - TArray> TOPNetworksPtr; - - TArray> TOPNodesPtr; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Templates/SharedPointer.h" +#include "DetailWidgetRow.h" + +#include "HoudiniPDGAssetLink.h" + +class IDetailGroup; +class IDetailCategoryBuilder; + +struct FWorkItemTally; +enum class EPDGLinkState : uint8; +enum class EHoudiniBGEOCommandletStatus : uint8; + +// Convenience struct to hold a label and tooltip for widgets. +struct FTextAndTooltip +{ +public: + FTextAndTooltip(int32 InValue, const FString& InText); + FTextAndTooltip(int32 InValue, const FString& InText, const FString &InTooltip); + FTextAndTooltip(int32 InValue, FString&& InText); + FTextAndTooltip(int32 InValue, FString&& InText, FString&& InTooltip); + + FString Text; + + FString ToolTip; + + int32 Value; +}; + +class FHoudiniPDGDetails : public TSharedFromThis +{ + public: + + void CreateWidget( + IDetailCategoryBuilder & HouPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink); + //UHoudiniAssetComponent* InHAC); + + void AddPDGAssetWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + + void AddWorkItemStatusWidget( + FDetailWidgetRow& InRow, const FString& TitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode); + + void AddPDGAssetStatus( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink); + + void AddPDGCommandletStatus( + IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus); + + void AddTOPNetworkWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + + void AddTOPNodeWidget( + IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink); + + static void RefreshPDGAssetLink( + UHoudiniPDGAssetLink* InPDGAssetLink); + + static void RefreshUI( + UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate = true); + + static void + CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + protected: + // Helper function for getting the work item tally and color + static bool GetWorkItemTallyValueAndColor( + UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode, const FString& InTallyItemString, + int32& OutValue, FLinearColor& OutColor); + + // Helper to get the status text for the selected TOP node, and the color with which to display it on the UI. + // Returns false if the InPDGAssetLink is invalid, or there is no selected TOP node. + static bool GetSelectedTOPNodeStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor); + + // Helper to get asset link status and status color for UI + static bool GetPDGStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor); + + // Helper for getting the commandlet status text and color for the UI + static void GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor); + + // Helper to check if the asset link state is Linked + static FORCEINLINE bool IsPDGLinked(UHoudiniPDGAssetLink* InPDGAssetLink) + { + return IsValid(InPDGAssetLink) && InPDGAssetLink->LinkState == EPDGLinkState::Linked; + } + + // Helper for binding IsPDGLinked to a TAttribute + static FORCEINLINE void BindDisableIfPDGNotLinked(TAttribute &InAttrToBind, UHoudiniPDGAssetLink* InPDGAssetLink) + { + InAttrToBind.Bind( + TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink); + }) + ); + } + + // Helper to disable a UI row if InPDGAssetLink is not linked + static FORCEINLINE void DisableIfPDGNotLinked(FDetailWidgetRow& InRow, UHoudiniPDGAssetLink* InPDGAssetLink) + { + BindDisableIfPDGNotLinked(InRow.IsEnabledAttr, InPDGAssetLink); + } + + private: + + TArray> TOPNetworksPtr; + + TArray> TOPNodesPtr; + +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp index b63e4d8d1..4643f07c9 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp @@ -1,7380 +1,7419 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterDetails.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniInput.h" -#include "HoudiniAsset.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "SNewFilePathPicker.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "IDetailGroup.h" -#include "IDetailCustomization.h" -#include "PropertyCustomizationHelpers.h" -#include "DetailWidgetRow.h" -#include "Math/UnitConversion.h" -#include "ScopedTransaction.h" -#include "EditorDirectories.h" - -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SNumericEntryBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SMultiLineEditableTextBox.h" -#include "Widgets/Colors/SColorPicker.h" -#include "Widgets/Views/SExpanderArrow.h" -#include "Widgets/Layout/SExpandableArea.h" -#include "Widgets/Views/STableRow.h" -#include "Widgets/Input/NumericUnitTypeInterface.inl" -#include "Widgets/Images/SImage.h" -#include "Widgets/Layout/SSeparator.h" -#include "Widgets/Layout/SSplitter.h" -#include "SCurveEditorView.h" -#include "SAssetDropTarget.h" -#include "AssetThumbnail.h" - -#include "Sound/SoundBase.h" -#include "Engine/SkeletalMesh.h" -#include "Particles/ParticleSystem.h" -#include "FoliageType.h" - -#include "HoudiniInputDetails.h" - -#include "Framework/SlateDelegates.h" -#include "Templates/SharedPointer.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -int32 -SCustomizedButton::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const -{ - TSharedPtr Content = GetContent(); - - // 0. Initialize Line Buffer. - TArray Line; - Line.SetNumUninitialized(2); - - // Initialize Color buffer. - FLinearColor Color = FLinearColor::White; - - // 1. Draw the radio button. - if (bIsRadioButton) - { - // Construct the radio button circles exactly once, - // All radio buttons share the same circles then - if (FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER || - FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER) - { - ConstructRadioButtonCircles(); - } - - DrawRadioButton(AllottedGeometry, OutDrawElements, LayerId, bChosen); - } - - // 2. Draw background color (if selected) - if (bChosen) - { - Line[0].X = AllottedGeometry.Size.X - AllottedGeometry.Size.Y / 2.0f + 2.5f; - Line[0].Y = Content->GetDesiredSize().Y / 2.0f; - Line[1].X = AllottedGeometry.Size.Y / 2.0f - 2.5f; - Line[1].Y = Content->GetDesiredSize().Y / 2.0f; - - Color = FLinearColor::White; - Color.A = bIsRadioButton ? 0.05 : 0.1; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, AllottedGeometry.Size.Y); - } - - // 3. Drawing square around the text - { - // Switch the point order for each line to save few value assignment cycles - Line[0].X = 0.0f; - Line[0].Y = 0.0f; - Line[1].X = 0.0f; - Line[1].Y = Content->GetDesiredSize().Y; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); - - //Line[0].X = 0.0f; - //Line[0].Y = Content->GetDesiredSize().Y; - Line[0].X = AllottedGeometry.Size.X; - Line[0].Y = Content->GetDesiredSize().Y; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, bChosen ? FLinearColor::Gray : FLinearColor::Black, true, 1.0f); - - //Line[0].X = AllottedGeometry.Size.X; - //Line[0].Y = Content->GetDesiredSize().Y; - Line[1].X = AllottedGeometry.Size.X; - Line[1].Y = 0.0f; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); /* draw gray bottom line if this tab is selected, black otherwise*/ - - //Line[0].X = AllottedGeometry.Size.X; - //Line[0].Y = 0.0f; - Line[0].X = 0.0f; - Line[0].Y = 0.0f; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); - } - - // 4. Draw child widget - Content->Paint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - - return LayerId; -}; - -void -SCustomizedButton::ConstructRadioButtonCircles() const -{ - TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); - TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); - OuterPoints.Empty(); - InnerPoints.Empty(); - - OuterPoints.SetNumZeroed(HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER); - InnerPoints.SetNumZeroed(8); - - // Construct outer circle - int32 CurDegree = 0; - int32 DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; - - for (int32 Idx = 0; Idx < HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; ++Idx) - { - OuterPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); - OuterPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); - - CurDegree += DegStep; - } - - // Construct inner circle - CurDegree = 0; - DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER; - for (int32 Idx = 0; Idx < 8; ++Idx) - { - InnerPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); - InnerPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); - - CurDegree += DegStep; - } -} - -void -SCustomizedButton::DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const -{ - TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); - TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); - if (OuterPoints.Num() <= 1 || InnerPoints.Num() <= 1) - return; - - FLinearColor ColorNonSelected = FLinearColor::White; - FLinearColor ColorSelected = FLinearColor::Yellow; - - // initialize line buffer - TArray Line; - Line.SetNumZeroed(2); - bool alternator = false; - - // Draw outer circle - Line[0] = OuterPoints.Last(); - for (int32 Idx = 0; Idx < OuterPoints.Num(); ++Idx) - { - // alternate the points order each time to some some assignment cycles - if (alternator) - { - Line[0].X = OuterPoints[Idx].X; - Line[0].Y = OuterPoints[Idx].Y; - } - else - { - Line[1].X = OuterPoints[Idx].X; - Line[1].Y = OuterPoints[Idx].Y; - } - - alternator = !alternator; - - // Draw a line segment - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, ColorNonSelected, true, 1.0f); - } - - // Draw inner circle - alternator = false; - Line[0] = InnerPoints.Last(); - for (int32 Idx = 0; Idx < InnerPoints.Num(); ++Idx) - { - // alternate the points order each time to some some assignment cycles - if (alternator) - { - Line[0].X = InnerPoints[Idx].X; - Line[0].Y = InnerPoints[Idx].Y; - } - else - { - Line[1].X = InnerPoints[Idx].X; - Line[1].Y = InnerPoints[Idx].Y; - } - - alternator = !alternator; - - // Draw a line segment - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, bSelected ? ColorSelected : ColorNonSelected, true, 3.0f); - } -} - -void -SCustomizedBox::SetHoudiniParameter(TArray& InParams) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - - bool bIsMultiparmInstanceHeader = MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 0; - - switch (MainParam->GetParameterType()) - { - case EHoudiniParameterType::Button: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON; - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP; - } - break; - - case EHoudiniParameterType::Color: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR; - } - break; - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor * ColorRampParameter = Cast(MainParam); - if (!ColorRampParameter || ColorRampParameter->IsPendingKill()) - return; - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP; - if (ColorRampParameter->CachedPoints.Num() > 0) - MarginHeight = MarginHeight + HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE * (float)(ColorRampParameter->CachedPoints.Num() - 1); - } - break; - - case EHoudiniParameterType::File: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE; - } - break; - - case EHoudiniParameterType::FileDir: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR; - } - break; - - case EHoudiniParameterType::FileGeo: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO; - } - break; - - case EHoudiniParameterType::FileImage: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE; - } - break; - - case EHoudiniParameterType::Float: - { - if (MainParam->GetTupleSize() == 3) - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3; - } - else - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT - + (MainParam->GetTupleSize() - 1)* HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE; - } - } - } - break; - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat * FloatRampParameter = Cast(MainParam); - if (!FloatRampParameter || FloatRampParameter->IsPendingKill()) - return; - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP; - - if (FloatRampParameter->CachedPoints.Num() > 0) - MarginHeight = MarginHeight + Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE * (float)(FloatRampParameter->CachedPoints.Num() - 1); - } - break; - - case EHoudiniParameterType::Folder: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER; - } - break; - - case EHoudiniParameterType::FolderList: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST; - } - break; - - case EHoudiniParameterType::Input: - { - UHoudiniParameterOperatorPath* InputParam = Cast(MainParam); - - if (!InputParam || InputParam->IsPendingKill() || !InputParam->HoudiniInput.IsValid()) - break; - - UHoudiniInput* Input = InputParam->HoudiniInput.Get(); - - if (!Input || Input->IsPendingKill()) - break; - - - if (bIsMultiparmInstanceHeader) - { - switch (Input->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - int32 ExpandedTransformUIs = 0; - for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) - { - if (Input->IsTransformUIExpanded(Idx)) - ExpandedTransformUIs += 1; - } - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER - + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Curve: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Asset: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Landscape: - { - if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::World: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Skeletal: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER; - } - break; - default: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER; - break; - } - } - else - { - switch (Input->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - int32 ExpandedTransformUIs = 0; - for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) - { - if (Input->IsTransformUIExpanded(Idx)) - ExpandedTransformUIs += 1; - } - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE - + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM; - } - break; - case EHoudiniInputType::Curve: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE; - } - break; - case EHoudiniInputType::Asset: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET; - } - break; - case EHoudiniInputType::Landscape: - { - if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH; - } - break; - case EHoudiniInputType::World: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD; - } - break; - case EHoudiniInputType::Skeletal: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL; - } - break; - default: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT; - break; - - } - } - } - break; - - case EHoudiniParameterType::Int: - { - if (MainParam->GetTupleSize() == 3) - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3; - } - else - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER + - (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE; - } - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE; - } - break; - - case EHoudiniParameterType::Label: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL; - } - break; - - case EHoudiniParameterType::MultiParm: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM; - } - break; - - case EHoudiniParameterType::Separator: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR; - bIsSeparator = true; - } - break; - - case EHoudiniParameterType::String: - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE; - } - } - break; - - case EHoudiniParameterType::StringAssetRef: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF; - } - break; - - case EHoudiniParameterType::StringChoice: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE; - } - break; - - case EHoudiniParameterType::Toggle: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE; - } - break; - - case EHoudiniParameterType::Invalid: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID; - break; - - default: - MarginHeight = 0.0f; - break; - } -} - -float -SCustomizedBox::AddIndentation(UHoudiniParameter* InParam, - TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists) -{ - if (!InParam || InParam->IsPendingKill()) - return 0.0f; - - bool bIsMainParmSimpleFolder = false; - // Get if this Parameter is a simple / collapsible folder - if (InParam->GetParameterType() == EHoudiniParameterType::Folder) - { - UHoudiniParameterFolder* FolderParm = Cast(InParam); - if (FolderParm) - bIsMainParmSimpleFolder = !FolderParm->IsTab(); - } - - int32 ParentId = InParam->GetParentParmId(); - UHoudiniParameter* CurParm = InParam; - float Indentation = 0.0f; - - while (ParentId >= 0) - { - UHoudiniParameter* ParentFolder = nullptr; - UHoudiniParameterMultiParm* ParentMultiParm = nullptr; - - if (InAllFoldersAndFolderLists.Contains(ParentId)) - ParentFolder = InAllFoldersAndFolderLists[ParentId]; - - if (InAllMultiParms.Contains(ParentId)) - ParentMultiParm = InAllMultiParms[ParentId]; - - // The parent is a folder, add one unit of indentation - if (ParentFolder) - { - // Update the parent parm id - ParentId = ParentFolder->GetParentParmId(); - - if (ParentFolder->GetParameterType() == EHoudiniParameterType::FolderList) - continue; - - UHoudiniParameterFolder* Folder = Cast(ParentFolder); - - if (!Folder) - continue; - - // update the current parm, find the parent of new cur param in the next round - CurParm = Folder; - Indentation += 1.0f; - } - // The parent is a multiparm - else if (ParentMultiParm) - { - // Update the parent parm id - ParentId = ParentMultiParm->GetParentParmId(); - - if (CurParm->GetChildIndex() == 0) - { - Indentation += 0.0f; - } - else - { - Indentation += 2.0f; - } - - // update the current parm, find the parent of new cur param in the next round - CurParm = ParentMultiParm; - } - else - { - // no folder/multiparm parent, end the loop - ParentId = -1; - } - } - - - float IndentationWidth = INDENTATION_UNIT_WIDTH * Indentation; - - // Add a base indentation to non simple/collapsible param - // Since it needs more space to offset the arrow width - if (!bIsMainParmSimpleFolder) - IndentationWidth += NON_FOLDER_OFFSET_WIDTH; - - this->AddSlot().AutoWidth() - [ - SNew(SBox).WidthOverride(IndentationWidth) - ]; - - - return IndentationWidth; -}; - -int32 -SCustomizedBox::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const -{ - - SHorizontalBox::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - - // Initialize line buffer - TArray Line; - Line.SetNumZeroed(2); - // Initialize color buffer - FLinearColor Color = FLinearColor::White; - Color.A = 0.3; - - // draw the bottom line if this row is the tab folder list - if (bIsTabFolderListRow) - { - // Get the start position of the tabs bottom line (right bottom pt of the right most child widget) - float VerticalLineStartPosX = 0.0f; - float VerticalLineStartPosY = 0.0f; - float BottomLineStartPosX = 0.0f; - float BottomLineStartPosY = -1.0f; - - for (int32 Idx = 0; Idx < Children.Num(); ++Idx) - { - TSharedPtr CurChild = Children.GetChildAt(Idx); - if (!CurChild.IsValid()) - continue; - - if (Idx == 0) - { - VerticalLineStartPosX = CurChild->GetDesiredSize().X; - VerticalLineStartPosY = CurChild->GetDesiredSize().Y; - } - - BottomLineStartPosX += CurChild->GetDesiredSize().X; - - if (BottomLineStartPosY < 0.0f) - BottomLineStartPosY= CurChild->GetDesiredSize().Y; - } - - // Draw bottom line - Line[0].X = BottomLineStartPosX; - Line[0].Y = BottomLineStartPosY; - Line[1].X = AllottedGeometry.Size.X; - Line[1].Y = BottomLineStartPosY; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - - // Draw divider lines - { - Line[0].Y = -MarginHeight; - Line[1].Y = AllottedGeometry.Size.Y + MarginHeight; - - int32 NumOfLinesToDraw = bIsTabFolderListRow ? DividerLinePositions.Num() - 1 : DividerLinePositions.Num(); - for (int32 Idx = 0; Idx < NumOfLinesToDraw; ++Idx) - { - const float& CurDivider = DividerLinePositions[Idx]; - Line[0].X = CurDivider; - Line[1].X = CurDivider; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - - // Draw the last inner most divider line differently when this the tabs' row. - if (bIsTabFolderListRow && DividerLinePositions.Num() > 0) - { - const float& TabDivider = DividerLinePositions.Last(); - Line[0].X = TabDivider; - Line[1].X = TabDivider; - Line[0].Y = 0.f; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - } - - // Draw tab ending lines - { - float YPos = 0.0f; - - for (const float & CurEndingDivider : EndingDividerLinePositions) - { - // Draw cur ending line (vertical) - - Line[0].X = CurEndingDivider; - Line[0].Y = -2.3f; - Line[1].X = CurEndingDivider; - Line[1].Y = YPos; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - - // Draw cur ending line (horizontal) - - // Line[0].X = CurEndingDivider; - Line[0].Y = YPos; - Line[1].X = AllottedGeometry.Size.X; - // Line[1].Y = YPos; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - - YPos += 2.0f; - } - } - - // Draw the separator line if this is the row of a separator parameter - { - if (bIsSeparator) - { - Line[0].X = 25.f; - if (DividerLinePositions.Num() > 0) - Line[0].X += DividerLinePositions.Last(); - - Line[0].Y = AllottedGeometry.Size.Y / 2.f; - Line[1].X = AllottedGeometry.Size.X - 20.f; - Line[1].Y = Line[0].Y; - - Color.A = 0.7; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.5f); - } - } - - return LayerId; -}; - -void -SHoudiniFloatRampCurveEditor::Construct(const FArguments & InArgs) -{ - SCurveEditor::Construct(SCurveEditor::FArguments() - .ViewMinInput(InArgs._ViewMinInput) - .ViewMaxInput(InArgs._ViewMaxInput) - .ViewMinOutput(InArgs._ViewMinOutput) - .ViewMaxOutput(InArgs._ViewMaxOutput) - .XAxisName(InArgs._XAxisName) - .YAxisName(InArgs._YAxisName) - .HideUI(InArgs._HideUI) - .DrawCurve(InArgs._DrawCurve) - .TimelineLength(InArgs._TimelineLength) - .AllowZoomOutput(InArgs._AllowZoomOutput) - .ShowInputGridNumbers(InArgs._ShowInputGridNumbers) - .ShowOutputGridNumbers(InArgs._ShowOutputGridNumbers) - .ShowZoomButtons(InArgs._ShowZoomButtons) - .ZoomToFitHorizontal(InArgs._ZoomToFitHorizontal) - .ZoomToFitVertical(InArgs._ZoomToFitVertical) - ); - - - UCurveEditorSettings * CurveEditorSettings = GetSettings(); - if (CurveEditorSettings) - { - CurveEditorSettings->SetTangentVisibility(ECurveEditorTangentVisibility::NoTangents); - } -} - -void -SHoudiniColorRampCurveEditor::Construct(const FArguments & InArgs) -{ - SColorGradientEditor::Construct(SColorGradientEditor::FArguments() - .ViewMinInput(InArgs._ViewMinInput) - .ViewMaxInput(InArgs._ViewMaxInput) - ); -} - - -FReply -SHoudiniFloatRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - FReply Reply = SCurveEditor::OnMouseButtonUp(MyGeometry, MouseEvent); - - if (!HoudiniFloatRampCurve.IsValid()) - return Reply; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - FRichCurve& FloatCurve = HoudiniFloatRampCurve.Get()->FloatCurve; - - TArray>& FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; - - if (FloatRampParameters.Num() < 1) - return Reply; - - if (!FloatRampParameters[0].IsValid()) - return Reply; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - // Do not allow modification when the parent HDA of the main param is being cooked. - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points of the main float ramp param to other parameters - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - // Modification is based on the main parameter, use synced points if the main param is on auto update mode, use cached points otherwise. - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - // On mouse button up handler handles point modification only - if (FloatCurve.GetNumKeys() != NumMainPoints) - return Reply; - - bool bNeedToRefreshEditor= false; - - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float& CurvePosition = FloatCurve.Keys[Idx].Time; - float& CurveValue = FloatCurve.Keys[Idx].Value; - - // This point is modified - if (MainPoint->GetPosition() != CurvePosition || MainPoint->GetValue() != CurveValue) - { - - // The editor needs refresh only if the main parameter is on manual mode, and has been modified - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedToRefreshEditor = true; - - // Iterate through the float ramp parameter of all selected HDAs. - for (auto & NextRampFloat : FloatRampParameters) - { - if (!NextRampFloat.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedRampFloat = NextRampFloat.Get(); - - if (!SelectedRampFloat) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedRampFloat)) - continue; - - if (SelectedRampFloat->IsAutoUpdate() && bCookingEnabled) - { - // The selected float ramp parameter is on auto update mode, use its synced points. - TArray &SelectedRampPoints = SelectedRampFloat->Points; - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // Synced points in the selected ramp is more than or the same number as that in the main parameter, - // modify the position and value of the synced point and mark them as changed. - - UHoudiniParameterRampFloatPoint*& ModifiedPoint = SelectedRampPoints[Idx]; - - if (!ModifiedPoint) - continue; - - if (ModifiedPoint->GetPosition() != CurvePosition && ModifiedPoint->PositionParentParm) - { - ModifiedPoint->SetPosition(CurvePosition); - ModifiedPoint->PositionParentParm->MarkChanged(true); - } - - if (ModifiedPoint->GetValue() != CurveValue && ModifiedPoint->ValueParentParm) - { - ModifiedPoint->SetValue(CurveValue); - ModifiedPoint->ValueParentParm->MarkChanged(true); - } - } - else - { - // Synced points in the selected ramp is less than that in the main parameter - // Since we have pushed the points of the main param to all of the selected ramps, - // We need to modify the insert event. - - int32 IndexInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedRampFloat->ModificationEvents.IsValidIndex(Idx)) - { - UHoudiniParameterRampModificationEvent*& ModEvent = SelectedRampFloat->ModificationEvents[Idx]; - if (!ModEvent) - continue; - - if (ModEvent->InsertPosition != CurvePosition) - ModEvent->InsertPosition = CurvePosition; - - if (ModEvent->InsertFloat != CurveValue) - ModEvent->InsertFloat = CurveValue; - } - - } - } - else - { - // The selected float ramp is on manual update mode, use the cached points. - TArray &FloatRampCachedPoints = SelectedRampFloat->CachedPoints; - - // Since we have pushed the points in main param to all the selected float ramp, - // we need to modify the corresponding cached point in the selected float ramp. - - if (FloatRampCachedPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampFloatPoint*& ModifiedCachedPoint = FloatRampCachedPoints[Idx]; - - if (!ModifiedCachedPoint) - continue; - - if (ModifiedCachedPoint->Position != CurvePosition) - { - ModifiedCachedPoint->Position = CurvePosition; - SelectedRampFloat->bCaching = true; - if (!bCookingEnabled) - { - //SelectedRampFloat->MarkChanged(true); - if (ModifiedCachedPoint->PositionParentParm) - ModifiedCachedPoint->PositionParentParm->MarkChanged(true); - } - } - - if (ModifiedCachedPoint->Value != CurveValue) - { - ModifiedCachedPoint->Value = CurveValue; - SelectedRampFloat->bCaching = true; - if (!bCookingEnabled) - { - //SelectedRampFloat->MarkChanged(true); - if (ModifiedCachedPoint->ValueParentParm) - ModifiedCachedPoint->ValueParentParm->MarkChanged(true); - } - } - } - } - } - } - } - - - if (bNeedToRefreshEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } - - return Reply; -} - -FReply -SHoudiniFloatRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) -{ - FReply Reply = SCurveEditor::OnKeyDown(MyGeometry, InKeyEvent); - - if (InKeyEvent.GetKey().ToString() != FString("Enter")) - return Reply; - - if (!HoudiniFloatRampCurve.IsValid() || !HoudiniFloatRampCurve.Get()) - return Reply; - - TArray> FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; - - if (FloatRampParameters.Num() < 1) - return Reply; - - if (!FloatRampParameters[0].IsValid()) - return Reply; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Do nothing if the main param is on auto update mode - if (MainParam->IsAutoUpdate() && bCookingEnabled) - return Reply; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points in the main float ramp to the float ramp parameters in all selected HDAs. - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - for (auto& NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - if (!SelectedFloatRamp) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not sync the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - // Sync the cached points if the selected float ramp parameter is on manual update mode - //FHoudiniParameterDetails::SyncCachedFloatRampPoints(SelectedFloatRamp); - SelectedFloatRamp->SyncCachedPoints(); - } - - return Reply; -} - -void -UHoudiniFloatRampCurve::OnCurveChanged(const TArray& ChangedCurveEditInfos) -{ - Super::OnCurveChanged(ChangedCurveEditInfos); - - if (FloatRampParameters.Num() < 1) - return; - - if (!FloatRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return; - - // Do not allow modification when the parent HDA of the main param is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - // Push all the points of the Main parameter to other parameters - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode, otherwise use its cached points. - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - bool bNeedUpdateEditor = false; - - // OnCurveChanged handler handles point delete and insertion only - - // A point is deleted. - if (FloatCurve.GetNumKeys() < NumMainPoints) - { - // Find the index of the deleted point - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float CurPointPosition = MainPoint->GetPosition(); - float CurCurvePosition = -1.0f; - - if (FloatCurve.Keys.IsValidIndex(Idx)) - CurCurvePosition = FloatCurve.Keys[Idx].Time; - - // Delete the point at Idx - if (CurCurvePosition != CurPointPosition) - { - // Iterate through all the float ramp parameter in all the selected HDAs - for (auto & NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - if (!SelectedFloatRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - TArray & SelectedRampPoints = SelectedFloatRamp->Points; - - // The selected float ramp is on auto update mode: - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // If the number of synced points of the selected float ramp is greater or equal to the number of points of that in the main param, - // Create a Houdini engine manager event to delete the point at Idx of the selected float ramp; - - UHoudiniParameterRampFloatPoint* PointToDelete = SelectedRampPoints[Idx]; - - if (!PointToDelete) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(SelectedFloatRamp, PointToDelete->InstanceIndex); - SelectedFloatRamp->MarkChanged(true); - } - else - { - // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, - // delete the corresponding inserting event. - - int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - SelectedFloatRamp->ModificationEvents.RemoveAt(IdxInEventsArray); - } - } - else - { - // The selected float ramp is on manual update mode: - // Since we have pushed all the points in main param to the cached points of the selected float ramp, - // remove the corresponding points from the cached points array. - - if (SelectedFloatRamp->CachedPoints.IsValidIndex(Idx)) - { - SelectedFloatRamp->CachedPoints.RemoveAt(Idx); - SelectedFloatRamp->bCaching = true; - } - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point is inserted - else if (FloatCurve.GetNumKeys() > NumMainPoints) - { - // Find the index of the inserted point - for (int32 Idx = 0; Idx < FloatCurve.GetNumKeys(); ++Idx) - { - - float CurPointPosition = -1.0f; - float CurCurvePosition = FloatCurve.Keys[Idx].Time; - - if (MainPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - CurPointPosition = MainPoint->GetPosition(); - } - - // Insert instance at Idx - if (CurPointPosition != CurCurvePosition) - { - // Iterate through the float ramp parameter of all selected HDAs. - for (auto & NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - // If the selected float ramp is on auto update mode: - // Since we have pushed all the points of main parameter to the selected, - // create a Houdini engine manager event to insert a point. - - FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent( - SelectedFloatRamp, CurCurvePosition, FloatCurve.Keys[Idx].Value, EHoudiniRampInterpolationType::LINEAR); - - SelectedFloatRamp->MarkChanged(true); - } - else - { - // If the selected float ramp is on manual update mode: - // push a new point to the cached points array - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(SelectedFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = CurCurvePosition; - NewCachedPoint->Value = FloatCurve.Keys[Idx].Value; - NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; - - if (Idx >= SelectedFloatRamp->CachedPoints.Num()) - SelectedFloatRamp->CachedPoints.Add(NewCachedPoint); - else - SelectedFloatRamp->CachedPoints.Insert(NewCachedPoint, Idx); - - SelectedFloatRamp->bCaching = true; - - if (!bCookingEnabled) - SelectedFloatRamp->MarkChanged(true); - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - if (bNeedUpdateEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } - -} - - -FReply -SHoudiniColorRampCurveEditor::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - FReply Reply = SColorGradientEditor::OnMouseButtonDown(MyGeometry, MouseEvent); - - if (HoudiniColorRampCurve.IsValid()) - { - UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); - if (Curve) - Curve->bEditing = true; - } - - return Reply; -} - -FReply -SHoudiniColorRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - - FReply Reply = SColorGradientEditor::OnMouseButtonUp(MyGeometry, MouseEvent); - - if (HoudiniColorRampCurve.IsValid()) - { - UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); - - if (Curve) - { - Curve->bEditing = false; - Curve->OnColorRampCurveChanged(true); - } - } - - return Reply; - -} - -FReply -SHoudiniColorRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) -{ - FReply Reply = SColorGradientEditor::OnKeyDown(MyGeometry, InKeyEvent); - - if (InKeyEvent.GetKey().ToString() != FString("Enter")) - return Reply; - - if (!HoudiniColorRampCurve.IsValid() || !HoudiniColorRampCurve.Get()) - return Reply; - - TArray> &ColorRampParameters = HoudiniColorRampCurve.Get()->ColorRampParameters; - - if (ColorRampParameters.Num() < 1) - return Reply; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - // Do nothing if the main param is on auto update mode - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - if (MainParam->IsAutoUpdate() && bCookingEnabled) - return Reply; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points in the main color ramp to the color ramp parameters in all selected HDAs. - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); - - for (auto& NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not sync the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - // Sync the cached points if the selected color ramp is on manual update mode - FHoudiniParameterDetails::SyncCachedColorRampPoints(SelectedColorRamp); - } - - return Reply; -} - -void -UHoudiniColorRampCurve::OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) -{ - Super::OnCurveChanged(ChangedCurveEditInfos); - - OnColorRampCurveChanged(); -} - -void -UHoudiniColorRampCurve::OnColorRampCurveChanged(bool bModificationOnly) -{ - // Array is always true in this case - // if (!FloatCurves) - // return; - - if (ColorRampParameters.Num() < 1) - return; - - if (!ColorRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return; - - // Do not allow modification when the parent HDA of the main param is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - // Push all the points of the main parameter to other parameters - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); - - // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode,otherwise use its cached points. - bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - bool bNeedUpdateEditor = false; - - // OnCurveChanged handler of color ramp curve editor handles point delete, insert and color change - - // A point is deleted - if (FloatCurves->GetNumKeys() < NumMainPoints) - { - if (bModificationOnly) - return; - - // Find the index of the deleted point - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float CurPointPosition = MainPoint->GetPosition(); - float CurCurvePosition = -1.0f; - - if (FloatCurves[0].Keys.IsValidIndex(Idx)) - CurCurvePosition = FloatCurves[0].Keys[Idx].Time; - - // Delete the point at Idx - if (CurCurvePosition != CurPointPosition) - { - // Iterate through all the color ramp parameter in all the selected HDAs - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - TArray & SelectedRampPoints = SelectedColorRamp->Points; - - // The selected color ramp is on auto update mode: - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // If the number of synced points of the selected color ramp is greater or equal to the number of points of that in the main param, - // create a Houdini engine manager event to delete the point at Idx of the selected float ramp; - - UHoudiniParameterRampColorPoint* PointToDelete = SelectedRampPoints[Idx]; - - if (!PointToDelete) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(SelectedColorRamp, PointToDelete->InstanceIndex); - SelectedColorRamp->MarkChanged(true); - } - else - { - // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, - // delete the corresponding inserting event. - - int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - SelectedColorRamp->ModificationEvents.RemoveAt(IdxInEventsArray); - } - } - else - { - // The selected color ramp is on manual update mode: - // Since we have pushed all the points in main param to the cached points of the selected float ramp, - // remove the corresponding points from the cached points array - if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) - { - SelectedColorRamp->CachedPoints.RemoveAt(Idx); - SelectedColorRamp->bCaching = true; - } - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point is inserted - else if (FloatCurves[0].GetNumKeys() > NumMainPoints) - { - - if (bModificationOnly) - return; - - // Find the index of the inserted point - for (int32 Idx = 0; Idx < FloatCurves[0].GetNumKeys(); ++Idx) - { - - float CurPointPosition = -1.0f; - float CurCurvePosition = FloatCurves[0].Keys[Idx].Time; - - if (MainPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - CurPointPosition = MainPoint->GetPosition(); - } - - // Insert a point at Idx - if (CurPointPosition != CurCurvePosition) - { - // Get the interpolation value of inserted color point - - FLinearColor ColorPrev = FLinearColor::Black; - FLinearColor ColorNext = FLinearColor::White; - float PositionPrev = 0.0f; - float PositionNext = 1.0f; - - if (MainParam->IsAutoUpdate() && bCookingEnabled) - { - // Try to get its previous point's color - if (MainParam->Points.IsValidIndex(Idx - 1)) - { - ColorPrev = MainParam->Points[Idx - 1]->GetValue(); - PositionPrev = MainParam->Points[Idx - 1]->GetPosition(); - } - - // Try to get its next point's color - if (MainParam->Points.IsValidIndex(Idx)) - { - ColorNext = MainParam->Points[Idx]->GetValue(); - PositionNext = MainParam->Points[Idx]->GetPosition(); - } - } - else - { - // Try to get its previous point's color - if (MainParam->CachedPoints.IsValidIndex(Idx - 1)) - { - ColorPrev = MainParam->CachedPoints[Idx - 1]->GetValue(); - PositionPrev = MainParam->CachedPoints[Idx - 1]->GetPosition(); - } - - // Try to get its next point's color - if (MainParam->CachedPoints.IsValidIndex(Idx)) - { - ColorNext = MainParam->CachedPoints[Idx]->GetValue(); - PositionNext = MainParam->CachedPoints[Idx]->GetPosition(); - } - } - - float TotalWeight = FMath::Abs(PositionNext - PositionPrev); - float PrevWeight = FMath::Abs(CurCurvePosition - PositionPrev); - float NextWeight = FMath::Abs(PositionNext - CurCurvePosition); - - FLinearColor InsertedColor = ColorPrev * (PrevWeight / TotalWeight) + ColorNext * (NextWeight / TotalWeight); - - // Iterate through the color ramp parameter of all selected HDAs. - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - // If the selected color ramp is on auto update mode: - // Since we have pushed all the points of main parameter to the selected, - // create a Houdini engine manager event to insert a point. - - FHoudiniParameterDetails::CreateColorRampParameterInsertEvent( - SelectedColorRamp, CurCurvePosition, InsertedColor, EHoudiniRampInterpolationType::LINEAR); - - SelectedColorRamp->MarkChanged(true); - } - else - { - // If the selected color ramp is on manual update mode: - // Push a new point to the cached points array - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(SelectedColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = CurCurvePosition; - NewCachedPoint->Value = InsertedColor; - NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; - - if (Idx >= SelectedColorRamp->CachedPoints.Num()) - SelectedColorRamp->CachedPoints.Add(NewCachedPoint); - else - SelectedColorRamp->CachedPoints.Insert(NewCachedPoint, Idx); - - SelectedColorRamp->bCaching = true; - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!MainParam->IsAutoUpdate() && bCookingEnabled) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point's color is changed - else - { - if (bEditing) - return; - - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - // Only handle color change - { - float CurvePosition = FloatCurves[0].Keys[Idx].Time; - float PointPosition = MainPoint->GetPosition(); - - FLinearColor CurveColor = FLinearColor::Black; - FLinearColor PointColor = MainPoint->GetValue(); - - CurveColor.R = FloatCurves[0].Keys[Idx].Value; - CurveColor.G = FloatCurves[1].Keys[Idx].Value; - CurveColor.B = FloatCurves[2].Keys[Idx].Value; - - // Color is changed at Idx - if (CurveColor != PointColor || CurvePosition != PointPosition) - { - // Iterate through the all selected color ramp parameters - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - // The selected color ramp parameter is on auto update mode - - if (SelectedColorRamp->Points.IsValidIndex(Idx)) - { - // If the number of synced points in the selected color ramp is more or equal to that in the main parameter: - // Modify the corresponding synced point of the selected color ramp, and marked it as changed. - - UHoudiniParameterRampColorPoint* Point = SelectedColorRamp->Points[Idx]; - - if (!Point) - continue; - - if (Point->GetValue() != CurveColor && Point->ValueParentParm) - { - Point->SetValue(CurveColor); - Point->ValueParentParm->MarkChanged(true); - } - - if (Point->GetPosition() != CurvePosition && Point->PositionParentParm) - { - Point->SetPosition(CurvePosition); - Point->PositionParentParm->MarkChanged(true); - } - } - else - { - // If the number of synced points in the selected color ramp is less than that in the main parameter: - // Since we have push the points in the main parameter to all selected parameters, - // we need to modify the corresponding insert event. - - int32 IdxInEventsArray = Idx - SelectedColorRamp->Points.Num(); - - if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = SelectedColorRamp->ModificationEvents[IdxInEventsArray]; - - if (!Event) - continue; - - if (Event->InsertColor != CurveColor) - Event->InsertColor = CurveColor; - - if (Event->InsertPosition != CurvePosition) - Event->InsertPosition = CurvePosition; - } - } - } - else - { - // The selected color ramp is on manual update mode - // Since we have push the points in the main parameter to all selected parameters, - // modify the corresponding point in the cached points array of the selected color ramp. - if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampColorPoint* CachedPoint = SelectedColorRamp->CachedPoints[Idx]; - - if (!CachedPoint) - continue; - - if (CachedPoint->Value != CurveColor) - { - CachedPoint->Value = CurveColor; - bNeedUpdateEditor = true; - } - - if (CachedPoint->Position != CurvePosition) - { - CachedPoint->Position = CurvePosition; - SelectedColorRamp->bCaching = true; - bNeedUpdateEditor = true; - } - } - } - } - } - } - } - } - - - if (bNeedUpdateEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } -} - -template< class T > -bool FHoudiniParameterDetails::CastParameters( - TArray InParams, TArray& OutCastedParams ) -{ - for (auto CurrentParam : InParams) - { - T* CastedParam = Cast(CurrentParam); - if (CastedParam && !CastedParam->IsPendingKill()) - OutCastedParams.Add(CastedParam); - } - - return (OutCastedParams.Num() == InParams.Num()); -} - - -void -FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* InParam = InParams[0]; - if (!InParam || InParam->IsPendingKill()) - return; - - // The directory won't parse if parameter ids are -1 - // simply return - if (InParam->GetParmId() < 0) - return; - - // This parameter is a part of the last float ramp. - if (CurrentRampFloat) - { - CreateWidgetFloatRamp(HouParameterCategory, InParams); - return; - } - // This parameter is a part of the last float ramp. - if (CurrentRampColor) - { - CreateWidgetColorRamp(HouParameterCategory, InParams); - return; - } - - switch (InParam->GetParameterType()) - { - case EHoudiniParameterType::Float: - { - CreateWidgetFloat(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Int: - { - CreateWidgetInt(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::String: - { - CreateWidgetString(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::IntChoice: - case EHoudiniParameterType::StringChoice: - { - CreateWidgetChoice(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Separator: - { - TArray SepParams; - if (CastParameters(InParams, SepParams)) - { - bool bEnabled = InParams.IsValidIndex(0) ? !SepParams[0]->IsDisabled() : true; - CreateWidgetSeparator(HouParameterCategory, InParams, bEnabled); - } - } - break; - - case EHoudiniParameterType::Color: - { - CreateWidgetColor(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Button: - { - CreateWidgetButton(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - CreateWidgetButtonStrip(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Label: - { - CreateWidgetLabel(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Toggle: - { - CreateWidgetToggle(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - CreateWidgetFile(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::FolderList: - { - CreateWidgetFolderList(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Folder: - { - CreateWidgetFolder(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::MultiParm: - { - CreateWidgetMultiParm(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::FloatRamp: - { - CreateWidgetFloatRamp(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::ColorRamp: - { - CreateWidgetColorRamp(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Input: - { - CreateWidgetOperatorPath(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Invalid: - { - HandleUnsupportedParmType(HouParameterCategory, InParams); - } - break; - - default: - { - HandleUnsupportedParmType(HouParameterCategory, InParams); - } - break; - } - - // Remove a divider lines recurrsively if current parameter hits the end of a tabs - RemoveTabDividers(HouParameterCategory, InParam); - -} - -void -FHoudiniParameterDetails::CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory) -{ - FDetailWidgetRow & Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()); - TSharedPtr TabEndingRow = SNew(SCustomizedBox); - - TabEndingRow->DividerLinePositions = DividerLinePositions; - - if (TabEndingRow.IsValid()) - CurrentTabEndingRow = TabEndingRow.Get(); - - Row.WholeRowWidget.Widget = TabEndingRow.ToSharedRef(); - Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniParameterDetails::CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam|| MainParam->IsPendingKill()) - return; - - if (!Row) - return; - - TSharedRef< SCustomizedBox > HorizontalBox = SNew(SCustomizedBox); - - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - - - if (MainParam->IsDirectChildOfMultiParm()) - { - FString ParameterLabelStr = MainParam->GetParameterLabel(); - - // If it is head of an multiparm instance - if (MainParam->GetChildIndex() == 0) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - } - ParameterLabelStr += TEXT(" (") + FString("") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - HorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - else - { - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(MainParam->GetParameterLabel()) : FText::GetEmpty(); - HorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - - Row->NameWidget.Widget = HorizontalBox; -} - -void -FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) -{ - if (!Row) - return; - - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - FString ParameterLabelStr = MainParam->GetParameterLabel(); - TSharedRef HorizontalBox = SNew(SCustomizedBox); - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - - TSharedPtr VerticalBox; - HorizontalBox->AddSlot() - [ - SAssignNew(VerticalBox, SVerticalBox) - ]; - - if (MainParam->IsDirectChildOfMultiParm()) - { - // If it is head of an multiparm instance - if (MainParam->GetChildIndex() == 0) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - } - - ParameterLabelStr += TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor* RampParameter = Cast(MainParam); - if (RampParameter) - { - if (RampParameter->bCaching) - ParameterLabelStr += "*"; - } - } - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - else - { - // TODO: Refactor me...extend 'auto/manual update' to all parameters? (It only applies to color and float ramps for now.) - bool bParamNeedUpdate = false; - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor* RampParameter = Cast(MainParam); - if (RampParameter) - bParamNeedUpdate = RampParameter->bCaching; - } - else if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat* RampParameter = Cast(MainParam); - if (RampParameter) - bParamNeedUpdate = RampParameter->bCaching; - } - - if (bParamNeedUpdate) - ParameterLabelStr += "*"; - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - - auto IsAutoUpdateChecked = [MainParam]() - { - if (!MainParam || MainParam->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainParam->IsAutoUpdate() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - auto OnAutoUpdateCheckBoxStateChanged = [MainParam, InParams, bCookingEnabled](ECheckBoxState NewState) - { - if (NewState == ECheckBoxState::Checked) - { - for (auto & NextSelectedParam : InParams) - { - if (!NextSelectedParam) - continue; - - if (NextSelectedParam->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not allow mode change when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) - continue; - - switch (MainParam->GetParameterType()) - { - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* ColorRampParameter = Cast(NextSelectedParam); - - if (!ColorRampParameter) - continue; - - // Do not sync the selected color ramp parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) - continue; - - // Sync the Cached curve points at update mode switch. - FHoudiniParameterDetails::SyncCachedColorRampPoints(ColorRampParameter); - } - break; - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParameter = Cast(NextSelectedParam); - - if (!FloatRampParameter) - continue; - - // Do not sync the selected float ramp parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) - continue; - - // Sync the Cached curve points at update mode switch. - //FHoudiniParameterDetails::SyncCachedFloatRampPoints(FloatRampParameter); - FloatRampParameter->SyncCachedPoints(); - } - break; - - default: - break; - } - - NextSelectedParam->SetAutoUpdate(true); - } - } - else - { - for (auto & NextSelectedParam : InParams) - { - if (!NextSelectedParam) - continue; - - if (!(NextSelectedParam->IsAutoUpdate() && bCookingEnabled)) - continue; - - // Do not allow mode change when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) - continue; - - NextSelectedParam->SetAutoUpdate(false); - } - } - }; - - // Auto update check box - TSharedPtr CheckBox; - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(SHorizontalBox) - - + SHorizontalBox::Slot() - [ - SAssignNew(CheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([OnAutoUpdateCheckBoxStateChanged](ECheckBoxState NewState) - { - OnAutoUpdateCheckBoxStateChanged(NewState); - }) - .IsChecked_Lambda([IsAutoUpdateChecked]() - { - return IsAutoUpdateChecked(); - }) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("AutoUpdate", "Auto-update")) - .ToolTipText(LOCTEXT("AutoUpdateTip", "When enabled, this parameter will automatically update its value while editing. Turning this off will allow you to more easily update it, and the update can be pushed by checking the toggle again.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - if ((MainParam->GetParameterType() != EHoudiniParameterType::FloatRamp) && (MainParam->GetParameterType() != EHoudiniParameterType::ColorRamp)) - CheckBox->SetVisibility(EVisibility::Hidden); - - Row->NameWidget.Widget = HorizontalBox; -} - -FDetailWidgetRow* -FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount) -{ - if (InParams.Num() <= 0) - return nullptr; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return nullptr; - - // Created row for the current parameter (if there is not a row created, do not show the parameter). - FDetailWidgetRow* Row = nullptr; - - // Current parameter is in a multiparm instance (directly) - if (MainParam->IsDirectChildOfMultiParm()) - { - int32 ParentMultiParmId = MainParam->GetParentParmId(); - - // If this is a folder param, its folder list parent parm is the multiparm - if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) - { - if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) // This should not happen - return nullptr; - - UHoudiniParameterFolderList* ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); - if (!ParentFolderList || ParentFolderList->IsPendingKill()) - return nullptr; // This should not happen - - ParentMultiParmId = ParentFolderList->GetParentParmId(); - } - - if (!AllMultiParms.Contains(ParentMultiParmId)) // This should not happen normally - return nullptr; - - // Get the parent multiparm - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentMultiParmId]; - - // The parent multiparm is visible. - if (ParentMultiParm && ParentMultiParm->IsShown() && MainParam->ShouldDisplay()) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - - } - // This item is not a direct child of a multiparm. - else - { - bool bIsFolder = MainParam->GetParameterType() == EHoudiniParameterType::Folder; - - // If this parameter is a folder, its parent folder should be the second top of the stack - int32 NestedMinStackDepth = bIsFolder ? 1 : 0; - - // Current parameter is inside a folder. - if (FolderStack.Num() > NestedMinStackDepth) - { - // If the current parameter is a folder, we take the top second queue on the stack, since the top one represents itself. - // Otherwise take the top queue on the stack. - TArray & CurrentLayerFolderQueue = bIsFolder ? - FolderStack[FolderStack.Num() - 2] : FolderStack.Last(); - - if (CurrentLayerFolderQueue.Num() <= 0) // Error state - return nullptr; - - bool bParentFolderVisible = CurrentLayerFolderQueue[0]->IsContentShown(); - - bool bIsSelectedTabVisible = false; - - // If its parent folder is visible, display current parameter, - // Otherwise, just prune the stacks. - if (bParentFolderVisible) - { - int32 ParentFolderId = MainParam->GetParentParmId(); - - // If the current parameter is a folder, its parent is a folderlist. - // So we need to continue to get the parent of the folderlist. - if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) - { - if (AllFoldersAndFolderLists.Contains(ParentFolderId)) - ParentFolderId = AllFoldersAndFolderLists[ParentFolderId]->GetParentParmId(); - else - return nullptr; // error state - } - - UHoudiniParameterFolder* ParentFolder = nullptr; - - if (AllFoldersAndFolderLists.Contains(ParentFolderId)) - ParentFolder = Cast(AllFoldersAndFolderLists[ParentFolderId]); - - bool bShouldDisplayRow = MainParam->ShouldDisplay(); - - // This row should be shown if its parent folder is shown. - if (ParentFolder) - bShouldDisplayRow &= (ParentFolder->IsTab() && ParentFolder->IsChosen()) || (!ParentFolder->IsTab() && ParentFolder->IsExpanded()); - - if (bShouldDisplayRow) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - } - - // prune the stack finally - if (bDecreaseChildCount) - { - CurrentLayerFolderQueue[0]->GetChildCounter() -= 1; - PruneStack(); - } - } - // If this parameter is in the root dir, just create a row. - else - { - if (MainParam->ShouldDisplay()) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - } - } - - if (!MainParam->IsVisible()) - return nullptr; - - - if (Row) - CurrentTabEndingRow = nullptr; - - return Row; -} - -void -FHoudiniParameterDetails::HandleUnsupportedParmType(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - CreateNestedRow(HouParameterCategory, (TArray)InParams); -} - -void -FHoudiniParameterDetails::CreateWidgetFloat( - IDetailCategoryBuilder & HouParameterCategory, - TArray& InParams ) -{ - TArray FloatParams; - if (!CastParameters(InParams, FloatParams)) - return; - - if (FloatParams.Num() <= 0) - return; - - UHoudiniParameterFloat* MainParam = FloatParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - // Helper function to find a unit from a string (name or abbreviation) - auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); - EUnit Unit = EUnit::Unspecified; - if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) - Unit = ParmUnit.GetValue(); - - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - // Lambdas for slider begin - auto SliderBegin = [&](TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), - FloatParams[0]->GetOuter()); - - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - FloatParams[Idx]->Modify(); - } - }; - - // Lambdas for slider end - auto SliderEnd = [&](TArray FloatParams) - { - // Mark the value as changed to trigger an update - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - FloatParams[Idx]->MarkChanged(true); - } - }; - - // Lambdas for changing the parameter value - auto ChangeFloatValueAt = [&](const float& Value, const int32& Index, const bool& DoChange, TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), - FloatParams[0]->GetOuter() ); - - bool bChanged = false; - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - FloatParams[Idx]->Modify(); - if (FloatParams[Idx]->SetValueAt(Value, Index)) - { - // Only mark the param has changed if DoChange is true!!! - if(DoChange) - FloatParams[Idx]->MarkChanged(true); - bChanged = true; - } - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if no parameter's value has actually been changed - Transaction.Cancel(); - } - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Revert to default value"), - FloatParams[0]->GetOuter()); - - if (TupleIndex < 0) - { - for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - if (FloatParams[Idx]->IsDefault()) - continue; - - FloatParams[Idx]->RevertToDefault(-1); - } - } - else - { - for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - if (FloatParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - FloatParams[Idx]->RevertToDefault(TupleIndex); - } - } - return FReply::Handled(); - }; - - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - //TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - if (MainParam->GetTupleSize() == 3) - { - // Should we swap Y and Z fields (only relevant for Vector3) - // Ignore the swapping if that parameter has the noswap tag - bool SwapVector3 = !MainParam->GetNoSwap(); - - auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float& Val, const bool& bDoChange) - { - ChangeFloatValueAt(Val, 0, bDoChange, FloatParams); - ChangeFloatValueAt(Val, 1, bDoChange, FloatParams); - ChangeFloatValueAt(Val, 2, bDoChange, FloatParams); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .AllowSpin(true) - .X(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, 0))) - .Y(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 2 : 1))) - .Z(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 1 : 2))) - .OnXCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, true); - else - ChangeFloatValueAt( Val, 0, true, FloatParams); - }) - .OnYCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, true); - else - ChangeFloatValueAt( Val, SwapVector3 ? 2 : 1, true, FloatParams); - }) - .OnZCommitted_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, true); - else - ChangeFloatValueAt( Val, SwapVector3 ? 1 : 2, true, FloatParams); - }) - .OnXChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, false); - else - ChangeFloatValueAt(Val, 0, false, FloatParams); - }) - .OnYChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, false); - else - ChangeFloatValueAt(Val, SwapVector3 ? 2 : 1, false, FloatParams); - }) - .OnZChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, false); - else - ChangeFloatValueAt(Val, SwapVector3 ? 1 : 2, false, FloatParams); - }) - .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) - .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("FloatParameterLockButtonToolTip", "When locked, change the vector value uniformly.")) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(MainParam->IsUniformLocked() ? FEditorStyle::GetBrush("Genericlock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([FloatParams, MainParam]() - { - if (!MainParam || MainParam->IsPendingKill()) - return FReply::Handled(); - - for (auto & CurParam : FloatParams) - { - if (!CurParam || CurParam->IsPendingKill()) - continue; - - CurParam->SwitchUniformLock(); - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - - return FReply::Handled(); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([FloatParams]() - { - for (auto & SelectedParam : FloatParams) - { - if (!SelectedParam) - continue; - - if (!SelectedParam->IsDefault()) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([FloatParams, RevertToDefault]() { return RevertToDefault(-1, FloatParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - else - { - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - TSharedPtr> NumericEntryBox; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< float >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - - .MinValue(MainParam->GetMin()) - .MaxValue(MainParam->GetMax()) - - .MinSliderValue(MainParam->GetUIMin()) - .MaxSliderValue(MainParam->GetUIMax()) - - .Value(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, Idx))) - .OnValueChanged_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) - .OnValueCommitted_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) - .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) - .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) - .SliderExponent(MainParam->IsLogarithmic() ?8.0f : 1.0f) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .OnClicked_Lambda([Idx, FloatParams, RevertToDefault]() { return RevertToDefault(Idx, FloatParams); }) - .Visibility_Lambda([Idx, FloatParams]() - { - for (auto & SelectedParam :FloatParams) - { - if (!SelectedParam) - continue; - - if (!SelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - } - } - - Row->ValueWidget.Widget =VerticalBox; - - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetInt(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray IntParams; - if (!CastParameters(InParams, IntParams)) - - if (IntParams.Num() <= 0) - return; - - UHoudiniParameterInt* MainParam = IntParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - // Helper function to find a unit from a string (name or abbreviation) - auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); - EUnit Unit = EUnit::Unspecified; - if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) - Unit = ParmUnit.GetValue(); - - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - // Lambda for slider begin - auto SliderBegin = [&](TArray IntParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), - IntParams[0]->GetOuter()); - - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - IntParams[Idx]->Modify(); - } - }; - - // Lambda for slider end - auto SliderEnd = [&](TArray IntParams) - { - // Mark the value as changed to trigger an update - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - IntParams[Idx]->MarkChanged(true); - } - }; - - // Lambda for changing the parameter value - auto ChangeIntValueAt = [&](const int32& Value, const int32& Index, const bool& DoChange, TArray IntParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), - IntParams[0]->GetOuter()); - - bool bChanged = false; - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - if (!IntParams[Idx]) - continue; - - IntParams[Idx]->Modify(); - if (IntParams[Idx]->SetValueAt(Value, Index)) - { - // Only mark the param has changed if DoChange is true!!! - if (DoChange) - IntParams[Idx]->MarkChanged(true); - bChanged = true; - } - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if there is no param has actually been changed - Transaction.Cancel(); - } - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray IntParams) - { - for (int32 Idx = 0; Idx < IntParams.Num(); Idx++) - { - if (!IntParams[Idx]) - continue; - - if (IntParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - IntParams[Idx]->RevertToDefault(TupleIndex); - } - - return FReply::Handled(); - }; - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - - .MinValue(MainParam->GetMin()) - .MaxValue(MainParam->GetMax()) - - .MinSliderValue(MainParam->GetUIMin()) - .MaxSliderValue(MainParam->GetUIMax()) - - .Value( TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterInt::GetValue, Idx))) - .OnValueChanged_Lambda( [=](int32 Val) { ChangeIntValueAt(Val, Idx, false, IntParams); } ) - .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeIntValueAt(Val, Idx, true, IntParams); }) - .OnBeginSliderMovement_Lambda( [=]() { SliderBegin(IntParams); }) - .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(IntParams); }) - .SliderExponent(MainParam->IsLogarithmic() ? 8.0f : 1.0f) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, IntParams]() - { - for (auto & NextSelectedParam : IntParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, IntParams, RevertToDefault]() { return RevertToDefault(Idx, IntParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - /* - if (NumericEntryBox.IsValid()) - NumericEntryBox->SetEnabled(!MainParam->IsDisabled()); - */ - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray StringParams; - if (!CastParameters(InParams, StringParams)) - return; - - if (StringParams.Num() <= 0) - return; - - UHoudiniParameterString* MainParam = StringParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - bool bIsMultiLine = false; - bool bIsUnrealRef = false; - UClass* UnrealRefClass = UObject::StaticClass(); - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - TMap& Tags = MainParam->GetTags(); - if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_TAG) && FCString::Atoi(*Tags[HOUDINI_PARAMETER_STRING_REF_TAG]) == 1) - { - bIsUnrealRef = true; - - if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_CLASS_TAG)) - { - UClass * FoundClass = FindObject(ANY_PACKAGE, *Tags[HOUDINI_PARAMETER_STRING_REF_CLASS_TAG]); - if (FoundClass != nullptr) - { - UnrealRefClass = FoundClass; - } - } - } - - if (Tags.Contains(HOUDINI_PARAMETER_STRING_MULTILINE_TAG)) - { - bIsMultiLine = true; - } - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - // Lambda for changing the parameter value - auto ChangeStringValueAt = [&](const FString& Value, UObject* ChosenObj, const int32& Index, const bool& DoChange, TArray StringParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterSrtingChange", "Houdini Parameter String: Changing a value"), - StringParams[0]->GetOuter()); - - bool bChanged = false; - for (int Idx = 0; Idx < StringParams.Num(); Idx++) - { - if (!StringParams[Idx]) - continue; - - StringParams[Idx]->Modify(); - if (StringParams[Idx]->SetValueAt(Value, Index)) - { - StringParams[Idx]->MarkChanged(true); - bChanged = true; - } - - StringParams[Idx]->SetAssetAt(ChosenObj, Index); - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if there is no param actually has been changed - Transaction.Cancel(); - } - - FHoudiniEngineUtils::UpdateEditorProperties(StringParams[0], false); - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray StringParams) - { - for (int32 Idx = 0; Idx < StringParams.Num(); Idx++) - { - if (!StringParams[Idx]) - continue; - - if (StringParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - StringParams[Idx]->RevertToDefault(TupleIndex); - } - - return FReply::Handled(); - }; - - if (bIsUnrealRef) - { - TSharedPtr EditableTextBox; - TSharedPtr HorizontalBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([UnrealRefClass](const UObject* InObject) - { - return InObject->IsA(UnrealRefClass); - }) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - ChangeStringValueAt(ReferenceStr, InObject, Idx, true, StringParams); - }) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - // Thumbnail - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouParameterCategory.GetParentLayout().GetThumbnailPool(); - - // Create a thumbnail for the selected object / class - UObject* EditObject = nullptr; - const FString AssetPath = MainParam->GetValueAt(Idx); - EditObject = StaticFindObject(nullptr, nullptr, *AssetPath, true); - - FAssetData AssetData; - if (IsValid(EditObject)) - { - AssetData = FAssetData(EditObject); - } - else - { - AssetData.AssetClass = UnrealRefClass->GetFName(); - } - - TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = MakeShareable( - new FAssetThumbnail(AssetData, 64, 64, AssetThumbnailPool)); - - TSharedPtr ThumbnailBorder; - HorizontalBox->AddSlot().Padding(0.f, 0.f, 2.f, 0.f).AutoWidth() - [ - SAssignNew(ThumbnailBorder, SBorder) - .OnMouseDoubleClick_Lambda([EditObject, Idx](const FGeometry&, const FPointerEvent&) - { - if (EditObject && GEditor) - GEditor->EditObject(EditObject); - - return FReply::Handled(); - }) - .Padding(5.f) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - TWeakPtr WeakThumbnailBorder(ThumbnailBorder); - ThumbnailBorder->SetBorderImage(TAttribute::Create( - TAttribute::FGetter::CreateLambda( - [WeakThumbnailBorder]() - { - TSharedPtr ThumbnailBorderPtr = WeakThumbnailBorder.Pin(); - if (ThumbnailBorderPtr.IsValid() && ThumbnailBorderPtr->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ) - )); - - FText MeshNameText = FText::GetEmpty(); - //if (InputObject) - // MeshNameText = FText::FromString(InputObject->GetName()); - - TSharedPtr StaticMeshComboButton; - - TSharedPtr ButtonBox; - HorizontalBox->AddSlot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .HAlign(HAlign_Fill) - [ - SAssignNew(ButtonBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SAssignNew(StaticMeshComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromName(AssetData.AssetName)) - .ToolTipText(FText::FromString(MainParam->GetValueAt(Idx))) - ] - ] - ] - ]; - - TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); - StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( - [UnrealRefClass, WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() - { - TArray AllowedClasses; - if (UnrealRefClass != UObject::StaticClass()) - { - // Use the class specified by the user - AllowedClasses.Add(UnrealRefClass); - } - else - { - // Using UObject would list way too many assets, and take a long time to open the menu, - // so we need to reestrict the classes a bit - AllowedClasses.Add(UStaticMesh::StaticClass()); - AllowedClasses.Add(UHoudiniAsset::StaticClass()); - AllowedClasses.Add(USkeletalMesh::StaticClass()); - AllowedClasses.Add(UBlueprint::StaticClass()); - AllowedClasses.Add(UMaterialInterface::StaticClass()); - AllowedClasses.Add(UTexture::StaticClass()); - AllowedClasses.Add(ULevel::StaticClass()); - AllowedClasses.Add(UStreamableRenderAsset::StaticClass()); - AllowedClasses.Add(USoundBase::StaticClass()); - AllowedClasses.Add(UParticleSystem::StaticClass()); - AllowedClasses.Add(UFoliageType::StaticClass()); - } - - TArray NewAssetFactories; - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(nullptr), - true, - AllowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda( - [WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) - { - TSharedPtr StaticMeshComboButtonPtr = WeakStaticMeshComboButton.Pin(); - if (StaticMeshComboButtonPtr.IsValid()) - { - StaticMeshComboButtonPtr->SetIsOpen(false); - - UObject * Object = AssetData.GetAsset(); - // Get the asset reference string for this object - // !! Accept null objects to allow clearing the asset picker !! - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); - - ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); - } - } - ), - FSimpleDelegate::CreateLambda([]() {})); - }) - ); - } - else if (bIsMultiLine) - { - TSharedPtr< SMultiLineEditableTextBox > MultiLineEditableTextBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) - {return true;}) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - FString NewString = ReferenceStr; - if (StringParams[0]->GetValueAt(Idx).Len() > 0) - NewString = StringParams[0]->GetValueAt(Idx) + "\n" + NewString; - - ChangeStringValueAt(NewString, nullptr, Idx, true, StringParams); - }) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Top).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(MultiLineEditableTextBox, SMultiLineEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainParam->GetValueAt(Idx))) - .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, StringParams]() - { - for (auto & NextSelectedParam : StringParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() { return RevertToDefault(Idx, StringParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - else - { - TSharedPtr< SEditableTextBox > EditableTextBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) - {return true;}) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - ChangeStringValueAt(ReferenceStr, nullptr, Idx, true, StringParams); - }) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(EditableTextBox, SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainParam->GetValueAt(Idx))) - .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) - { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, StringParams]() - { - for (auto & NextSelectedParam : StringParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() - { return RevertToDefault(Idx, StringParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ColorParams; - if (!CastParameters(InParams, ColorParams)) - return; - - if (ColorParams.Num() <= 0) - return; - - UHoudiniParameterColor* MainParam = ColorParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - bool bHasAlpha = (MainParam->GetTupleSize() == 4); - - // Add color picker UI. - TSharedPtr ColorBlock; - TSharedRef VerticalBox = SNew(SVerticalBox); - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(ColorBlock, SColorBlock) - .Color(MainParam->GetColorValue()) - .ShowBackgroundForAlpha(bHasAlpha) - ]; - - TWeakPtr WeakColorBlock(ColorBlock); - ColorBlock->SetOnMouseButtonDown(FPointerEventHandler::CreateLambda( - [MainParam, ColorParams, WeakColorBlock, bHasAlpha](const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) - { - if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) - return FReply::Unhandled(); - - TSharedPtr ColorBlockPtr = WeakColorBlock.Pin(); - FColorPickerArgs PickerArgs; - PickerArgs.ParentWidget = ColorBlockPtr.IsValid() ? ColorBlockPtr : nullptr; - PickerArgs.bUseAlpha = bHasAlpha; - PickerArgs.DisplayGamma = TAttribute< float >::Create( - TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), - MainParam->GetOuter(), true); - - bool bChanged = false; - for (auto & Param : ColorParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetColorValue(InColor)) - { - Param->MarkChanged(true); - bChanged = true; - } - } - - // cancel the transaction if there is actually no value changed - if (!bChanged) - { - Transaction.Cancel(); - } - }); - PickerArgs.InitialColorOverride = MainParam->GetColorValue(); - PickerArgs.bOnlyRefreshOnOk = true; - OpenColorPicker(PickerArgs); - return FReply::Handled(); - } - )); - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetButton(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ButtonParams; - if (!CastParameters(InParams, ButtonParams)) - return; - - if (ButtonParams.Num() <= 0) - return; - - UHoudiniParameterButton* MainParam = ButtonParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - FText ParameterTooltip = GetParameterTooltip(MainParam); - - TSharedRef HorizontalBox = SNew(SHorizontalBox); - TSharedPtr Button; - - // Add button UI. - HorizontalBox->AddSlot().Padding(1, 2, 4, 2) - [ - SAssignNew(Button, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ParameterLabelText) - .ToolTipText(ParameterTooltip) - .OnClicked(FOnClicked::CreateLambda( [MainParam, ButtonParams]() - { - for (auto & Param : ButtonParams) - { - if (!Param) - continue; - - // There is no undo redo operation for button - Param->MarkChanged(true); - } - - return FReply::Handled(); - })) - ]; - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ButtonStripParams; - if (!CastParameters(InParams, ButtonStripParams)) - return; - - if (ButtonStripParams.Num() <= 0) - return; - - UHoudiniParameterButtonStrip* MainParam = ButtonStripParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - if (!Row) - return; - - auto OnButtonStateChanged = [MainParam, ButtonStripParams](ECheckBoxState NewState, int32 Idx) - { - - /* - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterButtonStripChange", "Houdini Parameter Button Strip: Changing value"), - MainParam->GetOuter(), true); - */ - int32 StateInt = NewState == ECheckBoxState::Checked ? 1 : 0; - bool bChanged = false; - - for (auto & NextParam : ButtonStripParams) - { - if (!NextParam || NextParam->IsPendingKill()) - continue; - - if (!NextParam->Values.IsValidIndex(Idx)) - continue; - - //NextParam->Modify(); - if (NextParam->SetValueAt(Idx, StateInt)) - { - NextParam->MarkChanged(true); - bChanged = true; - } - } - - //if (!bChanged) - // Transaction.Cancel(); - - }; - - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - FText ParameterTooltip = GetParameterTooltip(MainParam); - - TSharedRef HorizontalBox = SNew(SHorizontalBox); - FLinearColor BgColor(0.53f, 0.81f, 0.82f, 1.0f); // Sky Blue Backgroud color - - for (int32 Idx = 0; Idx < MainParam->Count; ++Idx) - { - if (!MainParam->Values.IsValidIndex(Idx) || !MainParam->Labels.IsValidIndex(Idx)) - continue; - - bool bPressed = MainParam->Values[Idx] > 0; - FText LabelText = FText::FromString(MainParam->Labels[Idx]); - - TSharedPtr Button; - - HorizontalBox->AddSlot().Padding(0).FillWidth(1.0f) - [ - SAssignNew(Button, SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") - .IsChecked(bPressed ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - .OnCheckStateChanged_Lambda([OnButtonStateChanged, Idx](ECheckBoxState NewState) - { - OnButtonStateChanged(NewState, Idx); - }) - .Content() - [ - SNew(STextBlock) - .Text(LabelText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - Button->SetColorAndOpacity(BgColor); - } - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetLabel(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray LabelParams; - if (!CastParameters(InParams, LabelParams)) - return; - - if (LabelParams.Num() <= 0) - return; - - UHoudiniParameterLabel* MainParam = LabelParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - - for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) - { - FString NextLabelString = MainParam->GetStringAtIndex(Index); - FText ParameterLabelText = FText::FromString(NextLabelString); - - TSharedPtr TextBlock; - - // Add Label UI. - VerticalBox->AddSlot().Padding(1, 2, 4, 2) - [ - SAssignNew(TextBlock, STextBlock).Text(ParameterLabelText) - ]; - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetToggle(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray ToggleParams; - if (!CastParameters(InParams, ToggleParams)) - return; - - if (ToggleParams.Num() <= 0) - return; - - UHoudiniParameterToggle* MainParam = ToggleParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - - TSharedRef VerticalBox = SNew(SVerticalBox); - auto IsToggleCheckedLambda = [MainParam](int32 Index) - { - if (Index >= MainParam->GetNumValues()) - return ECheckBoxState::Unchecked; - - if (MainParam->GetValueAt(Index)) - return ECheckBoxState::Checked; - - return ECheckBoxState::Unchecked; - }; - - auto OnToggleCheckStateChanged = [MainParam, ToggleParams](ECheckBoxState NewState, int32 Index) - { - if (Index >= MainParam->GetNumValues()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterToggleChange", "Houdini Parameter Toggle: Changing value"), - MainParam->GetOuter(), true); - - bool bState = (NewState == ECheckBoxState::Checked); - - bool bChanged = false; - for (auto & Param : ToggleParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetValueAt(bState, Index)) - { - bChanged = true; - Param->MarkChanged(true); - } - } - - // Cancel the transaction if no parameter has actually been changed - if (!bChanged) - { - Transaction.Cancel(); - } - }; - - for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) - { - TSharedPtr< SCheckBox > CheckBox; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - [ - SAssignNew(CheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([OnToggleCheckStateChanged, Index](ECheckBoxState NewState) { - OnToggleCheckStateChanged(NewState, Index); - - }) - .IsChecked_Lambda([IsToggleCheckedLambda, Index]() { - return IsToggleCheckedLambda(Index); - }) - .Content() - [ - SNew(STextBlock) - .Text(ParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FileParams; - if (!CastParameters(InParams, FileParams)) - return; - - if (FileParams.Num() <= 0) - return; - - UHoudiniParameterFile* MainParam = FileParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - - FString FileTypeWidgetFilter = TEXT("All files (*.*)|*.*"); - if (!MainParam->GetFileFilters().IsEmpty()) - FileTypeWidgetFilter = FString::Printf(TEXT("%s files (%s)|%s"), *MainParam->GetFileFilters(), *MainParam->GetFileFilters(), *MainParam->GetFileFilters()); - - FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); - - auto UpdateCheckRelativePath = [MainParam](const FString & PickedPath) - { - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(MainParam->GetOuter()); - if (MainParam->GetOuter() && !PickedPath.IsEmpty() && FPaths::IsRelative(PickedPath)) - { - // Check if the path is relative to the UE4 project - FString AbsolutePath = FPaths::ConvertRelativePathToFull(PickedPath); - if (FPaths::FileExists(AbsolutePath)) - { - return AbsolutePath; - } - - // Check if the path is relative to the asset - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) - { - if (HoudiniAssetComponent->HoudiniAsset && !HoudiniAssetComponent->HoudiniAsset->IsPendingKill()) - { - FString AssetFilePath = FPaths::GetPath(HoudiniAssetComponent->HoudiniAsset->AssetFileName); - if (FPaths::FileExists(AssetFilePath)) - { - FString UpdatedFileWidgetPath = FPaths::Combine(*AssetFilePath, *PickedPath); - if (FPaths::FileExists(UpdatedFileWidgetPath)) - { - return UpdatedFileWidgetPath; - } - } - } - } - } - - return PickedPath; - }; - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - FString FileWidgetPath = MainParam->GetValueAt(Idx); - FString FileWidgetBrowsePath = BrowseWidgetDirectory; - - if (!FileWidgetPath.IsEmpty()) - { - FString FileWidgetDirPath = FPaths::GetPath(FileWidgetPath); - if (!FileWidgetDirPath.IsEmpty()) - FileWidgetBrowsePath = FileWidgetDirPath; - } - - bool IsDirectoryPicker = MainParam->GetParameterType() == EHoudiniParameterType::FileDir; - bool bIsNewFile = !MainParam->IsReadOnly(); - - FText BrowseTooltip = LOCTEXT("FileButtonToolTipText", "Choose a file from this computer"); - if (IsDirectoryPicker) - BrowseTooltip = LOCTEXT("DirButtonToolTipText", "Choose a directory from this computer"); - - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SNewFilePathPicker) - .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) - .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") - .BrowseButtonToolTip(BrowseTooltip) - .BrowseDirectory(FileWidgetBrowsePath) - .BrowseTitle(LOCTEXT("PropertyEditorTitle", "File picker...")) - .FilePath(FileWidgetPath) - .FileTypeFilter(FileTypeWidgetFilter) - .IsNewFile(bIsNewFile) - .IsDirectoryPicker(IsDirectoryPicker) - .ToolTipText_Lambda([MainParam]() - { - // return the current param value as a tooltip - FString FileValue = MainParam ? MainParam->GetValueAt(0) : FString(); - return FText::FromString(FileValue); - }) - .OnPathPicked(FOnPathPicked::CreateLambda([MainParam, FileParams, UpdateCheckRelativePath, Idx](const FString & PickedPath) - { - if (MainParam->GetNumValues() <= Idx) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFileChange", "Houdini Parameter File: Changing a file path"), - MainParam->GetOuter(), true); - - bool bChanged = false; - - for (auto & Param : FileParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetValueAt(UpdateCheckRelativePath(PickedPath), Idx)) - { - bChanged = true; - Param->MarkChanged(true); - } - } - - // Cancel the transaction if no value has actually been changed - if (!bChanged) - { - Transaction.Cancel(); - } - })) - ] - ]; - - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - - -void -FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ChoiceParams; - if (!CastParameters(InParams, ChoiceParams)) - return; - - if (ChoiceParams.Num() <= 0) - return; - - UHoudiniParameterChoice* MainParam = ChoiceParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - // Lambda for changing the parameter value - auto ChangeSelectionLambda = [ChoiceParams](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterChoiceChange", "Houdini Parameter Choice: Changing selection"), - ChoiceParams[0]->GetOuter()); - - const int32 NewIntValue = ChoiceParams[0]->GetIntValueFromLabel(*NewChoice); - - bool bChanged = false; - for (int Idx = 0; Idx < ChoiceParams.Num(); Idx++) - { - if (!ChoiceParams[Idx]) - continue; - - ChoiceParams[Idx]->Modify(); - if (ChoiceParams[Idx]->SetIntValue(NewIntValue)) - { - bChanged = true; - ChoiceParams[Idx]->MarkChanged(true); - ChoiceParams[Idx]->UpdateStringValueFromInt(); - } - } - - if (!bChanged) - { - // Cancel the transaction if no parameter was changed - Transaction.Cancel(); - } - }; - - // - MainParam->UpdateChoiceLabelsPtr(); - TArray>* OptionSource = MainParam->GetChoiceLabelsPtr(); - TSharedPtr IntialSelec; - if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValue())) - { - IntialSelec = (*OptionSource)[MainParam->GetIntValue()]; - } - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBox; - HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) - [ - SAssignNew( ComboBox, SComboBox< TSharedPtr< FString > > ) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - []( TSharedPtr< FString > InItem ) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [ChangeSelectionLambda](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - ChangeSelectionLambda(NewChoice, SelectType); - }) - [ - SNew(STextBlock) - .Text_Lambda([MainParam]() { return FText::FromString(MainParam->GetLabel()); }) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - ]; - - if ( ComboBox.IsValid() ) - ComboBox->SetEnabled( !MainParam->IsDisabled() ); - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetSeparator(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - TSharedRef HorizontalBox = SNew(SCustomizedBox); - - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - - Row->WholeRowWidget.Widget = HorizontalBox; -} - -void -FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray OperatorPathParams; - if (!CastParameters(InParams, OperatorPathParams)) - return; - - if (OperatorPathParams.Num() <= 0) - return; - - UHoudiniParameterOperatorPath* MainParam = OperatorPathParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - UHoudiniInput* MainInput = MainParam->HoudiniInput.Get(); - if (!MainInput) - return; - - // Build an array of edited inputs for multi edition - TArray EditedInputs; - EditedInputs.Add(MainInput); - - // Add the corresponding inputs found in the other HAC - for (int LinkedIdx = 1; LinkedIdx < OperatorPathParams.Num(); LinkedIdx++) - { - UHoudiniInput* LinkedInput = OperatorPathParams[LinkedIdx]->HoudiniInput.Get(); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if (!LinkedInput->Matches(*MainInput)) - continue; - - EditedInputs.Add(LinkedInput); - } - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FHoudiniInputDetails::CreateWidget(HouParameterCategory, EditedInputs, Row); - - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetFloatRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Parsing a float ramp: 1->(2->3->4)*->5 // - switch (MainParam->GetParameterType()) - { - //*****State 1: Float Ramp*****// - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); - if (FloatRampParameter) - { - CurrentRampFloat = FloatRampParameter; - CurrentRampFloatPointsArray.Empty(); - - CurrentRampParameterList = InParams; - - FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - CurrentRampRow = Row; - } - break; - } - - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParameter = Cast(MainParam); - if (FloatParameter) - { - bool bCreateNewPoint = true; - if (CurrentRampFloatPointsArray.Num() > 0) - { - UHoudiniParameterRampFloatPoint* LastPtInArr = CurrentRampFloatPointsArray.Last(); - if (LastPtInArr && !LastPtInArr->ValueParentParm) - bCreateNewPoint = false; - } - - //*****State 2: Float Parameter (position)*****// - if (bCreateNewPoint) - { - UHoudiniParameterRampFloatPoint* NewRampFloatPoint = nullptr; - - int32 PointIndex = CurrentRampFloatPointsArray.Num(); - if (CurrentRampFloat->Points.IsValidIndex(PointIndex)) - { - - // TODO: We should reuse existing point objects, if they exist. Currently - // this causes results in unexpected behaviour in other parts of this detail code. - // Give this code a bit of an overhaul at some point. - // NewRampFloatPoint = CurrentRampFloat->Points[PointIndex]; - } - - if (!NewRampFloatPoint) - { - // Create a new float ramp point, and add its pointer to the current float points buffer array. - NewRampFloatPoint = NewObject< UHoudiniParameterRampFloatPoint >(CurrentRampFloat, FName(), CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); - - } - - CurrentRampFloatPointsArray.Add(NewRampFloatPoint); - - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Set the float ramp point's position parent parm, and value - NewRampFloatPoint->PositionParentParm = FloatParameter; - NewRampFloatPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); - } - //*****State 3: Float Parameter (value)*****// - else - { - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Get the last point in the buffer array - if (CurrentRampFloatPointsArray.Num() > 0) - { - // Set the last inserted float ramp point's float parent parm, and value - UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); - LastAddedFloatRampPoint->ValueParentParm = FloatParameter; - LastAddedFloatRampPoint->SetValue(FloatParameter->GetValuesPtr()[0]); - } - } - } - - break; - } - //*****State 4: Choice parameter*****// - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); - if (ChoiceParameter && CurrentRampFloatPointsArray.Num() > 0) - { - // Set the last inserted float ramp point's interpolation parent parm, and value - UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); - - LastAddedFloatRampPoint->InterpolationParentParm = ChoiceParameter; - LastAddedFloatRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); - - // Set the index of this point in the multi parm. - LastAddedFloatRampPoint->InstanceIndex = CurrentRampFloatPointsArray.Num() - 1; - } - - - //*****State 5: All ramp points have been parsed, finish!*****// - if (CurrentRampFloatPointsArray.Num() >= (int32)CurrentRampFloat->MultiParmInstanceCount) - { - CurrentRampFloatPointsArray.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { - return P1.Position < P2.Position; - }); - - CurrentRampFloat->Points = CurrentRampFloatPointsArray; - - // Not caching, points are synced, update cached points - if (!CurrentRampFloat->bCaching) - { - const int32 NumPoints = CurrentRampFloat->Points.Num(); - CurrentRampFloat->CachedPoints.SetNum(NumPoints); - for (int32 i = 0; i < NumPoints; ++i) - { - UHoudiniParameterRampFloatPoint* FromPoint = CurrentRampFloat->Points[i]; - UHoudiniParameterRampFloatPoint* ToPoint = CurrentRampFloat->CachedPoints[i]; - ToPoint = nullptr; - check(FromPoint) - if (!ToPoint) - { - ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampFloat, RF_NoFlags, CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); - } - else - { - ToPoint->CopyStateFrom(FromPoint, true); - } - CurrentRampFloat->CachedPoints[i] = ToPoint; - } - } - - CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampFloat, CurrentRampParameterList); - - CurrentRampFloat->SetDefaultValues(); - - CurrentRampFloat = nullptr; - CurrentRampRow = nullptr; - } - - break; - } - - default: - break; - } - -} - -void -FHoudiniParameterDetails::CreateWidgetColorRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Parsing a color ramp: 1->(2->3->4)*->5 // - switch (MainParam->GetParameterType()) - { - //*****State 1: Color Ramp*****// - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* RampColor = Cast(MainParam); - if (RampColor) - { - CurrentRampColor = RampColor; - CurrentRampColorPointsArray.Empty(); - - CurrentRampParameterList = InParams; - - FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - CurrentRampRow = Row; - } - - break; - } - //*****State 2: Float parameter*****// - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParameter = Cast(MainParam); - if (FloatParameter) - { - // Create a new color ramp point, and add its pointer to the current color points buffer array. - UHoudiniParameterRampColorPoint* NewRampColorPoint = nullptr; - int32 PointIndex = CurrentRampColorPointsArray.Num(); - - if (CurrentRampColor->Points.IsValidIndex(PointIndex)) - { - NewRampColorPoint = CurrentRampColor->Points[PointIndex]; - } - - if (!NewRampColorPoint) - { - NewRampColorPoint = NewObject< UHoudiniParameterRampColorPoint >(CurrentRampColor, FName(), CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); - } - - CurrentRampColorPointsArray.Add(NewRampColorPoint); - - if (FloatParameter->GetNumberOfValues() <= 0) - return; - // Set the color ramp point's position parent parm, and value - NewRampColorPoint->PositionParentParm = FloatParameter; - NewRampColorPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); - } - - break; - } - //*****State 3: Color parameter*****// - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* ColorParameter = Cast(MainParam); - if (ColorParameter && CurrentRampColorPointsArray.Num() > 0) - { - // Set the last inserted color ramp point's color parent parm, and value - UHoudiniParameterRampColorPoint* LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); - LastAddedColorRampPoint->ValueParentParm = ColorParameter; - LastAddedColorRampPoint->SetValue(ColorParameter->GetColorValue()); - } - - break; - } - //*****State 4: Choice Parameter*****// - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); - if (ChoiceParameter) - { - // Set the last inserted color ramp point's interpolation parent parm, and value - UHoudiniParameterRampColorPoint*& LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); - - LastAddedColorRampPoint->InterpolationParentParm = ChoiceParameter; - LastAddedColorRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); - - // Set the index of this point in the multi parm. - LastAddedColorRampPoint->InstanceIndex = CurrentRampColorPointsArray.Num() - 1; - } - - - //*****State 5: All ramp points have been parsed, finish!*****// - if (CurrentRampColorPointsArray.Num() >= (int32)CurrentRampColor->MultiParmInstanceCount) - { - CurrentRampColorPointsArray.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) - { - return P1.Position < P2.Position; - }); - - CurrentRampColor->Points = CurrentRampColorPointsArray; - - // Not caching, points are synced, update cached points - - if (!CurrentRampColor->bCaching) - { - const int32 NumPoints = CurrentRampColor->Points.Num(); - CurrentRampColor->CachedPoints.SetNum(NumPoints); - - for (int32 i = 0; i < NumPoints; ++i) - { - UHoudiniParameterRampColorPoint* FromPoint = CurrentRampColor->Points[i]; - UHoudiniParameterRampColorPoint* ToPoint = CurrentRampColor->CachedPoints[i]; - - if (!ToPoint) - { - ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampColor, RF_NoFlags, CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); - } - else - { - ToPoint->CopyStateFrom(FromPoint, true); - } - CurrentRampColor->CachedPoints[i] = ToPoint; - } - } - - - CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampColor, CurrentRampParameterList); - - CurrentRampColor->SetDefaultValues(); - - CurrentRampColor = nullptr; - CurrentRampRow = nullptr; - } - - break; - } - - default: - break; - } - -} - - -FDetailWidgetRow* -FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() <= 0) - return nullptr; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam) - return nullptr; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, (TArray)InParams); - if (!Row) - return nullptr; - - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - - // Create the standard parameter name widget with an added autoupdate checkbox. - CreateNameWidgetWithAutoUpdate(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor *RampColorParam = Cast(MainParam); - if (!RampColorParam) - return nullptr; - - TSharedPtr ColorGradientEditor; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - SAssignNew(ColorGradientEditor, SHoudiniColorRampCurveEditor) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - ] - ]; - - if (!ColorGradientEditor.IsValid()) - return nullptr; - - // Avoid showing tooltips inside of the curve editor - ColorGradientEditor->EnableToolTipForceField(true); - - CurrentRampParameterColorCurve = NewObject( - GetTransientPackage(), UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); - - if (!CurrentRampParameterColorCurve) - return nullptr; - - CreatedColorRampCurves.Add(CurrentRampParameterColorCurve); - - // Add the ramp curve to root to avoid garabage collected. - CurrentRampParameterColorCurve->AddToRoot(); - - TArray ColorRampParameters; - CastParameters(InParams, ColorRampParameters); - - for (auto NextColorRamp : ColorRampParameters) - { - CurrentRampParameterColorCurve->ColorRampParameters.Add(NextColorRamp); - } - ColorGradientEditor->HoudiniColorRampCurve = CurrentRampParameterColorCurve; - - // Clear default curve points - for (int Idx = 0; Idx < 4; ++Idx) - { - FRichCurve& RichCurve = (CurrentRampParameterColorCurve->FloatCurves)[Idx]; - if (RichCurve.GetNumKeys() > 0) - RichCurve.Keys.Empty(); - } - ColorGradientEditor->SetCurveOwner(CurrentRampParameterColorCurve); - CreatedColorGradientEditors.Add(ColorGradientEditor); - } - else if(MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat *RampFloatParam = Cast(MainParam); - if (!RampFloatParam) - return nullptr; - - TSharedPtr FloatCurveEditor; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - SAssignNew(FloatCurveEditor, SHoudiniFloatRampCurveEditor) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - .HideUI(true) - .DrawCurve(true) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - .ViewMinOutput(0.0f) - .ViewMaxOutput(1.0f) - .TimelineLength(1.0f) - .AllowZoomOutput(false) - .ShowInputGridNumbers(false) - .ShowOutputGridNumbers(false) - .ShowZoomButtons(false) - .ZoomToFitHorizontal(false) - .ZoomToFitVertical(false) - .XAxisName(FString("X")) - .YAxisName(FString("Y")) - .ShowCurveSelector(false) - - ] - ]; - - if (!FloatCurveEditor.IsValid()) - return nullptr; - - // Avoid showing tooltips inside of the curve editor - FloatCurveEditor->EnableToolTipForceField(true); - - CurrentRampParameterFloatCurve = NewObject( - GetTransientPackage(), UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); - - if (!CurrentRampParameterFloatCurve) - return nullptr; - - CreatedFloatRampCurves.Add(CurrentRampParameterFloatCurve); - - // Add the ramp curve to root to avoid garbage collected - CurrentRampParameterFloatCurve->AddToRoot(); - - TArray FloatRampParameters; - CastParameters(InParams, FloatRampParameters); - for (auto NextFloatRamp : FloatRampParameters) - { - CurrentRampParameterFloatCurve->FloatRampParameters.Add(NextFloatRamp); - } - FloatCurveEditor->HoudiniFloatRampCurve = CurrentRampParameterFloatCurve; - - FloatCurveEditor->SetCurveOwner(CurrentRampParameterFloatCurve, true); - CreatedFloatCurveEditors.Add(FloatCurveEditor); - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - return Row; -} - - -void -FHoudiniParameterDetails::CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray& InParams) -{ - if (!Row || !InParameter) - return; - - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - UHoudiniParameterRampFloat * MainFloatRampParameter = nullptr; - UHoudiniParameterRampColor * MainColorRampParameter = nullptr; - - TArray FloatRampParameterList; - TArray ColorRampParameterList; - if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - MainFloatRampParameter = Cast(MainParam); - - if (!MainFloatRampParameter) - return; - - if (!CastParameters(InParams, FloatRampParameterList)) - return; - } - else if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - MainColorRampParameter = Cast(MainParam); - - if (!MainColorRampParameter) - return; - - if (!CastParameters(InParams, ColorRampParameterList)) - return; - } - else - { - return; - } - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Lambda for computing the float point to be inserted - auto GetInsertFloatPointLambda = [MainFloatRampParameter]( - const int32& InsertAtIndex, - float& OutPosition, - float& OutValue, - EHoudiniRampInterpolationType& OutInterpType) mutable - { - if (!MainFloatRampParameter) - return; - - float PrevPosition = 0.0f; - float NextPosition = 1.0f; - - TArray &CurrentPoints = MainFloatRampParameter->Points; - TArray &CachedPoints = MainFloatRampParameter->CachedPoints; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - int32 NumPoints = 0; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - { - NumPoints = CurrentPoints.Num(); - } - else - { - MainFloatRampParameter->SetCaching(true); - NumPoints = CachedPoints.Num(); - } - - if (InsertAtIndex >= NumPoints) - { - // Insert at the end - if (NumPoints > 0) - { - UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - PrevPoint = CurrentPoints.Last(); - else - PrevPoint = CachedPoints.Last(); - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - } - } - else if (InsertAtIndex <= 0) - { - // Insert at the beginning - if (NumPoints > 0) - { - UHoudiniParameterRampFloatPoint* NextPoint = nullptr; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - NextPoint = CurrentPoints[0]; - else - NextPoint = CachedPoints[0]; - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - OutInterpType = NextPoint->GetInterpolation(); - } - } - } - else - { - // Insert in the middle - if (NumPoints > 1) - { - UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; - UHoudiniParameterRampFloatPoint* NextPoint = nullptr; - - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - { - PrevPoint = CurrentPoints[InsertAtIndex - 1]; - NextPoint = CurrentPoints[InsertAtIndex]; - } - else - { - PrevPoint = CachedPoints[InsertAtIndex - 1]; - NextPoint = CachedPoints[InsertAtIndex]; - } - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - } - - if (PrevPoint && NextPoint) - { - OutValue = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; - } - } - } - - OutPosition = (PrevPosition + NextPosition) / 2.0f; - }; - - - // Lambda for computing the color point to be inserted - auto GetInsertColorPointLambda = [MainColorRampParameter]( - const int32& InsertAtIndex, - float& OutPosition, - FLinearColor& OutColor, - EHoudiniRampInterpolationType& OutInterpType) mutable - { - if (!MainColorRampParameter) - return; - - float PrevPosition = 0.0f; - float NextPosition = 1.0f; - - TArray &CurrentPoints = MainColorRampParameter->Points; - TArray &CachedPoints = MainColorRampParameter->CachedPoints; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - int32 NumPoints = 0; - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NumPoints = CurrentPoints.Num(); - else - NumPoints = CachedPoints.Num(); - - if (InsertAtIndex >= NumPoints) - { - // Insert at the end - if (NumPoints > 0) - { - UHoudiniParameterRampColorPoint* PrevPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - PrevPoint = CurrentPoints.Last(); - else - PrevPoint = CachedPoints.Last(); - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - } - } - else if (InsertAtIndex <= 0) - { - // Insert at the beginning - if (NumPoints > 0) - { - UHoudiniParameterRampColorPoint* NextPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NextPoint = CurrentPoints[0]; - else - NextPoint = CachedPoints[0]; - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - OutInterpType = NextPoint->GetInterpolation(); - } - } - } - else - { - // Insert in the middle - if (NumPoints > 1) - { - UHoudiniParameterRampColorPoint* PrevPoint = nullptr; - UHoudiniParameterRampColorPoint* NextPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - { - PrevPoint = CurrentPoints[InsertAtIndex - 1]; - NextPoint = CurrentPoints[InsertAtIndex]; - } - else - { - PrevPoint = CachedPoints[InsertAtIndex - 1]; - NextPoint = CachedPoints[InsertAtIndex]; - } - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - } - - if (PrevPoint && NextPoint) - { - OutColor = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; - } - } - } - - OutPosition = (PrevPosition + NextPosition) / 2.0f; - }; - - int32 RowIndex = 0; - auto InsertRampPoint_Lambda = [GetInsertColorPointLambda, GetInsertFloatPointLambda, &CategoryBuilder, bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, - UHoudiniParameterRampColor* MainRampColor, - TArray &RampFloatList, - TArray &RampColorList, - const int32& Index) mutable - { - if (MainRampFloat) - { - float InsertPosition = 0.0f; - float InsertValue = 1.0f; - EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; - - GetInsertFloatPointLambda(Index, InsertPosition, InsertValue, InsertInterp); - - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); - - for (auto & NextFloatRamp : RampFloatList) - { - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - CreateFloatRampParameterInsertEvent( - NextFloatRamp, InsertPosition, InsertValue, InsertInterp); - - NextFloatRamp->MarkChanged(true); - } - else - { - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject - (NextFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); - NewCachedPoint->Position = InsertPosition; - NewCachedPoint->Value = InsertValue; - NewCachedPoint->Interpolation = InsertInterp; - - NextFloatRamp->CachedPoints.Add(NewCachedPoint); - NextFloatRamp->bCaching = true; - if (!bCookingEnabled) - { - // If cooking is not enabled, be sure to mark this parameter as changed - // so that it triggers an update once cooking is enabled again. - NextFloatRamp->MarkChanged(true); - } - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - { - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - } - else if (MainRampColor) - { - float InsertPosition = 0.0f; - FLinearColor InsertColor = FLinearColor::Black; - EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; - - GetInsertColorPointLambda(Index, InsertPosition, InsertColor, InsertInterp); - - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); - for (auto& NextColorRamp : RampColorList) - { - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - CreateColorRampParameterInsertEvent( - NextColorRamp, InsertPosition, InsertColor, InsertInterp); - - NextColorRamp->MarkChanged(true); - } - else - { - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject - (NextColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); - NewCachedPoint->Position = InsertPosition; - NewCachedPoint->Value = InsertColor; - NewCachedPoint->Interpolation = InsertInterp; - - NextColorRamp->CachedPoints.Add(NewCachedPoint); - NextColorRamp->bCaching = true; - if (!bCookingEnabled) - NextColorRamp->MarkChanged(true); - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - auto DeleteRampPoint_Lambda = [bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, - UHoudiniParameterRampColor* MainRampColor, - TArray &FloatRampList, - TArray &ColorRampList, - const int32& Index) mutable - { - if (MainRampFloat) - { - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampList); - - for (auto& NextFloatRamp : FloatRampList) - { - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextFloatRamp->Points.Num() == 0) - return; - - UHoudiniParameterRampFloatPoint* PointToDelete = nullptr; - - if (Index == -1) - PointToDelete = NextFloatRamp->Points.Last(); - else if (NextFloatRamp->Points.IsValidIndex(Index)) - PointToDelete = NextFloatRamp->Points[Index]; - - if (!PointToDelete) - return; - - const int32 & InstanceIndexToDelete = PointToDelete->InstanceIndex; - - CreateFloatRampParameterDeleteEvent(NextFloatRamp, InstanceIndexToDelete); - NextFloatRamp->MarkChanged(true); - } - else - { - if (NextFloatRamp->CachedPoints.Num() == 0) - return; - - if (Index == -1) - NextFloatRamp->CachedPoints.Pop(); - else if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) - NextFloatRamp->CachedPoints.RemoveAt(Index); - else - return; - - NextFloatRamp->bCaching = true; - if (!bCookingEnabled) - NextFloatRamp->MarkChanged(true); - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); - } - } - else - { - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampList); - - for (auto& NextColorRamp : ColorRampList) - { - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextColorRamp->Points.Num() == 0) - return; - - UHoudiniParameterRampColorPoint* PointToRemove = nullptr; - - if (Index == -1) - PointToRemove = NextColorRamp->Points.Last(); - else if (NextColorRamp->Points.IsValidIndex(Index)) - PointToRemove = NextColorRamp->Points[Index]; - - if (!PointToRemove) - return; - - const int32 & InstanceIndexToDelete = PointToRemove->InstanceIndex; - - CreateColorRampParameterDeleteEvent(NextColorRamp, InstanceIndexToDelete); - - NextColorRamp->MarkChanged(true); - } - else - { - if (NextColorRamp->CachedPoints.Num() == 0) - return; - - if (Index == -1) - NextColorRamp->CachedPoints.Pop(); - else if (NextColorRamp->CachedPoints.IsValidIndex(Index)) - NextColorRamp->CachedPoints.RemoveAt(Index); - else - return; - - NextColorRamp->bCaching = true; - if (!bCookingEnabled) - NextColorRamp->MarkChanged(true); - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - - TSharedRef VerticalBox = StaticCastSharedRef(Row->ValueWidget.Widget); - - TSharedPtr GridPanel; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SAssignNew(GridPanel, SUniformGridPanel) - ]; - - //AllUniformGridPanels.Add(GridPanel.Get()); - - GridPanel->SetSlotPadding(FMargin(2.f, 2.f, 5.f, 3.f)); - GridPanel->AddSlot(0, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Position"))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - FString ValueString = TEXT("Value"); - if (!MainFloatRampParameter) - ValueString = TEXT("Color"); - - GridPanel->AddSlot(1, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(ValueString)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - GridPanel->AddSlot(2, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Interp."))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - - GridPanel->AddSlot(3, RowIndex) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton( - FSimpleDelegate::CreateLambda([InsertRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, bCookingEnabled]() mutable - { - int32 InsertAtIndex = -1; - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - InsertAtIndex = MainFloatRampParameter->Points.Num(); - else - InsertAtIndex = MainFloatRampParameter->CachedPoints.Num(); - } - else if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - InsertAtIndex = MainColorRampParameter->Points.Num(); - else - InsertAtIndex = MainColorRampParameter->CachedPoints.Num(); - } - - InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, InsertAtIndex); - }), - LOCTEXT("AddRampPoint", "Add a ramp point to the end"), true) - ] - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeRemoveButton( - FSimpleDelegate::CreateLambda([DeleteRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList]() mutable - { - DeleteRampPoint_Lambda( - MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, -1); - }), - LOCTEXT("DeleteRampPoint", "Delete the last ramp point"), true) - ] - - ]; - - EUnit Unit = EUnit::Unspecified; - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - int32 PointCount = 0; - // Use Synced points on auto update mode - // Use Cached points on manual update mode - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - PointCount = MainFloatRampParameter->Points.Num(); - else - PointCount = MainFloatRampParameter->CachedPoints.Num(); - } - - if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate()) - PointCount = MainColorRampParameter->Points.Num(); - else - PointCount = MainColorRampParameter->CachedPoints.Num(); - } - - // Lambda function for changing a ramp point - auto OnPointChangeCommit = [bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, UHoudiniParameterRampColor* MainRampColor, - UHoudiniParameterRampFloatPoint* MainRampFloatPoint, UHoudiniParameterRampColorPoint* MainRampColorPoint, - TArray &RampFloatList, TArray &RampColorList, - const int32& Index, const FString& ChangedDataName, - const float& NewPosition, const float& NewFloat, - const FLinearColor& NewColor, - const EHoudiniRampInterpolationType& NewInterpType) mutable - { - if (MainRampFloat && MainRampFloatPoint) - { - if (ChangedDataName == FString("position") && MainRampFloatPoint->GetPosition() == NewPosition) - return; - if (ChangedDataName == FString("value") && MainRampFloatPoint->GetValue() == NewFloat) - return; - if (ChangedDataName == FString("interp") && MainRampFloatPoint->GetInterpolation() == NewInterpType) - return; - - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); - for (auto NextFloatRamp : RampFloatList) - { - if (!NextFloatRamp) - continue; - - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextFloatRamp->Points.IsValidIndex(Index)) - { - UHoudiniParameterRampFloatPoint* CurrentFloatRampPoint = NextFloatRamp->Points[Index]; - if (!CurrentFloatRampPoint) - continue; - - if (ChangedDataName == FString("position")) - { - if (!CurrentFloatRampPoint->PositionParentParm) - continue; - - CurrentFloatRampPoint->SetPosition(NewPosition); - CurrentFloatRampPoint->PositionParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("value")) - { - if (!CurrentFloatRampPoint->PositionParentParm) - continue; - - CurrentFloatRampPoint->SetValue(NewFloat); - CurrentFloatRampPoint->ValueParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("interp")) - { - if (!CurrentFloatRampPoint->InterpolationParentParm) - continue; - - CurrentFloatRampPoint->SetInterpolation(NewInterpType); - CurrentFloatRampPoint->InterpolationParentParm->MarkChanged(true); - } - } - else - { - int32 IdxInEventsArray = Index - NextFloatRamp->Points.Num(); - if (NextFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = NextFloatRamp->ModificationEvents[IdxInEventsArray]; - if (!Event) - continue; - - if (ChangedDataName == FString("position")) - { - Event->InsertPosition = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - Event->InsertFloat = NewFloat; - } - else if (ChangedDataName == FString("interp")) - { - Event->InsertInterpolation = NewInterpType; - } - } - } - } - else - { - if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) - { - UHoudiniParameterRampFloatPoint* CachedPoint = NextFloatRamp->CachedPoints[Index]; - - if (!CachedPoint) - continue; - - if (ChangedDataName == FString("position")) - { - CachedPoint->Position = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - CachedPoint->Value = NewFloat; - } - else if (ChangedDataName == FString("interp")) - { - CachedPoint->Interpolation = NewInterpType; - } - - NextFloatRamp->bCaching = true; - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); - } - } - else if (MainRampColor && MainRampColorPoint) - { - if (ChangedDataName == FString("position") && MainRampColorPoint->GetPosition() == NewPosition) - return; - - if (ChangedDataName == FString("value") && MainRampColorPoint->GetValue() == NewColor) - return; - - if (ChangedDataName == FString("interp") && MainRampColorPoint->GetInterpolation() == NewInterpType) - return; - - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); - for (auto NextColorRamp : RampColorList) - { - if (!NextColorRamp) - continue; - - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextColorRamp->Points.IsValidIndex(Index)) - { - UHoudiniParameterRampColorPoint* CurrentColorRampPoint = NextColorRamp->Points[Index]; - if (!CurrentColorRampPoint) - continue; - - if (ChangedDataName == FString("position")) - { - if (!CurrentColorRampPoint->PositionParentParm) - continue; - - CurrentColorRampPoint->SetPosition(NewPosition); - CurrentColorRampPoint->PositionParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("value")) - { - if (!CurrentColorRampPoint->PositionParentParm) - continue; - - CurrentColorRampPoint->SetValue(NewColor); - CurrentColorRampPoint->ValueParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("interp")) - { - if (!CurrentColorRampPoint->InterpolationParentParm) - continue; - - CurrentColorRampPoint->SetInterpolation(NewInterpType); - CurrentColorRampPoint->InterpolationParentParm->MarkChanged(true); - } - } - else - { - int32 IdxInEventsArray = Index - NextColorRamp->Points.Num(); - if (NextColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = NextColorRamp->ModificationEvents[IdxInEventsArray]; - if (!Event) - continue; - - if (ChangedDataName == FString("position")) - { - Event->InsertPosition = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - Event->InsertColor = NewColor; - } - else if (ChangedDataName == FString("interp")) - { - Event->InsertInterpolation = NewInterpType; - } - - } - } - } - else - { - if (NextColorRamp->CachedPoints.IsValidIndex(Index)) - { - UHoudiniParameterRampColorPoint* CachedPoint = NextColorRamp->CachedPoints[Index]; - - if (!CachedPoint) - continue; - - if (ChangedDataName == FString("position")) - { - CachedPoint->Position = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - CachedPoint->Value = NewColor; - } - else if (ChangedDataName == FString("interp")) - { - CachedPoint->Interpolation = NewInterpType; - } - - NextColorRamp->bCaching = true; - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - for (int32 Index = 0; Index < PointCount; ++Index) - { - UHoudiniParameterRampFloatPoint* NextFloatRampPoint = nullptr; - UHoudiniParameterRampColorPoint* NextColorRampPoint = nullptr; - - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - NextFloatRampPoint = MainFloatRampParameter->Points[Index]; - else - NextFloatRampPoint = MainFloatRampParameter->CachedPoints[Index]; - } - if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NextColorRampPoint = MainColorRampParameter->Points[Index]; - else - NextColorRampPoint = MainColorRampParameter->CachedPoints[Index]; - } - - if (!NextFloatRampPoint && !NextColorRampPoint) - continue; - - RowIndex += 1; - - float CurPos = 0.f; - if (NextFloatRampPoint) - CurPos = NextFloatRampPoint->Position; - else - CurPos = NextColorRampPoint->Position; - - - GridPanel->AddSlot(0, RowIndex) - [ - SNew(SNumericEntryBox) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Value(CurPos) - .OnValueChanged_Lambda([](float Val) {}) - .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("position"), - Val, float(-1.0), - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .OnBeginSliderMovement_Lambda([]() {}) - .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("position"), - Val, float(-1.0), - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .SliderExponent(1.0f) - .TypeInterface(paramTypeInterface) - ]; - - if (NextFloatRampPoint) - { - GridPanel->AddSlot(1, RowIndex) - [ - SNew(SNumericEntryBox< float >) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Value(NextFloatRampPoint->Value) - .OnValueChanged_Lambda([](float Val){}) - .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), Val, - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .OnBeginSliderMovement_Lambda([]() {}) - .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), Val, - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .SliderExponent(1.0f) - .TypeInterface(paramTypeInterface) - ]; - } - else if (NextColorRampPoint) - { - auto OnColorChangeLambda = [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](FLinearColor InColor) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), float(-1.0), - InColor, - EHoudiniRampInterpolationType::LINEAR); - }; - - // Add color picker UI. - //TSharedPtr ColorBlock; - GridPanel->AddSlot(1, RowIndex) - [ - SNew(SColorBlock) - .Color(NextColorRampPoint->Value) - .OnMouseButtonDown( FPointerEventHandler::CreateLambda( - [NextColorRampPoint, OnColorChangeLambda](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) - { - if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) - return FReply::Unhandled(); - - FColorPickerArgs PickerArgs; - PickerArgs.bUseAlpha = true; - PickerArgs.DisplayGamma = TAttribute< float >::Create( - TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda(OnColorChangeLambda); - FLinearColor InitColor = NextColorRampPoint->Value; - PickerArgs.InitialColorOverride = InitColor; - PickerArgs.bOnlyRefreshOnOk = true; - OpenColorPicker(PickerArgs); - return FReply::Handled(); - })) - ]; - } - - int32 CurChoice = 0; - if (NextFloatRampPoint) - CurChoice = (int)NextFloatRampPoint->Interpolation; - else - CurChoice = (int)NextColorRampPoint->Interpolation; - - TSharedPtr >> ComboBoxCurveMethod; - GridPanel->AddSlot(2, RowIndex) - [ - SAssignNew(ComboBoxCurveMethod, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels())[CurChoice]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, - ColorRampParameterList, Index](TSharedPtr NewChoice, ESelectInfo::Type SelectType) mutable - { - EHoudiniRampInterpolationType NewInterpType = UHoudiniParameter::GetHoudiniInterpMethodFromString(*NewChoice.Get()); - - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("interp"), - float(-1.0), float(-1.0), - FLinearColor(), - NewInterpType); - }) - [ - SNew(STextBlock) - .Text_Lambda([NextFloatRampPoint, NextColorRampPoint]() - { - EHoudiniRampInterpolationType CurInterpType = EHoudiniRampInterpolationType::InValid; - if (NextFloatRampPoint) - CurInterpType = NextFloatRampPoint->GetInterpolation(); - else - CurInterpType = NextColorRampPoint->GetInterpolation(); - - return FText::FromString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(CurInterpType)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - GridPanel->AddSlot(3, RowIndex) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateLambda( - [InsertRampPoint_Lambda, MainFloatRampParameter, - MainColorRampParameter, FloatRampParameterList, - ColorRampParameterList, Index]() mutable - { - InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); - }), - LOCTEXT("AddRampPoint", "Add a ramp point before this point"), true) - ] - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateLambda( - [DeleteRampPoint_Lambda, MainFloatRampParameter, - MainColorRampParameter, FloatRampParameterList, - ColorRampParameterList, Index]() mutable - { - DeleteRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); - }), - LOCTEXT("DeleteFloatRampPoint", "Delete this ramp point"), true) - ] - ]; - - if (MainFloatRampParameter && CurrentRampParameterFloatCurve) - { - ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextFloatRampPoint->GetInterpolation()); - FRichCurve & RichCurve = CurrentRampParameterFloatCurve->FloatCurve; - FKeyHandle const KeyHandle = RichCurve.AddKey(NextFloatRampPoint->GetPosition(), NextFloatRampPoint->GetValue()); - RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); - } - - if (MainColorRampParameter && CurrentRampParameterColorCurve) - { - ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextColorRampPoint->GetInterpolation()); - for (int32 CurveIdx = 0; CurveIdx < 4; ++CurveIdx) - { - FRichCurve & RichCurve = CurrentRampParameterColorCurve->FloatCurves[CurveIdx]; - - FKeyHandle const KeyHandle = RichCurve.AddKey(NextColorRampPoint->GetPosition(), NextColorRampPoint->GetValue().Component(CurveIdx)); - RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); - } - } - } - - if (MainFloatRampParameter) - GridPanel->SetEnabled(!MainFloatRampParameter->IsDisabled()); - - if (MainColorRampParameter) - GridPanel->SetEnabled(!MainColorRampParameter->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetFolderList(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FolderListParams; - if (!CastParameters(InParams, FolderListParams)) - return; - - if (FolderListParams.Num() <= 0) - return; - - UHoudiniParameterFolderList* MainParam = FolderListParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Add this folder list to the folder map - AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); - - MainParam->GetTabs().Empty(); - - // A folder list will be followed by all its child folders, - // so set the CurrentFolderListSize to the tuple size, we'll process such many folder parameters right after - CurrentFolderListSize = MainParam->GetTupleSize(); - - if (MainParam->IsDirectChildOfMultiParm()) - MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); - - if (CurrentFolderListSize <= 0) // There should not be empty folder list, this will not happen normally - return; - - // The following folders belong to current folder list - CurrentFolderList = MainParam; - - // If the tab is either a tabs or radio button and the parameter is visible - if (MainParam->IsTabMenu() && MainParam->ShouldDisplay()) - { - // Set the current tabs to be not shown by default now. CreateWidgetTab will decide if the tabs is shown. - CurrentFolderList->SetTabsShown(false); - - // Create a row to hold tab buttons if the folder list is a tabs or radio button - - // CreateNestedRow does not actually create a row for tabs, it is responsible to prune the folder stack. - // ( CreateWidgetTab will be responsible to create a row according to the visibility of its outer level folders ) - FDetailWidgetRow* TabRow = CreateNestedRow(HouParameterCategory, InParams, false); - - } - - // When see a folder list, go depth first search at this step. - // Push an empty queue to the stack. - FolderStack.Add(TArray()); -} - - -void -FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FolderParams; - if (!CastParameters(InParams, FolderParams)) - return; - - if (FolderParams.Num() <= 0) - return; - - UHoudiniParameterFolder* MainParam = FolderParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen - return; - // If a folder is invisible, its children won't be listed by HAPI. - // So just reduce FolderListSize by 1, reduce the child counter of its parent folder by 1 if necessary, - // and prune the stack in such case. - if (!MainParam->IsVisible()) - { - CurrentFolderListSize -= 1; - - if (CurrentFolderListSize == 0) - { - if (FolderStack.Num() > 1) - { - TArray &ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) - ParentFolderQueue[0]->GetChildCounter() -= 1; - } - - PruneStack(); - } - - return; - } - - // We expect 'TupleSize' children param of this folder after finish processing all the child folders of cur folderlist - MainParam->ResetChildCounter(); - - // Add this folder to the folder map - AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); - - // Set the parent param to current folderList. - // it was parent multiparm's id if this folder is a child of a multiparms. - // This will cause problem if the folder is inside of a multiparm - MainParam->SetParentParmId(CurrentFolderList->GetParmId()); - - - // Case 1: The folder is a direct child of a multiparm. - if (MainParam->IsDirectChildOfMultiParm()) - { - if (FolderStack.Num() <= 0) // This should not happen - return; - - // Get its parent multiparm first - UHoudiniParameterMultiParm* ParentMultiParm = nullptr; - { - UHoudiniParameterFolderList * ParentFolderList = nullptr; - if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) - return; - - ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); - - if (!ParentFolderList) - return; - - if (AllMultiParms.Contains(ParentFolderList->GetParentParmId())) - ParentMultiParm = AllMultiParms[ParentFolderList->GetParentParmId()]; - - if (!ParentMultiParm) // This should not happen - return; - } - - bool bShown = ParentMultiParm->IsShown(); - - // Case 1-1: The folder is NOT tabs - if (!MainParam->IsTab()) - { - bShown = MainParam->IsExpanded() && bShown; - - // If the parent multiparm is shown. - if (ParentMultiParm->IsShown()) - { - FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderHeaderRow, InParams); - } - } - // Case 1-2: The folder IS tabs. - else - { - CreateWidgetTab(HouParameterCategory, MainParam, ParentMultiParm->IsShown()); - } - - // Push the folder to the queue if it is not a tab folder - // This step is handled by CreateWidgetTab() if it is tabs - if ((!MainParam->IsTab() || !ParentMultiParm->IsShown()) && MainParam->GetTupleSize() > 0) - { - TArray & MyQueue = FolderStack.Last(); - MainParam->SetIsContentShown(bShown); - MyQueue.Add(MainParam); - } - } - - // Case 2: The folder is NOT a direct child of a multiparm. - else - { - // Case 2-1: The folder is in another folder. - if (FolderStack.Num() > 1 && CurrentFolderListSize > 0) - { - TArray & MyFolderQueue = FolderStack.Last(); - TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - - if (ParentFolderQueue.Num() <= 0) //This should happen - return; - - // Peek the folder queue of the last layer to get its parent folder parm. - bool ParentFolderVisible = ParentFolderQueue[0]->IsContentShown(); - - // If this folder is expanded (selected if is tabs) - bool bExpanded = ParentFolderVisible; - - // Case 2-1-1: The folder is NOT in a tab menu. - if (!MainParam->IsTab()) - { - bExpanded &= MainParam->IsExpanded(); - - // The parent folder is visible. - if (ParentFolderVisible) - { - // Add the folder header UI. - FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderHeaderRow, InParams); - } - - MainParam->SetIsContentShown(bExpanded); - MyFolderQueue.Add(MainParam); - } - // Case 2-1-2: The folder IS in a tab menu. - else - { - bExpanded &= MainParam->IsChosen(); - - CreateWidgetTab(HouParameterCategory, MainParam, ParentFolderVisible); - } - } - // Case 2-2: The folder is in the root. - else - { - bool bExpanded = true; - - // Case 2-2-1: The folder is NOT under a tab menu. - if (!MainParam->IsTab()) - { - if (FolderStack.Num() <= 0) // This will not happen - return; - - // Create Folder header under root. - FDetailWidgetRow* FolderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderRow, InParams); - - if (FolderStack.Num() == 0) // This should not happen - return; - - TArray& MyFolderQueue = FolderStack[0]; - bExpanded &= MainParam->IsExpanded(); - MainParam->SetIsContentShown(bExpanded); - MyFolderQueue.Add(MainParam); - } - // Case 2-2-2: The folder IS under a tab menu. - else - { - // Tabs in root is always visible - CreateWidgetTab(HouParameterCategory, MainParam, true); - } - } - } - - - CurrentFolderListSize -= 1; - - // Prune the stack if finished parsing current folderlist - if (CurrentFolderListSize == 0) - { - if (FolderStack.Num() > 1 && !MainParam->IsDirectChildOfMultiParm()) - { - TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) - ParentFolderQueue[0]->GetChildCounter() -= 1; - } - - PruneStack(); - - CurrentFolderList = nullptr; - } -} - -void -FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray &InParams) -{ - if (!HeaderRow) // The folder is invisible. - return; - - TArray FolderParams; - if (!CastParameters(InParams, FolderParams)) - return; - - if (FolderParams.Num() <= 0) - return; - - UHoudiniParameterFolder* MainParam = FolderParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return; - - TSharedPtr VerticalBox; - - FString LabelStr = MainParam->GetParameterLabel(); - - TSharedPtr HorizontalBox; - TSharedPtr ExpanderArrow; - TSharedPtr ExpanderImage; - - HeaderRow->NameWidget.Widget = SAssignNew(HorizontalBox, SCustomizedBox); - - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - - if (MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 1) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - LabelStr = LabelStr + TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex) + TEXT(")"); - } - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SAssignNew(ExpanderArrow, SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([=]() - { - MainParam->ExpandButtonClicked(); - - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - - return FReply::Handled(); - }) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - - FText LabelText = FText::FromString(LabelStr); - - HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SNew(STextBlock) - .Text(LabelText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TWeakPtr WeakExpanderArrow(ExpanderArrow); - ExpanderImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([MainParam, WeakExpanderArrow]() - { - FName ResourceName; - TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); - if (MainParam->IsExpanded()) - { - ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - } - ) - )); - - if(MainParam->GetFolderType() == EHoudiniFolderParameterType::Simple) - ExpanderArrow->SetEnabled(false); - -} - -void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InFolder, const bool& bIsShown) -{ - if (!InFolder || InFolder->IsPendingKill() || !CurrentFolderList) - return; - - if (FolderStack.Num() <= 0) // error state - return; - - TArray & FolderQueue = FolderStack.Last(); - - // Cache all tabs of current tab folder list. - CurrentFolderList->AddTabFolder(InFolder); - - // If the tabs is not shown, just push the folder param into the queue. - if (!bIsShown) - { - InFolder->SetIsContentShown(bIsShown); - FolderQueue.Add(InFolder); - return; - } - - // tabs currently being processed - CurrentTabs.Add(InFolder); - - if (CurrentFolderListSize > 1) - return; - - // The tabs belong to current folder list - UHoudiniParameterFolderList* CurrentTabMenuFolderList = CurrentFolderList; - - // Create a row (UI) for current tabs - TSharedPtr HorizontalBox; - FDetailWidgetRow &Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()) - [ - SAssignNew(HorizontalBox, SCustomizedBox) - ]; - - // Put current tab folder list param into an array - TArray CurrentTabMenuFolderListArr; - CurrentTabMenuFolderListArr.Add(CurrentTabMenuFolderList); - - HorizontalBox->SetHoudiniParameter(CurrentTabMenuFolderListArr); - DividerLinePositions.Add(HorizontalBox->AddIndentation(InFolder, AllMultiParms, AllFoldersAndFolderLists)); - HorizontalBox->DividerLinePositions = DividerLinePositions; - - float DesiredHeight = 0.0f; - float DesiredWidth = 0.0f; - - // Process all tabs of current folder list at once when done. - - for (auto & CurTab : CurrentTabs) - { - if (!CurTab || CurTab->IsPendingKill()) - continue; - - CurTab->SetIsContentShown(CurTab->IsChosen()); - FolderQueue.Add(CurTab); - - auto OnTabClickedLambda = [CurrentTabMenuFolderList, CurTab, &HouParameterCategory]() - { - if (CurrentTabMenuFolderList) - { - if (!CurrentTabMenuFolderList->bIsTabMenu || CurrentTabMenuFolderList->TabFolders.Num() <= 0) - return FReply::Handled(); - - if (CurTab->IsChosen()) - return FReply::Handled(); - - CurTab->SetChosen(true); - - for (UHoudiniParameterFolder* NextFolder : CurrentTabMenuFolderList->TabFolders) - { - if (CurTab->GetParmId() != NextFolder->GetParmId() && NextFolder->IsChosen()) - NextFolder->SetChosen(false); - } - - HouParameterCategory.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); - }; - - FString FolderLabelString = TEXT(" ") + CurTab->GetParameterLabel(); - if (CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio) - FolderLabelString = TEXT(" ") + FolderLabelString; - - bool bChosen = CurTab->IsTab() && CurTab->IsChosen(); - - TSharedPtr CurCustomizedButton; - - HorizontalBox->AddSlot().VAlign(VAlign_Bottom) - .AutoWidth() - .Padding(0.f) - .HAlign(HAlign_Left) - [ - SAssignNew(CurCustomizedButton, SCustomizedButton) - .OnClicked_Lambda(OnTabClickedLambda) - .Content() - [ - SNew(STextBlock) - .Text(FText::FromString(FolderLabelString)) - ] - ]; - - CurCustomizedButton->bChosen = bChosen; - CurCustomizedButton->bIsRadioButton = CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio; - - DesiredHeight = CurCustomizedButton->GetDesiredSize().Y; - DesiredWidth += CurCustomizedButton->GetDesiredSize().X; - } - - HorizontalBox->bIsTabFolderListRow = true; - - Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - // Set the current tabs to be shown, since slate widgets have been created - CurrentTabMenuFolderList->SetTabsShown(true); - - // Clear the temporary tabs - CurrentTabs.Empty(); -} - -void -FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray MultiParmParams; - if (!CastParameters(InParams, MultiParmParams)) - return; - - if (MultiParmParams.Num() <= 0) - return; - - UHoudiniParameterMultiParm* MainParam = MultiParmParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Add current multiparm parameter to AllmultiParms map - AllMultiParms.Add(MainParam->GetParmId(), MainParam); - - // Create a new detail row - FDetailWidgetRow * Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - { - MainParam->SetIsShown(false); - return; - } - - MainParam->SetIsShown(true); - - MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); - - CreateNameWidget(Row, InParams, true); - - auto OnInstanceValueChangedLambda = [MainParam](int32 InValue) - { - if (InValue < 0) - return; - - int32 ChangesCount = FMath::Abs(MainParam->MultiParmInstanceLastModifyArray.Num() - InValue); - - if (MainParam->MultiParmInstanceLastModifyArray.Num() > InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->RemoveElement(-1); - - MainParam->MarkChanged(true); - } - else if (MainParam->MultiParmInstanceLastModifyArray.Num() < InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->InsertElement(); - - MainParam->MarkChanged(true); - } - }; - - // Add multiparm UI. - TSharedRef HorizontalBox = SNew(SHorizontalBox); - TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; - int32 NumericalCount = MainParam->MultiParmInstanceCount; - HorizontalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateLambda([OnInstanceValueChangedLambda](int32 InValue) { - OnInstanceValueChangedLambda(InValue); - })) - .Value(NumericalCount) - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamAddInstance", "Houdini Parameter Multi Parameter: Adding an instance"), - MainParam->GetOuter(), true); - - for (auto& Param : MultiParmParams) - { - if (!Param) - continue; - - // Add a reverse step for redo/undo - Param->MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Removed); - - Param->MarkChanged(true); - Param->Modify(); - - if (Param->MultiParmInstanceLastModifyArray.Num() > 0) - Param->MultiParmInstanceLastModifyArray.RemoveAt(Param->MultiParmInstanceLastModifyArray.Num() - 1); - - Param->InsertElement(); - - } - }), - LOCTEXT("AddMultiparmInstanceToolTipAddLastInstance", "Add an Instance"), true) - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - // Remove the last multiparm instance - PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamDeleteInstance", "Houdini Parameter Multi Parameter: Deleting an instance"), - MainParam->GetOuter(), true); - - for (auto & Param : MultiParmParams) - { - TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; - int32 RemovedIndex = LastModifiedArray.Num() - 1; - while (LastModifiedArray.IsValidIndex(RemovedIndex) && LastModifiedArray[RemovedIndex] == EHoudiniMultiParmModificationType::Removed) - RemovedIndex -= 1; - - // Add a reverse step for redo/undo - EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; - if (LastModifiedArray.IsValidIndex(RemovedIndex)) - { - PreviousModType = LastModifiedArray[RemovedIndex]; - LastModifiedArray[RemovedIndex] = EHoudiniMultiParmModificationType::Inserted; - } - - Param->MarkChanged(true); - - Param->Modify(); - - if (LastModifiedArray.IsValidIndex(RemovedIndex)) - { - LastModifiedArray[RemovedIndex] = PreviousModType; - } - - Param->RemoveElement(RemovedIndex); - } - - }), - LOCTEXT("RemoveLastMultiParamLastToolTipRemoveLastInstance", "Remove the last instance"), true) - - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamDeleteAllInstances", "Houdini Parameter Multi Parameter: Deleting all instances"), - MainParam->GetOuter(), true); - - for (auto & Param : MultiParmParams) - { - TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; - TArray IndicesToReverse; - - for (int32 Index = 0; Index < LastModifiedArray.Num(); ++Index) - { - if (LastModifiedArray[Index] == EHoudiniMultiParmModificationType::None) - { - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; - IndicesToReverse.Add(Index); - } - } - - Param->MarkChanged(true); - - Param->Modify(); - - for (int32 & Index : IndicesToReverse) - { - if (LastModifiedArray.IsValidIndex(Index)) - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::None; - } - - - Param->EmptyElements(); - } - - }), - LOCTEXT("HoudiniParameterRemoveAllMultiparmInstancesToolTip", "Remove all instances"), true) - ]; - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniParameterDetails::CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams) -{ - - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return; - - if (!HorizontalBox || !AllMultiParms.Contains(MainParam->GetParentParmId()) || !MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - return; - - UHoudiniParameterMultiParm* MainParentMultiParm = AllMultiParms[MainParam->GetParentParmId()]; - - if (!MainParentMultiParm) - return; - - if (!MainParentMultiParm->IsShown()) - return; - - // push all parent multiparm of the InParams to the array - TArray ParentMultiParams; - for (auto & InParam : InParams) - { - if (!InParam) - continue; - - if (!MultiParmInstanceIndices.Contains(InParam->GetParentParmId())) - continue; - - if (InParam->GetChildIndex() == 0) - { - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[InParam->GetParentParmId()]; - - if (ParentMultiParm) - ParentMultiParams.Add(ParentMultiParm); - } - } - - - int32 InstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - - TSharedRef AddButton = PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() - { - for (auto & ParentParam : ParentMultiParams) - { - // Add button call back - if (!ParentParam) - continue; - - TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; - - if (!LastModifiedArray.IsValidIndex(InstanceIndex)) - continue; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParmAddBeforeCurInstance", "Houdini Parameter Multi Parm: Adding an instance"), - ParentParam->GetOuter(), true); - - - int32 Index = InstanceIndex; - - // Add a reverse step for undo/redo - if (Index >= LastModifiedArray.Num()) - LastModifiedArray.Add(EHoudiniMultiParmModificationType::Removed); - else - LastModifiedArray.Insert(EHoudiniMultiParmModificationType::Removed, Index); - - ParentParam->MarkChanged(true); - ParentParam->Modify(); - - if (Index >= LastModifiedArray.Num() - 1 && LastModifiedArray.Num()) - LastModifiedArray.RemoveAt(LastModifiedArray.Num() - 1); - else - LastModifiedArray.RemoveAt(Index); - - ParentParam->InsertElementAt(InstanceIndex); - - } - }), - LOCTEXT("HoudiniParameterMultiParamAddBeforeCurrentInstanceToolTip", "Insert an instance before this instance")); - - - TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() - { - for (auto & ParentParam : ParentMultiParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParmDeleteCurInstance", "Houdini Parameter Multi Parm: Deleting an instance"), - ParentParam->GetOuter(), true); - - - TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; - - int32 Index = InstanceIndex; - EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; - while (LastModifiedArray.IsValidIndex(Index) && LastModifiedArray[Index] == EHoudiniMultiParmModificationType::Removed) - { - Index -= 1; - } - - if (LastModifiedArray.IsValidIndex(Index)) - { - PreviousModType = LastModifiedArray[Index]; - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; - } - - ParentParam->MarkChanged(true); - - ParentParam->Modify(); - - if (LastModifiedArray.IsValidIndex(Index)) - { - LastModifiedArray[Index] = PreviousModType; - } - - ParentParam->RemoveElement(InstanceIndex); - } - - }), - LOCTEXT("HoudiniParameterMultiParamDeleteCurrentInstanceToolTip", "Remove an instance"), true); - - - HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[AddButton]; - HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[RemoveButton]; - - int32 StartIdx = MainParam->GetParameterType() == EHoudiniParameterType::Folder ? 1 : 0; - if (MainParam->GetChildIndex() != StartIdx) - { - AddButton.Get().SetVisibility(EVisibility::Hidden); - RemoveButton.Get().SetVisibility(EVisibility::Hidden); - } - -} - -void -FHoudiniParameterDetails::PruneStack() -{ - for (int32 StackItr = FolderStack.Num() - 1; StackItr >= 0; --StackItr) - { - TArray &CurrentQueue = FolderStack[StackItr]; - - for (int32 QueueItr = CurrentQueue.Num() - 1; QueueItr >= 0; --QueueItr) - { - UHoudiniParameterFolder * CurrentFolder = CurrentQueue[QueueItr]; - if (!CurrentFolder || CurrentFolder->IsPendingKill()) - continue; - - if (CurrentFolder->GetChildCounter() == 0) - { - CurrentQueue.RemoveAt(QueueItr); - } - } - - if (CurrentQueue.Num() == 0) - { - FolderStack.RemoveAt(StackItr); - } - } -} - -FText -FHoudiniParameterDetails::GetParameterTooltip(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return FText(); - - // Tooltip starts with Label (name) - FString Tooltip = InParam->GetParameterLabel() + TEXT(" (") + InParam->GetParameterName() + TEXT(")"); - - // Append the parameter type - FString ParmTypeStr = GetParameterTypeString(InParam->GetParameterType(), InParam->GetTupleSize()); - if (!ParmTypeStr.IsEmpty()) - Tooltip += TEXT("\n") + ParmTypeStr; - - // If the parameter has some help, append it - FString Help = InParam->GetParameterHelp(); - if (!Help.IsEmpty()) - Tooltip += TEXT("\n") + Help; - - // If the parameter has an expression, append it - if (InParam->HasExpression()) - { - FString Expr = InParam->GetExpression(); - if (!Expr.IsEmpty()) - Tooltip += TEXT("\nExpression: ") + Expr; - } - - return FText::FromString(Tooltip); -} - -FString -FHoudiniParameterDetails::GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize) -{ - FString ParamStr; - - switch (InType) - { - case EHoudiniParameterType::Button: - ParamStr = TEXT("Button"); - break; - - case EHoudiniParameterType::ButtonStrip: - ParamStr = TEXT("Button Strip"); - break; - - case EHoudiniParameterType::Color: - { - if (InTupleSize == 4) - ParamStr = TEXT("Color with Alpha"); - else - ParamStr = TEXT("Color"); - } - break; - - case EHoudiniParameterType::ColorRamp: - ParamStr = TEXT("Color Ramp"); - break; - - case EHoudiniParameterType::File: - ParamStr = TEXT("File (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileDir: - ParamStr = TEXT("File Dir (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileGeo: - ParamStr = TEXT("File Geo (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileImage: - ParamStr = TEXT("File Image (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::Float: - ParamStr = TEXT("Float (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); - break; - - case EHoudiniParameterType::FloatRamp: - ParamStr = TEXT("Float Ramp"); - break; - - case EHoudiniParameterType::Folder: - case EHoudiniParameterType::FolderList: - break; - - case EHoudiniParameterType::Input: - ParamStr = TEXT("Opearator Path"); - break; - - case EHoudiniParameterType::Int: - ParamStr = TEXT("Integer (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); - break; - - case EHoudiniParameterType::IntChoice: - ParamStr = TEXT("Int Choice"); - break; - - case EHoudiniParameterType::Label: - ParamStr = TEXT("Label (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::MultiParm: - ParamStr = TEXT("MultiParm"); - break; - - case EHoudiniParameterType::Separator: - break; - - case EHoudiniParameterType::String: - ParamStr = TEXT("String (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::StringAssetRef: - ParamStr = TEXT("String Asset Ref (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::StringChoice: - ParamStr = TEXT("String Choice"); - break; - - case EHoudiniParameterType::Toggle: - ParamStr = TEXT("Toggle (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - default: - ParamStr = TEXT("invalid parameter type"); - break; - } - - - return ParamStr; -} - -void -FHoudiniParameterDetails::SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter) -{ - if (!ColorRampParameter) - return; - - // Do not sync when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) - return; - - TArray &CachedPoints = ColorRampParameter->CachedPoints; - TArray &CurrentPoints = ColorRampParameter->Points; - - bool bCurveNeedsUpdate = false; - bool bRampParmNeedsUpdate = false; - - int32 Idx = 0; - - while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) - { - UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[Idx]; - UHoudiniParameterRampColorPoint* CurrentPoint = CurrentPoints[Idx]; - - if (!CachedPoint || !CurrentPoint) - continue; - - if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) - { - if (CurrentPoint->PositionParentParm) - { - CurrentPoint->SetPosition(CachedPoint->GetPosition()); - CurrentPoint->PositionParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - if (CachedPoint->GetValue() != CurrentPoint->GetValue()) - { - if (CurrentPoint->ValueParentParm) - { - CurrentPoint->SetValue(CachedPoint->GetValue()); - CurrentPoint->ValueParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) - { - if (CurrentPoint->InterpolationParentParm) - { - CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); - CurrentPoint->InterpolationParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - Idx += 1; - } - - // Insert points - for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) - { - UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[IdxCachedPointLeft]; - - CreateColorRampParameterInsertEvent( - ColorRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); - - bCurveNeedsUpdate = true; - bRampParmNeedsUpdate = true; - - } - - // Delete points - for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) - { - ColorRampParameter->RemoveElement(IdxCurrentPointLeft); - - UHoudiniParameterRampColorPoint* Point = CurrentPoints[IdxCurrentPointLeft]; - - if (!Point) - continue; - - CreateColorRampParameterDeleteEvent(ColorRampParameter, Point->InstanceIndex); - - bCurveNeedsUpdate = true; - bRampParmNeedsUpdate = true; - } - - - ColorRampParameter->MarkChanged(bRampParmNeedsUpdate); -} - -//void -//FHoudiniParameterDetails::SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter) -//{ -// if (!FloatRampParameter) -// return; -// -// // Do not sync when the Houdini asset component is cooking -// if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) -// return; -// -// TArray &CachedPoints = FloatRampParameter->CachedPoints; -// TArray &CurrentPoints = FloatRampParameter->Points; -// -// int32 Idx = 0; -// -// while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) -// { -// UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; -// UHoudiniParameterRampFloatPoint* &CurrentPoint = CurrentPoints[Idx]; -// -// if (!CachedPoint || !CurrentPoint) -// continue; -// -// if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) -// { -// if (CurrentPoint->PositionParentParm) -// { -// CurrentPoint->SetPosition(CachedPoint->GetPosition()); -// CurrentPoint->PositionParentParm->MarkChanged(true); -// } -// } -// -// if (CachedPoint->GetValue() != CurrentPoint->GetValue()) -// { -// if (CurrentPoint->ValueParentParm) -// { -// CurrentPoint->SetValue(CachedPoint->GetValue()); -// CurrentPoint->ValueParentParm->MarkChanged(true); -// } -// } -// -// if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) -// { -// if (CurrentPoint->InterpolationParentParm) -// { -// CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); -// CurrentPoint->InterpolationParentParm->MarkChanged(true); -// } -// } -// -// Idx += 1; -// } -// -// // Insert points -// for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) -// { -// UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; -// if (!CachedPoint) -// continue; -// -// CreateFloatRampParameterInsertEvent( -// FloatRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); -// -// FloatRampParameter->MarkChanged(true); -// } -// -// // Remove points -// for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) -// { -// FloatRampParameter->RemoveElement(IdxCurrentPointLeft); -// -// UHoudiniParameterRampFloatPoint* Point = CurrentPoints[IdxCurrentPointLeft]; -// -// if (!Point) -// continue; -// -// CreateFloatRampParameterDeleteEvent(FloatRampParameter, Point->InstanceIndex); -// -// FloatRampParameter->MarkChanged(true); -// } -//} - -void -FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetFloatRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - InParam->ModificationEvents.Add(DeleteEvent); -} - -void -FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetColorRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - InParam->ModificationEvents.Add(DeleteEvent); -} - -void -FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, - const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetFloatRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertFloat = InValue; - InsertEvent->InsertInterpolation = InInterp; - - InParam->ModificationEvents.Add(InsertEvent); -} - -void -FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, - const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetColorRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertColor = InColor; - InsertEvent->InsertInterpolation = InInterp; - - InParam->ModificationEvents.Add(InsertEvent); -} - -void -FHoudiniParameterDetails:: ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters) -{ - if (FloatRampParameters.Num() < 1) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0]; - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) - { - UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx]; - - if (!NextFloatRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); - } -} - -void -FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters) -{ - if (FloatRampParameters.Num() < 1) - return; - - if (!FloatRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - - for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) - { - if (!FloatRampParameters[Idx].IsValid()) - continue; - - UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx].Get(); - - if (!NextFloatRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); - } - -} - -void -FHoudiniParameterDetails:: ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam) -{ - if (!Param || !MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) - return; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Use Synced points if the MainParam is on auto update mode - // Use Cached points if the Mainparam is on manual update mode - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - if (Param->IsAutoUpdate() && bCookingEnabled) - { - TArray & Points = Param->Points; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (MainPoint->GetPosition() != Point->GetPosition()) - { - if (Point->PositionParentParm) - { - Point->SetPosition(MainPoint->GetPosition()); - Point->PositionParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetValue() != Point->GetValue()) - { - if (Point->ValueParentParm) - { - Point->SetValue(MainPoint->GetValue()); - Point->ValueParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetInterpolation() != Point->GetInterpolation()) - { - if (Point->InterpolationParentParm) - { - Point->SetInterpolation(MainPoint->GetInterpolation()); - Point->InterpolationParentParm->MarkChanged(true); - } - } - - PointIdx += 1; - } - - int32 PointInsertIdx = PointIdx; - int32 PointDeleteIdx = PointIdx; - - // skip the pending modification events - for (auto & Event : Param->ModificationEvents) - { - if (!Event) - continue; - - if (Event->IsInsertEvent()) - PointInsertIdx += 1; - - if (Event->IsDeleteEvent()) - PointDeleteIdx += 1; - } - - // There are more points in MainPoints array - for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) - { - UHoudiniParameterRampFloatPoint*& NextMainPoint = MainPoints[PointInsertIdx]; - - if (!NextMainPoint) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(Param, - NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); - - Param->MarkChanged(true); - } - - // There are more points in Points array - for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) - { - UHoudiniParameterRampFloatPoint*& NextPoint = Points[PointDeleteIdx]; - - if (!NextPoint) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); - - Param->MarkChanged(true); - } - - } - else - { - TArray &Points = Param->CachedPoints; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (Point->Position != MainPoint->Position) - { - Point->Position = MainPoint->Position; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->PositionParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Value != MainPoint->Value) - { - Point->Value = MainPoint->Value; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->ValueParentParm) - Point->ValueParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Interpolation != MainPoint->Interpolation) - { - Point->Interpolation = MainPoint->Interpolation; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->InterpolationParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - PointIdx += 1; - } - - // There are more points in MainPoints array - for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) - { - UHoudiniParameterRampFloatPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; - - if (!NextMainPoint) - continue; - - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampFloatPoint::StaticClass()); - - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = NextMainPoint->GetPosition(); - NewCachedPoint->Value = NextMainPoint->GetValue(); - NewCachedPoint->Interpolation = NextMainPoint->GetInterpolation(); - - Points.Add(NewCachedPoint); - - Param->bCaching = true; - } - - // there are more points in Points array - for (int32 PointsLeftIdx = PointIdx; PointIdx < Points.Num(); ++PointIdx) - { - Points.Pop(); - Param->bCaching = true; - } - } - -} - - -void -FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters) -{ - if (ColorRampParameters.Num() < 1) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0]; - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) - { - UHoudiniParameterRampColor* NextColorRampParam = ColorRampParameters[Idx]; - - if (!NextColorRampParam) - continue; - - FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParam); - } -} - -void -FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters) -{ - if (ColorRampParameters.Num() < 1) - return; - - if (!ColorRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) - { - if (!ColorRampParameters[Idx].IsValid()) - continue; - - UHoudiniParameterRampColor* NextColorRampParameter = ColorRampParameters[Idx].Get(); - - if (!NextColorRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParameter); - - } - -} - -void -FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParam) -{ - if (!Param || !MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) - return; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Use Synced points if the MainParam is on auto update mode - // Use Cached points if the Mainparam is on manual update mode - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - if (Param->IsAutoUpdate() && bCookingEnabled) - { - TArray & Points = Param->Points; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (MainPoint->GetPosition() != Point->GetPosition()) - { - if (Point->PositionParentParm) - { - Point->SetPosition(MainPoint->GetPosition()); - Point->PositionParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetValue() != Point->GetValue()) - { - if (Point->ValueParentParm) - { - Point->SetValue(MainPoint->GetValue()); - Point->ValueParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetInterpolation() != Point->GetInterpolation()) - { - if (Point->InterpolationParentParm) - { - Point->SetInterpolation(MainPoint->GetInterpolation()); - Point->InterpolationParentParm->MarkChanged(true); - } - } - - PointIdx += 1; - - } - - int32 PointInsertIdx = PointIdx; - int32 PointDeleteIdx = PointIdx; - - // skip the pending modification events - for (auto & Event : Param->ModificationEvents) - { - if (!Event) - continue; - - if (Event->IsInsertEvent()) - PointInsertIdx += 1; - - if (Event->IsDeleteEvent()) - PointDeleteIdx += 1; - } - - // There are more points in MainPoints array - for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) - { - UHoudiniParameterRampColorPoint*& NextMainPoint = MainPoints[PointInsertIdx]; - - if (!NextMainPoint) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(Param, - NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); - - Param->MarkChanged(true); - } - - // There are more points in Points array - for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) - { - UHoudiniParameterRampColorPoint*& NextPoint = Points[PointDeleteIdx]; - - if (!NextPoint) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); - - Param->MarkChanged(true); - } - } - else - { - TArray &Points = Param->CachedPoints; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (Point->Position != MainPoint->Position) - { - Point->Position = MainPoint->Position; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->PositionParentParm) - Point->PositionParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Value != MainPoint->Value) - { - Point->Value = MainPoint->Value; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->ValueParentParm) - Point->ValueParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Interpolation != MainPoint->Interpolation) - { - Point->Interpolation = MainPoint->Interpolation; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->InterpolationParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - PointIdx += 1; - } - - // There are more points in Main Points array. - for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) - { - UHoudiniParameterRampColorPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; - - if (!NextMainPoint) - continue; - - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampColorPoint::StaticClass()); - - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = NextMainPoint->Position; - NewCachedPoint->Value = NextMainPoint->Value; - NewCachedPoint->Interpolation = NextMainPoint->Interpolation; - - Points.Add(NewCachedPoint); - - Param->bCaching = true; - } - - // There are more points in Points array - for (int32 PointsleftIdx = PointIdx; PointIdx < MainPoints.Num(); ++PointsleftIdx) - { - Points.Pop(); - - Param->bCaching = true; - } - } -} - -// Check recussively if a parameter hits the end of a visible tabs -void -FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - // When the paramId is invalid, the directory won't parse. - // So simply return the function - if (InParam->GetParmId() < 0) - return; - - // Do not end the tab if this param is a non empty parent type, leave it to its children - EHoudiniParameterType ParmType = InParam->GetParameterType(); - if ((ParmType == EHoudiniParameterType::FolderList || - ParmType == EHoudiniParameterType::Folder) && InParam->GetTupleSize() > 0) - return; - - if (ParmType == EHoudiniParameterType::MultiParm) - { - UHoudiniParameterMultiParm * InMultiParm = Cast(InParam); - if (!InMultiParm) - return; - - if (InMultiParm->MultiParmInstanceCount * InMultiParm->MultiParmInstanceLength > 0) - return; - } - - int32 ParentParamId = InParam->GetParentParmId(); - UHoudiniParameter* CurParam = InParam; - - while (AllFoldersAndFolderLists.Contains(ParentParamId) || AllMultiParms.Contains(ParentParamId)) - { - // The parent is a multiparm - if (AllMultiParms.Contains(ParentParamId)) - { - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentParamId]; - if (!ParentMultiParm || ParentMultiParm->IsPendingKill()) - return; - - if (ParentMultiParm->MultiParmInstanceCount * ParentMultiParm->MultiParmInstanceLength - 1 == CurParam->GetChildIndex()) - { - ParentParamId = ParentMultiParm->GetParentParmId(); - CurParam = ParentMultiParm; - - continue; - } - else - { - // return directly if the parameter is not the last child param of the multiparm - return; - } - } - // The parent is a folder or folderlist - else - { - UHoudiniParameter* ParentFolderParam = AllFoldersAndFolderLists[ParentParamId]; - CurParam = ParentFolderParam; - - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) - return; - - // The parent is a folder - if (ParentFolderParam->GetParameterType() == EHoudiniParameterType::Folder) - { - ParentParamId = ParentFolderParam->GetParentParmId(); - - continue; - } - // The parent is a folderlist - else - { - UHoudiniParameterFolderList* ParentFolderList = Cast(ParentFolderParam); - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) - return; - - if (ParentFolderList->IsTabMenu() && ParentFolderList->IsTabsShown() && ParentFolderList->IsTabParseFinished() && DividerLinePositions.Num() > 0) - { - if (!CurrentTabEndingRow) - CreateTabEndingRow(HouParameterCategory); - - if (CurrentTabEndingRow) - { - CurrentTabEndingRow->EndingDividerLinePositions.Add(DividerLinePositions.Top()); - CurrentTabEndingRow->DividerLinePositions.Pop(); - } - - DividerLinePositions.Pop(); - - ParentParamId = ParentFolderList->GetParentParmId(); - } - else - { - return; - } - - } - - } - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterDetails.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniInput.h" +#include "HoudiniAsset.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "SNewFilePathPicker.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "IDetailGroup.h" +#include "IDetailCustomization.h" +#include "PropertyCustomizationHelpers.h" +#include "DetailWidgetRow.h" +#include "Math/UnitConversion.h" +#include "ScopedTransaction.h" +#include "EditorDirectories.h" + +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "Widgets/Colors/SColorPicker.h" +#include "Widgets/Views/SExpanderArrow.h" +#include "Widgets/Layout/SExpandableArea.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Input/NumericUnitTypeInterface.inl" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SSplitter.h" +#include "SCurveEditorView.h" +#include "SAssetDropTarget.h" +#include "AssetThumbnail.h" + +#include "Sound/SoundBase.h" +#include "Engine/SkeletalMesh.h" +#include "Particles/ParticleSystem.h" +#include "FoliageType.h" + +#include "HoudiniInputDetails.h" + +#include "Framework/SlateDelegates.h" +#include "Templates/SharedPointer.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +int32 +SCustomizedButton::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + TSharedPtr Content = GetContent(); + + // 0. Initialize Line Buffer. + TArray Line; + Line.SetNumUninitialized(2); + + // Initialize Color buffer. + FLinearColor Color = FLinearColor::White; + + // 1. Draw the radio button. + if (bIsRadioButton) + { + // Construct the radio button circles exactly once, + // All radio buttons share the same circles then + if (FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER || + FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER) + { + ConstructRadioButtonCircles(); + } + + DrawRadioButton(AllottedGeometry, OutDrawElements, LayerId, bChosen); + } + + // 2. Draw background color (if selected) + if (bChosen) + { + Line[0].X = AllottedGeometry.Size.X - AllottedGeometry.Size.Y / 2.0f + 2.5f; + Line[0].Y = Content->GetDesiredSize().Y / 2.0f; + Line[1].X = AllottedGeometry.Size.Y / 2.0f - 2.5f; + Line[1].Y = Content->GetDesiredSize().Y / 2.0f; + + Color = FLinearColor::White; + Color.A = bIsRadioButton ? 0.05 : 0.1; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, AllottedGeometry.Size.Y); + } + + // 3. Drawing square around the text + { + // Switch the point order for each line to save few value assignment cycles + Line[0].X = 0.0f; + Line[0].Y = 0.0f; + Line[1].X = 0.0f; + Line[1].Y = Content->GetDesiredSize().Y; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); + + //Line[0].X = 0.0f; + //Line[0].Y = Content->GetDesiredSize().Y; + Line[0].X = AllottedGeometry.Size.X; + Line[0].Y = Content->GetDesiredSize().Y; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, bChosen ? FLinearColor::Gray : FLinearColor::Black, true, 1.0f); + + //Line[0].X = AllottedGeometry.Size.X; + //Line[0].Y = Content->GetDesiredSize().Y; + Line[1].X = AllottedGeometry.Size.X; + Line[1].Y = 0.0f; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); /* draw gray bottom line if this tab is selected, black otherwise*/ + + //Line[0].X = AllottedGeometry.Size.X; + //Line[0].Y = 0.0f; + Line[0].X = 0.0f; + Line[0].Y = 0.0f; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); + } + + // 4. Draw child widget + Content->Paint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + return LayerId; +}; + +void +SCustomizedButton::ConstructRadioButtonCircles() const +{ + TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); + TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); + OuterPoints.Empty(); + InnerPoints.Empty(); + + OuterPoints.SetNumZeroed(HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER); + InnerPoints.SetNumZeroed(8); + + // Construct outer circle + int32 CurDegree = 0; + int32 DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; + + for (int32 Idx = 0; Idx < HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; ++Idx) + { + OuterPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); + OuterPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); + + CurDegree += DegStep; + } + + // Construct inner circle + CurDegree = 0; + DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER; + for (int32 Idx = 0; Idx < 8; ++Idx) + { + InnerPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); + InnerPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); + + CurDegree += DegStep; + } +} + +void +SCustomizedButton::DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const +{ + TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); + TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); + if (OuterPoints.Num() <= 1 || InnerPoints.Num() <= 1) + return; + + FLinearColor ColorNonSelected = FLinearColor::White; + FLinearColor ColorSelected = FLinearColor::Yellow; + + // initialize line buffer + TArray Line; + Line.SetNumZeroed(2); + bool alternator = false; + + // Draw outer circle + Line[0] = OuterPoints.Last(); + for (int32 Idx = 0; Idx < OuterPoints.Num(); ++Idx) + { + // alternate the points order each time to some some assignment cycles + if (alternator) + { + Line[0].X = OuterPoints[Idx].X; + Line[0].Y = OuterPoints[Idx].Y; + } + else + { + Line[1].X = OuterPoints[Idx].X; + Line[1].Y = OuterPoints[Idx].Y; + } + + alternator = !alternator; + + // Draw a line segment + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, ColorNonSelected, true, 1.0f); + } + + // Draw inner circle + alternator = false; + Line[0] = InnerPoints.Last(); + for (int32 Idx = 0; Idx < InnerPoints.Num(); ++Idx) + { + // alternate the points order each time to some some assignment cycles + if (alternator) + { + Line[0].X = InnerPoints[Idx].X; + Line[0].Y = InnerPoints[Idx].Y; + } + else + { + Line[1].X = InnerPoints[Idx].X; + Line[1].Y = InnerPoints[Idx].Y; + } + + alternator = !alternator; + + // Draw a line segment + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, bSelected ? ColorSelected : ColorNonSelected, true, 3.0f); + } +} + +void +SCustomizedBox::SetHoudiniParameter(TArray& InParams) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + + bool bIsMultiparmInstanceHeader = MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 0; + + switch (MainParam->GetParameterType()) + { + case EHoudiniParameterType::Button: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON; + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP; + } + break; + + case EHoudiniParameterType::Color: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR; + } + break; + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor * ColorRampParameter = Cast(MainParam); + if (!ColorRampParameter || ColorRampParameter->IsPendingKill()) + return; + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP; + if (ColorRampParameter->CachedPoints.Num() > 0) + MarginHeight = MarginHeight + HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE * (float)(ColorRampParameter->CachedPoints.Num() - 1); + } + break; + + case EHoudiniParameterType::File: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE; + } + break; + + case EHoudiniParameterType::FileDir: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR; + } + break; + + case EHoudiniParameterType::FileGeo: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO; + } + break; + + case EHoudiniParameterType::FileImage: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE; + } + break; + + case EHoudiniParameterType::Float: + { + if (MainParam->GetTupleSize() == 3) + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3; + } + else + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT + + (MainParam->GetTupleSize() - 1)* HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE; + } + } + } + break; + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat * FloatRampParameter = Cast(MainParam); + if (!FloatRampParameter || FloatRampParameter->IsPendingKill()) + return; + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP; + + if (FloatRampParameter->CachedPoints.Num() > 0) + MarginHeight = MarginHeight + Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE * (float)(FloatRampParameter->CachedPoints.Num() - 1); + } + break; + + case EHoudiniParameterType::Folder: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER; + } + break; + + case EHoudiniParameterType::FolderList: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST; + } + break; + + case EHoudiniParameterType::Input: + { + UHoudiniParameterOperatorPath* InputParam = Cast(MainParam); + + if (!InputParam || InputParam->IsPendingKill() || !InputParam->HoudiniInput.IsValid()) + break; + + UHoudiniInput* Input = InputParam->HoudiniInput.Get(); + + if (!Input || Input->IsPendingKill()) + break; + + + if (bIsMultiparmInstanceHeader) + { + switch (Input->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + int32 ExpandedTransformUIs = 0; + for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) + { + if (Input->IsTransformUIExpanded(Idx)) + ExpandedTransformUIs += 1; + } + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER + + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Curve: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Asset: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Landscape: + { + if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::World: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Skeletal: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER; + } + break; + default: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER; + break; + } + } + else + { + switch (Input->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + int32 ExpandedTransformUIs = 0; + for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) + { + if (Input->IsTransformUIExpanded(Idx)) + ExpandedTransformUIs += 1; + } + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE + + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM; + } + break; + case EHoudiniInputType::Curve: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE; + } + break; + case EHoudiniInputType::Asset: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET; + } + break; + case EHoudiniInputType::Landscape: + { + if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH; + } + break; + case EHoudiniInputType::World: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD; + } + break; + case EHoudiniInputType::Skeletal: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL; + } + break; + default: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT; + break; + + } + } + } + break; + + case EHoudiniParameterType::Int: + { + if (MainParam->GetTupleSize() == 3) + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3; + } + else + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE; + } + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE; + } + break; + + case EHoudiniParameterType::Label: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL; + } + break; + + case EHoudiniParameterType::MultiParm: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM; + } + break; + + case EHoudiniParameterType::Separator: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR; + bIsSeparator = true; + } + break; + + case EHoudiniParameterType::String: + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE; + } + } + break; + + case EHoudiniParameterType::StringAssetRef: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF; + } + break; + + case EHoudiniParameterType::StringChoice: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE; + } + break; + + case EHoudiniParameterType::Toggle: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE; + } + break; + + case EHoudiniParameterType::Invalid: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID; + break; + + default: + MarginHeight = 0.0f; + break; + } +} + +float +SCustomizedBox::AddIndentation(UHoudiniParameter* InParam, + TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists) +{ + if (!InParam || InParam->IsPendingKill()) + return 0.0f; + + bool bIsMainParmSimpleFolder = false; + // Get if this Parameter is a simple / collapsible folder + if (InParam->GetParameterType() == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParm = Cast(InParam); + if (FolderParm) + bIsMainParmSimpleFolder = !FolderParm->IsTab(); + } + + int32 ParentId = InParam->GetParentParmId(); + UHoudiniParameter* CurParm = InParam; + float Indentation = 0.0f; + + while (ParentId >= 0) + { + UHoudiniParameter* ParentFolder = nullptr; + UHoudiniParameterMultiParm* ParentMultiParm = nullptr; + + if (InAllFoldersAndFolderLists.Contains(ParentId)) + ParentFolder = InAllFoldersAndFolderLists[ParentId]; + + if (InAllMultiParms.Contains(ParentId)) + ParentMultiParm = InAllMultiParms[ParentId]; + + // The parent is a folder, add one unit of indentation + if (ParentFolder) + { + // Update the parent parm id + ParentId = ParentFolder->GetParentParmId(); + + if (ParentFolder->GetParameterType() == EHoudiniParameterType::FolderList) + continue; + + UHoudiniParameterFolder* Folder = Cast(ParentFolder); + + if (!Folder) + continue; + + // update the current parm, find the parent of new cur param in the next round + CurParm = Folder; + Indentation += 1.0f; + } + // The parent is a multiparm + else if (ParentMultiParm) + { + // Update the parent parm id + ParentId = ParentMultiParm->GetParentParmId(); + + if (CurParm->GetChildIndex() == 0) + { + Indentation += 0.0f; + } + else + { + Indentation += 2.0f; + } + + // update the current parm, find the parent of new cur param in the next round + CurParm = ParentMultiParm; + } + else + { + // no folder/multiparm parent, end the loop + ParentId = -1; + } + } + + + float IndentationWidth = INDENTATION_UNIT_WIDTH * Indentation; + + // Add a base indentation to non simple/collapsible param + // Since it needs more space to offset the arrow width + if (!bIsMainParmSimpleFolder) + IndentationWidth += NON_FOLDER_OFFSET_WIDTH; + + this->AddSlot().AutoWidth() + [ + SNew(SBox).WidthOverride(IndentationWidth) + ]; + + + return IndentationWidth; +}; + +int32 +SCustomizedBox::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + + SHorizontalBox::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + // Initialize line buffer + TArray Line; + Line.SetNumZeroed(2); + // Initialize color buffer + FLinearColor Color = FLinearColor::White; + Color.A = 0.3; + + // draw the bottom line if this row is the tab folder list + if (bIsTabFolderListRow) + { + // Get the start position of the tabs bottom line (right bottom pt of the right most child widget) + float VerticalLineStartPosX = 0.0f; + float VerticalLineStartPosY = 0.0f; + float BottomLineStartPosX = 0.0f; + float BottomLineStartPosY = -1.0f; + + for (int32 Idx = 0; Idx < Children.Num(); ++Idx) + { + TSharedPtr CurChild = Children.GetChildAt(Idx); + if (!CurChild.IsValid()) + continue; + + if (Idx == 0) + { + VerticalLineStartPosX = CurChild->GetDesiredSize().X; + VerticalLineStartPosY = CurChild->GetDesiredSize().Y; + } + + BottomLineStartPosX += CurChild->GetDesiredSize().X; + + if (BottomLineStartPosY < 0.0f) + BottomLineStartPosY= CurChild->GetDesiredSize().Y; + } + + // Draw bottom line + Line[0].X = BottomLineStartPosX; + Line[0].Y = BottomLineStartPosY; + Line[1].X = AllottedGeometry.Size.X; + Line[1].Y = BottomLineStartPosY; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + + // Draw divider lines + { + Line[0].Y = -MarginHeight; + Line[1].Y = AllottedGeometry.Size.Y + MarginHeight; + + int32 NumOfLinesToDraw = bIsTabFolderListRow ? DividerLinePositions.Num() - 1 : DividerLinePositions.Num(); + for (int32 Idx = 0; Idx < NumOfLinesToDraw; ++Idx) + { + const float& CurDivider = DividerLinePositions[Idx]; + Line[0].X = CurDivider; + Line[1].X = CurDivider; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + + // Draw the last inner most divider line differently when this the tabs' row. + if (bIsTabFolderListRow && DividerLinePositions.Num() > 0) + { + const float& TabDivider = DividerLinePositions.Last(); + Line[0].X = TabDivider; + Line[1].X = TabDivider; + Line[0].Y = 0.f; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + } + + // Draw tab ending lines + { + float YPos = 0.0f; + + for (const float & CurEndingDivider : EndingDividerLinePositions) + { + // Draw cur ending line (vertical) + + Line[0].X = CurEndingDivider; + Line[0].Y = -2.3f; + Line[1].X = CurEndingDivider; + Line[1].Y = YPos; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + + // Draw cur ending line (horizontal) + + // Line[0].X = CurEndingDivider; + Line[0].Y = YPos; + Line[1].X = AllottedGeometry.Size.X; + // Line[1].Y = YPos; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + + YPos += 2.0f; + } + } + + // Draw the separator line if this is the row of a separator parameter + { + if (bIsSeparator) + { + Line[0].X = 25.f; + if (DividerLinePositions.Num() > 0) + Line[0].X += DividerLinePositions.Last(); + + Line[0].Y = AllottedGeometry.Size.Y / 2.f; + Line[1].X = AllottedGeometry.Size.X - 20.f; + Line[1].Y = Line[0].Y; + + Color.A = 0.7; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.5f); + } + } + + return LayerId; +}; + +void +SHoudiniFloatRampCurveEditor::Construct(const FArguments & InArgs) +{ + SCurveEditor::Construct(SCurveEditor::FArguments() + .ViewMinInput(InArgs._ViewMinInput) + .ViewMaxInput(InArgs._ViewMaxInput) + .ViewMinOutput(InArgs._ViewMinOutput) + .ViewMaxOutput(InArgs._ViewMaxOutput) + .XAxisName(InArgs._XAxisName) + .YAxisName(InArgs._YAxisName) + .HideUI(InArgs._HideUI) + .DrawCurve(InArgs._DrawCurve) + .TimelineLength(InArgs._TimelineLength) + .AllowZoomOutput(InArgs._AllowZoomOutput) + .ShowInputGridNumbers(InArgs._ShowInputGridNumbers) + .ShowOutputGridNumbers(InArgs._ShowOutputGridNumbers) + .ShowZoomButtons(InArgs._ShowZoomButtons) + .ZoomToFitHorizontal(InArgs._ZoomToFitHorizontal) + .ZoomToFitVertical(InArgs._ZoomToFitVertical) + ); + + + UCurveEditorSettings * CurveEditorSettings = GetSettings(); + if (CurveEditorSettings) + { + CurveEditorSettings->SetTangentVisibility(ECurveEditorTangentVisibility::NoTangents); + } +} + +void +SHoudiniColorRampCurveEditor::Construct(const FArguments & InArgs) +{ + SColorGradientEditor::Construct(SColorGradientEditor::FArguments() + .ViewMinInput(InArgs._ViewMinInput) + .ViewMaxInput(InArgs._ViewMaxInput) + ); +} + + +FReply +SHoudiniFloatRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = SCurveEditor::OnMouseButtonUp(MyGeometry, MouseEvent); + + if (!HoudiniFloatRampCurve.IsValid()) + return Reply; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + FRichCurve& FloatCurve = HoudiniFloatRampCurve.Get()->FloatCurve; + + TArray>& FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; + + if (FloatRampParameters.Num() < 1) + return Reply; + + if (!FloatRampParameters[0].IsValid()) + return Reply; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + // Do not allow modification when the parent HDA of the main param is being cooked. + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points of the main float ramp param to other parameters + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + // Modification is based on the main parameter, use synced points if the main param is on auto update mode, use cached points otherwise. + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + // On mouse button up handler handles point modification only + if (FloatCurve.GetNumKeys() != NumMainPoints) + return Reply; + + bool bNeedToRefreshEditor= false; + + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float& CurvePosition = FloatCurve.Keys[Idx].Time; + float& CurveValue = FloatCurve.Keys[Idx].Value; + + // This point is modified + if (MainPoint->GetPosition() != CurvePosition || MainPoint->GetValue() != CurveValue) + { + + // The editor needs refresh only if the main parameter is on manual mode, and has been modified + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedToRefreshEditor = true; + + // Iterate through the float ramp parameter of all selected HDAs. + for (auto & NextRampFloat : FloatRampParameters) + { + if (!NextRampFloat.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedRampFloat = NextRampFloat.Get(); + + if (!SelectedRampFloat) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedRampFloat)) + continue; + + if (SelectedRampFloat->IsAutoUpdate() && bCookingEnabled) + { + // The selected float ramp parameter is on auto update mode, use its synced points. + TArray &SelectedRampPoints = SelectedRampFloat->Points; + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // Synced points in the selected ramp is more than or the same number as that in the main parameter, + // modify the position and value of the synced point and mark them as changed. + + UHoudiniParameterRampFloatPoint*& ModifiedPoint = SelectedRampPoints[Idx]; + + if (!ModifiedPoint) + continue; + + if (ModifiedPoint->GetPosition() != CurvePosition && ModifiedPoint->PositionParentParm) + { + ModifiedPoint->SetPosition(CurvePosition); + ModifiedPoint->PositionParentParm->MarkChanged(true); + } + + if (ModifiedPoint->GetValue() != CurveValue && ModifiedPoint->ValueParentParm) + { + ModifiedPoint->SetValue(CurveValue); + ModifiedPoint->ValueParentParm->MarkChanged(true); + } + } + else + { + // Synced points in the selected ramp is less than that in the main parameter + // Since we have pushed the points of the main param to all of the selected ramps, + // We need to modify the insert event. + + int32 IndexInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedRampFloat->ModificationEvents.IsValidIndex(Idx)) + { + UHoudiniParameterRampModificationEvent*& ModEvent = SelectedRampFloat->ModificationEvents[Idx]; + if (!ModEvent) + continue; + + if (ModEvent->InsertPosition != CurvePosition) + ModEvent->InsertPosition = CurvePosition; + + if (ModEvent->InsertFloat != CurveValue) + ModEvent->InsertFloat = CurveValue; + } + + } + } + else + { + // The selected float ramp is on manual update mode, use the cached points. + TArray &FloatRampCachedPoints = SelectedRampFloat->CachedPoints; + + // Since we have pushed the points in main param to all the selected float ramp, + // we need to modify the corresponding cached point in the selected float ramp. + + if (FloatRampCachedPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampFloatPoint*& ModifiedCachedPoint = FloatRampCachedPoints[Idx]; + + if (!ModifiedCachedPoint) + continue; + + if (ModifiedCachedPoint->Position != CurvePosition) + { + ModifiedCachedPoint->Position = CurvePosition; + SelectedRampFloat->bCaching = true; + if (!bCookingEnabled) + { + //SelectedRampFloat->MarkChanged(true); + if (ModifiedCachedPoint->PositionParentParm) + ModifiedCachedPoint->PositionParentParm->MarkChanged(true); + } + } + + if (ModifiedCachedPoint->Value != CurveValue) + { + ModifiedCachedPoint->Value = CurveValue; + SelectedRampFloat->bCaching = true; + if (!bCookingEnabled) + { + //SelectedRampFloat->MarkChanged(true); + if (ModifiedCachedPoint->ValueParentParm) + ModifiedCachedPoint->ValueParentParm->MarkChanged(true); + } + } + } + } + } + } + } + + + if (bNeedToRefreshEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } + + return Reply; +} + +FReply +SHoudiniFloatRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + FReply Reply = SCurveEditor::OnKeyDown(MyGeometry, InKeyEvent); + + if (InKeyEvent.GetKey().ToString() != FString("Enter")) + return Reply; + + if (!HoudiniFloatRampCurve.IsValid() || !HoudiniFloatRampCurve.Get()) + return Reply; + + TArray> FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; + + if (FloatRampParameters.Num() < 1) + return Reply; + + if (!FloatRampParameters[0].IsValid()) + return Reply; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Do nothing if the main param is on auto update mode + if (MainParam->IsAutoUpdate() && bCookingEnabled) + return Reply; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points in the main float ramp to the float ramp parameters in all selected HDAs. + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + for (auto& NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + if (!SelectedFloatRamp) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not sync the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + // Sync the cached points if the selected float ramp parameter is on manual update mode + //FHoudiniParameterDetails::SyncCachedFloatRampPoints(SelectedFloatRamp); + SelectedFloatRamp->SyncCachedPoints(); + } + + return Reply; +} + +void +UHoudiniFloatRampCurve::OnCurveChanged(const TArray& ChangedCurveEditInfos) +{ + Super::OnCurveChanged(ChangedCurveEditInfos); + + if (FloatRampParameters.Num() < 1) + return; + + if (!FloatRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return; + + // Do not allow modification when the parent HDA of the main param is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + // Push all the points of the Main parameter to other parameters + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode, otherwise use its cached points. + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + bool bNeedUpdateEditor = false; + + // OnCurveChanged handler handles point delete and insertion only + + // A point is deleted. + if (FloatCurve.GetNumKeys() < NumMainPoints) + { + // Find the index of the deleted point + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float CurPointPosition = MainPoint->GetPosition(); + float CurCurvePosition = -1.0f; + + if (FloatCurve.Keys.IsValidIndex(Idx)) + CurCurvePosition = FloatCurve.Keys[Idx].Time; + + // Delete the point at Idx + if (CurCurvePosition != CurPointPosition) + { + // Iterate through all the float ramp parameter in all the selected HDAs + for (auto & NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + if (!SelectedFloatRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + TArray & SelectedRampPoints = SelectedFloatRamp->Points; + + // The selected float ramp is on auto update mode: + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // If the number of synced points of the selected float ramp is greater or equal to the number of points of that in the main param, + // Create a Houdini engine manager event to delete the point at Idx of the selected float ramp; + + UHoudiniParameterRampFloatPoint* PointToDelete = SelectedRampPoints[Idx]; + + if (!PointToDelete) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(SelectedFloatRamp, PointToDelete->InstanceIndex); + SelectedFloatRamp->MarkChanged(true); + } + else + { + // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, + // delete the corresponding inserting event. + + int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + SelectedFloatRamp->ModificationEvents.RemoveAt(IdxInEventsArray); + } + } + else + { + // The selected float ramp is on manual update mode: + // Since we have pushed all the points in main param to the cached points of the selected float ramp, + // remove the corresponding points from the cached points array. + + if (SelectedFloatRamp->CachedPoints.IsValidIndex(Idx)) + { + SelectedFloatRamp->CachedPoints.RemoveAt(Idx); + SelectedFloatRamp->bCaching = true; + } + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point is inserted + else if (FloatCurve.GetNumKeys() > NumMainPoints) + { + // Find the index of the inserted point + for (int32 Idx = 0; Idx < FloatCurve.GetNumKeys(); ++Idx) + { + + float CurPointPosition = -1.0f; + float CurCurvePosition = FloatCurve.Keys[Idx].Time; + + if (MainPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + CurPointPosition = MainPoint->GetPosition(); + } + + // Insert instance at Idx + if (CurPointPosition != CurCurvePosition) + { + // Iterate through the float ramp parameter of all selected HDAs. + for (auto & NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + // If the selected float ramp is on auto update mode: + // Since we have pushed all the points of main parameter to the selected, + // create a Houdini engine manager event to insert a point. + + FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent( + SelectedFloatRamp, CurCurvePosition, FloatCurve.Keys[Idx].Value, EHoudiniRampInterpolationType::LINEAR); + + SelectedFloatRamp->MarkChanged(true); + } + else + { + // If the selected float ramp is on manual update mode: + // push a new point to the cached points array + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(SelectedFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = CurCurvePosition; + NewCachedPoint->Value = FloatCurve.Keys[Idx].Value; + NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; + + if (Idx >= SelectedFloatRamp->CachedPoints.Num()) + SelectedFloatRamp->CachedPoints.Add(NewCachedPoint); + else + SelectedFloatRamp->CachedPoints.Insert(NewCachedPoint, Idx); + + SelectedFloatRamp->bCaching = true; + + if (!bCookingEnabled) + SelectedFloatRamp->MarkChanged(true); + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + if (bNeedUpdateEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } + +} + + +FReply +SHoudiniColorRampCurveEditor::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = SColorGradientEditor::OnMouseButtonDown(MyGeometry, MouseEvent); + + if (HoudiniColorRampCurve.IsValid()) + { + UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); + if (Curve) + Curve->bEditing = true; + } + + return Reply; +} + +FReply +SHoudiniColorRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + + FReply Reply = SColorGradientEditor::OnMouseButtonUp(MyGeometry, MouseEvent); + + if (HoudiniColorRampCurve.IsValid()) + { + UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); + + if (Curve) + { + Curve->bEditing = false; + Curve->OnColorRampCurveChanged(true); + } + } + + return Reply; + +} + +FReply +SHoudiniColorRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + FReply Reply = SColorGradientEditor::OnKeyDown(MyGeometry, InKeyEvent); + + if (InKeyEvent.GetKey().ToString() != FString("Enter")) + return Reply; + + if (!HoudiniColorRampCurve.IsValid() || !HoudiniColorRampCurve.Get()) + return Reply; + + TArray> &ColorRampParameters = HoudiniColorRampCurve.Get()->ColorRampParameters; + + if (ColorRampParameters.Num() < 1) + return Reply; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + // Do nothing if the main param is on auto update mode + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + if (MainParam->IsAutoUpdate() && bCookingEnabled) + return Reply; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points in the main color ramp to the color ramp parameters in all selected HDAs. + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); + + for (auto& NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not sync the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + // Sync the cached points if the selected color ramp is on manual update mode + FHoudiniParameterDetails::SyncCachedColorRampPoints(SelectedColorRamp); + } + + return Reply; +} + +void +UHoudiniColorRampCurve::OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) +{ + Super::OnCurveChanged(ChangedCurveEditInfos); + + OnColorRampCurveChanged(); +} + +void +UHoudiniColorRampCurve::OnColorRampCurveChanged(bool bModificationOnly) +{ + // Array is always true in this case + // if (!FloatCurves) + // return; + + if (ColorRampParameters.Num() < 1) + return; + + if (!ColorRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return; + + // Do not allow modification when the parent HDA of the main param is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + // Push all the points of the main parameter to other parameters + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); + + // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode,otherwise use its cached points. + bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + bool bNeedUpdateEditor = false; + + // OnCurveChanged handler of color ramp curve editor handles point delete, insert and color change + + // A point is deleted + if (FloatCurves->GetNumKeys() < NumMainPoints) + { + if (bModificationOnly) + return; + + // Find the index of the deleted point + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float CurPointPosition = MainPoint->GetPosition(); + float CurCurvePosition = -1.0f; + + if (FloatCurves[0].Keys.IsValidIndex(Idx)) + CurCurvePosition = FloatCurves[0].Keys[Idx].Time; + + // Delete the point at Idx + if (CurCurvePosition != CurPointPosition) + { + // Iterate through all the color ramp parameter in all the selected HDAs + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + TArray & SelectedRampPoints = SelectedColorRamp->Points; + + // The selected color ramp is on auto update mode: + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // If the number of synced points of the selected color ramp is greater or equal to the number of points of that in the main param, + // create a Houdini engine manager event to delete the point at Idx of the selected float ramp; + + UHoudiniParameterRampColorPoint* PointToDelete = SelectedRampPoints[Idx]; + + if (!PointToDelete) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(SelectedColorRamp, PointToDelete->InstanceIndex); + SelectedColorRamp->MarkChanged(true); + } + else + { + // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, + // delete the corresponding inserting event. + + int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + SelectedColorRamp->ModificationEvents.RemoveAt(IdxInEventsArray); + } + } + else + { + // The selected color ramp is on manual update mode: + // Since we have pushed all the points in main param to the cached points of the selected float ramp, + // remove the corresponding points from the cached points array + if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) + { + SelectedColorRamp->CachedPoints.RemoveAt(Idx); + SelectedColorRamp->bCaching = true; + } + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point is inserted + else if (FloatCurves[0].GetNumKeys() > NumMainPoints) + { + + if (bModificationOnly) + return; + + // Find the index of the inserted point + for (int32 Idx = 0; Idx < FloatCurves[0].GetNumKeys(); ++Idx) + { + + float CurPointPosition = -1.0f; + float CurCurvePosition = FloatCurves[0].Keys[Idx].Time; + + if (MainPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + CurPointPosition = MainPoint->GetPosition(); + } + + // Insert a point at Idx + if (CurPointPosition != CurCurvePosition) + { + // Get the interpolation value of inserted color point + + FLinearColor ColorPrev = FLinearColor::Black; + FLinearColor ColorNext = FLinearColor::White; + float PositionPrev = 0.0f; + float PositionNext = 1.0f; + + if (MainParam->IsAutoUpdate() && bCookingEnabled) + { + // Try to get its previous point's color + if (MainParam->Points.IsValidIndex(Idx - 1)) + { + ColorPrev = MainParam->Points[Idx - 1]->GetValue(); + PositionPrev = MainParam->Points[Idx - 1]->GetPosition(); + } + + // Try to get its next point's color + if (MainParam->Points.IsValidIndex(Idx)) + { + ColorNext = MainParam->Points[Idx]->GetValue(); + PositionNext = MainParam->Points[Idx]->GetPosition(); + } + } + else + { + // Try to get its previous point's color + if (MainParam->CachedPoints.IsValidIndex(Idx - 1)) + { + ColorPrev = MainParam->CachedPoints[Idx - 1]->GetValue(); + PositionPrev = MainParam->CachedPoints[Idx - 1]->GetPosition(); + } + + // Try to get its next point's color + if (MainParam->CachedPoints.IsValidIndex(Idx)) + { + ColorNext = MainParam->CachedPoints[Idx]->GetValue(); + PositionNext = MainParam->CachedPoints[Idx]->GetPosition(); + } + } + + float TotalWeight = FMath::Abs(PositionNext - PositionPrev); + float PrevWeight = FMath::Abs(CurCurvePosition - PositionPrev); + float NextWeight = FMath::Abs(PositionNext - CurCurvePosition); + + FLinearColor InsertedColor = ColorPrev * (PrevWeight / TotalWeight) + ColorNext * (NextWeight / TotalWeight); + + // Iterate through the color ramp parameter of all selected HDAs. + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + // If the selected color ramp is on auto update mode: + // Since we have pushed all the points of main parameter to the selected, + // create a Houdini engine manager event to insert a point. + + FHoudiniParameterDetails::CreateColorRampParameterInsertEvent( + SelectedColorRamp, CurCurvePosition, InsertedColor, EHoudiniRampInterpolationType::LINEAR); + + SelectedColorRamp->MarkChanged(true); + } + else + { + // If the selected color ramp is on manual update mode: + // Push a new point to the cached points array + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(SelectedColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = CurCurvePosition; + NewCachedPoint->Value = InsertedColor; + NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; + + if (Idx >= SelectedColorRamp->CachedPoints.Num()) + SelectedColorRamp->CachedPoints.Add(NewCachedPoint); + else + SelectedColorRamp->CachedPoints.Insert(NewCachedPoint, Idx); + + SelectedColorRamp->bCaching = true; + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!MainParam->IsAutoUpdate() && bCookingEnabled) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point's color is changed + else + { + if (bEditing) + return; + + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + // Only handle color change + { + float CurvePosition = FloatCurves[0].Keys[Idx].Time; + float PointPosition = MainPoint->GetPosition(); + + FLinearColor CurveColor = FLinearColor::Black; + FLinearColor PointColor = MainPoint->GetValue(); + + CurveColor.R = FloatCurves[0].Keys[Idx].Value; + CurveColor.G = FloatCurves[1].Keys[Idx].Value; + CurveColor.B = FloatCurves[2].Keys[Idx].Value; + + // Color is changed at Idx + if (CurveColor != PointColor || CurvePosition != PointPosition) + { + // Iterate through the all selected color ramp parameters + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + // The selected color ramp parameter is on auto update mode + + if (SelectedColorRamp->Points.IsValidIndex(Idx)) + { + // If the number of synced points in the selected color ramp is more or equal to that in the main parameter: + // Modify the corresponding synced point of the selected color ramp, and marked it as changed. + + UHoudiniParameterRampColorPoint* Point = SelectedColorRamp->Points[Idx]; + + if (!Point) + continue; + + if (Point->GetValue() != CurveColor && Point->ValueParentParm) + { + Point->SetValue(CurveColor); + Point->ValueParentParm->MarkChanged(true); + } + + if (Point->GetPosition() != CurvePosition && Point->PositionParentParm) + { + Point->SetPosition(CurvePosition); + Point->PositionParentParm->MarkChanged(true); + } + } + else + { + // If the number of synced points in the selected color ramp is less than that in the main parameter: + // Since we have push the points in the main parameter to all selected parameters, + // we need to modify the corresponding insert event. + + int32 IdxInEventsArray = Idx - SelectedColorRamp->Points.Num(); + + if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = SelectedColorRamp->ModificationEvents[IdxInEventsArray]; + + if (!Event) + continue; + + if (Event->InsertColor != CurveColor) + Event->InsertColor = CurveColor; + + if (Event->InsertPosition != CurvePosition) + Event->InsertPosition = CurvePosition; + } + } + } + else + { + // The selected color ramp is on manual update mode + // Since we have push the points in the main parameter to all selected parameters, + // modify the corresponding point in the cached points array of the selected color ramp. + if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampColorPoint* CachedPoint = SelectedColorRamp->CachedPoints[Idx]; + + if (!CachedPoint) + continue; + + if (CachedPoint->Value != CurveColor) + { + CachedPoint->Value = CurveColor; + bNeedUpdateEditor = true; + } + + if (CachedPoint->Position != CurvePosition) + { + CachedPoint->Position = CurvePosition; + SelectedColorRamp->bCaching = true; + bNeedUpdateEditor = true; + } + } + } + } + } + } + } + } + + + if (bNeedUpdateEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } +} + +template< class T > +bool FHoudiniParameterDetails::CastParameters( + TArray InParams, TArray& OutCastedParams ) +{ + for (auto CurrentParam : InParams) + { + T* CastedParam = Cast(CurrentParam); + if (CastedParam && !CastedParam->IsPendingKill()) + OutCastedParams.Add(CastedParam); + } + + return (OutCastedParams.Num() == InParams.Num()); +} + + +void +FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* InParam = InParams[0]; + if (!InParam || InParam->IsPendingKill()) + return; + + // The directory won't parse if parameter ids are -1 + // simply return + if (InParam->GetParmId() < 0) + return; + + if (CurrentRampFloat) + { + // CreateWidgetFloatRamp(HouParameterCategory, InParams); + // If this parameter is a part of the last float ramp, skip it + if (InParam->GetIsChildOfMultiParm() && InParam->GetParentParmId() == CurrentRampFloat->GetParmId()) + return; + + // This parameter is not part of the last float ramp (we've passed all of its points/instances), reset + // CurrentRampFloat in order to continue normal processing of parameters + CurrentRampFloat = nullptr; + } + if (CurrentRampColor) + { + // CreateWidgetColorRamp(HouParameterCategory, InParams); + // if this parameter is a part of the last color ramp, skip it + if (InParam->GetIsChildOfMultiParm() && InParam->GetParentParmId() == CurrentRampColor->GetParmId()) + return; + + // This parameter is not part of the last color ramp (we've passed all of its points/instances), reset + // CurrentRampColor in order to continue normal processing of parameters + CurrentRampColor = nullptr; + } + + switch (InParam->GetParameterType()) + { + case EHoudiniParameterType::Float: + { + CreateWidgetFloat(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Int: + { + CreateWidgetInt(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::String: + { + CreateWidgetString(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::StringChoice: + { + CreateWidgetChoice(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Separator: + { + TArray SepParams; + if (CastParameters(InParams, SepParams)) + { + bool bEnabled = InParams.IsValidIndex(0) ? !SepParams[0]->IsDisabled() : true; + CreateWidgetSeparator(HouParameterCategory, InParams, bEnabled); + } + } + break; + + case EHoudiniParameterType::Color: + { + CreateWidgetColor(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Button: + { + CreateWidgetButton(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + CreateWidgetButtonStrip(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Label: + { + CreateWidgetLabel(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Toggle: + { + CreateWidgetToggle(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + CreateWidgetFile(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::FolderList: + { + CreateWidgetFolderList(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Folder: + { + CreateWidgetFolder(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::MultiParm: + { + CreateWidgetMultiParm(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::FloatRamp: + { + CreateWidgetFloatRamp(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::ColorRamp: + { + CreateWidgetColorRamp(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Input: + { + CreateWidgetOperatorPath(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Invalid: + { + HandleUnsupportedParmType(HouParameterCategory, InParams); + } + break; + + default: + { + HandleUnsupportedParmType(HouParameterCategory, InParams); + } + break; + } + + // Remove a divider lines recurrsively if current parameter hits the end of a tabs + RemoveTabDividers(HouParameterCategory, InParam); + +} + +void +FHoudiniParameterDetails::CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory) +{ + FDetailWidgetRow & Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()); + TSharedPtr TabEndingRow = SNew(SCustomizedBox); + + TabEndingRow->DividerLinePositions = DividerLinePositions; + + if (TabEndingRow.IsValid()) + CurrentTabEndingRow = TabEndingRow.Get(); + + Row.WholeRowWidget.Widget = TabEndingRow.ToSharedRef(); + Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniParameterDetails::CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam|| MainParam->IsPendingKill()) + return; + + if (!Row) + return; + + TSharedRef< SCustomizedBox > HorizontalBox = SNew(SCustomizedBox); + + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + + + if (MainParam->IsDirectChildOfMultiParm()) + { + FString ParameterLabelStr = MainParam->GetParameterLabel(); + + // If it is head of an multiparm instance + if (MainParam->GetChildIndex() == 0) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + } + ParameterLabelStr += TEXT(" (") + FString("") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + HorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + else + { + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(MainParam->GetParameterLabel()) : FText::GetEmpty(); + HorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + + Row->NameWidget.Widget = HorizontalBox; +} + +void +FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) +{ + if (!Row) + return; + + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + FString ParameterLabelStr = MainParam->GetParameterLabel(); + TSharedRef HorizontalBox = SNew(SCustomizedBox); + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + + TSharedPtr VerticalBox; + HorizontalBox->AddSlot() + [ + SAssignNew(VerticalBox, SVerticalBox) + ]; + + if (MainParam->IsDirectChildOfMultiParm()) + { + // If it is head of an multiparm instance + if (MainParam->GetChildIndex() == 0) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + } + + ParameterLabelStr += TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* RampParameter = Cast(MainParam); + if (RampParameter) + { + if (RampParameter->bCaching) + ParameterLabelStr += "*"; + } + } + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + else + { + // TODO: Refactor me...extend 'auto/manual update' to all parameters? (It only applies to color and float ramps for now.) + bool bParamNeedUpdate = false; + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* RampParameter = Cast(MainParam); + if (RampParameter) + bParamNeedUpdate = RampParameter->bCaching; + } + else if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat* RampParameter = Cast(MainParam); + if (RampParameter) + bParamNeedUpdate = RampParameter->bCaching; + } + + if (bParamNeedUpdate) + ParameterLabelStr += "*"; + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + + auto IsAutoUpdateChecked = [MainParam]() + { + if (!MainParam || MainParam->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainParam->IsAutoUpdate() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + auto OnAutoUpdateCheckBoxStateChanged = [MainParam, InParams, bCookingEnabled](ECheckBoxState NewState) + { + if (NewState == ECheckBoxState::Checked) + { + for (auto & NextSelectedParam : InParams) + { + if (!NextSelectedParam) + continue; + + if (NextSelectedParam->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not allow mode change when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) + continue; + + switch (MainParam->GetParameterType()) + { + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* ColorRampParameter = Cast(NextSelectedParam); + + if (!ColorRampParameter) + continue; + + // Do not sync the selected color ramp parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) + continue; + + // Sync the Cached curve points at update mode switch. + FHoudiniParameterDetails::SyncCachedColorRampPoints(ColorRampParameter); + } + break; + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* FloatRampParameter = Cast(NextSelectedParam); + + if (!FloatRampParameter) + continue; + + // Do not sync the selected float ramp parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) + continue; + + // Sync the Cached curve points at update mode switch. + //FHoudiniParameterDetails::SyncCachedFloatRampPoints(FloatRampParameter); + FloatRampParameter->SyncCachedPoints(); + } + break; + + default: + break; + } + + NextSelectedParam->SetAutoUpdate(true); + } + } + else + { + for (auto & NextSelectedParam : InParams) + { + if (!NextSelectedParam) + continue; + + if (!(NextSelectedParam->IsAutoUpdate() && bCookingEnabled)) + continue; + + // Do not allow mode change when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) + continue; + + NextSelectedParam->SetAutoUpdate(false); + } + } + }; + + // Auto update check box + TSharedPtr CheckBox; + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + [ + SAssignNew(CheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([OnAutoUpdateCheckBoxStateChanged](ECheckBoxState NewState) + { + OnAutoUpdateCheckBoxStateChanged(NewState); + }) + .IsChecked_Lambda([IsAutoUpdateChecked]() + { + return IsAutoUpdateChecked(); + }) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AutoUpdate", "Auto-update")) + .ToolTipText(LOCTEXT("AutoUpdateTip", "When enabled, this parameter will automatically update its value while editing. Turning this off will allow you to more easily update it, and the update can be pushed by checking the toggle again.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + if ((MainParam->GetParameterType() != EHoudiniParameterType::FloatRamp) && (MainParam->GetParameterType() != EHoudiniParameterType::ColorRamp)) + CheckBox->SetVisibility(EVisibility::Hidden); + + Row->NameWidget.Widget = HorizontalBox; +} + +FDetailWidgetRow* +FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount) +{ + if (InParams.Num() <= 0) + return nullptr; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return nullptr; + + // Created row for the current parameter (if there is not a row created, do not show the parameter). + FDetailWidgetRow* Row = nullptr; + + // Current parameter is in a multiparm instance (directly) + if (MainParam->IsDirectChildOfMultiParm()) + { + int32 ParentMultiParmId = MainParam->GetParentParmId(); + + // If this is a folder param, its folder list parent parm is the multiparm + if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) + { + if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) // This should not happen + return nullptr; + + UHoudiniParameterFolderList* ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); + if (!ParentFolderList || ParentFolderList->IsPendingKill()) + return nullptr; // This should not happen + + ParentMultiParmId = ParentFolderList->GetParentParmId(); + } + + if (!AllMultiParms.Contains(ParentMultiParmId)) // This should not happen normally + return nullptr; + + // Get the parent multiparm + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentMultiParmId]; + + // The parent multiparm is visible. + if (ParentMultiParm && ParentMultiParm->IsShown() && MainParam->ShouldDisplay()) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + + } + // This item is not a direct child of a multiparm. + else + { + bool bIsFolder = MainParam->GetParameterType() == EHoudiniParameterType::Folder; + + // If this parameter is a folder, its parent folder should be the second top of the stack + int32 NestedMinStackDepth = bIsFolder ? 1 : 0; + + // Current parameter is inside a folder. + if (FolderStack.Num() > NestedMinStackDepth) + { + // If the current parameter is a folder, we take the top second queue on the stack, since the top one represents itself. + // Otherwise take the top queue on the stack. + TArray & CurrentLayerFolderQueue = bIsFolder ? + FolderStack[FolderStack.Num() - 2] : FolderStack.Last(); + + if (CurrentLayerFolderQueue.Num() <= 0) // Error state + return nullptr; + + bool bParentFolderVisible = CurrentLayerFolderQueue[0]->IsContentShown(); + + bool bIsSelectedTabVisible = false; + + // If its parent folder is visible, display current parameter, + // Otherwise, just prune the stacks. + if (bParentFolderVisible) + { + int32 ParentFolderId = MainParam->GetParentParmId(); + + // If the current parameter is a folder, its parent is a folderlist. + // So we need to continue to get the parent of the folderlist. + if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) + { + if (AllFoldersAndFolderLists.Contains(ParentFolderId)) + ParentFolderId = AllFoldersAndFolderLists[ParentFolderId]->GetParentParmId(); + else + return nullptr; // error state + } + + UHoudiniParameterFolder* ParentFolder = nullptr; + + if (AllFoldersAndFolderLists.Contains(ParentFolderId)) + ParentFolder = Cast(AllFoldersAndFolderLists[ParentFolderId]); + + bool bShouldDisplayRow = MainParam->ShouldDisplay(); + + // This row should be shown if its parent folder is shown. + if (ParentFolder) + bShouldDisplayRow &= (ParentFolder->IsTab() && ParentFolder->IsChosen()) || (!ParentFolder->IsTab() && ParentFolder->IsExpanded()); + + if (bShouldDisplayRow) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + } + + // prune the stack finally + if (bDecreaseChildCount) + { + CurrentLayerFolderQueue[0]->GetChildCounter() -= 1; + PruneStack(); + } + } + // If this parameter is in the root dir, just create a row. + else + { + if (MainParam->ShouldDisplay()) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + } + } + + if (!MainParam->IsVisible()) + return nullptr; + + + if (Row) + CurrentTabEndingRow = nullptr; + + return Row; +} + +void +FHoudiniParameterDetails::HandleUnsupportedParmType(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + CreateNestedRow(HouParameterCategory, (TArray)InParams); +} + +void +FHoudiniParameterDetails::CreateWidgetFloat( + IDetailCategoryBuilder & HouParameterCategory, + TArray& InParams ) +{ + TArray FloatParams; + if (!CastParameters(InParams, FloatParams)) + return; + + if (FloatParams.Num() <= 0) + return; + + UHoudiniParameterFloat* MainParam = FloatParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + // Helper function to find a unit from a string (name or abbreviation) + auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); + EUnit Unit = EUnit::Unspecified; + if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) + Unit = ParmUnit.GetValue(); + + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + // Lambdas for slider begin + auto SliderBegin = [&](TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), + FloatParams[0]->GetOuter()); + + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + FloatParams[Idx]->Modify(); + } + }; + + // Lambdas for slider end + auto SliderEnd = [&](TArray FloatParams) + { + // Mark the value as changed to trigger an update + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + FloatParams[Idx]->MarkChanged(true); + } + }; + + // Lambdas for changing the parameter value + auto ChangeFloatValueAt = [&](const float& Value, const int32& Index, const bool& DoChange, TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), + FloatParams[0]->GetOuter() ); + + bool bChanged = false; + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + FloatParams[Idx]->Modify(); + if (FloatParams[Idx]->SetValueAt(Value, Index)) + { + // Only mark the param has changed if DoChange is true!!! + if(DoChange) + FloatParams[Idx]->MarkChanged(true); + bChanged = true; + } + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if no parameter's value has actually been changed + Transaction.Cancel(); + } + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Revert to default value"), + FloatParams[0]->GetOuter()); + + if (TupleIndex < 0) + { + for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + if (FloatParams[Idx]->IsDefault()) + continue; + + FloatParams[Idx]->RevertToDefault(-1); + } + } + else + { + for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + if (FloatParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + FloatParams[Idx]->RevertToDefault(TupleIndex); + } + } + return FReply::Handled(); + }; + + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + //TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + if (MainParam->GetTupleSize() == 3) + { + // Should we swap Y and Z fields (only relevant for Vector3) + // Ignore the swapping if that parameter has the noswap tag + bool SwapVector3 = !MainParam->GetNoSwap(); + + auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float& Val, const bool& bDoChange) + { + ChangeFloatValueAt(Val, 0, bDoChange, FloatParams); + ChangeFloatValueAt(Val, 1, bDoChange, FloatParams); + ChangeFloatValueAt(Val, 2, bDoChange, FloatParams); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .AllowSpin(true) + .X(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, 0))) + .Y(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 2 : 1))) + .Z(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 1 : 2))) + .OnXCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, true); + else + ChangeFloatValueAt( Val, 0, true, FloatParams); + }) + .OnYCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, true); + else + ChangeFloatValueAt( Val, SwapVector3 ? 2 : 1, true, FloatParams); + }) + .OnZCommitted_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, true); + else + ChangeFloatValueAt( Val, SwapVector3 ? 1 : 2, true, FloatParams); + }) + .OnXChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, 0, false, FloatParams); + }) + .OnYChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, SwapVector3 ? 2 : 1, false, FloatParams); + }) + .OnZChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, SwapVector3 ? 1 : 2, false, FloatParams); + }) + .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) + .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("FloatParameterLockButtonToolTip", "When locked, change the vector value uniformly.")) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(MainParam->IsUniformLocked() ? FEditorStyle::GetBrush("Genericlock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([FloatParams, MainParam]() + { + if (!MainParam || MainParam->IsPendingKill()) + return FReply::Handled(); + + for (auto & CurParam : FloatParams) + { + if (!CurParam || CurParam->IsPendingKill()) + continue; + + CurParam->SwitchUniformLock(); + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + + return FReply::Handled(); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([FloatParams]() + { + for (auto & SelectedParam : FloatParams) + { + if (!SelectedParam) + continue; + + if (!SelectedParam->IsDefault()) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([FloatParams, RevertToDefault]() { return RevertToDefault(-1, FloatParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + else + { + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + TSharedPtr> NumericEntryBox; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< float >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + + .MinValue(MainParam->GetMin()) + .MaxValue(MainParam->GetMax()) + + .MinSliderValue(MainParam->GetUIMin()) + .MaxSliderValue(MainParam->GetUIMax()) + + .Value(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, Idx))) + .OnValueChanged_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) + .OnValueCommitted_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) + .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) + .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) + .SliderExponent(MainParam->IsLogarithmic() ?8.0f : 1.0f) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .OnClicked_Lambda([Idx, FloatParams, RevertToDefault]() { return RevertToDefault(Idx, FloatParams); }) + .Visibility_Lambda([Idx, FloatParams]() + { + for (auto & SelectedParam :FloatParams) + { + if (!SelectedParam) + continue; + + if (!SelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + } + } + + Row->ValueWidget.Widget =VerticalBox; + + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetInt(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray IntParams; + if (!CastParameters(InParams, IntParams)) + + if (IntParams.Num() <= 0) + return; + + UHoudiniParameterInt* MainParam = IntParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + // Helper function to find a unit from a string (name or abbreviation) + auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); + EUnit Unit = EUnit::Unspecified; + if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) + Unit = ParmUnit.GetValue(); + + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + // Lambda for slider begin + auto SliderBegin = [&](TArray IntParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), + IntParams[0]->GetOuter()); + + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + IntParams[Idx]->Modify(); + } + }; + + // Lambda for slider end + auto SliderEnd = [&](TArray IntParams) + { + // Mark the value as changed to trigger an update + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + IntParams[Idx]->MarkChanged(true); + } + }; + + // Lambda for changing the parameter value + auto ChangeIntValueAt = [&](const int32& Value, const int32& Index, const bool& DoChange, TArray IntParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), + IntParams[0]->GetOuter()); + + bool bChanged = false; + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + if (!IntParams[Idx]) + continue; + + IntParams[Idx]->Modify(); + if (IntParams[Idx]->SetValueAt(Value, Index)) + { + // Only mark the param has changed if DoChange is true!!! + if (DoChange) + IntParams[Idx]->MarkChanged(true); + bChanged = true; + } + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if there is no param has actually been changed + Transaction.Cancel(); + } + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray IntParams) + { + for (int32 Idx = 0; Idx < IntParams.Num(); Idx++) + { + if (!IntParams[Idx]) + continue; + + if (IntParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + IntParams[Idx]->RevertToDefault(TupleIndex); + } + + return FReply::Handled(); + }; + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + + .MinValue(MainParam->GetMin()) + .MaxValue(MainParam->GetMax()) + + .MinSliderValue(MainParam->GetUIMin()) + .MaxSliderValue(MainParam->GetUIMax()) + + .Value( TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterInt::GetValue, Idx))) + .OnValueChanged_Lambda( [=](int32 Val) { ChangeIntValueAt(Val, Idx, false, IntParams); } ) + .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeIntValueAt(Val, Idx, true, IntParams); }) + .OnBeginSliderMovement_Lambda( [=]() { SliderBegin(IntParams); }) + .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(IntParams); }) + .SliderExponent(MainParam->IsLogarithmic() ? 8.0f : 1.0f) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, IntParams]() + { + for (auto & NextSelectedParam : IntParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, IntParams, RevertToDefault]() { return RevertToDefault(Idx, IntParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + /* + if (NumericEntryBox.IsValid()) + NumericEntryBox->SetEnabled(!MainParam->IsDisabled()); + */ + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray StringParams; + if (!CastParameters(InParams, StringParams)) + return; + + if (StringParams.Num() <= 0) + return; + + UHoudiniParameterString* MainParam = StringParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + bool bIsMultiLine = false; + bool bIsUnrealRef = false; + UClass* UnrealRefClass = UObject::StaticClass(); + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + TMap& Tags = MainParam->GetTags(); + if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_TAG) && FCString::Atoi(*Tags[HOUDINI_PARAMETER_STRING_REF_TAG]) == 1) + { + bIsUnrealRef = true; + + if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_CLASS_TAG)) + { + UClass * FoundClass = FindObject(ANY_PACKAGE, *Tags[HOUDINI_PARAMETER_STRING_REF_CLASS_TAG]); + if (FoundClass != nullptr) + { + UnrealRefClass = FoundClass; + } + } + } + + if (Tags.Contains(HOUDINI_PARAMETER_STRING_MULTILINE_TAG)) + { + bIsMultiLine = true; + } + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + // Lambda for changing the parameter value + auto ChangeStringValueAt = [&](const FString& Value, UObject* ChosenObj, const int32& Index, const bool& DoChange, TArray StringParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterSrtingChange", "Houdini Parameter String: Changing a value"), + StringParams[0]->GetOuter()); + + bool bChanged = false; + for (int Idx = 0; Idx < StringParams.Num(); Idx++) + { + if (!StringParams[Idx]) + continue; + + StringParams[Idx]->Modify(); + if (StringParams[Idx]->SetValueAt(Value, Index)) + { + StringParams[Idx]->MarkChanged(true); + bChanged = true; + } + + StringParams[Idx]->SetAssetAt(ChosenObj, Index); + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if there is no param actually has been changed + Transaction.Cancel(); + } + + FHoudiniEngineUtils::UpdateEditorProperties(StringParams[0], false); + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray StringParams) + { + for (int32 Idx = 0; Idx < StringParams.Num(); Idx++) + { + if (!StringParams[Idx]) + continue; + + if (StringParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + StringParams[Idx]->RevertToDefault(TupleIndex); + } + + return FReply::Handled(); + }; + + if (bIsUnrealRef) + { + TSharedPtr EditableTextBox; + TSharedPtr HorizontalBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([UnrealRefClass](const UObject* InObject) + { + return InObject->IsA(UnrealRefClass); + }) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + ChangeStringValueAt(ReferenceStr, InObject, Idx, true, StringParams); + }) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + // Thumbnail + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouParameterCategory.GetParentLayout().GetThumbnailPool(); + + // Create a thumbnail for the selected object / class + UObject* EditObject = nullptr; + const FString AssetPath = MainParam->GetValueAt(Idx); + EditObject = StaticFindObject(nullptr, nullptr, *AssetPath, true); + + FAssetData AssetData; + if (IsValid(EditObject)) + { + AssetData = FAssetData(EditObject); + } + else + { + AssetData.AssetClass = UnrealRefClass->GetFName(); + } + + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = MakeShareable( + new FAssetThumbnail(AssetData, 64, 64, AssetThumbnailPool)); + + TSharedPtr ThumbnailBorder; + HorizontalBox->AddSlot().Padding(0.f, 0.f, 2.f, 0.f).AutoWidth() + [ + SAssignNew(ThumbnailBorder, SBorder) + .OnMouseDoubleClick_Lambda([EditObject, Idx](const FGeometry&, const FPointerEvent&) + { + if (EditObject && GEditor) + GEditor->EditObject(EditObject); + + return FReply::Handled(); + }) + .Padding(5.f) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + TWeakPtr WeakThumbnailBorder(ThumbnailBorder); + ThumbnailBorder->SetBorderImage(TAttribute::Create( + TAttribute::FGetter::CreateLambda( + [WeakThumbnailBorder]() + { + TSharedPtr ThumbnailBorderPtr = WeakThumbnailBorder.Pin(); + if (ThumbnailBorderPtr.IsValid() && ThumbnailBorderPtr->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ) + )); + + FText MeshNameText = FText::GetEmpty(); + //if (InputObject) + // MeshNameText = FText::FromString(InputObject->GetName()); + + TSharedPtr StaticMeshComboButton; + + TSharedPtr ButtonBox; + HorizontalBox->AddSlot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + [ + SAssignNew(ButtonBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SAssignNew(StaticMeshComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromName(AssetData.AssetName)) + .ToolTipText(FText::FromString(MainParam->GetValueAt(Idx))) + ] + ] + ] + ]; + + TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); + StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( + [UnrealRefClass, WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() + { + TArray AllowedClasses; + if (UnrealRefClass != UObject::StaticClass()) + { + // Use the class specified by the user + AllowedClasses.Add(UnrealRefClass); + } + else + { + // Using UObject would list way too many assets, and take a long time to open the menu, + // so we need to reestrict the classes a bit + AllowedClasses.Add(UStaticMesh::StaticClass()); + AllowedClasses.Add(UHoudiniAsset::StaticClass()); + AllowedClasses.Add(USkeletalMesh::StaticClass()); + AllowedClasses.Add(UBlueprint::StaticClass()); + AllowedClasses.Add(UMaterialInterface::StaticClass()); + AllowedClasses.Add(UTexture::StaticClass()); + AllowedClasses.Add(ULevel::StaticClass()); + AllowedClasses.Add(UStreamableRenderAsset::StaticClass()); + AllowedClasses.Add(USoundBase::StaticClass()); + AllowedClasses.Add(UParticleSystem::StaticClass()); + AllowedClasses.Add(UFoliageType::StaticClass()); + } + + TArray NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(nullptr), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( + [WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) + { + TSharedPtr StaticMeshComboButtonPtr = WeakStaticMeshComboButton.Pin(); + if (StaticMeshComboButtonPtr.IsValid()) + { + StaticMeshComboButtonPtr->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + // Get the asset reference string for this object + // !! Accept null objects to allow clearing the asset picker !! + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); + + ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); + } + } + ), + FSimpleDelegate::CreateLambda([]() {})); + }) + ); + } + else if (bIsMultiLine) + { + TSharedPtr< SMultiLineEditableTextBox > MultiLineEditableTextBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) + {return true;}) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + FString NewString = ReferenceStr; + if (StringParams[0]->GetValueAt(Idx).Len() > 0) + NewString = StringParams[0]->GetValueAt(Idx) + "\n" + NewString; + + ChangeStringValueAt(NewString, nullptr, Idx, true, StringParams); + }) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Top).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(MultiLineEditableTextBox, SMultiLineEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainParam->GetValueAt(Idx))) + .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, StringParams]() + { + for (auto & NextSelectedParam : StringParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() { return RevertToDefault(Idx, StringParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + else + { + TSharedPtr< SEditableTextBox > EditableTextBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) + {return true;}) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + ChangeStringValueAt(ReferenceStr, nullptr, Idx, true, StringParams); + }) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(EditableTextBox, SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainParam->GetValueAt(Idx))) + .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) + { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, StringParams]() + { + for (auto & NextSelectedParam : StringParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() + { return RevertToDefault(Idx, StringParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ColorParams; + if (!CastParameters(InParams, ColorParams)) + return; + + if (ColorParams.Num() <= 0) + return; + + UHoudiniParameterColor* MainParam = ColorParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + bool bHasAlpha = (MainParam->GetTupleSize() == 4); + + // Add color picker UI. + TSharedPtr ColorBlock; + TSharedRef VerticalBox = SNew(SVerticalBox); + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(ColorBlock, SColorBlock) + .Color(MainParam->GetColorValue()) + .ShowBackgroundForAlpha(bHasAlpha) + ]; + + TWeakPtr WeakColorBlock(ColorBlock); + ColorBlock->SetOnMouseButtonDown(FPointerEventHandler::CreateLambda( + [MainParam, ColorParams, WeakColorBlock, bHasAlpha](const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) + { + if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) + return FReply::Unhandled(); + + TSharedPtr ColorBlockPtr = WeakColorBlock.Pin(); + FColorPickerArgs PickerArgs; + PickerArgs.ParentWidget = ColorBlockPtr.IsValid() ? ColorBlockPtr : nullptr; + PickerArgs.bUseAlpha = bHasAlpha; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), + MainParam->GetOuter(), true); + + bool bChanged = false; + for (auto & Param : ColorParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetColorValue(InColor)) + { + Param->MarkChanged(true); + bChanged = true; + } + } + + // cancel the transaction if there is actually no value changed + if (!bChanged) + { + Transaction.Cancel(); + } + }); + PickerArgs.InitialColorOverride = MainParam->GetColorValue(); + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker(PickerArgs); + return FReply::Handled(); + } + )); + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetButton(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ButtonParams; + if (!CastParameters(InParams, ButtonParams)) + return; + + if (ButtonParams.Num() <= 0) + return; + + UHoudiniParameterButton* MainParam = ButtonParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + FText ParameterTooltip = GetParameterTooltip(MainParam); + + TSharedRef HorizontalBox = SNew(SHorizontalBox); + TSharedPtr Button; + + // Add button UI. + HorizontalBox->AddSlot().Padding(1, 2, 4, 2) + [ + SAssignNew(Button, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ParameterLabelText) + .ToolTipText(ParameterTooltip) + .OnClicked(FOnClicked::CreateLambda( [MainParam, ButtonParams]() + { + for (auto & Param : ButtonParams) + { + if (!Param) + continue; + + // There is no undo redo operation for button + Param->MarkChanged(true); + } + + return FReply::Handled(); + })) + ]; + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ButtonStripParams; + if (!CastParameters(InParams, ButtonStripParams)) + return; + + if (ButtonStripParams.Num() <= 0) + return; + + UHoudiniParameterButtonStrip* MainParam = ButtonStripParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + if (!Row) + return; + + auto OnButtonStateChanged = [MainParam, ButtonStripParams](ECheckBoxState NewState, int32 Idx) + { + + /* + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterButtonStripChange", "Houdini Parameter Button Strip: Changing value"), + MainParam->GetOuter(), true); + */ + int32 StateInt = NewState == ECheckBoxState::Checked ? 1 : 0; + bool bChanged = false; + + for (auto & NextParam : ButtonStripParams) + { + if (!NextParam || NextParam->IsPendingKill()) + continue; + + if (!NextParam->Values.IsValidIndex(Idx)) + continue; + + //NextParam->Modify(); + if (NextParam->SetValueAt(Idx, StateInt)) + { + NextParam->MarkChanged(true); + bChanged = true; + } + } + + //if (!bChanged) + // Transaction.Cancel(); + + }; + + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + FText ParameterTooltip = GetParameterTooltip(MainParam); + + TSharedRef HorizontalBox = SNew(SHorizontalBox); + FLinearColor BgColor(0.53f, 0.81f, 0.82f, 1.0f); // Sky Blue Backgroud color + + for (int32 Idx = 0; Idx < MainParam->Count; ++Idx) + { + if (!MainParam->Values.IsValidIndex(Idx) || !MainParam->Labels.IsValidIndex(Idx)) + continue; + + bool bPressed = MainParam->Values[Idx] > 0; + FText LabelText = FText::FromString(MainParam->Labels[Idx]); + + TSharedPtr Button; + + HorizontalBox->AddSlot().Padding(0).FillWidth(1.0f) + [ + SAssignNew(Button, SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") + .IsChecked(bPressed ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged_Lambda([OnButtonStateChanged, Idx](ECheckBoxState NewState) + { + OnButtonStateChanged(NewState, Idx); + }) + .Content() + [ + SNew(STextBlock) + .Text(LabelText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + Button->SetColorAndOpacity(BgColor); + } + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetLabel(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray LabelParams; + if (!CastParameters(InParams, LabelParams)) + return; + + if (LabelParams.Num() <= 0) + return; + + UHoudiniParameterLabel* MainParam = LabelParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + + for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) + { + FString NextLabelString = MainParam->GetStringAtIndex(Index); + FText ParameterLabelText = FText::FromString(NextLabelString); + + TSharedPtr TextBlock; + + // Add Label UI. + VerticalBox->AddSlot().Padding(1, 2, 4, 2) + [ + SAssignNew(TextBlock, STextBlock).Text(ParameterLabelText) + ]; + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetToggle(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray ToggleParams; + if (!CastParameters(InParams, ToggleParams)) + return; + + if (ToggleParams.Num() <= 0) + return; + + UHoudiniParameterToggle* MainParam = ToggleParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + + TSharedRef VerticalBox = SNew(SVerticalBox); + auto IsToggleCheckedLambda = [MainParam](int32 Index) + { + if (Index >= MainParam->GetNumValues()) + return ECheckBoxState::Unchecked; + + if (MainParam->GetValueAt(Index)) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; + }; + + auto OnToggleCheckStateChanged = [MainParam, ToggleParams](ECheckBoxState NewState, int32 Index) + { + if (Index >= MainParam->GetNumValues()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterToggleChange", "Houdini Parameter Toggle: Changing value"), + MainParam->GetOuter(), true); + + bool bState = (NewState == ECheckBoxState::Checked); + + bool bChanged = false; + for (auto & Param : ToggleParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetValueAt(bState, Index)) + { + bChanged = true; + Param->MarkChanged(true); + } + } + + // Cancel the transaction if no parameter has actually been changed + if (!bChanged) + { + Transaction.Cancel(); + } + }; + + for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) + { + TSharedPtr< SCheckBox > CheckBox; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + [ + SAssignNew(CheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([OnToggleCheckStateChanged, Index](ECheckBoxState NewState) { + OnToggleCheckStateChanged(NewState, Index); + + }) + .IsChecked_Lambda([IsToggleCheckedLambda, Index]() { + return IsToggleCheckedLambda(Index); + }) + .Content() + [ + SNew(STextBlock) + .Text(ParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FileParams; + if (!CastParameters(InParams, FileParams)) + return; + + if (FileParams.Num() <= 0) + return; + + UHoudiniParameterFile* MainParam = FileParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + + FString FileTypeWidgetFilter = TEXT("All files (*.*)|*.*"); + if (!MainParam->GetFileFilters().IsEmpty()) + FileTypeWidgetFilter = FString::Printf(TEXT("%s files (%s)|%s"), *MainParam->GetFileFilters(), *MainParam->GetFileFilters(), *MainParam->GetFileFilters()); + + FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); + + auto UpdateCheckRelativePath = [MainParam](const FString & PickedPath) + { + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(MainParam->GetOuter()); + if (MainParam->GetOuter() && !PickedPath.IsEmpty() && FPaths::IsRelative(PickedPath)) + { + // Check if the path is relative to the UE4 project + FString AbsolutePath = FPaths::ConvertRelativePathToFull(PickedPath); + if (FPaths::FileExists(AbsolutePath)) + { + return AbsolutePath; + } + + // Check if the path is relative to the asset + if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + { + if (HoudiniAssetComponent->HoudiniAsset && !HoudiniAssetComponent->HoudiniAsset->IsPendingKill()) + { + FString AssetFilePath = FPaths::GetPath(HoudiniAssetComponent->HoudiniAsset->AssetFileName); + if (FPaths::FileExists(AssetFilePath)) + { + FString UpdatedFileWidgetPath = FPaths::Combine(*AssetFilePath, *PickedPath); + if (FPaths::FileExists(UpdatedFileWidgetPath)) + { + return UpdatedFileWidgetPath; + } + } + } + } + } + + return PickedPath; + }; + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + FString FileWidgetPath = MainParam->GetValueAt(Idx); + FString FileWidgetBrowsePath = BrowseWidgetDirectory; + + if (!FileWidgetPath.IsEmpty()) + { + FString FileWidgetDirPath = FPaths::GetPath(FileWidgetPath); + if (!FileWidgetDirPath.IsEmpty()) + FileWidgetBrowsePath = FileWidgetDirPath; + } + + bool IsDirectoryPicker = MainParam->GetParameterType() == EHoudiniParameterType::FileDir; + bool bIsNewFile = !MainParam->IsReadOnly(); + + FText BrowseTooltip = LOCTEXT("FileButtonToolTipText", "Choose a file from this computer"); + if (IsDirectoryPicker) + BrowseTooltip = LOCTEXT("DirButtonToolTipText", "Choose a directory from this computer"); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SNewFilePathPicker) + .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) + .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") + .BrowseButtonToolTip(BrowseTooltip) + .BrowseDirectory(FileWidgetBrowsePath) + .BrowseTitle(LOCTEXT("PropertyEditorTitle", "File picker...")) + .FilePath(FileWidgetPath) + .FileTypeFilter(FileTypeWidgetFilter) + .IsNewFile(bIsNewFile) + .IsDirectoryPicker(IsDirectoryPicker) + .ToolTipText_Lambda([MainParam]() + { + // return the current param value as a tooltip + FString FileValue = MainParam ? MainParam->GetValueAt(0) : FString(); + return FText::FromString(FileValue); + }) + .OnPathPicked(FOnPathPicked::CreateLambda([MainParam, FileParams, UpdateCheckRelativePath, Idx](const FString & PickedPath) + { + if (MainParam->GetNumValues() <= Idx) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFileChange", "Houdini Parameter File: Changing a file path"), + MainParam->GetOuter(), true); + + bool bChanged = false; + + for (auto & Param : FileParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetValueAt(UpdateCheckRelativePath(PickedPath), Idx)) + { + bChanged = true; + Param->MarkChanged(true); + } + } + + // Cancel the transaction if no value has actually been changed + if (!bChanged) + { + Transaction.Cancel(); + } + })) + ] + ]; + + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + + +void +FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ChoiceParams; + if (!CastParameters(InParams, ChoiceParams)) + return; + + if (ChoiceParams.Num() <= 0) + return; + + UHoudiniParameterChoice* MainParam = ChoiceParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + // Lambda for changing the parameter value + auto ChangeSelectionLambda = [ChoiceParams](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterChoiceChange", "Houdini Parameter Choice: Changing selection"), + ChoiceParams[0]->GetOuter()); + + const int32 NewIntValue = ChoiceParams[0]->GetIntValueFromLabel(*NewChoice); + + bool bChanged = false; + for (int Idx = 0; Idx < ChoiceParams.Num(); Idx++) + { + if (!ChoiceParams[Idx]) + continue; + + ChoiceParams[Idx]->Modify(); + if (ChoiceParams[Idx]->SetIntValue(NewIntValue)) + { + bChanged = true; + ChoiceParams[Idx]->MarkChanged(true); + ChoiceParams[Idx]->UpdateStringValueFromInt(); + } + } + + if (!bChanged) + { + // Cancel the transaction if no parameter was changed + Transaction.Cancel(); + } + }; + + // + MainParam->UpdateChoiceLabelsPtr(); + TArray>* OptionSource = MainParam->GetChoiceLabelsPtr(); + TSharedPtr IntialSelec; + if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValue())) + { + IntialSelec = (*OptionSource)[MainParam->GetIntValue()]; + } + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBox; + HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SAssignNew( ComboBox, SComboBox< TSharedPtr< FString > > ) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + []( TSharedPtr< FString > InItem ) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [ChangeSelectionLambda](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + ChangeSelectionLambda(NewChoice, SelectType); + }) + [ + SNew(STextBlock) + .Text_Lambda([MainParam]() { return FText::FromString(MainParam->GetLabel()); }) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + if ( ComboBox.IsValid() ) + ComboBox->SetEnabled( !MainParam->IsDisabled() ); + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetSeparator(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + TSharedRef HorizontalBox = SNew(SCustomizedBox); + + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + + Row->WholeRowWidget.Widget = HorizontalBox; +} + +void +FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray OperatorPathParams; + if (!CastParameters(InParams, OperatorPathParams)) + return; + + if (OperatorPathParams.Num() <= 0) + return; + + UHoudiniParameterOperatorPath* MainParam = OperatorPathParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + UHoudiniInput* MainInput = MainParam->HoudiniInput.Get(); + if (!MainInput) + return; + + // Build an array of edited inputs for multi edition + TArray EditedInputs; + EditedInputs.Add(MainInput); + + // Add the corresponding inputs found in the other HAC + for (int LinkedIdx = 1; LinkedIdx < OperatorPathParams.Num(); LinkedIdx++) + { + UHoudiniInput* LinkedInput = OperatorPathParams[LinkedIdx]->HoudiniInput.Get(); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if (!LinkedInput->Matches(*MainInput)) + continue; + + EditedInputs.Add(LinkedInput); + } + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FHoudiniInputDetails::CreateWidget(HouParameterCategory, EditedInputs, Row); + + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetFloatRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // TODO: remove this once we have verified that updating the Points and CachedPoints arrays in + // TODO: HoudiniParameterTranslator::BuildAllParameters() (via RampParam->UpdatePointsArray()) is sufficient. + // // Parsing a float ramp: 1->(2->3->4)*->5 // + // switch (MainParam->GetParameterType()) + // { + // //*****State 1: Float Ramp*****// + // case EHoudiniParameterType::FloatRamp: + // { + // UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); + // if (FloatRampParameter) + // { + // CurrentRampFloat = FloatRampParameter; + // CurrentRampFloatPointsArray.Empty(); + // + // CurrentRampParameterList = InParams; + // + // FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + // CurrentRampRow = Row; + // } + // break; + // } + // + // case EHoudiniParameterType::Float: + // { + // UHoudiniParameterFloat* FloatParameter = Cast(MainParam); + // if (FloatParameter) + // { + // bool bCreateNewPoint = true; + // if (CurrentRampFloatPointsArray.Num() > 0) + // { + // UHoudiniParameterRampFloatPoint* LastPtInArr = CurrentRampFloatPointsArray.Last(); + // if (LastPtInArr && !LastPtInArr->ValueParentParm) + // bCreateNewPoint = false; + // } + // + // //*****State 2: Float Parameter (position)*****// + // if (bCreateNewPoint) + // { + // UHoudiniParameterRampFloatPoint* NewRampFloatPoint = nullptr; + // + // int32 PointIndex = CurrentRampFloatPointsArray.Num(); + // if (CurrentRampFloat->Points.IsValidIndex(PointIndex)) + // { + // + // // TODO: We should reuse existing point objects, if they exist. Currently + // // this causes results in unexpected behaviour in other parts of this detail code. + // // Give this code a bit of an overhaul at some point. + // // NewRampFloatPoint = CurrentRampFloat->Points[PointIndex]; + // } + // + // if (!NewRampFloatPoint) + // { + // // Create a new float ramp point, and add its pointer to the current float points buffer array. + // NewRampFloatPoint = NewObject< UHoudiniParameterRampFloatPoint >(CurrentRampFloat, FName(), CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); + // + // } + // + // CurrentRampFloatPointsArray.Add(NewRampFloatPoint); + // + // if (FloatParameter->GetNumberOfValues() <= 0) + // return; + // // Set the float ramp point's position parent parm, and value + // NewRampFloatPoint->PositionParentParm = FloatParameter; + // NewRampFloatPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); + // } + // //*****State 3: Float Parameter (value)*****// + // else + // { + // if (FloatParameter->GetNumberOfValues() <= 0) + // return; + // // Get the last point in the buffer array + // if (CurrentRampFloatPointsArray.Num() > 0) + // { + // // Set the last inserted float ramp point's float parent parm, and value + // UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); + // LastAddedFloatRampPoint->ValueParentParm = FloatParameter; + // LastAddedFloatRampPoint->SetValue(FloatParameter->GetValuesPtr()[0]); + // } + // } + // } + // + // break; + // } + // //*****State 4: Choice parameter*****// + // case EHoudiniParameterType::IntChoice: + // { + // UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); + // if (ChoiceParameter && CurrentRampFloatPointsArray.Num() > 0) + // { + // // Set the last inserted float ramp point's interpolation parent parm, and value + // UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); + // + // LastAddedFloatRampPoint->InterpolationParentParm = ChoiceParameter; + // LastAddedFloatRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + // + // // Set the index of this point in the multi parm. + // LastAddedFloatRampPoint->InstanceIndex = CurrentRampFloatPointsArray.Num() - 1; + // } + // + // + // //*****State 5: All ramp points have been parsed, finish!*****// + // if (CurrentRampFloatPointsArray.Num() >= (int32)CurrentRampFloat->MultiParmInstanceCount) + // { + // CurrentRampFloatPointsArray.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { + // return P1.Position < P2.Position; + // }); + // + // CurrentRampFloat->Points = CurrentRampFloatPointsArray; + // + // // Not caching, points are synced, update cached points + // if (!CurrentRampFloat->bCaching) + // { + // const int32 NumPoints = CurrentRampFloat->Points.Num(); + // CurrentRampFloat->CachedPoints.SetNum(NumPoints); + // for (int32 i = 0; i < NumPoints; ++i) + // { + // UHoudiniParameterRampFloatPoint* FromPoint = CurrentRampFloat->Points[i]; + // UHoudiniParameterRampFloatPoint* ToPoint = CurrentRampFloat->CachedPoints[i]; + // ToPoint = nullptr; + // check(FromPoint) + // if (!ToPoint) + // { + // ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampFloat, RF_NoFlags, CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); + // } + // else + // { + // ToPoint->CopyStateFrom(FromPoint, true); + // } + // CurrentRampFloat->CachedPoints[i] = ToPoint; + // } + // } + // + // CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampFloat, CurrentRampParameterList); + // + // CurrentRampFloat->SetDefaultValues(); + // + // CurrentRampFloat = nullptr; + // CurrentRampRow = nullptr; + // } + // + // break; + // } + // + // default: + // break; + // } + + //*****Float Ramp*****// + if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); + if (FloatRampParameter) + { + CurrentRampFloat = FloatRampParameter; + FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + CreateWidgetRampPoints(HouParameterCategory, Row, FloatRampParameter, InParams); + //FloatRampParameter->SetDefaultValues(); + } + } +} + +void +FHoudiniParameterDetails::CreateWidgetColorRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // TODO: remove this once we have verified that updating the Points and CachedPoints arrays in + // TODO: HoudiniParameterTranslator::BuildAllParameters() (via RampParam->UpdatePointsArray()) is sufficient. + // // Parsing a color ramp: 1->(2->3->4)*->5 // + // switch (MainParam->GetParameterType()) + // { + // //*****State 1: Color Ramp*****// + // case EHoudiniParameterType::ColorRamp: + // { + // UHoudiniParameterRampColor* RampColor = Cast(MainParam); + // if (RampColor) + // { + // CurrentRampColor = RampColor; + // CurrentRampColorPointsArray.Empty(); + // + // CurrentRampParameterList = InParams; + // + // FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + // CurrentRampRow = Row; + // } + // + // break; + // } + // //*****State 2: Float parameter*****// + // case EHoudiniParameterType::Float: + // { + // UHoudiniParameterFloat* FloatParameter = Cast(MainParam); + // if (FloatParameter) + // { + // // Create a new color ramp point, and add its pointer to the current color points buffer array. + // UHoudiniParameterRampColorPoint* NewRampColorPoint = nullptr; + // int32 PointIndex = CurrentRampColorPointsArray.Num(); + // + // if (CurrentRampColor->Points.IsValidIndex(PointIndex)) + // { + // NewRampColorPoint = CurrentRampColor->Points[PointIndex]; + // } + // + // if (!NewRampColorPoint) + // { + // NewRampColorPoint = NewObject< UHoudiniParameterRampColorPoint >(CurrentRampColor, FName(), CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); + // } + // + // CurrentRampColorPointsArray.Add(NewRampColorPoint); + // + // if (FloatParameter->GetNumberOfValues() <= 0) + // return; + // // Set the color ramp point's position parent parm, and value + // NewRampColorPoint->PositionParentParm = FloatParameter; + // NewRampColorPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); + // } + // + // break; + // } + // //*****State 3: Color parameter*****// + // case EHoudiniParameterType::Color: + // { + // UHoudiniParameterColor* ColorParameter = Cast(MainParam); + // if (ColorParameter && CurrentRampColorPointsArray.Num() > 0) + // { + // // Set the last inserted color ramp point's color parent parm, and value + // UHoudiniParameterRampColorPoint* LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); + // LastAddedColorRampPoint->ValueParentParm = ColorParameter; + // LastAddedColorRampPoint->SetValue(ColorParameter->GetColorValue()); + // } + // + // break; + // } + // //*****State 4: Choice Parameter*****// + // case EHoudiniParameterType::IntChoice: + // { + // UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); + // if (ChoiceParameter) + // { + // // Set the last inserted color ramp point's interpolation parent parm, and value + // UHoudiniParameterRampColorPoint*& LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); + // + // LastAddedColorRampPoint->InterpolationParentParm = ChoiceParameter; + // LastAddedColorRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + // + // // Set the index of this point in the multi parm. + // LastAddedColorRampPoint->InstanceIndex = CurrentRampColorPointsArray.Num() - 1; + // } + // + // + // //*****State 5: All ramp points have been parsed, finish!*****// + // if (CurrentRampColorPointsArray.Num() >= (int32)CurrentRampColor->MultiParmInstanceCount) + // { + // CurrentRampColorPointsArray.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) + // { + // return P1.Position < P2.Position; + // }); + // + // CurrentRampColor->Points = CurrentRampColorPointsArray; + // + // // Not caching, points are synced, update cached points + // + // if (!CurrentRampColor->bCaching) + // { + // const int32 NumPoints = CurrentRampColor->Points.Num(); + // CurrentRampColor->CachedPoints.SetNum(NumPoints); + // + // for (int32 i = 0; i < NumPoints; ++i) + // { + // UHoudiniParameterRampColorPoint* FromPoint = CurrentRampColor->Points[i]; + // UHoudiniParameterRampColorPoint* ToPoint = CurrentRampColor->CachedPoints[i]; + // + // if (!ToPoint) + // { + // ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampColor, RF_NoFlags, CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); + // } + // else + // { + // ToPoint->CopyStateFrom(FromPoint, true); + // } + // CurrentRampColor->CachedPoints[i] = ToPoint; + // } + // } + // + // + // CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampColor, CurrentRampParameterList); + // + // CurrentRampColor->SetDefaultValues(); + // + // CurrentRampColor = nullptr; + // CurrentRampRow = nullptr; + // } + // + // break; + // } + // + // default: + // break; + // } + + //*****Color Ramp*****// + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* RampColor = Cast(MainParam); + if (RampColor) + { + CurrentRampColor = RampColor; + FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + CreateWidgetRampPoints(HouParameterCategory, Row, RampColor, InParams); + //RampColor->SetDefaultValues(); + } + } + +} + + +FDetailWidgetRow* +FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() <= 0) + return nullptr; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam) + return nullptr; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, (TArray)InParams); + if (!Row) + return nullptr; + + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + + // Create the standard parameter name widget with an added autoupdate checkbox. + CreateNameWidgetWithAutoUpdate(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor *RampColorParam = Cast(MainParam); + if (!RampColorParam) + return nullptr; + + TSharedPtr ColorGradientEditor; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SBorder) + .VAlign(VAlign_Fill) + [ + SAssignNew(ColorGradientEditor, SHoudiniColorRampCurveEditor) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + ] + ]; + + if (!ColorGradientEditor.IsValid()) + return nullptr; + + // Avoid showing tooltips inside of the curve editor + ColorGradientEditor->EnableToolTipForceField(true); + + CurrentRampParameterColorCurve = NewObject( + GetTransientPackage(), UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + + if (!CurrentRampParameterColorCurve) + return nullptr; + + CreatedColorRampCurves.Add(CurrentRampParameterColorCurve); + + // Add the ramp curve to root to avoid garabage collected. + CurrentRampParameterColorCurve->AddToRoot(); + + TArray ColorRampParameters; + CastParameters(InParams, ColorRampParameters); + + for (auto NextColorRamp : ColorRampParameters) + { + CurrentRampParameterColorCurve->ColorRampParameters.Add(NextColorRamp); + } + ColorGradientEditor->HoudiniColorRampCurve = CurrentRampParameterColorCurve; + + // Clear default curve points + for (int Idx = 0; Idx < 4; ++Idx) + { + FRichCurve& RichCurve = (CurrentRampParameterColorCurve->FloatCurves)[Idx]; + if (RichCurve.GetNumKeys() > 0) + RichCurve.Keys.Empty(); + } + ColorGradientEditor->SetCurveOwner(CurrentRampParameterColorCurve); + CreatedColorGradientEditors.Add(ColorGradientEditor); + } + else if(MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat *RampFloatParam = Cast(MainParam); + if (!RampFloatParam) + return nullptr; + + TSharedPtr FloatCurveEditor; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SBorder) + .VAlign(VAlign_Fill) + [ + SAssignNew(FloatCurveEditor, SHoudiniFloatRampCurveEditor) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + .HideUI(true) + .DrawCurve(true) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + .ViewMinOutput(0.0f) + .ViewMaxOutput(1.0f) + .TimelineLength(1.0f) + .AllowZoomOutput(false) + .ShowInputGridNumbers(false) + .ShowOutputGridNumbers(false) + .ShowZoomButtons(false) + .ZoomToFitHorizontal(false) + .ZoomToFitVertical(false) + .XAxisName(FString("X")) + .YAxisName(FString("Y")) + .ShowCurveSelector(false) + + ] + ]; + + if (!FloatCurveEditor.IsValid()) + return nullptr; + + // Avoid showing tooltips inside of the curve editor + FloatCurveEditor->EnableToolTipForceField(true); + + CurrentRampParameterFloatCurve = NewObject( + GetTransientPackage(), UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + + if (!CurrentRampParameterFloatCurve) + return nullptr; + + CreatedFloatRampCurves.Add(CurrentRampParameterFloatCurve); + + // Add the ramp curve to root to avoid garbage collected + CurrentRampParameterFloatCurve->AddToRoot(); + + TArray FloatRampParameters; + CastParameters(InParams, FloatRampParameters); + for (auto NextFloatRamp : FloatRampParameters) + { + CurrentRampParameterFloatCurve->FloatRampParameters.Add(NextFloatRamp); + } + FloatCurveEditor->HoudiniFloatRampCurve = CurrentRampParameterFloatCurve; + + FloatCurveEditor->SetCurveOwner(CurrentRampParameterFloatCurve, true); + CreatedFloatCurveEditors.Add(FloatCurveEditor); + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + return Row; +} + + +void +FHoudiniParameterDetails::CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray& InParams) +{ + if (!Row || !InParameter) + return; + + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + UHoudiniParameterRampFloat * MainFloatRampParameter = nullptr; + UHoudiniParameterRampColor * MainColorRampParameter = nullptr; + + TArray FloatRampParameterList; + TArray ColorRampParameterList; + if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + MainFloatRampParameter = Cast(MainParam); + + if (!MainFloatRampParameter) + return; + + if (!CastParameters(InParams, FloatRampParameterList)) + return; + } + else if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + MainColorRampParameter = Cast(MainParam); + + if (!MainColorRampParameter) + return; + + if (!CastParameters(InParams, ColorRampParameterList)) + return; + } + else + { + return; + } + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Lambda for computing the float point to be inserted + auto GetInsertFloatPointLambda = [MainFloatRampParameter]( + const int32& InsertAtIndex, + float& OutPosition, + float& OutValue, + EHoudiniRampInterpolationType& OutInterpType) mutable + { + if (!MainFloatRampParameter) + return; + + float PrevPosition = 0.0f; + float NextPosition = 1.0f; + + TArray &CurrentPoints = MainFloatRampParameter->Points; + TArray &CachedPoints = MainFloatRampParameter->CachedPoints; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + int32 NumPoints = 0; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + { + NumPoints = CurrentPoints.Num(); + } + else + { + MainFloatRampParameter->SetCaching(true); + NumPoints = CachedPoints.Num(); + } + + if (InsertAtIndex >= NumPoints) + { + // Insert at the end + if (NumPoints > 0) + { + UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + PrevPoint = CurrentPoints.Last(); + else + PrevPoint = CachedPoints.Last(); + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + } + } + else if (InsertAtIndex <= 0) + { + // Insert at the beginning + if (NumPoints > 0) + { + UHoudiniParameterRampFloatPoint* NextPoint = nullptr; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + NextPoint = CurrentPoints[0]; + else + NextPoint = CachedPoints[0]; + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + OutInterpType = NextPoint->GetInterpolation(); + } + } + } + else + { + // Insert in the middle + if (NumPoints > 1) + { + UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; + UHoudiniParameterRampFloatPoint* NextPoint = nullptr; + + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + { + PrevPoint = CurrentPoints[InsertAtIndex - 1]; + NextPoint = CurrentPoints[InsertAtIndex]; + } + else + { + PrevPoint = CachedPoints[InsertAtIndex - 1]; + NextPoint = CachedPoints[InsertAtIndex]; + } + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + } + + if (PrevPoint && NextPoint) + { + OutValue = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; + } + } + } + + OutPosition = (PrevPosition + NextPosition) / 2.0f; + }; + + + // Lambda for computing the color point to be inserted + auto GetInsertColorPointLambda = [MainColorRampParameter]( + const int32& InsertAtIndex, + float& OutPosition, + FLinearColor& OutColor, + EHoudiniRampInterpolationType& OutInterpType) mutable + { + if (!MainColorRampParameter) + return; + + float PrevPosition = 0.0f; + float NextPosition = 1.0f; + + TArray &CurrentPoints = MainColorRampParameter->Points; + TArray &CachedPoints = MainColorRampParameter->CachedPoints; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + int32 NumPoints = 0; + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NumPoints = CurrentPoints.Num(); + else + NumPoints = CachedPoints.Num(); + + if (InsertAtIndex >= NumPoints) + { + // Insert at the end + if (NumPoints > 0) + { + UHoudiniParameterRampColorPoint* PrevPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + PrevPoint = CurrentPoints.Last(); + else + PrevPoint = CachedPoints.Last(); + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + } + } + else if (InsertAtIndex <= 0) + { + // Insert at the beginning + if (NumPoints > 0) + { + UHoudiniParameterRampColorPoint* NextPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NextPoint = CurrentPoints[0]; + else + NextPoint = CachedPoints[0]; + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + OutInterpType = NextPoint->GetInterpolation(); + } + } + } + else + { + // Insert in the middle + if (NumPoints > 1) + { + UHoudiniParameterRampColorPoint* PrevPoint = nullptr; + UHoudiniParameterRampColorPoint* NextPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + { + PrevPoint = CurrentPoints[InsertAtIndex - 1]; + NextPoint = CurrentPoints[InsertAtIndex]; + } + else + { + PrevPoint = CachedPoints[InsertAtIndex - 1]; + NextPoint = CachedPoints[InsertAtIndex]; + } + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + } + + if (PrevPoint && NextPoint) + { + OutColor = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; + } + } + } + + OutPosition = (PrevPosition + NextPosition) / 2.0f; + }; + + int32 RowIndex = 0; + auto InsertRampPoint_Lambda = [GetInsertColorPointLambda, GetInsertFloatPointLambda, &CategoryBuilder, bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, + UHoudiniParameterRampColor* MainRampColor, + TArray &RampFloatList, + TArray &RampColorList, + const int32& Index) mutable + { + if (MainRampFloat) + { + float InsertPosition = 0.0f; + float InsertValue = 1.0f; + EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; + + GetInsertFloatPointLambda(Index, InsertPosition, InsertValue, InsertInterp); + + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); + + for (auto & NextFloatRamp : RampFloatList) + { + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + CreateFloatRampParameterInsertEvent( + NextFloatRamp, InsertPosition, InsertValue, InsertInterp); + + NextFloatRamp->MarkChanged(true); + } + else + { + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject + (NextFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); + NewCachedPoint->Position = InsertPosition; + NewCachedPoint->Value = InsertValue; + NewCachedPoint->Interpolation = InsertInterp; + + NextFloatRamp->CachedPoints.Add(NewCachedPoint); + NextFloatRamp->bCaching = true; + if (!bCookingEnabled) + { + // If cooking is not enabled, be sure to mark this parameter as changed + // so that it triggers an update once cooking is enabled again. + NextFloatRamp->MarkChanged(true); + } + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + { + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + } + else if (MainRampColor) + { + float InsertPosition = 0.0f; + FLinearColor InsertColor = FLinearColor::Black; + EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; + + GetInsertColorPointLambda(Index, InsertPosition, InsertColor, InsertInterp); + + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); + for (auto& NextColorRamp : RampColorList) + { + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + CreateColorRampParameterInsertEvent( + NextColorRamp, InsertPosition, InsertColor, InsertInterp); + + NextColorRamp->MarkChanged(true); + } + else + { + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject + (NextColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); + NewCachedPoint->Position = InsertPosition; + NewCachedPoint->Value = InsertColor; + NewCachedPoint->Interpolation = InsertInterp; + + NextColorRamp->CachedPoints.Add(NewCachedPoint); + NextColorRamp->bCaching = true; + if (!bCookingEnabled) + NextColorRamp->MarkChanged(true); + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + auto DeleteRampPoint_Lambda = [bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, + UHoudiniParameterRampColor* MainRampColor, + TArray &FloatRampList, + TArray &ColorRampList, + const int32& Index) mutable + { + if (MainRampFloat) + { + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampList); + + for (auto& NextFloatRamp : FloatRampList) + { + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextFloatRamp->Points.Num() == 0) + return; + + UHoudiniParameterRampFloatPoint* PointToDelete = nullptr; + + if (Index == -1) + PointToDelete = NextFloatRamp->Points.Last(); + else if (NextFloatRamp->Points.IsValidIndex(Index)) + PointToDelete = NextFloatRamp->Points[Index]; + + if (!PointToDelete) + return; + + const int32 & InstanceIndexToDelete = PointToDelete->InstanceIndex; + + CreateFloatRampParameterDeleteEvent(NextFloatRamp, InstanceIndexToDelete); + NextFloatRamp->MarkChanged(true); + } + else + { + if (NextFloatRamp->CachedPoints.Num() == 0) + return; + + if (Index == -1) + NextFloatRamp->CachedPoints.Pop(); + else if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) + NextFloatRamp->CachedPoints.RemoveAt(Index); + else + return; + + NextFloatRamp->bCaching = true; + if (!bCookingEnabled) + NextFloatRamp->MarkChanged(true); + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); + } + } + else + { + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampList); + + for (auto& NextColorRamp : ColorRampList) + { + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextColorRamp->Points.Num() == 0) + return; + + UHoudiniParameterRampColorPoint* PointToRemove = nullptr; + + if (Index == -1) + PointToRemove = NextColorRamp->Points.Last(); + else if (NextColorRamp->Points.IsValidIndex(Index)) + PointToRemove = NextColorRamp->Points[Index]; + + if (!PointToRemove) + return; + + const int32 & InstanceIndexToDelete = PointToRemove->InstanceIndex; + + CreateColorRampParameterDeleteEvent(NextColorRamp, InstanceIndexToDelete); + + NextColorRamp->MarkChanged(true); + } + else + { + if (NextColorRamp->CachedPoints.Num() == 0) + return; + + if (Index == -1) + NextColorRamp->CachedPoints.Pop(); + else if (NextColorRamp->CachedPoints.IsValidIndex(Index)) + NextColorRamp->CachedPoints.RemoveAt(Index); + else + return; + + NextColorRamp->bCaching = true; + if (!bCookingEnabled) + NextColorRamp->MarkChanged(true); + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + + TSharedRef VerticalBox = StaticCastSharedRef(Row->ValueWidget.Widget); + + TSharedPtr GridPanel; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SAssignNew(GridPanel, SUniformGridPanel) + ]; + + //AllUniformGridPanels.Add(GridPanel.Get()); + + GridPanel->SetSlotPadding(FMargin(2.f, 2.f, 5.f, 3.f)); + GridPanel->AddSlot(0, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Position"))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + FString ValueString = TEXT("Value"); + if (!MainFloatRampParameter) + ValueString = TEXT("Color"); + + GridPanel->AddSlot(1, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(ValueString)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + GridPanel->AddSlot(2, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Interp."))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + + GridPanel->AddSlot(3, RowIndex) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateLambda([InsertRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, bCookingEnabled]() mutable + { + int32 InsertAtIndex = -1; + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + InsertAtIndex = MainFloatRampParameter->Points.Num(); + else + InsertAtIndex = MainFloatRampParameter->CachedPoints.Num(); + } + else if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + InsertAtIndex = MainColorRampParameter->Points.Num(); + else + InsertAtIndex = MainColorRampParameter->CachedPoints.Num(); + } + + InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, InsertAtIndex); + }), + LOCTEXT("AddRampPoint", "Add a ramp point to the end"), true) + ] + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateLambda([DeleteRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList]() mutable + { + DeleteRampPoint_Lambda( + MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, -1); + }), + LOCTEXT("DeleteRampPoint", "Delete the last ramp point"), true) + ] + + ]; + + EUnit Unit = EUnit::Unspecified; + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + int32 PointCount = 0; + // Use Synced points on auto update mode + // Use Cached points on manual update mode + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + PointCount = MainFloatRampParameter->Points.Num(); + else + PointCount = MainFloatRampParameter->CachedPoints.Num(); + } + + if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate()) + PointCount = MainColorRampParameter->Points.Num(); + else + PointCount = MainColorRampParameter->CachedPoints.Num(); + } + + // Lambda function for changing a ramp point + auto OnPointChangeCommit = [bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, UHoudiniParameterRampColor* MainRampColor, + UHoudiniParameterRampFloatPoint* MainRampFloatPoint, UHoudiniParameterRampColorPoint* MainRampColorPoint, + TArray &RampFloatList, TArray &RampColorList, + const int32& Index, const FString& ChangedDataName, + const float& NewPosition, const float& NewFloat, + const FLinearColor& NewColor, + const EHoudiniRampInterpolationType& NewInterpType) mutable + { + if (MainRampFloat && MainRampFloatPoint) + { + if (ChangedDataName == FString("position") && MainRampFloatPoint->GetPosition() == NewPosition) + return; + if (ChangedDataName == FString("value") && MainRampFloatPoint->GetValue() == NewFloat) + return; + if (ChangedDataName == FString("interp") && MainRampFloatPoint->GetInterpolation() == NewInterpType) + return; + + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); + for (auto NextFloatRamp : RampFloatList) + { + if (!NextFloatRamp) + continue; + + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextFloatRamp->Points.IsValidIndex(Index)) + { + UHoudiniParameterRampFloatPoint* CurrentFloatRampPoint = NextFloatRamp->Points[Index]; + if (!CurrentFloatRampPoint) + continue; + + if (ChangedDataName == FString("position")) + { + if (!CurrentFloatRampPoint->PositionParentParm) + continue; + + CurrentFloatRampPoint->SetPosition(NewPosition); + CurrentFloatRampPoint->PositionParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("value")) + { + if (!CurrentFloatRampPoint->PositionParentParm) + continue; + + CurrentFloatRampPoint->SetValue(NewFloat); + CurrentFloatRampPoint->ValueParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("interp")) + { + if (!CurrentFloatRampPoint->InterpolationParentParm) + continue; + + CurrentFloatRampPoint->SetInterpolation(NewInterpType); + CurrentFloatRampPoint->InterpolationParentParm->MarkChanged(true); + } + } + else + { + int32 IdxInEventsArray = Index - NextFloatRamp->Points.Num(); + if (NextFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = NextFloatRamp->ModificationEvents[IdxInEventsArray]; + if (!Event) + continue; + + if (ChangedDataName == FString("position")) + { + Event->InsertPosition = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + Event->InsertFloat = NewFloat; + } + else if (ChangedDataName == FString("interp")) + { + Event->InsertInterpolation = NewInterpType; + } + } + } + } + else + { + if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) + { + UHoudiniParameterRampFloatPoint* CachedPoint = NextFloatRamp->CachedPoints[Index]; + + if (!CachedPoint) + continue; + + if (ChangedDataName == FString("position")) + { + CachedPoint->Position = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + CachedPoint->Value = NewFloat; + } + else if (ChangedDataName == FString("interp")) + { + CachedPoint->Interpolation = NewInterpType; + } + + NextFloatRamp->bCaching = true; + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); + } + } + else if (MainRampColor && MainRampColorPoint) + { + if (ChangedDataName == FString("position") && MainRampColorPoint->GetPosition() == NewPosition) + return; + + if (ChangedDataName == FString("value") && MainRampColorPoint->GetValue() == NewColor) + return; + + if (ChangedDataName == FString("interp") && MainRampColorPoint->GetInterpolation() == NewInterpType) + return; + + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); + for (auto NextColorRamp : RampColorList) + { + if (!NextColorRamp) + continue; + + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextColorRamp->Points.IsValidIndex(Index)) + { + UHoudiniParameterRampColorPoint* CurrentColorRampPoint = NextColorRamp->Points[Index]; + if (!CurrentColorRampPoint) + continue; + + if (ChangedDataName == FString("position")) + { + if (!CurrentColorRampPoint->PositionParentParm) + continue; + + CurrentColorRampPoint->SetPosition(NewPosition); + CurrentColorRampPoint->PositionParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("value")) + { + if (!CurrentColorRampPoint->PositionParentParm) + continue; + + CurrentColorRampPoint->SetValue(NewColor); + CurrentColorRampPoint->ValueParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("interp")) + { + if (!CurrentColorRampPoint->InterpolationParentParm) + continue; + + CurrentColorRampPoint->SetInterpolation(NewInterpType); + CurrentColorRampPoint->InterpolationParentParm->MarkChanged(true); + } + } + else + { + int32 IdxInEventsArray = Index - NextColorRamp->Points.Num(); + if (NextColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = NextColorRamp->ModificationEvents[IdxInEventsArray]; + if (!Event) + continue; + + if (ChangedDataName == FString("position")) + { + Event->InsertPosition = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + Event->InsertColor = NewColor; + } + else if (ChangedDataName == FString("interp")) + { + Event->InsertInterpolation = NewInterpType; + } + + } + } + } + else + { + if (NextColorRamp->CachedPoints.IsValidIndex(Index)) + { + UHoudiniParameterRampColorPoint* CachedPoint = NextColorRamp->CachedPoints[Index]; + + if (!CachedPoint) + continue; + + if (ChangedDataName == FString("position")) + { + CachedPoint->Position = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + CachedPoint->Value = NewColor; + } + else if (ChangedDataName == FString("interp")) + { + CachedPoint->Interpolation = NewInterpType; + } + + NextColorRamp->bCaching = true; + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + for (int32 Index = 0; Index < PointCount; ++Index) + { + UHoudiniParameterRampFloatPoint* NextFloatRampPoint = nullptr; + UHoudiniParameterRampColorPoint* NextColorRampPoint = nullptr; + + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + NextFloatRampPoint = MainFloatRampParameter->Points[Index]; + else + NextFloatRampPoint = MainFloatRampParameter->CachedPoints[Index]; + } + if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NextColorRampPoint = MainColorRampParameter->Points[Index]; + else + NextColorRampPoint = MainColorRampParameter->CachedPoints[Index]; + } + + if (!NextFloatRampPoint && !NextColorRampPoint) + continue; + + RowIndex += 1; + + float CurPos = 0.f; + if (NextFloatRampPoint) + CurPos = NextFloatRampPoint->Position; + else + CurPos = NextColorRampPoint->Position; + + + GridPanel->AddSlot(0, RowIndex) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Value(CurPos) + .OnValueChanged_Lambda([](float Val) {}) + .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("position"), + Val, float(-1.0), + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .OnBeginSliderMovement_Lambda([]() {}) + .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("position"), + Val, float(-1.0), + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .SliderExponent(1.0f) + .TypeInterface(paramTypeInterface) + ]; + + if (NextFloatRampPoint) + { + GridPanel->AddSlot(1, RowIndex) + [ + SNew(SNumericEntryBox< float >) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Value(NextFloatRampPoint->Value) + .OnValueChanged_Lambda([](float Val){}) + .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), Val, + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .OnBeginSliderMovement_Lambda([]() {}) + .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), Val, + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .SliderExponent(1.0f) + .TypeInterface(paramTypeInterface) + ]; + } + else if (NextColorRampPoint) + { + auto OnColorChangeLambda = [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](FLinearColor InColor) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), float(-1.0), + InColor, + EHoudiniRampInterpolationType::LINEAR); + }; + + // Add color picker UI. + //TSharedPtr ColorBlock; + GridPanel->AddSlot(1, RowIndex) + [ + SNew(SColorBlock) + .Color(NextColorRampPoint->Value) + .OnMouseButtonDown( FPointerEventHandler::CreateLambda( + [NextColorRampPoint, OnColorChangeLambda](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) + { + if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) + return FReply::Unhandled(); + + FColorPickerArgs PickerArgs; + PickerArgs.bUseAlpha = true; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda(OnColorChangeLambda); + FLinearColor InitColor = NextColorRampPoint->Value; + PickerArgs.InitialColorOverride = InitColor; + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker(PickerArgs); + return FReply::Handled(); + })) + ]; + } + + int32 CurChoice = 0; + if (NextFloatRampPoint) + CurChoice = (int)NextFloatRampPoint->Interpolation; + else + CurChoice = (int)NextColorRampPoint->Interpolation; + + TSharedPtr >> ComboBoxCurveMethod; + GridPanel->AddSlot(2, RowIndex) + [ + SAssignNew(ComboBoxCurveMethod, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels())[CurChoice]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, + ColorRampParameterList, Index](TSharedPtr NewChoice, ESelectInfo::Type SelectType) mutable + { + EHoudiniRampInterpolationType NewInterpType = UHoudiniParameter::GetHoudiniInterpMethodFromString(*NewChoice.Get()); + + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("interp"), + float(-1.0), float(-1.0), + FLinearColor(), + NewInterpType); + }) + [ + SNew(STextBlock) + .Text_Lambda([NextFloatRampPoint, NextColorRampPoint]() + { + EHoudiniRampInterpolationType CurInterpType = EHoudiniRampInterpolationType::InValid; + if (NextFloatRampPoint) + CurInterpType = NextFloatRampPoint->GetInterpolation(); + else + CurInterpType = NextColorRampPoint->GetInterpolation(); + + return FText::FromString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(CurInterpType)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + GridPanel->AddSlot(3, RowIndex) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateLambda( + [InsertRampPoint_Lambda, MainFloatRampParameter, + MainColorRampParameter, FloatRampParameterList, + ColorRampParameterList, Index]() mutable + { + InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); + }), + LOCTEXT("AddRampPoint", "Add a ramp point before this point"), true) + ] + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateLambda( + [DeleteRampPoint_Lambda, MainFloatRampParameter, + MainColorRampParameter, FloatRampParameterList, + ColorRampParameterList, Index]() mutable + { + DeleteRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); + }), + LOCTEXT("DeleteFloatRampPoint", "Delete this ramp point"), true) + ] + ]; + + if (MainFloatRampParameter && CurrentRampParameterFloatCurve) + { + ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextFloatRampPoint->GetInterpolation()); + FRichCurve & RichCurve = CurrentRampParameterFloatCurve->FloatCurve; + FKeyHandle const KeyHandle = RichCurve.AddKey(NextFloatRampPoint->GetPosition(), NextFloatRampPoint->GetValue()); + RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); + } + + if (MainColorRampParameter && CurrentRampParameterColorCurve) + { + ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextColorRampPoint->GetInterpolation()); + for (int32 CurveIdx = 0; CurveIdx < 4; ++CurveIdx) + { + FRichCurve & RichCurve = CurrentRampParameterColorCurve->FloatCurves[CurveIdx]; + + FKeyHandle const KeyHandle = RichCurve.AddKey(NextColorRampPoint->GetPosition(), NextColorRampPoint->GetValue().Component(CurveIdx)); + RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); + } + } + } + + if (MainFloatRampParameter) + GridPanel->SetEnabled(!MainFloatRampParameter->IsDisabled()); + + if (MainColorRampParameter) + GridPanel->SetEnabled(!MainColorRampParameter->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetFolderList(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FolderListParams; + if (!CastParameters(InParams, FolderListParams)) + return; + + if (FolderListParams.Num() <= 0) + return; + + UHoudiniParameterFolderList* MainParam = FolderListParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Add this folder list to the folder map + AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); + + MainParam->GetTabs().Empty(); + + // A folder list will be followed by all its child folders, + // so set the CurrentFolderListSize to the tuple size, we'll process such many folder parameters right after + CurrentFolderListSize = MainParam->GetTupleSize(); + + if (MainParam->IsDirectChildOfMultiParm()) + MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); + + if (CurrentFolderListSize <= 0) // There should not be empty folder list, this will not happen normally + return; + + // The following folders belong to current folder list + CurrentFolderList = MainParam; + + // If the tab is either a tabs or radio button and the parameter is visible + if (MainParam->IsTabMenu() && MainParam->ShouldDisplay()) + { + // Set the current tabs to be not shown by default now. CreateWidgetTab will decide if the tabs is shown. + CurrentFolderList->SetTabsShown(false); + + // Create a row to hold tab buttons if the folder list is a tabs or radio button + + // CreateNestedRow does not actually create a row for tabs, it is responsible to prune the folder stack. + // ( CreateWidgetTab will be responsible to create a row according to the visibility of its outer level folders ) + FDetailWidgetRow* TabRow = CreateNestedRow(HouParameterCategory, InParams, false); + + } + + // When see a folder list, go depth first search at this step. + // Push an empty queue to the stack. + FolderStack.Add(TArray()); +} + + +void +FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FolderParams; + if (!CastParameters(InParams, FolderParams)) + return; + + if (FolderParams.Num() <= 0) + return; + + UHoudiniParameterFolder* MainParam = FolderParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen + return; + // If a folder is invisible, its children won't be listed by HAPI. + // So just reduce FolderListSize by 1, reduce the child counter of its parent folder by 1 if necessary, + // and prune the stack in such case. + if (!MainParam->IsVisible()) + { + CurrentFolderListSize -= 1; + + if (CurrentFolderListSize == 0) + { + if (FolderStack.Num() > 1) + { + TArray &ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + ParentFolderQueue[0]->GetChildCounter() -= 1; + } + + PruneStack(); + } + + return; + } + + // We expect 'TupleSize' children param of this folder after finish processing all the child folders of cur folderlist + MainParam->ResetChildCounter(); + + // Add this folder to the folder map + AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); + + // Set the parent param to current folderList. + // it was parent multiparm's id if this folder is a child of a multiparms. + // This will cause problem if the folder is inside of a multiparm + MainParam->SetParentParmId(CurrentFolderList->GetParmId()); + + + // Case 1: The folder is a direct child of a multiparm. + if (MainParam->IsDirectChildOfMultiParm()) + { + if (FolderStack.Num() <= 0) // This should not happen + return; + + // Get its parent multiparm first + UHoudiniParameterMultiParm* ParentMultiParm = nullptr; + { + UHoudiniParameterFolderList * ParentFolderList = nullptr; + if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) + return; + + ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); + + if (!ParentFolderList) + return; + + if (AllMultiParms.Contains(ParentFolderList->GetParentParmId())) + ParentMultiParm = AllMultiParms[ParentFolderList->GetParentParmId()]; + + if (!ParentMultiParm) // This should not happen + return; + } + + bool bShown = ParentMultiParm->IsShown(); + + // Case 1-1: The folder is NOT tabs + if (!MainParam->IsTab()) + { + bShown = MainParam->IsExpanded() && bShown; + + // If the parent multiparm is shown. + if (ParentMultiParm->IsShown()) + { + FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderHeaderRow, InParams); + } + } + // Case 1-2: The folder IS tabs. + else + { + CreateWidgetTab(HouParameterCategory, MainParam, ParentMultiParm->IsShown()); + } + + // Push the folder to the queue if it is not a tab folder + // This step is handled by CreateWidgetTab() if it is tabs + if ((!MainParam->IsTab() || !ParentMultiParm->IsShown()) && MainParam->GetTupleSize() > 0) + { + TArray & MyQueue = FolderStack.Last(); + MainParam->SetIsContentShown(bShown); + MyQueue.Add(MainParam); + } + } + + // Case 2: The folder is NOT a direct child of a multiparm. + else + { + // Case 2-1: The folder is in another folder. + if (FolderStack.Num() > 1 && CurrentFolderListSize > 0) + { + TArray & MyFolderQueue = FolderStack.Last(); + TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + + if (ParentFolderQueue.Num() <= 0) //This should happen + return; + + // Peek the folder queue of the last layer to get its parent folder parm. + bool ParentFolderVisible = ParentFolderQueue[0]->IsContentShown(); + + // If this folder is expanded (selected if is tabs) + bool bExpanded = ParentFolderVisible; + + // Case 2-1-1: The folder is NOT in a tab menu. + if (!MainParam->IsTab()) + { + bExpanded &= MainParam->IsExpanded(); + + // The parent folder is visible. + if (ParentFolderVisible) + { + // Add the folder header UI. + FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderHeaderRow, InParams); + } + + MainParam->SetIsContentShown(bExpanded); + MyFolderQueue.Add(MainParam); + } + // Case 2-1-2: The folder IS in a tab menu. + else + { + bExpanded &= MainParam->IsChosen(); + + CreateWidgetTab(HouParameterCategory, MainParam, ParentFolderVisible); + } + } + // Case 2-2: The folder is in the root. + else + { + bool bExpanded = true; + + // Case 2-2-1: The folder is NOT under a tab menu. + if (!MainParam->IsTab()) + { + if (FolderStack.Num() <= 0) // This will not happen + return; + + // Create Folder header under root. + FDetailWidgetRow* FolderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderRow, InParams); + + if (FolderStack.Num() == 0) // This should not happen + return; + + TArray& MyFolderQueue = FolderStack[0]; + bExpanded &= MainParam->IsExpanded(); + MainParam->SetIsContentShown(bExpanded); + MyFolderQueue.Add(MainParam); + } + // Case 2-2-2: The folder IS under a tab menu. + else + { + // Tabs in root is always visible + CreateWidgetTab(HouParameterCategory, MainParam, true); + } + } + } + + + CurrentFolderListSize -= 1; + + // Prune the stack if finished parsing current folderlist + if (CurrentFolderListSize == 0) + { + if (FolderStack.Num() > 1 && !MainParam->IsDirectChildOfMultiParm()) + { + TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + ParentFolderQueue[0]->GetChildCounter() -= 1; + } + + PruneStack(); + + CurrentFolderList = nullptr; + } +} + +void +FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray &InParams) +{ + if (!HeaderRow) // The folder is invisible. + return; + + TArray FolderParams; + if (!CastParameters(InParams, FolderParams)) + return; + + if (FolderParams.Num() <= 0) + return; + + UHoudiniParameterFolder* MainParam = FolderParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return; + + TSharedPtr VerticalBox; + + FString LabelStr = MainParam->GetParameterLabel(); + + TSharedPtr HorizontalBox; + TSharedPtr ExpanderArrow; + TSharedPtr ExpanderImage; + + HeaderRow->NameWidget.Widget = SAssignNew(HorizontalBox, SCustomizedBox); + + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + + if (MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 1) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + LabelStr = LabelStr + TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex) + TEXT(")"); + } + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SAssignNew(ExpanderArrow, SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([=]() + { + MainParam->ExpandButtonClicked(); + + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + + return FReply::Handled(); + }) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + + FText LabelText = FText::FromString(LabelStr); + + HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SNew(STextBlock) + .Text(LabelText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TWeakPtr WeakExpanderArrow(ExpanderArrow); + ExpanderImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([MainParam, WeakExpanderArrow]() + { + FName ResourceName; + TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); + if (MainParam->IsExpanded()) + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + } + ) + )); + + if(MainParam->GetFolderType() == EHoudiniFolderParameterType::Simple) + ExpanderArrow->SetEnabled(false); + +} + +void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InFolder, const bool& bIsShown) +{ + if (!InFolder || InFolder->IsPendingKill() || !CurrentFolderList) + return; + + if (FolderStack.Num() <= 0) // error state + return; + + TArray & FolderQueue = FolderStack.Last(); + + // Cache all tabs of current tab folder list. + CurrentFolderList->AddTabFolder(InFolder); + + // If the tabs is not shown, just push the folder param into the queue. + if (!bIsShown) + { + InFolder->SetIsContentShown(bIsShown); + FolderQueue.Add(InFolder); + return; + } + + // tabs currently being processed + CurrentTabs.Add(InFolder); + + if (CurrentFolderListSize > 1) + return; + + // The tabs belong to current folder list + UHoudiniParameterFolderList* CurrentTabMenuFolderList = CurrentFolderList; + + // Create a row (UI) for current tabs + TSharedPtr HorizontalBox; + FDetailWidgetRow &Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()) + [ + SAssignNew(HorizontalBox, SCustomizedBox) + ]; + + // Put current tab folder list param into an array + TArray CurrentTabMenuFolderListArr; + CurrentTabMenuFolderListArr.Add(CurrentTabMenuFolderList); + + HorizontalBox->SetHoudiniParameter(CurrentTabMenuFolderListArr); + DividerLinePositions.Add(HorizontalBox->AddIndentation(InFolder, AllMultiParms, AllFoldersAndFolderLists)); + HorizontalBox->DividerLinePositions = DividerLinePositions; + + float DesiredHeight = 0.0f; + float DesiredWidth = 0.0f; + + // Process all tabs of current folder list at once when done. + + for (auto & CurTab : CurrentTabs) + { + if (!CurTab || CurTab->IsPendingKill()) + continue; + + CurTab->SetIsContentShown(CurTab->IsChosen()); + FolderQueue.Add(CurTab); + + auto OnTabClickedLambda = [CurrentTabMenuFolderList, CurTab, &HouParameterCategory]() + { + if (CurrentTabMenuFolderList) + { + if (!CurrentTabMenuFolderList->bIsTabMenu || CurrentTabMenuFolderList->TabFolders.Num() <= 0) + return FReply::Handled(); + + if (CurTab->IsChosen()) + return FReply::Handled(); + + CurTab->SetChosen(true); + + for (UHoudiniParameterFolder* NextFolder : CurrentTabMenuFolderList->TabFolders) + { + if (CurTab->GetParmId() != NextFolder->GetParmId() && NextFolder->IsChosen()) + NextFolder->SetChosen(false); + } + + HouParameterCategory.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); + }; + + FString FolderLabelString = TEXT(" ") + CurTab->GetParameterLabel(); + if (CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio) + FolderLabelString = TEXT(" ") + FolderLabelString; + + bool bChosen = CurTab->IsTab() && CurTab->IsChosen(); + + TSharedPtr CurCustomizedButton; + + HorizontalBox->AddSlot().VAlign(VAlign_Bottom) + .AutoWidth() + .Padding(0.f) + .HAlign(HAlign_Left) + [ + SAssignNew(CurCustomizedButton, SCustomizedButton) + .OnClicked_Lambda(OnTabClickedLambda) + .Content() + [ + SNew(STextBlock) + .Text(FText::FromString(FolderLabelString)) + ] + ]; + + CurCustomizedButton->bChosen = bChosen; + CurCustomizedButton->bIsRadioButton = CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio; + + DesiredHeight = CurCustomizedButton->GetDesiredSize().Y; + DesiredWidth += CurCustomizedButton->GetDesiredSize().X; + } + + HorizontalBox->bIsTabFolderListRow = true; + + Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + // Set the current tabs to be shown, since slate widgets have been created + CurrentTabMenuFolderList->SetTabsShown(true); + + // Clear the temporary tabs + CurrentTabs.Empty(); +} + +void +FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray MultiParmParams; + if (!CastParameters(InParams, MultiParmParams)) + return; + + if (MultiParmParams.Num() <= 0) + return; + + UHoudiniParameterMultiParm* MainParam = MultiParmParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Add current multiparm parameter to AllmultiParms map + AllMultiParms.Add(MainParam->GetParmId(), MainParam); + + // Create a new detail row + FDetailWidgetRow * Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + { + MainParam->SetIsShown(false); + return; + } + + MainParam->SetIsShown(true); + + MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); + + CreateNameWidget(Row, InParams, true); + + auto OnInstanceValueChangedLambda = [MainParam](int32 InValue) + { + if (InValue < 0) + return; + + int32 ChangesCount = FMath::Abs(MainParam->MultiParmInstanceLastModifyArray.Num() - InValue); + + if (MainParam->MultiParmInstanceLastModifyArray.Num() > InValue) + { + for (int32 Idx = 0; Idx < ChangesCount; ++Idx) + MainParam->RemoveElement(-1); + + MainParam->MarkChanged(true); + } + else if (MainParam->MultiParmInstanceLastModifyArray.Num() < InValue) + { + for (int32 Idx = 0; Idx < ChangesCount; ++Idx) + MainParam->InsertElement(); + + MainParam->MarkChanged(true); + } + }; + + // Add multiparm UI. + TSharedRef HorizontalBox = SNew(SHorizontalBox); + TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; + int32 NumericalCount = MainParam->MultiParmInstanceCount; + HorizontalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateLambda([OnInstanceValueChangedLambda](int32 InValue) { + OnInstanceValueChangedLambda(InValue); + })) + .Value(NumericalCount) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamAddInstance", "Houdini Parameter Multi Parameter: Adding an instance"), + MainParam->GetOuter(), true); + + for (auto& Param : MultiParmParams) + { + if (!Param) + continue; + + // Add a reverse step for redo/undo + Param->MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Removed); + + Param->MarkChanged(true); + Param->Modify(); + + if (Param->MultiParmInstanceLastModifyArray.Num() > 0) + Param->MultiParmInstanceLastModifyArray.RemoveAt(Param->MultiParmInstanceLastModifyArray.Num() - 1); + + Param->InsertElement(); + + } + }), + LOCTEXT("AddMultiparmInstanceToolTipAddLastInstance", "Add an Instance"), true) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + // Remove the last multiparm instance + PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamDeleteInstance", "Houdini Parameter Multi Parameter: Deleting an instance"), + MainParam->GetOuter(), true); + + for (auto & Param : MultiParmParams) + { + TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; + int32 RemovedIndex = LastModifiedArray.Num() - 1; + while (LastModifiedArray.IsValidIndex(RemovedIndex) && LastModifiedArray[RemovedIndex] == EHoudiniMultiParmModificationType::Removed) + RemovedIndex -= 1; + + // Add a reverse step for redo/undo + EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; + if (LastModifiedArray.IsValidIndex(RemovedIndex)) + { + PreviousModType = LastModifiedArray[RemovedIndex]; + LastModifiedArray[RemovedIndex] = EHoudiniMultiParmModificationType::Inserted; + } + + Param->MarkChanged(true); + + Param->Modify(); + + if (LastModifiedArray.IsValidIndex(RemovedIndex)) + { + LastModifiedArray[RemovedIndex] = PreviousModType; + } + + Param->RemoveElement(RemovedIndex); + } + + }), + LOCTEXT("RemoveLastMultiParamLastToolTipRemoveLastInstance", "Remove the last instance"), true) + + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamDeleteAllInstances", "Houdini Parameter Multi Parameter: Deleting all instances"), + MainParam->GetOuter(), true); + + for (auto & Param : MultiParmParams) + { + TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; + TArray IndicesToReverse; + + for (int32 Index = 0; Index < LastModifiedArray.Num(); ++Index) + { + if (LastModifiedArray[Index] == EHoudiniMultiParmModificationType::None) + { + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; + IndicesToReverse.Add(Index); + } + } + + Param->MarkChanged(true); + + Param->Modify(); + + for (int32 & Index : IndicesToReverse) + { + if (LastModifiedArray.IsValidIndex(Index)) + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::None; + } + + + Param->EmptyElements(); + } + + }), + LOCTEXT("HoudiniParameterRemoveAllMultiparmInstancesToolTip", "Remove all instances"), true) + ]; + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniParameterDetails::CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams) +{ + + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return; + + if (!HorizontalBox || !AllMultiParms.Contains(MainParam->GetParentParmId()) || !MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + return; + + UHoudiniParameterMultiParm* MainParentMultiParm = AllMultiParms[MainParam->GetParentParmId()]; + + if (!MainParentMultiParm) + return; + + if (!MainParentMultiParm->IsShown()) + return; + + // push all parent multiparm of the InParams to the array + TArray ParentMultiParams; + for (auto & InParam : InParams) + { + if (!InParam) + continue; + + if (!MultiParmInstanceIndices.Contains(InParam->GetParentParmId())) + continue; + + if (InParam->GetChildIndex() == 0) + { + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[InParam->GetParentParmId()]; + + if (ParentMultiParm) + ParentMultiParams.Add(ParentMultiParm); + } + } + + + int32 InstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + + TSharedRef AddButton = PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() + { + for (auto & ParentParam : ParentMultiParams) + { + // Add button call back + if (!ParentParam) + continue; + + TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; + + if (!LastModifiedArray.IsValidIndex(InstanceIndex)) + continue; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParmAddBeforeCurInstance", "Houdini Parameter Multi Parm: Adding an instance"), + ParentParam->GetOuter(), true); + + + int32 Index = InstanceIndex; + + // Add a reverse step for undo/redo + if (Index >= LastModifiedArray.Num()) + LastModifiedArray.Add(EHoudiniMultiParmModificationType::Removed); + else + LastModifiedArray.Insert(EHoudiniMultiParmModificationType::Removed, Index); + + ParentParam->MarkChanged(true); + ParentParam->Modify(); + + if (Index >= LastModifiedArray.Num() - 1 && LastModifiedArray.Num()) + LastModifiedArray.RemoveAt(LastModifiedArray.Num() - 1); + else + LastModifiedArray.RemoveAt(Index); + + ParentParam->InsertElementAt(InstanceIndex); + + } + }), + LOCTEXT("HoudiniParameterMultiParamAddBeforeCurrentInstanceToolTip", "Insert an instance before this instance")); + + + TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() + { + for (auto & ParentParam : ParentMultiParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParmDeleteCurInstance", "Houdini Parameter Multi Parm: Deleting an instance"), + ParentParam->GetOuter(), true); + + + TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; + + int32 Index = InstanceIndex; + EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; + while (LastModifiedArray.IsValidIndex(Index) && LastModifiedArray[Index] == EHoudiniMultiParmModificationType::Removed) + { + Index -= 1; + } + + if (LastModifiedArray.IsValidIndex(Index)) + { + PreviousModType = LastModifiedArray[Index]; + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; + } + + ParentParam->MarkChanged(true); + + ParentParam->Modify(); + + if (LastModifiedArray.IsValidIndex(Index)) + { + LastModifiedArray[Index] = PreviousModType; + } + + ParentParam->RemoveElement(InstanceIndex); + } + + }), + LOCTEXT("HoudiniParameterMultiParamDeleteCurrentInstanceToolTip", "Remove an instance"), true); + + + HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[AddButton]; + HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[RemoveButton]; + + int32 StartIdx = MainParam->GetParameterType() == EHoudiniParameterType::Folder ? 1 : 0; + if (MainParam->GetChildIndex() != StartIdx) + { + AddButton.Get().SetVisibility(EVisibility::Hidden); + RemoveButton.Get().SetVisibility(EVisibility::Hidden); + } + +} + +void +FHoudiniParameterDetails::PruneStack() +{ + for (int32 StackItr = FolderStack.Num() - 1; StackItr >= 0; --StackItr) + { + TArray &CurrentQueue = FolderStack[StackItr]; + + for (int32 QueueItr = CurrentQueue.Num() - 1; QueueItr >= 0; --QueueItr) + { + UHoudiniParameterFolder * CurrentFolder = CurrentQueue[QueueItr]; + if (!CurrentFolder || CurrentFolder->IsPendingKill()) + continue; + + if (CurrentFolder->GetChildCounter() == 0) + { + CurrentQueue.RemoveAt(QueueItr); + } + } + + if (CurrentQueue.Num() == 0) + { + FolderStack.RemoveAt(StackItr); + } + } +} + +FText +FHoudiniParameterDetails::GetParameterTooltip(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return FText(); + + // Tooltip starts with Label (name) + FString Tooltip = InParam->GetParameterLabel() + TEXT(" (") + InParam->GetParameterName() + TEXT(")"); + + // Append the parameter type + FString ParmTypeStr = GetParameterTypeString(InParam->GetParameterType(), InParam->GetTupleSize()); + if (!ParmTypeStr.IsEmpty()) + Tooltip += TEXT("\n") + ParmTypeStr; + + // If the parameter has some help, append it + FString Help = InParam->GetParameterHelp(); + if (!Help.IsEmpty()) + Tooltip += TEXT("\n") + Help; + + // If the parameter has an expression, append it + if (InParam->HasExpression()) + { + FString Expr = InParam->GetExpression(); + if (!Expr.IsEmpty()) + Tooltip += TEXT("\nExpression: ") + Expr; + } + + return FText::FromString(Tooltip); +} + +FString +FHoudiniParameterDetails::GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize) +{ + FString ParamStr; + + switch (InType) + { + case EHoudiniParameterType::Button: + ParamStr = TEXT("Button"); + break; + + case EHoudiniParameterType::ButtonStrip: + ParamStr = TEXT("Button Strip"); + break; + + case EHoudiniParameterType::Color: + { + if (InTupleSize == 4) + ParamStr = TEXT("Color with Alpha"); + else + ParamStr = TEXT("Color"); + } + break; + + case EHoudiniParameterType::ColorRamp: + ParamStr = TEXT("Color Ramp"); + break; + + case EHoudiniParameterType::File: + ParamStr = TEXT("File (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileDir: + ParamStr = TEXT("File Dir (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileGeo: + ParamStr = TEXT("File Geo (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileImage: + ParamStr = TEXT("File Image (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::Float: + ParamStr = TEXT("Float (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); + break; + + case EHoudiniParameterType::FloatRamp: + ParamStr = TEXT("Float Ramp"); + break; + + case EHoudiniParameterType::Folder: + case EHoudiniParameterType::FolderList: + break; + + case EHoudiniParameterType::Input: + ParamStr = TEXT("Opearator Path"); + break; + + case EHoudiniParameterType::Int: + ParamStr = TEXT("Integer (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); + break; + + case EHoudiniParameterType::IntChoice: + ParamStr = TEXT("Int Choice"); + break; + + case EHoudiniParameterType::Label: + ParamStr = TEXT("Label (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::MultiParm: + ParamStr = TEXT("MultiParm"); + break; + + case EHoudiniParameterType::Separator: + break; + + case EHoudiniParameterType::String: + ParamStr = TEXT("String (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::StringAssetRef: + ParamStr = TEXT("String Asset Ref (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::StringChoice: + ParamStr = TEXT("String Choice"); + break; + + case EHoudiniParameterType::Toggle: + ParamStr = TEXT("Toggle (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + default: + ParamStr = TEXT("invalid parameter type"); + break; + } + + + return ParamStr; +} + +void +FHoudiniParameterDetails::SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter) +{ + if (!ColorRampParameter) + return; + + // Do not sync when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) + return; + + TArray &CachedPoints = ColorRampParameter->CachedPoints; + TArray &CurrentPoints = ColorRampParameter->Points; + + bool bCurveNeedsUpdate = false; + bool bRampParmNeedsUpdate = false; + + int32 Idx = 0; + + while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) + { + UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[Idx]; + UHoudiniParameterRampColorPoint* CurrentPoint = CurrentPoints[Idx]; + + if (!CachedPoint || !CurrentPoint) + continue; + + if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) + { + if (CurrentPoint->PositionParentParm) + { + CurrentPoint->SetPosition(CachedPoint->GetPosition()); + CurrentPoint->PositionParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + if (CachedPoint->GetValue() != CurrentPoint->GetValue()) + { + if (CurrentPoint->ValueParentParm) + { + CurrentPoint->SetValue(CachedPoint->GetValue()); + CurrentPoint->ValueParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) + { + if (CurrentPoint->InterpolationParentParm) + { + CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); + CurrentPoint->InterpolationParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + Idx += 1; + } + + // Insert points + for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) + { + UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[IdxCachedPointLeft]; + + CreateColorRampParameterInsertEvent( + ColorRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); + + bCurveNeedsUpdate = true; + bRampParmNeedsUpdate = true; + + } + + // Delete points + for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) + { + ColorRampParameter->RemoveElement(IdxCurrentPointLeft); + + UHoudiniParameterRampColorPoint* Point = CurrentPoints[IdxCurrentPointLeft]; + + if (!Point) + continue; + + CreateColorRampParameterDeleteEvent(ColorRampParameter, Point->InstanceIndex); + + bCurveNeedsUpdate = true; + bRampParmNeedsUpdate = true; + } + + + ColorRampParameter->MarkChanged(bRampParmNeedsUpdate); +} + +//void +//FHoudiniParameterDetails::SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter) +//{ +// if (!FloatRampParameter) +// return; +// +// // Do not sync when the Houdini asset component is cooking +// if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) +// return; +// +// TArray &CachedPoints = FloatRampParameter->CachedPoints; +// TArray &CurrentPoints = FloatRampParameter->Points; +// +// int32 Idx = 0; +// +// while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) +// { +// UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; +// UHoudiniParameterRampFloatPoint* &CurrentPoint = CurrentPoints[Idx]; +// +// if (!CachedPoint || !CurrentPoint) +// continue; +// +// if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) +// { +// if (CurrentPoint->PositionParentParm) +// { +// CurrentPoint->SetPosition(CachedPoint->GetPosition()); +// CurrentPoint->PositionParentParm->MarkChanged(true); +// } +// } +// +// if (CachedPoint->GetValue() != CurrentPoint->GetValue()) +// { +// if (CurrentPoint->ValueParentParm) +// { +// CurrentPoint->SetValue(CachedPoint->GetValue()); +// CurrentPoint->ValueParentParm->MarkChanged(true); +// } +// } +// +// if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) +// { +// if (CurrentPoint->InterpolationParentParm) +// { +// CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); +// CurrentPoint->InterpolationParentParm->MarkChanged(true); +// } +// } +// +// Idx += 1; +// } +// +// // Insert points +// for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) +// { +// UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; +// if (!CachedPoint) +// continue; +// +// CreateFloatRampParameterInsertEvent( +// FloatRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); +// +// FloatRampParameter->MarkChanged(true); +// } +// +// // Remove points +// for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) +// { +// FloatRampParameter->RemoveElement(IdxCurrentPointLeft); +// +// UHoudiniParameterRampFloatPoint* Point = CurrentPoints[IdxCurrentPointLeft]; +// +// if (!Point) +// continue; +// +// CreateFloatRampParameterDeleteEvent(FloatRampParameter, Point->InstanceIndex); +// +// FloatRampParameter->MarkChanged(true); +// } +//} + +void +FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetFloatRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + InParam->ModificationEvents.Add(DeleteEvent); +} + +void +FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetColorRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + InParam->ModificationEvents.Add(DeleteEvent); +} + +void +FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, + const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetFloatRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertFloat = InValue; + InsertEvent->InsertInterpolation = InInterp; + + InParam->ModificationEvents.Add(InsertEvent); +} + +void +FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, + const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetColorRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertColor = InColor; + InsertEvent->InsertInterpolation = InInterp; + + InParam->ModificationEvents.Add(InsertEvent); +} + +void +FHoudiniParameterDetails:: ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters) +{ + if (FloatRampParameters.Num() < 1) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0]; + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) + { + UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx]; + + if (!NextFloatRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); + } +} + +void +FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters) +{ + if (FloatRampParameters.Num() < 1) + return; + + if (!FloatRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + + for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) + { + if (!FloatRampParameters[Idx].IsValid()) + continue; + + UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx].Get(); + + if (!NextFloatRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); + } + +} + +void +FHoudiniParameterDetails:: ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam) +{ + if (!Param || !MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) + return; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Use Synced points if the MainParam is on auto update mode + // Use Cached points if the Mainparam is on manual update mode + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + if (Param->IsAutoUpdate() && bCookingEnabled) + { + TArray & Points = Param->Points; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (MainPoint->GetPosition() != Point->GetPosition()) + { + if (Point->PositionParentParm) + { + Point->SetPosition(MainPoint->GetPosition()); + Point->PositionParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetValue() != Point->GetValue()) + { + if (Point->ValueParentParm) + { + Point->SetValue(MainPoint->GetValue()); + Point->ValueParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetInterpolation() != Point->GetInterpolation()) + { + if (Point->InterpolationParentParm) + { + Point->SetInterpolation(MainPoint->GetInterpolation()); + Point->InterpolationParentParm->MarkChanged(true); + } + } + + PointIdx += 1; + } + + int32 PointInsertIdx = PointIdx; + int32 PointDeleteIdx = PointIdx; + + // skip the pending modification events + for (auto & Event : Param->ModificationEvents) + { + if (!Event) + continue; + + if (Event->IsInsertEvent()) + PointInsertIdx += 1; + + if (Event->IsDeleteEvent()) + PointDeleteIdx += 1; + } + + // There are more points in MainPoints array + for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) + { + UHoudiniParameterRampFloatPoint*& NextMainPoint = MainPoints[PointInsertIdx]; + + if (!NextMainPoint) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(Param, + NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); + + Param->MarkChanged(true); + } + + // There are more points in Points array + for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) + { + UHoudiniParameterRampFloatPoint*& NextPoint = Points[PointDeleteIdx]; + + if (!NextPoint) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); + + Param->MarkChanged(true); + } + + } + else + { + TArray &Points = Param->CachedPoints; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (Point->Position != MainPoint->Position) + { + Point->Position = MainPoint->Position; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->PositionParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Value != MainPoint->Value) + { + Point->Value = MainPoint->Value; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->ValueParentParm) + Point->ValueParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Interpolation != MainPoint->Interpolation) + { + Point->Interpolation = MainPoint->Interpolation; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->InterpolationParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + PointIdx += 1; + } + + // There are more points in MainPoints array + for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) + { + UHoudiniParameterRampFloatPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; + + if (!NextMainPoint) + continue; + + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampFloatPoint::StaticClass()); + + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = NextMainPoint->GetPosition(); + NewCachedPoint->Value = NextMainPoint->GetValue(); + NewCachedPoint->Interpolation = NextMainPoint->GetInterpolation(); + + Points.Add(NewCachedPoint); + + Param->bCaching = true; + } + + // there are more points in Points array + for (int32 PointsLeftIdx = PointIdx; PointIdx < Points.Num(); ++PointIdx) + { + Points.Pop(); + Param->bCaching = true; + } + } + +} + + +void +FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters) +{ + if (ColorRampParameters.Num() < 1) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0]; + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) + { + UHoudiniParameterRampColor* NextColorRampParam = ColorRampParameters[Idx]; + + if (!NextColorRampParam) + continue; + + FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParam); + } +} + +void +FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters) +{ + if (ColorRampParameters.Num() < 1) + return; + + if (!ColorRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) + { + if (!ColorRampParameters[Idx].IsValid()) + continue; + + UHoudiniParameterRampColor* NextColorRampParameter = ColorRampParameters[Idx].Get(); + + if (!NextColorRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParameter); + + } + +} + +void +FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParam) +{ + if (!Param || !MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) + return; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Use Synced points if the MainParam is on auto update mode + // Use Cached points if the Mainparam is on manual update mode + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + if (Param->IsAutoUpdate() && bCookingEnabled) + { + TArray & Points = Param->Points; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (MainPoint->GetPosition() != Point->GetPosition()) + { + if (Point->PositionParentParm) + { + Point->SetPosition(MainPoint->GetPosition()); + Point->PositionParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetValue() != Point->GetValue()) + { + if (Point->ValueParentParm) + { + Point->SetValue(MainPoint->GetValue()); + Point->ValueParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetInterpolation() != Point->GetInterpolation()) + { + if (Point->InterpolationParentParm) + { + Point->SetInterpolation(MainPoint->GetInterpolation()); + Point->InterpolationParentParm->MarkChanged(true); + } + } + + PointIdx += 1; + + } + + int32 PointInsertIdx = PointIdx; + int32 PointDeleteIdx = PointIdx; + + // skip the pending modification events + for (auto & Event : Param->ModificationEvents) + { + if (!Event) + continue; + + if (Event->IsInsertEvent()) + PointInsertIdx += 1; + + if (Event->IsDeleteEvent()) + PointDeleteIdx += 1; + } + + // There are more points in MainPoints array + for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) + { + UHoudiniParameterRampColorPoint*& NextMainPoint = MainPoints[PointInsertIdx]; + + if (!NextMainPoint) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(Param, + NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); + + Param->MarkChanged(true); + } + + // There are more points in Points array + for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) + { + UHoudiniParameterRampColorPoint*& NextPoint = Points[PointDeleteIdx]; + + if (!NextPoint) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); + + Param->MarkChanged(true); + } + } + else + { + TArray &Points = Param->CachedPoints; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (Point->Position != MainPoint->Position) + { + Point->Position = MainPoint->Position; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->PositionParentParm) + Point->PositionParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Value != MainPoint->Value) + { + Point->Value = MainPoint->Value; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->ValueParentParm) + Point->ValueParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Interpolation != MainPoint->Interpolation) + { + Point->Interpolation = MainPoint->Interpolation; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->InterpolationParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + PointIdx += 1; + } + + // There are more points in Main Points array. + for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) + { + UHoudiniParameterRampColorPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; + + if (!NextMainPoint) + continue; + + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampColorPoint::StaticClass()); + + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = NextMainPoint->Position; + NewCachedPoint->Value = NextMainPoint->Value; + NewCachedPoint->Interpolation = NextMainPoint->Interpolation; + + Points.Add(NewCachedPoint); + + Param->bCaching = true; + } + + // There are more points in Points array + for (int32 PointsleftIdx = PointIdx; PointIdx < MainPoints.Num(); ++PointsleftIdx) + { + Points.Pop(); + + Param->bCaching = true; + } + } +} + +// Check recussively if a parameter hits the end of a visible tabs +void +FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + // When the paramId is invalid, the directory won't parse. + // So simply return the function + if (InParam->GetParmId() < 0) + return; + + // Do not end the tab if this param is a non empty parent type, leave it to its children + EHoudiniParameterType ParmType = InParam->GetParameterType(); + if ((ParmType == EHoudiniParameterType::FolderList || + ParmType == EHoudiniParameterType::Folder) && InParam->GetTupleSize() > 0) + return; + + if (ParmType == EHoudiniParameterType::MultiParm) + { + UHoudiniParameterMultiParm * InMultiParm = Cast(InParam); + if (!InMultiParm) + return; + + if (InMultiParm->MultiParmInstanceCount * InMultiParm->MultiParmInstanceLength > 0) + return; + } + + int32 ParentParamId = InParam->GetParentParmId(); + UHoudiniParameter* CurParam = InParam; + + while (AllFoldersAndFolderLists.Contains(ParentParamId) || AllMultiParms.Contains(ParentParamId)) + { + // The parent is a multiparm + if (AllMultiParms.Contains(ParentParamId)) + { + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentParamId]; + if (!ParentMultiParm || ParentMultiParm->IsPendingKill()) + return; + + if (ParentMultiParm->MultiParmInstanceCount * ParentMultiParm->MultiParmInstanceLength - 1 == CurParam->GetChildIndex()) + { + ParentParamId = ParentMultiParm->GetParentParmId(); + CurParam = ParentMultiParm; + + continue; + } + else + { + // return directly if the parameter is not the last child param of the multiparm + return; + } + } + // The parent is a folder or folderlist + else + { + UHoudiniParameter* ParentFolderParam = AllFoldersAndFolderLists[ParentParamId]; + CurParam = ParentFolderParam; + + if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) + return; + + // The parent is a folder + if (ParentFolderParam->GetParameterType() == EHoudiniParameterType::Folder) + { + ParentParamId = ParentFolderParam->GetParentParmId(); + + continue; + } + // The parent is a folderlist + else + { + UHoudiniParameterFolderList* ParentFolderList = Cast(ParentFolderParam); + if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) + return; + + if (ParentFolderList->IsTabMenu() && ParentFolderList->IsTabsShown() && ParentFolderList->IsTabParseFinished() && DividerLinePositions.Num() > 0) + { + if (!CurrentTabEndingRow) + CreateTabEndingRow(HouParameterCategory); + + if (CurrentTabEndingRow) + { + CurrentTabEndingRow->EndingDividerLinePositions.Add(DividerLinePositions.Top()); + CurrentTabEndingRow->DividerLinePositions.Pop(); + } + + DividerLinePositions.Pop(); + + ParentParamId = ParentFolderList->GetParentParmId(); + } + else + { + return; + } + + } + + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h index 73cf64d3f..10848fb76 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h @@ -1,491 +1,491 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" - -#include "CoreMinimal.h" - -#include "Widgets/Layout/SUniformGridPanel.h" -#include "SCurveEditor.h" -#include "Editor/CurveEditor/Public/CurveEditorSettings.h" -#include "HoudiniParameterTranslator.h" -#include "Curves/CurveFloat.h" -#include "SColorGradientEditor.h" -#include "Curves/CurveLinearColor.h" - -#include "Widgets/SBoxPanel.h" -#include "Widgets/Input/SButton.h" - -#include "HoudiniParameterDetails.generated.h" - -class UHoudiniAssetComponent; -class UHoudiniParameter; -class UHoudiniParameterFloat; -class UHoudiniParameterInt; -class UHoudiniParameterString; -class UHoudiniParameterColor; -class UHoudiniParameterButton; -class UHoudiniParameterButtonStrip; -class UHoudiniParameterLabel; -class UHoudiniParameterToggle; -class UHoudiniParameterFile; -class UHoudiniParameterChoice; -class UHoudiniParameterFolder; -class UHoudiniParameterFolderList; -class UHoudiniParameterMultiParm; -class UHoudiniParameterRampFloat; -class UHoudiniParameterRampColor; -class UHoudiniParameterOperatorPath; - -class UHoudiniParameterRampColorPoint; -class UHoudiniParameterRampFloatPoint; - -class UHoudiniColorRampCurve; -class UHoudiniFloatRampCurve; - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class SHorizontalBox; -class SHoudiniAssetParameterRampCurveEditor; - -enum class EHoudiniRampInterpolationType : int8; - -class SCustomizedButton : public SButton -{ -public: - bool bChosen; - - bool bIsRadioButton; - -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; - - // Construct the circles for all radio buttons. Initialize at first use - void ConstructRadioButtonCircles() const; - - void DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const; -}; - -class SCustomizedBox : public SHorizontalBox -{ -public: - bool bIsTabFolderListRow; - - bool bIsSeparator; - - TArray DividerLinePositions; - - TArray EndingDividerLinePositions; - - float MarginHeight; - -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; - - // Add indentation to current row, computed by tracing the directory hierarchy, - // return the indentation width of this parameter row. - float AddIndentation(UHoudiniParameter* InParam, TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists); - - void SetHoudiniParameter(TArray& InParams); -}; - -class SHoudiniFloatRampCurveEditor : public SCurveEditor -{ -public: - SLATE_BEGIN_ARGS(SHoudiniFloatRampCurveEditor) - : _ViewMinInput(0.0f) - , _ViewMaxInput(10.0f) - , _ViewMinOutput(0.0f) - , _ViewMaxOutput(1.0f) - , _InputSnap(0.1f) - , _OutputSnap(0.05f) - , _InputSnappingEnabled(false) - , _OutputSnappingEnabled(false) - , _ShowTimeInFrames(false) - , _TimelineLength(5.0f) - , _DesiredSize(FVector2D::ZeroVector) - , _DrawCurve(true) - , _HideUI(true) - , _AllowZoomOutput(true) - , _AlwaysDisplayColorCurves(false) - , _ZoomToFitVertical(true) - , _ZoomToFitHorizontal(true) - , _ShowZoomButtons(true) - , _XAxisName() - , _YAxisName() - , _ShowInputGridNumbers(true) - , _ShowOutputGridNumbers(true) - , _ShowCurveSelector(true) - , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) - { - _Clipping = EWidgetClipping::ClipToBounds; - } - - SLATE_ATTRIBUTE(float, ViewMinInput) - SLATE_ATTRIBUTE(float, ViewMaxInput) - SLATE_ATTRIBUTE(TOptional, DataMinInput) - SLATE_ATTRIBUTE(TOptional, DataMaxInput) - SLATE_ATTRIBUTE(float, ViewMinOutput) - SLATE_ATTRIBUTE(float, ViewMaxOutput) - SLATE_ATTRIBUTE(float, InputSnap) - SLATE_ATTRIBUTE(float, OutputSnap) - SLATE_ATTRIBUTE(bool, InputSnappingEnabled) - SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) - SLATE_ATTRIBUTE(bool, ShowTimeInFrames) - SLATE_ATTRIBUTE(float, TimelineLength) - SLATE_ATTRIBUTE(FVector2D, DesiredSize) - SLATE_ATTRIBUTE(bool, AreCurvesVisible) - SLATE_ARGUMENT(bool, DrawCurve) - SLATE_ARGUMENT(bool, HideUI) - SLATE_ARGUMENT(bool, AllowZoomOutput) - SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) - SLATE_ARGUMENT(bool, ZoomToFitVertical) - SLATE_ARGUMENT(bool, ZoomToFitHorizontal) - SLATE_ARGUMENT(bool, ShowZoomButtons) - SLATE_ARGUMENT(TOptional, XAxisName) - SLATE_ARGUMENT(TOptional, YAxisName) - SLATE_ARGUMENT(bool, ShowInputGridNumbers) - SLATE_ARGUMENT(bool, ShowOutputGridNumbers) - SLATE_ARGUMENT(bool, ShowCurveSelector) - SLATE_ARGUMENT(FLinearColor, GridColor) - SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) - SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) - SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) - SLATE_EVENT(FSimpleDelegate, OnCreateAsset) - SLATE_END_ARGS() - - public: - TWeakObjectPtr HoudiniFloatRampCurve; - - /** Widget construction. **/ - void Construct(const FArguments & InArgs); - - virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; - -}; - - -class SHoudiniColorRampCurveEditor : public SColorGradientEditor -{ - -public: - SLATE_BEGIN_ARGS(SHoudiniColorRampCurveEditor) - : _ViewMinInput(0.0f) - , _ViewMaxInput(10.0f) - , _InputSnap(0.1f) - , _OutputSnap(0.05f) - , _InputSnappingEnabled(false) - , _OutputSnappingEnabled(false) - , _ShowTimeInFrames(false) - , _TimelineLength(5.0f) - , _DesiredSize(FVector2D::ZeroVector) - , _DrawCurve(true) - , _HideUI(true) - , _AllowZoomOutput(true) - , _AlwaysDisplayColorCurves(false) - , _ZoomToFitVertical(true) - , _ZoomToFitHorizontal(true) - , _ShowZoomButtons(true) - , _XAxisName() - , _YAxisName() - , _ShowInputGridNumbers(true) - , _ShowOutputGridNumbers(true) - , _ShowCurveSelector(true) - , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) - { - _Clipping = EWidgetClipping::ClipToBounds; - } - - SLATE_ATTRIBUTE(float, ViewMinInput) - SLATE_ATTRIBUTE(float, ViewMaxInput) - SLATE_ATTRIBUTE(TOptional, DataMinInput) - SLATE_ATTRIBUTE(TOptional, DataMaxInput) - SLATE_ATTRIBUTE(float, InputSnap) - SLATE_ATTRIBUTE(float, OutputSnap) - SLATE_ATTRIBUTE(bool, InputSnappingEnabled) - SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) - SLATE_ATTRIBUTE(bool, ShowTimeInFrames) - SLATE_ATTRIBUTE(float, TimelineLength) - SLATE_ATTRIBUTE(FVector2D, DesiredSize) - SLATE_ATTRIBUTE(bool, AreCurvesVisible) - SLATE_ARGUMENT(bool, DrawCurve) - SLATE_ARGUMENT(bool, HideUI) - SLATE_ARGUMENT(bool, AllowZoomOutput) - SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) - SLATE_ARGUMENT(bool, ZoomToFitVertical) - SLATE_ARGUMENT(bool, ZoomToFitHorizontal) - SLATE_ARGUMENT(bool, ShowZoomButtons) - SLATE_ARGUMENT(TOptional, XAxisName) - SLATE_ARGUMENT(TOptional, YAxisName) - SLATE_ARGUMENT(bool, ShowInputGridNumbers) - SLATE_ARGUMENT(bool, ShowOutputGridNumbers) - SLATE_ARGUMENT(bool, ShowCurveSelector) - SLATE_ARGUMENT(FLinearColor, GridColor) - SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) - SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) - SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) - SLATE_EVENT(FSimpleDelegate, OnCreateAsset) - SLATE_END_ARGS() - - public: - /** Widget construction. **/ - void Construct(const FArguments & InArgs); - - TWeakObjectPtr HoudiniColorRampCurve; - - virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; -}; - -UCLASS() -class UHoudiniFloatRampCurve : public UCurveFloat -{ - GENERATED_BODY() - - public: - - TArray> FloatRampParameters; - - virtual void OnCurveChanged(const TArray& ChangedCurveEditInfos) override; -}; - - -UCLASS() -class UHoudiniColorRampCurve : public UCurveLinearColor -{ - GENERATED_BODY() - - public: - bool bEditing = false; - - TArray> ColorRampParameters; - - virtual void OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) override; - - void OnColorRampCurveChanged(bool bModificationOnly = false); - -}; - - -//class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface -class FHoudiniParameterDetails : public TSharedFromThis -{ - public: - void CreateWidget( - IDetailCategoryBuilder & HouParameterCategory, - TArray &InParams); - - void CreateWidgetInt( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetFloat( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetString( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetColor( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetButton( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetButtonStrip( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetLabel( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetToggle( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFile( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetChoice( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetSeparator( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled); - void CreateWidgetFolderList( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFolder( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetMultiParm( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetOperatorPath( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFloatRamp( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetColorRamp( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - - void CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory); - - - void HandleUnsupportedParmType( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams - ); - - - static FText GetParameterTooltip(UHoudiniParameter* InParam); - - static FString GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize); - - static void SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter); - - //static void SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter); - - // replace the children parameter values of all (multi-selected) float ramp parameters with the main parameter (weak object pointer version) - static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters); - // raw pointer version - static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters); - // helper - static void ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam); - - - // replace the children parameter values of all (multi-selected) color ramp parameters with the main parameter (weak object pointer version) - static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters); - // raw pointer version - static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters); - // helper - static void ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParame); - - - - // Create an insert event for a float ramp parameter - static void CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, - const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); - - // Create an insert event for a color ramp parameter - static void CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, - const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp); - - // Create a delete event for a float ramp parameter - static void CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex); - - // Create a delete event for a color ramp parameter - static void CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex); - - - private: - - template< class T > - static bool CastParameters( - TArray InParams, TArray& OutCastedParams); - - // - // Private helper functions for widget creation - // - - // Creates the default name widget, the parameter will then fill the value after - void CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); - - // Creates the default name widget, with an extra checkbox for disabling the the parameter update - void CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); - - FDetailWidgetRow* CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount = true); // - - void CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray& InParams); // - - void CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InParam, const bool& bIsShown); // - - void CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams); // - - // Create the UI for ramp's curve editor. - FDetailWidgetRow* CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); // - - // Create the UI for ramp's stop points. - void CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray< - UHoudiniParameter*>& InParams); // - - void PruneStack(); - - void RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam); - - public: - // Stores the created ramp curves - // In order to avoid being grabage collected, curves are added to root, thus need to handle GC manually. - // These points are for releasing the memory when the detail class are destroyed - TArray CreatedFloatRampCurves; - TArray CreatedColorRampCurves; - // The curve editors reference the UHoudini*Curves as "CurveOwners" as raw (non UObject) pointers, so we have - // to set their owners to null here before we destroy the Created*RampCuvers - TArray> CreatedColorGradientEditors; - TArray> CreatedFloatCurveEditors; - - private: - // The parameter directory is flattened with BFS inside of DFS. - // When a folderlist is encountered, it goes 'one step' of DFS, otherwise BFS. - // So that use a Stack structure to reconstruct the tree. - TArray> FolderStack; - - // Float Ramp currently being processed - UHoudiniParameterRampFloat* CurrentRampFloat; - - // Color Ramp currently being processed - UHoudiniParameterRampColor* CurrentRampColor; - - TArray CurrentRampParameterList; - - // Cached curve points of float ramp which being processed - TArray CurrentRampFloatPointsArray; - - // Cached curve points of color ramp which being processed - TArray CurrentRampColorPointsArray; - - // Cached color ramp curve which being processed - UHoudiniColorRampCurve* CurrentRampParameterColorCurve; - - // Cached float ramp curve which being processed - UHoudiniFloatRampCurve* CurrentRampParameterFloatCurve; - - FDetailWidgetRow * CurrentRampRow; - - - /* Variables for keeping expansion state after adding multiparm instance*/ - TMap AllMultiParms; - - // Cached the map of parameter id and folders/folder lists - TMap AllFoldersAndFolderLists; - - /* Variables for keeping expansion state after adding multiparm instance*/ - - TMap MultiParmInstanceIndices; - - // Number of remaining folders for current folder list - int32 CurrentFolderListSize = 0; - - // The folder list currently being processed - UHoudiniParameterFolderList* CurrentFolderList; - - // Cached child folders of current tabs - TArray CurrentTabs; - - TArray DividerLinePositions; - - SCustomizedBox* CurrentTabEndingRow; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" + +#include "CoreMinimal.h" + +#include "Widgets/Layout/SUniformGridPanel.h" +#include "SCurveEditor.h" +#include "Editor/CurveEditor/Public/CurveEditorSettings.h" +#include "HoudiniParameterTranslator.h" +#include "Curves/CurveFloat.h" +#include "SColorGradientEditor.h" +#include "Curves/CurveLinearColor.h" + +#include "Widgets/SBoxPanel.h" +#include "Widgets/Input/SButton.h" + +#include "HoudiniParameterDetails.generated.h" + +class UHoudiniAssetComponent; +class UHoudiniParameter; +class UHoudiniParameterFloat; +class UHoudiniParameterInt; +class UHoudiniParameterString; +class UHoudiniParameterColor; +class UHoudiniParameterButton; +class UHoudiniParameterButtonStrip; +class UHoudiniParameterLabel; +class UHoudiniParameterToggle; +class UHoudiniParameterFile; +class UHoudiniParameterChoice; +class UHoudiniParameterFolder; +class UHoudiniParameterFolderList; +class UHoudiniParameterMultiParm; +class UHoudiniParameterRampFloat; +class UHoudiniParameterRampColor; +class UHoudiniParameterOperatorPath; + +class UHoudiniParameterRampColorPoint; +class UHoudiniParameterRampFloatPoint; + +class UHoudiniColorRampCurve; +class UHoudiniFloatRampCurve; + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class SHorizontalBox; +class SHoudiniAssetParameterRampCurveEditor; + +enum class EHoudiniRampInterpolationType : int8; + +class SCustomizedButton : public SButton +{ +public: + bool bChosen; + + bool bIsRadioButton; + +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + // Construct the circles for all radio buttons. Initialize at first use + void ConstructRadioButtonCircles() const; + + void DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const; +}; + +class SCustomizedBox : public SHorizontalBox +{ +public: + bool bIsTabFolderListRow; + + bool bIsSeparator; + + TArray DividerLinePositions; + + TArray EndingDividerLinePositions; + + float MarginHeight; + +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + // Add indentation to current row, computed by tracing the directory hierarchy, + // return the indentation width of this parameter row. + float AddIndentation(UHoudiniParameter* InParam, TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists); + + void SetHoudiniParameter(TArray& InParams); +}; + +class SHoudiniFloatRampCurveEditor : public SCurveEditor +{ +public: + SLATE_BEGIN_ARGS(SHoudiniFloatRampCurveEditor) + : _ViewMinInput(0.0f) + , _ViewMaxInput(10.0f) + , _ViewMinOutput(0.0f) + , _ViewMaxOutput(1.0f) + , _InputSnap(0.1f) + , _OutputSnap(0.05f) + , _InputSnappingEnabled(false) + , _OutputSnappingEnabled(false) + , _ShowTimeInFrames(false) + , _TimelineLength(5.0f) + , _DesiredSize(FVector2D::ZeroVector) + , _DrawCurve(true) + , _HideUI(true) + , _AllowZoomOutput(true) + , _AlwaysDisplayColorCurves(false) + , _ZoomToFitVertical(true) + , _ZoomToFitHorizontal(true) + , _ShowZoomButtons(true) + , _XAxisName() + , _YAxisName() + , _ShowInputGridNumbers(true) + , _ShowOutputGridNumbers(true) + , _ShowCurveSelector(true) + , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + + SLATE_ATTRIBUTE(float, ViewMinInput) + SLATE_ATTRIBUTE(float, ViewMaxInput) + SLATE_ATTRIBUTE(TOptional, DataMinInput) + SLATE_ATTRIBUTE(TOptional, DataMaxInput) + SLATE_ATTRIBUTE(float, ViewMinOutput) + SLATE_ATTRIBUTE(float, ViewMaxOutput) + SLATE_ATTRIBUTE(float, InputSnap) + SLATE_ATTRIBUTE(float, OutputSnap) + SLATE_ATTRIBUTE(bool, InputSnappingEnabled) + SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) + SLATE_ATTRIBUTE(bool, ShowTimeInFrames) + SLATE_ATTRIBUTE(float, TimelineLength) + SLATE_ATTRIBUTE(FVector2D, DesiredSize) + SLATE_ATTRIBUTE(bool, AreCurvesVisible) + SLATE_ARGUMENT(bool, DrawCurve) + SLATE_ARGUMENT(bool, HideUI) + SLATE_ARGUMENT(bool, AllowZoomOutput) + SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) + SLATE_ARGUMENT(bool, ZoomToFitVertical) + SLATE_ARGUMENT(bool, ZoomToFitHorizontal) + SLATE_ARGUMENT(bool, ShowZoomButtons) + SLATE_ARGUMENT(TOptional, XAxisName) + SLATE_ARGUMENT(TOptional, YAxisName) + SLATE_ARGUMENT(bool, ShowInputGridNumbers) + SLATE_ARGUMENT(bool, ShowOutputGridNumbers) + SLATE_ARGUMENT(bool, ShowCurveSelector) + SLATE_ARGUMENT(FLinearColor, GridColor) + SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) + SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) + SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) + SLATE_EVENT(FSimpleDelegate, OnCreateAsset) + SLATE_END_ARGS() + + public: + TWeakObjectPtr HoudiniFloatRampCurve; + + /** Widget construction. **/ + void Construct(const FArguments & InArgs); + + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + +}; + + +class SHoudiniColorRampCurveEditor : public SColorGradientEditor +{ + +public: + SLATE_BEGIN_ARGS(SHoudiniColorRampCurveEditor) + : _ViewMinInput(0.0f) + , _ViewMaxInput(10.0f) + , _InputSnap(0.1f) + , _OutputSnap(0.05f) + , _InputSnappingEnabled(false) + , _OutputSnappingEnabled(false) + , _ShowTimeInFrames(false) + , _TimelineLength(5.0f) + , _DesiredSize(FVector2D::ZeroVector) + , _DrawCurve(true) + , _HideUI(true) + , _AllowZoomOutput(true) + , _AlwaysDisplayColorCurves(false) + , _ZoomToFitVertical(true) + , _ZoomToFitHorizontal(true) + , _ShowZoomButtons(true) + , _XAxisName() + , _YAxisName() + , _ShowInputGridNumbers(true) + , _ShowOutputGridNumbers(true) + , _ShowCurveSelector(true) + , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + + SLATE_ATTRIBUTE(float, ViewMinInput) + SLATE_ATTRIBUTE(float, ViewMaxInput) + SLATE_ATTRIBUTE(TOptional, DataMinInput) + SLATE_ATTRIBUTE(TOptional, DataMaxInput) + SLATE_ATTRIBUTE(float, InputSnap) + SLATE_ATTRIBUTE(float, OutputSnap) + SLATE_ATTRIBUTE(bool, InputSnappingEnabled) + SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) + SLATE_ATTRIBUTE(bool, ShowTimeInFrames) + SLATE_ATTRIBUTE(float, TimelineLength) + SLATE_ATTRIBUTE(FVector2D, DesiredSize) + SLATE_ATTRIBUTE(bool, AreCurvesVisible) + SLATE_ARGUMENT(bool, DrawCurve) + SLATE_ARGUMENT(bool, HideUI) + SLATE_ARGUMENT(bool, AllowZoomOutput) + SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) + SLATE_ARGUMENT(bool, ZoomToFitVertical) + SLATE_ARGUMENT(bool, ZoomToFitHorizontal) + SLATE_ARGUMENT(bool, ShowZoomButtons) + SLATE_ARGUMENT(TOptional, XAxisName) + SLATE_ARGUMENT(TOptional, YAxisName) + SLATE_ARGUMENT(bool, ShowInputGridNumbers) + SLATE_ARGUMENT(bool, ShowOutputGridNumbers) + SLATE_ARGUMENT(bool, ShowCurveSelector) + SLATE_ARGUMENT(FLinearColor, GridColor) + SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) + SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) + SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) + SLATE_EVENT(FSimpleDelegate, OnCreateAsset) + SLATE_END_ARGS() + + public: + /** Widget construction. **/ + void Construct(const FArguments & InArgs); + + TWeakObjectPtr HoudiniColorRampCurve; + + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; +}; + +UCLASS() +class UHoudiniFloatRampCurve : public UCurveFloat +{ + GENERATED_BODY() + + public: + + TArray> FloatRampParameters; + + virtual void OnCurveChanged(const TArray& ChangedCurveEditInfos) override; +}; + + +UCLASS() +class UHoudiniColorRampCurve : public UCurveLinearColor +{ + GENERATED_BODY() + + public: + bool bEditing = false; + + TArray> ColorRampParameters; + + virtual void OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) override; + + void OnColorRampCurveChanged(bool bModificationOnly = false); + +}; + + +//class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface +class FHoudiniParameterDetails : public TSharedFromThis +{ + public: + void CreateWidget( + IDetailCategoryBuilder & HouParameterCategory, + TArray &InParams); + + void CreateWidgetInt( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetFloat( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetString( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetColor( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetButton( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetButtonStrip( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetLabel( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetToggle( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFile( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetChoice( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetSeparator( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled); + void CreateWidgetFolderList( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFolder( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetMultiParm( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetOperatorPath( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFloatRamp( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetColorRamp( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + + void CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory); + + + void HandleUnsupportedParmType( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams + ); + + + static FText GetParameterTooltip(UHoudiniParameter* InParam); + + static FString GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize); + + static void SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter); + + //static void SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter); + + // replace the children parameter values of all (multi-selected) float ramp parameters with the main parameter (weak object pointer version) + static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters); + // raw pointer version + static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters); + // helper + static void ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam); + + + // replace the children parameter values of all (multi-selected) color ramp parameters with the main parameter (weak object pointer version) + static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters); + // raw pointer version + static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters); + // helper + static void ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParame); + + + + // Create an insert event for a float ramp parameter + static void CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, + const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); + + // Create an insert event for a color ramp parameter + static void CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, + const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp); + + // Create a delete event for a float ramp parameter + static void CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex); + + // Create a delete event for a color ramp parameter + static void CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex); + + + private: + + template< class T > + static bool CastParameters( + TArray InParams, TArray& OutCastedParams); + + // + // Private helper functions for widget creation + // + + // Creates the default name widget, the parameter will then fill the value after + void CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); + + // Creates the default name widget, with an extra checkbox for disabling the the parameter update + void CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); + + FDetailWidgetRow* CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount = true); // + + void CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray& InParams); // + + void CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InParam, const bool& bIsShown); // + + void CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams); // + + // Create the UI for ramp's curve editor. + FDetailWidgetRow* CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); // + + // Create the UI for ramp's stop points. + void CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray< + UHoudiniParameter*>& InParams); // + + void PruneStack(); + + void RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam); + + public: + // Stores the created ramp curves + // In order to avoid being grabage collected, curves are added to root, thus need to handle GC manually. + // These points are for releasing the memory when the detail class are destroyed + TArray CreatedFloatRampCurves; + TArray CreatedColorRampCurves; + // The curve editors reference the UHoudini*Curves as "CurveOwners" as raw (non UObject) pointers, so we have + // to set their owners to null here before we destroy the Created*RampCuvers + TArray> CreatedColorGradientEditors; + TArray> CreatedFloatCurveEditors; + + private: + // The parameter directory is flattened with BFS inside of DFS. + // When a folderlist is encountered, it goes 'one step' of DFS, otherwise BFS. + // So that use a Stack structure to reconstruct the tree. + TArray> FolderStack; + + // Float Ramp currently being processed + UHoudiniParameterRampFloat* CurrentRampFloat; + + // Color Ramp currently being processed + UHoudiniParameterRampColor* CurrentRampColor; + + TArray CurrentRampParameterList; + + // Cached curve points of float ramp which being processed + TArray CurrentRampFloatPointsArray; + + // Cached curve points of color ramp which being processed + TArray CurrentRampColorPointsArray; + + // Cached color ramp curve which being processed + UHoudiniColorRampCurve* CurrentRampParameterColorCurve; + + // Cached float ramp curve which being processed + UHoudiniFloatRampCurve* CurrentRampParameterFloatCurve; + + FDetailWidgetRow * CurrentRampRow; + + + /* Variables for keeping expansion state after adding multiparm instance*/ + TMap AllMultiParms; + + // Cached the map of parameter id and folders/folder lists + TMap AllFoldersAndFolderLists; + + /* Variables for keeping expansion state after adding multiparm instance*/ + + TMap MultiParmInstanceIndices; + + // Number of remaining folders for current folder list + int32 CurrentFolderListSize = 0; + + // The folder list currently being processed + UHoudiniParameterFolderList* CurrentFolderList; + + // Cached child folders of current tabs + TArray CurrentTabs; + + TArray DividerLinePositions; + + SCustomizedBox* CurrentTabEndingRow; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp new file mode 100644 index 000000000..ac13a4c17 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp @@ -0,0 +1,261 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniPublicAPI.h" + +#include "HoudiniAsset.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniPublicAPIAssetWrapper.h" +#include "HoudiniPublicAPIInputTypes.h" + +UHoudiniPublicAPI::UHoudiniPublicAPI() +{ +} + +void +UHoudiniPublicAPI::CreateSession_Implementation() +{ + if (!IsSessionValid()) + FHoudiniEngineCommands::CreateSession(); +} + +void +UHoudiniPublicAPI::StopSession_Implementation() +{ + if (IsSessionValid()) + FHoudiniEngineCommands::StopSession(); +} + +void +UHoudiniPublicAPI::RestartSession_Implementation() +{ + if (IsSessionValid()) + FHoudiniEngineCommands::RestartSession(); + else + FHoudiniEngineCommands::CreateSession(); +} + +UHoudiniPublicAPIAssetWrapper* +UHoudiniPublicAPI::InstantiateAsset_Implementation( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject, + ULevel* InSpawnInLevelOverride, + const bool bInEnableAutoCook, + const bool bInEnableAutoBake, + const FString& InBakeDirectoryPath, + const EHoudiniEngineBakeOption InBakeMethod, + const bool bInRemoveOutputAfterBake, + const bool bInRecenterBakedActors, + const bool bInReplacePreviousBake) +{ + if (!IsValid(InHoudiniAsset) || !(InHoudiniAsset->AssetImportData)) + { + SetErrorMessage(TEXT("InHoudiniAsset is invalid or does not have AssetImportData.")); + return nullptr; + } + + // Create wrapper for asset instance + UHoudiniPublicAPIAssetWrapper* Wrapper = UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(this); + + if (Wrapper) + { + // Enable/disable error logging based on the API setting + Wrapper->SetLoggingErrorsEnabled(IsLoggingErrors()); + + if (!InstantiateAssetWithExistingWrapper( + Wrapper, + InHoudiniAsset, + InInstantiateAt, + InWorldContextObject, + InSpawnInLevelOverride, + bInEnableAutoCook, + bInEnableAutoBake, + InBakeDirectoryPath, + InBakeMethod, + bInRemoveOutputAfterBake, + bInRecenterBakedActors, + bInReplacePreviousBake)) + { + // failed to instantiate asset, return null + return nullptr; + } + } + + return Wrapper; +} + +bool +UHoudiniPublicAPI::InstantiateAssetWithExistingWrapper_Implementation( + UHoudiniPublicAPIAssetWrapper* InWrapper, + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject, + ULevel* InSpawnInLevelOverride, + const bool bInEnableAutoCook, + const bool bInEnableAutoBake, + const FString& InBakeDirectoryPath, + const EHoudiniEngineBakeOption InBakeMethod, + const bool bInRemoveOutputAfterBake, + const bool bInRecenterBakedActors, + const bool bInReplacePreviousBake) +{ + if (!IsValid(InWrapper)) + { + SetErrorMessage(TEXT("InWrapper is not valid.")); + return false; + } + + if (!IsValid(InHoudiniAsset) || !(InHoudiniAsset->AssetImportData)) + { + SetErrorMessage(TEXT("InHoudiniAsset is invalid or does not have AssetImportData.")); + return false; + } + + UWorld* OverrideWorldToSpawnIn = IsValid(InWorldContextObject) ? InWorldContextObject->GetWorld() : nullptr; + AActor* HoudiniAssetActor = FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(InHoudiniAsset, InInstantiateAt, OverrideWorldToSpawnIn, InSpawnInLevelOverride); + if (!IsValid(HoudiniAssetActor)) + { + // Determine the path of what would have been the owning level/world of the actor for error logging purposes + const FString LevelPath = IsValid(InSpawnInLevelOverride) ? InSpawnInLevelOverride->GetPathName() : FString(); + const FString WorldPath = IsValid(OverrideWorldToSpawnIn) ? OverrideWorldToSpawnIn->GetPathName() : FString(); + const FString OwnerPath = LevelPath.IsEmpty() ? WorldPath : LevelPath; + SetErrorMessage(FString::Printf(TEXT("Failed to spawn a AHoudiniAssetActor in %s."), *OwnerPath)); + return false; + } + + // Wrap the instantiated asset + if (!InWrapper->WrapHoudiniAssetObject(HoudiniAssetActor)) + { + FString WrapperError; + InWrapper->GetLastErrorMessage(WrapperError); + SetErrorMessage(FString::Printf( + TEXT("Failed to wrap '%s': %s."), *(HoudiniAssetActor->GetName()), *WrapperError)); + return false; + } + + InWrapper->SetAutoCookingEnabled(bInEnableAutoCook); + + FDirectoryPath BakeDirectoryPath; + BakeDirectoryPath.Path = InBakeDirectoryPath; + InWrapper->SetBakeFolder(BakeDirectoryPath); + InWrapper->SetBakeMethod(InBakeMethod); + InWrapper->SetRemoveOutputAfterBake(bInRemoveOutputAfterBake); + InWrapper->SetRecenterBakedActors(bInRecenterBakedActors); + InWrapper->SetReplacePreviousBake(bInReplacePreviousBake); + InWrapper->SetAutoBakeEnabled(bInEnableAutoBake); + + return true; +} + +void +UHoudiniPublicAPI::PauseAssetCooking_Implementation() +{ + if (!IsAssetCookingPaused()) + FHoudiniEngineCommands::PauseAssetCooking(); +} + +void +UHoudiniPublicAPI::ResumeAssetCooking_Implementation() +{ + if (IsAssetCookingPaused()) + FHoudiniEngineCommands::PauseAssetCooking(); +} + +UHoudiniPublicAPIInput* +UHoudiniPublicAPI::CreateEmptyInput_Implementation(TSubclassOf InInputClass, UHoudiniPublicAPIAssetWrapper* InOuter) +{ + UObject* Outer = InOuter; + if (!IsValid(Outer)) + Outer = this; + UHoudiniPublicAPIInput* const NewInput = NewObject(Outer, InInputClass.Get()); + if (!IsValid(NewInput)) + { + SetErrorMessage(TEXT("Could not create a new valid UHoudiniPublicAPIInput instance.")); + } + else if (Outer) + { + // Enable/disable error logging based on the outer's setting (if it implements the error logging interface) + const bool bShouldLogErrors = Outer->IsA() ? + Cast(Outer)->IsLoggingErrors() : IsLoggingErrors(); + NewInput->SetLoggingErrorsEnabled(bShouldLogErrors); + } + + return NewInput; +} + +EHoudiniPublicAPIRampInterpolationType +UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(const EHoudiniRampInterpolationType InInterpolationType) +{ + switch (InInterpolationType) + { + case EHoudiniRampInterpolationType::InValid: + return EHoudiniPublicAPIRampInterpolationType::InValid; + case EHoudiniRampInterpolationType::BEZIER: + return EHoudiniPublicAPIRampInterpolationType::BEZIER; + case EHoudiniRampInterpolationType::LINEAR: + return EHoudiniPublicAPIRampInterpolationType::LINEAR; + case EHoudiniRampInterpolationType::BSPLINE: + return EHoudiniPublicAPIRampInterpolationType::BSPLINE; + case EHoudiniRampInterpolationType::HERMITE: + return EHoudiniPublicAPIRampInterpolationType::HERMITE; + case EHoudiniRampInterpolationType::CONSTANT: + return EHoudiniPublicAPIRampInterpolationType::CONSTANT; + case EHoudiniRampInterpolationType::CATMULL_ROM: + return EHoudiniPublicAPIRampInterpolationType::CATMULL_ROM; + case EHoudiniRampInterpolationType::MONOTONE_CUBIC: + return EHoudiniPublicAPIRampInterpolationType::MONOTONE_CUBIC; + } + + return EHoudiniPublicAPIRampInterpolationType::InValid; +} + +EHoudiniRampInterpolationType +UHoudiniPublicAPI::ToHoudiniRampInterpolationType(const EHoudiniPublicAPIRampInterpolationType InInterpolationType) +{ + switch (InInterpolationType) + { + case EHoudiniPublicAPIRampInterpolationType::InValid: + return EHoudiniRampInterpolationType::InValid; + case EHoudiniPublicAPIRampInterpolationType::BEZIER: + return EHoudiniRampInterpolationType::BEZIER; + case EHoudiniPublicAPIRampInterpolationType::LINEAR: + return EHoudiniRampInterpolationType::LINEAR; + case EHoudiniPublicAPIRampInterpolationType::BSPLINE: + return EHoudiniRampInterpolationType::BSPLINE; + case EHoudiniPublicAPIRampInterpolationType::HERMITE: + return EHoudiniRampInterpolationType::HERMITE; + case EHoudiniPublicAPIRampInterpolationType::CONSTANT: + return EHoudiniRampInterpolationType::CONSTANT; + case EHoudiniPublicAPIRampInterpolationType::CATMULL_ROM: + return EHoudiniRampInterpolationType::CATMULL_ROM; + case EHoudiniPublicAPIRampInterpolationType::MONOTONE_CUBIC: + return EHoudiniRampInterpolationType::MONOTONE_CUBIC; + } + + return EHoudiniRampInterpolationType::InValid; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp new file mode 100644 index 000000000..6c33e2599 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp @@ -0,0 +1,4145 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniPublicAPIAssetWrapper.h" + +#include "HoudiniAssetActor.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniOutputDetails.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniPDGManager.h" +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPIInputTypes.h" + + +FHoudiniPublicAPIRampPoint::FHoudiniPublicAPIRampPoint() + : Position(0) + , Interpolation(EHoudiniPublicAPIRampInterpolationType::LINEAR) +{ +} + +FHoudiniPublicAPIRampPoint::FHoudiniPublicAPIRampPoint( + const float InPosition, + const EHoudiniPublicAPIRampInterpolationType InInterpolation) + : Position(InPosition) + , Interpolation(InInterpolation) +{ + +} + +FHoudiniPublicAPIFloatRampPoint::FHoudiniPublicAPIFloatRampPoint() + : Value(0) +{ +} + +FHoudiniPublicAPIFloatRampPoint::FHoudiniPublicAPIFloatRampPoint( + const float InPosition, + const float InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation) + : FHoudiniPublicAPIRampPoint(InPosition, InInterpolation) + , Value(InValue) +{ + +} + +FHoudiniPublicAPIColorRampPoint::FHoudiniPublicAPIColorRampPoint() + : Value(FLinearColor::Black) +{ +} + +FHoudiniPublicAPIColorRampPoint::FHoudiniPublicAPIColorRampPoint( + const float InPosition, + const FLinearColor& InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation) + : FHoudiniPublicAPIRampPoint(InPosition, InInterpolation) + , Value(InValue) +{ + +} + +FHoudiniParameterTuple::FHoudiniParameterTuple() + : BoolValues() + , Int32Values() + , FloatValues() + , StringValues() +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const bool& InValue) + : FHoudiniParameterTuple() +{ + BoolValues.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : BoolValues(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const int32& InValue) + : FHoudiniParameterTuple() +{ + Int32Values.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : Int32Values(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const float& InValue) + : FHoudiniParameterTuple() +{ + FloatValues.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : FloatValues(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const FString& InValue) + : FHoudiniParameterTuple() +{ + StringValues.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : StringValues(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InRampPoints) + : FloatRampPoints(InRampPoints) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InRampPoints) + : ColorRampPoints(InRampPoints) +{ +} + +// +// UHoudiniPublicAPIAssetWrapper +// + + +UHoudiniPublicAPIAssetWrapper::UHoudiniPublicAPIAssetWrapper() + : HoudiniAssetObject(nullptr) + , bAssetLinkSetupAttemptComplete(false) +{ + +} + +UHoudiniPublicAPIAssetWrapper* +UHoudiniPublicAPIAssetWrapper::CreateWrapper(UObject* InOuter, UObject* InHoudiniAssetActorOrComponent) +{ + if (!IsValid(InHoudiniAssetActorOrComponent)) + return nullptr; + + // Check if InHoudiniAssetActorOrComponent is supported + if (!CanWrapHoudiniObject(InHoudiniAssetActorOrComponent)) + return nullptr; + + UHoudiniPublicAPIAssetWrapper* NewWrapper = CreateEmptyWrapper(InOuter); + if (!IsValid(NewWrapper)) + return nullptr; + + // If we cannot wrap the specified actor, return nullptr. + if (!NewWrapper->WrapHoudiniAssetObject(InHoudiniAssetActorOrComponent)) + { + NewWrapper->MarkPendingKill(); + return nullptr; + } + + return NewWrapper; +} + +UHoudiniPublicAPIAssetWrapper* +UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(UObject* InOuter) +{ + UObject* const Outer = InOuter ? InOuter : GetTransientPackage(); + UClass* const Class = StaticClass(); + UHoudiniPublicAPIAssetWrapper* NewWrapper = NewObject( + Outer, Class, + MakeUniqueObjectName(Outer, Class)); + if (!IsValid(NewWrapper)) + return nullptr; + + return NewWrapper; +} + +bool +UHoudiniPublicAPIAssetWrapper::CanWrapHoudiniObject(UObject* InObject) +{ + if (!IsValid(InObject)) + return false; + + return InObject->IsA() || InObject->IsA(); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetTemporaryCookFolder_Implementation(FDirectoryPath& OutDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + OutDirectoryPath = HAC->TemporaryCookFolder; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetTemporaryCookFolder_Implementation(const FDirectoryPath& InDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + if (HAC->TemporaryCookFolder.Path != InDirectoryPath.Path) + HAC->TemporaryCookFolder = InDirectoryPath; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetBakeFolder_Implementation(FDirectoryPath& OutDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + OutDirectoryPath = HAC->BakeFolder; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetBakeFolder_Implementation(const FDirectoryPath& InDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + if (HAC->BakeFolder.Path != InDirectoryPath.Path) + HAC->BakeFolder = InDirectoryPath; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::BakeAllOutputs_Implementation() +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + HAC, + HAC->bReplacePreviousBake, + HAC->HoudiniEngineBakeOption, + HAC->bRemoveOutputAfterBake, + HAC->bRecenterBakedActors); +} + +bool +UHoudiniPublicAPIAssetWrapper::BakeAllOutputsWithSettings_Implementation( + EHoudiniEngineBakeOption InBakeOption, + bool bInReplacePreviousBake, + bool bInRemoveTempOutputsOnSuccess, + bool bInRecenterBakedActors) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(HAC, bInReplacePreviousBake, InBakeOption, bInRemoveTempOutputsOnSuccess, bInRecenterBakedActors); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetAutoBakeEnabled_Implementation(const bool bInAutoBakeEnabled) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->SetBakeAfterNextCookEnabled(bInAutoBakeEnabled); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::IsAutoBakeEnabled_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->IsBakeAfterNextCookEnabled(); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetBakeMethod_Implementation(const EHoudiniEngineBakeOption InBakeMethod) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->HoudiniEngineBakeOption = InBakeMethod; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetBakeMethod_Implementation(EHoudiniEngineBakeOption& OutBakeMethod) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + OutBakeMethod = HAC->HoudiniEngineBakeOption; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetRemoveOutputAfterBake_Implementation(const bool bInRemoveOutputAfterBake) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->bRemoveOutputAfterBake = bInRemoveOutputAfterBake; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRemoveOutputAfterBake_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->bRemoveOutputAfterBake; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetRecenterBakedActors_Implementation(const bool bInRecenterBakedActors) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->bRecenterBakedActors = bInRecenterBakedActors; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRecenterBakedActors_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->bRecenterBakedActors; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetReplacePreviousBake_Implementation(const bool bInReplacePreviousBake) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->bReplacePreviousBake = bInReplacePreviousBake; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetReplacePreviousBake_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->bReplacePreviousBake; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidHoudiniAssetActorWithError(AHoudiniAssetActor*& OutActor) const +{ + AHoudiniAssetActor* const Actor = GetHoudiniAssetActor(); + if (!IsValid(Actor)) + { + SetErrorMessage( + TEXT("Could not find a valid AHoudiniAssetActor for the wrapped asset, or no asset has been wrapped.")); + return false; + } + + OutActor = Actor; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidHoudiniAssetComponentWithError(UHoudiniAssetComponent*& OutHAC) const +{ + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (!IsValid(HAC)) + { + SetErrorMessage( + TEXT("Could not find a valid HoudiniAssetComponent for the wrapped asset, or no asset has been wrapped.")); + return false; + } + + OutHAC = HAC; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidOutputAtWithError(const int32 InOutputIndex, UHoudiniOutput*& OutOutput) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + // Check if InOutputIndex is a valid/in-range index + const int32 NumOutputs = HAC->GetNumOutputs(); + if (InOutputIndex < 0 || InOutputIndex >= NumOutputs) + { + SetErrorMessage(FString::Printf( + TEXT("Output index %d is out of range [0, %d]"), InOutputIndex, NumOutputs)); + return false; + } + + UHoudiniOutput* const Output= HAC->GetOutputAt(InOutputIndex); + if (!IsValid(Output)) + { + SetErrorMessage(FString::Printf(TEXT("Output at index %d is invalid."), InOutputIndex)); + return false; + } + + OutOutput = Output; + return true; +} + + +UHoudiniPDGAssetLink* +UHoudiniPublicAPIAssetWrapper::GetHoudiniPDGAssetLink() const +{ + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (!IsValid(HAC)) + return nullptr; + + return HAC->GetPDGAssetLink(); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidHoudiniPDGAssetLinkWithError(UHoudiniPDGAssetLink*& OutAssetLink) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniPDGAssetLink* const AssetLink = HAC->GetPDGAssetLink(); + if (!IsValid(AssetLink)) + { + SetErrorMessage( + TEXT("Could not find a valid HoudiniPDGAssetLink for the wrapped asset. Does it contain a TOP network?")); + return false; + } + + OutAssetLink = AssetLink; + return true; +} + +void +UHoudiniPublicAPIAssetWrapper::ClearHoudiniAssetObject_Implementation() +{ + UHoudiniPDGAssetLink* const AssetLink = GetHoudiniPDGAssetLink(); + if (IsValid(AssetLink)) + { + if (OnPDGPostTOPNetworkCookDelegateHandle.IsValid()) + AssetLink->GetOnPostTOPNetworkCookDelegate().Remove(OnPDGPostTOPNetworkCookDelegateHandle); + if (OnPDGPostBakeDelegateHandle.IsValid()) + AssetLink->GetOnPostBakeDelegate().Remove(OnPDGPostBakeDelegateHandle); + } + + bAssetLinkSetupAttemptComplete = false; + + FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefinedDelegate().Remove(OnHoudiniProxyMeshesRefinedDelegateHandle); + + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (IsValid(HAC)) + { + if (OnAssetStateChangeDelegateHandle.IsValid()) + HAC->GetOnAssetStateChangeDelegate().Remove(OnAssetStateChangeDelegateHandle); + if (OnPostCookDelegateHandle.IsValid()) + HAC->GetOnPostCookDelegate().Remove(OnPostCookDelegateHandle); + if (OnPostBakeDelegateHandle.IsValid()) + HAC->GetOnPostBakeDelegate().Remove(OnPostBakeDelegateHandle); + } + + OnPDGPostTOPNetworkCookDelegateHandle.Reset(); + OnPDGPostBakeDelegateHandle.Reset(); + OnAssetStateChangeDelegateHandle.Reset(); + OnPostCookDelegateHandle.Reset(); + OnPostBakeDelegateHandle.Reset(); + OnHoudiniProxyMeshesRefinedDelegateHandle.Reset(); + + HoudiniAssetObject = nullptr; + CachedHoudiniAssetActor = nullptr; + CachedHoudiniAssetComponent = nullptr; +} + +bool +UHoudiniPublicAPIAssetWrapper::WrapHoudiniAssetObject_Implementation(UObject* InHoudiniAssetObjectToWrap) +{ + // If InHoudiniAssetObjectToWrap is null, just unwrap any currently wrapped asset + if (!InHoudiniAssetObjectToWrap) + { + ClearHoudiniAssetObject(); + return true; + } + + // Check if InHoudiniAssetObjectToWrap is supported + if (!CanWrapHoudiniObject(InHoudiniAssetObjectToWrap)) + { + UClass* const ObjectClass = IsValid(InHoudiniAssetObjectToWrap) ? InHoudiniAssetObjectToWrap->GetClass() : nullptr; + SetErrorMessage(FString::Printf( + TEXT("Cannot wrap objects of class '%s'."), ObjectClass ? *(ObjectClass->GetName()) : TEXT("Unknown"))); + return false; + } + + // First unwrap/unbind if we are currently wrapping an instantiated asset + ClearHoudiniAssetObject(); + + HoudiniAssetObject = InHoudiniAssetObjectToWrap; + + // Cache the HoudiniAssetActor and HoudiniAssetComponent + if (HoudiniAssetObject->IsA()) + { + CachedHoudiniAssetActor = Cast(InHoudiniAssetObjectToWrap); + CachedHoudiniAssetComponent = CachedHoudiniAssetActor->HoudiniAssetComponent; + } + else if (HoudiniAssetObject->IsA()) + { + CachedHoudiniAssetComponent = Cast(InHoudiniAssetObjectToWrap); + CachedHoudiniAssetActor = Cast(CachedHoudiniAssetComponent->GetOwner()); + } + + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (IsValid(HAC)) + { + // Bind to HandleOnHoudiniAssetStateChange from the HAC: we also implement IHoudiniAssetStateEvents, and + // in the default implementation HandleOnHoudiniAssetStateChange will call the appropriate Handle functions + // for PostInstantiate, PostCook etc + OnAssetStateChangeDelegateHandle = HAC->GetOnAssetStateChangeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentStateChange")); + OnPostCookDelegateHandle = HAC->GetOnPostCookDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentPostCook")); + OnPostBakeDelegateHandle = HAC->GetOnPostBakeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentPostBake")); + } + + OnHoudiniProxyMeshesRefinedDelegateHandle = FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefinedDelegate().AddUFunction(this, TEXT("HandleOnHoudiniProxyMeshesRefinedGlobal")); + + // PDG asset link bindings: We attempt to bind to PDG here, but it likely is not available yet. + // We have to wait until post instantiation in order to know if there is a PDG asset link + // for this HDA. This is checked again in HandleOnHoudiniAssetComponentStateChange and sets + // bAssetLinkSetupAttemptComplete. + BindToPDGAssetLink(); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::DeleteInstantiatedAsset_Implementation() +{ + AHoudiniAssetActor* AssetActor = nullptr; + if (!GetValidHoudiniAssetActorWithError(AssetActor)) + return false; + + // Unbind / unwrap the HDA actor + ClearHoudiniAssetObject(); + AssetActor->Destroy(); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::Rebuild_Implementation() +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->MarkAsNeedRebuild(); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::Recook_Implementation() +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->MarkAsNeedCook(); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetAutoCookingEnabled_Implementation(const bool bInSetEnabled) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + if (HAC->IsCookingEnabled() == bInSetEnabled) + return false; + + HAC->SetCookingEnabled(bInSetEnabled); + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::IsAutoCookingEnabled_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->IsCookingEnabled(); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetFloatParameterValue_Implementation(FName InParameterTupleName, float InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParam = Cast(Param); + if (!IsValid(FloatParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FloatParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FloatParam->GetNumberOfValues())); + return false; + } + + bDidChangeValue = FloatParam->SetValueAt(InValue, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + static const int32 NumColorChannels = 4; + if (InAtIndex >= NumColorChannels) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, NumColorChannels)); + return false; + } + + const float CurrentValue = ColorParam->GetColorValue().Component(InAtIndex); + if (CurrentValue != InValue) + { + ColorParam->GetColorValue().Component(InAtIndex) = InValue; + bDidChangeValue = true; + } + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return bDidChangeValue; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetFloatParameterValue_Implementation(FName InParameterTupleName, float& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParam = Cast(Param); + if (!IsValid(FloatParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FloatParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FloatParam->GetNumberOfValues())); + return false; + } + + return FloatParam->GetValueAt(InAtIndex, OutValue); + } + else if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + static const int32 NumColorChannels = 4; + if (InAtIndex >= NumColorChannels) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, NumColorChannels)); + return false; + } + + OutValue = ColorParam->GetColorValue().Component(InAtIndex); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetColorParameterValue_Implementation(FName InParameterTupleName, const FLinearColor& InValue, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ColorParam->SetColorValue(InValue); + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return bDidChangeValue; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetColorParameterValue_Implementation(FName InParameterTupleName, FLinearColor& OutValue) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ColorParam->GetColorValue(); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetIntParameterValue_Implementation(FName InParameterTupleName, int32 InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Int) + { + UHoudiniParameterInt* IntParam = Cast(Param); + if (!IsValid(IntParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= IntParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, IntParam->GetNumberOfValues())); + return false; + } + + bDidChangeValue = IntParam->SetValueAt(InValue, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ChoiceParam->SetIntValue(InValue); + } + else if (ParamType == EHoudiniParameterType::MultiParm) + { + UHoudiniParameterMultiParm* MultiParam = Cast(Param); + if (!IsValid(MultiParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = MultiParam->SetValue(InValue); + } + else if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ToggleParam->SetValueAt(InValue != 0, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + const bool NewValue = InValue != 0; + if (FolderParam->IsChosen() != NewValue) + { + FolderParam->SetChosen(NewValue); + bDidChangeValue = true; + } + } + else if (ParamType == EHoudiniParameterType::FloatRamp || ParamType == EHoudiniParameterType::ColorRamp) + { + // For ramps we have to use the appropriate function so that delete/insert operations are managed correctly + bDidChangeValue = SetRampParameterNumPoints(InParameterTupleName, InValue); + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return bDidChangeValue; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetIntParameterValue_Implementation(FName InParameterTupleName, int32& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Int) + { + UHoudiniParameterInt* IntParam = Cast(Param); + if (!IsValid(IntParam)) + return false; + + if (InAtIndex >= IntParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, IntParam->GetNumberOfValues())); + return false; + } + + return IntParam->GetValueAt(InAtIndex, OutValue); + } + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ChoiceParam->GetIntValue(); + return true; + } + else if (ParamType == EHoudiniParameterType::MultiParm) + { + UHoudiniParameterMultiParm* MultiParam = Cast(Param); + if (!IsValid(MultiParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = MultiParam->GetValue(); + return true; + } + else if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ToggleParam->GetValueAt(InAtIndex); + return true; + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = FolderParam->IsChosen(); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetBoolParameterValue_Implementation(FName InParameterTupleName, bool InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ToggleParam->SetValueAt(InValue, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (FolderParam->IsChosen() != InValue) + { + FolderParam->SetChosen(InValue); + bDidChangeValue = true; + } + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return bDidChangeValue; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetBoolParameterValue_Implementation(FName InParameterTupleName, bool& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ToggleParam->GetValueAt(InAtIndex); + return true; + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = FolderParam->IsChosen(); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetStringParameterValue_Implementation(FName InParameterTupleName, const FString& InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::String || ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + // We have to handle asset references differently + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + // Find/load the asset, make sure it is a valid reference/object + const FSoftObjectPath AssetRef(InValue); + UObject* const Asset = AssetRef.TryLoad(); + if (IsValid(Asset)) + { + UObject* const CurrentAsset = StringParam->GetAssetAt(InAtIndex); + if (CurrentAsset != Asset) + { + StringParam->SetAssetAt(Asset, InAtIndex); + bDidChangeValue = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Asset reference '%s' is invalid. Not setting parameter value."), *InValue)); + } + } + else + { + bDidChangeValue = StringParam->SetValueAt(InValue, InAtIndex); + } + } + else if (ParamType == EHoudiniParameterType::StringChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ChoiceParam->SetStringValue(InValue); + } + else if (ParamType == EHoudiniParameterType::File || ParamType == EHoudiniParameterType::FileDir || + ParamType == EHoudiniParameterType::FileGeo || ParamType == EHoudiniParameterType::FileImage) + { + UHoudiniParameterFile* FileParam = Cast(Param); + if (!IsValid(FileParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FileParam->GetNumValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FileParam->GetNumValues())); + return false; + } + + bDidChangeValue = FileParam->SetValueAt(InValue, InAtIndex); + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return bDidChangeValue; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetStringParameterValue_Implementation(FName InParameterTupleName, FString& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::String || ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + // For asset references: get the asset, and then get the string reference from it and return that. + // If the asset is null, return the empty string. + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + UObject* const Asset = StringParam->GetAssetAt(InAtIndex); + if (IsValid(Asset)) + { + OutValue = UHoudiniParameterString::GetAssetReference(Asset); + } + else + { + OutValue.Empty(); + } + } + else + { + OutValue = StringParam->GetValueAt(InAtIndex); + } + return true; + } + else if (ParamType == EHoudiniParameterType::StringChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ChoiceParam->GetStringValue(); + return true; + } + else if (ParamType == EHoudiniParameterType::File || ParamType == EHoudiniParameterType::FileDir || + ParamType == EHoudiniParameterType::FileGeo || ParamType == EHoudiniParameterType::FileImage) + { + UHoudiniParameterFile* FileParam = Cast(Param); + if (!IsValid(FileParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FileParam->GetNumValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FileParam->GetNumValues())); + return false; + } + + OutValue = FileParam->GetValueAt(InAtIndex); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetAssetRefParameterValue_Implementation(FName InParameterTupleName, UObject* InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + // Find/load the asset, make sure it is a valid reference/object + UObject* const CurrentAsset = StringParam->GetAssetAt(InAtIndex); + if (CurrentAsset != InValue) + { + StringParam->SetAssetAt(InValue, InAtIndex); + bDidChangeValue = true; + } + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return bDidChangeValue; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetAssetRefParameterValue_Implementation(FName InParameterTupleName, UObject*& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + OutValue = StringParam->GetAssetAt(InAtIndex); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetRampParameterNumPoints_Implementation(FName InParameterTupleName, const int32 InNumPoints) const +{ + if (InNumPoints < 1) + { + SetErrorMessage(TEXT("InNumPoints must be >= 1.")); + return false; + } + + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParameterTupleName.ToString()))); + return false; + } + + if (bUseCachedPoints) + { + // When using the cached points we only have to resize the array + if (FloatRampParam) + { + const int32 CurrentNumPoints = FloatRampParam->CachedPoints.Num(); + if (CurrentNumPoints != InNumPoints) + { + FloatRampParam->CachedPoints.SetNum(InNumPoints); + // FloatRampParam->MarkChanged(true); + } + } + else + { + const int32 CurrentNumPoints = ColorRampParam->CachedPoints.Num(); + if (CurrentNumPoints != InNumPoints) + { + ColorRampParam->CachedPoints.SetNum(InNumPoints); + // ColorRampParam->MarkChanged(true); + } + } + + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + else + { + int32 NumPendingInsertOperations = 0; + int32 NumPendingDeleteOperations = 0; + TSet InstanceIndexesPendingDelete; + TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; + for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) + { + if (!IsValid(Event)) + continue; + + if (Event->IsInsertEvent()) + NumPendingInsertOperations++; + else if (Event->IsDeleteEvent()) + { + InstanceIndexesPendingDelete.Add(Event->DeleteInstanceIndex); + NumPendingDeleteOperations++; + } + } + + const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); + int32 CurrentNumPoints = PointsArraySize + NumPendingInsertOperations - NumPendingDeleteOperations; + + if (InNumPoints < CurrentNumPoints) + { + // When deleting points, first remove pending insert operations from the end + if (NumPendingInsertOperations > 0) + { + const int32 NumEvents = ModificationEvents.Num(); + TArray TempModificationArray; + TempModificationArray.Reserve(NumEvents); + + for (int32 Index = NumEvents - 1; Index >= 0; --Index) + { + UHoudiniParameterRampModificationEvent* const Event = ModificationEvents[Index]; + if (InNumPoints < CurrentNumPoints && IsValid(Event) && Event->IsInsertEvent()) + { + CurrentNumPoints--; + NumPendingInsertOperations--; + continue; + } + + TempModificationArray.Add(Event); + } + + Algo::Reverse(TempModificationArray); + ModificationEvents = MoveTemp(TempModificationArray); + } + + // If we still have points to delete... + if (InNumPoints < CurrentNumPoints) + { + // Deleting points, add delete operations, deleting from the end of Points (points that are not yet + // pending delete) + for (int32 Index = PointsArraySize - 1; (InNumPoints < CurrentNumPoints && Index >= 0); --Index) + { + int32 InstanceIndex = INDEX_NONE; + + if (FloatRampParam) + { + UHoudiniParameterRampFloatPoint const* const PointData = FloatRampParam->Points[Index]; + if (!IsValid(PointData)) + continue; + + InstanceIndex = PointData->InstanceIndex; + } + else + { + UHoudiniParameterRampColorPoint const* const PointData = ColorRampParam->Points[Index]; + if (!IsValid(PointData)) + continue; + + InstanceIndex = PointData->InstanceIndex; + } + + if (!InstanceIndexesPendingDelete.Contains(InstanceIndex)) + { + InstanceIndexesPendingDelete.Add(InstanceIndex); + CurrentNumPoints--; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + FloatRampParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (DeleteEvent) + { + if (FloatRampParam) + { + DeleteEvent->SetFloatRampEvent(); + } + else + { + DeleteEvent->SetColorRampEvent(); + } + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InstanceIndex; + + ModificationEvents.Add(DeleteEvent); + } + } + } + } + + Param->MarkChanged(true); + } + else if (InNumPoints > CurrentNumPoints) + { + // Adding points, add insert operations + while (InNumPoints > CurrentNumPoints) + { + CurrentNumPoints++; + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + Param, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (InsertEvent) + { + if (FloatRampParam) + { + InsertEvent->SetFloatRampEvent(); + } + else + { + InsertEvent->SetColorRampEvent(); + } + InsertEvent->SetInsertEvent(); + // Leave point position, value and interpolation at default + + ModificationEvents.Add(InsertEvent); + } + } + + Param->MarkChanged(true); + } + + // If at this point InNumPoints != CurrentNumPoints then something went wrong, we couldn't delete all the + // desired points + if (InNumPoints != CurrentNumPoints) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected error: could not delete the required number of ramp points " + "(target # points = %d; have # points %d)."), InNumPoints, CurrentNumPoints)); + return false; + } + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRampParameterNumPoints_Implementation(FName InParameterTupleName, int32& OutNumPoints) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParameterTupleName.ToString()))); + return false; + } + + if (bUseCachedPoints) + { + // When using the cached points we only have to resize the array + if (FloatRampParam) + { + OutNumPoints = FloatRampParam->CachedPoints.Num(); + } + else + { + OutNumPoints = ColorRampParam->CachedPoints.Num(); + } + } + else + { + int32 NumPendingInsertOperations = 0; + int32 NumPendingDeleteOperations = 0; + TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; + for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) + { + if (!IsValid(Event)) + continue; + + if (Event->IsInsertEvent()) + NumPendingInsertOperations++; + else if (Event->IsDeleteEvent()) + NumPendingDeleteOperations++; + } + + const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); + OutNumPoints = PointsArraySize + NumPendingInsertOperations - NumPendingDeleteOperations; + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetFloatRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const float InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType, + const bool bInMarkChanged) +{ + return SetRampParameterPointValue( + InParameterTupleName, InPointIndex, InPointPosition, InPointValue, FLinearColor::Black, InInterpolationType, bInMarkChanged); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetFloatRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + float& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const +{ + FLinearColor ColorValue; + return GetRampParameterPointValue( + InParameterTupleName, InPointIndex, OutPointPosition, OutPointValue, ColorValue, OutInterpolationType); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetFloatRampParameterPoints_Implementation( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged) +{ + const int32 TargetNumPoints = InRampPoints.Num(); + if (TargetNumPoints == 0) + { + SetErrorMessage(TEXT("InRampPoints must have at least one entry.")); + return false; + } + + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + return false; + } + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + // Set the ramp point count to match the size of InRampPoints + if (!SetRampParameterNumPoints(InParameterTupleName, TargetNumPoints)) + return false; + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + // Check that we fetched the correct number of point data objects + if (RampPointData.Num() != TargetNumPoints) + { + SetErrorMessage(FString::Printf(TEXT("Failed to set the number of ramp points to %d."), TargetNumPoints)); + return false; + } + + for (int32 Index = 0; Index < TargetNumPoints; ++Index) + { + TPair const& Entry = RampPointData[Index]; + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + const FHoudiniPublicAPIFloatRampPoint& NewRampPoint = InRampPoints[Index]; + const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( + NewRampPoint.Interpolation); + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + if (bUseCachedPoints) + { + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (FloatPointData->Position != NewRampPoint.Position) + { + FloatPointData->Position = NewRampPoint.Position; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Value != NewRampPoint.Value) + { + FloatPointData->Value = NewRampPoint.Value; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Interpolation != NewInterpolation) + { + FloatPointData->Interpolation = NewInterpolation; + FloatRampParam->bCaching = true; + } + } + else + { + // When setting the main points, we set the values using the setters on the point data but still manually + // mark the position/value/interpolation parent parameters as changed + if (FloatPointData->Position != NewRampPoint.Position && FloatPointData->PositionParentParm) + { + FloatPointData->SetPosition(NewRampPoint.Position); + if (bInMarkChanged) + FloatPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Value != NewRampPoint.Value && FloatPointData->ValueParentParm) + { + FloatPointData->SetValue(NewRampPoint.Value); + if (bInMarkChanged) + FloatPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Interpolation != NewInterpolation && FloatPointData->InterpolationParentParm) + { + FloatPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + FloatPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + Event->InsertPosition = NewRampPoint.Position; + Event->InsertFloat = NewRampPoint.Value; + Event->InsertInterpolation = NewInterpolation; + } + } + + if (bUseCachedPoints) + { + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetFloatRampParameterPoints_Implementation( + FName InParameterTupleName, + TArray& OutRampPoints) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp parameter."), *(Param->GetName()))); + return false; + } + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + OutRampPoints.Reserve(RampPointData.Num()); + const bool bAllowShrinking = false; + OutRampPoints.SetNum(0, bAllowShrinking); + for (TPair const& Entry : RampPointData) + { + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + FHoudiniPublicAPIFloatRampPoint TempPointData; + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* const FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = FloatPointData->Position; + TempPointData.Value = FloatPointData->Value; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + FloatPointData->Interpolation); + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = Event->InsertPosition; + TempPointData.Value = Event->InsertFloat; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); + } + + OutRampPoints.Add(TempPointData); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetColorRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const FLinearColor& InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType, + const bool bInMarkChanged) +{ + const float FloatValue = 0; + return SetRampParameterPointValue( + InParameterTupleName, InPointIndex, InPointPosition, FloatValue, InPointValue, InInterpolationType, bInMarkChanged); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetColorRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + FLinearColor& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const +{ + float FloatValue = 0; + return GetRampParameterPointValue( + InParameterTupleName, InPointIndex, OutPointPosition, FloatValue, OutPointValue, OutInterpolationType); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetColorRampParameterPoints_Implementation( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged) +{ + const int32 TargetNumPoints = InRampPoints.Num(); + if (TargetNumPoints == 0) + { + SetErrorMessage(TEXT("InRampPoints must have at least one entry.")); + return false; + } + + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + return false; + } + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + // Set the ramp point count to match the size of InRampPoints + if (!SetRampParameterNumPoints(InParameterTupleName, TargetNumPoints)) + return false; + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + // Check that we fetched the correct number of point data objects + if (RampPointData.Num() != TargetNumPoints) + { + SetErrorMessage(FString::Printf(TEXT("Failed to set the number of ramp points to %d."), TargetNumPoints)); + return false; + } + + for (int32 Index = 0; Index < TargetNumPoints; ++Index) + { + TPair const& Entry = RampPointData[Index]; + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + const FHoudiniPublicAPIColorRampPoint& NewRampPoint = InRampPoints[Index]; + const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( + NewRampPoint.Interpolation); + + if (bIsPointData) + { + UHoudiniParameterRampColorPoint* ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + if (bUseCachedPoints) + { + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (ColorPointData->Position != NewRampPoint.Position) + { + ColorPointData->Position = NewRampPoint.Position; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Value != NewRampPoint.Value) + { + ColorPointData->Value = NewRampPoint.Value; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Interpolation != NewInterpolation) + { + ColorPointData->Interpolation = NewInterpolation; + ColorRampParam->bCaching = true; + } + } + else + { + // When setting the main points, we set the values using the setters on the point data but still manually + // mark the position/value/interpolation parent parameters as changed + if (ColorPointData->Position != NewRampPoint.Position && ColorPointData->PositionParentParm) + { + ColorPointData->SetPosition(NewRampPoint.Position); + if (bInMarkChanged) + ColorPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Value != NewRampPoint.Value && ColorPointData->ValueParentParm) + { + ColorPointData->SetValue(NewRampPoint.Value); + if (bInMarkChanged) + ColorPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Interpolation != NewInterpolation && ColorPointData->InterpolationParentParm) + { + ColorPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + ColorPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + Event->InsertPosition = NewRampPoint.Position; + Event->InsertColor = NewRampPoint.Value; + Event->InsertInterpolation = NewInterpolation; + } + } + + if (bUseCachedPoints) + { + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetColorRampParameterPoints_Implementation( + FName InParameterTupleName, + TArray& OutRampPoints) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp parameter."), *(Param->GetName()))); + return false; + } + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + OutRampPoints.Reserve(RampPointData.Num()); + const bool bAllowShrinking = false; + OutRampPoints.SetNum(0, bAllowShrinking); + for (TPair const& Entry : RampPointData) + { + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + FHoudiniPublicAPIColorRampPoint TempPointData; + + if (bIsPointData) + { + UHoudiniParameterRampColorPoint* const ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = ColorPointData->Position; + TempPointData.Value = ColorPointData->Value; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + ColorPointData->Interpolation); + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = Event->InsertPosition; + TempPointData.Value = Event->InsertColor; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); + } + + OutRampPoints.Add(TempPointData); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::TriggerButtonParameter_Implementation(FName InButtonParameterName) +{ + UHoudiniParameter* Param = FindValidParameterByName(InButtonParameterName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidTrigger = false; + if (ParamType == EHoudiniParameterType::Button) + { + UHoudiniParameterButton* ButtonParam = Cast(Param); + if (!IsValid(ButtonParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + // Marking the button as changed will result in it being triggered/clicked via HAPI + if (!ButtonParam->HasChanged() || !ButtonParam->NeedsToTriggerUpdate()) + { + ButtonParam->MarkChanged(true); + bDidTrigger = true; + } + } + + return bDidTrigger; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetParameterTuples_Implementation(TMap& OutParameterTuples) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + const int32 NumParameters = HAC->GetNumParameters(); + OutParameterTuples.Empty(NumParameters); + OutParameterTuples.Reserve(NumParameters); + for (int32 Index = 0; Index < NumParameters; ++Index) + { + const UHoudiniParameter* const Param = HAC->GetParameterAt(Index); + const EHoudiniParameterType ParameterType = Param->GetParameterType(); + const int32 TupleSize = Param->GetTupleSize(); + const FName PTName(Param->GetParameterName()); + + FHoudiniParameterTuple ParameterTuple; + + bool bSkipped = false; + switch (ParameterType) + { + case EHoudiniParameterType::Color: + case EHoudiniParameterType::Float: + { + // Output as float + ParameterTuple.FloatValues.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetFloatParameterValue(PTName, ParameterTuple.FloatValues[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::Int: + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::MultiParm: + { + // Output as int + ParameterTuple.Int32Values.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetIntParameterValue(PTName, ParameterTuple.Int32Values[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringChoice: + case EHoudiniParameterType::StringAssetRef: + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + // Output as string + ParameterTuple.StringValues.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetStringParameterValue(PTName, ParameterTuple.StringValues[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::Toggle: + { + // Output as bool + ParameterTuple.BoolValues.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetBoolParameterValue(PTName, ParameterTuple.BoolValues[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::ColorRamp: + { + GetColorRampParameterPoints(PTName, ParameterTuple.ColorRampPoints); + break; + } + case EHoudiniParameterType::FloatRamp: + { + GetFloatRampParameterPoints(PTName, ParameterTuple.FloatRampPoints); + break; + } + + case EHoudiniParameterType::Button: + case EHoudiniParameterType::ButtonStrip: + case EHoudiniParameterType::Input: + case EHoudiniParameterType::Invalid: + case EHoudiniParameterType::Folder: + case EHoudiniParameterType::FolderList: + case EHoudiniParameterType::Label: + case EHoudiniParameterType::Separator: + default: + // Skipped + bSkipped = true; + break; + } + + if (!bSkipped) + OutParameterTuples.Add(PTName, ParameterTuple); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetParameterTuples_Implementation(const TMap& InParameterTuples) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + bool bSuccess = true; + for (const TPair& Entry : InParameterTuples) + { + const FName& ParameterTupleName = Entry.Key; + const FHoudiniParameterTuple& ParameterTuple = Entry.Value; + if (ParameterTuple.BoolValues.Num() > 0) + { + // Set as bool + const int32 TupleSize = ParameterTuple.BoolValues.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetBoolParameterValue(ParameterTupleName, ParameterTuple.BoolValues[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a bool at tuple index %d."), *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.FloatValues.Num() > 0) + { + // Set as float + const int32 TupleSize = ParameterTuple.FloatValues.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetFloatParameterValue(ParameterTupleName, ParameterTuple.FloatValues[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a float at tuple index %d."), + *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.Int32Values.Num() > 0) + { + // Set as int + const int32 TupleSize = ParameterTuple.Int32Values.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetIntParameterValue(ParameterTupleName, ParameterTuple.Int32Values[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a int at tuple index %d."), + *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.StringValues.Num() > 0) + { + // Set as string + const int32 TupleSize = ParameterTuple.StringValues.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetStringParameterValue(ParameterTupleName, ParameterTuple.StringValues[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a string at tuple index %d."), + *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.FloatRampPoints.Num() > 0) + { + // Set as a float ramp + if (!SetFloatRampParameterPoints(ParameterTupleName, ParameterTuple.FloatRampPoints)) + bSuccess = false; + } + else if (ParameterTuple.ColorRampPoints.Num() > 0) + { + // Set as a color ramp + if (!SetColorRampParameterPoints(ParameterTupleName, ParameterTuple.ColorRampPoints)) + bSuccess = false; + } + } + + return bSuccess; +} + +UHoudiniPublicAPIInput* +UHoudiniPublicAPIAssetWrapper::CreateEmptyInput_Implementation(TSubclassOf InInputClass) +{ + UHoudiniPublicAPI* API = UHoudiniPublicAPIBlueprintLib::GetAPI(); + if (!IsValid(API)) + return nullptr; + + UHoudiniPublicAPIInput* const NewInput = API->CreateEmptyInput(InInputClass, this); + if (!IsValid(NewInput)) + { + SetErrorMessage(FString::Printf( + TEXT("Failed to create a new input of class '%s'."), + *(IsValid(InInputClass.Get()) ? InInputClass->GetName() : FString()))); + + return nullptr; + } + + return NewInput; +} + +int32 +UHoudiniPublicAPIAssetWrapper::GetNumNodeInputs_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return -1; + + int32 NumNodeInputs = 0; + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + + if (!Input->IsObjectPathParameter()) + NumNodeInputs++; + } + + return NumNodeInputs; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputAtIndex_Implementation(const int32 InNodeInputIndex, const UHoudiniPublicAPIInput* InInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniInput* HoudiniInput = GetHoudiniNodeInputByIndex(InNodeInputIndex); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputAtIndex: Could not find a HoudiniInput for InNodeInputIndex %d. Has the HDA been instantiated?"), + InNodeInputIndex)); + return false; + } + + return PopulateHoudiniInput(InInput, HoudiniInput); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputAtIndex_Implementation(const int32 InNodeInputIndex, UHoudiniPublicAPIInput*& OutInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + const UHoudiniInput* HoudiniInput = GetHoudiniNodeInputByIndex(InNodeInputIndex); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("GetInputAtIndex: Could not find a HoudiniInput for InNodeInputIndex %d. Has the HDA been instantiated?"), + InNodeInputIndex)); + return false; + } + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + return false; + + OutInput = APIInput; + return bSuccessfullyCopied; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputsAtIndices_Implementation(const TMap& InInputs) +{ + bool bAnyFailures = false; + for (const TPair& Entry : InInputs) + { + if (!SetInputAtIndex(Entry.Key, Entry.Value)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputsAtIndices: Failed to set node input at index %d"), Entry.Key)); + bAnyFailures = true; + } + } + + return !bAnyFailures; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputsAtIndices_Implementation(TMap& OutInputs) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + bool bAnyFailures = false; + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const HoudiniInput = HAC->GetInputAt(Index); + if (!IsValid(HoudiniInput) || HoudiniInput->IsObjectPathParameter()) + continue; + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + { + bAnyFailures = true; + continue; + } + if (!bSuccessfullyCopied) + bAnyFailures = true; + + OutInputs.Add(HoudiniInput->GetInputIndex(), APIInput); + } + + return !bAnyFailures; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputParameter_Implementation(const FName& InParameterName, const UHoudiniPublicAPIInput* InInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniInput* HoudiniInput = FindValidHoudiniNodeInputParameter(InParameterName); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputParameter: Could not find a parameter-based HoudiniInput with name %s. Has the HDA been instantiated?"), + *(InParameterName.ToString()))); + return false; + } + + return PopulateHoudiniInput(InInput, HoudiniInput); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputParameter_Implementation(const FName& InParameterName, UHoudiniPublicAPIInput*& OutInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + const UHoudiniInput* HoudiniInput = FindValidHoudiniNodeInputParameter(InParameterName); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("GetInputParameter: Could not find a parameter-based HoudiniInput with name %s. Has the HDA been instantiated?"), + *(InParameterName.ToString()))); + return false; + } + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + return false; + + OutInput = APIInput; + return bSuccessfullyCopied; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputParameters_Implementation(const TMap& InInputs) +{ + bool bAnyFailures = false; + for (const TPair& Entry : InInputs) + { + if (!SetInputParameter(Entry.Key, Entry.Value)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputParameters: Failed to set input parameter %s"), *(Entry.Key.ToString()))); + bAnyFailures = true; + } + } + + return !bAnyFailures; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputParameters_Implementation(TMap& OutInputs) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + bool bAnyFailures = false; + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const HoudiniInput = HAC->GetInputAt(Index); + if (!IsValid(HoudiniInput) || !HoudiniInput->IsObjectPathParameter()) + continue; + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + { + bAnyFailures = true; + continue; + } + if (!bSuccessfullyCopied) + bAnyFailures = true; + + OutInputs.Add(FName(HoudiniInput->GetName()), APIInput); + } + + return !bAnyFailures; +} + +int32 +UHoudiniPublicAPIAssetWrapper::GetNumOutputs_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return -1; + + return HAC->GetNumOutputs(); +} + +EHoudiniOutputType +UHoudiniPublicAPIAssetWrapper::GetOutputTypeAt_Implementation(const int32 InIndex) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return EHoudiniOutputType::Invalid; + + return Output->GetType(); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetOutputIdentifiersAt_Implementation(const int32 InIndex, TArray& OutIdentifiers) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + const TMap& OutputObjects = Output->GetOutputObjects(); + OutIdentifiers.Empty(); + OutIdentifiers.Reserve(OutputObjects.Num()); + for (const TPair& Entry : OutputObjects) + { + OutIdentifiers.Add(FHoudiniPublicAPIOutputObjectIdentifier(Entry.Key)); + } + + return true; +} + +UObject* +UHoudiniPublicAPIAssetWrapper::GetOutputObjectAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return nullptr; + + const TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject const* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return nullptr; + + return OutputObject->bProxyIsCurrent ? OutputObject->ProxyObject : OutputObject->OutputObject; +} + +UObject* +UHoudiniPublicAPIAssetWrapper::GetOutputComponentAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return nullptr; + + const TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject const* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return nullptr; + + return OutputObject->bProxyIsCurrent ? OutputObject->ProxyComponent : OutputObject->OutputComponent; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetOutputBakeNameFallbackAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, FString& OutBakeName) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + const TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject const* const OutputObject =OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return false; + + OutBakeName = OutputObject->BakeName; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetOutputBakeNameFallbackAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FString& InBakeName) +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return false; + + OutputObject->BakeName = InBakeName; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::BakeOutputObjectAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FName InBakeName, const EHoudiniLandscapeOutputBakeType InLandscapeBakeType) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + TMap& OutputObjects = Output->GetOutputObjects(); + const FHoudiniOutputObjectIdentifier& Identifier = InIdentifier.GetIdentifier(); + FHoudiniOutputObject* const OutputObject = OutputObjects.Find(Identifier); + if (!OutputObject) + { + SetErrorMessage(FString::Printf( + TEXT("BakeOutputObjectAt: Could not find an output object using the specified identifier."))); + return false; + } + + // Determine the object to bake (this is different depending on landscape, curve or mesh + const EHoudiniOutputType OutputType = Output->GetType(); + + if (OutputType == EHoudiniOutputType::Mesh && OutputObject->bProxyIsCurrent) + { + // Output is currently a proxy, this cannot be baked without cooking first. + SetErrorMessage(FString::Printf( + TEXT("BakeOutputObjectAt: Object is a proxy mesh, please refine it before baking to CB."))); + + return false; + } + + UObject* ObjectToBake = nullptr; + switch (OutputType) + { + case EHoudiniOutputType::Landscape: + { + UHoudiniLandscapePtr* const LandscapePtr = Cast(OutputObject->OutputObject); + if (IsValid(LandscapePtr)) + { + ObjectToBake = LandscapePtr->LandscapeSoftPtr.IsValid() ? LandscapePtr->LandscapeSoftPtr.Get() : nullptr; + } + break; + } + case EHoudiniOutputType::Curve: + ObjectToBake = OutputObject->OutputComponent; + break; + case EHoudiniOutputType::Mesh: + ObjectToBake = OutputObject->OutputObject; + break; + case EHoudiniOutputType::Instancer: + case EHoudiniOutputType::Skeletal: + case EHoudiniOutputType::Invalid: + default: + SetErrorMessage(FString::Printf( + TEXT("BakeOutputObjectAt: unsupported output type (%d) for baking to CB."), OutputType)); + return false; + } + + if (!IsValid(ObjectToBake)) + { + SetErrorMessage(FString::Printf(TEXT("BakeOutputObjectAt: Could not find a valid object to bake to CB."))); + return false; + } + + // Find the corresponding HGPO in the output + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& HGPO : Output->GetHoudiniGeoPartObjects()) + { + if (!Identifier.Matches(HGPO)) + continue; + + HoudiniGeoPartObject = HGPO; + break; + } + + if (!HoudiniGeoPartObject.IsValid()) + { + SetErrorMessage(TEXT("BakeOutputObjectAt: Could not find a valid HGPO for the output object. Please recook.")); + return false; + } + + // Determine the HoudiniAssetName + FString HoudiniAssetName; + if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) + { + // If the HAC has a valid owner, use the owner's name + // TODO: Should this be more specific, such as checking for a HoudiniAssetActor? + HoudiniAssetName = HAC->GetOwner()->GetName(); + } + else if (HAC->GetHoudiniAsset()) + { + // Otherwise, if the HAC has a valid HoudiniAsset, use its name + HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); + } + else + { + // Fallback to the HAC's name + HoudiniAssetName = HAC->GetName(); + } + + TArray AllOutputs; + HAC->GetOutputs(AllOutputs); + + FHoudiniOutputDetails::OnBakeOutputObject( + InBakeName.IsNone() ? OutputObject->BakeName : InBakeName.ToString(), + ObjectToBake, + Identifier, + *OutputObject, + HoudiniGeoPartObject, + HAC, + HoudiniAssetName, + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + OutputType, + InLandscapeBakeType, + AllOutputs); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::HasAnyCurrentProxyOutput_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->HasAnyCurrentProxyOutput(); +} + +bool +UHoudiniPublicAPIAssetWrapper::HasAnyCurrentProxyOutputAt_Implementation(const int32 InIndex) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + return Output->HasAnyCurrentProxy(); +} + +bool +UHoudiniPublicAPIAssetWrapper::IsOutputCurrentProxyAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + return Output->IsProxyCurrent(InIdentifier.GetIdentifier()); +} + +EHoudiniProxyRefineRequestResult +UHoudiniPublicAPIAssetWrapper::RefineAllCurrentProxyOutputs_Implementation(const bool bInSilent) +{ + AHoudiniAssetActor* AssetActor = nullptr; + if (!GetValidHoudiniAssetActorWithError(AssetActor)) + return EHoudiniProxyRefineRequestResult::Invalid; + + return FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ AssetActor }, bInSilent); +} + +bool +UHoudiniPublicAPIAssetWrapper::HasPDGAssetLink_Implementation() const +{ + return IsValid(GetHoudiniPDGAssetLink()); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGTOPNetworkPaths_Implementation(TArray& OutTOPNetworkPaths) const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + const uint32 NumNetworks = AssetLink->AllTOPNetworks.Num(); + OutTOPNetworkPaths.Empty(NumNetworks); + for (UTOPNetwork const* const TOPNet : AssetLink->AllTOPNetworks) + { + OutTOPNetworkPaths.Add(TOPNet->NodePath); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGTOPNodePaths_Implementation(const FString& InNetworkRelativePath, TArray& OutTOPNodePaths) const +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* TOPNet = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) + return false; + + const uint32 NumNodes = TOPNet->AllTOPNodes.Num(); + OutTOPNodePaths.Empty(NumNodes); + for (UTOPNode const* const TOPNode : TOPNet->AllTOPNodes) + { + OutTOPNodePaths.Add(TOPNode->NodePath); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGDirtyAllNetworks_Implementation() +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + for (UTOPNetwork* const TOPNetwork : AssetLink->AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + FHoudiniPDGManager::DirtyAll(TOPNetwork); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGDirtyNetwork_Implementation(const FString& InNetworkRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* TOPNet = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) + return false; + + FHoudiniPDGManager::DirtyAll(TOPNet); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGDirtyNode_Implementation(const FString& InNetworkRelativePath, const FString& InNodeRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + int32 NodeIndex = INDEX_NONE; + UTOPNode* TOPNode = nullptr; + if (!GetValidTOPNodeByPathWithError(InNetworkRelativePath, InNodeRelativePath, NetworkIndex, NodeIndex, TOPNode)) + return false; + + FHoudiniPDGManager::DirtyTOPNode(TOPNode); + + return true; +} + +// bool +// UHoudiniPublicAPIAssetWrapper::PDGCookOutputsForAllNetworks_Implementation() +// { +// UHoudiniPDGAssetLink* const AssetLink = GetHoudiniPDGAssetLink(); +// if (!IsValid(AssetLink)) +// return false; +// +// for (UTOPNetwork* const TOPNetwork : AssetLink->AllTOPNetworks) +// { +// if (!IsValid(TOPNetwork)) +// continue; +// +// FHoudiniPDGManager::CookOutput(TOPNetwork); +// } +// +// return true; +// } + +bool +UHoudiniPublicAPIAssetWrapper::PDGCookOutputsForNetwork_Implementation(const FString& InNetworkRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* TOPNet = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) + return false; + + FHoudiniPDGManager::CookOutput(TOPNet); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGCookNode_Implementation(const FString& InNetworkRelativePath, const FString& InNodeRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + int32 NodeIndex = INDEX_NONE; + UTOPNode* TOPNode = nullptr; + if (!GetValidTOPNodeByPathWithError(InNetworkRelativePath, InNodeRelativePath, NetworkIndex, NodeIndex, TOPNode)) + return false; + + FHoudiniPDGManager::CookTOPNode(TOPNode); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGBakeAllOutputs_Implementation() +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + return PDGBakeAllOutputsWithSettings( + AssetLink->HoudiniEngineBakeOption, + AssetLink->PDGBakeSelectionOption, + AssetLink->PDGBakePackageReplaceMode, + AssetLink->bRecenterBakedActors); +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGBakeAllOutputsWithSettings_Implementation( + const EHoudiniEngineBakeOption InBakeOption, + const EPDGBakeSelectionOption InBakeSelection, + const EPDGBakePackageReplaceModeOption InBakeReplacementMode, + const bool bInRecenterBakedActors) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + bool bBakeSuccess = false; + switch (InBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + bBakeSuccess = FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors( + AssetLink, + InBakeSelection, + InBakeReplacementMode, + bInRecenterBakedActors); + break; + case EHoudiniEngineBakeOption::ToBlueprint: + bBakeSuccess = FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints( + AssetLink, + InBakeSelection, + InBakeReplacementMode, + bInRecenterBakedActors); + break; + default: + bBakeSuccess = false; + break; + } + + return bBakeSuccess; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGAutoBakeEnabled_Implementation(const bool bInAutoBakeEnabled) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->bBakeAfterAllWorkResultObjectsLoaded = bInAutoBakeEnabled; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::IsPDGAutoBakeEnabled_Implementation() const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + return AssetLink->bBakeAfterAllWorkResultObjectsLoaded; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGBakeMethod_Implementation(const EHoudiniEngineBakeOption InBakeMethod) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->HoudiniEngineBakeOption = InBakeMethod; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGBakeMethod_Implementation(EHoudiniEngineBakeOption& OutBakeMethod) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + OutBakeMethod = AssetLink->HoudiniEngineBakeOption; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGBakeSelection_Implementation(const EPDGBakeSelectionOption InBakeSelection) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->PDGBakeSelectionOption = InBakeSelection; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGBakeSelection_Implementation(EPDGBakeSelectionOption& OutBakeSelection) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + OutBakeSelection = AssetLink->PDGBakeSelectionOption; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGRecenterBakedActors_Implementation(const bool bInRecenterBakedActors) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->bRecenterBakedActors = bInRecenterBakedActors; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGRecenterBakedActors_Implementation() const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + return AssetLink->bRecenterBakedActors; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGBakingReplacementMode_Implementation(const EPDGBakePackageReplaceModeOption InBakingReplacementMode) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->PDGBakePackageReplaceMode = InBakingReplacementMode; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGBakingReplacementMode_Implementation(EPDGBakePackageReplaceModeOption& OutBakingReplacementMode) const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + OutBakingReplacementMode = AssetLink->PDGBakePackageReplaceMode; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::BindToPDGAssetLink() +{ + if (bAssetLinkSetupAttemptComplete) + return true; + + UHoudiniPDGAssetLink* const PDGAssetLink = GetHoudiniPDGAssetLink(); + if (IsValid(PDGAssetLink)) + { + OnPDGPostTOPNetworkCookDelegateHandle = PDGAssetLink->GetOnPostTOPNetworkCookDelegate().AddUFunction(this, TEXT("HandleOnHoudiniPDGAssetLinkTOPNetPostCook")); + OnPDGPostBakeDelegateHandle = PDGAssetLink->GetOnPostBakeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniPDGAssetLinkPostBake")); + bAssetLinkSetupAttemptComplete = true; + + return true; + } + + return false; +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentStateChange(UHoudiniAssetComponent* InHAC, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniAssetComponentStateChange: unexpected InHAC: %s, expected the wrapper's HAC."), + IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); + return; + } + + if (InToState == EHoudiniAssetState::PreInstantiation) + { + if (OnPreInstantiationDelegate.IsBound()) + OnPreInstantiationDelegate.Broadcast(this); + } + + if (InFromState == EHoudiniAssetState::Instantiating && InToState == EHoudiniAssetState::PreCook) + { + // PDG link setup / bindings: we have to wait until post instantiation to check if we have an asset link and + // configure bindings + if (!bAssetLinkSetupAttemptComplete) + { + BindToPDGAssetLink(); + bAssetLinkSetupAttemptComplete = true; + } + + if (OnPostInstantiationDelegate.IsBound()) + OnPostInstantiationDelegate.Broadcast(this); + } + + if (InFromState == EHoudiniAssetState::PreProcess) + { + if (OnPreProcessStateExitedDelegate.IsBound()) + OnPreProcessStateExitedDelegate.Broadcast(this); + } + + if (InFromState == EHoudiniAssetState::Processing && InToState == EHoudiniAssetState::None) + { + if (OnPostProcessingDelegate.IsBound()) + OnPostProcessingDelegate.Broadcast(this); + } +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentPostCook(UHoudiniAssetComponent* InHAC, const bool bInCookSuccess) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniAssetComponentPostCook: unexpected InHAC: %s, expected the wrapper's HAC."), + IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); + return; + } + + if (OnPostCookDelegate.IsBound()) + OnPostCookDelegate.Broadcast(this, bInCookSuccess); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentPostBake(UHoudiniAssetComponent* InHAC, const bool bInBakeSuccess) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniAssetComponentPostBake: unexpected InHAC: %s, expected the wrapper's HAC."), + IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); + return; + } + + if (OnPostBakeDelegate.IsBound()) + OnPostBakeDelegate.Broadcast(this, bInBakeSuccess); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniPDGAssetLinkTOPNetPostCook(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InTOPNet, const bool bInAnyWorkItemsFailed) +{ + if (!IsValid(InPDGAssetLink)) + return; + + if (InPDGAssetLink != GetHoudiniPDGAssetLink()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniPDGAssetLinkTOPNetPostCook: unexpected InPDGAssetLink: %s, expected the wrapper's PDGAssetLink."), + IsValid(InPDGAssetLink) ? *InPDGAssetLink->GetName() : TEXT(""))); + return; + } + + if (OnPostPDGTOPNetworkCookDelegate.IsBound()) + OnPostPDGTOPNetworkCookDelegate.Broadcast(this, !bInAnyWorkItemsFailed); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniPDGAssetLinkPostBake(UHoudiniPDGAssetLink* InPDGAssetLink, const bool bInBakeSuccess) +{ + if (!IsValid(InPDGAssetLink)) + return; + + if (InPDGAssetLink != GetHoudiniPDGAssetLink()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniPDGAssetLinkPostBake: unexpected InPDGAssetLink: %s, expected the wrapper's PDGAssetLink."), + IsValid(InPDGAssetLink) ? *InPDGAssetLink->GetName() : TEXT(""))); + return; + } + + if (OnPostPDGBakeDelegate.IsBound()) + OnPostPDGBakeDelegate.Broadcast(this, bInBakeSuccess); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniProxyMeshesRefinedGlobal(UHoudiniAssetComponent* InHAC, const EHoudiniProxyRefineResult InResult) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + return; + + if (OnProxyMeshesRefinedDelegate.IsBound()) + OnProxyMeshesRefinedDelegate.Broadcast(this, InResult); +} + +UHoudiniParameter* +UHoudiniPublicAPIAssetWrapper::FindValidParameterByName(const FName& InParameterTupleName) const +{ + AActor* const Actor = GetHoudiniAssetActor(); + const FString ActorName = IsValid(Actor) ? Actor->GetName() : FString(); + + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (!IsValid(HAC)) + { + SetErrorMessage(FString::Printf(TEXT("Could not find HAC on Actor '%s'"), *ActorName)); + return nullptr; + } + + UHoudiniParameter* const Param = HAC->FindParameterByName(InParameterTupleName.ToString()); + if (!IsValid(Param)) + { + SetErrorMessage(FString::Printf( + TEXT("Could not find valid parameter tuple '%s' on '%s'."), + *InParameterTupleName.ToString(), *ActorName)); + return nullptr; + } + + return Param; +} + +bool +UHoudiniPublicAPIAssetWrapper::FindRampPointData( + UHoudiniParameter* const InParam, + const int32 InIndex, + TArray>& OutPointData) const +{ + if (!IsValid(InParam)) + return false; + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!InParam->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !InParam->IsAutoUpdate(); + + const bool bFetchAllPoints = (InIndex == INDEX_NONE); + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = InParam->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(InParam); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *InParam->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(InParam); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *InParam->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParam->GetName()))); + return false; + } + + if (bUseCachedPoints) + { + // When using the cached points we only have to resize the array + if (FloatRampParam) + { + if (!bFetchAllPoints && !FloatRampParam->CachedPoints.IsValidIndex(InIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, FloatRampParam->CachedPoints.Num() - 1)); + return false; + } + + if (bFetchAllPoints) + { + // Get all points + OutPointData.Reserve(FloatRampParam->CachedPoints.Num()); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + for (UHoudiniParameterRampFloatPoint* const RampPoint : FloatRampParam->CachedPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(RampPoint, bIsPointData)); + } + } + else + { + OutPointData.Reserve(1); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + + const bool bIsPointData = true; + OutPointData.Add(TPair(FloatRampParam->CachedPoints[InIndex], bIsPointData)); + } + + return true; + } + else + { + if (!bFetchAllPoints && !ColorRampParam->CachedPoints.IsValidIndex(InIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, ColorRampParam->CachedPoints.Num() - 1)); + return false; + } + + if (bFetchAllPoints) + { + // Get all points + OutPointData.Reserve(ColorRampParam->CachedPoints.Num()); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + for (UHoudiniParameterRampColorPoint* const RampPoint : ColorRampParam->CachedPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(RampPoint, bIsPointData)); + } + } + else + { + OutPointData.Reserve(1); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + + const bool bIsPointData = true; + OutPointData.Add(TPair(ColorRampParam->CachedPoints[InIndex], bIsPointData)); + } + + return true; + } + } + else + { + TSet InstanceIndexesPendingDelete; + int32 NumInsertOps = 0; + TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; + for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) + { + if (!IsValid(Event)) + continue; + + if (Event->IsInsertEvent()) + NumInsertOps++; + else if (Event->IsDeleteEvent()) + InstanceIndexesPendingDelete.Add(Event->DeleteInstanceIndex); + } + + const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); + const int32 NumActivePointsInArray = PointsArraySize - InstanceIndexesPendingDelete.Num(); + const int32 TotalNumPoints = NumActivePointsInArray + NumInsertOps; + + // Reserve the expected amount of space needed in the array and reset / destruct existing items so we can + // add from index 0 + if (bFetchAllPoints) + OutPointData.Reserve(TotalNumPoints); + else + OutPointData.Reserve(1); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + + if (bFetchAllPoints || InIndex < NumActivePointsInArray) + { + // Getting all points or point is in the points array + if (FloatRampParam) + { + const int32 ArraySize = FloatRampParam->Points.Num(); + int32 PointIndex = 0; + for (int32 Index = 0; Index < ArraySize && (bFetchAllPoints || PointIndex <= InIndex); ++Index) + { + UHoudiniParameterRampFloatPoint* const PointData = FloatRampParam->Points[Index]; + const bool bIsDeletedPoint = (PointData && InstanceIndexesPendingDelete.Contains(PointData->InstanceIndex)); + if (!bIsDeletedPoint) + { + if (PointIndex == InIndex || bFetchAllPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(PointData, bIsPointData)); + } + + // If we are fetching only the point at InIndex, then we are done here + if (PointIndex == InIndex) + return true; + + PointIndex++; + } + } + } + else + { + const int32 ArraySize = ColorRampParam->Points.Num(); + int32 PointIndex = 0; + for (int32 Index = 0; Index < ArraySize && (bFetchAllPoints || PointIndex <= InIndex); ++Index) + { + UHoudiniParameterRampColorPoint* const PointData = ColorRampParam->Points[Index]; + const bool bIsDeletedPoint = (PointData && InstanceIndexesPendingDelete.Contains(PointData->InstanceIndex)); + if (!bIsDeletedPoint) + { + if (PointIndex == InIndex || bFetchAllPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(PointData, bIsPointData)); + } + + // If we are fetching only the point at InIndex, then we are done here + if (PointIndex == InIndex) + return true; + + PointIndex++; + } + } + } + } + + if (bFetchAllPoints || InIndex < TotalNumPoints) + { + // Point is an insert operation + const int32 NumEvents = ModificationEvents.Num(); + int32 PointIndex = NumActivePointsInArray; + for (int32 Index = 0; Index < NumEvents && (bFetchAllPoints || PointIndex <= InIndex); ++Index) + { + UHoudiniParameterRampModificationEvent* const Event = ModificationEvents[Index]; + if (!IsValid(Event)) + continue; + + if (!Event->IsInsertEvent()) + continue; + + if (PointIndex == InIndex || bFetchAllPoints) + { + const bool bIsPointData = false; + OutPointData.Add(TPair(Event, bIsPointData)); + } + + if (PointIndex == InIndex) + return true; + + PointIndex++; + } + } + else + { + // Point is out of range + SetErrorMessage(FString::Printf( + TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, TotalNumPoints)); + return false; + } + + if (bFetchAllPoints) + { + if (TotalNumPoints != OutPointData.Num()) + { + SetErrorMessage(FString::Printf( + TEXT("Failed to fetch all ramp points. Got %d, expected %d."), OutPointData.Num(), TotalNumPoints)); + return false; + } + + return true; + } + } + + // If we reach this point we didn't find the point + SetErrorMessage(FString::Printf(TEXT("Could not find valid ramp point at index %d."), InIndex)); + return false; +} + +bool UHoudiniPublicAPIAssetWrapper::SetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPosition, + const float InFloatValue, + const FLinearColor& InColorValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation, + const bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + return false; + } + + const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( + InInterpolation); + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + // Get the point at InPointIndex's data + TArray> RampPointData; + if (!FindRampPointData(Param, InPointIndex, RampPointData) || RampPointData.Num() < 1) + return false; + + UObject* const PointData = RampPointData[0].Key; + const bool bIsPointData = RampPointData[0].Value; + if (!IsValid(PointData)) + return false; + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* FloatPointData = nullptr; + UHoudiniParameterRampColorPoint* ColorPointData = nullptr; + + if (FloatRampParam) + { + FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + else + { + ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + + if (bUseCachedPoints) + { + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (FloatPointData) + { + if (FloatPointData->Position != InPosition) + { + FloatPointData->Position = InPosition; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Value != InFloatValue) + { + FloatPointData->Value = InFloatValue; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Interpolation != NewInterpolation) + { + FloatPointData->Interpolation = NewInterpolation; + FloatRampParam->bCaching = true; + } + } + else if (ColorPointData) + { + if (ColorPointData->Position != InPosition) + { + ColorPointData->Position = InPosition; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Value != InColorValue) + { + ColorPointData->Value = InColorValue; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Interpolation != NewInterpolation) + { + ColorPointData->Interpolation = NewInterpolation; + ColorRampParam->bCaching = true; + } + } + + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + else + { + // When setting the main points, we set the values using the setters on the point data but still manually + // mark the position/value/interpolation parent parameters as changed + if (FloatPointData) + { + if (FloatPointData->Position != InPosition && FloatPointData->PositionParentParm) + { + FloatPointData->SetPosition(InPosition); + if (bInMarkChanged) + FloatPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Value != InFloatValue && FloatPointData->ValueParentParm) + { + FloatPointData->SetValue(InFloatValue); + if (bInMarkChanged) + FloatPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Interpolation != NewInterpolation && FloatPointData->InterpolationParentParm) + { + FloatPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + FloatPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + else if (ColorPointData) + { + if (ColorPointData->Position != InPosition && ColorPointData->PositionParentParm) + { + ColorPointData->SetPosition(InPosition); + if (bInMarkChanged) + ColorPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Value != InColorValue && ColorPointData->ValueParentParm) + { + ColorPointData->SetValue(InColorValue); + if (bInMarkChanged) + ColorPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Interpolation != NewInterpolation && ColorPointData->InterpolationParentParm) + { + ColorPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + ColorPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + Event->InsertPosition = InPosition; + if (FloatRampParam) + { + Event->InsertFloat = InFloatValue; + } + else if (ColorRampParam) + { + Event->InsertColor = InColorValue; + } + Event->InsertInterpolation = NewInterpolation; + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPosition, + float& OutFloatValue, + FLinearColor& OutColorValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolation) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + return false; + } + + TArray> RampPointData; + if (!FindRampPointData(Param, InPointIndex, RampPointData) || RampPointData.Num() < 1) + return false; + + UObject* const PointData = RampPointData[0].Key; + const bool bIsPointData = RampPointData[0].Value; + if (!IsValid(PointData)) + return false; + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* FloatPointData = nullptr; + UHoudiniParameterRampColorPoint* ColorPointData = nullptr; + + if (FloatRampParam) + { + FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + else + { + ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (FloatPointData) + { + OutPosition = FloatPointData->Position; + OutFloatValue = FloatPointData->Value; + OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + FloatPointData->Interpolation); + } + else if (ColorPointData) + { + OutPosition = ColorPointData->Position; + OutColorValue = ColorPointData->Value; + OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + ColorPointData->Interpolation); + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + OutPosition = Event->InsertPosition; + if (FloatRampParam) + { + OutFloatValue = Event->InsertFloat; + } + else if (ColorRampParam) + { + OutColorValue = Event->InsertColor; + } + OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); + } + + return true; +} + +UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) +{ + if (InNodeInputIndex < 0) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->GetInputIndex() == InNodeInputIndex) + return Input; + } + + return nullptr; +} + +const UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) const +{ + if (InNodeInputIndex < 0) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->GetInputIndex() == InNodeInputIndex) + return Input; + } + + return nullptr; +} + +UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) +{ + if (InInputParameterName == NAME_None) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const FString InputParameterName = InInputParameterName.ToString(); + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->IsObjectPathParameter() && Input->GetName() == InputParameterName) + return Input; + } + + return nullptr; +} + +const UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) const +{ + if (InInputParameterName == NAME_None) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const FString InputParameterName = InInputParameterName.ToString(); + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->IsObjectPathParameter() && Input->GetName() == InputParameterName) + return Input; + } + + return nullptr; +} + +bool +UHoudiniPublicAPIAssetWrapper::CreateAndPopulateAPIInput(const UHoudiniInput* InHoudiniInput, UHoudiniPublicAPIInput*& OutAPIInput) +{ + if (!IsValid(InHoudiniInput)) + return false; + + TSubclassOf APIInputClass; + const EHoudiniInputType InputType = InHoudiniInput->GetInputType(); + switch (InputType) + { + case EHoudiniInputType::Geometry: + APIInputClass = UHoudiniPublicAPIGeoInput::StaticClass(); + break; + case EHoudiniInputType::Curve: + APIInputClass = UHoudiniPublicAPICurveInput::StaticClass(); + break; + case EHoudiniInputType::Asset: + APIInputClass = UHoudiniPublicAPIAssetInput::StaticClass(); + break; + case EHoudiniInputType::World: + APIInputClass = UHoudiniPublicAPIWorldInput::StaticClass(); + break; + case EHoudiniInputType::Landscape: + APIInputClass = UHoudiniPublicAPILandscapeInput::StaticClass(); + break; + case EHoudiniInputType::Skeletal: + // Not yet implemented + SetErrorMessage(FString::Printf(TEXT("GetInputAtIndex: Input type not yet implemented %d"), InputType)); + return false; + case EHoudiniInputType::Invalid: + SetErrorMessage(FString::Printf(TEXT("GetInputAtIndex: Invalid input type %d"), InputType)); + return false; + } + + UHoudiniPublicAPIInput* APIInput = CreateEmptyInput(APIInputClass); + if (!IsValid(APIInput)) + { + return false; + } + + const bool bSuccessfullyCopied = APIInput->PopulateFromHoudiniInput(InHoudiniInput); + OutAPIInput = APIInput; + return bSuccessfullyCopied; +} + +bool +UHoudiniPublicAPIAssetWrapper::PopulateHoudiniInput(const UHoudiniPublicAPIInput* InAPIInput, UHoudiniInput* InHoudiniInput) const +{ + if (!IsValid(InHoudiniInput)) + return false; + + return InAPIInput->UpdateHoudiniInput(InHoudiniInput); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidTOPNetworkByPathWithError(const FString& InNetworkRelativePath, int32& OutNetworkIndex, UTOPNetwork*& OutNetwork) const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* const Network = UHoudiniPDGAssetLink::GetTOPNetworkByNodePath( + InNetworkRelativePath, AssetLink->AllTOPNetworks, NetworkIndex); + if (!IsValid(Network)) + { + SetErrorMessage(FString::Printf( + TEXT("Could not find valid TOP network at relative path '%s'."), *InNetworkRelativePath)); + return false; + } + + OutNetworkIndex = NetworkIndex; + OutNetwork = Network; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidTOPNodeByPathWithError( + const FString& InNetworkRelativePath, + const FString& InNodeRelativePath, + int32& OutNetworkIndex, + int32& OutNodeIndex, + UTOPNode*& OutNode) const +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* Network = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, Network)) + return false; + + int32 NodeIndex = INDEX_NONE; + UTOPNode* const Node = UHoudiniPDGAssetLink::GetTOPNodeByNodePath( + InNodeRelativePath, Network->AllTOPNodes, NodeIndex); + if (!IsValid(Node)) + { + SetErrorMessage(FString::Printf( + TEXT("Could not find valid TOP node at relative path '%s'."), *InNodeRelativePath)); + return false; + } + + OutNetworkIndex = NetworkIndex; + OutNodeIndex = NodeIndex; + OutNode = Node; + return true; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp new file mode 100644 index 000000000..97b05f935 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp @@ -0,0 +1,40 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPI.h" + + +UHoudiniPublicAPIBlueprintLib::UHoudiniPublicAPIBlueprintLib(class FObjectInitializer const & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniPublicAPI* UHoudiniPublicAPIBlueprintLib::GetAPI() +{ + static UHoudiniPublicAPI* Obj = NewObject(GetTransientPackage(), NAME_None, RF_MarkAsRootSet); + return Obj; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp new file mode 100644 index 000000000..aca5bafd9 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp @@ -0,0 +1,856 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIInputTypes.h" + +#include "HoudiniPublicAPIAssetWrapper.h" + + +UHoudiniPublicAPIInput::UHoudiniPublicAPIInput() +{ + bKeepWorldTransform = false; + bImportAsReference = false; +} + +bool +UHoudiniPublicAPIInput::SetInputObjects_Implementation(const TArray& InObjects) +{ + bool bHasFailures = false; + InputObjects.Empty(InObjects.Num()); + for (UObject* const Object : InObjects) + { + if (!IsValid(Object)) + { + SetErrorMessage(FString::Printf(TEXT("An input object is null or invalid."))); + bHasFailures = true; + continue; + } + else if (!IsAcceptableObjectForInput(Object)) + { + SetErrorMessage(FString::Printf( + TEXT("Object '%s' is not of an acceptable type for inputs of class %s."), + *(Object->GetName()), *(GetClass()->GetName()))); + bHasFailures = true; + continue; + } + + InputObjects.Add(Object); + } + + return !bHasFailures; +} + +bool +UHoudiniPublicAPIInput::GetInputObjects_Implementation(TArray& OutObjects) +{ + OutObjects = InputObjects; + + return true; +} + +bool +UHoudiniPublicAPIInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + const EHoudiniInputType InputType = GetInputType(); + if (!IsValid(InInput)) + { + SetErrorMessage(TEXT("InInput is invalid.")); + return false; + } + + if (InInput->GetInputType() != InputType) + { + SetErrorMessage(FString::Printf( + TEXT("Incompatible input types %d vs %d"), InInput->GetInputType(), InputType)); + return false; + } + + bKeepWorldTransform = InInput->GetKeepWorldTransform(); + bImportAsReference = InInput->GetImportAsReference(); + + const TArray* SrcInputObjectsPtr = InInput->GetHoudiniInputObjectArray(InputType); + if (SrcInputObjectsPtr && SrcInputObjectsPtr->Num() > 0) + { + InputObjects.Empty(SrcInputObjectsPtr->Num()); + for (UHoudiniInputObject const* const SrcInputObject : *SrcInputObjectsPtr) + { + if (!IsValid(SrcInputObject)) + continue; + + UObject* NewInputObject = ConvertInternalInputObject(SrcInputObject->GetObject()); + if (NewInputObject && NewInputObject->IsPendingKill()) + { + SetErrorMessage(FString::Printf( + TEXT("One of the input objects is non-null but pending kill/invalid."))); + return false; + } + + InputObjects.Add(NewInputObject); + + CopyHoudiniInputObjectProperties(SrcInputObject, NewInputObject); + } + } + + return true; +} + +bool +UHoudiniPublicAPIInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!IsValid(InInput)) + { + SetErrorMessage(TEXT("InInput is invalid.")); + return false; + } + + // Set / change the input type + const EHoudiniInputType InputType = GetInputType(); + bool bBlueprintStructureModified = false; + InInput->SetInputType(InputType, bBlueprintStructureModified); + + // Set any general settings + bool bAnyChanges = false; + if (InInput->GetKeepWorldTransform() != bKeepWorldTransform) + { + InInput->SetKeepWorldTransform(bKeepWorldTransform); + bAnyChanges = true; + } + if (InInput->GetImportAsReference() != bImportAsReference) + { + InInput->SetImportAsReference(bImportAsReference); + bAnyChanges = true; + } + + // Copy / set the input objects on the Houdini Input + const int32 NumInputObjects = InputObjects.Num(); + InInput->SetInputObjectsNumber(InputType, NumInputObjects); + for (int32 Index = 0; Index < NumInputObjects; ++Index) + { + UObject* const InputObject = InputObjects[Index]; + + if (!IsValid(InputObject)) + { + InInput->SetInputObjectAt(Index, nullptr); + } + else + { + ConvertAPIInputObjectAndAssignToInput(InputObject, InInput, Index); + UHoudiniInputObject *DstInputObject = InInput->GetHoudiniInputObjectAt(Index); + if (DstInputObject) + CopyPropertiesToHoudiniInputObject(InputObject, DstInputObject); + } + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + return true; +} + +bool +UHoudiniPublicAPIInput::CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) +{ + if (!IsValid(InInputObject) || !IsValid(InObject)) + return false; + + return true; +} + +bool +UHoudiniPublicAPIInput::CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const +{ + if (!IsValid(InObject) || !IsValid(InInputObject)) + return false; + + // const EHoudiniInputObjectType InputObjectType = InInputObject->Type; + + if (InInputObject->GetImportAsReference() != bImportAsReference) + { + InInputObject->SetImportAsReference(bImportAsReference); + InInputObject->MarkChanged(true); + } + + // switch (InputObjectType) + // { + // case EHoudiniInputObjectType::StaticMesh: + // case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + // break; + // case EHoudiniInputObjectType::Object: + // case EHoudiniInputObjectType::SkeletalMesh: + // case EHoudiniInputObjectType::HoudiniSplineComponent: + // case EHoudiniInputObjectType::SceneComponent: + // case EHoudiniInputObjectType::StaticMeshComponent: + // case EHoudiniInputObjectType::InstancedStaticMeshComponent: + // case EHoudiniInputObjectType::SplineComponent: + // case EHoudiniInputObjectType::HoudiniAssetComponent: + // case EHoudiniInputObjectType::Actor: + // case EHoudiniInputObjectType::Landscape: + // case EHoudiniInputObjectType::Brush: + // case EHoudiniInputObjectType::CameraComponent: + // case EHoudiniInputObjectType::DataTable: + // case EHoudiniInputObjectType::HoudiniAssetActor: + // break; + // + // case EHoudiniInputObjectType::Invalid: + // return false; + // } + + return true; +} + +UObject* +UHoudiniPublicAPIInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const +{ + if (!IsValid(InHoudiniInput)) + return nullptr; + + UObject* const ObjectToSet = (InAPIInputObject && !InAPIInputObject->IsPendingKill()) ? InAPIInputObject : nullptr; + InHoudiniInput->SetInputObjectAt(InInputIndex, ObjectToSet); + + return ObjectToSet; +} + +UHoudiniPublicAPIGeoInput::UHoudiniPublicAPIGeoInput() +{ + bKeepWorldTransform = false; + bPackBeforeMerge = false; + bExportLODs = false; + bExportSockets = false; + bExportColliders = false; +} + +bool +UHoudiniPublicAPIGeoInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + bPackBeforeMerge = InInput->GetPackBeforeMerge(); + bExportLODs = InInput->GetExportLODs(); + bExportSockets = InInput->GetExportSockets(); + bExportColliders = InInput->GetExportColliders(); + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + bool bAnyChanges = false; + if (InInput->GetPackBeforeMerge() != bPackBeforeMerge) + { + InInput->SetPackBeforeMerge(bPackBeforeMerge); + bAnyChanges = true; + } + if (InInput->GetExportLODs() != bExportLODs) + { + InInput->SetExportLODs(bExportLODs); + bAnyChanges = true; + } + if (InInput->GetExportSockets() != bExportSockets) + { + InInput->SetExportSockets(bExportSockets); + bAnyChanges = true; + } + if (InInput->GetExportColliders() != bExportColliders) + { + InInput->SetExportColliders(bExportColliders); + bAnyChanges = true; + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) +{ + if (!Super::CopyHoudiniInputObjectProperties(InInputObject, InObject)) + return false; + + if (!IsValid(InInputObject) || !IsValid(InObject)) + return false; + + // Copy the transform offset + SetObjectTransformOffset(InObject, InInputObject->Transform); + + return true; +} + + +bool +UHoudiniPublicAPIGeoInput::CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const +{ + if (!Super::CopyPropertiesToHoudiniInputObject(InObject, InInputObject)) + return false; + + if (!IsValid(InObject) || !IsValid(InInputObject)) + return false; + + // Copy the transform offset + FTransform Transform; + if (GetObjectTransformOffset(InObject, Transform)) + { + if (!InInputObject->Transform.Equals(Transform)) + { + InInputObject->Transform = Transform; + InInputObject->MarkChanged(true); + } + } + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::SetObjectTransformOffset_Implementation(UObject* InObject, const FTransform& InTransform) +{ + // Ensure that InObject is valid and has already been added as input object + if (!IsValid(InObject)) + { + SetErrorMessage(TEXT("InObject is invalid.")); + return false; + } + + if (INDEX_NONE == InputObjects.Find(InObject)) + { + SetErrorMessage(FString::Printf( + TEXT("InObject '%s' is not currently set as input object on this input."), *(InObject->GetName()))); + return false; + } + + InputObjectTransformOffsets.Add(InObject, InTransform); + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::GetObjectTransformOffset_Implementation(UObject* InObject, FTransform& OutTransform) const +{ + // Ensure that InObject is valid and has already been added as input object + if (!IsValid(InObject)) + { + SetErrorMessage(TEXT("InObject is invalid.")); + return false; + } + + if (INDEX_NONE == InputObjects.Find(InObject)) + { + SetErrorMessage(FString::Printf( + TEXT("InObject '%s' is not currently set as input object on this input."), *(InObject->GetName()))); + return false; + } + + FTransform const* const TransformPtr = InputObjectTransformOffsets.Find(InObject); + if (!TransformPtr) + { + SetErrorMessage(FString::Printf( + TEXT("InObject '%s' does not have a transform offset set."), *(InObject->GetName()))); + return false; + } + + OutTransform = *TransformPtr; + return true; +} + + +UHoudiniPublicAPICurveInputObject::UHoudiniPublicAPICurveInputObject() + : bClosed(false) + , bReversed(false) + , CurveType(EHoudiniPublicAPICurveType::Polygon) + , CurveMethod(EHoudiniPublicAPICurveMethod::CVs) +{ + +} + + +void +UHoudiniPublicAPICurveInputObject::PopulateFromHoudiniSplineComponent(UHoudiniSplineComponent const* const InSpline) +{ + if (!IsValid(InSpline)) + return; + + bClosed = InSpline->IsClosedCurve(); + bReversed = InSpline->IsReversed(); + CurveType = ToHoudiniPublicAPICurveType(InSpline->GetCurveType()); + CurveMethod = ToHoudiniPublicAPICurveMethod(InSpline->GetCurveMethod()); + CurvePoints = InSpline->CurvePoints; +} + +void +UHoudiniPublicAPICurveInputObject::CopyToHoudiniSplineComponent(UHoudiniSplineComponent* const InSpline) const +{ + if (!IsValid(InSpline)) + return; + + InSpline->SetClosedCurve(bClosed); + InSpline->SetReversed(bReversed); + InSpline->SetCurveType(ToHoudiniCurveType(CurveType)); + InSpline->SetCurveMethod(ToHoudiniCurveMethod(CurveMethod)); + InSpline->ResetCurvePoints(); + InSpline->ResetDisplayPoints(); + InSpline->CurvePoints = CurvePoints; +} + +EHoudiniCurveType +UHoudiniPublicAPICurveInputObject::ToHoudiniCurveType(const EHoudiniPublicAPICurveType InCurveType) +{ + switch (InCurveType) + { + case EHoudiniPublicAPICurveType::Invalid: + return EHoudiniCurveType::Invalid; + case EHoudiniPublicAPICurveType::Polygon: + return EHoudiniCurveType::Polygon; + case EHoudiniPublicAPICurveType::Nurbs: + return EHoudiniCurveType::Nurbs; + case EHoudiniPublicAPICurveType::Bezier: + return EHoudiniCurveType::Bezier; + case EHoudiniPublicAPICurveType::Points: + return EHoudiniCurveType::Points; + } + + return EHoudiniCurveType::Invalid; +} + +EHoudiniCurveMethod +UHoudiniPublicAPICurveInputObject::ToHoudiniCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod) +{ + switch (InCurveMethod) + { + case EHoudiniPublicAPICurveMethod::Invalid: + return EHoudiniCurveMethod::Invalid; + case EHoudiniPublicAPICurveMethod::CVs: + return EHoudiniCurveMethod::CVs; + case EHoudiniPublicAPICurveMethod::Breakpoints: + return EHoudiniCurveMethod::Breakpoints; + case EHoudiniPublicAPICurveMethod::Freehand: + return EHoudiniCurveMethod::Freehand; + } + + return EHoudiniCurveMethod::Invalid; +} + +EHoudiniPublicAPICurveType +UHoudiniPublicAPICurveInputObject::ToHoudiniPublicAPICurveType(const EHoudiniCurveType InCurveType) +{ + switch (InCurveType) + { + case EHoudiniCurveType::Invalid: + return EHoudiniPublicAPICurveType::Invalid; + case EHoudiniCurveType::Polygon: + return EHoudiniPublicAPICurveType::Polygon; + case EHoudiniCurveType::Nurbs: + return EHoudiniPublicAPICurveType::Nurbs; + case EHoudiniCurveType::Bezier: + return EHoudiniPublicAPICurveType::Bezier; + case EHoudiniCurveType::Points: + return EHoudiniPublicAPICurveType::Points; + } + + return EHoudiniPublicAPICurveType::Invalid; +} + +EHoudiniPublicAPICurveMethod +UHoudiniPublicAPICurveInputObject::ToHoudiniPublicAPICurveMethod(const EHoudiniCurveMethod InCurveMethod) +{ + switch (InCurveMethod) + { + case EHoudiniCurveMethod::Invalid: + return EHoudiniPublicAPICurveMethod::Invalid; + case EHoudiniCurveMethod::CVs: + return EHoudiniPublicAPICurveMethod::CVs; + case EHoudiniCurveMethod::Breakpoints: + return EHoudiniPublicAPICurveMethod::Breakpoints; + case EHoudiniCurveMethod::Freehand: + return EHoudiniPublicAPICurveMethod::Freehand; + } + + return EHoudiniPublicAPICurveMethod::Invalid; +} + +UHoudiniPublicAPICurveInput::UHoudiniPublicAPICurveInput() +{ + bKeepWorldTransform = false; + bCookOnCurveChanged = true; + bAddRotAndScaleAttributesOnCurves = false; +} + +bool +UHoudiniPublicAPICurveInput::IsAcceptableObjectForInput_Implementation(UObject* InObject) const +{ + if (!IsValid(InObject)) + return false; + + if (InObject->IsA()) + return true; + + return Super::IsAcceptableObjectForInput_Implementation(InObject); +} + +bool +UHoudiniPublicAPICurveInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + bCookOnCurveChanged = InInput->GetCookOnCurveChange(); + bAddRotAndScaleAttributesOnCurves = InInput->IsAddRotAndScaleAttributesEnabled(); + + return true; +} + +bool +UHoudiniPublicAPICurveInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + bool bAnyChanges = false; + if (InInput->GetCookOnCurveChange() != bCookOnCurveChanged) + { + InInput->SetCookOnCurveChange(bCookOnCurveChanged); + bAnyChanges = true; + } + if (InInput->IsAddRotAndScaleAttributesEnabled() != bAddRotAndScaleAttributesOnCurves) + { + InInput->SetAddRotAndScaleAttributes(bAddRotAndScaleAttributesOnCurves); + bAnyChanges = true; + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + return true; +} + +UObject* +UHoudiniPublicAPICurveInput::ConvertInternalInputObject(UObject* InInternalInputObject) +{ + UObject* Object = Super::ConvertInternalInputObject(InInternalInputObject); + + // If the input object is a houdini spline component, convert it to an API curve wrapper + if (IsValid(Object) && Object->IsA()) + { + UHoudiniPublicAPICurveInputObject* const Curve = NewObject( + this, UHoudiniPublicAPICurveInputObject::StaticClass()); + if (IsValid(Curve)) + { + Curve->PopulateFromHoudiniSplineComponent(Cast(Object)); + return Curve; + } + } + + return Object; +} + +UObject* +UHoudiniPublicAPICurveInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const +{ + UObject* Object = nullptr; + + // If the input is an API curve wrapper, convert it to a UHoudiniSplineComponent + if (IsValid(InAPIInputObject) && InAPIInputObject->IsA() && IsValid(InHoudiniInput)) + { + UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputComponent = nullptr; + const bool bAttachToParent = true; + const bool bAppendToInputArray = false; + bool bBlueprintStructureModified; + UHoudiniInputHoudiniSplineComponent* const NewHoudiniInputObject = InHoudiniInput->CreateHoudiniSplineInput(FromHoudiniSplineInputComponent, bAttachToParent, bAppendToInputArray, bBlueprintStructureModified); + if (IsValid(NewHoudiniInputObject)) + { + UHoudiniSplineComponent* HoudiniSplineComponent = NewHoudiniInputObject->GetCurveComponent(); + if (IsValid(HoudiniSplineComponent)) + { + // Populate the HoudiniSplineComponent from the curve wrapper + Cast(InAPIInputObject)->CopyToHoudiniSplineComponent(HoudiniSplineComponent); + Object = HoudiniSplineComponent; + } + } + + TArray* HoudiniInputObjectArray = InHoudiniInput->GetHoudiniInputObjectArray(InHoudiniInput->GetInputType()); + if (HoudiniInputObjectArray && HoudiniInputObjectArray->IsValidIndex(InInputIndex)) + (*HoudiniInputObjectArray)[InInputIndex] = IsValid(NewHoudiniInputObject) ? NewHoudiniInputObject : nullptr; + } + else + { + Object = Super::ConvertAPIInputObjectAndAssignToInput(InAPIInputObject, InHoudiniInput, InInputIndex); + } + + return Object; +} + + +UHoudiniPublicAPIAssetInput::UHoudiniPublicAPIAssetInput() +{ + bKeepWorldTransform = true; +} + +bool +UHoudiniPublicAPIAssetInput::IsAcceptableObjectForInput_Implementation(UObject* InObject) const +{ + if (IsValid(InObject) && InObject->IsA()) + { + UHoudiniPublicAPIAssetWrapper* const Wrapper = Cast(InObject); + AHoudiniAssetActor* const AssetActor = Cast(Wrapper->GetHoudiniAssetActor()); + if (IsValid(AssetActor) && IsValid(AssetActor->HoudiniAssetComponent)) + return true; + } + + return Super::IsAcceptableObjectForInput_Implementation(InObject); +} + +bool +UHoudiniPublicAPIAssetInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + return true; +} + +bool +UHoudiniPublicAPIAssetInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + return true; +} + +UObject* +UHoudiniPublicAPIAssetInput::ConvertInternalInputObject(UObject* InInternalInputObject) +{ + // If InInternalInputObject is a Houdini Asset Component or Houdini Asset Actor, wrap it with the API and return + // wrapper. + if (IsValid(InInternalInputObject)) + { + if ((InInternalInputObject->IsA() || InInternalInputObject->IsA()) && + UHoudiniPublicAPIAssetWrapper::CanWrapHoudiniObject(InInternalInputObject)) + { + return UHoudiniPublicAPIAssetWrapper::CreateWrapper(this, InInternalInputObject); + } + } + + return Super::ConvertInternalInputObject(InInternalInputObject); +} + +UObject* +UHoudiniPublicAPIAssetInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const +{ + // If InAPIInputObject is an asset wrapper, extract the underlying HoudiniAssetComponent. + if (IsValid(InAPIInputObject) && InAPIInputObject->IsA()) + { + UHoudiniPublicAPIAssetWrapper* const Wrapper = Cast(InAPIInputObject); + if (Wrapper) + { + UHoudiniAssetComponent* const HAC = Wrapper->GetHoudiniAssetComponent(); + if (IsValid(HAC)) + { + return Super::ConvertAPIInputObjectAndAssignToInput(HAC, InHoudiniInput, InInputIndex); + } + } + } + + return Super::ConvertAPIInputObjectAndAssignToInput(InAPIInputObject, InHoudiniInput, InInputIndex); +} + + +UHoudiniPublicAPIWorldInput::UHoudiniPublicAPIWorldInput() +{ + bKeepWorldTransform = true; + bIsWorldInputBoundSelector = false; + bWorldInputBoundSelectorAutoUpdate = false; + UnrealSplineResolution = 50.0f; +} + +bool +UHoudiniPublicAPIWorldInput::SetInputObjects_Implementation(const TArray& InObjects) +{ + if (bIsWorldInputBoundSelector) + { + SetErrorMessage( + TEXT("This world input is not currently configured as a bound selector (bIsWorldInputBoundSelector == false)")); + return false; + } + + return Super::SetInputObjects_Implementation(InObjects); +} + +bool +UHoudiniPublicAPIWorldInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + TArray const* const BoundSelectorObjectArray = InInput->GetBoundSelectorObjectArray(); + if (BoundSelectorObjectArray) + WorldInputBoundSelectorObjects = *BoundSelectorObjectArray; + else + WorldInputBoundSelectorObjects.Empty(); + bIsWorldInputBoundSelector = InInput->IsWorldInputBoundSelector(); + bWorldInputBoundSelectorAutoUpdate = InInput->GetWorldInputBoundSelectorAutoUpdates(); + UnrealSplineResolution = InInput->GetUnrealSplineResolution(); + + return true; +} + +bool +UHoudiniPublicAPIWorldInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + InInput->SetBoundSelectorObjectsNumber(WorldInputBoundSelectorObjects.Num()); + TArray* const BoundSelectorObjectArray = InInput->GetBoundSelectorObjectArray(); + if (BoundSelectorObjectArray) + *BoundSelectorObjectArray = WorldInputBoundSelectorObjects; + InInput->SetWorldInputBoundSelector(bIsWorldInputBoundSelector); + InInput->SetWorldInputBoundSelectorAutoUpdates(bWorldInputBoundSelectorAutoUpdate); + InInput->SetUnrealSplineResolution(UnrealSplineResolution); + InInput->MarkChanged(true); + + return true; +} + + +UHoudiniPublicAPILandscapeInput::UHoudiniPublicAPILandscapeInput() + : bUpdateInputLandscape(false) + , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) + , bLandscapeExportSelectionOnly(false) + , bLandscapeAutoSelectComponent(false) + , bLandscapeExportMaterials(false) + , bLandscapeExportLighting(false) + , bLandscapeExportNormalizedUVs(false) + , bLandscapeExportTileUVs(false) +{ + +} + +bool +UHoudiniPublicAPILandscapeInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + bUpdateInputLandscape = InInput->bUpdateInputLandscape; + LandscapeExportType = InInput->GetLandscapeExportType(); + bLandscapeExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; + bLandscapeAutoSelectComponent = InInput->bLandscapeAutoSelectComponent; + bLandscapeExportMaterials = InInput->bLandscapeExportMaterials; + bLandscapeExportLighting = InInput->bLandscapeExportLighting; + bLandscapeExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; + bLandscapeExportTileUVs = InInput->bLandscapeExportTileUVs; + + return true; +} + +bool +UHoudiniPublicAPILandscapeInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + bool bAnyChanges = false; + if (InInput->bUpdateInputLandscape != bUpdateInputLandscape) + { + InInput->bUpdateInputLandscape = bUpdateInputLandscape; + bAnyChanges = true; + } + + if (InInput->GetLandscapeExportType() != LandscapeExportType) + { + InInput->SetLandscapeExportType(LandscapeExportType); + InInput->SetHasLandscapeExportTypeChanged(true); + + // Mark each input object as changed as well + TArray* LandscapeInputObjectsArray = InInput->GetHoudiniInputObjectArray(GetInputType()); + if (LandscapeInputObjectsArray) + { + for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) + { + if (!NextInputObj) + continue; + NextInputObj->MarkChanged(true); + } + } + + bAnyChanges = true; + } + + if (InInput->bLandscapeExportSelectionOnly != bLandscapeExportSelectionOnly) + { + InInput->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; + bAnyChanges = true; + } + + if (InInput->bLandscapeAutoSelectComponent != bLandscapeAutoSelectComponent) + { + InInput->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportMaterials != bLandscapeExportMaterials) + { + InInput->bLandscapeExportMaterials = bLandscapeExportMaterials; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportLighting != bLandscapeExportLighting) + { + InInput->bLandscapeExportLighting = bLandscapeExportLighting; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportNormalizedUVs != bLandscapeExportNormalizedUVs) + { + InInput->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportTileUVs != bLandscapeExportTileUVs) + { + InInput->bLandscapeExportTileUVs = bLandscapeExportTileUVs; + bAnyChanges = true; + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + + return true; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp new file mode 100644 index 000000000..caacedd09 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp @@ -0,0 +1,79 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniPublicAPIObjectBase.h" + +UHoudiniPublicAPIObjectBase::UHoudiniPublicAPIObjectBase() + : LastErrorMessage() + , bHasError(false) + , bIsLoggingErrors(true) +{ + +} + +bool +UHoudiniPublicAPIObjectBase::GetLastErrorMessage_Implementation(FString& OutLastErrorMessage) const +{ + if (!bHasError) + { + OutLastErrorMessage = FString(); + return false; + } + + OutLastErrorMessage = LastErrorMessage; + return true; +} + +void +UHoudiniPublicAPIObjectBase::ClearErrorMessages_Implementation() +{ + LastErrorMessage = FString(); + bHasError = false; +} + +void +UHoudiniPublicAPIObjectBase::SetErrorMessage_Implementation( + const FString& InErrorMessage, + const EHoudiniPublicAPIErrorLogOption InLoggingOption) const +{ + LastErrorMessage = InErrorMessage; + bHasError = true; + switch (InLoggingOption) + { + case EHoudiniPublicAPIErrorLogOption::Invalid: + case EHoudiniPublicAPIErrorLogOption::Auto: + case EHoudiniPublicAPIErrorLogOption::Log: + { + static const FString Prefix = TEXT("[HoudiniEngine:PublicAPI]"); + HOUDINI_LOG_WARNING(TEXT("%s %s"), *Prefix, *InErrorMessage); + break; + } + case EHoudiniPublicAPIErrorLogOption::NoLog: + // Don't log + break; + } +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp new file mode 100644 index 000000000..faf41bbce --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp @@ -0,0 +1,49 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIOutputTypes.h" + +FHoudiniPublicAPIOutputObjectIdentifier::FHoudiniPublicAPIOutputObjectIdentifier() + : SplitIdentifier() + , PartName() + , Identifier() +{ +} + +FHoudiniPublicAPIOutputObjectIdentifier::FHoudiniPublicAPIOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) + : SplitIdentifier() + , PartName() +{ + SetIdentifier(InIdentifier); +} + +void +FHoudiniPublicAPIOutputObjectIdentifier::SetIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + Identifier = InIdentifier; + SplitIdentifier = Identifier.SplitIdentifier; + PartName = Identifier.PartName; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp new file mode 100644 index 000000000..22f1f42c2 --- /dev/null +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp @@ -0,0 +1,314 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIProcessHDANode.h" + +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPIAssetWrapper.h" +#include "HoudiniPublicAPIInputTypes.h" + + +UHoudiniPublicAPIProcessHDANode::UHoudiniPublicAPIProcessHDANode(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + if ( HasAnyFlags(RF_ClassDefaultObject) == false ) + { + AddToRoot(); + } + + AssetWrapper = nullptr; + bCookSuccess = false; + bBakeSuccess = false; + + HoudiniAsset = nullptr; + InstantiateAt = FTransform::Identity; + WorldContextObject = nullptr; + SpawnInLevelOverride = nullptr; + bEnableAutoCook = true; + bEnableAutoBake = false; + BakeDirectoryPath = FString(); + BakeMethod = EHoudiniEngineBakeOption::ToActor; + bRemoveOutputAfterBake = false; + bRecenterBakedActors = false; + bReplacePreviousBake = false; + bDeleteInstantiatedAssetOnCompletionOrFailure = false; +} + +UHoudiniPublicAPIProcessHDANode* +UHoudiniPublicAPIProcessHDANode::ProcessHDA( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + const TMap& InParameters, + const TMap& InNodeInputs, + const TMap& InParameterInputs, + UObject* InWorldContextObject, + ULevel* InSpawnInLevelOverride, + const bool bInEnableAutoCook, + const bool bInEnableAutoBake, + const FString& InBakeDirectoryPath, + const EHoudiniEngineBakeOption InBakeMethod, + const bool bInRemoveOutputAfterBake, + const bool bInRecenterBakedActors, + const bool bInReplacePreviousBake, + const bool bInDeleteInstantiatedAssetOnCompletionOrFailure) +{ + UHoudiniPublicAPIProcessHDANode* Node = NewObject(); + + Node->HoudiniAsset = InHoudiniAsset; + Node->InstantiateAt = InInstantiateAt; + Node->Parameters = InParameters; + Node->NodeInputs = InNodeInputs; + Node->ParameterInputs = InParameterInputs; + Node->WorldContextObject = InWorldContextObject; + Node->SpawnInLevelOverride = InSpawnInLevelOverride; + Node->bEnableAutoCook = bInEnableAutoCook; + Node->bEnableAutoBake = bInEnableAutoBake; + Node->BakeDirectoryPath = InBakeDirectoryPath; + Node->BakeMethod = InBakeMethod; + Node->bRemoveOutputAfterBake = bInRemoveOutputAfterBake; + Node->bRecenterBakedActors = bInRecenterBakedActors; + Node->bReplacePreviousBake = bInReplacePreviousBake; + Node->bDeleteInstantiatedAssetOnCompletionOrFailure = bInDeleteInstantiatedAssetOnCompletionOrFailure; + + return Node; +} + + +void +UHoudiniPublicAPIProcessHDANode::Activate() +{ + UHoudiniPublicAPI* API = UHoudiniPublicAPIBlueprintLib::GetAPI(); + if (!IsValid(API)) + { + HandleFailure(); + return; + } + + AssetWrapper = UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(API); + if (!IsValid(AssetWrapper)) + { + HandleFailure(); + return; + } + + AssetWrapper->GetOnPreInstantiationDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation); + AssetWrapper->GetOnPostInstantiationDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation); + AssetWrapper->GetOnPostCookDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook); + AssetWrapper->GetOnPreProcessStateExitedDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreProcess); + AssetWrapper->GetOnPostProcessingDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostProcessing); + AssetWrapper->GetOnPostBakeDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake); + + if (!API->InstantiateAssetWithExistingWrapper( + AssetWrapper, + HoudiniAsset, + InstantiateAt, + WorldContextObject, + SpawnInLevelOverride, + bEnableAutoCook, + bEnableAutoBake, + BakeDirectoryPath, + BakeMethod, + bRemoveOutputAfterBake, + bRecenterBakedActors, + bReplacePreviousBake)) + { + HandleFailure(); + return; + } +} + +void +UHoudiniPublicAPIProcessHDANode::UnbindDelegates() +{ + AssetWrapper->GetOnPreInstantiationDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation); + AssetWrapper->GetOnPostInstantiationDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation); + AssetWrapper->GetOnPostCookDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook); + AssetWrapper->GetOnPreProcessStateExitedDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreProcess); + AssetWrapper->GetOnPostProcessingDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostProcessing); + AssetWrapper->GetOnPostBakeDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake); +} + +void +UHoudiniPublicAPIProcessHDANode::HandleFailure() +{ + if (Failed.IsBound()) + Failed.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + UnbindDelegates(); + + RemoveFromRoot(); + + if (bDeleteInstantiatedAssetOnCompletionOrFailure && IsValid(AssetWrapper)) + AssetWrapper->DeleteInstantiatedAsset(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandleComplete() +{ + if (Completed.IsBound()) + Completed.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + UnbindDelegates(); + + RemoveFromRoot(); + + if (bDeleteInstantiatedAssetOnCompletionOrFailure && IsValid(AssetWrapper)) + AssetWrapper->DeleteInstantiatedAsset(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + // Set any parameters specified when the node was created + if (Parameters.Num() > 0 && IsValid(AssetWrapper)) + { + AssetWrapper->SetParameterTuples(Parameters); + } + + if (PreInstantiation.IsBound()) + PreInstantiation.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + // Set any inputs specified when the node was created + if (IsValid(AssetWrapper)) + { + if (NodeInputs.Num() > 0) + { + AssetWrapper->SetInputsAtIndices(NodeInputs); + } + if (ParameterInputs.Num() > 0) + { + AssetWrapper->SetInputParameters(ParameterInputs); + } + + // // Set any parameters specified when the node was created + // if (Parameters.Num() > 0) + // { + // AssetWrapper->SetParameterTuples(Parameters); + // } + } + + if (PostInstantiation.IsBound()) + PostInstantiation.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + if (!bEnableAutoCook) + HandleComplete(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInCookSuccess) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + bCookSuccess = bInCookSuccess; + + if (PostAutoCook.IsBound()) + PostAutoCook.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePreProcess(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + if (PreProcess.IsBound()) + PreProcess.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostProcessing(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + if (PostProcessing.IsBound()) + PostProcessing.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + if (!bEnableAutoBake) + HandleComplete(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInBakeSuccess) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + bBakeSuccess = bInBakeSuccess; + + if (PostAutoBake.IsBound()) + PostAutoBake.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + HandleComplete(); +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp index 2520b6b90..eb20ab799 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp @@ -1,324 +1,324 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniRuntimeSettingsDetails.h" - -#include "HoudiniApi.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimeUtils.h" - -#include "HAPI/HAPI_Version.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "Internationalization/Internationalization.h" -#include "DetailLayoutBuilder.h" -#include "DetailWidgetRow.h" -#include "Widgets/Input/SNumericEntryBox.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -TSharedRef< IDetailCustomization > -FHoudiniRuntimeSettingsDetails::MakeInstance() -{ - return MakeShareable(new FHoudiniRuntimeSettingsDetails); -} - -FHoudiniRuntimeSettingsDetails::FHoudiniRuntimeSettingsDetails() -{} - -FHoudiniRuntimeSettingsDetails::~FHoudiniRuntimeSettingsDetails() -{} - -void -FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) -{ - // Create basic categories. - DetailBuilder.EditCategory("Session", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Instantiating", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Cooking", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Parameters", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Static Mesh", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("PDG Settings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Legacy", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); - - // Create Plugin Information category. - { - static const FName InformationCategory = TEXT("Plugin Information"); - IDetailCategoryBuilder & InformationCategoryBuilder = DetailBuilder.EditCategory(InformationCategory); - - // Add built Houdini version. - CreateHoudiniEntry( - LOCTEXT("HInformationBuilt", "Built against Houdini"), - InformationCategoryBuilder, HAPI_VERSION_HOUDINI_MAJOR, HAPI_VERSION_HOUDINI_MINOR, - HAPI_VERSION_HOUDINI_BUILD, HAPI_VERSION_HOUDINI_PATCH); - - // Add built against Houdini Engine version. - CreateHoudiniEngineEntry( - LOCTEXT("HEngineInformationBuilt", "Built against Houdini Engine"), - InformationCategoryBuilder, HAPI_VERSION_HOUDINI_ENGINE_MAJOR, - HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API); - - // Add running against Houdini version. - { - int32 RunningMajor = 0; - int32 RunningMinor = 0; - int32 RunningBuild = 0; - int32 RunningPatch = 0; - - if (FHoudiniApi::IsHAPIInitialized()) - { - const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); - // Retrieve version numbers for running Houdini. - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MAJOR, &RunningMajor); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MINOR, &RunningMinor); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_BUILD, &RunningBuild); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_PATCH, &RunningPatch); - } - - CreateHoudiniEntry( - LOCTEXT("HInformationRunning", "Running against Houdini"), - InformationCategoryBuilder, RunningMajor, RunningMinor, RunningBuild, RunningPatch); - } - - // Add running against Houdini Engine version. - { - int32 RunningEngineMajor = 0; - int32 RunningEngineMinor = 0; - int32 RunningEngineApi = 0; - - if (FHoudiniApi::IsHAPIInitialized()) - { - 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); - } - - CreateHoudiniEngineEntry( - LOCTEXT("HEngineInformationRunning", "Running against Houdini Engine"), - InformationCategoryBuilder, RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - } - - // Add path of libHAPI. - { - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - if (LibHAPILocation.IsEmpty()) - LibHAPILocation = TEXT("Not Found"); - - CreateHAPILibraryPathEntry(LibHAPILocation, InformationCategoryBuilder); - } - - // Add licensing info. - { - FString HAPILicenseType = TEXT(""); - if (!FHoudiniEngineUtils::GetLicenseType(HAPILicenseType)) - HAPILicenseType = TEXT("Unknown"); - - CreateHAPILicenseEntry(HAPILicenseType, InformationCategoryBuilder); - } - } -} - -void -FHoudiniRuntimeSettingsDetails::CreateHoudiniEntry( - const FText & EntryName, - IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, - int32 VersionPatch) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(EntryName) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMajor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMinor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionBuild) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionPatch) - ]; - NumericEntryBox->SetEnabled(false); - } - - Row.ValueWidget.Widget = HorizontalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHoudiniEngineEntry( - const FText & EntryName, - IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionApi) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(EntryName) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMajor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMinor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionApi) - ]; - NumericEntryBox->SetEnabled(false); - } - - Row.ValueWidget.Widget = HorizontalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHAPILibraryPathEntry( - const FString & LibHAPIPath, - IDetailCategoryBuilder & DetailCategoryBuilder) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - FString LibHAPIName = FString::Printf(TEXT("Location of %s"), *FHoudiniEngineRuntimeUtils::GetLibHAPIName()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(FText::FromString(LibHAPIName)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef TextBlock = - SNew(STextBlock) - .Text(FText::FromString(LibHAPIPath)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TextBlock->SetEnabled(false); - Row.ValueWidget.Widget = TextBlock; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHAPILicenseEntry( - const FString & LibHAPILicense, - IDetailCategoryBuilder & DetailCategoryBuilder) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - FString LibHAPILicenseTypeText = TEXT("Acquired License Type"); - - Row.NameWidget.Widget = SNew(STextBlock) - .Text(FText::FromString(LibHAPILicenseTypeText)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef TextBlock = SNew(STextBlock) - .Text(FText::FromString(LibHAPILicense)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TextBlock->SetEnabled(false); - Row.ValueWidget.Widget = TextBlock; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniRuntimeSettingsDetails.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimeUtils.h" + +#include "HAPI/HAPI_Version.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "Internationalization/Internationalization.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SNumericEntryBox.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +TSharedRef< IDetailCustomization > +FHoudiniRuntimeSettingsDetails::MakeInstance() +{ + return MakeShareable(new FHoudiniRuntimeSettingsDetails); +} + +FHoudiniRuntimeSettingsDetails::FHoudiniRuntimeSettingsDetails() +{} + +FHoudiniRuntimeSettingsDetails::~FHoudiniRuntimeSettingsDetails() +{} + +void +FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) +{ + // Create basic categories. + DetailBuilder.EditCategory("Session", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Instantiating", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Cooking", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Parameters", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Static Mesh", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("PDG Settings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Legacy", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); + + // Create Plugin Information category. + { + static const FName InformationCategory = TEXT("Plugin Information"); + IDetailCategoryBuilder & InformationCategoryBuilder = DetailBuilder.EditCategory(InformationCategory); + + // Add built Houdini version. + CreateHoudiniEntry( + LOCTEXT("HInformationBuilt", "Built against Houdini"), + InformationCategoryBuilder, HAPI_VERSION_HOUDINI_MAJOR, HAPI_VERSION_HOUDINI_MINOR, + HAPI_VERSION_HOUDINI_BUILD, HAPI_VERSION_HOUDINI_PATCH); + + // Add built against Houdini Engine version. + CreateHoudiniEngineEntry( + LOCTEXT("HEngineInformationBuilt", "Built against Houdini Engine"), + InformationCategoryBuilder, HAPI_VERSION_HOUDINI_ENGINE_MAJOR, + HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API); + + // Add running against Houdini version. + { + int32 RunningMajor = 0; + int32 RunningMinor = 0; + int32 RunningBuild = 0; + int32 RunningPatch = 0; + + if (FHoudiniApi::IsHAPIInitialized()) + { + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + // Retrieve version numbers for running Houdini. + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MAJOR, &RunningMajor); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MINOR, &RunningMinor); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_BUILD, &RunningBuild); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_PATCH, &RunningPatch); + } + + CreateHoudiniEntry( + LOCTEXT("HInformationRunning", "Running against Houdini"), + InformationCategoryBuilder, RunningMajor, RunningMinor, RunningBuild, RunningPatch); + } + + // Add running against Houdini Engine version. + { + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + if (FHoudiniApi::IsHAPIInitialized()) + { + 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); + } + + CreateHoudiniEngineEntry( + LOCTEXT("HEngineInformationRunning", "Running against Houdini Engine"), + InformationCategoryBuilder, RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + } + + // Add path of libHAPI. + { + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + if (LibHAPILocation.IsEmpty()) + LibHAPILocation = TEXT("Not Found"); + + CreateHAPILibraryPathEntry(LibHAPILocation, InformationCategoryBuilder); + } + + // Add licensing info. + { + FString HAPILicenseType = TEXT(""); + if (!FHoudiniEngineUtils::GetLicenseType(HAPILicenseType)) + HAPILicenseType = TEXT("Unknown"); + + CreateHAPILicenseEntry(HAPILicenseType, InformationCategoryBuilder); + } + } +} + +void +FHoudiniRuntimeSettingsDetails::CreateHoudiniEntry( + const FText & EntryName, + IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, + int32 VersionPatch) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(EntryName) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMajor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMinor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionBuild) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionPatch) + ]; + NumericEntryBox->SetEnabled(false); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHoudiniEngineEntry( + const FText & EntryName, + IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionApi) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(EntryName) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMajor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMinor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionApi) + ]; + NumericEntryBox->SetEnabled(false); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHAPILibraryPathEntry( + const FString & LibHAPIPath, + IDetailCategoryBuilder & DetailCategoryBuilder) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + FString LibHAPIName = FString::Printf(TEXT("Location of %s"), *FHoudiniEngineRuntimeUtils::GetLibHAPIName()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(FText::FromString(LibHAPIName)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef TextBlock = + SNew(STextBlock) + .Text(FText::FromString(LibHAPIPath)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TextBlock->SetEnabled(false); + Row.ValueWidget.Widget = TextBlock; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHAPILicenseEntry( + const FString & LibHAPILicense, + IDetailCategoryBuilder & DetailCategoryBuilder) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + FString LibHAPILicenseTypeText = TEXT("Acquired License Type"); + + Row.NameWidget.Widget = SNew(STextBlock) + .Text(FText::FromString(LibHAPILicenseTypeText)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef TextBlock = SNew(STextBlock) + .Text(FText::FromString(LibHAPILicense)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TextBlock->SetEnabled(false); + Row.ValueWidget.Widget = TextBlock; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h index 1123bbdcf..b3aab58cc 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "DetailCategoryBuilder.h" -#include "IDetailCustomization.h" - -class FHoudiniRuntimeSettingsDetails : public IDetailCustomization -{ -public: - - /** Constructor. **/ - FHoudiniRuntimeSettingsDetails(); - - /** Destructor. **/ - virtual ~FHoudiniRuntimeSettingsDetails(); - - /** IDetailCustomization methods. **/ -public: - - virtual void CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) override; - -public: - - /** Create an instance of this detail layout class. **/ - static TSharedRef< IDetailCustomization > MakeInstance(); - -protected: - - /** Used to create Houdini version entry. **/ - void CreateHoudiniEntry( - const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, int32 VersionPatch); - - /** Used to create Houdini Engine version entry. **/ - void CreateHoudiniEngineEntry( - const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionApi); - - /** Used to create libHAPI dynamic library path entry. **/ - void CreateHAPILibraryPathEntry( - const FString & LibHAPIPath, IDetailCategoryBuilder & DetailCategoryBuilder); - - /** Used to create libHAPI license information entry. **/ - void CreateHAPILicenseEntry( - const FString & LibHAPILicense, IDetailCategoryBuilder & DetailCategoryBuilder); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" + +class FHoudiniRuntimeSettingsDetails : public IDetailCustomization +{ +public: + + /** Constructor. **/ + FHoudiniRuntimeSettingsDetails(); + + /** Destructor. **/ + virtual ~FHoudiniRuntimeSettingsDetails(); + + /** IDetailCustomization methods. **/ +public: + + virtual void CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) override; + +public: + + /** Create an instance of this detail layout class. **/ + static TSharedRef< IDetailCustomization > MakeInstance(); + +protected: + + /** Used to create Houdini version entry. **/ + void CreateHoudiniEntry( + const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, int32 VersionPatch); + + /** Used to create Houdini Engine version entry. **/ + void CreateHoudiniEngineEntry( + const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionApi); + + /** Used to create libHAPI dynamic library path entry. **/ + void CreateHAPILibraryPathEntry( + const FString & LibHAPIPath, IDetailCategoryBuilder & DetailCategoryBuilder); + + /** Used to create libHAPI license information entry. **/ + void CreateHAPILicenseEntry( + const FString & LibHAPILicense, IDetailCategoryBuilder & DetailCategoryBuilder); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp index e159cb270..5a0ddd942 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp @@ -1,1022 +1,1022 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineComponentVisualizer.h" - -#include "ActorEditorUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniApi.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInputObject.h" -#include "HoudiniInput.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniEngineUtils.h" - -#include "Editor/UnrealEdEngine.h" -#include "UnrealEdGlobals.h" -#include "ComponentVisualizerManager.h" - -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "ScopedTransaction.h" -#include "EditorViewportClient.h" -#include "Engine/Selection.h" -#include "HModel.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_HIT_PROXY(HHoudiniSplineVisProxy, HComponentVisProxy); -IMPLEMENT_HIT_PROXY(HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy); -IMPLEMENT_HIT_PROXY(HHoudiniSplineCurveSegmentVisProxy, HHoudiniSplineVisProxy); - -FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() - : TCommands< FHoudiniSplineComponentVisualizerCommands >( - "HoudiniSplineComponentVisualizer", - LOCTEXT("HoudiniSplineComponentVisualizer", "Houdini Spline Component Visualizer"), - NAME_None, - FEditorStyle::GetStyleSetName()) -{} - -void -FHoudiniSplineComponentVisualizerCommands::RegisterCommands() -{ - UI_COMMAND( - CommandAddControlPoint, "Add Control Point", "Add control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND( - CommandDuplicateControlPoint, "Duplicate Control Point", "Duplicate control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND( - CommandDeleteControlPoint, "Delete Control Point", "delete control points.", - EUserInterfaceActionType::Button, FInputChord(EKeys::Delete)); - - UI_COMMAND(CommandDeselectAllControlPoints, "Deselect All", "Deselect all control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(CommandInsertControlPoint, "Insert Control Point", "Insert a control point on curve.", - EUserInterfaceActionType::Button, FInputChord()); -} - - -FHoudiniSplineComponentVisualizer::FHoudiniSplineComponentVisualizer() - :FComponentVisualizer() - ,bAllowDuplication(false) - ,EditedCurveSegmentIndex(-1) - ,CachedRotation(FQuat::Identity) - ,CachedScale3D(FVector::OneVector) - ,bMovingPoints(false) - ,bInsertingOnCurveControlPoints(false) - ,bRecordingMovingPoints(false) -{ - FHoudiniSplineComponentVisualizerCommands::Register(); - VisualizerActions = MakeShareable(new FUICommandList); -} - -void -FHoudiniSplineComponentVisualizer::OnRegister() -{ - HOUDINI_LOG_MESSAGE(TEXT("Houdini Spline Component Visualizer Registered!")); - const auto & Commands = FHoudiniSplineComponentVisualizerCommands::Get(); - - VisualizerActions->MapAction( - Commands.CommandAddControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnAddControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsAddControlPointValid)); - - VisualizerActions->MapAction( - Commands.CommandDuplicateControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid)); - - VisualizerActions->MapAction( - Commands.CommandDeleteControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeleteControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid)); - - VisualizerActions->MapAction(Commands.CommandDeselectAllControlPoints, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid)); - - VisualizerActions->MapAction(Commands.CommandInsertControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnInsertControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsInsertControlPointValid)); -} - - -void -FHoudiniSplineComponentVisualizer::DrawVisualization( - const UActorComponent * Component, - const FSceneView * View, - FPrimitiveDrawInterface * PDI) -{ - const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >(Component); - - if (!IsValid(HoudiniSplineComponent) - || !PDI - || !HoudiniSplineComponent->IsVisible() - || !HoudiniSplineComponent->IsHoudiniSplineVisible()) - return; - - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - - // Note: Undo a transaction clears the active visualizer in ComponnetVisMangaer, which is private to Visualizer manager. - // HandleProxyForComponentVis() sets the active visualizer. So the selection will be lost after undo. - - // A Way to bypass this annoying UE4 implementation: - // If the drawing spline is the one being edited and an undo just happened, - // force to trigger a 'bubble' hit proxy to re-activate the visualizer. - if (HoudiniSplineComponent == EditedHoudiniSplineComponent && EditedHoudiniSplineComponent->bPostUndo) - { - EditedHoudiniSplineComponent->bPostUndo = false; - - FEditorViewportClient * FoundViewportClient = FindViewportClient(EditedHoudiniSplineComponent, View); - HComponentVisProxy * BubbleComponentHitProxy = new HComponentVisProxy(EditedHoudiniSplineComponent); - - if (FoundViewportClient && BubbleComponentHitProxy) - { - FViewportClick BubbleClick(View, FoundViewportClient, FKey(), EInputEvent::IE_Axis, 0, 0); - GUnrealEd->ComponentVisManager.HandleProxyForComponentVis(FoundViewportClient, BubbleComponentHitProxy, BubbleClick); - } - } - - static const FColor ColorNormal = FColor(255.f, 255.f, 255.f); - static const FColor ColorNormalHandleFirst(172.f, 255.f, 172.f); - static const FColor ColorNormalHandleSecond(254.f, 216.f, 177.f); - - static const FColor ColorSelectedHandle(255.f, 0.f, 0.f); - static const FColor ColorSelectedHandleFirst(0.f, 192.f, 0.f); - static const FColor ColorSelectedHandleSecond(255.f, 159.f, 0.f); - - static const float SizeGrabHandleSelected = 15.f; - static const float SizeGrabHandleNormalLarge = 18.f; - static const float SizeGrabHandleNormalSmall = 12.f; - - FVector PreviousPosition; - - if (HoudiniSplineComponent) - { - const FTransform & HoudiniSplineComponentTransform = HoudiniSplineComponent->GetComponentTransform(); - - const TArray< FVector > & DisplayPoints = HoudiniSplineComponent->DisplayPoints; // not used yet - const TArray< FTransform > & CurvePoints = HoudiniSplineComponent->CurvePoints; - - // Draw display points (simply linearly connect the control points for temporary) - for (int32 Index = 0; Index < DisplayPoints.Num(); ++Index) - { - const FVector & CurrentPoint = DisplayPoints[Index]; - // Fix incorrect scale when actor has been scaled - //FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); - FVector CurrentPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint); - if (Index > 0) - { - // Add a hitproxy for the line segment - PDI->SetHitProxy(new HHoudiniSplineCurveSegmentVisProxy(HoudiniSplineComponent, Index)); - // Draw a line connecting the previous point and the current point - PDI->DrawLine(PreviousPosition, CurrentPosition, ColorNormal, SDPG_Foreground); - PDI->SetHitProxy(nullptr); - } - - PreviousPosition = CurrentPosition; - } - - // Draw control points (do not draw control points if the curve is an output) - if (!HoudiniSplineComponent->bIsOutputCurve) - { - for (int32 Index = 0; Index < CurvePoints.Num(); ++Index) - { - const FVector & ControlPoint = HoudiniSplineComponentTransform.TransformPosition(CurvePoints[Index].GetLocation()); - - HHoudiniSplineControlPointVisProxy * HitProxy = new HHoudiniSplineControlPointVisProxy(HoudiniSplineComponent, Index); - PDI->SetHitProxy(HitProxy); - - FColor DrawColor = ColorNormal; - float DrawSize = SizeGrabHandleNormalSmall; - - if (Index == 0) - { - DrawColor = ColorNormalHandleFirst; - DrawSize = SizeGrabHandleNormalLarge; - } - - if (Index == 1) - DrawColor = ColorNormalHandleSecond; - - // If this is an point that being editted - if (EditedHoudiniSplineComponent == HoudiniSplineComponent && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Contains(Index)) - { - if (Index == 0) - { - DrawColor = ColorSelectedHandleFirst; - } - - else if (Index == 1) - { - DrawColor = ColorSelectedHandleSecond; - DrawSize = SizeGrabHandleSelected; - } - - else - { - DrawColor = ColorSelectedHandle; - DrawSize = SizeGrabHandleSelected; - - } - } - - PDI->DrawPoint(ControlPoint, DrawColor, DrawSize, SDPG_Foreground); - PDI->SetHitProxy(nullptr); - } - - } - } -} - - -bool -FHoudiniSplineComponentVisualizer::VisProxyHandleClick( - FEditorViewportClient* InViewportClient, - HComponentVisProxy* VisProxy, - const FViewportClick& Click) -{ - if (!InViewportClient || !VisProxy || !VisProxy->Component.IsValid()) - return false; - - const UHoudiniSplineComponent * HoudiniSplineComponent = CastChecked< const UHoudiniSplineComponent >(VisProxy->Component.Get()); - - AActor* OldSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); - SplinePropertyPath = FComponentPropertyPath(HoudiniSplineComponent); - AActor* NewSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); - - if (!SplinePropertyPath.IsValid()) - { - SplinePropertyPath.Reset(); - return false; - } - - if (OldSplineOwningActor != NewSplineOwningActor) - { - // Reset selection state if we are selecting a different actor to the one previously selected - EditedCurveSegmentIndex = INDEX_NONE; - } - - // Note: This is for re-activating the component visualizer an undo. - // Return true if the hit proxy is a bubble (Neither HHoudiniSplineControlPointVisProxy nor HHoudiniSplineCurveSegmentVisProxy ) - // - if (!VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType()) && !VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) - return true; - - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - - EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); - - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - bool editingCurve = false; - - // If VisProxy is a HHoudiniSplineControlPointVisProxy - if (VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType())) - { - HHoudiniSplineControlPointVisProxy * ControlPointProxy = (HHoudiniSplineControlPointVisProxy*)VisProxy; - - if (!ControlPointProxy) - return editingCurve; - - editingCurve = true; - - // Clear the edited curve segment if a control point is clicked. - EditedCurveSegmentIndex = -1; - - if (Click.GetKey() != EKeys::LeftMouseButton) - return editingCurve; - - - if (InViewportClient->IsCtrlPressed()) - { - if (EditedControlPointsIndexes.Contains(ControlPointProxy->ControlPointIndex)) - { - EditedControlPointsIndexes.Remove(ControlPointProxy->ControlPointIndex); - } - else - { - EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); - } - } - else - { - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); - } - } - // VisProxy is a HHoudiniSplineCurveSegmentProxy - else if (VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) - { - //HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = Cast(VisProxy); - - HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = (HHoudiniSplineCurveSegmentVisProxy*)(VisProxy); - - if (!CurveSegmentProxy) - return false; - - editingCurve = true; - - if (Click.GetKey() == EKeys::LeftMouseButton && InViewportClient->IsAltPressed() && EditedHoudiniSplineComponent) - { - // Continuesly (Alt) inserting on-curve control points is only valid with Breakpoints mode, otherwise it has to be on linear curve type. - if (EditedHoudiniSplineComponent->CurveType != EHoudiniCurveType::Polygon && EditedHoudiniSplineComponent->CurveMethod != EHoudiniCurveMethod::Breakpoints) - return editingCurve; - - bInsertingOnCurveControlPoints = true; - - editingCurve = true; - EditedControlPointsIndexes.Empty(); - - EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; - int32 InsertedIndex = OnInsertControlPointWithoutUpdate(); - - if (InsertedIndex < 0) return false; - EditedControlPointsIndexes.Add(InsertedIndex); - - EditedCurveSegmentIndex = -1; - bInsertingOnCurveControlPoints = true; - - RefreshViewport(); - } - // Insert one on-curve control point. - else - { - EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; - return editingCurve; - } - } - - return editingCurve; -} - -bool -FHoudiniSplineComponentVisualizer::HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - if (Key == EKeys::Enter) - { - EditedHoudiniSplineComponent->MarkChanged(true); - - return true; - } - - bool bHandled = false; - - if (Key == EKeys::LeftMouseButton) - { - if (Event == IE_Pressed) - { - bMovingPoints = true; // Started moving points when the left mouse button is pressed - bAllowDuplication = true; - bRecordingMovingPoints = false; - } - - if (Event == IE_Released) - { - bMovingPoints = false; // Stopped moving points when the left mouse button is released - bAllowDuplication = false; - - if (bRecordingMovingPoints) - { - // Only mark the component as changed if a point was actually moved otherwise it will - // cook even if a point was selected. - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - } - - bRecordingMovingPoints = false; // allow recording pt moving again - } - } - - - if (Key == EKeys::Delete) - { - if (Event == IE_Pressed) return true; - - if (IsDeleteControlPointValid()) - { - OnDeleteControlPoint(); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - return true; - } - } - - - if (Event == IE_Pressed && VisualizerActions) - { - if (FSlateApplication::IsInitialized()) - bHandled = VisualizerActions->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), false); - } - - RefreshViewport(); - - return bHandled; -} - -void -FHoudiniSplineComponentVisualizer::EndEditing() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - // Clear edited spline if the EndEditing() function is not called from postUndo - if (!EditedHoudiniSplineComponent->bPostUndo) - { - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); - - EditedHoudiniSplineComponent = nullptr; - EditedCurveSegmentIndex = -1; - } - - //RefreshViewport(); -} - -bool -FHoudiniSplineComponentVisualizer::GetWidgetLocation( - const FEditorViewportClient* ViewportClient, - FVector& OutLocation) const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - const TArray& CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - // Set the widget location to the center of mass of the selected control points - int32 Sum = 0; - FVector CenterLocation = FVector::ZeroVector; - for (int32 EditedIdx = 0; EditedIdx < EditedControlPointsIndexes.Num(); EditedIdx++) - { - if (!CurvePoints.IsValidIndex(EditedIdx)) - continue; - - CenterLocation += CurvePoints[EditedControlPointsIndexes[EditedIdx]].GetLocation(); - Sum++; - } - - if(Sum > 0) - CenterLocation /= Sum; - - OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(CenterLocation); - - return true; -} - -bool -FHoudiniSplineComponentVisualizer::IsVisualizingArchetype() const -{ - UHoudiniSplineComponent* SplineComp = GetEditedHoudiniSplineComponent(); - return (SplineComp && SplineComp->GetOwner() && FActorEditorUtils::IsAPreviewOrInactiveActor(SplineComp->GetOwner())); -} - -bool -FHoudiniSplineComponentVisualizer::HandleInputDelta( - FEditorViewportClient* ViewportClient, - FViewport* Viewport, - FVector& DeltaTranslate, - FRotator& DeltaRotate, - FVector& DeltaScale) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!ViewportClient || !EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - if (ViewportClient->IsAltPressed() && bAllowDuplication) - { - OnDuplicateControlPoint(); - bAllowDuplication = false; - } - else - { - if (!bRecordingMovingPoints) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentMovingPointsTransaction", "Houdini Spline Component: Moving curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - - EditedHoudiniSplineComponent->Modify(); - - bRecordingMovingPoints = true; - } - } - - - TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - const FTransform & HoudiniSplineComponentTransform = EditedHoudiniSplineComponent->GetComponentTransform(); - - for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i ) - { - - FTransform CurrentPoint = EditedHoudiniSplineComponent->CurvePoints[EditedControlPointsIndexes[i]]; - - if (!DeltaTranslate.IsZero()) - { - FVector OldWorldPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint.GetLocation()); - FVector NewWorldPosition = OldWorldPosition + DeltaTranslate; - FVector NewLocalPosition = HoudiniSplineComponentTransform.InverseTransformPosition(NewWorldPosition); - CurrentPoint.SetLocation( NewLocalPosition ); - } - - if (!DeltaRotate.IsZero()) - { - FQuat OldWorldRotation = HoudiniSplineComponentTransform.GetRotation() * CurrentPoint.GetRotation(); - FQuat NewWorldRotation = DeltaRotate.Quaternion() * OldWorldRotation; - FQuat NewLocalRotation = HoudiniSplineComponentTransform.GetRotation().Inverse() * NewWorldRotation; - CurrentPoint.SetRotation(NewLocalRotation); - } - - if (!DeltaScale.IsZero()) - { - FVector NewScale = CurrentPoint.GetScale3D() * (FVector(1.f, 1.f, 1.f) + DeltaScale); - CurrentPoint.SetScale3D(NewScale); - } - - - EditedHoudiniSplineComponent->EditPointAtindex(CurrentPoint, EditedControlPointsIndexes[i]); - } - - RefreshViewport(); - - return true; -} - -TSharedPtr -FHoudiniSplineComponentVisualizer::GenerateContextMenu() const -{ - FHoudiniEngineEditor& HoudiniEngineEditor = FHoudiniEngineEditor::Get(); - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - FMenuBuilder MenuBuilder(true, VisualizerActions); - MenuBuilder.BeginSection("Houdini Spline actions"); - - // Create the context menu section - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - { - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, - NAME_None, TAttribute(), TAttribute(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDuplicateControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDeleteControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDeselectAllControlPoints, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandInsertControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - } - - MenuBuilder.EndSection(); - TSharedPtr MenuWidget = MenuBuilder.MakeWidget(); - return MenuWidget; -} - -// Used by alt-pressed on-curve control port insertion. -// We don't want it to be cooked before finishing editing. -// * Need to call WaitForHoudiniInputUpdate() after done. -int32 -FHoudiniSplineComponentVisualizer::OnInsertControlPointWithoutUpdate() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return -1; - - TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - TArray & DisplayPoints = EditedHoudiniSplineComponent->DisplayPoints; - - if (EditedCurveSegmentIndex >= DisplayPoints.Num()) - return -1; - - // ... // - int InsertAfterIndex = 0; - - TArray & DisplayPointIndexDivider = EditedHoudiniSplineComponent->DisplayPointIndexDivider; - for (int itr = 0; itr < DisplayPointIndexDivider.Num(); ++itr) - { - if (DisplayPointIndexDivider[itr] >= EditedCurveSegmentIndex) - { - InsertAfterIndex = itr; - break; - } - } - // ... // - - if (InsertAfterIndex >= CurvePoints.Num()) return -1; - - FTransform NewPoint = CurvePoints[InsertAfterIndex]; - NewPoint.SetLocation(DisplayPoints[EditedCurveSegmentIndex]); - // To Do: Should interpolate the rotation and scale as well here. - // ... - - // Insert new control point on curve, and add it to selected CP. - int32 NewPointIndex = AddControlPointAfter(NewPoint, InsertAfterIndex); - - // Don't have to reconstruct the index divider each time. - //EditedHoudiniSplineComponent->Construct(EditedHoudiniSplineComponent->DisplayPoints); - EditedHoudiniSplineComponent->DisplayPointIndexDivider.Insert(EditedCurveSegmentIndex, InsertAfterIndex); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - return NewPointIndex; -} - -void -FHoudiniSplineComponentVisualizer::OnInsertControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - int32 NewPointIndex = OnInsertControlPointWithoutUpdate(); - - if (NewPointIndex < 0) return; - - - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Add(NewPointIndex); - - RefreshViewport(); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); -} - -bool -FHoudiniSplineComponentVisualizer::IsInsertControlPointValid() const -{ - return EditedCurveSegmentIndex >= 0; -} - -void -FHoudiniSplineComponentVisualizer::OnAddControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentInsertingPointsTransaction", "Houdini Spline Component: Inserting curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - - EditedHoudiniSplineComponent->Modify(); - - EditedControlPointsIndexes.Sort(); - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - TArray tNewSelectedPoints; - - if (EditedControlPointsIndexes.Num() == 1) - { - FTransform Point = CurvePoints[EditedControlPointsIndexes[0]]; - FTransform NewTransform = FTransform::Identity; - FVector Location = Point.GetLocation(); - //FQuat Rotation = Point.GetRotation(); - //FVector Scale = Point.GetScale3D(); - - NewTransform.SetLocation(Location + 1.f); - //NewTransform.SetRotation(Rotation); - //NewTransform.SetScale3D(Scale); - - - - int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[0]); - tNewSelectedPoints.Add(NewPointIndex); - } - else - { - int IndexIncrement = 0; - int CurrentPointIndex, LastPointIndex; - FTransform CurrentPoint, LastPoint; - - for (int32 n = 0; n < EditedControlPointsIndexes.Num(); ++n) - { - // Insert a new point between each adjacent pair of points - if (n > 0) - { - CurrentPointIndex = EditedControlPointsIndexes[n]; - LastPointIndex = EditedControlPointsIndexes[n - 1]; - CurrentPoint = CurvePoints[CurrentPointIndex + IndexIncrement]; - LastPoint = CurvePoints[LastPointIndex + IndexIncrement]; - - // Insert a point in the middle of LastPoint and CurrentPoint - FVector NewPointLocation = LastPoint.GetLocation() + (CurrentPoint.GetLocation() - LastPoint.GetLocation()) / 2.f; - FVector NewPointScale = LastPoint.GetScale3D() + (CurrentPoint.GetScale3D() - LastPoint.GetScale3D()) / 2.f; - FQuat NewPointRotation = FQuat::Slerp(LastPoint.GetRotation(), CurrentPoint.GetRotation(), .5f); - - FTransform NewTransform = FTransform::Identity; - NewTransform.SetLocation(NewPointLocation); - NewTransform.SetScale3D(NewPointScale); - NewTransform.SetRotation(NewPointRotation); - - int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[n - 1] + IndexIncrement); - tNewSelectedPoints.Add(NewPointIndex); - - - IndexIncrement += 1; - } - } - } - - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes = tNewSelectedPoints; - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - RefreshViewport(); -} - - -bool -FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - return EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill() && - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; -} - -void -FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentDeletingPointsTransaction", "Houdini Spline Component: Deleting curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - EditedHoudiniSplineComponent->Modify(); - - EditedControlPointsIndexes.Sort(); - - int32 SelectedIndexAfterDelete = EditedControlPointsIndexes[0] - 1; - SelectedIndexAfterDelete = FMath::Max(SelectedIndexAfterDelete, 0); - - for (int32 n = EditedControlPointsIndexes.Num() - 1; n >= 0; --n) - { - int32 RemoveIndex = EditedControlPointsIndexes[n]; - EditedHoudiniSplineComponent->RemovePointAtIndex(RemoveIndex); - - } - - EditedControlPointsIndexes.Empty(); - OnDeselectAllControlPoints(); - EditedControlPointsIndexes.Add(SelectedIndexAfterDelete); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - // Force refresh the viewport after deleting points to ensure the consistency of HitProxy - RefreshViewport(); - -} - -bool -FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - // We only allow the number of Control Points is at least 2 after delete - if (EditedHoudiniSplineComponent->GetCurvePointCount() - EditedControlPointsIndexes.Num() < 2) - return false; - - return true; -} - -void -FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentDuplicatingPointsTransaction", "Houdini Spline Component: Duplicating curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - EditedHoudiniSplineComponent->Modify(); - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - EditedControlPointsIndexes.Sort(); - - TArray tNewSelectedPoints; - int IncrementIndex = 0; - for (int n = 0; n < EditedControlPointsIndexes.Num(); ++n) - { - int32 IndexAfter = EditedControlPointsIndexes[n] + IncrementIndex; - FTransform CurrentPoint = CurvePoints[IndexAfter]; - if (IndexAfter == 0) - IndexAfter = -1; - int32 NewPointIndex = AddControlPointAfter(CurrentPoint, IndexAfter); - tNewSelectedPoints.Add(NewPointIndex); - IncrementIndex ++; - } - - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes = tNewSelectedPoints; - - EditedHoudiniSplineComponent->MarkModified(true); - - RefreshViewport(); -} - -bool -FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if(!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill() - || EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() == 0) - return false; - - return true; -} - -void -FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); -} - -bool -FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - return EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; - - return false; -} - -int32 -FHoudiniSplineComponentVisualizer::AddControlPointAfter( - const FTransform & NewPoint, - const int32 & nIndex) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return nIndex; - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - int32 NewControlPointIndex = nIndex + 1; - - if (NewControlPointIndex == CurvePoints.Num()) - EditedHoudiniSplineComponent->AppendPoint(NewPoint); - else - EditedHoudiniSplineComponent->InsertPointAtIndex(NewPoint, NewControlPointIndex); - - // Return the index of the inserted control point - return NewControlPointIndex; -} - -void -FHoudiniSplineComponentVisualizer::RefreshViewport() -{ - if (GEditor) - GEditor->RedrawLevelEditingViewports(true); -} - -// Find the EditorViewportClient of the viewport where the Houdini Spline Component lives in -FEditorViewportClient * -FHoudiniSplineComponentVisualizer::FindViewportClient( - const UHoudiniSplineComponent * InHoudiniSplineComponent, - const FSceneView * View) -{ - if (!View || !InHoudiniSplineComponent) - return nullptr; - - UWorld * World = InHoudiniSplineComponent->GetWorld(); - uint32 ViewKey = View->GetViewKey(); - - const TArray & AllViewportClients = GUnrealEd->GetAllViewportClients(); - - for (auto & NextViewportClient : AllViewportClients) - { - if (!NextViewportClient) - continue; - - if (NextViewportClient->GetWorld() != World) - continue; - - // Found the viewport client which matches the unique key of the current scene view - if (NextViewportClient->ViewState.GetReference()->GetViewKey() == ViewKey) - return NextViewportClient; - } - - return nullptr; -} - -bool -FHoudiniSplineComponentVisualizer::IsCookOnCurveChanged(UHoudiniSplineComponent * InHoudiniSplineComponent) -{ - if (!InHoudiniSplineComponent) - return true; - - return InHoudiniSplineComponent->bCookOnCurveChanged; - - // UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - // UHoudiniInputObject * InputObject = Cast(EditedHoudiniSplineComponent->GetOuter()); - // if (!InputObject) - // return true; - // - // UHoudiniInput * Input = Cast(InputObject->GetOuter()); - // - // if (!Input) - // return true; - // - // return Input->GetCookOnCurveChange(); -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineComponentVisualizer.h" + +#include "ActorEditorUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniApi.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInputObject.h" +#include "HoudiniInput.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniEngineUtils.h" + +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" +#include "ComponentVisualizerManager.h" + +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "ScopedTransaction.h" +#include "EditorViewportClient.h" +#include "Engine/Selection.h" +#include "HModel.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_HIT_PROXY(HHoudiniSplineVisProxy, HComponentVisProxy); +IMPLEMENT_HIT_PROXY(HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy); +IMPLEMENT_HIT_PROXY(HHoudiniSplineCurveSegmentVisProxy, HHoudiniSplineVisProxy); + +FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() + : TCommands< FHoudiniSplineComponentVisualizerCommands >( + "HoudiniSplineComponentVisualizer", + LOCTEXT("HoudiniSplineComponentVisualizer", "Houdini Spline Component Visualizer"), + NAME_None, + FEditorStyle::GetStyleSetName()) +{} + +void +FHoudiniSplineComponentVisualizerCommands::RegisterCommands() +{ + UI_COMMAND( + CommandAddControlPoint, "Add Control Point", "Add control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND( + CommandDuplicateControlPoint, "Duplicate Control Point", "Duplicate control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND( + CommandDeleteControlPoint, "Delete Control Point", "delete control points.", + EUserInterfaceActionType::Button, FInputChord(EKeys::Delete)); + + UI_COMMAND(CommandDeselectAllControlPoints, "Deselect All", "Deselect all control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(CommandInsertControlPoint, "Insert Control Point", "Insert a control point on curve.", + EUserInterfaceActionType::Button, FInputChord()); +} + + +FHoudiniSplineComponentVisualizer::FHoudiniSplineComponentVisualizer() + :FComponentVisualizer() + ,bAllowDuplication(false) + ,EditedCurveSegmentIndex(-1) + ,CachedRotation(FQuat::Identity) + ,CachedScale3D(FVector::OneVector) + ,bMovingPoints(false) + ,bInsertingOnCurveControlPoints(false) + ,bRecordingMovingPoints(false) +{ + FHoudiniSplineComponentVisualizerCommands::Register(); + VisualizerActions = MakeShareable(new FUICommandList); +} + +void +FHoudiniSplineComponentVisualizer::OnRegister() +{ + HOUDINI_LOG_MESSAGE(TEXT("Houdini Spline Component Visualizer Registered!")); + const auto & Commands = FHoudiniSplineComponentVisualizerCommands::Get(); + + VisualizerActions->MapAction( + Commands.CommandAddControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnAddControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsAddControlPointValid)); + + VisualizerActions->MapAction( + Commands.CommandDuplicateControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid)); + + VisualizerActions->MapAction( + Commands.CommandDeleteControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeleteControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid)); + + VisualizerActions->MapAction(Commands.CommandDeselectAllControlPoints, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid)); + + VisualizerActions->MapAction(Commands.CommandInsertControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnInsertControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsInsertControlPointValid)); +} + + +void +FHoudiniSplineComponentVisualizer::DrawVisualization( + const UActorComponent * Component, + const FSceneView * View, + FPrimitiveDrawInterface * PDI) +{ + const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >(Component); + + if (!IsValid(HoudiniSplineComponent) + || !PDI + || !HoudiniSplineComponent->IsVisible() + || !HoudiniSplineComponent->IsHoudiniSplineVisible()) + return; + + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + + // Note: Undo a transaction clears the active visualizer in ComponnetVisMangaer, which is private to Visualizer manager. + // HandleProxyForComponentVis() sets the active visualizer. So the selection will be lost after undo. + + // A Way to bypass this annoying UE4 implementation: + // If the drawing spline is the one being edited and an undo just happened, + // force to trigger a 'bubble' hit proxy to re-activate the visualizer. + if (HoudiniSplineComponent == EditedHoudiniSplineComponent && EditedHoudiniSplineComponent->bPostUndo) + { + EditedHoudiniSplineComponent->bPostUndo = false; + + FEditorViewportClient * FoundViewportClient = FindViewportClient(EditedHoudiniSplineComponent, View); + HComponentVisProxy * BubbleComponentHitProxy = new HComponentVisProxy(EditedHoudiniSplineComponent); + + if (FoundViewportClient && BubbleComponentHitProxy) + { + FViewportClick BubbleClick(View, FoundViewportClient, FKey(), EInputEvent::IE_Axis, 0, 0); + GUnrealEd->ComponentVisManager.HandleProxyForComponentVis(FoundViewportClient, BubbleComponentHitProxy, BubbleClick); + } + } + + static const FColor ColorNormal = FColor(255.f, 255.f, 255.f); + static const FColor ColorNormalHandleFirst(172.f, 255.f, 172.f); + static const FColor ColorNormalHandleSecond(254.f, 216.f, 177.f); + + static const FColor ColorSelectedHandle(255.f, 0.f, 0.f); + static const FColor ColorSelectedHandleFirst(0.f, 192.f, 0.f); + static const FColor ColorSelectedHandleSecond(255.f, 159.f, 0.f); + + static const float SizeGrabHandleSelected = 15.f; + static const float SizeGrabHandleNormalLarge = 18.f; + static const float SizeGrabHandleNormalSmall = 12.f; + + FVector PreviousPosition; + + if (HoudiniSplineComponent) + { + const FTransform & HoudiniSplineComponentTransform = HoudiniSplineComponent->GetComponentTransform(); + + const TArray< FVector > & DisplayPoints = HoudiniSplineComponent->DisplayPoints; // not used yet + const TArray< FTransform > & CurvePoints = HoudiniSplineComponent->CurvePoints; + + // Draw display points (simply linearly connect the control points for temporary) + for (int32 Index = 0; Index < DisplayPoints.Num(); ++Index) + { + const FVector & CurrentPoint = DisplayPoints[Index]; + // Fix incorrect scale when actor has been scaled + //FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); + FVector CurrentPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint); + if (Index > 0) + { + // Add a hitproxy for the line segment + PDI->SetHitProxy(new HHoudiniSplineCurveSegmentVisProxy(HoudiniSplineComponent, Index)); + // Draw a line connecting the previous point and the current point + PDI->DrawLine(PreviousPosition, CurrentPosition, ColorNormal, SDPG_Foreground); + PDI->SetHitProxy(nullptr); + } + + PreviousPosition = CurrentPosition; + } + + // Draw control points (do not draw control points if the curve is an output) + if (!HoudiniSplineComponent->bIsOutputCurve) + { + for (int32 Index = 0; Index < CurvePoints.Num(); ++Index) + { + const FVector & ControlPoint = HoudiniSplineComponentTransform.TransformPosition(CurvePoints[Index].GetLocation()); + + HHoudiniSplineControlPointVisProxy * HitProxy = new HHoudiniSplineControlPointVisProxy(HoudiniSplineComponent, Index); + PDI->SetHitProxy(HitProxy); + + FColor DrawColor = ColorNormal; + float DrawSize = SizeGrabHandleNormalSmall; + + if (Index == 0) + { + DrawColor = ColorNormalHandleFirst; + DrawSize = SizeGrabHandleNormalLarge; + } + + if (Index == 1) + DrawColor = ColorNormalHandleSecond; + + // If this is an point that being editted + if (EditedHoudiniSplineComponent == HoudiniSplineComponent && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Contains(Index)) + { + if (Index == 0) + { + DrawColor = ColorSelectedHandleFirst; + } + + else if (Index == 1) + { + DrawColor = ColorSelectedHandleSecond; + DrawSize = SizeGrabHandleSelected; + } + + else + { + DrawColor = ColorSelectedHandle; + DrawSize = SizeGrabHandleSelected; + + } + } + + PDI->DrawPoint(ControlPoint, DrawColor, DrawSize, SDPG_Foreground); + PDI->SetHitProxy(nullptr); + } + + } + } +} + + +bool +FHoudiniSplineComponentVisualizer::VisProxyHandleClick( + FEditorViewportClient* InViewportClient, + HComponentVisProxy* VisProxy, + const FViewportClick& Click) +{ + if (!InViewportClient || !VisProxy || !VisProxy->Component.IsValid()) + return false; + + const UHoudiniSplineComponent * HoudiniSplineComponent = CastChecked< const UHoudiniSplineComponent >(VisProxy->Component.Get()); + + AActor* OldSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); + SplinePropertyPath = FComponentPropertyPath(HoudiniSplineComponent); + AActor* NewSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); + + if (!SplinePropertyPath.IsValid()) + { + SplinePropertyPath.Reset(); + return false; + } + + if (OldSplineOwningActor != NewSplineOwningActor) + { + // Reset selection state if we are selecting a different actor to the one previously selected + EditedCurveSegmentIndex = INDEX_NONE; + } + + // Note: This is for re-activating the component visualizer an undo. + // Return true if the hit proxy is a bubble (Neither HHoudiniSplineControlPointVisProxy nor HHoudiniSplineCurveSegmentVisProxy ) + // + if (!VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType()) && !VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) + return true; + + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + + EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); + + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + bool editingCurve = false; + + // If VisProxy is a HHoudiniSplineControlPointVisProxy + if (VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType())) + { + HHoudiniSplineControlPointVisProxy * ControlPointProxy = (HHoudiniSplineControlPointVisProxy*)VisProxy; + + if (!ControlPointProxy) + return editingCurve; + + editingCurve = true; + + // Clear the edited curve segment if a control point is clicked. + EditedCurveSegmentIndex = -1; + + if (Click.GetKey() != EKeys::LeftMouseButton) + return editingCurve; + + + if (InViewportClient->IsCtrlPressed()) + { + if (EditedControlPointsIndexes.Contains(ControlPointProxy->ControlPointIndex)) + { + EditedControlPointsIndexes.Remove(ControlPointProxy->ControlPointIndex); + } + else + { + EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); + } + } + else + { + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); + } + } + // VisProxy is a HHoudiniSplineCurveSegmentProxy + else if (VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) + { + //HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = Cast(VisProxy); + + HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = (HHoudiniSplineCurveSegmentVisProxy*)(VisProxy); + + if (!CurveSegmentProxy) + return false; + + editingCurve = true; + + if (Click.GetKey() == EKeys::LeftMouseButton && InViewportClient->IsAltPressed() && EditedHoudiniSplineComponent) + { + // Continuesly (Alt) inserting on-curve control points is only valid with Breakpoints mode, otherwise it has to be on linear curve type. + if (EditedHoudiniSplineComponent->CurveType != EHoudiniCurveType::Polygon && EditedHoudiniSplineComponent->CurveMethod != EHoudiniCurveMethod::Breakpoints) + return editingCurve; + + bInsertingOnCurveControlPoints = true; + + editingCurve = true; + EditedControlPointsIndexes.Empty(); + + EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; + int32 InsertedIndex = OnInsertControlPointWithoutUpdate(); + + if (InsertedIndex < 0) return false; + EditedControlPointsIndexes.Add(InsertedIndex); + + EditedCurveSegmentIndex = -1; + bInsertingOnCurveControlPoints = true; + + RefreshViewport(); + } + // Insert one on-curve control point. + else + { + EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; + return editingCurve; + } + } + + return editingCurve; +} + +bool +FHoudiniSplineComponentVisualizer::HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + if (Key == EKeys::Enter) + { + EditedHoudiniSplineComponent->MarkChanged(true); + + return true; + } + + bool bHandled = false; + + if (Key == EKeys::LeftMouseButton) + { + if (Event == IE_Pressed) + { + bMovingPoints = true; // Started moving points when the left mouse button is pressed + bAllowDuplication = true; + bRecordingMovingPoints = false; + } + + if (Event == IE_Released) + { + bMovingPoints = false; // Stopped moving points when the left mouse button is released + bAllowDuplication = false; + + if (bRecordingMovingPoints) + { + // Only mark the component as changed if a point was actually moved otherwise it will + // cook even if a point was selected. + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + } + + bRecordingMovingPoints = false; // allow recording pt moving again + } + } + + + if (Key == EKeys::Delete) + { + if (Event == IE_Pressed) return true; + + if (IsDeleteControlPointValid()) + { + OnDeleteControlPoint(); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + return true; + } + } + + + if (Event == IE_Pressed && VisualizerActions) + { + if (FSlateApplication::IsInitialized()) + bHandled = VisualizerActions->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), false); + } + + RefreshViewport(); + + return bHandled; +} + +void +FHoudiniSplineComponentVisualizer::EndEditing() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + // Clear edited spline if the EndEditing() function is not called from postUndo + if (!EditedHoudiniSplineComponent->bPostUndo) + { + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); + + EditedHoudiniSplineComponent = nullptr; + EditedCurveSegmentIndex = -1; + } + + //RefreshViewport(); +} + +bool +FHoudiniSplineComponentVisualizer::GetWidgetLocation( + const FEditorViewportClient* ViewportClient, + FVector& OutLocation) const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + const TArray& CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + // Set the widget location to the center of mass of the selected control points + int32 Sum = 0; + FVector CenterLocation = FVector::ZeroVector; + for (int32 EditedIdx = 0; EditedIdx < EditedControlPointsIndexes.Num(); EditedIdx++) + { + if (!CurvePoints.IsValidIndex(EditedIdx)) + continue; + + CenterLocation += CurvePoints[EditedControlPointsIndexes[EditedIdx]].GetLocation(); + Sum++; + } + + if(Sum > 0) + CenterLocation /= Sum; + + OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(CenterLocation); + + return true; +} + +bool +FHoudiniSplineComponentVisualizer::IsVisualizingArchetype() const +{ + UHoudiniSplineComponent* SplineComp = GetEditedHoudiniSplineComponent(); + return (SplineComp && SplineComp->GetOwner() && FActorEditorUtils::IsAPreviewOrInactiveActor(SplineComp->GetOwner())); +} + +bool +FHoudiniSplineComponentVisualizer::HandleInputDelta( + FEditorViewportClient* ViewportClient, + FViewport* Viewport, + FVector& DeltaTranslate, + FRotator& DeltaRotate, + FVector& DeltaScale) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!ViewportClient || !EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + if (ViewportClient->IsAltPressed() && bAllowDuplication) + { + OnDuplicateControlPoint(); + bAllowDuplication = false; + } + else + { + if (!bRecordingMovingPoints) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentMovingPointsTransaction", "Houdini Spline Component: Moving curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + + EditedHoudiniSplineComponent->Modify(); + + bRecordingMovingPoints = true; + } + } + + + TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + const FTransform & HoudiniSplineComponentTransform = EditedHoudiniSplineComponent->GetComponentTransform(); + + for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i ) + { + + FTransform CurrentPoint = EditedHoudiniSplineComponent->CurvePoints[EditedControlPointsIndexes[i]]; + + if (!DeltaTranslate.IsZero()) + { + FVector OldWorldPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint.GetLocation()); + FVector NewWorldPosition = OldWorldPosition + DeltaTranslate; + FVector NewLocalPosition = HoudiniSplineComponentTransform.InverseTransformPosition(NewWorldPosition); + CurrentPoint.SetLocation( NewLocalPosition ); + } + + if (!DeltaRotate.IsZero()) + { + FQuat OldWorldRotation = HoudiniSplineComponentTransform.GetRotation() * CurrentPoint.GetRotation(); + FQuat NewWorldRotation = DeltaRotate.Quaternion() * OldWorldRotation; + FQuat NewLocalRotation = HoudiniSplineComponentTransform.GetRotation().Inverse() * NewWorldRotation; + CurrentPoint.SetRotation(NewLocalRotation); + } + + if (!DeltaScale.IsZero()) + { + FVector NewScale = CurrentPoint.GetScale3D() * (FVector(1.f, 1.f, 1.f) + DeltaScale); + CurrentPoint.SetScale3D(NewScale); + } + + + EditedHoudiniSplineComponent->EditPointAtindex(CurrentPoint, EditedControlPointsIndexes[i]); + } + + RefreshViewport(); + + return true; +} + +TSharedPtr +FHoudiniSplineComponentVisualizer::GenerateContextMenu() const +{ + FHoudiniEngineEditor& HoudiniEngineEditor = FHoudiniEngineEditor::Get(); + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + FMenuBuilder MenuBuilder(true, VisualizerActions); + MenuBuilder.BeginSection("Houdini Spline actions"); + + // Create the context menu section + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + { + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, + NAME_None, TAttribute(), TAttribute(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDuplicateControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDeleteControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDeselectAllControlPoints, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandInsertControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + } + + MenuBuilder.EndSection(); + TSharedPtr MenuWidget = MenuBuilder.MakeWidget(); + return MenuWidget; +} + +// Used by alt-pressed on-curve control port insertion. +// We don't want it to be cooked before finishing editing. +// * Need to call WaitForHoudiniInputUpdate() after done. +int32 +FHoudiniSplineComponentVisualizer::OnInsertControlPointWithoutUpdate() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return -1; + + TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + TArray & DisplayPoints = EditedHoudiniSplineComponent->DisplayPoints; + + if (EditedCurveSegmentIndex >= DisplayPoints.Num()) + return -1; + + // ... // + int InsertAfterIndex = 0; + + TArray & DisplayPointIndexDivider = EditedHoudiniSplineComponent->DisplayPointIndexDivider; + for (int itr = 0; itr < DisplayPointIndexDivider.Num(); ++itr) + { + if (DisplayPointIndexDivider[itr] >= EditedCurveSegmentIndex) + { + InsertAfterIndex = itr; + break; + } + } + // ... // + + if (InsertAfterIndex >= CurvePoints.Num()) return -1; + + FTransform NewPoint = CurvePoints[InsertAfterIndex]; + NewPoint.SetLocation(DisplayPoints[EditedCurveSegmentIndex]); + // To Do: Should interpolate the rotation and scale as well here. + // ... + + // Insert new control point on curve, and add it to selected CP. + int32 NewPointIndex = AddControlPointAfter(NewPoint, InsertAfterIndex); + + // Don't have to reconstruct the index divider each time. + //EditedHoudiniSplineComponent->Construct(EditedHoudiniSplineComponent->DisplayPoints); + EditedHoudiniSplineComponent->DisplayPointIndexDivider.Insert(EditedCurveSegmentIndex, InsertAfterIndex); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + return NewPointIndex; +} + +void +FHoudiniSplineComponentVisualizer::OnInsertControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + int32 NewPointIndex = OnInsertControlPointWithoutUpdate(); + + if (NewPointIndex < 0) return; + + + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Add(NewPointIndex); + + RefreshViewport(); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); +} + +bool +FHoudiniSplineComponentVisualizer::IsInsertControlPointValid() const +{ + return EditedCurveSegmentIndex >= 0; +} + +void +FHoudiniSplineComponentVisualizer::OnAddControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentInsertingPointsTransaction", "Houdini Spline Component: Inserting curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + + EditedHoudiniSplineComponent->Modify(); + + EditedControlPointsIndexes.Sort(); + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + TArray tNewSelectedPoints; + + if (EditedControlPointsIndexes.Num() == 1) + { + FTransform Point = CurvePoints[EditedControlPointsIndexes[0]]; + FTransform NewTransform = FTransform::Identity; + FVector Location = Point.GetLocation(); + //FQuat Rotation = Point.GetRotation(); + //FVector Scale = Point.GetScale3D(); + + NewTransform.SetLocation(Location + 1.f); + //NewTransform.SetRotation(Rotation); + //NewTransform.SetScale3D(Scale); + + + + int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[0]); + tNewSelectedPoints.Add(NewPointIndex); + } + else + { + int IndexIncrement = 0; + int CurrentPointIndex, LastPointIndex; + FTransform CurrentPoint, LastPoint; + + for (int32 n = 0; n < EditedControlPointsIndexes.Num(); ++n) + { + // Insert a new point between each adjacent pair of points + if (n > 0) + { + CurrentPointIndex = EditedControlPointsIndexes[n]; + LastPointIndex = EditedControlPointsIndexes[n - 1]; + CurrentPoint = CurvePoints[CurrentPointIndex + IndexIncrement]; + LastPoint = CurvePoints[LastPointIndex + IndexIncrement]; + + // Insert a point in the middle of LastPoint and CurrentPoint + FVector NewPointLocation = LastPoint.GetLocation() + (CurrentPoint.GetLocation() - LastPoint.GetLocation()) / 2.f; + FVector NewPointScale = LastPoint.GetScale3D() + (CurrentPoint.GetScale3D() - LastPoint.GetScale3D()) / 2.f; + FQuat NewPointRotation = FQuat::Slerp(LastPoint.GetRotation(), CurrentPoint.GetRotation(), .5f); + + FTransform NewTransform = FTransform::Identity; + NewTransform.SetLocation(NewPointLocation); + NewTransform.SetScale3D(NewPointScale); + NewTransform.SetRotation(NewPointRotation); + + int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[n - 1] + IndexIncrement); + tNewSelectedPoints.Add(NewPointIndex); + + + IndexIncrement += 1; + } + } + } + + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes = tNewSelectedPoints; + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + RefreshViewport(); +} + + +bool +FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + return EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill() && + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; +} + +void +FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentDeletingPointsTransaction", "Houdini Spline Component: Deleting curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + EditedHoudiniSplineComponent->Modify(); + + EditedControlPointsIndexes.Sort(); + + int32 SelectedIndexAfterDelete = EditedControlPointsIndexes[0] - 1; + SelectedIndexAfterDelete = FMath::Max(SelectedIndexAfterDelete, 0); + + for (int32 n = EditedControlPointsIndexes.Num() - 1; n >= 0; --n) + { + int32 RemoveIndex = EditedControlPointsIndexes[n]; + EditedHoudiniSplineComponent->RemovePointAtIndex(RemoveIndex); + + } + + EditedControlPointsIndexes.Empty(); + OnDeselectAllControlPoints(); + EditedControlPointsIndexes.Add(SelectedIndexAfterDelete); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + // Force refresh the viewport after deleting points to ensure the consistency of HitProxy + RefreshViewport(); + +} + +bool +FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + // We only allow the number of Control Points is at least 2 after delete + if (EditedHoudiniSplineComponent->GetCurvePointCount() - EditedControlPointsIndexes.Num() < 2) + return false; + + return true; +} + +void +FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentDuplicatingPointsTransaction", "Houdini Spline Component: Duplicating curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + EditedHoudiniSplineComponent->Modify(); + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + EditedControlPointsIndexes.Sort(); + + TArray tNewSelectedPoints; + int IncrementIndex = 0; + for (int n = 0; n < EditedControlPointsIndexes.Num(); ++n) + { + int32 IndexAfter = EditedControlPointsIndexes[n] + IncrementIndex; + FTransform CurrentPoint = CurvePoints[IndexAfter]; + if (IndexAfter == 0) + IndexAfter = -1; + int32 NewPointIndex = AddControlPointAfter(CurrentPoint, IndexAfter); + tNewSelectedPoints.Add(NewPointIndex); + IncrementIndex ++; + } + + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes = tNewSelectedPoints; + + EditedHoudiniSplineComponent->MarkModified(true); + + RefreshViewport(); +} + +bool +FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if(!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill() + || EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() == 0) + return false; + + return true; +} + +void +FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); +} + +bool +FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + return EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; + + return false; +} + +int32 +FHoudiniSplineComponentVisualizer::AddControlPointAfter( + const FTransform & NewPoint, + const int32 & nIndex) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return nIndex; + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + int32 NewControlPointIndex = nIndex + 1; + + if (NewControlPointIndex == CurvePoints.Num()) + EditedHoudiniSplineComponent->AppendPoint(NewPoint); + else + EditedHoudiniSplineComponent->InsertPointAtIndex(NewPoint, NewControlPointIndex); + + // Return the index of the inserted control point + return NewControlPointIndex; +} + +void +FHoudiniSplineComponentVisualizer::RefreshViewport() +{ + if (GEditor) + GEditor->RedrawLevelEditingViewports(true); +} + +// Find the EditorViewportClient of the viewport where the Houdini Spline Component lives in +FEditorViewportClient * +FHoudiniSplineComponentVisualizer::FindViewportClient( + const UHoudiniSplineComponent * InHoudiniSplineComponent, + const FSceneView * View) +{ + if (!View || !InHoudiniSplineComponent) + return nullptr; + + UWorld * World = InHoudiniSplineComponent->GetWorld(); + uint32 ViewKey = View->GetViewKey(); + + const TArray & AllViewportClients = GUnrealEd->GetAllViewportClients(); + + for (auto & NextViewportClient : AllViewportClients) + { + if (!NextViewportClient) + continue; + + if (NextViewportClient->GetWorld() != World) + continue; + + // Found the viewport client which matches the unique key of the current scene view + if (NextViewportClient->ViewState.GetReference()->GetViewKey() == ViewKey) + return NextViewportClient; + } + + return nullptr; +} + +bool +FHoudiniSplineComponentVisualizer::IsCookOnCurveChanged(UHoudiniSplineComponent * InHoudiniSplineComponent) +{ + if (!InHoudiniSplineComponent) + return true; + + return InHoudiniSplineComponent->bCookOnCurveChanged; + + // UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + // UHoudiniInputObject * InputObject = Cast(EditedHoudiniSplineComponent->GetOuter()); + // if (!InputObject) + // return true; + // + // UHoudiniInput * Input = Cast(InputObject->GetOuter()); + // + // if (!Input) + // return true; + // + // return Input->GetCookOnCurveChange(); +}; + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h index d98c976d0..fedba9821 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h @@ -1,185 +1,185 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniSplineComponent.h" - -#include "HitProxies.h" -#include "ComponentVisualizer.h" -#include "Framework/Commands/UICommandList.h" -#include "Framework/Commands/Commands.h" - -class FEditorViewportClient; - -/** Base class for clickable spline editing proxies. **/ -struct HHoudiniSplineVisProxy : public HComponentVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineVisProxy(const UActorComponent * InComponent) - : HComponentVisProxy(InComponent, HPP_Wireframe) - {} -}; - -/** Proxy for a spline control point. **/ -struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , ControlPointIndex(InControlPointIndex) - {} - - int32 ControlPointIndex; -}; - -/** Proxy for a spline display point. **/ -struct HHoudiniSplineCurveSegmentVisProxy : public HHoudiniSplineVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 InDisplayPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , DisplayPointIndex(InDisplayPointIndex) - {} - - int32 DisplayPointIndex; -}; - -class FHoudiniSplineComponentVisualizerCommands : public TCommands< FHoudiniSplineComponentVisualizerCommands > -{ - public: - FHoudiniSplineComponentVisualizerCommands(); - - /** Register commands. **/ - virtual void RegisterCommands() override; - - public: - TSharedPtr CommandAddControlPoint; - - TSharedPtr CommandDuplicateControlPoint; - - TSharedPtr CommandDeleteControlPoint; - - TSharedPtr CommandDeselectAllControlPoints; - - TSharedPtr CommandInsertControlPoint; -}; - - -/** **/ -class FHoudiniSplineComponentVisualizer : public FComponentVisualizer -{ - public: - FHoudiniSplineComponentVisualizer(); - - private: - void RefreshViewport(); - - public: - virtual void OnRegister() override; - - virtual void DrawVisualization( - const UActorComponent * Component, const FSceneView * View, - FPrimitiveDrawInterface * PDI) override; - - virtual bool VisProxyHandleClick( - FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, - const FViewportClick& Click) override; - - virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; - virtual bool IsVisualizingArchetype() const override; - - virtual void EndEditing() override; - - virtual bool HandleInputDelta( - FEditorViewportClient* ViewportClient, FViewport* Viewport, - FVector& DeltaTranslate, FRotator& DeltaRotate, - FVector& DeltaScale) override; - - virtual bool HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) override; - - virtual TSharedPtr GenerateContextMenu() const override; - - protected: - - /** Callbacks for add control point action**/ - void OnAddControlPoint(); - bool IsAddControlPointValid() const; - - /** Callbacks for delete control point action. **/ - void OnDeleteControlPoint(); - bool IsDeleteControlPointValid() const; - - /** Callbacks for duplicate control point action. **/ - void OnDuplicateControlPoint(); - bool IsDuplicateControlPointValid() const; - - /** Callbacks for deselect all control points action. **/ - void OnDeselectAllControlPoints(); - bool IsDeselectAllControlPointsValid() const; - - /** Callbacks for inserting a control point action.**/ - void OnInsertControlPoint(); - bool IsInsertControlPointValid() const; - // For alt-pressed inserting control point on curve. - int32 OnInsertControlPointWithoutUpdate(); - - int32 AddControlPointAfter(const FTransform & NewPoint, const int32 & nIndex); - - public: - /** Property path from the parent actor to the component */ - // NOTE: We need to use SplinePropertyPath on the visualizer as opposed to a direct pointer since the - // direct pointer breaks during Blueprint reconstructions properly - // (see SplineComponent / SplineMeshComponent visualizers). - FComponentPropertyPath SplinePropertyPath; - UHoudiniSplineComponent* GetEditedHoudiniSplineComponent() const { return Cast(SplinePropertyPath.GetComponent()); } - - protected: - - bool bAllowDuplication; - - int32 EditedCurveSegmentIndex; - - TSharedPtr VisualizerActions; - - /** Rotation used for the gizmo widgets **/ - FQuat CachedRotation; - - FVector CachedScale3D; - - /** Indicates wether or not a transaction should be recorded when moving a point **/ - bool bMovingPoints; - - bool bInsertingOnCurveControlPoints; - - bool bRecordingMovingPoints; - - private: - FEditorViewportClient * FindViewportClient(const UHoudiniSplineComponent * InHoudiniSplineComponent, const FSceneView * View); - - bool IsCookOnCurveChanged(UHoudiniSplineComponent* InHoudiniSplineComponent); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniSplineComponent.h" + +#include "HitProxies.h" +#include "ComponentVisualizer.h" +#include "Framework/Commands/UICommandList.h" +#include "Framework/Commands/Commands.h" + +class FEditorViewportClient; + +/** Base class for clickable spline editing proxies. **/ +struct HHoudiniSplineVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineVisProxy(const UActorComponent * InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) + {} +}; + +/** Proxy for a spline control point. **/ +struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , ControlPointIndex(InControlPointIndex) + {} + + int32 ControlPointIndex; +}; + +/** Proxy for a spline display point. **/ +struct HHoudiniSplineCurveSegmentVisProxy : public HHoudiniSplineVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 InDisplayPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , DisplayPointIndex(InDisplayPointIndex) + {} + + int32 DisplayPointIndex; +}; + +class FHoudiniSplineComponentVisualizerCommands : public TCommands< FHoudiniSplineComponentVisualizerCommands > +{ + public: + FHoudiniSplineComponentVisualizerCommands(); + + /** Register commands. **/ + virtual void RegisterCommands() override; + + public: + TSharedPtr CommandAddControlPoint; + + TSharedPtr CommandDuplicateControlPoint; + + TSharedPtr CommandDeleteControlPoint; + + TSharedPtr CommandDeselectAllControlPoints; + + TSharedPtr CommandInsertControlPoint; +}; + + +/** **/ +class FHoudiniSplineComponentVisualizer : public FComponentVisualizer +{ + public: + FHoudiniSplineComponentVisualizer(); + + private: + void RefreshViewport(); + + public: + virtual void OnRegister() override; + + virtual void DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI) override; + + virtual bool VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, + const FViewportClick& Click) override; + + virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; + virtual bool IsVisualizingArchetype() const override; + + virtual void EndEditing() override; + + virtual bool HandleInputDelta( + FEditorViewportClient* ViewportClient, FViewport* Viewport, + FVector& DeltaTranslate, FRotator& DeltaRotate, + FVector& DeltaScale) override; + + virtual bool HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) override; + + virtual TSharedPtr GenerateContextMenu() const override; + + protected: + + /** Callbacks for add control point action**/ + void OnAddControlPoint(); + bool IsAddControlPointValid() const; + + /** Callbacks for delete control point action. **/ + void OnDeleteControlPoint(); + bool IsDeleteControlPointValid() const; + + /** Callbacks for duplicate control point action. **/ + void OnDuplicateControlPoint(); + bool IsDuplicateControlPointValid() const; + + /** Callbacks for deselect all control points action. **/ + void OnDeselectAllControlPoints(); + bool IsDeselectAllControlPointsValid() const; + + /** Callbacks for inserting a control point action.**/ + void OnInsertControlPoint(); + bool IsInsertControlPointValid() const; + // For alt-pressed inserting control point on curve. + int32 OnInsertControlPointWithoutUpdate(); + + int32 AddControlPointAfter(const FTransform & NewPoint, const int32 & nIndex); + + public: + /** Property path from the parent actor to the component */ + // NOTE: We need to use SplinePropertyPath on the visualizer as opposed to a direct pointer since the + // direct pointer breaks during Blueprint reconstructions properly + // (see SplineComponent / SplineMeshComponent visualizers). + FComponentPropertyPath SplinePropertyPath; + UHoudiniSplineComponent* GetEditedHoudiniSplineComponent() const { return Cast(SplinePropertyPath.GetComponent()); } + + protected: + + bool bAllowDuplication; + + int32 EditedCurveSegmentIndex; + + TSharedPtr VisualizerActions; + + /** Rotation used for the gizmo widgets **/ + FQuat CachedRotation; + + FVector CachedScale3D; + + /** Indicates wether or not a transaction should be recorded when moving a point **/ + bool bMovingPoints; + + bool bInsertingOnCurveControlPoints; + + bool bRecordingMovingPoints; + + private: + FEditorViewportClient * FindViewportClient(const UHoudiniSplineComponent * InHoudiniSplineComponent, const FSceneView * View); + + bool IsCookOnCurveChanged(UHoudiniSplineComponent* InHoudiniSplineComponent); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp b/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp index 7f8046a09..a34697eed 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp @@ -1,26 +1,26 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniTool.h b/Source/HoudiniEngineEditor/Private/HoudiniTool.h index 87d50681e..92e14c4bf 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniTool.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniTool.h @@ -1,56 +1,56 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -UENUM() -enum class EHoudiniToolType : uint8 -{ - // For tools that generates geometry, and do not need input - HTOOLTYPE_GENERATOR UMETA(DisplayName = "Generator"), - - // For tools that have a single input, the selection will be merged in that single input - HTOOLTYPE_OPERATOR_SINGLE UMETA(DisplayName = "Operator (single)"), - - // For Tools that have multiple input, a single selected asset will be applied to each input - HTOOLTYPE_OPERATOR_MULTI UMETA(DisplayName = "Operator (multiple)"), - - // For tools that needs to be applied each time for each single selected - HTOOLTYPE_OPERATOR_BATCH UMETA(DisplayName = "Batch Operator") -}; - -UENUM() -enum class EHoudiniToolSelectionType : uint8 -{ - // For tools that can be applied both to Content Browser and World selection - HTOOL_SELECTION_ALL UMETA(DisplayName = "Content Browser AND World"), - - // For tools that can be applied only to World selection - HTOOL_SELECTION_WORLD_ONLY UMETA(DisplayName = "World selection only"), - - // For tools that can be applied only to Content Browser selection - HTOOL_SELECTION_CB_ONLY UMETA(DisplayName = "Content browser selection only") +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +UENUM() +enum class EHoudiniToolType : uint8 +{ + // For tools that generates geometry, and do not need input + HTOOLTYPE_GENERATOR UMETA(DisplayName = "Generator"), + + // For tools that have a single input, the selection will be merged in that single input + HTOOLTYPE_OPERATOR_SINGLE UMETA(DisplayName = "Operator (single)"), + + // For Tools that have multiple input, a single selected asset will be applied to each input + HTOOLTYPE_OPERATOR_MULTI UMETA(DisplayName = "Operator (multiple)"), + + // For tools that needs to be applied each time for each single selected + HTOOLTYPE_OPERATOR_BATCH UMETA(DisplayName = "Batch Operator") +}; + +UENUM() +enum class EHoudiniToolSelectionType : uint8 +{ + // For tools that can be applied both to Content Browser and World selection + HTOOL_SELECTION_ALL UMETA(DisplayName = "Content Browser AND World"), + + // For tools that can be applied only to World selection + HTOOL_SELECTION_WORLD_ONLY UMETA(DisplayName = "World selection only"), + + // For tools that can be applied only to Content Browser selection + HTOOL_SELECTION_CB_ONLY UMETA(DisplayName = "Content browser selection only") }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp index 44c91fec7..e133905b1 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp @@ -1,352 +1,352 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "SNewFilePathPicker.h" - -#include "HoudiniApi.h" -#include "DesktopPlatformModule.h" -#include "Widgets/SBoxPanel.h" -#include "Framework/Application/SlateApplication.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SButton.h" - -#define LOCTEXT_NAMESPACE "SNewFilePathPicker" - -/* SNewFilePathPicker interface - *****************************************************************************/ - -void SNewFilePathPicker::Construct( const FArguments& InArgs ) -{ - BrowseDirectory = InArgs._BrowseDirectory; - BrowseTitle = InArgs._BrowseTitle; - FilePath = InArgs._FilePath; - FileTypeFilter = InArgs._FileTypeFilter; - OnPathPicked = InArgs._OnPathPicked; - IsNewFile = InArgs._IsNewFile; - IsDirectoryPicker = InArgs._IsDirectoryPicker; - - ChildSlot - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - [ - // file path text box - SAssignNew(TextBox, SEditableTextBox) - .Text(this, &SNewFilePathPicker::HandleTextBoxText) - .Font(InArgs._Font) - .SelectAllTextWhenFocused(true) - .ClearKeyboardFocusOnCommit(false) - .OnTextCommitted(this, &SNewFilePathPicker::HandleTextBoxTextCommitted) - .SelectAllTextOnCommit(false) - .IsReadOnly(InArgs._IsReadOnly) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(4.0f, 0.0f, 0.0f, 0.0f) - .VAlign(VAlign_Center) - [ - // browse button - SNew(SButton) - .ButtonStyle(InArgs._BrowseButtonStyle) - .ToolTipText(InArgs._BrowseButtonToolTip) - .OnClicked(this, &SNewFilePathPicker::HandleBrowseButtonClicked) - .ContentPadding(2.0f) - .ForegroundColor(FSlateColor::UseForeground()) - .IsFocusable(false) - [ - SNew(SImage) - .Image(InArgs._BrowseButtonImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ] - ]; -} - - -/* SNewFilePathPicker callbacks - *****************************************************************************/ -#if PLATFORM_WINDOWS - -#include "Windows/WindowsHWrapper.h" -#include "Windows/COMPointer.h" -//#include "Misc/Paths.h" -//#include "Misc/Guid.h" -#include "HAL/FileManager.h" -#include "Windows/AllowWindowsPlatformTypes.h" -#include -//#include -#include -//#include -//#include -//#include -//#include -#include "Windows/HideWindowsPlatformTypes.h" -//#pragma comment( lib, "version.lib" ) - -bool FileDialogShared( bool bSave, const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames, int32& OutFilterIndex ) -{ - FScopedSystemModalMode SystemModalScope; - - bool bSuccess = false; - TComPtr FileDialog; - if ( SUCCEEDED( ::CoCreateInstance( bSave ? CLSID_FileSaveDialog : CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, bSave ? IID_IFileSaveDialog : IID_IFileOpenDialog, IID_PPV_ARGS_Helper( &FileDialog ) ) ) ) - { - if ( bSave ) - { - // Set the default "filename" - if ( !DefaultFile.IsEmpty() ) - { - FileDialog->SetFileName( *FPaths::GetCleanFilename( DefaultFile ) ); - } - DWORD dwFlags = 0; - FileDialog->GetOptions( &dwFlags ); - FileDialog->SetOptions( dwFlags & ~FOS_OVERWRITEPROMPT ); - } - else - { - // Set this up as a multi-select picker - if ( Flags & EFileDialogFlags::Multiple ) - { - DWORD dwFlags = 0; - FileDialog->GetOptions( &dwFlags ); - FileDialog->SetOptions( dwFlags | FOS_ALLOWMULTISELECT ); - } - } - - // Set up common settings - FileDialog->SetTitle( *DialogTitle ); - if ( !DefaultPath.IsEmpty() ) - { - // SHCreateItemFromParsingName requires the given path be absolute and use \ rather than / as our normalized paths do - FString DefaultWindowsPath = FPaths::ConvertRelativePathToFull( DefaultPath ); - DefaultWindowsPath.ReplaceInline( TEXT( "/" ), TEXT( "\\" ), ESearchCase::CaseSensitive ); - - TComPtr DefaultPathItem; - if ( SUCCEEDED( ::SHCreateItemFromParsingName( *DefaultWindowsPath, nullptr, IID_PPV_ARGS( &DefaultPathItem ) ) ) ) - { - FileDialog->SetFolder( DefaultPathItem ); - } - } - - // Set-up the file type filters - TArray UnformattedExtensions; - TArray FileDialogFilters; - { - // Split the given filter string (formatted as "Pair1String1|Pair1String2|Pair2String1|Pair2String2") into the Windows specific filter struct - FileTypes.ParseIntoArray( UnformattedExtensions, TEXT( "|" ), true ); - - if ( UnformattedExtensions.Num() % 2 == 0 ) - { - FileDialogFilters.Reserve( UnformattedExtensions.Num() / 2 ); - for ( int32 ExtensionIndex = 0; ExtensionIndex < UnformattedExtensions.Num();) - { - COMDLG_FILTERSPEC& NewFilterSpec = FileDialogFilters[FileDialogFilters.AddDefaulted()]; - NewFilterSpec.pszName = *UnformattedExtensions[ExtensionIndex++]; - NewFilterSpec.pszSpec = *UnformattedExtensions[ExtensionIndex++]; - } - } - } - FileDialog->SetFileTypes( FileDialogFilters.Num(), FileDialogFilters.GetData() ); - - // Show the picker - if ( SUCCEEDED( FileDialog->Show( (HWND)ParentWindowHandle ) ) ) - { - OutFilterIndex = 0; - if ( SUCCEEDED( FileDialog->GetFileTypeIndex( (UINT*)&OutFilterIndex ) ) ) - { - OutFilterIndex -= 1; // GetFileTypeIndex returns a 1-based index - } - - auto AddOutFilename = [&OutFilenames]( const FString& InFilename ) - { - FString& OutFilename = OutFilenames[OutFilenames.Add( InFilename )]; - OutFilename = IFileManager::Get().ConvertToRelativePath( *OutFilename ); - FPaths::NormalizeFilename( OutFilename ); - }; - - if ( bSave ) - { - TComPtr Result; - if ( SUCCEEDED( FileDialog->GetResult( &Result ) ) ) - { - PWSTR pFilePath = nullptr; - if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) - { - bSuccess = true; - - // Apply the selected extension if the given filename doesn't already have one - FString SaveFilePath = pFilePath; - if ( FileDialogFilters.IsValidIndex( OutFilterIndex ) ) - { - // Build a "clean" version of the selected extension (without the wildcard) - FString CleanExtension = FileDialogFilters[OutFilterIndex].pszSpec; - if ( CleanExtension == TEXT( "*.*" ) ) - { - CleanExtension.Reset(); - } - else - { - const int32 WildCardIndex = CleanExtension.Find( TEXT( "*" ) ); - if ( WildCardIndex != INDEX_NONE ) - { - CleanExtension = CleanExtension.RightChop( WildCardIndex + 1 ); - } - } - - // We need to split these before testing the extension to avoid anything within the path being treated as a file extension - FString SaveFileName = FPaths::GetCleanFilename( SaveFilePath ); - SaveFilePath = FPaths::GetPath( SaveFilePath ); - - // Apply the extension if the file name doesn't already have one - if ( FPaths::GetExtension( SaveFileName ).IsEmpty() && !CleanExtension.IsEmpty() ) - { - SaveFileName = FPaths::SetExtension( SaveFileName, CleanExtension ); - } - - SaveFilePath /= SaveFileName; - } - AddOutFilename( SaveFilePath ); - - ::CoTaskMemFree( pFilePath ); - } - } - } - else - { - IFileOpenDialog* FileOpenDialog = static_cast( FileDialog.Get() ); - - TComPtr Results; - if ( SUCCEEDED( FileOpenDialog->GetResults( &Results ) ) ) - { - DWORD NumResults = 0; - Results->GetCount( &NumResults ); - for ( DWORD ResultIndex = 0; ResultIndex < NumResults; ++ResultIndex ) - { - TComPtr Result; - if ( SUCCEEDED( Results->GetItemAt( ResultIndex, &Result ) ) ) - { - PWSTR pFilePath = nullptr; - if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) - { - bSuccess = true; - AddOutFilename( pFilePath ); - ::CoTaskMemFree( pFilePath ); - } - } - } - } - } - } - } - - return bSuccess; -} - -bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) -{ - int32 DummyFilterIndex = 0; - return FileDialogShared( true, ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames, DummyFilterIndex ); -} - -#else - -bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) -{ - IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); - return DesktopPlatform->SaveFileDialog( ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames ); -} -#endif - -FReply SNewFilePathPicker::HandleBrowseButtonClicked() -{ - IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); - - if (DesktopPlatform == nullptr) - { - return FReply::Handled(); - } - - const FString DefaultPath = BrowseDirectory.IsSet() - ? BrowseDirectory.Get() - : FPaths::GetPath(FilePath.Get()); - - // show the file browse dialog - if (!FSlateApplication::IsInitialized()) - return FReply::Handled(); - - TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); - void* ParentWindowHandle = (ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid()) - ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() - : nullptr; - - if(!IsDirectoryPicker.Get()) - { - TArray OutFiles; - // CG: Use SaveFileDialog instead of OpenFileDialog - if ( IsNewFile.Get() ) - { - if ( SaveFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) - { - OnPathPicked.ExecuteIfBound( OutFiles[0] ); - } - } - else - { - if ( DesktopPlatform->OpenFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) - { - OnPathPicked.ExecuteIfBound( OutFiles[0] ); - } - } - } - else - { - FString OutDir; - if (DesktopPlatform->OpenDirectoryDialog(ParentWindowHandle, LOCTEXT("FolderDialogTitle", "Choose a directory").ToString(), DefaultPath, OutDir)) - { - OnPathPicked.ExecuteIfBound(OutDir); - } - } - - return FReply::Handled(); -} - - -FText SNewFilePathPicker::HandleTextBoxText() const -{ - return FText::FromString(FilePath.Get()); -} - - -void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type CommitInfo ) -{ - OnPathPicked.ExecuteIfBound(NewText.ToString()); -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "SNewFilePathPicker.h" + +#include "HoudiniApi.h" +#include "DesktopPlatformModule.h" +#include "Widgets/SBoxPanel.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SButton.h" + +#define LOCTEXT_NAMESPACE "SNewFilePathPicker" + +/* SNewFilePathPicker interface + *****************************************************************************/ + +void SNewFilePathPicker::Construct( const FArguments& InArgs ) +{ + BrowseDirectory = InArgs._BrowseDirectory; + BrowseTitle = InArgs._BrowseTitle; + FilePath = InArgs._FilePath; + FileTypeFilter = InArgs._FileTypeFilter; + OnPathPicked = InArgs._OnPathPicked; + IsNewFile = InArgs._IsNewFile; + IsDirectoryPicker = InArgs._IsDirectoryPicker; + + ChildSlot + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + // file path text box + SAssignNew(TextBox, SEditableTextBox) + .Text(this, &SNewFilePathPicker::HandleTextBoxText) + .Font(InArgs._Font) + .SelectAllTextWhenFocused(true) + .ClearKeyboardFocusOnCommit(false) + .OnTextCommitted(this, &SNewFilePathPicker::HandleTextBoxTextCommitted) + .SelectAllTextOnCommit(false) + .IsReadOnly(InArgs._IsReadOnly) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(4.0f, 0.0f, 0.0f, 0.0f) + .VAlign(VAlign_Center) + [ + // browse button + SNew(SButton) + .ButtonStyle(InArgs._BrowseButtonStyle) + .ToolTipText(InArgs._BrowseButtonToolTip) + .OnClicked(this, &SNewFilePathPicker::HandleBrowseButtonClicked) + .ContentPadding(2.0f) + .ForegroundColor(FSlateColor::UseForeground()) + .IsFocusable(false) + [ + SNew(SImage) + .Image(InArgs._BrowseButtonImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ] + ]; +} + + +/* SNewFilePathPicker callbacks + *****************************************************************************/ +#if PLATFORM_WINDOWS + +#include "Windows/WindowsHWrapper.h" +#include "Windows/COMPointer.h" +//#include "Misc/Paths.h" +//#include "Misc/Guid.h" +#include "HAL/FileManager.h" +#include "Windows/AllowWindowsPlatformTypes.h" +#include +//#include +#include +//#include +//#include +//#include +//#include +#include "Windows/HideWindowsPlatformTypes.h" +//#pragma comment( lib, "version.lib" ) + +bool FileDialogShared( bool bSave, const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames, int32& OutFilterIndex ) +{ + FScopedSystemModalMode SystemModalScope; + + bool bSuccess = false; + TComPtr FileDialog; + if ( SUCCEEDED( ::CoCreateInstance( bSave ? CLSID_FileSaveDialog : CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, bSave ? IID_IFileSaveDialog : IID_IFileOpenDialog, IID_PPV_ARGS_Helper( &FileDialog ) ) ) ) + { + if ( bSave ) + { + // Set the default "filename" + if ( !DefaultFile.IsEmpty() ) + { + FileDialog->SetFileName( *FPaths::GetCleanFilename( DefaultFile ) ); + } + DWORD dwFlags = 0; + FileDialog->GetOptions( &dwFlags ); + FileDialog->SetOptions( dwFlags & ~FOS_OVERWRITEPROMPT ); + } + else + { + // Set this up as a multi-select picker + if ( Flags & EFileDialogFlags::Multiple ) + { + DWORD dwFlags = 0; + FileDialog->GetOptions( &dwFlags ); + FileDialog->SetOptions( dwFlags | FOS_ALLOWMULTISELECT ); + } + } + + // Set up common settings + FileDialog->SetTitle( *DialogTitle ); + if ( !DefaultPath.IsEmpty() ) + { + // SHCreateItemFromParsingName requires the given path be absolute and use \ rather than / as our normalized paths do + FString DefaultWindowsPath = FPaths::ConvertRelativePathToFull( DefaultPath ); + DefaultWindowsPath.ReplaceInline( TEXT( "/" ), TEXT( "\\" ), ESearchCase::CaseSensitive ); + + TComPtr DefaultPathItem; + if ( SUCCEEDED( ::SHCreateItemFromParsingName( *DefaultWindowsPath, nullptr, IID_PPV_ARGS( &DefaultPathItem ) ) ) ) + { + FileDialog->SetFolder( DefaultPathItem ); + } + } + + // Set-up the file type filters + TArray UnformattedExtensions; + TArray FileDialogFilters; + { + // Split the given filter string (formatted as "Pair1String1|Pair1String2|Pair2String1|Pair2String2") into the Windows specific filter struct + FileTypes.ParseIntoArray( UnformattedExtensions, TEXT( "|" ), true ); + + if ( UnformattedExtensions.Num() % 2 == 0 ) + { + FileDialogFilters.Reserve( UnformattedExtensions.Num() / 2 ); + for ( int32 ExtensionIndex = 0; ExtensionIndex < UnformattedExtensions.Num();) + { + COMDLG_FILTERSPEC& NewFilterSpec = FileDialogFilters[FileDialogFilters.AddDefaulted()]; + NewFilterSpec.pszName = *UnformattedExtensions[ExtensionIndex++]; + NewFilterSpec.pszSpec = *UnformattedExtensions[ExtensionIndex++]; + } + } + } + FileDialog->SetFileTypes( FileDialogFilters.Num(), FileDialogFilters.GetData() ); + + // Show the picker + if ( SUCCEEDED( FileDialog->Show( (HWND)ParentWindowHandle ) ) ) + { + OutFilterIndex = 0; + if ( SUCCEEDED( FileDialog->GetFileTypeIndex( (UINT*)&OutFilterIndex ) ) ) + { + OutFilterIndex -= 1; // GetFileTypeIndex returns a 1-based index + } + + auto AddOutFilename = [&OutFilenames]( const FString& InFilename ) + { + FString& OutFilename = OutFilenames[OutFilenames.Add( InFilename )]; + OutFilename = IFileManager::Get().ConvertToRelativePath( *OutFilename ); + FPaths::NormalizeFilename( OutFilename ); + }; + + if ( bSave ) + { + TComPtr Result; + if ( SUCCEEDED( FileDialog->GetResult( &Result ) ) ) + { + PWSTR pFilePath = nullptr; + if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) + { + bSuccess = true; + + // Apply the selected extension if the given filename doesn't already have one + FString SaveFilePath = pFilePath; + if ( FileDialogFilters.IsValidIndex( OutFilterIndex ) ) + { + // Build a "clean" version of the selected extension (without the wildcard) + FString CleanExtension = FileDialogFilters[OutFilterIndex].pszSpec; + if ( CleanExtension == TEXT( "*.*" ) ) + { + CleanExtension.Reset(); + } + else + { + const int32 WildCardIndex = CleanExtension.Find( TEXT( "*" ) ); + if ( WildCardIndex != INDEX_NONE ) + { + CleanExtension = CleanExtension.RightChop( WildCardIndex + 1 ); + } + } + + // We need to split these before testing the extension to avoid anything within the path being treated as a file extension + FString SaveFileName = FPaths::GetCleanFilename( SaveFilePath ); + SaveFilePath = FPaths::GetPath( SaveFilePath ); + + // Apply the extension if the file name doesn't already have one + if ( FPaths::GetExtension( SaveFileName ).IsEmpty() && !CleanExtension.IsEmpty() ) + { + SaveFileName = FPaths::SetExtension( SaveFileName, CleanExtension ); + } + + SaveFilePath /= SaveFileName; + } + AddOutFilename( SaveFilePath ); + + ::CoTaskMemFree( pFilePath ); + } + } + } + else + { + IFileOpenDialog* FileOpenDialog = static_cast( FileDialog.Get() ); + + TComPtr Results; + if ( SUCCEEDED( FileOpenDialog->GetResults( &Results ) ) ) + { + DWORD NumResults = 0; + Results->GetCount( &NumResults ); + for ( DWORD ResultIndex = 0; ResultIndex < NumResults; ++ResultIndex ) + { + TComPtr Result; + if ( SUCCEEDED( Results->GetItemAt( ResultIndex, &Result ) ) ) + { + PWSTR pFilePath = nullptr; + if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) + { + bSuccess = true; + AddOutFilename( pFilePath ); + ::CoTaskMemFree( pFilePath ); + } + } + } + } + } + } + } + + return bSuccess; +} + +bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) +{ + int32 DummyFilterIndex = 0; + return FileDialogShared( true, ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames, DummyFilterIndex ); +} + +#else + +bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + return DesktopPlatform->SaveFileDialog( ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames ); +} +#endif + +FReply SNewFilePathPicker::HandleBrowseButtonClicked() +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + + if (DesktopPlatform == nullptr) + { + return FReply::Handled(); + } + + const FString DefaultPath = BrowseDirectory.IsSet() + ? BrowseDirectory.Get() + : FPaths::GetPath(FilePath.Get()); + + // show the file browse dialog + if (!FSlateApplication::IsInitialized()) + return FReply::Handled(); + + TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + void* ParentWindowHandle = (ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid()) + ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() + : nullptr; + + if(!IsDirectoryPicker.Get()) + { + TArray OutFiles; + // CG: Use SaveFileDialog instead of OpenFileDialog + if ( IsNewFile.Get() ) + { + if ( SaveFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) + { + OnPathPicked.ExecuteIfBound( OutFiles[0] ); + } + } + else + { + if ( DesktopPlatform->OpenFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) + { + OnPathPicked.ExecuteIfBound( OutFiles[0] ); + } + } + } + else + { + FString OutDir; + if (DesktopPlatform->OpenDirectoryDialog(ParentWindowHandle, LOCTEXT("FolderDialogTitle", "Choose a directory").ToString(), DefaultPath, OutDir)) + { + OnPathPicked.ExecuteIfBound(OutDir); + } + } + + return FReply::Handled(); +} + + +FText SNewFilePathPicker::HandleTextBoxText() const +{ + return FText::FromString(FilePath.Get()); +} + + +void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type CommitInfo ) +{ + OnPathPicked.ExecuteIfBound(NewText.ToString()); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h index 8ca1dbbca..06a542733 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h @@ -1,150 +1,150 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// This is the same as SFilePathPicker but it uses SaveFileDialog instead of OpenFileDialog -// to allow browsing to a new path - -#pragma once - -#include "CoreMinimal.h" -#include "Misc/Attribute.h" -#include "Fonts/SlateFontInfo.h" -#include "Input/Reply.h" -#include "Styling/SlateWidgetStyleAsset.h" -#include "Styling/ISlateStyle.h" -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SCompoundWidget.h" -#include "Styling/SlateTypes.h" - -class SEditableTextBox; - -/** - * Declares a delegate that is executed when a file was picked in the SFilePathPicker widget. - * - * The first parameter will contain the path to the picked file. - */ -DECLARE_DELEGATE_OneParam(FOnPathPicked, const FString& /*PickedPath*/); - - -/** - * Implements an editable text box with a browse button. - */ -class SNewFilePathPicker - : public SCompoundWidget -{ -public: - - SLATE_BEGIN_ARGS(SNewFilePathPicker) - : _BrowseButtonToolTip(NSLOCTEXT("SNewFilePathPicker", "BrowseButtonToolTip", "Choose a file from this computer")) - , _FileTypeFilter(TEXT("All files (*.*)|*.*")) - , _Font() - , _IsReadOnly(false) - , _IsNewFile(true) - , _IsDirectoryPicker(false) - { } - - /** Browse button image resource. */ - SLATE_ATTRIBUTE(const FSlateBrush*, BrowseButtonImage) - - /** Browse button visual style. */ - SLATE_STYLE_ARGUMENT(FButtonStyle, BrowseButtonStyle) - - /** Browse button tool tip text. */ - SLATE_ATTRIBUTE(FText, BrowseButtonToolTip) - - /** The directory to browse by default */ - SLATE_ATTRIBUTE(FString, BrowseDirectory) - - /** Title for the browse dialog window. */ - SLATE_ATTRIBUTE(FText, BrowseTitle) - - /** The currently selected file path. */ - SLATE_ATTRIBUTE(FString, FilePath) - - /** File type filter string. */ - SLATE_ATTRIBUTE(FString, FileTypeFilter) - - /** Font color and opacity of the path text box. */ - SLATE_ATTRIBUTE(FSlateFontInfo, Font) - - /** Whether the path text box can be modified by the user. */ - SLATE_ATTRIBUTE(bool, IsReadOnly) - - /** Whether to use the new-file dialog instead of open-file */ - SLATE_ATTRIBUTE(bool, IsNewFile) - - /** Whether to use the a directory picker dialog */ - SLATE_ATTRIBUTE(bool, IsDirectoryPicker) - - /** Called when a file path has been picked. */ - SLATE_EVENT(FOnPathPicked, OnPathPicked) - - SLATE_END_ARGS() - - /** - * Constructs a new widget. - * - * @param InArgs The construction arguments. - */ - void Construct( const FArguments& InArgs ); - -private: - - /** Callback for clicking the browse button. */ - FReply HandleBrowseButtonClicked( ); - - /** Callback for getting the text in the path text box. */ - FText HandleTextBoxText( ) const; - - /** Callback for committing the text in the path text box. */ - void HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type /*CommitInfo*/ ); - -private: - - /** Holds the directory path to browse by default. */ - TAttribute BrowseDirectory; - - /** Holds the title for the browse dialog window. */ - TAttribute BrowseTitle; - - /** Holds the currently selected file path. */ - TAttribute FilePath; - - /** Holds the file type filter string. */ - TAttribute FileTypeFilter; - - /** Holds the editable text box. */ - TSharedPtr TextBox; - - TAttribute IsNewFile; - - TAttribute IsDirectoryPicker; - -private: - - /** Holds a delegate that is executed when a file was picked. */ - FOnPathPicked OnPathPicked; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// This is the same as SFilePathPicker but it uses SaveFileDialog instead of OpenFileDialog +// to allow browsing to a new path + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Attribute.h" +#include "Fonts/SlateFontInfo.h" +#include "Input/Reply.h" +#include "Styling/SlateWidgetStyleAsset.h" +#include "Styling/ISlateStyle.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Styling/SlateTypes.h" + +class SEditableTextBox; + +/** + * Declares a delegate that is executed when a file was picked in the SFilePathPicker widget. + * + * The first parameter will contain the path to the picked file. + */ +DECLARE_DELEGATE_OneParam(FOnPathPicked, const FString& /*PickedPath*/); + + +/** + * Implements an editable text box with a browse button. + */ +class SNewFilePathPicker + : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SNewFilePathPicker) + : _BrowseButtonToolTip(NSLOCTEXT("SNewFilePathPicker", "BrowseButtonToolTip", "Choose a file from this computer")) + , _FileTypeFilter(TEXT("All files (*.*)|*.*")) + , _Font() + , _IsReadOnly(false) + , _IsNewFile(true) + , _IsDirectoryPicker(false) + { } + + /** Browse button image resource. */ + SLATE_ATTRIBUTE(const FSlateBrush*, BrowseButtonImage) + + /** Browse button visual style. */ + SLATE_STYLE_ARGUMENT(FButtonStyle, BrowseButtonStyle) + + /** Browse button tool tip text. */ + SLATE_ATTRIBUTE(FText, BrowseButtonToolTip) + + /** The directory to browse by default */ + SLATE_ATTRIBUTE(FString, BrowseDirectory) + + /** Title for the browse dialog window. */ + SLATE_ATTRIBUTE(FText, BrowseTitle) + + /** The currently selected file path. */ + SLATE_ATTRIBUTE(FString, FilePath) + + /** File type filter string. */ + SLATE_ATTRIBUTE(FString, FileTypeFilter) + + /** Font color and opacity of the path text box. */ + SLATE_ATTRIBUTE(FSlateFontInfo, Font) + + /** Whether the path text box can be modified by the user. */ + SLATE_ATTRIBUTE(bool, IsReadOnly) + + /** Whether to use the new-file dialog instead of open-file */ + SLATE_ATTRIBUTE(bool, IsNewFile) + + /** Whether to use the a directory picker dialog */ + SLATE_ATTRIBUTE(bool, IsDirectoryPicker) + + /** Called when a file path has been picked. */ + SLATE_EVENT(FOnPathPicked, OnPathPicked) + + SLATE_END_ARGS() + + /** + * Constructs a new widget. + * + * @param InArgs The construction arguments. + */ + void Construct( const FArguments& InArgs ); + +private: + + /** Callback for clicking the browse button. */ + FReply HandleBrowseButtonClicked( ); + + /** Callback for getting the text in the path text box. */ + FText HandleTextBoxText( ) const; + + /** Callback for committing the text in the path text box. */ + void HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type /*CommitInfo*/ ); + +private: + + /** Holds the directory path to browse by default. */ + TAttribute BrowseDirectory; + + /** Holds the title for the browse dialog window. */ + TAttribute BrowseTitle; + + /** Holds the currently selected file path. */ + TAttribute FilePath; + + /** Holds the file type filter string. */ + TAttribute FileTypeFilter; + + /** Holds the editable text box. */ + TSharedPtr TextBox; + + TAttribute IsNewFile; + + TAttribute IsDirectoryPicker; + +private: + + /** Holds a delegate that is executed when a file was picked. */ + FOnPathPicked OnPathPicked; +}; diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp index 0e24db99a..34845b12f 100644 --- a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp @@ -78,16 +78,21 @@ UHoudiniAssetComponent* FHoudiniEditorTestUtils::InstantiateAsset(FAutomationTes // Need to allocate on heap otherwise it will be garbage collected. bool * FinishedCook = new bool(false); bool * CookSuccessful = new bool(false); - - HoudiniComponent->GetOnPostCookDelegate().BindLambda([=](UHoudiniAssetComponent* HAC, bool IsSuccess) + FDelegateHandle * PostCookDelegateHandle = new FDelegateHandle(); + + auto OnPostCookLambda = [=](UHoudiniAssetComponent* HAC, bool IsSuccess) { if (FinishedCook != nullptr && CookSuccessful != nullptr) { *FinishedCook = true; *CookSuccessful = IsSuccess; - HoudiniComponent->GetOnPostCookDelegate().Unbind(); + if (PostCookDelegateHandle != nullptr) + HoudiniComponent->GetOnPostCookDelegate().Remove(*PostCookDelegateHandle); } - }); + + }; + + *PostCookDelegateHandle = HoudiniComponent->GetOnPostCookDelegate().AddLambda(OnPostCookLambda); Test->AddCommand(new FFunctionLatentCommand([=]() { @@ -106,6 +111,7 @@ UHoudiniAssetComponent* FHoudiniEditorTestUtils::InstantiateAsset(FAutomationTes OnFinishInstantiate(HoudiniComponent, CookSuccessfulResult); delete FinishedCook; delete CookSuccessful; + delete PostCookDelegateHandle; return true; } diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h new file mode 100644 index 000000000..bb7909522 --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h @@ -0,0 +1,214 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniParameter.h" +#include "HoudiniPublicAPIObjectBase.h" + +#include "HoudiniPublicAPI.generated.h" + +class ULevel; + +class UHoudiniAsset; +class UHoudiniPublicAPIAssetWrapper; +class UHoudiniPublicAPIInput; + +/** Public API version of EHoudiniRampInterpolationType: blueprints do not support int8 based enums. */ +UENUM(BlueprintType) +enum class EHoudiniPublicAPIRampInterpolationType : uint8 +{ + InValid = 0, + + CONSTANT = 1, + LINEAR = 2, + CATMULL_ROM = 3, + MONOTONE_CUBIC = 4, + BEZIER = 5, + BSPLINE = 6, + HERMITE = 7 +}; + +/** + * The Houdini Engine v2 Plug-in's Public API. + * + * The API allows one to manage a Houdini Engine session (Create/Stop/Restart), Pause/Resume asset cooking and + * instantiate HDA's and interact with it (set/update inputs, parameters, cook, iterate over outputs and bake outputs). + * + * Interaction with an instantiated HDA is done via UHoudiniPublicAPIAssetWrapper. + * + */ +UCLASS(BlueprintType, Blueprintable, Category="Houdini|Public API") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPI : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + + UHoudiniPublicAPI(); + + // Session + + /** Returns true if there is a valid Houdini Engine session running/connected */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsSessionValid() const; + FORCEINLINE + virtual bool IsSessionValid_Implementation() const { return FHoudiniEngineCommands::IsSessionValid(); } + + /** Start a new Houdini Engine Session if there is no current session */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void CreateSession(); + + /** Stops the current session */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void StopSession(); + + /** Stops, then creates a new session */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void RestartSession(); + + // Assets + + /** + * Instantiates an HDA in the specified world/level. Returns a wrapper for instantiated asset. + * @param InHoudiniAsset The HDA to instantiate. + * @param InInstantiateAt The Transform to instantiate the HDA with. + * @param InWorldContextObject A world context object for identifying the world to spawn in, if + * @InSpawnInLevelOverride is null. + * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both + * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor + * context world's current level. + * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after + * parameter, transform and input changes. + * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. + * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. + * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. + * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. + * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. + * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with + * the new bake's output. Defaults to false. + * @return A wrapper for the instantiated asset, or nullptr if InHoudiniAsset or InInstantiateAt is invalid, or + * the AHoudiniAssetActor could not be spawned. See UHoudiniPublicAPIAssetWrapper. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InInstantiateAt")) + UHoudiniPublicAPIAssetWrapper* InstantiateAsset( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject=nullptr, + ULevel* InSpawnInLevelOverride=nullptr, + const bool bInEnableAutoCook=true, + const bool bInEnableAutoBake=false, + const FString& InBakeDirectoryPath="", + const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, + const bool bInRemoveOutputAfterBake=false, + const bool bInRecenterBakedActors=false, + const bool bInReplacePreviousBake=false); + + /** + * Instantiates an HDA in the specified world/level using an existing wrapper. + * @param InWrapper The wrapper to instantiate the HDA with. + * @param InHoudiniAsset The HDA to instantiate. + * @param InInstantiateAt The Transform to instantiate the HDA with. + * @param InWorldContextObject A world context object for identifying the world to spawn in, if + * InSpawnInLevelOverride is null. + * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both + * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor + * context world's current level. + * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after + * parameter, transform and input changes. + * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. + * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. + * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. + * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. + * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. + * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with + * the new bake's output. Defaults to false. + * @return true if InWrapper and InHoudiniAsset is valid and the AHoudiniAssetActor was spawned. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InInstantiateAt")) + bool InstantiateAssetWithExistingWrapper( + UHoudiniPublicAPIAssetWrapper* InWrapper, + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject=nullptr, + ULevel* InSpawnInLevelOverride=nullptr, + const bool bInEnableAutoCook=true, + const bool bInEnableAutoBake=false, + const FString& InBakeDirectoryPath="", + const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, + const bool bInRemoveOutputAfterBake=false, + const bool bInRecenterBakedActors=false, + const bool bInReplacePreviousBake=false); + + // Cooking + + /** Returns true if asset cooking is paused. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsAssetCookingPaused() const; + FORCEINLINE + virtual bool IsAssetCookingPaused_Implementation() const { return FHoudiniEngineCommands::IsAssetCookingPaused(); } + + /** Pause asset cooking (if not already paused) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void PauseAssetCooking(); + + /** Resume asset cooking (if it was paused) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void ResumeAssetCooking(); + + // Inputs + + /** + * Create a new empty API input object. The user must populate it and then set it as an input on an asset wrapper. + * @param InInputClass The class of the input to create, must be a subclass of UHoudiniPublicAPIInput. + * @param InOuter The owner of the input, if nullptr, then this API instance will be set as the outer. + * @return The newly created empty input object instance. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(DeterminesOutputType="InInputClass")) + UHoudiniPublicAPIInput* CreateEmptyInput(TSubclassOf InInputClass, UHoudiniPublicAPIAssetWrapper* InOuter=nullptr); + + // Helpers -- enum conversions + + /** + * Helper for converting from EHoudiniRampInterpolationType to EHoudiniPublicAPIRampInterpolationType + * @param InInterpolationType The EHoudiniRampInterpolationType to convert. + * @return The EHoudiniPublicAPIRampInterpolationType value of InInterpolationType. + */ + static EHoudiniPublicAPIRampInterpolationType ToHoudiniPublicAPIRampInterpolationType(const EHoudiniRampInterpolationType InInterpolationType); + + /** + * Helper for converting from EHoudiniPublicAPIRampInterpolationType to EHoudiniRampInterpolationType + * @param InInterpolationType The EHoudiniPublicAPIRampInterpolationType to convert. + * @return The EHoudiniRampInterpolationType value of InInterpolationType. + */ + static EHoudiniRampInterpolationType ToHoudiniRampInterpolationType(const EHoudiniPublicAPIRampInterpolationType InInterpolationType); + +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h new file mode 100644 index 000000000..c5b28cb0c --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h @@ -0,0 +1,1557 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" + +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniParameter.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPublicAPIObjectBase.h" +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIOutputTypes.h" +#include "IHoudiniAssetStateEvents.h" + +#include "HoudiniPublicAPIAssetWrapper.generated.h" + + +class UHoudiniPublicAPIInput; + +/** + * The base class of a struct for Houdini Ramp points. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIRampPoint +{ + GENERATED_BODY(); + +public: + FHoudiniPublicAPIRampPoint(); + + FHoudiniPublicAPIRampPoint( + const float InPosition, + const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); + + /** The position of the point on the Ramp's x-axis [0,1]. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + float Position; + + /** The interpolation type of the point. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + EHoudiniPublicAPIRampInterpolationType Interpolation; +}; + +/** + * A struct for Houdini float ramp points. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIFloatRampPoint : public FHoudiniPublicAPIRampPoint +{ + GENERATED_BODY(); + +public: + + FHoudiniPublicAPIFloatRampPoint(); + + FHoudiniPublicAPIFloatRampPoint( + const float InPosition, + const float InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); + + /** The value of the point. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + float Value; + +}; + +/** + * A struct for Houdini color ramp points. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIColorRampPoint : public FHoudiniPublicAPIRampPoint +{ + GENERATED_BODY(); + +public: + + FHoudiniPublicAPIColorRampPoint(); + + FHoudiniPublicAPIColorRampPoint( + const float InPosition, + const FLinearColor& InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); + + /** The value of the point. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + FLinearColor Value; + +}; + +/** + * A struct for storing the values of a Houdini parameter tuple. + * Currently supports bool, int32, float and FString storage. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniParameterTuple +{ + GENERATED_BODY(); + +public: + FHoudiniParameterTuple(); + + /** + * Wrap a single bool value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const bool& InValue); + /** + * Wrap a bool tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap a single int32 value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const int32& InValue); + /** + * Wrap a int32 tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap a single float value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const float& InValue); + /** + * Wrap a float tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap a single FString value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const FString& InValue); + /** + * Wrap a FString tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap float ramp points + * @param InRampPoints The float ramp points. + */ + FHoudiniParameterTuple(const TArray& InRampPoints); + + /** + * Wrap color ramp points + * @param InRampPoints The color ramp points. + */ + FHoudiniParameterTuple(const TArray& InRampPoints); + + // Parameter tuple storage + + /** For bool compatible parameters, the bool parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray BoolValues; + + /** For int32 compatible parameters, the int32 parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray Int32Values; + + /** For float compatible parameters, the float parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray FloatValues; + + /** For string compatible parameters, the string parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray StringValues; + + /** For float ramp parameters, the ramp points. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray FloatRampPoints; + + /** For color ramp parameters, the ramp points. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray ColorRampPoints; +}; + +/** + * A wrapper for spawned/instantiating HDAs. + * + * The wrapper/HDA should be instantiated via UHoudiniPublicAPI::InstantiateAsset(). Alternatively an empty + * wrapper can be created via UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper() and an HDA later instantiated and + * assigned to the wrapper via UHoudiniPublicAPI::InstantiateAssetWithExistingWrapper(). + * + * The wrapper provides functionality for interacting/manipulating a + * AHoudiniAssetActor / UHoudiniAssetComponent: + * - Get/Set Inputs + * - Get/Set Parameters + * - Manually initiate a cook/recook + * - Subscribe to delegates: + * - #OnPreInstantiationDelegate (good place to set parameter values before the first cook) + * - #OnPostInstantiationDelegate (good place to set/configure inputs before the first cook) + * - #OnPostCookDelegate + * - #OnPreProcessStateExitedDelegate + * - #OnPostProcessingDelegate (output objects are available if the cook was successful) + * - #OnPostBakeDelegate + * - #OnPostPDGTOPNetworkCookDelegate + * - #OnPostPDGBakeDelegate + * - #OnProxyMeshesRefinedDelegate + * - Iterate over outputs and find the output assets + * - Bake outputs + * - PDG: Dirty all, cook outputs + */ +UCLASS(BlueprintType, Blueprintable, Category="Houdini Engine|Public API") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIAssetWrapper(); + + // Delegate types + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHoudiniAssetStateChange, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetPostCook, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper, const bool, bInCookSuccess); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetPostBake, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper, const bool, bInBakeSuccess); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetProxyMeshesRefinedDelegate, UHoudiniPublicAPIAssetWrapper* const, InAssetWrapper, const EHoudiniProxyRefineResult, InResult); + + /** + * Factory function for creating new wrapper instances around instantiated assets. + * @param InOuter The outer for the new wrapper instance. + * @param InHoudiniAssetActorOrComponent The AHoudiniAssetActor or UHoudiniAssetComponent to wrap. + * @return The newly instantiated wrapper that wraps InHoudiniAssetActor, or nullptr if the wrapper could not + * be created, or if InHoudiniAssetActorOrComponent is invalid or not of a supported type. + */ + UFUNCTION(BlueprintCallable, Category="Houdini|Public API") + static UHoudiniPublicAPIAssetWrapper* CreateWrapper(UObject* InOuter, UObject* InHoudiniAssetActorOrComponent); + + /** + * Factory function for creating a new empty wrapper instance. + * An instantiated actor can be wrapped using SetHoudiniAssetActor. + * @param InOuter the outer for the new wrapper instance. + * @return The newly instantiated wrapper. + */ + UFUNCTION(BlueprintCallable, Category="Houdini|Public API") + static UHoudiniPublicAPIAssetWrapper* CreateEmptyWrapper(UObject* InOuter); + + /** + * Checks if InObject can be wrapped by instances of UHoudiniPublicAPIAssetWrapper. + * @param InObject The object to check for compatiblity. + * @return true if InObject can be wrapped by instances of UHoudiniPublicAPIAssetWrapper. + */ + UFUNCTION(BlueprintCallable, Category="Houdini|Public API") + static bool CanWrapHoudiniObject(UObject* InObject); + + /** + * Wrap the specified instantiated houdini asset object. Supported objects are: AHoudiniAssetActor, + * UHoudiniAssetComponent. This will first unwrap/unbind the currently wrapped instantiated + * asset if InHoudiniAssetObjectToWrap is valid and of a supported class. + * + * If InHoudiniAssetObjectToWrap is nullptr, then this wrapper will unwrap/unbind the currently wrapped + * instantiated asset and return true. + * + * @param InHoudiniAssetObjectToWrap The object to wrap, or nullptr to unwrap/unbind if currently wrapping an + * asset. + * @return true if InHoudiniAssetObjectToWrap is valid, of a supported class and was successfully wrapped, or true + * if InHoudiniAssetObjectToWrap is nullptr. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool WrapHoudiniAssetObject(UObject* InHoudiniAssetObjectToWrap); + + // Accessors and mutators + + /** + * Get the wrapped instantiated houdini asset object. + * @return The wrapped instantiated houdini asset object. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UObject* GetHoudiniAssetObject() const; + FORCEINLINE + virtual UObject* GetHoudiniAssetObject_Implementation() const { return HoudiniAssetObject.Get(); } + + /** + * Helper function for getting the instantiated HDA asset actor, if HoudiniAssetObject is an AHoudiniAssetActor or + * a UHoudiniAssetComponent owned by a AHoudiniAssetActor. + * @return The instantiated AHoudiniAssetActor, if HoudiniAssetObject is an AHoudiniAssetActor or + * a UHoudiniAssetComponent owned by a AHoudiniAssetActor, otherwise nullptr. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + AHoudiniAssetActor* GetHoudiniAssetActor() const; + FORCEINLINE + virtual AHoudiniAssetActor* GetHoudiniAssetActor_Implementation() const { return CachedHoudiniAssetActor.Get(); } + + /** + * Helper function for getting the UHoudiniAssetComponent of the HDA, if HoudiniAssetObject is a + * UHoudiniAssetComponent or an AHoudiniAssetActor. + * @return The instantiated AHoudiniAssetActor, if HoudiniAssetObject is a + * UHoudiniAssetComponent or an AHoudiniAssetActor, otherwise nullptr. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UHoudiniAssetComponent* GetHoudiniAssetComponent() const; + FORCEINLINE + virtual UHoudiniAssetComponent* GetHoudiniAssetComponent_Implementation() const { return CachedHoudiniAssetComponent.Get(); } + + /** + * Get the Temp Folder fallback as configured on asset details panel + * @param OutDirectoryPath The currently configured fallback temporary cook folder. + * @return true if the wrapper is valid and the value was fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetTemporaryCookFolder(FDirectoryPath& OutDirectoryPath) const; + + /** + * Set the Temp Folder fallback as configured on asset details panel. Returns true if the value was changed. + * @param InDirectoryPath The new temp folder fallback to set. + * @return true if the wrapper is valid and the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetTemporaryCookFolder(const FDirectoryPath& InDirectoryPath) const; + + /** + * Get the Bake Folder fallback as configured on asset details panel. + * @param OutDirectoryPath The current bake folder fallback. + * @return true if the wrapper is valid and the value was fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetBakeFolder(FDirectoryPath& OutDirectoryPath) const; + + /** + * Set the Bake Folder fallback as configured on asset details panel. Returns true if the value was changed. + * @param InDirectoryPath The new bake folder fallback. + * @return true if the wrapper is valid and the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetBakeFolder(const FDirectoryPath& InDirectoryPath) const; + + // Houdini Engine Actions + + /** Delete the instantiated asset from its level and mark the wrapper for destruction. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool DeleteInstantiatedAsset(); + + /** Rebuild the HDA node in Houdini. Returns true if the asset was successfully marked as needing to be rebuilt. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool Rebuild(); + + // Cooking + + /** Recook the asset. Returns true if the asset was successfully marked as needing to be cooked. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool Recook(); + + /** + * Enable or disable auto cooking of the asset (on parameter changes, input updates and transform changes, for + * example) + * @param bInSetEnabled Whether or not enable auto cooking. + * @return true if the value was changed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetAutoCookingEnabled(const bool bInSetEnabled); + + /** Returns true if auto cooking is enabled for this instantiated asset. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsAutoCookingEnabled() const; + + // Baking + + /** + * Bake all outputs of the instantiated asset using the settings configured on the asset. + * @return true if the wrapper is valid and the baking process was started. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool BakeAllOutputs(); + + /** + * Bake all outputs of the instantiated asset using the specified settings. + * @param InBakeOption The bake method/target, (to actor vs blueprint, for example). + * @param bInReplacePreviousBake If true, replace the previous bake output (assets + actor) with the + * new results. + * @param bInRemoveTempOutputsOnSuccess If true, the temporary outputs of the wrapper asset are removed + * after a successful bake. + * @param bInRecenterBakedActors If true, recenter the baked actors to their bounding box center after the bake. + * @return true if the wrapper is valid and the baking process was started. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool BakeAllOutputsWithSettings( + EHoudiniEngineBakeOption InBakeOption, + bool bInReplacePreviousBake=false, + bool bInRemoveTempOutputsOnSuccess=false, + bool bInRecenterBakedActors=false); + + /** + * Set whether to automatically bake all outputs after a successful cook. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetAutoBakeEnabled(const bool bInAutoBakeEnabled); + + /** Returns true if auto bake is enabled. See SetAutoBakeEnabled. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsAutoBakeEnabled() const; + + /** + * Sets the bake method to use (to actor, blueprint, foliage). + * @param InBakeMethod The new bake method to set. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetBakeMethod(const EHoudiniEngineBakeOption InBakeMethod); + + /** + * Gets the currently set bake method to use (to actor, blueprint, foliage). + * @param OutBakeMethod The current bake method. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetBakeMethod(EHoudiniEngineBakeOption& OutBakeMethod); + + /** + * Set the bRemoveOutputAfterBake property, that controls if temporary outputs are removed after a successful bake. + * @param bInRemoveOutputAfterBake If true, then after a successful bake, the HACs outputs will be cleared and + * removed. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetRemoveOutputAfterBake(const bool bInRemoveOutputAfterBake); + + /** + * Get the bRemoveOutputAfterBake property, that controls if temporary outputs are removed after a successful bake. + * @return true if bRemoveOutputAfterBake is true. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetRemoveOutputAfterBake() const; + + /** + * Set the bRecenterBakedActors property that controls if baked actors are recentered around their bounding box center. + * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center after bake. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetRecenterBakedActors(const bool bInRecenterBakedActors); + + /** Gets the bRecenterBakedActors property. If true, recenter baked actors to their bounding box center after bake. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetRecenterBakedActors() const; + + /** + * Set the bReplacePreviousBake property, if true, replace the previously baked output (if any) instead of creating + * new objects. + * @param bInReplacePreviousBake If true, replace the previously baked output (if any) instead of creating new + * objects. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetReplacePreviousBake(const bool bInReplacePreviousBake); + + /** Get the bReplacePreviousBake property. + * @return The value of bReplacePreviousBake. If true, previous bake output (if any) will be replaced by subsequent + * bakes. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetReplacePreviousBake() const; + + // Parameters + + /** + * Set the value of a float-based parameter. + * Supported parameter types: + * - Float + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. Defaults to 0. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetFloatParameterValue(FName InParameterTupleName, float InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a float parameter. Returns true if the parameter and index was found and the value set in OutValue. + * Supported parameter types: + * - Float + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the wrapper is valid and the parameter was found. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetFloatParameterValue(FName InParameterTupleName, float& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of a color parameter. + * Supported parameter types: + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetColorParameterValue(FName InParameterTupleName, const FLinearColor& InValue, bool bInMarkChanged=true); + + /** + * Get the value of a color parameter. Returns true if the parameter was found and the value set in OutValue. + * Supported parameter types: + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetColorParameterValue(FName InParameterTupleName, FLinearColor& OutValue) const; + + /** + * Set the value of a int32 parameter. + * Supported parameter types: + * - Int + * - IntChoice + * - MultiParm + * - Toggle + * - Folder (set the folder as currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. Defaults to 0. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetIntParameterValue(FName InParameterTupleName, int32 InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a int32 parameter. + * Supported parameter types: + * - Int + * - IntChoice + * - MultiParm + * - Toggle + * - Folder (get if the folder is currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the parameter and index was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetIntParameterValue(FName InParameterTupleName, int32& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of a bool parameter. + * Supported parameter types: + * - Toggle + * - Folder (set the folder as currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetBoolParameterValue(FName InParameterTupleName, bool InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a bool parameter. + * Supported parameter types: + * - Toggle + * - Folder (get if the folder is currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the parameter and index was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetBoolParameterValue(FName InParameterTupleName, bool& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of a String parameter. + * Supported parameter types: + * - String + * - StringChoice + * - StringAssetRef + * - File + * - FileDir + * - FileGeo + * - FileImage + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetStringParameterValue(FName InParameterTupleName, const FString& InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a String parameter. + * Supported parameter types: + * - String + * - StringChoice + * - StringAssetRef + * - File + * - FileDir + * - FileGeo + * - FileImage + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the parameter was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetStringParameterValue(FName InParameterTupleName, FString& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of an AssetRef parameter. + * Supported parameter types: + * - StringAssetRef + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetAssetRefParameterValue(FName InParameterTupleName, UObject* InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of an AssetRef parameter. + * Supported parameter types: + * - StringAssetRef + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return True if the parameter was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetAssetRefParameterValue(FName InParameterTupleName, UObject*& OutValue, int32 InAtIndex=0) const; + + /** + * Sets the number of points of the specified ramp parameter. This will insert or remove points from the end + * as necessary. + * @param InParameterTupleName The name of the parameter tuple. + * @param InNumPoints The new number of points to set. Must be >= 1. + * @return true if the parameter was found and the number of points set, or if the number of points was already InNumPoints. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetRampParameterNumPoints(FName InParameterTupleName, const int32 InNumPoints) const; + + /** + * Gets the number of points of the specified ramp parameter. + * @param InParameterTupleName The name of the parameter tuple. + * @param OutNumPoints The number of points the ramp has. + * @return true if the parameter was found and the number of points fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetRampParameterNumPoints(FName InParameterTupleName, int32& OutNumPoints) const; + + /** + * Set the position, value and interpolation of a point of a FloatRamp parameter. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to set. + * @param InPointPosition The point position to set [0, 1]. + * @param InPointValue The value to set for the point. + * @param InInterpolationType The interpolation to set at the point. Defaults to EHoudiniPublicAPIRampInterpolationType.Linear. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetFloatRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const float InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType=EHoudiniPublicAPIRampInterpolationType::LINEAR, + const bool bInMarkChanged=true); + + /** + * Get the position, value and interpolation of a point of a FloatRamp parameter. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to get. + * @param OutPointPosition The point position [0, 1]. + * @param OutPointValue The value at the point. + * @param OutInterpolationType The interpolation of the point. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetFloatRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + float& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const; + + /** + * Set all of the points (position, value and interpolation) of float ramp. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InRampPoints An array of structs to set as the ramp's points. + * @param bInMarkChanged If true, parameters are marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the values were set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetFloatRampParameterPoints( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged=true); + + /** + * Get the all of the points (position, value and interpolation) of a FloatRamp parameter. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param OutRampPoints The array to populate with the ramp's points. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetFloatRampParameterPoints( + FName InParameterTupleName, + TArray& OutRampPoints) const; + + /** + * Set the position, value and interpolation of a point of a ColorRamp parameter. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to set. + * @param InPointPosition The point position to set [0, 1]. + * @param InPointValue The value to set for the point. + * @param InInterpolationType The interpolation to set at the point. Defaults to EHoudiniPublicAPIRampInterpolationType.Linear. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetColorRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const FLinearColor& InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType=EHoudiniPublicAPIRampInterpolationType::LINEAR, + const bool bInMarkChanged=true); + + /** + * Get the position, value and interpolation of a point of a ColorRamp parameter. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to get. + * @param OutPointPosition The point position [0, 1]. + * @param OutPointValue The value at the point. + * @param OutInterpolationType The interpolation of the point. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetColorRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + FLinearColor& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const; + + /** + * Set all of the points (position, value and interpolation) of color ramp. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InRampPoints An array of structs to set as the ramp's points. + * @param bInMarkChanged If true, parameters are marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the values were set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetColorRampParameterPoints( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged=true); + + /** + * Get the all of the points (position, value and interpolation) of a ColorRamp parameter. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param OutRampPoints The array to populate with the ramp's points. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetColorRampParameterPoints( + FName InParameterTupleName, + TArray& OutRampPoints) const; + + /** + * Trigger / click the specified button parameter. + * @return True if the button was found and triggered/clicked. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool TriggerButtonParameter(FName InButtonParameterName); + + /** + * Gets all parameter tuples (with their values) from this asset and outputs it to OutParameterTuples. + * @param OutParameterTuples Populated with all parameter tuples and their values. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetParameterTuples(TMap& OutParameterTuples) const; + + /** + * Sets all parameter tuple values (matched by name and compatible type) from InParameterTuples on this + * instantiated asset. + * @param InParameterTuples The parameter tuples to set. + * @return false if any entry in InParameterTuples could not be found on the asset or had an incompatible type/size. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetParameterTuples(const TMap& InParameterTuples); + + // Inputs + + /** + * Creates an empty input wrapper. + * @param InInputClass the class of the input to create. See the UHoudiniPublicAPIInput class hierarchy. + * @return The newly created input wrapper, or null if the input wrapper could not be created. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(DeterminesOutputType="InInputClass")) + UHoudiniPublicAPIInput* CreateEmptyInput(TSubclassOf InInputClass); + + /** + * Get the number of node inputs supported by the asset. + * @return The number of node inputs (inputs on the HDA node, excluding parameter-based inputs). Returns -1 if the + * asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UPARAM(DisplayName="OutNumNodeInputs") int32 GetNumNodeInputs() const; + + /** + * Set a node input at the specific index. + * @param InNodeInputIndex The index of the node input, starts at 0. + * @param InInput The input wrapper to use to set the input. + * @return false if the the input could not be set, for example, if the wrapper is invalid, or if the input index + * is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetInputAtIndex(const int32 InNodeInputIndex, const UHoudiniPublicAPIInput* InInput); + + /** + * Get the node input at the specific index and sets OutInput. This is a copy of the input structure. Changes + * properties in OutInput won't affect the instantiated HDA until a subsequent call to SetInputAtIndex. + * @param InNodeInputIndex The index of the node input to get. + * @param OutInput Copy of the input configuration and data for node input index InNodeInputIndex. + * @return false if the input could be fetched, for example if the wrapper is invalid or the input index is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetInputAtIndex(const int32 InNodeInputIndex, UHoudiniPublicAPIInput*& OutInput); + + /** + * Set node inputs at the specified indices via a map. + * @param InInputs A map of node input index to input wrapper to use to set inputs on the instantiated asset. + * @return true if all inputs from InInputs were set successfully. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetInputsAtIndices(const TMap& InInputs); + + /** + * Get all node inputs. + * @param OutInputs All node inputs as a map, with the node input index as key. The input configuration is copied + * from instantiated asset, and changing an input property from the entry in this map will not affect the + * instantiated asset until a subsequent SetInputsAtIndices() call or SetInputAtIndex() call. + * @return false if the wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetInputsAtIndices(TMap& OutInputs); + + /** + * Set a parameter-based input via parameter name. + * @param InParameterName The name of the input parameter. + * @param InInput The input wrapper to use to set/configure the input. + * @return false if the wrapper is invalid, InParameterName is not a valid input parameter, or if InInput could + * not be used to successfully configure/set the input. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool SetInputParameter(const FName& InParameterName, const UHoudiniPublicAPIInput* InInput); + + /** + * Get a parameter-based input via parameter name. + * @param InParameterName The name of the input parameter. + * @param OutInput A copy of the input configuration for InParameterName. + * @return false if the wrapper is invalid, InParameterName is not a valid input parameter, or the current input + * configuration could not be successfully copied to a new UHoudiniPublicAPIInput wrapper. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool GetInputParameter(const FName& InParameterName, UHoudiniPublicAPIInput*& OutInput); + + /** + * Set a parameter-based inputs via a map, + * @param InInputs A map of input parameter names to input wrapper to use to set inputs on the instantiated asset. + * @return true if all inputs from InInputs were set successfully. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool SetInputParameters(const TMap& InInputs); + + /** + * Get a parameter-based inputs as a map + * @param OutInputs All parameter inputs as a map, with the input parameter name as key. The input configuration is copied + * from instantiated asset, and changing an input property from the entry in this map will not affect the + * instantiated asset until a subsequent SetInputParameters() call or SetInputParameter() call. + * @return false if the wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool GetInputParameters(TMap& OutInputs); + + // Outputs + + /** + * Gets the number of outputs of the instantiated asset. + * @return the number of outputs of the instantiated asset. -1 if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + int32 GetNumOutputs() const; + + /** + * Gets the output type of the output at index InIndex. + * @param InIndex The output index to get the type for. + * @return the output type of the output at index InIndex. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + EHoudiniOutputType GetOutputTypeAt(const int32 InIndex) const; + + /** + * Populates OutIdentifiers with the output object identifiers of all the output objects of the output at InIndex. + * @param InIndex The output index to get output identifiers for. + * @param OutIdentifiers The output identifiers of the output objects at output index InIndex. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetOutputIdentifiersAt(const int32 InIndex, TArray& OutIdentifiers) const; + + /** + * Gets the output object at InIndex identified by InIdentifier. + * @param InIndex The output index to get output object from. + * @param InIdentifier The output identifier of the output object to get from output index InIndex. + * @return nullptr if the index/identifier is invalid or if the asset/wrapper is invalid, otherwise the output + * object. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UObject* GetOutputObjectAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; + + /** + * Gets the output component at InIndex identified by InIdentifier. + * @param InIndex The output index to get output component from. + * @param InIdentifier The output identifier of the output component to get from output index InIndex. + * @return nullptr if the index/identifier is invalid or if the asset/wrapper is invalid, otherwise the output + * component. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UObject* GetOutputComponentAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; + + /** + * Gets the output's fallback BakeName (as configured on the output details panel) for the output at InIndex + * identified by InIdentifier. + * @param InIndex The output index of the output object to get fallback BakeName for. + * @param InIdentifier The output identifier of the output object to get fallback BakeName for. + * @param OutBakeName The fallback BakeName configured for the output object identified by InIndex and InIdentifier. + * @return false if the index/identifier is invalid or if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetOutputBakeNameFallbackAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, FString& OutBakeName) const; + + /** + * Sets the output's fallback BakeName (as configured on the output details panel) for the output at InIndex + * identified by InIdentifier. + * @param InIndex The output index of the output object to set fallback BakeName for. + * @param InIdentifier The output identifier of the output object to set fallback BakeName for. + * @param InBakeName The fallback BakeName to set for the output object identified by InIndex and InIdentifier. + * @return false if the index/identifier is invalid or if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetOutputBakeNameFallbackAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FString& InBakeName); + + /** + * Bake the specified output object to the content browser. + * @param InIndex The output index of the output object to bake. + * @param InIdentifier The output identifier of the output object to bake. + * @param InBakeName The bake name to bake with. + * @param InLandscapeBakeType For landscape assets, the output bake type. + * @return true if the output was baked successfully, false if the wrapper/asset is invalid, or the output index + * and output identifier combination is invalid, or if baking failed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool BakeOutputObjectAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FName InBakeName=NAME_None, const EHoudiniLandscapeOutputBakeType InLandscapeBakeType=EHoudiniLandscapeOutputBakeType::InValid); + + /** + * Returns true if the wrapped asset has any proxy output on any outputs. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool HasAnyCurrentProxyOutput() const; + + /** + * Returns true if the wrapped asset has any proxy output at output InIndex. + * @param InIndex The output index to check for proxies. + * @return true if the wrapped asset has any proxy output at output InIndex. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool HasAnyCurrentProxyOutputAt(const int32 InIndex) const; + + /** + * Returns true if the output object at output InIndex with identifier InIdentifier is a proxy. + * @param InIndex The output index of the output object to check. + * @param InIdentifier The output identifier of the output object at output index InIndex to check. + * @return true if the output object at output InIndex with identifier InIdentifier is a proxy. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsOutputCurrentProxyAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; + + // Proxy mesh + + /** + * Refines all current proxy mesh outputs (if any) to static mesh. This could trigger a cook if the asset is loaded + * and has no cook data. + * @param bInSilent If true, then slate notifications about the refinement process are not shown. + * @return Whether the refinement process is needed, requires a pending asynchronous cook, or was completed + * synchronously. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + EHoudiniProxyRefineRequestResult RefineAllCurrentProxyOutputs(const bool bInSilent); + + // PDG + + /** + * Returns true if the wrapped asset is valid and has a PDG asset link. + * @return true if the wrapped asset is valid and has a PDG asset link. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool HasPDGAssetLink() const; + + /** + * Gets the paths (relative to the instantiated asset) of all TOP networks in the HDA. + * @param OutTOPNetworkPaths The relative paths of the TOP networks in the HDA. + * @return false if the asset/wrapper is invalid, or does not contain any TOP networks. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGTOPNetworkPaths(TArray& OutTOPNetworkPaths) const; + + /** + * Gets the paths (relative to the specified TOP network) of all TOP nodes in the network. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(), to fetch TOP node paths for. + * @return false if the asset/wrapper is invalid, or does not contain any TOP networks. + */ + // Returns false if the asset/wrapper is invalid, or does not contain the specified TOP network. + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGTOPNodePaths(const FString& InNetworkRelativePath, TArray& OutTOPNodePaths) const; + + /** + * Dirty all TOP networks in this asset. + * @return true if TOP networks were dirtied. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGDirtyAllNetworks(); + + /** + * Dirty the specified TOP network. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @return true if the TOP network was dirtied. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGDirtyNetwork(const FString& InNetworkRelativePath); + + /** + * Dirty the specified TOP node. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @param InNodeRelativePath The relative path of the TOP node inside the specified TOP network. + * @return true if TOP nodes were dirtied. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGDirtyNode(const FString& InNetworkRelativePath, const FString& InNodeRelativePath); + + // // Cook all outputs for all TOP networks in this asset. + // // Returns true if TOP networks were set to cook. + // UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + // bool PDGCookOutputsForAllNetworks(); + + /** + * Cook all outputs for the specified TOP network. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @return true if the TOP network was set to cook. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGCookOutputsForNetwork(const FString& InNetworkRelativePath); + + /** + * Cook the specified TOP node. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @param InNodeRelativePath The relative path of the TOP node inside the specified TOP network. + * @return true if the TOP node was set to cook. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGCookNode(const FString& InNetworkRelativePath, const FString& InNodeRelativePath); + + /** + * Bake all outputs of the instantiated asset's PDG contexts using the settings configured on the asset. + * @return true if the bake was successful. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGBakeAllOutputs(); + + /** + * Bake all outputs of the instantiated asset's PDG contexts using the specified settings. + * @param InBakeOption The bake option (to actors, blueprints or foliage). + * @param InBakeSelection Whether to bake outputs from all networks, the selected network or the selected node. + * @param InBakeReplacementMode Whether to replace previous bake results/existing assets with the same name + * when baking. + * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center. Defaults to false. + * @return true if the bake was successful. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGBakeAllOutputsWithSettings( + const EHoudiniEngineBakeOption InBakeOption, + const EPDGBakeSelectionOption InBakeSelection, + const EPDGBakePackageReplaceModeOption InBakeReplacementMode, + const bool bInRecenterBakedActors=false); + + /** + * Set whether to automatically bake PDG work items after a successfully loading them. + * @param bInAutoBakeEnabled If true, automatically bake work items after successfully loading them. + * @return false if the asset/wrapper is invalid, or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGAutoBakeEnabled(const bool bInAutoBakeEnabled); + + /** Returns true if PDG auto bake is enabled. See SetPDGAutoBakeEnabled(). */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsPDGAutoBakeEnabled() const; + + /** + * Sets the bake method to use for PDG baking (to actor, blueprint, foliage). + * @param InBakeMethod The new bake method to set. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGBakeMethod(const EHoudiniEngineBakeOption InBakeMethod); + + /** + * Gets the currently set bake method to use for PDG baking (to actor, blueprint, foliage). + * @param OutBakeMethod The current bake method. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGBakeMethod(EHoudiniEngineBakeOption& OutBakeMethod); + + /** + * Set which outputs to bake for PDG, for example, all, selected network, selected node + * @param InBakeSelection The new bake selection. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGBakeSelection(const EPDGBakeSelectionOption InBakeSelection); + + /** + * Get which outputs to bake for PDG, for example, all, selected network, selected node + * @param OutBakeSelection The current bake selection setting. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGBakeSelection(EPDGBakeSelectionOption& OutBakeSelection); + + /** + * Setter for the bRecenterBakedActors property, that determines if baked actors are recentered to their bounding + * box center after a PDG bake, on the PDG asset link. + * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center after bake (PDG) + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGRecenterBakedActors(const bool bInRecenterBakedActors); + + /** + * Getter for the bRecenterBakedActors property, that determines if baked actors are recentered to their bounding + * box center after a PDG bake, on the PDG asset link. + * @return true if baked actors should be recentered to their bounding box center after bake (PDG) + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGRecenterBakedActors() const; + + /** + * Set the replacement mode to use for PDG baking (replace previous bake output vs increment) + * @param InBakingReplacementMode The new replacement mode to set. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGBakingReplacementMode(const EPDGBakePackageReplaceModeOption InBakingReplacementMode); + + /** + * Get the replacement mode to use for PDG baking (replace previous bake output vs increment) + * @param OutBakingReplacementMode The current replacement mode. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGBakingReplacementMode(EPDGBakePackageReplaceModeOption& OutBakingReplacementMode) const; + + /** + * Getter for the OnPreInstantiationDelegate, broadcast before the HDA is instantiated. The HDA's default parameter + * definitions are available, but the node has not yet been instantiated in HAPI/Houdini Engine. Parameter values + * can be set at this point. + */ + FOnHoudiniAssetStateChange& GetOnPreInstantiationDelegate() { return OnPreInstantiationDelegate; } + + /** + * Getter for the OnPostInstantiationDelegate, broadcast after the HDA is instantiated. This is a good place to + * set/configure inputs before the first cook. + */ + FOnHoudiniAssetStateChange& GetOnPostInstantiationDelegate() { return OnPostInstantiationDelegate; } + + /** + * Getter for the OnPostCookDelegate, broadcast after the HDA has cooked. + */ + FOnHoudiniAssetPostCook& GetOnPostCookDelegate() { return OnPostCookDelegate; } + + /** + * Getter for the OnPreProcessStateExitedDelegate, broadcast after the output pre-processing phase of the HDA, + * but before it enters the post processing phase. When this delegate is broadcast output objects/assets have not + * yet been created. + */ + FOnHoudiniAssetStateChange& GetOnPreProcessStateExitedDelegate() { return OnPreProcessStateExitedDelegate; } + + /** + * Getter for the OnPostProcessingDelegate, broadcast after the HDA has processed its outputs and created output + * objects/assets. + */ + FOnHoudiniAssetStateChange& GetOnPostProcessingDelegate() { return OnPostProcessingDelegate; } + + /** + * Getter for the OnPostBakeDelegate, broadcast after the HDA has finished baking outputs (not called for + * individual outputs that are baked to the content browser). + */ + FOnHoudiniAssetPostBake& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } + + /** + * Getter for the OnPostPDGTOPNetworkCookDelegate, broadcast after the HDA/PDG has cooked a TOP Network. Work item + * results have not necessarily yet been loaded. + */ + FOnHoudiniAssetPostCook& GetOnPostPDGTOPNetworkCookDelegate() { return OnPostPDGTOPNetworkCookDelegate; } + + /** + * Getter for the OnPostPDGBakeDelegate, broadcast after the HDA/PDG has finished baking outputs (not called for + * individual outputs that are baked to the content browser). + */ + FOnHoudiniAssetPostBake& GetOnPostPDGBakeDelegate() { return OnPostPDGBakeDelegate; } + + /** + * Getter for the OnProxyMeshesRefinedDelegate, broadcast for each proxy mesh of the HDA's outputs that has been + * refined to a UStaticMesh. + */ + FOnHoudiniAssetProxyMeshesRefinedDelegate& GetOnProxyMeshesRefinedDelegate() { return OnProxyMeshesRefinedDelegate; } + +protected: + + /** This will unwrap/unbind the currently wrapped instantiated asset. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void ClearHoudiniAssetObject(); + + /** + * Attempt to bind to the asset's PDG asset link, if it has one, and if the wrapper is not already bound to its + * events. + */ + UFUNCTION() + bool BindToPDGAssetLink(); + + /** Handler that is bound to the wrapped HAC's state change delegate. */ + UFUNCTION() + void HandleOnHoudiniAssetComponentStateChange(UHoudiniAssetComponent* InHAC, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState); + + /** Handler that is bound to the wrapped HAC's PostCook delegate. */ + UFUNCTION() + void HandleOnHoudiniAssetComponentPostCook(UHoudiniAssetComponent* InHAC, const bool bInCookSuccess); + + /** Handler that is bound to the wrapped HAC's PostBake delegate. */ + UFUNCTION() + void HandleOnHoudiniAssetComponentPostBake(UHoudiniAssetComponent* InHAC, const bool bInBakeSuccess); + + /** Handler that is bound to the wrapped PDG asset link's OnPostTOPNetworkCookDelegate delegate. */ + UFUNCTION() + void HandleOnHoudiniPDGAssetLinkTOPNetPostCook(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InTOPNet, const bool bInAnyWorkItemsFailed); + + /** Handler that is bound to the wrapped PDG asset link's OnPostBake delegate. */ + UFUNCTION() + void HandleOnHoudiniPDGAssetLinkPostBake(UHoudiniPDGAssetLink* InPDGAssetLink, const bool bInBakeSuccess); + + /** + * Handler that is bound to FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefined(). It is called for any HAC + * that has its proxy meshes refined. If relevant for this wrapped asset, then + * #OnProxyMeshesRefinedDelegate is broadcast. + */ + UFUNCTION() + void HandleOnHoudiniProxyMeshesRefinedGlobal(UHoudiniAssetComponent* InHAC, const EHoudiniProxyRefineResult InResult); + + /** + * Helper function for getting the instantiated asset's AHoudiniAssetActor. If there is no valid + * AHoudiniAssetActor an error is set with SetErrorMessage() and false is returned. + * @param OutActor Set to the AHoudiniAssetActor of the wrapped asset, if found. + * @return true if the wrapped asset has/is a valid AHoudiniAssetActor, false otherwise. + */ + bool GetValidHoudiniAssetActorWithError(AHoudiniAssetActor*& OutActor) const; + + /** + * Helper function for getting the instantiated asset's UHoudiniAssetComponent. If there is no valid + * HoudiniAssetComponent an error is set with SetErrorMessage() and false is returned. + * @param OutHAC Set to the HoudiniAssetComponent of the wrapped asset, if found. + * @return true if the wrapped asset has a valid HoudiniAssetComponent, false otherwise. + */ + bool GetValidHoudiniAssetComponentWithError(UHoudiniAssetComponent*& OutHAC) const; + + /** + * Helper function for getting a valid output at the specified index. If there is no valid + * UHoudiniOutput at that index (either the index is out of range or the output is null/invalid) an error is set + * with SetErrorMessage() and false is returned. + * @param InOutputIndex The output index. + * @param OutOutput Set to the valid UHoudiniOutput at InOutputIndex, if found. + * @return true if there is a valid UHoudiniOutput at index InOutputIndex. + */ + bool GetValidOutputAtWithError(const int32 InOutputIndex, UHoudiniOutput*& OutOutput) const; + + /** Helper function for getting the instantiated asset's PDG asset link. */ + UHoudiniPDGAssetLink* GetHoudiniPDGAssetLink() const; + + /** + * Helper function for getting the instantiated asset's UHoudiniPDGAssetLink. If there is no valid + * UHoudiniPDGAssetLink an error is set with SetErrorMessage() and false is returned. + * @param OutAssetLink Set to the UHoudiniPDGAssetLink of the wrapped asset, if found. + * @return true if the wrapped asset has a valid UHoudiniPDGAssetLink, false otherwise. + */ + bool GetValidHoudiniPDGAssetLinkWithError(UHoudiniPDGAssetLink*& OutAssetLink) const; + + /** Helper function for getting a valid parameter by name */ + UHoudiniParameter* FindValidParameterByName(const FName& InParameterTupleName) const; + + /** + * Helper function to find the appropriate array index for a ramp point. + * @param InParam The parameter. + * @param InIndex The index of the ramp point to get. If INDEX_NONE, all points are fetched. + * @param OutPointData Array populated with all fetched points. The bool in the pair is true if the + * object is UHoudiniParameterRampFloatPoint or UHoudiniParameterRampColorPoint and false if it is + * UHoudiniParameterRampModificationEvent. + * @return true if the point was, or all points were, fetched successfully. + */ + bool FindRampPointData( + UHoudiniParameter* const InParam, const int32 InIndex, TArray>& OutPointData) const; + + /** + * Set the position, value and interpolation of a point of a ramp parameter. + * Supported parameter types: + * - FloatRamp + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to set. + * @param InPosition The point position [0, 1]. + * @param InFloatValue The float value at the point (if this is a float ramp). + * @param InColorValue The color value at the point (if this is a color ramp). + * @param InInterpolation The interpolation of the point. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + bool SetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPosition, + const float InFloatValue, + const FLinearColor& InColorValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation, + const bool bInMarkChanged=true); + + /** + * Get the position, value and interpolation of a point of a ramp parameter. + * Supported parameter types: + * - FloatRamp + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to get. + * @param OutPosition The point position [0, 1]. + * @param OutFloatValue The float value at the point (if this is a float ramp). + * @param OutColorValue The color value at the point (if this is a color ramp). + * @param OutInterpolation The interpolation of the point. + * @return True if the parameter was found and output values set. + */ + bool GetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPosition, + float& OutFloatValue, + FLinearColor& OutColorValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolation) const; + + /** Helper function for getting an input by node index */ + UHoudiniInput* GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex); + const UHoudiniInput* GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) const; + + /** Helper function for getting an input by parameter name */ + UHoudiniInput* FindValidHoudiniNodeInputParameter(const FName& InInputParameterName); + const UHoudiniInput* FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) const; + + /** Helper function for populating a UHoudiniPublicAPIInput from a UHoudiniInput */ + bool CreateAndPopulateAPIInput(const UHoudiniInput* InHoudiniInput, UHoudiniPublicAPIInput*& OutAPIInput); + /** Helper function for populating a UHoudiniInput from a UHoudiniPublicAPIInput */ + bool PopulateHoudiniInput(const UHoudiniPublicAPIInput* InAPIInput, UHoudiniInput* InHoudiniInput) const; + + /** + * Helper functions for getting a TOP network by path. If the TOP network could not be found, an error is set + * with SetErrorMessage, and false is returned. + * @param InNetworkRelativePath The relative path to the network inside the asset. + * @param OutNetworkIndex The index to the network in the asset link's AllNetworks array. + * @param OutNetwork The network that was found at InNetworkRelativePath. + * @return true if the network was found and is valid, false otherwise. + */ + bool GetValidTOPNetworkByPathWithError(const FString& InNetworkRelativePath, int32& OutNetworkIndex, UTOPNetwork*& OutNetwork) const; + + /** + * Helper functions for getting a TOP node by path. If the TOP node could not be found, an error is set + * with SetErrorMessage, and false is returned. + * @param InNetworkRelativePath The relative path to the network inside the asset. + * @param InNodeRelativePath The relative path to the node inside the network. + * @param OutNetworkIndex The index to the network in the asset link's AllTOPNetworks array. + * @param OutNodeIndex The index to the TOP node in the network's AllTOPNodes array. + * @param OutNode The node that was found. + * @return true if the node was found and is valid, false otherwise. + */ + bool GetValidTOPNodeByPathWithError( + const FString& InNetworkRelativePath, + const FString& InNodeRelativePath, + int32& OutNetworkIndex, + int32& OutNodeIndex, + UTOPNode*& OutNode) const; + + /** The wrapped Houdini Asset object (not the uasset, an AHoudiniAssetActor or UHoudiniAssetComponent). */ + UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") + TSoftObjectPtr HoudiniAssetObject; + + /** The wrapped AHoudiniAssetActor (derived from HoudiniAssetObject when calling WrapHoudiniAssetObject()). */ + UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") + TSoftObjectPtr CachedHoudiniAssetActor; + + /** The wrapped UHoudiniAssetComponent (derived from HoudiniAssetObject when calling WrapHoudiniAssetObject()). */ + UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") + TSoftObjectPtr CachedHoudiniAssetComponent; + + /** + * Delegate that is broadcast when entering the PreInstantiation state: the HDA's default parameter definitions are + * available, but the node has not yet been instantiated in HAPI/Houdini Engine. Parameters can be set at this point. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPreInstantiationDelegate; + + /** + * Delegate that is broadcast after the asset was successfully instantiated. This is a good place to set/configure + * inputs before the first cook. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPostInstantiationDelegate; + + /** Delegate that is broadcast after a cook completes. Output objects/assets have not yet been created/updated. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostCook OnPostCookDelegate; + + /** Delegate that is broadcast when PreProcess is exited. Output objects/assets have not yet been created/updated. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPreProcessStateExitedDelegate; + + /** + * Delegate that is broadcast when the Processing state is exited and the None state is entered. Output objects + * assets have been created/updated. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPostProcessingDelegate; + + /** + * Delegate that is broadcast after baking the asset (not called for individual outputs that are baked to the + * content browser). + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostBake OnPostBakeDelegate; + + /** + * Delegate that is broadcast after a cook of a TOP network completes. Work item results have not necessarily yet + * been loaded. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostCook OnPostPDGTOPNetworkCookDelegate; + + /** + * Delegate that is broadcast after baking PDG outputs (not called for individual outputs that are baked to the + * content browser). + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostBake OnPostPDGBakeDelegate; + + /** Delegate that is broadcast after refining all proxy meshes for this wrapped asset. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetProxyMeshesRefinedDelegate OnProxyMeshesRefinedDelegate; + + /** + * This starts as false and is set to true in HandleOnHoudiniAssetComponentStateChange during post instantiation, + * once we have checked for a PDG asset link and configured the bindings if there is one. + */ + UPROPERTY() + bool bAssetLinkSetupAttemptComplete; + + // Delegate handles + + /** Handle for the binding to the HAC's asset state change delegate */ + FDelegateHandle OnAssetStateChangeDelegateHandle; + /** Handle for the binding to the HAC's post cook delegate */ + FDelegateHandle OnPostCookDelegateHandle; + /** Handle for the binding to the HAC's post bake delegate */ + FDelegateHandle OnPostBakeDelegateHandle; + /** Handle for the binding to the global proxy mesh refined delegate */ + FDelegateHandle OnHoudiniProxyMeshesRefinedDelegateHandle; + /** Handle for the binding to the HAC's PDG asset link's post TOP Network cook delegate */ + FDelegateHandle OnPDGPostTOPNetworkCookDelegateHandle; + /** Handle for the binding to the HAC's PDG asset link's post bake delegate */ + FDelegateHandle OnPDGPostBakeDelegateHandle; + +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h new file mode 100644 index 000000000..173a83f99 --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h @@ -0,0 +1,48 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" + +#include "HoudiniPublicAPIBlueprintLib.generated.h" + +class UHoudiniPublicAPI; + +/** + * Houdini Public API Blueprint function library + */ +UCLASS(Category="Houdini Engine|Public API") +class UHoudiniPublicAPIBlueprintLib : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + +public: + /** Returns the Houdini Public API instance. */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "GetHoudiniEnginePublicAPI"), Category = "Houdini Engine") + static UHoudiniPublicAPI* GetAPI(); +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h new file mode 100644 index 000000000..7d5763409 --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h @@ -0,0 +1,552 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniInput.h" +#include "HoudiniPublicAPIObjectBase.h" + +#include "HoudiniPublicAPIInputTypes.generated.h" + + +/** + * This class is the base class of a hierarchy that represents an input to an HDA in the public API. + * + * Each type of input has a derived class: + * - Geometry: UHoudiniPublicAPIGeoInput + * - Asset: UHoudiniPublicAPIAssetInput + * - Curve: UHoudiniPublicAPICurveInput + * - World: UHoudiniPublicAPIWorldInput + * - Landscape: UHoudiniPublicAPILandscapeInput + * + * Each instance of one of these classes represents the configuration of an input and wraps the actor/object/asset + * used as the input. These instances are always treated as copies of the actual state of the HDA's input: changing + * a property of one of these instances does not immediately affect the instantiated HDA: one has to pass the input + * instances as arguments to UHoudiniPublicAPIAssetWrapper::SetInputAtIndex() or + * UHoudiniPublicAPIAssetWrapper::SetInputParameter() functions to actually change the inputs on the instantiated asset. + * A copy of the existing input state of an instantiated HDA can be fetched via + * UHoudiniPublicAPIAssetWrapper::GetInputAtIndex() and UHoudiniPublicAPIAssetWrapper::GetInputParameter(). + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPIInput : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIInput(); + + /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value .*/ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bKeepWorldTransform; + + /** + * Indicates that all the input objects are imported to Houdini as references instead of actual geo + * (for Geo/World/Asset input types only) + */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bImportAsReference; + + /** Returns true if InObject is acceptable for this input type. + * @param InObject The object to check for acceptance as an input object on this input. + * @return true if InObject is acceptable for this input type. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") + bool IsAcceptableObjectForInput(UObject* InObject) const; + virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const { return UHoudiniInput::IsObjectAcceptable(GetInputType(), InObject); } + + /** + * Sets the specified objects as the input objects. + * @param InObjects The objects to set as input objects for this input. + * @return false if any object was incompatible (all compatible objects are added). + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") + bool SetInputObjects(const TArray& InObjects); + + /** + * Gets the currently assigned input objects. + * @param OutObjects The current input objects of this input. + * @return true if input objects were successfully added to OutObjects (even if there are no input objects). + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") + bool GetInputObjects(TArray& OutObjects); + + /** + * Populate this input instance from the internal UHoudiniInput instance InInput. + * This copies the configuration and UHoudiniInputObject(s). + * @param InInput The internal UHoudiniInput to copy to this instance. + * @return false if there were any errors while copying the input data. + */ + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput); + + /** + * Update an internal UHoudiniInput instance InInput from this API input instance. + * This copies the configuration and UHoudiniInputObject(s) from this API instance instance to InInput. + * @param InInput The internal UHoudiniInput to update from to this API instance. + * @return false if there were any errors while copying the input data. + */ + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const; + +protected: + /** + * Copy any properties we need from the UHoudiniInputObject InSrc for the the input object it wraps. + * @param InInputObject The UHoudiniInputObject to copy from. + * @param InObject The input object to copy per-object properties for. + * @return false if the copy failed, for example if InSrc is invalid or of incompatible type. + */ + virtual bool CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject); + + /** + * Copy any properties for InObject from this input wrapper to InInputObject. + * @param InObject The input object to copy per-object properties for. + * @param InInputObject The Houdini input object to copy the properties to. + * @return false if the copy failed, for example if InSrc is invalid or of incompatible type. + */ + virtual bool CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const; + + /** + * Convert an object used as an input in an internal UHoudiniInput to be API compatible. + * @param InInternalInputObject An object as an input by UHoudiniInput + * @return An API compatible input object created from InInternalInputObject. By default this is just + * InInternalInputObject. + */ + virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) { return InInternalInputObject; } + + /** + * Convert an object used as an input in the API to be UHoudiniInput to be compatible and assign it to the + * InHoudiniInput at index InIndex. + * @param InAPIInputObject An input object in the API. + * @param InHoudiniInput The UHoudiniInput to assign the input to. + * @param InInputIndex The object index in InHoudiniInput to assign the converted input object to. + * @return A UHoudiniInput compatible input object created from InAPIInputObject. By default this is just + * InAPIInputObject. + */ + virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const; + + /** + * Returns the type of the input. Subclasses should override this and return the appropriate input type. + * The base class / default implementation returns EHoudiniInputType::Invalid + */ + virtual EHoudiniInputType GetInputType() const { return EHoudiniInputType::Invalid; } + + /** The input objects for this input. */ + UPROPERTY() + TArray InputObjects; + +}; + + +/** + * API wrapper input class for geometry inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPIGeoInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIGeoInput(); + + /** Indicates that the geometry must be packed before merging it into the input */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bPackBeforeMerge; + + /** Indicates that all LODs in the input should be marshalled to Houdini */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bExportLODs; + + /** Indicates that all sockets in the input should be marshalled to Houdini */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bExportSockets; + + /** Indicates that all colliders in the input should be marshalled to Houdini */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bExportColliders; + + /** + * Set the transform offset of the specified input object InObject (must already have been set via SetInputObjects()). + * @param InObject The input object to set a transform offset for. + * @param InTransform The transform offset to set. + * @return true if the object was found and the transform offset set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs", Meta=(AutoCreateRefTerm="InTransform")) + bool SetObjectTransformOffset(UObject* InObject, const FTransform& InTransform); + + /** + * Get the transform offset of the specified input object InObject (must already have been set via SetInputObjects()). + * @param InObject The input object to get a transform offset for. + * @param OutTransform The transform offset that was fetched. + * @return true if the object was found and the transform offset fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs", Meta=(AutoCreateRefTerm="InTransform")) + bool GetObjectTransformOffset(UObject* InObject, FTransform& OutTransform) const; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual bool CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) override; + + virtual bool CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const override; + + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Geometry; } + + /** Per-Input-Object data: the transform offset per input object. */ + UPROPERTY() + TMap InputObjectTransformOffsets; +}; + +UENUM(BlueprintType) +enum class EHoudiniPublicAPICurveType : uint8 +{ + Invalid = 0, + + Polygon = 1, + Nurbs = 2, + Bezier = 3, + Points = 4 +}; + +UENUM(BlueprintType) +enum class EHoudiniPublicAPICurveMethod : uint8 +{ + Invalid = 0, + + CVs = 1, + Breakpoints = 2, + Freehand = 3 +}; + +/** + * API wrapper input class for curve inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs | Input Objects") +class UHoudiniPublicAPICurveInputObject : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPICurveInputObject(); + + /** + * Set the points of the curve (replacing any previously set points with InCurvePoints). + * @param InCurvePoints The new points to set / replace the curve's points with. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetCurvePoints(const TArray& InCurvePoints); + FORCEINLINE + virtual void SetCurvePoints_Implementation(const TArray& InCurvePoints) { CurvePoints = InCurvePoints; } + + /** + * Append a point to the end of this curve. + * @param InCurvePoint The point to append. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Meta=(AutoCreateRefTerm="InCurvePoint"), Category="Houdini Engine | Public API | Inputs | Input Objects") + void AppendCurvePoint(const FTransform& InCurvePoint); + FORCEINLINE + virtual void AppendCurvePoint_Implementation(const FTransform& InCurvePoint) { CurvePoints.Add(InCurvePoint); } + + /** Remove all points from the curve. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void ClearCurvePoints(); + FORCEINLINE + virtual void ClearCurvePoints_Implementation() { CurvePoints.Empty(); } + + /** + * Get all points of the curve. + * @param OutCurvePoints Set to a copy of all of the points of the curve. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void GetCurvePoints(TArray& OutCurvePoints) const; + FORCEINLINE + virtual void GetCurvePoints_Implementation(TArray& OutCurvePoints) const { OutCurvePoints = CurvePoints; } + + /** Returns true if this is a closed curve. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool IsClosed() const; + FORCEINLINE + virtual bool IsClosed_Implementation() const { return bClosed; } + + /** + * Set whether the curve is closed or not. + * @param bInClosed The new closed setting for the curve: set to true if the curve is closed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetClosed(const bool bInClosed); + FORCEINLINE + virtual void SetClosed_Implementation(const bool bInClosed) { bClosed = bInClosed; } + + /** Returns true if the curve is reversed. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool IsReversed() const; + FORCEINLINE + virtual bool IsReversed_Implementation() const { return bReversed; } + + /** + * Set whether the curve is reversed or not. + * @param bInReversed The new reversed setting for the curve: set to true if the curve is reversed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetReversed(const bool bInReversed); + FORCEINLINE + virtual void SetReversed_Implementation(const bool bInReversed) { bReversed = bInReversed; } + + /** Returns the curve type (for example: polygon, nurbs, bezier) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveType GetCurveType() const; + FORCEINLINE + virtual EHoudiniPublicAPICurveType GetCurveType_Implementation() const { return CurveType; } + + /** + * Set the curve type (for example: polygon, nurbs, bezier). + * @param InCurveType The new curve type. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetCurveType(const EHoudiniPublicAPICurveType InCurveType); + FORCEINLINE + virtual void SetCurveType_Implementation(const EHoudiniPublicAPICurveType InCurveType) { CurveType = InCurveType; } + + /** Get the curve method, for example CVs, or freehand. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveMethod GetCurveMethod() const; + FORCEINLINE + virtual EHoudiniPublicAPICurveMethod GetCurveMethod_Implementation() const { return CurveMethod; } + + /** + * Set the curve method, for example CVs, or freehand. + * @param InCurveMethod The new curve method. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod); + FORCEINLINE + virtual void SetCurveMethod_Implementation(const EHoudiniPublicAPICurveMethod InCurveMethod) { CurveMethod = InCurveMethod; } + + /** + * Populate this wrapper from a UHoudiniSplineComponent. + * @param InSpline The spline to populate this wrapper from. + */ + void PopulateFromHoudiniSplineComponent(UHoudiniSplineComponent const* const InSpline); + + /** + * Copies the curve data to a UHoudiniSplineComponent. + * @param InSpline The spline to copy to. + */ + void CopyToHoudiniSplineComponent(UHoudiniSplineComponent* const InSpline) const; + +protected: + /** The control points of the curve. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Houdini Engine | Public API | Inputs | Input Objects") + TArray CurvePoints; + + /** Whether the curve is closed (true) or not. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool bClosed; + + /** Whether the curve is reversed (true) or not. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool bReversed; + + /** The curve type (for example: polygon, nurbs, bezier). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveType CurveType; + + /** The curve method, for example CVs, or freehand. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveMethod CurveMethod; + + /** Helper function for converting EHoudiniPublicAPICurveType to EHoudiniCurveType */ + static EHoudiniCurveType ToHoudiniCurveType(const EHoudiniPublicAPICurveType InCurveType); + + /** Helper function for converting EHoudiniPublicAPICurveMethod to EHoudiniCurveMethod */ + static EHoudiniCurveMethod ToHoudiniCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod); + + /** Helper function for converting EHoudiniCurveType to EHoudiniPublicAPICurveType */ + static EHoudiniPublicAPICurveType ToHoudiniPublicAPICurveType(const EHoudiniCurveType InCurveType); + + /** Helper function for converting EHoudiniCurveMethod to EHoudiniPublicAPICurveMethod */ + static EHoudiniPublicAPICurveMethod ToHoudiniPublicAPICurveMethod(const EHoudiniCurveMethod InCurveMethod); +}; + +/** + * API wrapper input class for curve inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPICurveInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPICurveInput(); + + /** Indicates that if trigger cook automatically on curve Input spline modified */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bCookOnCurveChanged; + + /** Set this to true to add rot and scale attributes on curve inputs. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bAddRotAndScaleAttributesOnCurves; + + virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const override; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Curve; } + + virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) override; + + virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const override; +}; + + +/** + * API wrapper input class for asset inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPIAssetInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIAssetInput(); + + virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const override; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Asset; } + + virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) override; + + virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const override; +}; + + +/** + * API wrapper input class for world inputs. Derived from UHoudiniPublicAPIGeoInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPIWorldInput : public UHoudiniPublicAPIGeoInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIWorldInput(); + + /** Objects used for automatic bound selection */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + TArray WorldInputBoundSelectorObjects; + + /** Indicates that this world input is in "BoundSelector" mode */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bIsWorldInputBoundSelector; + + /** Indicates that selected actors by the bound selectors should update automatically */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bWorldInputBoundSelectorAutoUpdate; + + /** Resolution used when converting unreal splines to houdini curves */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + float UnrealSplineResolution; + + /** + * Setter for world input object array. If this is a bounds selector (#bIsWorldInputBoundSelector is true), then + * this function always returns false (and sets nothing), in that case only #WorldInputBoundSelectorObjects can be + * modified. + * @param InObjects The world input objects. + * @return true if all objects in InObjects could be set as world input objects, false otherwise. Always false + * if #bIsWorldInputBoundSelector is true. + */ + virtual bool SetInputObjects_Implementation(const TArray& InObjects) override; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::World; } + +}; + + +/** + * API wrapper input class for landscape inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPILandscapeInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPILandscapeInput(); + + /** Indicates that the landscape input's source landscape should be updated instead of creating a new component */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bUpdateInputLandscape; + + /** Indicates if the landscape should be exported as heightfield, mesh or points */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + EHoudiniLandscapeExportType LandscapeExportType; + + /** Is set to true when landscape input is set to selection only. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportSelectionOnly; + + /** Is set to true when the automatic selection of landscape component is active */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeAutoSelectComponent; + + /** Is set to true when materials are to be exported. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportMaterials; + + /** Is set to true when lightmap information export is desired. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportLighting; + + /** Is set to true when uvs should be exported in [0,1] space. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportNormalizedUVs; + + /** Is set to true when uvs should be exported for each tile separately. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportTileUVs; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Landscape; } + +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h new file mode 100644 index 000000000..1c7d8154e --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h @@ -0,0 +1,110 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "HoudiniPublicAPIObjectBase.generated.h" + +/** An enum with values that determine if API errors are logged. */ +UENUM(BlueprintType) +enum class EHoudiniPublicAPIErrorLogOption : uint8 +{ + Invalid = 0, + Auto = 1, + Log = 2, + NoLog = 3, +}; + +/** + * Base class for API UObjects. Implements error logging: record and get a error messages for Houdini Public API objects. + */ +UCLASS() +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIObjectBase : public UObject +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIObjectBase(); + + /** + * Gets the last error message recorded. + * @param OutLastErrorMessage Set to the last error message recorded, or the empty string if there are no errors + * messages. + * @return true if there was an error message to set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + bool GetLastErrorMessage(FString& OutLastErrorMessage) const; + + /** Clear any error messages that have been set. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + void ClearErrorMessages(); + + /** + * Returns whether or not API errors are written to the log. + * @return true if API errors are logged as warnings, false if API errors are not logged. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + bool IsLoggingErrors() const; + FORCEINLINE + virtual bool IsLoggingErrors_Implementation() const { return bIsLoggingErrors; } + + /** + * Sets whether or not API errors are written to the log. + * @param bInEnabled True if API errors should be logged as warnings, false if API errors should not logged. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + void SetLoggingErrorsEnabled(const bool bInEnabled); + FORCEINLINE + virtual void SetLoggingErrorsEnabled_Implementation(const bool bInEnabled) { bIsLoggingErrors = bInEnabled; } + +protected: + /** + * Set an error message. This is recorded as the current/last error message. + * @param InErrorMessage The error message to set. + * @param InLoggingOption Determines the behavior around logging the error message. If + * EHoudiniPublicAPIErrorLogOption.Invalid or EHoudiniPublicAPIErrorLogOption.Auto then IsLoggingErrors() is used to + * determine if the error message should be logged. If EHoudiniPublicAPIErrorLogOption.Log, then the error message + * is logged as a warning. If EHoudiniPublicAPIErrorLogOption.NoLog then the error message is not logged. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + void SetErrorMessage( + const FString& InErrorMessage, + const EHoudiniPublicAPIErrorLogOption InLoggingOption=EHoudiniPublicAPIErrorLogOption::Auto) const; + + /** The last error message that was set. */ + UPROPERTY() + mutable FString LastErrorMessage; + + /** True if an errors have been set and not yet cleared. */ + UPROPERTY() + mutable bool bHasError; + + /** If true, API errors logged with SetErrorMessage are written to the log as warnings by default. */ + UPROPERTY() + bool bIsLoggingErrors; +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h new file mode 100644 index 000000000..f7b714d34 --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h @@ -0,0 +1,71 @@ +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniOutput.h" + +#include "HoudiniPublicAPIOutputTypes.generated.h" + +/** + * This class represents an output object identifier for an output object of a wrapped Houdini asset in the + * public API. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API | Outputs") +struct FHoudiniPublicAPIOutputObjectIdentifier +{ + GENERATED_BODY() + +public: + FHoudiniPublicAPIOutputObjectIdentifier(); + + FHoudiniPublicAPIOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); + + /** Returns the internal output object identifier wrapped by this class. */ + const FHoudiniOutputObjectIdentifier& GetIdentifier() const { return Identifier; } + + /** + * Sets the internal output object identifier wrapped by this class. + * @param InIdentifier The internal output object identifier. + */ + void SetIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); + + /** String identifier for the split that created the output object identified by this identifier. */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Houdini Engine | Public API | Outputs") + FString SplitIdentifier; + + /** Name of the part used to generate the output object identified by this identifier. */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Houdini Engine | Public API | Outputs") + FString PartName; + +protected: + + /** The internal output object identifier. */ + UPROPERTY() + FHoudiniOutputObjectIdentifier Identifier; +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h new file mode 100644 index 000000000..eeb658076 --- /dev/null +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h @@ -0,0 +1,286 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Kismet/BlueprintAsyncActionBase.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniPublicAPIAssetWrapper.h" + +#include "HoudiniPublicAPIProcessHDANode.generated.h" + + +class UHoudiniPublicAPIInput; +class UHoudiniAsset; + +// Delegate type for output pins on the node. +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnProcessHDANodeOutputPinDelegate, UHoudiniPublicAPIAssetWrapper*, AssetWrapper, const bool, bCookSuccess, const bool, bBakeSuccess); + +/** + * A Blueprint async node for instantiating and cooking/processing an HDA, with delegate output pins for the + * various phases/state changes of the HDA. + */ +UCLASS() +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIProcessHDANode : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIProcessHDANode(const FObjectInitializer& ObjectInitializer); + + /** + * Instantiates an HDA in the specified world/level. Sets parameters and inputs supplied in InParameters, + * InNodeInputs and InParameterInputs. If bInEnableAutoCook is true, cooks the HDA. If bInEnableAutoBake is + * true, bakes the cooked outputs according to the supplied baking parameters. + * This all happens asynchronously, with the various output pins firing at the various points in the process: + * - PreInstantiation: before the HDA is instantiated, a good place to set parameter values before the first cook. + * - PostInstantiation: after the HDA is instantiated, a good place to set/configure inputs before the first cook. + * - PostAutoCook: right after a cook + * - PreProcess: after a cook but before output objects have been created/processed + * - PostProcessing: after output objects have been created + * - PostAutoBake: after outputs have been baked + * - Completed: upon successful completion (could be PostInstantiation if auto cook is disabled, PostProcessing + * if auto bake is disabled, or after PostAutoBake if auto bake is enabled. + * - Failed: If the process failed at any point. + * @param InHoudiniAsset The HDA to instantiate. + * @param InInstantiateAt The Transform to instantiate the HDA with. + * @param InParameters The parameter values to set before cooking the instantiated HDA. + * @param InNodeInputs The node inputs to set before cooking the instantiated HDA. + * @param InParameterInputs The parameter-based inputs to set before cooking the instantiated HDA. + * @param InWorldContextObject A world context object for identifying the world to spawn in, if + * InSpawnInLevelOverride is null. + * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both + * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor + * context world's current level. + * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after + * parameter, transform and input changes. + * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. + * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. + * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. + * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. + * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. + * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with + * the new bake's output. Defaults to false. + * @param bInDeleteInstantiatedAssetOnCompletionOrFailure If true, deletes the instantiated asset actor on + * completion or failure. Defaults to false. + * @return The blueprint async node. + */ + UFUNCTION(BlueprintCallable, meta=(AdvancedDisplay=5,AutoCreateRefTerm="InInstantiateAt,InParameters,InNodeInputs,InParameterInputs",BlueprintInternalUseOnly="true", WorldContext="WorldContextObject"), Category="Houdini|Public API") + static UHoudiniPublicAPIProcessHDANode* ProcessHDA( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + const TMap& InParameters, + const TMap& InNodeInputs, + const TMap& InParameterInputs, + UObject* InWorldContextObject=nullptr, + ULevel* InSpawnInLevelOverride=nullptr, + const bool bInEnableAutoCook=true, + const bool bInEnableAutoBake=false, + const FString& InBakeDirectoryPath="", + const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, + const bool bInRemoveOutputAfterBake=false, + const bool bInRecenterBakedActors=false, + const bool bInReplacePreviousBake=false, + const bool bInDeleteInstantiatedAssetOnCompletionOrFailure=false); + + virtual void Activate() override; + + /** + * Delegate that is broadcast when entering the PreInstantiation state: the HDA's default parameter definitions are + * available, but the node has not yet been instantiated in HAPI/Houdini Engine + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PreInstantiation; + + /** Delegate that is broadcast after the asset was successfully instantiated */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostInstantiation; + + /** + * Delegate that is broadcast after a cook completes, but before outputs have been created. This will not be + * broadcast from this node if bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostAutoCook; + + /** + * Delegate that is broadcast just after PostCook (after parameters have been updated) but before creating + * output objects. This will not be broadcast from this node if bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PreProcess; + + /** + * Delegate that is broadcast after processing HDA outputs after a cook, the output objects have been created. + * This will not be broadcast from this node if bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostProcessing; + + /** + * Delegate that is broadcast after auto-baking the asset (not called for individual outputs that are baked to the + * content browser). This will not be broadcast from this node if bInAutoBake or bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostAutoBake; + + /** + * Deletate that is broadcast on completion of async processing of the instantiated asset by this node. + * After this broadcast, the instantiated asset will be deleted if bInDeleteInstantiatedAssetOnCompletion=true + * was set on creation. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate Completed; + + /** Deletate that is broadcast if we fail during activation of the node. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate Failed; + +protected: + + // Output pin data + + /** The asset wrapper for the instantiated HDA processed by this node. */ + UPROPERTY() + UHoudiniPublicAPIAssetWrapper* AssetWrapper; + + /** True if the last cook was successful. */ + UPROPERTY() + bool bCookSuccess; + + /** True if the last bake was successful. */ + UPROPERTY() + bool bBakeSuccess; + + // End: Output pin data + + /** The HDA to instantiate. */ + UPROPERTY() + UHoudiniAsset* HoudiniAsset; + + /** The transform the instantiate the asset with. */ + UPROPERTY() + FTransform InstantiateAt; + + /** The parameters to set on #PreInstantiation */ + UPROPERTY() + TMap Parameters; + + /** The node inputs to set on #PostInstantiation */ + UPROPERTY() + TMap NodeInputs; + + /** The object path parameter inputs to set on #PostInstantiation */ + UPROPERTY() + TMap ParameterInputs; + + /** The world context object: spawn in this world if #SpawnInLevelOverride is not set. */ + UPROPERTY() + UObject* WorldContextObject; + + /** The level to spawn in. If both this and #WorldContextObject is not set, spawn in the editor context's level. */ + UPROPERTY() + ULevel* SpawnInLevelOverride; + + /** Whether to set the instantiated asset to auto cook. */ + UPROPERTY() + bool bEnableAutoCook; + + /** Whether to set the instantiated asset to auto bake after a cook. */ + UPROPERTY() + bool bEnableAutoBake; + + /** Set the fallback bake directory, for if output attributes do not specify it. */ + UPROPERTY() + FString BakeDirectoryPath; + + /** The bake method/target: for example, to actors vs to blueprints. */ + UPROPERTY() + EHoudiniEngineBakeOption BakeMethod; + + /** Remove temporary HDA output after a bake. */ + UPROPERTY() + bool bRemoveOutputAfterBake; + + /** Recenter the baked actors at their bounding box center. */ + UPROPERTY() + bool bRecenterBakedActors; + + /** + * Replace previous bake output on each bake. For the purposes of this node, this would mostly apply to .uassets and + * not actors. + */ + UPROPERTY() + bool bReplacePreviousBake; + + /** Whether or not to delete the instantiated asset after Complete is called. */ + UPROPERTY() + bool bDeleteInstantiatedAssetOnCompletionOrFailure; + + /** Unbind all delegates */ + void UnbindDelegates(); + + /** Broadcast Failure and removes the node from the root set. */ + UFUNCTION() + virtual void HandleFailure(); + + /** Broadcast Complete and removes the node from the root set. */ + UFUNCTION() + virtual void HandleComplete(); + + /** + * Bound to the asset wrapper's pre-instantiation delegate. Sets the HDAs parameters from #Parameters and + * broadcasts #PreInstantiation. + */ + UFUNCTION() + virtual void HandlePreInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** + * Bound to the asset wrapper's post-instantiation delegate. Sets the HDAs inputs from #NodeInputs and + * #ParameterInputs and broadcasts #PostInstantiation. + */ + UFUNCTION() + virtual void HandlePostInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** Bound to the asset wrapper's post-cook delegate. Broadcasts #PostAutoCook. */ + UFUNCTION() + virtual void HandlePostAutoCook(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInCookSuccess); + + /** Bound to the asset wrapper's pre-processing delegate. Broadcasts #PreProcess. */ + UFUNCTION() + virtual void HandlePreProcess(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** Bound to the asset wrapper's post-processing delegate. Broadcasts #PostProcessing. */ + UFUNCTION() + virtual void HandlePostProcessing(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** Bound to the asset wrapper's post-bake delegate. Broadcasts #PostAutoBake. */ + UFUNCTION() + virtual void HandlePostAutoBake(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInBakeSuccess); +}; diff --git a/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h b/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h index 3a722a67b..940f0ab60 100644 --- a/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h +++ b/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Modules/ModuleInterface.h" - -class IHoudiniEngineEditor : public IModuleInterface -{ - public: - /** Register and unregister component visualizers used by this module. **/ - virtual void RegisterComponentVisualizers() {} - virtual void UnregisterComponentVisualizers() {} - - /** Register and unregister detail presenters used by this module. **/ - virtual void RegisterDetails() {} - virtual void UnregisterDetails() {} - - /** Register and unregister asset type actions. **/ - virtual void RegisterAssetTypeActions() {} - virtual void UnregisterAssetTypeActions() {} - - /** Create and register / unregister asset brokers. **/ - virtual void RegisterAssetBrokers() {} - virtual void UnregisterAssetBrokers() {} - - /** Create and register actor factories. **/ - virtual void RegisterActorFactories() {} - - /** Extend menu. **/ - virtual void ExtendMenu() {} - - /** Register and unregister thumbnails. **/ - virtual void RegisterThumbnails() {} - virtual void UnregisterThumbnails() {} - - /** Register and unregister for undo/redo notifications. **/ - virtual void RegisterForUndo() {} - virtual void UnregisterForUndo() {} - - /** Create custom modes **/ - virtual void RegisterModes() {} - virtual void UnregisterModes() {} - - /** Create custom placement extensions */ - virtual void RegisterPlacementModeExtensions() {} - virtual void UnregisterPlacementModeExtensions() {} -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Modules/ModuleInterface.h" + +class IHoudiniEngineEditor : public IModuleInterface +{ + public: + /** Register and unregister component visualizers used by this module. **/ + virtual void RegisterComponentVisualizers() {} + virtual void UnregisterComponentVisualizers() {} + + /** Register and unregister detail presenters used by this module. **/ + virtual void RegisterDetails() {} + virtual void UnregisterDetails() {} + + /** Register and unregister asset type actions. **/ + virtual void RegisterAssetTypeActions() {} + virtual void UnregisterAssetTypeActions() {} + + /** Create and register / unregister asset brokers. **/ + virtual void RegisterAssetBrokers() {} + virtual void UnregisterAssetBrokers() {} + + /** Create and register actor factories. **/ + virtual void RegisterActorFactories() {} + + /** Extend menu. **/ + virtual void ExtendMenu() {} + + /** Register and unregister thumbnails. **/ + virtual void RegisterThumbnails() {} + virtual void UnregisterThumbnails() {} + + /** Register and unregister for undo/redo notifications. **/ + virtual void RegisterForUndo() {} + virtual void UnregisterForUndo() {} + + /** Create custom modes **/ + virtual void RegisterModes() {} + virtual void UnregisterModes() {} + + /** Create custom placement extensions */ + virtual void RegisterPlacementModeExtensions() {} + virtual void UnregisterPlacementModeExtensions() {} +}; diff --git a/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs b/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs index ce72dab42..2e4f8b8e5 100644 --- a/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs +++ b/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs @@ -1,92 +1,92 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -using UnrealBuildTool; -using System; -using System.IO; - -public class HoudiniEngineRuntime : ModuleRules -{ - public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) - { - bPrecompile = true; - PCHUsage = PCHUsageMode.NoSharedPCHs; - PrivatePCHHeaderFile = "Private/HoudiniEngineRuntimePrivatePCH.h"; - - // Check if we are compiling for unsupported platforms. - if ( Target.Platform != UnrealTargetPlatform.Win64 && - Target.Platform != UnrealTargetPlatform.Mac && - Target.Platform != UnrealTargetPlatform.Linux && - Target.Platform != UnrealTargetPlatform.Switch ) - { - System.Console.WriteLine( string.Format( "Houdini Engine Runtime: Compiling for untested target platform. Please let us know how it goes!" ) ); - } - - - PublicIncludePaths.AddRange( - new string[] {} - ); - - PrivateIncludePaths.AddRange( - new string[] { } - ); - - // Add common dependencies. - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "Engine", - "RenderCore", - "InputCore", - "RHI", - "Foliage", - "Landscape" - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "Landscape", - "PhysicsCore" - } - ); - - if (Target.bBuildEditor == true) - { - PrivateDependencyModuleNames.AddRange( - new string[] - { - "UnrealEd", - "Kismet", - } - ); - } - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using UnrealBuildTool; +using System; +using System.IO; + +public class HoudiniEngineRuntime : ModuleRules +{ + public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) + { + bPrecompile = true; + PCHUsage = PCHUsageMode.NoSharedPCHs; + PrivatePCHHeaderFile = "Private/HoudiniEngineRuntimePrivatePCH.h"; + + // Check if we are compiling for unsupported platforms. + if ( Target.Platform != UnrealTargetPlatform.Win64 && + Target.Platform != UnrealTargetPlatform.Mac && + Target.Platform != UnrealTargetPlatform.Linux && + Target.Platform != UnrealTargetPlatform.Switch ) + { + System.Console.WriteLine( string.Format( "Houdini Engine Runtime: Compiling for untested target platform. Please let us know how it goes!" ) ); + } + + + PublicIncludePaths.AddRange( + new string[] {} + ); + + PrivateIncludePaths.AddRange( + new string[] { } + ); + + // Add common dependencies. + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "RenderCore", + "InputCore", + "RHI", + "Foliage", + "Landscape" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Landscape", + "PhysicsCore" + } + ); + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "Kismet", + } + ); + } + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp index a05783499..4eb06991c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp @@ -1,200 +1,200 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAsset.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Misc/Paths.h" -#include "HAL/UnrealMemory.h" - -UHoudiniAsset::UHoudiniAsset(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , AssetFileName(TEXT("")) - , AssetBytesCount(0) - , bAssetLimitedCommercial(false) - , bAssetNonCommercial(false) - , bAssetExpanded(false) -{} - -void -UHoudiniAsset::CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName) -{ - AssetFileName = InFileName; - - // Calculate buffer size. - AssetBytesCount = BufferEnd - BufferStart; - - if (AssetBytesCount) - { - // Allocate buffer to store the raw data. - AssetBytes.SetNumUninitialized(AssetBytesCount); - // Copy data into the newly allocated buffer. - FMemory::Memcpy(AssetBytes.GetData(), BufferStart, AssetBytesCount); - } - - FString FileExtension = FPaths::GetExtension(InFileName); - - // Expanded HDAs are imported via a "houdini.hdalibrary" file inside the .hda directory - // Identify them first, then update the file path to point to the .hda dir - if (FileExtension.Equals(TEXT("hdalibrary"), ESearchCase::IgnoreCase)) - { - bAssetExpanded = true; - - // Use the parent ".hda" directory as the filename - AssetFileName = FPaths::GetPath(AssetFileName); - FileExtension = FPaths::GetExtension(AssetFileName); - } - - if (FileExtension.Equals(TEXT("hdalc"), ESearchCase::IgnoreCase) - || FileExtension.Equals(TEXT("otlc"), ESearchCase::IgnoreCase)) - { - // Check if the HDA is limited (Indie) ... - bAssetLimitedCommercial = true; - } - else if (FileExtension.Equals(TEXT("hdanc"), ESearchCase::IgnoreCase) - || FileExtension.Equals(TEXT("otlnc"), ESearchCase::IgnoreCase)) - { - // ... or non commercial (Apprentice) - bAssetNonCommercial = true; - } -} - -void -UHoudiniAsset::FinishDestroy() -{ - // Release buffer which was used to store raw OTL data. - AssetBytes.Empty(); - Super::FinishDestroy(); -} - -const uint8 * -UHoudiniAsset::GetAssetBytes() const -{ - return AssetBytes.GetData(); -} - -const FString & -UHoudiniAsset::GetAssetFileName() const -{ - return AssetFileName; -} - -uint32 -UHoudiniAsset::GetAssetBytesCount() const -{ - return AssetBytesCount; -} - -void -UHoudiniAsset::Serialize(FArchive & Ar) -{ - // Serializes our UProperties - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Get the version - uint32 HoudiniAssetVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - - // Only version 1 assets needs manual serialization - if ( HoudiniAssetVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE - || HoudiniAssetVersion > VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION ) - return SerializeLegacy(Ar); -} - -void -UHoudiniAsset::SerializeLegacy(FArchive & Ar) -{ - uint32 FileFormatVersion; - Ar << FileFormatVersion; - - Ar << AssetBytesCount; - if (Ar.IsLoading()) - { - // Allocate sufficient space to read stored raw OTL data. - AssetBytes.SetNumUninitialized(AssetBytesCount); - } - - if (AssetBytesCount) - Ar.Serialize(AssetBytes.GetData(), AssetBytesCount); - - // Serialized flags. - union - { - struct - { - uint32 bLegacyPreviewHoudiniLogo : 1; - uint32 bLegacyAssetLimitedCommercial : 1; - uint32 bLegacyAssetNonCommercial : 1; - }; - uint32 HoudiniAssetFlagsPacked; - }; - Ar << HoudiniAssetFlagsPacked; - - bAssetNonCommercial = bLegacyAssetNonCommercial; - bAssetLimitedCommercial = bLegacyAssetLimitedCommercial; - - // Serialize asset file path. - Ar << AssetFileName; -} - -void -UHoudiniAsset::GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const -{ - // Filename - OutTags.Add(FAssetRegistryTag("FileName", AssetFileName, FAssetRegistryTag::TT_Alphabetical)); - - // Bytes - OutTags.Add(FAssetRegistryTag("Bytes", FString::FromInt(AssetBytesCount), FAssetRegistryTag::TT_Numerical)); - - // Indicate if the Asset is Full / Indie / NC - FString AssetType = TEXT("Full"); - if (bAssetLimitedCommercial) - AssetType = TEXT("Limited Commercial (LC)"); - else if (bAssetNonCommercial) - AssetType = TEXT("Non Commercial (NC)"); - - OutTags.Add(FAssetRegistryTag("Asset Type", AssetType, FAssetRegistryTag::TT_Alphabetical)); - - Super::GetAssetRegistryTags(OutTags); -} - -bool -UHoudiniAsset::IsAssetLimitedCommercial() const -{ - return bAssetLimitedCommercial; -} - -bool -UHoudiniAsset::IsAssetNonCommercial() const -{ - return bAssetNonCommercial; -} - -bool -UHoudiniAsset::IsExpandedHDA() const -{ - return bAssetExpanded; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAsset.h" +#include "HoudiniPluginSerializationVersion.h" + +#include "Misc/Paths.h" +#include "HAL/UnrealMemory.h" + +UHoudiniAsset::UHoudiniAsset(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , AssetFileName(TEXT("")) + , AssetBytesCount(0) + , bAssetLimitedCommercial(false) + , bAssetNonCommercial(false) + , bAssetExpanded(false) +{} + +void +UHoudiniAsset::CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName) +{ + AssetFileName = InFileName; + + // Calculate buffer size. + AssetBytesCount = BufferEnd - BufferStart; + + if (AssetBytesCount) + { + // Allocate buffer to store the raw data. + AssetBytes.SetNumUninitialized(AssetBytesCount); + // Copy data into the newly allocated buffer. + FMemory::Memcpy(AssetBytes.GetData(), BufferStart, AssetBytesCount); + } + + FString FileExtension = FPaths::GetExtension(InFileName); + + // Expanded HDAs are imported via a "houdini.hdalibrary" file inside the .hda directory + // Identify them first, then update the file path to point to the .hda dir + if (FileExtension.Equals(TEXT("hdalibrary"), ESearchCase::IgnoreCase)) + { + bAssetExpanded = true; + + // Use the parent ".hda" directory as the filename + AssetFileName = FPaths::GetPath(AssetFileName); + FileExtension = FPaths::GetExtension(AssetFileName); + } + + if (FileExtension.Equals(TEXT("hdalc"), ESearchCase::IgnoreCase) + || FileExtension.Equals(TEXT("otlc"), ESearchCase::IgnoreCase)) + { + // Check if the HDA is limited (Indie) ... + bAssetLimitedCommercial = true; + } + else if (FileExtension.Equals(TEXT("hdanc"), ESearchCase::IgnoreCase) + || FileExtension.Equals(TEXT("otlnc"), ESearchCase::IgnoreCase)) + { + // ... or non commercial (Apprentice) + bAssetNonCommercial = true; + } +} + +void +UHoudiniAsset::FinishDestroy() +{ + // Release buffer which was used to store raw OTL data. + AssetBytes.Empty(); + Super::FinishDestroy(); +} + +const uint8 * +UHoudiniAsset::GetAssetBytes() const +{ + return AssetBytes.GetData(); +} + +const FString & +UHoudiniAsset::GetAssetFileName() const +{ + return AssetFileName; +} + +uint32 +UHoudiniAsset::GetAssetBytesCount() const +{ + return AssetBytesCount; +} + +void +UHoudiniAsset::Serialize(FArchive & Ar) +{ + // Serializes our UProperties + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Get the version + uint32 HoudiniAssetVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + + // Only version 1 assets needs manual serialization + if ( HoudiniAssetVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE + || HoudiniAssetVersion > VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION ) + return SerializeLegacy(Ar); +} + +void +UHoudiniAsset::SerializeLegacy(FArchive & Ar) +{ + uint32 FileFormatVersion; + Ar << FileFormatVersion; + + Ar << AssetBytesCount; + if (Ar.IsLoading()) + { + // Allocate sufficient space to read stored raw OTL data. + AssetBytes.SetNumUninitialized(AssetBytesCount); + } + + if (AssetBytesCount) + Ar.Serialize(AssetBytes.GetData(), AssetBytesCount); + + // Serialized flags. + union + { + struct + { + uint32 bLegacyPreviewHoudiniLogo : 1; + uint32 bLegacyAssetLimitedCommercial : 1; + uint32 bLegacyAssetNonCommercial : 1; + }; + uint32 HoudiniAssetFlagsPacked; + }; + Ar << HoudiniAssetFlagsPacked; + + bAssetNonCommercial = bLegacyAssetNonCommercial; + bAssetLimitedCommercial = bLegacyAssetLimitedCommercial; + + // Serialize asset file path. + Ar << AssetFileName; +} + +void +UHoudiniAsset::GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const +{ + // Filename + OutTags.Add(FAssetRegistryTag("FileName", AssetFileName, FAssetRegistryTag::TT_Alphabetical)); + + // Bytes + OutTags.Add(FAssetRegistryTag("Bytes", FString::FromInt(AssetBytesCount), FAssetRegistryTag::TT_Numerical)); + + // Indicate if the Asset is Full / Indie / NC + FString AssetType = TEXT("Full"); + if (bAssetLimitedCommercial) + AssetType = TEXT("Limited Commercial (LC)"); + else if (bAssetNonCommercial) + AssetType = TEXT("Non Commercial (NC)"); + + OutTags.Add(FAssetRegistryTag("Asset Type", AssetType, FAssetRegistryTag::TT_Alphabetical)); + + Super::GetAssetRegistryTags(OutTags); +} + +bool +UHoudiniAsset::IsAssetLimitedCommercial() const +{ + return bAssetLimitedCommercial; +} + +bool +UHoudiniAsset::IsAssetNonCommercial() const +{ + return bAssetNonCommercial; +} + +bool +UHoudiniAsset::IsExpandedHDA() const +{ + return bAssetExpanded; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h index 0fa3734cb..d651062fb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h @@ -1,104 +1,104 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/Object.h" -#include "HoudiniAsset.generated.h" - -class UAssetImportData; - -UCLASS(BlueprintType, EditInlineNew, config = Engine) -class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject -{ - GENERATED_UCLASS_BODY() - - public: - - // UOBject functions - virtual void FinishDestroy() override; - virtual void Serialize(FArchive & Ar) override; - virtual void GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const override; - - // Creates and initialize this asset from a given buffer / file. - void CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName); - - // Return buffer containing the raw Houdini OTL data. - const uint8* GetAssetBytes() const; - - // Return path of the corresponding OTL/HDA file. - const FString& GetAssetFileName() const; - - // Return the size in bytes of raw Houdini OTL data. - uint32 GetAssetBytesCount() const; - - // Return true if this asset is a limited commercial asset. - bool IsAssetLimitedCommercial() const; - - // Return true if this asset is a non commercial asset. - bool IsAssetNonCommercial() const; - - // Return true if this asset is an expanded HDA (HDA dir) - bool IsExpandedHDA() const; - - private: - // Used to load old (version1) versions of HoudiniAssets - void SerializeLegacy(FArchive & Ar); - - public: - - // Source filename of the OTL. - UPROPERTY() - FString AssetFileName; - -#if WITH_EDITORONLY_DATA - // Importing data and options used for this Houdini asset. - UPROPERTY(Category = ImportSettings, VisibleAnywhere, Instanced) - UAssetImportData * AssetImportData; -#endif - - private: - - // Buffer containing the raw HDA OTL data. - UPROPERTY() - TArray AssetBytes; - - // Size in bytes of the raw HDA data. - UPROPERTY() - uint32 AssetBytesCount; - - // Indicates if this is a limited commercial asset. - UPROPERTY() - bool bAssetLimitedCommercial; - - // Indicates if this is a non-commercial license asset. - UPROPERTY() - bool bAssetNonCommercial; - - // Indicates if this is an expanded HDA file - UPROPERTY() - bool bAssetExpanded; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/Object.h" +#include "HoudiniAsset.generated.h" + +class UAssetImportData; + +UCLASS(BlueprintType, EditInlineNew, config = Engine) +class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject +{ + GENERATED_UCLASS_BODY() + + public: + + // UOBject functions + virtual void FinishDestroy() override; + virtual void Serialize(FArchive & Ar) override; + virtual void GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const override; + + // Creates and initialize this asset from a given buffer / file. + void CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName); + + // Return buffer containing the raw Houdini OTL data. + const uint8* GetAssetBytes() const; + + // Return path of the corresponding OTL/HDA file. + const FString& GetAssetFileName() const; + + // Return the size in bytes of raw Houdini OTL data. + uint32 GetAssetBytesCount() const; + + // Return true if this asset is a limited commercial asset. + bool IsAssetLimitedCommercial() const; + + // Return true if this asset is a non commercial asset. + bool IsAssetNonCommercial() const; + + // Return true if this asset is an expanded HDA (HDA dir) + bool IsExpandedHDA() const; + + private: + // Used to load old (version1) versions of HoudiniAssets + void SerializeLegacy(FArchive & Ar); + + public: + + // Source filename of the OTL. + UPROPERTY() + FString AssetFileName; + +#if WITH_EDITORONLY_DATA + // Importing data and options used for this Houdini asset. + UPROPERTY(Category = ImportSettings, VisibleAnywhere, Instanced) + UAssetImportData * AssetImportData; +#endif + + private: + + // Buffer containing the raw HDA OTL data. + UPROPERTY() + TArray AssetBytes; + + // Size in bytes of the raw HDA data. + UPROPERTY() + uint32 AssetBytesCount; + + // Indicates if this is a limited commercial asset. + UPROPERTY() + bool bAssetLimitedCommercial; + + // Indicates if this is a non-commercial license asset. + UPROPERTY() + bool bAssetNonCommercial; + + // Indicates if this is an expanded HDA file + UPROPERTY() + bool bAssetExpanded; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp index cf7257fee..84c914fed 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp @@ -1,146 +1,146 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniPDGAssetLink.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -AHoudiniAssetActor::AHoudiniAssetActor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - SetCanBeDamaged(false); - //PrimaryActorTick.bCanEverTick = true; - //PrimaryActorTick.bStartWithTickEnabled = true; - - // Create Houdini component and attach it to a root component. - HoudiniAssetComponent = - ObjectInitializer.CreateDefaultSubobject< UHoudiniAssetComponent >(this, TEXT("HoudiniAssetComponent")); - - //HoudiniAssetComponent->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); - - RootComponent = HoudiniAssetComponent; -} - -UHoudiniAssetComponent * -AHoudiniAssetActor::GetHoudiniAssetComponent() const -{ - return HoudiniAssetComponent; -} - -/* -#if WITH_EDITOR -bool -AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) -{ - if (!ActorPropString) - return false; - - // Locate actor which is being copied in clipboard string. - AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor(this, *ActorPropString); - - // We no longer need clipboard string and can empty it. This seems to avoid occasional crash bug in UE4 which - // happens on copy / paste. - ActorPropString->Empty(); - - if (!CopiedActor || CopiedActor->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import from copy: Duplicated actor not found")); - return false; - } - - // Get Houdini component of an actor which is being copied. - UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; - if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill()) - return false; - - HoudiniAssetComponent->OnComponentClipboardCopy(CopiedActorHoudiniAssetComponent); - - // If actor is copied through moving, we need to copy main transform. - const FTransform & ComponentWorldTransform = CopiedActorHoudiniAssetComponent->GetComponentTransform(); - HoudiniAssetComponent->SetWorldLocationAndRotation( - ComponentWorldTransform.GetLocation(), - ComponentWorldTransform.GetRotation()); - - // We also need to copy actor label. - const FString & CopiedActorLabel = CopiedActor->GetActorLabel(); - FActorLabelUtilities::SetActorLabelUnique(this, CopiedActorLabel); - - return true; -} -#endif -*/ - -#if WITH_EDITOR -bool -AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) const -{ - Super::GetReferencedContentObjects(Objects); - - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) - { - UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); - if (HoudiniAsset && !HoudiniAsset->IsPendingKill()) - Objects.AddUnique(HoudiniAsset); - } - - return true; -} -#endif - -#if WITH_EDITOR -void -AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - // Some property changes need to be forwarded to the component (ie Transform) - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return; - - FProperty* Property = PropertyChangedEvent.MemberProperty; - if (!Property) - return; - - FName PropertyName = Property->GetFName(); - if (PropertyName == HoudiniAssetComponent->GetRelativeLocationPropertyName() - || PropertyName == HoudiniAssetComponent->GetRelativeRotationPropertyName() - || PropertyName == HoudiniAssetComponent->GetRelativeScale3DPropertyName()) - { - HoudiniAssetComponent->SetHasComponentTransformChanged(true); - } -} -#endif - - -bool -AHoudiniAssetActor::IsUsedForPreview() const -{ - return HasAnyFlags(RF_Transient); -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniPDGAssetLink.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +AHoudiniAssetActor::AHoudiniAssetActor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + SetCanBeDamaged(false); + //PrimaryActorTick.bCanEverTick = true; + //PrimaryActorTick.bStartWithTickEnabled = true; + + // Create Houdini component and attach it to a root component. + HoudiniAssetComponent = + ObjectInitializer.CreateDefaultSubobject< UHoudiniAssetComponent >(this, TEXT("HoudiniAssetComponent")); + + //HoudiniAssetComponent->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); + + RootComponent = HoudiniAssetComponent; +} + +UHoudiniAssetComponent * +AHoudiniAssetActor::GetHoudiniAssetComponent() const +{ + return HoudiniAssetComponent; +} + +/* +#if WITH_EDITOR +bool +AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) +{ + if (!ActorPropString) + return false; + + // Locate actor which is being copied in clipboard string. + AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor(this, *ActorPropString); + + // We no longer need clipboard string and can empty it. This seems to avoid occasional crash bug in UE4 which + // happens on copy / paste. + ActorPropString->Empty(); + + if (!CopiedActor || CopiedActor->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import from copy: Duplicated actor not found")); + return false; + } + + // Get Houdini component of an actor which is being copied. + UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; + if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill()) + return false; + + HoudiniAssetComponent->OnComponentClipboardCopy(CopiedActorHoudiniAssetComponent); + + // If actor is copied through moving, we need to copy main transform. + const FTransform & ComponentWorldTransform = CopiedActorHoudiniAssetComponent->GetComponentTransform(); + HoudiniAssetComponent->SetWorldLocationAndRotation( + ComponentWorldTransform.GetLocation(), + ComponentWorldTransform.GetRotation()); + + // We also need to copy actor label. + const FString & CopiedActorLabel = CopiedActor->GetActorLabel(); + FActorLabelUtilities::SetActorLabelUnique(this, CopiedActorLabel); + + return true; +} +#endif +*/ + +#if WITH_EDITOR +bool +AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) const +{ + Super::GetReferencedContentObjects(Objects); + + if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + { + UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); + if (HoudiniAsset && !HoudiniAsset->IsPendingKill()) + Objects.AddUnique(HoudiniAsset); + } + + return true; +} +#endif + +#if WITH_EDITOR +void +AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + // Some property changes need to be forwarded to the component (ie Transform) + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return; + + FProperty* Property = PropertyChangedEvent.MemberProperty; + if (!Property) + return; + + FName PropertyName = Property->GetFName(); + if (PropertyName == HoudiniAssetComponent->GetRelativeLocationPropertyName() + || PropertyName == HoudiniAssetComponent->GetRelativeRotationPropertyName() + || PropertyName == HoudiniAssetComponent->GetRelativeScale3DPropertyName()) + { + HoudiniAssetComponent->SetHasComponentTransformChanged(true); + } +} +#endif + + +bool +AHoudiniAssetActor::IsUsedForPreview() const +{ + return HasAnyFlags(RF_Transient); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h index f081efc07..c3a316e75 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h @@ -1,77 +1,77 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" - -#include "UObject/ObjectMacros.h" -#include "Components/ActorComponent.h" -#include "GameFramework/Actor.h" - -#include "HoudiniAssetActor.generated.h" - -class UHoudiniPDGAssetLink; - -UCLASS(hidecategories = (Input), ConversionRoot, meta = (ChildCanTick), Blueprintable) -class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor -{ - GENERATED_UCLASS_BODY() - - // Pointer to the root HoudiniAssetComponent - UPROPERTY(Category = HoudiniAssetActor, VisibleAnywhere, BlueprintReadOnly, meta = (ExposeFunctionCategories = "Mesh,Rendering,Physics,Components|HoudiniEngine")/*, AllowPrivateAccess = "true"*/) - UHoudiniAssetComponent * HoudiniAssetComponent; - -public: - - // Returns the actor's houdini component. - UHoudiniAssetComponent* GetHoudiniAssetComponent() const; - - bool IsUsedForPreview() const; - - // Gets the Houdini PDG asset link associated with this actor, if it has one. - UHoudiniPDGAssetLink* GetPDGAssetLink() const { return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; } - -#if WITH_EDITOR - - // Called after a property has been changed - // Used to forward property changes to the HAC - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; - - // Used by the "Sync to Content Browser" right-click menu option in the editor. - virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; - -/* -public: - - // Called before editor paste, true allow import - virtual bool ShouldImport(FString * ActorPropString, bool IsMovingLevel) override; - - // Used by the "Sync to Content Browser" right-click menu option in the editor. - virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; -*/ -#endif -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" + +#include "UObject/ObjectMacros.h" +#include "Components/ActorComponent.h" +#include "GameFramework/Actor.h" + +#include "HoudiniAssetActor.generated.h" + +class UHoudiniPDGAssetLink; + +UCLASS(hidecategories = (Input), ConversionRoot, meta = (ChildCanTick), Blueprintable) +class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor +{ + GENERATED_UCLASS_BODY() + + // Pointer to the root HoudiniAssetComponent + UPROPERTY(Category = HoudiniAssetActor, VisibleAnywhere, BlueprintReadOnly, meta = (ExposeFunctionCategories = "Mesh,Rendering,Physics,Components|HoudiniEngine")/*, AllowPrivateAccess = "true"*/) + UHoudiniAssetComponent * HoudiniAssetComponent; + +public: + + // Returns the actor's houdini component. + UHoudiniAssetComponent* GetHoudiniAssetComponent() const; + + bool IsUsedForPreview() const; + + // Gets the Houdini PDG asset link associated with this actor, if it has one. + UHoudiniPDGAssetLink* GetPDGAssetLink() const { return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; } + +#if WITH_EDITOR + + // Called after a property has been changed + // Used to forward property changes to the HAC + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; + + // Used by the "Sync to Content Browser" right-click menu option in the editor. + virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; + +/* +public: + + // Called before editor paste, true allow import + virtual bool ShouldImport(FString * ActorPropString, bool IsMovingLevel) override; + + // Used by the "Sync to Content Browser" right-click menu option in the editor. + virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; +*/ +#endif +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp index 0c78efa42..2156b4a7e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp @@ -1,2377 +1,2377 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetBlueprintComponent.h" - -#include "HoudiniEngineCopyPropertiesInterface.h" -#include "HoudiniOutput.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Engine/SCS_Node.h" -#include "Engine/SimpleConstructionScript.h" -#include "UObject/Object.h" -#include "Logging/LogMacros.h" - -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniInput.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Kismet2/BlueprintEditorUtils.h" - #include "Kismet2/KismetEditorUtilities.h" - #include "Toolkits/AssetEditorManager.h" - #include "Kismet2/ComponentEditorUtils.h" - #include "ComponentAssetBroker.h" -#endif - -HOUDINI_BP_DEFINE_LOG_CATEGORY(); - -UHoudiniAssetBlueprintComponent::UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - -#if WITH_EDITOR - if (IsTemplate()) - { - // CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); - //GEditor->GetEditorSubsystem()->OnAssetEditorRequestClose().AddUObject( this, &UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent ); - } -#endif - - bForceNeedUpdate = false; - bHoudiniAssetChanged = false; - bIsInBlueprintEditor = false; - bCanDeleteHoudiniNodes = false; - - // AssetState will be updated by changes to the HoudiniAsset - // or parameter changes on the Component template. - AssetState = EHoudiniAssetState::None; - bHasRegisteredComponentTemplate = false; - bHasBeenLoaded = false; - bUpdatedFromTemplate = false; - - // Disable proxy mesh by default (unsupported for now) - bOverrideGlobalProxyStaticMeshSettings = true; - bEnableProxyStaticMeshOverride = false; - bEnableProxyStaticMeshRefinementByTimerOverride = false; - bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = false; - bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = false; - StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; - - // Set default mobility to Movable - Mobility = EComponentMobility::Movable; -} - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() -{ - // We need to propagate changes made here back to the corresponding component in - // the Blueprint Generated Class ("_GEN_VARIABLE"). The reason being that - // the Blueprint editor works directly with the GEN_VARIABLE component (all - // PostEditChange() calls, Details Customizations, etc will receive the GEN_VARIABLE instance) BUT - // when the Editor runs the construction script it uses a different component instance, so all changes - // made to that instance won't write back to the Blueprint definition. - // To Summarize: - // Be sure to sync the Parameters array (and any other relevant properties) back - // to the corresponding component on the Blueprint Generated class otherwise these wont be - // accessible in the Details Customization callbacks. - - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] To Component: %s"), *(CachedTemplateComponent->GetPathName())); - - // This should never be called by component templates. - check(!IsTemplate()); - - if (!CachedTemplateComponent.IsValid()) - return; - - USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; - check(SCS); - - /* - USCS_Node* SCSNodeForInstance = FindSCSNodeForInstanceComponent(SCS, this); - if (SCSNodeForInstance) - { - - } - else - { - - } - */ - - //// If we don't have an SCS node for this preview instance, we need to create one, regardless - //// of whether output updates are required. - //if (!CachedTemplateComponent->bOutputsRequireUpdate && SCSNodeForInstance != nullptr) - // return; - - // TODO: If the blueprint editor is NOT open, then we shouldn't attempting - // to copy state back to the BPGC at all! - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - check(BlueprintEditor); - - TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); - check(SCSEditor); - - USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - // check(SCSHACNode); - - // This is the actor instance that is being used for component editing. - AActor* PreviewActor = GetPreviewActor(); - check(PreviewActor); - - // NOTE: Inputs are only from component templates to instances, not the other way around ... I think. - - // ----------------------------------------------------- - // Copy outputs to component template - // ----------------------------------------------------- - - // Populate / update the outputs for the template from the preview / instance. - // TODO: Wrap the Blueprint manipulation in a transaction - TArray& TemplateOutputs = CachedTemplateComponent->Outputs; - TSet StaleTemplateOutputs(TemplateOutputs); - - TemplateOutputs.SetNum(Outputs.Num()); - CachedOutputNodes.Empty(); - - for (int i = 0; i < Outputs.Num(); i++) - { - // Find a output on the template that corresponds to this output from the instance. - UHoudiniOutput* TemplateOutput = nullptr; - UHoudiniOutput* InstanceOutput = nullptr; - InstanceOutput = Outputs[i]; - - //check(InstanceOutput) - if (!InstanceOutput || InstanceOutput->IsPendingKill()) - continue; - - // Ensure that instance outputs won't delete houdini content. - // Houdini content should only be allowed to be deleted from - // the component template. - InstanceOutput->SetCanDeleteHoudiniNodes(false); - - TemplateOutput = TemplateOutputs[i]; - - if (TemplateOutput) - { - check(TemplateOutput->GetOuter() == CachedTemplateComponent.Get()); - StaleTemplateOutputs.Remove(TemplateOutput); - } - - - if (TemplateOutput) - { - // Copy properties from the current instance component while preserving output objects - // and instanced outputs. - TemplateOutput->CopyPropertiesFrom(InstanceOutput, true); - } - else - { - // NOTE: If the template output is NULL it means that the HDA spawned a new component / output in the transient world - // and the new output object needs to be copied back to the BPGC. - - // Corresponding template output could not be found. Create one by duplicating the instance output. - TemplateOutput = InstanceOutput->DuplicateAndCopyProperties(CachedTemplateComponent.Get(), FName(InstanceOutput->GetName())); - // Treat these the same one would components created by CreateDefaultSubObject. - // NOTE: CreateDefaultSubobject performs lots of checks, and unfortunately we can't use it directly (it is - // only allowed to be used in a constructor). Not sure whether we need to either. For now, we just set the - // object flags to be similar to components created by CreateDefaultSubobject. - TemplateOutput->SetFlags(RF_Public|RF_ArchetypeObject|RF_DefaultSubObject); - TemplateOutputs[i] = TemplateOutput; - } - - check(TemplateOutput); - TemplateOutput->SetCanDeleteHoudiniNodes(false); - - // Keep track of potential stale output objects on the template component, for this output. - TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); - TArray StaleTemplateObjects; - TemplateOutputObjects.GetKeys(StaleTemplateObjects); - - for (auto& Entry : InstanceOutput->GetOutputObjects()) - { - - // Prepare the FHoudiniOutputObject for the template component - const FHoudiniOutputObject& InstanceObj = Entry.Value; - FHoudiniOutputObject TemplateObj; - - // Any output present in the Instance Outputs should be - // transferred to the template. - // Remove this output object from stale outputs list. - StaleTemplateObjects.Remove(Entry.Key); - - if (TemplateOutputObjects.Contains(Entry.Key)) - { - // Reuse the existing template object - TemplateObj = TemplateOutputObjects.FindChecked(Entry.Key); - } - else - { - // Create a new template output object object by duplicating the instance object. - // Keep the output object, but clear the output component since we have to - // create a new component template. - TemplateObj = InstanceObj; - TemplateObj.ProxyComponent = nullptr; - TemplateObj.OutputComponent = nullptr; - TemplateObj.ProxyObject = nullptr; - } - - USceneComponent* ComponentInstance = Cast(InstanceObj.OutputComponent); - USceneComponent* ComponentTemplate = Cast(TemplateObj.OutputComponent); - UObject* OutputObject = InstanceObj.OutputObject; - - if (ComponentInstance) - { - // The translation process has either constructed new components, or it is - // reusing existing components, or changed an output (or all or none of the aforementioned). - // Carefully inspect the SCS graph to determine whether there is a corresponding - // (and compatible) node for this output. If not, create a new node and remove unusable node, if any. - - USCS_Node* ComponentNode = nullptr; - { - // Check whether the current OutputComponent being referenced by the template is still valid. - // Even if it was removed in the editor, it doesn't have any associated destroyed / pendingkill state. - // Instead we're going to check for validity by finding an SCS node with a matching template component. - bool bValidComponentTemplate = (ComponentTemplate != nullptr); - if (ComponentTemplate) - { - - ComponentNode = FindSCSNodeForTemplateComponentInClassHierarchy(ComponentTemplate); - bValidComponentTemplate = bValidComponentTemplate && (ComponentNode != nullptr); - } - - if (!bValidComponentTemplate) - { - // Either this component was removed from the editor or it doesn't exist yet. - // Ensure the references are cleared - TemplateObj.OutputComponent = nullptr; - ComponentTemplate = nullptr; - } - } - - // NOTE: we can't use the component instance name directly due to the Blueprint compiler performing an internal checking - // using FComponentEditorUtils::IsValidVariableNameString(), which will return false if the name looks like an autogenerated name... - //FString ComponentName = ComponentInstance->GetName(); - FString ComponentName = FBlueprintEditorUtils::GetClassNameWithoutSuffix(ComponentInstance->GetClass()); - FName ComponentFName = FName(ComponentName); - - - const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )( - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | - EditorUtilities::ECopyOptions::CallPostEditChangeProperty | - EditorUtilities::ECopyOptions::CallPostEditMove); - - if (IsValid(ComponentNode)) - { - // Check if we have an existing and compatible SCS node containing a USceneComponent as a template component. - bool bComponentNodeIsValid = true; - - ComponentTemplate = Cast(ComponentNode->ComponentTemplate); - - bComponentNodeIsValid = bComponentNodeIsValid && ComponentInstance->GetClass() == ComponentNode->ComponentClass; - bComponentNodeIsValid = bComponentNodeIsValid && ComponentTemplate != nullptr; - // TODO: Do we need to perform any other compatibility checks? - - if (!bComponentNodeIsValid) - { - // Component template is not compatible. We can't reuse it. - - SCSHACNode->RemoveChildNode(ComponentNode); - SCS->RemoveNode(ComponentNode); - ComponentNode = nullptr; - ComponentTemplate = nullptr; - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - } - - if (ComponentNode) - { - // We found a reusable SCS node. Just copy the component instance - // properties over to the existing template. - check(ComponentNode->ComponentTemplate); - - // UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - // //Params.bReplaceObjectClassReferences = false; - // Params.bDoDelta = false; // Perform a deep copy - // Params.bClearReferences = false; - // UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - - FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - - ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; - } - else - { - // We couldn't find a reusable SCS node. - // Duplicate the instance component and create a new corresponding SCS node - ComponentNode = SCS->CreateNode(ComponentInstance->GetClass(), ComponentFName); - - UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // We need a deep copy of parameters here so the CDO values get copied as well - UEditorEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - // FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); - - // { - // UInstancedStaticMeshComponent* Component = Cast(ComponentNode->ComponentTemplate); - // if (Component) - // { - // } - // } - - // NOTE: The EComponentCreationMethod here is currently set to be the same as a component that was - // created manually in the editor. - ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; - - // Add this node to the SCS root set. - - // Attach the new node the HAC SCS node - // NOTE: This will add the node to the SCS->AllNodes list too but it won't update - // the nodename map. We can't forcibly update the Node/Name map either since the - // relevant functions have not been exported. - SCSHACNode->AddChildNode(ComponentNode); - - // Set the output component. - TemplateObj.OutputComponent = ComponentNode->ComponentTemplate; - - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - - // Cache the mapping between the output and the SCS node. - check(ComponentNode); - CachedOutputNodes.Add(Entry.Key, ComponentNode->VariableGuid); - } // if (ComponentInstance) - /* - else if (InstanceObj.OutputObject) - { - - } - */ - - // Add the updated output object to the template output - TemplateOutputObjects.Add(Entry.Key, TemplateObj); - } - - // Cleanup stale objects for this template output. - for (const auto& StaleId : StaleTemplateObjects) - { - FHoudiniOutputObject& OutputObj = TemplateOutputObjects.FindChecked(StaleId); - - // Ensure the component template is no longer referencing this output. - TemplateOutputObjects.Remove(StaleId); - - USceneComponent* TemplateComponent = Cast(OutputObj.OutputComponent); - - if (TemplateComponent) - { - USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(TemplateComponent); - if (StaleNode) - { - - SCS->RemoveNode(StaleNode, false); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - /* - else - { - - } - */ - } - /* - else - { - - } - */ - } - } //for (int i = 0; i < Outputs.Num(); i++) - - // Clean up stale outputs on the component template. - for (UHoudiniOutput* StaleOutput : StaleTemplateOutputs) - { - if (!StaleOutput) - continue; - - // Remove any components contained in this output from the SCS graph - for (auto& Entry : StaleOutput->GetOutputObjects()) - { - FHoudiniOutputObject& StaleObject = Entry.Value; - USceneComponent* OutputComponent = Cast(StaleObject.OutputComponent); - - if (OutputComponent) - { - - USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(OutputComponent); - if (StaleNode) - { - - SCS->RemoveNode(StaleNode, false); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - } - } - - TemplateOutputs.Remove(StaleOutput); - //StaleOutput->ConditionalBeginDestroy(); - } - - SCS->ValidateSceneRootNodes(); - - // Copy parameters from this component to the template component. - // NOTE: We need to do this since the preview component will be cooking the HDA and get populated with - // all the parameters. This data needs to be sent back to the component template. - UClass* ComponentClass = CachedTemplateComponent->GetClass(); - UHoudiniAssetBlueprintComponent* DefaultObj = Cast(ComponentClass->GetDefaultObject()); - bool bBPStructureModified = false; - CachedTemplateComponent->CopyDetailsFromComponent( - this, - true, - true, - true, - false, - true, - bBPStructureModified, - /* SetFlags */ CachedTemplateComponent->GetMaskedFlags(RF_PropagateToSubObjects)); - - if (bBPStructureModified) - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - - // Copy the cached output nodes back to the template so that - // reconstructed actors can correctly update output objects - // with newly constructed components during ApplyComponentInstanceData() calls. - CachedTemplateComponent->CachedOutputNodes = CachedOutputNodes; - - CachedTemplateComponent->MarkPackageDirty(); - PostEditChange(); - - CachedTemplateComponent->AssetId = AssetId; - CachedTemplateComponent->HapiGUID = HapiGUID; - CachedTemplateComponent->AssetCookCount = AssetCookCount; - CachedTemplateComponent->AssetStateResult = AssetStateResult; - CachedTemplateComponent->bLastCookSuccess = bLastCookSuccess; - -#if WITH_EDITOR - // TODO: Do we need to handle this right now or can we wait for the next Houdini Engine manager tick to process it? - if (CachedTemplateComponent->NeedBlueprintStructureUpdate()) - { - // We are about to recompile the blueprint. This will reconstruct the preview actor so we need to ensure - // that the old actor won't release the houdini nodes. - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); - SetCanDeleteHoudiniNodes(false); - } - /*else if (CachedTemplateComponent->NeedBlueprintUpdate()) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(CachedTemplateComponent.Get()); - }*/ -#endif -} -#endif - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties) -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] From Component: %s"), *(FromComponent->GetPathName())); - - // This should never be called by component templates. - check(!IsTemplate()); - - // Make sure all TransientDuplicate properties from the Template Component needed by this transient component - // gets copied. - - ComponentGUID = FromComponent->ComponentGUID; - - /* - { - const TArray Children = GetAttachChildren(); - for (USceneComponent* Child : Children) - { - if (!Child) - continue; - } - } - */ - - // AssetState = FromComponent->PreviewAssetState; - - // This state should not be shared between template / instance components. - //bFullyLoaded = FromComponent->bFullyLoaded; - - bNoProxyMeshNextCookRequested = FromComponent->bNoProxyMeshNextCookRequested; - - // Reconstruct outputs and update them to point to component instances as opposed to templates. - UObject* TemplateOuter = CachedTemplateComponent->GetOuter(); - - USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; - check(SCS); - - // NOTE: We can find the SCS node for the HoudiniAssetComponent from either the template component or the instance (editor preview) component. - USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - check(SCSHACNode); - - // ----------------------------------------------------- - // Copy outputs to component template - // ----------------------------------------------------- - - TArray& TemplateOutputs = CachedTemplateComponent->Outputs; - - TSet StaleInstanceOutputs(Outputs); - - Outputs.SetNum(TemplateOutputs.Num()); - - for (int i = 0; i < TemplateOutputs.Num(); i++) - { - UHoudiniOutput* TemplateOutput = TemplateOutputs[i]; - if (!IsValid(TemplateOutput)) - continue; - - UHoudiniOutput* InstanceOutput = Outputs[i]; - if (!(InstanceOutput->GetOuter() == this)) - InstanceOutput = nullptr; - - if (InstanceOutput) - { - StaleInstanceOutputs.Remove(InstanceOutput); - } - - if (InstanceOutput) - { - // Copy properties from the current instance component while preserving output objects - // and instanced outputs. - InstanceOutput->CopyPropertiesFrom(TemplateOutput, true); - } - else - { - InstanceOutput = TemplateOutput->DuplicateAndCopyProperties(this, FName(TemplateOutput->GetName())); - if (IsValid(InstanceOutput)) - InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); - } - - Outputs[i] = InstanceOutput; - - if (!IsValid(InstanceOutput)) - continue; - - InstanceOutput->SetCanDeleteHoudiniNodes(false); - - TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); - TMap& InstanceOutputObjects = InstanceOutput->GetOutputObjects(); - TArray StaleOutputObjects; - InstanceOutputObjects.GetKeys(StaleOutputObjects); - - for (auto& Entry : TemplateOutputObjects) - { - const FHoudiniOutputObject& TemplateObj = Entry.Value; - FHoudiniOutputObject InstanceObj = TemplateObj; - - if (!InstanceOutputObjects.Contains(Entry.Key)) - continue; - - StaleOutputObjects.Remove(Entry.Key); - InstanceObj = InstanceOutputObjects.FindChecked(Entry.Key); - - } // for (auto& Entry : TemplateOutputObjects) - - // Cleanup stale output objects for this output. - for (const auto& StaleId : StaleOutputObjects) - { - //TemplateOutput - //check(TemplateOutputs); - - FHoudiniOutputObject& OutputObj = InstanceOutputObjects.FindChecked(StaleId); - - InstanceOutputObjects.Remove(StaleId); - if (OutputObj.OutputComponent) - { - //OutputObj.OutputComponent->ConditionalBeginDestroy(); - OutputObj.OutputComponent = nullptr; - } - } - } // for (int i = 0; i < TemplateOutputs.Num(); i++) - - // Cleanup any stale outputs found on the component instance. - for (UHoudiniOutput* StaleOutput : StaleInstanceOutputs) - { - if (!StaleOutput) - continue; - - if (!(StaleOutput->GetOuter() == this)) - continue; - - // We don't want to clear stale outputs on components instances. Only on template components. - StaleOutput->SetCanDeleteHoudiniNodes(false); - } - - // Copy parameters from the component template to the instance. - bool bBlueprintStructureChanged = false; - CopyDetailsFromComponent(FromComponent, - false, - bClearFromInputs, - bClearToInputs, - false, - true, - bBlueprintStructureChanged, - /*SetFlags*/ RF_Public, - /*ClearFlags*/ RF_DefaultSubObject|RF_ArchetypeObject); -} -#endif - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent( - UHoudiniAssetBlueprintComponent* FromComponent, - const bool bCreateSCSNodes, - const bool bClearChangedToInputs, - const bool bClearChangedFromInputs, - const bool bInCanDeleteHoudiniNodes, - const bool bCopyInputObjectComponentProperties, - bool &bOutBlueprintStructureChanged, - EObjectFlags SetFlags, - EObjectFlags ClearFlags) -{ - check(FromComponent); - - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] FromComponent: %s"), *(FromComponent->GetPathName())); - - /* - if (!FromComponent->HoudiniAsset) - { - return; - } - */ - - // TODO: Try to reuse objects here when we're able. - //// Copy UHoudiniOutput state from instance to template - //UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - ////Params.bReplaceObjectClassReferences = false; - ////Params.bClearReferences = false; - //Params.bDoDelta = true; - //UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - - // Record input remapping that will need to take place when duplicating parameters. - TMap InputMapping; - - // ----------------------------------------------------- - // Copy inputs - // ----------------------------------------------------- - - // TODO: Add support for input components - { - TArray& FromInputs = FromComponent->Inputs; - TSet StaleInputs(Inputs); - USimpleConstructionScript* SCS = GetSCS(); - USCS_Node* SCSHACNode = nullptr; - - if (bCreateSCSNodes) - { - SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - } - - Inputs.SetNum(FromInputs.Num()); - for (int i = 0; i < FromInputs.Num(); i++) - { - UHoudiniInput* FromInput = nullptr; - UHoudiniInput* ToInput = nullptr; - FromInput = FromInputs[i]; - - check(FromInput); - - ToInput = Inputs[i]; - - if (ToInput) - { - // Check whether the instance and template input objects are compatible. - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - - if (!bIsValid) - { - ToInput = nullptr; - } - } - - // TODO: Process stale input objects - - // NOTE: The CopyStateFrom() / DuplicateAndCopyState() will copy/duplicate/cleanup internal inputs to - // ensure that there aren't any shared instances between the ToInput/FromInput. - if (ToInput) - { - // We have a compatible input that we can reuse. - StaleInputs.Remove(ToInput); - ToInput->CopyStateFrom(FromInput, true, bInCanDeleteHoudiniNodes); - } - else - { - - // We don't have an existing / compatible input. Create a new one. - ToInput = FromInput->DuplicateAndCopyState(this, bInCanDeleteHoudiniNodes); - if (SetFlags != RF_NoFlags) - ToInput->SetFlags(SetFlags); - if (ClearFlags != RF_NoFlags) - ToInput->ClearFlags( ClearFlags ); - } - - check(ToInput); - - - UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, bCopyInputObjectComponentProperties, bCreateSCSNodes, SCSHACNode, &bOutBlueprintStructureChanged); - - Inputs[i] = ToInput; - InputMapping.Add(FromInput, ToInput); - - if (bClearChangedToInputs) - { - // Clear the changed flags on the FromInput so that it doesn't trigger - // another update. The ToInput will now be carrying to changed/update flags. - ToInput->MarkChanged(false); - ToInput->MarkAllInputObjectsChanged(false); - } - - if (bClearChangedFromInputs) - { - // Clear the changed flags on the FromInput so that it doesn't trigger - // another update. The ToInput will now be carrying to changed/update flags. - FromInput->MarkChanged(false); - FromInput->MarkAllInputObjectsChanged(false); - } - } - - // Cleanup any stale inputs from this component. - // NOTE: We would typically only have stale inputs when copying state from - // the component instance to the component template. Garbage collection - // eventually picks up the input objects and removes the content - // but until such time we are stuck with those nodes as inputs in the Houdini session - // so we get rid of those nodes immediately here to avoid some user confusion. - for (UHoudiniInput* StaleInput : StaleInputs) - { - if (!IsValid(StaleInput)) - continue; - - check(StaleInput->GetOuter() == this); - - if (StaleInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - StaleInput->ConditionalBeginDestroy(); - } - } - - - // ----------------------------------------------------- - // Copy parameters (and optionally remap inputs). - // ----------------------------------------------------- - TMap ParameterMapping; - - TArray& FromParameters = FromComponent->Parameters; - Parameters.SetNum(FromParameters.Num()); - - for (int i = 0; i < FromParameters.Num(); i++) - { - UHoudiniParameter* FromParameter = nullptr; - UHoudiniParameter* ToParameter = nullptr; - - FromParameter = FromParameters[i]; - - check(FromParameter); - - if (Parameters.IsValidIndex(i)) - { - ToParameter = Parameters[i]; - } - - if (ToParameter) - { - bool bIsValid = true; - // Check whether To/From parameters are compatible - bIsValid = bIsValid && ToParameter->Matches(*FromParameter); - bIsValid = bIsValid && ToParameter->GetOuter() == this; - - if (!bIsValid) - ToParameter = nullptr; - } - - if (ToParameter) - { - // Parameter already exists. Simply sync the state. - ToParameter->CopyStateFrom(FromParameter, true, ClearFlags, SetFlags); - } - else - { - // TODO: Check whether parameters are the same to avoid recreating them. - ToParameter = FromParameter->DuplicateAndCopyState(this, ClearFlags, SetFlags); - Parameters[i] = ToParameter; - } - - check(ToParameter); - ParameterMapping.Add(FromParameter, ToParameter); - - if (bClearChangedFromInputs) - { - // We clear the Changed flag on the FromParameter (most likely on the component template) - // since the template parameter state has now been transfered to the preview component and - // will resume processing from there. - FromParameter->MarkChanged(false); - } - } - - // Apply remappings on the new parameters - for (UHoudiniParameter* ToParameter : Parameters) - { - ToParameter->RemapParameters(ParameterMapping); - ToParameter->RemapInputs(InputMapping); - } - - FProperty* ParametersProperty = GetClass()->FindPropertyByName(TEXT("Parameters")); - FPropertyChangedEvent Evt(ParametersProperty); - PostEditChangeProperty(Evt); - - bEnableCooking = FromComponent->bEnableCooking; - bRecookRequested = FromComponent->bRecookRequested; - bRebuildRequested = FromComponent->bRebuildRequested; -} - -void -UHoudiniAssetBlueprintComponent::UpdateInputObjectComponentReferences( - USimpleConstructionScript* SCS, - UHoudiniInput* FromInput, - UHoudiniInput* ToInput, - const bool bCopyInputObjectProperties, - const bool bCreateMissingSCSNodes, - USCS_Node* SCSHACParent, - bool* bOutSCSNodeCreated) -{ - TArray ToInputObjects; - TArray FromInputObjects; - TArray StaleInputObjects; - - ToInput->GetAllHoudiniInputSplineComponents(ToInputObjects); - FromInput->GetAllHoudiniInputSplineComponents(FromInputObjects); - - StaleInputObjects = ToInputObjects; - - const int32 NumInputObjects = FromInputObjects.Num(); - ToInputObjects.SetNum(NumInputObjects); - - const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - //Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; - - for(int32 InputObjectIndex = 0; InputObjectIndex < NumInputObjects; ++InputObjectIndex) - { - UHoudiniInputHoudiniSplineComponent* FromInputObject = FromInputObjects[InputObjectIndex]; - UHoudiniInputHoudiniSplineComponent* ToInputObject = ToInputObjects[InputObjectIndex]; - if (!FromInputObject) - continue; - if (!ToInputObject) - continue; - - USCS_Node* SCSNode = nullptr; - if (CachedInputNodes.Contains(ToInputObject->Guid)) - { - // Reuse / update the existing SCS node. - SCSNode = SCS->FindSCSNodeByGuid( CachedInputNodes.FindChecked(ToInputObject->Guid) ); - } - - if (!SCSNode) - { - if (!bCreateMissingSCSNodes) - continue; // This input object should be removed. - } - - USceneComponent* ToComponent = nullptr; - USceneComponent* FromComponent = Cast(FromInputObject->GetObject()); - - StaleInputObjects.Remove(ToInputObject); - - if (FromComponent) - { - if (!SCSNode) - { - if (bCreateMissingSCSNodes) - { - // Create a new SCS node - SCSNode = SCS->CreateNode(FromComponent->GetClass()); - SCSHACParent->AddChildNode(SCSNode); - if (bOutSCSNodeCreated) - { - *bOutSCSNodeCreated = true; - } - AddInputObjectMapping(ToInputObject->Guid, SCSNode->VariableGuid); - } - } - - if (SCSNode) - { - if (bCreateMissingSCSNodes) - { - // If we have been instructed to create missing SCS nodes, assume we are copying - // the the component template. - ToComponent = Cast(SCSNode->ComponentTemplate); - } - else - { - // We are not copying to the component template, so we're assuming this is a - // component instance. Find the component on the owning actor that matches the SCS node. - AActor* ToOwningActor = ToInput->GetTypedOuter(); - check(ToOwningActor); - - ToComponent = Cast(FindComponentInstanceInActor(ToOwningActor, SCSNode)); - } - - if (bCopyInputObjectProperties && ToComponent) - { - USceneComponent* ToAttachParent = ToComponent->GetAttachParent(); - // Copy specific properties from the component template to the instance, if supported by the component. - // We typically resort to this in order to transfer Transient and TransientDuplicate properties from the - // component template over to the instance (typically HasChanged / NeedsToTriggerUpdate flags) in order for - // the instance to cook properly. - IHoudiniEngineCopyPropertiesInterface* ToCopyableComponent = Cast(ToComponent); - if (ToCopyableComponent) - { - // Let the component manage its own data copying. - ToCopyableComponent->CopyPropertiesFrom(FromComponent); - } - else - { - // The component doesn't implement the property copy interface. Simply do a general property copy. - //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, ToComponent, Params); - FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, ToComponent, ComponentCopyOptions); - } - ToComponent->PostEditChange(); - } - } - } - - ToInputObject->Update(ToComponent); - ToInputObjects[InputObjectIndex] = ToInputObject; - } - - for (UHoudiniInputObject* StaleInputObject : StaleInputObjects) - { - if (!StaleInputObject) - continue; - StaleInputObject->InvalidateData(); - ToInput->RemoveHoudiniInputObject(StaleInputObject); - ToInput->MarkChanged(true); - // TODO: Find the corresponding SCS node and remove it - } -} - -#endif - -#if WITH_EDITOR -bool -UHoudiniAssetBlueprintComponent::HasOpenEditor() const -{ - if (IsTemplate()) - { - IAssetEditorInstance* EditorInstance = FindEditorInstance(); - - return EditorInstance != nullptr; - } - - return false; -} -#endif - -#if WITH_EDITOR -IAssetEditorInstance* -UHoudiniAssetBlueprintComponent::FindEditorInstance() const -{ - UClass* BPGC = Cast(GetOuter()); - if (!IsValid(BPGC)) - return nullptr; - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!IsValid(Blueprint)) - return nullptr; - if (!CachedAssetEditorSubsystem.IsValid()) - return nullptr; - - IAssetEditorInstance* EditorInstance = CachedAssetEditorSubsystem->FindEditorForAsset(Blueprint, false); - - return EditorInstance; -} -#endif - -#if WITH_EDITOR -AActor* -UHoudiniAssetBlueprintComponent::GetPreviewActor() const -{ - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - if (BlueprintEditor) - { - return BlueprintEditor->GetPreviewActor(); - } - return nullptr; -} -#endif - -UHoudiniAssetComponent* -UHoudiniAssetBlueprintComponent::GetCachedTemplate() const -{ - return CachedTemplateComponent.Get(); -} - -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateParameters() const -//{ -// return IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateInputs() const -//{ -// return !IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateOutputs() const -//{ -// return !IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanProcessOutputs() const -//{ -// return !IsTemplate(); -//} - -//bool -//UHoudiniAssetBlueprintComponent::CanInstantiateAsset() const -//{ -// // If this is a preview component, it should not trigger an asset instantiation. It should wait -// // for the BPGC template component to finish the cook, get the synced data and then translate. -// -// if (IsPreview()) -// return false; -// -// return true; -//} -// -//// Check whether the HAC can translate Houdini outputs at all -//bool -//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini() const -//{ -// // Template components can't translate Houdini output since they typically do not exist in a world. -// if (IsTemplate()) -// return false; -// // Preview components and normally instanced actors can translate Houdini outputs. -// return true; -//} -// -//// Check whether the HAC can translate a specific output type. -//bool -//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini(EHoudiniOutputType OutputType) const -//{ -// // Blueprint components have limited translation support, for now. -// if (OutputType == EHoudiniOutputType::Mesh) -// return true; -// -// return false; -//} -// -bool -UHoudiniAssetBlueprintComponent::CanDeleteHoudiniNodes() const -{ - return bCanDeleteHoudiniNodes; -} - -void -UHoudiniAssetBlueprintComponent::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; - - for (UHoudiniInput* Input : Inputs) - { - Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for (UHoudiniOutput* Output : Outputs) - { - Output->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -bool -UHoudiniAssetBlueprintComponent::IsValidComponent() const -{ - if (!Super::IsValidComponent()) - return false; - - if (IsTemplate()) - { - UObject* Outer = this->GetOuter(); - if (!IsValid(Outer)) - return false; - UBlueprintGeneratedClass* BPGC = Cast(Outer); - if (!BPGC) - return false; - // Ensure this component is still in the SCS - USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; - if (!SCS) - return false; - USCS_Node* SCSNode = FindSCSNodeForTemplateComponentInClassHierarchy(this); - if (!SCSNode) - return false; - /*UClass* OwnerClass = Outer->GetClass(); - if (!IsValid(OwnerClass)) - return false;*/ - /*UBlueprint* Blueprint = Cast(Outhe); - if (Blueprint) - { - - }*/ - } - -#if WITH_EDITOR - if (!IsTemplate()) - { - if (!GetOwner()) - { - // If it's not a template, it needs an owner! - return false; - } - - USimpleConstructionScript* SCS = GetSCS(); - if (SCS) - { - // We're dealing with a Blueprint related component. - AActor* PreviewActor = GetPreviewActor(); - AActor* OwningActor = GetOwner(); - if (!OwningActor) - return false; - if (OwningActor != PreviewActor) - { - return false; - } - } - - } - - if (IsPreview() && false) - { - USimpleConstructionScript* SCS = GetSCS(); - if (!SCS) - return false; // Preview components should have an SCS. - - // We want to specifically detect whether an editor component is still being previewed. We do this - // by checking whether the owning actor is still the active editor actor in the SCS. - AActor* PreviewActor = GetPreviewActor(); - if (!PreviewActor) - { - return false; - } - - // Ensure this component still belongs the to the current preview actor. - if (PreviewActor != GetOwner()) - { - return false; - } - - /* - AActor* EditorActor = SCS->GetComponentEditorActorInstance(); - if (GetOwner() != EditorActor) - { - return false; - } - */ - } -#endif - - return true; -} - -bool -UHoudiniAssetBlueprintComponent::IsInputTypeSupported(EHoudiniInputType InType) const -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - case EHoudiniInputType::Curve: - return true; - break; - default: - break; - } - return false; -} - -bool -UHoudiniAssetBlueprintComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const -{ - switch (InType) - { - case EHoudiniOutputType::Mesh: - case EHoudiniOutputType::Instancer: - return true; - break; - default: - break; - } - return false; -} - -bool -UHoudiniAssetBlueprintComponent::IsProxyStaticMeshEnabled() const -{ - // TODO: Investigate adding support for proxy meshes in BP - // Disabled for now - return false; -} - -//void -//UHoudiniAssetBlueprintComponent::BroadcastPreAssetCook() -//{ -// // ------------------------------------------------ -// // NOTE: This code will run on TEMPLATE components -// // ------------------------------------------------ -// -// // The HoudiniAsset is about to be recooked. This flag will indicate to -// // the transient components that output processing needs to be baked -// // back to the BP definition. -// bOutputsRequireUpdate = true; -// -// Super::BroadcastPreAssetCook(); -//} - -void -UHoudiniAssetBlueprintComponent::OnPrePreCook() -{ - check(IsPreview()); - - Super::OnPrePreCook(); - - // We need to allow deleting houdini nodes - SetCanDeleteHoudiniNodes(true); -} - -void -UHoudiniAssetBlueprintComponent::OnPostPreCook() -{ - check(IsPreview()); - - Super::OnPostPreCook(); - - // Ensure the houdini nodes can be deleted during the translation process. - SetCanDeleteHoudiniNodes(false); -} - -void -UHoudiniAssetBlueprintComponent::OnPreOutputProcessing() -{ - check(IsPreview()); - - Super::OnPreOutputProcessing(); - - // Ensure the houdini nodes can be deleted during the translation process. - SetCanDeleteHoudiniNodes(true); -} - -void -UHoudiniAssetBlueprintComponent::OnPostOutputProcessing() -{ - Super::OnPostOutputProcessing(); - - // ------------------------------------------------ - // NOTE: - // In Blueprint editor mode, this code will run on PREVIEW components - // In Map editor mode, this code will run on component instances. - // ------------------------------------------------ - if (IsPreview()) - { - // Ensure all the inputs / outputs belonging to the - // preview actor won't be deleted by PreviewActor destruction. - SetCanDeleteHoudiniNodes(false); - -#if WITH_EDITOR - CopyStateToTemplateComponent(); -#endif - - } - bUpdatedFromTemplate = false; -} - -void UHoudiniAssetBlueprintComponent::OnPrePreInstantiation() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnPrePreInstantiation] Component: %s"), *(GetPathName())); - - check(IsPreview()); - - if (bUpdatedFromTemplate) - return; - - check(CachedTemplateComponent.IsValid()); - - // This HDA is about to be cooked but not through template parameter changes. It is likely that an input changed directly in the preview world. - // We need to flag our inputs and parameters appropriately in order to preserve their values. - - // We need to mark all our parameters as changed/not triggering update - for (auto CurrentParam : Parameters) - { - if (CurrentParam) - { - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(false); - } - } - - // We need to mark all our inputs as changed/not triggering update - for (auto CurrentInput : Inputs) - { - if (CurrentInput) - { - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(false); - CurrentInput->MarkDataUploadNeeded(true); - } - } -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniRegisterCompleted() -{ - if (IsTemplate()) - { - // TODO: Do we need to set any status flags or clear stuff to ensure - // the BP HAC will cook properly when the BP is opened again... - - // If the template is being registered, we need to invalidate the AssetId here since it likely - // contains a stale asset id from its last cook. - AssetId = -1; - // Template component's have very limited update requirements / capabilities. - // Mostly just cache parameters and cook state. - AssetState = EHoudiniAssetState::ProcessTemplate; - } - - Super::NotifyHoudiniRegisterCompleted(); -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniPreUnregister() -{ - if (IsTemplate()) - { - // Templates can delete Houdini nodes when they get deregistered. - SetCanDeleteHoudiniNodes(true); - } - Super::NotifyHoudiniPreUnregister(); -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniPostUnregister() -{ - InvalidateData(); - - Super::NotifyHoudiniPostUnregister(); - - if (IsTemplate()) - { - SetCanDeleteHoudiniNodes(false); - } -} - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::OnComponentCreated() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnComponentCreated] Component: %s"), *(GetPathName())); - - Super::OnComponentCreated(); - bUpdatedFromTemplate = false; - - CachePreviewState(); - - if (IsPreview()) - { - // Don't set an initial AssetState here. Preview components should only cook when template's - // Houdini Asset or HDA parameters have changed. - - // Clear these to ensure that we're not sharing references with the component template (otherwise - // the shared objects will get deleted when the component instance gets destroyed). - // These objects will be properly duplicated when copying state from the component template. - Inputs.Empty(); - Parameters.Empty(); - } - - // Wait until InitializeComponent() for blueprint construction to complete before we start caching blueprint data. - -} -#endif - - -void -UHoudiniAssetBlueprintComponent::OnRegister() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnRegister] Component: %s"), *(GetPathName())); - - Super::OnRegister(); - - // We run our Blueprint caching functions here since this the last hook that we have before - // entering HoudiniEngineTick(); - - CacheBlueprintData(); - CachePreviewState(); - - if (IsPreview()) - { - check(CachedTemplateComponent.Get()); - // Ensure that the component template has been registered since it needs to be processed for parameter updates by the HE manager. - if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(CachedTemplateComponent.Get())) - { - // The template component has not been registered yet, which means that we're probably busy opening a Blueprint editor and this - // preview component will need to be updated. - - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(CachedTemplateComponent.Get(), true); - CachedTemplateComponent->SetCanDeleteHoudiniNodes(false); - // Since we're likely opening a fresh blueprint editor, we'll need to instantiate the HDA. - bHasRegisteredComponentTemplate = true; - } - } - - if (IsTemplate()) - { - // We're initializing the asset id for HAC template here since it doesn't get unloaded - // from memory, for example, between Blueprint Editor open/close so we need to make sure - // that the AssetId has indeed been reset between registrations. - AssetId = -1; - } - - //TickInitialization(); -} - -void -UHoudiniAssetBlueprintComponent::BeginDestroy() -{ - Super::BeginDestroy(); -} - -void -UHoudiniAssetBlueprintComponent::DestroyComponent(bool bPromoteChildren) -{ - //FDebug::DumpStackTraceToLog(); - if (CachedTemplateComponent.IsValid() && TemplatePropertiesChangedHandle.IsValid()) - { - CachedTemplateComponent->Modify(); - CachedTemplateComponent->OnParametersChangedEvent.Remove(TemplatePropertiesChangedHandle); - } - Super::DestroyComponent(bPromoteChildren); -} - -void -UHoudiniAssetBlueprintComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - Super::OnComponentDestroyed(bDestroyingHierarchy); -} - -TStructOnScope -UHoudiniAssetBlueprintComponent::GetComponentInstanceData() const -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::GetComponentInstanceData] Component: %s"), *(GetPathName())); - - TStructOnScope ComponentInstanceData = MakeStructOnScope(this); - FHoudiniAssetBlueprintInstanceData* InstanceData = ComponentInstanceData.Cast(); - - InstanceData->AssetId = AssetId; - InstanceData->AssetState = AssetState; - InstanceData->SubAssetIndex = SubAssetIndex; - InstanceData->ComponentGUID = ComponentGUID; - InstanceData->HapiGUID = HapiGUID; - InstanceData->HoudiniAsset = HoudiniAsset; - InstanceData->SourceName = GetPathName(); - InstanceData->AssetCookCount = AssetCookCount; - InstanceData->bHasBeenLoaded = bHasBeenLoaded; - InstanceData->bHasBeenDuplicated = bHasBeenDuplicated; - InstanceData->bPendingDelete = bPendingDelete; - InstanceData->bRecookRequested = bRecookRequested; - InstanceData->bEnableCooking = bEnableCooking; - InstanceData->bForceNeedUpdate = bForceNeedUpdate; - InstanceData->bLastCookSuccess = bLastCookSuccess; - InstanceData->bRegisteredComponentTemplate = bHasRegisteredComponentTemplate; - - InstanceData->Inputs.Empty(); - - for (UHoudiniInput* Input : Inputs) - { - if (!Input) - continue; - UHoudiniInput* TransientInput = Input->DuplicateAndCopyState(GetTransientPackage(), false); - InstanceData->Inputs.Add(TransientInput); - } - - // Cache the current outputs - InstanceData->Outputs.Empty(); - int OutputIndex = 0; - for(UHoudiniOutput* Output : Outputs) - { - if (!Output) - continue; - - TMap OutputObjects = Output->GetOutputObjects(); - for (auto& Entry : OutputObjects) - { - FHoudiniAssetBlueprintOutput OutputObjectData; - OutputObjectData.OutputIndex = OutputIndex; - OutputObjectData.OutputObject = Entry.Value; - InstanceData->Outputs.Add(Entry.Key, OutputObjectData); - } - - ++OutputIndex; - } - - return ComponentInstanceData; - -} - -void -UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData(FHoudiniAssetBlueprintInstanceData* InstanceData, const bool bPostUCS) -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData] Component: %s"), *(GetPathName())); - check(InstanceData); - - if (!bPostUCS) - { - // Initialize the component before the User Construction Script runs - USimpleConstructionScript* SCS = GetSCS(); - check(SCS); - - TArray StaleInputs(Inputs); - - // We need to update references contain in inputs / outputs to point to new reconstructed components. - const int32 NumInputs = InstanceData->Inputs.Num(); - Inputs.SetNum(NumInputs); - for (int i = 0; i < NumInputs; ++i) - { - UHoudiniInput* FromInput = InstanceData->Inputs[i]; - UHoudiniInput* ToInput = Inputs[i]; - - if (ToInput) - { - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - if (!bIsValid) - { - ToInput = nullptr; - } - } - - if (ToInput) - { - // Reuse input - StaleInputs.Remove(ToInput); - ToInput->CopyStateFrom(FromInput, true, false); - } - else - { - // Create new input - ToInput = FromInput->DuplicateAndCopyState(this, false); - } - -#if WITH_EDITOR - // We can't create missing SCS nodes here since we're likely already in the middle of a - // Blueprint reconstruction. We'll have to recreate missing SCS nodes next time the - // component state if copied to the template. - UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, true, false); -#endif - - Inputs[i] = ToInput; - } - - // We need to update FHoudiniOutputObject SceneComponent references to - // the newly created components. Since we cached a map of Output Object IDs to - // SCSNodes (during CopyStateToTemplateComponent), we can the SCSNode that corresponds to this output objects and find - // the SceneComponent that matches the SCSNode's variable name. - // It is important to note that it is safe to do it this way since we're in the pre-UCS - // phase so that current components should match the SCS graph exactly (no user construction script - // interference here yet). - - for (auto& Entry : InstanceData->Outputs) - { - FHoudiniOutputObjectIdentifier& ObjectId = Entry.Key; - FHoudiniAssetBlueprintOutput& OutputData = Entry.Value; - - // NOTE: Output objects are going to be empty here since they dissapear during actor reconstruction. - // We'll need to repopulate from the instance data. - - check(Outputs.IsValidIndex(OutputData.OutputIndex)); - UHoudiniOutput* Output = Outputs[OutputData.OutputIndex]; - check(Output); - TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniOutputObject NewObject = OutputData.OutputObject; - - if (OutputData.OutputObject.OutputComponent) - { - // Update the output component reference. - check(CachedOutputNodes.Contains(ObjectId)) - const FGuid VariableGuid = CachedOutputNodes.FindChecked(ObjectId); - USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(VariableGuid); - - if (SCSNode) - { - // Find the component that corresponds to the SCS node. - USceneComponent* SceneComponent = FindActorComponentByName(GetOwner(), SCSNode->GetVariableName()); - NewObject.OutputComponent = SceneComponent; - } - else - { - NewObject.OutputComponent = nullptr; - } - } - - OutputObjects.Add(ObjectId, NewObject); - } - - if (CachedTemplateComponent.IsValid()) - { -#if WITH_EDITOR - CopyStateFromTemplateComponent( CachedTemplateComponent.Get(), false, false, true); -#endif - } - - AssetId = InstanceData->AssetId; - SubAssetIndex = InstanceData->SubAssetIndex; - ComponentGUID = InstanceData->ComponentGUID; - HapiGUID = InstanceData->HapiGUID; - - // Apply the previous HoudiniAsset to the component - // so that we can compare it against the template during CopyStateFromTemplate() calls to see whether it changed. - HoudiniAsset = InstanceData->HoudiniAsset; - - AssetCookCount = InstanceData->AssetCookCount; - bHasBeenLoaded = InstanceData->bHasBeenLoaded; - bHasBeenDuplicated = InstanceData->bHasBeenDuplicated; - bPendingDelete = InstanceData->bPendingDelete; - bRecookRequested = InstanceData->bRecookRequested; - bEnableCooking = InstanceData->bEnableCooking; - bForceNeedUpdate = InstanceData->bForceNeedUpdate; - bLastCookSuccess = InstanceData->bLastCookSuccess; - bHasRegisteredComponentTemplate = InstanceData->bRegisteredComponentTemplate; - - AssetState = InstanceData->AssetState; - - SetCanDeleteHoudiniNodes(false); - - } // if (!bPostUCS) - /* - else - { - // PostUCS - - } - */ -} - - -void -UHoudiniAssetBlueprintComponent::HoudiniEngineTick() -{ - if (!IsFullyLoaded()) - { - USimpleConstructionScript* SCS = GetSCS(); - if (SCS == nullptr) - { - OnFullyLoaded(); - } - else if (IsPreview()) - { - AActor* OwningActor = GetOwner(); - check(OwningActor); - - // If this is a *preview component*, it is important to wait for the template component to be fully loaded - // since it needs to be initialized so that the component instance can copy initial values from the template. - check(CachedTemplateComponent.Get()); - - if (CachedTemplateComponent->IsFullyLoaded()) - { -#if WITH_EDITOR - if(SCS->IsConstructingEditorComponents()) - { - // We're stuck in an editor blueprint construction / preview actor update. Wait some more. - } - else - { - OnFullyLoaded(); - } -#else - OnFullyLoaded(); -#endif - } - } - else - { - // Anything else can go onto being fully loaded at this point. - OnFullyLoaded(); - } - } -} - -void -UHoudiniAssetBlueprintComponent::OnFullyLoaded() -{ - Super::OnFullyLoaded(); - - // Check whether this component is inside a Blueprint editor. If this object lives outside the blueprint editor (in , then we need to ensure that this - // component won't be influencing the Blueprint asset. - - if (!IsTemplate()) - { -#if WITH_EDITOR - AActor* PreviewActor = GetPreviewActor(); -#else - AActor* PreviewActor = nullptr; -#endif - AActor* OwningActor = GetOwner(); - - if (!PreviewActor) - { - bIsInBlueprintEditor = false; - AssetState = EHoudiniAssetState::None; - return; - } - - if (OwningActor && PreviewActor != OwningActor) - { - bIsInBlueprintEditor = false; - AssetState = EHoudiniAssetState::None; - return; - } - } - - bIsInBlueprintEditor = true; - - CachePreviewState(); - CacheBlueprintData(); - - /* - for (UHoudiniOutput* Output : Outputs) - { - if (!Output) - continue; - } - */ - - if (IsTemplate()) - { - AssetId = -1; - AssetState = EHoudiniAssetState::ProcessTemplate; - } - - if (IsPreview()) - { - check(CachedTemplateComponent.Get()); - - // If this is a preview actor, sync initial settings from the component template -#if WITH_EDITOR - CopyStateFromTemplateComponent(CachedTemplateComponent.Get(), false, false, true); -#endif - - TemplatePropertiesChangedHandle = CachedTemplateComponent->OnParametersChangedEvent.AddUObject(this, &UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler); - if (bHoudiniAssetChanged) - { - - // The HoudiniAsset has changed, so we need to force the PreviewInstance to re-instantiate - AssetState = EHoudiniAssetState::NeedInstantiation; - bForceNeedUpdate = true; - bHoudiniAssetChanged = false; - // TODO: Make this better? - CachedTemplateComponent->bHoudiniAssetChanged = false; - } - - if (bHasRegisteredComponentTemplate) - { - // We have a newly registered component template. One of two things happened to cause this: - // 1. A new HoudiniAssetBlueprintComponent was created and registered. - // 2. The Blueprint Editor was closed / opened. - // The problem that arises in the #2 is that the template component was never fully unloaded - // from memory (it was deregistered but not destroyed). After deregistration we had the - // opportunity to invalidate asset/node ids but now that it has reregistered (without going - // through the normal initialization process) we will have to force a call to MarkAsNeedInstantiation - // during the next OnTemplateParametersChangedHandler() invocation. - bHasBeenLoaded = true; - } - } -} - -void -UHoudiniAssetBlueprintComponent::OnTemplateParametersChanged() -{ - OnParametersChangedEvent.Broadcast(this); -} - -void UHoudiniAssetBlueprintComponent::OnBlueprintStructureModified() -{ - check(IsTemplate()); - bBlueprintStructureModified = false; - -#if WITH_EDITOR - if (IsTemplate()) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(this); - } - else - { - check(CachedTemplateComponent.IsValid()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); - } -#endif -} - -void UHoudiniAssetBlueprintComponent::OnBlueprintModified() -{ - check(IsTemplate()); - bBlueprintModified = false; -#if WITH_EDITOR - FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(this); -#endif -} - -void -UHoudiniAssetBlueprintComponent::OnHoudiniAssetChanged() -{ - if (IsTemplate()) - { - // Invalidate data associated with this component since we're about to change and reinstantiate the Houdini Asset. - SetCanDeleteHoudiniNodes(true); - InvalidateData(); - SetCanDeleteHoudiniNodes(false); - Parameters.Empty(); - Inputs.Empty(); - } - - Super::OnHoudiniAssetChanged(); - - // Set on template components, then copied to preview components, and - // then used (and reset) during OnFullyLoaded. - bHoudiniAssetChanged = true; -} - -void -UHoudiniAssetBlueprintComponent::RegisterHoudiniComponent(UHoudiniAssetComponent *InComponent) -{ - // We only want to register this component if it is the preview actor for the Blueprint editor. -#if WITH_EDITOR - AActor* PreviewActor = GetPreviewActor(); -#else - AActor* PreviewActor = nullptr; -#endif - AActor* OwningActor = GetOwner(); - if (!OwningActor) - return; - - if (PreviewActor != OwningActor) - return; - - Super::RegisterHoudiniComponent(InComponent); -} - - - - -//bool UHoudiniAssetBlueprintComponent::TickInitialization() -//{ -// return true; -// -// if (IsFullyLoaded()) -// return true; -// -// bool bHasFinishedLoading = Super::TickInitialization(); -// -// if (!bHasFinishedLoading) -// return false; -// -// if (!IsTemplate()) -// { -// -// if (CachedTemplateComponent.Get()) -// { -// // Now that that SCS has finished constructing editor components, we can continue. -// // Copy the current state from the template component, in case there is something that can be processed. -// CopyStateFromTemplateComponent(CachedTemplateComponent.Get()); -// } -// -// AssetStateResult = EHoudiniAssetStateResult::None; -// AssetState = EHoudiniAssetState::None; -// -// bForceNeedUpdate = true; -// AssetState = EHoudiniAssetState::PostCook; -// } -// -// return true; -//} - -template -inline void -UHoudiniAssetBlueprintComponent::SetTypedValueAt(const FString& Name, ValueT& Value, int Index) -{ - ParamT* Parameter = Cast(FindParameterByName(Name)); - if (!Parameter) - return; - - Parameter->SetValueAt(Value, Index); -} - -bool -UHoudiniAssetBlueprintComponent::HasParameter(FString Name) -{ - return FindParameterByName(Name) != nullptr; -} - -void -UHoudiniAssetBlueprintComponent::SetFloatParameter(FString Name, float Value, int Index) -{ - SetTypedValueAt(Name, Value, Index); -} - -void -UHoudiniAssetBlueprintComponent::SetToggleValueAt(FString Name, bool Value, int Index) -{ - UHoudiniParameterToggle* Parameter = Cast(FindParameterByName(Name)); - if (!Parameter) - return; - - Parameter->SetValueAt(Value, Index); -} - -//void UHoudiniAssetBlueprintComponent::OnPostCookHandler(UHoudiniAssetComponent* InComponent) -//{ -// -// // Before this component handles any translation, we need to make sure that it still belongs to the editor actor. -// // When a blueprint gets recompiled, the editor actor gets replaced with a new one but the old actor has not yet -// // been ftroyed / garbage collected so its components still receive cook events from the template. -// -// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponent); -// if (!IsValid(ComponentTemplate)) -// return; -// -// CopyStateFromTemplateComponent(ComponentTemplate); -// bForceNeedUpdate = true; -//} - -void -UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler(UHoudiniAssetComponent* InComponentTemplate) -{ - if (!(AssetState == EHoudiniAssetState::None || AssetState == EHoudiniAssetState::NeedInstantiation || AssetState == EHoudiniAssetState::NeedRebuild)) - // Don't process parameter changes since we're already cooking -- it is going to break things badly if we do. - return; - - if (!IsValidComponent()) - return; - - UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); - if (!ComponentTemplate) - return; - - // The component instance needs to copy values from the template. - bool bBlueprintStructureChanged = false; -#if WITH_EDITOR - CopyDetailsFromComponent(ComponentTemplate, - false, - false, - true, - false, - true, - bBlueprintStructureChanged, - RF_Public, - RF_ClassDefaultObject|RF_ArchetypeObject); -#endif - - SetCanDeleteHoudiniNodes(false); - - if (bHasRegisteredComponentTemplate) - { - // NOTE: It is very important to call this *after* CopyDetailsFromComponent(), since CopyDetailsFromComponent - // will clobber the inputs and parameter states on this component. - - // If we already have a valid asset id, keep it. - if (AssetId >= 0) - { - MarkAsNeedCook(); - } - else - { - MarkAsNeedInstantiation(); - } - - bHasRegisteredComponentTemplate = false; - bFullyLoaded = true; // MarkAsNeedInstantiation sets this to false. Force to true. - // While MarkAsNeedInstantiation() sets ParametersChanged to true, it does not - // set the 'NeedToTriggerUpdate' flag (both of which needs to be true in order - // to trigger an HDA update) so we are going to force NeedUpdate() to return true - // in order to get an initial cook. - bForceNeedUpdate = true; - } - - bUpdatedFromTemplate = true; -} - -void -UHoudiniAssetBlueprintComponent::InvalidateData() -{ - if (IsTemplate()) - { - // Ensure transient properties are invalidated/released for parameters, inputs and outputs as if the - // the object was undergoing destruction since the template component will likely be reregistered - // without being destroyed. - for(UHoudiniParameter* Param : Parameters) - { - Param->InvalidateData(); - } - - for(UHoudiniInput* Input : Inputs) - { - Input->InvalidateData(); - } - - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - AssetId = -1; - } -} - -//void UHoudiniAssetBlueprintComponent::OnTemplateHoudiniAssetChangedHandler(UHoudiniAssetComponent* InComponentTemplate) -//{ -// -// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); -// if (!ComponentTemplate) -// return; -// check(IsPreview()); -// -// // The Houdini Asset was changed on the template. We need to recook. -// AssetState = EHoudiniAssetState::NeedInstantiation; -// -//} - -USceneComponent* -UHoudiniAssetBlueprintComponent::FindOwnerComponentByName(FName ComponentName) const -{ - AActor* Owner = GetOwner(); - if (!Owner) - return nullptr; - - return FindActorComponentByName(Owner, ComponentName); -} - -USceneComponent* -UHoudiniAssetBlueprintComponent::FindActorComponentByName(AActor* InActor, FName ComponentName) const -{ - const TSet& Components = InActor->GetComponents(); - - for (UActorComponent* Component : Components) - { - USceneComponent* SceneComponent = Cast(Component); - if (!IsValid(SceneComponent)) - continue; - if (FName(SceneComponent->GetName()) == ComponentName) - return SceneComponent; - } - - return nullptr; -} - -bool UHoudiniAssetBlueprintComponent::GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid) -{ - FGuid* SCSGuid = CachedInputNodes.Find(InputGuid); - if (!SCSGuid) - return false; - OutSCSGuid = *SCSGuid; - return true; -} - -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const -{ - const TArray& AllNodes = SCS->GetAllNodes(); - - for (USCS_Node* Node : AllNodes) - { - if (!Node) - continue; - - if (Node->ComponentTemplate == InComponent) - return Node; - } - - return nullptr; -} - -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponentInClassHierarchy( - const UActorComponent* InComponent) const -{ - UObject* Outer = this->GetOuter(); - if (!IsValid(Outer)) - return nullptr; - UBlueprintGeneratedClass* MainBPGC; - if (IsTemplate()) - { - MainBPGC = Cast(Outer); - } - else - { - AActor* OwningActor = GetOwner(); - MainBPGC = Cast(OwningActor->GetClass()); - } - - check(MainBPGC); - TArray BPGCStack; - UBlueprintGeneratedClass::GetGeneratedClassesHierarchy(MainBPGC, BPGCStack); - for(const UBlueprintGeneratedClass* BPGC : BPGCStack) - { - USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; - if (!SCS) - return nullptr; - USCS_Node* SCSNode = FindSCSNodeForTemplateComponent(SCS, InComponent); - SCSNode = SCS->FindSCSNode(InComponent->GetFName()); - if (SCSNode) - return SCSNode; - } - - return nullptr; -} - -#if WITH_EDITOR -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const -{ - const TArray& AllNodes = SCS->GetAllNodes(); - - if (!InComponent) - return nullptr; - - for (USCS_Node* Node : AllNodes) - { - if (!Node) - continue; - if (Node->EditorComponentInstance.Get() == InComponent) - return Node; - } - - return nullptr; -} -#endif - -UActorComponent* -UHoudiniAssetBlueprintComponent::FindComponentInstanceInActor(const AActor* InActor, - USCS_Node* SCSNode) const -{ - UActorComponent* ComponentTemplate = SCSNode->ComponentTemplate; - - UActorComponent* ComponentInstance = NULL; - if (InActor != NULL) - { - if (SCSNode != NULL) - { - FName VariableName = SCSNode->GetVariableName(); - if (VariableName != NAME_None) - { - UWorld* World = InActor->GetWorld(); - FObjectPropertyBase* Property = FindFProperty(InActor->GetClass(), VariableName); - if (Property != NULL) - { - // Return the component instance that's stored in the property with the given variable name - ComponentInstance = Cast(Property->GetObjectPropertyValue_InContainer(InActor)); - } - else if (World != nullptr && World->WorldType == EWorldType::EditorPreview) - { - // If this is the preview actor, return the cached component instance that's being used for the pmnaview actor prior to recompiling the Blueprint -#if WITH_EDITOR - ComponentInstance = SCSNode->EditorComponentInstance.Get(); -#endif - } - } - } - else if (ComponentTemplate != NULL) - { -#if WITH_EDITOR - TInlineComponentArray Components; - InActor->GetComponents(Components); - ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components); -#endif - } - } - - return ComponentInstance; -} - - -//void UHoudiniAssetBlueprintComponent::OnOutputProcessingCompletedHandler(UHoudiniAssetComponent* InComponent) -//{ -// -// UHoudiniAssetBlueprintComponent* TemplateComponent = Cast(InComponent); -// if (!IsValid(TemplateComponent)) -// return; -// -// CopyStateFromComponent(TemplateComponent); -// bForceNeedUpdate = true; -//} - -//#if WITH_EDITOR -//void UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason) -//{ -// -// if (CachedBlueprint.Get()) -// { -// } -// -// if (Asset) -// { -// -// } -// -//} -//#endif - -void -UHoudiniAssetBlueprintComponent::CachePreviewState() -{ - bCachedIsPreview = false; - -#if WITH_EDITOR - AActor* ComponentOwner = GetOwner(); - if (!IsValid(ComponentOwner)) - return; - - USimpleConstructionScript* SCS = GetSCS(); - if (SCS == nullptr) - return; - - // Get the preview actor directly from the BlueprintEditor. - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - if (BlueprintEditor) - { - AActor* PreviewActor = BlueprintEditor->GetPreviewActor(); - if (PreviewActor == ComponentOwner) - { - bCachedIsPreview = true; - return; - } - } -#endif -} - -void -UHoudiniAssetBlueprintComponent::CacheBlueprintData() -{ - CachedBlueprint = nullptr; - CachedActorCDO = nullptr; - CachedTemplateComponent = IsTemplate() ? this : nullptr; - -#if WITH_EDITOR - CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); -#endif - - UBlueprintGeneratedClass* BPGC = Cast(GetOuter()); - if (BPGC) - { - // Dealing with a component template - CachedBlueprint = Cast(BPGC->ClassGeneratedBy); - } - else - { - // Dealing with a component instance. - CachedBlueprint = Cast(GetOuter()->GetClass()->ClassGeneratedBy); - } - - if (CreationMethod != EComponentCreationMethod::SimpleConstructionScript) - return; - - AActor* ComponentOwner = this->GetOwner(); - if (!IsValid(ComponentOwner)) - return; - UClass* OwnerClass = ComponentOwner->GetClass(); - if (!IsValid(OwnerClass)) - return; - - if (!IsTemplate()) - { - // NOTE: The following code allows us to find the component template from an instance. - CachedActorCDO = Cast< AActor >(CachedBlueprint->GeneratedClass->GetDefaultObject()); - if (!CachedActorCDO.IsValid() || (CachedActorCDO.Get() == ComponentOwner)) - return; -#if WITH_EDITOR - UActorComponent* TargetComponent = EditorUtilities::FindMatchingComponentInstance(this, CachedActorCDO.Get()); - CachedTemplateComponent = Cast(TargetComponent); -#endif - } - -} - -USimpleConstructionScript* -UHoudiniAssetBlueprintComponent::GetSCS() const -{ - if (!CachedBlueprint.Get()) - return nullptr; - - return CachedBlueprint->SimpleConstructionScript; -} - -//------------------------------------------------------------------------------------------------ -// FHoudiniAssetBlueprintInstanceData -//------------------------------------------------------------------------------------------------ - -FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData() - : HoudiniAsset(nullptr) - , AssetId(-1) - , AssetState(EHoudiniAssetState::None) - , SubAssetIndex(-1) - , AssetCookCount(0) - , bHasBeenLoaded(false) - , bHasBeenDuplicated(false) - , bPendingDelete(false) - , bRecookRequested(false) - , bRebuildRequested(false) - , bEnableCooking(true) - , bForceNeedUpdate(false) - , bLastCookSuccess(false) - , ComponentGUID(FGuid()) - , HapiGUID(FGuid()) - , bRegisteredComponentTemplate(false) - , SourceName() -{ - -} - -FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent) - : FActorComponentInstanceData(SourceComponent) -{ - -} - -void -FHoudiniAssetBlueprintInstanceData::AddReferencedObjects(FReferenceCollector & Collector) -{ - Super::AddReferencedObjects(Collector); - // TODO: Do we need to add references to output objects here? - // Any other references? - // What are the implications? -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetBlueprintComponent.h" + +#include "HoudiniEngineCopyPropertiesInterface.h" +#include "HoudiniOutput.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Engine/SCS_Node.h" +#include "Engine/SimpleConstructionScript.h" +#include "UObject/Object.h" +#include "Logging/LogMacros.h" + +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniInput.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Kismet2/BlueprintEditorUtils.h" + #include "Kismet2/KismetEditorUtilities.h" + #include "Toolkits/AssetEditorManager.h" + #include "Kismet2/ComponentEditorUtils.h" + #include "ComponentAssetBroker.h" +#endif + +HOUDINI_BP_DEFINE_LOG_CATEGORY(); + +UHoudiniAssetBlueprintComponent::UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + +#if WITH_EDITOR + if (IsTemplate()) + { + // CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); + //GEditor->GetEditorSubsystem()->OnAssetEditorRequestClose().AddUObject( this, &UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent ); + } +#endif + + bForceNeedUpdate = false; + bHoudiniAssetChanged = false; + bIsInBlueprintEditor = false; + bCanDeleteHoudiniNodes = false; + + // AssetState will be updated by changes to the HoudiniAsset + // or parameter changes on the Component template. + AssetState = EHoudiniAssetState::None; + bHasRegisteredComponentTemplate = false; + bHasBeenLoaded = false; + bUpdatedFromTemplate = false; + + // Disable proxy mesh by default (unsupported for now) + bOverrideGlobalProxyStaticMeshSettings = true; + bEnableProxyStaticMeshOverride = false; + bEnableProxyStaticMeshRefinementByTimerOverride = false; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = false; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = false; + StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; + + // Set default mobility to Movable + Mobility = EComponentMobility::Movable; +} + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() +{ + // We need to propagate changes made here back to the corresponding component in + // the Blueprint Generated Class ("_GEN_VARIABLE"). The reason being that + // the Blueprint editor works directly with the GEN_VARIABLE component (all + // PostEditChange() calls, Details Customizations, etc will receive the GEN_VARIABLE instance) BUT + // when the Editor runs the construction script it uses a different component instance, so all changes + // made to that instance won't write back to the Blueprint definition. + // To Summarize: + // Be sure to sync the Parameters array (and any other relevant properties) back + // to the corresponding component on the Blueprint Generated class otherwise these wont be + // accessible in the Details Customization callbacks. + + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] To Component: %s"), *(CachedTemplateComponent->GetPathName())); + + // This should never be called by component templates. + check(!IsTemplate()); + + if (!CachedTemplateComponent.IsValid()) + return; + + USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; + check(SCS); + + /* + USCS_Node* SCSNodeForInstance = FindSCSNodeForInstanceComponent(SCS, this); + if (SCSNodeForInstance) + { + + } + else + { + + } + */ + + //// If we don't have an SCS node for this preview instance, we need to create one, regardless + //// of whether output updates are required. + //if (!CachedTemplateComponent->bOutputsRequireUpdate && SCSNodeForInstance != nullptr) + // return; + + // TODO: If the blueprint editor is NOT open, then we shouldn't attempting + // to copy state back to the BPGC at all! + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + check(BlueprintEditor); + + TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); + check(SCSEditor); + + USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + // check(SCSHACNode); + + // This is the actor instance that is being used for component editing. + AActor* PreviewActor = GetPreviewActor(); + check(PreviewActor); + + // NOTE: Inputs are only from component templates to instances, not the other way around ... I think. + + // ----------------------------------------------------- + // Copy outputs to component template + // ----------------------------------------------------- + + // Populate / update the outputs for the template from the preview / instance. + // TODO: Wrap the Blueprint manipulation in a transaction + TArray& TemplateOutputs = CachedTemplateComponent->Outputs; + TSet StaleTemplateOutputs(TemplateOutputs); + + TemplateOutputs.SetNum(Outputs.Num()); + CachedOutputNodes.Empty(); + + for (int i = 0; i < Outputs.Num(); i++) + { + // Find a output on the template that corresponds to this output from the instance. + UHoudiniOutput* TemplateOutput = nullptr; + UHoudiniOutput* InstanceOutput = nullptr; + InstanceOutput = Outputs[i]; + + //check(InstanceOutput) + if (!InstanceOutput || InstanceOutput->IsPendingKill()) + continue; + + // Ensure that instance outputs won't delete houdini content. + // Houdini content should only be allowed to be deleted from + // the component template. + InstanceOutput->SetCanDeleteHoudiniNodes(false); + + TemplateOutput = TemplateOutputs[i]; + + if (TemplateOutput) + { + check(TemplateOutput->GetOuter() == CachedTemplateComponent.Get()); + StaleTemplateOutputs.Remove(TemplateOutput); + } + + + if (TemplateOutput) + { + // Copy properties from the current instance component while preserving output objects + // and instanced outputs. + TemplateOutput->CopyPropertiesFrom(InstanceOutput, true); + } + else + { + // NOTE: If the template output is NULL it means that the HDA spawned a new component / output in the transient world + // and the new output object needs to be copied back to the BPGC. + + // Corresponding template output could not be found. Create one by duplicating the instance output. + TemplateOutput = InstanceOutput->DuplicateAndCopyProperties(CachedTemplateComponent.Get(), FName(InstanceOutput->GetName())); + // Treat these the same one would components created by CreateDefaultSubObject. + // NOTE: CreateDefaultSubobject performs lots of checks, and unfortunately we can't use it directly (it is + // only allowed to be used in a constructor). Not sure whether we need to either. For now, we just set the + // object flags to be similar to components created by CreateDefaultSubobject. + TemplateOutput->SetFlags(RF_Public|RF_ArchetypeObject|RF_DefaultSubObject); + TemplateOutputs[i] = TemplateOutput; + } + + check(TemplateOutput); + TemplateOutput->SetCanDeleteHoudiniNodes(false); + + // Keep track of potential stale output objects on the template component, for this output. + TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); + TArray StaleTemplateObjects; + TemplateOutputObjects.GetKeys(StaleTemplateObjects); + + for (auto& Entry : InstanceOutput->GetOutputObjects()) + { + + // Prepare the FHoudiniOutputObject for the template component + const FHoudiniOutputObject& InstanceObj = Entry.Value; + FHoudiniOutputObject TemplateObj; + + // Any output present in the Instance Outputs should be + // transferred to the template. + // Remove this output object from stale outputs list. + StaleTemplateObjects.Remove(Entry.Key); + + if (TemplateOutputObjects.Contains(Entry.Key)) + { + // Reuse the existing template object + TemplateObj = TemplateOutputObjects.FindChecked(Entry.Key); + } + else + { + // Create a new template output object object by duplicating the instance object. + // Keep the output object, but clear the output component since we have to + // create a new component template. + TemplateObj = InstanceObj; + TemplateObj.ProxyComponent = nullptr; + TemplateObj.OutputComponent = nullptr; + TemplateObj.ProxyObject = nullptr; + } + + USceneComponent* ComponentInstance = Cast(InstanceObj.OutputComponent); + USceneComponent* ComponentTemplate = Cast(TemplateObj.OutputComponent); + UObject* OutputObject = InstanceObj.OutputObject; + + if (ComponentInstance) + { + // The translation process has either constructed new components, or it is + // reusing existing components, or changed an output (or all or none of the aforementioned). + // Carefully inspect the SCS graph to determine whether there is a corresponding + // (and compatible) node for this output. If not, create a new node and remove unusable node, if any. + + USCS_Node* ComponentNode = nullptr; + { + // Check whether the current OutputComponent being referenced by the template is still valid. + // Even if it was removed in the editor, it doesn't have any associated destroyed / pendingkill state. + // Instead we're going to check for validity by finding an SCS node with a matching template component. + bool bValidComponentTemplate = (ComponentTemplate != nullptr); + if (ComponentTemplate) + { + + ComponentNode = FindSCSNodeForTemplateComponentInClassHierarchy(ComponentTemplate); + bValidComponentTemplate = bValidComponentTemplate && (ComponentNode != nullptr); + } + + if (!bValidComponentTemplate) + { + // Either this component was removed from the editor or it doesn't exist yet. + // Ensure the references are cleared + TemplateObj.OutputComponent = nullptr; + ComponentTemplate = nullptr; + } + } + + // NOTE: we can't use the component instance name directly due to the Blueprint compiler performing an internal checking + // using FComponentEditorUtils::IsValidVariableNameString(), which will return false if the name looks like an autogenerated name... + //FString ComponentName = ComponentInstance->GetName(); + FString ComponentName = FBlueprintEditorUtils::GetClassNameWithoutSuffix(ComponentInstance->GetClass()); + FName ComponentFName = FName(ComponentName); + + + const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )( + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | + EditorUtilities::ECopyOptions::CallPostEditChangeProperty | + EditorUtilities::ECopyOptions::CallPostEditMove); + + if (IsValid(ComponentNode)) + { + // Check if we have an existing and compatible SCS node containing a USceneComponent as a template component. + bool bComponentNodeIsValid = true; + + ComponentTemplate = Cast(ComponentNode->ComponentTemplate); + + bComponentNodeIsValid = bComponentNodeIsValid && ComponentInstance->GetClass() == ComponentNode->ComponentClass; + bComponentNodeIsValid = bComponentNodeIsValid && ComponentTemplate != nullptr; + // TODO: Do we need to perform any other compatibility checks? + + if (!bComponentNodeIsValid) + { + // Component template is not compatible. We can't reuse it. + + SCSHACNode->RemoveChildNode(ComponentNode); + SCS->RemoveNode(ComponentNode); + ComponentNode = nullptr; + ComponentTemplate = nullptr; + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + } + + if (ComponentNode) + { + // We found a reusable SCS node. Just copy the component instance + // properties over to the existing template. + check(ComponentNode->ComponentTemplate); + + // UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + // //Params.bReplaceObjectClassReferences = false; + // Params.bDoDelta = false; // Perform a deep copy + // Params.bClearReferences = false; + // UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + + FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + + ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; + } + else + { + // We couldn't find a reusable SCS node. + // Duplicate the instance component and create a new corresponding SCS node + ComponentNode = SCS->CreateNode(ComponentInstance->GetClass(), ComponentFName); + + UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // We need a deep copy of parameters here so the CDO values get copied as well + UEditorEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + // FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); + + // { + // UInstancedStaticMeshComponent* Component = Cast(ComponentNode->ComponentTemplate); + // if (Component) + // { + // } + // } + + // NOTE: The EComponentCreationMethod here is currently set to be the same as a component that was + // created manually in the editor. + ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; + + // Add this node to the SCS root set. + + // Attach the new node the HAC SCS node + // NOTE: This will add the node to the SCS->AllNodes list too but it won't update + // the nodename map. We can't forcibly update the Node/Name map either since the + // relevant functions have not been exported. + SCSHACNode->AddChildNode(ComponentNode); + + // Set the output component. + TemplateObj.OutputComponent = ComponentNode->ComponentTemplate; + + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + + // Cache the mapping between the output and the SCS node. + check(ComponentNode); + CachedOutputNodes.Add(Entry.Key, ComponentNode->VariableGuid); + } // if (ComponentInstance) + /* + else if (InstanceObj.OutputObject) + { + + } + */ + + // Add the updated output object to the template output + TemplateOutputObjects.Add(Entry.Key, TemplateObj); + } + + // Cleanup stale objects for this template output. + for (const auto& StaleId : StaleTemplateObjects) + { + FHoudiniOutputObject& OutputObj = TemplateOutputObjects.FindChecked(StaleId); + + // Ensure the component template is no longer referencing this output. + TemplateOutputObjects.Remove(StaleId); + + USceneComponent* TemplateComponent = Cast(OutputObj.OutputComponent); + + if (TemplateComponent) + { + USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(TemplateComponent); + if (StaleNode) + { + + SCS->RemoveNode(StaleNode, false); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + /* + else + { + + } + */ + } + /* + else + { + + } + */ + } + } //for (int i = 0; i < Outputs.Num(); i++) + + // Clean up stale outputs on the component template. + for (UHoudiniOutput* StaleOutput : StaleTemplateOutputs) + { + if (!StaleOutput) + continue; + + // Remove any components contained in this output from the SCS graph + for (auto& Entry : StaleOutput->GetOutputObjects()) + { + FHoudiniOutputObject& StaleObject = Entry.Value; + USceneComponent* OutputComponent = Cast(StaleObject.OutputComponent); + + if (OutputComponent) + { + + USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(OutputComponent); + if (StaleNode) + { + + SCS->RemoveNode(StaleNode, false); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + } + } + + TemplateOutputs.Remove(StaleOutput); + //StaleOutput->ConditionalBeginDestroy(); + } + + SCS->ValidateSceneRootNodes(); + + // Copy parameters from this component to the template component. + // NOTE: We need to do this since the preview component will be cooking the HDA and get populated with + // all the parameters. This data needs to be sent back to the component template. + UClass* ComponentClass = CachedTemplateComponent->GetClass(); + UHoudiniAssetBlueprintComponent* DefaultObj = Cast(ComponentClass->GetDefaultObject()); + bool bBPStructureModified = false; + CachedTemplateComponent->CopyDetailsFromComponent( + this, + true, + true, + true, + false, + true, + bBPStructureModified, + /* SetFlags */ CachedTemplateComponent->GetMaskedFlags(RF_PropagateToSubObjects)); + + if (bBPStructureModified) + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + + // Copy the cached output nodes back to the template so that + // reconstructed actors can correctly update output objects + // with newly constructed components during ApplyComponentInstanceData() calls. + CachedTemplateComponent->CachedOutputNodes = CachedOutputNodes; + + CachedTemplateComponent->MarkPackageDirty(); + PostEditChange(); + + CachedTemplateComponent->AssetId = AssetId; + CachedTemplateComponent->HapiGUID = HapiGUID; + CachedTemplateComponent->AssetCookCount = AssetCookCount; + CachedTemplateComponent->AssetStateResult = AssetStateResult; + CachedTemplateComponent->bLastCookSuccess = bLastCookSuccess; + +#if WITH_EDITOR + // TODO: Do we need to handle this right now or can we wait for the next Houdini Engine manager tick to process it? + if (CachedTemplateComponent->NeedBlueprintStructureUpdate()) + { + // We are about to recompile the blueprint. This will reconstruct the preview actor so we need to ensure + // that the old actor won't release the houdini nodes. + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); + SetCanDeleteHoudiniNodes(false); + } + /*else if (CachedTemplateComponent->NeedBlueprintUpdate()) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(CachedTemplateComponent.Get()); + }*/ +#endif +} +#endif + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties) +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] From Component: %s"), *(FromComponent->GetPathName())); + + // This should never be called by component templates. + check(!IsTemplate()); + + // Make sure all TransientDuplicate properties from the Template Component needed by this transient component + // gets copied. + + ComponentGUID = FromComponent->ComponentGUID; + + /* + { + const TArray Children = GetAttachChildren(); + for (USceneComponent* Child : Children) + { + if (!Child) + continue; + } + } + */ + + // AssetState = FromComponent->PreviewAssetState; + + // This state should not be shared between template / instance components. + //bFullyLoaded = FromComponent->bFullyLoaded; + + bNoProxyMeshNextCookRequested = FromComponent->bNoProxyMeshNextCookRequested; + + // Reconstruct outputs and update them to point to component instances as opposed to templates. + UObject* TemplateOuter = CachedTemplateComponent->GetOuter(); + + USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; + check(SCS); + + // NOTE: We can find the SCS node for the HoudiniAssetComponent from either the template component or the instance (editor preview) component. + USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + check(SCSHACNode); + + // ----------------------------------------------------- + // Copy outputs to component template + // ----------------------------------------------------- + + TArray& TemplateOutputs = CachedTemplateComponent->Outputs; + + TSet StaleInstanceOutputs(Outputs); + + Outputs.SetNum(TemplateOutputs.Num()); + + for (int i = 0; i < TemplateOutputs.Num(); i++) + { + UHoudiniOutput* TemplateOutput = TemplateOutputs[i]; + if (!IsValid(TemplateOutput)) + continue; + + UHoudiniOutput* InstanceOutput = Outputs[i]; + if (!(InstanceOutput->GetOuter() == this)) + InstanceOutput = nullptr; + + if (InstanceOutput) + { + StaleInstanceOutputs.Remove(InstanceOutput); + } + + if (InstanceOutput) + { + // Copy properties from the current instance component while preserving output objects + // and instanced outputs. + InstanceOutput->CopyPropertiesFrom(TemplateOutput, true); + } + else + { + InstanceOutput = TemplateOutput->DuplicateAndCopyProperties(this, FName(TemplateOutput->GetName())); + if (IsValid(InstanceOutput)) + InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); + } + + Outputs[i] = InstanceOutput; + + if (!IsValid(InstanceOutput)) + continue; + + InstanceOutput->SetCanDeleteHoudiniNodes(false); + + TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); + TMap& InstanceOutputObjects = InstanceOutput->GetOutputObjects(); + TArray StaleOutputObjects; + InstanceOutputObjects.GetKeys(StaleOutputObjects); + + for (auto& Entry : TemplateOutputObjects) + { + const FHoudiniOutputObject& TemplateObj = Entry.Value; + FHoudiniOutputObject InstanceObj = TemplateObj; + + if (!InstanceOutputObjects.Contains(Entry.Key)) + continue; + + StaleOutputObjects.Remove(Entry.Key); + InstanceObj = InstanceOutputObjects.FindChecked(Entry.Key); + + } // for (auto& Entry : TemplateOutputObjects) + + // Cleanup stale output objects for this output. + for (const auto& StaleId : StaleOutputObjects) + { + //TemplateOutput + //check(TemplateOutputs); + + FHoudiniOutputObject& OutputObj = InstanceOutputObjects.FindChecked(StaleId); + + InstanceOutputObjects.Remove(StaleId); + if (OutputObj.OutputComponent) + { + //OutputObj.OutputComponent->ConditionalBeginDestroy(); + OutputObj.OutputComponent = nullptr; + } + } + } // for (int i = 0; i < TemplateOutputs.Num(); i++) + + // Cleanup any stale outputs found on the component instance. + for (UHoudiniOutput* StaleOutput : StaleInstanceOutputs) + { + if (!StaleOutput) + continue; + + if (!(StaleOutput->GetOuter() == this)) + continue; + + // We don't want to clear stale outputs on components instances. Only on template components. + StaleOutput->SetCanDeleteHoudiniNodes(false); + } + + // Copy parameters from the component template to the instance. + bool bBlueprintStructureChanged = false; + CopyDetailsFromComponent(FromComponent, + false, + bClearFromInputs, + bClearToInputs, + false, + true, + bBlueprintStructureChanged, + /*SetFlags*/ RF_Public, + /*ClearFlags*/ RF_DefaultSubObject|RF_ArchetypeObject); +} +#endif + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent( + UHoudiniAssetBlueprintComponent* FromComponent, + const bool bCreateSCSNodes, + const bool bClearChangedToInputs, + const bool bClearChangedFromInputs, + const bool bInCanDeleteHoudiniNodes, + const bool bCopyInputObjectComponentProperties, + bool &bOutBlueprintStructureChanged, + EObjectFlags SetFlags, + EObjectFlags ClearFlags) +{ + check(FromComponent); + + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] FromComponent: %s"), *(FromComponent->GetPathName())); + + /* + if (!FromComponent->HoudiniAsset) + { + return; + } + */ + + // TODO: Try to reuse objects here when we're able. + //// Copy UHoudiniOutput state from instance to template + //UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + ////Params.bReplaceObjectClassReferences = false; + ////Params.bClearReferences = false; + //Params.bDoDelta = true; + //UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + + // Record input remapping that will need to take place when duplicating parameters. + TMap InputMapping; + + // ----------------------------------------------------- + // Copy inputs + // ----------------------------------------------------- + + // TODO: Add support for input components + { + TArray& FromInputs = FromComponent->Inputs; + TSet StaleInputs(Inputs); + USimpleConstructionScript* SCS = GetSCS(); + USCS_Node* SCSHACNode = nullptr; + + if (bCreateSCSNodes) + { + SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + } + + Inputs.SetNum(FromInputs.Num()); + for (int i = 0; i < FromInputs.Num(); i++) + { + UHoudiniInput* FromInput = nullptr; + UHoudiniInput* ToInput = nullptr; + FromInput = FromInputs[i]; + + check(FromInput); + + ToInput = Inputs[i]; + + if (ToInput) + { + // Check whether the instance and template input objects are compatible. + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + + if (!bIsValid) + { + ToInput = nullptr; + } + } + + // TODO: Process stale input objects + + // NOTE: The CopyStateFrom() / DuplicateAndCopyState() will copy/duplicate/cleanup internal inputs to + // ensure that there aren't any shared instances between the ToInput/FromInput. + if (ToInput) + { + // We have a compatible input that we can reuse. + StaleInputs.Remove(ToInput); + ToInput->CopyStateFrom(FromInput, true, bInCanDeleteHoudiniNodes); + } + else + { + + // We don't have an existing / compatible input. Create a new one. + ToInput = FromInput->DuplicateAndCopyState(this, bInCanDeleteHoudiniNodes); + if (SetFlags != RF_NoFlags) + ToInput->SetFlags(SetFlags); + if (ClearFlags != RF_NoFlags) + ToInput->ClearFlags( ClearFlags ); + } + + check(ToInput); + + + UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, bCopyInputObjectComponentProperties, bCreateSCSNodes, SCSHACNode, &bOutBlueprintStructureChanged); + + Inputs[i] = ToInput; + InputMapping.Add(FromInput, ToInput); + + if (bClearChangedToInputs) + { + // Clear the changed flags on the FromInput so that it doesn't trigger + // another update. The ToInput will now be carrying to changed/update flags. + ToInput->MarkChanged(false); + ToInput->MarkAllInputObjectsChanged(false); + } + + if (bClearChangedFromInputs) + { + // Clear the changed flags on the FromInput so that it doesn't trigger + // another update. The ToInput will now be carrying to changed/update flags. + FromInput->MarkChanged(false); + FromInput->MarkAllInputObjectsChanged(false); + } + } + + // Cleanup any stale inputs from this component. + // NOTE: We would typically only have stale inputs when copying state from + // the component instance to the component template. Garbage collection + // eventually picks up the input objects and removes the content + // but until such time we are stuck with those nodes as inputs in the Houdini session + // so we get rid of those nodes immediately here to avoid some user confusion. + for (UHoudiniInput* StaleInput : StaleInputs) + { + if (!IsValid(StaleInput)) + continue; + + check(StaleInput->GetOuter() == this); + + if (StaleInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + StaleInput->ConditionalBeginDestroy(); + } + } + + + // ----------------------------------------------------- + // Copy parameters (and optionally remap inputs). + // ----------------------------------------------------- + TMap ParameterMapping; + + TArray& FromParameters = FromComponent->Parameters; + Parameters.SetNum(FromParameters.Num()); + + for (int i = 0; i < FromParameters.Num(); i++) + { + UHoudiniParameter* FromParameter = nullptr; + UHoudiniParameter* ToParameter = nullptr; + + FromParameter = FromParameters[i]; + + check(FromParameter); + + if (Parameters.IsValidIndex(i)) + { + ToParameter = Parameters[i]; + } + + if (ToParameter) + { + bool bIsValid = true; + // Check whether To/From parameters are compatible + bIsValid = bIsValid && ToParameter->Matches(*FromParameter); + bIsValid = bIsValid && ToParameter->GetOuter() == this; + + if (!bIsValid) + ToParameter = nullptr; + } + + if (ToParameter) + { + // Parameter already exists. Simply sync the state. + ToParameter->CopyStateFrom(FromParameter, true, ClearFlags, SetFlags); + } + else + { + // TODO: Check whether parameters are the same to avoid recreating them. + ToParameter = FromParameter->DuplicateAndCopyState(this, ClearFlags, SetFlags); + Parameters[i] = ToParameter; + } + + check(ToParameter); + ParameterMapping.Add(FromParameter, ToParameter); + + if (bClearChangedFromInputs) + { + // We clear the Changed flag on the FromParameter (most likely on the component template) + // since the template parameter state has now been transfered to the preview component and + // will resume processing from there. + FromParameter->MarkChanged(false); + } + } + + // Apply remappings on the new parameters + for (UHoudiniParameter* ToParameter : Parameters) + { + ToParameter->RemapParameters(ParameterMapping); + ToParameter->RemapInputs(InputMapping); + } + + FProperty* ParametersProperty = GetClass()->FindPropertyByName(TEXT("Parameters")); + FPropertyChangedEvent Evt(ParametersProperty); + PostEditChangeProperty(Evt); + + bEnableCooking = FromComponent->bEnableCooking; + bRecookRequested = FromComponent->bRecookRequested; + bRebuildRequested = FromComponent->bRebuildRequested; +} + +void +UHoudiniAssetBlueprintComponent::UpdateInputObjectComponentReferences( + USimpleConstructionScript* SCS, + UHoudiniInput* FromInput, + UHoudiniInput* ToInput, + const bool bCopyInputObjectProperties, + const bool bCreateMissingSCSNodes, + USCS_Node* SCSHACParent, + bool* bOutSCSNodeCreated) +{ + TArray ToInputObjects; + TArray FromInputObjects; + TArray StaleInputObjects; + + ToInput->GetAllHoudiniInputSplineComponents(ToInputObjects); + FromInput->GetAllHoudiniInputSplineComponents(FromInputObjects); + + StaleInputObjects = ToInputObjects; + + const int32 NumInputObjects = FromInputObjects.Num(); + ToInputObjects.SetNum(NumInputObjects); + + const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + //Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; + + for(int32 InputObjectIndex = 0; InputObjectIndex < NumInputObjects; ++InputObjectIndex) + { + UHoudiniInputHoudiniSplineComponent* FromInputObject = FromInputObjects[InputObjectIndex]; + UHoudiniInputHoudiniSplineComponent* ToInputObject = ToInputObjects[InputObjectIndex]; + if (!FromInputObject) + continue; + if (!ToInputObject) + continue; + + USCS_Node* SCSNode = nullptr; + if (CachedInputNodes.Contains(ToInputObject->Guid)) + { + // Reuse / update the existing SCS node. + SCSNode = SCS->FindSCSNodeByGuid( CachedInputNodes.FindChecked(ToInputObject->Guid) ); + } + + if (!SCSNode) + { + if (!bCreateMissingSCSNodes) + continue; // This input object should be removed. + } + + USceneComponent* ToComponent = nullptr; + USceneComponent* FromComponent = Cast(FromInputObject->GetObject()); + + StaleInputObjects.Remove(ToInputObject); + + if (FromComponent) + { + if (!SCSNode) + { + if (bCreateMissingSCSNodes) + { + // Create a new SCS node + SCSNode = SCS->CreateNode(FromComponent->GetClass()); + SCSHACParent->AddChildNode(SCSNode); + if (bOutSCSNodeCreated) + { + *bOutSCSNodeCreated = true; + } + AddInputObjectMapping(ToInputObject->Guid, SCSNode->VariableGuid); + } + } + + if (SCSNode) + { + if (bCreateMissingSCSNodes) + { + // If we have been instructed to create missing SCS nodes, assume we are copying + // the the component template. + ToComponent = Cast(SCSNode->ComponentTemplate); + } + else + { + // We are not copying to the component template, so we're assuming this is a + // component instance. Find the component on the owning actor that matches the SCS node. + AActor* ToOwningActor = ToInput->GetTypedOuter(); + check(ToOwningActor); + + ToComponent = Cast(FindComponentInstanceInActor(ToOwningActor, SCSNode)); + } + + if (bCopyInputObjectProperties && ToComponent) + { + USceneComponent* ToAttachParent = ToComponent->GetAttachParent(); + // Copy specific properties from the component template to the instance, if supported by the component. + // We typically resort to this in order to transfer Transient and TransientDuplicate properties from the + // component template over to the instance (typically HasChanged / NeedsToTriggerUpdate flags) in order for + // the instance to cook properly. + IHoudiniEngineCopyPropertiesInterface* ToCopyableComponent = Cast(ToComponent); + if (ToCopyableComponent) + { + // Let the component manage its own data copying. + ToCopyableComponent->CopyPropertiesFrom(FromComponent); + } + else + { + // The component doesn't implement the property copy interface. Simply do a general property copy. + //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, ToComponent, Params); + FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, ToComponent, ComponentCopyOptions); + } + ToComponent->PostEditChange(); + } + } + } + + ToInputObject->Update(ToComponent); + ToInputObjects[InputObjectIndex] = ToInputObject; + } + + for (UHoudiniInputObject* StaleInputObject : StaleInputObjects) + { + if (!StaleInputObject) + continue; + StaleInputObject->InvalidateData(); + ToInput->RemoveHoudiniInputObject(StaleInputObject); + ToInput->MarkChanged(true); + // TODO: Find the corresponding SCS node and remove it + } +} + +#endif + +#if WITH_EDITOR +bool +UHoudiniAssetBlueprintComponent::HasOpenEditor() const +{ + if (IsTemplate()) + { + IAssetEditorInstance* EditorInstance = FindEditorInstance(); + + return EditorInstance != nullptr; + } + + return false; +} +#endif + +#if WITH_EDITOR +IAssetEditorInstance* +UHoudiniAssetBlueprintComponent::FindEditorInstance() const +{ + UClass* BPGC = Cast(GetOuter()); + if (!IsValid(BPGC)) + return nullptr; + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!IsValid(Blueprint)) + return nullptr; + if (!CachedAssetEditorSubsystem.IsValid()) + return nullptr; + + IAssetEditorInstance* EditorInstance = CachedAssetEditorSubsystem->FindEditorForAsset(Blueprint, false); + + return EditorInstance; +} +#endif + +#if WITH_EDITOR +AActor* +UHoudiniAssetBlueprintComponent::GetPreviewActor() const +{ + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + if (BlueprintEditor) + { + return BlueprintEditor->GetPreviewActor(); + } + return nullptr; +} +#endif + +UHoudiniAssetComponent* +UHoudiniAssetBlueprintComponent::GetCachedTemplate() const +{ + return CachedTemplateComponent.Get(); +} + +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateParameters() const +//{ +// return IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateInputs() const +//{ +// return !IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateOutputs() const +//{ +// return !IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanProcessOutputs() const +//{ +// return !IsTemplate(); +//} + +//bool +//UHoudiniAssetBlueprintComponent::CanInstantiateAsset() const +//{ +// // If this is a preview component, it should not trigger an asset instantiation. It should wait +// // for the BPGC template component to finish the cook, get the synced data and then translate. +// +// if (IsPreview()) +// return false; +// +// return true; +//} +// +//// Check whether the HAC can translate Houdini outputs at all +//bool +//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini() const +//{ +// // Template components can't translate Houdini output since they typically do not exist in a world. +// if (IsTemplate()) +// return false; +// // Preview components and normally instanced actors can translate Houdini outputs. +// return true; +//} +// +//// Check whether the HAC can translate a specific output type. +//bool +//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini(EHoudiniOutputType OutputType) const +//{ +// // Blueprint components have limited translation support, for now. +// if (OutputType == EHoudiniOutputType::Mesh) +// return true; +// +// return false; +//} +// +bool +UHoudiniAssetBlueprintComponent::CanDeleteHoudiniNodes() const +{ + return bCanDeleteHoudiniNodes; +} + +void +UHoudiniAssetBlueprintComponent::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; + + for (UHoudiniInput* Input : Inputs) + { + Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for (UHoudiniOutput* Output : Outputs) + { + Output->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +bool +UHoudiniAssetBlueprintComponent::IsValidComponent() const +{ + if (!Super::IsValidComponent()) + return false; + + if (IsTemplate()) + { + UObject* Outer = this->GetOuter(); + if (!IsValid(Outer)) + return false; + UBlueprintGeneratedClass* BPGC = Cast(Outer); + if (!BPGC) + return false; + // Ensure this component is still in the SCS + USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; + if (!SCS) + return false; + USCS_Node* SCSNode = FindSCSNodeForTemplateComponentInClassHierarchy(this); + if (!SCSNode) + return false; + /*UClass* OwnerClass = Outer->GetClass(); + if (!IsValid(OwnerClass)) + return false;*/ + /*UBlueprint* Blueprint = Cast(Outhe); + if (Blueprint) + { + + }*/ + } + +#if WITH_EDITOR + if (!IsTemplate()) + { + if (!GetOwner()) + { + // If it's not a template, it needs an owner! + return false; + } + + USimpleConstructionScript* SCS = GetSCS(); + if (SCS) + { + // We're dealing with a Blueprint related component. + AActor* PreviewActor = GetPreviewActor(); + AActor* OwningActor = GetOwner(); + if (!OwningActor) + return false; + if (OwningActor != PreviewActor) + { + return false; + } + } + + } + + if (IsPreview() && false) + { + USimpleConstructionScript* SCS = GetSCS(); + if (!SCS) + return false; // Preview components should have an SCS. + + // We want to specifically detect whether an editor component is still being previewed. We do this + // by checking whether the owning actor is still the active editor actor in the SCS. + AActor* PreviewActor = GetPreviewActor(); + if (!PreviewActor) + { + return false; + } + + // Ensure this component still belongs the to the current preview actor. + if (PreviewActor != GetOwner()) + { + return false; + } + + /* + AActor* EditorActor = SCS->GetComponentEditorActorInstance(); + if (GetOwner() != EditorActor) + { + return false; + } + */ + } +#endif + + return true; +} + +bool +UHoudiniAssetBlueprintComponent::IsInputTypeSupported(EHoudiniInputType InType) const +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + case EHoudiniInputType::Curve: + return true; + break; + default: + break; + } + return false; +} + +bool +UHoudiniAssetBlueprintComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const +{ + switch (InType) + { + case EHoudiniOutputType::Mesh: + case EHoudiniOutputType::Instancer: + return true; + break; + default: + break; + } + return false; +} + +bool +UHoudiniAssetBlueprintComponent::IsProxyStaticMeshEnabled() const +{ + // TODO: Investigate adding support for proxy meshes in BP + // Disabled for now + return false; +} + +//void +//UHoudiniAssetBlueprintComponent::BroadcastPreAssetCook() +//{ +// // ------------------------------------------------ +// // NOTE: This code will run on TEMPLATE components +// // ------------------------------------------------ +// +// // The HoudiniAsset is about to be recooked. This flag will indicate to +// // the transient components that output processing needs to be baked +// // back to the BP definition. +// bOutputsRequireUpdate = true; +// +// Super::BroadcastPreAssetCook(); +//} + +void +UHoudiniAssetBlueprintComponent::OnPrePreCook() +{ + check(IsPreview()); + + Super::OnPrePreCook(); + + // We need to allow deleting houdini nodes + SetCanDeleteHoudiniNodes(true); +} + +void +UHoudiniAssetBlueprintComponent::OnPostPreCook() +{ + check(IsPreview()); + + Super::OnPostPreCook(); + + // Ensure the houdini nodes can be deleted during the translation process. + SetCanDeleteHoudiniNodes(false); +} + +void +UHoudiniAssetBlueprintComponent::OnPreOutputProcessing() +{ + check(IsPreview()); + + Super::OnPreOutputProcessing(); + + // Ensure the houdini nodes can be deleted during the translation process. + SetCanDeleteHoudiniNodes(true); +} + +void +UHoudiniAssetBlueprintComponent::OnPostOutputProcessing() +{ + Super::OnPostOutputProcessing(); + + // ------------------------------------------------ + // NOTE: + // In Blueprint editor mode, this code will run on PREVIEW components + // In Map editor mode, this code will run on component instances. + // ------------------------------------------------ + if (IsPreview()) + { + // Ensure all the inputs / outputs belonging to the + // preview actor won't be deleted by PreviewActor destruction. + SetCanDeleteHoudiniNodes(false); + +#if WITH_EDITOR + CopyStateToTemplateComponent(); +#endif + + } + bUpdatedFromTemplate = false; +} + +void UHoudiniAssetBlueprintComponent::OnPrePreInstantiation() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnPrePreInstantiation] Component: %s"), *(GetPathName())); + + check(IsPreview()); + + if (bUpdatedFromTemplate) + return; + + check(CachedTemplateComponent.IsValid()); + + // This HDA is about to be cooked but not through template parameter changes. It is likely that an input changed directly in the preview world. + // We need to flag our inputs and parameters appropriately in order to preserve their values. + + // We need to mark all our parameters as changed/not triggering update + for (auto CurrentParam : Parameters) + { + if (CurrentParam) + { + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(false); + } + } + + // We need to mark all our inputs as changed/not triggering update + for (auto CurrentInput : Inputs) + { + if (CurrentInput) + { + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(false); + CurrentInput->MarkDataUploadNeeded(true); + } + } +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniRegisterCompleted() +{ + if (IsTemplate()) + { + // TODO: Do we need to set any status flags or clear stuff to ensure + // the BP HAC will cook properly when the BP is opened again... + + // If the template is being registered, we need to invalidate the AssetId here since it likely + // contains a stale asset id from its last cook. + AssetId = -1; + // Template component's have very limited update requirements / capabilities. + // Mostly just cache parameters and cook state. + SetAssetState(EHoudiniAssetState::ProcessTemplate); + } + + Super::NotifyHoudiniRegisterCompleted(); +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniPreUnregister() +{ + if (IsTemplate()) + { + // Templates can delete Houdini nodes when they get deregistered. + SetCanDeleteHoudiniNodes(true); + } + Super::NotifyHoudiniPreUnregister(); +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniPostUnregister() +{ + InvalidateData(); + + Super::NotifyHoudiniPostUnregister(); + + if (IsTemplate()) + { + SetCanDeleteHoudiniNodes(false); + } +} + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::OnComponentCreated() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnComponentCreated] Component: %s"), *(GetPathName())); + + Super::OnComponentCreated(); + bUpdatedFromTemplate = false; + + CachePreviewState(); + + if (IsPreview()) + { + // Don't set an initial AssetState here. Preview components should only cook when template's + // Houdini Asset or HDA parameters have changed. + + // Clear these to ensure that we're not sharing references with the component template (otherwise + // the shared objects will get deleted when the component instance gets destroyed). + // These objects will be properly duplicated when copying state from the component template. + Inputs.Empty(); + Parameters.Empty(); + } + + // Wait until InitializeComponent() for blueprint construction to complete before we start caching blueprint data. + +} +#endif + + +void +UHoudiniAssetBlueprintComponent::OnRegister() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnRegister] Component: %s"), *(GetPathName())); + + Super::OnRegister(); + + // We run our Blueprint caching functions here since this the last hook that we have before + // entering HoudiniEngineTick(); + + CacheBlueprintData(); + CachePreviewState(); + + if (IsPreview()) + { + check(CachedTemplateComponent.Get()); + // Ensure that the component template has been registered since it needs to be processed for parameter updates by the HE manager. + if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(CachedTemplateComponent.Get())) + { + // The template component has not been registered yet, which means that we're probably busy opening a Blueprint editor and this + // preview component will need to be updated. + + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(CachedTemplateComponent.Get(), true); + CachedTemplateComponent->SetCanDeleteHoudiniNodes(false); + // Since we're likely opening a fresh blueprint editor, we'll need to instantiate the HDA. + bHasRegisteredComponentTemplate = true; + } + } + + if (IsTemplate()) + { + // We're initializing the asset id for HAC template here since it doesn't get unloaded + // from memory, for example, between Blueprint Editor open/close so we need to make sure + // that the AssetId has indeed been reset between registrations. + AssetId = -1; + } + + //TickInitialization(); +} + +void +UHoudiniAssetBlueprintComponent::BeginDestroy() +{ + Super::BeginDestroy(); +} + +void +UHoudiniAssetBlueprintComponent::DestroyComponent(bool bPromoteChildren) +{ + //FDebug::DumpStackTraceToLog(); + if (CachedTemplateComponent.IsValid() && TemplatePropertiesChangedHandle.IsValid()) + { + CachedTemplateComponent->Modify(); + CachedTemplateComponent->OnParametersChangedEvent.Remove(TemplatePropertiesChangedHandle); + } + Super::DestroyComponent(bPromoteChildren); +} + +void +UHoudiniAssetBlueprintComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); +} + +TStructOnScope +UHoudiniAssetBlueprintComponent::GetComponentInstanceData() const +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::GetComponentInstanceData] Component: %s"), *(GetPathName())); + + TStructOnScope ComponentInstanceData = MakeStructOnScope(this); + FHoudiniAssetBlueprintInstanceData* InstanceData = ComponentInstanceData.Cast(); + + InstanceData->AssetId = AssetId; + InstanceData->AssetState = AssetState; + InstanceData->SubAssetIndex = SubAssetIndex; + InstanceData->ComponentGUID = ComponentGUID; + InstanceData->HapiGUID = HapiGUID; + InstanceData->HoudiniAsset = HoudiniAsset; + InstanceData->SourceName = GetPathName(); + InstanceData->AssetCookCount = AssetCookCount; + InstanceData->bHasBeenLoaded = bHasBeenLoaded; + InstanceData->bHasBeenDuplicated = bHasBeenDuplicated; + InstanceData->bPendingDelete = bPendingDelete; + InstanceData->bRecookRequested = bRecookRequested; + InstanceData->bEnableCooking = bEnableCooking; + InstanceData->bForceNeedUpdate = bForceNeedUpdate; + InstanceData->bLastCookSuccess = bLastCookSuccess; + InstanceData->bRegisteredComponentTemplate = bHasRegisteredComponentTemplate; + + InstanceData->Inputs.Empty(); + + for (UHoudiniInput* Input : Inputs) + { + if (!Input) + continue; + UHoudiniInput* TransientInput = Input->DuplicateAndCopyState(GetTransientPackage(), false); + InstanceData->Inputs.Add(TransientInput); + } + + // Cache the current outputs + InstanceData->Outputs.Empty(); + int OutputIndex = 0; + for(UHoudiniOutput* Output : Outputs) + { + if (!Output) + continue; + + TMap OutputObjects = Output->GetOutputObjects(); + for (auto& Entry : OutputObjects) + { + FHoudiniAssetBlueprintOutput OutputObjectData; + OutputObjectData.OutputIndex = OutputIndex; + OutputObjectData.OutputObject = Entry.Value; + InstanceData->Outputs.Add(Entry.Key, OutputObjectData); + } + + ++OutputIndex; + } + + return ComponentInstanceData; + +} + +void +UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData(FHoudiniAssetBlueprintInstanceData* InstanceData, const bool bPostUCS) +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData] Component: %s"), *(GetPathName())); + check(InstanceData); + + if (!bPostUCS) + { + // Initialize the component before the User Construction Script runs + USimpleConstructionScript* SCS = GetSCS(); + check(SCS); + + TArray StaleInputs(Inputs); + + // We need to update references contain in inputs / outputs to point to new reconstructed components. + const int32 NumInputs = InstanceData->Inputs.Num(); + Inputs.SetNum(NumInputs); + for (int i = 0; i < NumInputs; ++i) + { + UHoudiniInput* FromInput = InstanceData->Inputs[i]; + UHoudiniInput* ToInput = Inputs[i]; + + if (ToInput) + { + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + if (!bIsValid) + { + ToInput = nullptr; + } + } + + if (ToInput) + { + // Reuse input + StaleInputs.Remove(ToInput); + ToInput->CopyStateFrom(FromInput, true, false); + } + else + { + // Create new input + ToInput = FromInput->DuplicateAndCopyState(this, false); + } + +#if WITH_EDITOR + // We can't create missing SCS nodes here since we're likely already in the middle of a + // Blueprint reconstruction. We'll have to recreate missing SCS nodes next time the + // component state if copied to the template. + UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, true, false); +#endif + + Inputs[i] = ToInput; + } + + // We need to update FHoudiniOutputObject SceneComponent references to + // the newly created components. Since we cached a map of Output Object IDs to + // SCSNodes (during CopyStateToTemplateComponent), we can the SCSNode that corresponds to this output objects and find + // the SceneComponent that matches the SCSNode's variable name. + // It is important to note that it is safe to do it this way since we're in the pre-UCS + // phase so that current components should match the SCS graph exactly (no user construction script + // interference here yet). + + for (auto& Entry : InstanceData->Outputs) + { + FHoudiniOutputObjectIdentifier& ObjectId = Entry.Key; + FHoudiniAssetBlueprintOutput& OutputData = Entry.Value; + + // NOTE: Output objects are going to be empty here since they dissapear during actor reconstruction. + // We'll need to repopulate from the instance data. + + check(Outputs.IsValidIndex(OutputData.OutputIndex)); + UHoudiniOutput* Output = Outputs[OutputData.OutputIndex]; + check(Output); + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject NewObject = OutputData.OutputObject; + + if (OutputData.OutputObject.OutputComponent) + { + // Update the output component reference. + check(CachedOutputNodes.Contains(ObjectId)) + const FGuid VariableGuid = CachedOutputNodes.FindChecked(ObjectId); + USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(VariableGuid); + + if (SCSNode) + { + // Find the component that corresponds to the SCS node. + USceneComponent* SceneComponent = FindActorComponentByName(GetOwner(), SCSNode->GetVariableName()); + NewObject.OutputComponent = SceneComponent; + } + else + { + NewObject.OutputComponent = nullptr; + } + } + + OutputObjects.Add(ObjectId, NewObject); + } + + if (CachedTemplateComponent.IsValid()) + { +#if WITH_EDITOR + CopyStateFromTemplateComponent( CachedTemplateComponent.Get(), false, false, true); +#endif + } + + AssetId = InstanceData->AssetId; + SubAssetIndex = InstanceData->SubAssetIndex; + ComponentGUID = InstanceData->ComponentGUID; + HapiGUID = InstanceData->HapiGUID; + + // Apply the previous HoudiniAsset to the component + // so that we can compare it against the template during CopyStateFromTemplate() calls to see whether it changed. + HoudiniAsset = InstanceData->HoudiniAsset; + + AssetCookCount = InstanceData->AssetCookCount; + bHasBeenLoaded = InstanceData->bHasBeenLoaded; + bHasBeenDuplicated = InstanceData->bHasBeenDuplicated; + bPendingDelete = InstanceData->bPendingDelete; + bRecookRequested = InstanceData->bRecookRequested; + bEnableCooking = InstanceData->bEnableCooking; + bForceNeedUpdate = InstanceData->bForceNeedUpdate; + bLastCookSuccess = InstanceData->bLastCookSuccess; + bHasRegisteredComponentTemplate = InstanceData->bRegisteredComponentTemplate; + + AssetState = InstanceData->AssetState; + + SetCanDeleteHoudiniNodes(false); + + } // if (!bPostUCS) + /* + else + { + // PostUCS + + } + */ +} + + +void +UHoudiniAssetBlueprintComponent::HoudiniEngineTick() +{ + if (!IsFullyLoaded()) + { + USimpleConstructionScript* SCS = GetSCS(); + if (SCS == nullptr) + { + OnFullyLoaded(); + } + else if (IsPreview()) + { + AActor* OwningActor = GetOwner(); + check(OwningActor); + + // If this is a *preview component*, it is important to wait for the template component to be fully loaded + // since it needs to be initialized so that the component instance can copy initial values from the template. + check(CachedTemplateComponent.Get()); + + if (CachedTemplateComponent->IsFullyLoaded()) + { +#if WITH_EDITOR + if(SCS->IsConstructingEditorComponents()) + { + // We're stuck in an editor blueprint construction / preview actor update. Wait some more. + } + else + { + OnFullyLoaded(); + } +#else + OnFullyLoaded(); +#endif + } + } + else + { + // Anything else can go onto being fully loaded at this point. + OnFullyLoaded(); + } + } +} + +void +UHoudiniAssetBlueprintComponent::OnFullyLoaded() +{ + Super::OnFullyLoaded(); + + // Check whether this component is inside a Blueprint editor. If this object lives outside the blueprint editor (in , then we need to ensure that this + // component won't be influencing the Blueprint asset. + + if (!IsTemplate()) + { +#if WITH_EDITOR + AActor* PreviewActor = GetPreviewActor(); +#else + AActor* PreviewActor = nullptr; +#endif + AActor* OwningActor = GetOwner(); + + if (!PreviewActor) + { + bIsInBlueprintEditor = false; + AssetState = EHoudiniAssetState::None; + return; + } + + if (OwningActor && PreviewActor != OwningActor) + { + bIsInBlueprintEditor = false; + AssetState = EHoudiniAssetState::None; + return; + } + } + + bIsInBlueprintEditor = true; + + CachePreviewState(); + CacheBlueprintData(); + + /* + for (UHoudiniOutput* Output : Outputs) + { + if (!Output) + continue; + } + */ + + if (IsTemplate()) + { + AssetId = -1; + AssetState = EHoudiniAssetState::ProcessTemplate; + } + + if (IsPreview()) + { + check(CachedTemplateComponent.Get()); + + // If this is a preview actor, sync initial settings from the component template +#if WITH_EDITOR + CopyStateFromTemplateComponent(CachedTemplateComponent.Get(), false, false, true); +#endif + + TemplatePropertiesChangedHandle = CachedTemplateComponent->OnParametersChangedEvent.AddUObject(this, &UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler); + if (bHoudiniAssetChanged) + { + + // The HoudiniAsset has changed, so we need to force the PreviewInstance to re-instantiate + AssetState = EHoudiniAssetState::NeedInstantiation; + bForceNeedUpdate = true; + bHoudiniAssetChanged = false; + // TODO: Make this better? + CachedTemplateComponent->bHoudiniAssetChanged = false; + } + + if (bHasRegisteredComponentTemplate) + { + // We have a newly registered component template. One of two things happened to cause this: + // 1. A new HoudiniAssetBlueprintComponent was created and registered. + // 2. The Blueprint Editor was closed / opened. + // The problem that arises in the #2 is that the template component was never fully unloaded + // from memory (it was deregistered but not destroyed). After deregistration we had the + // opportunity to invalidate asset/node ids but now that it has reregistered (without going + // through the normal initialization process) we will have to force a call to MarkAsNeedInstantiation + // during the next OnTemplateParametersChangedHandler() invocation. + bHasBeenLoaded = true; + } + } +} + +void +UHoudiniAssetBlueprintComponent::OnTemplateParametersChanged() +{ + OnParametersChangedEvent.Broadcast(this); +} + +void UHoudiniAssetBlueprintComponent::OnBlueprintStructureModified() +{ + check(IsTemplate()); + bBlueprintStructureModified = false; + +#if WITH_EDITOR + if (IsTemplate()) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(this); + } + else + { + check(CachedTemplateComponent.IsValid()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); + } +#endif +} + +void UHoudiniAssetBlueprintComponent::OnBlueprintModified() +{ + check(IsTemplate()); + bBlueprintModified = false; +#if WITH_EDITOR + FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(this); +#endif +} + +void +UHoudiniAssetBlueprintComponent::OnHoudiniAssetChanged() +{ + if (IsTemplate()) + { + // Invalidate data associated with this component since we're about to change and reinstantiate the Houdini Asset. + SetCanDeleteHoudiniNodes(true); + InvalidateData(); + SetCanDeleteHoudiniNodes(false); + Parameters.Empty(); + Inputs.Empty(); + } + + Super::OnHoudiniAssetChanged(); + + // Set on template components, then copied to preview components, and + // then used (and reset) during OnFullyLoaded. + bHoudiniAssetChanged = true; +} + +void +UHoudiniAssetBlueprintComponent::RegisterHoudiniComponent(UHoudiniAssetComponent *InComponent) +{ + // We only want to register this component if it is the preview actor for the Blueprint editor. +#if WITH_EDITOR + AActor* PreviewActor = GetPreviewActor(); +#else + AActor* PreviewActor = nullptr; +#endif + AActor* OwningActor = GetOwner(); + if (!OwningActor) + return; + + if (PreviewActor != OwningActor) + return; + + Super::RegisterHoudiniComponent(InComponent); +} + + + + +//bool UHoudiniAssetBlueprintComponent::TickInitialization() +//{ +// return true; +// +// if (IsFullyLoaded()) +// return true; +// +// bool bHasFinishedLoading = Super::TickInitialization(); +// +// if (!bHasFinishedLoading) +// return false; +// +// if (!IsTemplate()) +// { +// +// if (CachedTemplateComponent.Get()) +// { +// // Now that that SCS has finished constructing editor components, we can continue. +// // Copy the current state from the template component, in case there is something that can be processed. +// CopyStateFromTemplateComponent(CachedTemplateComponent.Get()); +// } +// +// AssetStateResult = EHoudiniAssetStateResult::None; +// AssetState = EHoudiniAssetState::None; +// +// bForceNeedUpdate = true; +// AssetState = EHoudiniAssetState::PostCook; +// } +// +// return true; +//} + +template +inline void +UHoudiniAssetBlueprintComponent::SetTypedValueAt(const FString& Name, ValueT& Value, int Index) +{ + ParamT* Parameter = Cast(FindParameterByName(Name)); + if (!Parameter) + return; + + Parameter->SetValueAt(Value, Index); +} + +bool +UHoudiniAssetBlueprintComponent::HasParameter(FString Name) +{ + return FindParameterByName(Name) != nullptr; +} + +void +UHoudiniAssetBlueprintComponent::SetFloatParameter(FString Name, float Value, int Index) +{ + SetTypedValueAt(Name, Value, Index); +} + +void +UHoudiniAssetBlueprintComponent::SetToggleValueAt(FString Name, bool Value, int Index) +{ + UHoudiniParameterToggle* Parameter = Cast(FindParameterByName(Name)); + if (!Parameter) + return; + + Parameter->SetValueAt(Value, Index); +} + +//void UHoudiniAssetBlueprintComponent::OnPostCookHandler(UHoudiniAssetComponent* InComponent) +//{ +// +// // Before this component handles any translation, we need to make sure that it still belongs to the editor actor. +// // When a blueprint gets recompiled, the editor actor gets replaced with a new one but the old actor has not yet +// // been ftroyed / garbage collected so its components still receive cook events from the template. +// +// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponent); +// if (!IsValid(ComponentTemplate)) +// return; +// +// CopyStateFromTemplateComponent(ComponentTemplate); +// bForceNeedUpdate = true; +//} + +void +UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler(UHoudiniAssetComponent* InComponentTemplate) +{ + if (!(AssetState == EHoudiniAssetState::None || AssetState == EHoudiniAssetState::NeedInstantiation || AssetState == EHoudiniAssetState::NeedRebuild)) + // Don't process parameter changes since we're already cooking -- it is going to break things badly if we do. + return; + + if (!IsValidComponent()) + return; + + UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); + if (!ComponentTemplate) + return; + + // The component instance needs to copy values from the template. + bool bBlueprintStructureChanged = false; +#if WITH_EDITOR + CopyDetailsFromComponent(ComponentTemplate, + false, + false, + true, + false, + true, + bBlueprintStructureChanged, + RF_Public, + RF_ClassDefaultObject|RF_ArchetypeObject); +#endif + + SetCanDeleteHoudiniNodes(false); + + if (bHasRegisteredComponentTemplate) + { + // NOTE: It is very important to call this *after* CopyDetailsFromComponent(), since CopyDetailsFromComponent + // will clobber the inputs and parameter states on this component. + + // If we already have a valid asset id, keep it. + if (AssetId >= 0) + { + MarkAsNeedCook(); + } + else + { + MarkAsNeedInstantiation(); + } + + bHasRegisteredComponentTemplate = false; + bFullyLoaded = true; // MarkAsNeedInstantiation sets this to false. Force to true. + // While MarkAsNeedInstantiation() sets ParametersChanged to true, it does not + // set the 'NeedToTriggerUpdate' flag (both of which needs to be true in order + // to trigger an HDA update) so we are going to force NeedUpdate() to return true + // in order to get an initial cook. + bForceNeedUpdate = true; + } + + bUpdatedFromTemplate = true; +} + +void +UHoudiniAssetBlueprintComponent::InvalidateData() +{ + if (IsTemplate()) + { + // Ensure transient properties are invalidated/released for parameters, inputs and outputs as if the + // the object was undergoing destruction since the template component will likely be reregistered + // without being destroyed. + for(UHoudiniParameter* Param : Parameters) + { + Param->InvalidateData(); + } + + for(UHoudiniInput* Input : Inputs) + { + Input->InvalidateData(); + } + + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + AssetId = -1; + } +} + +//void UHoudiniAssetBlueprintComponent::OnTemplateHoudiniAssetChangedHandler(UHoudiniAssetComponent* InComponentTemplate) +//{ +// +// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); +// if (!ComponentTemplate) +// return; +// check(IsPreview()); +// +// // The Houdini Asset was changed on the template. We need to recook. +// AssetState = EHoudiniAssetState::NeedInstantiation; +// +//} + +USceneComponent* +UHoudiniAssetBlueprintComponent::FindOwnerComponentByName(FName ComponentName) const +{ + AActor* Owner = GetOwner(); + if (!Owner) + return nullptr; + + return FindActorComponentByName(Owner, ComponentName); +} + +USceneComponent* +UHoudiniAssetBlueprintComponent::FindActorComponentByName(AActor* InActor, FName ComponentName) const +{ + const TSet& Components = InActor->GetComponents(); + + for (UActorComponent* Component : Components) + { + USceneComponent* SceneComponent = Cast(Component); + if (!IsValid(SceneComponent)) + continue; + if (FName(SceneComponent->GetName()) == ComponentName) + return SceneComponent; + } + + return nullptr; +} + +bool UHoudiniAssetBlueprintComponent::GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid) +{ + FGuid* SCSGuid = CachedInputNodes.Find(InputGuid); + if (!SCSGuid) + return false; + OutSCSGuid = *SCSGuid; + return true; +} + +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const +{ + const TArray& AllNodes = SCS->GetAllNodes(); + + for (USCS_Node* Node : AllNodes) + { + if (!Node) + continue; + + if (Node->ComponentTemplate == InComponent) + return Node; + } + + return nullptr; +} + +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponentInClassHierarchy( + const UActorComponent* InComponent) const +{ + UObject* Outer = this->GetOuter(); + if (!IsValid(Outer)) + return nullptr; + UBlueprintGeneratedClass* MainBPGC; + if (IsTemplate()) + { + MainBPGC = Cast(Outer); + } + else + { + AActor* OwningActor = GetOwner(); + MainBPGC = Cast(OwningActor->GetClass()); + } + + check(MainBPGC); + TArray BPGCStack; + UBlueprintGeneratedClass::GetGeneratedClassesHierarchy(MainBPGC, BPGCStack); + for(const UBlueprintGeneratedClass* BPGC : BPGCStack) + { + USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; + if (!SCS) + return nullptr; + USCS_Node* SCSNode = FindSCSNodeForTemplateComponent(SCS, InComponent); + SCSNode = SCS->FindSCSNode(InComponent->GetFName()); + if (SCSNode) + return SCSNode; + } + + return nullptr; +} + +#if WITH_EDITOR +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const +{ + const TArray& AllNodes = SCS->GetAllNodes(); + + if (!InComponent) + return nullptr; + + for (USCS_Node* Node : AllNodes) + { + if (!Node) + continue; + if (Node->EditorComponentInstance.Get() == InComponent) + return Node; + } + + return nullptr; +} +#endif + +UActorComponent* +UHoudiniAssetBlueprintComponent::FindComponentInstanceInActor(const AActor* InActor, + USCS_Node* SCSNode) const +{ + UActorComponent* ComponentTemplate = SCSNode->ComponentTemplate; + + UActorComponent* ComponentInstance = NULL; + if (InActor != NULL) + { + if (SCSNode != NULL) + { + FName VariableName = SCSNode->GetVariableName(); + if (VariableName != NAME_None) + { + UWorld* World = InActor->GetWorld(); + FObjectPropertyBase* Property = FindFProperty(InActor->GetClass(), VariableName); + if (Property != NULL) + { + // Return the component instance that's stored in the property with the given variable name + ComponentInstance = Cast(Property->GetObjectPropertyValue_InContainer(InActor)); + } + else if (World != nullptr && World->WorldType == EWorldType::EditorPreview) + { + // If this is the preview actor, return the cached component instance that's being used for the pmnaview actor prior to recompiling the Blueprint +#if WITH_EDITOR + ComponentInstance = SCSNode->EditorComponentInstance.Get(); +#endif + } + } + } + else if (ComponentTemplate != NULL) + { +#if WITH_EDITOR + TInlineComponentArray Components; + InActor->GetComponents(Components); + ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components); +#endif + } + } + + return ComponentInstance; +} + + +//void UHoudiniAssetBlueprintComponent::OnOutputProcessingCompletedHandler(UHoudiniAssetComponent* InComponent) +//{ +// +// UHoudiniAssetBlueprintComponent* TemplateComponent = Cast(InComponent); +// if (!IsValid(TemplateComponent)) +// return; +// +// CopyStateFromComponent(TemplateComponent); +// bForceNeedUpdate = true; +//} + +//#if WITH_EDITOR +//void UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason) +//{ +// +// if (CachedBlueprint.Get()) +// { +// } +// +// if (Asset) +// { +// +// } +// +//} +//#endif + +void +UHoudiniAssetBlueprintComponent::CachePreviewState() +{ + bCachedIsPreview = false; + +#if WITH_EDITOR + AActor* ComponentOwner = GetOwner(); + if (!IsValid(ComponentOwner)) + return; + + USimpleConstructionScript* SCS = GetSCS(); + if (SCS == nullptr) + return; + + // Get the preview actor directly from the BlueprintEditor. + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + if (BlueprintEditor) + { + AActor* PreviewActor = BlueprintEditor->GetPreviewActor(); + if (PreviewActor == ComponentOwner) + { + bCachedIsPreview = true; + return; + } + } +#endif +} + +void +UHoudiniAssetBlueprintComponent::CacheBlueprintData() +{ + CachedBlueprint = nullptr; + CachedActorCDO = nullptr; + CachedTemplateComponent = IsTemplate() ? this : nullptr; + +#if WITH_EDITOR + CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); +#endif + + UBlueprintGeneratedClass* BPGC = Cast(GetOuter()); + if (BPGC) + { + // Dealing with a component template + CachedBlueprint = Cast(BPGC->ClassGeneratedBy); + } + else + { + // Dealing with a component instance. + CachedBlueprint = Cast(GetOuter()->GetClass()->ClassGeneratedBy); + } + + if (CreationMethod != EComponentCreationMethod::SimpleConstructionScript) + return; + + AActor* ComponentOwner = this->GetOwner(); + if (!IsValid(ComponentOwner)) + return; + UClass* OwnerClass = ComponentOwner->GetClass(); + if (!IsValid(OwnerClass)) + return; + + if (!IsTemplate()) + { + // NOTE: The following code allows us to find the component template from an instance. + CachedActorCDO = Cast< AActor >(CachedBlueprint->GeneratedClass->GetDefaultObject()); + if (!CachedActorCDO.IsValid() || (CachedActorCDO.Get() == ComponentOwner)) + return; +#if WITH_EDITOR + UActorComponent* TargetComponent = EditorUtilities::FindMatchingComponentInstance(this, CachedActorCDO.Get()); + CachedTemplateComponent = Cast(TargetComponent); +#endif + } + +} + +USimpleConstructionScript* +UHoudiniAssetBlueprintComponent::GetSCS() const +{ + if (!CachedBlueprint.Get()) + return nullptr; + + return CachedBlueprint->SimpleConstructionScript; +} + +//------------------------------------------------------------------------------------------------ +// FHoudiniAssetBlueprintInstanceData +//------------------------------------------------------------------------------------------------ + +FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData() + : HoudiniAsset(nullptr) + , AssetId(-1) + , AssetState(EHoudiniAssetState::None) + , SubAssetIndex(-1) + , AssetCookCount(0) + , bHasBeenLoaded(false) + , bHasBeenDuplicated(false) + , bPendingDelete(false) + , bRecookRequested(false) + , bRebuildRequested(false) + , bEnableCooking(true) + , bForceNeedUpdate(false) + , bLastCookSuccess(false) + , ComponentGUID(FGuid()) + , HapiGUID(FGuid()) + , bRegisteredComponentTemplate(false) + , SourceName() +{ + +} + +FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent) + : FActorComponentInstanceData(SourceComponent) +{ + +} + +void +FHoudiniAssetBlueprintInstanceData::AddReferencedObjects(FReferenceCollector & Collector) +{ + Super::AddReferencedObjects(Collector); + // TODO: Do we need to add references to output objects here? + // Any other references? + // What are the implications? +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h index d02640bd5..3c23886e5 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h @@ -1,351 +1,351 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Delegates/IDelegateInstance.h" -#include "Engine/Blueprint.h" -#include "HoudiniAssetComponent.h" - -#if WITH_EDITOR - #include "Subsystems/AssetEditorSubsystem.h" -#endif - -#include "HoudiniAssetBlueprintComponent.generated.h" - -class USCS_Node; - -UCLASS(NotBlueprintType, Experimental, meta=(BlueprintSpawnableComponent, DisplayName="Houdini Asset")) -class HOUDINIENGINERUNTIME_API UHoudiniAssetBlueprintComponent : public UHoudiniAssetComponent -{ - GENERATED_BODY() - -public: - UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer); - -#if WITH_EDITOR - // Sync certain variables of this HoudiniAssetComponent to the blueprint generated class. - // This is typically used when the Blueprint definition is being edited and the HAC cook - // took place in a transient HoudiniAssetComponent. Certain properties needs to be copied - // from the transient component back to the Blueprint generated class in order to be retained - // as part of the Client MeetingBlueprint definition. - - void CopyStateToTemplateComponent(); - - void CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties); - - void CopyDetailsFromComponent( - UHoudiniAssetBlueprintComponent* FromComponent, - const bool bCreateSCSNodes, - const bool bClearChangedToInputs, - const bool bClearChangedFromInputs, - const bool bInCanDeleteHoudiniNodes, - const bool bCopyInputObjectComponentProperties, - bool &bOutBlueprintStructureChanged, - EObjectFlags SetFlags=RF_NoFlags, - EObjectFlags ClearFlags=RF_NoFlags); - - // Update references on ToInput by looking up component references on FromInput in the SCS graph, on locating the correct component for ToInput. - void UpdateInputObjectComponentReferences( - USimpleConstructionScript* SCS, - UHoudiniInput* FromInput, - UHoudiniInput* ToInput, - const bool bCopyInputObjectProperties, - const bool bCreateMissingSCSNodes=false, - USCS_Node* ParentSCSNode=nullptr, - bool* bOutSCSNodeCreated=nullptr); - - virtual bool HasOpenEditor() const override; - IAssetEditorInstance* FindEditorInstance() const; - AActor* GetPreviewActor() const; -#endif - - virtual UHoudiniAssetComponent* GetCachedTemplate() const override; - - //------------------------------------------------------------------------------------------------ - // Supported Features - //------------------------------------------------------------------------------------------------ - - // Some features may be unavaible depending on the context in which the Houdini Asset Component - // has been instantiated. - - virtual bool CanDeleteHoudiniNodes() const override; - - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - - virtual bool IsValidComponent() const override; - - virtual bool IsInputTypeSupported(EHoudiniInputType InType) const override; - virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const override; - - virtual bool IsProxyStaticMeshEnabled() const override; - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - //virtual void BroadcastPreAssetCook() override; - virtual void OnPrePreCook() override; - virtual void OnPostPreCook() override; - virtual void OnPreOutputProcessing() override; - virtual void OnPostOutputProcessing() override; - virtual void OnPrePreInstantiation() override; - virtual void NotifyHoudiniRegisterCompleted() override; - virtual void NotifyHoudiniPreUnregister() override; - virtual void NotifyHoudiniPostUnregister() override; - - //------------------------------------------------------------------------------------------------ - // UActorComponent overrides - //------------------------------------------------------------------------------------------------ -#if WITH_EDITOR - virtual void OnComponentCreated() override; -#endif - - virtual void OnRegister() override; - - virtual void BeginDestroy() override; - virtual void DestroyComponent(bool bPromoteChildren = false) override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - // Refer USplineComponent for a decent reference on how to use component instance data. - virtual TStructOnScope GetComponentInstanceData() const override; - void ApplyComponentInstanceData(struct FHoudiniAssetBlueprintInstanceData* ComponentInstanceData, const bool bPostUCS); - - //------------------------------------------------------------------------------------------------ - // UHoudiniAssetComponent overrides - //------------------------------------------------------------------------------------------------ - - FHoudiniAssetComponentEvent OnParametersChangedEvent; - FHoudiniAssetComponentEvent OnHoudiniAssetChangedEvent; - - virtual void HoudiniEngineTick() override; - virtual void OnFullyLoaded() override; - virtual void OnTemplateParametersChanged() override; - virtual void OnHoudiniAssetChanged() override; - virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) override; - - virtual void OnBlueprintStructureModified() override; - virtual void OnBlueprintModified() override; - - - //------------------------------------------------------------------------------------------------ - // Blueprint functions - //------------------------------------------------------------------------------------------------ - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - bool HasParameter(FString Name); - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - void SetFloatParameter(FString Name, float Value, int Index=0); - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - void SetToggleValueAt(FString Name, bool Value, int Index=0); - - void AddInputObjectMapping(const FGuid& InputGuid, const FGuid& SCSVariableGuid) { CachedInputNodes.Add(InputGuid, SCSVariableGuid); } - bool GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid); - void RemoveInputObjectSCSVariableGuid(const FGuid& InputGuid) { CachedInputNodes.Remove(InputGuid); }; - - USCS_Node* FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; - USCS_Node* FindSCSNodeForTemplateComponentInClassHierarchy(const UActorComponent* InComponent) const; -#if WITH_EDITOR - USCS_Node* FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; -#endif // WITH_EDITOR - UActorComponent* FindComponentInstanceInActor(const AActor* InActor, USCS_Node* SCSNode) const; - -protected: - - template - void SetTypedValueAt(const FString& Name, ValueT& Value, int Index=0); - - void OnTemplateParametersChangedHandler(UHoudiniAssetComponent* ComponentTemplate); - void InvalidateData(); - - USceneComponent* FindOwnerComponentByName(FName ComponentName) const; - USceneComponent* FindActorComponentByName(AActor * InActor, FName ComponentName) const; - - void CachePreviewState(); - void CacheBlueprintData(); - - USimpleConstructionScript* GetSCS() const; - - //// The output translation has finished. - //void OnOutputProcessingCompletedHandler(UHoudiniAssetComponent * InComponent); - -#if WITH_EDITOR - //void ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason); - TWeakObjectPtr CachedAssetEditorSubsystem; -#endif - - TWeakObjectPtr CachedBlueprint; - TWeakObjectPtr CachedActorCDO; - TWeakObjectPtr CachedTemplateComponent; - - /*UPROPERTY(DuplicateTransient) - bool bOutputsRequireUpdate;*/ - UPROPERTY() - bool FauxBPProperty; - - UPROPERTY() - bool bHoudiniAssetChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bUpdatedFromTemplate; - - UPROPERTY() - bool bIsInBlueprintEditor; - - UPROPERTY(Transient, DuplicateTransient) - bool bCanDeleteHoudiniNodes; - - UPROPERTY(Transient, DuplicateTransient) - bool bHasRegisteredComponentTemplate; - - FDelegateHandle TemplatePropertiesChangedHandle; - - // This is used to keep track of which SCS variable names correspond to which - // output objects. - // This seems like it will cause issues in the map. - UPROPERTY() - TMap CachedOutputNodes; - - // This is used to keep track of which (SCS) variable guids correspond to which - // input objects. - UPROPERTY() - TMap CachedInputNodes; -}; - - -///** Used to keep track of output data and mappings during reconstruction */ -USTRUCT() -struct FHoudiniAssetBlueprintOutput -{ - GENERATED_BODY() - - UPROPERTY() - int32 OutputIndex; - - UPROPERTY() - FHoudiniOutputObject OutputObject; - - FHoudiniAssetBlueprintOutput() - : OutputIndex(INDEX_NONE) - { - - } -}; - - -/** Used to store HoudiniAssetComponent data during BP reconstruction */ -USTRUCT() -struct FHoudiniAssetBlueprintInstanceData : public FActorComponentInstanceData -{ - GENERATED_BODY() -public: - FHoudiniAssetBlueprintInstanceData(); - FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent); - - virtual ~FHoudiniAssetBlueprintInstanceData() = default; - - /*virtual bool ContainsData() const override - { - return (HAC != nullptr) || Super::ContainsData(); - }*/ - - virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override - { - Super::ApplyToComponent(Component, CacheApplyPhase); - CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); - } - - virtual void AddReferencedObjects(FReferenceCollector& Collector) override; - - // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. - UPROPERTY() - UHoudiniAsset* HoudiniAsset; - - UPROPERTY() - int32 AssetId; - - UPROPERTY() - EHoudiniAssetState AssetState; - - // Subasset index - UPROPERTY() - uint32 SubAssetIndex; - - UPROPERTY() - uint32 AssetCookCount; - - UPROPERTY() - bool bHasBeenLoaded; - - UPROPERTY() - bool bHasBeenDuplicated; - - UPROPERTY() - bool bPendingDelete; - - UPROPERTY() - bool bRecookRequested; - - UPROPERTY() - bool bRebuildRequested; - - UPROPERTY() - bool bEnableCooking; - - UPROPERTY() - bool bForceNeedUpdate; - - UPROPERTY() - bool bLastCookSuccess; - - /*UPROPERTY(DuplicateTransient) - TSet DownstreamHoudiniAssets;*/ - - UPROPERTY() - FGuid ComponentGUID; - - UPROPERTY() - FGuid HapiGUID; - - UPROPERTY() - bool bRegisteredComponentTemplate; - - // Name of the component from which this - // data was copied. Used for debugging purposes. - UPROPERTY() - FString SourceName; - - UPROPERTY() - TMap Outputs; - - UPROPERTY() - TArray Inputs; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Delegates/IDelegateInstance.h" +#include "Engine/Blueprint.h" +#include "HoudiniAssetComponent.h" + +#if WITH_EDITOR + #include "Subsystems/AssetEditorSubsystem.h" +#endif + +#include "HoudiniAssetBlueprintComponent.generated.h" + +class USCS_Node; + +UCLASS(NotBlueprintType, Experimental, meta=(BlueprintSpawnableComponent, DisplayName="Houdini Asset")) +class HOUDINIENGINERUNTIME_API UHoudiniAssetBlueprintComponent : public UHoudiniAssetComponent +{ + GENERATED_BODY() + +public: + UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer); + +#if WITH_EDITOR + // Sync certain variables of this HoudiniAssetComponent to the blueprint generated class. + // This is typically used when the Blueprint definition is being edited and the HAC cook + // took place in a transient HoudiniAssetComponent. Certain properties needs to be copied + // from the transient component back to the Blueprint generated class in order to be retained + // as part of the Client MeetingBlueprint definition. + + void CopyStateToTemplateComponent(); + + void CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties); + + void CopyDetailsFromComponent( + UHoudiniAssetBlueprintComponent* FromComponent, + const bool bCreateSCSNodes, + const bool bClearChangedToInputs, + const bool bClearChangedFromInputs, + const bool bInCanDeleteHoudiniNodes, + const bool bCopyInputObjectComponentProperties, + bool &bOutBlueprintStructureChanged, + EObjectFlags SetFlags=RF_NoFlags, + EObjectFlags ClearFlags=RF_NoFlags); + + // Update references on ToInput by looking up component references on FromInput in the SCS graph, on locating the correct component for ToInput. + void UpdateInputObjectComponentReferences( + USimpleConstructionScript* SCS, + UHoudiniInput* FromInput, + UHoudiniInput* ToInput, + const bool bCopyInputObjectProperties, + const bool bCreateMissingSCSNodes=false, + USCS_Node* ParentSCSNode=nullptr, + bool* bOutSCSNodeCreated=nullptr); + + virtual bool HasOpenEditor() const override; + IAssetEditorInstance* FindEditorInstance() const; + AActor* GetPreviewActor() const; +#endif + + virtual UHoudiniAssetComponent* GetCachedTemplate() const override; + + //------------------------------------------------------------------------------------------------ + // Supported Features + //------------------------------------------------------------------------------------------------ + + // Some features may be unavaible depending on the context in which the Houdini Asset Component + // has been instantiated. + + virtual bool CanDeleteHoudiniNodes() const override; + + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + + virtual bool IsValidComponent() const override; + + virtual bool IsInputTypeSupported(EHoudiniInputType InType) const override; + virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const override; + + virtual bool IsProxyStaticMeshEnabled() const override; + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + //virtual void BroadcastPreAssetCook() override; + virtual void OnPrePreCook() override; + virtual void OnPostPreCook() override; + virtual void OnPreOutputProcessing() override; + virtual void OnPostOutputProcessing() override; + virtual void OnPrePreInstantiation() override; + virtual void NotifyHoudiniRegisterCompleted() override; + virtual void NotifyHoudiniPreUnregister() override; + virtual void NotifyHoudiniPostUnregister() override; + + //------------------------------------------------------------------------------------------------ + // UActorComponent overrides + //------------------------------------------------------------------------------------------------ +#if WITH_EDITOR + virtual void OnComponentCreated() override; +#endif + + virtual void OnRegister() override; + + virtual void BeginDestroy() override; + virtual void DestroyComponent(bool bPromoteChildren = false) override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + // Refer USplineComponent for a decent reference on how to use component instance data. + virtual TStructOnScope GetComponentInstanceData() const override; + void ApplyComponentInstanceData(struct FHoudiniAssetBlueprintInstanceData* ComponentInstanceData, const bool bPostUCS); + + //------------------------------------------------------------------------------------------------ + // UHoudiniAssetComponent overrides + //------------------------------------------------------------------------------------------------ + + FHoudiniAssetComponentEvent OnParametersChangedEvent; + FHoudiniAssetComponentEvent OnHoudiniAssetChangedEvent; + + virtual void HoudiniEngineTick() override; + virtual void OnFullyLoaded() override; + virtual void OnTemplateParametersChanged() override; + virtual void OnHoudiniAssetChanged() override; + virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) override; + + virtual void OnBlueprintStructureModified() override; + virtual void OnBlueprintModified() override; + + + //------------------------------------------------------------------------------------------------ + // Blueprint functions + //------------------------------------------------------------------------------------------------ + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + bool HasParameter(FString Name); + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + void SetFloatParameter(FString Name, float Value, int Index=0); + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + void SetToggleValueAt(FString Name, bool Value, int Index=0); + + void AddInputObjectMapping(const FGuid& InputGuid, const FGuid& SCSVariableGuid) { CachedInputNodes.Add(InputGuid, SCSVariableGuid); } + bool GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid); + void RemoveInputObjectSCSVariableGuid(const FGuid& InputGuid) { CachedInputNodes.Remove(InputGuid); }; + + USCS_Node* FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; + USCS_Node* FindSCSNodeForTemplateComponentInClassHierarchy(const UActorComponent* InComponent) const; +#if WITH_EDITOR + USCS_Node* FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; +#endif // WITH_EDITOR + UActorComponent* FindComponentInstanceInActor(const AActor* InActor, USCS_Node* SCSNode) const; + +protected: + + template + void SetTypedValueAt(const FString& Name, ValueT& Value, int Index=0); + + void OnTemplateParametersChangedHandler(UHoudiniAssetComponent* ComponentTemplate); + void InvalidateData(); + + USceneComponent* FindOwnerComponentByName(FName ComponentName) const; + USceneComponent* FindActorComponentByName(AActor * InActor, FName ComponentName) const; + + void CachePreviewState(); + void CacheBlueprintData(); + + USimpleConstructionScript* GetSCS() const; + + //// The output translation has finished. + //void OnOutputProcessingCompletedHandler(UHoudiniAssetComponent * InComponent); + +#if WITH_EDITOR + //void ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason); + TWeakObjectPtr CachedAssetEditorSubsystem; +#endif + + TWeakObjectPtr CachedBlueprint; + TWeakObjectPtr CachedActorCDO; + TWeakObjectPtr CachedTemplateComponent; + + /*UPROPERTY(DuplicateTransient) + bool bOutputsRequireUpdate;*/ + UPROPERTY() + bool FauxBPProperty; + + UPROPERTY() + bool bHoudiniAssetChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bUpdatedFromTemplate; + + UPROPERTY() + bool bIsInBlueprintEditor; + + UPROPERTY(Transient, DuplicateTransient) + bool bCanDeleteHoudiniNodes; + + UPROPERTY(Transient, DuplicateTransient) + bool bHasRegisteredComponentTemplate; + + FDelegateHandle TemplatePropertiesChangedHandle; + + // This is used to keep track of which SCS variable names correspond to which + // output objects. + // This seems like it will cause issues in the map. + UPROPERTY() + TMap CachedOutputNodes; + + // This is used to keep track of which (SCS) variable guids correspond to which + // input objects. + UPROPERTY() + TMap CachedInputNodes; +}; + + +///** Used to keep track of output data and mappings during reconstruction */ +USTRUCT() +struct FHoudiniAssetBlueprintOutput +{ + GENERATED_BODY() + + UPROPERTY() + int32 OutputIndex; + + UPROPERTY() + FHoudiniOutputObject OutputObject; + + FHoudiniAssetBlueprintOutput() + : OutputIndex(INDEX_NONE) + { + + } +}; + + +/** Used to store HoudiniAssetComponent data during BP reconstruction */ +USTRUCT() +struct FHoudiniAssetBlueprintInstanceData : public FActorComponentInstanceData +{ + GENERATED_BODY() +public: + FHoudiniAssetBlueprintInstanceData(); + FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent); + + virtual ~FHoudiniAssetBlueprintInstanceData() = default; + + /*virtual bool ContainsData() const override + { + return (HAC != nullptr) || Super::ContainsData(); + }*/ + + virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override + { + Super::ApplyToComponent(Component, CacheApplyPhase); + CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); + } + + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + + // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. + UPROPERTY() + UHoudiniAsset* HoudiniAsset; + + UPROPERTY() + int32 AssetId; + + UPROPERTY() + EHoudiniAssetState AssetState; + + // Subasset index + UPROPERTY() + uint32 SubAssetIndex; + + UPROPERTY() + uint32 AssetCookCount; + + UPROPERTY() + bool bHasBeenLoaded; + + UPROPERTY() + bool bHasBeenDuplicated; + + UPROPERTY() + bool bPendingDelete; + + UPROPERTY() + bool bRecookRequested; + + UPROPERTY() + bool bRebuildRequested; + + UPROPERTY() + bool bEnableCooking; + + UPROPERTY() + bool bForceNeedUpdate; + + UPROPERTY() + bool bLastCookSuccess; + + /*UPROPERTY(DuplicateTransient) + TSet DownstreamHoudiniAssets;*/ + + UPROPERTY() + FGuid ComponentGUID; + + UPROPERTY() + FGuid HapiGUID; + + UPROPERTY() + bool bRegisteredComponentTemplate; + + // Name of the component from which this + // data was copied. Used for debugging purposes. + UPROPERTY() + FString SourceName; + + UPROPERTY() + TMap Outputs; + + UPROPERTY() + TArray Inputs; +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp index 1b7059f4d..0986ee54e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp @@ -1,2850 +1,2898 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetComponent.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniHandleComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "Engine/StaticMesh.h" -#include "Components/StaticMeshComponent.h" -#include "TimerManager.h" -#include "Landscape.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" -#include "PhysicsEngine/BodySetup.h" -#include "UObject/UObjectGlobals.h" - -#if WITH_EDITOR - #include "Editor/UnrealEd/Private/GeomFitUtils.h" -#endif - -#include "ComponentReregisterContext.h" - -// Macro to update given properties on all children components of the HAC. -#define HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( COMPONENT_CLASS, PROPERTY ) \ - do \ - { \ - TArray ReregisterComponents; \ - TArray LocalAttachChildren;\ - GetChildrenComponents(true, LocalAttachChildren); \ - for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) \ - { \ - COMPONENT_CLASS * Component = Cast(*Iter); \ - if (Component) \ - { \ - Component->PROPERTY = PROPERTY; \ - ReregisterComponents.Add(Component); \ - } \ - } \ - \ - if (ReregisterComponents.Num() > 0) \ - { \ - FMultiComponentReregisterContext MultiComponentReregisterContext(ReregisterComponents); \ - } \ - } \ - while(0) - - -void -UHoudiniAssetComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - - // Legacy serialization - // Either try to convert or skip depending the setting value - if (bEnableBackwardCompatibility) - { - // Attemp to convert the v1 object to v2 - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - // Deserialize the legacy data, we'll do the actual conversion in PostLoad() - // After everything has been deserialized - Version1CompatibilityHAC = NewObject(this); - Version1CompatibilityHAC->Serialize(Ar); - } - else - { - // Skip the v1 object - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -bool -UHoudiniAssetComponent::ConvertLegacyData() -{ - if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill()) - return false; - - // Set the Houdini Asset - if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill()) - return false; - - HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset; - - // Convert all parameters - for (auto& LegacyParmPair : Version1CompatibilityHAC->Parameters) - { - if (!LegacyParmPair.Value) - continue; - - UHoudiniParameter* Parm = LegacyParmPair.Value->ConvertLegacyData(this); - LegacyParmPair.Value->CopyLegacyParameterData(Parm); - Parameters.Add(Parm); - } - - // Convert all inputs - for (auto& LegacyInput : Version1CompatibilityHAC->Inputs) - { - // Convert v1 input to v2 - UHoudiniInput* Input = LegacyInput->ConvertLegacyInput(this); - - Inputs.Add(Input); - } - - // Lambdas for finding/creating outputs from an HGPO - auto FindOrCreateOutput = [&](FHoudiniGeoPartObject& InNewHGPO, bool& bNew) - { - UHoudiniOutput* NewOutput = nullptr; - - // See if we can add to an existing output - UHoudiniOutput** FoundOutput = nullptr; - FoundOutput = Outputs.FindByPredicate( - [InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; }); - - if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill()) - { - // FoundOutput is valid, add to it - NewOutput = *FoundOutput; - bNew = false; - } - else - { - // Create a new output object - NewOutput = NewObject( - this, UHoudiniOutput::StaticClass(), NAME_None, RF_NoFlags); - bNew = true; - } - - return NewOutput; - }; - - - // Convert all outputs - // Start by handling the Static Meshes - for (auto& LegacySM : Version1CompatibilityHAC->StaticMeshes) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacySM.Key.ConvertLegacyData(); - - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(NewHGPO); - // Mark if the HoudiniOutput is editable - NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); - } - - // Build a new output object identifier - FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); - Identifier.bLoaded = true; - Identifier.PartName = NewHGPO.PartName; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); - OutputObj.OutputObject = LegacySM.Value; - OutputObj.OutputComponent = nullptr; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - // Handle the SMC for this SM / HGPO - if (LegacySM.Value && !LegacySM.Value->IsPendingKill()) - { - UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value); - if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill()) - OutputObj.OutputComponent = *FoundSMC; - } - - // Add to the outputs - Outputs.AddUnique(NewOutput); - - //NewOutput->StaleCount; - //NewOutput->bLandscapeWorldComposition; - //NewOutput->HoudiniCreatedSocketActors; - //NewOutput->HoudiniAttachedSocketActors; - //NewOutput->bHasEditableNodeBuilt - false; - } - - // ... then Landscapes - for (auto& LegacyLandscape : Version1CompatibilityHAC->LandscapeComponents) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacyLandscape.Key.ConvertLegacyData(); - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(NewHGPO); - // Mark if the HoudiniOutput is editable - NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); - } - - // Build a new output object identifier - FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); - Identifier.bLoaded = true; - Identifier.PartName = NewHGPO.PartName; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); - - // We need to create a LandscapePtr wrapper for the landscaope - UHoudiniLandscapePtr* LandscapePtr = NewObject(NewOutput); - LandscapePtr->SetSoftPtr(LegacyLandscape.Value.IsValid() ? LegacyLandscape.Value.Get() : nullptr); - - OutputObj.OutputObject = LandscapePtr; - OutputObj.OutputComponent = nullptr; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - // Add to the outputs - Outputs.AddUnique(NewOutput); - } - - // ... instancers - for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs) - { - if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill()) - continue; - - FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData(); - - // Prepare this output object's output identifier - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = InstancerHGPO.ObjectId; - OutputIdentifier.GeoId = InstancerHGPO.GeoId; - OutputIdentifier.PartId = InstancerHGPO.PartId; - OutputIdentifier.PartName = InstancerHGPO.PartName; - - EHoudiniInstancerType InstancerType = EHoudiniInstancerType::ObjectInstancer; - if (LegacyInstanceIn->Flags.bIsPackedPrimitiveInstancer) - InstancerType = EHoudiniInstancerType::PackedPrimitive; - else if (LegacyInstanceIn->Flags.bAttributeInstancerOverride) - InstancerType = EHoudiniInstancerType::AttributeInstancer; - else if (LegacyInstanceIn->Flags.bIsAttributeInstancer) - InstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - else if (LegacyInstanceIn->ObjectToInstanceId >= 0) - InstancerType = EHoudiniInstancerType::ObjectInstancer; - - InstancerHGPO.InstancerType = InstancerType; - - //bool bIsMSIC = LegacyInstanceIn->Flags.bIsSplitMeshInstancer; - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(InstancerHGPO); - } - - // Get the output's instanced outputs - TMap& InstancedOutputs = NewOutput->GetInstancedOutputs(); - - int32 InstFieldIdx = 0; - for (auto& LegacyInstanceInputField : LegacyInstanceIn->InstanceInputFields) - { - FHoudiniGeoPartObject InstInputFieldHGPO = LegacyInstanceInputField->HoudiniGeoPartObject.ConvertLegacyData(); - - // Create an instanced output for this object - FHoudiniInstancedOutput NewInstOut; - NewInstOut.OriginalObject = LegacyInstanceInputField->OriginalObject; - NewInstOut.OriginalObjectIndex = -1; - NewInstOut.OriginalTransforms = LegacyInstanceInputField->InstancedTransforms; - - for (auto& InstObj : LegacyInstanceInputField->InstancedObjects) - NewInstOut.VariationObjects.Add(InstObj); - - int32 NumVar = LegacyInstanceInputField->RotationOffsets.Num(); - for (int32 Idx = 0; Idx < NumVar; Idx++) - { - FTransform TransOffset; - TransOffset.SetLocation(FVector::ZeroVector); - if (LegacyInstanceInputField->RotationOffsets.IsValidIndex(Idx)) - TransOffset.SetRotation(LegacyInstanceInputField->RotationOffsets[Idx].Quaternion()); - - if (LegacyInstanceInputField->ScaleOffsets.IsValidIndex(Idx)) - TransOffset.SetScale3D(LegacyInstanceInputField->ScaleOffsets[Idx]); - - NewInstOut.VariationTransformOffsets.Add(TransOffset); - } - - // Build an identifier for the instance output - FHoudiniOutputObjectIdentifier Identifier; - Identifier.ObjectId = InstInputFieldHGPO.ObjectId; - Identifier.GeoId = InstInputFieldHGPO.GeoId; - Identifier.PartId = InstInputFieldHGPO.PartId; - Identifier.PartName = InstInputFieldHGPO.PartName; - Identifier.SplitIdentifier = FString::FromInt(InstFieldIdx); - Identifier.bLoaded = true; - - // Add the instance output to the outputs - InstancedOutputs.Add(Identifier, NewInstOut); - - // Now create an Output object for each variation - int32 VarIdx = 0; - for (auto& LegacyComp : LegacyInstanceInputField->InstancerComponents) - { - // Build a new output object identifier for this variation - FHoudiniOutputObjectIdentifier VarIdentifier; - VarIdentifier.ObjectId = InstInputFieldHGPO.ObjectId; - VarIdentifier.GeoId = InstInputFieldHGPO.GeoId; - VarIdentifier.PartId = InstInputFieldHGPO.PartId; - VarIdentifier.PartName = InstInputFieldHGPO.PartName; - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - VarIdentifier.SplitIdentifier = - FString::FromInt(InstFieldIdx) + TEXT("_") + FString::FromInt(VarIdx); - VarIdentifier.bLoaded = true; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(VarIdentifier); - - OutputObj.OutputObject = nullptr; - OutputObj.OutputComponent = LegacyComp; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - VarIdx++; - } - - // ??? - //LegacyInstanceInputField->VariationTransformsArray; - //LegacyInstanceInputField->InstanceColorOverride; - //LegacyInstanceInputField->VariationInstanceColorOverrideArray; - - // Index of the variation used for each transform - //NewInstOut.TransformVariationIndices; - //NewInstOut.bUniformScaleLocked = false; - - InstFieldIdx++; - } - - // Add to the outputs - Outputs.AddUnique(NewOutput); - } - - // ... then Spline Components (for Curve IN) - for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents) - { - UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value; - if (!CurSplineComp || CurSplineComp->IsPendingKill()) - continue; - - // TODO: Needed? - // Attach the spline to the HAC - CurSplineComp->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - - // Editable curve? / Should create an output for it! - if (CurSplineComp->IsEditableOutputCurve()) - { - FHoudiniGeoPartObject CurHGPO = LegacyCurve.Key.ConvertLegacyData(); - - // Look for an output for that HGPO - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(CurHGPO); - } - - // Build an output object id for the editable curve output - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; - EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; - EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; - EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; - - TMap& OutputObjects = NewOutput->GetOutputObjects(); - FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - FoundOutputObject.OutputComponent = CurSplineComp; - - //CurSplineComp->SetHasEditableNodeBuilt(true); - CurSplineComp->SetIsInputCurve(false); - } - else - { - // Input! - // Conversion of the inputs should have done the job already - CurSplineComp->SetIsInputCurve(true); - } - } - - // ... Handles - for (auto& LegacyHandle : Version1CompatibilityHAC->HandleComponents) - { - // TODO: Handles!! - UHoudiniHandleComponent* NewHandle = nullptr; - HandleComponents.Add(NewHandle); - } - - // ... Materials - UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials; - if(LegacyMaterials && !LegacyMaterials->IsPendingKill()) - { - // Assignements: Apply to all outputs since they're not tied to an HGPO... - for (auto& CurOutput : Outputs) - { - TMap& CurrAssign = CurOutput->GetAssignementMaterials(); - for (auto& LegacyMaterial : LegacyMaterials->Assignments) - { - CurrAssign.Add(LegacyMaterial.Key, LegacyMaterial.Value); - } - } - - // Replacements - // Try to find the output matching the HGPO - for (auto& LegacyMaterial : LegacyMaterials->Replacements) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacyMaterial.Key.ConvertLegacyData(); - - TMap& LegacyReplacement = LegacyMaterial.Value; - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - if (bCreatedNew) - continue; - - TMap& CurReplacement = NewOutput->GetReplacementMaterials(); - for (auto& CurLegacyReplacement : LegacyReplacement) - { - CurReplacement.Add(CurLegacyReplacement.Key, CurLegacyReplacement.Value); - } - } - } - - - // ... Bake Name overrides - for (auto& LegacyBakeNameOverride : Version1CompatibilityHAC->BakeNameOverrides) - { - // In Outputs? - } - - // ... then Downstream asset connections (due to Asset inputs) - for (auto& LegacyDownstreamHAC : Version1CompatibilityHAC->DownstreamAssetConnections) - { - //TSet DownstreamHoudiniAssets; - } - - // Then convert all remaing flags and properties - StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = Version1CompatibilityHAC->bGeneratedDoubleSidedGeometry; - StaticMeshGenerationProperties.GeneratedPhysMaterial = Version1CompatibilityHAC->GeneratedPhysMaterial; - StaticMeshGenerationProperties.DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; - StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; - StaticMeshGenerationProperties.GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; - StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier = Version1CompatibilityHAC->GeneratedLpvBiasMultiplier; - StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; - StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; - StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; - StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = Version1CompatibilityHAC->GeneratedStreamingDistanceMultiplier; - //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; - StaticMeshGenerationProperties.GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; - - StaticMeshBuildSettings.DistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; - - BakeFolder.Path = Version1CompatibilityHAC->BakeFolder.ToString(); - TemporaryCookFolder.Path = Version1CompatibilityHAC->TempCookFolder.ToString(); - - ComponentGUID = Version1CompatibilityHAC->ComponentGUID; - - bEnableCooking = Version1CompatibilityHAC->bEnableCooking; - bUploadTransformsToHoudiniEngine = Version1CompatibilityHAC->bUploadTransformsToHoudiniEngine; - bCookOnTransformChange = Version1CompatibilityHAC->bTransformChangeTriggersCooks; - bCookOnParameterChange = true; - //Version1CompatibilityHAC->bCookingTriggersDownstreamCooks; - bCookOnAssetInputCook = true; - bOutputless = false; - bOutputTemplateGeos = false; - bFullyLoaded = Version1CompatibilityHAC->bFullyLoaded; - - //bContainsHoudiniLogoGeometry = Version1CompatibilityHAC->bContainsHoudiniLogoGeometry; - //bIsNativeComponent = Version1CompatibilityHAC->bIsNativeComponent; - //bIsPreviewComponent = Version1CompatibilityHAC->bIsPreviewComponent; - //bLoadedComponent = Version1CompatibilityHAC->bLoadedComponent; - //bIsPlayModeActive_Unused = Version1CompatibilityHAC->bIsPlayModeActive_Unused; - //Version1CompatibilityHAC->bTimeCookInPlaymode_Unused; - //Version1CompatibilityHAC->bUseHoudiniMaterials; - - //Version1CompatibilityHAC->GeneratedGeometryScaleFactor; - //Version1CompatibilityHAC->TransformScaleFactor; - //Version1CompatibilityHAC->PresetBuffer; - //Version1CompatibilityHAC->DefaultPresetBuffer; - //Version1CompatibilityHAC->ParameterByName; - - // Now that we're done, update all the output's types - for (auto& CurOutput : Outputs) - { - CurOutput->UpdateOutputType(); - } - - // - // Clean up the legacy HAC - // - - Version1CompatibilityHAC->Parameters.Empty(); - Version1CompatibilityHAC->Inputs.Empty(); - Version1CompatibilityHAC->StaticMeshes.Empty(); - Version1CompatibilityHAC->LandscapeComponents.Empty(); - Version1CompatibilityHAC->InstanceInputs.Empty(); - Version1CompatibilityHAC->SplineComponents.Empty(); - Version1CompatibilityHAC->HandleComponents.Empty(); - //Version1CompatibilityHAC->HoudiniAssetComponentMaterials.Empty(); - Version1CompatibilityHAC->BakeNameOverrides.Empty(); - Version1CompatibilityHAC->DownstreamAssetConnections.Empty(); - Version1CompatibilityHAC->MarkPendingKill(); - Version1CompatibilityHAC = nullptr; - - return true; -} - - -UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - HoudiniAsset = nullptr; - bCookOnParameterChange = true; - bUploadTransformsToHoudiniEngine = true; - bCookOnTransformChange = false; - //bUseNativeHoudiniMaterials = true; - bCookOnAssetInputCook = true; - - AssetId = -1; - AssetState = EHoudiniAssetState::PreInstantiation; - AssetStateResult = EHoudiniAssetStateResult::None; - AssetCookCount = 0; - - SubAssetIndex = -1; - - // Make an invalid GUID, since we do not have any cooking requests. - HapiGUID.Invalidate(); - - // Create unique component GUID. - ComponentGUID = FGuid::NewGuid(); - - bUploadTransformsToHoudiniEngine = true; - - bHasBeenLoaded = false; - bHasBeenDuplicated = false; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = false; - bEnableCooking = true; - bForceNeedUpdate = false; - bLastCookSuccess = false; - bBlueprintStructureModified = false; - bBlueprintModified = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // Folder used for cooking, the value is initialized by Output Translator - // TemporaryCookFolder.Path = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - - // Folder used for baking this asset's outputs, the value is initialized by Output Translator - // BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - bHasComponentTransformChanged = false; - - bFullyLoaded = false; - - bOutputless = false; - - bOutputTemplateGeos = false; - - PDGAssetLink = nullptr; - - StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; - - bOverrideGlobalProxyStaticMeshSettings = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bEnableProxyStaticMeshOverride = HoudiniRuntimeSettings->bEnableProxyStaticMesh; - bEnableProxyStaticMeshRefinementByTimerOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; - ProxyMeshAutoRefineTimeoutSecondsOverride = HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; - bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; - bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; - } - else - { - bEnableProxyStaticMeshOverride = false; - bEnableProxyStaticMeshRefinementByTimerOverride = true; - ProxyMeshAutoRefineTimeoutSecondsOverride = 10.0f; - bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = true; - bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = true; - } - - bNoProxyMeshNextCookRequested = false; - bBakeAfterNextCook = false; - -#if WITH_EDITORONLY_DATA - bGenerateMenuExpanded = true; - bBakeMenuExpanded = true; - bAssetOptionMenuExpanded = true; - bHelpAndDebugMenuExpanded = true; - - HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; - - bRemoveOutputAfterBake = false; - bRecenterBakedActors = false; - bReplacePreviousBake = false; -#endif - - // - // Set component properties. - // - - Mobility = EComponentMobility::Static; - - SetGenerateOverlapEvents(false); - - // Similar to UMeshComponent. - CastShadow = true; - bUseAsOccluder = true; - bCanEverAffectNavigation = true; - - // This component requires render update. - bNeverNeedsRenderUpdate = false; - - Bounds = FBox(ForceInitToZero); - - LastTickTime = 0.0; - - // Initialize the default SM Build settings with the plugin's settings default values - StaticMeshBuildSettings = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); -} - -UHoudiniAssetComponent::~UHoudiniAssetComponent() -{ - // Unregister ourself so our houdini node can be delete. - - // This gets called in UnRegisterHoudiniComponent, with appropriate checks. Don't call it here. - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); -} - -void UHoudiniAssetComponent::PostInitProperties() -{ - Super::PostInitProperties(); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - { - // Copy default static mesh generation parameters from settings. - StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; - StaticMeshGenerationProperties.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; - StaticMeshGenerationProperties.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; - StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; - StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; - StaticMeshGenerationProperties.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; - StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; - StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; - StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; - StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; - StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; - StaticMeshGenerationProperties.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; - } - - // Register ourself to the HER singleton - RegisterHoudiniComponent(this); -} - -UHoudiniAsset * -UHoudiniAssetComponent::GetHoudiniAsset() const -{ - return HoudiniAsset; -} - -FString -UHoudiniAssetComponent::GetDisplayName() const -{ - return GetOwner() ? GetOwner()->GetName() : GetName(); -} - -void -UHoudiniAssetComponent::GetOutputs(TArray& OutOutputs) const -{ - for (UHoudiniOutput* Output : Outputs) - { - OutOutputs.Add(Output); - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh; - } - else - { - return false; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementByTimerEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; - } - else - { - return false; - } - } -} - -float -UHoudiniAssetComponent::GetProxyMeshAutoRefineTimeoutSeconds() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return ProxyMeshAutoRefineTimeoutSecondsOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; - } - else - { - return 5.0f; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; - } - else - { - return false; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; - } - else - { - return false; - } - } -} - - -void -UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) -{ - // Check the asset validity - if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill()) - return; - - // If it is the same asset, do nothing. - if ( InHoudiniAsset == HoudiniAsset ) - return; - - HoudiniAsset = InHoudiniAsset; -} - - -void -UHoudiniAssetComponent::OnHoudiniAssetChanged() -{ - // TODO: clear input/params/outputs? - Parameters.Empty(); - - // The asset has been changed, mark us as needing to be reinstantiated - MarkAsNeedInstantiation(); - - // Force an update on the next tick - bForceNeedUpdate = true; -} - -bool -UHoudiniAssetComponent::NeedUpdateParameters() const -{ - // This is being split into a separate function to that it can - // be called separately for component templates. - - if (!bCookOnParameterChange) - return false; - - // Go through all our parameters, return true if they have been updated - for (auto CurrentParm : Parameters) - { - if (!CurrentParm || CurrentParm->IsPendingKill()) - continue; - - if (!CurrentParm->HasChanged()) - continue; - - // See if the parameter doesn't require an update - // (because it has failed to upload previously or has been loaded) - if (!CurrentParm->NeedsToTriggerUpdate()) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateParameters()] Parameters need update for component: %s"), *(GetPathName())); - return true; - } - - return false; -} - -bool -UHoudiniAssetComponent::NeedUpdateInputs() const -{ - // Go through all our inputs, return true if they have been updated - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (!CurrentInput->HasChanged()) - continue; - - // See if the input doesn't require an update - // (because it has failed to upload previously or has been loaded) - if (!CurrentInput->NeedsToTriggerUpdate()) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateInputs()] Inputs need update for component: %s"), *(GetPathName())); - return true; - } - - return false; -} - -bool -UHoudiniAssetComponent::HasPreviousBakeOutput() const -{ - // Look for any bake output objects in the output array - for (const UHoudiniOutput* Output : Outputs) - { - if (!IsValid(Output)) - continue; - - if (BakedOutputs.Num() == 0) - return false; - - for (const FHoudiniBakedOutput& BakedOutput : BakedOutputs) - { - if (BakedOutput.BakedOutputObjects.Num() > 0) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::NeedUpdate() const -{ - if (AssetState != DebugLastAssetState) - { - DebugLastAssetState = AssetState; - } - - // It is important to check this when dealing with Blueprints since the - // preview components start receiving events from the template component - // before the preview component have finished initialization. - if (!IsFullyLoaded()) - return false; - - // We must have a valid asset - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return false; - - if (bForceNeedUpdate) - return true; - - // If we don't want to cook on parameter/input change dont bother looking for updates - if (!bCookOnParameterChange && !bRecookRequested && !bRebuildRequested) - return false; - - // Check if the HAC's transform has changed and transform triggers cook is enabled - if (bCookOnTransformChange && bHasComponentTransformChanged) - return true; - - if (NeedUpdateParameters()) - return true; - - if (NeedUpdateInputs()) - return true; - - // Go through all outputs, filter the editable nodes. Return true if they have been updated. - for (auto CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - // We only care about editable outputs - if (!CurrentOutput->IsEditableNode()) - continue; - - // Trigger an update if the output object is marked as modified by user. - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - for (auto& NextPair : OutputObjects) - { - // For now, only editable curves can trigger update - UHoudiniSplineComponent* HoudiniSplineComponent = Cast(NextPair.Value.OutputComponent); - if (!HoudiniSplineComponent) - continue; - - // Output curves cant trigger an update! - if (HoudiniSplineComponent->bIsOutputCurve) - continue; - - if (HoudiniSplineComponent->NeedsToTriggerUpdate()) - return true; - } - } - - return false; -} - -// Indicates if any of the HAC's output components needs to be updated (no recook needed) -bool -UHoudiniAssetComponent::NeedOutputUpdate() const -{ - // Go through all outputs - for (auto CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs()) - { - if (InstOutput.Value.bChanged) - return true; - } - } - - return false; -} - -bool UHoudiniAssetComponent::NeedBlueprintStructureUpdate() const -{ - // TODO: Add similar flags to inputs, parametsr - return bBlueprintStructureModified; -} - -bool UHoudiniAssetComponent::NeedBlueprintUpdate() const -{ - // TODO: Add similar flags to inputs, parametsr - return bBlueprintModified; -} - -bool -UHoudiniAssetComponent::NotifyCookedToDownstreamAssets() -{ - // Before notifying, clean up our downstream assets - // - check that they are still valid - // - check that we are still connected to one of its asset input - // - check that the asset as the CookOnAssetInputCook trigger enabled - TArray DownstreamToDelete; - for(auto& CurrentDownstreamHAC : DownstreamHoudiniAssets) - { - // Remove the downstream connection by default, - // unless we actually were properly connected to one of this HDa's input. - bool bRemoveDownstream = true; - if (CurrentDownstreamHAC && !CurrentDownstreamHAC->IsPendingKill()) - { - // Go through the HAC's input - for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs) - { - if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill()) - continue; - - EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType(); - if (CurrentDownstreamInputType != EHoudiniInputType::Asset - && CurrentDownstreamInputType != EHoudiniInputType::World) - continue; - - if (!CurrentDownstreamInput->ContainsInputObject(this, CurrentDownstreamInputType)) - continue; - - if (CurrentDownstreamHAC->bCookOnAssetInputCook) - { - // Mark that HAC's input has changed - CurrentDownstreamInput->MarkChanged(true); - } - bRemoveDownstream = false; - } - } - - if (bRemoveDownstream) - { - DownstreamToDelete.Add(CurrentDownstreamHAC); - } - } - - for (auto ToDelete : DownstreamToDelete) - { - DownstreamHoudiniAssets.Remove(ToDelete); - } - - return true; -} - -bool -UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() -{ - for (auto& CurrentInput : Inputs) - { - EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World) - continue; - - TArray* ObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInputType); - if (!ObjectArray) - continue; - - for (auto& CurrentInputObject : (*ObjectArray)) - { - // Get the input HDA - UHoudiniAssetComponent* InputHAC = CurrentInputObject - ? Cast(CurrentInputObject->GetObject()) - : nullptr; - - if (!InputHAC) - continue; - - // If the input HDA needs to be instantiated, force him to instantiate - // if the input HDA is in any other state than None, we need to wait for him - // to finish whatever it's doing - if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) - { - // Tell the input HAC to instantiate - InputHAC->AssetState = EHoudiniAssetState::PreInstantiation; - - // We need to wait - return true; - } - else if (InputHAC->GetAssetState() != EHoudiniAssetState::None) - { - // We need to wait - return true; - } - } - } - - return false; -} - -void -UHoudiniAssetComponent::BeginDestroy() -{ - if (CanDeleteHoudiniNodes()) - { - } - - // Gets called through UnRegisterHoudiniComponent(). - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - - // Unregister ourself so our houdini node can be deleted - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - Super::BeginDestroy(); -} - -void -UHoudiniAssetComponent::MarkAsNeedCook() -{ - // Force the asset state to NeedCook - //AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = true; - bRebuildRequested = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/trigger update - for (auto CurrentParam : Parameters) - { - if (!IsValid(CurrentParam)) - continue; - - // Do not trigger parameter update for Button/Button strip when recooking - // As we don't want to trigger the buttons - if (CurrentParam->IsA() || CurrentParam->IsA()) - continue; - - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(true); - } - - // We need to mark all of our editable curves as changed - for (auto Output : Outputs) - { - if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) - continue; - - for (auto& OutputObjectEntry : Output->GetOutputObjects()) - { - FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; - if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) - continue; - - UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); - if (!IsValid(SplineComponent)) - continue; - - // This sets bHasChanged and bNeedsToTriggerUpdate - SplineComponent->MarkChanged(true); - } - } - - // We need to mark all our inputs as changed/trigger update - for (auto CurrentInput : Inputs) - { - if (!IsValid(CurrentInput)) - continue; - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(true); - CurrentInput->MarkDataUploadNeeded(true); - - // In addition to marking the input as changed/need update, we also need to make sure that any changes on the - // Unreal side have been recorded for the input before sending to Houdini. For that we also mark each input - // object as changed/need update and explicitly call the Update function on each input object. For example, for - // input actors this would recreate the Houdini input actor components from the actor's components, picking up - // any new components since the last call to Update. - TArray* InputObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); - if (InputObjectArray && InputObjectArray->Num() > 0) - { - for (auto CurrentInputObject : *InputObjectArray) - { - if (!IsValid(CurrentInputObject)) - continue; - - UObject* const Object = CurrentInputObject->GetObject(); - if (IsValid(Object)) - CurrentInputObject->Update(Object); - - CurrentInputObject->MarkChanged(true); - CurrentInputObject->SetNeedsToTriggerUpdate(true); - CurrentInputObject->MarkTransformChanged(true); - } - } - } - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -void -UHoudiniAssetComponent::MarkAsNeedRebuild() -{ - // Invalidate the asset ID - //AssetId = -1; - - // Force the asset state to NeedRebuild - AssetState = EHoudiniAssetState::NeedRebuild; - AssetStateResult = EHoudiniAssetStateResult::None; - - // Reset some of the asset's flag - //AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = true; - bFullyLoaded = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/trigger update - for (auto CurrentParam : Parameters) - { - if (!IsValid(CurrentParam)) - continue; - - // Do not trigger parameter update for Button/Button strip when rebuilding - // As we don't want to trigger the buttons - if (CurrentParam->IsA() || CurrentParam->IsA()) - continue; - - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(true); - } - - // We need to mark all of our editable curves as changed - for (auto Output : Outputs) - { - if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) - continue; - - for (auto& OutputObjectEntry : Output->GetOutputObjects()) - { - FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; - if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) - continue; - - UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); - if (!IsValid(SplineComponent)) - continue; - - // This sets bHasChanged and bNeedsToTriggerUpdate - SplineComponent->MarkChanged(true); - } - } - - // We need to mark all our inputs as changed/trigger update - for (auto CurrentInput : Inputs) - { - if (!IsValid(CurrentInput)) - continue; - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(true); - CurrentInput->MarkDataUploadNeeded(true); - } - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -// Marks the asset as needing to be instantiated -void -UHoudiniAssetComponent::MarkAsNeedInstantiation() -{ - // Invalidate the asset ID - AssetId = -1; - - if (Parameters.Num() <= 0 && Inputs.Num() <= 0 && Outputs.Num() <= 0) - { - // The asset has no parameters or inputs. - // This likely indicates it has never cooked/been instantiated. - // Set its state to PreInstantiation to force its instantiation - // so that we can have its parameters/input interface - AssetState = EHoudiniAssetState::PreInstantiation; - } - else - { - // The asset has cooked before since we have a parameter/input interface - // Set its state to need instantiation so that the asset is instantiated - // after being modified - AssetState = EHoudiniAssetState::NeedInstantiation; - } - - AssetStateResult = EHoudiniAssetStateResult::None; - - // Reset some of the asset's flag - AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = false; - bFullyLoaded = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/not triggering update - for (auto CurrentParam : Parameters) - { - if (CurrentParam) - { - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(false); - } - } - - // We need to mark all our inputs as changed/not triggering update - for (auto CurrentInput : Inputs) - { - if (CurrentInput) - { - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(false); - CurrentInput->MarkDataUploadNeeded(true); - } - } - - /*if (!CanInstantiateAsset()) - { - AssetState = EHoudiniAssetState::None; - AssetStateResult = EHoudiniAssetStateResult::None; - }*/ - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -void UHoudiniAssetComponent::MarkAsBlueprintStructureModified() -{ - bBlueprintStructureModified = true; -} - -void UHoudiniAssetComponent::MarkAsBlueprintModified() -{ - bBlueprintModified = true; -} - -void -UHoudiniAssetComponent::PostLoad() -{ - Super::PostLoad(); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - bool bAutomaticLegacyHDARebuild = HoudiniRuntimeSettings->bAutomaticLegacyHDARebuild; - - // Legacy serialization: either try to convert or skip depending the setting value - if (bEnableBackwardCompatibility && Version1CompatibilityHAC != nullptr) - { - // If we have deserialized legacy v1 data, attempt to convert it now - ConvertLegacyData(); - - if (bAutomaticLegacyHDARebuild) - MarkAsNeedRebuild(); - else - MarkAsNeedInstantiation(); - } - else - { - // Normal v2 objet, mark as need instantiation - MarkAsNeedInstantiation(); - } - - // Component has been loaded, not duplicated - bHasBeenDuplicated = false; - - // We need to register ourself - RegisterHoudiniComponent(this); - - // Register our PDG Asset link if we have any - - // !!! Do not update rendering while loading, do it when setting up the render state - // UpdateRenderingInformation(); -} - -void -UHoudiniAssetComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context) -{ - UpdateRenderingInformation(); - Super::CreateRenderState_Concurrent(Context); -} - -void -UHoudiniAssetComponent::PostEditImport() -{ - Super::PostEditImport(); - - MarkAsNeedInstantiation(); - - // Component has been duplicated, not loaded - // We do need the loaded flag to reapply parameters, inputs - // and properly update some of the output objects - bHasBeenDuplicated = true; - - //RemoveAllAttachedComponents(); - - AssetState = EHoudiniAssetState::PreInstantiation; - AssetStateResult = EHoudiniAssetStateResult::None; - - // TODO? - // REGISTER? -} - -void -UHoudiniAssetComponent::UpdatePostDuplicate() -{ - // TODO: - // - Keep the output objects/components (remove duplicatetransient on the output object uproperties) - // - Duplicate created objects (ie SM) and materials - // - Update the output components to use these instead - // This should remove the need for a cook on duplicate - - // For now, we simply clean some of the HAC's component manually - const TArray Children = GetAttachChildren(); - - for (auto & NextChild : Children) - { - if (!NextChild || NextChild->IsPendingKill()) - continue; - - USceneComponent * ComponentToRemove = nullptr; - if (NextChild->IsA()) - { - ComponentToRemove = NextChild; - } - else if (NextChild->IsA()) - { - ComponentToRemove = NextChild; - } - /* do not destroy attached duplicated editable curves, they are needed to restore editable curves - else if (NextChild->IsA()) - { - // Remove duplicated editable curve output's Houdini Spline Component, since they will be re-built at duplication. - UHoudiniSplineComponent * HoudiniSplineComponent = Cast(NextChild); - if (HoudiniSplineComponent && HoudiniSplineComponent->IsEditableOutputCurve()) - ComponentToRemove = NextChild; - } - */ - if (ComponentToRemove) - { - ComponentToRemove->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - ComponentToRemove->UnregisterComponent(); - ComponentToRemove->DestroyComponent(); - } - } - - // if there is an associated PDG asset link, call its UpdatePostDuplicate to cleanup references to - // to the original instance's PDG output actors - if (IsValid(PDGAssetLink)) - { - PDGAssetLink->UpdatePostDuplicate(); - } - - SetHasBeenDuplicated(false); -} - -bool UHoudiniAssetComponent::IsInputTypeSupported(EHoudiniInputType InType) const -{ - return true; -} - -bool UHoudiniAssetComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const -{ - return true; -} - -bool -UHoudiniAssetComponent::IsPreview() const -{ - return bCachedIsPreview; -} - -bool UHoudiniAssetComponent::IsValidComponent() const -{ - return true; -} - -void UHoudiniAssetComponent::OnFullyLoaded() -{ - bFullyLoaded = true; -} - - -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); - } - */ -} - -void -UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - - if (CanDeleteHoudiniNodes()) - { - } - - // Unregister ourself so our houdini node can be deleted - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - HoudiniAsset = nullptr; - - // Clear Parameters - for (UHoudiniParameter*& CurrentParm : Parameters) - { - if (CurrentParm && !CurrentParm->IsPendingKill()) - { - CurrentParm->ConditionalBeginDestroy(); - } - else if (GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE) - { - // TODO unneeded log? - // Avoid spamming that error when leaving PIE mode - HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); - } - - CurrentParm = nullptr; - } - - Parameters.Empty(); - - // Clear Inputs - for (UHoudiniInput*& CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - // Destroy connected Houdini asset. - CurrentInput->ConditionalBeginDestroy(); - CurrentInput = nullptr; - } - - Inputs.Empty(); - - // Clear Output - for (UHoudiniOutput*& CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - // Destroy all Houdini created socket actors. - TArray & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors(); - for (auto & CurCreatedActor : CurCreatedSocketActors) - { - if (!CurCreatedActor || CurCreatedActor->IsPendingKill()) - continue; - - CurCreatedActor->Destroy(); - } - CurCreatedSocketActors.Empty(); - - // Detach all Houdini attached socket actors - TArray & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors(); - for (auto & CurAttachedSocketActor : CurAttachedSocketActors) - { - if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill()) - continue; - - CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - } - CurAttachedSocketActors.Empty(); - -#if WITH_EDITOR - // Clean up foliages instances - for (auto& CurrentOutputObject : CurrentOutput->GetOutputObjects()) - { - // Foliage instancers store a HISMC in the components - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentOutputObject.Value.OutputComponent); - if (!FoliageHISMC) - continue; - - UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - continue; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(FoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - continue; - - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - continue; - - if (IsInGameThread() && IsGarbageCollecting()) - { - // TODO: ?? - // Calling DeleteInstancesForComponent during GC will cause unreal to crash... - HOUDINI_LOG_WARNING(TEXT("%s: Unable to clear foliage instances because of GC"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); - } - else - { - // Clean up the instances generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(this, FoliageType); - } - - if (FoliageHISMC->GetInstanceCount() > 0) - { - // If the component still has instances left after the cleanup, - // make sure that we dont delete it, as the leftover instances are likely hand-placed - CurrentOutputObject.Value.OutputComponent = nullptr; - } - else - { - // Remove the foliage type if it doesn't have any more instances - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - } - } -#endif - - CurrentOutput->Clear(); - // Destroy connected Houdini asset. - CurrentOutput->ConditionalBeginDestroy(); - CurrentOutput = nullptr; - } - - Outputs.Empty(); - - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - // Unregister ourself so our houdini node can be delete. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); - - - // Clear all TOP data and temporary geo/objects from the PDG asset link (if valid) - if (IsValid(PDGAssetLink)) - { -#if WITH_EDITOR - const UWorld* const World = GetWorld(); - if (IsValid(World)) - { - // Only do this for editor worlds, only interactively (not during engine shutdown or garbage collection) - if (World->WorldType == EWorldType::Editor && GIsRunning && !GIsGarbageCollecting) - { - // In case we are recording a transaction (undo, for example) notify that the object will be - // modified. - PDGAssetLink->Modify(); - PDGAssetLink->ClearAllTOPData(); - } - } -#endif - } - - Super::OnComponentDestroyed(bDestroyingHierarchy); -} - -void UHoudiniAssetComponent::RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) -{ - // Registration of this component is wrapped in this virtual function to allow - // derived classed to override this behaviour. - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(InComponent); -} - -void -UHoudiniAssetComponent::OnRegister() -{ - Super::OnRegister(); - - // NOTE: Wait until HoudiniEngineTick() before deciding to mark this object as fully loaded - // since preview components need to wait for component templates to finish their initialization - // before being able to perform state transfers. - - /* - // 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(); - } - } - */ - - // Let TickInitialization() take care of manipulating the bFullyLoaded state. - //bFullyLoaded = true; - - //// If we're constructing editable components in the SCS editor, set the component instance corresponding to this node for editing purposes - // - //USimpleConstructionScript* SCS = GetSCS(); - //if (SCS == nullptr) - //{ - // bFullyLoaded = true; - //} - //else - //{ - // if(SCS->IsConstructingEditorComponents()) - // { - // // We're not fully loaded yet. We're expecting - // } - // else - // { - // bFullyLoaded = true; - // } - //} - -} - -UHoudiniParameter* -UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) -{ - if (!InOtherParam || InOtherParam->IsPendingKill()) - return nullptr; - - for (auto CurrentParam : Parameters) - { - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - if (CurrentParam->Matches(*InOtherParam)) - return CurrentParam; - } - - return nullptr; -} - -UHoudiniInput* -UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) -{ - if (!InOtherInput || InOtherInput->IsPendingKill()) - return nullptr; - - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->Matches(*InOtherInput)) - return CurrentInput; - } - - return nullptr; -} - -UHoudiniHandleComponent* -UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle) -{ - if (!InOtherHandle || InOtherHandle->IsPendingKill()) - return nullptr; - - for (auto CurrentHandle : HandleComponents) - { - if (!CurrentHandle || CurrentHandle->IsPendingKill()) - continue; - - if (CurrentHandle->Matches(*InOtherHandle)) - return CurrentHandle; - } - - return nullptr; -} - -UHoudiniParameter* -UHoudiniAssetComponent::FindParameterByName(const FString& InParamName) -{ - for (auto CurrentParam : Parameters) - { - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - if (CurrentParam->GetParameterName().Equals(InParamName)) - return CurrentParam; - } - - return nullptr; -} - - -void -UHoudiniAssetComponent::OnChildAttached(USceneComponent* ChildComponent) -{ - Super::OnChildAttached(ChildComponent); - - // ... Do corresponding things for other houdini component types. - // ... -} - - -void -UHoudiniAssetComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) -{ - Super::OnUpdateTransform(UpdateTransformFlags, Teleport); - - SetHasComponentTransformChanged(true); -} - -void UHoudiniAssetComponent::HoudiniEngineTick() -{ - if (!IsFullyLoaded()) - { - OnFullyLoaded(); - } -} - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - FProperty * Property = PropertyChangedEvent.MemberProperty; - if (!Property) - return; - - FName PropertyName = Property->GetFName(); - - // Changing the Houdini Asset? - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, HoudiniAsset)) - { - OnHoudiniAssetChanged(); - } - else if (PropertyName == GetRelativeLocationPropertyName() - || PropertyName == GetRelativeRotationPropertyName() - || PropertyName == GetRelativeScale3DPropertyName()) - { - SetHasComponentTransformChanged(true); - } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bOverrideGlobalProxyStaticMeshSettings) - || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bEnableProxyStaticMeshRefinementByTimerOverride) - || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, ProxyMeshAutoRefineTimeoutSecondsOverride)) - { - ClearRefineMeshesTimer(); - // Reset the timer - // SetRefineMeshesTimer will check the relevant settings and only set the timer if enabled via settings - SetRefineMeshesTimer(); - } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, 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); - } - } - else if (PropertyName == TEXT("bVisible")) - { - // Visibility has changed, propagate it to children. - SetVisibility(IsVisible(), true); - } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bHiddenInGame)) - { - // Visibility has changed, propagate it to children. - SetHiddenInGame(bHiddenInGame, true); - } - else - { - // TODO: - // Propagate properties (mobility/visibility etc.. to children components) - // Look in v1 for: if (Property->HasMetaData(TEXT("Category"))) {} and HOUDINI_UPDATE_ALL_CHILD_COMPONENTS - } - - 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 (UHoudiniOutput* CurOutput : Outputs) - { - if (!CurOutput) - continue; - - for (auto& Pair : CurOutput->GetOutputObjects()) - { - UStaticMesh* StaticMesh = Cast(Pair.Value.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) - continue; - - SetStaticMeshGenerationProperties(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 ReregisterComponents; - { - TArray 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); - } - } - } -} -#endif - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::PostEditUndo() -{ - Super::PostEditUndo(); - - if (!IsPendingKill()) - { - // Make sure we are registered with the HER singleton - // We could be undoing a HoudiniActor delete - if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(this)) - { - MarkAsNeedInstantiation(); - - // Component has been loaded, not duplicated - bHasBeenDuplicated = false; - - RegisterHoudiniComponent(this); - } - } -} - -#endif - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::OnActorMoved(AActor* Actor) -{ - if (GetOwner() != Actor) - return; - - SetHasComponentTransformChanged(true); -} -#endif - -void -UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged) -{ - // Only update the value if we're fully loaded - // This avoid triggering a recook when loading a level - if(bFullyLoaded) - bHasComponentTransformChanged = InHasChanged; -} - -void -UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // Check the object validity - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // If it is the same object, do nothing. - if (InPDGAssetLink == PDGAssetLink) - return; - - PDGAssetLink = InPDGAssetLink; -} - - -FBoxSphereBounds -UHoudiniAssetComponent::CalcBounds(const FTransform & LocalToWorld) const -{ - FBoxSphereBounds LocalBounds; - FBox BoundingBox = GetAssetBounds(nullptr, false); - 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; -} - - -FBox -UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const -{ - FBox BoxBounds(ForceInitToZero); - - // Query the bounds for all output objects - for (auto & CurOutput : Outputs) - { - if (!CurOutput || CurOutput->IsPendingKill()) - continue; - - BoxBounds += CurOutput->GetBounds(); - } - - // Query the bounds for all our inputs - for (auto & CurInput : Inputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - BoxBounds += CurInput->GetBounds(); - } - - // Query the bounds for all input parameters - for (auto & CurParam : Parameters) - { - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (CurParam->GetParameterType() != EHoudiniParameterType::Input) - continue; - - UHoudiniParameterOperatorPath* InputParam = Cast(CurParam); - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (!InputParam->HoudiniInput.IsValid()) - continue; - - BoxBounds += InputParam->HoudiniInput.Get()->GetBounds(); - } - - // Query the bounds for all our Houdini handles - for (auto & CurHandleComp : HandleComponents) - { - if (!CurHandleComp || CurHandleComp->IsPendingKill()) - continue; - - BoxBounds += CurHandleComp->GetBounds(); - } - - // Also scan all our decendants for SMC bounds not just top-level children - // ( split mesh instances' mesh bounds were not gathered proiperly ) - TArray 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(pChild)) - { - if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) - continue; - - FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); - if (StaticMeshBounds.IsValid) - BoxBounds += StaticMeshBounds; - } - } - - // If nothing was found, init with the asset's location - if (BoxBounds.GetVolume() == 0.0f) - BoxBounds += GetComponentLocation(); - - return BoxBounds; -} - -void -UHoudiniAssetComponent::ClearRefineMeshesTimer() -{ - UWorld *World = GetWorld(); - if (!World) - { - //HOUDINI_LOG_ERROR(TEXT("Cannot ClearRefineMeshesTimer, World is nullptr!")); - return; - } - - World->GetTimerManager().ClearTimer(RefineMeshesTimer); -} - -void -UHoudiniAssetComponent::SetRefineMeshesTimer() -{ - UWorld *World = GetWorld(); - if (!World) - { - HOUDINI_LOG_ERROR(TEXT("Cannot SetRefineMeshesTimer, World is nullptr!")); - return; - } - - // Check if timer-based proxy mesh refinement is enable for this component - const bool bEnableTimer = IsProxyStaticMeshRefinementByTimerEnabled(); - const float TimeSeconds = GetProxyMeshAutoRefineTimeoutSeconds(); - if (bEnableTimer) - { - World->GetTimerManager().SetTimer(RefineMeshesTimer, this, &UHoudiniAssetComponent::OnRefineMeshesTimerFired, 1.0f, false, TimeSeconds); - } - else - { - World->GetTimerManager().ClearTimer(RefineMeshesTimer); - } -} - -void -UHoudiniAssetComponent::OnRefineMeshesTimerFired() -{ - HOUDINI_LOG_MESSAGE(TEXT("UHoudiniAssetComponent::OnRefineMeshesTimerFired()")); - if (OnRefineMeshesTimerDelegate.IsBound()) - { - OnRefineMeshesTimerDelegate.Broadcast(this); - } -} - -bool -UHoudiniAssetComponent::HasAnyCurrentProxyOutput() const -{ - for (const UHoudiniOutput *Output : Outputs) - { - if (Output->HasAnyCurrentProxy()) - { - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasAnyProxyOutput() const -{ - for (const UHoudiniOutput *Output : Outputs) - { - if (Output->HasAnyProxy()) - { - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasAnyOutputComponent() const -{ - for (UHoudiniOutput *Output : Outputs) - { - for(auto& CurrentOutputObject : Output->GetOutputObjects()) - { - if(CurrentOutputObject.Value.OutputComponent) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasOutputObject(UObject* InOutputObjectToFind) const -{ - for (const auto& CurOutput : Outputs) - { - for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) - { - if (CurOutputObject.Value.OutputObject == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.OutputComponent == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.ProxyObject == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.ProxyComponent == InOutputObjectToFind) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const -{ - // Get the state of the asset and check if it is pre-cook, cooked, pending delete/rebuild or invalid - bOutNeedsRebuildOrDelete = false; - bOutInvalidState = false; - switch (AssetState) - { - case EHoudiniAssetState::NeedInstantiation: - case EHoudiniAssetState::PreInstantiation: - case EHoudiniAssetState::Instantiating: - case EHoudiniAssetState::PreCook: - case EHoudiniAssetState::Cooking: - case EHoudiniAssetState::PostCook: - case EHoudiniAssetState::PreProcess: - case EHoudiniAssetState::Processing: - return false; - break; - case EHoudiniAssetState::None: - return true; - break; - case EHoudiniAssetState::NeedRebuild: - case EHoudiniAssetState::NeedDelete: - case EHoudiniAssetState::Deleting: - bOutNeedsRebuildOrDelete = true; - break; - default: - bOutInvalidState = true; - break; - } - - return false; -} - -void -UHoudiniAssetComponent::SetInputPresets(const TMap& InPresets) -{ - // Set the input preset for this HAC -#if WITH_EDITOR - InputPresets = InPresets; -#endif -} - - -void -UHoudiniAssetComponent::ApplyInputPresets() -{ - if (InputPresets.Num() <= 0) - return; - -#if WITH_EDITOR - // Ignore inputs that have been preset to curve - TArray InputArray; - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetInputType() != EHoudiniInputType::Curve) - InputArray.Add(CurrentInput); - } - - // Try to apply the supplied Object to the Input - for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset) - { - UObject * Object = IterToolPreset.Key(); - if (!Object || Object->IsPendingKill()) - continue; - - int32 InputNumber = IterToolPreset.Value(); - if (!InputArray.IsValidIndex(InputNumber)) - continue; - - // If the object is a landscape, add a new landscape input - if (Object->IsA()) - { - // selecting a landscape - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Landscape); - if (InsertNum == 0) - { - // Landscape inputs only support one object! - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Landscape, InsertNum, Object); - } - } - - // If the object is an actor, add a new world input - if (Object->IsA()) - { - // selecting an actor - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::World); - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::World, InsertNum, Object); - } - - // If the object is a static mesh, add a new geometry input (TODO: or BP ? ) - if (Object->IsA()) - { - // selecting a Staticn Mesh - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Geometry, InsertNum, Object); - } - - if (Object->IsA()) - { - // selecting a Houdini Asset - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Asset); - if (InsertNum == 0) - { - // Assert inputs only support one object! - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Asset, InsertNum, Object); - } - } - } - - // The input objects have been set, now change the input type - bool bBPStructureModified = false; - for (auto CurrentInput : Inputs) - { - int32 NumGeo = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - int32 NumAsset = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Asset); - int32 NumWorld = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::World); - int32 NumLandscape = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Landscape); - - EHoudiniInputType NewInputType = EHoudiniInputType::Invalid; - if (NumLandscape > 0 && NumLandscape >= NumGeo && NumLandscape >= NumAsset && NumLandscape >= NumWorld) - NewInputType = EHoudiniInputType::Landscape; - else if (NumWorld > 0 && NumWorld >= NumGeo && NumWorld >= NumAsset && NumWorld >= NumLandscape) - NewInputType = EHoudiniInputType::World; - else if (NumAsset > 0 && NumAsset >= NumGeo && NumAsset >= NumWorld && NumAsset >= NumLandscape) - NewInputType = EHoudiniInputType::Asset; - else if (NumGeo > 0 && NumGeo >= NumAsset && NumGeo >= NumWorld && NumGeo >= NumLandscape) - NewInputType = EHoudiniInputType::Geometry; - - if (NewInputType == EHoudiniInputType::Invalid) - continue; - - // Change the input type, unless if it was preset to a different type and we have object for the preset type - if (CurrentInput->GetInputType() == EHoudiniInputType::Geometry && NewInputType != EHoudiniInputType::Geometry) - { - CurrentInput->SetInputType(NewInputType, bBPStructureModified); - } - else - { - // Input type was preset, only change if that type is empty - if(CurrentInput->GetNumberOfInputObjects() <= 0) - CurrentInput->SetInputType(NewInputType, bBPStructureModified); - } - } - if (bBPStructureModified) - { - MarkAsBlueprintStructureModified(); - } -#endif - - // Discard the tool presets after their first setup - InputPresets.Empty(); -} - - -bool -UHoudiniAssetComponent::IsComponentValid() const -{ - if (!IsValidLowLevel()) - return false; - - if (IsTemplate()) - return false; - - if (IsPendingKillOrUnreachable()) - return false; - - if (!GetOuter()) //|| !GetOuter()->GetLevel() ) - return false; - - return true; -} - -bool -UHoudiniAssetComponent::IsInstantiatingOrCooking() const -{ - return HapiGUID.IsValid(); -} - - -void -UHoudiniAssetComponent::SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const -{ -#if WITH_EDITOR - if (!InStaticMesh) - return; - - // Make sure static mesh has a new lighting guid. - InStaticMesh->LightingGuid = FGuid::NewGuid(); - InStaticMesh->LODGroup = NAME_None; - - // Set resolution of lightmap. - InStaticMesh->LightMapResolution = StaticMeshGenerationProperties.GeneratedLightMapResolution; - - // Set Bias multiplier for Light Propagation Volume lighting. - InStaticMesh->LpvBiasMultiplier = StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier; - - // Set the global light map coordinate index if it looks valid - if (InStaticMesh->RenderData.IsValid() && InStaticMesh->RenderData->LODResources.Num() > 0) - { - int32 NumUVs = InStaticMesh->RenderData->LODResources[0].GetNumTexCoords(); - if (NumUVs > StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex) - { - InStaticMesh->LightMapCoordinateIndex = StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex; - } - } - - // TODO - // Set method for LOD texture factor computation. - //InStaticMesh->bUseMaximumStreamingTexelRatio = StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; - // Set distance where textures using UV 0 are streamed in/out. - GOES ON COMPONENT - // InStaticMesh->StreamingDistanceMultiplier = StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; - - // Add user data. - for (int32 AssetUserDataIdx = 0; AssetUserDataIdx < StaticMeshGenerationProperties.GeneratedAssetUserData.Num(); AssetUserDataIdx++) - InStaticMesh->AddAssetUserData(StaticMeshGenerationProperties.GeneratedAssetUserData[AssetUserDataIdx]); - - // - if (!InStaticMesh->BodySetup) - InStaticMesh->CreateBodySetup(); - - UBodySetup* BodySetup = InStaticMesh->BodySetup; - if (!InStaticMesh->BodySetup) - return; - - // Set flag whether physics triangle mesh will use double sided faces when doing scene queries. - BodySetup->bDoubleSidedGeometry = StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry; - - // Assign physical material for simple collision. - BodySetup->PhysMaterial = StaticMeshGenerationProperties.GeneratedPhysMaterial; - - BodySetup->DefaultInstance.CopyBodyInstancePropertiesFrom(&StaticMeshGenerationProperties.DefaultBodyInstance); - - // Assign collision trace behavior. - BodySetup->CollisionTraceFlag = StaticMeshGenerationProperties.GeneratedCollisionTraceFlag; - - // Assign walkable slope behavior. - BodySetup->WalkableSlopeOverride = StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride; - - // We want to use all of geometry for collision detection purposes. - BodySetup->bMeshCollideAll = true; - -#endif -} - - -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 LocalAttachChildren; - GetChildrenComponents(true, LocalAttachChildren); - for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) - { - USceneComponent * SceneComponent = *Iter; - if (IsValid(SceneComponent)) - SceneComponent->RecreatePhysicsState(); - } - - // !!! Do not call UpdateBounds() here as this could cause - // a loading loop in post load on game builds! -} - - -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(&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); -} \ No newline at end of file +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" +#include "TimerManager.h" +#include "Landscape.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" +#include "PhysicsEngine/BodySetup.h" +#include "UObject/UObjectGlobals.h" + +#if WITH_EDITOR + #include "Editor/UnrealEd/Private/GeomFitUtils.h" +#endif + +#include "ComponentReregisterContext.h" + +// Macro to update given properties on all children components of the HAC. +#define HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( COMPONENT_CLASS, PROPERTY ) \ + do \ + { \ + TArray ReregisterComponents; \ + TArray LocalAttachChildren;\ + GetChildrenComponents(true, LocalAttachChildren); \ + for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) \ + { \ + COMPONENT_CLASS * Component = Cast(*Iter); \ + if (Component) \ + { \ + Component->PROPERTY = PROPERTY; \ + ReregisterComponents.Add(Component); \ + } \ + } \ + \ + if (ReregisterComponents.Num() > 0) \ + { \ + FMultiComponentReregisterContext MultiComponentReregisterContext(ReregisterComponents); \ + } \ + } \ + while(0) + + +void +UHoudiniAssetComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + + // Legacy serialization + // Either try to convert or skip depending the setting value + if (bEnableBackwardCompatibility) + { + // Attemp to convert the v1 object to v2 + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + // Deserialize the legacy data, we'll do the actual conversion in PostLoad() + // After everything has been deserialized + Version1CompatibilityHAC = NewObject(this); + Version1CompatibilityHAC->Serialize(Ar); + } + else + { + // Skip the v1 object + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +bool +UHoudiniAssetComponent::ConvertLegacyData() +{ + if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill()) + return false; + + // Set the Houdini Asset + if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill()) + return false; + + HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset; + + // Convert all parameters + for (auto& LegacyParmPair : Version1CompatibilityHAC->Parameters) + { + if (!LegacyParmPair.Value) + continue; + + UHoudiniParameter* Parm = LegacyParmPair.Value->ConvertLegacyData(this); + LegacyParmPair.Value->CopyLegacyParameterData(Parm); + Parameters.Add(Parm); + } + + // Convert all inputs + for (auto& LegacyInput : Version1CompatibilityHAC->Inputs) + { + // Convert v1 input to v2 + UHoudiniInput* Input = LegacyInput->ConvertLegacyInput(this); + + Inputs.Add(Input); + } + + // Lambdas for finding/creating outputs from an HGPO + auto FindOrCreateOutput = [&](FHoudiniGeoPartObject& InNewHGPO, bool& bNew) + { + UHoudiniOutput* NewOutput = nullptr; + + // See if we can add to an existing output + UHoudiniOutput** FoundOutput = nullptr; + FoundOutput = Outputs.FindByPredicate( + [InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; }); + + if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill()) + { + // FoundOutput is valid, add to it + NewOutput = *FoundOutput; + bNew = false; + } + else + { + // Create a new output object + NewOutput = NewObject( + this, UHoudiniOutput::StaticClass(), NAME_None, RF_NoFlags); + bNew = true; + } + + return NewOutput; + }; + + + // Convert all outputs + // Start by handling the Static Meshes + for (auto& LegacySM : Version1CompatibilityHAC->StaticMeshes) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacySM.Key.ConvertLegacyData(); + + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(NewHGPO); + // Mark if the HoudiniOutput is editable + NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); + } + + // Build a new output object identifier + FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); + Identifier.bLoaded = true; + Identifier.PartName = NewHGPO.PartName; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); + OutputObj.OutputObject = LegacySM.Value; + OutputObj.OutputComponent = nullptr; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + // Handle the SMC for this SM / HGPO + if (LegacySM.Value && !LegacySM.Value->IsPendingKill()) + { + UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value); + if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill()) + OutputObj.OutputComponent = *FoundSMC; + } + + // Add to the outputs + Outputs.AddUnique(NewOutput); + + //NewOutput->StaleCount; + //NewOutput->bLandscapeWorldComposition; + //NewOutput->HoudiniCreatedSocketActors; + //NewOutput->HoudiniAttachedSocketActors; + //NewOutput->bHasEditableNodeBuilt - false; + } + + // ... then Landscapes + for (auto& LegacyLandscape : Version1CompatibilityHAC->LandscapeComponents) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacyLandscape.Key.ConvertLegacyData(); + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(NewHGPO); + // Mark if the HoudiniOutput is editable + NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); + } + + // Build a new output object identifier + FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); + Identifier.bLoaded = true; + Identifier.PartName = NewHGPO.PartName; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); + + // We need to create a LandscapePtr wrapper for the landscaope + UHoudiniLandscapePtr* LandscapePtr = NewObject(NewOutput); + LandscapePtr->SetSoftPtr(LegacyLandscape.Value.IsValid() ? LegacyLandscape.Value.Get() : nullptr); + + OutputObj.OutputObject = LandscapePtr; + OutputObj.OutputComponent = nullptr; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + // Add to the outputs + Outputs.AddUnique(NewOutput); + } + + // ... instancers + for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs) + { + if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill()) + continue; + + FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData(); + + // Prepare this output object's output identifier + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = InstancerHGPO.ObjectId; + OutputIdentifier.GeoId = InstancerHGPO.GeoId; + OutputIdentifier.PartId = InstancerHGPO.PartId; + OutputIdentifier.PartName = InstancerHGPO.PartName; + + EHoudiniInstancerType InstancerType = EHoudiniInstancerType::ObjectInstancer; + if (LegacyInstanceIn->Flags.bIsPackedPrimitiveInstancer) + InstancerType = EHoudiniInstancerType::PackedPrimitive; + else if (LegacyInstanceIn->Flags.bAttributeInstancerOverride) + InstancerType = EHoudiniInstancerType::AttributeInstancer; + else if (LegacyInstanceIn->Flags.bIsAttributeInstancer) + InstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + else if (LegacyInstanceIn->ObjectToInstanceId >= 0) + InstancerType = EHoudiniInstancerType::ObjectInstancer; + + InstancerHGPO.InstancerType = InstancerType; + + //bool bIsMSIC = LegacyInstanceIn->Flags.bIsSplitMeshInstancer; + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(InstancerHGPO); + } + + // Get the output's instanced outputs + TMap& InstancedOutputs = NewOutput->GetInstancedOutputs(); + + int32 InstFieldIdx = 0; + for (auto& LegacyInstanceInputField : LegacyInstanceIn->InstanceInputFields) + { + FHoudiniGeoPartObject InstInputFieldHGPO = LegacyInstanceInputField->HoudiniGeoPartObject.ConvertLegacyData(); + + // Create an instanced output for this object + FHoudiniInstancedOutput NewInstOut; + NewInstOut.OriginalObject = LegacyInstanceInputField->OriginalObject; + NewInstOut.OriginalObjectIndex = -1; + NewInstOut.OriginalTransforms = LegacyInstanceInputField->InstancedTransforms; + + for (auto& InstObj : LegacyInstanceInputField->InstancedObjects) + NewInstOut.VariationObjects.Add(InstObj); + + int32 NumVar = LegacyInstanceInputField->RotationOffsets.Num(); + for (int32 Idx = 0; Idx < NumVar; Idx++) + { + FTransform TransOffset; + TransOffset.SetLocation(FVector::ZeroVector); + if (LegacyInstanceInputField->RotationOffsets.IsValidIndex(Idx)) + TransOffset.SetRotation(LegacyInstanceInputField->RotationOffsets[Idx].Quaternion()); + + if (LegacyInstanceInputField->ScaleOffsets.IsValidIndex(Idx)) + TransOffset.SetScale3D(LegacyInstanceInputField->ScaleOffsets[Idx]); + + NewInstOut.VariationTransformOffsets.Add(TransOffset); + } + + // Build an identifier for the instance output + FHoudiniOutputObjectIdentifier Identifier; + Identifier.ObjectId = InstInputFieldHGPO.ObjectId; + Identifier.GeoId = InstInputFieldHGPO.GeoId; + Identifier.PartId = InstInputFieldHGPO.PartId; + Identifier.PartName = InstInputFieldHGPO.PartName; + Identifier.SplitIdentifier = FString::FromInt(InstFieldIdx); + Identifier.bLoaded = true; + + // Add the instance output to the outputs + InstancedOutputs.Add(Identifier, NewInstOut); + + // Now create an Output object for each variation + int32 VarIdx = 0; + for (auto& LegacyComp : LegacyInstanceInputField->InstancerComponents) + { + // Build a new output object identifier for this variation + FHoudiniOutputObjectIdentifier VarIdentifier; + VarIdentifier.ObjectId = InstInputFieldHGPO.ObjectId; + VarIdentifier.GeoId = InstInputFieldHGPO.GeoId; + VarIdentifier.PartId = InstInputFieldHGPO.PartId; + VarIdentifier.PartName = InstInputFieldHGPO.PartName; + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + VarIdentifier.SplitIdentifier = + FString::FromInt(InstFieldIdx) + TEXT("_") + FString::FromInt(VarIdx); + VarIdentifier.bLoaded = true; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(VarIdentifier); + + OutputObj.OutputObject = nullptr; + OutputObj.OutputComponent = LegacyComp; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + VarIdx++; + } + + // ??? + //LegacyInstanceInputField->VariationTransformsArray; + //LegacyInstanceInputField->InstanceColorOverride; + //LegacyInstanceInputField->VariationInstanceColorOverrideArray; + + // Index of the variation used for each transform + //NewInstOut.TransformVariationIndices; + //NewInstOut.bUniformScaleLocked = false; + + InstFieldIdx++; + } + + // Add to the outputs + Outputs.AddUnique(NewOutput); + } + + // ... then Spline Components (for Curve IN) + for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents) + { + UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value; + if (!CurSplineComp || CurSplineComp->IsPendingKill()) + continue; + + // TODO: Needed? + // Attach the spline to the HAC + CurSplineComp->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + // Editable curve? / Should create an output for it! + if (CurSplineComp->IsEditableOutputCurve()) + { + FHoudiniGeoPartObject CurHGPO = LegacyCurve.Key.ConvertLegacyData(); + + // Look for an output for that HGPO + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(CurHGPO); + } + + // Build an output object id for the editable curve output + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; + EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; + EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; + EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; + + TMap& OutputObjects = NewOutput->GetOutputObjects(); + FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + FoundOutputObject.OutputComponent = CurSplineComp; + + //CurSplineComp->SetHasEditableNodeBuilt(true); + CurSplineComp->SetIsInputCurve(false); + } + else + { + // Input! + // Conversion of the inputs should have done the job already + CurSplineComp->SetIsInputCurve(true); + } + } + + // ... Handles + for (auto& LegacyHandle : Version1CompatibilityHAC->HandleComponents) + { + // TODO: Handles!! + UHoudiniHandleComponent* NewHandle = nullptr; + HandleComponents.Add(NewHandle); + } + + // ... Materials + UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials; + if(LegacyMaterials && !LegacyMaterials->IsPendingKill()) + { + // Assignements: Apply to all outputs since they're not tied to an HGPO... + for (auto& CurOutput : Outputs) + { + TMap& CurrAssign = CurOutput->GetAssignementMaterials(); + for (auto& LegacyMaterial : LegacyMaterials->Assignments) + { + CurrAssign.Add(LegacyMaterial.Key, LegacyMaterial.Value); + } + } + + // Replacements + // Try to find the output matching the HGPO + for (auto& LegacyMaterial : LegacyMaterials->Replacements) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacyMaterial.Key.ConvertLegacyData(); + + TMap& LegacyReplacement = LegacyMaterial.Value; + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + if (bCreatedNew) + continue; + + TMap& CurReplacement = NewOutput->GetReplacementMaterials(); + for (auto& CurLegacyReplacement : LegacyReplacement) + { + CurReplacement.Add(CurLegacyReplacement.Key, CurLegacyReplacement.Value); + } + } + } + + + // ... Bake Name overrides + for (auto& LegacyBakeNameOverride : Version1CompatibilityHAC->BakeNameOverrides) + { + // In Outputs? + } + + // ... then Downstream asset connections (due to Asset inputs) + for (auto& LegacyDownstreamHAC : Version1CompatibilityHAC->DownstreamAssetConnections) + { + //TSet DownstreamHoudiniAssets; + } + + // Then convert all remaing flags and properties + StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = Version1CompatibilityHAC->bGeneratedDoubleSidedGeometry; + StaticMeshGenerationProperties.GeneratedPhysMaterial = Version1CompatibilityHAC->GeneratedPhysMaterial; + StaticMeshGenerationProperties.DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; + StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; + StaticMeshGenerationProperties.GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; + StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier = Version1CompatibilityHAC->GeneratedLpvBiasMultiplier; + StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; + StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; + StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; + StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = Version1CompatibilityHAC->GeneratedStreamingDistanceMultiplier; + //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; + StaticMeshGenerationProperties.GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; + + StaticMeshBuildSettings.DistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; + + BakeFolder.Path = Version1CompatibilityHAC->BakeFolder.ToString(); + TemporaryCookFolder.Path = Version1CompatibilityHAC->TempCookFolder.ToString(); + + ComponentGUID = Version1CompatibilityHAC->ComponentGUID; + + bEnableCooking = Version1CompatibilityHAC->bEnableCooking; + bUploadTransformsToHoudiniEngine = Version1CompatibilityHAC->bUploadTransformsToHoudiniEngine; + bCookOnTransformChange = Version1CompatibilityHAC->bTransformChangeTriggersCooks; + bCookOnParameterChange = true; + //Version1CompatibilityHAC->bCookingTriggersDownstreamCooks; + bCookOnAssetInputCook = true; + bOutputless = false; + bOutputTemplateGeos = false; + bFullyLoaded = Version1CompatibilityHAC->bFullyLoaded; + + //bContainsHoudiniLogoGeometry = Version1CompatibilityHAC->bContainsHoudiniLogoGeometry; + //bIsNativeComponent = Version1CompatibilityHAC->bIsNativeComponent; + //bIsPreviewComponent = Version1CompatibilityHAC->bIsPreviewComponent; + //bLoadedComponent = Version1CompatibilityHAC->bLoadedComponent; + //bIsPlayModeActive_Unused = Version1CompatibilityHAC->bIsPlayModeActive_Unused; + //Version1CompatibilityHAC->bTimeCookInPlaymode_Unused; + //Version1CompatibilityHAC->bUseHoudiniMaterials; + + //Version1CompatibilityHAC->GeneratedGeometryScaleFactor; + //Version1CompatibilityHAC->TransformScaleFactor; + //Version1CompatibilityHAC->PresetBuffer; + //Version1CompatibilityHAC->DefaultPresetBuffer; + //Version1CompatibilityHAC->ParameterByName; + + // Now that we're done, update all the output's types + for (auto& CurOutput : Outputs) + { + CurOutput->UpdateOutputType(); + } + + // + // Clean up the legacy HAC + // + + Version1CompatibilityHAC->Parameters.Empty(); + Version1CompatibilityHAC->Inputs.Empty(); + Version1CompatibilityHAC->StaticMeshes.Empty(); + Version1CompatibilityHAC->LandscapeComponents.Empty(); + Version1CompatibilityHAC->InstanceInputs.Empty(); + Version1CompatibilityHAC->SplineComponents.Empty(); + Version1CompatibilityHAC->HandleComponents.Empty(); + //Version1CompatibilityHAC->HoudiniAssetComponentMaterials.Empty(); + Version1CompatibilityHAC->BakeNameOverrides.Empty(); + Version1CompatibilityHAC->DownstreamAssetConnections.Empty(); + Version1CompatibilityHAC->MarkPendingKill(); + Version1CompatibilityHAC = nullptr; + + return true; +} + + +UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + HoudiniAsset = nullptr; + bCookOnParameterChange = true; + bUploadTransformsToHoudiniEngine = true; + bCookOnTransformChange = false; + //bUseNativeHoudiniMaterials = true; + bCookOnAssetInputCook = true; + + AssetId = -1; + AssetState = EHoudiniAssetState::NewHDA; + AssetStateResult = EHoudiniAssetStateResult::None; + AssetCookCount = 0; + + SubAssetIndex = -1; + + // Make an invalid GUID, since we do not have any cooking requests. + HapiGUID.Invalidate(); + + HapiAssetName = FString(); + + // Create unique component GUID. + ComponentGUID = FGuid::NewGuid(); + + bUploadTransformsToHoudiniEngine = true; + + bHasBeenLoaded = false; + bHasBeenDuplicated = false; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = false; + bEnableCooking = true; + bForceNeedUpdate = false; + bLastCookSuccess = false; + bBlueprintStructureModified = false; + bBlueprintModified = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // Folder used for cooking, the value is initialized by Output Translator + // TemporaryCookFolder.Path = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + + // Folder used for baking this asset's outputs, the value is initialized by Output Translator + // BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + bHasComponentTransformChanged = false; + + bFullyLoaded = false; + + bOutputless = false; + + bOutputTemplateGeos = false; + + PDGAssetLink = nullptr; + + StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; + + bOverrideGlobalProxyStaticMeshSettings = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bEnableProxyStaticMeshOverride = HoudiniRuntimeSettings->bEnableProxyStaticMesh; + bEnableProxyStaticMeshRefinementByTimerOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; + ProxyMeshAutoRefineTimeoutSecondsOverride = HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; + } + else + { + bEnableProxyStaticMeshOverride = false; + bEnableProxyStaticMeshRefinementByTimerOverride = true; + ProxyMeshAutoRefineTimeoutSecondsOverride = 10.0f; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = true; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = true; + } + + bNoProxyMeshNextCookRequested = false; + bBakeAfterNextCook = false; + +#if WITH_EDITORONLY_DATA + bGenerateMenuExpanded = true; + bBakeMenuExpanded = true; + bAssetOptionMenuExpanded = true; + bHelpAndDebugMenuExpanded = true; + + HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; + + bRemoveOutputAfterBake = false; + bRecenterBakedActors = false; + bReplacePreviousBake = false; +#endif + + // + // Set component properties. + // + + Mobility = EComponentMobility::Static; + + SetGenerateOverlapEvents(false); + + // Similar to UMeshComponent. + CastShadow = true; + bUseAsOccluder = true; + bCanEverAffectNavigation = true; + + // This component requires render update. + bNeverNeedsRenderUpdate = false; + + Bounds = FBox(ForceInitToZero); + + LastTickTime = 0.0; + + // Initialize the default SM Build settings with the plugin's settings default values + StaticMeshBuildSettings = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); +} + +UHoudiniAssetComponent::~UHoudiniAssetComponent() +{ + // Unregister ourself so our houdini node can be delete. + + // This gets called in UnRegisterHoudiniComponent, with appropriate checks. Don't call it here. + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); +} + +void UHoudiniAssetComponent::PostInitProperties() +{ + Super::PostInitProperties(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + // Copy default static mesh generation parameters from settings. + StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; + StaticMeshGenerationProperties.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; + StaticMeshGenerationProperties.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; + StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; + StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; + StaticMeshGenerationProperties.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; + StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; + StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; + StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; + StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; + StaticMeshGenerationProperties.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; + } + + // Register ourself to the HER singleton + RegisterHoudiniComponent(this); +} + +UHoudiniAsset * +UHoudiniAssetComponent::GetHoudiniAsset() const +{ + return HoudiniAsset; +} + +FString +UHoudiniAssetComponent::GetDisplayName() const +{ + return GetOwner() ? GetOwner()->GetName() : GetName(); +} + +void +UHoudiniAssetComponent::GetOutputs(TArray& OutOutputs) const +{ + for (UHoudiniOutput* Output : Outputs) + { + OutOutputs.Add(Output); + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh; + } + else + { + return false; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementByTimerEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; + } + else + { + return false; + } + } +} + +float +UHoudiniAssetComponent::GetProxyMeshAutoRefineTimeoutSeconds() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return ProxyMeshAutoRefineTimeoutSecondsOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; + } + else + { + return 5.0f; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; + } + else + { + return false; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; + } + else + { + return false; + } + } +} + + +void +UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) +{ + // Check the asset validity + if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill()) + return; + + // If it is the same asset, do nothing. + if ( InHoudiniAsset == HoudiniAsset ) + return; + + HoudiniAsset = InHoudiniAsset; +} + + +void +UHoudiniAssetComponent::OnHoudiniAssetChanged() +{ + // TODO: clear input/params/outputs? + Parameters.Empty(); + + // The asset has been changed, mark us as needing to be reinstantiated + MarkAsNeedInstantiation(); + + // Force an update on the next tick + bForceNeedUpdate = true; +} + +bool +UHoudiniAssetComponent::NeedUpdateParameters() const +{ + // This is being split into a separate function to that it can + // be called separately for component templates. + + if (!bCookOnParameterChange) + return false; + + // Go through all our parameters, return true if they have been updated + for (auto CurrentParm : Parameters) + { + if (!CurrentParm || CurrentParm->IsPendingKill()) + continue; + + if (!CurrentParm->HasChanged()) + continue; + + // See if the parameter doesn't require an update + // (because it has failed to upload previously or has been loaded) + if (!CurrentParm->NeedsToTriggerUpdate()) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateParameters()] Parameters need update for component: %s"), *(GetPathName())); + return true; + } + + return false; +} + +bool +UHoudiniAssetComponent::NeedUpdateInputs() const +{ + // Go through all our inputs, return true if they have been updated + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (!CurrentInput->HasChanged()) + continue; + + // See if the input doesn't require an update + // (because it has failed to upload previously or has been loaded) + if (!CurrentInput->NeedsToTriggerUpdate()) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateInputs()] Inputs need update for component: %s"), *(GetPathName())); + return true; + } + + return false; +} + +bool +UHoudiniAssetComponent::HasPreviousBakeOutput() const +{ + // Look for any bake output objects in the output array + for (const UHoudiniOutput* Output : Outputs) + { + if (!IsValid(Output)) + continue; + + if (BakedOutputs.Num() == 0) + return false; + + for (const FHoudiniBakedOutput& BakedOutput : BakedOutputs) + { + if (BakedOutput.BakedOutputObjects.Num() > 0) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::NeedUpdate() const +{ + if (AssetState != DebugLastAssetState) + { + DebugLastAssetState = AssetState; + } + + // It is important to check this when dealing with Blueprints since the + // preview components start receiving events from the template component + // before the preview component have finished initialization. + if (!IsFullyLoaded()) + return false; + + // We must have a valid asset + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return false; + + if (bForceNeedUpdate) + return true; + + // If we don't want to cook on parameter/input change dont bother looking for updates + if (!bCookOnParameterChange && !bRecookRequested && !bRebuildRequested) + return false; + + // Check if the HAC's transform has changed and transform triggers cook is enabled + if (bCookOnTransformChange && bHasComponentTransformChanged) + return true; + + if (NeedUpdateParameters()) + return true; + + if (NeedUpdateInputs()) + return true; + + // Go through all outputs, filter the editable nodes. Return true if they have been updated. + for (auto CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + // We only care about editable outputs + if (!CurrentOutput->IsEditableNode()) + continue; + + // Trigger an update if the output object is marked as modified by user. + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + for (auto& NextPair : OutputObjects) + { + // For now, only editable curves can trigger update + UHoudiniSplineComponent* HoudiniSplineComponent = Cast(NextPair.Value.OutputComponent); + if (!HoudiniSplineComponent) + continue; + + // Output curves cant trigger an update! + if (HoudiniSplineComponent->bIsOutputCurve) + continue; + + if (HoudiniSplineComponent->NeedsToTriggerUpdate()) + return true; + } + } + + return false; +} + +// Indicates if any of the HAC's output components needs to be updated (no recook needed) +bool +UHoudiniAssetComponent::NeedOutputUpdate() const +{ + // Go through all outputs + for (auto CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs()) + { + if (InstOutput.Value.bChanged) + return true; + } + } + + return false; +} + +bool UHoudiniAssetComponent::NeedBlueprintStructureUpdate() const +{ + // TODO: Add similar flags to inputs, parametsr + return bBlueprintStructureModified; +} + +bool UHoudiniAssetComponent::NeedBlueprintUpdate() const +{ + // TODO: Add similar flags to inputs, parametsr + return bBlueprintModified; +} + +bool +UHoudiniAssetComponent::NotifyCookedToDownstreamAssets() +{ + // Before notifying, clean up our downstream assets + // - check that they are still valid + // - check that we are still connected to one of its asset input + // - check that the asset as the CookOnAssetInputCook trigger enabled + TArray DownstreamToDelete; + for(auto& CurrentDownstreamHAC : DownstreamHoudiniAssets) + { + // Remove the downstream connection by default, + // unless we actually were properly connected to one of this HDa's input. + bool bRemoveDownstream = true; + if (CurrentDownstreamHAC && !CurrentDownstreamHAC->IsPendingKill()) + { + // Go through the HAC's input + for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs) + { + if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill()) + continue; + + EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType(); + if (CurrentDownstreamInputType != EHoudiniInputType::Asset + && CurrentDownstreamInputType != EHoudiniInputType::World) + continue; + + if (!CurrentDownstreamInput->ContainsInputObject(this, CurrentDownstreamInputType)) + continue; + + if (CurrentDownstreamHAC->bCookOnAssetInputCook) + { + // Mark that HAC's input has changed + CurrentDownstreamInput->MarkChanged(true); + } + bRemoveDownstream = false; + } + } + + if (bRemoveDownstream) + { + DownstreamToDelete.Add(CurrentDownstreamHAC); + } + } + + for (auto ToDelete : DownstreamToDelete) + { + DownstreamHoudiniAssets.Remove(ToDelete); + } + + return true; +} + +bool +UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() +{ + for (auto& CurrentInput : Inputs) + { + EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World) + continue; + + TArray* ObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInputType); + if (!ObjectArray) + continue; + + for (auto& CurrentInputObject : (*ObjectArray)) + { + // Get the input HDA + UHoudiniAssetComponent* InputHAC = CurrentInputObject + ? Cast(CurrentInputObject->GetObject()) + : nullptr; + + if (!InputHAC) + continue; + + // If the input HDA needs to be instantiated, force him to instantiate + // if the input HDA is in any other state than None, we need to wait for him + // to finish whatever it's doing + if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) + { + // Tell the input HAC to instantiate + InputHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + + // We need to wait + return true; + } + else if (InputHAC->GetAssetState() != EHoudiniAssetState::None) + { + // We need to wait + return true; + } + } + } + + return false; +} + +void +UHoudiniAssetComponent::BeginDestroy() +{ + if (CanDeleteHoudiniNodes()) + { + } + + // Gets called through UnRegisterHoudiniComponent(). + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + + // Unregister ourself so our houdini node can be deleted + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + Super::BeginDestroy(); +} + +void +UHoudiniAssetComponent::MarkAsNeedCook() +{ + // Force the asset state to NeedCook + //AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = true; + bRebuildRequested = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/trigger update + for (auto CurrentParam : Parameters) + { + if (!IsValid(CurrentParam)) + continue; + + // Do not trigger parameter update for Button/Button strip when recooking + // As we don't want to trigger the buttons + if (CurrentParam->IsA() || CurrentParam->IsA()) + continue; + + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(true); + } + + // We need to mark all of our editable curves as changed + for (auto Output : Outputs) + { + if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) + continue; + + for (auto& OutputObjectEntry : Output->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; + if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + continue; + + UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + continue; + + // This sets bHasChanged and bNeedsToTriggerUpdate + SplineComponent->MarkChanged(true); + } + } + + // We need to mark all our inputs as changed/trigger update + for (auto CurrentInput : Inputs) + { + if (!IsValid(CurrentInput)) + continue; + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(true); + CurrentInput->MarkDataUploadNeeded(true); + + // In addition to marking the input as changed/need update, we also need to make sure that any changes on the + // Unreal side have been recorded for the input before sending to Houdini. For that we also mark each input + // object as changed/need update and explicitly call the Update function on each input object. For example, for + // input actors this would recreate the Houdini input actor components from the actor's components, picking up + // any new components since the last call to Update. + TArray* InputObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); + if (InputObjectArray && InputObjectArray->Num() > 0) + { + for (auto CurrentInputObject : *InputObjectArray) + { + if (!IsValid(CurrentInputObject)) + continue; + + UObject* const Object = CurrentInputObject->GetObject(); + if (IsValid(Object)) + CurrentInputObject->Update(Object); + + CurrentInputObject->MarkChanged(true); + CurrentInputObject->SetNeedsToTriggerUpdate(true); + CurrentInputObject->MarkTransformChanged(true); + } + } + } + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +void +UHoudiniAssetComponent::MarkAsNeedRebuild() +{ + // Invalidate the asset ID + //AssetId = -1; + + // Force the asset state to NeedRebuild + SetAssetState(EHoudiniAssetState::NeedRebuild); + AssetStateResult = EHoudiniAssetStateResult::None; + + // Reset some of the asset's flag + //AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = true; + bFullyLoaded = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/trigger update + for (auto CurrentParam : Parameters) + { + if (!IsValid(CurrentParam)) + continue; + + // Do not trigger parameter update for Button/Button strip when rebuilding + // As we don't want to trigger the buttons + if (CurrentParam->IsA() || CurrentParam->IsA()) + continue; + + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(true); + } + + // We need to mark all of our editable curves as changed + for (auto Output : Outputs) + { + if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) + continue; + + for (auto& OutputObjectEntry : Output->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; + if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + continue; + + UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + continue; + + // This sets bHasChanged and bNeedsToTriggerUpdate + SplineComponent->MarkChanged(true); + } + } + + // We need to mark all our inputs as changed/trigger update + for (auto CurrentInput : Inputs) + { + if (!IsValid(CurrentInput)) + continue; + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(true); + CurrentInput->MarkDataUploadNeeded(true); + } + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +// Marks the asset as needing to be instantiated +void +UHoudiniAssetComponent::MarkAsNeedInstantiation() +{ + // Invalidate the asset ID + AssetId = -1; + + if (Parameters.Num() <= 0 && Inputs.Num() <= 0 && Outputs.Num() <= 0) + { + // The asset has no parameters or inputs. + // This likely indicates it has never cooked/been instantiated. + // Set its state to NewHDA to force its instantiation + // so that we can have its parameters/input interface + SetAssetState(EHoudiniAssetState::NewHDA); + } + else + { + // The asset has cooked before since we have a parameter/input interface + // Set its state to need instantiation so that the asset is instantiated + // after being modified + SetAssetState(EHoudiniAssetState::NeedInstantiation); + } + + AssetStateResult = EHoudiniAssetStateResult::None; + + // Reset some of the asset's flag + AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = false; + bFullyLoaded = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/not triggering update + for (auto CurrentParam : Parameters) + { + if (CurrentParam) + { + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(false); + } + } + + // We need to mark all our inputs as changed/not triggering update + for (auto CurrentInput : Inputs) + { + if (CurrentInput) + { + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(false); + CurrentInput->MarkDataUploadNeeded(true); + } + } + + /*if (!CanInstantiateAsset()) + { + AssetState = EHoudiniAssetState::None; + AssetStateResult = EHoudiniAssetStateResult::None; + }*/ + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +void UHoudiniAssetComponent::MarkAsBlueprintStructureModified() +{ + bBlueprintStructureModified = true; +} + +void UHoudiniAssetComponent::MarkAsBlueprintModified() +{ + bBlueprintModified = true; +} + +void +UHoudiniAssetComponent::PostLoad() +{ + Super::PostLoad(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + bool bAutomaticLegacyHDARebuild = HoudiniRuntimeSettings->bAutomaticLegacyHDARebuild; + + // Legacy serialization: either try to convert or skip depending the setting value + if (bEnableBackwardCompatibility && Version1CompatibilityHAC != nullptr) + { + // If we have deserialized legacy v1 data, attempt to convert it now + ConvertLegacyData(); + + if (bAutomaticLegacyHDARebuild) + MarkAsNeedRebuild(); + else + MarkAsNeedInstantiation(); + } + else + { + // Normal v2 objet, mark as need instantiation + MarkAsNeedInstantiation(); + } + + // Component has been loaded, not duplicated + bHasBeenDuplicated = false; + + // We need to register ourself + RegisterHoudiniComponent(this); + + // Register our PDG Asset link if we have any + + // !!! Do not update rendering while loading, do it when setting up the render state + // UpdateRenderingInformation(); +} + +void +UHoudiniAssetComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context) +{ + UpdateRenderingInformation(); + Super::CreateRenderState_Concurrent(Context); +} + +void +UHoudiniAssetComponent::PostEditImport() +{ + Super::PostEditImport(); + + MarkAsNeedInstantiation(); + + // Component has been duplicated, not loaded + // We do need the loaded flag to reapply parameters, inputs + // and properly update some of the output objects + bHasBeenDuplicated = true; + + //RemoveAllAttachedComponents(); + + AssetState = EHoudiniAssetState::PreInstantiation; + AssetStateResult = EHoudiniAssetStateResult::None; + + // TODO? + // REGISTER? +} + +void +UHoudiniAssetComponent::UpdatePostDuplicate() +{ + // TODO: + // - Keep the output objects/components (remove duplicatetransient on the output object uproperties) + // - Duplicate created objects (ie SM) and materials + // - Update the output components to use these instead + // This should remove the need for a cook on duplicate + + // For now, we simply clean some of the HAC's component manually + const TArray Children = GetAttachChildren(); + + for (auto & NextChild : Children) + { + if (!NextChild || NextChild->IsPendingKill()) + continue; + + USceneComponent * ComponentToRemove = nullptr; + if (NextChild->IsA()) + { + ComponentToRemove = NextChild; + } + else if (NextChild->IsA()) + { + ComponentToRemove = NextChild; + } + /* do not destroy attached duplicated editable curves, they are needed to restore editable curves + else if (NextChild->IsA()) + { + // Remove duplicated editable curve output's Houdini Spline Component, since they will be re-built at duplication. + UHoudiniSplineComponent * HoudiniSplineComponent = Cast(NextChild); + if (HoudiniSplineComponent && HoudiniSplineComponent->IsEditableOutputCurve()) + ComponentToRemove = NextChild; + } + */ + if (ComponentToRemove) + { + ComponentToRemove->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + ComponentToRemove->UnregisterComponent(); + ComponentToRemove->DestroyComponent(); + } + } + + // if there is an associated PDG asset link, call its UpdatePostDuplicate to cleanup references to + // to the original instance's PDG output actors + if (IsValid(PDGAssetLink)) + { + PDGAssetLink->UpdatePostDuplicate(); + } + + SetHasBeenDuplicated(false); +} + +bool UHoudiniAssetComponent::IsInputTypeSupported(EHoudiniInputType InType) const +{ + return true; +} + +bool UHoudiniAssetComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const +{ + return true; +} + +bool +UHoudiniAssetComponent::IsPreview() const +{ + return bCachedIsPreview; +} + +bool UHoudiniAssetComponent::IsValidComponent() const +{ + return true; +} + +void UHoudiniAssetComponent::OnFullyLoaded() +{ + bFullyLoaded = true; +} + + +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); + } + */ +} + +void +UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + + if (CanDeleteHoudiniNodes()) + { + } + + // Unregister ourself so our houdini node can be deleted + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + HoudiniAsset = nullptr; + + // Clear Parameters + for (UHoudiniParameter*& CurrentParm : Parameters) + { + if (CurrentParm && !CurrentParm->IsPendingKill()) + { + CurrentParm->ConditionalBeginDestroy(); + } + else if (GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE) + { + // TODO unneeded log? + // Avoid spamming that error when leaving PIE mode + HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } + + CurrentParm = nullptr; + } + + Parameters.Empty(); + + // Clear Inputs + for (UHoudiniInput*& CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + // Destroy connected Houdini asset. + CurrentInput->ConditionalBeginDestroy(); + CurrentInput = nullptr; + } + + Inputs.Empty(); + + // Clear Output + for (UHoudiniOutput*& CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + // Destroy all Houdini created socket actors. + TArray & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors(); + for (auto & CurCreatedActor : CurCreatedSocketActors) + { + if (!CurCreatedActor || CurCreatedActor->IsPendingKill()) + continue; + + CurCreatedActor->Destroy(); + } + CurCreatedSocketActors.Empty(); + + // Detach all Houdini attached socket actors + TArray & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors(); + for (auto & CurAttachedSocketActor : CurAttachedSocketActors) + { + if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill()) + continue; + + CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + } + CurAttachedSocketActors.Empty(); + +#if WITH_EDITOR + // Clean up foliages instances + for (auto& CurrentOutputObject : CurrentOutput->GetOutputObjects()) + { + // Foliage instancers store a HISMC in the components + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentOutputObject.Value.OutputComponent); + if (!FoliageHISMC) + continue; + + UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + continue; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(FoliageHISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + continue; + + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + if (!FoliageType || FoliageType->IsPendingKill()) + continue; + + if (IsInGameThread() && IsGarbageCollecting()) + { + // TODO: ?? + // Calling DeleteInstancesForComponent during GC will cause unreal to crash... + HOUDINI_LOG_WARNING(TEXT("%s: Unable to clear foliage instances because of GC"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } + else + { + // Clean up the instances generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(this, FoliageType); + } + + if (FoliageHISMC->GetInstanceCount() > 0) + { + // If the component still has instances left after the cleanup, + // make sure that we dont delete it, as the leftover instances are likely hand-placed + CurrentOutputObject.Value.OutputComponent = nullptr; + } + else + { + // Remove the foliage type if it doesn't have any more instances + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + } + } +#endif + + CurrentOutput->Clear(); + // Destroy connected Houdini asset. + CurrentOutput->ConditionalBeginDestroy(); + CurrentOutput = nullptr; + } + + Outputs.Empty(); + + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + // Unregister ourself so our houdini node can be delete. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); + + + // Clear all TOP data and temporary geo/objects from the PDG asset link (if valid) + if (IsValid(PDGAssetLink)) + { +#if WITH_EDITOR + const UWorld* const World = GetWorld(); + if (IsValid(World)) + { + // Only do this for editor worlds, only interactively (not during engine shutdown or garbage collection) + if (World->WorldType == EWorldType::Editor && GIsRunning && !GIsGarbageCollecting) + { + // In case we are recording a transaction (undo, for example) notify that the object will be + // modified. + PDGAssetLink->Modify(); + PDGAssetLink->ClearAllTOPData(); + } + } +#endif + } + + Super::OnComponentDestroyed(bDestroyingHierarchy); +} + +void UHoudiniAssetComponent::RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) +{ + // Registration of this component is wrapped in this virtual function to allow + // derived classed to override this behaviour. + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(InComponent); +} + +void +UHoudiniAssetComponent::OnRegister() +{ + Super::OnRegister(); + + // NOTE: Wait until HoudiniEngineTick() before deciding to mark this object as fully loaded + // since preview components need to wait for component templates to finish their initialization + // before being able to perform state transfers. + + /* + // 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(); + } + } + */ + + // Let TickInitialization() take care of manipulating the bFullyLoaded state. + //bFullyLoaded = true; + + //// If we're constructing editable components in the SCS editor, set the component instance corresponding to this node for editing purposes + // + //USimpleConstructionScript* SCS = GetSCS(); + //if (SCS == nullptr) + //{ + // bFullyLoaded = true; + //} + //else + //{ + // if(SCS->IsConstructingEditorComponents()) + // { + // // We're not fully loaded yet. We're expecting + // } + // else + // { + // bFullyLoaded = true; + // } + //} + +} + +UHoudiniParameter* +UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) +{ + if (!InOtherParam || InOtherParam->IsPendingKill()) + return nullptr; + + for (auto CurrentParam : Parameters) + { + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + if (CurrentParam->Matches(*InOtherParam)) + return CurrentParam; + } + + return nullptr; +} + +UHoudiniInput* +UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) +{ + if (!InOtherInput || InOtherInput->IsPendingKill()) + return nullptr; + + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->Matches(*InOtherInput)) + return CurrentInput; + } + + return nullptr; +} + +UHoudiniHandleComponent* +UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle) +{ + if (!InOtherHandle || InOtherHandle->IsPendingKill()) + return nullptr; + + for (auto CurrentHandle : HandleComponents) + { + if (!CurrentHandle || CurrentHandle->IsPendingKill()) + continue; + + if (CurrentHandle->Matches(*InOtherHandle)) + return CurrentHandle; + } + + return nullptr; +} + +UHoudiniParameter* +UHoudiniAssetComponent::FindParameterByName(const FString& InParamName) +{ + for (auto CurrentParam : Parameters) + { + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + if (CurrentParam->GetParameterName().Equals(InParamName)) + return CurrentParam; + } + + return nullptr; +} + + +void +UHoudiniAssetComponent::OnChildAttached(USceneComponent* ChildComponent) +{ + Super::OnChildAttached(ChildComponent); + + // ... Do corresponding things for other houdini component types. + // ... +} + + +void +UHoudiniAssetComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + + SetHasComponentTransformChanged(true); +} + +void UHoudiniAssetComponent::HoudiniEngineTick() +{ + if (!IsFullyLoaded()) + { + OnFullyLoaded(); + } +} + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + if (!Property) + return; + + FName PropertyName = Property->GetFName(); + + // Changing the Houdini Asset? + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, HoudiniAsset)) + { + OnHoudiniAssetChanged(); + } + else if (PropertyName == GetRelativeLocationPropertyName() + || PropertyName == GetRelativeRotationPropertyName() + || PropertyName == GetRelativeScale3DPropertyName()) + { + SetHasComponentTransformChanged(true); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bOverrideGlobalProxyStaticMeshSettings) + || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bEnableProxyStaticMeshRefinementByTimerOverride) + || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, ProxyMeshAutoRefineTimeoutSecondsOverride)) + { + ClearRefineMeshesTimer(); + // Reset the timer + // SetRefineMeshesTimer will check the relevant settings and only set the timer if enabled via settings + SetRefineMeshesTimer(); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, 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); + } + } + else if (PropertyName == TEXT("bVisible")) + { + // Visibility has changed, propagate it to children. + SetVisibility(IsVisible(), true); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bHiddenInGame)) + { + // Visibility has changed, propagate it to children. + SetHiddenInGame(bHiddenInGame, true); + } + else + { + // TODO: + // Propagate properties (mobility/visibility etc.. to children components) + // Look in v1 for: if (Property->HasMetaData(TEXT("Category"))) {} and HOUDINI_UPDATE_ALL_CHILD_COMPONENTS + } + + 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 (UHoudiniOutput* CurOutput : Outputs) + { + if (!CurOutput) + continue; + + for (auto& Pair : CurOutput->GetOutputObjects()) + { + UStaticMesh* StaticMesh = Cast(Pair.Value.OutputObject); + if (!StaticMesh || StaticMesh->IsPendingKill()) + continue; + + SetStaticMeshGenerationProperties(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 ReregisterComponents; + { + TArray 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); + } + } + } +} +#endif + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::PostEditUndo() +{ + Super::PostEditUndo(); + + if (!IsPendingKill()) + { + // Make sure we are registered with the HER singleton + // We could be undoing a HoudiniActor delete + if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(this)) + { + MarkAsNeedInstantiation(); + + // Component has been loaded, not duplicated + bHasBeenDuplicated = false; + + RegisterHoudiniComponent(this); + } + } +} + +#endif + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::OnActorMoved(AActor* Actor) +{ + if (GetOwner() != Actor) + return; + + SetHasComponentTransformChanged(true); +} +#endif + +void +UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged) +{ + // Only update the value if we're fully loaded + // This avoid triggering a recook when loading a level + if(bFullyLoaded) + bHasComponentTransformChanged = InHasChanged; +} + +void +UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // Check the object validity + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // If it is the same object, do nothing. + if (InPDGAssetLink == PDGAssetLink) + return; + + PDGAssetLink = InPDGAssetLink; +} + + +FBoxSphereBounds +UHoudiniAssetComponent::CalcBounds(const FTransform & LocalToWorld) const +{ + FBoxSphereBounds LocalBounds; + FBox BoundingBox = GetAssetBounds(nullptr, false); + 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; +} + + +FBox +UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const +{ + FBox BoxBounds(ForceInitToZero); + + // Query the bounds for all output objects + for (auto & CurOutput : Outputs) + { + if (!CurOutput || CurOutput->IsPendingKill()) + continue; + + BoxBounds += CurOutput->GetBounds(); + } + + // Query the bounds for all our inputs + for (auto & CurInput : Inputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + BoxBounds += CurInput->GetBounds(); + } + + // Query the bounds for all input parameters + for (auto & CurParam : Parameters) + { + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (CurParam->GetParameterType() != EHoudiniParameterType::Input) + continue; + + UHoudiniParameterOperatorPath* InputParam = Cast(CurParam); + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (!InputParam->HoudiniInput.IsValid()) + continue; + + BoxBounds += InputParam->HoudiniInput.Get()->GetBounds(); + } + + // Query the bounds for all our Houdini handles + for (auto & CurHandleComp : HandleComponents) + { + if (!CurHandleComp || CurHandleComp->IsPendingKill()) + continue; + + BoxBounds += CurHandleComp->GetBounds(); + } + + // Also scan all our decendants for SMC bounds not just top-level children + // ( split mesh instances' mesh bounds were not gathered proiperly ) + TArray 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(pChild)) + { + if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + continue; + + FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); + if (StaticMeshBounds.IsValid) + BoxBounds += StaticMeshBounds; + } + } + + // If nothing was found, init with the asset's location + if (BoxBounds.GetVolume() == 0.0f) + BoxBounds += GetComponentLocation(); + + return BoxBounds; +} + +void +UHoudiniAssetComponent::ClearRefineMeshesTimer() +{ + UWorld *World = GetWorld(); + if (!World) + { + //HOUDINI_LOG_ERROR(TEXT("Cannot ClearRefineMeshesTimer, World is nullptr!")); + return; + } + + World->GetTimerManager().ClearTimer(RefineMeshesTimer); +} + +void +UHoudiniAssetComponent::SetRefineMeshesTimer() +{ + UWorld *World = GetWorld(); + if (!World) + { + HOUDINI_LOG_ERROR(TEXT("Cannot SetRefineMeshesTimer, World is nullptr!")); + return; + } + + // Check if timer-based proxy mesh refinement is enable for this component + const bool bEnableTimer = IsProxyStaticMeshRefinementByTimerEnabled(); + const float TimeSeconds = GetProxyMeshAutoRefineTimeoutSeconds(); + if (bEnableTimer) + { + World->GetTimerManager().SetTimer(RefineMeshesTimer, this, &UHoudiniAssetComponent::OnRefineMeshesTimerFired, 1.0f, false, TimeSeconds); + } + else + { + World->GetTimerManager().ClearTimer(RefineMeshesTimer); + } +} + +void +UHoudiniAssetComponent::OnRefineMeshesTimerFired() +{ + HOUDINI_LOG_MESSAGE(TEXT("UHoudiniAssetComponent::OnRefineMeshesTimerFired()")); + if (OnRefineMeshesTimerDelegate.IsBound()) + { + OnRefineMeshesTimerDelegate.Broadcast(this); + } +} + +bool +UHoudiniAssetComponent::HasAnyCurrentProxyOutput() const +{ + for (const UHoudiniOutput *Output : Outputs) + { + if (Output->HasAnyCurrentProxy()) + { + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasAnyProxyOutput() const +{ + for (const UHoudiniOutput *Output : Outputs) + { + if (Output->HasAnyProxy()) + { + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasAnyOutputComponent() const +{ + for (UHoudiniOutput *Output : Outputs) + { + for(auto& CurrentOutputObject : Output->GetOutputObjects()) + { + if(CurrentOutputObject.Value.OutputComponent) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasOutputObject(UObject* InOutputObjectToFind) const +{ + for (const auto& CurOutput : Outputs) + { + for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) + { + if (CurOutputObject.Value.OutputObject == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.OutputComponent == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.ProxyObject == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.ProxyComponent == InOutputObjectToFind) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const +{ + // Get the state of the asset and check if it is pre-cook, cooked, pending delete/rebuild or invalid + bOutNeedsRebuildOrDelete = false; + bOutInvalidState = false; + switch (AssetState) + { + case EHoudiniAssetState::NewHDA: + case EHoudiniAssetState::NeedInstantiation: + case EHoudiniAssetState::PreInstantiation: + case EHoudiniAssetState::Instantiating: + case EHoudiniAssetState::PreCook: + case EHoudiniAssetState::Cooking: + case EHoudiniAssetState::PostCook: + case EHoudiniAssetState::PreProcess: + case EHoudiniAssetState::Processing: + return false; + break; + case EHoudiniAssetState::None: + return true; + break; + case EHoudiniAssetState::NeedRebuild: + case EHoudiniAssetState::NeedDelete: + case EHoudiniAssetState::Deleting: + bOutNeedsRebuildOrDelete = true; + break; + default: + bOutInvalidState = true; + break; + } + + return false; +} + +void +UHoudiniAssetComponent::SetInputPresets(const TMap& InPresets) +{ + // Set the input preset for this HAC +#if WITH_EDITOR + InputPresets = InPresets; +#endif +} + + +void +UHoudiniAssetComponent::ApplyInputPresets() +{ + if (InputPresets.Num() <= 0) + return; + +#if WITH_EDITOR + // Ignore inputs that have been preset to curve + TArray InputArray; + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetInputType() != EHoudiniInputType::Curve) + InputArray.Add(CurrentInput); + } + + // Try to apply the supplied Object to the Input + for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset) + { + UObject * Object = IterToolPreset.Key(); + if (!Object || Object->IsPendingKill()) + continue; + + int32 InputNumber = IterToolPreset.Value(); + if (!InputArray.IsValidIndex(InputNumber)) + continue; + + // If the object is a landscape, add a new landscape input + if (Object->IsA()) + { + // selecting a landscape + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Landscape); + if (InsertNum == 0) + { + // Landscape inputs only support one object! + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Landscape, InsertNum, Object); + } + } + + // If the object is an actor, add a new world input + if (Object->IsA()) + { + // selecting an actor + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::World); + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::World, InsertNum, Object); + } + + // If the object is a static mesh, add a new geometry input (TODO: or BP ? ) + if (Object->IsA()) + { + // selecting a Staticn Mesh + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Geometry, InsertNum, Object); + } + + if (Object->IsA()) + { + // selecting a Houdini Asset + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Asset); + if (InsertNum == 0) + { + // Assert inputs only support one object! + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Asset, InsertNum, Object); + } + } + } + + // The input objects have been set, now change the input type + bool bBPStructureModified = false; + for (auto CurrentInput : Inputs) + { + int32 NumGeo = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + int32 NumAsset = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Asset); + int32 NumWorld = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::World); + int32 NumLandscape = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Landscape); + + EHoudiniInputType NewInputType = EHoudiniInputType::Invalid; + if (NumLandscape > 0 && NumLandscape >= NumGeo && NumLandscape >= NumAsset && NumLandscape >= NumWorld) + NewInputType = EHoudiniInputType::Landscape; + else if (NumWorld > 0 && NumWorld >= NumGeo && NumWorld >= NumAsset && NumWorld >= NumLandscape) + NewInputType = EHoudiniInputType::World; + else if (NumAsset > 0 && NumAsset >= NumGeo && NumAsset >= NumWorld && NumAsset >= NumLandscape) + NewInputType = EHoudiniInputType::Asset; + else if (NumGeo > 0 && NumGeo >= NumAsset && NumGeo >= NumWorld && NumGeo >= NumLandscape) + NewInputType = EHoudiniInputType::Geometry; + + if (NewInputType == EHoudiniInputType::Invalid) + continue; + + // Change the input type, unless if it was preset to a different type and we have object for the preset type + if (CurrentInput->GetInputType() == EHoudiniInputType::Geometry && NewInputType != EHoudiniInputType::Geometry) + { + CurrentInput->SetInputType(NewInputType, bBPStructureModified); + } + else + { + // Input type was preset, only change if that type is empty + if(CurrentInput->GetNumberOfInputObjects() <= 0) + CurrentInput->SetInputType(NewInputType, bBPStructureModified); + } + } + if (bBPStructureModified) + { + MarkAsBlueprintStructureModified(); + } +#endif + + // Discard the tool presets after their first setup + InputPresets.Empty(); +} + + +bool +UHoudiniAssetComponent::IsComponentValid() const +{ + if (!IsValidLowLevel()) + return false; + + if (IsTemplate()) + return false; + + if (IsPendingKillOrUnreachable()) + return false; + + if (!GetOuter()) //|| !GetOuter()->GetLevel() ) + return false; + + return true; +} + +bool +UHoudiniAssetComponent::IsInstantiatingOrCooking() const +{ + return HapiGUID.IsValid(); +} + + +void +UHoudiniAssetComponent::SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const +{ +#if WITH_EDITOR + if (!InStaticMesh) + return; + + // Make sure static mesh has a new lighting guid. + InStaticMesh->LightingGuid = FGuid::NewGuid(); + InStaticMesh->LODGroup = NAME_None; + + // Set resolution of lightmap. + InStaticMesh->LightMapResolution = StaticMeshGenerationProperties.GeneratedLightMapResolution; + + // Set Bias multiplier for Light Propagation Volume lighting. + InStaticMesh->LpvBiasMultiplier = StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier; + + // Set the global light map coordinate index if it looks valid + if (InStaticMesh->RenderData.IsValid() && InStaticMesh->RenderData->LODResources.Num() > 0) + { + int32 NumUVs = InStaticMesh->RenderData->LODResources[0].GetNumTexCoords(); + if (NumUVs > StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex) + { + InStaticMesh->LightMapCoordinateIndex = StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex; + } + } + + // TODO + // Set method for LOD texture factor computation. + //InStaticMesh->bUseMaximumStreamingTexelRatio = StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; + // Set distance where textures using UV 0 are streamed in/out. - GOES ON COMPONENT + // InStaticMesh->StreamingDistanceMultiplier = StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; + + // Add user data. + for (int32 AssetUserDataIdx = 0; AssetUserDataIdx < StaticMeshGenerationProperties.GeneratedAssetUserData.Num(); AssetUserDataIdx++) + InStaticMesh->AddAssetUserData(StaticMeshGenerationProperties.GeneratedAssetUserData[AssetUserDataIdx]); + + // + if (!InStaticMesh->BodySetup) + InStaticMesh->CreateBodySetup(); + + UBodySetup* BodySetup = InStaticMesh->BodySetup; + if (!InStaticMesh->BodySetup) + return; + + // Set flag whether physics triangle mesh will use double sided faces when doing scene queries. + BodySetup->bDoubleSidedGeometry = StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry; + + // Assign physical material for simple collision. + BodySetup->PhysMaterial = StaticMeshGenerationProperties.GeneratedPhysMaterial; + + BodySetup->DefaultInstance.CopyBodyInstancePropertiesFrom(&StaticMeshGenerationProperties.DefaultBodyInstance); + + // Assign collision trace behavior. + BodySetup->CollisionTraceFlag = StaticMeshGenerationProperties.GeneratedCollisionTraceFlag; + + // Assign walkable slope behavior. + BodySetup->WalkableSlopeOverride = StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride; + + // We want to use all of geometry for collision detection purposes. + BodySetup->bMeshCollideAll = true; + +#endif +} + + +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 LocalAttachChildren; + GetChildrenComponents(true, LocalAttachChildren); + for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) + { + USceneComponent * SceneComponent = *Iter; + if (IsValid(SceneComponent)) + SceneComponent->RecreatePhysicsState(); + } + + // !!! Do not call UpdateBounds() here as this could cause + // a loading loop in post load on game builds! +} + + +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(&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::SetAssetState(EHoudiniAssetState InNewState) +{ + const EHoudiniAssetState OldState = AssetState; + AssetState = InNewState; + + HandleOnHoudiniAssetStateChange(this, OldState, InNewState); +} + +void +UHoudiniAssetComponent::HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) +{ + IHoudiniAssetStateEvents::HandleOnHoudiniAssetStateChange(InHoudiniAssetContext, InFromState, InToState); + + if (InFromState == InToState) + return; + + if (this != InHoudiniAssetContext) + return; + + FOnAssetStateChangeDelegate& StateChangeDelegate = GetOnAssetStateChangeDelegate(); + if (StateChangeDelegate.IsBound()) + StateChangeDelegate.Broadcast(this, InFromState, InToState); + + if (InToState == EHoudiniAssetState::PostCook) + { + HandleOnPostCook(); + } + +} + +void +UHoudiniAssetComponent::HandleOnPostCook() +{ + if (OnPostCookDelegate.IsBound()) + OnPostCookDelegate.Broadcast(this, bLastCookSuccess); +} + +void +UHoudiniAssetComponent::HandleOnPostBake(bool bInSuccess) +{ + if (OnPostBakeDelegate.IsBound()) + OnPostBakeDelegate.Broadcast(this, bInSuccess); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h index f64504a82..7f3940995 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h @@ -1,759 +1,770 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniOutput.h" -#include "HoudiniInputTypes.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Engine/EngineTypes.h" -#include "Components/PrimitiveComponent.h" -#include "Components/SceneComponent.h" - -#include "HoudiniAssetComponent.generated.h" - -class UHoudiniAsset; -class UHoudiniParameter; -class UHoudiniInput; -class UHoudiniOutput; -class UHoudiniHandleComponent; -class UHoudiniPDGAssetLink; -class UHoudiniAssetComponent_V1; - -UENUM() -enum class EHoudiniAssetState : uint8 -{ - // Loaded / Duplicated HDA, - // Will need to be instantiated upon change/update - NeedInstantiation, - - // Newly created HDA, needs to be instantiated immediately - PreInstantiation, - - // Instantiating task in progress - Instantiating, - - // Instantiated HDA, needs to be cooked immediately - PreCook, - - // Cooking task in progress - Cooking, - - // Cooking has finished - PostCook, - - // Cooked HDA, needs to be processed immediately - PreProcess, - - // Processing task in progress - Processing, - - // Processed / Updated HDA - // Will need to be cooked upon change/update - None, - - // Asset needs to be rebuilt (Deleted/Instantiated/Cooked) - NeedRebuild, - - // Asset needs to be deleted - NeedDelete, - - // Deleting - Deleting, - - // Process component template. This is ticking has very limited - // functionality, typically limited to checking for parameter updates - // in order to trigger PostEditChange() to run construction scripts again. - ProcessTemplate, -}; - -UENUM() -enum class EHoudiniAssetStateResult : uint8 -{ - None, - Working, - Success, - FinishedWithError, - FinishedWithFatalError, - Aborted -}; - -UENUM() -enum class EHoudiniStaticMeshMethod : uint8 -{ - // Static Meshes will be generated by using Raw Meshes. - RawMesh, - // Static Meshes will be generated by using Mesh Descriptions. - FMeshDescription, - // Always build Houdini Proxy Meshes (dev) - UHoudiniStaticMesh, -}; - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EHoudiniEngineBakeOption : uint8 -{ - ToActor, - ToBlueprint, - ToFoliage, - ToWorldOutliner, -}; -#endif - -class UHoudiniAssetComponent; - -DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetEvent, UHoudiniAsset*); -DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetComponentEvent, UHoudiniAssetComponent*) - -UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility), editinlinenew) -class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent -{ - GENERATED_UCLASS_BODY() - - // Declare translators as friend so they can easily directly modify - // Inputs, outputs and parameters - friend class FHoudiniEngineManager; - friend struct FHoudiniOutputTranslator; - friend struct FHoudiniInputTranslator; - friend struct FHoudiniSplineTranslator; - friend struct FHoudiniParameterTranslator; - friend struct FHoudiniPDGManager; - friend struct FHoudiniHandleTranslator; - -#if WITH_EDITORONLY_DATA - friend class FHoudiniAssetComponentDetails; -#endif - -public: - - // Declare the delegate that is broadcast when RefineMeshesTimer fires - DECLARE_MULTICAST_DELEGATE_OneParam(FOnRefineMeshesTimerDelegate, UHoudiniAssetComponent*); - DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPostCookBakeDelegate, UHoudiniAssetComponent*); - DECLARE_DELEGATE_TwoParams(FOnPostCookDelegate, UHoudiniAssetComponent*, bool); - - virtual ~UHoudiniAssetComponent(); - - virtual void Serialize(FArchive & Ar) override; - - virtual bool ConvertLegacyData(); - - // Called after the C++ constructor and after the properties have been initialized, including those loaded from config. - // This is called before any serialization or other setup has happened. - virtual void PostInitProperties() override; - - // Returns the Owner actor / HAC name - FString GetDisplayName() const; - - // Indicates if the HAC needs to be updated - bool NeedUpdate() const; - - // Indicates if the HAC's transform needs to be updated - bool NeedTransformUpdate() const { return (bHasComponentTransformChanged && bUploadTransformsToHoudiniEngine); }; - - // Indicates if any of the HAC's output components needs to be updated (no recook needed) - bool NeedOutputUpdate() const; - - // Check whether any inputs / outputs / parameters have made blueprint modifications. - bool NeedBlueprintStructureUpdate() const; - bool NeedBlueprintUpdate() const; - - // Try to find one of our parameter that matches another (name, type, size and enabled) - UHoudiniParameter* FindMatchingParameter(UHoudiniParameter* InOtherParam); - - // Try to find one of our input that matches another one (name, isobjpath, index / parmId) - UHoudiniInput* FindMatchingInput(UHoudiniInput* InOtherInput); - - // Try to find one of our handle that matches another one (name and handle type) - UHoudiniHandleComponent* FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle); - - // Finds a parameter by name - UHoudiniParameter* FindParameterByName(const FString& InParamName); - - // Returns True if the component has at least one mesh output of class U - template - bool HasMeshOutputObjectOfClass() const; - - // Returns True if the component has at least one mesh output with a current proxy - bool HasAnyCurrentProxyOutput() const; - - // Returns True if the component has at least one proxy mesh output (not necessarily current/displayed) - bool HasAnyProxyOutput() const; - - // Returns True if the component has at least one non-proxy output component amongst its outputs - bool HasAnyOutputComponent() const; - - // Returns true if the component has InOutputObjectToFind in its output object - bool HasOutputObject(UObject* InOutputObjectToFind) const; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - UHoudiniAsset * GetHoudiniAsset() const; - int32 GetAssetId() const { return AssetId; }; - EHoudiniAssetState GetAssetState() const { return AssetState; }; - FString GetAssetStateAsString() const { return FHoudiniEngineRuntimeUtils::EnumToString(TEXT("EHoudiniAssetState"), GetAssetState()); }; - EHoudiniAssetStateResult GetAssetStateResult() const { return AssetStateResult; }; - FGuid GetHapiGUID() const { return HapiGUID; }; - FGuid GetComponentGUID() const { return ComponentGUID; }; - - int32 GetNumInputs() const { return Inputs.Num(); }; - int32 GetNumOutputs() const { return Outputs.Num(); }; - int32 GetNumParameters() const { return Parameters.Num(); }; - int32 GetNumHandles() const { return HandleComponents.Num(); }; - - UHoudiniInput* GetInputAt(const int32& Idx) { return Inputs.IsValidIndex(Idx) ? Inputs[Idx] : nullptr; }; - UHoudiniOutput* GetOutputAt(const int32& Idx) { return Outputs.IsValidIndex(Idx) ? Outputs[Idx] : nullptr;}; - UHoudiniParameter* GetParameterAt(const int32& Idx) { return Parameters.IsValidIndex(Idx) ? Parameters[Idx] : nullptr;}; - UHoudiniHandleComponent* GetHandleComponentAt(const int32& Idx) { return HandleComponents.IsValidIndex(Idx) ? HandleComponents[Idx] : nullptr; }; - - void GetOutputs(TArray& OutOutputs) const; - - TArray& GetBakedOutputs() { return BakedOutputs; } - const TArray& GetBakedOutputs() const { return BakedOutputs; } - - /* - TArray& GetParameters() { return Parameters; }; - TArray& GetInputs() { return Inputs; }; - TArray& GetOutputs() { return Outputs; }; - */ - - bool IsCookingEnabled() const { return bEnableCooking; }; - bool HasBeenLoaded() const { return bHasBeenLoaded; }; - bool HasBeenDuplicated() const { return bHasBeenDuplicated; }; - bool HasRecookBeenRequested() const { return bRecookRequested; }; - bool HasRebuildBeenRequested() const { return bRebuildRequested; }; - - //bool GetEditorPropertiesNeedFullUpdate() const { return bEditorPropertiesNeedFullUpdate; }; - - int32 GetAssetCookCount() const { return AssetCookCount; }; - - bool IsFullyLoaded() const { return bFullyLoaded; }; - - UHoudiniPDGAssetLink * GetPDGAssetLink() const { return PDGAssetLink; }; - - virtual bool IsProxyStaticMeshEnabled() const; - bool IsProxyStaticMeshRefinementByTimerEnabled() const; - float GetProxyMeshAutoRefineTimeoutSeconds() const; - bool IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const; - bool IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const; - // If true, then the next cook should not build proxy meshes, regardless of global or override settings, - // but should instead directly build UStaticMesh - bool HasNoProxyMeshNextCookBeenRequested() const { return bNoProxyMeshNextCookRequested; } - // Returns true if the asset state indicates that it has been cooked in this session, false otherwise. - bool IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const; - // Returns true if the asset should be bake after the next cook - bool IsBakeAfterNextCookEnabled() const { return bBakeAfterNextCook; } - - FOnPostCookDelegate& GetOnPostCookDelegate() { return OnPostCookDelegate; } - FOnPostCookBakeDelegate& GetOnPostCookBakeDelegate() { return OnPostCookBakeDelegate; } - - // Derived blueprint based components will check whether the template - // component contains updates that needs to processed. - bool NeedUpdateParameters() const; - bool NeedUpdateInputs() const; - - // Returns true if the component has any previous baked output recorded in its outputs - bool HasPreviousBakeOutput() const; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - //void SetAssetId(const int& InAssetId); - //void SetAssetState(const EHoudiniAssetState& InAssetState) { AssetState = InAssetState; }; - //void SetAssetStateResult(const EHoudiniAssetStateResult& InAssetStateResult) { AssetStateResult = InAssetStateResult; }; - - //void SetHapiGUID(const FGuid& InGUID) { HapiGUID = InGUID; }; - //void SetComponentGUID(const FGuid& InGUID) { ComponentGUID = InGUID; }; - - //UFUNCTION(BlueprintSetter) - virtual void SetHoudiniAsset(UHoudiniAsset * NewHoudiniAsset); - - void SetHasBeenLoaded(const bool& InLoaded) { bHasBeenLoaded = InLoaded; }; - - void SetHasBeenDuplicated(const bool& InDuplicated) { bHasBeenDuplicated = InDuplicated; }; - - //void SetEditorPropertiesNeedFullUpdate(const bool& InUpdate) { bEditorPropertiesNeedFullUpdate = InUpdate; }; - - // Marks the assets as needing a recook - void MarkAsNeedCook(); - // Marks the assets as needing a full rebuild - void MarkAsNeedRebuild(); - // Marks the asset as needing to be instantiated - void MarkAsNeedInstantiation(); - // The blueprint has been structurally modified - void MarkAsBlueprintStructureModified(); - // The blueprint has been modified but not structurally changed. - void MarkAsBlueprintModified(); - - // - void SetAssetCookCount(const int32& InCount) { AssetCookCount = InCount; }; - // - void SetRecookRequested(const bool& InRecook) { bRecookRequested = InRecook; }; - // - void SetRebuildRequested(const bool& InRebuild) { bRebuildRequested = InRebuild; }; - // - void SetHasComponentTransformChanged(const bool& InHasChanged); - - // Set to True to force the next cook to not build a proxy mesh (regardless of global or override settings) and - // instead build a UStaticMesh directly (if applicable for the output type). - void SetNoProxyMeshNextCookRequested(bool bInNoProxyMeshNextCookRequested) { bNoProxyMeshNextCookRequested = bInNoProxyMeshNextCookRequested; } - - // Set to True to force the next cook to bake the asset after the cook completes. - void SetBakeAfterNextCookEnabled(bool bInEnabled) { bBakeAfterNextCook = bInEnabled; } - - // - void SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink); - // - virtual void OnHoudiniAssetChanged(); - - // - void AddDownstreamHoudiniAsset(UHoudiniAssetComponent* InDownstreamAsset) { DownstreamHoudiniAssets.Add(InDownstreamAsset); }; - // - void RemoveDownstreamHoudiniAsset(UHoudiniAssetComponent* InRemoveDownstreamAsset) { DownstreamHoudiniAssets.Remove(InRemoveDownstreamAsset); }; - // - void ClearDownstreamHoudiniAsset() { DownstreamHoudiniAssets.Empty(); }; - // - bool NotifyCookedToDownstreamAssets(); - // - bool NeedsToWaitForInputHoudiniAssets(); - - // Clear/disable the RefineMeshesTimer. - void ClearRefineMeshesTimer(); - - // Re-set the RefineMeshesTimer to its default value. - void SetRefineMeshesTimer(); - - virtual void OnRefineMeshesTimerFired(); - - // Called by RefineMeshesTimer when the timer is triggered. - // Checks for any UHoudiniStaticMesh in Outputs and bakes UStaticMesh for them via FHoudiniMeshTranslator. - FOnRefineMeshesTimerDelegate& GetOnRefineMeshesTimerDelegate() { return OnRefineMeshesTimerDelegate; } - - // Returns true if the asset is valid for cook/bake - virtual bool IsComponentValid() const; - // Return false if this component has no cooking or instantiation in progress. - bool IsInstantiatingOrCooking() const; - - // HoudiniEngineTick will be called by HoudiniEngineManager::Tick() - virtual void HoudiniEngineTick(); - -#if WITH_EDITOR - // This alternate version of PostEditChange is called when properties inside structs are modified. The property that was actually modified - // is located at the tail of the list. The head of the list of the FStructProperty member variable that contains the property that was modified. - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; - - //Called after applying a transaction to the object. Default implementation simply calls PostEditChange. - virtual void PostEditUndo() override; - - // Whether this component is currently open in a Blueprint editor. This - // method is overridden by HoudiniAssetBlueprintComponent. - virtual bool HasOpenEditor() const { return false; }; - -#endif - - void SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const; - - virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent); - - virtual void OnRegister() override; - - // USceneComponent methods. - virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override; - virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; - - FBox GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const; - - // Set this component's input presets - void SetInputPresets(const TMap& InPresets); - // Apply the preset input for HoudiniTools - void ApplyInputPresets(); - - // return the cached component template, if available. - virtual UHoudiniAssetComponent* GetCachedTemplate() const { return nullptr; } - - virtual FPrimitiveSceneProxy* CreateSceneProxy() override; - - //------------------------------------------------------------------------------------------------ - // Supported Features - //------------------------------------------------------------------------------------------------ - - // Whether or not this component should be able to delete the Houdini nodes - // that correspond to the HoudiniAsset when being deregistered. - virtual bool CanDeleteHoudiniNodes() const { return true; } - - virtual bool IsInputTypeSupported(EHoudiniInputType InType) const; - virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const; - - //------------------------------------------------------------------------------------------------5 - // Characteristics - //------------------------------------------------------------------------------------------------ - - // Try to determine whether this component belongs to a preview actor. - // Preview / Template components need to sync their data for HDA cooks and output translations. - bool IsPreview() const; - - virtual bool IsValidComponent() const; - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - // TODO: After the cook worfklow rework, most of these won't be needed anymore, so clean up! - //FHoudiniAssetComponentEvent OnTemplateParametersChanged; - //FHoudiniAssetComponentEvent OnPreAssetCook; - //FHoudiniAssetComponentEvent OnCookCompleted; - //FHoudiniAssetComponentEvent OnOutputProcessingCompleted; - - /*virtual void BroadcastParametersChanged(); - virtual void BroadcastPreAssetCook(); - virtual void BroadcastCookCompleted();*/ - - virtual void OnPrePreCook() {}; - virtual void OnPostPreCook() {}; - virtual void OnPreOutputProcessing() {}; - virtual void OnPostOutputProcessing() {}; - virtual void OnPrePreInstantiation() {}; - - - virtual void NotifyHoudiniRegisterCompleted() {}; - virtual void NotifyHoudiniPreUnregister() {}; - virtual void NotifyHoudiniPostUnregister() {}; - - virtual void OnFullyLoaded(); - - // Component template parameters have been updated. - // Broadcast delegate, and let preview components take care of the rest. - virtual void OnTemplateParametersChanged() { }; - virtual void OnBlueprintStructureModified() { }; - virtual void OnBlueprintModified() { }; - - -protected: - - // UActorComponents Method - virtual void OnComponentCreated() override; - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - - virtual void OnChildAttached(USceneComponent* ChildComponent) override; - - virtual void BeginDestroy() override; - - // - virtual void CreateRenderState_Concurrent(FRegisterComponentContext* Context) override; - - // Do any object - specific cleanup required immediately after loading an object. - // This is not called for newly - created objects, and by default will always execute on the game thread. - virtual void PostLoad() override; - - // Called after importing property values for this object (paste, duplicate or .t3d import) - // Allow the object to perform any cleanup for properties which shouldn't be duplicated or - // Are unsupported by the script serialization - virtual void PostEditImport() override; - - // - void OnActorMoved(AActor* Actor); - - // - void UpdatePostDuplicate(); - - // - //static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); - - // Updates physics state, bounds, and mark render state dirty - // Should be call PostLoad and PostProcessing - void UpdateRenderingInformation(); - -public: - - // Houdini Asset associated with this component. - /*Category = HoudiniAsset, EditAnywhere, meta = (DisplayPriority=0)*/ - UPROPERTY(Category = HoudiniAsset, EditAnywhere)// BlueprintSetter = SetHoudiniAsset, BlueprintReadWrite, ) - UHoudiniAsset* HoudiniAsset; - - // Automatically cook when a parameter or input is changed - UPROPERTY() - bool bCookOnParameterChange; - - // Enables uploading of transformation changes back to Houdini Engine. - UPROPERTY() - bool bUploadTransformsToHoudiniEngine; - - // Transform changes automatically trigger cooks. - UPROPERTY() - bool bCookOnTransformChange; - - // Houdini materials will be converted to Unreal Materials. - //UPROPERTY() - //bool bUseNativeHoudiniMaterials; - - // This asset will cook when its asset input cook - UPROPERTY() - bool bCookOnAssetInputCook; - - // Enabling this will prevent the HDA from producing any output after cooking. - UPROPERTY() - bool bOutputless; - - // Enabling this will allow outputing the asset's templated geos - UPROPERTY() - bool bOutputTemplateGeos; - - // Temporary cook folder - UPROPERTY() - FDirectoryPath TemporaryCookFolder; - - // Folder used for baking this asset's outputs (unless set by prim/detail attribute on the output). Falls back to - // the default from the plugin settings if not set. - UPROPERTY() - FDirectoryPath BakeFolder; - - // The method used to create Static Meshes - UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 0)) - EHoudiniStaticMeshMethod StaticMeshMethod; - - // Generation properties for the Static Meshes generated by this Houdini Asset - UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 1)/*, meta = (ShowOnlyInnerProperties)*/) - FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; - - // Build Settings to be used when generating the Static Meshes for this Houdini Asset - UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 2)) - FMeshBuildSettings StaticMeshBuildSettings; - - // Override the global fast proxy mesh settings on this component? - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere/*, meta = (DisplayAfter = "StaticMeshGenerationProperties")*/) - bool bOverrideGlobalProxyStaticMeshSettings; - - // For StaticMesh outputs: should a fast proxy be created first? - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Enable Proxy Static Mesh", EditCondition="bOverrideGlobalProxyStaticMeshSettings")) - bool bEnableProxyStaticMeshOverride; - - // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes After a Timeout", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementByTimerOverride; - - // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride")) - float ProxyMeshAutoRefineTimeoutSecondsOverride; - - // Automatically refine proxy meshes to UStaticMesh before the map is saved - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes When Saving a Map", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; - - // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bGenerateMenuExpanded; - - UPROPERTY() - bool bBakeMenuExpanded; - - UPROPERTY() - bool bAssetOptionMenuExpanded; - - UPROPERTY() - bool bHelpAndDebugMenuExpanded; - - UPROPERTY() - EHoudiniEngineBakeOption HoudiniEngineBakeOption; - - // If true, then after a successful bake, the HACs outputs will be cleared and removed. - UPROPERTY() - bool bRemoveOutputAfterBake; - - // If true, recenter baked actors to their bounding box center after bake - UPROPERTY() - bool bRecenterBakedActors; - - // If true, replace the previously baked output (if any) instead of creating new objects - UPROPERTY() - bool bReplacePreviousBake; -#endif - -protected: - - // Id of corresponding Houdini asset. - UPROPERTY(DuplicateTransient) - int32 AssetId; - - // List of dependent downstream HACs that have us as an asset input - UPROPERTY(DuplicateTransient) - TSet DownstreamHoudiniAssets; - - // Unique GUID created by component. - UPROPERTY(DuplicateTransient) - FGuid ComponentGUID; - - // GUID used to track asynchronous cooking requests. - UPROPERTY(DuplicateTransient) - FGuid HapiGUID; - - // Current state of the asset - UPROPERTY(DuplicateTransient) - EHoudiniAssetState AssetState; - - // Last asset state logged. - UPROPERTY(DuplicateTransient) - mutable EHoudiniAssetState DebugLastAssetState; - - // Result of the current asset's state - UPROPERTY(DuplicateTransient) - EHoudiniAssetStateResult AssetStateResult; - - //// Contains the context for keeping track of shared - //// Houdini data. - //UPROPERTY(DuplicateTransient) - //UHoudiniAssetContext* AssetContext; - - // Subasset index - UPROPERTY() - uint32 SubAssetIndex; - - // Number of times this asset has been cooked. - UPROPERTY(DuplicateTransient) - int32 AssetCookCount; - - // - UPROPERTY(DuplicateTransient) - bool bHasBeenLoaded; - - UPROPERTY(DuplicateTransient) - bool bHasBeenDuplicated; - - UPROPERTY(DuplicateTransient) - bool bPendingDelete; - - UPROPERTY(DuplicateTransient) - bool bRecookRequested; - - UPROPERTY(DuplicateTransient) - bool bRebuildRequested; - - UPROPERTY(DuplicateTransient) - bool bEnableCooking; - - UPROPERTY(DuplicateTransient) - bool bForceNeedUpdate; - - UPROPERTY(DuplicateTransient) - bool bLastCookSuccess; - - UPROPERTY(DuplicateTransient) - bool bBlueprintStructureModified; - - UPROPERTY(DuplicateTransient) - bool bBlueprintModified; - - //UPROPERTY(DuplicateTransient) - //bool bEditorPropertiesNeedFullUpdate; - - UPROPERTY(Instanced) - TArray Parameters; - - UPROPERTY(Instanced) - TArray Inputs; - - UPROPERTY(Instanced) - TArray Outputs; - - // The baked outputs from the last bake. - UPROPERTY() - TArray BakedOutputs; - - // Any actors that aren't explicitly - // tracked by output objects should be registered - // here so that they can be cleaned up. - UPROPERTY() - TArray> UntrackedOutputs; - - UPROPERTY() - TArray HandleComponents; - - UPROPERTY(Transient, DuplicateTransient) - bool bHasComponentTransformChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bFullyLoaded; - - UPROPERTY() - UHoudiniPDGAssetLink* PDGAssetLink; - - // Timer that is used to trigger creation of UStaticMesh for all mesh outputs - // that still have UHoudiniStaticMeshes. The timer is cleared on PreCook and reset - // at the end of the PostCook. - UPROPERTY() - FTimerHandle RefineMeshesTimer; - - // Delegate that is used to broadcast when RefineMeshesTimer fires - FOnRefineMeshesTimerDelegate OnRefineMeshesTimerDelegate; - - // If true, don't build a proxy mesh next cook (regardless of global or override settings), - // instead build the UStaticMesh directly (if applicable for the output types). - UPROPERTY(DuplicateTransient) - bool bNoProxyMeshNextCookRequested; - - // Maps a UObject to an Input number, used to preset the asset's inputs - UPROPERTY(Transient, DuplicateTransient) - TMap InputPresets; - - // If true, bake the asset after its next cook. - UPROPERTY(DuplicateTransient) - bool bBakeAfterNextCook; - - // Delegate to broadcast after a post cook event - // Arguments are (HoudiniAssetComponent* HAC, bool IsSuccessful) - FOnPostCookDelegate OnPostCookDelegate; - - // Delegate to broadcast when baking after a cook. - // Currently we cannot call the bake functions from here (Runtime module) - // or from the HoudiniEngineManager (HoudiniEngine) module, so we use - // a delegate. - FOnPostCookBakeDelegate OnPostCookBakeDelegate; - - // Cached flag of whether this object is considered to be a 'preview' component or not. - // This is typically useful in destructors when references to the World, for example, - // is no longer available. - UPROPERTY(Transient, DuplicateTransient) - bool bCachedIsPreview; - - USimpleConstructionScript* GetSCS() const; - - // Object used to convert V1 HAC to V2 HAC - UHoudiniAssetComponent_V1* Version1CompatibilityHAC; - - // The last timestamp this component was ticked - // used to prioritize/limit the number of HAC processed per tick - UPROPERTY(Transient) - double LastTickTime; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniOutput.h" +#include "HoudiniInputTypes.h" +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniAssetStateTypes.h" +#include "IHoudiniAssetStateEvents.h" + +#include "Engine/EngineTypes.h" +#include "Components/PrimitiveComponent.h" +#include "Components/SceneComponent.h" + +#include "HoudiniAssetComponent.generated.h" + +class UHoudiniAsset; +class UHoudiniParameter; +class UHoudiniInput; +class UHoudiniOutput; +class UHoudiniHandleComponent; +class UHoudiniPDGAssetLink; +class UHoudiniAssetComponent_V1; + +UENUM() +enum class EHoudiniStaticMeshMethod : uint8 +{ + // Static Meshes will be generated by using Raw Meshes. + RawMesh, + // Static Meshes will be generated by using Mesh Descriptions. + FMeshDescription, + // Always build Houdini Proxy Meshes (dev) + UHoudiniStaticMesh, +}; + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EHoudiniEngineBakeOption : uint8 +{ + ToActor, + ToBlueprint, + ToFoliage, + ToWorldOutliner, +}; +#endif + +class UHoudiniAssetComponent; + +DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetEvent, UHoudiniAsset*); +DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetComponentEvent, UHoudiniAssetComponent*) + +UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility), editinlinenew) +class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent, public IHoudiniAssetStateEvents +{ + GENERATED_UCLASS_BODY() + + // Declare translators as friend so they can easily directly modify + // Inputs, outputs and parameters + friend class FHoudiniEngineManager; + friend struct FHoudiniOutputTranslator; + friend struct FHoudiniInputTranslator; + friend struct FHoudiniSplineTranslator; + friend struct FHoudiniParameterTranslator; + friend struct FHoudiniPDGManager; + friend struct FHoudiniHandleTranslator; + +#if WITH_EDITORONLY_DATA + friend class FHoudiniAssetComponentDetails; +#endif + +public: + + // Declare the delegate that is broadcast when RefineMeshesTimer fires + DECLARE_MULTICAST_DELEGATE_OneParam(FOnRefineMeshesTimerDelegate, UHoudiniAssetComponent*); + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPostCookBakeDelegate, UHoudiniAssetComponent*); + // Delegate for when EHoudiniAssetState changes from InFromState to InToState on a Houdini Asset Component (InHAC). + DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnAssetStateChangeDelegate, UHoudiniAssetComponent*, const EHoudiniAssetState, const EHoudiniAssetState); + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostCookDelegate, UHoudiniAssetComponent*, bool); + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostBakeDelegate, UHoudiniAssetComponent*, bool); + + virtual ~UHoudiniAssetComponent(); + + virtual void Serialize(FArchive & Ar) override; + + virtual bool ConvertLegacyData(); + + // Called after the C++ constructor and after the properties have been initialized, including those loaded from config. + // This is called before any serialization or other setup has happened. + virtual void PostInitProperties() override; + + // Returns the Owner actor / HAC name + FString GetDisplayName() const; + + // Indicates if the HAC needs to be updated + bool NeedUpdate() const; + + // Indicates if the HAC's transform needs to be updated + bool NeedTransformUpdate() const { return (bHasComponentTransformChanged && bUploadTransformsToHoudiniEngine); }; + + // Indicates if any of the HAC's output components needs to be updated (no recook needed) + bool NeedOutputUpdate() const; + + // Check whether any inputs / outputs / parameters have made blueprint modifications. + bool NeedBlueprintStructureUpdate() const; + bool NeedBlueprintUpdate() const; + + // Try to find one of our parameter that matches another (name, type, size and enabled) + UHoudiniParameter* FindMatchingParameter(UHoudiniParameter* InOtherParam); + + // Try to find one of our input that matches another one (name, isobjpath, index / parmId) + UHoudiniInput* FindMatchingInput(UHoudiniInput* InOtherInput); + + // Try to find one of our handle that matches another one (name and handle type) + UHoudiniHandleComponent* FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle); + + // Finds a parameter by name + UHoudiniParameter* FindParameterByName(const FString& InParamName); + + // Returns True if the component has at least one mesh output of class U + template + bool HasMeshOutputObjectOfClass() const; + + // Returns True if the component has at least one mesh output with a current proxy + bool HasAnyCurrentProxyOutput() const; + + // Returns True if the component has at least one proxy mesh output (not necessarily current/displayed) + bool HasAnyProxyOutput() const; + + // Returns True if the component has at least one non-proxy output component amongst its outputs + bool HasAnyOutputComponent() const; + + // Returns true if the component has InOutputObjectToFind in its output object + bool HasOutputObject(UObject* InOutputObjectToFind) const; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + UHoudiniAsset * GetHoudiniAsset() const; + int32 GetAssetId() const { return AssetId; }; + EHoudiniAssetState GetAssetState() const { return AssetState; }; + FString GetAssetStateAsString() const { return FHoudiniEngineRuntimeUtils::EnumToString(TEXT("EHoudiniAssetState"), GetAssetState()); }; + EHoudiniAssetStateResult GetAssetStateResult() const { return AssetStateResult; }; + FGuid GetHapiGUID() const { return HapiGUID; }; + FString GetHapiAssetName() const { return HapiAssetName; }; + FGuid GetComponentGUID() const { return ComponentGUID; }; + + int32 GetNumInputs() const { return Inputs.Num(); }; + int32 GetNumOutputs() const { return Outputs.Num(); }; + int32 GetNumParameters() const { return Parameters.Num(); }; + int32 GetNumHandles() const { return HandleComponents.Num(); }; + + UHoudiniInput* GetInputAt(const int32& Idx) { return Inputs.IsValidIndex(Idx) ? Inputs[Idx] : nullptr; }; + UHoudiniOutput* GetOutputAt(const int32& Idx) { return Outputs.IsValidIndex(Idx) ? Outputs[Idx] : nullptr;}; + UHoudiniParameter* GetParameterAt(const int32& Idx) { return Parameters.IsValidIndex(Idx) ? Parameters[Idx] : nullptr;}; + UHoudiniHandleComponent* GetHandleComponentAt(const int32& Idx) { return HandleComponents.IsValidIndex(Idx) ? HandleComponents[Idx] : nullptr; }; + + void GetOutputs(TArray& OutOutputs) const; + + TArray& GetBakedOutputs() { return BakedOutputs; } + const TArray& GetBakedOutputs() const { return BakedOutputs; } + + /* + TArray& GetParameters() { return Parameters; }; + TArray& GetInputs() { return Inputs; }; + TArray& GetOutputs() { return Outputs; }; + */ + + bool IsCookingEnabled() const { return bEnableCooking; }; + bool HasBeenLoaded() const { return bHasBeenLoaded; }; + bool HasBeenDuplicated() const { return bHasBeenDuplicated; }; + bool HasRecookBeenRequested() const { return bRecookRequested; }; + bool HasRebuildBeenRequested() const { return bRebuildRequested; }; + + //bool GetEditorPropertiesNeedFullUpdate() const { return bEditorPropertiesNeedFullUpdate; }; + + int32 GetAssetCookCount() const { return AssetCookCount; }; + + bool IsFullyLoaded() const { return bFullyLoaded; }; + + UHoudiniPDGAssetLink * GetPDGAssetLink() const { return PDGAssetLink; }; + + virtual bool IsProxyStaticMeshEnabled() const; + bool IsProxyStaticMeshRefinementByTimerEnabled() const; + float GetProxyMeshAutoRefineTimeoutSeconds() const; + bool IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const; + bool IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const; + // If true, then the next cook should not build proxy meshes, regardless of global or override settings, + // but should instead directly build UStaticMesh + bool HasNoProxyMeshNextCookBeenRequested() const { return bNoProxyMeshNextCookRequested; } + // Returns true if the asset state indicates that it has been cooked in this session, false otherwise. + bool IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const; + // Returns true if the asset should be bake after the next cook + bool IsBakeAfterNextCookEnabled() const { return bBakeAfterNextCook; } + + FOnPostCookDelegate& GetOnPostCookDelegate() { return OnPostCookDelegate; } + FOnPostCookBakeDelegate& GetOnPostCookBakeDelegate() { return OnPostCookBakeDelegate; } + FOnPostBakeDelegate& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } + + FOnAssetStateChangeDelegate& GetOnAssetStateChangeDelegate() { return OnAssetStateChangeDelegate; } + + // Derived blueprint based components will check whether the template + // component contains updates that needs to processed. + bool NeedUpdateParameters() const; + bool NeedUpdateInputs() const; + + // Returns true if the component has any previous baked output recorded in its outputs + bool HasPreviousBakeOutput() const; + + // Returns true if the last cook of the HDA was successful + bool WasLastCookSuccessful() const { return bLastCookSuccess; } + + // Returns true if a parameter definition update (excluding values) is needed. + bool IsParameterDefinitionUpdateNeeded() const { return bParameterDefinitionUpdateNeeded; } + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + //void SetAssetId(const int& InAssetId); + //void SetAssetState(const EHoudiniAssetState& InAssetState) { AssetState = InAssetState; }; + //void SetAssetStateResult(const EHoudiniAssetStateResult& InAssetStateResult) { AssetStateResult = InAssetStateResult; }; + + //void SetHapiGUID(const FGuid& InGUID) { HapiGUID = InGUID; }; + //void SetComponentGUID(const FGuid& InGUID) { ComponentGUID = InGUID; }; + + //UFUNCTION(BlueprintSetter) + virtual void SetHoudiniAsset(UHoudiniAsset * NewHoudiniAsset); + + void SetCookingEnabled(const bool& bInCookingEnabled) { bEnableCooking = bInCookingEnabled; }; + + void SetHasBeenLoaded(const bool& InLoaded) { bHasBeenLoaded = InLoaded; }; + + void SetHasBeenDuplicated(const bool& InDuplicated) { bHasBeenDuplicated = InDuplicated; }; + + //void SetEditorPropertiesNeedFullUpdate(const bool& InUpdate) { bEditorPropertiesNeedFullUpdate = InUpdate; }; + + // Marks the assets as needing a recook + void MarkAsNeedCook(); + // Marks the assets as needing a full rebuild + void MarkAsNeedRebuild(); + // Marks the asset as needing to be instantiated + void MarkAsNeedInstantiation(); + // The blueprint has been structurally modified + void MarkAsBlueprintStructureModified(); + // The blueprint has been modified but not structurally changed. + void MarkAsBlueprintModified(); + + // + void SetAssetCookCount(const int32& InCount) { AssetCookCount = InCount; }; + // + void SetRecookRequested(const bool& InRecook) { bRecookRequested = InRecook; }; + // + void SetRebuildRequested(const bool& InRebuild) { bRebuildRequested = InRebuild; }; + // + void SetHasComponentTransformChanged(const bool& InHasChanged); + + // Set to True to force the next cook to not build a proxy mesh (regardless of global or override settings) and + // instead build a UStaticMesh directly (if applicable for the output type). + void SetNoProxyMeshNextCookRequested(bool bInNoProxyMeshNextCookRequested) { bNoProxyMeshNextCookRequested = bInNoProxyMeshNextCookRequested; } + + // Set to True to force the next cook to bake the asset after the cook completes. + void SetBakeAfterNextCookEnabled(bool bInEnabled) { bBakeAfterNextCook = bInEnabled; } + + // + void SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink); + // + virtual void OnHoudiniAssetChanged(); + + // + void AddDownstreamHoudiniAsset(UHoudiniAssetComponent* InDownstreamAsset) { DownstreamHoudiniAssets.Add(InDownstreamAsset); }; + // + void RemoveDownstreamHoudiniAsset(UHoudiniAssetComponent* InRemoveDownstreamAsset) { DownstreamHoudiniAssets.Remove(InRemoveDownstreamAsset); }; + // + void ClearDownstreamHoudiniAsset() { DownstreamHoudiniAssets.Empty(); }; + // + bool NotifyCookedToDownstreamAssets(); + // + bool NeedsToWaitForInputHoudiniAssets(); + + // Clear/disable the RefineMeshesTimer. + void ClearRefineMeshesTimer(); + + // Re-set the RefineMeshesTimer to its default value. + void SetRefineMeshesTimer(); + + virtual void OnRefineMeshesTimerFired(); + + // Called by RefineMeshesTimer when the timer is triggered. + // Checks for any UHoudiniStaticMesh in Outputs and bakes UStaticMesh for them via FHoudiniMeshTranslator. + FOnRefineMeshesTimerDelegate& GetOnRefineMeshesTimerDelegate() { return OnRefineMeshesTimerDelegate; } + + // Returns true if the asset is valid for cook/bake + virtual bool IsComponentValid() const; + // Return false if this component has no cooking or instantiation in progress. + bool IsInstantiatingOrCooking() const; + + // HoudiniEngineTick will be called by HoudiniEngineManager::Tick() + virtual void HoudiniEngineTick(); + +#if WITH_EDITOR + // This alternate version of PostEditChange is called when properties inside structs are modified. The property that was actually modified + // is located at the tail of the list. The head of the list of the FStructProperty member variable that contains the property that was modified. + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; + + //Called after applying a transaction to the object. Default implementation simply calls PostEditChange. + virtual void PostEditUndo() override; + + // Whether this component is currently open in a Blueprint editor. This + // method is overridden by HoudiniAssetBlueprintComponent. + virtual bool HasOpenEditor() const { return false; }; + +#endif + + void SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const; + + virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent); + + virtual void OnRegister() override; + + // USceneComponent methods. + virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; + + FBox GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const; + + // Set this component's input presets + void SetInputPresets(const TMap& InPresets); + // Apply the preset input for HoudiniTools + void ApplyInputPresets(); + + // return the cached component template, if available. + virtual UHoudiniAssetComponent* GetCachedTemplate() const { return nullptr; } + + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + + //------------------------------------------------------------------------------------------------ + // Supported Features + //------------------------------------------------------------------------------------------------ + + // Whether or not this component should be able to delete the Houdini nodes + // that correspond to the HoudiniAsset when being deregistered. + virtual bool CanDeleteHoudiniNodes() const { return true; } + + virtual bool IsInputTypeSupported(EHoudiniInputType InType) const; + virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const; + + //------------------------------------------------------------------------------------------------5 + // Characteristics + //------------------------------------------------------------------------------------------------ + + // Try to determine whether this component belongs to a preview actor. + // Preview / Template components need to sync their data for HDA cooks and output translations. + bool IsPreview() const; + + virtual bool IsValidComponent() const; + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + // TODO: After the cook worfklow rework, most of these won't be needed anymore, so clean up! + //FHoudiniAssetComponentEvent OnTemplateParametersChanged; + //FHoudiniAssetComponentEvent OnPreAssetCook; + //FHoudiniAssetComponentEvent OnCookCompleted; + //FHoudiniAssetComponentEvent OnOutputProcessingCompleted; + + /*virtual void BroadcastParametersChanged(); + virtual void BroadcastPreAssetCook(); + virtual void BroadcastCookCompleted();*/ + + virtual void OnPrePreCook() {}; + virtual void OnPostPreCook() {}; + virtual void OnPreOutputProcessing() {}; + virtual void OnPostOutputProcessing() {}; + virtual void OnPrePreInstantiation() {}; + + + virtual void NotifyHoudiniRegisterCompleted() {}; + virtual void NotifyHoudiniPreUnregister() {}; + virtual void NotifyHoudiniPostUnregister() {}; + + virtual void OnFullyLoaded(); + + // Component template parameters have been updated. + // Broadcast delegate, and let preview components take care of the rest. + virtual void OnTemplateParametersChanged() { }; + virtual void OnBlueprintStructureModified() { }; + virtual void OnBlueprintModified() { }; + + // + // Begin: IHoudiniAssetStateEvents + // + + virtual void HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) override; + + FORCEINLINE + virtual FOnHoudiniAssetStateChange& GetOnHoudiniAssetStateChangeDelegate() override { return OnHoudiniAssetStateChangeDelegate; } + + // + // End: IHoudiniAssetStateEvents + // + + // Called by HandleOnHoudiniAssetStateChange when entering the PostCook state. Broadcasts OnPostCookDelegate. + void HandleOnPostCook(); + + // Called by baking code after baking all outputs of this HAC (HoudiniEngineBakeOption) + void HandleOnPostBake(const bool bInSuccess); + +protected: + + // UActorComponents Method + virtual void OnComponentCreated() override; + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + virtual void OnChildAttached(USceneComponent* ChildComponent) override; + + virtual void BeginDestroy() override; + + // + virtual void CreateRenderState_Concurrent(FRegisterComponentContext* Context) override; + + // Do any object - specific cleanup required immediately after loading an object. + // This is not called for newly - created objects, and by default will always execute on the game thread. + virtual void PostLoad() override; + + // Called after importing property values for this object (paste, duplicate or .t3d import) + // Allow the object to perform any cleanup for properties which shouldn't be duplicated or + // Are unsupported by the script serialization + virtual void PostEditImport() override; + + // + void OnActorMoved(AActor* Actor); + + // + void UpdatePostDuplicate(); + + // + //static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); + + // Updates physics state, bounds, and mark render state dirty + // Should be call PostLoad and PostProcessing + void UpdateRenderingInformation(); + + // Mutators + + // Set asset state + void SetAssetState(EHoudiniAssetState InNewState); + +public: + + // Houdini Asset associated with this component. + /*Category = HoudiniAsset, EditAnywhere, meta = (DisplayPriority=0)*/ + UPROPERTY(Category = HoudiniAsset, EditAnywhere)// BlueprintSetter = SetHoudiniAsset, BlueprintReadWrite, ) + UHoudiniAsset* HoudiniAsset; + + // Automatically cook when a parameter or input is changed + UPROPERTY() + bool bCookOnParameterChange; + + // Enables uploading of transformation changes back to Houdini Engine. + UPROPERTY() + bool bUploadTransformsToHoudiniEngine; + + // Transform changes automatically trigger cooks. + UPROPERTY() + bool bCookOnTransformChange; + + // Houdini materials will be converted to Unreal Materials. + //UPROPERTY() + //bool bUseNativeHoudiniMaterials; + + // This asset will cook when its asset input cook + UPROPERTY() + bool bCookOnAssetInputCook; + + // Enabling this will prevent the HDA from producing any output after cooking. + UPROPERTY() + bool bOutputless; + + // Enabling this will allow outputing the asset's templated geos + UPROPERTY() + bool bOutputTemplateGeos; + + // Temporary cook folder + UPROPERTY() + FDirectoryPath TemporaryCookFolder; + + // Folder used for baking this asset's outputs (unless set by prim/detail attribute on the output). Falls back to + // the default from the plugin settings if not set. + UPROPERTY() + FDirectoryPath BakeFolder; + + // The method used to create Static Meshes + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 0)) + EHoudiniStaticMeshMethod StaticMeshMethod; + + // Generation properties for the Static Meshes generated by this Houdini Asset + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 1)/*, meta = (ShowOnlyInnerProperties)*/) + FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; + + // Build Settings to be used when generating the Static Meshes for this Houdini Asset + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 2)) + FMeshBuildSettings StaticMeshBuildSettings; + + // Override the global fast proxy mesh settings on this component? + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere/*, meta = (DisplayAfter = "StaticMeshGenerationProperties")*/) + bool bOverrideGlobalProxyStaticMeshSettings; + + // For StaticMesh outputs: should a fast proxy be created first? + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Enable Proxy Static Mesh", EditCondition="bOverrideGlobalProxyStaticMeshSettings")) + bool bEnableProxyStaticMeshOverride; + + // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes After a Timeout", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementByTimerOverride; + + // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride")) + float ProxyMeshAutoRefineTimeoutSecondsOverride; + + // Automatically refine proxy meshes to UStaticMesh before the map is saved + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes When Saving a Map", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; + + // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bGenerateMenuExpanded; + + UPROPERTY() + bool bBakeMenuExpanded; + + UPROPERTY() + bool bAssetOptionMenuExpanded; + + UPROPERTY() + bool bHelpAndDebugMenuExpanded; + + UPROPERTY() + EHoudiniEngineBakeOption HoudiniEngineBakeOption; + + // If true, then after a successful bake, the HACs outputs will be cleared and removed. + UPROPERTY() + bool bRemoveOutputAfterBake; + + // If true, recenter baked actors to their bounding box center after bake + UPROPERTY() + bool bRecenterBakedActors; + + // If true, replace the previously baked output (if any) instead of creating new objects + UPROPERTY() + bool bReplacePreviousBake; +#endif + +protected: + + // Id of corresponding Houdini asset. + UPROPERTY(DuplicateTransient) + int32 AssetId; + + // List of dependent downstream HACs that have us as an asset input + UPROPERTY(DuplicateTransient) + TSet DownstreamHoudiniAssets; + + // Unique GUID created by component. + UPROPERTY(DuplicateTransient) + FGuid ComponentGUID; + + // GUID used to track asynchronous cooking requests. + UPROPERTY(DuplicateTransient) + FGuid HapiGUID; + + // The asset name of the selected asset inside the asset library + UPROPERTY(DuplicateTransient) + FString HapiAssetName; + + // Current state of the asset + UPROPERTY(DuplicateTransient) + EHoudiniAssetState AssetState; + + // Last asset state logged. + UPROPERTY(DuplicateTransient) + mutable EHoudiniAssetState DebugLastAssetState; + + // Result of the current asset's state + UPROPERTY(DuplicateTransient) + EHoudiniAssetStateResult AssetStateResult; + + //// Contains the context for keeping track of shared + //// Houdini data. + //UPROPERTY(DuplicateTransient) + //UHoudiniAssetContext* AssetContext; + + // Subasset index + UPROPERTY() + uint32 SubAssetIndex; + + // Number of times this asset has been cooked. + UPROPERTY(DuplicateTransient) + int32 AssetCookCount; + + // + UPROPERTY(DuplicateTransient) + bool bHasBeenLoaded; + + UPROPERTY(DuplicateTransient) + bool bHasBeenDuplicated; + + UPROPERTY(DuplicateTransient) + bool bPendingDelete; + + UPROPERTY(DuplicateTransient) + bool bRecookRequested; + + UPROPERTY(DuplicateTransient) + bool bRebuildRequested; + + UPROPERTY(DuplicateTransient) + bool bEnableCooking; + + UPROPERTY(DuplicateTransient) + bool bForceNeedUpdate; + + UPROPERTY(DuplicateTransient) + bool bLastCookSuccess; + + // Indicates that the parameter state (excluding values) on the HAC and the instantiated node needs to be synced. + // The most common use for this would be a newly instantiated HDA that has only a default parameter interface + // from its asset definition, and needs to sync pre-cook. + UPROPERTY(DuplicateTransient) + bool bParameterDefinitionUpdateNeeded; + + UPROPERTY(DuplicateTransient) + bool bBlueprintStructureModified; + + UPROPERTY(DuplicateTransient) + bool bBlueprintModified; + + //UPROPERTY(DuplicateTransient) + //bool bEditorPropertiesNeedFullUpdate; + + UPROPERTY(Instanced) + TArray Parameters; + + UPROPERTY(Instanced) + TArray Inputs; + + UPROPERTY(Instanced) + TArray Outputs; + + // The baked outputs from the last bake. + UPROPERTY() + TArray BakedOutputs; + + // Any actors that aren't explicitly + // tracked by output objects should be registered + // here so that they can be cleaned up. + UPROPERTY() + TArray> UntrackedOutputs; + + UPROPERTY() + TArray HandleComponents; + + UPROPERTY(Transient, DuplicateTransient) + bool bHasComponentTransformChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bFullyLoaded; + + UPROPERTY() + UHoudiniPDGAssetLink* PDGAssetLink; + + // Timer that is used to trigger creation of UStaticMesh for all mesh outputs + // that still have UHoudiniStaticMeshes. The timer is cleared on PreCook and reset + // at the end of the PostCook. + UPROPERTY() + FTimerHandle RefineMeshesTimer; + + // Delegate that is used to broadcast when RefineMeshesTimer fires + FOnRefineMeshesTimerDelegate OnRefineMeshesTimerDelegate; + + // If true, don't build a proxy mesh next cook (regardless of global or override settings), + // instead build the UStaticMesh directly (if applicable for the output types). + UPROPERTY(DuplicateTransient) + bool bNoProxyMeshNextCookRequested; + + // Maps a UObject to an Input number, used to preset the asset's inputs + UPROPERTY(Transient, DuplicateTransient) + TMap InputPresets; + + // If true, bake the asset after its next cook. + UPROPERTY(DuplicateTransient) + bool bBakeAfterNextCook; + + // Delegate to broadcast after a post cook event + // Arguments are (HoudiniAssetComponent* HAC, bool IsSuccessful) + FOnPostCookDelegate OnPostCookDelegate; + + // Delegate to broadcast when baking after a cook. + // Currently we cannot call the bake functions from here (Runtime module) + // or from the HoudiniEngineManager (HoudiniEngine) module, so we use + // a delegate. + FOnPostCookBakeDelegate OnPostCookBakeDelegate; + + // Delegate to broadcast after baking the HAC. Not called when just baking individual outputs directly. + // Arguments are (HoudiniAssetComponent* HAC, bool bIsSuccessful) + FOnPostBakeDelegate OnPostBakeDelegate; + + // Delegate that is broadcast when the asset state changes (HAC version). + FOnAssetStateChangeDelegate OnAssetStateChangeDelegate; + + // Cached flag of whether this object is considered to be a 'preview' component or not. + // This is typically useful in destructors when references to the World, for example, + // is no longer available. + UPROPERTY(Transient, DuplicateTransient) + bool bCachedIsPreview; + + USimpleConstructionScript* GetSCS() const; + + // Object used to convert V1 HAC to V2 HAC + UHoudiniAssetComponent_V1* Version1CompatibilityHAC; + + // The last timestamp this component was ticked + // used to prioritize/limit the number of HAC processed per tick + UPROPERTY(Transient) + double LastTickTime; + + // + // Begin: IHoudiniAssetStateEvents + // + + // Delegate that is broadcast when AssetState changes + FOnHoudiniAssetStateChange OnHoudiniAssetStateChangeDelegate; + + // + // End: IHoudiniAssetStateEvents + // + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h new file mode 100644 index 000000000..0144fe633 --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h @@ -0,0 +1,92 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + + +UENUM() +enum class EHoudiniAssetState : uint8 +{ + // Loaded / Duplicated HDA, + // Will need to be instantiated upon change/update + NeedInstantiation, + + // Newly created HDA, fetch its default parameters then proceed to PreInstantiation + NewHDA, + + // Newly created HDA, after default parameters fetch, needs to be instantiated immediately + PreInstantiation, + + // Instantiating task in progress + Instantiating, + + // Instantiated HDA, needs to be cooked immediately + PreCook, + + // Cooking task in progress + Cooking, + + // Cooking has finished + PostCook, + + // Cooked HDA, needs to be processed immediately + PreProcess, + + // Processing task in progress + Processing, + + // Processed / Updated HDA + // Will need to be cooked upon change/update + None, + + // Asset needs to be rebuilt (Deleted/Instantiated/Cooked) + NeedRebuild, + + // Asset needs to be deleted + NeedDelete, + + // Deleting + Deleting, + + // Process component template. This is ticking has very limited + // functionality, typically limited to checking for parameter updates + // in order to trigger PostEditChange() to run construction scripts again. + ProcessTemplate, +}; + +UENUM() +enum class EHoudiniAssetStateResult : uint8 +{ + None, + Working, + Success, + FinishedWithError, + FinishedWithFatalError, + Aborted +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp index 42a5c38b3..cf650ccac 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp @@ -1,1802 +1,1802 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniCompatibilityHelpers.h" - -#include "HoudiniPluginSerializationVersion.h" - -#include "HoudiniInput.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniHandleComponent.h" - -#include "Engine/StaticMesh.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "EngineUtils.h" // for TActorIterator<> - -#if WITH_EDITOR - #include "Editor.h" - #include "Editor/UnrealEdEngine.h" - #include "UnrealEdGlobals.h" -#endif - -// TODO: -// HoudiniInstancedActorComponent ? -// HoudiniMeshSplitInstancerComponent ? - -UHoudiniAssetComponent_V1::UHoudiniAssetComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - - -void -UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) -{ - if (!Ar.IsLoading()) - return; - - //Super::Serialize(Ar); - - //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Serialize component flags. - Ar << HoudiniAssetComponentFlagsPacked; - - // Serialize format version. - uint32 HoudiniAssetComponentVersion = 0;//Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - Ar << HoudiniAssetComponentVersion; - - // ComponenState Enum, saved as uint8 - // 0 invalid - // 1 None - // 2 Instantiated - // 3 BeingCooked - // Serialize component state. - uint8 ComponentState = 0; - Ar << ComponentState; - - // Serialize scaling information and import axis. - Ar << GeneratedGeometryScaleFactor; - Ar << TransformScaleFactor; - - // ImportAxis Enum, saved as uint8 - // 0 unreal 1 Houdini - //uint8 ImportAxis = 0; - Ar << ImportAxis; - - // Serialize generated component GUID. - Ar << ComponentGUID; - - // If component is in invalid state, we can skip the rest of serialization. - //if (ComponentState == EHoudiniAssetComponentState_V1::Invalid) - if (ComponentState == 0) - return; - - // Serialize Houdini asset. - Ar << HoudiniAsset; - - // 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; - int TempId; - Ar << TempId; - bIsPlayModeActive_Unused = false; - } - - // Serialization of default preset. - Ar << DefaultPresetBuffer; - - // Serialization of preset. - { - bool bPresetSaved = false; - Ar << bPresetSaved; - - if (bPresetSaved) - { - Ar << PresetBuffer; - } - } - - // Serialize parameters. - //SerializeParameters(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::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; - } - - // 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::TIterator IterParams(Parameters); IterParams; ++IterParams) - { - UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); - if (HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill()) - ParameterByName.Add(HoudiniAssetParameter->ParameterName, HoudiniAssetParameter); - } - } - } - - // Serialize inputs. - //SerializeInputs(Ar); - { - Ar << Inputs; - - /* - if (Ar.IsLoading()) - { - for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) - { - UHoudiniAssetInput_V1 * HoudiniAssetInput = Inputs[InputIdx]; - if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) - Inputs[InputIdx]->SetHoudiniAssetComponent(this); - } - } - */ - } - - // 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); - { - //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; - int HoudiniInstanceInputKey = -1; - - Ar << HoudiniInstanceInputKey; - Ar << InstanceInputs[InstanceInputIdx]; - } - } - } - - // 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 DirtyPackages; - if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES) - { - TMap SavedPackages; - Ar << SavedPackages; - } - - if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS) - { - // Temporary Mesh Packages - TMap MeshPackages; - Ar << MeshPackages; - - // Temporary Landscape Layers Packages - TMap LayerPackages; - Ar << LayerPackages; - } -} - -uint32 -GetTypeHash(const FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) -{ - return HoudiniGeoPartObject.GetTypeHash(); -} - -FArchive & -operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) -{ - HoudiniGeoPartObject.Serialize(Ar); - return Ar; -} - -uint32 -FHoudiniGeoPartObject_V1::GetTypeHash() const -{ - int32 HashBuffer[4] = { ObjectId, GeoId, PartId, SplitId }; - int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); - return FCrc::StrCrc32(*SplitName, Hash); -} - -bool -FHoudiniGeoPartObject_V1SortPredicate::operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const -{ - /*if (!A.IsValid() || !B.IsValid()) - return false;*/ - - if (A.ObjectId == B.ObjectId) - { - if (A.GeoId == B.GeoId) - { - if (A.PartId == B.PartId) - return A.SplitId < B.SplitId; - else - return A.PartId < B.PartId; - } - else - { - return A.GeoId < B.GeoId; - } - } - - return A.ObjectId < B.ObjectId; -} - -void -FHoudiniGeoPartObject_V1::Serialize(FArchive & Ar) -{ - //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniGeoPartObjectVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniGeoPartObjectVersion; - - Ar << TransformMatrix; - - Ar << ObjectName; - Ar << PartName; - Ar << SplitName; - - // Serialize instancer material. - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME) - Ar << InstancerMaterialName; - - // Serialize instancer attribute material. - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME) - Ar << InstancerAttributeMaterialName; - - Ar << AssetId; - Ar << ObjectId; - Ar << GeoId; - Ar << PartId; - Ar << SplitId; - - Ar << HoudiniGeoPartObjectFlagsPacked; - - if (HoudiniGeoPartObjectVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - // Prior to this version the unused flags space was not zero-initialized, so - // zero them out now to prevent misinterpreting any 1s - HoudiniGeoPartObjectFlagsPacked &= 0x3FFFF; - } - - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH) - { - Ar << NodePath; - } -} - - -FHoudiniGeoPartObject -FHoudiniGeoPartObject_V1::ConvertLegacyData() -{ - FHoudiniGeoPartObject NewHGPO; - - NewHGPO.AssetId = AssetId; - // NewHGPO.AssetName; - - NewHGPO.ObjectId = ObjectId; - NewHGPO.ObjectName = ObjectName; - - NewHGPO.GeoId = GeoId; - - NewHGPO.PartId = PartId; - NewHGPO.PartName = PartName; - NewHGPO.bHasCustomPartName = bHasCustomName; - - NewHGPO.SplitGroups.Add(SplitName); - - NewHGPO.TransformMatrix = TransformMatrix; - NewHGPO.NodePath = NodePath; - - // NewHGPO.VolumeName; - // NewHGPO.VolumeTileIndex; - - NewHGPO.bIsVisible = bIsVisible; - NewHGPO.bIsEditable = bIsEditable; - // NewHGPO.bIsTemplated; - // NewHGPO.bIsInstanced; - - NewHGPO.bHasGeoChanged = bHasGeoChanged; - // NewHGPO.bHasPartChanged; - // NewHGPO.bHasTransformChanged; - // NewHGPO.bHasMaterialsChanged; - NewHGPO.bLoaded = true; //bIsLoaded; - - // Hamdle Part Type - if (bIsCurve) - { - NewHGPO.Type = EHoudiniPartType::Curve; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - else if (bIsVolume) - { - NewHGPO.Type = EHoudiniPartType::Volume; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - else if (bIsInstancer) - { - NewHGPO.Type = EHoudiniPartType::Instancer; - NewHGPO.InstancerType = EHoudiniInstancerType::ObjectInstancer; - } - else if (bIsPackedPrimitiveInstancer) - { - NewHGPO.Type = EHoudiniPartType::Instancer; - NewHGPO.InstancerType = EHoudiniInstancerType::PackedPrimitive; - } - else - { - NewHGPO.Type = EHoudiniPartType::Mesh; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - - // Instancer specific flags - if (NewHGPO.Type == EHoudiniPartType::Instancer) - { - //bInstancerMaterialAvailable - //bInstancerAttributeMaterialAvailable - //InstancerMaterialName - //InstancerAttributeMaterialName - } - - // Collision specific flags - if (NewHGPO.Type == EHoudiniPartType::Mesh) - { - //bIsCollidable - //bIsRenderCollidable - //bIsUCXCollisionGeo - //bIsSimpleCollisionGeo - //bHasCollisionBeenAdded - //bHasSocketBeenAdded - } - - //bIsBox - //bIsSphere - - if (NewHGPO.SplitGroups.Num() <= 0) - { - NewHGPO.SplitGroups.Add("main_geo"); - } - - return NewHGPO; -} - -UHoudiniAssetInput::UHoudiniAssetInput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} -void -UHoudiniAssetInput::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Serialize current choice selection. - // Enum serialized as uint8 - // 0 GeometryInput - // 1 AssetInput - // 2 CurveInput - // 3 LandscapeInput - // 4 WorldInput - // 5 SkeletonInput - //SerializeEnumeration< EHoudiniAssetInputType::Enum >(Ar, ChoiceIndex); - Ar << ChoiceIndex; - - Ar << ChoiceStringValue; - - // We need these temporary variables for undo state tracking. - bool bLocalInputAssetConnectedInHoudini = bInputAssetConnectedInHoudini; - UHoudiniAssetComponent_V1 * LocalInputAssetComponent = InputAssetComponent; - - Ar << HoudiniAssetInputFlagsPacked; - - // Serialize input index. - Ar << InputIndex; - - // Serialize input objects (if it's assigned). - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT) - { - Ar << InputObjects; - } - else - { - UObject* InputObject = nullptr; - Ar << InputObject; - InputObjects.Empty(); - InputObjects.Add(InputObject); - } - - // Serialize input asset. - Ar << InputAssetComponent; - - // Serialize curve and curve parameters (if we have those). - Ar << InputCurve; - Ar << InputCurveParameters; - - // Serialize landscape used for input. - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT) - { - if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF) - { - ALandscapeProxy* InputLandscapePtr = nullptr; - Ar << InputLandscapePtr; - - InputLandscapeProxy = InputLandscapePtr; - } - else - { - Ar << InputLandscapeProxy; - } - - } - - // Serialize world outliner inputs. - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT) - { - Ar << InputOutlinerMeshArray; - } - - // Create necessary widget resources. - bLoadedParameter = true; - // If we're loading for real for the first time we need to reset this - // flag so we can reconnect when we get our parameters uploaded. - bInputAssetConnectedInHoudini = false; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT) - Ar << UnrealSplineResolution; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS) - { - Ar << InputTransforms; - } - else - { - InputTransforms.SetNum(InputObjects.Num()); - for (int32 n = 0; n < InputTransforms.Num(); n++) - InputTransforms[n] = FTransform::Identity; - } - - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - Ar << InputLandscapeTransform; -} - -UHoudiniInput* -UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) -{ - UHoudiniInput* Input = NewObject( - InOuter, UHoudiniInput::StaticClass(), FName(*ParameterLabel), RF_Transactional); - - EHoudiniInputType InputType = EHoudiniInputType::Invalid; - if (ChoiceIndex == 0) - InputType = EHoudiniInputType::Geometry; - else if (ChoiceIndex == 1) - InputType = EHoudiniInputType::Asset; - else if (ChoiceIndex == 2) - InputType = EHoudiniInputType::Curve; - else if (ChoiceIndex == 3) - InputType = EHoudiniInputType::Landscape; - else if (ChoiceIndex == 4) - InputType = EHoudiniInputType::World; - else if (ChoiceIndex == 5) - { - //InputType = EHoudiniInputType::Skeletal; - InputType = EHoudiniInputType::Invalid; - } - else - { - // Invalid - InputType = EHoudiniInputType::Invalid; - } - - bool bBlueprintStructureModified = false; - Input->SetInputType(InputType, bBlueprintStructureModified); - - Input->SetExportColliders(false); - Input->SetExportLODs(bExportAllLODs); - Input->SetExportSockets(bExportSockets); - Input->SetCookOnCurveChange(true); - - // If KeepWorldTransform is set to 2, use the default value - if (bKeepWorldTransform == 2) - Input->SetKeepWorldTransform((bool)Input->GetDefaultXTransformType()); - else - Input->SetKeepWorldTransform((bool)bKeepWorldTransform); - - Input->SetUnrealSplineResolution(UnrealSplineResolution); - Input->SetPackBeforeMerge(bPackBeforeMerge); - if(bIsObjectPathParameter) - Input->SetObjectPathParameter(ParmId); - else - Input->SetSOPInput(InputIndex); - - Input->SetImportAsReference(false); - Input->SetHelp(ParameterHelp); - //Input->SetInputNodeId(-1); - - if (bLandscapeExportAsHeightfield) - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Heightfield); - else if (bLandscapeExportAsMesh) - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Mesh); - else - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Points); - - Input->SetLabel(ParameterLabel); - Input->SetName(ParameterName); - Input->SetUpdateInputLandscape(bUpdateInputLandscape); - - if (InputType == EHoudiniInputType::Geometry) - { - // Get the geo input object array - bool bNeedToEmpty = true; - TArray* GeoInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(GeoInputObjectsPtr)) - { - // Add the geometry input objects - for (int32 AtIndex = 0; AtIndex < InputObjects.Num(); AtIndex++) - { - // Create a new InputObject wrapper - UObject* CurObject = InputObjects[AtIndex]; - if (!CurObject || CurObject->IsPendingKill()) - continue; - - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurObject, Input, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - continue; - - // Remove the default/null object - if (bNeedToEmpty) - { - GeoInputObjectsPtr->Empty(); - bNeedToEmpty = false; - } - - // Add to the geo input array - GeoInputObjectsPtr->Add(NewInputObject); - } - } - } - else if (InputType == EHoudiniInputType::Asset && InputAssetComponent != nullptr && !InputAssetComponent->IsPendingKill()) - { - // Get the asset input object array - TArray* AssetInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(AssetInputObjectsPtr)) - { - // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent - // We can simply use the v1's HAC outer for that - UHoudiniAssetComponent* InputHAC = Cast(InputAssetComponent->GetOuter()); - if (InputHAC && !InputHAC->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputHAC, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the asset input array - AssetInputObjectsPtr->Add(NewInputObject); - } - } - } - } - else if (InputType == EHoudiniInputType::Curve) - { - // Get the curve input object array - TArray* CurveInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(CurveInputObjectsPtr)) - { - if (InputCurve && !InputCurve->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputCurve, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the curve input array - CurveInputObjectsPtr->Add(NewInputObject); - } - - // InputCurve->SetInputObject(NewInputObject); - - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = Cast(NewInputObject); - if(HoudiniSplineInput) - HoudiniSplineInput->Update(InputCurve); - } - } - - // TODO ??? - //InputCurveParameters; - } - else if (InputType == EHoudiniInputType::Landscape) - { - // Get the Landscape input object array - TArray* LandscapeInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(LandscapeInputObjectsPtr)) - { - // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent - // We can simply use the v1's HAC outer for that - ALandscapeProxy* InLandscape = InputLandscapeProxy.Get(); - if (InLandscape && !InLandscape->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InLandscape, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the geo input array - LandscapeInputObjectsPtr->Add(NewInputObject); - } - } - } - - Input->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; - Input->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; - Input->bLandscapeExportLighting = bLandscapeExportLighting; - Input->bLandscapeExportMaterials = bLandscapeExportMaterials; - Input->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; - Input->bLandscapeExportTileUVs = bLandscapeExportTileUVs; - - //bLandscapeExportCurves; - } - else if (InputType == EHoudiniInputType::World) - { - // Get the world input object array - TArray* WorldInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - - UWorld* MyWorld = InOuter->GetWorld(); - if (ensure(WorldInputObjectsPtr)) - { - // Add the geometry input objects - for (int32 AtIndex = 0; AtIndex < InputOutlinerMeshArray.Num(); AtIndex++) - { - FHoudiniAssetInputOutlinerMesh_V1& CurWorldInObj = InputOutlinerMeshArray[AtIndex]; - - AActor* CurActor = nullptr; - if (CurWorldInObj.ActorPtr.IsValid()) - { - CurActor = CurWorldInObj.ActorPtr.Get(); - } - else - { - // Try to update the actor ptr via the pathname - CurWorldInObj.TryToUpdateActorPtrFromActorPathName(MyWorld); - if (CurWorldInObj.ActorPtr.IsValid()) - { - CurActor = CurWorldInObj.ActorPtr.Get(); - } - } - - if(!CurActor || CurActor->IsPendingKill()) - continue; - - // Create a new InputObject wrapper for the actor - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurActor, Input, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - continue; - - // Add to the geo input array - WorldInputObjectsPtr->Add(NewInputObject); - } - } - - /* - CurWorldInObj->HoudiniAssetParameterVersion; - CurWorldInObj->ActorPtr; - CurWorldInObj->ActorPathName; - CurWorldInObj->StaticMeshComponent; - CurWorldInObj->StaticMesh; - CurWorldInObj->SplineComponent; - CurWorldInObj->NumberOfSplineControlPoints; - CurWorldInObj->SplineControlPointsTransform; - CurWorldInObj->SplineLength; - CurWorldInObj->SplineResolution; - CurWorldInObj->ActorTransform; - CurWorldInObj->ComponentTransform; - CurWorldInObj->AssetId; - CurWorldInObj->KeepWorldTransform; - CurWorldInObj->MeshComponentsMaterials; - CurWorldInObj->InstanceIndex; - */ - - //InputOutlinerMeshArray; - } - else - { - // Invalid - } - - //ChoiceStringValue; - //bStaticMeshChanged; - //bSwitchedToCurve; - //bLoadedParameter = true; - //bInputAssetConnectedInHoudini; - //InputTransforms; - //InputLandscapeTransform; - - return Input; -} - -FArchive& -operator<<(FArchive& Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh) -{ - HoudiniAssetInputOutlinerMesh.Serialize(Ar); - return Ar; -} - -void -FHoudiniAssetInputOutlinerMesh_V1::Serialize(FArchive& Ar) -{ - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniAssetParameterVersion; - - Ar << ActorPtr; - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - { - Ar << ActorPathName; - } - - if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) - { - Ar << StaticMeshComponent; - Ar << StaticMesh; - } - - Ar << ActorTransform; - - Ar << AssetId; - if (Ar.IsLoading() && !Ar.IsTransacting()) - AssetId = -1; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE - && HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) - { - Ar << SplineComponent; - Ar << NumberOfSplineControlPoints; - Ar << SplineLength; - Ar << SplineResolution; - Ar << ComponentTransform; - } - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM) - Ar << KeepWorldTransform; - - // UE4.19 SERIALIZATION FIX: - // The component materials serialization (24) was actually missing in the UE4.19 H17.0 / H16.5 plugin. - // However subsequent serialized changes (25+) were present in those version. This caused crashes when loading - // a level that was saved with 4.19+16.5/17.0 on a newer version of Unreal or Houdini... - // If the serialized version is exactly that of the fix, we can ignore the materials paths as well - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - Ar << MeshComponentsMaterials; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX) - Ar << InstanceIndex; -} - -bool -FHoudiniAssetInputOutlinerMesh_V1::TryToUpdateActorPtrFromActorPathName(UWorld* InWorld) -{ - // Ensure our current ActorPathName looks valid - if (ActorPathName.IsEmpty() || ActorPathName.Equals(TEXT("None"), ESearchCase::IgnoreCase)) - return false; - - // We'll try to find the corresponding actor by browsing through all the actors in the world.. - // Get the editor world - UWorld* World = InWorld; - if (!World) - return false; - - // Then try to find the actor corresponding to our path/name - bool FoundActor = false; - for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) - { - if (ActorIt->GetPathName() != ActorPathName) - continue; - - // We found the actor - ActorPtr = *ActorIt; - FoundActor = true; - - break; - } - - if (FoundActor) - { - // We need to invalid our components so they can be updated later - // from the new actor - StaticMesh = NULL; - StaticMeshComponent = NULL; - SplineComponent = NULL; - } - - return FoundActor; -} - -UHoudiniAssetComponentMaterials_V1::UHoudiniAssetComponentMaterials_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} -void -UHoudiniAssetComponentMaterials_V1::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Assignments; - Ar << Replacements; -} - -UHoudiniHandleComponent_V1::UHoudiniHandleComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniAssetInstanceInput::UHoudiniAssetInstanceInput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetInstanceInput::Serialize(FArchive& Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Flags.HoudiniAssetInstanceInputFlagsPacked; - Ar << HoudiniGeoPartObject; - - Ar << ObjectToInstanceId; - // Object id is transient - if (Ar.IsLoading() && !Ar.IsTransacting()) - ObjectToInstanceId = -1; - - // Serialize fields. - Ar << InstanceInputFields; -} - -UHoudiniAssetInstanceInputField::UHoudiniAssetInstanceInputField(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetInstanceInputField::Serialize(FArchive& Ar) -{ - // Call base implementation first. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - const int32 InstanceInputFieldVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - - Ar << HoudiniAssetInstanceInputFieldFlagsPacked; - Ar << HoudiniGeoPartObject; - - FString UnusedInstancePathName; - Ar << UnusedInstancePathName; - Ar << RotationOffsets; - Ar << ScaleOffsets; - Ar << bScaleOffsetsLinearlyArray; - - Ar << InstancedTransforms; - Ar << VariationTransformsArray; - - if (Ar.IsSaving() || (Ar.IsLoading() && InstanceInputFieldVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS)) - { - Ar << InstanceColorOverride; - Ar << VariationInstanceColorOverrideArray; - } - - Ar << InstancerComponents; - Ar << InstancedObjects; - Ar << OriginalObject; -} - -void -UHoudiniHandleComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // XFormn Parames is an array of 9 float params + tuple index - // TX TY TZ - // RX RY RZ - // SX SY SZ - - //UHoudiniAssetParameterFloat_V1* XFormParams[9]; - //int32 XFormParamsTupleIndex[9]; - for (int32 i = 0; i < 9; ++i) - { - Ar << XFormParams[i]; - Ar << XFormParamsTupleIndex[i]; - } - - //UHoudiniAssetParameterChoice_V1* RSTParm; - Ar << RSTParm; - //int32 RSTParmTupleIdx; - Ar << RSTParmTupleIdx; - - //UHoudiniAssetParameterChoice_V1* RotOrderParm; - Ar << RotOrderParm; - //int32 RotOrderParmTupleIdx; - Ar << RotOrderParmTupleIdx; -} - -/* -UHoudiniHandleComponent* -UHoudiniHandleComponent_V1::ConvertLegacyData(UObject* Outer) -{ - UHoudiniHandleComponent* NewHandle = nullptr; - - return NewHandle; -} -*/ - -bool -UHoudiniHandleComponent_V1::UpdateFromLegacyData(UHoudiniHandleComponent* NewHC) -{ - if (!NewHC || NewHC->IsPendingKill()) - return false; - - // TODO - //NewHC->XformParms; - //NewHC->RSTParm; - //NewHC->RotOrderParm; - //NewHC->HandleType; - //NewHC->HandleName; - - return true; -} - -UHoudiniSplineComponent_V1::UHoudiniSplineComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniSplineComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - int32 Version = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << Version; - - Ar << HoudiniGeoPartObject; - - if (Version < VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM) - { - // Before, curve points where stored as Vectors, not Transforms - TArray OldCurvePoints; - Ar << OldCurvePoints; - - CurvePoints.SetNumUninitialized(OldCurvePoints.Num()); - - FTransform trans = FTransform::Identity; - for (int32 n = 0; n < CurvePoints.Num(); n++) - { - trans.SetLocation(OldCurvePoints[n]); - CurvePoints[n] = trans; - } - } - else - { - Ar << CurvePoints; - } - - Ar << CurveDisplayPoints; - - Ar << CurveType; - Ar << CurveMethod; - Ar << bClosedCurve; -} - -UHoudiniSplineComponent* -UHoudiniSplineComponent_V1::ConvertLegacyData(UObject* Outer) -{ - UHoudiniSplineComponent* NewSpline = NewObject( - GetOuter(), UHoudiniSplineComponent::StaticClass()); - - UpdateFromLegacyData(NewSpline); - - return NewSpline; -} - -bool -UHoudiniSplineComponent_V1::UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline) -{ - if (!NewSpline || NewSpline->IsPendingKill()) - return false; - - NewSpline->SetFlags(RF_Transactional); - - NewSpline->CurvePoints = CurvePoints; - NewSpline->DisplayPoints = CurveDisplayPoints; - //NewSpline->DisplayPointIndexDivider; - //NewSpline->HoudiniSplineName; - NewSpline->bClosed = bClosedCurve; - NewSpline->bReversed = false; - NewSpline->bIsHoudiniSplineVisible = true; - - //0 Polygon 1 Nurbs 2 Bezier - if (CurveType == 0) - NewSpline->CurveType = EHoudiniCurveType::Polygon; - else if (CurveType == 1) - NewSpline->CurveType = EHoudiniCurveType::Nurbs; - else if (CurveType == 2) - NewSpline->CurveType = EHoudiniCurveType::Bezier; - else - NewSpline->CurveType = EHoudiniCurveType::Invalid; - - // 0 CVs, 1 Breakpoints, 2 Freehand - if (CurveMethod == 0) - NewSpline->CurveMethod = EHoudiniCurveMethod::CVs; - else if (CurveMethod == 1) - NewSpline->CurveMethod = EHoudiniCurveMethod::Breakpoints; - else if (CurveMethod == 2) - NewSpline->CurveMethod = EHoudiniCurveMethod::Freehand; - else - NewSpline->CurveMethod = EHoudiniCurveMethod::Invalid; - - NewSpline->bIsOutputCurve = false; - - NewSpline->HoudiniGeoPartObject = HoudiniGeoPartObject.ConvertLegacyData(); - - if (NewSpline->HoudiniGeoPartObject.bIsEditable) - { - NewSpline->bIsEditableOutputCurve = true; - NewSpline->bIsInputCurve = false; - } - else - { - NewSpline->bIsInputCurve = false; - NewSpline->bIsEditableOutputCurve = true; - } - - // Create a default Houdini spline input if a null pointer is passed in. - FName HoudiniSplineName = MakeUniqueObjectName(GetOuter(), UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); - NewSpline->SetHoudiniSplineName(HoudiniSplineName.ToString()); - - //NewSpline->bHasChanged; - //NewSpline->bNeedsToTriggerUpdate; - //NewSpline->InputObject; - //NewSpline->NodeId; - //NewSpline->PartName; - - return true; -} - -UHoudiniAssetParameter::UHoudiniAssetParameter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameter::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniAssetParameterVersion; - - Ar << HoudiniAssetParameterFlagsPacked; - - if (Ar.IsLoading()) - bChanged = false; - - Ar << ParameterName; - Ar << ParameterLabel; - - Ar << NodeId; - if (!Ar.IsTransacting() && Ar.IsLoading()) - { - // NodeId is invalid after load - NodeId = -1; - } - Ar << ParmId; - - Ar << ParmParentId; - Ar << ChildIndex; - - Ar << TupleSize; - Ar << ValuesIndex; - Ar << MultiparmInstanceIndex; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER) - { - UObject* Dummy = nullptr; - Ar << Dummy; - } - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP) - { - Ar << ParameterHelp; - } - else - { - ParameterHelp = TEXT(""); - } - /* - if (Ar.IsTransacting()) - { - Ar << PrimaryObject; - Ar << ParentParameter; - } - */ -} - -UHoudiniParameter* -UHoudiniAssetParameter::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameter::Create(Outer, ParameterName); -} - -void -UHoudiniAssetParameter::CopyLegacyParameterData(UHoudiniParameter* InNewParm) -{ - if (!InNewParm || InNewParm->IsPendingKill()) - return; - - InNewParm->Name = ParameterName; - InNewParm->Label = ParameterLabel; - //InNewParm->ParmType; - InNewParm->TupleSize = TupleSize; - InNewParm->NodeId = NodeId; - InNewParm->ParmId = ParmId; - InNewParm->ParentParmId = ParmParentId; - InNewParm->ChildIndex = ChildIndex; - InNewParm->bIsVisible = true; - InNewParm->bIsDisabled = bIsDisabled; - InNewParm->bHasChanged = bChanged; - //InNewParm->bNeedsToTriggerUpdate; - //InNewParm->bIsDefault; - InNewParm->bIsSpare = bIsSpare; - InNewParm->bJoinNext = false; - InNewParm->bIsChildOfMultiParm = bIsChildOfMultiparm; - // TODO: MultiparmInstanceIndex ? - //InNewParm->bIsDirectChildOfMultiParm; - InNewParm->bPendingRevertToDefault = false; - //InNewParm->TuplePendingRevertToDefault = false; - InNewParm->Help = ParameterHelp; - InNewParm->TagCount = 0; - InNewParm->ValueIndex = ValuesIndex; - //InNewParm->bHasExpression; - //InNewParm->bShowExpression; - //InNewParm->ParamExpression; - //InNewParm->Tags; - InNewParm->bAutoUpdate = true; -} - -UHoudiniAssetParameterChoice::UHoudiniAssetParameterChoice(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterChoice::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - { - StringChoiceValues.Empty(); - StringChoiceLabels.Empty(); - } - - int32 NumChoices = StringChoiceValues.Num(); - Ar << NumChoices; - - int32 NumLabels = StringChoiceLabels.Num(); - Ar << NumLabels; - - if (Ar.IsLoading()) - { - FString Temp; - for (int32 ChoiceIdx = 0; ChoiceIdx < NumChoices; ++ChoiceIdx) - { - Ar << Temp; - StringChoiceValues.Add(Temp); - } - - for (int32 LabelIdx = 0; LabelIdx < NumLabels; ++LabelIdx) - { - Ar << Temp; - StringChoiceLabels.Add(Temp); - } - } - - Ar << StringValue; - Ar << CurrentValue; - - Ar << bStringChoiceList; -} - -UHoudiniParameter* -UHoudiniAssetParameterChoice::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterChoice* Parm = nullptr; - if (bStringChoiceList) - { - Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::StringChoice); - } - else - { - Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::IntChoice); - } - - Parm->SetNumChoices(StringChoiceValues.Num()); - for (int32 Idx = 0; Idx < StringChoiceValues.Num(); Idx++) - { - FString * ChoiceValue = Parm->GetStringChoiceValueAt(Idx); - if (ChoiceValue) - *ChoiceValue = StringChoiceValues[Idx]; - FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); - } - - for (int32 Idx = 0; Idx < StringChoiceLabels.Num(); Idx++) - { - FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); - if (ChoiceLabel) - *ChoiceLabel = StringChoiceValues[Idx]; - } - - Parm->SetStringValue(StringValue); - Parm->SetIntValue(CurrentValue); - //Parm->DefaultStringValue = StringValue; - //Parm->SetDefault(); - //Parm->DefaultIntValue = CurrentValue; - - return Parm; -} - -UHoudiniAssetParameterButton::UHoudiniAssetParameterButton(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterButton::ConvertLegacyData(UObject* Outer) -{ - // Button strips where not supported in v1, just create a normal button - return UHoudiniParameterButton::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterColor::UHoudiniAssetParameterColor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterColor::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - Color = FColor::White; - - Ar << Color; -} - -UHoudiniParameter* -UHoudiniAssetParameterColor::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterColor* Parm = UHoudiniParameterColor::Create(Outer, ParameterName); - Parm->SetColorValue(Color); - - //Parm->DefaultColor = Color; - Parm->SetDefaultValue(); - - //Parm->bIsChildOfRamp = false; - - return Parm; -} - -UHoudiniAssetParameterFile::UHoudiniAssetParameterFile(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterFile::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - Ar << Filters; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY) - Ar << IsReadOnly; -} - -UHoudiniParameter* -UHoudiniAssetParameterFile::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterFile* Parm = UHoudiniParameterFile::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - Parm->SetFileFilters(Filters); - Parm->SetReadOnly(IsReadOnly); - - return Parm; -} - -UHoudiniAssetParameterFloat::UHoudiniAssetParameterFloat(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterFloat::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - - Ar << ValueMin; - Ar << ValueMax; - - Ar << ValueUIMin; - Ar << ValueUIMax; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) - Ar << ValueUnit; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP) - Ar << NoSwap; -} - -UHoudiniParameter* -UHoudiniAssetParameterFloat::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterFloat* Parm = UHoudiniParameterFloat::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - Parm->SetUnit(ValueUnit); - Parm->SetHasMin(true); - Parm->SetHasMax(true); - Parm->SetHasUIMin(true); - Parm->SetHasUIMax(true); - Parm->SetIsLogarithmic(false); - Parm->SetMin(ValueMin); - Parm->SetMax(ValueMax); - Parm->SetUIMin(ValueUIMin); - Parm->SetUIMax(ValueUIMax); - - //Parm->bIsChildOfRamp = false; - - return Parm; -} - -UHoudiniAssetParameterFolder::UHoudiniAssetParameterFolder(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterFolder::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterFolder::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterFolderList::UHoudiniAssetParameterFolderList(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterFolderList::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterFolderList::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterInt::UHoudiniAssetParameterInt(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterInt::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - - Ar << ValueMin; - Ar << ValueMax; - - Ar << ValueUIMin; - Ar << ValueUIMax; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) - Ar << ValueUnit; -} - -UHoudiniParameter* -UHoudiniAssetParameterInt::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterInt* Parm = UHoudiniParameterInt::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - - //Parm->DefaultValues = Values; - Parm->SetUnit(ValueUnit); - Parm->SetHasMin(true); - Parm->SetHasMax(true); - Parm->SetHasUIMin(true); - Parm->SetHasUIMax(true); - Parm->SetIsLogarithmic(false); - Parm->SetMin(ValueMin); - Parm->SetMax(ValueMax); - Parm->SetUIMin(ValueUIMin); - Parm->SetUIMax(ValueUIMax); - - return Parm; -} - -UHoudiniAssetParameterLabel::UHoudiniAssetParameterLabel(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterLabel::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterLabel::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterMultiparm::UHoudiniAssetParameterMultiparm(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterMultiparm::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - MultiparmValue = 0; - - Ar << MultiparmValue; -} - -UHoudiniParameter* -UHoudiniAssetParameterMultiparm::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterMultiParm* Parm = UHoudiniParameterMultiParm::Create(Outer, ParameterName); - - //Parm->bIsShown; - //Parm->Value; - //Parm->TemplateName; - Parm->MultiparmValue = MultiparmValue; - //Parm->MultiParmInstanceNum; - //Parm->MultiParmInstanceLength; - //Parm->MultiParmInstanceCount; - //Parm->InstanceStartOffset; - //Parm->DefaultInstanceCount; - - // TODO: - // MultiparmInstanceIndex? - - return Parm; -} - -UHoudiniAssetParameterRamp::UHoudiniAssetParameterRamp(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterRamp::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - int32 multiparmvalue = 0; - Ar << multiparmvalue; - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << HoudiniAssetParameterRampCurveFloat; - Ar << HoudiniAssetParameterRampCurveColor; - - Ar << bIsFloatRamp; -} - -UHoudiniParameter* -UHoudiniAssetParameterRamp::ConvertLegacyData(UObject* Outer) -{ - if (bIsFloatRamp) - { - UHoudiniParameterRampFloat* Parm = UHoudiniParameterRampFloat::Create(Outer, ParameterName); - - // TODO: - // Convert HoudiniAssetParameterRampCurveFloat - - return Parm; - } - else - { - UHoudiniParameterRampColor* Parm = UHoudiniParameterRampColor::Create(Outer, ParameterName); - - // TODO: - // Convert HoudiniAssetParameterRampCurveColor - - return Parm; - } -} - -UHoudiniAssetParameterSeparator::UHoudiniAssetParameterSeparator(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterSeparator::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterSeparator::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterString::UHoudiniAssetParameterString(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterString::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; -} - -UHoudiniParameter* -UHoudiniAssetParameterString::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterString* Parm = UHoudiniParameterString::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetIsAssetRef(false); - Parm->SetDefaultValues(); - - //Parm->DefaultValues = Values; - //Parm->ChosenAssets.Empty(); - //Parm->bIsAssetRef = false; - - return Parm; -} - -UHoudiniAssetParameterToggle::UHoudiniAssetParameterToggle(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterToggle::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; -} - -UHoudiniParameter* -UHoudiniAssetParameterToggle::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterToggle* Parm = UHoudiniParameterToggle::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt((bool)Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - return Parm; -} - -UHoudiniMeshSplitInstancerComponent_V1::UHoudiniMeshSplitInstancerComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniMeshSplitInstancerComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << InstancedMesh; - Ar << OverrideMaterial; - Ar << Instances; -} - -bool -UHoudiniMeshSplitInstancerComponent_V1::UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC) -{ - if (!NewMSIC || NewMSIC->IsPendingKill()) - return false; - - NewMSIC->Instances = Instances; - NewMSIC->OverrideMaterials.Add(OverrideMaterial); - NewMSIC->InstancedMesh = InstancedMesh; - - return true; -} - -UHoudiniInstancedActorComponent_V1::UHoudiniInstancedActorComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniInstancedActorComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << InstancedAsset; - Ar << Instances; -} - -bool -UHoudiniInstancedActorComponent_V1::UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC) -{ - if (!NewIAC || NewIAC->IsPendingKill()) - return false; - - //NewIAC->SetInstancedObject(InstancedAsset); - NewIAC->InstancedObject = InstancedAsset; - NewIAC->InstancedActors = Instances; - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniCompatibilityHelpers.h" + +#include "HoudiniPluginSerializationVersion.h" + +#include "HoudiniInput.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniHandleComponent.h" + +#include "Engine/StaticMesh.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "EngineUtils.h" // for TActorIterator<> + +#if WITH_EDITOR + #include "Editor.h" + #include "Editor/UnrealEdEngine.h" + #include "UnrealEdGlobals.h" +#endif + +// TODO: +// HoudiniInstancedActorComponent ? +// HoudiniMeshSplitInstancerComponent ? + +UHoudiniAssetComponent_V1::UHoudiniAssetComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + + +void +UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) +{ + if (!Ar.IsLoading()) + return; + + //Super::Serialize(Ar); + + //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Serialize component flags. + Ar << HoudiniAssetComponentFlagsPacked; + + // Serialize format version. + uint32 HoudiniAssetComponentVersion = 0;//Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + Ar << HoudiniAssetComponentVersion; + + // ComponenState Enum, saved as uint8 + // 0 invalid + // 1 None + // 2 Instantiated + // 3 BeingCooked + // Serialize component state. + uint8 ComponentState = 0; + Ar << ComponentState; + + // Serialize scaling information and import axis. + Ar << GeneratedGeometryScaleFactor; + Ar << TransformScaleFactor; + + // ImportAxis Enum, saved as uint8 + // 0 unreal 1 Houdini + //uint8 ImportAxis = 0; + Ar << ImportAxis; + + // Serialize generated component GUID. + Ar << ComponentGUID; + + // If component is in invalid state, we can skip the rest of serialization. + //if (ComponentState == EHoudiniAssetComponentState_V1::Invalid) + if (ComponentState == 0) + return; + + // Serialize Houdini asset. + Ar << HoudiniAsset; + + // 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; + int TempId; + Ar << TempId; + bIsPlayModeActive_Unused = false; + } + + // Serialization of default preset. + Ar << DefaultPresetBuffer; + + // Serialization of preset. + { + bool bPresetSaved = false; + Ar << bPresetSaved; + + if (bPresetSaved) + { + Ar << PresetBuffer; + } + } + + // Serialize parameters. + //SerializeParameters(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::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; + } + + // 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::TIterator IterParams(Parameters); IterParams; ++IterParams) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill()) + ParameterByName.Add(HoudiniAssetParameter->ParameterName, HoudiniAssetParameter); + } + } + } + + // Serialize inputs. + //SerializeInputs(Ar); + { + Ar << Inputs; + + /* + if (Ar.IsLoading()) + { + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) + { + UHoudiniAssetInput_V1 * HoudiniAssetInput = Inputs[InputIdx]; + if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) + Inputs[InputIdx]->SetHoudiniAssetComponent(this); + } + } + */ + } + + // 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); + { + //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; + int HoudiniInstanceInputKey = -1; + + Ar << HoudiniInstanceInputKey; + Ar << InstanceInputs[InstanceInputIdx]; + } + } + } + + // 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 DirtyPackages; + if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES) + { + TMap SavedPackages; + Ar << SavedPackages; + } + + if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS) + { + // Temporary Mesh Packages + TMap MeshPackages; + Ar << MeshPackages; + + // Temporary Landscape Layers Packages + TMap LayerPackages; + Ar << LayerPackages; + } +} + +uint32 +GetTypeHash(const FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) +{ + return HoudiniGeoPartObject.GetTypeHash(); +} + +FArchive & +operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) +{ + HoudiniGeoPartObject.Serialize(Ar); + return Ar; +} + +uint32 +FHoudiniGeoPartObject_V1::GetTypeHash() const +{ + int32 HashBuffer[4] = { ObjectId, GeoId, PartId, SplitId }; + int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitName, Hash); +} + +bool +FHoudiniGeoPartObject_V1SortPredicate::operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const +{ + /*if (!A.IsValid() || !B.IsValid()) + return false;*/ + + if (A.ObjectId == B.ObjectId) + { + if (A.GeoId == B.GeoId) + { + if (A.PartId == B.PartId) + return A.SplitId < B.SplitId; + else + return A.PartId < B.PartId; + } + else + { + return A.GeoId < B.GeoId; + } + } + + return A.ObjectId < B.ObjectId; +} + +void +FHoudiniGeoPartObject_V1::Serialize(FArchive & Ar) +{ + //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniGeoPartObjectVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniGeoPartObjectVersion; + + Ar << TransformMatrix; + + Ar << ObjectName; + Ar << PartName; + Ar << SplitName; + + // Serialize instancer material. + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME) + Ar << InstancerMaterialName; + + // Serialize instancer attribute material. + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME) + Ar << InstancerAttributeMaterialName; + + Ar << AssetId; + Ar << ObjectId; + Ar << GeoId; + Ar << PartId; + Ar << SplitId; + + Ar << HoudiniGeoPartObjectFlagsPacked; + + if (HoudiniGeoPartObjectVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + // Prior to this version the unused flags space was not zero-initialized, so + // zero them out now to prevent misinterpreting any 1s + HoudiniGeoPartObjectFlagsPacked &= 0x3FFFF; + } + + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH) + { + Ar << NodePath; + } +} + + +FHoudiniGeoPartObject +FHoudiniGeoPartObject_V1::ConvertLegacyData() +{ + FHoudiniGeoPartObject NewHGPO; + + NewHGPO.AssetId = AssetId; + // NewHGPO.AssetName; + + NewHGPO.ObjectId = ObjectId; + NewHGPO.ObjectName = ObjectName; + + NewHGPO.GeoId = GeoId; + + NewHGPO.PartId = PartId; + NewHGPO.PartName = PartName; + NewHGPO.bHasCustomPartName = bHasCustomName; + + NewHGPO.SplitGroups.Add(SplitName); + + NewHGPO.TransformMatrix = TransformMatrix; + NewHGPO.NodePath = NodePath; + + // NewHGPO.VolumeName; + // NewHGPO.VolumeTileIndex; + + NewHGPO.bIsVisible = bIsVisible; + NewHGPO.bIsEditable = bIsEditable; + // NewHGPO.bIsTemplated; + // NewHGPO.bIsInstanced; + + NewHGPO.bHasGeoChanged = bHasGeoChanged; + // NewHGPO.bHasPartChanged; + // NewHGPO.bHasTransformChanged; + // NewHGPO.bHasMaterialsChanged; + NewHGPO.bLoaded = true; //bIsLoaded; + + // Hamdle Part Type + if (bIsCurve) + { + NewHGPO.Type = EHoudiniPartType::Curve; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + else if (bIsVolume) + { + NewHGPO.Type = EHoudiniPartType::Volume; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + else if (bIsInstancer) + { + NewHGPO.Type = EHoudiniPartType::Instancer; + NewHGPO.InstancerType = EHoudiniInstancerType::ObjectInstancer; + } + else if (bIsPackedPrimitiveInstancer) + { + NewHGPO.Type = EHoudiniPartType::Instancer; + NewHGPO.InstancerType = EHoudiniInstancerType::PackedPrimitive; + } + else + { + NewHGPO.Type = EHoudiniPartType::Mesh; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + + // Instancer specific flags + if (NewHGPO.Type == EHoudiniPartType::Instancer) + { + //bInstancerMaterialAvailable + //bInstancerAttributeMaterialAvailable + //InstancerMaterialName + //InstancerAttributeMaterialName + } + + // Collision specific flags + if (NewHGPO.Type == EHoudiniPartType::Mesh) + { + //bIsCollidable + //bIsRenderCollidable + //bIsUCXCollisionGeo + //bIsSimpleCollisionGeo + //bHasCollisionBeenAdded + //bHasSocketBeenAdded + } + + //bIsBox + //bIsSphere + + if (NewHGPO.SplitGroups.Num() <= 0) + { + NewHGPO.SplitGroups.Add("main_geo"); + } + + return NewHGPO; +} + +UHoudiniAssetInput::UHoudiniAssetInput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} +void +UHoudiniAssetInput::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Serialize current choice selection. + // Enum serialized as uint8 + // 0 GeometryInput + // 1 AssetInput + // 2 CurveInput + // 3 LandscapeInput + // 4 WorldInput + // 5 SkeletonInput + //SerializeEnumeration< EHoudiniAssetInputType::Enum >(Ar, ChoiceIndex); + Ar << ChoiceIndex; + + Ar << ChoiceStringValue; + + // We need these temporary variables for undo state tracking. + bool bLocalInputAssetConnectedInHoudini = bInputAssetConnectedInHoudini; + UHoudiniAssetComponent_V1 * LocalInputAssetComponent = InputAssetComponent; + + Ar << HoudiniAssetInputFlagsPacked; + + // Serialize input index. + Ar << InputIndex; + + // Serialize input objects (if it's assigned). + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT) + { + Ar << InputObjects; + } + else + { + UObject* InputObject = nullptr; + Ar << InputObject; + InputObjects.Empty(); + InputObjects.Add(InputObject); + } + + // Serialize input asset. + Ar << InputAssetComponent; + + // Serialize curve and curve parameters (if we have those). + Ar << InputCurve; + Ar << InputCurveParameters; + + // Serialize landscape used for input. + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT) + { + if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF) + { + ALandscapeProxy* InputLandscapePtr = nullptr; + Ar << InputLandscapePtr; + + InputLandscapeProxy = InputLandscapePtr; + } + else + { + Ar << InputLandscapeProxy; + } + + } + + // Serialize world outliner inputs. + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT) + { + Ar << InputOutlinerMeshArray; + } + + // Create necessary widget resources. + bLoadedParameter = true; + // If we're loading for real for the first time we need to reset this + // flag so we can reconnect when we get our parameters uploaded. + bInputAssetConnectedInHoudini = false; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT) + Ar << UnrealSplineResolution; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS) + { + Ar << InputTransforms; + } + else + { + InputTransforms.SetNum(InputObjects.Num()); + for (int32 n = 0; n < InputTransforms.Num(); n++) + InputTransforms[n] = FTransform::Identity; + } + + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + Ar << InputLandscapeTransform; +} + +UHoudiniInput* +UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) +{ + UHoudiniInput* Input = NewObject( + InOuter, UHoudiniInput::StaticClass(), FName(*ParameterLabel), RF_Transactional); + + EHoudiniInputType InputType = EHoudiniInputType::Invalid; + if (ChoiceIndex == 0) + InputType = EHoudiniInputType::Geometry; + else if (ChoiceIndex == 1) + InputType = EHoudiniInputType::Asset; + else if (ChoiceIndex == 2) + InputType = EHoudiniInputType::Curve; + else if (ChoiceIndex == 3) + InputType = EHoudiniInputType::Landscape; + else if (ChoiceIndex == 4) + InputType = EHoudiniInputType::World; + else if (ChoiceIndex == 5) + { + //InputType = EHoudiniInputType::Skeletal; + InputType = EHoudiniInputType::Invalid; + } + else + { + // Invalid + InputType = EHoudiniInputType::Invalid; + } + + bool bBlueprintStructureModified = false; + Input->SetInputType(InputType, bBlueprintStructureModified); + + Input->SetExportColliders(false); + Input->SetExportLODs(bExportAllLODs); + Input->SetExportSockets(bExportSockets); + Input->SetCookOnCurveChange(true); + + // If KeepWorldTransform is set to 2, use the default value + if (bKeepWorldTransform == 2) + Input->SetKeepWorldTransform((bool)Input->GetDefaultXTransformType()); + else + Input->SetKeepWorldTransform((bool)bKeepWorldTransform); + + Input->SetUnrealSplineResolution(UnrealSplineResolution); + Input->SetPackBeforeMerge(bPackBeforeMerge); + if(bIsObjectPathParameter) + Input->SetObjectPathParameter(ParmId); + else + Input->SetSOPInput(InputIndex); + + Input->SetImportAsReference(false); + Input->SetHelp(ParameterHelp); + //Input->SetInputNodeId(-1); + + if (bLandscapeExportAsHeightfield) + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Heightfield); + else if (bLandscapeExportAsMesh) + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Mesh); + else + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Points); + + Input->SetLabel(ParameterLabel); + Input->SetName(ParameterName); + Input->SetUpdateInputLandscape(bUpdateInputLandscape); + + if (InputType == EHoudiniInputType::Geometry) + { + // Get the geo input object array + bool bNeedToEmpty = true; + TArray* GeoInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(GeoInputObjectsPtr)) + { + // Add the geometry input objects + for (int32 AtIndex = 0; AtIndex < InputObjects.Num(); AtIndex++) + { + // Create a new InputObject wrapper + UObject* CurObject = InputObjects[AtIndex]; + if (!CurObject || CurObject->IsPendingKill()) + continue; + + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurObject, Input, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + continue; + + // Remove the default/null object + if (bNeedToEmpty) + { + GeoInputObjectsPtr->Empty(); + bNeedToEmpty = false; + } + + // Add to the geo input array + GeoInputObjectsPtr->Add(NewInputObject); + } + } + } + else if (InputType == EHoudiniInputType::Asset && InputAssetComponent != nullptr && !InputAssetComponent->IsPendingKill()) + { + // Get the asset input object array + TArray* AssetInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(AssetInputObjectsPtr)) + { + // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent + // We can simply use the v1's HAC outer for that + UHoudiniAssetComponent* InputHAC = Cast(InputAssetComponent->GetOuter()); + if (InputHAC && !InputHAC->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputHAC, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the asset input array + AssetInputObjectsPtr->Add(NewInputObject); + } + } + } + } + else if (InputType == EHoudiniInputType::Curve) + { + // Get the curve input object array + TArray* CurveInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(CurveInputObjectsPtr)) + { + if (InputCurve && !InputCurve->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputCurve, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the curve input array + CurveInputObjectsPtr->Add(NewInputObject); + } + + // InputCurve->SetInputObject(NewInputObject); + + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = Cast(NewInputObject); + if(HoudiniSplineInput) + HoudiniSplineInput->Update(InputCurve); + } + } + + // TODO ??? + //InputCurveParameters; + } + else if (InputType == EHoudiniInputType::Landscape) + { + // Get the Landscape input object array + TArray* LandscapeInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(LandscapeInputObjectsPtr)) + { + // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent + // We can simply use the v1's HAC outer for that + ALandscapeProxy* InLandscape = InputLandscapeProxy.Get(); + if (InLandscape && !InLandscape->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InLandscape, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the geo input array + LandscapeInputObjectsPtr->Add(NewInputObject); + } + } + } + + Input->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; + Input->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; + Input->bLandscapeExportLighting = bLandscapeExportLighting; + Input->bLandscapeExportMaterials = bLandscapeExportMaterials; + Input->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; + Input->bLandscapeExportTileUVs = bLandscapeExportTileUVs; + + //bLandscapeExportCurves; + } + else if (InputType == EHoudiniInputType::World) + { + // Get the world input object array + TArray* WorldInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + + UWorld* MyWorld = InOuter->GetWorld(); + if (ensure(WorldInputObjectsPtr)) + { + // Add the geometry input objects + for (int32 AtIndex = 0; AtIndex < InputOutlinerMeshArray.Num(); AtIndex++) + { + FHoudiniAssetInputOutlinerMesh_V1& CurWorldInObj = InputOutlinerMeshArray[AtIndex]; + + AActor* CurActor = nullptr; + if (CurWorldInObj.ActorPtr.IsValid()) + { + CurActor = CurWorldInObj.ActorPtr.Get(); + } + else + { + // Try to update the actor ptr via the pathname + CurWorldInObj.TryToUpdateActorPtrFromActorPathName(MyWorld); + if (CurWorldInObj.ActorPtr.IsValid()) + { + CurActor = CurWorldInObj.ActorPtr.Get(); + } + } + + if(!CurActor || CurActor->IsPendingKill()) + continue; + + // Create a new InputObject wrapper for the actor + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurActor, Input, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + continue; + + // Add to the geo input array + WorldInputObjectsPtr->Add(NewInputObject); + } + } + + /* + CurWorldInObj->HoudiniAssetParameterVersion; + CurWorldInObj->ActorPtr; + CurWorldInObj->ActorPathName; + CurWorldInObj->StaticMeshComponent; + CurWorldInObj->StaticMesh; + CurWorldInObj->SplineComponent; + CurWorldInObj->NumberOfSplineControlPoints; + CurWorldInObj->SplineControlPointsTransform; + CurWorldInObj->SplineLength; + CurWorldInObj->SplineResolution; + CurWorldInObj->ActorTransform; + CurWorldInObj->ComponentTransform; + CurWorldInObj->AssetId; + CurWorldInObj->KeepWorldTransform; + CurWorldInObj->MeshComponentsMaterials; + CurWorldInObj->InstanceIndex; + */ + + //InputOutlinerMeshArray; + } + else + { + // Invalid + } + + //ChoiceStringValue; + //bStaticMeshChanged; + //bSwitchedToCurve; + //bLoadedParameter = true; + //bInputAssetConnectedInHoudini; + //InputTransforms; + //InputLandscapeTransform; + + return Input; +} + +FArchive& +operator<<(FArchive& Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh) +{ + HoudiniAssetInputOutlinerMesh.Serialize(Ar); + return Ar; +} + +void +FHoudiniAssetInputOutlinerMesh_V1::Serialize(FArchive& Ar) +{ + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniAssetParameterVersion; + + Ar << ActorPtr; + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + { + Ar << ActorPathName; + } + + if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) + { + Ar << StaticMeshComponent; + Ar << StaticMesh; + } + + Ar << ActorTransform; + + Ar << AssetId; + if (Ar.IsLoading() && !Ar.IsTransacting()) + AssetId = -1; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE + && HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) + { + Ar << SplineComponent; + Ar << NumberOfSplineControlPoints; + Ar << SplineLength; + Ar << SplineResolution; + Ar << ComponentTransform; + } + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM) + Ar << KeepWorldTransform; + + // UE4.19 SERIALIZATION FIX: + // The component materials serialization (24) was actually missing in the UE4.19 H17.0 / H16.5 plugin. + // However subsequent serialized changes (25+) were present in those version. This caused crashes when loading + // a level that was saved with 4.19+16.5/17.0 on a newer version of Unreal or Houdini... + // If the serialized version is exactly that of the fix, we can ignore the materials paths as well + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + Ar << MeshComponentsMaterials; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX) + Ar << InstanceIndex; +} + +bool +FHoudiniAssetInputOutlinerMesh_V1::TryToUpdateActorPtrFromActorPathName(UWorld* InWorld) +{ + // Ensure our current ActorPathName looks valid + if (ActorPathName.IsEmpty() || ActorPathName.Equals(TEXT("None"), ESearchCase::IgnoreCase)) + return false; + + // We'll try to find the corresponding actor by browsing through all the actors in the world.. + // Get the editor world + UWorld* World = InWorld; + if (!World) + return false; + + // Then try to find the actor corresponding to our path/name + bool FoundActor = false; + for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) + { + if (ActorIt->GetPathName() != ActorPathName) + continue; + + // We found the actor + ActorPtr = *ActorIt; + FoundActor = true; + + break; + } + + if (FoundActor) + { + // We need to invalid our components so they can be updated later + // from the new actor + StaticMesh = NULL; + StaticMeshComponent = NULL; + SplineComponent = NULL; + } + + return FoundActor; +} + +UHoudiniAssetComponentMaterials_V1::UHoudiniAssetComponentMaterials_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} +void +UHoudiniAssetComponentMaterials_V1::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Assignments; + Ar << Replacements; +} + +UHoudiniHandleComponent_V1::UHoudiniHandleComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniAssetInstanceInput::UHoudiniAssetInstanceInput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetInstanceInput::Serialize(FArchive& Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Flags.HoudiniAssetInstanceInputFlagsPacked; + Ar << HoudiniGeoPartObject; + + Ar << ObjectToInstanceId; + // Object id is transient + if (Ar.IsLoading() && !Ar.IsTransacting()) + ObjectToInstanceId = -1; + + // Serialize fields. + Ar << InstanceInputFields; +} + +UHoudiniAssetInstanceInputField::UHoudiniAssetInstanceInputField(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetInstanceInputField::Serialize(FArchive& Ar) +{ + // Call base implementation first. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + const int32 InstanceInputFieldVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + + Ar << HoudiniAssetInstanceInputFieldFlagsPacked; + Ar << HoudiniGeoPartObject; + + FString UnusedInstancePathName; + Ar << UnusedInstancePathName; + Ar << RotationOffsets; + Ar << ScaleOffsets; + Ar << bScaleOffsetsLinearlyArray; + + Ar << InstancedTransforms; + Ar << VariationTransformsArray; + + if (Ar.IsSaving() || (Ar.IsLoading() && InstanceInputFieldVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS)) + { + Ar << InstanceColorOverride; + Ar << VariationInstanceColorOverrideArray; + } + + Ar << InstancerComponents; + Ar << InstancedObjects; + Ar << OriginalObject; +} + +void +UHoudiniHandleComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // XFormn Parames is an array of 9 float params + tuple index + // TX TY TZ + // RX RY RZ + // SX SY SZ + + //UHoudiniAssetParameterFloat_V1* XFormParams[9]; + //int32 XFormParamsTupleIndex[9]; + for (int32 i = 0; i < 9; ++i) + { + Ar << XFormParams[i]; + Ar << XFormParamsTupleIndex[i]; + } + + //UHoudiniAssetParameterChoice_V1* RSTParm; + Ar << RSTParm; + //int32 RSTParmTupleIdx; + Ar << RSTParmTupleIdx; + + //UHoudiniAssetParameterChoice_V1* RotOrderParm; + Ar << RotOrderParm; + //int32 RotOrderParmTupleIdx; + Ar << RotOrderParmTupleIdx; +} + +/* +UHoudiniHandleComponent* +UHoudiniHandleComponent_V1::ConvertLegacyData(UObject* Outer) +{ + UHoudiniHandleComponent* NewHandle = nullptr; + + return NewHandle; +} +*/ + +bool +UHoudiniHandleComponent_V1::UpdateFromLegacyData(UHoudiniHandleComponent* NewHC) +{ + if (!NewHC || NewHC->IsPendingKill()) + return false; + + // TODO + //NewHC->XformParms; + //NewHC->RSTParm; + //NewHC->RotOrderParm; + //NewHC->HandleType; + //NewHC->HandleName; + + return true; +} + +UHoudiniSplineComponent_V1::UHoudiniSplineComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniSplineComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + int32 Version = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << Version; + + Ar << HoudiniGeoPartObject; + + if (Version < VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM) + { + // Before, curve points where stored as Vectors, not Transforms + TArray OldCurvePoints; + Ar << OldCurvePoints; + + CurvePoints.SetNumUninitialized(OldCurvePoints.Num()); + + FTransform trans = FTransform::Identity; + for (int32 n = 0; n < CurvePoints.Num(); n++) + { + trans.SetLocation(OldCurvePoints[n]); + CurvePoints[n] = trans; + } + } + else + { + Ar << CurvePoints; + } + + Ar << CurveDisplayPoints; + + Ar << CurveType; + Ar << CurveMethod; + Ar << bClosedCurve; +} + +UHoudiniSplineComponent* +UHoudiniSplineComponent_V1::ConvertLegacyData(UObject* Outer) +{ + UHoudiniSplineComponent* NewSpline = NewObject( + GetOuter(), UHoudiniSplineComponent::StaticClass()); + + UpdateFromLegacyData(NewSpline); + + return NewSpline; +} + +bool +UHoudiniSplineComponent_V1::UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline) +{ + if (!NewSpline || NewSpline->IsPendingKill()) + return false; + + NewSpline->SetFlags(RF_Transactional); + + NewSpline->CurvePoints = CurvePoints; + NewSpline->DisplayPoints = CurveDisplayPoints; + //NewSpline->DisplayPointIndexDivider; + //NewSpline->HoudiniSplineName; + NewSpline->bClosed = bClosedCurve; + NewSpline->bReversed = false; + NewSpline->bIsHoudiniSplineVisible = true; + + //0 Polygon 1 Nurbs 2 Bezier + if (CurveType == 0) + NewSpline->CurveType = EHoudiniCurveType::Polygon; + else if (CurveType == 1) + NewSpline->CurveType = EHoudiniCurveType::Nurbs; + else if (CurveType == 2) + NewSpline->CurveType = EHoudiniCurveType::Bezier; + else + NewSpline->CurveType = EHoudiniCurveType::Invalid; + + // 0 CVs, 1 Breakpoints, 2 Freehand + if (CurveMethod == 0) + NewSpline->CurveMethod = EHoudiniCurveMethod::CVs; + else if (CurveMethod == 1) + NewSpline->CurveMethod = EHoudiniCurveMethod::Breakpoints; + else if (CurveMethod == 2) + NewSpline->CurveMethod = EHoudiniCurveMethod::Freehand; + else + NewSpline->CurveMethod = EHoudiniCurveMethod::Invalid; + + NewSpline->bIsOutputCurve = false; + + NewSpline->HoudiniGeoPartObject = HoudiniGeoPartObject.ConvertLegacyData(); + + if (NewSpline->HoudiniGeoPartObject.bIsEditable) + { + NewSpline->bIsEditableOutputCurve = true; + NewSpline->bIsInputCurve = false; + } + else + { + NewSpline->bIsInputCurve = false; + NewSpline->bIsEditableOutputCurve = true; + } + + // Create a default Houdini spline input if a null pointer is passed in. + FName HoudiniSplineName = MakeUniqueObjectName(GetOuter(), UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); + NewSpline->SetHoudiniSplineName(HoudiniSplineName.ToString()); + + //NewSpline->bHasChanged; + //NewSpline->bNeedsToTriggerUpdate; + //NewSpline->InputObject; + //NewSpline->NodeId; + //NewSpline->PartName; + + return true; +} + +UHoudiniAssetParameter::UHoudiniAssetParameter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameter::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniAssetParameterVersion; + + Ar << HoudiniAssetParameterFlagsPacked; + + if (Ar.IsLoading()) + bChanged = false; + + Ar << ParameterName; + Ar << ParameterLabel; + + Ar << NodeId; + if (!Ar.IsTransacting() && Ar.IsLoading()) + { + // NodeId is invalid after load + NodeId = -1; + } + Ar << ParmId; + + Ar << ParmParentId; + Ar << ChildIndex; + + Ar << TupleSize; + Ar << ValuesIndex; + Ar << MultiparmInstanceIndex; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER) + { + UObject* Dummy = nullptr; + Ar << Dummy; + } + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP) + { + Ar << ParameterHelp; + } + else + { + ParameterHelp = TEXT(""); + } + /* + if (Ar.IsTransacting()) + { + Ar << PrimaryObject; + Ar << ParentParameter; + } + */ +} + +UHoudiniParameter* +UHoudiniAssetParameter::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameter::Create(Outer, ParameterName); +} + +void +UHoudiniAssetParameter::CopyLegacyParameterData(UHoudiniParameter* InNewParm) +{ + if (!InNewParm || InNewParm->IsPendingKill()) + return; + + InNewParm->Name = ParameterName; + InNewParm->Label = ParameterLabel; + //InNewParm->ParmType; + InNewParm->TupleSize = TupleSize; + InNewParm->NodeId = NodeId; + InNewParm->ParmId = ParmId; + InNewParm->ParentParmId = ParmParentId; + InNewParm->ChildIndex = ChildIndex; + InNewParm->bIsVisible = true; + InNewParm->bIsDisabled = bIsDisabled; + InNewParm->bHasChanged = bChanged; + //InNewParm->bNeedsToTriggerUpdate; + //InNewParm->bIsDefault; + InNewParm->bIsSpare = bIsSpare; + InNewParm->bJoinNext = false; + InNewParm->bIsChildOfMultiParm = bIsChildOfMultiparm; + // TODO: MultiparmInstanceIndex ? + //InNewParm->bIsDirectChildOfMultiParm; + InNewParm->bPendingRevertToDefault = false; + //InNewParm->TuplePendingRevertToDefault = false; + InNewParm->Help = ParameterHelp; + InNewParm->TagCount = 0; + InNewParm->ValueIndex = ValuesIndex; + //InNewParm->bHasExpression; + //InNewParm->bShowExpression; + //InNewParm->ParamExpression; + //InNewParm->Tags; + InNewParm->bAutoUpdate = true; +} + +UHoudiniAssetParameterChoice::UHoudiniAssetParameterChoice(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterChoice::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + { + StringChoiceValues.Empty(); + StringChoiceLabels.Empty(); + } + + int32 NumChoices = StringChoiceValues.Num(); + Ar << NumChoices; + + int32 NumLabels = StringChoiceLabels.Num(); + Ar << NumLabels; + + if (Ar.IsLoading()) + { + FString Temp; + for (int32 ChoiceIdx = 0; ChoiceIdx < NumChoices; ++ChoiceIdx) + { + Ar << Temp; + StringChoiceValues.Add(Temp); + } + + for (int32 LabelIdx = 0; LabelIdx < NumLabels; ++LabelIdx) + { + Ar << Temp; + StringChoiceLabels.Add(Temp); + } + } + + Ar << StringValue; + Ar << CurrentValue; + + Ar << bStringChoiceList; +} + +UHoudiniParameter* +UHoudiniAssetParameterChoice::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterChoice* Parm = nullptr; + if (bStringChoiceList) + { + Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::StringChoice); + } + else + { + Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::IntChoice); + } + + Parm->SetNumChoices(StringChoiceValues.Num()); + for (int32 Idx = 0; Idx < StringChoiceValues.Num(); Idx++) + { + FString * ChoiceValue = Parm->GetStringChoiceValueAt(Idx); + if (ChoiceValue) + *ChoiceValue = StringChoiceValues[Idx]; + FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); + } + + for (int32 Idx = 0; Idx < StringChoiceLabels.Num(); Idx++) + { + FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); + if (ChoiceLabel) + *ChoiceLabel = StringChoiceValues[Idx]; + } + + Parm->SetStringValue(StringValue); + Parm->SetIntValue(CurrentValue); + //Parm->DefaultStringValue = StringValue; + //Parm->SetDefault(); + //Parm->DefaultIntValue = CurrentValue; + + return Parm; +} + +UHoudiniAssetParameterButton::UHoudiniAssetParameterButton(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterButton::ConvertLegacyData(UObject* Outer) +{ + // Button strips where not supported in v1, just create a normal button + return UHoudiniParameterButton::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterColor::UHoudiniAssetParameterColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterColor::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + Color = FColor::White; + + Ar << Color; +} + +UHoudiniParameter* +UHoudiniAssetParameterColor::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterColor* Parm = UHoudiniParameterColor::Create(Outer, ParameterName); + Parm->SetColorValue(Color); + + //Parm->DefaultColor = Color; + Parm->SetDefaultValue(); + + //Parm->bIsChildOfRamp = false; + + return Parm; +} + +UHoudiniAssetParameterFile::UHoudiniAssetParameterFile(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterFile::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + Ar << Filters; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY) + Ar << IsReadOnly; +} + +UHoudiniParameter* +UHoudiniAssetParameterFile::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterFile* Parm = UHoudiniParameterFile::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + Parm->SetFileFilters(Filters); + Parm->SetReadOnly(IsReadOnly); + + return Parm; +} + +UHoudiniAssetParameterFloat::UHoudiniAssetParameterFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterFloat::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + + Ar << ValueMin; + Ar << ValueMax; + + Ar << ValueUIMin; + Ar << ValueUIMax; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) + Ar << ValueUnit; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP) + Ar << NoSwap; +} + +UHoudiniParameter* +UHoudiniAssetParameterFloat::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterFloat* Parm = UHoudiniParameterFloat::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + Parm->SetUnit(ValueUnit); + Parm->SetHasMin(true); + Parm->SetHasMax(true); + Parm->SetHasUIMin(true); + Parm->SetHasUIMax(true); + Parm->SetIsLogarithmic(false); + Parm->SetMin(ValueMin); + Parm->SetMax(ValueMax); + Parm->SetUIMin(ValueUIMin); + Parm->SetUIMax(ValueUIMax); + + //Parm->bIsChildOfRamp = false; + + return Parm; +} + +UHoudiniAssetParameterFolder::UHoudiniAssetParameterFolder(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterFolder::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterFolder::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterFolderList::UHoudiniAssetParameterFolderList(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterFolderList::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterFolderList::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterInt::UHoudiniAssetParameterInt(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterInt::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + + Ar << ValueMin; + Ar << ValueMax; + + Ar << ValueUIMin; + Ar << ValueUIMax; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) + Ar << ValueUnit; +} + +UHoudiniParameter* +UHoudiniAssetParameterInt::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterInt* Parm = UHoudiniParameterInt::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + + //Parm->DefaultValues = Values; + Parm->SetUnit(ValueUnit); + Parm->SetHasMin(true); + Parm->SetHasMax(true); + Parm->SetHasUIMin(true); + Parm->SetHasUIMax(true); + Parm->SetIsLogarithmic(false); + Parm->SetMin(ValueMin); + Parm->SetMax(ValueMax); + Parm->SetUIMin(ValueUIMin); + Parm->SetUIMax(ValueUIMax); + + return Parm; +} + +UHoudiniAssetParameterLabel::UHoudiniAssetParameterLabel(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterLabel::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterLabel::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterMultiparm::UHoudiniAssetParameterMultiparm(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterMultiparm::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + MultiparmValue = 0; + + Ar << MultiparmValue; +} + +UHoudiniParameter* +UHoudiniAssetParameterMultiparm::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterMultiParm* Parm = UHoudiniParameterMultiParm::Create(Outer, ParameterName); + + //Parm->bIsShown; + //Parm->Value; + //Parm->TemplateName; + Parm->MultiparmValue = MultiparmValue; + //Parm->MultiParmInstanceNum; + //Parm->MultiParmInstanceLength; + //Parm->MultiParmInstanceCount; + //Parm->InstanceStartOffset; + //Parm->DefaultInstanceCount; + + // TODO: + // MultiparmInstanceIndex? + + return Parm; +} + +UHoudiniAssetParameterRamp::UHoudiniAssetParameterRamp(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterRamp::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + int32 multiparmvalue = 0; + Ar << multiparmvalue; + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << HoudiniAssetParameterRampCurveFloat; + Ar << HoudiniAssetParameterRampCurveColor; + + Ar << bIsFloatRamp; +} + +UHoudiniParameter* +UHoudiniAssetParameterRamp::ConvertLegacyData(UObject* Outer) +{ + if (bIsFloatRamp) + { + UHoudiniParameterRampFloat* Parm = UHoudiniParameterRampFloat::Create(Outer, ParameterName); + + // TODO: + // Convert HoudiniAssetParameterRampCurveFloat + + return Parm; + } + else + { + UHoudiniParameterRampColor* Parm = UHoudiniParameterRampColor::Create(Outer, ParameterName); + + // TODO: + // Convert HoudiniAssetParameterRampCurveColor + + return Parm; + } +} + +UHoudiniAssetParameterSeparator::UHoudiniAssetParameterSeparator(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterSeparator::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterSeparator::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterString::UHoudiniAssetParameterString(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterString::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; +} + +UHoudiniParameter* +UHoudiniAssetParameterString::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterString* Parm = UHoudiniParameterString::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetIsAssetRef(false); + Parm->SetDefaultValues(); + + //Parm->DefaultValues = Values; + //Parm->ChosenAssets.Empty(); + //Parm->bIsAssetRef = false; + + return Parm; +} + +UHoudiniAssetParameterToggle::UHoudiniAssetParameterToggle(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterToggle::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; +} + +UHoudiniParameter* +UHoudiniAssetParameterToggle::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterToggle* Parm = UHoudiniParameterToggle::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt((bool)Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + return Parm; +} + +UHoudiniMeshSplitInstancerComponent_V1::UHoudiniMeshSplitInstancerComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniMeshSplitInstancerComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << InstancedMesh; + Ar << OverrideMaterial; + Ar << Instances; +} + +bool +UHoudiniMeshSplitInstancerComponent_V1::UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC) +{ + if (!NewMSIC || NewMSIC->IsPendingKill()) + return false; + + NewMSIC->Instances = Instances; + NewMSIC->OverrideMaterials.Add(OverrideMaterial); + NewMSIC->InstancedMesh = InstancedMesh; + + return true; +} + +UHoudiniInstancedActorComponent_V1::UHoudiniInstancedActorComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniInstancedActorComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << InstancedAsset; + Ar << Instances; +} + +bool +UHoudiniInstancedActorComponent_V1::UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC) +{ + if (!NewIAC || NewIAC->IsPendingKill()) + return false; + + //NewIAC->SetInstancedObject(InstancedAsset); + NewIAC->InstancedObject = InstancedAsset; + NewIAC->InstancedActors = Instances; + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h index 8e50b6cb1..96829e372 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h @@ -1,1098 +1,1098 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniAsset.h" - -#include "Components/PrimitiveComponent.h" - -#include "Curves/CurveFloat.h" -#include "Curves/CurveLinearColor.h" - -#include "HoudiniCompatibilityHelpers.generated.h" - -class UStaticMesh; -class UStaticMeshComponent; -class USplineComponent; -class ALandscapeProxy; -class UMaterialInterface; -class UHoudiniInput; -class UHoudiniParameter; -class UHoudiniHandleComponent; -class UHoudiniSplineComponent; -class UHoudiniInstancedActorComponent; -class UHoudiniMeshSplitInstancerComponent; -class UFoliageType_InstancedStaticMesh; - - -struct FHoudiniGeoPartObject; - - -struct FHoudiniGeoPartObject_V1 -{ -public: - - void Serialize(FArchive & Ar); - - FHoudiniGeoPartObject ConvertLegacyData(); - - /** Return hash value for this object, used when using this object as a key inside hashing containers. **/ - uint32 GetTypeHash() const; - - /** Transform of this geo part object. **/ - FTransform TransformMatrix; - - /** Name of associated object. **/ - FString ObjectName; - - /** Name of associated part. **/ - FString PartName; - - /** Name of group which was used for splitting, empty if there's none. **/ - FString SplitName; - - /** Name of the instancer material, if available. **/ - FString InstancerMaterialName; - - /** Name of attribute material, if available. **/ - FString InstancerAttributeMaterialName; - - /** Id of corresponding HAPI Asset. **/ - //HAPI_NodeId AssetId; - int AssetId; - - /** Id of corresponding HAPI Object. **/ - //HAPI_NodeId ObjectId; - int ObjectId; - - /** Id of corresponding HAPI Geo. **/ - //HAPI_NodeId GeoId; - int GeoId; - - /** Id of corresponding HAPI Part. **/ - //HAPI_PartId PartId; - int PartId; - - /** Id of a split. In most cases this will be 0. **/ - int32 SplitId; - - /** Path to the corresponding node */ - mutable FString NodePath; - - /** Flags used by geo part object. **/ - union - { - struct - { - /* Is set to true when referenced object is visible. This is typically used by instancers. **/ - uint32 bIsVisible : 1; - - /** Is set to true when referenced object is an instancer. **/ - uint32 bIsInstancer : 1; - - /** Is set to true when referenced object is a curve. **/ - uint32 bIsCurve : 1; - - /** Is set to true when referenced object is editable. **/ - uint32 bIsEditable : 1; - - /** Is set to true when geometry has changed. **/ - uint32 bHasGeoChanged : 1; - - /** Is set to true when referenced object is collidable. **/ - uint32 bIsCollidable : 1; - - /** Is set to true when referenced object is collidable and is renderable. **/ - uint32 bIsRenderCollidable : 1; - - /** Is set to true when referenced object has just been loaded. **/ - uint32 bIsLoaded : 1; - - /** Unused flags. **/ - uint32 bPlaceHolderFlags : 3; - - /** Is set to true when referenced object has been loaded during transaction. **/ - uint32 bIsTransacting : 1; - - /** Is set to true when referenced object has a custom name. **/ - uint32 bHasCustomName : 1; - - /** Is set to true when referenced object is a box. **/ - uint32 bIsBox : 1; - - /** Is set to true when referenced object is a sphere. **/ - uint32 bIsSphere : 1; - - /** Is set to true when instancer material is available. **/ - uint32 bInstancerMaterialAvailable : 1; - - /** Is set to true when referenced object is a volume. **/ - uint32 bIsVolume : 1; - - /** Is set to true when instancer attribute material is available. **/ - uint32 bInstancerAttributeMaterialAvailable : 1; - - /** Is set when referenced object contains packed primitive instancing */ - uint32 bIsPackedPrimitiveInstancer : 1; - - /** Is set to true when referenced object is a UCX collision geo. **/ - uint32 bIsUCXCollisionGeo : 1; - - /** Is set to true when referenced object is a rendered UCX collision geo. **/ - uint32 bIsSimpleCollisionGeo : 1; - - /** Is set to true when new collision geo has been generated **/ - uint32 bHasCollisionBeenAdded : 1; - - /** Is set to true when new sockets have been added **/ - uint32 bHasSocketBeenAdded : 1; - - /** unused flag space is zero initialized */ - uint32 UnusedFlagsSpace : 14; - }; - - uint32 HoudiniGeoPartObjectFlagsPacked; - }; - - /** Temporary variable holding serialization version. **/ - uint32 HoudiniGeoPartObjectVersion; -}; - -/** Function used by hashing containers to create a unique hash for this type of object. **/ -uint32 GetTypeHash(const FHoudiniGeoPartObject_V1 & HoudiniGeoPartObject); - -/** Serialization function. **/ -FArchive& operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject); - -/** Functor used to sort geo part objects. **/ -struct FHoudiniGeoPartObject_V1SortPredicate -{ - bool operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const; -}; - - -struct FHoudiniAssetInputOutlinerMesh_V1 -{ - /** Serialization. **/ - void Serialize(FArchive & Ar); - - /** Update the Actor pointer from the store Actor path/name **/ - bool TryToUpdateActorPtrFromActorPathName(UWorld* InWorld); - - /** Temporary variable holding serialization version. **/ - uint32 HoudiniAssetParameterVersion; - - /** Selected Actor. **/ - TWeakObjectPtr ActorPtr = nullptr; - - /** Selected Actor's path, used to find the actor back after loading. **/ - FString ActorPathName = TEXT("NONE"); - - /** Selected mesh's component, for reference. **/ - UStaticMeshComponent * StaticMeshComponent = nullptr; - - /** The selected mesh. **/ - UStaticMesh * StaticMesh = nullptr; - - /** Spline Component **/ - USplineComponent * SplineComponent = nullptr; - - /** Number of CVs used by the spline component, used to detect modification **/ - int32 NumberOfSplineControlPoints = -1; - - /** Transform of the UnrealSpline CVs, used to detect modification of the spline (Rotation/Scale) **/ - TArray SplineControlPointsTransform; - - /** Spline Length, used to detect modification of the spline.. **/ - float SplineLength = -1.0f; - - /** Spline resolution used to generate the asset, used to detect setting modification **/ - float SplineResolution = -1.0f; - - /** Actor transform used to see if the transfrom changed since last marshal into Houdini. **/ - FTransform ActorTransform; - - /** Component transform used to see if the transform has changed since last marshalling **/ - FTransform ComponentTransform; - - /** Mesh's input asset id. **/ - //HAPI_NodeId AssetId = -1; - int AssetId = -1; - - /** TranformType used to generate the asset **/ - int32 KeepWorldTransform = 2; - - /** Path Materials assigned on the SMC **/ - TArray MeshComponentsMaterials; - - /** If the world In is a ISM, index of this instance **/ - uint32 InstanceIndex = -1; -}; - -/** Serialization function. **/ -FArchive & operator<<(FArchive & Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh); - -/* -UCLASS(EditInlineNew, config = Engine) -class UHoudiniAsset_V1 -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; -}; -*/ - -UCLASS() -class UHoudiniAssetParameter : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer); - - void CopyLegacyParameterData(UHoudiniParameter* InNewParm); - - /** Name of this parameter. **/ - FString ParameterName; - - /** Label of this parameter. **/ - FString ParameterLabel; - - /** Node this parameter belongs to. **/ - int NodeId; - - /** Id of this parameter. **/ - int ParmId; - - /** Id of parent parameter, -1 if root is parent. **/ - int ParmParentId; - - /** Child index within its parent parameter. **/ - int32 ChildIndex; - - /** Tuple size - arrays. **/ - int32 TupleSize; - - /** Internal HAPI cached value index. **/ - int32 ValuesIndex; - - /** The multiparm instance index. **/ - int32 MultiparmInstanceIndex; - - /** The parameter's help, to be used as a tooltip **/ - FString ParameterHelp; - - /** Flags used by this parameter. **/ - union - { - struct - { - /** Is set to true if this parameter is spare, that is, created by Houdini Engine only. **/ - uint32 bIsSpare : 1; - - /** Is set to true if this parameter is disabled. **/ - uint32 bIsDisabled : 1; - - /** Is set to true if value of this parameter has been changed by user. **/ - uint32 bChanged : 1; - - /** Is set to true when parameter's slider (if it has one) is being dragged. Transient. **/ - uint32 bSliderDragged : 1; - - /** Is set to true if the parameter is a multiparm child parameter. **/ - uint32 bIsChildOfMultiparm : 1; - - /** Is set to true if this parameter is a Substance parameter. **/ - uint32 bIsSubstanceParameter : 1; - - /** Is set to true if this parameter is a multiparm **/ - uint32 bIsMultiparm : 1; - }; - - uint32 HoudiniAssetParameterFlagsPacked; - }; - - /** Temporary variable holding parameter serialization version. **/ - uint32 HoudiniAssetParameterVersion; -}; - -UCLASS() -class UHoudiniAssetParameterButton : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterChoice : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Choice values for this property. **/ - TArray StringChoiceValues; - - /** Choice labels for this property. **/ - TArray StringChoiceLabels; - - /** Value of this property. **/ - FString StringValue; - - /** Current value for this property. **/ - int32 CurrentValue; - - /** Is set to true when this choice list is a string choice list. **/ - bool bStringChoiceList; -}; - -UCLASS() -class UHoudiniAssetParameterColor : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Color for this property. **/ - FLinearColor Color; -}; - -UCLASS() -class UHoudiniAssetParameterFile : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< FString > Values; - - /** Filters of this property. **/ - FString Filters; - - /** Is the file parameter read-only? **/ - bool IsReadOnly; -}; - -UCLASS() -class UHoudiniAssetParameterFloat : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< float > Values; - - /** Min and Max values for this property. **/ - float ValueMin; - float ValueMax; - - /** Min and Max values for UI for this property. **/ - float ValueUIMin; - float ValueUIMax; - - /** Unit for this property **/ - FString ValueUnit; - - /** Do we have the noswap tag? **/ - bool NoSwap; -}; - -UCLASS() -class UHoudiniAssetParameterFolder : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterFolderList : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterInt : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< int32 > Values; - - /** Min and Max values for this property. **/ - int32 ValueMin; - int32 ValueMax; - - /** Min and Max values for UI for this property. **/ - int32 ValueUIMin; - int32 ValueUIMax; - - /** Unit for this property **/ - FString ValueUnit; -}; - -UCLASS() -class UHoudiniAssetParameterLabel : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterMultiparm : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Value of this property. **/ - int32 MultiparmValue; -}; - -UCLASS() -class UHoudiniAssetParameterRamp : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - void CopyLegacyParameterData(UHoudiniParameter* InNewParm); - - //! Curves which are being edited. - UCurveFloat * HoudiniAssetParameterRampCurveFloat; - UCurveLinearColor * HoudiniAssetParameterRampCurveColor; - - //! Set to true if this ramp is a float ramp. Otherwise is considered a color ramp. - bool bIsFloatRamp; -}; - -UCLASS() -class UHoudiniAssetParameterSeparator : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterString : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< FString > Values; -}; - -UCLASS() -class UHoudiniAssetParameterToggle : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< int32 > Values; -}; - -UCLASS() -class UHoudiniAssetComponentMaterials_V1 : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** Material assignments. **/ - TMap Assignments; - - /** Material replacements. **/ - TMap> Replacements; -}; - -UCLASS() -class UHoudiniHandleComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniHandleComponent* NewHC); - - //virtual UHoudiniHandleComponent* ConvertLegacyData(UObject* Outer); - - UHoudiniAssetParameterFloat* XFormParams[9]; - int32 XFormParamsTupleIndex[9]; - - UHoudiniAssetParameterChoice* RSTParm; - int32 RSTParmTupleIdx; - - UHoudiniAssetParameterChoice* RotOrderParm; - int32 RotOrderParmTupleIdx; -}; - -UCLASS() -class UHoudiniSplineComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniSplineComponent* ConvertLegacyData(UObject* Outer); - - bool UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline); - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** List of points composing this curve. **/ - TArray CurvePoints; - - /** List of refined points used for drawing. **/ - TArray CurveDisplayPoints; - - /** Type of this curve. **/ - // 0 Polygon 1 Nurbs 2 Bezier - uint8 CurveType; - - /** Method used for this curve. **/ - // 0 CVs, 1 Breakpoints, 2 Freehand - uint8 CurveMethod; - - /** Whether this spline is closed. **/ - bool bClosedCurve; -}; - -UCLASS() -class UHoudiniAssetInput : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - UHoudiniInput* ConvertLegacyInput(UObject* Outer); - - // Input type: - // 0 GeometryInput - // 1 AssetInput - // 2 CurveInput - // 3 LandscapeInput - // 4 WorldInput - // 5 SkeletonInput - uint8 ChoiceIndex; - - /** Value of choice option. **/ - FString ChoiceStringValue; - - /** Index of this input. **/ - int32 InputIndex; - - /** Objects used for geometry input. **/ - TArray InputObjects; - - /** Houdini spline component which is used for curve input. **/ - UHoudiniSplineComponent * InputCurve; - - /** Houdini asset component pointer of the input asset (actor). **/ - UHoudiniAssetComponent_V1 * InputAssetComponent; - - /** Landscape actor used for input. **/ - TSoftObjectPtr InputLandscapeProxy; - - /** List of selected meshes and actors from the World Outliner. **/ - TArray InputOutlinerMeshArray; - - /** Parameters used by a curve input asset. **/ - TMap InputCurveParameters; - - float UnrealSplineResolution; - - /** Array containing the transform corrections for the assets in a geometry input **/ - TArray InputTransforms; - - /** Transform used by the input landscape **/ - FTransform InputLandscapeTransform; - - /** Flags used by this input. **/ - union - { - struct - { - /** Is set to true when static mesh used for geometry input has changed. **/ - uint32 bStaticMeshChanged : 1; - - /** Is set to true when choice switches to curve mode. **/ - uint32 bSwitchedToCurve : 1; - - /** Is set to true if this parameter has been loaded. **/ - uint32 bLoadedParameter : 1; - - /** Is set to true if the asset input is actually connected inside Houdini. **/ - uint32 bInputAssetConnectedInHoudini : 1; - - /** Is set to true when landscape input is set to selection only. **/ - uint32 bLandscapeExportSelectionOnly : 1; - - /** Is set to true when landscape curves are to be exported. **/ - uint32 bLandscapeExportCurves : 1; - - /** Is set to true when the landscape is to be exported as a mesh, not just points. **/ - uint32 bLandscapeExportAsMesh : 1; - - /** Is set to true when materials are to be exported. **/ - uint32 bLandscapeExportMaterials : 1; - - /** Is set to true when lightmap information export is desired. **/ - uint32 bLandscapeExportLighting : 1; - - /** Is set to true when uvs should be exported in [0,1] space. **/ - uint32 bLandscapeExportNormalizedUVs : 1; - - /** Is set to true when uvs should be exported for each tile separately. **/ - uint32 bLandscapeExportTileUVs : 1; - - /** Is set to true when being used as an object-path parameter instead of an input */ - uint32 bIsObjectPathParameter : 1; - - /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value **/ - uint32 bKeepWorldTransform : 2; - - /** Is set to true when the landscape is to be exported as a heightfield **/ - uint32 bLandscapeExportAsHeightfield : 1; - - /** Is set to true when the automatic selection of landscape component is active **/ - uint32 bLandscapeAutoSelectComponent : 1; - - /** Indicates that the geometry must be packed before merging it into the input **/ - uint32 bPackBeforeMerge : 1; - - /** Indicates that all LODs in the input should be marshalled to Houdini **/ - uint32 bExportAllLODs : 1; - - /** Indicates that all sockets in the input should be marshalled to Houdini **/ - uint32 bExportSockets : 1; - - /** Indicates that the landscape input's source landscape should be updated instead of creating a new component **/ - uint32 bUpdateInputLandscape : 1; - }; - - uint32 HoudiniAssetInputFlagsPacked; - }; -}; - -UCLASS() -class UHoudiniAssetInstanceInput : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** List of fields created by this instance input. **/ - TArray InstanceInputFields; - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** Id of an object to instance. **/ - int ObjectToInstanceId; - -public: - /** Flags used by this input. **/ - union FHoudiniAssetInstanceInputFlags - { - struct - { - /** Set to true if this is an attribute instancer. **/ - uint32 bIsAttributeInstancer : 1; - - /** Set to true if this attribute instancer uses overrides. **/ - uint32 bAttributeInstancerOverride : 1; - - /** Set to true if this is a packed primitive instancer **/ - uint32 bIsPackedPrimitiveInstancer : 1; - - /** Set to true if this is a split mesh instancer */ - uint32 bIsSplitMeshInstancer : 1; - }; - - uint32 HoudiniAssetInstanceInputFlagsPacked; - }; - FHoudiniAssetInstanceInputFlags Flags; -}; - -UCLASS() -class UHoudiniAssetInstanceInputField : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** Original object used by the instancer. **/ - UObject* OriginalObject; - - /** Currently used Objects */ - TArray< UObject* > InstancedObjects; - - /** Used instanced actor component. **/ - TArray< USceneComponent * > InstancerComponents; - - /** Flags used by this input field. **/ - uint32 HoudiniAssetInstanceInputFieldFlagsPacked; - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** Rotation offset for instanced component. **/ - TArray< FRotator > RotationOffsets; - - /** Scale offset for instanced component. **/ - TArray< FVector > ScaleOffsets; - - /** Whether to scale linearly for all fields. **/ - TArray< bool > bScaleOffsetsLinearlyArray; - - /** Transforms, one for each instance. **/ - TArray< FTransform > InstancedTransforms; - - /** Assignment of Transforms to each variation **/ - TArray< TArray< FTransform > > VariationTransformsArray; - - /** Color overrides, one per instance **/ - TArray InstanceColorOverride; - - /** Per-variation color override assignments */ - TArray< TArray< FLinearColor > > VariationInstanceColorOverrideArray; -}; - -//UCLASS() -UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), - ShowCategories = (Mobility), editinlinenew) -class UHoudiniAssetComponent_V1 : public UPrimitiveComponent//, public IHoudiniCookHandler -{ - GENERATED_UCLASS_BODY() - -public: - /* - // IHoudiniCookHandler interface - virtual FString GetBakingBaseName(const struct FHoudiniGeoPartObject_V1& GeoPartObject) override { return FString(); }; - virtual void SetStaticMeshGenerationParameters(class UStaticMesh* StaticMesh) override {}; - virtual class UMaterialInterface * GetAssignmentMaterial(const FString& MaterialName) override { return nullptr; }; - virtual void ClearAssignmentMaterials() override {}; - virtual void AddAssignmentMaterial(const FString& MaterialName, class UMaterialInterface* MaterialInterface) override {}; - virtual class UMaterialInterface * GetReplacementMaterial(const struct FHoudiniGeoPartObject_V1& GeoPartObject, const FString& MaterialName) override { return nullptr; }; - */ - - /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Double Sided Geometry")) - uint32 bGeneratedDoubleSidedGeometry : 1; - - /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * GeneratedPhysMaterial; - - /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ - UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Collision Complexity")) - TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; - - /** Resolution of lightmap. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 GeneratedLightMapResolution; - - /** Bias multiplier for Light Propagation Volume lighting. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float GeneratedLpvBiasMultiplier; - - /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) - float GeneratedDistanceFieldResolutionScale; - - /** Custom walkable slope setting for generated mesh's body. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride GeneratedWalkableSlopeOverride; - - /** The light map coordinate index. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light map coordinate index")) - int32 GeneratedLightMapCoordinateIndex; - - /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; - - /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Streaming Distance Multiplier")) - float GeneratedStreamingDistanceMultiplier; - - /** Default settings when using this mesh for instanced foliage. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; - - /** Array of user data stored with the asset. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Asset User Data")) - TArray GeneratedAssetUserData; - - /** The output folder for baking actions */ - UPROPERTY() - FText BakeFolder; - - /** The temporary output folder for cooking actions */ - UPROPERTY() - FText TempCookFolder; - - virtual void Serialize(FArchive & Ar) override; - - /** Houdini Asset associated with this component. **/ - UHoudiniAsset* HoudiniAsset; - - /** Unique GUID created by component. **/ - FGuid ComponentGUID; - - /** Scale factor used for generated geometry of this component. **/ - float GeneratedGeometryScaleFactor; - - /** Scale factor used for geo transforms of this component. **/ - float TransformScaleFactor; - - /** Buffer to hold preset data for serialization purposes. Used only during serialization. **/ - TArray PresetBuffer; - - /** Buffer to hold default preset for reset purposes. **/ - TArray DefaultPresetBuffer; - - /** Parameters for this component's asset, indexed by parameter id. **/ - //TMap Parameters; - TMap Parameters; - - /** Parameters for this component's asset, indexed by name for fast look up. **/ - TMap ParameterByName; - - /** Inputs for this component's asset. **/ - TArray Inputs; - - /** Instance inputs for this component's asset **/ - TArray InstanceInputs; - - /** Material assignments. **/ - UHoudiniAssetComponentMaterials_V1 * HoudiniAssetComponentMaterials; - - /** Map of HAPI objects and corresponding static meshes. Also map of static meshes and corresponding components. **/ - TMap StaticMeshes; - TMap StaticMeshComponents; - - /** List of dependent downstream asset connections that have this asset as an asset input. **/ - TMap> DownstreamAssetConnections; - - /** Map of asset handle components. **/ - TMap HandleComponents; - - /** Map of curve / spline components. **/ - TMap SplineComponents; - - /** Map of Landscape / Heightfield components. **/ - TMap> LandscapeComponents; - - /** Overrides for baking names per part */ - TMap BakeNameOverrides; - - /** Import axis. **/ - uint8 ImportAxis; - - /** Flags used by Houdini component. **/ - union - { - struct - { - /** Enables cooking for this Houdini Asset. **/ - uint32 bEnableCooking : 1; - - /** Enables uploading of transformation changes back to Houdini Engine. **/ - uint32 bUploadTransformsToHoudiniEngine : 1; - - /** Enables cooking upon transformation changes. **/ - uint32 bTransformChangeTriggersCooks : 1; - - /** Is set to true when this component contains Houdini logo geometry. **/ - uint32 bContainsHoudiniLogoGeometry : 1; - - /** Is set to true when this component is native and false is when it is dynamic. **/ - uint32 bIsNativeComponent : 1; - - /** Is set to true when this component belongs to a preview actor. **/ - uint32 bIsPreviewComponent : 1; - - /** Is set to true if this component has been loaded. **/ - uint32 bLoadedComponent : 1; - - /** Unused **/ - uint32 bIsPlayModeActive_Unused : 1; - - /** unused flag **/ - uint32 bTimeCookInPlaymode_Unused : 1; - - /** Is set to true when Houdini materials are used. **/ - uint32 bUseHoudiniMaterials : 1; - - /** Is set to true when cooking this asset will trigger cooks of downstream connected assets. **/ - uint32 bCookingTriggersDownstreamCooks : 1; - - /** Is set to true after the asset is fully loaded and registered **/ - uint32 bFullyLoaded : 1; - }; - - uint32 HoudiniAssetComponentFlagsPacked; - }; -}; - -UCLASS() -class UHoudiniInstancedActorComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC); - - //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) - UObject* InstancedAsset; - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - TArray Instances; -}; - -UCLASS() -class UHoudiniMeshSplitInstancerComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC); - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - TArray Instances; - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - UMaterialInterface* OverrideMaterial; - - //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) - UStaticMesh* InstancedMesh; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniAsset.h" + +#include "Components/PrimitiveComponent.h" + +#include "Curves/CurveFloat.h" +#include "Curves/CurveLinearColor.h" + +#include "HoudiniCompatibilityHelpers.generated.h" + +class UStaticMesh; +class UStaticMeshComponent; +class USplineComponent; +class ALandscapeProxy; +class UMaterialInterface; +class UHoudiniInput; +class UHoudiniParameter; +class UHoudiniHandleComponent; +class UHoudiniSplineComponent; +class UHoudiniInstancedActorComponent; +class UHoudiniMeshSplitInstancerComponent; +class UFoliageType_InstancedStaticMesh; + + +struct FHoudiniGeoPartObject; + + +struct FHoudiniGeoPartObject_V1 +{ +public: + + void Serialize(FArchive & Ar); + + FHoudiniGeoPartObject ConvertLegacyData(); + + /** Return hash value for this object, used when using this object as a key inside hashing containers. **/ + uint32 GetTypeHash() const; + + /** Transform of this geo part object. **/ + FTransform TransformMatrix; + + /** Name of associated object. **/ + FString ObjectName; + + /** Name of associated part. **/ + FString PartName; + + /** Name of group which was used for splitting, empty if there's none. **/ + FString SplitName; + + /** Name of the instancer material, if available. **/ + FString InstancerMaterialName; + + /** Name of attribute material, if available. **/ + FString InstancerAttributeMaterialName; + + /** Id of corresponding HAPI Asset. **/ + //HAPI_NodeId AssetId; + int AssetId; + + /** Id of corresponding HAPI Object. **/ + //HAPI_NodeId ObjectId; + int ObjectId; + + /** Id of corresponding HAPI Geo. **/ + //HAPI_NodeId GeoId; + int GeoId; + + /** Id of corresponding HAPI Part. **/ + //HAPI_PartId PartId; + int PartId; + + /** Id of a split. In most cases this will be 0. **/ + int32 SplitId; + + /** Path to the corresponding node */ + mutable FString NodePath; + + /** Flags used by geo part object. **/ + union + { + struct + { + /* Is set to true when referenced object is visible. This is typically used by instancers. **/ + uint32 bIsVisible : 1; + + /** Is set to true when referenced object is an instancer. **/ + uint32 bIsInstancer : 1; + + /** Is set to true when referenced object is a curve. **/ + uint32 bIsCurve : 1; + + /** Is set to true when referenced object is editable. **/ + uint32 bIsEditable : 1; + + /** Is set to true when geometry has changed. **/ + uint32 bHasGeoChanged : 1; + + /** Is set to true when referenced object is collidable. **/ + uint32 bIsCollidable : 1; + + /** Is set to true when referenced object is collidable and is renderable. **/ + uint32 bIsRenderCollidable : 1; + + /** Is set to true when referenced object has just been loaded. **/ + uint32 bIsLoaded : 1; + + /** Unused flags. **/ + uint32 bPlaceHolderFlags : 3; + + /** Is set to true when referenced object has been loaded during transaction. **/ + uint32 bIsTransacting : 1; + + /** Is set to true when referenced object has a custom name. **/ + uint32 bHasCustomName : 1; + + /** Is set to true when referenced object is a box. **/ + uint32 bIsBox : 1; + + /** Is set to true when referenced object is a sphere. **/ + uint32 bIsSphere : 1; + + /** Is set to true when instancer material is available. **/ + uint32 bInstancerMaterialAvailable : 1; + + /** Is set to true when referenced object is a volume. **/ + uint32 bIsVolume : 1; + + /** Is set to true when instancer attribute material is available. **/ + uint32 bInstancerAttributeMaterialAvailable : 1; + + /** Is set when referenced object contains packed primitive instancing */ + uint32 bIsPackedPrimitiveInstancer : 1; + + /** Is set to true when referenced object is a UCX collision geo. **/ + uint32 bIsUCXCollisionGeo : 1; + + /** Is set to true when referenced object is a rendered UCX collision geo. **/ + uint32 bIsSimpleCollisionGeo : 1; + + /** Is set to true when new collision geo has been generated **/ + uint32 bHasCollisionBeenAdded : 1; + + /** Is set to true when new sockets have been added **/ + uint32 bHasSocketBeenAdded : 1; + + /** unused flag space is zero initialized */ + uint32 UnusedFlagsSpace : 14; + }; + + uint32 HoudiniGeoPartObjectFlagsPacked; + }; + + /** Temporary variable holding serialization version. **/ + uint32 HoudiniGeoPartObjectVersion; +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +uint32 GetTypeHash(const FHoudiniGeoPartObject_V1 & HoudiniGeoPartObject); + +/** Serialization function. **/ +FArchive& operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject); + +/** Functor used to sort geo part objects. **/ +struct FHoudiniGeoPartObject_V1SortPredicate +{ + bool operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const; +}; + + +struct FHoudiniAssetInputOutlinerMesh_V1 +{ + /** Serialization. **/ + void Serialize(FArchive & Ar); + + /** Update the Actor pointer from the store Actor path/name **/ + bool TryToUpdateActorPtrFromActorPathName(UWorld* InWorld); + + /** Temporary variable holding serialization version. **/ + uint32 HoudiniAssetParameterVersion; + + /** Selected Actor. **/ + TWeakObjectPtr ActorPtr = nullptr; + + /** Selected Actor's path, used to find the actor back after loading. **/ + FString ActorPathName = TEXT("NONE"); + + /** Selected mesh's component, for reference. **/ + UStaticMeshComponent * StaticMeshComponent = nullptr; + + /** The selected mesh. **/ + UStaticMesh * StaticMesh = nullptr; + + /** Spline Component **/ + USplineComponent * SplineComponent = nullptr; + + /** Number of CVs used by the spline component, used to detect modification **/ + int32 NumberOfSplineControlPoints = -1; + + /** Transform of the UnrealSpline CVs, used to detect modification of the spline (Rotation/Scale) **/ + TArray SplineControlPointsTransform; + + /** Spline Length, used to detect modification of the spline.. **/ + float SplineLength = -1.0f; + + /** Spline resolution used to generate the asset, used to detect setting modification **/ + float SplineResolution = -1.0f; + + /** Actor transform used to see if the transfrom changed since last marshal into Houdini. **/ + FTransform ActorTransform; + + /** Component transform used to see if the transform has changed since last marshalling **/ + FTransform ComponentTransform; + + /** Mesh's input asset id. **/ + //HAPI_NodeId AssetId = -1; + int AssetId = -1; + + /** TranformType used to generate the asset **/ + int32 KeepWorldTransform = 2; + + /** Path Materials assigned on the SMC **/ + TArray MeshComponentsMaterials; + + /** If the world In is a ISM, index of this instance **/ + uint32 InstanceIndex = -1; +}; + +/** Serialization function. **/ +FArchive & operator<<(FArchive & Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh); + +/* +UCLASS(EditInlineNew, config = Engine) +class UHoudiniAsset_V1 +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; +}; +*/ + +UCLASS() +class UHoudiniAssetParameter : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer); + + void CopyLegacyParameterData(UHoudiniParameter* InNewParm); + + /** Name of this parameter. **/ + FString ParameterName; + + /** Label of this parameter. **/ + FString ParameterLabel; + + /** Node this parameter belongs to. **/ + int NodeId; + + /** Id of this parameter. **/ + int ParmId; + + /** Id of parent parameter, -1 if root is parent. **/ + int ParmParentId; + + /** Child index within its parent parameter. **/ + int32 ChildIndex; + + /** Tuple size - arrays. **/ + int32 TupleSize; + + /** Internal HAPI cached value index. **/ + int32 ValuesIndex; + + /** The multiparm instance index. **/ + int32 MultiparmInstanceIndex; + + /** The parameter's help, to be used as a tooltip **/ + FString ParameterHelp; + + /** Flags used by this parameter. **/ + union + { + struct + { + /** Is set to true if this parameter is spare, that is, created by Houdini Engine only. **/ + uint32 bIsSpare : 1; + + /** Is set to true if this parameter is disabled. **/ + uint32 bIsDisabled : 1; + + /** Is set to true if value of this parameter has been changed by user. **/ + uint32 bChanged : 1; + + /** Is set to true when parameter's slider (if it has one) is being dragged. Transient. **/ + uint32 bSliderDragged : 1; + + /** Is set to true if the parameter is a multiparm child parameter. **/ + uint32 bIsChildOfMultiparm : 1; + + /** Is set to true if this parameter is a Substance parameter. **/ + uint32 bIsSubstanceParameter : 1; + + /** Is set to true if this parameter is a multiparm **/ + uint32 bIsMultiparm : 1; + }; + + uint32 HoudiniAssetParameterFlagsPacked; + }; + + /** Temporary variable holding parameter serialization version. **/ + uint32 HoudiniAssetParameterVersion; +}; + +UCLASS() +class UHoudiniAssetParameterButton : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterChoice : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Choice values for this property. **/ + TArray StringChoiceValues; + + /** Choice labels for this property. **/ + TArray StringChoiceLabels; + + /** Value of this property. **/ + FString StringValue; + + /** Current value for this property. **/ + int32 CurrentValue; + + /** Is set to true when this choice list is a string choice list. **/ + bool bStringChoiceList; +}; + +UCLASS() +class UHoudiniAssetParameterColor : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Color for this property. **/ + FLinearColor Color; +}; + +UCLASS() +class UHoudiniAssetParameterFile : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< FString > Values; + + /** Filters of this property. **/ + FString Filters; + + /** Is the file parameter read-only? **/ + bool IsReadOnly; +}; + +UCLASS() +class UHoudiniAssetParameterFloat : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< float > Values; + + /** Min and Max values for this property. **/ + float ValueMin; + float ValueMax; + + /** Min and Max values for UI for this property. **/ + float ValueUIMin; + float ValueUIMax; + + /** Unit for this property **/ + FString ValueUnit; + + /** Do we have the noswap tag? **/ + bool NoSwap; +}; + +UCLASS() +class UHoudiniAssetParameterFolder : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterFolderList : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterInt : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< int32 > Values; + + /** Min and Max values for this property. **/ + int32 ValueMin; + int32 ValueMax; + + /** Min and Max values for UI for this property. **/ + int32 ValueUIMin; + int32 ValueUIMax; + + /** Unit for this property **/ + FString ValueUnit; +}; + +UCLASS() +class UHoudiniAssetParameterLabel : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterMultiparm : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Value of this property. **/ + int32 MultiparmValue; +}; + +UCLASS() +class UHoudiniAssetParameterRamp : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + void CopyLegacyParameterData(UHoudiniParameter* InNewParm); + + //! Curves which are being edited. + UCurveFloat * HoudiniAssetParameterRampCurveFloat; + UCurveLinearColor * HoudiniAssetParameterRampCurveColor; + + //! Set to true if this ramp is a float ramp. Otherwise is considered a color ramp. + bool bIsFloatRamp; +}; + +UCLASS() +class UHoudiniAssetParameterSeparator : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterString : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< FString > Values; +}; + +UCLASS() +class UHoudiniAssetParameterToggle : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< int32 > Values; +}; + +UCLASS() +class UHoudiniAssetComponentMaterials_V1 : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** Material assignments. **/ + TMap Assignments; + + /** Material replacements. **/ + TMap> Replacements; +}; + +UCLASS() +class UHoudiniHandleComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniHandleComponent* NewHC); + + //virtual UHoudiniHandleComponent* ConvertLegacyData(UObject* Outer); + + UHoudiniAssetParameterFloat* XFormParams[9]; + int32 XFormParamsTupleIndex[9]; + + UHoudiniAssetParameterChoice* RSTParm; + int32 RSTParmTupleIdx; + + UHoudiniAssetParameterChoice* RotOrderParm; + int32 RotOrderParmTupleIdx; +}; + +UCLASS() +class UHoudiniSplineComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniSplineComponent* ConvertLegacyData(UObject* Outer); + + bool UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline); + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** List of points composing this curve. **/ + TArray CurvePoints; + + /** List of refined points used for drawing. **/ + TArray CurveDisplayPoints; + + /** Type of this curve. **/ + // 0 Polygon 1 Nurbs 2 Bezier + uint8 CurveType; + + /** Method used for this curve. **/ + // 0 CVs, 1 Breakpoints, 2 Freehand + uint8 CurveMethod; + + /** Whether this spline is closed. **/ + bool bClosedCurve; +}; + +UCLASS() +class UHoudiniAssetInput : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + UHoudiniInput* ConvertLegacyInput(UObject* Outer); + + // Input type: + // 0 GeometryInput + // 1 AssetInput + // 2 CurveInput + // 3 LandscapeInput + // 4 WorldInput + // 5 SkeletonInput + uint8 ChoiceIndex; + + /** Value of choice option. **/ + FString ChoiceStringValue; + + /** Index of this input. **/ + int32 InputIndex; + + /** Objects used for geometry input. **/ + TArray InputObjects; + + /** Houdini spline component which is used for curve input. **/ + UHoudiniSplineComponent * InputCurve; + + /** Houdini asset component pointer of the input asset (actor). **/ + UHoudiniAssetComponent_V1 * InputAssetComponent; + + /** Landscape actor used for input. **/ + TSoftObjectPtr InputLandscapeProxy; + + /** List of selected meshes and actors from the World Outliner. **/ + TArray InputOutlinerMeshArray; + + /** Parameters used by a curve input asset. **/ + TMap InputCurveParameters; + + float UnrealSplineResolution; + + /** Array containing the transform corrections for the assets in a geometry input **/ + TArray InputTransforms; + + /** Transform used by the input landscape **/ + FTransform InputLandscapeTransform; + + /** Flags used by this input. **/ + union + { + struct + { + /** Is set to true when static mesh used for geometry input has changed. **/ + uint32 bStaticMeshChanged : 1; + + /** Is set to true when choice switches to curve mode. **/ + uint32 bSwitchedToCurve : 1; + + /** Is set to true if this parameter has been loaded. **/ + uint32 bLoadedParameter : 1; + + /** Is set to true if the asset input is actually connected inside Houdini. **/ + uint32 bInputAssetConnectedInHoudini : 1; + + /** Is set to true when landscape input is set to selection only. **/ + uint32 bLandscapeExportSelectionOnly : 1; + + /** Is set to true when landscape curves are to be exported. **/ + uint32 bLandscapeExportCurves : 1; + + /** Is set to true when the landscape is to be exported as a mesh, not just points. **/ + uint32 bLandscapeExportAsMesh : 1; + + /** Is set to true when materials are to be exported. **/ + uint32 bLandscapeExportMaterials : 1; + + /** Is set to true when lightmap information export is desired. **/ + uint32 bLandscapeExportLighting : 1; + + /** Is set to true when uvs should be exported in [0,1] space. **/ + uint32 bLandscapeExportNormalizedUVs : 1; + + /** Is set to true when uvs should be exported for each tile separately. **/ + uint32 bLandscapeExportTileUVs : 1; + + /** Is set to true when being used as an object-path parameter instead of an input */ + uint32 bIsObjectPathParameter : 1; + + /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value **/ + uint32 bKeepWorldTransform : 2; + + /** Is set to true when the landscape is to be exported as a heightfield **/ + uint32 bLandscapeExportAsHeightfield : 1; + + /** Is set to true when the automatic selection of landscape component is active **/ + uint32 bLandscapeAutoSelectComponent : 1; + + /** Indicates that the geometry must be packed before merging it into the input **/ + uint32 bPackBeforeMerge : 1; + + /** Indicates that all LODs in the input should be marshalled to Houdini **/ + uint32 bExportAllLODs : 1; + + /** Indicates that all sockets in the input should be marshalled to Houdini **/ + uint32 bExportSockets : 1; + + /** Indicates that the landscape input's source landscape should be updated instead of creating a new component **/ + uint32 bUpdateInputLandscape : 1; + }; + + uint32 HoudiniAssetInputFlagsPacked; + }; +}; + +UCLASS() +class UHoudiniAssetInstanceInput : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** List of fields created by this instance input. **/ + TArray InstanceInputFields; + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** Id of an object to instance. **/ + int ObjectToInstanceId; + +public: + /** Flags used by this input. **/ + union FHoudiniAssetInstanceInputFlags + { + struct + { + /** Set to true if this is an attribute instancer. **/ + uint32 bIsAttributeInstancer : 1; + + /** Set to true if this attribute instancer uses overrides. **/ + uint32 bAttributeInstancerOverride : 1; + + /** Set to true if this is a packed primitive instancer **/ + uint32 bIsPackedPrimitiveInstancer : 1; + + /** Set to true if this is a split mesh instancer */ + uint32 bIsSplitMeshInstancer : 1; + }; + + uint32 HoudiniAssetInstanceInputFlagsPacked; + }; + FHoudiniAssetInstanceInputFlags Flags; +}; + +UCLASS() +class UHoudiniAssetInstanceInputField : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** Original object used by the instancer. **/ + UObject* OriginalObject; + + /** Currently used Objects */ + TArray< UObject* > InstancedObjects; + + /** Used instanced actor component. **/ + TArray< USceneComponent * > InstancerComponents; + + /** Flags used by this input field. **/ + uint32 HoudiniAssetInstanceInputFieldFlagsPacked; + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** Rotation offset for instanced component. **/ + TArray< FRotator > RotationOffsets; + + /** Scale offset for instanced component. **/ + TArray< FVector > ScaleOffsets; + + /** Whether to scale linearly for all fields. **/ + TArray< bool > bScaleOffsetsLinearlyArray; + + /** Transforms, one for each instance. **/ + TArray< FTransform > InstancedTransforms; + + /** Assignment of Transforms to each variation **/ + TArray< TArray< FTransform > > VariationTransformsArray; + + /** Color overrides, one per instance **/ + TArray InstanceColorOverride; + + /** Per-variation color override assignments */ + TArray< TArray< FLinearColor > > VariationInstanceColorOverrideArray; +}; + +//UCLASS() +UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), + ShowCategories = (Mobility), editinlinenew) +class UHoudiniAssetComponent_V1 : public UPrimitiveComponent//, public IHoudiniCookHandler +{ + GENERATED_UCLASS_BODY() + +public: + /* + // IHoudiniCookHandler interface + virtual FString GetBakingBaseName(const struct FHoudiniGeoPartObject_V1& GeoPartObject) override { return FString(); }; + virtual void SetStaticMeshGenerationParameters(class UStaticMesh* StaticMesh) override {}; + virtual class UMaterialInterface * GetAssignmentMaterial(const FString& MaterialName) override { return nullptr; }; + virtual void ClearAssignmentMaterials() override {}; + virtual void AddAssignmentMaterial(const FString& MaterialName, class UMaterialInterface* MaterialInterface) override {}; + virtual class UMaterialInterface * GetReplacementMaterial(const struct FHoudiniGeoPartObject_V1& GeoPartObject, const FString& MaterialName) override { return nullptr; }; + */ + + /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Double Sided Geometry")) + uint32 bGeneratedDoubleSidedGeometry : 1; + + /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * GeneratedPhysMaterial; + + /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ + UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Collision Complexity")) + TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; + + /** Resolution of lightmap. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 GeneratedLightMapResolution; + + /** Bias multiplier for Light Propagation Volume lighting. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) + float GeneratedLpvBiasMultiplier; + + /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /** Custom walkable slope setting for generated mesh's body. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride GeneratedWalkableSlopeOverride; + + /** The light map coordinate index. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Light map coordinate index")) + int32 GeneratedLightMapCoordinateIndex; + + /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; + + /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Streaming Distance Multiplier")) + float GeneratedStreamingDistanceMultiplier; + + /** Default settings when using this mesh for instanced foliage. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; + + /** Array of user data stored with the asset. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Asset User Data")) + TArray GeneratedAssetUserData; + + /** The output folder for baking actions */ + UPROPERTY() + FText BakeFolder; + + /** The temporary output folder for cooking actions */ + UPROPERTY() + FText TempCookFolder; + + virtual void Serialize(FArchive & Ar) override; + + /** Houdini Asset associated with this component. **/ + UHoudiniAsset* HoudiniAsset; + + /** Unique GUID created by component. **/ + FGuid ComponentGUID; + + /** Scale factor used for generated geometry of this component. **/ + float GeneratedGeometryScaleFactor; + + /** Scale factor used for geo transforms of this component. **/ + float TransformScaleFactor; + + /** Buffer to hold preset data for serialization purposes. Used only during serialization. **/ + TArray PresetBuffer; + + /** Buffer to hold default preset for reset purposes. **/ + TArray DefaultPresetBuffer; + + /** Parameters for this component's asset, indexed by parameter id. **/ + //TMap Parameters; + TMap Parameters; + + /** Parameters for this component's asset, indexed by name for fast look up. **/ + TMap ParameterByName; + + /** Inputs for this component's asset. **/ + TArray Inputs; + + /** Instance inputs for this component's asset **/ + TArray InstanceInputs; + + /** Material assignments. **/ + UHoudiniAssetComponentMaterials_V1 * HoudiniAssetComponentMaterials; + + /** Map of HAPI objects and corresponding static meshes. Also map of static meshes and corresponding components. **/ + TMap StaticMeshes; + TMap StaticMeshComponents; + + /** List of dependent downstream asset connections that have this asset as an asset input. **/ + TMap> DownstreamAssetConnections; + + /** Map of asset handle components. **/ + TMap HandleComponents; + + /** Map of curve / spline components. **/ + TMap SplineComponents; + + /** Map of Landscape / Heightfield components. **/ + TMap> LandscapeComponents; + + /** Overrides for baking names per part */ + TMap BakeNameOverrides; + + /** Import axis. **/ + uint8 ImportAxis; + + /** Flags used by Houdini component. **/ + union + { + struct + { + /** Enables cooking for this Houdini Asset. **/ + uint32 bEnableCooking : 1; + + /** Enables uploading of transformation changes back to Houdini Engine. **/ + uint32 bUploadTransformsToHoudiniEngine : 1; + + /** Enables cooking upon transformation changes. **/ + uint32 bTransformChangeTriggersCooks : 1; + + /** Is set to true when this component contains Houdini logo geometry. **/ + uint32 bContainsHoudiniLogoGeometry : 1; + + /** Is set to true when this component is native and false is when it is dynamic. **/ + uint32 bIsNativeComponent : 1; + + /** Is set to true when this component belongs to a preview actor. **/ + uint32 bIsPreviewComponent : 1; + + /** Is set to true if this component has been loaded. **/ + uint32 bLoadedComponent : 1; + + /** Unused **/ + uint32 bIsPlayModeActive_Unused : 1; + + /** unused flag **/ + uint32 bTimeCookInPlaymode_Unused : 1; + + /** Is set to true when Houdini materials are used. **/ + uint32 bUseHoudiniMaterials : 1; + + /** Is set to true when cooking this asset will trigger cooks of downstream connected assets. **/ + uint32 bCookingTriggersDownstreamCooks : 1; + + /** Is set to true after the asset is fully loaded and registered **/ + uint32 bFullyLoaded : 1; + }; + + uint32 HoudiniAssetComponentFlagsPacked; + }; +}; + +UCLASS() +class UHoudiniInstancedActorComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC); + + //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) + UObject* InstancedAsset; + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + TArray Instances; +}; + +UCLASS() +class UHoudiniMeshSplitInstancerComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC); + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + TArray Instances; + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + UMaterialInterface* OverrideMaterial; + + //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) + UStaticMesh* InstancedMesh; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp index 9e71ff1ac..fc9e61728 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp @@ -1,32 +1,32 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineCopyPropertiesInterface.h" - -void IHoudiniEngineCopyPropertiesInterface::CopyPropertiesFrom(UObject* FromObject) -{ - -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineCopyPropertiesInterface.h" + +void IHoudiniEngineCopyPropertiesInterface::CopyPropertiesFrom(UObject* FromObject) +{ + +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h index aa49d8f5a..57cf3cf8d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Engine/Engine.h" -#include "UObject/ObjectMacros.h" -#include "UObject/Interface.h" -#include "HoudiniEngineCopyPropertiesInterface.generated.h" - - -UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) -class UHoudiniEngineCopyPropertiesInterface : public UInterface -{ - GENERATED_BODY() -}; - -class HOUDINIENGINERUNTIME_API IHoudiniEngineCopyPropertiesInterface -{ - GENERATED_BODY() - -public: - virtual void CopyPropertiesFrom(UObject* FromObject); -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Engine/Engine.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Interface.h" +#include "HoudiniEngineCopyPropertiesInterface.generated.h" + + +UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) +class UHoudiniEngineCopyPropertiesInterface : public UInterface +{ + GENERATED_BODY() +}; + +class HOUDINIENGINERUNTIME_API IHoudiniEngineCopyPropertiesInterface +{ + GENERATED_BODY() + +public: + virtual void CopyPropertiesFrom(UObject* FromObject); +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp index a10eb445d..31c72c8cc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp @@ -1,323 +1,323 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" - -#include "HoudiniAssetComponent.h" - -#include "Modules/ModuleManager.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FHoudiniEngineRuntime, HoudiniEngineRuntime); -DEFINE_LOG_CATEGORY(LogHoudiniEngineRuntime); - -FHoudiniEngineRuntime * -FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; - - -FHoudiniEngineRuntime & -FHoudiniEngineRuntime::Get() -{ - return *HoudiniEngineRuntimeInstance; -} - - -bool -FHoudiniEngineRuntime::IsInitialized() -{ - return FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance != nullptr; -} - - -FHoudiniEngineRuntime::FHoudiniEngineRuntime() -{ -} - - -void FHoudiniEngineRuntime::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module - // Store the instance. - FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = this; -} - - -void FHoudiniEngineRuntime::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. - FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; -} - - -int32 -FHoudiniEngineRuntime::GetRegisteredHoudiniComponentCount() -{ - if (!IsInitialized()) - return 0; - - FScopeLock ScopeLock(&CriticalSection); - return RegisteredHoudiniComponents.Num(); -} - - -UHoudiniAssetComponent* -FHoudiniEngineRuntime::GetRegisteredHoudiniComponentAt(const int32& Index) -{ - if (!IsInitialized()) - return nullptr; - - FScopeLock ScopeLock(&CriticalSection); - - if (!RegisteredHoudiniComponents.IsValidIndex(Index)) - return nullptr; - - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Index]; - if (!Ptr.IsValid()) - return nullptr; - - if (Ptr.IsStale()) - return nullptr; - - return Ptr.Get(); -} - - -void -FHoudiniEngineRuntime::CleanUpRegisteredHoudiniComponents() -{ - // Remove Stale and invalid components - FScopeLock ScopeLock(&CriticalSection); - for (int Idx = RegisteredHoudiniComponents.Num() - 1; Idx >= 0; Idx--) - { - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Idx]; - if ( !Ptr.IsValid() || Ptr.IsStale() ) - { - UnRegisterHoudiniComponent(Idx); - continue; - } - - UHoudiniAssetComponent* CurrentHAC = Ptr.Get(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) - { - UnRegisterHoudiniComponent(Idx); - continue; - } - } -} - - -bool -FHoudiniEngineRuntime::IsComponentRegistered(UHoudiniAssetComponent* HAC) const -{ - // No need for duplicates - if (HAC && RegisteredHoudiniComponents.Find(HAC) != INDEX_NONE) - return true; - - return false; -} - - -void -FHoudiniEngineRuntime::RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype) -{ - if (!FHoudiniEngineRuntime::IsInitialized()) - return; - - if (!HAC || HAC->IsPendingKill()) - return; - - // RF_Transient indicates a temporary/preview object - // No need to instantiate/cook those in Houdini - // RF_ArchetypeObject is the template for blueprinted HDA, so we need to be able to register those. - if (HAC->HasAnyFlags(RF_Transient) || (HAC->HasAnyFlags(RF_ArchetypeObject) && !bAllowArchetype) || HAC->HasAnyFlags(RF_ClassDefaultObject)) - return; - - // No need for duplicates - if (IsComponentRegistered(HAC)) - return; - - HOUDINI_BP_MESSAGE(TEXT("[FHoudiniEngineRuntime::RegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); - - // Before adding, clean up the all ready registered - CleanUpRegisteredHoudiniComponents(); - - // Add the new component - { - FScopeLock ScopeLock(&CriticalSection); - RegisteredHoudiniComponents.Add(HAC); - } - - HAC->NotifyHoudiniRegisterCompleted(); -} - - -void -FHoudiniEngineRuntime::MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent) -{ - if (InNodeId >= 0) - { - // FDebug::DumpStackTraceToLog(); - - NodeIdsPendingDelete.AddUnique(InNodeId); - - if (bDeleteParent) - { - NodeIdsParentPendingDelete.AddUnique(InNodeId); - } - } -} - - -void -FHoudiniEngineRuntime::UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC) -{ - if (!IsInitialized()) - return; - - if (!HAC || HAC->IsPendingKill()) - return; - - // Calling GetPathName here may lead to some crashes due to invalid outers... - //HOUDINI_LOG_DISPLAY(TEXT("[FHoudiniEngineRuntime::UnRegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); - - FScopeLock ScopeLock(&CriticalSection); - - int32 FoundIdx = RegisteredHoudiniComponents.Find(HAC); - if (!RegisteredHoudiniComponents.IsValidIndex(FoundIdx)) - return; - HAC->NotifyHoudiniPreUnregister(); - UnRegisterHoudiniComponent(FoundIdx); - HAC->NotifyHoudiniPostUnregister(); -} - - -void -FHoudiniEngineRuntime::UnRegisterHoudiniComponent(const int32& ValidIndex) -{ - if (!IsInitialized()) - return; - - FScopeLock ScopeLock(&CriticalSection); - - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[ValidIndex]; - if (Ptr.IsValid(true, false)) - { - UHoudiniAssetComponent* HAC = Ptr.Get(); - if (HAC && HAC->CanDeleteHoudiniNodes()) - { - MarkNodeIdAsPendingDelete(HAC->GetAssetId(), true); - } - } - - RegisteredHoudiniComponents.RemoveAt(ValidIndex); -} - - -int32 -FHoudiniEngineRuntime::GetNodeIdsPendingDeleteCount() -{ - if (!IsInitialized()) - return 0; - - FScopeLock ScopeLock(&CriticalSection); - - return NodeIdsPendingDelete.Num(); -} - - -int32 -FHoudiniEngineRuntime::GetNodeIdsPendingDeleteAt(const int32& Index) -{ - if (!IsInitialized()) - return -1; - - FScopeLock ScopeLock(&CriticalSection); - - if (!NodeIdsPendingDelete.IsValidIndex(Index)) - return -1; - - return NodeIdsPendingDelete[Index]; -} - - -void -FHoudiniEngineRuntime::RemoveNodeIdPendingDeleteAt(const int32& Index) -{ - if (!IsInitialized()) - return; - - FScopeLock ScopeLock(&CriticalSection); - if (!NodeIdsPendingDelete.IsValidIndex(Index)) - return; - - NodeIdsPendingDelete.RemoveAt(Index); -} - - -bool -FHoudiniEngineRuntime::IsParentNodePendingDelete(const int32& NodeId) -{ - return NodeIdsParentPendingDelete.Contains(NodeId); -} - - -void -FHoudiniEngineRuntime::RemoveParentNodePendingDelete(const int32& NodeId) -{ - if (NodeIdsParentPendingDelete.Contains(NodeId)) - NodeIdsParentPendingDelete.Remove(NodeId); -} - - -FString -FHoudiniEngineRuntime::GetDefaultTemporaryCookFolder() const -{ - // Get Runtime settings to get the Temp Cook Folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (!HoudiniRuntimeSettings) - return HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - - return HoudiniRuntimeSettings->DefaultTemporaryCookFolder; -} - - -FString -FHoudiniEngineRuntime::GetDefaultBakeFolder() const -{ - // Get Runtime settings to get the default bake Folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (!HoudiniRuntimeSettings) - return HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - return HoudiniRuntimeSettings->DefaultBakeFolder; -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" + +#include "HoudiniAssetComponent.h" + +#include "Modules/ModuleManager.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHoudiniEngineRuntime, HoudiniEngineRuntime); +DEFINE_LOG_CATEGORY(LogHoudiniEngineRuntime); + +FHoudiniEngineRuntime * +FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; + + +FHoudiniEngineRuntime & +FHoudiniEngineRuntime::Get() +{ + return *HoudiniEngineRuntimeInstance; +} + + +bool +FHoudiniEngineRuntime::IsInitialized() +{ + return FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance != nullptr; +} + + +FHoudiniEngineRuntime::FHoudiniEngineRuntime() +{ +} + + +void FHoudiniEngineRuntime::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + // Store the instance. + FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = this; +} + + +void FHoudiniEngineRuntime::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. + FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; +} + + +int32 +FHoudiniEngineRuntime::GetRegisteredHoudiniComponentCount() +{ + if (!IsInitialized()) + return 0; + + FScopeLock ScopeLock(&CriticalSection); + return RegisteredHoudiniComponents.Num(); +} + + +UHoudiniAssetComponent* +FHoudiniEngineRuntime::GetRegisteredHoudiniComponentAt(const int32& Index) +{ + if (!IsInitialized()) + return nullptr; + + FScopeLock ScopeLock(&CriticalSection); + + if (!RegisteredHoudiniComponents.IsValidIndex(Index)) + return nullptr; + + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Index]; + if (!Ptr.IsValid()) + return nullptr; + + if (Ptr.IsStale()) + return nullptr; + + return Ptr.Get(); +} + + +void +FHoudiniEngineRuntime::CleanUpRegisteredHoudiniComponents() +{ + // Remove Stale and invalid components + FScopeLock ScopeLock(&CriticalSection); + for (int Idx = RegisteredHoudiniComponents.Num() - 1; Idx >= 0; Idx--) + { + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Idx]; + if ( !Ptr.IsValid() || Ptr.IsStale() ) + { + UnRegisterHoudiniComponent(Idx); + continue; + } + + UHoudiniAssetComponent* CurrentHAC = Ptr.Get(); + if (!CurrentHAC || CurrentHAC->IsPendingKill()) + { + UnRegisterHoudiniComponent(Idx); + continue; + } + } +} + + +bool +FHoudiniEngineRuntime::IsComponentRegistered(UHoudiniAssetComponent* HAC) const +{ + // No need for duplicates + if (HAC && RegisteredHoudiniComponents.Find(HAC) != INDEX_NONE) + return true; + + return false; +} + + +void +FHoudiniEngineRuntime::RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype) +{ + if (!FHoudiniEngineRuntime::IsInitialized()) + return; + + if (!HAC || HAC->IsPendingKill()) + return; + + // RF_Transient indicates a temporary/preview object + // No need to instantiate/cook those in Houdini + // RF_ArchetypeObject is the template for blueprinted HDA, so we need to be able to register those. + if (HAC->HasAnyFlags(RF_Transient) || (HAC->HasAnyFlags(RF_ArchetypeObject) && !bAllowArchetype) || HAC->HasAnyFlags(RF_ClassDefaultObject)) + return; + + // No need for duplicates + if (IsComponentRegistered(HAC)) + return; + + HOUDINI_BP_MESSAGE(TEXT("[FHoudiniEngineRuntime::RegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); + + // Before adding, clean up the all ready registered + CleanUpRegisteredHoudiniComponents(); + + // Add the new component + { + FScopeLock ScopeLock(&CriticalSection); + RegisteredHoudiniComponents.Add(HAC); + } + + HAC->NotifyHoudiniRegisterCompleted(); +} + + +void +FHoudiniEngineRuntime::MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent) +{ + if (InNodeId >= 0) + { + // FDebug::DumpStackTraceToLog(); + + NodeIdsPendingDelete.AddUnique(InNodeId); + + if (bDeleteParent) + { + NodeIdsParentPendingDelete.AddUnique(InNodeId); + } + } +} + + +void +FHoudiniEngineRuntime::UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC) +{ + if (!IsInitialized()) + return; + + if (!HAC || HAC->IsPendingKill()) + return; + + // Calling GetPathName here may lead to some crashes due to invalid outers... + //HOUDINI_LOG_DISPLAY(TEXT("[FHoudiniEngineRuntime::UnRegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); + + FScopeLock ScopeLock(&CriticalSection); + + int32 FoundIdx = RegisteredHoudiniComponents.Find(HAC); + if (!RegisteredHoudiniComponents.IsValidIndex(FoundIdx)) + return; + HAC->NotifyHoudiniPreUnregister(); + UnRegisterHoudiniComponent(FoundIdx); + HAC->NotifyHoudiniPostUnregister(); +} + + +void +FHoudiniEngineRuntime::UnRegisterHoudiniComponent(const int32& ValidIndex) +{ + if (!IsInitialized()) + return; + + FScopeLock ScopeLock(&CriticalSection); + + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[ValidIndex]; + if (Ptr.IsValid(true, false)) + { + UHoudiniAssetComponent* HAC = Ptr.Get(); + if (HAC && HAC->CanDeleteHoudiniNodes()) + { + MarkNodeIdAsPendingDelete(HAC->GetAssetId(), true); + } + } + + RegisteredHoudiniComponents.RemoveAt(ValidIndex); +} + + +int32 +FHoudiniEngineRuntime::GetNodeIdsPendingDeleteCount() +{ + if (!IsInitialized()) + return 0; + + FScopeLock ScopeLock(&CriticalSection); + + return NodeIdsPendingDelete.Num(); +} + + +int32 +FHoudiniEngineRuntime::GetNodeIdsPendingDeleteAt(const int32& Index) +{ + if (!IsInitialized()) + return -1; + + FScopeLock ScopeLock(&CriticalSection); + + if (!NodeIdsPendingDelete.IsValidIndex(Index)) + return -1; + + return NodeIdsPendingDelete[Index]; +} + + +void +FHoudiniEngineRuntime::RemoveNodeIdPendingDeleteAt(const int32& Index) +{ + if (!IsInitialized()) + return; + + FScopeLock ScopeLock(&CriticalSection); + if (!NodeIdsPendingDelete.IsValidIndex(Index)) + return; + + NodeIdsPendingDelete.RemoveAt(Index); +} + + +bool +FHoudiniEngineRuntime::IsParentNodePendingDelete(const int32& NodeId) +{ + return NodeIdsParentPendingDelete.Contains(NodeId); +} + + +void +FHoudiniEngineRuntime::RemoveParentNodePendingDelete(const int32& NodeId) +{ + if (NodeIdsParentPendingDelete.Contains(NodeId)) + NodeIdsParentPendingDelete.Remove(NodeId); +} + + +FString +FHoudiniEngineRuntime::GetDefaultTemporaryCookFolder() const +{ + // Get Runtime settings to get the Temp Cook Folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (!HoudiniRuntimeSettings) + return HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + + return HoudiniRuntimeSettings->DefaultTemporaryCookFolder; +} + + +FString +FHoudiniEngineRuntime::GetDefaultBakeFolder() const +{ + // Get Runtime settings to get the default bake Folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (!HoudiniRuntimeSettings) + return HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + return HoudiniRuntimeSettings->DefaultBakeFolder; +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h index ecf7c8bfd..d8998dcdb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h @@ -1,107 +1,107 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" -#include "HoudiniPDGAssetLink.h" - -#include "Modules/ModuleInterface.h" -#include "Misc/ScopeLock.h" -#include "UObject/WeakObjectPtrTemplates.h" - -class HOUDINIENGINERUNTIME_API FHoudiniEngineRuntime : public IModuleInterface -{ - public: - FHoudiniEngineRuntime(); - - // - // IModuleInterface methods. - // - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // Return singleton instance of Houdini Engine Runtime, used internally. - static FHoudiniEngineRuntime & Get(); - - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // - // Houdini Asset Component registry - // - // Ensure that the registered components are all still valid - void CleanUpRegisteredHoudiniComponents(); - - void RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype=false); - - void UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC); - void UnRegisterHoudiniComponent(const int32& ValidIdx); - - bool IsComponentRegistered(UHoudiniAssetComponent* HAC) const; - int32 GetRegisteredHoudiniComponentCount(); - UHoudiniAssetComponent* GetRegisteredHoudiniComponentAt(const int32& Index); - - virtual TArray>* GetRegisteredHoudiniComponents() { return &RegisteredHoudiniComponents; }; - - // - // Node deletion - // - void MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent = false); - - int32 GetNodeIdsPendingDeleteCount(); - int32 GetNodeIdsPendingDeleteAt(const int32& Index); - void RemoveNodeIdPendingDeleteAt(const int32& Index); - - bool IsParentNodePendingDelete(const int32& NodeId); - - void RemoveParentNodePendingDelete(const int32& NodeId); - - // - // - // - - // Returns the folder to be used for temporary cook content - FString GetDefaultTemporaryCookFolder() const; - - // Returns the defualt folder used for baking - FString GetDefaultBakeFolder() const; - - private: - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // Singleton instance. - static FHoudiniEngineRuntime * HoudiniEngineRuntimeInstance; - - // - TArray> RegisteredHoudiniComponents; - - TArray NodeIdsPendingDelete; - - TArray NodeIdsParentPendingDelete; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" +#include "HoudiniPDGAssetLink.h" + +#include "Modules/ModuleInterface.h" +#include "Misc/ScopeLock.h" +#include "UObject/WeakObjectPtrTemplates.h" + +class HOUDINIENGINERUNTIME_API FHoudiniEngineRuntime : public IModuleInterface +{ + public: + FHoudiniEngineRuntime(); + + // + // IModuleInterface methods. + // + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // Return singleton instance of Houdini Engine Runtime, used internally. + static FHoudiniEngineRuntime & Get(); + + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // + // Houdini Asset Component registry + // + // Ensure that the registered components are all still valid + void CleanUpRegisteredHoudiniComponents(); + + void RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype=false); + + void UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC); + void UnRegisterHoudiniComponent(const int32& ValidIdx); + + bool IsComponentRegistered(UHoudiniAssetComponent* HAC) const; + int32 GetRegisteredHoudiniComponentCount(); + UHoudiniAssetComponent* GetRegisteredHoudiniComponentAt(const int32& Index); + + virtual TArray>* GetRegisteredHoudiniComponents() { return &RegisteredHoudiniComponents; }; + + // + // Node deletion + // + void MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent = false); + + int32 GetNodeIdsPendingDeleteCount(); + int32 GetNodeIdsPendingDeleteAt(const int32& Index); + void RemoveNodeIdPendingDeleteAt(const int32& Index); + + bool IsParentNodePendingDelete(const int32& NodeId); + + void RemoveParentNodePendingDelete(const int32& NodeId); + + // + // + // + + // Returns the folder to be used for temporary cook content + FString GetDefaultTemporaryCookFolder() const; + + // Returns the defualt folder used for baking + FString GetDefaultBakeFolder() const; + + private: + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // Singleton instance. + static FHoudiniEngineRuntime * HoudiniEngineRuntimeInstance; + + // + TArray> RegisteredHoudiniComponents; + + TArray NodeIdsPendingDelete; + + TArray NodeIdsParentPendingDelete; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h index b5c8ace83..da5cfb633 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h @@ -1,273 +1,273 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Logging/LogMacros.h" - -// Define module names. -#define HOUDINI_MODULE "HoudiniEngine" -#define HOUDINI_MODULE_EDITOR "HoudiniEngineEditor" -#define HOUDINI_MODULE_RUNTIME "HoudiniEngineRuntime" - -// Declare the log category depending on the module we're in -#ifdef HOUDINI_ENGINE_EDITOR - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_EDITOR -HOUDINIENGINEEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, All); -#else - #ifdef HOUDINI_ENGINE - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE - HOUDINIENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngine, Log, All); - #else - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME - HOUDINIENGINERUNTIME_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineRuntime, Log, All); - #endif -#endif - -//--------------------------------------------------------------------------------------------------------------------- -// Default session settings -//--------------------------------------------------------------------------------------------------------------------- - -#define HAPI_UNREAL_SESSION_SERVER_AUTOSTART true -#define HAPI_UNREAL_SESSION_SERVER_TIMEOUT 3000.0f -#define HAPI_UNREAL_SESSION_SERVER_HOST TEXT( "localhost" ) -#define HAPI_UNREAL_SESSION_SERVER_PORT 9090 -#if PLATFORM_MAC - #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "/tmp/hapi" ) -#else - #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "hapi" ) -#endif - - - -// Names of HAPI libraries on different platforms. -#define HAPI_LIB_OBJECT_WINDOWS TEXT( "libHAPIL.dll" ) -#define HAPI_LIB_OBJECT_MAC TEXT( "libHAPIL.dylib" ) -#define HAPI_LIB_OBJECT_LINUX TEXT( "libHAPIL.so" ) - -//--------------------------------------------------------------------------------------------------------------------- -// LOG MACROS -//--------------------------------------------------------------------------------------------------------------------- - -// Whether to enable logging. -#define HOUDINI_ENGINE_LOGGING 1 - -#ifdef HOUDINI_ENGINE_LOGGING - #ifdef HOUDINI_ENGINE - #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngine, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #else - #ifdef HOUDINI_ENGINE_EDITOR - #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngineEditor, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #else - #define HOUDINI_LOG_HELPER( VERBOSITY, HOUDINI_LOG_TEXT, ... ) \ - do \ - { \ - UE_LOG( LogHoudiniEngineRuntime, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #endif - #endif - - #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Display, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) -#endif - - -#define HOUDINI_DEBUG_EXPAND_UE_LOG(LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngine##LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - - -// --------------------------------------------------------- -// Blueprint Debug Logging -// --------------------------------------------------------- -// Set HOUDINI_ENGINE_DEBUG_BP=1 to enable Blueprint logging -#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BP) - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); - #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() - #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - - -// --------------------------------------------------------- -// PDG Debug Logging -// --------------------------------------------------------- -// Set HOUDINI_ENGINE_DEBUG_PDG=1 to enable PDG logging -#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_PDG) - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEnginePDG, Log, All); - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); - #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() - #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - - -// --------------------------------------------------------- -// Landscape Debug Logging -// --------------------------------------------------------- -// Set HOUDINI_ENGINE_DEBUG_LANDSCAPE=1 to enable PDG logging -#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineLandscape, Log, All); - #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEngineLandscape); - #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() - #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - - -// --------------------------------------------------------- -// Baking Debug Logging -// --------------------------------------------------------- -// Set HOUDINI_ENGINE_DEBUG_BAKING=1 to enable PDG logging -#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BAKING) - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBaking, Log, All); - #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEngineBaking); - #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() - #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - - -// HAPI_Common -enum HAPI_UNREAL_NodeType -{ - HAPI_UNREAL_NODETYPE_ANY = -1, - HAPU_UNREAL_NODETYPE_NONE = 0 -}; - -enum HAPI_UNREAL_NodeFlags -{ - HAPI_UNREAL_NODEFLAGS_ANY = -1, - HAPI_UNREAL_NODEFLAGS_NONE = 0 -}; - -// Default cook/bake folder -#define HAPI_UNREAL_DEFAULT_BAKE_FOLDER TEXT("/Game/HoudiniEngine/Bake"); -#define HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER TEXT("/Game/HoudiniEngine/Temp"); - -// Default PDG Filters -#define HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER "HE_"; -#define HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER "HE_OUT_"; - -// Struct to enable global silent flag - this will force dialogs to not show up. -struct FHoudiniScopedGlobalSilence -{ - FHoudiniScopedGlobalSilence() - { - bGlobalSilent = GIsSilent; - GIsSilent = true; - } - - ~FHoudiniScopedGlobalSilence() - { - GIsSilent = bGlobalSilent; - } - - bool bGlobalSilent; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" + +// Define module names. +#define HOUDINI_MODULE "HoudiniEngine" +#define HOUDINI_MODULE_EDITOR "HoudiniEngineEditor" +#define HOUDINI_MODULE_RUNTIME "HoudiniEngineRuntime" + +// Declare the log category depending on the module we're in +#ifdef HOUDINI_ENGINE_EDITOR + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_EDITOR +HOUDINIENGINEEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, All); +#else + #ifdef HOUDINI_ENGINE + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE + HOUDINIENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngine, Log, All); + #else + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME + HOUDINIENGINERUNTIME_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineRuntime, Log, All); + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------- +// Default session settings +//--------------------------------------------------------------------------------------------------------------------- + +#define HAPI_UNREAL_SESSION_SERVER_AUTOSTART true +#define HAPI_UNREAL_SESSION_SERVER_TIMEOUT 3000.0f +#define HAPI_UNREAL_SESSION_SERVER_HOST TEXT( "localhost" ) +#define HAPI_UNREAL_SESSION_SERVER_PORT 9090 +#if PLATFORM_MAC + #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "/tmp/hapi" ) +#else + #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "hapi" ) +#endif + + + +// Names of HAPI libraries on different platforms. +#define HAPI_LIB_OBJECT_WINDOWS TEXT( "libHAPIL.dll" ) +#define HAPI_LIB_OBJECT_MAC TEXT( "libHAPIL.dylib" ) +#define HAPI_LIB_OBJECT_LINUX TEXT( "libHAPIL.so" ) + +//--------------------------------------------------------------------------------------------------------------------- +// LOG MACROS +//--------------------------------------------------------------------------------------------------------------------- + +// Whether to enable logging. +#define HOUDINI_ENGINE_LOGGING 1 + +#ifdef HOUDINI_ENGINE_LOGGING + #ifdef HOUDINI_ENGINE + #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngine, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #else + #ifdef HOUDINI_ENGINE_EDITOR + #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngineEditor, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #else + #define HOUDINI_LOG_HELPER( VERBOSITY, HOUDINI_LOG_TEXT, ... ) \ + do \ + { \ + UE_LOG( LogHoudiniEngineRuntime, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #endif + #endif + + #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Display, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) +#endif + + +#define HOUDINI_DEBUG_EXPAND_UE_LOG(LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngine##LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + + +// --------------------------------------------------------- +// Blueprint Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_BP=1 to enable Blueprint logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BP) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); + #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() + #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + +// --------------------------------------------------------- +// PDG Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_PDG=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_PDG) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEnginePDG, Log, All); + #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); + #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() + #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + +// --------------------------------------------------------- +// Landscape Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_LANDSCAPE=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineLandscape, Log, All); + #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineLandscape); + #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() + #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + +// --------------------------------------------------------- +// Baking Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_BAKING=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BAKING) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBaking, Log, All); + #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineBaking); + #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() + #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + +// HAPI_Common +enum HAPI_UNREAL_NodeType +{ + HAPI_UNREAL_NODETYPE_ANY = -1, + HAPU_UNREAL_NODETYPE_NONE = 0 +}; + +enum HAPI_UNREAL_NodeFlags +{ + HAPI_UNREAL_NODEFLAGS_ANY = -1, + HAPI_UNREAL_NODEFLAGS_NONE = 0 +}; + +// Default cook/bake folder +#define HAPI_UNREAL_DEFAULT_BAKE_FOLDER TEXT("/Game/HoudiniEngine/Bake"); +#define HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER TEXT("/Game/HoudiniEngine/Temp"); + +// Default PDG Filters +#define HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER "HE_"; +#define HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER "HE_OUT_"; + +// Struct to enable global silent flag - this will force dialogs to not show up. +struct FHoudiniScopedGlobalSilence +{ + FHoudiniScopedGlobalSilence() + { + bGlobalSilent = GIsSilent; + GIsSilent = true; + } + + ~FHoudiniScopedGlobalSilence() + { + GIsSilent = bGlobalSilent; + } + + bool bGlobalSilent; +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp index 544b13b96..d2b563a12 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp @@ -1,660 +1,660 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" - -#include "EngineUtils.h" -#include "Engine/EngineTypes.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Kismet2/BlueprintEditorUtils.h" -#endif - - -FString -FHoudiniEngineRuntimeUtils::GetLibHAPIName() -{ - static const FString LibHAPIName = - -#if PLATFORM_WINDOWS - HAPI_LIB_OBJECT_WINDOWS; -#elif PLATFORM_MAC - HAPI_LIB_OBJECT_MAC; -#elif PLATFORM_LINUX - HAPI_LIB_OBJECT_LINUX; -#else - TEXT(""); -#endif - - return LibHAPIName; -} - - -void -FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes) -{ - OutBBoxes.Empty(); - - for (auto CurrentActor : InActors) - { - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true)); - } -} - - -bool -FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors) -{ - if (!IsValid(World)) - return false; - - OutActors.Empty(); - for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) - { - AActor* CurrentActor = *ActorItr; - if (!IsValid(CurrentActor)) - continue; - - if (!CurrentActor->GetClass()->IsChildOf(ActorType.Get())) - continue; - - if (ExcludeActors && ExcludeActors->Contains(CurrentActor)) - continue; - - // Special case - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); - for (auto InBounds : BBoxes) - { - // Check if both actor's bounds intersects - if (!ActorBounds.Intersect(InBounds)) - continue; - - OutActors.Add(CurrentActor); - break; - } - } - - return true; -} - -bool -FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly) -{ - bool bDeleted = false; - OutPackage = nullptr; - bOutPackageIsInMemoryOnly = false; - - if (!IsValid(InObjectToDelete)) - return false; - - // Don't try to delete the object if it has references (we do this here to avoid the FMessageDialog in DeleteSingleObject - bool bIsReferenced = false; - bool bIsReferencedByUndo = false; - if (!GatherObjectReferencersForDeletion(InObjectToDelete, bIsReferenced, bIsReferencedByUndo)) - return false; - - if (bIsReferenced) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject] Not deleting %s: there are still references to it."), *InObjectToDelete->GetFullName()); - } - else - { - // Even though we already checked for references, we still let DeleteSingleObject check for references, since - // we want that code path where it'll clean up in-memory references (undo buffer/transactions) - const bool bCheckForReferences = true; - if (DeleteSingleObject(InObjectToDelete, bCheckForReferences)) - { - bDeleted = true; - - OutPackage = InObjectToDelete->GetOutermost(); - - FString PackageFilename; - if (!IsValid(OutPackage) || !FPackageName::DoesPackageExist(OutPackage->GetName(), nullptr, &PackageFilename)) - { - // Package is in memory only, we don't have call CleanUpAfterSuccessfulDelete on it, just do garbage - // collection to pick up the stale package - bOutPackageIsInMemoryOnly = true; - } - else - { - // There is an on-disk package that is now potentially empty, CleanUpAfterSuccessfulDelete must be - // called on this. Since CleanUpAfterSuccessfulDelete does garbage collection, we return the Package - // as part of this function so that the caller can collect all Packages and do one call to - // CleanUpAfterSuccessfulDelete with an array - } - } - } - - return bDeleted; -} - -int32 -FHoudiniEngineRuntimeUtils::SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted) -{ - int32 NumDeleted = 0; - bool bGarbageCollectionRequired = false; - TSet PackagesToCleanUp; - TSet ProcessedObjects; - while (InObjectsToDelete.Num() > 0) - { - UObject* const ObjectToDelete = InObjectsToDelete.Pop(); - - if (ProcessedObjects.Contains(ObjectToDelete)) - continue; - - ProcessedObjects.Add(ObjectToDelete); - - if (!IsValid(ObjectToDelete)) - continue; - - UPackage* Package = nullptr; - bool bInMemoryPackageOnly = false; - if (SafeDeleteSingleObject(ObjectToDelete, Package, bInMemoryPackageOnly)) - { - NumDeleted++; - if (bInMemoryPackageOnly) - { - // Packages that are in-memory only are cleaned up by garbage collection - if (!bGarbageCollectionRequired) - bGarbageCollectionRequired = true; - } - else - { - // Clean up potentially empty packages in one call to CleanupAfterSuccessfulDelete at the end - PackagesToCleanUp.Add(Package); - } - } - else if (OutObjectsNotDeleted) - { - OutObjectsNotDeleted->Add(ObjectToDelete); - } - } - - // CleanupAfterSuccessfulDelete calls CollectGarbage, so don't call it here if we have PackagesToCleanUp - if (bGarbageCollectionRequired && PackagesToCleanUp.Num() <= 0) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - if (PackagesToCleanUp.Num() > 0) - CleanupAfterSuccessfulDelete(PackagesToCleanUp.Array()); - - return NumDeleted; -} - - -#if WITH_EDITOR -int32 -FHoudiniEngineRuntimeUtils::CopyComponentProperties(UActorComponent* SourceComponent, UActorComponent* TargetComponent, const EditorUtilities::FCopyOptions& Options) -{ - UClass* ComponentClass = SourceComponent->GetClass(); - check( ComponentClass == TargetComponent->GetClass() ); - - const bool bIsPreviewing = ( Options.Flags & EditorUtilities::ECopyOptions::PreviewOnly ) != 0; - int32 CopiedPropertyCount = 0; - bool bTransformChanged = false; - - // Build a list of matching component archetype instances for propagation (if requested) - TArray ComponentArchetypeInstances; - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - TArray Instances; - TargetComponent->GetArchetypeInstances(Instances); - for(UObject* ObjInstance : Instances) - { - UActorComponent* ComponentInstance = Cast(ObjInstance); - if (ComponentInstance && ComponentInstance != SourceComponent && ComponentInstance != TargetComponent) - { - ComponentArchetypeInstances.Add(ComponentInstance); - } - } - } - - TSet SourceUCSModifiedProperties; - SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties); - - TArray ComponentInstancesToReregister; - - // Copy component properties - for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) - { - const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); - const bool bIsIdentical = Property->Identical_InContainer( SourceComponent, TargetComponent ); - const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); - const bool bIsTransform = - Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); - - // auto SourceComponentIsRoot = [&]() - // { - // USceneComponent* RootComponent = SourceActor->GetRootComponent(); - // if (SourceComponent == RootComponent) - // { - // return true; - // } - // else if (RootComponent == nullptr && bSourceActorIsBPCDO) - // { - // // If we're dealing with a BP CDO as source, then look at the target for whether this is the root component - // return (TargetComponent == TargetActor->GetRootComponent()); - // } - // return false; - // }; - - TSet ModifiedObjects; - // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - // && ( !bIsTransform || (!bSourceActorIsCDO && !bTargetActorIsCDO) || !SourceComponentIsRoot() ) ) - if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - && ( !bIsTransform )) - { - const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) - && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); - if( bIsSafeToCopy ) - { - // if (!Options.CanCopyProperty(*Property, *SourceActor)) - // { - // continue; - // } - if (!Options.CanCopyProperty(*Property, *SourceComponent)) - { - continue; - } - - if( !bIsPreviewing ) - { - if( !ModifiedObjects.Contains(TargetComponent) ) - { - TargetComponent->SetFlags(RF_Transactional); - TargetComponent->Modify(); - ModifiedObjects.Add(TargetComponent); - } - - if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) - { - TargetComponent->PreEditChange( Property ); - } - - // Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances. - TArray ComponentArchetypeInstancesToChange; - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances) - { - if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) ) - { - bool bAdd = true; - // We also need to double check that either the direct archetype of the target is also identical - if (ComponentArchetypeInstance->GetArchetype() != TargetComponent) - { - UActorComponent* CheckComponent = CastChecked(ComponentArchetypeInstance->GetArchetype()); - while (CheckComponent != ComponentArchetypeInstance) - { - if (!Property->Identical_InContainer( CheckComponent, TargetComponent )) - { - bAdd = false; - break; - } - CheckComponent = CastChecked(CheckComponent->GetArchetype()); - } - } - - if (bAdd) - { - ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance ); - } - } - } - } - - EditorUtilities::CopySingleProperty(SourceComponent, TargetComponent, Property); - - if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) - { - FPropertyChangedEvent PropertyChangedEvent( Property ); - TargetComponent->PostEditChangeProperty( PropertyChangedEvent ); - } - - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex ) - { - UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex]; - if( ComponentArchetypeInstance != nullptr ) - { - if( !ModifiedObjects.Contains(ComponentArchetypeInstance) ) - { - // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. - // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. - if (!ComponentArchetypeInstance->IsCreatedByConstructionScript()) - { - ComponentArchetypeInstance->SetFlags(RF_Transactional); - ComponentArchetypeInstance->Modify(); - ModifiedObjects.Add(ComponentArchetypeInstance); - } - - // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. - AActor* Owner = ComponentArchetypeInstance->GetOwner(); - if( Owner != nullptr && !ModifiedObjects.Contains(Owner)) - { - Owner->Modify(); - ModifiedObjects.Add(Owner); - } - } - - if (ComponentArchetypeInstance->IsRegistered()) - { - ComponentArchetypeInstance->UnregisterComponent(); - ComponentInstancesToReregister.Add(ComponentArchetypeInstance); - } - - EditorUtilities::CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property ); - } - } - } - } - - ++CopiedPropertyCount; - - if( bIsTransform ) - { - bTransformChanged = true; - } - } - } - } - - for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister) - { - ModifiedComponentInstance->RegisterComponent(); - } - - return CopiedPropertyCount; -} -#endif - - -#if WITH_EDITOR -FBlueprintEditor* -FHoudiniEngineRuntimeUtils::GetBlueprintEditor(const UObject* InObject) -{ - if (!IsValid(InObject)) - return nullptr; - - UObject* Outer = InObject->GetOuter(); - if (!IsValid(Outer)) - return nullptr; - - UBlueprintGeneratedClass* OuterBPClass = Cast(Outer->GetClass()); - - if (!OuterBPClass) - return nullptr; - - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - return static_cast(AssetEditorSubsystem->FindEditorForAsset(OuterBPClass->ClassGeneratedBy, false)); -} -#endif - - -#if WITH_EDITOR -void -FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate) -{ - if (!ComponentTemplate) - return; - - UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); - if (!BPGC) - return; - - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!Blueprint) - return; - - Blueprint->Modify(); - - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(Blueprint, false)); - check(BlueprintEditor); - - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - TSharedPtr SCSEditor = nullptr; - - SCSEditor = BlueprintEditor->GetSCSEditor(); - check(SCSEditor); - SCSEditor->SaveSCSCurrentState(SCS); - - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); - - SCSEditor->UpdateTree(true); -} -#endif - -#if WITH_EDITOR -void -FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTemplate) -{ - if (!ComponentTemplate) - return; - - UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); - if (!BPGC) - return; - - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!Blueprint) - return; - - Blueprint->Modify(); - - FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); -} -#endif - - -#if WITH_EDITOR - -// Centralized call to set actor label (changing Actor's implementation was too risky) -bool FHoudiniEngineRuntimeUtils::SetActorLabel(AActor* Actor, const FString& ActorLabel) -{ - // Clean up the incoming string a bit - FString NewActorLabel = ActorLabel.TrimStartAndEnd(); - if (NewActorLabel == Actor->GetActorLabel()) - { - return false; - } - Actor->SetActorLabel(NewActorLabel); - return true; -} - -void -FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) -{ - FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); - Obj->PostEditChangeProperty(Evt); -} - -void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FProperty* Property) -{ - FPropertyChangedEvent Evt(Property); - Obj->PostEditChangeProperty(Evt); -} - -void FHoudiniEngineRuntimeUtils::PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange) -{ - if (!InObject) - return; - if (!InObject->HasAnyFlags(RF_ArchetypeObject)) - return; - - // Iterate over the modified properties and propagate value changed to all archetype instances - TArray ArchetypeInstances; - InObject->GetArchetypeInstances(ArchetypeInstances); - for (UObject* Instance : ArchetypeInstances) - { - UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Found Archetype instance: %s"), *(Instance->GetPathName())); - for (FName PropertyName : DeltaChange.ChangedProperties) - { - UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Changed property: %s"), *(PropertyName.ToString())); - // FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation()); - } - } -} - -void FHoudiniEngineRuntimeUtils::ForAllArchetypeInstances(UObject* InTemplateObj, TFunctionRef Operation) -{ - if (!InTemplateObj) - return; - if (!InTemplateObj->HasAnyFlags(RF_ArchetypeObject|RF_DefaultSubObject)) - return; - - TArray Instances; - InTemplateObj->GetArchetypeInstances(Instances); - - for(UObject* Instance : Instances) - { - Operation(Instance); - } -} -#endif - -FHoudiniStaticMeshGenerationProperties -FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties() -{ - FHoudiniStaticMeshGenerationProperties SMGP; - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - { - SMGP.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; - SMGP.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; - SMGP.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; - SMGP.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; - SMGP.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; - SMGP.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; - SMGP.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; - SMGP.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; - SMGP.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; - SMGP.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; - SMGP.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; - SMGP.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; - } - - return SMGP; -} - -FMeshBuildSettings -FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings() -{ - FMeshBuildSettings DefaultBuildSettings; - - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - if(HoudiniRuntimeSettings) - { - DefaultBuildSettings.bRemoveDegenerates = HoudiniRuntimeSettings->bRemoveDegenerates; - DefaultBuildSettings.bUseMikkTSpace = HoudiniRuntimeSettings->bUseMikkTSpace; - DefaultBuildSettings.bBuildAdjacencyBuffer = HoudiniRuntimeSettings->bBuildAdjacencyBuffer; - DefaultBuildSettings.MinLightmapResolution = HoudiniRuntimeSettings->MinLightmapResolution; - DefaultBuildSettings.bUseFullPrecisionUVs = HoudiniRuntimeSettings->bUseFullPrecisionUVs; - DefaultBuildSettings.SrcLightmapIndex = HoudiniRuntimeSettings->SrcLightmapIndex; - DefaultBuildSettings.DstLightmapIndex = HoudiniRuntimeSettings->DstLightmapIndex; - - DefaultBuildSettings.bComputeWeightedNormals = HoudiniRuntimeSettings->bComputeWeightedNormals; - DefaultBuildSettings.bBuildReversedIndexBuffer = HoudiniRuntimeSettings->bBuildReversedIndexBuffer; - DefaultBuildSettings.bUseHighPrecisionTangentBasis = HoudiniRuntimeSettings->bUseHighPrecisionTangentBasis; - DefaultBuildSettings.bGenerateDistanceFieldAsIfTwoSided = HoudiniRuntimeSettings->bGenerateDistanceFieldAsIfTwoSided; - DefaultBuildSettings.bSupportFaceRemap = HoudiniRuntimeSettings->bSupportFaceRemap; - DefaultBuildSettings.DistanceFieldResolutionScale = HoudiniRuntimeSettings->DistanceFieldResolutionScale; - - // Recomputing normals. - EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag; - switch (RecomputeNormalFlag) - { - case HRSRF_Never: - { - DefaultBuildSettings.bRecomputeNormals = false; - break; - } - - case HRSRF_Always: - case HRSRF_OnlyIfMissing: - default: - { - DefaultBuildSettings.bRecomputeNormals = true; - break; - } - } - - // Recomputing tangents. - EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; - switch (RecomputeTangentFlag) - { - case HRSRF_Never: - { - DefaultBuildSettings.bRecomputeTangents = false; - break; - } - - case HRSRF_Always: - case HRSRF_OnlyIfMissing: - default: - { - DefaultBuildSettings.bRecomputeTangents = true; - break; - } - } - - // Lightmap UV generation. - EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; - switch (GenerateLightmapUVFlag) - { - case HRSRF_Never: - { - DefaultBuildSettings.bGenerateLightmapUVs = false; - break; - } - - case HRSRF_Always: - case HRSRF_OnlyIfMissing: - default: - { - DefaultBuildSettings.bGenerateLightmapUVs = true; - break; - } - } - } - - return DefaultBuildSettings; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" + +#include "EngineUtils.h" +#include "Engine/EngineTypes.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Kismet2/BlueprintEditorUtils.h" +#endif + + +FString +FHoudiniEngineRuntimeUtils::GetLibHAPIName() +{ + static const FString LibHAPIName = + +#if PLATFORM_WINDOWS + HAPI_LIB_OBJECT_WINDOWS; +#elif PLATFORM_MAC + HAPI_LIB_OBJECT_MAC; +#elif PLATFORM_LINUX + HAPI_LIB_OBJECT_LINUX; +#else + TEXT(""); +#endif + + return LibHAPIName; +} + + +void +FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes) +{ + OutBBoxes.Empty(); + + for (auto CurrentActor : InActors) + { + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true)); + } +} + + +bool +FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors) +{ + if (!IsValid(World)) + return false; + + OutActors.Empty(); + for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) + { + AActor* CurrentActor = *ActorItr; + if (!IsValid(CurrentActor)) + continue; + + if (!CurrentActor->GetClass()->IsChildOf(ActorType.Get())) + continue; + + if (ExcludeActors && ExcludeActors->Contains(CurrentActor)) + continue; + + // Special case + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); + for (auto InBounds : BBoxes) + { + // Check if both actor's bounds intersects + if (!ActorBounds.Intersect(InBounds)) + continue; + + OutActors.Add(CurrentActor); + break; + } + } + + return true; +} + +bool +FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly) +{ + bool bDeleted = false; + OutPackage = nullptr; + bOutPackageIsInMemoryOnly = false; + + if (!IsValid(InObjectToDelete)) + return false; + + // Don't try to delete the object if it has references (we do this here to avoid the FMessageDialog in DeleteSingleObject + bool bIsReferenced = false; + bool bIsReferencedByUndo = false; + if (!GatherObjectReferencersForDeletion(InObjectToDelete, bIsReferenced, bIsReferencedByUndo)) + return false; + + if (bIsReferenced) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject] Not deleting %s: there are still references to it."), *InObjectToDelete->GetFullName()); + } + else + { + // Even though we already checked for references, we still let DeleteSingleObject check for references, since + // we want that code path where it'll clean up in-memory references (undo buffer/transactions) + const bool bCheckForReferences = true; + if (DeleteSingleObject(InObjectToDelete, bCheckForReferences)) + { + bDeleted = true; + + OutPackage = InObjectToDelete->GetOutermost(); + + FString PackageFilename; + if (!IsValid(OutPackage) || !FPackageName::DoesPackageExist(OutPackage->GetName(), nullptr, &PackageFilename)) + { + // Package is in memory only, we don't have call CleanUpAfterSuccessfulDelete on it, just do garbage + // collection to pick up the stale package + bOutPackageIsInMemoryOnly = true; + } + else + { + // There is an on-disk package that is now potentially empty, CleanUpAfterSuccessfulDelete must be + // called on this. Since CleanUpAfterSuccessfulDelete does garbage collection, we return the Package + // as part of this function so that the caller can collect all Packages and do one call to + // CleanUpAfterSuccessfulDelete with an array + } + } + } + + return bDeleted; +} + +int32 +FHoudiniEngineRuntimeUtils::SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted) +{ + int32 NumDeleted = 0; + bool bGarbageCollectionRequired = false; + TSet PackagesToCleanUp; + TSet ProcessedObjects; + while (InObjectsToDelete.Num() > 0) + { + UObject* const ObjectToDelete = InObjectsToDelete.Pop(); + + if (ProcessedObjects.Contains(ObjectToDelete)) + continue; + + ProcessedObjects.Add(ObjectToDelete); + + if (!IsValid(ObjectToDelete)) + continue; + + UPackage* Package = nullptr; + bool bInMemoryPackageOnly = false; + if (SafeDeleteSingleObject(ObjectToDelete, Package, bInMemoryPackageOnly)) + { + NumDeleted++; + if (bInMemoryPackageOnly) + { + // Packages that are in-memory only are cleaned up by garbage collection + if (!bGarbageCollectionRequired) + bGarbageCollectionRequired = true; + } + else + { + // Clean up potentially empty packages in one call to CleanupAfterSuccessfulDelete at the end + PackagesToCleanUp.Add(Package); + } + } + else if (OutObjectsNotDeleted) + { + OutObjectsNotDeleted->Add(ObjectToDelete); + } + } + + // CleanupAfterSuccessfulDelete calls CollectGarbage, so don't call it here if we have PackagesToCleanUp + if (bGarbageCollectionRequired && PackagesToCleanUp.Num() <= 0) + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + if (PackagesToCleanUp.Num() > 0) + CleanupAfterSuccessfulDelete(PackagesToCleanUp.Array()); + + return NumDeleted; +} + + +#if WITH_EDITOR +int32 +FHoudiniEngineRuntimeUtils::CopyComponentProperties(UActorComponent* SourceComponent, UActorComponent* TargetComponent, const EditorUtilities::FCopyOptions& Options) +{ + UClass* ComponentClass = SourceComponent->GetClass(); + check( ComponentClass == TargetComponent->GetClass() ); + + const bool bIsPreviewing = ( Options.Flags & EditorUtilities::ECopyOptions::PreviewOnly ) != 0; + int32 CopiedPropertyCount = 0; + bool bTransformChanged = false; + + // Build a list of matching component archetype instances for propagation (if requested) + TArray ComponentArchetypeInstances; + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + TArray Instances; + TargetComponent->GetArchetypeInstances(Instances); + for(UObject* ObjInstance : Instances) + { + UActorComponent* ComponentInstance = Cast(ObjInstance); + if (ComponentInstance && ComponentInstance != SourceComponent && ComponentInstance != TargetComponent) + { + ComponentArchetypeInstances.Add(ComponentInstance); + } + } + } + + TSet SourceUCSModifiedProperties; + SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties); + + TArray ComponentInstancesToReregister; + + // Copy component properties + for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) + { + const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); + const bool bIsIdentical = Property->Identical_InContainer( SourceComponent, TargetComponent ); + const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); + const bool bIsTransform = + Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); + + // auto SourceComponentIsRoot = [&]() + // { + // USceneComponent* RootComponent = SourceActor->GetRootComponent(); + // if (SourceComponent == RootComponent) + // { + // return true; + // } + // else if (RootComponent == nullptr && bSourceActorIsBPCDO) + // { + // // If we're dealing with a BP CDO as source, then look at the target for whether this is the root component + // return (TargetComponent == TargetActor->GetRootComponent()); + // } + // return false; + // }; + + TSet ModifiedObjects; + // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + // && ( !bIsTransform || (!bSourceActorIsCDO && !bTargetActorIsCDO) || !SourceComponentIsRoot() ) ) + if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + && ( !bIsTransform )) + { + const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) + && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); + if( bIsSafeToCopy ) + { + // if (!Options.CanCopyProperty(*Property, *SourceActor)) + // { + // continue; + // } + if (!Options.CanCopyProperty(*Property, *SourceComponent)) + { + continue; + } + + if( !bIsPreviewing ) + { + if( !ModifiedObjects.Contains(TargetComponent) ) + { + TargetComponent->SetFlags(RF_Transactional); + TargetComponent->Modify(); + ModifiedObjects.Add(TargetComponent); + } + + if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) + { + TargetComponent->PreEditChange( Property ); + } + + // Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances. + TArray ComponentArchetypeInstancesToChange; + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances) + { + if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) ) + { + bool bAdd = true; + // We also need to double check that either the direct archetype of the target is also identical + if (ComponentArchetypeInstance->GetArchetype() != TargetComponent) + { + UActorComponent* CheckComponent = CastChecked(ComponentArchetypeInstance->GetArchetype()); + while (CheckComponent != ComponentArchetypeInstance) + { + if (!Property->Identical_InContainer( CheckComponent, TargetComponent )) + { + bAdd = false; + break; + } + CheckComponent = CastChecked(CheckComponent->GetArchetype()); + } + } + + if (bAdd) + { + ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance ); + } + } + } + } + + EditorUtilities::CopySingleProperty(SourceComponent, TargetComponent, Property); + + if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) + { + FPropertyChangedEvent PropertyChangedEvent( Property ); + TargetComponent->PostEditChangeProperty( PropertyChangedEvent ); + } + + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex ) + { + UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex]; + if( ComponentArchetypeInstance != nullptr ) + { + if( !ModifiedObjects.Contains(ComponentArchetypeInstance) ) + { + // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. + // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. + if (!ComponentArchetypeInstance->IsCreatedByConstructionScript()) + { + ComponentArchetypeInstance->SetFlags(RF_Transactional); + ComponentArchetypeInstance->Modify(); + ModifiedObjects.Add(ComponentArchetypeInstance); + } + + // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. + AActor* Owner = ComponentArchetypeInstance->GetOwner(); + if( Owner != nullptr && !ModifiedObjects.Contains(Owner)) + { + Owner->Modify(); + ModifiedObjects.Add(Owner); + } + } + + if (ComponentArchetypeInstance->IsRegistered()) + { + ComponentArchetypeInstance->UnregisterComponent(); + ComponentInstancesToReregister.Add(ComponentArchetypeInstance); + } + + EditorUtilities::CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property ); + } + } + } + } + + ++CopiedPropertyCount; + + if( bIsTransform ) + { + bTransformChanged = true; + } + } + } + } + + for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister) + { + ModifiedComponentInstance->RegisterComponent(); + } + + return CopiedPropertyCount; +} +#endif + + +#if WITH_EDITOR +FBlueprintEditor* +FHoudiniEngineRuntimeUtils::GetBlueprintEditor(const UObject* InObject) +{ + if (!IsValid(InObject)) + return nullptr; + + UObject* Outer = InObject->GetOuter(); + if (!IsValid(Outer)) + return nullptr; + + UBlueprintGeneratedClass* OuterBPClass = Cast(Outer->GetClass()); + + if (!OuterBPClass) + return nullptr; + + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + return static_cast(AssetEditorSubsystem->FindEditorForAsset(OuterBPClass->ClassGeneratedBy, false)); +} +#endif + + +#if WITH_EDITOR +void +FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate) +{ + if (!ComponentTemplate) + return; + + UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); + if (!BPGC) + return; + + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!Blueprint) + return; + + Blueprint->Modify(); + + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(Blueprint, false)); + check(BlueprintEditor); + + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + TSharedPtr SCSEditor = nullptr; + + SCSEditor = BlueprintEditor->GetSCSEditor(); + check(SCSEditor); + SCSEditor->SaveSCSCurrentState(SCS); + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + + SCSEditor->UpdateTree(true); +} +#endif + +#if WITH_EDITOR +void +FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTemplate) +{ + if (!ComponentTemplate) + return; + + UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); + if (!BPGC) + return; + + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!Blueprint) + return; + + Blueprint->Modify(); + + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); +} +#endif + + +#if WITH_EDITOR + +// Centralized call to set actor label (changing Actor's implementation was too risky) +bool FHoudiniEngineRuntimeUtils::SetActorLabel(AActor* Actor, const FString& ActorLabel) +{ + // Clean up the incoming string a bit + FString NewActorLabel = ActorLabel.TrimStartAndEnd(); + if (NewActorLabel == Actor->GetActorLabel()) + { + return false; + } + Actor->SetActorLabel(NewActorLabel); + return true; +} + +void +FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) +{ + FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); + Obj->PostEditChangeProperty(Evt); +} + +void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FProperty* Property) +{ + FPropertyChangedEvent Evt(Property); + Obj->PostEditChangeProperty(Evt); +} + +void FHoudiniEngineRuntimeUtils::PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange) +{ + if (!InObject) + return; + if (!InObject->HasAnyFlags(RF_ArchetypeObject)) + return; + + // Iterate over the modified properties and propagate value changed to all archetype instances + TArray ArchetypeInstances; + InObject->GetArchetypeInstances(ArchetypeInstances); + for (UObject* Instance : ArchetypeInstances) + { + UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Found Archetype instance: %s"), *(Instance->GetPathName())); + for (FName PropertyName : DeltaChange.ChangedProperties) + { + UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Changed property: %s"), *(PropertyName.ToString())); + // FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation()); + } + } +} + +void FHoudiniEngineRuntimeUtils::ForAllArchetypeInstances(UObject* InTemplateObj, TFunctionRef Operation) +{ + if (!InTemplateObj) + return; + if (!InTemplateObj->HasAnyFlags(RF_ArchetypeObject|RF_DefaultSubObject)) + return; + + TArray Instances; + InTemplateObj->GetArchetypeInstances(Instances); + + for(UObject* Instance : Instances) + { + Operation(Instance); + } +} +#endif + +FHoudiniStaticMeshGenerationProperties +FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties() +{ + FHoudiniStaticMeshGenerationProperties SMGP; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + SMGP.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; + SMGP.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; + SMGP.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; + SMGP.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; + SMGP.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; + SMGP.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + SMGP.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; + SMGP.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; + SMGP.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; + SMGP.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; + SMGP.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; + SMGP.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; + } + + return SMGP; +} + +FMeshBuildSettings +FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings() +{ + FMeshBuildSettings DefaultBuildSettings; + + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + if(HoudiniRuntimeSettings) + { + DefaultBuildSettings.bRemoveDegenerates = HoudiniRuntimeSettings->bRemoveDegenerates; + DefaultBuildSettings.bUseMikkTSpace = HoudiniRuntimeSettings->bUseMikkTSpace; + DefaultBuildSettings.bBuildAdjacencyBuffer = HoudiniRuntimeSettings->bBuildAdjacencyBuffer; + DefaultBuildSettings.MinLightmapResolution = HoudiniRuntimeSettings->MinLightmapResolution; + DefaultBuildSettings.bUseFullPrecisionUVs = HoudiniRuntimeSettings->bUseFullPrecisionUVs; + DefaultBuildSettings.SrcLightmapIndex = HoudiniRuntimeSettings->SrcLightmapIndex; + DefaultBuildSettings.DstLightmapIndex = HoudiniRuntimeSettings->DstLightmapIndex; + + DefaultBuildSettings.bComputeWeightedNormals = HoudiniRuntimeSettings->bComputeWeightedNormals; + DefaultBuildSettings.bBuildReversedIndexBuffer = HoudiniRuntimeSettings->bBuildReversedIndexBuffer; + DefaultBuildSettings.bUseHighPrecisionTangentBasis = HoudiniRuntimeSettings->bUseHighPrecisionTangentBasis; + DefaultBuildSettings.bGenerateDistanceFieldAsIfTwoSided = HoudiniRuntimeSettings->bGenerateDistanceFieldAsIfTwoSided; + DefaultBuildSettings.bSupportFaceRemap = HoudiniRuntimeSettings->bSupportFaceRemap; + DefaultBuildSettings.DistanceFieldResolutionScale = HoudiniRuntimeSettings->DistanceFieldResolutionScale; + + // Recomputing normals. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag; + switch (RecomputeNormalFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bRecomputeNormals = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bRecomputeNormals = true; + break; + } + } + + // Recomputing tangents. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; + switch (RecomputeTangentFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bRecomputeTangents = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bRecomputeTangents = true; + break; + } + } + + // Lightmap UV generation. + EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; + switch (GenerateLightmapUVFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bGenerateLightmapUVs = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bGenerateLightmapUVs = true; + break; + } + } + } + + return DefaultBuildSettings; +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h index b2569d2e3..ffa4c1063 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h @@ -1,356 +1,356 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/ObjectMacros.h" -#include "UObject/UObjectGlobals.h" -#include "UObject/Class.h" - -#if WITH_EDITOR - #include "SSCSEditor.h" - #include "ObjectTools.h" - #include "Kismet2/ComponentEditorUtils.h" - #include "Editor/Transactor.h" -#endif - -class AActor; -class UWorld; -struct FHoudiniStaticMeshGenerationProperties; -struct FMeshBuildSettings; - -template -class TSubclassOf; - -struct FBox; - - -struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils -{ - public: - - // Return platform specific name of libHAPI. - static FString GetLibHAPIName(); - - // Returns default SM Generation Properties using the default settings values - static FHoudiniStaticMeshGenerationProperties GetDefaultStaticMeshGenerationProperties(); - - // Returns default SM Build Settings using the default settings values - static FMeshBuildSettings GetDefaultMeshBuildSettings(); - - // ----------------------------------------------- - // Bounding Box utilities - // ----------------------------------------------- - - // Collect all the bounding boxes form the specified list of actors. OutBBoxes will be emptied. - static void GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes); - - // Collect actors that derive from the given class that intersect with the given array of bounding boxes. - static bool FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors); - - // ----------------------------------------------- - // File path utilities - // ----------------------------------------------- - - // Joins paths by taking into account whether paths - // successive paths are relative or absolute. - // Truncate everything preceding an absolute path. - // Taken and adapted from FPaths::Combine(). - template - FORCEINLINE static FString JoinPaths(PathTypes&&... InPaths) - { - const TCHAR* Paths[] = { GetTCharPtr(Forward(InPaths))... }; - const int32 NumPaths = UE_ARRAY_COUNT(Paths); - - FString Out = TEXT(""); - if (NumPaths <= 0) - return Out; - Out = Paths[NumPaths-1]; - // Process paths in reverse and terminate when we reach an absolute path. - for (int32 i=NumPaths-2; i >= 0; --i) - { - if (FCString::Strlen(Paths[i]) == 0) - continue; - if (Out[0] == '/') - { - // We already have an absolute path. Terminate. - break; - } - Out = Paths[i] / Out; - } - if (Out.Len() > 0 && Out[0] != '/') - Out = TEXT("/") + Out; - return Out; - } - - // ------------------------------------------------------------------ - // ObjectTools (Make some editor only ObjectTools functions available - // in editor builds) - // ------------------------------------------------------------------ - - // Check/gather references to InObject. - // Returns true if the function could execute (editor vs runtime) - FORCEINLINE static bool GatherObjectReferencersForDeletion(UObject* InObject, bool& bOutIsReferenced, bool& bOutIsReferencedByUndo, FReferencerInformationList* OutMemoryReferences = nullptr, bool bInRequireReferencingProperties = false) - { -#if WITH_EDITOR - ObjectTools::GatherObjectReferencersForDeletion(InObject, bOutIsReferenced, bOutIsReferencedByUndo, OutMemoryReferences, bInRequireReferencingProperties); - - return true; -#else - return false; -#endif - } - - // Delete a single object from its package. Returns true if the object was deleted. In non-editor - // builds this function returns false. - FORCEINLINE static bool DeleteSingleObject(UObject* InObjectToDelete, bool bInPerformReferenceCheck=true) - { -#if WITH_EDITOR - return ObjectTools::DeleteSingleObject(InObjectToDelete, bInPerformReferenceCheck); -#else - return false; -#endif - } - - // Collects garbage and marks truely empty packages for delete - // Returns true if the function could execute (editor vs runtime) - FORCEINLINE static bool CleanupAfterSuccessfulDelete(const TArray& InObjectsDeletedSuccessfully, bool bInPerformReferenceCheck=true) - { -#if WITH_EDITOR - ObjectTools::CleanupAfterSuccessfulDelete(InObjectsDeletedSuccessfully, bInPerformReferenceCheck); - return true; -#else - return false; -#endif - } - - // Deletes a single object. Returns true if the object was deleted. - // The object is only deleted if there are no references to it. - // If the package is on disk then bOutPackageIsInMemoryOnly is false and the CleanUpAfterSuccessfulDelete - // must be called on the package after execution of this function. - static bool SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly); - - // Deletes and cleans up on disk empty-packages for the objects in InObjectsToDelete. - // Objects are popped from InObjectsToDelete as they are processed (ran into cases where objects are garbage collected - // before we can properly delete them and cleanup their packages, so we tend to pass in a UPROPERTY based TArray - // that holds references to UObject to prevent garbage collection until we can delete them in this function) - // OutObjectsNotDeleted can optionally be used to return objects that could not be deleted. - // The function returns the number of objects that were deleted. - static int32 SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted=nullptr); - - // ------------------------------------------------- - // Type utilities - // ------------------------------------------------- - - // Taken from here: https://answers.unrealengine.com/questions/330496/conversion-of-enum-to-string.html - // Return the string representation of an enum value. - template - static FString EnumToString(const FString& EnumName, const T Value) - { - UEnum* Enum = FindObject(ANY_PACKAGE, *EnumName); - return *(Enum ? Enum->GetNameStringByValue(static_cast(Value)) : "null"); - } - - template - static FString EnumToString(const T Value) - { - return UEnum::GetValueAsString(Value); - } - - // ------------------------------------------------- - // Blueprint utilities - // ------------------------------------------------- -#if WITH_EDITOR - // This function contains an excerpt from UEditorUtilities::CopyActorProperties() - // for specifically dealing with copying properties between components as well as propagating - // property changes to archetype instances. - static int32 CopyComponentProperties(UActorComponent* FromComponent, UActorComponent* ToComponent, const EditorUtilities::FCopyOptions& Options); - - // Get the SCSEditor for the given HoudiniAssetComponent - static FBlueprintEditor* GetBlueprintEditor(const UObject* InObject); - - static void MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate); - static void MarkBlueprintAsModified(UActorComponent* ComponentTemplate); -#endif - - // ------------------------------------------------- - // Editor Helpers - // ------------------------------------------------- -#if WITH_EDITOR - static bool SetActorLabel(AActor* Actor, const FString& ActorLabel); - - static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); - - static void DoPostEditChangeProperty(UObject* Obj, FProperty* Property); - - static void PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange); - - template - static TSet PropagateDefaultValueChange(USceneComponent* InSceneComponentTemplate, const FName& PropertyName, const T& OldValue, const T& NewValue) - { - TSet UpdatedInstances; - FComponentEditorUtils::PropagateDefaultValueChange(InSceneComponentTemplate, FindFieldChecked(InSceneComponentTemplate->GetClass(), PropertyName), OldValue, NewValue, UpdatedInstances); - return UpdatedInstances; - } - - // Perform this operation on the given archetype as well as each archetype instance. If the given - // object in not an archetype instance, then don't do anything. - static void ForAllArchetypeInstances(UObject* Archetype, TFunctionRef Operation); - -#endif - - /** - // * Set the value on an UObject using reflection. - // * @param Object The object to copy the value into. - // * @param PropertyName The name of the property to set. - // * @param Value The value to assign to the property. - // * - // * @return true if the value was set correctly - // */ - //template - //static bool SetPropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) - //{ - // // Get the property addresses for the source and destination objects. - // FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - - // // Get the property addresses for the object - // ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); - - // if ( SourceAddr == NULL ) - // { - // return false; - // } - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - // { - // FEditPropertyChain PropertyChain; - // PropertyChain.AddHead(Property); - // Object->PreEditChange(PropertyChain); - // } - - // // Set the value on the destination object. - // *SourceAddr = Value; - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - // { - // FPropertyChangedEvent PropertyEvent(Property); - // Object->PostEditChangeProperty(PropertyEvent); - // } - - // return true; - //} -#if WITH_EDITOR - template - static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) - { - // Get the property addresses for the source and destination objects. - FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - - // Get the property addresses for the object - ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); - - if ( SourceAddr == NULL ) - { - return false; - } - - if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FEditPropertyChain PropertyChain; - PropertyChain.AddHead(Property); - ((UObject*)Object)->PreEditChange(PropertyChain); - } - - // Set the value on the destination object. - if (*SourceAddr != Value) - { - TSet UpdatedInstances; - *SourceAddr = Value; - PropagateDefaultValueChange(Object, Property, *SourceAddr, Value, UpdatedInstances); - } - - if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FPropertyChangedEvent PropertyEvent(Property); - Object->PostEditChangeProperty(PropertyEvent); - } - - return true; - } -#endif - -#if WITH_EDITOR - // Bool specialization - template - static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, bool NewBool) - { - // Get the property addresses for the source and destination objects. - FBoolProperty* BoolProperty = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - check(BoolProperty); - - // Get the property addresses for the object - const int32 PropertyOffset = INDEX_NONE; - void* CurrentValue = PropertyOffset == INDEX_NONE ? BoolProperty->ContainerPtrToValuePtr(Object) : ((uint8*)Object + PropertyOffset); - check(CurrentValue); - - const bool CurrentBool = BoolProperty->GetPropertyValue(CurrentValue); - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FEditPropertyChain PropertyChain; - PropertyChain.AddHead(BoolProperty); - ((UObject*)Object)->PreEditChange(PropertyChain); - } - - // Set the value on the destination object. - if (CurrentBool != NewBool) - { - TSet UpdatedInstances; - BoolProperty->SetPropertyValue(CurrentValue, NewBool); - FComponentEditorUtils::PropagateDefaultValueChange(Object, BoolProperty, CurrentBool, NewBool, UpdatedInstances); - } - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FPropertyChangedEvent PropertyEvent(BoolProperty); - Object->PostEditChangeProperty(PropertyEvent); - } - - return true; - } -#endif - - protected: - // taken from FPaths::GetTCharPtr - FORCEINLINE static const TCHAR* GetTCharPtr(const TCHAR* Ptr) - { - return Ptr; - } - FORCEINLINE static const TCHAR* GetTCharPtr(const FString& Str) - { - return *Str; - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/ObjectMacros.h" +#include "UObject/UObjectGlobals.h" +#include "UObject/Class.h" + +#if WITH_EDITOR + #include "SSCSEditor.h" + #include "ObjectTools.h" + #include "Kismet2/ComponentEditorUtils.h" + #include "Editor/Transactor.h" +#endif + +class AActor; +class UWorld; +struct FHoudiniStaticMeshGenerationProperties; +struct FMeshBuildSettings; + +template +class TSubclassOf; + +struct FBox; + + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils +{ + public: + + // Return platform specific name of libHAPI. + static FString GetLibHAPIName(); + + // Returns default SM Generation Properties using the default settings values + static FHoudiniStaticMeshGenerationProperties GetDefaultStaticMeshGenerationProperties(); + + // Returns default SM Build Settings using the default settings values + static FMeshBuildSettings GetDefaultMeshBuildSettings(); + + // ----------------------------------------------- + // Bounding Box utilities + // ----------------------------------------------- + + // Collect all the bounding boxes form the specified list of actors. OutBBoxes will be emptied. + static void GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes); + + // Collect actors that derive from the given class that intersect with the given array of bounding boxes. + static bool FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors); + + // ----------------------------------------------- + // File path utilities + // ----------------------------------------------- + + // Joins paths by taking into account whether paths + // successive paths are relative or absolute. + // Truncate everything preceding an absolute path. + // Taken and adapted from FPaths::Combine(). + template + FORCEINLINE static FString JoinPaths(PathTypes&&... InPaths) + { + const TCHAR* Paths[] = { GetTCharPtr(Forward(InPaths))... }; + const int32 NumPaths = UE_ARRAY_COUNT(Paths); + + FString Out = TEXT(""); + if (NumPaths <= 0) + return Out; + Out = Paths[NumPaths-1]; + // Process paths in reverse and terminate when we reach an absolute path. + for (int32 i=NumPaths-2; i >= 0; --i) + { + if (FCString::Strlen(Paths[i]) == 0) + continue; + if (Out[0] == '/') + { + // We already have an absolute path. Terminate. + break; + } + Out = Paths[i] / Out; + } + if (Out.Len() > 0 && Out[0] != '/') + Out = TEXT("/") + Out; + return Out; + } + + // ------------------------------------------------------------------ + // ObjectTools (Make some editor only ObjectTools functions available + // in editor builds) + // ------------------------------------------------------------------ + + // Check/gather references to InObject. + // Returns true if the function could execute (editor vs runtime) + FORCEINLINE static bool GatherObjectReferencersForDeletion(UObject* InObject, bool& bOutIsReferenced, bool& bOutIsReferencedByUndo, FReferencerInformationList* OutMemoryReferences = nullptr, bool bInRequireReferencingProperties = false) + { +#if WITH_EDITOR + ObjectTools::GatherObjectReferencersForDeletion(InObject, bOutIsReferenced, bOutIsReferencedByUndo, OutMemoryReferences, bInRequireReferencingProperties); + + return true; +#else + return false; +#endif + } + + // Delete a single object from its package. Returns true if the object was deleted. In non-editor + // builds this function returns false. + FORCEINLINE static bool DeleteSingleObject(UObject* InObjectToDelete, bool bInPerformReferenceCheck=true) + { +#if WITH_EDITOR + return ObjectTools::DeleteSingleObject(InObjectToDelete, bInPerformReferenceCheck); +#else + return false; +#endif + } + + // Collects garbage and marks truely empty packages for delete + // Returns true if the function could execute (editor vs runtime) + FORCEINLINE static bool CleanupAfterSuccessfulDelete(const TArray& InObjectsDeletedSuccessfully, bool bInPerformReferenceCheck=true) + { +#if WITH_EDITOR + ObjectTools::CleanupAfterSuccessfulDelete(InObjectsDeletedSuccessfully, bInPerformReferenceCheck); + return true; +#else + return false; +#endif + } + + // Deletes a single object. Returns true if the object was deleted. + // The object is only deleted if there are no references to it. + // If the package is on disk then bOutPackageIsInMemoryOnly is false and the CleanUpAfterSuccessfulDelete + // must be called on the package after execution of this function. + static bool SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly); + + // Deletes and cleans up on disk empty-packages for the objects in InObjectsToDelete. + // Objects are popped from InObjectsToDelete as they are processed (ran into cases where objects are garbage collected + // before we can properly delete them and cleanup their packages, so we tend to pass in a UPROPERTY based TArray + // that holds references to UObject to prevent garbage collection until we can delete them in this function) + // OutObjectsNotDeleted can optionally be used to return objects that could not be deleted. + // The function returns the number of objects that were deleted. + static int32 SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted=nullptr); + + // ------------------------------------------------- + // Type utilities + // ------------------------------------------------- + + // Taken from here: https://answers.unrealengine.com/questions/330496/conversion-of-enum-to-string.html + // Return the string representation of an enum value. + template + static FString EnumToString(const FString& EnumName, const T Value) + { + UEnum* Enum = FindObject(ANY_PACKAGE, *EnumName); + return *(Enum ? Enum->GetNameStringByValue(static_cast(Value)) : "null"); + } + + template + static FString EnumToString(const T Value) + { + return UEnum::GetValueAsString(Value); + } + + // ------------------------------------------------- + // Blueprint utilities + // ------------------------------------------------- +#if WITH_EDITOR + // This function contains an excerpt from UEditorUtilities::CopyActorProperties() + // for specifically dealing with copying properties between components as well as propagating + // property changes to archetype instances. + static int32 CopyComponentProperties(UActorComponent* FromComponent, UActorComponent* ToComponent, const EditorUtilities::FCopyOptions& Options); + + // Get the SCSEditor for the given HoudiniAssetComponent + static FBlueprintEditor* GetBlueprintEditor(const UObject* InObject); + + static void MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate); + static void MarkBlueprintAsModified(UActorComponent* ComponentTemplate); +#endif + + // ------------------------------------------------- + // Editor Helpers + // ------------------------------------------------- +#if WITH_EDITOR + static bool SetActorLabel(AActor* Actor, const FString& ActorLabel); + + static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); + + static void DoPostEditChangeProperty(UObject* Obj, FProperty* Property); + + static void PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange); + + template + static TSet PropagateDefaultValueChange(USceneComponent* InSceneComponentTemplate, const FName& PropertyName, const T& OldValue, const T& NewValue) + { + TSet UpdatedInstances; + FComponentEditorUtils::PropagateDefaultValueChange(InSceneComponentTemplate, FindFieldChecked(InSceneComponentTemplate->GetClass(), PropertyName), OldValue, NewValue, UpdatedInstances); + return UpdatedInstances; + } + + // Perform this operation on the given archetype as well as each archetype instance. If the given + // object in not an archetype instance, then don't do anything. + static void ForAllArchetypeInstances(UObject* Archetype, TFunctionRef Operation); + +#endif + + /** + // * Set the value on an UObject using reflection. + // * @param Object The object to copy the value into. + // * @param PropertyName The name of the property to set. + // * @param Value The value to assign to the property. + // * + // * @return true if the value was set correctly + // */ + //template + //static bool SetPropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) + //{ + // // Get the property addresses for the source and destination objects. + // FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + + // // Get the property addresses for the object + // ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); + + // if ( SourceAddr == NULL ) + // { + // return false; + // } + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + // { + // FEditPropertyChain PropertyChain; + // PropertyChain.AddHead(Property); + // Object->PreEditChange(PropertyChain); + // } + + // // Set the value on the destination object. + // *SourceAddr = Value; + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + // { + // FPropertyChangedEvent PropertyEvent(Property); + // Object->PostEditChangeProperty(PropertyEvent); + // } + + // return true; + //} +#if WITH_EDITOR + template + static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) + { + // Get the property addresses for the source and destination objects. + FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + + // Get the property addresses for the object + ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); + + if ( SourceAddr == NULL ) + { + return false; + } + + if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FEditPropertyChain PropertyChain; + PropertyChain.AddHead(Property); + ((UObject*)Object)->PreEditChange(PropertyChain); + } + + // Set the value on the destination object. + if (*SourceAddr != Value) + { + TSet UpdatedInstances; + *SourceAddr = Value; + PropagateDefaultValueChange(Object, Property, *SourceAddr, Value, UpdatedInstances); + } + + if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FPropertyChangedEvent PropertyEvent(Property); + Object->PostEditChangeProperty(PropertyEvent); + } + + return true; + } +#endif + +#if WITH_EDITOR + // Bool specialization + template + static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, bool NewBool) + { + // Get the property addresses for the source and destination objects. + FBoolProperty* BoolProperty = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + check(BoolProperty); + + // Get the property addresses for the object + const int32 PropertyOffset = INDEX_NONE; + void* CurrentValue = PropertyOffset == INDEX_NONE ? BoolProperty->ContainerPtrToValuePtr(Object) : ((uint8*)Object + PropertyOffset); + check(CurrentValue); + + const bool CurrentBool = BoolProperty->GetPropertyValue(CurrentValue); + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FEditPropertyChain PropertyChain; + PropertyChain.AddHead(BoolProperty); + ((UObject*)Object)->PreEditChange(PropertyChain); + } + + // Set the value on the destination object. + if (CurrentBool != NewBool) + { + TSet UpdatedInstances; + BoolProperty->SetPropertyValue(CurrentValue, NewBool); + FComponentEditorUtils::PropagateDefaultValueChange(Object, BoolProperty, CurrentBool, NewBool, UpdatedInstances); + } + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FPropertyChangedEvent PropertyEvent(BoolProperty); + Object->PostEditChangeProperty(PropertyEvent); + } + + return true; + } +#endif + + protected: + // taken from FPaths::GetTCharPtr + FORCEINLINE static const TCHAR* GetTCharPtr(const TCHAR* Ptr) + { + return Ptr; + } + FORCEINLINE static const TCHAR* GetTCharPtr(const FString& Str) + { + return *Str; + } }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp index af591214d..0d23dc46d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp @@ -1,1494 +1,1512 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGenericAttribute.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAssetComponent.h" - -#include "Engine/StaticMesh.h" -#include "Components/ActorComponent.h" -#include "Components/PrimitiveComponent.h" -#include "Components/StaticMeshComponent.h" - -#include "PhysicsEngine/BodySetup.h" -#include "EditorFramework/AssetImportData.h" -#include "AI/Navigation/NavCollisionBase.h" - -double -FHoudiniGenericAttribute::GetDoubleValue(int32 index) const -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return DoubleValues[index]; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return (double)IntValues[index]; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return FCString::Atod(*StringValues[index]); - } - - return 0.0f; -} - -void -FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 index) const -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetDoubleValue(index * AttributeTupleSize + n); -} - -int64 -FHoudiniGenericAttribute::GetIntValue(int32 index) const -{ - if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return IntValues[index]; - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return (int64)DoubleValues[index]; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return FCString::Atoi64(*StringValues[index]); - } - - return 0; -} - -void -FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) const -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetIntValue(index * AttributeTupleSize + n); -} - -FString -FHoudiniGenericAttribute::GetStringValue(int32 index) const -{ - if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return StringValues[index]; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return FString::FromInt((int32)IntValues[index]); - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return FString::SanitizeFloat(DoubleValues[index]); - } - - return FString(); -} - -void -FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 index) const -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetStringValue(index * AttributeTupleSize + n); -} - -bool -FHoudiniGenericAttribute::GetBoolValue(int32 index) const -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return DoubleValues[index] == 0.0 ? false : true; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return IntValues[index] == 0 ? false : true; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return StringValues[index].Equals(TEXT("true"), ESearchCase::IgnoreCase) ? true : false; - } - - return false; -} - -void -FHoudiniGenericAttribute::GetBoolTuple(TArray& TupleValues, int32 index) const -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetBoolValue(index * AttributeTupleSize + n); -} - -void* -FHoudiniGenericAttribute::GetData() -{ - if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.Num() > 0) - return StringValues.GetData(); - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.Num() > 0) - return IntValues.GetData(); - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.Num() > 0) - return DoubleValues.GetData(); - } - - return nullptr; -} - -void -FHoudiniGenericAttribute::SetDoubleValue(double InValue, int32 index) -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (!DoubleValues.IsValidIndex(index)) - DoubleValues.SetNum(index + 1); - DoubleValues[index] = InValue; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (!IntValues.IsValidIndex(index)) - IntValues.SetNum(index + 1); - IntValues[index] = InValue; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (!StringValues.IsValidIndex(index)) - StringValues.SetNum(index + 1); - StringValues[index] = FString::SanitizeFloat(InValue); - } -} - -void -FHoudiniGenericAttribute::SetDoubleTuple(const TArray& InTupleValues, int32 index) -{ - if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) - return; - - for (int32 n = 0; n < AttributeTupleSize; n++) - SetDoubleValue(InTupleValues[n], index * AttributeTupleSize + n); -} - -void -FHoudiniGenericAttribute::SetIntValue(int64 InValue, int32 index) -{ - if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (!IntValues.IsValidIndex(index)) - IntValues.SetNum(index + 1); - IntValues[index] = InValue; - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (!DoubleValues.IsValidIndex(index)) - DoubleValues.SetNum(index + 1); - DoubleValues[index] = InValue; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (!StringValues.IsValidIndex(index)) - StringValues.SetNum(index + 1); - StringValues[index] = FString::Printf(TEXT("%lld"), InValue); - } -} - -void -FHoudiniGenericAttribute::SetIntTuple(const TArray& InTupleValues, int32 index) -{ - if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) - return; - - for (int32 n = 0; n < AttributeTupleSize; n++) - SetIntValue(InTupleValues[n], index * AttributeTupleSize + n); -} - -void -FHoudiniGenericAttribute::SetStringValue(const FString& InValue, int32 index) -{ - if (AttributeType == EAttribStorageType::STRING) - { - if (!StringValues.IsValidIndex(index)) - StringValues.SetNum(index + 1); - StringValues[index] = InValue; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (!IntValues.IsValidIndex(index)) - IntValues.SetNum(index + 1); - IntValues[index] = FCString::Atoi64(*InValue); - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (!DoubleValues.IsValidIndex(index)) - DoubleValues.SetNum(index + 1); - DoubleValues[index] = FCString::Atod(*InValue); - } -} - -void -FHoudiniGenericAttribute::SetStringTuple(const TArray& InTupleValues, int32 index) -{ - if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) - return; - - for (int32 n = 0; n < AttributeTupleSize; n++) - SetStringValue(InTupleValues[n], index * AttributeTupleSize + n); -} - -void -FHoudiniGenericAttribute::SetBoolValue(bool InValue, int32 index) -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (!DoubleValues.IsValidIndex(index)) - DoubleValues.SetNum(index + 1); - DoubleValues[index] = InValue ? 1.0 : 0.0; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (!IntValues.IsValidIndex(index)) - IntValues.SetNum(index + 1); - IntValues[index] = InValue ? 1 : 0; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (!StringValues.IsValidIndex(index)) - StringValues.SetNum(index + 1); - StringValues[index] = InValue ? "true" : "false"; - } -} - -void -FHoudiniGenericAttribute::SetBoolTuple(const TArray& InTupleValues, int32 index) -{ - if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) - return; - - for (int32 n = 0; n < AttributeTupleSize; n++) - SetBoolValue(InTupleValues[n], index * AttributeTupleSize + n); -} - -bool -FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( - UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Get the Property name - const FString& PropertyName = InPropertyAttribute.AttributeName; - if (PropertyName.IsEmpty()) - return false; - - // Some Properties need to be handle and modified manually... - if (PropertyName == "CollisionProfileName") - { - UPrimitiveComponent* PC = Cast(InObject); - if (IsValid(PC)) - { - FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); - FName Value = FName(*StringValue); - PC->SetCollisionProfileName(Value); - - // Patch the StaticMeshGenerationProperties on the HAC - UHoudiniAssetComponent* HAC = Cast(InObject); - if (IsValid(HAC)) - { - HAC->StaticMeshGenerationProperties.DefaultBodyInstance.SetCollisionProfileName(Value); - } - - return true; - } - return false; - } - - if (PropertyName == "CollisionEnabled") - { - UPrimitiveComponent* PC = Cast(InObject); - if (PC && !PC->IsPendingKill()) - { - FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); - if (StringValue == "NoCollision") - { - PC->SetCollisionEnabled(ECollisionEnabled::NoCollision); - return true; - } - else if (StringValue == "QueryOnly") - { - PC->SetCollisionEnabled(ECollisionEnabled::QueryOnly); - return true; - } - else if (StringValue == "PhysicsOnly") - { - PC->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); - return true; - } - else if (StringValue == "QueryAndPhysics") - { - PC->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); - return true; - } - return false; - } - } - - // Specialize CastShadow to avoid paying the cost of finding property + calling Property change twice - if (PropertyName == "CastShadow") - { - UPrimitiveComponent* Component = Cast< UPrimitiveComponent >(InObject); - if (Component && !Component->IsPendingKill()) - { - bool Value = InPropertyAttribute.GetBoolValue(AtIndex); - Component->SetCastShadow(Value); - return true; - } - return false; - } - - // Handle Component Tags manually here - if (PropertyName.Contains("Tags")) - { - UActorComponent* AC = Cast< UActorComponent >(InObject); - if (AC && !AC->IsPendingKill()) - { - FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); - if (!AC->ComponentTags.Contains(NameAttr)) - AC->ComponentTags.Add(NameAttr); - /* - for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++) - { - FName NameAttr = FName(*InPropertyAttribute.GetStringValue(nIdx)); - if (!AC->ComponentTags.Contains(NameAttr)) - AC->ComponentTags.Add(NameAttr); - } - */ - return true; - } - return false; - } - - // Try to find the corresponding UProperty - void* OutContainer = nullptr; - FProperty* FoundProperty = nullptr; - UObject* FoundPropertyObject = nullptr; - if (!FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) - return false; - - // Modify the Property we found - if (!ModifyPropertyValueOnObject(FoundPropertyObject, InPropertyAttribute, FoundProperty, OutContainer, AtIndex)) - return false; - - return true; -} - - -bool -FHoudiniGenericAttribute::FindPropertyOnObject( - UObject* InObject, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - UObject*& OutFoundPropertyObject, - void*& OutContainer) -{ -#if WITH_EDITOR - if (!InObject || InObject->IsPendingKill()) - return false; - - if (InPropertyName.IsEmpty()) - return false; - - UClass* ObjectClass = InObject->GetClass(); - if (!ObjectClass || ObjectClass->IsPendingKill()) - return false; - - // Set the result pointer to null - OutContainer = nullptr; - OutFoundProperty = nullptr; - OutFoundPropertyObject = InObject; - - bool bPropertyHasBeenFound = false; - FHoudiniGenericAttribute::TryToFindProperty( - InObject, - ObjectClass, - InPropertyName, - OutFoundProperty, - bPropertyHasBeenFound, - OutContainer); - - /* - // TODO: Parsing needs to be made recursively! - // Iterate manually on the properties, in order to handle StructProperties correctly - for (TFieldIterator PropIt(ObjectClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) - { - FProperty* CurrentProperty = *PropIt; - if (!CurrentProperty) - continue; - - FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = CurrentProperty->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - OutFoundProperty = CurrentProperty; - - // If it's an equality, we dont need to keep searching - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bPropertyHasBeenFound = true; - break; - } - } - - // StructProperty need to be a nested struct - //if (UStructProperty* StructProperty = Cast< UStructProperty >(CurrentProperty)) - // bPropertyHasBeenFound = TryToFindInStructProperty(InObject, InPropertyName, StructProperty, OutFoundProperty, OutStructContainer); - //else if (UArrayProperty* ArrayProperty = Cast< UArrayProperty >(CurrentProperty)) - // bPropertyHasBeenFound = TryToFindInArrayProperty(InObject, InPropertyName, ArrayProperty, OutFoundProperty, OutStructContainer); - - // Handle StructProperty - FStructProperty* StructProperty = CastField(CurrentProperty); - if (StructProperty) - { - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) - continue; - - for (TFieldIterator It(Struct); It; ++It) - { - FProperty* Property = *It; - if (!Property) - continue; - - DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - Name = Property->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - OutFoundProperty = Property; - OutStructContainer = StructProperty->ContainerPtrToValuePtr< void >(InObject, 0); - - // If it's an equality, we dont need to keep searching - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bPropertyHasBeenFound = true; - break; - } - } - } - } - - if (bPropertyHasBeenFound) - break; - } - - if (bPropertyHasBeenFound) - return true; - */ - - // Try with FindField?? - if (!OutFoundProperty) - OutFoundProperty = FindFProperty(ObjectClass, *InPropertyName); - - // Try with FindPropertyByName ?? - if (!OutFoundProperty) - OutFoundProperty = ObjectClass->FindPropertyByName(*InPropertyName); - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - - // Handle common properties nested in classes - // Static Meshes - UStaticMesh* SM = Cast(InObject); - if (SM && !SM->IsPendingKill()) - { - if (SM->BodySetup && FindPropertyOnObject( - SM->BodySetup, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - - if (SM->AssetImportData && FindPropertyOnObject( - SM->AssetImportData, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - - if (SM->NavCollision && FindPropertyOnObject( - SM->NavCollision, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - } - - // For Actors, parse their components - AActor* Actor = Cast(InObject); - if (Actor && !Actor->IsPendingKill()) - { - TArray AllComponents; - Actor->GetComponents(AllComponents, true); - - int32 CompIdx = 0; - for (USceneComponent * SceneComponent : AllComponents) - { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; - - if (FindPropertyOnObject( - SceneComponent, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - } - } - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - -#endif - return false; -} - - -bool -FHoudiniGenericAttribute::TryToFindProperty( - void* InContainer, - UStruct* InStruct, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - bool& bOutPropertyHasBeenFound, - void*& OutContainer) -{ -#if WITH_EDITOR - if (!InContainer) - return false; - - if (!InStruct || InStruct->IsPendingKill()) - return false; - - if (InPropertyName.IsEmpty()) - return false; - - // Iterate manually on the properties, in order to handle StructProperties correctly - for (TFieldIterator PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) - { - FProperty* CurrentProperty = *PropIt; - if (!CurrentProperty) - continue; - - FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = CurrentProperty->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - OutFoundProperty = CurrentProperty; - OutContainer = InContainer; - - // If it's an equality, we dont need to keep searching anymore - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bOutPropertyHasBeenFound = true; - break; - } - } - - // Do a recursive parsing for StructProperties - FStructProperty* StructProperty = CastField(CurrentProperty); - if (StructProperty) - { - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) - continue; - - TryToFindProperty( - StructProperty->ContainerPtrToValuePtr(InContainer, 0), - Struct, - InPropertyName, - OutFoundProperty, - bOutPropertyHasBeenFound, - OutContainer); - } - - if (bOutPropertyHasBeenFound) - break; - } - - if (bOutPropertyHasBeenFound) - return true; - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - -#endif - return false; -} - - -bool -FHoudiniGenericAttribute::ModifyPropertyValueOnObject( - UObject* InObject, - FHoudiniGenericAttribute InGenericAttribute, - FProperty* FoundProperty, - void* InContainer, - const int32& InAtIndex) -{ - if (!InObject || InObject->IsPendingKill() || !FoundProperty) - return false; - - // Determine the container to use (either InContainer if specified, or InObject) - void* Container = InContainer ? InContainer : InObject; - - // Property class name, used for logging - const FString PropertyClassName = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); - - // Initialize using the found property - FProperty* InnerProperty = FoundProperty; - - AActor* InOwner = Cast(InObject->GetOuter()); - bool bHasModifiedProperty = false; - - - auto OnPropertyChanged = [InObject, InOwner, &bHasModifiedProperty](FProperty* InProperty) - { -#if WITH_EDITOR - FPropertyChangedEvent Evt(InProperty); - InObject->PostEditChangeProperty(Evt); - if (InOwner) - { - // If we are setting properties on an Actor component, we want to notify the - // actor of the changes too since the property change might be handled in the actor's - // PostEditChange callbacks (one such an example occurs when changing the material for a decal actor). - InOwner->PostEditChangeProperty(Evt); - } -#endif - bHasModifiedProperty = true; - }; - - FArrayProperty* ArrayProperty = CastField(FoundProperty); - TSharedPtr ArrayHelper; - if (ArrayProperty) - { - InnerProperty = ArrayProperty->Inner; - ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); - } - - // TODO: implement support for array attributes received from Houdini - - // Get the "proper" AtIndex in the flat array by using the attribute tuple size - // TODO: fix the issue when changing array of tuple properties! - const int32 TupleSize = InGenericAttribute.AttributeTupleSize; - int32 AtIndex = InAtIndex * TupleSize; - FFieldClass* PropertyClass = InnerProperty->GetClass(); - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || - PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - // Supported non-struct properties - - // If the attribute from Houdini has a tuple size > 1, we support setting it on arrays on the unreal side - // For example: a 3 float from Houdini can be set as a TArray in Unreal. - - // If this is an ArrayProperty, ensure that it is at least large enough for our tuple - // TODO: should we just set this to exactly our tuple size? - if (ArrayHelper.IsValid()) - ArrayHelper->ExpandForIndex(TupleSize - 1); - - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - void* ValuePtr = nullptr; - if (ArrayHelper.IsValid()) - { - ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); - } - else - { - // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim - // on the property to determine if our TupleIndex is in range, if not, give up, we cannot set any more - // of our tuple indices on this property. - if (TupleIndex >= InnerProperty->ArrayDim) - break; - - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - } - - if (ValuePtr) - { - // Handle each property type that we support - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) - { - // Numeric properties are supported as floats and ints, and can also be set from a received string - FNumericProperty* const Property = CastField(InnerProperty); - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - Property->SetNumericPropertyValueFromString(ValuePtr, *InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); - } - else if (Property->IsFloatingPoint()) - { - Property->SetFloatingPointPropertyValue(ValuePtr, InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex)); - } - else if (Property->IsInteger()) - { - Property->SetIntPropertyValue(ValuePtr, InGenericAttribute.GetIntValue(AtIndex + TupleIndex)); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) - { - FBoolProperty* const Property = CastField(InnerProperty); - Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetBoolValue(AtIndex + TupleIndex)); - } - else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) - { - FStrProperty* const Property = CastField(InnerProperty); - Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); - } - else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - FNameProperty* const Property = CastField(InnerProperty); - Property->SetPropertyValue(ValuePtr, FName(*InGenericAttribute.GetStringValue(AtIndex + TupleIndex))); - } - - OnPropertyChanged(InnerProperty); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); - if (TupleIndex == 0) - return false; - } - } - } - else if (FStructProperty* StructProperty = CastField(InnerProperty)) - { - // struct properties - - // If we receive an attribute with tuple size > 1 and the target is an Unreal struct property, then we set - // as many of the values as we can in the struct. For example: a 4-float received from Houdini where the - // target is an FVector, the FVector.X, Y and Z would be set from the 4-float indices 0-2. Index 3 from the - // 4-float would then be ignored. - - const int32 TupleIndex = 0; - // If this is an array property, ensure it has enough space - // TODO: should we just set the array size to 1 for non-arrays and to the array size for arrays (once we support array attributes from Houdini)? - // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) - if (ArrayHelper.IsValid()) - ArrayHelper->ExpandForIndex(TupleIndex); - - void* PropertyValue = nullptr; - if (ArrayHelper.IsValid()) - PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); - else - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - - if (PropertyValue) - { - const FName PropertyName = StructProperty->Struct->GetFName(); - if (PropertyName == NAME_Vector) - { - // Found a vector property, fill it with up to 3 tuple values - FVector& Vector = *static_cast(PropertyValue); - Vector = FVector::ZeroVector; - Vector.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); - if (InGenericAttribute.AttributeTupleSize > 1) - Vector.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - Vector.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); - - OnPropertyChanged(StructProperty); - } - else if (PropertyName == NAME_Transform) - { - // Found a transform property fill it with the attribute tuple values - FVector Translation; - Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); - if (InGenericAttribute.AttributeTupleSize > 1) - Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); - - FQuat Rotation; - if (InGenericAttribute.AttributeTupleSize > 3) - Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); - if (InGenericAttribute.AttributeTupleSize > 4) - Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 4); - if (InGenericAttribute.AttributeTupleSize > 5) - Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 5); - if (InGenericAttribute.AttributeTupleSize > 6) - Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 6); - - FVector Scale(1, 1, 1); - if (InGenericAttribute.AttributeTupleSize > 7) - Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 7); - if (InGenericAttribute.AttributeTupleSize > 8) - Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 8); - if (InGenericAttribute.AttributeTupleSize > 9) - Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 9); - - FTransform& Transform = *static_cast(PropertyValue); - Transform = FTransform::Identity; - Transform.SetTranslation(Translation); - Transform.SetRotation(Rotation); - Transform.SetScale3D(Scale); - - OnPropertyChanged(StructProperty); - } - else if (PropertyName == NAME_Color) - { - FColor& Color = *static_cast(PropertyValue); - Color = FColor::Black; - Color.R = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - Color.G = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - Color.B = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 2); - if (InGenericAttribute.AttributeTupleSize > 3) - Color.A = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 3); - - OnPropertyChanged(StructProperty); - } - else if (PropertyName == NAME_LinearColor) - { - FLinearColor& LinearColor = *static_cast(PropertyValue); - LinearColor = FLinearColor::Black; - LinearColor.R = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - LinearColor.G = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - LinearColor.B = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); - if (InGenericAttribute.AttributeTupleSize > 3) - LinearColor.A = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); - - OnPropertyChanged(StructProperty); - } - else if (PropertyName == "Int32Interval") - { - FInt32Interval& Interval = *static_cast(PropertyValue); - Interval = FInt32Interval(); - Interval.Min = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - Interval.Max = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); - - OnPropertyChanged(StructProperty); - } - else if (PropertyName == "FloatInterval") - { - FFloatInterval& Interval = *static_cast(PropertyValue); - Interval = FFloatInterval(); - Interval.Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - Interval.Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); - - OnPropertyChanged(StructProperty); - } - else - { - HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); - return false; - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) - { - // OBJECT PATH PROPERTY - const int32 TupleIndex = 0; - // If this is an array property, ensure it has enough space - // TODO: should we just set the array size to 1 for non-arrays or to the array size for arrays (once we support array attributes from Houdini)? - // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) - if (ArrayHelper.IsValid()) - ArrayHelper->ExpandForIndex(TupleIndex); - - FString Value = InGenericAttribute.GetStringValue(AtIndex + TupleIndex); - void* ValuePtr = nullptr; - if (ArrayHelper.IsValid()) - ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); - else - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - - if (ValuePtr) - { - TSoftObjectPtr ValueObjectPtr; - ValueObjectPtr = Value; - UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); - - // Ensure the ObjectProperty class matches the ValueObject that we just loaded - if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) - { - ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); - OnPropertyChanged(ObjectProperty); - } - else - { - HOUDINI_LOG_WARNING( - TEXT("Could net set object property %s: ObjectProperty's object class (%s) does not match referenced object class (%s)!"), - *InGenericAttribute.AttributeName, *(ObjectProperty->PropertyClass->GetName()), IsValid(ValueObject) ? *(ValueObject->GetClass()->GetName()) : TEXT("NULL")); - return false; - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else - { - // Property was found, but is of an unsupported type - HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); - return false; - } - - if (bHasModifiedProperty) - { -#if WITH_EDITOR - InObject->PostEditChange(); - if (InOwner) - { - InOwner->PostEditChange(); - } -#endif - } - - return true; -} - -bool -FHoudiniGenericAttribute::GetPropertyValueFromObject( - UObject* InObject, - FProperty* InFoundProperty, - void* InContainer, - FHoudiniGenericAttribute& InGenericAttribute, - const int32& InAtIndex) -{ - if (!InObject || InObject->IsPendingKill() || !InFoundProperty) - return false; - - // Determine the container to use (either InContainer if specified, or InObject) - void* Container = InContainer ? InContainer : InObject; - - // Property class name, used for logging - const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); - - // Initialize using the found property - FProperty* InnerProperty = InFoundProperty; - - FArrayProperty* ArrayProperty = CastField(InFoundProperty); - TSharedPtr ArrayHelper; - if (ArrayProperty) - { - InnerProperty = ArrayProperty->Inner; - ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); - } - - // TODO: implement support for array attributes received from Houdini - - // Get the "proper" AtIndex in the flat array by using the attribute tuple size - // TODO: fix the issue when changing array of tuple properties! - const int32 TupleSize = InGenericAttribute.AttributeTupleSize; - int32 AtIndex = InAtIndex * TupleSize; - FFieldClass* PropertyClass = InnerProperty->GetClass(); - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || - PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - // Supported non-struct properties - - // If the attribute from Houdini has a tuple size > 1, we support getting it on arrays on the unreal side - // For example: a 3 float in Houdini can be set from a TArray in Unreal. - - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - void* ValuePtr = nullptr; - if (ArrayHelper.IsValid()) - { - // Check that we are not out of range - if (TupleIndex >= ArrayHelper->Num()) - break; - - ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); - } - else - { - // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim - // on the property to determine if our TupleIndex is in range, if not, give up, we cannot get any more - // of our tuple indices from this property. - if (TupleIndex >= InnerProperty->ArrayDim) - break; - - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - } - - if (ValuePtr) - { - // Handle each property type that we support - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) - { - // Numeric properties are supported as floats and ints, and can also be set from a received string - FNumericProperty* const Property = CastField(InnerProperty); - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - InGenericAttribute.SetStringValue(Property->GetNumericPropertyValueToString(ValuePtr), AtIndex + TupleIndex); - } - else if (Property->IsFloatingPoint()) - { - InGenericAttribute.SetDoubleValue(Property->GetFloatingPointPropertyValue(ValuePtr), AtIndex + TupleIndex); - } - else if (Property->IsInteger()) - { - InGenericAttribute.SetIntValue(Property->GetSignedIntPropertyValue(ValuePtr), AtIndex + TupleIndex); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) - { - FBoolProperty* const Property = CastField(InnerProperty); - InGenericAttribute.SetBoolValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); - } - else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) - { - FStrProperty* const Property = CastField(InnerProperty); - InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); - } - else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - FNameProperty* const Property = CastField(InnerProperty); - InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr).ToString(), AtIndex + TupleIndex); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); - if (TupleIndex == 0) - return false; - } - } - } - else if (FStructProperty* StructProperty = CastField(InnerProperty)) - { - // struct properties - - // Set as many as the tuple values as we can from the Unreal struct. - - const int32 TupleIndex = 0; - - void* PropertyValue = nullptr; - if (ArrayHelper.IsValid()) - { - if (ArrayHelper->IsValidIndex(TupleIndex)) - PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); - } - else if (TupleIndex < InnerProperty->ArrayDim) - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - } - - if (PropertyValue) - { - const FName PropertyName = StructProperty->Struct->GetFName(); - if (PropertyName == NAME_Vector) - { - // Found a vector property, fill it with up to 3 tuple values - const FVector& Vector = *static_cast(PropertyValue); - InGenericAttribute.SetDoubleValue(Vector.X, AtIndex + TupleIndex + 0); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetDoubleValue(Vector.Y, AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - InGenericAttribute.SetDoubleValue(Vector.Z, AtIndex + TupleIndex + 2); - } - else if (PropertyName == NAME_Transform) - { - // Found a transform property fill it with the attribute tuple values - const FTransform& Transform = *static_cast(PropertyValue); - const FVector Translation = Transform.GetTranslation(); - const FQuat Rotation = Transform.GetRotation(); - const FVector Scale = Transform.GetScale3D(); - - InGenericAttribute.SetDoubleValue(Translation.X, AtIndex + TupleIndex + 0); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetDoubleValue(Translation.Y, AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - InGenericAttribute.SetDoubleValue(Translation.Z, AtIndex + TupleIndex + 2); - - if (InGenericAttribute.AttributeTupleSize > 3) - InGenericAttribute.SetDoubleValue(Rotation.W, AtIndex + TupleIndex + 3); - if (InGenericAttribute.AttributeTupleSize > 4) - InGenericAttribute.SetDoubleValue(Rotation.X, AtIndex + TupleIndex + 4); - if (InGenericAttribute.AttributeTupleSize > 5) - InGenericAttribute.SetDoubleValue(Rotation.Y, AtIndex + TupleIndex + 5); - if (InGenericAttribute.AttributeTupleSize > 6) - InGenericAttribute.SetDoubleValue(Rotation.Z, AtIndex + TupleIndex + 6); - - if (InGenericAttribute.AttributeTupleSize > 7) - InGenericAttribute.SetDoubleValue(Scale.X, AtIndex + TupleIndex + 7); - if (InGenericAttribute.AttributeTupleSize > 8) - InGenericAttribute.SetDoubleValue(Scale.Y, AtIndex + TupleIndex + 8); - if (InGenericAttribute.AttributeTupleSize > 9) - InGenericAttribute.SetDoubleValue(Scale.Z, AtIndex + TupleIndex + 9); - } - else if (PropertyName == NAME_Color) - { - const FColor& Color = *static_cast(PropertyValue); - InGenericAttribute.SetIntValue(Color.R, AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetIntValue(Color.G, AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - InGenericAttribute.SetIntValue(Color.B, AtIndex + TupleIndex + 2); - if (InGenericAttribute.AttributeTupleSize > 3) - InGenericAttribute.SetIntValue(Color.A, AtIndex + TupleIndex + 3); - } - else if (PropertyName == NAME_LinearColor) - { - const FLinearColor& LinearColor = *static_cast(PropertyValue); - InGenericAttribute.SetDoubleValue(LinearColor.R, AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetDoubleValue(LinearColor.G, AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - InGenericAttribute.SetDoubleValue(LinearColor.B, AtIndex + TupleIndex + 2); - if (InGenericAttribute.AttributeTupleSize > 3) - InGenericAttribute.SetDoubleValue(LinearColor.A, AtIndex + TupleIndex + 3); - } - else if (PropertyName == "Int32Interval") - { - const FInt32Interval& Interval = *static_cast(PropertyValue); - InGenericAttribute.SetIntValue(Interval.Min, AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetIntValue(Interval.Max, AtIndex + TupleIndex + 1); - } - else if (PropertyName == "FloatInterval") - { - const FFloatInterval& Interval = *static_cast(PropertyValue); - InGenericAttribute.SetDoubleValue(Interval.Min, AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetDoubleValue(Interval.Max, AtIndex + TupleIndex + 1); - } - else - { - HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); - return false; - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) - { - // OBJECT PATH PROPERTY - const int32 TupleIndex = 0; - - void* ValuePtr = nullptr; - if (ArrayHelper.IsValid()) - { - if (ArrayHelper->IsValidIndex(TupleIndex)) - ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); - } - else if (TupleIndex < InnerProperty->ArrayDim) - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - } - - if (ValuePtr) - { - UObject* ValueObject = ObjectProperty->GetObjectPropertyValue(ValuePtr); - const TSoftObjectPtr ValueObjectPtr = ValueObject; - InGenericAttribute.SetStringValue(ValueObjectPtr.ToString(), AtIndex + TupleIndex); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else - { - // Property was found, but is of an unsupported type - HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); - return false; - } - - return true; -} - -bool -FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( - UObject* InObject, - FProperty* InFoundProperty, - void* InContainer, - int32& OutAttributeTupleSize, - EAttribStorageType& OutAttributeStorageType) -{ - if (!InObject || InObject->IsPendingKill() || !InFoundProperty) - return false; - - // Determine the container to use (either InContainer if specified, or InObject) - void* Container = InContainer ? InContainer : InObject; - - // Property class name, used for logging - const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); - - // Initialize using the found property - FProperty* InnerProperty = InFoundProperty; - - // FArrayProperty* ArrayProperty = CastField(InFoundProperty); - // TSharedPtr ArrayHelper; - // if (ArrayProperty) - // { - // InnerProperty = ArrayProperty->Inner; - // ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); - // } - - FFieldClass* PropertyClass = InnerProperty->GetClass(); - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || - PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - // Supported non-struct properties - - // Here we cannot really do better than tuple size of 1 (since we need to support arrays in the future, we - // cannot just assume array size == tuple size going to Houdini) - OutAttributeTupleSize = 1; - - // Handle each property type that we support - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) - { - // Numeric properties are supported as floats and ints, and can also be set from a received string - FNumericProperty* const Property = CastField(InnerProperty); - if (Property->IsFloatingPoint()) - { - OutAttributeStorageType = EAttribStorageType::FLOAT; - } - else if (Property->IsInteger()) - { - OutAttributeStorageType = EAttribStorageType::INT; - } - else - { - HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *Property->GetName(), *PropertyClassName); - return false; - } - } - else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) - { - OutAttributeStorageType = EAttribStorageType::INT; - } - else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) - { - OutAttributeStorageType = EAttribStorageType::STRING; - } - else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - OutAttributeStorageType = EAttribStorageType::STRING; - } - } - else if (FStructProperty* StructProperty = CastField(InnerProperty)) - { - // struct properties - - const FName PropertyName = StructProperty->Struct->GetFName(); - if (PropertyName == NAME_Vector) - { - OutAttributeTupleSize = 3; - OutAttributeStorageType = EAttribStorageType::FLOAT; - } - else if (PropertyName == NAME_Transform) - { - OutAttributeTupleSize = 10; - OutAttributeStorageType = EAttribStorageType::FLOAT; - } - else if (PropertyName == NAME_Color) - { - OutAttributeTupleSize = 4; - OutAttributeStorageType = EAttribStorageType::INT; - } - else if (PropertyName == NAME_LinearColor) - { - OutAttributeTupleSize = 4; - OutAttributeStorageType = EAttribStorageType::FLOAT; - } - else if (PropertyName == "Int32Interval") - { - OutAttributeTupleSize = 2; - OutAttributeStorageType = EAttribStorageType::INT; - } - else if (PropertyName == "FloatInterval") - { - OutAttributeTupleSize = 2; - OutAttributeStorageType = EAttribStorageType::FLOAT; - } - else - { - HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InFoundProperty->GetName(), *PropertyClassName, *PropertyName.ToString()); - return false; - } - } - else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) - { - OutAttributeTupleSize = 1; - OutAttributeStorageType = EAttribStorageType::STRING; - } - else - { - // Property was found, but is of an unsupported type - HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InFoundProperty->GetName()); - return false; - } - - return true; -} - -/* -bool -FHoudiniEngineUtils::TryToFindInStructProperty( - UObject* Object, FString UPropertyNameToFind, UStructProperty* StructProperty, UProperty*& FoundProperty, void*& StructContainer ) -{ - if ( !StructProperty || !Object ) - return false; - - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - for (TFieldIterator< UProperty > It(Struct); It; ++It) - { - UProperty* Property = *It; - if ( !Property ) - continue; - - FString DisplayName = It->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = It->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - FoundProperty = Property; - StructContainer = StructProperty->ContainerPtrToValuePtr< void >( Object, 0); - - // If it's an equality, we dont need to keep searching - if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) - return true; - } - - if ( FoundProperty ) - continue; - - UStructProperty* NestedStruct = Cast( Property ); - if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) - return true; - - UArrayProperty* ArrayProp = Cast( Property ); - if ( ArrayProp && TryToFindInArrayProperty( Object, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::TryToFindInArrayProperty( - UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ) -{ - if ( !ArrayProperty || !Object ) - return false; - - UProperty* Property = ArrayProperty->Inner; - if ( !Property ) - return false; - - FString DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = Property->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - FoundProperty = Property; - StructContainer = ArrayProperty->ContainerPtrToValuePtr< void >( Object, 0); - - // If it's an equality, we dont need to keep searching - if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) - return true; - } - - if ( !FoundProperty ) - { - UStructProperty* NestedStruct = Cast( Property ); - if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) - return true; - - UArrayProperty* ArrayProp = Cast( Property ); - if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) - return true; - } - - return false; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGenericAttribute.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" + +#include "Engine/StaticMesh.h" +#include "Components/ActorComponent.h" +#include "Components/PrimitiveComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Landscape.h" + +#include "PhysicsEngine/BodySetup.h" +#include "EditorFramework/AssetImportData.h" +#include "AI/Navigation/NavCollisionBase.h" + +double +FHoudiniGenericAttribute::GetDoubleValue(int32 index) const +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return DoubleValues[index]; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return (double)IntValues[index]; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return FCString::Atod(*StringValues[index]); + } + + return 0.0f; +} + +void +FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetDoubleValue(index * AttributeTupleSize + n); +} + +int64 +FHoudiniGenericAttribute::GetIntValue(int32 index) const +{ + if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return IntValues[index]; + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return (int64)DoubleValues[index]; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return FCString::Atoi64(*StringValues[index]); + } + + return 0; +} + +void +FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetIntValue(index * AttributeTupleSize + n); +} + +FString +FHoudiniGenericAttribute::GetStringValue(int32 index) const +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return StringValues[index]; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return FString::FromInt((int32)IntValues[index]); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return FString::SanitizeFloat(DoubleValues[index]); + } + + return FString(); +} + +void +FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetStringValue(index * AttributeTupleSize + n); +} + +bool +FHoudiniGenericAttribute::GetBoolValue(int32 index) const +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return DoubleValues[index] == 0.0 ? false : true; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return IntValues[index] == 0 ? false : true; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return StringValues[index].Equals(TEXT("true"), ESearchCase::IgnoreCase) ? true : false; + } + + return false; +} + +void +FHoudiniGenericAttribute::GetBoolTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetBoolValue(index * AttributeTupleSize + n); +} + +void* +FHoudiniGenericAttribute::GetData() +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.Num() > 0) + return StringValues.GetData(); + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.Num() > 0) + return IntValues.GetData(); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.Num() > 0) + return DoubleValues.GetData(); + } + + return nullptr; +} + +void +FHoudiniGenericAttribute::SetDoubleValue(double InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = FString::SanitizeFloat(InValue); + } +} + +void +FHoudiniGenericAttribute::SetDoubleTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetDoubleValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetIntValue(int64 InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = FString::Printf(TEXT("%lld"), InValue); + } +} + +void +FHoudiniGenericAttribute::SetIntTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetIntValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetStringValue(const FString& InValue, int32 index) +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = FCString::Atoi64(*InValue); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = FCString::Atod(*InValue); + } +} + +void +FHoudiniGenericAttribute::SetStringTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetStringValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetBoolValue(bool InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue ? 1.0 : 0.0; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue ? 1 : 0; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = InValue ? "true" : "false"; + } +} + +void +FHoudiniGenericAttribute::SetBoolTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetBoolValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +bool +FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( + UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Get the Property name + const FString& PropertyName = InPropertyAttribute.AttributeName; + if (PropertyName.IsEmpty()) + return false; + + // Some Properties need to be handle and modified manually... + if (PropertyName.Equals("CollisionProfileName", ESearchCase::IgnoreCase)) + { + UPrimitiveComponent* PC = Cast(InObject); + if (IsValid(PC)) + { + FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); + FName Value = FName(*StringValue); + PC->SetCollisionProfileName(Value); + + // Patch the StaticMeshGenerationProperties on the HAC + UHoudiniAssetComponent* HAC = Cast(InObject); + if (IsValid(HAC)) + { + HAC->StaticMeshGenerationProperties.DefaultBodyInstance.SetCollisionProfileName(Value); + } + + return true; + } + return false; + } + + if (PropertyName.Equals("CollisionEnabled", ESearchCase::IgnoreCase)) + { + UPrimitiveComponent* PC = Cast(InObject); + if (PC && !PC->IsPendingKill()) + { + FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); + if (StringValue.Equals("NoCollision", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::NoCollision); + return true; + } + else if (StringValue.Equals("QueryOnly", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::QueryOnly); + return true; + } + else if (StringValue.Equals("PhysicsOnly", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); + return true; + } + else if (StringValue.Equals("QueryAndPhysics", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + return true; + } + return false; + } + } + + // Specialize CastShadow to avoid paying the cost of finding property + calling Property change twice + if (PropertyName.Equals("CastShadow", ESearchCase::IgnoreCase)) + { + UPrimitiveComponent* Component = Cast< UPrimitiveComponent >(InObject); + if (Component && !Component->IsPendingKill()) + { + bool Value = InPropertyAttribute.GetBoolValue(AtIndex); + Component->SetCastShadow(Value); + return true; + } + return false; + } + + // Handle Component Tags manually here + if (PropertyName.Contains("Tags")) + { + UActorComponent* AC = Cast< UActorComponent >(InObject); + if (AC && !AC->IsPendingKill()) + { + FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); + if (!AC->ComponentTags.Contains(NameAttr)) + AC->ComponentTags.Add(NameAttr); + /* + for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++) + { + FName NameAttr = FName(*InPropertyAttribute.GetStringValue(nIdx)); + if (!AC->ComponentTags.Contains(NameAttr)) + AC->ComponentTags.Add(NameAttr); + } + */ + return true; + } + return false; + } +#if WITH_EDITOR + // Handle landscape edit layers toggling + if (PropertyName.Equals("EnableEditLayers", ESearchCase::IgnoreCase) + || PropertyName.Equals("bCanHaveLayersContent", ESearchCase::IgnoreCase)) + { + ALandscape* Landscape = Cast(InObject); + if (IsValid(Landscape)) + { + if(InPropertyAttribute.GetBoolValue(AtIndex) != Landscape->CanHaveLayersContent()) + Landscape->ToggleCanHaveLayersContent(); + + return true; + } + + return false; + } +#endif + + // Try to find the corresponding UProperty + void* OutContainer = nullptr; + FProperty* FoundProperty = nullptr; + UObject* FoundPropertyObject = nullptr; + if (!FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) + return false; + + // Modify the Property we found + if (!ModifyPropertyValueOnObject(FoundPropertyObject, InPropertyAttribute, FoundProperty, OutContainer, AtIndex)) + return false; + + return true; +} + + +bool +FHoudiniGenericAttribute::FindPropertyOnObject( + UObject* InObject, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + UObject*& OutFoundPropertyObject, + void*& OutContainer) +{ +#if WITH_EDITOR + if (!InObject || InObject->IsPendingKill()) + return false; + + if (InPropertyName.IsEmpty()) + return false; + + UClass* ObjectClass = InObject->GetClass(); + if (!ObjectClass || ObjectClass->IsPendingKill()) + return false; + + // Set the result pointer to null + OutContainer = nullptr; + OutFoundProperty = nullptr; + OutFoundPropertyObject = InObject; + + bool bPropertyHasBeenFound = false; + FHoudiniGenericAttribute::TryToFindProperty( + InObject, + ObjectClass, + InPropertyName, + OutFoundProperty, + bPropertyHasBeenFound, + OutContainer); + + /* + // TODO: Parsing needs to be made recursively! + // Iterate manually on the properties, in order to handle StructProperties correctly + for (TFieldIterator PropIt(ObjectClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) + { + FProperty* CurrentProperty = *PropIt; + if (!CurrentProperty) + continue; + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + OutFoundProperty = CurrentProperty; + + // If it's an equality, we dont need to keep searching + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bPropertyHasBeenFound = true; + break; + } + } + + // StructProperty need to be a nested struct + //if (UStructProperty* StructProperty = Cast< UStructProperty >(CurrentProperty)) + // bPropertyHasBeenFound = TryToFindInStructProperty(InObject, InPropertyName, StructProperty, OutFoundProperty, OutStructContainer); + //else if (UArrayProperty* ArrayProperty = Cast< UArrayProperty >(CurrentProperty)) + // bPropertyHasBeenFound = TryToFindInArrayProperty(InObject, InPropertyName, ArrayProperty, OutFoundProperty, OutStructContainer); + + // Handle StructProperty + FStructProperty* StructProperty = CastField(CurrentProperty); + if (StructProperty) + { + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + if (!Struct || Struct->IsPendingKill()) + continue; + + for (TFieldIterator It(Struct); It; ++It) + { + FProperty* Property = *It; + if (!Property) + continue; + + DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + Name = Property->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + OutFoundProperty = Property; + OutStructContainer = StructProperty->ContainerPtrToValuePtr< void >(InObject, 0); + + // If it's an equality, we dont need to keep searching + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bPropertyHasBeenFound = true; + break; + } + } + } + } + + if (bPropertyHasBeenFound) + break; + } + + if (bPropertyHasBeenFound) + return true; + */ + + // Try with FindField?? + if (!OutFoundProperty) + OutFoundProperty = FindFProperty(ObjectClass, *InPropertyName); + + // Try with FindPropertyByName ?? + if (!OutFoundProperty) + OutFoundProperty = ObjectClass->FindPropertyByName(*InPropertyName); + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + + // Handle common properties nested in classes + // Static Meshes + UStaticMesh* SM = Cast(InObject); + if (SM && !SM->IsPendingKill()) + { + if (SM->BodySetup && FindPropertyOnObject( + SM->BodySetup, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + + if (SM->AssetImportData && FindPropertyOnObject( + SM->AssetImportData, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + + if (SM->NavCollision && FindPropertyOnObject( + SM->NavCollision, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + } + + // For Actors, parse their components + AActor* Actor = Cast(InObject); + if (Actor && !Actor->IsPendingKill()) + { + TArray AllComponents; + Actor->GetComponents(AllComponents, true); + + int32 CompIdx = 0; + for (USceneComponent * SceneComponent : AllComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + if (FindPropertyOnObject( + SceneComponent, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + } + } + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + +#endif + return false; +} + + +bool +FHoudiniGenericAttribute::TryToFindProperty( + void* InContainer, + UStruct* InStruct, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + bool& bOutPropertyHasBeenFound, + void*& OutContainer) +{ +#if WITH_EDITOR + if (!InContainer) + return false; + + if (!InStruct || InStruct->IsPendingKill()) + return false; + + if (InPropertyName.IsEmpty()) + return false; + + // Iterate manually on the properties, in order to handle StructProperties correctly + for (TFieldIterator PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) + { + FProperty* CurrentProperty = *PropIt; + if (!CurrentProperty) + continue; + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + OutFoundProperty = CurrentProperty; + OutContainer = InContainer; + + // If it's an equality, we dont need to keep searching anymore + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bOutPropertyHasBeenFound = true; + break; + } + } + + // Do a recursive parsing for StructProperties + FStructProperty* StructProperty = CastField(CurrentProperty); + if (StructProperty) + { + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + if (!Struct || Struct->IsPendingKill()) + continue; + + TryToFindProperty( + StructProperty->ContainerPtrToValuePtr(InContainer, 0), + Struct, + InPropertyName, + OutFoundProperty, + bOutPropertyHasBeenFound, + OutContainer); + } + + if (bOutPropertyHasBeenFound) + break; + } + + if (bOutPropertyHasBeenFound) + return true; + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + +#endif + return false; +} + + +bool +FHoudiniGenericAttribute::ModifyPropertyValueOnObject( + UObject* InObject, + FHoudiniGenericAttribute InGenericAttribute, + FProperty* FoundProperty, + void* InContainer, + const int32& InAtIndex) +{ + if (!InObject || InObject->IsPendingKill() || !FoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Property class name, used for logging + const FString PropertyClassName = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); + + // Initialize using the found property + FProperty* InnerProperty = FoundProperty; + + AActor* InOwner = Cast(InObject->GetOuter()); + bool bHasModifiedProperty = false; + + + auto OnPropertyChanged = [InObject, InOwner, &bHasModifiedProperty](FProperty* InProperty) + { +#if WITH_EDITOR + FPropertyChangedEvent Evt(InProperty); + InObject->PostEditChangeProperty(Evt); + if (InOwner) + { + // If we are setting properties on an Actor component, we want to notify the + // actor of the changes too since the property change might be handled in the actor's + // PostEditChange callbacks (one such an example occurs when changing the material for a decal actor). + InOwner->PostEditChangeProperty(Evt); + } +#endif + bHasModifiedProperty = true; + }; + + FArrayProperty* ArrayProperty = CastField(FoundProperty); + TSharedPtr ArrayHelper; + if (ArrayProperty) + { + InnerProperty = ArrayProperty->Inner; + ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); + } + + // TODO: implement support for array attributes received from Houdini + + // Get the "proper" AtIndex in the flat array by using the attribute tuple size + // TODO: fix the issue when changing array of tuple properties! + const int32 TupleSize = InGenericAttribute.AttributeTupleSize; + int32 AtIndex = InAtIndex * TupleSize; + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + // Supported non-struct properties + + // If the attribute from Houdini has a tuple size > 1, we support setting it on arrays on the unreal side + // For example: a 3 float from Houdini can be set as a TArray in Unreal. + + // If this is an ArrayProperty, ensure that it is at least large enough for our tuple + // TODO: should we just set this to exactly our tuple size? + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleSize - 1); + + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + { + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + } + else + { + // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim + // on the property to determine if our TupleIndex is in range, if not, give up, we cannot set any more + // of our tuple indices on this property. + if (TupleIndex >= InnerProperty->ArrayDim) + break; + + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (ValuePtr) + { + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) + { + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + Property->SetNumericPropertyValueFromString(ValuePtr, *InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); + } + else if (Property->IsFloatingPoint()) + { + Property->SetFloatingPointPropertyValue(ValuePtr, InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex)); + } + else if (Property->IsInteger()) + { + Property->SetIntPropertyValue(ValuePtr, InGenericAttribute.GetIntValue(AtIndex + TupleIndex)); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) + { + FBoolProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetBoolValue(AtIndex + TupleIndex)); + } + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) + { + FStrProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); + } + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + FNameProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, FName(*InGenericAttribute.GetStringValue(AtIndex + TupleIndex))); + } + + OnPropertyChanged(InnerProperty); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); + if (TupleIndex == 0) + return false; + } + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties + + // If we receive an attribute with tuple size > 1 and the target is an Unreal struct property, then we set + // as many of the values as we can in the struct. For example: a 4-float received from Houdini where the + // target is an FVector, the FVector.X, Y and Z would be set from the 4-float indices 0-2. Index 3 from the + // 4-float would then be ignored. + + const int32 TupleIndex = 0; + // If this is an array property, ensure it has enough space + // TODO: should we just set the array size to 1 for non-arrays and to the array size for arrays (once we support array attributes from Houdini)? + // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleIndex); + + void* PropertyValue = nullptr; + if (ArrayHelper.IsValid()) + PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); + else + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + + if (PropertyValue) + { + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + // Found a vector property, fill it with up to 3 tuple values + FVector& Vector = *static_cast(PropertyValue); + Vector = FVector::ZeroVector; + Vector.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + Vector.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Vector.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == NAME_Transform) + { + // Found a transform property fill it with the attribute tuple values + FVector Translation; + Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + + FQuat Rotation; + if (InGenericAttribute.AttributeTupleSize > 3) + Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); + if (InGenericAttribute.AttributeTupleSize > 4) + Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 4); + if (InGenericAttribute.AttributeTupleSize > 5) + Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 5); + if (InGenericAttribute.AttributeTupleSize > 6) + Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 6); + + FVector Scale(1, 1, 1); + if (InGenericAttribute.AttributeTupleSize > 7) + Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 7); + if (InGenericAttribute.AttributeTupleSize > 8) + Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 8); + if (InGenericAttribute.AttributeTupleSize > 9) + Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 9); + + FTransform& Transform = *static_cast(PropertyValue); + Transform = FTransform::Identity; + Transform.SetTranslation(Translation); + Transform.SetRotation(Rotation); + Transform.SetScale3D(Scale); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == NAME_Color) + { + FColor& Color = *static_cast(PropertyValue); + Color = FColor::Black; + Color.R = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Color.G = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Color.B = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + Color.A = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 3); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == NAME_LinearColor) + { + FLinearColor& LinearColor = *static_cast(PropertyValue); + LinearColor = FLinearColor::Black; + LinearColor.R = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + LinearColor.G = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + LinearColor.B = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + LinearColor.A = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == "Int32Interval") + { + FInt32Interval& Interval = *static_cast(PropertyValue); + Interval = FInt32Interval(); + Interval.Min = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Interval.Max = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == "FloatInterval") + { + FFloatInterval& Interval = *static_cast(PropertyValue); + Interval = FFloatInterval(); + Interval.Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Interval.Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + + OnPropertyChanged(StructProperty); + } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + // OBJECT PATH PROPERTY + const int32 TupleIndex = 0; + // If this is an array property, ensure it has enough space + // TODO: should we just set the array size to 1 for non-arrays or to the array size for arrays (once we support array attributes from Houdini)? + // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleIndex); + + FString Value = InGenericAttribute.GetStringValue(AtIndex + TupleIndex); + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + else + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + + if (ValuePtr) + { + TSoftObjectPtr ValueObjectPtr; + ValueObjectPtr = Value; + UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); + + // Ensure the ObjectProperty class matches the ValueObject that we just loaded + if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) + { + ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); + OnPropertyChanged(ObjectProperty); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Could net set object property %s: ObjectProperty's object class (%s) does not match referenced object class (%s)!"), + *InGenericAttribute.AttributeName, *(ObjectProperty->PropertyClass->GetName()), IsValid(ValueObject) ? *(ValueObject->GetClass()->GetName()) : TEXT("NULL")); + return false; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); + return false; + } + + if (bHasModifiedProperty) + { +#if WITH_EDITOR + InObject->PostEditChange(); + if (InOwner) + { + InOwner->PostEditChange(); + } +#endif + } + + return true; +} + +bool +FHoudiniGenericAttribute::GetPropertyValueFromObject( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + FHoudiniGenericAttribute& InGenericAttribute, + const int32& InAtIndex) +{ + if (!InObject || InObject->IsPendingKill() || !InFoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Property class name, used for logging + const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); + + // Initialize using the found property + FProperty* InnerProperty = InFoundProperty; + + FArrayProperty* ArrayProperty = CastField(InFoundProperty); + TSharedPtr ArrayHelper; + if (ArrayProperty) + { + InnerProperty = ArrayProperty->Inner; + ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); + } + + // TODO: implement support for array attributes received from Houdini + + // Get the "proper" AtIndex in the flat array by using the attribute tuple size + // TODO: fix the issue when changing array of tuple properties! + const int32 TupleSize = InGenericAttribute.AttributeTupleSize; + int32 AtIndex = InAtIndex * TupleSize; + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + // Supported non-struct properties + + // If the attribute from Houdini has a tuple size > 1, we support getting it on arrays on the unreal side + // For example: a 3 float in Houdini can be set from a TArray in Unreal. + + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + { + // Check that we are not out of range + if (TupleIndex >= ArrayHelper->Num()) + break; + + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + } + else + { + // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim + // on the property to determine if our TupleIndex is in range, if not, give up, we cannot get any more + // of our tuple indices from this property. + if (TupleIndex >= InnerProperty->ArrayDim) + break; + + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (ValuePtr) + { + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) + { + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + InGenericAttribute.SetStringValue(Property->GetNumericPropertyValueToString(ValuePtr), AtIndex + TupleIndex); + } + else if (Property->IsFloatingPoint()) + { + InGenericAttribute.SetDoubleValue(Property->GetFloatingPointPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else if (Property->IsInteger()) + { + InGenericAttribute.SetIntValue(Property->GetSignedIntPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) + { + FBoolProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetBoolValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) + { + FStrProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + FNameProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr).ToString(), AtIndex + TupleIndex); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); + if (TupleIndex == 0) + return false; + } + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties + + // Set as many as the tuple values as we can from the Unreal struct. + + const int32 TupleIndex = 0; + + void* PropertyValue = nullptr; + if (ArrayHelper.IsValid()) + { + if (ArrayHelper->IsValidIndex(TupleIndex)) + PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); + } + else if (TupleIndex < InnerProperty->ArrayDim) + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (PropertyValue) + { + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + // Found a vector property, fill it with up to 3 tuple values + const FVector& Vector = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(Vector.X, AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Vector.Y, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(Vector.Z, AtIndex + TupleIndex + 2); + } + else if (PropertyName == NAME_Transform) + { + // Found a transform property fill it with the attribute tuple values + const FTransform& Transform = *static_cast(PropertyValue); + const FVector Translation = Transform.GetTranslation(); + const FQuat Rotation = Transform.GetRotation(); + const FVector Scale = Transform.GetScale3D(); + + InGenericAttribute.SetDoubleValue(Translation.X, AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Translation.Y, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(Translation.Z, AtIndex + TupleIndex + 2); + + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetDoubleValue(Rotation.W, AtIndex + TupleIndex + 3); + if (InGenericAttribute.AttributeTupleSize > 4) + InGenericAttribute.SetDoubleValue(Rotation.X, AtIndex + TupleIndex + 4); + if (InGenericAttribute.AttributeTupleSize > 5) + InGenericAttribute.SetDoubleValue(Rotation.Y, AtIndex + TupleIndex + 5); + if (InGenericAttribute.AttributeTupleSize > 6) + InGenericAttribute.SetDoubleValue(Rotation.Z, AtIndex + TupleIndex + 6); + + if (InGenericAttribute.AttributeTupleSize > 7) + InGenericAttribute.SetDoubleValue(Scale.X, AtIndex + TupleIndex + 7); + if (InGenericAttribute.AttributeTupleSize > 8) + InGenericAttribute.SetDoubleValue(Scale.Y, AtIndex + TupleIndex + 8); + if (InGenericAttribute.AttributeTupleSize > 9) + InGenericAttribute.SetDoubleValue(Scale.Z, AtIndex + TupleIndex + 9); + } + else if (PropertyName == NAME_Color) + { + const FColor& Color = *static_cast(PropertyValue); + InGenericAttribute.SetIntValue(Color.R, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetIntValue(Color.G, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetIntValue(Color.B, AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetIntValue(Color.A, AtIndex + TupleIndex + 3); + } + else if (PropertyName == NAME_LinearColor) + { + const FLinearColor& LinearColor = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(LinearColor.R, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(LinearColor.G, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(LinearColor.B, AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetDoubleValue(LinearColor.A, AtIndex + TupleIndex + 3); + } + else if (PropertyName == "Int32Interval") + { + const FInt32Interval& Interval = *static_cast(PropertyValue); + InGenericAttribute.SetIntValue(Interval.Min, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetIntValue(Interval.Max, AtIndex + TupleIndex + 1); + } + else if (PropertyName == "FloatInterval") + { + const FFloatInterval& Interval = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(Interval.Min, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Interval.Max, AtIndex + TupleIndex + 1); + } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + // OBJECT PATH PROPERTY + const int32 TupleIndex = 0; + + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + { + if (ArrayHelper->IsValidIndex(TupleIndex)) + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + } + else if (TupleIndex < InnerProperty->ArrayDim) + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (ValuePtr) + { + UObject* ValueObject = ObjectProperty->GetObjectPropertyValue(ValuePtr); + const TSoftObjectPtr ValueObjectPtr = ValueObject; + InGenericAttribute.SetStringValue(ValueObjectPtr.ToString(), AtIndex + TupleIndex); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); + return false; + } + + return true; +} + +bool +FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + int32& OutAttributeTupleSize, + EAttribStorageType& OutAttributeStorageType) +{ + if (!InObject || InObject->IsPendingKill() || !InFoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Property class name, used for logging + const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); + + // Initialize using the found property + FProperty* InnerProperty = InFoundProperty; + + // FArrayProperty* ArrayProperty = CastField(InFoundProperty); + // TSharedPtr ArrayHelper; + // if (ArrayProperty) + // { + // InnerProperty = ArrayProperty->Inner; + // ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); + // } + + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + // Supported non-struct properties + + // Here we cannot really do better than tuple size of 1 (since we need to support arrays in the future, we + // cannot just assume array size == tuple size going to Houdini) + OutAttributeTupleSize = 1; + + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) + { + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (Property->IsFloatingPoint()) + { + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (Property->IsInteger()) + { + OutAttributeStorageType = EAttribStorageType::INT; + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *Property->GetName(), *PropertyClassName); + return false; + } + } + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::STRING; + } + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::STRING; + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties + + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + OutAttributeTupleSize = 3; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == NAME_Transform) + { + OutAttributeTupleSize = 10; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == NAME_Color) + { + OutAttributeTupleSize = 4; + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyName == NAME_LinearColor) + { + OutAttributeTupleSize = 4; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == "Int32Interval") + { + OutAttributeTupleSize = 2; + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyName == "FloatInterval") + { + OutAttributeTupleSize = 2; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InFoundProperty->GetName(), *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + OutAttributeTupleSize = 1; + OutAttributeStorageType = EAttribStorageType::STRING; + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InFoundProperty->GetName()); + return false; + } + + return true; +} + +/* +bool +FHoudiniEngineUtils::TryToFindInStructProperty( + UObject* Object, FString UPropertyNameToFind, UStructProperty* StructProperty, UProperty*& FoundProperty, void*& StructContainer ) +{ + if ( !StructProperty || !Object ) + return false; + + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + for (TFieldIterator< UProperty > It(Struct); It; ++It) + { + UProperty* Property = *It; + if ( !Property ) + continue; + + FString DisplayName = It->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = It->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = StructProperty->ContainerPtrToValuePtr< void >( Object, 0); + + // If it's an equality, we dont need to keep searching + if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) + return true; + } + + if ( FoundProperty ) + continue; + + UStructProperty* NestedStruct = Cast( Property ); + if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) + return true; + + UArrayProperty* ArrayProp = Cast( Property ); + if ( ArrayProp && TryToFindInArrayProperty( Object, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::TryToFindInArrayProperty( + UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ) +{ + if ( !ArrayProperty || !Object ) + return false; + + UProperty* Property = ArrayProperty->Inner; + if ( !Property ) + return false; + + FString DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = Property->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = ArrayProperty->ContainerPtrToValuePtr< void >( Object, 0); + + // If it's an equality, we dont need to keep searching + if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) + return true; + } + + if ( !FoundProperty ) + { + UStructProperty* NestedStruct = Cast( Property ); + if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) + return true; + + UArrayProperty* ArrayProp = Cast( Property ); + if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) + return true; + } + + return false; +} */ \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h index 52ea4322c..13ccf4053 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h @@ -1,156 +1,156 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniGenericAttribute.generated.h" - -UENUM() -enum class EAttribStorageType : int8 -{ - Invalid = -1, - - INT = 0, - INT64 = 1, - FLOAT = 2, - FLOAT64 = 3, - STRING = 4 -}; - -UENUM() -enum class EAttribOwner : int8 -{ - Invalid = -1, - - Vertex, - Point, - Prim, - Detail, -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY() - FString AttributeName; - - UPROPERTY() - EAttribStorageType AttributeType = EAttribStorageType::Invalid; - UPROPERTY() - EAttribOwner AttributeOwner = EAttribOwner::Invalid; - - UPROPERTY() - int32 AttributeCount = -1; - UPROPERTY() - int32 AttributeTupleSize = -1; - - UPROPERTY() - TArray DoubleValues; - UPROPERTY() - TArray IntValues; - UPROPERTY() - TArray StringValues; - - // Accessors - - double GetDoubleValue(int32 index = 0) const; - void GetDoubleTuple(TArray& TupleValues, int32 index = 0) const; - - int64 GetIntValue(int32 index = 0) const; - void GetIntTuple(TArray& TupleValues, int32 index = 0) const; - - FString GetStringValue(int32 index = 0) const; - void GetStringTuple(TArray& TupleValues, int32 index = 0) const; - - bool GetBoolValue(int32 index = 0) const; - void GetBoolTuple(TArray& TupleValues, int32 index = 0) const; - - void* GetData(); - - // Mutators - - void SetDoubleValue(double InValue, int32 index = 0); - void SetDoubleTuple(const TArray& InTupleValues, int32 index = 0); - - void SetIntValue(int64 InValue, int32 index = 0); - void SetIntTuple(const TArray& InTupleValues, int32 index = 0); - - void SetStringValue(const FString& InValue, int32 index = 0); - void SetStringTuple(const TArray& InTupleValues, int32 index = 0); - - void SetBoolValue(bool InValue, int32 index = 0); - void SetBoolTuple(const TArray& InTupleValues, int32 index = 0); - - // - static bool UpdatePropertyAttributeOnObject( - UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex = 0); - - // Tries to find a Uproperty by name/label on an object - // FoundPropertyObject will be the object that actually contains the property - // and can be different from InObject if the property is nested. - static bool FindPropertyOnObject( - UObject* InObject, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - UObject*& OutFoundPropertyObject, - void*& OutContainer); - - // Modifies the value of a found Property - static bool ModifyPropertyValueOnObject( - UObject* InObject, - FHoudiniGenericAttribute InGenericAttribute, - FProperty* FoundProperty, - void* InContainer, - const int32& AtIndex = 0 ); - - // Gets the value of a found Property and sets it in the appropriate - // array and index in InGenericAttribute. - static bool GetPropertyValueFromObject( - UObject* InObject, - FProperty* InFoundProperty, - void* InContainer, - FHoudiniGenericAttribute& InGenericAttribute, - const int32& InAtIndex = 0); - - // Helper: determines a valid tuple size and storage type for a Houdini attribute from an Unreal FProperty - static bool GetAttributeTupleSizeAndStorageFromProperty( - UObject* InObject, - FProperty* InFoundProperty, - void* InContainer, - int32& OutAttributeTupleSize, - EAttribStorageType& OutAttributeStorageType); - - // Recursive search for a given property on a UObject - static bool TryToFindProperty( - void* InContainer, - UStruct* InStruct, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - bool& bOutPropertyHasBeenFound, - void*& OutContainer); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniGenericAttribute.generated.h" + +UENUM() +enum class EAttribStorageType : int8 +{ + Invalid = -1, + + INT = 0, + INT64 = 1, + FLOAT = 2, + FLOAT64 = 3, + STRING = 4 +}; + +UENUM() +enum class EAttribOwner : int8 +{ + Invalid = -1, + + Vertex, + Point, + Prim, + Detail, +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY() + FString AttributeName; + + UPROPERTY() + EAttribStorageType AttributeType = EAttribStorageType::Invalid; + UPROPERTY() + EAttribOwner AttributeOwner = EAttribOwner::Invalid; + + UPROPERTY() + int32 AttributeCount = -1; + UPROPERTY() + int32 AttributeTupleSize = -1; + + UPROPERTY() + TArray DoubleValues; + UPROPERTY() + TArray IntValues; + UPROPERTY() + TArray StringValues; + + // Accessors + + double GetDoubleValue(int32 index = 0) const; + void GetDoubleTuple(TArray& TupleValues, int32 index = 0) const; + + int64 GetIntValue(int32 index = 0) const; + void GetIntTuple(TArray& TupleValues, int32 index = 0) const; + + FString GetStringValue(int32 index = 0) const; + void GetStringTuple(TArray& TupleValues, int32 index = 0) const; + + bool GetBoolValue(int32 index = 0) const; + void GetBoolTuple(TArray& TupleValues, int32 index = 0) const; + + void* GetData(); + + // Mutators + + void SetDoubleValue(double InValue, int32 index = 0); + void SetDoubleTuple(const TArray& InTupleValues, int32 index = 0); + + void SetIntValue(int64 InValue, int32 index = 0); + void SetIntTuple(const TArray& InTupleValues, int32 index = 0); + + void SetStringValue(const FString& InValue, int32 index = 0); + void SetStringTuple(const TArray& InTupleValues, int32 index = 0); + + void SetBoolValue(bool InValue, int32 index = 0); + void SetBoolTuple(const TArray& InTupleValues, int32 index = 0); + + // + static bool UpdatePropertyAttributeOnObject( + UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex = 0); + + // Tries to find a Uproperty by name/label on an object + // FoundPropertyObject will be the object that actually contains the property + // and can be different from InObject if the property is nested. + static bool FindPropertyOnObject( + UObject* InObject, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + UObject*& OutFoundPropertyObject, + void*& OutContainer); + + // Modifies the value of a found Property + static bool ModifyPropertyValueOnObject( + UObject* InObject, + FHoudiniGenericAttribute InGenericAttribute, + FProperty* FoundProperty, + void* InContainer, + const int32& AtIndex = 0 ); + + // Gets the value of a found Property and sets it in the appropriate + // array and index in InGenericAttribute. + static bool GetPropertyValueFromObject( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + FHoudiniGenericAttribute& InGenericAttribute, + const int32& InAtIndex = 0); + + // Helper: determines a valid tuple size and storage type for a Houdini attribute from an Unreal FProperty + static bool GetAttributeTupleSizeAndStorageFromProperty( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + int32& OutAttributeTupleSize, + EAttribStorageType& OutAttributeStorageType); + + // Recursive search for a given property on a UObject + static bool TryToFindProperty( + void* InContainer, + UStruct* InStruct, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + bool& bOutPropertyHasBeenFound, + void*& OutContainer); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp index 49171ceb6..bea64f3d3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp @@ -1,185 +1,185 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoPartObject.h" - -// -FHoudiniGeoPartObject::FHoudiniGeoPartObject() - : AssetId(-1) - , AssetName(TEXT("")) - , ObjectId(-1) - , ObjectName(TEXT("")) - , GeoId(-1) - , PartId(-1) - , PartName(TEXT("")) - , bHasCustomPartName(false) - , TransformMatrix(FMatrix::Identity) - , NodePath(TEXT("")) - , Type(EHoudiniPartType::Invalid) - , InstancerType(EHoudiniInstancerType::Invalid) - , VolumeName(TEXT("")) - , VolumeTileIndex(-1) - , bIsVisible(false) - , bIsEditable(false) - , bIsTemplated(false) - , bIsInstanced(false) - , bHasGeoChanged(true) - , bHasPartChanged(true) - , bHasTransformChanged(true) - , bHasMaterialsChanged(true) - , bLoaded(false) -{ - -} - -bool -FHoudiniGeoPartObject::IsValid() const -{ - return (ObjectId >= 0 && GeoId >= 0 && PartId >= 0); -} - -bool -FHoudiniGeoPartObject::operator==(const FHoudiniGeoPartObject & InGeoPartObject) const -{ - // TODO: split?? - return Equals(InGeoPartObject, true); -} - -bool -FHoudiniGeoPartObject::Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const -{ - // TODO: This will likely need some improvement! - - /* - // Object/Geo/Part IDs must match - if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) - return false; - - if (!bIgnoreSplit) - { - // If the split type and index match, we're equal... - if (SplitType == GeoPartObject.SplitType && SplitIndex == GeoPartObject.SplitIndex) - return true; - - // ... if not we should compare our names - return CompareNames(GeoPartObject, bIgnoreSplit); - } - */ - - // See if objects / geo / part ids match - bool MatchingIDs = true; - if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) - MatchingIDs = false; - - // See if the type matches - bool MatchingType = (Type == GeoPartObject.Type); - // Both IDs and type match, consider the two HGPO as equals - if (MatchingIDs && MatchingType) - return true; - - // Both IDs and type do not match, consider the two HGPOs as different - if (!MatchingIDs && !MatchingType) - return false; - - // If only the ID dont match we can do some further checks - - // If one of the two HGPO has been loaded - if ((bLoaded && !GeoPartObject.bLoaded) - || (!bLoaded && GeoPartObject.bLoaded)) - { - // For loaded HGPOs, part names should be a sufficent comparison - if (PartName.Equals(GeoPartObject.PartName)) - return true; - } - - // TODO: This was causing issues somehow with tiled landscapes - // ... if not, compare by names - if(!MatchingIDs) - return CompareNames(GeoPartObject, bIgnoreSplit); - - return false; -} - -void -FHoudiniGeoPartObject::SetCustomPartName(const FString & InName) -{ - if (InName.IsEmpty()) - return; - - PartName = InName; - bHasCustomPartName = true; -} - -bool -FHoudiniGeoPartObject::CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const -{ - //TODO: AssetName? - - // Object, part and split names must match - if (!ObjectName.Equals(HoudiniGeoPartObject.ObjectName) - || !PartName.Equals(HoudiniGeoPartObject.PartName)) - { - return false; - } - - /* - // Split should also match if we dont ignore it - if (!bIgnoreSplit && !SplitName.Equals(HoudiniGeoPartObject.SplitName)) - { - return false; - } - */ - - return true; -} - -FString -FHoudiniGeoPartObject::HoudiniPartTypeToString(const EHoudiniPartType& InType) -{ - FString OutTypeStr; - switch (InType) - { - case EHoudiniPartType::Mesh: - OutTypeStr = TEXT("Mesh"); - break; - case EHoudiniPartType::Instancer: - OutTypeStr = TEXT("Instancer"); - break; - case EHoudiniPartType::Curve: - OutTypeStr = TEXT("Curve"); - break; - case EHoudiniPartType::Volume: - OutTypeStr = TEXT("Volume"); - break; - - default: - case EHoudiniPartType::Invalid: - OutTypeStr = TEXT("Invalid"); - break; - } - - return OutTypeStr; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoPartObject.h" + +// +FHoudiniGeoPartObject::FHoudiniGeoPartObject() + : AssetId(-1) + , AssetName(TEXT("")) + , ObjectId(-1) + , ObjectName(TEXT("")) + , GeoId(-1) + , PartId(-1) + , PartName(TEXT("")) + , bHasCustomPartName(false) + , TransformMatrix(FMatrix::Identity) + , NodePath(TEXT("")) + , Type(EHoudiniPartType::Invalid) + , InstancerType(EHoudiniInstancerType::Invalid) + , VolumeName(TEXT("")) + , VolumeTileIndex(-1) + , bIsVisible(false) + , bIsEditable(false) + , bIsTemplated(false) + , bIsInstanced(false) + , bHasGeoChanged(true) + , bHasPartChanged(true) + , bHasTransformChanged(true) + , bHasMaterialsChanged(true) + , bLoaded(false) +{ + +} + +bool +FHoudiniGeoPartObject::IsValid() const +{ + return (ObjectId >= 0 && GeoId >= 0 && PartId >= 0); +} + +bool +FHoudiniGeoPartObject::operator==(const FHoudiniGeoPartObject & InGeoPartObject) const +{ + // TODO: split?? + return Equals(InGeoPartObject, true); +} + +bool +FHoudiniGeoPartObject::Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const +{ + // TODO: This will likely need some improvement! + + /* + // Object/Geo/Part IDs must match + if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) + return false; + + if (!bIgnoreSplit) + { + // If the split type and index match, we're equal... + if (SplitType == GeoPartObject.SplitType && SplitIndex == GeoPartObject.SplitIndex) + return true; + + // ... if not we should compare our names + return CompareNames(GeoPartObject, bIgnoreSplit); + } + */ + + // See if objects / geo / part ids match + bool MatchingIDs = true; + if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) + MatchingIDs = false; + + // See if the type matches + bool MatchingType = (Type == GeoPartObject.Type); + // Both IDs and type match, consider the two HGPO as equals + if (MatchingIDs && MatchingType) + return true; + + // Both IDs and type do not match, consider the two HGPOs as different + if (!MatchingIDs && !MatchingType) + return false; + + // If only the ID dont match we can do some further checks + + // If one of the two HGPO has been loaded + if ((bLoaded && !GeoPartObject.bLoaded) + || (!bLoaded && GeoPartObject.bLoaded)) + { + // For loaded HGPOs, part names should be a sufficent comparison + if (PartName.Equals(GeoPartObject.PartName)) + return true; + } + + // TODO: This was causing issues somehow with tiled landscapes + // ... if not, compare by names + if(!MatchingIDs) + return CompareNames(GeoPartObject, bIgnoreSplit); + + return false; +} + +void +FHoudiniGeoPartObject::SetCustomPartName(const FString & InName) +{ + if (InName.IsEmpty()) + return; + + PartName = InName; + bHasCustomPartName = true; +} + +bool +FHoudiniGeoPartObject::CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const +{ + //TODO: AssetName? + + // Object, part and split names must match + if (!ObjectName.Equals(HoudiniGeoPartObject.ObjectName) + || !PartName.Equals(HoudiniGeoPartObject.PartName)) + { + return false; + } + + /* + // Split should also match if we dont ignore it + if (!bIgnoreSplit && !SplitName.Equals(HoudiniGeoPartObject.SplitName)) + { + return false; + } + */ + + return true; +} + +FString +FHoudiniGeoPartObject::HoudiniPartTypeToString(const EHoudiniPartType& InType) +{ + FString OutTypeStr; + switch (InType) + { + case EHoudiniPartType::Mesh: + OutTypeStr = TEXT("Mesh"); + break; + case EHoudiniPartType::Instancer: + OutTypeStr = TEXT("Instancer"); + break; + case EHoudiniPartType::Curve: + OutTypeStr = TEXT("Curve"); + break; + case EHoudiniPartType::Volume: + OutTypeStr = TEXT("Volume"); + break; + + default: + case EHoudiniPartType::Invalid: + OutTypeStr = TEXT("Invalid"); + break; + } + + return OutTypeStr; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h index 099a37da6..7b8db79ce 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h @@ -1,419 +1,423 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniGeoPartObject.generated.h" - -UENUM() -enum class EHoudiniGeoType : uint8 -{ - Invalid, - - Default, - Intermediate, - Input, - Curve -}; - -UENUM() -enum class EHoudiniPartType : uint8 -{ - Invalid, - - Mesh, - Instancer, - Curve, - Volume -}; - -UENUM() -enum class EHoudiniInstancerType : uint8 -{ - Invalid, - - ObjectInstancer, - PackedPrimitive, - AttributeInstancer, - OldSchoolAttributeInstancer -}; - -UENUM() -enum class EHoudiniCurveType : int8 -{ - Invalid = -1, - - Polygon = 0, - Nurbs = 1, - Bezier = 2, - Points = 3 -}; - -UENUM() -enum class EHoudiniCurveMethod : int8 -{ - Invalid = -1, - - CVs = 0, - Breakpoints = 1, - Freehand = 2 -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniObjectInfo -{ - GENERATED_USTRUCT_BODY() - - FString Name = TEXT(""); - - int32 NodeId = -1; - int32 ObjectToInstanceID = -1; - - bool bHasTransformChanged = false; - bool bHaveGeosChanged = false; - bool bIsVisible = false; - bool bIsInstancer = false; - bool bIsInstanced = false; - - int32 GeoCount = -1; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGeoInfo -{ - GENERATED_USTRUCT_BODY() - - EHoudiniGeoType Type = EHoudiniGeoType::Invalid; - FString Name = TEXT(""); - int32 NodeId = -1; - - bool bIsEditable = false; - bool bIsTemplated = false; - bool bIsDisplayGeo = false; - bool bHasGeoChanged = false; - bool bHasMaterialChanged = false; - - int32 PartCount = -1; - int32 PointGroupCount = -1; - int32 PrimitiveGroupCount = -1; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniPartInfo -{ - GENERATED_USTRUCT_BODY() - - int32 PartId = -1; - FString Name = TEXT(""); - - EHoudiniPartType Type = EHoudiniPartType::Invalid; - - int32 FaceCount = -1; - int32 VertexCount = -1; - int32 PointCount = -1; - - int32 PointAttributeCounts = -1; - int32 VertexAttributeCounts = -1; - int32 PrimitiveAttributeCounts = -1; - int32 DetailAttributeCounts = -1; - - bool bIsInstanced = false; - - int32 InstancedPartCount = -1; - int32 InstanceCount = -1; - - bool bHasChanged = false; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniVolumeInfo -{ - GENERATED_USTRUCT_BODY() - - FString Name = TEXT(""); - bool bIsVDB = false; // replaces VolumeType Type; - - int32 TupleSize = -1; - bool bIsFloat = false; // replaces StorageType StorageType; - int32 TileSize = -1; - - FTransform Transform = FTransform::Identity; - bool bHasTaper = false; - - int32 XLength = -1; - int32 YLength = -1; - int32 ZLength = -1; - - int32 MinX = -1; - int32 MinY = -1; - int32 MinZ = -1; - - float XTaper = 0.0f; - float YTaper = 0.0f; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniCurveInfo -{ - GENERATED_USTRUCT_BODY() - - EHoudiniCurveType Type = EHoudiniCurveType::Invalid; - - int32 CurveCount = -1; - int32 VertexCount = -1; - int32 KnotCount = -1; - - bool bIsPeriodic = false; - bool bIsRational = false; - - int32 Order = -1; - - bool bHasKnots = false; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniMeshSocket -{ - GENERATED_USTRUCT_BODY() - - // Equality operator, used by containers - bool operator==(const FHoudiniMeshSocket& InSocket) const - { - return Transform.Equals(InSocket.Transform) - && Name == InSocket.Name - && Actor == InSocket.Actor - && Tag == InSocket.Tag; - } - - // Members - FTransform Transform = FTransform::Identity; - FString Name = TEXT("Socket"); - FString Actor = FString(); - FString Tag = FString(); -}; - -/* -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniSplitDataCache -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY() - FString SplitName; - //UPROPERTY() - //FHoudiniOutputObjectIdentifier SplitIdentifier; - //EHoudiniSplitType SplitType; - - UPROPERTY() - TArray Positions; - UPROPERTY() - TArray Indices; - - UPROPERTY() - TArray Normals; - UPROPERTY() - TArray Tangents; - UPROPERTY() - TArray Binormals; - - UPROPERTY() - TArray Colors; - - //UPROPERTY() - //TArray> UVs; - - //TArray EdgeHardnesses; - UPROPERTY() - TArray FaceSmoothingMasks; - UPROPERTY() - TArray LightMapResolutions; - - UPROPERTY() - TArray MaterialIndices; - UPROPERTY() - TArray Materials; - - UPROPERTY() - float lod_screensize; - - UPROPERTY() - FKAggregateGeom AggregateCollisions; -}; -*/ - - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject -{ -public: - - GENERATED_USTRUCT_BODY() - - FHoudiniGeoPartObject(); - - // Indicates if this HGPO is valid - bool IsValid() const; - - // Equality operator, used by containers - bool operator==(const FHoudiniGeoPartObject & GeoPartObject) const; - - // Checks equality, with the possibility to ignore the HGPO's splits - bool Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const; - - // Comparison based on object/part/split name. - bool CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const; - - void SetCustomPartName(const FString & InName); - - static FString HoudiniPartTypeToString(const EHoudiniPartType& InType); - -public: - - // NodeId of corresponding HAPI Asset. - UPROPERTY() - int32 AssetId; - - // Name of corresponding HDA. - UPROPERTY() - FString AssetName; - - // NodeId of corresponding HAPI Object. - UPROPERTY() - int32 ObjectId; - - // Name of associated object. - UPROPERTY() - FString ObjectName; - - // NodeId of corresponding HAPI Geo. - UPROPERTY() - int32 GeoId; - - // PartId of corresponding HAPI Part. - UPROPERTY() - int32 PartId; - - // Name of associated part. - UPROPERTY() - FString PartName; - - UPROPERTY() - bool bHasCustomPartName; - - /* - // Type of the split. - UPROPERTY() - EHoudiniSplitType SplitType; - - // Index of a split. In most cases this will be 0. - UPROPERTY() - int32 SplitIndex; - - // Name of group which was used for splitting, empty if there's none. - UPROPERTY() - FString SplitName; - */ - - // Split groups handled by this HGPO - UPROPERTY() - TArray SplitGroups; - - // Transform of this geo part object. - UPROPERTY() - FTransform TransformMatrix; - - // Path to the corresponding node - UPROPERTY() - FString NodePath; - - // Indicates the type of the referenced object - UPROPERTY() - EHoudiniPartType Type; - - // Indicates the type of instancer - UPROPERTY() - EHoudiniInstancerType InstancerType; - - // - UPROPERTY() - FString VolumeName; - - // - UPROPERTY() - int32 VolumeTileIndex; - - // Is set to true when referenced object is visible. - UPROPERTY() - bool bIsVisible; - - // Is set to true when referenced object is editable. - UPROPERTY() - bool bIsEditable; - - // Is set to true when referenced object is templated. - UPROPERTY() - bool bIsTemplated; - - // Is set to true when the referenced object is instanced. - UPROPERTY() - bool bIsInstanced; - - // Indicates the parent geo has changed and needs to be rebuilt - UPROPERTY() - bool bHasGeoChanged; - - // Indicates the part has changed and needs to be rebuilt - UPROPERTY() - bool bHasPartChanged; - - // Indicates only the transform needs to be updated - UPROPERTY() - bool bHasTransformChanged; - - // Indicates only the material needs to be updated - UPROPERTY() - bool bHasMaterialsChanged; - - // Indicates this object has been loaded - bool bLoaded; - - // We also keep a cache of the various info objects - // That we've extracted from HAPI - - // ObjectInfo cache - FHoudiniObjectInfo ObjectInfo; - // GeoInfo cache - FHoudiniGeoInfo GeoInfo; - // PartInfo cache - FHoudiniPartInfo PartInfo; - // VolumeInfo cache - FHoudiniVolumeInfo VolumeInfo; - // CurveInfo cache - FHoudiniCurveInfo CurveInfo; - - // Cache of this HGPO split data - //TArray SplitCache; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniGeoPartObject.generated.h" + +UENUM() +enum class EHoudiniGeoType : uint8 +{ + Invalid, + + Default, + Intermediate, + Input, + Curve +}; + +UENUM() +enum class EHoudiniPartType : uint8 +{ + Invalid, + + Mesh, + Instancer, + Curve, + Volume +}; + +UENUM() +enum class EHoudiniInstancerType : uint8 +{ + Invalid, + + ObjectInstancer, + PackedPrimitive, + AttributeInstancer, + OldSchoolAttributeInstancer +}; + +UENUM() +enum class EHoudiniCurveType : int8 +{ + Invalid = -1, + + Polygon = 0, + Nurbs = 1, + Bezier = 2, + Points = 3 +}; + +UENUM() +enum class EHoudiniCurveMethod : int8 +{ + Invalid = -1, + + CVs = 0, + Breakpoints = 1, + Freehand = 2 +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniObjectInfo +{ + GENERATED_USTRUCT_BODY() + + FString Name = TEXT(""); + + int32 NodeId = -1; + int32 ObjectToInstanceID = -1; + + bool bHasTransformChanged = false; + bool bHaveGeosChanged = false; + bool bIsVisible = false; + bool bIsInstancer = false; + bool bIsInstanced = false; + + int32 GeoCount = -1; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGeoInfo +{ + GENERATED_USTRUCT_BODY() + + EHoudiniGeoType Type = EHoudiniGeoType::Invalid; + FString Name = TEXT(""); + int32 NodeId = -1; + + bool bIsEditable = false; + bool bIsTemplated = false; + bool bIsDisplayGeo = false; + bool bHasGeoChanged = false; + bool bHasMaterialChanged = false; + + int32 PartCount = -1; + int32 PointGroupCount = -1; + int32 PrimitiveGroupCount = -1; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniPartInfo +{ + GENERATED_USTRUCT_BODY() + + int32 PartId = -1; + FString Name = TEXT(""); + + EHoudiniPartType Type = EHoudiniPartType::Invalid; + + int32 FaceCount = -1; + int32 VertexCount = -1; + int32 PointCount = -1; + + int32 PointAttributeCounts = -1; + int32 VertexAttributeCounts = -1; + int32 PrimitiveAttributeCounts = -1; + int32 DetailAttributeCounts = -1; + + bool bIsInstanced = false; + + int32 InstancedPartCount = -1; + int32 InstanceCount = -1; + + bool bHasChanged = false; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniVolumeInfo +{ + GENERATED_USTRUCT_BODY() + + FString Name = TEXT(""); + bool bIsVDB = false; // replaces VolumeType Type; + + int32 TupleSize = -1; + bool bIsFloat = false; // replaces StorageType StorageType; + int32 TileSize = -1; + + FTransform Transform = FTransform::Identity; + bool bHasTaper = false; + + int32 XLength = -1; + int32 YLength = -1; + int32 ZLength = -1; + + int32 MinX = -1; + int32 MinY = -1; + int32 MinZ = -1; + + float XTaper = 0.0f; + float YTaper = 0.0f; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniCurveInfo +{ + GENERATED_USTRUCT_BODY() + + EHoudiniCurveType Type = EHoudiniCurveType::Invalid; + + int32 CurveCount = -1; + int32 VertexCount = -1; + int32 KnotCount = -1; + + bool bIsPeriodic = false; + bool bIsRational = false; + + int32 Order = -1; + + bool bHasKnots = false; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniMeshSocket +{ + GENERATED_USTRUCT_BODY() + + // Equality operator, used by containers + bool operator==(const FHoudiniMeshSocket& InSocket) const + { + return Transform.Equals(InSocket.Transform) + && Name == InSocket.Name + && Actor == InSocket.Actor + && Tag == InSocket.Tag; + } + + // Members + FTransform Transform = FTransform::Identity; + FString Name = TEXT("Socket"); + FString Actor = FString(); + FString Tag = FString(); +}; + +/* +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniSplitDataCache +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY() + FString SplitName; + //UPROPERTY() + //FHoudiniOutputObjectIdentifier SplitIdentifier; + //EHoudiniSplitType SplitType; + + UPROPERTY() + TArray Positions; + UPROPERTY() + TArray Indices; + + UPROPERTY() + TArray Normals; + UPROPERTY() + TArray Tangents; + UPROPERTY() + TArray Binormals; + + UPROPERTY() + TArray Colors; + + //UPROPERTY() + //TArray> UVs; + + //TArray EdgeHardnesses; + UPROPERTY() + TArray FaceSmoothingMasks; + UPROPERTY() + TArray LightMapResolutions; + + UPROPERTY() + TArray MaterialIndices; + UPROPERTY() + TArray Materials; + + UPROPERTY() + float lod_screensize; + + UPROPERTY() + FKAggregateGeom AggregateCollisions; +}; +*/ + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject +{ +public: + + GENERATED_USTRUCT_BODY() + + FHoudiniGeoPartObject(); + + // Indicates if this HGPO is valid + bool IsValid() const; + + // Equality operator, used by containers + bool operator==(const FHoudiniGeoPartObject & GeoPartObject) const; + + // Checks equality, with the possibility to ignore the HGPO's splits + bool Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const; + + // Comparison based on object/part/split name. + bool CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const; + + void SetCustomPartName(const FString & InName); + + static FString HoudiniPartTypeToString(const EHoudiniPartType& InType); + +public: + + // NodeId of corresponding HAPI Asset. + UPROPERTY() + int32 AssetId; + + // Name of corresponding HDA. + UPROPERTY() + FString AssetName; + + // NodeId of corresponding HAPI Object. + UPROPERTY() + int32 ObjectId; + + // Name of associated object. + UPROPERTY() + FString ObjectName; + + // NodeId of corresponding HAPI Geo. + UPROPERTY() + int32 GeoId; + + // PartId of corresponding HAPI Part. + UPROPERTY() + int32 PartId; + + // Name of associated part. + UPROPERTY() + FString PartName; + + UPROPERTY() + bool bHasCustomPartName; + + /* + // Type of the split. + UPROPERTY() + EHoudiniSplitType SplitType; + + // Index of a split. In most cases this will be 0. + UPROPERTY() + int32 SplitIndex; + + // Name of group which was used for splitting, empty if there's none. + UPROPERTY() + FString SplitName; + */ + + // Split groups handled by this HGPO + UPROPERTY() + TArray SplitGroups; + + // Transform of this geo part object. + UPROPERTY() + FTransform TransformMatrix; + + // Path to the corresponding node + UPROPERTY() + FString NodePath; + + // Indicates the type of the referenced object + UPROPERTY() + EHoudiniPartType Type; + + // Indicates the type of instancer + UPROPERTY() + EHoudiniInstancerType InstancerType; + + // + UPROPERTY() + FString VolumeName; + + // + UPROPERTY() + int32 VolumeTileIndex; + + // Is set to true when referenced object is visible. + UPROPERTY() + bool bIsVisible; + + // Is set to true when referenced object is editable. + UPROPERTY() + bool bIsEditable; + + // Is set to true when referenced object is templated. + UPROPERTY() + bool bIsTemplated; + + // Is set to true when the referenced object is instanced. + UPROPERTY() + bool bIsInstanced; + + // Indicates the parent geo has changed and needs to be rebuilt + UPROPERTY() + bool bHasGeoChanged; + + // Indicates the part has changed and needs to be rebuilt + UPROPERTY() + bool bHasPartChanged; + + // Indicates only the transform needs to be updated + UPROPERTY() + bool bHasTransformChanged; + + // Indicates only the material needs to be updated + UPROPERTY() + bool bHasMaterialsChanged; + + // Indicates this object has been loaded + bool bLoaded; + + // We also keep a cache of the various info objects + // That we've extracted from HAPI + + // ObjectInfo cache + FHoudiniObjectInfo ObjectInfo; + // GeoInfo cache + FHoudiniGeoInfo GeoInfo; + // PartInfo cache + FHoudiniPartInfo PartInfo; + // VolumeInfo cache + FHoudiniVolumeInfo VolumeInfo; + // CurveInfo cache + FHoudiniCurveInfo CurveInfo; + + // Stores the Mesh Sockets found for a given HGPO + UPROPERTY() + TArray AllMeshSockets; + + // Cache of this HGPO split data + //TArray SplitCache; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp index 6d76a2c12..0ed795c0e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp @@ -1,255 +1,255 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleComponent.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniRuntimeSettings.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -void -UHoudiniHandleComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniHandleComponent_V1* CompatibilityHC = NewObject(); - CompatibilityHC->Serialize(Ar); - CompatibilityHC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : serialized data will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -UHoudiniHandleParameter::UHoudiniHandleParameter(const FObjectInitializer & ObjectInitializer) - :Super(ObjectInitializer) -{}; - -UHoudiniHandleComponent::UHoudiniHandleComponent(const FObjectInitializer & ObjectInitializer) - :Super(ObjectInitializer) -{}; - - -bool -UHoudiniHandleParameter::Bind(float & OutValue, const char * CmpName, int32 InTupleIdx, - const FString & HandleParmName, UHoudiniParameter* Parameter) -{ - if (!Parameter) - return false; - - if (HandleParmName != CmpName) - return false; - - UHoudiniParameterFloat* FloatParameter = Cast(Parameter); - - if (!FloatParameter) - return false; - - AssetParameter = Parameter; - - if (FloatParameter) - { - // It is possible that the handle param is bound to a single tuple param. - // Ignore the preset tuple index if that's the case or we'll crash. - if (Parameter->GetTupleSize() <= InTupleIdx) - InTupleIdx = 0; - - auto Optional = FloatParameter->GetValue(InTupleIdx); - if (Optional.IsSet()) - { - TupleIndex = InTupleIdx; - OutValue = Optional.GetValue(); - return true; - } - } - - return false; -} - -bool -UHoudiniHandleParameter::Bind(TSharedPtr & OutValue, const char * CmpName, - int32 InTupleIdx, const FString & HandleParmName, UHoudiniParameter* Parameter) -{ - if (!Parameter) - return false; - - if (HandleParmName != CmpName) - return false; - - UHoudiniParameterChoice* ChoiceParameter = Cast(Parameter); - - if (!ChoiceParameter) - return false; - - AssetParameter = Parameter; - - if (ChoiceParameter) - { - // It is possible that the handle param is bound to a single tuple param. - // Ignore the preset tuple index if that's the case or we'll crash. - if (Parameter->GetTupleSize() <= InTupleIdx) - InTupleIdx = 0; - - auto Optional = ChoiceParameter->GetValue(InTupleIdx); - if (Optional.IsSet()) - { - TupleIndex = InTupleIdx; - OutValue = Optional.GetValue(); - return true; - } - } - - return false; -} - -TSharedPtr -UHoudiniHandleParameter::Get(TSharedPtr DefaultValue) const -{ - UHoudiniParameterChoice* ChoiceParameter = Cast(AssetParameter); - if (ChoiceParameter) - { - auto Optional = ChoiceParameter->GetValue(TupleIndex); - if (Optional.IsSet()) - return Optional.GetValue(); - } - - return DefaultValue; -} - -UHoudiniHandleParameter & -UHoudiniHandleParameter::operator=(float Value) -{ - UHoudiniParameterFloat* FloatParameter = Cast(AssetParameter); - if (FloatParameter) - { - FloatParameter->SetValue(Value, TupleIndex); - FloatParameter->MarkChanged(true); - } - - return *this; -} - -void -UHoudiniHandleComponent::InitializeHandleParameters() -{ - if (XformParms.Num() < int32(EXformParameter::COUNT)) - { - XformParms.Empty(); - for (int32 n = 0; n < int32(EXformParameter::COUNT); ++n) - { - UHoudiniHandleParameter* XformHandle = NewObject(this, UHoudiniHandleParameter::StaticClass()); - XformParms.Add(XformHandle); - } - } - - if (!RSTParm) - { - RSTParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); - } - - if (!RotOrderParm) - { - RotOrderParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); - } -} - -bool -UHoudiniHandleComponent::CheckHandleValid() const -{ - if (XformParms.Num() < int32(EXformParameter::COUNT)) - return false; - - for (auto& XformParm : XformParms) - { - if (!XformParm) - return false; - } - - if (!RSTParm) - return false; - - if (!RotOrderParm) - return false; - - return true; -} - -FBox -UHoudiniHandleComponent::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - return BoxBounds + GetComponentLocation(); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniRuntimeSettings.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +void +UHoudiniHandleComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniHandleComponent_V1* CompatibilityHC = NewObject(); + CompatibilityHC->Serialize(Ar); + CompatibilityHC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : serialized data will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +UHoudiniHandleParameter::UHoudiniHandleParameter(const FObjectInitializer & ObjectInitializer) + :Super(ObjectInitializer) +{}; + +UHoudiniHandleComponent::UHoudiniHandleComponent(const FObjectInitializer & ObjectInitializer) + :Super(ObjectInitializer) +{}; + + +bool +UHoudiniHandleParameter::Bind(float & OutValue, const char * CmpName, int32 InTupleIdx, + const FString & HandleParmName, UHoudiniParameter* Parameter) +{ + if (!Parameter) + return false; + + if (HandleParmName != CmpName) + return false; + + UHoudiniParameterFloat* FloatParameter = Cast(Parameter); + + if (!FloatParameter) + return false; + + AssetParameter = Parameter; + + if (FloatParameter) + { + // It is possible that the handle param is bound to a single tuple param. + // Ignore the preset tuple index if that's the case or we'll crash. + if (Parameter->GetTupleSize() <= InTupleIdx) + InTupleIdx = 0; + + auto Optional = FloatParameter->GetValue(InTupleIdx); + if (Optional.IsSet()) + { + TupleIndex = InTupleIdx; + OutValue = Optional.GetValue(); + return true; + } + } + + return false; +} + +bool +UHoudiniHandleParameter::Bind(TSharedPtr & OutValue, const char * CmpName, + int32 InTupleIdx, const FString & HandleParmName, UHoudiniParameter* Parameter) +{ + if (!Parameter) + return false; + + if (HandleParmName != CmpName) + return false; + + UHoudiniParameterChoice* ChoiceParameter = Cast(Parameter); + + if (!ChoiceParameter) + return false; + + AssetParameter = Parameter; + + if (ChoiceParameter) + { + // It is possible that the handle param is bound to a single tuple param. + // Ignore the preset tuple index if that's the case or we'll crash. + if (Parameter->GetTupleSize() <= InTupleIdx) + InTupleIdx = 0; + + auto Optional = ChoiceParameter->GetValue(InTupleIdx); + if (Optional.IsSet()) + { + TupleIndex = InTupleIdx; + OutValue = Optional.GetValue(); + return true; + } + } + + return false; +} + +TSharedPtr +UHoudiniHandleParameter::Get(TSharedPtr DefaultValue) const +{ + UHoudiniParameterChoice* ChoiceParameter = Cast(AssetParameter); + if (ChoiceParameter) + { + auto Optional = ChoiceParameter->GetValue(TupleIndex); + if (Optional.IsSet()) + return Optional.GetValue(); + } + + return DefaultValue; +} + +UHoudiniHandleParameter & +UHoudiniHandleParameter::operator=(float Value) +{ + UHoudiniParameterFloat* FloatParameter = Cast(AssetParameter); + if (FloatParameter) + { + FloatParameter->SetValue(Value, TupleIndex); + FloatParameter->MarkChanged(true); + } + + return *this; +} + +void +UHoudiniHandleComponent::InitializeHandleParameters() +{ + if (XformParms.Num() < int32(EXformParameter::COUNT)) + { + XformParms.Empty(); + for (int32 n = 0; n < int32(EXformParameter::COUNT); ++n) + { + UHoudiniHandleParameter* XformHandle = NewObject(this, UHoudiniHandleParameter::StaticClass()); + XformParms.Add(XformHandle); + } + } + + if (!RSTParm) + { + RSTParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); + } + + if (!RotOrderParm) + { + RotOrderParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); + } +} + +bool +UHoudiniHandleComponent::CheckHandleValid() const +{ + if (XformParms.Num() < int32(EXformParameter::COUNT)) + return false; + + for (auto& XformParm : XformParms) + { + if (!XformParm) + return false; + } + + if (!RSTParm) + return false; + + if (!RotOrderParm) + return false; + + return true; +} + +FBox +UHoudiniHandleComponent::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + return BoxBounds + GetComponentLocation(); +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h index d422e95ea..62ea88ca9 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h @@ -1,135 +1,135 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" - -#include "HoudiniHandleComponent.generated.h" - -class UHoudiniParameter; - -UENUM() -enum class EXformParameter : uint8 -{ - TX, TY, TZ, - RX, RY, RZ, - SX, SY, SZ, - COUNT -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniHandleParameter : public UObject -{ -public: - GENERATED_UCLASS_BODY() - - UPROPERTY() - UHoudiniParameter* AssetParameter; - - UPROPERTY() - int32 TupleIndex; - - - bool Bind( - float & OutValue, - const char * CmpName, - int32 InTupleIdx, - const FString & HandleParmName, - UHoudiniParameter* Parameter); - - bool Bind( - TSharedPtr & OutValue, - const char * CmpName, - int32 InTupleIdx, - const FString & HandleParmName, - UHoudiniParameter* Parameter); - - TSharedPtr Get(TSharedPtr DefaultValue) const; - - UHoudiniHandleParameter & operator=(float Value); - -}; - -UENUM() -enum class EHoudiniHandleType : uint8 -{ - Xform, - Bounder, - Unsupported -}; - -UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) -class HOUDINIENGINERUNTIME_API UHoudiniHandleComponent : public USceneComponent -{ -public: - - friend class UHoudiniAssetComponent; - - friend class FHoudiniHandleComponentVisualizer; - - GENERATED_UCLASS_BODY() - - virtual void Serialize(FArchive & Ar) override; - - FString GetHandleName() const { return HandleName; }; - EHoudiniHandleType GetHandleType() const { return HandleType; }; - - void SetHandleName(const FString& InHandleName) { HandleName = InHandleName; }; - void SetHandleType(const EHoudiniHandleType& InHandleType) { HandleType = InHandleType; }; - - // Equality, consider two handle equals if they have the same name, type, tuple size and disabled status - bool operator==(const UHoudiniHandleComponent& other) const - { - return (HandleType == other.HandleType && HandleName.Equals(other.HandleName)); - } - - bool Matches(const UHoudiniHandleComponent& other) const { return (*this == other); }; - - void InitializeHandleParameters(); - - bool CheckHandleValid() const; - - FBox GetBounds() const; - -public: - UPROPERTY() - TArray XformParms; - - UPROPERTY() - UHoudiniHandleParameter* RSTParm; - - UPROPERTY() - UHoudiniHandleParameter* RotOrderParm; - -private: - UPROPERTY() - EHoudiniHandleType HandleType; - - UPROPERTY() - FString HandleName; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" + +#include "HoudiniHandleComponent.generated.h" + +class UHoudiniParameter; + +UENUM() +enum class EXformParameter : uint8 +{ + TX, TY, TZ, + RX, RY, RZ, + SX, SY, SZ, + COUNT +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniHandleParameter : public UObject +{ +public: + GENERATED_UCLASS_BODY() + + UPROPERTY() + UHoudiniParameter* AssetParameter; + + UPROPERTY() + int32 TupleIndex; + + + bool Bind( + float & OutValue, + const char * CmpName, + int32 InTupleIdx, + const FString & HandleParmName, + UHoudiniParameter* Parameter); + + bool Bind( + TSharedPtr & OutValue, + const char * CmpName, + int32 InTupleIdx, + const FString & HandleParmName, + UHoudiniParameter* Parameter); + + TSharedPtr Get(TSharedPtr DefaultValue) const; + + UHoudiniHandleParameter & operator=(float Value); + +}; + +UENUM() +enum class EHoudiniHandleType : uint8 +{ + Xform, + Bounder, + Unsupported +}; + +UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) +class HOUDINIENGINERUNTIME_API UHoudiniHandleComponent : public USceneComponent +{ +public: + + friend class UHoudiniAssetComponent; + + friend class FHoudiniHandleComponentVisualizer; + + GENERATED_UCLASS_BODY() + + virtual void Serialize(FArchive & Ar) override; + + FString GetHandleName() const { return HandleName; }; + EHoudiniHandleType GetHandleType() const { return HandleType; }; + + void SetHandleName(const FString& InHandleName) { HandleName = InHandleName; }; + void SetHandleType(const EHoudiniHandleType& InHandleType) { HandleType = InHandleType; }; + + // Equality, consider two handle equals if they have the same name, type, tuple size and disabled status + bool operator==(const UHoudiniHandleComponent& other) const + { + return (HandleType == other.HandleType && HandleName.Equals(other.HandleName)); + } + + bool Matches(const UHoudiniHandleComponent& other) const { return (*this == other); }; + + void InitializeHandleParameters(); + + bool CheckHandleValid() const; + + FBox GetBounds() const; + +public: + UPROPERTY() + TArray XformParms; + + UPROPERTY() + UHoudiniHandleParameter* RSTParm; + + UPROPERTY() + UHoudiniHandleParameter* RotOrderParm; + +private: + UPROPERTY() + EHoudiniHandleType HandleType; + + UPROPERTY() + FString HandleName; + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp index 3276ed190..0623ac747 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp @@ -1,2600 +1,2606 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInput.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutput.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniAsset.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetBlueprintComponent.h" - -#include "EngineUtils.h" -#include "Engine/Brush.h" -#include "Engine/Engine.h" -#include "Engine/DataTable.h" -#include "Model.h" -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "UObject/UObjectGlobals.h" -#include "FoliageType_InstancedStaticMesh.h" - -#include "Components/SplineComponent.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Landscape.h" - -#if WITH_EDITOR - -#include "Kismet2/BlueprintEditorUtils.h" -#include "Kismet2/KismetEditorUtilities.h" - -#endif - -// -UHoudiniInput::UHoudiniInput() - : Type(EHoudiniInputType::Invalid) - , PreviousType(EHoudiniInputType::Invalid) - , AssetNodeId(-1) - , InputNodeId(-1) - , InputIndex(0) - , ParmId(-1) - , bIsObjectPathParameter(false) - , bHasChanged(false) - , bPackBeforeMerge(false) - , bExportLODs(false) - , bExportSockets(false) - , bExportColliders(false) - , bCookOnCurveChanged(true) - , bStaticMeshChanged(false) - , bInputAssetConnectedInHoudini(false) - , DefaultCurveOffset(0.f) - , bAddRotAndScaleAttributesOnCurves(false) - , bIsWorldInputBoundSelector(false) - , bWorldInputBoundSelectorAutoUpdate(false) - , UnrealSplineResolution(50.0f) - , bUpdateInputLandscape(false) - , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) - , bLandscapeExportSelectionOnly(false) - , bLandscapeAutoSelectComponent(false) - , bLandscapeExportMaterials(false) - , bLandscapeExportLighting(false) - , bLandscapeExportNormalizedUVs(false) - , bLandscapeExportTileUVs(false) -{ - Name = TEXT(""); - Label = TEXT(""); - SetFlags(RF_Transactional); - - // Geometry inputs always have one null default object - GeometryInputObjects.Add(nullptr); - - KeepWorldTransform = GetDefaultXTransformType(); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; - - bAddRotAndScaleAttributesOnCurves = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bAddRotAndScaleAttributesOnCurves : false; -} - -void -UHoudiniInput::BeginDestroy() -{ - InvalidateData(); - - // DO NOT MANUALLY DESTROY OUR INPUT OBJECTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - - // Mark all our input objects for destruction - ForAllHoudiniInputObjectArrays([](TArray& ObjectArray) { - ObjectArray.Empty(); - }); - - Super::BeginDestroy(); -} - -#if WITH_EDITOR -void UHoudiniInput::PostEditUndo() -{ - Super::PostEditUndo(); - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!InputObjectsPtr) - return; - - MarkChanged(true); - bool bBlueprintStructureChanged = false; - - if (HasInputTypeChanged()) - { - // If the input type has changed on undo, previousType becomes new type - /* This does not work properly, see the corresponding part in FHoudiniInputDetails::AddInputTypeComboBox(...), after Transaction(... - ) - { - EHoudiniInputType NewType = PreviousType; - SetInputType(NewType); - } - */ - EHoudiniInputType Temp = Type; - Type = PreviousType; - PreviousType = EHoudiniInputType::Invalid; - - // If the undo action caused input type changing, treat it as a regular type changing - // after set up the new and prev types properly - SetInputType(Temp, bBlueprintStructureChanged); - } - else - { - if (Type == EHoudiniInputType::Asset) - { - // Mark the input asset object as changed, since only undo changing asset will get into here. - // The input array will be empty when undo adding asset (only support single asset input object in an input now) - for (auto & NextAssetInputObj : *InputObjectsPtr) - { - if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) - continue; - - NextAssetInputObj->MarkChanged(true); - } - } - - - if (Type == EHoudiniInputType::World) - { - if (WorldInputObjects.Num() == 0 && InputNodeId >= 0) - { - for (auto & NextNodeId : CreatedDataNodeIds) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NextNodeId, true); - } - - CreatedDataNodeIds.Empty(); - - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - InputNodeId = -1; - } - } - - if (Type == EHoudiniInputType::Curve) - { - if (PreviousType != EHoudiniInputType::Curve) - { - for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); - if (!SplineInput || SplineInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - USceneComponent* OuterComponent = Cast(GetOuter()); - - // Attach the new Houdini spline component to it's owner - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - HoudiniSplineComponent->MarkChanged(true); - } - return; - } - bool bUndoDelete = false; - bool bUndoInsert = false; - bool bUndoDeletedObjArrayEmptied = false; - - TArray< USceneComponent* > childActor; - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) - childActor = OuterHAC->GetAttachChildren(); - - // Undo delete input objects action - for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) - { - UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; - if (!InputObject || InputObject->IsPendingKill()) - continue; - - UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); - - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) - continue; - - UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - // If the last change deleted this curve input, recreate this Houdini Spline input. - if (!SplineComponent->GetAttachParent()) - { - bUndoDelete = true; - - if (!bUndoDeletedObjArrayEmptied) - LastUndoDeletedInputs.Empty(); - - bUndoDeletedObjArrayEmptied = true; - - UHoudiniSplineComponent * ReconstructedSpline = NewObject( - GetOuter(), UHoudiniSplineComponent::StaticClass()); - - if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) - continue; - - ReconstructedSpline->SetFlags(RF_Transactional); - ReconstructedSpline->CopyHoudiniData(SplineComponent); - - UHoudiniInputObject * ReconstructedInputObject = UHoudiniInputHoudiniSplineComponent::Create( - ReconstructedSpline, GetOuter(), ReconstructedSpline->GetHoudiniSplineName()); - UHoudiniInputHoudiniSplineComponent *ReconstructedHoudiniSplineInput = (UHoudiniInputHoudiniSplineComponent*)ReconstructedInputObject; - (*InputObjectsPtr)[Index] = ReconstructedHoudiniSplineInput; - - ReconstructedSpline->RegisterComponent(); - ReconstructedSpline->SetFlags(RF_Transactional); - - CreateHoudiniSplineInput(ReconstructedHoudiniSplineInput, true, true, bBlueprintStructureChanged); - - // Cast the reconstructed Houdini Spline Input to a generic HoudiniInput object. - UHoudiniInputObject * ReconstructedHoudiniInput = Cast(ReconstructedHoudiniSplineInput); - - LastUndoDeletedInputs.Add(ReconstructedHoudiniInput); - // Reset the LastInsertedInputsArray for redoing this undo action. - } - } - - if (bUndoDelete) - return; - - // Undo insert input objects action - for (int Index = 0; Index < LastInsertedInputs.Num(); ++Index) - { - bUndoInsert = true; - UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - HoudiniSplineComponent->DestroyComponent(); - } - - if (bUndoInsert) - return; - - for (int Index = 0; Index < LastUndoDeletedInputs.Num(); ++Index) - { - UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; - - UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) - continue; - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - - HoudiniSplineComponent->DestroyComponent(); - } - } - } - - if (bBlueprintStructureChanged) - { - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); - } - -} -#endif - - -FBox -UHoudiniInput::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - - switch (Type) - { - case EHoudiniInputType::Curve: - { - for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) - { - const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); - if (!CurInCurve || CurInCurve->IsPendingKill()) - continue; - - UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); - if (!CurCurve || CurCurve->IsPendingKill()) - continue; - - FBox CurCurveBound(ForceInitToZero); - for (auto & Trans : CurCurve->CurvePoints) - { - CurCurveBound += Trans.GetLocation(); - } - - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - - if (OuterHAC && !OuterHAC->IsPendingKill()) - BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); - } - } - break; - - case EHoudiniInputType::Asset: - { - for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) - { - UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); - if (!CurInAsset || CurInAsset->IsPendingKill()) - continue; - - UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) - continue; - - BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); - } - } - break; - - case EHoudiniInputType::World: - { - for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) - { - UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); - if (CurInActor && !CurInActor->IsPendingKill()) - { - AActor* Actor = CurInActor->GetActor(); - if (!Actor || Actor->IsPendingKill()) - continue; - - FVector Origin, Extent; - Actor->GetActorBounds(false, Origin, Extent); - - BoxBounds += FBox::BuildAABB(Origin, Extent); - } - else - { - // World Input now also support HoudiniAssets - UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); - if (CurInAsset && !CurInAsset->IsPendingKill()) - { - UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) - continue; - - BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); - continue; - } - } - } - } - break; - - case EHoudiniInputType::Landscape: - { - for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) - { - UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); - if (!CurInLandscape || CurInLandscape->IsPendingKill()) - continue; - - ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); - if (!CurLandscape || CurLandscape->IsPendingKill()) - continue; - - FVector Origin, Extent; - CurLandscape->GetActorBounds(false, Origin, Extent); - - BoxBounds += FBox::BuildAABB(Origin, Extent); - } - } - break; - - case EHoudiniInputType::Skeletal: - case EHoudiniInputType::Invalid: - default: - break; - } - - return BoxBounds; -} - -FString -UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) -{ - FString InputTypeStr; - switch (InInputType) - { - case EHoudiniInputType::Geometry: - { - InputTypeStr = TEXT("Geometry Input"); - } - break; - - case EHoudiniInputType::Asset: - { - InputTypeStr = TEXT("Asset Input"); - } - break; - - case EHoudiniInputType::Curve: - { - InputTypeStr = TEXT("Curve Input"); - } - break; - - case EHoudiniInputType::Landscape: - { - InputTypeStr = TEXT("Landscape Input"); - } - break; - - case EHoudiniInputType::World: - { - InputTypeStr = TEXT("World Outliner Input"); - } - break; - - case EHoudiniInputType::Skeletal: - { - InputTypeStr = TEXT("Skeletal Mesh Input"); - } - break; - } - - return InputTypeStr; -} - - -EHoudiniInputType -UHoudiniInput::StringToInputType(const FString& InInputTypeString) -{ - if (InInputTypeString.StartsWith(TEXT("Geometry"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Geometry; - } - else if (InInputTypeString.StartsWith(TEXT("Asset"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Asset; - } - else if (InInputTypeString.StartsWith(TEXT("Curve"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Curve; - } - else if (InInputTypeString.StartsWith(TEXT("Landscape"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Landscape; - } - else if (InInputTypeString.StartsWith(TEXT("World"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::World; - } - else if (InInputTypeString.StartsWith(TEXT("Skeletal"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Skeletal; - } - - return EHoudiniInputType::Invalid; -} - - -EHoudiniCurveType UHoudiniInput::StringToHoudiniCurveType(const FString& HoudiniCurveTypeString) -{ - if (HoudiniCurveTypeString.StartsWith(TEXT("Polygon"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Polygon; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Nurbs"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Nurbs; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Bezier"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Bezier; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Points"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Points; - } - - return EHoudiniCurveType::Invalid; -} - -EHoudiniCurveMethod UHoudiniInput::StringToHoudiniCurveMethod(const FString& HoudiniCurveMethodString) -{ - if (HoudiniCurveMethodString.StartsWith(TEXT("CVs"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::CVs; - } - else if (HoudiniCurveMethodString.StartsWith(TEXT("Breakpoints"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::Breakpoints; - } - - else if (HoudiniCurveMethodString.StartsWith(TEXT("Freehand"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::Freehand; - } - - return EHoudiniCurveMethod::Invalid; - -} - -// -void -UHoudiniInput::SetSOPInput(const int32& InInputIndex) -{ - // Set the input index - InputIndex = InInputIndex; - - // Invalidate objpath parameter - ParmId = -1; - bIsObjectPathParameter = false; -} - -void -UHoudiniInput::SetObjectPathParameter(const int32& InParmId) -{ - // Set as objpath parameter - ParmId = InParmId; - bIsObjectPathParameter = true; - - // Invalidate the geo input - InputIndex = -1; -} - -EHoudiniXformType -UHoudiniInput::GetDefaultXTransformType() -{ - switch (Type) - { - case EHoudiniInputType::Curve: - case EHoudiniInputType::Geometry: - case EHoudiniInputType::Skeletal: - return EHoudiniXformType::None; - case EHoudiniInputType::Asset: - case EHoudiniInputType::Landscape: - case EHoudiniInputType::World: - return EHoudiniXformType::IntoThisObject; - } - - return EHoudiniXformType::Auto; -} - -bool -UHoudiniInput::GetKeepWorldTransform() const -{ - bool bReturn = false; - switch (KeepWorldTransform) - { - case EHoudiniXformType::Auto: - { - // Return default values corresponding to the input type: - if (Type == EHoudiniInputType::Curve - || Type == EHoudiniInputType::Geometry - || Type == EHoudiniInputType::Skeletal ) - { - // NONE for Geo, Curve and skeletal mesh IN - bReturn = false; - } - else - { - // INTO THIS OBJECT for Asset, Landscape and World IN - bReturn = true; - } - break; - } - - case EHoudiniXformType::None: - { - bReturn = false; - break; - } - - case EHoudiniXformType::IntoThisObject: - { - bReturn = true; - break; - } - } - - return bReturn; -} - -void -UHoudiniInput::SetKeepWorldTransform(const bool& bInKeepWorldTransform) -{ - if (bInKeepWorldTransform) - { - KeepWorldTransform = EHoudiniXformType::IntoThisObject; - } - else - { - KeepWorldTransform = EHoudiniXformType::None; - } -} - -void -UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlueprintStructureModified) -{ - if (InInputType == Type) - return; - - SetPreviousInputType(Type); - - // Mark this input as changed - MarkChanged(true); - bOutBlueprintStructureModified = true; - - // Check previous input type - switch (PreviousType) - { - case EHoudiniInputType::Asset: - { - break; - } - - case EHoudiniInputType::Curve: - { - // detach the input curves from the asset component - if (GetNumberOfInputObjects() > 0) - { - for (UHoudiniInputObject * CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); - - if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); - - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - HoudiniSplineComponent->Modify(); - - const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); - - if (bIsArchetype) - { -#if WITH_EDITOR - check(HoudiniSplineComponent->IsTemplate()); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); -#endif - } - else - { - AActor* OwningActor = HoudiniSplineComponent->GetOwner(); - check(OwningActor); - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - HoudiniSplineComponent->SetVisibility(false, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(false); - HoudiniSplineComponent->SetHiddenInGame(true, true); - - // This NodeId shouldn't be invalidated like this. If a spline component - // or curve input is no longer valid, the input object should be removed from the HoudinInput - // to get cleaned up properly. - // HoudiniSplineComponent->SetNodeId(-1); - HoudiniSplineComponent->MarkChanged(true); - } - - bOutBlueprintStructureModified = true; - } - } - break; - } - - case EHoudiniInputType::Geometry: - { - break; - } - - case EHoudiniInputType::Landscape: - { - TArray* InputObjectsArray = GetHoudiniInputObjectArray(PreviousType); - - if (!InputObjectsArray) - break; - - for (int32 Idx = 0; Idx < InputObjectsArray->Num(); ++Idx) - { - UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; - - if (!InputObj || InputObj->IsPendingKill()) - continue; - - UHoudiniInputLandscape* InputLandscape = Cast(InputObj); - - if (!InputLandscape || InputLandscape->IsPendingKill()) - continue; - - // do something? - } - - break; - } - - case EHoudiniInputType::Skeletal: - { - break; - } - - case EHoudiniInputType::World: - { - break; - } - - default: - break; - } - - - Type = InInputType; - - // TODO: NOPE, not needed - // Set keep world transform to default w.r.t to new input type. - //KeepWorldTransform = GetDefaultXTransformType(); - - // Check current input type - switch (InInputType) - { - case EHoudiniInputType::World: - case EHoudiniInputType::Asset: - { - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !bImportAsReference) - { - for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) - continue; - - UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) - continue; - - CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); - } - } - } - break; - - case EHoudiniInputType::Curve: - { - if (GetNumberOfInputObjects() == 0) - { - CreateNewCurveInputObject(bOutBlueprintStructureModified); - MarkChanged(true); - } - else - { - for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(CurrentInput); - if (!IsValid(SplineInput)) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); - if (!IsValid(HoudiniSplineComponent)) - continue; - - HoudiniSplineComponent->Modify(); - - const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); - - if (bIsArchetype) - { -#if WITH_EDITOR - check(HoudiniSplineComponent->IsTemplate()); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); -#endif - } - else - { - // Attach the new Houdini spline component to it's owner - AActor* OwningActor = HoudiniSplineComponent->GetOwner(); - check(OwningActor); - USceneComponent* OuterComponent = Cast(GetOuter()); - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->MarkChanged(true); - - } - - bOutBlueprintStructureModified = true; - } - } - } - break; - - case EHoudiniInputType::Geometry: - { - - } - break; - - case EHoudiniInputType::Landscape: - { - // Need to do anything on select? - } - break; - - case EHoudiniInputType::Skeletal: - { - } - break; - - default: - { - } - break; - } -} - -UHoudiniInputObject* -UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) -{ - if (CurveInputObjects.Num() > 0) - return nullptr; - - UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); - if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) - return nullptr; - - UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - // Default Houdini spline component input should not be visible at initialization - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - - CurveInputObjects.Add(NewCurveInputObject); - SetInputObjectsNumber(EHoudiniInputType::Curve, 1); - CurveInputObjects.SetNum(1); - - return NewCurveInputObject; -} - -void -UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) -{ - MarkDataUploadNeeded(bInChanged); - - // Mark all the objects from this input has changed so they upload themselves - - TSet InputTypes; - InputTypes.Add(Type); - InputTypes.Add(EHoudiniInputType::Curve); - - TArray* NewInputObjects = GetHoudiniInputObjectArray(Type); - if (NewInputObjects) - { - for (auto CurInputObject : *NewInputObjects) - { - if (CurInputObject && !CurInputObject->IsPendingKill()) - CurInputObject->MarkChanged(bInChanged); - } - } -} - -UHoudiniInput * UHoudiniInput::DuplicateAndCopyState(UObject * DestOuter, bool bInCanDeleteHoudiniNodes) -{ - UHoudiniInput* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); - - NewInput->CopyStateFrom(this, false, bInCanDeleteHoudiniNodes); - - return NewInput; -} - -void UHoudiniInput::CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes) -{ - - // Preserve the current input objects before the copy to ensure we don't lose - // access to input objects and have them end up in the garbage. - - TMap*> PrevInputObjectsMap; - - for(EHoudiniInputType InputType : HoudiniInputTypeList) - { - PrevInputObjectsMap.Add(InputType, GetHoudiniInputObjectArray(InputType)); - } - - // TArray PrevInputObjects; - // TArray* OldToInputObjects = GetHoudiniInputObjectArray(Type); - // if (OldToInputObjects) - // PrevInputObjects = *OldToInputObjects; - - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - } - - AssetNodeId = InInput->AssetNodeId; - InputNodeId = InInput->InputNodeId; - ParmId = InInput->ParmId; - bCanDeleteHoudiniNodes = bInCanDeleteHoudiniNodes; - - //if (bInCanDeleteHoudiniNodes) - //{ - // // Delete stale data nodes before they get overwritten. - // TSet NewNodeIds(InInput->CreatedDataNodeIds); - // for (int32 NodeId : CreatedDataNodeIds) - // { - // if (!NewNodeIds.Contains(NodeId)) - // { - // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); - // } - // } - //} - - CreatedDataNodeIds = InInput->CreatedDataNodeIds; - - // Important note: At this point the new object may still share objects with InInput. - // The CopyInputs() will properly duplicate inputs where necessary. - - // Copy states of Input Objects that correspond to the current type. - - for(auto& Entry : PrevInputObjectsMap) - { - EHoudiniInputType InputType = Entry.Key; - TArray* PrevInputObjects = Entry.Value; - TArray* ToInputObjects = GetHoudiniInputObjectArray(InputType); - TArray* FromInputObjects = InInput->GetHoudiniInputObjectArray(InputType); - - if (ToInputObjects && FromInputObjects) - { - *ToInputObjects = *PrevInputObjects; - CopyInputs(*ToInputObjects, *FromInputObjects, bInCanDeleteHoudiniNodes); - } - } - -} - -void UHoudiniInput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -void UHoudiniInput::InvalidateData() -{ - // If valid, mark our input node for deletion - if (InputNodeId >= 0) - { - // .. but if we're an asset input, don't delete the node as InputNodeId - // is set to the input HDA's node ID! - if (Type != EHoudiniInputType::Asset) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - } - - InputNodeId = -1; - } - - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - if (!InputObject) - continue; - - if (InputObject->IsA()) - { - // When the input object is a HoudiniAssetComponent, - // we need to be sure that this HDA node id is not in CreatedDataNodeIds - // We dont want to delete the input HDA node! - CreatedDataNodeIds.Remove(InputObject->InputNodeId); - } - - InputObject->InvalidateData(); - } - - if (bCanDeleteHoudiniNodes) - { - auto& HoudiniEngineRuntime = FHoudiniEngineRuntime::Get(); - for(int32 NodeId : CreatedDataNodeIds) - { - HoudiniEngineRuntime.MarkNodeIdAsPendingDelete(NodeId, true); - } - } - - CreatedDataNodeIds.Empty(); -} - -void UHoudiniInput::CopyInputs(TArray& ToInputObjects, TArray& FromInputObjects, bool bInCanDeleteHoudiniNodes) -{ - TSet StaleObjects(ToInputObjects); - - const int32 NumInputs = FromInputObjects.Num(); - UObject* TargetOuter = GetOuter(); - - ToInputObjects.SetNum(NumInputs); - - - for (int i = 0; i < NumInputs; i++) - { - UHoudiniInputObject* FromObject = FromInputObjects[i]; - UHoudiniInputObject* ToObject = ToInputObjects[i]; - - if (!FromObject) - { - ToInputObjects[i] = nullptr; - continue; - } - - if (ToObject) - { - bool IsValid = true; - // Is ToInput and FromInput the same or do we have to create a input object? - IsValid = IsValid && ToObject->Matches(*FromObject); - IsValid = IsValid && ToObject->GetOuter() == TargetOuter; - - if (!IsValid) - { - ToObject = nullptr; - } - } - - if (ToObject) - { - // We have an existing (matching) object. Copy the - // state from the incoming input. - StaleObjects.Remove(ToObject); - ToObject->CopyStateFrom(FromObject, true); - } - else - { - // We need to create a new input here. - ToObject = FromObject->DuplicateAndCopyState(TargetOuter); - ToInputObjects[i] = ToObject; - } - - ToObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); - } - - - for (UHoudiniInputObject* StaleInputObject : StaleObjects) - { - if (!StaleInputObject) - continue; - if (StaleInputObject->GetOuter() == this) - { - StaleInputObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); - } - } -} - - -UHoudiniInputHoudiniSplineComponent* -UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * FromHoudiniSplineInputComponent, const bool & bAttachToparent, const bool & bAppendToInputArray, bool& bOutBlueprintStructureModified) -{ - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = nullptr; - UHoudiniSplineComponent* HoudiniSplineComponent = nullptr; - - UObject* OuterObj = GetOuter(); - USceneComponent* OuterComp = Cast(GetOuter()); - bool bOuterIsTemplate = (OuterObj && OuterObj->IsTemplate()); - - if (!FromHoudiniSplineInputComponent) - { - // NOTE: If we're inside the Blueprint editor, the outer here is going to the be HAC component template. - check(OuterObj) - - // Create a default Houdini spline input if a null pointer is passed in. - FName HoudiniSplineName = MakeUniqueObjectName(OuterComp, UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); - - // Create a Houdini Input Object. - UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( - nullptr, OuterObj, HoudiniSplineName.ToString()); - - if (!NewInputObject || NewInputObject->IsPendingKill()) - return nullptr; - - HoudiniSplineInput = Cast(NewInputObject); - if (!HoudiniSplineInput) - return nullptr; - - HoudiniSplineComponent = NewObject( - HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - HoudiniSplineInput->Update(HoudiniSplineComponent); - - HoudiniSplineComponent->SetHoudiniSplineName(HoudiniSplineName.ToString()); - HoudiniSplineComponent->SetFlags(RF_Transactional); - - // Set the default position of curve to avoid overlapping. - HoudiniSplineComponent->SetOffset(DefaultCurveOffset); - DefaultCurveOffset += 100.f; - - if (!bOuterIsTemplate) - { - HoudiniSplineComponent->RegisterComponent(); - - // Attach the new Houdini spline component to it's owner. - if (bAttachToparent) - HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); - } - - //push the new input object to the array for new type. - if (bAppendToInputArray && Type == EHoudiniInputType::Curve) - GetHoudiniInputObjectArray(Type)->Add(NewInputObject); - -#if WITH_EDITOR - if (bOuterIsTemplate) - { - UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); - if (HAB) - { - UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (Blueprint) - { - TArray Components; - Components.Add(HoudiniSplineComponent); - - USCS_Node* HABNode = HAB->FindSCSNodeForTemplateComponentInClassHierarchy(HAB); - - // NOTE: FAddComponentsToBlueprintParams was introduced in 4.26 so for the sake of - // backwards compatibility, manually determine which SCSNode was added instead of - // relying on Params.OutNodes. - FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - Params.OptionalNewRootNode = HABNode; - const TSet PreviousSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); - - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, Params); - USCS_Node* NewNode = nullptr; - const TSet CurrentSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); - const TSet AddedNodes = CurrentSCSNodes.Difference(PreviousSCSNodes); - - if (AddedNodes.Num() > 0) - { - // Record Input / SCS node mapping - USCS_Node* SCSNode = AddedNodes.Array()[0]; - HAB->AddInputObjectMapping(NewInputObject->GetInputGuid(), SCSNode->VariableGuid); - SCSNode->ComponentTemplate->SetFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject); - } - - Blueprint->Modify(); - bOutBlueprintStructureModified = true; - } - } - } -#endif - } - else - { - // Otherwise, get the Houdini spline, and Houdini spline input from the argument. - HoudiniSplineInput = FromHoudiniSplineInputComponent; - HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - // Attach the new Houdini spline component to it's owner. - HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); - } - - // Mark the created UHoudiniSplineComponent as an input, and set its InputObject. - HoudiniSplineComponent->SetIsInputCurve(true); - - // HoudiniSplineComponent->SetInputObject(HoudiniSplineInput); - - // Set Houdini Spline Component bHasChanged and bNeedsToTrigerUpdate to true. - HoudiniSplineComponent->MarkChanged(true); - - - - return HoudiniSplineInput; -} - -void -UHoudiniInput::RemoveSplineFromInputObject( - UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, - bool& bOutBlueprintStructureModified) const -{ - if (!InHoudiniSplineInputObject) - return; - - UObject* OuterObj = GetOuter(); - const bool bOuterIsTemplate = OuterObj && OuterObj->IsTemplate(); - - if (bOuterIsTemplate) - { -#if WITH_EDITOR - // Find the SCS node that corresponds to this input and remove it. - UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); - if (HAB) - { - const UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (Blueprint) - { - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - check(SCS); - FGuid SCSGuid; - if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) - { - // TODO: Move this SCS variable removal code to a reusable utility function. We're - // going to need to reuse this in a few other places too. - USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(SCSGuid); - if (SCSNode) - { - SCS->RemoveNodeAndPromoteChildren(SCSNode); - SCSNode->SetOnNameChanged(FSCSNodeNameChanged()); - bOutBlueprintStructureModified = true; - HAB->RemoveInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid); - - if (SCSNode->ComponentTemplate != nullptr) - { - const FName TemplateName = SCSNode->ComponentTemplate->GetFName(); - const FString RemovedName = SCSNode->GetVariableName().ToString() + TEXT("_REMOVED_") + FGuid::NewGuid().ToString(); - - SCSNode->ComponentTemplate->Modify(); - SCSNode->ComponentTemplate->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); - - TArray ArchetypeInstances; - auto DestroyArchetypeInstances = [&ArchetypeInstances, &RemovedName](UActorComponent* ComponentTemplate) - { - ComponentTemplate->GetArchetypeInstances(ArchetypeInstances); - for (UObject* ArchetypeInstance : ArchetypeInstances) - { - if (!ArchetypeInstance->HasAllFlags(RF_ArchetypeObject | RF_InheritableComponentTemplate)) - { - CastChecked(ArchetypeInstance)->DestroyComponent(); - ArchetypeInstance->Rename(*RemovedName, nullptr, REN_DontCreateRedirectors); - } - } - }; - - DestroyArchetypeInstances(SCSNode->ComponentTemplate); - - if (Blueprint) - { - // Children need to have their inherited component template instance of the component renamed out of the way as well - TArray ChildrenOfClass; - GetDerivedClasses(Blueprint->GeneratedClass, ChildrenOfClass); - - for (UClass* ChildClass : ChildrenOfClass) - { - UBlueprintGeneratedClass* BPChildClass = CastChecked(ChildClass); - - if (UActorComponent* Component = (UActorComponent*)FindObjectWithOuter(BPChildClass, UActorComponent::StaticClass(), TemplateName)) - { - Component->Modify(); - Component->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); - - DestroyArchetypeInstances(Component); - } - } - } - } - } - } // if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) - } // if (Blueprint) - } -#endif - } // if (bIsOuterTemplate) - else - { - UHoudiniSplineComponent* HoudiniSplineComponent = InHoudiniSplineInputObject->GetCurveComponent(); - if (HoudiniSplineComponent) - { - // detach the input curves from the asset component - //FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - //HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - // Destroy the Houdini Spline Component - //InputObjectsPtr->RemoveAt(AtIndex); - HoudiniSplineComponent->DestroyComponent(); - } - } - InHoudiniSplineInputObject->Update(nullptr); -} - - -TArray* -UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - return &GeometryInputObjects; - - case EHoudiniInputType::Curve: - return &CurveInputObjects; - - case EHoudiniInputType::Asset: - return &AssetInputObjects; - - case EHoudiniInputType::Landscape: - return &LandscapeInputObjects; - - case EHoudiniInputType::World: - return &WorldInputObjects; - - case EHoudiniInputType::Skeletal: - return &SkeletalInputObjects; - - default: - case EHoudiniInputType::Invalid: - return nullptr; - } - - return nullptr; -} - -TArray* -UHoudiniInput::GetBoundSelectorObjectArray() -{ - return &WorldInputBoundSelectorObjects; -} - -const TArray* -UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - return &GeometryInputObjects; - - case EHoudiniInputType::Curve: - return &CurveInputObjects; - - case EHoudiniInputType::Asset: - return &AssetInputObjects; - - case EHoudiniInputType::Landscape: - return &LandscapeInputObjects; - - case EHoudiniInputType::World: - return &WorldInputObjects; - - case EHoudiniInputType::Skeletal: - return &SkeletalInputObjects; - - default: - case EHoudiniInputType::Invalid: - return nullptr; - } - - return nullptr; -} - -UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) -{ - return GetHoudiniInputObjectAt(Type, AtIndex); -} - -const UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) const -{ - const TArray* InputObjectsArray = GetHoudiniInputObjectArray(Type); - if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) - return nullptr; - - return (*InputObjectsArray)[AtIndex]; -} - -UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsArray = GetHoudiniInputObjectArray(InType); - if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) - return nullptr; - - return (*InputObjectsArray)[AtIndex]; -} - -UObject* -UHoudiniInput::GetInputObjectAt(const int32& AtIndex) -{ - return GetInputObjectAt(Type, AtIndex); -} - -AActor* -UHoudiniInput::GetBoundSelectorObjectAt(const int32& AtIndex) -{ - if (!WorldInputBoundSelectorObjects.IsValidIndex(AtIndex)) - return nullptr; - - return WorldInputBoundSelectorObjects[AtIndex]; -} - -UObject* -UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); - if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) - return nullptr; - - return HoudiniInputObject->GetObject(); -} - -void -UHoudiniInput::InsertInputObjectAt(const int32& AtIndex) -{ - InsertInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - InputObjectsPtr->InsertDefaulted(AtIndex, 1); - MarkChanged(true); -} - -void -UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) -{ - DeleteInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (!InputObjectsPtr->IsValidIndex(AtIndex)) - return; - - bool bBlueprintStructureModified = false; - - if (Type == EHoudiniInputType::Asset) - { - // ... TODO operations for removing asset input type - } - else if (Type == EHoudiniInputType::Curve) - { - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast((*InputObjectsPtr)[AtIndex]); - if (HoudiniSplineInputObject) - { - RemoveSplineFromInputObject(HoudiniSplineInputObject, bBlueprintStructureModified); - } - } - else if (Type == EHoudiniInputType::Geometry) - { - // ... TODO operations for removing geometry input type - } - else if (Type == EHoudiniInputType::Landscape) - { - // ... TODO operations for removing landscape input type - } - else if (Type == EHoudiniInputType::Skeletal) - { - // ... TODO operations for removing skeletal input type - } - else if (Type == EHoudiniInputType::World) - { - // ... TODO operations for removing world input type - } - else - { - // ... invalid input type - } - - MarkChanged(true); - - UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; - if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) - { - // Mark the input object's nodes for deletion - InputObjectToDelete->InvalidateData(); - - // If the deleted object wasnt null, trigger a re upload of the input data - MarkDataUploadNeeded(true); - } - - InputObjectsPtr->RemoveAt(AtIndex); - - // Delete the merge node when all the input objects are deleted. - if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); - InputNodeId = -1; - } - -#if WITH_EDITOR - if (bBlueprintStructureModified) - { - UActorComponent* Component = Cast(GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(Component); - } -#endif -} - -void -UHoudiniInput::DuplicateInputObjectAt(const int32& AtIndex) -{ - DuplicateInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (!InputObjectsPtr->IsValidIndex(AtIndex)) - return; - - // If the duplicated object is not null, trigger a re upload of the input data - bool bTriggerUpload = (*InputObjectsPtr)[AtIndex] != nullptr; - - // TODO: Duplicate the UHoudiniInputObject!! - UHoudiniInputObject* DuplicateInput = (*InputObjectsPtr)[AtIndex]; - InputObjectsPtr->Insert(DuplicateInput, AtIndex); - - MarkChanged(true); - - if (bTriggerUpload) - MarkDataUploadNeeded(true); -} - -int32 -UHoudiniInput::GetNumberOfInputObjects() -{ - return GetNumberOfInputObjects(Type); -} - -int32 -UHoudiniInput::GetNumberOfInputObjects(const EHoudiniInputType& InType) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return 0; - - return InputObjectsPtr->Num(); -} - -int32 -UHoudiniInput::GetNumberOfInputMeshes() -{ - return GetNumberOfInputMeshes(Type); -} - -int32 -UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return 0; - - // TODO? - // If geometry input, and we only have one null object, return 0 - int32 Num = InputObjectsPtr->Num(); - - // TODO: Fix BP properly! - // Special case for SM in BP: - // we need to add extra input objects store in BlueprintStaticMeshes - // Same thing for Actor InputObjects! - for (auto InputObj : *InputObjectsPtr) - { - if (!InputObj || InputObj->IsPendingKill()) - continue; - - UHoudiniInputStaticMesh* InputSM = Cast(InputObj); - if (InputSM && !InputSM->IsPendingKill()) - { - if (InputSM->BlueprintStaticMeshes.Num() > 0) - { - Num += (InputSM->BlueprintStaticMeshes.Num() - 1); - } - } - - UHoudiniInputActor* InputActor = Cast(InputObj); - if (InputActor && !InputActor->IsPendingKill()) - { - if (InputActor->GetActorComponents().Num() > 0) - { - Num += (InputActor->GetActorComponents().Num() - 1); - } - } - } - - return Num; -} - - -int32 -UHoudiniInput::GetNumberOfBoundSelectorObjects() const -{ - return WorldInputBoundSelectorObjects.Num(); -} - -void -UHoudiniInput::SetInputObjectAt(const int32& AtIndex, UObject* InObject) -{ - return SetInputObjectAt(Type, AtIndex, InObject); -} - -void -UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject) -{ - // Start by making sure we have the proper number of input objects - int32 NumIntObject = GetNumberOfInputObjects(InType); - if (NumIntObject <= AtIndex) - { - // We need to resize the array - SetInputObjectsNumber(InType, AtIndex + 1); - } - - UObject* CurrentInputObject = GetInputObjectAt(InType, AtIndex); - if (CurrentInputObject == InObject) - { - // Nothing to do - return; - } - - UHoudiniInputObject* CurrentInputObjectWrapper = GetHoudiniInputObjectAt(InType, AtIndex); - if (!InObject) - { - // We want to set the input object to null - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!ensure(InputObjectsPtr != nullptr && InputObjectsPtr->IsValidIndex(AtIndex))) - return; - - if (CurrentInputObjectWrapper) - { - // TODO: Check this case - // Do not destroy the input object manually! this messes up GC - //CurrentInputObjectWrapper->ConditionalBeginDestroy(); - MarkDataUploadNeeded(true); - } - - (*InputObjectsPtr)[AtIndex] = nullptr; - return; - } - - // Get the type of the previous and new input objects - EHoudiniInputObjectType NewObjectType = InObject ? UHoudiniInputObject::GetInputObjectTypeFromObject(InObject) : EHoudiniInputObjectType::Invalid; - EHoudiniInputObjectType CurrentObjectType = CurrentInputObjectWrapper ? CurrentInputObjectWrapper->Type : EHoudiniInputObjectType::Invalid; - - // See if we can reuse the existing InputObject - if (CurrentObjectType == NewObjectType && NewObjectType != EHoudiniInputObjectType::Invalid) - { - // The InputObjectTypes match, we can just update the existing object - CurrentInputObjectWrapper->Update(InObject); - CurrentInputObjectWrapper->MarkChanged(true); - return; - } - - // Destroy the existing input object - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!ensure(InputObjectsPtr)) - return; - - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InObject, this, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - return; - - // Mark that input object as changed so we know we need to update it - NewInputObject->MarkChanged(true); - if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) - { - // TODO: - // For some input type, we may have to copy some of the previous object's property before deleting it - - // Delete the previous object - CurrentInputObjectWrapper->MarkPendingKill(); - (*InputObjectsPtr)[AtIndex] = nullptr; - } - - // Update the input object array with the newly created input object - (*InputObjectsPtr)[AtIndex] = NewInputObject; -} - -void -UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (InputObjectsPtr->Num() == InNewCount) - { - // Nothing to do - return; - } - - if (InNewCount > InputObjectsPtr->Num()) - { - // Simply add new default InputObjects - InputObjectsPtr->SetNum(InNewCount); - } - else - { - // TODO: Check this case! - // Do not destroy the input object themselves manually, - // destroy the input object's nodes and reduce the array's size - for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - if (bCanDeleteHoudiniNodes) - CurrentInputObject->InvalidateData(); - - /*/ - //FHoudiniInputTranslator::DestroyInput(Inputs[InputIdx]); - CurrentObject->ConditionalBeginDestroy(); - (*InputObjectsPtr)[InObjIdx] = nullptr; - */ - } - - // Decrease the input object array size - InputObjectsPtr->SetNum(InNewCount); - } - - // Also delete the input's merge node when all the input objects are deleted. - if (InNewCount == 0 && InputNodeId >= 0) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - InputNodeId = -1; - } -} - -void -UHoudiniInput::SetBoundSelectorObjectsNumber(const int32& InNewCount) -{ - if (WorldInputBoundSelectorObjects.Num() == InNewCount) - { - // Nothing to do - return; - } - - if (InNewCount > WorldInputBoundSelectorObjects.Num()) - { - // Simply add new default InputObjects - WorldInputBoundSelectorObjects.SetNum(InNewCount); - } - else - { - /* - // TODO: Not Needed? - // Do not destroy the input object themselves manually, - // destroy the input object's nodes and reduce the array's size - for (int32 InObjIdx = WorldInputBoundSelectorObjects.Num() - 1; InObjIdx >= InNewCount; InObjIdx--) - { - UHoudiniInputObject* CurrentInputObject = WorldInputBoundSelectorObjects[InObjIdx]; - if (!CurrentInputObject) - continue; - - CurrentInputObject->MarkInputNodesForDeletion(); - } - */ - - // Decrease the input object array size - WorldInputBoundSelectorObjects.SetNum(InNewCount); - } -} - -void -UHoudiniInput::SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor) -{ - // Start by making sure we have the proper number of objects - int32 NumIntObject = GetNumberOfBoundSelectorObjects(); - if (NumIntObject <= AtIndex) - { - // We need to resize the array - SetBoundSelectorObjectsNumber(AtIndex + 1); - } - - AActor* CurrentActor = GetBoundSelectorObjectAt(AtIndex); - if (CurrentActor == InActor) - { - // Nothing to do - return; - } - - // Update the array with the new object - WorldInputBoundSelectorObjects[AtIndex] = InActor; -} - -// Helper function indicating what classes are supported by an input type -TArray -UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) -{ - TArray AllowedClasses; - switch (InInputType) - { - case EHoudiniInputType::Geometry: - AllowedClasses.Add(UStaticMesh::StaticClass()); - AllowedClasses.Add(USkeletalMesh::StaticClass()); - AllowedClasses.Add(UBlueprint::StaticClass()); - AllowedClasses.Add(UDataTable::StaticClass()); - AllowedClasses.Add(UFoliageType_InstancedStaticMesh::StaticClass()); - break; - - case EHoudiniInputType::Curve: - AllowedClasses.Add(USplineComponent::StaticClass()); - AllowedClasses.Add(UHoudiniSplineComponent::StaticClass()); - break; - - case EHoudiniInputType::Asset: - AllowedClasses.Add(UHoudiniAssetComponent::StaticClass()); - break; - - case EHoudiniInputType::Landscape: - AllowedClasses.Add(ALandscapeProxy::StaticClass()); - break; - - case EHoudiniInputType::World: - AllowedClasses.Add(AActor::StaticClass()); - break; - - case EHoudiniInputType::Skeletal: - AllowedClasses.Add(USkeletalMesh::StaticClass()); - break; - - default: - break; - } - - return AllowedClasses; -} - -// Helper function indicating if an object is supported by an input type -bool -UHoudiniInput::IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject) -{ - TArray AllowedClasses = GetAllowedClasses(InInputType); - for (auto CurClass : AllowedClasses) - { - if (InObject->IsA(CurClass)) - return true; - } - - return false; -} - -bool -UHoudiniInput::IsDataUploadNeeded() -{ - if (bDataUploadNeeded) - return true; - - return HasChanged(); -} - -// Indicates if this input has changed and should be updated -bool -UHoudiniInput::HasChanged() -{ - if (bHasChanged) - return true; - - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->HasChanged()) - return true; - } - - return false; -} - -bool -UHoudiniInput::IsTransformUploadNeeded() -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->HasTransformChanged()) - return true; - } - - return false; -} - -// Indicates if this input needs to trigger an update -bool -UHoudiniInput::NeedsToTriggerUpdate() -{ - if (bNeedsToTriggerUpdate) - return true; - - const TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->NeedsToTriggerUpdate()) - return true; - } - - return false; -} - -FString -UHoudiniInput::GetNodeBaseName() const -{ - UHoudiniAssetComponent* HAC = Cast(GetOuter()); - FString NodeBaseName = HAC ? HAC->GetDisplayName() : TEXT("HoudiniAsset"); - - // Unfortunately CreateInputNode always prefix with input_... - if (IsObjectPathParameter()) - NodeBaseName += TEXT("_") + GetName(); - else - NodeBaseName += TEXT("_input") + FString::FromInt(GetInputIndex()); - - return NodeBaseName; -} - -void -UHoudiniInput::OnTransformUIExpand(const int32& AtIndex) -{ -#if WITH_EDITORONLY_DATA - if (TransformUIExpanded.IsValidIndex(AtIndex)) - { - TransformUIExpanded[AtIndex] = !TransformUIExpanded[AtIndex]; - } - else - { - // We need to append values to the expanded array - for (int32 Index = TransformUIExpanded.Num(); Index <= AtIndex; Index++) - { - TransformUIExpanded.Add(Index == AtIndex ? true : false); - } - } -#endif -} - -bool -UHoudiniInput::IsTransformUIExpanded(const int32& AtIndex) -{ -#if WITH_EDITORONLY_DATA - return TransformUIExpanded.IsValidIndex(AtIndex) ? TransformUIExpanded[AtIndex] : false; -#else - return false; -#endif -}; - -FTransform* -UHoudiniInput::GetTransformOffset(const int32& AtIndex) -{ - UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); - if (InObject) - return &(InObject->Transform); - - return nullptr; -} - -const FTransform -UHoudiniInput::GetTransformOffset(const int32& AtIndex) const -{ - const UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); - if (InObject) - return InObject->Transform; - - return FTransform::Identity; -} - -TOptional -UHoudiniInput::GetPositionOffsetX(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().X; -} - -TOptional -UHoudiniInput::GetPositionOffsetY(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().Y; -} - -TOptional -UHoudiniInput::GetPositionOffsetZ(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().Z; -} - -TOptional -UHoudiniInput::GetRotationOffsetRoll(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Roll; -} - -TOptional -UHoudiniInput::GetRotationOffsetPitch(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Pitch; -} - -TOptional -UHoudiniInput::GetRotationOffsetYaw(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Yaw; -} - -TOptional -UHoudiniInput::GetScaleOffsetX(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().X; -} - -TOptional -UHoudiniInput::GetScaleOffsetY(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().Y; -} - -TOptional -UHoudiniInput::GetScaleOffsetZ(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().Z; -} - -bool -UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = GetTransformOffset(AtIndex); - if (!Transform) - return false; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - if (Position[XYZIndex] == Value) - return false; - Position[XYZIndex] = Value; - Transform->SetLocation(Position); - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - if (Rotator.Roll == Value) - return false; - Rotator.Roll = Value; - break; - } - - case 1: - { - if (Rotator.Pitch == Value) - return false; - Rotator.Pitch = Value; - break; - } - - case 2: - { - if (Rotator.Yaw == Value) - return false; - Rotator.Yaw = Value; - break; - } - } - Transform->SetRotation(Rotator.Quaternion()); - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - if (Scale[XYZIndex] == Value) - return false; - - Scale[XYZIndex] = Value; - Transform->SetScale3D(Scale); - } - - MarkChanged(true); - bStaticMeshChanged = true; - - return true; -} - -void -UHoudiniInput::SetAddRotAndScaleAttributes(const bool& InValue) -{ - if (bAddRotAndScaleAttributesOnCurves == InValue) - return; - - bAddRotAndScaleAttributesOnCurves = InValue; - - // Mark all input obj as changed - MarkAllInputObjectsChanged(true); -} - -#if WITH_EDITOR -FText -UHoudiniInput::GetCurrentSelectionText() const -{ - FText CurrentSelectionText; - switch (Type) - { - case EHoudiniInputType::Landscape : - { - if (LandscapeInputObjects.Num() > 0) - { - UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; - - UHoudiniInputLandscape* InputLandscape = Cast(InputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) - return CurrentSelectionText; - - ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) - return CurrentSelectionText; - - CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); - } - } - break; - - case EHoudiniInputType::Asset : - { - if (AssetInputObjects.Num() > 0) - { - UHoudiniInputObject* InputObject = AssetInputObjects[0]; - - UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) - return CurrentSelectionText; - - UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!HAC || HAC->IsPendingKill()) - return CurrentSelectionText; - - UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return CurrentSelectionText; - - CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); - } - } - break; - - default: - break; - } - - return CurrentSelectionText; -} -#endif - -bool -UHoudiniInput::HasLandscapeExportTypeChanged () const -{ - if (Type != EHoudiniInputType::Landscape) - return false; - - return bLandscapeHasExportTypeChanged; -} - -void -UHoudiniInput::SetHasLandscapeExportTypeChanged(const bool InChanged) -{ - if (Type != EHoudiniInputType::Landscape) - return; - - bLandscapeHasExportTypeChanged = InChanged; -} - -bool -UHoudiniInput::GetUpdateInputLandscape() const -{ - if (Type != EHoudiniInputType::Landscape) - return false; - - return bUpdateInputLandscape; -} - -void -UHoudiniInput::SetUpdateInputLandscape(const bool bInUpdateInputLandcape) -{ - if (Type != EHoudiniInputType::Landscape) - return; - - bUpdateInputLandscape = bInUpdateInputLandcape; -} - - -bool -UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() -{ - // Dont do anything if we're not a World Input - if (Type != EHoudiniInputType::World) - return false; - - // Build an array of the current selection's bounds - TArray AllBBox; - for (auto CurrentActor : WorldInputBoundSelectorObjects) - { - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); - } - - // - // Select all actors in our bound selectors bounding boxes - // - - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - - //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); - UWorld* MyWorld = GetWorld(); - TArray NewSelectedActors; - for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) - { - AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - // Check that actor is currently not selected - if (WorldInputBoundSelectorObjects.Contains(CurrentActor)) - continue; - - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - // For BrushActors, both the actor and its brush must be valid - ABrush* BrushActor = Cast(CurrentActor); - if (BrushActor) - { - if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) - continue; - } - - FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); - for (auto InBounds : AllBBox) - { - // Check if both actor's bounds intersects - if (!ActorBounds.Intersect(InBounds)) - continue; - - NewSelectedActors.Add(CurrentActor); - break; - } - } - - return UpdateWorldSelection(NewSelectedActors); -} - -bool -UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) -{ - TArray NewSelectedActors = InNewSelection; - - // Update our current selection with the new one - // Keep actors that are still selected, remove the one that are not selected anymore - bool bHasSelectionChanged = false; - for (int32 Idx = WorldInputObjects.Num() - 1; Idx >= 0; Idx--) - { - UHoudiniInputActor* InputActor = Cast(WorldInputObjects[Idx]); - AActor* CurActor = InputActor ? InputActor->GetActor() : nullptr; - - if (CurActor && NewSelectedActors.Contains(CurActor)) - { - // The actor is still selected, remove it from the new selection - NewSelectedActors.Remove(CurActor); - } - else - { - // That actor is no longer selected, remove itr from our current selection - DeleteInputObjectAt(EHoudiniInputType::World, Idx); - bHasSelectionChanged = true; - } - } - - if (NewSelectedActors.Num() > 0) - bHasSelectionChanged = true; - - // Then add the newly selected Actors - int32 InputObjectIdx = GetNumberOfInputObjects(EHoudiniInputType::World); - int32 NewInputObjectNumber = InputObjectIdx + NewSelectedActors.Num(); - SetInputObjectsNumber(EHoudiniInputType::World, NewInputObjectNumber); - for (const auto& CurActor : NewSelectedActors) - { - // Update the input objects from the valid selected actors array - SetInputObjectAt(InputObjectIdx++, CurActor); - } - - MarkChanged(bHasSelectionChanged); - - return bHasSelectionChanged; -} - - -bool -UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Returns true if the object is one of our input object for the given type - const TArray* ObjectArray = GetHoudiniInputObjectArray(InType); - if (!ObjectArray) - return false; - - for (auto& CurrentInputObject : (*ObjectArray)) - { - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - if (CurrentInputObject->GetObject() == InObject) - return true; - } - - return false; -} - -void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const -{ - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : SkeletalInputObjects) - { - Fn(InputObject); - } -} - -TArray*> UHoudiniInput::GetAllObjectArrays() const -{ - return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; -} - -TArray*> UHoudiniInput::GetAllObjectArrays() -{ - return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; -} - -void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const -{ - TArray*> ObjectArrays = GetAllObjectArrays(); - for (const TArray* ObjectArrayPtr : ObjectArrays) - { - if (!ObjectArrayPtr) - continue; - Fn(*ObjectArrayPtr); - } -} - -void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) -{ - TArray*> ObjectArrays = GetAllObjectArrays(); - for (TArray* ObjectArrayPtr : ObjectArrays) - { - if (!ObjectArrayPtr) - continue; - Fn(*ObjectArrayPtr); - } -} - -void UHoudiniInput::GetAllHoudiniInputObjects(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddInputObject = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (InputObject) - OutObjects.Add(InputObject); - }; - ForAllHoudiniInputObjects(AddInputObject); -} - -void UHoudiniInput::ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const -{ - auto ProcessSceneComponent = [Fn](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - Fn(SceneComponentInput); - }; - ForAllHoudiniInputObjects(ProcessSceneComponent); -} - -void UHoudiniInput::GetAllHoudiniInputSceneComponents(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - OutObjects.Add(SceneComponentInput); - }; - ForAllHoudiniInputObjects(AddSceneComponent); -} - -void UHoudiniInput::GetAllHoudiniInputSplineComponents(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputHoudiniSplineComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - OutObjects.Add(SceneComponentInput); - }; - ForAllHoudiniInputObjects(AddSceneComponent); -} - - -void UHoudiniInput::RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject) -{ - if (!InInputObject) - return; - - ForAllHoudiniInputObjectArrays([InInputObject](TArray& ObjectArray) { - ObjectArray.Remove(InInputObject); - }); - - return; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInput.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutput.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniAsset.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetBlueprintComponent.h" + +#include "EngineUtils.h" +#include "Engine/Brush.h" +#include "Engine/Engine.h" +#include "Engine/DataTable.h" +#include "Model.h" +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "UObject/UObjectGlobals.h" +#include "FoliageType_InstancedStaticMesh.h" + +#include "Components/SplineComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Landscape.h" + +#if WITH_EDITOR + +#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" + +#endif + +// +UHoudiniInput::UHoudiniInput() + : Type(EHoudiniInputType::Invalid) + , PreviousType(EHoudiniInputType::Invalid) + , AssetNodeId(-1) + , InputNodeId(-1) + , InputIndex(0) + , ParmId(-1) + , bIsObjectPathParameter(false) + , bHasChanged(false) + , bPackBeforeMerge(false) + , bExportLODs(false) + , bExportSockets(false) + , bExportColliders(false) + , bCookOnCurveChanged(true) + , bStaticMeshChanged(false) + , bInputAssetConnectedInHoudini(false) + , DefaultCurveOffset(0.f) + , bAddRotAndScaleAttributesOnCurves(false) + , bIsWorldInputBoundSelector(false) + , bWorldInputBoundSelectorAutoUpdate(false) + , UnrealSplineResolution(50.0f) + , bUpdateInputLandscape(false) + , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) + , bLandscapeExportSelectionOnly(false) + , bLandscapeAutoSelectComponent(false) + , bLandscapeExportMaterials(false) + , bLandscapeExportLighting(false) + , bLandscapeExportNormalizedUVs(false) + , bLandscapeExportTileUVs(false) +{ + Name = TEXT(""); + Label = TEXT(""); + SetFlags(RF_Transactional); + + // Geometry inputs always have one null default object + GeometryInputObjects.Add(nullptr); + + KeepWorldTransform = GetDefaultXTransformType(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; + + bAddRotAndScaleAttributesOnCurves = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bAddRotAndScaleAttributesOnCurves : false; +} + +void +UHoudiniInput::BeginDestroy() +{ + InvalidateData(); + + // DO NOT MANUALLY DESTROY OUR INPUT OBJECTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + + // Mark all our input objects for destruction + ForAllHoudiniInputObjectArrays([](TArray& ObjectArray) { + ObjectArray.Empty(); + }); + + Super::BeginDestroy(); +} + +#if WITH_EDITOR +void UHoudiniInput::PostEditUndo() +{ + Super::PostEditUndo(); + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!InputObjectsPtr) + return; + + MarkChanged(true); + bool bBlueprintStructureChanged = false; + + if (HasInputTypeChanged()) + { + // If the input type has changed on undo, previousType becomes new type + /* This does not work properly, see the corresponding part in FHoudiniInputDetails::AddInputTypeComboBox(...), after Transaction(... + ) + { + EHoudiniInputType NewType = PreviousType; + SetInputType(NewType); + } + */ + EHoudiniInputType Temp = Type; + Type = PreviousType; + PreviousType = EHoudiniInputType::Invalid; + + // If the undo action caused input type changing, treat it as a regular type changing + // after set up the new and prev types properly + SetInputType(Temp, bBlueprintStructureChanged); + } + else + { + if (Type == EHoudiniInputType::Asset) + { + // Mark the input asset object as changed, since only undo changing asset will get into here. + // The input array will be empty when undo adding asset (only support single asset input object in an input now) + for (auto & NextAssetInputObj : *InputObjectsPtr) + { + if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) + continue; + + NextAssetInputObj->MarkChanged(true); + } + } + + + if (Type == EHoudiniInputType::World) + { + if (WorldInputObjects.Num() == 0 && InputNodeId >= 0) + { + for (auto & NextNodeId : CreatedDataNodeIds) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NextNodeId, true); + } + + CreatedDataNodeIds.Empty(); + + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + InputNodeId = -1; + } + } + + if (Type == EHoudiniInputType::Curve) + { + if (PreviousType != EHoudiniInputType::Curve) + { + for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); + if (!SplineInput || SplineInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + USceneComponent* OuterComponent = Cast(GetOuter()); + + // Attach the new Houdini spline component to it's owner + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + HoudiniSplineComponent->MarkChanged(true); + } + return; + } + bool bUndoDelete = false; + bool bUndoInsert = false; + bool bUndoDeletedObjArrayEmptied = false; + + TArray< USceneComponent* > childActor; + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !OuterHAC->IsPendingKill()) + childActor = OuterHAC->GetAttachChildren(); + + // Undo delete input objects action + for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) + { + UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; + if (!InputObject || InputObject->IsPendingKill()) + continue; + + UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); + + if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + continue; + + UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + // If the last change deleted this curve input, recreate this Houdini Spline input. + if (!SplineComponent->GetAttachParent()) + { + bUndoDelete = true; + + if (!bUndoDeletedObjArrayEmptied) + LastUndoDeletedInputs.Empty(); + + bUndoDeletedObjArrayEmptied = true; + + UHoudiniSplineComponent * ReconstructedSpline = NewObject( + GetOuter(), UHoudiniSplineComponent::StaticClass()); + + if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) + continue; + + ReconstructedSpline->SetFlags(RF_Transactional); + ReconstructedSpline->CopyHoudiniData(SplineComponent); + + UHoudiniInputObject * ReconstructedInputObject = UHoudiniInputHoudiniSplineComponent::Create( + ReconstructedSpline, GetOuter(), ReconstructedSpline->GetHoudiniSplineName()); + UHoudiniInputHoudiniSplineComponent *ReconstructedHoudiniSplineInput = (UHoudiniInputHoudiniSplineComponent*)ReconstructedInputObject; + (*InputObjectsPtr)[Index] = ReconstructedHoudiniSplineInput; + + ReconstructedSpline->RegisterComponent(); + ReconstructedSpline->SetFlags(RF_Transactional); + + CreateHoudiniSplineInput(ReconstructedHoudiniSplineInput, true, true, bBlueprintStructureChanged); + + // Cast the reconstructed Houdini Spline Input to a generic HoudiniInput object. + UHoudiniInputObject * ReconstructedHoudiniInput = Cast(ReconstructedHoudiniSplineInput); + + LastUndoDeletedInputs.Add(ReconstructedHoudiniInput); + // Reset the LastInsertedInputsArray for redoing this undo action. + } + } + + if (bUndoDelete) + return; + + // Undo insert input objects action + for (int Index = 0; Index < LastInsertedInputs.Num(); ++Index) + { + bUndoInsert = true; + UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; + if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + HoudiniSplineComponent->DestroyComponent(); + } + + if (bUndoInsert) + return; + + for (int Index = 0; Index < LastUndoDeletedInputs.Num(); ++Index) + { + UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; + + UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); + if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); + if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) + continue; + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + + HoudiniSplineComponent->DestroyComponent(); + } + } + } + + if (bBlueprintStructureChanged) + { + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); + } + +} +#endif + + +FBox +UHoudiniInput::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + + switch (Type) + { + case EHoudiniInputType::Curve: + { + for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) + { + const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); + if (!CurInCurve || CurInCurve->IsPendingKill()) + continue; + + UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); + if (!CurCurve || CurCurve->IsPendingKill()) + continue; + + FBox CurCurveBound(ForceInitToZero); + for (auto & Trans : CurCurve->CurvePoints) + { + CurCurveBound += Trans.GetLocation(); + } + + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + + if (OuterHAC && !OuterHAC->IsPendingKill()) + BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); + } + } + break; + + case EHoudiniInputType::Asset: + { + for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) + { + UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); + if (!CurInAsset || CurInAsset->IsPendingKill()) + continue; + + UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); + if (!CurInHAC || CurInHAC->IsPendingKill()) + continue; + + BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); + } + } + break; + + case EHoudiniInputType::World: + { + for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) + { + UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); + if (CurInActor && !CurInActor->IsPendingKill()) + { + AActor* Actor = CurInActor->GetActor(); + if (!Actor || Actor->IsPendingKill()) + continue; + + FVector Origin, Extent; + Actor->GetActorBounds(false, Origin, Extent); + + BoxBounds += FBox::BuildAABB(Origin, Extent); + } + else + { + // World Input now also support HoudiniAssets + UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); + if (CurInAsset && !CurInAsset->IsPendingKill()) + { + UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); + if (!CurInHAC || CurInHAC->IsPendingKill()) + continue; + + BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); + continue; + } + } + } + } + break; + + case EHoudiniInputType::Landscape: + { + for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) + { + UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); + if (!CurInLandscape || CurInLandscape->IsPendingKill()) + continue; + + ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); + if (!CurLandscape || CurLandscape->IsPendingKill()) + continue; + + FVector Origin, Extent; + CurLandscape->GetActorBounds(false, Origin, Extent); + + BoxBounds += FBox::BuildAABB(Origin, Extent); + } + } + break; + + case EHoudiniInputType::Skeletal: + case EHoudiniInputType::Invalid: + default: + break; + } + + return BoxBounds; +} + +FString +UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) +{ + FString InputTypeStr; + switch (InInputType) + { + case EHoudiniInputType::Geometry: + { + InputTypeStr = TEXT("Geometry Input"); + } + break; + + case EHoudiniInputType::Asset: + { + InputTypeStr = TEXT("Asset Input"); + } + break; + + case EHoudiniInputType::Curve: + { + InputTypeStr = TEXT("Curve Input"); + } + break; + + case EHoudiniInputType::Landscape: + { + InputTypeStr = TEXT("Landscape Input"); + } + break; + + case EHoudiniInputType::World: + { + InputTypeStr = TEXT("World Outliner Input"); + } + break; + + case EHoudiniInputType::Skeletal: + { + InputTypeStr = TEXT("Skeletal Mesh Input"); + } + break; + } + + return InputTypeStr; +} + + +EHoudiniInputType +UHoudiniInput::StringToInputType(const FString& InInputTypeString) +{ + if (InInputTypeString.StartsWith(TEXT("Geometry"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Geometry; + } + else if (InInputTypeString.StartsWith(TEXT("Asset"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Asset; + } + else if (InInputTypeString.StartsWith(TEXT("Curve"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Curve; + } + else if (InInputTypeString.StartsWith(TEXT("Landscape"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Landscape; + } + else if (InInputTypeString.StartsWith(TEXT("World"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::World; + } + else if (InInputTypeString.StartsWith(TEXT("Skeletal"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Skeletal; + } + + return EHoudiniInputType::Invalid; +} + + +EHoudiniCurveType UHoudiniInput::StringToHoudiniCurveType(const FString& HoudiniCurveTypeString) +{ + if (HoudiniCurveTypeString.StartsWith(TEXT("Polygon"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Polygon; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Nurbs"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Nurbs; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Bezier"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Bezier; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Points"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Points; + } + + return EHoudiniCurveType::Invalid; +} + +EHoudiniCurveMethod UHoudiniInput::StringToHoudiniCurveMethod(const FString& HoudiniCurveMethodString) +{ + if (HoudiniCurveMethodString.StartsWith(TEXT("CVs"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::CVs; + } + else if (HoudiniCurveMethodString.StartsWith(TEXT("Breakpoints"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::Breakpoints; + } + + else if (HoudiniCurveMethodString.StartsWith(TEXT("Freehand"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::Freehand; + } + + return EHoudiniCurveMethod::Invalid; + +} + +// +void +UHoudiniInput::SetSOPInput(const int32& InInputIndex) +{ + // Set the input index + InputIndex = InInputIndex; + + // Invalidate objpath parameter + ParmId = -1; + bIsObjectPathParameter = false; +} + +void +UHoudiniInput::SetObjectPathParameter(const int32& InParmId) +{ + // Set as objpath parameter + ParmId = InParmId; + bIsObjectPathParameter = true; + + // Invalidate the geo input + InputIndex = -1; +} + +EHoudiniXformType +UHoudiniInput::GetDefaultXTransformType() +{ + switch (Type) + { + case EHoudiniInputType::Curve: + case EHoudiniInputType::Geometry: + case EHoudiniInputType::Skeletal: + return EHoudiniXformType::None; + case EHoudiniInputType::Asset: + case EHoudiniInputType::Landscape: + case EHoudiniInputType::World: + return EHoudiniXformType::IntoThisObject; + } + + return EHoudiniXformType::Auto; +} + +bool +UHoudiniInput::GetKeepWorldTransform() const +{ + bool bReturn = false; + switch (KeepWorldTransform) + { + case EHoudiniXformType::Auto: + { + // Return default values corresponding to the input type: + if (Type == EHoudiniInputType::Curve + || Type == EHoudiniInputType::Geometry + || Type == EHoudiniInputType::Skeletal ) + { + // NONE for Geo, Curve and skeletal mesh IN + bReturn = false; + } + else + { + // INTO THIS OBJECT for Asset, Landscape and World IN + bReturn = true; + } + break; + } + + case EHoudiniXformType::None: + { + bReturn = false; + break; + } + + case EHoudiniXformType::IntoThisObject: + { + bReturn = true; + break; + } + } + + return bReturn; +} + +void +UHoudiniInput::SetKeepWorldTransform(const bool& bInKeepWorldTransform) +{ + if (bInKeepWorldTransform) + { + KeepWorldTransform = EHoudiniXformType::IntoThisObject; + } + else + { + KeepWorldTransform = EHoudiniXformType::None; + } +} + +void +UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlueprintStructureModified) +{ + if (InInputType == Type) + return; + + SetPreviousInputType(Type); + + // Mark this input as changed + MarkChanged(true); + bOutBlueprintStructureModified = true; + + // Check previous input type + switch (PreviousType) + { + case EHoudiniInputType::Asset: + { + break; + } + + case EHoudiniInputType::Curve: + { + // detach the input curves from the asset component + if (GetNumberOfInputObjects() > 0) + { + for (UHoudiniInputObject * CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); + + if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); + + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + HoudiniSplineComponent->Modify(); + + const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); + + if (bIsArchetype) + { +#if WITH_EDITOR + check(HoudiniSplineComponent->IsTemplate()); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); +#endif + } + else + { + AActor* OwningActor = HoudiniSplineComponent->GetOwner(); + check(OwningActor); + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + HoudiniSplineComponent->SetVisibility(false, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(false); + HoudiniSplineComponent->SetHiddenInGame(true, true); + + // This NodeId shouldn't be invalidated like this. If a spline component + // or curve input is no longer valid, the input object should be removed from the HoudinInput + // to get cleaned up properly. + // HoudiniSplineComponent->SetNodeId(-1); + HoudiniSplineComponent->MarkChanged(true); + } + + bOutBlueprintStructureModified = true; + } + } + break; + } + + case EHoudiniInputType::Geometry: + { + break; + } + + case EHoudiniInputType::Landscape: + { + TArray* InputObjectsArray = GetHoudiniInputObjectArray(PreviousType); + + if (!InputObjectsArray) + break; + + for (int32 Idx = 0; Idx < InputObjectsArray->Num(); ++Idx) + { + UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; + + if (!InputObj || InputObj->IsPendingKill()) + continue; + + UHoudiniInputLandscape* InputLandscape = Cast(InputObj); + + if (!InputLandscape || InputLandscape->IsPendingKill()) + continue; + + // do something? + } + + break; + } + + case EHoudiniInputType::Skeletal: + { + break; + } + + case EHoudiniInputType::World: + { + break; + } + + default: + break; + } + + + Type = InInputType; + + // TODO: NOPE, not needed + // Set keep world transform to default w.r.t to new input type. + //KeepWorldTransform = GetDefaultXTransformType(); + + // Check current input type + switch (InInputType) + { + case EHoudiniInputType::World: + case EHoudiniInputType::Asset: + { + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !bImportAsReference) + { + for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); + if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + continue; + + UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); + if (!CurrentHAC || CurrentHAC->IsPendingKill()) + continue; + + CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); + } + } + } + break; + + case EHoudiniInputType::Curve: + { + if (GetNumberOfInputObjects() == 0) + { + CreateNewCurveInputObject(bOutBlueprintStructureModified); + MarkChanged(true); + } + else + { + for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(CurrentInput); + if (!IsValid(SplineInput)) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); + if (!IsValid(HoudiniSplineComponent)) + continue; + + HoudiniSplineComponent->Modify(); + + const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); + + if (bIsArchetype) + { +#if WITH_EDITOR + check(HoudiniSplineComponent->IsTemplate()); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); +#endif + } + else + { + // Attach the new Houdini spline component to it's owner + AActor* OwningActor = HoudiniSplineComponent->GetOwner(); + check(OwningActor); + USceneComponent* OuterComponent = Cast(GetOuter()); + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->MarkChanged(true); + + } + + bOutBlueprintStructureModified = true; + } + } + } + break; + + case EHoudiniInputType::Geometry: + { + + } + break; + + case EHoudiniInputType::Landscape: + { + // Need to do anything on select? + } + break; + + case EHoudiniInputType::Skeletal: + { + } + break; + + default: + { + } + break; + } +} + +UHoudiniInputObject* +UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) +{ + if (CurveInputObjects.Num() > 0) + return nullptr; + + UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); + if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) + return nullptr; + + UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + // Default Houdini spline component input should not be visible at initialization + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + + CurveInputObjects.Add(NewCurveInputObject); + SetInputObjectsNumber(EHoudiniInputType::Curve, 1); + CurveInputObjects.SetNum(1); + + return NewCurveInputObject; +} + +void +UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) +{ + MarkDataUploadNeeded(bInChanged); + + // Mark all the objects from this input has changed so they upload themselves + + TSet InputTypes; + InputTypes.Add(Type); + InputTypes.Add(EHoudiniInputType::Curve); + + TArray* NewInputObjects = GetHoudiniInputObjectArray(Type); + if (NewInputObjects) + { + for (auto CurInputObject : *NewInputObjects) + { + if (CurInputObject && !CurInputObject->IsPendingKill()) + CurInputObject->MarkChanged(bInChanged); + } + } +} + +UHoudiniInput * UHoudiniInput::DuplicateAndCopyState(UObject * DestOuter, bool bInCanDeleteHoudiniNodes) +{ + UHoudiniInput* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); + + NewInput->CopyStateFrom(this, false, bInCanDeleteHoudiniNodes); + + return NewInput; +} + +void UHoudiniInput::CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes) +{ + + // Preserve the current input objects before the copy to ensure we don't lose + // access to input objects and have them end up in the garbage. + + TMap*> PrevInputObjectsMap; + + for(EHoudiniInputType InputType : HoudiniInputTypeList) + { + PrevInputObjectsMap.Add(InputType, GetHoudiniInputObjectArray(InputType)); + } + + // TArray PrevInputObjects; + // TArray* OldToInputObjects = GetHoudiniInputObjectArray(Type); + // if (OldToInputObjects) + // PrevInputObjects = *OldToInputObjects; + + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + } + + AssetNodeId = InInput->AssetNodeId; + InputNodeId = InInput->InputNodeId; + ParmId = InInput->ParmId; + bCanDeleteHoudiniNodes = bInCanDeleteHoudiniNodes; + + //if (bInCanDeleteHoudiniNodes) + //{ + // // Delete stale data nodes before they get overwritten. + // TSet NewNodeIds(InInput->CreatedDataNodeIds); + // for (int32 NodeId : CreatedDataNodeIds) + // { + // if (!NewNodeIds.Contains(NodeId)) + // { + // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); + // } + // } + //} + + CreatedDataNodeIds = InInput->CreatedDataNodeIds; + + // Important note: At this point the new object may still share objects with InInput. + // The CopyInputs() will properly duplicate inputs where necessary. + + // Copy states of Input Objects that correspond to the current type. + + for(auto& Entry : PrevInputObjectsMap) + { + EHoudiniInputType InputType = Entry.Key; + TArray* PrevInputObjects = Entry.Value; + TArray* ToInputObjects = GetHoudiniInputObjectArray(InputType); + TArray* FromInputObjects = InInput->GetHoudiniInputObjectArray(InputType); + + if (ToInputObjects && FromInputObjects) + { + *ToInputObjects = *PrevInputObjects; + CopyInputs(*ToInputObjects, *FromInputObjects, bInCanDeleteHoudiniNodes); + } + } + +} + +void UHoudiniInput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +void UHoudiniInput::InvalidateData() +{ + // If valid, mark our input node for deletion + if (InputNodeId >= 0) + { + // .. but if we're an asset input, don't delete the node as InputNodeId + // is set to the input HDA's node ID! + if (Type != EHoudiniInputType::Asset) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + } + + InputNodeId = -1; + } + + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + if (!InputObject) + continue; + + if (InputObject->IsA()) + { + // When the input object is a HoudiniAssetComponent, + // we need to be sure that this HDA node id is not in CreatedDataNodeIds + // We dont want to delete the input HDA node! + CreatedDataNodeIds.Remove(InputObject->InputNodeId); + } + + InputObject->InvalidateData(); + } + + if (bCanDeleteHoudiniNodes) + { + auto& HoudiniEngineRuntime = FHoudiniEngineRuntime::Get(); + for(int32 NodeId : CreatedDataNodeIds) + { + HoudiniEngineRuntime.MarkNodeIdAsPendingDelete(NodeId, true); + } + } + + CreatedDataNodeIds.Empty(); +} + +void UHoudiniInput::CopyInputs(TArray& ToInputObjects, TArray& FromInputObjects, bool bInCanDeleteHoudiniNodes) +{ + TSet StaleObjects(ToInputObjects); + + const int32 NumInputs = FromInputObjects.Num(); + UObject* TargetOuter = GetOuter(); + + ToInputObjects.SetNum(NumInputs); + + + for (int i = 0; i < NumInputs; i++) + { + UHoudiniInputObject* FromObject = FromInputObjects[i]; + UHoudiniInputObject* ToObject = ToInputObjects[i]; + + if (!FromObject) + { + ToInputObjects[i] = nullptr; + continue; + } + + if (ToObject) + { + bool IsValid = true; + // Is ToInput and FromInput the same or do we have to create a input object? + IsValid = IsValid && ToObject->Matches(*FromObject); + IsValid = IsValid && ToObject->GetOuter() == TargetOuter; + + if (!IsValid) + { + ToObject = nullptr; + } + } + + if (ToObject) + { + // We have an existing (matching) object. Copy the + // state from the incoming input. + StaleObjects.Remove(ToObject); + ToObject->CopyStateFrom(FromObject, true); + } + else + { + // We need to create a new input here. + ToObject = FromObject->DuplicateAndCopyState(TargetOuter); + ToInputObjects[i] = ToObject; + } + + ToObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); + } + + + for (UHoudiniInputObject* StaleInputObject : StaleObjects) + { + if (!StaleInputObject) + continue; + if (StaleInputObject->GetOuter() == this) + { + StaleInputObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); + } + } +} + + +UHoudiniInputHoudiniSplineComponent* +UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * FromHoudiniSplineInputComponent, const bool & bAttachToparent, const bool & bAppendToInputArray, bool& bOutBlueprintStructureModified) +{ + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = nullptr; + UHoudiniSplineComponent* HoudiniSplineComponent = nullptr; + + UObject* OuterObj = GetOuter(); + USceneComponent* OuterComp = Cast(GetOuter()); + bool bOuterIsTemplate = (OuterObj && OuterObj->IsTemplate()); + + if (!FromHoudiniSplineInputComponent) + { + // NOTE: If we're inside the Blueprint editor, the outer here is going to the be HAC component template. + check(OuterObj) + + // Create a default Houdini spline input if a null pointer is passed in. + FName HoudiniSplineName = MakeUniqueObjectName(OuterComp, UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); + + // Create a Houdini Input Object. + UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( + nullptr, OuterObj, HoudiniSplineName.ToString()); + + if (!NewInputObject || NewInputObject->IsPendingKill()) + return nullptr; + + HoudiniSplineInput = Cast(NewInputObject); + if (!HoudiniSplineInput) + return nullptr; + + HoudiniSplineComponent = NewObject( + HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + HoudiniSplineInput->Update(HoudiniSplineComponent); + + HoudiniSplineComponent->SetHoudiniSplineName(HoudiniSplineName.ToString()); + HoudiniSplineComponent->SetFlags(RF_Transactional); + + // Set the default position of curve to avoid overlapping. + HoudiniSplineComponent->SetOffset(DefaultCurveOffset); + DefaultCurveOffset += 100.f; + + if (!bOuterIsTemplate) + { + HoudiniSplineComponent->RegisterComponent(); + + // Attach the new Houdini spline component to it's owner. + if (bAttachToparent) + HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); + } + + //push the new input object to the array for new type. + if (bAppendToInputArray && Type == EHoudiniInputType::Curve) + GetHoudiniInputObjectArray(Type)->Add(NewInputObject); + +#if WITH_EDITOR + if (bOuterIsTemplate) + { + UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); + if (HAB) + { + UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (Blueprint) + { + TArray Components; + Components.Add(HoudiniSplineComponent); + + USCS_Node* HABNode = HAB->FindSCSNodeForTemplateComponentInClassHierarchy(HAB); + + // NOTE: FAddComponentsToBlueprintParams was introduced in 4.26 so for the sake of + // backwards compatibility, manually determine which SCSNode was added instead of + // relying on Params.OutNodes. + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.OptionalNewRootNode = HABNode; + const TSet PreviousSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); + + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, Params); + USCS_Node* NewNode = nullptr; + const TSet CurrentSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); + const TSet AddedNodes = CurrentSCSNodes.Difference(PreviousSCSNodes); + + if (AddedNodes.Num() > 0) + { + // Record Input / SCS node mapping + USCS_Node* SCSNode = AddedNodes.Array()[0]; + HAB->AddInputObjectMapping(NewInputObject->GetInputGuid(), SCSNode->VariableGuid); + SCSNode->ComponentTemplate->SetFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject); + } + + Blueprint->Modify(); + bOutBlueprintStructureModified = true; + } + } + } +#endif + } + else + { + // Otherwise, get the Houdini spline, and Houdini spline input from the argument. + HoudiniSplineInput = FromHoudiniSplineInputComponent; + HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + // Attach the new Houdini spline component to it's owner. + HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); + } + + // Mark the created UHoudiniSplineComponent as an input, and set its InputObject. + HoudiniSplineComponent->SetIsInputCurve(true); + + // HoudiniSplineComponent->SetInputObject(HoudiniSplineInput); + + // Set Houdini Spline Component bHasChanged and bNeedsToTrigerUpdate to true. + HoudiniSplineComponent->MarkChanged(true); + + + + return HoudiniSplineInput; +} + +void +UHoudiniInput::RemoveSplineFromInputObject( + UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, + bool& bOutBlueprintStructureModified) const +{ + if (!InHoudiniSplineInputObject) + return; + + UObject* OuterObj = GetOuter(); + const bool bOuterIsTemplate = OuterObj && OuterObj->IsTemplate(); + + if (bOuterIsTemplate) + { +#if WITH_EDITOR + // Find the SCS node that corresponds to this input and remove it. + UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); + if (HAB) + { + const UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (Blueprint) + { + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + check(SCS); + FGuid SCSGuid; + if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) + { + // TODO: Move this SCS variable removal code to a reusable utility function. We're + // going to need to reuse this in a few other places too. + USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(SCSGuid); + if (SCSNode) + { + SCS->RemoveNodeAndPromoteChildren(SCSNode); + SCSNode->SetOnNameChanged(FSCSNodeNameChanged()); + bOutBlueprintStructureModified = true; + HAB->RemoveInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid); + + if (SCSNode->ComponentTemplate != nullptr) + { + const FName TemplateName = SCSNode->ComponentTemplate->GetFName(); + const FString RemovedName = SCSNode->GetVariableName().ToString() + TEXT("_REMOVED_") + FGuid::NewGuid().ToString(); + + SCSNode->ComponentTemplate->Modify(); + SCSNode->ComponentTemplate->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); + + TArray ArchetypeInstances; + auto DestroyArchetypeInstances = [&ArchetypeInstances, &RemovedName](UActorComponent* ComponentTemplate) + { + ComponentTemplate->GetArchetypeInstances(ArchetypeInstances); + for (UObject* ArchetypeInstance : ArchetypeInstances) + { + if (!ArchetypeInstance->HasAllFlags(RF_ArchetypeObject | RF_InheritableComponentTemplate)) + { + CastChecked(ArchetypeInstance)->DestroyComponent(); + ArchetypeInstance->Rename(*RemovedName, nullptr, REN_DontCreateRedirectors); + } + } + }; + + DestroyArchetypeInstances(SCSNode->ComponentTemplate); + + if (Blueprint) + { + // Children need to have their inherited component template instance of the component renamed out of the way as well + TArray ChildrenOfClass; + GetDerivedClasses(Blueprint->GeneratedClass, ChildrenOfClass); + + for (UClass* ChildClass : ChildrenOfClass) + { + UBlueprintGeneratedClass* BPChildClass = CastChecked(ChildClass); + + if (UActorComponent* Component = (UActorComponent*)FindObjectWithOuter(BPChildClass, UActorComponent::StaticClass(), TemplateName)) + { + Component->Modify(); + Component->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); + + DestroyArchetypeInstances(Component); + } + } + } + } + } + } // if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) + } // if (Blueprint) + } +#endif + } // if (bIsOuterTemplate) + else + { + UHoudiniSplineComponent* HoudiniSplineComponent = InHoudiniSplineInputObject->GetCurveComponent(); + if (HoudiniSplineComponent) + { + // detach the input curves from the asset component + //FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + //HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + // Destroy the Houdini Spline Component + //InputObjectsPtr->RemoveAt(AtIndex); + HoudiniSplineComponent->DestroyComponent(); + } + } + InHoudiniSplineInputObject->Update(nullptr); +} + + +TArray* +UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + return &GeometryInputObjects; + + case EHoudiniInputType::Curve: + return &CurveInputObjects; + + case EHoudiniInputType::Asset: + return &AssetInputObjects; + + case EHoudiniInputType::Landscape: + return &LandscapeInputObjects; + + case EHoudiniInputType::World: + return &WorldInputObjects; + + case EHoudiniInputType::Skeletal: + return &SkeletalInputObjects; + + default: + case EHoudiniInputType::Invalid: + return nullptr; + } + + return nullptr; +} + +TArray* +UHoudiniInput::GetBoundSelectorObjectArray() +{ + return &WorldInputBoundSelectorObjects; +} + +const TArray* +UHoudiniInput::GetBoundSelectorObjectArray() const +{ + return &WorldInputBoundSelectorObjects; +} + +const TArray* +UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + return &GeometryInputObjects; + + case EHoudiniInputType::Curve: + return &CurveInputObjects; + + case EHoudiniInputType::Asset: + return &AssetInputObjects; + + case EHoudiniInputType::Landscape: + return &LandscapeInputObjects; + + case EHoudiniInputType::World: + return &WorldInputObjects; + + case EHoudiniInputType::Skeletal: + return &SkeletalInputObjects; + + default: + case EHoudiniInputType::Invalid: + return nullptr; + } + + return nullptr; +} + +UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) +{ + return GetHoudiniInputObjectAt(Type, AtIndex); +} + +const UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) const +{ + const TArray* InputObjectsArray = GetHoudiniInputObjectArray(Type); + if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) + return nullptr; + + return (*InputObjectsArray)[AtIndex]; +} + +UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsArray = GetHoudiniInputObjectArray(InType); + if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) + return nullptr; + + return (*InputObjectsArray)[AtIndex]; +} + +UObject* +UHoudiniInput::GetInputObjectAt(const int32& AtIndex) +{ + return GetInputObjectAt(Type, AtIndex); +} + +AActor* +UHoudiniInput::GetBoundSelectorObjectAt(const int32& AtIndex) +{ + if (!WorldInputBoundSelectorObjects.IsValidIndex(AtIndex)) + return nullptr; + + return WorldInputBoundSelectorObjects[AtIndex]; +} + +UObject* +UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); + if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) + return nullptr; + + return HoudiniInputObject->GetObject(); +} + +void +UHoudiniInput::InsertInputObjectAt(const int32& AtIndex) +{ + InsertInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + InputObjectsPtr->InsertDefaulted(AtIndex, 1); + MarkChanged(true); +} + +void +UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) +{ + DeleteInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (!InputObjectsPtr->IsValidIndex(AtIndex)) + return; + + bool bBlueprintStructureModified = false; + + if (Type == EHoudiniInputType::Asset) + { + // ... TODO operations for removing asset input type + } + else if (Type == EHoudiniInputType::Curve) + { + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast((*InputObjectsPtr)[AtIndex]); + if (HoudiniSplineInputObject) + { + RemoveSplineFromInputObject(HoudiniSplineInputObject, bBlueprintStructureModified); + } + } + else if (Type == EHoudiniInputType::Geometry) + { + // ... TODO operations for removing geometry input type + } + else if (Type == EHoudiniInputType::Landscape) + { + // ... TODO operations for removing landscape input type + } + else if (Type == EHoudiniInputType::Skeletal) + { + // ... TODO operations for removing skeletal input type + } + else if (Type == EHoudiniInputType::World) + { + // ... TODO operations for removing world input type + } + else + { + // ... invalid input type + } + + MarkChanged(true); + + UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; + if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) + { + // Mark the input object's nodes for deletion + InputObjectToDelete->InvalidateData(); + + // If the deleted object wasnt null, trigger a re upload of the input data + MarkDataUploadNeeded(true); + } + + InputObjectsPtr->RemoveAt(AtIndex); + + // Delete the merge node when all the input objects are deleted. + if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); + InputNodeId = -1; + } + +#if WITH_EDITOR + if (bBlueprintStructureModified) + { + UActorComponent* Component = Cast(GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(Component); + } +#endif +} + +void +UHoudiniInput::DuplicateInputObjectAt(const int32& AtIndex) +{ + DuplicateInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (!InputObjectsPtr->IsValidIndex(AtIndex)) + return; + + // If the duplicated object is not null, trigger a re upload of the input data + bool bTriggerUpload = (*InputObjectsPtr)[AtIndex] != nullptr; + + // TODO: Duplicate the UHoudiniInputObject!! + UHoudiniInputObject* DuplicateInput = (*InputObjectsPtr)[AtIndex]; + InputObjectsPtr->Insert(DuplicateInput, AtIndex); + + MarkChanged(true); + + if (bTriggerUpload) + MarkDataUploadNeeded(true); +} + +int32 +UHoudiniInput::GetNumberOfInputObjects() +{ + return GetNumberOfInputObjects(Type); +} + +int32 +UHoudiniInput::GetNumberOfInputObjects(const EHoudiniInputType& InType) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return 0; + + return InputObjectsPtr->Num(); +} + +int32 +UHoudiniInput::GetNumberOfInputMeshes() +{ + return GetNumberOfInputMeshes(Type); +} + +int32 +UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return 0; + + // TODO? + // If geometry input, and we only have one null object, return 0 + int32 Num = InputObjectsPtr->Num(); + + // TODO: Fix BP properly! + // Special case for SM in BP: + // we need to add extra input objects store in BlueprintStaticMeshes + // Same thing for Actor InputObjects! + for (auto InputObj : *InputObjectsPtr) + { + if (!InputObj || InputObj->IsPendingKill()) + continue; + + UHoudiniInputStaticMesh* InputSM = Cast(InputObj); + if (InputSM && !InputSM->IsPendingKill()) + { + if (InputSM->BlueprintStaticMeshes.Num() > 0) + { + Num += (InputSM->BlueprintStaticMeshes.Num() - 1); + } + } + + UHoudiniInputActor* InputActor = Cast(InputObj); + if (InputActor && !InputActor->IsPendingKill()) + { + if (InputActor->GetActorComponents().Num() > 0) + { + Num += (InputActor->GetActorComponents().Num() - 1); + } + } + } + + return Num; +} + + +int32 +UHoudiniInput::GetNumberOfBoundSelectorObjects() const +{ + return WorldInputBoundSelectorObjects.Num(); +} + +void +UHoudiniInput::SetInputObjectAt(const int32& AtIndex, UObject* InObject) +{ + return SetInputObjectAt(Type, AtIndex, InObject); +} + +void +UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject) +{ + // Start by making sure we have the proper number of input objects + int32 NumIntObject = GetNumberOfInputObjects(InType); + if (NumIntObject <= AtIndex) + { + // We need to resize the array + SetInputObjectsNumber(InType, AtIndex + 1); + } + + UObject* CurrentInputObject = GetInputObjectAt(InType, AtIndex); + if (CurrentInputObject == InObject) + { + // Nothing to do + return; + } + + UHoudiniInputObject* CurrentInputObjectWrapper = GetHoudiniInputObjectAt(InType, AtIndex); + if (!InObject) + { + // We want to set the input object to null + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!ensure(InputObjectsPtr != nullptr && InputObjectsPtr->IsValidIndex(AtIndex))) + return; + + if (CurrentInputObjectWrapper) + { + // TODO: Check this case + // Do not destroy the input object manually! this messes up GC + //CurrentInputObjectWrapper->ConditionalBeginDestroy(); + MarkDataUploadNeeded(true); + } + + (*InputObjectsPtr)[AtIndex] = nullptr; + return; + } + + // Get the type of the previous and new input objects + EHoudiniInputObjectType NewObjectType = InObject ? UHoudiniInputObject::GetInputObjectTypeFromObject(InObject) : EHoudiniInputObjectType::Invalid; + EHoudiniInputObjectType CurrentObjectType = CurrentInputObjectWrapper ? CurrentInputObjectWrapper->Type : EHoudiniInputObjectType::Invalid; + + // See if we can reuse the existing InputObject + if (CurrentObjectType == NewObjectType && NewObjectType != EHoudiniInputObjectType::Invalid) + { + // The InputObjectTypes match, we can just update the existing object + CurrentInputObjectWrapper->Update(InObject); + CurrentInputObjectWrapper->MarkChanged(true); + return; + } + + // Destroy the existing input object + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!ensure(InputObjectsPtr)) + return; + + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InObject, this, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + return; + + // Mark that input object as changed so we know we need to update it + NewInputObject->MarkChanged(true); + if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) + { + // TODO: + // For some input type, we may have to copy some of the previous object's property before deleting it + + // Delete the previous object + CurrentInputObjectWrapper->MarkPendingKill(); + (*InputObjectsPtr)[AtIndex] = nullptr; + } + + // Update the input object array with the newly created input object + (*InputObjectsPtr)[AtIndex] = NewInputObject; +} + +void +UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (InputObjectsPtr->Num() == InNewCount) + { + // Nothing to do + return; + } + + if (InNewCount > InputObjectsPtr->Num()) + { + // Simply add new default InputObjects + InputObjectsPtr->SetNum(InNewCount); + } + else + { + // TODO: Check this case! + // Do not destroy the input object themselves manually, + // destroy the input object's nodes and reduce the array's size + for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + if (bCanDeleteHoudiniNodes) + CurrentInputObject->InvalidateData(); + + /*/ + //FHoudiniInputTranslator::DestroyInput(Inputs[InputIdx]); + CurrentObject->ConditionalBeginDestroy(); + (*InputObjectsPtr)[InObjIdx] = nullptr; + */ + } + + // Decrease the input object array size + InputObjectsPtr->SetNum(InNewCount); + } + + // Also delete the input's merge node when all the input objects are deleted. + if (InNewCount == 0 && InputNodeId >= 0) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + InputNodeId = -1; + } +} + +void +UHoudiniInput::SetBoundSelectorObjectsNumber(const int32& InNewCount) +{ + if (WorldInputBoundSelectorObjects.Num() == InNewCount) + { + // Nothing to do + return; + } + + if (InNewCount > WorldInputBoundSelectorObjects.Num()) + { + // Simply add new default InputObjects + WorldInputBoundSelectorObjects.SetNum(InNewCount); + } + else + { + /* + // TODO: Not Needed? + // Do not destroy the input object themselves manually, + // destroy the input object's nodes and reduce the array's size + for (int32 InObjIdx = WorldInputBoundSelectorObjects.Num() - 1; InObjIdx >= InNewCount; InObjIdx--) + { + UHoudiniInputObject* CurrentInputObject = WorldInputBoundSelectorObjects[InObjIdx]; + if (!CurrentInputObject) + continue; + + CurrentInputObject->MarkInputNodesForDeletion(); + } + */ + + // Decrease the input object array size + WorldInputBoundSelectorObjects.SetNum(InNewCount); + } +} + +void +UHoudiniInput::SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor) +{ + // Start by making sure we have the proper number of objects + int32 NumIntObject = GetNumberOfBoundSelectorObjects(); + if (NumIntObject <= AtIndex) + { + // We need to resize the array + SetBoundSelectorObjectsNumber(AtIndex + 1); + } + + AActor* CurrentActor = GetBoundSelectorObjectAt(AtIndex); + if (CurrentActor == InActor) + { + // Nothing to do + return; + } + + // Update the array with the new object + WorldInputBoundSelectorObjects[AtIndex] = InActor; +} + +// Helper function indicating what classes are supported by an input type +TArray +UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) +{ + TArray AllowedClasses; + switch (InInputType) + { + case EHoudiniInputType::Geometry: + AllowedClasses.Add(UStaticMesh::StaticClass()); + AllowedClasses.Add(USkeletalMesh::StaticClass()); + AllowedClasses.Add(UBlueprint::StaticClass()); + AllowedClasses.Add(UDataTable::StaticClass()); + AllowedClasses.Add(UFoliageType_InstancedStaticMesh::StaticClass()); + break; + + case EHoudiniInputType::Curve: + AllowedClasses.Add(USplineComponent::StaticClass()); + AllowedClasses.Add(UHoudiniSplineComponent::StaticClass()); + break; + + case EHoudiniInputType::Asset: + AllowedClasses.Add(UHoudiniAssetComponent::StaticClass()); + break; + + case EHoudiniInputType::Landscape: + AllowedClasses.Add(ALandscapeProxy::StaticClass()); + break; + + case EHoudiniInputType::World: + AllowedClasses.Add(AActor::StaticClass()); + break; + + case EHoudiniInputType::Skeletal: + AllowedClasses.Add(USkeletalMesh::StaticClass()); + break; + + default: + break; + } + + return AllowedClasses; +} + +// Helper function indicating if an object is supported by an input type +bool +UHoudiniInput::IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject) +{ + TArray AllowedClasses = GetAllowedClasses(InInputType); + for (auto CurClass : AllowedClasses) + { + if (InObject->IsA(CurClass)) + return true; + } + + return false; +} + +bool +UHoudiniInput::IsDataUploadNeeded() +{ + if (bDataUploadNeeded) + return true; + + return HasChanged(); +} + +// Indicates if this input has changed and should be updated +bool +UHoudiniInput::HasChanged() +{ + if (bHasChanged) + return true; + + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->HasChanged()) + return true; + } + + return false; +} + +bool +UHoudiniInput::IsTransformUploadNeeded() +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->HasTransformChanged()) + return true; + } + + return false; +} + +// Indicates if this input needs to trigger an update +bool +UHoudiniInput::NeedsToTriggerUpdate() +{ + if (bNeedsToTriggerUpdate) + return true; + + const TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->NeedsToTriggerUpdate()) + return true; + } + + return false; +} + +FString +UHoudiniInput::GetNodeBaseName() const +{ + UHoudiniAssetComponent* HAC = Cast(GetOuter()); + FString NodeBaseName = HAC ? HAC->GetDisplayName() : TEXT("HoudiniAsset"); + + // Unfortunately CreateInputNode always prefix with input_... + if (IsObjectPathParameter()) + NodeBaseName += TEXT("_") + GetName(); + else + NodeBaseName += TEXT("_input") + FString::FromInt(GetInputIndex()); + + return NodeBaseName; +} + +void +UHoudiniInput::OnTransformUIExpand(const int32& AtIndex) +{ +#if WITH_EDITORONLY_DATA + if (TransformUIExpanded.IsValidIndex(AtIndex)) + { + TransformUIExpanded[AtIndex] = !TransformUIExpanded[AtIndex]; + } + else + { + // We need to append values to the expanded array + for (int32 Index = TransformUIExpanded.Num(); Index <= AtIndex; Index++) + { + TransformUIExpanded.Add(Index == AtIndex ? true : false); + } + } +#endif +} + +bool +UHoudiniInput::IsTransformUIExpanded(const int32& AtIndex) +{ +#if WITH_EDITORONLY_DATA + return TransformUIExpanded.IsValidIndex(AtIndex) ? TransformUIExpanded[AtIndex] : false; +#else + return false; +#endif +}; + +FTransform* +UHoudiniInput::GetTransformOffset(const int32& AtIndex) +{ + UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); + if (InObject) + return &(InObject->Transform); + + return nullptr; +} + +const FTransform +UHoudiniInput::GetTransformOffset(const int32& AtIndex) const +{ + const UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); + if (InObject) + return InObject->Transform; + + return FTransform::Identity; +} + +TOptional +UHoudiniInput::GetPositionOffsetX(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().X; +} + +TOptional +UHoudiniInput::GetPositionOffsetY(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().Y; +} + +TOptional +UHoudiniInput::GetPositionOffsetZ(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().Z; +} + +TOptional +UHoudiniInput::GetRotationOffsetRoll(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Roll; +} + +TOptional +UHoudiniInput::GetRotationOffsetPitch(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Pitch; +} + +TOptional +UHoudiniInput::GetRotationOffsetYaw(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Yaw; +} + +TOptional +UHoudiniInput::GetScaleOffsetX(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().X; +} + +TOptional +UHoudiniInput::GetScaleOffsetY(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().Y; +} + +TOptional +UHoudiniInput::GetScaleOffsetZ(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().Z; +} + +bool +UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = GetTransformOffset(AtIndex); + if (!Transform) + return false; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + if (Position[XYZIndex] == Value) + return false; + Position[XYZIndex] = Value; + Transform->SetLocation(Position); + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + if (Rotator.Roll == Value) + return false; + Rotator.Roll = Value; + break; + } + + case 1: + { + if (Rotator.Pitch == Value) + return false; + Rotator.Pitch = Value; + break; + } + + case 2: + { + if (Rotator.Yaw == Value) + return false; + Rotator.Yaw = Value; + break; + } + } + Transform->SetRotation(Rotator.Quaternion()); + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + if (Scale[XYZIndex] == Value) + return false; + + Scale[XYZIndex] = Value; + Transform->SetScale3D(Scale); + } + + MarkChanged(true); + bStaticMeshChanged = true; + + return true; +} + +void +UHoudiniInput::SetAddRotAndScaleAttributes(const bool& InValue) +{ + if (bAddRotAndScaleAttributesOnCurves == InValue) + return; + + bAddRotAndScaleAttributesOnCurves = InValue; + + // Mark all input obj as changed + MarkAllInputObjectsChanged(true); +} + +#if WITH_EDITOR +FText +UHoudiniInput::GetCurrentSelectionText() const +{ + FText CurrentSelectionText; + switch (Type) + { + case EHoudiniInputType::Landscape : + { + if (LandscapeInputObjects.Num() > 0) + { + UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; + + UHoudiniInputLandscape* InputLandscape = Cast(InputObject); + if (!InputLandscape || InputLandscape->IsPendingKill()) + return CurrentSelectionText; + + ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); + if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + return CurrentSelectionText; + + CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); + } + } + break; + + case EHoudiniInputType::Asset : + { + if (AssetInputObjects.Num() > 0) + { + UHoudiniInputObject* InputObject = AssetInputObjects[0]; + + UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); + if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + return CurrentSelectionText; + + UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); + if (!HAC || HAC->IsPendingKill()) + return CurrentSelectionText; + + UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return CurrentSelectionText; + + CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); + } + } + break; + + default: + break; + } + + return CurrentSelectionText; +} +#endif + +bool +UHoudiniInput::HasLandscapeExportTypeChanged () const +{ + if (Type != EHoudiniInputType::Landscape) + return false; + + return bLandscapeHasExportTypeChanged; +} + +void +UHoudiniInput::SetHasLandscapeExportTypeChanged(const bool InChanged) +{ + if (Type != EHoudiniInputType::Landscape) + return; + + bLandscapeHasExportTypeChanged = InChanged; +} + +bool +UHoudiniInput::GetUpdateInputLandscape() const +{ + if (Type != EHoudiniInputType::Landscape) + return false; + + return bUpdateInputLandscape; +} + +void +UHoudiniInput::SetUpdateInputLandscape(const bool bInUpdateInputLandcape) +{ + if (Type != EHoudiniInputType::Landscape) + return; + + bUpdateInputLandscape = bInUpdateInputLandcape; +} + + +bool +UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() +{ + // Dont do anything if we're not a World Input + if (Type != EHoudiniInputType::World) + return false; + + // Build an array of the current selection's bounds + TArray AllBBox; + for (auto CurrentActor : WorldInputBoundSelectorObjects) + { + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); + } + + // + // Select all actors in our bound selectors bounding boxes + // + + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + + //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); + UWorld* MyWorld = GetWorld(); + TArray NewSelectedActors; + for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) + { + AActor *CurrentActor = *ActorItr; + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + // Check that actor is currently not selected + if (WorldInputBoundSelectorObjects.Contains(CurrentActor)) + continue; + + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + // For BrushActors, both the actor and its brush must be valid + ABrush* BrushActor = Cast(CurrentActor); + if (BrushActor) + { + if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) + continue; + } + + FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); + for (auto InBounds : AllBBox) + { + // Check if both actor's bounds intersects + if (!ActorBounds.Intersect(InBounds)) + continue; + + NewSelectedActors.Add(CurrentActor); + break; + } + } + + return UpdateWorldSelection(NewSelectedActors); +} + +bool +UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) +{ + TArray NewSelectedActors = InNewSelection; + + // Update our current selection with the new one + // Keep actors that are still selected, remove the one that are not selected anymore + bool bHasSelectionChanged = false; + for (int32 Idx = WorldInputObjects.Num() - 1; Idx >= 0; Idx--) + { + UHoudiniInputActor* InputActor = Cast(WorldInputObjects[Idx]); + AActor* CurActor = InputActor ? InputActor->GetActor() : nullptr; + + if (CurActor && NewSelectedActors.Contains(CurActor)) + { + // The actor is still selected, remove it from the new selection + NewSelectedActors.Remove(CurActor); + } + else + { + // That actor is no longer selected, remove itr from our current selection + DeleteInputObjectAt(EHoudiniInputType::World, Idx); + bHasSelectionChanged = true; + } + } + + if (NewSelectedActors.Num() > 0) + bHasSelectionChanged = true; + + // Then add the newly selected Actors + int32 InputObjectIdx = GetNumberOfInputObjects(EHoudiniInputType::World); + int32 NewInputObjectNumber = InputObjectIdx + NewSelectedActors.Num(); + SetInputObjectsNumber(EHoudiniInputType::World, NewInputObjectNumber); + for (const auto& CurActor : NewSelectedActors) + { + // Update the input objects from the valid selected actors array + SetInputObjectAt(InputObjectIdx++, CurActor); + } + + MarkChanged(bHasSelectionChanged); + + return bHasSelectionChanged; +} + + +bool +UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Returns true if the object is one of our input object for the given type + const TArray* ObjectArray = GetHoudiniInputObjectArray(InType); + if (!ObjectArray) + return false; + + for (auto& CurrentInputObject : (*ObjectArray)) + { + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + if (CurrentInputObject->GetObject() == InObject) + return true; + } + + return false; +} + +void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const +{ + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : SkeletalInputObjects) + { + Fn(InputObject); + } +} + +TArray*> UHoudiniInput::GetAllObjectArrays() const +{ + return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; +} + +TArray*> UHoudiniInput::GetAllObjectArrays() +{ + return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; +} + +void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const +{ + TArray*> ObjectArrays = GetAllObjectArrays(); + for (const TArray* ObjectArrayPtr : ObjectArrays) + { + if (!ObjectArrayPtr) + continue; + Fn(*ObjectArrayPtr); + } +} + +void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) +{ + TArray*> ObjectArrays = GetAllObjectArrays(); + for (TArray* ObjectArrayPtr : ObjectArrays) + { + if (!ObjectArrayPtr) + continue; + Fn(*ObjectArrayPtr); + } +} + +void UHoudiniInput::GetAllHoudiniInputObjects(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddInputObject = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (InputObject) + OutObjects.Add(InputObject); + }; + ForAllHoudiniInputObjects(AddInputObject); +} + +void UHoudiniInput::ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const +{ + auto ProcessSceneComponent = [Fn](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + Fn(SceneComponentInput); + }; + ForAllHoudiniInputObjects(ProcessSceneComponent); +} + +void UHoudiniInput::GetAllHoudiniInputSceneComponents(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + OutObjects.Add(SceneComponentInput); + }; + ForAllHoudiniInputObjects(AddSceneComponent); +} + +void UHoudiniInput::GetAllHoudiniInputSplineComponents(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputHoudiniSplineComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + OutObjects.Add(SceneComponentInput); + }; + ForAllHoudiniInputObjects(AddSceneComponent); +} + + +void UHoudiniInput::RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject) +{ + if (!InInputObject) + return; + + ForAllHoudiniInputObjectArrays([InInputObject](TArray& ObjectArray) { + ObjectArray.Remove(InInputObject); + }); + + return; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h index c00e2084b..1959b9d04 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h @@ -1,557 +1,558 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreTypes.h" -#include "UObject/Object.h" -#include "UObject/ObjectMacros.h" -#include "Misc/Optional.h" - -#include "HoudiniInputTypes.h" -#include "HoudiniInputObject.h" - -#include "GameFramework/Actor.h" -#include "LandscapeProxy.h" - -#include "HoudiniInput.generated.h" - - - -class FReply; - -enum class EHoudiniCurveType : int8; -enum class ECheckBoxState : unsigned char; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject -{ - GENERATED_BODY() - -public: - - UHoudiniInput(); - - // Equality operator, - // We consider two inputs equals if they have the same name, objparam state, and input index/parmId - // TODO: ParmId might be an incorrect condition - bool operator==(const UHoudiniInput& other) const - { - return (bIsObjectPathParameter == other.bIsObjectPathParameter - && InputIndex == other.InputIndex - && ParmId == other.ParmId - && Name.Equals(other.Name) - && Label.Equals(other.Label)); - } - - bool Matches(const UHoudiniInput& other) const { return (*this == other); }; - - // Helper function returning a string from an InputType - static FString InputTypeToString(const EHoudiniInputType& InInputType); - - // Helper function returning an InputType from a string - static EHoudiniInputType StringToInputType(const FString& InInputTypeString); - // Helper function returning a Houdini curve type from a string - static EHoudiniCurveType StringToHoudiniCurveType(const FString& CurveTypeString); - // Helper function returning a Houdini curve method from a string - static EHoudiniCurveMethod StringToHoudiniCurveMethod(const FString& CurveMethodString); - - // Helper function indicating what classes are supported by an input type - static TArray GetAllowedClasses(const EHoudiniInputType& InInputType); - - // Helper function indicating if an object is supported by an input type - static bool IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject); - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - // Returns the NodeId of the asset / object merge we are associated with - int32 GetAssetNodeId() const { return AssetNodeId; }; - // For objpath parameter, return the associated ParamId, -1 if we're a Geo input - int32 GetParameterId() const { return bIsObjectPathParameter ? ParmId : -1; }; - // Returns the NodeId of the node plugged into this input - int32 GetInputNodeId() const { return InputNodeId; }; - - // For Geo inputs, returns the InputIndex, -1 if we're an object path parameter - int32 GetInputIndex() const { return bIsObjectPathParameter ? -1 : InputIndex; }; - // Return the array containing all the nodes created for this input's data - TArray& GetCreatedDataNodeIds() { return CreatedDataNodeIds; }; - // Returns the current input type - EHoudiniInputType GetInputType() const { return Type; }; - // Returns the previous input type - EHoudiniInputType GetPreviousInputType() const { return PreviousType; }; - // Returns the current input type as a string - FString GetInputTypeAsString() const { return InputTypeToString(Type); }; - - EHoudiniXformType GetDefaultXTransformType(); - // Returns true when this input's Transform Type is set to NONE, - // false if set to INTO_THIS_OBJECT, 2 will use the input's type default value - bool GetKeepWorldTransform() const; - // Indicates if this input has changed and should be updated - bool HasChanged(); - // Indicates if this input needs to trigger an update - bool NeedsToTriggerUpdate(); - // Indicates this input should upload its data - bool IsDataUploadNeeded(); - // Indicates this input's transform need to be uploaded - bool IsTransformUploadNeeded(); - // Indicates if this input type has been changed - bool HasInputTypeChanged() const { return PreviousType != EHoudiniInputType::Invalid ? PreviousType != Type : false; } - // - bool GetUpdateInputLandscape() const; - - void SetUpdateInputLandscape(const bool bInUpdateInputLandcape); - - FString GetName() const { return Name; }; - FString GetLabel() const { return Label; }; - FString GetHelp() const { return Help; }; - bool GetPackBeforeMerge() const { return bPackBeforeMerge; }; - bool GetImportAsReference() const { return bImportAsReference; }; - bool GetExportLODs() const { return bExportLODs; }; - bool GetExportSockets() const { return bExportSockets; }; - bool GetExportColliders() const { return bExportColliders; }; - bool IsObjectPathParameter() const { return bIsObjectPathParameter; }; - float GetUnrealSplineResolution() const { return UnrealSplineResolution; }; - - virtual bool GetCookOnCurveChange() const { return bCookOnCurveChanged; }; - - TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType); - const TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const; - TArray* GetBoundSelectorObjectArray(); - - UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex); - const UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex) const; - AActor* GetBoundSelectorObjectAt(const int32& AtIndex); - - UHoudiniInputObject* GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - UObject* GetInputObjectAt(const int32& AtIndex); - UObject* GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - int32 GetNumberOfInputObjects(); - int32 GetNumberOfInputObjects(const EHoudiniInputType& InType); - - int32 GetNumberOfInputMeshes(); - int32 GetNumberOfInputMeshes(const EHoudiniInputType& InType); - - int32 GetNumberOfBoundSelectorObjects() const; - - bool IsWorldInputBoundSelector() const { return bIsWorldInputBoundSelector; }; - bool GetWorldInputBoundSelectorAutoUpdates() const { return bWorldInputBoundSelectorAutoUpdate; }; - - FString GetNodeBaseName() const; - - bool IsTransformUIExpanded(const int32& AtIndex); - - // Return the transform offset for a given input object - FTransform* GetTransformOffset(const int32& AtIndex); - const FTransform GetTransformOffset(const int32& AtIndex) const; - - // Returns the position offset for a given input object - TOptional GetPositionOffsetX(int32 AtIndex) const; - TOptional GetPositionOffsetY(int32 AtIndex) const; - TOptional GetPositionOffsetZ(int32 AtIndex) const; - - // Returns the rotation offset for a given input object - TOptional GetRotationOffsetRoll(int32 AtIndex) const; - TOptional GetRotationOffsetPitch(int32 AtIndex) const; - TOptional GetRotationOffsetYaw(int32 AtIndex) const; - - // Returns the scale offset for a given input object - TOptional GetScaleOffsetX(int32 AtIndex) const; - TOptional GetScaleOffsetY(int32 AtIndex) const; - TOptional GetScaleOffsetZ(int32 AtIndex) const; - - // Returns true if the object is one of our input object for the given type - bool ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const; - - // Get all input object arrays - TArray*> GetAllObjectArrays() const; - TArray*> GetAllObjectArrays(); - - // Iterate over all input object arrays - void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const; - void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn); - - void ForAllHoudiniInputObjects(TFunctionRef Fn) const; - // Collect top-level HoudiniInputObjects from this UHoudiniInput. Does not traverse nested input objects. - void GetAllHoudiniInputObjects(TArray& OutObjects) const; - // Collect top-level UHoudiniInputSceneComponent from this UHoudiniInput. Does not traverse nested input objects. - void ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const; - void GetAllHoudiniInputSceneComponents(TArray& OutObjects) const; - void GetAllHoudiniInputSplineComponents(TArray& OutObjects) const; - - // Remove all instances of this input object from all object arrays. - void RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject); - - bool IsAddRotAndScaleAttributesEnabled() const { return bAddRotAndScaleAttributesOnCurves; }; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - - void MarkChanged(const bool& bInChanged) - { - bHasChanged = bInChanged; - SetNeedsToTriggerUpdate(bInChanged); - }; - void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - void MarkDataUploadNeeded(const bool& bInDataUploadNeeded) { bDataUploadNeeded = bInDataUploadNeeded; }; - void MarkAllInputObjectsChanged(const bool& bInChanged); - - void SetSOPInput(const int32& InInputIndex); - void SetObjectPathParameter(const int32& InParmId); - void SetKeepWorldTransform(const bool& bInKeepWorldTransform); - - void SetName(const FString& InName) { Name = InName; }; - void SetLabel(const FString& InLabel) { Label = InLabel; }; - void SetHelp(const FString& InHelp) { Help = InHelp; }; - void SetAssetNodeId(const int32& InNodeId) { AssetNodeId = InNodeId; }; - void SetInputType(const EHoudiniInputType &InInputType, bool& bOutBlueprintStructureModified); - void SetPreviousInputType(const EHoudiniInputType& InType) { PreviousType = InType; }; - void SetPackBeforeMerge(const bool& bInPackBeforeMerge) { bPackBeforeMerge = bInPackBeforeMerge; }; - void SetImportAsReference(const bool& bInImportAsReference) { bImportAsReference = bInImportAsReference; }; - void SetExportLODs(const bool& bInExportLODs) { bExportLODs = bInExportLODs; }; - void SetExportSockets(const bool& bInExportSockets) { bExportSockets = bInExportSockets; }; - void SetExportColliders(const bool& bInExportColliders) { bExportColliders = bInExportColliders; }; - void SetInputNodeId(const int32& InCreatedNodeId) { InputNodeId = InCreatedNodeId; }; - void SetUnrealSplineResolution(const float& InResolution) { UnrealSplineResolution = InResolution; }; - - virtual void SetCookOnCurveChange(const bool & bInCookOnCurveChanged) { bCookOnCurveChanged = bInCookOnCurveChanged; }; - - void ResetDefaultCurveOffset() { DefaultCurveOffset = 0.f; } - - UHoudiniInputObject* CreateNewCurveInputObject(bool& bBlueprintStructureModified); - - void SetGeometryInputObjectsNumber(const int32& NewCount); - void SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount); - - void InsertInputObjectAt(const int32& AtIndex); - void InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void DeleteInputObjectAt(const int32& AtIndex); - void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void DuplicateInputObjectAt(const int32& AtIndex); - void DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void SetInputObjectAt(const int32& AtIndex, UObject* InObject); - void SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject); - - void SetBoundSelectorObjectsNumber(const int32& InNewCount); - void SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor); - void SetWorldInputBoundSelector(const bool& InIsBoundSelector) { bIsWorldInputBoundSelector = InIsBoundSelector; }; - void SetWorldInputBoundSelectorAutoUpdates(const bool& InAutoUpdate) { bWorldInputBoundSelectorAutoUpdate = InAutoUpdate; }; - - // Updates the world selection using bound selectors - // returns false if the selection hasn't changed - bool UpdateWorldSelectionFromBoundSelectors(); - // Updates the world selection - // returns false if the selection hasn't changed - bool UpdateWorldSelection(const TArray& InNewSelection); - - void OnTransformUIExpand(const int32& AtIndex); - - // Sets the input's transform offset - bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - - // Sets the input's transform scale values - void SetPositionOffsetX(float InValue, int32 AtIndex); - void SetPositionOffsetY(float InValue, int32 AtIndex); - void SetPositionOffsetZ(float InValue, int32 AtIndex); - - // Sets the input's transform rotation value - void SetRotationOffsetRoll(float InValue, int32 AtIndex); - void SetRotationOffsetPitch(float InValue, int32 AtIndex); - void SetRotationOffsetYaw(float InValue, int32 AtIndex); - - // Sets the input's transform scale values - void SetScaleOffsetX(float InValue, int32 AtIndex); - void SetScaleOffsetY(float InValue, int32 AtIndex); - void SetScaleOffsetZ(float InValue, int32 AtIndex); - - void SetAddRotAndScaleAttributes(const bool& InValue); - - // Duplicate this object and copy its state to the resulting object. - // This is typically used to transfer state between between template and instance components. - UHoudiniInput* DuplicateAndCopyState(UObject* DestOuter, bool bInCanDeleteHoudiniNodes); - virtual void CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes); - - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() { return bCanDeleteHoudiniNodes; } - - virtual void InvalidateData(); - -protected: - void CopyInputs(TArray& ToInputs, TArray& FromInputs, bool bInCanDeleteHoudiniNodes); - -public: - - // Create a Houdini Spline input component, with an existing Houdini Spline input Object. - // Pass in nullptr to create a default Houdini Spline - UHoudiniInputHoudiniSplineComponent* CreateHoudiniSplineInput( - UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputObject, - const bool & bAttachToParent, - const bool & bAppendToInputArray, - bool& bOutBlueprintStructureModified); - - // Given an existing spline input object, remove the associated - // Houdini Spline component from the owning actor / blueprint. - void RemoveSplineFromInputObject( - UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, - bool& bOutBlueprintStructureModified) const; - - bool HasLandscapeExportTypeChanged () const; - - void SetHasLandscapeExportTypeChanged(const bool InChanged); - -#if WITH_EDITOR - FText GetCurrentSelectionText() const; -#endif - - EHoudiniLandscapeExportType GetLandscapeExportType() const { return LandscapeExportType; }; - - void SetLandscapeExportType(const EHoudiniLandscapeExportType InType) { LandscapeExportType = InType; }; - - virtual void BeginDestroy() override; - -#if WITH_EDITOR - virtual void PostEditUndo() override; -#endif - - FBox GetBounds() const; - -protected: - - // Name of the input / Object path parameter - UPROPERTY() - FString Name; - - // Label of the SOP input or of the object path parameter - UPROPERTY() - FString Label; - - // Input type - UPROPERTY() - EHoudiniInputType Type; - - // Previous type, used to detect input type changes - UPROPERTY(Transient, DuplicateTransient) - EHoudiniInputType PreviousType; - - // NodeId of the asset / object merge we are associated with - UPROPERTY(Transient, DuplicateTransient) - int32 AssetNodeId; - - // NodeId of the created input node - // when there is multiple inputs objects, this will be the merge node. - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputNodeId; - - // SOP input index (-1 if we're an object path input) - UPROPERTY() - int32 InputIndex; - - // Parameter Id of the associated object path parameter (-1 if we're a SOP input) - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 ParmId; - - // Indicates if we're an object path parameter input - UPROPERTY() - bool bIsObjectPathParameter; - - // Array containing all the node Ids created by this input - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - TArray CreatedDataNodeIds; - - // Indicates data connected to this input should be uploaded - UPROPERTY(Transient, DuplicateTransient) - bool bHasChanged; - - // Indicates this input should trigger an HDA update/cook - UPROPERTY(Transient, DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Indicates data for this input needs to be uploaded - // If this is false but the input has changed, we may have just updated in input parameter, - // and don't need to resend all the input data - bool bDataUploadNeeded; - - // Help for this parameter/input - UPROPERTY() - FString Help; - - //------------------------------------------------------------------------------------------------------------------------- - // General Input options - - // Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value - UPROPERTY() - EHoudiniXformType KeepWorldTransform; - - // Indicates that the geometry must be packed before merging it into the input - UPROPERTY() - bool bPackBeforeMerge; - - // Indicates that all the input objects are imported to Houdini as references instead of actual geo - // (for Geo/World/Asset input types only) - UPROPERTY() - bool bImportAsReference = false; - - // Indicates that all LODs in the input should be marshalled to Houdini - UPROPERTY() - bool bExportLODs; - - // Indicates that all sockets in the input should be marshalled to Houdini - UPROPERTY() - bool bExportSockets; - - // Indicates that all colliders in the input should be marshalled to Houdini - UPROPERTY() - bool bExportColliders; - - // Indicates that if trigger cook automatically on curve Input spline modified - UPROPERTY() - bool bCookOnCurveChanged; - - //------------------------------------------------------------------------------------------------------------------------- - // Geometry objects - UPROPERTY() - TArray GeometryInputObjects; - - // Is set to true when static mesh used for geometry input has changed. - UPROPERTY() - bool bStaticMeshChanged; - -#if WITH_EDITORONLY_DATA - // Are the transform UI expanded ? - // Values default to false and are actually added to the array in OnTransformUIExpand() - UPROPERTY() - TArray TransformUIExpanded; -#endif - - //------------------------------------------------------------------------------------------------------------------------- - // Asset inputs - UPROPERTY() - TArray AssetInputObjects; - - // Is set to true if the asset input is actually connected inside Houdini. - UPROPERTY() - bool bInputAssetConnectedInHoudini; - - //------------------------------------------------------------------------------------------------------------------------- - // Curve/Spline inputs - UPROPERTY() - TArray CurveInputObjects; - - // Offset used when using muiltiple curves - UPROPERTY() - float DefaultCurveOffset; - - // Set this to true to add rot and scale attributes on curve inputs. - UPROPERTY() - bool bAddRotAndScaleAttributesOnCurves; - - //------------------------------------------------------------------------------------------------------------------------- - // Landscape inputs - UPROPERTY() - TArray LandscapeInputObjects; - - UPROPERTY() - bool bLandscapeHasExportTypeChanged = false; - - //------------------------------------------------------------------------------------------------------------------------- - // World inputs - UPROPERTY() - TArray WorldInputObjects; - - // Objects used for automatic bound selection - UPROPERTY() - TArray WorldInputBoundSelectorObjects; - - // Indicates that this world input is in "BoundSelector" mode - UPROPERTY() - bool bIsWorldInputBoundSelector; - - // Indicates that selected actors by the bound selectors should update automatically - UPROPERTY() - bool bWorldInputBoundSelectorAutoUpdate; - - // Resolution used when converting unreal splines to houdini curves - UPROPERTY() - float UnrealSplineResolution; - - //------------------------------------------------------------------------------------------------------------------------- - // Skeletal Inputs - UPROPERTY() - TArray SkeletalInputObjects; - -public: - - // This array is to record the last insert action, for undo input insertion actions. - UPROPERTY(Transient, DuplicateTransient) - TArray LastInsertedInputs; - - // This array is to cache the action of last undo delete action, and redo that action. - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - TArray LastUndoDeletedInputs; - - - // Indicates that the landscape input's source landscape should be updated instead of creating a new component - UPROPERTY() - bool bUpdateInputLandscape; - - // Indicates if the landscape should be exported as heightfield, mesh or points - UPROPERTY() - EHoudiniLandscapeExportType LandscapeExportType = EHoudiniLandscapeExportType::Heightfield; - - // Is set to true when landscape input is set to selection only. - UPROPERTY() - bool bLandscapeExportSelectionOnly = false; - - // Is set to true when the automatic selection of landscape component is active - UPROPERTY() - bool bLandscapeAutoSelectComponent = false; - - // Is set to true when materials are to be exported. - UPROPERTY() - bool bLandscapeExportMaterials = false; - - // Is set to true when lightmap information export is desired. - UPROPERTY() - bool bLandscapeExportLighting = false; - - // Is set to true when uvs should be exported in [0,1] space. - UPROPERTY() - bool bLandscapeExportNormalizedUVs = false; - - // Is set to true when uvs should be exported for each tile separately. - UPROPERTY() - bool bLandscapeExportTileUVs = false; - - UPROPERTY() - bool bCanDeleteHoudiniNodes = true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreTypes.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" +#include "Misc/Optional.h" + +#include "HoudiniInputTypes.h" +#include "HoudiniInputObject.h" + +#include "GameFramework/Actor.h" +#include "LandscapeProxy.h" + +#include "HoudiniInput.generated.h" + + + +class FReply; + +enum class EHoudiniCurveType : int8; +enum class ECheckBoxState : unsigned char; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject +{ + GENERATED_BODY() + +public: + + UHoudiniInput(); + + // Equality operator, + // We consider two inputs equals if they have the same name, objparam state, and input index/parmId + // TODO: ParmId might be an incorrect condition + bool operator==(const UHoudiniInput& other) const + { + return (bIsObjectPathParameter == other.bIsObjectPathParameter + && InputIndex == other.InputIndex + && ParmId == other.ParmId + && Name.Equals(other.Name) + && Label.Equals(other.Label)); + } + + bool Matches(const UHoudiniInput& other) const { return (*this == other); }; + + // Helper function returning a string from an InputType + static FString InputTypeToString(const EHoudiniInputType& InInputType); + + // Helper function returning an InputType from a string + static EHoudiniInputType StringToInputType(const FString& InInputTypeString); + // Helper function returning a Houdini curve type from a string + static EHoudiniCurveType StringToHoudiniCurveType(const FString& CurveTypeString); + // Helper function returning a Houdini curve method from a string + static EHoudiniCurveMethod StringToHoudiniCurveMethod(const FString& CurveMethodString); + + // Helper function indicating what classes are supported by an input type + static TArray GetAllowedClasses(const EHoudiniInputType& InInputType); + + // Helper function indicating if an object is supported by an input type + static bool IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject); + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + // Returns the NodeId of the asset / object merge we are associated with + int32 GetAssetNodeId() const { return AssetNodeId; }; + // For objpath parameter, return the associated ParamId, -1 if we're a Geo input + int32 GetParameterId() const { return bIsObjectPathParameter ? ParmId : -1; }; + // Returns the NodeId of the node plugged into this input + int32 GetInputNodeId() const { return InputNodeId; }; + + // For Geo inputs, returns the InputIndex, -1 if we're an object path parameter + int32 GetInputIndex() const { return bIsObjectPathParameter ? -1 : InputIndex; }; + // Return the array containing all the nodes created for this input's data + TArray& GetCreatedDataNodeIds() { return CreatedDataNodeIds; }; + // Returns the current input type + EHoudiniInputType GetInputType() const { return Type; }; + // Returns the previous input type + EHoudiniInputType GetPreviousInputType() const { return PreviousType; }; + // Returns the current input type as a string + FString GetInputTypeAsString() const { return InputTypeToString(Type); }; + + EHoudiniXformType GetDefaultXTransformType(); + // Returns true when this input's Transform Type is set to NONE, + // false if set to INTO_THIS_OBJECT, 2 will use the input's type default value + bool GetKeepWorldTransform() const; + // Indicates if this input has changed and should be updated + bool HasChanged(); + // Indicates if this input needs to trigger an update + bool NeedsToTriggerUpdate(); + // Indicates this input should upload its data + bool IsDataUploadNeeded(); + // Indicates this input's transform need to be uploaded + bool IsTransformUploadNeeded(); + // Indicates if this input type has been changed + bool HasInputTypeChanged() const { return PreviousType != EHoudiniInputType::Invalid ? PreviousType != Type : false; } + // + bool GetUpdateInputLandscape() const; + + void SetUpdateInputLandscape(const bool bInUpdateInputLandcape); + + FString GetName() const { return Name; }; + FString GetLabel() const { return Label; }; + FString GetHelp() const { return Help; }; + bool GetPackBeforeMerge() const { return bPackBeforeMerge; }; + bool GetImportAsReference() const { return bImportAsReference; }; + bool GetExportLODs() const { return bExportLODs; }; + bool GetExportSockets() const { return bExportSockets; }; + bool GetExportColliders() const { return bExportColliders; }; + bool IsObjectPathParameter() const { return bIsObjectPathParameter; }; + float GetUnrealSplineResolution() const { return UnrealSplineResolution; }; + + virtual bool GetCookOnCurveChange() const { return bCookOnCurveChanged; }; + + TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType); + const TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const; + TArray* GetBoundSelectorObjectArray(); + const TArray* GetBoundSelectorObjectArray() const; + + UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex); + const UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex) const; + AActor* GetBoundSelectorObjectAt(const int32& AtIndex); + + UHoudiniInputObject* GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + UObject* GetInputObjectAt(const int32& AtIndex); + UObject* GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + int32 GetNumberOfInputObjects(); + int32 GetNumberOfInputObjects(const EHoudiniInputType& InType); + + int32 GetNumberOfInputMeshes(); + int32 GetNumberOfInputMeshes(const EHoudiniInputType& InType); + + int32 GetNumberOfBoundSelectorObjects() const; + + bool IsWorldInputBoundSelector() const { return bIsWorldInputBoundSelector; }; + bool GetWorldInputBoundSelectorAutoUpdates() const { return bWorldInputBoundSelectorAutoUpdate; }; + + FString GetNodeBaseName() const; + + bool IsTransformUIExpanded(const int32& AtIndex); + + // Return the transform offset for a given input object + FTransform* GetTransformOffset(const int32& AtIndex); + const FTransform GetTransformOffset(const int32& AtIndex) const; + + // Returns the position offset for a given input object + TOptional GetPositionOffsetX(int32 AtIndex) const; + TOptional GetPositionOffsetY(int32 AtIndex) const; + TOptional GetPositionOffsetZ(int32 AtIndex) const; + + // Returns the rotation offset for a given input object + TOptional GetRotationOffsetRoll(int32 AtIndex) const; + TOptional GetRotationOffsetPitch(int32 AtIndex) const; + TOptional GetRotationOffsetYaw(int32 AtIndex) const; + + // Returns the scale offset for a given input object + TOptional GetScaleOffsetX(int32 AtIndex) const; + TOptional GetScaleOffsetY(int32 AtIndex) const; + TOptional GetScaleOffsetZ(int32 AtIndex) const; + + // Returns true if the object is one of our input object for the given type + bool ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const; + + // Get all input object arrays + TArray*> GetAllObjectArrays() const; + TArray*> GetAllObjectArrays(); + + // Iterate over all input object arrays + void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const; + void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn); + + void ForAllHoudiniInputObjects(TFunctionRef Fn) const; + // Collect top-level HoudiniInputObjects from this UHoudiniInput. Does not traverse nested input objects. + void GetAllHoudiniInputObjects(TArray& OutObjects) const; + // Collect top-level UHoudiniInputSceneComponent from this UHoudiniInput. Does not traverse nested input objects. + void ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const; + void GetAllHoudiniInputSceneComponents(TArray& OutObjects) const; + void GetAllHoudiniInputSplineComponents(TArray& OutObjects) const; + + // Remove all instances of this input object from all object arrays. + void RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject); + + bool IsAddRotAndScaleAttributesEnabled() const { return bAddRotAndScaleAttributesOnCurves; }; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + + void MarkChanged(const bool& bInChanged) + { + bHasChanged = bInChanged; + SetNeedsToTriggerUpdate(bInChanged); + }; + void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + void MarkDataUploadNeeded(const bool& bInDataUploadNeeded) { bDataUploadNeeded = bInDataUploadNeeded; }; + void MarkAllInputObjectsChanged(const bool& bInChanged); + + void SetSOPInput(const int32& InInputIndex); + void SetObjectPathParameter(const int32& InParmId); + void SetKeepWorldTransform(const bool& bInKeepWorldTransform); + + void SetName(const FString& InName) { Name = InName; }; + void SetLabel(const FString& InLabel) { Label = InLabel; }; + void SetHelp(const FString& InHelp) { Help = InHelp; }; + void SetAssetNodeId(const int32& InNodeId) { AssetNodeId = InNodeId; }; + void SetInputType(const EHoudiniInputType &InInputType, bool& bOutBlueprintStructureModified); + void SetPreviousInputType(const EHoudiniInputType& InType) { PreviousType = InType; }; + void SetPackBeforeMerge(const bool& bInPackBeforeMerge) { bPackBeforeMerge = bInPackBeforeMerge; }; + void SetImportAsReference(const bool& bInImportAsReference) { bImportAsReference = bInImportAsReference; }; + void SetExportLODs(const bool& bInExportLODs) { bExportLODs = bInExportLODs; }; + void SetExportSockets(const bool& bInExportSockets) { bExportSockets = bInExportSockets; }; + void SetExportColliders(const bool& bInExportColliders) { bExportColliders = bInExportColliders; }; + void SetInputNodeId(const int32& InCreatedNodeId) { InputNodeId = InCreatedNodeId; }; + void SetUnrealSplineResolution(const float& InResolution) { UnrealSplineResolution = InResolution; }; + + virtual void SetCookOnCurveChange(const bool & bInCookOnCurveChanged) { bCookOnCurveChanged = bInCookOnCurveChanged; }; + + void ResetDefaultCurveOffset() { DefaultCurveOffset = 0.f; } + + UHoudiniInputObject* CreateNewCurveInputObject(bool& bBlueprintStructureModified); + + void SetGeometryInputObjectsNumber(const int32& NewCount); + void SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount); + + void InsertInputObjectAt(const int32& AtIndex); + void InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void DeleteInputObjectAt(const int32& AtIndex); + void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void DuplicateInputObjectAt(const int32& AtIndex); + void DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void SetInputObjectAt(const int32& AtIndex, UObject* InObject); + void SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject); + + void SetBoundSelectorObjectsNumber(const int32& InNewCount); + void SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor); + void SetWorldInputBoundSelector(const bool& InIsBoundSelector) { bIsWorldInputBoundSelector = InIsBoundSelector; }; + void SetWorldInputBoundSelectorAutoUpdates(const bool& InAutoUpdate) { bWorldInputBoundSelectorAutoUpdate = InAutoUpdate; }; + + // Updates the world selection using bound selectors + // returns false if the selection hasn't changed + bool UpdateWorldSelectionFromBoundSelectors(); + // Updates the world selection + // returns false if the selection hasn't changed + bool UpdateWorldSelection(const TArray& InNewSelection); + + void OnTransformUIExpand(const int32& AtIndex); + + // Sets the input's transform offset + bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + + // Sets the input's transform scale values + void SetPositionOffsetX(float InValue, int32 AtIndex); + void SetPositionOffsetY(float InValue, int32 AtIndex); + void SetPositionOffsetZ(float InValue, int32 AtIndex); + + // Sets the input's transform rotation value + void SetRotationOffsetRoll(float InValue, int32 AtIndex); + void SetRotationOffsetPitch(float InValue, int32 AtIndex); + void SetRotationOffsetYaw(float InValue, int32 AtIndex); + + // Sets the input's transform scale values + void SetScaleOffsetX(float InValue, int32 AtIndex); + void SetScaleOffsetY(float InValue, int32 AtIndex); + void SetScaleOffsetZ(float InValue, int32 AtIndex); + + void SetAddRotAndScaleAttributes(const bool& InValue); + + // Duplicate this object and copy its state to the resulting object. + // This is typically used to transfer state between between template and instance components. + UHoudiniInput* DuplicateAndCopyState(UObject* DestOuter, bool bInCanDeleteHoudiniNodes); + virtual void CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes); + + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() { return bCanDeleteHoudiniNodes; } + + virtual void InvalidateData(); + +protected: + void CopyInputs(TArray& ToInputs, TArray& FromInputs, bool bInCanDeleteHoudiniNodes); + +public: + + // Create a Houdini Spline input component, with an existing Houdini Spline input Object. + // Pass in nullptr to create a default Houdini Spline + UHoudiniInputHoudiniSplineComponent* CreateHoudiniSplineInput( + UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputObject, + const bool & bAttachToParent, + const bool & bAppendToInputArray, + bool& bOutBlueprintStructureModified); + + // Given an existing spline input object, remove the associated + // Houdini Spline component from the owning actor / blueprint. + void RemoveSplineFromInputObject( + UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, + bool& bOutBlueprintStructureModified) const; + + bool HasLandscapeExportTypeChanged () const; + + void SetHasLandscapeExportTypeChanged(const bool InChanged); + +#if WITH_EDITOR + FText GetCurrentSelectionText() const; +#endif + + EHoudiniLandscapeExportType GetLandscapeExportType() const { return LandscapeExportType; }; + + void SetLandscapeExportType(const EHoudiniLandscapeExportType InType) { LandscapeExportType = InType; }; + + virtual void BeginDestroy() override; + +#if WITH_EDITOR + virtual void PostEditUndo() override; +#endif + + FBox GetBounds() const; + +protected: + + // Name of the input / Object path parameter + UPROPERTY() + FString Name; + + // Label of the SOP input or of the object path parameter + UPROPERTY() + FString Label; + + // Input type + UPROPERTY() + EHoudiniInputType Type; + + // Previous type, used to detect input type changes + UPROPERTY(Transient, DuplicateTransient) + EHoudiniInputType PreviousType; + + // NodeId of the asset / object merge we are associated with + UPROPERTY(Transient, DuplicateTransient) + int32 AssetNodeId; + + // NodeId of the created input node + // when there is multiple inputs objects, this will be the merge node. + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputNodeId; + + // SOP input index (-1 if we're an object path input) + UPROPERTY() + int32 InputIndex; + + // Parameter Id of the associated object path parameter (-1 if we're a SOP input) + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 ParmId; + + // Indicates if we're an object path parameter input + UPROPERTY() + bool bIsObjectPathParameter; + + // Array containing all the node Ids created by this input + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + TArray CreatedDataNodeIds; + + // Indicates data connected to this input should be uploaded + UPROPERTY(Transient, DuplicateTransient) + bool bHasChanged; + + // Indicates this input should trigger an HDA update/cook + UPROPERTY(Transient, DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Indicates data for this input needs to be uploaded + // If this is false but the input has changed, we may have just updated in input parameter, + // and don't need to resend all the input data + bool bDataUploadNeeded; + + // Help for this parameter/input + UPROPERTY() + FString Help; + + //------------------------------------------------------------------------------------------------------------------------- + // General Input options + + // Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value + UPROPERTY() + EHoudiniXformType KeepWorldTransform; + + // Indicates that the geometry must be packed before merging it into the input + UPROPERTY() + bool bPackBeforeMerge; + + // Indicates that all the input objects are imported to Houdini as references instead of actual geo + // (for Geo/World/Asset input types only) + UPROPERTY() + bool bImportAsReference = false; + + // Indicates that all LODs in the input should be marshalled to Houdini + UPROPERTY() + bool bExportLODs; + + // Indicates that all sockets in the input should be marshalled to Houdini + UPROPERTY() + bool bExportSockets; + + // Indicates that all colliders in the input should be marshalled to Houdini + UPROPERTY() + bool bExportColliders; + + // Indicates that if trigger cook automatically on curve Input spline modified + UPROPERTY() + bool bCookOnCurveChanged; + + //------------------------------------------------------------------------------------------------------------------------- + // Geometry objects + UPROPERTY() + TArray GeometryInputObjects; + + // Is set to true when static mesh used for geometry input has changed. + UPROPERTY() + bool bStaticMeshChanged; + +#if WITH_EDITORONLY_DATA + // Are the transform UI expanded ? + // Values default to false and are actually added to the array in OnTransformUIExpand() + UPROPERTY() + TArray TransformUIExpanded; +#endif + + //------------------------------------------------------------------------------------------------------------------------- + // Asset inputs + UPROPERTY() + TArray AssetInputObjects; + + // Is set to true if the asset input is actually connected inside Houdini. + UPROPERTY() + bool bInputAssetConnectedInHoudini; + + //------------------------------------------------------------------------------------------------------------------------- + // Curve/Spline inputs + UPROPERTY() + TArray CurveInputObjects; + + // Offset used when using muiltiple curves + UPROPERTY() + float DefaultCurveOffset; + + // Set this to true to add rot and scale attributes on curve inputs. + UPROPERTY() + bool bAddRotAndScaleAttributesOnCurves; + + //------------------------------------------------------------------------------------------------------------------------- + // Landscape inputs + UPROPERTY() + TArray LandscapeInputObjects; + + UPROPERTY() + bool bLandscapeHasExportTypeChanged = false; + + //------------------------------------------------------------------------------------------------------------------------- + // World inputs + UPROPERTY() + TArray WorldInputObjects; + + // Objects used for automatic bound selection + UPROPERTY() + TArray WorldInputBoundSelectorObjects; + + // Indicates that this world input is in "BoundSelector" mode + UPROPERTY() + bool bIsWorldInputBoundSelector; + + // Indicates that selected actors by the bound selectors should update automatically + UPROPERTY() + bool bWorldInputBoundSelectorAutoUpdate; + + // Resolution used when converting unreal splines to houdini curves + UPROPERTY() + float UnrealSplineResolution; + + //------------------------------------------------------------------------------------------------------------------------- + // Skeletal Inputs + UPROPERTY() + TArray SkeletalInputObjects; + +public: + + // This array is to record the last insert action, for undo input insertion actions. + UPROPERTY(Transient, DuplicateTransient) + TArray LastInsertedInputs; + + // This array is to cache the action of last undo delete action, and redo that action. + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + TArray LastUndoDeletedInputs; + + + // Indicates that the landscape input's source landscape should be updated instead of creating a new component + UPROPERTY() + bool bUpdateInputLandscape; + + // Indicates if the landscape should be exported as heightfield, mesh or points + UPROPERTY() + EHoudiniLandscapeExportType LandscapeExportType = EHoudiniLandscapeExportType::Heightfield; + + // Is set to true when landscape input is set to selection only. + UPROPERTY() + bool bLandscapeExportSelectionOnly = false; + + // Is set to true when the automatic selection of landscape component is active + UPROPERTY() + bool bLandscapeAutoSelectComponent = false; + + // Is set to true when materials are to be exported. + UPROPERTY() + bool bLandscapeExportMaterials = false; + + // Is set to true when lightmap information export is desired. + UPROPERTY() + bool bLandscapeExportLighting = false; + + // Is set to true when uvs should be exported in [0,1] space. + UPROPERTY() + bool bLandscapeExportNormalizedUVs = false; + + // Is set to true when uvs should be exported for each tile separately. + UPROPERTY() + bool bLandscapeExportTileUVs = false; + + UPROPERTY() + bool bCanDeleteHoudiniNodes = true; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp index fe5f2def3..878ea8bc2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp @@ -1,1864 +1,1864 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInputObject.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInput.h" - -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "Engine/DataTable.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "Engine/Brush.h" -#include "Engine/Engine.h" -#include "GameFramework/Volume.h" -#include "Camera/CameraComponent.h" -#include "FoliageType_InstancedStaticMesh.h" - -#include "Model.h" -#include "Engine/Brush.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "Kismet/KismetSystemLibrary.h" - -//----------------------------------------------------------------------------------------------------------------------------- -// Constructors -//----------------------------------------------------------------------------------------------------------------------------- - -// -UHoudiniInputObject::UHoudiniInputObject(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , Transform(FTransform::Identity) - , Type(EHoudiniInputObjectType::Invalid) - , InputNodeId(-1) - , InputObjectNodeId(-1) - , bHasChanged(false) - , bNeedsToTriggerUpdate(false) - , bTransformChanged(false) - , bImportAsReference(false) - , bCanDeleteHoudiniNodes(true) -{ - Guid = FGuid::NewGuid(); -} - -// -UHoudiniInputStaticMesh::UHoudiniInputStaticMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSkeletalMesh::UHoudiniInputSkeletalMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSceneComponent::UHoudiniInputSceneComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputMeshComponent::UHoudiniInputMeshComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputInstancedMeshComponent::UHoudiniInputInstancedMeshComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSplineComponent::UHoudiniInputSplineComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , NumberOfSplineControlPoints(-1) - , SplineLength(-1.0f) - , SplineResolution(-1.0f) - , SplineClosed(false) -{ - -} - -// -UHoudiniInputCameraComponent::UHoudiniInputCameraComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , FOV(0.0f) - , AspectRatio(1.0f) - , bIsOrthographic(false) - , OrthoWidth(2.0f) - , OrthoNearClipPlane(0.0f) - , OrthoFarClipPlane(-1.0f) -{ - -} - -// Returns true if the attached actor's (parent) transform has been modified -bool -UHoudiniInputSplineComponent::HasActorTransformChanged() const -{ - return false; -} - -// Returns true if the attached component's transform has been modified -bool -UHoudiniInputSplineComponent::HasComponentTransformChanged() const -{ - return false; -} - -// Return true if the component itself has been modified -bool -UHoudiniInputSplineComponent::HasComponentChanged() const -{ - USplineComponent* SplineComponent = Cast(InputObject.LoadSynchronous()); - - if (!SplineComponent) - return false; - - if (SplineClosed != SplineComponent->IsClosedLoop()) - return true; - - - if (SplineComponent->GetNumberOfSplinePoints() != NumberOfSplineControlPoints) - return true; - - for (int32 n = 0; n < SplineComponent->GetNumberOfSplinePoints(); ++n) - { - const FTransform &CurSplineComponentTransform = SplineComponent->GetTransformAtSplinePoint(n, ESplineCoordinateSpace::Local); - const FTransform &CurInputTransform = SplineControlPoints[n]; - - if (CurInputTransform.GetLocation() != CurSplineComponentTransform.GetLocation()) - return true; - - if (CurInputTransform.GetRotation().Rotator() != CurSplineComponentTransform.GetRotation().Rotator()) - return true; - - if (CurInputTransform.GetScale3D() != CurSplineComponentTransform.GetScale3D()) - return true; - } - - return false; -} - -bool -UHoudiniInputSplineComponent::HasSplineComponentChanged(float fCurrentSplineResolution) const -{ - return false; -} - -// -UHoudiniInputHoudiniSplineComponent::UHoudiniInputHoudiniSplineComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , CurveType(EHoudiniCurveType::Polygon) - , CurveMethod(EHoudiniCurveMethod::CVs) - , Reversed(false) -{ - -} - -// -UHoudiniInputHoudiniAsset::UHoudiniInputHoudiniAsset(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , AssetOutputIndex(-1) -{ - -} - -// -UHoudiniInputActor::UHoudiniInputActor(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , LastUpdateNumComponentsAdded(0) - , LastUpdateNumComponentsRemoved(0) -{ - -} - -// -UHoudiniInputLandscape::UHoudiniInputLandscape(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputBrush::UHoudiniInputBrush() - : CombinedModel(nullptr) - , bIgnoreInputObject(false) -{ - -} - -//----------------------------------------------------------------------------------------------------------------------------- -// Accessors -//----------------------------------------------------------------------------------------------------------------------------- - -UObject* -UHoudiniInputObject::GetObject() const -{ - return InputObject.LoadSynchronous(); -} - -UStaticMesh* -UHoudiniInputStaticMesh::GetStaticMesh() const -{ - return Cast(InputObject.LoadSynchronous()); -} - -UBlueprint* -UHoudiniInputStaticMesh::GetBlueprint() const -{ - return Cast(InputObject.LoadSynchronous()); -} - -bool UHoudiniInputStaticMesh::bIsBlueprint() const -{ - return (InputObject.IsValid() && InputObject.Get()->IsA()); -} - -USkeletalMesh* -UHoudiniInputSkeletalMesh::GetSkeletalMesh() -{ - return Cast(InputObject.LoadSynchronous()); -} - -USceneComponent* -UHoudiniInputSceneComponent::GetSceneComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UStaticMeshComponent* -UHoudiniInputMeshComponent::GetStaticMeshComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UStaticMesh* -UHoudiniInputMeshComponent::GetStaticMesh() -{ - return StaticMesh.Get(); -} - -UInstancedStaticMeshComponent* -UHoudiniInputInstancedMeshComponent::GetInstancedStaticMeshComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -USplineComponent* -UHoudiniInputSplineComponent::GetSplineComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UHoudiniSplineComponent* -UHoudiniInputHoudiniSplineComponent::GetCurveComponent() const -{ - return Cast(GetObject()); - //return Cast(InputObject.LoadSynchronous()); -} - -UCameraComponent* -UHoudiniInputCameraComponent::GetCameraComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UHoudiniAssetComponent* -UHoudiniInputHoudiniAsset::GetHoudiniAssetComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -AActor* -UHoudiniInputActor::GetActor() -{ - return Cast(InputObject.LoadSynchronous()); -} - -ALandscapeProxy* -UHoudiniInputLandscape::GetLandscapeProxy() -{ - return Cast(InputObject.LoadSynchronous()); -} - -void -UHoudiniInputLandscape::SetLandscapeProxy(UObject* InLandscapeProxy) -{ - UObject* LandscapeProxy = Cast(InLandscapeProxy); - if (LandscapeProxy) - InputObject = LandscapeProxy; -} - -ABrush* -UHoudiniInputBrush::GetBrush() const -{ - return Cast(InputObject.LoadSynchronous()); -} - - -//----------------------------------------------------------------------------------------------------------------------------- -// CREATE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -UHoudiniInputObject * -UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InName) -{ - if (!InObject) - return nullptr; - - UHoudiniInputObject* HoudiniInputObject = nullptr; - - EHoudiniInputObjectType InputObjectType = GetInputObjectTypeFromObject(InObject); - switch (InputObjectType) - { - case EHoudiniInputObjectType::Object: - HoudiniInputObject = UHoudiniInputObject::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::StaticMesh: - HoudiniInputObject = UHoudiniInputStaticMesh::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::SkeletalMesh: - HoudiniInputObject = UHoudiniInputSkeletalMesh::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::SceneComponent: - // Do not create input objects for unknown scene component! - //HoudiniInputObject = UHoudiniInputSceneComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::StaticMeshComponent: - HoudiniInputObject = UHoudiniInputMeshComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - HoudiniInputObject = UHoudiniInputInstancedMeshComponent::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::SplineComponent: - HoudiniInputObject = UHoudiniInputSplineComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::HoudiniSplineComponent: - HoudiniInputObject = UHoudiniInputHoudiniSplineComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::HoudiniAssetActor: - { - AHoudiniAssetActor* HoudiniActor = Cast(InObject); - if (HoudiniActor) - { - HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniActor->GetHoudiniAssetComponent(), InOuter, InName); - } - else - { - HoudiniInputObject = nullptr; - } - } - break; - - case EHoudiniInputObjectType::HoudiniAssetComponent: - HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::Actor: - HoudiniInputObject = UHoudiniInputActor::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Landscape: - HoudiniInputObject = UHoudiniInputLandscape::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Brush: - HoudiniInputObject = UHoudiniInputBrush::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::CameraComponent: - HoudiniInputObject = UHoudiniInputCameraComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::DataTable: - HoudiniInputObject = UHoudiniInputDataTable::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: - HoudiniInputObject = UHoudiniInputFoliageType_InstancedStaticMesh::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Invalid: - default: - break; - } - - return HoudiniInputObject; -} - - -UHoudiniInputObject * -UHoudiniInputInstancedMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_ISMC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputInstancedMeshComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::InstancedStaticMeshComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SMC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputMeshComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputMeshComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMeshComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Spline_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSplineComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSplineComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SplineComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputHoudiniSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_HoudiniSpline_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputHoudiniSplineComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniSplineComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputCameraComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Camera_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputCameraComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputCameraComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputCameraComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::CameraComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputHoudiniAsset::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - UHoudiniAssetComponent * InHoudiniAssetComponent = Cast(InObject); - if (!InHoudiniAssetComponent) - return nullptr; - - FString InputObjectNameStr = "HoudiniInputObject_HAC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniAsset::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputHoudiniAsset * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputHoudiniAsset::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniAssetComponent; - - HoudiniInputObject->InputNodeId = InHoudiniAssetComponent->GetAssetId(); - HoudiniInputObject->InputObjectNodeId = InHoudiniAssetComponent->GetAssetId(); - - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputSceneComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SceneComp_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSceneComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSceneComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSceneComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SceneComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputLandscape::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Landscape_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputLandscape::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputLandscape * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputLandscape::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Landscape; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputBrush * -UHoudiniInputBrush::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Brush_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputBrush::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputBrush * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputBrush::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Brush; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputActor::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Actor_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputActor::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputActor * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputActor::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Actor; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SM_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputStaticMesh::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputStaticMesh * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMesh; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -// void UHoudiniInputStaticMesh::DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputStaticMesh*& OutNewInput) -// { -// UHoudiniInputStaticMesh* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); -// OutNewInput = NewInput; -// OutNewInput->CopyStateFrom(this, false); -// } - -void -UHoudiniInputStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - UHoudiniInputStaticMesh* StaticMeshInput = Cast(InInput); - check(InInput); - - TArray PrevInputs = BlueprintStaticMeshes; - - Super::CopyStateFrom(StaticMeshInput, bCopyAllProperties); - - const int32 NumInputs = StaticMeshInput->BlueprintStaticMeshes.Num(); - BlueprintStaticMeshes = PrevInputs; - TArray StaleInputs(BlueprintStaticMeshes); - - BlueprintStaticMeshes.SetNum(NumInputs); - - for (int i = 0; i < NumInputs; ++i) - { - UHoudiniInputStaticMesh* FromInput = StaticMeshInput->BlueprintStaticMeshes[i]; - UHoudiniInputStaticMesh* ToInput = BlueprintStaticMeshes[i]; - - if (!FromInput) - { - BlueprintStaticMeshes[i] = nullptr; - continue; - } - - if (ToInput) - { - // Check whether the ToInput can be reused - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - if (!bIsValid) - { - ToInput = nullptr; - } - } - - if (ToInput) - { - // We have a reusable input - ToInput->CopyStateFrom(FromInput, true); - } - else - { - // We need to create a new input - ToInput = Cast(FromInput->DuplicateAndCopyState(this)); - } - - BlueprintStaticMeshes[i] = ToInput; - } - - for(UHoudiniInputStaticMesh* StaleInput : StaleInputs) - { - if (!StaleInput) - continue; - StaleInput->InvalidateData(); - } -} - -void -UHoudiniInputStaticMesh::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - Super::SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) - { - if (!Input) - continue; - Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -void -UHoudiniInputStaticMesh::InvalidateData() -{ - for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) - { - if (!Input) - continue; - Input->InvalidateData(); - } - - Super::InvalidateData(); -} - - -UHoudiniInputObject * -UHoudiniInputSkeletalMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SkelMesh_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSkeletalMesh::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSkeletalMesh * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSkeletalMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SkeletalMesh; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputObject::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputObject::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputObject * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputObject::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Object; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -bool -UHoudiniInputObject::Matches(const UHoudiniInputObject& Other) const -{ - return (Type == Other.Type - && InputNodeId == Other.InputNodeId - && InputObjectNodeId == Other.InputObjectNodeId - ); -} - -//----------------------------------------------------------------------------------------------------------------------------- -// DELETE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -void -UHoudiniInputObject::InvalidateData() -{ - // If valid, mark our input nodes for deletion.. - if (this->IsA() || !bCanDeleteHoudiniNodes) - { - // Unless if we're a HoudiniAssetInput! we don't want to delete the other HDA's node! - // just invalidate the node IDs! - InputNodeId = -1; - InputObjectNodeId = -1; - return; - } - - if (InputNodeId >= 0) - { - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); - InputNodeId = -1; - } - - // ... and the parent OBJ as well to clean up - if (InputObjectNodeId >= 0) - { - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputObjectNodeId); - InputObjectNodeId = -1; - } - - -} - -void -UHoudiniInputObject::BeginDestroy() -{ - // Invalidate and mark our input node for deletion - InvalidateData(); - - Super::BeginDestroy(); -} - -//----------------------------------------------------------------------------------------------------------------------------- -// UPDATE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -void -UHoudiniInputObject::Update(UObject * InObject) -{ - InputObject = InObject; -} - -void -UHoudiniInputStaticMesh::Update(UObject * InObject) -{ - // Nothing to do - Super::Update(InObject); - // Static Mesh input accepts SM, BP, FoliageType_InstancedStaticMesh (static mesh) and FoliageType_Actor (if blueprint actor). - UStaticMesh* SM = Cast(InObject); - UBlueprint* BP = Cast(InObject); - - ensure(SM || BP); -} - -void -UHoudiniInputSkeletalMesh::Update(UObject * InObject) -{ - // Nothing to do - Super::Update(InObject); - - USkeletalMesh* SkelMesh = Cast(InObject); - ensure(SkelMesh); -} - -void -UHoudiniInputSceneComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - USceneComponent* USC = Cast(InObject); - ensure(USC); - if (USC) - { - Transform = USC->GetComponentTransform(); - } -} - - -bool -UHoudiniInputSceneComponent::HasActorTransformChanged() const -{ - // Returns true if the attached actor's (parent) transform has been modified - USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); - if (!MyComp || MyComp->IsPendingKill()) - return false; - - AActor* MyActor = MyComp->GetOwner(); - if (!MyActor) - return false; - - return (!ActorTransform.Equals(MyActor->GetTransform())); -} - - -bool -UHoudiniInputSceneComponent::HasComponentTransformChanged() const -{ - // Returns true if the attached actor's (parent) transform has been modified - USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); - if (!MyComp || MyComp->IsPendingKill()) - return false; - - return !Transform.Equals(MyComp->GetComponentTransform()); -} - - -bool -UHoudiniInputSceneComponent::HasComponentChanged() const -{ - // Should return true if the component itself has been modified - // Should be overriden in child classes - return false; -} - - -bool -UHoudiniInputMeshComponent::HasComponentChanged() const -{ - UStaticMeshComponent* SMC = Cast(InputObject.LoadSynchronous()); - UStaticMesh* MySM = StaticMesh.Get(); - - // Return true if SMC's static mesh has been modified - return (MySM != SMC->GetStaticMesh()); -} - -bool -UHoudiniInputCameraComponent::HasComponentChanged() const -{ - UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); - if (Camera && !Camera->IsPendingKill()) - { - bool bOrtho = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; - if (bOrtho != bIsOrthographic) - return true; - - if (Camera->FieldOfView != FOV) - return true; - - if (Camera->AspectRatio != AspectRatio) - return true; - - if (Camera->OrthoWidth != OrthoWidth) - return true; - - if (Camera->OrthoNearClipPlane != OrthoNearClipPlane) - return true; - - if (Camera->OrthoFarClipPlane != OrthoFarClipPlane) - return true; - } - - return false; -} - - - -void -UHoudiniInputCameraComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); - - ensure(Camera); - - if (Camera && !Camera->IsPendingKill()) - { - bIsOrthographic = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; - FOV = Camera->FieldOfView; - AspectRatio = Camera->AspectRatio; - OrthoWidth = Camera->OrthoWidth; - OrthoNearClipPlane = Camera->OrthoNearClipPlane; - OrthoFarClipPlane = Camera->OrthoFarClipPlane; - } -} - -void -UHoudiniInputMeshComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UStaticMeshComponent* SMC = Cast(InObject); - - ensure(SMC); - - if (SMC) - { - StaticMesh = TSoftObjectPtr(SMC->GetStaticMesh()); - - TArray Materials = SMC->GetMaterials(); - for (auto CurrentMat : Materials) - { - // TODO: Update material ref here - FString MatRef; - MeshComponentsMaterials.Add(MatRef); - } - } -} - -void -UHoudiniInputInstancedMeshComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UInstancedStaticMeshComponent* ISMC = Cast(InObject); - - ensure(ISMC); - - if (ISMC) - { - uint32 InstanceCount = ISMC->GetInstanceCount(); - InstanceTransforms.SetNum(InstanceCount); - - // Copy the instances' transforms - for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) - { - FTransform CurTransform = FTransform::Identity; - ISMC->GetInstanceTransform(InstIdx, CurTransform); - InstanceTransforms[InstIdx] = CurTransform; - } - } -} - -bool -UHoudiniInputInstancedMeshComponent::HasInstancesChanged() const -{ - UInstancedStaticMeshComponent* ISMC = Cast(InputObject.LoadSynchronous()); - if (!ISMC) - return false; - - uint32 InstanceCount = ISMC->GetInstanceCount(); - if (InstanceTransforms.Num() != InstanceCount) - return true; - - // Copy the instances' transforms - for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) - { - FTransform CurTransform = FTransform::Identity; - ISMC->GetInstanceTransform(InstIdx, CurTransform); - - if(!InstanceTransforms[InstIdx].Equals(CurTransform)) - return true; - } - - return false; -} - -bool -UHoudiniInputInstancedMeshComponent::HasComponentTransformChanged() const -{ - if (Super::HasComponentTransformChanged()) - return true; - - return HasInstancesChanged(); -} - -void -UHoudiniInputSplineComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - USplineComponent* Spline = Cast(InObject); - - ensure(Spline); - - if (Spline) - { - NumberOfSplineControlPoints = Spline->GetNumberOfSplinePoints(); - SplineLength = Spline->GetSplineLength(); - SplineClosed = Spline->IsClosedLoop(); - - //SplineResolution = -1.0f; - - SplineControlPoints.SetNumZeroed(NumberOfSplineControlPoints); - for (int32 Idx = 0; Idx < NumberOfSplineControlPoints; Idx++) - { - SplineControlPoints[Idx] = Spline->GetTransformAtSplinePoint(Idx, ESplineCoordinateSpace::Local); - } - } -} - -void -UHoudiniInputHoudiniSplineComponent::Update(UObject* InObject) -{ - Super::Update(InObject); - - // We store the component references as a normal pointer property instead of using a soft object reference. - // If we use a soft object reference, the editor will complain about deleting a reference that is in use - // everytime we try to delete the actor, even though everything is contained within the actor. - - CachedComponent = Cast(InObject); - InputObject = nullptr; - - // We need a strong ref to the spline component to prevent it from being GCed - //MyHoudiniSplineComponent = Cast(InObject); - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - { - // Use default values - CurveType = EHoudiniCurveType::Polygon; - CurveMethod = EHoudiniCurveMethod::CVs; - Reversed = false; - } - else - { - CurveType = HoudiniSplineComponent->GetCurveType(); - CurveMethod = HoudiniSplineComponent->GetCurveMethod(); - Reversed = false;//Spline->IsReversed(); - } -} - -UObject* -UHoudiniInputHoudiniSplineComponent::GetObject() const -{ - return CachedComponent; -} - -void -UHoudiniInputHoudiniSplineComponent::MarkChanged(const bool& bInChanged) -{ - Super::MarkChanged(bInChanged); - - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent) - { - HoudiniSplineComponent->MarkChanged(bInChanged); - } -} - -void -UHoudiniInputHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) -{ - Super::SetNeedsToTriggerUpdate(bInTriggersUpdate); - - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent) - { - HoudiniSplineComponent->SetNeedsToTriggerUpdate(bInTriggersUpdate); - } -} - -bool -UHoudiniInputHoudiniSplineComponent::HasChanged() const -{ - if (Super::HasChanged()) - return true; - - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent && HoudiniSplineComponent->HasChanged()) - return true; - - return false; -} - -bool -UHoudiniInputHoudiniSplineComponent::NeedsToTriggerUpdate() const -{ - if (Super::NeedsToTriggerUpdate()) - return true; - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent && HoudiniSplineComponent->NeedsToTriggerUpdate()) - return true; - - return false; -} - -void -UHoudiniInputHoudiniAsset::Update(UObject * InObject) -{ - Super::Update(InObject); - - UHoudiniAssetComponent* HAC = Cast(InObject); - - ensure(HAC); - - if (HAC) - { - // TODO: Notify HAC that we're a downstream? - InputNodeId = HAC->GetAssetId(); - InputObjectNodeId = HAC->GetAssetId(); - - // TODO: Allow selection of the asset output - AssetOutputIndex = 0; - } -} - - -void -UHoudiniInputActor::Update(UObject * InObject) -{ - const bool bHasInputObjectChanged = InputObject != InObject; - - Super::Update(InObject); - - AActor* Actor = Cast(InObject); - ensure(Actor); - - if (Actor) - { - Transform = Actor->GetTransform(); - - // If we are updating (InObject == InputObject), then remove stale components and add new components, - // if InObject != InputObject, remove all components and rebuild - - if (bHasInputObjectChanged) - { - // The actor's components that can be sent as inputs - LastUpdateNumComponentsRemoved = ActorComponents.Num(); - - ActorComponents.Empty(); - ActorSceneComponents.Empty(); - - TArray AllComponents; - Actor->GetComponents(AllComponents, true); - - int32 CompIdx = 0; - ActorComponents.SetNum(AllComponents.Num()); - for (USceneComponent * SceneComponent : AllComponents) - { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; - - UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( - SceneComponent, GetOuter(), Actor->GetName()); - if (!InputObj) - continue; - - UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); - if (!SceneInput) - continue; - - ActorComponents[CompIdx++] = SceneInput; - ActorSceneComponents.Add(TSoftObjectPtr(SceneComponent)); - } - ActorComponents.SetNum(CompIdx); - LastUpdateNumComponentsAdded = CompIdx; - } - else - { - LastUpdateNumComponentsAdded = 0; - LastUpdateNumComponentsRemoved = 0; - - // Look for any components to add or remove - TSet NewComponents; - const bool bIncludeFromChildActors = true; - Actor->ForEachComponent(bIncludeFromChildActors, [&](USceneComponent* InComp) - { - if (IsValid(InComp)) - { - if (!ActorSceneComponents.Contains(InComp)) - { - NewComponents.Add(InComp); - } - } - }); - - // Update the actor input components (from the same actor) - TArray ComponentIndicesToRemove; - const int32 NumActorComponents = ActorComponents.Num(); - for (int32 Index = 0; Index < NumActorComponents; ++Index) - { - UHoudiniInputSceneComponent* CurActorComp = ActorComponents[Index]; - if (!CurActorComp || CurActorComp->IsPendingKill()) - { - ComponentIndicesToRemove.Add(Index); - continue; - } - - // Does the component still exist on Actor? - UObject* const CompObj = CurActorComp->GetObject(); - // Make sure the actor is still valid - if (!CompObj || CompObj->IsPendingKill()) - { - // If it's not, mark it for deletion - if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) - { - CurActorComp->InvalidateData(); - } - - ComponentIndicesToRemove.Add(Index); - continue; - } - } - - // Remove the destroyed/invalid components - const int32 NumToRemove = ComponentIndicesToRemove.Num(); - if (NumToRemove > 0) - { - for (int32 Index = NumToRemove - 1; Index >= 0; --Index) - { - const int32& IndexToRemove = ComponentIndicesToRemove[Index]; - - UHoudiniInputSceneComponent* const CurActorComp = ActorComponents[IndexToRemove]; - if (CurActorComp) - ActorSceneComponents.Remove(CurActorComp->InputObject); - - const bool bAllowShrink = false; - ActorComponents.RemoveAtSwap(IndexToRemove, 1, bAllowShrink); - - LastUpdateNumComponentsRemoved++; - } - } - - if (NewComponents.Num() > 0) - { - for (USceneComponent * SceneComponent : NewComponents) - { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; - - UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( - SceneComponent, GetOuter(), Actor->GetName()); - if (!InputObj) - continue; - - UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); - if (!SceneInput) - continue; - - ActorComponents.Add(SceneInput); - ActorSceneComponents.Add(SceneComponent); - - LastUpdateNumComponentsAdded++; - } - } - - if (LastUpdateNumComponentsAdded > 0 || LastUpdateNumComponentsRemoved > 0) - { - ActorComponents.Shrink(); - } - } - } - else - { - // If we don't have a valid actor or null, delete any input components we still have and mark as changed - if (ActorComponents.Num() > 0) - { - LastUpdateNumComponentsAdded = 0; - LastUpdateNumComponentsRemoved = ActorComponents.Num(); - ActorComponents.Empty(); - ActorSceneComponents.Empty(); - } - else - { - LastUpdateNumComponentsAdded = 0; - LastUpdateNumComponentsRemoved = 0; - } - } -} - -bool -UHoudiniInputActor::HasActorTransformChanged() -{ - if (!GetActor()) - return false; - - if (!Transform.Equals(GetActor()->GetTransform())) - return true; - - return false; -} - -bool -UHoudiniInputActor::HasContentChanged() const -{ - return false; -} - -bool -UHoudiniInputLandscape::HasActorTransformChanged() -{ - return Super::HasActorTransformChanged(); - //return false; -} - -void -UHoudiniInputLandscape::Update(UObject * InObject) -{ - Super::Update(InObject); - - ALandscapeProxy* Landscape = Cast(InObject); - - //ensure(Landscape); - - if (Landscape) - { - // Nothing to do for landscapes? - } -} - -EHoudiniInputObjectType -UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) -{ - if (InObject->IsA(USceneComponent::StaticClass())) - { - // Handle component inputs - // UISMC derived from USMC, so always test instances before static meshes - if (InObject->IsA(UInstancedStaticMeshComponent::StaticClass())) - { - return EHoudiniInputObjectType::InstancedStaticMeshComponent; - } - else if (InObject->IsA(UStaticMeshComponent::StaticClass())) - { - return EHoudiniInputObjectType::StaticMeshComponent; - } - else if (InObject->IsA(USplineComponent::StaticClass())) - { - return EHoudiniInputObjectType::SplineComponent; - } - else if (InObject->IsA(UHoudiniSplineComponent::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniSplineComponent; - } - else if (InObject->IsA(UHoudiniAssetComponent::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniAssetComponent; - } - else if (InObject->IsA(UCameraComponent::StaticClass())) - { - return EHoudiniInputObjectType::CameraComponent; - } - else - { - return EHoudiniInputObjectType::SceneComponent; - } - } - else if (InObject->IsA(AActor::StaticClass())) - { - // Handle actors - if (InObject->IsA(ALandscapeProxy::StaticClass())) - { - return EHoudiniInputObjectType::Landscape; - } - else if (InObject->IsA(ABrush::StaticClass())) - { - return EHoudiniInputObjectType::Brush; - } - else if (InObject->IsA(AHoudiniAssetActor::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniAssetActor; - } - else - { - return EHoudiniInputObjectType::Actor; - } - } - else if (InObject->IsA(UBlueprint::StaticClass())) - { - return EHoudiniInputObjectType::StaticMesh; - } - else if (InObject->IsA(UFoliageType_InstancedStaticMesh::StaticClass())) - { - return EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; - } - else - { - if (InObject->IsA(UStaticMesh::StaticClass())) - { - return EHoudiniInputObjectType::StaticMesh; - } - else if (InObject->IsA(USkeletalMesh::StaticClass())) - { - return EHoudiniInputObjectType::SkeletalMesh; - } - else if (InObject->IsA(UDataTable::StaticClass())) - { - return EHoudiniInputObjectType::DataTable; - } - else - { - return EHoudiniInputObjectType::Object; - } - } - - return EHoudiniInputObjectType::Invalid; -} - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniInputBrush -//----------------------------------------------------------------------------------------------------------------------------- - -FHoudiniBrushInfo::FHoudiniBrushInfo() - : CachedTransform() - , CachedOrigin(ForceInitToZero) - , CachedExtent(ForceInitToZero) - , CachedBrushType(EBrushType::Brush_Default) - , CachedSurfaceHash(0) -{ -} - -FHoudiniBrushInfo::FHoudiniBrushInfo(ABrush* InBrushActor) -{ - if (!InBrushActor) - return; - - BrushActor = InBrushActor; - CachedTransform = BrushActor->GetActorTransform(); - BrushActor->GetActorBounds(false, CachedOrigin, CachedExtent); - CachedBrushType = BrushActor->BrushType; - -#if WITH_EDITOR - UModel* Model = BrushActor->Brush; - - // Cache the hash of the surface properties - if (IsValid(Model) && IsValid(Model->Polys)) - { - int32 NumPolys = Model->Polys->Element.Num(); - CachedSurfaceHash = 0; - for(int32 iPoly = 0; iPoly < NumPolys; ++iPoly) - { - const FPoly& Poly = Model->Polys->Element[iPoly]; - CombinePolyHash(CachedSurfaceHash, Poly); - } - } - else - { - CachedSurfaceHash = 0; - } -#endif -} - -bool FHoudiniBrushInfo::HasChanged() const -{ - if (!BrushActor.IsValid()) - return false; - - // Has the transform changed? - if (!BrushActor->GetActorTransform().Equals(CachedTransform)) - return true; - - if (BrushActor->BrushType != CachedBrushType) - return true; - - // Has the actor bounds changed? - FVector TmpOrigin, TmpExtent; - BrushActor->GetActorBounds(false, TmpOrigin, TmpExtent); - - if (!(TmpOrigin.Equals(CachedOrigin) && TmpExtent.Equals(CachedExtent) )) - return true; -#if WITH_EDITOR - // Is there a tracked surface property that changed? - UModel* Model = BrushActor->Brush; - if (IsValid(Model) && IsValid(Model->Polys)) - { - // Hash the incoming surface properties and compared it against the cached hash. - int32 NumPolys = Model->Polys->Element.Num(); - uint64 SurfaceHash = 0; - for (int32 iPoly = 0; iPoly < NumPolys; ++iPoly) - { - const FPoly& Poly = Model->Polys->Element[iPoly]; - CombinePolyHash(SurfaceHash, Poly); - } - if (SurfaceHash != CachedSurfaceHash) - return true; - } - else - { - if (CachedSurfaceHash != 0) - return true; - } -#endif - return false; -} - -int32 FHoudiniBrushInfo::GetNumVertexIndicesFromModel(const UModel* Model) -{ - const TArray& Nodes = Model->Nodes; - int32 NumIndices = 0; - // Build the face counts buffer by iterating over the BSP nodes. - for(const FBspNode& Node : Nodes) - { - NumIndices += Node.NumVertices; - } - return NumIndices; -} - -UModel* UHoudiniInputBrush::GetCachedModel() const -{ - return CombinedModel; -} - -bool UHoudiniInputBrush::HasBrushesChanged(const TArray& InBrushes) const -{ - if (InBrushes.Num() != BrushesInfo.Num()) - return true; - - int32 NumBrushes = BrushesInfo.Num(); - - for (int32 InfoIndex = 0; InfoIndex < NumBrushes; ++InfoIndex) - { - const FHoudiniBrushInfo& BrushInfo = BrushesInfo[InfoIndex]; - // Has the cached brush actor invalid? - if (!BrushInfo.BrushActor.IsValid()) - return true; - - // Has there been an order change in the actors list? - if (InBrushes[InfoIndex] != BrushInfo.BrushActor.Get()) - return true; - - // Has there been any other changes to the brush? - if (BrushInfo.HasChanged()) - return true; - } - - // Nothing has changed. - return false; -} - -void UHoudiniInputBrush::UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes) -{ - ABrush* InputBrush = GetBrush(); - if (IsValid(InputBrush)) - { - CachedInputBrushType = InputBrush->BrushType; - } - - // Cache the combined model aswell as the brushes used to generate this model. - CombinedModel = InCombinedModel; - - BrushesInfo.SetNumUninitialized(InBrushes.Num()); - for (int i = 0; i < InBrushes.Num(); ++i) - { - if (!InBrushes[i]) - continue; - BrushesInfo[i] = FHoudiniBrushInfo(InBrushes[i]); - } -} - - -void -UHoudiniInputBrush::Update(UObject * InObject) -{ - Super::Update(InObject); - - ABrush* BrushActor = GetBrush(); - if (!IsValid(BrushActor)) - { - bIgnoreInputObject = true; - return; - } - - CachedInputBrushType = BrushActor->BrushType; - - bIgnoreInputObject = ShouldIgnoreThisInput(); -} - -bool -UHoudiniInputBrush::ShouldIgnoreThisInput() -{ - // Invalid brush, should be ignored - ABrush* BrushActor = GetBrush(); - ensure(BrushActor); - if (!BrushActor) - return true; - - // If the BrushType has changed since caching this object, this object cannot be ignored. - if (CachedInputBrushType != BrushActor->BrushType) - return false; - - // If it's not an additive brush, we want to ignore it - bool bShouldBeIgnored = BrushActor->BrushType != EBrushType::Brush_Add; - - // If this is not a static brush (e.g., AVolume), ignore it. - if (!bShouldBeIgnored) - bShouldBeIgnored = !BrushActor->IsStaticBrush(); - - return bShouldBeIgnored; -} - -bool UHoudiniInputBrush::HasContentChanged() const -{ - ABrush* BrushActor = GetBrush(); - ensure(BrushActor); - - if (!BrushActor) - return false; - - if (BrushActor->BrushType != CachedInputBrushType) - return true; - - if (bIgnoreInputObject) - return false; - - // Find intersecting actors and capture their properties so that - // we can determine whether something has changed. - TArray IntersectingBrushes; - FindIntersectingSubtractiveBrushes(this, IntersectingBrushes); - - if (HasBrushesChanged(IntersectingBrushes)) - { - return true; - } - - return false; -} - -bool -UHoudiniInputBrush::HasActorTransformChanged() -{ - if (bIgnoreInputObject) - return false; - - return Super::HasActorTransformChanged(); -} - - -bool UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes) -{ - TArray IntersectingActors; - TArray Bounds; - - - if (!IsValid(InputBrush)) - return false; - - ABrush* BrushActor = InputBrush->GetBrush(); - if (!IsValid(BrushActor)) - return false; - - - OutBrushes.Empty(); - - Bounds.Add( BrushActor->GetComponentsBoundingBox(true, true) ); - - FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(BrushActor->GetWorld(), ABrush::StaticClass(), Bounds, nullptr, IntersectingActors); - - //-------------------------------------------------------------------------------------------------- - // Filter the actors to only keep intersecting subtractive brushes. - //-------------------------------------------------------------------------------------------------- - for (AActor* Actor : IntersectingActors) - { - // Filter out anything that is not a static brush (typically volume actors). - ABrush* Brush = Cast(Actor); - - // NOTE: The brush actor needs to be added in the correct map/level order - // together with the subtractive brushes otherwise the CSG operations - // will not match the BSP in the level. - if (Actor == BrushActor) - OutBrushes.Add(Brush); - - if (!(Brush && Brush->IsStaticBrush())) - continue; - - if (Brush->BrushType == Brush_Subtract) - OutBrushes.Add(Brush); - } - - return true; -} - -#if WITH_EDITOR -void -UHoudiniInputObject::PostEditUndo() -{ - Super::PostEditUndo(); - MarkChanged(true); -} -#endif - -UHoudiniInputObject* -UHoudiniInputObject::DuplicateAndCopyState(UObject * DestOuter) -{ - UHoudiniInputObject* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); - NewInput->CopyStateFrom(this, false); - return NewInput; -} - -void -UHoudiniInputObject::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References should be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - } - - InputNodeId = InInput->InputNodeId; - InputObjectNodeId = InInput->InputObjectNodeId; - bHasChanged = InInput->bHasChanged; - bNeedsToTriggerUpdate = InInput->bNeedsToTriggerUpdate; - bTransformChanged = InInput->bTransformChanged; - Guid = InInput->Guid; - -#if WITH_EDITORONLY_DATA - bUniformScaleLocked = InInput->bUniformScaleLocked; -#endif - -} - -void -UHoudiniInputObject::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; -} - - -// -UHoudiniInputDataTable::UHoudiniInputDataTable(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -UHoudiniInputObject * -UHoudiniInputDataTable::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_DT_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputDataTable::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputDataTable * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputDataTable::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::DataTable; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UDataTable* -UHoudiniInputDataTable::GetDataTable() const -{ - return Cast(InputObject.LoadSynchronous()); -} - -UHoudiniInputFoliageType_InstancedStaticMesh::UHoudiniInputFoliageType_InstancedStaticMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -UHoudiniInputObject* -UHoudiniInputFoliageType_InstancedStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_FoliageSM_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputFoliageType_InstancedStaticMesh * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -void -UHoudiniInputFoliageType_InstancedStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - UHoudiniInputFoliageType_InstancedStaticMesh* FoliageTypeSM = Cast(InInput); - if (!IsValid(FoliageTypeSM)) - return; - - UHoudiniInputObject::CopyStateFrom(FoliageTypeSM, bCopyAllProperties); - - // BlueprintStaticMeshes array is not used in UHoudiniInputFoliageType_InstancedStaticMesh - BlueprintStaticMeshes.Empty(); -} - -void -UHoudiniInputFoliageType_InstancedStaticMesh::Update(UObject * InObject) -{ - UHoudiniInputObject::Update(InObject); - UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InObject); - ensure(FoliageType); - ensure(FoliageType->GetStaticMesh()); -} - -UStaticMesh* -UHoudiniInputFoliageType_InstancedStaticMesh::GetStaticMesh() const -{ - if (!InputObject.IsValid()) - return nullptr; - - UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InputObject.LoadSynchronous()); - if (!IsValid(FoliageType)) - return nullptr; - - return FoliageType->GetStaticMesh(); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputObject.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInput.h" + +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "Engine/DataTable.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "Engine/Brush.h" +#include "Engine/Engine.h" +#include "GameFramework/Volume.h" +#include "Camera/CameraComponent.h" +#include "FoliageType_InstancedStaticMesh.h" + +#include "Model.h" +#include "Engine/Brush.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "Kismet/KismetSystemLibrary.h" + +//----------------------------------------------------------------------------------------------------------------------------- +// Constructors +//----------------------------------------------------------------------------------------------------------------------------- + +// +UHoudiniInputObject::UHoudiniInputObject(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , Transform(FTransform::Identity) + , Type(EHoudiniInputObjectType::Invalid) + , InputNodeId(-1) + , InputObjectNodeId(-1) + , bHasChanged(false) + , bNeedsToTriggerUpdate(false) + , bTransformChanged(false) + , bImportAsReference(false) + , bCanDeleteHoudiniNodes(true) +{ + Guid = FGuid::NewGuid(); +} + +// +UHoudiniInputStaticMesh::UHoudiniInputStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSkeletalMesh::UHoudiniInputSkeletalMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSceneComponent::UHoudiniInputSceneComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputMeshComponent::UHoudiniInputMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputInstancedMeshComponent::UHoudiniInputInstancedMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSplineComponent::UHoudiniInputSplineComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , NumberOfSplineControlPoints(-1) + , SplineLength(-1.0f) + , SplineResolution(-1.0f) + , SplineClosed(false) +{ + +} + +// +UHoudiniInputCameraComponent::UHoudiniInputCameraComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , FOV(0.0f) + , AspectRatio(1.0f) + , bIsOrthographic(false) + , OrthoWidth(2.0f) + , OrthoNearClipPlane(0.0f) + , OrthoFarClipPlane(-1.0f) +{ + +} + +// Returns true if the attached actor's (parent) transform has been modified +bool +UHoudiniInputSplineComponent::HasActorTransformChanged() const +{ + return false; +} + +// Returns true if the attached component's transform has been modified +bool +UHoudiniInputSplineComponent::HasComponentTransformChanged() const +{ + return false; +} + +// Return true if the component itself has been modified +bool +UHoudiniInputSplineComponent::HasComponentChanged() const +{ + USplineComponent* SplineComponent = Cast(InputObject.LoadSynchronous()); + + if (!SplineComponent) + return false; + + if (SplineClosed != SplineComponent->IsClosedLoop()) + return true; + + + if (SplineComponent->GetNumberOfSplinePoints() != NumberOfSplineControlPoints) + return true; + + for (int32 n = 0; n < SplineComponent->GetNumberOfSplinePoints(); ++n) + { + const FTransform &CurSplineComponentTransform = SplineComponent->GetTransformAtSplinePoint(n, ESplineCoordinateSpace::Local); + const FTransform &CurInputTransform = SplineControlPoints[n]; + + if (CurInputTransform.GetLocation() != CurSplineComponentTransform.GetLocation()) + return true; + + if (CurInputTransform.GetRotation().Rotator() != CurSplineComponentTransform.GetRotation().Rotator()) + return true; + + if (CurInputTransform.GetScale3D() != CurSplineComponentTransform.GetScale3D()) + return true; + } + + return false; +} + +bool +UHoudiniInputSplineComponent::HasSplineComponentChanged(float fCurrentSplineResolution) const +{ + return false; +} + +// +UHoudiniInputHoudiniSplineComponent::UHoudiniInputHoudiniSplineComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , CurveType(EHoudiniCurveType::Polygon) + , CurveMethod(EHoudiniCurveMethod::CVs) + , Reversed(false) +{ + +} + +// +UHoudiniInputHoudiniAsset::UHoudiniInputHoudiniAsset(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , AssetOutputIndex(-1) +{ + +} + +// +UHoudiniInputActor::UHoudiniInputActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , LastUpdateNumComponentsAdded(0) + , LastUpdateNumComponentsRemoved(0) +{ + +} + +// +UHoudiniInputLandscape::UHoudiniInputLandscape(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputBrush::UHoudiniInputBrush() + : CombinedModel(nullptr) + , bIgnoreInputObject(false) +{ + +} + +//----------------------------------------------------------------------------------------------------------------------------- +// Accessors +//----------------------------------------------------------------------------------------------------------------------------- + +UObject* +UHoudiniInputObject::GetObject() const +{ + return InputObject.LoadSynchronous(); +} + +UStaticMesh* +UHoudiniInputStaticMesh::GetStaticMesh() const +{ + return Cast(InputObject.LoadSynchronous()); +} + +UBlueprint* +UHoudiniInputStaticMesh::GetBlueprint() const +{ + return Cast(InputObject.LoadSynchronous()); +} + +bool UHoudiniInputStaticMesh::bIsBlueprint() const +{ + return (InputObject.IsValid() && InputObject.Get()->IsA()); +} + +USkeletalMesh* +UHoudiniInputSkeletalMesh::GetSkeletalMesh() +{ + return Cast(InputObject.LoadSynchronous()); +} + +USceneComponent* +UHoudiniInputSceneComponent::GetSceneComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UStaticMeshComponent* +UHoudiniInputMeshComponent::GetStaticMeshComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UStaticMesh* +UHoudiniInputMeshComponent::GetStaticMesh() +{ + return StaticMesh.Get(); +} + +UInstancedStaticMeshComponent* +UHoudiniInputInstancedMeshComponent::GetInstancedStaticMeshComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +USplineComponent* +UHoudiniInputSplineComponent::GetSplineComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniSplineComponent* +UHoudiniInputHoudiniSplineComponent::GetCurveComponent() const +{ + return Cast(GetObject()); + //return Cast(InputObject.LoadSynchronous()); +} + +UCameraComponent* +UHoudiniInputCameraComponent::GetCameraComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniAssetComponent* +UHoudiniInputHoudiniAsset::GetHoudiniAssetComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +AActor* +UHoudiniInputActor::GetActor() +{ + return Cast(InputObject.LoadSynchronous()); +} + +ALandscapeProxy* +UHoudiniInputLandscape::GetLandscapeProxy() +{ + return Cast(InputObject.LoadSynchronous()); +} + +void +UHoudiniInputLandscape::SetLandscapeProxy(UObject* InLandscapeProxy) +{ + UObject* LandscapeProxy = Cast(InLandscapeProxy); + if (LandscapeProxy) + InputObject = LandscapeProxy; +} + +ABrush* +UHoudiniInputBrush::GetBrush() const +{ + return Cast(InputObject.LoadSynchronous()); +} + + +//----------------------------------------------------------------------------------------------------------------------------- +// CREATE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +UHoudiniInputObject * +UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InName) +{ + if (!InObject) + return nullptr; + + UHoudiniInputObject* HoudiniInputObject = nullptr; + + EHoudiniInputObjectType InputObjectType = GetInputObjectTypeFromObject(InObject); + switch (InputObjectType) + { + case EHoudiniInputObjectType::Object: + HoudiniInputObject = UHoudiniInputObject::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::StaticMesh: + HoudiniInputObject = UHoudiniInputStaticMesh::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::SkeletalMesh: + HoudiniInputObject = UHoudiniInputSkeletalMesh::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::SceneComponent: + // Do not create input objects for unknown scene component! + //HoudiniInputObject = UHoudiniInputSceneComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::StaticMeshComponent: + HoudiniInputObject = UHoudiniInputMeshComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + HoudiniInputObject = UHoudiniInputInstancedMeshComponent::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::SplineComponent: + HoudiniInputObject = UHoudiniInputSplineComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::HoudiniSplineComponent: + HoudiniInputObject = UHoudiniInputHoudiniSplineComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::HoudiniAssetActor: + { + AHoudiniAssetActor* HoudiniActor = Cast(InObject); + if (HoudiniActor) + { + HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniActor->GetHoudiniAssetComponent(), InOuter, InName); + } + else + { + HoudiniInputObject = nullptr; + } + } + break; + + case EHoudiniInputObjectType::HoudiniAssetComponent: + HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::Actor: + HoudiniInputObject = UHoudiniInputActor::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Landscape: + HoudiniInputObject = UHoudiniInputLandscape::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Brush: + HoudiniInputObject = UHoudiniInputBrush::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::CameraComponent: + HoudiniInputObject = UHoudiniInputCameraComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::DataTable: + HoudiniInputObject = UHoudiniInputDataTable::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + HoudiniInputObject = UHoudiniInputFoliageType_InstancedStaticMesh::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Invalid: + default: + break; + } + + return HoudiniInputObject; +} + + +UHoudiniInputObject * +UHoudiniInputInstancedMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_ISMC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputInstancedMeshComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::InstancedStaticMeshComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SMC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputMeshComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputMeshComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMeshComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Spline_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSplineComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSplineComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SplineComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputHoudiniSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_HoudiniSpline_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputHoudiniSplineComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniSplineComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputCameraComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Camera_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputCameraComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputCameraComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputCameraComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::CameraComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputHoudiniAsset::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + UHoudiniAssetComponent * InHoudiniAssetComponent = Cast(InObject); + if (!InHoudiniAssetComponent) + return nullptr; + + FString InputObjectNameStr = "HoudiniInputObject_HAC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniAsset::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputHoudiniAsset * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputHoudiniAsset::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniAssetComponent; + + HoudiniInputObject->InputNodeId = InHoudiniAssetComponent->GetAssetId(); + HoudiniInputObject->InputObjectNodeId = InHoudiniAssetComponent->GetAssetId(); + + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputSceneComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SceneComp_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSceneComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSceneComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSceneComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SceneComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputLandscape::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Landscape_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputLandscape::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputLandscape * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputLandscape::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Landscape; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputBrush * +UHoudiniInputBrush::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Brush_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputBrush::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputBrush * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputBrush::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Brush; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputActor::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Actor_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputActor::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputActor * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputActor::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Actor; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SM_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputStaticMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputStaticMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +// void UHoudiniInputStaticMesh::DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputStaticMesh*& OutNewInput) +// { +// UHoudiniInputStaticMesh* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); +// OutNewInput = NewInput; +// OutNewInput->CopyStateFrom(this, false); +// } + +void +UHoudiniInputStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + UHoudiniInputStaticMesh* StaticMeshInput = Cast(InInput); + check(InInput); + + TArray PrevInputs = BlueprintStaticMeshes; + + Super::CopyStateFrom(StaticMeshInput, bCopyAllProperties); + + const int32 NumInputs = StaticMeshInput->BlueprintStaticMeshes.Num(); + BlueprintStaticMeshes = PrevInputs; + TArray StaleInputs(BlueprintStaticMeshes); + + BlueprintStaticMeshes.SetNum(NumInputs); + + for (int i = 0; i < NumInputs; ++i) + { + UHoudiniInputStaticMesh* FromInput = StaticMeshInput->BlueprintStaticMeshes[i]; + UHoudiniInputStaticMesh* ToInput = BlueprintStaticMeshes[i]; + + if (!FromInput) + { + BlueprintStaticMeshes[i] = nullptr; + continue; + } + + if (ToInput) + { + // Check whether the ToInput can be reused + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + if (!bIsValid) + { + ToInput = nullptr; + } + } + + if (ToInput) + { + // We have a reusable input + ToInput->CopyStateFrom(FromInput, true); + } + else + { + // We need to create a new input + ToInput = Cast(FromInput->DuplicateAndCopyState(this)); + } + + BlueprintStaticMeshes[i] = ToInput; + } + + for(UHoudiniInputStaticMesh* StaleInput : StaleInputs) + { + if (!StaleInput) + continue; + StaleInput->InvalidateData(); + } +} + +void +UHoudiniInputStaticMesh::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + Super::SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) + { + if (!Input) + continue; + Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +void +UHoudiniInputStaticMesh::InvalidateData() +{ + for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) + { + if (!Input) + continue; + Input->InvalidateData(); + } + + Super::InvalidateData(); +} + + +UHoudiniInputObject * +UHoudiniInputSkeletalMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SkelMesh_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSkeletalMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSkeletalMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSkeletalMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SkeletalMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputObject::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputObject::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputObject * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputObject::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Object; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +bool +UHoudiniInputObject::Matches(const UHoudiniInputObject& Other) const +{ + return (Type == Other.Type + && InputNodeId == Other.InputNodeId + && InputObjectNodeId == Other.InputObjectNodeId + ); +} + +//----------------------------------------------------------------------------------------------------------------------------- +// DELETE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +void +UHoudiniInputObject::InvalidateData() +{ + // If valid, mark our input nodes for deletion.. + if (this->IsA() || !bCanDeleteHoudiniNodes) + { + // Unless if we're a HoudiniAssetInput! we don't want to delete the other HDA's node! + // just invalidate the node IDs! + InputNodeId = -1; + InputObjectNodeId = -1; + return; + } + + if (InputNodeId >= 0) + { + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); + InputNodeId = -1; + } + + // ... and the parent OBJ as well to clean up + if (InputObjectNodeId >= 0) + { + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputObjectNodeId); + InputObjectNodeId = -1; + } + + +} + +void +UHoudiniInputObject::BeginDestroy() +{ + // Invalidate and mark our input node for deletion + InvalidateData(); + + Super::BeginDestroy(); +} + +//----------------------------------------------------------------------------------------------------------------------------- +// UPDATE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +void +UHoudiniInputObject::Update(UObject * InObject) +{ + InputObject = InObject; +} + +void +UHoudiniInputStaticMesh::Update(UObject * InObject) +{ + // Nothing to do + Super::Update(InObject); + // Static Mesh input accepts SM, BP, FoliageType_InstancedStaticMesh (static mesh) and FoliageType_Actor (if blueprint actor). + UStaticMesh* SM = Cast(InObject); + UBlueprint* BP = Cast(InObject); + + ensure(SM || BP); +} + +void +UHoudiniInputSkeletalMesh::Update(UObject * InObject) +{ + // Nothing to do + Super::Update(InObject); + + USkeletalMesh* SkelMesh = Cast(InObject); + ensure(SkelMesh); +} + +void +UHoudiniInputSceneComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + USceneComponent* USC = Cast(InObject); + ensure(USC); + if (USC) + { + Transform = USC->GetComponentTransform(); + } +} + + +bool +UHoudiniInputSceneComponent::HasActorTransformChanged() const +{ + // Returns true if the attached actor's (parent) transform has been modified + USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); + if (!MyComp || MyComp->IsPendingKill()) + return false; + + AActor* MyActor = MyComp->GetOwner(); + if (!MyActor) + return false; + + return (!ActorTransform.Equals(MyActor->GetTransform())); +} + + +bool +UHoudiniInputSceneComponent::HasComponentTransformChanged() const +{ + // Returns true if the attached actor's (parent) transform has been modified + USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); + if (!MyComp || MyComp->IsPendingKill()) + return false; + + return !Transform.Equals(MyComp->GetComponentTransform()); +} + + +bool +UHoudiniInputSceneComponent::HasComponentChanged() const +{ + // Should return true if the component itself has been modified + // Should be overriden in child classes + return false; +} + + +bool +UHoudiniInputMeshComponent::HasComponentChanged() const +{ + UStaticMeshComponent* SMC = Cast(InputObject.LoadSynchronous()); + UStaticMesh* MySM = StaticMesh.Get(); + + // Return true if SMC's static mesh has been modified + return (MySM != SMC->GetStaticMesh()); +} + +bool +UHoudiniInputCameraComponent::HasComponentChanged() const +{ + UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); + if (Camera && !Camera->IsPendingKill()) + { + bool bOrtho = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; + if (bOrtho != bIsOrthographic) + return true; + + if (Camera->FieldOfView != FOV) + return true; + + if (Camera->AspectRatio != AspectRatio) + return true; + + if (Camera->OrthoWidth != OrthoWidth) + return true; + + if (Camera->OrthoNearClipPlane != OrthoNearClipPlane) + return true; + + if (Camera->OrthoFarClipPlane != OrthoFarClipPlane) + return true; + } + + return false; +} + + + +void +UHoudiniInputCameraComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); + + ensure(Camera); + + if (Camera && !Camera->IsPendingKill()) + { + bIsOrthographic = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; + FOV = Camera->FieldOfView; + AspectRatio = Camera->AspectRatio; + OrthoWidth = Camera->OrthoWidth; + OrthoNearClipPlane = Camera->OrthoNearClipPlane; + OrthoFarClipPlane = Camera->OrthoFarClipPlane; + } +} + +void +UHoudiniInputMeshComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UStaticMeshComponent* SMC = Cast(InObject); + + ensure(SMC); + + if (SMC) + { + StaticMesh = TSoftObjectPtr(SMC->GetStaticMesh()); + + TArray Materials = SMC->GetMaterials(); + for (auto CurrentMat : Materials) + { + // TODO: Update material ref here + FString MatRef; + MeshComponentsMaterials.Add(MatRef); + } + } +} + +void +UHoudiniInputInstancedMeshComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UInstancedStaticMeshComponent* ISMC = Cast(InObject); + + ensure(ISMC); + + if (ISMC) + { + uint32 InstanceCount = ISMC->GetInstanceCount(); + InstanceTransforms.SetNum(InstanceCount); + + // Copy the instances' transforms + for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) + { + FTransform CurTransform = FTransform::Identity; + ISMC->GetInstanceTransform(InstIdx, CurTransform); + InstanceTransforms[InstIdx] = CurTransform; + } + } +} + +bool +UHoudiniInputInstancedMeshComponent::HasInstancesChanged() const +{ + UInstancedStaticMeshComponent* ISMC = Cast(InputObject.LoadSynchronous()); + if (!ISMC) + return false; + + uint32 InstanceCount = ISMC->GetInstanceCount(); + if (InstanceTransforms.Num() != InstanceCount) + return true; + + // Copy the instances' transforms + for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) + { + FTransform CurTransform = FTransform::Identity; + ISMC->GetInstanceTransform(InstIdx, CurTransform); + + if(!InstanceTransforms[InstIdx].Equals(CurTransform)) + return true; + } + + return false; +} + +bool +UHoudiniInputInstancedMeshComponent::HasComponentTransformChanged() const +{ + if (Super::HasComponentTransformChanged()) + return true; + + return HasInstancesChanged(); +} + +void +UHoudiniInputSplineComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + USplineComponent* Spline = Cast(InObject); + + ensure(Spline); + + if (Spline) + { + NumberOfSplineControlPoints = Spline->GetNumberOfSplinePoints(); + SplineLength = Spline->GetSplineLength(); + SplineClosed = Spline->IsClosedLoop(); + + //SplineResolution = -1.0f; + + SplineControlPoints.SetNumZeroed(NumberOfSplineControlPoints); + for (int32 Idx = 0; Idx < NumberOfSplineControlPoints; Idx++) + { + SplineControlPoints[Idx] = Spline->GetTransformAtSplinePoint(Idx, ESplineCoordinateSpace::Local); + } + } +} + +void +UHoudiniInputHoudiniSplineComponent::Update(UObject* InObject) +{ + Super::Update(InObject); + + // We store the component references as a normal pointer property instead of using a soft object reference. + // If we use a soft object reference, the editor will complain about deleting a reference that is in use + // everytime we try to delete the actor, even though everything is contained within the actor. + + CachedComponent = Cast(InObject); + InputObject = nullptr; + + // We need a strong ref to the spline component to prevent it from being GCed + //MyHoudiniSplineComponent = Cast(InObject); + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + { + // Use default values + CurveType = EHoudiniCurveType::Polygon; + CurveMethod = EHoudiniCurveMethod::CVs; + Reversed = false; + } + else + { + CurveType = HoudiniSplineComponent->GetCurveType(); + CurveMethod = HoudiniSplineComponent->GetCurveMethod(); + Reversed = false;//Spline->IsReversed(); + } +} + +UObject* +UHoudiniInputHoudiniSplineComponent::GetObject() const +{ + return CachedComponent; +} + +void +UHoudiniInputHoudiniSplineComponent::MarkChanged(const bool& bInChanged) +{ + Super::MarkChanged(bInChanged); + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent) + { + HoudiniSplineComponent->MarkChanged(bInChanged); + } +} + +void +UHoudiniInputHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) +{ + Super::SetNeedsToTriggerUpdate(bInTriggersUpdate); + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent) + { + HoudiniSplineComponent->SetNeedsToTriggerUpdate(bInTriggersUpdate); + } +} + +bool +UHoudiniInputHoudiniSplineComponent::HasChanged() const +{ + if (Super::HasChanged()) + return true; + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent && HoudiniSplineComponent->HasChanged()) + return true; + + return false; +} + +bool +UHoudiniInputHoudiniSplineComponent::NeedsToTriggerUpdate() const +{ + if (Super::NeedsToTriggerUpdate()) + return true; + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent && HoudiniSplineComponent->NeedsToTriggerUpdate()) + return true; + + return false; +} + +void +UHoudiniInputHoudiniAsset::Update(UObject * InObject) +{ + Super::Update(InObject); + + UHoudiniAssetComponent* HAC = Cast(InObject); + + ensure(HAC); + + if (HAC) + { + // TODO: Notify HAC that we're a downstream? + InputNodeId = HAC->GetAssetId(); + InputObjectNodeId = HAC->GetAssetId(); + + // TODO: Allow selection of the asset output + AssetOutputIndex = 0; + } +} + + +void +UHoudiniInputActor::Update(UObject * InObject) +{ + const bool bHasInputObjectChanged = InputObject != InObject; + + Super::Update(InObject); + + AActor* Actor = Cast(InObject); + ensure(Actor); + + if (Actor) + { + Transform = Actor->GetTransform(); + + // If we are updating (InObject == InputObject), then remove stale components and add new components, + // if InObject != InputObject, remove all components and rebuild + + if (bHasInputObjectChanged) + { + // The actor's components that can be sent as inputs + LastUpdateNumComponentsRemoved = ActorComponents.Num(); + + ActorComponents.Empty(); + ActorSceneComponents.Empty(); + + TArray AllComponents; + Actor->GetComponents(AllComponents, true); + + int32 CompIdx = 0; + ActorComponents.SetNum(AllComponents.Num()); + for (USceneComponent * SceneComponent : AllComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( + SceneComponent, GetOuter(), Actor->GetName()); + if (!InputObj) + continue; + + UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); + if (!SceneInput) + continue; + + ActorComponents[CompIdx++] = SceneInput; + ActorSceneComponents.Add(TSoftObjectPtr(SceneComponent)); + } + ActorComponents.SetNum(CompIdx); + LastUpdateNumComponentsAdded = CompIdx; + } + else + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = 0; + + // Look for any components to add or remove + TSet NewComponents; + const bool bIncludeFromChildActors = true; + Actor->ForEachComponent(bIncludeFromChildActors, [&](USceneComponent* InComp) + { + if (IsValid(InComp)) + { + if (!ActorSceneComponents.Contains(InComp)) + { + NewComponents.Add(InComp); + } + } + }); + + // Update the actor input components (from the same actor) + TArray ComponentIndicesToRemove; + const int32 NumActorComponents = ActorComponents.Num(); + for (int32 Index = 0; Index < NumActorComponents; ++Index) + { + UHoudiniInputSceneComponent* CurActorComp = ActorComponents[Index]; + if (!CurActorComp || CurActorComp->IsPendingKill()) + { + ComponentIndicesToRemove.Add(Index); + continue; + } + + // Does the component still exist on Actor? + UObject* const CompObj = CurActorComp->GetObject(); + // Make sure the actor is still valid + if (!CompObj || CompObj->IsPendingKill()) + { + // If it's not, mark it for deletion + if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) + { + CurActorComp->InvalidateData(); + } + + ComponentIndicesToRemove.Add(Index); + continue; + } + } + + // Remove the destroyed/invalid components + const int32 NumToRemove = ComponentIndicesToRemove.Num(); + if (NumToRemove > 0) + { + for (int32 Index = NumToRemove - 1; Index >= 0; --Index) + { + const int32& IndexToRemove = ComponentIndicesToRemove[Index]; + + UHoudiniInputSceneComponent* const CurActorComp = ActorComponents[IndexToRemove]; + if (CurActorComp) + ActorSceneComponents.Remove(CurActorComp->InputObject); + + const bool bAllowShrink = false; + ActorComponents.RemoveAtSwap(IndexToRemove, 1, bAllowShrink); + + LastUpdateNumComponentsRemoved++; + } + } + + if (NewComponents.Num() > 0) + { + for (USceneComponent * SceneComponent : NewComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( + SceneComponent, GetOuter(), Actor->GetName()); + if (!InputObj) + continue; + + UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); + if (!SceneInput) + continue; + + ActorComponents.Add(SceneInput); + ActorSceneComponents.Add(SceneComponent); + + LastUpdateNumComponentsAdded++; + } + } + + if (LastUpdateNumComponentsAdded > 0 || LastUpdateNumComponentsRemoved > 0) + { + ActorComponents.Shrink(); + } + } + } + else + { + // If we don't have a valid actor or null, delete any input components we still have and mark as changed + if (ActorComponents.Num() > 0) + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = ActorComponents.Num(); + ActorComponents.Empty(); + ActorSceneComponents.Empty(); + } + else + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = 0; + } + } +} + +bool +UHoudiniInputActor::HasActorTransformChanged() +{ + if (!GetActor()) + return false; + + if (!Transform.Equals(GetActor()->GetTransform())) + return true; + + return false; +} + +bool +UHoudiniInputActor::HasContentChanged() const +{ + return false; +} + +bool +UHoudiniInputLandscape::HasActorTransformChanged() +{ + return Super::HasActorTransformChanged(); + //return false; +} + +void +UHoudiniInputLandscape::Update(UObject * InObject) +{ + Super::Update(InObject); + + ALandscapeProxy* Landscape = Cast(InObject); + + //ensure(Landscape); + + if (Landscape) + { + // Nothing to do for landscapes? + } +} + +EHoudiniInputObjectType +UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) +{ + if (InObject->IsA(USceneComponent::StaticClass())) + { + // Handle component inputs + // UISMC derived from USMC, so always test instances before static meshes + if (InObject->IsA(UInstancedStaticMeshComponent::StaticClass())) + { + return EHoudiniInputObjectType::InstancedStaticMeshComponent; + } + else if (InObject->IsA(UStaticMeshComponent::StaticClass())) + { + return EHoudiniInputObjectType::StaticMeshComponent; + } + else if (InObject->IsA(USplineComponent::StaticClass())) + { + return EHoudiniInputObjectType::SplineComponent; + } + else if (InObject->IsA(UHoudiniSplineComponent::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniSplineComponent; + } + else if (InObject->IsA(UHoudiniAssetComponent::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniAssetComponent; + } + else if (InObject->IsA(UCameraComponent::StaticClass())) + { + return EHoudiniInputObjectType::CameraComponent; + } + else + { + return EHoudiniInputObjectType::SceneComponent; + } + } + else if (InObject->IsA(AActor::StaticClass())) + { + // Handle actors + if (InObject->IsA(ALandscapeProxy::StaticClass())) + { + return EHoudiniInputObjectType::Landscape; + } + else if (InObject->IsA(ABrush::StaticClass())) + { + return EHoudiniInputObjectType::Brush; + } + else if (InObject->IsA(AHoudiniAssetActor::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniAssetActor; + } + else + { + return EHoudiniInputObjectType::Actor; + } + } + else if (InObject->IsA(UBlueprint::StaticClass())) + { + return EHoudiniInputObjectType::StaticMesh; + } + else if (InObject->IsA(UFoliageType_InstancedStaticMesh::StaticClass())) + { + return EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; + } + else + { + if (InObject->IsA(UStaticMesh::StaticClass())) + { + return EHoudiniInputObjectType::StaticMesh; + } + else if (InObject->IsA(USkeletalMesh::StaticClass())) + { + return EHoudiniInputObjectType::SkeletalMesh; + } + else if (InObject->IsA(UDataTable::StaticClass())) + { + return EHoudiniInputObjectType::DataTable; + } + else + { + return EHoudiniInputObjectType::Object; + } + } + + return EHoudiniInputObjectType::Invalid; +} + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniInputBrush +//----------------------------------------------------------------------------------------------------------------------------- + +FHoudiniBrushInfo::FHoudiniBrushInfo() + : CachedTransform() + , CachedOrigin(ForceInitToZero) + , CachedExtent(ForceInitToZero) + , CachedBrushType(EBrushType::Brush_Default) + , CachedSurfaceHash(0) +{ +} + +FHoudiniBrushInfo::FHoudiniBrushInfo(ABrush* InBrushActor) +{ + if (!InBrushActor) + return; + + BrushActor = InBrushActor; + CachedTransform = BrushActor->GetActorTransform(); + BrushActor->GetActorBounds(false, CachedOrigin, CachedExtent); + CachedBrushType = BrushActor->BrushType; + +#if WITH_EDITOR + UModel* Model = BrushActor->Brush; + + // Cache the hash of the surface properties + if (IsValid(Model) && IsValid(Model->Polys)) + { + int32 NumPolys = Model->Polys->Element.Num(); + CachedSurfaceHash = 0; + for(int32 iPoly = 0; iPoly < NumPolys; ++iPoly) + { + const FPoly& Poly = Model->Polys->Element[iPoly]; + CombinePolyHash(CachedSurfaceHash, Poly); + } + } + else + { + CachedSurfaceHash = 0; + } +#endif +} + +bool FHoudiniBrushInfo::HasChanged() const +{ + if (!BrushActor.IsValid()) + return false; + + // Has the transform changed? + if (!BrushActor->GetActorTransform().Equals(CachedTransform)) + return true; + + if (BrushActor->BrushType != CachedBrushType) + return true; + + // Has the actor bounds changed? + FVector TmpOrigin, TmpExtent; + BrushActor->GetActorBounds(false, TmpOrigin, TmpExtent); + + if (!(TmpOrigin.Equals(CachedOrigin) && TmpExtent.Equals(CachedExtent) )) + return true; +#if WITH_EDITOR + // Is there a tracked surface property that changed? + UModel* Model = BrushActor->Brush; + if (IsValid(Model) && IsValid(Model->Polys)) + { + // Hash the incoming surface properties and compared it against the cached hash. + int32 NumPolys = Model->Polys->Element.Num(); + uint64 SurfaceHash = 0; + for (int32 iPoly = 0; iPoly < NumPolys; ++iPoly) + { + const FPoly& Poly = Model->Polys->Element[iPoly]; + CombinePolyHash(SurfaceHash, Poly); + } + if (SurfaceHash != CachedSurfaceHash) + return true; + } + else + { + if (CachedSurfaceHash != 0) + return true; + } +#endif + return false; +} + +int32 FHoudiniBrushInfo::GetNumVertexIndicesFromModel(const UModel* Model) +{ + const TArray& Nodes = Model->Nodes; + int32 NumIndices = 0; + // Build the face counts buffer by iterating over the BSP nodes. + for(const FBspNode& Node : Nodes) + { + NumIndices += Node.NumVertices; + } + return NumIndices; +} + +UModel* UHoudiniInputBrush::GetCachedModel() const +{ + return CombinedModel; +} + +bool UHoudiniInputBrush::HasBrushesChanged(const TArray& InBrushes) const +{ + if (InBrushes.Num() != BrushesInfo.Num()) + return true; + + int32 NumBrushes = BrushesInfo.Num(); + + for (int32 InfoIndex = 0; InfoIndex < NumBrushes; ++InfoIndex) + { + const FHoudiniBrushInfo& BrushInfo = BrushesInfo[InfoIndex]; + // Has the cached brush actor invalid? + if (!BrushInfo.BrushActor.IsValid()) + return true; + + // Has there been an order change in the actors list? + if (InBrushes[InfoIndex] != BrushInfo.BrushActor.Get()) + return true; + + // Has there been any other changes to the brush? + if (BrushInfo.HasChanged()) + return true; + } + + // Nothing has changed. + return false; +} + +void UHoudiniInputBrush::UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes) +{ + ABrush* InputBrush = GetBrush(); + if (IsValid(InputBrush)) + { + CachedInputBrushType = InputBrush->BrushType; + } + + // Cache the combined model aswell as the brushes used to generate this model. + CombinedModel = InCombinedModel; + + BrushesInfo.SetNumUninitialized(InBrushes.Num()); + for (int i = 0; i < InBrushes.Num(); ++i) + { + if (!InBrushes[i]) + continue; + BrushesInfo[i] = FHoudiniBrushInfo(InBrushes[i]); + } +} + + +void +UHoudiniInputBrush::Update(UObject * InObject) +{ + Super::Update(InObject); + + ABrush* BrushActor = GetBrush(); + if (!IsValid(BrushActor)) + { + bIgnoreInputObject = true; + return; + } + + CachedInputBrushType = BrushActor->BrushType; + + bIgnoreInputObject = ShouldIgnoreThisInput(); +} + +bool +UHoudiniInputBrush::ShouldIgnoreThisInput() +{ + // Invalid brush, should be ignored + ABrush* BrushActor = GetBrush(); + ensure(BrushActor); + if (!BrushActor) + return true; + + // If the BrushType has changed since caching this object, this object cannot be ignored. + if (CachedInputBrushType != BrushActor->BrushType) + return false; + + // If it's not an additive brush, we want to ignore it + bool bShouldBeIgnored = BrushActor->BrushType != EBrushType::Brush_Add; + + // If this is not a static brush (e.g., AVolume), ignore it. + if (!bShouldBeIgnored) + bShouldBeIgnored = !BrushActor->IsStaticBrush(); + + return bShouldBeIgnored; +} + +bool UHoudiniInputBrush::HasContentChanged() const +{ + ABrush* BrushActor = GetBrush(); + ensure(BrushActor); + + if (!BrushActor) + return false; + + if (BrushActor->BrushType != CachedInputBrushType) + return true; + + if (bIgnoreInputObject) + return false; + + // Find intersecting actors and capture their properties so that + // we can determine whether something has changed. + TArray IntersectingBrushes; + FindIntersectingSubtractiveBrushes(this, IntersectingBrushes); + + if (HasBrushesChanged(IntersectingBrushes)) + { + return true; + } + + return false; +} + +bool +UHoudiniInputBrush::HasActorTransformChanged() +{ + if (bIgnoreInputObject) + return false; + + return Super::HasActorTransformChanged(); +} + + +bool UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes) +{ + TArray IntersectingActors; + TArray Bounds; + + + if (!IsValid(InputBrush)) + return false; + + ABrush* BrushActor = InputBrush->GetBrush(); + if (!IsValid(BrushActor)) + return false; + + + OutBrushes.Empty(); + + Bounds.Add( BrushActor->GetComponentsBoundingBox(true, true) ); + + FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(BrushActor->GetWorld(), ABrush::StaticClass(), Bounds, nullptr, IntersectingActors); + + //-------------------------------------------------------------------------------------------------- + // Filter the actors to only keep intersecting subtractive brushes. + //-------------------------------------------------------------------------------------------------- + for (AActor* Actor : IntersectingActors) + { + // Filter out anything that is not a static brush (typically volume actors). + ABrush* Brush = Cast(Actor); + + // NOTE: The brush actor needs to be added in the correct map/level order + // together with the subtractive brushes otherwise the CSG operations + // will not match the BSP in the level. + if (Actor == BrushActor) + OutBrushes.Add(Brush); + + if (!(Brush && Brush->IsStaticBrush())) + continue; + + if (Brush->BrushType == Brush_Subtract) + OutBrushes.Add(Brush); + } + + return true; +} + +#if WITH_EDITOR +void +UHoudiniInputObject::PostEditUndo() +{ + Super::PostEditUndo(); + MarkChanged(true); +} +#endif + +UHoudiniInputObject* +UHoudiniInputObject::DuplicateAndCopyState(UObject * DestOuter) +{ + UHoudiniInputObject* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); + NewInput->CopyStateFrom(this, false); + return NewInput; +} + +void +UHoudiniInputObject::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References should be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + } + + InputNodeId = InInput->InputNodeId; + InputObjectNodeId = InInput->InputObjectNodeId; + bHasChanged = InInput->bHasChanged; + bNeedsToTriggerUpdate = InInput->bNeedsToTriggerUpdate; + bTransformChanged = InInput->bTransformChanged; + Guid = InInput->Guid; + +#if WITH_EDITORONLY_DATA + bUniformScaleLocked = InInput->bUniformScaleLocked; +#endif + +} + +void +UHoudiniInputObject::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; +} + + +// +UHoudiniInputDataTable::UHoudiniInputDataTable(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniInputObject * +UHoudiniInputDataTable::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_DT_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputDataTable::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputDataTable * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputDataTable::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::DataTable; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UDataTable* +UHoudiniInputDataTable::GetDataTable() const +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniInputFoliageType_InstancedStaticMesh::UHoudiniInputFoliageType_InstancedStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniInputObject* +UHoudiniInputFoliageType_InstancedStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_FoliageSM_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputFoliageType_InstancedStaticMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +void +UHoudiniInputFoliageType_InstancedStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + UHoudiniInputFoliageType_InstancedStaticMesh* FoliageTypeSM = Cast(InInput); + if (!IsValid(FoliageTypeSM)) + return; + + UHoudiniInputObject::CopyStateFrom(FoliageTypeSM, bCopyAllProperties); + + // BlueprintStaticMeshes array is not used in UHoudiniInputFoliageType_InstancedStaticMesh + BlueprintStaticMeshes.Empty(); +} + +void +UHoudiniInputFoliageType_InstancedStaticMesh::Update(UObject * InObject) +{ + UHoudiniInputObject::Update(InObject); + UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InObject); + ensure(FoliageType); + ensure(FoliageType->GetStaticMesh()); +} + +UStaticMesh* +UHoudiniInputFoliageType_InstancedStaticMesh::GetStaticMesh() const +{ + if (!InputObject.IsValid()) + return nullptr; + + UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InputObject.LoadSynchronous()); + if (!IsValid(FoliageType)) + return nullptr; + + return FoliageType->GetStaticMesh(); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h index ce7b11c51..bc42a5d9a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h @@ -1,861 +1,861 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include - -#include "HoudiniSplineComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "CoreTypes.h" -#include "Materials/MaterialInterface.h" -#include "UObject/Object.h" -#include "UObject/ObjectMacros.h" - -#include "Engine/Brush.h" -#include "Engine/Polys.h" -#include "UObject/SoftObjectPtr.h" - -#include "HoudiniInputObject.generated.h" - -class UStaticMesh; -class USkeletalMesh; -class USceneComponent; -class UStaticMeshComponent; -class UInstancedStaticMeshComponent; -class USplineComponent; -class UHoudiniAssetComponent; -class AActor; -class ALandscapeProxy; -class ABrush; -class UHoudiniInput; -class ALandscapeProxy; -class UModel; -class UHoudiniInput; -class UCameraComponent; - -UENUM() -enum class EHoudiniInputObjectType : uint8 -{ - Invalid, - - Object, - StaticMesh, - SkeletalMesh, - SceneComponent, - StaticMeshComponent, - InstancedStaticMeshComponent, - SplineComponent, - HoudiniSplineComponent, - HoudiniAssetComponent, - Actor, - Landscape, - Brush, - CameraComponent, - DataTable, - HoudiniAssetActor, - FoliageType_InstancedStaticMesh, -}; - -//----------------------------------------------------------------------------------------------------------------------------- -// UObjects input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - // Create the proper input object - static UHoudiniInputObject * CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InParamName); - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // Check whether two input objects match - virtual bool Matches(const UHoudiniInputObject& Other) const; - - // - static EHoudiniInputObjectType GetInputObjectTypeFromObject(UObject* InObject); - - // - virtual void Update(UObject * InObject); - - // Invalidate and ask for the deletion of this input object's node - virtual void InvalidateData(); - - // UObject accessor - virtual UObject* GetObject() const; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const { return bHasChanged; }; - - // Indicates if this input has changed and should be updated - virtual bool HasTransformChanged() const { return bTransformChanged; }; - - // Indicates if this input needs to trigger an update - virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; - - virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - void MarkTransformChanged(const bool& bInChanged) { bTransformChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - - void SetImportAsReference(const bool& bInImportAsRef) { bImportAsReference = bInImportAsRef; }; - bool GetImportAsReference() const { return bImportAsReference; }; - -#if WITH_EDITOR - void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; - bool IsUniformScaleLocked() const { return bUniformScaleLocked; }; - - void PostEditUndo() override; -#endif - - virtual UHoudiniInputObject* DuplicateAndCopyState(UObject* DestOuter); - virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties); - - // Set whether this object can delete Houdini nodes. - virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } - - FGuid GetInputGuid() const { return Guid; } - - -protected: - - virtual void BeginDestroy() override; - -public: - - // The object referenced by this input - // This property should be protected. Don't access this directly. Use GetObject() / Update() instead. - UPROPERTY() - TSoftObjectPtr InputObject; - - // The object's transform/transform offset - UPROPERTY() - FTransform Transform; - - // The type of Object this input refers to - UPROPERTY() - EHoudiniInputObjectType Type; - - // This input object's "main" (SOP) NodeId - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputNodeId; - - // This input object's "container" (OBJ) NodeId - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputObjectNodeId; - - // Guid that uniquely identifies this input object. - // Also useful to correlate inputs between blueprint component templates and instances. - UPROPERTY(DuplicateTransient) - FGuid Guid; - -protected: - - // Indicates this input object has changed - UPROPERTY(DuplicateTransient) - bool bHasChanged; - - // Indicates this input object should trigger an input update/cook - UPROPERTY(DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Indicates that this input transform should be updated - UPROPERTY(DuplicateTransient) - bool bTransformChanged; - - UPROPERTY() - bool bImportAsReference; - - // Indicates if change the scale of Transfrom Offset of this object uniformly -#if WITH_EDITORONLY_DATA - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - bool bUniformScaleLocked; -#endif - - UPROPERTY() - bool bCanDeleteHoudiniNodes; -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UStaticMesh input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputStaticMesh : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // UHoudiniInputObject overrides - - // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; - virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; - virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) override; - virtual void InvalidateData() override; - - // - virtual void Update(UObject * InObject) override; - - // Nothing to add for Static Meshes? - - // StaticMesh accessor - virtual class UStaticMesh* GetStaticMesh() const; - - // Blueprint accessor - virtual class UBlueprint* GetBlueprint() const; - - // Check if this SM Input object is passed in as a BP - virtual bool bIsBlueprint() const; - - // The Blueprint's Static Meshe Components that can be sent as inputs - UPROPERTY() - TArray BlueprintStaticMeshes; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USkeletalMesh input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSkeletalMesh : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // Nothing to add for SkeletalMesh Meshes? - - // StaticMesh accessor - class USkeletalMesh* GetSkeletalMesh(); -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USceneComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSceneComponent : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // SceneComponent accessor - class USceneComponent* GetSceneComponent(); - - // Returns true if the attached actor's (parent) transform has been modified - virtual bool HasActorTransformChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const; - - // Return true if the component itself has been modified - virtual bool HasComponentChanged() const; - -public: - - // This component's parent Actor transform - UPROPERTY() - FTransform ActorTransform = FTransform::Identity; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UStaticMeshComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputMeshComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // StaticMeshComponent accessor - UStaticMeshComponent* GetStaticMeshComponent(); - - // Get the referenced StaticMesh - UStaticMesh* GetStaticMesh(); - - // Returns true if the attached component's materials have been modified - bool HasComponentMaterialsChanged() const; - - // Return true if SMC's static mesh has been modified - virtual bool HasComponentChanged() const override; - -public: - - // Keep track of the selected Static Mesh - UPROPERTY() - TSoftObjectPtr StaticMesh = nullptr; - - // Path to the materials assigned on the SMC - UPROPERTY() - TArray MeshComponentsMaterials; -}; - - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UInstancedStaticMeshComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputInstancedMeshComponent : public UHoudiniInputMeshComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // InstancedStaticMeshComponent accessor - UInstancedStaticMeshComponent* GetInstancedStaticMeshComponent(); - - // Returns true if the instances have changed - bool HasInstancesChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const override; - -public: - - // Array of transform for this ISMC's instances - UPROPERTY() - TArray InstanceTransforms; -}; - - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USplineComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSplineComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // USplineComponent accessor - USplineComponent* GetSplineComponent(); - - // Returns true if the attached spline component has been modified - bool HasSplineComponentChanged(float fCurrentSplineResolution) const; - - // Returns true if the attached actor's (parent) transform has been modified - virtual bool HasActorTransformChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const; - - // Return true if the component itself has been modified - virtual bool HasComponentChanged() const; - -public: - - // Number of CVs used by the spline component, used to detect modification - UPROPERTY() - int32 NumberOfSplineControlPoints = -1; - - // Spline Length, used for fast detection of modifications of the spline.. - UPROPERTY() - float SplineLength = -1.0f; - - // Spline resolution used to generate the asset, used to detect setting modification - UPROPERTY() - float SplineResolution = -1.0f; - - // Is the spline closed? - UPROPERTY() - bool SplineClosed = false; - - // Transforms of each of the spline's control points - UPROPERTY() - TArray SplineControlPoints; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniSplineComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniSplineComponent : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - virtual void Update(UObject * InObject) override; - - virtual UObject* GetObject() const override; - - virtual void MarkChanged(const bool& bInChanged) override; - - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) override; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const override; - - // Indicates if this input needs to trigger an update - virtual bool NeedsToTriggerUpdate() const override; - - // UHoudiniSplineComponent accessor - UHoudiniSplineComponent* GetCurveComponent() const; - -public: - - // The type of curve (polygon, NURBS, bezier) - UPROPERTY() - EHoudiniCurveType CurveType = EHoudiniCurveType::Polygon; - - // The curve's method (CVs, Breakpoint, Freehand) - UPROPERTY() - EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::CVs; - - UPROPERTY() - bool Reversed = false; - - -protected: - - // NOTE: We are using this reference to the component since the component, for now, - // lives on the same actor as this input object. If we use a Soft Object Reference instead the editor - // will complain about breaking references everytime we try to delete the actor. - UPROPERTY(Instanced) - UHoudiniSplineComponent* CachedComponent; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UCameraComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputCameraComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // UCameraComponent accessor - UCameraComponent* GetCameraComponent(); - - // Return true if SMC's static mesh has been modified - virtual bool HasComponentChanged() const override; - -public: - - UPROPERTY() - float FOV; - - UPROPERTY() - float AspectRatio; - - UPROPERTY() - //TEnumAsByte ProjectionType; - bool bIsOrthographic; - - UPROPERTY() - float OrthoWidth; - UPROPERTY() - float OrthoNearClipPlane; - UPROPERTY() - float OrthoFarClipPlane; - -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniAssetComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniAsset : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // UHoudiniAssetComponent accessor - UHoudiniAssetComponent* GetHoudiniAssetComponent(); -public: - - // The output index of the node that we want to use as input - UPROPERTY() - int32 AssetOutputIndex; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// AActor input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // - virtual bool HasActorTransformChanged(); - - // Return true if any content of this actor has possibly changed (for example geometry edits on a - // Brush or changes on procedurally generated content). - // NOTE: This is more generally applicable and could be moved to the HoudiniInputObject class. - virtual bool HasContentChanged() const; - - // AActor accessor - AActor* GetActor(); - - const TArray& GetActorComponents() const { return ActorComponents; } - - // The number of components added with the last call to Update - int32 GetLastUpdateNumComponentsAdded() const { return LastUpdateNumComponentsAdded; } - // The number of components remove with the last call to Update - int32 GetLastUpdateNumComponentsRemoved() const { return LastUpdateNumComponentsRemoved; } - -protected: - - // The actor's components that can be sent as inputs - UPROPERTY() - TArray ActorComponents; - - // The USceneComponents the actor had the last time we called Update (matches the ones in ActorComponents). - UPROPERTY() - TSet> ActorSceneComponents; - - // The number of components added with the last call to Update - UPROPERTY() - int32 LastUpdateNumComponentsAdded; - - // The number of components remove with the last call to Update - UPROPERTY() - int32 LastUpdateNumComponentsRemoved; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// ALandscapeProxy input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActor -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - virtual bool HasActorTransformChanged() override; - - // ALandscapeProxy accessor - ALandscapeProxy* GetLandscapeProxy(); - - void SetLandscapeProxy(UObject* InLandscapeProxy); - - // Used to restore an input landscape's transform to its original state - UPROPERTY() - FTransform CachedInputLandscapeTraqnsform; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// ABrush input -//----------------------------------------------------------------------------------------------------------------------------- -// Cache info for a brush in order to determine whether it has changed. - -#define BRUSH_HASH_SURFACE_PROPERTIES 0 - -//USTRUCT() -//struct FHoudiniBrushSurfaceInfo { -// GENERATED_BODY() -// -// FVector Base; -// FVector Normal; -// FVector TextureU; -// FVector TextureV; -// TSoftObjectPtr Material; -// -// FHoudiniBrushSurfaceInfo(const FVector& InBase, const FVector& InNormal, const FVector& InTextureU, const FVector& InTextureV, UMaterialInterface* InMaterial) -// : Base(InBase) -// , Normal(InNormal) -// , TextureU(InTextureU) -// , TextureV(InTextureV) -// , Material(InMaterial) -// { } -// -// inline bool operator==(const FHoudiniBrushSurfaceInfo& Other) { -// return Base.Equals(Other.Base) -// && Normal.Equals(Other.Normal) -// && TextureU.Equals(Other.TextureU) -// && TextureV.Equals(Other.TextureV) -// && Material.Get() == Other.Material.Get(); -// } -// -// inline bool operator==(const FPoly& Poly) { -// return Base.Equals(Poly.Base) -// && Normal.Equals(Poly.Normal) -// && TextureU.Equals(Poly.TextureU) -// && TextureV.Equals(Poly.TextureV) -// && Material.Get() == Poly.Material; -// } -//}; - -USTRUCT() -struct FHoudiniBrushInfo -{ - GENERATED_BODY() - - UPROPERTY() - TWeakObjectPtr BrushActor; - UPROPERTY() - FTransform CachedTransform; - UPROPERTY() - FVector CachedOrigin; - UPROPERTY() - FVector CachedExtent; - UPROPERTY() - TEnumAsByte CachedBrushType; - - UPROPERTY() - uint64 CachedSurfaceHash; - - bool HasChanged() const; - - static int32 GetNumVertexIndicesFromModel(const UModel* Model); - - FHoudiniBrushInfo(); - FHoudiniBrushInfo(ABrush* InBrushActor); - - template - inline void HashCombine(uint64& s, const T & v) const - { - std::hash h; - s^= h(v) + 0x9e3779b9 + (s<< 6) + (s>> 2); - } - - inline void HashCombine(uint64& s, const FVector & V) const - { - HashCombine(s, V.X); - HashCombine(s, V.Y); - HashCombine(s, V.Z); - } - - inline void CombinePolyHash(uint64& Hash, const FPoly& Poly) const - { - HashCombine(Hash, Poly.Base); - HashCombine(Hash, Poly.TextureU); - HashCombine(Hash, Poly.TextureV); - HashCombine(Hash, Poly.Normal); - HashCombine(Hash, (uint64)(Poly.Material)); - } -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor -{ - GENERATED_BODY() - -public: - - UHoudiniInputBrush(); - - // Factory function - static UHoudiniInputBrush* Create(UObject* InObject, UObject* InOuter, const FString& InName); - - //---------------------------------------------------------------------- - // UHoudiniInputActor Interface - Begin - //---------------------------------------------------------------------- - - virtual void Update(UObject * InObject) override; - - // Indicates if this input has changed and should be updated - virtual bool HasContentChanged() const override; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const override { return (!bIgnoreInputObject) && bHasChanged; }; - - // Indicates if this input has changed and should be updated - virtual bool HasTransformChanged() const override { return (!bIgnoreInputObject) && bTransformChanged; }; - - virtual bool HasActorTransformChanged() override; - - virtual bool NeedsToTriggerUpdate() const override { return (!bIgnoreInputObject) && bNeedsToTriggerUpdate; }; - - //---------------------------------------------------------------------- - // UHoudiniInputActor Interface - End - //---------------------------------------------------------------------- - - - // ABrush accessor - ABrush* GetBrush() const; - - UModel* GetCachedModel() const; - - // Check whether any of the brushes, or their transforms, used to generate this model have changed. - bool HasBrushesChanged(const TArray& InBrushes) const; - - // Cache the combined model as well as the input brushes. - void UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes); - - // Returns whether this input object should be ignored when uploading objects to Houdini. - // This mechanism could be implemented on UHoudiniInputObject. - bool ShouldIgnoreThisInput(); - - - // Find only the subtractive brush actors that intersect with the InputObject (Brush actor) bounding box but - // excluding any selector bounds actors. - static bool FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes); - -protected: - UPROPERTY() - TArray BrushesInfo; - - UPROPERTY(Transient, DuplicateTransient) - UModel* CombinedModel; - - UPROPERTY() - bool bIgnoreInputObject; - - UPROPERTY() - TEnumAsByte CachedInputBrushType; -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UDataTable input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputDataTable : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // DataTable accessor - class UDataTable* GetDataTable() const; -}; - -//----------------------------------------------------------------------------------------------------------------------------- -// UFoliageType_InstancedStaticMesh input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputFoliageType_InstancedStaticMesh : public UHoudiniInputStaticMesh -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // UHoudiniInputObject overrides - - // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; - virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; - - // - virtual void Update(UObject * InObject) override; - - // StaticMesh accessor - virtual class UStaticMesh* GetStaticMesh() const override; - - virtual class UBlueprint* GetBlueprint() const override { return nullptr; } - - virtual bool bIsBlueprint() const override { return false; } -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include + +#include "HoudiniSplineComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "CoreTypes.h" +#include "Materials/MaterialInterface.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" + +#include "Engine/Brush.h" +#include "Engine/Polys.h" +#include "UObject/SoftObjectPtr.h" + +#include "HoudiniInputObject.generated.h" + +class UStaticMesh; +class USkeletalMesh; +class USceneComponent; +class UStaticMeshComponent; +class UInstancedStaticMeshComponent; +class USplineComponent; +class UHoudiniAssetComponent; +class AActor; +class ALandscapeProxy; +class ABrush; +class UHoudiniInput; +class ALandscapeProxy; +class UModel; +class UHoudiniInput; +class UCameraComponent; + +UENUM() +enum class EHoudiniInputObjectType : uint8 +{ + Invalid, + + Object, + StaticMesh, + SkeletalMesh, + SceneComponent, + StaticMeshComponent, + InstancedStaticMeshComponent, + SplineComponent, + HoudiniSplineComponent, + HoudiniAssetComponent, + Actor, + Landscape, + Brush, + CameraComponent, + DataTable, + HoudiniAssetActor, + FoliageType_InstancedStaticMesh, +}; + +//----------------------------------------------------------------------------------------------------------------------------- +// UObjects input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + // Create the proper input object + static UHoudiniInputObject * CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InParamName); + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // Check whether two input objects match + virtual bool Matches(const UHoudiniInputObject& Other) const; + + // + static EHoudiniInputObjectType GetInputObjectTypeFromObject(UObject* InObject); + + // + virtual void Update(UObject * InObject); + + // Invalidate and ask for the deletion of this input object's node + virtual void InvalidateData(); + + // UObject accessor + virtual UObject* GetObject() const; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const { return bHasChanged; }; + + // Indicates if this input has changed and should be updated + virtual bool HasTransformChanged() const { return bTransformChanged; }; + + // Indicates if this input needs to trigger an update + virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; + + virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + void MarkTransformChanged(const bool& bInChanged) { bTransformChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + + void SetImportAsReference(const bool& bInImportAsRef) { bImportAsReference = bInImportAsRef; }; + bool GetImportAsReference() const { return bImportAsReference; }; + +#if WITH_EDITOR + void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; + bool IsUniformScaleLocked() const { return bUniformScaleLocked; }; + + void PostEditUndo() override; +#endif + + virtual UHoudiniInputObject* DuplicateAndCopyState(UObject* DestOuter); + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties); + + // Set whether this object can delete Houdini nodes. + virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } + + FGuid GetInputGuid() const { return Guid; } + + +protected: + + virtual void BeginDestroy() override; + +public: + + // The object referenced by this input + // This property should be protected. Don't access this directly. Use GetObject() / Update() instead. + UPROPERTY() + TSoftObjectPtr InputObject; + + // The object's transform/transform offset + UPROPERTY() + FTransform Transform; + + // The type of Object this input refers to + UPROPERTY() + EHoudiniInputObjectType Type; + + // This input object's "main" (SOP) NodeId + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputNodeId; + + // This input object's "container" (OBJ) NodeId + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputObjectNodeId; + + // Guid that uniquely identifies this input object. + // Also useful to correlate inputs between blueprint component templates and instances. + UPROPERTY(DuplicateTransient) + FGuid Guid; + +protected: + + // Indicates this input object has changed + UPROPERTY(DuplicateTransient) + bool bHasChanged; + + // Indicates this input object should trigger an input update/cook + UPROPERTY(DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Indicates that this input transform should be updated + UPROPERTY(DuplicateTransient) + bool bTransformChanged; + + UPROPERTY() + bool bImportAsReference; + + // Indicates if change the scale of Transfrom Offset of this object uniformly +#if WITH_EDITORONLY_DATA + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + bool bUniformScaleLocked; +#endif + + UPROPERTY() + bool bCanDeleteHoudiniNodes; +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UStaticMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputStaticMesh : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // UHoudiniInputObject overrides + + // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; + virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) override; + virtual void InvalidateData() override; + + // + virtual void Update(UObject * InObject) override; + + // Nothing to add for Static Meshes? + + // StaticMesh accessor + virtual class UStaticMesh* GetStaticMesh() const; + + // Blueprint accessor + virtual class UBlueprint* GetBlueprint() const; + + // Check if this SM Input object is passed in as a BP + virtual bool bIsBlueprint() const; + + // The Blueprint's Static Meshe Components that can be sent as inputs + UPROPERTY() + TArray BlueprintStaticMeshes; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USkeletalMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSkeletalMesh : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // Nothing to add for SkeletalMesh Meshes? + + // StaticMesh accessor + class USkeletalMesh* GetSkeletalMesh(); +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USceneComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSceneComponent : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // SceneComponent accessor + class USceneComponent* GetSceneComponent(); + + // Returns true if the attached actor's (parent) transform has been modified + virtual bool HasActorTransformChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const; + + // Return true if the component itself has been modified + virtual bool HasComponentChanged() const; + +public: + + // This component's parent Actor transform + UPROPERTY() + FTransform ActorTransform = FTransform::Identity; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UStaticMeshComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputMeshComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // StaticMeshComponent accessor + UStaticMeshComponent* GetStaticMeshComponent(); + + // Get the referenced StaticMesh + UStaticMesh* GetStaticMesh(); + + // Returns true if the attached component's materials have been modified + bool HasComponentMaterialsChanged() const; + + // Return true if SMC's static mesh has been modified + virtual bool HasComponentChanged() const override; + +public: + + // Keep track of the selected Static Mesh + UPROPERTY() + TSoftObjectPtr StaticMesh = nullptr; + + // Path to the materials assigned on the SMC + UPROPERTY() + TArray MeshComponentsMaterials; +}; + + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UInstancedStaticMeshComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputInstancedMeshComponent : public UHoudiniInputMeshComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // InstancedStaticMeshComponent accessor + UInstancedStaticMeshComponent* GetInstancedStaticMeshComponent(); + + // Returns true if the instances have changed + bool HasInstancesChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const override; + +public: + + // Array of transform for this ISMC's instances + UPROPERTY() + TArray InstanceTransforms; +}; + + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USplineComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSplineComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // USplineComponent accessor + USplineComponent* GetSplineComponent(); + + // Returns true if the attached spline component has been modified + bool HasSplineComponentChanged(float fCurrentSplineResolution) const; + + // Returns true if the attached actor's (parent) transform has been modified + virtual bool HasActorTransformChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const; + + // Return true if the component itself has been modified + virtual bool HasComponentChanged() const; + +public: + + // Number of CVs used by the spline component, used to detect modification + UPROPERTY() + int32 NumberOfSplineControlPoints = -1; + + // Spline Length, used for fast detection of modifications of the spline.. + UPROPERTY() + float SplineLength = -1.0f; + + // Spline resolution used to generate the asset, used to detect setting modification + UPROPERTY() + float SplineResolution = -1.0f; + + // Is the spline closed? + UPROPERTY() + bool SplineClosed = false; + + // Transforms of each of the spline's control points + UPROPERTY() + TArray SplineControlPoints; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniSplineComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniSplineComponent : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + virtual void Update(UObject * InObject) override; + + virtual UObject* GetObject() const override; + + virtual void MarkChanged(const bool& bInChanged) override; + + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) override; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const override; + + // Indicates if this input needs to trigger an update + virtual bool NeedsToTriggerUpdate() const override; + + // UHoudiniSplineComponent accessor + UHoudiniSplineComponent* GetCurveComponent() const; + +public: + + // The type of curve (polygon, NURBS, bezier) + UPROPERTY() + EHoudiniCurveType CurveType = EHoudiniCurveType::Polygon; + + // The curve's method (CVs, Breakpoint, Freehand) + UPROPERTY() + EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::CVs; + + UPROPERTY() + bool Reversed = false; + + +protected: + + // NOTE: We are using this reference to the component since the component, for now, + // lives on the same actor as this input object. If we use a Soft Object Reference instead the editor + // will complain about breaking references everytime we try to delete the actor. + UPROPERTY(Instanced) + UHoudiniSplineComponent* CachedComponent; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UCameraComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputCameraComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // UCameraComponent accessor + UCameraComponent* GetCameraComponent(); + + // Return true if SMC's static mesh has been modified + virtual bool HasComponentChanged() const override; + +public: + + UPROPERTY() + float FOV; + + UPROPERTY() + float AspectRatio; + + UPROPERTY() + //TEnumAsByte ProjectionType; + bool bIsOrthographic; + + UPROPERTY() + float OrthoWidth; + UPROPERTY() + float OrthoNearClipPlane; + UPROPERTY() + float OrthoFarClipPlane; + +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniAssetComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniAsset : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // UHoudiniAssetComponent accessor + UHoudiniAssetComponent* GetHoudiniAssetComponent(); +public: + + // The output index of the node that we want to use as input + UPROPERTY() + int32 AssetOutputIndex; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// AActor input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // + virtual bool HasActorTransformChanged(); + + // Return true if any content of this actor has possibly changed (for example geometry edits on a + // Brush or changes on procedurally generated content). + // NOTE: This is more generally applicable and could be moved to the HoudiniInputObject class. + virtual bool HasContentChanged() const; + + // AActor accessor + AActor* GetActor(); + + const TArray& GetActorComponents() const { return ActorComponents; } + + // The number of components added with the last call to Update + int32 GetLastUpdateNumComponentsAdded() const { return LastUpdateNumComponentsAdded; } + // The number of components remove with the last call to Update + int32 GetLastUpdateNumComponentsRemoved() const { return LastUpdateNumComponentsRemoved; } + +protected: + + // The actor's components that can be sent as inputs + UPROPERTY() + TArray ActorComponents; + + // The USceneComponents the actor had the last time we called Update (matches the ones in ActorComponents). + UPROPERTY() + TSet> ActorSceneComponents; + + // The number of components added with the last call to Update + UPROPERTY() + int32 LastUpdateNumComponentsAdded; + + // The number of components remove with the last call to Update + UPROPERTY() + int32 LastUpdateNumComponentsRemoved; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// ALandscapeProxy input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActor +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + virtual bool HasActorTransformChanged() override; + + // ALandscapeProxy accessor + ALandscapeProxy* GetLandscapeProxy(); + + void SetLandscapeProxy(UObject* InLandscapeProxy); + + // Used to restore an input landscape's transform to its original state + UPROPERTY() + FTransform CachedInputLandscapeTraqnsform; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// ABrush input +//----------------------------------------------------------------------------------------------------------------------------- +// Cache info for a brush in order to determine whether it has changed. + +#define BRUSH_HASH_SURFACE_PROPERTIES 0 + +//USTRUCT() +//struct FHoudiniBrushSurfaceInfo { +// GENERATED_BODY() +// +// FVector Base; +// FVector Normal; +// FVector TextureU; +// FVector TextureV; +// TSoftObjectPtr Material; +// +// FHoudiniBrushSurfaceInfo(const FVector& InBase, const FVector& InNormal, const FVector& InTextureU, const FVector& InTextureV, UMaterialInterface* InMaterial) +// : Base(InBase) +// , Normal(InNormal) +// , TextureU(InTextureU) +// , TextureV(InTextureV) +// , Material(InMaterial) +// { } +// +// inline bool operator==(const FHoudiniBrushSurfaceInfo& Other) { +// return Base.Equals(Other.Base) +// && Normal.Equals(Other.Normal) +// && TextureU.Equals(Other.TextureU) +// && TextureV.Equals(Other.TextureV) +// && Material.Get() == Other.Material.Get(); +// } +// +// inline bool operator==(const FPoly& Poly) { +// return Base.Equals(Poly.Base) +// && Normal.Equals(Poly.Normal) +// && TextureU.Equals(Poly.TextureU) +// && TextureV.Equals(Poly.TextureV) +// && Material.Get() == Poly.Material; +// } +//}; + +USTRUCT() +struct FHoudiniBrushInfo +{ + GENERATED_BODY() + + UPROPERTY() + TWeakObjectPtr BrushActor; + UPROPERTY() + FTransform CachedTransform; + UPROPERTY() + FVector CachedOrigin; + UPROPERTY() + FVector CachedExtent; + UPROPERTY() + TEnumAsByte CachedBrushType; + + UPROPERTY() + uint64 CachedSurfaceHash; + + bool HasChanged() const; + + static int32 GetNumVertexIndicesFromModel(const UModel* Model); + + FHoudiniBrushInfo(); + FHoudiniBrushInfo(ABrush* InBrushActor); + + template + inline void HashCombine(uint64& s, const T & v) const + { + std::hash h; + s^= h(v) + 0x9e3779b9 + (s<< 6) + (s>> 2); + } + + inline void HashCombine(uint64& s, const FVector & V) const + { + HashCombine(s, V.X); + HashCombine(s, V.Y); + HashCombine(s, V.Z); + } + + inline void CombinePolyHash(uint64& Hash, const FPoly& Poly) const + { + HashCombine(Hash, Poly.Base); + HashCombine(Hash, Poly.TextureU); + HashCombine(Hash, Poly.TextureV); + HashCombine(Hash, Poly.Normal); + HashCombine(Hash, (uint64)(Poly.Material)); + } +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor +{ + GENERATED_BODY() + +public: + + UHoudiniInputBrush(); + + // Factory function + static UHoudiniInputBrush* Create(UObject* InObject, UObject* InOuter, const FString& InName); + + //---------------------------------------------------------------------- + // UHoudiniInputActor Interface - Begin + //---------------------------------------------------------------------- + + virtual void Update(UObject * InObject) override; + + // Indicates if this input has changed and should be updated + virtual bool HasContentChanged() const override; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const override { return (!bIgnoreInputObject) && bHasChanged; }; + + // Indicates if this input has changed and should be updated + virtual bool HasTransformChanged() const override { return (!bIgnoreInputObject) && bTransformChanged; }; + + virtual bool HasActorTransformChanged() override; + + virtual bool NeedsToTriggerUpdate() const override { return (!bIgnoreInputObject) && bNeedsToTriggerUpdate; }; + + //---------------------------------------------------------------------- + // UHoudiniInputActor Interface - End + //---------------------------------------------------------------------- + + + // ABrush accessor + ABrush* GetBrush() const; + + UModel* GetCachedModel() const; + + // Check whether any of the brushes, or their transforms, used to generate this model have changed. + bool HasBrushesChanged(const TArray& InBrushes) const; + + // Cache the combined model as well as the input brushes. + void UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes); + + // Returns whether this input object should be ignored when uploading objects to Houdini. + // This mechanism could be implemented on UHoudiniInputObject. + bool ShouldIgnoreThisInput(); + + + // Find only the subtractive brush actors that intersect with the InputObject (Brush actor) bounding box but + // excluding any selector bounds actors. + static bool FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes); + +protected: + UPROPERTY() + TArray BrushesInfo; + + UPROPERTY(Transient, DuplicateTransient) + UModel* CombinedModel; + + UPROPERTY() + bool bIgnoreInputObject; + + UPROPERTY() + TEnumAsByte CachedInputBrushType; +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UDataTable input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputDataTable : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // DataTable accessor + class UDataTable* GetDataTable() const; +}; + +//----------------------------------------------------------------------------------------------------------------------------- +// UFoliageType_InstancedStaticMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputFoliageType_InstancedStaticMesh : public UHoudiniInputStaticMesh +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // UHoudiniInputObject overrides + + // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; + + // + virtual void Update(UObject * InObject) override; + + // StaticMesh accessor + virtual class UStaticMesh* GetStaticMesh() const override; + + virtual class UBlueprint* GetBlueprint() const override { return nullptr; } + + virtual bool bIsBlueprint() const override { return false; } +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h index 9632a6b57..7a0d78b2d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h @@ -1,68 +1,68 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -UENUM() -enum class EHoudiniInputType : uint8 -{ - Invalid, - - Geometry, - Curve, - Asset, - Landscape, - World, - Skeletal, - -}; -// Maintain an iterable list of houdini input types -static const EHoudiniInputType HoudiniInputTypeList[] = { - EHoudiniInputType::Geometry, - EHoudiniInputType::Curve, - EHoudiniInputType::Asset, - EHoudiniInputType::Landscape, - EHoudiniInputType::World, - EHoudiniInputType::Skeletal }; - -UENUM() -enum class EHoudiniXformType : uint8 -{ - None, - IntoThisObject, - Auto -}; - -UENUM() -enum class EHoudiniLandscapeExportType : uint8 -{ - Heightfield, - Mesh, - Points +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +UENUM() +enum class EHoudiniInputType : uint8 +{ + Invalid, + + Geometry, + Curve, + Asset, + Landscape, + World, + Skeletal, + +}; +// Maintain an iterable list of houdini input types +static const EHoudiniInputType HoudiniInputTypeList[] = { + EHoudiniInputType::Geometry, + EHoudiniInputType::Curve, + EHoudiniInputType::Asset, + EHoudiniInputType::Landscape, + EHoudiniInputType::World, + EHoudiniInputType::Skeletal }; + +UENUM() +enum class EHoudiniXformType : uint8 +{ + None, + IntoThisObject, + Auto +}; + +UENUM() +enum class EHoudiniLandscapeExportType : uint8 +{ + Heightfield, + Mesh, + Points }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp index f0fced9b7..c6287686c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp @@ -1,248 +1,248 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInstancedActorComponent.h" - -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniInstancedActorComponent::UHoudiniInstancedActorComponent( const FObjectInitializer& ObjectInitializer ) -: Super( ObjectInitializer ) -, InstancedObject( nullptr ) -{ - // - // Set default component properties. - // - Mobility = EComponentMobility::Static; - bCanEverAffectNavigation = true; - bNeverNeedsRenderUpdate = false; - Bounds = FBox(ForceInitToZero); -} - - -void -UHoudiniInstancedActorComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on the setting value - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniInstancedActorComponent_V1* CompatibilityIAC = NewObject(); - CompatibilityIAC->Serialize(Ar); - CompatibilityIAC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - - -void UHoudiniInstancedActorComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) -{ - ClearAllInstances(); - Super::OnComponentDestroyed( bDestroyingHierarchy ); -} - - -void -UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector ) -{ - UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); - if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) - { - if ( ThisHIAC->InstancedObject && !ThisHIAC->InstancedObject->IsPendingKill() ) - Collector.AddReferencedObject( ThisHIAC->InstancedObject, ThisHIAC ); - - Collector.AddReferencedObjects(ThisHIAC->InstancedActors, ThisHIAC ); - } -} - - -int32 -UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform, AActor * NewActor) -{ - if (!NewActor || NewActor->IsPendingKill()) - return -1; - - NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - NewActor->SetActorRelativeTransform(InstanceTransform); - return InstancedActors.Add(NewActor); -} - - -bool -UHoudiniInstancedActorComponent::SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor) -{ - if (!NewActor || NewActor->IsPendingKill()) - return false; - - if (!InstancedActors.IsValidIndex(Idx)) - return false; - - NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - NewActor->SetActorRelativeTransform(InstanceTransform); - NewActor->RegisterAllComponents(); - InstancedActors[Idx] = NewActor; - - return true; -} - - -bool -UHoudiniInstancedActorComponent::SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform) -{ - if (!InstancedActors.IsValidIndex(Idx)) - return false; - - InstancedActors[Idx]->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - InstancedActors[Idx]->SetActorRelativeTransform(InstanceTransform); - - return true; -} - - -void -UHoudiniInstancedActorComponent::ClearAllInstances() -{ - for ( AActor* Instance : InstancedActors ) - { - if ( Instance && !Instance->IsPendingKill() ) - Instance->Destroy(); - } - InstancedActors.Empty(); -} - - -void -UHoudiniInstancedActorComponent::SetNumberOfInstances(const int32& NewInstanceNum) -{ - int32 OldInstanceNum = InstancedActors.Num(); - - // If we want less instances than we already have, destroy the extra properly - if (NewInstanceNum < OldInstanceNum) - { - for (int32 Idx = NewInstanceNum - 1; Idx < InstancedActors.Num(); Idx++) - { - AActor* Instance = InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; - if (Instance && !Instance->IsPendingKill()) - Instance->Destroy(); - } - } - - // Grow the array with nulls if needed - InstancedActors.SetNumZeroed(NewInstanceNum); -} - - -void -UHoudiniInstancedActorComponent::OnComponentCreated() -{ - Super::OnComponentCreated(); - - // If our instances are parented to another actor we should duplicate them - bool bNeedDuplicate = false; - for (auto CurrentInstance : InstancedActors) - { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) - continue; - - if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) - bNeedDuplicate = true; - } - - if ( !bNeedDuplicate ) - return; - - // TODO: CHECK ME! - // We need to duplicate our instances - TArray SourceInstances = InstancedActors; - InstancedActors.Empty(); - for (AActor* CurrentInstance : SourceInstances) - { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) - continue; - - FTransform InstanceTransform; - if ( CurrentInstance->GetRootComponent() ) - InstanceTransform = CurrentInstance->GetRootComponent()->GetRelativeTransform(); - - // AddInstance( InstanceTransform ); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInstancedActorComponent.h" + +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniInstancedActorComponent::UHoudiniInstancedActorComponent( const FObjectInitializer& ObjectInitializer ) +: Super( ObjectInitializer ) +, InstancedObject( nullptr ) +{ + // + // Set default component properties. + // + Mobility = EComponentMobility::Static; + bCanEverAffectNavigation = true; + bNeverNeedsRenderUpdate = false; + Bounds = FBox(ForceInitToZero); +} + + +void +UHoudiniInstancedActorComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on the setting value + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniInstancedActorComponent_V1* CompatibilityIAC = NewObject(); + CompatibilityIAC->Serialize(Ar); + CompatibilityIAC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + + +void UHoudiniInstancedActorComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + ClearAllInstances(); + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + + +void +UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); + if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) + { + if ( ThisHIAC->InstancedObject && !ThisHIAC->InstancedObject->IsPendingKill() ) + Collector.AddReferencedObject( ThisHIAC->InstancedObject, ThisHIAC ); + + Collector.AddReferencedObjects(ThisHIAC->InstancedActors, ThisHIAC ); + } +} + + +int32 +UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform, AActor * NewActor) +{ + if (!NewActor || NewActor->IsPendingKill()) + return -1; + + NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + NewActor->SetActorRelativeTransform(InstanceTransform); + return InstancedActors.Add(NewActor); +} + + +bool +UHoudiniInstancedActorComponent::SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor) +{ + if (!NewActor || NewActor->IsPendingKill()) + return false; + + if (!InstancedActors.IsValidIndex(Idx)) + return false; + + NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + NewActor->SetActorRelativeTransform(InstanceTransform); + NewActor->RegisterAllComponents(); + InstancedActors[Idx] = NewActor; + + return true; +} + + +bool +UHoudiniInstancedActorComponent::SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform) +{ + if (!InstancedActors.IsValidIndex(Idx)) + return false; + + InstancedActors[Idx]->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + InstancedActors[Idx]->SetActorRelativeTransform(InstanceTransform); + + return true; +} + + +void +UHoudiniInstancedActorComponent::ClearAllInstances() +{ + for ( AActor* Instance : InstancedActors ) + { + if ( Instance && !Instance->IsPendingKill() ) + Instance->Destroy(); + } + InstancedActors.Empty(); +} + + +void +UHoudiniInstancedActorComponent::SetNumberOfInstances(const int32& NewInstanceNum) +{ + int32 OldInstanceNum = InstancedActors.Num(); + + // If we want less instances than we already have, destroy the extra properly + if (NewInstanceNum < OldInstanceNum) + { + for (int32 Idx = NewInstanceNum - 1; Idx < InstancedActors.Num(); Idx++) + { + AActor* Instance = InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; + if (Instance && !Instance->IsPendingKill()) + Instance->Destroy(); + } + } + + // Grow the array with nulls if needed + InstancedActors.SetNumZeroed(NewInstanceNum); +} + + +void +UHoudiniInstancedActorComponent::OnComponentCreated() +{ + Super::OnComponentCreated(); + + // If our instances are parented to another actor we should duplicate them + bool bNeedDuplicate = false; + for (auto CurrentInstance : InstancedActors) + { + if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + continue; + + if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) + bNeedDuplicate = true; + } + + if ( !bNeedDuplicate ) + return; + + // TODO: CHECK ME! + // We need to duplicate our instances + TArray SourceInstances = InstancedActors; + InstancedActors.Empty(); + for (AActor* CurrentInstance : SourceInstances) + { + if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + continue; + + FTransform InstanceTransform; + if ( CurrentInstance->GetRootComponent() ) + InstanceTransform = CurrentInstance->GetRootComponent()->GetRelativeTransform(); + + // AddInstance( InstanceTransform ); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h index 32a61b696..44ea7cf74 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h @@ -1,91 +1,91 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" - -#include "HoudiniInstancedActorComponent.generated.h" - - -UCLASS()//( config = Engine ) -class HOUDINIENGINERUNTIME_API UHoudiniInstancedActorComponent : public USceneComponent -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniInstancedActorComponent_V1; - - public: - - virtual void Serialize(FArchive & Ar) override; - - virtual void OnComponentCreated() override; - virtual void OnComponentDestroyed( bool bDestroyingHierarchy ) override; - - static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); - - // Object mutator - void SetInstancedObject(class UObject* InObject) { InstancedObject = InObject; } - // Object accessor - class UObject* GetInstancedObject() const { return InstancedObject; } - - - // Instance Accessor - TArray& GetInstancedActorsForWrite() { return InstancedActors; } - // const Instance accessor - const TArray& GetInstancedActors() const { return InstancedActors; } - - // Returns the instanced actor at a given index - AActor* GetInstancedActorAt(const int32& Idx) { return InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; } - - // Add an instance to this component. Transform is given in local space of this component. - int32 AddInstance(const FTransform& InstanceTransform, AActor * NewActor); - - // Sets the instance at a given index in this component. Transform is given in local space of this component. - bool SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor); - - // Updates the transform for a given actor. Transform is given in local space of this component. - bool SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform); - - // Destroy all existing instances - void ClearAllInstances(); - - // Sets the number of instances needed - // Properly deletes extras, new instance actors are nulled - void SetNumberOfInstances(const int32& NewInstanceNum); - - // Set the instances. Transforms are given in local space of this component. - bool SetInstanceTransforms(const TArray& InstanceTransforms); - - private: - - UPROPERTY(VisibleAnywhere, Category = Instances ) - UObject* InstancedObject; - - UPROPERTY(VisibleInstanceOnly, Category = Instances ) - TArray InstancedActors; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" + +#include "HoudiniInstancedActorComponent.generated.h" + + +UCLASS()//( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniInstancedActorComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniInstancedActorComponent_V1; + + public: + + virtual void Serialize(FArchive & Ar) override; + + virtual void OnComponentCreated() override; + virtual void OnComponentDestroyed( bool bDestroyingHierarchy ) override; + + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + + // Object mutator + void SetInstancedObject(class UObject* InObject) { InstancedObject = InObject; } + // Object accessor + class UObject* GetInstancedObject() const { return InstancedObject; } + + + // Instance Accessor + TArray& GetInstancedActorsForWrite() { return InstancedActors; } + // const Instance accessor + const TArray& GetInstancedActors() const { return InstancedActors; } + + // Returns the instanced actor at a given index + AActor* GetInstancedActorAt(const int32& Idx) { return InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; } + + // Add an instance to this component. Transform is given in local space of this component. + int32 AddInstance(const FTransform& InstanceTransform, AActor * NewActor); + + // Sets the instance at a given index in this component. Transform is given in local space of this component. + bool SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor); + + // Updates the transform for a given actor. Transform is given in local space of this component. + bool SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform); + + // Destroy all existing instances + void ClearAllInstances(); + + // Sets the number of instances needed + // Properly deletes extras, new instance actors are nulled + void SetNumberOfInstances(const int32& NewInstanceNum); + + // Set the instances. Transforms are given in local space of this component. + bool SetInstanceTransforms(const TArray& InstanceTransforms); + + private: + + UPROPERTY(VisibleAnywhere, Category = Instances ) + UObject* InstancedObject; + + UPROPERTY(VisibleInstanceOnly, Category = Instances ) + TArray InstancedActors; + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp index dbb9fda15..a1ca7abca 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp @@ -1,247 +1,247 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniMeshSplitInstancerComponent.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -#include "Components/StaticMeshComponent.h" - -/* -#if WITH_EDITOR - #include "ScopedTransaction.h" - #include "LevelEditorViewport.h" - #include "MeshPaintHelpers.h" -#endif -*/ - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniMeshSplitInstancerComponent::UHoudiniMeshSplitInstancerComponent(const FObjectInitializer& ObjectInitializer) - : Super( ObjectInitializer ) - , InstancedMesh( nullptr ) -{ -} - -void -UHoudiniMeshSplitInstancerComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on the setting value - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniMeshSplitInstancerComponent_V1* CompatibilityMSIC = NewObject(); - CompatibilityMSIC->Serialize(Ar); - CompatibilityMSIC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -void -UHoudiniMeshSplitInstancerComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) -{ - ClearInstances(0); - Super::OnComponentDestroyed( bDestroyingHierarchy ); -} - - -void -UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) -{ - UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); - if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) - { - Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); - for(auto& Mat : ThisMSIC->OverrideMaterials) - Collector.AddReferencedObject(Mat, ThisMSIC); - Collector.AddReferencedObjects(ThisMSIC->Instances, ThisMSIC); - } -} - -bool -UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( - const TArray& InstanceTransforms) -{ - if (Instances.Num() <= 0 && InstanceTransforms.Num() <= 0) - return false; - - if (!GetOwner() || GetOwner()->IsPendingKill()) - return false; - - // Destroy previous instances while keeping some of the one that we'll be able to reuse - ClearInstances(InstanceTransforms.Num()); - - // - if( !InstancedMesh || InstancedMesh->IsPendingKill() ) - { - HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); - return false; - } - - // Only create new SMC for newly added instances - for (int32 iAdd = Instances.Num(); iAdd < InstanceTransforms.Num(); iAdd++) - { - const FTransform& InstanceTransform = InstanceTransforms[iAdd]; - UStaticMeshComponent* SMC = NewObject< UStaticMeshComponent >( - GetOwner(), UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - SMC->SetRelativeTransform(InstanceTransform); - Instances.Add(SMC); - GetOwner()->AddInstanceComponent(SMC); - } - - // We should now have the same number of instances than transform - ensure(InstanceTransforms.Num() == Instances.Num()); - if (InstanceTransforms.Num() != Instances.Num()) - return false; - - for (int32 iIns = 0; iIns < Instances.Num(); ++iIns) - { - UStaticMeshComponent* SMC = Instances[iIns]; - const FTransform& InstanceTransform = InstanceTransforms[iIns]; - - if (!SMC || SMC->IsPendingKill()) - continue; - - SMC->SetRelativeTransform(InstanceTransform); - - // Attach created static mesh component to this thing - SMC->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - - SMC->SetStaticMesh(InstancedMesh); - SMC->SetVisibility(IsVisible()); - SMC->SetMobility(Mobility); - - // TODO: Revert to default if override is null?? - UMaterialInterface* MI = nullptr; - if (OverrideMaterials.Num() > 0) - { - if (OverrideMaterials.IsValidIndex(iIns)) - MI = OverrideMaterials[iIns]; - else - MI = OverrideMaterials[0]; - } - - if (MI && !MI->IsPendingKill()) - { - int32 MeshMaterialCount = InstancedMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - SMC->SetMaterial(Idx, MI); - } - - SMC->RegisterComponent(); - - /* - // TODO: - // Properties not being propagated to newly created UStaticMeshComponents - if (UHoudiniAssetComponent * pHoudiniAsset = Cast(GetAttachParent())) - { - pHoudiniAsset->CopyComponentPropertiesTo(SMC); - } - */ - } - - return true; -} - -void -UHoudiniMeshSplitInstancerComponent::ClearInstances(int32 NumToKeep) -{ - if (NumToKeep <= 0) - { - for (auto&& Instance : Instances) - { - if (Instance) - { - Instance->ConditionalBeginDestroy(); - } - } - Instances.Empty(); - } - else if (NumToKeep > 0 && NumToKeep < Instances.Num()) - { - for (int32 i = NumToKeep; i < Instances.Num(); ++i) - { - UStaticMeshComponent * Instance = Instances[i]; - if (Instance) - { - Instance->ConditionalBeginDestroy(); - } - } - Instances.SetNum(NumToKeep); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniMeshSplitInstancerComponent.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +#include "Components/StaticMeshComponent.h" + +/* +#if WITH_EDITOR + #include "ScopedTransaction.h" + #include "LevelEditorViewport.h" + #include "MeshPaintHelpers.h" +#endif +*/ + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniMeshSplitInstancerComponent::UHoudiniMeshSplitInstancerComponent(const FObjectInitializer& ObjectInitializer) + : Super( ObjectInitializer ) + , InstancedMesh( nullptr ) +{ +} + +void +UHoudiniMeshSplitInstancerComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on the setting value + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniMeshSplitInstancerComponent_V1* CompatibilityMSIC = NewObject(); + CompatibilityMSIC->Serialize(Ar); + CompatibilityMSIC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +void +UHoudiniMeshSplitInstancerComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + ClearInstances(0); + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + + +void +UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); + if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) + { + Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); + for(auto& Mat : ThisMSIC->OverrideMaterials) + Collector.AddReferencedObject(Mat, ThisMSIC); + Collector.AddReferencedObjects(ThisMSIC->Instances, ThisMSIC); + } +} + +bool +UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( + const TArray& InstanceTransforms) +{ + if (Instances.Num() <= 0 && InstanceTransforms.Num() <= 0) + return false; + + if (!GetOwner() || GetOwner()->IsPendingKill()) + return false; + + // Destroy previous instances while keeping some of the one that we'll be able to reuse + ClearInstances(InstanceTransforms.Num()); + + // + if( !InstancedMesh || InstancedMesh->IsPendingKill() ) + { + HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); + return false; + } + + // Only create new SMC for newly added instances + for (int32 iAdd = Instances.Num(); iAdd < InstanceTransforms.Num(); iAdd++) + { + const FTransform& InstanceTransform = InstanceTransforms[iAdd]; + UStaticMeshComponent* SMC = NewObject< UStaticMeshComponent >( + GetOwner(), UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + SMC->SetRelativeTransform(InstanceTransform); + Instances.Add(SMC); + GetOwner()->AddInstanceComponent(SMC); + } + + // We should now have the same number of instances than transform + ensure(InstanceTransforms.Num() == Instances.Num()); + if (InstanceTransforms.Num() != Instances.Num()) + return false; + + for (int32 iIns = 0; iIns < Instances.Num(); ++iIns) + { + UStaticMeshComponent* SMC = Instances[iIns]; + const FTransform& InstanceTransform = InstanceTransforms[iIns]; + + if (!SMC || SMC->IsPendingKill()) + continue; + + SMC->SetRelativeTransform(InstanceTransform); + + // Attach created static mesh component to this thing + SMC->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + SMC->SetStaticMesh(InstancedMesh); + SMC->SetVisibility(IsVisible()); + SMC->SetMobility(Mobility); + + // TODO: Revert to default if override is null?? + UMaterialInterface* MI = nullptr; + if (OverrideMaterials.Num() > 0) + { + if (OverrideMaterials.IsValidIndex(iIns)) + MI = OverrideMaterials[iIns]; + else + MI = OverrideMaterials[0]; + } + + if (MI && !MI->IsPendingKill()) + { + int32 MeshMaterialCount = InstancedMesh->StaticMaterials.Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + SMC->SetMaterial(Idx, MI); + } + + SMC->RegisterComponent(); + + /* + // TODO: + // Properties not being propagated to newly created UStaticMeshComponents + if (UHoudiniAssetComponent * pHoudiniAsset = Cast(GetAttachParent())) + { + pHoudiniAsset->CopyComponentPropertiesTo(SMC); + } + */ + } + + return true; +} + +void +UHoudiniMeshSplitInstancerComponent::ClearInstances(int32 NumToKeep) +{ + if (NumToKeep <= 0) + { + for (auto&& Instance : Instances) + { + if (Instance) + { + Instance->ConditionalBeginDestroy(); + } + } + Instances.Empty(); + } + else if (NumToKeep > 0 && NumToKeep < Instances.Num()) + { + for (int32 i = NumToKeep; i < Instances.Num(); ++i) + { + UStaticMeshComponent * Instance = Instances[i]; + if (Instance) + { + Instance->ConditionalBeginDestroy(); + } + } + Instances.SetNum(NumToKeep); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h index 835546667..12f98bd75 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h @@ -1,85 +1,85 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" -#include "Engine/StaticMesh.h" -#include "Materials/MaterialInterface.h" -#include "HoudiniMeshSplitInstancerComponent.generated.h" - -/** -* UHoudiniMeshSplitInstancerComponent is used to manage a single static mesh being -* 'instanced' multiple times by multiple UStaticMeshComponents. This is as opposed to the -* UInstancedStaticMeshComponent wherein a single mesh is instanced multiple times by one component. -*/ - -UCLASS()//( config = Engine ) -class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USceneComponent -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniMeshSplitInstancerComponent_V1; - - public: - - virtual void Serialize(FArchive & Ar) override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - - static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); - - // Static Mesh mutator - void SetStaticMesh(class UStaticMesh* StaticMesh) { InstancedMesh = StaticMesh; } - - // Static mesh accessor - class UStaticMesh* GetStaticMesh() const { return InstancedMesh; } - - // Overide material mutator - void SetOverrideMaterials(const TArray& InMaterialOverrides) { OverrideMaterials = InMaterialOverrides; } - - // Destroy existing instances, keeping a given number of them to be reused - void ClearInstances(int32 NumToKeep); - - // Set the instances. Transforms are given in local space of this component. - bool SetInstanceTransforms(const TArray& InstanceTransforms); - - // Instance Accessor - TArray& GetInstancesForWrite() { return Instances; } - // const Instance accessor - const TArray& GetInstances() const { return Instances; } - - private: - - UPROPERTY(VisibleInstanceOnly, Category = Instances) - TArray Instances; - - UPROPERTY(VisibleInstanceOnly, Category = Instances) - TArray OverrideMaterials; - - UPROPERTY(VisibleAnywhere, Category = Instances) - class UStaticMesh* InstancedMesh; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" +#include "Engine/StaticMesh.h" +#include "Materials/MaterialInterface.h" +#include "HoudiniMeshSplitInstancerComponent.generated.h" + +/** +* UHoudiniMeshSplitInstancerComponent is used to manage a single static mesh being +* 'instanced' multiple times by multiple UStaticMeshComponents. This is as opposed to the +* UInstancedStaticMeshComponent wherein a single mesh is instanced multiple times by one component. +*/ + +UCLASS()//( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniMeshSplitInstancerComponent_V1; + + public: + + virtual void Serialize(FArchive & Ar) override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); + + // Static Mesh mutator + void SetStaticMesh(class UStaticMesh* StaticMesh) { InstancedMesh = StaticMesh; } + + // Static mesh accessor + class UStaticMesh* GetStaticMesh() const { return InstancedMesh; } + + // Overide material mutator + void SetOverrideMaterials(const TArray& InMaterialOverrides) { OverrideMaterials = InMaterialOverrides; } + + // Destroy existing instances, keeping a given number of them to be reused + void ClearInstances(int32 NumToKeep); + + // Set the instances. Transforms are given in local space of this component. + bool SetInstanceTransforms(const TArray& InstanceTransforms); + + // Instance Accessor + TArray& GetInstancesForWrite() { return Instances; } + // const Instance accessor + const TArray& GetInstances() const { return Instances; } + + private: + + UPROPERTY(VisibleInstanceOnly, Category = Instances) + TArray Instances; + + UPROPERTY(VisibleInstanceOnly, Category = Instances) + TArray OverrideMaterials; + + UPROPERTY(VisibleAnywhere, Category = Instances) + class UStaticMesh* InstancedMesh; +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp index d7661d317..d0bb82f4e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp @@ -1,916 +1,916 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniOutput.h" -#include "HoudiniAssetComponent.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniSplineComponent.h" - -#include "Components/SceneComponent.h" -#include "Components/MeshComponent.h" -#include "Components/SplineComponent.h" -#include "Misc/StringFormatArg.h" -#include "Engine/Engine.h" - -UHoudiniLandscapePtr::UHoudiniLandscapePtr(class FObjectInitializer const& Initializer) -{ - // bIsWorldCompositionLandscape = false; - // BakeType = EHoudiniLandscapeOutputBakeType::Detachment; -}; - -uint32 -GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier) -{ - return HoudiniOutputObjectIdentifier.GetTypeHash(); -} - -void -FHoudiniInstancedOutput::SetVariationObjectAt(const int32& AtIndex, UObject* InObject) -{ - // Resize the array if needed - if (VariationObjects.Num() <= AtIndex) - VariationObjects.SetNum(AtIndex + 1); - - if (VariationTransformOffsets.Num() <= AtIndex) - VariationTransformOffsets.SetNum(AtIndex + 1); - - UObject* CurrentObject = VariationObjects[AtIndex].LoadSynchronous(); - if (CurrentObject == InObject) - return; - - VariationObjects[AtIndex] = InObject; -} - -bool -FHoudiniInstancedOutput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; - if (!Transform) - return false; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - if (Position[XYZIndex] == Value) - return false; - Position[XYZIndex] = Value; - Transform->SetLocation(Position); - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - if (Rotator.Roll == Value) - return false; - Rotator.Roll = Value; - break; - } - - case 1: - { - if (Rotator.Pitch == Value) - return false; - Rotator.Pitch = Value; - break; - } - - case 2: - { - if (Rotator.Yaw == Value) - return false; - Rotator.Yaw = Value; - break; - } - } - Transform->SetRotation(Rotator.Quaternion()); - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - if (Scale[XYZIndex] == Value) - return false; - - Scale[XYZIndex] = Value; - Transform->SetScale3D(Scale); - } - - MarkChanged(true); - - return true; -} - -float -FHoudiniInstancedOutput::GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; - if (!Transform) - return 0.0f; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - return Position[XYZIndex]; - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - return Rotator.Roll; - } - - case 1: - { - return Rotator.Pitch; - } - - case 2: - { - return Rotator.Yaw; - } - } - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - return Scale[XYZIndex]; - } - - return 0.0f; -} - -// ---------------------------------------------------- -// FHoudiniOutputObjectIdentifier -// ---------------------------------------------------- - -FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier() -{ - ObjectId = -1; - GeoId = -1; - PartId = -1; - SplitIdentifier = FString(); - PartName = FString(); -} - -FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier( - const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier) -{ - ObjectId = InObjectId; - GeoId = InGeoId; - PartId = InPartId; - SplitIdentifier = InSplitIdentifier; -} - -uint32 -FHoudiniOutputObjectIdentifier::GetTypeHash() const -{ - int32 HashBuffer[3] = { ObjectId, GeoId, PartId }; - int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); - return FCrc::StrCrc32(*SplitIdentifier, Hash); -} - -bool -FHoudiniOutputObjectIdentifier::operator==(const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier) const -{ - // Object/Geo/Part IDs must match - bool bMatchingIds = true; - if (ObjectId != InOutputObjectIdentifier.ObjectId - || GeoId != InOutputObjectIdentifier.GeoId - || PartId != InOutputObjectIdentifier.PartId) - bMatchingIds = false; - - if ((bLoaded && !InOutputObjectIdentifier.bLoaded) - || (!bLoaded && InOutputObjectIdentifier.bLoaded)) - { - // If one of the two identifier is loaded, - // we can simply compare the part names - if (PartName.Equals(InOutputObjectIdentifier.PartName) - && SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) - return true; - } - - if (!bMatchingIds) - { - return false; - } - - // If split ID and name match, we're equal... - if (SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) - return true; - - // ... if not we're different - return false; -} - -bool -FHoudiniOutputObjectIdentifier::Matches(const FHoudiniGeoPartObject& InHGPO) const -{ - // Object/Geo/Part IDs must match - bool bMatchingIds = true; - if (ObjectId != InHGPO.ObjectId - || GeoId != InHGPO.GeoId - || PartId != InHGPO.PartId) - bMatchingIds = false; - - if ((bLoaded && !InHGPO.bLoaded) || (!bLoaded && InHGPO.bLoaded)) - { - // If either the HGPO or the Identifer is nmarked as loaded, - // we can simply compare the part names - if (PartName.Equals(InHGPO.PartName)) - return true; - } - - if (!bMatchingIds) - { - return false; - } - - // If the HGPO has our split identifier - //if (InHGPO.SplitGroups.Contains(SplitIdentifier)) - // return true; - - // - return true; -} - - -// ---------------------------------------------------- -// FHoudiniBakedOutputObjectIdentifier -// ---------------------------------------------------- - - -FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier() -{ - PartId = -1; - SplitIdentifier = FString(); -} - -FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier( - const int32& InPartId, const FString& InSplitIdentifier) -{ - PartId = InPartId; - SplitIdentifier = InSplitIdentifier; -} - -FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - PartId = InIdentifier.PartId; - SplitIdentifier = InIdentifier.SplitIdentifier; -} - -uint32 -FHoudiniBakedOutputObjectIdentifier::GetTypeHash() const -{ - const int32 HashBuffer = PartId; - const int32 Hash = FCrc::MemCrc32((void *)&HashBuffer, sizeof(HashBuffer)); - return FCrc::StrCrc32(*SplitIdentifier, Hash); -} - -uint32 -GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) -{ - return InIdentifier.GetTypeHash(); -} - -bool -FHoudiniBakedOutputObjectIdentifier::operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const -{ - return (InIdentifier.PartId == PartId && InIdentifier.SplitIdentifier.Equals(SplitIdentifier)); -} - - -// ---------------------------------------------------- -// FHoudiniBakedOutputObject -// ---------------------------------------------------- - - -FHoudiniBakedOutputObject::FHoudiniBakedOutputObject() - : Actor() - , ActorBakeName(NAME_None) - , BakedObject() - , BakedComponent() -{ -} - - -FHoudiniBakedOutputObject::FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject, UObject* InBakedComponent) - : Actor(FSoftObjectPath(InActor).ToString()) - , ActorBakeName(InActorBakeName) - , BakedObject(FSoftObjectPath(InBakeObject).ToString()) - , BakedComponent(FSoftObjectPath(InBakedComponent).ToString()) -{ -} - - -AActor* -FHoudiniBakedOutputObject::GetActorIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ActorPath(Actor); - - if (!ActorPath.IsValid()) - return nullptr; - - UObject* Object = ActorPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ActorPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Cast(Object); -} - -UObject* -FHoudiniBakedOutputObject::GetBakedObjectIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ObjectPath(BakedObject); - - if (!ObjectPath.IsValid()) - return nullptr; - - UObject* Object = ObjectPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ObjectPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Object; -} - -UObject* -FHoudiniBakedOutputObject::GetBakedComponentIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ComponentPath(BakedComponent); - - if (!ComponentPath.IsValid()) - return nullptr; - - UObject* Object = ComponentPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ComponentPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Object; -} - -UBlueprint* -FHoudiniBakedOutputObject::GetBlueprintIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath BlueprintPath(Blueprint); - - if (!BlueprintPath.IsValid()) - return nullptr; - - UObject* Object = BlueprintPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = BlueprintPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Cast(Object); -} - - -UHoudiniOutput::UHoudiniOutput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Type(EHoudiniOutputType::Invalid) - , StaleCount(0) - , bLandscapeWorldComposition(false) - , bIsEditableNode(false) - , bHasEditableNodeBuilt(false) - , bCanDeleteHoudiniNodes(true) -{ - -} - -UHoudiniOutput::~UHoudiniOutput() -{ - Type = EHoudiniOutputType::Invalid; - StaleCount = 0; - bIsUpdating = false; - - HoudiniGeoPartObjects.Empty(); - OutputObjects.Empty(); - InstancedOutputs.Empty(); - AssignementMaterials.Empty(); - ReplacementMaterials.Empty(); -} - -void -UHoudiniOutput::BeginDestroy() -{ - Super::BeginDestroy(); -} - -FBox -UHoudiniOutput::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - - switch (GetType()) - { - case EHoudiniOutputType::Mesh: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - - UMeshComponent* MeshComp = nullptr; - if (CurObj.bProxyIsCurrent) - { - MeshComp = Cast(CurObj.ProxyComponent); - } - else - { - MeshComp = Cast(CurObj.OutputComponent); - } - - if (!MeshComp || MeshComp->IsPendingKill()) - continue; - - BoxBounds += MeshComp->Bounds.GetBox(); - } - } - break; - - case EHoudiniOutputType::Landscape: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - UHoudiniLandscapePtr* CurLandscapeObj = Cast(CurObj.OutputObject); - if (!CurLandscapeObj || CurLandscapeObj->IsPendingKill()) - continue; - - ALandscapeProxy* Landscape = Cast(CurLandscapeObj->GetRawPtr()); - if (!Landscape || Landscape->IsPendingKill()) - continue; - - FVector Origin, Extent; - Landscape->GetActorBounds(false, Origin, Extent); - - FBox LandscapeBounds = FBox::BuildAABB(Origin, Extent); - BoxBounds += LandscapeBounds; - } - } - break; - - case EHoudiniOutputType::Instancer: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - USceneComponent* InstancedComp = Cast(CurObj.OutputObject); - if (!InstancedComp || InstancedComp->IsPendingKill()) - continue; - - BoxBounds += InstancedComp->Bounds.GetBox(); - } - } - break; - - case EHoudiniOutputType::Curve: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - UHoudiniSplineComponent* CurHoudiniSplineComp = Cast(CurObj.OutputComponent); - if (!CurHoudiniSplineComp || CurHoudiniSplineComp->IsPendingKill()) - continue; - - FBox CurCurveBound(ForceInitToZero); - for (auto & Trans : CurHoudiniSplineComp->CurvePoints) - { - CurCurveBound += Trans.GetLocation(); - } - - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) - BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); - } - - } - break; - - case EHoudiniOutputType::Skeletal: - case EHoudiniOutputType::Invalid: - break; - - default: - break; - } - - return BoxBounds; -} - -void -UHoudiniOutput::Clear() -{ - StaleCount = 0; - - HoudiniGeoPartObjects.Empty(); - - for (auto& CurrentOutputObject : OutputObjects) - { - // Clear the output component - USceneComponent* SceneComp = Cast(CurrentOutputObject.Value.OutputComponent); - if (SceneComp && !SceneComp->IsPendingKill()) - { - SceneComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComp->UnregisterComponent(); - SceneComp->DestroyComponent(); - } - - // Also destroy proxy components - USceneComponent* ProxyComp = Cast(CurrentOutputObject.Value.ProxyComponent); - if (ProxyComp && !ProxyComp->IsPendingKill()) - { - ProxyComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - ProxyComp->UnregisterComponent(); - ProxyComp->DestroyComponent(); - } - - if (Type == EHoudiniOutputType::Landscape && !bLandscapeWorldComposition && !IsGarbageCollecting()) - { - // NOTE: We cannot resolve soft pointers during garbage collection. Any Get() or IsValid() call - // will result in a StaticFindObject() call which will raise an exception during GC. - UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentOutputObject.Value.OutputObject); - ALandscapeProxy* LandscapeProxy = LandscapePtr ? LandscapePtr->GetRawPtr() : nullptr; - if (LandscapeProxy && !LandscapeProxy->IsPendingKill()) - { - LandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - LandscapeProxy->ConditionalBeginDestroy(); - LandscapeProxy->Destroy(); - } - } - } - - OutputObjects.Empty(); - InstancedOutputs.Empty(); - AssignementMaterials.Empty(); - ReplacementMaterials.Empty(); - - Type = EHoudiniOutputType::Invalid; -} - -bool -UHoudiniOutput::ShouldDeferClear() const -{ - if (Type == EHoudiniOutputType::Landscape) - return true; - - return false; -} - -const bool -UHoudiniOutput::HasGeoChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasGeoChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasTransformChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasTransformChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasMaterialsChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasMaterialsChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const -{ - return HoudiniGeoPartObjects.Find(InHGPO) != INDEX_NONE; -} - -const bool -UHoudiniOutput::HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const -{ - if (InHGPO.Type != EHoudiniPartType::Volume) - return false; - - if (InHGPO.VolumeName.IsEmpty()) - return false; - - for (auto& currentHGPO : HoudiniGeoPartObjects) - { - // Asset/Object/Geo IDs should match - if (currentHGPO.AssetId != InHGPO.AssetId - || currentHGPO.ObjectId != InHGPO.ObjectId - || currentHGPO.GeoId != InHGPO.GeoId) - { - continue; - } - - // Both HGPO type should be volumes - if (currentHGPO.Type != EHoudiniPartType::Volume) - { - continue; - } - - // Volume tile index should match - if (currentHGPO.VolumeTileIndex != InHGPO.VolumeTileIndex) - { - continue; - } - - // We've specified if we want the name to match/to be different: - // when looking in previous outputs, we want the name to match - // when looking in newly created outputs, we want to be sure the names are different - bool bNameMatch = InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase); - if (bNameMatch != bVolumeNameShouldMatch) - continue; - - return true; - } - - return false; -} - -void -UHoudiniOutput::MarkAllHGPOsAsStale(const bool& bInStale) -{ - // Since objects can only be added to this array, - // Simply keep track of the current number of HoudiniGeoPartObject - StaleCount = bInStale ? HoudiniGeoPartObjects.Num() : 0; -} - -void -UHoudiniOutput::DeleteAllStaleHGPOs() -{ - // Simply delete the first "StaleCount" objects and reset the stale marker - HoudiniGeoPartObjects.RemoveAt(0, StaleCount); - StaleCount = 0; -} - -void -UHoudiniOutput::AddNewHGPO(const FHoudiniGeoPartObject& InHGPO) -{ - HoudiniGeoPartObjects.Add(InHGPO); -} - -void -UHoudiniOutput::UpdateOutputType() -{ - int32 MeshCount = 0; - int32 CurveCount = 0; - int32 VolumeCount = 0; - int32 InstancerCount = 0; - for (auto& HGPO : HoudiniGeoPartObjects) - { - switch (HGPO.Type) - { - case EHoudiniPartType::Mesh: - MeshCount++; - break; - case EHoudiniPartType::Curve: - CurveCount++; - break; - case EHoudiniPartType::Volume: - VolumeCount++; - break; - case EHoudiniPartType::Instancer: - InstancerCount++; - break; - default: - case EHoudiniPartType::Invalid: - break; - } - } - - if (VolumeCount > 0) - { - // If we have a volume, we're a landscape - Type = EHoudiniOutputType::Landscape; - } - else if (InstancerCount > 0) - { - // if we have at least an instancer, we're one - Type = EHoudiniOutputType::Instancer; - } - else if (MeshCount > 0) - { - Type = EHoudiniOutputType::Mesh; - } - else if (CurveCount > 0) - { - Type = EHoudiniOutputType::Curve; - } - else - { - // No valid HGPO detected... - Type = EHoudiniOutputType::Invalid; - } -} - -UHoudiniOutput* -UHoudiniOutput::DuplicateAndCopyProperties(UObject* DestOuter, FName NewName) -{ - UHoudiniOutput* NewOutput = Cast(StaticDuplicateObject(this, DestOuter, NewName)); - - NewOutput->CopyPropertiesFrom(this, false); - - return NewOutput; -} - -void -UHoudiniOutput::CopyPropertiesFrom(UHoudiniOutput* InInput, bool bCopyAllProperties) -{ - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - // Stash all the data that we want to preserve, and re-apply after property copy took place - // (similar to Get/Apply component instance data). This is typically only needed - // for certain properties that require cleanup when being replaced / removed. - TMap PrevOutputObjects = OutputObjects; - TMap PrevInstancedOutputs = InstancedOutputs; - - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - - // Restore the desired properties. - OutputObjects = PrevOutputObjects; - InstancedOutputs = PrevInstancedOutputs; - } - - // Copy any additional DuplicateTransient properties. - bHasEditableNodeBuilt = InInput->bHasEditableNodeBuilt; -} - -void -UHoudiniOutput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; -} - -FString -UHoudiniOutput::OutputTypeToString(const EHoudiniOutputType& InOutputType) -{ - FString OutputTypeStr; - switch (InOutputType) - { - case EHoudiniOutputType::Mesh: - OutputTypeStr = TEXT("Mesh"); - break; - case EHoudiniOutputType::Instancer: - OutputTypeStr = TEXT("Instancer"); - break; - case EHoudiniOutputType::Landscape: - OutputTypeStr = TEXT("Landscape"); - break; - case EHoudiniOutputType::Curve: - OutputTypeStr = TEXT("Curve"); - break; - case EHoudiniOutputType::Skeletal: - OutputTypeStr = TEXT("Skeletal"); - break; - - default: - case EHoudiniOutputType::Invalid: - OutputTypeStr = TEXT("Invalid"); - break; - } - - return OutputTypeStr; -} - -void -UHoudiniOutput::MarkAsLoaded(const bool& InLoaded) -{ - // Mark all HGPO as loaded - for (auto& HGPO : HoudiniGeoPartObjects) - { - HGPO.bLoaded = InLoaded; - } - - // Mark all output object's identifier as loaded - for (auto& Iter : OutputObjects) - { - FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; - Identifier.bLoaded = InLoaded; - } - - // Instanced outputs - for (auto& Iter : InstancedOutputs) - { - FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; - Identifier.bLoaded = InLoaded; - } -} - - -const bool -UHoudiniOutput::HasAnyProxy() const -{ - for (const auto& Pair : OutputObjects) - { - UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) - { - return true; - } - } - - return false; -} - -const bool -UHoudiniOutput::HasProxy(const FHoudiniOutputObjectIdentifier& InIdentifier) const -{ - const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - if (!FoundOutputObject) - return false; - - UObject* FoundProxy = FoundOutputObject->ProxyObject; - if (!FoundProxy || FoundProxy->IsPendingKill()) - return false; - - return true; -} - -const bool -UHoudiniOutput::HasAnyCurrentProxy() const -{ - for (const auto& Pair : OutputObjects) - { - UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) - { - if(Pair.Value.bProxyIsCurrent) - { - return true; - } - } - } - - return false; -} - -const bool -UHoudiniOutput::IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const -{ - if (!HasProxy(InIdentifier)) - return false; - - const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - if (!FoundOutputObject) - return false; - - return FoundOutputObject->bProxyIsCurrent; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniOutput.h" +#include "HoudiniAssetComponent.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniSplineComponent.h" + +#include "Components/SceneComponent.h" +#include "Components/MeshComponent.h" +#include "Components/SplineComponent.h" +#include "Misc/StringFormatArg.h" +#include "Engine/Engine.h" + +UHoudiniLandscapePtr::UHoudiniLandscapePtr(class FObjectInitializer const& Initializer) +{ + // bIsWorldCompositionLandscape = false; + // BakeType = EHoudiniLandscapeOutputBakeType::Detachment; +}; + +uint32 +GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier) +{ + return HoudiniOutputObjectIdentifier.GetTypeHash(); +} + +void +FHoudiniInstancedOutput::SetVariationObjectAt(const int32& AtIndex, UObject* InObject) +{ + // Resize the array if needed + if (VariationObjects.Num() <= AtIndex) + VariationObjects.SetNum(AtIndex + 1); + + if (VariationTransformOffsets.Num() <= AtIndex) + VariationTransformOffsets.SetNum(AtIndex + 1); + + UObject* CurrentObject = VariationObjects[AtIndex].LoadSynchronous(); + if (CurrentObject == InObject) + return; + + VariationObjects[AtIndex] = InObject; +} + +bool +FHoudiniInstancedOutput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; + if (!Transform) + return false; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + if (Position[XYZIndex] == Value) + return false; + Position[XYZIndex] = Value; + Transform->SetLocation(Position); + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + if (Rotator.Roll == Value) + return false; + Rotator.Roll = Value; + break; + } + + case 1: + { + if (Rotator.Pitch == Value) + return false; + Rotator.Pitch = Value; + break; + } + + case 2: + { + if (Rotator.Yaw == Value) + return false; + Rotator.Yaw = Value; + break; + } + } + Transform->SetRotation(Rotator.Quaternion()); + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + if (Scale[XYZIndex] == Value) + return false; + + Scale[XYZIndex] = Value; + Transform->SetScale3D(Scale); + } + + MarkChanged(true); + + return true; +} + +float +FHoudiniInstancedOutput::GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; + if (!Transform) + return 0.0f; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + return Position[XYZIndex]; + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + return Rotator.Roll; + } + + case 1: + { + return Rotator.Pitch; + } + + case 2: + { + return Rotator.Yaw; + } + } + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + return Scale[XYZIndex]; + } + + return 0.0f; +} + +// ---------------------------------------------------- +// FHoudiniOutputObjectIdentifier +// ---------------------------------------------------- + +FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier() +{ + ObjectId = -1; + GeoId = -1; + PartId = -1; + SplitIdentifier = FString(); + PartName = FString(); +} + +FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier( + const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier) +{ + ObjectId = InObjectId; + GeoId = InGeoId; + PartId = InPartId; + SplitIdentifier = InSplitIdentifier; +} + +uint32 +FHoudiniOutputObjectIdentifier::GetTypeHash() const +{ + int32 HashBuffer[3] = { ObjectId, GeoId, PartId }; + int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitIdentifier, Hash); +} + +bool +FHoudiniOutputObjectIdentifier::operator==(const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier) const +{ + // Object/Geo/Part IDs must match + bool bMatchingIds = true; + if (ObjectId != InOutputObjectIdentifier.ObjectId + || GeoId != InOutputObjectIdentifier.GeoId + || PartId != InOutputObjectIdentifier.PartId) + bMatchingIds = false; + + if ((bLoaded && !InOutputObjectIdentifier.bLoaded) + || (!bLoaded && InOutputObjectIdentifier.bLoaded)) + { + // If one of the two identifier is loaded, + // we can simply compare the part names + if (PartName.Equals(InOutputObjectIdentifier.PartName) + && SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) + return true; + } + + if (!bMatchingIds) + { + return false; + } + + // If split ID and name match, we're equal... + if (SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) + return true; + + // ... if not we're different + return false; +} + +bool +FHoudiniOutputObjectIdentifier::Matches(const FHoudiniGeoPartObject& InHGPO) const +{ + // Object/Geo/Part IDs must match + bool bMatchingIds = true; + if (ObjectId != InHGPO.ObjectId + || GeoId != InHGPO.GeoId + || PartId != InHGPO.PartId) + bMatchingIds = false; + + if ((bLoaded && !InHGPO.bLoaded) || (!bLoaded && InHGPO.bLoaded)) + { + // If either the HGPO or the Identifer is nmarked as loaded, + // we can simply compare the part names + if (PartName.Equals(InHGPO.PartName)) + return true; + } + + if (!bMatchingIds) + { + return false; + } + + // If the HGPO has our split identifier + //if (InHGPO.SplitGroups.Contains(SplitIdentifier)) + // return true; + + // + return true; +} + + +// ---------------------------------------------------- +// FHoudiniBakedOutputObjectIdentifier +// ---------------------------------------------------- + + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier() +{ + PartId = -1; + SplitIdentifier = FString(); +} + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier( + const int32& InPartId, const FString& InSplitIdentifier) +{ + PartId = InPartId; + SplitIdentifier = InSplitIdentifier; +} + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + PartId = InIdentifier.PartId; + SplitIdentifier = InIdentifier.SplitIdentifier; +} + +uint32 +FHoudiniBakedOutputObjectIdentifier::GetTypeHash() const +{ + const int32 HashBuffer = PartId; + const int32 Hash = FCrc::MemCrc32((void *)&HashBuffer, sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitIdentifier, Hash); +} + +uint32 +GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) +{ + return InIdentifier.GetTypeHash(); +} + +bool +FHoudiniBakedOutputObjectIdentifier::operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const +{ + return (InIdentifier.PartId == PartId && InIdentifier.SplitIdentifier.Equals(SplitIdentifier)); +} + + +// ---------------------------------------------------- +// FHoudiniBakedOutputObject +// ---------------------------------------------------- + + +FHoudiniBakedOutputObject::FHoudiniBakedOutputObject() + : Actor() + , ActorBakeName(NAME_None) + , BakedObject() + , BakedComponent() +{ +} + + +FHoudiniBakedOutputObject::FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject, UObject* InBakedComponent) + : Actor(FSoftObjectPath(InActor).ToString()) + , ActorBakeName(InActorBakeName) + , BakedObject(FSoftObjectPath(InBakeObject).ToString()) + , BakedComponent(FSoftObjectPath(InBakedComponent).ToString()) +{ +} + + +AActor* +FHoudiniBakedOutputObject::GetActorIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ActorPath(Actor); + + if (!ActorPath.IsValid()) + return nullptr; + + UObject* Object = ActorPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ActorPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + +UObject* +FHoudiniBakedOutputObject::GetBakedObjectIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ObjectPath(BakedObject); + + if (!ObjectPath.IsValid()) + return nullptr; + + UObject* Object = ObjectPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ObjectPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Object; +} + +UObject* +FHoudiniBakedOutputObject::GetBakedComponentIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ComponentPath(BakedComponent); + + if (!ComponentPath.IsValid()) + return nullptr; + + UObject* Object = ComponentPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ComponentPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Object; +} + +UBlueprint* +FHoudiniBakedOutputObject::GetBlueprintIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath BlueprintPath(Blueprint); + + if (!BlueprintPath.IsValid()) + return nullptr; + + UObject* Object = BlueprintPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = BlueprintPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + + +UHoudiniOutput::UHoudiniOutput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Type(EHoudiniOutputType::Invalid) + , StaleCount(0) + , bLandscapeWorldComposition(false) + , bIsEditableNode(false) + , bHasEditableNodeBuilt(false) + , bCanDeleteHoudiniNodes(true) +{ + +} + +UHoudiniOutput::~UHoudiniOutput() +{ + Type = EHoudiniOutputType::Invalid; + StaleCount = 0; + bIsUpdating = false; + + HoudiniGeoPartObjects.Empty(); + OutputObjects.Empty(); + InstancedOutputs.Empty(); + AssignementMaterials.Empty(); + ReplacementMaterials.Empty(); +} + +void +UHoudiniOutput::BeginDestroy() +{ + Super::BeginDestroy(); +} + +FBox +UHoudiniOutput::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + + switch (GetType()) + { + case EHoudiniOutputType::Mesh: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + + UMeshComponent* MeshComp = nullptr; + if (CurObj.bProxyIsCurrent) + { + MeshComp = Cast(CurObj.ProxyComponent); + } + else + { + MeshComp = Cast(CurObj.OutputComponent); + } + + if (!MeshComp || MeshComp->IsPendingKill()) + continue; + + BoxBounds += MeshComp->Bounds.GetBox(); + } + } + break; + + case EHoudiniOutputType::Landscape: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + UHoudiniLandscapePtr* CurLandscapeObj = Cast(CurObj.OutputObject); + if (!CurLandscapeObj || CurLandscapeObj->IsPendingKill()) + continue; + + ALandscapeProxy* Landscape = Cast(CurLandscapeObj->GetRawPtr()); + if (!Landscape || Landscape->IsPendingKill()) + continue; + + FVector Origin, Extent; + Landscape->GetActorBounds(false, Origin, Extent); + + FBox LandscapeBounds = FBox::BuildAABB(Origin, Extent); + BoxBounds += LandscapeBounds; + } + } + break; + + case EHoudiniOutputType::Instancer: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + USceneComponent* InstancedComp = Cast(CurObj.OutputObject); + if (!InstancedComp || InstancedComp->IsPendingKill()) + continue; + + BoxBounds += InstancedComp->Bounds.GetBox(); + } + } + break; + + case EHoudiniOutputType::Curve: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + UHoudiniSplineComponent* CurHoudiniSplineComp = Cast(CurObj.OutputComponent); + if (!CurHoudiniSplineComp || CurHoudiniSplineComp->IsPendingKill()) + continue; + + FBox CurCurveBound(ForceInitToZero); + for (auto & Trans : CurHoudiniSplineComp->CurvePoints) + { + CurCurveBound += Trans.GetLocation(); + } + + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !OuterHAC->IsPendingKill()) + BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); + } + + } + break; + + case EHoudiniOutputType::Skeletal: + case EHoudiniOutputType::Invalid: + break; + + default: + break; + } + + return BoxBounds; +} + +void +UHoudiniOutput::Clear() +{ + StaleCount = 0; + + HoudiniGeoPartObjects.Empty(); + + for (auto& CurrentOutputObject : OutputObjects) + { + // Clear the output component + USceneComponent* SceneComp = Cast(CurrentOutputObject.Value.OutputComponent); + if (SceneComp && !SceneComp->IsPendingKill()) + { + SceneComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComp->UnregisterComponent(); + SceneComp->DestroyComponent(); + } + + // Also destroy proxy components + USceneComponent* ProxyComp = Cast(CurrentOutputObject.Value.ProxyComponent); + if (ProxyComp && !ProxyComp->IsPendingKill()) + { + ProxyComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + ProxyComp->UnregisterComponent(); + ProxyComp->DestroyComponent(); + } + + if (Type == EHoudiniOutputType::Landscape && !bLandscapeWorldComposition && !IsGarbageCollecting()) + { + // NOTE: We cannot resolve soft pointers during garbage collection. Any Get() or IsValid() call + // will result in a StaticFindObject() call which will raise an exception during GC. + UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentOutputObject.Value.OutputObject); + ALandscapeProxy* LandscapeProxy = LandscapePtr ? LandscapePtr->GetRawPtr() : nullptr; + if (LandscapeProxy && !LandscapeProxy->IsPendingKill()) + { + LandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + LandscapeProxy->ConditionalBeginDestroy(); + LandscapeProxy->Destroy(); + } + } + } + + OutputObjects.Empty(); + InstancedOutputs.Empty(); + AssignementMaterials.Empty(); + ReplacementMaterials.Empty(); + + Type = EHoudiniOutputType::Invalid; +} + +bool +UHoudiniOutput::ShouldDeferClear() const +{ + if (Type == EHoudiniOutputType::Landscape) + return true; + + return false; +} + +const bool +UHoudiniOutput::HasGeoChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasGeoChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasTransformChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasTransformChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasMaterialsChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasMaterialsChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const +{ + return HoudiniGeoPartObjects.Find(InHGPO) != INDEX_NONE; +} + +const bool +UHoudiniOutput::HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const +{ + if (InHGPO.Type != EHoudiniPartType::Volume) + return false; + + if (InHGPO.VolumeName.IsEmpty()) + return false; + + for (auto& currentHGPO : HoudiniGeoPartObjects) + { + // Asset/Object/Geo IDs should match + if (currentHGPO.AssetId != InHGPO.AssetId + || currentHGPO.ObjectId != InHGPO.ObjectId + || currentHGPO.GeoId != InHGPO.GeoId) + { + continue; + } + + // Both HGPO type should be volumes + if (currentHGPO.Type != EHoudiniPartType::Volume) + { + continue; + } + + // Volume tile index should match + if (currentHGPO.VolumeTileIndex != InHGPO.VolumeTileIndex) + { + continue; + } + + // We've specified if we want the name to match/to be different: + // when looking in previous outputs, we want the name to match + // when looking in newly created outputs, we want to be sure the names are different + bool bNameMatch = InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase); + if (bNameMatch != bVolumeNameShouldMatch) + continue; + + return true; + } + + return false; +} + +void +UHoudiniOutput::MarkAllHGPOsAsStale(const bool& bInStale) +{ + // Since objects can only be added to this array, + // Simply keep track of the current number of HoudiniGeoPartObject + StaleCount = bInStale ? HoudiniGeoPartObjects.Num() : 0; +} + +void +UHoudiniOutput::DeleteAllStaleHGPOs() +{ + // Simply delete the first "StaleCount" objects and reset the stale marker + HoudiniGeoPartObjects.RemoveAt(0, StaleCount); + StaleCount = 0; +} + +void +UHoudiniOutput::AddNewHGPO(const FHoudiniGeoPartObject& InHGPO) +{ + HoudiniGeoPartObjects.Add(InHGPO); +} + +void +UHoudiniOutput::UpdateOutputType() +{ + int32 MeshCount = 0; + int32 CurveCount = 0; + int32 VolumeCount = 0; + int32 InstancerCount = 0; + for (auto& HGPO : HoudiniGeoPartObjects) + { + switch (HGPO.Type) + { + case EHoudiniPartType::Mesh: + MeshCount++; + break; + case EHoudiniPartType::Curve: + CurveCount++; + break; + case EHoudiniPartType::Volume: + VolumeCount++; + break; + case EHoudiniPartType::Instancer: + InstancerCount++; + break; + default: + case EHoudiniPartType::Invalid: + break; + } + } + + if (VolumeCount > 0) + { + // If we have a volume, we're a landscape + Type = EHoudiniOutputType::Landscape; + } + else if (InstancerCount > 0) + { + // if we have at least an instancer, we're one + Type = EHoudiniOutputType::Instancer; + } + else if (MeshCount > 0) + { + Type = EHoudiniOutputType::Mesh; + } + else if (CurveCount > 0) + { + Type = EHoudiniOutputType::Curve; + } + else + { + // No valid HGPO detected... + Type = EHoudiniOutputType::Invalid; + } +} + +UHoudiniOutput* +UHoudiniOutput::DuplicateAndCopyProperties(UObject* DestOuter, FName NewName) +{ + UHoudiniOutput* NewOutput = Cast(StaticDuplicateObject(this, DestOuter, NewName)); + + NewOutput->CopyPropertiesFrom(this, false); + + return NewOutput; +} + +void +UHoudiniOutput::CopyPropertiesFrom(UHoudiniOutput* InInput, bool bCopyAllProperties) +{ + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + // Stash all the data that we want to preserve, and re-apply after property copy took place + // (similar to Get/Apply component instance data). This is typically only needed + // for certain properties that require cleanup when being replaced / removed. + TMap PrevOutputObjects = OutputObjects; + TMap PrevInstancedOutputs = InstancedOutputs; + + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + + // Restore the desired properties. + OutputObjects = PrevOutputObjects; + InstancedOutputs = PrevInstancedOutputs; + } + + // Copy any additional DuplicateTransient properties. + bHasEditableNodeBuilt = InInput->bHasEditableNodeBuilt; +} + +void +UHoudiniOutput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; +} + +FString +UHoudiniOutput::OutputTypeToString(const EHoudiniOutputType& InOutputType) +{ + FString OutputTypeStr; + switch (InOutputType) + { + case EHoudiniOutputType::Mesh: + OutputTypeStr = TEXT("Mesh"); + break; + case EHoudiniOutputType::Instancer: + OutputTypeStr = TEXT("Instancer"); + break; + case EHoudiniOutputType::Landscape: + OutputTypeStr = TEXT("Landscape"); + break; + case EHoudiniOutputType::Curve: + OutputTypeStr = TEXT("Curve"); + break; + case EHoudiniOutputType::Skeletal: + OutputTypeStr = TEXT("Skeletal"); + break; + + default: + case EHoudiniOutputType::Invalid: + OutputTypeStr = TEXT("Invalid"); + break; + } + + return OutputTypeStr; +} + +void +UHoudiniOutput::MarkAsLoaded(const bool& InLoaded) +{ + // Mark all HGPO as loaded + for (auto& HGPO : HoudiniGeoPartObjects) + { + HGPO.bLoaded = InLoaded; + } + + // Mark all output object's identifier as loaded + for (auto& Iter : OutputObjects) + { + FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; + Identifier.bLoaded = InLoaded; + } + + // Instanced outputs + for (auto& Iter : InstancedOutputs) + { + FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; + Identifier.bLoaded = InLoaded; + } +} + + +const bool +UHoudiniOutput::HasAnyProxy() const +{ + for (const auto& Pair : OutputObjects) + { + UObject* FoundProxy = Pair.Value.ProxyObject; + if (FoundProxy && !FoundProxy->IsPendingKill()) + { + return true; + } + } + + return false; +} + +const bool +UHoudiniOutput::HasProxy(const FHoudiniOutputObjectIdentifier& InIdentifier) const +{ + const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + if (!FoundOutputObject) + return false; + + UObject* FoundProxy = FoundOutputObject->ProxyObject; + if (!FoundProxy || FoundProxy->IsPendingKill()) + return false; + + return true; +} + +const bool +UHoudiniOutput::HasAnyCurrentProxy() const +{ + for (const auto& Pair : OutputObjects) + { + UObject* FoundProxy = Pair.Value.ProxyObject; + if (FoundProxy && !FoundProxy->IsPendingKill()) + { + if(Pair.Value.bProxyIsCurrent) + { + return true; + } + } + } + + return false; +} + +const bool +UHoudiniOutput::IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const +{ + if (!HasProxy(InIdentifier)) + return false; + + const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + if (!FoundOutputObject) + return false; + + return FoundOutputObject->bProxyIsCurrent; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h index dcff22373..217ef7255 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h @@ -1,598 +1,627 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "HoudiniGeoPartObject.h" -#include "LandscapeProxy.h" -#include "Misc/StringFormatArg.h" -#include "UObject/SoftObjectPtr.h" - -#include "HoudiniOutput.generated.h" - -class UMaterialInterface; - -UENUM() -enum class EHoudiniOutputType : uint8 -{ - Invalid, - - Mesh, - Instancer, - Landscape, - Curve, - Skeletal -}; - -UENUM() -enum class EHoudiniCurveOutputType : uint8 -{ - UnrealSpline, - HoudiniSpline, -}; - -UENUM() -enum class EHoudiniLandscapeOutputBakeType : uint8 -{ - Detachment, - BakeToImage, - BakeToWorld, - InValid, -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniCurveOutputProperties -{ - GENERATED_USTRUCT_BODY() - - // Curve output properties - UPROPERTY() - EHoudiniCurveOutputType CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; - - UPROPERTY() - int32 NumPoints = -1; - - UPROPERTY() - bool bClosed = false; - - UPROPERTY() - EHoudiniCurveType CurveType = EHoudiniCurveType::Invalid; - - UPROPERTY() - EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::Invalid; -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniLandscapePtr : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - FORCEINLINE - void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; - - FORCEINLINE - TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; - - // Calling Get() during GC will raise an exception because Get calls StaticFindObject. - FORCEINLINE - ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; - - FORCEINLINE - FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; - - FORCEINLINE - void SetLandscapeOutputBakeType(const EHoudiniLandscapeOutputBakeType & InBakeType) { BakeType = InBakeType; }; - - FORCEINLINE - EHoudiniLandscapeOutputBakeType GetLandscapeOutputBakeType() const { return BakeType; }; - - UPROPERTY() - TSoftObjectPtr LandscapeSoftPtr; - - UPROPERTY() - EHoudiniLandscapeOutputBakeType BakeType; -}; - - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniOutputObjectIdentifier -{ - GENERATED_USTRUCT_BODY() - -public: - // Constructors - FHoudiniOutputObjectIdentifier(); - FHoudiniOutputObjectIdentifier(const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier); - - // Return hash value for this object, used when using this object as a key inside hashing containers. - uint32 GetTypeHash() const; - - // Comparison operator, used by hashing containers. - bool operator==(const FHoudiniOutputObjectIdentifier& HoudiniGeoPartObject) const; - - bool Matches(const FHoudiniGeoPartObject& HoudiniGeoPartObject) const; - -public: - - // NodeId of corresponding Houdini Object. - UPROPERTY() - int32 ObjectId = -1; - - // NodeId of corresponding Houdini Geo. - UPROPERTY() - int32 GeoId = -1; - - // PartId - UPROPERTY() - int32 PartId = -1; - - // String identifier for the split that created this - UPROPERTY() - FString SplitIdentifier = FString(); - - // Name of the part used to generate the output - UPROPERTY() - FString PartName = FString(); - - // First valid primitive index for this output - // (used to read generic attributes) - UPROPERTY() - int32 PrimitiveIndex = -1; - - // First valid point index for this output - // (used to read generic attributes) - UPROPERTY() - int32 PointIndex = -1; - - bool bLoaded = false; -}; - -/** Function used by hashing containers to create a unique hash for this type of object. **/ -HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier); - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObjectIdentifier -{ - GENERATED_USTRUCT_BODY() - -public: - // Constructors - FHoudiniBakedOutputObjectIdentifier(); - FHoudiniBakedOutputObjectIdentifier(const int32& InPartId, const FString& InSplitIdentifier); - FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); - - // Return hash value for this object, used when using this object as a key inside hashing containers. - uint32 GetTypeHash() const; - - // Comparison operator, used by hashing containers. - bool operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const; - -public: - - // PartId - UPROPERTY() - int32 PartId = -1; - - // String identifier for the split that created this - UPROPERTY() - FString SplitIdentifier = FString(); -}; - -/** Function used by hashing containers to create a unique hash for this type of object. **/ -HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier); - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput -{ - GENERATED_USTRUCT_BODY() - -public: - - void MarkChanged(const bool& InChanged) { bChanged = InChanged; }; - - void SetVariationObjectAt(const int32& AtIndex, UObject* InObject); - - bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - - float GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - -#if WITH_EDITOR - void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; - bool IsUnformScaleLocked() const { return bUniformScaleLocked; }; -#endif - -public: - - // Original object used by the instancer. - UPROPERTY() - TSoftObjectPtr OriginalObject = nullptr; - - UPROPERTY() - int32 OriginalObjectIndex = -1; - - // Original HoudiniGeoPartObject used by the instancer - //UPROPERTY() - //FHoudiniGeoPartObject OriginalHGPO; - - // Original Instance transforms - UPROPERTY() - TArray OriginalTransforms; - - // Variation objects currently used for instancing - UPROPERTY() - TArray> VariationObjects; - - // Transform offsets, one for each variation. - UPROPERTY() - TArray VariationTransformOffsets; - - // Index of the variation used for each transform - UPROPERTY() - TArray TransformVariationIndices; - - // Indicates this instanced output's component should be recreated - UPROPERTY() - bool bChanged = false; - - // Indicates this instanced output is stale and should be removed - UPROPERTY() - bool bStale = false; - - // Indicates if change the scale of Transfrom Offset of this object uniformly -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bUniformScaleLocked = false; -#endif - // TODO - // Color overrides?? -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject -{ - GENERATED_USTRUCT_BODY() - - public: - FHoudiniBakedOutputObject(); - - FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject=nullptr, UObject* InBakedComponent=nullptr); - - // Returns Actor if valid, otherwise nullptr - AActor* GetActorIfValid(bool bInTryLoad=true) const; - - // Returns BakedObject if valid, otherwise nullptr - UObject* GetBakedObjectIfValid(bool bInTryLoad=true) const; - - // Returns BakedComponent if valid, otherwise nullptr - UObject* GetBakedComponentIfValid(bool bInTryLoad=true) const; - - // Returns Blueprint if valid, otherwise nullptr - UBlueprint* GetBlueprintIfValid(bool bInTryLoad=true) const; - - // The actor that the baked output was associated with - UPROPERTY() - FString Actor; - - // The blueprint that baked output was associated with, if any - UPROPERTY() - FString Blueprint; - - // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. - UPROPERTY() - FName ActorBakeName = NAME_None; - - // The baked output asset - UPROPERTY() - FString BakedObject; - - // The baked output component - UPROPERTY() - FString BakedComponent; - - // In the case of instance actor component baking, this is the array of instanced actors - UPROPERTY() - TArray InstancedActors; - - // In the case of mesh split instancer baking: this is the array of instance components - UPROPERTY() - TArray InstancedComponents; -}; - -// Container to hold the map of baked objects. There should be one of -// these for each UHoudiniOutput. We manage this separately from UHoudiniOutput so -// that the "previous/last" bake objects can survive output reconstruction or PDG -// dirty/dirty all operations. -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutput -{ - GENERATED_USTRUCT_BODY() - - public: - UPROPERTY() - TMap BakedOutputObjects; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject -{ - GENERATED_USTRUCT_BODY() - - public: - - // The main output object - UPROPERTY() - UObject* OutputObject = nullptr; - - // The main output component - UPROPERTY() - UObject* OutputComponent = nullptr; - - // Proxy object - UPROPERTY() - UObject* ProxyObject = nullptr; - - // Proxy Component - UPROPERTY() - UObject* ProxyComponent = nullptr; - - // Mesh output properties - // If this is true the proxy mesh is "current", - // in other words, it is newer than the UStaticMesh - UPROPERTY() - bool bProxyIsCurrent = false; - - // Implicit output objects shouldn't be created as actors / components in the scene. - UPROPERTY() - bool bIsImplicit = false; - - // Bake Name override for this output object - UPROPERTY() - FString BakeName; - - UPROPERTY() - FHoudiniCurveOutputProperties CurveOutputProperty; - - - // NOTE: The idea behind CachedAttributes and CachedTokens is to - // collect attributes (such as unreal_level_path and unreal_output_name) - // and context-specific tokens (hda name, hda actor name, geo and part ids, tile_id, etc) - // and cache them directly on the output object. When the object gets baked, - // certain tokens can be updated specifically for the bake pass afterwhich the - // the string / path attributes can be resolved with the updated tokens. - // - // A more concrete example: - // unreal_output_name = "{hda_actor_name}_PurplePlants_{geo_id}_{part_id}" - // unreal_level_path = "{out}/{hda_name}/{guid}/PurplePlants/{geo_id}/{part_id}" - // - // All of the aforementions tokens and attributes would be cached on the output object - // when it is being cooked so that the same values are available at bake time. During - // a bake some tokens may be updated, such as `{out}` to change where assets get serialized. - - UPROPERTY() - TMap CachedAttributes; - - // Cache any tokens here that is needed for string resolving - // at bake time. - UPROPERTY() - TMap CachedTokens; -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject -{ - GENERATED_UCLASS_BODY() - - // Declare translators as friend so they can easily directly modify - // and access our HGPO and Output objects - friend struct FHoudiniMeshTranslator; - friend struct FHoudiniInstanceTranslator; - - virtual ~UHoudiniOutput(); - -public: - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - const EHoudiniOutputType& GetType() const { return Type; }; - - const TArray& GetHoudiniGeoPartObjects() const { return HoudiniGeoPartObjects; }; - - // Returns true if we have a HGPO that matches - const bool HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const; - - // Returns true if the HGPO is fromn the same HF as us - const bool HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const; - - // Returns the output objects and their corresponding identifiers - TMap& GetOutputObjects() { return OutputObjects; }; - - // Returns the output objects and their corresponding identifiers - const TMap& GetOutputObjects() const { return OutputObjects; }; - - // Returns this output's assignement material map - TMap& GetAssignementMaterials() { return AssignementMaterials; }; - - // Returns this output's replacement material map - TMap& GetReplacementMaterials() { return ReplacementMaterials; }; - - // Returns the instanced outputs maps - TMap& GetInstancedOutputs() { return InstancedOutputs; }; - - const bool HasGeoChanged() const; - const bool HasTransformChanged() const; - const bool HasMaterialsChanged() const; - - // Returns true if there are any proxy objects in output (current or not) - const bool HasAnyProxy() const; - // Returns true if the specified identifier has a proxy object (current or not) - const bool HasProxy(const FHoudiniOutputObjectIdentifier &InIdentifier) const; - // Returns true if there are any current (most up to date and visible) proxy in the output - const bool HasAnyCurrentProxy() const; - // Returns true if the specified identifier's proxy is "current" (in other words, newer than - // the non-proxy and the proxy should thus be shown instead. - const bool IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const; - - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - void UpdateOutputType(); - - // Adds a new HoudiniGeoPartObject to our array - void AddNewHGPO(const FHoudiniGeoPartObject& InHGPO); - - // Mark all the current HGPO as stale (from a previous cook) - // So we can delte them all by calling DeleteAllStaleHGPOs after. - void MarkAllHGPOsAsStale(const bool& InStale); - - // Delete all the HGPO that were marked as stale - void DeleteAllStaleHGPOs(); - - void SetOutputObjects(const TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; - - void SetInstancedOutputs(const TMap& InInstancedOuput) { InstancedOutputs = InInstancedOuput; }; - - // Marks all HGPO and OutputIdentifier as loaded - void MarkAsLoaded(const bool& InLoaded); - - FORCEINLINE - const bool IsEditableNode() { return bIsEditableNode; }; - - FORCEINLINE - void SetIsEditableNode(bool IsEditable) { bIsEditableNode = IsEditable; } - - FORCEINLINE - const bool HasEditableNodeBuilt() { return bHasEditableNodeBuilt; }; - - FORCEINLINE - void SetHasEditableNodeBuilt(bool HasBuilt) { bHasEditableNodeBuilt = HasBuilt; } - - FORCEINLINE - void SetIsUpdating(bool bInIsUpdating) { bIsUpdating = bInIsUpdating; }; - - FORCEINLINE - bool IsUpdating() const { return bIsUpdating; }; - - FORCEINLINE - void SetLandscapeWorldComposition(const bool bInLandscapeWorldComposition) { bLandscapeWorldComposition = bInLandscapeWorldComposition; }; - - FORCEINLINE - bool IsLandscapeWorldComposition () const { return bLandscapeWorldComposition; }; - - FORCEINLINE - TArray & GetHoudiniCreatedSocketActors() { return HoudiniCreatedSocketActors; }; - - FORCEINLINE - TArray & GetHoudiniAttachedSocketActors() { return HoudiniAttachedSocketActors; } - - // Duplicate this object and copy its state to the resulting object. - // This is typically used to transfer state between between template and instance components. - UHoudiniOutput* DuplicateAndCopyProperties(UObject* DestOuter, FName NewName); - - // Copy properties but preserve output objects - virtual void CopyPropertiesFrom(UHoudiniOutput* InOutput, bool bCopyAllProperties); - - // Set whether this object can delete Houdini nodes. - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } - - //------------------------------------------------------------------------------------------------ - // Helpers - //------------------------------------------------------------------------------------------------ - static FString OutputTypeToString(const EHoudiniOutputType& InOutputType); - - FBox GetBounds() const; - - void Clear(); - - bool ShouldDeferClear() const; - -protected: - - virtual void BeginDestroy() override; - -protected: - - // Indicates the type of output we're dealing with - UPROPERTY() - EHoudiniOutputType Type; - - // The output's corresponding HGPO - UPROPERTY() - TArray HoudiniGeoPartObjects; - - // - UPROPERTY(DuplicateTransient) - TMap OutputObjects; - - // Instanced outputs - // Stores the instance variations objects (replacement), transform offsets - UPROPERTY() - TMap InstancedOutputs; - - // The material assignments for this output - UPROPERTY() - TMap AssignementMaterials; - - UPROPERTY() - TMap ReplacementMaterials; - - // Indicates the number of stale HGPO - int32 StaleCount; - - UPROPERTY() - bool bLandscapeWorldComposition; - - // stores the created actors for sockets with actor references. - // - UPROPERTY() - TArray HoudiniCreatedSocketActors; - - UPROPERTY() - TArray HoudiniAttachedSocketActors; - -private: - // Use HoudiniOutput to represent an editable curve. - // This flag tells whether this output is an editable curve. - UPROPERTY() - bool bIsEditableNode; - - // An editable node is only built once. This flag indicates whether this node has been built. - UPROPERTY(DuplicateTransient) - bool bHasEditableNodeBuilt; - - // The IsUpdating flag is set to true when this out exists and is being updated. - UPROPERTY() - bool bIsUpdating; - - UPROPERTY() - bool bCanDeleteHoudiniNodes; -}; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "HoudiniGeoPartObject.h" +#include "LandscapeProxy.h" +#include "Misc/StringFormatArg.h" +#include "UObject/SoftObjectPtr.h" + +#include "HoudiniOutput.generated.h" + +class UMaterialInterface; + +UENUM() +enum class EHoudiniOutputType : uint8 +{ + Invalid, + + Mesh, + Instancer, + Landscape, + Curve, + Skeletal +}; + +UENUM() +enum class EHoudiniCurveOutputType : uint8 +{ + UnrealSpline, + HoudiniSpline, +}; + +UENUM() +enum class EHoudiniLandscapeOutputBakeType : uint8 +{ + Detachment, + BakeToImage, + BakeToWorld, + InValid, +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniCurveOutputProperties +{ + GENERATED_USTRUCT_BODY() + + // Curve output properties + UPROPERTY() + EHoudiniCurveOutputType CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; + + UPROPERTY() + int32 NumPoints = -1; + + UPROPERTY() + bool bClosed = false; + + UPROPERTY() + EHoudiniCurveType CurveType = EHoudiniCurveType::Invalid; + + UPROPERTY() + EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::Invalid; +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniLandscapePtr : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + FORCEINLINE + void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; + + FORCEINLINE + TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; + + // Calling Get() during GC will raise an exception because Get calls StaticFindObject. + FORCEINLINE + ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; + + FORCEINLINE + FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; + + FORCEINLINE + void SetLandscapeOutputBakeType(const EHoudiniLandscapeOutputBakeType & InBakeType) { BakeType = InBakeType; }; + + FORCEINLINE + EHoudiniLandscapeOutputBakeType GetLandscapeOutputBakeType() const { return BakeType; }; + + UPROPERTY() + TSoftObjectPtr LandscapeSoftPtr; + + UPROPERTY() + EHoudiniLandscapeOutputBakeType BakeType; +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniLandscapeEditLayer : public UObject +{ + GENERATED_BODY() + +public: + FORCEINLINE + void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; + + FORCEINLINE + TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; + + // Calling Get() during GC will raise an exception because Get calls StaticFindObject. + FORCEINLINE + ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; + + FORCEINLINE + FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; + + UPROPERTY() + TSoftObjectPtr LandscapeSoftPtr; + + UPROPERTY() + FString LayerName; +}; + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniOutputObjectIdentifier +{ + GENERATED_USTRUCT_BODY() + +public: + // Constructors + FHoudiniOutputObjectIdentifier(); + FHoudiniOutputObjectIdentifier(const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier); + + // Return hash value for this object, used when using this object as a key inside hashing containers. + uint32 GetTypeHash() const; + + // Comparison operator, used by hashing containers. + bool operator==(const FHoudiniOutputObjectIdentifier& HoudiniGeoPartObject) const; + + bool Matches(const FHoudiniGeoPartObject& HoudiniGeoPartObject) const; + +public: + + // NodeId of corresponding Houdini Object. + UPROPERTY() + int32 ObjectId = -1; + + // NodeId of corresponding Houdini Geo. + UPROPERTY() + int32 GeoId = -1; + + // PartId + UPROPERTY() + int32 PartId = -1; + + // String identifier for the split that created this + UPROPERTY() + FString SplitIdentifier = FString(); + + // Name of the part used to generate the output + UPROPERTY() + FString PartName = FString(); + + // First valid primitive index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PrimitiveIndex = -1; + + // First valid point index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PointIndex = -1; + + bool bLoaded = false; +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier); + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObjectIdentifier +{ + GENERATED_USTRUCT_BODY() + +public: + // Constructors + FHoudiniBakedOutputObjectIdentifier(); + FHoudiniBakedOutputObjectIdentifier(const int32& InPartId, const FString& InSplitIdentifier); + FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); + + // Return hash value for this object, used when using this object as a key inside hashing containers. + uint32 GetTypeHash() const; + + // Comparison operator, used by hashing containers. + bool operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const; + +public: + + // PartId + UPROPERTY() + int32 PartId = -1; + + // String identifier for the split that created this + UPROPERTY() + FString SplitIdentifier = FString(); +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier); + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput +{ + GENERATED_USTRUCT_BODY() + +public: + + void MarkChanged(const bool& InChanged) { bChanged = InChanged; }; + + void SetVariationObjectAt(const int32& AtIndex, UObject* InObject); + + bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + + float GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + +#if WITH_EDITOR + void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; + bool IsUnformScaleLocked() const { return bUniformScaleLocked; }; +#endif + +public: + + // Original object used by the instancer. + UPROPERTY() + TSoftObjectPtr OriginalObject = nullptr; + + UPROPERTY() + int32 OriginalObjectIndex = -1; + + // Original HoudiniGeoPartObject used by the instancer + //UPROPERTY() + //FHoudiniGeoPartObject OriginalHGPO; + + // Original Instance transforms + UPROPERTY() + TArray OriginalTransforms; + + // Variation objects currently used for instancing + UPROPERTY() + TArray> VariationObjects; + + // Transform offsets, one for each variation. + UPROPERTY() + TArray VariationTransformOffsets; + + // Index of the variation used for each transform + UPROPERTY() + TArray TransformVariationIndices; + + // Indicates this instanced output's component should be recreated + UPROPERTY() + bool bChanged = false; + + // Indicates this instanced output is stale and should be removed + UPROPERTY() + bool bStale = false; + + // Indicates if change the scale of Transfrom Offset of this object uniformly +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bUniformScaleLocked = false; +#endif + // TODO + // Color overrides?? +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject +{ + GENERATED_USTRUCT_BODY() + + public: + FHoudiniBakedOutputObject(); + + FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject=nullptr, UObject* InBakedComponent=nullptr); + + // Returns Actor if valid, otherwise nullptr + AActor* GetActorIfValid(bool bInTryLoad=true) const; + + // Returns BakedObject if valid, otherwise nullptr + UObject* GetBakedObjectIfValid(bool bInTryLoad=true) const; + + // Returns BakedComponent if valid, otherwise nullptr + UObject* GetBakedComponentIfValid(bool bInTryLoad=true) const; + + // Returns Blueprint if valid, otherwise nullptr + UBlueprint* GetBlueprintIfValid(bool bInTryLoad=true) const; + + // The actor that the baked output was associated with + UPROPERTY() + FString Actor; + + // The blueprint that baked output was associated with, if any + UPROPERTY() + FString Blueprint; + + // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. + UPROPERTY() + FName ActorBakeName = NAME_None; + + // The baked output asset + UPROPERTY() + FString BakedObject; + + // The baked output component + UPROPERTY() + FString BakedComponent; + + // In the case of instance actor component baking, this is the array of instanced actors + UPROPERTY() + TArray InstancedActors; + + // In the case of mesh split instancer baking: this is the array of instance components + UPROPERTY() + TArray InstancedComponents; +}; + +// Container to hold the map of baked objects. There should be one of +// these for each UHoudiniOutput. We manage this separately from UHoudiniOutput so +// that the "previous/last" bake objects can survive output reconstruction or PDG +// dirty/dirty all operations. +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutput +{ + GENERATED_USTRUCT_BODY() + + public: + UPROPERTY() + TMap BakedOutputObjects; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject +{ + GENERATED_USTRUCT_BODY() + + public: + + // The main output object + UPROPERTY() + UObject* OutputObject = nullptr; + + // The main output component + UPROPERTY() + UObject* OutputComponent = nullptr; + + // Proxy object + UPROPERTY() + UObject* ProxyObject = nullptr; + + // Proxy Component + UPROPERTY() + UObject* ProxyComponent = nullptr; + + // Mesh output properties + // If this is true the proxy mesh is "current", + // in other words, it is newer than the UStaticMesh + UPROPERTY() + bool bProxyIsCurrent = false; + + // Implicit output objects shouldn't be created as actors / components in the scene. + UPROPERTY() + bool bIsImplicit = false; + + // Bake Name override for this output object + UPROPERTY() + FString BakeName; + + UPROPERTY() + FHoudiniCurveOutputProperties CurveOutputProperty; + + + // NOTE: The idea behind CachedAttributes and CachedTokens is to + // collect attributes (such as unreal_level_path and unreal_output_name) + // and context-specific tokens (hda name, hda actor name, geo and part ids, tile_id, etc) + // and cache them directly on the output object. When the object gets baked, + // certain tokens can be updated specifically for the bake pass afterwhich the + // the string / path attributes can be resolved with the updated tokens. + // + // A more concrete example: + // unreal_output_name = "{hda_actor_name}_PurplePlants_{geo_id}_{part_id}" + // unreal_level_path = "{out}/{hda_name}/{guid}/PurplePlants/{geo_id}/{part_id}" + // + // All of the aforementions tokens and attributes would be cached on the output object + // when it is being cooked so that the same values are available at bake time. During + // a bake some tokens may be updated, such as `{out}` to change where assets get serialized. + + UPROPERTY() + TMap CachedAttributes; + + // Cache any tokens here that is needed for string resolving + // at bake time. + UPROPERTY() + TMap CachedTokens; +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject +{ + GENERATED_UCLASS_BODY() + + // Declare translators as friend so they can easily directly modify + // and access our HGPO and Output objects + friend struct FHoudiniMeshTranslator; + friend struct FHoudiniInstanceTranslator; + friend struct FHoudiniOutputTranslator; + + virtual ~UHoudiniOutput(); + +public: + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + const EHoudiniOutputType& GetType() const { return Type; }; + + const TArray& GetHoudiniGeoPartObjects() const { return HoudiniGeoPartObjects; }; + + // Returns true if we have a HGPO that matches + const bool HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const; + + // Returns true if the HGPO is fromn the same HF as us + const bool HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const; + + // Returns the output objects and their corresponding identifiers + TMap& GetOutputObjects() { return OutputObjects; }; + + // Returns the output objects and their corresponding identifiers + const TMap& GetOutputObjects() const { return OutputObjects; }; + + // Returns this output's assignement material map + TMap& GetAssignementMaterials() { return AssignementMaterials; }; + + // Returns this output's replacement material map + TMap& GetReplacementMaterials() { return ReplacementMaterials; }; + + // Returns the instanced outputs maps + TMap& GetInstancedOutputs() { return InstancedOutputs; }; + + const bool HasGeoChanged() const; + const bool HasTransformChanged() const; + const bool HasMaterialsChanged() const; + + // Returns true if there are any proxy objects in output (current or not) + const bool HasAnyProxy() const; + // Returns true if the specified identifier has a proxy object (current or not) + const bool HasProxy(const FHoudiniOutputObjectIdentifier &InIdentifier) const; + // Returns true if there are any current (most up to date and visible) proxy in the output + const bool HasAnyCurrentProxy() const; + // Returns true if the specified identifier's proxy is "current" (in other words, newer than + // the non-proxy and the proxy should thus be shown instead. + const bool IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const; + + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + void UpdateOutputType(); + + // Adds a new HoudiniGeoPartObject to our array + void AddNewHGPO(const FHoudiniGeoPartObject& InHGPO); + + // Mark all the current HGPO as stale (from a previous cook) + // So we can delte them all by calling DeleteAllStaleHGPOs after. + void MarkAllHGPOsAsStale(const bool& InStale); + + // Delete all the HGPO that were marked as stale + void DeleteAllStaleHGPOs(); + + void SetOutputObjects(const TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; + + void SetInstancedOutputs(const TMap& InInstancedOuput) { InstancedOutputs = InInstancedOuput; }; + + // Marks all HGPO and OutputIdentifier as loaded + void MarkAsLoaded(const bool& InLoaded); + + FORCEINLINE + const bool IsEditableNode() { return bIsEditableNode; }; + + FORCEINLINE + void SetIsEditableNode(bool IsEditable) { bIsEditableNode = IsEditable; } + + FORCEINLINE + const bool HasEditableNodeBuilt() { return bHasEditableNodeBuilt; }; + + FORCEINLINE + void SetHasEditableNodeBuilt(bool HasBuilt) { bHasEditableNodeBuilt = HasBuilt; } + + FORCEINLINE + void SetIsUpdating(bool bInIsUpdating) { bIsUpdating = bInIsUpdating; }; + + FORCEINLINE + bool IsUpdating() const { return bIsUpdating; }; + + FORCEINLINE + void SetLandscapeWorldComposition(const bool bInLandscapeWorldComposition) { bLandscapeWorldComposition = bInLandscapeWorldComposition; }; + + FORCEINLINE + bool IsLandscapeWorldComposition () const { return bLandscapeWorldComposition; }; + + FORCEINLINE + TArray & GetHoudiniCreatedSocketActors() { return HoudiniCreatedSocketActors; }; + + FORCEINLINE + TArray & GetHoudiniAttachedSocketActors() { return HoudiniAttachedSocketActors; } + + // Duplicate this object and copy its state to the resulting object. + // This is typically used to transfer state between between template and instance components. + UHoudiniOutput* DuplicateAndCopyProperties(UObject* DestOuter, FName NewName); + + // Copy properties but preserve output objects + virtual void CopyPropertiesFrom(UHoudiniOutput* InOutput, bool bCopyAllProperties); + + // Set whether this object can delete Houdini nodes. + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } + + //------------------------------------------------------------------------------------------------ + // Helpers + //------------------------------------------------------------------------------------------------ + static FString OutputTypeToString(const EHoudiniOutputType& InOutputType); + + FBox GetBounds() const; + + void Clear(); + + bool ShouldDeferClear() const; + +protected: + + virtual void BeginDestroy() override; + +protected: + + // Indicates the type of output we're dealing with + UPROPERTY() + EHoudiniOutputType Type; + + // The output's corresponding HGPO + UPROPERTY() + TArray HoudiniGeoPartObjects; + + // + UPROPERTY(DuplicateTransient) + TMap OutputObjects; + + // Instanced outputs + // Stores the instance variations objects (replacement), transform offsets + UPROPERTY() + TMap InstancedOutputs; + + // The material assignments for this output + UPROPERTY() + TMap AssignementMaterials; + + UPROPERTY() + TMap ReplacementMaterials; + + // Indicates the number of stale HGPO + int32 StaleCount; + + UPROPERTY() + bool bLandscapeWorldComposition; + + // stores the created actors for sockets with actor references. + // + UPROPERTY() + TArray HoudiniCreatedSocketActors; + + UPROPERTY() + TArray HoudiniAttachedSocketActors; + +private: + // Use HoudiniOutput to represent an editable curve. + // This flag tells whether this output is an editable curve. + UPROPERTY() + bool bIsEditableNode; + + // An editable node is only built once. This flag indicates whether this node has been built. + UPROPERTY(DuplicateTransient) + bool bHasEditableNodeBuilt; + + // The IsUpdating flag is set to true when this out exists and is being updated. + UPROPERTY() + bool bIsUpdating; + + UPROPERTY() + bool bCanDeleteHoudiniNodes; +}; + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp index 702d5d25d..145207ac7 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp @@ -1,1758 +1,1979 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGAssetLink.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniOutput.h" - -#include "Engine/StaticMesh.h" -#include "GameFramework/Actor.h" -#include "Landscape.h" - -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#if WITH_EDITOR - #include "FileHelpers.h" - #include "EditorModeManager.h" - #include "EditorModes.h" -#endif - -// -UHoudiniPDGAssetLink::UHoudiniPDGAssetLink(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , AssetName() - , AssetNodePath() - , AssetID(-1) - , SelectedTOPNetworkIndex(-1) - , LinkState(EPDGLinkState::Inactive) - , bAutoCook(false) - , bUseTOPNodeFilter(true) - , bUseTOPOutputFilter(true) - , NumWorkitems(0) - , WorkItemTally() - , OutputCachePath() - , bNeedsUIRefresh(false) - , OutputParentActor(nullptr) -{ - TOPNodeFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER; - TOPOutputFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER; - -#if WITH_EDITORONLY_DATA - bBakeMenuExpanded = true; - HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; - PDGBakeSelectionOption = EPDGBakeSelectionOption::All; - PDGBakePackageReplaceMode = EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - bRecenterBakedActors = false; - bBakeAfterAllWorkResultObjectsLoaded = false; -#endif - - // Folder used for baking PDG outputs - BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - // TODO: - // Update init, move default filter to PCH -} - -FTOPWorkResultObject::FTOPWorkResultObject() -{ - // ResultObjects = nullptr; - Name = FString(); - FilePath = FString(); - State = EPDGWorkResultState::None; - WorkItemResultInfoIndex = INDEX_NONE; - bAutoBakedSinceLastLoad = false; -} - -FTOPWorkResultObject::~FTOPWorkResultObject() -{ - // DestroyResultOutputs(); -} - -FTOPWorkResult::FTOPWorkResult() -{ - WorkItemIndex = -1; - WorkItemID = -1; - - ResultObjects.SetNum(0); -} - -bool -FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const -{ - if (WorkItemIndex != OtherWorkResult.WorkItemIndex) - return false; - if (WorkItemID != OtherWorkResult.WorkItemID) - return false; - /* - if (ResultObjects != OtherWorkResult.ResultObjects) - return false; - */ - - return true; -} - -void -FTOPWorkResult::ClearAndDestroyResultObjects() -{ - if (ResultObjects.Num() <= 0) - return; - - for (FTOPWorkResultObject& ResultObject : ResultObjects) - { - ResultObject.DestroyResultOutputsAndRemoveOutputActor(); - } - - ResultObjects.Empty(); -} - -int32 -FTOPWorkResult::IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) -{ - const int32 NumEntries = ResultObjects.Num(); - for (int32 Index = 0; Index < NumEntries; ++Index) - { - const FTOPWorkResultObject& CurResultObject = ResultObjects[Index]; - if (CurResultObject.WorkItemResultInfoIndex == InWorkItemResultInfoIndex) - { - return Index; - } - } - - return INDEX_NONE; -} - -FTOPWorkResultObject* -FTOPWorkResult::GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) -{ - const int32 ArrayIndex = IndexOfWorkResultObjectByHAPIResultInfoIndex(InWorkItemResultInfoIndex); - if (ArrayIndex == INDEX_NONE) - { - return nullptr; - } - - return GetWorkResultObjectByArrayIndex(ArrayIndex); -} - -FTOPWorkResultObject* -FTOPWorkResult::GetWorkResultObjectByArrayIndex(const int32& InArrayIndex) -{ - if (!ResultObjects.IsValidIndex(InArrayIndex)) - { - return nullptr; - } - - return &ResultObjects[InArrayIndex]; -} - - -FWorkItemTallyBase::~FWorkItemTallyBase() -{ -} - -bool -FWorkItemTallyBase::AreAllWorkItemsComplete() const -{ - return ( - NumWaitingWorkItems() == 0 && NumCookingWorkItems() == 0 && NumScheduledWorkItems() == 0 - && (NumWorkItems() == (NumCookedWorkItems() + NumErroredWorkItems())) ); -} - -bool -FWorkItemTallyBase::AnyWorkItemsFailed() const -{ - return NumErroredWorkItems() > 0; -} - -bool -FWorkItemTallyBase::AnyWorkItemsPending() const -{ - return (NumWorkItems() > 0 && (NumWaitingWorkItems() > 0 || NumCookingWorkItems() > 0 || NumScheduledWorkItems() > 0)); -} - -FString -FWorkItemTallyBase::ProgressRatio() const -{ - const float Ratio = NumWorkItems() > 0 ? (NumCookedWorkItems() / NumWorkItems()) * 100.f : 0; - - return FString::Printf(TEXT("%.1f%%"), Ratio); -} - - -FWorkItemTally::FWorkItemTally() -{ - AllWorkItems.Empty(); - WaitingWorkItems.Empty(); - ScheduledWorkItems.Empty(); - CookingWorkItems.Empty(); - CookedWorkItems.Empty(); - ErroredWorkItems.Empty(); - CookCancelledWorkItems.Empty(); -} - -void -FWorkItemTally::ZeroAll() -{ - AllWorkItems.Empty(); - WaitingWorkItems.Empty(); - ScheduledWorkItems.Empty(); - CookingWorkItems.Empty(); - CookedWorkItems.Empty(); - ErroredWorkItems.Empty(); - CookCancelledWorkItems.Empty(); -} - -void -FWorkItemTally::RemoveWorkItem(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - AllWorkItems.Remove(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsWaiting(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - WaitingWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsScheduled(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - ScheduledWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsCooking(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - CookingWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsCooked(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - CookedWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsErrored(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - ErroredWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsCookCancelled(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - CookCancelledWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RemoveWorkItemFromAllStateSets(int32 InWorkItemID) -{ - WaitingWorkItems.Remove(InWorkItemID); - ScheduledWorkItems.Remove(InWorkItemID); - CookingWorkItems.Remove(InWorkItemID); - CookedWorkItems.Remove(InWorkItemID); - ErroredWorkItems.Remove(InWorkItemID); - CookCancelledWorkItems.Remove(InWorkItemID); -} - - -FAggregatedWorkItemTally::FAggregatedWorkItemTally() -{ - TotalWorkItems = 0; - WaitingWorkItems = 0; - ScheduledWorkItems = 0; - CookingWorkItems = 0; - CookedWorkItems = 0; - ErroredWorkItems = 0; - CookCancelledWorkItems = 0; -} - -void -FAggregatedWorkItemTally::ZeroAll() -{ - TotalWorkItems = 0; - WaitingWorkItems = 0; - ScheduledWorkItems = 0; - CookingWorkItems = 0; - CookedWorkItems = 0; - ErroredWorkItems = 0; - CookCancelledWorkItems = 0; -} - -void -FAggregatedWorkItemTally::Add(const FWorkItemTallyBase& InWorkItemTally) -{ - TotalWorkItems += InWorkItemTally.NumWorkItems(); - WaitingWorkItems += InWorkItemTally.NumWaitingWorkItems(); - ScheduledWorkItems += InWorkItemTally.NumScheduledWorkItems(); - CookingWorkItems += InWorkItemTally.NumCookingWorkItems(); - CookedWorkItems += InWorkItemTally.NumCookedWorkItems(); - ErroredWorkItems += InWorkItemTally.NumErroredWorkItems(); - CookCancelledWorkItems += InWorkItemTally.NumCookCancelledWorkItems(); -} - -void -FAggregatedWorkItemTally::Subtract(const FWorkItemTallyBase& InWorkItemTally) -{ - TotalWorkItems -= InWorkItemTally.NumWorkItems(); - WaitingWorkItems -= InWorkItemTally.NumWaitingWorkItems(); - ScheduledWorkItems -= InWorkItemTally.NumScheduledWorkItems(); - CookingWorkItems -= InWorkItemTally.NumCookingWorkItems(); - CookedWorkItems -= InWorkItemTally.NumCookedWorkItems(); - ErroredWorkItems -= InWorkItemTally.NumErroredWorkItems(); - CookCancelledWorkItems -= InWorkItemTally.NumCookCancelledWorkItems(); -} - - -UTOPNode::UTOPNode() -{ - NodeId = -1; - NodeName = FString(); - NodePath = FString(); - ParentName = FString(); - - WorkResultParent = nullptr; - WorkResult.SetNum(0); - - bHidden = false; - bAutoLoad = false; - - NodeState = EPDGNodeState::None; - - bCachedHaveNotLoadedWorkResults = false; - bCachedHaveLoadedWorkResults = false; - bHasChildNodes = false; - - bShow = false; - - InvalidateLandscapeCache(); -} - -bool -UTOPNode::operator==(const UTOPNode& Other) const -{ - if (!NodeName.Equals(Other.NodeName)) - return false; - - if (!ParentName.Equals(Other.ParentName)) - return false; - - //if (NodeId != Other.NodeId) - // return false; - - return true; -} - -void -UTOPNode::Reset() -{ - NodeState = EPDGNodeState::None; - WorkItemTally.ZeroAll(); - AggregatedWorkItemTally.ZeroAll(); -} - -void UTOPNode::OnWorkItemWaiting(int32 InWorkItemID) -{ - FTOPWorkResult* const WorkItem = GetWorkResultByID(InWorkItemID); - if (WorkItem) - { - // Clear the bAutoBakedSinceLastLoad flag on the work results since we are expecting a cook of the work item - for (FTOPWorkResultObject& WRO : WorkItem->ResultObjects) - { - WRO.SetAutoBakedSinceLastLoad(false); - } - } - WorkItemTally.RecordWorkItemAsWaiting(InWorkItemID); -} - -void -UTOPNode::OnWorkItemCooked(int32 InWorkItemID) -{ - if (GetWorkItemTally().NumCookedWorkItems() == 0) - { - // We want to invalidate the landscape cache values in any situation where - // all the work items are being recooked. - InvalidateLandscapeCache(); - } - WorkItemTally.RecordWorkItemAsCooked(InWorkItemID); -} - -void -UTOPNode::SetVisibleInLevel(bool bInVisible) -{ - if (bShow == bInVisible) - return; - - bShow = bInVisible; - UpdateOutputVisibilityInLevel(); -} - -void -UTOPNode::UpdateOutputVisibilityInLevel() -{ - AActor* Actor = OutputActorOwner.GetOutputActor(); - if (IsValid(Actor)) - { - Actor->SetHidden(!bShow); -#if WITH_EDITOR - Actor->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - AActor* WROActor = WRO.GetOutputActorOwner().GetOutputActor(); - if (IsValid(WROActor)) - { - WROActor->SetHidden(!bShow); -#if WITH_EDITOR - WROActor->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - - // We need to manually handle child landscape's visiblity - for (UHoudiniOutput* ResultOutput : WRO.GetResultOutputs()) - { - if (!ResultOutput || ResultOutput->IsPendingKill()) - continue; - - for (auto& Pair : ResultOutput->GetOutputObjects()) - { - FHoudiniOutputObject& OutputObject = Pair.Value; - ALandscapeProxy* LandscapeProxy = Cast(OutputObject.OutputObject); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) - continue; - - ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); - if (!Landscape || Landscape->IsPendingKill()) - continue; - - Landscape->SetHidden(!bShow); -#if WITH_EDITOR - Landscape->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - } - } - } -} - -void -UTOPNode::SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad) -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::NotLoaded || - (WRO.State == EPDGWorkResultState::Deleted && bInAlsoSetDeletedToLoad)) - WRO.State = EPDGWorkResultState::ToLoad; - } - } -} - -void -UTOPNode::SetLoadedWorkResultsToDelete() -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::Loaded) - WRO.State = EPDGWorkResultState::ToDelete; - } - } -} - - -void -UTOPNode::DeleteWorkResultOutputObjects() -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::Loaded) - { - // Delete and clean up that WRObj - WRO.DestroyResultOutputs(); - WRO.GetOutputActorOwner().DestroyOutputActor(); - WRO.State = EPDGWorkResultState::Deleted; - } - } - } - bCachedHaveLoadedWorkResults = false; -} - -FString -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex) -{ - return FString::Printf(TEXT("%d_%d"), InWorkItemIndex, InWorkResultObjectArrayIndex); -} - -FString -UTOPNode::GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex) -{ - return GetBakedWorkResultObjectOutputsKey(InWorkResult.WorkItemIndex, InWorkResultObjectArrayIndex); -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const -{ - // Check that indices are valid - if (!WorkResult.IsValidIndex(InWorkResultArrayIndex)) - return false; - const FTOPWorkResult& WorkResultEntry = WorkResult[InWorkResultArrayIndex]; - if (!WorkResultEntry.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) - return false; - - OutKey = GetBakedWorkResultObjectOutputsKey(WorkResultEntry, InWorkResultObjectArrayIndex); - - return true; -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) -{ - FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) - return false; - OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); - if (!OutBakedOutput) - return false; - - return true; -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const -{ - FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) - return false; - OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); - if (!OutBakedOutput) - return false; - - return true; -} - -int32 -UTOPNode::IndexOfWorkResultByID(const int32& InWorkItemID) -{ - const int32 NumEntries = WorkResult.Num(); - for (int32 Index = 0; Index < NumEntries; ++Index) - { - const FTOPWorkResult& CurResult = WorkResult[Index]; - if (CurResult.WorkItemID == InWorkItemID) - { - return Index; - } - } - - return INDEX_NONE; -} - -FTOPWorkResult* -UTOPNode::GetWorkResultByID(const int32& InWorkItemID) -{ - const int32 ArrayIndex = IndexOfWorkResultByID(InWorkItemID); - if (!WorkResult.IsValidIndex(ArrayIndex)) - return nullptr; - - return &WorkResult[ArrayIndex]; -} - -int32 -UTOPNode::IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) -{ - const int32 NumEntries = WorkResult.Num(); - for (int32 Index = 0; Index < NumEntries; ++Index) - { - const FTOPWorkResult& CurResult = WorkResult[Index]; - if (CurResult.WorkItemIndex == InWorkItemIndex && (!bInWithInvalidWorkItemID || CurResult.WorkItemID == INDEX_NONE)) - { - return Index; - } - } - - return INDEX_NONE; -} - -FTOPWorkResult* -UTOPNode::GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) -{ - const int32 ArrayIndex = IndexOfWorkResultByHAPIIndex(InWorkItemIndex, bInWithInvalidWorkItemID); - if (!WorkResult.IsValidIndex(ArrayIndex)) - return nullptr; - return &WorkResult[ArrayIndex]; -} - -FTOPWorkResult* -UTOPNode::GetWorkResultByArrayIndex(const int32& InArrayIndex) -{ - if (!WorkResult.IsValidIndex(InArrayIndex)) - return nullptr; - return &WorkResult[InArrayIndex]; -} - -bool -UTOPNode::IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const -{ - if (!IsValid(InNetwork)) - { - return false; - } - - return ParentName == FString::Printf(TEXT("%s_%s"), *InNetwork->ParentName, *InNetwork->NodeName); -} - -#if WITH_EDITOR -void -UTOPNode::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) -{ - Super::PostEditChangeChainProperty(InPropertyChangedEvent); - - const FName PropertyName = InPropertyChangedEvent.GetPropertyName(); - if (PropertyName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) - { - UpdateOutputVisibilityInLevel(); - } -} -#endif - -#if WITH_EDITOR -void -UTOPNode::PostTransacted(const FTransactionObjectEvent& TransactionEvent) -{ - Super::PostTransacted(TransactionEvent); - - if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) - return; - - bool bUpdateVisibility = false; - for (const FName& PropName : TransactionEvent.GetChangedProperties()) - { - if (PropName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) - { - bUpdateVisibility = true; - } - } - - if (bUpdateVisibility) - UpdateOutputVisibilityInLevel(); -} -#endif - -void -UTOPNode::OnDirtyNode() -{ - InvalidateLandscapeCache(); -} - -void -UTOPNode::InvalidateLandscapeCache() -{ - LandscapeReferenceLocation.bIsCached = false; - LandscapeSizeInfo.bIsCached = false; -} - -UTOPNetwork::UTOPNetwork() -{ - NodeId = -1; - NodeName = FString(); - - AllTOPNodes.SetNum(0); - SelectedTOPIndex = -1; - - ParentName = FString(); - - bShowResults = false; - bAutoLoadResults = false; -} - -bool -UTOPNetwork::operator==(const UTOPNetwork& Other) const -{ - if (!NodeName.Equals(Other.NodeName)) - return false; - - if (!ParentName.Equals(Other.ParentName)) - return false; - - //if (NodeId != Other.NodeId) - // return false; - - return true; -} - -void -UTOPNetwork::SetLoadedWorkResultsToDelete() -{ - for (UTOPNode* Node : AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - Node->SetLoadedWorkResultsToDelete(); - } -} - -void -UTOPNetwork::DeleteWorkResultOutputObjects() -{ - for (UTOPNode* Node : AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - Node->DeleteWorkResultOutputObjects(); - } -} - -bool -UTOPNetwork::AnyWorkItemsPending() const -{ - for (const UTOPNode* const TOPNode : AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->AnyWorkItemsPending()) - return true; - } - - return false; -} - - -void -UHoudiniPDGAssetLink::SelectTOPNetwork(const int32& AtIndex) -{ - if (!AllTOPNetworks.IsValidIndex(AtIndex)) - return; - - SelectedTOPNetworkIndex = AtIndex; -} - - -void -UHoudiniPDGAssetLink::SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex) -{ - if (!IsValid(InTOPNetwork)) - return; - - if (!InTOPNetwork->AllTOPNodes.IsValidIndex(AtIndex)) - return; - - InTOPNetwork->SelectedTOPIndex = AtIndex; -} - - -UTOPNetwork* -UHoudiniPDGAssetLink::GetSelectedTOPNetwork() -{ - return GetTOPNetwork(SelectedTOPNetworkIndex); -} - - -UTOPNode* -UHoudiniPDGAssetLink::GetSelectedTOPNode() -{ - UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); - if (!IsValid(SelectedTOPNetwork)) - return nullptr; - - if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) - return nullptr; - - UTOPNode* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; - if (!IsValid(SelectedTOPNode)) - return nullptr; - - return SelectedTOPNode; -} - -FString -UHoudiniPDGAssetLink::GetSelectedTOPNodeName() -{ - FString NodeName = FString(); - - const UTOPNode* const SelectedTOPNode = GetSelectedTOPNode(); - if (IsValid(SelectedTOPNode)) - NodeName = SelectedTOPNode->NodeName; - - return NodeName; -} - -FString -UHoudiniPDGAssetLink::GetSelectedTOPNetworkName() -{ - FString NetworkName = FString(); - - const UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNetwork)) - NetworkName = SelectedTOPNetwork->NodeName; - - return NetworkName; -} - -UTOPNetwork* -UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) -{ - if(AllTOPNetworks.IsValidIndex(AtIndex)) - { - return AllTOPNetworks[AtIndex]; - } - - return nullptr; -} - -UTOPNetwork* -UHoudiniPDGAssetLink::GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex) -{ - OutIndex = INDEX_NONE; - int32 Index = -1; - for (UTOPNetwork* CurrentTOPNet : InTOPNetworks) - { - Index += 1; - - if (!IsValid(CurrentTOPNet)) - continue; - - if (CurrentTOPNet->NodePath.Equals(InNodePath)) - { - OutIndex = Index; - return CurrentTOPNet; - } - } - - return nullptr; -} - -UTOPNode* -UHoudiniPDGAssetLink::GetParentTOPNode(const UTOPNode* InNode) -{ - if (!IsValid(InNode)) - return nullptr; - - FString NodePath = InNode->NodePath; - FString ParentPath; - - if (NodePath.EndsWith("/")) - NodePath.LeftChopInline(1); - - if (NodePath.Split("/", &ParentPath, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromEnd) && !ParentPath.IsEmpty()) - { - for (UTOPNetwork* TOPNet : AllTOPNetworks) - { - if (!IsValid(TOPNet)) - continue; - - for (UTOPNode* TOPNode : TOPNet->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->NodePath == ParentPath && InNode->NodeId != TOPNode->NodeId) - { - return TOPNode; - } - } - } - } - - return nullptr; -} - -UTOPNode* -UHoudiniPDGAssetLink::GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex) -{ - OutIndex = INDEX_NONE; - int32 Index = -1; - for (UTOPNode* CurrentTOPNode : InTOPNodes) - { - Index += 1; - - if (!IsValid(CurrentTOPNode)) - continue; - - if (CurrentTOPNode->NodePath.Equals(InNodePath)) - { - OutIndex = Index; - return CurrentTOPNode; - } - } - - return nullptr; -} - -void -UHoudiniPDGAssetLink::ClearAllTOPData() -{ - // Clears all TOP data - for(UTOPNetwork* CurrentNetwork : AllTOPNetworks) - { - if (!IsValid(CurrentNetwork)) - continue; - - for(UTOPNode* CurrentTOPNode : CurrentNetwork->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - ClearTOPNodeWorkItemResults(CurrentTOPNode); - } - } - - AllTOPNetworks.Empty(); -} - -void -UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork) -{ - if (!IsValid(TOPNetwork)) - return; - - for(UTOPNode* CurrentTOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - ClearTOPNodeWorkItemResults(CurrentTOPNode); - } -} - -void -UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) -{ - if (!IsValid(TOPNode)) - return; - - TOPNode->OnDirtyNode(); - - for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) - { - DestroyWorkItemResultData(CurrentWorkResult); - } - TOPNode->WorkResult.Empty(); - - FOutputActorOwner& OutputActorOwner = TOPNode->GetOutputActorOwner(); - AActor* OutputActor = OutputActorOwner.GetOutputActor(); - if (IsValid(OutputActor)) - { - // Destroy any attached actors (which we'll assume that any attachments left - // are untracked actors associated with the TOPNode) - TArray AttachedActors; - OutputActor->GetAttachedActors(AttachedActors); - for (AActor* Actor : AttachedActors) - { - if (!IsValid(Actor)) - continue; - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - } - } - - if (TOPNode->WorkResultParent && !TOPNode->WorkResultParent->IsPendingKill()) - { - - // TODO: Destroy the Parent Object - // DestroyImmediate(topNode._workResultParentGO); - } - - OutputActorOwner.DestroyOutputActor(); -} - - -void -UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); - if (WorkResult) - { - DestroyWorkItemResultData(*WorkResult); - // TODO: Should we destroy the FTOPWorkResult struct entirely here? - //TOPNode.WorkResult.RemoveByPredicate - } -} - -void -UHoudiniPDGAssetLink::DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - // TODO: Update ClearWorkItemResultByID or GetWorkResultByID to return the index of the work item - // so that we don't have to find its index again to remove it from the array - ClearWorkItemResultByID(InWorkItemID, InTOPNode); - // Find the index of the FTOPWorkResult for InWorkItemID in InTOPNode.WorkResult and remove it - const int32 Index = InTOPNode->WorkResult.IndexOfByPredicate( - [InWorkItemID](const FTOPWorkResult& InWorkItem) { return InWorkItem.WorkItemID == InWorkItemID; }); - if (Index != INDEX_NONE && Index >= 0) - InTOPNode->WorkResult.RemoveAt(Index); -} - -FTOPWorkResult* -UHoudiniPDGAssetLink::GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return nullptr; - return InTOPNode->GetWorkResultByID(InWorkItemID); -} - -FDirectoryPath -UHoudiniPDGAssetLink::GetTemporaryCookFolder() const -{ - UObject* Owner = GetOuter(); - UHoudiniAssetComponent* HAC = Cast(Owner); - if (HAC) - return HAC->TemporaryCookFolder; - - FDirectoryPath TempPath; - TempPath.Path = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - return TempPath; -} - -void -UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) -{ - ResultObject.DestroyResultOutputsAndRemoveOutputActor(); -} - -void -UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result) -{ - Result.ClearAndDestroyResultObjects(); -} - - -UTOPNode* -UHoudiniPDGAssetLink::GetTOPNode(const int32& InNodeID) -{ - for (UTOPNetwork* CurrentTOPNet : AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - if (CurrentTOPNode->NodeId == InNodeID) - return CurrentTOPNode; - } - } - - return nullptr; -} - -void -UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork) -{ - if (!IsValid(InNode) || !IsValid(InNetwork)) - return; - - if (!InNode->bHasChildNodes) - return; - - FString PrefixPath = InNode->NodePath; - if (!PrefixPath.EndsWith("/")) - PrefixPath += "/"; - InNode->ZeroWorkItemTally(); - InNode->NodeState = EPDGNodeState::None; - - TMap NodeStateOrder; - NodeStateOrder.Add(EPDGNodeState::None, 0); - NodeStateOrder.Add(EPDGNodeState::Cook_Complete, 1); - NodeStateOrder.Add(EPDGNodeState::Dirtied, 2); - NodeStateOrder.Add(EPDGNodeState::Cook_Failed, 3); - NodeStateOrder.Add(EPDGNodeState::Dirtying, 4); - NodeStateOrder.Add(EPDGNodeState::Cooking, 5); - - int8 CurrentState = 0; - - for (const UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - if (Node->NodePath.StartsWith(PrefixPath) && !Node->bHasChildNodes) - { - InNode->AggregateTallyFromChildNode(Node); - const int8 VisitedNodeState = NodeStateOrder.FindChecked(Node->NodeState); - if (VisitedNodeState > CurrentState) - CurrentState = VisitedNodeState; - } - } - - EPDGNodeState const* const NewState = NodeStateOrder.FindKey(CurrentState); - if (NewState) - InNode->NodeState = *NewState; -} - -void -UHoudiniPDGAssetLink::UpdateWorkItemTally() -{ - WorkItemTally.ZeroAll(); - for(UTOPNetwork* CurrentTOPNet : AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - for(UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - // Only add up the tallys from nodes without children (since parent's aggregate the child work items counts) - if (CurrentTOPNode->bHasChildNodes) - { - UpdateTOPNodeWithChildrenWorkItemTallyAndState(CurrentTOPNode, CurrentTOPNet); - } - else - { - WorkItemTally.Add(CurrentTOPNode->GetWorkItemTally()); - } - } - } -} - - -void -UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork) -{ - if (!IsValid(TOPNetwork)) - return; - - for (UTOPNode* CurTOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(CurTOPNode)) - continue; - - CurTOPNode->ZeroWorkItemTally(); - } -} - - -FString -UHoudiniPDGAssetLink::GetAssetLinkStatus(const EPDGLinkState& InLinkState) -{ - FString Status; - switch (InLinkState) - { - case EPDGLinkState::Inactive: - Status = TEXT("Inactive"); - case EPDGLinkState::Linking: - Status = TEXT("Linking"); - case EPDGLinkState::Linked: - Status = TEXT("Linked"); - case EPDGLinkState::Error_Not_Linked: - Status = TEXT("Not Linked"); - default: - Status = TEXT(""); - } - - return Status; -} - -FString -UHoudiniPDGAssetLink::GetTOPNodeStatus(const UTOPNode* InTOPNode) -{ - static const FString InvalidOrUnknownStatus = TEXT(""); - - if (!IsValid(InTOPNode)) - return InvalidOrUnknownStatus; - - if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) - { - return TEXT("Cook Failed"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) - { - return TEXT("Cook Completed"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Cooking) - { - return TEXT("Cook In Progress"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) - { - return TEXT("Dirtied"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) - { - return TEXT("Dirtying"); - } - - return InvalidOrUnknownStatus; -} - -FLinearColor -UHoudiniPDGAssetLink::GetTOPNodeStatusColor(const UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return FLinearColor::White; - - if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) - { - return FLinearColor::Red; - } - else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) - { - return FLinearColor::Green; - } - else if (InTOPNode->NodeState == EPDGNodeState::Cooking) - { - return FLinearColor(0.0, 1.0f, 1.0f); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) - { - return FLinearColor(1.0f, 0.5f, 0.0f); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) - { - return FLinearColor::Yellow; - } - - return FLinearColor::White; -} - -AActor* -UHoudiniPDGAssetLink::GetOwnerActor() const -{ - UObject* Outer = GetOuter(); - UActorComponent* Component = Cast(Outer); - if (IsValid(Component)) - return Component->GetOwner(); - else - return Cast(Outer); -} - -bool -UHoudiniPDGAssetLink::HasTemporaryOutputs() const -{ - // Loop over all networks, all nodes, all work items and check for any valid output objects - for (const UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (const UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - for (const FTOPWorkResult& WorkResult : TOPNode->WorkResult) - { - for (const FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) - { - // If the WorkResultObject's actor is not valid, then it no longer has temporary objects in the - // scene - if (!IsValid(WorkResultObject.GetOutputActorOwner().GetOutputActor())) - continue; - - for (UHoudiniOutput* Output : WorkResultObject.GetResultOutputs()) - { - if (!IsValid(Output)) - continue; - - const EHoudiniOutputType OutputType = Output->GetType(); - for (const auto& OutputObjectPair : Output->GetOutputObjects()) - { - if ((OutputType == EHoudiniOutputType::Landscape && IsValid(OutputObjectPair.Value.OutputObject)) || - IsValid(OutputObjectPair.Value.OutputComponent)) - { - return true; - } - } - } - } - } - } - } - - return false; -} - -void -UHoudiniPDGAssetLink::UpdatePostDuplicate() -{ - // Loop over all networks, all nodes, all work items and clear output actors - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - for (FTOPWorkResult& WorkResult : TOPNode->WorkResult) - { - for (FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) - { - WorkResultObject.GetOutputActorOwner().SetOutputActor(nullptr); - WorkResultObject.State = EPDGWorkResultState::None; - WorkResultObject.SetResultOutputs(TArray()); - } - } - TOPNode->GetOutputActorOwner().SetOutputActor(nullptr); - TOPNode->bCachedHaveNotLoadedWorkResults = false; - TOPNode->bCachedHaveLoadedWorkResults = false; - } - } -} - -void -UHoudiniPDGAssetLink::UpdateTOPNodeAutoloadAndVisibility() -{ - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->bAutoLoad) - { - // // Set work results that are cooked but in NotLoaded state to ToLoad - // TOPNode.SetNotLoadedWorkResultsToLoad(); - } - - TOPNode->UpdateOutputVisibilityInLevel(); - } - } -} - -void -UHoudiniPDGAssetLink::FilterTOPNodesAndOutputs() -{ - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - // TOP Node visibility filter via TOPNodeFilter - if (bUseTOPNodeFilter) - { - TOPNode->bHidden = !TOPNodeFilter.IsEmpty() && !TOPNode->NodeName.StartsWith(TOPNodeFilter); - } - else - { - TOPNode->bHidden = false; - } - - // Auto load results filter via TOPNodeOutputFilter - if (bUseTOPOutputFilter) - { - const bool bNewAutoLoad = TOPOutputFilter.IsEmpty() || TOPNode->NodeName.StartsWith(TOPOutputFilter); - if (bNewAutoLoad != TOPNode->bAutoLoad) - { - if (bNewAutoLoad) - { - // Set work results that are cooked but in NotLoaded state to ToLoad - TOPNode->bAutoLoad = true; - // TOPNode->SetNotLoadedWorkResultsToLoad(); - TOPNode->SetVisibleInLevel(true); - } - else - { - TOPNode->bAutoLoad = false; - TOPNode->SetVisibleInLevel(false); - } - TOPNode->UpdateOutputVisibilityInLevel(); - } - } - } - } -} - -#if WITH_EDITORONLY_DATA -void -UHoudiniPDGAssetLink::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedChainEvent) -{ - Super::PostEditChangeChainProperty(InPropertyChangedChainEvent); - - const FName PropertyName = InPropertyChangedChainEvent.GetPropertyName(); - if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) - { - // Refilter TOP nodes - FilterTOPNodesAndOutputs(); - bNeedsUIRefresh = true; - } - else if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) - { - bNeedsUIRefresh = true; - } -} - -#endif - -#if WITH_EDITORONLY_DATA -void -UHoudiniPDGAssetLink::PostTransacted(const FTransactionObjectEvent& TransactionEvent) -{ - Super::PostTransacted(TransactionEvent); - - if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) - return; - - bool bDoFilterTOPNodesAndOutputs = false; - for (const FName& PropName : TransactionEvent.GetChangedProperties()) - { - if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) - { - bDoFilterTOPNodesAndOutputs = true; - bNeedsUIRefresh = true; - } - else if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) - { - bNeedsUIRefresh = true; - } - } - - if (bDoFilterTOPNodesAndOutputs) - FilterTOPNodesAndOutputs(); -} -#endif - -void -FTOPWorkResultObject::DestroyResultOutputs() -{ - // Delete output components and gather output objects for deletion - bool bDidDestroyObjects = false; - bool bDidModifyFoliage = false; - - AActor* const OutputActor = OutputActorOwner.GetOutputActor(); - - for (UHoudiniOutput* CurOutput : ResultOutputs) - { - for (auto& Pair : CurOutput->GetOutputObjects()) - { - FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - FHoudiniOutputObject& OutputObject = Pair.Value; - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) - { - // Instancer components require some special handling around foliage - // TODO: move/refactor so that we can use the InstanceTranslator's helper functions (RemoveAndDestroyComponent and CleanupFoliageInstances) - bool bDestroyComponent = true; - if (OutputObject.OutputComponent->IsA()) - { - UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OutputObject.OutputComponent); - if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) - { - // Make sure foliage our foliage instances have been removed - USceneComponent* ParentComponent = nullptr; - if (IsValid(OutputActor)) - ParentComponent = Cast(OutputActor->GetRootComponent()); - else - ParentComponent = Cast(HISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) - { - UStaticMesh* FoliageSM = HISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - return; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(HISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return; - - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - return; -#if WITH_EDITOR - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); - - // Remove the foliage type if it doesn't have any more instances - if(HISMC->GetInstanceCount() == 0) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - bDidModifyFoliage = true; -#endif - } - - // // do not delete FISMC that still have instances left - // // as we have cleaned up our instances before, these have been hand-placed - // if (HISMC->GetInstanceCount() > 0) - bDestroyComponent = false; - - OutputObject.OutputComponent = nullptr; - } - } - - if (bDestroyComponent) - { - USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from its actor first - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - // Detach from its parent component if attached - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - bDidDestroyObjects = true; - - OutputObject.OutputComponent = nullptr; - } - } - } - if (OutputObject.OutputObject && !OutputObject.OutputObject->IsPendingKill()) - { - // For actors we detach them first and then destroy - AActor* Actor = Cast(OutputObject.OutputObject); - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - if (LandscapePtr) - { - Actor = LandscapePtr->GetRawPtr(); - } - - if (Actor) - { - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - - bDidDestroyObjects = true; - } - else - { - // ... if not an actor, mark as pending kill - // OutputObject.OutputObject->MarkPendingKill(); - if (IsValid(OutputObject.OutputObject)) - OutputObjectsToDelete.Add(OutputObject.OutputObject); - OutputObject.OutputObject = nullptr; - } - } - } - } - - ResultOutputs.Empty(); - - if (bDidDestroyObjects) - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - // Delete the output objects we found - if (OutputObjectsToDelete.Num() > 0) - FHoudiniEngineRuntimeUtils::SafeDeleteObjects(OutputObjectsToDelete); - -#if WITH_EDITOR - if (bDidModifyFoliage) - { - // Repopulate the foliage types in the foliage mode UI if foliage mode is active - // There is a helper function in FHoudiniEngineUtils for this, but we cannot access it from this module. - // TODO: refactor? - FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); - if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) - { - EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); - EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); - } - } -#endif -} - -void FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor() -{ - DestroyResultOutputs(); - GetOutputActorOwner().DestroyOutputActor(); -} - -bool -FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) -{ - // InAssetLink and InWorld must not be null - if (!InAssetLink || InAssetLink->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InAssetLink is null!")); - return false; - } - if (!InWorld || InWorld->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InWorld is null!")); - return false; - } - - AActor* AssetLinkActor = InAssetLink->GetOwnerActor(); - - const bool bParentActorIsValid = IsValid(InParentActor); - ULevel* LevelToSpawnIn = nullptr; - if (bParentActorIsValid) - { - LevelToSpawnIn = InParentActor->GetLevel(); - } - else - { - // Get the level containing the asset link's actor - if (IsValid(AssetLinkActor)) - LevelToSpawnIn = AssetLinkActor->GetLevel(); - } - - // Fallback to InWorld's current level - UWorld* WorldToSpawnIn = nullptr; - if (!IsValid(LevelToSpawnIn)) - { - LevelToSpawnIn = InWorld->GetCurrentLevel(); - WorldToSpawnIn = InWorld; - } - else - { - WorldToSpawnIn = LevelToSpawnIn->GetWorld(); - } - - if (!IsValid(WorldToSpawnIn) || !IsValid(LevelToSpawnIn)) - { - HOUDINI_LOG_WARNING( - TEXT("Could not determine level and world to spawn PDG output actor in: asset link %s, name %s"), - *(InAssetLink->GetPathName()), - *(InName.ToString())); - return false; - } - - FActorSpawnParameters SpawnParams; - SpawnParams.Name = MakeUniqueObjectName(InWorld, AActor::StaticClass(), InName); - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; - SpawnParams.OverrideLevel = LevelToSpawnIn; - AActor *Actor = WorldToSpawnIn->SpawnActor(SpawnParams); - SetOutputActor(Actor); -#if WITH_EDITOR - FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, InName.ToString()); -#endif - - // Set the actor transform: create a root component if it does not have one - USceneComponent* RootComponent = Actor->GetRootComponent(); - if (!RootComponent || RootComponent->IsPendingKill()) - { - RootComponent = NewObject(Actor, USceneComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - RootComponent->CreationMethod = EComponentCreationMethod::Instance; - Actor->SetRootComponent(RootComponent); - RootComponent->OnComponentCreated(); - RootComponent->RegisterComponent(); - } - - RootComponent->SetVisibility(true); - RootComponent->SetMobility(EComponentMobility::Static); - - const FVector ActorSpawnLocation = InParentActor ? InParentActor->GetActorLocation() : FVector::ZeroVector; - const FRotator ActorSpawnRotator = InParentActor ? InParentActor->GetActorRotation() : FRotator::ZeroRotator; - Actor->SetActorLocation(ActorSpawnLocation); - Actor->SetActorRotation(ActorSpawnRotator); - -#if WITH_EDITOR - if (IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) - { - Actor->SetFolderPath(InParentActor->GetFolderPath()); - Actor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); - } - else if (IsValid(AssetLinkActor) && AssetLinkActor->GetLevel() == LevelToSpawnIn) - { - Actor->SetFolderPath(*FString::Format( - TEXT("{0}/{1}_Output"), - { FStringFormatArg(AssetLinkActor->GetFolderPath().ToString()), FStringFormatArg(AssetLinkActor->GetActorLabel()) } - )); - } - else - { - Actor->SetFolderPath(*FString::Format(TEXT("{0}_Output"), { FStringFormatArg(InAssetLink->GetName()) })); - } -#else - if(IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) - { - OutputActor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); - } -#endif - - return true; -} - -bool -FOutputActorOwner::DestroyOutputActor() -{ - bool bDestroyed = false; - AActor *Actor = GetOutputActor(); - if (IsValid(Actor)) - { - // Detach from parent before destroying the actor - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - - bDestroyed = true; - } - - SetOutputActor(nullptr); - - return bDestroyed; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGAssetLink.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniOutput.h" + +#include "Engine/StaticMesh.h" +#include "GameFramework/Actor.h" +#include "Landscape.h" + +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#if WITH_EDITOR + #include "FileHelpers.h" + #include "EditorModeManager.h" + #include "EditorModes.h" +#endif + +// +UHoudiniPDGAssetLink::UHoudiniPDGAssetLink(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , AssetName() + , AssetNodePath() + , AssetID(-1) + , SelectedTOPNetworkIndex(-1) + , LinkState(EPDGLinkState::Inactive) + , bAutoCook(false) + , bUseTOPNodeFilter(true) + , bUseTOPOutputFilter(true) + , NumWorkitems(0) + , WorkItemTally() + , OutputCachePath() + , bNeedsUIRefresh(false) + , OutputParentActor(nullptr) +{ + TOPNodeFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER; + TOPOutputFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER; + +#if WITH_EDITORONLY_DATA + bBakeMenuExpanded = true; + HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; + PDGBakeSelectionOption = EPDGBakeSelectionOption::All; + PDGBakePackageReplaceMode = EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + bRecenterBakedActors = false; + bBakeAfterAllWorkResultObjectsLoaded = false; +#endif + + // Folder used for baking PDG outputs + BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + // TODO: + // Update init, move default filter to PCH +} + +FTOPWorkResultObject::FTOPWorkResultObject() +{ + // ResultObjects = nullptr; + Name = FString(); + FilePath = FString(); + State = EPDGWorkResultState::None; + WorkItemResultInfoIndex = INDEX_NONE; + bAutoBakedSinceLastLoad = false; +} + +FTOPWorkResultObject::~FTOPWorkResultObject() +{ + // DestroyResultOutputs(); +} + +FTOPWorkResult::FTOPWorkResult() +{ + WorkItemIndex = -1; + WorkItemID = -1; + + ResultObjects.SetNum(0); +} + +bool +FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const +{ + if (WorkItemIndex != OtherWorkResult.WorkItemIndex) + return false; + if (WorkItemID != OtherWorkResult.WorkItemID) + return false; + /* + if (ResultObjects != OtherWorkResult.ResultObjects) + return false; + */ + + return true; +} + +void +FTOPWorkResult::ClearAndDestroyResultObjects() +{ + if (ResultObjects.Num() <= 0) + return; + + for (FTOPWorkResultObject& ResultObject : ResultObjects) + { + ResultObject.DestroyResultOutputsAndRemoveOutputActor(); + } + + ResultObjects.Empty(); +} + +int32 +FTOPWorkResult::IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) +{ + const int32 NumEntries = ResultObjects.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResultObject& CurResultObject = ResultObjects[Index]; + if (CurResultObject.WorkItemResultInfoIndex == InWorkItemResultInfoIndex) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResultObject* +FTOPWorkResult::GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) +{ + const int32 ArrayIndex = IndexOfWorkResultObjectByHAPIResultInfoIndex(InWorkItemResultInfoIndex); + if (ArrayIndex == INDEX_NONE) + { + return nullptr; + } + + return GetWorkResultObjectByArrayIndex(ArrayIndex); +} + +FTOPWorkResultObject* +FTOPWorkResult::GetWorkResultObjectByArrayIndex(const int32& InArrayIndex) +{ + if (!ResultObjects.IsValidIndex(InArrayIndex)) + { + return nullptr; + } + + return &ResultObjects[InArrayIndex]; +} + + +FWorkItemTallyBase::~FWorkItemTallyBase() +{ +} + +bool +FWorkItemTallyBase::AreAllWorkItemsComplete() const +{ + return ( + NumWaitingWorkItems() == 0 && NumCookingWorkItems() == 0 && NumScheduledWorkItems() == 0 + && (NumWorkItems() == (NumCookedWorkItems() + NumErroredWorkItems())) ); +} + +bool +FWorkItemTallyBase::AnyWorkItemsFailed() const +{ + return NumErroredWorkItems() > 0; +} + +bool +FWorkItemTallyBase::AnyWorkItemsPending() const +{ + return (NumWorkItems() > 0 && (NumWaitingWorkItems() > 0 || NumCookingWorkItems() > 0 || NumScheduledWorkItems() > 0)); +} + +FString +FWorkItemTallyBase::ProgressRatio() const +{ + const float Ratio = NumWorkItems() > 0 ? (NumCookedWorkItems() / NumWorkItems()) * 100.f : 0; + + return FString::Printf(TEXT("%.1f%%"), Ratio); +} + + +FWorkItemTally::FWorkItemTally() +{ + AllWorkItems.Empty(); + WaitingWorkItems.Empty(); + ScheduledWorkItems.Empty(); + CookingWorkItems.Empty(); + CookedWorkItems.Empty(); + ErroredWorkItems.Empty(); + CookCancelledWorkItems.Empty(); +} + +void +FWorkItemTally::ZeroAll() +{ + AllWorkItems.Empty(); + WaitingWorkItems.Empty(); + ScheduledWorkItems.Empty(); + CookingWorkItems.Empty(); + CookedWorkItems.Empty(); + ErroredWorkItems.Empty(); + CookCancelledWorkItems.Empty(); +} + +void +FWorkItemTally::RemoveWorkItem(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + AllWorkItems.Remove(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsWaiting(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + WaitingWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsScheduled(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + ScheduledWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCooking(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookingWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCooked(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookedWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsErrored(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + ErroredWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCookCancelled(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookCancelledWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RemoveWorkItemFromAllStateSets(int32 InWorkItemID) +{ + WaitingWorkItems.Remove(InWorkItemID); + ScheduledWorkItems.Remove(InWorkItemID); + CookingWorkItems.Remove(InWorkItemID); + CookedWorkItems.Remove(InWorkItemID); + ErroredWorkItems.Remove(InWorkItemID); + CookCancelledWorkItems.Remove(InWorkItemID); +} + + +FAggregatedWorkItemTally::FAggregatedWorkItemTally() +{ + TotalWorkItems = 0; + WaitingWorkItems = 0; + ScheduledWorkItems = 0; + CookingWorkItems = 0; + CookedWorkItems = 0; + ErroredWorkItems = 0; + CookCancelledWorkItems = 0; +} + +void +FAggregatedWorkItemTally::ZeroAll() +{ + TotalWorkItems = 0; + WaitingWorkItems = 0; + ScheduledWorkItems = 0; + CookingWorkItems = 0; + CookedWorkItems = 0; + ErroredWorkItems = 0; + CookCancelledWorkItems = 0; +} + +void +FAggregatedWorkItemTally::Add(const FWorkItemTallyBase& InWorkItemTally) +{ + TotalWorkItems += InWorkItemTally.NumWorkItems(); + WaitingWorkItems += InWorkItemTally.NumWaitingWorkItems(); + ScheduledWorkItems += InWorkItemTally.NumScheduledWorkItems(); + CookingWorkItems += InWorkItemTally.NumCookingWorkItems(); + CookedWorkItems += InWorkItemTally.NumCookedWorkItems(); + ErroredWorkItems += InWorkItemTally.NumErroredWorkItems(); + CookCancelledWorkItems += InWorkItemTally.NumCookCancelledWorkItems(); +} + +void +FAggregatedWorkItemTally::Subtract(const FWorkItemTallyBase& InWorkItemTally) +{ + TotalWorkItems -= InWorkItemTally.NumWorkItems(); + WaitingWorkItems -= InWorkItemTally.NumWaitingWorkItems(); + ScheduledWorkItems -= InWorkItemTally.NumScheduledWorkItems(); + CookingWorkItems -= InWorkItemTally.NumCookingWorkItems(); + CookedWorkItems -= InWorkItemTally.NumCookedWorkItems(); + ErroredWorkItems -= InWorkItemTally.NumErroredWorkItems(); + CookCancelledWorkItems -= InWorkItemTally.NumCookCancelledWorkItems(); +} + + +UTOPNode::UTOPNode() +{ + NodeId = -1; + NodeName = FString(); + NodePath = FString(); + ParentName = FString(); + + WorkResultParent = nullptr; + WorkResult.SetNum(0); + + bHidden = false; + bAutoLoad = false; + + NodeState = EPDGNodeState::None; + + bCachedHaveNotLoadedWorkResults = false; + bCachedHaveLoadedWorkResults = false; + bHasChildNodes = false; + + bShow = false; + + bHasReceivedCookCompleteEvent = false; + + InvalidateLandscapeCache(); +} + +bool +UTOPNode::operator==(const UTOPNode& Other) const +{ + if (!NodeName.Equals(Other.NodeName)) + return false; + + if (!ParentName.Equals(Other.ParentName)) + return false; + + //if (NodeId != Other.NodeId) + // return false; + + return true; +} + +void +UTOPNode::Reset() +{ + NodeState = EPDGNodeState::None; + WorkItemTally.ZeroAll(); + AggregatedWorkItemTally.ZeroAll(); +} + +void UTOPNode::OnWorkItemWaiting(int32 InWorkItemID) +{ + FTOPWorkResult* const WorkItem = GetWorkResultByID(InWorkItemID); + if (WorkItem) + { + // Clear the bAutoBakedSinceLastLoad flag on the work results since we are expecting a cook of the work item + for (FTOPWorkResultObject& WRO : WorkItem->ResultObjects) + { + WRO.SetAutoBakedSinceLastLoad(false); + } + } + WorkItemTally.RecordWorkItemAsWaiting(InWorkItemID); +} + +void +UTOPNode::OnWorkItemCooked(int32 InWorkItemID) +{ + if (GetWorkItemTally().NumCookedWorkItems() == 0) + { + // We want to invalidate the landscape cache values in any situation where + // all the work items are being recooked. + InvalidateLandscapeCache(); + } + WorkItemTally.RecordWorkItemAsCooked(InWorkItemID); +} + +void +UTOPNode::SetVisibleInLevel(bool bInVisible) +{ + if (bShow == bInVisible) + return; + + bShow = bInVisible; + UpdateOutputVisibilityInLevel(); +} + +void +UTOPNode::UpdateOutputVisibilityInLevel() +{ + AActor* Actor = OutputActorOwner.GetOutputActor(); + if (IsValid(Actor)) + { + Actor->SetHidden(!bShow); +#if WITH_EDITOR + Actor->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + AActor* WROActor = WRO.GetOutputActorOwner().GetOutputActor(); + if (IsValid(WROActor)) + { + WROActor->SetHidden(!bShow); +#if WITH_EDITOR + WROActor->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + + // We need to manually handle child landscape's visiblity + for (UHoudiniOutput* ResultOutput : WRO.GetResultOutputs()) + { + if (!ResultOutput || ResultOutput->IsPendingKill()) + continue; + + for (auto& Pair : ResultOutput->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = Pair.Value; + ALandscapeProxy* LandscapeProxy = Cast(OutputObject.OutputObject); + if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + continue; + + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (!Landscape || Landscape->IsPendingKill()) + continue; + + Landscape->SetHidden(!bShow); +#if WITH_EDITOR + Landscape->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + } + } + } +} + +void +UTOPNode::SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad) +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::NotLoaded || + (WRO.State == EPDGWorkResultState::Deleted && bInAlsoSetDeletedToLoad)) + { + WRO.State = EPDGWorkResultState::ToLoad; + WRO.SetAutoBakedSinceLastLoad(false); + } + } + } +} + +void +UTOPNode::SetLoadedWorkResultsToDelete() +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::Loaded) + WRO.State = EPDGWorkResultState::ToDelete; + } + } +} + + +void +UTOPNode::DeleteWorkResultOutputObjects() +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::Loaded) + { + // Delete and clean up that WRObj + WRO.DestroyResultOutputs(); + WRO.GetOutputActorOwner().DestroyOutputActor(); + WRO.State = EPDGWorkResultState::Deleted; + } + } + } + bCachedHaveLoadedWorkResults = false; +} + +FString +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex) +{ + return FString::Printf(TEXT("%d_%d"), InWorkItemIndex, InWorkResultObjectArrayIndex); +} + +FString +UTOPNode::GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex) +{ + return GetBakedWorkResultObjectOutputsKey(InWorkResult.WorkItemIndex, InWorkResultObjectArrayIndex); +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const +{ + // Check that indices are valid + if (!WorkResult.IsValidIndex(InWorkResultArrayIndex)) + return false; + const FTOPWorkResult& WorkResultEntry = WorkResult[InWorkResultArrayIndex]; + if (!WorkResultEntry.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) + return false; + + OutKey = GetBakedWorkResultObjectOutputsKey(WorkResultEntry, InWorkResultObjectArrayIndex); + + return true; +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) +{ + FString Key; + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) + return false; + OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); + if (!OutBakedOutput) + return false; + + return true; +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const +{ + FString Key; + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) + return false; + OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); + if (!OutBakedOutput) + return false; + + return true; +} + +int32 +UTOPNode::IndexOfWorkResultByID(const int32& InWorkItemID) +{ + const int32 NumEntries = WorkResult.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResult& CurResult = WorkResult[Index]; + if (CurResult.WorkItemID == InWorkItemID) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByID(const int32& InWorkItemID) +{ + const int32 ArrayIndex = IndexOfWorkResultByID(InWorkItemID); + if (!WorkResult.IsValidIndex(ArrayIndex)) + return nullptr; + + return &WorkResult[ArrayIndex]; +} + +int32 +UTOPNode::IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) +{ + const int32 NumEntries = WorkResult.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResult& CurResult = WorkResult[Index]; + if (CurResult.WorkItemIndex == InWorkItemIndex && (!bInWithInvalidWorkItemID || CurResult.WorkItemID == INDEX_NONE)) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) +{ + const int32 ArrayIndex = IndexOfWorkResultByHAPIIndex(InWorkItemIndex, bInWithInvalidWorkItemID); + if (!WorkResult.IsValidIndex(ArrayIndex)) + return nullptr; + return &WorkResult[ArrayIndex]; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByArrayIndex(const int32& InArrayIndex) +{ + if (!WorkResult.IsValidIndex(InArrayIndex)) + return nullptr; + return &WorkResult[InArrayIndex]; +} + +bool +UTOPNode::IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const +{ + if (!IsValid(InNetwork)) + { + return false; + } + + return ParentName == FString::Printf(TEXT("%s_%s"), *InNetwork->ParentName, *InNetwork->NodeName); +} + +bool +UTOPNode::CanStillBeAutoBaked() const +{ + // Only nodes that have results auto-loaded are auto-baked + if (!bAutoLoad) + return false; + + // Nodes with failures are not auto-baked + if (AnyWorkItemsFailed()) + return false; + + // All work items are not yet complete, so node cannot yet be baked + if (!AreAllWorkItemsComplete()) + return true; + + // Work items that are currently loaded or has not tagged has auto baked since last load can still be baked + for (const FTOPWorkResult& WorkResultEntry : WorkResult) + { + for (const FTOPWorkResultObject& WRO : WorkResultEntry.ResultObjects) + { + switch (WRO.State) + { + case EPDGWorkResultState::NotLoaded: + case EPDGWorkResultState::ToLoad: + case EPDGWorkResultState::Loading: + return true; + case EPDGWorkResultState::Loaded: + if (!WRO.AutoBakedSinceLastLoad()) + return true; + break; + case EPDGWorkResultState::ToDelete: + case EPDGWorkResultState::Deleting: + case EPDGWorkResultState::Deleted: + case EPDGWorkResultState::None: + break; + } + } + } + + return false; +} + +#if WITH_EDITOR +void +UTOPNode::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) +{ + Super::PostEditChangeChainProperty(InPropertyChangedEvent); + + const FName PropertyName = InPropertyChangedEvent.GetPropertyName(); + if (PropertyName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) + { + UpdateOutputVisibilityInLevel(); + } +} +#endif + +#if WITH_EDITOR +void +UTOPNode::PostTransacted(const FTransactionObjectEvent& TransactionEvent) +{ + Super::PostTransacted(TransactionEvent); + + if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) + return; + + bool bUpdateVisibility = false; + for (const FName& PropName : TransactionEvent.GetChangedProperties()) + { + if (PropName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) + { + bUpdateVisibility = true; + } + } + + if (bUpdateVisibility) + UpdateOutputVisibilityInLevel(); +} +#endif + +void +UTOPNode::OnDirtyNode() +{ + InvalidateLandscapeCache(); + bHasReceivedCookCompleteEvent = false; +} + +void +UTOPNode::InvalidateLandscapeCache() +{ + LandscapeReferenceLocation.bIsCached = false; + LandscapeSizeInfo.bIsCached = false; + ClearedLandscapeLayers.Empty(); +} + +UTOPNetwork::UTOPNetwork() +{ + NodeId = -1; + NodeName = FString(); + + AllTOPNodes.SetNum(0); + SelectedTOPIndex = -1; + + ParentName = FString(); + + bShowResults = false; + bAutoLoadResults = false; +} + +bool +UTOPNetwork::operator==(const UTOPNetwork& Other) const +{ + if (!NodeName.Equals(Other.NodeName)) + return false; + + if (!ParentName.Equals(Other.ParentName)) + return false; + + //if (NodeId != Other.NodeId) + // return false; + + return true; +} + +void +UTOPNetwork::SetLoadedWorkResultsToDelete() +{ + for (UTOPNode* Node : AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + Node->SetLoadedWorkResultsToDelete(); + } +} + +void +UTOPNetwork::DeleteWorkResultOutputObjects() +{ + for (UTOPNode* Node : AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + Node->DeleteWorkResultOutputObjects(); + } +} + +bool +UTOPNetwork::AnyWorkItemsPending() const +{ + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->AnyWorkItemsPending()) + return true; + } + + return false; +} + +bool +UTOPNetwork::AnyWorkItemsFailed() const +{ + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->AnyWorkItemsFailed()) + return true; + } + + return false; +} + +bool +UTOPNetwork::CanStillBeAutoBaked() const +{ + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (TOPNode->CanStillBeAutoBaked()) + return true; + } + + return false; +} + +void +UTOPNetwork::HandleOnPDGEventCookCompleteReceivedByChildNode(UHoudiniPDGAssetLink* const InAssetLink, UTOPNode* const InTOPNode) +{ + if (!IsValid(InAssetLink)) + return; + + // Check if all nodes have recieved the HAPI_PDG_EVENT_COOK_COMPLETE event, if so, broadcast the OnPostCook handler. + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (!TOPNode->HasReceivedCookCompleteEvent()) + return; + } + + if (OnPostCookDelegate.IsBound()) + OnPostCookDelegate.Broadcast(this, AnyWorkItemsFailed()); + + InAssetLink->HandleOnTOPNetworkCookComplete(this); +} + +void +UHoudiniPDGAssetLink::SelectTOPNetwork(const int32& AtIndex) +{ + if (!AllTOPNetworks.IsValidIndex(AtIndex)) + return; + + SelectedTOPNetworkIndex = AtIndex; +} + + +void +UHoudiniPDGAssetLink::SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex) +{ + if (!IsValid(InTOPNetwork)) + return; + + if (!InTOPNetwork->AllTOPNodes.IsValidIndex(AtIndex)) + return; + + InTOPNetwork->SelectedTOPIndex = AtIndex; +} + + +UTOPNetwork* +UHoudiniPDGAssetLink::GetSelectedTOPNetwork() +{ + return GetTOPNetwork(SelectedTOPNetworkIndex); +} + +const UTOPNetwork* +UHoudiniPDGAssetLink::GetSelectedTOPNetwork() const +{ + return GetTOPNetwork(SelectedTOPNetworkIndex); +} + +UTOPNode* +UHoudiniPDGAssetLink::GetSelectedTOPNode() +{ + UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNetwork)) + return nullptr; + + if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) + return nullptr; + + UTOPNode* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; + if (!IsValid(SelectedTOPNode)) + return nullptr; + + return SelectedTOPNode; +} + +const UTOPNode* +UHoudiniPDGAssetLink::GetSelectedTOPNode() const +{ + UTOPNetwork const* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNetwork)) + return nullptr; + + if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) + return nullptr; + + UTOPNode const* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; + if (!IsValid(SelectedTOPNode)) + return nullptr; + + return SelectedTOPNode; +} + +FString +UHoudiniPDGAssetLink::GetSelectedTOPNodeName() +{ + FString NodeName = FString(); + + const UTOPNode* const SelectedTOPNode = GetSelectedTOPNode(); + if (IsValid(SelectedTOPNode)) + NodeName = SelectedTOPNode->NodeName; + + return NodeName; +} + +FString +UHoudiniPDGAssetLink::GetSelectedTOPNetworkName() +{ + FString NetworkName = FString(); + + const UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNetwork)) + NetworkName = SelectedTOPNetwork->NodeName; + + return NetworkName; +} + +UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) +{ + if(AllTOPNetworks.IsValidIndex(AtIndex)) + { + return AllTOPNetworks[AtIndex]; + } + + return nullptr; +} + +const UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) const +{ + if(AllTOPNetworks.IsValidIndex(AtIndex)) + { + return AllTOPNetworks[AtIndex]; + } + + return nullptr; +} + +UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex) +{ + OutIndex = INDEX_NONE; + int32 Index = -1; + for (UTOPNetwork* CurrentTOPNet : InTOPNetworks) + { + Index += 1; + + if (!IsValid(CurrentTOPNet)) + continue; + + if (CurrentTOPNet->NodePath.Equals(InNodePath)) + { + OutIndex = Index; + return CurrentTOPNet; + } + } + + return nullptr; +} + +UTOPNode* +UHoudiniPDGAssetLink::GetParentTOPNode(const UTOPNode* InNode) +{ + if (!IsValid(InNode)) + return nullptr; + + FString NodePath = InNode->NodePath; + FString ParentPath; + + if (NodePath.EndsWith("/")) + NodePath.LeftChopInline(1); + + if (NodePath.Split("/", &ParentPath, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromEnd) && !ParentPath.IsEmpty()) + { + for (UTOPNetwork* TOPNet : AllTOPNetworks) + { + if (!IsValid(TOPNet)) + continue; + + for (UTOPNode* TOPNode : TOPNet->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->NodePath == ParentPath && InNode->NodeId != TOPNode->NodeId) + { + return TOPNode; + } + } + } + } + + return nullptr; +} + +UTOPNode* +UHoudiniPDGAssetLink::GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex) +{ + OutIndex = INDEX_NONE; + int32 Index = -1; + for (UTOPNode* CurrentTOPNode : InTOPNodes) + { + Index += 1; + + if (!IsValid(CurrentTOPNode)) + continue; + + if (CurrentTOPNode->NodePath.Equals(InNodePath)) + { + OutIndex = Index; + return CurrentTOPNode; + } + } + + return nullptr; +} + +void +UHoudiniPDGAssetLink::ClearAllTOPData() +{ + // Clears all TOP data + for(UTOPNetwork* CurrentNetwork : AllTOPNetworks) + { + if (!IsValid(CurrentNetwork)) + continue; + + for(UTOPNode* CurrentTOPNode : CurrentNetwork->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + ClearTOPNodeWorkItemResults(CurrentTOPNode); + } + } + + AllTOPNetworks.Empty(); +} + +void +UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork) +{ + if (!IsValid(TOPNetwork)) + return; + + for(UTOPNode* CurrentTOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + ClearTOPNodeWorkItemResults(CurrentTOPNode); + } +} + +void +UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) +{ + if (!IsValid(TOPNode)) + return; + + TOPNode->OnDirtyNode(); + + for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) + { + DestroyWorkItemResultData(CurrentWorkResult); + } + TOPNode->WorkResult.Empty(); + + FOutputActorOwner& OutputActorOwner = TOPNode->GetOutputActorOwner(); + AActor* OutputActor = OutputActorOwner.GetOutputActor(); + if (IsValid(OutputActor)) + { + // Destroy any attached actors (which we'll assume that any attachments left + // are untracked actors associated with the TOPNode) + TArray AttachedActors; + OutputActor->GetAttachedActors(AttachedActors); + for (AActor* Actor : AttachedActors) + { + if (!IsValid(Actor)) + continue; + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + } + } + + if (TOPNode->WorkResultParent && !TOPNode->WorkResultParent->IsPendingKill()) + { + + // TODO: Destroy the Parent Object + // DestroyImmediate(topNode._workResultParentGO); + } + + OutputActorOwner.DestroyOutputActor(); +} + + +void +UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); + if (WorkResult) + { + DestroyWorkItemResultData(*WorkResult); + // TODO: Should we destroy the FTOPWorkResult struct entirely here? + //TOPNode.WorkResult.RemoveByPredicate + } +} + +void +UHoudiniPDGAssetLink::DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + // TODO: Update ClearWorkItemResultByID or GetWorkResultByID to return the index of the work item + // so that we don't have to find its index again to remove it from the array + ClearWorkItemResultByID(InWorkItemID, InTOPNode); + // Find the index of the FTOPWorkResult for InWorkItemID in InTOPNode.WorkResult and remove it + const int32 Index = InTOPNode->WorkResult.IndexOfByPredicate( + [InWorkItemID](const FTOPWorkResult& InWorkItem) { return InWorkItem.WorkItemID == InWorkItemID; }); + if (Index != INDEX_NONE && Index >= 0) + InTOPNode->WorkResult.RemoveAt(Index); +} + +FTOPWorkResult* +UHoudiniPDGAssetLink::GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return nullptr; + return InTOPNode->GetWorkResultByID(InWorkItemID); +} + +FDirectoryPath +UHoudiniPDGAssetLink::GetTemporaryCookFolder() const +{ + UHoudiniAssetComponent* HAC = GetOuterHoudiniAssetComponent(); + if (HAC) + return HAC->TemporaryCookFolder; + + FDirectoryPath TempPath; + TempPath.Path = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + return TempPath; +} + +void +UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) +{ + ResultObject.DestroyResultOutputsAndRemoveOutputActor(); +} + +void +UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result) +{ + Result.ClearAndDestroyResultObjects(); +} + +void +UHoudiniPDGAssetLink::HandleOnTOPNetworkCookComplete(UTOPNetwork* const InTOPNet) +{ + if (!IsValid(InTOPNet)) + return; + + if (OnPostTOPNetworkCookDelegate.IsBound()) + { + OnPostTOPNetworkCookDelegate.Broadcast(this, InTOPNet, InTOPNet->AnyWorkItemsFailed()); + } +} + + +UTOPNode* +UHoudiniPDGAssetLink::GetTOPNode(const int32& InNodeID) +{ + UTOPNetwork* Network = nullptr; + UTOPNode* Node = nullptr; + + if (GetTOPNodeAndNetworkByNodeId(InNodeID, Network, Node)) + return Node; + + return nullptr; +} + + +bool +UHoudiniPDGAssetLink::GetTOPNodeAndNetworkByNodeId(const int32& InNodeID, UTOPNetwork*& OutNetwork, UTOPNode*& OutNode) +{ + OutNetwork = nullptr; + OutNode = nullptr; + + for (UTOPNetwork* CurrentTOPNet : AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + if (CurrentTOPNode->NodeId == InNodeID) + { + OutNetwork = CurrentTOPNet; + OutNode = CurrentTOPNode; + return true; + } + } + } + + return false; +} + + +void +UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork) +{ + if (!IsValid(InNode) || !IsValid(InNetwork)) + return; + + if (!InNode->bHasChildNodes) + return; + + FString PrefixPath = InNode->NodePath; + if (!PrefixPath.EndsWith("/")) + PrefixPath += "/"; + InNode->ZeroWorkItemTally(); + InNode->NodeState = EPDGNodeState::None; + + TMap NodeStateOrder; + NodeStateOrder.Add(EPDGNodeState::None, 0); + NodeStateOrder.Add(EPDGNodeState::Cook_Complete, 1); + NodeStateOrder.Add(EPDGNodeState::Dirtied, 2); + NodeStateOrder.Add(EPDGNodeState::Cook_Failed, 3); + NodeStateOrder.Add(EPDGNodeState::Dirtying, 4); + NodeStateOrder.Add(EPDGNodeState::Cooking, 5); + + int8 CurrentState = 0; + + for (const UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + if (Node->NodePath.StartsWith(PrefixPath) && !Node->bHasChildNodes) + { + InNode->AggregateTallyFromChildNode(Node); + const int8 VisitedNodeState = NodeStateOrder.FindChecked(Node->NodeState); + if (VisitedNodeState > CurrentState) + CurrentState = VisitedNodeState; + } + } + + EPDGNodeState const* const NewState = NodeStateOrder.FindKey(CurrentState); + if (NewState) + InNode->NodeState = *NewState; +} + +void +UHoudiniPDGAssetLink::UpdateWorkItemTally() +{ + WorkItemTally.ZeroAll(); + for(UTOPNetwork* CurrentTOPNet : AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + for(UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + // Only add up the tallys from nodes without children (since parent's aggregate the child work items counts) + if (CurrentTOPNode->bHasChildNodes) + { + UpdateTOPNodeWithChildrenWorkItemTallyAndState(CurrentTOPNode, CurrentTOPNet); + } + else + { + WorkItemTally.Add(CurrentTOPNode->GetWorkItemTally()); + } + } + } +} + + +void +UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork) +{ + if (!IsValid(TOPNetwork)) + return; + + for (UTOPNode* CurTOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(CurTOPNode)) + continue; + + CurTOPNode->ZeroWorkItemTally(); + } +} + + +FString +UHoudiniPDGAssetLink::GetAssetLinkStatus(const EPDGLinkState& InLinkState) +{ + FString Status; + switch (InLinkState) + { + case EPDGLinkState::Inactive: + Status = TEXT("Inactive"); + case EPDGLinkState::Linking: + Status = TEXT("Linking"); + case EPDGLinkState::Linked: + Status = TEXT("Linked"); + case EPDGLinkState::Error_Not_Linked: + Status = TEXT("Not Linked"); + default: + Status = TEXT(""); + } + + return Status; +} + +FString +UHoudiniPDGAssetLink::GetTOPNodeStatus(const UTOPNode* InTOPNode) +{ + static const FString InvalidOrUnknownStatus = TEXT(""); + + if (!IsValid(InTOPNode)) + return InvalidOrUnknownStatus; + + if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) + { + return TEXT("Cook Failed"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) + { + return TEXT("Cook Completed"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Cooking) + { + return TEXT("Cook In Progress"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) + { + return TEXT("Dirtied"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) + { + return TEXT("Dirtying"); + } + + return InvalidOrUnknownStatus; +} + +FLinearColor +UHoudiniPDGAssetLink::GetTOPNodeStatusColor(const UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return FLinearColor::White; + + if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) + { + return FLinearColor::Red; + } + else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) + { + return FLinearColor::Green; + } + else if (InTOPNode->NodeState == EPDGNodeState::Cooking) + { + return FLinearColor(0.0, 1.0f, 1.0f); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) + { + return FLinearColor(1.0f, 0.5f, 0.0f); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) + { + return FLinearColor::Yellow; + } + + return FLinearColor::White; +} + +AActor* +UHoudiniPDGAssetLink::GetOwnerActor() const +{ + UObject* Outer = GetOuter(); + UActorComponent* Component = Cast(Outer); + if (IsValid(Component)) + return Component->GetOwner(); + else + return Cast(Outer); +} + +bool +UHoudiniPDGAssetLink::HasTemporaryOutputs() const +{ + // Loop over all networks, all nodes, all work items and check for any valid output objects + for (const UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (const UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + for (const FTOPWorkResult& WorkResult : TOPNode->WorkResult) + { + for (const FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) + { + // If the WorkResultObject's actor is not valid, then it no longer has temporary objects in the + // scene + if (!IsValid(WorkResultObject.GetOutputActorOwner().GetOutputActor())) + continue; + + for (UHoudiniOutput* Output : WorkResultObject.GetResultOutputs()) + { + if (!IsValid(Output)) + continue; + + const EHoudiniOutputType OutputType = Output->GetType(); + for (const auto& OutputObjectPair : Output->GetOutputObjects()) + { + if ((OutputType == EHoudiniOutputType::Landscape && IsValid(OutputObjectPair.Value.OutputObject)) || + IsValid(OutputObjectPair.Value.OutputComponent)) + { + return true; + } + } + } + } + } + } + } + + return false; +} + +void +UHoudiniPDGAssetLink::UpdatePostDuplicate() +{ + // Loop over all networks, all nodes, all work items and clear output actors + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + for (FTOPWorkResult& WorkResult : TOPNode->WorkResult) + { + for (FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) + { + WorkResultObject.GetOutputActorOwner().SetOutputActor(nullptr); + WorkResultObject.State = EPDGWorkResultState::None; + WorkResultObject.SetResultOutputs(TArray()); + } + } + TOPNode->GetOutputActorOwner().SetOutputActor(nullptr); + TOPNode->bCachedHaveNotLoadedWorkResults = false; + TOPNode->bCachedHaveLoadedWorkResults = false; + } + } +} + +UHoudiniAssetComponent* UHoudiniPDGAssetLink::GetOuterHoudiniAssetComponent() const +{ + return Cast( GetTypedOuter() ); +} + +void +UHoudiniPDGAssetLink::UpdateTOPNodeAutoloadAndVisibility() +{ + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->bAutoLoad) + { + // // Set work results that are cooked but in NotLoaded state to ToLoad + // TOPNode.SetNotLoadedWorkResultsToLoad(); + } + + TOPNode->UpdateOutputVisibilityInLevel(); + } + } +} + +void +UHoudiniPDGAssetLink::FilterTOPNodesAndOutputs() +{ + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + // TOP Node visibility filter via TOPNodeFilter + if (bUseTOPNodeFilter) + { + TOPNode->bHidden = !TOPNodeFilter.IsEmpty() && !TOPNode->NodeName.StartsWith(TOPNodeFilter); + } + else + { + TOPNode->bHidden = false; + } + + // Auto load results filter via TOPNodeOutputFilter + if (bUseTOPOutputFilter) + { + const bool bNewAutoLoad = TOPOutputFilter.IsEmpty() || TOPNode->NodeName.StartsWith(TOPOutputFilter); + if (bNewAutoLoad != TOPNode->bAutoLoad) + { + if (bNewAutoLoad) + { + // Set work results that are cooked but in NotLoaded state to ToLoad + TOPNode->bAutoLoad = true; + // TOPNode->SetNotLoadedWorkResultsToLoad(); + TOPNode->SetVisibleInLevel(true); + } + else + { + TOPNode->bAutoLoad = false; + TOPNode->SetVisibleInLevel(false); + } + TOPNode->UpdateOutputVisibilityInLevel(); + } + } + } + } +} + +#if WITH_EDITORONLY_DATA +bool +UHoudiniPDGAssetLink::AnyRemainingAutoBakeNodes() const +{ + if (!bBakeAfterAllWorkResultObjectsLoaded) + return false; + + switch (PDGBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + { + for (const UTOPNetwork* const TOPNet : AllTOPNetworks) + { + if (!IsValid(TOPNet)) + continue; + + if (TOPNet->CanStillBeAutoBaked()) + { + return true; + } + } + break; + } + case EPDGBakeSelectionOption::SelectedNetwork: + { + const UTOPNetwork* const TOPNet = GetSelectedTOPNetwork(); + if (IsValid(TOPNet) && TOPNet->CanStillBeAutoBaked()) + { + return true; + } + } + case EPDGBakeSelectionOption::SelectedNode: + { + UTOPNode const* const TOPNode = GetSelectedTOPNode(); + if (IsValid(TOPNode) && TOPNode->CanStillBeAutoBaked()) + { + return true; + } + } + default: + return false; + } + + return false; +} +#endif + +void +UHoudiniPDGAssetLink::HandleOnPostBake(const bool bInSuccess) +{ + if (OnPostBakeDelegate.IsBound()) + OnPostBakeDelegate.Broadcast(this, bInSuccess); +} + +#if WITH_EDITORONLY_DATA +void +UHoudiniPDGAssetLink::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedChainEvent) +{ + Super::PostEditChangeChainProperty(InPropertyChangedChainEvent); + + const FName PropertyName = InPropertyChangedChainEvent.GetPropertyName(); + if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) + { + // Refilter TOP nodes + FilterTOPNodesAndOutputs(); + bNeedsUIRefresh = true; + } + else if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) + { + bNeedsUIRefresh = true; + } +} + +#endif + +#if WITH_EDITORONLY_DATA +void +UHoudiniPDGAssetLink::PostTransacted(const FTransactionObjectEvent& TransactionEvent) +{ + Super::PostTransacted(TransactionEvent); + + if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) + return; + + bool bDoFilterTOPNodesAndOutputs = false; + for (const FName& PropName : TransactionEvent.GetChangedProperties()) + { + if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) + { + bDoFilterTOPNodesAndOutputs = true; + bNeedsUIRefresh = true; + } + else if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) + { + bNeedsUIRefresh = true; + } + } + + if (bDoFilterTOPNodesAndOutputs) + FilterTOPNodesAndOutputs(); +} +#endif + +void +FTOPWorkResultObject::DestroyResultOutputs() +{ + // Delete output components and gather output objects for deletion + bool bDidDestroyObjects = false; + bool bDidModifyFoliage = false; + + AActor* const OutputActor = OutputActorOwner.GetOutputActor(); + + for (UHoudiniOutput* CurOutput : ResultOutputs) + { + for (auto& Pair : CurOutput->GetOutputObjects()) + { + FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniOutputObject& OutputObject = Pair.Value; + if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) + { + // Instancer components require some special handling around foliage + // TODO: move/refactor so that we can use the InstanceTranslator's helper functions (RemoveAndDestroyComponent and CleanupFoliageInstances) + bool bDestroyComponent = true; + if (OutputObject.OutputComponent->IsA()) + { + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OutputObject.OutputComponent); + if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) + { + // Make sure foliage our foliage instances have been removed + USceneComponent* ParentComponent = nullptr; + if (IsValid(OutputActor)) + ParentComponent = Cast(OutputActor->GetRootComponent()); + else + ParentComponent = Cast(HISMC->GetOuter()); + if (ParentComponent && !ParentComponent->IsPendingKill()) + { + UStaticMesh* FoliageSM = HISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + return; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(HISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return; + + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + if (!FoliageType || FoliageType->IsPendingKill()) + return; +#if WITH_EDITOR + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); + + // Remove the foliage type if it doesn't have any more instances + if(HISMC->GetInstanceCount() == 0) + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + + bDidModifyFoliage = true; +#endif + } + + // // do not delete FISMC that still have instances left + // // as we have cleaned up our instances before, these have been hand-placed + // if (HISMC->GetInstanceCount() > 0) + bDestroyComponent = false; + + OutputObject.OutputComponent = nullptr; + } + } + + if (bDestroyComponent) + { + USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from its actor first + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + // Detach from its parent component if attached + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + bDidDestroyObjects = true; + + OutputObject.OutputComponent = nullptr; + } + } + } + if (OutputObject.OutputObject && !OutputObject.OutputObject->IsPendingKill()) + { + // For actors we detach them first and then destroy + AActor* Actor = Cast(OutputObject.OutputObject); + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + if (LandscapePtr) + { + Actor = LandscapePtr->GetRawPtr(); + } + + if (Actor) + { + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + + bDidDestroyObjects = true; + } + else + { + // ... if not an actor, mark as pending kill + // OutputObject.OutputObject->MarkPendingKill(); + if (IsValid(OutputObject.OutputObject)) + OutputObjectsToDelete.Add(OutputObject.OutputObject); + OutputObject.OutputObject = nullptr; + } + } + } + } + + ResultOutputs.Empty(); + + if (bDidDestroyObjects) + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Delete the output objects we found + if (OutputObjectsToDelete.Num() > 0) + FHoudiniEngineRuntimeUtils::SafeDeleteObjects(OutputObjectsToDelete); + +#if WITH_EDITOR + if (bDidModifyFoliage) + { + // Repopulate the foliage types in the foliage mode UI if foliage mode is active + // There is a helper function in FHoudiniEngineUtils for this, but we cannot access it from this module. + // TODO: refactor? + FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); + if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) + { + EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); + EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); + } + } +#endif +} + +void FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor() +{ + DestroyResultOutputs(); + GetOutputActorOwner().DestroyOutputActor(); +} + +bool +FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) +{ + // InAssetLink and InWorld must not be null + if (!InAssetLink || InAssetLink->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InAssetLink is null!")); + return false; + } + if (!InWorld || InWorld->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InWorld is null!")); + return false; + } + + AActor* AssetLinkActor = InAssetLink->GetOwnerActor(); + + const bool bParentActorIsValid = IsValid(InParentActor); + ULevel* LevelToSpawnIn = nullptr; + if (bParentActorIsValid) + { + LevelToSpawnIn = InParentActor->GetLevel(); + } + else + { + // Get the level containing the asset link's actor + if (IsValid(AssetLinkActor)) + LevelToSpawnIn = AssetLinkActor->GetLevel(); + } + + // Fallback to InWorld's current level + UWorld* WorldToSpawnIn = nullptr; + if (!IsValid(LevelToSpawnIn)) + { + LevelToSpawnIn = InWorld->GetCurrentLevel(); + WorldToSpawnIn = InWorld; + } + else + { + WorldToSpawnIn = LevelToSpawnIn->GetWorld(); + } + + if (!IsValid(WorldToSpawnIn) || !IsValid(LevelToSpawnIn)) + { + HOUDINI_LOG_WARNING( + TEXT("Could not determine level and world to spawn PDG output actor in: asset link %s, name %s"), + *(InAssetLink->GetPathName()), + *(InName.ToString())); + return false; + } + + FActorSpawnParameters SpawnParams; + SpawnParams.Name = MakeUniqueObjectName(InWorld, AActor::StaticClass(), InName); + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; + SpawnParams.OverrideLevel = LevelToSpawnIn; + AActor *Actor = WorldToSpawnIn->SpawnActor(SpawnParams); + SetOutputActor(Actor); +#if WITH_EDITOR + FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, InName.ToString()); +#endif + + // Set the actor transform: create a root component if it does not have one + USceneComponent* RootComponent = Actor->GetRootComponent(); + if (!RootComponent || RootComponent->IsPendingKill()) + { + RootComponent = NewObject(Actor, USceneComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + RootComponent->CreationMethod = EComponentCreationMethod::Instance; + Actor->SetRootComponent(RootComponent); + RootComponent->OnComponentCreated(); + RootComponent->RegisterComponent(); + } + + RootComponent->SetVisibility(true); + RootComponent->SetMobility(EComponentMobility::Static); + + const FVector ActorSpawnLocation = InParentActor ? InParentActor->GetActorLocation() : FVector::ZeroVector; + const FRotator ActorSpawnRotator = InParentActor ? InParentActor->GetActorRotation() : FRotator::ZeroRotator; + Actor->SetActorLocation(ActorSpawnLocation); + Actor->SetActorRotation(ActorSpawnRotator); + +#if WITH_EDITOR + if (IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) + { + Actor->SetFolderPath(InParentActor->GetFolderPath()); + Actor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); + } + else if (IsValid(AssetLinkActor) && AssetLinkActor->GetLevel() == LevelToSpawnIn) + { + Actor->SetFolderPath(*FString::Format( + TEXT("{0}/{1}_Output"), + { FStringFormatArg(AssetLinkActor->GetFolderPath().ToString()), FStringFormatArg(AssetLinkActor->GetActorLabel()) } + )); + } + else + { + Actor->SetFolderPath(*FString::Format(TEXT("{0}_Output"), { FStringFormatArg(InAssetLink->GetName()) })); + } +#else + if(IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) + { + OutputActor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); + } +#endif + + return true; +} + +bool +FOutputActorOwner::DestroyOutputActor() +{ + bool bDestroyed = false; + AActor *Actor = GetOutputActor(); + if (IsValid(Actor)) + { + // Detach from parent before destroying the actor + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + + bDestroyed = true; + } + + SetOutputActor(nullptr); + + return bDestroyed; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h index 0fc8a5c8c..c22fdd6c8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h @@ -1,838 +1,909 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -//#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniTranslatorTypes.h" - -#include "HoudiniPDGAssetLink.generated.h" - -struct FHoudiniPackageParams; - -UENUM() -enum class EPDGLinkState : uint8 -{ - Inactive, - Linking, - Linked, - Error_Not_Linked -}; - - -UENUM() -enum class EPDGNodeState : uint8 -{ - None, - Dirtied, - Dirtying, - Cooking, - Cook_Complete, - Cook_Failed -}; - -UENUM() -enum class EPDGWorkResultState : uint8 -{ - None, - ToLoad, - Loading, - Loaded, - ToDelete, - Deleting, - Deleted, - NotLoaded -}; - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EPDGBakeSelectionOption : uint8 -{ - All, - SelectedNetwork, - SelectedNode -}; -#endif - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EPDGBakePackageReplaceModeOption : uint8 -{ - CreateNewAssets, - ReplaceExistingAssets -}; -#endif - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FOutputActorOwner -{ - GENERATED_BODY(); -public: - FOutputActorOwner() - : OutputActor(nullptr) {}; - - virtual ~FOutputActorOwner() {}; - - // Create OutputActor, an actor to hold work result output - virtual bool CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName); - - // Return OutputActor - virtual AActor* GetOutputActor() const { return OutputActor; } - - // Setter for OutputActor - virtual void SetOutputActor(AActor* InActor) { OutputActor = InActor; } - - // Destroy OutputActor if it is valid. - virtual bool DestroyOutputActor(); - -private: - UPROPERTY(NonTransactional) - AActor* OutputActor; - -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FTOPWorkResultObject(); - - // Call DestroyResultObjects in the destructor - virtual ~FTOPWorkResultObject(); - - // Set ResultObjects to a copy of InUpdatedOutputs - void SetResultOutputs(const TArray& InUpdatedOutputs) { ResultOutputs = InUpdatedOutputs; } - - // Getter for ResultOutputs - TArray& GetResultOutputs() { return ResultOutputs; } - - // Getter for ResultOutputs - const TArray& GetResultOutputs() const { return ResultOutputs; } - - // Destroy ResultOutputs - void DestroyResultOutputs(); - - // Get the OutputActor owner struct - FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } - - // Get the OutputActor owner struct - const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } - - // Destroy the ResultOutputs and remove the output actor. - void DestroyResultOutputsAndRemoveOutputActor(); - - // Getter for bAutoBakedSinceLastLoad: indicates if this work result object has been auto-baked since it's last load. - bool AutoBakedSinceLastLoad() const { return bAutoBakedSinceLastLoad; } - // Setter for bAutoBakedSinceLastLoad - void SetAutoBakedSinceLastLoad(bool bInAutoBakedSinceLastLoad) { bAutoBakedSinceLastLoad = bInAutoBakedSinceLastLoad; } - -public: - - UPROPERTY(NonTransactional) - FString Name; - UPROPERTY(NonTransactional) - FString FilePath; - UPROPERTY(NonTransactional) - EPDGWorkResultState State; - // The index in the WorkItemResultInfo array of this item as it was received from HAPI. - UPROPERTY(NonTransactional) - int32 WorkItemResultInfoIndex; - -protected: - // UPROPERTY() - // TArray ResultObjects; - - UPROPERTY(NonTransactional) - TArray ResultOutputs; - - // If true, indicates that the work result object has been auto-baked since it was last loaded. - UPROPERTY(NonTransactional) - bool bAutoBakedSinceLastLoad = false; - -private: - // List of objects to delete, internal use only (DestroyResultOutputs) - UPROPERTY(NonTransactional) - TArray OutputObjectsToDelete; - - UPROPERTY(NonTransactional) - FOutputActorOwner OutputActorOwner; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FTOPWorkResult -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FTOPWorkResult(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const FTOPWorkResult& OtherWorkResult) const; - - // Calls FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor on each entry in ResultObjects and clears the array. - void ClearAndDestroyResultObjects(); - - // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. - int32 IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); - // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. - FTOPWorkResultObject* GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); - // Return the FTOPWorkResultObject at InArrayIndex in the ResultObjects array, or nullptr if InArrayIndex is not a valid index. - FTOPWorkResultObject* GetWorkResultObjectByArrayIndex(const int32& InArrayIndex); - -public: - - UPROPERTY(NonTransactional) - int32 WorkItemIndex; - UPROPERTY(Transient) - int32 WorkItemID; - - UPROPERTY(NonTransactional) - TArray ResultObjects; - - /* - UPROPERTY() - TArray ResultObjects; - - UPROPERTY() - TArray ResultNames; - UPROPERTY() - TArray ResultFilePaths; - UPROPERTY() - TArray ResultStates; - */ -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FWorkItemTallyBase -{ - GENERATED_USTRUCT_BODY() - -public: - virtual ~FWorkItemTallyBase(); - - // - // Mutators - // - - // Zero all counts, including total. - virtual void ZeroAll() {}; - - // - // Accessors - // - - bool AreAllWorkItemsComplete() const; - bool AnyWorkItemsFailed() const; - bool AnyWorkItemsPending() const; - - virtual int32 NumWorkItems() const { return 0; }; - virtual int32 NumWaitingWorkItems() const { return 0; }; - virtual int32 NumScheduledWorkItems() const { return 0; }; - virtual int32 NumCookingWorkItems() const { return 0; }; - virtual int32 NumCookedWorkItems() const { return 0; }; - virtual int32 NumErroredWorkItems() const { return 0; }; - virtual int32 NumCookCancelledWorkItems() const { return 0; }; - - FString ProgressRatio() const; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FWorkItemTally : public FWorkItemTallyBase -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FWorkItemTally(); - - // - // Mutators - // - - // Empty all state sets, as well as AllWorkItems. - virtual void ZeroAll() override; - - // Remove a work item from all state sets and AllWorkItems. - void RemoveWorkItem(int32 InWorkItemID); - - void RecordWorkItemAsWaiting(int32 InWorkItemID); - void RecordWorkItemAsScheduled(int32 InWorkItemID); - void RecordWorkItemAsCooking(int32 InWorkItemID); - void RecordWorkItemAsCooked(int32 InWorkItemID); - void RecordWorkItemAsErrored(int32 InWorkItemID); - void RecordWorkItemAsCookCancelled(int32 InWorkItemID); - - // - // Accessors - // - - virtual int32 NumWorkItems() const override { return AllWorkItems.Num(); } - virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems.Num(); } - virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems.Num(); } - virtual int32 NumCookingWorkItems() const override { return CookingWorkItems.Num(); } - virtual int32 NumCookedWorkItems() const override { return CookedWorkItems.Num(); } - virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems.Num(); } - virtual int32 NumCookCancelledWorkItems() const override { return CookCancelledWorkItems.Num(); } - -protected: - - // Removes the work item id from all state sets (but not from AllWorkItems -- use RemoveWorkItem for that). - void RemoveWorkItemFromAllStateSets(int32 InWorkItemID); - - // We use sets to keep track of in what state a work item is. The set stores the WorkItemID. - - UPROPERTY() - TSet AllWorkItems; - UPROPERTY() - TSet WaitingWorkItems; - UPROPERTY() - TSet ScheduledWorkItems; - UPROPERTY() - TSet CookingWorkItems; - UPROPERTY() - TSet CookedWorkItems; - UPROPERTY() - TSet ErroredWorkItems; - UPROPERTY() - TSet CookCancelledWorkItems; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FAggregatedWorkItemTally : public FWorkItemTallyBase -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FAggregatedWorkItemTally(); - - virtual void ZeroAll() override; - - void Add(const FWorkItemTallyBase& InWorkItemTally); - - void Subtract(const FWorkItemTallyBase& InWorkItemTally); - - virtual int32 NumWorkItems() const override { return TotalWorkItems; } - virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems; } - virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems; } - virtual int32 NumCookingWorkItems() const override { return CookingWorkItems; } - virtual int32 NumCookedWorkItems() const override { return CookedWorkItems; } - virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems; } - -protected: - UPROPERTY() - int32 TotalWorkItems; - UPROPERTY() - int32 WaitingWorkItems; - UPROPERTY() - int32 ScheduledWorkItems; - UPROPERTY() - int32 CookingWorkItems; - UPROPERTY() - int32 CookedWorkItems; - UPROPERTY() - int32 ErroredWorkItems; - UPROPERTY() - int32 CookCancelledWorkItems; - -}; - -// Container for baked outputs of a PDG work result object. -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniPDGWorkResultObjectBakedOutput -{ - GENERATED_BODY() - - public: - // Array of baked output per output index of the work result object's outputs. - UPROPERTY() - TArray BakedOutputs; -}; - -// Forward declare the UTOPNetwork here for some references in the UTOPNode -class UTOPNetwork; - -UCLASS() -class HOUDINIENGINERUNTIME_API UTOPNode : public UObject -{ - GENERATED_BODY() - -public: - // Constructor - UTOPNode(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const UTOPNode& Other) const; - - void Reset(); - - const FWorkItemTallyBase& GetWorkItemTally() const - { - if (bHasChildNodes) - return AggregatedWorkItemTally; - return WorkItemTally; - } - - void AggregateTallyFromChildNode(const UTOPNode* InChildNode) - { - if (IsValid(InChildNode)) - AggregatedWorkItemTally.Add(InChildNode->GetWorkItemTally()); - } - - bool AreAllWorkItemsComplete() const { return GetWorkItemTally().AreAllWorkItemsComplete(); }; - bool AnyWorkItemsFailed() const { return GetWorkItemTally().AnyWorkItemsFailed(); }; - bool AnyWorkItemsPending() const { return GetWorkItemTally().AnyWorkItemsPending(); }; - void ZeroWorkItemTally() - { - WorkItemTally.ZeroAll(); - AggregatedWorkItemTally.ZeroAll(); - } - - // Called by PDG manager when work item events are received - - // Notification that a work item has been created - void OnWorkItemCreated(int32 InWorkItemID) { }; - - // Notification that a work item has been removed. - void OnWorkItemRemoved(int32 InWorkItemID) { WorkItemTally.RemoveWorkItem(InWorkItemID); }; - - // Notification that a work item has moved to the waiting state. - void OnWorkItemWaiting(int32 InWorkItemID); - - // Notification that a work item has been scheduled. - void OnWorkItemScheduled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsScheduled(InWorkItemID); }; - - // Notification that a work item has started cooking. - void OnWorkItemCooking(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCooking(InWorkItemID); }; - - // Notification that a work item has been cooked. - void OnWorkItemCooked(int32 InWorkItemID); - - // Notification that a work item has errored. - void OnWorkItemErrored(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsErrored(InWorkItemID); }; - - // Notification that a work item cook has been cancelled. - void OnWorkItemCookCancelled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCookCancelled(InWorkItemID); }; - - bool IsVisibleInLevel() const { return bShow; } - void SetVisibleInLevel(bool bInVisible); - void UpdateOutputVisibilityInLevel(); - - // Sets all WorkResultObjects that are in the NotLoaded state to ToLoad. - void SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad=false); - - // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output - // actors). - void SetLoadedWorkResultsToDelete(); - - // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output - // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); - - // Get the OutputActor owner struct - FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } - - // Get the OutputActor owner struct - const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } - - // Get the baked outputs from the last bake. The map keys are [work_result.work_item_index]_[work_result_object_index] - TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } - const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } - // Helper to construct the key used to look up baked work results. - static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex); - // Helper to construct the key used to look up baked work results. - static FString GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex); - // Helper to construct the key used to look up baked work results. - bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const; - // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object. - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); - // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object (const version). - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; - - // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. - int32 IndexOfWorkResultByID(const int32& InWorkItemID); - // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. - FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID); - // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. - // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. - int32 IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); - // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. - // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. - FTOPWorkResult* GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); - // Return the FTOPWorkResult at InArrayIndex in the WorkResult array, or nullptr if InArrayIndex is not a valid index. - FTOPWorkResult* GetWorkResultByArrayIndex(const int32& InArrayIndex); - - // Returns true if InNetwork is the parent TOP Net of this node. - bool IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const; - -#if WITH_EDITOR - void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; -#endif - -#if WITH_EDITOR - void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; -#endif - -public: - - UPROPERTY(Transient, NonTransactional) - int32 NodeId; - UPROPERTY(NonTransactional) - FString NodeName; - UPROPERTY(NonTransactional) - FString NodePath; - UPROPERTY(NonTransactional) - FString ParentName; - - UPROPERTY() - UObject* WorkResultParent; - UPROPERTY(NonTransactional) - TArray WorkResult; - - // Hidden in the nodes combobox - UPROPERTY() - bool bHidden; - UPROPERTY() - bool bAutoLoad; - - UPROPERTY(Transient, NonTransactional) - EPDGNodeState NodeState; - - // This is set when the TOP node's work items are processed by - // FHoudiniPDGManager based on if any NotLoaded work result objects are found - UPROPERTY(NonTransactional) - bool bCachedHaveNotLoadedWorkResults; - - // This is set when the TOP node's work items are processed by - // FHoudiniPDGManager based on if any Loaded work result objects are found - UPROPERTY(NonTransactional) - bool bCachedHaveLoadedWorkResults; - - // True if this node has child nodes - UPROPERTY(NonTransactional) - bool bHasChildNodes; - - // These notification events have been introduced so that we can start encapsulating code. - // in this class as opposed to modifying this object in various places throughout the codebase. - - // Notification that this TOP node has been dirtied. - void OnDirtyNode(); - - // Accessors for the landscape data caches - FHoudiniLandscapeExtent& GetLandscapeExtent() { return LandscapeExtent; } - FHoudiniLandscapeReferenceLocation& GetLandscapeReferenceLocation() { return LandscapeReferenceLocation; } - FHoudiniLandscapeTileSizeInfo& GetLandscapeSizeInfo() { return LandscapeSizeInfo; } - -protected: - void InvalidateLandscapeCache(); - - // Value caches used during landscape tile creation. - FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; - FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; - FHoudiniLandscapeExtent LandscapeExtent; - - // Visible in the level - UPROPERTY() - bool bShow; - - // Map of [work_result_index]_[work_result_object_index] to the work result object's baked outputs. - UPROPERTY() - TMap BakedWorkResultObjectOutputs; - - // This node's own work items, used when bHasChildNodes is false. - UPROPERTY(Transient, NonTransactional) - FWorkItemTally WorkItemTally; - // This node's aggregated work item tallys (sum of child work item tally, use when bHasChildNodes is true) - UPROPERTY(Transient, NonTransactional) - FAggregatedWorkItemTally AggregatedWorkItemTally; - -private: - UPROPERTY() - FOutputActorOwner OutputActorOwner; -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject -{ - GENERATED_BODY() - -public: - - // Constructor - UTOPNetwork(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const UTOPNetwork& Other) const; - - // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output - // actors). - void SetLoadedWorkResultsToDelete(); - - // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output - // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); - - // Returns true if any node in this TOP net has pending (waiting, scheduled, cooking) work items. - bool AnyWorkItemsPending() const; - -public: - - UPROPERTY(Transient, NonTransactional) - int32 NodeId; - UPROPERTY(NonTransactional) - FString NodeName; - // HAPI path to this node (relative to the HDA) - UPROPERTY(NonTransactional) - FString NodePath; - - UPROPERTY() - TArray AllTOPNodes; - - // TODO: Should be using SelectedNodeName instead? - // Index is not consistent after updating filter - UPROPERTY() - int32 SelectedTOPIndex; - - UPROPERTY(NonTransactional) - FString ParentName; - - UPROPERTY() - bool bShowResults; - UPROPERTY() - bool bAutoLoadResults; -}; - - -class UHoudiniPDGAssetLink; -DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemHAPIIndex*/, int32 /*WorkItemResultInfoIndex*/); - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - friend class UHoudiniAssetComponent; - - static FString GetAssetLinkStatus(const EPDGLinkState& InLinkState); - static FString GetTOPNodeStatus(const UTOPNode* InTOPNode); - static FLinearColor GetTOPNodeStatusColor(const UTOPNode* InTOPNode); - - void UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork); - void UpdateWorkItemTally(); - static void ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork); - - // Set the TOP network at the given index as currently selected TOP network - void SelectTOPNetwork(const int32& AtIndex); - // Set the TOP node at the given index in the given TOP network as currently selected TOP node - void SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex); - - UTOPNode* GetSelectedTOPNode(); - UTOPNetwork* GetSelectedTOPNetwork(); - - FString GetSelectedTOPNodeName(); - FString GetSelectedTOPNetworkName(); - - UTOPNode* GetTOPNode(const int32& InNodeID); - UTOPNetwork* GetTOPNetwork(const int32& AtIndex); - - // Find the node with relative path 'InNodePath' from its topnet. - static UTOPNode* GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex); - // Find the network with relative path 'InNetPath' from the HDA - static UTOPNetwork* GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex); - - // Get the parent TOP node of the specified node. This is resolved - UTOPNode* GetParentTOPNode(const UTOPNode* InNode); - - static void ClearTOPNodeWorkItemResults(UTOPNode* TOPNode); - static void ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork); - // Clear the result objects of a work item (FTOPWorkResult.ResultObjects), but don't delete the work item from - // TOPNode.WorkResults (for example, the work item was dirtied but not removed from PDG) - static void ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - // Calls ClearWorkItemResultByID and then deletes the FTOPWorkResult from InTOPNode.Result as well. For example: - // the work item was removed in PDG. - static void DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - static FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - - // This should be called after the owner and this PDG asset link is duplicated. Set all output parent actors to - // null in all TOP networks/nodes. Since the TOP Networks/TOP nodes are all structs, we cannot set - // DuplicateTransient property on their OutputActor properties. - void UpdatePostDuplicate(); - - // Load the geometry generated as results of the given work item, of the given TOP node. - // The load will be done asynchronously. - // Results must be tagged with 'file', and must have a file path, otherwise will not be loaded. - //void LoadResults(FTOPNode TOPNode, HAPI_PDG_WorkitemInfo workItemInfo, HAPI_PDG_WorkitemResultInfo[] resultInfos, HAPI_PDG_WorkitemId workItemID) - - // Gets the temporary cook folder. If the parent of this asset link is a HoudiniAssetComponent use that, otherwise - // use the default static mesh temporary cook folder. - FDirectoryPath GetTemporaryCookFolder() const; - - // Get the actor that owns this PDG asset link. If the asset link is owned by a component, - // then the component's owning actor is returned. Can return null if this is now owned by - // an actor or component. - AActor* GetOwnerActor() const; - - // Checks if the asset link has any temporary outputs and returns true if it has - bool HasTemporaryOutputs() const; - - // Filter TOP nodes and outputs (hidden/visible) by TOPNodeFilter and TOPOutputFilter. - void FilterTOPNodesAndOutputs(); - - // On all FTOPNodes: Load not loaded items if bAutoload is true, and update the level visibility of work items - // result. Used when FTOPNode.bShow and/or FTOPNode.bAutoload changed. - void UpdateTOPNodeAutoloadAndVisibility(); - -#if WITH_EDITORONLY_DATA - void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; -#endif - -#if WITH_EDITORONLY_DATA - void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; -#endif - -private: - - void ClearAllTOPData(); - - static void DestroyWorkItemResultData(FTOPWorkResult& Result); - - static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); - -public: - - //UPROPERTY() - //UHoudiniAsset* HoudiniAsset; - - //UPROPERTY() - //UHoudiniAssetComponent* ParentHAC; - - UPROPERTY(DuplicateTransient, NonTransactional) - FString AssetName; - - // The full path to the HDA in HAPI - UPROPERTY(DuplicateTransient, NonTransactional) - FString AssetNodePath; - - UPROPERTY(DuplicateTransient, NonTransactional) - int32 AssetID; - - UPROPERTY() - TArray AllTOPNetworks; - - UPROPERTY() - int32 SelectedTOPNetworkIndex; - - UPROPERTY(Transient, NonTransactional) - EPDGLinkState LinkState; - - UPROPERTY() - bool bAutoCook; - UPROPERTY() - bool bUseTOPNodeFilter; - UPROPERTY() - bool bUseTOPOutputFilter; - UPROPERTY() - FString TOPNodeFilter; - UPROPERTY() - FString TOPOutputFilter; - - UPROPERTY(NonTransactional) - int32 NumWorkitems; - UPROPERTY(Transient, NonTransactional) - FAggregatedWorkItemTally WorkItemTally; - - UPROPERTY() - FString OutputCachePath; - - UPROPERTY(Transient) - bool bNeedsUIRefresh; - - // A parent actor to serve as the parent of any output actors - // that are created. - // If null, then output actors are created under a folder - UPROPERTY(EditAnywhere, Category="Output") - AActor* OutputParentActor; - - // Folder used for baking PDG outputs - UPROPERTY() - FDirectoryPath BakeFolder; - - // - // Notifications - // - - // Delegate that is broadcast when a work result object has been loaded - FHoudiniPDGAssetLinkWorkResultObjectLoaded OnWorkResultObjectLoaded; - - // - // End: Notifications - // - -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bBakeMenuExpanded; - - // What kind of output to bake, for example, bake actors, bake to blueprint - UPROPERTY() - EHoudiniEngineBakeOption HoudiniEngineBakeOption; - - // Which outputs to bake, for example, all, selected network, selected node - UPROPERTY() - EPDGBakeSelectionOption PDGBakeSelectionOption; - - // This determines if the baked assets should replace existing assets with the same name, - // or always generate new assets (with numerical suffixes if needed to create unique names) - UPROPERTY() - EPDGBakePackageReplaceModeOption PDGBakePackageReplaceMode; - - // If true, recenter baked actors to their bounding box center after bake - UPROPERTY() - bool bRecenterBakedActors; - - // Auto-bake: if this is true, it indicates that once all work result objects for the node is loaded they should - // all be baked - UPROPERTY() - bool bBakeAfterAllWorkResultObjectsLoaded; - - // The delegate handle of the auto bake helper function bound to OnWorkResultObjectLoaded. - FDelegateHandle AutoBakeDelegateHandle; -#endif -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +//#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniTranslatorTypes.h" + +#include "HoudiniPDGAssetLink.generated.h" + +struct FHoudiniPackageParams; + +UENUM() +enum class EPDGLinkState : uint8 +{ + Inactive, + Linking, + Linked, + Error_Not_Linked +}; + + +UENUM() +enum class EPDGNodeState : uint8 +{ + None, + Dirtied, + Dirtying, + Cooking, + Cook_Complete, + Cook_Failed +}; + +UENUM() +enum class EPDGWorkResultState : uint8 +{ + None, + ToLoad, + Loading, + Loaded, + ToDelete, + Deleting, + Deleted, + NotLoaded +}; + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EPDGBakeSelectionOption : uint8 +{ + All, + SelectedNetwork, + SelectedNode +}; +#endif + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EPDGBakePackageReplaceModeOption : uint8 +{ + CreateNewAssets, + ReplaceExistingAssets +}; +#endif + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FOutputActorOwner +{ + GENERATED_BODY(); +public: + FOutputActorOwner() + : OutputActor(nullptr) {}; + + virtual ~FOutputActorOwner() {}; + + // Create OutputActor, an actor to hold work result output + virtual bool CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName); + + // Return OutputActor + virtual AActor* GetOutputActor() const { return OutputActor; } + + // Setter for OutputActor + virtual void SetOutputActor(AActor* InActor) { OutputActor = InActor; } + + // Destroy OutputActor if it is valid. + virtual bool DestroyOutputActor(); + +private: + UPROPERTY(NonTransactional) + AActor* OutputActor; + +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FTOPWorkResultObject(); + + // Call DestroyResultObjects in the destructor + virtual ~FTOPWorkResultObject(); + + // Set ResultObjects to a copy of InUpdatedOutputs + void SetResultOutputs(const TArray& InUpdatedOutputs) { ResultOutputs = InUpdatedOutputs; } + + // Getter for ResultOutputs + TArray& GetResultOutputs() { return ResultOutputs; } + + // Getter for ResultOutputs + const TArray& GetResultOutputs() const { return ResultOutputs; } + + // Destroy ResultOutputs + void DestroyResultOutputs(); + + // Get the OutputActor owner struct + FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } + + // Get the OutputActor owner struct + const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } + + // Destroy the ResultOutputs and remove the output actor. + void DestroyResultOutputsAndRemoveOutputActor(); + + // Getter for bAutoBakedSinceLastLoad: indicates if this work result object has been auto-baked since it's last load. + bool AutoBakedSinceLastLoad() const { return bAutoBakedSinceLastLoad; } + // Setter for bAutoBakedSinceLastLoad + void SetAutoBakedSinceLastLoad(bool bInAutoBakedSinceLastLoad) { bAutoBakedSinceLastLoad = bInAutoBakedSinceLastLoad; } + +public: + + UPROPERTY(NonTransactional) + FString Name; + UPROPERTY(NonTransactional) + FString FilePath; + UPROPERTY(NonTransactional) + EPDGWorkResultState State; + // The index in the WorkItemResultInfo array of this item as it was received from HAPI. + UPROPERTY(NonTransactional) + int32 WorkItemResultInfoIndex; + +protected: + // UPROPERTY() + // TArray ResultObjects; + + UPROPERTY(NonTransactional) + TArray ResultOutputs; + + // If true, indicates that the work result object has been auto-baked since it was last loaded. + UPROPERTY(NonTransactional) + bool bAutoBakedSinceLastLoad = false; + +private: + // List of objects to delete, internal use only (DestroyResultOutputs) + UPROPERTY(NonTransactional) + TArray OutputObjectsToDelete; + + UPROPERTY(NonTransactional) + FOutputActorOwner OutputActorOwner; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FTOPWorkResult +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FTOPWorkResult(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const FTOPWorkResult& OtherWorkResult) const; + + // Calls FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor on each entry in ResultObjects and clears the array. + void ClearAndDestroyResultObjects(); + + // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. + int32 IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); + // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. + FTOPWorkResultObject* GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); + // Return the FTOPWorkResultObject at InArrayIndex in the ResultObjects array, or nullptr if InArrayIndex is not a valid index. + FTOPWorkResultObject* GetWorkResultObjectByArrayIndex(const int32& InArrayIndex); + +public: + + UPROPERTY(NonTransactional) + int32 WorkItemIndex; + UPROPERTY(Transient) + int32 WorkItemID; + + UPROPERTY(NonTransactional) + TArray ResultObjects; + + /* + UPROPERTY() + TArray ResultObjects; + + UPROPERTY() + TArray ResultNames; + UPROPERTY() + TArray ResultFilePaths; + UPROPERTY() + TArray ResultStates; + */ +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FWorkItemTallyBase +{ + GENERATED_USTRUCT_BODY() + +public: + virtual ~FWorkItemTallyBase(); + + // + // Mutators + // + + // Zero all counts, including total. + virtual void ZeroAll() {}; + + // + // Accessors + // + + bool AreAllWorkItemsComplete() const; + bool AnyWorkItemsFailed() const; + bool AnyWorkItemsPending() const; + + virtual int32 NumWorkItems() const { return 0; }; + virtual int32 NumWaitingWorkItems() const { return 0; }; + virtual int32 NumScheduledWorkItems() const { return 0; }; + virtual int32 NumCookingWorkItems() const { return 0; }; + virtual int32 NumCookedWorkItems() const { return 0; }; + virtual int32 NumErroredWorkItems() const { return 0; }; + virtual int32 NumCookCancelledWorkItems() const { return 0; }; + + FString ProgressRatio() const; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FWorkItemTally : public FWorkItemTallyBase +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FWorkItemTally(); + + // + // Mutators + // + + // Empty all state sets, as well as AllWorkItems. + virtual void ZeroAll() override; + + // Remove a work item from all state sets and AllWorkItems. + void RemoveWorkItem(int32 InWorkItemID); + + void RecordWorkItemAsWaiting(int32 InWorkItemID); + void RecordWorkItemAsScheduled(int32 InWorkItemID); + void RecordWorkItemAsCooking(int32 InWorkItemID); + void RecordWorkItemAsCooked(int32 InWorkItemID); + void RecordWorkItemAsErrored(int32 InWorkItemID); + void RecordWorkItemAsCookCancelled(int32 InWorkItemID); + + // + // Accessors + // + + virtual int32 NumWorkItems() const override { return AllWorkItems.Num(); } + virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems.Num(); } + virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems.Num(); } + virtual int32 NumCookingWorkItems() const override { return CookingWorkItems.Num(); } + virtual int32 NumCookedWorkItems() const override { return CookedWorkItems.Num(); } + virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems.Num(); } + virtual int32 NumCookCancelledWorkItems() const override { return CookCancelledWorkItems.Num(); } + +protected: + + // Removes the work item id from all state sets (but not from AllWorkItems -- use RemoveWorkItem for that). + void RemoveWorkItemFromAllStateSets(int32 InWorkItemID); + + // We use sets to keep track of in what state a work item is. The set stores the WorkItemID. + + UPROPERTY() + TSet AllWorkItems; + UPROPERTY() + TSet WaitingWorkItems; + UPROPERTY() + TSet ScheduledWorkItems; + UPROPERTY() + TSet CookingWorkItems; + UPROPERTY() + TSet CookedWorkItems; + UPROPERTY() + TSet ErroredWorkItems; + UPROPERTY() + TSet CookCancelledWorkItems; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FAggregatedWorkItemTally : public FWorkItemTallyBase +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FAggregatedWorkItemTally(); + + virtual void ZeroAll() override; + + void Add(const FWorkItemTallyBase& InWorkItemTally); + + void Subtract(const FWorkItemTallyBase& InWorkItemTally); + + virtual int32 NumWorkItems() const override { return TotalWorkItems; } + virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems; } + virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems; } + virtual int32 NumCookingWorkItems() const override { return CookingWorkItems; } + virtual int32 NumCookedWorkItems() const override { return CookedWorkItems; } + virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems; } + +protected: + UPROPERTY() + int32 TotalWorkItems; + UPROPERTY() + int32 WaitingWorkItems; + UPROPERTY() + int32 ScheduledWorkItems; + UPROPERTY() + int32 CookingWorkItems; + UPROPERTY() + int32 CookedWorkItems; + UPROPERTY() + int32 ErroredWorkItems; + UPROPERTY() + int32 CookCancelledWorkItems; + +}; + +// Container for baked outputs of a PDG work result object. +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniPDGWorkResultObjectBakedOutput +{ + GENERATED_BODY() + + public: + // Array of baked output per output index of the work result object's outputs. + UPROPERTY() + TArray BakedOutputs; +}; + +// Forward declare the UTOPNetwork here for some references in the UTOPNode +class UTOPNetwork; + +UCLASS() +class HOUDINIENGINERUNTIME_API UTOPNode : public UObject +{ + GENERATED_BODY() + +public: + // Constructor + UTOPNode(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const UTOPNode& Other) const; + + void Reset(); + + const FWorkItemTallyBase& GetWorkItemTally() const + { + if (bHasChildNodes) + return AggregatedWorkItemTally; + return WorkItemTally; + } + + void AggregateTallyFromChildNode(const UTOPNode* InChildNode) + { + if (IsValid(InChildNode)) + AggregatedWorkItemTally.Add(InChildNode->GetWorkItemTally()); + } + + bool AreAllWorkItemsComplete() const { return GetWorkItemTally().AreAllWorkItemsComplete(); }; + bool AnyWorkItemsFailed() const { return GetWorkItemTally().AnyWorkItemsFailed(); }; + bool AnyWorkItemsPending() const { return GetWorkItemTally().AnyWorkItemsPending(); }; + void ZeroWorkItemTally() + { + WorkItemTally.ZeroAll(); + AggregatedWorkItemTally.ZeroAll(); + } + + // Called by PDG manager when work item events are received + + // Notification that a work item has been created + void OnWorkItemCreated(int32 InWorkItemID) { }; + + // Notification that a work item has been removed. + void OnWorkItemRemoved(int32 InWorkItemID) { WorkItemTally.RemoveWorkItem(InWorkItemID); }; + + // Notification that a work item has moved to the waiting state. + void OnWorkItemWaiting(int32 InWorkItemID); + + // Notification that a work item has been scheduled. + void OnWorkItemScheduled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsScheduled(InWorkItemID); }; + + // Notification that a work item has started cooking. + void OnWorkItemCooking(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCooking(InWorkItemID); }; + + // Notification that a work item has been cooked. + void OnWorkItemCooked(int32 InWorkItemID); + + // Notification that a work item has errored. + void OnWorkItemErrored(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsErrored(InWorkItemID); }; + + // Notification that a work item cook has been cancelled. + void OnWorkItemCookCancelled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCookCancelled(InWorkItemID); }; + + bool IsVisibleInLevel() const { return bShow; } + void SetVisibleInLevel(bool bInVisible); + void UpdateOutputVisibilityInLevel(); + + // Sets all WorkResultObjects that are in the NotLoaded state to ToLoad. + void SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad=false); + + // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output + // actors). + void SetLoadedWorkResultsToDelete(); + + // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output + // objects and actors and sets the state to Deleted. + void DeleteWorkResultOutputObjects(); + + // Get the OutputActor owner struct + FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } + + // Get the OutputActor owner struct + const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } + + // Get the baked outputs from the last bake. The map keys are [work_result.work_item_index]_[work_result_object_index] + TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } + const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } + // Helper to construct the key used to look up baked work results. + static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex); + // Helper to construct the key used to look up baked work results. + static FString GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex); + // Helper to construct the key used to look up baked work results. + bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const; + // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object. + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); + // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object (const version). + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; + + // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. + int32 IndexOfWorkResultByID(const int32& InWorkItemID); + // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. + FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID); + // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. + // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. + int32 IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); + // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. + // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. + FTOPWorkResult* GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); + // Return the FTOPWorkResult at InArrayIndex in the WorkResult array, or nullptr if InArrayIndex is not a valid index. + FTOPWorkResult* GetWorkResultByArrayIndex(const int32& InArrayIndex); + + // Returns true if InNetwork is the parent TOP Net of this node. + bool IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const; + + // Returns true if this node can still be auto-baked + bool CanStillBeAutoBaked() const; + +#if WITH_EDITOR + void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif + +#if WITH_EDITOR + void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; +#endif + +public: + + UPROPERTY(Transient, NonTransactional) + int32 NodeId; + UPROPERTY(NonTransactional) + FString NodeName; + UPROPERTY(NonTransactional) + FString NodePath; + UPROPERTY(NonTransactional) + FString ParentName; + + UPROPERTY() + UObject* WorkResultParent; + UPROPERTY(NonTransactional) + TArray WorkResult; + + // Hidden in the nodes combobox + UPROPERTY() + bool bHidden; + UPROPERTY() + bool bAutoLoad; + + UPROPERTY(Transient, NonTransactional) + EPDGNodeState NodeState; + + // This is set when the TOP node's work items are processed by + // FHoudiniPDGManager based on if any NotLoaded work result objects are found + UPROPERTY(NonTransactional) + bool bCachedHaveNotLoadedWorkResults; + + // This is set when the TOP node's work items are processed by + // FHoudiniPDGManager based on if any Loaded work result objects are found + UPROPERTY(NonTransactional) + bool bCachedHaveLoadedWorkResults; + + // True if this node has child nodes + UPROPERTY(NonTransactional) + bool bHasChildNodes; + + // These notification events have been introduced so that we can start encapsulating code. + // in this class as opposed to modifying this object in various places throughout the codebase. + + // Notification that this TOP node has been dirtied. + void OnDirtyNode(); + + // Accessors for the landscape data caches + FHoudiniLandscapeExtent& GetLandscapeExtent() { return LandscapeExtent; } + FHoudiniLandscapeReferenceLocation& GetLandscapeReferenceLocation() { return LandscapeReferenceLocation; } + FHoudiniLandscapeTileSizeInfo& GetLandscapeSizeInfo() { return LandscapeSizeInfo; } + // More cached landscape data + UPROPERTY() + TSet ClearedLandscapeLayers; + + // Returns true if the node has received the HAPI_PDG_EVENT_COOK_COMPLETE event since the last the cook started + bool HasReceivedCookCompleteEvent() const { return bHasReceivedCookCompleteEvent; } + // Handler for when the node receives the HAPI_PDG_EVENT_COOK_START (called for each node when a TOPNet starts cooking) + void HandleOnPDGEventCookStart() { bHasReceivedCookCompleteEvent = false; } + // Handler for when the node receives the HAPI_PDG_EVENT_COOK_COMPLETE event (called for each node when a TOPNet completes cooking) + void HandleOnPDGEventCookComplete() { bHasReceivedCookCompleteEvent = true; } + +protected: + void InvalidateLandscapeCache(); + + // Value caches used during landscape tile creation. + FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; + FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; + FHoudiniLandscapeExtent LandscapeExtent; + + // Visible in the level + UPROPERTY() + bool bShow; + + // Map of [work_result_index]_[work_result_object_index] to the work result object's baked outputs. + UPROPERTY() + TMap BakedWorkResultObjectOutputs; + + // This node's own work items, used when bHasChildNodes is false. + UPROPERTY(Transient, NonTransactional) + FWorkItemTally WorkItemTally; + // This node's aggregated work item tallys (sum of child work item tally, use when bHasChildNodes is true) + UPROPERTY(Transient, NonTransactional) + FAggregatedWorkItemTally AggregatedWorkItemTally; + + // Set to true when the node recieves HAPI_PDG_EVENT_COOK_COMPLETE event + UPROPERTY(Transient, NonTransactional) + bool bHasReceivedCookCompleteEvent; + +private: + UPROPERTY() + FOutputActorOwner OutputActorOwner; +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject +{ + GENERATED_BODY() + +public: + + // Delegate that is broadcast when cook of the network is complete. Parameters are the UTOPNetwork and bAnyFailedWorkItems. + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostCookDelegate, UTOPNetwork*, const bool); + + // Constructor + UTOPNetwork(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const UTOPNetwork& Other) const; + + // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output + // actors). + void SetLoadedWorkResultsToDelete(); + + // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output + // objects and actors and sets the state to Deleted. + void DeleteWorkResultOutputObjects(); + + // Returns true if any node in this TOP net has pending (waiting, scheduled, cooking) work items. + bool AnyWorkItemsPending() const; + + // Returns true if any node in this TOP net has failed/errored work items. + bool AnyWorkItemsFailed() const; + + // Returns true if this network has nodes that can still be auto-baked + bool CanStillBeAutoBaked() const; + + // Handler for when a node in the newtork receives the HAPI_PDG_EVENT_COOK_COMPLETE event (called for each node when a TOPNet completes cooking) + void HandleOnPDGEventCookCompleteReceivedByChildNode(UHoudiniPDGAssetLink* const InAssetLink, UTOPNode* const InTOPNode); + + FOnPostCookDelegate& GetOnPostCookDelegate() { return OnPostCookDelegate; } + +public: + + UPROPERTY(Transient, NonTransactional) + int32 NodeId; + UPROPERTY(NonTransactional) + FString NodeName; + // HAPI path to this node (relative to the HDA) + UPROPERTY(NonTransactional) + FString NodePath; + + UPROPERTY() + TArray AllTOPNodes; + + // TODO: Should be using SelectedNodeName instead? + // Index is not consistent after updating filter + UPROPERTY() + int32 SelectedTOPIndex; + + UPROPERTY(NonTransactional) + FString ParentName; + + UPROPERTY() + bool bShowResults; + UPROPERTY() + bool bAutoLoadResults; + + FOnPostCookDelegate OnPostCookDelegate; +}; + + +class UHoudiniPDGAssetLink; +DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemHAPIIndex*/, int32 /*WorkItemResultInfoIndex*/); + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + friend class UHoudiniAssetComponent; + + // Delegate for when the entire bake operation is complete (all selected nodes/networks have been baked). + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostBakeDelegate, UHoudiniPDGAssetLink*, const bool); + // Delegate for when a network completes a cook. Passes the asset link, the network, a bAnyWorkItemsFailed. + DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnPostTOPNetworkCookDelegate, UHoudiniPDGAssetLink*, UTOPNetwork*, const bool); + + static FString GetAssetLinkStatus(const EPDGLinkState& InLinkState); + static FString GetTOPNodeStatus(const UTOPNode* InTOPNode); + static FLinearColor GetTOPNodeStatusColor(const UTOPNode* InTOPNode); + + void UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork); + void UpdateWorkItemTally(); + static void ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork); + + // Set the TOP network at the given index as currently selected TOP network + void SelectTOPNetwork(const int32& AtIndex); + // Set the TOP node at the given index in the given TOP network as currently selected TOP node + void SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex); + + UTOPNode* GetSelectedTOPNode(); + const UTOPNode* GetSelectedTOPNode() const; + UTOPNetwork* GetSelectedTOPNetwork(); + const UTOPNetwork* GetSelectedTOPNetwork() const; + + FString GetSelectedTOPNodeName(); + FString GetSelectedTOPNetworkName(); + + UTOPNode* GetTOPNode(const int32& InNodeID); + bool GetTOPNodeAndNetworkByNodeId(const int32& InNodeID, UTOPNetwork*& OutNetwork, UTOPNode*& OutNode); + UTOPNetwork* GetTOPNetwork(const int32& AtIndex); + const UTOPNetwork* GetTOPNetwork(const int32& AtIndex) const; + + // Find the node with relative path 'InNodePath' from its topnet. + static UTOPNode* GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex); + // Find the network with relative path 'InNetPath' from the HDA + static UTOPNetwork* GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex); + + // Get the parent TOP node of the specified node. This is resolved + UTOPNode* GetParentTOPNode(const UTOPNode* InNode); + + static void ClearTOPNodeWorkItemResults(UTOPNode* TOPNode); + static void ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork); + // Clear the result objects of a work item (FTOPWorkResult.ResultObjects), but don't delete the work item from + // TOPNode.WorkResults (for example, the work item was dirtied but not removed from PDG) + static void ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + // Calls ClearWorkItemResultByID and then deletes the FTOPWorkResult from InTOPNode.Result as well. For example: + // the work item was removed in PDG. + static void DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + static FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + + // This should be called after the owner and this PDG asset link is duplicated. Set all output parent actors to + // null in all TOP networks/nodes. Since the TOP Networks/TOP nodes are all structs, we cannot set + // DuplicateTransient property on their OutputActor properties. + void UpdatePostDuplicate(); + + // Load the geometry generated as results of the given work item, of the given TOP node. + // The load will be done asynchronously. + // Results must be tagged with 'file', and must have a file path, otherwise will not be loaded. + //void LoadResults(FTOPNode TOPNode, HAPI_PDG_WorkitemInfo workItemInfo, HAPI_PDG_WorkitemResultInfo[] resultInfos, HAPI_PDG_WorkitemId workItemID) + + // Return the first UHoudiniAssetComponent in the parent chain. If this asset link is not + // owned by a HoudiniAssetComponent, a nullptr will be returned. + UHoudiniAssetComponent* GetOuterHoudiniAssetComponent() const; + + // Gets the temporary cook folder. If the parent of this asset link is a HoudiniAssetComponent use that, otherwise + // use the default static mesh temporary cook folder. + FDirectoryPath GetTemporaryCookFolder() const; + + // Get the actor that owns this PDG asset link. If the asset link is owned by a component, + // then the component's owning actor is returned. Can return null if this is now owned by + // an actor or component. + AActor* GetOwnerActor() const; + + // Checks if the asset link has any temporary outputs and returns true if it has + bool HasTemporaryOutputs() const; + + // Filter TOP nodes and outputs (hidden/visible) by TOPNodeFilter and TOPOutputFilter. + void FilterTOPNodesAndOutputs(); + + // On all FTOPNodes: Load not loaded items if bAutoload is true, and update the level visibility of work items + // result. Used when FTOPNode.bShow and/or FTOPNode.bAutoload changed. + void UpdateTOPNodeAutoloadAndVisibility(); + +#if WITH_EDITORONLY_DATA + // Returns true if there are any nodes left that can/must still be auto-baked. + bool AnyRemainingAutoBakeNodes() const; +#endif + + // Delegate handlers + + // Get the post bake delegate + FOnPostBakeDelegate& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } + + // Called by baking code after baking all of the outputs + void HandleOnPostBake(const bool bInSuccess); + + FOnPostTOPNetworkCookDelegate& GetOnPostTOPNetworkCookDelegate() { return OnPostTOPNetworkCookDelegate; } + + // Handler for when a TOP network completes a cook. Called by the TOP Net once all of its nodes have received + // HAPI_PDG_EVENT_COOK_COMPLETE. + void HandleOnTOPNetworkCookComplete(UTOPNetwork* const InTOPNet); + +#if WITH_EDITORONLY_DATA + void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif + +#if WITH_EDITORONLY_DATA + void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; +#endif + +private: + + void ClearAllTOPData(); + + static void DestroyWorkItemResultData(FTOPWorkResult& Result); + + static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); + +public: + + //UPROPERTY() + //UHoudiniAsset* HoudiniAsset; + + //UPROPERTY() + //UHoudiniAssetComponent* ParentHAC; + + UPROPERTY(DuplicateTransient, NonTransactional) + FString AssetName; + + // The full path to the HDA in HAPI + UPROPERTY(DuplicateTransient, NonTransactional) + FString AssetNodePath; + + UPROPERTY(DuplicateTransient, NonTransactional) + int32 AssetID; + + UPROPERTY() + TArray AllTOPNetworks; + + UPROPERTY() + int32 SelectedTOPNetworkIndex; + + UPROPERTY(Transient, NonTransactional) + EPDGLinkState LinkState; + + UPROPERTY() + bool bAutoCook; + UPROPERTY() + bool bUseTOPNodeFilter; + UPROPERTY() + bool bUseTOPOutputFilter; + UPROPERTY() + FString TOPNodeFilter; + UPROPERTY() + FString TOPOutputFilter; + + UPROPERTY(NonTransactional) + int32 NumWorkitems; + UPROPERTY(Transient, NonTransactional) + FAggregatedWorkItemTally WorkItemTally; + + UPROPERTY() + FString OutputCachePath; + + UPROPERTY(Transient) + bool bNeedsUIRefresh; + + // A parent actor to serve as the parent of any output actors + // that are created. + // If null, then output actors are created under a folder + UPROPERTY(EditAnywhere, Category="Output") + AActor* OutputParentActor; + + // Folder used for baking PDG outputs + UPROPERTY() + FDirectoryPath BakeFolder; + + // + // Notifications + // + + // Delegate that is broadcast when a work result object has been loaded + FHoudiniPDGAssetLinkWorkResultObjectLoaded OnWorkResultObjectLoaded; + + // Delegate that is broadcast after a bake. + FOnPostBakeDelegate OnPostBakeDelegate; + + // Delegate that is broadcast after a TOP Network completes a cook. + FOnPostTOPNetworkCookDelegate OnPostTOPNetworkCookDelegate; + + // + // End: Notifications + // + +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bBakeMenuExpanded; + + // What kind of output to bake, for example, bake actors, bake to blueprint + UPROPERTY() + EHoudiniEngineBakeOption HoudiniEngineBakeOption; + + // Which outputs to bake, for example, all, selected network, selected node + UPROPERTY() + EPDGBakeSelectionOption PDGBakeSelectionOption; + + // This determines if the baked assets should replace existing assets with the same name, + // or always generate new assets (with numerical suffixes if needed to create unique names) + UPROPERTY() + EPDGBakePackageReplaceModeOption PDGBakePackageReplaceMode; + + // If true, recenter baked actors to their bounding box center after bake + UPROPERTY() + bool bRecenterBakedActors; + + // Auto-bake: if this is true, it indicates that once all work result objects for the node is loaded they should + // all be baked + UPROPERTY() + bool bBakeAfterAllWorkResultObjectsLoaded; + + // The delegate handle of the auto bake helper function bound to OnWorkResultObjectLoaded. + FDelegateHandle AutoBakeDelegateHandle; +#endif +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp index c2d744215..068fd82f8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp @@ -1,258 +1,258 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameter.h" - -UHoudiniParameter::UHoudiniParameter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , ParmType(EHoudiniParameterType::Invalid) - , TupleSize(0) - , NodeId(-1) - , ParmId(-1) - , ParentParmId(-1) - , ChildIndex(-1) - , bIsVisible(true) - , bIsDisabled(false) - , bHasChanged(false) - , bNeedsToTriggerUpdate(true) - , bIsDefault(false) - , bIsSpare(false) - , bJoinNext(false) - , bIsChildOfMultiParm(false) - , bIsDirectChildOfMultiParm(false) - , TagCount(0) - , ValueIndex(-1) - , bHasExpression(false) - , bShowExpression(false) -{ - Name = TEXT(""); - Label = TEXT(""); - Help = TEXT(""); - -} - -UHoudiniParameter * -UHoudiniParameter::Create( UObject* InOuter, const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameter_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameter::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameter * HoudiniAssetParameter = NewObject< UHoudiniParameter >( - InOuter, UHoudiniParameter::StaticClass(), ParamName, RF_Public | RF_Transactional); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameter::IsChildParameter() const -{ - return ParentParmId >= 0; -} - -void -UHoudiniParameter::RevertToDefault() -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); -} - -void -UHoudiniParameter::RevertToDefault(const int32& InAtTupleIndex) -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.AddUnique(InAtTupleIndex); - - MarkChanged(true); -} - -void -UHoudiniParameter::MarkDefault(const bool& bInDefault) -{ - bIsDefault = bInDefault; - - if (bInDefault) - { - // No need to revert default parameter - bPendingRevertToDefault = false; - TuplePendingRevertToDefault.Empty(); - } -} - -EHoudiniRampInterpolationType UHoudiniParameter::GetHoudiniInterpMethodFromInt(int32 InInt) -{ - EHoudiniRampInterpolationType Result = EHoudiniRampInterpolationType::InValid; - switch (InInt) - { - case 0: - Result = EHoudiniRampInterpolationType::CONSTANT; - break; - case 1: - Result = EHoudiniRampInterpolationType::LINEAR; - break; - case 2: - Result = EHoudiniRampInterpolationType::CATMULL_ROM; - break; - case 3: - Result = EHoudiniRampInterpolationType::MONOTONE_CUBIC; - break; - case 4: - Result = EHoudiniRampInterpolationType::BEZIER; - break; - case 5: - Result = EHoudiniRampInterpolationType::BSPLINE; - break; - case 6: - Result = EHoudiniRampInterpolationType::HERMITE; - break; - } - - return Result; -} - -FString -UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType) -{ - FString Result = FString("InValid"); - switch (InType) - { - case EHoudiniRampInterpolationType::CONSTANT: - Result = FString("Constant"); - break; - case EHoudiniRampInterpolationType::LINEAR: - Result = FString("Linear"); - break; - case EHoudiniRampInterpolationType::BEZIER: - Result = FString("Bezier"); - break; - case EHoudiniRampInterpolationType::BSPLINE: - Result = FString("B-Spline"); - break; - case EHoudiniRampInterpolationType::MONOTONE_CUBIC: - Result = FString("Monotone Cubic"); - break; - case EHoudiniRampInterpolationType::CATMULL_ROM: - Result = FString("Catmull Rom"); - break; - case EHoudiniRampInterpolationType::HERMITE: - Result = FString("Hermite"); - break; - } - - return Result; -} -EHoudiniRampInterpolationType -UHoudiniParameter::GetHoudiniInterpMethodFromString(const FString& InString) -{ - if (InString.StartsWith(TEXT("Constant"))) - return EHoudiniRampInterpolationType::CONSTANT; - - else if (InString.StartsWith("Linear")) - return EHoudiniRampInterpolationType::LINEAR; - - else if (InString.StartsWith("Bezier")) - return EHoudiniRampInterpolationType::BEZIER; - - else if (InString.StartsWith("B-Spline")) - return EHoudiniRampInterpolationType::BSPLINE; - - else if (InString.StartsWith("Monotone Cubic")) - return EHoudiniRampInterpolationType::MONOTONE_CUBIC; - - else if (InString.StartsWith("Catmull Rom")) - return EHoudiniRampInterpolationType::CATMULL_ROM; - - else if (InString.StartsWith("Hermite")) - return EHoudiniRampInterpolationType::HERMITE; - - return EHoudiniRampInterpolationType::InValid; -} - - - -ERichCurveInterpMode -UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType) -{ - switch (InType) - { - case EHoudiniRampInterpolationType::CONSTANT: - return ERichCurveInterpMode::RCIM_Constant; - - case EHoudiniRampInterpolationType::LINEAR: - return ERichCurveInterpMode::RCIM_Linear; - - default: - break; - } - - return ERichCurveInterpMode::RCIM_Cubic; -} - -UHoudiniParameter* -UHoudiniParameter::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags, EObjectFlags SetFlags) -{ - UHoudiniParameter* NewParameter = Cast(StaticDuplicateObject(this, DestOuter)); - - NewParameter->CopyStateFrom(this, false); - - return NewParameter; -} - -void -UHoudiniParameter::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - - NodeId = InParameter->NodeId; - ParmId = InParameter->ParmId; - ParentParmId = InParameter->ParentParmId; - bIsDefault = InParameter->bIsDefault; - bPendingRevertToDefault = InParameter->bPendingRevertToDefault; - TuplePendingRevertToDefault = InParameter->TuplePendingRevertToDefault; - bShowExpression = InParameter->bShowExpression; -} - -void -UHoudiniParameter::InvalidateData() -{ - -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameter.h" + +UHoudiniParameter::UHoudiniParameter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , ParmType(EHoudiniParameterType::Invalid) + , TupleSize(0) + , NodeId(-1) + , ParmId(-1) + , ParentParmId(-1) + , ChildIndex(-1) + , bIsVisible(true) + , bIsDisabled(false) + , bHasChanged(false) + , bNeedsToTriggerUpdate(true) + , bIsDefault(false) + , bIsSpare(false) + , bJoinNext(false) + , bIsChildOfMultiParm(false) + , bIsDirectChildOfMultiParm(false) + , TagCount(0) + , ValueIndex(-1) + , bHasExpression(false) + , bShowExpression(false) +{ + Name = TEXT(""); + Label = TEXT(""); + Help = TEXT(""); + +} + +UHoudiniParameter * +UHoudiniParameter::Create( UObject* InOuter, const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameter_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameter::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameter * HoudiniAssetParameter = NewObject< UHoudiniParameter >( + InOuter, UHoudiniParameter::StaticClass(), ParamName, RF_Public | RF_Transactional); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameter::IsChildParameter() const +{ + return ParentParmId >= 0; +} + +void +UHoudiniParameter::RevertToDefault() +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); +} + +void +UHoudiniParameter::RevertToDefault(const int32& InAtTupleIndex) +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.AddUnique(InAtTupleIndex); + + MarkChanged(true); +} + +void +UHoudiniParameter::MarkDefault(const bool& bInDefault) +{ + bIsDefault = bInDefault; + + if (bInDefault) + { + // No need to revert default parameter + bPendingRevertToDefault = false; + TuplePendingRevertToDefault.Empty(); + } +} + +EHoudiniRampInterpolationType UHoudiniParameter::GetHoudiniInterpMethodFromInt(int32 InInt) +{ + EHoudiniRampInterpolationType Result = EHoudiniRampInterpolationType::InValid; + switch (InInt) + { + case 0: + Result = EHoudiniRampInterpolationType::CONSTANT; + break; + case 1: + Result = EHoudiniRampInterpolationType::LINEAR; + break; + case 2: + Result = EHoudiniRampInterpolationType::CATMULL_ROM; + break; + case 3: + Result = EHoudiniRampInterpolationType::MONOTONE_CUBIC; + break; + case 4: + Result = EHoudiniRampInterpolationType::BEZIER; + break; + case 5: + Result = EHoudiniRampInterpolationType::BSPLINE; + break; + case 6: + Result = EHoudiniRampInterpolationType::HERMITE; + break; + } + + return Result; +} + +FString +UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType) +{ + FString Result = FString("InValid"); + switch (InType) + { + case EHoudiniRampInterpolationType::CONSTANT: + Result = FString("Constant"); + break; + case EHoudiniRampInterpolationType::LINEAR: + Result = FString("Linear"); + break; + case EHoudiniRampInterpolationType::BEZIER: + Result = FString("Bezier"); + break; + case EHoudiniRampInterpolationType::BSPLINE: + Result = FString("B-Spline"); + break; + case EHoudiniRampInterpolationType::MONOTONE_CUBIC: + Result = FString("Monotone Cubic"); + break; + case EHoudiniRampInterpolationType::CATMULL_ROM: + Result = FString("Catmull Rom"); + break; + case EHoudiniRampInterpolationType::HERMITE: + Result = FString("Hermite"); + break; + } + + return Result; +} +EHoudiniRampInterpolationType +UHoudiniParameter::GetHoudiniInterpMethodFromString(const FString& InString) +{ + if (InString.StartsWith(TEXT("Constant"))) + return EHoudiniRampInterpolationType::CONSTANT; + + else if (InString.StartsWith("Linear")) + return EHoudiniRampInterpolationType::LINEAR; + + else if (InString.StartsWith("Bezier")) + return EHoudiniRampInterpolationType::BEZIER; + + else if (InString.StartsWith("B-Spline")) + return EHoudiniRampInterpolationType::BSPLINE; + + else if (InString.StartsWith("Monotone Cubic")) + return EHoudiniRampInterpolationType::MONOTONE_CUBIC; + + else if (InString.StartsWith("Catmull Rom")) + return EHoudiniRampInterpolationType::CATMULL_ROM; + + else if (InString.StartsWith("Hermite")) + return EHoudiniRampInterpolationType::HERMITE; + + return EHoudiniRampInterpolationType::InValid; +} + + + +ERichCurveInterpMode +UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType) +{ + switch (InType) + { + case EHoudiniRampInterpolationType::CONSTANT: + return ERichCurveInterpMode::RCIM_Constant; + + case EHoudiniRampInterpolationType::LINEAR: + return ERichCurveInterpMode::RCIM_Linear; + + default: + break; + } + + return ERichCurveInterpMode::RCIM_Cubic; +} + +UHoudiniParameter* +UHoudiniParameter::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags, EObjectFlags SetFlags) +{ + UHoudiniParameter* NewParameter = Cast(StaticDuplicateObject(this, DestOuter)); + + NewParameter->CopyStateFrom(this, false); + + return NewParameter; +} + +void +UHoudiniParameter::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + + NodeId = InParameter->NodeId; + ParmId = InParameter->ParmId; + ParentParmId = InParameter->ParentParmId; + bIsDefault = InParameter->bIsDefault; + bPendingRevertToDefault = InParameter->bPendingRevertToDefault; + TuplePendingRevertToDefault = InParameter->TuplePendingRevertToDefault; + bShowExpression = InParameter->bShowExpression; +} + +void +UHoudiniParameter::InvalidateData() +{ + +} + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h index 45214ff43..5b75c5dd2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h @@ -1,325 +1,325 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/Object.h" -#include "Curves/RealCurve.h" -#include "HoudiniInput.h" - -#include "HoudiniParameter.generated.h" - -UENUM() -enum class EHoudiniParameterType : uint8 -{ - Invalid, - - Button, - ButtonStrip, - Color, - ColorRamp, - File, - FileDir, - FileGeo, - FileImage, - Float, - FloatRamp, - Folder, - FolderList, - Input, - Int, - IntChoice, - Label, - MultiParm, - Separator, - String, - StringChoice, - StringAssetRef, - Toggle, -}; - -UENUM() -enum class EHoudiniRampInterpolationType : int8 -{ - InValid = -1, - - CONSTANT = 0, - LINEAR = 1, - CATMULL_ROM = 2, - MONOTONE_CUBIC = 3, - BEZIER = 4, - BSPLINE = 5, - HERMITE = 6 -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject -{ - -public: - - GENERATED_UCLASS_BODY() - - friend class UHoudiniAssetParameter; - - // - static UHoudiniParameter * Create(UObject* Outer, const FString& ParamName); - - // Equality, consider two param equals if they have the same name, type, tuple size and disabled status - bool operator==(const UHoudiniParameter& other) const - { - return ( TupleSize == other.TupleSize && ParmType == other.ParmType - && Name.Equals(other.Name) && bIsDisabled == other.bIsDisabled ); - } - - bool Matches(const UHoudiniParameter& other) const { return (*this == other); }; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - // Get parent parameter for this parameter. - virtual const FString & GetParameterName() const { return Name; }; - virtual const FString & GetParameterLabel() const { return Label; }; - virtual const FString GetParameterHelp() const { return Help; }; - - virtual EHoudiniParameterType GetParameterType() const { return ParmType; }; - virtual int32 GetTupleSize() const { return TupleSize; }; - virtual int32 GetNodeId() const { return NodeId; }; - virtual int32 GetParmId() const { return ParmId; }; - virtual int32 GetParentParmId() const { return ParentParmId; }; - virtual int32 GetChildIndex() const { return ChildIndex; }; - - virtual bool IsVisible() const { return bIsVisible; }; - virtual bool ShouldDisplay() const{ return bIsVisible && ParmType != EHoudiniParameterType::Invalid; }; - virtual bool IsDisabled() const { return bIsDisabled; }; - virtual bool HasChanged() const { return bHasChanged; }; - virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; - virtual bool IsDefault() const { return true; }; - virtual bool IsSpare() const { return bIsSpare; }; - virtual bool GetJoinNext() const { return bJoinNext; }; - virtual bool IsAutoUpdate() const { return bAutoUpdate; }; - - virtual int32 GetTagCount() const { return TagCount; }; - virtual int32 GetValueIndex() const { return ValueIndex; }; - - virtual TMap& GetTags() { return Tags; }; - - virtual bool IsChildParameter() const; - - virtual bool IsPendingRevertToDefault() const { return bPendingRevertToDefault; }; - virtual void GetTuplePendingRevertToDefault(TArray& OutArray) { OutArray = TuplePendingRevertToDefault; }; - - virtual bool HasExpression() const { return bHasExpression; }; - virtual bool IsShowingExpression() const { return bShowExpression; }; - virtual FString GetExpression() const { return ParamExpression; }; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - - // Set parent parameter for this parameter. - virtual void SetParameterName(const FString& InName) { Name = InName; }; - virtual void SetParameterLabel(const FString& InLabel) { Label = InLabel; }; - virtual void SetParameterHelp(const FString& InHelp) { Help = InHelp; }; - - virtual void SetParameterType(const EHoudiniParameterType& InType) { ParmType = InType; }; - virtual void SetTupleSize(const uint32& InTupleSize) { TupleSize = InTupleSize; }; - virtual void SetNodeId(const int32& InNodeId) { NodeId = InNodeId; }; - virtual void SetParmId(const int32& InParmId) { ParmId = InParmId; }; - virtual void SetParentParmId(const int32& InParentParmId) { ParentParmId = InParentParmId; }; - virtual void SetChildIndex(const int32& InChildIndex) { ChildIndex = InChildIndex; }; - - virtual void SetIsChildOfMultiParm(const bool& IsChildOfMultiParam) { bIsChildOfMultiParm = IsChildOfMultiParam; }; - virtual bool GetIsChildOfMultiParm() const { return bIsChildOfMultiParm; }; - - virtual void SetIsDirectChildOfMultiParm(const bool& IsDirectChildOfMultiParam) { bIsDirectChildOfMultiParm = IsDirectChildOfMultiParam; }; - virtual bool IsDirectChildOfMultiParm() const { return bIsDirectChildOfMultiParm; }; - - virtual void SetVisible(const bool& InIsVisible) { bIsVisible = InIsVisible; }; - virtual void SetDisabled(const bool& InIsDisabled) { bIsDisabled = InIsDisabled; }; - virtual void SetDefault(const bool& InIsDefault) { bIsDefault = InIsDefault; }; - virtual void SetSpare(const bool& InIsSpare) { bIsSpare = InIsSpare; }; - virtual void SetJoinNext(const bool& InJoinNext) { bJoinNext = InJoinNext; }; - - virtual void SetTagCount(const uint32& InTagCount) { TagCount = InTagCount; }; - virtual void SetValueIndex(const uint32& InValueIndex) { ValueIndex = InValueIndex; }; - - virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - virtual void RevertToDefault(); - virtual void RevertToDefault(const int32& TupleIndex); - virtual void MarkDefault(const bool& bInDefault); - - virtual void SetHasExpression(const bool& InHasExpression) { bHasExpression = InHasExpression; }; - virtual void SetShowExpression(const bool& InShowExpression) { bShowExpression = InShowExpression; }; - virtual void SetExpression(const FString& InParamExpression) { ParamExpression = InParamExpression; }; - - virtual void SetAutoUpdate(const bool& InAutoUpdate) { bAutoUpdate = InAutoUpdate; }; - - static FString GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType); - static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromInt(int32 InInt); - static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromString(const FString& InString); - - static ERichCurveInterpMode EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType); - - // Duplicate this object for state transfer between component instances and templates - UHoudiniParameter* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - // Replace any input references using the provided mapping - virtual void RemapInputs(const TMap& InputMapping) {}; - - // Replace any parameter references using the provided mapping - virtual void RemapParameters(const TMap& ParameterMapping) {}; - - // Invalidate ids - virtual void InvalidateData(); - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - virtual void OnPreCook() {}; - -protected: - - //--------------------------------------------------------------------------------------------- - // ParmInfos - //--------------------------------------------------------------------------------------------- - - // - UPROPERTY() - FString Name; - - // - UPROPERTY() - FString Label; - - // Unreal type of the parameter - UPROPERTY() - EHoudiniParameterType ParmType; - - // Tuple size. For scalar parameters this value is 1, but for vector parameters this value can be greater. - UPROPERTY() - uint32 TupleSize; - - // Node this parameter belongs to. - UPROPERTY(DuplicateTransient) - int32 NodeId; - - // Id of this parameter. - UPROPERTY(DuplicateTransient) - int32 ParmId; - - // Id of parent parameter, -1 if root is parent. - UPROPERTY(DuplicateTransient) - int32 ParentParmId; - - // Child index within its parent parameter. - UPROPERTY() - int32 ChildIndex; - - // - UPROPERTY() - bool bIsVisible; - - // - UPROPERTY() - bool bIsDisabled; - - // Is set to true if value of this parameter has been changed by user. - UPROPERTY() - bool bHasChanged; - - // Is set to true if value of this parameter will trigger an update of the asset - UPROPERTY() - bool bNeedsToTriggerUpdate; - - // Indicates that this parameter is still using its default value - UPROPERTY(Transient, DuplicateTransient) - bool bIsDefault; - - // Permissions for file parms - UPROPERTY() - bool bIsSpare; - - // - UPROPERTY() - bool bJoinNext; - - // - UPROPERTY() - bool bIsChildOfMultiParm; - - UPROPERTY() - bool bIsDirectChildOfMultiParm; - - // Indicates a parameter value needs to be reverted to its default - UPROPERTY(DuplicateTransient) - bool bPendingRevertToDefault; - - UPROPERTY(DuplicateTransient) - TArray TuplePendingRevertToDefault; - - // - UPROPERTY() - FString Help; - - // Number of tags on this parameter - UPROPERTY() - uint32 TagCount; - - // The index to use to look into the values array in order to retrieve the actual value(s) of this parameter. - UPROPERTY() - int32 ValueIndex; - - //------------------------------------------------------------------------------------------------------------------------- - // Expression - // TODO: Use tuple array for this - // Indicates the parameters has an expression value - UPROPERTY() - bool bHasExpression; - - // Indicates we are currently displaying the parameter's value - UPROPERTY(DuplicateTransient) - bool bShowExpression; - - // The parameter's expression - UPROPERTY() - FString ParamExpression; - - UPROPERTY() - TMap Tags; - - UPROPERTY() - bool bAutoUpdate = true; - - -}; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/Object.h" +#include "Curves/RealCurve.h" +#include "HoudiniInput.h" + +#include "HoudiniParameter.generated.h" + +UENUM() +enum class EHoudiniParameterType : uint8 +{ + Invalid, + + Button, + ButtonStrip, + Color, + ColorRamp, + File, + FileDir, + FileGeo, + FileImage, + Float, + FloatRamp, + Folder, + FolderList, + Input, + Int, + IntChoice, + Label, + MultiParm, + Separator, + String, + StringChoice, + StringAssetRef, + Toggle, +}; + +UENUM() +enum class EHoudiniRampInterpolationType : int8 +{ + InValid = -1, + + CONSTANT = 0, + LINEAR = 1, + CATMULL_ROM = 2, + MONOTONE_CUBIC = 3, + BEZIER = 4, + BSPLINE = 5, + HERMITE = 6 +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject +{ + +public: + + GENERATED_UCLASS_BODY() + + friend class UHoudiniAssetParameter; + + // + static UHoudiniParameter * Create(UObject* Outer, const FString& ParamName); + + // Equality, consider two param equals if they have the same name, type, tuple size and disabled status + bool operator==(const UHoudiniParameter& other) const + { + return ( TupleSize == other.TupleSize && ParmType == other.ParmType + && Name.Equals(other.Name) && bIsDisabled == other.bIsDisabled ); + } + + bool Matches(const UHoudiniParameter& other) const { return (*this == other); }; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + // Get parent parameter for this parameter. + virtual const FString & GetParameterName() const { return Name; }; + virtual const FString & GetParameterLabel() const { return Label; }; + virtual const FString GetParameterHelp() const { return Help; }; + + virtual EHoudiniParameterType GetParameterType() const { return ParmType; }; + virtual int32 GetTupleSize() const { return TupleSize; }; + virtual int32 GetNodeId() const { return NodeId; }; + virtual int32 GetParmId() const { return ParmId; }; + virtual int32 GetParentParmId() const { return ParentParmId; }; + virtual int32 GetChildIndex() const { return ChildIndex; }; + + virtual bool IsVisible() const { return bIsVisible; }; + virtual bool ShouldDisplay() const{ return bIsVisible && ParmType != EHoudiniParameterType::Invalid; }; + virtual bool IsDisabled() const { return bIsDisabled; }; + virtual bool HasChanged() const { return bHasChanged; }; + virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; + virtual bool IsDefault() const { return true; }; + virtual bool IsSpare() const { return bIsSpare; }; + virtual bool GetJoinNext() const { return bJoinNext; }; + virtual bool IsAutoUpdate() const { return bAutoUpdate; }; + + virtual int32 GetTagCount() const { return TagCount; }; + virtual int32 GetValueIndex() const { return ValueIndex; }; + + virtual TMap& GetTags() { return Tags; }; + + virtual bool IsChildParameter() const; + + virtual bool IsPendingRevertToDefault() const { return bPendingRevertToDefault; }; + virtual void GetTuplePendingRevertToDefault(TArray& OutArray) { OutArray = TuplePendingRevertToDefault; }; + + virtual bool HasExpression() const { return bHasExpression; }; + virtual bool IsShowingExpression() const { return bShowExpression; }; + virtual FString GetExpression() const { return ParamExpression; }; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + + // Set parent parameter for this parameter. + virtual void SetParameterName(const FString& InName) { Name = InName; }; + virtual void SetParameterLabel(const FString& InLabel) { Label = InLabel; }; + virtual void SetParameterHelp(const FString& InHelp) { Help = InHelp; }; + + virtual void SetParameterType(const EHoudiniParameterType& InType) { ParmType = InType; }; + virtual void SetTupleSize(const uint32& InTupleSize) { TupleSize = InTupleSize; }; + virtual void SetNodeId(const int32& InNodeId) { NodeId = InNodeId; }; + virtual void SetParmId(const int32& InParmId) { ParmId = InParmId; }; + virtual void SetParentParmId(const int32& InParentParmId) { ParentParmId = InParentParmId; }; + virtual void SetChildIndex(const int32& InChildIndex) { ChildIndex = InChildIndex; }; + + virtual void SetIsChildOfMultiParm(const bool& IsChildOfMultiParam) { bIsChildOfMultiParm = IsChildOfMultiParam; }; + virtual bool GetIsChildOfMultiParm() const { return bIsChildOfMultiParm; }; + + virtual void SetIsDirectChildOfMultiParm(const bool& IsDirectChildOfMultiParam) { bIsDirectChildOfMultiParm = IsDirectChildOfMultiParam; }; + virtual bool IsDirectChildOfMultiParm() const { return bIsDirectChildOfMultiParm; }; + + virtual void SetVisible(const bool& InIsVisible) { bIsVisible = InIsVisible; }; + virtual void SetDisabled(const bool& InIsDisabled) { bIsDisabled = InIsDisabled; }; + virtual void SetDefault(const bool& InIsDefault) { bIsDefault = InIsDefault; }; + virtual void SetSpare(const bool& InIsSpare) { bIsSpare = InIsSpare; }; + virtual void SetJoinNext(const bool& InJoinNext) { bJoinNext = InJoinNext; }; + + virtual void SetTagCount(const uint32& InTagCount) { TagCount = InTagCount; }; + virtual void SetValueIndex(const uint32& InValueIndex) { ValueIndex = InValueIndex; }; + + virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + virtual void RevertToDefault(); + virtual void RevertToDefault(const int32& TupleIndex); + virtual void MarkDefault(const bool& bInDefault); + + virtual void SetHasExpression(const bool& InHasExpression) { bHasExpression = InHasExpression; }; + virtual void SetShowExpression(const bool& InShowExpression) { bShowExpression = InShowExpression; }; + virtual void SetExpression(const FString& InParamExpression) { ParamExpression = InParamExpression; }; + + virtual void SetAutoUpdate(const bool& InAutoUpdate) { bAutoUpdate = InAutoUpdate; }; + + static FString GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType); + static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromInt(int32 InInt); + static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromString(const FString& InString); + + static ERichCurveInterpMode EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType); + + // Duplicate this object for state transfer between component instances and templates + UHoudiniParameter* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + // Replace any input references using the provided mapping + virtual void RemapInputs(const TMap& InputMapping) {}; + + // Replace any parameter references using the provided mapping + virtual void RemapParameters(const TMap& ParameterMapping) {}; + + // Invalidate ids + virtual void InvalidateData(); + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + virtual void OnPreCook() {}; + +protected: + + //--------------------------------------------------------------------------------------------- + // ParmInfos + //--------------------------------------------------------------------------------------------- + + // + UPROPERTY() + FString Name; + + // + UPROPERTY() + FString Label; + + // Unreal type of the parameter + UPROPERTY() + EHoudiniParameterType ParmType; + + // Tuple size. For scalar parameters this value is 1, but for vector parameters this value can be greater. + UPROPERTY() + uint32 TupleSize; + + // Node this parameter belongs to. + UPROPERTY(DuplicateTransient) + int32 NodeId; + + // Id of this parameter. + UPROPERTY(DuplicateTransient) + int32 ParmId; + + // Id of parent parameter, -1 if root is parent. + UPROPERTY(DuplicateTransient) + int32 ParentParmId; + + // Child index within its parent parameter. + UPROPERTY() + int32 ChildIndex; + + // + UPROPERTY() + bool bIsVisible; + + // + UPROPERTY() + bool bIsDisabled; + + // Is set to true if value of this parameter has been changed by user. + UPROPERTY() + bool bHasChanged; + + // Is set to true if value of this parameter will trigger an update of the asset + UPROPERTY() + bool bNeedsToTriggerUpdate; + + // Indicates that this parameter is still using its default value + UPROPERTY(Transient, DuplicateTransient) + bool bIsDefault; + + // Permissions for file parms + UPROPERTY() + bool bIsSpare; + + // + UPROPERTY() + bool bJoinNext; + + // + UPROPERTY() + bool bIsChildOfMultiParm; + + UPROPERTY() + bool bIsDirectChildOfMultiParm; + + // Indicates a parameter value needs to be reverted to its default + UPROPERTY(DuplicateTransient) + bool bPendingRevertToDefault; + + UPROPERTY(DuplicateTransient) + TArray TuplePendingRevertToDefault; + + // + UPROPERTY() + FString Help; + + // Number of tags on this parameter + UPROPERTY() + uint32 TagCount; + + // The index to use to look into the values array in order to retrieve the actual value(s) of this parameter. + UPROPERTY() + int32 ValueIndex; + + //------------------------------------------------------------------------------------------------------------------------- + // Expression + // TODO: Use tuple array for this + // Indicates the parameters has an expression value + UPROPERTY() + bool bHasExpression; + + // Indicates we are currently displaying the parameter's value + UPROPERTY(DuplicateTransient) + bool bShowExpression; + + // The parameter's expression + UPROPERTY() + FString ParamExpression; + + UPROPERTY() + TMap Tags; + + UPROPERTY() + bool bAutoUpdate = true; + + +}; + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp index 269c23cc6..f190cfc8f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterButton.h" - -UHoudiniParameterButton::UHoudiniParameterButton(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Button; -} - -UHoudiniParameterButton * -UHoudiniParameterButton::Create( UObject* InOuter, const FString& InParamName ) -{ - FString ParamNameStr = "HoudiniParameterButton_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButton::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterButton * HoudiniAssetParameter = NewObject< UHoudiniParameterButton >( - InOuter, UHoudiniParameterButton::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Button); - - //HoudiniAssetParameterButton->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterButton.h" + +UHoudiniParameterButton::UHoudiniParameterButton(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Button; +} + +UHoudiniParameterButton * +UHoudiniParameterButton::Create( UObject* InOuter, const FString& InParamName ) +{ + FString ParamNameStr = "HoudiniParameterButton_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButton::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterButton * HoudiniAssetParameter = NewObject< UHoudiniParameterButton >( + InOuter, UHoudiniParameterButton::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Button); + + //HoudiniAssetParameterButton->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h index e35ee513e..875788fad 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterButton.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterButton : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterButton * Create( - UObject* Outer, - const FString& ParamName); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterButton.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterButton : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterButton * Create( + UObject* Outer, + const FString& ParamName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp index 785c2fc90..efe606496 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp @@ -1,74 +1,74 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterButtonStrip.h" - -UHoudiniParameterButtonStrip::UHoudiniParameterButtonStrip(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::ButtonStrip; -} - -UHoudiniParameterButtonStrip * -UHoudiniParameterButtonStrip::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterButton_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButtonStrip::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterButtonStrip * HoudiniAssetParameter = NewObject< UHoudiniParameterButtonStrip >( - InOuter, UHoudiniParameterButtonStrip::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::ButtonStrip); - - HoudiniAssetParameter->Count = 0; - - return HoudiniAssetParameter; -} - -FString * -UHoudiniParameterButtonStrip::GetStringLabelAt(const int32 & InIndex) -{ - if (!Labels.IsValidIndex(InIndex)) - return nullptr; - - return &(Labels[InIndex]); -} - -bool -UHoudiniParameterButtonStrip::SetValueAt(const int32 & InIdx, int32 InVal) -{ - if (!Values.IsValidIndex(InIdx)) - return false; - - if (Values[InIdx] == InVal) - return false; - - Values[InIdx] = InVal; - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterButtonStrip.h" + +UHoudiniParameterButtonStrip::UHoudiniParameterButtonStrip(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::ButtonStrip; +} + +UHoudiniParameterButtonStrip * +UHoudiniParameterButtonStrip::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterButton_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButtonStrip::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterButtonStrip * HoudiniAssetParameter = NewObject< UHoudiniParameterButtonStrip >( + InOuter, UHoudiniParameterButtonStrip::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::ButtonStrip); + + HoudiniAssetParameter->Count = 0; + + return HoudiniAssetParameter; +} + +FString * +UHoudiniParameterButtonStrip::GetStringLabelAt(const int32 & InIndex) +{ + if (!Labels.IsValidIndex(InIndex)) + return nullptr; + + return &(Labels[InIndex]); +} + +bool +UHoudiniParameterButtonStrip::SetValueAt(const int32 & InIdx, int32 InVal) +{ + if (!Values.IsValidIndex(InIdx)) + return false; + + if (Values[InIdx] == InVal) + return false; + + Values[InIdx] = InVal; + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h index a515e51cb..13c5d2347 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h @@ -1,66 +1,66 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterButtonStrip.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterButtonStrip : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterButtonStrip * Create( - UObject* InOuter, - const FString& InParamName); - - UPROPERTY() - int32 Count; - - UPROPERTY() - TArray Labels; - - UPROPERTY() - TArray Values; - - - void InitializeLabels(const int32 & InSize) { Labels.SetNumZeroed(InSize); Values.SetNumZeroed(InSize); Count = InSize; }; - - bool SetValueAt(const int32 & InIdx, int32 InVal); - - - FString * GetStringLabelAt(const int32 & InIndex); - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterButtonStrip.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterButtonStrip : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterButtonStrip * Create( + UObject* InOuter, + const FString& InParamName); + + UPROPERTY() + int32 Count; + + UPROPERTY() + TArray Labels; + + UPROPERTY() + TArray Values; + + + void InitializeLabels(const int32 & InSize) { Labels.SetNumZeroed(InSize); Values.SetNumZeroed(InSize); Count = InSize; }; + + bool SetValueAt(const int32 & InIdx, int32 InVal); + + + FString * GetStringLabelAt(const int32 & InIndex); + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp index 73dde74db..fe66b2e42 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp @@ -1,260 +1,260 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterChoice.h" - -UHoudiniParameterChoice::UHoudiniParameterChoice(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , IntValue(-1) -{ - ParmType = EHoudiniParameterType::IntChoice; -} - -UHoudiniParameterChoice * -UHoudiniParameterChoice::Create( UObject* InOuter, const FString& InParamName, const EHoudiniParameterType& InParmType) -{ - FString ParamNameStr = "HoudiniParameterChoice_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterChoice::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterChoice * HoudiniAssetParameter = NewObject< UHoudiniParameterChoice >( - InOuter, UHoudiniParameterChoice::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(InParmType); - - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -void -UHoudiniParameterChoice::BeginDestroy() -{ - // We need to clean up our arrays - for (auto& Ptr : ChoiceLabelsPtr) - { - Ptr.Reset(); - } - ChoiceLabelsPtr.Empty(); - - // Then the string arrays - StringChoiceLabels.Empty(); - StringChoiceValues.Empty(); - - Super::BeginDestroy(); -} - - -FString* -UHoudiniParameterChoice::GetStringChoiceValueAt(const int32& InAtIndex) -{ - if (!StringChoiceValues.IsValidIndex(InAtIndex)) - return nullptr; - - return &(StringChoiceValues[InAtIndex]); -} - -FString* -UHoudiniParameterChoice::GetStringChoiceLabelAt(const int32& InAtIndex) -{ - if (!StringChoiceLabels.IsValidIndex(InAtIndex)) - return nullptr; - - return &(StringChoiceLabels[InAtIndex]); -} - -void -UHoudiniParameterChoice::SetNumChoices(const int32& InNumChoices) -{ - // Set the array sizes - StringChoiceValues.SetNumZeroed(InNumChoices); - StringChoiceLabels.SetNumZeroed(InNumChoices); - - UpdateChoiceLabelsPtr(); -} - -// Update the pointers to the ChoiceLabels -bool -UHoudiniParameterChoice::UpdateChoiceLabelsPtr() -{ - /* - bool bNeedUpdate = false; - if (StringChoiceLabels.Num() != ChoiceLabelsPtr.Num()) - { - bNeedUpdate = true; - } - else - { - for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) - { - if (ChoiceLabelsPtr[Idx].Get() == &(StringChoiceLabels[Idx])) - continue; - - bNeedUpdate = true; - break; - } - } - - if (!bNeedUpdate) - return true; - */ - - // Updates the Label Ptr array - ChoiceLabelsPtr.SetNumZeroed(StringChoiceLabels.Num()); - for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) - { - ChoiceLabelsPtr[Idx] = MakeShared(StringChoiceLabels[Idx]); - } - - return true; -} - -bool -UHoudiniParameterChoice::SetIntValue(const int32& InIntValue) -{ - if (InIntValue == IntValue) - return false; - - IntValue = InIntValue; - - return true; -} - -bool -UHoudiniParameterChoice::SetStringValue(const FString& InStringValue) -{ - if (InStringValue.Equals(StringValue)) - return false; - - StringValue = InStringValue; - - return true; -}; - -bool -UHoudiniParameterChoice::UpdateIntValueFromString() -{ - int32 FoundInt = INDEX_NONE; - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - // Update the int values from the string value - FoundInt = StringChoiceLabels.Find(StringValue); - } - else - { - // Update the int values from the string value - FoundInt = StringChoiceValues.Find(StringValue); - } - - if (FoundInt == INDEX_NONE) - return false; - - if (IntValue == FoundInt) - return false; - - IntValue = FoundInt; - - return true; -} - -bool -UHoudiniParameterChoice::UpdateStringValueFromInt() -{ - // Update the string value from the int value - FString NewStringValue; - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - // IntChoices only have labels - if (!StringChoiceLabels.IsValidIndex(IntValue)) - return false; - - NewStringValue = StringChoiceLabels[IntValue]; - } - else - { - // StringChoices should use values - if (!StringChoiceValues.IsValidIndex(IntValue)) - return false; - - NewStringValue = StringChoiceValues[IntValue]; - } - - if (StringValue.Equals(NewStringValue)) - return false; - - StringValue = NewStringValue; - - return true; -} - -const int32 -UHoudiniParameterChoice::GetIntValueFromLabel(const FString& InSelectedLabel) const -{ - return StringChoiceLabels.Find(InSelectedLabel); -} - -TOptional< TSharedPtr > -UHoudiniParameterChoice::GetValue(int32 Idx) const -{ - if (Idx == 0 && StringChoiceValues.IsValidIndex(IntValue)) - { - return TOptional< TSharedPtr< FString > >(MakeShared(StringChoiceValues[IntValue])); - } - - return TOptional< TSharedPtr< FString > >(); -} - -bool -UHoudiniParameterChoice::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - return IntValue == DefaultIntValue; - } - - if (GetParameterType() == EHoudiniParameterType::StringChoice) - { - return StringValue == DefaultStringValue; - } - - return true; -} - -void -UHoudiniParameterChoice::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterChoice.h" + +UHoudiniParameterChoice::UHoudiniParameterChoice(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , IntValue(-1) +{ + ParmType = EHoudiniParameterType::IntChoice; +} + +UHoudiniParameterChoice * +UHoudiniParameterChoice::Create( UObject* InOuter, const FString& InParamName, const EHoudiniParameterType& InParmType) +{ + FString ParamNameStr = "HoudiniParameterChoice_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterChoice::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterChoice * HoudiniAssetParameter = NewObject< UHoudiniParameterChoice >( + InOuter, UHoudiniParameterChoice::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(InParmType); + + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +void +UHoudiniParameterChoice::BeginDestroy() +{ + // We need to clean up our arrays + for (auto& Ptr : ChoiceLabelsPtr) + { + Ptr.Reset(); + } + ChoiceLabelsPtr.Empty(); + + // Then the string arrays + StringChoiceLabels.Empty(); + StringChoiceValues.Empty(); + + Super::BeginDestroy(); +} + + +FString* +UHoudiniParameterChoice::GetStringChoiceValueAt(const int32& InAtIndex) +{ + if (!StringChoiceValues.IsValidIndex(InAtIndex)) + return nullptr; + + return &(StringChoiceValues[InAtIndex]); +} + +FString* +UHoudiniParameterChoice::GetStringChoiceLabelAt(const int32& InAtIndex) +{ + if (!StringChoiceLabels.IsValidIndex(InAtIndex)) + return nullptr; + + return &(StringChoiceLabels[InAtIndex]); +} + +void +UHoudiniParameterChoice::SetNumChoices(const int32& InNumChoices) +{ + // Set the array sizes + StringChoiceValues.SetNumZeroed(InNumChoices); + StringChoiceLabels.SetNumZeroed(InNumChoices); + + UpdateChoiceLabelsPtr(); +} + +// Update the pointers to the ChoiceLabels +bool +UHoudiniParameterChoice::UpdateChoiceLabelsPtr() +{ + /* + bool bNeedUpdate = false; + if (StringChoiceLabels.Num() != ChoiceLabelsPtr.Num()) + { + bNeedUpdate = true; + } + else + { + for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) + { + if (ChoiceLabelsPtr[Idx].Get() == &(StringChoiceLabels[Idx])) + continue; + + bNeedUpdate = true; + break; + } + } + + if (!bNeedUpdate) + return true; + */ + + // Updates the Label Ptr array + ChoiceLabelsPtr.SetNumZeroed(StringChoiceLabels.Num()); + for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) + { + ChoiceLabelsPtr[Idx] = MakeShared(StringChoiceLabels[Idx]); + } + + return true; +} + +bool +UHoudiniParameterChoice::SetIntValue(const int32& InIntValue) +{ + if (InIntValue == IntValue) + return false; + + IntValue = InIntValue; + + return true; +} + +bool +UHoudiniParameterChoice::SetStringValue(const FString& InStringValue) +{ + if (InStringValue.Equals(StringValue)) + return false; + + StringValue = InStringValue; + + return true; +}; + +bool +UHoudiniParameterChoice::UpdateIntValueFromString() +{ + int32 FoundInt = INDEX_NONE; + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + // Update the int values from the string value + FoundInt = StringChoiceLabels.Find(StringValue); + } + else + { + // Update the int values from the string value + FoundInt = StringChoiceValues.Find(StringValue); + } + + if (FoundInt == INDEX_NONE) + return false; + + if (IntValue == FoundInt) + return false; + + IntValue = FoundInt; + + return true; +} + +bool +UHoudiniParameterChoice::UpdateStringValueFromInt() +{ + // Update the string value from the int value + FString NewStringValue; + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + // IntChoices only have labels + if (!StringChoiceLabels.IsValidIndex(IntValue)) + return false; + + NewStringValue = StringChoiceLabels[IntValue]; + } + else + { + // StringChoices should use values + if (!StringChoiceValues.IsValidIndex(IntValue)) + return false; + + NewStringValue = StringChoiceValues[IntValue]; + } + + if (StringValue.Equals(NewStringValue)) + return false; + + StringValue = NewStringValue; + + return true; +} + +const int32 +UHoudiniParameterChoice::GetIntValueFromLabel(const FString& InSelectedLabel) const +{ + return StringChoiceLabels.Find(InSelectedLabel); +} + +TOptional< TSharedPtr > +UHoudiniParameterChoice::GetValue(int32 Idx) const +{ + if (Idx == 0 && StringChoiceValues.IsValidIndex(IntValue)) + { + return TOptional< TSharedPtr< FString > >(MakeShared(StringChoiceValues[IntValue])); + } + + return TOptional< TSharedPtr< FString > >(); +} + +bool +UHoudiniParameterChoice::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + return IntValue == DefaultIntValue; + } + + if (GetParameterType() == EHoudiniParameterType::StringChoice) + { + return StringValue == DefaultStringValue; + } + + return true; +} + +void +UHoudiniParameterChoice::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h index c23fc2bd2..372aacc6f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h @@ -1,125 +1,125 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterChoice.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create an instance of this class. - static UHoudiniParameterChoice * Create( - UObject* Outer, - const FString& ParamName, - const EHoudiniParameterType& ParmType); - - virtual void BeginDestroy() override; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - const int32 GetIntValue() const { return IntValue; }; - const FString GetStringValue() const { return StringValue; }; - const int32 GetNumChoices() const { return StringChoiceLabels.Num(); }; - const FString GetLabel() const { return StringChoiceLabels.IsValidIndex(IntValue) ? StringChoiceLabels[IntValue] : FString(); }; - const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; - - bool IsDefault() const override; - - TOptional> GetValue(int32 Idx) const; - - const int32 GetIntValueFromLabel(const FString& InSelectedLabel) const; - - FString* GetStringChoiceValueAt(const int32& InAtIndex); - FString* GetStringChoiceLabelAt(const int32& InAtIndex); - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Returns the ChoiceLabel SharedPtr array, used for UI only - TArray>* GetChoiceLabelsPtr() { return &ChoiceLabelsPtr; }; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - bool SetIntValue(const int32& InIntValue); - bool SetStringValue(const FString& InStringValue); - void SetNumChoices(const int32& InNumChoices); - - // For string choices only, update the int values from the string value - bool UpdateIntValueFromString(); - // For int choices only, update the string value from the int value - bool UpdateStringValueFromInt(); - // Update the pointers to the ChoiceLabels - bool UpdateChoiceLabelsPtr(); - - void SetDefaultIntValue() { DefaultIntValue = IntValue; }; - void SetDefaultStringValue() { DefaultStringValue = StringValue; }; - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - void RevertToDefault() override; - -protected: - - // Current int value for this property. - UPROPERTY() - int32 IntValue; - - // Default int value for this property, assigned at creating the parameter. - UPROPERTY() - int32 DefaultIntValue; - - // Current string value for this property - UPROPERTY() - FString StringValue; - - // Default string value for this property, assigned at creating the parameter. - UPROPERTY() - FString DefaultStringValue; - - // Used only for StringChoices! - // All the possible string values for this parameter's choices - UPROPERTY() - TArray StringChoiceValues; - - // Labels corresponding to this parameter's choices. - UPROPERTY() - TArray StringChoiceLabels; - - // Array of SharedPtr pointing to this parameter's label, used for UI only - TArray> ChoiceLabelsPtr; - - UPROPERTY() - bool bIsChildOfRamp; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterChoice.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create an instance of this class. + static UHoudiniParameterChoice * Create( + UObject* Outer, + const FString& ParamName, + const EHoudiniParameterType& ParmType); + + virtual void BeginDestroy() override; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + const int32 GetIntValue() const { return IntValue; }; + const FString GetStringValue() const { return StringValue; }; + const int32 GetNumChoices() const { return StringChoiceLabels.Num(); }; + const FString GetLabel() const { return StringChoiceLabels.IsValidIndex(IntValue) ? StringChoiceLabels[IntValue] : FString(); }; + const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; + + bool IsDefault() const override; + + TOptional> GetValue(int32 Idx) const; + + const int32 GetIntValueFromLabel(const FString& InSelectedLabel) const; + + FString* GetStringChoiceValueAt(const int32& InAtIndex); + FString* GetStringChoiceLabelAt(const int32& InAtIndex); + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Returns the ChoiceLabel SharedPtr array, used for UI only + TArray>* GetChoiceLabelsPtr() { return &ChoiceLabelsPtr; }; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + bool SetIntValue(const int32& InIntValue); + bool SetStringValue(const FString& InStringValue); + void SetNumChoices(const int32& InNumChoices); + + // For string choices only, update the int values from the string value + bool UpdateIntValueFromString(); + // For int choices only, update the string value from the int value + bool UpdateStringValueFromInt(); + // Update the pointers to the ChoiceLabels + bool UpdateChoiceLabelsPtr(); + + void SetDefaultIntValue() { DefaultIntValue = IntValue; }; + void SetDefaultStringValue() { DefaultStringValue = StringValue; }; + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + void RevertToDefault() override; + +protected: + + // Current int value for this property. + UPROPERTY() + int32 IntValue; + + // Default int value for this property, assigned at creating the parameter. + UPROPERTY() + int32 DefaultIntValue; + + // Current string value for this property + UPROPERTY() + FString StringValue; + + // Default string value for this property, assigned at creating the parameter. + UPROPERTY() + FString DefaultStringValue; + + // Used only for StringChoices! + // All the possible string values for this parameter's choices + UPROPERTY() + TArray StringChoiceValues; + + // Labels corresponding to this parameter's choices. + UPROPERTY() + TArray StringChoiceLabels; + + // Array of SharedPtr pointing to this parameter's label, used for UI only + TArray> ChoiceLabelsPtr; + + UPROPERTY() + bool bIsChildOfRamp; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp index e981690ce..f97df8433 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp @@ -1,84 +1,84 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterColor.h" - -UHoudiniParameterColor::UHoudiniParameterColor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Color( FLinearColor::White ) -{ - ParmType = EHoudiniParameterType::Color; -} - -UHoudiniParameterColor * -UHoudiniParameterColor::Create( UObject* InOuter, const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterColor_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterColor::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterColor * HoudiniAssetParameter = NewObject< UHoudiniParameterColor >( - InOuter, UHoudiniParameterColor::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Color); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->bIsChildOfRamp = false; - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterColor::SetColorValue(const FLinearColor& InColor) -{ - if (InColor == Color) - return false; - - Color = InColor; - - return true; -} - -bool -UHoudiniParameterColor::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - return Color == DefaultColor; -} - -void -UHoudiniParameterColor::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterColor.h" + +UHoudiniParameterColor::UHoudiniParameterColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Color( FLinearColor::White ) +{ + ParmType = EHoudiniParameterType::Color; +} + +UHoudiniParameterColor * +UHoudiniParameterColor::Create( UObject* InOuter, const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterColor_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterColor::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterColor * HoudiniAssetParameter = NewObject< UHoudiniParameterColor >( + InOuter, UHoudiniParameterColor::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Color); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->bIsChildOfRamp = false; + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterColor::SetColorValue(const FLinearColor& InColor) +{ + if (InColor == Color) + return false; + + Color = InColor; + + return true; +} + +bool +UHoudiniParameterColor::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + return Color == DefaultColor; +} + +void +UHoudiniParameterColor::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h index 7224b183f..06e115bae 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h @@ -1,73 +1,73 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterColor.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterColor : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - - public: - - // Create instance of this class. - static UHoudiniParameterColor * Create( - UObject* Outer, - const FString& ParamName); - - // Accessor - FLinearColor GetColorValue() const { return Color; }; - - bool IsDefault() const override; - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Mutators - bool SetColorValue(const FLinearColor& InColor); - - void SetDefaultValue() { DefaultColor = Color; }; - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - void RevertToDefault() override; - - protected: - - // Color for this property. - UPROPERTY() - FLinearColor Color; - - // Default color for this property - UPROPERTY() - FLinearColor DefaultColor; - - UPROPERTY() - bool bIsChildOfRamp; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterColor.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterColor : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + + public: + + // Create instance of this class. + static UHoudiniParameterColor * Create( + UObject* Outer, + const FString& ParamName); + + // Accessor + FLinearColor GetColorValue() const { return Color; }; + + bool IsDefault() const override; + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Mutators + bool SetColorValue(const FLinearColor& InColor); + + void SetDefaultValue() { DefaultColor = Color; }; + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + void RevertToDefault() override; + + protected: + + // Color for this property. + UPROPERTY() + FLinearColor Color; + + // Default color for this property + UPROPERTY() + FLinearColor DefaultColor; + + UPROPERTY() + bool bIsChildOfRamp; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp index c474d3cf9..9779b1f15 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp @@ -1,101 +1,101 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFile.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" - -#include "Misc/Paths.h" - -UHoudiniParameterFile::UHoudiniParameterFile(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Filters() - , bIsReadOnly(false) -{ - ParmType = EHoudiniParameterType::File; -} - -UHoudiniParameterFile * -UHoudiniParameterFile::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFile_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFile::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFile * HoudiniAssetParameter = NewObject< UHoudiniParameterFile >( - InOuter, UHoudiniParameterFile::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::File); - - return HoudiniAssetParameter; -} - - -bool -UHoudiniParameterFile::SetValueAt(const FString& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index] == InValue) - return false; - - Values[Index] = InValue; - - return true; -} - -bool -UHoudiniParameterFile::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!DefaultValues.IsValidIndex(Idx)) - break; - - if (Values[Idx] != DefaultValues[Idx]) - return false; - } - - return true; -} - -void -UHoudiniParameterFile::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFile.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" + +#include "Misc/Paths.h" + +UHoudiniParameterFile::UHoudiniParameterFile(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Filters() + , bIsReadOnly(false) +{ + ParmType = EHoudiniParameterType::File; +} + +UHoudiniParameterFile * +UHoudiniParameterFile::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFile_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFile::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFile * HoudiniAssetParameter = NewObject< UHoudiniParameterFile >( + InOuter, UHoudiniParameterFile::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::File); + + return HoudiniAssetParameter; +} + + +bool +UHoudiniParameterFile::SetValueAt(const FString& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index] == InValue) + return false; + + Values[Index] = InValue; + + return true; +} + +bool +UHoudiniParameterFile::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!DefaultValues.IsValidIndex(Idx)) + break; + + if (Values[Idx] != DefaultValues[Idx]) + return false; + } + + return true; +} + +void +UHoudiniParameterFile::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h index 00a4ea8b6..0b0e57fd0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h @@ -1,78 +1,78 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFile.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFile : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFile * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetFileFilters() const { return Filters; }; - bool IsReadOnly() const { return bIsReadOnly; }; - FString GetValueAt(int32 Index) { return Values[Index]; }; - int32 GetNumValues() { return Values.Num(); }; - - void SetNumberOfValues(const uint32& NumValues) { Values.SetNum(NumValues); }; - bool SetValueAt(const FString& InValue, const uint32& Index); - - bool IsDefault() const override; - - // Mutators - void SetFileFilters(const FString& InFilters) { Filters = InFilters; }; - void SetReadOnly(const bool& InReadOnly) { bIsReadOnly = InReadOnly; }; - - void SetDefaultValues(); - -protected: - - // Values of this property. - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; - - // Filters of this property. - UPROPERTY() - FString Filters; - - // Is the file parameter read-only? - UPROPERTY() - bool bIsReadOnly; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFile.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFile : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFile * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetFileFilters() const { return Filters; }; + bool IsReadOnly() const { return bIsReadOnly; }; + FString GetValueAt(int32 Index) { return Values[Index]; }; + int32 GetNumValues() { return Values.Num(); }; + + void SetNumberOfValues(const uint32& NumValues) { Values.SetNum(NumValues); }; + bool SetValueAt(const FString& InValue, const uint32& Index); + + bool IsDefault() const override; + + // Mutators + void SetFileFilters(const FString& InFilters) { Filters = InFilters; }; + void SetReadOnly(const bool& InReadOnly) { bIsReadOnly = InReadOnly; }; + + void SetDefaultValues(); + +protected: + + // Values of this property. + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; + + // Filters of this property. + UPROPERTY() + FString Filters; + + // Is the file parameter read-only? + UPROPERTY() + bool bIsReadOnly; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp index 43b987411..961a01a24 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp @@ -1,158 +1,169 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFloat.h" - -UHoudiniParameterFloat::UHoudiniParameterFloat(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Unit(TEXT("")) - , bNoSwap(false) - , bHasMin(false) - , bHasMax(false) - , bHasUIMin(false) - , bHasUIMax(false) - , bIsLogarithmic(false) - , Min(TNumericLimits::Lowest()) - , Max(TNumericLimits::Max()) - , UIMin(TNumericLimits::Lowest()) - , UIMax(TNumericLimits::Max()) -{ - ParmType = EHoudiniParameterType::Float; -} - -UHoudiniParameterFloat * -UHoudiniParameterFloat::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFloat_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFloat::StaticClass(), *ParamNameStr); - // We need to create a new parameter - UHoudiniParameterFloat * HoudiniAssetParameter = NewObject< UHoudiniParameterFloat >( - InOuter, UHoudiniParameterFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); - - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Float); - - HoudiniAssetParameter->bIsChildOfRamp = false; - HoudiniAssetParameter->bIsLogarithmic = false; - return HoudiniAssetParameter; -} - -TOptional< float > -UHoudiniParameterFloat::GetValue(int32 Idx) const -{ - if (Values.IsValidIndex(Idx)) - return TOptional< float >(Values[Idx]); - else - return TOptional< float >(); -} - -bool -UHoudiniParameterFloat::SetValueAt(const float& InValue, const int32& AtIndex) -{ - if (!Values.IsValidIndex(AtIndex)) - return false; - - if (InValue == Values[AtIndex]) - return false; - - Values[AtIndex] = FMath::Clamp< float >(InValue, Min, Max); - - return true; -} - -void -UHoudiniParameterFloat::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterFloat::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterFloat::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; -} - -void -UHoudiniParameterFloat::SetValue(float InValue, int32 Idx) -{ - if (!Values.IsValidIndex(Idx)) - return; - - if (InValue == Values[Idx]) - return; - - Values[Idx] = FMath::Clamp< float >(InValue, Min, Max); -} - -void -UHoudiniParameterFloat::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } -} - -void -UHoudiniParameterFloat::RevertToDefault(const int32& TupleIndex) -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.AddUnique(TupleIndex); - - MarkChanged(true); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFloat.h" + +UHoudiniParameterFloat::UHoudiniParameterFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Unit(TEXT("")) + , bNoSwap(false) + , bHasMin(false) + , bHasMax(false) + , bHasUIMin(false) + , bHasUIMax(false) + , bIsLogarithmic(false) + , Min(TNumericLimits::Lowest()) + , Max(TNumericLimits::Max()) + , UIMin(TNumericLimits::Lowest()) + , UIMax(TNumericLimits::Max()) +{ + ParmType = EHoudiniParameterType::Float; +} + +UHoudiniParameterFloat * +UHoudiniParameterFloat::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFloat_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFloat::StaticClass(), *ParamNameStr); + // We need to create a new parameter + UHoudiniParameterFloat * HoudiniAssetParameter = NewObject< UHoudiniParameterFloat >( + InOuter, UHoudiniParameterFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); + + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Float); + + HoudiniAssetParameter->bIsChildOfRamp = false; + HoudiniAssetParameter->bIsLogarithmic = false; + return HoudiniAssetParameter; +} + +TOptional< float > +UHoudiniParameterFloat::GetValue(int32 Idx) const +{ + if (Values.IsValidIndex(Idx)) + return TOptional< float >(Values[Idx]); + else + return TOptional< float >(); +} + +bool +UHoudiniParameterFloat::GetValueAt(const int32& AtIndex, float& OutValue) const +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + OutValue = Values[AtIndex]; + + return true; +} + +bool +UHoudiniParameterFloat::SetValueAt(const float& InValue, const int32& AtIndex) +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + if (InValue == Values[AtIndex]) + return false; + + Values[AtIndex] = FMath::Clamp< float >(InValue, Min, Max); + + return true; +} + +void +UHoudiniParameterFloat::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterFloat::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterFloat::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; +} + +void +UHoudiniParameterFloat::SetValue(float InValue, int32 Idx) +{ + if (!Values.IsValidIndex(Idx)) + return; + + if (InValue == Values[Idx]) + return; + + Values[Idx] = FMath::Clamp< float >(InValue, Min, Max); +} + +void +UHoudiniParameterFloat::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } +} + +void +UHoudiniParameterFloat::RevertToDefault(const int32& TupleIndex) +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.AddUnique(TupleIndex); + + MarkChanged(true); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h index a13ac87d7..d36967194 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h @@ -1,165 +1,166 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFloat.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFloat : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFloat * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetUnit() const { return Unit; }; - bool GetNoSwap() const { return bNoSwap; }; - - bool HasMin() const { return bHasMin; }; - bool HasMax() const { return bHasMax; }; - bool HasUIMin() const { return bHasUIMin; }; - bool HasUIMax() const { return bHasUIMax; }; - bool IsLogarithmic() const { return bIsLogarithmic; }; - - float GetMin() const { return Min; }; - float GetMax() const { return Max; }; - float GetUIMin() const { return UIMin; }; - float GetUIMax() const { return UIMax; }; - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Check if current value at Index is the default - bool IsDefaultValueAtIndex(const int32& Idx) const; - - bool IsDefault() const override; - - // Get value of this property - TOptional< float > GetValue(int32 Idx) const; - - // Write access to the value array - float* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - // Mutators - void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; - void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; - void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; - void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; - void SetIsLogarithmic(const bool& bInLog) { bIsLogarithmic = bInLog; }; - - void SetMin(const float& InMin) { Min = InMin; }; - void SetMax(const float& InMax) { Max = InMax; }; - void SetUIMin(const float& InUIMin) { UIMin = InUIMin; }; - void SetUIMax(const float& InUIMax) { UIMax = InUIMax; }; - - void SetUnit(const FString& InUnit) { Unit = InUnit; }; - void SetNoSwap(const bool& InNoSwap) { bNoSwap = InNoSwap; }; - - void SetDefaultValues(); - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - bool SetValueAt(const float& InValue, const int32& AtIndex); - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - /** Set value of this property, used by Slate. **/ - void SetValue(float InValue, int32 Idx); - - void RevertToDefault() override; - void RevertToDefault(const int32& TupleIndex) override; - -#if WITH_EDITOR - void SwitchUniformLock() { bUniformLocked = !bUniformLocked; }; - bool IsUniformLocked() const { return bUniformLocked; }; -#endif - -protected: - - // Float Values - UPROPERTY() - TArray Values; - - // Default float values, assigned at creating the parameter - UPROPERTY() - TArray DefaultValues; - - // Unit for this property - UPROPERTY() - FString Unit; - - // Do we have the noswap tag? - UPROPERTY() - bool bNoSwap; - - // Indicates we have a min/max value - UPROPERTY() - bool bHasMin; - // - UPROPERTY() - bool bHasMax; - - // Indicates we have a UI min/max - UPROPERTY() - bool bHasUIMin; - // - UPROPERTY() - bool bHasUIMax; - - UPROPERTY() - bool bIsLogarithmic; - - // Min and Max values for this property. - UPROPERTY() - float Min; - // - UPROPERTY() - float Max; - - // Min and Max values of this property for slider UI - UPROPERTY() - float UIMin; - // - UPROPERTY() - float UIMax; - - UPROPERTY() - bool bIsChildOfRamp; - -#if WITH_EDITORONLY_DATA - // Indicates whether the float VEC change value uniformly - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - bool bUniformLocked; -#endif +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFloat.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFloat : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFloat * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetUnit() const { return Unit; }; + bool GetNoSwap() const { return bNoSwap; }; + + bool HasMin() const { return bHasMin; }; + bool HasMax() const { return bHasMax; }; + bool HasUIMin() const { return bHasUIMin; }; + bool HasUIMax() const { return bHasUIMax; }; + bool IsLogarithmic() const { return bIsLogarithmic; }; + + float GetMin() const { return Min; }; + float GetMax() const { return Max; }; + float GetUIMin() const { return UIMin; }; + float GetUIMax() const { return UIMax; }; + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Check if current value at Index is the default + bool IsDefaultValueAtIndex(const int32& Idx) const; + + bool IsDefault() const override; + + // Get value of this property + TOptional< float > GetValue(int32 Idx) const; + bool GetValueAt(const int32& AtIndex, float& OutValue) const; + + // Write access to the value array + float* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + // Mutators + void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; + void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; + void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; + void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; + void SetIsLogarithmic(const bool& bInLog) { bIsLogarithmic = bInLog; }; + + void SetMin(const float& InMin) { Min = InMin; }; + void SetMax(const float& InMax) { Max = InMax; }; + void SetUIMin(const float& InUIMin) { UIMin = InUIMin; }; + void SetUIMax(const float& InUIMax) { UIMax = InUIMax; }; + + void SetUnit(const FString& InUnit) { Unit = InUnit; }; + void SetNoSwap(const bool& InNoSwap) { bNoSwap = InNoSwap; }; + + void SetDefaultValues(); + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + bool SetValueAt(const float& InValue, const int32& AtIndex); + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + /** Set value of this property, used by Slate. **/ + void SetValue(float InValue, int32 Idx); + + void RevertToDefault() override; + void RevertToDefault(const int32& TupleIndex) override; + +#if WITH_EDITOR + void SwitchUniformLock() { bUniformLocked = !bUniformLocked; }; + bool IsUniformLocked() const { return bUniformLocked; }; +#endif + +protected: + + // Float Values + UPROPERTY() + TArray Values; + + // Default float values, assigned at creating the parameter + UPROPERTY() + TArray DefaultValues; + + // Unit for this property + UPROPERTY() + FString Unit; + + // Do we have the noswap tag? + UPROPERTY() + bool bNoSwap; + + // Indicates we have a min/max value + UPROPERTY() + bool bHasMin; + // + UPROPERTY() + bool bHasMax; + + // Indicates we have a UI min/max + UPROPERTY() + bool bHasUIMin; + // + UPROPERTY() + bool bHasUIMax; + + UPROPERTY() + bool bIsLogarithmic; + + // Min and Max values for this property. + UPROPERTY() + float Min; + // + UPROPERTY() + float Max; + + // Min and Max values of this property for slider UI + UPROPERTY() + float UIMin; + // + UPROPERTY() + float UIMax; + + UPROPERTY() + bool bIsChildOfRamp; + +#if WITH_EDITORONLY_DATA + // Indicates whether the float VEC change value uniformly + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + bool bUniformLocked; +#endif }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp index 4bb4968bc..2bf8f9ea3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp @@ -1,52 +1,52 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFolder.h" - -UHoudiniParameterFolder::UHoudiniParameterFolder(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - ,bExpanded(true) - ,bChosen(false) -{ - ParmType = EHoudiniParameterType::Folder; -} - -UHoudiniParameterFolder * -UHoudiniParameterFolder::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFolder_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolder::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFolder * HoudiniAssetParameter = NewObject< UHoudiniParameterFolder >( - InOuter, UHoudiniParameterFolder::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Folder); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFolder.h" + +UHoudiniParameterFolder::UHoudiniParameterFolder(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + ,bExpanded(true) + ,bChosen(false) +{ + ParmType = EHoudiniParameterType::Folder; +} + +UHoudiniParameterFolder * +UHoudiniParameterFolder::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFolder_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolder::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFolder * HoudiniAssetParameter = NewObject< UHoudiniParameterFolder >( + InOuter, UHoudiniParameterFolder::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Folder); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h index 206ca2ac6..d38d3fa02 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h @@ -1,110 +1,110 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFolder.generated.h" - -UENUM() -enum class EHoudiniFolderParameterType : uint8 -{ - Invalid, - - Collapsible, - Simple, - Tabs, - Radio, - Other, -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFolder : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFolder * Create( - UObject* Outer, - const FString& ParamName); - - FORCEINLINE - EHoudiniFolderParameterType GetFolderType() const { return FolderType; }; - - FORCEINLINE - void SetFolderType(EHoudiniFolderParameterType Type) { FolderType = Type; }; - - FORCEINLINE - void SetExpanded(const bool InExpanded) { bExpanded = InExpanded; }; - FORCEINLINE - bool IsExpanded() const { return bExpanded; }; - FORCEINLINE - void ExpandButtonClicked() { bExpanded = !bExpanded; }; - - FORCEINLINE - void SetChosen(const bool InChosen) { bChosen = InChosen; }; - FORCEINLINE - bool IsChosen() const { return bChosen; }; - - FORCEINLINE - bool IsTab() const { return FolderType == EHoudiniFolderParameterType::Tabs || FolderType == EHoudiniFolderParameterType::Radio; }; - - - FORCEINLINE - void ResetChildCounter() { ChildCounter = TupleSize; } - - FORCEINLINE - int32& GetChildCounter() { return ChildCounter; }; - - FORCEINLINE - bool IsContentShown() const { return bIsContentShown; }; - - FORCEINLINE - void SetIsContentShown(const bool& bInShown) { bIsContentShown = bInShown; }; - - - -private: - UPROPERTY() - EHoudiniFolderParameterType FolderType; - - UPROPERTY() - bool bExpanded; - - UPROPERTY() - bool bChosen; - - - UPROPERTY() - int32 ChildCounter; - - UPROPERTY() - bool bIsContentShown; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFolder.generated.h" + +UENUM() +enum class EHoudiniFolderParameterType : uint8 +{ + Invalid, + + Collapsible, + Simple, + Tabs, + Radio, + Other, +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFolder : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFolder * Create( + UObject* Outer, + const FString& ParamName); + + FORCEINLINE + EHoudiniFolderParameterType GetFolderType() const { return FolderType; }; + + FORCEINLINE + void SetFolderType(EHoudiniFolderParameterType Type) { FolderType = Type; }; + + FORCEINLINE + void SetExpanded(const bool InExpanded) { bExpanded = InExpanded; }; + FORCEINLINE + bool IsExpanded() const { return bExpanded; }; + FORCEINLINE + void ExpandButtonClicked() { bExpanded = !bExpanded; }; + + FORCEINLINE + void SetChosen(const bool InChosen) { bChosen = InChosen; }; + FORCEINLINE + bool IsChosen() const { return bChosen; }; + + FORCEINLINE + bool IsTab() const { return FolderType == EHoudiniFolderParameterType::Tabs || FolderType == EHoudiniFolderParameterType::Radio; }; + + + FORCEINLINE + void ResetChildCounter() { ChildCounter = TupleSize; } + + FORCEINLINE + int32& GetChildCounter() { return ChildCounter; }; + + FORCEINLINE + bool IsContentShown() const { return bIsContentShown; }; + + FORCEINLINE + void SetIsContentShown(const bool& bInShown) { bIsContentShown = bInShown; }; + + + +private: + UPROPERTY() + EHoudiniFolderParameterType FolderType; + + UPROPERTY() + bool bExpanded; + + UPROPERTY() + bool bChosen; + + + UPROPERTY() + int32 ChildCounter; + + UPROPERTY() + bool bIsContentShown; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp index 31ea2c061..3abe846ea 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp @@ -1,108 +1,108 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterFolder.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -UHoudiniParameterFolderList::UHoudiniParameterFolderList(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), bIsTabMenu(false), bIsTabsShown(false) -{ - ParmType = EHoudiniParameterType::FolderList; -} - -UHoudiniParameterFolderList * -UHoudiniParameterFolderList::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFolderList_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolderList::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFolderList * HoudiniAssetParameter = NewObject< UHoudiniParameterFolderList >( - InOuter, UHoudiniParameterFolderList::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::FolderList); - - HoudiniAssetParameter->bIsTabMenu = false; - return HoudiniAssetParameter; -} - -void -UHoudiniParameterFolderList::AddTabFolder(UHoudiniParameterFolder* InFolderParm) -{ - TabFolders.Add(InFolderParm); -} - -bool -UHoudiniParameterFolderList::IsTabParseFinished() const -{ - for (auto & CurTab : TabFolders) - { - if (!CurTab || CurTab->IsPendingKill()) - continue; - - if (!CurTab->IsTab()) - continue; - - // Go through visible tab only - if (!CurTab->IsChosen()) - continue; - - if (CurTab->GetChildCounter() > 0) - return false; - } - - return true; -} - -void -UHoudiniParameterFolderList::RemapParameters(const TMap& InputMapping) -{ - const int32 NumFolders = TabFolders.Num(); - for (int i = 0; i < NumFolders; i++) - { - UHoudiniParameter* FromParameter = TabFolders[i]; - - if (!FromParameter) - continue; - - UHoudiniParameterFolder* ToParameter = nullptr; - if (InputMapping.Contains(FromParameter)) - { - ToParameter = Cast(InputMapping.FindRef(FromParameter)); - } - - if (!ToParameter) - { - HOUDINI_LOG_WARNING(TEXT("[UHoudiniParameterFolderList::RemapParameters] Could not find mapping for existing parameter %s (%s)."), *(FromParameter->GetParameterName()), *(FromParameter->GetPathName()) ); - } - - TabFolders[i] = ToParameter; - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterFolder.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +UHoudiniParameterFolderList::UHoudiniParameterFolderList(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), bIsTabMenu(false), bIsTabsShown(false) +{ + ParmType = EHoudiniParameterType::FolderList; +} + +UHoudiniParameterFolderList * +UHoudiniParameterFolderList::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFolderList_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolderList::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFolderList * HoudiniAssetParameter = NewObject< UHoudiniParameterFolderList >( + InOuter, UHoudiniParameterFolderList::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::FolderList); + + HoudiniAssetParameter->bIsTabMenu = false; + return HoudiniAssetParameter; +} + +void +UHoudiniParameterFolderList::AddTabFolder(UHoudiniParameterFolder* InFolderParm) +{ + TabFolders.Add(InFolderParm); +} + +bool +UHoudiniParameterFolderList::IsTabParseFinished() const +{ + for (auto & CurTab : TabFolders) + { + if (!CurTab || CurTab->IsPendingKill()) + continue; + + if (!CurTab->IsTab()) + continue; + + // Go through visible tab only + if (!CurTab->IsChosen()) + continue; + + if (CurTab->GetChildCounter() > 0) + return false; + } + + return true; +} + +void +UHoudiniParameterFolderList::RemapParameters(const TMap& InputMapping) +{ + const int32 NumFolders = TabFolders.Num(); + for (int i = 0; i < NumFolders; i++) + { + UHoudiniParameter* FromParameter = TabFolders[i]; + + if (!FromParameter) + continue; + + UHoudiniParameterFolder* ToParameter = nullptr; + if (InputMapping.Contains(FromParameter)) + { + ToParameter = Cast(InputMapping.FindRef(FromParameter)); + } + + if (!ToParameter) + { + HOUDINI_LOG_WARNING(TEXT("[UHoudiniParameterFolderList::RemapParameters] Could not find mapping for existing parameter %s (%s)."), *(FromParameter->GetParameterName()), *(FromParameter->GetPathName()) ); + } + + TabFolders[i] = ToParameter; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h index 011118cf6..77d31dc88 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h @@ -1,81 +1,81 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFolderList.generated.h" - -class UHoudiniParameterFolder; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFolderList : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFolderList * Create( - UObject* Outer, - const FString& ParamName); - - void AddTabFolder(UHoudiniParameterFolder* InFolderParm); - - FORCEINLINE - TArray& GetTabs() { return TabFolders; }; - - FORCEINLINE - bool IsTabMenu() const { return bIsTabMenu; }; - - FORCEINLINE - void SetIsTabMenu(const bool InIsTabMenu) { bIsTabMenu = InIsTabMenu; }; - - FORCEINLINE - bool IsTabsShown() const { return bIsTabsShown; }; - - FORCEINLINE - void SetTabsShown(const bool& bInTabsShown) { bIsTabsShown = bInTabsShown; }; - - bool IsTabParseFinished() const; - - UPROPERTY() - bool bIsTabMenu; - - UPROPERTY() - bool bIsTabsShown; - - UPROPERTY() - TArray TabFolders; - - - //------------------------------------------------------------------------------------------------ - // UHoudiniParameter overrides - //------------------------------------------------------------------------------------------------ - virtual void RemapParameters(const TMap& InputMapping) override; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFolderList.generated.h" + +class UHoudiniParameterFolder; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFolderList : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFolderList * Create( + UObject* Outer, + const FString& ParamName); + + void AddTabFolder(UHoudiniParameterFolder* InFolderParm); + + FORCEINLINE + TArray& GetTabs() { return TabFolders; }; + + FORCEINLINE + bool IsTabMenu() const { return bIsTabMenu; }; + + FORCEINLINE + void SetIsTabMenu(const bool InIsTabMenu) { bIsTabMenu = InIsTabMenu; }; + + FORCEINLINE + bool IsTabsShown() const { return bIsTabsShown; }; + + FORCEINLINE + void SetTabsShown(const bool& bInTabsShown) { bIsTabsShown = bInTabsShown; }; + + bool IsTabParseFinished() const; + + UPROPERTY() + bool bIsTabMenu; + + UPROPERTY() + bool bIsTabsShown; + + UPROPERTY() + TArray TabFolders; + + + //------------------------------------------------------------------------------------------------ + // UHoudiniParameter overrides + //------------------------------------------------------------------------------------------------ + virtual void RemapParameters(const TMap& InputMapping) override; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp index 69e8ce113..3168e70b1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp @@ -1,119 +1,130 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterInt.h" - -UHoudiniParameterInt::UHoudiniParameterInt(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Unit() - , bHasMin(false) - , bHasMax(false) - , bHasUIMin(false) - , bHasUIMax(false) - , bIsLogarithmic(false) - , Min(0) - , Max(0) - , UIMin(0) - , UIMax(0) -{ - ParmType = EHoudiniParameterType::Int; -} - -UHoudiniParameterInt * -UHoudiniParameterInt::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterInt_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterInt::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterInt * HoudiniAssetParameter = NewObject< UHoudiniParameterInt >( - InOuter, UHoudiniParameterInt::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Int); - HoudiniAssetParameter->bIsLogarithmic = false; - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -TOptional -UHoudiniParameterInt::GetValue(int32 Idx) const -{ - if (Values.IsValidIndex(Idx)) - return TOptional(Values[Idx]); - else - return TOptional(); -} - -bool -UHoudiniParameterInt::SetValueAt(const int32& InValue, const int32& AtIndex) -{ - if (!Values.IsValidIndex(AtIndex)) - return false; - - if (InValue == Values[AtIndex]) - return false; - - Values[AtIndex] = FMath::Clamp< int32 >(InValue, Min, Max); - - return true; -} - -void -UHoudiniParameterInt::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterInt::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterInt::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterInt.h" + +UHoudiniParameterInt::UHoudiniParameterInt(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Unit() + , bHasMin(false) + , bHasMax(false) + , bHasUIMin(false) + , bHasUIMax(false) + , bIsLogarithmic(false) + , Min(0) + , Max(0) + , UIMin(0) + , UIMax(0) +{ + ParmType = EHoudiniParameterType::Int; +} + +UHoudiniParameterInt * +UHoudiniParameterInt::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterInt_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterInt::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterInt * HoudiniAssetParameter = NewObject< UHoudiniParameterInt >( + InOuter, UHoudiniParameterInt::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Int); + HoudiniAssetParameter->bIsLogarithmic = false; + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +TOptional +UHoudiniParameterInt::GetValue(int32 Idx) const +{ + if (Values.IsValidIndex(Idx)) + return TOptional(Values[Idx]); + else + return TOptional(); +} + +bool +UHoudiniParameterInt::GetValueAt(const int32& AtIndex, int32& OutValue) const +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + OutValue = Values[AtIndex]; + + return true; +} + +bool +UHoudiniParameterInt::SetValueAt(const int32& InValue, const int32& AtIndex) +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + if (InValue == Values[AtIndex]) + return false; + + Values[AtIndex] = FMath::Clamp< int32 >(InValue, Min, Max); + + return true; +} + +void +UHoudiniParameterInt::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterInt::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterInt::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h index 1c21464d6..2cde7aa9c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h @@ -1,131 +1,132 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterInt.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterInt : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterInt * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetUnit() const { return Unit; }; - - bool HasMin() const { return bHasMin; }; - bool HasMax() const { return bHasMax; }; - bool HasUIMin() const { return bHasUIMin; }; - bool HasUIMax() const { return bHasUIMax; }; - bool IsLogarithmic() const { return bIsLogarithmic; }; - - int32 GetMin() const { return Min; }; - int32 GetMax() const { return Max; }; - int32 GetUIMin() const { return UIMin; }; - int32 GetUIMax() const { return UIMax; }; - - // Get value of this property - TOptional GetValue(int32 Idx) const; - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - bool IsDefaultValueAtIndex(const int32& Idx) const; - bool IsDefault() const override; - - // Mutators - void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; - void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; - void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; - void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; - - void SetMin(const int32& InMin) { Min = InMin; }; - void SetMax(const int32& InMax) { Max = InMax; }; - void SetUIMin(const int32& InUIMin) { UIMin = InUIMin; }; - void SetUIMax(const int32& InUIMax) { UIMax = InUIMax; }; - void SetIsLogarithmic(const bool& bInIsLog) { bIsLogarithmic = bInIsLog; }; - - void SetUnit(const FString& InUnit) { Unit = InUnit; }; - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - bool SetValueAt(const int32& InValue, const int32& AtIndex); - - void SetDefaultValues(); - -protected: - - // Int Values - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; - - // Unit for this property - UPROPERTY() - FString Unit; - - // Indicates we have a min/max value - UPROPERTY() - bool bHasMin; - // - UPROPERTY() - bool bHasMax; - - // Indicates we have a UI min/max - UPROPERTY() - bool bHasUIMin; - // - UPROPERTY() - bool bHasUIMax; - - UPROPERTY() - bool bIsLogarithmic; - - // Min and Max values for this property. - UPROPERTY() - int32 Min; - // - UPROPERTY() - int32 Max; - - // Min and Max values of this property for slider UI - UPROPERTY() - int32 UIMin; - // - UPROPERTY() - int32 UIMax; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterInt.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterInt : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterInt * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetUnit() const { return Unit; }; + + bool HasMin() const { return bHasMin; }; + bool HasMax() const { return bHasMax; }; + bool HasUIMin() const { return bHasUIMin; }; + bool HasUIMax() const { return bHasUIMax; }; + bool IsLogarithmic() const { return bIsLogarithmic; }; + + int32 GetMin() const { return Min; }; + int32 GetMax() const { return Max; }; + int32 GetUIMin() const { return UIMin; }; + int32 GetUIMax() const { return UIMax; }; + + // Get value of this property + TOptional GetValue(int32 Idx) const; + bool GetValueAt(const int32& AtIndex, int32& OutValue) const; + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + bool IsDefaultValueAtIndex(const int32& Idx) const; + bool IsDefault() const override; + + // Mutators + void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; + void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; + void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; + void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; + + void SetMin(const int32& InMin) { Min = InMin; }; + void SetMax(const int32& InMax) { Max = InMax; }; + void SetUIMin(const int32& InUIMin) { UIMin = InUIMin; }; + void SetUIMax(const int32& InUIMax) { UIMax = InUIMax; }; + void SetIsLogarithmic(const bool& bInIsLog) { bIsLogarithmic = bInIsLog; }; + + void SetUnit(const FString& InUnit) { Unit = InUnit; }; + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + bool SetValueAt(const int32& InValue, const int32& AtIndex); + + void SetDefaultValues(); + +protected: + + // Int Values + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; + + // Unit for this property + UPROPERTY() + FString Unit; + + // Indicates we have a min/max value + UPROPERTY() + bool bHasMin; + // + UPROPERTY() + bool bHasMax; + + // Indicates we have a UI min/max + UPROPERTY() + bool bHasUIMin; + // + UPROPERTY() + bool bHasUIMax; + + UPROPERTY() + bool bIsLogarithmic; + + // Min and Max values for this property. + UPROPERTY() + int32 Min; + // + UPROPERTY() + int32 Max; + + // Min and Max values of this property for slider UI + UPROPERTY() + int32 UIMin; + // + UPROPERTY() + int32 UIMax; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp index a1939516d..97a233bba 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp @@ -1,62 +1,62 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterLabel.h" - -UHoudiniParameterLabel::UHoudiniParameterLabel(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Label; -} - -UHoudiniParameterLabel * -UHoudiniParameterLabel::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterLabel_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterLabel::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterLabel * HoudiniAssetParameter = NewObject< UHoudiniParameterLabel >( - InOuter, UHoudiniParameterLabel::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Label); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -FString -UHoudiniParameterLabel::GetStringAtIndex(int32 Index) -{ - if (LabelStrings.IsValidIndex(Index)) - { - return LabelStrings[Index]; - } - - return FString(""); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterLabel.h" + +UHoudiniParameterLabel::UHoudiniParameterLabel(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Label; +} + +UHoudiniParameterLabel * +UHoudiniParameterLabel::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterLabel_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterLabel::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterLabel * HoudiniAssetParameter = NewObject< UHoudiniParameterLabel >( + InOuter, UHoudiniParameterLabel::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Label); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +FString +UHoudiniParameterLabel::GetStringAtIndex(int32 Index) +{ + if (LabelStrings.IsValidIndex(Index)) + { + return LabelStrings[Index]; + } + + return FString(""); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h index 780dda43c..8482aa542 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h @@ -1,56 +1,56 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterLabel.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterLabel : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterLabel * Create( - UObject* Outer, - const FString& ParamName); - - UPROPERTY() - TArray LabelStrings; - - FORCEINLINE - void AddLabelString(FString &NewString) { LabelStrings.Add(NewString); }; - - FString GetStringAtIndex(int32 Index); - - FORCEINLINE - void EmptyLabelString() { LabelStrings.Empty(); }; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterLabel.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterLabel : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterLabel * Create( + UObject* Outer, + const FString& ParamName); + + UPROPERTY() + TArray LabelStrings; + + FORCEINLINE + void AddLabelString(FString &NewString) { LabelStrings.Add(NewString); }; + + FString GetStringAtIndex(int32 Index); + + FORCEINLINE + void EmptyLabelString() { LabelStrings.Empty(); }; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp index 15aa3cf27..65850b8e0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp @@ -1,144 +1,154 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterMultiParm.h" - -UHoudiniParameterMultiParm::UHoudiniParameterMultiParm(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), bIsShown(false), InstanceStartOffset(0) -{ - // TODO Proper Init - ParmType = EHoudiniParameterType::MultiParm; -} - -UHoudiniParameterMultiParm * -UHoudiniParameterMultiParm::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterMultiParm::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterMultiParm * HoudiniAssetParameter = NewObject< UHoudiniParameterMultiParm >( - InOuter, UHoudiniParameterMultiParm::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::MultiParm); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->DefaultInstanceCount = -1; - - return HoudiniAssetParameter; -} - - -void -UHoudiniParameterMultiParm::InsertElement() -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); -} - -void -UHoudiniParameterMultiParm::InsertElementAt(int32 Index) -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - if (Index >= MultiParmInstanceLastModifyArray.Num()) - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); - else - MultiParmInstanceLastModifyArray.Insert(EHoudiniMultiParmModificationType::Inserted, Index); -} - -/** Decrement value, used by Slate. **/ -void -UHoudiniParameterMultiParm::RemoveElement(int32 Index) -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - // Remove the last element - if (Index == -1) - { - Index = MultiParmInstanceLastModifyArray.Num() - 1; - while (MultiParmInstanceLastModifyArray.IsValidIndex(Index) && MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Removed) - Index -= 1; - } - - if (MultiParmInstanceLastModifyArray.IsValidIndex(Index)) - { - // If the removed is a to be inserted instance, simply remove it. - if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) - MultiParmInstanceLastModifyArray.RemoveAt(Index); - // Otherwise mark it as to be removed. - else - MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; - } -} - -void -UHoudiniParameterMultiParm::EmptyElements() -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - for (int32 Index = MultiParmInstanceLastModifyArray.Num() - 1; Index >= 0; --Index) - { - // If the removed is a to be inserted instance, simply remove it. - // Interation starts from the tail, so that the indices won't be changed by element removal. - if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) - MultiParmInstanceLastModifyArray.RemoveAt(Index); - else // Otherwise mark it as to be removed. - MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; - } -} - -void -UHoudiniParameterMultiParm::InitializeModifyArray() -{ - for (uint32 Index = 0; Index < MultiParmInstanceCount; ++Index) - { - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::None); - } -} - -bool -UHoudiniParameterMultiParm::IsDefault() const -{ - //UE_LOG(LogTemp, Warning, TEXT("%d, %d"), MultiParmInstanceNum, MultiParmInstanceCount); - return DefaultInstanceCount == MultiParmInstanceCount; -} - -void -UHoudiniParameterMultiParm::SetDefaultInstanceCount(int32 InCount) -{ - if (DefaultInstanceCount >= 0) - return; - - DefaultInstanceCount = InCount; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterMultiParm.h" + +UHoudiniParameterMultiParm::UHoudiniParameterMultiParm(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), bIsShown(false), InstanceStartOffset(0) +{ + // TODO Proper Init + ParmType = EHoudiniParameterType::MultiParm; +} + +UHoudiniParameterMultiParm * +UHoudiniParameterMultiParm::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterMultiParm::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterMultiParm * HoudiniAssetParameter = NewObject< UHoudiniParameterMultiParm >( + InOuter, UHoudiniParameterMultiParm::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::MultiParm); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->DefaultInstanceCount = -1; + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterMultiParm::SetValue(const int32& InValue) +{ + if (InValue == Value) + return false; + + Value = InValue; + + return true; +} + +void +UHoudiniParameterMultiParm::InsertElement() +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); +} + +void +UHoudiniParameterMultiParm::InsertElementAt(int32 Index) +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + if (Index >= MultiParmInstanceLastModifyArray.Num()) + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); + else + MultiParmInstanceLastModifyArray.Insert(EHoudiniMultiParmModificationType::Inserted, Index); +} + +/** Decrement value, used by Slate. **/ +void +UHoudiniParameterMultiParm::RemoveElement(int32 Index) +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + // Remove the last element + if (Index == -1) + { + Index = MultiParmInstanceLastModifyArray.Num() - 1; + while (MultiParmInstanceLastModifyArray.IsValidIndex(Index) && MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Removed) + Index -= 1; + } + + if (MultiParmInstanceLastModifyArray.IsValidIndex(Index)) + { + // If the removed is a to be inserted instance, simply remove it. + if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) + MultiParmInstanceLastModifyArray.RemoveAt(Index); + // Otherwise mark it as to be removed. + else + MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; + } +} + +void +UHoudiniParameterMultiParm::EmptyElements() +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + for (int32 Index = MultiParmInstanceLastModifyArray.Num() - 1; Index >= 0; --Index) + { + // If the removed is a to be inserted instance, simply remove it. + // Interation starts from the tail, so that the indices won't be changed by element removal. + if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) + MultiParmInstanceLastModifyArray.RemoveAt(Index); + else // Otherwise mark it as to be removed. + MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; + } +} + +void +UHoudiniParameterMultiParm::InitializeModifyArray() +{ + for (uint32 Index = 0; Index < MultiParmInstanceCount; ++Index) + { + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::None); + } +} + +bool +UHoudiniParameterMultiParm::IsDefault() const +{ + //UE_LOG(LogTemp, Warning, TEXT("%d, %d"), MultiParmInstanceNum, MultiParmInstanceCount); + return DefaultInstanceCount == MultiParmInstanceCount; +} + +void +UHoudiniParameterMultiParm::SetDefaultInstanceCount(int32 InCount) +{ + if (DefaultInstanceCount >= 0) + return; + + DefaultInstanceCount = InCount; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h index 9536fd70d..735b2f474 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h @@ -1,129 +1,128 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterMultiParm.generated.h" - -UENUM() -enum class EHoudiniMultiParmModificationType : uint8 -{ - None, - - Inserted, - Removed, - Modified -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterMultiParm * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FORCEINLINE - int32 GetValue() const { return Value; }; - FORCEINLINE - int32 GetInstanceCount() const { return MultiParmInstanceCount; }; - - // Mutators - FORCEINLINE - void SetValue(const int32& InValue) { Value = InValue; }; - FORCEINLINE - void SetInstanceCount(const int32 InCount) { MultiParmInstanceCount = InCount; }; - - FORCEINLINE - void SetIsShown(const bool InIsShown) { bIsShown = InIsShown; }; - - FORCEINLINE - bool IsShown() const { return bIsShown; }; - - - /** Increment value, used by Slate. **/ - void InsertElement(); - - void InsertElementAt(int32 Index); - - /** Decrement value, used by Slate. **/ - void RemoveElement(int32 Index); - - /** Empty the values, used by Slate. **/ - void EmptyElements(); - - UPROPERTY() - bool bIsShown; - - // Value of the multiparm - UPROPERTY() - int32 Value; - - // - UPROPERTY() - FString TemplateName; - - // Value of this property. - UPROPERTY() - int32 MultiparmValue; - - // - UPROPERTY() - uint32 MultiParmInstanceNum; - - // - UPROPERTY() - uint32 MultiParmInstanceLength; - - // - UPROPERTY() - uint32 MultiParmInstanceCount; - - UPROPERTY() - uint32 InstanceStartOffset; - - // This array records the last modified instance of the multiparm - UPROPERTY() - TArray MultiParmInstanceLastModifyArray; - - UPROPERTY() - int32 DefaultInstanceCount; - - bool IsDefault() const override; - - void SetDefaultInstanceCount(int32 InCount); - -private: - void InitializeModifyArray(); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterMultiParm.generated.h" + +UENUM() +enum class EHoudiniMultiParmModificationType : uint8 +{ + None, + + Inserted, + Removed, + Modified +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterMultiParm * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FORCEINLINE + int32 GetValue() const { return Value; }; + FORCEINLINE + int32 GetInstanceCount() const { return MultiParmInstanceCount; }; + + // Mutators + bool SetValue(const int32& InValue); + FORCEINLINE + void SetInstanceCount(const int32 InCount) { MultiParmInstanceCount = InCount; }; + + FORCEINLINE + void SetIsShown(const bool InIsShown) { bIsShown = InIsShown; }; + + FORCEINLINE + bool IsShown() const { return bIsShown; }; + + + /** Increment value, used by Slate. **/ + void InsertElement(); + + void InsertElementAt(int32 Index); + + /** Decrement value, used by Slate. **/ + void RemoveElement(int32 Index); + + /** Empty the values, used by Slate. **/ + void EmptyElements(); + + UPROPERTY() + bool bIsShown; + + // Value of the multiparm + UPROPERTY() + int32 Value; + + // + UPROPERTY() + FString TemplateName; + + // Value of this property. + UPROPERTY() + int32 MultiparmValue; + + // + UPROPERTY() + uint32 MultiParmInstanceNum; + + // + UPROPERTY() + uint32 MultiParmInstanceLength; + + // + UPROPERTY() + uint32 MultiParmInstanceCount; + + UPROPERTY() + uint32 InstanceStartOffset; + + // This array records the last modified instance of the multiparm + UPROPERTY() + TArray MultiParmInstanceLastModifyArray; + + UPROPERTY() + int32 DefaultInstanceCount; + + bool IsDefault() const override; + + void SetDefaultInstanceCount(int32 InCount); + +private: + void InitializeModifyArray(); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp index 2bf983369..9b237e3cf 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp @@ -1,95 +1,95 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniInput.h" - - -UHoudiniParameterOperatorPath::UHoudiniParameterOperatorPath( - const FObjectInitializer &ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -UHoudiniParameterOperatorPath * -UHoudiniParameterOperatorPath::Create(UObject *InOuter, const FString &InParamName) -{ - FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; - FName ParamName = MakeUniqueObjectName( - InOuter, UHoudiniParameterOperatorPath::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterOperatorPath *HoudiniAssetParameter = - NewObject( - InOuter, UHoudiniParameterOperatorPath::StaticClass(), ParamName, - RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Input); - - - return HoudiniAssetParameter; -} - -bool UHoudiniParameterOperatorPath::HasChanged() const -{ - if (Super::HasChanged()) - return true; - if (HoudiniInput.IsValid() && HoudiniInput->HasChanged()) - return true; - return false; -} - -bool UHoudiniParameterOperatorPath::NeedsToTriggerUpdate() const -{ - if (Super::NeedsToTriggerUpdate()) - return true; - if (HoudiniInput.IsValid() && HoudiniInput->NeedsToTriggerUpdate()) - return true; - return false; -} - -void UHoudiniParameterOperatorPath::MarkChanged(const bool & bInChanged) -{ - Super::MarkChanged(bInChanged); -} - -void -UHoudiniParameterOperatorPath::RemapInputs(const TMap& InputMapping) -{ - if (!HoudiniInput.IsValid()) - return; - - if (InputMapping.Contains(HoudiniInput.Get())) - { - HoudiniInput = InputMapping.FindRef(HoudiniInput.Get()); - } - else - { - HoudiniInput = nullptr; - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniInput.h" + + +UHoudiniParameterOperatorPath::UHoudiniParameterOperatorPath( + const FObjectInitializer &ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniParameterOperatorPath * +UHoudiniParameterOperatorPath::Create(UObject *InOuter, const FString &InParamName) +{ + FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; + FName ParamName = MakeUniqueObjectName( + InOuter, UHoudiniParameterOperatorPath::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterOperatorPath *HoudiniAssetParameter = + NewObject( + InOuter, UHoudiniParameterOperatorPath::StaticClass(), ParamName, + RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Input); + + + return HoudiniAssetParameter; +} + +bool UHoudiniParameterOperatorPath::HasChanged() const +{ + if (Super::HasChanged()) + return true; + if (HoudiniInput.IsValid() && HoudiniInput->HasChanged()) + return true; + return false; +} + +bool UHoudiniParameterOperatorPath::NeedsToTriggerUpdate() const +{ + if (Super::NeedsToTriggerUpdate()) + return true; + if (HoudiniInput.IsValid() && HoudiniInput->NeedsToTriggerUpdate()) + return true; + return false; +} + +void UHoudiniParameterOperatorPath::MarkChanged(const bool & bInChanged) +{ + Super::MarkChanged(bInChanged); +} + +void +UHoudiniParameterOperatorPath::RemapInputs(const TMap& InputMapping) +{ + if (!HoudiniInput.IsValid()) + return; + + if (InputMapping.Contains(HoudiniInput.Get())) + { + HoudiniInput = InputMapping.FindRef(HoudiniInput.Get()); + } + else + { + HoudiniInput = nullptr; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h index c22b93e5e..3960e55ee 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h @@ -1,58 +1,58 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterOperatorPath.generated.h" - - -class UHoudiniInput; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterOperatorPath - : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - // Create instance of this class. - static UHoudiniParameterOperatorPath * - Create(UObject *Outer, const FString &ParamName); - - virtual bool HasChanged() const override; - virtual bool NeedsToTriggerUpdate() const override; - virtual void MarkChanged(const bool& bInChanged) override; - - //------------------------------------------------------------------------------------------------ - // UHoudiniParameter overrides - //------------------------------------------------------------------------------------------------ - virtual void RemapInputs(const TMap& InputMapping) override; - - UPROPERTY() - TWeakObjectPtr HoudiniInput; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterOperatorPath.generated.h" + + +class UHoudiniInput; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterOperatorPath + : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + // Create instance of this class. + static UHoudiniParameterOperatorPath * + Create(UObject *Outer, const FString &ParamName); + + virtual bool HasChanged() const override; + virtual bool NeedsToTriggerUpdate() const override; + virtual void MarkChanged(const bool& bInChanged) override; + + //------------------------------------------------------------------------------------------------ + // UHoudiniParameter overrides + //------------------------------------------------------------------------------------------------ + virtual void RemapInputs(const TMap& InputMapping) override; + + UPROPERTY() + TWeakObjectPtr HoudiniInput; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp index 1fcd9bb92..9c0a07e02 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp @@ -1,701 +1,969 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterRamp.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterChoice.h" - -#include "UObject/UnrealType.h" - - -void -UHoudiniParameterRampFloatPoint::SetPosition(const float InPosition) -{ - if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Position = InPosition; - PositionParentParm->SetValueAt(InPosition, 0); - PositionParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampFloatPoint::SetValue(const float InValue) -{ - if (ValueParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Value = InValue; - ValueParentParm->SetValueAt(InValue, 0); - ValueParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampFloatPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) -{ - if (InterpolationParentParm) - { - Interpolation = InInterpolation; - InterpolationParentParm->SetIntValue((int32)InInterpolation); - InterpolationParentParm->UpdateStringValueFromInt(); - InterpolationParentParm->SetIsChildOfRamp(); - } -} - -UHoudiniParameterRampFloatPoint* -UHoudiniParameterRampFloatPoint::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - UHoudiniParameterRampFloatPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); - - NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); - - return NewPoint; -} - -void -UHoudiniParameterRampFloatPoint::CopyStateFrom(UHoudiniParameterRampFloatPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - // Ensure this object's flags match the desired flags. - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -} - -void -UHoudiniParameterRampFloatPoint::RemapParameters( - const TMap& ParameterMapping) -{ - if (ParameterMapping.Contains(PositionParentParm)) - { - PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); - } - else - { - PositionParentParm = nullptr; - } - - if (ParameterMapping.Contains(ValueParentParm)) - { - ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); - } - else - { - ValueParentParm = nullptr; - } - - if (ParameterMapping.Contains(InterpolationParentParm)) - { - InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); - } - else - { - InterpolationParentParm = nullptr; - } -}; - - -void -UHoudiniParameterRampColorPoint::SetPosition(const float InPosition) -{ - if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Position = InPosition; - PositionParentParm->SetValueAt(InPosition, 0); - PositionParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampColorPoint::SetValue(const FLinearColor InValue) -{ - if (!ValueParentParm) - return; - - Value = InValue; - ValueParentParm->SetColorValue(InValue); - ValueParentParm->SetIsChildOfRamp(); -}; - -void -UHoudiniParameterRampColorPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) -{ - if (!InterpolationParentParm) - return; - - Interpolation = InInterpolation; - InterpolationParentParm->SetIntValue((int32)InInterpolation); - InterpolationParentParm->UpdateStringValueFromInt(); - InterpolationParentParm->SetIsChildOfRamp(); -} -UHoudiniParameterRampColorPoint * UHoudiniParameterRampColorPoint::DuplicateAndCopyState(UObject * DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - - UHoudiniParameterRampColorPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); - - NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); - - return NewPoint; - -} -void UHoudiniParameterRampColorPoint::CopyStateFrom(UHoudiniParameterRampColorPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ -#if WITH_EDITOR - PreEditChange(nullptr); -#endif - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - // Ensure this object's flags match the desired flags. - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -#if WITH_EDITOR - PostEditChange(); -#endif -} - -void UHoudiniParameterRampColorPoint::RemapParameters(const TMap& ParameterMapping) -{ - if (ParameterMapping.Contains(PositionParentParm)) - { - PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); - } - else - { - PositionParentParm = nullptr; - } - - if (ParameterMapping.Contains(ValueParentParm)) - { - ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); - } - else - { - ValueParentParm = nullptr; - } - - if (ParameterMapping.Contains(InterpolationParentParm)) - { - InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); - } - else - { - InterpolationParentParm = nullptr; - } -} - - -UHoudiniParameterRampFloat::UHoudiniParameterRampFloat(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), - NumDefaultPoints(-1), - bCaching(false) -{ - ParmType = EHoudiniParameterType::FloatRamp; -} - -void -UHoudiniParameterRampFloat::OnPreCook() -{ - if (bCaching) - { - SyncCachedPoints(); - bCaching = false; - } -} - -UHoudiniParameterRampFloat * -UHoudiniParameterRampFloat::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampFloat::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterRampFloat * HoudiniParameter = NewObject< UHoudiniParameterRampFloat >( - InOuter, UHoudiniParameterRampFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniParameter->SetParameterType(EHoudiniParameterType::FloatRamp); - - HoudiniParameter->NumDefaultPoints = -1; - - HoudiniParameter->bCaching = false; - - return HoudiniParameter; -} - -void UHoudiniParameterRampFloat::CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ -#if WITH_EDITOR - PreEditChange(nullptr); -#endif - - UHoudiniParameterRampFloat* FromParameter = Cast(InParameter); - check(FromParameter); - - TArray PrevCachedPoints = CachedPoints; - TArray PrevPoints = Points; - - Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); - - CachedPoints = PrevCachedPoints; - Points = PrevPoints; - - - auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) - { - const int32 NumPts = FromArray.Num(); - - ToArray.SetNum(NumPts); - - for(int32 i = 0; i < NumPts; ++i) - { - UHoudiniParameterRampFloatPoint* FromPoint = FromArray[i]; - UHoudiniParameterRampFloatPoint* ToPoint = ToArray[i]; - - check(FromPoint); - - if (ToPoint) - { - // Ensure the destination point is outered to this parameter - bool bIsValid = ToPoint->GetOuter() == NewOuter; - if (!bIsValid) - ToPoint = nullptr; - } - - if (!ToPoint) - { - // Duplicate a new copy using FromPoint - ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); - } - else - { - // We have a valid point that we can reuse. Simply copy state. - ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); - } - - ToArray[i] = ToPoint; - } - }; - - CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); - CopyPointsStateFn(FromParameter->Points, Points, this); - -#if WITH_EDITOR - PostEditChange(); -#endif -} - -void -UHoudiniParameterRampFloat::RemapParameters(const TMap& ParameterMapping) -{ - Super::RemapParameters(ParameterMapping); - - AActor* OuterActor = GetTypedOuter(); - - - for(UHoudiniParameterRampFloatPoint* CurrentPoint : Points) - { - CurrentPoint->RemapParameters(ParameterMapping); - } - - for(UHoudiniParameterRampFloatPoint* CurrentPoint : CachedPoints) - { - CurrentPoint->RemapParameters(ParameterMapping); - } -} - -void -UHoudiniParameterRampFloat::SyncCachedPoints() -{ - int32 Idx = 0; - - while (Idx < CachedPoints.Num() && Idx < Points.Num()) - { - UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; - UHoudiniParameterRampFloatPoint* &CurrentPoint = Points[Idx]; - - if (!CachedPoint || !CurrentPoint) - continue; - - if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) - { - if (CurrentPoint->PositionParentParm) - { - CurrentPoint->SetPosition(CachedPoint->GetPosition()); - CurrentPoint->PositionParentParm->MarkChanged(true); - } - } - - if (CachedPoint->GetValue() != CurrentPoint->GetValue()) - { - if (CurrentPoint->ValueParentParm) - { - CurrentPoint->SetValue(CachedPoint->GetValue()); - CurrentPoint->ValueParentParm->MarkChanged(true); - } - } - - if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) - { - if (CurrentPoint->InterpolationParentParm) - { - CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); - CurrentPoint->InterpolationParentParm->MarkChanged(true); - } - } - - Idx += 1; - } - - // Insert points - for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) - { - UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; - if (!CachedPoint) - continue; - - CreateInsertEvent(CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); - - MarkChanged(true); - } - - // Remove points - for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < Points.Num(); ++IdxCurrentPointLeft) - { - RemoveElement(IdxCurrentPointLeft); - - UHoudiniParameterRampFloatPoint* Point = Points[IdxCurrentPointLeft]; - - if (!Point) - continue; - - CreateDeleteEvent(Point->InstanceIndex); - - MarkChanged(true); - } -} - - -void -UHoudiniParameterRampFloat::CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) -{ - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - this, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetFloatRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertFloat = InValue; - InsertEvent->InsertInterpolation = InInterp; - - ModificationEvents.Add(InsertEvent); -} - -void -UHoudiniParameterRampFloat::CreateDeleteEvent(const int32 &InDeleteIndex) -{ - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - this, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetFloatRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - ModificationEvents.Add(DeleteEvent); -} - - -UHoudiniParameterRampColor::UHoudiniParameterRampColor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), - bCaching(false), - NumDefaultPoints(-1) -{ - ParmType = EHoudiniParameterType::ColorRamp; -} - - -UHoudiniParameterRampColor * -UHoudiniParameterRampColor::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampColor::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterRampColor * HoudiniParameter = NewObject< UHoudiniParameterRampColor >( - InOuter, UHoudiniParameterRampColor::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniParameter->SetParameterType(EHoudiniParameterType::ColorRamp); - - HoudiniParameter->NumDefaultPoints = -1; - - HoudiniParameter->bCaching = false; - - return HoudiniParameter; -} - - -bool -UHoudiniParameterRampFloat::IsDefault() const -{ - if (NumDefaultPoints < 0) - return true; - - if (NumDefaultPoints != Points.Num()) - { - return false; - } - - TArray Positions = DefaultPositions; - TArray Values = DefaultValues; - TArray Choices = DefaultChoices; - - for (auto & NextPt : Points) - { - if (!NextPt) - return false; - - bool bFoundMatch = false; - for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) - { - if (Positions[DefaultIdx] == NextPt->Position && - Values[DefaultIdx] == NextPt->Value && - Choices[DefaultIdx] == (int32)NextPt->Interpolation) - { - Positions.RemoveAt(DefaultIdx); - Values.RemoveAt(DefaultIdx); - Choices.RemoveAt(DefaultIdx); - bFoundMatch = true; - } - } - - if (!bFoundMatch) - return false; - } - - if (Positions.Num() > 0) - return false; - - return true; -} - -void UHoudiniParameterRampColor::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - UHoudiniParameterRampColor* FromParameter = Cast(InParameter); - check(FromParameter); - - TArray PrevCachedPoints = CachedPoints; - TArray PrevPoints = Points; - - Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); - - CachedPoints = PrevCachedPoints; - Points = PrevPoints; - - - auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) - { - const int32 NumPts = FromArray.Num(); - - ToArray.SetNum(NumPts); - - for(int32 i = 0; i < NumPts; ++i) - { - UHoudiniParameterRampColorPoint* FromPoint = FromArray[i]; - UHoudiniParameterRampColorPoint* ToPoint = ToArray[i]; - - check(FromPoint); - - if (ToPoint) - { - // Ensure the destination point is outered to this parameter - bool bIsValid = ToPoint->GetOuter() == NewOuter; - if (!bIsValid) - ToPoint = nullptr; - } - - if (!ToPoint) - { - // Duplicate a new copy using FromPoint - ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); - } - else - { - // We have a valid point that we can reuse. Simply copy state. - ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); - } - - ToArray[i] = ToPoint; - } - }; - - CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); - CopyPointsStateFn(FromParameter->Points, Points, this); - - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -} - -void UHoudiniParameterRampColor::RemapParameters(const TMap& ParameterMapping) -{ - Super::RemapParameters(ParameterMapping); - - AActor* OuterActor = GetTypedOuter(); - - - for(UHoudiniParameterRampColorPoint* CurrentPoint : Points) - { - CurrentPoint->RemapParameters(ParameterMapping); - } - - for(UHoudiniParameterRampColorPoint* CurrentPoint : CachedPoints) - { - CurrentPoint->RemapParameters(ParameterMapping); - } -} - -bool -UHoudiniParameterRampColor::IsDefault() const -{ - if (NumDefaultPoints < 0) - return true; - - if (NumDefaultPoints != Points.Num()) - return false; - - TArray Positions = DefaultPositions; - TArray Values = DefaultValues; - TArray Choices = DefaultChoices; - - for (auto & NextPt : Points) - { - if (!NextPt) - return false; - - bool bFoundMatch = false; - for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) - { - if (Positions[DefaultIdx] == NextPt->Position && - Values[DefaultIdx] == NextPt->Value && - Choices[DefaultIdx] == (int32)NextPt->Interpolation) - { - Positions.RemoveAt(DefaultIdx); - Values.RemoveAt(DefaultIdx); - Choices.RemoveAt(DefaultIdx); - bFoundMatch = true; - } - } - - if (!bFoundMatch) - return false; - } - - if (Positions.Num() > 0) - return false; - - return true; -} - - -void -UHoudiniParameterRampColor::SetDefaultValues() -{ - if (NumDefaultPoints >= 0) - return; - - - if (DefaultPositions.Num() > 0) - return; - - DefaultPositions.Empty(); - DefaultValues.Empty(); - DefaultChoices.Empty(); - - for (auto & NextPoint : Points) - { - if (!NextPoint) - continue; - - DefaultPositions.Add(NextPoint->Position); - DefaultValues.Add(NextPoint->Value); - DefaultChoices.Add((int32)NextPoint->Interpolation); - - } - - NumDefaultPoints = Points.Num(); -} - -void -UHoudiniParameterRampFloat::SetDefaultValues() -{ - if (DefaultPositions.Num() > 0) - return; - - DefaultPositions.Empty(); - DefaultValues.Empty(); - DefaultChoices.Empty(); - - for (auto & NextPoint : Points) - { - if (!NextPoint) - continue; - - DefaultPositions.Add(NextPoint->Position); - DefaultValues.Add(NextPoint->Value); - DefaultChoices.Add((int32)NextPoint->Interpolation); - - } - - NumDefaultPoints = Points.Num(); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterRamp.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterChoice.h" + +#include "UObject/UnrealType.h" + + +void +UHoudiniParameterRampFloatPoint::SetPosition(const float InPosition) +{ + if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Position = InPosition; + PositionParentParm->SetValueAt(InPosition, 0); + PositionParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampFloatPoint::SetValue(const float InValue) +{ + if (ValueParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Value = InValue; + ValueParentParm->SetValueAt(InValue, 0); + ValueParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampFloatPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) +{ + if (InterpolationParentParm) + { + Interpolation = InInterpolation; + InterpolationParentParm->SetIntValue((int32)InInterpolation); + InterpolationParentParm->UpdateStringValueFromInt(); + InterpolationParentParm->SetIsChildOfRamp(); + } +} + +UHoudiniParameterRampFloatPoint* +UHoudiniParameterRampFloatPoint::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + UHoudiniParameterRampFloatPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); + + NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); + + return NewPoint; +} + +void +UHoudiniParameterRampFloatPoint::CopyStateFrom(UHoudiniParameterRampFloatPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + // Ensure this object's flags match the desired flags. + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +} + +void +UHoudiniParameterRampFloatPoint::RemapParameters( + const TMap& ParameterMapping) +{ + if (ParameterMapping.Contains(PositionParentParm)) + { + PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); + } + else + { + PositionParentParm = nullptr; + } + + if (ParameterMapping.Contains(ValueParentParm)) + { + ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); + } + else + { + ValueParentParm = nullptr; + } + + if (ParameterMapping.Contains(InterpolationParentParm)) + { + InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); + } + else + { + InterpolationParentParm = nullptr; + } +}; + + +void +UHoudiniParameterRampColorPoint::SetPosition(const float InPosition) +{ + if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Position = InPosition; + PositionParentParm->SetValueAt(InPosition, 0); + PositionParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampColorPoint::SetValue(const FLinearColor InValue) +{ + if (!ValueParentParm) + return; + + Value = InValue; + ValueParentParm->SetColorValue(InValue); + ValueParentParm->SetIsChildOfRamp(); +}; + +void +UHoudiniParameterRampColorPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) +{ + if (!InterpolationParentParm) + return; + + Interpolation = InInterpolation; + InterpolationParentParm->SetIntValue((int32)InInterpolation); + InterpolationParentParm->UpdateStringValueFromInt(); + InterpolationParentParm->SetIsChildOfRamp(); +} +UHoudiniParameterRampColorPoint * UHoudiniParameterRampColorPoint::DuplicateAndCopyState(UObject * DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + + UHoudiniParameterRampColorPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); + + NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); + + return NewPoint; + +} +void UHoudiniParameterRampColorPoint::CopyStateFrom(UHoudiniParameterRampColorPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ +#if WITH_EDITOR + PreEditChange(nullptr); +#endif + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + // Ensure this object's flags match the desired flags. + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +#if WITH_EDITOR + PostEditChange(); +#endif +} + +void UHoudiniParameterRampColorPoint::RemapParameters(const TMap& ParameterMapping) +{ + if (ParameterMapping.Contains(PositionParentParm)) + { + PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); + } + else + { + PositionParentParm = nullptr; + } + + if (ParameterMapping.Contains(ValueParentParm)) + { + ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); + } + else + { + ValueParentParm = nullptr; + } + + if (ParameterMapping.Contains(InterpolationParentParm)) + { + InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); + } + else + { + InterpolationParentParm = nullptr; + } +} + + +UHoudiniParameterRampFloat::UHoudiniParameterRampFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), + NumDefaultPoints(-1), + bCaching(false) +{ + ParmType = EHoudiniParameterType::FloatRamp; +} + +void +UHoudiniParameterRampFloat::OnPreCook() +{ + if (bCaching) + { + SyncCachedPoints(); + bCaching = false; + } +} + +UHoudiniParameterRampFloat * +UHoudiniParameterRampFloat::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampFloat::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterRampFloat * HoudiniParameter = NewObject< UHoudiniParameterRampFloat >( + InOuter, UHoudiniParameterRampFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniParameter->SetParameterType(EHoudiniParameterType::FloatRamp); + + HoudiniParameter->NumDefaultPoints = -1; + + HoudiniParameter->bCaching = false; + + return HoudiniParameter; +} + +void UHoudiniParameterRampFloat::CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ +#if WITH_EDITOR + PreEditChange(nullptr); +#endif + + UHoudiniParameterRampFloat* FromParameter = Cast(InParameter); + check(FromParameter); + + TArray PrevCachedPoints = CachedPoints; + TArray PrevPoints = Points; + + Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); + + CachedPoints = PrevCachedPoints; + Points = PrevPoints; + + + auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) + { + const int32 NumPts = FromArray.Num(); + + ToArray.SetNum(NumPts); + + for(int32 i = 0; i < NumPts; ++i) + { + UHoudiniParameterRampFloatPoint* FromPoint = FromArray[i]; + UHoudiniParameterRampFloatPoint* ToPoint = ToArray[i]; + + check(FromPoint); + + if (ToPoint) + { + // Ensure the destination point is outered to this parameter + bool bIsValid = ToPoint->GetOuter() == NewOuter; + if (!bIsValid) + ToPoint = nullptr; + } + + if (!ToPoint) + { + // Duplicate a new copy using FromPoint + ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); + } + else + { + // We have a valid point that we can reuse. Simply copy state. + ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); + } + + ToArray[i] = ToPoint; + } + }; + + CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); + CopyPointsStateFn(FromParameter->Points, Points, this); + +#if WITH_EDITOR + PostEditChange(); +#endif +} + +void +UHoudiniParameterRampFloat::RemapParameters(const TMap& ParameterMapping) +{ + Super::RemapParameters(ParameterMapping); + + AActor* OuterActor = GetTypedOuter(); + + + for(UHoudiniParameterRampFloatPoint* CurrentPoint : Points) + { + CurrentPoint->RemapParameters(ParameterMapping); + } + + for(UHoudiniParameterRampFloatPoint* CurrentPoint : CachedPoints) + { + CurrentPoint->RemapParameters(ParameterMapping); + } +} + +void +UHoudiniParameterRampFloat::SyncCachedPoints() +{ + int32 Idx = 0; + + while (Idx < CachedPoints.Num() && Idx < Points.Num()) + { + UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; + UHoudiniParameterRampFloatPoint* &CurrentPoint = Points[Idx]; + + if (!CachedPoint || !CurrentPoint) + continue; + + if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) + { + if (CurrentPoint->PositionParentParm) + { + CurrentPoint->SetPosition(CachedPoint->GetPosition()); + CurrentPoint->PositionParentParm->MarkChanged(true); + } + } + + if (CachedPoint->GetValue() != CurrentPoint->GetValue()) + { + if (CurrentPoint->ValueParentParm) + { + CurrentPoint->SetValue(CachedPoint->GetValue()); + CurrentPoint->ValueParentParm->MarkChanged(true); + } + } + + if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) + { + if (CurrentPoint->InterpolationParentParm) + { + CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); + CurrentPoint->InterpolationParentParm->MarkChanged(true); + } + } + + Idx += 1; + } + + // Insert points + for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) + { + UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; + if (!CachedPoint) + continue; + + CreateInsertEvent(CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); + + MarkChanged(true); + } + + // Remove points + for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < Points.Num(); ++IdxCurrentPointLeft) + { + RemoveElement(IdxCurrentPointLeft); + + UHoudiniParameterRampFloatPoint* Point = Points[IdxCurrentPointLeft]; + + if (!Point) + continue; + + CreateDeleteEvent(Point->InstanceIndex); + + MarkChanged(true); + } +} + + +void +UHoudiniParameterRampFloat::CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) +{ + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + this, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetFloatRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertFloat = InValue; + InsertEvent->InsertInterpolation = InInterp; + + ModificationEvents.Add(InsertEvent); +} + +void +UHoudiniParameterRampFloat::CreateDeleteEvent(const int32 &InDeleteIndex) +{ + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + this, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetFloatRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + ModificationEvents.Add(DeleteEvent); +} + +bool +UHoudiniParameterRampFloat::UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex) +{ + // Copy existing points to NewPoints + TArray NewPoints; + const int32 NumInstances = GetInstanceCount(); + NewPoints.Reserve(NumInstances); + for (UHoudiniParameterRampFloatPoint* const PointData : Points) + { + if (!IsValid(PointData)) + continue; + + PointData->InstanceIndex = NewPoints.Num(); + PointData->PositionParentParm = nullptr; + PointData->ValueParentParm = nullptr; + PointData->InterpolationParentParm = nullptr; + NewPoints.Add(PointData); + + // We don't need more than NumInstances points in the array + if (NewPoints.Num() == NumInstances) + break; + } + + // Loop over InParameters and look for children of this ramp + int32 CurrentInstanceIndex = 0; + const int32 NumParameters = InParameters.Num(); + for (int32 Index = InStartParamIndex; Index < NumParameters; ++Index) + { + UHoudiniParameter* const Param = InParameters[Index]; + if (!IsValid(Param)) + continue; + + if (!Param->GetIsChildOfMultiParm() || Param->GetParentParmId() != ParmId) + continue; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType != EHoudiniParameterType::Float && ParamType != EHoudiniParameterType::IntChoice) + continue; + + // Ensure we have a valid point object at the current index + UHoudiniParameterRampFloatPoint* Point = nullptr; + if (!NewPoints.IsValidIndex(CurrentInstanceIndex)) + { + Point = NewObject(this, FName(), this->GetMaskedFlags(RF_PropagateToSubObjects)); + Point->InstanceIndex = CurrentInstanceIndex; + NewPoints.Add(Point); + } + else + { + Point = NewPoints[CurrentInstanceIndex]; + } + + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParameter = Cast(Param); + if (FloatParameter) + { + //*****Float Parameter (position)*****// + if (!Point->PositionParentParm) + { + if (FloatParameter->GetNumberOfValues() <= 0) + continue; + // Set the float ramp point's position parent parm, and value + Point->PositionParentParm = FloatParameter; + Point->SetPosition(FloatParameter->GetValuesPtr()[0]); + } + //*****Float Parameter (value)*****// + else + { + if (FloatParameter->GetNumberOfValues() <= 0) + continue;; + Point->ValueParentParm = FloatParameter; + Point->SetValue(FloatParameter->GetValuesPtr()[0]); + } + } + } + //*****Choice parameter (Interpolation)*****// + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParameter = Cast(Param); + if (ChoiceParameter) + { + Point->InterpolationParentParm = ChoiceParameter; + Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + CurrentInstanceIndex++; + } + } + } + + //*****All ramp points have been parsed, finish!*****// + if (NewPoints.Num() == NumInstances) + { + NewPoints.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { + return P1.Position < P2.Position; + }); + + Points = MoveTemp(NewPoints); + + // Not caching, points are synced, update cached points + if (!bCaching) + { + const int32 NumPoints = Points.Num(); + CachedPoints.SetNumZeroed(NumPoints); + + for (int32 i = 0; i < NumPoints; ++i) + { + UHoudiniParameterRampFloatPoint* const FromPoint = Points[i]; + UHoudiniParameterRampFloatPoint* ToPoint = CachedPoints[i]; + + // Nothing we can do/copy if FromPoint is null/pending kill + if (!IsValid(FromPoint)) + continue; + + if (!IsValid(ToPoint)) + { + ToPoint = FromPoint->DuplicateAndCopyState(this, RF_NoFlags, GetMaskedFlags(RF_PropagateToSubObjects)); + CachedPoints[i] = ToPoint; + } + else + { + ToPoint->CopyStateFrom(FromPoint, true); + } + } + } + + SetDefaultValues(); + + return true; + } + + return false; +} + +UHoudiniParameterRampColor::UHoudiniParameterRampColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), + bCaching(false), + NumDefaultPoints(-1) +{ + ParmType = EHoudiniParameterType::ColorRamp; +} + + +UHoudiniParameterRampColor * +UHoudiniParameterRampColor::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampColor::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterRampColor * HoudiniParameter = NewObject< UHoudiniParameterRampColor >( + InOuter, UHoudiniParameterRampColor::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniParameter->SetParameterType(EHoudiniParameterType::ColorRamp); + + HoudiniParameter->NumDefaultPoints = -1; + + HoudiniParameter->bCaching = false; + + return HoudiniParameter; +} + + +bool +UHoudiniParameterRampFloat::IsDefault() const +{ + if (NumDefaultPoints < 0) + return true; + + if (NumDefaultPoints != Points.Num()) + { + return false; + } + + TArray Positions = DefaultPositions; + TArray Values = DefaultValues; + TArray Choices = DefaultChoices; + + for (auto & NextPt : Points) + { + if (!NextPt) + return false; + + bool bFoundMatch = false; + for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) + { + if (Positions[DefaultIdx] == NextPt->Position && + Values[DefaultIdx] == NextPt->Value && + Choices[DefaultIdx] == (int32)NextPt->Interpolation) + { + Positions.RemoveAt(DefaultIdx); + Values.RemoveAt(DefaultIdx); + Choices.RemoveAt(DefaultIdx); + bFoundMatch = true; + } + } + + if (!bFoundMatch) + return false; + } + + if (Positions.Num() > 0) + return false; + + return true; +} + +void UHoudiniParameterRampColor::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + UHoudiniParameterRampColor* FromParameter = Cast(InParameter); + check(FromParameter); + + TArray PrevCachedPoints = CachedPoints; + TArray PrevPoints = Points; + + Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); + + CachedPoints = PrevCachedPoints; + Points = PrevPoints; + + + auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) + { + const int32 NumPts = FromArray.Num(); + + ToArray.SetNum(NumPts); + + for(int32 i = 0; i < NumPts; ++i) + { + UHoudiniParameterRampColorPoint* FromPoint = FromArray[i]; + UHoudiniParameterRampColorPoint* ToPoint = ToArray[i]; + + check(FromPoint); + + if (ToPoint) + { + // Ensure the destination point is outered to this parameter + bool bIsValid = ToPoint->GetOuter() == NewOuter; + if (!bIsValid) + ToPoint = nullptr; + } + + if (!ToPoint) + { + // Duplicate a new copy using FromPoint + ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); + } + else + { + // We have a valid point that we can reuse. Simply copy state. + ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); + } + + ToArray[i] = ToPoint; + } + }; + + CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); + CopyPointsStateFn(FromParameter->Points, Points, this); + + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +} + +void UHoudiniParameterRampColor::RemapParameters(const TMap& ParameterMapping) +{ + Super::RemapParameters(ParameterMapping); + + AActor* OuterActor = GetTypedOuter(); + + + for(UHoudiniParameterRampColorPoint* CurrentPoint : Points) + { + CurrentPoint->RemapParameters(ParameterMapping); + } + + for(UHoudiniParameterRampColorPoint* CurrentPoint : CachedPoints) + { + CurrentPoint->RemapParameters(ParameterMapping); + } +} + +bool +UHoudiniParameterRampColor::UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex) +{ + // Copy existing points to NewPoints + TArray NewPoints; + const int32 NumInstances = GetInstanceCount(); + NewPoints.Reserve(NumInstances); + for (UHoudiniParameterRampColorPoint* const PointData : Points) + { + if (!IsValid(PointData)) + continue; + + PointData->InstanceIndex = NewPoints.Num(); + PointData->PositionParentParm = nullptr; + PointData->ValueParentParm = nullptr; + PointData->InterpolationParentParm = nullptr; + NewPoints.Add(PointData); + + // We don't need more than NumInstances points in the array + if (NewPoints.Num() == NumInstances) + break; + } + + // Loop over InParameters and look for children of this ramp + int32 CurrentInstanceIndex = 0; + const int32 NumParameters = InParameters.Num(); + for (int32 Index = InStartParamIndex; Index < NumParameters; ++Index) + { + UHoudiniParameter* const Param = InParameters[Index]; + if (!IsValid(Param)) + continue; + + if (!Param->GetIsChildOfMultiParm() || Param->GetParentParmId() != ParmId) + continue; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType != EHoudiniParameterType::Float && ParamType != EHoudiniParameterType::Color && + ParamType != EHoudiniParameterType::IntChoice) + continue; + + // Ensure we have a valid point object at the current index + UHoudiniParameterRampColorPoint* Point = nullptr; + if (!NewPoints.IsValidIndex(CurrentInstanceIndex)) + { + Point = NewObject(this, FName(), this->GetMaskedFlags(RF_PropagateToSubObjects)); + Point->InstanceIndex = CurrentInstanceIndex; + NewPoints.Add(Point); + } + else + { + Point = NewPoints[CurrentInstanceIndex]; + } + + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParameter = Cast(Param); + if (FloatParameter) + { + //*****Float Parameter (position)*****// + if (!Point->PositionParentParm) + { + if (FloatParameter->GetNumberOfValues() <= 0) + continue; + // Set the float ramp point's position parent parm, and value + Point->PositionParentParm = FloatParameter; + Point->SetPosition(FloatParameter->GetValuesPtr()[0]); + } + } + } + else if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParameter = Cast(Param); + if (ColorParameter) + { + //*****Color Parameter (value)*****// + Point->ValueParentParm = ColorParameter; + Point->SetValue(ColorParameter->GetColorValue()); + } + } + //*****Choice parameter (Interpolation)*****// + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParameter = Cast(Param); + if (ChoiceParameter) + { + Point->InterpolationParentParm = ChoiceParameter; + Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + CurrentInstanceIndex++; + } + } + } + + //*****All ramp points have been parsed, finish!*****// + if (NewPoints.Num() == NumInstances) + { + NewPoints.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) { + return P1.Position < P2.Position; + }); + + Points = MoveTemp(NewPoints); + + // Not caching, points are synced, update cached points + if (!bCaching) + { + const int32 NumPoints = Points.Num(); + CachedPoints.SetNumZeroed(NumPoints); + + for (int32 i = 0; i < NumPoints; ++i) + { + UHoudiniParameterRampColorPoint* const FromPoint = Points[i]; + UHoudiniParameterRampColorPoint* ToPoint = CachedPoints[i]; + + // Nothing we can do/copy if FromPoint is null/pending kill + if (!IsValid(FromPoint)) + continue; + + if (!IsValid(ToPoint)) + { + ToPoint = FromPoint->DuplicateAndCopyState(this, RF_NoFlags, GetMaskedFlags(RF_PropagateToSubObjects)); + CachedPoints[i] = ToPoint; + } + else + { + ToPoint->CopyStateFrom(FromPoint, true); + } + } + } + + SetDefaultValues(); + + return true; + } + + return false; +} + +bool +UHoudiniParameterRampColor::IsDefault() const +{ + if (NumDefaultPoints < 0) + return true; + + if (NumDefaultPoints != Points.Num()) + return false; + + TArray Positions = DefaultPositions; + TArray Values = DefaultValues; + TArray Choices = DefaultChoices; + + for (auto & NextPt : Points) + { + if (!NextPt) + return false; + + bool bFoundMatch = false; + for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) + { + if (Positions[DefaultIdx] == NextPt->Position && + Values[DefaultIdx] == NextPt->Value && + Choices[DefaultIdx] == (int32)NextPt->Interpolation) + { + Positions.RemoveAt(DefaultIdx); + Values.RemoveAt(DefaultIdx); + Choices.RemoveAt(DefaultIdx); + bFoundMatch = true; + } + } + + if (!bFoundMatch) + return false; + } + + if (Positions.Num() > 0) + return false; + + return true; +} + + +void +UHoudiniParameterRampColor::SetDefaultValues() +{ + if (NumDefaultPoints >= 0) + return; + + + if (DefaultPositions.Num() > 0) + return; + + DefaultPositions.Empty(); + DefaultValues.Empty(); + DefaultChoices.Empty(); + + for (auto & NextPoint : Points) + { + if (!NextPoint) + continue; + + DefaultPositions.Add(NextPoint->Position); + DefaultValues.Add(NextPoint->Value); + DefaultChoices.Add((int32)NextPoint->Interpolation); + + } + + NumDefaultPoints = Points.Num(); +} + +void +UHoudiniParameterRampFloat::SetDefaultValues() +{ + if (DefaultPositions.Num() > 0) + return; + + DefaultPositions.Empty(); + DefaultValues.Empty(); + DefaultChoices.Empty(); + + for (auto & NextPoint : Points) + { + if (!NextPoint) + continue; + + DefaultPositions.Add(NextPoint->Position); + DefaultValues.Add(NextPoint->Value); + DefaultChoices.Add((int32)NextPoint->Interpolation); + + } + + NumDefaultPoints = Points.Num(); + } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h index 72dbac599..5f08a9901 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h @@ -1,312 +1,330 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Curves/CurveFloat.h" -#include "Curves/CurveLinearColor.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.generated.h" - -class UHoudiniParameterRampFloat; -class UHoudiniParameterRampColor; -class UHoudiniParameter; -class UHoudiniParameterFloat; -class UHoudiniParameterChoice; -class UHoudiniParameterColor; - -UENUM() -enum class EHoudiniRampPointConstructStatus : uint8 -{ - None, - - INITIALIZED, - POSITION_INSERTED, - VALUE_INSERTED, - INTERPTYPE_INSERTED -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampModificationEvent : public UObject -{ - GENERATED_BODY() -public: - FORCEINLINE - void SetInsertEvent() { bIsInsertEvent = true; }; - - FORCEINLINE - void SetDeleteEvent() { bIsInsertEvent = false; }; - - FORCEINLINE - void SetFloatRampEvent() { bIsFloatRamp = true; }; - - FORCEINLINE - void SetColorRampEvent() { bIsFloatRamp = false; }; - - FORCEINLINE - bool IsInsertEvent() const { return bIsInsertEvent; }; - - FORCEINLINE - bool IsDeleteEvent() const { return !bIsInsertEvent; }; - - FORCEINLINE - bool IsFloatRampEvent() { return bIsFloatRamp; }; - - FORCEINLINE - bool IsColorRampEvent() { return !bIsFloatRamp; }; - - -private: - UPROPERTY() - bool bIsInsertEvent = false; - - UPROPERTY() - bool bIsFloatRamp = false; - -public: - UPROPERTY() - int32 DeleteInstanceIndex = -1; - - UPROPERTY() - float InsertPosition; - - UPROPERTY() - float InsertFloat; - - UPROPERTY() - FLinearColor InsertColor; - - UPROPERTY() - EHoudiniRampInterpolationType InsertInterpolation; -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloatPoint : public UObject -{ - GENERATED_BODY() - -public: - - UPROPERTY() - float Position; - - UPROPERTY() - float Value; - - UPROPERTY() - EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; - - UPROPERTY() - int32 InstanceIndex = -1; - - UPROPERTY() - UHoudiniParameterFloat* PositionParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterFloat* ValueParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterChoice* InterpolationParentParm = nullptr; - - FORCEINLINE - float GetPosition() const { return Position; }; - - void SetPosition(const float InPosition); - - FORCEINLINE - float GetValue() const { return Value; }; - - void SetValue(const float InValue); - - FORCEINLINE - EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; - - void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); - - UHoudiniParameterRampFloatPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); - - void CopyStateFrom(UHoudiniParameterRampFloatPoint* InParameter, bool bCopyAllProperties, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); - - void RemapParameters(const TMap& ParameterMapping); - -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColorPoint : public UObject -{ - GENERATED_BODY() - -public: - - UPROPERTY() - float Position; - - UPROPERTY() - FLinearColor Value; - - UPROPERTY() - EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; - - UPROPERTY() - int32 InstanceIndex = -1; - - UPROPERTY() - UHoudiniParameterFloat * PositionParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterColor* ValueParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterChoice* InterpolationParentParm = nullptr; - - FORCEINLINE - float GetPosition() const { return Position; }; - - void SetPosition(const float InPosition); - FORCEINLINE - FLinearColor GetValue() const { return Value; }; - - void SetValue(const FLinearColor InValue); - - FORCEINLINE - EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; - - void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); - - UHoudiniParameterRampColorPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - void CopyStateFrom(UHoudiniParameterRampColorPoint* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - void RemapParameters(const TMap& ParameterMapping); -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParameterMultiParm -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void OnPreCook() override; - - // Create instance of this class. - static UHoudiniParameterRampFloat * Create( - UObject* Outer, - const FString& ParamName); - - FORCEINLINE - bool IsCaching() const { return bCaching; }; - - FORCEINLINE - void SetCaching(const bool bInCaching) { bCaching = bInCaching; }; - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; - - virtual void RemapParameters(const TMap& ParameterMapping) override; - - void SyncCachedPoints(); - - void CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); - - void CreateDeleteEvent(const int32 &InDeleteIndex); - - UPROPERTY() - TArray Points; - - UPROPERTY() - TArray CachedPoints; - - UPROPERTY() - TArray DefaultPositions; - - UPROPERTY() - TArray DefaultValues; - - UPROPERTY() - TArray DefaultChoices; - - UPROPERTY() - int32 NumDefaultPoints; - - UPROPERTY() - bool bCaching; - - UPROPERTY() - TArray ModificationEvents; - - bool IsDefault() const override; - - void SetDefaultValues(); - -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColor : public UHoudiniParameterMultiParm -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterRampColor * Create( - UObject* Outer, - const FString& ParamName); - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; - - virtual void RemapParameters(const TMap& ParameterMapping) override; - - UPROPERTY(Instanced) - TArray Points; - - UPROPERTY() - bool bCaching; - - UPROPERTY(Instanced) - TArray CachedPoints; - - UPROPERTY() - TArray DefaultPositions; - - UPROPERTY() - TArray DefaultValues; - - UPROPERTY() - TArray DefaultChoices; - - UPROPERTY() - int32 NumDefaultPoints; - - UPROPERTY() - TArray ModificationEvents; - - bool IsDefault() const override; - - void SetDefaultValues(); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Curves/CurveFloat.h" +#include "Curves/CurveLinearColor.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.generated.h" + +class UHoudiniParameterRampFloat; +class UHoudiniParameterRampColor; +class UHoudiniParameter; +class UHoudiniParameterFloat; +class UHoudiniParameterChoice; +class UHoudiniParameterColor; + +UENUM() +enum class EHoudiniRampPointConstructStatus : uint8 +{ + None, + + INITIALIZED, + POSITION_INSERTED, + VALUE_INSERTED, + INTERPTYPE_INSERTED +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampModificationEvent : public UObject +{ + GENERATED_BODY() +public: + FORCEINLINE + void SetInsertEvent() { bIsInsertEvent = true; }; + + FORCEINLINE + void SetDeleteEvent() { bIsInsertEvent = false; }; + + FORCEINLINE + void SetFloatRampEvent() { bIsFloatRamp = true; }; + + FORCEINLINE + void SetColorRampEvent() { bIsFloatRamp = false; }; + + FORCEINLINE + bool IsInsertEvent() const { return bIsInsertEvent; }; + + FORCEINLINE + bool IsDeleteEvent() const { return !bIsInsertEvent; }; + + FORCEINLINE + bool IsFloatRampEvent() { return bIsFloatRamp; }; + + FORCEINLINE + bool IsColorRampEvent() { return !bIsFloatRamp; }; + + +private: + UPROPERTY() + bool bIsInsertEvent = false; + + UPROPERTY() + bool bIsFloatRamp = false; + +public: + UPROPERTY() + int32 DeleteInstanceIndex = -1; + + UPROPERTY() + float InsertPosition; + + UPROPERTY() + float InsertFloat; + + UPROPERTY() + FLinearColor InsertColor; + + UPROPERTY() + EHoudiniRampInterpolationType InsertInterpolation; +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloatPoint : public UObject +{ + GENERATED_BODY() + +public: + + UPROPERTY() + float Position; + + UPROPERTY() + float Value; + + UPROPERTY() + EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; + + UPROPERTY() + int32 InstanceIndex = -1; + + UPROPERTY() + UHoudiniParameterFloat* PositionParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterFloat* ValueParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterChoice* InterpolationParentParm = nullptr; + + FORCEINLINE + float GetPosition() const { return Position; }; + + void SetPosition(const float InPosition); + + FORCEINLINE + float GetValue() const { return Value; }; + + void SetValue(const float InValue); + + FORCEINLINE + EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; + + void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); + + UHoudiniParameterRampFloatPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); + + void CopyStateFrom(UHoudiniParameterRampFloatPoint* InParameter, bool bCopyAllProperties, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); + + void RemapParameters(const TMap& ParameterMapping); + +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColorPoint : public UObject +{ + GENERATED_BODY() + +public: + + UPROPERTY() + float Position; + + UPROPERTY() + FLinearColor Value; + + UPROPERTY() + EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; + + UPROPERTY() + int32 InstanceIndex = -1; + + UPROPERTY() + UHoudiniParameterFloat * PositionParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterColor* ValueParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterChoice* InterpolationParentParm = nullptr; + + FORCEINLINE + float GetPosition() const { return Position; }; + + void SetPosition(const float InPosition); + FORCEINLINE + FLinearColor GetValue() const { return Value; }; + + void SetValue(const FLinearColor InValue); + + FORCEINLINE + EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; + + void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); + + UHoudiniParameterRampColorPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + void CopyStateFrom(UHoudiniParameterRampColorPoint* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + void RemapParameters(const TMap& ParameterMapping); +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParameterMultiParm +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void OnPreCook() override; + + // Create instance of this class. + static UHoudiniParameterRampFloat * Create( + UObject* Outer, + const FString& ParamName); + + FORCEINLINE + bool IsCaching() const { return bCaching; }; + + FORCEINLINE + void SetCaching(const bool bInCaching) { bCaching = bInCaching; }; + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; + + virtual void RemapParameters(const TMap& ParameterMapping) override; + + void SyncCachedPoints(); + + void CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); + + void CreateDeleteEvent(const int32 &InDeleteIndex); + + /** + * Update/populates the Points array from InParameters. + * @param InParameters An array of parameters containing this ramp multiparm's instances (the parameters for each + * of its points). + * @param InStartParamIndex The index in InParameters where this ramp multiparm's child parameters start. + * @return true if we found enough parameters to build a number of points == NumInstances(). + */ + bool UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex); + + UPROPERTY() + TArray Points; + + UPROPERTY() + TArray CachedPoints; + + UPROPERTY() + TArray DefaultPositions; + + UPROPERTY() + TArray DefaultValues; + + UPROPERTY() + TArray DefaultChoices; + + UPROPERTY() + int32 NumDefaultPoints; + + UPROPERTY() + bool bCaching; + + UPROPERTY() + TArray ModificationEvents; + + bool IsDefault() const override; + + void SetDefaultValues(); + +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColor : public UHoudiniParameterMultiParm +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterRampColor * Create( + UObject* Outer, + const FString& ParamName); + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; + + virtual void RemapParameters(const TMap& ParameterMapping) override; + + /** + * Update/populates the Points array from InParameters. + * @param InParameters An array of parameters containing this ramp multiparm's instances (the parameters for each + * of its points). + * @param InStartParamIndex The index in InParameters where this ramp multiparm's child parameters start. + * @return true if we found enough parameters to build a number of points == NumInstances(). + */ + bool UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex); + + UPROPERTY(Instanced) + TArray Points; + + UPROPERTY() + bool bCaching; + + UPROPERTY(Instanced) + TArray CachedPoints; + + UPROPERTY() + TArray DefaultPositions; + + UPROPERTY() + TArray DefaultValues; + + UPROPERTY() + TArray DefaultChoices; + + UPROPERTY() + int32 NumDefaultPoints; + + UPROPERTY() + TArray ModificationEvents; + + bool IsDefault() const override; + + void SetDefaultValues(); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp index 9fe3a8db1..4babd80df 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp @@ -1,51 +1,51 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterSeparator.h" - -UHoudiniParameterSeparator::UHoudiniParameterSeparator(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Separator; -} - -UHoudiniParameterSeparator * -UHoudiniParameterSeparator::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterSeparator_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterSeparator::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterSeparator * HoudiniAssetParameter = NewObject< UHoudiniParameterSeparator >( - InOuter, UHoudiniParameterSeparator::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Separator); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterSeparator.h" + +UHoudiniParameterSeparator::UHoudiniParameterSeparator(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Separator; +} + +UHoudiniParameterSeparator * +UHoudiniParameterSeparator::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterSeparator_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterSeparator::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterSeparator * HoudiniAssetParameter = NewObject< UHoudiniParameterSeparator >( + InOuter, UHoudiniParameterSeparator::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Separator); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h index f408b88dc..8f0c00821 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterSeparator.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterSeparator : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterSeparator * Create( - UObject* Outer, - const FString& ParamName); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterSeparator.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterSeparator : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterSeparator * Create( + UObject* Outer, + const FString& ParamName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp index 1751a07a8..352a6de41 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp @@ -1,136 +1,136 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterString.h" - -UHoudiniParameterString::UHoudiniParameterString(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , bIsAssetRef(false) -{ - ParmType = EHoudiniParameterType::String; -} - -UHoudiniParameterString * -UHoudiniParameterString::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterString_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterString::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterString * HoudiniAssetParameter = NewObject< UHoudiniParameterString >( - InOuter, UHoudiniParameterString::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::String); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterString::SetValueAt(const FString& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index].Equals(InValue, ESearchCase::CaseSensitive)) - return false; - - Values[Index] = InValue; - - return true; -} - -void -UHoudiniParameterString::SetAssetAt(UObject* InObject, const uint32& Index) -{ - if (!ChosenAssets.IsValidIndex(Index)) - return; - - ChosenAssets[Index] = InObject; - -} - -FString -UHoudiniParameterString::GetAssetReference(UObject* InObject) -{ - // Get the asset reference string for a given UObject - if (!InObject || InObject->IsPendingKill()) - return FString(); - - // Start by getting the Object's full name - FString AssetReference = InObject->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - return AssetReference; -} - -void -UHoudiniParameterString::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterString::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterString::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterString.h" + +UHoudiniParameterString::UHoudiniParameterString(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , bIsAssetRef(false) +{ + ParmType = EHoudiniParameterType::String; +} + +UHoudiniParameterString * +UHoudiniParameterString::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterString_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterString::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterString * HoudiniAssetParameter = NewObject< UHoudiniParameterString >( + InOuter, UHoudiniParameterString::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::String); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterString::SetValueAt(const FString& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index].Equals(InValue, ESearchCase::CaseSensitive)) + return false; + + Values[Index] = InValue; + + return true; +} + +void +UHoudiniParameterString::SetAssetAt(UObject* InObject, const uint32& Index) +{ + if (!ChosenAssets.IsValidIndex(Index)) + return; + + ChosenAssets[Index] = InObject; + +} + +FString +UHoudiniParameterString::GetAssetReference(UObject* InObject) +{ + // Get the asset reference string for a given UObject + if (!InObject || InObject->IsPendingKill()) + return FString(); + + // Start by getting the Object's full name + FString AssetReference = InObject->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + return AssetReference; +} + +void +UHoudiniParameterString::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterString::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterString::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h index 1f4c13bdc..d43014f81 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h @@ -1,91 +1,91 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterString.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterString : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterString * Create( - UObject* Outer, const FString& ParamName); - - // Accessor - FString GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? Values[Index] : FString(); }; - - UObject* GetAssetAt(const uint32& Index) const { return ChosenAssets.IsValidIndex(Index) ? ChosenAssets[Index] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - bool IsAssetRef() const { return bIsAssetRef; }; - - bool IsDefaultValueAtIndex(const int32& Idx) const; - bool IsDefault() const override; - - // Mutators - void SetIsAssetRef(const bool& InIsAssetRef) { bIsAssetRef = InIsAssetRef; }; - - bool SetValueAt(const FString& InValue, const uint32& Index); - - void SetAssetAt(UObject* InObject, const uint32& Index); - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNum(InNumValues); ChosenAssets.SetNum(InNumValues); }; - - TArray & GetChosenAssets() { return ChosenAssets; }; - - void SetDefaultValues(); - - // Utility - - // Get the asset reference string for a given UObject - static FString GetAssetReference(UObject* InObject); - - -protected: - - // Values of this property. - UPROPERTY() - TArray< FString > Values; - - UPROPERTY() - TArray< FString > DefaultValues; - - UPROPERTY() - TArray ChosenAssets; - - // Indicates this string parameter should be treated as an asset reference - // and display an object picker - UPROPERTY() - bool bIsAssetRef; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterString.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterString : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterString * Create( + UObject* Outer, const FString& ParamName); + + // Accessor + FString GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? Values[Index] : FString(); }; + + UObject* GetAssetAt(const uint32& Index) const { return ChosenAssets.IsValidIndex(Index) ? ChosenAssets[Index] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + bool IsAssetRef() const { return bIsAssetRef; }; + + bool IsDefaultValueAtIndex(const int32& Idx) const; + bool IsDefault() const override; + + // Mutators + void SetIsAssetRef(const bool& InIsAssetRef) { bIsAssetRef = InIsAssetRef; }; + + bool SetValueAt(const FString& InValue, const uint32& Index); + + void SetAssetAt(UObject* InObject, const uint32& Index); + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNum(InNumValues); ChosenAssets.SetNum(InNumValues); }; + + TArray & GetChosenAssets() { return ChosenAssets; }; + + void SetDefaultValues(); + + // Utility + + // Get the asset reference string for a given UObject + static FString GetAssetReference(UObject* InObject); + + +protected: + + // Values of this property. + UPROPERTY() + TArray< FString > Values; + + UPROPERTY() + TArray< FString > DefaultValues; + + UPROPERTY() + TArray ChosenAssets; + + // Indicates this string parameter should be treated as an asset reference + // and display an object picker + UPROPERTY() + bool bIsAssetRef; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp index 5c7e1d205..3e81d9ba0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp @@ -1,95 +1,95 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterToggle.h" - -UHoudiniParameterToggle::UHoudiniParameterToggle(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Toggle; -} - -UHoudiniParameterToggle * -UHoudiniParameterToggle::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterToggle_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterToggle::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterToggle * HoudiniAssetParameter = NewObject< UHoudiniParameterToggle >( - InOuter, UHoudiniParameterToggle::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Toggle); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterToggle::SetValueAt(const bool& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index] == 0 && !InValue) - return false; - - if (Values[Index] == 1 && InValue) - return false; - - Values[Index] = InValue ? 1 : 0; - return true; -} - -bool -UHoudiniParameterToggle::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!DefaultValues.IsValidIndex(Idx)) - break; - - if (Values[Idx] != DefaultValues[Idx]) - return false; - } - - return true; -} - -void -UHoudiniParameterToggle::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterToggle.h" + +UHoudiniParameterToggle::UHoudiniParameterToggle(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Toggle; +} + +UHoudiniParameterToggle * +UHoudiniParameterToggle::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterToggle_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterToggle::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterToggle * HoudiniAssetParameter = NewObject< UHoudiniParameterToggle >( + InOuter, UHoudiniParameterToggle::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Toggle); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterToggle::SetValueAt(const bool& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index] == 0 && !InValue) + return false; + + if (Values[Index] == 1 && InValue) + return false; + + Values[Index] = InValue ? 1 : 0; + return true; +} + +bool +UHoudiniParameterToggle::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!DefaultValues.IsValidIndex(Idx)) + break; + + if (Values[Idx] != DefaultValues[Idx]) + return false; + } + + return true; +} + +void +UHoudiniParameterToggle::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h index 63209a9bf..8b141ff45 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h @@ -1,68 +1,68 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" -#include "Styling/SlateTypes.h" -#include "HoudiniParameterToggle.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterToggle : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterToggle * Create( - UObject* Outer, - const FString& ParamName); - - // Accessor - bool GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? (bool)Values[Index] : false; }; - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - bool IsDefault() const override; - - // Mutators - bool SetValueAt(const bool& InValue, const uint32& Index); - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - - int32 GetNumValues() { return Values.Num(); }; - - void SetDefaultValues(); - -protected: - - // Values of this property. - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" +#include "Styling/SlateTypes.h" +#include "HoudiniParameterToggle.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterToggle : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterToggle * Create( + UObject* Outer, + const FString& ParamName); + + // Accessor + bool GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? (bool)Values[Index] : false; }; + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + bool IsDefault() const override; + + // Mutators + bool SetValueAt(const bool& InValue, const uint32& Index); + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + + int32 GetNumValues() { return Values.Num(); }; + + void SetDefaultValues(); + +protected: + + // Values of this property. + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp index e54063122..781fa70a2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp @@ -1,35 +1,35 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPluginSerializationVersion.h" - -#include "Serialization/CustomVersion.h" -#include "Misc/Guid.h" - -const FGuid FHoudiniCustomSerializationVersion::GUID( 0x1AB9CECC, 0x6913, 0x4875, 0x203d51fb ); - -// Register the custom version with core -FCustomVersionRegistration GRegisterHoudiniCustomVersion( FHoudiniCustomSerializationVersion::GUID, VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION, TEXT( "HoudiniUE4PluginVer" ) ); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPluginSerializationVersion.h" + +#include "Serialization/CustomVersion.h" +#include "Misc/Guid.h" + +const FGuid FHoudiniCustomSerializationVersion::GUID( 0x1AB9CECC, 0x6913, 0x4875, 0x203d51fb ); + +// Register the custom version with core +FCustomVersionRegistration GRegisterHoudiniCustomVersion( FHoudiniCustomSerializationVersion::GUID, VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION, TEXT( "HoudiniUE4PluginVer" ) ); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h index e765a93ad..a0b703439 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h @@ -1,93 +1,93 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Misc/Guid.h" - -// Deprecated per-class versions used to load old files -// -// Serialization of parameter name map. -#define VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP 2 -// Serialization of instancer material, if it is available. -#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME 1 -// Serialization of attribute instancer material, if it is available. -#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME 2 -// Landscape serialization in asset inputs. -#define VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT 1 -// Asset instance member. -#define VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER 2 -// World Outliner inputs. -#define VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT 3 - - -enum EHoudiniPluginSerializationVersion -{ - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE = 5, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE = 6, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM = 7, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT = 8, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH = 9, - VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM = 10, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT = 11, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_CUSTOM_LINKER = 12, // added custom linker version to archives - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ACTOR_INSTANCING = 13, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES = 14, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT = 15, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE = 16, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES = 17, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS = 18, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY = 19, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS = 20, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP = 21, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS = 22, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP = 23, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT = 24, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY = 25, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX = 26, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM = 27, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME = 28, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX = 29, // Version 29 is a fix for a serialization issue with UE4.19 / H17.0/16.5, 29 is actually version 26 minus the version 24 changes... - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_POST_419_SERIALIZATION_FIX = 30, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF = 31, - - //------------------------------------------------------------ - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE = 100, - - // ------------------------------------------------------ - // - this needs to be the last line (see note below) - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE, - VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION = VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE - 1 -}; - -struct FHoudiniCustomSerializationVersion -{ - // The GUID for this custom version number - const static FGuid GUID; - -private: - FHoudiniCustomSerializationVersion() {} -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Misc/Guid.h" + +// Deprecated per-class versions used to load old files +// +// Serialization of parameter name map. +#define VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP 2 +// Serialization of instancer material, if it is available. +#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME 1 +// Serialization of attribute instancer material, if it is available. +#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME 2 +// Landscape serialization in asset inputs. +#define VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT 1 +// Asset instance member. +#define VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER 2 +// World Outliner inputs. +#define VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT 3 + + +enum EHoudiniPluginSerializationVersion +{ + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE = 5, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE = 6, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM = 7, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT = 8, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH = 9, + VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM = 10, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT = 11, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_CUSTOM_LINKER = 12, // added custom linker version to archives + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ACTOR_INSTANCING = 13, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES = 14, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT = 15, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE = 16, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES = 17, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS = 18, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY = 19, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS = 20, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP = 21, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS = 22, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP = 23, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT = 24, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY = 25, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX = 26, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM = 27, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME = 28, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX = 29, // Version 29 is a fix for a serialization issue with UE4.19 / H17.0/16.5, 29 is actually version 26 minus the version 24 changes... + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_POST_419_SERIALIZATION_FIX = 30, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF = 31, + + //------------------------------------------------------------ + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE = 100, + + // ------------------------------------------------------ + // - this needs to be the last line (see note below) + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE, + VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION = VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE - 1 +}; + +struct FHoudiniCustomSerializationVersion +{ + // The GUID for this custom version number + const static FGuid GUID; + +private: + FHoudiniCustomSerializationVersion() {} +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp index e80c6b5a8..ff4460b11 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp @@ -1,406 +1,406 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniRuntimeSettings.h" - - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Misc/Paths.h" -// #include "Internationalization/Internationalization.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - - -FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() - : bGeneratedDoubleSidedGeometry(false) - , GeneratedPhysMaterial(nullptr) - , GeneratedCollisionTraceFlag(CTF_UseDefault) - , GeneratedLightMapResolution(64) - , GeneratedLpvBiasMultiplier(1.0f) - , GeneratedWalkableSlopeOverride() - , GeneratedLightMapCoordinateIndex(1) - , bGeneratedUseMaximumStreamingTexelRatio(false) - , GeneratedStreamingDistanceMultiplier(1.0f) - , GeneratedFoliageDefaultSettings(nullptr) - , GeneratedAssetUserData() -{ - DefaultBodyInstance.SetCollisionProfileName("BlockAll"); -} - - -UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) - : Super( ObjectInitializer ) -{ - // Session options. - SessionType = HRSST_NamedPipe; - ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; - ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; - ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; - bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; - AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; - - bSyncWithHoudiniCook = true; - bCookUsingHoudiniTime = true; - bSyncViewport = false; - bSyncHoudiniViewport = false; - bSyncUnrealViewport = false; - - // Instantiating options. - bShowMultiAssetDialog = true; - bPreferHdaMemoryCopyOverHdaSourceFile = false; - - // Cooking options. - bPauseCookingOnStart = false; - bDisplaySlateCookingNotifications = true; - DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - // Parameter options - //bTreatRampParametersAsMultiparms = false; - - // Custom Houdini location. - bUseCustomHoudiniLocation = false; - CustomHoudiniLocation.Path = TEXT(""); - - // Arguments for HAPI_Initialize - CookingThreadStackSize = -1; - - // Landscape marshalling default values. - MarshallingLandscapesUseDefaultUnrealScaling = false; - MarshallingLandscapesUseFullResolution = true; - MarshallingLandscapesForceMinMaxValues = false; - MarshallingLandscapesForcedMinValue = -2000.0f; - MarshallingLandscapesForcedMaxValue = 4553.0f; - - // Spline marshalling - MarshallingSplineResolution = 50.0f; - - // Static mesh proxy refinement settings - bEnableProxyStaticMesh = false; - bShowDefaultMesh = true; - bEnableProxyStaticMeshRefinementByTimer = true; - ProxyMeshAutoRefineTimeoutSeconds = 10.0f; - bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; - bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; - - // Generated StaticMesh settings. - bDoubleSidedGeometry = false; - PhysMaterial = nullptr; - DefaultBodyInstance.SetCollisionProfileName("BlockAll"); - CollisionTraceFlag = CTF_UseDefault; - LightMapResolution = 32; - LpvBiasMultiplier = 1.0f; - LightMapCoordinateIndex = 1; - bUseMaximumStreamingTexelRatio = false; - StreamingDistanceMultiplier = 1.0f; - GeneratedDistanceFieldResolutionScale = 0.0f; - - // Static Mesh build settings. - bUseFullPrecisionUVs = false; - SrcLightmapIndex = 0; - DstLightmapIndex = 1; - MinLightmapResolution = 64; - bRemoveDegenerates = true; - GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; - RecomputeNormalsFlag = HRSRF_OnlyIfMissing; - RecomputeTangentsFlag = HRSRF_OnlyIfMissing; - bUseMikkTSpace = true; - bBuildAdjacencyBuffer = true; // v1 default false - - bComputeWeightedNormals = false; - bBuildReversedIndexBuffer = true; - bUseHighPrecisionTangentBasis = false; - bGenerateDistanceFieldAsIfTwoSided = false; - bSupportFaceRemap = false; - //BuildScale3D = FVector(1.0f, 1.0f, 1.0f); - DistanceFieldResolutionScale = 2.0f; // ue default is 1.0 - - bPDGAsyncCommandletImportEnabled = false; - - // Legacy settings - bEnableBackwardCompatibility = true; - bAutomaticLegacyHDARebuild = false; - - // Curve inputs and editable output curves - bAddRotAndScaleAttributesOnCurves = false; -} - -UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() -{} - - -FProperty * -UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const -{ - for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) - { - FProperty * Property = *PropIt; - - if (Property->GetNameCPP() == PropertyName) - return Property; - } - - return nullptr; -} - - -void -UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) -{ - FProperty * Property = LocateProperty(PropertyName); - if (Property) - { - if (bReadOnly) - Property->SetPropertyFlags(CPF_EditConst); - else - Property->ClearPropertyFlags(CPF_EditConst); - } -} - - -void -UHoudiniRuntimeSettings::PostInitProperties() -{ - Super::PostInitProperties(); - - // Set Collision generation options as read only - { - if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Set marshalling attributes options as read only - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - /* - // Set Cook Folder as read-only - { - if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) - Property->SetPropertyFlags( CPF_EditConst ); - } - */ - - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - Property->SetPropertyFlags(CPF_EditConst); - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Disable UI elements depending on current session type. -#if WITH_EDITOR - - UpdateSessionUI(); - -#endif // WITH_EDITOR - - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); -} - - -#if WITH_EDITOR - -void -UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - FProperty * Property = PropertyChangedEvent.MemberProperty; - FProperty * LookupProperty = nullptr; - - if (!Property) - return; - if (Property->GetName() == TEXT("SessionType")) - UpdateSessionUI(); - else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); - else if (Property->GetName() == TEXT("CustomHoudiniLocation")) - { - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; - FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); - - // If path does not point to libHAPI location, we need to let user know. - if (!FPaths::FileExists(LibHAPICustomPath)) - { - FString MessageString = FString::Printf( - TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); - - FPlatformMisc::MessageBoxExt( - EAppMsgType::Ok, *MessageString, - TEXT("Invalid Custom Location Specified, resetting.")); - - CustomHoudiniLocationPath = TEXT(""); - } - } - else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) - { - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->SetPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->SetPropertyFlags(CPF_EditConst); - } - else - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->ClearPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->ClearPropertyFlags(CPF_EditConst); - } - } - - /* - if ( Property->GetName() == TEXT( "bEnableCooking" ) ) - { - // Cooking is disabled, we need to disable transform change triggers cooks option is as well. - if ( bEnableCooking ) - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) - { - // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. - if ( bUploadTransformsToHoudiniEngine ) - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - */ -} - - - -void -UHoudiniRuntimeSettings::UpdateSessionUI() -{ - SetPropertyReadOnly(TEXT("ServerHost"), true); - SetPropertyReadOnly(TEXT("ServerPort"), true); - SetPropertyReadOnly(TEXT("ServerPipeName"), true); - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); - - bool bServerType = false; - - switch (SessionType) - { - case HRSST_Socket: - { - SetPropertyReadOnly(TEXT("ServerHost"), false); - SetPropertyReadOnly(TEXT("ServerPort"), false); - bServerType = true; - break; - } - - case HRSST_NamedPipe: - { - SetPropertyReadOnly(TEXT("ServerPipeName"), false); - bServerType = true; - break; - } - - default: - break; - } - - if (bServerType) - { - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); - } -} - -#endif // WITH_EDITOR - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniRuntimeSettings.h" + + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Misc/Paths.h" +// #include "Internationalization/Internationalization.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + + +FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() + : bGeneratedDoubleSidedGeometry(false) + , GeneratedPhysMaterial(nullptr) + , GeneratedCollisionTraceFlag(CTF_UseDefault) + , GeneratedLightMapResolution(64) + , GeneratedLpvBiasMultiplier(1.0f) + , GeneratedWalkableSlopeOverride() + , GeneratedLightMapCoordinateIndex(1) + , bGeneratedUseMaximumStreamingTexelRatio(false) + , GeneratedStreamingDistanceMultiplier(1.0f) + , GeneratedFoliageDefaultSettings(nullptr) + , GeneratedAssetUserData() +{ + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); +} + + +UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // Session options. + SessionType = HRSST_NamedPipe; + ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; + ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; + ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; + bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; + AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; + + bSyncWithHoudiniCook = true; + bCookUsingHoudiniTime = true; + bSyncViewport = false; + bSyncHoudiniViewport = false; + bSyncUnrealViewport = false; + + // Instantiating options. + bShowMultiAssetDialog = true; + bPreferHdaMemoryCopyOverHdaSourceFile = false; + + // Cooking options. + bPauseCookingOnStart = false; + bDisplaySlateCookingNotifications = true; + DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + // Parameter options + //bTreatRampParametersAsMultiparms = false; + + // Custom Houdini location. + bUseCustomHoudiniLocation = false; + CustomHoudiniLocation.Path = TEXT(""); + + // Arguments for HAPI_Initialize + CookingThreadStackSize = -1; + + // Landscape marshalling default values. + MarshallingLandscapesUseDefaultUnrealScaling = false; + MarshallingLandscapesUseFullResolution = true; + MarshallingLandscapesForceMinMaxValues = false; + MarshallingLandscapesForcedMinValue = -2000.0f; + MarshallingLandscapesForcedMaxValue = 4553.0f; + + // Spline marshalling + MarshallingSplineResolution = 50.0f; + + // Static mesh proxy refinement settings + bEnableProxyStaticMesh = false; + bShowDefaultMesh = true; + bEnableProxyStaticMeshRefinementByTimer = true; + ProxyMeshAutoRefineTimeoutSeconds = 10.0f; + bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; + bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; + + // Generated StaticMesh settings. + bDoubleSidedGeometry = false; + PhysMaterial = nullptr; + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); + CollisionTraceFlag = CTF_UseDefault; + LightMapResolution = 32; + LpvBiasMultiplier = 1.0f; + LightMapCoordinateIndex = 1; + bUseMaximumStreamingTexelRatio = false; + StreamingDistanceMultiplier = 1.0f; + GeneratedDistanceFieldResolutionScale = 0.0f; + + // Static Mesh build settings. + bUseFullPrecisionUVs = false; + SrcLightmapIndex = 0; + DstLightmapIndex = 1; + MinLightmapResolution = 64; + bRemoveDegenerates = true; + GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; + RecomputeNormalsFlag = HRSRF_OnlyIfMissing; + RecomputeTangentsFlag = HRSRF_OnlyIfMissing; + bUseMikkTSpace = true; + bBuildAdjacencyBuffer = true; // v1 default false + + bComputeWeightedNormals = false; + bBuildReversedIndexBuffer = true; + bUseHighPrecisionTangentBasis = false; + bGenerateDistanceFieldAsIfTwoSided = false; + bSupportFaceRemap = false; + //BuildScale3D = FVector(1.0f, 1.0f, 1.0f); + DistanceFieldResolutionScale = 2.0f; // ue default is 1.0 + + bPDGAsyncCommandletImportEnabled = false; + + // Legacy settings + bEnableBackwardCompatibility = true; + bAutomaticLegacyHDARebuild = false; + + // Curve inputs and editable output curves + bAddRotAndScaleAttributesOnCurves = false; +} + +UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() +{} + + +FProperty * +UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const +{ + for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) + { + FProperty * Property = *PropIt; + + if (Property->GetNameCPP() == PropertyName) + return Property; + } + + return nullptr; +} + + +void +UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) +{ + FProperty * Property = LocateProperty(PropertyName); + if (Property) + { + if (bReadOnly) + Property->SetPropertyFlags(CPF_EditConst); + else + Property->ClearPropertyFlags(CPF_EditConst); + } +} + + +void +UHoudiniRuntimeSettings::PostInitProperties() +{ + Super::PostInitProperties(); + + // Set Collision generation options as read only + { + if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Set marshalling attributes options as read only + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + /* + // Set Cook Folder as read-only + { + if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + } + */ + + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + Property->SetPropertyFlags(CPF_EditConst); + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Disable UI elements depending on current session type. +#if WITH_EDITOR + + UpdateSessionUI(); + +#endif // WITH_EDITOR + + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); +} + + +#if WITH_EDITOR + +void +UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + FProperty * LookupProperty = nullptr; + + if (!Property) + return; + if (Property->GetName() == TEXT("SessionType")) + UpdateSessionUI(); + else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); + else if (Property->GetName() == TEXT("CustomHoudiniLocation")) + { + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; + FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); + + // If path does not point to libHAPI location, we need to let user know. + if (!FPaths::FileExists(LibHAPICustomPath)) + { + FString MessageString = FString::Printf( + TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); + + FPlatformMisc::MessageBoxExt( + EAppMsgType::Ok, *MessageString, + TEXT("Invalid Custom Location Specified, resetting.")); + + CustomHoudiniLocationPath = TEXT(""); + } + } + else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) + { + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->SetPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->SetPropertyFlags(CPF_EditConst); + } + else + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->ClearPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->ClearPropertyFlags(CPF_EditConst); + } + } + + /* + if ( Property->GetName() == TEXT( "bEnableCooking" ) ) + { + // Cooking is disabled, we need to disable transform change triggers cooks option is as well. + if ( bEnableCooking ) + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) + { + // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. + if ( bUploadTransformsToHoudiniEngine ) + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + */ +} + + + +void +UHoudiniRuntimeSettings::UpdateSessionUI() +{ + SetPropertyReadOnly(TEXT("ServerHost"), true); + SetPropertyReadOnly(TEXT("ServerPort"), true); + SetPropertyReadOnly(TEXT("ServerPipeName"), true); + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); + + bool bServerType = false; + + switch (SessionType) + { + case HRSST_Socket: + { + SetPropertyReadOnly(TEXT("ServerHost"), false); + SetPropertyReadOnly(TEXT("ServerPort"), false); + bServerType = true; + break; + } + + case HRSST_NamedPipe: + { + SetPropertyReadOnly(TEXT("ServerPipeName"), false); + bServerType = true; + break; + } + + default: + break; + } + + if (bServerType) + { + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); + } +} + +#endif // WITH_EDITOR + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h index e33abb796..ac4cd8d7a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h @@ -1,498 +1,498 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Object.h" -#include "Engine/EngineTypes.h" -#include "Engine/AssetUserData.h" -#include "PhysicsEngine/BodyInstance.h" - -#include "HoudiniRuntimeSettings.generated.h" - -class UFoliageType_InstancedStaticMesh; - -UENUM() -enum EHoudiniRuntimeSettingsSessionType -{ - // In process session. - HRSST_InProcess UMETA(Hidden), - - // TCP socket connection to Houdini Engine server. - HRSST_Socket UMETA(DisplayName = "TCP socket"), - - // Connection to Houdini Engine server via pipe connection. - HRSST_NamedPipe UMETA(DisplayName = "Named pipe or domain socket"), - - // No session, prevents license/Engine cook - HRSST_None UMETA(DisplayName = "None"), - - HRSST_MAX -}; - - -UENUM() -enum EHoudiniRuntimeSettingsRecomputeFlag -{ - // Recompute always. - HRSRF_Always UMETA(DisplayName = "Always"), - - // Recompute only if missing. - HRSRF_OnlyIfMissing UMETA(DisplayName = "Only if missing"), - - // Do not recompute. - HRSRF_Never UMETA(DisplayName = "Never"), - - HRSRF_MAX, -}; - -USTRUCT(BlueprintType) -struct HOUDINIENGINERUNTIME_API FHoudiniStaticMeshGenerationProperties -{ - GENERATED_USTRUCT_BODY() - - // Constructor - FHoudiniStaticMeshGenerationProperties(); - - public: - - /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Double Sided Geometry")) - uint32 bGeneratedDoubleSidedGeometry : 1; - - /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * GeneratedPhysMaterial; - - /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Collision Complexity")) - TEnumAsByte GeneratedCollisionTraceFlag; - - /** Resolution of lightmap. */ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 GeneratedLightMapResolution; - - /** Bias multiplier for Light Propagation Volume lighting. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float GeneratedLpvBiasMultiplier; - - /** Custom walkable slope setting for generated mesh's body. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride GeneratedWalkableSlopeOverride; - - /** The light map coordinate index. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light map coordinate index")) - int32 GeneratedLightMapCoordinateIndex; - - /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; - - /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Streaming Distance Multiplier")) - float GeneratedStreamingDistanceMultiplier; - - /** Default settings when using this mesh for instanced foliage. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh* GeneratedFoliageDefaultSettings = nullptr; - - /** Array of user data stored with the asset. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Asset User Data")) - TArray GeneratedAssetUserData; -}; - - -UCLASS(config = Engine, defaultconfig) -class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject -{ - GENERATED_UCLASS_BODY() - - public: - - // Destructor. - virtual ~UHoudiniRuntimeSettings(); - - // - virtual void PostInitProperties() override; - -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; -#endif - -protected: - - // Locate property of this class by name. - FProperty * LocateProperty(const FString & PropertyName) const; - - // Make specified property read only. - void SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly = true); - -#if WITH_EDITOR - // Update session ui elements. - void UpdateSessionUI(); -#endif - - public: - - //------------------------------------------------------------------------------------------------------------- - // Session options. - //------------------------------------------------------------------------------------------------------------- - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - TEnumAsByte SessionType; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - FString ServerHost; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - int32 ServerPort; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - FString ServerPipeName; - - // Whether to automatically start a HARS process - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - bool bStartAutomaticServer; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - float AutomaticServerTimeout; - - // If enabled, changes made in Houdini, when connected to Houdini running in Session Sync mode will be automatically be pushed to Unreal. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bSyncWithHoudiniCook; - - // If enabled, the Houdini Timeline time will be used to cook assets. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bCookUsingHoudiniTime; - - // Enable when wanting to sync the Houdini and Unreal viewport when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bSyncViewport; - - // If enabled, Houdini's viewport will be synchronized to Unreal's when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Houdini Viewport to Unreal's viewport.", EditCondition = "bSyncViewport")) - bool bSyncHoudiniViewport; - - // If enabled, Unreal's viewport will be synchronized to Houdini's when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Unreal Viewport to Houdini's viewport", EditCondition = "bSyncViewport")) - bool bSyncUnrealViewport; - - //------------------------------------------------------------------------------------------------------------- - // Instantiating options. - //------------------------------------------------------------------------------------------------------------- - - // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. - UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) - bool bShowMultiAssetDialog; - - // When enabled, the plugin will always instantiate the memory copy of a HDA stored in the .uasset file - // instead of using the latest version of the HDA file itself. - // This helps ensuring consistency between users when using HDAs, but will not work with expanded HDAs. - // When disabled, the plugin will always instantiate the latest version of the source HDA file if it is - // available, and will fallback to the memory copy if the source file cannot be found - UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) - bool bPreferHdaMemoryCopyOverHdaSourceFile; - - //------------------------------------------------------------------------------------------------------------- - // Cooking options. - //------------------------------------------------------------------------------------------------------------- - - // Whether houdini engine cooking is paused or not upon initializing the plugin - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - bool bPauseCookingOnStart; - - // Whether to display instantiation and cooking Slate notifications. - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - bool bDisplaySlateCookingNotifications; - - // Default content folder storing all the temporary cook data (Static meshes, materials, textures, landscape layer infos...) - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - FString DefaultTemporaryCookFolder; - - // Default content folder used when baking houdini asset data to native unreal objects - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - FString DefaultBakeFolder; - - //------------------------------------------------------------------------------------------------------------- - // Parameter options. - //------------------------------------------------------------------------------------------------------------- - - /* Deprecated! - // Forces the treatment of ramp parameters as multiparms. - UPROPERTY(GlobalConfig, EditAnywhere, Category = Parameters) - bool bTreatRampParametersAsMultiparms; - */ - - //------------------------------------------------------------------------------------------------------------- - // Geometry Marshalling - //------------------------------------------------------------------------------------------------------------- - - // If true, generated Landscapes will be marshalled using default unreal scaling. - // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms - // as Unreal's default landscape - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use default Unreal scaling.")) - bool MarshallingLandscapesUseDefaultUnrealScaling; - - // If true, generated Landscapes will be using full precision for their ZAxis, - // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use full resolution for data conversion.")) - bool MarshallingLandscapesUseFullResolution; - - // If true, the min/max values used to convert heightfields to landscape will be forced values - // This is usefull when importing multiple landscapes from different HDAs - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Force Min/Max values for data conversion")) - bool MarshallingLandscapesForceMinMaxValues; - - // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced min value")) - float MarshallingLandscapesForcedMinValue; - - // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced max value")) - float MarshallingLandscapesForcedMaxValue; - - // If this is enabled, additional rot & scale attributes are added on curve inputs - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Add rot & scale attributes on curve inputs")) - bool bAddRotAndScaleAttributesOnCurves; - - // Default resolution used when converting Unreal Spline Components to Houdini Curves (step in cm between control points, 0 only send the control points) - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Default spline resolution (cm)")) - float MarshallingSplineResolution; - - //------------------------------------------------------------------------------------------------------------- - // Static Mesh Options - //------------------------------------------------------------------------------------------------------------- - - // For StaticMesh outputs: should a fast proxy be created first? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Enable Proxy Static Mesh")) - bool bEnableProxyStaticMesh; - - // For static mesh outputs and socket actors: should spawn a default actor if the reference is invalid? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Show Default Mesh")) - bool bShowDefaultMesh; - - // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes After a Timeout", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementByTimer; - - // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bEnableProxyStaticMesh && bEnableProxyStaticMeshRefinementByTimer")) - float ProxyMeshAutoRefineTimeoutSeconds; - - // Automatically refine proxy meshes to UStaticMesh before the map is saved - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes When Saving a Map", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementOnPreSaveWorld; - - // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes On PIE", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementOnPreBeginPIE; - - //------------------------------------------------------------------------------------------------------------- - // Generated StaticMesh settings. - //------------------------------------------------------------------------------------------------------------- - - /// If true, the physics triangle mesh will use double sided faces for new Houdini Assets when doing scene queries. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Double Sided Geometry")) - uint32 bDoubleSidedGeometry : 1; - - /// Physical material to use for simple collision of new Houdini Assets. Encodes information about density, friction etc. - UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * PhysMaterial; - - /// Default properties of the body instance - UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /// Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate for new Houdini Assets. - UPROPERTY(GlobalConfig, VisibleDefaultsOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Collision Complexity")) - TEnumAsByte CollisionTraceFlag; - - /// Resolution of lightmap for baked lighting. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 LightMapResolution; - - /// Bias multiplier for Light Propagation Volume lighting for new Houdini Assets. - UPROPERTY(GlobalConfig, EditAnywhere, BlueprintReadOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float LpvBiasMultiplier; - - /// Default Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) - float GeneratedDistanceFieldResolutionScale; - - /// Custom walkable slope setting for bodies of new Houdini Assets. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride WalkableSlopeOverride; - - /// The UV coordinate index of lightmap - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light map coordinate index")) - int32 LightMapCoordinateIndex; - - /// True if mesh should use a less-conservative method of mip LOD texture factor computation for new Houdini Assets. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bUseMaximumStreamingTexelRatio : 1; - - /// Allows artists to adjust the distance where textures using UV 0 are streamed in/out for new Houdini Assets. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Streaming Distance Multiplier")) - float StreamingDistanceMultiplier; - - /// Default settings when using new Houdini Asset mesh for instanced foliage. - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh * FoliageDefaultSettings; - - /// Array of user data stored with the new Houdini Asset. - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Asset User Data")) - TArray AssetUserData; - - //------------------------------------------------------------------------------------------------------------- - // Static Mesh build settings. - //------------------------------------------------------------------------------------------------------------- - - // If true, UVs will be stored at full floating point precision. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") - bool bUseFullPrecisionUVs; - - // Source UV set for generated lightmap. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Source Lightmap Index")) - int32 SrcLightmapIndex; - - // Destination UV set for generated lightmap. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Destination Lightmap Index")) - int32 DstLightmapIndex; - - // Target lightmap resolution to for generated lightmap. Determines the padding between UV shells in a packed lightmap. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") - int32 MinLightmapResolution; - - // If true, degenerate triangles will be removed. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") - bool bRemoveDegenerates; - - // Lightmap UV generation - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Lightmap UVs")) - TEnumAsByte GenerateLightmapUVsFlag; - - // Normals generation - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Normals")) - TEnumAsByte RecomputeNormalsFlag; - - // Tangents generation - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Tangents")) - TEnumAsByte RecomputeTangentsFlag; - - // If true, recomputed tangents and normals will be calculated using MikkT Space. This method does require properly laid out UVs though otherwise you'll get a degenerate tangent warning - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Using MikkT Space")) - bool bUseMikkTSpace; - - // Required for PNT tessellation but can be slow. Recommend disabling for larger meshes. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") - bool bBuildAdjacencyBuffer; - - // If true, we will use the surface area and the corner angle of the triangle as a ratio when computing the normals. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") - uint8 bComputeWeightedNormals : 1; - - // Required to optimize mesh in mirrored transform. Double index buffer size. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") - uint8 bBuildReversedIndexBuffer : 1; - - // If true, Tangents will be stored at 16 bit vs 8 bit precision. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") - uint8 bUseHighPrecisionTangentBasis : 1; - - // Scale to apply to the mesh when allocating the distance field volume texture. - // The default scale is 1, which is assuming that the mesh will be placed unscaled in the world. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") - float DistanceFieldResolutionScale; - - // Whether to generate the distance field treating every triangle hit as a front face. - // When enabled prevents the distance field from being discarded due to the mesh being open, but also lowers Distance Field AO quality. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Two-Sided Distance Field Generation")) - uint8 bGenerateDistanceFieldAsIfTwoSided : 1; - - // Enable the Physical Material Mask - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Enable Physical Material Mask")) - uint8 bSupportFaceRemap : 1; - - //------------------------------------------------------------------------------------------------------------- - // PDG Commandlet import - //------------------------------------------------------------------------------------------------------------- - // Is the PDG commandlet enabled? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta = (DisplayName = "Async Importer Enabled")) - bool bPDGAsyncCommandletImportEnabled; - - //------------------------------------------------------------------------------------------------------------- - // Legacy - //------------------------------------------------------------------------------------------------------------- - // Whether to enable backward compatibility - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", Meta = (DisplayName = "Enable backward compatibility with Version 1")) - bool bEnableBackwardCompatibility; - - // Automatically rebuild legacy HAC - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", meta = (DisplayName = "Automatically rebuild legacy Houdini Asset Components", EditCondition = "bEnableBackwardCompatibility")) - bool bAutomaticLegacyHDARebuild; - - //------------------------------------------------------------------------------------------------------------- - // Custom Houdini Location - //------------------------------------------------------------------------------------------------------------- - // Whether to use custom Houdini location. - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Use custom Houdini location (requires restart)")) - bool bUseCustomHoudiniLocation; - - // Custom Houdini location (where HAPI library is located). - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Custom Houdini location")) - FDirectoryPath CustomHoudiniLocation; - - //------------------------------------------------------------------------------------------------------------- - // HAPI_Initialize - //------------------------------------------------------------------------------------------------------------- - // Evaluation thread stack size in bytes. -1 for default - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - int32 CookingThreadStackSize; - - // List of paths to Houdini-compatible .env files (; separated on Windows, : otherwise) - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString HoudiniEnvironmentFiles; - - // Path to find other OTL/HDA files - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString OtlSearchPath; - - // Sets HOUDINI_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString DsoSearchPath; - - // Sets HOUDINI_IMAGE_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString ImageDsoSearchPath; - - // Sets HOUDINI_AUDIO_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString AudioDsoSearchPath; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" +#include "Engine/AssetUserData.h" +#include "PhysicsEngine/BodyInstance.h" + +#include "HoudiniRuntimeSettings.generated.h" + +class UFoliageType_InstancedStaticMesh; + +UENUM() +enum EHoudiniRuntimeSettingsSessionType +{ + // In process session. + HRSST_InProcess UMETA(Hidden), + + // TCP socket connection to Houdini Engine server. + HRSST_Socket UMETA(DisplayName = "TCP socket"), + + // Connection to Houdini Engine server via pipe connection. + HRSST_NamedPipe UMETA(DisplayName = "Named pipe or domain socket"), + + // No session, prevents license/Engine cook + HRSST_None UMETA(DisplayName = "None"), + + HRSST_MAX +}; + + +UENUM() +enum EHoudiniRuntimeSettingsRecomputeFlag +{ + // Recompute always. + HRSRF_Always UMETA(DisplayName = "Always"), + + // Recompute only if missing. + HRSRF_OnlyIfMissing UMETA(DisplayName = "Only if missing"), + + // Do not recompute. + HRSRF_Never UMETA(DisplayName = "Never"), + + HRSRF_MAX, +}; + +USTRUCT(BlueprintType) +struct HOUDINIENGINERUNTIME_API FHoudiniStaticMeshGenerationProperties +{ + GENERATED_USTRUCT_BODY() + + // Constructor + FHoudiniStaticMeshGenerationProperties(); + + public: + + /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Double Sided Geometry")) + uint32 bGeneratedDoubleSidedGeometry : 1; + + /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * GeneratedPhysMaterial; + + /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Collision Complexity")) + TEnumAsByte GeneratedCollisionTraceFlag; + + /** Resolution of lightmap. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 GeneratedLightMapResolution; + + /** Bias multiplier for Light Propagation Volume lighting. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) + float GeneratedLpvBiasMultiplier; + + /** Custom walkable slope setting for generated mesh's body. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride GeneratedWalkableSlopeOverride; + + /** The light map coordinate index. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light map coordinate index")) + int32 GeneratedLightMapCoordinateIndex; + + /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; + + /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Streaming Distance Multiplier")) + float GeneratedStreamingDistanceMultiplier; + + /** Default settings when using this mesh for instanced foliage. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh* GeneratedFoliageDefaultSettings = nullptr; + + /** Array of user data stored with the asset. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Asset User Data")) + TArray GeneratedAssetUserData; +}; + + +UCLASS(config = Engine, defaultconfig) +class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject +{ + GENERATED_UCLASS_BODY() + + public: + + // Destructor. + virtual ~UHoudiniRuntimeSettings(); + + // + virtual void PostInitProperties() override; + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; +#endif + +protected: + + // Locate property of this class by name. + FProperty * LocateProperty(const FString & PropertyName) const; + + // Make specified property read only. + void SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly = true); + +#if WITH_EDITOR + // Update session ui elements. + void UpdateSessionUI(); +#endif + + public: + + //------------------------------------------------------------------------------------------------------------- + // Session options. + //------------------------------------------------------------------------------------------------------------- + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + TEnumAsByte SessionType; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + FString ServerHost; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + int32 ServerPort; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + FString ServerPipeName; + + // Whether to automatically start a HARS process + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + bool bStartAutomaticServer; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + float AutomaticServerTimeout; + + // If enabled, changes made in Houdini, when connected to Houdini running in Session Sync mode will be automatically be pushed to Unreal. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bSyncWithHoudiniCook; + + // If enabled, the Houdini Timeline time will be used to cook assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bCookUsingHoudiniTime; + + // Enable when wanting to sync the Houdini and Unreal viewport when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bSyncViewport; + + // If enabled, Houdini's viewport will be synchronized to Unreal's when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Houdini Viewport to Unreal's viewport.", EditCondition = "bSyncViewport")) + bool bSyncHoudiniViewport; + + // If enabled, Unreal's viewport will be synchronized to Houdini's when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Unreal Viewport to Houdini's viewport", EditCondition = "bSyncViewport")) + bool bSyncUnrealViewport; + + //------------------------------------------------------------------------------------------------------------- + // Instantiating options. + //------------------------------------------------------------------------------------------------------------- + + // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) + bool bShowMultiAssetDialog; + + // When enabled, the plugin will always instantiate the memory copy of a HDA stored in the .uasset file + // instead of using the latest version of the HDA file itself. + // This helps ensuring consistency between users when using HDAs, but will not work with expanded HDAs. + // When disabled, the plugin will always instantiate the latest version of the source HDA file if it is + // available, and will fallback to the memory copy if the source file cannot be found + UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) + bool bPreferHdaMemoryCopyOverHdaSourceFile; + + //------------------------------------------------------------------------------------------------------------- + // Cooking options. + //------------------------------------------------------------------------------------------------------------- + + // Whether houdini engine cooking is paused or not upon initializing the plugin + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + bool bPauseCookingOnStart; + + // Whether to display instantiation and cooking Slate notifications. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + bool bDisplaySlateCookingNotifications; + + // Default content folder storing all the temporary cook data (Static meshes, materials, textures, landscape layer infos...) + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + FString DefaultTemporaryCookFolder; + + // Default content folder used when baking houdini asset data to native unreal objects + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + FString DefaultBakeFolder; + + //------------------------------------------------------------------------------------------------------------- + // Parameter options. + //------------------------------------------------------------------------------------------------------------- + + /* Deprecated! + // Forces the treatment of ramp parameters as multiparms. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Parameters) + bool bTreatRampParametersAsMultiparms; + */ + + //------------------------------------------------------------------------------------------------------------- + // Geometry Marshalling + //------------------------------------------------------------------------------------------------------------- + + // If true, generated Landscapes will be marshalled using default unreal scaling. + // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms + // as Unreal's default landscape + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use default Unreal scaling.")) + bool MarshallingLandscapesUseDefaultUnrealScaling; + + // If true, generated Landscapes will be using full precision for their ZAxis, + // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use full resolution for data conversion.")) + bool MarshallingLandscapesUseFullResolution; + + // If true, the min/max values used to convert heightfields to landscape will be forced values + // This is usefull when importing multiple landscapes from different HDAs + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Force Min/Max values for data conversion")) + bool MarshallingLandscapesForceMinMaxValues; + + // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced min value")) + float MarshallingLandscapesForcedMinValue; + + // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced max value")) + float MarshallingLandscapesForcedMaxValue; + + // If this is enabled, additional rot & scale attributes are added on curve inputs + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Add rot & scale attributes on curve inputs")) + bool bAddRotAndScaleAttributesOnCurves; + + // Default resolution used when converting Unreal Spline Components to Houdini Curves (step in cm between control points, 0 only send the control points) + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Default spline resolution (cm)")) + float MarshallingSplineResolution; + + //------------------------------------------------------------------------------------------------------------- + // Static Mesh Options + //------------------------------------------------------------------------------------------------------------- + + // For StaticMesh outputs: should a fast proxy be created first? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Enable Proxy Static Mesh")) + bool bEnableProxyStaticMesh; + + // For static mesh outputs and socket actors: should spawn a default actor if the reference is invalid? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Show Default Mesh")) + bool bShowDefaultMesh; + + // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes After a Timeout", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementByTimer; + + // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bEnableProxyStaticMesh && bEnableProxyStaticMeshRefinementByTimer")) + float ProxyMeshAutoRefineTimeoutSeconds; + + // Automatically refine proxy meshes to UStaticMesh before the map is saved + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes When Saving a Map", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementOnPreSaveWorld; + + // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes On PIE", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementOnPreBeginPIE; + + //------------------------------------------------------------------------------------------------------------- + // Generated StaticMesh settings. + //------------------------------------------------------------------------------------------------------------- + + /// If true, the physics triangle mesh will use double sided faces for new Houdini Assets when doing scene queries. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Double Sided Geometry")) + uint32 bDoubleSidedGeometry : 1; + + /// Physical material to use for simple collision of new Houdini Assets. Encodes information about density, friction etc. + UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * PhysMaterial; + + /// Default properties of the body instance + UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /// Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate for new Houdini Assets. + UPROPERTY(GlobalConfig, VisibleDefaultsOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Collision Complexity")) + TEnumAsByte CollisionTraceFlag; + + /// Resolution of lightmap for baked lighting. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 LightMapResolution; + + /// Bias multiplier for Light Propagation Volume lighting for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, BlueprintReadOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) + float LpvBiasMultiplier; + + /// Default Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /// Custom walkable slope setting for bodies of new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride WalkableSlopeOverride; + + /// The UV coordinate index of lightmap + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light map coordinate index")) + int32 LightMapCoordinateIndex; + + /// True if mesh should use a less-conservative method of mip LOD texture factor computation for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bUseMaximumStreamingTexelRatio : 1; + + /// Allows artists to adjust the distance where textures using UV 0 are streamed in/out for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Streaming Distance Multiplier")) + float StreamingDistanceMultiplier; + + /// Default settings when using new Houdini Asset mesh for instanced foliage. + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh * FoliageDefaultSettings; + + /// Array of user data stored with the new Houdini Asset. + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Asset User Data")) + TArray AssetUserData; + + //------------------------------------------------------------------------------------------------------------- + // Static Mesh build settings. + //------------------------------------------------------------------------------------------------------------- + + // If true, UVs will be stored at full floating point precision. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bUseFullPrecisionUVs; + + // Source UV set for generated lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Source Lightmap Index")) + int32 SrcLightmapIndex; + + // Destination UV set for generated lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Destination Lightmap Index")) + int32 DstLightmapIndex; + + // Target lightmap resolution to for generated lightmap. Determines the padding between UV shells in a packed lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + int32 MinLightmapResolution; + + // If true, degenerate triangles will be removed. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bRemoveDegenerates; + + // Lightmap UV generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Lightmap UVs")) + TEnumAsByte GenerateLightmapUVsFlag; + + // Normals generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Normals")) + TEnumAsByte RecomputeNormalsFlag; + + // Tangents generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Tangents")) + TEnumAsByte RecomputeTangentsFlag; + + // If true, recomputed tangents and normals will be calculated using MikkT Space. This method does require properly laid out UVs though otherwise you'll get a degenerate tangent warning + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Using MikkT Space")) + bool bUseMikkTSpace; + + // Required for PNT tessellation but can be slow. Recommend disabling for larger meshes. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bBuildAdjacencyBuffer; + + // If true, we will use the surface area and the corner angle of the triangle as a ratio when computing the normals. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + uint8 bComputeWeightedNormals : 1; + + // Required to optimize mesh in mirrored transform. Double index buffer size. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + uint8 bBuildReversedIndexBuffer : 1; + + // If true, Tangents will be stored at 16 bit vs 8 bit precision. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + uint8 bUseHighPrecisionTangentBasis : 1; + + // Scale to apply to the mesh when allocating the distance field volume texture. + // The default scale is 1, which is assuming that the mesh will be placed unscaled in the world. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + float DistanceFieldResolutionScale; + + // Whether to generate the distance field treating every triangle hit as a front face. + // When enabled prevents the distance field from being discarded due to the mesh being open, but also lowers Distance Field AO quality. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Two-Sided Distance Field Generation")) + uint8 bGenerateDistanceFieldAsIfTwoSided : 1; + + // Enable the Physical Material Mask + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Enable Physical Material Mask")) + uint8 bSupportFaceRemap : 1; + + //------------------------------------------------------------------------------------------------------------- + // PDG Commandlet import + //------------------------------------------------------------------------------------------------------------- + // Is the PDG commandlet enabled? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta = (DisplayName = "Async Importer Enabled")) + bool bPDGAsyncCommandletImportEnabled; + + //------------------------------------------------------------------------------------------------------------- + // Legacy + //------------------------------------------------------------------------------------------------------------- + // Whether to enable backward compatibility + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", Meta = (DisplayName = "Enable backward compatibility with Version 1")) + bool bEnableBackwardCompatibility; + + // Automatically rebuild legacy HAC + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", meta = (DisplayName = "Automatically rebuild legacy Houdini Asset Components", EditCondition = "bEnableBackwardCompatibility")) + bool bAutomaticLegacyHDARebuild; + + //------------------------------------------------------------------------------------------------------------- + // Custom Houdini Location + //------------------------------------------------------------------------------------------------------------- + // Whether to use custom Houdini location. + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Use custom Houdini location (requires restart)")) + bool bUseCustomHoudiniLocation; + + // Custom Houdini location (where HAPI library is located). + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Custom Houdini location")) + FDirectoryPath CustomHoudiniLocation; + + //------------------------------------------------------------------------------------------------------------- + // HAPI_Initialize + //------------------------------------------------------------------------------------------------------------- + // Evaluation thread stack size in bytes. -1 for default + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + int32 CookingThreadStackSize; + + // List of paths to Houdini-compatible .env files (; separated on Windows, : otherwise) + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString HoudiniEnvironmentFiles; + + // Path to find other OTL/HDA files + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString OtlSearchPath; + + // Sets HOUDINI_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString DsoSearchPath; + + // Sets HOUDINI_IMAGE_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString ImageDsoSearchPath; + + // Sets HOUDINI_AUDIO_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString AudioDsoSearchPath; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp index e54c857d0..c118c7faf 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp @@ -1,554 +1,554 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineComponent.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniInput.h" -#include "HoudiniInputObject.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Components/MeshComponent.h" -#include "Algo/Reverse.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -void -UHoudiniSplineComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on HOUDINI_ENGINE_ENABLE_BACKWARD_COMPATIBILITY - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniSplineComponent_V1* CompatibilitySC = NewObject(); - CompatibilitySC->Serialize(Ar); - CompatibilitySC->UpdateFromLegacyData(this); - - Construct(CompatibilitySC->CurveDisplayPoints); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -UHoudiniSplineComponent::UHoudiniSplineComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , bClosed(false) - , bReversed(false) - , bIsHoudiniSplineVisible(true) - , CurveType(EHoudiniCurveType::Polygon) - , CurveMethod(EHoudiniCurveMethod::CVs) - , bHasChanged(false) - , bNeedsToTriggerUpdate(false) - , bIsInputCurve(false) - , bIsEditableOutputCurve(false) - , NodeId(-1) -{ - - // Add two default points to the curve - FTransform defaultPoint = FTransform::Identity; - - // Set this component to not tick? - // SetComponentTickEnabled(false); - - // Default curve. - CurvePoints.Add(defaultPoint); - DisplayPoints.Add(defaultPoint.GetLocation()); - - defaultPoint.SetTranslation(FVector(200.f, 0.f, 0.f)); - CurvePoints.Add(defaultPoint); - DisplayPoints.Add(defaultPoint.GetLocation()); - - bIsOutputCurve = false; - bCookOnCurveChanged = true; - -#if WITH_EDITOR - bPostUndo = false; -#endif -} - -void -UHoudiniSplineComponent::Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint) -{ - DisplayPoints.Empty(); - DisplayPointIndexDivider.Empty(); - - float DisplayPointStepSize; - - // Resample the display points for linear curve. - - if (InCurveDisplayPoints.Num() <= 0) - return; - - // Add an additional displaypoint to the end for closed curve - if (bClosed && InCurveDisplayPoints.Num() > 2) - { - FVector & FirstPoint = InCurveDisplayPoints[0]; - FVector ClosingPoint; - ClosingPoint.X = FirstPoint.X; - ClosingPoint.Y = FirstPoint.Y; - ClosingPoint.Z = FirstPoint.Z; - - InCurveDisplayPoints.Add(ClosingPoint); - } - - - if (CurveType == EHoudiniCurveType::Polygon) - { - FVector Pt1, Pt2; - Pt1 = InCurveDisplayPoints[0]; - int32 CurrentDisplayPointIndex = 0; - - for (int Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) - { - DisplayPointStepSize = 10.f; - - Pt2 = InCurveDisplayPoints[Index]; - - FVector Direction = Pt2 - Pt1; - - float SegmentLength = Direction.Size(); - - int32 NumOfDisplayPt = SegmentLength / DisplayPointStepSize; - - // Make sure there are at least 20 display points on a segment. - while( NumOfDisplayPt < 20 && SegmentLength > 0.01) - { - DisplayPointStepSize /= 2.f; - NumOfDisplayPt = SegmentLength / DisplayPointStepSize; - } - - Direction.Normalize(0.01f); - - FVector StepVector = Direction * DisplayPointStepSize; - - // Always add the start point of a line segment - FVector NextDisplayPt = Pt1; - if (NumOfDisplayPt == 0) DisplayPoints.Add(NextDisplayPt); - - for (int32 itr = 0; itr < NumOfDisplayPt; ++itr) - { - DisplayPoints.Add(NextDisplayPt); - NextDisplayPt += StepVector; - CurrentDisplayPointIndex += 1; - } - - DisplayPointIndexDivider.Add(CurrentDisplayPointIndex); - - Pt1 = Pt2; - } - - // Add the ending point - DisplayPoints.Add(Pt1); - // Duplicate the last index, to make the DisplaPointyIndexDivider array matches the length of DP array - DisplayPointIndexDivider.Add(CurrentDisplayPointIndex + 1); - } - else if (CurveType == EHoudiniCurveType::Points) - { - // do not add display points for Points curve type, just show the CVs - } - else - { - // Needs a better algorithm to divide the display points - // Refined display points does not strictly interpolate the curve points. - - FVector Pt1, Pt2; - Pt1 = InCurveDisplayPoints[0]; - - int Itr = 1; - - int32 ClosestIndex = -1; - float ClosestDistance = -1.f; - - for (int32 Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) - { - if (Itr >= CurvePoints.Num()) break; - - Pt2 = InCurveDisplayPoints[Index]; - - FVector ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); - - float Distance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); - - if (ClosestDistance < 0.f || Distance < ClosestDistance) - { - ClosestDistance = Distance; - ClosestIndex = Index; - } - else - { - Itr += 1; - if (Itr >= CurvePoints.Num()) break; - - DisplayPointIndexDivider.Add(Index-1); - - ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); - ClosestDistance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); - - } - } - - DisplayPointIndexDivider.Add(InCurveDisplayPoints.Num()); - - DisplayPoints = InCurveDisplayPoints; - } -} - - -void -UHoudiniSplineComponent::CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent) -{ - if (!OtherHoudiniSplineComponent) - return; - - CurvePoints = OtherHoudiniSplineComponent->CurvePoints; - DisplayPoints = OtherHoudiniSplineComponent->DisplayPoints; - DisplayPointIndexDivider = OtherHoudiniSplineComponent->DisplayPointIndexDivider; - CurveType = OtherHoudiniSplineComponent->CurveType; - CurveMethod = OtherHoudiniSplineComponent->CurveMethod; - bClosed = OtherHoudiniSplineComponent->bClosed; -#if WITH_EDITORONLY_DATA - bVisualizeComponent = OtherHoudiniSplineComponent->bVisualizeComponent; -#endif - bReversed = OtherHoudiniSplineComponent->bReversed; - SetVisibility(OtherHoudiniSplineComponent->IsVisible()); - - HoudiniSplineName = OtherHoudiniSplineComponent->HoudiniSplineName; -} - -UHoudiniSplineComponent::~UHoudiniSplineComponent() -{} - -void -UHoudiniSplineComponent::AppendPoint(const FTransform& NewPoint) -{ - CurvePoints.Add(NewPoint); -} - -void -UHoudiniSplineComponent::InsertPointAtIndex(const FTransform& NewPoint, const int32& Index) -{ - check(Index >= 0 && Index < CurvePoints.Num()); - CurvePoints.Insert(NewPoint, Index); - bHasChanged = true; -} - - -void -UHoudiniSplineComponent::RemovePointAtIndex(const int32& Index) -{ - check(Index >= 0 && Index < CurvePoints.Num()); - CurvePoints.RemoveAt(Index); - bHasChanged = true; -} - -void -UHoudiniSplineComponent::SetReversed(const bool& InReversed) -{ - // don't need to do anything if the reversed state doesn't change. - if (InReversed == bReversed) - return; - - bReversed = InReversed; - ReverseCurvePoints(); - MarkChanged(true); -} - -void -UHoudiniSplineComponent::ReverseCurvePoints() -{ - if (CurvePoints.Num() < 2) - return; - - Algo::Reverse(CurvePoints); -} - -void -UHoudiniSplineComponent::EditPointAtindex(const FTransform& NewPoint, const int32& Index) -{ - if (!CurvePoints.IsValidIndex(Index)) - return; - - CurvePoints[Index] = NewPoint; - bHasChanged = true; -} - -#if WITH_EDITOR -void -UHoudiniSplineComponent::PostEditChangeProperty(FPropertyChangedEvent& PeopertyChangedEvent) -{ - Super::PostEditChangeProperty(PeopertyChangedEvent); - - FName PropertyName = (PeopertyChangedEvent.Property != nullptr) ? PeopertyChangedEvent.Property->GetFName() : NAME_None; - - // Responses to the uproperty changes - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bClosed)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bClosed")); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bReversed)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bReversed")); - ReverseCurvePoints(); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveType)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveType")); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveMethod)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveMethod")); - MarkChanged(true); - } -} -#endif - -void -UHoudiniSplineComponent::PostLoad() -{ - Super::PostLoad(); -} - -TStructOnScope -UHoudiniSplineComponent::GetComponentInstanceData() const -{ - TStructOnScope ComponentInstanceData = MakeStructOnScope(this); - FHoudiniSplineComponentInstanceData* InstanceData = ComponentInstanceData.Cast(); - - return ComponentInstanceData; -} - -void -UHoudiniSplineComponent::ApplyComponentInstanceData( - FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS) -{ - check(ComponentInstanceData); -} - -void -UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) -{ - // Capture properties that we want to preserve during copy - const int32 PrevNodeId = NodeId; - - UActorComponent* FromComponent = Cast(FromObject); - check(FromComponent); - - UHoudiniSplineComponent* FromSplineComponent = Cast(FromObject); - if (FromSplineComponent) - { - CurvePoints = FromSplineComponent->CurvePoints; - DisplayPoints = FromSplineComponent->DisplayPoints; - DisplayPointIndexDivider = FromSplineComponent->DisplayPointIndexDivider; -#if WITH_EDITORONLY_DATA - EditedControlPointsIndexes = FromSplineComponent->EditedControlPointsIndexes; -#endif - - HoudiniSplineName = FromSplineComponent->HoudiniSplineName; - bClosed = FromSplineComponent->bClosed; - bIsHoudiniSplineVisible = FromSplineComponent->bIsHoudiniSplineVisible; - CurveType = FromSplineComponent->CurveType; - CurveMethod = FromSplineComponent->CurveMethod; - bIsInputCurve = FromSplineComponent->bIsInputCurve; - bIsOutputCurve = FromSplineComponent->bIsOutputCurve; - bIsEditableOutputCurve = FromSplineComponent->bIsEditableOutputCurve; - bCookOnCurveChanged = FromSplineComponent->bCookOnCurveChanged; - - bHasChanged = FromSplineComponent->bHasChanged; - bNeedsToTriggerUpdate = FromSplineComponent->bNeedsToTriggerUpdate; - } - - // Restore properties that we want to preserve - NodeId = PrevNodeId; -} - - -void -UHoudiniSplineComponent::OnUnregister() -{ - Super::OnUnregister(); -} - -void -UHoudiniSplineComponent::OnComponentCreated() -{ - Super::OnComponentCreated(); -} - -void -UHoudiniSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - Super::OnComponentDestroyed(bDestroyingHierarchy); - - if (IsInputCurve()) - { - // This component can't just come out of nowhere and decide to delete an input object! - // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! - - // InputObject->MarkPendingKill(); - - // if(NodeId > -1) - // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); - - SetNodeId(-1); // Set nodeId to invalid for reconstruct on re-do - } -} - - -#if WITH_EDITOR -void -UHoudiniSplineComponent::PostEditUndo() -{ - Super::PostEditUndo(); - - bPostUndo = true; - - MarkChanged(true); -} -#endif - -void -UHoudiniSplineComponent::SetOffset(const float& Offset) -{ - for (int n = 0; n < CurvePoints.Num(); ++n) - CurvePoints[n].AddToTranslation(FVector(0.f, Offset, 0.f)); - - for (int n = 0; n < DisplayPoints.Num(); ++n) - DisplayPoints[n] += FVector(0.f, Offset, 0.f); -} - -void -UHoudiniSplineComponent::ResetCurvePoints() -{ - CurvePoints.Empty(); -} - -void -UHoudiniSplineComponent::ResetDisplayPoints() -{ - DisplayPoints.Empty(); -} - -void -UHoudiniSplineComponent::AddCurvePoints(const TArray& Points) -{ - CurvePoints.Append(Points); -} - -void -UHoudiniSplineComponent::AddDisplayPoints(const TArray& Points) -{ - DisplayPoints.Append(Points); -} - -bool -UHoudiniSplineComponent::NeedsToTriggerUpdate() const -{ - return bNeedsToTriggerUpdate; -} - -void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate) -{ - bNeedsToTriggerUpdate = NeedsToTriggerUpdate; -} - -void UHoudiniSplineComponent::SetCurveType(const EHoudiniCurveType & NewCurveType) -{ - CurveType = NewCurveType; -} - -bool UHoudiniSplineComponent::HasChanged() const -{ - return bHasChanged; -} - -void UHoudiniSplineComponent::MarkChanged(const bool& Changed) -{ - bHasChanged = Changed; - bNeedsToTriggerUpdate = Changed; -} - -FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() -{ -} - -FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent) - : FActorComponentInstanceData(SourceComponent) -{ -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniInput.h" +#include "HoudiniInputObject.h" +#include "HoudiniPluginSerializationVersion.h" + +#include "Components/MeshComponent.h" +#include "Algo/Reverse.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +void +UHoudiniSplineComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on HOUDINI_ENGINE_ENABLE_BACKWARD_COMPATIBILITY + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniSplineComponent_V1* CompatibilitySC = NewObject(); + CompatibilitySC->Serialize(Ar); + CompatibilitySC->UpdateFromLegacyData(this); + + Construct(CompatibilitySC->CurveDisplayPoints); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +UHoudiniSplineComponent::UHoudiniSplineComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , bClosed(false) + , bReversed(false) + , bIsHoudiniSplineVisible(true) + , CurveType(EHoudiniCurveType::Polygon) + , CurveMethod(EHoudiniCurveMethod::CVs) + , bHasChanged(false) + , bNeedsToTriggerUpdate(false) + , bIsInputCurve(false) + , bIsEditableOutputCurve(false) + , NodeId(-1) +{ + + // Add two default points to the curve + FTransform defaultPoint = FTransform::Identity; + + // Set this component to not tick? + // SetComponentTickEnabled(false); + + // Default curve. + CurvePoints.Add(defaultPoint); + DisplayPoints.Add(defaultPoint.GetLocation()); + + defaultPoint.SetTranslation(FVector(200.f, 0.f, 0.f)); + CurvePoints.Add(defaultPoint); + DisplayPoints.Add(defaultPoint.GetLocation()); + + bIsOutputCurve = false; + bCookOnCurveChanged = true; + +#if WITH_EDITOR + bPostUndo = false; +#endif +} + +void +UHoudiniSplineComponent::Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint) +{ + DisplayPoints.Empty(); + DisplayPointIndexDivider.Empty(); + + float DisplayPointStepSize; + + // Resample the display points for linear curve. + + if (InCurveDisplayPoints.Num() <= 0) + return; + + // Add an additional displaypoint to the end for closed curve + if (bClosed && InCurveDisplayPoints.Num() > 2) + { + FVector & FirstPoint = InCurveDisplayPoints[0]; + FVector ClosingPoint; + ClosingPoint.X = FirstPoint.X; + ClosingPoint.Y = FirstPoint.Y; + ClosingPoint.Z = FirstPoint.Z; + + InCurveDisplayPoints.Add(ClosingPoint); + } + + + if (CurveType == EHoudiniCurveType::Polygon) + { + FVector Pt1, Pt2; + Pt1 = InCurveDisplayPoints[0]; + int32 CurrentDisplayPointIndex = 0; + + for (int Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) + { + DisplayPointStepSize = 10.f; + + Pt2 = InCurveDisplayPoints[Index]; + + FVector Direction = Pt2 - Pt1; + + float SegmentLength = Direction.Size(); + + int32 NumOfDisplayPt = SegmentLength / DisplayPointStepSize; + + // Make sure there are at least 20 display points on a segment. + while( NumOfDisplayPt < 20 && SegmentLength > 0.01) + { + DisplayPointStepSize /= 2.f; + NumOfDisplayPt = SegmentLength / DisplayPointStepSize; + } + + Direction.Normalize(0.01f); + + FVector StepVector = Direction * DisplayPointStepSize; + + // Always add the start point of a line segment + FVector NextDisplayPt = Pt1; + if (NumOfDisplayPt == 0) DisplayPoints.Add(NextDisplayPt); + + for (int32 itr = 0; itr < NumOfDisplayPt; ++itr) + { + DisplayPoints.Add(NextDisplayPt); + NextDisplayPt += StepVector; + CurrentDisplayPointIndex += 1; + } + + DisplayPointIndexDivider.Add(CurrentDisplayPointIndex); + + Pt1 = Pt2; + } + + // Add the ending point + DisplayPoints.Add(Pt1); + // Duplicate the last index, to make the DisplaPointyIndexDivider array matches the length of DP array + DisplayPointIndexDivider.Add(CurrentDisplayPointIndex + 1); + } + else if (CurveType == EHoudiniCurveType::Points) + { + // do not add display points for Points curve type, just show the CVs + } + else + { + // Needs a better algorithm to divide the display points + // Refined display points does not strictly interpolate the curve points. + + FVector Pt1, Pt2; + Pt1 = InCurveDisplayPoints[0]; + + int Itr = 1; + + int32 ClosestIndex = -1; + float ClosestDistance = -1.f; + + for (int32 Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) + { + if (Itr >= CurvePoints.Num()) break; + + Pt2 = InCurveDisplayPoints[Index]; + + FVector ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); + + float Distance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); + + if (ClosestDistance < 0.f || Distance < ClosestDistance) + { + ClosestDistance = Distance; + ClosestIndex = Index; + } + else + { + Itr += 1; + if (Itr >= CurvePoints.Num()) break; + + DisplayPointIndexDivider.Add(Index-1); + + ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); + ClosestDistance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); + + } + } + + DisplayPointIndexDivider.Add(InCurveDisplayPoints.Num()); + + DisplayPoints = InCurveDisplayPoints; + } +} + + +void +UHoudiniSplineComponent::CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent) +{ + if (!OtherHoudiniSplineComponent) + return; + + CurvePoints = OtherHoudiniSplineComponent->CurvePoints; + DisplayPoints = OtherHoudiniSplineComponent->DisplayPoints; + DisplayPointIndexDivider = OtherHoudiniSplineComponent->DisplayPointIndexDivider; + CurveType = OtherHoudiniSplineComponent->CurveType; + CurveMethod = OtherHoudiniSplineComponent->CurveMethod; + bClosed = OtherHoudiniSplineComponent->bClosed; +#if WITH_EDITORONLY_DATA + bVisualizeComponent = OtherHoudiniSplineComponent->bVisualizeComponent; +#endif + bReversed = OtherHoudiniSplineComponent->bReversed; + SetVisibility(OtherHoudiniSplineComponent->IsVisible()); + + HoudiniSplineName = OtherHoudiniSplineComponent->HoudiniSplineName; +} + +UHoudiniSplineComponent::~UHoudiniSplineComponent() +{} + +void +UHoudiniSplineComponent::AppendPoint(const FTransform& NewPoint) +{ + CurvePoints.Add(NewPoint); +} + +void +UHoudiniSplineComponent::InsertPointAtIndex(const FTransform& NewPoint, const int32& Index) +{ + check(Index >= 0 && Index < CurvePoints.Num()); + CurvePoints.Insert(NewPoint, Index); + bHasChanged = true; +} + + +void +UHoudiniSplineComponent::RemovePointAtIndex(const int32& Index) +{ + check(Index >= 0 && Index < CurvePoints.Num()); + CurvePoints.RemoveAt(Index); + bHasChanged = true; +} + +void +UHoudiniSplineComponent::SetReversed(const bool& InReversed) +{ + // don't need to do anything if the reversed state doesn't change. + if (InReversed == bReversed) + return; + + bReversed = InReversed; + ReverseCurvePoints(); + MarkChanged(true); +} + +void +UHoudiniSplineComponent::ReverseCurvePoints() +{ + if (CurvePoints.Num() < 2) + return; + + Algo::Reverse(CurvePoints); +} + +void +UHoudiniSplineComponent::EditPointAtindex(const FTransform& NewPoint, const int32& Index) +{ + if (!CurvePoints.IsValidIndex(Index)) + return; + + CurvePoints[Index] = NewPoint; + bHasChanged = true; +} + +#if WITH_EDITOR +void +UHoudiniSplineComponent::PostEditChangeProperty(FPropertyChangedEvent& PeopertyChangedEvent) +{ + Super::PostEditChangeProperty(PeopertyChangedEvent); + + FName PropertyName = (PeopertyChangedEvent.Property != nullptr) ? PeopertyChangedEvent.Property->GetFName() : NAME_None; + + // Responses to the uproperty changes + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bClosed)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bClosed")); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bReversed)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bReversed")); + ReverseCurvePoints(); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveType)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveType")); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveMethod)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveMethod")); + MarkChanged(true); + } +} +#endif + +void +UHoudiniSplineComponent::PostLoad() +{ + Super::PostLoad(); +} + +TStructOnScope +UHoudiniSplineComponent::GetComponentInstanceData() const +{ + TStructOnScope ComponentInstanceData = MakeStructOnScope(this); + FHoudiniSplineComponentInstanceData* InstanceData = ComponentInstanceData.Cast(); + + return ComponentInstanceData; +} + +void +UHoudiniSplineComponent::ApplyComponentInstanceData( + FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS) +{ + check(ComponentInstanceData); +} + +void +UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) +{ + // Capture properties that we want to preserve during copy + const int32 PrevNodeId = NodeId; + + UActorComponent* FromComponent = Cast(FromObject); + check(FromComponent); + + UHoudiniSplineComponent* FromSplineComponent = Cast(FromObject); + if (FromSplineComponent) + { + CurvePoints = FromSplineComponent->CurvePoints; + DisplayPoints = FromSplineComponent->DisplayPoints; + DisplayPointIndexDivider = FromSplineComponent->DisplayPointIndexDivider; +#if WITH_EDITORONLY_DATA + EditedControlPointsIndexes = FromSplineComponent->EditedControlPointsIndexes; +#endif + + HoudiniSplineName = FromSplineComponent->HoudiniSplineName; + bClosed = FromSplineComponent->bClosed; + bIsHoudiniSplineVisible = FromSplineComponent->bIsHoudiniSplineVisible; + CurveType = FromSplineComponent->CurveType; + CurveMethod = FromSplineComponent->CurveMethod; + bIsInputCurve = FromSplineComponent->bIsInputCurve; + bIsOutputCurve = FromSplineComponent->bIsOutputCurve; + bIsEditableOutputCurve = FromSplineComponent->bIsEditableOutputCurve; + bCookOnCurveChanged = FromSplineComponent->bCookOnCurveChanged; + + bHasChanged = FromSplineComponent->bHasChanged; + bNeedsToTriggerUpdate = FromSplineComponent->bNeedsToTriggerUpdate; + } + + // Restore properties that we want to preserve + NodeId = PrevNodeId; +} + + +void +UHoudiniSplineComponent::OnUnregister() +{ + Super::OnUnregister(); +} + +void +UHoudiniSplineComponent::OnComponentCreated() +{ + Super::OnComponentCreated(); +} + +void +UHoudiniSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); + + if (IsInputCurve()) + { + // This component can't just come out of nowhere and decide to delete an input object! + // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! + + // InputObject->MarkPendingKill(); + + // if(NodeId > -1) + // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); + + SetNodeId(-1); // Set nodeId to invalid for reconstruct on re-do + } +} + + +#if WITH_EDITOR +void +UHoudiniSplineComponent::PostEditUndo() +{ + Super::PostEditUndo(); + + bPostUndo = true; + + MarkChanged(true); +} +#endif + +void +UHoudiniSplineComponent::SetOffset(const float& Offset) +{ + for (int n = 0; n < CurvePoints.Num(); ++n) + CurvePoints[n].AddToTranslation(FVector(0.f, Offset, 0.f)); + + for (int n = 0; n < DisplayPoints.Num(); ++n) + DisplayPoints[n] += FVector(0.f, Offset, 0.f); +} + +void +UHoudiniSplineComponent::ResetCurvePoints() +{ + CurvePoints.Empty(); +} + +void +UHoudiniSplineComponent::ResetDisplayPoints() +{ + DisplayPoints.Empty(); +} + +void +UHoudiniSplineComponent::AddCurvePoints(const TArray& Points) +{ + CurvePoints.Append(Points); +} + +void +UHoudiniSplineComponent::AddDisplayPoints(const TArray& Points) +{ + DisplayPoints.Append(Points); +} + +bool +UHoudiniSplineComponent::NeedsToTriggerUpdate() const +{ + return bNeedsToTriggerUpdate; +} + +void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate) +{ + bNeedsToTriggerUpdate = NeedsToTriggerUpdate; +} + +void UHoudiniSplineComponent::SetCurveType(const EHoudiniCurveType & NewCurveType) +{ + CurveType = NewCurveType; +} + +bool UHoudiniSplineComponent::HasChanged() const +{ + return bHasChanged; +} + +void UHoudiniSplineComponent::MarkChanged(const bool& Changed) +{ + bHasChanged = Changed; + bNeedsToTriggerUpdate = Changed; +} + +FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() +{ +} + +FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent) + : FActorComponentInstanceData(SourceComponent) +{ +} + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h index ef8160bdf..d467566a1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h @@ -1,276 +1,276 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "HoudiniEngineCopyPropertiesInterface.h" -#include "UObject/ObjectMacros.h" -#include "Components/SceneComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniSplineComponent.generated.h" - -class UHoudiniAssetComponent; - -enum class EHoudiniCurveType : int8; - -enum class EHoudiniCurveMethod : int8; - -class UHoudiniInputObject; - -UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) -class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, public IHoudiniEngineCopyPropertiesInterface -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniSplineComponent_V1; - - virtual ~UHoudiniSplineComponent(); - - virtual void Serialize(FArchive & Ar) override; - - public: - - void Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint = -1); - - void CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent); - - void ResetCurvePoints(); - - void ResetDisplayPoints(); - - void AddCurvePoints(const TArray& Points); - - void AddDisplayPoints(const TArray& Points); - - void AppendPoint(const FTransform& NewPoint); - - void InsertPointAtIndex(const FTransform& NewPoint, const int32& Index); - - void RemovePointAtIndex(const int32& Index); - - void EditPointAtindex(const FTransform& NewPoint, const int32& Index); - - void MarkModified(const bool & InModified) { bHasChanged = InModified; }; - - // To set the offset of default position of houdini curve - void SetOffset(const float& Offset); - - bool HasChanged() const; - - void MarkChanged(const bool& Changed); - - FORCEINLINE - FString& GetHoudiniSplineName() { return HoudiniSplineName; } - - FORCEINLINE - void SetHoudiniSplineName(const FString& NewName) { HoudiniSplineName = NewName; } - - bool NeedsToTriggerUpdate() const; - - void SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate); - - FORCEINLINE - EHoudiniCurveType GetCurveType() const { return CurveType; } - - void SetCurveType(const EHoudiniCurveType& NewCurveType); - - FORCEINLINE - EHoudiniCurveMethod GetCurveMethod() const { return CurveMethod; } - - FORCEINLINE - void SetCurveMethod(const EHoudiniCurveMethod& NewCurveMethod) { CurveMethod = NewCurveMethod; } - - FORCEINLINE - int32 GetCurvePointCount() const { return CurvePoints.Num(); } - - FORCEINLINE - bool IsClosedCurve() const { return bClosed; } - - FORCEINLINE - void SetClosedCurve(const bool& Closed) { bClosed = Closed; } - - FORCEINLINE - bool IsReversed() const { return bReversed; } - - void SetReversed(const bool& Reversed); - - FORCEINLINE - bool IsInputCurve() const { return bIsInputCurve; } - - FORCEINLINE - void SetIsInputCurve(const bool& bIsInput) { bIsInputCurve = bIsInput; } - - FORCEINLINE - bool IsEditableOutputCurve() const { return bIsEditableOutputCurve; } - - FORCEINLINE - void SetIsEditableOutputCurve(const bool& bInIsEditable) { bIsEditableOutputCurve = bInIsEditable; }; - - FORCEINLINE - int32 GetNodeId() const { return NodeId; } - - FORCEINLINE - void SetNodeId(const int32& NewNodeId) { NodeId = NewNodeId; } - - FORCEINLINE - FString GetGeoPartName() const { return PartName; } - - FORCEINLINE - bool IsHoudiniSplineVisible() const { return bIsHoudiniSplineVisible; } - - FORCEINLINE - void SetHoudiniSplineVisible(bool Visible) { bIsHoudiniSplineVisible = Visible; } - - FORCEINLINE - void SetGeoPartName(const FString & InPartName) { PartName = InPartName; } - - virtual void OnUnregister() override; - - virtual void OnComponentCreated() override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - -#if WITH_EDITOR - virtual void PostEditUndo() override; - virtual void PostEditChangeProperty(FPropertyChangedEvent & PeopertyChangedEvent) override; -#endif - - virtual void PostLoad() override; - - virtual TStructOnScope GetComponentInstanceData() const override; - void ApplyComponentInstanceData(struct FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS); - - virtual void CopyPropertiesFrom(UObject* FromObject) override; - - private: - - void ReverseCurvePoints(); - - public: - - UPROPERTY() - TArray CurvePoints; - - UPROPERTY() - TArray DisplayPoints; - - UPROPERTY() - TArray DisplayPointIndexDivider; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - FString HoudiniSplineName; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bClosed; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bReversed; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bIsHoudiniSplineVisible; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - EHoudiniCurveType CurveType; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - EHoudiniCurveMethod CurveMethod; - - UPROPERTY() - bool bIsOutputCurve; - - UPROPERTY() - bool bCookOnCurveChanged; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - TArray EditedControlPointsIndexes; - - UPROPERTY(NonTransactional) - bool bPostUndo; -#endif - - protected: - // Corresponding geo part object. - FHoudiniGeoPartObject HoudiniGeoPartObject; - - private: - UPROPERTY(Transient, DuplicateTransient) - bool bHasChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Whether this is a Houdini curve input - UPROPERTY() - bool bIsInputCurve; - - UPROPERTY() - bool bIsEditableOutputCurve; - - // Corresponds to the Curve NodeId in Houdini - UPROPERTY(Transient, DuplicateTransient) - int32 NodeId; - - UPROPERTY() - FString PartName; -}; - -// Used to store HoudiniAssetComponent data during BP reconstruction -USTRUCT() -struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData -{ - GENERATED_BODY() -public: - - FHoudiniSplineComponentInstanceData(); - FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent); - - virtual ~FHoudiniSplineComponentInstanceData() = default; - - virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override - { - Super::ApplyToComponent(Component, CacheApplyPhase); - CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); - } - - UPROPERTY() - TArray CurvePoints; - - UPROPERTY() - TArray DisplayPoints; - - UPROPERTY() - TArray DisplayPointIndexDivider; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - TArray EditedControlPointsIndexes; -#endif - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniEngineCopyPropertiesInterface.h" +#include "UObject/ObjectMacros.h" +#include "Components/SceneComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniSplineComponent.generated.h" + +class UHoudiniAssetComponent; + +enum class EHoudiniCurveType : int8; + +enum class EHoudiniCurveMethod : int8; + +class UHoudiniInputObject; + +UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) +class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, public IHoudiniEngineCopyPropertiesInterface +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniSplineComponent_V1; + + virtual ~UHoudiniSplineComponent(); + + virtual void Serialize(FArchive & Ar) override; + + public: + + void Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint = -1); + + void CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent); + + void ResetCurvePoints(); + + void ResetDisplayPoints(); + + void AddCurvePoints(const TArray& Points); + + void AddDisplayPoints(const TArray& Points); + + void AppendPoint(const FTransform& NewPoint); + + void InsertPointAtIndex(const FTransform& NewPoint, const int32& Index); + + void RemovePointAtIndex(const int32& Index); + + void EditPointAtindex(const FTransform& NewPoint, const int32& Index); + + void MarkModified(const bool & InModified) { bHasChanged = InModified; }; + + // To set the offset of default position of houdini curve + void SetOffset(const float& Offset); + + bool HasChanged() const; + + void MarkChanged(const bool& Changed); + + FORCEINLINE + FString& GetHoudiniSplineName() { return HoudiniSplineName; } + + FORCEINLINE + void SetHoudiniSplineName(const FString& NewName) { HoudiniSplineName = NewName; } + + bool NeedsToTriggerUpdate() const; + + void SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate); + + FORCEINLINE + EHoudiniCurveType GetCurveType() const { return CurveType; } + + void SetCurveType(const EHoudiniCurveType& NewCurveType); + + FORCEINLINE + EHoudiniCurveMethod GetCurveMethod() const { return CurveMethod; } + + FORCEINLINE + void SetCurveMethod(const EHoudiniCurveMethod& NewCurveMethod) { CurveMethod = NewCurveMethod; } + + FORCEINLINE + int32 GetCurvePointCount() const { return CurvePoints.Num(); } + + FORCEINLINE + bool IsClosedCurve() const { return bClosed; } + + FORCEINLINE + void SetClosedCurve(const bool& Closed) { bClosed = Closed; } + + FORCEINLINE + bool IsReversed() const { return bReversed; } + + void SetReversed(const bool& Reversed); + + FORCEINLINE + bool IsInputCurve() const { return bIsInputCurve; } + + FORCEINLINE + void SetIsInputCurve(const bool& bIsInput) { bIsInputCurve = bIsInput; } + + FORCEINLINE + bool IsEditableOutputCurve() const { return bIsEditableOutputCurve; } + + FORCEINLINE + void SetIsEditableOutputCurve(const bool& bInIsEditable) { bIsEditableOutputCurve = bInIsEditable; }; + + FORCEINLINE + int32 GetNodeId() const { return NodeId; } + + FORCEINLINE + void SetNodeId(const int32& NewNodeId) { NodeId = NewNodeId; } + + FORCEINLINE + FString GetGeoPartName() const { return PartName; } + + FORCEINLINE + bool IsHoudiniSplineVisible() const { return bIsHoudiniSplineVisible; } + + FORCEINLINE + void SetHoudiniSplineVisible(bool Visible) { bIsHoudiniSplineVisible = Visible; } + + FORCEINLINE + void SetGeoPartName(const FString & InPartName) { PartName = InPartName; } + + virtual void OnUnregister() override; + + virtual void OnComponentCreated() override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + +#if WITH_EDITOR + virtual void PostEditUndo() override; + virtual void PostEditChangeProperty(FPropertyChangedEvent & PeopertyChangedEvent) override; +#endif + + virtual void PostLoad() override; + + virtual TStructOnScope GetComponentInstanceData() const override; + void ApplyComponentInstanceData(struct FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS); + + virtual void CopyPropertiesFrom(UObject* FromObject) override; + + private: + + void ReverseCurvePoints(); + + public: + + UPROPERTY() + TArray CurvePoints; + + UPROPERTY() + TArray DisplayPoints; + + UPROPERTY() + TArray DisplayPointIndexDivider; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + FString HoudiniSplineName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bClosed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bReversed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bIsHoudiniSplineVisible; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + EHoudiniCurveType CurveType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + EHoudiniCurveMethod CurveMethod; + + UPROPERTY() + bool bIsOutputCurve; + + UPROPERTY() + bool bCookOnCurveChanged; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + TArray EditedControlPointsIndexes; + + UPROPERTY(NonTransactional) + bool bPostUndo; +#endif + + protected: + // Corresponding geo part object. + FHoudiniGeoPartObject HoudiniGeoPartObject; + + private: + UPROPERTY(Transient, DuplicateTransient) + bool bHasChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Whether this is a Houdini curve input + UPROPERTY() + bool bIsInputCurve; + + UPROPERTY() + bool bIsEditableOutputCurve; + + // Corresponds to the Curve NodeId in Houdini + UPROPERTY(Transient, DuplicateTransient) + int32 NodeId; + + UPROPERTY() + FString PartName; +}; + +// Used to store HoudiniAssetComponent data during BP reconstruction +USTRUCT() +struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData +{ + GENERATED_BODY() +public: + + FHoudiniSplineComponentInstanceData(); + FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent); + + virtual ~FHoudiniSplineComponentInstanceData() = default; + + virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override + { + Super::ApplyToComponent(Component, CacheApplyPhase); + CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); + } + + UPROPERTY() + TArray CurvePoints; + + UPROPERTY() + TArray DisplayPoints; + + UPROPERTY() + TArray DisplayPointIndexDivider; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + TArray EditedControlPointsIndexes; +#endif + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp index 7b0975753..c04ddcbd4 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp @@ -1,343 +1,343 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMesh.h" - -UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - bHasNormals = false; - bHasTangents = false; - bHasColors = false; - NumUVLayers = false; - bHasPerFaceMaterials = false; -} - -void UHoudiniStaticMesh::Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials) -{ - // Initialize the vertex positions and triangle indices arrays - VertexPositions.Init(FVector::ZeroVector, InNumVertices); - TriangleIndices.Init(FIntVector(-1, -1, -1), InNumTriangles); - if (InInitialNumStaticMaterials > 0) - StaticMaterials.Init(FStaticMaterial(), InInitialNumStaticMaterials); - else - StaticMaterials.Empty(); - - SetNumUVLayers(InNumUVLayers); - SetHasNormals(bInHasNormals); - SetHasTangents(bInHasTangents); - SetHasColors(bInHasColors); - SetHasPerFaceMaterials(bInHasPerFaceMaterials); -} - -void UHoudiniStaticMesh::SetHasPerFaceMaterials(bool bInHasPerFaceMaterials) -{ - bHasPerFaceMaterials = bInHasPerFaceMaterials; - if (bHasPerFaceMaterials) - MaterialIDsPerTriangle.Init(-1, GetNumTriangles()); - else - MaterialIDsPerTriangle.Empty(); -} - -void UHoudiniStaticMesh::SetHasNormals(bool bInHasNormals) -{ - bHasNormals = bInHasNormals; - if (bHasNormals) - VertexInstanceNormals.Init(FVector(0, 0, 1), GetNumVertexInstances()); - else - VertexInstanceNormals.Empty(); -} - -void UHoudiniStaticMesh::SetHasTangents(bool bInHasTangents) -{ - bHasTangents = bInHasTangents; - if (bHasTangents) - { - VertexInstanceUTangents.Init(FVector(1, 0, 0), GetNumVertexInstances()); - VertexInstanceVTangents.Init(FVector(0, 1, 0), GetNumVertexInstances()); - } - else - { - VertexInstanceUTangents.Empty(); - VertexInstanceVTangents.Empty(); - } -} - -void UHoudiniStaticMesh::SetHasColors(bool bInHasColors) -{ - bHasColors = bInHasColors; - if (bHasColors) - VertexInstanceColors.Init(FColor(127, 127, 127), GetNumVertexInstances()); - else - VertexInstanceColors.Empty(); -} - -void UHoudiniStaticMesh::SetNumUVLayers(uint32 InNumUVLayers) -{ - NumUVLayers = InNumUVLayers; - if (NumUVLayers > 0) - VertexInstanceUVs.Init(FVector2D::ZeroVector, GetNumVertexInstances() * NumUVLayers); - else - VertexInstanceUVs.Empty(); -} - -void UHoudiniStaticMesh::SetNumStaticMaterials(uint32 InNumMaterials) -{ - if (InNumMaterials > 0) - StaticMaterials.SetNum(InNumMaterials); - else - StaticMaterials.Empty(); -} - -void UHoudiniStaticMesh::SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition) -{ - check(VertexPositions.IsValidIndex(InVertexIndex)); - - VertexPositions[InVertexIndex] = InPosition; -} - -void UHoudiniStaticMesh::SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices) -{ - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[0])); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[1])); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[2])); - - TriangleIndices[InTriangleIndex] = InTriangleVertexIndices; -} - -void UHoudiniStaticMesh::SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal) -{ - if (!bHasNormals) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceNormals.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceNormals[VertexInstanceIndex] = InNormal; -} - -void UHoudiniStaticMesh::SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent) -{ - if (!bHasTangents) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceUTangents.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceUTangents[VertexInstanceIndex] = InUTangent; -} - -void UHoudiniStaticMesh::SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent) -{ - if (!bHasTangents) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceVTangents.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceVTangents[VertexInstanceIndex] = InVTangent; -} - -void UHoudiniStaticMesh::SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor) -{ - if (!bHasColors) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceColors.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceColors[VertexInstanceIndex] = InColor; -} - -void UHoudiniStaticMesh::SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV) -{ - if (NumUVLayers <= 0) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceUVIndex = InUVLayer * GetNumVertexInstances() + InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceUVs.IsValidIndex(VertexInstanceUVIndex)); - - VertexInstanceUVs[VertexInstanceUVIndex] = InUV; -} - -void UHoudiniStaticMesh::SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID) -{ - if (!bHasPerFaceMaterials) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - check(MaterialIDsPerTriangle.IsValidIndex(InTriangleIndex)); - - MaterialIDsPerTriangle[InTriangleIndex] = InMaterialID; -} - -void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial) -{ - check(StaticMaterials.IsValidIndex(InMaterialIndex)); - StaticMaterials[InMaterialIndex] = InStaticMaterial; -} - -void UHoudiniStaticMesh::Optimize() -{ - VertexPositions.Shrink(); - TriangleIndices.Shrink(); - VertexInstanceColors.Shrink(); - VertexInstanceNormals.Shrink(); - VertexInstanceUTangents.Shrink(); - VertexInstanceVTangents.Shrink(); - VertexInstanceUVs.Shrink(); - MaterialIDsPerTriangle.Shrink(); - StaticMaterials.Shrink(); -} - -FBox UHoudiniStaticMesh::CalcBounds() const -{ - const uint32 NumVertices = VertexPositions.Num(); - - if (NumVertices == 0) - return FBox(); - - const FVector InitPosition = VertexPositions[0]; - double MinX = InitPosition.X, MaxX = InitPosition.X, MinY = InitPosition.Y, MaxY = InitPosition.Y, MinZ = InitPosition.Z, MaxZ = InitPosition.Z; - for (uint32 VertIdx = 0; VertIdx < NumVertices; ++VertIdx) - { - const FVector Position = VertexPositions[VertIdx]; - if (Position.X < MinX) MinX = Position.X; else if (Position.X > MaxX) MaxX = Position.X; - if (Position.Y < MinY) MinY = Position.Y; else if (Position.Y > MaxY) MaxY = Position.Y; - if (Position.Z < MinZ) MinZ = Position.Z; else if (Position.Z > MaxZ) MaxZ = Position.Z; - } - - return FBox(FVector(MinX, MinY, MinZ), FVector(MaxX, MaxY, MaxZ)); -} - -UMaterialInterface* UHoudiniStaticMesh::GetMaterial(int32 InMaterialIndex) -{ - check(StaticMaterials.IsValidIndex(InMaterialIndex)); - - return StaticMaterials[InMaterialIndex].MaterialInterface; -} - -int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const -{ - if (InMaterialSlotName == NAME_None) - return -1; - - const uint32 NumMaterials = StaticMaterials.Num(); - for (uint32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) - { - if (StaticMaterials[MaterialIndex].MaterialSlotName == InMaterialSlotName) - return (int32)MaterialIndex; - } - - return -1; -} - -bool UHoudiniStaticMesh::IsValid(bool bInSkipVertexIndicesCheck) const -{ - // Validate the number of vertices, indices and triangles. This is basically the same function as FRawMesh::IsValid() - const int32 NumVertices = GetNumVertices(); - const int32 NumVertexInstances = GetNumVertexInstances(); - const int32 NumTriangles = GetNumTriangles(); - - auto ValidateAttributeArraySize = [](int32 InArrayNum, int32 InExpectedSize) - { - return InArrayNum == 0 || InArrayNum == InExpectedSize; - }; - - bool bValid = NumVertices > 0 - && NumVertexInstances > 0 - && NumTriangles > 0 - && (NumVertexInstances / 3) == NumTriangles - && ValidateAttributeArraySize(MaterialIDsPerTriangle.Num(), NumTriangles) - && ValidateAttributeArraySize(VertexInstanceNormals.Num(), NumVertexInstances) - && ValidateAttributeArraySize(VertexInstanceUTangents.Num(), NumVertexInstances) - && ValidateAttributeArraySize(VertexInstanceVTangents.Num(), NumVertexInstances) - && ValidateAttributeArraySize(VertexInstanceColors.Num(), NumVertexInstances) - // Must have at least 1 UV layer - && NumUVLayers > 0 - && VertexInstanceUVs.Num() == NumUVLayers * NumVertexInstances; - - if (!bInSkipVertexIndicesCheck) - { - int32 TriangleIndex = 0; - while (bValid && TriangleIndex < NumTriangles) - { - bValid = bValid && (TriangleIndices[TriangleIndex].X < NumVertices); - bValid = bValid && (TriangleIndices[TriangleIndex].Y < NumVertices); - bValid = bValid && (TriangleIndices[TriangleIndex].Z < NumVertices); - TriangleIndex++; - } - } - - return bValid; -} - -void UHoudiniStaticMesh::Serialize(FArchive &InArchive) -{ - Super::Serialize(InArchive); - - VertexPositions.Shrink(); - VertexPositions.BulkSerialize(InArchive); - - TriangleIndices.Shrink(); - TriangleIndices.BulkSerialize(InArchive); - - VertexInstanceColors.Shrink(); - VertexInstanceColors.BulkSerialize(InArchive); - - VertexInstanceNormals.Shrink(); - VertexInstanceNormals.BulkSerialize(InArchive); - - VertexInstanceUTangents.Shrink(); - VertexInstanceUTangents.BulkSerialize(InArchive); - - VertexInstanceVTangents.Shrink(); - VertexInstanceVTangents.BulkSerialize(InArchive); - - VertexInstanceUVs.Shrink(); - VertexInstanceUVs.BulkSerialize(InArchive); - - MaterialIDsPerTriangle.Shrink(); - MaterialIDsPerTriangle.BulkSerialize(InArchive); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMesh.h" + +UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bHasNormals = false; + bHasTangents = false; + bHasColors = false; + NumUVLayers = false; + bHasPerFaceMaterials = false; +} + +void UHoudiniStaticMesh::Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials) +{ + // Initialize the vertex positions and triangle indices arrays + VertexPositions.Init(FVector::ZeroVector, InNumVertices); + TriangleIndices.Init(FIntVector(-1, -1, -1), InNumTriangles); + if (InInitialNumStaticMaterials > 0) + StaticMaterials.Init(FStaticMaterial(), InInitialNumStaticMaterials); + else + StaticMaterials.Empty(); + + SetNumUVLayers(InNumUVLayers); + SetHasNormals(bInHasNormals); + SetHasTangents(bInHasTangents); + SetHasColors(bInHasColors); + SetHasPerFaceMaterials(bInHasPerFaceMaterials); +} + +void UHoudiniStaticMesh::SetHasPerFaceMaterials(bool bInHasPerFaceMaterials) +{ + bHasPerFaceMaterials = bInHasPerFaceMaterials; + if (bHasPerFaceMaterials) + MaterialIDsPerTriangle.Init(-1, GetNumTriangles()); + else + MaterialIDsPerTriangle.Empty(); +} + +void UHoudiniStaticMesh::SetHasNormals(bool bInHasNormals) +{ + bHasNormals = bInHasNormals; + if (bHasNormals) + VertexInstanceNormals.Init(FVector(0, 0, 1), GetNumVertexInstances()); + else + VertexInstanceNormals.Empty(); +} + +void UHoudiniStaticMesh::SetHasTangents(bool bInHasTangents) +{ + bHasTangents = bInHasTangents; + if (bHasTangents) + { + VertexInstanceUTangents.Init(FVector(1, 0, 0), GetNumVertexInstances()); + VertexInstanceVTangents.Init(FVector(0, 1, 0), GetNumVertexInstances()); + } + else + { + VertexInstanceUTangents.Empty(); + VertexInstanceVTangents.Empty(); + } +} + +void UHoudiniStaticMesh::SetHasColors(bool bInHasColors) +{ + bHasColors = bInHasColors; + if (bHasColors) + VertexInstanceColors.Init(FColor(127, 127, 127), GetNumVertexInstances()); + else + VertexInstanceColors.Empty(); +} + +void UHoudiniStaticMesh::SetNumUVLayers(uint32 InNumUVLayers) +{ + NumUVLayers = InNumUVLayers; + if (NumUVLayers > 0) + VertexInstanceUVs.Init(FVector2D::ZeroVector, GetNumVertexInstances() * NumUVLayers); + else + VertexInstanceUVs.Empty(); +} + +void UHoudiniStaticMesh::SetNumStaticMaterials(uint32 InNumMaterials) +{ + if (InNumMaterials > 0) + StaticMaterials.SetNum(InNumMaterials); + else + StaticMaterials.Empty(); +} + +void UHoudiniStaticMesh::SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition) +{ + check(VertexPositions.IsValidIndex(InVertexIndex)); + + VertexPositions[InVertexIndex] = InPosition; +} + +void UHoudiniStaticMesh::SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices) +{ + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[0])); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[1])); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[2])); + + TriangleIndices[InTriangleIndex] = InTriangleVertexIndices; +} + +void UHoudiniStaticMesh::SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal) +{ + if (!bHasNormals) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceNormals.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceNormals[VertexInstanceIndex] = InNormal; +} + +void UHoudiniStaticMesh::SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent) +{ + if (!bHasTangents) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceUTangents.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceUTangents[VertexInstanceIndex] = InUTangent; +} + +void UHoudiniStaticMesh::SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent) +{ + if (!bHasTangents) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceVTangents.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceVTangents[VertexInstanceIndex] = InVTangent; +} + +void UHoudiniStaticMesh::SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor) +{ + if (!bHasColors) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceColors.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceColors[VertexInstanceIndex] = InColor; +} + +void UHoudiniStaticMesh::SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV) +{ + if (NumUVLayers <= 0) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceUVIndex = InUVLayer * GetNumVertexInstances() + InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceUVs.IsValidIndex(VertexInstanceUVIndex)); + + VertexInstanceUVs[VertexInstanceUVIndex] = InUV; +} + +void UHoudiniStaticMesh::SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID) +{ + if (!bHasPerFaceMaterials) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + check(MaterialIDsPerTriangle.IsValidIndex(InTriangleIndex)); + + MaterialIDsPerTriangle[InTriangleIndex] = InMaterialID; +} + +void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial) +{ + check(StaticMaterials.IsValidIndex(InMaterialIndex)); + StaticMaterials[InMaterialIndex] = InStaticMaterial; +} + +void UHoudiniStaticMesh::Optimize() +{ + VertexPositions.Shrink(); + TriangleIndices.Shrink(); + VertexInstanceColors.Shrink(); + VertexInstanceNormals.Shrink(); + VertexInstanceUTangents.Shrink(); + VertexInstanceVTangents.Shrink(); + VertexInstanceUVs.Shrink(); + MaterialIDsPerTriangle.Shrink(); + StaticMaterials.Shrink(); +} + +FBox UHoudiniStaticMesh::CalcBounds() const +{ + const uint32 NumVertices = VertexPositions.Num(); + + if (NumVertices == 0) + return FBox(); + + const FVector InitPosition = VertexPositions[0]; + double MinX = InitPosition.X, MaxX = InitPosition.X, MinY = InitPosition.Y, MaxY = InitPosition.Y, MinZ = InitPosition.Z, MaxZ = InitPosition.Z; + for (uint32 VertIdx = 0; VertIdx < NumVertices; ++VertIdx) + { + const FVector Position = VertexPositions[VertIdx]; + if (Position.X < MinX) MinX = Position.X; else if (Position.X > MaxX) MaxX = Position.X; + if (Position.Y < MinY) MinY = Position.Y; else if (Position.Y > MaxY) MaxY = Position.Y; + if (Position.Z < MinZ) MinZ = Position.Z; else if (Position.Z > MaxZ) MaxZ = Position.Z; + } + + return FBox(FVector(MinX, MinY, MinZ), FVector(MaxX, MaxY, MaxZ)); +} + +UMaterialInterface* UHoudiniStaticMesh::GetMaterial(int32 InMaterialIndex) +{ + check(StaticMaterials.IsValidIndex(InMaterialIndex)); + + return StaticMaterials[InMaterialIndex].MaterialInterface; +} + +int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const +{ + if (InMaterialSlotName == NAME_None) + return -1; + + const uint32 NumMaterials = StaticMaterials.Num(); + for (uint32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) + { + if (StaticMaterials[MaterialIndex].MaterialSlotName == InMaterialSlotName) + return (int32)MaterialIndex; + } + + return -1; +} + +bool UHoudiniStaticMesh::IsValid(bool bInSkipVertexIndicesCheck) const +{ + // Validate the number of vertices, indices and triangles. This is basically the same function as FRawMesh::IsValid() + const int32 NumVertices = GetNumVertices(); + const int32 NumVertexInstances = GetNumVertexInstances(); + const int32 NumTriangles = GetNumTriangles(); + + auto ValidateAttributeArraySize = [](int32 InArrayNum, int32 InExpectedSize) + { + return InArrayNum == 0 || InArrayNum == InExpectedSize; + }; + + bool bValid = NumVertices > 0 + && NumVertexInstances > 0 + && NumTriangles > 0 + && (NumVertexInstances / 3) == NumTriangles + && ValidateAttributeArraySize(MaterialIDsPerTriangle.Num(), NumTriangles) + && ValidateAttributeArraySize(VertexInstanceNormals.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceUTangents.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceVTangents.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceColors.Num(), NumVertexInstances) + // Must have at least 1 UV layer + && NumUVLayers > 0 + && VertexInstanceUVs.Num() == NumUVLayers * NumVertexInstances; + + if (!bInSkipVertexIndicesCheck) + { + int32 TriangleIndex = 0; + while (bValid && TriangleIndex < NumTriangles) + { + bValid = bValid && (TriangleIndices[TriangleIndex].X < NumVertices); + bValid = bValid && (TriangleIndices[TriangleIndex].Y < NumVertices); + bValid = bValid && (TriangleIndices[TriangleIndex].Z < NumVertices); + TriangleIndex++; + } + } + + return bValid; +} + +void UHoudiniStaticMesh::Serialize(FArchive &InArchive) +{ + Super::Serialize(InArchive); + + VertexPositions.Shrink(); + VertexPositions.BulkSerialize(InArchive); + + TriangleIndices.Shrink(); + TriangleIndices.BulkSerialize(InArchive); + + VertexInstanceColors.Shrink(); + VertexInstanceColors.BulkSerialize(InArchive); + + VertexInstanceNormals.Shrink(); + VertexInstanceNormals.BulkSerialize(InArchive); + + VertexInstanceUTangents.Shrink(); + VertexInstanceUTangents.BulkSerialize(InArchive); + + VertexInstanceVTangents.Shrink(); + VertexInstanceVTangents.BulkSerialize(InArchive); + + VertexInstanceUVs.Shrink(); + VertexInstanceUVs.BulkSerialize(InArchive); + + MaterialIDsPerTriangle.Shrink(); + MaterialIDsPerTriangle.BulkSerialize(InArchive); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h index 99aa0830f..f012ddc82 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h @@ -1,234 +1,234 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Engine/StaticMesh.h" - -#include "HoudiniStaticMesh.generated.h" - -/** - * This is a simple static mesh that is meant to be built in one go, without modifications afterwards. - * The number of vertices and triangles must be known before hand. - */ -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject -{ - GENERATED_BODY() - -public: - - UHoudiniStaticMesh(const FObjectInitializer &ObjectInitializer); - - // Clears all existing data and initializes internal arrays to the relevant sizes to accommodate the - // mesh based InNumVertices, InNumTriangles, UVs etc. - UFUNCTION() - void Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials); - - UFUNCTION() - bool HasPerFaceMaterials() const { return bHasPerFaceMaterials; } - - UFUNCTION() - void SetHasPerFaceMaterials(bool bInHasPerFaceMaterials); - - UFUNCTION() - bool HasNormals() const { return bHasNormals; } - - UFUNCTION() - void SetHasNormals(bool bInHasNormals); - - UFUNCTION() - bool HasTangents() const { return bHasTangents; } - - UFUNCTION() - void SetHasTangents(bool bInHasTangents); - - UFUNCTION() - bool HasColors() const { return bHasColors; } - - UFUNCTION() - void SetHasColors(bool bInHasColors); - - UFUNCTION() - uint32 GetNumUVLayers() const { return NumUVLayers; } - - UFUNCTION() - void SetNumUVLayers(uint32 InNumUVLayers); - - UFUNCTION() - uint32 GetNumStaticMaterials() const { return StaticMaterials.Num(); } - - UFUNCTION() - void SetNumStaticMaterials(uint32 InNumStaticMaterials); - - UFUNCTION() - uint32 GetNumVertices() const { return VertexPositions.Num(); } - - UFUNCTION() - uint32 GetNumTriangles() const { return TriangleIndices.Num(); } - - UFUNCTION() - uint32 GetNumVertexInstances() const { return TriangleIndices.Num() * 3; } - - UFUNCTION() - void SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition); - - UFUNCTION() - void SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices); - - UFUNCTION() - void SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal); - - UFUNCTION() - void SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent); - - UFUNCTION() - void SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent); - - UFUNCTION() - void SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor); - - UFUNCTION() - void SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV); - - UFUNCTION() - void SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID); - - UFUNCTION() - void SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial); - - UFUNCTION() - uint32 AddStaticMaterial(const FStaticMaterial& InStaticMaterial) { return StaticMaterials.Add(InStaticMaterial); } - - // Meant to be called after the mesh data arrays are populated. - // Currently only calls Shrink on the arrays - UFUNCTION() - void Optimize(); - - UFUNCTION() - FBox CalcBounds() const; - - UFUNCTION() - const TArray& GetVertexPositions() const { return VertexPositions; } - - UFUNCTION() - const TArray& GetTriangleIndices() const { return TriangleIndices; } - - UFUNCTION() - const TArray& GetVertexInstanceColors() const { return VertexInstanceColors; } - - UFUNCTION() - const TArray& GetVertexInstanceNormals() const { return VertexInstanceNormals; } - - UFUNCTION() - const TArray& GetVertexInstanceUTangents() const { return VertexInstanceUTangents; } - - UFUNCTION() - const TArray& GetVertexInstanceVTangents() const { return VertexInstanceVTangents; } - - UFUNCTION() - const TArray& GetVertexInstanceUVs() const { return VertexInstanceUVs; } - - UFUNCTION() - const TArray& GetMaterialIDsPerTriangle() const { return MaterialIDsPerTriangle; } - - UFUNCTION() - const TArray& GetStaticMaterials() const { return StaticMaterials; } - - TArray& GetStaticMaterials() { return StaticMaterials; } - - UFUNCTION() - UMaterialInterface* GetMaterial(int32 InMaterialIndex); - - UFUNCTION() - int32 GetMaterialIndex(FName InMaterialSlotName) const; - - // Checks if the mesh is valid by checking face, vertex and attribute (normals etc) counts. - // If bSkipVertexIndicesCheck is true, then we don't loop over all triangle vertex indices to - // check if each index is valid (< NumVertices) - UFUNCTION() - bool IsValid(bool bInSkipVertexIndicesCheck=false) const; - - // Custom serialization: we use TArray::BulkSerialize to speed up array serialization - virtual void Serialize(FArchive &InArchive) override; - -protected: - - UPROPERTY() - bool bHasNormals; - - UPROPERTY() - bool bHasTangents; - - UPROPERTY() - bool bHasColors; - - /** The number of UV layers that the mesh has */ - UPROPERTY() - uint32 NumUVLayers; - - UPROPERTY() - bool bHasPerFaceMaterials; - - /** Vertex positions. The vertex id == vertex index => indexes into this array. */ - UPROPERTY(SkipSerialization) - TArray VertexPositions; - - /** Triangle vertices. Triangle id == triangle index => indexes into this array, which returns a FIntVector of - * vertex ids/indices for VertexPositions. - */ - UPROPERTY(SkipSerialization) - TArray TriangleIndices; - - /** Array of colors per vertex instance, in other words, a color per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceColors; - - /** Array of normals per vertex instance, in other words, a normal per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceNormals; - - /** Array of U tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceUTangents; - - /** Array of V tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceVTangents; - - /** Array of UV layers to array of per triangle-vertex UVs. Index: UVLayerIndex * (NumVertexInstances) + 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceUVs; - - /** Array of material ID per triangle. Indexed by Triangle ID/Index. */ - UPROPERTY(SkipSerialization) - TArray MaterialIDsPerTriangle; - - /** The materials of the mesh. Index by MaterialID (MaterialIndex). */ - UPROPERTY() - TArray StaticMaterials; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/StaticMesh.h" + +#include "HoudiniStaticMesh.generated.h" + +/** + * This is a simple static mesh that is meant to be built in one go, without modifications afterwards. + * The number of vertices and triangles must be known before hand. + */ +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject +{ + GENERATED_BODY() + +public: + + UHoudiniStaticMesh(const FObjectInitializer &ObjectInitializer); + + // Clears all existing data and initializes internal arrays to the relevant sizes to accommodate the + // mesh based InNumVertices, InNumTriangles, UVs etc. + UFUNCTION() + void Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials); + + UFUNCTION() + bool HasPerFaceMaterials() const { return bHasPerFaceMaterials; } + + UFUNCTION() + void SetHasPerFaceMaterials(bool bInHasPerFaceMaterials); + + UFUNCTION() + bool HasNormals() const { return bHasNormals; } + + UFUNCTION() + void SetHasNormals(bool bInHasNormals); + + UFUNCTION() + bool HasTangents() const { return bHasTangents; } + + UFUNCTION() + void SetHasTangents(bool bInHasTangents); + + UFUNCTION() + bool HasColors() const { return bHasColors; } + + UFUNCTION() + void SetHasColors(bool bInHasColors); + + UFUNCTION() + uint32 GetNumUVLayers() const { return NumUVLayers; } + + UFUNCTION() + void SetNumUVLayers(uint32 InNumUVLayers); + + UFUNCTION() + uint32 GetNumStaticMaterials() const { return StaticMaterials.Num(); } + + UFUNCTION() + void SetNumStaticMaterials(uint32 InNumStaticMaterials); + + UFUNCTION() + uint32 GetNumVertices() const { return VertexPositions.Num(); } + + UFUNCTION() + uint32 GetNumTriangles() const { return TriangleIndices.Num(); } + + UFUNCTION() + uint32 GetNumVertexInstances() const { return TriangleIndices.Num() * 3; } + + UFUNCTION() + void SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition); + + UFUNCTION() + void SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices); + + UFUNCTION() + void SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal); + + UFUNCTION() + void SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent); + + UFUNCTION() + void SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent); + + UFUNCTION() + void SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor); + + UFUNCTION() + void SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV); + + UFUNCTION() + void SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID); + + UFUNCTION() + void SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial); + + UFUNCTION() + uint32 AddStaticMaterial(const FStaticMaterial& InStaticMaterial) { return StaticMaterials.Add(InStaticMaterial); } + + // Meant to be called after the mesh data arrays are populated. + // Currently only calls Shrink on the arrays + UFUNCTION() + void Optimize(); + + UFUNCTION() + FBox CalcBounds() const; + + UFUNCTION() + const TArray& GetVertexPositions() const { return VertexPositions; } + + UFUNCTION() + const TArray& GetTriangleIndices() const { return TriangleIndices; } + + UFUNCTION() + const TArray& GetVertexInstanceColors() const { return VertexInstanceColors; } + + UFUNCTION() + const TArray& GetVertexInstanceNormals() const { return VertexInstanceNormals; } + + UFUNCTION() + const TArray& GetVertexInstanceUTangents() const { return VertexInstanceUTangents; } + + UFUNCTION() + const TArray& GetVertexInstanceVTangents() const { return VertexInstanceVTangents; } + + UFUNCTION() + const TArray& GetVertexInstanceUVs() const { return VertexInstanceUVs; } + + UFUNCTION() + const TArray& GetMaterialIDsPerTriangle() const { return MaterialIDsPerTriangle; } + + UFUNCTION() + const TArray& GetStaticMaterials() const { return StaticMaterials; } + + TArray& GetStaticMaterials() { return StaticMaterials; } + + UFUNCTION() + UMaterialInterface* GetMaterial(int32 InMaterialIndex); + + UFUNCTION() + int32 GetMaterialIndex(FName InMaterialSlotName) const; + + // Checks if the mesh is valid by checking face, vertex and attribute (normals etc) counts. + // If bSkipVertexIndicesCheck is true, then we don't loop over all triangle vertex indices to + // check if each index is valid (< NumVertices) + UFUNCTION() + bool IsValid(bool bInSkipVertexIndicesCheck=false) const; + + // Custom serialization: we use TArray::BulkSerialize to speed up array serialization + virtual void Serialize(FArchive &InArchive) override; + +protected: + + UPROPERTY() + bool bHasNormals; + + UPROPERTY() + bool bHasTangents; + + UPROPERTY() + bool bHasColors; + + /** The number of UV layers that the mesh has */ + UPROPERTY() + uint32 NumUVLayers; + + UPROPERTY() + bool bHasPerFaceMaterials; + + /** Vertex positions. The vertex id == vertex index => indexes into this array. */ + UPROPERTY(SkipSerialization) + TArray VertexPositions; + + /** Triangle vertices. Triangle id == triangle index => indexes into this array, which returns a FIntVector of + * vertex ids/indices for VertexPositions. + */ + UPROPERTY(SkipSerialization) + TArray TriangleIndices; + + /** Array of colors per vertex instance, in other words, a color per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceColors; + + /** Array of normals per vertex instance, in other words, a normal per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceNormals; + + /** Array of U tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceUTangents; + + /** Array of V tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceVTangents; + + /** Array of UV layers to array of per triangle-vertex UVs. Index: UVLayerIndex * (NumVertexInstances) + 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceUVs; + + /** Array of material ID per triangle. Indexed by Triangle ID/Index. */ + UPROPERTY(SkipSerialization) + TArray MaterialIDsPerTriangle; + + /** The materials of the mesh. Index by MaterialID (MaterialIndex). */ + UPROPERTY() + TArray StaticMaterials; +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp index 452daab7a..d2cb3026e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp @@ -1,213 +1,213 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMeshComponent.h" - -#include "Components/BillboardComponent.h" -#include "Engine/CollisionProfile.h" - -#include "HoudiniStaticMesh.h" -#include "HoudiniStaticMeshSceneProxy.h" - - -UHoudiniStaticMeshComponent::UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer) : - Super(InInitialzer) -{ - PrimaryComponentTick.bCanEverTick = false; - - SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); - - Mesh = nullptr; - bHoudiniIconVisible = true; - -#if WITH_EDITOR - bVisualizeComponent = true; -#endif -} - -void UHoudiniStaticMeshComponent::OnRegister() -{ - Super::OnRegister(); - -#if WITH_EDITORONLY_DATA - if (bVisualizeComponent && SpriteComponent != nullptr && GetOwner()) - { - SpriteComponent->SetSprite(LoadObject(nullptr, TEXT("/HoudiniEngine/Textures/icon_houdini_logo_128.icon_houdini_logo_128"))); - UpdateSpriteComponent(); - } -#endif -} - -//void -//UHoudiniStaticMeshComponent::PostLoad() -//{ -// Super::PostLoad(); -// -// //NotifyMeshUpdated(); -//} - -void UHoudiniStaticMeshComponent::SetMesh(UHoudiniStaticMesh *InMesh) -{ - if (Mesh == InMesh) - return; - - Mesh = InMesh; - NotifyMeshUpdated(); -} - -FPrimitiveSceneProxy* UHoudiniStaticMeshComponent::CreateSceneProxy() -{ - check(SceneProxy == nullptr); - - FHoudiniStaticMeshSceneProxy* NewProxy = nullptr; - if (Mesh && Mesh->GetNumTriangles() > 0) - { - NewProxy = new FHoudiniStaticMeshSceneProxy(this, GetScene()->GetFeatureLevel()); - NewProxy->Build(); - } - return NewProxy; -} - -FBoxSphereBounds UHoudiniStaticMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const -{ - FBox LocalBoundingBox = LocalBounds; - FBoxSphereBounds Ret(LocalBoundingBox.TransformBy(InLocalToWorld)); - Ret.BoxExtent *= BoundsScale; - Ret.SphereRadius *= BoundsScale; - return Ret; -} - -#if WITH_EDITOR -void UHoudiniStaticMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - const FName NAME_HoudiniIconVisible(TEXT("bHoudiniIconVisible")); - const FName NAME_Mesh(TEXT("Mesh")); - const FName NAME_PropertyChanged = PropertyChangedEvent.GetPropertyName(); - if (NAME_PropertyChanged == NAME_HoudiniIconVisible) - { - UpdateSpriteComponent(); - } - else if (NAME_PropertyChanged == NAME_Mesh) - { - NotifyMeshUpdated(); - } -} -#endif - -void UHoudiniStaticMeshComponent::SetHoudiniIconVisible(bool bInHoudiniIconVisible) -{ - bHoudiniIconVisible = bInHoudiniIconVisible; -#if WITH_EDITORONLY_DATA - UpdateSpriteComponent(); -#endif -} - -void UHoudiniStaticMeshComponent::NotifyMeshUpdated() -{ - MarkRenderStateDirty(); - if (Mesh) - { - LocalBounds = Mesh->CalcBounds(); - } - else - { - LocalBounds.Init(); - } - -#if WITH_EDITORONLY_DATA - UpdateSpriteComponent(); -#endif - - UpdateBounds(); -} - -#if WITH_EDITORONLY_DATA -void UHoudiniStaticMeshComponent::UpdateSpriteComponent() -{ - if (SpriteComponent) - { - SpriteComponent->SetRelativeLocation(FVector(0, 0, LocalBounds.GetExtent().Z)); - SpriteComponent->SetVisibility(bHoudiniIconVisible); - } -} -#endif - -int32 UHoudiniStaticMeshComponent::GetNumMaterials() const -{ - // From UStaticMesh: - // @note : you don't have to consider Materials.Num() - // that only counts if overridden and it can't be more than GetStaticMesh()->Materials. - if (Mesh) - { - return Mesh->GetNumStaticMaterials(); - } - else - { - return 0; - } -} - -int32 UHoudiniStaticMeshComponent::GetMaterialIndex(FName MaterialSlotName) const -{ - return Mesh ? Mesh->GetMaterialIndex(MaterialSlotName) : -1; -} - -TArray UHoudiniStaticMeshComponent::GetMaterialSlotNames() const -{ - TArray MaterialNames; - if (Mesh) - { - const TArray &StaticMaterials = Mesh->GetStaticMaterials(); - const int32 NumMaterials = StaticMaterials.Num(); - for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) - { - const FStaticMaterial &StaticMaterial = StaticMaterials[MaterialIndex]; - MaterialNames.Add(StaticMaterial.MaterialSlotName); - } - } - return MaterialNames; -} - -bool UHoudiniStaticMeshComponent::IsMaterialSlotNameValid(FName MaterialSlotName) const -{ - return GetMaterialIndex(MaterialSlotName) >= 0; -} - -UMaterialInterface* UHoudiniStaticMeshComponent::GetMaterial(int32 MaterialIndex) const -{ - // From UStaticMesh: - // If we have a base materials array, use that - if (OverrideMaterials.IsValidIndex(MaterialIndex) && OverrideMaterials[MaterialIndex]) - { - return OverrideMaterials[MaterialIndex]; - } - // Otherwise get from static mesh - else - { - return Mesh ? Mesh->GetMaterial(MaterialIndex) : nullptr; - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMeshComponent.h" + +#include "Components/BillboardComponent.h" +#include "Engine/CollisionProfile.h" + +#include "HoudiniStaticMesh.h" +#include "HoudiniStaticMeshSceneProxy.h" + + +UHoudiniStaticMeshComponent::UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer) : + Super(InInitialzer) +{ + PrimaryComponentTick.bCanEverTick = false; + + SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); + + Mesh = nullptr; + bHoudiniIconVisible = true; + +#if WITH_EDITOR + bVisualizeComponent = true; +#endif +} + +void UHoudiniStaticMeshComponent::OnRegister() +{ + Super::OnRegister(); + +#if WITH_EDITORONLY_DATA + if (bVisualizeComponent && SpriteComponent != nullptr && GetOwner()) + { + SpriteComponent->SetSprite(LoadObject(nullptr, TEXT("/HoudiniEngine/Textures/icon_houdini_logo_128.icon_houdini_logo_128"))); + UpdateSpriteComponent(); + } +#endif +} + +//void +//UHoudiniStaticMeshComponent::PostLoad() +//{ +// Super::PostLoad(); +// +// //NotifyMeshUpdated(); +//} + +void UHoudiniStaticMeshComponent::SetMesh(UHoudiniStaticMesh *InMesh) +{ + if (Mesh == InMesh) + return; + + Mesh = InMesh; + NotifyMeshUpdated(); +} + +FPrimitiveSceneProxy* UHoudiniStaticMeshComponent::CreateSceneProxy() +{ + check(SceneProxy == nullptr); + + FHoudiniStaticMeshSceneProxy* NewProxy = nullptr; + if (Mesh && Mesh->GetNumTriangles() > 0) + { + NewProxy = new FHoudiniStaticMeshSceneProxy(this, GetScene()->GetFeatureLevel()); + NewProxy->Build(); + } + return NewProxy; +} + +FBoxSphereBounds UHoudiniStaticMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const +{ + FBox LocalBoundingBox = LocalBounds; + FBoxSphereBounds Ret(LocalBoundingBox.TransformBy(InLocalToWorld)); + Ret.BoxExtent *= BoundsScale; + Ret.SphereRadius *= BoundsScale; + return Ret; +} + +#if WITH_EDITOR +void UHoudiniStaticMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + const FName NAME_HoudiniIconVisible(TEXT("bHoudiniIconVisible")); + const FName NAME_Mesh(TEXT("Mesh")); + const FName NAME_PropertyChanged = PropertyChangedEvent.GetPropertyName(); + if (NAME_PropertyChanged == NAME_HoudiniIconVisible) + { + UpdateSpriteComponent(); + } + else if (NAME_PropertyChanged == NAME_Mesh) + { + NotifyMeshUpdated(); + } +} +#endif + +void UHoudiniStaticMeshComponent::SetHoudiniIconVisible(bool bInHoudiniIconVisible) +{ + bHoudiniIconVisible = bInHoudiniIconVisible; +#if WITH_EDITORONLY_DATA + UpdateSpriteComponent(); +#endif +} + +void UHoudiniStaticMeshComponent::NotifyMeshUpdated() +{ + MarkRenderStateDirty(); + if (Mesh) + { + LocalBounds = Mesh->CalcBounds(); + } + else + { + LocalBounds.Init(); + } + +#if WITH_EDITORONLY_DATA + UpdateSpriteComponent(); +#endif + + UpdateBounds(); +} + +#if WITH_EDITORONLY_DATA +void UHoudiniStaticMeshComponent::UpdateSpriteComponent() +{ + if (SpriteComponent) + { + SpriteComponent->SetRelativeLocation(FVector(0, 0, LocalBounds.GetExtent().Z)); + SpriteComponent->SetVisibility(bHoudiniIconVisible); + } +} +#endif + +int32 UHoudiniStaticMeshComponent::GetNumMaterials() const +{ + // From UStaticMesh: + // @note : you don't have to consider Materials.Num() + // that only counts if overridden and it can't be more than GetStaticMesh()->Materials. + if (Mesh) + { + return Mesh->GetNumStaticMaterials(); + } + else + { + return 0; + } +} + +int32 UHoudiniStaticMeshComponent::GetMaterialIndex(FName MaterialSlotName) const +{ + return Mesh ? Mesh->GetMaterialIndex(MaterialSlotName) : -1; +} + +TArray UHoudiniStaticMeshComponent::GetMaterialSlotNames() const +{ + TArray MaterialNames; + if (Mesh) + { + const TArray &StaticMaterials = Mesh->GetStaticMaterials(); + const int32 NumMaterials = StaticMaterials.Num(); + for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) + { + const FStaticMaterial &StaticMaterial = StaticMaterials[MaterialIndex]; + MaterialNames.Add(StaticMaterial.MaterialSlotName); + } + } + return MaterialNames; +} + +bool UHoudiniStaticMeshComponent::IsMaterialSlotNameValid(FName MaterialSlotName) const +{ + return GetMaterialIndex(MaterialSlotName) >= 0; +} + +UMaterialInterface* UHoudiniStaticMeshComponent::GetMaterial(int32 MaterialIndex) const +{ + // From UStaticMesh: + // If we have a base materials array, use that + if (OverrideMaterials.IsValidIndex(MaterialIndex) && OverrideMaterials[MaterialIndex]) + { + return OverrideMaterials[MaterialIndex]; + } + // Otherwise get from static mesh + else + { + return Mesh ? Mesh->GetMaterial(MaterialIndex) : nullptr; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h index e428c9d4e..1af521325 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h @@ -1,98 +1,98 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Components/MeshComponent.h" - -#include "HoudiniStaticMeshComponent.generated.h" - -class UHoudiniStaticMesh; -class UBillboardComponent; - -UCLASS(EditInlineNew, ClassGroup = "Houdini Engine | Rendering") -class HOUDINIENGINERUNTIME_API UHoudiniStaticMeshComponent : public UMeshComponent -{ - GENERATED_BODY() - -public: - UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer); - - UFUNCTION() - void SetMesh(UHoudiniStaticMesh *InMesh); - - UFUNCTION() - UHoudiniStaticMesh* GetMesh() { return Mesh; } - - // Call this if the mesh updated (outside of calling SetMesh). - UFUNCTION() - void NotifyMeshUpdated(); - - virtual void OnRegister() override; - - //virtual void PostLoad() override; - - // UPrimitiveComponent interface - virtual FPrimitiveSceneProxy* CreateSceneProxy() override; - virtual int32 GetNumMaterials() const override; - virtual UMaterialInterface* GetMaterial(int32 ElementIndex) const override; - virtual int32 GetMaterialIndex(FName MaterialSlotName) const override; - virtual TArray GetMaterialSlotNames() const override; - virtual bool IsMaterialSlotNameValid(FName MaterialSlotName) const override; - // end - UPrimitiveComponent interface - - // USceneComponent Interface. - virtual FBoxSphereBounds CalcBounds(const FTransform& InLocalToWorld) const override; - // end - USceneComponent Interface. - -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; -#endif - - UFUNCTION() - bool IsHoudiniIconVisible() const { return bHoudiniIconVisible; } - - UFUNCTION() - void SetHoudiniIconVisible(bool bInHoudiniIconVisible); - -protected: -#if WITH_EDITORONLY_DATA - virtual void UpdateSpriteComponent(); -#endif - - /** The mesh. */ - UPROPERTY(EditAnywhere, Category = "Mesh") - UHoudiniStaticMesh *Mesh; - - /** Local space bounds of mesh. */ - UPROPERTY() - FBox LocalBounds; - - UPROPERTY(EditAnywhere, Category = "Icons") - bool bHoudiniIconVisible; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Components/MeshComponent.h" + +#include "HoudiniStaticMeshComponent.generated.h" + +class UHoudiniStaticMesh; +class UBillboardComponent; + +UCLASS(EditInlineNew, ClassGroup = "Houdini Engine | Rendering") +class HOUDINIENGINERUNTIME_API UHoudiniStaticMeshComponent : public UMeshComponent +{ + GENERATED_BODY() + +public: + UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer); + + UFUNCTION() + void SetMesh(UHoudiniStaticMesh *InMesh); + + UFUNCTION() + UHoudiniStaticMesh* GetMesh() { return Mesh; } + + // Call this if the mesh updated (outside of calling SetMesh). + UFUNCTION() + void NotifyMeshUpdated(); + + virtual void OnRegister() override; + + //virtual void PostLoad() override; + + // UPrimitiveComponent interface + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + virtual int32 GetNumMaterials() const override; + virtual UMaterialInterface* GetMaterial(int32 ElementIndex) const override; + virtual int32 GetMaterialIndex(FName MaterialSlotName) const override; + virtual TArray GetMaterialSlotNames() const override; + virtual bool IsMaterialSlotNameValid(FName MaterialSlotName) const override; + // end - UPrimitiveComponent interface + + // USceneComponent Interface. + virtual FBoxSphereBounds CalcBounds(const FTransform& InLocalToWorld) const override; + // end - USceneComponent Interface. + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + UFUNCTION() + bool IsHoudiniIconVisible() const { return bHoudiniIconVisible; } + + UFUNCTION() + void SetHoudiniIconVisible(bool bInHoudiniIconVisible); + +protected: +#if WITH_EDITORONLY_DATA + virtual void UpdateSpriteComponent(); +#endif + + /** The mesh. */ + UPROPERTY(EditAnywhere, Category = "Mesh") + UHoudiniStaticMesh *Mesh; + + /** Local space bounds of mesh. */ + UPROPERTY() + FBox LocalBounds; + + UPROPERTY(EditAnywhere, Category = "Icons") + bool bHoudiniIconVisible; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp index 775b438a2..f7ff2d6c0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp @@ -1,541 +1,541 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMeshSceneProxy.h" - -#include "Async/ParallelFor.h" -#include "Materials/Material.h" -#include "PrimitiveViewRelevance.h" -#include "Engine/Engine.h" - -#include "ProfilingDebugging/CpuProfilerTrace.h" - -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniStaticMesh.h" - -// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h - -// -// FHoudiniStaticMeshRenderBufferSet -// - -FHoudiniStaticMeshRenderBufferSet::FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type InFeatureLevel) - : LocalVertexFactory(InFeatureLevel, "FHoudiniStaticMeshRenderBufferSet") -{ -} - - -FHoudiniStaticMeshRenderBufferSet::~FHoudiniStaticMeshRenderBufferSet() -{ - check(IsInRenderingThread()); - - if (NumTriangles > 0) - { - PositionVertexBuffer.ReleaseResource(); - ColorVertexBuffer.ReleaseResource(); - StaticMeshVertexBuffer.ReleaseResource(); - LocalVertexFactory.ReleaseResource(); - if (TriangleIndexBuffer.IsInitialized()) - { - TriangleIndexBuffer.ReleaseResource(); - } - } -} - -void FHoudiniStaticMeshRenderBufferSet::CopyBuffers() -{ - check(IsInRenderingThread()); - - if (NumTriangles == 0) - { - return; - } - - InitOrUpdateResource(&PositionVertexBuffer); - InitOrUpdateResource(&ColorVertexBuffer); - InitOrUpdateResource(&StaticMeshVertexBuffer); - - FLocalVertexFactory::FDataType Data; - PositionVertexBuffer.BindPositionVertexBuffer(&LocalVertexFactory, Data); - StaticMeshVertexBuffer.BindTangentVertexBuffer(&LocalVertexFactory, Data); - StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&LocalVertexFactory, Data); - ColorVertexBuffer.BindColorVertexBuffer(&LocalVertexFactory, Data); - - LocalVertexFactory.SetData(Data); - InitOrUpdateResource(&LocalVertexFactory); - - if (TriangleIndexBuffer.Indices.Num() > 0) - { - TriangleIndexBuffer.InitResource(); - } -} - -void FHoudiniStaticMeshRenderBufferSet::InitOrUpdateResource(FRenderResource* Resource) -{ - check(IsInRenderingThread()); - - if (Resource->IsInitialized()) - Resource->UpdateRHI(); - else - Resource->InitResource(); -} - -void FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet) -{ - if (BufferSet->NumTriangles == 0) - { - return; - } - - ENQUEUE_RENDER_COMMAND(FMeshRenderBufferSetDestroy)( - [BufferSet](FRHICommandListImmediate& RHICmdList) - { - delete BufferSet; - }); -} - -// -// End - FHoudiniStaticMeshRenderBufferSet -// - -// -// FHoudiniStaticMeshSceneProxy -// - -FHoudiniStaticMeshSceneProxy::FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel) - : FPrimitiveSceneProxy(InComponent) - , DefaultVertexColor(255, 255, 255) - , FeatureLevel(InFeatureLevel) - , Component(InComponent) - , MaterialRelevance(InComponent ? InComponent->GetMaterialRelevance(InFeatureLevel) : FMaterialRelevance()) -{ -} - -FHoudiniStaticMeshSceneProxy::~FHoudiniStaticMeshSceneProxy() -{ - check(IsInRenderingThread()); - - for (FHoudiniStaticMeshRenderBufferSet* BufferSet : BufferSets) - { - FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); - } -} - -uint32 FHoudiniStaticMeshSceneProxy::AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet) -{ - OutBufferSet = MakeNewBufferSet(); - OutBufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); - - BufferSetsLock.Lock(); - const uint32 NewIndex = BufferSets.Add(OutBufferSet); - BufferSetsLock.Unlock(); - return NewIndex; -} - -void FHoudiniStaticMeshSceneProxy::ReleaseRenderBufferSet(uint32 InIndex) -{ - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - - BufferSetsLock.Lock(); - check(BufferSets.IsValidIndex(InIndex)); - BufferSet = BufferSets[InIndex]; - BufferSets.RemoveAt(InIndex); - BufferSetsLock.Unlock(); - - FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); -} - -void FHoudiniStaticMeshSceneProxy::UpdatedReferencedMaterials() -{ - // copied from FPrimitiveSceneProxy::FPrimitiveSceneProxy() -#if WITH_EDITOR - TArray Materials; - Component->GetUsedMaterials(Materials, true); - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshRenderBufferSetDestroy)( - [this, Materials](FRHICommandListImmediate& RHICmdList) - { - this->SetUsedMaterialForVerification(Materials); - }); -#endif -} - -void FHoudiniStaticMeshSceneProxy::Build() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::Build")); - - // Allocate a buffer set per material - const uint32 NumMaterials = GetNumMaterials(); - if (NumMaterials == 0) - { - // No materials, allocate a singel buffer set using the default material - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - AllocateNewRenderBufferSet(BufferSet); - BufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); - } - else - { - for (uint32 MaterialIdx = 0; MaterialIdx < NumMaterials; ++MaterialIdx) - { - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - AllocateNewRenderBufferSet(BufferSet); - BufferSet->Material = GetMaterial(MaterialIdx); - } - } - - if (Component) - { - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (Mesh) - { - if (NumMaterials > 1 && Mesh->HasPerFaceMaterials()) - { - BuildBufferSetsByMaterial(); - } - else - { - BuildSingleBufferSet(); - } - } - } -} - -void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const -{ - const bool bRenderAsWireframe = (AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe); - - // Set up the wireframe material - FMaterialRenderProxy *WireframeMaterialProxy = nullptr; - if (bRenderAsWireframe) - { - FColoredMaterialRenderProxy *WireframeMaterialInstance = new FColoredMaterialRenderProxy( - GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, - FLinearColor(0.6f, 0.6f, 0.6f) - ); - Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); - WireframeMaterialProxy = WireframeMaterialInstance; - } - - ESceneDepthPriorityGroup DepthPriority = SDPG_World; - - const int32 NumViews = Views.Num(); - for (int32 ViewIdx = 0; ViewIdx < NumViews; ++ViewIdx) - { - if (!(VisibilityMap & (1 << ViewIdx))) - continue; - - const FSceneView *View = Views[ViewIdx]; - - bool bHasPrecomputedVolumetricLightmap; - FMatrix PreviousLocalToWorld; - int32 SingleCaptureIndex; - bool bOutputVelocity; - GetScene().GetPrimitiveUniformShaderParameters_RenderThread( - GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity); - - const uint32 NumBufferSets = BufferSets.Num(); - for (uint32 BufferSetIdx = 0; BufferSetIdx < NumBufferSets; ++BufferSetIdx) - { - FHoudiniStaticMeshRenderBufferSet *BufferSet = BufferSets[BufferSetIdx]; - - UMaterialInterface *Material = BufferSet->Material; - FMaterialRenderProxy *MaterialProxy = Material->GetRenderProxy(); - - if (BufferSet->NumTriangles == 0) - continue; - - FDynamicPrimitiveUniformBuffer &DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); - DynamicPrimitiveUniformBuffer.Set( - GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity); - - if (BufferSet->TriangleIndexBuffer.Indices.Num() > 0) - { - FMeshBatch& Mesh = Collector.AllocateMesh(); - if (PopulateMeshElement(Mesh, *BufferSet, MaterialProxy, false, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) - { - Collector.AddMesh(ViewIdx, Mesh); - } - if (bRenderAsWireframe) - { - FMeshBatch& WireframeMesh = Collector.AllocateMesh(); - if (PopulateMeshElement(WireframeMesh, *BufferSet, WireframeMaterialProxy, true, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) - { - Collector.AddMesh(ViewIdx, WireframeMesh); - } - } - } - } - } -} - -bool FHoudiniStaticMeshSceneProxy::PopulateMeshElement( - FMeshBatch &InMeshBatch, - const FHoudiniStaticMeshRenderBufferSet& Buffers, - FMaterialRenderProxy* Material, - bool bRenderAsWireframe, - ESceneDepthPriorityGroup DepthPriority, - int ViewIndex, - FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const -{ - FMeshBatchElement& BatchElement = InMeshBatch.Elements[0]; - BatchElement.IndexBuffer = &Buffers.TriangleIndexBuffer; - InMeshBatch.bWireframe = bRenderAsWireframe; - InMeshBatch.VertexFactory = &Buffers.LocalVertexFactory; - InMeshBatch.MaterialRenderProxy = Material; - - BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; - - BatchElement.FirstIndex = 0; - BatchElement.NumPrimitives = Buffers.NumTriangles; - BatchElement.MinVertexIndex = 0; - BatchElement.MaxVertexIndex = Buffers.PositionVertexBuffer.GetNumVertices() - 1; - InMeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative(); - InMeshBatch.Type = PT_TriangleList; - InMeshBatch.DepthPriorityGroup = DepthPriority; - InMeshBatch.bCanApplyViewModeOverrides = false; - - return true; -} - - -FPrimitiveViewRelevance FHoudiniStaticMeshSceneProxy::GetViewRelevance(const FSceneView* View) const -{ - FPrimitiveViewRelevance Result; - - Result.bDrawRelevance = IsShown(View); - Result.bDynamicRelevance = true; - Result.bRenderCustomDepth = ShouldRenderCustomDepth(); - Result.bRenderInMainPass = ShouldRenderInMainPass(); - Result.bShadowRelevance = IsShadowCast(View); - Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; - Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); - MaterialRelevance.SetPrimitiveViewRelevance(Result); - Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass; - - return Result; -} - -bool FHoudiniStaticMeshSceneProxy::CanBeOccluded() const -{ - return !MaterialRelevance.bDisableDepthTest; -} - -void FHoudiniStaticMeshSceneProxy::PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs, uint32 InTriangleGroupStartIdx, uint32 InNumTrianglesInGroup) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::PopulateBuffers")); - - check(InMesh); - check(InBuffers); - - const uint32 NumTriangles = InTriangleIDs ? InNumTrianglesInGroup : InMesh->GetNumTriangles(); - InBuffers->NumTriangles = NumTriangles; - - if (NumTriangles == 0) - return; - - const uint32 NumVertices = NumTriangles * 3; - const uint32 NumUVLayers = InMesh->GetNumUVLayers(); - - InBuffers->PositionVertexBuffer.Init(NumVertices); - // There must be at least one UV layer - // TODO: Would it be possible to have no UV layers and bind to a dummy 0/black SRV? - InBuffers->StaticMeshVertexBuffer.Init(NumVertices, NumUVLayers > 0 ? NumUVLayers : 1); - InBuffers->ColorVertexBuffer.Init(NumVertices); - InBuffers->TriangleIndexBuffer.Indices.AddUninitialized(NumTriangles * 3); - - const TArray& VertexPositions = InMesh->GetVertexPositions(); - const TArray& TriangleIndices = InMesh->GetTriangleIndices(); - const TArray& VertexInstanceColors = InMesh->GetVertexInstanceColors(); - const TArray& VertexInstanceNormals = InMesh->GetVertexInstanceNormals(); - const TArray& VertexInstanceUTangents = InMesh->GetVertexInstanceUTangents(); - const TArray& VertexInstanceVTangents = InMesh->GetVertexInstanceVTangents(); - const TArray& VertexInstanceUVs = InMesh->GetVertexInstanceUVs(); - - const bool bHasColors = InMesh->HasColors(); - const bool bHasNormals = InMesh->HasNormals(); - const bool bHasTangents = InMesh->HasTangents(); - - FThreadSafeCounter VertCounter(0); - //for (uint32 TriangleIDIdx = 0; TriangleIDIdx < NumTriangles; ++TriangleIDIdx) - ParallelFor(NumTriangles, [&](uint32 TriangleIDIdx) - { - const uint32 TriangleID = InTriangleIDs ? (*InTriangleIDs)[InTriangleGroupStartIdx + TriangleIDIdx] : TriangleIDIdx; - const FIntVector &TriIndices = TriangleIndices[TriangleID]; - - FVector TangentU; - FVector TangentV; - uint32 VertIdx = VertCounter.Add(3); - for (uint8 TriVertIdx = 0; TriVertIdx < 3; ++TriVertIdx) - { - const uint32 MeshVtxIdx = TriIndices[TriVertIdx]; - const uint32 MeshVtxInstanceIdx = TriangleID * 3 + TriVertIdx; - - InBuffers->PositionVertexBuffer.VertexPosition(VertIdx) = VertexPositions[TriIndices[TriVertIdx]]; - - FVector Normal = bHasNormals ? VertexInstanceNormals[MeshVtxInstanceIdx] : FVector(0, 0, 1); - if (bHasTangents) - { - TangentU = VertexInstanceUTangents[MeshVtxInstanceIdx]; - TangentV = VertexInstanceVTangents[MeshVtxInstanceIdx]; - } - else - { - Normal.FindBestAxisVectors(TangentU, TangentV); - } - InBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, TangentU, TangentV, Normal); - - if (NumUVLayers > 0) - { - for (uint8 UVLayerIdx = 0; UVLayerIdx < NumUVLayers; ++UVLayerIdx) - { - InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, UVLayerIdx, VertexInstanceUVs[MeshVtxInstanceIdx]); - } - } - else - { - InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, 0, FVector2D::ZeroVector); - } - - InBuffers->ColorVertexBuffer.VertexColor(VertIdx) = bHasColors ? VertexInstanceColors[MeshVtxInstanceIdx] : DefaultVertexColor; - - InBuffers->TriangleIndexBuffer.Indices[VertIdx] = VertIdx; - VertIdx++; - } - }); -} - -void FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet")); - - if (!Component) - return; - - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (!Mesh) - return; - - if (BufferSets.Num() == 0) - return; - - FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets.Last(); - - PopulateBuffers(Mesh, Buffers); - - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( - [Buffers](FRHICommandListImmediate& RHICMdList) - { - Buffers->CopyBuffers(); - }); -} - -void FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial")); - - // We need to group tris by which material they use, and populate a buffer set for each group - if (!Component) - return; - - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (!Mesh) - return; - - if (BufferSets.Num() == 0) - return; - - const TArray& MaterialIDsPerTriangle = Mesh->GetMaterialIDsPerTriangle(); - - const uint32 NumTriangles = MaterialIDsPerTriangle.Num(); - const uint32 NumMaterials = GetNumMaterials(); - TArray TriCountPerMaterialSafe; - TriCountPerMaterialSafe.Init(FThreadSafeCounter(0), NumMaterials); - ParallelFor(NumTriangles, [&](uint32 TriangleID) - { - const int32 MatID = MaterialIDsPerTriangle[TriangleID]; - if (MatID >= 0 && (uint32) MatID < NumMaterials) - { - TriCountPerMaterialSafe[MatID].Increment(); - } - }); - - TArray TriCountPerMaterial; - TArray OffsetPerMaterial; - TArray WrittenPerMaterial; - TriCountPerMaterial.Init(0, NumMaterials); - OffsetPerMaterial.Init(0, NumMaterials); - WrittenPerMaterial.Init(FThreadSafeCounter(0), NumMaterials); - for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) - { - const uint32 Count = TriCountPerMaterialSafe[MatID].GetValue(); - TriCountPerMaterial[MatID] = Count; - if (MatID > 0) - { - OffsetPerMaterial[MatID] = OffsetPerMaterial[MatID - 1] + TriCountPerMaterial[MatID - 1]; - } - } - - TArray GroupTriangleIDs; - GroupTriangleIDs.Init(0, NumTriangles); - ParallelFor(NumTriangles, [&](uint32 TriangleID) - { - const int32 MatID = MaterialIDsPerTriangle[TriangleID]; - if (MatID >= 0 && (uint32) MatID < NumMaterials) - { - GroupTriangleIDs[OffsetPerMaterial[MatID] + WrittenPerMaterial[MatID].Add(1)] = TriangleID; - } - }); - - for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) - { - if (TriCountPerMaterial[MatID] == 0) - continue; - - FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets[MatID]; - - PopulateBuffers( - Mesh, Buffers, - &GroupTriangleIDs, OffsetPerMaterial[MatID], TriCountPerMaterial[MatID] - ); - - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( - [Buffers](FRHICommandListImmediate& RHICMdList) - { - Buffers->CopyBuffers(); - }); - } -} - -UMaterialInterface* FHoudiniStaticMeshSceneProxy::GetMaterial(uint32 InMaterialIdx) const -{ - if (!Component) - return UMaterial::GetDefaultMaterial(MD_Surface); - UMaterialInterface *Material = Component->GetMaterial(InMaterialIdx); - return Material ? Material : UMaterial::GetDefaultMaterial(MD_Surface); -} - -// -// End - FHoudiniStaticMeshSceneProxy -// +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMeshSceneProxy.h" + +#include "Async/ParallelFor.h" +#include "Materials/Material.h" +#include "PrimitiveViewRelevance.h" +#include "Engine/Engine.h" + +#include "ProfilingDebugging/CpuProfilerTrace.h" + +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniStaticMesh.h" + +// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h + +// +// FHoudiniStaticMeshRenderBufferSet +// + +FHoudiniStaticMeshRenderBufferSet::FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type InFeatureLevel) + : LocalVertexFactory(InFeatureLevel, "FHoudiniStaticMeshRenderBufferSet") +{ +} + + +FHoudiniStaticMeshRenderBufferSet::~FHoudiniStaticMeshRenderBufferSet() +{ + check(IsInRenderingThread()); + + if (NumTriangles > 0) + { + PositionVertexBuffer.ReleaseResource(); + ColorVertexBuffer.ReleaseResource(); + StaticMeshVertexBuffer.ReleaseResource(); + LocalVertexFactory.ReleaseResource(); + if (TriangleIndexBuffer.IsInitialized()) + { + TriangleIndexBuffer.ReleaseResource(); + } + } +} + +void FHoudiniStaticMeshRenderBufferSet::CopyBuffers() +{ + check(IsInRenderingThread()); + + if (NumTriangles == 0) + { + return; + } + + InitOrUpdateResource(&PositionVertexBuffer); + InitOrUpdateResource(&ColorVertexBuffer); + InitOrUpdateResource(&StaticMeshVertexBuffer); + + FLocalVertexFactory::FDataType Data; + PositionVertexBuffer.BindPositionVertexBuffer(&LocalVertexFactory, Data); + StaticMeshVertexBuffer.BindTangentVertexBuffer(&LocalVertexFactory, Data); + StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&LocalVertexFactory, Data); + ColorVertexBuffer.BindColorVertexBuffer(&LocalVertexFactory, Data); + + LocalVertexFactory.SetData(Data); + InitOrUpdateResource(&LocalVertexFactory); + + if (TriangleIndexBuffer.Indices.Num() > 0) + { + TriangleIndexBuffer.InitResource(); + } +} + +void FHoudiniStaticMeshRenderBufferSet::InitOrUpdateResource(FRenderResource* Resource) +{ + check(IsInRenderingThread()); + + if (Resource->IsInitialized()) + Resource->UpdateRHI(); + else + Resource->InitResource(); +} + +void FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet) +{ + if (BufferSet->NumTriangles == 0) + { + return; + } + + ENQUEUE_RENDER_COMMAND(FMeshRenderBufferSetDestroy)( + [BufferSet](FRHICommandListImmediate& RHICmdList) + { + delete BufferSet; + }); +} + +// +// End - FHoudiniStaticMeshRenderBufferSet +// + +// +// FHoudiniStaticMeshSceneProxy +// + +FHoudiniStaticMeshSceneProxy::FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel) + : FPrimitiveSceneProxy(InComponent) + , DefaultVertexColor(255, 255, 255) + , FeatureLevel(InFeatureLevel) + , Component(InComponent) + , MaterialRelevance(InComponent ? InComponent->GetMaterialRelevance(InFeatureLevel) : FMaterialRelevance()) +{ +} + +FHoudiniStaticMeshSceneProxy::~FHoudiniStaticMeshSceneProxy() +{ + check(IsInRenderingThread()); + + for (FHoudiniStaticMeshRenderBufferSet* BufferSet : BufferSets) + { + FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); + } +} + +uint32 FHoudiniStaticMeshSceneProxy::AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet) +{ + OutBufferSet = MakeNewBufferSet(); + OutBufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); + + BufferSetsLock.Lock(); + const uint32 NewIndex = BufferSets.Add(OutBufferSet); + BufferSetsLock.Unlock(); + return NewIndex; +} + +void FHoudiniStaticMeshSceneProxy::ReleaseRenderBufferSet(uint32 InIndex) +{ + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + + BufferSetsLock.Lock(); + check(BufferSets.IsValidIndex(InIndex)); + BufferSet = BufferSets[InIndex]; + BufferSets.RemoveAt(InIndex); + BufferSetsLock.Unlock(); + + FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); +} + +void FHoudiniStaticMeshSceneProxy::UpdatedReferencedMaterials() +{ + // copied from FPrimitiveSceneProxy::FPrimitiveSceneProxy() +#if WITH_EDITOR + TArray Materials; + Component->GetUsedMaterials(Materials, true); + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshRenderBufferSetDestroy)( + [this, Materials](FRHICommandListImmediate& RHICmdList) + { + this->SetUsedMaterialForVerification(Materials); + }); +#endif +} + +void FHoudiniStaticMeshSceneProxy::Build() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::Build")); + + // Allocate a buffer set per material + const uint32 NumMaterials = GetNumMaterials(); + if (NumMaterials == 0) + { + // No materials, allocate a singel buffer set using the default material + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + AllocateNewRenderBufferSet(BufferSet); + BufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); + } + else + { + for (uint32 MaterialIdx = 0; MaterialIdx < NumMaterials; ++MaterialIdx) + { + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + AllocateNewRenderBufferSet(BufferSet); + BufferSet->Material = GetMaterial(MaterialIdx); + } + } + + if (Component) + { + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (Mesh) + { + if (NumMaterials > 1 && Mesh->HasPerFaceMaterials()) + { + BuildBufferSetsByMaterial(); + } + else + { + BuildSingleBufferSet(); + } + } + } +} + +void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const +{ + const bool bRenderAsWireframe = (AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe); + + // Set up the wireframe material + FMaterialRenderProxy *WireframeMaterialProxy = nullptr; + if (bRenderAsWireframe) + { + FColoredMaterialRenderProxy *WireframeMaterialInstance = new FColoredMaterialRenderProxy( + GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, + FLinearColor(0.6f, 0.6f, 0.6f) + ); + Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); + WireframeMaterialProxy = WireframeMaterialInstance; + } + + ESceneDepthPriorityGroup DepthPriority = SDPG_World; + + const int32 NumViews = Views.Num(); + for (int32 ViewIdx = 0; ViewIdx < NumViews; ++ViewIdx) + { + if (!(VisibilityMap & (1 << ViewIdx))) + continue; + + const FSceneView *View = Views[ViewIdx]; + + bool bHasPrecomputedVolumetricLightmap; + FMatrix PreviousLocalToWorld; + int32 SingleCaptureIndex; + bool bOutputVelocity; + GetScene().GetPrimitiveUniformShaderParameters_RenderThread( + GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity); + + const uint32 NumBufferSets = BufferSets.Num(); + for (uint32 BufferSetIdx = 0; BufferSetIdx < NumBufferSets; ++BufferSetIdx) + { + FHoudiniStaticMeshRenderBufferSet *BufferSet = BufferSets[BufferSetIdx]; + + UMaterialInterface *Material = BufferSet->Material; + FMaterialRenderProxy *MaterialProxy = Material->GetRenderProxy(); + + if (BufferSet->NumTriangles == 0) + continue; + + FDynamicPrimitiveUniformBuffer &DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); + DynamicPrimitiveUniformBuffer.Set( + GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity); + + if (BufferSet->TriangleIndexBuffer.Indices.Num() > 0) + { + FMeshBatch& Mesh = Collector.AllocateMesh(); + if (PopulateMeshElement(Mesh, *BufferSet, MaterialProxy, false, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) + { + Collector.AddMesh(ViewIdx, Mesh); + } + if (bRenderAsWireframe) + { + FMeshBatch& WireframeMesh = Collector.AllocateMesh(); + if (PopulateMeshElement(WireframeMesh, *BufferSet, WireframeMaterialProxy, true, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) + { + Collector.AddMesh(ViewIdx, WireframeMesh); + } + } + } + } + } +} + +bool FHoudiniStaticMeshSceneProxy::PopulateMeshElement( + FMeshBatch &InMeshBatch, + const FHoudiniStaticMeshRenderBufferSet& Buffers, + FMaterialRenderProxy* Material, + bool bRenderAsWireframe, + ESceneDepthPriorityGroup DepthPriority, + int ViewIndex, + FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const +{ + FMeshBatchElement& BatchElement = InMeshBatch.Elements[0]; + BatchElement.IndexBuffer = &Buffers.TriangleIndexBuffer; + InMeshBatch.bWireframe = bRenderAsWireframe; + InMeshBatch.VertexFactory = &Buffers.LocalVertexFactory; + InMeshBatch.MaterialRenderProxy = Material; + + BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; + + BatchElement.FirstIndex = 0; + BatchElement.NumPrimitives = Buffers.NumTriangles; + BatchElement.MinVertexIndex = 0; + BatchElement.MaxVertexIndex = Buffers.PositionVertexBuffer.GetNumVertices() - 1; + InMeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative(); + InMeshBatch.Type = PT_TriangleList; + InMeshBatch.DepthPriorityGroup = DepthPriority; + InMeshBatch.bCanApplyViewModeOverrides = false; + + return true; +} + + +FPrimitiveViewRelevance FHoudiniStaticMeshSceneProxy::GetViewRelevance(const FSceneView* View) const +{ + FPrimitiveViewRelevance Result; + + Result.bDrawRelevance = IsShown(View); + Result.bDynamicRelevance = true; + Result.bRenderCustomDepth = ShouldRenderCustomDepth(); + Result.bRenderInMainPass = ShouldRenderInMainPass(); + Result.bShadowRelevance = IsShadowCast(View); + Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; + Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); + MaterialRelevance.SetPrimitiveViewRelevance(Result); + Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass; + + return Result; +} + +bool FHoudiniStaticMeshSceneProxy::CanBeOccluded() const +{ + return !MaterialRelevance.bDisableDepthTest; +} + +void FHoudiniStaticMeshSceneProxy::PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs, uint32 InTriangleGroupStartIdx, uint32 InNumTrianglesInGroup) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::PopulateBuffers")); + + check(InMesh); + check(InBuffers); + + const uint32 NumTriangles = InTriangleIDs ? InNumTrianglesInGroup : InMesh->GetNumTriangles(); + InBuffers->NumTriangles = NumTriangles; + + if (NumTriangles == 0) + return; + + const uint32 NumVertices = NumTriangles * 3; + const uint32 NumUVLayers = InMesh->GetNumUVLayers(); + + InBuffers->PositionVertexBuffer.Init(NumVertices); + // There must be at least one UV layer + // TODO: Would it be possible to have no UV layers and bind to a dummy 0/black SRV? + InBuffers->StaticMeshVertexBuffer.Init(NumVertices, NumUVLayers > 0 ? NumUVLayers : 1); + InBuffers->ColorVertexBuffer.Init(NumVertices); + InBuffers->TriangleIndexBuffer.Indices.AddUninitialized(NumTriangles * 3); + + const TArray& VertexPositions = InMesh->GetVertexPositions(); + const TArray& TriangleIndices = InMesh->GetTriangleIndices(); + const TArray& VertexInstanceColors = InMesh->GetVertexInstanceColors(); + const TArray& VertexInstanceNormals = InMesh->GetVertexInstanceNormals(); + const TArray& VertexInstanceUTangents = InMesh->GetVertexInstanceUTangents(); + const TArray& VertexInstanceVTangents = InMesh->GetVertexInstanceVTangents(); + const TArray& VertexInstanceUVs = InMesh->GetVertexInstanceUVs(); + + const bool bHasColors = InMesh->HasColors(); + const bool bHasNormals = InMesh->HasNormals(); + const bool bHasTangents = InMesh->HasTangents(); + + FThreadSafeCounter VertCounter(0); + //for (uint32 TriangleIDIdx = 0; TriangleIDIdx < NumTriangles; ++TriangleIDIdx) + ParallelFor(NumTriangles, [&](uint32 TriangleIDIdx) + { + const uint32 TriangleID = InTriangleIDs ? (*InTriangleIDs)[InTriangleGroupStartIdx + TriangleIDIdx] : TriangleIDIdx; + const FIntVector &TriIndices = TriangleIndices[TriangleID]; + + FVector TangentU; + FVector TangentV; + uint32 VertIdx = VertCounter.Add(3); + for (uint8 TriVertIdx = 0; TriVertIdx < 3; ++TriVertIdx) + { + const uint32 MeshVtxIdx = TriIndices[TriVertIdx]; + const uint32 MeshVtxInstanceIdx = TriangleID * 3 + TriVertIdx; + + InBuffers->PositionVertexBuffer.VertexPosition(VertIdx) = VertexPositions[TriIndices[TriVertIdx]]; + + FVector Normal = bHasNormals ? VertexInstanceNormals[MeshVtxInstanceIdx] : FVector(0, 0, 1); + if (bHasTangents) + { + TangentU = VertexInstanceUTangents[MeshVtxInstanceIdx]; + TangentV = VertexInstanceVTangents[MeshVtxInstanceIdx]; + } + else + { + Normal.FindBestAxisVectors(TangentU, TangentV); + } + InBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, TangentU, TangentV, Normal); + + if (NumUVLayers > 0) + { + for (uint8 UVLayerIdx = 0; UVLayerIdx < NumUVLayers; ++UVLayerIdx) + { + InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, UVLayerIdx, VertexInstanceUVs[MeshVtxInstanceIdx]); + } + } + else + { + InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, 0, FVector2D::ZeroVector); + } + + InBuffers->ColorVertexBuffer.VertexColor(VertIdx) = bHasColors ? VertexInstanceColors[MeshVtxInstanceIdx] : DefaultVertexColor; + + InBuffers->TriangleIndexBuffer.Indices[VertIdx] = VertIdx; + VertIdx++; + } + }); +} + +void FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet")); + + if (!Component) + return; + + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (!Mesh) + return; + + if (BufferSets.Num() == 0) + return; + + FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets.Last(); + + PopulateBuffers(Mesh, Buffers); + + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( + [Buffers](FRHICommandListImmediate& RHICMdList) + { + Buffers->CopyBuffers(); + }); +} + +void FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial")); + + // We need to group tris by which material they use, and populate a buffer set for each group + if (!Component) + return; + + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (!Mesh) + return; + + if (BufferSets.Num() == 0) + return; + + const TArray& MaterialIDsPerTriangle = Mesh->GetMaterialIDsPerTriangle(); + + const uint32 NumTriangles = MaterialIDsPerTriangle.Num(); + const uint32 NumMaterials = GetNumMaterials(); + TArray TriCountPerMaterialSafe; + TriCountPerMaterialSafe.Init(FThreadSafeCounter(0), NumMaterials); + ParallelFor(NumTriangles, [&](uint32 TriangleID) + { + const int32 MatID = MaterialIDsPerTriangle[TriangleID]; + if (MatID >= 0 && (uint32) MatID < NumMaterials) + { + TriCountPerMaterialSafe[MatID].Increment(); + } + }); + + TArray TriCountPerMaterial; + TArray OffsetPerMaterial; + TArray WrittenPerMaterial; + TriCountPerMaterial.Init(0, NumMaterials); + OffsetPerMaterial.Init(0, NumMaterials); + WrittenPerMaterial.Init(FThreadSafeCounter(0), NumMaterials); + for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) + { + const uint32 Count = TriCountPerMaterialSafe[MatID].GetValue(); + TriCountPerMaterial[MatID] = Count; + if (MatID > 0) + { + OffsetPerMaterial[MatID] = OffsetPerMaterial[MatID - 1] + TriCountPerMaterial[MatID - 1]; + } + } + + TArray GroupTriangleIDs; + GroupTriangleIDs.Init(0, NumTriangles); + ParallelFor(NumTriangles, [&](uint32 TriangleID) + { + const int32 MatID = MaterialIDsPerTriangle[TriangleID]; + if (MatID >= 0 && (uint32) MatID < NumMaterials) + { + GroupTriangleIDs[OffsetPerMaterial[MatID] + WrittenPerMaterial[MatID].Add(1)] = TriangleID; + } + }); + + for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) + { + if (TriCountPerMaterial[MatID] == 0) + continue; + + FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets[MatID]; + + PopulateBuffers( + Mesh, Buffers, + &GroupTriangleIDs, OffsetPerMaterial[MatID], TriCountPerMaterial[MatID] + ); + + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( + [Buffers](FRHICommandListImmediate& RHICMdList) + { + Buffers->CopyBuffers(); + }); + } +} + +UMaterialInterface* FHoudiniStaticMeshSceneProxy::GetMaterial(uint32 InMaterialIdx) const +{ + if (!Component) + return UMaterial::GetDefaultMaterial(MD_Surface); + UMaterialInterface *Material = Component->GetMaterial(InMaterialIdx); + return Material ? Material : UMaterial::GetDefaultMaterial(MD_Surface); +} + +// +// End - FHoudiniStaticMeshSceneProxy +// diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h index 8de764e79..43f60cb9d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h @@ -1,168 +1,168 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h - -#pragma once - -#include "CoreMinimal.h" -#include "PrimitiveSceneProxy.h" -#include "VertexFactory.h" -#include "LocalVertexFactory.h" -#include "Rendering/ColorVertexBuffer.h" -#include "Rendering/PositionVertexBuffer.h" -#include "Rendering/StaticMeshVertexBuffer.h" -#include "DynamicMeshBuilder.h" - -#include "HoudiniStaticMeshComponent.h" - -class UHoudiniStaticMesh; - -class FHoudiniStaticMeshRenderBufferSet -{ -public: - // Data members - - /** The number of triangles in the buffer set. */ - int NumTriangles; - - /** The static mesh data buffer. */ - FStaticMeshVertexBuffer StaticMeshVertexBuffer; - - /** The position buffer. */ - FPositionVertexBuffer PositionVertexBuffer; - - /** The triangle indices buffer. */ - FDynamicMeshIndexBuffer32 TriangleIndexBuffer; - - /** The color buffer */ - FColorVertexBuffer ColorVertexBuffer; - - FLocalVertexFactory LocalVertexFactory; - - /** Default material for this mesh. */ - UMaterialInterface* Material = nullptr; - - // Functions - - FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type FeatureLevelType); - - virtual ~FHoudiniStaticMeshRenderBufferSet(); - - /** - * Copy buffers to GPU. - * @warning render thread only. - */ - virtual void CopyBuffers(); - - /** - * Initialize (or update) a render resource. - * @warning Render thread only. - */ - void InitOrUpdateResource(FRenderResource* Resource); - -protected: - friend class FHoudiniStaticMeshSceneProxy; - - // Queue a command on the render thread to destroy the given buffer set - static void DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet); -}; - - -class FHoudiniStaticMeshSceneProxy : public FPrimitiveSceneProxy -{ -public: - FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel); - - virtual ~FHoudiniStaticMeshSceneProxy(); - - uint32 AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet); - - void ReleaseRenderBufferSet(uint32 InIndex); - - void UpdatedReferencedMaterials(); - - // Build buffer sets to render the mesh. - virtual void Build(); - - // FPrimitiveSceneProxy - virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; - - virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; - - virtual bool CanBeOccluded() const override; - - virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + FPrimitiveSceneProxy::GetAllocatedSize()); } - - SIZE_T GetTypeHash() const override - { - static size_t UniquePointer; - return reinterpret_cast(&UniquePointer); - } - - // end - FPrimitiveSceneProxy - - // Color to use if vertex does not have an assigned color. - FColor DefaultVertexColor; - - ERHIFeatureLevel::Type FeatureLevel; - -protected: - void PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs=nullptr, uint32 InTriangleGroupStartIdx=0u, uint32 InNumTrianglesInGroup=0u); - - // Virtual function for creating a new buffer set instances. - // Subclasses can overwrite this is they use a different buffer set with - // different instantiation requirements. - virtual FHoudiniStaticMeshRenderBufferSet* MakeNewBufferSet() { return new FHoudiniStaticMeshRenderBufferSet(FeatureLevel); } - - // Build a single buffer set for the entire mesh (one material for the entire mesh). - void BuildSingleBufferSet(); - - void BuildBufferSetsByMaterial(); - - // Get the number of materials from the parent mesh/component - uint32 GetNumMaterials() const { return Component ? Component->GetNumMaterials() : 0; } - - virtual bool PopulateMeshElement( - FMeshBatch &InMeshBatch, - const FHoudiniStaticMeshRenderBufferSet& Buffers, - FMaterialRenderProxy* Material, - bool bRenderAsWireframe, - ESceneDepthPriorityGroup DepthPriority, - int ViewIndex, - FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const; - - virtual UMaterialInterface* GetMaterial(uint32 InMaterialIdx) const; - - UHoudiniStaticMeshComponent *Component; - - TArray BufferSets; - - FCriticalSection BufferSetsLock; - - FMaterialRelevance MaterialRelevance; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h + +#pragma once + +#include "CoreMinimal.h" +#include "PrimitiveSceneProxy.h" +#include "VertexFactory.h" +#include "LocalVertexFactory.h" +#include "Rendering/ColorVertexBuffer.h" +#include "Rendering/PositionVertexBuffer.h" +#include "Rendering/StaticMeshVertexBuffer.h" +#include "DynamicMeshBuilder.h" + +#include "HoudiniStaticMeshComponent.h" + +class UHoudiniStaticMesh; + +class FHoudiniStaticMeshRenderBufferSet +{ +public: + // Data members + + /** The number of triangles in the buffer set. */ + int NumTriangles; + + /** The static mesh data buffer. */ + FStaticMeshVertexBuffer StaticMeshVertexBuffer; + + /** The position buffer. */ + FPositionVertexBuffer PositionVertexBuffer; + + /** The triangle indices buffer. */ + FDynamicMeshIndexBuffer32 TriangleIndexBuffer; + + /** The color buffer */ + FColorVertexBuffer ColorVertexBuffer; + + FLocalVertexFactory LocalVertexFactory; + + /** Default material for this mesh. */ + UMaterialInterface* Material = nullptr; + + // Functions + + FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type FeatureLevelType); + + virtual ~FHoudiniStaticMeshRenderBufferSet(); + + /** + * Copy buffers to GPU. + * @warning render thread only. + */ + virtual void CopyBuffers(); + + /** + * Initialize (or update) a render resource. + * @warning Render thread only. + */ + void InitOrUpdateResource(FRenderResource* Resource); + +protected: + friend class FHoudiniStaticMeshSceneProxy; + + // Queue a command on the render thread to destroy the given buffer set + static void DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet); +}; + + +class FHoudiniStaticMeshSceneProxy : public FPrimitiveSceneProxy +{ +public: + FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel); + + virtual ~FHoudiniStaticMeshSceneProxy(); + + uint32 AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet); + + void ReleaseRenderBufferSet(uint32 InIndex); + + void UpdatedReferencedMaterials(); + + // Build buffer sets to render the mesh. + virtual void Build(); + + // FPrimitiveSceneProxy + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; + + virtual bool CanBeOccluded() const override; + + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + FPrimitiveSceneProxy::GetAllocatedSize()); } + + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast(&UniquePointer); + } + + // end - FPrimitiveSceneProxy + + // Color to use if vertex does not have an assigned color. + FColor DefaultVertexColor; + + ERHIFeatureLevel::Type FeatureLevel; + +protected: + void PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs=nullptr, uint32 InTriangleGroupStartIdx=0u, uint32 InNumTrianglesInGroup=0u); + + // Virtual function for creating a new buffer set instances. + // Subclasses can overwrite this is they use a different buffer set with + // different instantiation requirements. + virtual FHoudiniStaticMeshRenderBufferSet* MakeNewBufferSet() { return new FHoudiniStaticMeshRenderBufferSet(FeatureLevel); } + + // Build a single buffer set for the entire mesh (one material for the entire mesh). + void BuildSingleBufferSet(); + + void BuildBufferSetsByMaterial(); + + // Get the number of materials from the parent mesh/component + uint32 GetNumMaterials() const { return Component ? Component->GetNumMaterials() : 0; } + + virtual bool PopulateMeshElement( + FMeshBatch &InMeshBatch, + const FHoudiniStaticMeshRenderBufferSet& Buffers, + FMaterialRenderProxy* Material, + bool bRenderAsWireframe, + ESceneDepthPriorityGroup DepthPriority, + int ViewIndex, + FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const; + + virtual UMaterialInterface* GetMaterial(uint32 InMaterialIdx) const; + + UHoudiniStaticMeshComponent *Component; + + TArray BufferSets; + + FCriticalSection BufferSetsLock; + + FMaterialRelevance MaterialRelevance; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp new file mode 100644 index 000000000..9a44588b7 --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp @@ -0,0 +1,38 @@ +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "IHoudiniAssetStateEvents.h" + +void +IHoudiniAssetStateEvents::HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) +{ + if (InFromState == InToState) + return; + + FOnHoudiniAssetStateChange& StateChangeDelegate = GetOnHoudiniAssetStateChangeDelegate(); + if (StateChangeDelegate.IsBound()) + StateChangeDelegate.Broadcast(InHoudiniAssetContext, InFromState, InToState); +} diff --git a/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h new file mode 100644 index 000000000..253006b8b --- /dev/null +++ b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h @@ -0,0 +1,57 @@ +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "HoudiniAssetStateTypes.h" + +#include "IHoudiniAssetStateEvents.generated.h" + +// Delegate for when EHoudiniAssetState changes from InFromState to InToState on an instantiated Houdini Asset (InHoudiniAssetContext). +DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnHoudiniAssetStateChange, UObject*, const EHoudiniAssetState, const EHoudiniAssetState); + +UINTERFACE() +class HOUDINIENGINERUNTIME_API UHoudiniAssetStateEvents : public UInterface +{ + GENERATED_BODY() +}; + + +/** + * EHoudiniAssetState events: event handlers for when a Houdini Asset changes state. + */ +class HOUDINIENGINERUNTIME_API IHoudiniAssetStateEvents +{ + GENERATED_BODY() + +public: + virtual void HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState); + + virtual FOnHoudiniAssetStateChange& GetOnHoudiniAssetStateChangeDelegate() = 0; +}; From 35bb42d4631b04f8810c1a7d301de0990a4c61af Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Mon, 26 Jul 2021 10:32:17 -0400 Subject: [PATCH 11/16] Unreal: Version 2 - Updated license file --- LICENSE.md | 109 +++++++++++------------------------------------------ 1 file changed, 21 insertions(+), 88 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 1fffeda34..21630f364 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,93 +1,26 @@ - ALPHA AND BETA SOFTWARE - CONFIDENTIAL DISCLOSURE AGREEMENT -Revised 10/2011 -This Agreement is made today between Side Effects Software Inc., a corporation -incorporated under the laws of Ontario, Canada and having a place of business -at 123 Front Street West, Suite 1401, Toronto ("Side Effects Software") and -you ("Beta Tester"). -BACKGROUND: + Copyright (c) 2021 + Side Effects Software Inc. All rights reserved. -1. Side Effects Software is in the business of developing and marketing certain - computer graphics software and related materials. -2. Beta Tester, in order to permit Side Effects Software in refining and - perfecting such software and materials, has expressed an interest in testing - certain alpha/beta versions of software more fully described in Schedule A - (the "Software & Materials"). -3. Each Animator, as an employee, contractor or agent of the Beta Tester, may - have access to the Software & Materials and perhaps to other confidential - information of Side Effects Software such as trade secrets, business or - product plans, which might be disclosed during the course of the software - testing (the "Confidential Information"). -4. Side Effects Software wishes to ensure that the Software & Materials are not - used by Beta Tester for purposes other than alpha/beta testing and that they - are not disclosed to any other party without the prior written consent of - Side Effects Software; + Redistribution and use of in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: -NOW THEREFORE, in consideration of this background and the provision of -such materials to Beta Tester and other good and valuable consideration (the -receipt and sufficiency of which are hereby acknowledged), Beta Tester agrees -with Side Effects Software as follows: + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. -1. Side Effects Software hereby grants to Beta Tester on the terms set out - herein a personal, non-transferable and non-exclusive license to use the - object code version of the Software & Materials for its internal operations - on its computers. Any commercial exploitation of the Software is at the - Beta Tester's risk. Beta Tester's right to use the Software & Materials is - limited to those rights expressly set out in this Agreement. Beta Tester - shall carry out testing of the Software & Materials in accordance with such - reasonable instructions as Side Effects Software may provide to it from time - to time. -2. Beta Tester shall use all reasonable efforts (which shall consist of at - least the same level of diligence as it uses to protect its own proprietary - information and trade secrets) to protect the confidentiality of all - Software & Materials, including all product features, and other Confidential - Information of Side Effects Software that may come to the attention of or - knowledge of Beta Tester as a result of undertaking such testing. Beta - Tester shall not discuss product features or show the Software & Materials - to anyone. Beta Tester shall not copy, publish, disclose, attempt to - recreate the source code version of the Software or make any use other than - as contemplated herein of any of the Software & Material or any such - Confidential Information. For the purposes hereof, Confidential Information - shall not include any information that: - - At the time of such disclosure, is generally available to the public - through no fault of Beta Tester; - - Was in possession of Beta Tester without any obligation of confidentiality - prior to the date hereof and was not acquired directly or indirectly from - Side Effects Software; or - - Was received by Beta Tester after the date hereof from a third party who - imposed no obligation of confidentiality and who did not acquire any such - information directly or indirectly from Side Effects Software. -3. Beta Tester shall not communicate or otherwise disclose to Side Effects - Software during the term of this Agreement any confidential or proprietary - information of any other third party. -4. In accepting this Agreement the Beta Tester agrees to test and evaluate the - Software & Materials and to report all problems, concerns, deficiencies and - suggestions for improvements to Side Effects Software. A representative from - Side Effects Software may be contacting Beta Tester weekly for a report. -5. Upon completion of such testing or at any time on the request of Side - Effects Software, Beta Tester shall promptly return to Side Effects Software - all copies of the Software & Materials, as well as any Confidential - Information, then in its possession or control and shall, if requested, - provide Side Effects Software with a certificate signed by an authorized - representative of Beta Tester to such effect from an officer of Beta Tester. -6. All Software & Materials, as well as any Confidential Information, is - provided "as is". Side Effects Software makes no representation, warranty or - guarantee with respect to any such material and assumes no liability for the - use and performance of any alpha and beta software. Side Effects Software - reserves the right to alter all aspects of the Software and Documentation - from one alpha or beta version to the next, including the user interface, - screen displays, fonts and functionality. -7. The Software will timeout and cease to function one month after its build - date, regardless of when it was downloaded or installed. + 2. The names Side Effects Software and SideFX may not be used to endorse or + promote products derived from this software without specific prior + written permission. -SCHEDULE A -SOFTWARE AND MATERIALS -The following Software and related Materials are bound by the attached Alpha -and Beta Software -Test Agreement: -Software: Houdini Engine for Unreal -Version: Version 2.0 - alpha - -You must accept these terms and conditions to install the Software and -Materials. + THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + \ No newline at end of file From 6ae51a83233762a7db1ebc6c3c8e4617728c9bab Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Wed, 4 Aug 2021 14:29:20 -0400 Subject: [PATCH 12/16] Houdini Engine for Unreal - Version 2.0.3 Update 3 of the V2 Plugin. The plug-in is now linked to Houdini 18.5.633 / HAPI 3.6.3. New features: Added a new plugin setting to select the executable (Houdini/FX/Core/Indie) to be used when opening Session Sync or debug hip files. Bug fixes: Fixed a bug where the Dirtying of a node, or all outputs, on the PDG asset link could delete non-temp assets used as output objects (ie, when using "unreal_instance"). The PDG Asset Link now only cleans Houdini generated objects (temp, not baked). Fixed Color Parameter not opening Color Picker. The Houdini proxy meshes now support calculating normals if the output mesh from the HDA did not provide any. This fixes render differences with the refined Static Mesh. Fixed the Houdini icon displayed above Houdini ProxyMeshes not always being placed properly. Public API: The various Set*ParameterValue functions now return false only if an error occurs or an invalid value is given. Previously the functions also returned false if the parameter already had the new value, which was not the expected behavior. Fixed incorrect material assignment when splitting instancers. The Instancer Translator now stores and uses the original instance indices. This fixes a lot of issues where attributes would not be read/applied properly when splitting instancers. (ie, generic attributes, tags..) Fixed an issue where BSP input would invoke a recook at the start of every unreal session. Note that you have to recook and save the map again for this fix to be applied. Fixed an issue where HDAs with editable curves and mesh outputs were not being recooked properly at the start of an unreal session. Use the help URL instead of the help string if it is enabled. Fixed Houdini Materials and Textures being recreated/converted multiple times if the same material was assigned to multiple parts. This was easily the case when using packed primitives. This greatly improves cook times on assets that generate unreal materials from Houdini materials. Fixed HDAs not recreating/updating their output meshes when only a Part had changed. This could be the case for example when just modifying a Houdini material parameter, but not the actual geometry. Fixed an issue where disabling folders and rebuilding would change all the parameters in that folder to their default values. Fixed issue where invisible folders were incorrectly detected, resulting in other missing parameters --- Content/Examples/Cpp/CurveInputExample.cpp | 143 + Content/Examples/Cpp/CurveInputExample.h | 62 + .../Examples/EUA/EUA_CurveInputExample.uasset | Bin 0 -> 449110 bytes .../Examples/Python/asset_input_example.py | 310 +- .../Python/bake_all_outputs_example.py | 24 + .../Python/bake_output_object_example.py | 24 + .../Examples/Python/curve_input_example.py | 334 +- .../Python/eau_curve_input_example.py | 128 + Content/Examples/Python/geo_input_example.py | 338 +- Content/Examples/Python/instances_example.py | 24 + .../Python/landscape_input_example.py | 248 +- Content/Examples/Python/outputs_example.py | 24 + Content/Examples/Python/pdg_example.py | 24 + .../Examples/Python/process_hda_example.py | 368 +- .../Examples/Python/ramp_parameter_example.py | 280 +- .../Examples/Python/start_session_example.py | 24 + .../Examples/Python/world_input_example.py | 350 +- HoudiniEngine.uplugin | 4 +- Source/HoudiniEngine/HoudiniEngine.Build.cs | 8 +- Source/HoudiniEngine/Private/HBSPOps.cpp | 19 +- Source/HoudiniEngine/Private/HCsgUtils.cpp | 1 + Source/HoudiniEngine/Private/HoudiniApi.cpp | 7842 +++++++++-------- .../HoudiniEngine/Private/HoudiniEngine.cpp | 32 + Source/HoudiniEngine/Private/HoudiniEngine.h | 5 +- .../Private/HoudiniEnginePrivatePCH.h | 821 +- .../Private/HoudiniEngineUtils.cpp | 3 + .../Private/HoudiniEngineUtils.h | 3 - .../Private/HoudiniGeoImporter.cpp | 10 + .../Private/HoudiniInstanceTranslator.cpp | 432 +- .../Private/HoudiniInstanceTranslator.h | 67 +- .../Private/HoudiniMaterialTranslator.cpp | 27 +- .../Private/HoudiniMaterialTranslator.h | 1 + .../Private/HoudiniMeshTranslator.cpp | 122 +- .../Private/HoudiniMeshTranslator.h | 6 + .../Private/HoudiniOutputTranslator.cpp | 31 +- .../Private/HoudiniPDGImporterMessages.cpp | 1 + .../Private/HoudiniPDGImporterMessages.h | 1 + .../Private/HoudiniPDGTranslator.cpp | 15 +- .../Private/HoudiniPDGTranslator.h | 1 + .../Private/HoudiniParameterTranslator.cpp | 43 +- .../Private/HoudiniStringResolver.cpp | 1 + Source/HoudiniEngine/Public/HAPI/HAPI.h | 57 + .../HoudiniEngine/Public/HAPI/HAPI_Version.h | 4 +- Source/HoudiniEngine/Public/HoudiniApi.h | 2029 ++--- .../HoudiniEngineEditor.Build.cs | 238 +- .../Private/HoudiniEngineBakeUtils.cpp | 405 +- .../Private/HoudiniEngineBakeUtils.h | 35 +- .../Private/HoudiniEngineCommands.cpp | 8 +- .../Private/HoudiniOutputDetails.cpp | 7 +- .../Private/HoudiniOutputDetails.h | 2 +- .../Private/HoudiniParameterDetails.cpp | 84 +- .../Private/HoudiniPublicAPI.cpp | 522 +- .../Private/HoudiniPublicAPIAssetWrapper.cpp | 72 +- .../Private/SNewFilePathPicker.cpp | 1 + .../Private/SNewFilePathPicker.h | 1 + .../Public/HoudiniPublicAPI.h | 428 +- .../Public/HoudiniPublicAPIAssetWrapper.h | 17 +- .../HoudiniEngineRuntime.Build.cs | 185 +- .../HoudiniAssetBlueprintComponent.cpp | 3 - .../Private/HoudiniAssetComponent.cpp | 12 +- .../Private/HoudiniCompatibilityHelpers.h | 6 - .../HoudiniEngineCopyPropertiesInterface.cpp | 1 + .../Private/HoudiniEngineRuntimePrivatePCH.h | 550 +- .../Private/HoudiniEngineRuntimeUtils.cpp | 2 +- .../Private/HoudiniEngineRuntimeUtils.h | 1 + .../Private/HoudiniGenericAttribute.cpp | 19 +- .../Private/HoudiniInputObject.h | 3 +- .../Private/HoudiniOutput.cpp | 23 + .../Private/HoudiniOutput.h | 16 +- .../Private/HoudiniPDGAssetLink.cpp | 3976 ++++----- .../Private/HoudiniParameter.h | 7 +- .../Private/HoudiniParameterChoice.cpp | 11 +- .../Private/HoudiniParameterChoice.h | 18 +- .../Private/HoudiniParameterRamp.cpp | 4 +- .../Private/HoudiniRuntimeSettings.cpp | 3 +- .../Private/HoudiniRuntimeSettings.h | 24 +- .../Private/HoudiniStaticMesh.cpp | 127 +- .../Private/HoudiniStaticMesh.h | 28 +- .../Private/HoudiniStaticMeshComponent.cpp | 26 +- .../Private/HoudiniStaticMeshSceneProxy.cpp | 15 +- .../Private/HoudiniStaticMeshSceneProxy.h | 5 + 81 files changed, 11478 insertions(+), 9698 deletions(-) create mode 100644 Content/Examples/Cpp/CurveInputExample.cpp create mode 100644 Content/Examples/Cpp/CurveInputExample.h create mode 100644 Content/Examples/EUA/EUA_CurveInputExample.uasset create mode 100644 Content/Examples/Python/eau_curve_input_example.py diff --git a/Content/Examples/Cpp/CurveInputExample.cpp b/Content/Examples/Cpp/CurveInputExample.cpp new file mode 100644 index 000000000..28242407b --- /dev/null +++ b/Content/Examples/Cpp/CurveInputExample.cpp @@ -0,0 +1,143 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "Examples/CurveInputExample.h" + +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPIAssetWrapper.h" +#include "HoudiniPublicAPIInputTypes.h" + +ACurveInputExample::ACurveInputExample() + : AssetWrapper(nullptr) +{ + +} + +void ACurveInputExample::RunCurveInputExample_Implementation() +{ + // Get the API instance + UHoudiniPublicAPI* const API = UHoudiniPublicAPIBlueprintLib::GetAPI(); + // Ensure we have a running session + if (!API->IsSessionValid()) + API->CreateSession(); + // Load our HDA uasset + UHoudiniAsset* const ExampleHDA = Cast(StaticLoadObject(UHoudiniAsset::StaticClass(), nullptr, TEXT("/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0"))); + // Create an API wrapper instance for instantiating the HDA and interacting with it + AssetWrapper = API->InstantiateAsset(ExampleHDA, FTransform::Identity); + if (IsValid(AssetWrapper)) + { + // Pre-instantiation is the earliest point where we can set parameter values + AssetWrapper->GetOnPreInstantiationDelegate().AddUniqueDynamic(this, &ACurveInputExample::SetInitialParameterValues); + // Post-instantiation is the earliest point where we can set inputs + AssetWrapper->GetOnPostInstantiationDelegate().AddUniqueDynamic(this, &ACurveInputExample::SetInputs); + // After a cook and after the plugin has created/updated objects/assets from the node's outputs + AssetWrapper->GetOnPostProcessingDelegate().AddUniqueDynamic(this, &ACurveInputExample::PrintOutputs); + } +} + +void ACurveInputExample::SetInitialParameterValues_Implementation(UHoudiniPublicAPIAssetWrapper* InWrapper) +{ + // Uncheck the upvectoratstart parameter + InWrapper->SetBoolParameterValue(TEXT("upvectoratstart"), false); + + // Set the scale to 0.2 + InWrapper->SetFloatParameterValue(TEXT("scale"), 0.2f); + + // Since we are done with setting the initial values, we can unbind from the delegate + InWrapper->GetOnPreInstantiationDelegate().RemoveDynamic(this, &ACurveInputExample::SetInitialParameterValues); +} + +void ACurveInputExample::SetInputs_Implementation(UHoudiniPublicAPIAssetWrapper* InWrapper) +{ + // Create an empty geometry input + UHoudiniPublicAPIGeoInput* const GeoInput = Cast(InWrapper->CreateEmptyInput(UHoudiniPublicAPIGeoInput::StaticClass())); + // Load the cube static mesh asset + UStaticMesh* const Cube = Cast(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, TEXT("/Engine/BasicShapes/Cube.Cube"))); + // Set the input object array for our geometry input, in this case containing only the cube + GeoInput->SetInputObjects({Cube}); + + // Set the input on the instantiated HDA via the wrapper + InWrapper->SetInputAtIndex(0, GeoInput); + + // Create an empty curve input + UHoudiniPublicAPICurveInput* const CurveInput = Cast(InWrapper->CreateEmptyInput(UHoudiniPublicAPICurveInput::StaticClass())); + // Create the curve input object + UHoudiniPublicAPICurveInputObject* const CurveObject = NewObject(CurveInput); + // Make it a Nurbs curve + CurveObject->SetCurveType(EHoudiniPublicAPICurveType::Nurbs); + // Set the points of the curve, for this example we create a helix consisting of 100 points + TArray CurvePoints; + CurvePoints.Reserve(100); + for (int32 i = 0; i < 100; ++i) + { + const float t = i / 20.0f * PI * 2.0f; + const float x = 100.0f * cos(t); + const float y = 100.0f * sin(t); + const float z = i; + CurvePoints.Emplace(FTransform(FVector(x, y, z))); + } + CurveObject->SetCurvePoints(CurvePoints); + // Set the curve wrapper as an input object + CurveInput->SetInputObjects({CurveObject}); + // Copy the input data to the HDA as node input 1 + InWrapper->SetInputAtIndex(1, CurveInput); + + // Since we are done with setting the initial values, we can unbind from the delegate + InWrapper->GetOnPostInstantiationDelegate().RemoveDynamic(this, &ACurveInputExample::SetInputs); +} + +void ACurveInputExample::PrintOutputs_Implementation(UHoudiniPublicAPIAssetWrapper* InWrapper) +{ + // Print out all outputs generated by the HDA + const int32 NumOutputs = InWrapper->GetNumOutputs(); + UE_LOG(LogTemp, Log, TEXT("NumOutputs: %d"), NumOutputs); + if (NumOutputs > 0) + { + for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex) + { + TArray Identifiers; + InWrapper->GetOutputIdentifiersAt(OutputIndex, Identifiers); + UE_LOG(LogTemp, Log, TEXT("\toutput index: %d"), OutputIndex); + UE_LOG(LogTemp, Log, TEXT("\toutput type: %d"), InWrapper->GetOutputTypeAt(OutputIndex)); + UE_LOG(LogTemp, Log, TEXT("\tnum_output_objects: %d"), Identifiers.Num()); + if (Identifiers.Num() > 0) + { + for (const FHoudiniPublicAPIOutputObjectIdentifier& Identifier : Identifiers) + { + UObject* const OutputObject = InWrapper->GetOutputObjectAt(OutputIndex, Identifier); + UObject* const OutputComponent = InWrapper->GetOutputComponentAt(OutputIndex, Identifier); + const bool bIsProxy = InWrapper->IsOutputCurrentProxyAt(OutputIndex, Identifier); + UE_LOG(LogTemp, Log, TEXT("\t\tidentifier: %s_%s"), *(Identifier.PartName), *(Identifier.SplitIdentifier)); + UE_LOG(LogTemp, Log, TEXT("\t\toutput_object: %s"), IsValid(OutputObject) ? *(OutputObject->GetFName().ToString()) : TEXT("None")) + UE_LOG(LogTemp, Log, TEXT("\t\toutput_component: %s"), IsValid(OutputComponent) ? *(OutputComponent->GetFName().ToString()) : TEXT("None")) + UE_LOG(LogTemp, Log, TEXT("\t\tis_proxy: %d"), bIsProxy) + UE_LOG(LogTemp, Log, TEXT("")) + } + } + } + } +} diff --git a/Content/Examples/Cpp/CurveInputExample.h b/Content/Examples/Cpp/CurveInputExample.h new file mode 100644 index 000000000..769835f9d --- /dev/null +++ b/Content/Examples/Cpp/CurveInputExample.h @@ -0,0 +1,62 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "EditorUtilityActor.h" + +#include "CurveInputExample.generated.h" + +class UHoudiniPublicAPIAssetWrapper; + +UCLASS() +class HOUDINIENGINEEDITOR_API ACurveInputExample : public AEditorUtilityActor +{ +GENERATED_BODY() + +public: + ACurveInputExample(); + + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor) + void RunCurveInputExample(); + +protected: + /** Set our initial parameter values: disable upvectorstart and set the scale to 0.2. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor) + void SetInitialParameterValues(UHoudiniPublicAPIAssetWrapper* InWrapper); + + /** Configure our inputs: input 0 is a cube and input 1 a helix. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor) + void SetInputs(UHoudiniPublicAPIAssetWrapper* InWrapper); + + /** Print the outputs that were generated by the HDA (after a cook) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor) + void PrintOutputs(UHoudiniPublicAPIAssetWrapper* InWrapper); + + UPROPERTY(BlueprintReadWrite) + UHoudiniPublicAPIAssetWrapper* AssetWrapper; +}; diff --git a/Content/Examples/EUA/EUA_CurveInputExample.uasset b/Content/Examples/EUA/EUA_CurveInputExample.uasset new file mode 100644 index 0000000000000000000000000000000000000000..60d5db06178474590b575732ae27db609fc8f708 GIT binary patch literal 449110 zcmd?S2VfjU(ttZ_axgg^N3d)#7>si!$_lo@2?xRvS9eEN;= zeLVd1Z&q8OoS){!W$~CXMjCGvspR!JhZR$DSuyUY|t58zO0> zqy_sub*wl3#BrCMnf+|j)3*AVa_$4*^4^A0fAM#xdh1H=w%bmw8nEqzRAVZcNo_Zv zSHJ$Ig6IADhrbr8Ah(&{VFzD(0Vi_>;)EqD+D!j{A@Nf1{gYhxu4an$oXYgBXsu0oGw%NO#Xe4js``d(x@CA{YpS`Wp(#5dl}Wd@)Jg4$*<@3FvZa1Z zvaTha@y@w>%q^R6--zVQRPU>279e>j}}l{Yu1n(EESk53D`c;DUCXJ%&#TBfzmOf_Z4 z3U_#y+&8n&8cyaONntOq`xX12YvB1+O>T8#I+^tn+r8YA5z$sm(FlQ*YVlTGv{7dR z+P#hb@37>^9T?!8nyyLc5aq?QWvdJFTniY}E zc$-Xkrha`te^Pqdw8qpP4Vi{oD+1$g=+Vg*re{Kq;jOi`jVa-Prc}K*tJjvJH!yd) zh30M%^4+)CVF%OWigfw_?eTpFHfK#IMkX`kW~W+O8tQ4yTVtP>JELuCM>QFgMPFn@ zuVfp9Y3s|O)VQXKG|B^NjBBY*HZ-=jq`c*KX?l;bmHVoilG3&E)@&LQdE2(WKD1p4 zUGeVy>D*3+KBJn(BwG%sn4WB!CfGc;{SBL<52AmFWk)Q<5{cTW#I>{moY>L?X0`t{z3wfZbEgAx=8 zfpt)xS*d@$4{*HD&vfFIhsGW0-!U=l?T!(x4fWpB zD{VX(61U}w^rl*xl8uuysc}t>^SqupwRQ@x#NW`C5r!*Ha+hx$gi*ZS_{eO3a( z_=dUz%DqR=`seLtkj699PD+nXPxFpFYQX}-w0@qtxM^T|6W-ESx-7us9zgR$G)-$j~3fkBR%^0s-WWHYll1|QScpq{GU zno+_5-bL$P{C(H<36Db1IM9Uq>WypL}n&)k*ls-cvnsxnaIk=kIHH1++BBhW(&6+spBglX7HT4&!0S_ zu76(LswSE3Mex%tBEv1!Ee$ePH_mH!>nG=Z@I97!TXzM_*w&f;6qN&I)4l)v$ToRZ zZM`IA>(3|syLadvqw0lA8>Tjh{*Zg56(_@czHejSo*lT&Wz!E{n0RB(m)Eg;pUq{jw(4lEyvo%FKX+>m12|KpnIUt?wDwQ;r*nEQ(}9`r#U^VJtVIIn z&dY-^;o}27${FlYnTcX%h^`PJY^cv^|Ky(U)Sy+{Mr>53eWkD0TYif&x6){7&NnM_ zT3hr$^0XY>s8}^i^hsFlC)S)bE~oYwnWqCUgMkdLyfN=A&Q%T69+660p^>_J?!7qT z)x&bQkeZWPl1-VZB0V`B-ty2NdvTPzxs?X-s6S`x%jjSxssW<=8 zLya5SMo!VW(prcfc<04mp4rxgEOWM*jFK%PkTPEyg_+a%kRNv*m%|Hki&0x{{gos)ThD{H`H=gd9D+>>rhw@eVP)_6G?@BJgr@3u0v ztVQfS{-?zkVH)|itIWx#{qx-O%=lSrHdS9UsH$(@is6-g2MioOU`Y9Z@`^!&`}Q3^ zyt;DG!2Uxe!|QTs`Qgm{Z3@ozq3>_sO~#&R%bt`H16fe@UbwpJg9hrjv6VF@qOs`v zem!hIn#9gz_V0P^taU5~In$pv=TDz|ZTZ1W=l%Nn?DH)&sqao|-u5S4d2d^ZJAZjQ z&06y~+RFFHxBFu1pK(?zBwn!Ms;h2>+g6b`op~ph_aBEfXA68qB-VyFs0>_(n~Ix5kO@orj8QtLQuV<6CPR8|q}1Z8Kp< zH`IDp|6}C59MIO=H}0jBw4v0icSy+!YQFxx4ZC|*)(LOPxG7N2n83Y!IYv=!8hs2W?d zNBM+N<-Vn3T+jbt$jM8Y6qyE;MG76H|5RruvNc z;G~0=!x(q09`C?2J{WGAXqYYjYmL#RHb8ONKI8vlI@~Ho4`TNGTfg*%%c^`QVEOc- z{g7q7g*{z><#{*|~hQ(pVRDET-&O}c}HBC)>ch{`- zv%QBRt3byKJ9tl(Uv#M6BZimD!2Q2Ddox_Ds+^z`4(hmO)8A`*Wy{UIeg$hZm_Ba* z;Xfw8IeKp!FPq8MRGGEK&9|7lM>k9rPkpBN0;+EoFJJ#Cf}3j0q`XhJTJstx?K`a! zLX@$6V9T*@;05`iwA&MvA3)?P*h3v)p;yLBEm*y5|hL5w>(cXA+}*v8#1_C z_1;a_{PqHVU==Qv4TasX*J|CM1h%oa$3Kot+j2FOy?>X_m|%(1ItOn9MZqg8l#9j6yRRhWOb{`}~Br7alpi_`8{#;z5O$ipJ{x>Sag5=T)->y)M>c z{ou#VZ!n!B&t~m+dBXB9S%K(Wx$y043{G=Xb>93>79`D3+lyZ|MgUTB(JG_daMpWQ zV&dmLmTAOcYeGBb9rJ0orKSsWn_H|c>dmRVVUW!dYoNL@IW6NY-o3gjtV38x zh<@?thacX$?J|0TedW}f&Y{KXhL(&qZ@dQ%ANvv0{ZtcoTyNe5Pcj!-t4SkyKVSK; zUzy%au44r3aNY-fAf?GSiY()uzv)LaOkTRBDp@yubUNMaZF$PR_d^=-&CGT8ac8_@ zurR)wH?;Hde?yq8>!#65CNOXQxnFGoi?|Mzx7@>zuLg^>slb-6?mQMPnruX8* z$o%F_*yiMa+Iw7w(d&NcHNBWAq*h_y(CfY5gQr7psTH_^-iqg+dOh<;o0rsP`IKk9 zIS*ZU6S1MTLdTbr;_YvG=PZzJD^`ch%S<_BkwIErHH1m-`0nP;XISjAg3gTB>P|E0 zz^iSOxA)sSzZkp9kk$mT79RG*?6!&lX>Vzq@eDFy>Qs#XX*!C-*Rfr!#_KY_i&d#z zpx>cG7QcrDQKXLCVAXTRTmH*`e+fhQwT(aMowDJPouPG(Dz|tC#PMz#GkyishF`1A zCz#aAQq82-hkeH+qor!Ucq6Me9g4A$UtmCMSo_|Go+=Nk)J8xtIlQ~ZU2rw?N`6a@ z4>xkB-8R7PDqO{)<^6i&*N33^^DDObcst#k9O`1L_!!^DoAzYamr+jnw_7r|Bi&9_ zllR&@qTk2w7yvYc@^@rWJ|!#Jawa!9zaSLX)wykz^T zX~^RGHHo;5YGU55Y-llV=R7m@fAq3Z+mE5}bmG9{A`<~_d+TiMnz z6twF@+>z5))@xbb@SeJU?s)$uRv@kCbo8Y+9)`$sQ`<6wi2T=QW=^v`6UWCp{O~E4 z!}_C|7)8S%7REPk-uIwwv#lWG^}6f0u6CR(n`FFe=6`gZ?TWCEtns|teZ><|L8F@5 zWXv?0@xEDb(;2qGwvvoD|Eyj2M%y{njYt~z=HSC0I|E&B8XydN_fLD=g2pr%6C0Wi zFr2sceS4mdQXJJ}oa|9eqcLGL{_L%Necgo?!+;8`wRkt|u}`IK-goki`KVen(g1upWc@AJ8V}+@j39raJKmG4Nkr<&JZlqT%Ak z9w0<(l{Mmxf8ODFl#$;cp$X&C>BQ^3d_?E#kYuMqTiN$#UH0UbPML9!wL^rsgq!u! z>w4q6cR8J~{;;v+yf;U_d0H2z9kV)MifVH7%=3SJeO`fjIctucZ`-#}d$sOLU(c_% z>qA6dadY72X}Bl8zkMsX->+%AojLL^e@0mROlt!f_VI?iwG*tNCH^8xm07bm@A(zB zK~d!u%5DW*{OwcEe!)!Rml|`Xq0R;veBSDF9`#zDT6;Lths2w@^zoh?_|~&})z>>{>mzS7x0!t$Igy>! zhn)MPp}8q?13Ha2uDLuun+^4#l}4r-WxId|YPQ+`)E-8Em^*XhJh#`6Iz4FB4evAz z?sX9!u5FcdnaY&JI9p_;*_S2O0qgNI<_tRN1OSXqX0o{vh>W-TW}Tj4!lTU2i!Xis z`F7CPiSm2rJ^RL~OfmTZeeaS*=dB8P%#7sC=y}TN=2qE@lx(RWs_Ly*k*Q{u#tLb4 zjKPO?KjUx)IWONA;LGMNcn+a3r8+tpZ`%A!59SOUt>NkVdETT4FJBeA!-%5wZ9f^i zu2IK9R$#y1xZlHvf(Lxx#dM~NnQFbn4Li*R9tBP(r>-)h2Bk)JoAEiPcF6WH8<}nE z3f?cbfAy{Hi7p{{dvxpc9@8x{d)cXvHbWXYZ<{U$o^Drzb49^>@!fB(F)gKLid$`5 zjqfJRS}Djf)A0$L-*TJjnyDoghghZFAs-*S1H>LLl>Q9@bcG>t$La;b)A=C`7!DG*2a`OAM_J(d+wGb>p zxvjm!zFK)#lbMPquhES0>C9fx;@m0q)3ECp)YAK5Cqm{j(XTJL@(~7Ml z@L1xKC6u-b4<3-NXX2F#EzZ^+w~ZdVeKl*KUqzS5Af>f*#KhIc!a6zTrN@szQQ9D8 zvQes6x1@vUS&uc>TOKJGmz|z!v7!9?KHKMKcvjkX#P*r}NB_tmO0HP!yLb9-Zw5!M z+GP0Z2X24T&^C9q_}7C*-G+Wh+u^Ni+XHdM#|OP(K-&VQkP-DW&xH&9x(#Me*}Bia zaxDGvGyS3MarA?S(gQy?%dWN6@B8~wzXz@jxcbaf$G|n?3?O`hM>@a0DSfgz6U}~2 zNb0?`PR(d6-|@-11CrBHUS`BG>*J>T*G6?V9B7<7#3$1%o0+_Ivh$8XL->{2g#Ut` zm!;^URV3EI+USkT_ksJHlPy_quSYu1gIlG7EU&yd(XD4bijYYk%w|j*J$@~{+07W|ZMn`i74M^?X7zAyVK&Hkcg#?nHZNgShGprck~J1E`?*$vQcIp9r0E=?}bzCEtz!V>{R`p4UP46 zvH{!#civd8?ihsK_qfi>obikW8SLe{G~4w!6csCncCy@X^$~lbk4!{KEWhI#?tH~S zXtmKOGYjhG@&D=B_gK6E*(n_;ZohDkhNRD4vcbaJyQNDH#$%#vg00`9AvI^*)Tu-_ z=0EybWn2DV(&?GyGt;u|+N;^%#K%zn8L>B0-VHU9oq?#c03L!&~FjdtNe7Qg$9Qqfq_D+w*W7Z3gb}zpi<(p<`>UzgqLlD}VpOAd$QI#p^k+ z``w+SYm>a!-uQR~9hj7qO}SZXdtdc*^P1S+Yze8U6+I;Wz1;!oeRsjL^H>!yMWQWj zFhTazu&bx7)u|@s@)tHi+^B1(m!L4?{j$w@yD%J1shO3&{HFW^GYE2*u6|ry z|F5}7ah9m(14DOQhmpk-xBl%)gO0utZX&0RCmtG>z1`d+(IjudP0t;Q{>@c0H;?Xr z3)+(DWs*!Uw|=tHy{2W|sphS`_hGK?B;_*}r+;Mw2b{X=$9tCRnW3?^nHg0|?mK zJlm{WB(vgCh?O|$xU=QqCjl$*h!BM^=9VcQNE-%`=*n_yK6Y(z-Pc>R&b3e2YwPzO z(f1*hla z9xZM!`&D|A`!ucsw}(Wl7frY&K~u^VzkP7kxd`rF-hI`(|G?Sa-pcz7xQMgAc+VcZ zqz{1j5r^bAOP<#nVNboVvgiFWpzzz#9%M(;(xtB^?1kPp$uG-D86FuwEjz?sdViI& zYy9)DvhI0py7z5eq^w7gvTcf#^(<1>w@6vPB4z!Hlnp3SHn2$9pdw|1iS&_2pB4sKY`8-dX zmlnD2z9MDM7AbqRNZIBEaHlVIMapIrDVtTKY<`ilzZNNbqe$7CMasS{P&QxiV|w71 zKMs_sZVM~h$=()v-&IA*UN2I%TakW_E>bq8NZHsTW#fvJO)gUQP?54nix{g0I4gYe70kFqYNq*`D^m7SfwBjr%{L2_Etaxt{eI$r@l(BV zsFWQq*A(L^@KfD~{GDmD+IUdsdt`;s3_q23or9SF&ay>qWjY5@c6MHw&NGyqlUJtm z3}xr~WhyH(eVou)3-ZRnEjz48+2IAs7Sm<{c$EE5k+KVlw0TjHvP+7TU0S5<@*-td z=9Qi1U;EuR2c@T5rq#mA2zXH*Rz^&KUs##y{;)F5)1%uL<*%?Z<=L<@<@K;K;#vH{ z%7~Bf)3Sg1#K5NA!#@uzySGT$k44H>!KDs?2hYE3i`w;tvhxbGiQN9Dz~ z)qXlm%1+9=Pwi&pooEw3#a+vqrRGP9G)lv9KR_AYu>cOMPEr@`?wu&{<7oql7kB{ z&j*P7T)ORl;j+Vd&4Gg)FE&stoHGCX@Djjz-GKu>k2`Q4Ue8`!RsuLKzaa;84jkpT zhFg}MZtpnwK({bBe=7kT7a#QA_CAlfd~iz%;N;^I2IrL$z;W?G9|H$|aPj%I1aQLe z$%oT*(X#V_i_aQ9KD6rMGol1=!tlw5(^vvHZ##TV9E9;vyP%s~^E;&kaKi9WI4Y-$ zN&v?lAAB%e|A0@KB!1m)U3R_>!$;xh_*9kv zPX4@bKZwYW!!UCu>#rq%v;=VS@wwW;XICHQqa}dj;zQgc44-vw zTXsIk$LHA)deizWz@M;J9?#Oz76t=P|%3 zUvG1J>G|La2cM}v%yA`v`BK7FL!qa8Sk&%TS7oo@N~?C-$2 zu9HP*K?&fv`1F%z@fY}UP<(pbx9s@1{sMFhgEO%Na9n%_6o50g1aMq@;Oj7aE+_#U z*FPO3aQ+iAJ`a`vj*AcM6$a_N4%hpH=kG5jf~<>G`@h0P^5$7ZaR(`57M*oP7Do z!~`c_e$I>uPQLuy855j*`FTAiIQjCkG$uIt^3!!m>E-AB5c#Q!2~NKJOfNAUe3CH# z=lB@lg!w<$mlzK5gD^Ys*%;u=Mv2PL)z{yY7!L7+FgP2&R$9I$-W$dT!(xCF#s~YA z7*5v$_#7GooG^SYEis%f1@L(!1~_5(d|YBU-3#Ee`s=0TYv6?8)4RlQSU(Mup9wL* z3B#wg#Bf-j3&ZE^7~q8Ab9V{gyye9H<_kVcoq1bv`&S9zyy?Kfp4v79&T?-oJ70&v zQTssg**YdT#9%>7;f#q14pegCw8R8Q?RdrKteD`ekcZFWnBb@#ulT$j6CAbU70!zP ziifY&j#oI_#sr5w-Y&n5j|tAId2m`w0Ox5(?+ljqAC|-q+MR=9kl(ou9PlI+`$?1S z)=To?=z1e?^!fJwZwGu(3OLJ4BlVJK6*w39ovwMOv~X7N;m|yAuJPggR$@3S=D|^X zu6nn$_`nCcz70P5JmkptN)IRD^B8ch^Xc||3E=$8;X(A;RKIN@q1VGdzwG@0A3v+; zA&jrx{jm6rd;2W_r_4Y9pBUg&06=~&oG)U6qxON~v*rh-ryKats*6vb62QrSzGSkY zS}vU3V}g@Ux0V>->=}a3X)(dc$7fLtaFQYTych$Vxgl`AD>0mMVdfnr<q5c7dU`o-o6J%NPfRLa47S;V#>n)3!6{F;D8r!F81Nv{aGMCrG!JPz&XZ; zv(J~y4(F~sx@{#Lrq94R)^|+a{dU>m{Fn!4YX{CH0Fz&%`RJfM|D$yKArB7kv{HP| z@X_eJg-<{M^+(}+p9hEcV=0`!;|R%b;a2g$QGPql$7k1m@xWPKaA0VDSpjT3?}dT! zz*$S$2F{y4oM*O=2M*){&I1roem9iI1Bd60fb+c%r@1m7IJ}1fIQR3P{HBeF2M*60 z0pb*&-?kbb51dVWIFtS6@0b`59A2djKCk$6>%3PyaEOpgHlt>FUE#ep~&Zc;Ix)!{@!jG8k;4e0iWPq+DJ z#{*~EJbZ?q9}k@E^58swQ9N+ihXCDleCjWa2TpGvj>^xsSHuGc|IwA7%dUwB4)1mW zj>=EDA|`pIw6A$S#~q&o7R7@P@mS!fo%qY`@xbYyhtEBC#RG@ufWSxDdF;LMz!~Vn zQGNa8gYm!_;=?gt1(Nf>JrWO`p*|etgYxI&fx|mf8J}1D<~zL<51gHRII~&cl;4si z@xa+x+6JEs5hVGIedBUH!zxXH~IORT^&;91_{30GWtkZ%|tv^0jd>s!Q z+yvn4?Za95=hDJqFCXy`_EP}Ie0Exn$xi-2$$2T}1%kQpKfRE5Dy&X>rp#^f4|5E# zR3B80i3bjB2Am80`ShIe@xa;Mhoke)puLtI&Ubn180>XYx=rxqXYc*ufunRg-`Atp z)Wida<{2M_GomgYIJ63!BYb?`of;1uofp#n_)MFz>~Ox#;{(3iqI|H0kIzfyi>ZR3 z{3s(8`$>~a^UK5FC?9O%>$jiM@!+F;u)YsxgY2@y`NqMKb!Wb5qjcNb_kXrEUzxI$ z_@i_SgQIkt>Fd}bWI>~Owz#s`0)zcW5R_~Uck z@uh{s{Nuve&mW)5Pc1E+gYw|$H!3=vx$JPh%A*_KTvC4f+}ArtomE;m&qwDnwXZd^LfkUgv zkHYDFUp#O$E~ex2mj~j3qxQApbHP92fusJm!g=)Jc;GNL?)ZHD&v@WyJVf!??D5jV z!G2@@*&q*}QBTGLXTv-=*{9-xqj6>B>x-T#EgbAB=(bfJKJPrY>~KEMvk&+lrp|L; z`^|TLF&;RoN0YuC@4Z%9IPe?zs2%d1-+c1zWry=w9^Lp>sM2k7-wxUH!+78*-MaX2 zW`A5-IM5Azly0gI&iu5raNuj;(5kE79{7CO;e47mK76lL{uwwA`S$f^|BeTa^1-z} zoEyJcb~vBp;j^=Y&n{~SV)EPf$9UiGqDbv$qsAB9ujBOW-)2MXuDZQ_BW^Mbwhx36Wr>vt_J97FDUru)z z5Dy%cQ-!m1U}@p7j_%^~mXFVagX4jdkI%xPrG>+Ku#1nkr*TX?aCBdq!r6U%JaF{+IUS!t6XSuib{^d}-y(rxODc;Kkrt8jLk84nyEf@|+|_3eY34v7bj&Zi3J`9tG@qw}f4`S!4Q;OIQ3a5g+L z9ymJBDV(84#{);_Ifb*&vC9tUJ!g)^z8>k=*O&NV#DkBI2aej;$NF$KJ0%`CI-e?> zE6|nx_XK=Rv0ufM zh5Z*MKkoR@+W!8Hw58+o&{F}Nuy*tFItoYmP2qeT6CCDK7oRnrEod?aIoWDx;-8foE7rm{5vK%aIuTede4@gui-cBco)vlnBd@( zxN!E50ZtzP^Y@tG%s-#{;}dbBA9IJ+P?^5dXzPKybS z(oNyq5fdDhQ-$+JOmOo3;7%_uJ74GX0pqhm-uP@46C8T)^1%UUC~2CUA0=t3q?9CL2hfYLAuV$B(V=n;56+N89B{H6$4DafI7yD9B^@AX54oNw z$K52cZaG=5C&+PENv!)#lIz{&I7$-p!bG_qFUOIR(BBi}dYl|bNMc=Wcex%b$7)IV zOZXUjO4?WQkCWp9Nr%b#u^c5GBZ=o@*kj2vUlnqFxTN8dX3OucYOu{2|4L53z;$NwJ3=$&YiMdEv)-XLDxH*&E7_e1BVpIcM)9dGIwq zcy9wqjEkGcI~W)@H*aIfV|>YT+n{foN}4JuCCTLIBqAYUWisFX@H#9%5h&g@?(5@OG--K6iFw@`N5J7k@Qzdhf2a;I8w@v zkaWD9^F7}EB=wVXVrVsT&W}T1b7s%`JLd!CoN++6u}AeFNe4=rEop}2b&})%AAjha zmn6L`=@m)ON;*T*dy;OFbd{vrB;6|MeMzebt^Ov*=Oitb^ns*%Bz+`lB`Nz{j{lbQ zrKFAI`Xo6%FX=2vUrE|Z^1hbiHRu|x*Cbsgsk2;PEJxlScZnR|m9$XOX_9`D^e4$bSB|er zI#1GSa(%fRua|U#q#GsOEa_B9uS>dC(lwHuVq#=@eO4?4+wvslM^k+$% zNE#|>Q%Rdi+FVi>NoA6*kT$NA^mj>DOX@0lx5%-Z9JiFTNX}oBbc!SxdO1nI$Z01z z{wl}YC9NUncgXQhNoPv>kEDd;-6hAnB@LAGVUo6%)Lqg=lDDQDPnYAWa@!GXYsvXMInI?dN7C|=){(q*B|R+Xk4Qo;*OTk@CH+&*AC>f&q{k&aA&Gl7 zkg^RWJt^n^BWYzx|C02!q-P{8mGpz8rzAZsX>F-1|GYOPacq!Oir)&-2>cJv!rSmO zvOqr9l)<;~K73E#xuy)BhM!lF#P~8sj3?v6*f0muS;mqv=3aOZzEK_|4?bqzgumf= z^0=QiPm+AbcsV&oj*+kTBvA)mfoG60c!s>QBq=X_DCgYAJ*P=K+|TtnlHhaX4m^<^ z_#0Tr4l+br9RDVHugZ}+T{%xC32)5dC*63 zgOAC(SQ0XaEJ6qJE|G*RBAf5ZIeExE?QspQfPbkZ?)gcMz(-$PCg;@WNPX&3kNTHO zLZ*>zWcqr!zEKkOuaHC==!PpLK_8CbjEqx`oKu%;w=Q{CNqSGx`;u;wL>~3&1MNUd z^3XrzsZJsfo^bQ%EB)vqeMIM=d$bSKr{BN_pBp66SB~@*d?>$J5_P$D>nMoK*UCOCTUvHHJ-pCSj4E)dh0xYh9MV(HPkP~=-w%}J} z3*Jpj9qQldU&G_nxl7WElF)@(=WhQRxuecvNzX~TN74t9$m5)R=CY6E2u|p+Hzc9= z9+D$Ejk@{0ofBJck$`PGP9_Q3U?@~_tkNJ7j0T+(c zp`0?+#T?0}9Gy*j=w8dh0?-sw-cU^UEY*8!VLLX_A1?y|jZ2 zBiq1*#~54MfhOP%-t^&3N#wsJ>3&Jfxm>?32|6jw-jQ?o6uxARrp~*PE|CO$#ueH@ z$1^0oFG=N@bIQ0z?&#+SlAtHD0ByoF-8qt= zE8Ig{;6fj%%YBql=Q&B#rw%mW8XUR)RuVFctaHtgy!$25269U|Hq}4mNFBzAF@0Oo zijr;>Irvr5k8*y2ByhpLr0h1o>?b+DR1!8TbM8uVuJ_SCI_N=3zym)0{zVdYBj>-8 zC+EQbP1606u+O-Lx8NOkjXuL@r^vzFE9c~4i;$T?ZT`aVYzx(B_a z_9S$m?{g)gGtfKasXnCt+&fPax&l2y*#mM-J^I49Dt|F{_#T)|vfHayk%DMhIHU~7P4)gz|{yBCR_>zyUagl$H&PO)MM@KP^;0}J^`LJ^h?vMD_^c`H$ zx76nv{Gafz!4X`abgsc4-AR4y$dlvEYi!X|o%+o6w>a0#`|dS1@5xU2mU-pf`=qU~WHYJKw1qi^};=*xHfbLQ^*{d4Aa{b z&bI13v96_|Ion&nWg8kBvh#XX)i-3*EtBn4d7YeZWun+)li7yZsby(pBY`@$b>@Ur zb7Mmt*p6>WH>X;%4MH`>x{p}_X+yj+l`(y5NHOrEThbE5-c|mukj0eQ64ah03l`+I%A!cE9BU=`JXP{& z*S52}Ts2EQa$BYDR9W7LD(&RoyR3a!(Qjv*^s7lACxjlhS3!vSEw{g+Afs8f)7`F%=~Bn6g$21kM^ju-`?(KY%2xp zR-xW>>0L&G;t5$yvG59&ujH3&54C^6ab>?ovn)a!AZ^zRgoN;@tx>A}k9S(H6w1~L z@4v2Ms)~vMYF_2C@Ti35qm=eqfQ^hGu z$g~;Nn(8e(UnD$jy)b+os{nq!6s;EtRa+0*2&cySku535&IlR*gcvi;^j|`@Lc}&RGk|^qFPow0=w(hpQX=K~V_dy>kMY1!7 zTDkqyIioP$6_cob1`yo7ZtnM0HGjY%zt3`0owLbgKPw$?kqDiA1K>wtd%xJHrK#wXxN}`U^9l7mbL5AAG5}E z-eT6tO8>EMXBqxy1_|bzLgiiC^+RXe_BL}uw#``HWqt~9&9?+qj(Z4=>^!!O+}Eug zq(6qS>K58Ywy}`H^0x5dseb9yW@7}F+i0)9=_Bogjo6g7Q4Ta_FrHklE z2V9ja4w3fMFHl}9R5u}EZ0%)2d?|arhs@)FwHeNP;I330ZhC>uyS=e+uxqRqtdO(m za-J3&533S;D;VYWWn20^SXRkY7=bOT)Cu5+m38;w7uGyVQFr`YIZVh0VJsDw6$hOK zmD@(jcr&9WB+OwUv@cQnr*QvO>iUTdJV5lyP}w}#*BdB*LuD&mmE`u7?Ld@_kS%CK zOpT$UXZp(-wfe~>MD9$;!;q!HQ|CTc2L_{6ycFr?uQ0wk>r@Nn0=?nFnj%{u`%4?! z`fV-ixT!`MDI9;G(cXcUaPxZl@Cx@`t(~CXV1bQ+to3%Z{}dUQ!N$7Wu0&Xqwnmkyz?H;^G#-N(i2%<+A{zN}=OSL(4z|hnMz=#6o-7Lp0z{ zG8(-_j)K4Kg!jU7D?)RJhvdTPeM>|M<#6zCG8PCHMBoYM=l6@mezIRJE(@4Ye5tiH$Hw=1S)Oelq(E6dM6Mp|4m9{l$9dFS#Qmf3T@jDYYu(8asj-!v)T; zNLV@>j2E8ABg%+e2UsM;!nAx)nD2D{b?@52ze{=O|Et`ik)i1kTK~m<2*>x5cwM@XVo(LL*<Yg$eS_#lWp<@3OZ z$Trn`+0|G%*xjtlSbx9FkMLWcaAag#>-#nG`N@t%euN+)4|+vmtQ-YDq4S4|gyTiR zF$0X`50x`=@G1KV7mbi}e98er&q}$X9Q$b3NLV^Ax>2**nT8jO>Tb#x9I0!t2xIAxvx= zbf021DY7+{Hs)IRY_Q?X{zk%v3O^1OIT$3ok7ZUZ^T7}~8*ccK5^4{XoW24_y|uz~ zk80RL`rD;MpsDwW#36{w**V;}1nVl@R{U!1)U7cY~q*b)0yUtxLV{GG}Q$yCg6Y|hqRG)%T$J!cdv`d9((1D^! ztK^EAywaRoo?y(;xWw=VON|Qo9~(R&4+=)XQa-ns*me1YJlGg@pK7a0q4iWVyW+p+ z>aB!4BpKC?;)>tEjL~2IhrfwBRtrv?F@JE@*YNjXIUi*5u~w=D=YGZ#s)>Z9)}`C$ z{2oti&HP;nVft6Ol-F9V{XHucG>;8Z`GlyzS!n=z6y8(IgqyT8WF&yBX&HG14PedWn*1 ztcUehVrvOw;S_IQXSSf7grQaOyVPT_y@D?_ehZJY(}$=lG;1~yg>)pI9c!^d;4~V{ znj>3OOU3q4?<9IU!^}pkk21aoN>6u}c8Mw_OpL5J?mCiisol8=kGDkjN9V%){)`oT zfK609AuR$uOe?5`I$3H4QJjQuxV@vWOsK_EC%D>pF|x#8;d?grYg8A}K}yr{LN(?a zYpuR#x_{$Qkz z9_ZR{LPk4^P8!=N1plDO7Svk6^1_xOT7*B3J%-1NcV8iMRbStRz?#5f7;NkuY&LxT z3V|~}5|(P`LcT}tszu;^9PGCo&|PKN#XE?NW9`ip&V#bKdoqKlkV>=G?G;ixA+Ol@ zVEySd(w)2aKd8(8oWj~$_pyza zUZ_qAx2-f{*dCW7{GJqsvYubVm7*!aP2sdTGO|78Wc&#tf0*-HxDoz zZsQn47ukn0!f-!6Nj;D3rA$useBDLg%d@ zp2hrfOe74Y0;?s&WrhiE_=MJPtdtyLq*{WfRq0<5xo4$~h|Um!b8I9mjZj#8azY1TTu_33=U7)Tk=OhYIyb*0;sy zMLTkBXDf}Yuu=uxS>3YhobV;<-X})F(w#dN55=MwtrPM9V-yBDGg^D6eQRYB*_$m^ z8kU2*c5#y5a(f?CrJlR6%+y*&FI)eN-Oven@HDD#I%nV`*e8fiiM&U52v~dqzDvl1 zu~G0;N85Ypua#3gA}p}e{1)2dtT}z9#_5r8ltLB`&u(SJH^J*^kaMDY>}E;GD*&Q; zto#SJvDP-&NIg0ZJBmn=U0JV|GNN6~@~pqH^2XXaF)`wL33*IA3RbSfmkIysnr5^l zofX--Vp1toz}7%BRU0g-%r$ExtnLhu|EXc)cIY=G2+NQ=h6|jtBVnnXVI?WRqdoV# z{Bw@qu8wuEF03;GJC@Pr*x2V5tgReMEQM&K{w3tK6;WfLR2m?74>a1ESS;t*Y5YMg z)>w&!u`0&+GUi0qYn(;ZBgx~+K z*fSSIj*IH%TzjTCr7nzYRcXnH4mRV=%z~Z5sB$*UjCg+;SK_D4DXg_4JzV1CFErR9mUIfk@%Eo5_TZa4z>_$U%)|I(wDw+hExI9 zu8vp@xzLBzaf#900e0=f?%oUhKwT}crB)hdh}38e*QQq=DZhA%O5=65-wAnvPDjUpom*B%NiDUHwdz3wAcu)y>#Z%Ncs!t3aO)@VL_p-CSjqN4mHBV7( zsCDSJ!JdEXk0<11QBf_ZK5$!5X-~+DUH-`Z!4n+UN8YcN3!3wW4%cS>xUs{v*~fKL zhik(tH%Hdi(W^7-zr@#a*HjYnQky8+DL>bV2U#nA2)ckLEb&il#3&_gEb`m2t#!mI zQm!ZzyHLOXza7`ny-pg}X*Ryt?~Ux-8cAohS`fQv3Hlfy~Ja%QHUV2 zyMTEg&t#C~4HP)HMZ!{fQqLZL%04GfyyW)C`?arjJ)IdJn+H3@&iwt2Rbp4%=_5WC zwg~@QKb{qMr0I@GSh~7s`Au7??Vf)PFlSOe>! z4#Tqhu+Tm9o;4dhDIlX`;fgA07wqtQ7%8+Y^t4`7LSBex@zVdJ*DrE)ZN49(zKrT8 zJo_0UrE1;8<;y%0Ni)@)xt@8xC2jkike97?bi5fkG@z9`#unbiE5@HeGOEm&R2!-@ z$1&1)u=aU{guHaHqp;AmNH$R)Mi#Hz`m03qZ5$b|e1Oy$E~WOF8N6BIf&&E3V;zP? z%nhvI9i)Rl_zKUBH3GZy%D z(#8%F@@nGW%W?l#tajb+l5e%!wO#g&*k?CbGYEUS^XU$cf_q)21oIzdY(a#-REiP@AHvusQs&PzHzdiM)W(sr57Sw zQq1v{tWAT}Vyzg)0cp1@C#?H02O*DEMv)oxIOCj-BgT_c%~JFANLVTr0R?oI3o$j~GDPMQ^4j$%T853i-7hmn z`a+cQUy*I7bvs$0+FiOtn7}0?_rB@h-yZkgeu;fU12JEX_i_y4!>rzD$?x5T{#In~ zm79;ZNZ+*cM9UJ+=3p?#XBJ{wYQ@dM4{L9b8_wQ5r)FN zp{iA4%9i%Q+w8(hr%1hEC!=ES)?~+mw)9>k=D(+3??<+#Is|`=84_#X+R>~vp-=3t z5Ht#&Jo6PEn_UaRPNNpH=?9UpR`zSSxF_T_`Ihp*|6BMu`%25Yvd-LyAAvW{{Ky}k zg7sZFN5kP$*r=WLb@rE*6hq=e*2iQ``m4*(W=gcZnMiIoibU zSK7>nQ-l*-OJIA^K=Xy0nJe?l@FqXKv7yA&o8t8c16zog{OpZvQm7x4f3eojaIhi-m- zS1U43JQb6#7480Sd;&PKRkS0R*fj{Wyy})2CYN^$*{4T($xM`+mynOgbd+;h@k09% zqhW@%yYkqJ1(yi5W zgAc^Kk0c_U_@B)G$SN@*c40FQ;Gd$w>=PdN?ZXAm(hkG278SIDHrOuMRkV$N%L)NG zKt=wsM#!^Ii$G((KGD%JL$44gpwBiQW!Df`545pebSonY7T8F{NXWCkHgG!mm|2(! z`6RPFE3FlVb;;@poMq=9qGm*zp$+paw1KynZ>hzcfKN-TWT^D1-tU)deOZ3DGH&fK zw{(h;23vZFJWI5iNOoYk@#H9PD4Z(ZIu=}bnY}N}y2I8B-V>VjTj@wmtinz{-Blu# z`oU4X#qmy`b9ebJ7ukkt^4vBi`*`zh0z3MJd$FER^)P!e_X*Lg)LbF5J(WoK7E8o( zFIER}Q=+(b2Nmme#5Xuv%bvQ(D0d7JSSxlI7FLLjKqKi$BNA>`lhFX!I9NV#8&(U^ zI;$J(eZ0XkicWv_>7=ot)^JdZ%Hk=AU^AQxpRb1W{S4N`d&X0 zw$d&CNp4r3v=_Wl-13;h3*K?$dV~qlQdW;xjH*T75aYqt#%?F#s=Eew8-yFR$c_E! zsS(&XoFebmWyh6-V6c*MPwlni5++(9>pb!M{kbr`r*WBl ze=djtxjaHVOYaEYQsCwi;lg{?)gz)@n?z!+BZpSU-oeI3nz5v@uI$s6$T&6uo`GGB z$M>`AM%EkQOAJw5qMm3+Fx+gJX|D3<`q06gl27%ZWQus$Q$>2G3oRRjh8jD~&D}f_ z+l_sFkSl-sHpc*7{u}rbUYE$Wb<~)%vDnbg%)vJD%{Z}V1zn6^iuKC)vDS?wGPg6A z<0q9x!cxy7=y5)Ux^}n?_SfsX^_Z7*hUdEzQQWhpPfv~ERZCm!rFWxwTST^^lp~&~ zKBo1u(Ln4hvED9g7xv+-0>NFptCi;hndh*t`O-nuII5TFwt|(fvA1pf7F64I z+$y(oTOq65mX`i`q(5TYZ5v4?}!FWZS0Di zgx9mfuxz}^?pGrI&ME-0A{%odZiLjJX^2KMV=#N$z0*iXLTu!y(O10CKJ1cby$wGU z-xN(|Ed^{(BoK&L59A`P{MokvB)ndEd=ghIiZu}Syh2&*6>J(bD}A%^Cq{@BAG~dB z8&)oCq?0=mVh37Y#y+)j68QBtniN(N7K3tQNMUg(w><3rda)w#9CXBn#`0NU&Xr0y@31QG2X;_56qi5D(@X3sHClwz_>gNG_e- zH@~TuIw-PDrIqzBpclFW&4^Ax{)i6RU7L8O_T2$!EhuH}SMW(lNmT#UVzqOJ+k3a~ z)>9o4*_!gK#4MZ7TbHqdz0BWuKsf_AG;;PA~DplVGjtJ zn>c~xTdYs!BfExyMGAMZR%ER!{6gzt5PRa=1yN(8{&;@>cUWc$>JDg^f`wP#>>H0* zd2{`>+-M*+5;gL7pY0flrBc+&q20H_syDMfa}M({F=%VKa*n1TqRTlkULtl-bf-vI zYUvZd)E?*Jv~y$|>Mdb~@$CfUnlB32(fs3Ft?Nwgm=xn`E3x6A^La?iy#K_=nMg(n&9@U+eXe>N8bUl{Z@JLu18!C=nMPxhLXVx*0 zQv3Xdl}&Ufvw~VW*jskz6BZ$ItZ@Ropvt_Fb**)cv*migybqhbe2u=m|c8T%Am8`omce2Zz_3(t8TUpi!ZXnZorZZ3>zc&~= zcRT{0cVr}lo}r_H)fIdz8_mPlvfYu%wGhKsh8XK84ApMr?X%3AcF#-Le7;Kjo8tucBp|42J;vztX$b=xPtGS&KJu?>3@iLCiCU0RBp5}$_YgJq8gJ3@O2~Lc-J_#~?#Di5u0W@PHF8HZ6RRIl0B!S2$SWJwBmFuOrIJC#W+WM3H zHdR*KSx+q>S91qnSo`m+9ILJlUpWq9Z<8ZwrQ^aIx!_>vu5>9WQi`}Ok&_C>iaNoI)wbXu%7-{7x zSafaSHR8P@d(qJ`+S_mG_w$~W(*L*PqaDTTFOd{d-qU#li`qWNi5&#vJbVRuq+&QQ!!eM z8PJgvFe7dNMXmR3pHRn2LmL4RSg8)f!b4z918lIW;SFdAJhjZN4QlrT6Kkg2K8FBr zO!Z;?{#oe1qt!HWqFOE7FQ3+73SwJh|FA}d$3+AkO-7^#?q~FoC%6Tx8e0`h98Jh< zl#u-eQJm4SJ~#Z~k@)XuxQ<~pBa%8QqgInZbNgHp769w@){C)xi$BS147Z~R>~o6t zokM)9CTb)CSazj_`>k%UYnaFeQ4}oTV9kV)$In8;;nO0cjU9$%S8)nk-*ahQ21*RbMc&J}P)Mbr=>{ zpw*a%=(V-a(B*bVzm*~Mxuq}^v@5XacGkGk9fk!~M7y9G{vk60GY}Ci`@A0U5u_Mg zh=kd>(eCRWF0h(A3=7MJo?0l(f#3v&zyKN)V-}VR0Yc2oUh& zi6o=N@$DHgy9y0FXlSuxhO%)X=D?N?!wP01+p5mDK*z$fkwT8p8TgErl{BP}FZgs+ zzHFQkZK-c1K)#S>J5NC|u(a`L{M9<= zTC|N`%!zDQaez~?>G_Drlp^<$ejXXxkKCy-`MNMW7jpN&K zOn3)H!dly>K@@)TBU=hqXnBT1sg&>zj=Zyn--qA#rEtEK%X^$vZuLDve07i&4!%AV zB~i-xNGAR~+7oLZNr1zx4~1T17PRtAzU584Df|*v#{L=!OS!G1U%|H6S!q_>6thDk zp{Zw?dw)Qi1(7X;uSv0+{XpU5dZ8C<-1^nC!y?;LS~K&rvx^xTo6Pbt6vv0Pu@|%= zd~YMw%ymSpZKRy<7DbJ#?&JV_zWBi&jI8w5{W7Tb{D{cbloGp(EYnKy_Ku8fPi^9$ zy|8Zv9u?V+JI4C$wPqO=`#vqc=MzOImHwb7x%@Ii=r&!r`8;W)NHcpIe zL$R@YhwR#u`&Ff^(ByBCt>~E8R&;&ca&Pd4;gcfUSzn;>SEx7nEuI|NBD+U}7OP~u z*n!e2DO|RA(jJ>E7+Ypz`-RQmd3^nr7f;{tWf#jqybFl;G@Ro1O6RHI>x1m};j5Y) zXUX~=Zxi4SU8~g`cV~j`o6>vB{JZ%=Pn6W@xVURZQ_THAFZfPRR6AXLDzg4Xga?0- zdCaajGE3o$V3}em5D_O5h`)fA#a3We!~MG#}iJoWG8;LP>Hp0sNFlS_rdD^ z8Idio;A3H_&o_Lc@Ya>w(K1SWy%bO2tjN1`-0dDWzI<-)JUjAE-SeHlD}>c&S1N;V zOeMT?B3sSJf^j%E@~$u}xbr;!PAgXlxzd`!J0ZgP&E1s|>|;~D4&!~DUA0cUdvg8= z_0Nx_qvFsJnp_asno1&(6?}E}g&|uuHi*wcJ~12X(c;hGX|ei2F5Z=mj9(ZDOFh~` z`)zbo+;I)$@uJA~Rnl|09VSzJr&PB6WnIV5-rX<=lK*O6ZF7tSSaTbD+*r96TLw(qY%4_n)oSeVt}!~yZD z(cyUAL@?3Stk~dV6OF$t5?1iWf#TXaz#!m|%OhLYJu&+l4Z6Sd=!(dD6nmaG$Af1s zB1T4h34g$@v*PL7o!dl!@U(~n*nLAp&;|&sD!WYv*xgphyxqBEW3o2>$5&*c{L zU$CM_l$%|@S9cf|t=koByRJ?g-L7z;hlsin;YM#^SKt}j*n)jSRkgsnCK8s~If3-C z2EjLAu_Ubp$`@xkN+0lM9MGpw0N#N%b~O-Q;hH1%4tNkJ!JoH#q4+vY6fET(?X&H% z+u!Ru+=jK673aWu<-0YuAIk|H`3s(N*QW^k2v+!BGvxoN@`McDL{_fq#UtU1LcE(M zEBVC!_!3a>wlltS6NRPfMdk>eSEpUR=aV(hz}^&jzv^Whg)B4Andgt|&5^CBc2!zy zMAm)@-KF_0k!^MLDM@6;t`Q-PR)*{h%gjrR!LD^!K88=wNN_vxe0Gp6iiDNV*Ear2 zuiZZ18rhC|-%7=xom>gIEwUxW-tL33PtKu_&=5!o8Uh_+qptR8K5LZ_)k40J685j$ z-eFkK6a9z1Lv+@z$pH=c%v!|viJaLeCc8t>@^*b@xWKxj!?5hW0%V$~5Ro9_M~oV4 z=d6^o5=Xq46?LnzfY02*oRaYF{2j2+Ak0y$BZ4KgW6mbhhqi}T(7txpCR{~N=mq{) z!n>=(u%H?G484Vi#x?wC*DulWtSGYXBT#Ut=k*_441| zo2dF#SLWO6*To&C9&4;5A@{qT(+%fqS< zm6wBK$NK!hA07#~uvI)9X4_ytzRq&CwDixCNAuQ$k(AL6lyW6SHV}>`8V2 z**9^p!;W=!tBW7*Fs$Hd4Rn(Af{+NipNM(R?iXOB*gZfGkTd3NYA3u$d|2%*NVV;B zH@ikwQ|7JL|15Z4Z)ugkU|%$AGj?ueuk%QGrm$b$JJN%9XF(hNE4(lI(GF7xEp4?4 z76V!ZywT})*8`RWQ4@HZITBwItzw^~CL;J)hhZ_EcwSJQ6=wW5yI;=kw!n&@Wou1f z)8P4GAz=MrT|8cpsv67CXf%;*WcG=IcjS&kdx|p-`R}U#z2op?hiL<4@vq=>XpXgG z=T!6yw1RHzOri#nX1h*@T}cF*Z|GRPqLlcf-*VMxeEu7Bh#VCwd#W9L)w}MxKi_L; zkXfKf{2|`jA=FL{8CB)TGDo5PV_nAiiv4N4PLIX^HK5U>CdZfuPr~Mz*B0F>SQR zdP(H{iZ%G)&0uZd<=Xw?b{`NtjZedV6-vMuiZF6`q&7DAT8CjlSz2Um5dYco2GLk( z%5FW&3(SJpFGO0ffaoDUH($<)qOjsc{0?i&+E%o0J$#FcozanBW_dd+6ESB*8D(I- z5eX~MzN`cj?_%w*+Pw9p)vO=}Bg}VpI@)*qcH{&5&+g2{PlqN@p8v5Rp%BqZyQj^1 zp41}N^kyV1jgeJ~e$l=MJ$L*4R%BahFPOw$xc%K`sBIm#~D;O3-wSb66PQ9r_V^5tb6MLeOIv z(e`trEh#Zx;&SZ6+Oe19V_(3Yhp2?YA^WA~w_&JyS;o1P`@1j>pi$s2RBEvfY=7&5 ziXk~$d3=E=9R69F)rgXOUmADW_0eHQ596he)TorM4CSb=!j6cwMJN`{IF!0Cd|O|K zZHuvI)@R>@y^E~^duggCSl^dlLBQ99!qJXL3hxlJWq1$JKk%=inV|I$Yl6%`lgU+I z>7h$o4!<0-^KIC+cm^l?ta_~a`(4;NIJzfofU3hh>i-(G*m;j}jc*?`&arN078CHIfRU&MS1R6#`JN5_%3 zGo%jdOG&<;!uE-E7wncbZqd`%pKL&1(qPzG+n4yQpV%Y+v*%hvEdf>%@g85i9rYw( z2jrJ!F-OPOfxPmt^xI%nVP~bc2^)+RX86W3?uS{V^z2}jfjjwJ5qS95ux&9<9ZjWw z3tPHBmBL?N`UA&ehvF%OyHrY8zYP6$VLyUO%;KFmeFat-Hpo&I-vRQ97wieZ%nRNI zxCJuWI z;6F!WyngR#AG!yB=J*qHuJySS zUK>A_=#4mx{FWz27dRLDTg>@?dBz9Ehk2Zoe;J8}_X=v^KlZx-SBQ^b9*Nlwuz@vW z`FtMlUEn#h!V6#V9GH6>+SeqXlmzGdSyGNCoNeQ|bQzW2)^q7`RJxr}n%m=%;mas; zEch-2c8$va@SnoZKat=~)VJ-z*2nxWA=NDnt=p0k?!uI#J1-`j_+i+3a1t+c8~ zz7xDc=ze~xqf?A(K8mo+dt{^)*&%Gr%q{9(DYZL>y_2J`?xRI)7yBls5;b-*>a_DL zdnoC|tuo=8E#YX#d;$rFHG{_nZa};RIsy?U#4zN}a%c%$!*{@Z3BHAl6=El`*S4_g z0$l@pfp``43Uo7C#(Eog5)l)$fmjzf7XA&a4R{sr2=m$&Y&onrY&dj1_+NUg=mq#4 zs~LdEC$i;!Kj|Z3riZUFFs@m@MXLYzrCRXkMgPkgAD%k|PvHB)b%ZN?(;1$Q+S?rp zhfgJS1AhS!Paf{(8B2J393lvKS{wc#tR$js_(QA?D|4U&cE&(2Ai9OMOT-?)9q3m$ zim<*9H^Rm02!9<sMb4VoU zhS?_OTkydV36*c)!dnR-x0shef-ocP?zt_@?hseSljpp0f;Pc5us(A21KftFDzqKs zR_-W-rAYGaIE)2*7!Wy>ye->-*M<3%Tu1NWxh<)+FvpkA zpukF^ZD38tUF3dGj8wk6OvVH7bQrFZ=$kZBKDHJe!*fO@tvVeO8oTT18K2Pfpd9A@ zl6KHG@FS$RBllUsVuHrd>Yyel4a&h9_6pn9-c$;IY6MRDCN!3gZ|Q{7Wzk?~zo{lW86P1=2$Di@1VXXdQimzAkX+~$yJ9Wisprra^_W7N>~7l2s6H-#>Mq(U!2Kj518f3I3`3e6corANz z@Hz*_2${NawE}bOqi7b6r{M8kkw&p{k>ME&QaV9Fj0?5}Rs=H|Twx{+j}Tf3kymIf zP#p6@>;;0J&kWlZOPuU+65)>Z8DlO|y!EDb@#h6^qlxcl(A*BY5NFUzBK8x`Fd`S~ zO5t@{FjuZ#sNeW{k5{R|It@Ho{6VwJ6$yC2yrU3!l<%FB79I0^DKXHC*smC_EykMH z5#|^*GWL}<;n=utbu5Yc9Ihv<_2GF!TY!_~b4v25H+Tx*9;^alZvw0WW<9XKQ}8tBg><&wz%*iW2NE67Y__Y|z+p^#yxLu&*3i8xahA zzb72!IYQxObcY+Y+4+xmhuGH;@g1LVePHf`UcyLZ`~^}4zJhm&m@cdmxC2@ob0bI_ z_D9JbgYZ$3e7Qzj(Q^c^6nN20JVzNpEwe?GFzjyuG|%(cv4RE)!!w6gft7})0S92E z6c!HN7h>et{{<{yZXj3PV5RfJw#Bi0bH5A1-o@0Bu@h)Gv@5L(JVeMQXal-IYeFNy z7lbT>aFAevhynM z+Ow}<4-4Br_Wj&E$w^|Y!;QM4=fp^S3%Qc5Y@qzD_brOUj*=-3%wm=(*J$B6%4hTt zTL$N0h9L7W`vJ``zr?HwyZ{Y7B5YgiTkr@*hP{J(hxv$nr!~jh(H}^U+;a?m0q0<* zf*CZTf6y2h12iVIzT^(}MWdaeu=Rz16#5NF_q3L*of;w4+cgTH_IELIRDUx^%m2X%b zz6W@M%)`rN7)B>y-0KX3w!KLDxIOqFvK%~Tc`sJ#8OCj3?Ve%W27C>MaT}RhT?B7L z){V#@-@_338&L`gamTJzGFg+j)y!qW)oj-&u#&P@@UCP}by$X1D$6CX91hE&S^#r5 z<^(6+VBDf4cpCneH(t?ElzG*iqFI8Ip-B=Hjf|p*c3UZm)n0~48e$d~Ee$cxk@qSZ zV#R}Dtd|hYlrVQ1B8I}{BG-NLPOM7t$e6?aZrX5gP$m@KF$cHe9WgJ)r?hu^n}eA< zmvh5A;;~$gvCI&KcSH$fDLvk?4#xRjH4S!2$a^A_0Yqo4Z6VIgFv%&|L41a*-LA*5 zdtTP2Xo0vP!x&G9AxfCL3}E)h<=kk29cWyR+u%F(cvrbjSVy!}))@{Dp|%6D4tbxl z{)hxIEK)36Yv+-w+PR--S6U*5MA~yZe{fBa*RA6Y<~79~I!Vn5a-chR#8y(VW zLiR>s)+qmMd0^=ZCl9P4V}mWMq)957=azPPU|P1cz1dpjA=IuRng^|mxs9@HfGVvl zbt-cK>sXXwO&OWJ!=0=bP7+*(A0$v#S!W#<-kBV?Fcd#T#?SQ;X0 zk9j`w_Haii6=Uc9AS!6*lIHlZR{F@eV`%^F93i83C>1GwR2*8-18wTsU`|n-jwf>@ zb|Y`s&Ihr@8*_{rS8VII^HuEXkH)v-@rI*>-R^^Xn2VJy1C(_g){a^G$Ot!h&E1~i ze%5BuL`zv}b-u6S&bI8$=`qcH55+XgRw$-fx>^s@prTz;c?{?^<`vvels0n;%Sbdl zIY`Ts=2ZaI681buerY{SDV(%ro(#1w(abQs>Y31%U}snxD2=ReCi%$Q`Ud zlH>v);l8zrE%-)Q>isT&3Nfk>{(;|%x!pI)egm1LZgCG zGU7CZ{0qGo9EnC%Yl`mPQR~j-qt$ew++(-(3KBYtjZ|~%G^%b-6l8(NVxH%#-92rY z3W`@E^HLO+V?8f!N&OlB*=uBS%k!VM%rVUCk;+qR%57v+V6UkyQ$g9^*vl0_PWoob?sDJ#kAUMsG=oUx#79A*%m3^EF4d({e|w)eYC{$VEc--K03lZQ?puS zMVf1?r4J={J<9$3=%Lh6^4SpHRl?6#VAT|U7oJB$>s*TOnJv~iQ9|zh)0L1la6Z;f zG#{Q#yFArz@B`D5p`hCRhFp^*b=vX$b4`*_TFJUs4bgnQ2k(fNm}wRYrd80 zG793>Hf-?_dY+1(211*tRgM^WlFDo?%u=Xd&ro|F!%5*keT^*?%YQGAP`7(k=~A=S zR(o3A`6brt8EW6GUXPojHqPF^S1%%z*2*F{*UV+Y`6r<@Lzh82KtIL90@-zg+E=V} zgFgPIcJsPZI@a+wb5uDIe~Wyd4c?t%3Fa27g@(shyO7Jcv>B+duf!A4yw`WU>n@6!y2RveBF; z~^&5$joW{A!M0bT~8)5Pk$q?MVIk@xh2PYOu!^ zWrR}36|M2eL=;+L+w22^Qg~xmA_^TY3LTkU<=B9`R&QOq+E!QHykiZ21E0M`&VveWwheq%5*jGKgM;1XZ zmWXh28&7YUbw@lRuTs+`_9e(UqV(nx`x4}?2-rmHJRmmv5+GSTcQ;#4-M$2rW%&;$ zg{>P|WL=T4p5Pq0UI6{VQ4eoEv;H|taDj?v$oVUt09ITi=dJQRsl4*!&M&dumw+Df z%tap=O6*IpzOz{OECF_L@w`Oo2Hm~{`Q->NGnm*$0MV|p$uf!-`UcpR^ILV%#^$Z? z+gB$rUiL=AQ-rmkyX<+Th&f)#63#8~j!DS8dP}ul_V^xWA1c~8O8jcZsMzv3_nS*_ zG!W9mu*fVC+G8E7+5?pJDQj}XyjTJvZ$oKG)B#Ij-si&*)&Y^$4?Qe(f*M0?>*smD zsvmDBVSFeYF=sGkP=@6w@=ToUS`MHFJepNdT0&!d=qi z8K*-p?A!z_D;|*i1Q^pT9A-k}@wijbmN`~YBNp{DS18(A>sOK)uJ<~+>fTYIx2Rs< zQQ=j8ds|Zjj#+2v^*2f|7dm6fE0yi^u#qj;GaoG~6+b`&lEp@6&5bZ#+h? z<3~r-u55ktr6!b1P`lE?i^oP16;N*=ahLRXYDA}~fZChE(GsuoMf*FYai_9tPEy#M z`qlecl)Yxl#Ql%flERvW_nDg4d$ugG=)K)eqi>82WvBH%nb-F?c(p#t&#}K*z#Nm7 z{gR%K&L_hd!dq^yRA{Z3hMfwKZJoEIc3!leZb09{&GYp`XDeudsjOOHURL^*A9eo2p+ z_eVg=&F@GHZA~|GiS0WrF&^e?r4RK_0iY~ve|JelN!H`;9yz3nBSChpsOKlftuy}B zydm8DBoyEAt69x?oO=yEmA+>4xQ1(qM@w7#vj9?as%KcG2j=_?D>SP8i3fGR%*S&$ zsU#cHN??^!l>jx|YLNN32Qw|`FFeVFFGQ(2q+W6ErOd}u`*@lXH9)R1O*Kf%;ar2v z$9H|yi1a-PC>38am%6^WC2Zz?j%~R=P@W3(YpJC2gVd z@$|au?_>*g)=Bam&zL!epmtfXWSV9qx2mZk&C zrV}016TEKx%9LSEq9jJrg>8@7v-UQ}ZQeXVr=`?;Ud(h+b22ks)bqjabV2N$Inmv> zR;y=r32~wU>^*ZnTG8*vWg0{#3FtZB8EXpbB5m1{==4ve%|OoWvTe6=%w~m9Y=(1?M&Kj^=zEk5{BTpo-1b^fN; zbzY2?HLg3~d$n+0BNNi{N}pRFGx{g3^MVdMLvW`kt4Tc4T}fAjMF z(@5&K`&W@zautbk=bPBhp8g4zJTfXkVpn}zsH%}HIZFViHTTMhs)~qc_1+=UD z8J$;~h+V-?U& zf#q6x>CM|E7a9k#N|DarTbA%>xb;Q3voT_FiB$ihE%QM?S-Iv8cYo3|8%OETjTUU4Q93_=)m6qKcU((J0xMt<&Du|4ywMFlo zG3VTC)^E};YK!0SMMMS^(|dq2PtwrpwY3dRg?T9JPWgPQTU6Ss$Bvjiyk)$52HcLP z2URQJO}AELKH|{u_)!a1^{`t))#1%+2ae(WV{a&G$uiZTrupXA%WK``k%%qFW44G@oy7VM z=FHOXwd!A%G3U6Nx7lK3GN`1;%_1gb5Kqr5|54(~nNkXuW=WD>r}JHAuQaj73eD^^ zLu~77tdT`**aErR!NeRflp|VF8WRy4C*OGMm?J`uAck*_pXyh75I^>^1Jk=j#6E4< z1G%fyn^&r0r5qtrkhZ(h!@J#_S9;)COH4@34z6Mk1a}YdsTqb`bCH%*t_euXDaX(C z#HX$CTX~E*GcGY^sej>L;#m!>BbnD~KoPfSlFY|?4{A`mwX#iqmACaC?C`?gO1uF= zzW+7dREN|^&R&+x$8(bMeYmjIsZ^uczhl3{N$bh{czN*Zo&IKhrC;yy`ri5Y>MQJ% zlX9JqN9C6&e?B3P{~UQ#5hyhaRPige=LvbVbA*x{i8*@-=@UqQir@OF?(#mrPROG( zZri?nj3mOP-c05`yY?=RD5+%Jn?DcF$!Z@HQD$>&OzK5vuJpPmDG_C69T{IAHIfLI zc~7L9O-w|%5)m%*TWZyeF|k9i+*j>u@CC#y6&{b&lrk2ASt8^LlJ8~4$Pt%VS;S}} zT!|T@ZgnFuW5oLC)*r!Pv0z(iq$KE+pPsCwWZ&l|txHcu$~TOH+m0 zmHw5tFP*DcxU>h+zf+Fi!h?~kNS@+m8J|T|RKX&P+yArPTIcm!gt3$Liom>2S1LA6?r)l2)l(5(O5Js$Pd%9hlL zc+(Q^$=7>4iFY1(-6P@cokz`iKk#@BheZ;LH^0N(P2VSC@rm80@ZZ!P5v-LY-slsH z=pUYN_A;~eE`y2apIm7%N3NB2Gp}(a)~99s;P1YFOZhA+8gKT*c+D8z56632+Uw$7 zu6nNvJ>xmDiUXNpa(?aoD-xkA7SewWjWSuISj~Okr|O%cf_mqQ`JKt?tJm&SFh>RD z97O*dn%g&R)mNX<1KvlVr-FKygQ9^IOPN9cIkGsMs)u(ZDTt)4dOj~)%{sqOeMwtk zA0y^gLht^Q>>7M^k z-*I$n(H`9Z4w=^OEpopiuRpy0!-|>2bmX;%*MGoQbiD^X!gCno&rE-YxsAvEJPz7p zOgK}6>o&K?Sctqm+!5+Sn(blpJXFLUcu$QQPwYHk)UTw`Nxz^>wDPFFho}0F`jl3a z7y^trMbSm=SdEk}N+MLdvBev6OuXv1^HuEn53Lp3`t9B~W--jW%KHYCIZk=sfHH@< z&wh~0x+wPEx3U6?s&-C|RvV)4%;Tzl8C7H*!qVWa4Z8+J8?4_{8@e&b7FpJCA8XFY zWDR%PWdYL66lFW_)sE;#W#`w}j!MVDO8c5QSm{hN2P+*3j7U0~-x;H{tI~HX8%WFog6|&}aqOp21Oj=#mY6;C~*ZxYgf=i^uka$XH z##m@Zb6h)|2h+Nm(W}&W(Twn4SnIH-YOP=GaI(uKxYGO_479JZJJOHQ@9>7EW-Aa* zGjb23w?t~q2R&r^>DPR?SIT}Ui1ci|ugipDN7dt`5u>eHt#>;2n!N;gYSN2@U!}gV zD|N6lwL`}OKb|QazD<;o7oWUG$Xkr~Dj1c~)uma}?B_w{U;+xl7MSsM|Y+ zP&yi_*f9mkVp=Oq=~ll{l6&ZmC$&e=T?X-#ptSn(dma3S0B8;+gJ_q_>2b%DcIK>T zB_)0}x4aAjk4l#*oua-#taQ3{&KvDle-WGU;@O3^r}!*#$w!MUqv7}&d#0HG+^l%4 z`5C+JWxEM}h9+aK3`gxyn*vI+=5m&NF2Od%duw)GiTCm|$Asst*3gycA&>Z<$y-ck zwiuD|6!6D%Q#=L#Kxr}Qo2XTj$XnpPsTP>4oHQ`{6`hpmc5cNs!T|~9D2kE_Ky|AZ z((i(WclRN1KkIRKFT!p2Rn32+P6a^qRA6p$rvh^0 zM+I+dNGy+Ta%SIcfSxNW%lJoRi*e4f7Of6t|IsGyHm~l0daNzHXe*W|>E+2-Fwb42-UBS5t*m%9d#B!Y2>bbNvnt*0vdFcS z*Ota__1X&3q+}>I+6rIJ!PkwkR)R0lA`+izYQ?L*!psi(4)TLpu=x%A-t^Uh)C1@& zJWGSfxmn{ucHQ)a%s-Is#`DZ*9dC4ob!|)9if7kmZKZpQKgP~n-J04ezJAKqFOLcD zLE2090+eo&@`%V++w~sgd;*c;@bajhylq{1WDOF@yn!`II7__s%VWYmy31o*wvX~1 z5Yqco@~Gb@BDp{O8c%H3)zL$qZ~UEN5%Aa|^}6w-0N)i(uY}IpfJMkDsAtw<_MLyx zKkJ4zSnH`a%q!P27HHkot>&BPo%v}9>loCEWW4IjV z*9ar}#`h?^@EGLWMm;%=(lUAh4pJ);;b<}57T<_WBeFEQ<0+vL@y`5k(%n`xqT)K1 zc5sxG#c=#&)mVR5ji@|g#t}>A-&G^BWpkHwaEe-=0Zz6?jVR|Na&>7OJ(I}KB|@Jw zG&ClxzJDlphGNv_9h(vp<1CS6a}-9-U9o>i**UXKGOuR2uY1Lh0?gyi_DJ~(;WUN9 zDr3}cix5#`Nt3%;t#6`73ARDXt~=vUS?0e34nwsqaKTdQOd-Y=>nW_Kve)N@X4W>< zmm@;)&sHRqhnbqKr$u7P!&Y3h{H}2M%riU-g?;_()$3c%W9zG77bA1m-)Ht3$*v5R zv*zrOQ*XugF4y+CGOvf4{ZRW3!}#y4jqGV{Ge5fNY}CryFV=H0=+*Fg!>U=Mr2zCe zMk;6AUe`YDtvSaP?3NaO8@H)637{;!WQb2?ExfJaRKtIhMd!R?n@O-wG(@G1RNo=?|{O8ns~EsNO#tjxu(9 z!NRcQg~u3sCGP%*SB!am($9<&9nKVfoTS~mUmYp$Mq9JOx_6u8ozB)#n4j%XI8u>R z<}5_lSYt7FHOKOr-;Jx@ffPUOj`?de~)NaK@~6 zZK2cD*(SbH_)se(q1?uFZV7xS`vwidv(&%Oj0K}&?l)7{{03p>Pn`yBNsWeGNUX!d zNvc&Qv0s$6vsVq;JkMF~UXyJq9jf=()GWfCr_FoZA(KuXo7*#jW{x($<@cZLe>9J? z*v^N_4{Is*Ky>dhMZ2t1!f~`+Z_2&gRt^J{rNpc^)q6eNc?|c*D^edzI}$44=ymwo z)mnczY};#R&qq-iR)FWmipos)$Xn3y5$h}|>)x%bz5m=V)qjOAS$lJ^!(8lKyZ64^ zIUSf*Jk1^gpsdvurvs|^S86q^p78yMqVRa*w<6-rW9NQG#w!@1q8*P$LFDL^HyN&W zJ-(UWmF;rIH*>DLW{Ic&A5VNkx5QF<^!Vl$yx^Oq?eB^2mf>$mMfmj*?6hSa?~ZL~ z7?$Vg%~VoSKC{)YXbDeO1oIg5;PoRS;-Qh zj0Lmjr{a_Dx;mboh{6a{M?sp$->RPG*KGGeBE>Z8#{{N9)p)I`LdMuLY|EHd7B4c* z5}v2M*KFr$V2|yq(jIzCtX2Ws@$7XC(A%mho-<(mrnkf>!MYCPkvar=mL*X6G0Z8E z(Lm=XHOC9H(*b>De^tn0N0iu)1l~bBxjCz%|J?mZkW%)L-1#=K zAIbZgkzJDj&ulZ4CczFb)_rDwP_4we(*(0UuGM|+j*>iowCfYS2IrcU_EwO#;}Kdt zeu>@4_L?ah8kq%B_+a~{AQD4t>sU*9Z>jA(M=ZH|JXJ(K(+`D7#3AXN*%zgrD5A9Dsom+&kg){*h%4poQrOPqTt^YO+H z?EisZCHLoJ?OD|V%DeSM=Hni`^8@|Cdx2(~YLGnR+z*+rJb2YMd`GvoWxn}N*{=B+ z5KzzVgF=shiZTKz>yeaZKdCh}7W&K*=d883L_#LP!3mjEa`X3+Nhyht)Pzh*O~p?P zFvVm0g}sEGgw0{x;tf);LVtHV3ER&WMS1ps!oFDTq{+E)Y_v2voV;}FpN-}g6R$L@T z2bm;RMl_ryR^}sCGKgwHq#;N6Cg!&odyRPDUaa8AxCtUCN^{7$lXE|0KDYMrChY_Ie0=cw{FQ)Psl1?R=yEVLdgE13sYk7pQ zJO<_h)(0pptu-unB)fR{U7K>X$ND6c#Ff-IQlmNbfHj==wnI7494(ZySMWIN61_H! zEPlr$g)~Y0Sx1{`(ywUS{$v<-;#f5>YGbVz%0s!U~H{I@kM?Y<9^4&UmM_ zk}M~jsQ0$E9!|X3ZImOf@7XBKyW=Cao(+1axI!oSh>I`=l=e~MQu>EW zg~L;*wCiZloZ9t#sdPI>WSpqLdbFjfz}iIbE$OKM>ao6QE*;Q<;sn+Y&7nm6=`7>D z%(Xho14FE(o3F>!GZyaL5l?TRC7zX!M2~xYyKOviqSGOYm)T?Rs-JXxWY!k$8bm#z z;m#vUtGfG(N-FLCBJYjB981*~i3W+CeCzcG`V~DT;;%tMTY!CIeHzYQ%UxsPTL!RYi?D03-wsi`!&tM zQ>aAiwNmWrZ(CGWZ)I&+S!kP7mQUVT;|5;6fKWb)2c5%vuPPF!f6poA)~x-M25_2j z;|sYG8}8f@Pfrb6K=}ha%TSP5_-c`Oum6lFgnECl;u5n*3ri-SgY)_>+*sQQ^?usc zu@mu-?@$ebnW)z&gnBQpyQIdN3eoBDL=*zE0CN;VYIfKkXlHxu*!-SjxlS|0$4~so z6*Ja@QeVqjFw0YK4GtG&gTLtW?Mi8Md1KYgKz{9vy4spRO=E*^8v>^KI@ABW>ZU+l zeN|1PB{$7?auQ`5hq(0n#jhZ|{;$9Odc`PohH-AkcT^MLcc)Ci|L2t1fiiD1GN%~l zbw;%(8Nc5*&H}t0{r}nkTwV3rb))w_Aoubsd;dP^=u5VI!v$a3>xB9ZWh1Zov7va# z)GsQx81N4opP$m_w%#Mo>37B%#pljD=)VShQ|I@VEt)i?WbL@LzB9^RJ?1+D-edG0 zzhBs;yx^8@b??mn?B0vMHQ>E(`tHCxihjynd17V8tn#aG+YuP1=m)KT5qQ`6=x~sT-xPlyKjkl=h$m zgaPlBR8bb4&!sett`|_6N-02zk6q~EY2yd(d&tN`J!_3?)ccZgjdq?luF>u$;~M=~ zZCs;YD~xMDT`xDT(eE|JHO8^ZxW>4yF|IMrd6aNq+|P?EiHC_s8Q^7taSc4}((z2+ z8-%;NjIx7`GfrmwZcD#^Gwx%C>1dR_(tsO3)rl)$2H_ivj2nJYMMJKVteo_wal?DY zIVc&pZJArdzD)zqo2>v zUN{(wyUCwE-_CSVcy#W>!jsEq)Kv!#RQifvqMB>gU>^Ds&$Q96QtkEU}GrFu%oP&$^<2?Sq6=ed;T(e*-{ zDV;+JygH21I7<2Sdp@N+N;Bzq8KvQr4x!)2)474pjg*?`x|+^sQW{F>EJ~+Qnnmz> zN}cHX96Fy%3AmX-X#v42S(Bi?rF}RN1I`bTL|xc|zrt#3g(7C@Qb24pj8? z_3nMpECQfaQg?F>ht3lUS^i9=-~QZ`vXTj96DA7U2qr0;&{+^ma0`BbdV+1rIwaGT zDh)OYmIglsOM|h>xH~Y?)qzH9k1ijx>tP|?nsd0OM_|oh8{pAk8(>y*-@&xOlHlH8 zNw9I*ghDnIVyGd*27d={1)~RV1;?8k0p|xxLIi>(Aq(b`kcnVP2uH9aq{Lhj(h@8Q zu?d!h{7}hc7{?_16Vg+bCYU$9v*6z@Nids!$^$od{h0pYkj25|DHD=mV3Y7q+*p+AC0qXiG;B<)5 zr6fbfg`ihnLpS?xM-3>Kk_@RAqF=cTKom5O3Cf^{F`mX#>H_A$AaB2RrojLp>N1T7 zujCqdYfYC5&;fXYc%#p zf9wO@CKQ}fBBZCvCM3he2s3?1 zZMPBPHI$OMT2Z8I!l)X`EbFj|{!b`No=1g*rK`M&Yw3ef zK-6ZEGr+8J2AGxKMP*!)AB=h-$?l#6}Lkv#{iHq$rb;4680oyTpeC!psX> zFNT*l0~7$RQ<7o)g$E!JgS!pH97Y*t9Kc2BQCD8yXdXI%Awvg1v}77OVAi1nW*xey z%$E#5L^vao7U*G0G8_}(oyZYXs}U&u4#$P5G=*o|=H8+}NN)iUO__QNn6+Og$Rvs;fdU1ZYT`0Em&8+62tnCScY!MP;NPZj-` zJ@Hf!jC58(QGUVToYd@$?3|3OjO@W#xr5RM4bCY_PaQO9aA83fbQUT#6C8eDkE#la zSX51|3e=ksiL8)8i8&B@VHy-*)%LSRhl%5p;f4xdwDJmw zGRm2I4ezu^Rl~%(`r4CEG4~l6A$`6j0gcASUAYSvlgzi@Q>4z z;S~$lm`S2ANrpxJP-aOg7G;>^Zi9bA~7no*RSl?^jh zl$n+>I6EybvoN!OGC+6?A|)B_yYS(qu|ofY#tL~JjTJe0m>3EFPGGVu`7Hd#KS^Ei z8Gd#BkpAIFNyb!0%x5aE1x=CL9o->Ko=d07>j4$jq9iO(BLlUb22j=^i6CU4 z<`PUy%cOzAO|mLezeIjnhh6Bt31uA~+{Um|6UEf5@=|Jy8a{;X>>?oOFn)F24}GEo z9k?1(9ze?`irJpLjiw)%;&c(9{*;SfUAq|f`IllGm<#RMuXmpw9p zfwE=g&l`osd~nYRCv*{++f)8xx;qK~L{?e(OI#8&$dHofq9p!_$z@r2aKg}FEx80G z@lQ-e%StAS>8mlAF5-t!HgsRt^XMOrl(G(!1hQ#Ll}Eq3h^cNby27un&luh6DdL<2 z=_B}{Hx0D2m=MeI2T%^C$6cS}@{bx9`#ksTsKfXkD?TR6yEXX0?5}X5QHQ@4V;W7r zM6(6dP!}=#?nYPm)%69Vq-cms7EUmrV)iO-EeyF8ztpXwFy^@_Hfm5m%P0f=v`|CL zQpH?HgaEwAhq7@92$<$DA(I#6g*e>+ggD&*2uDpcmmO2A5CQ-GxQG{VkDui@MXAw$~jv($O6;&!gW#y}JFOGUjP!pnZFy@(5 ze-i$Qtg`YIToR$Lkdg~f68}V0tgL)Fm#njvJR2qPPsG#8%GYp7gxf;uoR5D-QFW;GCsa_#+m2BOe7(eLqT2=+s2T@eJhVZln^i zYmWLJ(i|+F4?u427FyYgU2}U|nj44a0OaQ0C8x}_IVvj-8yR*Wg1O#_raX~hTPg^x zK9y2{(r8NQlt2yyNJVA^prS}c5w1{>2XPwNh1hk4{CnSIO68QyT|rbA&cCk$Aze|V z0`Xi?kh?M@rd{btwNIink&?M92t|{$gF^t5=APoXdw!6S1`$mU03>2?KzaPDVe)+EV_csim~)h6sf=( zjVQ=nxrBblt}B?v9Z%^vO6IN{hpr&A0;`dtNJR;*P>{O<;flN~k7?99ti^$pDyZ2h zlrrdgGM!JL^IephC`~AvDDr}nQ55JRK+wwg)%9Guz~O(88rd_2++EZps!pc~8mj9e zN@A@7zq-Ciw}}G<;YtrjA4C~xxbhqg*Bs;4ph0^`Gcs7*gD90#;YpO1(DiIer&BtE z(i}=g?{*krX+)if|BA60zR2nxg52kzLb>YImpL9Q~r&nd=Oqxzj7s@C@HEF z{Yyy(PYA}ST!n0u?u83M+@|?w-CWkuQo*6dJ!>`h3eaq0Kui%h z^&w?BBLrytc+)t)XH*hoSPbmg`trIu3fTjtIDRw=4U|8P(PQp4vZ`T5pz)|Fm4O-M z0w)E5spU=8jUm^!7`OjvoP)Syp4wn!*pSO3xYsBkj5amPHQd1+I?K42ZWQck{GMZ+ znJR*Yg~t%E;N4G`d4E#cac$mv5Bd&INHl}Pn`w!*)FCA)^IR84U{3@1z9oVpTHr!F*M>uVkCz&i2jTXe86Julm8jb7wIqrF1FeV`)@v@5P88A8 z=pTuf*a)l#<>8@7Z2ID%77u+%6wwPwi{ZsaMgW3Zjjj#bE6N4o_t11E_@h~c?u}VZ zvFlR{*W?KWDD;Fu3!BUHJ7r=|I*y+5~lJQL8&8MWAS)DI$uENS1B!{bTuU`ieE$ND@xx``jygEly;=DE9v|W zrB#%6qTkEuoI&U7==>3-6_haT`i&AcU0p%vwb%Qu>L~&y;BL=zECL zpOg-!G6zvgrSt*)9!TdyD5X(Kr!rA@0h&)#{*k_Fp;|6#XP!VQ+boJ|(%{cS)C7q(~o}XIB5$qneo^+cB(leAI?&|RSejhjY z>oEPl?A71BJ7B+_6s# z8@Zrp?V~s6W}Y|xb8~0S*JzrLARKRqs3K?(K-b!Wi2@RL|4=;e4z1wuR~fAIzQHZb z$l?i9BQNAM%_zvC01Yh;Y*(9VWS(T4S#HEbt|-OrHW>wa80TOusdURsr%XRts)uE3 z!u7yq@J`&+Xxz!8*~KUfze{`ERQ>)r`-~effU{L&gKz+&MN#ks;ne)WiF(agu{2{6}#weW2LC-I{?d{Po6x{Xk z;^*G%_COknA)m|0+d}`N?~~qHdqj_Iek#1adunO>%+k|zu%Aqo1jmyUzwyAKuDSBi zzs?zvGvf9~+7Bq+b`IPmPve;~_r3%3vTq!Iamn>}b^CO$S1=wc>}g{>f+=CSN2ZUi ztq6!GhVsfA0wwtcf$G4t^2WeXf$FJas%l1;&j^U!rDMzMrv)0(7vnVxbY`+ev`bUe zVEPwAPfQ@Tzw3W1pFaMk;TO(4q~P)6r@V@?=(dRzrrYHu#+v@x_*)McH8p?kye)OF zjoW@l9c;OAr*-^Y2u`Cds;>&vR8*hBNN5zp??9O%04{I7&ZvV&FMe|8fQmW%!zjon z9XMq4n0B?>_JU1AI*u9jK=HmOtZvE~hnsPu?q=kz!aq7#+~cIY<{ItupdFRhB+@so z$}3|1be(VBh0mUUVe#elpZ09Lb(<{`oSyz5?#V?0-Exn^)rq1(ogfy z2Rc|eIO?Z|c8j%s+V$VtEj;10p2L^_@Wmr9oxe*VcLCjD{iKr{9jvAGlTN3g-)F5iMA@_TC*(dS7 zUR(-v(-|>&aUc+kBVwoNrDt}n8L{Zj!cARIdZJ%r>1?x7>L%+HQRtNU8y8jm)v@5- z5mV1z)P2@0M5e7Un^J;9+^O*7z!VwnLv`l*@jG}pW&}&9ix6iRG45kGTsvpQ*;f?I z^L_F5<=J0jnZlLG$j1mu2Vc4D-mILVXAb{l^)7v8E!Dx=nrcEHPnoz+_V+tKF?8+f zj{o_3_q>rh*!2c0VyxomN&oW(=M`lS$}h+mR8&+nC@sA(Gb1BEGdnN6AUh{7E4@Ix zU}H=@_765RmecF&=>7Y(H6lFDa~*$UWqG5&s=+_GzP7w#N_j)0e^zCn#!nl60`xlm zX_O!EQxzdK_^GzWzz~r)ydbxCgTF#PEa#`U?9tSbZUv;OrV8yf_{-}9oyup@D~!u0 zR|f|8CpR^UTWe?z>aQ$s@YmEf`cDZo`X>jd<*Hy`0~PecKRkC#@j=6Dn<}U}e__qE zDtcLE9OmK%T0Jq^b!!4w9^JWN^ts2*ek|vIckcy)Sz*`d2$qwZlQuXbhhCPNNAF0@ zq4&_`WfT_Xp9+kFQsoT|gvVKg(}t!gQvwYQ zQ=6)*PYJE-jjKH8^!9L3q4M3BG#AO3_=1~ z{yy?|P_vjx(NtFfQuGxn#UPj<4{0vrCC7f_<<@p`nN;4^MXr~RdHCCXzR4?C+x^h@ zXMgj4I>=>(t<;e#qp%<^wXi5ZBYV)G!ohxNM=w+(iaAU3_?(7WTup_0YX7Zabluqq^grus@fEp7^_&apM2xU|NCga33&@r zJG}SH;weWF$dmQ>Ys`M#eLoyhwEDqcUp&74)ZIWnE9`1+2$kaKNr_%mT3AHX%E&D& z%1XmKOv%QlWoHr%GxIa^ax4lkk9HZ0{S#rSS5AuQz{%y)+1^Vl(g$XFN_jO@6{$kJ zOT21kRYg;Ib&$Toc_8cVr<(aermQYE6&T0 zx~SiZFFO>jZOrPv>7K{Bf|OR+T#(OvfRt%@X{n?ivocciax({~r4)(#nZ`n*7B;MJ}9@(i&qp)Wt*b zB7siAOCfIsy@yGqjJQ}r7Nu(m@29@|`>9vf^(bC(=6=toK5#(^=z@GMgWuKzM18mC zf7)k0e{9i$i_br|_OKyu>0sDb5ORRf`MHDhb8~XDGstHt$jBO0l$tv@b5L$hb_RJa zgNky*DvSKOhxk5-3zZ4IvjXHXR8{*cnuKp&PETgc6o_L2!`p}}5|`4LqDA73)l-Ll ze)wG_*OVUK@tyhwZvcr_ST7R>#l&!D0tg23^Hau2*2nx}w-5!je)@wy)YosmqU8SD zpI!38lSkyCLta>9-@t?pV@{ko>dHk&4tV6_pI2aXR@eYkW*lO4JJAo}T-s4b%r} zrUW{XuM!ac4jDd^ejI3DE4`nhN!*+`J5kYGNW3JQ03mmJp<<^q_r7m*;jtwf8n+*Q z+N3kD0s^csY^dZzG;jp{A_S<3XTYD^*lBEic})XZ{~7*1WGU(cQ~SzMD|qPGg_(g1 zIIF6$k`Q5i4!jA7Fg5KMLNb>L=_mj2f{5XtewT9mV?T_zu-p8b2Tq!o1w>e3olPwX zM9i0n7{j5AhE65rbx^Qa7H)u(!)y^O;vd^Y-$U{rB_5V*st+_sBn9Uz{+Z?UeM50C z=I1<9q3I2~TqgFZ080_->Wq$=S!G)*K2I_+mT4f7qcwOlU8H`M+{54dilLWQC*APu zPdgq4lp{Y_1_0yW;3ZJ|;FOZ9i^+|g7N`$JdDGS)J-`h|N7q5FMPV``#XVjD-4@R`4C9Q7GpxX zV&G8B+d17E+Jg*Fvxx=~z5_iYqkxv}<3<6-3J_#M5`%-Wd0_Q}VH+!6T6y8{vko{Y zvuymX8+5Rqrb+~+7t@V|>0@i_t1BobMJ#=CqrtVE>;{GOrYI<;P*hL`FXZ$~zESEF z))=xNn7gig7Q&_j;D8s`9EG37HKPJE1J$K71NHUvi4BCBYA6z@u}pYtUvRXzFsqx8 z4C6<8I?FEx831uR4;%Z~ho$*X~SlqE=SP~M<8rp##C)Gqn* znx+E&DNXg{Y&7}<6;&v}j3KB`fp^Z99kM$fPsWD429+EYl9`O@r(QMK*Q0v$!p2KV zJ6C=s+5Em?F_3=;t823YNn^rLw$cTA+c1rdduo1O{CfA9NHK zJopg717n+*o67_rT7WdXH+F9O|4e8LVlN4zJ1%Scc;|wpM8PkZS^q!#q$= zdapy;tnu6VUnsun`3t*tdwcKYz@8O0$fz7A(ZES`LA-WkU`8#iFBvb7lu zmqvQiWNm#paV0NL!J^cQN?5s-3npyq0(y7Vz*FZR(>P-8rn;}T`Tk20HL=3dtw=L8 z+L3`NVwqYNfX^U8*W z&n+%qJn5O7)OUM#0hd`}8CHZ{&0JPjT|Pyu=g+LFZE6sRQwTFm>;Pyc@^6T{gp}3? z0e?klGMLcP@lW{GNoh1)uU|J;24Hkn<&AG!3_ zGagFMA3G2Tw!%pF1P>v`c=VLSSjN8nrWgvbrC5?Dl&bhQmNgLbrAUDW`!bwPTuS&> zo(nmj0?CG^r235YYUov}4{>3tudQM?-@sQ<5O4A#$@j8h@_HIM?Ty`&ytQ4tAc+ex z@I`ta2FF>(&GMJ&|2iYXw(;vtBjZ-%{GM^%gKyrIE{?*-EB&UI4Bl>(`q4OJ_gv`F zlYVBW78K^C7NupR=47VQ#*XxYyv(e;+=ATfoZQ^3fY5)2z(+o}`%Ff!N|x zl|*)$Ul@A~1P_EPnX3CL{JeCdsv0QXAC8XM#{IR?-6M>%oe+H;b4na?35+aV`Oe8N&!Tax&^g+7OEzazny86>k3d}pH@VyTS+A&nxw=d)7!EG`y2gVO0a z`9|HsQ`?XQF3DPKO6w`q;=Vt^qN zLvxdSJI)4%%pmDwat2~kKRmHh@fn$a6<+yF{k7AF-~Kt@$YnzAHh!S*FMRydCnN6Z zKXmbp$BlY#;sqr-7^~J`9X;tPKQALav#=;PBa7Cz^NP}o3es}vOX1lCMT2SgP;RbI zt8Zt~omM3roP5PsgC@m(4#vpEODCS)w|n)e>mSZvKH!3?Cy!Y56_AB|E`#6J1ITW; z>x-J(UGRPofIdE%SD0Iro>7#VH8`V?HX7un=BDM)_a}3U zax(|zS@d6kZV76`Aj;#Q%HoJ;yx1dBJP$FDX0>EVVhas2+4tZQkv}N>9Eq~ygTj8J z11#Q-N^Q>GE=mo7xQLb7_mdwV+VAIMhuu8>mGWPP{?d=TfF2o+mUz+1^i}t7nSRMj zg>!x_e7yF)!zLiIYlS&ILxIOmji*UXmuNwLznjk7EYKt}_#bERZ{7Jqs#r=0#zuWwsPu9Uc<{PNIYCxx{v62%!oce7p!-Yk)oRL8nQ{UQc z(MRJpFxh-Ez*(Vb#k@-;ZPWD4T_KjOfPe{e)B z%sDI1J^suOKNxxU=A$?F4D^4R2ZhS0hvLxf;?cpLGRos5h}nTI;{FT>X57{dSLlHo zryw6AIP|FR?@jNYk+o#8pvpoZ%M%9WB7NDo=E>0O;HEwOeSqGf(`?DXP zy;cW{Ou-^jA=Pdt`Gq6@QKvLe_|QM9YO)9*TGt@$>KLL!wW!0)Q3@J*b$&GJ7{kpQ zJMFxhA$OPDa@Jl$PtQH?HXUpSC>!GtT|0n&h-J2LE3Ed>MZEsW4dWc5;II;T#-{l@ zzxUIs-1RH3+`R4jP0yl`7q(65GrC`9`$J1Nt$TT=|D1NxGK|^^!>h>n5DoXCUlepQ z)+EeJ6AfaS0#nWETH5?cZ>(?L$^x_#0#<>Fo?=yIc%ZtvmiuC|hy?O7+Rodi&%Y?` zoYA-EP8@gs;8_o0v{u-tVD$!?M-X5Pt-jE+L$!W-8#O&Eq=IM1R?^A?Jp)65Fzi~U z5_PgDg;k9B3Bg2=McmnpB|Y>jMoW4acg}8m|MbV}z4Pz*G`i3)_u?Q`UzX#-QmF<{uCseGBIE^~tg z`udDJg2ix0Ru=9akQXdAz-`6<|rEO#OIq2th8FXVON@;q>G1I85C|Ieyh~>2MQ8(Wf&y3?> zR5<#!W2lg$Z###kff0M63+GSnHvF8-g3I3D^@99gzIuhbfbOsbzl5$YefQj~p$EKC zbZ_>gnSD{0FB$hWnhjiR%OoZa#3$uywPl^5>uU?O9*EzD*Y$%r=voL^mE8 zXl$yl@gIXtr^+-)D6K1D8;#tjK#RuMkai+jiaH!MOXF3NARj>xK)9#*A9wn?H1tuY*{LaKl~fW{j?Hb$h|+ zBlCr$RMqX_B30{1svZVP=;&e8Xsmuo4vARgmhIZ}&7Y4c8GYr^+kY_av!{RM{-Fin z9S8P)_Fgom{raqzhhNcmMyJ~zd2m}D%r^NGcRwlqD}7@8R3~$j3aY;f5fw~*FW)$& z^SM{tTYA-hF8TEJ7fRu0g%uU_ef~{7rrhw`FGE+qozt~Tga1Jt%r@;4mB(*9c*NkT zoSXi9^tua+uGntr1MLQl$nZLz+ZU`pBQI}5$?8Au?fgln-)_Wstgy&bB3iaZ+G6+Q z%(qngEC1Tfzp3ks!|s2gXWi=c|3$Rh6Y-w!f61N$XCIPx$!mY~Z?}KfN(|Nt!@G|8 zP^vlVh!tvs2};vNthgN%GBwNK;2HfKe$T0U#2=%x-Ztj@GfdTRNvjU zG>g(sf@G?VYYm-+)Gc&sp+%@LIs;y)cu-FD8Po5I=zQ8oQ@8$(;u{X=&Pqx&OP?-`#Q_vGo{?v;Pr zy!2mw`uXjUH}kW4zl^Z7p!m|vkH5OK_=2SA-41(T)eIf1rPcer)UQ46`sv&2J8m4Y z>D|AMNWOULN>J5}qR2wSpS^u`-s-QHVX#(MoYcE^K?428;f3*bedMu=*DM&4 zyK?c8+LXE#YteTrtfkdE1ZmZTJ;q~&wN>@*Jjsro;(yp}D*ZmNOb1&yN2h-b)#5mnzJQ$mh!y_@V{3Q6Bn#&kE-JkCvjMxE{pI-0I#>)83($n7gNM04L7?U0 zV&kTeJDr3Oa2yzbb^fe_>6zB*W5sS>uZvNjzfR*po-+eyBAbpPc?$lIWt7NuKKr53 zB-7E6wb!}%G()MDh*^8!@h4sN)zb3|<_tdY+=V$SZ{#kZN6gyG=sGPqwJC4h@X{;R zW$e3U-SBc9EC#F{yD1SqBlC6pUT;4-uGjEoUq8El#~UxJ$DrI;1o;@)UOT^cQTg@# z3)cR->WKHpzW0L;rf2!6r$lHP_}1D?7DsGXn)*i8?=wFAanxnk_DX)EaYiUOxpGjJH z{;G=ip80*`;*-~$^z8I4-vJ43Jc4}m{h-oz-5(v>e)Nsm2R{7#go3Yiu$FT4(I*^U z`dvTZ^d*Z9+^+D}Lr*^S-;eJ8Ir?pd#Xvc=+)_LS@b=hmeWUwVk6!e~!6yv4Xj?%b zE6lbO4~*I3OtD*JhqImk*hiz*T=V|u>%JeF^xdTQu@ll0k#a7-ZS_vcdloGJbpPes z-TPYyjL{0SEwZEX<}IFMVlEfV)s?7~{s~_Q42I)MN=Xm=w&RF6U!xR+w!`9hFD_uqJMf(Ly+Sd&Tu9zjAZh!^Ibz z*Qfuxe|FB&!E6iR=tG+sLDrG4`zD<*uxR-`FHOF8_2K(q1XkD&Ol-Jx-5r;08F_b~xAQg*xiX2nfF992Rvb`U)k#Zt zJNA8d%; zO;3AV2ir$0&)*x+knWjE215v@Zi-8D8O{r_5pNOn@v;|B&i$l1Z$-nc{RV9F-(64; zH|lO?mlS=s+7=y5KdFe{BN5OCRvSzp$MGO<2>#I#&)M>`ZUXtCfoUeMqd+cCahYn& zp&>*pko#`>Y_DC)ULSe!GxvSH`i;VQ+y&r+myYrM)W)}xADi*u!rUvr{NEqzZ$0xj z9V`X{2{fVU;9>4h5NNr$AR2+h0EX=H@|mBXI;&`u4z{zYG1ayB&WgXG zHuryI_a@dm5X}N1|IQ#2;hI9~N8#f*Sf1vNyD1{EzQc()@ z7YBHL+{-m@lzzN6_m&q%{Ohd~r>q0q%XqAj3XI1Ji-ECdx#$H3V2xDhU@aZJz*o(q ziC&=ZRxe2hYt87zHDIKPh^F@mr%8 zI#?ol;fqo9!p-BMhcY6cEWH*u?3vHH|7-Z=^LH!!XmaP9&Hf2TFQB%p(F+|c2KKMz zq8Au|HF}|gwRH62ZR%Gv(F^q5YFl(L{bVD4(F^DUt6e6LtJBriCK{;}!*|@QQb+Nl zHn1F8wrKm$M|L}^WWoBESFXD9=XPd6=849&gm>$g33M>qL<6fbE%HpW^~)gPI0g9_ zf%TbY9n3b}P@`(aXPVJd>od(dSY*l-%NJYCGtC&T^_gZJ%r+Il(qF4T(~NzY(LB?P z5n7*L*1>EO7Fxk*p=X+Rpq7GP*lG4-%@~~Z5oR6CHjDWiKGuwJTOVuI!EE!P0=hBy z_~_RDSTja!eS}#Dv(2fFrW@PtW6h}p(=yU>QgdjTIrxNg>ps>Dq*@EOwZvo1 zggK}9AXXKf7us5V!|KP>Jsj)_IxfLY+RM$or5Yk$g4=x7**`BFcVWr;7dEE;>tBx_ z&RsxvcnNMfT|Zl1x$4_a6GvUVZhi7|`?p)FgGJ`}P^I?0Rk7Q!+MjqKnhmQM*vvnk zd-Z{j9vr%I;|Wg|6t6f*2kQi0GmgLb!WZ>Y5NZp%VYM%ns%f{T_bKIT@;A22dA!G` zr{$t)+>GROET@c_O}TH}edgVzm&{Im{_gY*@6PVO!M|&iyBP-Yz$m5_4zGd%s*!2#h*02`PHPkVi!?3!9?Fbt*kx0u==f$ zSLGcv+`_^w6*}M3ntCJQ!J}>Y#Zgd+@ z?8GiM59xf+-9_x$$o7}l?zt88R~08$d_AS|PqQF%RC*=h-Maln2eU<`K``Hw z;=fYf#!q!JH>sfds}LT6AGzf7lk2bhy}c?aJW^4`XFqEkNK^>lt^ z$emjby}PXF!rihS9Pwy+kq#CEQH|Z2LN_v*|C{sE0grDyY2+1!)fodH-s2aH2bR=i zO#uoyFtEVWqvuS|KCF1vE!))%n7e3u9jue75@nL(yQYBJ4}RpCfN$R&3zn_-9e2z( ze_=C$r!n2uaB$HZQ-3bJpvN7%A2jW%)fkf%w!L;tRyQMd--YTmN73Nn ztgd3`Ke$Fr_BVPKRw=F9*F`BkM72b$l%82~@$d5w>OcJE-Opdv>B`@NdJ)KB|6w&< zZ+L3Nn;qv4%HR0rkAIz9zwd!MSPYa>%gvuL04oFOV5gb5Mk$}D8QWAFP%Ms+pv^4= zdgF?F)->*&U2tCK^G22U=iY@Ps*{YE?Pb zMc+O9>0mL?lr87RU;tKsR|nf#+!*xTs$q4omKI1T1*=6OkgI};-R!~j>o4b_A9OI! zJBr;Tnmjo^1vH7oBw`0;KY!A}mlb@Tdr8W6OS^uwrGUGD?y%V1NY{(c^gsK2#hoSR zU3qT)fZLLv(7|l0qi`$Z{>;wD)X1Nur3Wmy|FXQ>-v4~$anIZ_oj|U1MLtIG{hY@- zzPh0Iuv?BCdu{LBuB&t~+Zw4FRUB7JKcU{uoBQ6S-;BF_#05*Y44igRM^CTv#t7PP z`0BS|TPljr+I#0oBYJgsP6vxjMfRs!+s2oAZl|8ya`CJq3-?$vdd){G-(UCW4kGgA z<|ed+S#@1qSV*15!NTPz-*fhNk?*L?>Rm7W=dtHbd%onHk8b&}`qM8D<1U~(EZ=Zd zx?X?MSD(JUruc$xPo{sq!?SrR~ih8L+6_U z?#95#N8eN5nqE*cdtT|KyY07N{Z~sebuhhIjo&sVyz1=FvvX(tG-kuF+iSP`^xrf7 zwG#bx8_(7wkdxdZ0(CUZhz;(K8UOAu_y2n4E?j!fE9W%)ehB*e4-iN{ z4KreaJp7(JZ|L}0O3{jbOaB^n`}Nw5zBg0FR)N&PwkmwMBM@(0Rt4ORZY|-^y9Ta3ufH$f*JXyi z3ym>irF2KHCtoVP@6i#rUiV#Q_8EKcZdOV>!^U0*>-xJ676YZ!a)E0Mz{)5(*lFM# z<50>cYQ{Fz#tK38Ei|e#lRu;d#|$ckkfNvc z_uHtTb=3~7xQ(qPgflkl?-#huwy{|`V&Ob&S=oN;R&*Y*@t&7IKW@l||C))=9*Cm@ zIJK_7>tHbu&X!w$#{jIW9Xi;~z<_Zm;f&w1D0X*QSBKEN8v`RBeYdW^>tLkbgGY?U z13qcq`a3#mm0BHaM6l~Ezy7Y%bm)#3!dTbZF*YkquQlVxXm?N_{=Vz)u5L_Jn)0`6 zj=!f{!+V8`ZW#F8P5b|Jrdd<68w26BuD|PGG0>DP=f+?F*7bKCY-@32(08kb)xla? zAfXhj7KuQv3Z^Aoml%Camh`0K%6wEGlYPK+TsL^9r8BVKwxV zkIp~suA0&Li`SMvpS7y<3p!W~1hVBmCaYWG)xq=(Y@Juc?qjmLW%Rbdz_D3+uG`;* zfnTI?M(lfEamc(`?GAkA|Jl0=fGD2tf7oJz-G_=8eDP>7xuioeF%b_$DG56O1yNBc zF%T6jFi{Ld5$sm%7VN;lZvD?YcHmfM5AHnB@BjN7J$Cmqvor6#dGAf`uKljw9ljn3 zJz_;_QJ|HkFFlhmB`9#kEj^RE9we-yC)P>bveFb7;(>Ph(lZGo8)W$|JnvHOS|7Cya+_+O=t}h_H#%>z^K)&s=f}Hc4{COKx@cFJ;g?AXBXmia5=>Qj6YFh}OarQ@xedA1$Tmrs z`~K9_F519UX`>oo%;EYB&5VShW^rnw-FtLLib$9$RcDZA(QvbTC)PnvBD;QlYkaVw zU2t9%pK~#JFn?4go^I{ztdo88rTvy@WpmBkO9SO?m_b$_DqZFw{|+VR=wuDaokNEq2I384rSq*c8Dq5d*Pp!hP+*TJ`^ z?ei^PgkMVJ1*z_`O}ypw74ny*f6OjQdD~HC_8H~?g(zSNFghYm2N`_^jv|g|Kw3ij z?1cCB$@@AFS(w&&1||W>!J|-+sb^c8AIE=A;D%&A*f@Ok2D z0coI6hzCC0R9I_L*=?;>I#jcZ$yIGg!pMeNzWs+KU89!;@1D+^ z8)q93eEX&Y&?}1{<(Z(&9pTM@1{ARVGn!x^YMvR#o%NB)$lVv1-F(5UT`l=Gp-U%N z?~SXLj!6J=>cJlr;CaSli+*xCzu3eJ)_uHx+;q3IB*-l8)>YjU_ zgq7F}4mX$ME^s46c;xfYKDJA%*hK#`K)+TympDn0P%NA<1fV^A`i*)yW*5;j?2}(P zPU(A^gek#ADsOxZPkCQBSE+&c>^`fQ=lt9*>H9^%Us)>8@0`ornllCn_hp-USTs+& z3Cz;58d4G~QbziG!9IE3yb0t%h>_=8sRg$bC|zD7FSof}IQC1H*PGSO=cW(PO~>GU zTsRcbO@Glq0FWS+ve0$lTk%mk4aSPHMF=DyCBlh;##b(9cVPREE#x$gf*4Qgqf)m@+uLTDNJNph@bO3He>nHIuJ)fClCSg zbc};DM8_iH74AF$SwOhIZ-BVy1eZ}%07A;bLDIR<+phu>YXIP8}<*$U{V#-2yc7EH` z-Y?mr&{K&opLrwAFc^vlw}(gF`uBEMYKRPUSS-}j8tSWllG>`)Y=(q z1nE|NW@b%tT7$h5$P6!{LBI3fdiw({wlp^`za?Ag^77_s-Bm{Viwue*Zzc}{*| z16=K-l^oCB??FV4O13`!p#5-OD5u(;&+}azNmz*yJ9LlA{Tc2^RLI2v2lHFM;t4jk z>X|eBRgR>EgKDCx3Z0b?-yZd%1{<9?He} zVtNaY(h5`vGMp6kpiqLca6IF{dtcwX%Wd~+Itr_Eoj;4jAq3<<52p80F#a!sa?y7K@j`Ic89K4KrwPvWKhLv);RGSK9Zk zamzRK8ZaSCBFgh?BPb)0+pmqvJ94^nSf=IP274ZTzcF#vZ}_zcfEYv36EFy6@h8yVrL3cC}0chIrth(XNmA>uT(` z%DnxnPrYc@H6*NJn;^=HSW6Ifp_Z|R4`0MuMyQNy*(+nMCr}OXI1OCW190%z@7RK> znf0t@)r-6yR#SHg3Byf@q=$w2E-Y(3J7jt+I4yW#i1g{-+%y4qZc7>jSxs9cnjbIB zi``vSM8c{|iA~Zpbw)QLF9B{w()_48`aAoh=ExR240Gd)E#yd9=SxsAg~6M;Nv_5h zxLG@V_3P(7$8tAnKbx9vZxKqu=F&Orj&8I^{@+GKfo6?p?|2|{Wljbf7{#pq+wT!s6B#^Y9R@osK3zWp6zvbncx^u588+| z60Ye1I9R&hrQw3*Y4$6gE`EIVynR&?_GjAOSVW!HJcp{OMi(uXck)!-cIW`=1(%Jh zcMi|Le;fbWI{xsg8ucgdYYbe`vHw49FK~Bl%s+QxJ{a*=2Q(OYQm5;jLl86@pM zZ*(KHOi~6K8h+>B)*?d{I(kKtu>U{K4!Cpdac$oC zu2#a0!-VU9RCRey!v4Jj4dCYUE8kNO9SyA4t?U(i{esU!680||Xn=aZbXlf14Zd-@B-3ZoYd-*#93_4%{^`Jom=2TAoF^v5VTLrgqav*uSjb3JPG;C+KrZCeLik zm2;6J=Y(;g!euGIEl7X@UQxvhql{~`Zjc)aGpQrCcowY5FfQPprcz^HftLwNtSRKy zl;N%%P+@ZKi%#no9BeqCt=Zl?*_MsI{qV;m026qDc|SV;Ct~}gQ>pQmA$0@ILsw|9 zNmvPY1(}rNcZ4sZ+)q8T8N1O^o44=S$(*5M_ehqyWz{H91hfwwRy+96$~HE0>`w|G zrgyAQ!b&(QxVaowR;k)&WuWW_CtcV-@X#o;Rh8pk*Lt=MhJrFR6ykw)Pq8|`@n9{h zsGS?Mnr`%SC1LWpD9E|Qn~s&YwD=h1F5ay3$fxNBo83Ok9K!8VK0t|Osk|}czHIiN zvPzij)8yo*dxGP@EFEivrHbMtqiV>U1uy7>Ag$&F2=$jS0>z`HKvsay1hnq#?d$CC z=kM(6FIfgExo;O|Sr%rj>KGlYS@{Gvey`3G3)9}lzziK@6PXcG#W2#$7=dnty#g{{ z&O~KirV3d%WXA34z2nR_{c_vlx1~-e;DL_)z!{=rQH3b{P$A?7d!0DcU$}-)lr_32 zi~I+Cf6sb5tS@ivam`~ltsgxA+Ub}sYF>gil z?wABnC)~o4JUr$MPh01Ece7Q;Bg2Qz8g`h3RdfprvkzL` zO_w$owlQ1ao1-o?>S|q9el$PX`W`j4;U_guNf!9NR|S6P*k9GcK1Bp-glJ68Y?5OZ zo6=YH`K&Yd%F1kuc#98@mu2xH8;7mR-ruS&FiXeEp@jvP;^*S*QSjh};_WJss~0}* zz=8VO!WHZjeKW)Tx{xq(yP6GBLP6S63lQorV+4vvJ#-dFM~?$RvQWj*;S}Cn)OH}K zEJmcBePB@T?hU-fhx^psX?A}aFhR$D6lO2o`u*9473gexaM*X_cInoMLCYL(Z`$#R zg#En@D^OeSnD5^HP2B{$SJ)UGnljXtgps!z|9@&&Wj@lofKone5{wP2Piaf~at~d9 z4I6WPLdRY z$6;)DqQ%xa*?w#qw!D>^1$BijY# z@OOOV=?J2vZrHPUR(}oaMG?UZn|r8jDyv-TJ~6JH9+_vgHlWVw+KC_Qfn4ZVZD>%a zAZcjA&;{D=Qm|cx@8Rs{M_5LdiY?krt*Y8@jF{~Lx3Ilk+5YIlU9#B}?IU$T+e-b# z+HB7NKY#WpG25B#7ci4Fpw0T*QzYdy7peVyunxV*oG+xD)s~rVd4g2Tz zSM}}vZFi~MtC?+O=0(EP>GFf6J|sW16pyxL)Azg;h{$`Q7uumKEh25UyDQr(z{A7G z-&Y$X=Y{^b`@ueLBttZ5fHteMC%Suhfb3x3w=>$Ei{$GKrGT6$DzKcC{3J2F3|!p0 zM?a&SEN)Cn<{g!1^~>w+vW^l( z!+HUZZP;0hzG3|!{?0^VIpCw=HEU^-d7O%x@D7`MlZ2IMh9%-W zK?xNzN0%{kN4-Q!s)vkgs|PxXQ@M2bocSxOb<+p#{8`x(_KwP|QHTfHN9_v`224FK z+<154+s9hNPm!<^4KUnXj!o}3l>Mswa8`bgueMuKhlX6qT~?+h25A5Gc(PNy*7t3~ z=Y5{JEuPnsgvryGRh&TTD7h0?o-R5$=Kid+-4kv8-o^v=re0Wk z7ScjIP9t@1;VCedwl3%AfJ-8?#5tX!dd<#sBVkpgGASb2k;(^?omy6YAUEiL*#-EH z@}n{-I22`$u4PfLJZA!}FXM7lHpMt*kAwTt$|nQrp{yeIGDXkal=S;68P` zu9yT66wH`H$U?u5(GwjOeqF;~mD2H&q0ZO(B&?#H$wx%jC_C}?$nV*rO@~dUw0apJ z86C;0QFM!hAJ#>Uo(&uqaMP~O*fJs@TL}Env5Iykxrl_@+3c8?iN|e{++M6d&%P(o z+p_Rusy%nJyKt;U^3Gh5a6-qdER+`=Q-U*5s1u1ukpzj^QL4`3pp%+i>N`!mGRQ2m zQ+1QgH65Y*EYlnzJ`4TMBYRKz$kP5*>W0d58{s0ftcMj%+T-hzmxVoxVOr3K(NLbxJ#8;Ky;Hzab-6y}k zgmOb!n`^yke&Gu6MaSy>A-)bHS`zafdarO^YL;+N@F1j5pXUfFQ+-{izUV@mfmcMk z$8NY^Ii&A5;ERrtohSX>XZ~gFSpsR`zd71$_+RWC;vf{$$3qCXdia57Z!YZoo2{b_{3*E0p&zJUJHpgz$&4VUYu6zsu zy6Kn_nlVsU2~GHqv|eD0=~&F{^bl7~#`uXHPn-~LdjCd!v0)EbU@sH1J*{7wKlyrJ zH0RBR=T!zj<^Z#FOuFY-L^9p2DCd=mXr@RKfDV!?0esTh^D}zHwBrYdHM*;^{cH~0 z0Ql3Hd52n#_UCVUy?eQOziO?SDjRSJq%ufYC42+AM@7=1t%J_2-Tgep{*EqV#GcNq z76@?k@o@KdaQ71Vx{JL=drY=-_7sb}eLbE1Sz0K1DvA=g`}ue{OJb(8r#kq0`+L(+ z$*v=Kacps1(hoo=3L5BVWWW^|^F)RQMnVzK)X3C8#MKoFO!T>i`X*dsQ?BIIvuKLB zURlBpe|#0ECL%+=E>9%n>nUFOd;_789*?JIAmZ?JO$B^?10#WvfTyp|7l`z@Mn;m# z@9Pau>@!vV1bMt#nK~EJb*D%r)4c-ZLC2uOiNoJv;ao#KV}XH4&%o5w$XLkZqiQh_ z>YEw~jZICAgl>ROt>Uq&AxSt&Hp@sv z@>}K&dFg7Sf0A4a&aj}1ni2|;r*{ANcsz_E8n@bv9ZEA<?uf#? zm$Y+>ZW)M40E%$?2}6x&R%P=xFSYqQk}qq1p353d!j#aFskretFc7NwKH%}R_4Z4i zuv-{v@?Vg!x>Dv`q-8BAR9ajhvckX^fl^dLKjM~PT+}C&pl|BT>chM}?c=8XYLGt6 zfQ_J1J7GXSeN0Hg$YZ~r2-{_a0LCLo{aj$v5v4^&i*1^*h;-636sw_O;E0kJ5qRPiQLB*^#j>9 z?xRXR*I!~et>WIj35?UXuaK}31~3HOSjrB75{#;<7Ti&=H=^V#@d~$65ElHE_gYWb z;6UGfgdmx|q9SZc<^=R*n)_sI@N!YY2~^?|Pnww_Hs#(5aOmN=t*09FG|xEsFt*0L z_^Z-3RT%__!cp|&^)c?_HnR_R{v^v!%TfVMS@5T*!7Km90k$@EXtJxitIj~)`Yu}yTGi?UJwBO=4DrCjs#$qc z7q<=&?fkrILB@o7HYBWs&BM(q<!umF2HVBdSbxF1dcNfS+o08yW&P1kVkfw_);E5>^g2SPK=c%R$RPO4#)eB-2%z zgtbHIaHvRELNb4#Lzc-wJn%3@;PoY8tOa-7okNC)U!6Na!v2&iX@D5WwN5v zAxr|0gZ<2LblxiIg3~+o0NeO#Yrl7&++_v{`&aoHU?BNc*s7;uk%>K*LhLNy8 z-*I^x7xYPUaBZ)|f<;fC0B>!7pzuG_pv2vp?mV z>a*=6>`zRjHAL0iFwS6?=8W2w3$hMHy#Kl7c48q({;Q4i z2~davwhYBO0%j-&xjOq&Dp*;|NbY-K>mt=$?dA< zBk1o|WVr^%jIE49J=!B9rZ0QD*xNFsSMAlox`WPRTLPKk9xceU$;$*$jn~&<&hyCc%T~q2`h&l zZJB!NBxeD1mxZs6PDb&z=VPn_@45Bd{=5<&_@ZNf%JG~=v_#wWD?d}fYvR>xjb$hkHN5cM;BWZ*@;V$(B&D8X2*@o{4G;hSo z`3g+Ra*%)Ak%R+z^?c{QLAp2{x1Y`8o%q|qCLeElK|x4aq<~-e(s^}y~Uj! z>{iEE@Y9W(Zz5s;DlY>J^q#(F>K@Z!_EB^1et#A9>H!J+Q(mSq(pW{--++gJ$M4qb zkJGk^51cb6X3bUs3Hwt93eVf_^gJofv;8Fu{g5%xZO+hi1pVCxTIQBsq4l@SjbCf0 zMyj-i zIiN=c7-uWYP=(7qq6lSu{}rUYhkM{wX#YFb+&a>J22Z=4L-K3te(I;&n%0)FTPMux z$a&R`gvsY;{-yiZfbpZr8?>f&I?3PsW!L_#0X+jqSP298_wHXik63P5PxnMxr6;x< z>aOco$+r#%K{9RX79>C+hjKWgf=lMlQZv5tTuJYZt1rWH6nf6@^YF&;^VavZ2^^y8 z*(ty+QQC5Azc&sZ?OVKXLbVse zdWlxWa9VYpu>p)yX1zi@aNsGd{CH@K472FGW=H%@=S=X#b41hYr`$C`MPu>Zh&BDs9#ymdAEEVC|OyQa7zB-JrnBMS>6kOI_vMhH4bYG@F)q=tpvuHjuJJ)oiK1~*GIeCnY0;h85}3LZScyWKiv2k z{nRtw{NuQK4S(CAsqb?9j$8*^mPD1;I4kD`P%$z|e)uJOPR z9h0v?hLTGxk}B^7P$Z`WC6=Y~oF2?*>N$FZZNx^i+~lA=Hoz<$tLPU%fo2YKYZbG< z@le71^Qm4h@9Tb-#gAmA8YKYQZTtU=z9ub5+2CIv9g)U(l@jHCDZimV6D{&Xs`e&^GEuv6p7zhT{S%wQt`aL*n~d33h!DB-}jT^dSk0pvVXI3OhI|32=!5QQ%Mn z=9*553^Rl|k1 zqK?@weHEc?nmb|x5MLTU3TV_hNZo}u4jub<5)W@cF@wdnEV(VlkjIRlw>5AncL>&Ay%x{tN9+R=UY z_S?O-uOwj=ZJTL`F3lz(ZWpe4n#~zfyV0U;Pg8+j&=N{h;Ic#j+UH(cr+z%Dq4id^ z>reIG{n964Smz;wim=VMsGP?qkM5rAf7C9i*JGog%hg9i*|PY7I85gsvCWN?q#129 zUrmN>W-Ib+L5-1y_1PicggZ5Rr44IgKaOOZmyuj5RoT?ySJoukWtSWCTOSUvAz=#I zCgz$>3RD+8NK^;wBlKwZ?KpB=yk@_7RIO@G@lLNLQwy@{7%~LfCoVlWxUo|^?xI6p zH6t}TB$BX-wm}m_xLB;ZQRMMe5ZJ%n+>`d*vVh*Q@KZ<2@Qud6Mf?rIovSk1cMT1s6q$UD8bhu0*4^7Ru%ac*S)75gW_`3Q%CQ!I9s8{bKC9 znxW0k2MaP_m1tQZXSo+TCj@N`o^Wc1#f~>CvW|FPEcQxp+=0A;G9J*V#kfIZFIa5s zKK|S2jtAzEFa-@1i-b-J#b>cYpX@tA)vPO^_W=}a9giz}MLgeMZKW?SsB#!bV@&85Q8} zF`Dff;N=4EaC1g)ES#((zwJ~B?b*D+>UfKV*N^9{X(adu3k(RwwsRGoKfg8JzvYwK zwy6=0Pc6L-dXq2(YzHb4*A*05T9C_jfX1Zo;iI<&rV8U84w~=uaMTPER#CQtnFD2@ z6aquVj>o90a<-9O6b-n_nQdeOm9m7phH1b5xB<0Z(|1s(+Bx={NBz_|mG!IG$6N8N z$~7dW{Hu%CU9EkC8$9P?(vR5*mLyC;1Hu(qOoLk@*3gwKZq&d6+@MLZz%wA{f}RXOF(M<;&*6C%jc9e@iaq zmzXdCH&#sG*rtks3>U@j1d61nDsf8%`G!3d4VVuFe8Xy##edv4OdQZ5y~CV)!g(P_ zCtZwBJ&UCYwTgWMtmtlyStJBG7o)%b;)e zO?BJZX&UJ!p_%7M*x%+GfJV!djl1$LkG0vKc|c>`mHApEY@L*VB!X#(0D;)U-Gn3d<>L)u3)@PaW|(mgM2ouN1hDN<5!Bwxy1Yqq)Ei zGGMh5{793P5?ej~09RySuuJeY>%z z7F|rQql&)q=o8>SqTri_D6-gfQM`+{r;oQ6%A_bsA%s%7`;GAN^`0~ta;Hx5$2ChI zae|PKI%QAdKJwrt`fcs3UFmIo61I;LG03P;@G43byd+y(OdKkwkL~cD9@jtFM{QaW zcy^pFylG0(TH-WtO%Fi(s_{XO>-3n)+r2kJWO?1^5edWg0rwa3!zw?1ZliLH!_<>E zJAD@HtEw%``8aJilnuO;#?S1%hDkvWrU{q&z4N&D#`HY!L&vb4L!K4E&kj@$=f*gJ z?et*#SoQXysV~9}q3p8o^K%Y&;zx(Q=G(oiKfdQVWexB{$FRSGJS&2qji?+&`B~C6 zdRg%9>AbmdwgJJnZ#n=!bPW65isA?QVrVD!npAdMtCbGb>|%0Np-GcjKlFx0vgV5J zDQsAva;H$*%GiM9H;~bQIG`NlZa}J>6ozZ^!bFR|U6>wpJ+Gm(H^&{TnY$P-=F*PtiZ*&)m^hG>9T^`?vtIy+^8VU>qMv@Oycm;TlC}-C&II| z+YH+;dJ~nQHQr+5hg+t}*@IU|n<3tc2^&)BW=O*RGBY&g=nD1p4fPE4^^EuiJbhEH z5PEZZ`T_%?fuYER<%0MsFhTUD>CxgzMLjXeWl>+vf3lCbC=#wvT#?CAeZAs#jKQ*- zGl-OK&Lpg&&3P@NOA&Jh+Wi~<9279HhhX*PHyH;KRB}idwgQyALd>~2vNDll^VWx4 zms>>NyYMmJj=KxW2IHd)5H3pup#5xKdg^OyEz78QmAOZQ#WzV9wr0qnB6>NAsGP@R zyk`hKZdkA2b@hxr+h`w@T^4?>-8;WkSO2iprZa{ERF~F12K>-5?3EzTB7Vxx-A1Ew zT$ilL96qd{V3yvH8qd3E+df$U~fjyTn1=T&yB zU`xuDofo~_dO+D_;ire^y=pp~hpUS0Rd7qHw@a}Q?3vSw3Z@gYFw&%{_Rcp{P=tl z!Rl8Xrs%!@ZC%Oy9bIXK#nKF&l+YDw+3$ZULrJMjDszTP+y-k17BSjQQXBe*5U`eZ zD!62RaRB4?p5avXSW}ItoJx9A#%>x1IS9sm4GO)%ul|7Mwcjqxu!z3y>NBp>+b@_n zppBxJbqAd%er>;c$Hy_;DAQe4&%F7*m4sE)su`vWzGHNDKxOrn*-b@p?>s-Ay)yGY zrW>b$YkDY$cCUg|XvFWUkj%#b6jCAob_4HrGz}HRq#fV;`RaRrX%(WHRTk;h4kWCi zRS5R{(_e@I>H>0>r9wdah`!yHyyYbd*9^Jc&$ElV5ecgI9|$_28|k~@*3leu(aTqgMJskU41@5t({rsDC+^$z9sb`VHKsdKobO6 zwF&X`I{33~R$CcuRzB*($z65h)#5sDh?3rIJrm+x3k$XFB6a3 zB)Pp>2Q!(lH}q91xe?OQ%R1D5yes!Tu8E&*ao`J0_>tj&jyYQxM|mC zY#9-dEd;2vD$PY{EW&EtRo}3mn{o42+nPyn^GVnoTo6iM#1Gx9&lU}YRFbZ**j;%tm%7gyJ%r! z3%3Oa^5;rx9#-~yNTBpL8j!GxR`zT}7yS(o2$HEB5D&Cpx-nq#;!bVsHf~a{AuyV} znuN_&fz$le|0TbQIW|H+>7a?E4%!ZF^&xK z8_Dpp(5*WNgrZPlkOKtD?Sen8aW`BTzl*zL#-Sg-?9|6dYbBmexQ{aavZu>&{UjZJ z!tR}JT$A}0B&?#fvOD6qVY=Ft8=KbK1+jux*fk23JSG8xfFDx;+Vd%1w&V zbFW;x&Q20mQN007YY%kNbMf&u<4*RpSTH%$xBb1JV&I`H`~+RwmVI#TTC1=`!O1=WEB0~`7hJ3M8zW{H>;_}IND@r; z4q#7oMqmF$kAR?Uh<^UQ0WSXNwI@`rSbMH1(htV7DMbzdfWwwv}>Ith7#|?QAjb0QG==LicV&D z_x@Dt^k>nQ*-be0C)i(sgtElI=yX@3Ik*W_&L#Y6BNQRGUw!?#+PqQQ2H7X>RdeA! zT#|(?14yg{e^7XUelx2#?GwNk+Q$!T9#+LQ#)^a;kfbj7Q)C>D6pC}+u87UyoQr8# z0z#9zCg@sct+Ls>_E@9*l_MptLzZYEBcNU2dhYe}Sp%%29|c^0F*2ww2`gcXaI?xS zO8%H10E-32h+?SR5V(tP5X@rQ_9`nzFBSVcF%r1$2OCR&IG+CzJ{*c&Nwut{Xi zG8@UNW=q0CAu$z-R9`aCLa4N)a4N}>F#^RxuLd<+;-V9LYcZu+jL^;hB$=m7bmz|+ zn;-K0Y9*Mcm#H)mFX1A2^7X(c_ig81Ncuc5?)^6shC3IMr5E&2GX4S+nY?m*i`pYp zc5u+0*jGcAT1CXX)^UrfF#s5mWgQR?O#QUC?-06g32$4@GwVU8cZ?%psgM{2I-}JP zMtYl8IkK9Jikv^2edgTgowf_(7Vx#V)jI~H$zqk!5jY@QXLJN+ia3H+KW4@pP;;;HH(prq$3{q{8u*Sd>6^aZ;`O_hoE|Q8v@gVYdfk$dw4%IT>(zo9#EpCgo zfHR3)q9rIEc+y(soPK>|qRpyFx;<3mdo3hkkt9CLQH6FPx{fXS$?5!J6E9fz@&0ks z-QX?tjQl_xqY4Gql%YaCRTXGUqMff*T659bcB|2|Cy^;p%SbBp7?G4zyFv47oNHDQ z@9J&4&@9r9gppN&-W7aoNmaYJB^GD1Jl^yosn8Qd z(uB_WeGSH&@}du}J(cp~!ax#M(JJ&7(Usv<{pF+A_bigsw$5xc)_ov|k4dyxg@AT( zXl%9juhs~n!tM;WKG$>r39D!oT7u}(57_U0$$y+^-VgDxyJvcS0Dkh(&wSg?2k<&3v`CM_0g^_=Gq{6*8-(Y%km6(U&&39D!oB6$v3 zqQxo%v}=TDOwMeQV-}mzSM~X!MsyGwSDbtHIWtoZi`7Qkg$qY6;k!e5-nC0pj|u3 zZtyPMtCmUeHM-l^o}o*^Dq2-)Af4C~{A`ltvkzwLYqyHO5qqtZEPf!4QB_z?WT;BE zB7K|7jYpmt#abeWRNZ%7Z_=JYBvpZ}roJPO?myu#;;!=T&_H$Jxz8kwtSZXyPGXM% zZT_Vx>g>!G+2;F>Y51&9eG7Y^8Lbej3ec`GQggV?{VO(!Q7VT*d)%-mVPsV)KYl<} zeyIKw`i@;;yCY}r@b8&t74HUT%UZ6@LK2X>0qT2qo#m+quWr-Gh z8=(F8he~&ssSdFZooF*W&3WJv5=K^`^5dsEGPX(2UpEwuY0savq{RpBp=20`E2b(V zW%XgQP_A-&o3`8Ma?*x}@wacA)~wfcVLVAyNZL-q$f}}z>%dJH8G}-b2F3s#1RZtUz>itN+2~vfpNI{L%@}rcM;u$l?d$7`=*59T{FF zP?3?5-|IW!%X5d?CZ)cg*LgIz2T4_4Ba)h4Ww+y2smfn|>*I=%7O&VOjI1gNz!3`4 zITs-4FE}o_U!;#!1$LB$KB#qlviSk)4c0f-Y%(?_)a2)-Q<9 z+iN%1xxdq?t0au9D&@xyc$F^;Z-!rTzH6~6{?zV{e(iFBb|!uxj!{+WHrF==zc{vI>=-b-qJ%X-8$6#%Xu74BxC7=MZ{64aCP} zomhoPUW$Z~RjB;#*9eZE@WdkOiRHuW*y{~QssfW6^zZzTFtVzYpA~|t(7%fW z;ww#6NGstajI1i<#}BNH(RU8W;s@dwRmGu&46jmOk&#hy)oxC1CjzV&Yt>mgcWcaU zlB&S+snLqZ?AH9f=6gN1Hyr=skPZnWtI7%B2nFe!3lQorV??o31sBDp?7kt)VeMuO z4>OT)l@aH=D8YL+Br8o-fc9FMKi_y%4l!Sl=(O2mW$*+NMpl*bu&7hg?s+Of|`NK%zgh@@jG4K9AwY-YBnU+3MCXZLj^ zVPsV)zo{(nDiIs!I%QvKXtl1+c=v%j2r*0+j0b$D6kB8W9LE*s9Ve|{td>`<5+IssXPuMLCHTf_r$;cWGx4AnZa9X*{LQOGN%ak!+^cQp-a?$IW{lksiE$6MK+!*VU zLc%1|!PGqxJ(Qf6Uq2-t<=@r5N~^>Ds@f%+{B!W!Z~-h=F{-zO9P|fhUw*-U{H7sc z!iYy&eJ6i^(U^pR`QT8*kMcbvE&~*fQaTJ3I|7ZNLsD$h+YK|D<+|ee@VXlZlCZu~ znUp8pxENS;nV~Yyf2p;l^@i9l8PgMvS^(i?;pa%+K-Pe&|>= zB0tL4D_jil6CP^y_RxaCw(A#%-3$EL#ks8fRJMDQIIYz>+dWlRpB^!UI}`Y!V?mG^ z70R=Ia2dcC{Fs9BT>fsQyZrt?ZY`@4yTL4Zoo|~C4&s~G-=(wJ+Es6a6 zR?SM~C^?mg3F}2Obn?EZw$4tS`o#(qFy#4jyp0e;0;5YKf znje~$YZ}&?vY>yJgTN0RYb`}U5kJarcyKXgp?h5a$L+_ocd`p_S!=A}`ljJPHysPY z8KPqm-O9InTuf?&1JZJh2BMXsp|c^R)VLNn^w;rq1J229%*~PE+1w$ zR^^AY@_T%>-I6*qW>)*e3w-JKvUzywWfE2nV~WbCXj4=4rzjK} zH$Y_(`DRhaF~RYlM6*mUyvv@WdlyKjV-?*h9YQpG*{tVr^j5Il8qN63h#x;C4M|z} zp-=b$KXk03hYv7$NS`1@P?_q7zAOU#(6J!Mj0%McP>#bp(vlGrUt0YzcA9;D%J^t` z{Td2vDcy4Ka`NN;FYH%lR9SxO?qd&W4~pkgzax_SsYNbr{z}_y&(=5TUv3ZTK*ETf zOcfy|aOs>u0EIz1q7Y;_k<=Q#yD~Z%yf@g{SLdr`$X&7Xtwr_D!hNNQ7igccrPsw3 zl~&t@+b&G@vJtE&VQ5okVJK7=7Kxw2WN;#jA83?FuQP!Aii$uNvUp%rkCC#}BSI1N zIJ#ui(ZnMogwdVe^)u*fZ7j_jR*xTu-1)V?eC`r{&3ug)JD}aBgNH~MSv|6Wbrhs? z#uZ#5LITM6Q%pT7K{m;pU!K=C3D>RL@JjglCoGT46fML9?K6glo9fPI+pp32I_=KG z-nB>=Sv^SnK(2*Bn$bd#Izb!rSR5Ta4g|>uF*!P%!Zg3HA8fXa4%kRV7SreC=xXPE zUurWu(WKA9;4=bg=CFSJL>x?N9nh`%VS&}`5i=${KX)yZgpu`wKF1I5hEkDM5a}Wd zWra?p(_p-qeqYOzEXkaH_>tyfL50UJ1&877Sxv+wOXyv@9*M_aN+$`2Z zWy6_|wR-_`bZivG6Wj##A_*!!6e^u_W%^vqc;tr)@4zSMD1DO@O!O#{3@{0iD7D4o zH;o4$d2bcja@J;@k-A$BfX@WT7@@m2@-Mc66zu=_-H_)+4*kho3iwclcN0LHhF(XX{oENJc=xVlhbZ6J_w6+ZS zM^8)tS`8Sil!>1$JBA#ZHKMC^TG-xtm)i851UJz!D}>=t#LsV44sNk$)5ANASf_F;Px#u*>1 zoW)&vy-mdSyXUt`>m~MK(8u$myw_6a$~(J2Y-rMU5$j1eHe+?VkJq7 zw^#(a0IlUve{o!q1tO#@wENcV+R&tCCA-W~qw8x$mGd z_jl{!Lefc_Bah$PY(CMoip8D==Ye)QM)p=kU~X_r;cd4pdgr2 z$E{Sv9qI_Jza-Q~e|g^C9_#_m9sy!M)^;kv2kPiYr7JpUpi@0QN8i{8eXCZVW2h@M z(&Oq1j15fDcccw;jr4d%rYtT>B=YuUkAkdSyghxK{oO}-h}q~1i_V^6^kQK@ZOI#j zCGmwI+dseuC7K{1MTa$&DzpSwlAV7jvJaJjE@l?16GMy&%a$RA#rlUa#+Wh2gds+S z)txa0PiE7pF8VYdV|BKqYK2~%Xg^~?fWe3*dU?0L9z%Tw;FN_&s80hUJrh%biO4`C zFhw<}ClI0!g!8yOk-niW&(Oq>1qu!I>F(vPuP3WQ3ljXVMkV4cLycad>Op3WiaflX z{h2J3T%*Q(WRS-CA_E?W&o?y^3He-ouAZrhiHU$?U}~gWSfj3zyk(eZK?0@MsLKCW z8@;FE^$torseb9>GXOdOY;Ka1PIa&*O@CCZ;BO`h0zTQ)3>_ zkjF9Nb0xFeV5I1s{gAngI;B!5y;A?*I$n6r3j*Ct8yCO@p93v0)4tB0K5PeXrifZlpArpe z0VpZ;e+_5hm$eJ~K{F|V%d9`Xw+9lk{Po8X>FF7X^ik(WzyVV<)Dv;_47j=?15_;83TI|Mz z?4eHNo($;by*uhw(mmT92jbdX;@3!&9`WHh71Cq}34^H$912IVCH9d?&s|DVOBac6 zXF3)eWWs2YJ`n^rg6t{z6Y~Mu>1%T&40mk`Gtoe2JVOqb%P}@Z-(oj373msrOgSQs z0WufVNzpUZ<+Gp%Ti{5k9;`DBQ04miI!}gAl1P&Isj3#aMj1_Bf$wB$@mSdumD18I zCIsmSDlOxon3ze+scppn=YKLev{E6bcOV3=M=Pd=n#GBYlyni3s&LMM9oX#G)@~jf9(u z7PyK=j{u6Dy{PfyZw)kv`-+Q+K)PNF`C&LIHhP4;&Gh}-aGxw8pQbX#i+zPV?wX>A zj=CJQF;x=So!K27-Mu>Y>(r?eOS0CF;!E~UEvW2D7!^!YM^S8+h-`o&nlMGGqX@?7 z99AnCTQ=bB90w_a+ECdS&Jw|i+*3FM4qq9_>X#|nrnS3!tSzi;Dm z4epy-@3_+@?MT>uiXtFTQjKt;LYiBEh{7OUWFg3KQcOEQ64Q4M=7e0?EzG#O zs#Z*$b35QZAVZ22fQkj6{Y03?qi$?H(VCnZt*%{DZ%o1-NM%yU4{@%fSoR9@18;vh zzRB{+^Wn4k%QjU~xHEdi{6(GnV?NJdML*&e`?76(BHC@7~r*j{5pcJh|m z)`9!xY&hSomov!{pyuiA0ST)pOF-mE0!TuAiuQ|;MIHHpAGV)4&SnjU2#CjN;F=zQ zcDe44Z-y|$ zt;pA1>^0hBG8Tfrg#BtLQaC~EMRA`18K59-4~0qvq6nmfT#&5jF9|NzG{cMA>7TEF z+u=si%@9{XPAZ5hA2ZG9q|BPga8e4r;=$2-`???91MOq7_B&pgAESdw0Pe9Lf{%oq zG~M*`a6+VLdG(l#D*9FiB&?$SP%NTx)}}Tt8@_%vTYuBEMKeG40iat}jY2%oZah%y za^kvd+mQCYjWy4_nMT6?Ha`S3(m&Ql!v1zY1g}w~e+&=mr7ZPE|E?kMPsdh6W+?of ze&{)(jQ-U~pjH<5j1D0ijlCIrEx9OC?v9D2gHeCgR{=ILeTQpz>KwRQ+93dm5Ne{6 zyl_vu#YQi!=AN4}c|*WJ4-$qI5$-8!$Z-QF9SbQ6gLILFAj3&97rzT7dgXG4#~xVO zY=v%*&$m~^L7S(TXakY^V-&5AB9A2{O?rK}*`i|>i#~0%ZM%XuP?}FHT2*v1Gjmqm zJ0I6_Q`f}Zt(=;9hJ;BAuOdVngT%<|=eE$R+iu$}Ax+I@a2IY=NFYsk|AP6Gg%*TC~d%!#Dfov+TNf z*v-4veQ38R$%L9>S}XJWRcu8>t3AX$<$doLW{c)*n(xGK#UY8dHbJx`tfEC5uT0UJ z$(r<5NNWSDd{*zh(9k~a;lz`l-yW_^5^WuVXh~Q_i*}7NMeBf=Wc0NPy;-Gk@2&4g zH}B2e`tufRVYSW+NusTbN;T)OTtAL-^YI_l+m_zW9)r_T}E|T27R^OuC&siL<)&5qoNi3&E-AJOXheS(n#zmNwoEmXvM92^fSuI;>M(8-cfm0 z-;;z@v}j@IHSC|;U)8twx80?3uV%KDnU}&^ySkpTMf<2($gFzJM{u|GXw}?BE#fdq zv}#DS##dEaXC2?oPf@?Jobzt^I1*OTqJ?)2*7&vY;m1B&7QsTl@2c8!VeBi@Au!JJ z!;%Z?e@OzKbfqp)hKwVPe3V9ReSSN>QckNZ^sxGCi|Kvuv|U6JIxIAIXtJxitIj~) z`Yu}yTGi@Pjf4>=w##wh_9$Zb@?OU~`<5h%;--5}*<~zmHgnG9v}|WnN3AX z^IR-FV+2YC88NTLKov@8L5bw3LWL%Xs;O*LpX1+~w`R|ZwEp9^=1Eg6p%T#m8wql4 z)h+rz-)bFw_UFMzDzQ&V7;&wo9M`IkA=;)0yuKuiwcxJ1bI9=Ut8*t1RB9DsI)AGQ zp&;h9Hdw4w&Px+?$Y&(<>WJr+$P+Vv!rtFRnR6c)by7HP86?(PsS>t+63w^#dnP&P;xexv|6rn4MP6tqM^6P4Qv z%hFIbM)c4dN)m<}AP`+)6_#T|c?Ol!d-|TKdrXJfN6o$a{Z-Vf2T(Rt6Z}X4a7^cK zRiUH+Mk|CWRKN<$t)T=$_YFVT|Is5y%gu39m)xwgJ&=T9?FP{mR$)0>;aOBpbM5f` z?^-=JPp)<4-uOM+r$E^tU5SL@f))VBbpBQqO7g8n?G(b>0cKOBjbSR@m5L~_;$NG} zNlYccIo79ggw+RPxVdSspjp?u{Pg&n?r+6w+dELHO7^g@gJtY2XUQ7Aj8tS=K4H;{ z%)qohcEKlJRJxI-S8NszZ?bBNIF9e~d{@ViBi8Hh_qG3gE|$h1#Wpr{yto z%3F6{gvwcYdcc&LsS%<$t<7sLUqAgG%7zM}DhV!21fc!?+I4AegC|-Y z=ARv+kK8q5#~4g>j^Fk>O5R$=7@dRImAe(xvF|s6SO1J~J3qO7_aW{xq$>f}5b4t= zBn(Rk>ZGuON<6A9*T;4sE9oG~mZ}KWR}@44R173$(g&Oo&>oY}dl#>NZ@ajU=XSQ~ zv~vOp`wuGl8%348ja{Q}kNk`;5N+OJ9B^f!N|am4KlHSH+Izx& zyHK}#t#u~G_Xpyl0DSF{2 zF6&b=WbCpiQPCqbWGK2Bsyn$Ay+@3R?SVxL>{2XNBm{;m-#}7yP}S~-PK#OXY73U7 zvIY%!^{W;M`xA=34oP2p$}Vu6NiWN_TdMnL2hBsP{-rBA(4P{L)O5ebLecDy{H~f2 zW{XMKe^AliBGEvQyW5i4@XoS%BVBAa4$2!I(Z+P_88Zs0e7E zXOp?5b41+nbHq>Hw?r9#5B%8S=>GB6%Y_~WItLqlCSh1YAf+OYUVeRZQ1VONcE0Yp z+es9uZgjxvu%2YR0~G~7rU10tj_K`SHg|+wvX%X#OI86*N!Wi-$zM})lUvDi{m;2{ z+@@s})_LYk%LTz*%S*|95P@n^(H`bikkW=Dgr zZY$QqJrIx6z%@Mp?S~yYM1S3oVY#`j)uG9iGfYX?|CGG!T^1!Od5VS%PY%y!%dO;3 zr*G1)bIwN;E>vf8#&#~YUWo^wt&vi!&~p$5{k+B9(z=C8|A{JrB&?#9d<)Xv%UYd2 z_I;shop7qwnQ_4dFxYsGSCs$&> zTxNGA8`YiMN^Y`bp!@wpNuqtTx1H3mHZ1m4N37&+P{vNaZ{xQ$SZTGf>zEOjDmj>w zFf1WZu0m(60=-=bj1HSI0^L;V02CB`-(;gsoLYzZ3-tDJR_8@a21rmSRWLd$8G!!S zgZ#6vSv=9+wkJaEX6>^kVgErz$GuLu6vWs8Tto$*@NJ^b_4`X=v`yBw_zSCC3f2+)A#* zE=&EkGL+m5)u!A^zAyXw*wbFM`LlnX)3B-cia4F9ft2F?u{xGB4SxyaPW!GJXR-Ym z3BwWsttwLTyOh3RC0Aa*JPH_&ofjk?yU1pvPVByit0!tg*-#)=C|s5ZKzqWp!^b9P zP7_3}9)8a_Gw3u4`wuEP_ET~zxe`6OLwgxYULSd0xt09v^wf_pTg(up_BOld)1O6D z^0r7R0;;IF4Y}3GHc6QK{?ybi+9V812(+q5$;;1^r=oI>cgr5s?DBNct}w$dlM+Vg zLfNG$InaK6s$Tqp+?MvM7JRCwxka-p3HuK!Iqr4Jt>jAVmz&8_ax+DgToks@B6iq2 z!H%x?f4rPgdpY*Epe(q%(hey_WY@26jSn`o3(l+Jb1o*Y8VSP^f_saUynOrRpy&?Q zXJ}?53^j{W6YbukJF-kui9ml9?j6rab1U=N!)7E;<9i86*nd#b@lFl76J9j7>kt?&y0<_cjvXHRc?I#I3uRT73J1d3MV&C9P}4xaqh&dxg7M_<}+ zi8ekyApRQKmt4A%1MOctYKC_F(b8g`<`ByvpEWc{*nd#TUxI@`K};QUsAK&Cn8mon zAMbu8!LE4+?$S7|ZzKCJe*GJZLL5{U<6f2sMV@H?t{c_GGNP(&L_iI*S0NjYl9apy z;*|bzJ`#o{1Oh2ia=ItaQre=B3Zjet;X)`IDu${YxGWKX_Q=3FcZaUkuuY2Hp`JOm zOC|~X4=OosjOA8xC3aUL&{j`IC0D4svdW-Msy45#&HnG4E^3!{9v~^X7E+3uyS}eE zHF%Rn_4dFuRwMv$gSk83r&am?sc+W`N7Y!o9pv2<>kp86!GMf zjjgO&b!lRsT7AsnzJWGfNJ`!jaZdl_EeXRC0x2;o`2nSRa{A|hq3qI>9BBVG{@z~g z2C-(*eb0-hU$yZiVgErT$MXkrE4dP9Sh8fj>?|AAuiVp!o^NJHaC`B&JO8 zSgUn;D!5gx)p4Z{s3M5sLYt7VLvX2Ef9B5IJM-%v?)oDC z!vfE&8Bl*iKnMFnIsJ<|eRIllew&%jEnQpxfRnujI{R@f<^uXC)OH2~ef`01?cR8{ z-0Q3Crx&mJ=Z(a%xiwNe-lp5A{i@l*!h0>6m8}2B5+{2ZK+i;=w!UupaOH6;m)#>f zPvvzAoy!L3V1M+gYuk3--Nj35dgxSkmmyU`99LgP}5gjGlqo z!(gBXJ&i6(tSccbV+r zm$q$ipWE9!L~K9~wrhhNCwmR@mjZIVOirM-GZ^G{p9(S~e(dVLtG(;$O?m#?aUg#m z19F_q+#t_Hi2gyCToq92CH!=9=ZzLUt#jCb9BkJHIZpPE1v&MWDin!O5K~c zzXMYWO`zxaio!^KxFUk|l*+?ZoM5`l4EmqCc!Tm2Xo10(Hd98VJ1j33dgq>Nx5|0P zlx7U(bk6G^)l1LK#h7MBz<%YO?`2bR)4AXJM)dgP$)o4)pwn(~2B4L3sr#1%FSz5G zy?9>t^723L;$%!5ASSvmU`%%h{TEb^$CB2U+GiK>c9&o1-7+UV9_j|W8QWo7GigtO z|9?XWx04=(5a=nI!NeHk)^=KSs;_r;cDK$)ekM*bAcd$hQ67j^=E^>N*!tKbw=Ij_ zwzG)vJB^c>8zGmGjWfGU@(p-jt`YBn=zowyCeooa_$-t3=})tL{=Cai{WarO|0w%=%mJ+PEVE5tH`yo- z$#+TLf4IWeGv%q9uuuL|6=MPuY4{(6{5vSVZY{iK%P;6WzF_;Q4JU9iCJwNV?z2p> z$?onKqk5`;oAq(K9+!O*mOYxcb*&5abu4HOx=Fy4=hDmJ5GFE>kB~BbucC(Ub{Kq? zDU0fHU}E}sUXIn(i`!eBe~3efJqq{Rp-OV>;lr+Z_wKe&aU5BMlQD6CCNT#>N>Dww z-EOQIml`L`E8pkz=IIBEq3%~1A)|WgIm;Mzr5dw)YxuZjJ^69H1ySV916yziaX=v# zT=N-z-*2qfXN!dH^IG_ea58fvqz0kkdRpI$r~TjcTAUeOB5<5)Zzw_*!44?t?91rv zpWLBo%3xkF;mevq<>LpsN@v4Mv{N z*&5o~-*%61w7@WhjR8zA?!MbTz&k1#GP-^Lh;0#48$t}Ma zly~8%OU@X%cXsEZoqj2E?9QHUG6mi;$;zML^X-@YI=R4QXaAcy zYp2J~1b?(;2^wZn75gCNV@MVHLRkc4A7Y~|`^VmFGM=EB0V@U*v?+)5AP&|^&{NT%HWiIo)+0xI z4yv&m?e#CexcLViBY*STs7a#?K|3O)j!}|y*TG-=B^_TI?H{~Kj+2?2pbHV8t^X)1 zOl)>dm}657>YlY)^7N^GWOzd%@26mXo$QcWsT6nVX=$L&o2aa1wZCck?Uk z?($5wc7>3wKUIOL4wWscU6)_{wrAhi&<)^^wv0hA=&U1X?Pr&!8*&Bh^XQi1=#{a9 zy*IA=V8OXl@+)lp$yzY6pm_T;@7Skv?3d1K-5>nXmNh;>n?5$+KP2Q1wNM+FAMm$B z)2nBB_iuZBlTo&@&92HLIN2*m#LVzmgNfLL1Bf}z^;{|28T0Pms#Om<@8(_h$+~gG zx#GzFRX8FZfeh1MNx`>Q;!m9GzIzZ(_5h9+I;n}P5wDbCK=gldi%=|?B;;{k$A+d?U^qScv zl{>%kPAK&V>z%kN7h<3-dj*O3F2oKcgNfLL!y@Hf^boO8w=6h|Kir}!B)FSO!i%C0 zoPC5NVz@QX)IQ{`!x!88WuL(V=oSZ^YT{}{B8IM~_N^hd{v^bD{aLbnlJBOJC6zHXHX`sxTgD*xIe$2f zJ3brQsCt(7-~7jUE_=GHu#(rrXR-CE=*}FAi=TCHP5EwFV$Gm-e}hljvc@Ol#)%Ka z01`k97F`K^ek(e7WKW0BxvAW~9p+?qzk-v!f`rTrX)>6QO*pdgj=qtNMiKJtrLq&} zwxjrAPdR@5RZ?O~^kX6i2SvH>vZ-eRzo_ z(w0fR`m;LN!S+^bM}CkX9K_4Xj;rc1ym6 zg`ZCDfs-)^hJ#;yf@#DnV`!f?R*rXjO-STtyY9&7U%7rD)Q!y5OG04#k-0YoZDv1n zE9jo)xxzZa87FIeGB$m$0sM!=CI-Dy4tC$Q$=hSotgl1~$@9V>2HLV$kcgRgF&Iq5 zCVX2(24U(e<4IjUyyf2g4bO!|bGc1c|7YGzKnR_fd&R?$?B}PJH-`yl^U`L;JTCW2 z$;ZjeO~ml!6=g|6enwaazogu2e_vYqQ-i}0!8NdbY2t>UieuH@@iTK~91pvF7bi0} z5vL*Bj3HvMo$vN#q2<5Ndv9-ZW9JH)${r_U5DYP>BVydcJSd?~nvhTnb*xZo83;l9 ze6Eysx)q%4k}t4d@^rKaW^Xn~LSTFF{r7)A+2pox^FeOLfOUH&@JA|8%vS{8HQT-PN>T{D)}#J&^%PC8CzZX$+< zT=#eudkcgg3i4ZgAA9!9Vm}B41cnX{Y}QL)`-FloVzT1W1PeGf%GWqmeT$Qsn~0N; zrJWZREffFULXhtj9JlP<$Vf1rjXyr4=Rew3?84o0x-@BO+!7x6qb*|)3_9zGSo^Ba zbmR4*eL6Un-W}Wgrbpq>K+A_+gtMV;HvX)O+GyW;{s&$e)t`kP8a_@2{%FhoF(d>| zg9DZR-&?ovedN0O!PT0TU+lBQ$r}IcV)_OL{~+mW*q1Xzb^@4$lEI{H@Kha+zKIxR z#yrN0-n_YBa9I?8VMX!diN3jQHK~~){SnB&N5_6_zFcxxkTc;-dHJ#2Oq|Tz)Vv49 zJTm^mz+q!IyKl($dhgPj6%No2`Va#7V0*_x%hTucHVNl*7JM%36l;x>nVXv78RX+# zHisN?2Jka??g-&I#e0CgZ2bAp36C*L93q7&MA}lv=*gkrkG71#Dd;o@HADM&-9LH9 z;>vxuEypd-RS&b81$DFWCoc4Hm&#sB@8aQ;r_Z+$`++~&vezsI(L~846AmXrv!G-! ziy1r>M6Pcth*2yyqx|d2-*Y#(uSx6uwpIIm(Kr?xiTra;In*=kh_lD?S;dOJl{F+z zW^NXPqyDBxamimjV|h6%?Zr+1)n*5@LqkUi*q+|x$Ni=5x4bsanXsd#>GXV@%-k$i zjBMW{?b>g92i19(Av6HOTwDq%g_*aMR=e}ERzz-K!sSY zvZV&Y%O#>fl2FMArCby#B?4vB8b25i&G>UV#S1tHR|2C{8dRz z%0PYp`sjxYCiP=h(7!cW4BFv6LwD$J=nj1i-J!RkI}nEM06c6^lwbxomQ&u*l>y+u z?tywp_zZLd2%iBrYd{r_9$|JTc*2~4TA=j_3Xxn+DzN3Hpa>=PsdNb1&{Ux2XTNY2 zdW#L=8H7%i;$ShUP_k3FTuUL4Bz2}>6iS20ne0@BXgSx0QDu;av07Rz7%g?Ku`>rI z%xZ=T-uXj$=1$7=oB`{F9J=9}dRU)J>uEv;=tKtOY1U#eGPD)EFfz0iz5p_`RT+m2 zZ7oJ4LtBxt$k5jF;>gf>+0@t>Kw2^24@yJPf-chu*9gQVXiBO?f-b7j%vHrKA{efK zZ3!`&w<-<{6H7w~v63LAFnKjd^U50d>LXObh)ZcW>l#=y+M|{t8Om*iw)rxNM65); z3$9nhQ#4&JAqf$I zx+t?y7bH?7sSSY?^;Sd#n%)}{LMo^}NlQ1_0ph15s5)fSC9{$U6-9uC0Ew(Ne1VLW zI_d}&t5gtX5!5EJMcp2fu<{VAtZ41Ewbfow@$0%us81Du9H3o8{U(@mTi<6@(-^s;k$l#%l_KuE@_6`nCZjSamCjoERut7uIhSc5R zL51o4VBb3Ve$34#szl=yI?JXyR)t`qlJ;$@dWOT(d(#wx$-50|VpXV!jV4y!y)MN0 zUkm{&(6Ku1iZM`uO9x15FygAKR|u!6R*!}zR`A6c9z#=$+9sT)W;6k*3*il~Ri!S( zN)rwR^{RtHV`!h<7~kptCUxON5_RQheIZ?99_=Dr2vX?~Qn!d+ua^nWIiu;!`}Cgu z=-SCE?AoJawCh!$^mm$nEc6=G@m;#w8T3CW zr_$&2zowVF=|a8eJf<+?7}a!Ps1V!|hSXreb+zRS}H5Od6^(Sg%cuaF8;T|Lrd1v+)TulNz# LU>x=>v|jgLZ^xVa literal 0 HcmV?d00001 diff --git a/Content/Examples/Python/asset_input_example.py b/Content/Examples/Python/asset_input_example.py index a9ac3f867..5a47cc35f 100644 --- a/Content/Examples/Python/asset_input_example.py +++ b/Content/Examples/Python/asset_input_example.py @@ -1,143 +1,167 @@ -""" An example script that uses the API to instantiate two HDAs. The first HDA -will be used as an input to the second HDA. For the second HDA we set 2 inputs: -an asset input (the first instantiated HDA) and a curve input (a helix). The -inputs are set during post instantiation (before the first cook). After the -first cook and output creation (post processing) the input structure is fetched -and logged. - -""" -import math - -import unreal - -_g_wrapper1 = None -_g_wrapper2 = None - - -def get_copy_curve_hda_path(): - return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' - - -def get_copy_curve_hda(): - return unreal.load_object(None, get_copy_curve_hda_path()) - - -def get_pig_head_hda_path(): - return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' - - -def get_pig_head_hda(): - return unreal.load_object(None, get_pig_head_hda_path()) - - -def configure_inputs(in_wrapper): - print('configure_inputs') - - # Unbind from the delegate - in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) - - # Create a geo input - asset_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIAssetInput) - # Set the input objects/assets for this input - # asset_input.set_input_objects((_g_wrapper1.get_houdini_asset_actor().houdini_asset_component, )) - asset_input.set_input_objects((_g_wrapper1, )) - # copy the input data to the HDA as node input 0 - in_wrapper.set_input_at_index(0, asset_input) - # We can now discard the API input object - asset_input = None - - # Create a curve input - curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput) - # Create a curve wrapper/helper - curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) - # Make it a Nurbs curve - curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) - # Set the points of the curve, for this example we create a helix - # consisting of 100 points - curve_points = [] - for i in range(10): - t = i / 10.0 * math.pi * 2.0 - x = 100.0 * math.cos(t) - y = 100.0 * math.sin(t) - z = i * 10.0 - curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) - curve_object.set_curve_points(curve_points) - # Set the curve wrapper as an input object - curve_input.set_input_objects((curve_object, )) - # Copy the input data to the HDA as node input 1 - in_wrapper.set_input_at_index(1, curve_input) - # We can now discard the API input object - curve_input = None - - -def print_api_input(in_input): - print('\t\tInput type: {0}'.format(in_input.__class__)) - print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) - print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) - if isinstance(in_input, unreal.HoudiniPublicAPICurveInput): - print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) - print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) - - input_objects = in_input.get_input_objects() - if not input_objects: - print('\t\tEmpty input!') - else: - print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) - for idx, input_object in enumerate(input_objects): - print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) - if hasattr(in_input, 'get_object_transform_offset'): - print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) - if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): - print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) - print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) - print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) - print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) - print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) - - -def print_inputs(in_wrapper): - print('print_inputs') - - # Unbind from the delegate - in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) - - # Fetch inputs, iterate over it and log - node_inputs = in_wrapper.get_inputs_at_indices() - parm_inputs = in_wrapper.get_input_parameters() - - if not node_inputs: - print('No node inputs found!') - else: - print('Number of node inputs: {0}'.format(len(node_inputs))) - for input_index, input_wrapper in node_inputs.items(): - print('\tInput index: {0}'.format(input_index)) - print_api_input(input_wrapper) - - if not parm_inputs: - print('No parameter inputs found!') - else: - print('Number of parameter inputs: {0}'.format(len(parm_inputs))) - for parm_name, input_wrapper in parm_inputs.items(): - print('\tInput parameter name: {0}'.format(parm_name)) - print_api_input(input_wrapper) - - -def run(): - # get the API singleton - api = unreal.HoudiniPublicAPIBlueprintLib.get_api() - - global _g_wrapper1, _g_wrapper2 - # instantiate the input HDA with auto-cook enabled - _g_wrapper1 = api.instantiate_asset(get_pig_head_hda(), unreal.Transform()) - - # instantiate the copy curve HDA - _g_wrapper2 = api.instantiate_asset(get_copy_curve_hda(), unreal.Transform()) - - # Configure inputs on_post_instantiation, after instantiation, but before first cook - _g_wrapper2.on_post_instantiation_delegate.add_callable(configure_inputs) - # Print the input state after the cook and output creation. - _g_wrapper2.on_post_processing_delegate.add_callable(print_inputs) - - -if __name__ == '__main__': - run() +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that uses the API to instantiate two HDAs. The first HDA +will be used as an input to the second HDA. For the second HDA we set 2 inputs: +an asset input (the first instantiated HDA) and a curve input (a helix). The +inputs are set during post instantiation (before the first cook). After the +first cook and output creation (post processing) the input structure is fetched +and logged. + +""" +import math + +import unreal + +_g_wrapper1 = None +_g_wrapper2 = None + + +def get_copy_curve_hda_path(): + return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' + + +def get_copy_curve_hda(): + return unreal.load_object(None, get_copy_curve_hda_path()) + + +def get_pig_head_hda_path(): + return '/HoudiniEngine/Examples/hda/pig_head_subdivider_v01.pig_head_subdivider_v01' + + +def get_pig_head_hda(): + return unreal.load_object(None, get_pig_head_hda_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Create a geo input + asset_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIAssetInput) + # Set the input objects/assets for this input + # asset_input.set_input_objects((_g_wrapper1.get_houdini_asset_actor().houdini_asset_component, )) + asset_input.set_input_objects((_g_wrapper1, )) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, asset_input) + # We can now discard the API input object + asset_input = None + + # Create a curve input + curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(10): + t = i / 10.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i * 10.0 + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Copy the input data to the HDA as node input 1 + in_wrapper.set_input_at_index(1, curve_input) + # We can now discard the API input object + curve_input = None + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPICurveInput): + print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) + print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): + print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) + print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) + print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) + print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) + print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper1, _g_wrapper2 + # instantiate the input HDA with auto-cook enabled + _g_wrapper1 = api.instantiate_asset(get_pig_head_hda(), unreal.Transform()) + + # instantiate the copy curve HDA + _g_wrapper2 = api.instantiate_asset(get_copy_curve_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper2.on_post_instantiation_delegate.add_callable(configure_inputs) + # Print the input state after the cook and output creation. + _g_wrapper2.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/bake_all_outputs_example.py b/Content/Examples/Python/bake_all_outputs_example.py index dbf9c3875..6daf6555e 100644 --- a/Content/Examples/Python/bake_all_outputs_example.py +++ b/Content/Examples/Python/bake_all_outputs_example.py @@ -1,3 +1,27 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + import unreal """ Example script for instantiating an asset, cooking it and baking all of diff --git a/Content/Examples/Python/bake_output_object_example.py b/Content/Examples/Python/bake_output_object_example.py index ac265067e..bdcab3974 100644 --- a/Content/Examples/Python/bake_output_object_example.py +++ b/Content/Examples/Python/bake_output_object_example.py @@ -1,3 +1,27 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + import unreal """ Example script for instantiating an asset, cooking it and baking an diff --git a/Content/Examples/Python/curve_input_example.py b/Content/Examples/Python/curve_input_example.py index f01cee415..6d4c93ea6 100644 --- a/Content/Examples/Python/curve_input_example.py +++ b/Content/Examples/Python/curve_input_example.py @@ -1,155 +1,179 @@ -""" An example script that uses the API to instantiate an HDA and then -set 2 inputs: a geometry input (a cube) and a curve input (a helix). The -inputs are set during post instantiation (before the first cook). After the -first cook and output creation (post processing) the input structure is fetched -and logged. - -""" -import math - -import unreal - -_g_wrapper = None - - -def get_test_hda_path(): - return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' - - -def get_test_hda(): - return unreal.load_object(None, get_test_hda_path()) - - -def get_geo_asset_path(): - return '/Engine/BasicShapes/Cube.Cube' - - -def get_geo_asset(): - return unreal.load_object(None, get_geo_asset_path()) - - -def configure_inputs(in_wrapper): - print('configure_inputs') - - # Unbind from the delegate - in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) - - # Create a geo input - geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) - # Set the input objects/assets for this input - geo_object = get_geo_asset() - if not geo_input.set_input_objects((geo_object, )): - # If any errors occurred, get the last error message - print('Error on geo_input: {0}'.format(geo_input.get_last_error_message())) - # copy the input data to the HDA as node input 0 - in_wrapper.set_input_at_index(0, geo_input) - # We can now discard the API input object - geo_input = None - - # Create a curve input - curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput) - # Create a curve wrapper/helper - curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) - # Make it a Nurbs curve - curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) - # Set the points of the curve, for this example we create a helix - # consisting of 100 points - curve_points = [] - for i in range(100): - t = i / 20.0 * math.pi * 2.0 - x = 100.0 * math.cos(t) - y = 100.0 * math.sin(t) - z = i - curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) - curve_object.set_curve_points(curve_points) - # Error handling/message example: try to set geo_object on curve input - if not curve_input.set_input_objects((geo_object, )): - print('Error (example) while setting \'{0}\' on curve input: {1}'.format( - geo_object.get_name(), curve_input.get_last_error_message() - )) - # Set the curve wrapper as an input object - curve_input.set_input_objects((curve_object, )) - # Copy the input data to the HDA as node input 1 - in_wrapper.set_input_at_index(1, curve_input) - # We can now discard the API input object - curve_input = None - - # Check for errors on the wrapper - last_error = in_wrapper.get_last_error_message() - if last_error: - print('Error on wrapper during input configuration: {0}'.format(last_error)) - - -def print_api_input(in_input): - print('\t\tInput type: {0}'.format(in_input.__class__)) - print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) - print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) - if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): - print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) - print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) - print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) - print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) - elif isinstance(in_input, unreal.HoudiniPublicAPICurveInput): - print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) - print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) - - input_objects = in_input.get_input_objects() - if not input_objects: - print('\t\tEmpty input!') - else: - print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) - for idx, input_object in enumerate(input_objects): - print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) - if hasattr(in_input, 'get_object_transform_offset'): - print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) - if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): - print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) - print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) - print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) - print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) - print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) - - -def print_inputs(in_wrapper): - print('print_inputs') - - # Unbind from the delegate - in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) - - # Fetch inputs, iterate over it and log - node_inputs = in_wrapper.get_inputs_at_indices() - parm_inputs = in_wrapper.get_input_parameters() - - if not node_inputs: - print('No node inputs found!') - else: - print('Number of node inputs: {0}'.format(len(node_inputs))) - for input_index, input_wrapper in node_inputs.items(): - print('\tInput index: {0}'.format(input_index)) - print_api_input(input_wrapper) - - if not parm_inputs: - print('No parameter inputs found!') - else: - print('Number of parameter inputs: {0}'.format(len(parm_inputs))) - for parm_name, input_wrapper in parm_inputs.items(): - print('\tInput parameter name: {0}'.format(parm_name)) - print_api_input(input_wrapper) - - -def run(): - # get the API singleton - api = unreal.HoudiniPublicAPIBlueprintLib.get_api() - - global _g_wrapper - # instantiate an asset with auto-cook enabled - _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) - - # Configure inputs on_post_instantiation, after instantiation, but before first cook - _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) - # Print the input state after the cook and output creation. - _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) - - -if __name__ == '__main__': - run() +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that uses the API to instantiate an HDA and then +set 2 inputs: a geometry input (a cube) and a curve input (a helix). The +inputs are set during post instantiation (before the first cook). After the +first cook and output creation (post processing) the input structure is fetched +and logged. + +""" +import math + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Create a geo input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input + geo_object = get_geo_asset() + if not geo_input.set_input_objects((geo_object, )): + # If any errors occurred, get the last error message + print('Error on geo_input: {0}'.format(geo_input.get_last_error_message())) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, geo_input) + # We can now discard the API input object + geo_input = None + + # Create a curve input + curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(100): + t = i / 20.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Error handling/message example: try to set geo_object on curve input + if not curve_input.set_input_objects((geo_object, )): + print('Error (example) while setting \'{0}\' on curve input: {1}'.format( + geo_object.get_name(), curve_input.get_last_error_message() + )) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Copy the input data to the HDA as node input 1 + in_wrapper.set_input_at_index(1, curve_input) + # We can now discard the API input object + curve_input = None + + # Check for errors on the wrapper + last_error = in_wrapper.get_last_error_message() + if last_error: + print('Error on wrapper during input configuration: {0}'.format(last_error)) + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + elif isinstance(in_input, unreal.HoudiniPublicAPICurveInput): + print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) + print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): + print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) + print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) + print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) + print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) + print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Print the input state after the cook and output creation. + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/eau_curve_input_example.py b/Content/Examples/Python/eau_curve_input_example.py new file mode 100644 index 000000000..f29022197 --- /dev/null +++ b/Content/Examples/Python/eau_curve_input_example.py @@ -0,0 +1,128 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import math + +import unreal + +@unreal.uclass() +class CurveInputExample(unreal.PlacedEditorUtilityBase): + def __init__(self, *args, **kwargs): + self._asset_wrapper = None + + def run_curve_input_example(self): + # Get the API instance + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + # Ensure we have a running session + if not api.is_session_valid(): + api.create_session() + # Load our HDA uasset + example_hda = unreal.load_object(None, '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0') + # Create an API wrapper instance for instantiating the HDA and interacting with it + self._asset_wrapper = api.instantiate_asset(example_hda, instantiate_at=unreal.Transform()) + if self._asset_wrapper: + # Pre-instantiation is the earliest point where we can set parameter values + self._asset_wrapper.on_pre_instantiation_delegate.add_callable(self._set_initial_parameter_values) + # Jumping ahead a bit: we also want to configure inputs, but inputs are only available after instantiation + self._asset_wrapper.on_post_instantiation_delegate.add_callable(self._set_inputs) + # Jumping ahead a bit: we also want to print the outputs after the node has cook and the plug-in has processed the output + self._asset_wrapper.on_post_processing_delegate.add_callable(self._print_outputs) + + def _set_initial_parameter_values(self, in_wrapper): + """ Set our initial parameter values: disable upvectorstart and set the scale to 0.2. """ + # Uncheck the upvectoratstart parameter + in_wrapper.set_bool_parameter_value('upvectoratstart', False) + + # Set the scale to 0.2 + in_wrapper.set_float_parameter_value('scale', 0.2) + + # Since we are done with setting the initial values, we can unbind from the delegate + in_wrapper.on_pre_instantiation_delegate.remove_callable(self._set_initial_parameter_values) + + def _set_inputs(self, in_wrapper): + """ Configure our inputs: input 0 is a cube and input 1 a helix. """ + # Create an empty geometry input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Load the cube static mesh asset + cube = unreal.load_object(None, '/Engine/BasicShapes/Cube.Cube') + # Set the input object array for our geometry input, in this case containing only the cube + geo_input.set_input_objects((cube, )) + + # Set the input on the instantiated HDA via the wrapper + in_wrapper.set_input_at_index(0, geo_input) + + # Create a curve input + curve_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(100): + t = i / 20.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Copy the input data to the HDA as node input 1 + in_wrapper.set_input_at_index(1, curve_input) + + # unbind from the delegate, since we are done with setting inputs + in_wrapper.on_post_instantiation_delegate.remove_callable(self._set_inputs) + + def _print_outputs(self, in_wrapper): + """ Print the outputs that were generated by the HDA (after a cook) """ + num_outputs = in_wrapper.get_num_outputs() + print('num_outputs: {}'.format(num_outputs)) + if num_outputs > 0: + for output_idx in range(num_outputs): + identifiers = in_wrapper.get_output_identifiers_at(output_idx) + print('\toutput index: {}'.format(output_idx)) + print('\toutput type: {}'.format(in_wrapper.get_output_type_at(output_idx))) + print('\tnum_output_objects: {}'.format(len(identifiers))) + if identifiers: + for identifier in identifiers: + output_object = in_wrapper.get_output_object_at(output_idx, identifier) + output_component = in_wrapper.get_output_component_at(output_idx, identifier) + is_proxy = in_wrapper.is_output_current_proxy_at(output_idx, identifier) + print('\t\tidentifier: {}'.format(identifier)) + print('\t\toutput_object: {}'.format(output_object.get_name() if output_object else 'None')) + print('\t\toutput_component: {}'.format(output_component.get_name() if output_component else 'None')) + print('\t\tis_proxy: {}'.format(is_proxy)) + print('') + + +def run(): + # Spawn CurveInputExample and call run_curve_input_example + curve_input_example_actor = unreal.EditorLevelLibrary.spawn_actor_from_class(CurveInputExample.static_class(), unreal.Vector.ZERO, unreal.Rotator()) + curve_input_example_actor.run_curve_input_example() + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/geo_input_example.py b/Content/Examples/Python/geo_input_example.py index 721f76cd4..3ac7bb1f9 100644 --- a/Content/Examples/Python/geo_input_example.py +++ b/Content/Examples/Python/geo_input_example.py @@ -1,157 +1,181 @@ -""" An example script that uses the API to instantiate an HDA and then -set 2 geometry inputs: one node input and one object path parameter -input. The inputs are set during post instantiation (before the first -cook). After the first cook and output creation (post processing) the -input structure is fetched and logged. - -""" - -import unreal - -_g_wrapper = None - - -def get_test_hda_path(): - return '/HoudiniEngine/Examples/hda/subnet_test_2_0.subnet_test_2_0' - - -def get_test_hda(): - return unreal.load_object(None, get_test_hda_path()) - - -def get_geo_asset_path(): - return '/Engine/BasicShapes/Cube.Cube' - - -def get_geo_asset(): - return unreal.load_object(None, get_geo_asset_path()) - - -def get_cylinder_asset_path(): - return '/Engine/BasicShapes/Cylinder.Cylinder' - - -def get_cylinder_asset(): - return unreal.load_object(None, get_cylinder_asset_path()) - - -def configure_inputs(in_wrapper): - print('configure_inputs') - - # Unbind from the delegate - in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) - - # Deprecated input functions - # in_wrapper.set_input_type(0, unreal.HoudiniInputType.GEOMETRY) - # in_wrapper.set_input_objects(0, (get_geo_asset(), )) - - # in_wrapper.set_input_type(1, unreal.HoudiniInputType.GEOMETRY) - # in_wrapper.set_input_objects(1, (get_geo_asset(), )) - # in_wrapper.set_input_import_as_reference(1, True) - - # Create a geometry input - geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) - # Set the input objects/assets for this input - geo_asset = get_geo_asset() - geo_input.set_input_objects((geo_asset, )) - # Set the transform of the input geo - geo_input.set_object_transform_offset( - geo_asset, - unreal.Transform( - (200, 0, 100), - (45, 0, 45), - (2, 2, 2), - ) - ) - # copy the input data to the HDA as node input 0 - in_wrapper.set_input_at_index(0, geo_input) - # We can now discard the API input object - geo_input = None - - # Create a another geometry input - geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) - # Set the input objects/assets for this input (cylinder in this case) - geo_asset = get_cylinder_asset() - geo_input.set_input_objects((geo_asset, )) - # Set the transform of the input geo - geo_input.set_object_transform_offset( - geo_asset, - unreal.Transform( - (-200, 0, 0), - (0, 0, 0), - (2, 2, 2), - ) - ) - # copy the input data to the HDA as input parameter 'objpath1' - in_wrapper.set_input_parameter('objpath1', geo_input) - # We can now discard the API input object - geo_input = None - - # Set the subnet_test HDA to output its first input - in_wrapper.set_int_parameter_value('enable_geo', 1) - - -def print_api_input(in_input): - print('\t\tInput type: {0}'.format(in_input.__class__)) - print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) - print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) - if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): - print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) - print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) - print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) - print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) - - input_objects = in_input.get_input_objects() - if not input_objects: - print('\t\tEmpty input!') - else: - print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) - for idx, input_object in enumerate(input_objects): - print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) - if hasattr(in_input, 'get_object_transform_offset'): - print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) - - -def print_inputs(in_wrapper): - print('print_inputs') - - # Unbind from the delegate - in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) - - # Fetch inputs, iterate over it and log - node_inputs = in_wrapper.get_inputs_at_indices() - parm_inputs = in_wrapper.get_input_parameters() - - if not node_inputs: - print('No node inputs found!') - else: - print('Number of node inputs: {0}'.format(len(node_inputs))) - for input_index, input_wrapper in node_inputs.items(): - print('\tInput index: {0}'.format(input_index)) - print_api_input(input_wrapper) - - if not parm_inputs: - print('No parameter inputs found!') - else: - print('Number of parameter inputs: {0}'.format(len(parm_inputs))) - for parm_name, input_wrapper in parm_inputs.items(): - print('\tInput parameter name: {0}'.format(parm_name)) - print_api_input(input_wrapper) - - -def run(): - # get the API singleton - api = unreal.HoudiniPublicAPIBlueprintLib.get_api() - - global _g_wrapper - # instantiate an asset with auto-cook enabled - _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) - - # Configure inputs on_post_instantiation, after instantiation, but before first cook - _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) - # Bind on_post_processing, after cook + output creation - _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) - - -if __name__ == '__main__': - run() +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that uses the API to instantiate an HDA and then +set 2 geometry inputs: one node input and one object path parameter +input. The inputs are set during post instantiation (before the first +cook). After the first cook and output creation (post processing) the +input structure is fetched and logged. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/subnet_test_2_0.subnet_test_2_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def get_cylinder_asset_path(): + return '/Engine/BasicShapes/Cylinder.Cylinder' + + +def get_cylinder_asset(): + return unreal.load_object(None, get_cylinder_asset_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Deprecated input functions + # in_wrapper.set_input_type(0, unreal.HoudiniInputType.GEOMETRY) + # in_wrapper.set_input_objects(0, (get_geo_asset(), )) + + # in_wrapper.set_input_type(1, unreal.HoudiniInputType.GEOMETRY) + # in_wrapper.set_input_objects(1, (get_geo_asset(), )) + # in_wrapper.set_input_import_as_reference(1, True) + + # Create a geometry input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input + geo_asset = get_geo_asset() + geo_input.set_input_objects((geo_asset, )) + # Set the transform of the input geo + geo_input.set_object_transform_offset( + geo_asset, + unreal.Transform( + (200, 0, 100), + (45, 0, 45), + (2, 2, 2), + ) + ) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, geo_input) + # We can now discard the API input object + geo_input = None + + # Create a another geometry input + geo_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input (cylinder in this case) + geo_asset = get_cylinder_asset() + geo_input.set_input_objects((geo_asset, )) + # Set the transform of the input geo + geo_input.set_object_transform_offset( + geo_asset, + unreal.Transform( + (-200, 0, 0), + (0, 0, 0), + (2, 2, 2), + ) + ) + # copy the input data to the HDA as input parameter 'objpath1' + in_wrapper.set_input_parameter('objpath1', geo_input) + # We can now discard the API input object + geo_input = None + + # Set the subnet_test HDA to output its first input + in_wrapper.set_int_parameter_value('enable_geo', 1) + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Bind on_post_processing, after cook + output creation + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/instances_example.py b/Content/Examples/Python/instances_example.py index 2611889d0..6e33eb0e3 100644 --- a/Content/Examples/Python/instances_example.py +++ b/Content/Examples/Python/instances_example.py @@ -1,3 +1,27 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + """ Example script that instantiates an HDA using the API and then setting some parameter values after instantiation but before the first cook. diff --git a/Content/Examples/Python/landscape_input_example.py b/Content/Examples/Python/landscape_input_example.py index 5bf6b8f69..1064b3273 100644 --- a/Content/Examples/Python/landscape_input_example.py +++ b/Content/Examples/Python/landscape_input_example.py @@ -1,112 +1,136 @@ -""" Example script using the API to instantiate an HDA and set a landscape -input. - -""" - -import unreal - -_g_wrapper = None - - -def get_test_hda_path(): - return '/HoudiniEngine/Examples/hda/hilly_landscape_erode_1_0.hilly_landscape_erode_1_0' - - -def get_test_hda(): - return unreal.load_object(None, get_test_hda_path()) - - -def load_map(): - return unreal.EditorLoadingAndSavingUtils.load_map('/HoudiniEngine/Examples/Maps/LandscapeInputExample.LandscapeInputExample') - -def get_landscape(): - return unreal.EditorLevelLibrary.get_actor_reference('PersistentLevel.Landscape_0') - -def configure_inputs(in_wrapper): - print('configure_inputs') - - # Unbind from the delegate - in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) - - # Create a landscape input - landscape_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPILandscapeInput) - # Send landscapes as heightfields - landscape_input.landscape_export_type = unreal.HoudiniLandscapeExportType.HEIGHTFIELD - # Keep world transform - landscape_input.keep_world_transform = True - # Set the input objects/assets for this input - landscape_object = get_landscape() - landscape_input.set_input_objects((landscape_object, )) - # copy the input data to the HDA as node input 0 - in_wrapper.set_input_at_index(0, landscape_input) - # We can now discard the API input object - landscape_input = None - - -def print_api_input(in_input): - print('\t\tInput type: {0}'.format(in_input.__class__)) - print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) - if isinstance(in_input, unreal.HoudiniPublicAPILandscapeInput): - print('\t\tbLandscapeAutoSelectComponent: {0}'.format(in_input.landscape_auto_select_component)) - print('\t\tbLandscapeExportLighting: {0}'.format(in_input.landscape_export_lighting)) - print('\t\tbLandscapeExportMaterials: {0}'.format(in_input.landscape_export_materials)) - print('\t\tbLandscapeExportNormalizedUVs: {0}'.format(in_input.landscape_export_normalized_u_vs)) - print('\t\tbLandscapeExportSelectionOnly: {0}'.format(in_input.landscape_export_selection_only)) - print('\t\tbLandscapeExportTileUVs: {0}'.format(in_input.landscape_export_tile_u_vs)) - print('\t\tbLandscapeExportType: {0}'.format(in_input.landscape_export_type)) - - input_objects = in_input.get_input_objects() - if not input_objects: - print('\t\tEmpty input!') - else: - print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) - for idx, input_object in enumerate(input_objects): - print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) - - -def print_inputs(in_wrapper): - print('print_inputs') - - # Unbind from the delegate - in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) - - # Fetch inputs, iterate over it and log - node_inputs = in_wrapper.get_inputs_at_indices() - parm_inputs = in_wrapper.get_input_parameters() - - if not node_inputs: - print('No node inputs found!') - else: - print('Number of node inputs: {0}'.format(len(node_inputs))) - for input_index, input_wrapper in node_inputs.items(): - print('\tInput index: {0}'.format(input_index)) - print_api_input(input_wrapper) - - if not parm_inputs: - print('No parameter inputs found!') - else: - print('Number of parameter inputs: {0}'.format(len(parm_inputs))) - for parm_name, input_wrapper in parm_inputs.items(): - print('\tInput parameter name: {0}'.format(parm_name)) - print_api_input(input_wrapper) - - -def run(): - # Load the example map - load_map() - - # get the API singleton - api = unreal.HoudiniPublicAPIBlueprintLib.get_api() - - global _g_wrapper - # instantiate an asset with auto-cook enabled - _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) - - # Configure inputs on_post_instantiation, after instantiation, but before first cook - _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) - # Print the input state after the cook and output creation. - _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) - - -if __name__ == '__main__': - run() +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" Example script using the API to instantiate an HDA and set a landscape +input. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/hilly_landscape_erode_1_0.hilly_landscape_erode_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def load_map(): + return unreal.EditorLoadingAndSavingUtils.load_map('/HoudiniEngine/Examples/Maps/LandscapeInputExample.LandscapeInputExample') + +def get_landscape(): + return unreal.EditorLevelLibrary.get_actor_reference('PersistentLevel.Landscape_0') + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Create a landscape input + landscape_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPILandscapeInput) + # Send landscapes as heightfields + landscape_input.landscape_export_type = unreal.HoudiniLandscapeExportType.HEIGHTFIELD + # Keep world transform + landscape_input.keep_world_transform = True + # Set the input objects/assets for this input + landscape_object = get_landscape() + landscape_input.set_input_objects((landscape_object, )) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, landscape_input) + # We can now discard the API input object + landscape_input = None + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + if isinstance(in_input, unreal.HoudiniPublicAPILandscapeInput): + print('\t\tbLandscapeAutoSelectComponent: {0}'.format(in_input.landscape_auto_select_component)) + print('\t\tbLandscapeExportLighting: {0}'.format(in_input.landscape_export_lighting)) + print('\t\tbLandscapeExportMaterials: {0}'.format(in_input.landscape_export_materials)) + print('\t\tbLandscapeExportNormalizedUVs: {0}'.format(in_input.landscape_export_normalized_u_vs)) + print('\t\tbLandscapeExportSelectionOnly: {0}'.format(in_input.landscape_export_selection_only)) + print('\t\tbLandscapeExportTileUVs: {0}'.format(in_input.landscape_export_tile_u_vs)) + print('\t\tbLandscapeExportType: {0}'.format(in_input.landscape_export_type)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def run(): + # Load the example map + load_map() + + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Print the input state after the cook and output creation. + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/outputs_example.py b/Content/Examples/Python/outputs_example.py index 1179aead7..6269ab1ce 100644 --- a/Content/Examples/Python/outputs_example.py +++ b/Content/Examples/Python/outputs_example.py @@ -1,3 +1,27 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + import unreal """ Example script for instantiating an asset, setting parameters, cooking it diff --git a/Content/Examples/Python/pdg_example.py b/Content/Examples/Python/pdg_example.py index 8c04af287..06cf80e6d 100644 --- a/Content/Examples/Python/pdg_example.py +++ b/Content/Examples/Python/pdg_example.py @@ -1,3 +1,27 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + """ An example script that instantiates an HDA that contains a TOP network. After the HDA itself has cooked (in on_post_process) we iterate over all TOP networks in the HDA and print their paths. Auto-bake is then enabled for diff --git a/Content/Examples/Python/process_hda_example.py b/Content/Examples/Python/process_hda_example.py index 2002e8183..09429845b 100644 --- a/Content/Examples/Python/process_hda_example.py +++ b/Content/Examples/Python/process_hda_example.py @@ -1,172 +1,196 @@ -""" An example script that uses the API and -HoudiniEngineV2.asyncprocessor.ProcessHDA. ProcessHDA is configured with the -asset to instantiate, as well as 2 inputs: a geometry input (a cube) and a -curve input (a helix). - -ProcessHDA is then activiated upon which the asset will be instantiated, -inputs set, and cooked. The ProcessHDA class's on_post_processing() function is -overridden to fetch the input structure and logged. The other state/phase -functions (on_pre_instantiate(), on_post_instantiate() etc) are overridden to -simply log the function name, in order to observe progress in the log. - -""" -import math - -import unreal - -from HoudiniEngineV2.asyncprocessor import ProcessHDA - - -_g_processor = None - - -class ProcessHDAExample(ProcessHDA): - @staticmethod - def _print_api_input(in_input): - print('\t\tInput type: {0}'.format(in_input.__class__)) - print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) - print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) - if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): - print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) - print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) - print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) - print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) - elif isinstance(in_input, unreal.HoudiniPublicAPICurveInput): - print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) - print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) - - input_objects = in_input.get_input_objects() - if not input_objects: - print('\t\tEmpty input!') - else: - print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) - for idx, input_object in enumerate(input_objects): - print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) - if hasattr(in_input, 'get_object_transform_offset'): - print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) - if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): - print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) - print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) - print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) - print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) - print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) - - def on_failure(self): - print('on_failure') - global _g_processor - _g_processor = None - - def on_complete(self): - print('on_complete') - global _g_processor - _g_processor = None - - def on_pre_instantiation(self): - print('on_pre_instantiation') - - def on_post_instantiation(self): - print('on_post_instantiation') - - def on_post_auto_cook(self, cook_success): - print('on_post_auto_cook, success = {0}'.format(cook_success)) - - def on_pre_process(self): - print('on_pre_process') - - def on_post_processing(self): - print('on_post_processing') - - # Fetch inputs, iterate over it and log - node_inputs = self.asset_wrapper.get_inputs_at_indices() - parm_inputs = self.asset_wrapper.get_input_parameters() - - if not node_inputs: - print('No node inputs found!') - else: - print('Number of node inputs: {0}'.format(len(node_inputs))) - for input_index, input_wrapper in node_inputs.items(): - print('\tInput index: {0}'.format(input_index)) - self._print_api_input(input_wrapper) - - if not parm_inputs: - print('No parameter inputs found!') - else: - print('Number of parameter inputs: {0}'.format(len(parm_inputs))) - for parm_name, input_wrapper in parm_inputs.items(): - print('\tInput parameter name: {0}'.format(parm_name)) - self._print_api_input(input_wrapper) - - def on_post_auto_bake(self, bake_success): - print('on_post_auto_bake, succes = {0}'.format(bake_success)) - - -def get_test_hda_path(): - return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' - - -def get_test_hda(): - return unreal.load_object(None, get_test_hda_path()) - - -def get_geo_asset_path(): - return '/Engine/BasicShapes/Cube.Cube' - - -def get_geo_asset(): - return unreal.load_object(None, get_geo_asset_path()) - - -def build_inputs(): - print('configure_inputs') - - # get the API singleton - houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api() - - node_inputs = {} - - # Create a geo input - geo_input = houdini_api.create_empty_input(unreal.HoudiniPublicAPIGeoInput) - # Set the input objects/assets for this input - geo_object = get_geo_asset() - geo_input.set_input_objects((geo_object, )) - # store the input data to the HDA as node input 0 - node_inputs[0] = geo_input - - # Create a curve input - curve_input = houdini_api.create_empty_input(unreal.HoudiniPublicAPICurveInput) - # Create a curve wrapper/helper - curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) - # Make it a Nurbs curve - curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) - # Set the points of the curve, for this example we create a helix - # consisting of 100 points - curve_points = [] - for i in range(100): - t = i / 20.0 * math.pi * 2.0 - x = 100.0 * math.cos(t) - y = 100.0 * math.sin(t) - z = i - curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) - curve_object.set_curve_points(curve_points) - # Set the curve wrapper as an input object - curve_input.set_input_objects((curve_object, )) - # Store the input data to the HDA as node input 1 - node_inputs[1] = curve_input - - return node_inputs - - -def run(): - # Create the processor with preconfigured inputs - global _g_processor - _g_processor = ProcessHDAExample( - get_test_hda(), node_inputs=build_inputs()) - # Activate the processor, this will starts instantiation, and then cook - if not _g_processor.activate(): - unreal.log_warning('Activation failed.') - else: - unreal.log('Activated!') - - -if __name__ == '__main__': - run() +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that uses the API and +HoudiniEngineV2.asyncprocessor.ProcessHDA. ProcessHDA is configured with the +asset to instantiate, as well as 2 inputs: a geometry input (a cube) and a +curve input (a helix). + +ProcessHDA is then activiated upon which the asset will be instantiated, +inputs set, and cooked. The ProcessHDA class's on_post_processing() function is +overridden to fetch the input structure and logged. The other state/phase +functions (on_pre_instantiate(), on_post_instantiate() etc) are overridden to +simply log the function name, in order to observe progress in the log. + +""" +import math + +import unreal + +from HoudiniEngineV2.asyncprocessor import ProcessHDA + + +_g_processor = None + + +class ProcessHDAExample(ProcessHDA): + @staticmethod + def _print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIGeoInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + elif isinstance(in_input, unreal.HoudiniPublicAPICurveInput): + print('\t\tbCookOnCurveChanged: {0}'.format(in_input.cook_on_curve_changed)) + print('\t\tbAddRotAndScaleAttributesOnCurves: {0}'.format(in_input.add_rot_and_scale_attributes_on_curves)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + if isinstance(input_object, unreal.HoudiniPublicAPICurveInputObject): + print('\t\t\tbClosed: {0}'.format(input_object.is_closed())) + print('\t\t\tCurveMethod: {0}'.format(input_object.get_curve_method())) + print('\t\t\tCurveType: {0}'.format(input_object.get_curve_type())) + print('\t\t\tReversed: {0}'.format(input_object.is_reversed())) + print('\t\t\tCurvePoints: {0}'.format(input_object.get_curve_points())) + + def on_failure(self): + print('on_failure') + global _g_processor + _g_processor = None + + def on_complete(self): + print('on_complete') + global _g_processor + _g_processor = None + + def on_pre_instantiation(self): + print('on_pre_instantiation') + + def on_post_instantiation(self): + print('on_post_instantiation') + + def on_post_auto_cook(self, cook_success): + print('on_post_auto_cook, success = {0}'.format(cook_success)) + + def on_pre_process(self): + print('on_pre_process') + + def on_post_processing(self): + print('on_post_processing') + + # Fetch inputs, iterate over it and log + node_inputs = self.asset_wrapper.get_inputs_at_indices() + parm_inputs = self.asset_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + self._print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + self._print_api_input(input_wrapper) + + def on_post_auto_bake(self, bake_success): + print('on_post_auto_bake, succes = {0}'.format(bake_success)) + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def build_inputs(): + print('configure_inputs') + + # get the API singleton + houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + node_inputs = {} + + # Create a geo input + geo_input = houdini_api.create_empty_input(unreal.HoudiniPublicAPIGeoInput) + # Set the input objects/assets for this input + geo_object = get_geo_asset() + geo_input.set_input_objects((geo_object, )) + # store the input data to the HDA as node input 0 + node_inputs[0] = geo_input + + # Create a curve input + curve_input = houdini_api.create_empty_input(unreal.HoudiniPublicAPICurveInput) + # Create a curve wrapper/helper + curve_object = unreal.HoudiniPublicAPICurveInputObject(curve_input) + # Make it a Nurbs curve + curve_object.set_curve_type(unreal.HoudiniPublicAPICurveType.NURBS) + # Set the points of the curve, for this example we create a helix + # consisting of 100 points + curve_points = [] + for i in range(100): + t = i / 20.0 * math.pi * 2.0 + x = 100.0 * math.cos(t) + y = 100.0 * math.sin(t) + z = i + curve_points.append(unreal.Transform([x, y, z], [0, 0, 0], [1, 1, 1])) + curve_object.set_curve_points(curve_points) + # Set the curve wrapper as an input object + curve_input.set_input_objects((curve_object, )) + # Store the input data to the HDA as node input 1 + node_inputs[1] = curve_input + + return node_inputs + + +def run(): + # Create the processor with preconfigured inputs + global _g_processor + _g_processor = ProcessHDAExample( + get_test_hda(), node_inputs=build_inputs()) + # Activate the processor, this will starts instantiation, and then cook + if not _g_processor.activate(): + unreal.log_warning('Activation failed.') + else: + unreal.log('Activated!') + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/ramp_parameter_example.py b/Content/Examples/Python/ramp_parameter_example.py index eca32f603..488eaa2c3 100644 --- a/Content/Examples/Python/ramp_parameter_example.py +++ b/Content/Examples/Python/ramp_parameter_example.py @@ -1,128 +1,152 @@ -""" An example script that uses the API to instantiate an HDA and then set -the ramp points of a float ramp and a color ramp. - -""" -import math - -import unreal - -_g_wrapper = None - - -def get_test_hda_path(): - return '/HoudiniEngine/Examples/hda/ramp_example_1_0.ramp_example_1_0' - - -def get_test_hda(): - return unreal.load_object(None, get_test_hda_path()) - - -def set_parameters(in_wrapper): - print('set_parameters') - - # Unbind from the delegate - in_wrapper.on_post_instantiation_delegate.remove_callable(set_parameters) - - # There are two ramps: heightramp and colorramp. The height ramp is a float - # ramp. As an example we'll set the number of ramp points and then set - # each point individually - in_wrapper.set_ramp_parameter_num_points('heightramp', 6) - in_wrapper.set_float_ramp_parameter_point_value('heightramp', 0, 0.0, 0.1) - in_wrapper.set_float_ramp_parameter_point_value('heightramp', 1, 0.2, 0.6) - in_wrapper.set_float_ramp_parameter_point_value('heightramp', 2, 0.4, 1.0) - in_wrapper.set_float_ramp_parameter_point_value('heightramp', 3, 0.6, 1.4) - in_wrapper.set_float_ramp_parameter_point_value('heightramp', 4, 0.8, 1.8) - in_wrapper.set_float_ramp_parameter_point_value('heightramp', 5, 1.0, 2.2) - - # For the color ramp, as an example, we can set the all the points via an - # array. - in_wrapper.set_color_ramp_parameter_points('colorramp', ( - unreal.HoudiniPublicAPIColorRampPoint(position=0.0, value=unreal.LinearColor.GRAY), - unreal.HoudiniPublicAPIColorRampPoint(position=0.5, value=unreal.LinearColor.GREEN), - unreal.HoudiniPublicAPIColorRampPoint(position=1.0, value=unreal.LinearColor.RED), - )) - - -def print_parameters(in_wrapper): - print('print_parameters') - - in_wrapper.on_post_processing_delegate.remove_callable(print_parameters) - - # Print the ramp points directly - print('heightramp: num points {0}:'.format(in_wrapper.get_ramp_parameter_num_points('heightramp'))) - heightramp_data = in_wrapper.get_float_ramp_parameter_points('heightramp') - if not heightramp_data: - print('\tNone') - else: - for idx, point_data in enumerate(heightramp_data): - print('\t\t{0}: position={1:.6f}; value={2:.6f}; interpoloation={3}'.format( - idx, - point_data.position, - point_data.value, - point_data.interpolation - )) - - print('colorramp: num points {0}:'.format(in_wrapper.get_ramp_parameter_num_points('colorramp'))) - colorramp_data = in_wrapper.get_color_ramp_parameter_points('colorramp') - if not colorramp_data: - print('\tNone') - else: - for idx, point_data in enumerate(colorramp_data): - print('\t\t{0}: position={1:.6f}; value={2}; interpoloation={3}'.format( - idx, - point_data.position, - point_data.value, - point_data.interpolation - )) - - # Print all parameter values - param_tuples = in_wrapper.get_parameter_tuples() - print('parameter tuples: {}'.format(len(param_tuples) if param_tuples else 0)) - if param_tuples: - for param_tuple_name, param_tuple in param_tuples.items(): - print('parameter tuple name: {}'.format(param_tuple_name)) - print('\tbool_values: {}'.format(param_tuple.bool_values)) - print('\tfloat_values: {}'.format(param_tuple.float_values)) - print('\tint32_values: {}'.format(param_tuple.int32_values)) - print('\tstring_values: {}'.format(param_tuple.string_values)) - if not param_tuple.float_ramp_points: - print('\tfloat_ramp_points: None') - else: - print('\tfloat_ramp_points:') - for idx, point_data in enumerate(param_tuple.float_ramp_points): - print('\t\t{0}: position={1:.6f}; value={2:.6f}; interpoloation={3}'.format( - idx, - point_data.position, - point_data.value, - point_data.interpolation - )) - if not param_tuple.color_ramp_points: - print('\tcolor_ramp_points: None') - else: - print('\tcolor_ramp_points:') - for idx, point_data in enumerate(param_tuple.color_ramp_points): - print('\t\t{0}: position={1:.6f}; value={2}; interpoloation={3}'.format( - idx, - point_data.position, - point_data.value, - point_data.interpolation - )) - - -def run(): - # get the API singleton - api = unreal.HoudiniPublicAPIBlueprintLib.get_api() - - global _g_wrapper - # instantiate an asset with auto-cook enabled - _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) - - # Set the float and color ramps on post instantiation, before the first - # cook. - _g_wrapper.on_post_instantiation_delegate.add_callable(set_parameters) - # Print the parameter state after the cook and output creation. - _g_wrapper.on_post_processing_delegate.add_callable(print_parameters) - - -if __name__ == '__main__': - run() +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that uses the API to instantiate an HDA and then set +the ramp points of a float ramp and a color ramp. + +""" +import math + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/ramp_example_1_0.ramp_example_1_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def set_parameters(in_wrapper): + print('set_parameters') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(set_parameters) + + # There are two ramps: heightramp and colorramp. The height ramp is a float + # ramp. As an example we'll set the number of ramp points and then set + # each point individually + in_wrapper.set_ramp_parameter_num_points('heightramp', 6) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 0, 0.0, 0.1) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 1, 0.2, 0.6) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 2, 0.4, 1.0) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 3, 0.6, 1.4) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 4, 0.8, 1.8) + in_wrapper.set_float_ramp_parameter_point_value('heightramp', 5, 1.0, 2.2) + + # For the color ramp, as an example, we can set the all the points via an + # array. + in_wrapper.set_color_ramp_parameter_points('colorramp', ( + unreal.HoudiniPublicAPIColorRampPoint(position=0.0, value=unreal.LinearColor.GRAY), + unreal.HoudiniPublicAPIColorRampPoint(position=0.5, value=unreal.LinearColor.GREEN), + unreal.HoudiniPublicAPIColorRampPoint(position=1.0, value=unreal.LinearColor.RED), + )) + + +def print_parameters(in_wrapper): + print('print_parameters') + + in_wrapper.on_post_processing_delegate.remove_callable(print_parameters) + + # Print the ramp points directly + print('heightramp: num points {0}:'.format(in_wrapper.get_ramp_parameter_num_points('heightramp'))) + heightramp_data = in_wrapper.get_float_ramp_parameter_points('heightramp') + if not heightramp_data: + print('\tNone') + else: + for idx, point_data in enumerate(heightramp_data): + print('\t\t{0}: position={1:.6f}; value={2:.6f}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + + print('colorramp: num points {0}:'.format(in_wrapper.get_ramp_parameter_num_points('colorramp'))) + colorramp_data = in_wrapper.get_color_ramp_parameter_points('colorramp') + if not colorramp_data: + print('\tNone') + else: + for idx, point_data in enumerate(colorramp_data): + print('\t\t{0}: position={1:.6f}; value={2}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + + # Print all parameter values + param_tuples = in_wrapper.get_parameter_tuples() + print('parameter tuples: {}'.format(len(param_tuples) if param_tuples else 0)) + if param_tuples: + for param_tuple_name, param_tuple in param_tuples.items(): + print('parameter tuple name: {}'.format(param_tuple_name)) + print('\tbool_values: {}'.format(param_tuple.bool_values)) + print('\tfloat_values: {}'.format(param_tuple.float_values)) + print('\tint32_values: {}'.format(param_tuple.int32_values)) + print('\tstring_values: {}'.format(param_tuple.string_values)) + if not param_tuple.float_ramp_points: + print('\tfloat_ramp_points: None') + else: + print('\tfloat_ramp_points:') + for idx, point_data in enumerate(param_tuple.float_ramp_points): + print('\t\t{0}: position={1:.6f}; value={2:.6f}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + if not param_tuple.color_ramp_points: + print('\tcolor_ramp_points: None') + else: + print('\tcolor_ramp_points:') + for idx, point_data in enumerate(param_tuple.color_ramp_points): + print('\t\t{0}: position={1:.6f}; value={2}; interpoloation={3}'.format( + idx, + point_data.position, + point_data.value, + point_data.interpolation + )) + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Set the float and color ramps on post instantiation, before the first + # cook. + _g_wrapper.on_post_instantiation_delegate.add_callable(set_parameters) + # Print the parameter state after the cook and output creation. + _g_wrapper.on_post_processing_delegate.add_callable(print_parameters) + + +if __name__ == '__main__': + run() diff --git a/Content/Examples/Python/start_session_example.py b/Content/Examples/Python/start_session_example.py index f8f41cc3c..e4f8854e0 100644 --- a/Content/Examples/Python/start_session_example.py +++ b/Content/Examples/Python/start_session_example.py @@ -1,3 +1,27 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + import unreal """ Example for getting the API instance and starting/creating the Houdini diff --git a/Content/Examples/Python/world_input_example.py b/Content/Examples/Python/world_input_example.py index 706bbc4dc..7b74e2929 100644 --- a/Content/Examples/Python/world_input_example.py +++ b/Content/Examples/Python/world_input_example.py @@ -1,163 +1,187 @@ -""" An example script that spawns some actors and then uses the API to -instantiate an HDA and set the actors as world inputs. The inputs are set -during post instantiation (before the first cook). After the first cook and -output creation (post processing) the input structure is fetched and logged. - -""" - -import unreal - -_g_wrapper = None - - -def get_test_hda_path(): - return '/HoudiniEngine/Examples/hda/subnet_test_2_0.subnet_test_2_0' - - -def get_test_hda(): - return unreal.load_object(None, get_test_hda_path()) - - -def get_geo_asset_path(): - return '/Engine/BasicShapes/Cube.Cube' - - -def get_geo_asset(): - return unreal.load_object(None, get_geo_asset_path()) - - -def get_cylinder_asset_path(): - return '/Engine/BasicShapes/Cylinder.Cylinder' - - -def get_cylinder_asset(): - return unreal.load_object(None, get_cylinder_asset_path()) - - -def configure_inputs(in_wrapper): - print('configure_inputs') - - # Unbind from the delegate - in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) - - # Spawn some actors - actors = spawn_actors() - - # Create a world input - world_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIWorldInput) - # Set the input objects/assets for this input - world_input.set_input_objects(actors) - # copy the input data to the HDA as node input 0 - in_wrapper.set_input_at_index(0, world_input) - # We can now discard the API input object - world_input = None - - # Set the subnet_test HDA to output its first input - in_wrapper.set_int_parameter_value('enable_geo', 1) - - -def print_api_input(in_input): - print('\t\tInput type: {0}'.format(in_input.__class__)) - print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) - print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) - if isinstance(in_input, unreal.HoudiniPublicAPIWorldInput): - print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) - print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) - print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) - print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) - print('\t\tbIsWorldInputBoundSelector: {0}'.format(in_input.is_world_input_bound_selector)) - print('\t\tbWorldInputBoundSelectorAutoUpdate: {0}'.format(in_input.world_input_bound_selector_auto_update)) - - input_objects = in_input.get_input_objects() - if not input_objects: - print('\t\tEmpty input!') - else: - print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) - for idx, input_object in enumerate(input_objects): - print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) - if hasattr(in_input, 'get_object_transform_offset'): - print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) - - -def print_inputs(in_wrapper): - print('print_inputs') - - # Unbind from the delegate - in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) - - # Fetch inputs, iterate over it and log - node_inputs = in_wrapper.get_inputs_at_indices() - parm_inputs = in_wrapper.get_input_parameters() - - if not node_inputs: - print('No node inputs found!') - else: - print('Number of node inputs: {0}'.format(len(node_inputs))) - for input_index, input_wrapper in node_inputs.items(): - print('\tInput index: {0}'.format(input_index)) - print_api_input(input_wrapper) - - if not parm_inputs: - print('No parameter inputs found!') - else: - print('Number of parameter inputs: {0}'.format(len(parm_inputs))) - for parm_name, input_wrapper in parm_inputs.items(): - print('\tInput parameter name: {0}'.format(parm_name)) - print_api_input(input_wrapper) - - -def spawn_actors(): - actors = [] - # Spawn a static mesh actor and assign a cylinder to its static mesh - # component - actor = unreal.EditorLevelLibrary.spawn_actor_from_class( - unreal.StaticMeshActor, location=(0, 0, 0)) - actor.static_mesh_component.set_static_mesh(get_cylinder_asset()) - actor.set_actor_label('Cylinder') - actor.set_actor_transform( - unreal.Transform( - (-200, 0, 0), - (0, 0, 0), - (2, 2, 2), - ), - sweep=False, - teleport=True - ) - actors.append(actor) - - # Spawn a static mesh actor and assign a cube to its static mesh - # component - actor = unreal.EditorLevelLibrary.spawn_actor_from_class( - unreal.StaticMeshActor, location=(0, 0, 0)) - actor.static_mesh_component.set_static_mesh(get_geo_asset()) - actor.set_actor_label('Cube') - actor.set_actor_transform( - unreal.Transform( - (200, 0, 100), - (45, 0, 45), - (2, 2, 2), - ), - sweep=False, - teleport=True - ) - actors.append(actor) - - return actors - - -def run(): - # get the API singleton - api = unreal.HoudiniPublicAPIBlueprintLib.get_api() - - global _g_wrapper - # instantiate an asset with auto-cook enabled - _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) - - # Configure inputs on_post_instantiation, after instantiation, but before first cook - _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) - # Bind on_post_processing, after cook + output creation - _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) - - -if __name__ == '__main__': - run() +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" An example script that spawns some actors and then uses the API to +instantiate an HDA and set the actors as world inputs. The inputs are set +during post instantiation (before the first cook). After the first cook and +output creation (post processing) the input structure is fetched and logged. + +""" + +import unreal + +_g_wrapper = None + + +def get_test_hda_path(): + return '/HoudiniEngine/Examples/hda/subnet_test_2_0.subnet_test_2_0' + + +def get_test_hda(): + return unreal.load_object(None, get_test_hda_path()) + + +def get_geo_asset_path(): + return '/Engine/BasicShapes/Cube.Cube' + + +def get_geo_asset(): + return unreal.load_object(None, get_geo_asset_path()) + + +def get_cylinder_asset_path(): + return '/Engine/BasicShapes/Cylinder.Cylinder' + + +def get_cylinder_asset(): + return unreal.load_object(None, get_cylinder_asset_path()) + + +def configure_inputs(in_wrapper): + print('configure_inputs') + + # Unbind from the delegate + in_wrapper.on_post_instantiation_delegate.remove_callable(configure_inputs) + + # Spawn some actors + actors = spawn_actors() + + # Create a world input + world_input = in_wrapper.create_empty_input(unreal.HoudiniPublicAPIWorldInput) + # Set the input objects/assets for this input + world_input.set_input_objects(actors) + # copy the input data to the HDA as node input 0 + in_wrapper.set_input_at_index(0, world_input) + # We can now discard the API input object + world_input = None + + # Set the subnet_test HDA to output its first input + in_wrapper.set_int_parameter_value('enable_geo', 1) + + +def print_api_input(in_input): + print('\t\tInput type: {0}'.format(in_input.__class__)) + print('\t\tbKeepWorldTransform: {0}'.format(in_input.keep_world_transform)) + print('\t\tbImportAsReference: {0}'.format(in_input.import_as_reference)) + if isinstance(in_input, unreal.HoudiniPublicAPIWorldInput): + print('\t\tbPackBeforeMerge: {0}'.format(in_input.pack_before_merge)) + print('\t\tbExportLODs: {0}'.format(in_input.export_lo_ds)) + print('\t\tbExportSockets: {0}'.format(in_input.export_sockets)) + print('\t\tbExportColliders: {0}'.format(in_input.export_colliders)) + print('\t\tbIsWorldInputBoundSelector: {0}'.format(in_input.is_world_input_bound_selector)) + print('\t\tbWorldInputBoundSelectorAutoUpdate: {0}'.format(in_input.world_input_bound_selector_auto_update)) + + input_objects = in_input.get_input_objects() + if not input_objects: + print('\t\tEmpty input!') + else: + print('\t\tNumber of objects in input: {0}'.format(len(input_objects))) + for idx, input_object in enumerate(input_objects): + print('\t\t\tInput object #{0}: {1}'.format(idx, input_object)) + if hasattr(in_input, 'get_object_transform_offset'): + print('\t\t\tObject Transform Offset: {0}'.format(in_input.get_object_transform_offset(input_object))) + + +def print_inputs(in_wrapper): + print('print_inputs') + + # Unbind from the delegate + in_wrapper.on_post_processing_delegate.remove_callable(print_inputs) + + # Fetch inputs, iterate over it and log + node_inputs = in_wrapper.get_inputs_at_indices() + parm_inputs = in_wrapper.get_input_parameters() + + if not node_inputs: + print('No node inputs found!') + else: + print('Number of node inputs: {0}'.format(len(node_inputs))) + for input_index, input_wrapper in node_inputs.items(): + print('\tInput index: {0}'.format(input_index)) + print_api_input(input_wrapper) + + if not parm_inputs: + print('No parameter inputs found!') + else: + print('Number of parameter inputs: {0}'.format(len(parm_inputs))) + for parm_name, input_wrapper in parm_inputs.items(): + print('\tInput parameter name: {0}'.format(parm_name)) + print_api_input(input_wrapper) + + +def spawn_actors(): + actors = [] + # Spawn a static mesh actor and assign a cylinder to its static mesh + # component + actor = unreal.EditorLevelLibrary.spawn_actor_from_class( + unreal.StaticMeshActor, location=(0, 0, 0)) + actor.static_mesh_component.set_static_mesh(get_cylinder_asset()) + actor.set_actor_label('Cylinder') + actor.set_actor_transform( + unreal.Transform( + (-200, 0, 0), + (0, 0, 0), + (2, 2, 2), + ), + sweep=False, + teleport=True + ) + actors.append(actor) + + # Spawn a static mesh actor and assign a cube to its static mesh + # component + actor = unreal.EditorLevelLibrary.spawn_actor_from_class( + unreal.StaticMeshActor, location=(0, 0, 0)) + actor.static_mesh_component.set_static_mesh(get_geo_asset()) + actor.set_actor_label('Cube') + actor.set_actor_transform( + unreal.Transform( + (200, 0, 100), + (45, 0, 45), + (2, 2, 2), + ), + sweep=False, + teleport=True + ) + actors.append(actor) + + return actors + + +def run(): + # get the API singleton + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + global _g_wrapper + # instantiate an asset with auto-cook enabled + _g_wrapper = api.instantiate_asset(get_test_hda(), unreal.Transform()) + + # Configure inputs on_post_instantiation, after instantiation, but before first cook + _g_wrapper.on_post_instantiation_delegate.add_callable(configure_inputs) + # Bind on_post_processing, after cook + output creation + _g_wrapper.on_post_processing_delegate.add_callable(print_inputs) + + +if __name__ == '__main__': + run() diff --git a/HoudiniEngine.uplugin b/HoudiniEngine.uplugin index 6258a8302..6c5b74e75 100644 --- a/HoudiniEngine.uplugin +++ b/HoudiniEngine.uplugin @@ -1,8 +1,8 @@ { "FileVersion" : 3, "FriendlyName" : "Houdini Engine v2", - "Version" : 18050596, - "VersionName" : "v2.0 - H18.5.596", + "Version" : 18050633, + "VersionName" : "v2.0 - H18.5.633", "CreatedBy" : "Side Effects Software Inc.", "CreatedByURL" : "http://www.sidefx.com", "DocsURL" : "http://www.sidefx.com/docs/unreal/", diff --git a/Source/HoudiniEngine/HoudiniEngine.Build.cs b/Source/HoudiniEngine/HoudiniEngine.Build.cs index 76fd317f0..8be19f614 100644 --- a/Source/HoudiniEngine/HoudiniEngine.Build.cs +++ b/Source/HoudiniEngine/HoudiniEngine.Build.cs @@ -32,8 +32,8 @@ /* - Houdini Version: 18.5.596 - Houdini Engine Version: 3.6.2 + Houdini Version: 18.5.633 + Houdini Engine Version: 3.6.3 Unreal Version: 4.26.0 */ @@ -47,9 +47,9 @@ public class HoudiniEngine : ModuleRules { private string GetHFSPath() { - string HoudiniVersion = "18.5.596"; + string HoudiniVersion = "18.5.633"; bool bIsRelease = true; - string HFSPath = ""; + string HFSPath = "C:/cygwin/home/prisms/builder-new/Nightly18.5CMakePython3/dev/hfs"; string RegistryPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Side Effects Software"; string Registry32Path = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Side Effects Software"; string log; diff --git a/Source/HoudiniEngine/Private/HBSPOps.cpp b/Source/HoudiniEngine/Private/HBSPOps.cpp index 1fcee919d..a6d318b36 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.cpp +++ b/Source/HoudiniEngine/Private/HBSPOps.cpp @@ -199,7 +199,7 @@ static void FilterBound FMemMark Mark(FMemStack::Get()); FBspNode& Node = Model->Nodes [iNode]; FBspSurf& Surf = Model->Surfs [Node.iSurf]; - FVector Base = Surf.Plane * Surf.Plane.W; + FVector Base = Surf.Plane * Surf.Plane.W; FVector& Normal = Model->Vectors[Surf.vNormal]; FBox Bound(ForceInit); @@ -665,11 +665,11 @@ ABrush* FHBSPOps::csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType B } /** Add a new point to the model (preventing duplicates) and return its index. */ -static int32 AddThing( TArray& Vectors, FVector& V, float Thresh, int32 Check ) +static int32 AddThing( TArray& Vectors, const FVector& V, float Thresh, int32 Check ) { if( Check ) { - // See if this is very close to an existing point/vector. + // See if this is very close to an existing point/vector. for( int32 i=0; iSurfs[NewIndex]; // This node has a new polygon being added by bspBrushCSG; must set its properties here. - Surf->pBase = bspAddPoint (Model,&EdPoly->Base,1,BspPoints); - Surf->vNormal = bspAddVector (Model,&EdPoly->Normal,1,BspVectors); - Surf->vTextureU = bspAddVector (Model,&EdPoly->TextureU,0,BspVectors); - Surf->vTextureV = bspAddVector (Model,&EdPoly->TextureV,0,BspVectors); + FVector Base = EdPoly->Base, Normal = EdPoly->Normal, TextureU = EdPoly->TextureU, TextureV = EdPoly->TextureV; + Surf->pBase = bspAddPoint (Model,&Base,1,BspPoints); + Surf->vNormal = bspAddVector (Model,&Normal,1,BspVectors); + Surf->vTextureU = bspAddVector (Model,&TextureU,0,BspVectors); + Surf->vTextureV = bspAddVector (Model,&TextureV,0,BspVectors); Surf->Material = EdPoly->Material; Surf->Actor = NULL; @@ -1269,7 +1270,8 @@ int32 FHBSPOps::bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePl FVert* VertPool = &Model->Verts[ Node.iVertPool ]; for( uint8 i=0; iVertices.Num(); i++ ) { - int32 pVertex = bspAddPoint(Model,&EdPoly->Vertices[i],0, BspPoints); + FVector Vertex = EdPoly->Vertices[i]; + int32 pVertex = bspAddPoint(Model,&Vertex,0, BspPoints); if( Node.NumVertices==0 || VertPool[Node.NumVertices-1].pVertex!=pVertex ) { VertPool[Node.NumVertices].iSide = INDEX_NONE; @@ -1481,3 +1483,4 @@ int32 UHBspPointsGrid::FindOrAddPoint(const FVector& Point, int32 Index, float P return Index; } + diff --git a/Source/HoudiniEngine/Private/HCsgUtils.cpp b/Source/HoudiniEngine/Private/HCsgUtils.cpp index 4b7d09a8a..6d31b3732 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.cpp +++ b/Source/HoudiniEngine/Private/HCsgUtils.cpp @@ -1459,3 +1459,4 @@ void UHCsgUtils::bspCleanup( UModel *Model ) if( Model->Nodes.Num() > 0 ) CleanupNodes( Model, 0, INDEX_NONE ); } + diff --git a/Source/HoudiniEngine/Private/HoudiniApi.cpp b/Source/HoudiniEngine/Private/HoudiniApi.cpp index 0fef0024c..b3bb9b89d 100644 --- a/Source/HoudiniEngine/Private/HoudiniApi.cpp +++ b/Source/HoudiniEngine/Private/HoudiniApi.cpp @@ -1,3879 +1,3963 @@ -/* - * Copyright (c) <2021> 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. - * - * - * COMMENTS: - * This file is generated. Do not modify directly. - */ - -#include "HoudiniApi.h" -#include "HoudiniEnginePrivatePCH.h" - - -FHoudiniApi::AddAttributeFuncPtr -FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; - -FHoudiniApi::AddGroupFuncPtr -FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; - -FHoudiniApi::AssetInfo_CreateFuncPtr -FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; - -FHoudiniApi::AssetInfo_InitFuncPtr -FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; - -FHoudiniApi::AttributeInfo_CreateFuncPtr -FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; - -FHoudiniApi::AttributeInfo_InitFuncPtr -FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; - -FHoudiniApi::BindCustomImplementationFuncPtr -FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; - -FHoudiniApi::CancelPDGCookFuncPtr -FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; - -FHoudiniApi::CheckForSpecificErrorsFuncPtr -FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; - -FHoudiniApi::CleanupFuncPtr -FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; - -FHoudiniApi::ClearConnectionErrorFuncPtr -FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; - -FHoudiniApi::CloseSessionFuncPtr -FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; - -FHoudiniApi::CommitGeoFuncPtr -FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; - -FHoudiniApi::CommitWorkitemsFuncPtr -FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; - -FHoudiniApi::ComposeChildNodeListFuncPtr -FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; - -FHoudiniApi::ComposeNodeCookResultFuncPtr -FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; - -FHoudiniApi::ComposeObjectListFuncPtr -FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; - -FHoudiniApi::ConnectNodeInputFuncPtr -FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; - -FHoudiniApi::ConvertMatrixToEulerFuncPtr -FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; - -FHoudiniApi::ConvertMatrixToQuatFuncPtr -FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; - -FHoudiniApi::ConvertTransformFuncPtr -FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; - -FHoudiniApi::ConvertTransformEulerToMatrixFuncPtr -FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; - -FHoudiniApi::ConvertTransformQuatToMatrixFuncPtr -FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; - -FHoudiniApi::CookNodeFuncPtr -FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; - -FHoudiniApi::CookOptions_AreEqualFuncPtr -FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; - -FHoudiniApi::CookOptions_CreateFuncPtr -FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; - -FHoudiniApi::CookOptions_InitFuncPtr -FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; - -FHoudiniApi::CookPDGFuncPtr -FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; - -FHoudiniApi::CreateCustomSessionFuncPtr -FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; - -FHoudiniApi::CreateHeightFieldInputFuncPtr -FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; - -FHoudiniApi::CreateHeightfieldInputVolumeNodeFuncPtr -FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; - -FHoudiniApi::CreateInProcessSessionFuncPtr -FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; - -FHoudiniApi::CreateInputNodeFuncPtr -FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; - -FHoudiniApi::CreateNodeFuncPtr -FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; - -FHoudiniApi::CreateThriftNamedPipeSessionFuncPtr -FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; - -FHoudiniApi::CreateThriftSocketSessionFuncPtr -FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; - -FHoudiniApi::CreateWorkitemFuncPtr -FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; - -FHoudiniApi::CurveInfo_CreateFuncPtr -FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; - -FHoudiniApi::CurveInfo_InitFuncPtr -FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; - -FHoudiniApi::DeleteAttributeFuncPtr -FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; - -FHoudiniApi::DeleteGroupFuncPtr -FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; - -FHoudiniApi::DeleteNodeFuncPtr -FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; - -FHoudiniApi::DirtyPDGNodeFuncPtr -FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; - -FHoudiniApi::DisconnectNodeInputFuncPtr -FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; - -FHoudiniApi::DisconnectNodeOutputsAtFuncPtr -FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; - -FHoudiniApi::ExtractImageToFileFuncPtr -FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; - -FHoudiniApi::ExtractImageToMemoryFuncPtr -FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; - -FHoudiniApi::GeoInfo_CreateFuncPtr -FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; - -FHoudiniApi::GeoInfo_GetGroupCountByTypeFuncPtr -FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; - -FHoudiniApi::GeoInfo_InitFuncPtr -FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; - -FHoudiniApi::GetActiveCacheCountFuncPtr -FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; - -FHoudiniApi::GetActiveCacheNamesFuncPtr -FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmCountsFuncPtr -FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmInfosFuncPtr -FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmValuesFuncPtr -FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; - -FHoudiniApi::GetAssetInfoFuncPtr -FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; - -FHoudiniApi::GetAttributeFloat64ArrayDataFuncPtr -FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeFloat64DataFuncPtr -FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; - -FHoudiniApi::GetAttributeFloatArrayDataFuncPtr -FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; - -FHoudiniApi::GetAttributeFloatDataFuncPtr -FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; - -FHoudiniApi::GetAttributeInfoFuncPtr -FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; - -FHoudiniApi::GetAttributeInt16ArrayDataFuncPtr -FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeInt16DataFuncPtr -FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; - -FHoudiniApi::GetAttributeInt64ArrayDataFuncPtr -FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeInt64DataFuncPtr -FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; - -FHoudiniApi::GetAttributeInt8ArrayDataFuncPtr -FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeInt8DataFuncPtr -FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; - -FHoudiniApi::GetAttributeIntArrayDataFuncPtr -FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; - -FHoudiniApi::GetAttributeIntDataFuncPtr -FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; - -FHoudiniApi::GetAttributeNamesFuncPtr -FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; - -FHoudiniApi::GetAttributeStringArrayDataFuncPtr -FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; - -FHoudiniApi::GetAttributeStringDataFuncPtr -FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; - -FHoudiniApi::GetAttributeUInt8ArrayDataFuncPtr -FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeUInt8DataFuncPtr -FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; - -FHoudiniApi::GetAvailableAssetCountFuncPtr -FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; - -FHoudiniApi::GetAvailableAssetsFuncPtr -FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; - -FHoudiniApi::GetBoxInfoFuncPtr -FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; - -FHoudiniApi::GetCachePropertyFuncPtr -FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; - -FHoudiniApi::GetComposedChildNodeListFuncPtr -FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; - -FHoudiniApi::GetComposedNodeCookResultFuncPtr -FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; - -FHoudiniApi::GetComposedObjectListFuncPtr -FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; - -FHoudiniApi::GetComposedObjectTransformsFuncPtr -FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; - -FHoudiniApi::GetConnectionErrorFuncPtr -FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; - -FHoudiniApi::GetConnectionErrorLengthFuncPtr -FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; - -FHoudiniApi::GetCookingCurrentCountFuncPtr -FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; - -FHoudiniApi::GetCookingTotalCountFuncPtr -FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; - -FHoudiniApi::GetCurveCountsFuncPtr -FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; - -FHoudiniApi::GetCurveInfoFuncPtr -FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; - -FHoudiniApi::GetCurveKnotsFuncPtr -FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; - -FHoudiniApi::GetCurveOrdersFuncPtr -FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; - -FHoudiniApi::GetDisplayGeoInfoFuncPtr -FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; - -FHoudiniApi::GetEnvIntFuncPtr -FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; - -FHoudiniApi::GetFaceCountsFuncPtr -FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; - -FHoudiniApi::GetFirstVolumeTileFuncPtr -FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; - -FHoudiniApi::GetGeoInfoFuncPtr -FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; - -FHoudiniApi::GetGeoSizeFuncPtr -FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; - -FHoudiniApi::GetGroupCountOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetGroupMembershipFuncPtr -FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; - -FHoudiniApi::GetGroupMembershipOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetGroupNamesFuncPtr -FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; - -FHoudiniApi::GetGroupNamesOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetHIPFileNodeCountFuncPtr -FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; - -FHoudiniApi::GetHIPFileNodeIdsFuncPtr -FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; - -FHoudiniApi::GetHandleBindingInfoFuncPtr -FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; - -FHoudiniApi::GetHandleInfoFuncPtr -FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; - -FHoudiniApi::GetHeightFieldDataFuncPtr -FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; - -FHoudiniApi::GetImageFilePathFuncPtr -FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; - -FHoudiniApi::GetImageInfoFuncPtr -FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; - -FHoudiniApi::GetImageMemoryBufferFuncPtr -FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; - -FHoudiniApi::GetImagePlaneCountFuncPtr -FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; - -FHoudiniApi::GetImagePlanesFuncPtr -FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; - -FHoudiniApi::GetInstanceTransformsOnPartFuncPtr -FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; - -FHoudiniApi::GetInstancedObjectIdsFuncPtr -FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; - -FHoudiniApi::GetInstancedPartIdsFuncPtr -FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; - -FHoudiniApi::GetInstancerPartTransformsFuncPtr -FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; - -FHoudiniApi::GetManagerNodeIdFuncPtr -FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; - -FHoudiniApi::GetMaterialInfoFuncPtr -FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; - -FHoudiniApi::GetMaterialNodeIdsOnFacesFuncPtr -FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; - -FHoudiniApi::GetNextVolumeTileFuncPtr -FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; - -FHoudiniApi::GetNodeInfoFuncPtr -FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; - -FHoudiniApi::GetNodeInputNameFuncPtr -FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; - -FHoudiniApi::GetNodeOutputNameFuncPtr -FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; - -FHoudiniApi::GetNodePathFuncPtr -FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; - -FHoudiniApi::GetNumWorkitemsFuncPtr -FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; - -FHoudiniApi::GetObjectInfoFuncPtr -FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; - -FHoudiniApi::GetObjectTransformFuncPtr -FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; - -FHoudiniApi::GetOutputNodeIdFuncPtr -FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; - -FHoudiniApi::GetPDGEventsFuncPtr -FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; - -FHoudiniApi::GetPDGGraphContextIdFuncPtr -FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; - -FHoudiniApi::GetPDGGraphContextsFuncPtr -FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; - -FHoudiniApi::GetPDGStateFuncPtr -FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; - -FHoudiniApi::GetParametersFuncPtr -FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; - -FHoudiniApi::GetParmChoiceListsFuncPtr -FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; - -FHoudiniApi::GetParmExpressionFuncPtr -FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; - -FHoudiniApi::GetParmFileFuncPtr -FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; - -FHoudiniApi::GetParmFloatValueFuncPtr -FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; - -FHoudiniApi::GetParmFloatValuesFuncPtr -FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; - -FHoudiniApi::GetParmIdFromNameFuncPtr -FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; - -FHoudiniApi::GetParmInfoFuncPtr -FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; - -FHoudiniApi::GetParmInfoFromNameFuncPtr -FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; - -FHoudiniApi::GetParmIntValueFuncPtr -FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; - -FHoudiniApi::GetParmIntValuesFuncPtr -FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; - -FHoudiniApi::GetParmNodeValueFuncPtr -FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; - -FHoudiniApi::GetParmStringValueFuncPtr -FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; - -FHoudiniApi::GetParmStringValuesFuncPtr -FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; - -FHoudiniApi::GetParmTagNameFuncPtr -FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; - -FHoudiniApi::GetParmTagValueFuncPtr -FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; - -FHoudiniApi::GetParmWithTagFuncPtr -FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; - -FHoudiniApi::GetPartInfoFuncPtr -FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; - -FHoudiniApi::GetPresetFuncPtr -FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; - -FHoudiniApi::GetPresetBufLengthFuncPtr -FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; - -FHoudiniApi::GetServerEnvIntFuncPtr -FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; - -FHoudiniApi::GetServerEnvStringFuncPtr -FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; - -FHoudiniApi::GetServerEnvVarCountFuncPtr -FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; - -FHoudiniApi::GetServerEnvVarListFuncPtr -FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; - -FHoudiniApi::GetSessionEnvIntFuncPtr -FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; - -FHoudiniApi::GetSessionSyncInfoFuncPtr -FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; - -FHoudiniApi::GetSphereInfoFuncPtr -FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; - -FHoudiniApi::GetStatusFuncPtr -FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; - -FHoudiniApi::GetStatusStringFuncPtr -FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; - -FHoudiniApi::GetStatusStringBufLengthFuncPtr -FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; - -FHoudiniApi::GetStringFuncPtr -FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; - -FHoudiniApi::GetStringBatchFuncPtr -FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; - -FHoudiniApi::GetStringBatchSizeFuncPtr -FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; - -FHoudiniApi::GetStringBufLengthFuncPtr -FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; - -FHoudiniApi::GetSupportedImageFileFormatCountFuncPtr -FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; - -FHoudiniApi::GetSupportedImageFileFormatsFuncPtr -FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; - -FHoudiniApi::GetTimeFuncPtr -FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; - -FHoudiniApi::GetTimelineOptionsFuncPtr -FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; - -FHoudiniApi::GetTotalCookCountFuncPtr -FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; - -FHoudiniApi::GetUseHoudiniTimeFuncPtr -FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; - -FHoudiniApi::GetVertexListFuncPtr -FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; - -FHoudiniApi::GetViewportFuncPtr -FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; - -FHoudiniApi::GetVolumeBoundsFuncPtr -FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; - -FHoudiniApi::GetVolumeInfoFuncPtr -FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; - -FHoudiniApi::GetVolumeTileFloatDataFuncPtr -FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; - -FHoudiniApi::GetVolumeTileIntDataFuncPtr -FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; - -FHoudiniApi::GetVolumeVisualInfoFuncPtr -FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; - -FHoudiniApi::GetVolumeVoxelFloatDataFuncPtr -FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; - -FHoudiniApi::GetVolumeVoxelIntDataFuncPtr -FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; - -FHoudiniApi::GetWorkitemDataLengthFuncPtr -FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; - -FHoudiniApi::GetWorkitemFloatDataFuncPtr -FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; - -FHoudiniApi::GetWorkitemInfoFuncPtr -FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; - -FHoudiniApi::GetWorkitemIntDataFuncPtr -FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; - -FHoudiniApi::GetWorkitemResultInfoFuncPtr -FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; - -FHoudiniApi::GetWorkitemStringDataFuncPtr -FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; - -FHoudiniApi::GetWorkitemsFuncPtr -FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; - -FHoudiniApi::HandleBindingInfo_CreateFuncPtr -FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; - -FHoudiniApi::HandleBindingInfo_InitFuncPtr -FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; - -FHoudiniApi::HandleInfo_CreateFuncPtr -FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; - -FHoudiniApi::HandleInfo_InitFuncPtr -FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; - -FHoudiniApi::ImageFileFormat_CreateFuncPtr -FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; - -FHoudiniApi::ImageFileFormat_InitFuncPtr -FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; - -FHoudiniApi::ImageInfo_CreateFuncPtr -FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; - -FHoudiniApi::ImageInfo_InitFuncPtr -FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; - -FHoudiniApi::InitializeFuncPtr -FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; - -FHoudiniApi::InsertMultiparmInstanceFuncPtr -FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; - -FHoudiniApi::InterruptFuncPtr -FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; - -FHoudiniApi::IsInitializedFuncPtr -FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; - -FHoudiniApi::IsNodeValidFuncPtr -FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; - -FHoudiniApi::IsSessionValidFuncPtr -FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; - -FHoudiniApi::Keyframe_CreateFuncPtr -FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; - -FHoudiniApi::Keyframe_InitFuncPtr -FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; - -FHoudiniApi::LoadAssetLibraryFromFileFuncPtr -FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; - -FHoudiniApi::LoadAssetLibraryFromMemoryFuncPtr -FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; - -FHoudiniApi::LoadGeoFromFileFuncPtr -FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; - -FHoudiniApi::LoadGeoFromMemoryFuncPtr -FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; - -FHoudiniApi::LoadHIPFileFuncPtr -FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; - -FHoudiniApi::LoadNodeFromFileFuncPtr -FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; - -FHoudiniApi::MaterialInfo_CreateFuncPtr -FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; - -FHoudiniApi::MaterialInfo_InitFuncPtr -FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; - -FHoudiniApi::MergeHIPFileFuncPtr -FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; - -FHoudiniApi::NodeInfo_CreateFuncPtr -FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; - -FHoudiniApi::NodeInfo_InitFuncPtr -FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; - -FHoudiniApi::ObjectInfo_CreateFuncPtr -FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; - -FHoudiniApi::ObjectInfo_InitFuncPtr -FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; - -FHoudiniApi::ParmChoiceInfo_CreateFuncPtr -FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; - -FHoudiniApi::ParmChoiceInfo_InitFuncPtr -FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; - -FHoudiniApi::ParmHasExpressionFuncPtr -FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; - -FHoudiniApi::ParmHasTagFuncPtr -FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; - -FHoudiniApi::ParmInfo_CreateFuncPtr -FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; - -FHoudiniApi::ParmInfo_GetFloatValueCountFuncPtr -FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; - -FHoudiniApi::ParmInfo_GetIntValueCountFuncPtr -FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; - -FHoudiniApi::ParmInfo_GetStringValueCountFuncPtr -FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; - -FHoudiniApi::ParmInfo_InitFuncPtr -FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; - -FHoudiniApi::ParmInfo_IsFloatFuncPtr -FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; - -FHoudiniApi::ParmInfo_IsIntFuncPtr -FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; - -FHoudiniApi::ParmInfo_IsNodeFuncPtr -FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; - -FHoudiniApi::ParmInfo_IsNonValueFuncPtr -FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; - -FHoudiniApi::ParmInfo_IsPathFuncPtr -FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; - -FHoudiniApi::ParmInfo_IsStringFuncPtr -FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; - -FHoudiniApi::PartInfo_CreateFuncPtr -FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; - -FHoudiniApi::PartInfo_GetAttributeCountByOwnerFuncPtr -FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; - -FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerFuncPtr -FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; - -FHoudiniApi::PartInfo_GetElementCountByGroupTypeFuncPtr -FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; - -FHoudiniApi::PartInfo_InitFuncPtr -FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; - -FHoudiniApi::PausePDGCookFuncPtr -FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; - -FHoudiniApi::PythonThreadInterpreterLockFuncPtr -FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; - -FHoudiniApi::QueryNodeInputFuncPtr -FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; - -FHoudiniApi::QueryNodeOutputConnectedCountFuncPtr -FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; - -FHoudiniApi::QueryNodeOutputConnectedNodesFuncPtr -FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; - -FHoudiniApi::RemoveCustomStringFuncPtr -FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; - -FHoudiniApi::RemoveMultiparmInstanceFuncPtr -FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; - -FHoudiniApi::RemoveParmExpressionFuncPtr -FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; - -FHoudiniApi::RenameNodeFuncPtr -FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; - -FHoudiniApi::RenderCOPToImageFuncPtr -FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; - -FHoudiniApi::RenderTextureToImageFuncPtr -FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; - -FHoudiniApi::ResetSimulationFuncPtr -FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; - -FHoudiniApi::RevertGeoFuncPtr -FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; - -FHoudiniApi::RevertParmToDefaultFuncPtr -FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; - -FHoudiniApi::RevertParmToDefaultsFuncPtr -FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; - -FHoudiniApi::SaveGeoToFileFuncPtr -FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; - -FHoudiniApi::SaveGeoToMemoryFuncPtr -FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; - -FHoudiniApi::SaveHIPFileFuncPtr -FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; - -FHoudiniApi::SaveNodeToFileFuncPtr -FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; - -FHoudiniApi::SessionSyncInfo_CreateFuncPtr -FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; - -FHoudiniApi::SetAnimCurveFuncPtr -FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; - -FHoudiniApi::SetAttributeFloat64DataFuncPtr -FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; - -FHoudiniApi::SetAttributeFloatDataFuncPtr -FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; - -FHoudiniApi::SetAttributeInt16DataFuncPtr -FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; - -FHoudiniApi::SetAttributeInt64DataFuncPtr -FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; - -FHoudiniApi::SetAttributeInt8DataFuncPtr -FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; - -FHoudiniApi::SetAttributeIntDataFuncPtr -FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; - -FHoudiniApi::SetAttributeStringDataFuncPtr -FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; - -FHoudiniApi::SetAttributeUInt8DataFuncPtr -FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; - -FHoudiniApi::SetCachePropertyFuncPtr -FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; - -FHoudiniApi::SetCurveCountsFuncPtr -FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; - -FHoudiniApi::SetCurveInfoFuncPtr -FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; - -FHoudiniApi::SetCurveKnotsFuncPtr -FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; - -FHoudiniApi::SetCurveOrdersFuncPtr -FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; - -FHoudiniApi::SetCustomStringFuncPtr -FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; - -FHoudiniApi::SetFaceCountsFuncPtr -FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; - -FHoudiniApi::SetGroupMembershipFuncPtr -FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; - -FHoudiniApi::SetHeightFieldDataFuncPtr -FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; - -FHoudiniApi::SetImageInfoFuncPtr -FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; - -FHoudiniApi::SetNodeDisplayFuncPtr -FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; - -FHoudiniApi::SetObjectTransformFuncPtr -FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; - -FHoudiniApi::SetParmExpressionFuncPtr -FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; - -FHoudiniApi::SetParmFloatValueFuncPtr -FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; - -FHoudiniApi::SetParmFloatValuesFuncPtr -FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; - -FHoudiniApi::SetParmIntValueFuncPtr -FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; - -FHoudiniApi::SetParmIntValuesFuncPtr -FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; - -FHoudiniApi::SetParmNodeValueFuncPtr -FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; - -FHoudiniApi::SetParmStringValueFuncPtr -FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; - -FHoudiniApi::SetPartInfoFuncPtr -FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; - -FHoudiniApi::SetPresetFuncPtr -FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; - -FHoudiniApi::SetServerEnvIntFuncPtr -FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; - -FHoudiniApi::SetServerEnvStringFuncPtr -FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; - -FHoudiniApi::SetSessionSyncFuncPtr -FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; - -FHoudiniApi::SetSessionSyncInfoFuncPtr -FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; - -FHoudiniApi::SetTimeFuncPtr -FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; - -FHoudiniApi::SetTimelineOptionsFuncPtr -FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; - -FHoudiniApi::SetTransformAnimCurveFuncPtr -FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; - -FHoudiniApi::SetUseHoudiniTimeFuncPtr -FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; - -FHoudiniApi::SetVertexListFuncPtr -FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; - -FHoudiniApi::SetViewportFuncPtr -FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; - -FHoudiniApi::SetVolumeInfoFuncPtr -FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; - -FHoudiniApi::SetVolumeTileFloatDataFuncPtr -FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; - -FHoudiniApi::SetVolumeTileIntDataFuncPtr -FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; - -FHoudiniApi::SetVolumeVoxelFloatDataFuncPtr -FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; - -FHoudiniApi::SetVolumeVoxelIntDataFuncPtr -FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; - -FHoudiniApi::SetWorkitemFloatDataFuncPtr -FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; - -FHoudiniApi::SetWorkitemIntDataFuncPtr -FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; - -FHoudiniApi::SetWorkitemStringDataFuncPtr -FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; - -FHoudiniApi::StartThriftNamedPipeServerFuncPtr -FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; - -FHoudiniApi::StartThriftSocketServerFuncPtr -FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; - -FHoudiniApi::ThriftServerOptions_CreateFuncPtr -FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; - -FHoudiniApi::ThriftServerOptions_InitFuncPtr -FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; - -FHoudiniApi::TimelineOptions_CreateFuncPtr -FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; - -FHoudiniApi::TimelineOptions_InitFuncPtr -FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; - -FHoudiniApi::TransformEuler_CreateFuncPtr -FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; - -FHoudiniApi::TransformEuler_InitFuncPtr -FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; - -FHoudiniApi::Transform_CreateFuncPtr -FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; - -FHoudiniApi::Transform_InitFuncPtr -FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; - -FHoudiniApi::Viewport_CreateFuncPtr -FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; - -FHoudiniApi::VolumeInfo_CreateFuncPtr -FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; - -FHoudiniApi::VolumeInfo_InitFuncPtr -FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; - -FHoudiniApi::VolumeTileInfo_CreateFuncPtr -FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; - -FHoudiniApi::VolumeTileInfo_InitFuncPtr -FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; - - -void -FHoudiniApi::InitializeHAPI(void* LibraryHandle) -{ - if(!LibraryHandle) return; - - FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute")); - FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup")); - FHoudiniApi::AssetInfo_Create = (AssetInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Create")); - FHoudiniApi::AssetInfo_Init = (AssetInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Init")); - FHoudiniApi::AttributeInfo_Create = (AttributeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Create")); - FHoudiniApi::AttributeInfo_Init = (AttributeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Init")); - FHoudiniApi::BindCustomImplementation = (BindCustomImplementationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_BindCustomImplementation")); - FHoudiniApi::CancelPDGCook = (CancelPDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CancelPDGCook")); - FHoudiniApi::CheckForSpecificErrors = (CheckForSpecificErrorsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CheckForSpecificErrors")); - FHoudiniApi::Cleanup = (CleanupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Cleanup")); - FHoudiniApi::ClearConnectionError = (ClearConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ClearConnectionError")); - FHoudiniApi::CloseSession = (CloseSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CloseSession")); - FHoudiniApi::CommitGeo = (CommitGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitGeo")); - FHoudiniApi::CommitWorkitems = (CommitWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitWorkitems")); - FHoudiniApi::ComposeChildNodeList = (ComposeChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeChildNodeList")); - FHoudiniApi::ComposeNodeCookResult = (ComposeNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeNodeCookResult")); - FHoudiniApi::ComposeObjectList = (ComposeObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeObjectList")); - FHoudiniApi::ConnectNodeInput = (ConnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConnectNodeInput")); - FHoudiniApi::ConvertMatrixToEuler = (ConvertMatrixToEulerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToEuler")); - FHoudiniApi::ConvertMatrixToQuat = (ConvertMatrixToQuatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToQuat")); - FHoudiniApi::ConvertTransform = (ConvertTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransform")); - FHoudiniApi::ConvertTransformEulerToMatrix = (ConvertTransformEulerToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformEulerToMatrix")); - FHoudiniApi::ConvertTransformQuatToMatrix = (ConvertTransformQuatToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformQuatToMatrix")); - FHoudiniApi::CookNode = (CookNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookNode")); - FHoudiniApi::CookOptions_AreEqual = (CookOptions_AreEqualFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_AreEqual")); - FHoudiniApi::CookOptions_Create = (CookOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Create")); - FHoudiniApi::CookOptions_Init = (CookOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Init")); - FHoudiniApi::CookPDG = (CookPDGFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookPDG")); - FHoudiniApi::CreateCustomSession = (CreateCustomSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateCustomSession")); - FHoudiniApi::CreateHeightFieldInput = (CreateHeightFieldInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightFieldInput")); - FHoudiniApi::CreateHeightfieldInputVolumeNode = (CreateHeightfieldInputVolumeNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightfieldInputVolumeNode")); - FHoudiniApi::CreateInProcessSession = (CreateInProcessSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInProcessSession")); - FHoudiniApi::CreateInputNode = (CreateInputNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInputNode")); - FHoudiniApi::CreateNode = (CreateNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateNode")); - FHoudiniApi::CreateThriftNamedPipeSession = (CreateThriftNamedPipeSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftNamedPipeSession")); - FHoudiniApi::CreateThriftSocketSession = (CreateThriftSocketSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftSocketSession")); - FHoudiniApi::CreateWorkitem = (CreateWorkitemFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateWorkitem")); - FHoudiniApi::CurveInfo_Create = (CurveInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Create")); - FHoudiniApi::CurveInfo_Init = (CurveInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Init")); - FHoudiniApi::DeleteAttribute = (DeleteAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteAttribute")); - FHoudiniApi::DeleteGroup = (DeleteGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteGroup")); - FHoudiniApi::DeleteNode = (DeleteNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteNode")); - FHoudiniApi::DirtyPDGNode = (DirtyPDGNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DirtyPDGNode")); - FHoudiniApi::DisconnectNodeInput = (DisconnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeInput")); - FHoudiniApi::DisconnectNodeOutputsAt = (DisconnectNodeOutputsAtFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeOutputsAt")); - FHoudiniApi::ExtractImageToFile = (ExtractImageToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToFile")); - FHoudiniApi::ExtractImageToMemory = (ExtractImageToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToMemory")); - FHoudiniApi::GeoInfo_Create = (GeoInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Create")); - FHoudiniApi::GeoInfo_GetGroupCountByType = (GeoInfo_GetGroupCountByTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_GetGroupCountByType")); - FHoudiniApi::GeoInfo_Init = (GeoInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Init")); - FHoudiniApi::GetActiveCacheCount = (GetActiveCacheCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheCount")); - FHoudiniApi::GetActiveCacheNames = (GetActiveCacheNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheNames")); - FHoudiniApi::GetAssetDefinitionParmCounts = (GetAssetDefinitionParmCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmCounts")); - FHoudiniApi::GetAssetDefinitionParmInfos = (GetAssetDefinitionParmInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmInfos")); - FHoudiniApi::GetAssetDefinitionParmValues = (GetAssetDefinitionParmValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmValues")); - FHoudiniApi::GetAssetInfo = (GetAssetInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetInfo")); - FHoudiniApi::GetAttributeFloat64ArrayData = (GetAttributeFloat64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64ArrayData")); - FHoudiniApi::GetAttributeFloat64Data = (GetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64Data")); - FHoudiniApi::GetAttributeFloatArrayData = (GetAttributeFloatArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatArrayData")); - FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); - FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); - FHoudiniApi::GetAttributeInt16ArrayData = (GetAttributeInt16ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16ArrayData")); - FHoudiniApi::GetAttributeInt16Data = (GetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16Data")); - FHoudiniApi::GetAttributeInt64ArrayData = (GetAttributeInt64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64ArrayData")); - FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); - FHoudiniApi::GetAttributeInt8ArrayData = (GetAttributeInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8ArrayData")); - FHoudiniApi::GetAttributeInt8Data = (GetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8Data")); - FHoudiniApi::GetAttributeIntArrayData = (GetAttributeIntArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntArrayData")); - FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); - FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); - FHoudiniApi::GetAttributeStringArrayData = (GetAttributeStringArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringArrayData")); - FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); - FHoudiniApi::GetAttributeUInt8ArrayData = (GetAttributeUInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8ArrayData")); - FHoudiniApi::GetAttributeUInt8Data = (GetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8Data")); - FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); - FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); - FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); - FHoudiniApi::GetCacheProperty = (GetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCacheProperty")); - FHoudiniApi::GetComposedChildNodeList = (GetComposedChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedChildNodeList")); - FHoudiniApi::GetComposedNodeCookResult = (GetComposedNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedNodeCookResult")); - FHoudiniApi::GetComposedObjectList = (GetComposedObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectList")); - FHoudiniApi::GetComposedObjectTransforms = (GetComposedObjectTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectTransforms")); - FHoudiniApi::GetConnectionError = (GetConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionError")); - FHoudiniApi::GetConnectionErrorLength = (GetConnectionErrorLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionErrorLength")); - FHoudiniApi::GetCookingCurrentCount = (GetCookingCurrentCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingCurrentCount")); - FHoudiniApi::GetCookingTotalCount = (GetCookingTotalCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingTotalCount")); - FHoudiniApi::GetCurveCounts = (GetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveCounts")); - FHoudiniApi::GetCurveInfo = (GetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveInfo")); - FHoudiniApi::GetCurveKnots = (GetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveKnots")); - FHoudiniApi::GetCurveOrders = (GetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveOrders")); - FHoudiniApi::GetDisplayGeoInfo = (GetDisplayGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetDisplayGeoInfo")); - FHoudiniApi::GetEnvInt = (GetEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEnvInt")); - FHoudiniApi::GetFaceCounts = (GetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFaceCounts")); - FHoudiniApi::GetFirstVolumeTile = (GetFirstVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFirstVolumeTile")); - FHoudiniApi::GetGeoInfo = (GetGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoInfo")); - FHoudiniApi::GetGeoSize = (GetGeoSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoSize")); - FHoudiniApi::GetGroupCountOnPackedInstancePart = (GetGroupCountOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupCountOnPackedInstancePart")); - FHoudiniApi::GetGroupMembership = (GetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembership")); - FHoudiniApi::GetGroupMembershipOnPackedInstancePart = (GetGroupMembershipOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembershipOnPackedInstancePart")); - FHoudiniApi::GetGroupNames = (GetGroupNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNames")); - FHoudiniApi::GetGroupNamesOnPackedInstancePart = (GetGroupNamesOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNamesOnPackedInstancePart")); - FHoudiniApi::GetHIPFileNodeCount = (GetHIPFileNodeCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeCount")); - FHoudiniApi::GetHIPFileNodeIds = (GetHIPFileNodeIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeIds")); - FHoudiniApi::GetHandleBindingInfo = (GetHandleBindingInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleBindingInfo")); - FHoudiniApi::GetHandleInfo = (GetHandleInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleInfo")); - FHoudiniApi::GetHeightFieldData = (GetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHeightFieldData")); - FHoudiniApi::GetImageFilePath = (GetImageFilePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageFilePath")); - FHoudiniApi::GetImageInfo = (GetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageInfo")); - FHoudiniApi::GetImageMemoryBuffer = (GetImageMemoryBufferFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageMemoryBuffer")); - FHoudiniApi::GetImagePlaneCount = (GetImagePlaneCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlaneCount")); - FHoudiniApi::GetImagePlanes = (GetImagePlanesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlanes")); - FHoudiniApi::GetInstanceTransformsOnPart = (GetInstanceTransformsOnPartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstanceTransformsOnPart")); - FHoudiniApi::GetInstancedObjectIds = (GetInstancedObjectIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedObjectIds")); - FHoudiniApi::GetInstancedPartIds = (GetInstancedPartIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedPartIds")); - FHoudiniApi::GetInstancerPartTransforms = (GetInstancerPartTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancerPartTransforms")); - FHoudiniApi::GetManagerNodeId = (GetManagerNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetManagerNodeId")); - FHoudiniApi::GetMaterialInfo = (GetMaterialInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialInfo")); - FHoudiniApi::GetMaterialNodeIdsOnFaces = (GetMaterialNodeIdsOnFacesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialNodeIdsOnFaces")); - FHoudiniApi::GetNextVolumeTile = (GetNextVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNextVolumeTile")); - FHoudiniApi::GetNodeInfo = (GetNodeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInfo")); - FHoudiniApi::GetNodeInputName = (GetNodeInputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInputName")); - FHoudiniApi::GetNodeOutputName = (GetNodeOutputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeOutputName")); - FHoudiniApi::GetNodePath = (GetNodePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodePath")); - FHoudiniApi::GetNumWorkitems = (GetNumWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNumWorkitems")); - FHoudiniApi::GetObjectInfo = (GetObjectInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectInfo")); - FHoudiniApi::GetObjectTransform = (GetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectTransform")); - FHoudiniApi::GetOutputNodeId = (GetOutputNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputNodeId")); - FHoudiniApi::GetPDGEvents = (GetPDGEventsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGEvents")); - FHoudiniApi::GetPDGGraphContextId = (GetPDGGraphContextIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContextId")); - FHoudiniApi::GetPDGGraphContexts = (GetPDGGraphContextsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContexts")); - FHoudiniApi::GetPDGState = (GetPDGStateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGState")); - FHoudiniApi::GetParameters = (GetParametersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParameters")); - FHoudiniApi::GetParmChoiceLists = (GetParmChoiceListsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmChoiceLists")); - FHoudiniApi::GetParmExpression = (GetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmExpression")); - FHoudiniApi::GetParmFile = (GetParmFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFile")); - FHoudiniApi::GetParmFloatValue = (GetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValue")); - FHoudiniApi::GetParmFloatValues = (GetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValues")); - FHoudiniApi::GetParmIdFromName = (GetParmIdFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIdFromName")); - FHoudiniApi::GetParmInfo = (GetParmInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfo")); - FHoudiniApi::GetParmInfoFromName = (GetParmInfoFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfoFromName")); - FHoudiniApi::GetParmIntValue = (GetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValue")); - FHoudiniApi::GetParmIntValues = (GetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValues")); - FHoudiniApi::GetParmNodeValue = (GetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmNodeValue")); - FHoudiniApi::GetParmStringValue = (GetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValue")); - FHoudiniApi::GetParmStringValues = (GetParmStringValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValues")); - FHoudiniApi::GetParmTagName = (GetParmTagNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagName")); - FHoudiniApi::GetParmTagValue = (GetParmTagValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagValue")); - FHoudiniApi::GetParmWithTag = (GetParmWithTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmWithTag")); - FHoudiniApi::GetPartInfo = (GetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPartInfo")); - FHoudiniApi::GetPreset = (GetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPreset")); - FHoudiniApi::GetPresetBufLength = (GetPresetBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPresetBufLength")); - FHoudiniApi::GetServerEnvInt = (GetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvInt")); - FHoudiniApi::GetServerEnvString = (GetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvString")); - FHoudiniApi::GetServerEnvVarCount = (GetServerEnvVarCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarCount")); - FHoudiniApi::GetServerEnvVarList = (GetServerEnvVarListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarList")); - FHoudiniApi::GetSessionEnvInt = (GetSessionEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionEnvInt")); - FHoudiniApi::GetSessionSyncInfo = (GetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionSyncInfo")); - FHoudiniApi::GetSphereInfo = (GetSphereInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSphereInfo")); - FHoudiniApi::GetStatus = (GetStatusFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatus")); - FHoudiniApi::GetStatusString = (GetStatusStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusString")); - FHoudiniApi::GetStatusStringBufLength = (GetStatusStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusStringBufLength")); - FHoudiniApi::GetString = (GetStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetString")); - FHoudiniApi::GetStringBatch = (GetStringBatchFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatch")); - FHoudiniApi::GetStringBatchSize = (GetStringBatchSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatchSize")); - FHoudiniApi::GetStringBufLength = (GetStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBufLength")); - FHoudiniApi::GetSupportedImageFileFormatCount = (GetSupportedImageFileFormatCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormatCount")); - FHoudiniApi::GetSupportedImageFileFormats = (GetSupportedImageFileFormatsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormats")); - FHoudiniApi::GetTime = (GetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTime")); - FHoudiniApi::GetTimelineOptions = (GetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTimelineOptions")); - FHoudiniApi::GetTotalCookCount = (GetTotalCookCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTotalCookCount")); - FHoudiniApi::GetUseHoudiniTime = (GetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetUseHoudiniTime")); - FHoudiniApi::GetVertexList = (GetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVertexList")); - FHoudiniApi::GetViewport = (GetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetViewport")); - FHoudiniApi::GetVolumeBounds = (GetVolumeBoundsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeBounds")); - FHoudiniApi::GetVolumeInfo = (GetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeInfo")); - FHoudiniApi::GetVolumeTileFloatData = (GetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileFloatData")); - FHoudiniApi::GetVolumeTileIntData = (GetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileIntData")); - FHoudiniApi::GetVolumeVisualInfo = (GetVolumeVisualInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVisualInfo")); - FHoudiniApi::GetVolumeVoxelFloatData = (GetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelFloatData")); - FHoudiniApi::GetVolumeVoxelIntData = (GetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelIntData")); - FHoudiniApi::GetWorkitemDataLength = (GetWorkitemDataLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemDataLength")); - FHoudiniApi::GetWorkitemFloatData = (GetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemFloatData")); - FHoudiniApi::GetWorkitemInfo = (GetWorkitemInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemInfo")); - FHoudiniApi::GetWorkitemIntData = (GetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemIntData")); - FHoudiniApi::GetWorkitemResultInfo = (GetWorkitemResultInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemResultInfo")); - FHoudiniApi::GetWorkitemStringData = (GetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemStringData")); - FHoudiniApi::GetWorkitems = (GetWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitems")); - FHoudiniApi::HandleBindingInfo_Create = (HandleBindingInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Create")); - FHoudiniApi::HandleBindingInfo_Init = (HandleBindingInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Init")); - FHoudiniApi::HandleInfo_Create = (HandleInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Create")); - FHoudiniApi::HandleInfo_Init = (HandleInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Init")); - FHoudiniApi::ImageFileFormat_Create = (ImageFileFormat_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Create")); - FHoudiniApi::ImageFileFormat_Init = (ImageFileFormat_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Init")); - FHoudiniApi::ImageInfo_Create = (ImageInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Create")); - FHoudiniApi::ImageInfo_Init = (ImageInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Init")); - FHoudiniApi::Initialize = (InitializeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Initialize")); - FHoudiniApi::InsertMultiparmInstance = (InsertMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_InsertMultiparmInstance")); - FHoudiniApi::Interrupt = (InterruptFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Interrupt")); - FHoudiniApi::IsInitialized = (IsInitializedFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsInitialized")); - FHoudiniApi::IsNodeValid = (IsNodeValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsNodeValid")); - FHoudiniApi::IsSessionValid = (IsSessionValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsSessionValid")); - FHoudiniApi::Keyframe_Create = (Keyframe_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Create")); - FHoudiniApi::Keyframe_Init = (Keyframe_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Init")); - FHoudiniApi::LoadAssetLibraryFromFile = (LoadAssetLibraryFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromFile")); - FHoudiniApi::LoadAssetLibraryFromMemory = (LoadAssetLibraryFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromMemory")); - FHoudiniApi::LoadGeoFromFile = (LoadGeoFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromFile")); - FHoudiniApi::LoadGeoFromMemory = (LoadGeoFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromMemory")); - FHoudiniApi::LoadHIPFile = (LoadHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadHIPFile")); - FHoudiniApi::LoadNodeFromFile = (LoadNodeFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadNodeFromFile")); - FHoudiniApi::MaterialInfo_Create = (MaterialInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Create")); - FHoudiniApi::MaterialInfo_Init = (MaterialInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Init")); - FHoudiniApi::MergeHIPFile = (MergeHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MergeHIPFile")); - FHoudiniApi::NodeInfo_Create = (NodeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Create")); - FHoudiniApi::NodeInfo_Init = (NodeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Init")); - FHoudiniApi::ObjectInfo_Create = (ObjectInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Create")); - FHoudiniApi::ObjectInfo_Init = (ObjectInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Init")); - FHoudiniApi::ParmChoiceInfo_Create = (ParmChoiceInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Create")); - FHoudiniApi::ParmChoiceInfo_Init = (ParmChoiceInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Init")); - FHoudiniApi::ParmHasExpression = (ParmHasExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasExpression")); - FHoudiniApi::ParmHasTag = (ParmHasTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasTag")); - FHoudiniApi::ParmInfo_Create = (ParmInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Create")); - FHoudiniApi::ParmInfo_GetFloatValueCount = (ParmInfo_GetFloatValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetFloatValueCount")); - FHoudiniApi::ParmInfo_GetIntValueCount = (ParmInfo_GetIntValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetIntValueCount")); - FHoudiniApi::ParmInfo_GetStringValueCount = (ParmInfo_GetStringValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetStringValueCount")); - FHoudiniApi::ParmInfo_Init = (ParmInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Init")); - FHoudiniApi::ParmInfo_IsFloat = (ParmInfo_IsFloatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsFloat")); - FHoudiniApi::ParmInfo_IsInt = (ParmInfo_IsIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsInt")); - FHoudiniApi::ParmInfo_IsNode = (ParmInfo_IsNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNode")); - FHoudiniApi::ParmInfo_IsNonValue = (ParmInfo_IsNonValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNonValue")); - FHoudiniApi::ParmInfo_IsPath = (ParmInfo_IsPathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsPath")); - FHoudiniApi::ParmInfo_IsString = (ParmInfo_IsStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsString")); - FHoudiniApi::PartInfo_Create = (PartInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Create")); - FHoudiniApi::PartInfo_GetAttributeCountByOwner = (PartInfo_GetAttributeCountByOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetAttributeCountByOwner")); - FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = (PartInfo_GetElementCountByAttributeOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByAttributeOwner")); - FHoudiniApi::PartInfo_GetElementCountByGroupType = (PartInfo_GetElementCountByGroupTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByGroupType")); - FHoudiniApi::PartInfo_Init = (PartInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Init")); - FHoudiniApi::PausePDGCook = (PausePDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PausePDGCook")); - FHoudiniApi::PythonThreadInterpreterLock = (PythonThreadInterpreterLockFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PythonThreadInterpreterLock")); - FHoudiniApi::QueryNodeInput = (QueryNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeInput")); - FHoudiniApi::QueryNodeOutputConnectedCount = (QueryNodeOutputConnectedCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedCount")); - FHoudiniApi::QueryNodeOutputConnectedNodes = (QueryNodeOutputConnectedNodesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedNodes")); - FHoudiniApi::RemoveCustomString = (RemoveCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveCustomString")); - FHoudiniApi::RemoveMultiparmInstance = (RemoveMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveMultiparmInstance")); - FHoudiniApi::RemoveParmExpression = (RemoveParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveParmExpression")); - FHoudiniApi::RenameNode = (RenameNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenameNode")); - FHoudiniApi::RenderCOPToImage = (RenderCOPToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderCOPToImage")); - FHoudiniApi::RenderTextureToImage = (RenderTextureToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderTextureToImage")); - FHoudiniApi::ResetSimulation = (ResetSimulationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ResetSimulation")); - FHoudiniApi::RevertGeo = (RevertGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertGeo")); - FHoudiniApi::RevertParmToDefault = (RevertParmToDefaultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefault")); - FHoudiniApi::RevertParmToDefaults = (RevertParmToDefaultsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefaults")); - FHoudiniApi::SaveGeoToFile = (SaveGeoToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToFile")); - FHoudiniApi::SaveGeoToMemory = (SaveGeoToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToMemory")); - FHoudiniApi::SaveHIPFile = (SaveHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveHIPFile")); - FHoudiniApi::SaveNodeToFile = (SaveNodeToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveNodeToFile")); - FHoudiniApi::SessionSyncInfo_Create = (SessionSyncInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SessionSyncInfo_Create")); - FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); - FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); - FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); - FHoudiniApi::SetAttributeInt16Data = (SetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt16Data")); - FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); - FHoudiniApi::SetAttributeInt8Data = (SetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt8Data")); - FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); - FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); - FHoudiniApi::SetAttributeUInt8Data = (SetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeUInt8Data")); - FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); - FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); - FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); - FHoudiniApi::SetCurveKnots = (SetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveKnots")); - FHoudiniApi::SetCurveOrders = (SetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveOrders")); - FHoudiniApi::SetCustomString = (SetCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCustomString")); - FHoudiniApi::SetFaceCounts = (SetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetFaceCounts")); - FHoudiniApi::SetGroupMembership = (SetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetGroupMembership")); - FHoudiniApi::SetHeightFieldData = (SetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetHeightFieldData")); - FHoudiniApi::SetImageInfo = (SetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetImageInfo")); - FHoudiniApi::SetNodeDisplay = (SetNodeDisplayFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetNodeDisplay")); - FHoudiniApi::SetObjectTransform = (SetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetObjectTransform")); - FHoudiniApi::SetParmExpression = (SetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmExpression")); - FHoudiniApi::SetParmFloatValue = (SetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValue")); - FHoudiniApi::SetParmFloatValues = (SetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValues")); - FHoudiniApi::SetParmIntValue = (SetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValue")); - FHoudiniApi::SetParmIntValues = (SetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValues")); - FHoudiniApi::SetParmNodeValue = (SetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmNodeValue")); - FHoudiniApi::SetParmStringValue = (SetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmStringValue")); - FHoudiniApi::SetPartInfo = (SetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPartInfo")); - FHoudiniApi::SetPreset = (SetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPreset")); - FHoudiniApi::SetServerEnvInt = (SetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvInt")); - FHoudiniApi::SetServerEnvString = (SetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvString")); - FHoudiniApi::SetSessionSync = (SetSessionSyncFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSync")); - FHoudiniApi::SetSessionSyncInfo = (SetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSyncInfo")); - FHoudiniApi::SetTime = (SetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTime")); - FHoudiniApi::SetTimelineOptions = (SetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTimelineOptions")); - FHoudiniApi::SetTransformAnimCurve = (SetTransformAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTransformAnimCurve")); - FHoudiniApi::SetUseHoudiniTime = (SetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetUseHoudiniTime")); - FHoudiniApi::SetVertexList = (SetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVertexList")); - FHoudiniApi::SetViewport = (SetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetViewport")); - FHoudiniApi::SetVolumeInfo = (SetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeInfo")); - FHoudiniApi::SetVolumeTileFloatData = (SetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileFloatData")); - FHoudiniApi::SetVolumeTileIntData = (SetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileIntData")); - FHoudiniApi::SetVolumeVoxelFloatData = (SetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelFloatData")); - FHoudiniApi::SetVolumeVoxelIntData = (SetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelIntData")); - FHoudiniApi::SetWorkitemFloatData = (SetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemFloatData")); - FHoudiniApi::SetWorkitemIntData = (SetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemIntData")); - FHoudiniApi::SetWorkitemStringData = (SetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemStringData")); - FHoudiniApi::StartThriftNamedPipeServer = (StartThriftNamedPipeServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftNamedPipeServer")); - FHoudiniApi::StartThriftSocketServer = (StartThriftSocketServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftSocketServer")); - FHoudiniApi::ThriftServerOptions_Create = (ThriftServerOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Create")); - FHoudiniApi::ThriftServerOptions_Init = (ThriftServerOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Init")); - FHoudiniApi::TimelineOptions_Create = (TimelineOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Create")); - FHoudiniApi::TimelineOptions_Init = (TimelineOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Init")); - FHoudiniApi::TransformEuler_Create = (TransformEuler_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Create")); - FHoudiniApi::TransformEuler_Init = (TransformEuler_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Init")); - FHoudiniApi::Transform_Create = (Transform_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Create")); - FHoudiniApi::Transform_Init = (Transform_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Init")); - FHoudiniApi::Viewport_Create = (Viewport_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Viewport_Create")); - FHoudiniApi::VolumeInfo_Create = (VolumeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Create")); - FHoudiniApi::VolumeInfo_Init = (VolumeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Init")); - FHoudiniApi::VolumeTileInfo_Create = (VolumeTileInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Create")); - FHoudiniApi::VolumeTileInfo_Init = (VolumeTileInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Init")); -} - - -void -FHoudiniApi::FinalizeHAPI() -{ - FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; - FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; - FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; - FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; - FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; - FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; - FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; - FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; - FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; - FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; - FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; - FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; - FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; - FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; - FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; - FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; - FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; - FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; - FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; - FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; - FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; - FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; - FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; - FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; - FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; - FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; - FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; - FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; - FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; - FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; - FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; - FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; - FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; - FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; - FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; - FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; - FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; - FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; - FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; - FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; - FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; - FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; - FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; - FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; - FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; - FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; - FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; - FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; - FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; - FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; - FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; - FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; - FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; - FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; - FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; - FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; - FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; - FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; - FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; - FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; - FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; - FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; - FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; - FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; - FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; - FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; - FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; - FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; - FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; - FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; - FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; - FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; - FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; - FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; - FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; - FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; - FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; - FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; - FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; - FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; - FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; - FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; - FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; - FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; - FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; - FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; - FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; - FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; - FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; - FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; - FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; - FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; - FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; - FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; - FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; - FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; - FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; - FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; - FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; - FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; - FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; - FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; - FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; - FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; - FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; - FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; - FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; - FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; - FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; - FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; - FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; - FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; - FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; - FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; - FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; - FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; - FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; - FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; - FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; - FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; - FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; - FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; - FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; - FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; - FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; - FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; - FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; - FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; - FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; - FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; - FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; - FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; - FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; - FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; - FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; - FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; - FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; - FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; - FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; - FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; - FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; - FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; - FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; - FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; - FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; - FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; - FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; - FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; - FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; - FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; - FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; - FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; - FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; - FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; - FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; - FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; - FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; - FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; - FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; - FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; - FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; - FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; - FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; - FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; - FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; - FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; - FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; - FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; - FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; - FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; - FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; - FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; - FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; - FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; - FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; - FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; - FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; - FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; - FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; - FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; - FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; - FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; - FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; - FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; - FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; - FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; - FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; - FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; - FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; - FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; - FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; - FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; - FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; - FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; - FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; - FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; - FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; - FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; - FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; - FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; - FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; - FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; - FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; - FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; - FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; - FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; - FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; - FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; - FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; - FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; - FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; - FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; - FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; - FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; - FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; - FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; - FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; - FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; - FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; - FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; - FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; - FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; - FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; - FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; - FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; - FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; - FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; - FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; - FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; - FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; - FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; - FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; - FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; - FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; - FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; - FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; - FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; - FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; - FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; - FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; - FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; - FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; - FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; - FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; - FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; - FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; - FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; - FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; - FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; - FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; - FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; - FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; - FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; - FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; - FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; - FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; - FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; - FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; - FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; - FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; - FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; - FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; - FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; - FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; - FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; - FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; - FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; - FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; - FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; - FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; - FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; - FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; - FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; - FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; - FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; - FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; - FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; - FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; - FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; - FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; - FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; - FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; - FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; - FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; - FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; - FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; - FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; - FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; - FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; - FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; - FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; - FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; - FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; - FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; - FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; - FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; - FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; - FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; - FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; - FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; - FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; - FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; - FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; - FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; - FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; - FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; - FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; - FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; - FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; - FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; - FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; - FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; - FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; - FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; - FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; - FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; - FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; - FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; - FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; -} - - -bool -FHoudiniApi::IsHAPIInitialized() -{ - return ( FHoudiniApi::IsInitialized != &FHoudiniApi::IsInitializedEmptyStub ); -} - - -HAPI_Result -FHoudiniApi::AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_AssetInfo -FHoudiniApi::AssetInfo_CreateEmptyStub() -{ - return HAPI_AssetInfo(); -} - - -void -FHoudiniApi::AssetInfo_InitEmptyStub(HAPI_AssetInfo * in) -{ - return; -} - - -HAPI_AttributeInfo -FHoudiniApi::AttributeInfo_CreateEmptyStub() -{ - return HAPI_AttributeInfo(); -} - - -void -FHoudiniApi::AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CleanupEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ClearConnectionErrorEmptyStub() -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CloseSessionEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Bool -FHoudiniApi::CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right) -{ - return HAPI_Bool(); -} - - -HAPI_CookOptions -FHoudiniApi::CookOptions_CreateEmptyStub() -{ - return HAPI_CookOptions(); -} - - -void -FHoudiniApi::CookOptions_InitEmptyStub(HAPI_CookOptions * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateInProcessSessionEmptyStub(HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_CurveInfo -FHoudiniApi::CurveInfo_CreateEmptyStub() -{ - return HAPI_CurveInfo(); -} - - -void -FHoudiniApi::CurveInfo_InitEmptyStub(HAPI_CurveInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_GeoInfo -FHoudiniApi::GeoInfo_CreateEmptyStub() -{ - return HAPI_GeoInfo(); -} - - -int -FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type) -{ - return -1; -} - - -void -FHoudiniApi::GeoInfo_InitEmptyStub(HAPI_GeoInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetConnectionErrorLengthEmptyStub(int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTimeEmptyStub(const HAPI_Session * session, float * time) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_HandleBindingInfo -FHoudiniApi::HandleBindingInfo_CreateEmptyStub() -{ - return HAPI_HandleBindingInfo(); -} - - -void -FHoudiniApi::HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in) -{ - return; -} - - -HAPI_HandleInfo -FHoudiniApi::HandleInfo_CreateEmptyStub() -{ - return HAPI_HandleInfo(); -} - - -void -FHoudiniApi::HandleInfo_InitEmptyStub(HAPI_HandleInfo * in) -{ - return; -} - - -HAPI_ImageFileFormat -FHoudiniApi::ImageFileFormat_CreateEmptyStub() -{ - return HAPI_ImageFileFormat(); -} - - -void -FHoudiniApi::ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in) -{ - return; -} - - -HAPI_ImageInfo -FHoudiniApi::ImageInfo_CreateEmptyStub() -{ - return HAPI_ImageInfo(); -} - - -void -FHoudiniApi::ImageInfo_InitEmptyStub(HAPI_ImageInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::InterruptEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsInitializedEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsSessionValidEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Keyframe -FHoudiniApi::Keyframe_CreateEmptyStub() -{ - return HAPI_Keyframe(); -} - - -void -FHoudiniApi::Keyframe_InitEmptyStub(HAPI_Keyframe * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_MaterialInfo -FHoudiniApi::MaterialInfo_CreateEmptyStub() -{ - return HAPI_MaterialInfo(); -} - - -void -FHoudiniApi::MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_NodeInfo -FHoudiniApi::NodeInfo_CreateEmptyStub() -{ - return HAPI_NodeInfo(); -} - - -void -FHoudiniApi::NodeInfo_InitEmptyStub(HAPI_NodeInfo * in) -{ - return; -} - - -HAPI_ObjectInfo -FHoudiniApi::ObjectInfo_CreateEmptyStub() -{ - return HAPI_ObjectInfo(); -} - - -void -FHoudiniApi::ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in) -{ - return; -} - - -HAPI_ParmChoiceInfo -FHoudiniApi::ParmChoiceInfo_CreateEmptyStub() -{ - return HAPI_ParmChoiceInfo(); -} - - -void -FHoudiniApi::ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_ParmInfo -FHoudiniApi::ParmInfo_CreateEmptyStub() -{ - return HAPI_ParmInfo(); -} - - -int -FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in) -{ - return -1; -} - - -int -FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in) -{ - return -1; -} - - -int -FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in) -{ - return -1; -} - - -void -FHoudiniApi::ParmInfo_InitEmptyStub(HAPI_ParmInfo * in) -{ - return; -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_PartInfo -FHoudiniApi::PartInfo_CreateEmptyStub() -{ - return HAPI_PartInfo(); -} - - -int -FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) -{ - return -1; -} - - -int -FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) -{ - return -1; -} - - -int -FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type) -{ - return -1; -} - - -void -FHoudiniApi::PartInfo_InitEmptyStub(HAPI_PartInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_SessionSyncInfo -FHoudiniApi::SessionSyncInfo_CreateEmptyStub() -{ - return HAPI_SessionSyncInfo(); -} - - -HAPI_Result -FHoudiniApi::SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTimeEmptyStub(const HAPI_Session * session, float time) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_ThriftServerOptions -FHoudiniApi::ThriftServerOptions_CreateEmptyStub() -{ - return HAPI_ThriftServerOptions(); -} - - -void -FHoudiniApi::ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in) -{ - return; -} - - -HAPI_TimelineOptions -FHoudiniApi::TimelineOptions_CreateEmptyStub() -{ - return HAPI_TimelineOptions(); -} - - -void -FHoudiniApi::TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in) -{ - return; -} - - -HAPI_TransformEuler -FHoudiniApi::TransformEuler_CreateEmptyStub() -{ - return HAPI_TransformEuler(); -} - - -void -FHoudiniApi::TransformEuler_InitEmptyStub(HAPI_TransformEuler * in) -{ - return; -} - - -HAPI_Transform -FHoudiniApi::Transform_CreateEmptyStub() -{ - return HAPI_Transform(); -} - - -void -FHoudiniApi::Transform_InitEmptyStub(HAPI_Transform * in) -{ - return; -} - - -HAPI_Viewport -FHoudiniApi::Viewport_CreateEmptyStub() -{ - return HAPI_Viewport(); -} - - -HAPI_VolumeInfo -FHoudiniApi::VolumeInfo_CreateEmptyStub() -{ - return HAPI_VolumeInfo(); -} - - -void -FHoudiniApi::VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in) -{ - return; -} - - -HAPI_VolumeTileInfo -FHoudiniApi::VolumeTileInfo_CreateEmptyStub() -{ - return HAPI_VolumeTileInfo(); -} - - -void -FHoudiniApi::VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in) -{ - return; -} - - +/* + * Copyright (c) <2021> 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. + * + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +#include "HoudiniApi.h" +#include "HoudiniEnginePrivatePCH.h" + + +FHoudiniApi::AddAttributeFuncPtr +FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; + +FHoudiniApi::AddGroupFuncPtr +FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; + +FHoudiniApi::AssetInfo_CreateFuncPtr +FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; + +FHoudiniApi::AssetInfo_InitFuncPtr +FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; + +FHoudiniApi::AttributeInfo_CreateFuncPtr +FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; + +FHoudiniApi::AttributeInfo_InitFuncPtr +FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; + +FHoudiniApi::BindCustomImplementationFuncPtr +FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; + +FHoudiniApi::CancelPDGCookFuncPtr +FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; + +FHoudiniApi::CheckForSpecificErrorsFuncPtr +FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; + +FHoudiniApi::CleanupFuncPtr +FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; + +FHoudiniApi::ClearConnectionErrorFuncPtr +FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; + +FHoudiniApi::CloseSessionFuncPtr +FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; + +FHoudiniApi::CommitGeoFuncPtr +FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; + +FHoudiniApi::CommitWorkitemsFuncPtr +FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; + +FHoudiniApi::ComposeChildNodeListFuncPtr +FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; + +FHoudiniApi::ComposeNodeCookResultFuncPtr +FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; + +FHoudiniApi::ComposeObjectListFuncPtr +FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + +FHoudiniApi::CompositorOptions_CreateFuncPtr +FHoudiniApi::CompositorOptions_Create = &FHoudiniApi::CompositorOptions_CreateEmptyStub; + +FHoudiniApi::CompositorOptions_InitFuncPtr +FHoudiniApi::CompositorOptions_Init = &FHoudiniApi::CompositorOptions_InitEmptyStub; + +FHoudiniApi::ConnectNodeInputFuncPtr +FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; + +FHoudiniApi::ConvertMatrixToEulerFuncPtr +FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; + +FHoudiniApi::ConvertMatrixToQuatFuncPtr +FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; + +FHoudiniApi::ConvertTransformFuncPtr +FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; + +FHoudiniApi::ConvertTransformEulerToMatrixFuncPtr +FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; + +FHoudiniApi::ConvertTransformQuatToMatrixFuncPtr +FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; + +FHoudiniApi::CookNodeFuncPtr +FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; + +FHoudiniApi::CookOptions_AreEqualFuncPtr +FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; + +FHoudiniApi::CookOptions_CreateFuncPtr +FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; + +FHoudiniApi::CookOptions_InitFuncPtr +FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; + +FHoudiniApi::CookPDGFuncPtr +FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; + +FHoudiniApi::CreateCustomSessionFuncPtr +FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; + +FHoudiniApi::CreateHeightFieldInputFuncPtr +FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; + +FHoudiniApi::CreateHeightfieldInputVolumeNodeFuncPtr +FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; + +FHoudiniApi::CreateInProcessSessionFuncPtr +FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; + +FHoudiniApi::CreateInputNodeFuncPtr +FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; + +FHoudiniApi::CreateNodeFuncPtr +FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; + +FHoudiniApi::CreateThriftNamedPipeSessionFuncPtr +FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; + +FHoudiniApi::CreateThriftSocketSessionFuncPtr +FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; + +FHoudiniApi::CreateWorkitemFuncPtr +FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; + +FHoudiniApi::CurveInfo_CreateFuncPtr +FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; + +FHoudiniApi::CurveInfo_InitFuncPtr +FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; + +FHoudiniApi::DeleteAttributeFuncPtr +FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; + +FHoudiniApi::DeleteGroupFuncPtr +FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; + +FHoudiniApi::DeleteNodeFuncPtr +FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; + +FHoudiniApi::DirtyPDGNodeFuncPtr +FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; + +FHoudiniApi::DisconnectNodeInputFuncPtr +FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; + +FHoudiniApi::DisconnectNodeOutputsAtFuncPtr +FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; + +FHoudiniApi::ExtractImageToFileFuncPtr +FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; + +FHoudiniApi::ExtractImageToMemoryFuncPtr +FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; + +FHoudiniApi::GeoInfo_CreateFuncPtr +FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; + +FHoudiniApi::GeoInfo_GetGroupCountByTypeFuncPtr +FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; + +FHoudiniApi::GeoInfo_InitFuncPtr +FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; + +FHoudiniApi::GetActiveCacheCountFuncPtr +FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; + +FHoudiniApi::GetActiveCacheNamesFuncPtr +FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmCountsFuncPtr +FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmInfosFuncPtr +FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmValuesFuncPtr +FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; + +FHoudiniApi::GetAssetInfoFuncPtr +FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; + +FHoudiniApi::GetAttributeFloat64ArrayDataFuncPtr +FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeFloat64DataFuncPtr +FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; + +FHoudiniApi::GetAttributeFloatArrayDataFuncPtr +FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; + +FHoudiniApi::GetAttributeFloatDataFuncPtr +FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; + +FHoudiniApi::GetAttributeInfoFuncPtr +FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + +FHoudiniApi::GetAttributeInt16ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt16DataFuncPtr +FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; + +FHoudiniApi::GetAttributeInt64ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt64DataFuncPtr +FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + +FHoudiniApi::GetAttributeInt8ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt8DataFuncPtr +FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; + +FHoudiniApi::GetAttributeIntArrayDataFuncPtr +FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; + +FHoudiniApi::GetAttributeIntDataFuncPtr +FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; + +FHoudiniApi::GetAttributeNamesFuncPtr +FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; + +FHoudiniApi::GetAttributeStringArrayDataFuncPtr +FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; + +FHoudiniApi::GetAttributeStringDataFuncPtr +FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + +FHoudiniApi::GetAttributeUInt8ArrayDataFuncPtr +FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeUInt8DataFuncPtr +FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; + +FHoudiniApi::GetAvailableAssetCountFuncPtr +FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; + +FHoudiniApi::GetAvailableAssetsFuncPtr +FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; + +FHoudiniApi::GetBoxInfoFuncPtr +FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; + +FHoudiniApi::GetCachePropertyFuncPtr +FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; + +FHoudiniApi::GetComposedChildNodeListFuncPtr +FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; + +FHoudiniApi::GetComposedNodeCookResultFuncPtr +FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; + +FHoudiniApi::GetComposedObjectListFuncPtr +FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; + +FHoudiniApi::GetComposedObjectTransformsFuncPtr +FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + +FHoudiniApi::GetCompositorOptionsFuncPtr +FHoudiniApi::GetCompositorOptions = &FHoudiniApi::GetCompositorOptionsEmptyStub; + +FHoudiniApi::GetConnectionErrorFuncPtr +FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; + +FHoudiniApi::GetConnectionErrorLengthFuncPtr +FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; + +FHoudiniApi::GetCookingCurrentCountFuncPtr +FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; + +FHoudiniApi::GetCookingTotalCountFuncPtr +FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; + +FHoudiniApi::GetCurveCountsFuncPtr +FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; + +FHoudiniApi::GetCurveInfoFuncPtr +FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; + +FHoudiniApi::GetCurveKnotsFuncPtr +FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; + +FHoudiniApi::GetCurveOrdersFuncPtr +FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; + +FHoudiniApi::GetDisplayGeoInfoFuncPtr +FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + +FHoudiniApi::GetEdgeCountOfEdgeGroupFuncPtr +FHoudiniApi::GetEdgeCountOfEdgeGroup = &FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub; + +FHoudiniApi::GetEnvIntFuncPtr +FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; + +FHoudiniApi::GetFaceCountsFuncPtr +FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; + +FHoudiniApi::GetFirstVolumeTileFuncPtr +FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; + +FHoudiniApi::GetGeoInfoFuncPtr +FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; + +FHoudiniApi::GetGeoSizeFuncPtr +FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; + +FHoudiniApi::GetGroupCountOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetGroupMembershipFuncPtr +FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; + +FHoudiniApi::GetGroupMembershipOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetGroupNamesFuncPtr +FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; + +FHoudiniApi::GetGroupNamesOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetHIPFileNodeCountFuncPtr +FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; + +FHoudiniApi::GetHIPFileNodeIdsFuncPtr +FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; + +FHoudiniApi::GetHandleBindingInfoFuncPtr +FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; + +FHoudiniApi::GetHandleInfoFuncPtr +FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; + +FHoudiniApi::GetHeightFieldDataFuncPtr +FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; + +FHoudiniApi::GetImageFilePathFuncPtr +FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; + +FHoudiniApi::GetImageInfoFuncPtr +FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; + +FHoudiniApi::GetImageMemoryBufferFuncPtr +FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; + +FHoudiniApi::GetImagePlaneCountFuncPtr +FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; + +FHoudiniApi::GetImagePlanesFuncPtr +FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; + +FHoudiniApi::GetInstanceTransformsOnPartFuncPtr +FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; + +FHoudiniApi::GetInstancedObjectIdsFuncPtr +FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; + +FHoudiniApi::GetInstancedPartIdsFuncPtr +FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; + +FHoudiniApi::GetInstancerPartTransformsFuncPtr +FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; + +FHoudiniApi::GetManagerNodeIdFuncPtr +FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; + +FHoudiniApi::GetMaterialInfoFuncPtr +FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; + +FHoudiniApi::GetMaterialNodeIdsOnFacesFuncPtr +FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; + +FHoudiniApi::GetNextVolumeTileFuncPtr +FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; + +FHoudiniApi::GetNodeInfoFuncPtr +FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; + +FHoudiniApi::GetNodeInputNameFuncPtr +FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; + +FHoudiniApi::GetNodeOutputNameFuncPtr +FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; + +FHoudiniApi::GetNodePathFuncPtr +FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; + +FHoudiniApi::GetNumWorkitemsFuncPtr +FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; + +FHoudiniApi::GetObjectInfoFuncPtr +FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; + +FHoudiniApi::GetObjectTransformFuncPtr +FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + +FHoudiniApi::GetOutputGeoCountFuncPtr +FHoudiniApi::GetOutputGeoCount = &FHoudiniApi::GetOutputGeoCountEmptyStub; + +FHoudiniApi::GetOutputGeoInfosFuncPtr +FHoudiniApi::GetOutputGeoInfos = &FHoudiniApi::GetOutputGeoInfosEmptyStub; + +FHoudiniApi::GetOutputNodeIdFuncPtr +FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; + +FHoudiniApi::GetPDGEventsFuncPtr +FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; + +FHoudiniApi::GetPDGGraphContextIdFuncPtr +FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; + +FHoudiniApi::GetPDGGraphContextsFuncPtr +FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; + +FHoudiniApi::GetPDGStateFuncPtr +FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; + +FHoudiniApi::GetParametersFuncPtr +FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; + +FHoudiniApi::GetParmChoiceListsFuncPtr +FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; + +FHoudiniApi::GetParmExpressionFuncPtr +FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; + +FHoudiniApi::GetParmFileFuncPtr +FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; + +FHoudiniApi::GetParmFloatValueFuncPtr +FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; + +FHoudiniApi::GetParmFloatValuesFuncPtr +FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; + +FHoudiniApi::GetParmIdFromNameFuncPtr +FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; + +FHoudiniApi::GetParmInfoFuncPtr +FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; + +FHoudiniApi::GetParmInfoFromNameFuncPtr +FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; + +FHoudiniApi::GetParmIntValueFuncPtr +FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; + +FHoudiniApi::GetParmIntValuesFuncPtr +FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; + +FHoudiniApi::GetParmNodeValueFuncPtr +FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; + +FHoudiniApi::GetParmStringValueFuncPtr +FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; + +FHoudiniApi::GetParmStringValuesFuncPtr +FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; + +FHoudiniApi::GetParmTagNameFuncPtr +FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; + +FHoudiniApi::GetParmTagValueFuncPtr +FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; + +FHoudiniApi::GetParmWithTagFuncPtr +FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; + +FHoudiniApi::GetPartInfoFuncPtr +FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; + +FHoudiniApi::GetPresetFuncPtr +FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; + +FHoudiniApi::GetPresetBufLengthFuncPtr +FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; + +FHoudiniApi::GetServerEnvIntFuncPtr +FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; + +FHoudiniApi::GetServerEnvStringFuncPtr +FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; + +FHoudiniApi::GetServerEnvVarCountFuncPtr +FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; + +FHoudiniApi::GetServerEnvVarListFuncPtr +FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; + +FHoudiniApi::GetSessionEnvIntFuncPtr +FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; + +FHoudiniApi::GetSessionSyncInfoFuncPtr +FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; + +FHoudiniApi::GetSphereInfoFuncPtr +FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; + +FHoudiniApi::GetStatusFuncPtr +FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; + +FHoudiniApi::GetStatusStringFuncPtr +FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; + +FHoudiniApi::GetStatusStringBufLengthFuncPtr +FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; + +FHoudiniApi::GetStringFuncPtr +FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; + +FHoudiniApi::GetStringBatchFuncPtr +FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; + +FHoudiniApi::GetStringBatchSizeFuncPtr +FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; + +FHoudiniApi::GetStringBufLengthFuncPtr +FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; + +FHoudiniApi::GetSupportedImageFileFormatCountFuncPtr +FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; + +FHoudiniApi::GetSupportedImageFileFormatsFuncPtr +FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; + +FHoudiniApi::GetTimeFuncPtr +FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; + +FHoudiniApi::GetTimelineOptionsFuncPtr +FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; + +FHoudiniApi::GetTotalCookCountFuncPtr +FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; + +FHoudiniApi::GetUseHoudiniTimeFuncPtr +FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; + +FHoudiniApi::GetVertexListFuncPtr +FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; + +FHoudiniApi::GetViewportFuncPtr +FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; + +FHoudiniApi::GetVolumeBoundsFuncPtr +FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; + +FHoudiniApi::GetVolumeInfoFuncPtr +FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; + +FHoudiniApi::GetVolumeTileFloatDataFuncPtr +FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; + +FHoudiniApi::GetVolumeTileIntDataFuncPtr +FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; + +FHoudiniApi::GetVolumeVisualInfoFuncPtr +FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; + +FHoudiniApi::GetVolumeVoxelFloatDataFuncPtr +FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; + +FHoudiniApi::GetVolumeVoxelIntDataFuncPtr +FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; + +FHoudiniApi::GetWorkitemDataLengthFuncPtr +FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; + +FHoudiniApi::GetWorkitemFloatDataFuncPtr +FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; + +FHoudiniApi::GetWorkitemInfoFuncPtr +FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; + +FHoudiniApi::GetWorkitemIntDataFuncPtr +FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; + +FHoudiniApi::GetWorkitemResultInfoFuncPtr +FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; + +FHoudiniApi::GetWorkitemStringDataFuncPtr +FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; + +FHoudiniApi::GetWorkitemsFuncPtr +FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; + +FHoudiniApi::HandleBindingInfo_CreateFuncPtr +FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; + +FHoudiniApi::HandleBindingInfo_InitFuncPtr +FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; + +FHoudiniApi::HandleInfo_CreateFuncPtr +FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; + +FHoudiniApi::HandleInfo_InitFuncPtr +FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; + +FHoudiniApi::ImageFileFormat_CreateFuncPtr +FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; + +FHoudiniApi::ImageFileFormat_InitFuncPtr +FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; + +FHoudiniApi::ImageInfo_CreateFuncPtr +FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; + +FHoudiniApi::ImageInfo_InitFuncPtr +FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; + +FHoudiniApi::InitializeFuncPtr +FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; + +FHoudiniApi::InsertMultiparmInstanceFuncPtr +FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; + +FHoudiniApi::InterruptFuncPtr +FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; + +FHoudiniApi::IsInitializedFuncPtr +FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; + +FHoudiniApi::IsNodeValidFuncPtr +FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; + +FHoudiniApi::IsSessionValidFuncPtr +FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; + +FHoudiniApi::Keyframe_CreateFuncPtr +FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; + +FHoudiniApi::Keyframe_InitFuncPtr +FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; + +FHoudiniApi::LoadAssetLibraryFromFileFuncPtr +FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; + +FHoudiniApi::LoadAssetLibraryFromMemoryFuncPtr +FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; + +FHoudiniApi::LoadGeoFromFileFuncPtr +FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; + +FHoudiniApi::LoadGeoFromMemoryFuncPtr +FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; + +FHoudiniApi::LoadHIPFileFuncPtr +FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; + +FHoudiniApi::LoadNodeFromFileFuncPtr +FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; + +FHoudiniApi::MaterialInfo_CreateFuncPtr +FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; + +FHoudiniApi::MaterialInfo_InitFuncPtr +FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; + +FHoudiniApi::MergeHIPFileFuncPtr +FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; + +FHoudiniApi::NodeInfo_CreateFuncPtr +FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; + +FHoudiniApi::NodeInfo_InitFuncPtr +FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; + +FHoudiniApi::ObjectInfo_CreateFuncPtr +FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; + +FHoudiniApi::ObjectInfo_InitFuncPtr +FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; + +FHoudiniApi::ParmChoiceInfo_CreateFuncPtr +FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; + +FHoudiniApi::ParmChoiceInfo_InitFuncPtr +FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; + +FHoudiniApi::ParmHasExpressionFuncPtr +FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; + +FHoudiniApi::ParmHasTagFuncPtr +FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; + +FHoudiniApi::ParmInfo_CreateFuncPtr +FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; + +FHoudiniApi::ParmInfo_GetFloatValueCountFuncPtr +FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; + +FHoudiniApi::ParmInfo_GetIntValueCountFuncPtr +FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; + +FHoudiniApi::ParmInfo_GetStringValueCountFuncPtr +FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; + +FHoudiniApi::ParmInfo_InitFuncPtr +FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; + +FHoudiniApi::ParmInfo_IsFloatFuncPtr +FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; + +FHoudiniApi::ParmInfo_IsIntFuncPtr +FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; + +FHoudiniApi::ParmInfo_IsNodeFuncPtr +FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; + +FHoudiniApi::ParmInfo_IsNonValueFuncPtr +FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; + +FHoudiniApi::ParmInfo_IsPathFuncPtr +FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; + +FHoudiniApi::ParmInfo_IsStringFuncPtr +FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; + +FHoudiniApi::PartInfo_CreateFuncPtr +FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; + +FHoudiniApi::PartInfo_GetAttributeCountByOwnerFuncPtr +FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; + +FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerFuncPtr +FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; + +FHoudiniApi::PartInfo_GetElementCountByGroupTypeFuncPtr +FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; + +FHoudiniApi::PartInfo_InitFuncPtr +FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; + +FHoudiniApi::PausePDGCookFuncPtr +FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; + +FHoudiniApi::PythonThreadInterpreterLockFuncPtr +FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; + +FHoudiniApi::QueryNodeInputFuncPtr +FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; + +FHoudiniApi::QueryNodeOutputConnectedCountFuncPtr +FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; + +FHoudiniApi::QueryNodeOutputConnectedNodesFuncPtr +FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; + +FHoudiniApi::RemoveCustomStringFuncPtr +FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; + +FHoudiniApi::RemoveMultiparmInstanceFuncPtr +FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; + +FHoudiniApi::RemoveParmExpressionFuncPtr +FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; + +FHoudiniApi::RenameNodeFuncPtr +FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; + +FHoudiniApi::RenderCOPToImageFuncPtr +FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; + +FHoudiniApi::RenderTextureToImageFuncPtr +FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; + +FHoudiniApi::ResetSimulationFuncPtr +FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; + +FHoudiniApi::RevertGeoFuncPtr +FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; + +FHoudiniApi::RevertParmToDefaultFuncPtr +FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; + +FHoudiniApi::RevertParmToDefaultsFuncPtr +FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; + +FHoudiniApi::SaveGeoToFileFuncPtr +FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; + +FHoudiniApi::SaveGeoToMemoryFuncPtr +FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; + +FHoudiniApi::SaveHIPFileFuncPtr +FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; + +FHoudiniApi::SaveNodeToFileFuncPtr +FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; + +FHoudiniApi::SessionSyncInfo_CreateFuncPtr +FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; + +FHoudiniApi::SetAnimCurveFuncPtr +FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; + +FHoudiniApi::SetAttributeFloat64DataFuncPtr +FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; + +FHoudiniApi::SetAttributeFloatDataFuncPtr +FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + +FHoudiniApi::SetAttributeInt16DataFuncPtr +FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; + +FHoudiniApi::SetAttributeInt64DataFuncPtr +FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + +FHoudiniApi::SetAttributeInt8DataFuncPtr +FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; + +FHoudiniApi::SetAttributeIntDataFuncPtr +FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; + +FHoudiniApi::SetAttributeStringDataFuncPtr +FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + +FHoudiniApi::SetAttributeUInt8DataFuncPtr +FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; + +FHoudiniApi::SetCachePropertyFuncPtr +FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + +FHoudiniApi::SetCompositorOptionsFuncPtr +FHoudiniApi::SetCompositorOptions = &FHoudiniApi::SetCompositorOptionsEmptyStub; + +FHoudiniApi::SetCurveCountsFuncPtr +FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; + +FHoudiniApi::SetCurveInfoFuncPtr +FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; + +FHoudiniApi::SetCurveKnotsFuncPtr +FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; + +FHoudiniApi::SetCurveOrdersFuncPtr +FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; + +FHoudiniApi::SetCustomStringFuncPtr +FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; + +FHoudiniApi::SetFaceCountsFuncPtr +FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; + +FHoudiniApi::SetGroupMembershipFuncPtr +FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; + +FHoudiniApi::SetHeightFieldDataFuncPtr +FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; + +FHoudiniApi::SetImageInfoFuncPtr +FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; + +FHoudiniApi::SetNodeDisplayFuncPtr +FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; + +FHoudiniApi::SetObjectTransformFuncPtr +FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; + +FHoudiniApi::SetParmExpressionFuncPtr +FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; + +FHoudiniApi::SetParmFloatValueFuncPtr +FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; + +FHoudiniApi::SetParmFloatValuesFuncPtr +FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; + +FHoudiniApi::SetParmIntValueFuncPtr +FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; + +FHoudiniApi::SetParmIntValuesFuncPtr +FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; + +FHoudiniApi::SetParmNodeValueFuncPtr +FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; + +FHoudiniApi::SetParmStringValueFuncPtr +FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; + +FHoudiniApi::SetPartInfoFuncPtr +FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; + +FHoudiniApi::SetPresetFuncPtr +FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; + +FHoudiniApi::SetServerEnvIntFuncPtr +FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; + +FHoudiniApi::SetServerEnvStringFuncPtr +FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; + +FHoudiniApi::SetSessionSyncFuncPtr +FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; + +FHoudiniApi::SetSessionSyncInfoFuncPtr +FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; + +FHoudiniApi::SetTimeFuncPtr +FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; + +FHoudiniApi::SetTimelineOptionsFuncPtr +FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; + +FHoudiniApi::SetTransformAnimCurveFuncPtr +FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; + +FHoudiniApi::SetUseHoudiniTimeFuncPtr +FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; + +FHoudiniApi::SetVertexListFuncPtr +FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; + +FHoudiniApi::SetViewportFuncPtr +FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; + +FHoudiniApi::SetVolumeInfoFuncPtr +FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; + +FHoudiniApi::SetVolumeTileFloatDataFuncPtr +FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; + +FHoudiniApi::SetVolumeTileIntDataFuncPtr +FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; + +FHoudiniApi::SetVolumeVoxelFloatDataFuncPtr +FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; + +FHoudiniApi::SetVolumeVoxelIntDataFuncPtr +FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; + +FHoudiniApi::SetWorkitemFloatDataFuncPtr +FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; + +FHoudiniApi::SetWorkitemIntDataFuncPtr +FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; + +FHoudiniApi::SetWorkitemStringDataFuncPtr +FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; + +FHoudiniApi::StartThriftNamedPipeServerFuncPtr +FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; + +FHoudiniApi::StartThriftSocketServerFuncPtr +FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; + +FHoudiniApi::ThriftServerOptions_CreateFuncPtr +FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; + +FHoudiniApi::ThriftServerOptions_InitFuncPtr +FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; + +FHoudiniApi::TimelineOptions_CreateFuncPtr +FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; + +FHoudiniApi::TimelineOptions_InitFuncPtr +FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; + +FHoudiniApi::TransformEuler_CreateFuncPtr +FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; + +FHoudiniApi::TransformEuler_InitFuncPtr +FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; + +FHoudiniApi::Transform_CreateFuncPtr +FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; + +FHoudiniApi::Transform_InitFuncPtr +FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; + +FHoudiniApi::Viewport_CreateFuncPtr +FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; + +FHoudiniApi::VolumeInfo_CreateFuncPtr +FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; + +FHoudiniApi::VolumeInfo_InitFuncPtr +FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; + +FHoudiniApi::VolumeTileInfo_CreateFuncPtr +FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; + +FHoudiniApi::VolumeTileInfo_InitFuncPtr +FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; + + +void +FHoudiniApi::InitializeHAPI(void* LibraryHandle) +{ + if(!LibraryHandle) return; + + FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute")); + FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup")); + FHoudiniApi::AssetInfo_Create = (AssetInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Create")); + FHoudiniApi::AssetInfo_Init = (AssetInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Init")); + FHoudiniApi::AttributeInfo_Create = (AttributeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Create")); + FHoudiniApi::AttributeInfo_Init = (AttributeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Init")); + FHoudiniApi::BindCustomImplementation = (BindCustomImplementationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_BindCustomImplementation")); + FHoudiniApi::CancelPDGCook = (CancelPDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CancelPDGCook")); + FHoudiniApi::CheckForSpecificErrors = (CheckForSpecificErrorsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CheckForSpecificErrors")); + FHoudiniApi::Cleanup = (CleanupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Cleanup")); + FHoudiniApi::ClearConnectionError = (ClearConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ClearConnectionError")); + FHoudiniApi::CloseSession = (CloseSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CloseSession")); + FHoudiniApi::CommitGeo = (CommitGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitGeo")); + FHoudiniApi::CommitWorkitems = (CommitWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitWorkitems")); + FHoudiniApi::ComposeChildNodeList = (ComposeChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeChildNodeList")); + FHoudiniApi::ComposeNodeCookResult = (ComposeNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeNodeCookResult")); + FHoudiniApi::ComposeObjectList = (ComposeObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeObjectList")); + FHoudiniApi::CompositorOptions_Create = (CompositorOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CompositorOptions_Create")); + FHoudiniApi::CompositorOptions_Init = (CompositorOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CompositorOptions_Init")); + FHoudiniApi::ConnectNodeInput = (ConnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConnectNodeInput")); + FHoudiniApi::ConvertMatrixToEuler = (ConvertMatrixToEulerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToEuler")); + FHoudiniApi::ConvertMatrixToQuat = (ConvertMatrixToQuatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToQuat")); + FHoudiniApi::ConvertTransform = (ConvertTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransform")); + FHoudiniApi::ConvertTransformEulerToMatrix = (ConvertTransformEulerToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformEulerToMatrix")); + FHoudiniApi::ConvertTransformQuatToMatrix = (ConvertTransformQuatToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformQuatToMatrix")); + FHoudiniApi::CookNode = (CookNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookNode")); + FHoudiniApi::CookOptions_AreEqual = (CookOptions_AreEqualFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_AreEqual")); + FHoudiniApi::CookOptions_Create = (CookOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Create")); + FHoudiniApi::CookOptions_Init = (CookOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Init")); + FHoudiniApi::CookPDG = (CookPDGFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookPDG")); + FHoudiniApi::CreateCustomSession = (CreateCustomSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateCustomSession")); + FHoudiniApi::CreateHeightFieldInput = (CreateHeightFieldInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightFieldInput")); + FHoudiniApi::CreateHeightfieldInputVolumeNode = (CreateHeightfieldInputVolumeNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightfieldInputVolumeNode")); + FHoudiniApi::CreateInProcessSession = (CreateInProcessSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInProcessSession")); + FHoudiniApi::CreateInputNode = (CreateInputNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInputNode")); + FHoudiniApi::CreateNode = (CreateNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateNode")); + FHoudiniApi::CreateThriftNamedPipeSession = (CreateThriftNamedPipeSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftNamedPipeSession")); + FHoudiniApi::CreateThriftSocketSession = (CreateThriftSocketSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftSocketSession")); + FHoudiniApi::CreateWorkitem = (CreateWorkitemFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateWorkitem")); + FHoudiniApi::CurveInfo_Create = (CurveInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Create")); + FHoudiniApi::CurveInfo_Init = (CurveInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Init")); + FHoudiniApi::DeleteAttribute = (DeleteAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteAttribute")); + FHoudiniApi::DeleteGroup = (DeleteGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteGroup")); + FHoudiniApi::DeleteNode = (DeleteNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteNode")); + FHoudiniApi::DirtyPDGNode = (DirtyPDGNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DirtyPDGNode")); + FHoudiniApi::DisconnectNodeInput = (DisconnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeInput")); + FHoudiniApi::DisconnectNodeOutputsAt = (DisconnectNodeOutputsAtFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeOutputsAt")); + FHoudiniApi::ExtractImageToFile = (ExtractImageToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToFile")); + FHoudiniApi::ExtractImageToMemory = (ExtractImageToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToMemory")); + FHoudiniApi::GeoInfo_Create = (GeoInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Create")); + FHoudiniApi::GeoInfo_GetGroupCountByType = (GeoInfo_GetGroupCountByTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_GetGroupCountByType")); + FHoudiniApi::GeoInfo_Init = (GeoInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Init")); + FHoudiniApi::GetActiveCacheCount = (GetActiveCacheCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheCount")); + FHoudiniApi::GetActiveCacheNames = (GetActiveCacheNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheNames")); + FHoudiniApi::GetAssetDefinitionParmCounts = (GetAssetDefinitionParmCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmCounts")); + FHoudiniApi::GetAssetDefinitionParmInfos = (GetAssetDefinitionParmInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmInfos")); + FHoudiniApi::GetAssetDefinitionParmValues = (GetAssetDefinitionParmValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmValues")); + FHoudiniApi::GetAssetInfo = (GetAssetInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetInfo")); + FHoudiniApi::GetAttributeFloat64ArrayData = (GetAttributeFloat64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64ArrayData")); + FHoudiniApi::GetAttributeFloat64Data = (GetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64Data")); + FHoudiniApi::GetAttributeFloatArrayData = (GetAttributeFloatArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatArrayData")); + FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); + FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); + FHoudiniApi::GetAttributeInt16ArrayData = (GetAttributeInt16ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16ArrayData")); + FHoudiniApi::GetAttributeInt16Data = (GetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16Data")); + FHoudiniApi::GetAttributeInt64ArrayData = (GetAttributeInt64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64ArrayData")); + FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); + FHoudiniApi::GetAttributeInt8ArrayData = (GetAttributeInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8ArrayData")); + FHoudiniApi::GetAttributeInt8Data = (GetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8Data")); + FHoudiniApi::GetAttributeIntArrayData = (GetAttributeIntArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntArrayData")); + FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); + FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); + FHoudiniApi::GetAttributeStringArrayData = (GetAttributeStringArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringArrayData")); + FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); + FHoudiniApi::GetAttributeUInt8ArrayData = (GetAttributeUInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8ArrayData")); + FHoudiniApi::GetAttributeUInt8Data = (GetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8Data")); + FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); + FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); + FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); + FHoudiniApi::GetCacheProperty = (GetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCacheProperty")); + FHoudiniApi::GetComposedChildNodeList = (GetComposedChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedChildNodeList")); + FHoudiniApi::GetComposedNodeCookResult = (GetComposedNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedNodeCookResult")); + FHoudiniApi::GetComposedObjectList = (GetComposedObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectList")); + FHoudiniApi::GetComposedObjectTransforms = (GetComposedObjectTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectTransforms")); + FHoudiniApi::GetCompositorOptions = (GetCompositorOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCompositorOptions")); + FHoudiniApi::GetConnectionError = (GetConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionError")); + FHoudiniApi::GetConnectionErrorLength = (GetConnectionErrorLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionErrorLength")); + FHoudiniApi::GetCookingCurrentCount = (GetCookingCurrentCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingCurrentCount")); + FHoudiniApi::GetCookingTotalCount = (GetCookingTotalCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingTotalCount")); + FHoudiniApi::GetCurveCounts = (GetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveCounts")); + FHoudiniApi::GetCurveInfo = (GetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveInfo")); + FHoudiniApi::GetCurveKnots = (GetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveKnots")); + FHoudiniApi::GetCurveOrders = (GetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveOrders")); + FHoudiniApi::GetDisplayGeoInfo = (GetDisplayGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetDisplayGeoInfo")); + FHoudiniApi::GetEdgeCountOfEdgeGroup = (GetEdgeCountOfEdgeGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEdgeCountOfEdgeGroup")); + FHoudiniApi::GetEnvInt = (GetEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEnvInt")); + FHoudiniApi::GetFaceCounts = (GetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFaceCounts")); + FHoudiniApi::GetFirstVolumeTile = (GetFirstVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFirstVolumeTile")); + FHoudiniApi::GetGeoInfo = (GetGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoInfo")); + FHoudiniApi::GetGeoSize = (GetGeoSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoSize")); + FHoudiniApi::GetGroupCountOnPackedInstancePart = (GetGroupCountOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupCountOnPackedInstancePart")); + FHoudiniApi::GetGroupMembership = (GetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembership")); + FHoudiniApi::GetGroupMembershipOnPackedInstancePart = (GetGroupMembershipOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembershipOnPackedInstancePart")); + FHoudiniApi::GetGroupNames = (GetGroupNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNames")); + FHoudiniApi::GetGroupNamesOnPackedInstancePart = (GetGroupNamesOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNamesOnPackedInstancePart")); + FHoudiniApi::GetHIPFileNodeCount = (GetHIPFileNodeCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeCount")); + FHoudiniApi::GetHIPFileNodeIds = (GetHIPFileNodeIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeIds")); + FHoudiniApi::GetHandleBindingInfo = (GetHandleBindingInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleBindingInfo")); + FHoudiniApi::GetHandleInfo = (GetHandleInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleInfo")); + FHoudiniApi::GetHeightFieldData = (GetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHeightFieldData")); + FHoudiniApi::GetImageFilePath = (GetImageFilePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageFilePath")); + FHoudiniApi::GetImageInfo = (GetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageInfo")); + FHoudiniApi::GetImageMemoryBuffer = (GetImageMemoryBufferFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageMemoryBuffer")); + FHoudiniApi::GetImagePlaneCount = (GetImagePlaneCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlaneCount")); + FHoudiniApi::GetImagePlanes = (GetImagePlanesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlanes")); + FHoudiniApi::GetInstanceTransformsOnPart = (GetInstanceTransformsOnPartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstanceTransformsOnPart")); + FHoudiniApi::GetInstancedObjectIds = (GetInstancedObjectIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedObjectIds")); + FHoudiniApi::GetInstancedPartIds = (GetInstancedPartIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedPartIds")); + FHoudiniApi::GetInstancerPartTransforms = (GetInstancerPartTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancerPartTransforms")); + FHoudiniApi::GetManagerNodeId = (GetManagerNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetManagerNodeId")); + FHoudiniApi::GetMaterialInfo = (GetMaterialInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialInfo")); + FHoudiniApi::GetMaterialNodeIdsOnFaces = (GetMaterialNodeIdsOnFacesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialNodeIdsOnFaces")); + FHoudiniApi::GetNextVolumeTile = (GetNextVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNextVolumeTile")); + FHoudiniApi::GetNodeInfo = (GetNodeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInfo")); + FHoudiniApi::GetNodeInputName = (GetNodeInputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInputName")); + FHoudiniApi::GetNodeOutputName = (GetNodeOutputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeOutputName")); + FHoudiniApi::GetNodePath = (GetNodePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodePath")); + FHoudiniApi::GetNumWorkitems = (GetNumWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNumWorkitems")); + FHoudiniApi::GetObjectInfo = (GetObjectInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectInfo")); + FHoudiniApi::GetObjectTransform = (GetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectTransform")); + FHoudiniApi::GetOutputGeoCount = (GetOutputGeoCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputGeoCount")); + FHoudiniApi::GetOutputGeoInfos = (GetOutputGeoInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputGeoInfos")); + FHoudiniApi::GetOutputNodeId = (GetOutputNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputNodeId")); + FHoudiniApi::GetPDGEvents = (GetPDGEventsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGEvents")); + FHoudiniApi::GetPDGGraphContextId = (GetPDGGraphContextIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContextId")); + FHoudiniApi::GetPDGGraphContexts = (GetPDGGraphContextsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContexts")); + FHoudiniApi::GetPDGState = (GetPDGStateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGState")); + FHoudiniApi::GetParameters = (GetParametersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParameters")); + FHoudiniApi::GetParmChoiceLists = (GetParmChoiceListsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmChoiceLists")); + FHoudiniApi::GetParmExpression = (GetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmExpression")); + FHoudiniApi::GetParmFile = (GetParmFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFile")); + FHoudiniApi::GetParmFloatValue = (GetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValue")); + FHoudiniApi::GetParmFloatValues = (GetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValues")); + FHoudiniApi::GetParmIdFromName = (GetParmIdFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIdFromName")); + FHoudiniApi::GetParmInfo = (GetParmInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfo")); + FHoudiniApi::GetParmInfoFromName = (GetParmInfoFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfoFromName")); + FHoudiniApi::GetParmIntValue = (GetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValue")); + FHoudiniApi::GetParmIntValues = (GetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValues")); + FHoudiniApi::GetParmNodeValue = (GetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmNodeValue")); + FHoudiniApi::GetParmStringValue = (GetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValue")); + FHoudiniApi::GetParmStringValues = (GetParmStringValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValues")); + FHoudiniApi::GetParmTagName = (GetParmTagNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagName")); + FHoudiniApi::GetParmTagValue = (GetParmTagValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagValue")); + FHoudiniApi::GetParmWithTag = (GetParmWithTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmWithTag")); + FHoudiniApi::GetPartInfo = (GetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPartInfo")); + FHoudiniApi::GetPreset = (GetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPreset")); + FHoudiniApi::GetPresetBufLength = (GetPresetBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPresetBufLength")); + FHoudiniApi::GetServerEnvInt = (GetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvInt")); + FHoudiniApi::GetServerEnvString = (GetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvString")); + FHoudiniApi::GetServerEnvVarCount = (GetServerEnvVarCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarCount")); + FHoudiniApi::GetServerEnvVarList = (GetServerEnvVarListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarList")); + FHoudiniApi::GetSessionEnvInt = (GetSessionEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionEnvInt")); + FHoudiniApi::GetSessionSyncInfo = (GetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionSyncInfo")); + FHoudiniApi::GetSphereInfo = (GetSphereInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSphereInfo")); + FHoudiniApi::GetStatus = (GetStatusFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatus")); + FHoudiniApi::GetStatusString = (GetStatusStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusString")); + FHoudiniApi::GetStatusStringBufLength = (GetStatusStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusStringBufLength")); + FHoudiniApi::GetString = (GetStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetString")); + FHoudiniApi::GetStringBatch = (GetStringBatchFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatch")); + FHoudiniApi::GetStringBatchSize = (GetStringBatchSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatchSize")); + FHoudiniApi::GetStringBufLength = (GetStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBufLength")); + FHoudiniApi::GetSupportedImageFileFormatCount = (GetSupportedImageFileFormatCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormatCount")); + FHoudiniApi::GetSupportedImageFileFormats = (GetSupportedImageFileFormatsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormats")); + FHoudiniApi::GetTime = (GetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTime")); + FHoudiniApi::GetTimelineOptions = (GetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTimelineOptions")); + FHoudiniApi::GetTotalCookCount = (GetTotalCookCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTotalCookCount")); + FHoudiniApi::GetUseHoudiniTime = (GetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetUseHoudiniTime")); + FHoudiniApi::GetVertexList = (GetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVertexList")); + FHoudiniApi::GetViewport = (GetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetViewport")); + FHoudiniApi::GetVolumeBounds = (GetVolumeBoundsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeBounds")); + FHoudiniApi::GetVolumeInfo = (GetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeInfo")); + FHoudiniApi::GetVolumeTileFloatData = (GetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileFloatData")); + FHoudiniApi::GetVolumeTileIntData = (GetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileIntData")); + FHoudiniApi::GetVolumeVisualInfo = (GetVolumeVisualInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVisualInfo")); + FHoudiniApi::GetVolumeVoxelFloatData = (GetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelFloatData")); + FHoudiniApi::GetVolumeVoxelIntData = (GetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelIntData")); + FHoudiniApi::GetWorkitemDataLength = (GetWorkitemDataLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemDataLength")); + FHoudiniApi::GetWorkitemFloatData = (GetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemFloatData")); + FHoudiniApi::GetWorkitemInfo = (GetWorkitemInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemInfo")); + FHoudiniApi::GetWorkitemIntData = (GetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemIntData")); + FHoudiniApi::GetWorkitemResultInfo = (GetWorkitemResultInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemResultInfo")); + FHoudiniApi::GetWorkitemStringData = (GetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemStringData")); + FHoudiniApi::GetWorkitems = (GetWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitems")); + FHoudiniApi::HandleBindingInfo_Create = (HandleBindingInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Create")); + FHoudiniApi::HandleBindingInfo_Init = (HandleBindingInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Init")); + FHoudiniApi::HandleInfo_Create = (HandleInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Create")); + FHoudiniApi::HandleInfo_Init = (HandleInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Init")); + FHoudiniApi::ImageFileFormat_Create = (ImageFileFormat_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Create")); + FHoudiniApi::ImageFileFormat_Init = (ImageFileFormat_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Init")); + FHoudiniApi::ImageInfo_Create = (ImageInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Create")); + FHoudiniApi::ImageInfo_Init = (ImageInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Init")); + FHoudiniApi::Initialize = (InitializeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Initialize")); + FHoudiniApi::InsertMultiparmInstance = (InsertMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_InsertMultiparmInstance")); + FHoudiniApi::Interrupt = (InterruptFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Interrupt")); + FHoudiniApi::IsInitialized = (IsInitializedFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsInitialized")); + FHoudiniApi::IsNodeValid = (IsNodeValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsNodeValid")); + FHoudiniApi::IsSessionValid = (IsSessionValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsSessionValid")); + FHoudiniApi::Keyframe_Create = (Keyframe_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Create")); + FHoudiniApi::Keyframe_Init = (Keyframe_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Init")); + FHoudiniApi::LoadAssetLibraryFromFile = (LoadAssetLibraryFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromFile")); + FHoudiniApi::LoadAssetLibraryFromMemory = (LoadAssetLibraryFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromMemory")); + FHoudiniApi::LoadGeoFromFile = (LoadGeoFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromFile")); + FHoudiniApi::LoadGeoFromMemory = (LoadGeoFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromMemory")); + FHoudiniApi::LoadHIPFile = (LoadHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadHIPFile")); + FHoudiniApi::LoadNodeFromFile = (LoadNodeFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadNodeFromFile")); + FHoudiniApi::MaterialInfo_Create = (MaterialInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Create")); + FHoudiniApi::MaterialInfo_Init = (MaterialInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Init")); + FHoudiniApi::MergeHIPFile = (MergeHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MergeHIPFile")); + FHoudiniApi::NodeInfo_Create = (NodeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Create")); + FHoudiniApi::NodeInfo_Init = (NodeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Init")); + FHoudiniApi::ObjectInfo_Create = (ObjectInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Create")); + FHoudiniApi::ObjectInfo_Init = (ObjectInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Init")); + FHoudiniApi::ParmChoiceInfo_Create = (ParmChoiceInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Create")); + FHoudiniApi::ParmChoiceInfo_Init = (ParmChoiceInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Init")); + FHoudiniApi::ParmHasExpression = (ParmHasExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasExpression")); + FHoudiniApi::ParmHasTag = (ParmHasTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasTag")); + FHoudiniApi::ParmInfo_Create = (ParmInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Create")); + FHoudiniApi::ParmInfo_GetFloatValueCount = (ParmInfo_GetFloatValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetFloatValueCount")); + FHoudiniApi::ParmInfo_GetIntValueCount = (ParmInfo_GetIntValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetIntValueCount")); + FHoudiniApi::ParmInfo_GetStringValueCount = (ParmInfo_GetStringValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetStringValueCount")); + FHoudiniApi::ParmInfo_Init = (ParmInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Init")); + FHoudiniApi::ParmInfo_IsFloat = (ParmInfo_IsFloatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsFloat")); + FHoudiniApi::ParmInfo_IsInt = (ParmInfo_IsIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsInt")); + FHoudiniApi::ParmInfo_IsNode = (ParmInfo_IsNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNode")); + FHoudiniApi::ParmInfo_IsNonValue = (ParmInfo_IsNonValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNonValue")); + FHoudiniApi::ParmInfo_IsPath = (ParmInfo_IsPathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsPath")); + FHoudiniApi::ParmInfo_IsString = (ParmInfo_IsStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsString")); + FHoudiniApi::PartInfo_Create = (PartInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Create")); + FHoudiniApi::PartInfo_GetAttributeCountByOwner = (PartInfo_GetAttributeCountByOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetAttributeCountByOwner")); + FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = (PartInfo_GetElementCountByAttributeOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByAttributeOwner")); + FHoudiniApi::PartInfo_GetElementCountByGroupType = (PartInfo_GetElementCountByGroupTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByGroupType")); + FHoudiniApi::PartInfo_Init = (PartInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Init")); + FHoudiniApi::PausePDGCook = (PausePDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PausePDGCook")); + FHoudiniApi::PythonThreadInterpreterLock = (PythonThreadInterpreterLockFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PythonThreadInterpreterLock")); + FHoudiniApi::QueryNodeInput = (QueryNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeInput")); + FHoudiniApi::QueryNodeOutputConnectedCount = (QueryNodeOutputConnectedCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedCount")); + FHoudiniApi::QueryNodeOutputConnectedNodes = (QueryNodeOutputConnectedNodesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedNodes")); + FHoudiniApi::RemoveCustomString = (RemoveCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveCustomString")); + FHoudiniApi::RemoveMultiparmInstance = (RemoveMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveMultiparmInstance")); + FHoudiniApi::RemoveParmExpression = (RemoveParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveParmExpression")); + FHoudiniApi::RenameNode = (RenameNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenameNode")); + FHoudiniApi::RenderCOPToImage = (RenderCOPToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderCOPToImage")); + FHoudiniApi::RenderTextureToImage = (RenderTextureToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderTextureToImage")); + FHoudiniApi::ResetSimulation = (ResetSimulationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ResetSimulation")); + FHoudiniApi::RevertGeo = (RevertGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertGeo")); + FHoudiniApi::RevertParmToDefault = (RevertParmToDefaultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefault")); + FHoudiniApi::RevertParmToDefaults = (RevertParmToDefaultsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefaults")); + FHoudiniApi::SaveGeoToFile = (SaveGeoToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToFile")); + FHoudiniApi::SaveGeoToMemory = (SaveGeoToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToMemory")); + FHoudiniApi::SaveHIPFile = (SaveHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveHIPFile")); + FHoudiniApi::SaveNodeToFile = (SaveNodeToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveNodeToFile")); + FHoudiniApi::SessionSyncInfo_Create = (SessionSyncInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SessionSyncInfo_Create")); + FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); + FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); + FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); + FHoudiniApi::SetAttributeInt16Data = (SetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt16Data")); + FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); + FHoudiniApi::SetAttributeInt8Data = (SetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt8Data")); + FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); + FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); + FHoudiniApi::SetAttributeUInt8Data = (SetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeUInt8Data")); + FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); + FHoudiniApi::SetCompositorOptions = (SetCompositorOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCompositorOptions")); + FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); + FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); + FHoudiniApi::SetCurveKnots = (SetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveKnots")); + FHoudiniApi::SetCurveOrders = (SetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveOrders")); + FHoudiniApi::SetCustomString = (SetCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCustomString")); + FHoudiniApi::SetFaceCounts = (SetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetFaceCounts")); + FHoudiniApi::SetGroupMembership = (SetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetGroupMembership")); + FHoudiniApi::SetHeightFieldData = (SetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetHeightFieldData")); + FHoudiniApi::SetImageInfo = (SetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetImageInfo")); + FHoudiniApi::SetNodeDisplay = (SetNodeDisplayFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetNodeDisplay")); + FHoudiniApi::SetObjectTransform = (SetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetObjectTransform")); + FHoudiniApi::SetParmExpression = (SetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmExpression")); + FHoudiniApi::SetParmFloatValue = (SetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValue")); + FHoudiniApi::SetParmFloatValues = (SetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValues")); + FHoudiniApi::SetParmIntValue = (SetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValue")); + FHoudiniApi::SetParmIntValues = (SetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValues")); + FHoudiniApi::SetParmNodeValue = (SetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmNodeValue")); + FHoudiniApi::SetParmStringValue = (SetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmStringValue")); + FHoudiniApi::SetPartInfo = (SetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPartInfo")); + FHoudiniApi::SetPreset = (SetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPreset")); + FHoudiniApi::SetServerEnvInt = (SetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvInt")); + FHoudiniApi::SetServerEnvString = (SetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvString")); + FHoudiniApi::SetSessionSync = (SetSessionSyncFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSync")); + FHoudiniApi::SetSessionSyncInfo = (SetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSyncInfo")); + FHoudiniApi::SetTime = (SetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTime")); + FHoudiniApi::SetTimelineOptions = (SetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTimelineOptions")); + FHoudiniApi::SetTransformAnimCurve = (SetTransformAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTransformAnimCurve")); + FHoudiniApi::SetUseHoudiniTime = (SetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetUseHoudiniTime")); + FHoudiniApi::SetVertexList = (SetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVertexList")); + FHoudiniApi::SetViewport = (SetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetViewport")); + FHoudiniApi::SetVolumeInfo = (SetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeInfo")); + FHoudiniApi::SetVolumeTileFloatData = (SetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileFloatData")); + FHoudiniApi::SetVolumeTileIntData = (SetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileIntData")); + FHoudiniApi::SetVolumeVoxelFloatData = (SetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelFloatData")); + FHoudiniApi::SetVolumeVoxelIntData = (SetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelIntData")); + FHoudiniApi::SetWorkitemFloatData = (SetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemFloatData")); + FHoudiniApi::SetWorkitemIntData = (SetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemIntData")); + FHoudiniApi::SetWorkitemStringData = (SetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemStringData")); + FHoudiniApi::StartThriftNamedPipeServer = (StartThriftNamedPipeServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftNamedPipeServer")); + FHoudiniApi::StartThriftSocketServer = (StartThriftSocketServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftSocketServer")); + FHoudiniApi::ThriftServerOptions_Create = (ThriftServerOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Create")); + FHoudiniApi::ThriftServerOptions_Init = (ThriftServerOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Init")); + FHoudiniApi::TimelineOptions_Create = (TimelineOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Create")); + FHoudiniApi::TimelineOptions_Init = (TimelineOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Init")); + FHoudiniApi::TransformEuler_Create = (TransformEuler_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Create")); + FHoudiniApi::TransformEuler_Init = (TransformEuler_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Init")); + FHoudiniApi::Transform_Create = (Transform_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Create")); + FHoudiniApi::Transform_Init = (Transform_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Init")); + FHoudiniApi::Viewport_Create = (Viewport_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Viewport_Create")); + FHoudiniApi::VolumeInfo_Create = (VolumeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Create")); + FHoudiniApi::VolumeInfo_Init = (VolumeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Init")); + FHoudiniApi::VolumeTileInfo_Create = (VolumeTileInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Create")); + FHoudiniApi::VolumeTileInfo_Init = (VolumeTileInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Init")); +} + + +void +FHoudiniApi::FinalizeHAPI() +{ + FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; + FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; + FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; + FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; + FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; + FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; + FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; + FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; + FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; + FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; + FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; + FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; + FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; + FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; + FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; + FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; + FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + FHoudiniApi::CompositorOptions_Create = &FHoudiniApi::CompositorOptions_CreateEmptyStub; + FHoudiniApi::CompositorOptions_Init = &FHoudiniApi::CompositorOptions_InitEmptyStub; + FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; + FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; + FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; + FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; + FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; + FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; + FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; + FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; + FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; + FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; + FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; + FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; + FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; + FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; + FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; + FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; + FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; + FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; + FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; + FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; + FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; + FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; + FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; + FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; + FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; + FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; + FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; + FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; + FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; + FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; + FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; + FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; + FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; + FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; + FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; + FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; + FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; + FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; + FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; + FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; + FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; + FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; + FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; + FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; + FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; + FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; + FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; + FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; + FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; + FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; + FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; + FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; + FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; + FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; + FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; + FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; + FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; + FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; + FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + FHoudiniApi::GetCompositorOptions = &FHoudiniApi::GetCompositorOptionsEmptyStub; + FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; + FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; + FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; + FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; + FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; + FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; + FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; + FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; + FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + FHoudiniApi::GetEdgeCountOfEdgeGroup = &FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub; + FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; + FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; + FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; + FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; + FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; + FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; + FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; + FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; + FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; + FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; + FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; + FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; + FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; + FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; + FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; + FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; + FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; + FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; + FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; + FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; + FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; + FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; + FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; + FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; + FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; + FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; + FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; + FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; + FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; + FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; + FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; + FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; + FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; + FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; + FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + FHoudiniApi::GetOutputGeoCount = &FHoudiniApi::GetOutputGeoCountEmptyStub; + FHoudiniApi::GetOutputGeoInfos = &FHoudiniApi::GetOutputGeoInfosEmptyStub; + FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; + FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; + FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; + FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; + FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; + FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; + FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; + FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; + FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; + FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; + FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; + FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; + FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; + FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; + FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; + FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; + FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; + FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; + FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; + FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; + FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; + FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; + FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; + FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; + FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; + FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; + FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; + FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; + FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; + FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; + FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; + FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; + FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; + FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; + FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; + FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; + FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; + FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; + FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; + FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; + FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; + FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; + FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; + FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; + FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; + FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; + FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; + FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; + FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; + FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; + FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; + FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; + FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; + FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; + FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; + FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; + FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; + FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; + FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; + FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; + FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; + FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; + FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; + FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; + FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; + FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; + FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; + FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; + FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; + FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; + FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; + FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; + FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; + FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; + FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; + FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; + FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; + FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; + FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; + FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; + FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; + FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; + FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; + FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; + FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; + FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; + FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; + FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; + FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; + FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; + FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; + FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; + FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; + FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; + FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; + FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; + FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; + FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; + FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; + FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; + FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; + FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; + FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; + FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; + FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; + FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; + FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; + FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; + FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; + FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; + FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; + FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; + FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; + FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; + FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; + FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; + FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; + FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; + FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; + FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; + FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; + FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; + FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; + FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; + FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; + FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; + FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; + FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; + FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; + FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; + FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; + FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; + FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; + FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; + FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; + FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; + FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + FHoudiniApi::SetCompositorOptions = &FHoudiniApi::SetCompositorOptionsEmptyStub; + FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; + FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; + FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; + FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; + FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; + FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; + FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; + FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; + FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; + FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; + FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; + FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; + FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; + FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; + FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; + FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; + FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; + FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; + FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; + FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; + FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; + FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; + FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; + FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; + FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; + FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; + FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; + FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; + FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; + FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; + FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; + FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; + FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; + FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; + FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; + FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; + FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; + FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; + FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; + FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; + FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; + FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; + FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; + FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; + FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; + FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; + FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; + FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; + FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; + FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; + FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; + FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; + FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; +} + + +bool +FHoudiniApi::IsHAPIInitialized() +{ + return ( FHoudiniApi::IsInitialized != &FHoudiniApi::IsInitializedEmptyStub ); +} + + +HAPI_Result +FHoudiniApi::AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_AssetInfo +FHoudiniApi::AssetInfo_CreateEmptyStub() +{ + return HAPI_AssetInfo(); +} + + +void +FHoudiniApi::AssetInfo_InitEmptyStub(HAPI_AssetInfo * in) +{ + return; +} + + +HAPI_AttributeInfo +FHoudiniApi::AttributeInfo_CreateEmptyStub() +{ + return HAPI_AttributeInfo(); +} + + +void +FHoudiniApi::AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CleanupEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ClearConnectionErrorEmptyStub() +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CloseSessionEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_CompositorOptions +FHoudiniApi::CompositorOptions_CreateEmptyStub() +{ + return HAPI_CompositorOptions(); +} + + +void +FHoudiniApi::CompositorOptions_InitEmptyStub(HAPI_CompositorOptions * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Bool +FHoudiniApi::CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right) +{ + return HAPI_Bool(); +} + + +HAPI_CookOptions +FHoudiniApi::CookOptions_CreateEmptyStub() +{ + return HAPI_CookOptions(); +} + + +void +FHoudiniApi::CookOptions_InitEmptyStub(HAPI_CookOptions * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateInProcessSessionEmptyStub(HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_CurveInfo +FHoudiniApi::CurveInfo_CreateEmptyStub() +{ + return HAPI_CurveInfo(); +} + + +void +FHoudiniApi::CurveInfo_InitEmptyStub(HAPI_CurveInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_GeoInfo +FHoudiniApi::GeoInfo_CreateEmptyStub() +{ + return HAPI_GeoInfo(); +} + + +int +FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type) +{ + return -1; +} + + +void +FHoudiniApi::GeoInfo_InitEmptyStub(HAPI_GeoInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCompositorOptionsEmptyStub(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetConnectionErrorLengthEmptyStub(int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetOutputGeoCountEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, int* count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetOutputGeoInfosEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTimeEmptyStub(const HAPI_Session * session, float * time) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_HandleBindingInfo +FHoudiniApi::HandleBindingInfo_CreateEmptyStub() +{ + return HAPI_HandleBindingInfo(); +} + + +void +FHoudiniApi::HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in) +{ + return; +} + + +HAPI_HandleInfo +FHoudiniApi::HandleInfo_CreateEmptyStub() +{ + return HAPI_HandleInfo(); +} + + +void +FHoudiniApi::HandleInfo_InitEmptyStub(HAPI_HandleInfo * in) +{ + return; +} + + +HAPI_ImageFileFormat +FHoudiniApi::ImageFileFormat_CreateEmptyStub() +{ + return HAPI_ImageFileFormat(); +} + + +void +FHoudiniApi::ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in) +{ + return; +} + + +HAPI_ImageInfo +FHoudiniApi::ImageInfo_CreateEmptyStub() +{ + return HAPI_ImageInfo(); +} + + +void +FHoudiniApi::ImageInfo_InitEmptyStub(HAPI_ImageInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::InterruptEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsInitializedEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsSessionValidEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Keyframe +FHoudiniApi::Keyframe_CreateEmptyStub() +{ + return HAPI_Keyframe(); +} + + +void +FHoudiniApi::Keyframe_InitEmptyStub(HAPI_Keyframe * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_MaterialInfo +FHoudiniApi::MaterialInfo_CreateEmptyStub() +{ + return HAPI_MaterialInfo(); +} + + +void +FHoudiniApi::MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_NodeInfo +FHoudiniApi::NodeInfo_CreateEmptyStub() +{ + return HAPI_NodeInfo(); +} + + +void +FHoudiniApi::NodeInfo_InitEmptyStub(HAPI_NodeInfo * in) +{ + return; +} + + +HAPI_ObjectInfo +FHoudiniApi::ObjectInfo_CreateEmptyStub() +{ + return HAPI_ObjectInfo(); +} + + +void +FHoudiniApi::ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in) +{ + return; +} + + +HAPI_ParmChoiceInfo +FHoudiniApi::ParmChoiceInfo_CreateEmptyStub() +{ + return HAPI_ParmChoiceInfo(); +} + + +void +FHoudiniApi::ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_ParmInfo +FHoudiniApi::ParmInfo_CreateEmptyStub() +{ + return HAPI_ParmInfo(); +} + + +int +FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in) +{ + return -1; +} + + +int +FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in) +{ + return -1; +} + + +int +FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in) +{ + return -1; +} + + +void +FHoudiniApi::ParmInfo_InitEmptyStub(HAPI_ParmInfo * in) +{ + return; +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_PartInfo +FHoudiniApi::PartInfo_CreateEmptyStub() +{ + return HAPI_PartInfo(); +} + + +int +FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) +{ + return -1; +} + + +int +FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) +{ + return -1; +} + + +int +FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type) +{ + return -1; +} + + +void +FHoudiniApi::PartInfo_InitEmptyStub(HAPI_PartInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_SessionSyncInfo +FHoudiniApi::SessionSyncInfo_CreateEmptyStub() +{ + return HAPI_SessionSyncInfo(); +} + + +HAPI_Result +FHoudiniApi::SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCompositorOptionsEmptyStub(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTimeEmptyStub(const HAPI_Session * session, float time) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_ThriftServerOptions +FHoudiniApi::ThriftServerOptions_CreateEmptyStub() +{ + return HAPI_ThriftServerOptions(); +} + + +void +FHoudiniApi::ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in) +{ + return; +} + + +HAPI_TimelineOptions +FHoudiniApi::TimelineOptions_CreateEmptyStub() +{ + return HAPI_TimelineOptions(); +} + + +void +FHoudiniApi::TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in) +{ + return; +} + + +HAPI_TransformEuler +FHoudiniApi::TransformEuler_CreateEmptyStub() +{ + return HAPI_TransformEuler(); +} + + +void +FHoudiniApi::TransformEuler_InitEmptyStub(HAPI_TransformEuler * in) +{ + return; +} + + +HAPI_Transform +FHoudiniApi::Transform_CreateEmptyStub() +{ + return HAPI_Transform(); +} + + +void +FHoudiniApi::Transform_InitEmptyStub(HAPI_Transform * in) +{ + return; +} + + +HAPI_Viewport +FHoudiniApi::Viewport_CreateEmptyStub() +{ + return HAPI_Viewport(); +} + + +HAPI_VolumeInfo +FHoudiniApi::VolumeInfo_CreateEmptyStub() +{ + return HAPI_VolumeInfo(); +} + + +void +FHoudiniApi::VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in) +{ + return; +} + + +HAPI_VolumeTileInfo +FHoudiniApi::VolumeTileInfo_CreateEmptyStub() +{ + return HAPI_VolumeTileInfo(); +} + + +void +FHoudiniApi::VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in) +{ + return; +} + + diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.cpp b/Source/HoudiniEngine/Private/HoudiniEngine.cpp index c0ee69d3e..7d949ea14 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngine.cpp @@ -396,6 +396,38 @@ FHoudiniEngine::GetLibHAPILocation() const return LibHAPILocation; } +const FString +FHoudiniEngine::GetHoudiniExecutable() +{ + FString HoudiniExecutable = TEXT("houdini"); + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + switch (HoudiniRuntimeSettings->HoudiniExecutable) + { + case EHoudiniExecutableType::HRSHE_HoudiniFX: + HoudiniExecutable = TEXT("houdinifx"); + break; + + case EHoudiniExecutableType::HRSHE_HoudiniCore: + HoudiniExecutable = TEXT("houdinicore"); + break; + + case EHoudiniExecutableType::HRSHE_HoudiniIndie: + HoudiniExecutable = TEXT("hindie"); + break; + + default: + case EHoudiniExecutableType::HRSHE_Houdini: + HoudiniExecutable = TEXT("houdini"); + break; + + } + } + + return HoudiniExecutable; +} + const HAPI_Session * FHoudiniEngine::GetSession() const { diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.h b/Source/HoudiniEngine/Private/HoudiniEngine.h index a921b1893..264aa78a7 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.h +++ b/Source/HoudiniEngine/Private/HoudiniEngine.h @@ -74,7 +74,10 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface static bool IsInitialized(); // Return the location of the currently loaded LibHAPI - virtual const FString & GetLibHAPILocation() const; + virtual const FString& GetLibHAPILocation() const; + + // Return the houdini executable to use + static const FString GetHoudiniExecutable(); // Session accessor virtual const HAPI_Session* GetSession() const; diff --git a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h index a3121280c..413d9a299 100644 --- a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h +++ b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h @@ -1,411 +1,410 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -// Indicate we're in the HoudiniEngine module -#define HOUDINI_ENGINE -#include "HoudiniEngineRuntimePrivatePCH.h" - -// HFS path definition coming from UBT/build.cs files. -#ifndef HOUDINI_ENGINE_HFS_PATH_DEFINE - #define HOUDINI_ENGINE_HFS_PATH "" -#else - #define HOUDINI_ENGINE_STRINGIFY_HELPER(X) #X - #define HOUDINI_ENGINE_STRINGIFY(X) HOUDINI_ENGINE_STRINGIFY_HELPER(X) - #define HOUDINI_ENGINE_HFS_PATH HOUDINI_ENGINE_STRINGIFY(HOUDINI_ENGINE_HFS_PATH_DEFINE) -#endif - -// HFS subfolder containing HAPI lib. -#define HAPI_HFS_SUBFOLDER_WINDOWS TEXT( "bin" ) -#define HAPI_HFS_SUBFOLDER_MAC TEXT( "dsolib" ) -#define HAPI_HFS_SUBFOLDER_LINUX TEXT( "dsolib" ) - -// Unreal HAPI Resources. -#define HAPI_UNREAL_RESOURCE_HOUDINI_LOGO TEXT( "/HoudiniEngine/houdini_logo.houdini_logo" ) -#define HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL TEXT( "/HoudiniEngine/houdini_default_material.houdini_default_material" ) -#define HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL TEXT( "/HoudiniEngine/houdini_templated_material.houdini_templated_material") -#define HAPI_UNREAL_RESOURCE_BGEO_IMPORT TEXT( "/HoudiniEngine/houdini_bgeo_import.houdini_bgeo_import" ) - -#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH TEXT("/HoudiniEngine/default_reference_static_mesh.default_reference_static_mesh") -#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL TEXT("/HoudiniEngine/default_reference_static_mesh_material.default_reference_static_mesh_material") - -// Client name so HAPI knows we're running inside unreal -#define HAPI_UNREAL_CLIENT_NAME "unreal" - -// Error checking - this macro will check the status and return specified parameter. -#define HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HAPI_LOG_ROUTINE ) \ - do \ - { \ - HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ - if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - return HAPI_PARAM_RETURN; \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR_RETURN( HAPI_PARAM_CALL, HAPI_PARAM_RETURN ) \ - HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HOUDINI_LOG_ERROR ) - -// Simple Error checking - this macro will check the status. -#define HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ - do \ - { \ - HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ - if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR( HAPI_PARAM_CALL ) \ - HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) - -// Error checking - this macro will check the status and returns it. -#define HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ - do \ - { \ - *HAPI_PARAM_RESULT = HAPI_PARAM_CALL; \ - if ( *HAPI_PARAM_RESULT != HAPI_RESULT_SUCCESS ) \ - { \ - HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ - } \ - } \ - while ( 0 ) - -#define HOUDINI_CHECK_ERROR_GET( HAPI_PARAM_RESULT, HAPI_PARAM_CALL ) \ - HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) - -// For Transform conversion between UE4 / Houdini -// Set to 0 to stop converting Houdini's coordinate space to unreal's -#define HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM 1 - -#define HAPI_UNREAL_SCALE_FACTOR_POSITION 100.0f -#define HAPI_UNREAL_SCALE_FACTOR_TRANSLATION 100.0f -#define HAPI_UNREAL_SCALE_FACTOR_SCALE 100.0f - -#define HAPI_UNREAL_SCALE_SMALL_VALUE KINDA_SMALL_NUMBER * 2.0f - -#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) - -// Attributes -#define HAPI_UNREAL_ATTRIB_POSITION HAPI_ATTRIB_POSITION -#define HAPI_UNREAL_ATTRIB_ROTATION "rot" -#define HAPI_UNREAL_ATTRIB_SCALE "scale" -#define HAPI_UNREAL_ATTRIB_UNIFORM_SCALE "pscale" -#define HAPI_UNREAL_ATTRIB_COLOR HAPI_ATTRIB_COLOR -#define HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR "unreal_lightmap_color" -#define HAPI_UNREAL_ATTRIB_ALPHA "Alpha" -#define HAPI_UNREAL_ATTRIB_UV HAPI_ATTRIB_UV -#define HAPI_UNREAL_ATTRIB_NORMAL HAPI_ATTRIB_NORMAL -#define HAPI_UNREAL_ATTRIB_TANGENTU HAPI_ATTRIB_TANGENT -#define HAPI_UNREAL_ATTRIB_TANGENTV HAPI_ATTRIB_TANGENT2 - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE "tile" -// Always the name of the main landscape actor. -// Names for landscape tile actors will be taken from 'unreal_output_name'. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME "unreal_landscape_shared_actor_name" -// This tile_actor_type succeeds the 'unreal_landscape_streaming_proxy' (v1) attribute. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE "unreal_landscape_tile_actor_type" -// This attribute is for backwards compatibility only. -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN "unreal_landscape_layer_min" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX "unreal_landscape_layer_max" - -// Path to the level in which an actor should be generated or which contained the input data -// "." - (Default) Generate geometry in the the current persistent world -// "Junk/Background" - Path to a Map that is relative to the current persistent world's Map. -// "/Game/Maps/Level01/Junk/Background" - Absolute path to the map in which the primitive should be output -#define HAPI_UNREAL_ATTRIB_LEVEL_PATH "unreal_level_path" - -// Path to the actor that contained the input data/should be generated -#define HAPI_UNREAL_ATTRIB_ACTOR_PATH "unreal_actor_path" - -// Path to the object plugged in a geo in -#define HAPI_UNREAL_ATTRIB_OBJECT_PATH "unreal_object_path" - -// Attributes used for data exchange between UE4 and Houdini -#define HAPI_UNREAL_ATTRIB_MATERIAL "unreal_material" -#define HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK "unreal_face_material" -#define HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE "unreal_material_instance" -#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE "unreal_material_hole" -#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE "unreal_material_hole_instance" -#define HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL "unreal_physical_material" -#define HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK "unreal_face_smoothing_mask" -#define HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION "unreal_lightmap_resolution" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE "lod_screensize" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX "lod" -#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX "_screensize" -#define HAPI_UNREAL_ATTRIB_TAG_PREFIX "unreal_tag_" - -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX "mesh_socket" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME "mesh_socket_name" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD "unreal_mesh_socket_name" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR "mesh_socket_actor" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD "unreal_mesh_socket_actor" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG "mesh_socket_tag" -#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD "unreal_mesh_socket_tag" - -#define HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME "unreal_input_mesh_name" -#define HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE "unreal_input_source_file" - -#define HAPI_UNREAL_ATTRIB_INSTANCE "instance" -#define HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE "unreal_instance" -#define HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES "unreal_split_instances" -#define HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER "unreal_foliage" -#define HAPI_UNREAL_ATTRIB_INSTANCE_ROTATION "rot" -#define HAPI_UNREAL_ATTRIB_INSTANCE_SCALE "scale" -#define HAPI_UNREAL_ATTRIB_INSTANCE_POSITION HAPI_ATTRIB_POSITION -#define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" -#define HAPI_UNREAL_ATTRIB_SPLIT_ATTR "unreal_split_attr" -#define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" -#define HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" -#define HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX "unreal_per_instance_custom_data" - - -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX "unreal_vertex_index" -#define HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER "unreal_unit_landscape_layer" -#define HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS "unreal_landscape_layer_nonweightblended" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO "unreal_landscape_layer_info" -// Landscape output mode: -// 0 - Default (Temp) mode -// 1 - Output heightfield to existing landscape editable layer -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_OUTPUT_MODE "unreal_landscape_output_mode" -#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_DEFAULT 0 -#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_EDITABLE_LAYER 1 - -// Edit layer -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME "unreal_landscape_editlayer_name" -// Clear the editlayer before blitting new data -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR "unreal_landscape_editlayer_clear" -// Landscape that is being targeted by "edit layer" outputs -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET "unreal_landscape_editlayer_target" -// Place the output layer "after" the given layer -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER "unreal_landscape_editlayer_after" - -#define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" -#define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" - -#define HAPI_UNREAL_ATTRIB_BAKE_FOLDER "unreal_bake_folder" -#define HAPI_UNREAL_ATTRIB_TEMP_FOLDER "unreal_temp_folder" -#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1 "unreal_generated_mesh_name" -#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2 "unreal_output_name" -#define HAPI_UNREAL_ATTRIB_BAKE_ACTOR "unreal_bake_actor" -#define HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER "unreal_bake_outliner_folder" - -// data tables -#define HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX "unreal_data_table_" -#define HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT "unreal_datatable_rowstruct" - -// Attributes for Curve Outputs -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE "unreal_output_curve" -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR "unreal_output_curve_linear" -#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED "unreal_output_curve_closed" -// We only support Unreal spline outputs for now -//#define HAPI_UNREAL_ATTRIB_OUTPUT_HOUDINI_CURVE "houdini_output_curve" - -// Geometry Node -#define HAPI_UNREAL_PARAM_TRANSLATE "t" -#define HAPI_UNREAL_PARAM_ROTATE "r" -#define HAPI_UNREAL_PARAM_SCALE "s" -#define HAPI_UNREAL_PARAM_PIVOT "p" -#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" - -// Houdini Curve -#define HAPI_UNREAL_PARAM_CURVE_TYPE "type" -#define HAPI_UNREAL_PARAM_CURVE_METHOD "method" -#define HAPI_UNREAL_PARAM_CURVE_COORDS "coords" -#define HAPI_UNREAL_PARAM_CURVE_CLOSED "close" -#define HAPI_UNREAL_PARAM_CURVE_REVERSED "reverse" - -#define HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT "0.0, 0.0, 3.0 3.0, 0.0, 3.0" - -// String Params tags -#define HOUDINI_PARAMETER_STRING_REF_TAG TEXT("unreal_ref") -#define HOUDINI_PARAMETER_STRING_REF_CLASS_TAG TEXT("unreal_ref_class") -#define HOUDINI_PARAMETER_STRING_MULTILINE_TAG TEXT("editor") - -// Parameter tags -#define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" -#define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" -#define HAPI_PARAM_TAG_UNITS "units" - -// TODO: unused, remove! -#define HAPI_PARAM_TAG_ASSET_REF "asset_ref" - -// Groups -#define HAPI_UNREAL_GROUP_LOD_PREFIX TEXT("lod") -#define HAPI_UNREAL_GROUP_SOCKET_PREFIX TEXT("mesh_socket") -#define HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD TEXT("socket") -//#define HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX TEXT("unreal_split") - -#define HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION TEXT("main_geo") - -#define HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX TEXT("collision_geo") -#define HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX TEXT("rendered_collision_geo") - -#define HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX TEXT("collision_geo_ucx") -#define HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX TEXT("rendered_collision_geo_ucx") - -#define HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX TEXT("collision_geo_simple") -#define HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX TEXT("rendered_collision_geo_simple") - -// Default material name. -#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) - -// Various variable names used to store meta information in generated packages. -#define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE TEXT( "HoudiniGeneratedTextureType" ) -#define HAPI_UNREAL_PACKAGE_META_NODE_PATH TEXT( "HoudiniNodePath" ) -#define HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER TEXT( "HoudiniPackageBakeCounter" ) -#define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) - -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL TEXT( "N" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE TEXT( "C_A" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR TEXT( "S" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS TEXT( "R" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC TEXT( "M" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE TEXT( "E" ) -#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK TEXT( "O" ) - -// Texture planes. -#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA "C A" -#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR "C" -#define HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA "A" -#define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" - -// Materials Diffuse. -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL "ogl_diff" -#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE "basecolor" - -#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" - -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL "ogl_tex1" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED "ogl_use_tex1" - -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE "basecolor_texture" -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED "basecolor_useTexture" - -#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" - -// Materials Normal. -#define HAPI_UNREAL_PARAM_MAP_NORMAL_OGL "ogl_normalmap" - -//#define HAPI_UNREAL_PARAM_MAP_NORMAL "normalTexture" -//#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "normalUseTexture" -#define HAPI_UNREAL_PARAM_MAP_NORMAL "baseNormal_texture" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "baseBumpAndNormal_enable" - -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" -#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" - -#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" - -// Materials Specular. -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL "ogl_spec" -#define HAPI_UNREAL_PARAM_COLOR_SPECULAR "reflect" - -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL "ogl_specmap" -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED "ogl_use_specmap" - -#define HAPI_UNREAL_PARAM_MAP_SPECULAR "reflect_texture" -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED "reflect_useTexture" - -#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" - -// Materials Roughness. -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL "ogl_rough" -#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS "rough" - -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL "ogl_roughmap" -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED "ogl_use_roughmap" - -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS "rough_texture" -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED "rough_useTexture" - -#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" - -// Materials Metallic. -#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" -#define HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL "ogl_metallic" - -#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL "ogl_metallicmap" -#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED "ogl_use_metallicmap" - -#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" -#define HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED "metallic_useTexture" - -#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" - -// Materials Emissive. -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL "ogl_emit" -#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE "emitcolor" - -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL "ogl_emissionmap" -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED "ogl_use_emissionmap" - -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED "emitcolor_useTexture" - -#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" - -// Materials Opacity. -#define HAPI_UNREAL_PARAM_ALPHA_OGL "ogl_alpha" -#define HAPI_UNREAL_PARAM_ALPHA "opac" - -#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL "ogl_opacitymap" -#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED "ogl_use_opacitymap" - -#define HAPI_UNREAL_PARAM_MAP_OPACITY "opaccolor_texture" -#define HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED "opaccolor_useTexture" - -// Number of GUID characters to keep for packages -#define PACKAGE_GUID_LENGTH 8 -#define PACKAGE_GUID_COMPONENT_LENGTH 12 - -/** Ramp related defines. **/ -#define HAPI_UNREAL_RAMP_FLOAT_AXIS_X "position" -#define HAPI_UNREAL_RAMP_FLOAT_AXIS_Y "value" -#define HAPI_UNREAL_RAMP_COLOR_AXIS_X "position" -#define HAPI_UNREAL_RAMP_COLOR_AXIS_Y "color" - -/** Handle types. **/ -#define HAPI_UNREAL_HANDLE_TRANSFORM "xform" -#define HAPI_UNREAL_HANDLE_BOUNDER "bound" - -#define HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME "height" - -#define HAPI_UNREAL_NOTIFICATION_FADEOUT 2.0f -#define HAPI_UNREAL_NOTIFICATION_EXPIRE 2.0f - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +// Indicate we're in the HoudiniEngine module +#define HOUDINI_ENGINE +#include "HoudiniEngineRuntimePrivatePCH.h" + +// HFS path definition coming from UBT/build.cs files. +#ifndef HOUDINI_ENGINE_HFS_PATH_DEFINE + #define HOUDINI_ENGINE_HFS_PATH "" +#else + #define HOUDINI_ENGINE_STRINGIFY_HELPER(X) #X + #define HOUDINI_ENGINE_STRINGIFY(X) HOUDINI_ENGINE_STRINGIFY_HELPER(X) + #define HOUDINI_ENGINE_HFS_PATH HOUDINI_ENGINE_STRINGIFY(HOUDINI_ENGINE_HFS_PATH_DEFINE) +#endif + +// HFS subfolder containing HAPI lib. +#define HAPI_HFS_SUBFOLDER_WINDOWS TEXT( "bin" ) +#define HAPI_HFS_SUBFOLDER_MAC TEXT( "dsolib" ) +#define HAPI_HFS_SUBFOLDER_LINUX TEXT( "dsolib" ) + +// Unreal HAPI Resources. +#define HAPI_UNREAL_RESOURCE_HOUDINI_LOGO TEXT( "/HoudiniEngine/houdini_logo.houdini_logo" ) +#define HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL TEXT( "/HoudiniEngine/houdini_default_material.houdini_default_material" ) +#define HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL TEXT( "/HoudiniEngine/houdini_templated_material.houdini_templated_material") +#define HAPI_UNREAL_RESOURCE_BGEO_IMPORT TEXT( "/HoudiniEngine/houdini_bgeo_import.houdini_bgeo_import" ) + +#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH TEXT("/HoudiniEngine/default_reference_static_mesh.default_reference_static_mesh") +#define HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL TEXT("/HoudiniEngine/default_reference_static_mesh_material.default_reference_static_mesh_material") + +// Client name so HAPI knows we're running inside unreal +#define HAPI_UNREAL_CLIENT_NAME "unreal" + +// Error checking - this macro will check the status and return specified parameter. +#define HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HAPI_LOG_ROUTINE ) \ + do \ + { \ + HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ + if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + return HAPI_PARAM_RETURN; \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR_RETURN( HAPI_PARAM_CALL, HAPI_PARAM_RETURN ) \ + HOUDINI_CHECK_ERROR_RETURN_HELPER( HAPI_PARAM_CALL, HAPI_PARAM_RETURN, HOUDINI_LOG_ERROR ) + +// Simple Error checking - this macro will check the status. +#define HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ + do \ + { \ + HAPI_Result ResultVariable = HAPI_PARAM_CALL; \ + if ( ResultVariable != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR( HAPI_PARAM_CALL ) \ + HOUDINI_CHECK_ERROR_HELPER( HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) + +// Error checking - this macro will check the status and returns it. +#define HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HAPI_LOG_ROUTINE ) \ + do \ + { \ + *HAPI_PARAM_RESULT = HAPI_PARAM_CALL; \ + if ( *HAPI_PARAM_RESULT != HAPI_RESULT_SUCCESS ) \ + { \ + HAPI_LOG_ROUTINE( TEXT( "Hapi failed: %s" ), *FHoudiniEngineUtils::GetErrorDescription() ); \ + } \ + } \ + while ( 0 ) + +#define HOUDINI_CHECK_ERROR_GET( HAPI_PARAM_RESULT, HAPI_PARAM_CALL ) \ + HOUDINI_CHECK_ERROR_GET_HELPER( HAPI_PARAM_RESULT, HAPI_PARAM_CALL, HOUDINI_LOG_ERROR ) + +// For Transform conversion between UE4 / Houdini +// Set to 0 to stop converting Houdini's coordinate space to unreal's +#define HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM 1 + +#define HAPI_UNREAL_SCALE_FACTOR_POSITION 100.0f +#define HAPI_UNREAL_SCALE_FACTOR_TRANSLATION 100.0f +#define HAPI_UNREAL_SCALE_FACTOR_SCALE 100.0f + +#define HAPI_UNREAL_SCALE_SMALL_VALUE KINDA_SMALL_NUMBER * 2.0f + +#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) + +// Attributes +#define HAPI_UNREAL_ATTRIB_POSITION HAPI_ATTRIB_POSITION +#define HAPI_UNREAL_ATTRIB_ROTATION "rot" +#define HAPI_UNREAL_ATTRIB_SCALE "scale" +#define HAPI_UNREAL_ATTRIB_UNIFORM_SCALE "pscale" +#define HAPI_UNREAL_ATTRIB_COLOR HAPI_ATTRIB_COLOR +#define HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR "unreal_lightmap_color" +#define HAPI_UNREAL_ATTRIB_ALPHA "Alpha" +#define HAPI_UNREAL_ATTRIB_UV HAPI_ATTRIB_UV +#define HAPI_UNREAL_ATTRIB_NORMAL HAPI_ATTRIB_NORMAL +#define HAPI_UNREAL_ATTRIB_TANGENTU HAPI_ATTRIB_TANGENT +#define HAPI_UNREAL_ATTRIB_TANGENTV HAPI_ATTRIB_TANGENT2 + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE "tile" +// Always the name of the main landscape actor. +// Names for landscape tile actors will be taken from 'unreal_output_name'. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME "unreal_landscape_shared_actor_name" +// This tile_actor_type succeeds the 'unreal_landscape_streaming_proxy' (v1) attribute. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE "unreal_landscape_tile_actor_type" +// This attribute is for backwards compatibility only. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN "unreal_landscape_layer_min" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX "unreal_landscape_layer_max" + +// Path to the level in which an actor should be generated or which contained the input data +// "." - (Default) Generate geometry in the the current persistent world +// "Junk/Background" - Path to a Map that is relative to the current persistent world's Map. +// "/Game/Maps/Level01/Junk/Background" - Absolute path to the map in which the primitive should be output +#define HAPI_UNREAL_ATTRIB_LEVEL_PATH "unreal_level_path" + +// Path to the actor that contained the input data/should be generated +#define HAPI_UNREAL_ATTRIB_ACTOR_PATH "unreal_actor_path" + +// Path to the object plugged in a geo in +#define HAPI_UNREAL_ATTRIB_OBJECT_PATH "unreal_object_path" + +// Attributes used for data exchange between UE4 and Houdini +#define HAPI_UNREAL_ATTRIB_MATERIAL "unreal_material" +#define HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK "unreal_face_material" +#define HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE "unreal_material_instance" +#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE "unreal_material_hole" +#define HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE "unreal_material_hole_instance" +#define HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL "unreal_physical_material" +#define HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK "unreal_face_smoothing_mask" +#define HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION "unreal_lightmap_resolution" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE "lod_screensize" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX "lod" +#define HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX "_screensize" +#define HAPI_UNREAL_ATTRIB_TAG_PREFIX "unreal_tag_" + +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX "mesh_socket" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME "mesh_socket_name" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD "unreal_mesh_socket_name" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR "mesh_socket_actor" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD "unreal_mesh_socket_actor" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG "mesh_socket_tag" +#define HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD "unreal_mesh_socket_tag" + +#define HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME "unreal_input_mesh_name" +#define HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE "unreal_input_source_file" + +#define HAPI_UNREAL_ATTRIB_INSTANCE "instance" +#define HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE "unreal_instance" +#define HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES "unreal_split_instances" +#define HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER "unreal_foliage" +#define HAPI_UNREAL_ATTRIB_INSTANCE_ROTATION "rot" +#define HAPI_UNREAL_ATTRIB_INSTANCE_SCALE "scale" +#define HAPI_UNREAL_ATTRIB_INSTANCE_POSITION HAPI_ATTRIB_POSITION +#define HAPI_UNREAL_ATTRIB_INSTANCE_COLOR "unreal_instance_color" +#define HAPI_UNREAL_ATTRIB_SPLIT_ATTR "unreal_split_attr" +#define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" +#define HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" +#define HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX "unreal_per_instance_custom_data" + + +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX "unreal_vertex_index" +#define HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER "unreal_unit_landscape_layer" +#define HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS "unreal_landscape_layer_nonweightblended" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO "unreal_landscape_layer_info" +// Landscape output mode: +// 0 - Default (Temp) mode +// 1 - Output heightfield to existing landscape editable layer +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_OUTPUT_MODE "unreal_landscape_output_mode" +#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_DEFAULT 0 +#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_EDITABLE_LAYER 1 + +// Edit layer +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME "unreal_landscape_editlayer_name" +// Clear the editlayer before blitting new data +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR "unreal_landscape_editlayer_clear" +// Landscape that is being targeted by "edit layer" outputs +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET "unreal_landscape_editlayer_target" +// Place the output layer "after" the given layer +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER "unreal_landscape_editlayer_after" + +#define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" +#define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" + +#define HAPI_UNREAL_ATTRIB_BAKE_FOLDER "unreal_bake_folder" +#define HAPI_UNREAL_ATTRIB_TEMP_FOLDER "unreal_temp_folder" +#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1 "unreal_generated_mesh_name" +#define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2 "unreal_output_name" +#define HAPI_UNREAL_ATTRIB_BAKE_ACTOR "unreal_bake_actor" +#define HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER "unreal_bake_outliner_folder" + +// data tables +#define HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX "unreal_data_table_" +#define HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT "unreal_datatable_rowstruct" + +// Attributes for Curve Outputs +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE "unreal_output_curve" +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR "unreal_output_curve_linear" +#define HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED "unreal_output_curve_closed" +// We only support Unreal spline outputs for now +//#define HAPI_UNREAL_ATTRIB_OUTPUT_HOUDINI_CURVE "houdini_output_curve" + +// Geometry Node +#define HAPI_UNREAL_PARAM_TRANSLATE "t" +#define HAPI_UNREAL_PARAM_ROTATE "r" +#define HAPI_UNREAL_PARAM_SCALE "s" +#define HAPI_UNREAL_PARAM_PIVOT "p" +#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" + +// Houdini Curve +#define HAPI_UNREAL_PARAM_CURVE_TYPE "type" +#define HAPI_UNREAL_PARAM_CURVE_METHOD "method" +#define HAPI_UNREAL_PARAM_CURVE_COORDS "coords" +#define HAPI_UNREAL_PARAM_CURVE_CLOSED "close" +#define HAPI_UNREAL_PARAM_CURVE_REVERSED "reverse" + +#define HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT "0.0, 0.0, 3.0 3.0, 0.0, 3.0" + +// String Params tags +#define HOUDINI_PARAMETER_STRING_REF_TAG TEXT("unreal_ref") +#define HOUDINI_PARAMETER_STRING_REF_CLASS_TAG TEXT("unreal_ref_class") +#define HOUDINI_PARAMETER_STRING_MULTILINE_TAG TEXT("editor") + +// Parameter tags +#define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" +#define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" +#define HAPI_PARAM_TAG_UNITS "units" + +// TODO: unused, remove! +#define HAPI_PARAM_TAG_ASSET_REF "asset_ref" + +// Groups +#define HAPI_UNREAL_GROUP_LOD_PREFIX TEXT("lod") +#define HAPI_UNREAL_GROUP_SOCKET_PREFIX TEXT("mesh_socket") +#define HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD TEXT("socket") +//#define HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX TEXT("unreal_split") + +#define HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION TEXT("main_geo") + +#define HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX TEXT("collision_geo") +#define HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX TEXT("rendered_collision_geo") + +#define HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX TEXT("collision_geo_ucx") +#define HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX TEXT("rendered_collision_geo_ucx") + +#define HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX TEXT("collision_geo_simple") +#define HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX TEXT("rendered_collision_geo_simple") + +// Default material name. +#define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) + +// Various variable names used to store meta information in generated packages. +#define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE TEXT( "HoudiniGeneratedTextureType" ) +#define HAPI_UNREAL_PACKAGE_META_NODE_PATH TEXT( "HoudiniNodePath" ) +#define HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER TEXT( "HoudiniPackageBakeCounter" ) + +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL TEXT( "N" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE TEXT( "C_A" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR TEXT( "S" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS TEXT( "R" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC TEXT( "M" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE TEXT( "E" ) +#define HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK TEXT( "O" ) + +// Texture planes. +#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA "C A" +#define HAPI_UNREAL_MATERIAL_TEXTURE_COLOR "C" +#define HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA "A" +#define HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL "N" + +// Materials Diffuse. +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL "ogl_diff" +#define HAPI_UNREAL_PARAM_COLOR_DIFFUSE "basecolor" + +#define HAPI_UNREAL_PARAM_TEXTURE_LAYERS_NUM "ogl_numtex" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL "ogl_tex1" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED "ogl_use_tex1" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE "basecolor_texture" +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED "basecolor_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_DIFFUSE_COLOR_SPACE "basecolor_textureColorSpace" + +// Materials Normal. +#define HAPI_UNREAL_PARAM_MAP_NORMAL_OGL "ogl_normalmap" + +//#define HAPI_UNREAL_PARAM_MAP_NORMAL "normalTexture" +//#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "normalUseTexture" +#define HAPI_UNREAL_PARAM_MAP_NORMAL "baseNormal_texture" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED "baseBumpAndNormal_enable" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE "ogl_normalmap_type" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT "Tangent Space" +#define HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD "World Space" + +#define HAPI_UNREAL_PARAM_MAP_NORMAL_COLOR_SPACE "normalTexColorSpace" + +// Materials Specular. +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL "ogl_spec" +#define HAPI_UNREAL_PARAM_COLOR_SPECULAR "reflect" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL "ogl_specmap" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED "ogl_use_specmap" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR "reflect_texture" +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED "reflect_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_SPECULAR_COLOR_SPACE "reflect_textureColorSpace" + +// Materials Roughness. +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL "ogl_rough" +#define HAPI_UNREAL_PARAM_VALUE_ROUGHNESS "rough" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL "ogl_roughmap" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED "ogl_use_roughmap" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS "rough_texture" +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED "rough_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_ROUGHNESS_COLOR_SPACE "rough_textureColorSpace" + +// Materials Metallic. +#define HAPI_UNREAL_PARAM_VALUE_METALLIC "metallic" +#define HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL "ogl_metallic" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL "ogl_metallicmap" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED "ogl_use_metallicmap" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC "metallic_texture" +#define HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED "metallic_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_METALLIC_COLOR_SPACE "metallic_textureColorSpace" + +// Materials Emissive. +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL "ogl_emit" +#define HAPI_UNREAL_PARAM_VALUE_EMISSIVE "emitcolor" + +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL "ogl_emissionmap" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED "ogl_use_emissionmap" + +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE "emitcolor_texture" +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED "emitcolor_useTexture" + +#define HAPI_UNREAL_PARAM_MAP_EMISSIVE_COLOR_SPACE "emitcolor_textureColorSpace" + +// Materials Opacity. +#define HAPI_UNREAL_PARAM_ALPHA_OGL "ogl_alpha" +#define HAPI_UNREAL_PARAM_ALPHA "opac" + +#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL "ogl_opacitymap" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED "ogl_use_opacitymap" + +#define HAPI_UNREAL_PARAM_MAP_OPACITY "opaccolor_texture" +#define HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED "opaccolor_useTexture" + +// Number of GUID characters to keep for packages +#define PACKAGE_GUID_LENGTH 8 +#define PACKAGE_GUID_COMPONENT_LENGTH 12 + +/** Ramp related defines. **/ +#define HAPI_UNREAL_RAMP_FLOAT_AXIS_X "position" +#define HAPI_UNREAL_RAMP_FLOAT_AXIS_Y "value" +#define HAPI_UNREAL_RAMP_COLOR_AXIS_X "position" +#define HAPI_UNREAL_RAMP_COLOR_AXIS_Y "color" + +/** Handle types. **/ +#define HAPI_UNREAL_HANDLE_TRANSFORM "xform" +#define HAPI_UNREAL_HANDLE_BOUNDER "bound" + +#define HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME "height" + +#define HAPI_UNREAL_NOTIFICATION_FADEOUT 2.0f +#define HAPI_UNREAL_NOTIFICATION_EXPIRE 2.0f + diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp index 112105740..b22f1f07d 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp @@ -411,6 +411,9 @@ FHoudiniEngineUtils::GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent) if (!FHoudiniEngineString::ToFString(AssetInfo.helpTextSH, HelpString)) return HelpString; + if (!FHoudiniEngineString::ToFString(AssetInfo.helpURLSH, HelpString)) + return HelpString; + if (HelpString.IsEmpty()) HelpString = TEXT("No Asset Help Found"); diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h index d5933bc20..64428e2f4 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h @@ -35,9 +35,6 @@ #include "HoudiniPackageParams.h" #include "Containers/UnrealString.h" -#include "SSCSEditor.h" - - class FString; class UStaticMesh; class UHoudiniAsset; diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp index a68b6ec68..d588b3e6a 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp @@ -161,6 +161,7 @@ UHoudiniGeoImporter::BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) { + TMap AllOutputMaterials; for (auto& CurOutput : InOutputs) { if (CurOutput->GetType() != EHoudiniOutputType::Mesh) @@ -215,10 +216,19 @@ UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObj NewOutputObjects, AssignementMaterials, ReplacementMaterials, + AllOutputMaterials, true, EHoudiniStaticMeshMethod::RawMesh, SMGP, MBS); + + for (auto& CurMat : AssignementMaterials) + { + // Adds the newly generated materials to the output materials array + // This is to avoid recreating those same materials again + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } } // Add all output objects and materials diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp index 56f15cdfd..ef86a0c7c 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp @@ -76,6 +76,7 @@ FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( InAllOutputs, OutInstancedOutputPartData.OriginalInstancedObjects, OutInstancedOutputPartData.OriginalInstancedTransforms, + OutInstancedOutputPartData.OriginalInstancedIndices, OutInstancedOutputPartData.SplitAttributeName, OutInstancedOutputPartData.SplitAttributeValues, OutInstancedOutputPartData.PerSplitAttributes)) @@ -250,6 +251,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( OutputIdentifier, InstancedOutputPartData.OriginalInstancedObjects, InstancedOutputPartData.OriginalInstancedTransforms, + InstancedOutputPartData.OriginalInstancedIndices, InOutput->GetInstancedOutputs(), VariationInstancedObjects, VariationInstancedTransforms, @@ -276,19 +278,22 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (InstancedObjectTransforms.Num() <= 0) continue; + // Get the original Index of that variations + int32 VariationOriginalIndex = VariationOriginalObjectIndices[InstanceObjectIdx]; + // Find the matching instance output now FHoudiniInstancedOutput* FoundInstancedOutput = nullptr; { // Instanced output only use the original object index for their split identifier FHoudiniOutputObjectIdentifier InstancedOutputIdentifier = OutputIdentifier; - InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]); + InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalIndex); FoundInstancedOutput = InstancedOutputs.Find(InstancedOutputIdentifier); } // Update the split identifier for this object // We use both the original object index and the variation index: ORIG_VAR OutputIdentifier.SplitIdentifier = - FString::FromInt(VariationOriginalObjectIndices[InstanceObjectIdx]) + FString::FromInt(VariationOriginalIndex) + TEXT("_") + FString::FromInt(VariationIndices[InstanceObjectIdx]); @@ -311,7 +316,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( // Extract the material for this variation TArray VariationMaterials; - if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) + if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex], InstancerMaterials, VariationMaterials)) VariationMaterials.Empty(); USceneComponent* NewInstancerComponent = nullptr; @@ -326,6 +331,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( InstancedOutputPartData.bSplitMeshInstancer, InstancedOutputPartData.bIsFoliageInstancer, VariationMaterials, + InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex], InstanceObjectIdx, InstancedOutputPartData.bForceHISM)) { @@ -337,10 +343,11 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( continue; // Copy the per-instance custom data if we have any - UpdateChangedPerInstanceCustomData( - InstancedOutputPartData.NumCustomFloats, - InstancedOutputPartData.PerInstanceCustomData, - NewInstancerComponent); + if (InstancedOutputPartData.PerInstanceCustomData.Num() > 0) + { + UpdateChangedPerInstanceCustomData( + InstancedOutputPartData.PerInstanceCustomData[VariationOriginalIndex], NewInstancerComponent); + } // If the instanced object (by ref) wasn't found, hide the component if(InstancedObject == DefaultReferenceSM) @@ -365,35 +372,37 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( NewOutputObject.CachedAttributes.Empty(); NewOutputObject.CachedTokens.Empty(); - // Todo: get the proper attribute value per variation... - // Cache the level path, output name and tile attributes on the output object - // So they can be reused for baking - if(InstancedOutputPartData.AllLevelPaths.Num() > 0 && !InstancedOutputPartData.AllLevelPaths[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[0]); + // Cache the level path, output name and tile attributes on the output object So they can be reused for baking + int32 FirstOriginalInstanceIndex = 0; + if(InstancedOutputPartData.OriginalInstancedIndices.IsValidIndex(VariationOriginalIndex) && InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex].Num() > 0) + FirstOriginalInstanceIndex = InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex][0]; + + if(InstancedOutputPartData.AllLevelPaths.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllLevelPaths[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[FirstOriginalInstanceIndex]); - if(InstancedOutputPartData.OutputNames.Num() > 0 && !InstancedOutputPartData.OutputNames[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[0]); + if(InstancedOutputPartData.OutputNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.OutputNames[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[FirstOriginalInstanceIndex]); - if(InstancedOutputPartData.TileValues.Num() > 0 && InstancedOutputPartData.TileValues[0] >= 0) + // TODO: Check! maybe accessed with just VariationOriginalIndex + if(InstancedOutputPartData.TileValues.IsValidIndex(FirstOriginalInstanceIndex) && InstancedOutputPartData.TileValues[FirstOriginalInstanceIndex] >= 0) { // cache the tile attribute as a token on the output object - NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[0])); + NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[FirstOriginalInstanceIndex])); } - if (InstancedOutputPartData.AllBakeActorNames.Num() > 0 && !InstancedOutputPartData.AllBakeActorNames[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[0]); + if(InstancedOutputPartData.AllBakeActorNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeActorNames[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[FirstOriginalInstanceIndex]); - if (InstancedOutputPartData.AllBakeFolders.Num() > 0 && !InstancedOutputPartData.AllBakeFolders[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, InstancedOutputPartData.AllBakeFolders[0]); + if(InstancedOutputPartData.AllBakeFolders.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeFolders[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, InstancedOutputPartData.AllBakeFolders[FirstOriginalInstanceIndex]); - if (InstancedOutputPartData.AllBakeOutlinerFolders.Num() > 0 && !InstancedOutputPartData.AllBakeOutlinerFolders[0].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[0]); + if(InstancedOutputPartData.AllBakeOutlinerFolders.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeOutlinerFolders[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[FirstOriginalInstanceIndex]); - if (InstancedOutputPartData.SplitAttributeValues.Num() > 0 - && !InstancedOutputPartData.SplitAttributeName.IsEmpty() - && InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalObjectIndices[InstanceObjectIdx])) + if(InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalIndex) + && !InstancedOutputPartData.SplitAttributeName.IsEmpty()) { - FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalObjectIndices[InstanceObjectIdx]]; + FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalIndex]; // Cache the split attribute both as attribute and token NewOutputObject.CachedAttributes.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); @@ -530,6 +539,9 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( TArray> OriginalInstancedTransforms; OriginalInstancedTransforms.Add(InInstancedOutput.OriginalTransforms); + TArray> OriginalInstanceIndices; + OriginalInstanceIndices.Add(InInstancedOutput.OriginalInstanceIndices); + // Update our variations using the changed instancedoutputs objects TArray> InstancedObjects; TArray> InstancedTransforms; @@ -539,6 +551,7 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( OutputIdentifier, OriginalInstancedObjects, OriginalInstancedTransforms, + OriginalInstanceIndices, InParentOutput->GetInstancedOutputs(), InstancedObjects, InstancedTransforms, @@ -621,7 +634,7 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( // Extract the material for this variation // FHoudiniInstancedOutput* FoundInstancedOutput = InstancedOutputs.Find(OutputIdentifier); TArray VariationMaterials; - if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, InstancerMaterials, VariationMaterials)) + if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, OriginalInstanceIndices[0], InstancerMaterials, VariationMaterials)) VariationMaterials.Empty(); USceneComponent* NewInstancerComponent = nullptr; @@ -636,6 +649,7 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( bSplitMeshInstancer, bIsFoliageInstancer, InstancerMaterials, + OriginalInstanceIndices[0], InstanceObjectIdx, bForceHISM)) { @@ -702,15 +716,18 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( const TArray& InAllOutputs, TArray& OutInstancedObjects, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValues, TMap& OutPerSplitAttributes) { TArray InstancedObjects; TArray> InstancedTransforms; + TArray> InstancedIndices; TArray InstancedHGPOs; TArray> InstancedHGPOTransforms; + TArray> InstancedHGPOIndices; bool bSuccess = false; switch (InHGPO.InstancerType) @@ -722,6 +739,7 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( InHGPO, InstancedHGPOs, InstancedHGPOTransforms, + InstancedHGPOIndices, OutSplitAttributeName, OutSplitAttributeValues, OutPerSplitAttributes); @@ -735,6 +753,7 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( InHGPO, InstancedObjects, InstancedTransforms, + InstancedIndices, OutSplitAttributeName, OutSplitAttributeValues, OutPerSplitAttributes); @@ -744,14 +763,14 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( case EHoudiniInstancerType::OldSchoolAttributeInstancer: { // Old school attribute override instancer - instance attribute w/ a HoudiniPath - bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); + bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms, InstancedHGPOIndices); } break; case EHoudiniInstancerType::ObjectInstancer: { // Old School object instancer - bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms); + bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms, InstancedHGPOIndices); } break; } @@ -803,12 +822,13 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( { InstancedObjects.Add(MatchingOutputObj); InstancedTransforms.Add(InstancedHGPOTransforms[HGPOIdx]); + InstancedIndices.Add(InstancedHGPOIndices[HGPOIdx]); } } } // - if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() ) + if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() || InstancedIndices.Num() != InstancedObjects.Num()) { // TODO // Error / warning @@ -817,6 +837,7 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( OutInstancedObjects = InstancedObjects; OutInstancedTransforms = InstancedTransforms; + OutInstancedIndices = InstancedIndices; return true; } @@ -827,6 +848,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( const FHoudiniOutputObjectIdentifier& InOutputIdentifier, const TArray& InOriginalObjects, const TArray>& InOriginalTransforms, + const TArray>& InOriginalInstancedIndices, TMap& InstancedOutputs, TArray>& OutVariationsInstancedObjects, TArray>& OutVariationsInstancedTransforms, @@ -872,6 +894,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( CurInstancedOutput.OriginalObject = OriginalObj; CurInstancedOutput.OriginalObjectIndex = InstObjIdx; CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + CurInstancedOutput.OriginalInstanceIndices = InOriginalInstancedIndices[InstObjIdx]; CurInstancedOutput.VariationObjects.Add(OriginalObj); CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); @@ -899,6 +922,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( } CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + CurInstancedOutput.OriginalInstanceIndices = InOriginalInstancedIndices[InstObjIdx]; // Shouldnt be needed... CurInstancedOutput.OriginalObjectIndex = InstObjIdx; @@ -1064,6 +1088,7 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancedHGPO, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValue, TMap& OutPerSplitAttributes) @@ -1140,6 +1165,15 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( OutInstancedHGPO.Add(InstancedHGPO); OutInstancedTransforms.Add(InstancerUnrealTransforms); + + TArray Indices; + Indices.SetNum(InstancerUnrealTransforms.Num()); + for (int32 Index = 0; Index < Indices.Num(); ++Index) + { + Indices[Index] = Index; + } + + OutInstancedIndices.Add(Indices); } // If we don't need to split the instances, we're done @@ -1152,20 +1186,24 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( // Move the output arrays to temp arrays TArray UnsplitInstancedHGPOs = OutInstancedHGPO; TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + TArray> UnsplitInstancedIndices = OutInstancedIndices; // Empty the output arrays OutInstancedHGPO.Empty(); OutInstancedTransforms.Empty(); + OutInstancedIndices.Empty(); OutSplitAttributeValue.Empty(); for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedHGPOs.Num(); ObjIdx++) { // Map of split values to transform arrays TMap> SplitTransformMap; + TMap> SplitIndicesMap; TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + TArray& CurrentIndices = UnsplitInstancedIndices[ObjIdx]; int32 NumInstances = CurrentTransforms.Num(); - if (AllSplitAttributeValues.Num() != NumInstances) + if (AllSplitAttributeValues.Num() != NumInstances || CurrentIndices.Num() != NumInstances) continue; // Split the transforms using the split values @@ -1173,6 +1211,7 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( { const FString& SplitAttrValue = AllSplitAttributeValues[InstIdx]; SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + SplitIndicesMap.FindOrAdd(SplitAttrValue).Add(CurrentIndices[InstIdx]); // Record attributes for any split value we have not yet seen if (bHasAnyPerSplitAttributes) @@ -1203,6 +1242,7 @@ FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( OutSplitAttributeValue.Add(Iterator.Key); OutInstancedHGPO.Add(UnsplitInstancedHGPOs[ObjIdx]); OutInstancedTransforms.Add(Iterator.Value); + OutInstancedIndices.Add(SplitIndicesMap[Iterator.Key]); } } @@ -1217,6 +1257,7 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancedObjects, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValue, TMap& OutPerSplitAttributes) @@ -1364,6 +1405,15 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( OutInstancedObjects.Add(AttributeObject); OutInstancedTransforms.Add(InstancerUnrealTransforms); + TArray Indices; + Indices.SetNum(InstancerUnrealTransforms.Num()); + for (int32 Index = 0; Index < Indices.Num(); ++Index) + { + Indices[Index] = Index; + } + + OutInstancedIndices.Add(Indices); + if(bHasSplitAttribute) SplitAttributeValuesPerObject.Add(AllSplitAttributeValues); } @@ -1452,14 +1502,20 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // Extract the transform values that correspond to this object, and add them to the output arrays const FString & InstancePath = Iter.Key; TArray ObjectTransforms; + TArray ObjectIndices; + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) { if (InstancePath.Equals(PointInstanceValues[Idx])) + { ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + ObjectIndices.Add(Idx); + } } OutInstancedObjects.Add(AttributeObject); OutInstancedTransforms.Add(ObjectTransforms); + OutInstancedIndices.Add(ObjectIndices); Success = true; } else @@ -1469,18 +1525,21 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // add them to the output arrays, and we will process the splits after const FString & InstancePath = Iter.Key; TArray ObjectTransforms; + TArray ObjectIndices; TArray ObjectSplitValues; for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) { if (InstancePath.Equals(PointInstanceValues[Idx])) { ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + ObjectIndices.Add(Idx); ObjectSplitValues.Add(AllSplitAttributeValues[Idx]); } } OutInstancedObjects.Add(AttributeObject); OutInstancedTransforms.Add(ObjectTransforms); + OutInstancedIndices.Add(ObjectIndices); SplitAttributeValuesPerObject.Add(ObjectSplitValues); Success = true; } @@ -1499,10 +1558,12 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // Move the output arrays to temp arrays TArray UnsplitInstancedObjects = OutInstancedObjects; TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + TArray> UnsplitInstancedIndices = OutInstancedIndices; // Empty the output arrays OutInstancedObjects.Empty(); OutInstancedTransforms.Empty(); + OutInstancedIndices.Empty(); // TODO: Output the split values as well! OutSplitAttributeValue.Empty(); @@ -1512,12 +1573,14 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // Map of split values to transform arrays TMap> SplitTransformMap; + TMap> SplitIndicesMap; TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + TArray& CurrentIndices = UnsplitInstancedIndices[ObjIdx]; TArray& CurrentSplits = SplitAttributeValuesPerObject[ObjIdx]; int32 NumInstances = CurrentTransforms.Num(); - if (CurrentSplits.Num() != NumInstances) + if (CurrentSplits.Num() != NumInstances || CurrentIndices.Num() != NumInstances) continue; // Split the transforms using the split values @@ -1525,6 +1588,7 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( { const FString& SplitAttrValue = CurrentSplits[InstIdx]; SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + SplitIndicesMap.FindOrAdd(SplitAttrValue).Add(CurrentIndices[InstIdx]); // Record attributes for any split value we have not yet seen if (bHasAnyPerSplitAttributes) @@ -1554,7 +1618,8 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( { OutSplitAttributeValue.Add(Iterator.Key); OutInstancedObjects.Add(InstancedObject); - OutInstancedTransforms.Add(Iterator.Value); + OutInstancedTransforms.Add(Iterator.Value); + OutInstancedIndices.Add(SplitIndicesMap[Iterator.Key]); } } @@ -1569,7 +1634,8 @@ FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( const FHoudiniGeoPartObject& InHGPO, const TArray& InAllOutputs, TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms) + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices) { if (InHGPO.InstancerType != EHoudiniInstancerType::OldSchoolAttributeInstancer) return false; @@ -1620,11 +1686,13 @@ FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( // Extract only the transforms that correspond to that specific object ID TArray InstanceTransforms; + TArray InstanceIndices; for (int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix) { if ((InstancedObjectIds[Ix] == InstancedObjectId) && (InstancerUnrealTransforms.IsValidIndex(Ix))) { InstanceTransforms.Add(InstancerUnrealTransforms[Ix]); + InstanceIndices.Add(Ix); } } @@ -1633,10 +1701,11 @@ FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( { OutInstancedHGPO.Add(PartToInstance); OutInstancedTransforms.Add(InstanceTransforms); + OutInstancedIndices.Add(InstanceIndices); } } - if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0) + if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0 && OutInstancedIndices.Num() > 0) return true; return false; @@ -1648,7 +1717,8 @@ FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( const FHoudiniGeoPartObject& InHGPO, const TArray& InAllOutputs, TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms) + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices) { if (InHGPO.InstancerType != EHoudiniInstancerType::ObjectInstancer) return false; @@ -1699,6 +1769,15 @@ FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( OutInstancedHGPO.Add(InstanceHGPO); OutInstancedTransforms.Add(InstancerUnrealTransforms); + + TArray Indices; + Indices.SetNum(InstancerUnrealTransforms.Num()); + for (int32 Index = 0; Index < Indices.Num(); ++Index) + { + Indices[Index] = Index; + } + + OutInstancedIndices.Add(Indices); } return true; @@ -1716,6 +1795,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( const bool& InIsSplitMeshInstancer, const bool& InIsFoliageInstancer, const TArray& InstancerMaterials, + const TArray& OriginalInstancerObjectIndices, const int32& InstancerObjectIdx, const bool& bForceHISM) { @@ -1799,14 +1879,14 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( if (OldType == NewType) NewComponent = OldComponent; - UMaterialInterface* InstancerMaterial = nullptr; - if (InstancerMaterials.Num() > 0) - { - if (InstancerMaterials.IsValidIndex(InstancerObjectIdx)) - InstancerMaterial = InstancerMaterials[InstancerObjectIdx]; - else - InstancerMaterial = InstancerMaterials[0]; - } + // First valid index in the original instancer part + // This should be used to access attributes that are store for the whole part, not split + // (ie, GenericProperty Attributes) + int32 FirstOriginalIndex = OriginalInstancerObjectIndices.Num() > 0 ? OriginalInstancerObjectIndices[0] : 0; + + // InstancerMaterials has all the material assignement per instance, + // Fetch the first one for the components that can only use one material + UMaterialInterface* InstancerMaterial = InstancerMaterials.Num() > 0 ? InstancerMaterials[0] : nullptr; bool bSuccess = false; switch (NewType) @@ -1816,7 +1896,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( { // Create an Instanced Static Mesh Component bSuccess = CreateOrUpdateInstancedStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM); + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM, FirstOriginalIndex); } break; @@ -1830,7 +1910,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( case HoudiniInstancedActorComponent: { bSuccess = CreateOrUpdateInstancedActorComponent( - InstancedObject, InstancedObjectTransforms, AllPropertyAttributes, ParentComponent, NewComponent); + InstancedObject, InstancedObjectTransforms, OriginalInstancerObjectIndices, AllPropertyAttributes, ParentComponent, NewComponent); } break; @@ -1838,7 +1918,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( { // Create a Static Mesh Component bSuccess = CreateOrUpdateStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + StaticMesh, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); } break; @@ -1846,14 +1926,14 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( { // Create a Houdini Static Mesh Component bSuccess = CreateOrUpdateHoudiniStaticMeshComponent( - HSM, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + HSM, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); } break; case Foliage: { bSuccess = CreateOrUpdateFoliageInstances( - StaticMesh, FoliageType, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + StaticMesh, FoliageType, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); } } @@ -1890,7 +1970,8 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( USceneComponent* ParentComponent, USceneComponent*& CreatedInstancedComponent, UMaterialInterface * InstancerMaterial, /*=nullptr*/ - const bool & bForceHISM) + const bool & bForceHISM, + const int32& InstancerObjectIdx) { if (!InstancedStaticMesh) return false; @@ -1920,7 +2001,10 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( } // Change the creation method so the component is listed in the details panels - InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; + if (InstancedStaticMeshComponent) + { + InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; + } bCreatedNewComponent = true; } @@ -1929,7 +2013,11 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( return false; InstancedStaticMeshComponent->SetStaticMesh(InstancedStaticMesh); - InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; + + if (InstancedStaticMeshComponent->GetBodyInstance()) + { + InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; + } InstancedStaticMeshComponent->OverrideMaterials.Empty(); if (InstancerMaterial) @@ -1944,8 +2032,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( InstancedStaticMeshComponent->AddInstances(InstancedObjectTransforms, false); // Apply generic attributes if we have any - // TODO: Handle variations w/ index - UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, InstancerObjectIdx); // Assign the new ISMC / HISMC to the output component if we created a new one if(bCreatedNewComponent) @@ -1958,7 +2045,8 @@ bool FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( UObject* InstancedObject, const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, + const TArray& OriginalInstancerObjectIndices, + const TArray& AllPropertyAttributes, USceneComponent* ParentComponent, USceneComponent*& CreatedInstancedComponent) { @@ -2028,8 +2116,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( } // Update the generic properties for that instance if any - // TODO: Handle instance variations w/ Idx - UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, Idx); + UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, OriginalInstancerObjectIndices[Idx]); } // Assign the new ISMC / HISMC to the output component if we created a new one @@ -2231,6 +2318,7 @@ bool FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( UStaticMesh* InstancedStaticMesh, const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, @@ -2282,8 +2370,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( } // Apply generic attributes if we have any - // TODO: Handle variations w/ index - UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, InOriginalIndex); // Assign the new ISMC / HISMC to the output component if we created a new one if (bCreatedNewComponent) @@ -2300,6 +2387,7 @@ bool FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( UHoudiniStaticMesh* InstancedProxyStaticMesh, const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, @@ -2348,7 +2436,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( // Apply generic attributes if we have any // TODO: Handle variations w/ index - UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, InOriginalIndex); // Assign the new HSMC to the output component if we created a new one if (bCreatedNewComponent) @@ -2367,6 +2455,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( UStaticMesh* InstancedStaticMesh, UFoliageType* InFoliageType, const TArray& InstancedObjectTransforms, + const int32& FirstOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, @@ -2487,9 +2576,9 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( // Try to apply generic properties attributes // either on the instancer, mesh or foliage type // TODO: Use proper atIndex!! - UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, 0); - UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, 0); - UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, 0); + UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, FirstOriginalIndex); + UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, FirstOriginalIndex); + UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, FirstOriginalIndex); if (IsValid(FoliageHISMC)) NewInstancedComponent = FoliageHISMC; @@ -2717,21 +2806,26 @@ FHoudiniInstanceTranslator::GetInstancerMaterials( bool FHoudiniInstanceTranslator::GetVariationMaterials( - FHoudiniInstancedOutput* InInstancedOutput , const int32& InVariationIndex, - const TArray& InInstancerMaterials, TArray& OutVariationMaterials) + FHoudiniInstancedOutput* InInstancedOutput, + const int32& InVariationIndex, + const TArray& InOriginalIndices, + const TArray& InInstancerMaterials, + TArray& OutVariationMaterials) { if (!InInstancedOutput || InInstancerMaterials.Num() <= 0) return false; - // TODO: FIXME This also need to be improved and wont work 100%!! - // No variations, reuse the full array if (InInstancedOutput->VariationObjects.Num() == 1) { - if (InInstancerMaterials.IsValidIndex(InInstancedOutput->OriginalObjectIndex)) - OutVariationMaterials.Add(InInstancerMaterials[InInstancedOutput->OriginalObjectIndex]); - else - OutVariationMaterials.Add(InInstancerMaterials[0]); + for (int32 Idx = 0; Idx < InOriginalIndices.Num(); Idx++) + { + if (InInstancerMaterials.IsValidIndex(InOriginalIndices[Idx])) + OutVariationMaterials.Add(InInstancerMaterials[InOriginalIndices[Idx]]); + else + OutVariationMaterials.Add(InInstancerMaterials[0]); + } + return true; } @@ -2750,10 +2844,13 @@ FHoudiniInstanceTranslator::GetVariationMaterials( } else { - if (InInstancerMaterials.IsValidIndex(InInstancedOutput->OriginalObjectIndex)) - OutVariationMaterials.Add(InInstancerMaterials[InInstancedOutput->OriginalObjectIndex]); - else - OutVariationMaterials.Add(InInstancerMaterials[0]); + for (int32 Idx = 0; Idx < InOriginalIndices.Num(); Idx++) + { + if (InInstancerMaterials.IsValidIndex(InOriginalIndices[Idx])) + OutVariationMaterials.Add(InInstancerMaterials[InOriginalIndices[Idx]]); + else + OutVariationMaterials.Add(InInstancerMaterials[0]); + } } return true; @@ -3067,15 +3164,16 @@ FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAP void FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() { - NumInstancedTransformsPerObject.Empty(); - OriginalInstancedTransformsFlat.Empty(); + NumInstancedTransformsPerObject.Empty(OriginalInstancedTransforms.Num()); + // We expect to have one or more entries per object + OriginalInstancedTransformsFlat.Empty(OriginalInstancedTransforms.Num()); for (const TArray& Transforms : OriginalInstancedTransforms) { NumInstancedTransformsPerObject.Add(Transforms.Num()); OriginalInstancedTransformsFlat.Append(Transforms); } - OriginalInstanceObjectPackagePaths.Empty(); + OriginalInstanceObjectPackagePaths.Empty(OriginalInstancedObjects.Num()); for (const UObject* Obj : OriginalInstancedObjects) { if (IsValid(Obj)) @@ -3087,26 +3185,49 @@ FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() OriginalInstanceObjectPackagePaths.Add(FString()); } } + + NumInstancedIndicesPerObject.Empty(OriginalInstancedIndices.Num()); + // We expect to have one or more entries per object + OriginalInstancedIndicesFlat.Empty(OriginalInstancedIndices.Num()); + for (const TArray& InstancedIndices : OriginalInstancedIndices) + { + NumInstancedIndicesPerObject.Add(InstancedIndices.Num()); + OriginalInstancedIndicesFlat.Append(InstancedIndices); + } + + NumPerInstanceCustomDataPerObject.Empty(PerInstanceCustomData.Num()); + // We expect to have one or more entries per object + PerInstanceCustomDataFlat.Empty(PerInstanceCustomData.Num()); + for (const TArray& PerInstanceCustomDataArray : PerInstanceCustomData) + { + NumPerInstanceCustomDataPerObject.Add(PerInstanceCustomDataArray.Num()); + PerInstanceCustomDataFlat.Append(PerInstanceCustomDataArray); + } } void FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() { - const int32 NumObjects = NumInstancedTransformsPerObject.Num(); - OriginalInstancedTransforms.Init(TArray(), NumObjects); - int32 ObjectIndexOffset = 0; - for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) { - TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; - const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; - for (int32 Index = 0; Index < NumInstances; ++Index) + const int32 NumObjects = NumInstancedTransformsPerObject.Num(); + OriginalInstancedTransforms.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) { - Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); + TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; + const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; + Transforms.Reserve(NumInstances); + for (int32 Index = 0; Index < NumInstances; ++Index) + { + Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumInstances; } - ObjectIndexOffset += NumInstances; + NumInstancedTransformsPerObject.Empty(); + OriginalInstancedTransformsFlat.Empty(); } - OriginalInstancedObjects.Empty(); + OriginalInstancedObjects.Empty(OriginalInstanceObjectPackagePaths.Num()); for (const FString& PackageFullPath : OriginalInstanceObjectPackagePaths) { FString PackagePath; @@ -3130,6 +3251,45 @@ FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays OriginalInstancedObjects.Add(nullptr); } } + OriginalInstanceObjectPackagePaths.Empty(); + + { + const int32 NumObjects = NumInstancedIndicesPerObject.Num(); + OriginalInstancedIndices.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 EntryIndex = 0; EntryIndex < NumObjects; ++EntryIndex) + { + TArray& InstancedIndices = OriginalInstancedIndices[EntryIndex]; + const int32 NumInstancedIndices = NumInstancedIndicesPerObject[EntryIndex]; + InstancedIndices.Reserve(NumInstancedIndices); + for (int32 Index = 0; Index < NumInstancedIndices; ++Index) + { + InstancedIndices.Add(OriginalInstancedIndicesFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumInstancedIndices; + } + NumInstancedIndicesPerObject.Empty(); + OriginalInstancedIndicesFlat.Empty(); + } + + { + const int32 NumObjects = NumPerInstanceCustomDataPerObject.Num(); + PerInstanceCustomData.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 EntryIndex = 0; EntryIndex < NumObjects; ++EntryIndex) + { + TArray& PerInstanceCustomDataArray = PerInstanceCustomData[EntryIndex]; + const int32 NumPerInstanceCustomData = NumPerInstanceCustomDataPerObject[EntryIndex]; + PerInstanceCustomDataArray.Reserve(NumPerInstanceCustomData); + for (int32 Index = 0; Index < NumPerInstanceCustomData; ++Index) + { + PerInstanceCustomDataArray.Add(PerInstanceCustomDataFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumPerInstanceCustomData; + } + NumPerInstanceCustomDataPerObject.Empty(); + PerInstanceCustomDataFlat.Empty(); + } } bool @@ -3139,7 +3299,6 @@ FHoudiniInstanceTranslator::GetPerInstanceCustomData( FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) { // Initialize sizes to zero - OutInstancedOutputPartData.NumCustomFloats = 0; OutInstancedOutputPartData.PerInstanceCustomData.SetNum(0); // First look for the number of custom floats @@ -3161,8 +3320,14 @@ FHoudiniInstanceTranslator::GetPerInstanceCustomData( if (CustomFloatsArray.Num() <= 0) return false; - OutInstancedOutputPartData.NumCustomFloats = CustomFloatsArray[0]; - if (OutInstancedOutputPartData.NumCustomFloats <= 0) + int32 NumCustomFloats = 0; + + for (int32 CustomFloatCount : CustomFloatsArray) + { + NumCustomFloats = FMath::Max(NumCustomFloats, CustomFloatCount); + } + + if (NumCustomFloats <= 0) return false; // We do have custom float, now read the per instance custom data @@ -3170,11 +3335,11 @@ FHoudiniInstanceTranslator::GetPerInstanceCustomData( // ie, unreal_per_instance_custom0, unreal_per_instance_custom1 etc... // We do not supprot tuples/arrays attributes for now. TArray> AllCustomDataAttributeValues; - AllCustomDataAttributeValues.SetNum(OutInstancedOutputPartData.NumCustomFloats); + AllCustomDataAttributeValues.SetNum(NumCustomFloats); // Read the custom data attributes int32 NumInstance = 0; - for (int32 nIdx = 0; nIdx < OutInstancedOutputPartData.NumCustomFloats; nIdx++) + for (int32 nIdx = 0; nIdx < NumCustomFloats; nIdx++) { // Build the custom data attribute FString CurrentAttr = TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX) + FString::FromInt(nIdx); @@ -3201,34 +3366,66 @@ FHoudiniInstanceTranslator::GetPerInstanceCustomData( if (NumInstance != AllCustomDataAttributeValues[nIdx].Num()) { HOUDINI_LOG_ERROR(TEXT("Instancer: Invalid number of Per-Instance Custom data attributes, ignoring...")); - OutInstancedOutputPartData.NumCustomFloats = 0; return false; } } // Check sizes - if (AllCustomDataAttributeValues.Num() != OutInstancedOutputPartData.NumCustomFloats) + if (AllCustomDataAttributeValues.Num() != NumCustomFloats) { HOUDINI_LOG_ERROR(TEXT("Instancer: Number of Per-Instance Custom data attributes don't match the number of custom floats, ignoring...")); - OutInstancedOutputPartData.NumCustomFloats = 0; return false; } - // Now that we have read all the custom data values, we need to "interlace" them - // in the final per-instance custom data array, fill missing values with zeroes - OutInstancedOutputPartData.PerInstanceCustomData.SetNumZeroed(OutInstancedOutputPartData.NumCustomFloats * NumInstance); + OutInstancedOutputPartData.PerInstanceCustomData.SetNum(OutInstancedOutputPartData.OriginalInstancedObjects.Num()); - // Fill the custom data array by interlacing the custom float values - for (int32 nCustomIdx = 0; nCustomIdx < OutInstancedOutputPartData.NumCustomFloats; nCustomIdx++) + for (int32 ObjIdx = 0; ObjIdx < OutInstancedOutputPartData.OriginalInstancedObjects.Num(); ++ObjIdx) { - int32 CurrentNumInstance = NumInstance; - if (NumInstance < AllCustomDataAttributeValues[nCustomIdx].Num()) - CurrentNumInstance = AllCustomDataAttributeValues[nCustomIdx].Num(); + OutInstancedOutputPartData.PerInstanceCustomData[ObjIdx].Reset(); + } - // Copy the attribute value we read into the custom data array - for (int32 nInstanceIdx = 0; nInstanceIdx < CurrentNumInstance; nInstanceIdx++) + for(int32 ObjIdx = 0; ObjIdx < OutInstancedOutputPartData.OriginalInstancedObjects.Num(); ++ObjIdx) + { + const TArray& InstanceIndices = OutInstancedOutputPartData.OriginalInstancedIndices[ObjIdx]; + + if (InstanceIndices.Num() == 0) { - OutInstancedOutputPartData.PerInstanceCustomData[nInstanceIdx * OutInstancedOutputPartData.NumCustomFloats + nCustomIdx] = AllCustomDataAttributeValues[nCustomIdx][nInstanceIdx]; + continue; + } + + // Perform some validation + int32 NumCustomFloatsForInstance = CustomFloatsArray[InstanceIndices[0]]; + for (int32 InstIdx : InstanceIndices) + { + if (CustomFloatsArray[InstIdx] != NumCustomFloatsForInstance) + { + NumCustomFloatsForInstance = -1; + break; + } + } + + if (NumCustomFloatsForInstance == -1) + { + continue; + } + + // Now that we have read all the custom data values, we need to "interlace" them + // in the final per-instance custom data array, fill missing values with zeroes + TArray& PerInstanceCustomData = OutInstancedOutputPartData.PerInstanceCustomData[ObjIdx]; + PerInstanceCustomData.Reserve(InstanceIndices.Num() * NumCustomFloatsForInstance); + + if(NumCustomFloatsForInstance == 0) + { + continue; + } + + for (int32 InstIdx : InstanceIndices) + { + for (int32 nCustomIdx = 0; nCustomIdx < NumCustomFloatsForInstance; ++nCustomIdx) + { + float CustomData = (InstIdx < AllCustomDataAttributeValues[nCustomIdx].Num() ? AllCustomDataAttributeValues[nCustomIdx][InstIdx] : 0.0f); + PerInstanceCustomData.Add(CustomData); + } } } @@ -3238,31 +3435,34 @@ FHoudiniInstanceTranslator::GetPerInstanceCustomData( bool FHoudiniInstanceTranslator::UpdateChangedPerInstanceCustomData( - const int32& InNumCustomFloats, const TArray& InPerInstanceCustomData, USceneComponent* InComponentToUpdate) { // Checks - if (InNumCustomFloats < 0) - return false; - UInstancedStaticMeshComponent* ISMC = Cast(InComponentToUpdate); if (!IsValid(ISMC)) return false; // No Custom data to add/remove - if (ISMC->NumCustomDataFloats == 0 && InNumCustomFloats == 0) + if (ISMC->NumCustomDataFloats == 0 && InPerInstanceCustomData.Num() == 0) return false; // We can copy the per instance custom data if we have any // TODO: Properly extract only needed values! - ISMC->NumCustomDataFloats = InNumCustomFloats; - int32 InstanceCount = ISMC->GetInstanceCount(); + int32 NumCustomFloats = InPerInstanceCustomData.Num() / InstanceCount; + + if (NumCustomFloats * InstanceCount != InPerInstanceCustomData.Num()) + { + ISMC->NumCustomDataFloats = 0; + ISMC->PerInstanceSMCustomData.Reset(); + return false; + } + + ISMC->NumCustomDataFloats = NumCustomFloats; // Clear out and reinit to 0 the PerInstanceCustomData array - ISMC->PerInstanceSMCustomData.Empty(InstanceCount * InNumCustomFloats); - ISMC->PerInstanceSMCustomData.SetNumZeroed(InstanceCount * InNumCustomFloats); + ISMC->PerInstanceSMCustomData.SetNumZeroed(InstanceCount * NumCustomFloats); // Behaviour copied From UInstancedStaticMeshComponent::SetCustomData() // except we modify all the instance/custom values at once diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h index 5268c71f4..50a9a7013 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h @@ -78,17 +78,42 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData UPROPERTY() TArray OriginalInstancedObjects; + // Object paths of OriginalInstancedObjects. Used by message passing system + // when sending messages from the async importer to the PDG manager. UObject*/references + // are not directly supported by the messaging system. See BuildFlatInstancedTransformsAndObjectPaths(). UPROPERTY() TArray OriginalInstanceObjectPackagePaths; TArray> OriginalInstancedTransforms; + TArray> OriginalInstancedIndices; + + // Number of entries in OriginalInstancedTransforms. Populated when building + // OriginalInstancedTransformsFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding + // OriginalInstancedTransforms from OriginalInstancedTransformsFlat in BuildOriginalInstancedTransformsAndObjectArrays(). UPROPERTY() TArray NumInstancedTransformsPerObject; + // Flattened version of OriginalInstancedTransforms. Used by message passing system + // when sending messages from the async importer to the PDG manager. Nested arrays + // are not supported by UPROPERTIES and thus not by the messaging system. + // See BuildFlatInstancedTransformsAndObjectPaths(). UPROPERTY() TArray OriginalInstancedTransformsFlat; + // Number of entries in OriginalInstancedIndices. Populated when building + // OriginalInstancedIndicesFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding + // OriginalInstancedIndices from OriginalInstancedIndicesFlat in BuildOriginalInstancedTransformsAndObjectArrays(). + UPROPERTY() + TArray NumInstancedIndicesPerObject; + + // Flattened version of OriginalInstancedIndices. Used by message passing system + // when sending messages from the async importer to the PDG manager. Nested arrays + // are not supported by UPROPERTIES and thus not by the messaging system. See + // BuildFlatInstancedTransformsAndObjectPaths(). + UPROPERTY() + TArray OriginalInstancedIndicesFlat; + UPROPERTY() FString SplitAttributeName; @@ -134,14 +159,22 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData UPROPERTY() TArray MaterialAttributes; - // Number of custom floats for the instancer - UPROPERTY() - int32 NumCustomFloats = -1; - - // Custom float array + // Custom float array per original instanced object // Size is NumCustomFloat * NumberOfInstances + TArray> PerInstanceCustomData; + + // Number of entries in PerInstanceCustomData. Populated when building + // PerInstanceCustomDataFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding + // PerInstanceCustomData from PerInstanceCustomDataFlat in BuildOriginalInstancedTransformsAndObjectArrays(). + UPROPERTY() + TArray NumPerInstanceCustomDataPerObject; + + // Flattened version of OriginalInstancedTransforms. Used by message passing system + // when sending messages from the async importer to the PDG manager. Nested arrays + // are not supported by UPROPERTIES and thus not by the messaging system. + // See BuildFlatInstancedTransformsAndObjectPaths(). UPROPERTY() - TArray PerInstanceCustomData; + TArray PerInstanceCustomDataFlat; void BuildFlatInstancedTransformsAndObjectPaths(); @@ -168,6 +201,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const TArray& InAllOutputs, TArray& OutInstancedObjects, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValues, TMap& OutPerSplitAttributes); @@ -176,6 +210,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancedHGPO, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValue, TMap& OutPerSplitAttributes); @@ -184,6 +219,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancedObjects, TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, FString& OutSplitAttributeName, TArray& OutSplitAttributeValue, TMap& OutPerSplitAttributes); @@ -192,19 +228,22 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const FHoudiniGeoPartObject& InHGPO, const TArray& InAllOutputs, TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms); + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices); static bool GetObjectInstancerHGPOsAndTransforms( const FHoudiniGeoPartObject& InHGPO, const TArray& InAllOutputs, TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms); + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices); // Updates the variations array using the instanced outputs static void UpdateInstanceVariationObjects( const FHoudiniOutputObjectIdentifier& InOutputIdentifier, const TArray& InOriginalObjects, const TArray>& InOriginalTransforms, + const TArray>& OriginalInstancedIndices, TMap& InstancedOutputs, TArray>& OutVariationsInstancedObjects, TArray>& OutVariationsInstancedTransforms, @@ -240,7 +279,8 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const bool& InIsSplitMeshInstancer, const bool& InIsFoliageInstancer, const TArray& InstancerMaterials, - const int32& InstancerObjectIdx = 0, + const TArray& OriginalInstancerObjectIndices, + const int32& InstancerObjectIdx = 0, const bool& bForceHISM = false); // Create or update an ISMC / HISMC @@ -252,12 +292,14 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator USceneComponent* ParentComponent, USceneComponent*& CreatedInstancedComponent, UMaterialInterface * InstancerMaterial = nullptr, - const bool& bForceHISM = false); + const bool& bForceHISM = false, + const int32& InstancerObjectIdx = 0); // Create or update an IAC static bool CreateOrUpdateInstancedActorComponent( UObject* InstancedObject, const TArray& InstancedObjectTransforms, + const TArray& OriginalInstancerObjectIndices, const TArray& AllPropertyAttributes, USceneComponent* ParentComponent, USceneComponent*& CreatedInstancedComponent); @@ -276,6 +318,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator static bool CreateOrUpdateStaticMeshComponent( UStaticMesh* InstancedStaticMesh, const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, @@ -286,6 +329,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator static bool CreateOrUpdateHoudiniStaticMeshComponent( UHoudiniStaticMesh* InstancedProxyStaticMesh, const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, @@ -297,6 +341,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator UStaticMesh* InstancedStaticMesh, UFoliageType* InFoliageType, const TArray& InstancedObjectTransforms, + const int32& FirstOriginalIndex, const TArray& AllPropertyAttributes, const FHoudiniGeoPartObject& InstancerGeoPartObject, USceneComponent* ParentComponent, @@ -349,6 +394,7 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator static bool GetVariationMaterials( FHoudiniInstancedOutput* InInstancedOutput, const int32& InVariationIndex, + const TArray& InOriginalIndices, const TArray& InInstancerMaterials, TArray& OutVariationMaterials); @@ -388,7 +434,6 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator // Update PerInstanceCustom data on the given component if possible static bool UpdateChangedPerInstanceCustomData( - const int32& InNumCustomFloats, const TArray& InPerInstanceCustomData, USceneComponent* InComponentToUpdate); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp index 3189e5440..3603b38f2 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp @@ -63,12 +63,14 @@ const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeY = -150; const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepX = 220; const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepY = 220; -bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( +bool +FHoudiniMaterialTranslator::CreateHoudiniMaterials( const HAPI_NodeId& InAssetId, const FHoudiniPackageParams& InPackageParams, const TArray& InUniqueMaterialIds, const TArray& InUniqueMaterialInfos, const TMap& InMaterials, + const TMap& InAllOutputMaterials, TMap& OutMaterials, TArray& OutPackages, const bool& bForceRecookAll, @@ -98,9 +100,9 @@ bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( for (int32 MaterialIdx = 0; MaterialIdx < InUniqueMaterialIds.Num(); MaterialIdx++) { - HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; + HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; - HAPI_MaterialInfo MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; + const HAPI_MaterialInfo& MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; if (!MaterialInfo.exists) { // The material does not exist, @@ -133,18 +135,31 @@ bool FHoudiniMaterialTranslator::CreateHoudiniMaterials( // Check first in the existing material map UMaterial * Material = nullptr; UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); + bool bCanReuseExistingMaterial = false; if (FoundMaterial) { + bCanReuseExistingMaterial = (bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll; Material = Cast(*FoundMaterial); } + if(!Material || !bCanReuseExistingMaterial) + { + // Try to see if another output/part of this HDA has already recreated this material + // Since those materials have just been recreated, they are considered up to date and can always be reused. + FoundMaterial = InAllOutputMaterials.Find(MaterialPathName); + if (FoundMaterial) + { + Material = Cast(*FoundMaterial); + bCanReuseExistingMaterial = true; + } + } + bool bCreatedNewMaterial = false; if (Material && !Material->IsPendingKill()) { - // If cached material exists and has not changed, we can reuse it. - if ((bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll) + // If the cached material exists and is up to date, we can reuse it. + if (bCanReuseExistingMaterial) { - // We found cached material, we can reuse it. OutMaterials.Add(MaterialPathName, Material); continue; } diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h index d44ca8e4d..485bb1ad6 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h @@ -60,6 +60,7 @@ struct HOUDINIENGINE_API FHoudiniMaterialTranslator const TArray& InUniqueMaterialIds, const TArray& InUniqueMaterialInfos, const TMap& InMaterials, + const TMap& InAllOutputMaterials, TMap& OutMaterials, TArray& OutPackages, const bool& bForceRecookAll, diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp index 361bf4d9d..128836c91 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp @@ -89,6 +89,7 @@ FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( const EHoudiniStaticMeshMethod& InStaticMeshMethod, const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, const FMeshBuildSettings& InMeshBuildSettings, + const TMap& InAllOutputMaterials, UObject* InOuterComponent, bool bInTreatExistingMaterialsAsUpToDate, bool bInDestroyProxies) @@ -140,6 +141,7 @@ FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( NewOutputObjects, AssignementMaterials, ReplacementMaterials, + InAllOutputMaterials, InForceRebuild, InStaticMeshMethod, InSMGenerationProperties, @@ -445,7 +447,7 @@ FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, con // Update navmesh? // Transform the component by transformation provided by HAPI. - InMeshComponent->SetRelativeTransform(InHGPO->TransformMatrix); + InMeshComponent->SetRelativeTransform(InHGPO ? InHGPO->TransformMatrix : FTransform::Identity); // If the static mesh had sockets, we can assign the desired actor to them now UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); @@ -555,6 +557,7 @@ FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( TMap& OutOutputObjects, TMap& AssignmentMaterialMap, TMap& ReplacementMaterialMap, + const TMap& InAllOutputMaterials, const bool& InForceRebuild, const EHoudiniStaticMeshMethod& InStaticMeshMethod, const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, @@ -563,7 +566,7 @@ FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( { // If we're not forcing the rebuild // No need to recreate something that hasn't changed - if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged) && InOutputObjects.Num() > 0) + if (!InForceRebuild && !InHGPO.bHasGeoChanged && !InHGPO.bHasPartChanged && InOutputObjects.Num() > 0) { // Simply reuse the existing meshes OutOutputObjects = InOutputObjects; @@ -576,6 +579,7 @@ FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( CurrentTranslator.SetInputObjects(InOutputObjects); CurrentTranslator.SetOutputObjects(OutOutputObjects); CurrentTranslator.SetInputAssignmentMaterials(AssignmentMaterialMap); + CurrentTranslator.SetAllOutputMaterials(InAllOutputMaterials); CurrentTranslator.SetReplacementMaterials(ReplacementMaterialMap); CurrentTranslator.SetPackageParams(InPackageParams, true); CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); @@ -4508,7 +4512,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; - bool bGenerateTangents = bReadTangents; + bool bGenerateTangentsFromNormalAttribute = false; if (bReadTangents) { // Extract this part's Tangents if needed @@ -4522,26 +4526,34 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + if ((SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0)) + bReadTangents = false; + // We need to manually generate tangents if: // - we have normals but dont have tangentu or tangentv attributes // - we have not specified that we wanted unreal to generate them - bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + bGenerateTangentsFromNormalAttribute = (NormalCount > 0) && !bReadTangents; // Check that the number of tangents read matches the number of normals TangentUCount = SplitTangentU.Num() / 3; TangentVCount = SplitTangentV.Num() / 3; - if (TangentUCount != NormalCount || TangentVCount != NormalCount) + if (NormalCount > 0 && (TangentUCount != NormalCount || TangentVCount != NormalCount)) { HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh: Generate tangents due to count mismatch (# U Tangents = %d; # V Tangents = %d; # Normals = %d)"), TangentUCount, TangentVCount, NormalCount); - bGenerateTangents = true; + bGenerateTangentsFromNormalAttribute = true; + bReadTangents = false; } - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + if (bGenerateTangentsFromNormalAttribute && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) { // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; + bGenerateTangentsFromNormalAttribute = false; } } + else + { + bGenerateTangentsFromNormalAttribute = (NormalCount > 0); + } //--------------------------------------------------------------------------------------------------------------------- // VERTEX COLORS AND ALPHAS @@ -4606,12 +4618,12 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() FoundStaticMesh->Initialize( NumVertexPositions, NumTriangles, - NumUVLayers, // NumUVLayers - 0, // InitialNumStaticMaterials - NormalCount > 0, // HasNormals - NormalCount > 0 && bReadTangents, // HasTangents - bSplitColorValid, // HasColors - bHasPerFaceMaterials // HasPerFaceMaterials + NumUVLayers, // NumUVLayers + 0, // InitialNumStaticMaterials + NormalCount > 0, // HasNormals + bReadTangents || bGenerateTangentsFromNormalAttribute, // HasTangents + bSplitColorValid, // HasColors + bHasPerFaceMaterials // HasPerFaceMaterials ); //--------------------------------------------------------------------------------------------------------------------- @@ -4664,7 +4676,8 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() for (int32 TriangleIdx = 0; TriangleIdx < NumTriangles; ++TriangleIdx) // ParallelFor(NumTriangles, [&](uint32 TriangleIdx) { - + // TODO: add some additional intermediate consts for index calculations to make the indexing + // TODO: code a bit more readable const int32 TriVertIdx0 = TriangleIdx * 3; FoundStaticMesh->SetTriangleVertexIndices(TriangleIdx, FIntVector( TriangleIndices[TriVertIdx0 + 0], @@ -4673,26 +4686,39 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() )); const int32 TriWindingIndex[3] = { 0, 2, 1 }; - if (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)) + // Normals and tangents (either getting tangents from attributes or generating tangents from the + // normals + if (NormalCount > 0 || bReadTangents) { - // Flip Z and Y coordinate for normal, but don't scale for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) { - const FVector Normal( - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] - ); - - FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); + const bool bHasNormal = (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)); + FVector Normal = FVector::ZeroVector; + if (bHasNormal) + { + // Flip Z and Y coordinate for normal, but don't scale + Normal.Set( + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] + ); + + FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); + } - if (bReadTangents) + if (bReadTangents || bGenerateTangentsFromNormalAttribute) { FVector TangentU, TangentV; - if (bGenerateTangents) + if (bGenerateTangentsFromNormalAttribute) { - // Generate the tangents if needed - Normal.FindBestAxisVectors(TangentU, TangentV); + if (bHasNormal) + { + // Generate the tangents if needed + Normal.FindBestAxisVectors(TangentU, TangentV); + + FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); + FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + } } else { @@ -4704,14 +4730,15 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() TangentU.X = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; TangentU.Y = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; TangentU.Z = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; - } - FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); - FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); + FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + } } } } + // Vertex Colors if (bSplitColorValid && SplitColors.IsValidIndex(TriVertIdx0 * AttribInfoColors.tupleSize + 3 * AttribInfoColors.tupleSize - 1)) { FLinearColor VertexLinearColor; @@ -4742,6 +4769,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() } } + // UVs if (NumUVLayers > 0) { // Dynamic mesh supports only 1 UV layer on the mesh it self. So we set the first layer @@ -4764,6 +4792,24 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() } } } + + FMeshBuildSettings BuildSettings; + UpdateMeshBuildSettings( + BuildSettings, + FoundStaticMesh->HasNormals(), + FoundStaticMesh->HasTangents(), + false); + // Compute normals if requested or needed/missing + if (BuildSettings.bRecomputeNormals) + { + FoundStaticMesh->CalculateNormals(BuildSettings.bComputeWeightedNormals); + } + + // Compute tangents if requested or needed/missing + if (BuildSettings.bRecomputeTangents) + { + FoundStaticMesh->CalculateTangents(BuildSettings.bComputeWeightedNormals); + } } //--------------------------------------------------------------------------------------------------------------------- @@ -5099,10 +5145,16 @@ FHoudiniMeshTranslator::CreateNeededMaterials() TArray MaterialAndTexturePackages; FHoudiniMaterialTranslator::CreateHoudiniMaterials( - HGPO.AssetId, PackageParams, - PartUniqueMaterialIds, PartUniqueMaterialInfos, - InputAssignmentMaterials, OutputAssignmentMaterials, - MaterialAndTexturePackages, false, bTreatExistingMaterialsAsUpToDate); + HGPO.AssetId, + PackageParams, + PartUniqueMaterialIds, + PartUniqueMaterialInfos, + InputAssignmentMaterials, + AllOutputMaterials, + OutputAssignmentMaterials, + MaterialAndTexturePackages, + false, + bTreatExistingMaterialsAsUpToDate); /* // Save the created packages if needed diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h index dd23d3604..740a7c491 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h @@ -84,6 +84,7 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator const EHoudiniStaticMeshMethod& InStaticMeshMethod, const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, const FMeshBuildSettings& InMeshBuildSettings, + const TMap& InAllOutputMaterials, UObject* InOuterComponent, bool bInTreatExistingMaterialsAsUpToDate=false, bool bInDestroyProxies=false); @@ -95,6 +96,7 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator TMap& OutOutputObjects, TMap& InAssignmentMaterialMap, TMap& InReplacementMaterialMap, + const TMap& InAllOutputMaterials, const bool& InForceRebuild, const EHoudiniStaticMeshMethod& InStaticMeshMethod, const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, @@ -155,6 +157,7 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator void SetInputAssignmentMaterials(const TMap& InInputMaterials) { InputAssignmentMaterials = InInputMaterials; }; void SetReplacementMaterials(const TMap& InReplacementMaterials) { ReplacementMaterials = InReplacementMaterials; }; + void SetAllOutputMaterials(const TMap& InAllOutputMaterials) { AllOutputMaterials = InAllOutputMaterials; }; //void SetInputObjectProperties(const TMap& InInputObjectProperties) { InputObjectProperties = InInputObjectProperties; }; //void SetOutputObjectProperties(TMap& InOutputObjectProperties) { OutputObjectProperties = InOutputObjectProperties; }; @@ -314,6 +317,9 @@ struct HOUDINIENGINE_API FHoudiniMeshTranslator TMap OutputAssignmentMaterials; // Input Replacement Materials maps TMap ReplacementMaterials; + // All the materials that have been generated by this Houdini Asset + // Used to avoid generating the same houdini material over and over again + TMap AllOutputMaterials; // Input mesh properties //TMap InputObjectProperties; diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp index 604134d09..b483582d2 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp @@ -258,7 +258,12 @@ FHoudiniOutputTranslator::UpdateOutputs( FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; FHoudiniLandscapeExtent LandscapeExtent; TSet ClearedLandscapeLayers; - + + // The houdini materials that have been generated by this HDA. + // We track them to prevent recreate the same houdini material over and over if it is assigned to multiple parts. + // (this can easily happen when using packed prims) + TMap AllOutputMaterials; + TArray CreatedPackages; for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) { @@ -308,6 +313,7 @@ FHoudiniOutputTranslator::UpdateOutputs( bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, HAC->StaticMeshGenerationProperties, HAC->StaticMeshBuildSettings, + AllOutputMaterials, OuterComponent); NumVisibleOutputs++; @@ -317,7 +323,6 @@ FHoudiniOutputTranslator::UpdateOutputs( { bOutHasHoudiniStaticMeshOutput &= CurOutput->HasAnyCurrentProxy(); } - break; } @@ -434,12 +439,21 @@ FHoudiniOutputTranslator::UpdateOutputs( // Do Nothing for now break; } + + for (auto& CurMat : CurOutput->AssignementMaterials) + { + // Add the newly generated materials if any + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } } // Now that all meshes have been created, process the instancers for (auto& CurOutput : InstancerOutputs) { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); + if (!FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent)) + continue; + NumVisibleOutputs++; } @@ -607,6 +621,9 @@ FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAss PackageParams.ComponentGUID = HAC->GetComponentGUID(); PackageParams.ObjectName = FString(); + // Keep track of all generated houdini materials to avoid recreating them over and over + TMap AllOutputMaterials; + bool bFoundProxies = false; TArray InstancerOutputs; for (auto& CurOutput : HAC->Outputs) @@ -623,6 +640,7 @@ FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAss HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, HAC->StaticMeshGenerationProperties, HAC->StaticMeshBuildSettings, + AllOutputMaterials, OuterComponent, true, // bInTreatExistingMaterialsAsUpToDate bInDestroyProxies @@ -633,6 +651,13 @@ FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAss { InstancerOutputs.Add(CurOutput); } + + for (auto& CurMat : CurOutput->AssignementMaterials) + { + //Adds the generated materials if any + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } } // Rebuild instancers if we built any static meshes from proxies diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp index 53b89fbfd..dfcb7b46a 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp @@ -104,3 +104,4 @@ FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage(const { } + diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h index 70c60ad7a..fa2f6f685 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h @@ -185,3 +185,4 @@ struct HOUDINIENGINE_API FHoudiniPDGImportBGEOResultMessage : public FHoudiniPDG TArray Outputs; }; + diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp index 18a4ee85b..c41a590c7 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp @@ -303,6 +303,9 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( UWorld* PersistentWorld = InOuterComponent->GetTypedOuter(); check(PersistentWorld); + // Keep track of all generated houdini materials to avoid recreating them over and over + TMap AllOutputMaterials; + for (UHoudiniOutput* CurOutput : InOutputs) { const EHoudiniOutputType OutputType = CurOutput->GetType(); @@ -338,6 +341,7 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( EHoudiniStaticMeshMethod::RawMesh, SMGP, MBS, + AllOutputMaterials, InOuterComponent, bInTreatExistingMaterialsAsUpToDate, bInDestroyProxies @@ -402,6 +406,13 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( } break; } + + for (auto& CurMat : CurOutput->GetAssignementMaterials()) + { + //Adds the generated materials if any + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } } // Process instancer outputs after all other outputs have been processed, since it @@ -414,11 +425,9 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( CurOutput, InOutputs, InOuterComponent, - InPreBuiltInstancedOutputPartData - ); + InPreBuiltInstancedOutputPartData); } } - USceneComponent* ParentComponent = Cast(InOuterComponent); diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h index 5909c5b69..689b1e9dd 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h @@ -84,3 +84,4 @@ struct HOUDINIENGINE_API FHoudiniPDGTranslator const TMap* InPreBuiltInstancedOutputPartData=nullptr ); }; + diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp index d9871822e..048a622f9 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp @@ -437,6 +437,7 @@ FHoudiniParameterTranslator::BuildAllParameters( // Check if any parent folder of this parameter is invisible bool SkipParm = false; + bool ParentFolderVisible = true; HAPI_ParmId ParentId = ParmInfo.parentId; while (ParentId > 0 && !SkipParm) { @@ -444,8 +445,9 @@ FHoudiniParameterTranslator::BuildAllParameters( return Info.id == ParentId; })) { + // We now keep invisible parameters but show/hid them in UpdateParameterFromInfo(). if (ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER) - SkipParm = true; + ParentFolderVisible = false; ParentId = ParentInfoPtr->parentId; } else @@ -593,6 +595,8 @@ FHoudiniParameterTranslator::BuildAllParameters( ColorRampsToIndex.Add(ColorRampParam, NewParameters.Num()); } } + + HoudiniAssetParameter->SetVisibleParent(ParentFolderVisible); // Add the new parameters NewParameters.Add(HoudiniAssetParameter); @@ -2044,6 +2048,8 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { return false; } + + CurrentIntValue = HoudiniParameterIntChoice->GetIndexFromValueArray(CurrentIntValue); // Check the value is valid if (CurrentIntValue >= ParmInfo.choiceCount) @@ -2092,7 +2098,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( HoudiniParameterIntChoice->SetNumChoices(ParmInfo.choiceCount); bool bMatchedSelectionLabel = false; - int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValue(); + int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValueIndex(); for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) { FString * ChoiceLabel = HoudiniParameterIntChoice->GetStringChoiceLabelAt(ChoiceIdx); @@ -2109,6 +2115,33 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { HoudiniParameterIntChoice->SetStringValue(*ChoiceLabel); } + + int32 IntValue = ChoiceIdx; + /* + // If useMenuItemTokenAsValue is set, then the value is not the index. Find the value using the token, if possible. + if (ParmInfo.useMenuItemTokenAsValue) + { + if (ChoiceIdx < ParmChoices.Num()) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + FString Token; + + if (HoudiniEngineString.ToFString(Token)) + { + try + { + int32 Value = FCString::Atoi(*Token); + IntValue = Value; + } + catch (...) + { + } + } + } + } + */ + + HoudiniParameterIntChoice->SetIntValueArray(ChoiceIdx, IntValue); } } else if (bUpdateValue) @@ -2698,7 +2731,9 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) return false; // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValue(); + const int32 IntValueIndex = ChoiceParam->GetIntValueIndex(); + const int32 IntValue = ChoiceParam->GetIntValue(IntValueIndex); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( FHoudiniEngine::Get().GetSession(), ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); @@ -2723,7 +2758,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) else { // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValue(); + int32 IntValue = ChoiceParam->GetIntValueIndex(); HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmIntValues( FHoudiniEngine::Get().GetSession(), ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp index c6f97412a..a3725b82f 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp @@ -217,3 +217,4 @@ void FHoudiniAttributeResolver::LogCachedAttributesAndTokens() const HOUDINI_LOG_DISPLAY(TEXT("%s"), *FString::Join(Lines, TEXT("\n"))); } + diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI.h b/Source/HoudiniEngine/Public/HAPI/HAPI.h index ead7ca823..26e26950e 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI.h @@ -4280,6 +4280,63 @@ HAPI_DECL HAPI_GetDisplayGeoInfo( const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info ); +/// @brief A helper method that gets the number of main geometry outputs inside +/// an Object node or SOP node. If the node is an Object node, this +/// method will return the cumulative number of geometry outputs for all +/// geometry nodes that it contains. When searching for output geometry, +/// this method will only consider subnetworks that have their display +/// flag enabled. +/// +/// This method must be called before HAPI_GetOutputGeoInfos() is +/// called. +/// +/// @ingroup GeometryGetters +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id of the Object or SOP node to get the geometry +/// output count of. +/// +/// @param[out] count +/// The number of geometry (SOP) outputs. +HAPI_DECL HAPI_GetOutputGeoCount( const HAPI_Session* session, + HAPI_NodeId node_id, + int* count); + +/// @brief Gets the geometry info structs (::HAPI_GeoInfo) for a node's +/// main geometry outputs. This method can only be called after +/// HAPI_GetOutputGeoCount() has been called with the same node id. +/// +/// @ingroup GeometryGetters +/// +/// @param[in] session +/// The session of Houdini you are interacting with. +/// See @ref HAPI_Sessions for more on sessions. +/// Pass NULL to just use the default in-process session. +/// +/// +/// @param[in] node_id +/// The node id of the Object or SOP node to get the output +/// geometry info structs (::HAPI_GeoInfo) for. +/// +/// @param[out] geo_infos_array +/// Output array where the output geometry info structs will be +/// stored. The size of the array must match the count argument +/// returned by the HAPI_GetOutputGeoCount() method. +/// +/// @param[in] count +/// Sanity check count. The count must be equal to the count +/// returned by the HAPI_GetOutputGeoCount() method. +HAPI_DECL HAPI_GetOutputGeoInfos( const HAPI_Session* session, + HAPI_NodeId node_id, + HAPI_GeoInfo* geo_infos_array, + int count ); + /// @brief Get the geometry info struct (::HAPI_GeoInfo) on a SOP node. /// /// @ingroup GeometryGetters diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h index aa75b641a..70a18952f 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h @@ -27,7 +27,7 @@ // expecting to compile against. #define HAPI_VERSION_HOUDINI_MAJOR 18 #define HAPI_VERSION_HOUDINI_MINOR 5 -#define HAPI_VERSION_HOUDINI_BUILD 596 +#define HAPI_VERSION_HOUDINI_BUILD 633 #define HAPI_VERSION_HOUDINI_PATCH 0 // The two components of the Houdini Engine (marketed) version. @@ -40,6 +40,6 @@ // might no longer compile. Semantic changes to the methods will also // cause this version to increase. This number will be reset to 0 // every time the Houdini Engine version is bumped. -#define HAPI_VERSION_HOUDINI_ENGINE_API 2 +#define HAPI_VERSION_HOUDINI_ENGINE_API 3 #endif // __HAPI_VERSION_h__ diff --git a/Source/HoudiniEngine/Public/HoudiniApi.h b/Source/HoudiniEngine/Public/HoudiniApi.h index fe1972dbf..2358241ec 100644 --- a/Source/HoudiniEngine/Public/HoudiniApi.h +++ b/Source/HoudiniEngine/Public/HoudiniApi.h @@ -1,1004 +1,1025 @@ -/* - * Copyright (c) <2021> 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. - * - * - * COMMENTS: - * This file is generated. Do not modify directly. - */ - -#pragma once -#include "HAPI/HAPI.h" -#include "HAL/PlatformProcess.h" - - -struct HOUDINIENGINE_API FHoudiniApi -{ -public: - - static void InitializeHAPI(void* LibraryHandle); - static void FinalizeHAPI(); - static bool IsHAPIInitialized(); - -public: - - typedef HAPI_Result (*AddAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*AddGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - typedef HAPI_AssetInfo (*AssetInfo_CreateFuncPtr)(); - typedef void (*AssetInfo_InitFuncPtr)(HAPI_AssetInfo * in); - typedef HAPI_AttributeInfo (*AttributeInfo_CreateFuncPtr)(); - typedef void (*AttributeInfo_InitFuncPtr)(HAPI_AttributeInfo * in); - typedef HAPI_Result (*BindCustomImplementationFuncPtr)(HAPI_SessionType session_type, const char * dll_path); - typedef HAPI_Result (*CancelPDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - typedef HAPI_Result (*CheckForSpecificErrorsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); - typedef HAPI_Result (*CleanupFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*ClearConnectionErrorFuncPtr)(); - typedef HAPI_Result (*CloseSessionFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*CommitGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*CommitWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*ComposeChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - typedef HAPI_Result (*ComposeNodeCookResultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); - typedef HAPI_Result (*ComposeObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); - typedef HAPI_Result (*ConnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); - typedef HAPI_Result (*ConvertMatrixToEulerFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - typedef HAPI_Result (*ConvertMatrixToQuatFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); - typedef HAPI_Result (*ConvertTransformFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - typedef HAPI_Result (*ConvertTransformEulerToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); - typedef HAPI_Result (*ConvertTransformQuatToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); - typedef HAPI_Result (*CookNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); - typedef HAPI_Bool (*CookOptions_AreEqualFuncPtr)(const HAPI_CookOptions * left, const HAPI_CookOptions * right); - typedef HAPI_CookOptions (*CookOptions_CreateFuncPtr)(); - typedef void (*CookOptions_InitFuncPtr)(HAPI_CookOptions * in); - typedef HAPI_Result (*CookPDGFuncPtr)(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); - typedef HAPI_Result (*CreateCustomSessionFuncPtr)(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); - typedef HAPI_Result (*CreateHeightFieldInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); - typedef HAPI_Result (*CreateHeightfieldInputVolumeNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); - typedef HAPI_Result (*CreateInProcessSessionFuncPtr)(HAPI_Session * session); - typedef HAPI_Result (*CreateInputNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); - typedef HAPI_Result (*CreateNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); - typedef HAPI_Result (*CreateThriftNamedPipeSessionFuncPtr)(HAPI_Session * session, const char * pipe_name); - typedef HAPI_Result (*CreateThriftSocketSessionFuncPtr)(HAPI_Session * session, const char * host_name, int port); - typedef HAPI_Result (*CreateWorkitemFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); - typedef HAPI_CurveInfo (*CurveInfo_CreateFuncPtr)(); - typedef void (*CurveInfo_InitFuncPtr)(HAPI_CurveInfo * in); - typedef HAPI_Result (*DeleteAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*DeleteGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - typedef HAPI_Result (*DeleteNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*DirtyPDGNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); - typedef HAPI_Result (*DisconnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); - typedef HAPI_Result (*DisconnectNodeOutputsAtFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); - typedef HAPI_Result (*ExtractImageToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); - typedef HAPI_Result (*ExtractImageToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); - typedef HAPI_GeoInfo (*GeoInfo_CreateFuncPtr)(); - typedef int (*GeoInfo_GetGroupCountByTypeFuncPtr)(HAPI_GeoInfo * in, HAPI_GroupType type); - typedef void (*GeoInfo_InitFuncPtr)(HAPI_GeoInfo * in); - typedef HAPI_Result (*GetActiveCacheCountFuncPtr)(const HAPI_Session * session, int * active_cache_count); - typedef HAPI_Result (*GetActiveCacheNamesFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); - typedef HAPI_Result (*GetAssetDefinitionParmCountsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); - typedef HAPI_Result (*GetAssetDefinitionParmInfosFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); - typedef HAPI_Result (*GetAssetDefinitionParmValuesFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); - typedef HAPI_Result (*GetAssetInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); - typedef HAPI_Result (*GetAttributeFloat64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeFloatArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*GetAttributeInt16ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeInt64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeIntArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); - typedef HAPI_Result (*GetAttributeStringArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeUInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); - typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); - typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); - typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); - typedef HAPI_Result (*GetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); - typedef HAPI_Result (*GetComposedChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); - typedef HAPI_Result (*GetComposedNodeCookResultFuncPtr)(const HAPI_Session * session, char * string_value, int length); - typedef HAPI_Result (*GetComposedObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); - typedef HAPI_Result (*GetComposedObjectTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); - typedef HAPI_Result (*GetConnectionErrorFuncPtr)(char * string_value, int length, HAPI_Bool clear); - typedef HAPI_Result (*GetConnectionErrorLengthFuncPtr)(int * buffer_length); - typedef HAPI_Result (*GetCookingCurrentCountFuncPtr)(const HAPI_Session * session, int * count); - typedef HAPI_Result (*GetCookingTotalCountFuncPtr)(const HAPI_Session * session, int * count); - typedef HAPI_Result (*GetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); - typedef HAPI_Result (*GetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); - typedef HAPI_Result (*GetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); - typedef HAPI_Result (*GetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); - typedef HAPI_Result (*GetDisplayGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); - typedef HAPI_Result (*GetEnvIntFuncPtr)(HAPI_EnvIntType int_type, int * value); - typedef HAPI_Result (*GetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); - typedef HAPI_Result (*GetFirstVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - typedef HAPI_Result (*GetGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); - typedef HAPI_Result (*GetGeoSizeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); - typedef HAPI_Result (*GetGroupCountOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); - typedef HAPI_Result (*GetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - typedef HAPI_Result (*GetGroupMembershipOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - typedef HAPI_Result (*GetGroupNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - typedef HAPI_Result (*GetGroupNamesOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - typedef HAPI_Result (*GetHIPFileNodeCountFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, int * count); - typedef HAPI_Result (*GetHIPFileNodeIdsFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); - typedef HAPI_Result (*GetHandleBindingInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); - typedef HAPI_Result (*GetHandleInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); - typedef HAPI_Result (*GetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); - typedef HAPI_Result (*GetImageFilePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); - typedef HAPI_Result (*GetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); - typedef HAPI_Result (*GetImageMemoryBufferFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); - typedef HAPI_Result (*GetImagePlaneCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); - typedef HAPI_Result (*GetImagePlanesFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); - typedef HAPI_Result (*GetInstanceTransformsOnPartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - typedef HAPI_Result (*GetInstancedObjectIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); - typedef HAPI_Result (*GetInstancedPartIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); - typedef HAPI_Result (*GetInstancerPartTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - typedef HAPI_Result (*GetManagerNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); - typedef HAPI_Result (*GetMaterialInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); - typedef HAPI_Result (*GetMaterialNodeIdsOnFacesFuncPtr)(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); - typedef HAPI_Result (*GetNextVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - typedef HAPI_Result (*GetNodeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); - typedef HAPI_Result (*GetNodeInputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); - typedef HAPI_Result (*GetNodeOutputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); - typedef HAPI_Result (*GetNodePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); - typedef HAPI_Result (*GetNumWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * num); - typedef HAPI_Result (*GetObjectInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); - typedef HAPI_Result (*GetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); - typedef HAPI_Result (*GetOutputNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); - typedef HAPI_Result (*GetPDGEventsFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); - typedef HAPI_Result (*GetPDGGraphContextIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); - typedef HAPI_Result (*GetPDGGraphContextsFuncPtr)(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); - typedef HAPI_Result (*GetPDGStateFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); - typedef HAPI_Result (*GetParametersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); - typedef HAPI_Result (*GetParmChoiceListsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); - typedef HAPI_Result (*GetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); - typedef HAPI_Result (*GetParmFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); - typedef HAPI_Result (*GetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); - typedef HAPI_Result (*GetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); - typedef HAPI_Result (*GetParmIdFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); - typedef HAPI_Result (*GetParmInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); - typedef HAPI_Result (*GetParmInfoFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); - typedef HAPI_Result (*GetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); - typedef HAPI_Result (*GetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); - typedef HAPI_Result (*GetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); - typedef HAPI_Result (*GetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); - typedef HAPI_Result (*GetParmStringValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); - typedef HAPI_Result (*GetParmTagNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); - typedef HAPI_Result (*GetParmTagValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); - typedef HAPI_Result (*GetParmWithTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); - typedef HAPI_Result (*GetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); - typedef HAPI_Result (*GetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); - typedef HAPI_Result (*GetPresetBufLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); - typedef HAPI_Result (*GetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int * value); - typedef HAPI_Result (*GetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); - typedef HAPI_Result (*GetServerEnvVarCountFuncPtr)(const HAPI_Session * session, int * env_count); - typedef HAPI_Result (*GetServerEnvVarListFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); - typedef HAPI_Result (*GetSessionEnvIntFuncPtr)(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); - typedef HAPI_Result (*GetSessionSyncInfoFuncPtr)(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); - typedef HAPI_Result (*GetSphereInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); - typedef HAPI_Result (*GetStatusFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, int * status); - typedef HAPI_Result (*GetStatusStringFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); - typedef HAPI_Result (*GetStatusStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); - typedef HAPI_Result (*GetStringFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); - typedef HAPI_Result (*GetStringBatchFuncPtr)(const HAPI_Session * session, char * char_buffer, int char_array_length); - typedef HAPI_Result (*GetStringBatchSizeFuncPtr)(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); - typedef HAPI_Result (*GetStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); - typedef HAPI_Result (*GetSupportedImageFileFormatCountFuncPtr)(const HAPI_Session * session, int * file_format_count); - typedef HAPI_Result (*GetSupportedImageFileFormatsFuncPtr)(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); - typedef HAPI_Result (*GetTimeFuncPtr)(const HAPI_Session * session, float * time); - typedef HAPI_Result (*GetTimelineOptionsFuncPtr)(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); - typedef HAPI_Result (*GetTotalCookCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - typedef HAPI_Result (*GetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool * enabled); - typedef HAPI_Result (*GetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); - typedef HAPI_Result (*GetViewportFuncPtr)(const HAPI_Session * session, HAPI_Viewport * viewport); - typedef HAPI_Result (*GetVolumeBoundsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); - typedef HAPI_Result (*GetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); - typedef HAPI_Result (*GetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); - typedef HAPI_Result (*GetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); - typedef HAPI_Result (*GetVolumeVisualInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); - typedef HAPI_Result (*GetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); - typedef HAPI_Result (*GetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); - typedef HAPI_Result (*GetWorkitemDataLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); - typedef HAPI_Result (*GetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); - typedef HAPI_Result (*GetWorkitemInfoFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); - typedef HAPI_Result (*GetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); - typedef HAPI_Result (*GetWorkitemResultInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); - typedef HAPI_Result (*GetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); - typedef HAPI_Result (*GetWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); - typedef HAPI_HandleBindingInfo (*HandleBindingInfo_CreateFuncPtr)(); - typedef void (*HandleBindingInfo_InitFuncPtr)(HAPI_HandleBindingInfo * in); - typedef HAPI_HandleInfo (*HandleInfo_CreateFuncPtr)(); - typedef void (*HandleInfo_InitFuncPtr)(HAPI_HandleInfo * in); - typedef HAPI_ImageFileFormat (*ImageFileFormat_CreateFuncPtr)(); - typedef void (*ImageFileFormat_InitFuncPtr)(HAPI_ImageFileFormat *in); - typedef HAPI_ImageInfo (*ImageInfo_CreateFuncPtr)(); - typedef void (*ImageInfo_InitFuncPtr)(HAPI_ImageInfo * in); - typedef HAPI_Result (*InitializeFuncPtr)(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); - typedef HAPI_Result (*InsertMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - typedef HAPI_Result (*InterruptFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*IsInitializedFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*IsNodeValidFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); - typedef HAPI_Result (*IsSessionValidFuncPtr)(const HAPI_Session * session); - typedef HAPI_Keyframe (*Keyframe_CreateFuncPtr)(); - typedef void (*Keyframe_InitFuncPtr)(HAPI_Keyframe * in); - typedef HAPI_Result (*LoadAssetLibraryFromFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - typedef HAPI_Result (*LoadAssetLibraryFromMemoryFuncPtr)(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - typedef HAPI_Result (*LoadGeoFromFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_Result (*LoadGeoFromMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); - typedef HAPI_Result (*LoadHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); - typedef HAPI_Result (*LoadNodeFromFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); - typedef HAPI_MaterialInfo (*MaterialInfo_CreateFuncPtr)(); - typedef void (*MaterialInfo_InitFuncPtr)(HAPI_MaterialInfo * in); - typedef HAPI_Result (*MergeHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); - typedef HAPI_NodeInfo (*NodeInfo_CreateFuncPtr)(); - typedef void (*NodeInfo_InitFuncPtr)(HAPI_NodeInfo * in); - typedef HAPI_ObjectInfo (*ObjectInfo_CreateFuncPtr)(); - typedef void (*ObjectInfo_InitFuncPtr)(HAPI_ObjectInfo * in); - typedef HAPI_ParmChoiceInfo (*ParmChoiceInfo_CreateFuncPtr)(); - typedef void (*ParmChoiceInfo_InitFuncPtr)(HAPI_ParmChoiceInfo * in); - typedef HAPI_Result (*ParmHasExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); - typedef HAPI_Result (*ParmHasTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); - typedef HAPI_ParmInfo (*ParmInfo_CreateFuncPtr)(); - typedef int (*ParmInfo_GetFloatValueCountFuncPtr)(const HAPI_ParmInfo * in); - typedef int (*ParmInfo_GetIntValueCountFuncPtr)(const HAPI_ParmInfo * in); - typedef int (*ParmInfo_GetStringValueCountFuncPtr)(const HAPI_ParmInfo* in); - typedef void (*ParmInfo_InitFuncPtr)(HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsFloatFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsIntFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsNodeFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsNonValueFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsPathFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsStringFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_PartInfo (*PartInfo_CreateFuncPtr)(); - typedef int (*PartInfo_GetAttributeCountByOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - typedef int (*PartInfo_GetElementCountByAttributeOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - typedef int (*PartInfo_GetElementCountByGroupTypeFuncPtr)(HAPI_PartInfo * in, HAPI_GroupType type); - typedef void (*PartInfo_InitFuncPtr)(HAPI_PartInfo * in); - typedef HAPI_Result (*PausePDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - typedef HAPI_Result (*PythonThreadInterpreterLockFuncPtr)(const HAPI_Session * session, HAPI_Bool locked); - typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); - typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); - typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const HAPI_StringHandle string_handle); - typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); - typedef HAPI_Result (*RenderCOPToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId cop_node_id); - typedef HAPI_Result (*RenderTextureToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); - typedef HAPI_Result (*ResetSimulationFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*RevertGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*RevertParmToDefaultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); - typedef HAPI_Result (*RevertParmToDefaultsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); - typedef HAPI_Result (*SaveGeoToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_Result (*SaveGeoToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); - typedef HAPI_Result (*SaveHIPFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); - typedef HAPI_Result (*SaveNodeToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_SessionSyncInfo (*SessionSyncInfo_CreateFuncPtr)(); - typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); - typedef HAPI_Result (*SetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); - typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); - typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); - typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); - typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); - typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); - typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); - typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); - typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); - typedef HAPI_Result (*SetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); - typedef HAPI_Result (*SetNodeDisplayFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); - typedef HAPI_Result (*SetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); - typedef HAPI_Result (*SetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*SetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); - typedef HAPI_Result (*SetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); - typedef HAPI_Result (*SetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); - typedef HAPI_Result (*SetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); - typedef HAPI_Result (*SetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); - typedef HAPI_Result (*SetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*SetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); - typedef HAPI_Result (*SetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); - typedef HAPI_Result (*SetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int value); - typedef HAPI_Result (*SetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, const char * value); - typedef HAPI_Result (*SetSessionSyncFuncPtr)(const HAPI_Session * session, HAPI_Bool enable); - typedef HAPI_Result (*SetSessionSyncInfoFuncPtr)(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); - typedef HAPI_Result (*SetTimeFuncPtr)(const HAPI_Session * session, float time); - typedef HAPI_Result (*SetTimelineOptionsFuncPtr)(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); - typedef HAPI_Result (*SetTransformAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - typedef HAPI_Result (*SetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool enabled); - typedef HAPI_Result (*SetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); - typedef HAPI_Result (*SetViewportFuncPtr)(const HAPI_Session * session, const HAPI_Viewport * viewport); - typedef HAPI_Result (*SetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); - typedef HAPI_Result (*SetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); - typedef HAPI_Result (*SetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); - typedef HAPI_Result (*SetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); - typedef HAPI_Result (*SetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); - typedef HAPI_Result (*SetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); - typedef HAPI_Result (*SetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); - typedef HAPI_Result (*SetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); - typedef HAPI_Result (*StartThriftNamedPipeServerFuncPtr)(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); - typedef HAPI_Result (*StartThriftSocketServerFuncPtr)(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); - typedef HAPI_ThriftServerOptions (*ThriftServerOptions_CreateFuncPtr)(); - typedef void (*ThriftServerOptions_InitFuncPtr)(HAPI_ThriftServerOptions * in); - typedef HAPI_TimelineOptions (*TimelineOptions_CreateFuncPtr)(); - typedef void (*TimelineOptions_InitFuncPtr)(HAPI_TimelineOptions * in); - typedef HAPI_TransformEuler (*TransformEuler_CreateFuncPtr)(); - typedef void (*TransformEuler_InitFuncPtr)(HAPI_TransformEuler * in); - typedef HAPI_Transform (*Transform_CreateFuncPtr)(); - typedef void (*Transform_InitFuncPtr)(HAPI_Transform * in); - typedef HAPI_Viewport (*Viewport_CreateFuncPtr)(); - typedef HAPI_VolumeInfo (*VolumeInfo_CreateFuncPtr)(); - typedef void (*VolumeInfo_InitFuncPtr)(HAPI_VolumeInfo * in); - typedef HAPI_VolumeTileInfo (*VolumeTileInfo_CreateFuncPtr)(); - typedef void (*VolumeTileInfo_InitFuncPtr)(HAPI_VolumeTileInfo * in); - -public: - - static AddAttributeFuncPtr AddAttribute; - static AddGroupFuncPtr AddGroup; - static AssetInfo_CreateFuncPtr AssetInfo_Create; - static AssetInfo_InitFuncPtr AssetInfo_Init; - static AttributeInfo_CreateFuncPtr AttributeInfo_Create; - static AttributeInfo_InitFuncPtr AttributeInfo_Init; - static BindCustomImplementationFuncPtr BindCustomImplementation; - static CancelPDGCookFuncPtr CancelPDGCook; - static CheckForSpecificErrorsFuncPtr CheckForSpecificErrors; - static CleanupFuncPtr Cleanup; - static ClearConnectionErrorFuncPtr ClearConnectionError; - static CloseSessionFuncPtr CloseSession; - static CommitGeoFuncPtr CommitGeo; - static CommitWorkitemsFuncPtr CommitWorkitems; - static ComposeChildNodeListFuncPtr ComposeChildNodeList; - static ComposeNodeCookResultFuncPtr ComposeNodeCookResult; - static ComposeObjectListFuncPtr ComposeObjectList; - static ConnectNodeInputFuncPtr ConnectNodeInput; - static ConvertMatrixToEulerFuncPtr ConvertMatrixToEuler; - static ConvertMatrixToQuatFuncPtr ConvertMatrixToQuat; - static ConvertTransformFuncPtr ConvertTransform; - static ConvertTransformEulerToMatrixFuncPtr ConvertTransformEulerToMatrix; - static ConvertTransformQuatToMatrixFuncPtr ConvertTransformQuatToMatrix; - static CookNodeFuncPtr CookNode; - static CookOptions_AreEqualFuncPtr CookOptions_AreEqual; - static CookOptions_CreateFuncPtr CookOptions_Create; - static CookOptions_InitFuncPtr CookOptions_Init; - static CookPDGFuncPtr CookPDG; - static CreateCustomSessionFuncPtr CreateCustomSession; - static CreateHeightFieldInputFuncPtr CreateHeightFieldInput; - static CreateHeightfieldInputVolumeNodeFuncPtr CreateHeightfieldInputVolumeNode; - static CreateInProcessSessionFuncPtr CreateInProcessSession; - static CreateInputNodeFuncPtr CreateInputNode; - static CreateNodeFuncPtr CreateNode; - static CreateThriftNamedPipeSessionFuncPtr CreateThriftNamedPipeSession; - static CreateThriftSocketSessionFuncPtr CreateThriftSocketSession; - static CreateWorkitemFuncPtr CreateWorkitem; - static CurveInfo_CreateFuncPtr CurveInfo_Create; - static CurveInfo_InitFuncPtr CurveInfo_Init; - static DeleteAttributeFuncPtr DeleteAttribute; - static DeleteGroupFuncPtr DeleteGroup; - static DeleteNodeFuncPtr DeleteNode; - static DirtyPDGNodeFuncPtr DirtyPDGNode; - static DisconnectNodeInputFuncPtr DisconnectNodeInput; - static DisconnectNodeOutputsAtFuncPtr DisconnectNodeOutputsAt; - static ExtractImageToFileFuncPtr ExtractImageToFile; - static ExtractImageToMemoryFuncPtr ExtractImageToMemory; - static GeoInfo_CreateFuncPtr GeoInfo_Create; - static GeoInfo_GetGroupCountByTypeFuncPtr GeoInfo_GetGroupCountByType; - static GeoInfo_InitFuncPtr GeoInfo_Init; - static GetActiveCacheCountFuncPtr GetActiveCacheCount; - static GetActiveCacheNamesFuncPtr GetActiveCacheNames; - static GetAssetDefinitionParmCountsFuncPtr GetAssetDefinitionParmCounts; - static GetAssetDefinitionParmInfosFuncPtr GetAssetDefinitionParmInfos; - static GetAssetDefinitionParmValuesFuncPtr GetAssetDefinitionParmValues; - static GetAssetInfoFuncPtr GetAssetInfo; - static GetAttributeFloat64ArrayDataFuncPtr GetAttributeFloat64ArrayData; - static GetAttributeFloat64DataFuncPtr GetAttributeFloat64Data; - static GetAttributeFloatArrayDataFuncPtr GetAttributeFloatArrayData; - static GetAttributeFloatDataFuncPtr GetAttributeFloatData; - static GetAttributeInfoFuncPtr GetAttributeInfo; - static GetAttributeInt16ArrayDataFuncPtr GetAttributeInt16ArrayData; - static GetAttributeInt16DataFuncPtr GetAttributeInt16Data; - static GetAttributeInt64ArrayDataFuncPtr GetAttributeInt64ArrayData; - static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; - static GetAttributeInt8ArrayDataFuncPtr GetAttributeInt8ArrayData; - static GetAttributeInt8DataFuncPtr GetAttributeInt8Data; - static GetAttributeIntArrayDataFuncPtr GetAttributeIntArrayData; - static GetAttributeIntDataFuncPtr GetAttributeIntData; - static GetAttributeNamesFuncPtr GetAttributeNames; - static GetAttributeStringArrayDataFuncPtr GetAttributeStringArrayData; - static GetAttributeStringDataFuncPtr GetAttributeStringData; - static GetAttributeUInt8ArrayDataFuncPtr GetAttributeUInt8ArrayData; - static GetAttributeUInt8DataFuncPtr GetAttributeUInt8Data; - static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; - static GetAvailableAssetsFuncPtr GetAvailableAssets; - static GetBoxInfoFuncPtr GetBoxInfo; - static GetCachePropertyFuncPtr GetCacheProperty; - static GetComposedChildNodeListFuncPtr GetComposedChildNodeList; - static GetComposedNodeCookResultFuncPtr GetComposedNodeCookResult; - static GetComposedObjectListFuncPtr GetComposedObjectList; - static GetComposedObjectTransformsFuncPtr GetComposedObjectTransforms; - static GetConnectionErrorFuncPtr GetConnectionError; - static GetConnectionErrorLengthFuncPtr GetConnectionErrorLength; - static GetCookingCurrentCountFuncPtr GetCookingCurrentCount; - static GetCookingTotalCountFuncPtr GetCookingTotalCount; - static GetCurveCountsFuncPtr GetCurveCounts; - static GetCurveInfoFuncPtr GetCurveInfo; - static GetCurveKnotsFuncPtr GetCurveKnots; - static GetCurveOrdersFuncPtr GetCurveOrders; - static GetDisplayGeoInfoFuncPtr GetDisplayGeoInfo; - static GetEnvIntFuncPtr GetEnvInt; - static GetFaceCountsFuncPtr GetFaceCounts; - static GetFirstVolumeTileFuncPtr GetFirstVolumeTile; - static GetGeoInfoFuncPtr GetGeoInfo; - static GetGeoSizeFuncPtr GetGeoSize; - static GetGroupCountOnPackedInstancePartFuncPtr GetGroupCountOnPackedInstancePart; - static GetGroupMembershipFuncPtr GetGroupMembership; - static GetGroupMembershipOnPackedInstancePartFuncPtr GetGroupMembershipOnPackedInstancePart; - static GetGroupNamesFuncPtr GetGroupNames; - static GetGroupNamesOnPackedInstancePartFuncPtr GetGroupNamesOnPackedInstancePart; - static GetHIPFileNodeCountFuncPtr GetHIPFileNodeCount; - static GetHIPFileNodeIdsFuncPtr GetHIPFileNodeIds; - static GetHandleBindingInfoFuncPtr GetHandleBindingInfo; - static GetHandleInfoFuncPtr GetHandleInfo; - static GetHeightFieldDataFuncPtr GetHeightFieldData; - static GetImageFilePathFuncPtr GetImageFilePath; - static GetImageInfoFuncPtr GetImageInfo; - static GetImageMemoryBufferFuncPtr GetImageMemoryBuffer; - static GetImagePlaneCountFuncPtr GetImagePlaneCount; - static GetImagePlanesFuncPtr GetImagePlanes; - static GetInstanceTransformsOnPartFuncPtr GetInstanceTransformsOnPart; - static GetInstancedObjectIdsFuncPtr GetInstancedObjectIds; - static GetInstancedPartIdsFuncPtr GetInstancedPartIds; - static GetInstancerPartTransformsFuncPtr GetInstancerPartTransforms; - static GetManagerNodeIdFuncPtr GetManagerNodeId; - static GetMaterialInfoFuncPtr GetMaterialInfo; - static GetMaterialNodeIdsOnFacesFuncPtr GetMaterialNodeIdsOnFaces; - static GetNextVolumeTileFuncPtr GetNextVolumeTile; - static GetNodeInfoFuncPtr GetNodeInfo; - static GetNodeInputNameFuncPtr GetNodeInputName; - static GetNodeOutputNameFuncPtr GetNodeOutputName; - static GetNodePathFuncPtr GetNodePath; - static GetNumWorkitemsFuncPtr GetNumWorkitems; - static GetObjectInfoFuncPtr GetObjectInfo; - static GetObjectTransformFuncPtr GetObjectTransform; - static GetOutputNodeIdFuncPtr GetOutputNodeId; - static GetPDGEventsFuncPtr GetPDGEvents; - static GetPDGGraphContextIdFuncPtr GetPDGGraphContextId; - static GetPDGGraphContextsFuncPtr GetPDGGraphContexts; - static GetPDGStateFuncPtr GetPDGState; - static GetParametersFuncPtr GetParameters; - static GetParmChoiceListsFuncPtr GetParmChoiceLists; - static GetParmExpressionFuncPtr GetParmExpression; - static GetParmFileFuncPtr GetParmFile; - static GetParmFloatValueFuncPtr GetParmFloatValue; - static GetParmFloatValuesFuncPtr GetParmFloatValues; - static GetParmIdFromNameFuncPtr GetParmIdFromName; - static GetParmInfoFuncPtr GetParmInfo; - static GetParmInfoFromNameFuncPtr GetParmInfoFromName; - static GetParmIntValueFuncPtr GetParmIntValue; - static GetParmIntValuesFuncPtr GetParmIntValues; - static GetParmNodeValueFuncPtr GetParmNodeValue; - static GetParmStringValueFuncPtr GetParmStringValue; - static GetParmStringValuesFuncPtr GetParmStringValues; - static GetParmTagNameFuncPtr GetParmTagName; - static GetParmTagValueFuncPtr GetParmTagValue; - static GetParmWithTagFuncPtr GetParmWithTag; - static GetPartInfoFuncPtr GetPartInfo; - static GetPresetFuncPtr GetPreset; - static GetPresetBufLengthFuncPtr GetPresetBufLength; - static GetServerEnvIntFuncPtr GetServerEnvInt; - static GetServerEnvStringFuncPtr GetServerEnvString; - static GetServerEnvVarCountFuncPtr GetServerEnvVarCount; - static GetServerEnvVarListFuncPtr GetServerEnvVarList; - static GetSessionEnvIntFuncPtr GetSessionEnvInt; - static GetSessionSyncInfoFuncPtr GetSessionSyncInfo; - static GetSphereInfoFuncPtr GetSphereInfo; - static GetStatusFuncPtr GetStatus; - static GetStatusStringFuncPtr GetStatusString; - static GetStatusStringBufLengthFuncPtr GetStatusStringBufLength; - static GetStringFuncPtr GetString; - static GetStringBatchFuncPtr GetStringBatch; - static GetStringBatchSizeFuncPtr GetStringBatchSize; - static GetStringBufLengthFuncPtr GetStringBufLength; - static GetSupportedImageFileFormatCountFuncPtr GetSupportedImageFileFormatCount; - static GetSupportedImageFileFormatsFuncPtr GetSupportedImageFileFormats; - static GetTimeFuncPtr GetTime; - static GetTimelineOptionsFuncPtr GetTimelineOptions; - static GetTotalCookCountFuncPtr GetTotalCookCount; - static GetUseHoudiniTimeFuncPtr GetUseHoudiniTime; - static GetVertexListFuncPtr GetVertexList; - static GetViewportFuncPtr GetViewport; - static GetVolumeBoundsFuncPtr GetVolumeBounds; - static GetVolumeInfoFuncPtr GetVolumeInfo; - static GetVolumeTileFloatDataFuncPtr GetVolumeTileFloatData; - static GetVolumeTileIntDataFuncPtr GetVolumeTileIntData; - static GetVolumeVisualInfoFuncPtr GetVolumeVisualInfo; - static GetVolumeVoxelFloatDataFuncPtr GetVolumeVoxelFloatData; - static GetVolumeVoxelIntDataFuncPtr GetVolumeVoxelIntData; - static GetWorkitemDataLengthFuncPtr GetWorkitemDataLength; - static GetWorkitemFloatDataFuncPtr GetWorkitemFloatData; - static GetWorkitemInfoFuncPtr GetWorkitemInfo; - static GetWorkitemIntDataFuncPtr GetWorkitemIntData; - static GetWorkitemResultInfoFuncPtr GetWorkitemResultInfo; - static GetWorkitemStringDataFuncPtr GetWorkitemStringData; - static GetWorkitemsFuncPtr GetWorkitems; - static HandleBindingInfo_CreateFuncPtr HandleBindingInfo_Create; - static HandleBindingInfo_InitFuncPtr HandleBindingInfo_Init; - static HandleInfo_CreateFuncPtr HandleInfo_Create; - static HandleInfo_InitFuncPtr HandleInfo_Init; - static ImageFileFormat_CreateFuncPtr ImageFileFormat_Create; - static ImageFileFormat_InitFuncPtr ImageFileFormat_Init; - static ImageInfo_CreateFuncPtr ImageInfo_Create; - static ImageInfo_InitFuncPtr ImageInfo_Init; - static InitializeFuncPtr Initialize; - static InsertMultiparmInstanceFuncPtr InsertMultiparmInstance; - static InterruptFuncPtr Interrupt; - static IsInitializedFuncPtr IsInitialized; - static IsNodeValidFuncPtr IsNodeValid; - static IsSessionValidFuncPtr IsSessionValid; - static Keyframe_CreateFuncPtr Keyframe_Create; - static Keyframe_InitFuncPtr Keyframe_Init; - static LoadAssetLibraryFromFileFuncPtr LoadAssetLibraryFromFile; - static LoadAssetLibraryFromMemoryFuncPtr LoadAssetLibraryFromMemory; - static LoadGeoFromFileFuncPtr LoadGeoFromFile; - static LoadGeoFromMemoryFuncPtr LoadGeoFromMemory; - static LoadHIPFileFuncPtr LoadHIPFile; - static LoadNodeFromFileFuncPtr LoadNodeFromFile; - static MaterialInfo_CreateFuncPtr MaterialInfo_Create; - static MaterialInfo_InitFuncPtr MaterialInfo_Init; - static MergeHIPFileFuncPtr MergeHIPFile; - static NodeInfo_CreateFuncPtr NodeInfo_Create; - static NodeInfo_InitFuncPtr NodeInfo_Init; - static ObjectInfo_CreateFuncPtr ObjectInfo_Create; - static ObjectInfo_InitFuncPtr ObjectInfo_Init; - static ParmChoiceInfo_CreateFuncPtr ParmChoiceInfo_Create; - static ParmChoiceInfo_InitFuncPtr ParmChoiceInfo_Init; - static ParmHasExpressionFuncPtr ParmHasExpression; - static ParmHasTagFuncPtr ParmHasTag; - static ParmInfo_CreateFuncPtr ParmInfo_Create; - static ParmInfo_GetFloatValueCountFuncPtr ParmInfo_GetFloatValueCount; - static ParmInfo_GetIntValueCountFuncPtr ParmInfo_GetIntValueCount; - static ParmInfo_GetStringValueCountFuncPtr ParmInfo_GetStringValueCount; - static ParmInfo_InitFuncPtr ParmInfo_Init; - static ParmInfo_IsFloatFuncPtr ParmInfo_IsFloat; - static ParmInfo_IsIntFuncPtr ParmInfo_IsInt; - static ParmInfo_IsNodeFuncPtr ParmInfo_IsNode; - static ParmInfo_IsNonValueFuncPtr ParmInfo_IsNonValue; - static ParmInfo_IsPathFuncPtr ParmInfo_IsPath; - static ParmInfo_IsStringFuncPtr ParmInfo_IsString; - static PartInfo_CreateFuncPtr PartInfo_Create; - static PartInfo_GetAttributeCountByOwnerFuncPtr PartInfo_GetAttributeCountByOwner; - static PartInfo_GetElementCountByAttributeOwnerFuncPtr PartInfo_GetElementCountByAttributeOwner; - static PartInfo_GetElementCountByGroupTypeFuncPtr PartInfo_GetElementCountByGroupType; - static PartInfo_InitFuncPtr PartInfo_Init; - static PausePDGCookFuncPtr PausePDGCook; - static PythonThreadInterpreterLockFuncPtr PythonThreadInterpreterLock; - static QueryNodeInputFuncPtr QueryNodeInput; - static QueryNodeOutputConnectedCountFuncPtr QueryNodeOutputConnectedCount; - static QueryNodeOutputConnectedNodesFuncPtr QueryNodeOutputConnectedNodes; - static RemoveCustomStringFuncPtr RemoveCustomString; - static RemoveMultiparmInstanceFuncPtr RemoveMultiparmInstance; - static RemoveParmExpressionFuncPtr RemoveParmExpression; - static RenameNodeFuncPtr RenameNode; - static RenderCOPToImageFuncPtr RenderCOPToImage; - static RenderTextureToImageFuncPtr RenderTextureToImage; - static ResetSimulationFuncPtr ResetSimulation; - static RevertGeoFuncPtr RevertGeo; - static RevertParmToDefaultFuncPtr RevertParmToDefault; - static RevertParmToDefaultsFuncPtr RevertParmToDefaults; - static SaveGeoToFileFuncPtr SaveGeoToFile; - static SaveGeoToMemoryFuncPtr SaveGeoToMemory; - static SaveHIPFileFuncPtr SaveHIPFile; - static SaveNodeToFileFuncPtr SaveNodeToFile; - static SessionSyncInfo_CreateFuncPtr SessionSyncInfo_Create; - static SetAnimCurveFuncPtr SetAnimCurve; - static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; - static SetAttributeFloatDataFuncPtr SetAttributeFloatData; - static SetAttributeInt16DataFuncPtr SetAttributeInt16Data; - static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; - static SetAttributeInt8DataFuncPtr SetAttributeInt8Data; - static SetAttributeIntDataFuncPtr SetAttributeIntData; - static SetAttributeStringDataFuncPtr SetAttributeStringData; - static SetAttributeUInt8DataFuncPtr SetAttributeUInt8Data; - static SetCachePropertyFuncPtr SetCacheProperty; - static SetCurveCountsFuncPtr SetCurveCounts; - static SetCurveInfoFuncPtr SetCurveInfo; - static SetCurveKnotsFuncPtr SetCurveKnots; - static SetCurveOrdersFuncPtr SetCurveOrders; - static SetCustomStringFuncPtr SetCustomString; - static SetFaceCountsFuncPtr SetFaceCounts; - static SetGroupMembershipFuncPtr SetGroupMembership; - static SetHeightFieldDataFuncPtr SetHeightFieldData; - static SetImageInfoFuncPtr SetImageInfo; - static SetNodeDisplayFuncPtr SetNodeDisplay; - static SetObjectTransformFuncPtr SetObjectTransform; - static SetParmExpressionFuncPtr SetParmExpression; - static SetParmFloatValueFuncPtr SetParmFloatValue; - static SetParmFloatValuesFuncPtr SetParmFloatValues; - static SetParmIntValueFuncPtr SetParmIntValue; - static SetParmIntValuesFuncPtr SetParmIntValues; - static SetParmNodeValueFuncPtr SetParmNodeValue; - static SetParmStringValueFuncPtr SetParmStringValue; - static SetPartInfoFuncPtr SetPartInfo; - static SetPresetFuncPtr SetPreset; - static SetServerEnvIntFuncPtr SetServerEnvInt; - static SetServerEnvStringFuncPtr SetServerEnvString; - static SetSessionSyncFuncPtr SetSessionSync; - static SetSessionSyncInfoFuncPtr SetSessionSyncInfo; - static SetTimeFuncPtr SetTime; - static SetTimelineOptionsFuncPtr SetTimelineOptions; - static SetTransformAnimCurveFuncPtr SetTransformAnimCurve; - static SetUseHoudiniTimeFuncPtr SetUseHoudiniTime; - static SetVertexListFuncPtr SetVertexList; - static SetViewportFuncPtr SetViewport; - static SetVolumeInfoFuncPtr SetVolumeInfo; - static SetVolumeTileFloatDataFuncPtr SetVolumeTileFloatData; - static SetVolumeTileIntDataFuncPtr SetVolumeTileIntData; - static SetVolumeVoxelFloatDataFuncPtr SetVolumeVoxelFloatData; - static SetVolumeVoxelIntDataFuncPtr SetVolumeVoxelIntData; - static SetWorkitemFloatDataFuncPtr SetWorkitemFloatData; - static SetWorkitemIntDataFuncPtr SetWorkitemIntData; - static SetWorkitemStringDataFuncPtr SetWorkitemStringData; - static StartThriftNamedPipeServerFuncPtr StartThriftNamedPipeServer; - static StartThriftSocketServerFuncPtr StartThriftSocketServer; - static ThriftServerOptions_CreateFuncPtr ThriftServerOptions_Create; - static ThriftServerOptions_InitFuncPtr ThriftServerOptions_Init; - static TimelineOptions_CreateFuncPtr TimelineOptions_Create; - static TimelineOptions_InitFuncPtr TimelineOptions_Init; - static TransformEuler_CreateFuncPtr TransformEuler_Create; - static TransformEuler_InitFuncPtr TransformEuler_Init; - static Transform_CreateFuncPtr Transform_Create; - static Transform_InitFuncPtr Transform_Init; - static Viewport_CreateFuncPtr Viewport_Create; - static VolumeInfo_CreateFuncPtr VolumeInfo_Create; - static VolumeInfo_InitFuncPtr VolumeInfo_Init; - static VolumeTileInfo_CreateFuncPtr VolumeTileInfo_Create; - static VolumeTileInfo_InitFuncPtr VolumeTileInfo_Init; - -public: - - static HAPI_Result AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - static HAPI_Result AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - static HAPI_AssetInfo AssetInfo_CreateEmptyStub(); - static void AssetInfo_InitEmptyStub(HAPI_AssetInfo * in); - static HAPI_AttributeInfo AttributeInfo_CreateEmptyStub(); - static void AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in); - static HAPI_Result BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path); - static HAPI_Result CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - static HAPI_Result CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); - static HAPI_Result CleanupEmptyStub(const HAPI_Session * session); - static HAPI_Result ClearConnectionErrorEmptyStub(); - static HAPI_Result CloseSessionEmptyStub(const HAPI_Session * session); - static HAPI_Result CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - static HAPI_Result ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); - static HAPI_Result ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); - static HAPI_Result ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); - static HAPI_Result ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - static HAPI_Result ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); - static HAPI_Result ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - static HAPI_Result ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); - static HAPI_Result ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); - static HAPI_Result CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); - static HAPI_Bool CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right); - static HAPI_CookOptions CookOptions_CreateEmptyStub(); - static void CookOptions_InitEmptyStub(HAPI_CookOptions * in); - static HAPI_Result CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); - static HAPI_Result CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); - static HAPI_Result CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); - static HAPI_Result CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); - static HAPI_Result CreateInProcessSessionEmptyStub(HAPI_Session * session); - static HAPI_Result CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); - static HAPI_Result CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); - static HAPI_Result CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name); - static HAPI_Result CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port); - static HAPI_Result CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); - static HAPI_CurveInfo CurveInfo_CreateEmptyStub(); - static void CurveInfo_InitEmptyStub(HAPI_CurveInfo * in); - static HAPI_Result DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - static HAPI_Result DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - static HAPI_Result DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); - static HAPI_Result DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); - static HAPI_Result DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); - static HAPI_Result ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); - static HAPI_Result ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); - static HAPI_GeoInfo GeoInfo_CreateEmptyStub(); - static int GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type); - static void GeoInfo_InitEmptyStub(HAPI_GeoInfo * in); - static HAPI_Result GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count); - static HAPI_Result GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); - static HAPI_Result GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); - static HAPI_Result GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); - static HAPI_Result GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); - static HAPI_Result GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); - static HAPI_Result GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); - static HAPI_Result GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); - static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); - static HAPI_Result GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); - static HAPI_Result GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); - static HAPI_Result GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); - static HAPI_Result GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); - static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); - static HAPI_Result GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); - static HAPI_Result GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); - static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); - static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); - static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); - static HAPI_Result GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); - static HAPI_Result GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); - static HAPI_Result GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length); - static HAPI_Result GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); - static HAPI_Result GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); - static HAPI_Result GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear); - static HAPI_Result GetConnectionErrorLengthEmptyStub(int * buffer_length); - static HAPI_Result GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count); - static HAPI_Result GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count); - static HAPI_Result GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); - static HAPI_Result GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); - static HAPI_Result GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); - static HAPI_Result GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); - static HAPI_Result GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); - static HAPI_Result GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value); - static HAPI_Result GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); - static HAPI_Result GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - static HAPI_Result GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); - static HAPI_Result GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); - static HAPI_Result GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); - static HAPI_Result GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - static HAPI_Result GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - static HAPI_Result GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - static HAPI_Result GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - static HAPI_Result GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count); - static HAPI_Result GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); - static HAPI_Result GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); - static HAPI_Result GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); - static HAPI_Result GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); - static HAPI_Result GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); - static HAPI_Result GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); - static HAPI_Result GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); - static HAPI_Result GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); - static HAPI_Result GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); - static HAPI_Result GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - static HAPI_Result GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); - static HAPI_Result GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); - static HAPI_Result GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - static HAPI_Result GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); - static HAPI_Result GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); - static HAPI_Result GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); - static HAPI_Result GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - static HAPI_Result GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); - static HAPI_Result GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); - static HAPI_Result GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); - static HAPI_Result GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); - static HAPI_Result GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num); - static HAPI_Result GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); - static HAPI_Result GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); - static HAPI_Result GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); - static HAPI_Result GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); - static HAPI_Result GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); - static HAPI_Result GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); - static HAPI_Result GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); - static HAPI_Result GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); - static HAPI_Result GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); - static HAPI_Result GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); - static HAPI_Result GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); - static HAPI_Result GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); - static HAPI_Result GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); - static HAPI_Result GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); - static HAPI_Result GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); - static HAPI_Result GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); - static HAPI_Result GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); - static HAPI_Result GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); - static HAPI_Result GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); - static HAPI_Result GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); - static HAPI_Result GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); - static HAPI_Result GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); - static HAPI_Result GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); - static HAPI_Result GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); - static HAPI_Result GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); - static HAPI_Result GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); - static HAPI_Result GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); - static HAPI_Result GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value); - static HAPI_Result GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); - static HAPI_Result GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count); - static HAPI_Result GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); - static HAPI_Result GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); - static HAPI_Result GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); - static HAPI_Result GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); - static HAPI_Result GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status); - static HAPI_Result GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); - static HAPI_Result GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); - static HAPI_Result GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); - static HAPI_Result GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length); - static HAPI_Result GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); - static HAPI_Result GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); - static HAPI_Result GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count); - static HAPI_Result GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); - static HAPI_Result GetTimeEmptyStub(const HAPI_Session * session, float * time); - static HAPI_Result GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); - static HAPI_Result GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - static HAPI_Result GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled); - static HAPI_Result GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); - static HAPI_Result GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport); - static HAPI_Result GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); - static HAPI_Result GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); - static HAPI_Result GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); - static HAPI_Result GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); - static HAPI_Result GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); - static HAPI_Result GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); - static HAPI_Result GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); - static HAPI_Result GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); - static HAPI_Result GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); - static HAPI_Result GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); - static HAPI_Result GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); - static HAPI_Result GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); - static HAPI_Result GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); - static HAPI_Result GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); - static HAPI_HandleBindingInfo HandleBindingInfo_CreateEmptyStub(); - static void HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in); - static HAPI_HandleInfo HandleInfo_CreateEmptyStub(); - static void HandleInfo_InitEmptyStub(HAPI_HandleInfo * in); - static HAPI_ImageFileFormat ImageFileFormat_CreateEmptyStub(); - static void ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in); - static HAPI_ImageInfo ImageInfo_CreateEmptyStub(); - static void ImageInfo_InitEmptyStub(HAPI_ImageInfo * in); - static HAPI_Result InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); - static HAPI_Result InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - static HAPI_Result InterruptEmptyStub(const HAPI_Session * session); - static HAPI_Result IsInitializedEmptyStub(const HAPI_Session * session); - static HAPI_Result IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); - static HAPI_Result IsSessionValidEmptyStub(const HAPI_Session * session); - static HAPI_Keyframe Keyframe_CreateEmptyStub(); - static void Keyframe_InitEmptyStub(HAPI_Keyframe * in); - static HAPI_Result LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - static HAPI_Result LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - static HAPI_Result LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_Result LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); - static HAPI_Result LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); - static HAPI_Result LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); - static HAPI_MaterialInfo MaterialInfo_CreateEmptyStub(); - static void MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in); - static HAPI_Result MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); - static HAPI_NodeInfo NodeInfo_CreateEmptyStub(); - static void NodeInfo_InitEmptyStub(HAPI_NodeInfo * in); - static HAPI_ObjectInfo ObjectInfo_CreateEmptyStub(); - static void ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in); - static HAPI_ParmChoiceInfo ParmChoiceInfo_CreateEmptyStub(); - static void ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in); - static HAPI_Result ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); - static HAPI_Result ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); - static HAPI_ParmInfo ParmInfo_CreateEmptyStub(); - static int ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in); - static int ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in); - static int ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in); - static void ParmInfo_InitEmptyStub(HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in); - static HAPI_PartInfo PartInfo_CreateEmptyStub(); - static int PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - static int PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - static int PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type); - static void PartInfo_InitEmptyStub(HAPI_PartInfo * in); - static HAPI_Result PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - static HAPI_Result PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked); - static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); - static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); - static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle); - static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); - static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); - static HAPI_Result RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id); - static HAPI_Result RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); - static HAPI_Result ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); - static HAPI_Result RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); - static HAPI_Result SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_Result SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); - static HAPI_Result SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); - static HAPI_Result SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_SessionSyncInfo SessionSyncInfo_CreateEmptyStub(); - static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); - static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); - static HAPI_Result SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); - static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); - static HAPI_Result SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); - static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); - static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); - static HAPI_Result SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); - static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); - static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); - static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); - static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); - static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); - static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); - static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); - static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); - static HAPI_Result SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); - static HAPI_Result SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); - static HAPI_Result SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); - static HAPI_Result SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - static HAPI_Result SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); - static HAPI_Result SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); - static HAPI_Result SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); - static HAPI_Result SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); - static HAPI_Result SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); - static HAPI_Result SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - static HAPI_Result SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); - static HAPI_Result SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); - static HAPI_Result SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value); - static HAPI_Result SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value); - static HAPI_Result SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable); - static HAPI_Result SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); - static HAPI_Result SetTimeEmptyStub(const HAPI_Session * session, float time); - static HAPI_Result SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); - static HAPI_Result SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - static HAPI_Result SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled); - static HAPI_Result SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); - static HAPI_Result SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport); - static HAPI_Result SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); - static HAPI_Result SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); - static HAPI_Result SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); - static HAPI_Result SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); - static HAPI_Result SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); - static HAPI_Result SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); - static HAPI_Result SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); - static HAPI_Result SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); - static HAPI_Result StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); - static HAPI_Result StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); - static HAPI_ThriftServerOptions ThriftServerOptions_CreateEmptyStub(); - static void ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in); - static HAPI_TimelineOptions TimelineOptions_CreateEmptyStub(); - static void TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in); - static HAPI_TransformEuler TransformEuler_CreateEmptyStub(); - static void TransformEuler_InitEmptyStub(HAPI_TransformEuler * in); - static HAPI_Transform Transform_CreateEmptyStub(); - static void Transform_InitEmptyStub(HAPI_Transform * in); - static HAPI_Viewport Viewport_CreateEmptyStub(); - static HAPI_VolumeInfo VolumeInfo_CreateEmptyStub(); - static void VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in); - static HAPI_VolumeTileInfo VolumeTileInfo_CreateEmptyStub(); - static void VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in); -}; +/* + * Copyright (c) <2021> 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. + * + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +#pragma once +#include "HAPI/HAPI.h" +#include "HAL/PlatformProcess.h" + + +struct HOUDINIENGINE_API FHoudiniApi +{ +public: + + static void InitializeHAPI(void* LibraryHandle); + static void FinalizeHAPI(); + static bool IsHAPIInitialized(); + +public: + + typedef HAPI_Result (*AddAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*AddGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + typedef HAPI_AssetInfo (*AssetInfo_CreateFuncPtr)(); + typedef void (*AssetInfo_InitFuncPtr)(HAPI_AssetInfo * in); + typedef HAPI_AttributeInfo (*AttributeInfo_CreateFuncPtr)(); + typedef void (*AttributeInfo_InitFuncPtr)(HAPI_AttributeInfo * in); + typedef HAPI_Result (*BindCustomImplementationFuncPtr)(HAPI_SessionType session_type, const char * dll_path); + typedef HAPI_Result (*CancelPDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + typedef HAPI_Result (*CheckForSpecificErrorsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); + typedef HAPI_Result (*CleanupFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*ClearConnectionErrorFuncPtr)(); + typedef HAPI_Result (*CloseSessionFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*CommitGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*CommitWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*ComposeChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + typedef HAPI_Result (*ComposeNodeCookResultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); + typedef HAPI_Result (*ComposeObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + typedef HAPI_CompositorOptions (*CompositorOptions_CreateFuncPtr)(); + typedef void (*CompositorOptions_InitFuncPtr)(HAPI_CompositorOptions * in); + typedef HAPI_Result (*ConnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); + typedef HAPI_Result (*ConvertMatrixToEulerFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + typedef HAPI_Result (*ConvertMatrixToQuatFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); + typedef HAPI_Result (*ConvertTransformFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + typedef HAPI_Result (*ConvertTransformEulerToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); + typedef HAPI_Result (*ConvertTransformQuatToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); + typedef HAPI_Result (*CookNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); + typedef HAPI_Bool (*CookOptions_AreEqualFuncPtr)(const HAPI_CookOptions * left, const HAPI_CookOptions * right); + typedef HAPI_CookOptions (*CookOptions_CreateFuncPtr)(); + typedef void (*CookOptions_InitFuncPtr)(HAPI_CookOptions * in); + typedef HAPI_Result (*CookPDGFuncPtr)(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); + typedef HAPI_Result (*CreateCustomSessionFuncPtr)(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); + typedef HAPI_Result (*CreateHeightFieldInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); + typedef HAPI_Result (*CreateHeightfieldInputVolumeNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); + typedef HAPI_Result (*CreateInProcessSessionFuncPtr)(HAPI_Session * session); + typedef HAPI_Result (*CreateInputNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); + typedef HAPI_Result (*CreateNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); + typedef HAPI_Result (*CreateThriftNamedPipeSessionFuncPtr)(HAPI_Session * session, const char * pipe_name); + typedef HAPI_Result (*CreateThriftSocketSessionFuncPtr)(HAPI_Session * session, const char * host_name, int port); + typedef HAPI_Result (*CreateWorkitemFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); + typedef HAPI_CurveInfo (*CurveInfo_CreateFuncPtr)(); + typedef void (*CurveInfo_InitFuncPtr)(HAPI_CurveInfo * in); + typedef HAPI_Result (*DeleteAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*DeleteGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + typedef HAPI_Result (*DeleteNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*DirtyPDGNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); + typedef HAPI_Result (*DisconnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); + typedef HAPI_Result (*DisconnectNodeOutputsAtFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); + typedef HAPI_Result (*ExtractImageToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); + typedef HAPI_Result (*ExtractImageToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); + typedef HAPI_GeoInfo (*GeoInfo_CreateFuncPtr)(); + typedef int (*GeoInfo_GetGroupCountByTypeFuncPtr)(HAPI_GeoInfo * in, HAPI_GroupType type); + typedef void (*GeoInfo_InitFuncPtr)(HAPI_GeoInfo * in); + typedef HAPI_Result (*GetActiveCacheCountFuncPtr)(const HAPI_Session * session, int * active_cache_count); + typedef HAPI_Result (*GetActiveCacheNamesFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); + typedef HAPI_Result (*GetAssetDefinitionParmCountsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); + typedef HAPI_Result (*GetAssetDefinitionParmInfosFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); + typedef HAPI_Result (*GetAssetDefinitionParmValuesFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); + typedef HAPI_Result (*GetAssetInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); + typedef HAPI_Result (*GetAttributeFloat64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeFloatArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*GetAttributeInt16ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInt64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeIntArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); + typedef HAPI_Result (*GetAttributeStringArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeUInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); + typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); + typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); + typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); + typedef HAPI_Result (*GetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); + typedef HAPI_Result (*GetComposedChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); + typedef HAPI_Result (*GetComposedNodeCookResultFuncPtr)(const HAPI_Session * session, char * string_value, int length); + typedef HAPI_Result (*GetComposedObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); + typedef HAPI_Result (*GetComposedObjectTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + typedef HAPI_Result (*GetCompositorOptionsFuncPtr)(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options); + typedef HAPI_Result (*GetConnectionErrorFuncPtr)(char * string_value, int length, HAPI_Bool clear); + typedef HAPI_Result (*GetConnectionErrorLengthFuncPtr)(int * buffer_length); + typedef HAPI_Result (*GetCookingCurrentCountFuncPtr)(const HAPI_Session * session, int * count); + typedef HAPI_Result (*GetCookingTotalCountFuncPtr)(const HAPI_Session * session, int * count); + typedef HAPI_Result (*GetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); + typedef HAPI_Result (*GetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); + typedef HAPI_Result (*GetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); + typedef HAPI_Result (*GetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); + typedef HAPI_Result (*GetDisplayGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetEdgeCountOfEdgeGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count); + typedef HAPI_Result (*GetEnvIntFuncPtr)(HAPI_EnvIntType int_type, int * value); + typedef HAPI_Result (*GetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); + typedef HAPI_Result (*GetFirstVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + typedef HAPI_Result (*GetGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetGeoSizeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); + typedef HAPI_Result (*GetGroupCountOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); + typedef HAPI_Result (*GetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + typedef HAPI_Result (*GetGroupMembershipOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + typedef HAPI_Result (*GetGroupNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + typedef HAPI_Result (*GetGroupNamesOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + typedef HAPI_Result (*GetHIPFileNodeCountFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, int * count); + typedef HAPI_Result (*GetHIPFileNodeIdsFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); + typedef HAPI_Result (*GetHandleBindingInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); + typedef HAPI_Result (*GetHandleInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); + typedef HAPI_Result (*GetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); + typedef HAPI_Result (*GetImageFilePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); + typedef HAPI_Result (*GetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); + typedef HAPI_Result (*GetImageMemoryBufferFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); + typedef HAPI_Result (*GetImagePlaneCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); + typedef HAPI_Result (*GetImagePlanesFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); + typedef HAPI_Result (*GetInstanceTransformsOnPartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + typedef HAPI_Result (*GetInstancedObjectIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); + typedef HAPI_Result (*GetInstancedPartIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); + typedef HAPI_Result (*GetInstancerPartTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + typedef HAPI_Result (*GetManagerNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); + typedef HAPI_Result (*GetMaterialInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); + typedef HAPI_Result (*GetMaterialNodeIdsOnFacesFuncPtr)(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); + typedef HAPI_Result (*GetNextVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + typedef HAPI_Result (*GetNodeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); + typedef HAPI_Result (*GetNodeInputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); + typedef HAPI_Result (*GetNodeOutputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); + typedef HAPI_Result (*GetNodePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); + typedef HAPI_Result (*GetNumWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * num); + typedef HAPI_Result (*GetObjectInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); + typedef HAPI_Result (*GetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + typedef HAPI_Result (*GetOutputGeoCountFuncPtr)(const HAPI_Session* session, HAPI_NodeId node_id, int* count); + typedef HAPI_Result (*GetOutputGeoInfosFuncPtr)(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count); + typedef HAPI_Result (*GetOutputNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); + typedef HAPI_Result (*GetPDGEventsFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); + typedef HAPI_Result (*GetPDGGraphContextIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); + typedef HAPI_Result (*GetPDGGraphContextsFuncPtr)(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); + typedef HAPI_Result (*GetPDGStateFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); + typedef HAPI_Result (*GetParametersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); + typedef HAPI_Result (*GetParmChoiceListsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); + typedef HAPI_Result (*GetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); + typedef HAPI_Result (*GetParmFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); + typedef HAPI_Result (*GetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); + typedef HAPI_Result (*GetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); + typedef HAPI_Result (*GetParmIdFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); + typedef HAPI_Result (*GetParmInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); + typedef HAPI_Result (*GetParmInfoFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); + typedef HAPI_Result (*GetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); + typedef HAPI_Result (*GetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); + typedef HAPI_Result (*GetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); + typedef HAPI_Result (*GetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); + typedef HAPI_Result (*GetParmStringValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); + typedef HAPI_Result (*GetParmTagNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); + typedef HAPI_Result (*GetParmTagValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); + typedef HAPI_Result (*GetParmWithTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); + typedef HAPI_Result (*GetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); + typedef HAPI_Result (*GetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); + typedef HAPI_Result (*GetPresetBufLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); + typedef HAPI_Result (*GetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int * value); + typedef HAPI_Result (*GetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); + typedef HAPI_Result (*GetServerEnvVarCountFuncPtr)(const HAPI_Session * session, int * env_count); + typedef HAPI_Result (*GetServerEnvVarListFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); + typedef HAPI_Result (*GetSessionEnvIntFuncPtr)(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); + typedef HAPI_Result (*GetSessionSyncInfoFuncPtr)(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); + typedef HAPI_Result (*GetSphereInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); + typedef HAPI_Result (*GetStatusFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, int * status); + typedef HAPI_Result (*GetStatusStringFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); + typedef HAPI_Result (*GetStatusStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); + typedef HAPI_Result (*GetStringFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); + typedef HAPI_Result (*GetStringBatchFuncPtr)(const HAPI_Session * session, char * char_buffer, int char_array_length); + typedef HAPI_Result (*GetStringBatchSizeFuncPtr)(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); + typedef HAPI_Result (*GetStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); + typedef HAPI_Result (*GetSupportedImageFileFormatCountFuncPtr)(const HAPI_Session * session, int * file_format_count); + typedef HAPI_Result (*GetSupportedImageFileFormatsFuncPtr)(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); + typedef HAPI_Result (*GetTimeFuncPtr)(const HAPI_Session * session, float * time); + typedef HAPI_Result (*GetTimelineOptionsFuncPtr)(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); + typedef HAPI_Result (*GetTotalCookCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + typedef HAPI_Result (*GetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool * enabled); + typedef HAPI_Result (*GetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); + typedef HAPI_Result (*GetViewportFuncPtr)(const HAPI_Session * session, HAPI_Viewport * viewport); + typedef HAPI_Result (*GetVolumeBoundsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); + typedef HAPI_Result (*GetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); + typedef HAPI_Result (*GetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); + typedef HAPI_Result (*GetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); + typedef HAPI_Result (*GetVolumeVisualInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); + typedef HAPI_Result (*GetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); + typedef HAPI_Result (*GetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); + typedef HAPI_Result (*GetWorkitemDataLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); + typedef HAPI_Result (*GetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); + typedef HAPI_Result (*GetWorkitemInfoFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); + typedef HAPI_Result (*GetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); + typedef HAPI_Result (*GetWorkitemResultInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); + typedef HAPI_Result (*GetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); + typedef HAPI_Result (*GetWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); + typedef HAPI_HandleBindingInfo (*HandleBindingInfo_CreateFuncPtr)(); + typedef void (*HandleBindingInfo_InitFuncPtr)(HAPI_HandleBindingInfo * in); + typedef HAPI_HandleInfo (*HandleInfo_CreateFuncPtr)(); + typedef void (*HandleInfo_InitFuncPtr)(HAPI_HandleInfo * in); + typedef HAPI_ImageFileFormat (*ImageFileFormat_CreateFuncPtr)(); + typedef void (*ImageFileFormat_InitFuncPtr)(HAPI_ImageFileFormat *in); + typedef HAPI_ImageInfo (*ImageInfo_CreateFuncPtr)(); + typedef void (*ImageInfo_InitFuncPtr)(HAPI_ImageInfo * in); + typedef HAPI_Result (*InitializeFuncPtr)(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); + typedef HAPI_Result (*InsertMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + typedef HAPI_Result (*InterruptFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*IsInitializedFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*IsNodeValidFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); + typedef HAPI_Result (*IsSessionValidFuncPtr)(const HAPI_Session * session); + typedef HAPI_Keyframe (*Keyframe_CreateFuncPtr)(); + typedef void (*Keyframe_InitFuncPtr)(HAPI_Keyframe * in); + typedef HAPI_Result (*LoadAssetLibraryFromFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + typedef HAPI_Result (*LoadAssetLibraryFromMemoryFuncPtr)(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + typedef HAPI_Result (*LoadGeoFromFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_Result (*LoadGeoFromMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); + typedef HAPI_Result (*LoadHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); + typedef HAPI_Result (*LoadNodeFromFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); + typedef HAPI_MaterialInfo (*MaterialInfo_CreateFuncPtr)(); + typedef void (*MaterialInfo_InitFuncPtr)(HAPI_MaterialInfo * in); + typedef HAPI_Result (*MergeHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); + typedef HAPI_NodeInfo (*NodeInfo_CreateFuncPtr)(); + typedef void (*NodeInfo_InitFuncPtr)(HAPI_NodeInfo * in); + typedef HAPI_ObjectInfo (*ObjectInfo_CreateFuncPtr)(); + typedef void (*ObjectInfo_InitFuncPtr)(HAPI_ObjectInfo * in); + typedef HAPI_ParmChoiceInfo (*ParmChoiceInfo_CreateFuncPtr)(); + typedef void (*ParmChoiceInfo_InitFuncPtr)(HAPI_ParmChoiceInfo * in); + typedef HAPI_Result (*ParmHasExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); + typedef HAPI_Result (*ParmHasTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); + typedef HAPI_ParmInfo (*ParmInfo_CreateFuncPtr)(); + typedef int (*ParmInfo_GetFloatValueCountFuncPtr)(const HAPI_ParmInfo * in); + typedef int (*ParmInfo_GetIntValueCountFuncPtr)(const HAPI_ParmInfo * in); + typedef int (*ParmInfo_GetStringValueCountFuncPtr)(const HAPI_ParmInfo* in); + typedef void (*ParmInfo_InitFuncPtr)(HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsFloatFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsIntFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsNodeFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsNonValueFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsPathFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsStringFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_PartInfo (*PartInfo_CreateFuncPtr)(); + typedef int (*PartInfo_GetAttributeCountByOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + typedef int (*PartInfo_GetElementCountByAttributeOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + typedef int (*PartInfo_GetElementCountByGroupTypeFuncPtr)(HAPI_PartInfo * in, HAPI_GroupType type); + typedef void (*PartInfo_InitFuncPtr)(HAPI_PartInfo * in); + typedef HAPI_Result (*PausePDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + typedef HAPI_Result (*PythonThreadInterpreterLockFuncPtr)(const HAPI_Session * session, HAPI_Bool locked); + typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); + typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); + typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); + typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const HAPI_StringHandle string_handle); + typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); + typedef HAPI_Result (*RenderCOPToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId cop_node_id); + typedef HAPI_Result (*RenderTextureToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); + typedef HAPI_Result (*ResetSimulationFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*RevertGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*RevertParmToDefaultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); + typedef HAPI_Result (*RevertParmToDefaultsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); + typedef HAPI_Result (*SaveGeoToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_Result (*SaveGeoToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); + typedef HAPI_Result (*SaveHIPFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); + typedef HAPI_Result (*SaveNodeToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_SessionSyncInfo (*SessionSyncInfo_CreateFuncPtr)(); + typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + typedef HAPI_Result (*SetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); + typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + typedef HAPI_Result (*SetCompositorOptionsFuncPtr)(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options); + typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); + typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); + typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); + typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); + typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); + typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); + typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); + typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); + typedef HAPI_Result (*SetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); + typedef HAPI_Result (*SetNodeDisplayFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); + typedef HAPI_Result (*SetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); + typedef HAPI_Result (*SetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*SetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); + typedef HAPI_Result (*SetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); + typedef HAPI_Result (*SetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); + typedef HAPI_Result (*SetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); + typedef HAPI_Result (*SetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); + typedef HAPI_Result (*SetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*SetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); + typedef HAPI_Result (*SetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); + typedef HAPI_Result (*SetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int value); + typedef HAPI_Result (*SetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, const char * value); + typedef HAPI_Result (*SetSessionSyncFuncPtr)(const HAPI_Session * session, HAPI_Bool enable); + typedef HAPI_Result (*SetSessionSyncInfoFuncPtr)(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); + typedef HAPI_Result (*SetTimeFuncPtr)(const HAPI_Session * session, float time); + typedef HAPI_Result (*SetTimelineOptionsFuncPtr)(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); + typedef HAPI_Result (*SetTransformAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + typedef HAPI_Result (*SetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool enabled); + typedef HAPI_Result (*SetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); + typedef HAPI_Result (*SetViewportFuncPtr)(const HAPI_Session * session, const HAPI_Viewport * viewport); + typedef HAPI_Result (*SetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); + typedef HAPI_Result (*SetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); + typedef HAPI_Result (*SetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); + typedef HAPI_Result (*SetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); + typedef HAPI_Result (*SetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); + typedef HAPI_Result (*SetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); + typedef HAPI_Result (*SetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); + typedef HAPI_Result (*SetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); + typedef HAPI_Result (*StartThriftNamedPipeServerFuncPtr)(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); + typedef HAPI_Result (*StartThriftSocketServerFuncPtr)(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); + typedef HAPI_ThriftServerOptions (*ThriftServerOptions_CreateFuncPtr)(); + typedef void (*ThriftServerOptions_InitFuncPtr)(HAPI_ThriftServerOptions * in); + typedef HAPI_TimelineOptions (*TimelineOptions_CreateFuncPtr)(); + typedef void (*TimelineOptions_InitFuncPtr)(HAPI_TimelineOptions * in); + typedef HAPI_TransformEuler (*TransformEuler_CreateFuncPtr)(); + typedef void (*TransformEuler_InitFuncPtr)(HAPI_TransformEuler * in); + typedef HAPI_Transform (*Transform_CreateFuncPtr)(); + typedef void (*Transform_InitFuncPtr)(HAPI_Transform * in); + typedef HAPI_Viewport (*Viewport_CreateFuncPtr)(); + typedef HAPI_VolumeInfo (*VolumeInfo_CreateFuncPtr)(); + typedef void (*VolumeInfo_InitFuncPtr)(HAPI_VolumeInfo * in); + typedef HAPI_VolumeTileInfo (*VolumeTileInfo_CreateFuncPtr)(); + typedef void (*VolumeTileInfo_InitFuncPtr)(HAPI_VolumeTileInfo * in); + +public: + + static AddAttributeFuncPtr AddAttribute; + static AddGroupFuncPtr AddGroup; + static AssetInfo_CreateFuncPtr AssetInfo_Create; + static AssetInfo_InitFuncPtr AssetInfo_Init; + static AttributeInfo_CreateFuncPtr AttributeInfo_Create; + static AttributeInfo_InitFuncPtr AttributeInfo_Init; + static BindCustomImplementationFuncPtr BindCustomImplementation; + static CancelPDGCookFuncPtr CancelPDGCook; + static CheckForSpecificErrorsFuncPtr CheckForSpecificErrors; + static CleanupFuncPtr Cleanup; + static ClearConnectionErrorFuncPtr ClearConnectionError; + static CloseSessionFuncPtr CloseSession; + static CommitGeoFuncPtr CommitGeo; + static CommitWorkitemsFuncPtr CommitWorkitems; + static ComposeChildNodeListFuncPtr ComposeChildNodeList; + static ComposeNodeCookResultFuncPtr ComposeNodeCookResult; + static ComposeObjectListFuncPtr ComposeObjectList; + static CompositorOptions_CreateFuncPtr CompositorOptions_Create; + static CompositorOptions_InitFuncPtr CompositorOptions_Init; + static ConnectNodeInputFuncPtr ConnectNodeInput; + static ConvertMatrixToEulerFuncPtr ConvertMatrixToEuler; + static ConvertMatrixToQuatFuncPtr ConvertMatrixToQuat; + static ConvertTransformFuncPtr ConvertTransform; + static ConvertTransformEulerToMatrixFuncPtr ConvertTransformEulerToMatrix; + static ConvertTransformQuatToMatrixFuncPtr ConvertTransformQuatToMatrix; + static CookNodeFuncPtr CookNode; + static CookOptions_AreEqualFuncPtr CookOptions_AreEqual; + static CookOptions_CreateFuncPtr CookOptions_Create; + static CookOptions_InitFuncPtr CookOptions_Init; + static CookPDGFuncPtr CookPDG; + static CreateCustomSessionFuncPtr CreateCustomSession; + static CreateHeightFieldInputFuncPtr CreateHeightFieldInput; + static CreateHeightfieldInputVolumeNodeFuncPtr CreateHeightfieldInputVolumeNode; + static CreateInProcessSessionFuncPtr CreateInProcessSession; + static CreateInputNodeFuncPtr CreateInputNode; + static CreateNodeFuncPtr CreateNode; + static CreateThriftNamedPipeSessionFuncPtr CreateThriftNamedPipeSession; + static CreateThriftSocketSessionFuncPtr CreateThriftSocketSession; + static CreateWorkitemFuncPtr CreateWorkitem; + static CurveInfo_CreateFuncPtr CurveInfo_Create; + static CurveInfo_InitFuncPtr CurveInfo_Init; + static DeleteAttributeFuncPtr DeleteAttribute; + static DeleteGroupFuncPtr DeleteGroup; + static DeleteNodeFuncPtr DeleteNode; + static DirtyPDGNodeFuncPtr DirtyPDGNode; + static DisconnectNodeInputFuncPtr DisconnectNodeInput; + static DisconnectNodeOutputsAtFuncPtr DisconnectNodeOutputsAt; + static ExtractImageToFileFuncPtr ExtractImageToFile; + static ExtractImageToMemoryFuncPtr ExtractImageToMemory; + static GeoInfo_CreateFuncPtr GeoInfo_Create; + static GeoInfo_GetGroupCountByTypeFuncPtr GeoInfo_GetGroupCountByType; + static GeoInfo_InitFuncPtr GeoInfo_Init; + static GetActiveCacheCountFuncPtr GetActiveCacheCount; + static GetActiveCacheNamesFuncPtr GetActiveCacheNames; + static GetAssetDefinitionParmCountsFuncPtr GetAssetDefinitionParmCounts; + static GetAssetDefinitionParmInfosFuncPtr GetAssetDefinitionParmInfos; + static GetAssetDefinitionParmValuesFuncPtr GetAssetDefinitionParmValues; + static GetAssetInfoFuncPtr GetAssetInfo; + static GetAttributeFloat64ArrayDataFuncPtr GetAttributeFloat64ArrayData; + static GetAttributeFloat64DataFuncPtr GetAttributeFloat64Data; + static GetAttributeFloatArrayDataFuncPtr GetAttributeFloatArrayData; + static GetAttributeFloatDataFuncPtr GetAttributeFloatData; + static GetAttributeInfoFuncPtr GetAttributeInfo; + static GetAttributeInt16ArrayDataFuncPtr GetAttributeInt16ArrayData; + static GetAttributeInt16DataFuncPtr GetAttributeInt16Data; + static GetAttributeInt64ArrayDataFuncPtr GetAttributeInt64ArrayData; + static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; + static GetAttributeInt8ArrayDataFuncPtr GetAttributeInt8ArrayData; + static GetAttributeInt8DataFuncPtr GetAttributeInt8Data; + static GetAttributeIntArrayDataFuncPtr GetAttributeIntArrayData; + static GetAttributeIntDataFuncPtr GetAttributeIntData; + static GetAttributeNamesFuncPtr GetAttributeNames; + static GetAttributeStringArrayDataFuncPtr GetAttributeStringArrayData; + static GetAttributeStringDataFuncPtr GetAttributeStringData; + static GetAttributeUInt8ArrayDataFuncPtr GetAttributeUInt8ArrayData; + static GetAttributeUInt8DataFuncPtr GetAttributeUInt8Data; + static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; + static GetAvailableAssetsFuncPtr GetAvailableAssets; + static GetBoxInfoFuncPtr GetBoxInfo; + static GetCachePropertyFuncPtr GetCacheProperty; + static GetComposedChildNodeListFuncPtr GetComposedChildNodeList; + static GetComposedNodeCookResultFuncPtr GetComposedNodeCookResult; + static GetComposedObjectListFuncPtr GetComposedObjectList; + static GetComposedObjectTransformsFuncPtr GetComposedObjectTransforms; + static GetCompositorOptionsFuncPtr GetCompositorOptions; + static GetConnectionErrorFuncPtr GetConnectionError; + static GetConnectionErrorLengthFuncPtr GetConnectionErrorLength; + static GetCookingCurrentCountFuncPtr GetCookingCurrentCount; + static GetCookingTotalCountFuncPtr GetCookingTotalCount; + static GetCurveCountsFuncPtr GetCurveCounts; + static GetCurveInfoFuncPtr GetCurveInfo; + static GetCurveKnotsFuncPtr GetCurveKnots; + static GetCurveOrdersFuncPtr GetCurveOrders; + static GetDisplayGeoInfoFuncPtr GetDisplayGeoInfo; + static GetEdgeCountOfEdgeGroupFuncPtr GetEdgeCountOfEdgeGroup; + static GetEnvIntFuncPtr GetEnvInt; + static GetFaceCountsFuncPtr GetFaceCounts; + static GetFirstVolumeTileFuncPtr GetFirstVolumeTile; + static GetGeoInfoFuncPtr GetGeoInfo; + static GetGeoSizeFuncPtr GetGeoSize; + static GetGroupCountOnPackedInstancePartFuncPtr GetGroupCountOnPackedInstancePart; + static GetGroupMembershipFuncPtr GetGroupMembership; + static GetGroupMembershipOnPackedInstancePartFuncPtr GetGroupMembershipOnPackedInstancePart; + static GetGroupNamesFuncPtr GetGroupNames; + static GetGroupNamesOnPackedInstancePartFuncPtr GetGroupNamesOnPackedInstancePart; + static GetHIPFileNodeCountFuncPtr GetHIPFileNodeCount; + static GetHIPFileNodeIdsFuncPtr GetHIPFileNodeIds; + static GetHandleBindingInfoFuncPtr GetHandleBindingInfo; + static GetHandleInfoFuncPtr GetHandleInfo; + static GetHeightFieldDataFuncPtr GetHeightFieldData; + static GetImageFilePathFuncPtr GetImageFilePath; + static GetImageInfoFuncPtr GetImageInfo; + static GetImageMemoryBufferFuncPtr GetImageMemoryBuffer; + static GetImagePlaneCountFuncPtr GetImagePlaneCount; + static GetImagePlanesFuncPtr GetImagePlanes; + static GetInstanceTransformsOnPartFuncPtr GetInstanceTransformsOnPart; + static GetInstancedObjectIdsFuncPtr GetInstancedObjectIds; + static GetInstancedPartIdsFuncPtr GetInstancedPartIds; + static GetInstancerPartTransformsFuncPtr GetInstancerPartTransforms; + static GetManagerNodeIdFuncPtr GetManagerNodeId; + static GetMaterialInfoFuncPtr GetMaterialInfo; + static GetMaterialNodeIdsOnFacesFuncPtr GetMaterialNodeIdsOnFaces; + static GetNextVolumeTileFuncPtr GetNextVolumeTile; + static GetNodeInfoFuncPtr GetNodeInfo; + static GetNodeInputNameFuncPtr GetNodeInputName; + static GetNodeOutputNameFuncPtr GetNodeOutputName; + static GetNodePathFuncPtr GetNodePath; + static GetNumWorkitemsFuncPtr GetNumWorkitems; + static GetObjectInfoFuncPtr GetObjectInfo; + static GetObjectTransformFuncPtr GetObjectTransform; + static GetOutputGeoCountFuncPtr GetOutputGeoCount; + static GetOutputGeoInfosFuncPtr GetOutputGeoInfos; + static GetOutputNodeIdFuncPtr GetOutputNodeId; + static GetPDGEventsFuncPtr GetPDGEvents; + static GetPDGGraphContextIdFuncPtr GetPDGGraphContextId; + static GetPDGGraphContextsFuncPtr GetPDGGraphContexts; + static GetPDGStateFuncPtr GetPDGState; + static GetParametersFuncPtr GetParameters; + static GetParmChoiceListsFuncPtr GetParmChoiceLists; + static GetParmExpressionFuncPtr GetParmExpression; + static GetParmFileFuncPtr GetParmFile; + static GetParmFloatValueFuncPtr GetParmFloatValue; + static GetParmFloatValuesFuncPtr GetParmFloatValues; + static GetParmIdFromNameFuncPtr GetParmIdFromName; + static GetParmInfoFuncPtr GetParmInfo; + static GetParmInfoFromNameFuncPtr GetParmInfoFromName; + static GetParmIntValueFuncPtr GetParmIntValue; + static GetParmIntValuesFuncPtr GetParmIntValues; + static GetParmNodeValueFuncPtr GetParmNodeValue; + static GetParmStringValueFuncPtr GetParmStringValue; + static GetParmStringValuesFuncPtr GetParmStringValues; + static GetParmTagNameFuncPtr GetParmTagName; + static GetParmTagValueFuncPtr GetParmTagValue; + static GetParmWithTagFuncPtr GetParmWithTag; + static GetPartInfoFuncPtr GetPartInfo; + static GetPresetFuncPtr GetPreset; + static GetPresetBufLengthFuncPtr GetPresetBufLength; + static GetServerEnvIntFuncPtr GetServerEnvInt; + static GetServerEnvStringFuncPtr GetServerEnvString; + static GetServerEnvVarCountFuncPtr GetServerEnvVarCount; + static GetServerEnvVarListFuncPtr GetServerEnvVarList; + static GetSessionEnvIntFuncPtr GetSessionEnvInt; + static GetSessionSyncInfoFuncPtr GetSessionSyncInfo; + static GetSphereInfoFuncPtr GetSphereInfo; + static GetStatusFuncPtr GetStatus; + static GetStatusStringFuncPtr GetStatusString; + static GetStatusStringBufLengthFuncPtr GetStatusStringBufLength; + static GetStringFuncPtr GetString; + static GetStringBatchFuncPtr GetStringBatch; + static GetStringBatchSizeFuncPtr GetStringBatchSize; + static GetStringBufLengthFuncPtr GetStringBufLength; + static GetSupportedImageFileFormatCountFuncPtr GetSupportedImageFileFormatCount; + static GetSupportedImageFileFormatsFuncPtr GetSupportedImageFileFormats; + static GetTimeFuncPtr GetTime; + static GetTimelineOptionsFuncPtr GetTimelineOptions; + static GetTotalCookCountFuncPtr GetTotalCookCount; + static GetUseHoudiniTimeFuncPtr GetUseHoudiniTime; + static GetVertexListFuncPtr GetVertexList; + static GetViewportFuncPtr GetViewport; + static GetVolumeBoundsFuncPtr GetVolumeBounds; + static GetVolumeInfoFuncPtr GetVolumeInfo; + static GetVolumeTileFloatDataFuncPtr GetVolumeTileFloatData; + static GetVolumeTileIntDataFuncPtr GetVolumeTileIntData; + static GetVolumeVisualInfoFuncPtr GetVolumeVisualInfo; + static GetVolumeVoxelFloatDataFuncPtr GetVolumeVoxelFloatData; + static GetVolumeVoxelIntDataFuncPtr GetVolumeVoxelIntData; + static GetWorkitemDataLengthFuncPtr GetWorkitemDataLength; + static GetWorkitemFloatDataFuncPtr GetWorkitemFloatData; + static GetWorkitemInfoFuncPtr GetWorkitemInfo; + static GetWorkitemIntDataFuncPtr GetWorkitemIntData; + static GetWorkitemResultInfoFuncPtr GetWorkitemResultInfo; + static GetWorkitemStringDataFuncPtr GetWorkitemStringData; + static GetWorkitemsFuncPtr GetWorkitems; + static HandleBindingInfo_CreateFuncPtr HandleBindingInfo_Create; + static HandleBindingInfo_InitFuncPtr HandleBindingInfo_Init; + static HandleInfo_CreateFuncPtr HandleInfo_Create; + static HandleInfo_InitFuncPtr HandleInfo_Init; + static ImageFileFormat_CreateFuncPtr ImageFileFormat_Create; + static ImageFileFormat_InitFuncPtr ImageFileFormat_Init; + static ImageInfo_CreateFuncPtr ImageInfo_Create; + static ImageInfo_InitFuncPtr ImageInfo_Init; + static InitializeFuncPtr Initialize; + static InsertMultiparmInstanceFuncPtr InsertMultiparmInstance; + static InterruptFuncPtr Interrupt; + static IsInitializedFuncPtr IsInitialized; + static IsNodeValidFuncPtr IsNodeValid; + static IsSessionValidFuncPtr IsSessionValid; + static Keyframe_CreateFuncPtr Keyframe_Create; + static Keyframe_InitFuncPtr Keyframe_Init; + static LoadAssetLibraryFromFileFuncPtr LoadAssetLibraryFromFile; + static LoadAssetLibraryFromMemoryFuncPtr LoadAssetLibraryFromMemory; + static LoadGeoFromFileFuncPtr LoadGeoFromFile; + static LoadGeoFromMemoryFuncPtr LoadGeoFromMemory; + static LoadHIPFileFuncPtr LoadHIPFile; + static LoadNodeFromFileFuncPtr LoadNodeFromFile; + static MaterialInfo_CreateFuncPtr MaterialInfo_Create; + static MaterialInfo_InitFuncPtr MaterialInfo_Init; + static MergeHIPFileFuncPtr MergeHIPFile; + static NodeInfo_CreateFuncPtr NodeInfo_Create; + static NodeInfo_InitFuncPtr NodeInfo_Init; + static ObjectInfo_CreateFuncPtr ObjectInfo_Create; + static ObjectInfo_InitFuncPtr ObjectInfo_Init; + static ParmChoiceInfo_CreateFuncPtr ParmChoiceInfo_Create; + static ParmChoiceInfo_InitFuncPtr ParmChoiceInfo_Init; + static ParmHasExpressionFuncPtr ParmHasExpression; + static ParmHasTagFuncPtr ParmHasTag; + static ParmInfo_CreateFuncPtr ParmInfo_Create; + static ParmInfo_GetFloatValueCountFuncPtr ParmInfo_GetFloatValueCount; + static ParmInfo_GetIntValueCountFuncPtr ParmInfo_GetIntValueCount; + static ParmInfo_GetStringValueCountFuncPtr ParmInfo_GetStringValueCount; + static ParmInfo_InitFuncPtr ParmInfo_Init; + static ParmInfo_IsFloatFuncPtr ParmInfo_IsFloat; + static ParmInfo_IsIntFuncPtr ParmInfo_IsInt; + static ParmInfo_IsNodeFuncPtr ParmInfo_IsNode; + static ParmInfo_IsNonValueFuncPtr ParmInfo_IsNonValue; + static ParmInfo_IsPathFuncPtr ParmInfo_IsPath; + static ParmInfo_IsStringFuncPtr ParmInfo_IsString; + static PartInfo_CreateFuncPtr PartInfo_Create; + static PartInfo_GetAttributeCountByOwnerFuncPtr PartInfo_GetAttributeCountByOwner; + static PartInfo_GetElementCountByAttributeOwnerFuncPtr PartInfo_GetElementCountByAttributeOwner; + static PartInfo_GetElementCountByGroupTypeFuncPtr PartInfo_GetElementCountByGroupType; + static PartInfo_InitFuncPtr PartInfo_Init; + static PausePDGCookFuncPtr PausePDGCook; + static PythonThreadInterpreterLockFuncPtr PythonThreadInterpreterLock; + static QueryNodeInputFuncPtr QueryNodeInput; + static QueryNodeOutputConnectedCountFuncPtr QueryNodeOutputConnectedCount; + static QueryNodeOutputConnectedNodesFuncPtr QueryNodeOutputConnectedNodes; + static RemoveCustomStringFuncPtr RemoveCustomString; + static RemoveMultiparmInstanceFuncPtr RemoveMultiparmInstance; + static RemoveParmExpressionFuncPtr RemoveParmExpression; + static RenameNodeFuncPtr RenameNode; + static RenderCOPToImageFuncPtr RenderCOPToImage; + static RenderTextureToImageFuncPtr RenderTextureToImage; + static ResetSimulationFuncPtr ResetSimulation; + static RevertGeoFuncPtr RevertGeo; + static RevertParmToDefaultFuncPtr RevertParmToDefault; + static RevertParmToDefaultsFuncPtr RevertParmToDefaults; + static SaveGeoToFileFuncPtr SaveGeoToFile; + static SaveGeoToMemoryFuncPtr SaveGeoToMemory; + static SaveHIPFileFuncPtr SaveHIPFile; + static SaveNodeToFileFuncPtr SaveNodeToFile; + static SessionSyncInfo_CreateFuncPtr SessionSyncInfo_Create; + static SetAnimCurveFuncPtr SetAnimCurve; + static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; + static SetAttributeFloatDataFuncPtr SetAttributeFloatData; + static SetAttributeInt16DataFuncPtr SetAttributeInt16Data; + static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; + static SetAttributeInt8DataFuncPtr SetAttributeInt8Data; + static SetAttributeIntDataFuncPtr SetAttributeIntData; + static SetAttributeStringDataFuncPtr SetAttributeStringData; + static SetAttributeUInt8DataFuncPtr SetAttributeUInt8Data; + static SetCachePropertyFuncPtr SetCacheProperty; + static SetCompositorOptionsFuncPtr SetCompositorOptions; + static SetCurveCountsFuncPtr SetCurveCounts; + static SetCurveInfoFuncPtr SetCurveInfo; + static SetCurveKnotsFuncPtr SetCurveKnots; + static SetCurveOrdersFuncPtr SetCurveOrders; + static SetCustomStringFuncPtr SetCustomString; + static SetFaceCountsFuncPtr SetFaceCounts; + static SetGroupMembershipFuncPtr SetGroupMembership; + static SetHeightFieldDataFuncPtr SetHeightFieldData; + static SetImageInfoFuncPtr SetImageInfo; + static SetNodeDisplayFuncPtr SetNodeDisplay; + static SetObjectTransformFuncPtr SetObjectTransform; + static SetParmExpressionFuncPtr SetParmExpression; + static SetParmFloatValueFuncPtr SetParmFloatValue; + static SetParmFloatValuesFuncPtr SetParmFloatValues; + static SetParmIntValueFuncPtr SetParmIntValue; + static SetParmIntValuesFuncPtr SetParmIntValues; + static SetParmNodeValueFuncPtr SetParmNodeValue; + static SetParmStringValueFuncPtr SetParmStringValue; + static SetPartInfoFuncPtr SetPartInfo; + static SetPresetFuncPtr SetPreset; + static SetServerEnvIntFuncPtr SetServerEnvInt; + static SetServerEnvStringFuncPtr SetServerEnvString; + static SetSessionSyncFuncPtr SetSessionSync; + static SetSessionSyncInfoFuncPtr SetSessionSyncInfo; + static SetTimeFuncPtr SetTime; + static SetTimelineOptionsFuncPtr SetTimelineOptions; + static SetTransformAnimCurveFuncPtr SetTransformAnimCurve; + static SetUseHoudiniTimeFuncPtr SetUseHoudiniTime; + static SetVertexListFuncPtr SetVertexList; + static SetViewportFuncPtr SetViewport; + static SetVolumeInfoFuncPtr SetVolumeInfo; + static SetVolumeTileFloatDataFuncPtr SetVolumeTileFloatData; + static SetVolumeTileIntDataFuncPtr SetVolumeTileIntData; + static SetVolumeVoxelFloatDataFuncPtr SetVolumeVoxelFloatData; + static SetVolumeVoxelIntDataFuncPtr SetVolumeVoxelIntData; + static SetWorkitemFloatDataFuncPtr SetWorkitemFloatData; + static SetWorkitemIntDataFuncPtr SetWorkitemIntData; + static SetWorkitemStringDataFuncPtr SetWorkitemStringData; + static StartThriftNamedPipeServerFuncPtr StartThriftNamedPipeServer; + static StartThriftSocketServerFuncPtr StartThriftSocketServer; + static ThriftServerOptions_CreateFuncPtr ThriftServerOptions_Create; + static ThriftServerOptions_InitFuncPtr ThriftServerOptions_Init; + static TimelineOptions_CreateFuncPtr TimelineOptions_Create; + static TimelineOptions_InitFuncPtr TimelineOptions_Init; + static TransformEuler_CreateFuncPtr TransformEuler_Create; + static TransformEuler_InitFuncPtr TransformEuler_Init; + static Transform_CreateFuncPtr Transform_Create; + static Transform_InitFuncPtr Transform_Init; + static Viewport_CreateFuncPtr Viewport_Create; + static VolumeInfo_CreateFuncPtr VolumeInfo_Create; + static VolumeInfo_InitFuncPtr VolumeInfo_Init; + static VolumeTileInfo_CreateFuncPtr VolumeTileInfo_Create; + static VolumeTileInfo_InitFuncPtr VolumeTileInfo_Init; + +public: + + static HAPI_Result AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + static HAPI_Result AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + static HAPI_AssetInfo AssetInfo_CreateEmptyStub(); + static void AssetInfo_InitEmptyStub(HAPI_AssetInfo * in); + static HAPI_AttributeInfo AttributeInfo_CreateEmptyStub(); + static void AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in); + static HAPI_Result BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path); + static HAPI_Result CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + static HAPI_Result CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); + static HAPI_Result CleanupEmptyStub(const HAPI_Session * session); + static HAPI_Result ClearConnectionErrorEmptyStub(); + static HAPI_Result CloseSessionEmptyStub(const HAPI_Session * session); + static HAPI_Result CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + static HAPI_Result ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); + static HAPI_Result ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + static HAPI_CompositorOptions CompositorOptions_CreateEmptyStub(); + static void CompositorOptions_InitEmptyStub(HAPI_CompositorOptions * in); + static HAPI_Result ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); + static HAPI_Result ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + static HAPI_Result ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); + static HAPI_Result ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + static HAPI_Result ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); + static HAPI_Result ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); + static HAPI_Result CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); + static HAPI_Bool CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right); + static HAPI_CookOptions CookOptions_CreateEmptyStub(); + static void CookOptions_InitEmptyStub(HAPI_CookOptions * in); + static HAPI_Result CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); + static HAPI_Result CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); + static HAPI_Result CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); + static HAPI_Result CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); + static HAPI_Result CreateInProcessSessionEmptyStub(HAPI_Session * session); + static HAPI_Result CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); + static HAPI_Result CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); + static HAPI_Result CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name); + static HAPI_Result CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port); + static HAPI_Result CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); + static HAPI_CurveInfo CurveInfo_CreateEmptyStub(); + static void CurveInfo_InitEmptyStub(HAPI_CurveInfo * in); + static HAPI_Result DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + static HAPI_Result DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + static HAPI_Result DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); + static HAPI_Result DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); + static HAPI_Result DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); + static HAPI_Result ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); + static HAPI_Result ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); + static HAPI_GeoInfo GeoInfo_CreateEmptyStub(); + static int GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type); + static void GeoInfo_InitEmptyStub(HAPI_GeoInfo * in); + static HAPI_Result GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count); + static HAPI_Result GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); + static HAPI_Result GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); + static HAPI_Result GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); + static HAPI_Result GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); + static HAPI_Result GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); + static HAPI_Result GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); + static HAPI_Result GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); + static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + static HAPI_Result GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); + static HAPI_Result GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + static HAPI_Result GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); + static HAPI_Result GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); + static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); + static HAPI_Result GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + static HAPI_Result GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); + static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); + static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); + static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); + static HAPI_Result GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); + static HAPI_Result GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); + static HAPI_Result GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length); + static HAPI_Result GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); + static HAPI_Result GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + static HAPI_Result GetCompositorOptionsEmptyStub(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options); + static HAPI_Result GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear); + static HAPI_Result GetConnectionErrorLengthEmptyStub(int * buffer_length); + static HAPI_Result GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count); + static HAPI_Result GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count); + static HAPI_Result GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); + static HAPI_Result GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); + static HAPI_Result GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); + static HAPI_Result GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); + static HAPI_Result GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetEdgeCountOfEdgeGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count); + static HAPI_Result GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value); + static HAPI_Result GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); + static HAPI_Result GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + static HAPI_Result GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); + static HAPI_Result GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); + static HAPI_Result GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + static HAPI_Result GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + static HAPI_Result GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + static HAPI_Result GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + static HAPI_Result GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count); + static HAPI_Result GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); + static HAPI_Result GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); + static HAPI_Result GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); + static HAPI_Result GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); + static HAPI_Result GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); + static HAPI_Result GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); + static HAPI_Result GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); + static HAPI_Result GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); + static HAPI_Result GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); + static HAPI_Result GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + static HAPI_Result GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); + static HAPI_Result GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); + static HAPI_Result GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + static HAPI_Result GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); + static HAPI_Result GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); + static HAPI_Result GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); + static HAPI_Result GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + static HAPI_Result GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); + static HAPI_Result GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); + static HAPI_Result GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); + static HAPI_Result GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); + static HAPI_Result GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num); + static HAPI_Result GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); + static HAPI_Result GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + static HAPI_Result GetOutputGeoCountEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, int* count); + static HAPI_Result GetOutputGeoInfosEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count); + static HAPI_Result GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); + static HAPI_Result GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); + static HAPI_Result GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); + static HAPI_Result GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); + static HAPI_Result GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); + static HAPI_Result GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); + static HAPI_Result GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); + static HAPI_Result GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); + static HAPI_Result GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); + static HAPI_Result GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); + static HAPI_Result GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); + static HAPI_Result GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); + static HAPI_Result GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); + static HAPI_Result GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); + static HAPI_Result GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); + static HAPI_Result GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); + static HAPI_Result GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); + static HAPI_Result GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); + static HAPI_Result GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); + static HAPI_Result GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); + static HAPI_Result GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); + static HAPI_Result GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); + static HAPI_Result GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); + static HAPI_Result GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); + static HAPI_Result GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); + static HAPI_Result GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value); + static HAPI_Result GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); + static HAPI_Result GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count); + static HAPI_Result GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); + static HAPI_Result GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); + static HAPI_Result GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); + static HAPI_Result GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); + static HAPI_Result GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status); + static HAPI_Result GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); + static HAPI_Result GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); + static HAPI_Result GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); + static HAPI_Result GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length); + static HAPI_Result GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); + static HAPI_Result GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); + static HAPI_Result GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count); + static HAPI_Result GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); + static HAPI_Result GetTimeEmptyStub(const HAPI_Session * session, float * time); + static HAPI_Result GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); + static HAPI_Result GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + static HAPI_Result GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled); + static HAPI_Result GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); + static HAPI_Result GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport); + static HAPI_Result GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); + static HAPI_Result GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); + static HAPI_Result GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); + static HAPI_Result GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); + static HAPI_Result GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); + static HAPI_Result GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); + static HAPI_Result GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); + static HAPI_Result GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); + static HAPI_Result GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); + static HAPI_Result GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); + static HAPI_Result GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); + static HAPI_Result GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); + static HAPI_Result GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); + static HAPI_Result GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); + static HAPI_HandleBindingInfo HandleBindingInfo_CreateEmptyStub(); + static void HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in); + static HAPI_HandleInfo HandleInfo_CreateEmptyStub(); + static void HandleInfo_InitEmptyStub(HAPI_HandleInfo * in); + static HAPI_ImageFileFormat ImageFileFormat_CreateEmptyStub(); + static void ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in); + static HAPI_ImageInfo ImageInfo_CreateEmptyStub(); + static void ImageInfo_InitEmptyStub(HAPI_ImageInfo * in); + static HAPI_Result InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); + static HAPI_Result InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + static HAPI_Result InterruptEmptyStub(const HAPI_Session * session); + static HAPI_Result IsInitializedEmptyStub(const HAPI_Session * session); + static HAPI_Result IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); + static HAPI_Result IsSessionValidEmptyStub(const HAPI_Session * session); + static HAPI_Keyframe Keyframe_CreateEmptyStub(); + static void Keyframe_InitEmptyStub(HAPI_Keyframe * in); + static HAPI_Result LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + static HAPI_Result LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + static HAPI_Result LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_Result LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); + static HAPI_Result LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); + static HAPI_Result LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); + static HAPI_MaterialInfo MaterialInfo_CreateEmptyStub(); + static void MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in); + static HAPI_Result MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); + static HAPI_NodeInfo NodeInfo_CreateEmptyStub(); + static void NodeInfo_InitEmptyStub(HAPI_NodeInfo * in); + static HAPI_ObjectInfo ObjectInfo_CreateEmptyStub(); + static void ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in); + static HAPI_ParmChoiceInfo ParmChoiceInfo_CreateEmptyStub(); + static void ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in); + static HAPI_Result ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); + static HAPI_Result ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); + static HAPI_ParmInfo ParmInfo_CreateEmptyStub(); + static int ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in); + static int ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in); + static int ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in); + static void ParmInfo_InitEmptyStub(HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in); + static HAPI_PartInfo PartInfo_CreateEmptyStub(); + static int PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + static int PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + static int PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type); + static void PartInfo_InitEmptyStub(HAPI_PartInfo * in); + static HAPI_Result PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + static HAPI_Result PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked); + static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); + static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); + static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); + static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle); + static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); + static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); + static HAPI_Result RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id); + static HAPI_Result RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); + static HAPI_Result ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); + static HAPI_Result RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); + static HAPI_Result SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_Result SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); + static HAPI_Result SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); + static HAPI_Result SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_SessionSyncInfo SessionSyncInfo_CreateEmptyStub(); + static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); + static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + static HAPI_Result SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); + static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + static HAPI_Result SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); + static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); + static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + static HAPI_Result SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); + static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + static HAPI_Result SetCompositorOptionsEmptyStub(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options); + static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); + static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); + static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); + static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); + static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); + static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); + static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); + static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); + static HAPI_Result SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); + static HAPI_Result SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); + static HAPI_Result SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); + static HAPI_Result SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + static HAPI_Result SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); + static HAPI_Result SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); + static HAPI_Result SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); + static HAPI_Result SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); + static HAPI_Result SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); + static HAPI_Result SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + static HAPI_Result SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); + static HAPI_Result SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); + static HAPI_Result SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value); + static HAPI_Result SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value); + static HAPI_Result SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable); + static HAPI_Result SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); + static HAPI_Result SetTimeEmptyStub(const HAPI_Session * session, float time); + static HAPI_Result SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); + static HAPI_Result SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + static HAPI_Result SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled); + static HAPI_Result SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); + static HAPI_Result SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport); + static HAPI_Result SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); + static HAPI_Result SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); + static HAPI_Result SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); + static HAPI_Result SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); + static HAPI_Result SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); + static HAPI_Result SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); + static HAPI_Result SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); + static HAPI_Result SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); + static HAPI_Result StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); + static HAPI_Result StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); + static HAPI_ThriftServerOptions ThriftServerOptions_CreateEmptyStub(); + static void ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in); + static HAPI_TimelineOptions TimelineOptions_CreateEmptyStub(); + static void TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in); + static HAPI_TransformEuler TransformEuler_CreateEmptyStub(); + static void TransformEuler_InitEmptyStub(HAPI_TransformEuler * in); + static HAPI_Transform Transform_CreateEmptyStub(); + static void Transform_InitEmptyStub(HAPI_Transform * in); + static HAPI_Viewport Viewport_CreateEmptyStub(); + static HAPI_VolumeInfo VolumeInfo_CreateEmptyStub(); + static void VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in); + static HAPI_VolumeTileInfo VolumeTileInfo_CreateEmptyStub(); + static void VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in); +}; diff --git a/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs b/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs index 07457e320..91c671201 100644 --- a/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs +++ b/Source/HoudiniEngineEditor/HoudiniEngineEditor.Build.cs @@ -1,119 +1,119 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using UnrealBuildTool; -using System; -using System.IO; - -public class HoudiniEngineEditor : ModuleRules -{ - public HoudiniEngineEditor( ReadOnlyTargetRules Target ) : base( Target ) - { - bPrecompile = true; - PCHUsage = PCHUsageMode.NoSharedPCHs; - PrivatePCHHeaderFile = "Private/HoudiniEngineEditorPrivatePCH.h"; - - // Check if we are compiling on unsupported platforms. - if ( Target.Platform != UnrealTargetPlatform.Win64 && - Target.Platform != UnrealTargetPlatform.Mac && - Target.Platform != UnrealTargetPlatform.Linux ) - { - string Err = string.Format( "Houdini Engine Editor: Compiling for unsupported platform." ); - System.Console.WriteLine( Err ); - throw new BuildException( Err ); - } - - PublicIncludePaths.AddRange( - new string[] { - Path.Combine(ModuleDirectory, "Public") - } - ); - - PrivateIncludePaths.AddRange( - new string[] { - "HoudiniEngine/Private", - "HoudiniEngineRuntime/Private" - } - ); - - PrivateIncludePathModuleNames.AddRange( - new string[] { - "PlacementMode" - } - ); - - // Add common dependencies. - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "HoudiniEngine", - "HoudiniEngineRuntime", - "Slate", - "SlateCore", - "Landscape", - "Foliage" - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "AppFramework", - "AssetTools", - "ContentBrowser", - "DesktopWidgets", - "EditorStyle", - "EditorWidgets", - "Engine", - "InputCore", - "LevelEditor", - "MainFrame", - "Projects", - "PropertyEditor", - "RHI", - "RawMesh", - "RenderCore", - "TargetPlatform", - "UnrealEd", - "ApplicationCore", - "CurveEditor", - "Json", - "SceneOutliner", - "PropertyPath", - "MaterialEditor" - } - ); - - DynamicallyLoadedModuleNames.AddRange( - new string[] - { - "PlacementMode", - } - ); - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using UnrealBuildTool; +using System; +using System.IO; + +public class HoudiniEngineEditor : ModuleRules +{ + public HoudiniEngineEditor( ReadOnlyTargetRules Target ) : base( Target ) + { + bPrecompile = true; + PCHUsage = PCHUsageMode.NoSharedPCHs; + PrivatePCHHeaderFile = "Private/HoudiniEngineEditorPrivatePCH.h"; + + // Check if we are compiling on unsupported platforms. + if ( Target.Platform != UnrealTargetPlatform.Win64 && + Target.Platform != UnrealTargetPlatform.Mac && + Target.Platform != UnrealTargetPlatform.Linux ) + { + string Err = string.Format( "Houdini Engine Editor: Compiling for unsupported platform." ); + System.Console.WriteLine( Err ); + throw new BuildException( Err ); + } + + PublicIncludePaths.AddRange( + new string[] { + Path.Combine(ModuleDirectory, "Public") + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "HoudiniEngine/Private", + "HoudiniEngineRuntime/Private" + } + ); + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "PlacementMode" + } + ); + + // Add common dependencies. + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "HoudiniEngine", + "HoudiniEngineRuntime", + "Slate", + "SlateCore", + "Landscape", + "Foliage" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "AppFramework", + "AssetTools", + "ContentBrowser", + "DesktopWidgets", + "EditorStyle", + "EditorWidgets", + "Engine", + "InputCore", + "LevelEditor", + "MainFrame", + "Projects", + "PropertyEditor", + "RHI", + "RawMesh", + "RenderCore", + "TargetPlatform", + "UnrealEd", + "ApplicationCore", + "CurveEditor", + "Json", + "SceneOutliner", + "PropertyPath", + "MaterialEditor" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + "PlacementMode", + } + ); + } +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp index 149911518..fdc3ed96c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp @@ -174,7 +174,8 @@ FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( case EHoudiniEngineBakeOption::ToFoliage: { - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake); + TMap AlreadyBakedMaterialsMap; + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake, AlreadyBakedMaterialsMap); } break; @@ -340,6 +341,8 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( // from the other outputs. bool bHasAnyInstancers = false; int32 NumProcessedOutputs = 0; + + TMap AlreadyBakedMaterialsMap; for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) { UHoudiniOutput* Output = InOutputs[OutputIdx]; @@ -376,6 +379,7 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( bInReplaceAssets, BakedActors, OutPackagesToSave, + AlreadyBakedMaterialsMap, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -460,6 +464,7 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( bInReplaceAssets, BakedActors, OutPackagesToSave, + AlreadyBakedMaterialsMap, InInstancerComponentTypesToBake, InFallbackActor, InFallbackWorldOutlinerFolder); @@ -507,7 +512,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( bool bInReplaceActors, bool bInReplaceAssets, TArray& OutActors, - TArray& OutPackagesToSave) + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap) { UHoudiniOutput* Output = InAllOutputs[InOutputIndex]; if (!Output || Output->IsPendingKill()) @@ -563,7 +569,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( // This will bake/duplicate the mesh if temporary, or return the input one if it is not BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); } else { @@ -761,7 +767,7 @@ FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComp } bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) +FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, TMap& InOutAlreadyBakedMaterialsMap) { if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) return false; @@ -824,7 +830,8 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* Houdi bInReplaceActors, bInReplaceAssets, BakedResults, - PackagesToSave); + PackagesToSave, + InOutAlreadyBakedMaterialsMap); } // Update the cached baked output data @@ -857,6 +864,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( bool bInReplaceAssets, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, TArray const* InInstancerComponentTypesToBake, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) @@ -915,7 +923,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( bInReplaceActors, bInReplaceAssets, OutActors, - OutPackagesToSave); + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap); } else if (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent)) @@ -935,6 +944,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( bInReplaceAssets, OutActors, OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -957,6 +967,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( bInReplaceAssets, OutActors, OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -993,6 +1004,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( bInReplaceAssets, OutActors, OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -1013,6 +1025,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( bInReplaceAssets, OutActors, OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -1045,6 +1058,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( bool bInReplaceAssets, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { @@ -1088,7 +1102,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( // This will bake/duplicate the mesh if temporary, or return the input one if it is not BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); } else { @@ -1187,7 +1201,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( if (!FoundActor) { - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform, RF_Transactional); + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform); if (!FoundActor || FoundActor->IsPendingKill()) continue; } @@ -1365,6 +1379,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( bool bInReplaceAssets, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { @@ -1408,7 +1423,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( // This will bake/duplicate the mesh if temporary, or return the input one if it is not BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); } else { @@ -1482,7 +1497,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( if (!SMFactory) return false; - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform()); if (!FoundActor || FoundActor->IsPendingKill()) return false; @@ -1742,6 +1757,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( bool bInReplaceAssets, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { @@ -1785,7 +1801,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( // This will bake/duplicate the mesh if temporary, or return the input one if it is not BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave); + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); } else { @@ -2056,6 +2072,7 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( bool bInReplaceAssets, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { @@ -2110,52 +2127,30 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( if (FoundHGPO && FoundHGPO->bIsTemplated) continue; - FHoudiniAttributeResolver Resolver; const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; ULevel* DesiredLevel = GWorld->GetCurrentLevel(); FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, - InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); - - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); - // See if this output object has an unreal_level_path attribute specified - // In which case, we need to create/find the desired level for baking instead of using the current one - bool bHasLevelPathAttribute = OutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); + + if (!ResolvePackageParams( + HoudiniAssetComponent, + InOutput, + Identifier, + OutputObject, + InHoudiniAssetName, + DefaultObjectName, + InBakeFolder, + bInReplaceAssets, + PackageParams, + OutPackagesToSave)) { - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - continue; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add the level to the packages to save? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } + continue; } - + // Bake the static mesh if it is still temporary UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( StaticMesh, @@ -2164,7 +2159,8 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( InAllOutputs, OutActors, InTempCookFolder.Path, - OutPackagesToSave); + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap); if (!BakedSM || BakedSM->IsPendingKill()) continue; @@ -2187,7 +2183,7 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( if (!FoundActor) { // Spawn the new actor - FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform(), RF_Transactional); + FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform()); if (!FoundActor || FoundActor->IsPendingKill()) continue; @@ -2262,6 +2258,64 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( return true; } +bool FHoudiniEngineBakeUtils::ResolvePackageParams( + const UHoudiniAssetComponent* HoudiniAssetComponent, + UHoudiniOutput* InOutput, + const FHoudiniOutputObjectIdentifier& Identifier, + const FHoudiniOutputObject& InOutputObject, + const FString& InHoudiniAssetName, + const FString& DefaultObjectName, + const FDirectoryPath& InBakeFolder, + const bool bInReplaceAssets, + FHoudiniPackageParams& OutPackageParams, + TArray& OutPackagesToSave) +{ + FHoudiniAttributeResolver Resolver; + + UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, Identifier, InOutputObject, DefaultObjectName, + InHoudiniAssetName, OutPackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); + + + // See if this output object has an unreal_level_path attribute specified + // In which case, we need to create/find the desired level for baking instead of using the current one + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add the level to the packages to save? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + return true; +} + bool FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( const UHoudiniAssetComponent* HoudiniAssetComponent, @@ -2531,7 +2585,8 @@ FHoudiniEngineBakeUtils::BakeStaticMesh( UStaticMesh * StaticMesh, const FHoudiniPackageParams& PackageParams, const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder) + const FDirectoryPath& InTempCookFolder, + TMap& InOutAlreadyBakedMaterialsMap) { if (!StaticMesh || StaticMesh->IsPendingKill()) return nullptr; @@ -2540,7 +2595,7 @@ FHoudiniEngineBakeUtils::BakeStaticMesh( TArray Outputs; const TArray BakedResults; UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave); + StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave, InOutAlreadyBakedMaterialsMap); if (BakedStaticMesh) { @@ -2715,42 +2770,60 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor(); const bool bHasSharedLandscape = SharedLandscapeActor != TileActor; - const bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; + bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; + if (bHasPreviousSharedLandscape) + { + // Ignore the previous shared landscape if the world's are different + // Typically in baking we treat completely different asset/output names in a bake as detached from the "previous" bake + if (PreviousSharedLandscapeActor->GetWorld() != SharedLandscapeActor->GetWorld()) + bHasPreviousSharedLandscape = false; + } + bool bLandscapeReplaced = false; if (bHasSharedLandscape) { // If we are baking in replace mode and we have a previous shared landscape actor, use the name of that // actor - const FString DesiredSharedLandscapeName = bHasPreviousSharedLandscape && bInReplaceActors - ? PreviousSharedLandscapeActor->GetName() - : InResolver.ResolveAttribute( - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, - SharedLandscapeActor->GetName()); - - // If we are not baking in replacement mode, create a unique name if the name is already in use - const FString SharedLandscapeName = !bInReplaceActors - ? MakeUniqueObjectNameIfNeeded(SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *DesiredSharedLandscapeName) - : DesiredSharedLandscapeName; - - if (SharedLandscapeActor->GetName() != SharedLandscapeName) + FString SharedLandscapeName = InResolver.ResolveAttribute( + HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + SharedLandscapeActor->GetName()); + + // If the shared landscape is still attached, or it's base name does not match the desired name, "bake" it + AActor* const AttachedParent = SharedLandscapeActor->GetAttachParentActor(); + if (AttachedParent || SharedLandscapeActor->GetFName().GetPlainNameString() != SharedLandscapeName) { - AActor* FoundActor = nullptr; - ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); - if (ExistingLandscape && bInReplaceActors) + if (bHasPreviousSharedLandscape && bInReplaceActors && + PreviousSharedLandscapeActor->GetFName().GetPlainNameString() == SharedLandscapeName) + { + SharedLandscapeName = PreviousSharedLandscapeActor->GetName(); + } + else if (!bInReplaceActors) { - // Even though we found an existing landscape with the desired type, we're just going to destroy/replace - // it for now. - FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); - ExistingLandscape->Destroy(); - bLandscapeReplaced = true; + // If we are not baking in replacement mode, create a unique name if the name is already in use + SharedLandscapeName = MakeUniqueObjectNameIfNeeded( + SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *SharedLandscapeName, SharedLandscapeActor); } + + if (SharedLandscapeActor->GetName() != SharedLandscapeName) + { + AActor* FoundActor = nullptr; + ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); + if (ExistingLandscape && bInReplaceActors) + { + // Even though we found an existing landscape with the desired type, we're just going to destroy/replace + // it for now. + FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); + ExistingLandscape->Destroy(); + bLandscapeReplaced = true; + } - // Fix name of shared landscape - FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); + // Fix name of shared landscape + FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); + } + + SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); } - - SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); } // Find the world where the landscape tile should be placed. @@ -2765,17 +2838,16 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( if (bHasLevelPathAttribute) PackagePath = InResolver.ResolveFullLevelPath(); - if (bInReplaceActors) - { - // If we are baking in replace mode: get the previous baked actor (if available) name, but only if it is in the - // same target level - if (IsValid(PreviousTileActor)) + // Get the previous baked actor (if available) name, but only if it is in the + // same target level, and it's plain name (no numeric suffix) matches ActorName + // In replacement mode we'll then replace the previous tile actor. + if (bInReplaceActors && IsValid(PreviousTileActor)) + { + UPackage* PreviousPackage = PreviousTileActor->GetPackage(); + if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath && + PreviousTileActor->GetFName().GetPlainNameString() == ActorName) { - UPackage* PreviousPackage = PreviousTileActor->GetPackage(); - if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath) - { - ActorName = PreviousTileActor->GetName(); - } + ActorName = PreviousTileActor->GetName(); } } @@ -2811,7 +2883,7 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( else { // incremental, keep existing actor and create a unique name for the new one - ActorName = MakeUniqueObjectName(TargetActor->GetOuter(), TargetActor->GetClass(), *ActorName).ToString(); + ActorName = MakeUniqueObjectNameIfNeeded(TargetActor->GetOuter(), TargetActor->GetClass(), ActorName, TileActor); } TargetActor = nullptr; } @@ -2874,6 +2946,99 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( // InBakedOutputObject.BakedObject = nullptr; // } + // Bake the landscape layer uassets + ULandscapeInfo* const LandscapeInfo = TileActor->GetLandscapeInfo(); + if (IsValid(LandscapeInfo) && LandscapeInfo->Layers.Num() > 0) + { + TSet TempLayers; + const int32 NumLayers = LandscapeInfo->Layers.Num(); + TempLayers.Reserve(NumLayers); + for (int32 LayerIndex = 0; LayerIndex < NumLayers; ++LayerIndex) + { + const FLandscapeInfoLayerSettings& Layer = LandscapeInfo->Layers[LayerIndex]; + if (!IsValid(Layer.LayerInfoObj)) + continue; + + if (!IsObjectInTempFolder(Layer.LayerInfoObj, PackageParams.TempCookFolder)) + continue; + + if (!TempLayers.Contains(Layer.LayerInfoObj)) + TempLayers.Add(Layer.LayerInfoObj); + } + + // Setup package params to duplicate each layer + FHoudiniPackageParams LayerPackageParams = PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + LayerPackageParams.ReplaceMode = AssetPackageReplaceMode; + + // Determine the final bake name of the "owning" landscape (shared landscape in tiled mode, or just the + // landscape actor itself in non-tiled mode + FString OwningLandscapeActorBakeName; + if (bHasSharedLandscape && IsValid(SharedLandscapeActor)) + { + SharedLandscapeActor->GetName(OwningLandscapeActorBakeName); + } + else + { + TileActor->GetName(OwningLandscapeActorBakeName); + } + + // Keep track of the landscape layers we are baking this time around, and replace in the baked output object + // at the end. + TMap ThisBakedLandscapeLayers; + + // Bake/duplicate temp layers and replace temp layers via LandscapeInfo + for (ULandscapeLayerInfoObject* const LayerInfo : TempLayers) + { + const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerInfo->LayerName.ToString()); + LayerPackageParams.SplitStr = SanitizedLayerName; + LayerPackageParams.ObjectName = OwningLandscapeActorBakeName + TEXT("_layer_") + SanitizedLayerName; + + // Get the previously baked layer info for this layer, if any + ULandscapeLayerInfoObject* const PreviousBakedLayerInfo = InBakedOutputObject.GetLandscapeLayerInfoIfValid( + LayerInfo->LayerName); + + // If our name is the base name (no number) of the previous, then we can fetch the bake counter for + // replacement / incrementing from it + int32 BakeCounter = 0; + if (IsValid(PreviousBakedLayerInfo) && LayerPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakedLayerInfo)) + { + // Get the bake counter from the previous bake + FHoudiniPackageParams::GetBakeCounterFromBakedAsset(PreviousBakedLayerInfo, BakeCounter); + } + + FString LayerPackageName; + UPackage* const LayerPackage = LayerPackageParams.CreatePackageForObject(LayerPackageName, BakeCounter); + if (IsValid(LayerPackage)) + { + BakeStats.NotifyPackageCreated(1); + ULandscapeLayerInfoObject* BakedLayer = DuplicateObject( + LayerInfo, LayerPackage, *LayerPackageName); + if (IsValid(BakedLayer)) + { + OutPackagesToSave.Add(LayerPackage); + + // Trigger update of the Layer Info + BakedLayer->PreEditChange(nullptr); + BakedLayer->PostEditChange(); + BakedLayer->MarkPackageDirty(); + + // Mark the package dirty... + LayerPackage->MarkPackageDirty(); + + LandscapeInfo->ReplaceLayer(LayerInfo, BakedLayer); + + // Record as the new baked result for the LayerName + ThisBakedLandscapeLayers.Add(LayerInfo->LayerName, FSoftObjectPath(BakedLayer).ToString()); + } + } + } + + // Update the baked landscape layers in InBakedOutputObject + InBakedOutputObject.LandscapeLayers = ThisBakedLandscapeLayers; + } + // Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks InOutputObject.OutputObject = nullptr; @@ -2904,7 +3069,8 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( const TArray& InParentOutputs, const TArray& InCurrentBakedActors, const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages) + TArray & OutCreatedPackages, + TMap& InOutAlreadyBakedMaterialsMap) { if (!InStaticMesh || InStaticMesh->IsPendingKill()) return nullptr; @@ -3027,7 +3193,7 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( } // Duplicate material resource. UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages); + Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages, InOutAlreadyBakedMaterialsMap); if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) continue; @@ -3248,7 +3414,7 @@ FHoudiniEngineBakeUtils::BakeCurve( if (!Factory) return false; - OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform(), RF_Transactional); + OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform()); } else { @@ -3419,7 +3585,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( } } - AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform(), RF_Transactional); + AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform()); USplineComponent* BakedUnrealSplineComponent = NewObject(NewActor); if (!BakedUnrealSplineComponent) @@ -3587,8 +3753,14 @@ GetHoudiniGeneratedNameFromMetaInformation( UMaterial * FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( UMaterial * Material, UMaterial* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutGeneratedPackages) + TArray & OutGeneratedPackages, + TMap& InOutAlreadyBakedMaterialsMap) { + if (InOutAlreadyBakedMaterialsMap.Contains(Material)) + { + return InOutAlreadyBakedMaterialsMap[Material]; + } + UMaterial * DuplicatedMaterial = nullptr; FString CreatedMaterialName; @@ -3657,6 +3829,8 @@ FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( OutGeneratedPackages.Add(MaterialPackage); + InOutAlreadyBakedMaterialsMap.Add(Material, DuplicatedMaterial); + return DuplicatedMaterial; } @@ -3877,17 +4051,11 @@ FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, EHoudiniOutputType return IsObjectTemporary(InObject, InOutputType, Outputs, TempPath); } -bool FHoudiniEngineBakeUtils::IsObjectTemporary( - UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) +bool FHoudiniEngineBakeUtils::IsObjectInTempFolder(UObject* const InObject, const FString& InTemporaryCookFolder) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; - int32 ParentOutputIndex = -1; - FHoudiniOutputObjectIdentifier Identifier; - if (FindOutputObject(InObject, InOutputType, InParentOutputs, ParentOutputIndex, Identifier)) - return true; - // Check the package path for this object // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated UPackage* ObjectPackage = InObject->GetOutermost(); @@ -3901,8 +4069,31 @@ bool FHoudiniEngineBakeUtils::IsObjectTemporary( const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder)) return true; - - /* + } + + return false; +} + +bool FHoudiniEngineBakeUtils::IsObjectTemporary( + UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + int32 ParentOutputIndex = -1; + FHoudiniOutputObjectIdentifier Identifier; + if (FindOutputObject(InObject, InOutputType, InParentOutputs, ParentOutputIndex, Identifier)) + return true; + + // Check the package path for this object + // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated + if (IsObjectInTempFolder(InObject, InTemporaryCookFolder)) + return true; + + /* + UPackage* ObjectPackage = InObject->GetOutermost(); + if (ObjectPackage && !ObjectPackage->IsPendingKill()) + { // TODO: this just indicates that the object was generated by H // it could as well have been baked before... // we should probably add a "temp" metadata @@ -3913,8 +4104,8 @@ bool FHoudiniEngineBakeUtils::IsObjectTemporary( if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) return true; - */ } + */ return false; } @@ -3980,7 +4171,7 @@ FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent( { HOUDINI_LOG_WARNING( TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), - *(ComponentClass->GetName()), + *(InSMC->GetName()), *(NewSMC->GetClass()->GetName())); NewSMC->PostEditChange(); @@ -5644,4 +5835,4 @@ FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( return NumDeleted; } -#undef LOCTEXT_NAMESPACE \ No newline at end of file +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h index d03c95e1f..6a4017832 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h @@ -195,7 +195,8 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils UStaticMesh * StaticMesh, const FHoudiniPackageParams & PackageParams, const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder); + const FDirectoryPath& InTempCookFolder, + TMap& InOutAlreadyBakedMaterialsMap); static bool BakeLandscape( const UHoudiniAssetComponent* HoudiniAssetComponent, @@ -232,6 +233,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplaceAssets, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, TArray const* InInstancerComponentTypesToBake=nullptr, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); @@ -251,6 +253,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplaceAssets, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); @@ -281,6 +284,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplaceAssets, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); @@ -298,6 +302,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplaceAssets, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); @@ -308,14 +313,16 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const TArray& InParentOutputs, const TArray& InCurrentBakedActors, const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages); + TArray & OutCreatedPackages, + TMap& InOutAlreadyBakedMaterialsMap); static UMaterial * DuplicateMaterialAndCreatePackage( UMaterial * Material, UMaterial* PreviousBakeMaterial, const FString & SubMaterialName, const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutCreatedPackages); + TArray & OutCreatedPackages, + TMap& InOutAlreadyBakedMaterialsMap); static void ReplaceDuplicatedMaterialTextureSample( UMaterialExpression * MaterialExpression, @@ -386,11 +393,12 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplaceActors, bool bInReplaceAssets, TArray& OutActors, - TArray& OutPackagesToSave); + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap); static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); - static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); + static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, TMap& InOutAlreadyBakedMaterialsMap); static bool BakeStaticMeshOutputToActors( const UHoudiniAssetComponent* HoudiniAssetComponent, @@ -404,9 +412,22 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplaceAssets, TArray& OutActors, TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); + static bool ResolvePackageParams( + const UHoudiniAssetComponent* HoudiniAssetComponent, + UHoudiniOutput* InOutput, + const FHoudiniOutputObjectIdentifier& Identifier, + const FHoudiniOutputObject& InOutputObject, + const FString& InHoudiniAssetName, + const FString& DefaultObjectName, + const FDirectoryPath& InBakeFolder, + const bool bInReplaceAssets, + FHoudiniPackageParams& OutPackageParams, + TArray& OutPackagesToSave); + static bool BakeHoudiniCurveOutputToActors( const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, @@ -460,6 +481,10 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils static bool IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC); + // Returns true if InObject is in InTemporaryCookFolder, or in the default Temporary cook folder from the runtime + // settings. + static bool IsObjectInTempFolder(UObject* const InObject, const FString& InTemporaryCookFolder); + static bool IsObjectTemporary( UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp index 08ab3dfaa..6b159a8a9 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp @@ -185,9 +185,11 @@ FHoudiniEngineCommands::OpenInHoudini() // Add quotes to the path to avoid issues with spaces UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); + // Then open the hip file in Houdini FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + TEXT("//houdini"); + FString HoudiniExecutable = FHoudiniEngine::Get().GetHoudiniExecutable(); + FString HoudiniLocation = LibHAPILocation + TEXT("//") + HoudiniExecutable; FProcHandle ProcHandle = FPlatformProcess::CreateProc( *HoudiniLocation, @@ -996,7 +998,9 @@ FHoudiniEngineCommands::OpenSessionSync() // Treat an unknown platform the same as Windows for now const FString HoudiniExeLocationRelativeToLibHAPI; # endif - FString HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/houdini"); + + FString HoudiniExecutable = FHoudiniEngine::Get().GetHoudiniExecutable(); + FString HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/") + HoudiniExecutable; HOUDINI_LOG_MESSAGE(TEXT("Path to houdini executable: %s"), *HoudiniLocation); FProcHandle HESSHandle = FPlatformProcess::CreateProc( *HoudiniLocation, diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp index 1e737864c..dd64d788d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp @@ -2108,7 +2108,7 @@ FHoudiniOutputDetails::GetOutputDebugDescription(UHoudiniOutput* InOutput) UObject* OutComp = Iter.Value.OutputComponent; if (OutComp) { - OutputValStr += OutObject->GetFullName() + TEXT(" (comp)\n"); + OutputValStr += OutComp->GetFullName() + TEXT(" (comp)\n"); } } } @@ -3582,8 +3582,9 @@ FHoudiniOutputDetails::OnBakeOutputObject( { FDirectoryPath TempCookFolderPath; TempCookFolderPath.Path = TempCookFolder; + TMap AlreadyBakedMaterialsMap; UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( - StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath); + StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath, AlreadyBakedMaterialsMap); } } break; @@ -3652,4 +3653,4 @@ FHoudiniOutputDetails::OnRevertBakeNameToDefault(UHoudiniOutput * InOutput, cons FoundOutputObject->BakeName = FString(); } -#undef LOCTEXT_NAMESPACE \ No newline at end of file +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h index 55f37c1d6..0c121a712 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h @@ -54,7 +54,7 @@ struct FHoudiniOutputObject; enum class EHoudiniOutputType : uint8; enum class EHoudiniLandscapeOutputBakeType : uint8; -class FHoudiniOutputDetails : public TSharedFromThis +class FHoudiniOutputDetails : public TSharedFromThis { public: void CreateWidget( diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp index 4643f07c9..35c98adca 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp @@ -3398,8 +3398,7 @@ FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParamete UHoudiniParameterColor* MainParam = ColorParams[0]; if (!MainParam || MainParam->IsPendingKill()) return; - - // Create a new detail row + // Create a new detail row FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); if (!Row) @@ -3418,54 +3417,46 @@ FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParamete SAssignNew(ColorBlock, SColorBlock) .Color(MainParam->GetColorValue()) .ShowBackgroundForAlpha(bHasAlpha) - ]; - - TWeakPtr WeakColorBlock(ColorBlock); - ColorBlock->SetOnMouseButtonDown(FPointerEventHandler::CreateLambda( - [MainParam, ColorParams, WeakColorBlock, bHasAlpha](const FGeometry & MyGeometry, const FPointerEvent & MouseEvent) - { - if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) - return FReply::Unhandled(); - - TSharedPtr ColorBlockPtr = WeakColorBlock.Pin(); - FColorPickerArgs PickerArgs; - PickerArgs.ParentWidget = ColorBlockPtr.IsValid() ? ColorBlockPtr : nullptr; - PickerArgs.bUseAlpha = bHasAlpha; - PickerArgs.DisplayGamma = TAttribute< float >::Create( - TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) + .OnMouseButtonDown_Lambda([this, ColorParams, MainParam, bHasAlpha](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), - MainParam->GetOuter(), true); - - bool bChanged = false; - for (auto & Param : ColorParams) + FColorPickerArgs PickerArgs; + PickerArgs.ParentWidget = FSlateApplication::Get().GetActiveTopLevelWindow(); + PickerArgs.bUseAlpha = bHasAlpha; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) { - if (!Param) - continue; + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), + MainParam->GetOuter(), true); - Param->Modify(); - if (Param->SetColorValue(InColor)) + bool bChanged = false; + for (auto & Param : ColorParams) { - Param->MarkChanged(true); - bChanged = true; + if (!Param) + continue; + + Param->Modify(); + if (Param->SetColorValue(InColor)) + { + Param->MarkChanged(true); + bChanged = true; + } } - } - // cancel the transaction if there is actually no value changed - if (!bChanged) - { - Transaction.Cancel(); - } - }); - PickerArgs.InitialColorOverride = MainParam->GetColorValue(); - PickerArgs.bOnlyRefreshOnOk = true; - OpenColorPicker(PickerArgs); - return FReply::Handled(); - } - )); + // cancel the transaction if there is actually no value changed + if (!bChanged) + { + Transaction.Cancel(); + } + }); + PickerArgs.InitialColorOverride = MainParam->GetColorValue(); + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker(PickerArgs); + return FReply::Handled(); + }) + ]; Row->ValueWidget.Widget = VerticalBox; Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); @@ -3978,9 +3969,9 @@ FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParamet MainParam->UpdateChoiceLabelsPtr(); TArray>* OptionSource = MainParam->GetChoiceLabelsPtr(); TSharedPtr IntialSelec; - if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValue())) + if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValueIndex())) { - IntialSelec = (*OptionSource)[MainParam->GetIntValue()]; + IntialSelec = (*OptionSource)[MainParam->GetIntValueIndex()]; } TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); @@ -5672,6 +5663,7 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen return; + // If a folder is invisible, its children won't be listed by HAPI. // So just reduce FolderListSize by 1, reduce the child counter of its parent folder by 1 if necessary, // and prune the stack in such case. diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp index ac13a4c17..135426a94 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp @@ -1,261 +1,261 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniPublicAPI.h" - -#include "HoudiniAsset.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniPublicAPIAssetWrapper.h" -#include "HoudiniPublicAPIInputTypes.h" - -UHoudiniPublicAPI::UHoudiniPublicAPI() -{ -} - -void -UHoudiniPublicAPI::CreateSession_Implementation() -{ - if (!IsSessionValid()) - FHoudiniEngineCommands::CreateSession(); -} - -void -UHoudiniPublicAPI::StopSession_Implementation() -{ - if (IsSessionValid()) - FHoudiniEngineCommands::StopSession(); -} - -void -UHoudiniPublicAPI::RestartSession_Implementation() -{ - if (IsSessionValid()) - FHoudiniEngineCommands::RestartSession(); - else - FHoudiniEngineCommands::CreateSession(); -} - -UHoudiniPublicAPIAssetWrapper* -UHoudiniPublicAPI::InstantiateAsset_Implementation( - UHoudiniAsset* InHoudiniAsset, - const FTransform& InInstantiateAt, - UObject* InWorldContextObject, - ULevel* InSpawnInLevelOverride, - const bool bInEnableAutoCook, - const bool bInEnableAutoBake, - const FString& InBakeDirectoryPath, - const EHoudiniEngineBakeOption InBakeMethod, - const bool bInRemoveOutputAfterBake, - const bool bInRecenterBakedActors, - const bool bInReplacePreviousBake) -{ - if (!IsValid(InHoudiniAsset) || !(InHoudiniAsset->AssetImportData)) - { - SetErrorMessage(TEXT("InHoudiniAsset is invalid or does not have AssetImportData.")); - return nullptr; - } - - // Create wrapper for asset instance - UHoudiniPublicAPIAssetWrapper* Wrapper = UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(this); - - if (Wrapper) - { - // Enable/disable error logging based on the API setting - Wrapper->SetLoggingErrorsEnabled(IsLoggingErrors()); - - if (!InstantiateAssetWithExistingWrapper( - Wrapper, - InHoudiniAsset, - InInstantiateAt, - InWorldContextObject, - InSpawnInLevelOverride, - bInEnableAutoCook, - bInEnableAutoBake, - InBakeDirectoryPath, - InBakeMethod, - bInRemoveOutputAfterBake, - bInRecenterBakedActors, - bInReplacePreviousBake)) - { - // failed to instantiate asset, return null - return nullptr; - } - } - - return Wrapper; -} - -bool -UHoudiniPublicAPI::InstantiateAssetWithExistingWrapper_Implementation( - UHoudiniPublicAPIAssetWrapper* InWrapper, - UHoudiniAsset* InHoudiniAsset, - const FTransform& InInstantiateAt, - UObject* InWorldContextObject, - ULevel* InSpawnInLevelOverride, - const bool bInEnableAutoCook, - const bool bInEnableAutoBake, - const FString& InBakeDirectoryPath, - const EHoudiniEngineBakeOption InBakeMethod, - const bool bInRemoveOutputAfterBake, - const bool bInRecenterBakedActors, - const bool bInReplacePreviousBake) -{ - if (!IsValid(InWrapper)) - { - SetErrorMessage(TEXT("InWrapper is not valid.")); - return false; - } - - if (!IsValid(InHoudiniAsset) || !(InHoudiniAsset->AssetImportData)) - { - SetErrorMessage(TEXT("InHoudiniAsset is invalid or does not have AssetImportData.")); - return false; - } - - UWorld* OverrideWorldToSpawnIn = IsValid(InWorldContextObject) ? InWorldContextObject->GetWorld() : nullptr; - AActor* HoudiniAssetActor = FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(InHoudiniAsset, InInstantiateAt, OverrideWorldToSpawnIn, InSpawnInLevelOverride); - if (!IsValid(HoudiniAssetActor)) - { - // Determine the path of what would have been the owning level/world of the actor for error logging purposes - const FString LevelPath = IsValid(InSpawnInLevelOverride) ? InSpawnInLevelOverride->GetPathName() : FString(); - const FString WorldPath = IsValid(OverrideWorldToSpawnIn) ? OverrideWorldToSpawnIn->GetPathName() : FString(); - const FString OwnerPath = LevelPath.IsEmpty() ? WorldPath : LevelPath; - SetErrorMessage(FString::Printf(TEXT("Failed to spawn a AHoudiniAssetActor in %s."), *OwnerPath)); - return false; - } - - // Wrap the instantiated asset - if (!InWrapper->WrapHoudiniAssetObject(HoudiniAssetActor)) - { - FString WrapperError; - InWrapper->GetLastErrorMessage(WrapperError); - SetErrorMessage(FString::Printf( - TEXT("Failed to wrap '%s': %s."), *(HoudiniAssetActor->GetName()), *WrapperError)); - return false; - } - - InWrapper->SetAutoCookingEnabled(bInEnableAutoCook); - - FDirectoryPath BakeDirectoryPath; - BakeDirectoryPath.Path = InBakeDirectoryPath; - InWrapper->SetBakeFolder(BakeDirectoryPath); - InWrapper->SetBakeMethod(InBakeMethod); - InWrapper->SetRemoveOutputAfterBake(bInRemoveOutputAfterBake); - InWrapper->SetRecenterBakedActors(bInRecenterBakedActors); - InWrapper->SetReplacePreviousBake(bInReplacePreviousBake); - InWrapper->SetAutoBakeEnabled(bInEnableAutoBake); - - return true; -} - -void -UHoudiniPublicAPI::PauseAssetCooking_Implementation() -{ - if (!IsAssetCookingPaused()) - FHoudiniEngineCommands::PauseAssetCooking(); -} - -void -UHoudiniPublicAPI::ResumeAssetCooking_Implementation() -{ - if (IsAssetCookingPaused()) - FHoudiniEngineCommands::PauseAssetCooking(); -} - -UHoudiniPublicAPIInput* -UHoudiniPublicAPI::CreateEmptyInput_Implementation(TSubclassOf InInputClass, UHoudiniPublicAPIAssetWrapper* InOuter) -{ - UObject* Outer = InOuter; - if (!IsValid(Outer)) - Outer = this; - UHoudiniPublicAPIInput* const NewInput = NewObject(Outer, InInputClass.Get()); - if (!IsValid(NewInput)) - { - SetErrorMessage(TEXT("Could not create a new valid UHoudiniPublicAPIInput instance.")); - } - else if (Outer) - { - // Enable/disable error logging based on the outer's setting (if it implements the error logging interface) - const bool bShouldLogErrors = Outer->IsA() ? - Cast(Outer)->IsLoggingErrors() : IsLoggingErrors(); - NewInput->SetLoggingErrorsEnabled(bShouldLogErrors); - } - - return NewInput; -} - -EHoudiniPublicAPIRampInterpolationType -UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(const EHoudiniRampInterpolationType InInterpolationType) -{ - switch (InInterpolationType) - { - case EHoudiniRampInterpolationType::InValid: - return EHoudiniPublicAPIRampInterpolationType::InValid; - case EHoudiniRampInterpolationType::BEZIER: - return EHoudiniPublicAPIRampInterpolationType::BEZIER; - case EHoudiniRampInterpolationType::LINEAR: - return EHoudiniPublicAPIRampInterpolationType::LINEAR; - case EHoudiniRampInterpolationType::BSPLINE: - return EHoudiniPublicAPIRampInterpolationType::BSPLINE; - case EHoudiniRampInterpolationType::HERMITE: - return EHoudiniPublicAPIRampInterpolationType::HERMITE; - case EHoudiniRampInterpolationType::CONSTANT: - return EHoudiniPublicAPIRampInterpolationType::CONSTANT; - case EHoudiniRampInterpolationType::CATMULL_ROM: - return EHoudiniPublicAPIRampInterpolationType::CATMULL_ROM; - case EHoudiniRampInterpolationType::MONOTONE_CUBIC: - return EHoudiniPublicAPIRampInterpolationType::MONOTONE_CUBIC; - } - - return EHoudiniPublicAPIRampInterpolationType::InValid; -} - -EHoudiniRampInterpolationType -UHoudiniPublicAPI::ToHoudiniRampInterpolationType(const EHoudiniPublicAPIRampInterpolationType InInterpolationType) -{ - switch (InInterpolationType) - { - case EHoudiniPublicAPIRampInterpolationType::InValid: - return EHoudiniRampInterpolationType::InValid; - case EHoudiniPublicAPIRampInterpolationType::BEZIER: - return EHoudiniRampInterpolationType::BEZIER; - case EHoudiniPublicAPIRampInterpolationType::LINEAR: - return EHoudiniRampInterpolationType::LINEAR; - case EHoudiniPublicAPIRampInterpolationType::BSPLINE: - return EHoudiniRampInterpolationType::BSPLINE; - case EHoudiniPublicAPIRampInterpolationType::HERMITE: - return EHoudiniRampInterpolationType::HERMITE; - case EHoudiniPublicAPIRampInterpolationType::CONSTANT: - return EHoudiniRampInterpolationType::CONSTANT; - case EHoudiniPublicAPIRampInterpolationType::CATMULL_ROM: - return EHoudiniRampInterpolationType::CATMULL_ROM; - case EHoudiniPublicAPIRampInterpolationType::MONOTONE_CUBIC: - return EHoudiniRampInterpolationType::MONOTONE_CUBIC; - } - - return EHoudiniRampInterpolationType::InValid; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniPublicAPI.h" + +#include "HoudiniAsset.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniPublicAPIAssetWrapper.h" +#include "HoudiniPublicAPIInputTypes.h" + +UHoudiniPublicAPI::UHoudiniPublicAPI() +{ +} + +void +UHoudiniPublicAPI::CreateSession_Implementation() +{ + if (!IsSessionValid()) + FHoudiniEngineCommands::CreateSession(); +} + +void +UHoudiniPublicAPI::StopSession_Implementation() +{ + if (IsSessionValid()) + FHoudiniEngineCommands::StopSession(); +} + +void +UHoudiniPublicAPI::RestartSession_Implementation() +{ + if (IsSessionValid()) + FHoudiniEngineCommands::RestartSession(); + else + FHoudiniEngineCommands::CreateSession(); +} + +UHoudiniPublicAPIAssetWrapper* +UHoudiniPublicAPI::InstantiateAsset_Implementation( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject, + ULevel* InSpawnInLevelOverride, + const bool bInEnableAutoCook, + const bool bInEnableAutoBake, + const FString& InBakeDirectoryPath, + const EHoudiniEngineBakeOption InBakeMethod, + const bool bInRemoveOutputAfterBake, + const bool bInRecenterBakedActors, + const bool bInReplacePreviousBake) +{ + if (!IsValid(InHoudiniAsset) || !(InHoudiniAsset->AssetImportData)) + { + SetErrorMessage(TEXT("InHoudiniAsset is invalid or does not have AssetImportData.")); + return nullptr; + } + + // Create wrapper for asset instance + UHoudiniPublicAPIAssetWrapper* Wrapper = UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(this); + + if (Wrapper) + { + // Enable/disable error logging based on the API setting + Wrapper->SetLoggingErrorsEnabled(IsLoggingErrors()); + + if (!InstantiateAssetWithExistingWrapper( + Wrapper, + InHoudiniAsset, + InInstantiateAt, + InWorldContextObject, + InSpawnInLevelOverride, + bInEnableAutoCook, + bInEnableAutoBake, + InBakeDirectoryPath, + InBakeMethod, + bInRemoveOutputAfterBake, + bInRecenterBakedActors, + bInReplacePreviousBake)) + { + // failed to instantiate asset, return null + return nullptr; + } + } + + return Wrapper; +} + +bool +UHoudiniPublicAPI::InstantiateAssetWithExistingWrapper_Implementation( + UHoudiniPublicAPIAssetWrapper* InWrapper, + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject, + ULevel* InSpawnInLevelOverride, + const bool bInEnableAutoCook, + const bool bInEnableAutoBake, + const FString& InBakeDirectoryPath, + const EHoudiniEngineBakeOption InBakeMethod, + const bool bInRemoveOutputAfterBake, + const bool bInRecenterBakedActors, + const bool bInReplacePreviousBake) +{ + if (!IsValid(InWrapper)) + { + SetErrorMessage(TEXT("InWrapper is not valid.")); + return false; + } + + if (!IsValid(InHoudiniAsset) || !(InHoudiniAsset->AssetImportData)) + { + SetErrorMessage(TEXT("InHoudiniAsset is invalid or does not have AssetImportData.")); + return false; + } + + UWorld* OverrideWorldToSpawnIn = IsValid(InWorldContextObject) ? InWorldContextObject->GetWorld() : nullptr; + AActor* HoudiniAssetActor = FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(InHoudiniAsset, InInstantiateAt, OverrideWorldToSpawnIn, InSpawnInLevelOverride); + if (!IsValid(HoudiniAssetActor)) + { + // Determine the path of what would have been the owning level/world of the actor for error logging purposes + const FString LevelPath = IsValid(InSpawnInLevelOverride) ? InSpawnInLevelOverride->GetPathName() : FString(); + const FString WorldPath = IsValid(OverrideWorldToSpawnIn) ? OverrideWorldToSpawnIn->GetPathName() : FString(); + const FString OwnerPath = LevelPath.IsEmpty() ? WorldPath : LevelPath; + SetErrorMessage(FString::Printf(TEXT("Failed to spawn a AHoudiniAssetActor in %s."), *OwnerPath)); + return false; + } + + // Wrap the instantiated asset + if (!InWrapper->WrapHoudiniAssetObject(HoudiniAssetActor)) + { + FString WrapperError; + InWrapper->GetLastErrorMessage(WrapperError); + SetErrorMessage(FString::Printf( + TEXT("Failed to wrap '%s': %s."), *(HoudiniAssetActor->GetName()), *WrapperError)); + return false; + } + + InWrapper->SetAutoCookingEnabled(bInEnableAutoCook); + + FDirectoryPath BakeDirectoryPath; + BakeDirectoryPath.Path = InBakeDirectoryPath; + InWrapper->SetBakeFolder(BakeDirectoryPath); + InWrapper->SetBakeMethod(InBakeMethod); + InWrapper->SetRemoveOutputAfterBake(bInRemoveOutputAfterBake); + InWrapper->SetRecenterBakedActors(bInRecenterBakedActors); + InWrapper->SetReplacePreviousBake(bInReplacePreviousBake); + InWrapper->SetAutoBakeEnabled(bInEnableAutoBake); + + return true; +} + +void +UHoudiniPublicAPI::PauseAssetCooking_Implementation() +{ + if (!IsAssetCookingPaused()) + FHoudiniEngineCommands::PauseAssetCooking(); +} + +void +UHoudiniPublicAPI::ResumeAssetCooking_Implementation() +{ + if (IsAssetCookingPaused()) + FHoudiniEngineCommands::PauseAssetCooking(); +} + +UHoudiniPublicAPIInput* +UHoudiniPublicAPI::CreateEmptyInput_Implementation(TSubclassOf InInputClass, UObject* InOuter) +{ + UObject* Outer = InOuter; + if (!IsValid(Outer)) + Outer = this; + UHoudiniPublicAPIInput* const NewInput = NewObject(Outer, InInputClass.Get()); + if (!IsValid(NewInput)) + { + SetErrorMessage(TEXT("Could not create a new valid UHoudiniPublicAPIInput instance.")); + } + else if (Outer) + { + // Enable/disable error logging based on the outer's setting (if it is a sub-class of UHoudiniPublicAPIObjectBase) + const bool bShouldLogErrors = Outer->IsA() ? + Cast(Outer)->IsLoggingErrors() : IsLoggingErrors(); + NewInput->SetLoggingErrorsEnabled(bShouldLogErrors); + } + + return NewInput; +} + +EHoudiniPublicAPIRampInterpolationType +UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(const EHoudiniRampInterpolationType InInterpolationType) +{ + switch (InInterpolationType) + { + case EHoudiniRampInterpolationType::InValid: + return EHoudiniPublicAPIRampInterpolationType::InValid; + case EHoudiniRampInterpolationType::BEZIER: + return EHoudiniPublicAPIRampInterpolationType::BEZIER; + case EHoudiniRampInterpolationType::LINEAR: + return EHoudiniPublicAPIRampInterpolationType::LINEAR; + case EHoudiniRampInterpolationType::BSPLINE: + return EHoudiniPublicAPIRampInterpolationType::BSPLINE; + case EHoudiniRampInterpolationType::HERMITE: + return EHoudiniPublicAPIRampInterpolationType::HERMITE; + case EHoudiniRampInterpolationType::CONSTANT: + return EHoudiniPublicAPIRampInterpolationType::CONSTANT; + case EHoudiniRampInterpolationType::CATMULL_ROM: + return EHoudiniPublicAPIRampInterpolationType::CATMULL_ROM; + case EHoudiniRampInterpolationType::MONOTONE_CUBIC: + return EHoudiniPublicAPIRampInterpolationType::MONOTONE_CUBIC; + } + + return EHoudiniPublicAPIRampInterpolationType::InValid; +} + +EHoudiniRampInterpolationType +UHoudiniPublicAPI::ToHoudiniRampInterpolationType(const EHoudiniPublicAPIRampInterpolationType InInterpolationType) +{ + switch (InInterpolationType) + { + case EHoudiniPublicAPIRampInterpolationType::InValid: + return EHoudiniRampInterpolationType::InValid; + case EHoudiniPublicAPIRampInterpolationType::BEZIER: + return EHoudiniRampInterpolationType::BEZIER; + case EHoudiniPublicAPIRampInterpolationType::LINEAR: + return EHoudiniRampInterpolationType::LINEAR; + case EHoudiniPublicAPIRampInterpolationType::BSPLINE: + return EHoudiniRampInterpolationType::BSPLINE; + case EHoudiniPublicAPIRampInterpolationType::HERMITE: + return EHoudiniRampInterpolationType::HERMITE; + case EHoudiniPublicAPIRampInterpolationType::CONSTANT: + return EHoudiniRampInterpolationType::CONSTANT; + case EHoudiniPublicAPIRampInterpolationType::CATMULL_ROM: + return EHoudiniRampInterpolationType::CATMULL_ROM; + case EHoudiniPublicAPIRampInterpolationType::MONOTONE_CUBIC: + return EHoudiniRampInterpolationType::MONOTONE_CUBIC; + } + + return EHoudiniRampInterpolationType::InValid; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp index 6c33e2599..43347d130 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp @@ -695,18 +695,25 @@ UHoudiniPublicAPIAssetWrapper::SetFloatParameterValue_Implementation(FName InPar return false; } - const float CurrentValue = ColorParam->GetColorValue().Component(InAtIndex); - if (CurrentValue != InValue) + FLinearColor CurrentColorValue = ColorParam->GetColorValue(); + if (CurrentColorValue.Component(InAtIndex) != InValue) { - ColorParam->GetColorValue().Component(InAtIndex) = InValue; + CurrentColorValue.Component(InAtIndex) = InValue; + ColorParam->SetColorValue(CurrentColorValue); bDidChangeValue = true; } } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetFloatParamterValue."), *InParameterTupleName.ToString())); + return false; + } if (bDidChangeValue && bInMarkChanged) Param->MarkChanged(true); - return bDidChangeValue; + return true; } bool @@ -784,11 +791,17 @@ UHoudiniPublicAPIAssetWrapper::SetColorParameterValue_Implementation(FName InPar bDidChangeValue = ColorParam->SetColorValue(InValue); } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetColorParamterValue."), *InParameterTupleName.ToString())); + return false; + } if (bDidChangeValue && bInMarkChanged) Param->MarkChanged(true); - return bDidChangeValue; + return true; } bool @@ -904,11 +917,17 @@ UHoudiniPublicAPIAssetWrapper::SetIntParameterValue_Implementation(FName InParam // For ramps we have to use the appropriate function so that delete/insert operations are managed correctly bDidChangeValue = SetRampParameterNumPoints(InParameterTupleName, InValue); } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetIntParameterValue."), *InParameterTupleName.ToString())); + return false; + } if (bDidChangeValue && bInMarkChanged) Param->MarkChanged(true); - return bDidChangeValue; + return true; } bool @@ -945,7 +964,7 @@ UHoudiniPublicAPIAssetWrapper::GetIntParameterValue_Implementation(FName InParam return false; } - OutValue = ChoiceParam->GetIntValue(); + OutValue = ChoiceParam->GetIntValue(ChoiceParam->GetIntValueIndex()); return true; } else if (ParamType == EHoudiniParameterType::MultiParm) @@ -1029,11 +1048,17 @@ UHoudiniPublicAPIAssetWrapper::SetBoolParameterValue_Implementation(FName InPara bDidChangeValue = true; } } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetBoolParameterValue."), *InParameterTupleName.ToString())); + return false; + } if (bDidChangeValue && bInMarkChanged) Param->MarkChanged(true); - return bDidChangeValue; + return true; } bool @@ -1121,6 +1146,7 @@ UHoudiniPublicAPIAssetWrapper::SetStringParameterValue_Implementation(FName InPa { SetErrorMessage(FString::Printf( TEXT("Asset reference '%s' is invalid. Not setting parameter value."), *InValue)); + return false; } } else @@ -1160,11 +1186,17 @@ UHoudiniPublicAPIAssetWrapper::SetStringParameterValue_Implementation(FName InPa bDidChangeValue = FileParam->SetValueAt(InValue, InAtIndex); } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetStringParameterValue."), *InParameterTupleName.ToString())); + return false; + } if (bDidChangeValue && bInMarkChanged) Param->MarkChanged(true); - return bDidChangeValue; + return true; } bool @@ -1286,11 +1318,17 @@ UHoudiniPublicAPIAssetWrapper::SetAssetRefParameterValue_Implementation(FName In bDidChangeValue = true; } } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetAssetRefParamter."), *InParameterTupleName.ToString())); + return false; + } if (bDidChangeValue && bInMarkChanged) Param->MarkChanged(true); - return bDidChangeValue; + return true; } bool @@ -1954,7 +1992,7 @@ UHoudiniPublicAPIAssetWrapper::SetColorRampParameterPoints_Implementation( } else { - SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a color ramp parameter."), *(Param->GetName()))); return false; } @@ -2170,7 +2208,7 @@ UHoudiniPublicAPIAssetWrapper::TriggerButtonParameter_Implementation(FName InBut // Handle all the cases where the underlying parameter value is an int or bool const EHoudiniParameterType ParamType = Param->GetParameterType(); - bool bDidTrigger = false; + // bool bDidTrigger = false; if (ParamType == EHoudiniParameterType::Button) { UHoudiniParameterButton* ButtonParam = Cast(Param); @@ -2185,11 +2223,17 @@ UHoudiniPublicAPIAssetWrapper::TriggerButtonParameter_Implementation(FName InBut if (!ButtonParam->HasChanged() || !ButtonParam->NeedsToTriggerUpdate()) { ButtonParam->MarkChanged(true); - bDidTrigger = true; + // bDidTrigger = true; } } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is not a button."), *InButtonParameterName.ToString())); + return false; + } - return bDidTrigger; + return true; } bool diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp index e133905b1..1e8a403cc 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp @@ -350,3 +350,4 @@ void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, EText } #undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h index 06a542733..4b6bd22fe 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h @@ -148,3 +148,4 @@ class SNewFilePathPicker /** Holds a delegate that is executed when a file was picked. */ FOnPathPicked OnPathPicked; }; + diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h index bb7909522..d9014811c 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h @@ -1,214 +1,214 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniParameter.h" -#include "HoudiniPublicAPIObjectBase.h" - -#include "HoudiniPublicAPI.generated.h" - -class ULevel; - -class UHoudiniAsset; -class UHoudiniPublicAPIAssetWrapper; -class UHoudiniPublicAPIInput; - -/** Public API version of EHoudiniRampInterpolationType: blueprints do not support int8 based enums. */ -UENUM(BlueprintType) -enum class EHoudiniPublicAPIRampInterpolationType : uint8 -{ - InValid = 0, - - CONSTANT = 1, - LINEAR = 2, - CATMULL_ROM = 3, - MONOTONE_CUBIC = 4, - BEZIER = 5, - BSPLINE = 6, - HERMITE = 7 -}; - -/** - * The Houdini Engine v2 Plug-in's Public API. - * - * The API allows one to manage a Houdini Engine session (Create/Stop/Restart), Pause/Resume asset cooking and - * instantiate HDA's and interact with it (set/update inputs, parameters, cook, iterate over outputs and bake outputs). - * - * Interaction with an instantiated HDA is done via UHoudiniPublicAPIAssetWrapper. - * - */ -UCLASS(BlueprintType, Blueprintable, Category="Houdini|Public API") -class HOUDINIENGINEEDITOR_API UHoudiniPublicAPI : public UHoudiniPublicAPIObjectBase -{ - GENERATED_BODY() - -public: - - UHoudiniPublicAPI(); - - // Session - - /** Returns true if there is a valid Houdini Engine session running/connected */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool IsSessionValid() const; - FORCEINLINE - virtual bool IsSessionValid_Implementation() const { return FHoudiniEngineCommands::IsSessionValid(); } - - /** Start a new Houdini Engine Session if there is no current session */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - void CreateSession(); - - /** Stops the current session */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - void StopSession(); - - /** Stops, then creates a new session */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - void RestartSession(); - - // Assets - - /** - * Instantiates an HDA in the specified world/level. Returns a wrapper for instantiated asset. - * @param InHoudiniAsset The HDA to instantiate. - * @param InInstantiateAt The Transform to instantiate the HDA with. - * @param InWorldContextObject A world context object for identifying the world to spawn in, if - * @InSpawnInLevelOverride is null. - * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both - * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor - * context world's current level. - * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after - * parameter, transform and input changes. - * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. - * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. - * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. - * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. - * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. - * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with - * the new bake's output. Defaults to false. - * @return A wrapper for the instantiated asset, or nullptr if InHoudiniAsset or InInstantiateAt is invalid, or - * the AHoudiniAssetActor could not be spawned. See UHoudiniPublicAPIAssetWrapper. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InInstantiateAt")) - UHoudiniPublicAPIAssetWrapper* InstantiateAsset( - UHoudiniAsset* InHoudiniAsset, - const FTransform& InInstantiateAt, - UObject* InWorldContextObject=nullptr, - ULevel* InSpawnInLevelOverride=nullptr, - const bool bInEnableAutoCook=true, - const bool bInEnableAutoBake=false, - const FString& InBakeDirectoryPath="", - const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, - const bool bInRemoveOutputAfterBake=false, - const bool bInRecenterBakedActors=false, - const bool bInReplacePreviousBake=false); - - /** - * Instantiates an HDA in the specified world/level using an existing wrapper. - * @param InWrapper The wrapper to instantiate the HDA with. - * @param InHoudiniAsset The HDA to instantiate. - * @param InInstantiateAt The Transform to instantiate the HDA with. - * @param InWorldContextObject A world context object for identifying the world to spawn in, if - * InSpawnInLevelOverride is null. - * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both - * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor - * context world's current level. - * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after - * parameter, transform and input changes. - * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. - * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. - * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. - * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. - * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. - * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with - * the new bake's output. Defaults to false. - * @return true if InWrapper and InHoudiniAsset is valid and the AHoudiniAssetActor was spawned. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InInstantiateAt")) - bool InstantiateAssetWithExistingWrapper( - UHoudiniPublicAPIAssetWrapper* InWrapper, - UHoudiniAsset* InHoudiniAsset, - const FTransform& InInstantiateAt, - UObject* InWorldContextObject=nullptr, - ULevel* InSpawnInLevelOverride=nullptr, - const bool bInEnableAutoCook=true, - const bool bInEnableAutoBake=false, - const FString& InBakeDirectoryPath="", - const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, - const bool bInRemoveOutputAfterBake=false, - const bool bInRecenterBakedActors=false, - const bool bInReplacePreviousBake=false); - - // Cooking - - /** Returns true if asset cooking is paused. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool IsAssetCookingPaused() const; - FORCEINLINE - virtual bool IsAssetCookingPaused_Implementation() const { return FHoudiniEngineCommands::IsAssetCookingPaused(); } - - /** Pause asset cooking (if not already paused) */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - void PauseAssetCooking(); - - /** Resume asset cooking (if it was paused) */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - void ResumeAssetCooking(); - - // Inputs - - /** - * Create a new empty API input object. The user must populate it and then set it as an input on an asset wrapper. - * @param InInputClass The class of the input to create, must be a subclass of UHoudiniPublicAPIInput. - * @param InOuter The owner of the input, if nullptr, then this API instance will be set as the outer. - * @return The newly created empty input object instance. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(DeterminesOutputType="InInputClass")) - UHoudiniPublicAPIInput* CreateEmptyInput(TSubclassOf InInputClass, UHoudiniPublicAPIAssetWrapper* InOuter=nullptr); - - // Helpers -- enum conversions - - /** - * Helper for converting from EHoudiniRampInterpolationType to EHoudiniPublicAPIRampInterpolationType - * @param InInterpolationType The EHoudiniRampInterpolationType to convert. - * @return The EHoudiniPublicAPIRampInterpolationType value of InInterpolationType. - */ - static EHoudiniPublicAPIRampInterpolationType ToHoudiniPublicAPIRampInterpolationType(const EHoudiniRampInterpolationType InInterpolationType); - - /** - * Helper for converting from EHoudiniPublicAPIRampInterpolationType to EHoudiniRampInterpolationType - * @param InInterpolationType The EHoudiniPublicAPIRampInterpolationType to convert. - * @return The EHoudiniRampInterpolationType value of InInterpolationType. - */ - static EHoudiniRampInterpolationType ToHoudiniRampInterpolationType(const EHoudiniPublicAPIRampInterpolationType InInterpolationType); - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniParameter.h" +#include "HoudiniPublicAPIObjectBase.h" + +#include "HoudiniPublicAPI.generated.h" + +class ULevel; + +class UHoudiniAsset; +class UHoudiniPublicAPIAssetWrapper; +class UHoudiniPublicAPIInput; + +/** Public API version of EHoudiniRampInterpolationType: blueprints do not support int8 based enums. */ +UENUM(BlueprintType) +enum class EHoudiniPublicAPIRampInterpolationType : uint8 +{ + InValid = 0, + + CONSTANT = 1, + LINEAR = 2, + CATMULL_ROM = 3, + MONOTONE_CUBIC = 4, + BEZIER = 5, + BSPLINE = 6, + HERMITE = 7 +}; + +/** + * The Houdini Engine v2 Plug-in's Public API. + * + * The API allows one to manage a Houdini Engine session (Create/Stop/Restart), Pause/Resume asset cooking and + * instantiate HDA's and interact with it (set/update inputs, parameters, cook, iterate over outputs and bake outputs). + * + * Interaction with an instantiated HDA is done via UHoudiniPublicAPIAssetWrapper. + * + */ +UCLASS(BlueprintType, Blueprintable, Category="Houdini|Public API") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPI : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + + UHoudiniPublicAPI(); + + // Session + + /** Returns true if there is a valid Houdini Engine session running/connected */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsSessionValid() const; + FORCEINLINE + virtual bool IsSessionValid_Implementation() const { return FHoudiniEngineCommands::IsSessionValid(); } + + /** Start a new Houdini Engine Session if there is no current session */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void CreateSession(); + + /** Stops the current session */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void StopSession(); + + /** Stops, then creates a new session */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void RestartSession(); + + // Assets + + /** + * Instantiates an HDA in the specified world/level. Returns a wrapper for instantiated asset. + * @param InHoudiniAsset The HDA to instantiate. + * @param InInstantiateAt The Transform to instantiate the HDA with. + * @param InWorldContextObject A world context object for identifying the world to spawn in, if + * @InSpawnInLevelOverride is null. + * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both + * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor + * context world's current level. + * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after + * parameter, transform and input changes. + * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. + * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. + * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. + * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. + * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. + * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with + * the new bake's output. Defaults to false. + * @return A wrapper for the instantiated asset, or nullptr if InHoudiniAsset or InInstantiateAt is invalid, or + * the AHoudiniAssetActor could not be spawned. See UHoudiniPublicAPIAssetWrapper. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InInstantiateAt")) + UHoudiniPublicAPIAssetWrapper* InstantiateAsset( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject=nullptr, + ULevel* InSpawnInLevelOverride=nullptr, + const bool bInEnableAutoCook=true, + const bool bInEnableAutoBake=false, + const FString& InBakeDirectoryPath="", + const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, + const bool bInRemoveOutputAfterBake=false, + const bool bInRecenterBakedActors=false, + const bool bInReplacePreviousBake=false); + + /** + * Instantiates an HDA in the specified world/level using an existing wrapper. + * @param InWrapper The wrapper to instantiate the HDA with. + * @param InHoudiniAsset The HDA to instantiate. + * @param InInstantiateAt The Transform to instantiate the HDA with. + * @param InWorldContextObject A world context object for identifying the world to spawn in, if + * InSpawnInLevelOverride is null. + * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both + * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor + * context world's current level. + * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after + * parameter, transform and input changes. + * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. + * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. + * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. + * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. + * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. + * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with + * the new bake's output. Defaults to false. + * @return true if InWrapper and InHoudiniAsset is valid and the AHoudiniAssetActor was spawned. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InInstantiateAt")) + bool InstantiateAssetWithExistingWrapper( + UHoudiniPublicAPIAssetWrapper* InWrapper, + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + UObject* InWorldContextObject=nullptr, + ULevel* InSpawnInLevelOverride=nullptr, + const bool bInEnableAutoCook=true, + const bool bInEnableAutoBake=false, + const FString& InBakeDirectoryPath="", + const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, + const bool bInRemoveOutputAfterBake=false, + const bool bInRecenterBakedActors=false, + const bool bInReplacePreviousBake=false); + + // Cooking + + /** Returns true if asset cooking is paused. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsAssetCookingPaused() const; + FORCEINLINE + virtual bool IsAssetCookingPaused_Implementation() const { return FHoudiniEngineCommands::IsAssetCookingPaused(); } + + /** Pause asset cooking (if not already paused) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void PauseAssetCooking(); + + /** Resume asset cooking (if it was paused) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void ResumeAssetCooking(); + + // Inputs + + /** + * Create a new empty API input object. The user must populate it and then set it as an input on an asset wrapper. + * @param InInputClass The class of the input to create, must be a subclass of UHoudiniPublicAPIInput. + * @param InOuter The owner of the input, if nullptr, then this API instance will be set as the outer. + * @return The newly created empty input object instance. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(DeterminesOutputType="InInputClass")) + UHoudiniPublicAPIInput* CreateEmptyInput(TSubclassOf InInputClass, UObject* InOuter=nullptr); + + // Helpers -- enum conversions + + /** + * Helper for converting from EHoudiniRampInterpolationType to EHoudiniPublicAPIRampInterpolationType + * @param InInterpolationType The EHoudiniRampInterpolationType to convert. + * @return The EHoudiniPublicAPIRampInterpolationType value of InInterpolationType. + */ + static EHoudiniPublicAPIRampInterpolationType ToHoudiniPublicAPIRampInterpolationType(const EHoudiniRampInterpolationType InInterpolationType); + + /** + * Helper for converting from EHoudiniPublicAPIRampInterpolationType to EHoudiniRampInterpolationType + * @param InInterpolationType The EHoudiniPublicAPIRampInterpolationType to convert. + * @return The EHoudiniRampInterpolationType value of InInterpolationType. + */ + static EHoudiniRampInterpolationType ToHoudiniRampInterpolationType(const EHoudiniPublicAPIRampInterpolationType InInterpolationType); + +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h index c5b28cb0c..7f9df43b2 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h @@ -497,7 +497,7 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. Defaults to 0. * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set. + * @return true if the value was set or the parameter already had the given value. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool SetFloatParameterValue(FName InParameterTupleName, float InValue, int32 InAtIndex=0, bool bInMarkChanged=true); @@ -523,7 +523,7 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub * @param InValue The value to set. * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set. + * @return true if the value was set or the parameter already had the given value. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool SetColorParameterValue(FName InParameterTupleName, const FLinearColor& InValue, bool bInMarkChanged=true); @@ -551,7 +551,7 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. Defaults to 0. * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set. + * @return true if the value was set or the parameter already had the given value. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool SetIntParameterValue(FName InParameterTupleName, int32 InValue, int32 InAtIndex=0, bool bInMarkChanged=true); @@ -582,7 +582,7 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set. + * @return true if the value was set or the parameter already had the given value. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool SetBoolParameterValue(FName InParameterTupleName, bool InValue, int32 InAtIndex=0, bool bInMarkChanged=true); @@ -615,7 +615,7 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set. + * @return true if the value was set or the parameter already had the given value. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool SetStringParameterValue(FName InParameterTupleName, const FString& InValue, int32 InAtIndex=0, bool bInMarkChanged=true); @@ -647,7 +647,7 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set. + * @return true if the value was set or the parameter already had the given value. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool SetAssetRefParameterValue(FName InParameterTupleName, UObject* InValue, int32 InAtIndex=0, bool bInMarkChanged=true); @@ -825,7 +825,7 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub /** * Trigger / click the specified button parameter. - * @return True if the button was found and triggered/clicked. + * @return True if the button was found and triggered/clicked, or was already marked to be triggered. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool TriggerButtonParameter(FName InButtonParameterName); @@ -1077,9 +1077,8 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub * Gets the paths (relative to the specified TOP network) of all TOP nodes in the network. * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by * GetPDGTOPNetworkPaths(), to fetch TOP node paths for. - * @return false if the asset/wrapper is invalid, or does not contain any TOP networks. + * @return false if the asset/wrapper is invalid, or does not contain the specified TOP network. */ - // Returns false if the asset/wrapper is invalid, or does not contain the specified TOP network. UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool GetPDGTOPNodePaths(const FString& InNetworkRelativePath, TArray& OutTOPNodePaths) const; diff --git a/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs b/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs index 2e4f8b8e5..3df57b4aa 100644 --- a/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs +++ b/Source/HoudiniEngineRuntime/HoudiniEngineRuntime.Build.cs @@ -1,92 +1,93 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -using UnrealBuildTool; -using System; -using System.IO; - -public class HoudiniEngineRuntime : ModuleRules -{ - public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) - { - bPrecompile = true; - PCHUsage = PCHUsageMode.NoSharedPCHs; - PrivatePCHHeaderFile = "Private/HoudiniEngineRuntimePrivatePCH.h"; - - // Check if we are compiling for unsupported platforms. - if ( Target.Platform != UnrealTargetPlatform.Win64 && - Target.Platform != UnrealTargetPlatform.Mac && - Target.Platform != UnrealTargetPlatform.Linux && - Target.Platform != UnrealTargetPlatform.Switch ) - { - System.Console.WriteLine( string.Format( "Houdini Engine Runtime: Compiling for untested target platform. Please let us know how it goes!" ) ); - } - - - PublicIncludePaths.AddRange( - new string[] {} - ); - - PrivateIncludePaths.AddRange( - new string[] { } - ); - - // Add common dependencies. - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "Engine", - "RenderCore", - "InputCore", - "RHI", - "Foliage", - "Landscape" - } - ); - - PrivateDependencyModuleNames.AddRange( - new string[] - { - "Landscape", - "PhysicsCore" - } - ); - - if (Target.bBuildEditor == true) - { - PrivateDependencyModuleNames.AddRange( - new string[] - { - "UnrealEd", - "Kismet", - } - ); - } - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using UnrealBuildTool; +using System; +using System.IO; + +public class HoudiniEngineRuntime : ModuleRules +{ + public HoudiniEngineRuntime( ReadOnlyTargetRules Target ) : base( Target ) + { + bPrecompile = true; + PCHUsage = PCHUsageMode.NoSharedPCHs; + PrivatePCHHeaderFile = "Private/HoudiniEngineRuntimePrivatePCH.h"; + + // Check if we are compiling for unsupported platforms. + if ( Target.Platform != UnrealTargetPlatform.Win64 && + Target.Platform != UnrealTargetPlatform.Mac && + Target.Platform != UnrealTargetPlatform.Linux && + Target.Platform != UnrealTargetPlatform.Switch ) + { + System.Console.WriteLine( string.Format( "Houdini Engine Runtime: Compiling for untested target platform. Please let us know how it goes!" ) ); + } + + + PublicIncludePaths.AddRange( + new string[] {} + ); + + PrivateIncludePaths.AddRange( + new string[] { } + ); + + // Add common dependencies. + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "RenderCore", + "InputCore", + "RHI", + "Foliage", + "Landscape", + "MeshUtilitiesCommon" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Landscape", + "PhysicsCore" + } + ); + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "UnrealEd", + "Kismet", + } + ); + } + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp index 2156b4a7e..a7bad2417 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp @@ -135,9 +135,6 @@ UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() // to copy state back to the BPGC at all! FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); check(BlueprintEditor); - - TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); - check(SCSEditor); USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); // check(SCSHACNode); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp index 0986ee54e..68403e6a3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp @@ -538,7 +538,6 @@ UHoudiniAssetComponent::ConvertLegacyData() StaticMeshGenerationProperties.DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; StaticMeshGenerationProperties.GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; - StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier = Version1CompatibilityHAC->GeneratedLpvBiasMultiplier; StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; @@ -743,7 +742,6 @@ void UHoudiniAssetComponent::PostInitProperties() StaticMeshGenerationProperties.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; StaticMeshGenerationProperties.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; - StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; StaticMeshGenerationProperties.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; @@ -1143,10 +1141,11 @@ UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() { for (auto& CurrentInput : Inputs) { - EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); if (!CurrentInput || CurrentInput->IsPendingKill()) continue; + EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); + if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World) continue; @@ -2161,10 +2160,6 @@ UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyC { 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); @@ -2743,9 +2738,6 @@ UHoudiniAssetComponent::SetStaticMeshGenerationProperties(UStaticMesh* InStaticM // Set resolution of lightmap. InStaticMesh->LightMapResolution = StaticMeshGenerationProperties.GeneratedLightMapResolution; - // Set Bias multiplier for Light Propagation Volume lighting. - InStaticMesh->LpvBiasMultiplier = StaticMeshGenerationProperties.GeneratedLpvBiasMultiplier; - // Set the global light map coordinate index if it looks valid if (InStaticMesh->RenderData.IsValid() && InStaticMesh->RenderData->LODResources.Num() > 0) { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h index 96829e372..7a9dadb63 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h @@ -898,12 +898,6 @@ class UHoudiniAssetComponent_V1 : public UPrimitiveComponent//, public IHoudiniC meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) int32 GeneratedLightMapResolution; - /** Bias multiplier for Light Propagation Volume lighting. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float GeneratedLpvBiasMultiplier; - /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp index fc9e61728..971e2ff47 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp @@ -30,3 +30,4 @@ void IHoudiniEngineCopyPropertiesInterface::CopyPropertiesFrom(UObject* FromObje { } + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h index da5cfb633..83ffaa5f1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h @@ -1,273 +1,277 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Logging/LogMacros.h" - -// Define module names. -#define HOUDINI_MODULE "HoudiniEngine" -#define HOUDINI_MODULE_EDITOR "HoudiniEngineEditor" -#define HOUDINI_MODULE_RUNTIME "HoudiniEngineRuntime" - -// Declare the log category depending on the module we're in -#ifdef HOUDINI_ENGINE_EDITOR - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_EDITOR -HOUDINIENGINEEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, All); -#else - #ifdef HOUDINI_ENGINE - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE - HOUDINIENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngine, Log, All); - #else - #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME - HOUDINIENGINERUNTIME_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineRuntime, Log, All); - #endif -#endif - -//--------------------------------------------------------------------------------------------------------------------- -// Default session settings -//--------------------------------------------------------------------------------------------------------------------- - -#define HAPI_UNREAL_SESSION_SERVER_AUTOSTART true -#define HAPI_UNREAL_SESSION_SERVER_TIMEOUT 3000.0f -#define HAPI_UNREAL_SESSION_SERVER_HOST TEXT( "localhost" ) -#define HAPI_UNREAL_SESSION_SERVER_PORT 9090 -#if PLATFORM_MAC - #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "/tmp/hapi" ) -#else - #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "hapi" ) -#endif - - - -// Names of HAPI libraries on different platforms. -#define HAPI_LIB_OBJECT_WINDOWS TEXT( "libHAPIL.dll" ) -#define HAPI_LIB_OBJECT_MAC TEXT( "libHAPIL.dylib" ) -#define HAPI_LIB_OBJECT_LINUX TEXT( "libHAPIL.so" ) - -//--------------------------------------------------------------------------------------------------------------------- -// LOG MACROS -//--------------------------------------------------------------------------------------------------------------------- - -// Whether to enable logging. -#define HOUDINI_ENGINE_LOGGING 1 - -#ifdef HOUDINI_ENGINE_LOGGING - #ifdef HOUDINI_ENGINE - #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngine, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #else - #ifdef HOUDINI_ENGINE_EDITOR - #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngineEditor, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #else - #define HOUDINI_LOG_HELPER( VERBOSITY, HOUDINI_LOG_TEXT, ... ) \ - do \ - { \ - UE_LOG( LogHoudiniEngineRuntime, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - #endif - #endif - - #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - - #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_LOG_HELPER( Display, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) -#endif - - -#define HOUDINI_DEBUG_EXPAND_UE_LOG(LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ...) \ - do \ - { \ - UE_LOG( LogHoudiniEngine##LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ - } \ - while ( 0 ) - - -// --------------------------------------------------------- -// Blueprint Debug Logging -// --------------------------------------------------------- -// Set HOUDINI_ENGINE_DEBUG_BP=1 to enable Blueprint logging -#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BP) - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); - #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_BP_DEFINE_LOG_CATEGORY() - #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - - -// --------------------------------------------------------- -// PDG Debug Logging -// --------------------------------------------------------- -// Set HOUDINI_ENGINE_DEBUG_PDG=1 to enable PDG logging -#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_PDG) - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEnginePDG, Log, All); - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); - #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() - #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - - -// --------------------------------------------------------- -// Landscape Debug Logging -// --------------------------------------------------------- -// Set HOUDINI_ENGINE_DEBUG_LANDSCAPE=1 to enable PDG logging -#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineLandscape, Log, All); - #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEngineLandscape); - #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() - #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - - -// --------------------------------------------------------- -// Baking Debug Logging -// --------------------------------------------------------- -// Set HOUDINI_ENGINE_DEBUG_BAKING=1 to enable PDG logging -#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BAKING) - DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBaking, Log, All); - #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() \ - DEFINE_LOG_CATEGORY(LogHoudiniEngineBaking); - #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) - #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) \ - HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) -#else - #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() - #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) - #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) -#endif - - -// HAPI_Common -enum HAPI_UNREAL_NodeType -{ - HAPI_UNREAL_NODETYPE_ANY = -1, - HAPU_UNREAL_NODETYPE_NONE = 0 -}; - -enum HAPI_UNREAL_NodeFlags -{ - HAPI_UNREAL_NODEFLAGS_ANY = -1, - HAPI_UNREAL_NODEFLAGS_NONE = 0 -}; - -// Default cook/bake folder -#define HAPI_UNREAL_DEFAULT_BAKE_FOLDER TEXT("/Game/HoudiniEngine/Bake"); -#define HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER TEXT("/Game/HoudiniEngine/Temp"); - -// Default PDG Filters -#define HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER "HE_"; -#define HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER "HE_OUT_"; - -// Struct to enable global silent flag - this will force dialogs to not show up. -struct FHoudiniScopedGlobalSilence -{ - FHoudiniScopedGlobalSilence() - { - bGlobalSilent = GIsSilent; - GIsSilent = true; - } - - ~FHoudiniScopedGlobalSilence() - { - GIsSilent = bGlobalSilent; - } - - bool bGlobalSilent; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" + +// Define module names. +#define HOUDINI_MODULE "HoudiniEngine" +#define HOUDINI_MODULE_EDITOR "HoudiniEngineEditor" +#define HOUDINI_MODULE_RUNTIME "HoudiniEngineRuntime" + +// Declare the log category depending on the module we're in +#ifdef HOUDINI_ENGINE_EDITOR + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_EDITOR +HOUDINIENGINEEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineEditor, Log, All); +#else + #ifdef HOUDINI_ENGINE + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE + HOUDINIENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngine, Log, All); + #else + #define HOUDINI_LOCTEXT_NAMESPACE HOUDINI_MODULE_RUNTIME + HOUDINIENGINERUNTIME_API DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineRuntime, Log, All); + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------- +// Default session settings +//--------------------------------------------------------------------------------------------------------------------- + +#define HAPI_UNREAL_SESSION_SERVER_AUTOSTART true +#define HAPI_UNREAL_SESSION_SERVER_TIMEOUT 3000.0f +#define HAPI_UNREAL_SESSION_SERVER_HOST TEXT( "localhost" ) +#define HAPI_UNREAL_SESSION_SERVER_PORT 9090 +#if PLATFORM_MAC + #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "/tmp/hapi" ) +#else + #define HAPI_UNREAL_SESSION_SERVER_PIPENAME TEXT( "hapi" ) +#endif + + + +// Names of HAPI libraries on different platforms. +#define HAPI_LIB_OBJECT_WINDOWS TEXT( "libHAPIL.dll" ) +#define HAPI_LIB_OBJECT_MAC TEXT( "libHAPIL.dylib" ) +#define HAPI_LIB_OBJECT_LINUX TEXT( "libHAPIL.so" ) + +//--------------------------------------------------------------------------------------------------------------------- +// LOG MACROS +//--------------------------------------------------------------------------------------------------------------------- + +// Whether to enable logging. +#define HOUDINI_ENGINE_LOGGING 1 + +#ifdef HOUDINI_ENGINE_LOGGING + #ifdef HOUDINI_ENGINE + #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngine, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #else + #ifdef HOUDINI_ENGINE_EDITOR + #define HOUDINI_LOG_HELPER(VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngineEditor, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #else + #define HOUDINI_LOG_HELPER( VERBOSITY, HOUDINI_LOG_TEXT, ... ) \ + do \ + { \ + UE_LOG( LogHoudiniEngineRuntime, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + #endif + #endif + + #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + + #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_LOG_HELPER( Display, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_LOG_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_WARNING( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LOG_DISPLAY( HOUDINI_LOG_TEXT, ... ) +#endif + + +#define HOUDINI_DEBUG_EXPAND_UE_LOG(LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ...) \ + do \ + { \ + UE_LOG( LogHoudiniEngine##LONG_NAME, VERBOSITY, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ); \ + } \ + while ( 0 ) + + +// --------------------------------------------------------- +// Blueprint Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_BP=1 to enable Blueprint logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BP) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBlueprint, Log, All); + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineBlueprint); + #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Blueprint, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_BP_DEFINE_LOG_CATEGORY() + #define HOUDINI_BP_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BP_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + +// --------------------------------------------------------- +// PDG Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_PDG=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_PDG) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEnginePDG, Log, All); + #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEnginePDG); + #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( PDG, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_PDG_DEFINE_LOG_CATEGORY() + #define HOUDINI_PDG_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_PDG_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + +// --------------------------------------------------------- +// Landscape Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_LANDSCAPE=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineLandscape, Log, All); + #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineLandscape); + #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY() + #define HOUDINI_LANDSCAPE_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_LANDSCAPE_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + +// --------------------------------------------------------- +// Baking Debug Logging +// --------------------------------------------------------- +// Set HOUDINI_ENGINE_DEBUG_BAKING=1 to enable PDG logging +#if defined(HOUDINI_ENGINE_LOGGING) && defined(HOUDINI_ENGINE_DEBUG_BAKING) + DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniEngineBaking, Log, All); + #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() \ + DEFINE_LOG_CATEGORY(LogHoudiniEngineBaking); + #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Log, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Fatal, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Error, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) + #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) \ + HOUDINI_DEBUG_EXPAND_UE_LOG( Landscape, Warning, HOUDINI_LOG_TEXT, ##__VA_ARGS__ ) +#else + #define HOUDINI_BAKING_DEFINE_LOG_CATEGORY() + #define HOUDINI_BAKING_MESSAGE( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_FATAL( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_ERROR( HOUDINI_LOG_TEXT, ... ) + #define HOUDINI_BAKING_WARNING( HOUDINI_LOG_TEXT, ... ) +#endif + + +// HAPI_Common +enum HAPI_UNREAL_NodeType +{ + HAPI_UNREAL_NODETYPE_ANY = -1, + HAPU_UNREAL_NODETYPE_NONE = 0 +}; + +enum HAPI_UNREAL_NodeFlags +{ + HAPI_UNREAL_NODEFLAGS_ANY = -1, + HAPI_UNREAL_NODEFLAGS_NONE = 0 +}; + +// Default cook/bake folder +#define HAPI_UNREAL_DEFAULT_BAKE_FOLDER TEXT("/Game/HoudiniEngine/Bake"); +#define HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER TEXT("/Game/HoudiniEngine/Temp"); + +// Various variable names used to store meta information in generated packages. +// More in HoudiniEnginePrivatePCH.h +#define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) + +// Default PDG Filters +#define HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER "HE_"; +#define HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER "HE_OUT_"; + +// Struct to enable global silent flag - this will force dialogs to not show up. +struct FHoudiniScopedGlobalSilence +{ + FHoudiniScopedGlobalSilence() + { + bGlobalSilent = GIsSilent; + GIsSilent = true; + } + + ~FHoudiniScopedGlobalSilence() + { + GIsSilent = bGlobalSilent; + } + + bool bGlobalSilent; +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp index d2b563a12..46374fd7e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp @@ -561,7 +561,7 @@ FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties() SMGP.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; SMGP.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; SMGP.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; - SMGP.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; + //SMGP.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; SMGP.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; SMGP.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; SMGP.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h index ffa4c1063..b0f9ac3eb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h @@ -29,6 +29,7 @@ #include "UObject/ObjectMacros.h" #include "UObject/UObjectGlobals.h" #include "UObject/Class.h" +#include "UObject/Package.h" #if WITH_EDITOR #include "SSCSEditor.h" diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp index 0d23dc46d..1da5f8fb6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp @@ -437,7 +437,24 @@ FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( void* OutContainer = nullptr; FProperty* FoundProperty = nullptr; UObject* FoundPropertyObject = nullptr; - if (!FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) + +#if WITH_EDITOR + // Try to match to source model properties when possible + if (UStaticMesh* SM = Cast(InObject)) + { + if (SM && !SM->IsPendingKill() && SM->GetNumSourceModels() > AtIndex) + { + bool bFoundProperty = false; + TryToFindProperty(&SM->GetSourceModel(AtIndex), SM->GetSourceModel(AtIndex).StaticStruct(), PropertyName, FoundProperty, bFoundProperty, OutContainer); + if (bFoundProperty) + { + FoundPropertyObject = InObject; + } + } + } +#endif + + if (!FoundProperty && !FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) return false; // Modify the Property we found diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h index bc42a5d9a..d79ee7c89 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h @@ -739,7 +739,8 @@ struct FHoudiniBrushInfo HashCombine(Hash, Poly.TextureU); HashCombine(Hash, Poly.TextureV); HashCombine(Hash, Poly.Normal); - HashCombine(Hash, (uint64)(Poly.Material)); + // Do not add addresses to the hash, otherwise it would force a recook every unreal session! + // HashCombine(Hash, (uint64)(Poly.Material)); } }; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp index d0bb82f4e..12792c8db 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp @@ -35,6 +35,7 @@ #include "Components/SplineComponent.h" #include "Misc/StringFormatArg.h" #include "Engine/Engine.h" +#include "LandscapeLayerInfoObject.h" UHoudiniLandscapePtr::UHoudiniLandscapePtr(class FObjectInitializer const& Initializer) { @@ -403,6 +404,28 @@ FHoudiniBakedOutputObject::GetBlueprintIfValid(bool bInTryLoad) const return Cast(Object); } +ULandscapeLayerInfoObject* +FHoudiniBakedOutputObject::GetLandscapeLayerInfoIfValid(const FName& InLayerName, const bool bInTryLoad) const +{ + if (!LandscapeLayers.Contains(InLayerName)) + return nullptr; + + const FString& LayerInfoPathStr = LandscapeLayers.FindChecked(InLayerName); + const FSoftObjectPath LayerInfoPath(LayerInfoPathStr); + + if (!LayerInfoPath.IsValid()) + return nullptr; + + UObject* Object = LayerInfoPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = LayerInfoPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + UHoudiniOutput::UHoudiniOutput(const FObjectInitializer & ObjectInitializer) : Super(ObjectInitializer) diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h index 217ef7255..2f57036dd 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h @@ -35,6 +35,7 @@ #include "HoudiniOutput.generated.h" class UMaterialInterface; +class ULandscapeLayerInfoObject; UENUM() enum class EHoudiniOutputType : uint8 @@ -282,6 +283,10 @@ struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput UPROPERTY() TArray TransformVariationIndices; + // Original Indices of the variation instances + UPROPERTY() + TArray OriginalInstanceIndices; + // Indicates this instanced output's component should be recreated UPROPERTY() bool bChanged = false; @@ -321,6 +326,9 @@ struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject // Returns Blueprint if valid, otherwise nullptr UBlueprint* GetBlueprintIfValid(bool bInTryLoad=true) const; + // Returns the ULandscapeLayerInfoObject, if valid and found in LandscapeLayers, otherwise nullptr + ULandscapeLayerInfoObject* GetLandscapeLayerInfoIfValid(const FName& InLayerName, const bool bInTryLoad=true) const; + // The actor that the baked output was associated with UPROPERTY() FString Actor; @@ -348,6 +356,10 @@ struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject // In the case of mesh split instancer baking: this is the array of instance components UPROPERTY() TArray InstancedComponents; + + // For landscapes this is the previously bake layer info assets (layer name as key, soft object path as value) + UPROPERTY() + TMap LandscapeLayers; }; // Container to hold the map of baked objects. There should be one of @@ -589,6 +601,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject UPROPERTY() TMap AssignementMaterials; + // The material replacements for this output UPROPERTY() TMap ReplacementMaterials; @@ -613,7 +626,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject bool bIsEditableNode; // An editable node is only built once. This flag indicates whether this node has been built. - UPROPERTY(DuplicateTransient) + // Transient, so resets every unreal session so curves must be rebuilt to work properly. + UPROPERTY(Transient, DuplicateTransient) bool bHasEditableNodeBuilt; // The IsUpdating flag is set to true when this out exists and is being updated. diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp index 145207ac7..e41eb188e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp @@ -1,1979 +1,1997 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGAssetLink.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniOutput.h" - -#include "Engine/StaticMesh.h" -#include "GameFramework/Actor.h" -#include "Landscape.h" - -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#if WITH_EDITOR - #include "FileHelpers.h" - #include "EditorModeManager.h" - #include "EditorModes.h" -#endif - -// -UHoudiniPDGAssetLink::UHoudiniPDGAssetLink(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , AssetName() - , AssetNodePath() - , AssetID(-1) - , SelectedTOPNetworkIndex(-1) - , LinkState(EPDGLinkState::Inactive) - , bAutoCook(false) - , bUseTOPNodeFilter(true) - , bUseTOPOutputFilter(true) - , NumWorkitems(0) - , WorkItemTally() - , OutputCachePath() - , bNeedsUIRefresh(false) - , OutputParentActor(nullptr) -{ - TOPNodeFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER; - TOPOutputFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER; - -#if WITH_EDITORONLY_DATA - bBakeMenuExpanded = true; - HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; - PDGBakeSelectionOption = EPDGBakeSelectionOption::All; - PDGBakePackageReplaceMode = EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - bRecenterBakedActors = false; - bBakeAfterAllWorkResultObjectsLoaded = false; -#endif - - // Folder used for baking PDG outputs - BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - // TODO: - // Update init, move default filter to PCH -} - -FTOPWorkResultObject::FTOPWorkResultObject() -{ - // ResultObjects = nullptr; - Name = FString(); - FilePath = FString(); - State = EPDGWorkResultState::None; - WorkItemResultInfoIndex = INDEX_NONE; - bAutoBakedSinceLastLoad = false; -} - -FTOPWorkResultObject::~FTOPWorkResultObject() -{ - // DestroyResultOutputs(); -} - -FTOPWorkResult::FTOPWorkResult() -{ - WorkItemIndex = -1; - WorkItemID = -1; - - ResultObjects.SetNum(0); -} - -bool -FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const -{ - if (WorkItemIndex != OtherWorkResult.WorkItemIndex) - return false; - if (WorkItemID != OtherWorkResult.WorkItemID) - return false; - /* - if (ResultObjects != OtherWorkResult.ResultObjects) - return false; - */ - - return true; -} - -void -FTOPWorkResult::ClearAndDestroyResultObjects() -{ - if (ResultObjects.Num() <= 0) - return; - - for (FTOPWorkResultObject& ResultObject : ResultObjects) - { - ResultObject.DestroyResultOutputsAndRemoveOutputActor(); - } - - ResultObjects.Empty(); -} - -int32 -FTOPWorkResult::IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) -{ - const int32 NumEntries = ResultObjects.Num(); - for (int32 Index = 0; Index < NumEntries; ++Index) - { - const FTOPWorkResultObject& CurResultObject = ResultObjects[Index]; - if (CurResultObject.WorkItemResultInfoIndex == InWorkItemResultInfoIndex) - { - return Index; - } - } - - return INDEX_NONE; -} - -FTOPWorkResultObject* -FTOPWorkResult::GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) -{ - const int32 ArrayIndex = IndexOfWorkResultObjectByHAPIResultInfoIndex(InWorkItemResultInfoIndex); - if (ArrayIndex == INDEX_NONE) - { - return nullptr; - } - - return GetWorkResultObjectByArrayIndex(ArrayIndex); -} - -FTOPWorkResultObject* -FTOPWorkResult::GetWorkResultObjectByArrayIndex(const int32& InArrayIndex) -{ - if (!ResultObjects.IsValidIndex(InArrayIndex)) - { - return nullptr; - } - - return &ResultObjects[InArrayIndex]; -} - - -FWorkItemTallyBase::~FWorkItemTallyBase() -{ -} - -bool -FWorkItemTallyBase::AreAllWorkItemsComplete() const -{ - return ( - NumWaitingWorkItems() == 0 && NumCookingWorkItems() == 0 && NumScheduledWorkItems() == 0 - && (NumWorkItems() == (NumCookedWorkItems() + NumErroredWorkItems())) ); -} - -bool -FWorkItemTallyBase::AnyWorkItemsFailed() const -{ - return NumErroredWorkItems() > 0; -} - -bool -FWorkItemTallyBase::AnyWorkItemsPending() const -{ - return (NumWorkItems() > 0 && (NumWaitingWorkItems() > 0 || NumCookingWorkItems() > 0 || NumScheduledWorkItems() > 0)); -} - -FString -FWorkItemTallyBase::ProgressRatio() const -{ - const float Ratio = NumWorkItems() > 0 ? (NumCookedWorkItems() / NumWorkItems()) * 100.f : 0; - - return FString::Printf(TEXT("%.1f%%"), Ratio); -} - - -FWorkItemTally::FWorkItemTally() -{ - AllWorkItems.Empty(); - WaitingWorkItems.Empty(); - ScheduledWorkItems.Empty(); - CookingWorkItems.Empty(); - CookedWorkItems.Empty(); - ErroredWorkItems.Empty(); - CookCancelledWorkItems.Empty(); -} - -void -FWorkItemTally::ZeroAll() -{ - AllWorkItems.Empty(); - WaitingWorkItems.Empty(); - ScheduledWorkItems.Empty(); - CookingWorkItems.Empty(); - CookedWorkItems.Empty(); - ErroredWorkItems.Empty(); - CookCancelledWorkItems.Empty(); -} - -void -FWorkItemTally::RemoveWorkItem(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - AllWorkItems.Remove(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsWaiting(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - WaitingWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsScheduled(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - ScheduledWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsCooking(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - CookingWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsCooked(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - CookedWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsErrored(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - ErroredWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RecordWorkItemAsCookCancelled(int32 InWorkItemID) -{ - RemoveWorkItemFromAllStateSets(InWorkItemID); - CookCancelledWorkItems.Add(InWorkItemID); - AllWorkItems.Add(InWorkItemID); -} - -void -FWorkItemTally::RemoveWorkItemFromAllStateSets(int32 InWorkItemID) -{ - WaitingWorkItems.Remove(InWorkItemID); - ScheduledWorkItems.Remove(InWorkItemID); - CookingWorkItems.Remove(InWorkItemID); - CookedWorkItems.Remove(InWorkItemID); - ErroredWorkItems.Remove(InWorkItemID); - CookCancelledWorkItems.Remove(InWorkItemID); -} - - -FAggregatedWorkItemTally::FAggregatedWorkItemTally() -{ - TotalWorkItems = 0; - WaitingWorkItems = 0; - ScheduledWorkItems = 0; - CookingWorkItems = 0; - CookedWorkItems = 0; - ErroredWorkItems = 0; - CookCancelledWorkItems = 0; -} - -void -FAggregatedWorkItemTally::ZeroAll() -{ - TotalWorkItems = 0; - WaitingWorkItems = 0; - ScheduledWorkItems = 0; - CookingWorkItems = 0; - CookedWorkItems = 0; - ErroredWorkItems = 0; - CookCancelledWorkItems = 0; -} - -void -FAggregatedWorkItemTally::Add(const FWorkItemTallyBase& InWorkItemTally) -{ - TotalWorkItems += InWorkItemTally.NumWorkItems(); - WaitingWorkItems += InWorkItemTally.NumWaitingWorkItems(); - ScheduledWorkItems += InWorkItemTally.NumScheduledWorkItems(); - CookingWorkItems += InWorkItemTally.NumCookingWorkItems(); - CookedWorkItems += InWorkItemTally.NumCookedWorkItems(); - ErroredWorkItems += InWorkItemTally.NumErroredWorkItems(); - CookCancelledWorkItems += InWorkItemTally.NumCookCancelledWorkItems(); -} - -void -FAggregatedWorkItemTally::Subtract(const FWorkItemTallyBase& InWorkItemTally) -{ - TotalWorkItems -= InWorkItemTally.NumWorkItems(); - WaitingWorkItems -= InWorkItemTally.NumWaitingWorkItems(); - ScheduledWorkItems -= InWorkItemTally.NumScheduledWorkItems(); - CookingWorkItems -= InWorkItemTally.NumCookingWorkItems(); - CookedWorkItems -= InWorkItemTally.NumCookedWorkItems(); - ErroredWorkItems -= InWorkItemTally.NumErroredWorkItems(); - CookCancelledWorkItems -= InWorkItemTally.NumCookCancelledWorkItems(); -} - - -UTOPNode::UTOPNode() -{ - NodeId = -1; - NodeName = FString(); - NodePath = FString(); - ParentName = FString(); - - WorkResultParent = nullptr; - WorkResult.SetNum(0); - - bHidden = false; - bAutoLoad = false; - - NodeState = EPDGNodeState::None; - - bCachedHaveNotLoadedWorkResults = false; - bCachedHaveLoadedWorkResults = false; - bHasChildNodes = false; - - bShow = false; - - bHasReceivedCookCompleteEvent = false; - - InvalidateLandscapeCache(); -} - -bool -UTOPNode::operator==(const UTOPNode& Other) const -{ - if (!NodeName.Equals(Other.NodeName)) - return false; - - if (!ParentName.Equals(Other.ParentName)) - return false; - - //if (NodeId != Other.NodeId) - // return false; - - return true; -} - -void -UTOPNode::Reset() -{ - NodeState = EPDGNodeState::None; - WorkItemTally.ZeroAll(); - AggregatedWorkItemTally.ZeroAll(); -} - -void UTOPNode::OnWorkItemWaiting(int32 InWorkItemID) -{ - FTOPWorkResult* const WorkItem = GetWorkResultByID(InWorkItemID); - if (WorkItem) - { - // Clear the bAutoBakedSinceLastLoad flag on the work results since we are expecting a cook of the work item - for (FTOPWorkResultObject& WRO : WorkItem->ResultObjects) - { - WRO.SetAutoBakedSinceLastLoad(false); - } - } - WorkItemTally.RecordWorkItemAsWaiting(InWorkItemID); -} - -void -UTOPNode::OnWorkItemCooked(int32 InWorkItemID) -{ - if (GetWorkItemTally().NumCookedWorkItems() == 0) - { - // We want to invalidate the landscape cache values in any situation where - // all the work items are being recooked. - InvalidateLandscapeCache(); - } - WorkItemTally.RecordWorkItemAsCooked(InWorkItemID); -} - -void -UTOPNode::SetVisibleInLevel(bool bInVisible) -{ - if (bShow == bInVisible) - return; - - bShow = bInVisible; - UpdateOutputVisibilityInLevel(); -} - -void -UTOPNode::UpdateOutputVisibilityInLevel() -{ - AActor* Actor = OutputActorOwner.GetOutputActor(); - if (IsValid(Actor)) - { - Actor->SetHidden(!bShow); -#if WITH_EDITOR - Actor->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - AActor* WROActor = WRO.GetOutputActorOwner().GetOutputActor(); - if (IsValid(WROActor)) - { - WROActor->SetHidden(!bShow); -#if WITH_EDITOR - WROActor->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - - // We need to manually handle child landscape's visiblity - for (UHoudiniOutput* ResultOutput : WRO.GetResultOutputs()) - { - if (!ResultOutput || ResultOutput->IsPendingKill()) - continue; - - for (auto& Pair : ResultOutput->GetOutputObjects()) - { - FHoudiniOutputObject& OutputObject = Pair.Value; - ALandscapeProxy* LandscapeProxy = Cast(OutputObject.OutputObject); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) - continue; - - ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); - if (!Landscape || Landscape->IsPendingKill()) - continue; - - Landscape->SetHidden(!bShow); -#if WITH_EDITOR - Landscape->SetIsTemporarilyHiddenInEditor(!bShow); -#endif - } - } - } - } -} - -void -UTOPNode::SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad) -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::NotLoaded || - (WRO.State == EPDGWorkResultState::Deleted && bInAlsoSetDeletedToLoad)) - { - WRO.State = EPDGWorkResultState::ToLoad; - WRO.SetAutoBakedSinceLastLoad(false); - } - } - } -} - -void -UTOPNode::SetLoadedWorkResultsToDelete() -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::Loaded) - WRO.State = EPDGWorkResultState::ToDelete; - } - } -} - - -void -UTOPNode::DeleteWorkResultOutputObjects() -{ - for (FTOPWorkResult& WorkItem : WorkResult) - { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::Loaded) - { - // Delete and clean up that WRObj - WRO.DestroyResultOutputs(); - WRO.GetOutputActorOwner().DestroyOutputActor(); - WRO.State = EPDGWorkResultState::Deleted; - } - } - } - bCachedHaveLoadedWorkResults = false; -} - -FString -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex) -{ - return FString::Printf(TEXT("%d_%d"), InWorkItemIndex, InWorkResultObjectArrayIndex); -} - -FString -UTOPNode::GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex) -{ - return GetBakedWorkResultObjectOutputsKey(InWorkResult.WorkItemIndex, InWorkResultObjectArrayIndex); -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const -{ - // Check that indices are valid - if (!WorkResult.IsValidIndex(InWorkResultArrayIndex)) - return false; - const FTOPWorkResult& WorkResultEntry = WorkResult[InWorkResultArrayIndex]; - if (!WorkResultEntry.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) - return false; - - OutKey = GetBakedWorkResultObjectOutputsKey(WorkResultEntry, InWorkResultObjectArrayIndex); - - return true; -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) -{ - FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) - return false; - OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); - if (!OutBakedOutput) - return false; - - return true; -} - -bool -UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const -{ - FString Key; - if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) - return false; - OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); - if (!OutBakedOutput) - return false; - - return true; -} - -int32 -UTOPNode::IndexOfWorkResultByID(const int32& InWorkItemID) -{ - const int32 NumEntries = WorkResult.Num(); - for (int32 Index = 0; Index < NumEntries; ++Index) - { - const FTOPWorkResult& CurResult = WorkResult[Index]; - if (CurResult.WorkItemID == InWorkItemID) - { - return Index; - } - } - - return INDEX_NONE; -} - -FTOPWorkResult* -UTOPNode::GetWorkResultByID(const int32& InWorkItemID) -{ - const int32 ArrayIndex = IndexOfWorkResultByID(InWorkItemID); - if (!WorkResult.IsValidIndex(ArrayIndex)) - return nullptr; - - return &WorkResult[ArrayIndex]; -} - -int32 -UTOPNode::IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) -{ - const int32 NumEntries = WorkResult.Num(); - for (int32 Index = 0; Index < NumEntries; ++Index) - { - const FTOPWorkResult& CurResult = WorkResult[Index]; - if (CurResult.WorkItemIndex == InWorkItemIndex && (!bInWithInvalidWorkItemID || CurResult.WorkItemID == INDEX_NONE)) - { - return Index; - } - } - - return INDEX_NONE; -} - -FTOPWorkResult* -UTOPNode::GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) -{ - const int32 ArrayIndex = IndexOfWorkResultByHAPIIndex(InWorkItemIndex, bInWithInvalidWorkItemID); - if (!WorkResult.IsValidIndex(ArrayIndex)) - return nullptr; - return &WorkResult[ArrayIndex]; -} - -FTOPWorkResult* -UTOPNode::GetWorkResultByArrayIndex(const int32& InArrayIndex) -{ - if (!WorkResult.IsValidIndex(InArrayIndex)) - return nullptr; - return &WorkResult[InArrayIndex]; -} - -bool -UTOPNode::IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const -{ - if (!IsValid(InNetwork)) - { - return false; - } - - return ParentName == FString::Printf(TEXT("%s_%s"), *InNetwork->ParentName, *InNetwork->NodeName); -} - -bool -UTOPNode::CanStillBeAutoBaked() const -{ - // Only nodes that have results auto-loaded are auto-baked - if (!bAutoLoad) - return false; - - // Nodes with failures are not auto-baked - if (AnyWorkItemsFailed()) - return false; - - // All work items are not yet complete, so node cannot yet be baked - if (!AreAllWorkItemsComplete()) - return true; - - // Work items that are currently loaded or has not tagged has auto baked since last load can still be baked - for (const FTOPWorkResult& WorkResultEntry : WorkResult) - { - for (const FTOPWorkResultObject& WRO : WorkResultEntry.ResultObjects) - { - switch (WRO.State) - { - case EPDGWorkResultState::NotLoaded: - case EPDGWorkResultState::ToLoad: - case EPDGWorkResultState::Loading: - return true; - case EPDGWorkResultState::Loaded: - if (!WRO.AutoBakedSinceLastLoad()) - return true; - break; - case EPDGWorkResultState::ToDelete: - case EPDGWorkResultState::Deleting: - case EPDGWorkResultState::Deleted: - case EPDGWorkResultState::None: - break; - } - } - } - - return false; -} - -#if WITH_EDITOR -void -UTOPNode::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) -{ - Super::PostEditChangeChainProperty(InPropertyChangedEvent); - - const FName PropertyName = InPropertyChangedEvent.GetPropertyName(); - if (PropertyName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) - { - UpdateOutputVisibilityInLevel(); - } -} -#endif - -#if WITH_EDITOR -void -UTOPNode::PostTransacted(const FTransactionObjectEvent& TransactionEvent) -{ - Super::PostTransacted(TransactionEvent); - - if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) - return; - - bool bUpdateVisibility = false; - for (const FName& PropName : TransactionEvent.GetChangedProperties()) - { - if (PropName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) - { - bUpdateVisibility = true; - } - } - - if (bUpdateVisibility) - UpdateOutputVisibilityInLevel(); -} -#endif - -void -UTOPNode::OnDirtyNode() -{ - InvalidateLandscapeCache(); - bHasReceivedCookCompleteEvent = false; -} - -void -UTOPNode::InvalidateLandscapeCache() -{ - LandscapeReferenceLocation.bIsCached = false; - LandscapeSizeInfo.bIsCached = false; - ClearedLandscapeLayers.Empty(); -} - -UTOPNetwork::UTOPNetwork() -{ - NodeId = -1; - NodeName = FString(); - - AllTOPNodes.SetNum(0); - SelectedTOPIndex = -1; - - ParentName = FString(); - - bShowResults = false; - bAutoLoadResults = false; -} - -bool -UTOPNetwork::operator==(const UTOPNetwork& Other) const -{ - if (!NodeName.Equals(Other.NodeName)) - return false; - - if (!ParentName.Equals(Other.ParentName)) - return false; - - //if (NodeId != Other.NodeId) - // return false; - - return true; -} - -void -UTOPNetwork::SetLoadedWorkResultsToDelete() -{ - for (UTOPNode* Node : AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - Node->SetLoadedWorkResultsToDelete(); - } -} - -void -UTOPNetwork::DeleteWorkResultOutputObjects() -{ - for (UTOPNode* Node : AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - Node->DeleteWorkResultOutputObjects(); - } -} - -bool -UTOPNetwork::AnyWorkItemsPending() const -{ - for (const UTOPNode* const TOPNode : AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->AnyWorkItemsPending()) - return true; - } - - return false; -} - -bool -UTOPNetwork::AnyWorkItemsFailed() const -{ - for (const UTOPNode* const TOPNode : AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->AnyWorkItemsFailed()) - return true; - } - - return false; -} - -bool -UTOPNetwork::CanStillBeAutoBaked() const -{ - for (const UTOPNode* const TOPNode : AllTOPNodes) - { - if (TOPNode->CanStillBeAutoBaked()) - return true; - } - - return false; -} - -void -UTOPNetwork::HandleOnPDGEventCookCompleteReceivedByChildNode(UHoudiniPDGAssetLink* const InAssetLink, UTOPNode* const InTOPNode) -{ - if (!IsValid(InAssetLink)) - return; - - // Check if all nodes have recieved the HAPI_PDG_EVENT_COOK_COMPLETE event, if so, broadcast the OnPostCook handler. - for (const UTOPNode* const TOPNode : AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (!TOPNode->HasReceivedCookCompleteEvent()) - return; - } - - if (OnPostCookDelegate.IsBound()) - OnPostCookDelegate.Broadcast(this, AnyWorkItemsFailed()); - - InAssetLink->HandleOnTOPNetworkCookComplete(this); -} - -void -UHoudiniPDGAssetLink::SelectTOPNetwork(const int32& AtIndex) -{ - if (!AllTOPNetworks.IsValidIndex(AtIndex)) - return; - - SelectedTOPNetworkIndex = AtIndex; -} - - -void -UHoudiniPDGAssetLink::SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex) -{ - if (!IsValid(InTOPNetwork)) - return; - - if (!InTOPNetwork->AllTOPNodes.IsValidIndex(AtIndex)) - return; - - InTOPNetwork->SelectedTOPIndex = AtIndex; -} - - -UTOPNetwork* -UHoudiniPDGAssetLink::GetSelectedTOPNetwork() -{ - return GetTOPNetwork(SelectedTOPNetworkIndex); -} - -const UTOPNetwork* -UHoudiniPDGAssetLink::GetSelectedTOPNetwork() const -{ - return GetTOPNetwork(SelectedTOPNetworkIndex); -} - -UTOPNode* -UHoudiniPDGAssetLink::GetSelectedTOPNode() -{ - UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); - if (!IsValid(SelectedTOPNetwork)) - return nullptr; - - if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) - return nullptr; - - UTOPNode* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; - if (!IsValid(SelectedTOPNode)) - return nullptr; - - return SelectedTOPNode; -} - -const UTOPNode* -UHoudiniPDGAssetLink::GetSelectedTOPNode() const -{ - UTOPNetwork const* const SelectedTOPNetwork = GetSelectedTOPNetwork(); - if (!IsValid(SelectedTOPNetwork)) - return nullptr; - - if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) - return nullptr; - - UTOPNode const* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; - if (!IsValid(SelectedTOPNode)) - return nullptr; - - return SelectedTOPNode; -} - -FString -UHoudiniPDGAssetLink::GetSelectedTOPNodeName() -{ - FString NodeName = FString(); - - const UTOPNode* const SelectedTOPNode = GetSelectedTOPNode(); - if (IsValid(SelectedTOPNode)) - NodeName = SelectedTOPNode->NodeName; - - return NodeName; -} - -FString -UHoudiniPDGAssetLink::GetSelectedTOPNetworkName() -{ - FString NetworkName = FString(); - - const UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNetwork)) - NetworkName = SelectedTOPNetwork->NodeName; - - return NetworkName; -} - -UTOPNetwork* -UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) -{ - if(AllTOPNetworks.IsValidIndex(AtIndex)) - { - return AllTOPNetworks[AtIndex]; - } - - return nullptr; -} - -const UTOPNetwork* -UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) const -{ - if(AllTOPNetworks.IsValidIndex(AtIndex)) - { - return AllTOPNetworks[AtIndex]; - } - - return nullptr; -} - -UTOPNetwork* -UHoudiniPDGAssetLink::GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex) -{ - OutIndex = INDEX_NONE; - int32 Index = -1; - for (UTOPNetwork* CurrentTOPNet : InTOPNetworks) - { - Index += 1; - - if (!IsValid(CurrentTOPNet)) - continue; - - if (CurrentTOPNet->NodePath.Equals(InNodePath)) - { - OutIndex = Index; - return CurrentTOPNet; - } - } - - return nullptr; -} - -UTOPNode* -UHoudiniPDGAssetLink::GetParentTOPNode(const UTOPNode* InNode) -{ - if (!IsValid(InNode)) - return nullptr; - - FString NodePath = InNode->NodePath; - FString ParentPath; - - if (NodePath.EndsWith("/")) - NodePath.LeftChopInline(1); - - if (NodePath.Split("/", &ParentPath, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromEnd) && !ParentPath.IsEmpty()) - { - for (UTOPNetwork* TOPNet : AllTOPNetworks) - { - if (!IsValid(TOPNet)) - continue; - - for (UTOPNode* TOPNode : TOPNet->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->NodePath == ParentPath && InNode->NodeId != TOPNode->NodeId) - { - return TOPNode; - } - } - } - } - - return nullptr; -} - -UTOPNode* -UHoudiniPDGAssetLink::GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex) -{ - OutIndex = INDEX_NONE; - int32 Index = -1; - for (UTOPNode* CurrentTOPNode : InTOPNodes) - { - Index += 1; - - if (!IsValid(CurrentTOPNode)) - continue; - - if (CurrentTOPNode->NodePath.Equals(InNodePath)) - { - OutIndex = Index; - return CurrentTOPNode; - } - } - - return nullptr; -} - -void -UHoudiniPDGAssetLink::ClearAllTOPData() -{ - // Clears all TOP data - for(UTOPNetwork* CurrentNetwork : AllTOPNetworks) - { - if (!IsValid(CurrentNetwork)) - continue; - - for(UTOPNode* CurrentTOPNode : CurrentNetwork->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - ClearTOPNodeWorkItemResults(CurrentTOPNode); - } - } - - AllTOPNetworks.Empty(); -} - -void -UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork) -{ - if (!IsValid(TOPNetwork)) - return; - - for(UTOPNode* CurrentTOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - ClearTOPNodeWorkItemResults(CurrentTOPNode); - } -} - -void -UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) -{ - if (!IsValid(TOPNode)) - return; - - TOPNode->OnDirtyNode(); - - for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) - { - DestroyWorkItemResultData(CurrentWorkResult); - } - TOPNode->WorkResult.Empty(); - - FOutputActorOwner& OutputActorOwner = TOPNode->GetOutputActorOwner(); - AActor* OutputActor = OutputActorOwner.GetOutputActor(); - if (IsValid(OutputActor)) - { - // Destroy any attached actors (which we'll assume that any attachments left - // are untracked actors associated with the TOPNode) - TArray AttachedActors; - OutputActor->GetAttachedActors(AttachedActors); - for (AActor* Actor : AttachedActors) - { - if (!IsValid(Actor)) - continue; - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - } - } - - if (TOPNode->WorkResultParent && !TOPNode->WorkResultParent->IsPendingKill()) - { - - // TODO: Destroy the Parent Object - // DestroyImmediate(topNode._workResultParentGO); - } - - OutputActorOwner.DestroyOutputActor(); -} - - -void -UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); - if (WorkResult) - { - DestroyWorkItemResultData(*WorkResult); - // TODO: Should we destroy the FTOPWorkResult struct entirely here? - //TOPNode.WorkResult.RemoveByPredicate - } -} - -void -UHoudiniPDGAssetLink::DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - // TODO: Update ClearWorkItemResultByID or GetWorkResultByID to return the index of the work item - // so that we don't have to find its index again to remove it from the array - ClearWorkItemResultByID(InWorkItemID, InTOPNode); - // Find the index of the FTOPWorkResult for InWorkItemID in InTOPNode.WorkResult and remove it - const int32 Index = InTOPNode->WorkResult.IndexOfByPredicate( - [InWorkItemID](const FTOPWorkResult& InWorkItem) { return InWorkItem.WorkItemID == InWorkItemID; }); - if (Index != INDEX_NONE && Index >= 0) - InTOPNode->WorkResult.RemoveAt(Index); -} - -FTOPWorkResult* -UHoudiniPDGAssetLink::GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return nullptr; - return InTOPNode->GetWorkResultByID(InWorkItemID); -} - -FDirectoryPath -UHoudiniPDGAssetLink::GetTemporaryCookFolder() const -{ - UHoudiniAssetComponent* HAC = GetOuterHoudiniAssetComponent(); - if (HAC) - return HAC->TemporaryCookFolder; - - FDirectoryPath TempPath; - TempPath.Path = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - return TempPath; -} - -void -UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) -{ - ResultObject.DestroyResultOutputsAndRemoveOutputActor(); -} - -void -UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result) -{ - Result.ClearAndDestroyResultObjects(); -} - -void -UHoudiniPDGAssetLink::HandleOnTOPNetworkCookComplete(UTOPNetwork* const InTOPNet) -{ - if (!IsValid(InTOPNet)) - return; - - if (OnPostTOPNetworkCookDelegate.IsBound()) - { - OnPostTOPNetworkCookDelegate.Broadcast(this, InTOPNet, InTOPNet->AnyWorkItemsFailed()); - } -} - - -UTOPNode* -UHoudiniPDGAssetLink::GetTOPNode(const int32& InNodeID) -{ - UTOPNetwork* Network = nullptr; - UTOPNode* Node = nullptr; - - if (GetTOPNodeAndNetworkByNodeId(InNodeID, Network, Node)) - return Node; - - return nullptr; -} - - -bool -UHoudiniPDGAssetLink::GetTOPNodeAndNetworkByNodeId(const int32& InNodeID, UTOPNetwork*& OutNetwork, UTOPNode*& OutNode) -{ - OutNetwork = nullptr; - OutNode = nullptr; - - for (UTOPNetwork* CurrentTOPNet : AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - if (CurrentTOPNode->NodeId == InNodeID) - { - OutNetwork = CurrentTOPNet; - OutNode = CurrentTOPNode; - return true; - } - } - } - - return false; -} - - -void -UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork) -{ - if (!IsValid(InNode) || !IsValid(InNetwork)) - return; - - if (!InNode->bHasChildNodes) - return; - - FString PrefixPath = InNode->NodePath; - if (!PrefixPath.EndsWith("/")) - PrefixPath += "/"; - InNode->ZeroWorkItemTally(); - InNode->NodeState = EPDGNodeState::None; - - TMap NodeStateOrder; - NodeStateOrder.Add(EPDGNodeState::None, 0); - NodeStateOrder.Add(EPDGNodeState::Cook_Complete, 1); - NodeStateOrder.Add(EPDGNodeState::Dirtied, 2); - NodeStateOrder.Add(EPDGNodeState::Cook_Failed, 3); - NodeStateOrder.Add(EPDGNodeState::Dirtying, 4); - NodeStateOrder.Add(EPDGNodeState::Cooking, 5); - - int8 CurrentState = 0; - - for (const UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - if (Node->NodePath.StartsWith(PrefixPath) && !Node->bHasChildNodes) - { - InNode->AggregateTallyFromChildNode(Node); - const int8 VisitedNodeState = NodeStateOrder.FindChecked(Node->NodeState); - if (VisitedNodeState > CurrentState) - CurrentState = VisitedNodeState; - } - } - - EPDGNodeState const* const NewState = NodeStateOrder.FindKey(CurrentState); - if (NewState) - InNode->NodeState = *NewState; -} - -void -UHoudiniPDGAssetLink::UpdateWorkItemTally() -{ - WorkItemTally.ZeroAll(); - for(UTOPNetwork* CurrentTOPNet : AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - for(UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - // Only add up the tallys from nodes without children (since parent's aggregate the child work items counts) - if (CurrentTOPNode->bHasChildNodes) - { - UpdateTOPNodeWithChildrenWorkItemTallyAndState(CurrentTOPNode, CurrentTOPNet); - } - else - { - WorkItemTally.Add(CurrentTOPNode->GetWorkItemTally()); - } - } - } -} - - -void -UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork) -{ - if (!IsValid(TOPNetwork)) - return; - - for (UTOPNode* CurTOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(CurTOPNode)) - continue; - - CurTOPNode->ZeroWorkItemTally(); - } -} - - -FString -UHoudiniPDGAssetLink::GetAssetLinkStatus(const EPDGLinkState& InLinkState) -{ - FString Status; - switch (InLinkState) - { - case EPDGLinkState::Inactive: - Status = TEXT("Inactive"); - case EPDGLinkState::Linking: - Status = TEXT("Linking"); - case EPDGLinkState::Linked: - Status = TEXT("Linked"); - case EPDGLinkState::Error_Not_Linked: - Status = TEXT("Not Linked"); - default: - Status = TEXT(""); - } - - return Status; -} - -FString -UHoudiniPDGAssetLink::GetTOPNodeStatus(const UTOPNode* InTOPNode) -{ - static const FString InvalidOrUnknownStatus = TEXT(""); - - if (!IsValid(InTOPNode)) - return InvalidOrUnknownStatus; - - if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) - { - return TEXT("Cook Failed"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) - { - return TEXT("Cook Completed"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Cooking) - { - return TEXT("Cook In Progress"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) - { - return TEXT("Dirtied"); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) - { - return TEXT("Dirtying"); - } - - return InvalidOrUnknownStatus; -} - -FLinearColor -UHoudiniPDGAssetLink::GetTOPNodeStatusColor(const UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return FLinearColor::White; - - if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) - { - return FLinearColor::Red; - } - else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) - { - return FLinearColor::Green; - } - else if (InTOPNode->NodeState == EPDGNodeState::Cooking) - { - return FLinearColor(0.0, 1.0f, 1.0f); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) - { - return FLinearColor(1.0f, 0.5f, 0.0f); - } - else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) - { - return FLinearColor::Yellow; - } - - return FLinearColor::White; -} - -AActor* -UHoudiniPDGAssetLink::GetOwnerActor() const -{ - UObject* Outer = GetOuter(); - UActorComponent* Component = Cast(Outer); - if (IsValid(Component)) - return Component->GetOwner(); - else - return Cast(Outer); -} - -bool -UHoudiniPDGAssetLink::HasTemporaryOutputs() const -{ - // Loop over all networks, all nodes, all work items and check for any valid output objects - for (const UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (const UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - for (const FTOPWorkResult& WorkResult : TOPNode->WorkResult) - { - for (const FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) - { - // If the WorkResultObject's actor is not valid, then it no longer has temporary objects in the - // scene - if (!IsValid(WorkResultObject.GetOutputActorOwner().GetOutputActor())) - continue; - - for (UHoudiniOutput* Output : WorkResultObject.GetResultOutputs()) - { - if (!IsValid(Output)) - continue; - - const EHoudiniOutputType OutputType = Output->GetType(); - for (const auto& OutputObjectPair : Output->GetOutputObjects()) - { - if ((OutputType == EHoudiniOutputType::Landscape && IsValid(OutputObjectPair.Value.OutputObject)) || - IsValid(OutputObjectPair.Value.OutputComponent)) - { - return true; - } - } - } - } - } - } - } - - return false; -} - -void -UHoudiniPDGAssetLink::UpdatePostDuplicate() -{ - // Loop over all networks, all nodes, all work items and clear output actors - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - for (FTOPWorkResult& WorkResult : TOPNode->WorkResult) - { - for (FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) - { - WorkResultObject.GetOutputActorOwner().SetOutputActor(nullptr); - WorkResultObject.State = EPDGWorkResultState::None; - WorkResultObject.SetResultOutputs(TArray()); - } - } - TOPNode->GetOutputActorOwner().SetOutputActor(nullptr); - TOPNode->bCachedHaveNotLoadedWorkResults = false; - TOPNode->bCachedHaveLoadedWorkResults = false; - } - } -} - -UHoudiniAssetComponent* UHoudiniPDGAssetLink::GetOuterHoudiniAssetComponent() const -{ - return Cast( GetTypedOuter() ); -} - -void -UHoudiniPDGAssetLink::UpdateTOPNodeAutoloadAndVisibility() -{ - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - if (TOPNode->bAutoLoad) - { - // // Set work results that are cooked but in NotLoaded state to ToLoad - // TOPNode.SetNotLoadedWorkResultsToLoad(); - } - - TOPNode->UpdateOutputVisibilityInLevel(); - } - } -} - -void -UHoudiniPDGAssetLink::FilterTOPNodesAndOutputs() -{ - for (UTOPNetwork* TOPNetwork : AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) - { - if (!IsValid(TOPNode)) - continue; - - // TOP Node visibility filter via TOPNodeFilter - if (bUseTOPNodeFilter) - { - TOPNode->bHidden = !TOPNodeFilter.IsEmpty() && !TOPNode->NodeName.StartsWith(TOPNodeFilter); - } - else - { - TOPNode->bHidden = false; - } - - // Auto load results filter via TOPNodeOutputFilter - if (bUseTOPOutputFilter) - { - const bool bNewAutoLoad = TOPOutputFilter.IsEmpty() || TOPNode->NodeName.StartsWith(TOPOutputFilter); - if (bNewAutoLoad != TOPNode->bAutoLoad) - { - if (bNewAutoLoad) - { - // Set work results that are cooked but in NotLoaded state to ToLoad - TOPNode->bAutoLoad = true; - // TOPNode->SetNotLoadedWorkResultsToLoad(); - TOPNode->SetVisibleInLevel(true); - } - else - { - TOPNode->bAutoLoad = false; - TOPNode->SetVisibleInLevel(false); - } - TOPNode->UpdateOutputVisibilityInLevel(); - } - } - } - } -} - -#if WITH_EDITORONLY_DATA -bool -UHoudiniPDGAssetLink::AnyRemainingAutoBakeNodes() const -{ - if (!bBakeAfterAllWorkResultObjectsLoaded) - return false; - - switch (PDGBakeSelectionOption) - { - case EPDGBakeSelectionOption::All: - { - for (const UTOPNetwork* const TOPNet : AllTOPNetworks) - { - if (!IsValid(TOPNet)) - continue; - - if (TOPNet->CanStillBeAutoBaked()) - { - return true; - } - } - break; - } - case EPDGBakeSelectionOption::SelectedNetwork: - { - const UTOPNetwork* const TOPNet = GetSelectedTOPNetwork(); - if (IsValid(TOPNet) && TOPNet->CanStillBeAutoBaked()) - { - return true; - } - } - case EPDGBakeSelectionOption::SelectedNode: - { - UTOPNode const* const TOPNode = GetSelectedTOPNode(); - if (IsValid(TOPNode) && TOPNode->CanStillBeAutoBaked()) - { - return true; - } - } - default: - return false; - } - - return false; -} -#endif - -void -UHoudiniPDGAssetLink::HandleOnPostBake(const bool bInSuccess) -{ - if (OnPostBakeDelegate.IsBound()) - OnPostBakeDelegate.Broadcast(this, bInSuccess); -} - -#if WITH_EDITORONLY_DATA -void -UHoudiniPDGAssetLink::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedChainEvent) -{ - Super::PostEditChangeChainProperty(InPropertyChangedChainEvent); - - const FName PropertyName = InPropertyChangedChainEvent.GetPropertyName(); - if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) - { - // Refilter TOP nodes - FilterTOPNodesAndOutputs(); - bNeedsUIRefresh = true; - } - else if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || - PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) - { - bNeedsUIRefresh = true; - } -} - -#endif - -#if WITH_EDITORONLY_DATA -void -UHoudiniPDGAssetLink::PostTransacted(const FTransactionObjectEvent& TransactionEvent) -{ - Super::PostTransacted(TransactionEvent); - - if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) - return; - - bool bDoFilterTOPNodesAndOutputs = false; - for (const FName& PropName : TransactionEvent.GetChangedProperties()) - { - if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) - { - bDoFilterTOPNodesAndOutputs = true; - bNeedsUIRefresh = true; - } - else if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode) || - PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) - { - bNeedsUIRefresh = true; - } - } - - if (bDoFilterTOPNodesAndOutputs) - FilterTOPNodesAndOutputs(); -} -#endif - -void -FTOPWorkResultObject::DestroyResultOutputs() -{ - // Delete output components and gather output objects for deletion - bool bDidDestroyObjects = false; - bool bDidModifyFoliage = false; - - AActor* const OutputActor = OutputActorOwner.GetOutputActor(); - - for (UHoudiniOutput* CurOutput : ResultOutputs) - { - for (auto& Pair : CurOutput->GetOutputObjects()) - { - FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - FHoudiniOutputObject& OutputObject = Pair.Value; - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) - { - // Instancer components require some special handling around foliage - // TODO: move/refactor so that we can use the InstanceTranslator's helper functions (RemoveAndDestroyComponent and CleanupFoliageInstances) - bool bDestroyComponent = true; - if (OutputObject.OutputComponent->IsA()) - { - UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OutputObject.OutputComponent); - if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) - { - // Make sure foliage our foliage instances have been removed - USceneComponent* ParentComponent = nullptr; - if (IsValid(OutputActor)) - ParentComponent = Cast(OutputActor->GetRootComponent()); - else - ParentComponent = Cast(HISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) - { - UStaticMesh* FoliageSM = HISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - return; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(HISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return; - - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - return; -#if WITH_EDITOR - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); - - // Remove the foliage type if it doesn't have any more instances - if(HISMC->GetInstanceCount() == 0) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - bDidModifyFoliage = true; -#endif - } - - // // do not delete FISMC that still have instances left - // // as we have cleaned up our instances before, these have been hand-placed - // if (HISMC->GetInstanceCount() > 0) - bDestroyComponent = false; - - OutputObject.OutputComponent = nullptr; - } - } - - if (bDestroyComponent) - { - USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from its actor first - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - // Detach from its parent component if attached - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - bDidDestroyObjects = true; - - OutputObject.OutputComponent = nullptr; - } - } - } - if (OutputObject.OutputObject && !OutputObject.OutputObject->IsPendingKill()) - { - // For actors we detach them first and then destroy - AActor* Actor = Cast(OutputObject.OutputObject); - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - if (LandscapePtr) - { - Actor = LandscapePtr->GetRawPtr(); - } - - if (Actor) - { - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - - bDidDestroyObjects = true; - } - else - { - // ... if not an actor, mark as pending kill - // OutputObject.OutputObject->MarkPendingKill(); - if (IsValid(OutputObject.OutputObject)) - OutputObjectsToDelete.Add(OutputObject.OutputObject); - OutputObject.OutputObject = nullptr; - } - } - } - } - - ResultOutputs.Empty(); - - if (bDidDestroyObjects) - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - // Delete the output objects we found - if (OutputObjectsToDelete.Num() > 0) - FHoudiniEngineRuntimeUtils::SafeDeleteObjects(OutputObjectsToDelete); - -#if WITH_EDITOR - if (bDidModifyFoliage) - { - // Repopulate the foliage types in the foliage mode UI if foliage mode is active - // There is a helper function in FHoudiniEngineUtils for this, but we cannot access it from this module. - // TODO: refactor? - FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); - if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) - { - EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); - EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); - } - } -#endif -} - -void FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor() -{ - DestroyResultOutputs(); - GetOutputActorOwner().DestroyOutputActor(); -} - -bool -FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) -{ - // InAssetLink and InWorld must not be null - if (!InAssetLink || InAssetLink->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InAssetLink is null!")); - return false; - } - if (!InWorld || InWorld->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InWorld is null!")); - return false; - } - - AActor* AssetLinkActor = InAssetLink->GetOwnerActor(); - - const bool bParentActorIsValid = IsValid(InParentActor); - ULevel* LevelToSpawnIn = nullptr; - if (bParentActorIsValid) - { - LevelToSpawnIn = InParentActor->GetLevel(); - } - else - { - // Get the level containing the asset link's actor - if (IsValid(AssetLinkActor)) - LevelToSpawnIn = AssetLinkActor->GetLevel(); - } - - // Fallback to InWorld's current level - UWorld* WorldToSpawnIn = nullptr; - if (!IsValid(LevelToSpawnIn)) - { - LevelToSpawnIn = InWorld->GetCurrentLevel(); - WorldToSpawnIn = InWorld; - } - else - { - WorldToSpawnIn = LevelToSpawnIn->GetWorld(); - } - - if (!IsValid(WorldToSpawnIn) || !IsValid(LevelToSpawnIn)) - { - HOUDINI_LOG_WARNING( - TEXT("Could not determine level and world to spawn PDG output actor in: asset link %s, name %s"), - *(InAssetLink->GetPathName()), - *(InName.ToString())); - return false; - } - - FActorSpawnParameters SpawnParams; - SpawnParams.Name = MakeUniqueObjectName(InWorld, AActor::StaticClass(), InName); - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; - SpawnParams.OverrideLevel = LevelToSpawnIn; - AActor *Actor = WorldToSpawnIn->SpawnActor(SpawnParams); - SetOutputActor(Actor); -#if WITH_EDITOR - FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, InName.ToString()); -#endif - - // Set the actor transform: create a root component if it does not have one - USceneComponent* RootComponent = Actor->GetRootComponent(); - if (!RootComponent || RootComponent->IsPendingKill()) - { - RootComponent = NewObject(Actor, USceneComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - RootComponent->CreationMethod = EComponentCreationMethod::Instance; - Actor->SetRootComponent(RootComponent); - RootComponent->OnComponentCreated(); - RootComponent->RegisterComponent(); - } - - RootComponent->SetVisibility(true); - RootComponent->SetMobility(EComponentMobility::Static); - - const FVector ActorSpawnLocation = InParentActor ? InParentActor->GetActorLocation() : FVector::ZeroVector; - const FRotator ActorSpawnRotator = InParentActor ? InParentActor->GetActorRotation() : FRotator::ZeroRotator; - Actor->SetActorLocation(ActorSpawnLocation); - Actor->SetActorRotation(ActorSpawnRotator); - -#if WITH_EDITOR - if (IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) - { - Actor->SetFolderPath(InParentActor->GetFolderPath()); - Actor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); - } - else if (IsValid(AssetLinkActor) && AssetLinkActor->GetLevel() == LevelToSpawnIn) - { - Actor->SetFolderPath(*FString::Format( - TEXT("{0}/{1}_Output"), - { FStringFormatArg(AssetLinkActor->GetFolderPath().ToString()), FStringFormatArg(AssetLinkActor->GetActorLabel()) } - )); - } - else - { - Actor->SetFolderPath(*FString::Format(TEXT("{0}_Output"), { FStringFormatArg(InAssetLink->GetName()) })); - } -#else - if(IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) - { - OutputActor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); - } -#endif - - return true; -} - -bool -FOutputActorOwner::DestroyOutputActor() -{ - bool bDestroyed = false; - AActor *Actor = GetOutputActor(); - if (IsValid(Actor)) - { - // Detach from parent before destroying the actor - Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - Actor->Destroy(); - - bDestroyed = true; - } - - SetOutputActor(nullptr); - - return bDestroyed; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGAssetLink.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniOutput.h" + +#include "Engine/StaticMesh.h" +#include "GameFramework/Actor.h" +#include "Landscape.h" +#include "UObject/MetaData.h" + +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#if WITH_EDITOR + #include "FileHelpers.h" + #include "EditorModeManager.h" + #include "EditorModes.h" +#endif + +// +UHoudiniPDGAssetLink::UHoudiniPDGAssetLink(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , AssetName() + , AssetNodePath() + , AssetID(-1) + , SelectedTOPNetworkIndex(-1) + , LinkState(EPDGLinkState::Inactive) + , bAutoCook(false) + , bUseTOPNodeFilter(true) + , bUseTOPOutputFilter(true) + , NumWorkitems(0) + , WorkItemTally() + , OutputCachePath() + , bNeedsUIRefresh(false) + , OutputParentActor(nullptr) +{ + TOPNodeFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER; + TOPOutputFilter = HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER; + +#if WITH_EDITORONLY_DATA + bBakeMenuExpanded = true; + HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; + PDGBakeSelectionOption = EPDGBakeSelectionOption::All; + PDGBakePackageReplaceMode = EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + bRecenterBakedActors = false; + bBakeAfterAllWorkResultObjectsLoaded = false; +#endif + + // Folder used for baking PDG outputs + BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + // TODO: + // Update init, move default filter to PCH +} + +FTOPWorkResultObject::FTOPWorkResultObject() +{ + // ResultObjects = nullptr; + Name = FString(); + FilePath = FString(); + State = EPDGWorkResultState::None; + WorkItemResultInfoIndex = INDEX_NONE; + bAutoBakedSinceLastLoad = false; +} + +FTOPWorkResultObject::~FTOPWorkResultObject() +{ + // DestroyResultOutputs(); +} + +FTOPWorkResult::FTOPWorkResult() +{ + WorkItemIndex = -1; + WorkItemID = -1; + + ResultObjects.SetNum(0); +} + +bool +FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const +{ + if (WorkItemIndex != OtherWorkResult.WorkItemIndex) + return false; + if (WorkItemID != OtherWorkResult.WorkItemID) + return false; + /* + if (ResultObjects != OtherWorkResult.ResultObjects) + return false; + */ + + return true; +} + +void +FTOPWorkResult::ClearAndDestroyResultObjects() +{ + if (ResultObjects.Num() <= 0) + return; + + for (FTOPWorkResultObject& ResultObject : ResultObjects) + { + ResultObject.DestroyResultOutputsAndRemoveOutputActor(); + } + + ResultObjects.Empty(); +} + +int32 +FTOPWorkResult::IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) +{ + const int32 NumEntries = ResultObjects.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResultObject& CurResultObject = ResultObjects[Index]; + if (CurResultObject.WorkItemResultInfoIndex == InWorkItemResultInfoIndex) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResultObject* +FTOPWorkResult::GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex) +{ + const int32 ArrayIndex = IndexOfWorkResultObjectByHAPIResultInfoIndex(InWorkItemResultInfoIndex); + if (ArrayIndex == INDEX_NONE) + { + return nullptr; + } + + return GetWorkResultObjectByArrayIndex(ArrayIndex); +} + +FTOPWorkResultObject* +FTOPWorkResult::GetWorkResultObjectByArrayIndex(const int32& InArrayIndex) +{ + if (!ResultObjects.IsValidIndex(InArrayIndex)) + { + return nullptr; + } + + return &ResultObjects[InArrayIndex]; +} + + +FWorkItemTallyBase::~FWorkItemTallyBase() +{ +} + +bool +FWorkItemTallyBase::AreAllWorkItemsComplete() const +{ + return ( + NumWaitingWorkItems() == 0 && NumCookingWorkItems() == 0 && NumScheduledWorkItems() == 0 + && (NumWorkItems() == (NumCookedWorkItems() + NumErroredWorkItems())) ); +} + +bool +FWorkItemTallyBase::AnyWorkItemsFailed() const +{ + return NumErroredWorkItems() > 0; +} + +bool +FWorkItemTallyBase::AnyWorkItemsPending() const +{ + return (NumWorkItems() > 0 && (NumWaitingWorkItems() > 0 || NumCookingWorkItems() > 0 || NumScheduledWorkItems() > 0)); +} + +FString +FWorkItemTallyBase::ProgressRatio() const +{ + const float Ratio = NumWorkItems() > 0 ? (NumCookedWorkItems() / NumWorkItems()) * 100.f : 0; + + return FString::Printf(TEXT("%.1f%%"), Ratio); +} + + +FWorkItemTally::FWorkItemTally() +{ + AllWorkItems.Empty(); + WaitingWorkItems.Empty(); + ScheduledWorkItems.Empty(); + CookingWorkItems.Empty(); + CookedWorkItems.Empty(); + ErroredWorkItems.Empty(); + CookCancelledWorkItems.Empty(); +} + +void +FWorkItemTally::ZeroAll() +{ + AllWorkItems.Empty(); + WaitingWorkItems.Empty(); + ScheduledWorkItems.Empty(); + CookingWorkItems.Empty(); + CookedWorkItems.Empty(); + ErroredWorkItems.Empty(); + CookCancelledWorkItems.Empty(); +} + +void +FWorkItemTally::RemoveWorkItem(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + AllWorkItems.Remove(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsWaiting(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + WaitingWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsScheduled(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + ScheduledWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCooking(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookingWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCooked(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookedWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsErrored(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + ErroredWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RecordWorkItemAsCookCancelled(int32 InWorkItemID) +{ + RemoveWorkItemFromAllStateSets(InWorkItemID); + CookCancelledWorkItems.Add(InWorkItemID); + AllWorkItems.Add(InWorkItemID); +} + +void +FWorkItemTally::RemoveWorkItemFromAllStateSets(int32 InWorkItemID) +{ + WaitingWorkItems.Remove(InWorkItemID); + ScheduledWorkItems.Remove(InWorkItemID); + CookingWorkItems.Remove(InWorkItemID); + CookedWorkItems.Remove(InWorkItemID); + ErroredWorkItems.Remove(InWorkItemID); + CookCancelledWorkItems.Remove(InWorkItemID); +} + + +FAggregatedWorkItemTally::FAggregatedWorkItemTally() +{ + TotalWorkItems = 0; + WaitingWorkItems = 0; + ScheduledWorkItems = 0; + CookingWorkItems = 0; + CookedWorkItems = 0; + ErroredWorkItems = 0; + CookCancelledWorkItems = 0; +} + +void +FAggregatedWorkItemTally::ZeroAll() +{ + TotalWorkItems = 0; + WaitingWorkItems = 0; + ScheduledWorkItems = 0; + CookingWorkItems = 0; + CookedWorkItems = 0; + ErroredWorkItems = 0; + CookCancelledWorkItems = 0; +} + +void +FAggregatedWorkItemTally::Add(const FWorkItemTallyBase& InWorkItemTally) +{ + TotalWorkItems += InWorkItemTally.NumWorkItems(); + WaitingWorkItems += InWorkItemTally.NumWaitingWorkItems(); + ScheduledWorkItems += InWorkItemTally.NumScheduledWorkItems(); + CookingWorkItems += InWorkItemTally.NumCookingWorkItems(); + CookedWorkItems += InWorkItemTally.NumCookedWorkItems(); + ErroredWorkItems += InWorkItemTally.NumErroredWorkItems(); + CookCancelledWorkItems += InWorkItemTally.NumCookCancelledWorkItems(); +} + +void +FAggregatedWorkItemTally::Subtract(const FWorkItemTallyBase& InWorkItemTally) +{ + TotalWorkItems -= InWorkItemTally.NumWorkItems(); + WaitingWorkItems -= InWorkItemTally.NumWaitingWorkItems(); + ScheduledWorkItems -= InWorkItemTally.NumScheduledWorkItems(); + CookingWorkItems -= InWorkItemTally.NumCookingWorkItems(); + CookedWorkItems -= InWorkItemTally.NumCookedWorkItems(); + ErroredWorkItems -= InWorkItemTally.NumErroredWorkItems(); + CookCancelledWorkItems -= InWorkItemTally.NumCookCancelledWorkItems(); +} + + +UTOPNode::UTOPNode() +{ + NodeId = -1; + NodeName = FString(); + NodePath = FString(); + ParentName = FString(); + + WorkResultParent = nullptr; + WorkResult.SetNum(0); + + bHidden = false; + bAutoLoad = false; + + NodeState = EPDGNodeState::None; + + bCachedHaveNotLoadedWorkResults = false; + bCachedHaveLoadedWorkResults = false; + bHasChildNodes = false; + + bShow = false; + + bHasReceivedCookCompleteEvent = false; + + InvalidateLandscapeCache(); +} + +bool +UTOPNode::operator==(const UTOPNode& Other) const +{ + if (!NodeName.Equals(Other.NodeName)) + return false; + + if (!ParentName.Equals(Other.ParentName)) + return false; + + //if (NodeId != Other.NodeId) + // return false; + + return true; +} + +void +UTOPNode::Reset() +{ + NodeState = EPDGNodeState::None; + WorkItemTally.ZeroAll(); + AggregatedWorkItemTally.ZeroAll(); +} + +void UTOPNode::OnWorkItemWaiting(int32 InWorkItemID) +{ + FTOPWorkResult* const WorkItem = GetWorkResultByID(InWorkItemID); + if (WorkItem) + { + // Clear the bAutoBakedSinceLastLoad flag on the work results since we are expecting a cook of the work item + for (FTOPWorkResultObject& WRO : WorkItem->ResultObjects) + { + WRO.SetAutoBakedSinceLastLoad(false); + } + } + WorkItemTally.RecordWorkItemAsWaiting(InWorkItemID); +} + +void +UTOPNode::OnWorkItemCooked(int32 InWorkItemID) +{ + if (GetWorkItemTally().NumCookedWorkItems() == 0) + { + // We want to invalidate the landscape cache values in any situation where + // all the work items are being recooked. + InvalidateLandscapeCache(); + } + WorkItemTally.RecordWorkItemAsCooked(InWorkItemID); +} + +void +UTOPNode::SetVisibleInLevel(bool bInVisible) +{ + if (bShow == bInVisible) + return; + + bShow = bInVisible; + UpdateOutputVisibilityInLevel(); +} + +void +UTOPNode::UpdateOutputVisibilityInLevel() +{ + AActor* Actor = OutputActorOwner.GetOutputActor(); + if (IsValid(Actor)) + { + Actor->SetHidden(!bShow); +#if WITH_EDITOR + Actor->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + AActor* WROActor = WRO.GetOutputActorOwner().GetOutputActor(); + if (IsValid(WROActor)) + { + WROActor->SetHidden(!bShow); +#if WITH_EDITOR + WROActor->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + + // We need to manually handle child landscape's visiblity + for (UHoudiniOutput* ResultOutput : WRO.GetResultOutputs()) + { + if (!ResultOutput || ResultOutput->IsPendingKill()) + continue; + + for (auto& Pair : ResultOutput->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = Pair.Value; + ALandscapeProxy* LandscapeProxy = Cast(OutputObject.OutputObject); + if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + continue; + + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (!Landscape || Landscape->IsPendingKill()) + continue; + + Landscape->SetHidden(!bShow); +#if WITH_EDITOR + Landscape->SetIsTemporarilyHiddenInEditor(!bShow); +#endif + } + } + } + } +} + +void +UTOPNode::SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad) +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::NotLoaded || + (WRO.State == EPDGWorkResultState::Deleted && bInAlsoSetDeletedToLoad)) + { + WRO.State = EPDGWorkResultState::ToLoad; + WRO.SetAutoBakedSinceLastLoad(false); + } + } + } +} + +void +UTOPNode::SetLoadedWorkResultsToDelete() +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::Loaded) + WRO.State = EPDGWorkResultState::ToDelete; + } + } +} + + +void +UTOPNode::DeleteWorkResultOutputObjects() +{ + for (FTOPWorkResult& WorkItem : WorkResult) + { + for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) + { + if (WRO.State == EPDGWorkResultState::Loaded) + { + // Delete and clean up that WRObj + WRO.DestroyResultOutputs(); + WRO.GetOutputActorOwner().DestroyOutputActor(); + WRO.State = EPDGWorkResultState::Deleted; + } + } + } + bCachedHaveLoadedWorkResults = false; +} + +FString +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex) +{ + return FString::Printf(TEXT("%d_%d"), InWorkItemIndex, InWorkResultObjectArrayIndex); +} + +FString +UTOPNode::GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex) +{ + return GetBakedWorkResultObjectOutputsKey(InWorkResult.WorkItemIndex, InWorkResultObjectArrayIndex); +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const +{ + // Check that indices are valid + if (!WorkResult.IsValidIndex(InWorkResultArrayIndex)) + return false; + const FTOPWorkResult& WorkResultEntry = WorkResult[InWorkResultArrayIndex]; + if (!WorkResultEntry.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) + return false; + + OutKey = GetBakedWorkResultObjectOutputsKey(WorkResultEntry, InWorkResultObjectArrayIndex); + + return true; +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput) +{ + FString Key; + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) + return false; + OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); + if (!OutBakedOutput) + return false; + + return true; +} + +bool +UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const +{ + FString Key; + if (!GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key)) + return false; + OutBakedOutput = BakedWorkResultObjectOutputs.Find(Key); + if (!OutBakedOutput) + return false; + + return true; +} + +int32 +UTOPNode::IndexOfWorkResultByID(const int32& InWorkItemID) +{ + const int32 NumEntries = WorkResult.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResult& CurResult = WorkResult[Index]; + if (CurResult.WorkItemID == InWorkItemID) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByID(const int32& InWorkItemID) +{ + const int32 ArrayIndex = IndexOfWorkResultByID(InWorkItemID); + if (!WorkResult.IsValidIndex(ArrayIndex)) + return nullptr; + + return &WorkResult[ArrayIndex]; +} + +int32 +UTOPNode::IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) +{ + const int32 NumEntries = WorkResult.Num(); + for (int32 Index = 0; Index < NumEntries; ++Index) + { + const FTOPWorkResult& CurResult = WorkResult[Index]; + if (CurResult.WorkItemIndex == InWorkItemIndex && (!bInWithInvalidWorkItemID || CurResult.WorkItemID == INDEX_NONE)) + { + return Index; + } + } + + return INDEX_NONE; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) +{ + const int32 ArrayIndex = IndexOfWorkResultByHAPIIndex(InWorkItemIndex, bInWithInvalidWorkItemID); + if (!WorkResult.IsValidIndex(ArrayIndex)) + return nullptr; + return &WorkResult[ArrayIndex]; +} + +FTOPWorkResult* +UTOPNode::GetWorkResultByArrayIndex(const int32& InArrayIndex) +{ + if (!WorkResult.IsValidIndex(InArrayIndex)) + return nullptr; + return &WorkResult[InArrayIndex]; +} + +bool +UTOPNode::IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const +{ + if (!IsValid(InNetwork)) + { + return false; + } + + return ParentName == FString::Printf(TEXT("%s_%s"), *InNetwork->ParentName, *InNetwork->NodeName); +} + +bool +UTOPNode::CanStillBeAutoBaked() const +{ + // Only nodes that have results auto-loaded are auto-baked + if (!bAutoLoad) + return false; + + // Nodes with failures are not auto-baked + if (AnyWorkItemsFailed()) + return false; + + // All work items are not yet complete, so node cannot yet be baked + if (!AreAllWorkItemsComplete()) + return true; + + // Work items that are currently loaded or has not tagged has auto baked since last load can still be baked + for (const FTOPWorkResult& WorkResultEntry : WorkResult) + { + for (const FTOPWorkResultObject& WRO : WorkResultEntry.ResultObjects) + { + switch (WRO.State) + { + case EPDGWorkResultState::NotLoaded: + case EPDGWorkResultState::ToLoad: + case EPDGWorkResultState::Loading: + return true; + case EPDGWorkResultState::Loaded: + if (!WRO.AutoBakedSinceLastLoad()) + return true; + break; + case EPDGWorkResultState::ToDelete: + case EPDGWorkResultState::Deleting: + case EPDGWorkResultState::Deleted: + case EPDGWorkResultState::None: + break; + } + } + } + + return false; +} + +#if WITH_EDITOR +void +UTOPNode::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedEvent) +{ + Super::PostEditChangeChainProperty(InPropertyChangedEvent); + + const FName PropertyName = InPropertyChangedEvent.GetPropertyName(); + if (PropertyName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) + { + UpdateOutputVisibilityInLevel(); + } +} +#endif + +#if WITH_EDITOR +void +UTOPNode::PostTransacted(const FTransactionObjectEvent& TransactionEvent) +{ + Super::PostTransacted(TransactionEvent); + + if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) + return; + + bool bUpdateVisibility = false; + for (const FName& PropName : TransactionEvent.GetChangedProperties()) + { + if (PropName == GET_MEMBER_NAME_CHECKED(UTOPNode, bShow)) + { + bUpdateVisibility = true; + } + } + + if (bUpdateVisibility) + UpdateOutputVisibilityInLevel(); +} +#endif + +void +UTOPNode::OnDirtyNode() +{ + InvalidateLandscapeCache(); + bHasReceivedCookCompleteEvent = false; +} + +void +UTOPNode::InvalidateLandscapeCache() +{ + LandscapeReferenceLocation.bIsCached = false; + LandscapeSizeInfo.bIsCached = false; + ClearedLandscapeLayers.Empty(); +} + +UTOPNetwork::UTOPNetwork() +{ + NodeId = -1; + NodeName = FString(); + + AllTOPNodes.SetNum(0); + SelectedTOPIndex = -1; + + ParentName = FString(); + + bShowResults = false; + bAutoLoadResults = false; +} + +bool +UTOPNetwork::operator==(const UTOPNetwork& Other) const +{ + if (!NodeName.Equals(Other.NodeName)) + return false; + + if (!ParentName.Equals(Other.ParentName)) + return false; + + //if (NodeId != Other.NodeId) + // return false; + + return true; +} + +void +UTOPNetwork::SetLoadedWorkResultsToDelete() +{ + for (UTOPNode* Node : AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + Node->SetLoadedWorkResultsToDelete(); + } +} + +void +UTOPNetwork::DeleteWorkResultOutputObjects() +{ + for (UTOPNode* Node : AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + Node->DeleteWorkResultOutputObjects(); + } +} + +bool +UTOPNetwork::AnyWorkItemsPending() const +{ + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->AnyWorkItemsPending()) + return true; + } + + return false; +} + +bool +UTOPNetwork::AnyWorkItemsFailed() const +{ + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->AnyWorkItemsFailed()) + return true; + } + + return false; +} + +bool +UTOPNetwork::CanStillBeAutoBaked() const +{ + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (TOPNode->CanStillBeAutoBaked()) + return true; + } + + return false; +} + +void +UTOPNetwork::HandleOnPDGEventCookCompleteReceivedByChildNode(UHoudiniPDGAssetLink* const InAssetLink, UTOPNode* const InTOPNode) +{ + if (!IsValid(InAssetLink)) + return; + + // Check if all nodes have recieved the HAPI_PDG_EVENT_COOK_COMPLETE event, if so, broadcast the OnPostCook handler. + for (const UTOPNode* const TOPNode : AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (!TOPNode->HasReceivedCookCompleteEvent()) + return; + } + + if (OnPostCookDelegate.IsBound()) + OnPostCookDelegate.Broadcast(this, AnyWorkItemsFailed()); + + InAssetLink->HandleOnTOPNetworkCookComplete(this); +} + +void +UHoudiniPDGAssetLink::SelectTOPNetwork(const int32& AtIndex) +{ + if (!AllTOPNetworks.IsValidIndex(AtIndex)) + return; + + SelectedTOPNetworkIndex = AtIndex; +} + + +void +UHoudiniPDGAssetLink::SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex) +{ + if (!IsValid(InTOPNetwork)) + return; + + if (!InTOPNetwork->AllTOPNodes.IsValidIndex(AtIndex)) + return; + + InTOPNetwork->SelectedTOPIndex = AtIndex; +} + + +UTOPNetwork* +UHoudiniPDGAssetLink::GetSelectedTOPNetwork() +{ + return GetTOPNetwork(SelectedTOPNetworkIndex); +} + +const UTOPNetwork* +UHoudiniPDGAssetLink::GetSelectedTOPNetwork() const +{ + return GetTOPNetwork(SelectedTOPNetworkIndex); +} + +UTOPNode* +UHoudiniPDGAssetLink::GetSelectedTOPNode() +{ + UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNetwork)) + return nullptr; + + if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) + return nullptr; + + UTOPNode* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; + if (!IsValid(SelectedTOPNode)) + return nullptr; + + return SelectedTOPNode; +} + +const UTOPNode* +UHoudiniPDGAssetLink::GetSelectedTOPNode() const +{ + UTOPNetwork const* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNetwork)) + return nullptr; + + if (!SelectedTOPNetwork->AllTOPNodes.IsValidIndex(SelectedTOPNetwork->SelectedTOPIndex)) + return nullptr; + + UTOPNode const* const SelectedTOPNode = SelectedTOPNetwork->AllTOPNodes[SelectedTOPNetwork->SelectedTOPIndex]; + if (!IsValid(SelectedTOPNode)) + return nullptr; + + return SelectedTOPNode; +} + +FString +UHoudiniPDGAssetLink::GetSelectedTOPNodeName() +{ + FString NodeName = FString(); + + const UTOPNode* const SelectedTOPNode = GetSelectedTOPNode(); + if (IsValid(SelectedTOPNode)) + NodeName = SelectedTOPNode->NodeName; + + return NodeName; +} + +FString +UHoudiniPDGAssetLink::GetSelectedTOPNetworkName() +{ + FString NetworkName = FString(); + + const UTOPNetwork* const SelectedTOPNetwork = GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNetwork)) + NetworkName = SelectedTOPNetwork->NodeName; + + return NetworkName; +} + +UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) +{ + if(AllTOPNetworks.IsValidIndex(AtIndex)) + { + return AllTOPNetworks[AtIndex]; + } + + return nullptr; +} + +const UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetwork(const int32& AtIndex) const +{ + if(AllTOPNetworks.IsValidIndex(AtIndex)) + { + return AllTOPNetworks[AtIndex]; + } + + return nullptr; +} + +UTOPNetwork* +UHoudiniPDGAssetLink::GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex) +{ + OutIndex = INDEX_NONE; + int32 Index = -1; + for (UTOPNetwork* CurrentTOPNet : InTOPNetworks) + { + Index += 1; + + if (!IsValid(CurrentTOPNet)) + continue; + + if (CurrentTOPNet->NodePath.Equals(InNodePath)) + { + OutIndex = Index; + return CurrentTOPNet; + } + } + + return nullptr; +} + +UTOPNode* +UHoudiniPDGAssetLink::GetParentTOPNode(const UTOPNode* InNode) +{ + if (!IsValid(InNode)) + return nullptr; + + FString NodePath = InNode->NodePath; + FString ParentPath; + + if (NodePath.EndsWith("/")) + NodePath.LeftChopInline(1); + + if (NodePath.Split("/", &ParentPath, nullptr, ESearchCase::IgnoreCase, ESearchDir::FromEnd) && !ParentPath.IsEmpty()) + { + for (UTOPNetwork* TOPNet : AllTOPNetworks) + { + if (!IsValid(TOPNet)) + continue; + + for (UTOPNode* TOPNode : TOPNet->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->NodePath == ParentPath && InNode->NodeId != TOPNode->NodeId) + { + return TOPNode; + } + } + } + } + + return nullptr; +} + +UTOPNode* +UHoudiniPDGAssetLink::GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex) +{ + OutIndex = INDEX_NONE; + int32 Index = -1; + for (UTOPNode* CurrentTOPNode : InTOPNodes) + { + Index += 1; + + if (!IsValid(CurrentTOPNode)) + continue; + + if (CurrentTOPNode->NodePath.Equals(InNodePath)) + { + OutIndex = Index; + return CurrentTOPNode; + } + } + + return nullptr; +} + +void +UHoudiniPDGAssetLink::ClearAllTOPData() +{ + // Clears all TOP data + for(UTOPNetwork* CurrentNetwork : AllTOPNetworks) + { + if (!IsValid(CurrentNetwork)) + continue; + + for(UTOPNode* CurrentTOPNode : CurrentNetwork->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + ClearTOPNodeWorkItemResults(CurrentTOPNode); + } + } + + AllTOPNetworks.Empty(); +} + +void +UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork) +{ + if (!IsValid(TOPNetwork)) + return; + + for(UTOPNode* CurrentTOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + ClearTOPNodeWorkItemResults(CurrentTOPNode); + } +} + +void +UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) +{ + if (!IsValid(TOPNode)) + return; + + TOPNode->OnDirtyNode(); + + for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) + { + DestroyWorkItemResultData(CurrentWorkResult); + } + TOPNode->WorkResult.Empty(); + + FOutputActorOwner& OutputActorOwner = TOPNode->GetOutputActorOwner(); + AActor* OutputActor = OutputActorOwner.GetOutputActor(); + if (IsValid(OutputActor)) + { + // Destroy any attached actors (which we'll assume that any attachments left + // are untracked actors associated with the TOPNode) + TArray AttachedActors; + OutputActor->GetAttachedActors(AttachedActors); + for (AActor* Actor : AttachedActors) + { + if (!IsValid(Actor)) + continue; + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + } + } + + if (TOPNode->WorkResultParent && !TOPNode->WorkResultParent->IsPendingKill()) + { + + // TODO: Destroy the Parent Object + // DestroyImmediate(topNode._workResultParentGO); + } + + OutputActorOwner.DestroyOutputActor(); +} + + +void +UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); + if (WorkResult) + { + DestroyWorkItemResultData(*WorkResult); + // TODO: Should we destroy the FTOPWorkResult struct entirely here? + //TOPNode.WorkResult.RemoveByPredicate + } +} + +void +UHoudiniPDGAssetLink::DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + // TODO: Update ClearWorkItemResultByID or GetWorkResultByID to return the index of the work item + // so that we don't have to find its index again to remove it from the array + ClearWorkItemResultByID(InWorkItemID, InTOPNode); + // Find the index of the FTOPWorkResult for InWorkItemID in InTOPNode.WorkResult and remove it + const int32 Index = InTOPNode->WorkResult.IndexOfByPredicate( + [InWorkItemID](const FTOPWorkResult& InWorkItem) { return InWorkItem.WorkItemID == InWorkItemID; }); + if (Index != INDEX_NONE && Index >= 0) + InTOPNode->WorkResult.RemoveAt(Index); +} + +FTOPWorkResult* +UHoudiniPDGAssetLink::GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return nullptr; + return InTOPNode->GetWorkResultByID(InWorkItemID); +} + +FDirectoryPath +UHoudiniPDGAssetLink::GetTemporaryCookFolder() const +{ + UHoudiniAssetComponent* HAC = GetOuterHoudiniAssetComponent(); + if (HAC) + return HAC->TemporaryCookFolder; + + FDirectoryPath TempPath; + TempPath.Path = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + return TempPath; +} + +void +UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) +{ + ResultObject.DestroyResultOutputsAndRemoveOutputActor(); +} + +void +UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result) +{ + Result.ClearAndDestroyResultObjects(); +} + +void +UHoudiniPDGAssetLink::HandleOnTOPNetworkCookComplete(UTOPNetwork* const InTOPNet) +{ + if (!IsValid(InTOPNet)) + return; + + if (OnPostTOPNetworkCookDelegate.IsBound()) + { + OnPostTOPNetworkCookDelegate.Broadcast(this, InTOPNet, InTOPNet->AnyWorkItemsFailed()); + } +} + + +UTOPNode* +UHoudiniPDGAssetLink::GetTOPNode(const int32& InNodeID) +{ + UTOPNetwork* Network = nullptr; + UTOPNode* Node = nullptr; + + if (GetTOPNodeAndNetworkByNodeId(InNodeID, Network, Node)) + return Node; + + return nullptr; +} + + +bool +UHoudiniPDGAssetLink::GetTOPNodeAndNetworkByNodeId(const int32& InNodeID, UTOPNetwork*& OutNetwork, UTOPNode*& OutNode) +{ + OutNetwork = nullptr; + OutNode = nullptr; + + for (UTOPNetwork* CurrentTOPNet : AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + if (CurrentTOPNode->NodeId == InNodeID) + { + OutNetwork = CurrentTOPNet; + OutNode = CurrentTOPNode; + return true; + } + } + } + + return false; +} + + +void +UHoudiniPDGAssetLink::UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork) +{ + if (!IsValid(InNode) || !IsValid(InNetwork)) + return; + + if (!InNode->bHasChildNodes) + return; + + FString PrefixPath = InNode->NodePath; + if (!PrefixPath.EndsWith("/")) + PrefixPath += "/"; + InNode->ZeroWorkItemTally(); + InNode->NodeState = EPDGNodeState::None; + + TMap NodeStateOrder; + NodeStateOrder.Add(EPDGNodeState::None, 0); + NodeStateOrder.Add(EPDGNodeState::Cook_Complete, 1); + NodeStateOrder.Add(EPDGNodeState::Dirtied, 2); + NodeStateOrder.Add(EPDGNodeState::Cook_Failed, 3); + NodeStateOrder.Add(EPDGNodeState::Dirtying, 4); + NodeStateOrder.Add(EPDGNodeState::Cooking, 5); + + int8 CurrentState = 0; + + for (const UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + if (Node->NodePath.StartsWith(PrefixPath) && !Node->bHasChildNodes) + { + InNode->AggregateTallyFromChildNode(Node); + const int8 VisitedNodeState = NodeStateOrder.FindChecked(Node->NodeState); + if (VisitedNodeState > CurrentState) + CurrentState = VisitedNodeState; + } + } + + EPDGNodeState const* const NewState = NodeStateOrder.FindKey(CurrentState); + if (NewState) + InNode->NodeState = *NewState; +} + +void +UHoudiniPDGAssetLink::UpdateWorkItemTally() +{ + WorkItemTally.ZeroAll(); + for(UTOPNetwork* CurrentTOPNet : AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + for(UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + // Only add up the tallys from nodes without children (since parent's aggregate the child work items counts) + if (CurrentTOPNode->bHasChildNodes) + { + UpdateTOPNodeWithChildrenWorkItemTallyAndState(CurrentTOPNode, CurrentTOPNet); + } + else + { + WorkItemTally.Add(CurrentTOPNode->GetWorkItemTally()); + } + } + } +} + + +void +UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork) +{ + if (!IsValid(TOPNetwork)) + return; + + for (UTOPNode* CurTOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(CurTOPNode)) + continue; + + CurTOPNode->ZeroWorkItemTally(); + } +} + + +FString +UHoudiniPDGAssetLink::GetAssetLinkStatus(const EPDGLinkState& InLinkState) +{ + FString Status; + switch (InLinkState) + { + case EPDGLinkState::Inactive: + Status = TEXT("Inactive"); + case EPDGLinkState::Linking: + Status = TEXT("Linking"); + case EPDGLinkState::Linked: + Status = TEXT("Linked"); + case EPDGLinkState::Error_Not_Linked: + Status = TEXT("Not Linked"); + default: + Status = TEXT(""); + } + + return Status; +} + +FString +UHoudiniPDGAssetLink::GetTOPNodeStatus(const UTOPNode* InTOPNode) +{ + static const FString InvalidOrUnknownStatus = TEXT(""); + + if (!IsValid(InTOPNode)) + return InvalidOrUnknownStatus; + + if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) + { + return TEXT("Cook Failed"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) + { + return TEXT("Cook Completed"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Cooking) + { + return TEXT("Cook In Progress"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) + { + return TEXT("Dirtied"); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) + { + return TEXT("Dirtying"); + } + + return InvalidOrUnknownStatus; +} + +FLinearColor +UHoudiniPDGAssetLink::GetTOPNodeStatusColor(const UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return FLinearColor::White; + + if (InTOPNode->NodeState == EPDGNodeState::Cook_Failed || InTOPNode->AnyWorkItemsFailed()) + { + return FLinearColor::Red; + } + else if (InTOPNode->NodeState == EPDGNodeState::Cook_Complete) + { + return FLinearColor::Green; + } + else if (InTOPNode->NodeState == EPDGNodeState::Cooking) + { + return FLinearColor(0.0, 1.0f, 1.0f); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtied) + { + return FLinearColor(1.0f, 0.5f, 0.0f); + } + else if (InTOPNode->NodeState == EPDGNodeState::Dirtying) + { + return FLinearColor::Yellow; + } + + return FLinearColor::White; +} + +AActor* +UHoudiniPDGAssetLink::GetOwnerActor() const +{ + UObject* Outer = GetOuter(); + UActorComponent* Component = Cast(Outer); + if (IsValid(Component)) + return Component->GetOwner(); + else + return Cast(Outer); +} + +bool +UHoudiniPDGAssetLink::HasTemporaryOutputs() const +{ + // Loop over all networks, all nodes, all work items and check for any valid output objects + for (const UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (const UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + for (const FTOPWorkResult& WorkResult : TOPNode->WorkResult) + { + for (const FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) + { + // If the WorkResultObject's actor is not valid, then it no longer has temporary objects in the + // scene + if (!IsValid(WorkResultObject.GetOutputActorOwner().GetOutputActor())) + continue; + + for (UHoudiniOutput* Output : WorkResultObject.GetResultOutputs()) + { + if (!IsValid(Output)) + continue; + + const EHoudiniOutputType OutputType = Output->GetType(); + for (const auto& OutputObjectPair : Output->GetOutputObjects()) + { + if ((OutputType == EHoudiniOutputType::Landscape && IsValid(OutputObjectPair.Value.OutputObject)) || + IsValid(OutputObjectPair.Value.OutputComponent)) + { + return true; + } + } + } + } + } + } + } + + return false; +} + +void +UHoudiniPDGAssetLink::UpdatePostDuplicate() +{ + // Loop over all networks, all nodes, all work items and clear output actors + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + for (FTOPWorkResult& WorkResult : TOPNode->WorkResult) + { + for (FTOPWorkResultObject& WorkResultObject : WorkResult.ResultObjects) + { + WorkResultObject.GetOutputActorOwner().SetOutputActor(nullptr); + WorkResultObject.State = EPDGWorkResultState::None; + WorkResultObject.SetResultOutputs(TArray()); + } + } + TOPNode->GetOutputActorOwner().SetOutputActor(nullptr); + TOPNode->bCachedHaveNotLoadedWorkResults = false; + TOPNode->bCachedHaveLoadedWorkResults = false; + } + } +} + +UHoudiniAssetComponent* UHoudiniPDGAssetLink::GetOuterHoudiniAssetComponent() const +{ + return Cast( GetTypedOuter() ); +} + +void +UHoudiniPDGAssetLink::UpdateTOPNodeAutoloadAndVisibility() +{ + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + if (TOPNode->bAutoLoad) + { + // // Set work results that are cooked but in NotLoaded state to ToLoad + // TOPNode.SetNotLoadedWorkResultsToLoad(); + } + + TOPNode->UpdateOutputVisibilityInLevel(); + } + } +} + +void +UHoudiniPDGAssetLink::FilterTOPNodesAndOutputs() +{ + for (UTOPNetwork* TOPNetwork : AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + for (UTOPNode* TOPNode : TOPNetwork->AllTOPNodes) + { + if (!IsValid(TOPNode)) + continue; + + // TOP Node visibility filter via TOPNodeFilter + if (bUseTOPNodeFilter) + { + TOPNode->bHidden = !TOPNodeFilter.IsEmpty() && !TOPNode->NodeName.StartsWith(TOPNodeFilter); + } + else + { + TOPNode->bHidden = false; + } + + // Auto load results filter via TOPNodeOutputFilter + if (bUseTOPOutputFilter) + { + const bool bNewAutoLoad = TOPOutputFilter.IsEmpty() || TOPNode->NodeName.StartsWith(TOPOutputFilter); + if (bNewAutoLoad != TOPNode->bAutoLoad) + { + if (bNewAutoLoad) + { + // Set work results that are cooked but in NotLoaded state to ToLoad + TOPNode->bAutoLoad = true; + // TOPNode->SetNotLoadedWorkResultsToLoad(); + TOPNode->SetVisibleInLevel(true); + } + else + { + TOPNode->bAutoLoad = false; + TOPNode->SetVisibleInLevel(false); + } + TOPNode->UpdateOutputVisibilityInLevel(); + } + } + } + } +} + +#if WITH_EDITORONLY_DATA +bool +UHoudiniPDGAssetLink::AnyRemainingAutoBakeNodes() const +{ + if (!bBakeAfterAllWorkResultObjectsLoaded) + return false; + + switch (PDGBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + { + for (const UTOPNetwork* const TOPNet : AllTOPNetworks) + { + if (!IsValid(TOPNet)) + continue; + + if (TOPNet->CanStillBeAutoBaked()) + { + return true; + } + } + break; + } + case EPDGBakeSelectionOption::SelectedNetwork: + { + const UTOPNetwork* const TOPNet = GetSelectedTOPNetwork(); + if (IsValid(TOPNet) && TOPNet->CanStillBeAutoBaked()) + { + return true; + } + } + case EPDGBakeSelectionOption::SelectedNode: + { + UTOPNode const* const TOPNode = GetSelectedTOPNode(); + if (IsValid(TOPNode) && TOPNode->CanStillBeAutoBaked()) + { + return true; + } + } + default: + return false; + } + + return false; +} +#endif + +void +UHoudiniPDGAssetLink::HandleOnPostBake(const bool bInSuccess) +{ + if (OnPostBakeDelegate.IsBound()) + OnPostBakeDelegate.Broadcast(this, bInSuccess); +} + +#if WITH_EDITORONLY_DATA +void +UHoudiniPDGAssetLink::PostEditChangeChainProperty(FPropertyChangedChainEvent& InPropertyChangedChainEvent) +{ + Super::PostEditChangeChainProperty(InPropertyChangedChainEvent); + + const FName PropertyName = InPropertyChangedChainEvent.GetPropertyName(); + if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) + { + // Refilter TOP nodes + FilterTOPNodesAndOutputs(); + bNeedsUIRefresh = true; + } + else if (PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || + PropertyName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) + { + bNeedsUIRefresh = true; + } +} + +#endif + +#if WITH_EDITORONLY_DATA +void +UHoudiniPDGAssetLink::PostTransacted(const FTransactionObjectEvent& TransactionEvent) +{ + Super::PostTransacted(TransactionEvent); + + if (TransactionEvent.GetEventType() != ETransactionObjectEventType::UndoRedo) + return; + + bool bDoFilterTOPNodesAndOutputs = false; + for (const FName& PropName : TransactionEvent.GetChangedProperties()) + { + if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter)) + { + bDoFilterTOPNodesAndOutputs = true; + bNeedsUIRefresh = true; + } + else if (PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode) || + PropName == GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded)) + { + bNeedsUIRefresh = true; + } + } + + if (bDoFilterTOPNodesAndOutputs) + FilterTOPNodesAndOutputs(); +} +#endif + +void +FTOPWorkResultObject::DestroyResultOutputs() +{ + // Delete output components and gather output objects for deletion + bool bDidDestroyObjects = false; + bool bDidModifyFoliage = false; + + AActor* const OutputActor = OutputActorOwner.GetOutputActor(); + + for (UHoudiniOutput* CurOutput : ResultOutputs) + { + for (auto& Pair : CurOutput->GetOutputObjects()) + { + FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniOutputObject& OutputObject = Pair.Value; + if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) + { + // Instancer components require some special handling around foliage + // TODO: move/refactor so that we can use the InstanceTranslator's helper functions (RemoveAndDestroyComponent and CleanupFoliageInstances) + bool bDestroyComponent = true; + if (OutputObject.OutputComponent->IsA()) + { + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OutputObject.OutputComponent); + if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) + { + // Make sure foliage our foliage instances have been removed + USceneComponent* ParentComponent = nullptr; + if (IsValid(OutputActor)) + ParentComponent = Cast(OutputActor->GetRootComponent()); + else + ParentComponent = Cast(HISMC->GetOuter()); + if (ParentComponent && !ParentComponent->IsPendingKill()) + { + UStaticMesh* FoliageSM = HISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + return; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(HISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return; + + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + if (!FoliageType || FoliageType->IsPendingKill()) + return; +#if WITH_EDITOR + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); + + // Remove the foliage type if it doesn't have any more instances + if(HISMC->GetInstanceCount() == 0) + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + + bDidModifyFoliage = true; +#endif + } + + // // do not delete FISMC that still have instances left + // // as we have cleaned up our instances before, these have been hand-placed + // if (HISMC->GetInstanceCount() > 0) + bDestroyComponent = false; + + OutputObject.OutputComponent = nullptr; + } + } + + if (bDestroyComponent) + { + USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from its actor first + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + // Detach from its parent component if attached + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + bDidDestroyObjects = true; + + OutputObject.OutputComponent = nullptr; + } + } + } + if (OutputObject.OutputObject && !OutputObject.OutputObject->IsPendingKill()) + { + // For actors we detach them first and then destroy + AActor* Actor = Cast(OutputObject.OutputObject); + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + if (LandscapePtr) + { + Actor = LandscapePtr->GetRawPtr(); + } + + if (Actor) + { + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + + bDidDestroyObjects = true; + } + else + { + // ... if not an actor, destroy the object if it is a plugin created temp object + if (IsValid(OutputObject.OutputObject) && !OutputObject.OutputObject->HasAnyFlags(RF_Transient)) + { + // Only delete if the object has metadata indicating it is a plugin created temp object + UPackage* const Package = OutputObject.OutputObject->GetOutermost(); + if (IsValid(Package)) + { + UMetaData* const MetaData = Package->GetMetaData(); + if (IsValid(MetaData)) + { + if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) + { + FString TempGUID; + TempGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); + TempGUID.TrimStartAndEndInline(); + if (!TempGUID.IsEmpty()) + OutputObjectsToDelete.Add(OutputObject.OutputObject); + } + } + } + } + OutputObject.OutputObject = nullptr; + } + } + } + } + + ResultOutputs.Empty(); + + if (bDidDestroyObjects) + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Delete the output objects we found + if (OutputObjectsToDelete.Num() > 0) + FHoudiniEngineRuntimeUtils::SafeDeleteObjects(OutputObjectsToDelete); + +#if WITH_EDITOR + if (bDidModifyFoliage) + { + // Repopulate the foliage types in the foliage mode UI if foliage mode is active + // There is a helper function in FHoudiniEngineUtils for this, but we cannot access it from this module. + // TODO: refactor? + FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); + if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) + { + EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); + EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); + } + } +#endif +} + +void FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor() +{ + DestroyResultOutputs(); + GetOutputActorOwner().DestroyOutputActor(); +} + +bool +FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) +{ + // InAssetLink and InWorld must not be null + if (!InAssetLink || InAssetLink->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InAssetLink is null!")); + return false; + } + if (!InWorld || InWorld->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InWorld is null!")); + return false; + } + + AActor* AssetLinkActor = InAssetLink->GetOwnerActor(); + + const bool bParentActorIsValid = IsValid(InParentActor); + ULevel* LevelToSpawnIn = nullptr; + if (bParentActorIsValid) + { + LevelToSpawnIn = InParentActor->GetLevel(); + } + else + { + // Get the level containing the asset link's actor + if (IsValid(AssetLinkActor)) + LevelToSpawnIn = AssetLinkActor->GetLevel(); + } + + // Fallback to InWorld's current level + UWorld* WorldToSpawnIn = nullptr; + if (!IsValid(LevelToSpawnIn)) + { + LevelToSpawnIn = InWorld->GetCurrentLevel(); + WorldToSpawnIn = InWorld; + } + else + { + WorldToSpawnIn = LevelToSpawnIn->GetWorld(); + } + + if (!IsValid(WorldToSpawnIn) || !IsValid(LevelToSpawnIn)) + { + HOUDINI_LOG_WARNING( + TEXT("Could not determine level and world to spawn PDG output actor in: asset link %s, name %s"), + *(InAssetLink->GetPathName()), + *(InName.ToString())); + return false; + } + + FActorSpawnParameters SpawnParams; + SpawnParams.Name = MakeUniqueObjectName(InWorld, AActor::StaticClass(), InName); + SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; + SpawnParams.OverrideLevel = LevelToSpawnIn; + AActor *Actor = WorldToSpawnIn->SpawnActor(SpawnParams); + SetOutputActor(Actor); +#if WITH_EDITOR + FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, InName.ToString()); +#endif + + // Set the actor transform: create a root component if it does not have one + USceneComponent* RootComponent = Actor->GetRootComponent(); + if (!RootComponent || RootComponent->IsPendingKill()) + { + RootComponent = NewObject(Actor, USceneComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + RootComponent->CreationMethod = EComponentCreationMethod::Instance; + Actor->SetRootComponent(RootComponent); + RootComponent->OnComponentCreated(); + RootComponent->RegisterComponent(); + } + + RootComponent->SetVisibility(true); + RootComponent->SetMobility(EComponentMobility::Static); + + const FVector ActorSpawnLocation = InParentActor ? InParentActor->GetActorLocation() : FVector::ZeroVector; + const FRotator ActorSpawnRotator = InParentActor ? InParentActor->GetActorRotation() : FRotator::ZeroRotator; + Actor->SetActorLocation(ActorSpawnLocation); + Actor->SetActorRotation(ActorSpawnRotator); + +#if WITH_EDITOR + if (IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) + { + Actor->SetFolderPath(InParentActor->GetFolderPath()); + Actor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); + } + else if (IsValid(AssetLinkActor) && AssetLinkActor->GetLevel() == LevelToSpawnIn) + { + Actor->SetFolderPath(*FString::Format( + TEXT("{0}/{1}_Output"), + { FStringFormatArg(AssetLinkActor->GetFolderPath().ToString()), FStringFormatArg(AssetLinkActor->GetActorLabel()) } + )); + } + else + { + Actor->SetFolderPath(*FString::Format(TEXT("{0}_Output"), { FStringFormatArg(InAssetLink->GetName()) })); + } +#else + if(IsValid(InParentActor) && InParentActor->GetLevel() == LevelToSpawnIn) + { + OutputActor->AttachToActor(InParentActor, FAttachmentTransformRules::KeepWorldTransform); + } +#endif + + return true; +} + +bool +FOutputActorOwner::DestroyOutputActor() +{ + bool bDestroyed = false; + AActor *Actor = GetOutputActor(); + if (IsValid(Actor)) + { + // Detach from parent before destroying the actor + Actor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + Actor->Destroy(); + + bDestroyed = true; + } + + SetOutputActor(nullptr); + + return bDestroyed; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h index 5b75c5dd2..033cd3a29 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h @@ -114,7 +114,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject virtual int32 GetChildIndex() const { return ChildIndex; }; virtual bool IsVisible() const { return bIsVisible; }; - virtual bool ShouldDisplay() const{ return bIsVisible && ParmType != EHoudiniParameterType::Invalid; }; + virtual bool ShouldDisplay() const{ return bIsVisible && bIsParentFolderVisible && ParmType != EHoudiniParameterType::Invalid; }; virtual bool IsDisabled() const { return bIsDisabled; }; virtual bool HasChanged() const { return bHasChanged; }; virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; @@ -160,6 +160,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject virtual bool IsDirectChildOfMultiParm() const { return bIsDirectChildOfMultiParm; }; virtual void SetVisible(const bool& InIsVisible) { bIsVisible = InIsVisible; }; + virtual void SetVisibleParent(const bool& InIsVisible) { bIsParentFolderVisible = InIsVisible; }; virtual void SetDisabled(const bool& InIsDisabled) { bIsDisabled = InIsDisabled; }; virtual void SetDefault(const bool& InIsDefault) { bIsDefault = InIsDefault; }; virtual void SetSpare(const bool& InIsSpare) { bIsSpare = InIsSpare; }; @@ -248,6 +249,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject UPROPERTY() bool bIsVisible; + // Is visible in hierarchy. (e.g. parm can be visible, but containing folder is not) + UPROPERTY() + bool bIsParentFolderVisible; + // UPROPERTY() bool bIsDisabled; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp index fe66b2e42..efca80766 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp @@ -64,6 +64,8 @@ UHoudiniParameterChoice::BeginDestroy() StringChoiceLabels.Empty(); StringChoiceValues.Empty(); + IntValuesArray.Empty(); + Super::BeginDestroy(); } @@ -93,6 +95,8 @@ UHoudiniParameterChoice::SetNumChoices(const int32& InNumChoices) StringChoiceValues.SetNumZeroed(InNumChoices); StringChoiceLabels.SetNumZeroed(InNumChoices); + IntValuesArray.SetNumZeroed(InNumChoices); + UpdateChoiceLabelsPtr(); } @@ -257,4 +261,9 @@ UHoudiniParameterChoice::RevertToDefault() MarkChanged(true); } -} \ No newline at end of file +} + +int32 UHoudiniParameterChoice::GetIndexFromValueArray(int32 Index) const +{ + return IntValuesArray.Find(Index); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h index 372aacc6f..55f89b49e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h @@ -49,11 +49,13 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParamete // Accessors //------------------------------------------------------------------------------------------------ - const int32 GetIntValue() const { return IntValue; }; + const int32 GetIntValueIndex() const { return IntValue; }; const FString GetStringValue() const { return StringValue; }; const int32 GetNumChoices() const { return StringChoiceLabels.Num(); }; const FString GetLabel() const { return StringChoiceLabels.IsValidIndex(IntValue) ? StringChoiceLabels[IntValue] : FString(); }; - const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; + const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; + const int32 GetIntValue(int32 InIntValueIndex) { return IntValuesArray.IsValidIndex(InIntValueIndex) ? IntValuesArray[InIntValueIndex] : IntValue; } + void SetIntValueArray(int32 Index, int32 Value) { if (IntValuesArray.IsValidIndex(Index)) IntValuesArray[Index] = Value; } bool IsDefault() const override; @@ -90,9 +92,12 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParamete void RevertToDefault() override; + int32 GetIndexFromValueArray(int32 Index) const; + protected: // Current int value for this property. + // More of an index to IntValuesArray UPROPERTY() int32 IntValue; @@ -122,4 +127,11 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParamete UPROPERTY() bool bIsChildOfRamp; -}; \ No newline at end of file + + // An array containing the values of all choices + // IntValues[i] should be i unless UseMenuItemTokenAsValue is enabled. + UPROPERTY() + TArray IntValuesArray; + + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp index 9c0a07e02..f9b1768e6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp @@ -534,7 +534,7 @@ UHoudiniParameterRampFloat::UpdatePointsArray(const TArray& if (ChoiceParameter) { Point->InterpolationParentParm = ChoiceParameter; - Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValueIndex())); CurrentInstanceIndex++; } } @@ -822,7 +822,7 @@ UHoudiniParameterRampColor::UpdatePointsArray(const TArray& if (ChoiceParameter) { Point->InterpolationParentParm = ChoiceParameter; - Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValueIndex())); CurrentInstanceIndex++; } } diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp index ff4460b11..7e0371a23 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp @@ -42,7 +42,6 @@ FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() , GeneratedPhysMaterial(nullptr) , GeneratedCollisionTraceFlag(CTF_UseDefault) , GeneratedLightMapResolution(64) - , GeneratedLpvBiasMultiplier(1.0f) , GeneratedWalkableSlopeOverride() , GeneratedLightMapCoordinateIndex(1) , bGeneratedUseMaximumStreamingTexelRatio(false) @@ -87,6 +86,7 @@ UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & Obj // Custom Houdini location. bUseCustomHoudiniLocation = false; CustomHoudiniLocation.Path = TEXT(""); + HoudiniExecutable = HRSHE_Houdini; // Arguments for HAPI_Initialize CookingThreadStackSize = -1; @@ -115,7 +115,6 @@ UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & Obj DefaultBodyInstance.SetCollisionProfileName("BlockAll"); CollisionTraceFlag = CTF_UseDefault; LightMapResolution = 32; - LpvBiasMultiplier = 1.0f; LightMapCoordinateIndex = 1; bUseMaximumStreamingTexelRatio = false; StreamingDistanceMultiplier = 1.0f; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h index ac4cd8d7a..75441420e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h @@ -70,6 +70,22 @@ enum EHoudiniRuntimeSettingsRecomputeFlag HRSRF_MAX, }; +UENUM() +enum EHoudiniExecutableType +{ + // Houdini + HRSHE_Houdini UMETA(DisplayName = "Houdini"), + + // Houdini FX + HRSHE_HoudiniFX UMETA(DisplayName = "Houdini FX"), + + // Houdini Core + HRSHE_HoudiniCore UMETA(DisplayName = "Houdini Core"), + + // Houdini Indie + HRSHE_HoudiniIndie UMETA(DisplayName = "Houdini Indie"), +}; + USTRUCT(BlueprintType) struct HOUDINIENGINERUNTIME_API FHoudiniStaticMeshGenerationProperties { @@ -100,10 +116,6 @@ struct HOUDINIENGINERUNTIME_API FHoudiniStaticMeshGenerationProperties UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) int32 GeneratedLightMapResolution; - /** Bias multiplier for Light Propagation Volume lighting. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float GeneratedLpvBiasMultiplier; - /** Custom walkable slope setting for generated mesh's body. */ UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Walkable Slope Override")) FWalkableSlopeOverride GeneratedWalkableSlopeOverride; @@ -469,6 +481,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Custom Houdini location")) FDirectoryPath CustomHoudiniLocation; + // Select the Houdini executable to be used when opening session sync or opening hip files + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Houdini Executable")) + TEnumAsByte HoudiniExecutable; + //------------------------------------------------------------------------------------------------------------- // HAPI_Initialize //------------------------------------------------------------------------------------------------------------- diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp index c04ddcbd4..776755bf7 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp @@ -26,13 +26,16 @@ #include "HoudiniStaticMesh.h" +#include "Async/ParallelFor.h" +#include "MeshUtilitiesCommon.h" + UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bHasNormals = false; bHasTangents = false; bHasColors = false; - NumUVLayers = false; + NumUVLayers = 0; bHasPerFaceMaterials = false; } @@ -218,6 +221,124 @@ void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStatic StaticMaterials[InMaterialIndex] = InStaticMaterial; } +void UHoudiniStaticMesh::CalculateNormals(bool bInComputeWeightedNormals) +{ + const int32 NumVertexInstances = GetNumVertexInstances(); + + // Pre-allocate space in the vertex instance normals array + VertexInstanceNormals.SetNum(NumVertexInstances); + + const int32 NumTriangles = GetNumTriangles(); + const int32 NumVertices = GetNumVertices(); + + // Setup a vertex normal array + TArray VertexNormals; + VertexNormals.SetNum(NumVertices); + + // Zero all entries in VertexNormals + // for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) + ParallelFor(NumVertices, [&VertexNormals](int32 VertexIndex) + { + VertexNormals[VertexIndex] = FVector::ZeroVector; + }); + + // Calculate face normals and sum them for each vertex that shares the triangle + // for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex) + ParallelFor(NumTriangles, [this, &VertexNormals, bInComputeWeightedNormals](int32 TriangleIndex) + { + const FIntVector& TriangleVertexIndices = TriangleIndices[TriangleIndex]; + + if (!VertexPositions.IsValidIndex(TriangleVertexIndices[0]) || + !VertexPositions.IsValidIndex(TriangleVertexIndices[1]) || + !VertexPositions.IsValidIndex(TriangleVertexIndices[2])) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexPositions index out of range %d, %d, %d, Num %d"), + TriangleVertexIndices[0], TriangleVertexIndices[1], TriangleVertexIndices[2], VertexPositions.Num()); + return; + } + + const FVector& V0 = VertexPositions[TriangleVertexIndices[0]]; + const FVector& V1 = VertexPositions[TriangleVertexIndices[1]]; + const FVector& V2 = VertexPositions[TriangleVertexIndices[2]]; + + FVector TriangleNormal = FVector::CrossProduct(V2 - V0, V1 - V0); + float Area = TriangleNormal.Size(); + TriangleNormal /= Area; + Area /= 2.0f; + + const float Weight[3] = { + bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V0, V1, V2) : 1.0f, + bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V1, V2, V0) : 1.0f, + bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V2, V0, V1) : 1.0f, + }; + + for (int CornerIndex = 0; CornerIndex < 3; ++CornerIndex) + { + const FVector WeightedNormal = TriangleNormal * Weight[CornerIndex]; + if (!WeightedNormal.IsNearlyZero(SMALL_NUMBER) && !WeightedNormal.ContainsNaN()) + { + if (!VertexNormals.IsValidIndex(TriangleVertexIndices[CornerIndex])) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexNormal index out of range %d, Num %d"), + TriangleVertexIndices[CornerIndex], VertexNormals.Num()); + continue; + } + VertexNormals[TriangleVertexIndices[CornerIndex]] += WeightedNormal; + } + } + }); + + // Normalize the vertex normals + // for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) + ParallelFor(NumVertices, [&VertexNormals](int32 VertexIndex) + { + VertexNormals[VertexIndex].Normalize(); + }); + + // Copy vertex normals to vertex instance normals + // for (int32 VertexInstanceIndex = 0; VertexInstanceIndex < NumVertexInstances; ++VertexInstanceIndex) + ParallelFor(NumVertexInstances, [this, &VertexNormals](int32 VertexInstanceIndex) + { + const int32 TriangleIndex = VertexInstanceIndex / 3; + const int32 CornerIndex = VertexInstanceIndex % 3; + const FIntVector& TriangleVertexIndices = TriangleIndices[TriangleIndex]; + if (!VertexNormals.IsValidIndex(TriangleVertexIndices[CornerIndex])) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexNormals index out of range %d, Num %d"), + TriangleVertexIndices[CornerIndex], VertexNormals.Num()); + return; + } + VertexInstanceNormals[VertexInstanceIndex] = VertexNormals[TriangleVertexIndices[CornerIndex]]; + }); + + bHasNormals = true; +} + +void UHoudiniStaticMesh::CalculateTangents(bool bInComputeWeightedNormals) +{ + const int32 NumVertexInstances = GetNumVertexInstances(); + + VertexInstanceUTangents.SetNum(NumVertexInstances); + VertexInstanceVTangents.SetNum(NumVertexInstances); + + // Calculate normals first if we don't have any + if (!HasNormals() || VertexInstanceNormals.Num() != NumVertexInstances) + CalculateNormals(bInComputeWeightedNormals); + + // for (int32 VertexInstanceIndex = 0; VertexInstanceIndex < NumVertexInstances; ++VertexInstanceIndex) + ParallelFor(NumVertexInstances, [this](int32 VertexInstanceIndex) + { + const FVector& Normal = VertexInstanceNormals[VertexInstanceIndex]; + Normal.FindBestAxisVectors( + VertexInstanceUTangents[VertexInstanceIndex], VertexInstanceVTangents[VertexInstanceIndex]); + }); + + bHasTangents = true; +} + void UHoudiniStaticMesh::Optimize() { VertexPositions.Shrink(); @@ -294,8 +415,7 @@ bool UHoudiniStaticMesh::IsValid(bool bInSkipVertexIndicesCheck) const && ValidateAttributeArraySize(VertexInstanceUTangents.Num(), NumVertexInstances) && ValidateAttributeArraySize(VertexInstanceVTangents.Num(), NumVertexInstances) && ValidateAttributeArraySize(VertexInstanceColors.Num(), NumVertexInstances) - // Must have at least 1 UV layer - && NumUVLayers > 0 + && NumUVLayers >= 0 && VertexInstanceUVs.Num() == NumUVLayers * NumVertexInstances; if (!bInSkipVertexIndicesCheck) @@ -341,3 +461,4 @@ void UHoudiniStaticMesh::Serialize(FArchive &InArchive) MaterialIDsPerTriangle.Shrink(); MaterialIDsPerTriangle.BulkSerialize(InArchive); } + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h index f012ddc82..5a46413d8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h @@ -124,8 +124,31 @@ class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject UFUNCTION() uint32 AddStaticMaterial(const FStaticMaterial& InStaticMaterial) { return StaticMaterials.Add(InStaticMaterial); } - // Meant to be called after the mesh data arrays are populated. - // Currently only calls Shrink on the arrays + /** Calculate the normals of the mesh by calculating the face normal of each triangle (if a triangle has vertices + * V0, V1, V2, get the vector perpendicular to the face Pf = (V2 - V0) x (V1 - V0). To calculate the + * vertex normal for V0 sum and then normalize all its shared face normals. If bInComputeWeightedNormals is true + * then the weight of each face normal that contributes to V0's normal is the area of the face multiplied by the V0 + * corner angle of that face. If bInComputeWeightedNormals is false then the weight is 1. + * + * @param bInComputeWeightedNormals Whether or not to use weighted normal calculation. Defaults to false. + */ + UFUNCTION() + void CalculateNormals(bool bInComputeWeightedNormals=false); + + /** + * Calculate tangents from the normals. Calculates normals first via CalculateNormals() if the mesh does not yet + * have normals. + * + * @param bInComputeWeightedNormals Whether or not to use weighted normal calculation if CalculateNormals() is + * called. Defaults to false. + */ + UFUNCTION() + void CalculateTangents(bool bInComputeWeightedNormals=false); + + /** + * Meant to be called after the mesh data arrays are populated. + * Currently only calls Shrink on the arrays + */ UFUNCTION() void Optimize(); @@ -232,3 +255,4 @@ class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject UPROPERTY() TArray StaticMaterials; }; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp index d2cb3026e..41f816b80 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp @@ -93,11 +93,19 @@ FPrimitiveSceneProxy* UHoudiniStaticMeshComponent::CreateSceneProxy() FBoxSphereBounds UHoudiniStaticMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const { - FBox LocalBoundingBox = LocalBounds; - FBoxSphereBounds Ret(LocalBoundingBox.TransformBy(InLocalToWorld)); - Ret.BoxExtent *= BoundsScale; - Ret.SphereRadius *= BoundsScale; - return Ret; + if (Mesh) + { + // mesh bounds + FBoxSphereBounds NewBounds = LocalBounds.TransformBy(InLocalToWorld); + NewBounds.BoxExtent *= BoundsScale; + NewBounds.SphereRadius *= BoundsScale; + + return NewBounds; + } + else + { + return FBoxSphereBounds(InLocalToWorld.GetLocation(), FVector::ZeroVector, 0.f); + } } #if WITH_EDITOR @@ -138,11 +146,11 @@ void UHoudiniStaticMeshComponent::NotifyMeshUpdated() LocalBounds.Init(); } + UpdateBounds(); + #if WITH_EDITORONLY_DATA UpdateSpriteComponent(); #endif - - UpdateBounds(); } #if WITH_EDITORONLY_DATA @@ -150,7 +158,8 @@ void UHoudiniStaticMeshComponent::UpdateSpriteComponent() { if (SpriteComponent) { - SpriteComponent->SetRelativeLocation(FVector(0, 0, LocalBounds.GetExtent().Z)); + const FBoxSphereBounds B = Bounds.TransformBy(GetComponentTransform().ToInverseMatrixWithScale()); + SpriteComponent->SetRelativeLocation(B.Origin + FVector(0, 0, B.BoxExtent.Size())); SpriteComponent->SetVisibility(bHoudiniIconVisible); } } @@ -211,3 +220,4 @@ UMaterialInterface* UHoudiniStaticMeshComponent::GetMaterial(int32 MaterialIndex return Mesh ? Mesh->GetMaterial(MaterialIndex) : nullptr; } } + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp index f7ff2d6c0..e624c39e2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp @@ -131,6 +131,9 @@ FHoudiniStaticMeshSceneProxy::FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshCom , FeatureLevel(InFeatureLevel) , Component(InComponent) , MaterialRelevance(InComponent ? InComponent->GetMaterialRelevance(InFeatureLevel) : FMaterialRelevance()) +#if STATICMESH_ENABLE_DEBUG_RENDERING + , Owner(InComponent ? InComponent->GetOwner() : nullptr) +#endif { } @@ -224,7 +227,8 @@ void FHoudiniStaticMeshSceneProxy::Build() void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const { - const bool bRenderAsWireframe = (AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe); + const FEngineShowFlags EngineShowFlags = ViewFamily.EngineShowFlags; + const bool bRenderAsWireframe = (AllowDebugViewmodes() && EngineShowFlags.Wireframe); // Set up the wireframe material FMaterialRenderProxy *WireframeMaterialProxy = nullptr; @@ -238,7 +242,7 @@ void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray Date: Mon, 13 Sep 2021 18:18:46 -0400 Subject: [PATCH 13/16] Houdini Engine for Unreal - Version 2.0.4 Update 4 of the V2 Plugin. The plug-in is now linked to Houdini 18.5.696 / HAPI 3.7.1. New features: - Added support for output nodes. When the "use output node" option is enabled, the plugin will now also use output nodes found in the HDA alongside the display node to produce results. - The Plugin Settings menu now indicates if a Houdini Engine for Unity/Unreal license is checked out. - Added support for material instances on instancers, and for baking instancers with material instances. Supported by all instancer baking types except foliage due to foliage material being closely tied to the static mesh. Bug fixes: - Fixed issue where invisible folders were incorrectly detected, resulting in other missing parameters - Fixed an issue where float ramp parameters were causing a Slate crash. - Fixed an issue where unreal_material_instance was not being applied on landscape outputs. - Fixed an issue where having a multiparm operator path and then a regular operator path would mess up the input asset order when you add or remove objects. - Fixed an issue where material instances were not being generated upon baking with static meshes and landscapes. - Optimized reading of attributes. - Fixed a bug preventing changing MeshGeneration properties via the details UI. - The plugin now logs detailed connection errors when failing to create a session. - Fixed a possible instantiation loop when rebuilding a loaded indie HDA without an indie license. - Fixed the Houdini Engine status incorrectly reporting a license failure after attempting to use an indie asset with a commercial license. - The plugin is now using the HAPI GetStringBatch() function when retrieving string arrays from HAPI. This is slightly faster than retrieving each string separately. - The plugin is now using HAPI's LoadGeoFromFile() function when importing bgeo files instead of manually using a file sop. - Fixed an issue where changing asset input type with a cook error wouldn't allow you to do it - This is an issue for HDAs with minimum inputs. - Fixed include/accessibility issues with the C++ Public API. --- .../Python/HoudiniEngineV2/asyncprocessor.py | 1060 +- HoudiniEngine.uplugin | 4 +- LICENSE.md | 50 +- README.md | 212 +- Source/HoudiniEngine/HoudiniEngine.Build.cs | 10 +- Source/HoudiniEngine/Private/HBSPOps.cpp | 2972 ++-- Source/HoudiniEngine/Private/HBSPOps.h | 362 +- Source/HoudiniEngine/Private/HCsgUtils.cpp | 2924 +-- Source/HoudiniEngine/Private/HCsgUtils.h | 556 +- Source/HoudiniEngine/Private/HoudiniApi.cpp | 7926 ++++----- .../HoudiniEngine/Private/HoudiniEngine.cpp | 2651 +-- Source/HoudiniEngine/Private/HoudiniEngine.h | 692 +- .../Private/HoudiniEngineManager.cpp | 3459 ++-- .../Private/HoudiniEngineManager.h | 384 +- .../Private/HoudiniEngineOutputStats.cpp | 118 +- .../Private/HoudiniEngineOutputStats.h | 142 +- .../Private/HoudiniEngineScheduler.cpp | 1302 +- .../Private/HoudiniEngineScheduler.h | 238 +- .../Private/HoudiniEngineString.cpp | 498 +- .../Private/HoudiniEngineString.h | 154 +- .../Private/HoudiniEngineTask.cpp | 99 +- .../HoudiniEngine/Private/HoudiniEngineTask.h | 204 +- .../Private/HoudiniEngineTaskInfo.cpp | 92 +- .../Private/HoudiniEngineTaskInfo.h | 156 +- .../Private/HoudiniEngineUtils.cpp | 10821 +++++------ .../Private/HoudiniEngineUtils.h | 1375 +- .../Private/HoudiniGeoImportCommandlet.cpp | 1553 +- .../Private/HoudiniGeoImportCommandlet.h | 304 +- .../Private/HoudiniGeoImporter.cpp | 1823 +- .../Private/HoudiniGeoImporter.h | 242 +- .../Private/HoudiniHandleTranslator.cpp | 739 +- .../Private/HoudiniHandleTranslator.h | 108 +- .../Private/HoudiniInputTranslator.cpp | 6156 +++---- .../Private/HoudiniInputTranslator.h | 428 +- .../Private/HoudiniInstanceTranslator.cpp | 7009 ++++---- .../Private/HoudiniInstanceTranslator.h | 891 +- .../Private/HoudiniLandscapeTranslator.cpp | 9000 +++++----- .../Private/HoudiniLandscapeTranslator.h | 917 +- .../Private/HoudiniMaterialTranslator.cpp | 6883 +++---- .../Private/HoudiniMaterialTranslator.h | 471 +- .../Private/HoudiniMeshTranslator.cpp | 13563 +++++++------- .../Private/HoudiniMeshTranslator.h | 850 +- .../Private/HoudiniOutputTranslator.cpp | 4468 ++--- .../Private/HoudiniOutputTranslator.h | 208 +- .../Private/HoudiniPDGImporterMessages.cpp | 214 +- .../Private/HoudiniPDGImporterMessages.h | 376 +- .../Private/HoudiniPDGManager.cpp | 4526 ++--- .../HoudiniEngine/Private/HoudiniPDGManager.h | 428 +- .../Private/HoudiniPDGTranslator.cpp | 1032 +- .../Private/HoudiniPDGTranslator.h | 174 +- .../Private/HoudiniPackageParams.cpp | 862 +- .../Private/HoudiniPackageParams.h | 480 +- .../Private/HoudiniParameterTranslator.cpp | 7030 ++++---- .../Private/HoudiniParameterTranslator.h | 314 +- .../Private/HoudiniSplineTranslator.cpp | 3432 ++-- .../Private/HoudiniSplineTranslator.h | 222 +- .../Private/HoudiniStringResolver.cpp | 440 +- .../Private/HoudiniStringResolver.h | 222 +- .../Private/SAssetSelectionWidget.cpp | 316 +- .../Private/SAssetSelectionWidget.h | 198 +- .../Private/UnrealBrushTranslator.cpp | 896 +- .../Private/UnrealBrushTranslator.h | 98 +- .../Private/UnrealFoliageTypeTranslator.cpp | 578 +- .../Private/UnrealFoliageTypeTranslator.h | 132 +- .../Private/UnrealInstanceTranslator.cpp | 422 +- .../Private/UnrealInstanceTranslator.h | 96 +- .../Private/UnrealLandscapeTranslator.cpp | 4400 ++--- .../Private/UnrealLandscapeTranslator.h | 514 +- .../Private/UnrealMeshTranslator.cpp | 8523 ++++----- .../Private/UnrealMeshTranslator.h | 350 +- .../Private/UnrealSplineTranslator.cpp | 248 +- .../Private/UnrealSplineTranslator.h | 76 +- .../HoudiniEngine/Public/HAPI/HAPI_Common.h | 30 +- .../HoudiniEngine/Public/HAPI/HAPI_Version.h | 6 +- Source/HoudiniEngine/Public/HoudiniApi.h | 2050 +-- .../Private/AssetTypeActions_HoudiniAsset.cpp | 916 +- .../Private/AssetTypeActions_HoudiniAsset.h | 168 +- .../Private/HoudiniAssetActorFactory.cpp | 232 +- .../Private/HoudiniAssetActorFactory.h | 116 +- .../Private/HoudiniAssetBroker.cpp | 142 +- .../Private/HoudiniAssetBroker.h | 98 +- .../Private/HoudiniAssetComponentDetails.cpp | 1184 +- .../Private/HoudiniAssetComponentDetails.h | 180 +- .../Private/HoudiniAssetFactory.cpp | 416 +- .../Private/HoudiniAssetFactory.h | 142 +- .../Private/HoudiniEngineBakeUtils.cpp | 11855 +++++++------ .../Private/HoudiniEngineBakeUtils.h | 1468 +- .../Private/HoudiniEngineCommands.cpp | 3655 ++-- .../Private/HoudiniEngineCommands.h | 565 +- .../Private/HoudiniEngineDetails.cpp | 3982 +++-- .../Private/HoudiniEngineDetails.h | 244 +- .../Private/HoudiniEngineEditor.cpp | 3086 ++-- .../Private/HoudiniEngineEditor.h | 698 +- .../Private/HoudiniEngineEditorPrivatePCH.h | 296 +- .../Private/HoudiniEngineEditorUtils.cpp | 1332 +- .../Private/HoudiniEngineEditorUtils.h | 176 +- .../Private/HoudiniEngineStyle.cpp | 620 +- .../Private/HoudiniEngineStyle.h | 82 +- .../Private/HoudiniGeoFactory.cpp | 762 +- .../Private/HoudiniGeoFactory.h | 166 +- .../HoudiniHandleComponentVisualizer.cpp | 518 +- .../HoudiniHandleComponentVisualizer.h | 226 +- .../Private/HoudiniHandleDetails.cpp | 792 +- .../Private/HoudiniHandleDetails.h | 86 +- .../Private/HoudiniInputDetails.cpp | 10046 +++++------ .../Private/HoudiniInputDetails.h | 334 +- .../Private/HoudiniOutputDetails.cpp | 7315 ++++---- .../Private/HoudiniOutputDetails.h | 450 +- .../Private/HoudiniPDGDetails.cpp | 5270 +++--- .../Private/HoudiniPDGDetails.h | 280 +- .../Private/HoudiniParameterDetails.cpp | 14812 ++++++++-------- .../Private/HoudiniParameterDetails.h | 980 +- .../Private/HoudiniPublicAPI.cpp | 13 + .../Private/HoudiniPublicAPIAssetWrapper.cpp | 8391 ++++----- .../Private/HoudiniPublicAPIBlueprintLib.cpp | 80 +- .../Private/HoudiniPublicAPIInputTypes.cpp | 1721 +- .../Private/HoudiniPublicAPIObjectBase.cpp | 157 +- .../Private/HoudiniPublicAPIOutputTypes.cpp | 128 +- .../HoudiniPublicAPIProcessHDANode.cpp | 630 +- .../Private/HoudiniRuntimeSettingsDetails.cpp | 646 +- .../Private/HoudiniRuntimeSettingsDetails.h | 142 +- .../HoudiniSplineComponentVisualizer.cpp | 2042 +-- .../HoudiniSplineComponentVisualizer.h | 368 +- .../Private/HoudiniTool.cpp | 52 +- .../HoudiniEngineEditor/Private/HoudiniTool.h | 110 +- .../Private/SNewFilePathPicker.cpp | 706 +- .../Private/SNewFilePathPicker.h | 302 +- .../Private/Tests/HoudiniEditorTestUtils.cpp | 5 +- .../Private/Tests/HoudiniEditorTestUtils.h | 2 + .../Public/HoudiniPublicAPI.h | 9 +- .../Public/HoudiniPublicAPIAssetWrapper.h | 3110 ++-- .../Public/HoudiniPublicAPIBlueprintLib.h | 96 +- .../Public/HoudiniPublicAPIInputTypes.h | 1107 +- .../Public/HoudiniPublicAPIObjectBase.h | 220 +- .../Public/HoudiniPublicAPIOutputTypes.h | 165 +- .../Public/HoudiniPublicAPIProcessHDANode.h | 571 +- .../Public/IHoudiniEngineEditor.h | 142 +- .../Private/HoudiniAsset.cpp | 400 +- .../Private/HoudiniAsset.h | 104 - .../Private/HoudiniAssetActor.cpp | 292 +- .../Private/HoudiniAssetActor.h | 154 +- .../HoudiniAssetBlueprintComponent.cpp | 4748 ++--- .../Private/HoudiniAssetBlueprintComponent.h | 702 +- .../Private/HoudiniAssetComponent.cpp | 5844 +++--- .../Private/HoudiniAssetComponent.h | 1543 +- .../Private/HoudiniAssetStateTypes.h | 184 +- .../Private/HoudiniCompatibilityHelpers.cpp | 3602 ++-- .../Private/HoudiniCompatibilityHelpers.h | 2182 +-- .../HoudiniEngineCopyPropertiesInterface.cpp | 66 +- .../HoudiniEngineCopyPropertiesInterface.h | 100 +- .../Private/HoudiniEngineRuntime.cpp | 646 +- .../Private/HoudiniEngineRuntime.h | 212 +- .../Private/HoudiniEngineRuntimeUtils.cpp | 1320 +- .../Private/HoudiniEngineRuntimeUtils.h | 712 +- .../Private/HoudiniGenericAttribute.cpp | 3056 ++-- .../Private/HoudiniGenericAttribute.h | 310 +- .../Private/HoudiniGeoPartObject.cpp | 368 +- .../Private/HoudiniGeoPartObject.h | 825 +- .../Private/HoudiniHandleComponent.cpp | 510 +- .../Private/HoudiniHandleComponent.h | 270 +- .../Private/HoudiniInput.cpp | 5212 +++--- .../Private/HoudiniInput.h | 1114 +- .../Private/HoudiniInputObject.cpp | 3728 ++-- .../Private/HoudiniInputObject.h | 1724 +- .../Private/HoudiniInputTypes.h | 117 +- .../HoudiniInstancedActorComponent.cpp | 494 +- .../Private/HoudiniInstancedActorComponent.h | 182 +- .../HoudiniMeshSplitInstancerComponent.cpp | 492 +- .../HoudiniMeshSplitInstancerComponent.h | 172 +- .../Private/HoudiniOutput.cpp | 1878 +- .../Private/HoudiniOutput.h | 1264 +- .../Private/HoudiniPDGAssetLink.h | 1800 +- .../Private/HoudiniParameter.cpp | 516 +- .../Private/HoudiniParameter.h | 649 +- .../Private/HoudiniParameterButton.cpp | 100 +- .../Private/HoudiniParameterButton.h | 86 +- .../Private/HoudiniParameterButtonStrip.cpp | 146 +- .../Private/HoudiniParameterButtonStrip.h | 130 +- .../Private/HoudiniParameterChoice.cpp | 538 +- .../Private/HoudiniParameterChoice.h | 274 +- .../Private/HoudiniParameterColor.cpp | 166 +- .../Private/HoudiniParameterColor.h | 144 +- .../Private/HoudiniParameterFile.cpp | 202 +- .../Private/HoudiniParameterFile.h | 154 +- .../Private/HoudiniParameterFloat.cpp | 338 +- .../Private/HoudiniParameterFloat.h | 330 +- .../Private/HoudiniParameterFolder.cpp | 104 +- .../Private/HoudiniParameterFolder.h | 218 +- .../Private/HoudiniParameterFolderList.cpp | 216 +- .../Private/HoudiniParameterFolderList.h | 160 +- .../Private/HoudiniParameterInt.cpp | 258 +- .../Private/HoudiniParameterInt.h | 262 +- .../Private/HoudiniParameterLabel.cpp | 124 +- .../Private/HoudiniParameterLabel.h | 110 +- .../Private/HoudiniParameterMultiParm.cpp | 308 +- .../Private/HoudiniParameterMultiParm.h | 254 +- .../Private/HoudiniParameterOperatorPath.cpp | 190 +- .../Private/HoudiniParameterOperatorPath.h | 114 +- .../Private/HoudiniParameterRamp.cpp | 1936 +- .../Private/HoudiniParameterRamp.h | 658 +- .../Private/HoudiniParameterSeparator.cpp | 102 +- .../Private/HoudiniParameterSeparator.h | 86 +- .../Private/HoudiniParameterString.cpp | 270 +- .../Private/HoudiniParameterString.h | 180 +- .../Private/HoudiniParameterToggle.cpp | 190 +- .../Private/HoudiniParameterToggle.h | 134 +- .../HoudiniPluginSerializationVersion.cpp | 70 +- .../HoudiniPluginSerializationVersion.h | 186 +- .../Private/HoudiniRuntimeSettings.cpp | 808 +- .../Private/HoudiniRuntimeSettings.h | 1026 +- .../Private/HoudiniSplineComponent.cpp | 1108 +- .../Private/HoudiniSplineComponent.h | 552 +- .../Private/HoudiniStaticMesh.cpp | 929 +- .../Private/HoudiniStaticMesh.h | 516 +- .../Private/HoudiniStaticMeshComponent.cpp | 446 +- .../Private/HoudiniStaticMeshComponent.h | 194 +- .../Private/HoudiniStaticMeshSceneProxy.cpp | 1104 +- .../Private/HoudiniStaticMeshSceneProxy.h | 344 +- .../Private/IHoudiniAssetStateEvents.cpp | 76 +- .../Private/IHoudiniAssetStateEvents.h | 114 +- 220 files changed, 145826 insertions(+), 144985 deletions(-) delete mode 100644 Source/HoudiniEngineRuntime/Private/HoudiniAsset.h diff --git a/Content/Python/HoudiniEngineV2/asyncprocessor.py b/Content/Python/HoudiniEngineV2/asyncprocessor.py index 57f8a8b80..c8cc28f06 100644 --- a/Content/Python/HoudiniEngineV2/asyncprocessor.py +++ b/Content/Python/HoudiniEngineV2/asyncprocessor.py @@ -1,530 +1,530 @@ -# Copyright (c) <2021> Side Effects Software Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. The name of Side Effects Software may not be used to endorse or -# promote products derived from this software without specific prior -# written permission. -# -# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import unreal - - -class ProcessHDA(object): - """ An object that wraps async processing of an HDA (instantiating, - cooking/processing/baking an HDA), with functions that are called at the - various stages of the process, that can be overridden by subclasses for - custom funtionality: - - - on_failure() - - on_complete(): upon successful completion (could be PostInstantiation - if auto cook is disabled, PostProcessing if auto bake is disabled, or - after PostAutoBake if auto bake is enabled. - - on_pre_instantiation(): before the HDA is instantiated, a good place - to set parameter values before the first cook. - - on_post_instantiation(): after the HDA is instantiated, a good place - to set/configure inputs before the first cook. - - on_post_auto_cook(): right after a cook - - on_pre_process(): after a cook but before output objects have been - created/processed - - on_post_processing(): after output objects have been created - - on_post_auto_bake(): after outputs have been baked - - Instantiate the processor via the constructor and then call the activate() - function to start the asynchronous process. - - """ - def __init__( - self, - houdini_asset, - instantiate_at=unreal.Transform(), - parameters=None, - node_inputs=None, - parameter_inputs=None, - world_context_object=None, - spawn_in_level_override=None, - enable_auto_cook=True, - enable_auto_bake=False, - bake_directory_path="", - bake_method=unreal.HoudiniEngineBakeOption.TO_ACTOR, - remove_output_after_bake=False, - recenter_baked_actors=False, - replace_previous_bake=False, - delete_instantiated_asset_on_completion_or_failure=False): - """ Instantiates an HDA in the specified world/level. Sets parameters - and inputs supplied in InParameters, InNodeInputs and parameter_inputs. - If bInEnableAutoCook is true, cooks the HDA. If bInEnableAutoBake is - true, bakes the cooked outputs according to the supplied baking - parameters. - - This all happens asynchronously, with the various output pins firing at - the various points in the process: - - - PreInstantiation: before the HDA is instantiated, a good place - to set parameter values before the first cook (parameter values - from ``parameters`` are automatically applied at this point) - - PostInstantiation: after the HDA is instantiated, a good place - to set/configure inputs before the first cook (inputs from - ``node_inputs`` and ``parameter_inputs`` are automatically applied - at this point) - - PostAutoCook: right after a cook - - PreProcess: after a cook but before output objects have been - created/processed - - PostProcessing: after output objects have been created - - PostAutoBake: after outputs have been baked - - Completed: upon successful completion (could be PostInstantiation - if auto cook is disabled, PostProcessing if auto bake is disabled, - or after PostAutoBake if auto bake is enabled). - - Failed: If the process failed at any point. - - Args: - houdini_asset (HoudiniAsset): The HDA to instantiate. - instantiate_at (Transform): The Transform to instantiate the HDA with. - parameters (Map(Name, HoudiniParameterTuple)): The parameters to set before cooking the instantiated HDA. - node_inputs (Map(int32, HoudiniPublicAPIInput)): The node inputs to set before cooking the instantiated HDA. - parameter_inputs (Map(Name, HoudiniPublicAPIInput)): The parameter-based inputs to set before cooking the instantiated HDA. - world_context_object (Object): A world context object for identifying the world to spawn in, if spawn_in_level_override is null. - spawn_in_level_override (Level): If not nullptr, then the HoudiniAssetActor is spawned in that level. If both spawn_in_level_override and world_context_object are null, then the actor is spawned in the current editor context world's current level. - enable_auto_cook (bool): If true (the default) the HDA will cook automatically after instantiation and after parameter, transform and input changes. - enable_auto_bake (bool): If true, the HDA output is automatically baked after a cook. Defaults to false. - bake_directory_path (str): The directory to bake to if the bake path is not set via attributes on the HDA output. - bake_method (HoudiniEngineBakeOption): The bake target (to actor vs blueprint). @see HoudiniEngineBakeOption. - remove_output_after_bake (bool): If true, HDA temporary outputs are removed after a bake. Defaults to false. - recenter_baked_actors (bool): Recenter the baked actors to their bounding box center. Defaults to false. - replace_previous_bake (bool): If true, on every bake replace the previous bake's output (assets + actors) with the new bake's output. Defaults to false. - delete_instantiated_asset_on_completion_or_failure (bool): If true, deletes the instantiated asset actor on completion or failure. Defaults to false. - - """ - super(ProcessHDA, self).__init__() - self._houdini_asset = houdini_asset - self._instantiate_at = instantiate_at - self._parameters = parameters - self._node_inputs = node_inputs - self._parameter_inputs = parameter_inputs - self._world_context_object = world_context_object - self._spawn_in_level_override = spawn_in_level_override - self._enable_auto_cook = enable_auto_cook - self._enable_auto_bake = enable_auto_bake - self._bake_directory_path = bake_directory_path - self._bake_method = bake_method - self._remove_output_after_bake = remove_output_after_bake - self._recenter_baked_actors = recenter_baked_actors - self._replace_previous_bake = replace_previous_bake - self._delete_instantiated_asset_on_completion_or_failure = delete_instantiated_asset_on_completion_or_failure - - self._asset_wrapper = None - self._cook_success = False - self._bake_success = False - - @property - def asset_wrapper(self): - """ The asset wrapper for the instantiated HDA processed by this node. """ - return self._asset_wrapper - - @property - def cook_success(self): - """ True if the last cook was successful. """ - return self._cook_success - - @property - def bake_success(self): - """ True if the last bake was successful. """ - return self._bake_success - - @property - def houdini_asset(self): - """ The HDA to instantiate. """ - return self._houdini_asset - - @property - def instantiate_at(self): - """ The transform the instantiate the asset with. """ - return self._instantiate_at - - @property - def parameters(self): - """ The parameters to set on on_pre_instantiation """ - return self._parameters - - @property - def node_inputs(self): - """ The node inputs to set on on_post_instantiation """ - return self._node_inputs - - @property - def parameter_inputs(self): - """ The object path parameter inputs to set on on_post_instantiation """ - return self._parameter_inputs - - @property - def world_context_object(self): - """ The world context object: spawn in this world if spawn_in_level_override is not set. """ - return self._world_context_object - - @property - def spawn_in_level_override(self): - """ The level to spawn in. If both this and world_context_object is not set, spawn in the editor context's level. """ - return self._spawn_in_level_override - - @property - def enable_auto_cook(self): - """ Whether to set the instantiated asset to auto cook. """ - return self._enable_auto_cook - - @property - def enable_auto_bake(self): - """ Whether to set the instantiated asset to auto bake after a cook. """ - return self._enable_auto_bake - - @property - def bake_directory_path(self): - """ Set the fallback bake directory, for if output attributes do not specify it. """ - return self._bake_directory_path - - @property - def bake_method(self): - """ The bake method/target: for example, to actors vs to blueprints. """ - return self._bake_method - - @property - def remove_output_after_bake(self): - """ Remove temporary HDA output after a bake. """ - return self._remove_output_after_bake - - @property - def recenter_baked_actors(self): - """ Recenter the baked actors at their bounding box center. """ - return self._recenter_baked_actors - - @property - def replace_previous_bake(self): - """ Replace previous bake output on each bake. For the purposes of this - node, this would mostly apply to .uassets and not actors. - - """ - return self._replace_previous_bake - - @property - def delete_instantiated_asset_on_completion_or_failure(self): - """ Whether or not to delete the instantiated asset after Complete is called. """ - return self._delete_instantiated_asset_on_completion_or_failure - - def activate(self): - """ Activate the process. This will: - - - instantiate houdini_asset and wrap it as asset_wrapper - - call on_failure() for any immediate failures - - otherwise bind to delegates from asset_wrapper so that the - various self.on_*() functions are called as appropriate - - Returns immediately (does not block until cooking/processing is - complete). - - Returns: - (bool): False if activation failed. - - """ - # Get the API instance - houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api() - if not houdini_api: - # Handle failures: this will unbind delegates and call on_failure() - self._handle_on_failure() - return False - - # Create an empty API asset wrapper - self._asset_wrapper = unreal.HoudiniPublicAPIAssetWrapper.create_empty_wrapper(houdini_api) - if not self._asset_wrapper: - # Handle failures: this will unbind delegates and call on_failure() - self._handle_on_failure() - return False - - # Bind to the wrapper's delegates for instantiation, cooking, baking - # etc events - self._asset_wrapper.on_pre_instantiation_delegate.add_callable( - self._handle_on_pre_instantiation) - self._asset_wrapper.on_post_instantiation_delegate.add_callable( - self._handle_on_post_instantiation) - self._asset_wrapper.on_post_cook_delegate.add_callable( - self._handle_on_post_auto_cook) - self._asset_wrapper.on_pre_process_state_exited_delegate.add_callable( - self._handle_on_pre_process) - self._asset_wrapper.on_post_processing_delegate.add_callable( - self._handle_on_post_processing) - self._asset_wrapper.on_post_bake_delegate.add_callable( - self._handle_on_post_auto_bake) - - # Begin the instantiation process of houdini_asset and wrap it with - # self.asset_wrapper - if not houdini_api.instantiate_asset_with_existing_wrapper( - self.asset_wrapper, - self.houdini_asset, - self.instantiate_at, - self.world_context_object, - self.spawn_in_level_override, - self.enable_auto_cook, - self.enable_auto_bake, - self.bake_directory_path, - self.bake_method, - self.remove_output_after_bake, - self.recenter_baked_actors, - self.replace_previous_bake): - # Handle failures: this will unbind delegates and call on_failure() - self._handle_on_failure() - return False - - return True - - def _unbind_delegates(self): - """ Unbinds from self.asset_wrapper's delegates (if valid). """ - if not self._asset_wrapper: - return - - self._asset_wrapper.on_pre_instantiation_delegate.add_callable( - self._handle_on_pre_instantiation) - self._asset_wrapper.on_post_instantiation_delegate.add_callable( - self._handle_on_post_instantiation) - self._asset_wrapper.on_post_cook_delegate.add_callable( - self._handle_on_post_auto_cook) - self._asset_wrapper.on_pre_process_state_exited_delegate.add_callable( - self._handle_on_pre_process) - self._asset_wrapper.on_post_processing_delegate.add_callable( - self._handle_on_post_processing) - self._asset_wrapper.on_post_bake_delegate.add_callable( - self._handle_on_post_auto_bake) - - def _check_wrapper(self, wrapper): - """ Checks that wrapper matches self.asset_wrapper. Logs a warning if - it does not. - - Args: - wrapper (HoudiniPublicAPIAssetWrapper): the wrapper to check - against self.asset_wrapper - - Returns: - (bool): True if the wrappers match. - - """ - if wrapper != self._asset_wrapper: - unreal.log_warning( - '[UHoudiniPublicAPIProcessHDANode] Received delegate event ' - 'from unexpected asset wrapper ({0} vs {1})!'.format( - self._asset_wrapper.get_name() if self._asset_wrapper else '', - wrapper.get_name() if wrapper else '' - ) - ) - return False - return True - - def _handle_on_failure(self): - """ Handle any failures during the lifecycle of the process. Calls - self.on_failure() and then unbinds from self.asset_wrapper and - optionally deletes the instantiated asset. - - """ - self.on_failure() - - self._unbind_delegates() - - if self.delete_instantiated_asset_on_completion_or_failure and self.asset_wrapper: - self.asset_wrapper.delete_instantiated_asset() - - def _handle_on_complete(self): - """ Handles completion of the process. This can happen at one of - three stages: - - - After on_post_instantiate(), if enable_auto_cook is False. - - After on_post_auto_cook(), if enable_auto_cook is True but - enable_auto_bake is False. - - After on_post_auto_bake(), if both enable_auto_cook and - enable_auto_bake are True. - - Calls self.on_complete() and then unbinds from self.asset_wrapper's - delegates and optionally deletes the instantiated asset. - - """ - self.on_complete() - - self._unbind_delegates() - - if self.delete_instantiated_asset_on_completion_or_failure and self.asset_wrapper: - self.asset_wrapper.delete_instantiated_asset() - - def _handle_on_pre_instantiation(self, wrapper): - """ Called during pre_instantiation. Sets ``parameters`` on the HDA - and calls self.on_pre_instantiation(). - - """ - if not self._check_wrapper(wrapper): - return - - # Set any parameters specified for the HDA - if self.asset_wrapper and self.parameters: - self.asset_wrapper.set_parameter_tuples(self.parameters) - - self.on_pre_instantiation() - - def _handle_on_post_instantiation(self, wrapper): - """ Called during post_instantiation. Sets inputs (``node_inputs`` and - ``parameter_inputs``) on the HDA and calls self.on_post_instantiation(). - - Completes execution if enable_auto_cook is False. - - """ - if not self._check_wrapper(wrapper): - return - - # Set any inputs specified when the node was created - if self.asset_wrapper: - if self.node_inputs: - self.asset_wrapper.set_inputs_at_indices(self.node_inputs) - if self.parameter_inputs: - self.asset_wrapper.set_input_parameters(self.parameter_inputs) - - self.on_post_instantiation() - - # If not set to auto cook, complete execution now - if not self.enable_auto_cook: - self._handle_on_complete() - - def _handle_on_post_auto_cook(self, wrapper, cook_success): - """ Called during post_cook. Sets self.cook_success and calls - self.on_post_auto_cook(). - - Args: - cook_success (bool): True if the cook was successful. - - """ - if not self._check_wrapper(wrapper): - return - - self._cook_success = cook_success - - self.on_post_auto_cook(cook_success) - - def _handle_on_pre_process(self, wrapper): - """ Called during pre_process. Calls self.on_pre_process(). - - """ - if not self._check_wrapper(wrapper): - return - - self.on_pre_process() - - def _handle_on_post_processing(self, wrapper): - """ Called during post_processing. Calls self.on_post_processing(). - - Completes execution if enable_auto_bake is False. - - """ - if not self._check_wrapper(wrapper): - return - - self.on_post_processing() - - # If not set to auto bake, complete execution now - if not self.enable_auto_bake: - self._handle_on_complete() - - def _handle_on_post_auto_bake(self, wrapper, bake_success): - """ Called during post_bake. Sets self.bake_success and calls - self.on_post_auto_bake(). - - Args: - bake_success (bool): True if the bake was successful. - - """ - if not self._check_wrapper(wrapper): - return - - self._bake_success = bake_success - - self.on_post_auto_bake(bake_success) - - self._handle_on_complete() - - def on_failure(self): - """ Called if the process fails to instantiate or fails to start - a cook. - - Subclasses can override this function implement custom functionality. - - """ - pass - - def on_complete(self): - """ Called if the process completes instantiation, cook and/or baking, - depending on enable_auto_cook and enable_auto_bake. - - Subclasses can override this function implement custom functionality. - - """ - pass - - def on_pre_instantiation(self): - """ Called during pre_instantiation. - - Subclasses can override this function implement custom functionality. - - """ - pass - - def on_post_instantiation(self): - """ Called during post_instantiation. - - Subclasses can override this function implement custom functionality. - - """ - pass - - def on_post_auto_cook(self, cook_success): - """ Called during post_cook. - - Subclasses can override this function implement custom functionality. - - Args: - cook_success (bool): True if the cook was successful. - - """ - pass - - def on_pre_process(self): - """ Called during pre_process. - - Subclasses can override this function implement custom functionality. - - """ - pass - - def on_post_processing(self): - """ Called during post_processing. - - Subclasses can override this function implement custom functionality. - - """ - pass - - def on_post_auto_bake(self, bake_success): - """ Called during post_bake. - - Subclasses can override this function implement custom functionality. - - Args: - bake_success (bool): True if the bake was successful. - - """ - pass +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import unreal + + +class ProcessHDA(object): + """ An object that wraps async processing of an HDA (instantiating, + cooking/processing/baking an HDA), with functions that are called at the + various stages of the process, that can be overridden by subclasses for + custom funtionality: + + - on_failure() + - on_complete(): upon successful completion (could be PostInstantiation + if auto cook is disabled, PostProcessing if auto bake is disabled, or + after PostAutoBake if auto bake is enabled. + - on_pre_instantiation(): before the HDA is instantiated, a good place + to set parameter values before the first cook. + - on_post_instantiation(): after the HDA is instantiated, a good place + to set/configure inputs before the first cook. + - on_post_auto_cook(): right after a cook + - on_pre_process(): after a cook but before output objects have been + created/processed + - on_post_processing(): after output objects have been created + - on_post_auto_bake(): after outputs have been baked + + Instantiate the processor via the constructor and then call the activate() + function to start the asynchronous process. + + """ + def __init__( + self, + houdini_asset, + instantiate_at=unreal.Transform(), + parameters=None, + node_inputs=None, + parameter_inputs=None, + world_context_object=None, + spawn_in_level_override=None, + enable_auto_cook=True, + enable_auto_bake=False, + bake_directory_path="", + bake_method=unreal.HoudiniEngineBakeOption.TO_ACTOR, + remove_output_after_bake=False, + recenter_baked_actors=False, + replace_previous_bake=False, + delete_instantiated_asset_on_completion_or_failure=False): + """ Instantiates an HDA in the specified world/level. Sets parameters + and inputs supplied in InParameters, InNodeInputs and parameter_inputs. + If bInEnableAutoCook is true, cooks the HDA. If bInEnableAutoBake is + true, bakes the cooked outputs according to the supplied baking + parameters. + + This all happens asynchronously, with the various output pins firing at + the various points in the process: + + - PreInstantiation: before the HDA is instantiated, a good place + to set parameter values before the first cook (parameter values + from ``parameters`` are automatically applied at this point) + - PostInstantiation: after the HDA is instantiated, a good place + to set/configure inputs before the first cook (inputs from + ``node_inputs`` and ``parameter_inputs`` are automatically applied + at this point) + - PostAutoCook: right after a cook + - PreProcess: after a cook but before output objects have been + created/processed + - PostProcessing: after output objects have been created + - PostAutoBake: after outputs have been baked + - Completed: upon successful completion (could be PostInstantiation + if auto cook is disabled, PostProcessing if auto bake is disabled, + or after PostAutoBake if auto bake is enabled). + - Failed: If the process failed at any point. + + Args: + houdini_asset (HoudiniAsset): The HDA to instantiate. + instantiate_at (Transform): The Transform to instantiate the HDA with. + parameters (Map(Name, HoudiniParameterTuple)): The parameters to set before cooking the instantiated HDA. + node_inputs (Map(int32, HoudiniPublicAPIInput)): The node inputs to set before cooking the instantiated HDA. + parameter_inputs (Map(Name, HoudiniPublicAPIInput)): The parameter-based inputs to set before cooking the instantiated HDA. + world_context_object (Object): A world context object for identifying the world to spawn in, if spawn_in_level_override is null. + spawn_in_level_override (Level): If not nullptr, then the HoudiniAssetActor is spawned in that level. If both spawn_in_level_override and world_context_object are null, then the actor is spawned in the current editor context world's current level. + enable_auto_cook (bool): If true (the default) the HDA will cook automatically after instantiation and after parameter, transform and input changes. + enable_auto_bake (bool): If true, the HDA output is automatically baked after a cook. Defaults to false. + bake_directory_path (str): The directory to bake to if the bake path is not set via attributes on the HDA output. + bake_method (HoudiniEngineBakeOption): The bake target (to actor vs blueprint). @see HoudiniEngineBakeOption. + remove_output_after_bake (bool): If true, HDA temporary outputs are removed after a bake. Defaults to false. + recenter_baked_actors (bool): Recenter the baked actors to their bounding box center. Defaults to false. + replace_previous_bake (bool): If true, on every bake replace the previous bake's output (assets + actors) with the new bake's output. Defaults to false. + delete_instantiated_asset_on_completion_or_failure (bool): If true, deletes the instantiated asset actor on completion or failure. Defaults to false. + + """ + super(ProcessHDA, self).__init__() + self._houdini_asset = houdini_asset + self._instantiate_at = instantiate_at + self._parameters = parameters + self._node_inputs = node_inputs + self._parameter_inputs = parameter_inputs + self._world_context_object = world_context_object + self._spawn_in_level_override = spawn_in_level_override + self._enable_auto_cook = enable_auto_cook + self._enable_auto_bake = enable_auto_bake + self._bake_directory_path = bake_directory_path + self._bake_method = bake_method + self._remove_output_after_bake = remove_output_after_bake + self._recenter_baked_actors = recenter_baked_actors + self._replace_previous_bake = replace_previous_bake + self._delete_instantiated_asset_on_completion_or_failure = delete_instantiated_asset_on_completion_or_failure + + self._asset_wrapper = None + self._cook_success = False + self._bake_success = False + + @property + def asset_wrapper(self): + """ The asset wrapper for the instantiated HDA processed by this node. """ + return self._asset_wrapper + + @property + def cook_success(self): + """ True if the last cook was successful. """ + return self._cook_success + + @property + def bake_success(self): + """ True if the last bake was successful. """ + return self._bake_success + + @property + def houdini_asset(self): + """ The HDA to instantiate. """ + return self._houdini_asset + + @property + def instantiate_at(self): + """ The transform the instantiate the asset with. """ + return self._instantiate_at + + @property + def parameters(self): + """ The parameters to set on on_pre_instantiation """ + return self._parameters + + @property + def node_inputs(self): + """ The node inputs to set on on_post_instantiation """ + return self._node_inputs + + @property + def parameter_inputs(self): + """ The object path parameter inputs to set on on_post_instantiation """ + return self._parameter_inputs + + @property + def world_context_object(self): + """ The world context object: spawn in this world if spawn_in_level_override is not set. """ + return self._world_context_object + + @property + def spawn_in_level_override(self): + """ The level to spawn in. If both this and world_context_object is not set, spawn in the editor context's level. """ + return self._spawn_in_level_override + + @property + def enable_auto_cook(self): + """ Whether to set the instantiated asset to auto cook. """ + return self._enable_auto_cook + + @property + def enable_auto_bake(self): + """ Whether to set the instantiated asset to auto bake after a cook. """ + return self._enable_auto_bake + + @property + def bake_directory_path(self): + """ Set the fallback bake directory, for if output attributes do not specify it. """ + return self._bake_directory_path + + @property + def bake_method(self): + """ The bake method/target: for example, to actors vs to blueprints. """ + return self._bake_method + + @property + def remove_output_after_bake(self): + """ Remove temporary HDA output after a bake. """ + return self._remove_output_after_bake + + @property + def recenter_baked_actors(self): + """ Recenter the baked actors at their bounding box center. """ + return self._recenter_baked_actors + + @property + def replace_previous_bake(self): + """ Replace previous bake output on each bake. For the purposes of this + node, this would mostly apply to .uassets and not actors. + + """ + return self._replace_previous_bake + + @property + def delete_instantiated_asset_on_completion_or_failure(self): + """ Whether or not to delete the instantiated asset after Complete is called. """ + return self._delete_instantiated_asset_on_completion_or_failure + + def activate(self): + """ Activate the process. This will: + + - instantiate houdini_asset and wrap it as asset_wrapper + - call on_failure() for any immediate failures + - otherwise bind to delegates from asset_wrapper so that the + various self.on_*() functions are called as appropriate + + Returns immediately (does not block until cooking/processing is + complete). + + Returns: + (bool): False if activation failed. + + """ + # Get the API instance + houdini_api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + if not houdini_api: + # Handle failures: this will unbind delegates and call on_failure() + self._handle_on_failure() + return False + + # Create an empty API asset wrapper + self._asset_wrapper = unreal.HoudiniPublicAPIAssetWrapper.create_empty_wrapper(houdini_api) + if not self._asset_wrapper: + # Handle failures: this will unbind delegates and call on_failure() + self._handle_on_failure() + return False + + # Bind to the wrapper's delegates for instantiation, cooking, baking + # etc events + self._asset_wrapper.on_pre_instantiation_delegate.add_callable( + self._handle_on_pre_instantiation) + self._asset_wrapper.on_post_instantiation_delegate.add_callable( + self._handle_on_post_instantiation) + self._asset_wrapper.on_post_cook_delegate.add_callable( + self._handle_on_post_auto_cook) + self._asset_wrapper.on_pre_process_state_exited_delegate.add_callable( + self._handle_on_pre_process) + self._asset_wrapper.on_post_processing_delegate.add_callable( + self._handle_on_post_processing) + self._asset_wrapper.on_post_bake_delegate.add_callable( + self._handle_on_post_auto_bake) + + # Begin the instantiation process of houdini_asset and wrap it with + # self.asset_wrapper + if not houdini_api.instantiate_asset_with_existing_wrapper( + self.asset_wrapper, + self.houdini_asset, + self.instantiate_at, + self.world_context_object, + self.spawn_in_level_override, + self.enable_auto_cook, + self.enable_auto_bake, + self.bake_directory_path, + self.bake_method, + self.remove_output_after_bake, + self.recenter_baked_actors, + self.replace_previous_bake): + # Handle failures: this will unbind delegates and call on_failure() + self._handle_on_failure() + return False + + return True + + def _unbind_delegates(self): + """ Unbinds from self.asset_wrapper's delegates (if valid). """ + if not self._asset_wrapper: + return + + self._asset_wrapper.on_pre_instantiation_delegate.add_callable( + self._handle_on_pre_instantiation) + self._asset_wrapper.on_post_instantiation_delegate.add_callable( + self._handle_on_post_instantiation) + self._asset_wrapper.on_post_cook_delegate.add_callable( + self._handle_on_post_auto_cook) + self._asset_wrapper.on_pre_process_state_exited_delegate.add_callable( + self._handle_on_pre_process) + self._asset_wrapper.on_post_processing_delegate.add_callable( + self._handle_on_post_processing) + self._asset_wrapper.on_post_bake_delegate.add_callable( + self._handle_on_post_auto_bake) + + def _check_wrapper(self, wrapper): + """ Checks that wrapper matches self.asset_wrapper. Logs a warning if + it does not. + + Args: + wrapper (HoudiniPublicAPIAssetWrapper): the wrapper to check + against self.asset_wrapper + + Returns: + (bool): True if the wrappers match. + + """ + if wrapper != self._asset_wrapper: + unreal.log_warning( + '[UHoudiniPublicAPIProcessHDANode] Received delegate event ' + 'from unexpected asset wrapper ({0} vs {1})!'.format( + self._asset_wrapper.get_name() if self._asset_wrapper else '', + wrapper.get_name() if wrapper else '' + ) + ) + return False + return True + + def _handle_on_failure(self): + """ Handle any failures during the lifecycle of the process. Calls + self.on_failure() and then unbinds from self.asset_wrapper and + optionally deletes the instantiated asset. + + """ + self.on_failure() + + self._unbind_delegates() + + if self.delete_instantiated_asset_on_completion_or_failure and self.asset_wrapper: + self.asset_wrapper.delete_instantiated_asset() + + def _handle_on_complete(self): + """ Handles completion of the process. This can happen at one of + three stages: + + - After on_post_instantiate(), if enable_auto_cook is False. + - After on_post_auto_cook(), if enable_auto_cook is True but + enable_auto_bake is False. + - After on_post_auto_bake(), if both enable_auto_cook and + enable_auto_bake are True. + + Calls self.on_complete() and then unbinds from self.asset_wrapper's + delegates and optionally deletes the instantiated asset. + + """ + self.on_complete() + + self._unbind_delegates() + + if self.delete_instantiated_asset_on_completion_or_failure and self.asset_wrapper: + self.asset_wrapper.delete_instantiated_asset() + + def _handle_on_pre_instantiation(self, wrapper): + """ Called during pre_instantiation. Sets ``parameters`` on the HDA + and calls self.on_pre_instantiation(). + + """ + if not self._check_wrapper(wrapper): + return + + # Set any parameters specified for the HDA + if self.asset_wrapper and self.parameters: + self.asset_wrapper.set_parameter_tuples(self.parameters) + + self.on_pre_instantiation() + + def _handle_on_post_instantiation(self, wrapper): + """ Called during post_instantiation. Sets inputs (``node_inputs`` and + ``parameter_inputs``) on the HDA and calls self.on_post_instantiation(). + + Completes execution if enable_auto_cook is False. + + """ + if not self._check_wrapper(wrapper): + return + + # Set any inputs specified when the node was created + if self.asset_wrapper: + if self.node_inputs: + self.asset_wrapper.set_inputs_at_indices(self.node_inputs) + if self.parameter_inputs: + self.asset_wrapper.set_input_parameters(self.parameter_inputs) + + self.on_post_instantiation() + + # If not set to auto cook, complete execution now + if not self.enable_auto_cook: + self._handle_on_complete() + + def _handle_on_post_auto_cook(self, wrapper, cook_success): + """ Called during post_cook. Sets self.cook_success and calls + self.on_post_auto_cook(). + + Args: + cook_success (bool): True if the cook was successful. + + """ + if not self._check_wrapper(wrapper): + return + + self._cook_success = cook_success + + self.on_post_auto_cook(cook_success) + + def _handle_on_pre_process(self, wrapper): + """ Called during pre_process. Calls self.on_pre_process(). + + """ + if not self._check_wrapper(wrapper): + return + + self.on_pre_process() + + def _handle_on_post_processing(self, wrapper): + """ Called during post_processing. Calls self.on_post_processing(). + + Completes execution if enable_auto_bake is False. + + """ + if not self._check_wrapper(wrapper): + return + + self.on_post_processing() + + # If not set to auto bake, complete execution now + if not self.enable_auto_bake: + self._handle_on_complete() + + def _handle_on_post_auto_bake(self, wrapper, bake_success): + """ Called during post_bake. Sets self.bake_success and calls + self.on_post_auto_bake(). + + Args: + bake_success (bool): True if the bake was successful. + + """ + if not self._check_wrapper(wrapper): + return + + self._bake_success = bake_success + + self.on_post_auto_bake(bake_success) + + self._handle_on_complete() + + def on_failure(self): + """ Called if the process fails to instantiate or fails to start + a cook. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_complete(self): + """ Called if the process completes instantiation, cook and/or baking, + depending on enable_auto_cook and enable_auto_bake. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_pre_instantiation(self): + """ Called during pre_instantiation. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_instantiation(self): + """ Called during post_instantiation. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_auto_cook(self, cook_success): + """ Called during post_cook. + + Subclasses can override this function implement custom functionality. + + Args: + cook_success (bool): True if the cook was successful. + + """ + pass + + def on_pre_process(self): + """ Called during pre_process. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_processing(self): + """ Called during post_processing. + + Subclasses can override this function implement custom functionality. + + """ + pass + + def on_post_auto_bake(self, bake_success): + """ Called during post_bake. + + Subclasses can override this function implement custom functionality. + + Args: + bake_success (bool): True if the bake was successful. + + """ + pass diff --git a/HoudiniEngine.uplugin b/HoudiniEngine.uplugin index 6c5b74e75..f64ef058e 100644 --- a/HoudiniEngine.uplugin +++ b/HoudiniEngine.uplugin @@ -1,8 +1,8 @@ { "FileVersion" : 3, "FriendlyName" : "Houdini Engine v2", - "Version" : 18050633, - "VersionName" : "v2.0 - H18.5.633", + "Version" : 18050696, + "VersionName" : "v2.0 - H18.5.696", "CreatedBy" : "Side Effects Software Inc.", "CreatedByURL" : "http://www.sidefx.com", "DocsURL" : "http://www.sidefx.com/docs/unreal/", diff --git a/LICENSE.md b/LICENSE.md index 21630f364..e6ad1fabc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,26 +1,26 @@ - - Copyright (c) 2021 - Side Effects Software Inc. All rights reserved. - - Redistribution and use of in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. The names Side Effects Software and SideFX may not be used to endorse or - promote products derived from this software without specific prior - written permission. - - THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS - OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, - EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + + Copyright (c) 2021 + Side Effects Software Inc. All rights reserved. + + Redistribution and use of in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. The names Side Effects Software and SideFX may not be used to endorse or + promote products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + \ No newline at end of file diff --git a/README.md b/README.md index 09588d4f1..a09e3f438 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,106 @@ -# Houdini Engine for Unreal - Version 2.0 - -Welcome to the repository for Version 2 of the Houdini Engine For Unreal Plugin. - -This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. - -Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. - -Here are some of the new features and improvements currently available: - - -Core: -- New and redesigned core architecture, more modular and lightweight. - All the Houdini Engine/HAPI logic is now Editor-only and contained in the “HoudiniEngine” module. All the custom runtime components and actors used by the plugin now simply acts as data-holders, and are processed by the HoudiniEngine modules, removing the need to bake HDA before packaging a game. -- The plugin now relies exclusively on native, UProperties based serialization, so operations like cut and paste, move between level, duplicate etc.. do not exhibit any of the issues that version 1 had with those operations. - -Outputs: -- Static Mesh creation time has been optimized and now uses Mesh Descriptions. -- Alternatively, you can also decide to use an even faster Proxy Mesh generation while editing the HDA. -  Those can then be automatically refined to Static Meshes, either on a timer, or when saving/playing the level. -- World composition support: Tiled heightfields can now be baked to multiple landscape actors/steaming proxies, and will create/update the levels needed for world composition. -  You can specify the level's path by using the new “unreal_level_path” attribute, that is also used by meshes and instancers so they can also be baked out to separate levels. -- Material overrides and generic uproperty attributes can either be applied on the instancer, or per instance (when using mesh split instancers or instanced Actors). -- It is possible to create foliage instances directly, without baking, when using the “unreal_foliage” attribute on an instancer. -- A class can be directly instantiated by the "unreal_instance" attribute (ie “PointLight”, “AudioVolume”… ). -- Curves can be outputed to SplineComponents by using the "unreal_output_curve" primitive attribute. - -Inputs: - -- Colliders on a Static Mesh can now be imported as group geometry. -- World inputs can now read data from BSP brushes. -- Instancers and Foliage are now imported as packed primitives. -- World inputs have an improved bound selector mode, that lets them send all the actors and objects contained in the bounds of the selected object. -- World inputs can now import data from all supported input objects (landscape, houdini asset actors..) -- World inputs can now import data from actors placed in a different level than the Houdini Asset Actors's. -- A single curve input can now create and import any number of curves. -- You can alt-click on curve inputs or editable curves to create new points. -  -Parameters: -- HDA parameters and inputs editing now support multi-selection. -- Parameter UI/UX has been improved: -- Folder UI (tabs, radio, collapsible) has been improved -- Ramps UI has been improved, and it is easy to turn off auto-update while editing them. -- When an asset is dropped on a string parameter, it automatically sets its value to the asset ref. -- String parameters can now be turned into an asset picker via the “unreal_ref” tag. -- Support for File parameters has been improved (custom extension, directory, new file...) -- Multi-line strings, Column Labels, Button Strip, Log Int and Floats are now supported. - -General: -- The plugin's UI has been completely revamped, a new Houdini Engine menu has been added to the editor. -- bgeo/bgeo.sc files can be imported natively in the content browser (Mesh and instancers). -- The PDG Asset Link has been added, allowing control of TOP networks nested in HDAs, and works similarly to the one in the Unity plugin. -- Session Sync is supported, allowing the plugin to connect to a session of Houdini Engine running inside Houdini. -  The state of Houdini Engine can be viewed in Houdini while working with the plugin in Unreal, and changes on either end, whether in Unreal via the plugin or in Houdini via its various interfaces, will be synchronized across so that both applications will be able to make changes and see the same results. -- Blueprint support: It is now possible to use Houdini Asset Components in the Blueprint Editor. - This lets you preset and use HDAs on Blueprint Actors, and changing parameters/inputs on the Houdini Asset will automatically update all placed instances of that Blueprint. - - -For more details on the new features and improvements available, please visit the [Wiki](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/wiki/What's-new-%3F). -Documentation for version 2.0 of the plugin is also available on the Side FX [Website](https://www.sidefx.com/docs/unreal/). - - -# Feedback - -Please send bug reports, feature requests and questions to [Side FX's support](https://www.sidefx.com/bugs/submit/). - - -# Compatibility - -Currently, [Version 2.0](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/releases) has binaries that have been built for UE4.26 and UE4.25, and is linked with the latest production build of Houdini, H18.5.462. - -Source code for the plugin is available on this repository for UE4.26, UE4.25, UE4.24, UE4.23 and the master branch of Unreal (4.27). - -Version 2 is also partially backward compatible with version 1 of the Houdini Engine for Unreal plugin. - -When loading a level that contains Houdini objects made with version 1, the plugin will attempt to convert the V1 components, parameters, inputs and outputs to their v2 equivalents. - -Some HDAs might need to be rebuilt after the conversion for their parameters and inputs to be displayed properly by the v2 plugin. - -The conversion of the legacy v1 data is still in progress and will be improved upon in the future. -However, the Houdini Digital Assets themselves (the HDA files), that were created for version 1 of the plugin are fully compatible with version 2, as it supports most of version 1 workflows. - -# Installing the plugin - -01. Download the pre-built binaries of the plugin in the "Releases" section of this repository. - -01. Extract the "HoudiniEngine" folder in the release to the "Plugins/Runtime" folder of Unreal. - You can install the plugin either directly in the engine folder (in "Engine/Plugins/Runtime/HoudiniEngine") or in your project folder (in "Plugins/Runtime/HoudiniEngine"). -01. Start Unreal Engine, open the Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. -01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". -01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. -01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters and inputs in the `Details` panel. Modifying any of the parameters will force the asset to recook and possibly update its geometry. - - -# Building from source - -01. Get the UE4 source code from: https://github.com/EpicGames/UnrealEngine/releases -01. Within the UE4 source, navigate to `Engine/Plugins/Runtime`, and clone this repo into a folder named `HoudiniEngine`. -01. Download and install the correct build of 64-bit Houdini. To get the build number, look at the header of `Source/HoudiniEngine/HoudiniEngine.Build.cs`, under `Houdini Version`. -01. Generate the UE4 Project Files (by running `GenerateProjectFiles`) and build Unreal, either in x64 `Debug Editor` or x64 `Development Editor`. -01. When starting the Unreal Engine editor, go to Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. -01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". -01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. -01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters in the `Details` pane. Modifying any of the parameters will force the asset to recook and possibly update its geometry. - - +# Houdini Engine for Unreal - Version 2.0 + +Welcome to the repository for Version 2 of the Houdini Engine For Unreal Plugin. + +This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. + +Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. + +Here are some of the new features and improvements currently available: + + +Core: +- New and redesigned core architecture, more modular and lightweight. + All the Houdini Engine/HAPI logic is now Editor-only and contained in the “HoudiniEngine” module. All the custom runtime components and actors used by the plugin now simply acts as data-holders, and are processed by the HoudiniEngine modules, removing the need to bake HDA before packaging a game. +- The plugin now relies exclusively on native, UProperties based serialization, so operations like cut and paste, move between level, duplicate etc.. do not exhibit any of the issues that version 1 had with those operations. + +Outputs: +- Static Mesh creation time has been optimized and now uses Mesh Descriptions. +- Alternatively, you can also decide to use an even faster Proxy Mesh generation while editing the HDA. +  Those can then be automatically refined to Static Meshes, either on a timer, or when saving/playing the level. +- World composition support: Tiled heightfields can now be baked to multiple landscape actors/steaming proxies, and will create/update the levels needed for world composition. +  You can specify the level's path by using the new “unreal_level_path” attribute, that is also used by meshes and instancers so they can also be baked out to separate levels. +- Material overrides and generic uproperty attributes can either be applied on the instancer, or per instance (when using mesh split instancers or instanced Actors). +- It is possible to create foliage instances directly, without baking, when using the “unreal_foliage” attribute on an instancer. +- A class can be directly instantiated by the "unreal_instance" attribute (ie “PointLight”, “AudioVolume”… ). +- Curves can be outputed to SplineComponents by using the "unreal_output_curve" primitive attribute. + +Inputs: + +- Colliders on a Static Mesh can now be imported as group geometry. +- World inputs can now read data from BSP brushes. +- Instancers and Foliage are now imported as packed primitives. +- World inputs have an improved bound selector mode, that lets them send all the actors and objects contained in the bounds of the selected object. +- World inputs can now import data from all supported input objects (landscape, houdini asset actors..) +- World inputs can now import data from actors placed in a different level than the Houdini Asset Actors's. +- A single curve input can now create and import any number of curves. +- You can alt-click on curve inputs or editable curves to create new points. +  +Parameters: +- HDA parameters and inputs editing now support multi-selection. +- Parameter UI/UX has been improved: +- Folder UI (tabs, radio, collapsible) has been improved +- Ramps UI has been improved, and it is easy to turn off auto-update while editing them. +- When an asset is dropped on a string parameter, it automatically sets its value to the asset ref. +- String parameters can now be turned into an asset picker via the “unreal_ref” tag. +- Support for File parameters has been improved (custom extension, directory, new file...) +- Multi-line strings, Column Labels, Button Strip, Log Int and Floats are now supported. + +General: +- The plugin's UI has been completely revamped, a new Houdini Engine menu has been added to the editor. +- bgeo/bgeo.sc files can be imported natively in the content browser (Mesh and instancers). +- The PDG Asset Link has been added, allowing control of TOP networks nested in HDAs, and works similarly to the one in the Unity plugin. +- Session Sync is supported, allowing the plugin to connect to a session of Houdini Engine running inside Houdini. +  The state of Houdini Engine can be viewed in Houdini while working with the plugin in Unreal, and changes on either end, whether in Unreal via the plugin or in Houdini via its various interfaces, will be synchronized across so that both applications will be able to make changes and see the same results. +- Blueprint support: It is now possible to use Houdini Asset Components in the Blueprint Editor. + This lets you preset and use HDAs on Blueprint Actors, and changing parameters/inputs on the Houdini Asset will automatically update all placed instances of that Blueprint. + + +For more details on the new features and improvements available, please visit the [Wiki](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/wiki/What's-new-%3F). +Documentation for version 2.0 of the plugin is also available on the Side FX [Website](https://www.sidefx.com/docs/unreal/). + + +# Feedback + +Please send bug reports, feature requests and questions to [Side FX's support](https://www.sidefx.com/bugs/submit/). + + +# Compatibility + +Currently, [Version 2.0](https://github.com/sideeffects/HoudiniEngineForUnreal-v2/releases) has binaries that have been built for UE4.26 and UE4.25, and is linked with the latest production build of Houdini, H18.5.462. + +Source code for the plugin is available on this repository for UE4.26, UE4.25, UE4.24, UE4.23 and the master branch of Unreal (4.27). + +Version 2 is also partially backward compatible with version 1 of the Houdini Engine for Unreal plugin. + +When loading a level that contains Houdini objects made with version 1, the plugin will attempt to convert the V1 components, parameters, inputs and outputs to their v2 equivalents. + +Some HDAs might need to be rebuilt after the conversion for their parameters and inputs to be displayed properly by the v2 plugin. + +The conversion of the legacy v1 data is still in progress and will be improved upon in the future. +However, the Houdini Digital Assets themselves (the HDA files), that were created for version 1 of the plugin are fully compatible with version 2, as it supports most of version 1 workflows. + +# Installing the plugin + +01. Download the pre-built binaries of the plugin in the "Releases" section of this repository. + +01. Extract the "HoudiniEngine" folder in the release to the "Plugins/Runtime" folder of Unreal. + You can install the plugin either directly in the engine folder (in "Engine/Plugins/Runtime/HoudiniEngine") or in your project folder (in "Plugins/Runtime/HoudiniEngine"). +01. Start Unreal Engine, open the Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. +01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". +01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. +01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters and inputs in the `Details` panel. Modifying any of the parameters will force the asset to recook and possibly update its geometry. + + +# Building from source + +01. Get the UE4 source code from: https://github.com/EpicGames/UnrealEngine/releases +01. Within the UE4 source, navigate to `Engine/Plugins/Runtime`, and clone this repo into a folder named `HoudiniEngine`. +01. Download and install the correct build of 64-bit Houdini. To get the build number, look at the header of `Source/HoudiniEngine/HoudiniEngine.Build.cs`, under `Houdini Version`. +01. Generate the UE4 Project Files (by running `GenerateProjectFiles`) and build Unreal, either in x64 `Debug Editor` or x64 `Development Editor`. +01. When starting the Unreal Engine editor, go to Plug-ins menu and make sure to enable the `HoudiniEngine v2` plug-in (in the `Rendering` section). Restart UE4 if you had to enable it. +01. To confirm that the plug-in has been successfully installed and enabled, you can check that the editor main menu bar now has a new "Houdini Engine" menu, between "Edit" and "Window". +01. You should now be able to import Houdini Digital Assets (HDA) `.otl` or `.hda` files or drag and drop them into the `Content Browser`. +01. Once you have an HDA in the `Content Browser` you should be able to drag it into the Editor viewport. This will spawn a new Houdini Asset Actor. Geometry cooking will be done in a separate thread and geometry will be displayed once the cooking is complete. At this point you will be able to see asset parameters in the `Details` pane. Modifying any of the parameters will force the asset to recook and possibly update its geometry. + + diff --git a/Source/HoudiniEngine/HoudiniEngine.Build.cs b/Source/HoudiniEngine/HoudiniEngine.Build.cs index 8be19f614..e5985839c 100644 --- a/Source/HoudiniEngine/HoudiniEngine.Build.cs +++ b/Source/HoudiniEngine/HoudiniEngine.Build.cs @@ -32,9 +32,9 @@ /* - Houdini Version: 18.5.633 - Houdini Engine Version: 3.6.3 - Unreal Version: 4.26.0 + Houdini Version: 18.5.696 + Houdini Engine Version: 3.7.1 + Unreal Version: 4.27.0 */ @@ -47,9 +47,9 @@ public class HoudiniEngine : ModuleRules { private string GetHFSPath() { - string HoudiniVersion = "18.5.633"; + string HoudiniVersion = "18.5.696"; bool bIsRelease = true; - string HFSPath = "C:/cygwin/home/prisms/builder-new/Nightly18.5CMakePython3/dev/hfs"; + string HFSPath = "C:/cygwin/home/prisms/builder-new/Nightly18.5CMake/dev/hfs"; string RegistryPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Side Effects Software"; string Registry32Path = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Side Effects Software"; string log; diff --git a/Source/HoudiniEngine/Private/HBSPOps.cpp b/Source/HoudiniEngine/Private/HBSPOps.cpp index a6d318b36..b081d0c80 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.cpp +++ b/Source/HoudiniEngine/Private/HBSPOps.cpp @@ -1,1486 +1,1486 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HBSPOps.h" -#include "EngineDefines.h" -#include "Model.h" -#include "Materials/Material.h" -#include "Engine/BrushBuilder.h" -#include "Editor/EditorEngine.h" -#include "Components/BrushComponent.h" -#include "GameFramework/Volume.h" - -DEFINE_LOG_CATEGORY_STATIC(LogBSPOps, Log, All); - -/** Errors encountered in Csg operation. */ -int32 FHBSPOps::GErrors = 0; -bool FHBSPOps::GFastRebuild = false; - -static void TagReferencedNodes( UModel *Model, int32 *NodeRef, int32 *PolyRef, int32 iNode ) -{ - FBspNode &Node = Model->Nodes[iNode]; - - NodeRef[iNode ] = 0; - PolyRef[Node.iSurf] = 0; - - if( Node.iFront != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iFront); - if( Node.iBack != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iBack ); - if( Node.iPlane != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iPlane); -} - -// -// Update a bounding volume by expanding it to enclose a list of polys. -// -static void UpdateBoundWithPolys( FBox& Bound, FPoly** PolyList, int32 nPolys ) -{ - for( int32 i=0; iVertices.Num(); j++ ) - Bound += PolyList[i]->Vertices[j]; -} - -// -// Update a convolution hull with a list of polys. -// -static void UpdateConvolutionWithPolys( UModel *Model, int32 iNode, FPoly **PolyList, int32 nPolys ) -{ - FBox Box(ForceInit); - - FBspNode &Node = Model->Nodes[iNode]; - Node.iCollisionBound = Model->LeafHulls.Num(); - for( int32 i=0; iiBrushPoly != INDEX_NONE ) - { - int32 j; - for( j=0; jiBrushPoly == PolyList[i]->iBrushPoly ) - break; - if( j >= i ) - Model->LeafHulls.Add(PolyList[i]->iBrushPoly); - } - for( int32 j=0; jVertices.Num(); j++ ) - Box += PolyList[i]->Vertices[j]; - } - Model->LeafHulls.Add(INDEX_NONE); - - // Add bounds. - Model->LeafHulls.Add( *(int32*)&Box.Min.X ); - Model->LeafHulls.Add( *(int32*)&Box.Min.Y ); - Model->LeafHulls.Add( *(int32*)&Box.Min.Z ); - Model->LeafHulls.Add( *(int32*)&Box.Max.X ); - Model->LeafHulls.Add( *(int32*)&Box.Max.Y ); - Model->LeafHulls.Add( *(int32*)&Box.Max.Z ); - -} - -// -// Cut a partitioning poly by a list of polys, and add the resulting inside pieces to the -// front list and back list. -// -static void SplitPartitioner -( - UModel* Model, - FPoly** PolyList, - FPoly** FrontList, - FPoly** BackList, - int32 n, - int32 nPolys, - int32& nFront, - int32& nBack, - FPoly InfiniteEdPoly, - TArray& AllocatedFPolys -) -{ - FPoly FrontPoly,BackPoly; - while( n < nPolys ) - { - FPoly* Poly = PolyList[n]; - switch( InfiniteEdPoly.SplitWithPlane(Poly->Vertices[0],Poly->Normal,&FrontPoly,&BackPoly,0) ) - { - case SP_Coplanar: - // May occasionally happen. -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got inficoplanar") ); - break; - - case SP_Front: - // Shouldn't happen if hull is correct. -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got infifront") ); - return; - - case SP_Split: - InfiniteEdPoly = BackPoly; - break; - - case SP_Back: - break; - } - n++; - } - - FPoly* New = new FPoly; - *New = InfiniteEdPoly; - New->Reverse(); - New->iBrushPoly |= 0x40000000; - FrontList[nFront++] = New; - AllocatedFPolys.Add( New ); - - New = new FPoly; - *New = InfiniteEdPoly; - BackList[nBack++] = New; - AllocatedFPolys.Add( New ); -} - -// -// Build an FPoly representing an "infinite" plane (which exceeds the maximum -// dimensions of the world in all directions) for a particular Bsp node. -// -FPoly FHBSPOps::BuildInfiniteFPoly( UModel* Model, int32 iNode ) -{ - FBspNode &Node = Model->Nodes [iNode ]; - FBspSurf &Poly = Model->Surfs [Node.iSurf ]; - FVector Base = Poly.Plane * Poly.Plane.W; - FVector Normal = Poly.Plane; - FVector Axis1,Axis2; - - // Find two non-problematic axis vectors. - Normal.FindBestAxisVectors( Axis1, Axis2 ); - - // Set up the FPoly. - FPoly EdPoly; - EdPoly.Init(); - EdPoly.Normal = Normal; - EdPoly.Base = Base; - new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX + Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX + Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX - Axis2*WORLD_MAX); - new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX - Axis2*WORLD_MAX); - - return EdPoly; -} - -// -// Recursively filter a set of polys defining a convex hull down the Bsp, -// splitting it into two halves at each node and adding in the appropriate -// face polys at splits. -// -static void FilterBound -( - UModel* Model, - FBox* ParentBound, - int32 iNode, - FPoly** PolyList, - int32 nPolys, - int32 Outside -) -{ - FMemMark Mark(FMemStack::Get()); - FBspNode& Node = Model->Nodes [iNode]; - FBspSurf& Surf = Model->Surfs [Node.iSurf]; - FVector Base = Surf.Plane * Surf.Plane.W; - FVector& Normal = Model->Vectors[Surf.vNormal]; - FBox Bound(ForceInit); - - Bound.Min.X = Bound.Min.Y = Bound.Min.Z = +WORLD_MAX; - Bound.Max.X = Bound.Max.Y = Bound.Max.Z = -WORLD_MAX; - - // Split bound into front half and back half. - FPoly** FrontList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nFront=0; - FPoly** BackList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nBack=0; - - // Keeping track of allocated FPoly structures to delete later on. - TArray AllocatedFPolys; - - FPoly* FrontPoly = new FPoly; - FPoly* BackPoly = new FPoly; - - // Keep track of allocations. - AllocatedFPolys.Add( FrontPoly ); - AllocatedFPolys.Add( BackPoly ); - - for( int32 i=0; iSplitWithPlane( Base, Normal, FrontPoly, BackPoly, 0 ) ) - { - case SP_Coplanar: -// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got coplanar") ); - FrontList[nFront++] = Poly; - BackList[nBack++] = Poly; - break; - - case SP_Front: - FrontList[nFront++] = Poly; - break; - - case SP_Back: - BackList[nBack++] = Poly; - break; - - case SP_Split: - FrontList[nFront++] = FrontPoly; - BackList [nBack++] = BackPoly; - - FrontPoly = new FPoly; - BackPoly = new FPoly; - - // Keep track of allocations. - AllocatedFPolys.Add( FrontPoly ); - AllocatedFPolys.Add( BackPoly ); - - break; - - default: - UE_LOG(LogBSPOps, Fatal, TEXT("FZoneFilter::FilterToLeaf: Unknown split code") ); - } - } - if( nFront && nBack ) - { - // Add partitioner plane to front and back. - FPoly InfiniteEdPoly = FHBSPOps::BuildInfiniteFPoly( Model, iNode ); - InfiniteEdPoly.iBrushPoly = iNode; - - SplitPartitioner(Model,PolyList,FrontList,BackList,0,nPolys,nFront,nBack,InfiniteEdPoly,AllocatedFPolys); - } - else - { -// if( !nFront ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty fronthull") ); -// if( !nBack ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty backhull") ); - } - - // Recursively update all our childrens' bounding volumes. - if( nFront > 0 ) - { - if( Node.iFront != INDEX_NONE ) - FilterBound( Model, &Bound, Node.iFront, FrontList, nFront, Outside || Node.IsCsg() ); - else if( Outside || Node.IsCsg() ) - UpdateBoundWithPolys( Bound, FrontList, nFront ); - else - UpdateConvolutionWithPolys( Model, iNode, FrontList, nFront ); - } - if( nBack > 0 ) - { - if( Node.iBack != INDEX_NONE) - FilterBound( Model, &Bound,Node.iBack, BackList, nBack, Outside && !Node.IsCsg() ); - else if( Outside && !Node.IsCsg() ) - UpdateBoundWithPolys( Bound, BackList, nBack ); - else - UpdateConvolutionWithPolys( Model, iNode, BackList, nBack ); - } - - // Update parent bound to enclose this bound. - if( ParentBound ) - *ParentBound += Bound; - - // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. - for( int32 i=0; i0); - - // No need to test if only one poly. - if( NumPolys==1 ) - return PolyList[0]; - - FPoly *Poly, *Best=NULL; - float Score, BestScore; - int32 i, Index, j, Inc; - int32 Splits, Front, Back, Coplanar, AllSemiSolids; - - //PortalBias -- added by Legend on 4/12/2000 - float PortalBias = InPortalBias / 100.0f; - Balance &= 0xFF; // keep only the low byte to recover "Balance" - //UE_LOG(LogBSPOps, Log, TEXT("Balance=%d PortalBias=%f"), Balance, PortalBias ); - - if (Opt==FHBSPOps::BSP_Optimal) Inc = 1; // Test lots of nodes. - else if (Opt==FHBSPOps::BSP_Good) Inc = FMath::Max(1,NumPolys/20); // Test 20 nodes. - else /* BSP_Lame */ Inc = FMath::Max(1,NumPolys/4); // Test 4 nodes. - - // See if there are any non-semisolid polygons here. - for( i=0; iPolyFlags & PF_AddLast) ) - break; - AllSemiSolids = (i>=NumPolys); - - // Search through all polygons in the pool and find: - // A. The number of splits each poly would make. - // B. The number of front and back nodes the polygon would create. - // C. Number of coplanars. - BestScore = 0; - for( i=0; iPolyFlags & PF_AddLast) && !(Poly->PolyFlags & PF_Portal) ) - && !AllSemiSolids ); - if( Index>=i+Inc || Index>=NumPolys ) - continue; - - for( j=0; jSplitWithPlaneFast( FPlane( Poly->Vertices[0], Poly->Normal), NULL, NULL ) ) - { - case SP_Coplanar: - Coplanar++; - break; - - case SP_Front: - Front++; - break; - - case SP_Back: - Back++; - break; - - case SP_Split: - // Disfavor splitting polys that are zone portals. - if( !(OtherPoly->PolyFlags & PF_Portal) ) - Splits++; - else - Splits += 16; - break; - } - } - // added by Legend 1/31/1999 - // Score optimization: minimize cuts vs. balance tree (as specified in BSP Rebuilder dialog) - Score = ( 100.0 - float(Balance) ) * Splits + float(Balance) * FMath::Abs( Front - Back ); - if( Poly->PolyFlags & PF_Portal ) - { - // PortalBias -- added by Legend on 4/12/2000 - // - // PortalBias enables level designers to control the effect of Portals on the BSP. - // This effect can range from 0.0 (ignore portals), to 1.0 (portals cut everything). - // - // In builds prior to this (since the 221 build dating back to 1/31/1999) the bias - // has been 1.0 causing the portals to cut the BSP in ways that will potentially - // degrade level performance, and increase the BSP complexity. - // - // By setting the bias to a value between 0.3 and 0.7 the positive effects of - // the portals are preserved without giving them unreasonable priority in the BSP. - // - // Portals should be weighted high enough in the BSP to separate major parts of the - // level from each other (pushing entire rooms down the branches of the BSP), but - // should not be so high that portals cut through adjacent geometry in a way that - // increases complexity of the room being (typically, accidentally) cut. - // - Score -= ( 100.0 - float(Balance) ) * Splits * PortalBias; // ignore PortalBias of the split polys -- bias toward portal selection for cutting planes! - } - //UE_LOG(LogBSPOps, Log, " %4d: Score = %f (Front = %4d, Back = %4d, Splits = %4d, Flags = %08X)", Index, Score, Front, Back, Splits, Poly->PolyFlags ); //LEC - - if( Score AllocatedFPolys; - - // To account for big EdPolys split up. - int32 NumPolysToAlloc = NumPolys + 8 + NumPolys/4; - int32 NumFront=0; FPoly **FrontList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; - int32 NumBack =0; FPoly **BackList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; - - FPoly *SplitPoly = FindBestSplit( NumPolys, PolyList, Opt, Balance, PortalBias ); - - // Add the splitter poly to the Bsp with either a new BspSurf or an existing one. - if( RebuildSimplePolys ) - { - SplitPoly->iLinkSurf = Model->Surfs.Num(); - } - - int32 iOurNode = bspAddNode(Model,iParent,NodePlace,0,SplitPoly, BspPoints, BspVectors); - int32 iPlaneNode = iOurNode; - - // Now divide all polygons in the pool into (A) polygons that are - // in front of Poly, and (B) polygons that are in back of Poly. - // Coplanar polys are inserted immediately, before recursing. - - // If any polygons are split by Poly, we ignrore the original poly, - // split it into two polys, and add two new polys to the pool. - FPoly *FrontEdPoly = new FPoly; - FPoly *BackEdPoly = new FPoly; - // Keep track of allocations. - AllocatedFPolys.Add( FrontEdPoly ); - AllocatedFPolys.Add( BackEdPoly ); - - for( int32 i=0; iSplitWithPlane( SplitPoly->Vertices[0], SplitPoly->Normal, FrontEdPoly, BackEdPoly, 0 ) ) - { - case SP_Coplanar: - if( RebuildSimplePolys ) - { - EdPoly->iLinkSurf = Model->Surfs.Num()-1; - } - iPlaneNode = bspAddNode( Model, iPlaneNode, NODE_Plane, 0, EdPoly, BspPoints, BspVectors ); - break; - - case SP_Front: - FrontList[NumFront++] = PolyList[i]; - break; - - case SP_Back: - BackList[NumBack++] = PolyList[i]; - break; - - case SP_Split: - - // Create front & back nodes. - FrontList[NumFront++] = FrontEdPoly; - BackList [NumBack ++] = BackEdPoly; - - FrontEdPoly = new FPoly; - BackEdPoly = new FPoly; - // Keep track of allocations. - AllocatedFPolys.Add( FrontEdPoly ); - AllocatedFPolys.Add( BackEdPoly ); - - break; - } - } - - // Recursively split the front and back pools. - if( NumFront > 0 ) SplitPolyList( Model, iOurNode, NODE_Front, NumFront, FrontList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); - if( NumBack > 0 ) SplitPolyList( Model, iOurNode, NODE_Back, NumBack, BackList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); - - // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. - for( int32 i=0; iGetName() ); // moved here so that we can easily debug when an actor has lost parts of its brush - - check(Actor->GetBrushComponent()); - check(Actor->Brush); - check(Actor->Brush->RootOutside); - - RebuildBrush(Actor->Brush, BspPoints, BspVectors); - - // Make sure simplified collision is up to date. - Actor->GetBrushComponent()->BuildSimpleBrushCollision(); - Actor->RebuildNavigationData(); -} - -/** - * Duplicates the specified brush and makes it into a CSG-able level brush. - * @return The new brush, or NULL if the original was empty. - */ -void FHBSPOps::csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - check(Src); - check(Src->GetBrushComponent()); - check(Src->Brush); - - // Handle empty brush. - if( !bAllowEmpty && !Src->Brush->Polys->Element.Num() ) - { - Dest->Brush = NULL; - Dest->GetBrushComponent()->Brush = NULL; - return; - } - - // Duplicate the brush and its polys. - Dest->PolyFlags = PolyFlags; - Dest->Brush = NewObject(Dest, NAME_None, ResFlags); - Dest->Brush->Initialize(nullptr, Src->Brush->RootOutside); - Dest->Brush->Polys = NewObject(Dest->Brush, NAME_None, ResFlags); - Dest->Brush->Polys->Element = Src->Brush->Polys->Element; - Dest->GetBrushComponent()->Brush = Dest->Brush; - if(Src->BrushBuilder != nullptr) - { - Dest->BrushBuilder = DuplicateObject(Src->BrushBuilder, Dest); - } - - // Update poly textures. - for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) - { - Dest->Brush->Polys->Element[i].iBrushPoly = INDEX_NONE; - } - - // Copy positioning, and build bounding box. - if(bCopyPosRotScale) - { - Dest->CopyPosRotScaleFrom( Src ); - } - - // If it's a moving brush, prep it. - if( bNeedsPrep ) - { - csgPrepMovingBrush( Dest, BspPoints, BspVectors ); - } -} - -/** - * Adds a brush to the list of CSG brushes in the level, using a CSG operation. - * - * @return A newly-created copy of the brush. - */ -ABrush* FHBSPOps::csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - check(Actor); - check(Actor->GetBrushComponent()); - check(Actor->Brush); - check(Actor->Brush->Polys); - check(Actor->GetWorld()); - - // Can't do this if brush has no polys. - if( !Actor->Brush->Polys->Element.Num() ) - return NULL; - - // Spawn a new actor for the brush. - - ABrush* Result = Actor->GetWorld()->SpawnBrush(); - Result->SetNotForClientOrServer(); - - // Duplicate the brush. - csgCopyBrush - ( - Result, - Actor, - PolyFlags, - RF_Transactional, - 0, - true, - false, - BspPoints, - BspVectors - ); - check(Result->Brush); - - if( Result->GetBrushBuilder() ) - { - FActorLabelUtilities::SetActorLabelUnique(Result, FText::Format(NSLOCTEXT("BSPBrushOps", "BrushName", "{0} Brush"), FText::FromString(Result->GetBrushBuilder()->GetClass()->GetDescription())).ToString()); - } - // Assign the default material to the brush's polys. - for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) - { - FPoly& CurrentPoly = Result->Brush->Polys->Element[i]; - if ( !CurrentPoly.Material ) - { - CurrentPoly.Material = UMaterial::GetDefaultMaterial(MD_Surface); - } - } - - // Set add-info. - Result->BrushType = BrushType; - - Result->ReregisterAllComponents(); - - return Result; -} - -/** Add a new point to the model (preventing duplicates) and return its index. */ -static int32 AddThing( TArray& Vectors, const FVector& V, float Thresh, int32 Check ) -{ - if( Check ) - { - // See if this is very close to an existing point/vector. - for( int32 i=0; i -Thresh) && (Temp < Thresh) ) - { - Temp=(V.Y - TableVect.Y); - if( (Temp > -Thresh) && (Temp < Thresh) ) - { - Temp=(V.Z - TableVect.Z); - if( (Temp > -Thresh) && (Temp < Thresh) ) - { - // Found nearly-matching vector. - return i; - } - } - } - } - } - return Vectors.Add( V ); -} - -/** Add a new vector to the model, merging near-duplicates, and return its index. */ -int32 FHBSPOps::bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ) -{ - const float Thresh = Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR; - - if (BspVectors) - { - // If a points grid has been built for quick vector lookup, use that instead of doing a linear search - const int32 NextIndex = Model->Vectors.Num(); - const int32 ReturnedIndex = BspVectors->FindOrAddPoint(*V, NextIndex, Thresh); - if (ReturnedIndex == NextIndex) - { - Model->Vectors.Add(*V); - } - - return ReturnedIndex; - } - - return AddThing - ( - Model->Vectors, - *V, - Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR, - 1 - ); -} - -/** Add a new point to the model, merging near-duplicates, and return its index. */ -int32 FHBSPOps::bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ) -{ - const float Thresh = Exact ? THRESH_POINTS_ARE_SAME : THRESH_POINTS_ARE_NEAR; - - if (BspPoints) - { - // If a points grid has been built for quick point lookup, use that instead of doing a linear search - const int32 NextIndex = Model->Points.Num(); - // Always look for points with a low threshold; a generous threshold can result in 'leaks' in the BSP and unwanted polys being generated - const int32 ReturnedIndex = BspPoints->FindOrAddPoint(*V, NextIndex, THRESH_POINTS_ARE_SAME); - if (ReturnedIndex == NextIndex) - { - Model->Points.Add(*V); - } - - return ReturnedIndex; - } - - // Try to find a match quickly from the Bsp. This finds all potential matches - // except for any dissociated from nodes/surfaces during a rebuild. - FVector Temp; - int32 pVertex; - float NearestDist = Model->FindNearestVertex(*V,Temp,Thresh,pVertex); - if( (NearestDist >= 0.0) && (NearestDist <= Thresh) ) - { - // Found an existing point. - return pVertex; - } - else - { - // No match found; add it slowly to find duplicates. - return AddThing(Model->Points, *V, Thresh, !GFastRebuild); - } -} - - -/** - * Builds Bsp from the editor polygon set (EdPolys) of a model. - * - * Opt = Bsp optimization, BSP_Lame (fast), BSP_Good (medium), BSP_Optimal (slow) - * Balance = 0-100, 0=only worry about minimizing splits, 100=only balance tree. - */ -void FHBSPOps::bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - int32 OriginalPolys = Model->Polys->Element.Num(); - - // Empty the model's tables. - if( RebuildSimplePolys==1 ) - { - // Empty everything but polys. - Model->EmptyModel( 1, 0 ); - } - else if( RebuildSimplePolys==0 ) - { - // Empty node vertices. - for( int32 i=0; iNodes.Num(); i++ ) - Model->Nodes[i].NumVertices = 0; - - // Refresh the Bsp. - bspRefresh(Model,1); - - // Empty nodes. - Model->EmptyModel( 0, 0 ); - } - if( Model->Polys->Element.Num() ) - { - // Allocate polygon pool. - FMemMark Mark(FMemStack::Get()); - FPoly** PolyList = new( FMemStack::Get(), Model->Polys->Element.Num() )FPoly*; - - // Add all FPolys to active list. - for( int32 i=0; iPolys->Element.Num(); i++ ) - if( Model->Polys->Element[i].Vertices.Num() ) - PolyList[i] = &Model->Polys->Element[i]; - - // Now split the entire Bsp by splitting the list of all polygons. - SplitPolyList - ( - Model, - INDEX_NONE, - NODE_Root, - Model->Polys->Element.Num(), - PolyList, - Opt, - Balance, - PortalBias, - RebuildSimplePolys, - BspPoints, - BspVectors - ); - - // Now build the bounding boxes for all nodes. - if( RebuildSimplePolys==0 ) - { - // Remove unreferenced things. - bspRefresh( Model, 1 ); - - // Rebuild all bounding boxes. - bspBuildBounds( Model ); - } - - Mark.Pop(); - } - -// UE_LOG(LogBSPOps, Log, TEXT("bspBuild built %i convex polys into %i nodes"), OriginalPolys, Model->Nodes.Num() ); -} - -/** - * If the Bsp's point and vector tables are nearly full, reorder them and delete unused ones. - */ -void FHBSPOps::bspRefresh( UModel* Model, bool NoRemapSurfs ) -{ - FMemStack& MemStack = FMemStack::Get(); - - FMemMark Mark(MemStack); - - int32 NumNodes = Model->Nodes.Num(); - int32 NumSurfs = Model->Surfs.Num(); - int32 NumVectors = Model->Vectors.Num(); - int32 NumPoints = Model->Points.Num(); - - // Remove unreferenced Bsp surfs. - int32* PolyRef; - if( NoRemapSurfs ) - { - PolyRef = NewZeroed(MemStack, NumSurfs); - } - else - { - PolyRef = NewOned(MemStack, NumSurfs); - } - - int32* NodeRef = NewOned(MemStack, NumNodes); - if( NumNodes > 0 ) - { - TagReferencedNodes( Model, NodeRef, PolyRef, 0 ); - } - - // Remap Bsp surfs. - { - int32 n=0; - for( int32 i=0; iSurfs[n] = Model->Surfs[i]; - PolyRef[i]=n++; - } - } - //UE_LOG(LogBSPOps, Log, TEXT("Polys: %i -> %i"), NumSurfs, n ); - Model->Surfs.RemoveAt( n, NumSurfs-n ); - NumSurfs = n; - } - - // Remap Bsp nodes. - { - int32 n=0; - for( int32 i=0; iNodes[n] = Model->Nodes[i]; - NodeRef[i]=n++; - } - } - //UE_LOG(LogBSPOps, Log, TEXT("Nodes: %i -> %i"), NumNodes, n ); - Model->Nodes.RemoveAt( n, NumNodes-n ); - NumNodes = n; - } - - // Update Bsp nodes. - for( int32 i=0; iNodes[i]; - Node->iSurf = PolyRef[Node->iSurf]; - if (Node->iFront != INDEX_NONE) Node->iFront = NodeRef[Node->iFront]; - if (Node->iBack != INDEX_NONE) Node->iBack = NodeRef[Node->iBack]; - if (Node->iPlane != INDEX_NONE) Node->iPlane = NodeRef[Node->iPlane]; - } - - // Remove unreferenced points and vectors. - int32* VectorRef = NewOned(MemStack, NumVectors); - int32* PointRef = NewOned(MemStack, NumPoints); - - // Check Bsp surfs. - TArray VertexRef; - for( int32 i=0; iSurfs[i]; - VectorRef [Surf->vNormal ] = 0; - VectorRef [Surf->vTextureU ] = 0; - VectorRef [Surf->vTextureV ] = 0; - PointRef [Surf->pBase ] = 0; - } - - // Check Bsp nodes. - for( int32 i=0; iNodes[i]; - FVert* VertPool = &Model->Verts[Node->iVertPool]; - for( int B=0; BNumVertices; B++ ) - { - PointRef[VertPool->pVertex] = 0; - VertPool++; - } - } - - // Remap points. - { - int32 n=0; - for( int32 i=0; iPoints[n] = Model->Points[i]; - PointRef[i] = n++; - } - //UE_LOG(LogBSPOps, Log, TEXT("Points: %i -> %i"), NumPoints, n ); - Model->Points.RemoveAt( n, NumPoints-n ); - NumPoints = n; - } - - // Remap vectors. - { - int32 n=0; - for (int32 i=0; iVectors[n] = Model->Vectors[i]; - VectorRef[i] = n++; - } - //UE_LOG(LogBSPOps, Log, TEXT("Vectors: %i -> %i"), NumVectors, n ); - Model->Vectors.RemoveAt( n, NumVectors-n ); - NumVectors = n; - } - - // Update Bsp surfs. - for( int32 i=0; iSurfs[i]; - Surf->vNormal = VectorRef [Surf->vNormal ]; - Surf->vTextureU = VectorRef [Surf->vTextureU]; - Surf->vTextureV = VectorRef [Surf->vTextureV]; - Surf->pBase = PointRef [Surf->pBase ]; - } - - // Update Bsp nodes. - for( int32 i=0; iNodes[i]; - FVert* VertPool = &Model->Verts[Node->iVertPool]; - for( int B=0; BNumVertices; B++ ) - { - VertPool->pVertex = PointRef [VertPool->pVertex]; - VertPool++; - } - } - - // Shrink the objects. - Model->ShrinkModel(); - - Mark.Pop(); -} - -// Build bounding volumes for all Bsp nodes. The bounding volume of the node -// completely encloses the "outside" space occupied by the nodes. Note that -// this is not the same as representing the bounding volume of all of the -// polygons within the node. -// -// We start with a practically-infinite cube and filter it down the Bsp, -// whittling it away until all of its convex volume fragments land in leaves. -void FHBSPOps::bspBuildBounds( UModel* Model ) -{ - if( Model->Nodes.Num()==0 ) - return; - - FPoly Polys[6], *PolyList[6]; - for( int32 i=0; i<6; i++ ) - { - PolyList[i] = &Polys[i]; - PolyList[i]->Init(); - PolyList[i]->iBrushPoly = INDEX_NONE; - } - - new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); - new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); - Polys[0].Normal =FVector( 0.000000, 0.000000, 1.000000 ); - Polys[0].Base =Polys[0].Vertices[0]; - - new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[1].Normal =FVector( 0.000000, 0.000000, -1.000000 ); - Polys[1].Base =Polys[1].Vertices[0]; - - new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[2].Normal =FVector( 0.000000, 1.000000, 0.000000 ); - Polys[2].Base =Polys[2].Vertices[0]; - - new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[3].Normal =FVector( 0.000000, -1.000000, 0.000000 ); - Polys[3].Base =Polys[3].Vertices[0]; - - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[4].Normal =FVector( 1.000000, 0.000000, 0.000000 ); - Polys[4].Base =Polys[4].Vertices[0]; - - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); - new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); - Polys[5].Normal =FVector(-1.000000, 0.000000, 0.000000 ); - Polys[5].Base =Polys[5].Vertices[0]; - // Empty hulls. - Model->LeafHulls.Empty(); - for( int32 i=0; iNodes.Num(); i++ ) - Model->Nodes[i].iCollisionBound = INDEX_NONE; - FilterBound( Model, NULL, 0, PolyList, 6, Model->RootOutside ); -// UE_LOG(LogBSPOps, Log, TEXT("bspBuildBounds: Generated %i hulls"), Model->LeafHulls.Num() ); -} - -/** - * Validate a brush, and set iLinks on all EdPolys to index of the - * first identical EdPoly in the list, or its index if it's the first. - * Not transactional. - */ -void FHBSPOps::bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ) -{ - check(Brush != nullptr); - Brush->Modify(); - if( ForceValidate || !Brush->Linked ) - { - Brush->Linked = 1; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - Brush->Polys->Element[i].iLink = i; - } - int32 n=0; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - FPoly* EdPoly = &Brush->Polys->Element[i]; - if( EdPoly->iLink==i ) - { - for( int32 j=i+1; jPolys->Element.Num(); j++ ) - { - FPoly* OtherPoly = &Brush->Polys->Element[j]; - if - ( OtherPoly->iLink == j - && OtherPoly->Material == EdPoly->Material - && OtherPoly->TextureU == EdPoly->TextureU - && OtherPoly->TextureV == EdPoly->TextureV - && OtherPoly->PolyFlags == EdPoly->PolyFlags - && (OtherPoly->Normal | EdPoly->Normal)>0.9999 ) - { - float Dist = FVector::PointPlaneDist( OtherPoly->Vertices[0], EdPoly->Vertices[0], EdPoly->Normal ); - if( Dist>-0.001 && Dist<0.001 ) - { - OtherPoly->iLink = i; - n++; - } - } - } - } - } -// UE_LOG(LogBSPOps, Log, TEXT("BspValidateBrush linked %i of %i polys"), n, Brush->Polys->Element.Num() ); - } - - // Build bounds. - Brush->BuildBound(); -} - -void FHBSPOps::bspUnlinkPolys( UModel* Brush ) -{ - Brush->Modify(); - Brush->Linked = 1; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - Brush->Polys->Element[i].iLink = i; - } -} - -// Add an editor polygon to the Bsp, and also stick a reference to it -// in the editor polygon's BspNodes list. If the editor polygon has more sides -// than the Bsp will allow, split it up into several sub-polygons. -// -// Returns: Index to newly-created node of Bsp. If several nodes were created because -// of split polys, returns the parent (highest one up in the Bsp). -int32 FHBSPOps::bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - if( NodePlace == NODE_Plane ) - { - // Make sure coplanars are added at the end of the coplanar list so that - // we don't insert NF_IsNew nodes with non NF_IsNew coplanar children. - while( Model->Nodes[iParent].iPlane != INDEX_NONE ) - { - iParent = Model->Nodes[iParent].iPlane; - } - } - FBspSurf* Surf = NULL; - if( EdPoly->iLinkSurf == Model->Surfs.Num() ) - { - int32 NewIndex = Model->Surfs.AddZeroed(); - Surf = &Model->Surfs[NewIndex]; - - // This node has a new polygon being added by bspBrushCSG; must set its properties here. - FVector Base = EdPoly->Base, Normal = EdPoly->Normal, TextureU = EdPoly->TextureU, TextureV = EdPoly->TextureV; - Surf->pBase = bspAddPoint (Model,&Base,1,BspPoints); - Surf->vNormal = bspAddVector (Model,&Normal,1,BspVectors); - Surf->vTextureU = bspAddVector (Model,&TextureU,0,BspVectors); - Surf->vTextureV = bspAddVector (Model,&TextureV,0,BspVectors); - Surf->Material = EdPoly->Material; - Surf->Actor = NULL; - - Surf->PolyFlags = EdPoly->PolyFlags & ~PF_NoAddToBSP; - Surf->LightMapScale= EdPoly->LightMapScale; - - // Find the LightmassPrimitiveSettings in the UModel... - int32 FoundLightmassIndex = INDEX_NONE; - if (Model->LightmassSettings.Find(EdPoly->LightmassSettings, FoundLightmassIndex) == false) - { - FoundLightmassIndex = Model->LightmassSettings.Add(EdPoly->LightmassSettings); - } - Surf->iLightmassIndex = FoundLightmassIndex; - - Surf->Actor = EdPoly->Actor; - Surf->iBrushPoly = EdPoly->iBrushPoly; - - if (EdPoly->Actor) - { - Surf->bHiddenEdTemporary = EdPoly->Actor->IsTemporarilyHiddenInEditor(); - Surf->bHiddenEdLevel = EdPoly->Actor->bHiddenEdLevel; - Surf->bHiddenEdLayer = EdPoly->Actor->bHiddenEdLayer; - } - - Surf->Plane = FPlane(EdPoly->Vertices[0],EdPoly->Normal); - } - else - { - check(EdPoly->iLinkSurf!=INDEX_NONE); - check(EdPoly->iLinkSurfSurfs.Num()); - Surf = &Model->Surfs[EdPoly->iLinkSurf]; - } - - // Set NodeFlags. - if( Surf->PolyFlags & PF_NotSolid ) NodeFlags |= NF_NotCsg; - if( Surf->PolyFlags & (PF_Invisible|PF_Portal) ) NodeFlags |= NF_NotVisBlocking; - - if( EdPoly->Vertices.Num() > FBspNode::MAX_NODE_VERTICES ) - { - // Split up into two coplanar sub-polygons (one with MAX_NODE_VERTICES vertices and - // one with all the remaining vertices) and recursively add them. - - // EdPoly1 is just the first MAX_NODE_VERTICES from EdPoly. - FMemMark Mark(FMemStack::Get()); - FPoly *EdPoly1 = new FPoly; - *EdPoly1 = *EdPoly; - EdPoly1->Vertices.RemoveAt(FBspNode::MAX_NODE_VERTICES,EdPoly->Vertices.Num() - FBspNode::MAX_NODE_VERTICES); - - // EdPoly2 is the first vertex from EdPoly, and the last EdPoly->Vertices.Num() - MAX_NODE_VERTICES + 1. - FPoly *EdPoly2 = new FPoly; - *EdPoly2 = *EdPoly; - EdPoly2->Vertices.RemoveAt(1,FBspNode::MAX_NODE_VERTICES - 2); - - int32 iNode = bspAddNode( Model, iParent, NodePlace, NodeFlags, EdPoly1, BspPoints, BspVectors ); // Add this poly first. - bspAddNode( Model, iNode, NODE_Plane, NodeFlags, EdPoly2, BspPoints, BspVectors ); // Then add other (may be bigger). - - delete EdPoly1; - delete EdPoly2; - - Mark.Pop(); - return iNode; // Return coplanar "parent" node (not coplanar child) - } - else - { - // Add node. - int32 iNode = Model->Nodes.AddZeroed(); - FBspNode& Node = Model->Nodes[iNode]; - - // Tell transaction tracking system that parent is about to be modified. - FBspNode* Parent=NULL; - if( NodePlace!=NODE_Root ) - Parent = &Model->Nodes[iParent]; - - // Set node properties. - Node.iSurf = EdPoly->iLinkSurf; - Node.NodeFlags = NodeFlags; - Node.iCollisionBound = INDEX_NONE; - Node.Plane = FPlane( EdPoly->Vertices[0], EdPoly->Normal ); - Node.iVertPool = Model->Verts.AddUninitialized(EdPoly->Vertices.Num()); - Node.iFront = INDEX_NONE; - Node.iBack = INDEX_NONE; - Node.iPlane = INDEX_NONE; - if( NodePlace==NODE_Root ) - { - Node.iLeaf[0] = INDEX_NONE; - Node.iLeaf[1] = INDEX_NONE; - Node.iZone[0] = 0; - Node.iZone[1] = 0; - } - else if( NodePlace==NODE_Front || NodePlace==NODE_Back ) - { - int32 ZoneFront=NodePlace==NODE_Front; - Node.iLeaf[0] = Parent->iLeaf[ZoneFront]; - Node.iLeaf[1] = Parent->iLeaf[ZoneFront]; - Node.iZone[0] = Parent->iZone[ZoneFront]; - Node.iZone[1] = Parent->iZone[ZoneFront]; - } - else - { - int32 IsFlipped = (Node.Plane|Parent->Plane)<0.0; - Node.iLeaf[0] = Parent->iLeaf[IsFlipped ]; - Node.iLeaf[1] = Parent->iLeaf[1-IsFlipped]; - Node.iZone[0] = Parent->iZone[IsFlipped ]; - Node.iZone[1] = Parent->iZone[1-IsFlipped]; - } - - // Link parent to this node. - if (NodePlace == NODE_Front) - { - Parent->iFront = iNode; - } - else if (NodePlace == NODE_Back) - { - Parent->iBack = iNode; - } - else if (NodePlace == NODE_Plane) - { - Parent->iPlane = iNode; - } - - // Add all points to point table, merging nearly-overlapping polygon points - // with other points in the poly to prevent criscrossing vertices from - // being generated. - - // Must maintain Node->NumVertices on the fly so that bspAddPoint is always - // called with the Bsp in a clean state. - Node.NumVertices = 0; - FVert* VertPool = &Model->Verts[ Node.iVertPool ]; - for( uint8 i=0; iVertices.Num(); i++ ) - { - FVector Vertex = EdPoly->Vertices[i]; - int32 pVertex = bspAddPoint(Model,&Vertex,0, BspPoints); - if( Node.NumVertices==0 || VertPool[Node.NumVertices-1].pVertex!=pVertex ) - { - VertPool[Node.NumVertices].iSide = INDEX_NONE; - VertPool[Node.NumVertices].pVertex = pVertex; - Node.NumVertices++; - } - } - if( Node.NumVertices>=2 && VertPool[0].pVertex==VertPool[Node.NumVertices-1].pVertex ) - { - Node.NumVertices--; - } - if( Node.NumVertices < 3 ) - { - GErrors++; -// UE_LOG(LogBSPOps, Warning, TEXT("bspAddNode: Infinitesimal polygon %i (%i)"), Node.NumVertices, EdPoly->Vertices.Num() ); - Node.NumVertices = 0; - } - - return iNode; - } -} - -/** - * Rebuild some brush internals - */ -void FHBSPOps::RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - Brush->Modify(); - Brush->EmptyModel(1, 0); - - // Build bounding box. - Brush->BuildBound(); - - // Build BSP for the brush. - bspBuild(Brush, BSP_Good, 15, 70, 1, 0, BspPoints, BspVectors); - bspRefresh(Brush, 1); - bspBuildBounds(Brush); -} - -/** - * Rotates the specified brush's vertices. - */ -void FHBSPOps::RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - if(Brush->GetBrushComponent()->Brush && Brush->GetBrushComponent()->Brush->Polys) - { - for( int32 poly = 0 ; poly < Brush->GetBrushComponent()->Brush->Polys->Element.Num() ; poly++ ) - { - FPoly* Poly = &(Brush->GetBrushComponent()->Brush->Polys->Element[poly]); - - // Rotate the vertices. - const FRotationMatrix RotMatrix( Rotation ); - for( int32 vertex = 0 ; vertex < Poly->Vertices.Num() ; vertex++ ) - { - Poly->Vertices[vertex] = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Vertices[vertex] - Brush->GetPivotOffset()); - } - Poly->Base = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Base - Brush->GetPivotOffset()); - - // Rotate the texture vectors. - Poly->TextureU = RotMatrix.TransformVector( Poly->TextureU ); - Poly->TextureV = RotMatrix.TransformVector( Poly->TextureV ); - - // Recalc the normal for the poly. - Poly->Normal = FVector::ZeroVector; - Poly->Finalize(Brush,0); - } - - Brush->GetBrushComponent()->Brush->BuildBound(); - - if( !Brush->IsStaticBrush() ) - { - csgPrepMovingBrush( Brush, BspPoints, BspVectors ); - } - - if ( bClearComponents ) - { - Brush->ReregisterAllComponents(); - } - } -} - - -void FHBSPOps::HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) -{ - // The default physics volume doesn't have an associated UModel, so we need to handle that case gracefully. - if(Volume.Brush) - { - FHBSPOps::csgPrepMovingBrush( &Volume, BspPoints, BspVectors); - } -} - -UHBspPointsGrid* UHBspPointsGrid::Create(float InGranularity, float InThreshold, int32 InitialSize) -{ - check(InThreshold / InGranularity <= 0.5f); - - UHBspPointsGrid* Obj = NewObject(GetTransientPackage(), UHBspPointsGrid::StaticClass()); - Obj->OneOverGranularity = 1.0f / InGranularity; - Obj->Threshold = InThreshold; - Obj->Clear(InitialSize); - - return Obj; -} - -void UHBspPointsGrid::Clear(int32 InitialSize) -{ - GridMap.Empty(InitialSize); -} - - -// Given a grid index in one axis, a real position on the grid and a threshold radius, -// return either: -// - the additional grid index it can overlap in that axis, or -// - the original grid index if there is no overlap. -int32 UHBspPointsGrid::GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold) -{ - if (GridPos - GridIndex < GridThreshold) - { - return GridIndex - 1; - } - else if (1.0f - (GridPos - GridIndex) < GridThreshold) - { - return GridIndex + 1; - } - else - { - return GridIndex; - } -} - -int32 UHBspPointsGrid::FindOrAddPoint(const FVector& Point, int32 Index, float PointThreshold) -{ - // Offset applied to the grid coordinates so aligned vertices (the normal case) don't overlap several grid items (taking into account the threshold) - const float GridOffset = 0.12345f; - - const float AdjustedPointX = Point.X - GridOffset; - const float AdjustedPointY = Point.Y - GridOffset; - const float AdjustedPointZ = Point.Z - GridOffset; - - const float GridX = AdjustedPointX * OneOverGranularity; - const float GridY = AdjustedPointY * OneOverGranularity; - const float GridZ = AdjustedPointZ * OneOverGranularity; - - // Get the grid indices corresponding to the point coordinates - const int32 GridIndexX = FMath::FloorToInt(GridX); - const int32 GridIndexY = FMath::FloorToInt(GridY); - const int32 GridIndexZ = FMath::FloorToInt(GridZ); - - // Find grid item in map - FHBspPointsGridItem& GridItem = GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, GridIndexZ)); - - // Iterate through grid item points and return a point if it's close to the threshold - const float PointThresholdSquared = PointThreshold * PointThreshold; - for (const FHBspIndexedPoint& IndexedPoint : GridItem.IndexedPoints) - { - if (FVector::DistSquared(IndexedPoint.Point, Point) <= PointThresholdSquared) - { - return IndexedPoint.Index; - } - } - - // Otherwise, the point is new: add it to the grid item. - GridItem.IndexedPoints.Emplace(Point, Index); - - // The grid has a maximum threshold of a certain radius. If the point is near the edge of a grid cube, it may overlap into other items. - // Add it to all grid items it can be seen from. - const float GridThreshold = Threshold * OneOverGranularity; - const int32 NeighbourX = GetAdjacentIndexIfOverlapping(GridIndexX, GridX, GridThreshold); - const int32 NeighbourY = GetAdjacentIndexIfOverlapping(GridIndexY, GridY, GridThreshold); - const int32 NeighbourZ = GetAdjacentIndexIfOverlapping(GridIndexZ, GridZ, GridThreshold); - - const bool bOverlapsInX = (NeighbourX != GridIndexX); - const bool bOverlapsInY = (NeighbourY != GridIndexY); - const bool bOverlapsInZ = (NeighbourZ != GridIndexZ); - - if (bOverlapsInX) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInY) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else - { - if (bOverlapsInY) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); - - if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - else if (bOverlapsInZ) - { - GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); - } - } - - return Index; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HBSPOps.h" +#include "EngineDefines.h" +#include "Model.h" +#include "Materials/Material.h" +#include "Engine/BrushBuilder.h" +#include "Editor/EditorEngine.h" +#include "Components/BrushComponent.h" +#include "GameFramework/Volume.h" + +DEFINE_LOG_CATEGORY_STATIC(LogBSPOps, Log, All); + +/** Errors encountered in Csg operation. */ +int32 FHBSPOps::GErrors = 0; +bool FHBSPOps::GFastRebuild = false; + +static void TagReferencedNodes( UModel *Model, int32 *NodeRef, int32 *PolyRef, int32 iNode ) +{ + FBspNode &Node = Model->Nodes[iNode]; + + NodeRef[iNode ] = 0; + PolyRef[Node.iSurf] = 0; + + if( Node.iFront != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iFront); + if( Node.iBack != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iBack ); + if( Node.iPlane != INDEX_NONE ) TagReferencedNodes(Model,NodeRef,PolyRef,Node.iPlane); +} + +// +// Update a bounding volume by expanding it to enclose a list of polys. +// +static void UpdateBoundWithPolys( FBox& Bound, FPoly** PolyList, int32 nPolys ) +{ + for( int32 i=0; iVertices.Num(); j++ ) + Bound += PolyList[i]->Vertices[j]; +} + +// +// Update a convolution hull with a list of polys. +// +static void UpdateConvolutionWithPolys( UModel *Model, int32 iNode, FPoly **PolyList, int32 nPolys ) +{ + FBox Box(ForceInit); + + FBspNode &Node = Model->Nodes[iNode]; + Node.iCollisionBound = Model->LeafHulls.Num(); + for( int32 i=0; iiBrushPoly != INDEX_NONE ) + { + int32 j; + for( j=0; jiBrushPoly == PolyList[i]->iBrushPoly ) + break; + if( j >= i ) + Model->LeafHulls.Add(PolyList[i]->iBrushPoly); + } + for( int32 j=0; jVertices.Num(); j++ ) + Box += PolyList[i]->Vertices[j]; + } + Model->LeafHulls.Add(INDEX_NONE); + + // Add bounds. + Model->LeafHulls.Add( *(int32*)&Box.Min.X ); + Model->LeafHulls.Add( *(int32*)&Box.Min.Y ); + Model->LeafHulls.Add( *(int32*)&Box.Min.Z ); + Model->LeafHulls.Add( *(int32*)&Box.Max.X ); + Model->LeafHulls.Add( *(int32*)&Box.Max.Y ); + Model->LeafHulls.Add( *(int32*)&Box.Max.Z ); + +} + +// +// Cut a partitioning poly by a list of polys, and add the resulting inside pieces to the +// front list and back list. +// +static void SplitPartitioner +( + UModel* Model, + FPoly** PolyList, + FPoly** FrontList, + FPoly** BackList, + int32 n, + int32 nPolys, + int32& nFront, + int32& nBack, + FPoly InfiniteEdPoly, + TArray& AllocatedFPolys +) +{ + FPoly FrontPoly,BackPoly; + while( n < nPolys ) + { + FPoly* Poly = PolyList[n]; + switch( InfiniteEdPoly.SplitWithPlane(Poly->Vertices[0],Poly->Normal,&FrontPoly,&BackPoly,0) ) + { + case SP_Coplanar: + // May occasionally happen. +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got inficoplanar") ); + break; + + case SP_Front: + // Shouldn't happen if hull is correct. +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got infifront") ); + return; + + case SP_Split: + InfiniteEdPoly = BackPoly; + break; + + case SP_Back: + break; + } + n++; + } + + FPoly* New = new FPoly; + *New = InfiniteEdPoly; + New->Reverse(); + New->iBrushPoly |= 0x40000000; + FrontList[nFront++] = New; + AllocatedFPolys.Add( New ); + + New = new FPoly; + *New = InfiniteEdPoly; + BackList[nBack++] = New; + AllocatedFPolys.Add( New ); +} + +// +// Build an FPoly representing an "infinite" plane (which exceeds the maximum +// dimensions of the world in all directions) for a particular Bsp node. +// +FPoly FHBSPOps::BuildInfiniteFPoly( UModel* Model, int32 iNode ) +{ + FBspNode &Node = Model->Nodes [iNode ]; + FBspSurf &Poly = Model->Surfs [Node.iSurf ]; + FVector Base = Poly.Plane * Poly.Plane.W; + FVector Normal = Poly.Plane; + FVector Axis1,Axis2; + + // Find two non-problematic axis vectors. + Normal.FindBestAxisVectors( Axis1, Axis2 ); + + // Set up the FPoly. + FPoly EdPoly; + EdPoly.Init(); + EdPoly.Normal = Normal; + EdPoly.Base = Base; + new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX + Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX + Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base - Axis1*WORLD_MAX - Axis2*WORLD_MAX); + new(EdPoly.Vertices) FVector(Base + Axis1*WORLD_MAX - Axis2*WORLD_MAX); + + return EdPoly; +} + +// +// Recursively filter a set of polys defining a convex hull down the Bsp, +// splitting it into two halves at each node and adding in the appropriate +// face polys at splits. +// +static void FilterBound +( + UModel* Model, + FBox* ParentBound, + int32 iNode, + FPoly** PolyList, + int32 nPolys, + int32 Outside +) +{ + FMemMark Mark(FMemStack::Get()); + FBspNode& Node = Model->Nodes [iNode]; + FBspSurf& Surf = Model->Surfs [Node.iSurf]; + FVector Base = Surf.Plane * Surf.Plane.W; + FVector& Normal = Model->Vectors[Surf.vNormal]; + FBox Bound(ForceInit); + + Bound.Min.X = Bound.Min.Y = Bound.Min.Z = +WORLD_MAX; + Bound.Max.X = Bound.Max.Y = Bound.Max.Z = -WORLD_MAX; + + // Split bound into front half and back half. + FPoly** FrontList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nFront=0; + FPoly** BackList = new(FMemStack::Get(),nPolys*2+16)FPoly*; int32 nBack=0; + + // Keeping track of allocated FPoly structures to delete later on. + TArray AllocatedFPolys; + + FPoly* FrontPoly = new FPoly; + FPoly* BackPoly = new FPoly; + + // Keep track of allocations. + AllocatedFPolys.Add( FrontPoly ); + AllocatedFPolys.Add( BackPoly ); + + for( int32 i=0; iSplitWithPlane( Base, Normal, FrontPoly, BackPoly, 0 ) ) + { + case SP_Coplanar: +// UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Got coplanar") ); + FrontList[nFront++] = Poly; + BackList[nBack++] = Poly; + break; + + case SP_Front: + FrontList[nFront++] = Poly; + break; + + case SP_Back: + BackList[nBack++] = Poly; + break; + + case SP_Split: + FrontList[nFront++] = FrontPoly; + BackList [nBack++] = BackPoly; + + FrontPoly = new FPoly; + BackPoly = new FPoly; + + // Keep track of allocations. + AllocatedFPolys.Add( FrontPoly ); + AllocatedFPolys.Add( BackPoly ); + + break; + + default: + UE_LOG(LogBSPOps, Fatal, TEXT("FZoneFilter::FilterToLeaf: Unknown split code") ); + } + } + if( nFront && nBack ) + { + // Add partitioner plane to front and back. + FPoly InfiniteEdPoly = FHBSPOps::BuildInfiniteFPoly( Model, iNode ); + InfiniteEdPoly.iBrushPoly = iNode; + + SplitPartitioner(Model,PolyList,FrontList,BackList,0,nPolys,nFront,nBack,InfiniteEdPoly,AllocatedFPolys); + } + else + { +// if( !nFront ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty fronthull") ); +// if( !nBack ) UE_LOG(LogBSPOps, Log, TEXT("FilterBound: Empty backhull") ); + } + + // Recursively update all our childrens' bounding volumes. + if( nFront > 0 ) + { + if( Node.iFront != INDEX_NONE ) + FilterBound( Model, &Bound, Node.iFront, FrontList, nFront, Outside || Node.IsCsg() ); + else if( Outside || Node.IsCsg() ) + UpdateBoundWithPolys( Bound, FrontList, nFront ); + else + UpdateConvolutionWithPolys( Model, iNode, FrontList, nFront ); + } + if( nBack > 0 ) + { + if( Node.iBack != INDEX_NONE) + FilterBound( Model, &Bound,Node.iBack, BackList, nBack, Outside && !Node.IsCsg() ); + else if( Outside && !Node.IsCsg() ) + UpdateBoundWithPolys( Bound, BackList, nBack ); + else + UpdateConvolutionWithPolys( Model, iNode, BackList, nBack ); + } + + // Update parent bound to enclose this bound. + if( ParentBound ) + *ParentBound += Bound; + + // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. + for( int32 i=0; i0); + + // No need to test if only one poly. + if( NumPolys==1 ) + return PolyList[0]; + + FPoly *Poly, *Best=NULL; + float Score, BestScore; + int32 i, Index, j, Inc; + int32 Splits, Front, Back, Coplanar, AllSemiSolids; + + //PortalBias -- added by Legend on 4/12/2000 + float PortalBias = InPortalBias / 100.0f; + Balance &= 0xFF; // keep only the low byte to recover "Balance" + //UE_LOG(LogBSPOps, Log, TEXT("Balance=%d PortalBias=%f"), Balance, PortalBias ); + + if (Opt==FHBSPOps::BSP_Optimal) Inc = 1; // Test lots of nodes. + else if (Opt==FHBSPOps::BSP_Good) Inc = FMath::Max(1,NumPolys/20); // Test 20 nodes. + else /* BSP_Lame */ Inc = FMath::Max(1,NumPolys/4); // Test 4 nodes. + + // See if there are any non-semisolid polygons here. + for( i=0; iPolyFlags & PF_AddLast) ) + break; + AllSemiSolids = (i>=NumPolys); + + // Search through all polygons in the pool and find: + // A. The number of splits each poly would make. + // B. The number of front and back nodes the polygon would create. + // C. Number of coplanars. + BestScore = 0; + for( i=0; iPolyFlags & PF_AddLast) && !(Poly->PolyFlags & PF_Portal) ) + && !AllSemiSolids ); + if( Index>=i+Inc || Index>=NumPolys ) + continue; + + for( j=0; jSplitWithPlaneFast( FPlane( Poly->Vertices[0], Poly->Normal), NULL, NULL ) ) + { + case SP_Coplanar: + Coplanar++; + break; + + case SP_Front: + Front++; + break; + + case SP_Back: + Back++; + break; + + case SP_Split: + // Disfavor splitting polys that are zone portals. + if( !(OtherPoly->PolyFlags & PF_Portal) ) + Splits++; + else + Splits += 16; + break; + } + } + // added by Legend 1/31/1999 + // Score optimization: minimize cuts vs. balance tree (as specified in BSP Rebuilder dialog) + Score = ( 100.0 - float(Balance) ) * Splits + float(Balance) * FMath::Abs( Front - Back ); + if( Poly->PolyFlags & PF_Portal ) + { + // PortalBias -- added by Legend on 4/12/2000 + // + // PortalBias enables level designers to control the effect of Portals on the BSP. + // This effect can range from 0.0 (ignore portals), to 1.0 (portals cut everything). + // + // In builds prior to this (since the 221 build dating back to 1/31/1999) the bias + // has been 1.0 causing the portals to cut the BSP in ways that will potentially + // degrade level performance, and increase the BSP complexity. + // + // By setting the bias to a value between 0.3 and 0.7 the positive effects of + // the portals are preserved without giving them unreasonable priority in the BSP. + // + // Portals should be weighted high enough in the BSP to separate major parts of the + // level from each other (pushing entire rooms down the branches of the BSP), but + // should not be so high that portals cut through adjacent geometry in a way that + // increases complexity of the room being (typically, accidentally) cut. + // + Score -= ( 100.0 - float(Balance) ) * Splits * PortalBias; // ignore PortalBias of the split polys -- bias toward portal selection for cutting planes! + } + //UE_LOG(LogBSPOps, Log, " %4d: Score = %f (Front = %4d, Back = %4d, Splits = %4d, Flags = %08X)", Index, Score, Front, Back, Splits, Poly->PolyFlags ); //LEC + + if( Score AllocatedFPolys; + + // To account for big EdPolys split up. + int32 NumPolysToAlloc = NumPolys + 8 + NumPolys/4; + int32 NumFront=0; FPoly **FrontList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; + int32 NumBack =0; FPoly **BackList = new(FMemStack::Get(),NumPolysToAlloc)FPoly*; + + FPoly *SplitPoly = FindBestSplit( NumPolys, PolyList, Opt, Balance, PortalBias ); + + // Add the splitter poly to the Bsp with either a new BspSurf or an existing one. + if( RebuildSimplePolys ) + { + SplitPoly->iLinkSurf = Model->Surfs.Num(); + } + + int32 iOurNode = bspAddNode(Model,iParent,NodePlace,0,SplitPoly, BspPoints, BspVectors); + int32 iPlaneNode = iOurNode; + + // Now divide all polygons in the pool into (A) polygons that are + // in front of Poly, and (B) polygons that are in back of Poly. + // Coplanar polys are inserted immediately, before recursing. + + // If any polygons are split by Poly, we ignrore the original poly, + // split it into two polys, and add two new polys to the pool. + FPoly *FrontEdPoly = new FPoly; + FPoly *BackEdPoly = new FPoly; + // Keep track of allocations. + AllocatedFPolys.Add( FrontEdPoly ); + AllocatedFPolys.Add( BackEdPoly ); + + for( int32 i=0; iSplitWithPlane( SplitPoly->Vertices[0], SplitPoly->Normal, FrontEdPoly, BackEdPoly, 0 ) ) + { + case SP_Coplanar: + if( RebuildSimplePolys ) + { + EdPoly->iLinkSurf = Model->Surfs.Num()-1; + } + iPlaneNode = bspAddNode( Model, iPlaneNode, NODE_Plane, 0, EdPoly, BspPoints, BspVectors ); + break; + + case SP_Front: + FrontList[NumFront++] = PolyList[i]; + break; + + case SP_Back: + BackList[NumBack++] = PolyList[i]; + break; + + case SP_Split: + + // Create front & back nodes. + FrontList[NumFront++] = FrontEdPoly; + BackList [NumBack ++] = BackEdPoly; + + FrontEdPoly = new FPoly; + BackEdPoly = new FPoly; + // Keep track of allocations. + AllocatedFPolys.Add( FrontEdPoly ); + AllocatedFPolys.Add( BackEdPoly ); + + break; + } + } + + // Recursively split the front and back pools. + if( NumFront > 0 ) SplitPolyList( Model, iOurNode, NODE_Front, NumFront, FrontList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); + if( NumBack > 0 ) SplitPolyList( Model, iOurNode, NODE_Back, NumBack, BackList, Opt, Balance, PortalBias, RebuildSimplePolys, BspPoints, BspVectors ); + + // Delete FPolys allocated above. We cannot use FMemStack::Get() for FPoly as the array data FPoly contains will be allocated in regular memory. + for( int32 i=0; iGetName() ); // moved here so that we can easily debug when an actor has lost parts of its brush + + check(Actor->GetBrushComponent()); + check(Actor->Brush); + check(Actor->Brush->RootOutside); + + RebuildBrush(Actor->Brush, BspPoints, BspVectors); + + // Make sure simplified collision is up to date. + Actor->GetBrushComponent()->BuildSimpleBrushCollision(); + Actor->RebuildNavigationData(); +} + +/** + * Duplicates the specified brush and makes it into a CSG-able level brush. + * @return The new brush, or NULL if the original was empty. + */ +void FHBSPOps::csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + check(Src); + check(Src->GetBrushComponent()); + check(Src->Brush); + + // Handle empty brush. + if( !bAllowEmpty && !Src->Brush->Polys->Element.Num() ) + { + Dest->Brush = NULL; + Dest->GetBrushComponent()->Brush = NULL; + return; + } + + // Duplicate the brush and its polys. + Dest->PolyFlags = PolyFlags; + Dest->Brush = NewObject(Dest, NAME_None, ResFlags); + Dest->Brush->Initialize(nullptr, Src->Brush->RootOutside); + Dest->Brush->Polys = NewObject(Dest->Brush, NAME_None, ResFlags); + Dest->Brush->Polys->Element = Src->Brush->Polys->Element; + Dest->GetBrushComponent()->Brush = Dest->Brush; + if(Src->BrushBuilder != nullptr) + { + Dest->BrushBuilder = DuplicateObject(Src->BrushBuilder, Dest); + } + + // Update poly textures. + for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) + { + Dest->Brush->Polys->Element[i].iBrushPoly = INDEX_NONE; + } + + // Copy positioning, and build bounding box. + if(bCopyPosRotScale) + { + Dest->CopyPosRotScaleFrom( Src ); + } + + // If it's a moving brush, prep it. + if( bNeedsPrep ) + { + csgPrepMovingBrush( Dest, BspPoints, BspVectors ); + } +} + +/** + * Adds a brush to the list of CSG brushes in the level, using a CSG operation. + * + * @return A newly-created copy of the brush. + */ +ABrush* FHBSPOps::csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + check(Actor); + check(Actor->GetBrushComponent()); + check(Actor->Brush); + check(Actor->Brush->Polys); + check(Actor->GetWorld()); + + // Can't do this if brush has no polys. + if( !Actor->Brush->Polys->Element.Num() ) + return NULL; + + // Spawn a new actor for the brush. + + ABrush* Result = Actor->GetWorld()->SpawnBrush(); + Result->SetNotForClientOrServer(); + + // Duplicate the brush. + csgCopyBrush + ( + Result, + Actor, + PolyFlags, + RF_Transactional, + 0, + true, + false, + BspPoints, + BspVectors + ); + check(Result->Brush); + + if( Result->GetBrushBuilder() ) + { + FActorLabelUtilities::SetActorLabelUnique(Result, FText::Format(NSLOCTEXT("BSPBrushOps", "BrushName", "{0} Brush"), FText::FromString(Result->GetBrushBuilder()->GetClass()->GetDescription())).ToString()); + } + // Assign the default material to the brush's polys. + for( int32 i=0; iBrush->Polys->Element.Num(); i++ ) + { + FPoly& CurrentPoly = Result->Brush->Polys->Element[i]; + if ( !CurrentPoly.Material ) + { + CurrentPoly.Material = UMaterial::GetDefaultMaterial(MD_Surface); + } + } + + // Set add-info. + Result->BrushType = BrushType; + + Result->ReregisterAllComponents(); + + return Result; +} + +/** Add a new point to the model (preventing duplicates) and return its index. */ +static int32 AddThing( TArray& Vectors, const FVector& V, float Thresh, int32 Check ) +{ + if( Check ) + { + // See if this is very close to an existing point/vector. + for( int32 i=0; i -Thresh) && (Temp < Thresh) ) + { + Temp=(V.Y - TableVect.Y); + if( (Temp > -Thresh) && (Temp < Thresh) ) + { + Temp=(V.Z - TableVect.Z); + if( (Temp > -Thresh) && (Temp < Thresh) ) + { + // Found nearly-matching vector. + return i; + } + } + } + } + } + return Vectors.Add( V ); +} + +/** Add a new vector to the model, merging near-duplicates, and return its index. */ +int32 FHBSPOps::bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ) +{ + const float Thresh = Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR; + + if (BspVectors) + { + // If a points grid has been built for quick vector lookup, use that instead of doing a linear search + const int32 NextIndex = Model->Vectors.Num(); + const int32 ReturnedIndex = BspVectors->FindOrAddPoint(*V, NextIndex, Thresh); + if (ReturnedIndex == NextIndex) + { + Model->Vectors.Add(*V); + } + + return ReturnedIndex; + } + + return AddThing + ( + Model->Vectors, + *V, + Exact ? THRESH_NORMALS_ARE_SAME : THRESH_VECTORS_ARE_NEAR, + 1 + ); +} + +/** Add a new point to the model, merging near-duplicates, and return its index. */ +int32 FHBSPOps::bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ) +{ + const float Thresh = Exact ? THRESH_POINTS_ARE_SAME : THRESH_POINTS_ARE_NEAR; + + if (BspPoints) + { + // If a points grid has been built for quick point lookup, use that instead of doing a linear search + const int32 NextIndex = Model->Points.Num(); + // Always look for points with a low threshold; a generous threshold can result in 'leaks' in the BSP and unwanted polys being generated + const int32 ReturnedIndex = BspPoints->FindOrAddPoint(*V, NextIndex, THRESH_POINTS_ARE_SAME); + if (ReturnedIndex == NextIndex) + { + Model->Points.Add(*V); + } + + return ReturnedIndex; + } + + // Try to find a match quickly from the Bsp. This finds all potential matches + // except for any dissociated from nodes/surfaces during a rebuild. + FVector Temp; + int32 pVertex; + float NearestDist = Model->FindNearestVertex(*V,Temp,Thresh,pVertex); + if( (NearestDist >= 0.0) && (NearestDist <= Thresh) ) + { + // Found an existing point. + return pVertex; + } + else + { + // No match found; add it slowly to find duplicates. + return AddThing(Model->Points, *V, Thresh, !GFastRebuild); + } +} + + +/** + * Builds Bsp from the editor polygon set (EdPolys) of a model. + * + * Opt = Bsp optimization, BSP_Lame (fast), BSP_Good (medium), BSP_Optimal (slow) + * Balance = 0-100, 0=only worry about minimizing splits, 100=only balance tree. + */ +void FHBSPOps::bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + int32 OriginalPolys = Model->Polys->Element.Num(); + + // Empty the model's tables. + if( RebuildSimplePolys==1 ) + { + // Empty everything but polys. + Model->EmptyModel( 1, 0 ); + } + else if( RebuildSimplePolys==0 ) + { + // Empty node vertices. + for( int32 i=0; iNodes.Num(); i++ ) + Model->Nodes[i].NumVertices = 0; + + // Refresh the Bsp. + bspRefresh(Model,1); + + // Empty nodes. + Model->EmptyModel( 0, 0 ); + } + if( Model->Polys->Element.Num() ) + { + // Allocate polygon pool. + FMemMark Mark(FMemStack::Get()); + FPoly** PolyList = new( FMemStack::Get(), Model->Polys->Element.Num() )FPoly*; + + // Add all FPolys to active list. + for( int32 i=0; iPolys->Element.Num(); i++ ) + if( Model->Polys->Element[i].Vertices.Num() ) + PolyList[i] = &Model->Polys->Element[i]; + + // Now split the entire Bsp by splitting the list of all polygons. + SplitPolyList + ( + Model, + INDEX_NONE, + NODE_Root, + Model->Polys->Element.Num(), + PolyList, + Opt, + Balance, + PortalBias, + RebuildSimplePolys, + BspPoints, + BspVectors + ); + + // Now build the bounding boxes for all nodes. + if( RebuildSimplePolys==0 ) + { + // Remove unreferenced things. + bspRefresh( Model, 1 ); + + // Rebuild all bounding boxes. + bspBuildBounds( Model ); + } + + Mark.Pop(); + } + +// UE_LOG(LogBSPOps, Log, TEXT("bspBuild built %i convex polys into %i nodes"), OriginalPolys, Model->Nodes.Num() ); +} + +/** + * If the Bsp's point and vector tables are nearly full, reorder them and delete unused ones. + */ +void FHBSPOps::bspRefresh( UModel* Model, bool NoRemapSurfs ) +{ + FMemStack& MemStack = FMemStack::Get(); + + FMemMark Mark(MemStack); + + int32 NumNodes = Model->Nodes.Num(); + int32 NumSurfs = Model->Surfs.Num(); + int32 NumVectors = Model->Vectors.Num(); + int32 NumPoints = Model->Points.Num(); + + // Remove unreferenced Bsp surfs. + int32* PolyRef; + if( NoRemapSurfs ) + { + PolyRef = NewZeroed(MemStack, NumSurfs); + } + else + { + PolyRef = NewOned(MemStack, NumSurfs); + } + + int32* NodeRef = NewOned(MemStack, NumNodes); + if( NumNodes > 0 ) + { + TagReferencedNodes( Model, NodeRef, PolyRef, 0 ); + } + + // Remap Bsp surfs. + { + int32 n=0; + for( int32 i=0; iSurfs[n] = Model->Surfs[i]; + PolyRef[i]=n++; + } + } + //UE_LOG(LogBSPOps, Log, TEXT("Polys: %i -> %i"), NumSurfs, n ); + Model->Surfs.RemoveAt( n, NumSurfs-n ); + NumSurfs = n; + } + + // Remap Bsp nodes. + { + int32 n=0; + for( int32 i=0; iNodes[n] = Model->Nodes[i]; + NodeRef[i]=n++; + } + } + //UE_LOG(LogBSPOps, Log, TEXT("Nodes: %i -> %i"), NumNodes, n ); + Model->Nodes.RemoveAt( n, NumNodes-n ); + NumNodes = n; + } + + // Update Bsp nodes. + for( int32 i=0; iNodes[i]; + Node->iSurf = PolyRef[Node->iSurf]; + if (Node->iFront != INDEX_NONE) Node->iFront = NodeRef[Node->iFront]; + if (Node->iBack != INDEX_NONE) Node->iBack = NodeRef[Node->iBack]; + if (Node->iPlane != INDEX_NONE) Node->iPlane = NodeRef[Node->iPlane]; + } + + // Remove unreferenced points and vectors. + int32* VectorRef = NewOned(MemStack, NumVectors); + int32* PointRef = NewOned(MemStack, NumPoints); + + // Check Bsp surfs. + TArray VertexRef; + for( int32 i=0; iSurfs[i]; + VectorRef [Surf->vNormal ] = 0; + VectorRef [Surf->vTextureU ] = 0; + VectorRef [Surf->vTextureV ] = 0; + PointRef [Surf->pBase ] = 0; + } + + // Check Bsp nodes. + for( int32 i=0; iNodes[i]; + FVert* VertPool = &Model->Verts[Node->iVertPool]; + for( int B=0; BNumVertices; B++ ) + { + PointRef[VertPool->pVertex] = 0; + VertPool++; + } + } + + // Remap points. + { + int32 n=0; + for( int32 i=0; iPoints[n] = Model->Points[i]; + PointRef[i] = n++; + } + //UE_LOG(LogBSPOps, Log, TEXT("Points: %i -> %i"), NumPoints, n ); + Model->Points.RemoveAt( n, NumPoints-n ); + NumPoints = n; + } + + // Remap vectors. + { + int32 n=0; + for (int32 i=0; iVectors[n] = Model->Vectors[i]; + VectorRef[i] = n++; + } + //UE_LOG(LogBSPOps, Log, TEXT("Vectors: %i -> %i"), NumVectors, n ); + Model->Vectors.RemoveAt( n, NumVectors-n ); + NumVectors = n; + } + + // Update Bsp surfs. + for( int32 i=0; iSurfs[i]; + Surf->vNormal = VectorRef [Surf->vNormal ]; + Surf->vTextureU = VectorRef [Surf->vTextureU]; + Surf->vTextureV = VectorRef [Surf->vTextureV]; + Surf->pBase = PointRef [Surf->pBase ]; + } + + // Update Bsp nodes. + for( int32 i=0; iNodes[i]; + FVert* VertPool = &Model->Verts[Node->iVertPool]; + for( int B=0; BNumVertices; B++ ) + { + VertPool->pVertex = PointRef [VertPool->pVertex]; + VertPool++; + } + } + + // Shrink the objects. + Model->ShrinkModel(); + + Mark.Pop(); +} + +// Build bounding volumes for all Bsp nodes. The bounding volume of the node +// completely encloses the "outside" space occupied by the nodes. Note that +// this is not the same as representing the bounding volume of all of the +// polygons within the node. +// +// We start with a practically-infinite cube and filter it down the Bsp, +// whittling it away until all of its convex volume fragments land in leaves. +void FHBSPOps::bspBuildBounds( UModel* Model ) +{ + if( Model->Nodes.Num()==0 ) + return; + + FPoly Polys[6], *PolyList[6]; + for( int32 i=0; i<6; i++ ) + { + PolyList[i] = &Polys[i]; + PolyList[i]->Init(); + PolyList[i]->iBrushPoly = INDEX_NONE; + } + + new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); + new(Polys[0].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,HALF_WORLD_MAX); + Polys[0].Normal =FVector( 0.000000, 0.000000, 1.000000 ); + Polys[0].Base =Polys[0].Vertices[0]; + + new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector( HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[1].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[1].Normal =FVector( 0.000000, 0.000000, -1.000000 ); + Polys[1].Base =Polys[1].Vertices[0]; + + new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector(-HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[2].Vertices)FVector( HALF_WORLD_MAX,HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[2].Normal =FVector( 0.000000, 1.000000, 0.000000 ); + Polys[2].Base =Polys[2].Vertices[0]; + + new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector( HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[3].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[3].Normal =FVector( 0.000000, -1.000000, 0.000000 ); + Polys[3].Base =Polys[3].Vertices[0]; + + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[4].Vertices)FVector(HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[4].Normal =FVector( 1.000000, 0.000000, 0.000000 ); + Polys[4].Base =Polys[4].Vertices[0]; + + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX,-HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX,-HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); + new(Polys[5].Vertices)FVector(-HALF_WORLD_MAX, HALF_WORLD_MAX,-HALF_WORLD_MAX); + Polys[5].Normal =FVector(-1.000000, 0.000000, 0.000000 ); + Polys[5].Base =Polys[5].Vertices[0]; + // Empty hulls. + Model->LeafHulls.Empty(); + for( int32 i=0; iNodes.Num(); i++ ) + Model->Nodes[i].iCollisionBound = INDEX_NONE; + FilterBound( Model, NULL, 0, PolyList, 6, Model->RootOutside ); +// UE_LOG(LogBSPOps, Log, TEXT("bspBuildBounds: Generated %i hulls"), Model->LeafHulls.Num() ); +} + +/** + * Validate a brush, and set iLinks on all EdPolys to index of the + * first identical EdPoly in the list, or its index if it's the first. + * Not transactional. + */ +void FHBSPOps::bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ) +{ + check(Brush != nullptr); + Brush->Modify(); + if( ForceValidate || !Brush->Linked ) + { + Brush->Linked = 1; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + Brush->Polys->Element[i].iLink = i; + } + int32 n=0; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + FPoly* EdPoly = &Brush->Polys->Element[i]; + if( EdPoly->iLink==i ) + { + for( int32 j=i+1; jPolys->Element.Num(); j++ ) + { + FPoly* OtherPoly = &Brush->Polys->Element[j]; + if + ( OtherPoly->iLink == j + && OtherPoly->Material == EdPoly->Material + && OtherPoly->TextureU == EdPoly->TextureU + && OtherPoly->TextureV == EdPoly->TextureV + && OtherPoly->PolyFlags == EdPoly->PolyFlags + && (OtherPoly->Normal | EdPoly->Normal)>0.9999 ) + { + float Dist = FVector::PointPlaneDist( OtherPoly->Vertices[0], EdPoly->Vertices[0], EdPoly->Normal ); + if( Dist>-0.001 && Dist<0.001 ) + { + OtherPoly->iLink = i; + n++; + } + } + } + } + } +// UE_LOG(LogBSPOps, Log, TEXT("BspValidateBrush linked %i of %i polys"), n, Brush->Polys->Element.Num() ); + } + + // Build bounds. + Brush->BuildBound(); +} + +void FHBSPOps::bspUnlinkPolys( UModel* Brush ) +{ + Brush->Modify(); + Brush->Linked = 1; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + Brush->Polys->Element[i].iLink = i; + } +} + +// Add an editor polygon to the Bsp, and also stick a reference to it +// in the editor polygon's BspNodes list. If the editor polygon has more sides +// than the Bsp will allow, split it up into several sub-polygons. +// +// Returns: Index to newly-created node of Bsp. If several nodes were created because +// of split polys, returns the parent (highest one up in the Bsp). +int32 FHBSPOps::bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + if( NodePlace == NODE_Plane ) + { + // Make sure coplanars are added at the end of the coplanar list so that + // we don't insert NF_IsNew nodes with non NF_IsNew coplanar children. + while( Model->Nodes[iParent].iPlane != INDEX_NONE ) + { + iParent = Model->Nodes[iParent].iPlane; + } + } + FBspSurf* Surf = NULL; + if( EdPoly->iLinkSurf == Model->Surfs.Num() ) + { + int32 NewIndex = Model->Surfs.AddZeroed(); + Surf = &Model->Surfs[NewIndex]; + + // This node has a new polygon being added by bspBrushCSG; must set its properties here. + FVector Base = EdPoly->Base, Normal = EdPoly->Normal, TextureU = EdPoly->TextureU, TextureV = EdPoly->TextureV; + Surf->pBase = bspAddPoint (Model,&Base,1,BspPoints); + Surf->vNormal = bspAddVector (Model,&Normal,1,BspVectors); + Surf->vTextureU = bspAddVector (Model,&TextureU,0,BspVectors); + Surf->vTextureV = bspAddVector (Model,&TextureV,0,BspVectors); + Surf->Material = EdPoly->Material; + Surf->Actor = NULL; + + Surf->PolyFlags = EdPoly->PolyFlags & ~PF_NoAddToBSP; + Surf->LightMapScale= EdPoly->LightMapScale; + + // Find the LightmassPrimitiveSettings in the UModel... + int32 FoundLightmassIndex = INDEX_NONE; + if (Model->LightmassSettings.Find(EdPoly->LightmassSettings, FoundLightmassIndex) == false) + { + FoundLightmassIndex = Model->LightmassSettings.Add(EdPoly->LightmassSettings); + } + Surf->iLightmassIndex = FoundLightmassIndex; + + Surf->Actor = EdPoly->Actor; + Surf->iBrushPoly = EdPoly->iBrushPoly; + + if (EdPoly->Actor) + { + Surf->bHiddenEdTemporary = EdPoly->Actor->IsTemporarilyHiddenInEditor(); + Surf->bHiddenEdLevel = EdPoly->Actor->bHiddenEdLevel; + Surf->bHiddenEdLayer = EdPoly->Actor->bHiddenEdLayer; + } + + Surf->Plane = FPlane(EdPoly->Vertices[0],EdPoly->Normal); + } + else + { + check(EdPoly->iLinkSurf!=INDEX_NONE); + check(EdPoly->iLinkSurfSurfs.Num()); + Surf = &Model->Surfs[EdPoly->iLinkSurf]; + } + + // Set NodeFlags. + if( Surf->PolyFlags & PF_NotSolid ) NodeFlags |= NF_NotCsg; + if( Surf->PolyFlags & (PF_Invisible|PF_Portal) ) NodeFlags |= NF_NotVisBlocking; + + if( EdPoly->Vertices.Num() > FBspNode::MAX_NODE_VERTICES ) + { + // Split up into two coplanar sub-polygons (one with MAX_NODE_VERTICES vertices and + // one with all the remaining vertices) and recursively add them. + + // EdPoly1 is just the first MAX_NODE_VERTICES from EdPoly. + FMemMark Mark(FMemStack::Get()); + FPoly *EdPoly1 = new FPoly; + *EdPoly1 = *EdPoly; + EdPoly1->Vertices.RemoveAt(FBspNode::MAX_NODE_VERTICES,EdPoly->Vertices.Num() - FBspNode::MAX_NODE_VERTICES); + + // EdPoly2 is the first vertex from EdPoly, and the last EdPoly->Vertices.Num() - MAX_NODE_VERTICES + 1. + FPoly *EdPoly2 = new FPoly; + *EdPoly2 = *EdPoly; + EdPoly2->Vertices.RemoveAt(1,FBspNode::MAX_NODE_VERTICES - 2); + + int32 iNode = bspAddNode( Model, iParent, NodePlace, NodeFlags, EdPoly1, BspPoints, BspVectors ); // Add this poly first. + bspAddNode( Model, iNode, NODE_Plane, NodeFlags, EdPoly2, BspPoints, BspVectors ); // Then add other (may be bigger). + + delete EdPoly1; + delete EdPoly2; + + Mark.Pop(); + return iNode; // Return coplanar "parent" node (not coplanar child) + } + else + { + // Add node. + int32 iNode = Model->Nodes.AddZeroed(); + FBspNode& Node = Model->Nodes[iNode]; + + // Tell transaction tracking system that parent is about to be modified. + FBspNode* Parent=NULL; + if( NodePlace!=NODE_Root ) + Parent = &Model->Nodes[iParent]; + + // Set node properties. + Node.iSurf = EdPoly->iLinkSurf; + Node.NodeFlags = NodeFlags; + Node.iCollisionBound = INDEX_NONE; + Node.Plane = FPlane( EdPoly->Vertices[0], EdPoly->Normal ); + Node.iVertPool = Model->Verts.AddUninitialized(EdPoly->Vertices.Num()); + Node.iFront = INDEX_NONE; + Node.iBack = INDEX_NONE; + Node.iPlane = INDEX_NONE; + if( NodePlace==NODE_Root ) + { + Node.iLeaf[0] = INDEX_NONE; + Node.iLeaf[1] = INDEX_NONE; + Node.iZone[0] = 0; + Node.iZone[1] = 0; + } + else if( NodePlace==NODE_Front || NodePlace==NODE_Back ) + { + int32 ZoneFront=NodePlace==NODE_Front; + Node.iLeaf[0] = Parent->iLeaf[ZoneFront]; + Node.iLeaf[1] = Parent->iLeaf[ZoneFront]; + Node.iZone[0] = Parent->iZone[ZoneFront]; + Node.iZone[1] = Parent->iZone[ZoneFront]; + } + else + { + int32 IsFlipped = (Node.Plane|Parent->Plane)<0.0; + Node.iLeaf[0] = Parent->iLeaf[IsFlipped ]; + Node.iLeaf[1] = Parent->iLeaf[1-IsFlipped]; + Node.iZone[0] = Parent->iZone[IsFlipped ]; + Node.iZone[1] = Parent->iZone[1-IsFlipped]; + } + + // Link parent to this node. + if (NodePlace == NODE_Front) + { + Parent->iFront = iNode; + } + else if (NodePlace == NODE_Back) + { + Parent->iBack = iNode; + } + else if (NodePlace == NODE_Plane) + { + Parent->iPlane = iNode; + } + + // Add all points to point table, merging nearly-overlapping polygon points + // with other points in the poly to prevent criscrossing vertices from + // being generated. + + // Must maintain Node->NumVertices on the fly so that bspAddPoint is always + // called with the Bsp in a clean state. + Node.NumVertices = 0; + FVert* VertPool = &Model->Verts[ Node.iVertPool ]; + for( uint8 i=0; iVertices.Num(); i++ ) + { + FVector Vertex = EdPoly->Vertices[i]; + int32 pVertex = bspAddPoint(Model,&Vertex,0, BspPoints); + if( Node.NumVertices==0 || VertPool[Node.NumVertices-1].pVertex!=pVertex ) + { + VertPool[Node.NumVertices].iSide = INDEX_NONE; + VertPool[Node.NumVertices].pVertex = pVertex; + Node.NumVertices++; + } + } + if( Node.NumVertices>=2 && VertPool[0].pVertex==VertPool[Node.NumVertices-1].pVertex ) + { + Node.NumVertices--; + } + if( Node.NumVertices < 3 ) + { + GErrors++; +// UE_LOG(LogBSPOps, Warning, TEXT("bspAddNode: Infinitesimal polygon %i (%i)"), Node.NumVertices, EdPoly->Vertices.Num() ); + Node.NumVertices = 0; + } + + return iNode; + } +} + +/** + * Rebuild some brush internals + */ +void FHBSPOps::RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + Brush->Modify(); + Brush->EmptyModel(1, 0); + + // Build bounding box. + Brush->BuildBound(); + + // Build BSP for the brush. + bspBuild(Brush, BSP_Good, 15, 70, 1, 0, BspPoints, BspVectors); + bspRefresh(Brush, 1); + bspBuildBounds(Brush); +} + +/** + * Rotates the specified brush's vertices. + */ +void FHBSPOps::RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + if(Brush->GetBrushComponent()->Brush && Brush->GetBrushComponent()->Brush->Polys) + { + for( int32 poly = 0 ; poly < Brush->GetBrushComponent()->Brush->Polys->Element.Num() ; poly++ ) + { + FPoly* Poly = &(Brush->GetBrushComponent()->Brush->Polys->Element[poly]); + + // Rotate the vertices. + const FRotationMatrix RotMatrix( Rotation ); + for( int32 vertex = 0 ; vertex < Poly->Vertices.Num() ; vertex++ ) + { + Poly->Vertices[vertex] = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Vertices[vertex] - Brush->GetPivotOffset()); + } + Poly->Base = Brush->GetPivotOffset() + RotMatrix.TransformVector(Poly->Base - Brush->GetPivotOffset()); + + // Rotate the texture vectors. + Poly->TextureU = RotMatrix.TransformVector( Poly->TextureU ); + Poly->TextureV = RotMatrix.TransformVector( Poly->TextureV ); + + // Recalc the normal for the poly. + Poly->Normal = FVector::ZeroVector; + Poly->Finalize(Brush,0); + } + + Brush->GetBrushComponent()->Brush->BuildBound(); + + if( !Brush->IsStaticBrush() ) + { + csgPrepMovingBrush( Brush, BspPoints, BspVectors ); + } + + if ( bClearComponents ) + { + Brush->ReregisterAllComponents(); + } + } +} + + +void FHBSPOps::HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors) +{ + // The default physics volume doesn't have an associated UModel, so we need to handle that case gracefully. + if(Volume.Brush) + { + FHBSPOps::csgPrepMovingBrush( &Volume, BspPoints, BspVectors); + } +} + +UHBspPointsGrid* UHBspPointsGrid::Create(float InGranularity, float InThreshold, int32 InitialSize) +{ + check(InThreshold / InGranularity <= 0.5f); + + UHBspPointsGrid* Obj = NewObject(GetTransientPackage(), UHBspPointsGrid::StaticClass()); + Obj->OneOverGranularity = 1.0f / InGranularity; + Obj->Threshold = InThreshold; + Obj->Clear(InitialSize); + + return Obj; +} + +void UHBspPointsGrid::Clear(int32 InitialSize) +{ + GridMap.Empty(InitialSize); +} + + +// Given a grid index in one axis, a real position on the grid and a threshold radius, +// return either: +// - the additional grid index it can overlap in that axis, or +// - the original grid index if there is no overlap. +int32 UHBspPointsGrid::GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold) +{ + if (GridPos - GridIndex < GridThreshold) + { + return GridIndex - 1; + } + else if (1.0f - (GridPos - GridIndex) < GridThreshold) + { + return GridIndex + 1; + } + else + { + return GridIndex; + } +} + +int32 UHBspPointsGrid::FindOrAddPoint(const FVector& Point, int32 Index, float PointThreshold) +{ + // Offset applied to the grid coordinates so aligned vertices (the normal case) don't overlap several grid items (taking into account the threshold) + const float GridOffset = 0.12345f; + + const float AdjustedPointX = Point.X - GridOffset; + const float AdjustedPointY = Point.Y - GridOffset; + const float AdjustedPointZ = Point.Z - GridOffset; + + const float GridX = AdjustedPointX * OneOverGranularity; + const float GridY = AdjustedPointY * OneOverGranularity; + const float GridZ = AdjustedPointZ * OneOverGranularity; + + // Get the grid indices corresponding to the point coordinates + const int32 GridIndexX = FMath::FloorToInt(GridX); + const int32 GridIndexY = FMath::FloorToInt(GridY); + const int32 GridIndexZ = FMath::FloorToInt(GridZ); + + // Find grid item in map + FHBspPointsGridItem& GridItem = GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, GridIndexZ)); + + // Iterate through grid item points and return a point if it's close to the threshold + const float PointThresholdSquared = PointThreshold * PointThreshold; + for (const FHBspIndexedPoint& IndexedPoint : GridItem.IndexedPoints) + { + if (FVector::DistSquared(IndexedPoint.Point, Point) <= PointThresholdSquared) + { + return IndexedPoint.Index; + } + } + + // Otherwise, the point is new: add it to the grid item. + GridItem.IndexedPoints.Emplace(Point, Index); + + // The grid has a maximum threshold of a certain radius. If the point is near the edge of a grid cube, it may overlap into other items. + // Add it to all grid items it can be seen from. + const float GridThreshold = Threshold * OneOverGranularity; + const int32 NeighbourX = GetAdjacentIndexIfOverlapping(GridIndexX, GridX, GridThreshold); + const int32 NeighbourY = GetAdjacentIndexIfOverlapping(GridIndexY, GridY, GridThreshold); + const int32 NeighbourZ = GetAdjacentIndexIfOverlapping(GridIndexZ, GridZ, GridThreshold); + + const bool bOverlapsInX = (NeighbourX != GridIndexX); + const bool bOverlapsInY = (NeighbourY != GridIndexY); + const bool bOverlapsInZ = (NeighbourZ != GridIndexZ); + + if (bOverlapsInX) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInY) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(NeighbourX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else + { + if (bOverlapsInY) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, GridIndexZ)).IndexedPoints.Emplace(Point, Index); + + if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, NeighbourY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + else if (bOverlapsInZ) + { + GridMap.FindOrAdd(FHBspPointsKey(GridIndexX, GridIndexY, NeighbourZ)).IndexedPoints.Emplace(Point, Index); + } + } + + return Index; +} + diff --git a/Source/HoudiniEngine/Private/HBSPOps.h b/Source/HoudiniEngine/Private/HBSPOps.h index f5a9a02a7..8e359b070 100644 --- a/Source/HoudiniEngine/Private/HBSPOps.h +++ b/Source/HoudiniEngine/Private/HBSPOps.h @@ -1,181 +1,181 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Engine/Brush.h" -#include "Engine/Polys.h" - -#include "HBSPOps.generated.h" - -class AVolume; -class UModel; - -// This codebase have been localised from UnrealEd/HBSPOps to remove static/global variables. -class FHBSPOps -{ -public: - FHBSPOps(); - - /** Quality level for rebuilding Bsp. */ - enum EBspOptimization - { - BSP_Lame, - BSP_Good, - BSP_Optimal - }; - - /** Possible positions of a child Bsp node relative to its parent (for BspAddToNode) */ - enum ENodePlace - { - NODE_Back = 0, // Node is in back of parent -> Bsp[iParent].iBack. - NODE_Front = 1, // Node is in front of parent -> Bsp[iParent].iFront. - NODE_Plane = 2, // Node is coplanar with parent -> Bsp[iParent].iPlane. - NODE_Root = 3, // Node is the Bsp root and has no parent -> Bsp[0]. - }; - - static void csgPrepMovingBrush( ABrush* Actor, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static void csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static ABrush* csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - static int32 bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ); - static int32 bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ); - static void bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - static void bspRefresh( UModel* Model, bool NoRemapSurfs ); - - static void bspBuildBounds( UModel* Model ); - - static void bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ); - static void bspUnlinkPolys( UModel* Brush ); - static int32 bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - /** - * Rebuild some brush internals - */ - static void RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - static FPoly BuildInfiniteFPoly( UModel* Model, int32 iNode ); - - /** - * Rotates the specified brush's vertices. - */ - static void RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - /** Called when an AVolume shape is changed*/ - static void HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); - - /** Errors encountered in Csg operation. */ - static int32 GErrors; - static bool GFastRebuild; - -protected: - static void SplitPolyList - ( - UModel *Model, - int32 iParent, - FHBSPOps::ENodePlace NodePlace, - int32 NumPolys, - FPoly **PolyList, - EBspOptimization Opt, - int32 Balance, - int32 PortalBias, - int32 RebuildSimplePolys, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); -}; - - -struct FHBspPointsKey -{ - int32 X; - int32 Y; - int32 Z; - - FHBspPointsKey(int32 InX, int32 InY, int32 InZ) - : X(InX) - , Y(InY) - , Z(InZ) - {} - - friend FORCEINLINE bool operator == (const FHBspPointsKey& A, const FHBspPointsKey& B) - { - return A.X == B.X && A.Y == B.Y && A.Z == B.Z; - } - - friend FORCEINLINE uint32 GetTypeHash(const FHBspPointsKey& Key) - { - return HashCombine(static_cast(Key.X), HashCombine(static_cast(Key.Y), static_cast(Key.Z))); - } -}; - -struct FHBspIndexedPoint -{ - FHBspIndexedPoint(const FVector& InPoint, int32 InIndex) - : Point(InPoint) - , Index(InIndex) - {} - - FVector Point; - int32 Index; -}; - - -struct FHBspPointsGridItem -{ - TArray> IndexedPoints; -}; - - -// Represents a sparse granular 3D grid into which points are added for quick (~O(1)) lookup. -// The 3D space is divided into a grid with a given granularity. -// Points are considered to have a given radius (threshold) and are added to the grid cube they fall in, and to up to seven neighbours if they overlap. -UCLASS() -class HOUDINIENGINE_API UHBspPointsGrid : public UObject -{ - GENERATED_BODY() -protected: - - UHBspPointsGrid() {} - -public: - // Create a new instance of this grid with the given arguments. - static UHBspPointsGrid* Create(float InGranularity, float InThreshold, int32 InitialSize = 0); - - void Clear(int32 InitialSize = 0); - - int32 FindOrAddPoint(const FVector& Point, int32 Index, float Threshold); - - static FORCEINLINE int32 GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold); - -private: - float OneOverGranularity; - float Threshold; - - typedef TMap FGridMap; - FGridMap GridMap; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/Brush.h" +#include "Engine/Polys.h" + +#include "HBSPOps.generated.h" + +class AVolume; +class UModel; + +// This codebase have been localised from UnrealEd/HBSPOps to remove static/global variables. +class FHBSPOps +{ +public: + FHBSPOps(); + + /** Quality level for rebuilding Bsp. */ + enum EBspOptimization + { + BSP_Lame, + BSP_Good, + BSP_Optimal + }; + + /** Possible positions of a child Bsp node relative to its parent (for BspAddToNode) */ + enum ENodePlace + { + NODE_Back = 0, // Node is in back of parent -> Bsp[iParent].iBack. + NODE_Front = 1, // Node is in front of parent -> Bsp[iParent].iFront. + NODE_Plane = 2, // Node is coplanar with parent -> Bsp[iParent].iPlane. + NODE_Root = 3, // Node is the Bsp root and has no parent -> Bsp[0]. + }; + + static void csgPrepMovingBrush( ABrush* Actor, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static void csgCopyBrush( ABrush* Dest, ABrush* Src, uint32 PolyFlags, EObjectFlags ResFlags, bool bNeedsPrep, bool bCopyPosRotScale, bool bAllowEmpty, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static ABrush* csgAddOperation( ABrush* Actor, uint32 PolyFlags, EBrushType BrushType, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + static int32 bspAddVector( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspVectors ); + static int32 bspAddPoint( UModel* Model, FVector* V, bool Exact, UHBspPointsGrid* BspPoints ); + static void bspBuild( UModel* Model, enum EBspOptimization Opt, int32 Balance, int32 PortalBias, int32 RebuildSimplePolys, int32 iNode, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + static void bspRefresh( UModel* Model, bool NoRemapSurfs ); + + static void bspBuildBounds( UModel* Model ); + + static void bspValidateBrush( UModel* Brush, bool ForceValidate, bool DoStatusUpdate ); + static void bspUnlinkPolys( UModel* Brush ); + static int32 bspAddNode( UModel* Model, int32 iParent, enum ENodePlace NodePlace, uint32 NodeFlags, FPoly* EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + /** + * Rebuild some brush internals + */ + static void RebuildBrush(UModel* Brush, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + static FPoly BuildInfiniteFPoly( UModel* Model, int32 iNode ); + + /** + * Rotates the specified brush's vertices. + */ + static void RotateBrushVerts(ABrush* Brush, const FRotator& Rotation, bool bClearComponents, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + /** Called when an AVolume shape is changed*/ + static void HandleVolumeShapeChanged(AVolume& Volume, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors); + + /** Errors encountered in Csg operation. */ + static int32 GErrors; + static bool GFastRebuild; + +protected: + static void SplitPolyList + ( + UModel *Model, + int32 iParent, + FHBSPOps::ENodePlace NodePlace, + int32 NumPolys, + FPoly **PolyList, + EBspOptimization Opt, + int32 Balance, + int32 PortalBias, + int32 RebuildSimplePolys, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); +}; + + +struct FHBspPointsKey +{ + int32 X; + int32 Y; + int32 Z; + + FHBspPointsKey(int32 InX, int32 InY, int32 InZ) + : X(InX) + , Y(InY) + , Z(InZ) + {} + + friend FORCEINLINE bool operator == (const FHBspPointsKey& A, const FHBspPointsKey& B) + { + return A.X == B.X && A.Y == B.Y && A.Z == B.Z; + } + + friend FORCEINLINE uint32 GetTypeHash(const FHBspPointsKey& Key) + { + return HashCombine(static_cast(Key.X), HashCombine(static_cast(Key.Y), static_cast(Key.Z))); + } +}; + +struct FHBspIndexedPoint +{ + FHBspIndexedPoint(const FVector& InPoint, int32 InIndex) + : Point(InPoint) + , Index(InIndex) + {} + + FVector Point; + int32 Index; +}; + + +struct FHBspPointsGridItem +{ + TArray> IndexedPoints; +}; + + +// Represents a sparse granular 3D grid into which points are added for quick (~O(1)) lookup. +// The 3D space is divided into a grid with a given granularity. +// Points are considered to have a given radius (threshold) and are added to the grid cube they fall in, and to up to seven neighbours if they overlap. +UCLASS() +class HOUDINIENGINE_API UHBspPointsGrid : public UObject +{ + GENERATED_BODY() +protected: + + UHBspPointsGrid() {} + +public: + // Create a new instance of this grid with the given arguments. + static UHBspPointsGrid* Create(float InGranularity, float InThreshold, int32 InitialSize = 0); + + void Clear(int32 InitialSize = 0); + + int32 FindOrAddPoint(const FVector& Point, int32 Index, float Threshold); + + static FORCEINLINE int32 GetAdjacentIndexIfOverlapping(int32 GridIndex, float GridPos, float GridThreshold); + +private: + float OneOverGranularity; + float Threshold; + + typedef TMap FGridMap; + FGridMap GridMap; +}; diff --git a/Source/HoudiniEngine/Private/HCsgUtils.cpp b/Source/HoudiniEngine/Private/HCsgUtils.cpp index 6d31b3732..55503b4d3 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.cpp +++ b/Source/HoudiniEngine/Private/HCsgUtils.cpp @@ -1,1462 +1,1462 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HCsgUtils.h" - -#include "Engine/Engine.h" -#include "Engine/Polys.h" -#include "Engine/Selection.h" -#include "Materials/Material.h" -#include "Misc/FeedbackContext.h" - -#include "ActorEditorUtils.h" -#include "Misc/ScopedSlowTask.h" - - -DEFINE_LOG_CATEGORY_STATIC(LogHCsgUtils, Log, All); - -#if WITH_EDITOR -#include "Editor.h" -#endif - -// Magic numbers. -#define THRESH_OPTGEOM_COPLANAR (0.25) /* Threshold for Bsp geometry optimization */ -#define THRESH_OPTGEOM_COSIDAL (0.25) /* Threshold for Bsp geometry optimization */ - - -UHCsgUtils::UHCsgUtils() -{ - // A TempModel is allocated for the HCsgUtils instance to avoid reallocation during inner loops. - TempModel = NewObject(); - TempModel->Initialize(nullptr, 1); - - /*GBspPoints = NewObject(); - GBspVectors = NewObject();*/ -} - - -/*---------------------------------------------------------------------------- - CSG leaf filter callbacks. -----------------------------------------------------------------------------*/ - -void UHCsgUtils::AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); - break; - case F_COSPATIAL_FACING_OUT: - if( !(EdPoly->PolyFlags & PF_Semisolid) ) - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_IN: - break; - } -} - -void UHCsgUtils::AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - // Only affect the world poly if it has been cut. - if( EdPoly->PolyFlags & PF_EdCut ) - FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors ); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_IN: - case F_COSPATIAL_FACING_OUT: - // Discard original poly. - GDiscarded++; - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - break; - } -} - -void UHCsgUtils::SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch (Filter) - { - case F_OUTSIDE: - case F_COSPATIAL_FACING_OUT: - case F_COSPATIAL_FACING_IN: - case F_COPLANAR_OUTSIDE: - break; - case F_COPLANAR_INSIDE: - case F_INSIDE: - EdPoly->Reverse(); - FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); // Add to Bsp back - EdPoly->Reverse(); - break; - } -} - -void UHCsgUtils::SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - // Only affect the world poly if it has been cut. - if( EdPoly->PolyFlags & PF_EdCut ) - FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors); - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - // Discard original poly. - GDiscarded++; - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - break; - } -} - -void UHCsgUtils::IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - case F_COSPATIAL_FACING_OUT: - // Ignore. - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - if( EdPoly->Fix()>=3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_IN: - // Ignore. - break; - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - if( EdPoly->Fix() >= 3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_INSIDE: - case F_COPLANAR_INSIDE: - case F_COSPATIAL_FACING_OUT: - case F_COSPATIAL_FACING_IN: - // Ignore. - break; - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - if( EdPoly->Fix()>=3 ) - new(GModel->Polys->Element)FPoly(*EdPoly); - break; - } -} - -void UHCsgUtils::DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, - EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - switch( Filter ) - { - case F_OUTSIDE: - case F_COPLANAR_OUTSIDE: - case F_COSPATIAL_FACING_OUT: - // Ignore. - break; - case F_COPLANAR_INSIDE: - case F_INSIDE: - case F_COSPATIAL_FACING_IN: - if( EdPoly->Fix() >= 3 ) - { - EdPoly->Reverse(); - new(GModel->Polys->Element)FPoly(*EdPoly); - EdPoly->Reverse(); - } - break; - } -} - -/*---------------------------------------------------------------------------- - CSG polygon filtering routine (calls the callbacks). -----------------------------------------------------------------------------*/ - -// -// Handle a piece of a polygon that was filtered to a leaf. -// -void UHCsgUtils::FilterLeaf -( - BspFilterFunc FilterFunc, - UModel* Model, - int32 iNode, - FPoly* EdPoly, - FCoplanarInfo CoplanarInfo, - int32 LeafOutside, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - EPolyNodeFilter FilterType; - - if( CoplanarInfo.iOriginalNode == INDEX_NONE ) - { - // Processing regular, non-coplanar polygons. - FilterType = LeafOutside ? F_OUTSIDE : F_INSIDE; - (this->*FilterFunc)( Model, iNode, EdPoly, FilterType, ENodePlace, BspPoints, BspVectors ); - } - else if( CoplanarInfo.ProcessingBack ) - { - // Finished filtering polygon through tree in back of parent coplanar. - DoneFilteringBack: - if ((!LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_INSIDE; - else if (( LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_OUTSIDE; - else if ((!LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_OUT; - else if (( LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_IN; - else - { - UE_LOG(LogHCsgUtils, Fatal, TEXT("FilterLeaf: Bad Locs")); - return; - } - (this->*FilterFunc)( Model, CoplanarInfo.iOriginalNode, EdPoly, FilterType, FHBSPOps::NODE_Plane, BspPoints, BspVectors ); - } - else - { - CoplanarInfo.FrontLeafOutside = LeafOutside; - - if( CoplanarInfo.iBackNode == INDEX_NONE ) - { - // Back tree is empty. - LeafOutside = CoplanarInfo.BackNodeOutside; - goto DoneFilteringBack; - } - else - { - // Call FilterEdPoly to filter through the back. This will result in - // another call to FilterLeaf with iNode = leaf this falls into in the - // back tree and EdPoly = the final EdPoly to insert. - CoplanarInfo.ProcessingBack=1; - FilterEdPoly( FilterFunc, Model, CoplanarInfo.iBackNode, EdPoly,CoplanarInfo, CoplanarInfo.BackNodeOutside, BspPoints, BspVectors ); - } - } -} - -// -// Filter an EdPoly through the Bsp recursively, calling FilterFunc -// for all chunks that fall into leaves. FCoplanarInfo is used to -// handle the tricky case of double-recursion for polys that must be -// filtered through a node's front, then filtered through the node's back, -// in order to handle coplanar CSG properly. -// -void UHCsgUtils::FilterEdPoly -( - BspFilterFunc FilterFunc, - UModel *Model, - int32 iNode, - FPoly *EdPoly, - FCoplanarInfo CoplanarInfo, - int32 Outside, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - int32 SplitResult,iOurFront,iOurBack; - int32 NewFrontOutside,NewBackOutside; - - FilterLoop: - - // Split em. - FPoly TempFrontEdPoly,TempBackEdPoly; - SplitResult = EdPoly->SplitWithPlane - ( - Model->Points [Model->Verts[Model->Nodes[iNode].iVertPool].pVertex], - Model->Vectors[Model->Surfs[Model->Nodes[iNode].iSurf].vNormal], - &TempFrontEdPoly, - &TempBackEdPoly, - 0 - ); - - // Process split results. - if( SplitResult == SP_Front ) - { - Front: - - FBspNode *Node = &Model->Nodes[iNode]; - Outside = Outside || Node->IsCsg(); - - if( Node->iFront == INDEX_NONE ) - { - FilterLeaf(FilterFunc,Model,iNode,EdPoly,CoplanarInfo,Outside,FHBSPOps::NODE_Front, BspPoints, BspVectors); - } - else - { - iNode = Node->iFront; - goto FilterLoop; - } - } - else if( SplitResult == SP_Back ) - { - FBspNode *Node = &Model->Nodes[iNode]; - Outside = Outside && !Node->IsCsg(); - - if( Node->iBack == INDEX_NONE ) - { - FilterLeaf( FilterFunc, Model, iNode, EdPoly, CoplanarInfo, Outside, FHBSPOps::NODE_Back, BspPoints, BspVectors); - } - else - { - iNode=Node->iBack; - goto FilterLoop; - } - } - else if( SplitResult == SP_Coplanar ) - { - if( CoplanarInfo.iOriginalNode != INDEX_NONE ) - { - // This will happen once in a blue moon when a polygon is barely outside the - // coplanar threshold and is split up into a new polygon that is - // is barely inside the coplanar threshold. To handle this, just classify - // it as front and it will be handled propery. - FHBSPOps::GErrors++; -// UE_LOG(LogEditorBsp, Warning, TEXT("FilterEdPoly: Encountered out-of-place coplanar") ); - goto Front; - } - CoplanarInfo.iOriginalNode = iNode; - CoplanarInfo.iBackNode = INDEX_NONE; - CoplanarInfo.ProcessingBack = 0; - CoplanarInfo.BackNodeOutside = Outside; - NewFrontOutside = Outside; - - // See whether Node's iFront or iBack points to the side of the tree on the front - // of this polygon (will be as expected if this polygon is facing the same - // way as first coplanar in link, otherwise opposite). - if( (FVector(Model->Nodes[iNode].Plane) | EdPoly->Normal) >= 0.0 ) - { - iOurFront = Model->Nodes[iNode].iFront; - iOurBack = Model->Nodes[iNode].iBack; - - if( Model->Nodes[iNode].IsCsg() ) - { - CoplanarInfo.BackNodeOutside = 0; - NewFrontOutside = 1; - } - } - else - { - iOurFront = Model->Nodes[iNode].iBack; - iOurBack = Model->Nodes[iNode].iFront; - - if( Model->Nodes[iNode].IsCsg() ) - { - CoplanarInfo.BackNodeOutside = 1; - NewFrontOutside = 0; - } - } - - // Process front and back. - if ((iOurFront==INDEX_NONE)&&(iOurBack==INDEX_NONE)) - { - // No front or back. - CoplanarInfo.ProcessingBack = 1; - CoplanarInfo.FrontLeafOutside = NewFrontOutside; - FilterLeaf - ( - FilterFunc, - Model, - iNode, - EdPoly, - CoplanarInfo, - CoplanarInfo.BackNodeOutside, - FHBSPOps::NODE_Plane, - BspPoints, - BspVectors - ); - } - else if( iOurFront==INDEX_NONE && iOurBack!=INDEX_NONE ) - { - // Back but no front. - CoplanarInfo.ProcessingBack = 1; - CoplanarInfo.iBackNode = iOurBack; - CoplanarInfo.FrontLeafOutside = NewFrontOutside; - - iNode = iOurBack; - Outside = CoplanarInfo.BackNodeOutside; - goto FilterLoop; - } - else - { - // Has a front and maybe a back. - - // Set iOurBack up to process back on next call to FilterLeaf, and loop - // to process front. Next call to FilterLeaf will set FrontLeafOutside. - CoplanarInfo.ProcessingBack = 0; - - // May be a node or may be INDEX_NONE. - CoplanarInfo.iBackNode = iOurBack; - - iNode = iOurFront; - Outside = NewFrontOutside; - goto FilterLoop; - } - } - else if( SplitResult == SP_Split ) - { - // Front half of split. - if( Model->Nodes[iNode].IsCsg() ) - { - NewFrontOutside = 1; - NewBackOutside = 0; - } - else - { - NewFrontOutside = Outside; - NewBackOutside = Outside; - } - - if( Model->Nodes[iNode].iFront==INDEX_NONE ) - { - FilterLeaf - ( - FilterFunc, - Model, - iNode, - &TempFrontEdPoly, - CoplanarInfo, - NewFrontOutside, - FHBSPOps::NODE_Front, - BspPoints, - BspVectors - ); - } - else - { - FilterEdPoly - ( - FilterFunc, - Model, - Model->Nodes[iNode].iFront, - &TempFrontEdPoly, - CoplanarInfo, - NewFrontOutside, - BspPoints, - BspVectors - ); - } - - // Back half of split. - if( Model->Nodes[iNode].iBack==INDEX_NONE ) - { - FilterLeaf - ( - FilterFunc, - Model, - iNode, - &TempBackEdPoly, - CoplanarInfo, - NewBackOutside, - FHBSPOps::NODE_Back, - BspPoints, - BspVectors - ); - } - else - { - FilterEdPoly - ( - FilterFunc, - Model, - Model->Nodes[iNode].iBack, - &TempBackEdPoly, - CoplanarInfo, - NewBackOutside, - BspPoints, - BspVectors - ); - } - } -} - -// -// Regular entry into FilterEdPoly (so higher-level callers don't have to -// deal with unnecessary info). Filters starting at root. -// -void UHCsgUtils::BspFilterFPoly( BspFilterFunc FilterFunc, UModel *Model, FPoly *EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) -{ - FCoplanarInfo StartingCoplanarInfo; - StartingCoplanarInfo.iOriginalNode = INDEX_NONE; - if( Model->Nodes.Num() == 0 ) - { - // If Bsp is empty, process at root. - (this->*FilterFunc)( Model, 0, EdPoly, Model->RootOutside ? F_OUTSIDE : F_INSIDE, FHBSPOps::NODE_Root, BspPoints, BspVectors ); - } - else - { - // Filter through Bsp. - FilterEdPoly( FilterFunc, Model, 0, EdPoly, StartingCoplanarInfo, Model->RootOutside, BspPoints, BspVectors ); - } -} - -int UHCsgUtils::bspNodeToFPoly -( - UModel* Model, - int32 iNode, - FPoly* EdPoly -) -{ - FPoly MasterEdPoly; - - FBspNode &Node = Model->Nodes[iNode]; - FBspSurf &Poly = Model->Surfs[Node.iSurf]; - FVert *VertPool = &Model->Verts[ Node.iVertPool ]; - - EdPoly->Base = Model->Points [Poly.pBase]; - EdPoly->Normal = Model->Vectors[Poly.vNormal]; - - EdPoly->PolyFlags = Poly.PolyFlags & ~(PF_EdCut | PF_EdProcessed | PF_Selected | PF_Memorized); - EdPoly->iLinkSurf = Node.iSurf; - EdPoly->Material = Poly.Material; - - EdPoly->Actor = Poly.Actor; - EdPoly->iBrushPoly = Poly.iBrushPoly; - - if( polyFindMaster(Model,Node.iSurf,MasterEdPoly) ) - EdPoly->ItemName = MasterEdPoly.ItemName; - else - EdPoly->ItemName = NAME_None; - - EdPoly->TextureU = Model->Vectors[Poly.vTextureU]; - EdPoly->TextureV = Model->Vectors[Poly.vTextureV]; - - EdPoly->LightMapScale = Poly.LightMapScale; - - EdPoly->LightmassSettings = Model->LightmassSettings[Poly.iLightmassIndex]; - - EdPoly->Vertices.Empty(); - - for(int32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++) - { - new(EdPoly->Vertices) FVector(Model->Points[VertPool[VertexIndex].pVertex]); - } - - if(EdPoly->Vertices.Num() < 3) - { - EdPoly->Vertices.Empty(); - } - else - { - // Remove colinear points and identical points (which will appear - // if T-joints were eliminated). - EdPoly->RemoveColinears(); - } - - return EdPoly->Vertices.Num(); -} - -/*--------------------------------------------------------------------------------------- - World filtering. ----------------------------------------------------------------------------------------*/ - -// -// Filter all relevant world polys through the brush. -// -void UHCsgUtils::FilterWorldThroughBrush -( - UModel* Model, - UModel* Brush, - EBrushType BrushType, - ECsgOper CSGOper, - int32 iNode, - FSphere* BrushSphere, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - // Loop through all coplanars. - while( iNode != INDEX_NONE ) - { - // Get surface. - int32 iSurf = Model->Nodes[iNode].iSurf; - - // Skip new nodes and their children, which are guaranteed new. - if( Model->Nodes[iNode].NodeFlags & NF_IsNew ) - return; - - // Sphere reject. - int DoFront = 1, DoBack = 1; - if( BrushSphere ) - { - float Dist = Model->Nodes[iNode].Plane.PlaneDot( BrushSphere->Center ); - DoFront = (Dist >= -BrushSphere->W); - DoBack = (Dist <= +BrushSphere->W); - } - - // Process only polys that aren't empty. - FPoly TempEdPoly; - if( DoFront && DoBack && (GEditor->bspNodeToFPoly(Model,iNode,&TempEdPoly)>0) ) - { - TempEdPoly.Actor = Model->Surfs[iSurf].Actor; - TempEdPoly.iBrushPoly = Model->Surfs[iSurf].iBrushPoly; - - if( BrushType==Brush_Add || BrushType==Brush_Subtract ) - { - // Add and subtract work the same in this step. - GNode = iNode; - GModel = Model; - GDiscarded = 0; - GNumNodes = Model->Nodes.Num(); - - // Find last coplanar in chain. - GLastCoplanar = iNode; - while( Model->Nodes[GLastCoplanar].iPlane != INDEX_NONE ) - GLastCoplanar = Model->Nodes[GLastCoplanar].iPlane; - - // Do the filter operation. - BspFilterFPoly - ( - BrushType==Brush_Add ? &UHCsgUtils::AddWorldToBrushFunc : &UHCsgUtils::SubtractWorldToBrushFunc, - Brush, - &TempEdPoly, - BspPoints, - BspVectors - ); - - if( GDiscarded == 0 ) - { - // Get rid of all the fragments we added. - Model->Nodes[GLastCoplanar].iPlane = INDEX_NONE; - const bool bAllowShrinking = false; - Model->Nodes.RemoveAt( GNumNodes, Model->Nodes.Num()-GNumNodes, bAllowShrinking ); - } - else - { - // Tag original world poly for deletion; has been deleted or replaced by partial fragments. - if( GModel->Nodes[GNode].NumVertices ) - { - GModel->Nodes[GNode].NumVertices = 0; - } - } - } - else if( CSGOper == CSG_Intersect ) - { - BspFilterFPoly( &UHCsgUtils::IntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); - } - else if( CSGOper == CSG_Deintersect ) - { - BspFilterFPoly( &UHCsgUtils::DeIntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); - } - } - - // Now recurse to filter all of the world's children nodes. - if( DoFront && (Model->Nodes[iNode].iFront != INDEX_NONE)) FilterWorldThroughBrush - ( - Model, - Brush, - BrushType, - CSGOper, - Model->Nodes[iNode].iFront, - BrushSphere, - BspPoints, - BspVectors - ); - if( DoBack && (Model->Nodes[iNode].iBack != INDEX_NONE) ) FilterWorldThroughBrush - ( - Model, - Brush, - BrushType, - CSGOper, - Model->Nodes[iNode].iBack, - BrushSphere, - BspPoints, - BspVectors - ); - iNode = Model->Nodes[iNode].iPlane; - } -} - -void UHCsgUtils::RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic) -{ - if (!IsValid(Model)) - return; - - UHCsgUtils* CsgUtils = NewObject(); - int32 CsgErrors = 0; - - UHBspPointsGrid* BspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* BspVectors = UHBspPointsGrid::Create(1/16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - - // Empty the model out. - const int32 NumPoints = Model->Points.Num(); - const int32 NumNodes = Model->Nodes.Num(); - const int32 NumVerts = Model->Verts.Num(); - const int32 NumVectors = Model->Vectors.Num(); - const int32 NumSurfs = Model->Surfs.Num(); - - Model->Modify(); - Model->EmptyModel(1, 1); - - // Reserve arrays an eighth bigger than the previous allocation - Model->Points.Empty(NumPoints + NumPoints / 8); - Model->Nodes.Empty(NumNodes + NumNodes / 8); - Model->Verts.Empty(NumVerts + NumVerts / 8); - Model->Vectors.Empty(NumVectors + NumVectors / 8); - Model->Surfs.Empty(NumSurfs + NumSurfs / 8); - - // Build list of all static brushes, first structural brushes and portals - TArray StaticBrushes; - for (ABrush* Brush : Brushes) - { - if ((Brush && (Brush->IsStaticBrush() || bTreatMovableBrushesAsStatic) && !FActorEditorUtils::IsABuilderBrush(Brush)) && - (!(Brush->PolyFlags & PF_Semisolid) || (Brush->BrushType != Brush_Add) || (Brush->PolyFlags & PF_Portal))) - { - StaticBrushes.Add(Brush); - - // Treat portals as solids for cutting. - if (Brush->PolyFlags & PF_Portal) - { - Brush->PolyFlags = (Brush->PolyFlags & ~PF_Semisolid) | PF_NotSolid; - } - } - } - - // Next append all detail brushes - for (ABrush* Brush : Brushes) - { - if (Brush && Brush->IsStaticBrush() && !FActorEditorUtils::IsABuilderBrush(Brush) && - (Brush->PolyFlags & PF_Semisolid) && !(Brush->PolyFlags & PF_Portal) && (Brush->BrushType == Brush_Add)) - { - StaticBrushes.Add(Brush); - } - } - - // Build list of dynamic brushes - TArray DynamicBrushes; - if (!bTreatMovableBrushesAsStatic) - { - for (ABrush* DynamicBrush : Brushes) - { - if (DynamicBrush && DynamicBrush->Brush && !DynamicBrush->IsStaticBrush()) - { - DynamicBrushes.Add(DynamicBrush); - } - } - } - - FScopedSlowTask SlowTask(StaticBrushes.Num() + DynamicBrushes.Num()); - SlowTask.MakeDialogDelayed(3.0f); - - // Compose all static brushes - for (ABrush* Brush : StaticBrushes) - { - SlowTask.EnterProgressFrame(1); - Brush->Modify(); - int32 Errors = CsgUtils->ComposeBrushCSG(Brush, Model, Brush->PolyFlags, (EBrushType)Brush->BrushType, CSG_None, false, true, false, false, BspPoints, BspVectors); - if (Errors > 1) - CsgErrors += Errors - 1; - } - - // Rebuild dynamic brush BSP's (if they weren't handled earlier) - for (ABrush* DynamicBrush : DynamicBrushes) - { - SlowTask.EnterProgressFrame(1); - UHBspPointsGrid* LocalBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* LocalBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - - FHBSPOps::csgPrepMovingBrush(DynamicBrush, LocalBspPoints, LocalBspVectors); - } -} - - - -UModel* UHCsgUtils::BuildModelFromBrushes(TArray& Brushes) -{ - // Generally UModels are initialized using ABrush. Here we manually - // initialize using relevant parts from - UModel* OutModel = NewObject(); - OutModel->SetFlags(RF_Transactional); - OutModel->RootOutside = true; - OutModel->EmptyModel(1,1); - OutModel->UpdateVertices(); - - if (!IsValid(OutModel)) - return nullptr; - - // Can we combine the brushes without modifying the actors here ...? - - //FVector Location(0.0f, 0.0f, 0.0f); - //FRotator Rotation(0.0f, 0.0f, 0.0f); - //for(int32 BrushesIdx = 0; BrushesIdx < Brushes.Num(); ++BrushesIdx ) - //{ - // // Cache the location and rotation. - // Location = Brushes[BrushesIdx]->GetActorLocation(); - // Rotation = Brushes[BrushesIdx]->GetActorRotation(); - - - // // Leave the actor's rotation but move it to origin so the Static Mesh will generate correctly. - // Brushes[BrushesIdx]->TeleportTo(Location - InPivotLocation, Rotation, false, true); - //} - - RebuildModelFromBrushes(OutModel, Brushes, true); - //GEditor->bspBuildFPolys(OutModel, true, 0); - - //if (0 < ConversionTempModel->Polys->Element.Num()) - //{ - // UStaticMesh* NewMesh = CreateStaticMeshFromBrush(Pkg, ObjName, NULL, ConversionTempModel); - // NewActor = FActorFactoryAssetProxy::AddActorForAsset( NewMesh ); - - // NewActor->Modify(); - - // NewActor->InvalidateLightingCache(); - // NewActor->PostEditChange(); - // NewActor->PostEditMove( true ); - // NewActor->Modify(); - // ULayersSubsystem* LayersSubsystem = GetEditorSubsystem(); - // LayersSubsystem->InitializeNewActorLayers(NewActor); - - // // Teleport the new actor to the old location but not the old rotation. The static mesh is built to the rotation already. - // NewActor->TeleportTo(InPivotLocation, FRotator(0.0f, 0.0f, 0.0f), false, true); - - // // Destroy the old brushes. - // for( int32 BrushIdx = 0; BrushIdx < InBrushesToConvert.Num(); ++BrushIdx ) - // { - // LayersSubsystem->DisassociateActorFromLayers(InBrushesToConvert[BrushIdx]); - // GWorld->EditorDestroyActor( InBrushesToConvert[BrushIdx], true ); - // } - - // // Notify the asset registry - // FAssetRegistryModule::AssetCreated(NewMesh); - //} - - //ConversionTempModel->EmptyModel(1, 1); - //RebuildAlteredBSP(); - //RedrawLevelEditingViewports(); - - //return NewActor; - - return OutModel; -} - -int UHCsgUtils::ComposeBrushCSG -( - ABrush* Actor, - UModel* Model, - uint32 PolyFlags, - EBrushType BrushType, - ECsgOper CSGOper, - bool bBuildBounds, - bool bMergePolys, - bool bReplaceNULLMaterialRefs, - bool bShowProgressBar, /*=true*/ - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors -) -{ - uint32 NotPolyFlags = 0; - int32 NumPolysFromBrush=0,i,j,ReallyBig; - UModel* Brush = Actor->Brush; - int32 Errors = 0; - - // Make sure we're in an acceptable state. - if( !Brush ) - { - return 0; - } - - // Non-solid and semisolid stuff can only be added. - if( BrushType != Brush_Add ) - { - NotPolyFlags |= (PF_Semisolid | PF_NotSolid); - } - - TempModel->EmptyModel(1,1); - - // Update status. - ReallyBig = (Brush->Polys->Element.Num() > 200) && bShowProgressBar; - if( ReallyBig ) - { - FText Description = NSLOCTEXT("UnrealEd", "PerformingCSGOperation", "Performing CSG operation"); - - if (BrushType != Brush_MAX) - { - if (BrushType == Brush_Add) - { - Description = NSLOCTEXT("UnrealEd", "AddingBrushToWorld", "Adding brush to world"); - } - else if (BrushType == Brush_Subtract) - { - Description = NSLOCTEXT("UnrealEd", "SubtractingBrushFromWorld", "Subtracting brush from world"); - } - } - else if (CSGOper != CSG_None) - { - if (CSGOper == CSG_Intersect) - { - Description = NSLOCTEXT("UnrealEd", "IntersectingBrushWithWorld", "Intersecting brush with world"); - } - else if (CSGOper == CSG_Deintersect) - { - Description = NSLOCTEXT("UnrealEd", "DeintersectingBrushWithWorld", "Deintersecting brush with world"); - } - } - - GWarn->BeginSlowTask( Description, true ); - // Transform original brush poly into same coordinate system as world - // so Bsp filtering operations make sense. - GWarn->StatusUpdate(0, 0, NSLOCTEXT("UnrealEd", "Transforming", "Transforming")); - } - - - //UMaterialInterface* SelectedMaterialInstance = GEditor->GetSelectedObjects()->GetTop(); - UMaterialInterface* SelectedMaterialInstance = nullptr; - - const FVector Scale = Actor->GetActorScale(); - const FRotator Rotation = Actor->GetActorRotation(); - const FVector Location = Actor->GetActorLocation(); - - const bool bIsMirrored = (Scale.X * Scale.Y * Scale.Z < 0.0f); - - // Cache actor transform which is used for the geometry being built - Brush->OwnerLocationWhenLastBuilt = Location; - Brush->OwnerRotationWhenLastBuilt = Rotation; - Brush->OwnerScaleWhenLastBuilt = Scale; - Brush->bCachedOwnerTransformValid = true; - - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly& CurrentPoly = Brush->Polys->Element[i]; - - // Set texture the first time. - if ( bReplaceNULLMaterialRefs ) - { - UMaterialInterface*& PolyMat = CurrentPoly.Material; - if ( !PolyMat || PolyMat == UMaterial::GetDefaultMaterial(MD_Surface) ) - { - PolyMat = SelectedMaterialInstance; - } - } - - // Get the brush poly. - FPoly DestEdPoly = CurrentPoly; - check(CurrentPoly.iLinkPolys->Element.Num()); - - // Set its backward brush link. - DestEdPoly.Actor = Actor; - DestEdPoly.iBrushPoly = i; - - // Update its flags. - DestEdPoly.PolyFlags = (DestEdPoly.PolyFlags | PolyFlags) & ~NotPolyFlags; - - // Set its internal link. - if (DestEdPoly.iLink == INDEX_NONE) - { - DestEdPoly.iLink = i; - } - - // Transform it. - DestEdPoly.Scale( Scale ); - DestEdPoly.Rotate( Rotation ); - DestEdPoly.Transform( Location ); - - // Reverse winding and normal if the parent brush is mirrored - if (bIsMirrored) - { - DestEdPoly.Reverse(); - DestEdPoly.CalcNormal(); - } - - // Add poly to the temp model. - new(TempModel->Polys->Element)FPoly( DestEdPoly ); - } - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringBrush", "Filtering brush") ); - - // Pass the brush polys through the world Bsp. - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - // Empty the brush. - Brush->EmptyModel(1,1); - - // Intersect and deintersect. - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly EdPoly = TempModel->Polys->Element[i]; - GModel = Brush; - // TODO: iLink / iLinkSurf in EdPoly / TempModel->Polys->Element[i] ? - BspFilterFPoly( CSGOper==CSG_Intersect ? &UHCsgUtils::IntersectBrushWithWorldFunc : &UHCsgUtils::DeIntersectBrushWithWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); - } - NumPolysFromBrush = Brush->Polys->Element.Num(); - } - else - { - // Add and subtract. - TMap SurfaceIndexRemap; - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly EdPoly = TempModel->Polys->Element[i]; - - // Mark the polygon as non-cut so that it won't be harmed unless it must - // be split, and set iLink so that BspAddNode will know to add its information - // if a node is added based on this poly. - EdPoly.PolyFlags &= ~(PF_EdCut); - const int32* SurfaceIndexPtr = SurfaceIndexRemap.Find(EdPoly.iLink); - if (SurfaceIndexPtr == nullptr) - { - const int32 NewSurfaceIndex = Model->Surfs.Num(); - SurfaceIndexRemap.Add(EdPoly.iLink, NewSurfaceIndex); - EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = NewSurfaceIndex; - } - else - { - EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = *SurfaceIndexPtr; - } - - // Filter brush through the world. - BspFilterFPoly( BrushType==Brush_Add ? &UHCsgUtils::AddBrushToWorldFunc : &UHCsgUtils::SubtractBrushFromWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); - } - } - if( Model->Nodes.Num() && !(PolyFlags & (PF_NotSolid | PF_Semisolid)) ) - { - // Quickly build a Bsp for the brush, tending to minimize splits rather than balance - // the tree. We only need the cutting planes, though the entire Bsp struct (polys and - // all) is built. - - /*FHBspPointsGrid* LevelModelPointsGrid = FHBspPointsGrid::GBspPoints; - FHBspPointsGrid* LevelModelVectorsGrid = FHBspPointsGrid::GBspVectors;*/ - - // For the bspBuild call, temporarily create a new pair of BspPointsGrids for the TempModel. - UHBspPointsGrid* TempBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); - UHBspPointsGrid* TempBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); - /*FHBspPointsGrid::GBspPoints = BspPoints.Get(); - FHBspPointsGrid::GBspVectors = BspVectors.Get();*/ - - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "BuildingBSP", "Building BSP") ); - - FHBSPOps::bspBuild( TempModel, FHBSPOps::BSP_Lame, 0, 70, 1, 0, TempBspPoints, TempBspVectors ); - - // Reinstate the original BspPointsGrids used for building the level Model. - /*FHBspPointsGrid::GBspPoints = LevelModelPointsGrid; - FHBspPointsGrid::GBspVectors = LevelModelVectorsGrid;*/ - - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringWorld", "Filtering world") ); - GModel = Brush; - TempModel->BuildBound(); - - FSphere BrushSphere = TempModel->Bounds.GetSphere(); - FilterWorldThroughBrush( Model, TempModel, BrushType, CSGOper, 0, &BrushSphere, BspPoints, BspVectors); - } - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "AdjustingBrush", "Adjusting brush") ); - - // Link polys obtained from the original brush. - for( i=NumPolysFromBrush-1; i>=0; i-- ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - for( j=0; jiLink == Brush->Polys->Element[j].iLink ) - { - DestEdPoly->iLink = j; - break; - } - } - if( j >= i ) DestEdPoly->iLink = i; - } - - // Link polys obtained from the world. - for( i=Brush->Polys->Element.Num()-1; i>=NumPolysFromBrush; i-- ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - for( j=NumPolysFromBrush; jiLink == Brush->Polys->Element[j].iLink ) - { - DestEdPoly->iLink = j; - break; - } - } - if( j >= i ) DestEdPoly->iLink = i; - } - Brush->Linked = 1; - - // Detransform the obtained brush back into its original coordinate system. - for( i=0; iPolys->Element.Num(); i++ ) - { - FPoly *DestEdPoly = &Brush->Polys->Element[i]; - DestEdPoly->Transform(-Location); - DestEdPoly->Rotate(Rotation.GetInverse()); - DestEdPoly->Scale(FVector(1.0f) / Scale); - DestEdPoly->Fix(); - DestEdPoly->Actor = NULL; - DestEdPoly->iBrushPoly = i; - } - } - - if( BrushType==Brush_Add || BrushType==Brush_Subtract ) - { - // Clean up nodes, reset node flags. - bspCleanup( Model ); - - // Rebuild bounding volumes. - if( bBuildBounds ) - { - FHBSPOps::bspBuildBounds( Model ); - } - } - - Brush->NumUniqueVertices = TempModel->Points.Num(); - // Release TempModel. - TempModel->EmptyModel(1,1); - - // Merge coplanars if needed. - if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) - { - if( ReallyBig ) - { - GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "Merging", "Merging") ); - } - if( bMergePolys ) - { - bspMergeCoplanars( Brush, 1, 0 ); - } - } - if( ReallyBig ) - { - GWarn->EndSlowTask(); - } - - return 1 + FHBSPOps::GErrors; -} - -/*---------------------------------------------------------------------------- - EdPoly building and compacting. -----------------------------------------------------------------------------*/ - -// -// Trys to merge two polygons. If they can be merged, replaces Poly1 and emptys Poly2 -// and returns 1. Otherwise, returns 0. -// -int UHCsgUtils::TryToMerge( FPoly *Poly1, FPoly *Poly2 ) -{ - // Find one overlapping point. - int32 Start1=0, Start2=0; - for( Start1=0; Start1Vertices.Num(); Start1++ ) - for( Start2=0; Start2Vertices.Num(); Start2++ ) - if( FVector::PointsAreSame(Poly1->Vertices[Start1], Poly2->Vertices[Start2]) ) - goto FoundOverlap; - return 0; - FoundOverlap: - - // Wrap around trying to merge. - int32 End1 = Start1; - int32 End2 = Start2; - int32 Test1 = Start1+1; if (Test1>=Poly1->Vertices.Num()) Test1 = 0; - int32 Test2 = Start2-1; if (Test2<0) Test2 = Poly2->Vertices.Num()-1; - if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) - { - End1 = Test1; - Start2 = Test2; - } - else - { - Test1 = Start1-1; if (Test1<0) Test1=Poly1->Vertices.Num()-1; - Test2 = Start2+1; if (Test2>=Poly2->Vertices.Num()) Test2=0; - if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) - { - Start1 = Test1; - End2 = Test2; - } - else return 0; - } - - // Build a new edpoly containing both polygons merged. - FPoly NewPoly = *Poly1; - NewPoly.Vertices.Empty(); - int32 Vertex = End1; - for( int32 i=0; iVertices.Num(); i++ ) - { - new(NewPoly.Vertices) FVector(Poly1->Vertices[Vertex]); - if( ++Vertex >= Poly1->Vertices.Num() ) - Vertex=0; - } - Vertex = End2; - for( int32 i=0; i<(Poly2->Vertices.Num()-2); i++ ) - { - if( ++Vertex >= Poly2->Vertices.Num() ) - Vertex=0; - new(NewPoly.Vertices) FVector(Poly2->Vertices[Vertex]); - } - - // Remove colinear vertices and check convexity. - if( NewPoly.RemoveColinears() ) - { - *Poly1 = NewPoly; - Poly2->Vertices.Empty(); - return true; - } - else return 0; -} - -// -// Merge all polygons in coplanar list that can be merged convexly. -// -void UHCsgUtils::MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ) -{ - int32 MergeAgain = 1; - while( MergeAgain ) - { - MergeAgain = 0; - for( int32 i=0; iPolys->Element[PolyList[i]]; - if( Poly1.Vertices.Num() > 0 ) - { - for( int32 j=i+1; jPolys->Element[PolyList[j]]; - if( Poly2.Vertices.Num() > 0 ) - { - if( TryToMerge( &Poly1, &Poly2 ) ) - MergeAgain=1; - } - } - } - } - } -} - -void UHCsgUtils::bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ) -{ - int32 OriginalNum = Model->Polys->Element.Num(); - - // Mark all polys as unprocessed. - for( int32 i=0; iPolys->Element.Num(); i++ ) - Model->Polys->Element[i].PolyFlags &= ~PF_EdProcessed; - - // Find matching coplanars and merge them. - FMemMark Mark(FMemStack::Get()); - int32* PolyList = new(FMemStack::Get(),Model->Polys->Element.Num())int32; - int32 n=0; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - FPoly* EdPoly = &Model->Polys->Element[i]; - if( EdPoly->Vertices.Num()>0 && !(EdPoly->PolyFlags & PF_EdProcessed) ) - { - int32 PolyCount = 0; - PolyList[PolyCount++] = i; - EdPoly->PolyFlags |= PF_EdProcessed; - for( int32 j=i+1; jPolys->Element.Num(); j++ ) - { - FPoly* OtherPoly = &Model->Polys->Element[j]; - if( OtherPoly->iLink == EdPoly->iLink && OtherPoly->Vertices.Num() ) - { - float Dist = (OtherPoly->Vertices[0] - EdPoly->Vertices[0]) | EdPoly->Normal; - if - ( Dist>-0.001 - && Dist<0.001 - && (OtherPoly->Normal|EdPoly->Normal)>0.9999 - && (MergeDisparateTextures - || ( FVector::PointsAreNear(OtherPoly->TextureU,EdPoly->TextureU,THRESH_VECTORS_ARE_NEAR) - && FVector::PointsAreNear(OtherPoly->TextureV,EdPoly->TextureV,THRESH_VECTORS_ARE_NEAR) ) ) ) - { - OtherPoly->PolyFlags |= PF_EdProcessed; - PolyList[PolyCount++] = j; - } - } - } - if( PolyCount > 1 ) - { - MergeCoplanars( Model, PolyList, PolyCount ); - n++; - } - } - } -// UE_LOG(LogEditorBsp, Log, TEXT("Found %i coplanar sets in %i"), n, Model->Polys->Element.Num() ); - Mark.Pop(); - - // Get rid of empty EdPolys while remapping iLinks. - FMemMark Mark2(FMemStack::Get()); - int32 j=0; - int32* Remap = new(FMemStack::Get(),Model->Polys->Element.Num())int32; - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - if( Model->Polys->Element[i].Vertices.Num() ) - { - Remap[i] = j; - Model->Polys->Element[j] = Model->Polys->Element[i]; - j++; - } - } - Model->Polys->Element.RemoveAt( j, Model->Polys->Element.Num()-j ); - if( RemapLinks ) - { - for( int32 i=0; iPolys->Element.Num(); i++ ) - { - if (Model->Polys->Element[i].iLink != INDEX_NONE) - { - CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*Remap'. - Model->Polys->Element[i].iLink = Remap[Model->Polys->Element[i].iLink]; - } - } - } -// UE_LOG(LogEditorBsp, Log, TEXT("BspMergeCoplanars reduced %i->%i"), OriginalNum, Model->Polys->Element.Num() ); - Mark2.Pop(); -} - -bool UHCsgUtils::polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly) -{ - FBspSurf &Surf = InModel->Surfs[iSurf]; - if( !Surf.Actor || !Surf.Actor->Brush->Polys->Element.IsValidIndex(Surf.iBrushPoly) ) - { - return false; - } - else - { - Poly = Surf.Actor->Brush->Polys->Element[Surf.iBrushPoly]; - return true; - } -} - -void UHCsgUtils::CleanupNodes( UModel *Model, int32 iNode, int32 iParent ) -{ - FBspNode *Node = &Model->Nodes[iNode]; - - // Transactionally empty vertices of tag-for-empty nodes. - Node->NodeFlags &= ~(NF_IsNew | NF_IsFront | NF_IsBack); - - // Recursively clean up front, back, and plane nodes. - if( Node->iFront != INDEX_NONE ) CleanupNodes( Model, Node->iFront, iNode ); - if( Node->iBack != INDEX_NONE ) CleanupNodes( Model, Node->iBack , iNode ); - if( Node->iPlane != INDEX_NONE ) CleanupNodes( Model, Node->iPlane, iNode ); - - // Reload Node since the recusive call aliases it. - Node = &Model->Nodes[iNode]; - - // If this is an empty node with a coplanar, replace it with the coplanar. - if( Node->NumVertices==0 && Node->iPlane!=INDEX_NONE ) - { - FBspNode* PlaneNode = &Model->Nodes[ Node->iPlane ]; - - // Stick our front, back, and parent nodes on the coplanar. - if( (Node->Plane | PlaneNode->Plane) >= 0.0 ) - { - PlaneNode->iFront = Node->iFront; - PlaneNode->iBack = Node->iBack; - } - else - { - PlaneNode->iFront = Node->iBack; - PlaneNode->iBack = Node->iFront; - } - - if( iParent == INDEX_NONE ) - { - // This node is the root. - *Node = *PlaneNode; // Replace root. - PlaneNode->NumVertices = 0; // Mark as unused. - } - else - { - // This is a child node. - FBspNode *ParentNode = &Model->Nodes[iParent]; - - if ( ParentNode->iFront == iNode ) ParentNode->iFront = Node->iPlane; - else if ( ParentNode->iBack == iNode ) ParentNode->iBack = Node->iPlane; - else if ( ParentNode->iPlane == iNode ) ParentNode->iPlane = Node->iPlane; - else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); - } - } - else if( Node->NumVertices == 0 && ( Node->iFront==INDEX_NONE || Node->iBack==INDEX_NONE ) ) - { - // Delete empty nodes with no fronts or backs. - // Replace empty nodes with only fronts. - // Replace empty nodes with only backs. - int32 iReplacementNode; - if ( Node->iFront != INDEX_NONE ) iReplacementNode = Node->iFront; - else if( Node->iBack != INDEX_NONE ) iReplacementNode = Node->iBack; - else iReplacementNode = INDEX_NONE; - - if( iParent == INDEX_NONE ) - { - // Root. - if( iReplacementNode == INDEX_NONE ) - { - Model->Nodes.Empty(); - } - else - { - *Node = Model->Nodes[iReplacementNode]; - } - } - else - { - // Regular node. - FBspNode *ParentNode = &Model->Nodes[iParent]; - - if ( ParentNode->iFront == iNode ) ParentNode->iFront = iReplacementNode; - else if( ParentNode->iBack == iNode ) ParentNode->iBack = iReplacementNode; - else if( ParentNode->iPlane == iNode ) ParentNode->iPlane = iReplacementNode; - else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); - } - } -} - - -void UHCsgUtils::bspCleanup( UModel *Model ) -{ - if( Model->Nodes.Num() > 0 ) - CleanupNodes( Model, 0, INDEX_NONE ); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HCsgUtils.h" + +#include "Engine/Engine.h" +#include "Engine/Polys.h" +#include "Engine/Selection.h" +#include "Materials/Material.h" +#include "Misc/FeedbackContext.h" + +#include "ActorEditorUtils.h" +#include "Misc/ScopedSlowTask.h" + + +DEFINE_LOG_CATEGORY_STATIC(LogHCsgUtils, Log, All); + +#if WITH_EDITOR +#include "Editor.h" +#endif + +// Magic numbers. +#define THRESH_OPTGEOM_COPLANAR (0.25) /* Threshold for Bsp geometry optimization */ +#define THRESH_OPTGEOM_COSIDAL (0.25) /* Threshold for Bsp geometry optimization */ + + +UHCsgUtils::UHCsgUtils() +{ + // A TempModel is allocated for the HCsgUtils instance to avoid reallocation during inner loops. + TempModel = NewObject(); + TempModel->Initialize(nullptr, 1); + + /*GBspPoints = NewObject(); + GBspVectors = NewObject();*/ +} + + +/*---------------------------------------------------------------------------- + CSG leaf filter callbacks. +----------------------------------------------------------------------------*/ + +void UHCsgUtils::AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); + break; + case F_COSPATIAL_FACING_OUT: + if( !(EdPoly->PolyFlags & PF_Semisolid) ) + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_IN: + break; + } +} + +void UHCsgUtils::AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + // Only affect the world poly if it has been cut. + if( EdPoly->PolyFlags & PF_EdCut ) + FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors ); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_IN: + case F_COSPATIAL_FACING_OUT: + // Discard original poly. + GDiscarded++; + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + break; + } +} + +void UHCsgUtils::SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch (Filter) + { + case F_OUTSIDE: + case F_COSPATIAL_FACING_OUT: + case F_COSPATIAL_FACING_IN: + case F_COPLANAR_OUTSIDE: + break; + case F_COPLANAR_INSIDE: + case F_INSIDE: + EdPoly->Reverse(); + FHBSPOps::bspAddNode (Model,iNode,ENodePlace,NF_IsNew,EdPoly, BspPoints, BspVectors); // Add to Bsp back + EdPoly->Reverse(); + break; + } +} + +void UHCsgUtils::SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + // Only affect the world poly if it has been cut. + if( EdPoly->PolyFlags & PF_EdCut ) + FHBSPOps::bspAddNode( GModel, GLastCoplanar, FHBSPOps::NODE_Plane, NF_IsNew, EdPoly, BspPoints, BspVectors); + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + // Discard original poly. + GDiscarded++; + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + break; + } +} + +void UHCsgUtils::IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + case F_COSPATIAL_FACING_OUT: + // Ignore. + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + if( EdPoly->Fix()>=3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_IN: + // Ignore. + break; + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + if( EdPoly->Fix() >= 3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_INSIDE: + case F_COPLANAR_INSIDE: + case F_COSPATIAL_FACING_OUT: + case F_COSPATIAL_FACING_IN: + // Ignore. + break; + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + if( EdPoly->Fix()>=3 ) + new(GModel->Polys->Element)FPoly(*EdPoly); + break; + } +} + +void UHCsgUtils::DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, + EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + switch( Filter ) + { + case F_OUTSIDE: + case F_COPLANAR_OUTSIDE: + case F_COSPATIAL_FACING_OUT: + // Ignore. + break; + case F_COPLANAR_INSIDE: + case F_INSIDE: + case F_COSPATIAL_FACING_IN: + if( EdPoly->Fix() >= 3 ) + { + EdPoly->Reverse(); + new(GModel->Polys->Element)FPoly(*EdPoly); + EdPoly->Reverse(); + } + break; + } +} + +/*---------------------------------------------------------------------------- + CSG polygon filtering routine (calls the callbacks). +----------------------------------------------------------------------------*/ + +// +// Handle a piece of a polygon that was filtered to a leaf. +// +void UHCsgUtils::FilterLeaf +( + BspFilterFunc FilterFunc, + UModel* Model, + int32 iNode, + FPoly* EdPoly, + FCoplanarInfo CoplanarInfo, + int32 LeafOutside, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + EPolyNodeFilter FilterType; + + if( CoplanarInfo.iOriginalNode == INDEX_NONE ) + { + // Processing regular, non-coplanar polygons. + FilterType = LeafOutside ? F_OUTSIDE : F_INSIDE; + (this->*FilterFunc)( Model, iNode, EdPoly, FilterType, ENodePlace, BspPoints, BspVectors ); + } + else if( CoplanarInfo.ProcessingBack ) + { + // Finished filtering polygon through tree in back of parent coplanar. + DoneFilteringBack: + if ((!LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_INSIDE; + else if (( LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COPLANAR_OUTSIDE; + else if ((!LeafOutside) && ( CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_OUT; + else if (( LeafOutside) && (!CoplanarInfo.FrontLeafOutside)) FilterType = F_COSPATIAL_FACING_IN; + else + { + UE_LOG(LogHCsgUtils, Fatal, TEXT("FilterLeaf: Bad Locs")); + return; + } + (this->*FilterFunc)( Model, CoplanarInfo.iOriginalNode, EdPoly, FilterType, FHBSPOps::NODE_Plane, BspPoints, BspVectors ); + } + else + { + CoplanarInfo.FrontLeafOutside = LeafOutside; + + if( CoplanarInfo.iBackNode == INDEX_NONE ) + { + // Back tree is empty. + LeafOutside = CoplanarInfo.BackNodeOutside; + goto DoneFilteringBack; + } + else + { + // Call FilterEdPoly to filter through the back. This will result in + // another call to FilterLeaf with iNode = leaf this falls into in the + // back tree and EdPoly = the final EdPoly to insert. + CoplanarInfo.ProcessingBack=1; + FilterEdPoly( FilterFunc, Model, CoplanarInfo.iBackNode, EdPoly,CoplanarInfo, CoplanarInfo.BackNodeOutside, BspPoints, BspVectors ); + } + } +} + +// +// Filter an EdPoly through the Bsp recursively, calling FilterFunc +// for all chunks that fall into leaves. FCoplanarInfo is used to +// handle the tricky case of double-recursion for polys that must be +// filtered through a node's front, then filtered through the node's back, +// in order to handle coplanar CSG properly. +// +void UHCsgUtils::FilterEdPoly +( + BspFilterFunc FilterFunc, + UModel *Model, + int32 iNode, + FPoly *EdPoly, + FCoplanarInfo CoplanarInfo, + int32 Outside, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + int32 SplitResult,iOurFront,iOurBack; + int32 NewFrontOutside,NewBackOutside; + + FilterLoop: + + // Split em. + FPoly TempFrontEdPoly,TempBackEdPoly; + SplitResult = EdPoly->SplitWithPlane + ( + Model->Points [Model->Verts[Model->Nodes[iNode].iVertPool].pVertex], + Model->Vectors[Model->Surfs[Model->Nodes[iNode].iSurf].vNormal], + &TempFrontEdPoly, + &TempBackEdPoly, + 0 + ); + + // Process split results. + if( SplitResult == SP_Front ) + { + Front: + + FBspNode *Node = &Model->Nodes[iNode]; + Outside = Outside || Node->IsCsg(); + + if( Node->iFront == INDEX_NONE ) + { + FilterLeaf(FilterFunc,Model,iNode,EdPoly,CoplanarInfo,Outside,FHBSPOps::NODE_Front, BspPoints, BspVectors); + } + else + { + iNode = Node->iFront; + goto FilterLoop; + } + } + else if( SplitResult == SP_Back ) + { + FBspNode *Node = &Model->Nodes[iNode]; + Outside = Outside && !Node->IsCsg(); + + if( Node->iBack == INDEX_NONE ) + { + FilterLeaf( FilterFunc, Model, iNode, EdPoly, CoplanarInfo, Outside, FHBSPOps::NODE_Back, BspPoints, BspVectors); + } + else + { + iNode=Node->iBack; + goto FilterLoop; + } + } + else if( SplitResult == SP_Coplanar ) + { + if( CoplanarInfo.iOriginalNode != INDEX_NONE ) + { + // This will happen once in a blue moon when a polygon is barely outside the + // coplanar threshold and is split up into a new polygon that is + // is barely inside the coplanar threshold. To handle this, just classify + // it as front and it will be handled propery. + FHBSPOps::GErrors++; +// UE_LOG(LogEditorBsp, Warning, TEXT("FilterEdPoly: Encountered out-of-place coplanar") ); + goto Front; + } + CoplanarInfo.iOriginalNode = iNode; + CoplanarInfo.iBackNode = INDEX_NONE; + CoplanarInfo.ProcessingBack = 0; + CoplanarInfo.BackNodeOutside = Outside; + NewFrontOutside = Outside; + + // See whether Node's iFront or iBack points to the side of the tree on the front + // of this polygon (will be as expected if this polygon is facing the same + // way as first coplanar in link, otherwise opposite). + if( (FVector(Model->Nodes[iNode].Plane) | EdPoly->Normal) >= 0.0 ) + { + iOurFront = Model->Nodes[iNode].iFront; + iOurBack = Model->Nodes[iNode].iBack; + + if( Model->Nodes[iNode].IsCsg() ) + { + CoplanarInfo.BackNodeOutside = 0; + NewFrontOutside = 1; + } + } + else + { + iOurFront = Model->Nodes[iNode].iBack; + iOurBack = Model->Nodes[iNode].iFront; + + if( Model->Nodes[iNode].IsCsg() ) + { + CoplanarInfo.BackNodeOutside = 1; + NewFrontOutside = 0; + } + } + + // Process front and back. + if ((iOurFront==INDEX_NONE)&&(iOurBack==INDEX_NONE)) + { + // No front or back. + CoplanarInfo.ProcessingBack = 1; + CoplanarInfo.FrontLeafOutside = NewFrontOutside; + FilterLeaf + ( + FilterFunc, + Model, + iNode, + EdPoly, + CoplanarInfo, + CoplanarInfo.BackNodeOutside, + FHBSPOps::NODE_Plane, + BspPoints, + BspVectors + ); + } + else if( iOurFront==INDEX_NONE && iOurBack!=INDEX_NONE ) + { + // Back but no front. + CoplanarInfo.ProcessingBack = 1; + CoplanarInfo.iBackNode = iOurBack; + CoplanarInfo.FrontLeafOutside = NewFrontOutside; + + iNode = iOurBack; + Outside = CoplanarInfo.BackNodeOutside; + goto FilterLoop; + } + else + { + // Has a front and maybe a back. + + // Set iOurBack up to process back on next call to FilterLeaf, and loop + // to process front. Next call to FilterLeaf will set FrontLeafOutside. + CoplanarInfo.ProcessingBack = 0; + + // May be a node or may be INDEX_NONE. + CoplanarInfo.iBackNode = iOurBack; + + iNode = iOurFront; + Outside = NewFrontOutside; + goto FilterLoop; + } + } + else if( SplitResult == SP_Split ) + { + // Front half of split. + if( Model->Nodes[iNode].IsCsg() ) + { + NewFrontOutside = 1; + NewBackOutside = 0; + } + else + { + NewFrontOutside = Outside; + NewBackOutside = Outside; + } + + if( Model->Nodes[iNode].iFront==INDEX_NONE ) + { + FilterLeaf + ( + FilterFunc, + Model, + iNode, + &TempFrontEdPoly, + CoplanarInfo, + NewFrontOutside, + FHBSPOps::NODE_Front, + BspPoints, + BspVectors + ); + } + else + { + FilterEdPoly + ( + FilterFunc, + Model, + Model->Nodes[iNode].iFront, + &TempFrontEdPoly, + CoplanarInfo, + NewFrontOutside, + BspPoints, + BspVectors + ); + } + + // Back half of split. + if( Model->Nodes[iNode].iBack==INDEX_NONE ) + { + FilterLeaf + ( + FilterFunc, + Model, + iNode, + &TempBackEdPoly, + CoplanarInfo, + NewBackOutside, + FHBSPOps::NODE_Back, + BspPoints, + BspVectors + ); + } + else + { + FilterEdPoly + ( + FilterFunc, + Model, + Model->Nodes[iNode].iBack, + &TempBackEdPoly, + CoplanarInfo, + NewBackOutside, + BspPoints, + BspVectors + ); + } + } +} + +// +// Regular entry into FilterEdPoly (so higher-level callers don't have to +// deal with unnecessary info). Filters starting at root. +// +void UHCsgUtils::BspFilterFPoly( BspFilterFunc FilterFunc, UModel *Model, FPoly *EdPoly, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ) +{ + FCoplanarInfo StartingCoplanarInfo; + StartingCoplanarInfo.iOriginalNode = INDEX_NONE; + if( Model->Nodes.Num() == 0 ) + { + // If Bsp is empty, process at root. + (this->*FilterFunc)( Model, 0, EdPoly, Model->RootOutside ? F_OUTSIDE : F_INSIDE, FHBSPOps::NODE_Root, BspPoints, BspVectors ); + } + else + { + // Filter through Bsp. + FilterEdPoly( FilterFunc, Model, 0, EdPoly, StartingCoplanarInfo, Model->RootOutside, BspPoints, BspVectors ); + } +} + +int UHCsgUtils::bspNodeToFPoly +( + UModel* Model, + int32 iNode, + FPoly* EdPoly +) +{ + FPoly MasterEdPoly; + + FBspNode &Node = Model->Nodes[iNode]; + FBspSurf &Poly = Model->Surfs[Node.iSurf]; + FVert *VertPool = &Model->Verts[ Node.iVertPool ]; + + EdPoly->Base = Model->Points [Poly.pBase]; + EdPoly->Normal = Model->Vectors[Poly.vNormal]; + + EdPoly->PolyFlags = Poly.PolyFlags & ~(PF_EdCut | PF_EdProcessed | PF_Selected | PF_Memorized); + EdPoly->iLinkSurf = Node.iSurf; + EdPoly->Material = Poly.Material; + + EdPoly->Actor = Poly.Actor; + EdPoly->iBrushPoly = Poly.iBrushPoly; + + if( polyFindMaster(Model,Node.iSurf,MasterEdPoly) ) + EdPoly->ItemName = MasterEdPoly.ItemName; + else + EdPoly->ItemName = NAME_None; + + EdPoly->TextureU = Model->Vectors[Poly.vTextureU]; + EdPoly->TextureV = Model->Vectors[Poly.vTextureV]; + + EdPoly->LightMapScale = Poly.LightMapScale; + + EdPoly->LightmassSettings = Model->LightmassSettings[Poly.iLightmassIndex]; + + EdPoly->Vertices.Empty(); + + for(int32 VertexIndex = 0;VertexIndex < Node.NumVertices;VertexIndex++) + { + new(EdPoly->Vertices) FVector(Model->Points[VertPool[VertexIndex].pVertex]); + } + + if(EdPoly->Vertices.Num() < 3) + { + EdPoly->Vertices.Empty(); + } + else + { + // Remove colinear points and identical points (which will appear + // if T-joints were eliminated). + EdPoly->RemoveColinears(); + } + + return EdPoly->Vertices.Num(); +} + +/*--------------------------------------------------------------------------------------- + World filtering. +---------------------------------------------------------------------------------------*/ + +// +// Filter all relevant world polys through the brush. +// +void UHCsgUtils::FilterWorldThroughBrush +( + UModel* Model, + UModel* Brush, + EBrushType BrushType, + ECsgOper CSGOper, + int32 iNode, + FSphere* BrushSphere, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + // Loop through all coplanars. + while( iNode != INDEX_NONE ) + { + // Get surface. + int32 iSurf = Model->Nodes[iNode].iSurf; + + // Skip new nodes and their children, which are guaranteed new. + if( Model->Nodes[iNode].NodeFlags & NF_IsNew ) + return; + + // Sphere reject. + int DoFront = 1, DoBack = 1; + if( BrushSphere ) + { + float Dist = Model->Nodes[iNode].Plane.PlaneDot( BrushSphere->Center ); + DoFront = (Dist >= -BrushSphere->W); + DoBack = (Dist <= +BrushSphere->W); + } + + // Process only polys that aren't empty. + FPoly TempEdPoly; + if( DoFront && DoBack && (GEditor->bspNodeToFPoly(Model,iNode,&TempEdPoly)>0) ) + { + TempEdPoly.Actor = Model->Surfs[iSurf].Actor; + TempEdPoly.iBrushPoly = Model->Surfs[iSurf].iBrushPoly; + + if( BrushType==Brush_Add || BrushType==Brush_Subtract ) + { + // Add and subtract work the same in this step. + GNode = iNode; + GModel = Model; + GDiscarded = 0; + GNumNodes = Model->Nodes.Num(); + + // Find last coplanar in chain. + GLastCoplanar = iNode; + while( Model->Nodes[GLastCoplanar].iPlane != INDEX_NONE ) + GLastCoplanar = Model->Nodes[GLastCoplanar].iPlane; + + // Do the filter operation. + BspFilterFPoly + ( + BrushType==Brush_Add ? &UHCsgUtils::AddWorldToBrushFunc : &UHCsgUtils::SubtractWorldToBrushFunc, + Brush, + &TempEdPoly, + BspPoints, + BspVectors + ); + + if( GDiscarded == 0 ) + { + // Get rid of all the fragments we added. + Model->Nodes[GLastCoplanar].iPlane = INDEX_NONE; + const bool bAllowShrinking = false; + Model->Nodes.RemoveAt( GNumNodes, Model->Nodes.Num()-GNumNodes, bAllowShrinking ); + } + else + { + // Tag original world poly for deletion; has been deleted or replaced by partial fragments. + if( GModel->Nodes[GNode].NumVertices ) + { + GModel->Nodes[GNode].NumVertices = 0; + } + } + } + else if( CSGOper == CSG_Intersect ) + { + BspFilterFPoly( &UHCsgUtils::IntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); + } + else if( CSGOper == CSG_Deintersect ) + { + BspFilterFPoly( &UHCsgUtils::DeIntersectWorldWithBrushFunc, Brush, &TempEdPoly, BspPoints, BspVectors ); + } + } + + // Now recurse to filter all of the world's children nodes. + if( DoFront && (Model->Nodes[iNode].iFront != INDEX_NONE)) FilterWorldThroughBrush + ( + Model, + Brush, + BrushType, + CSGOper, + Model->Nodes[iNode].iFront, + BrushSphere, + BspPoints, + BspVectors + ); + if( DoBack && (Model->Nodes[iNode].iBack != INDEX_NONE) ) FilterWorldThroughBrush + ( + Model, + Brush, + BrushType, + CSGOper, + Model->Nodes[iNode].iBack, + BrushSphere, + BspPoints, + BspVectors + ); + iNode = Model->Nodes[iNode].iPlane; + } +} + +void UHCsgUtils::RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic) +{ + if (!IsValid(Model)) + return; + + UHCsgUtils* CsgUtils = NewObject(); + int32 CsgErrors = 0; + + UHBspPointsGrid* BspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* BspVectors = UHBspPointsGrid::Create(1/16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + + // Empty the model out. + const int32 NumPoints = Model->Points.Num(); + const int32 NumNodes = Model->Nodes.Num(); + const int32 NumVerts = Model->Verts.Num(); + const int32 NumVectors = Model->Vectors.Num(); + const int32 NumSurfs = Model->Surfs.Num(); + + Model->Modify(); + Model->EmptyModel(1, 1); + + // Reserve arrays an eighth bigger than the previous allocation + Model->Points.Empty(NumPoints + NumPoints / 8); + Model->Nodes.Empty(NumNodes + NumNodes / 8); + Model->Verts.Empty(NumVerts + NumVerts / 8); + Model->Vectors.Empty(NumVectors + NumVectors / 8); + Model->Surfs.Empty(NumSurfs + NumSurfs / 8); + + // Build list of all static brushes, first structural brushes and portals + TArray StaticBrushes; + for (ABrush* Brush : Brushes) + { + if ((Brush && (Brush->IsStaticBrush() || bTreatMovableBrushesAsStatic) && !FActorEditorUtils::IsABuilderBrush(Brush)) && + (!(Brush->PolyFlags & PF_Semisolid) || (Brush->BrushType != Brush_Add) || (Brush->PolyFlags & PF_Portal))) + { + StaticBrushes.Add(Brush); + + // Treat portals as solids for cutting. + if (Brush->PolyFlags & PF_Portal) + { + Brush->PolyFlags = (Brush->PolyFlags & ~PF_Semisolid) | PF_NotSolid; + } + } + } + + // Next append all detail brushes + for (ABrush* Brush : Brushes) + { + if (Brush && Brush->IsStaticBrush() && !FActorEditorUtils::IsABuilderBrush(Brush) && + (Brush->PolyFlags & PF_Semisolid) && !(Brush->PolyFlags & PF_Portal) && (Brush->BrushType == Brush_Add)) + { + StaticBrushes.Add(Brush); + } + } + + // Build list of dynamic brushes + TArray DynamicBrushes; + if (!bTreatMovableBrushesAsStatic) + { + for (ABrush* DynamicBrush : Brushes) + { + if (DynamicBrush && DynamicBrush->Brush && !DynamicBrush->IsStaticBrush()) + { + DynamicBrushes.Add(DynamicBrush); + } + } + } + + FScopedSlowTask SlowTask(StaticBrushes.Num() + DynamicBrushes.Num()); + SlowTask.MakeDialogDelayed(3.0f); + + // Compose all static brushes + for (ABrush* Brush : StaticBrushes) + { + SlowTask.EnterProgressFrame(1); + Brush->Modify(); + int32 Errors = CsgUtils->ComposeBrushCSG(Brush, Model, Brush->PolyFlags, (EBrushType)Brush->BrushType, CSG_None, false, true, false, false, BspPoints, BspVectors); + if (Errors > 1) + CsgErrors += Errors - 1; + } + + // Rebuild dynamic brush BSP's (if they weren't handled earlier) + for (ABrush* DynamicBrush : DynamicBrushes) + { + SlowTask.EnterProgressFrame(1); + UHBspPointsGrid* LocalBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* LocalBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + + FHBSPOps::csgPrepMovingBrush(DynamicBrush, LocalBspPoints, LocalBspVectors); + } +} + + + +UModel* UHCsgUtils::BuildModelFromBrushes(TArray& Brushes) +{ + // Generally UModels are initialized using ABrush. Here we manually + // initialize using relevant parts from + UModel* OutModel = NewObject(); + OutModel->SetFlags(RF_Transactional); + OutModel->RootOutside = true; + OutModel->EmptyModel(1,1); + OutModel->UpdateVertices(); + + if (!IsValid(OutModel)) + return nullptr; + + // Can we combine the brushes without modifying the actors here ...? + + //FVector Location(0.0f, 0.0f, 0.0f); + //FRotator Rotation(0.0f, 0.0f, 0.0f); + //for(int32 BrushesIdx = 0; BrushesIdx < Brushes.Num(); ++BrushesIdx ) + //{ + // // Cache the location and rotation. + // Location = Brushes[BrushesIdx]->GetActorLocation(); + // Rotation = Brushes[BrushesIdx]->GetActorRotation(); + + + // // Leave the actor's rotation but move it to origin so the Static Mesh will generate correctly. + // Brushes[BrushesIdx]->TeleportTo(Location - InPivotLocation, Rotation, false, true); + //} + + RebuildModelFromBrushes(OutModel, Brushes, true); + //GEditor->bspBuildFPolys(OutModel, true, 0); + + //if (0 < ConversionTempModel->Polys->Element.Num()) + //{ + // UStaticMesh* NewMesh = CreateStaticMeshFromBrush(Pkg, ObjName, NULL, ConversionTempModel); + // NewActor = FActorFactoryAssetProxy::AddActorForAsset( NewMesh ); + + // NewActor->Modify(); + + // NewActor->InvalidateLightingCache(); + // NewActor->PostEditChange(); + // NewActor->PostEditMove( true ); + // NewActor->Modify(); + // ULayersSubsystem* LayersSubsystem = GetEditorSubsystem(); + // LayersSubsystem->InitializeNewActorLayers(NewActor); + + // // Teleport the new actor to the old location but not the old rotation. The static mesh is built to the rotation already. + // NewActor->TeleportTo(InPivotLocation, FRotator(0.0f, 0.0f, 0.0f), false, true); + + // // Destroy the old brushes. + // for( int32 BrushIdx = 0; BrushIdx < InBrushesToConvert.Num(); ++BrushIdx ) + // { + // LayersSubsystem->DisassociateActorFromLayers(InBrushesToConvert[BrushIdx]); + // GWorld->EditorDestroyActor( InBrushesToConvert[BrushIdx], true ); + // } + + // // Notify the asset registry + // FAssetRegistryModule::AssetCreated(NewMesh); + //} + + //ConversionTempModel->EmptyModel(1, 1); + //RebuildAlteredBSP(); + //RedrawLevelEditingViewports(); + + //return NewActor; + + return OutModel; +} + +int UHCsgUtils::ComposeBrushCSG +( + ABrush* Actor, + UModel* Model, + uint32 PolyFlags, + EBrushType BrushType, + ECsgOper CSGOper, + bool bBuildBounds, + bool bMergePolys, + bool bReplaceNULLMaterialRefs, + bool bShowProgressBar, /*=true*/ + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors +) +{ + uint32 NotPolyFlags = 0; + int32 NumPolysFromBrush=0,i,j,ReallyBig; + UModel* Brush = Actor->Brush; + int32 Errors = 0; + + // Make sure we're in an acceptable state. + if( !Brush ) + { + return 0; + } + + // Non-solid and semisolid stuff can only be added. + if( BrushType != Brush_Add ) + { + NotPolyFlags |= (PF_Semisolid | PF_NotSolid); + } + + TempModel->EmptyModel(1,1); + + // Update status. + ReallyBig = (Brush->Polys->Element.Num() > 200) && bShowProgressBar; + if( ReallyBig ) + { + FText Description = NSLOCTEXT("UnrealEd", "PerformingCSGOperation", "Performing CSG operation"); + + if (BrushType != Brush_MAX) + { + if (BrushType == Brush_Add) + { + Description = NSLOCTEXT("UnrealEd", "AddingBrushToWorld", "Adding brush to world"); + } + else if (BrushType == Brush_Subtract) + { + Description = NSLOCTEXT("UnrealEd", "SubtractingBrushFromWorld", "Subtracting brush from world"); + } + } + else if (CSGOper != CSG_None) + { + if (CSGOper == CSG_Intersect) + { + Description = NSLOCTEXT("UnrealEd", "IntersectingBrushWithWorld", "Intersecting brush with world"); + } + else if (CSGOper == CSG_Deintersect) + { + Description = NSLOCTEXT("UnrealEd", "DeintersectingBrushWithWorld", "Deintersecting brush with world"); + } + } + + GWarn->BeginSlowTask( Description, true ); + // Transform original brush poly into same coordinate system as world + // so Bsp filtering operations make sense. + GWarn->StatusUpdate(0, 0, NSLOCTEXT("UnrealEd", "Transforming", "Transforming")); + } + + + //UMaterialInterface* SelectedMaterialInstance = GEditor->GetSelectedObjects()->GetTop(); + UMaterialInterface* SelectedMaterialInstance = nullptr; + + const FVector Scale = Actor->GetActorScale(); + const FRotator Rotation = Actor->GetActorRotation(); + const FVector Location = Actor->GetActorLocation(); + + const bool bIsMirrored = (Scale.X * Scale.Y * Scale.Z < 0.0f); + + // Cache actor transform which is used for the geometry being built + Brush->OwnerLocationWhenLastBuilt = Location; + Brush->OwnerRotationWhenLastBuilt = Rotation; + Brush->OwnerScaleWhenLastBuilt = Scale; + Brush->bCachedOwnerTransformValid = true; + + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly& CurrentPoly = Brush->Polys->Element[i]; + + // Set texture the first time. + if ( bReplaceNULLMaterialRefs ) + { + UMaterialInterface*& PolyMat = CurrentPoly.Material; + if ( !PolyMat || PolyMat == UMaterial::GetDefaultMaterial(MD_Surface) ) + { + PolyMat = SelectedMaterialInstance; + } + } + + // Get the brush poly. + FPoly DestEdPoly = CurrentPoly; + check(CurrentPoly.iLinkPolys->Element.Num()); + + // Set its backward brush link. + DestEdPoly.Actor = Actor; + DestEdPoly.iBrushPoly = i; + + // Update its flags. + DestEdPoly.PolyFlags = (DestEdPoly.PolyFlags | PolyFlags) & ~NotPolyFlags; + + // Set its internal link. + if (DestEdPoly.iLink == INDEX_NONE) + { + DestEdPoly.iLink = i; + } + + // Transform it. + DestEdPoly.Scale( Scale ); + DestEdPoly.Rotate( Rotation ); + DestEdPoly.Transform( Location ); + + // Reverse winding and normal if the parent brush is mirrored + if (bIsMirrored) + { + DestEdPoly.Reverse(); + DestEdPoly.CalcNormal(); + } + + // Add poly to the temp model. + new(TempModel->Polys->Element)FPoly( DestEdPoly ); + } + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringBrush", "Filtering brush") ); + + // Pass the brush polys through the world Bsp. + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + // Empty the brush. + Brush->EmptyModel(1,1); + + // Intersect and deintersect. + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly EdPoly = TempModel->Polys->Element[i]; + GModel = Brush; + // TODO: iLink / iLinkSurf in EdPoly / TempModel->Polys->Element[i] ? + BspFilterFPoly( CSGOper==CSG_Intersect ? &UHCsgUtils::IntersectBrushWithWorldFunc : &UHCsgUtils::DeIntersectBrushWithWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); + } + NumPolysFromBrush = Brush->Polys->Element.Num(); + } + else + { + // Add and subtract. + TMap SurfaceIndexRemap; + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly EdPoly = TempModel->Polys->Element[i]; + + // Mark the polygon as non-cut so that it won't be harmed unless it must + // be split, and set iLink so that BspAddNode will know to add its information + // if a node is added based on this poly. + EdPoly.PolyFlags &= ~(PF_EdCut); + const int32* SurfaceIndexPtr = SurfaceIndexRemap.Find(EdPoly.iLink); + if (SurfaceIndexPtr == nullptr) + { + const int32 NewSurfaceIndex = Model->Surfs.Num(); + SurfaceIndexRemap.Add(EdPoly.iLink, NewSurfaceIndex); + EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = NewSurfaceIndex; + } + else + { + EdPoly.iLinkSurf = TempModel->Polys->Element[i].iLinkSurf = *SurfaceIndexPtr; + } + + // Filter brush through the world. + BspFilterFPoly( BrushType==Brush_Add ? &UHCsgUtils::AddBrushToWorldFunc : &UHCsgUtils::SubtractBrushFromWorldFunc, Model, &EdPoly, BspPoints, BspVectors ); + } + } + if( Model->Nodes.Num() && !(PolyFlags & (PF_NotSolid | PF_Semisolid)) ) + { + // Quickly build a Bsp for the brush, tending to minimize splits rather than balance + // the tree. We only need the cutting planes, though the entire Bsp struct (polys and + // all) is built. + + /*FHBspPointsGrid* LevelModelPointsGrid = FHBspPointsGrid::GBspPoints; + FHBspPointsGrid* LevelModelVectorsGrid = FHBspPointsGrid::GBspVectors;*/ + + // For the bspBuild call, temporarily create a new pair of BspPointsGrids for the TempModel. + UHBspPointsGrid* TempBspPoints = UHBspPointsGrid::Create(50.0f, THRESH_POINTS_ARE_SAME); + UHBspPointsGrid* TempBspVectors = UHBspPointsGrid::Create(1 / 16.0f, FMath::Max(THRESH_NORMALS_ARE_SAME, THRESH_VECTORS_ARE_NEAR)); + /*FHBspPointsGrid::GBspPoints = BspPoints.Get(); + FHBspPointsGrid::GBspVectors = BspVectors.Get();*/ + + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "BuildingBSP", "Building BSP") ); + + FHBSPOps::bspBuild( TempModel, FHBSPOps::BSP_Lame, 0, 70, 1, 0, TempBspPoints, TempBspVectors ); + + // Reinstate the original BspPointsGrids used for building the level Model. + /*FHBspPointsGrid::GBspPoints = LevelModelPointsGrid; + FHBspPointsGrid::GBspVectors = LevelModelVectorsGrid;*/ + + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "FilteringWorld", "Filtering world") ); + GModel = Brush; + TempModel->BuildBound(); + + FSphere BrushSphere = TempModel->Bounds.GetSphere(); + FilterWorldThroughBrush( Model, TempModel, BrushType, CSGOper, 0, &BrushSphere, BspPoints, BspVectors); + } + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + if( ReallyBig ) GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "AdjustingBrush", "Adjusting brush") ); + + // Link polys obtained from the original brush. + for( i=NumPolysFromBrush-1; i>=0; i-- ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + for( j=0; jiLink == Brush->Polys->Element[j].iLink ) + { + DestEdPoly->iLink = j; + break; + } + } + if( j >= i ) DestEdPoly->iLink = i; + } + + // Link polys obtained from the world. + for( i=Brush->Polys->Element.Num()-1; i>=NumPolysFromBrush; i-- ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + for( j=NumPolysFromBrush; jiLink == Brush->Polys->Element[j].iLink ) + { + DestEdPoly->iLink = j; + break; + } + } + if( j >= i ) DestEdPoly->iLink = i; + } + Brush->Linked = 1; + + // Detransform the obtained brush back into its original coordinate system. + for( i=0; iPolys->Element.Num(); i++ ) + { + FPoly *DestEdPoly = &Brush->Polys->Element[i]; + DestEdPoly->Transform(-Location); + DestEdPoly->Rotate(Rotation.GetInverse()); + DestEdPoly->Scale(FVector(1.0f) / Scale); + DestEdPoly->Fix(); + DestEdPoly->Actor = NULL; + DestEdPoly->iBrushPoly = i; + } + } + + if( BrushType==Brush_Add || BrushType==Brush_Subtract ) + { + // Clean up nodes, reset node flags. + bspCleanup( Model ); + + // Rebuild bounding volumes. + if( bBuildBounds ) + { + FHBSPOps::bspBuildBounds( Model ); + } + } + + Brush->NumUniqueVertices = TempModel->Points.Num(); + // Release TempModel. + TempModel->EmptyModel(1,1); + + // Merge coplanars if needed. + if( CSGOper==CSG_Intersect || CSGOper==CSG_Deintersect ) + { + if( ReallyBig ) + { + GWarn->StatusUpdate( 0, 0, NSLOCTEXT("UnrealEd", "Merging", "Merging") ); + } + if( bMergePolys ) + { + bspMergeCoplanars( Brush, 1, 0 ); + } + } + if( ReallyBig ) + { + GWarn->EndSlowTask(); + } + + return 1 + FHBSPOps::GErrors; +} + +/*---------------------------------------------------------------------------- + EdPoly building and compacting. +----------------------------------------------------------------------------*/ + +// +// Trys to merge two polygons. If they can be merged, replaces Poly1 and emptys Poly2 +// and returns 1. Otherwise, returns 0. +// +int UHCsgUtils::TryToMerge( FPoly *Poly1, FPoly *Poly2 ) +{ + // Find one overlapping point. + int32 Start1=0, Start2=0; + for( Start1=0; Start1Vertices.Num(); Start1++ ) + for( Start2=0; Start2Vertices.Num(); Start2++ ) + if( FVector::PointsAreSame(Poly1->Vertices[Start1], Poly2->Vertices[Start2]) ) + goto FoundOverlap; + return 0; + FoundOverlap: + + // Wrap around trying to merge. + int32 End1 = Start1; + int32 End2 = Start2; + int32 Test1 = Start1+1; if (Test1>=Poly1->Vertices.Num()) Test1 = 0; + int32 Test2 = Start2-1; if (Test2<0) Test2 = Poly2->Vertices.Num()-1; + if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) + { + End1 = Test1; + Start2 = Test2; + } + else + { + Test1 = Start1-1; if (Test1<0) Test1=Poly1->Vertices.Num()-1; + Test2 = Start2+1; if (Test2>=Poly2->Vertices.Num()) Test2=0; + if( FVector::PointsAreSame(Poly1->Vertices[Test1],Poly2->Vertices[Test2]) ) + { + Start1 = Test1; + End2 = Test2; + } + else return 0; + } + + // Build a new edpoly containing both polygons merged. + FPoly NewPoly = *Poly1; + NewPoly.Vertices.Empty(); + int32 Vertex = End1; + for( int32 i=0; iVertices.Num(); i++ ) + { + new(NewPoly.Vertices) FVector(Poly1->Vertices[Vertex]); + if( ++Vertex >= Poly1->Vertices.Num() ) + Vertex=0; + } + Vertex = End2; + for( int32 i=0; i<(Poly2->Vertices.Num()-2); i++ ) + { + if( ++Vertex >= Poly2->Vertices.Num() ) + Vertex=0; + new(NewPoly.Vertices) FVector(Poly2->Vertices[Vertex]); + } + + // Remove colinear vertices and check convexity. + if( NewPoly.RemoveColinears() ) + { + *Poly1 = NewPoly; + Poly2->Vertices.Empty(); + return true; + } + else return 0; +} + +// +// Merge all polygons in coplanar list that can be merged convexly. +// +void UHCsgUtils::MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ) +{ + int32 MergeAgain = 1; + while( MergeAgain ) + { + MergeAgain = 0; + for( int32 i=0; iPolys->Element[PolyList[i]]; + if( Poly1.Vertices.Num() > 0 ) + { + for( int32 j=i+1; jPolys->Element[PolyList[j]]; + if( Poly2.Vertices.Num() > 0 ) + { + if( TryToMerge( &Poly1, &Poly2 ) ) + MergeAgain=1; + } + } + } + } + } +} + +void UHCsgUtils::bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ) +{ + int32 OriginalNum = Model->Polys->Element.Num(); + + // Mark all polys as unprocessed. + for( int32 i=0; iPolys->Element.Num(); i++ ) + Model->Polys->Element[i].PolyFlags &= ~PF_EdProcessed; + + // Find matching coplanars and merge them. + FMemMark Mark(FMemStack::Get()); + int32* PolyList = new(FMemStack::Get(),Model->Polys->Element.Num())int32; + int32 n=0; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + FPoly* EdPoly = &Model->Polys->Element[i]; + if( EdPoly->Vertices.Num()>0 && !(EdPoly->PolyFlags & PF_EdProcessed) ) + { + int32 PolyCount = 0; + PolyList[PolyCount++] = i; + EdPoly->PolyFlags |= PF_EdProcessed; + for( int32 j=i+1; jPolys->Element.Num(); j++ ) + { + FPoly* OtherPoly = &Model->Polys->Element[j]; + if( OtherPoly->iLink == EdPoly->iLink && OtherPoly->Vertices.Num() ) + { + float Dist = (OtherPoly->Vertices[0] - EdPoly->Vertices[0]) | EdPoly->Normal; + if + ( Dist>-0.001 + && Dist<0.001 + && (OtherPoly->Normal|EdPoly->Normal)>0.9999 + && (MergeDisparateTextures + || ( FVector::PointsAreNear(OtherPoly->TextureU,EdPoly->TextureU,THRESH_VECTORS_ARE_NEAR) + && FVector::PointsAreNear(OtherPoly->TextureV,EdPoly->TextureV,THRESH_VECTORS_ARE_NEAR) ) ) ) + { + OtherPoly->PolyFlags |= PF_EdProcessed; + PolyList[PolyCount++] = j; + } + } + } + if( PolyCount > 1 ) + { + MergeCoplanars( Model, PolyList, PolyCount ); + n++; + } + } + } +// UE_LOG(LogEditorBsp, Log, TEXT("Found %i coplanar sets in %i"), n, Model->Polys->Element.Num() ); + Mark.Pop(); + + // Get rid of empty EdPolys while remapping iLinks. + FMemMark Mark2(FMemStack::Get()); + int32 j=0; + int32* Remap = new(FMemStack::Get(),Model->Polys->Element.Num())int32; + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + if( Model->Polys->Element[i].Vertices.Num() ) + { + Remap[i] = j; + Model->Polys->Element[j] = Model->Polys->Element[i]; + j++; + } + } + Model->Polys->Element.RemoveAt( j, Model->Polys->Element.Num()-j ); + if( RemapLinks ) + { + for( int32 i=0; iPolys->Element.Num(); i++ ) + { + if (Model->Polys->Element[i].iLink != INDEX_NONE) + { + CA_SUPPRESS(6001); // warning C6001: Using uninitialized memory '*Remap'. + Model->Polys->Element[i].iLink = Remap[Model->Polys->Element[i].iLink]; + } + } + } +// UE_LOG(LogEditorBsp, Log, TEXT("BspMergeCoplanars reduced %i->%i"), OriginalNum, Model->Polys->Element.Num() ); + Mark2.Pop(); +} + +bool UHCsgUtils::polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly) +{ + FBspSurf &Surf = InModel->Surfs[iSurf]; + if( !Surf.Actor || !Surf.Actor->Brush->Polys->Element.IsValidIndex(Surf.iBrushPoly) ) + { + return false; + } + else + { + Poly = Surf.Actor->Brush->Polys->Element[Surf.iBrushPoly]; + return true; + } +} + +void UHCsgUtils::CleanupNodes( UModel *Model, int32 iNode, int32 iParent ) +{ + FBspNode *Node = &Model->Nodes[iNode]; + + // Transactionally empty vertices of tag-for-empty nodes. + Node->NodeFlags &= ~(NF_IsNew | NF_IsFront | NF_IsBack); + + // Recursively clean up front, back, and plane nodes. + if( Node->iFront != INDEX_NONE ) CleanupNodes( Model, Node->iFront, iNode ); + if( Node->iBack != INDEX_NONE ) CleanupNodes( Model, Node->iBack , iNode ); + if( Node->iPlane != INDEX_NONE ) CleanupNodes( Model, Node->iPlane, iNode ); + + // Reload Node since the recusive call aliases it. + Node = &Model->Nodes[iNode]; + + // If this is an empty node with a coplanar, replace it with the coplanar. + if( Node->NumVertices==0 && Node->iPlane!=INDEX_NONE ) + { + FBspNode* PlaneNode = &Model->Nodes[ Node->iPlane ]; + + // Stick our front, back, and parent nodes on the coplanar. + if( (Node->Plane | PlaneNode->Plane) >= 0.0 ) + { + PlaneNode->iFront = Node->iFront; + PlaneNode->iBack = Node->iBack; + } + else + { + PlaneNode->iFront = Node->iBack; + PlaneNode->iBack = Node->iFront; + } + + if( iParent == INDEX_NONE ) + { + // This node is the root. + *Node = *PlaneNode; // Replace root. + PlaneNode->NumVertices = 0; // Mark as unused. + } + else + { + // This is a child node. + FBspNode *ParentNode = &Model->Nodes[iParent]; + + if ( ParentNode->iFront == iNode ) ParentNode->iFront = Node->iPlane; + else if ( ParentNode->iBack == iNode ) ParentNode->iBack = Node->iPlane; + else if ( ParentNode->iPlane == iNode ) ParentNode->iPlane = Node->iPlane; + else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); + } + } + else if( Node->NumVertices == 0 && ( Node->iFront==INDEX_NONE || Node->iBack==INDEX_NONE ) ) + { + // Delete empty nodes with no fronts or backs. + // Replace empty nodes with only fronts. + // Replace empty nodes with only backs. + int32 iReplacementNode; + if ( Node->iFront != INDEX_NONE ) iReplacementNode = Node->iFront; + else if( Node->iBack != INDEX_NONE ) iReplacementNode = Node->iBack; + else iReplacementNode = INDEX_NONE; + + if( iParent == INDEX_NONE ) + { + // Root. + if( iReplacementNode == INDEX_NONE ) + { + Model->Nodes.Empty(); + } + else + { + *Node = Model->Nodes[iReplacementNode]; + } + } + else + { + // Regular node. + FBspNode *ParentNode = &Model->Nodes[iParent]; + + if ( ParentNode->iFront == iNode ) ParentNode->iFront = iReplacementNode; + else if( ParentNode->iBack == iNode ) ParentNode->iBack = iReplacementNode; + else if( ParentNode->iPlane == iNode ) ParentNode->iPlane = iReplacementNode; + else UE_LOG(LogHCsgUtils, Fatal, TEXT("CleanupNodes: Parent and child are unlinked")); + } + } +} + + +void UHCsgUtils::bspCleanup( UModel *Model ) +{ + if( Model->Nodes.Num() > 0 ) + CleanupNodes( Model, 0, INDEX_NONE ); +} + diff --git a/Source/HoudiniEngine/Private/HCsgUtils.h b/Source/HoudiniEngine/Private/HCsgUtils.h index c02fe3ed6..cc48c0b48 100644 --- a/Source/HoudiniEngine/Private/HCsgUtils.h +++ b/Source/HoudiniEngine/Private/HCsgUtils.h @@ -1,278 +1,278 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "HBSPOps.h" -#include "Engine/Brush.h" -#include "Model.h" - -#include "HCsgUtils.generated.h" - -//USTRUCT() -//struct FHCsgContext -//{ -// GENERATED_BODY() -// -// int32 Errors; -// -// -// UPROPERTY() -// class UModel* TempModel; -// -// UPROPERTY() -// class UModel* ConversionTempModel; -//}; - -// This HCsgUtils is one big fork of the codebase located UnrealEd/Private/EditorBsp.cpp. -// The main purpose was to remove parts of the code that store state in global/static variables as well -// as dependency on editor state (such as retrieving selected brushes). -UCLASS() -class HOUDINIENGINE_API UHCsgUtils : public UObject -{ - GENERATED_BODY() -public: - - UHCsgUtils(); - - /** - * Builds up a model from a set of brushes. Used by RebuildLevel. - * - * @param Model The model to be rebuilt. - * @param bSelectedBrushesOnly Use all brushes in the current level or just the selected ones?. - * @param bTreatMovableBrushesAsStatic Treat moveable brushes as static?. - */ - static void RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic); - - /** - * Converts passed in brushes into a single static mesh actor. - * Note: This replaces all the brushes with a single actor. This actor will not be attached to anything unless a single brush was converted. - * - * @param InStaticMeshPackageName The name to save the brushes to. - * @param InBrushesToConvert A list of brushes being converted. - * - * @return Returns the newly created actor with the newly created static mesh. - */ - static UModel* BuildModelFromBrushes(TArray& Brushes); - - /** - * Forked version of UEditorEngine::bspBrushCSG() from UnrealEd/Private/EditorBsp.cpp. - * - * Apply the appropriate CSG operation required in order to compose the brush actor onto the given model. - * - * @param Actor The brush actor to apply. - * @param Model The model to apply the CSG operation to; typically the world's model. - * @param PolyFlags PolyFlags to set on brush's polys. - * @param BrushType The type of brush. - * @param CSGOper The CSG operation to perform. - * @param bBuildBounds If true, updates bounding volumes on Model for CSG_Add or CSG_Subtract operations. - * @param bMergePolys If true, coplanar polygons are merged for CSG_Intersect or CSG_Deintersect operations. - * @param bReplaceNULLMaterialRefs If true, replace NULL material references with a reference to the GB-selected material. - * @param bShowProgressBar If true, display progress bar for complex brushes - * @return 0 if nothing happened, 1 if the operation was error-free, or 1+N if N CSG errors occurred. - */ - int ComposeBrushCSG( - ABrush* Actor, - UModel* Model, - uint32 PolyFlags, - EBrushType BrushType, - ECsgOper CSGOper, - bool bBuildBounds, - bool bMergePolys, - bool bReplaceNULLMaterialRefs, - bool bShowProgressBar, /*=true*/ - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - -protected: - // - // Status of filtered polygons: - // - enum EPolyNodeFilter - { - F_OUTSIDE = 0, // Leaf is an exterior leaf (visible to viewers). - F_INSIDE = 1, // Leaf is an interior leaf (non-visible, hidden behind backface). - F_COPLANAR_OUTSIDE = 2, // Poly is coplanar and in the exterior (visible to viewers). - F_COPLANAR_INSIDE = 3, // Poly is coplanar and inside (invisible to viewers). - F_COSPATIAL_FACING_IN = 4, // Poly is coplanar, cospatial, and facing in. - F_COSPATIAL_FACING_OUT = 5, // Poly is coplanar, cospatial, and facing out. - }; - - - // - // Information used by FilterEdPoly. - // - class FCoplanarInfo - { - public: - int32 iOriginalNode; - int32 iBackNode; - int BackNodeOutside; - int FrontLeafOutside; - int ProcessingBack; - }; - - // - // Generic filter function called by BspFilterEdPolys. A and B are pointers - // to any integers that your specific routine requires (or NULL if not needed). - // - typedef void (UHCsgUtils::*BspFilterFunc) - ( - UModel* Model, - int32 iNode, - FPoly* EdPoly, - EPolyNodeFilter Leaf, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // State shared between bspBrushCSG and AddWorldToBrushFunc. These are very - // tightly tied into the function AddWorldToBrush, not for general use. - // - int32 GDiscarded; // Number of polys discarded and not added. - int32 GNode; // Node AddBrushToWorld is adding to. - int32 GLastCoplanar; // Last coplanar beneath GNode at start of AddWorldToBrush. - int32 GNumNodes; // Number of Bsp nodes at start of AddWorldToBrush. - - UPROPERTY() - UModel* GModel; // Level map Model we're adding to. - - UPROPERTY() - class UModel* TempModel; - - //// Globals removed from FBspPointsGrid - //UPROPERTY() - //UHBspPointsGrid* GBspPoints; - - //UPROPERTY() - //UHBspPointsGrid* GBspVectors; - - /*struct BspFilterOp { - void Apply(UHCsgUtils* Obj, UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) {}; - };*/ - - // - // Handle a piece of a polygon that was filtered to a leaf. - // - void FilterLeaf( - BspFilterFunc FilterFunc, - UModel* Model, - int32 iNode, - FPoly* EdPoly, - FCoplanarInfo CoplanarInfo, - int32 LeafOutside, - FHBSPOps::ENodePlace ENodePlace, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // Function to filter an EdPoly through the Bsp, calling a callback - // function for all chunks that fall into leaves. - // - void FilterEdPoly - ( - BspFilterFunc FilterFunc, - UModel *Model, - int32 iNode, - FPoly *EdPoly, - FCoplanarInfo CoplanarInfo, - int32 Outside, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - // - // Regular entry into FilterEdPoly (so higher-level callers don't have to - // deal with unnecessary info). Filters starting at root. - // - void BspFilterFPoly - ( - BspFilterFunc FilterFunc, - UModel *Model, - FPoly *EdPoly, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - - int bspNodeToFPoly - ( - UModel* Model, - int32 iNode, - FPoly* EdPoly - ); - - - //---------------------------------------------------------------------------- - // World Filtering - //---------------------------------------------------------------------------- - - // - // Filter all relevant world polys through the brush. - // - void FilterWorldThroughBrush - ( - UModel* Model, - UModel* Brush, - EBrushType BrushType, - ECsgOper CSGOper, - int32 iNode, - FSphere* BrushSphere, - UHBspPointsGrid* BspPoints, - UHBspPointsGrid* BspVectors - ); - - //---------------------------------------------------------------------------- - // CSG leaf filter callbacks / operations. - // --------------------------------------------------------------------------- - void AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - void DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); - - - //---------------------------------------------------------------------------- - // Forked various functions located in: EditorBsp.cpp, EditorCsg.cpp - //---------------------------------------------------------------------------- - static int TryToMerge( FPoly *Poly1, FPoly *Poly2 ); - static void MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ); - void bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ); - bool polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly); - static void CleanupNodes( UModel *Model, int32 iNode, int32 iParent ); - void bspCleanup( UModel *Model ); - -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HBSPOps.h" +#include "Engine/Brush.h" +#include "Model.h" + +#include "HCsgUtils.generated.h" + +//USTRUCT() +//struct FHCsgContext +//{ +// GENERATED_BODY() +// +// int32 Errors; +// +// +// UPROPERTY() +// class UModel* TempModel; +// +// UPROPERTY() +// class UModel* ConversionTempModel; +//}; + +// This HCsgUtils is one big fork of the codebase located UnrealEd/Private/EditorBsp.cpp. +// The main purpose was to remove parts of the code that store state in global/static variables as well +// as dependency on editor state (such as retrieving selected brushes). +UCLASS() +class HOUDINIENGINE_API UHCsgUtils : public UObject +{ + GENERATED_BODY() +public: + + UHCsgUtils(); + + /** + * Builds up a model from a set of brushes. Used by RebuildLevel. + * + * @param Model The model to be rebuilt. + * @param bSelectedBrushesOnly Use all brushes in the current level or just the selected ones?. + * @param bTreatMovableBrushesAsStatic Treat moveable brushes as static?. + */ + static void RebuildModelFromBrushes(UModel* Model, TArray& Brushes, bool bTreatMovableBrushesAsStatic); + + /** + * Converts passed in brushes into a single static mesh actor. + * Note: This replaces all the brushes with a single actor. This actor will not be attached to anything unless a single brush was converted. + * + * @param InStaticMeshPackageName The name to save the brushes to. + * @param InBrushesToConvert A list of brushes being converted. + * + * @return Returns the newly created actor with the newly created static mesh. + */ + static UModel* BuildModelFromBrushes(TArray& Brushes); + + /** + * Forked version of UEditorEngine::bspBrushCSG() from UnrealEd/Private/EditorBsp.cpp. + * + * Apply the appropriate CSG operation required in order to compose the brush actor onto the given model. + * + * @param Actor The brush actor to apply. + * @param Model The model to apply the CSG operation to; typically the world's model. + * @param PolyFlags PolyFlags to set on brush's polys. + * @param BrushType The type of brush. + * @param CSGOper The CSG operation to perform. + * @param bBuildBounds If true, updates bounding volumes on Model for CSG_Add or CSG_Subtract operations. + * @param bMergePolys If true, coplanar polygons are merged for CSG_Intersect or CSG_Deintersect operations. + * @param bReplaceNULLMaterialRefs If true, replace NULL material references with a reference to the GB-selected material. + * @param bShowProgressBar If true, display progress bar for complex brushes + * @return 0 if nothing happened, 1 if the operation was error-free, or 1+N if N CSG errors occurred. + */ + int ComposeBrushCSG( + ABrush* Actor, + UModel* Model, + uint32 PolyFlags, + EBrushType BrushType, + ECsgOper CSGOper, + bool bBuildBounds, + bool bMergePolys, + bool bReplaceNULLMaterialRefs, + bool bShowProgressBar, /*=true*/ + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + +protected: + // + // Status of filtered polygons: + // + enum EPolyNodeFilter + { + F_OUTSIDE = 0, // Leaf is an exterior leaf (visible to viewers). + F_INSIDE = 1, // Leaf is an interior leaf (non-visible, hidden behind backface). + F_COPLANAR_OUTSIDE = 2, // Poly is coplanar and in the exterior (visible to viewers). + F_COPLANAR_INSIDE = 3, // Poly is coplanar and inside (invisible to viewers). + F_COSPATIAL_FACING_IN = 4, // Poly is coplanar, cospatial, and facing in. + F_COSPATIAL_FACING_OUT = 5, // Poly is coplanar, cospatial, and facing out. + }; + + + // + // Information used by FilterEdPoly. + // + class FCoplanarInfo + { + public: + int32 iOriginalNode; + int32 iBackNode; + int BackNodeOutside; + int FrontLeafOutside; + int ProcessingBack; + }; + + // + // Generic filter function called by BspFilterEdPolys. A and B are pointers + // to any integers that your specific routine requires (or NULL if not needed). + // + typedef void (UHCsgUtils::*BspFilterFunc) + ( + UModel* Model, + int32 iNode, + FPoly* EdPoly, + EPolyNodeFilter Leaf, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // State shared between bspBrushCSG and AddWorldToBrushFunc. These are very + // tightly tied into the function AddWorldToBrush, not for general use. + // + int32 GDiscarded; // Number of polys discarded and not added. + int32 GNode; // Node AddBrushToWorld is adding to. + int32 GLastCoplanar; // Last coplanar beneath GNode at start of AddWorldToBrush. + int32 GNumNodes; // Number of Bsp nodes at start of AddWorldToBrush. + + UPROPERTY() + UModel* GModel; // Level map Model we're adding to. + + UPROPERTY() + class UModel* TempModel; + + //// Globals removed from FBspPointsGrid + //UPROPERTY() + //UHBspPointsGrid* GBspPoints; + + //UPROPERTY() + //UHBspPointsGrid* GBspVectors; + + /*struct BspFilterOp { + void Apply(UHCsgUtils* Obj, UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, ENodePlace ENodePlace ) {}; + };*/ + + // + // Handle a piece of a polygon that was filtered to a leaf. + // + void FilterLeaf( + BspFilterFunc FilterFunc, + UModel* Model, + int32 iNode, + FPoly* EdPoly, + FCoplanarInfo CoplanarInfo, + int32 LeafOutside, + FHBSPOps::ENodePlace ENodePlace, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // Function to filter an EdPoly through the Bsp, calling a callback + // function for all chunks that fall into leaves. + // + void FilterEdPoly + ( + BspFilterFunc FilterFunc, + UModel *Model, + int32 iNode, + FPoly *EdPoly, + FCoplanarInfo CoplanarInfo, + int32 Outside, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + // + // Regular entry into FilterEdPoly (so higher-level callers don't have to + // deal with unnecessary info). Filters starting at root. + // + void BspFilterFPoly + ( + BspFilterFunc FilterFunc, + UModel *Model, + FPoly *EdPoly, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + + int bspNodeToFPoly + ( + UModel* Model, + int32 iNode, + FPoly* EdPoly + ); + + + //---------------------------------------------------------------------------- + // World Filtering + //---------------------------------------------------------------------------- + + // + // Filter all relevant world polys through the brush. + // + void FilterWorldThroughBrush + ( + UModel* Model, + UModel* Brush, + EBrushType BrushType, + ECsgOper CSGOper, + int32 iNode, + FSphere* BrushSphere, + UHBspPointsGrid* BspPoints, + UHBspPointsGrid* BspVectors + ); + + //---------------------------------------------------------------------------- + // CSG leaf filter callbacks / operations. + // --------------------------------------------------------------------------- + void AddBrushToWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void AddWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void SubtractBrushFromWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void SubtractWorldToBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void IntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void IntersectWorldWithBrushFunc( UModel *Model, int32 iNode, FPoly *EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void DeIntersectBrushWithWorldFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + void DeIntersectWorldWithBrushFunc( UModel* Model, int32 iNode, FPoly* EdPoly, EPolyNodeFilter Filter, FHBSPOps::ENodePlace ENodePlace, UHBspPointsGrid* BspPoints, UHBspPointsGrid* BspVectors ); + + + //---------------------------------------------------------------------------- + // Forked various functions located in: EditorBsp.cpp, EditorCsg.cpp + //---------------------------------------------------------------------------- + static int TryToMerge( FPoly *Poly1, FPoly *Poly2 ); + static void MergeCoplanars( UModel* Model, int32* PolyList, int32 PolyCount ); + void bspMergeCoplanars( UModel* Model, bool RemapLinks, bool MergeDisparateTextures ); + bool polyFindMaster(UModel* InModel, int32 iSurf, FPoly &Poly); + static void CleanupNodes( UModel *Model, int32 iNode, int32 iParent ); + void bspCleanup( UModel *Model ); + +}; + diff --git a/Source/HoudiniEngine/Private/HoudiniApi.cpp b/Source/HoudiniEngine/Private/HoudiniApi.cpp index b3bb9b89d..b024e09f6 100644 --- a/Source/HoudiniEngine/Private/HoudiniApi.cpp +++ b/Source/HoudiniEngine/Private/HoudiniApi.cpp @@ -1,3963 +1,3963 @@ -/* - * Copyright (c) <2021> 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. - * - * - * COMMENTS: - * This file is generated. Do not modify directly. - */ - -#include "HoudiniApi.h" -#include "HoudiniEnginePrivatePCH.h" - - -FHoudiniApi::AddAttributeFuncPtr -FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; - -FHoudiniApi::AddGroupFuncPtr -FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; - -FHoudiniApi::AssetInfo_CreateFuncPtr -FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; - -FHoudiniApi::AssetInfo_InitFuncPtr -FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; - -FHoudiniApi::AttributeInfo_CreateFuncPtr -FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; - -FHoudiniApi::AttributeInfo_InitFuncPtr -FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; - -FHoudiniApi::BindCustomImplementationFuncPtr -FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; - -FHoudiniApi::CancelPDGCookFuncPtr -FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; - -FHoudiniApi::CheckForSpecificErrorsFuncPtr -FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; - -FHoudiniApi::CleanupFuncPtr -FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; - -FHoudiniApi::ClearConnectionErrorFuncPtr -FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; - -FHoudiniApi::CloseSessionFuncPtr -FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; - -FHoudiniApi::CommitGeoFuncPtr -FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; - -FHoudiniApi::CommitWorkitemsFuncPtr -FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; - -FHoudiniApi::ComposeChildNodeListFuncPtr -FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; - -FHoudiniApi::ComposeNodeCookResultFuncPtr -FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; - -FHoudiniApi::ComposeObjectListFuncPtr -FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; - -FHoudiniApi::CompositorOptions_CreateFuncPtr -FHoudiniApi::CompositorOptions_Create = &FHoudiniApi::CompositorOptions_CreateEmptyStub; - -FHoudiniApi::CompositorOptions_InitFuncPtr -FHoudiniApi::CompositorOptions_Init = &FHoudiniApi::CompositorOptions_InitEmptyStub; - -FHoudiniApi::ConnectNodeInputFuncPtr -FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; - -FHoudiniApi::ConvertMatrixToEulerFuncPtr -FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; - -FHoudiniApi::ConvertMatrixToQuatFuncPtr -FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; - -FHoudiniApi::ConvertTransformFuncPtr -FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; - -FHoudiniApi::ConvertTransformEulerToMatrixFuncPtr -FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; - -FHoudiniApi::ConvertTransformQuatToMatrixFuncPtr -FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; - -FHoudiniApi::CookNodeFuncPtr -FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; - -FHoudiniApi::CookOptions_AreEqualFuncPtr -FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; - -FHoudiniApi::CookOptions_CreateFuncPtr -FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; - -FHoudiniApi::CookOptions_InitFuncPtr -FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; - -FHoudiniApi::CookPDGFuncPtr -FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; - -FHoudiniApi::CreateCustomSessionFuncPtr -FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; - -FHoudiniApi::CreateHeightFieldInputFuncPtr -FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; - -FHoudiniApi::CreateHeightfieldInputVolumeNodeFuncPtr -FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; - -FHoudiniApi::CreateInProcessSessionFuncPtr -FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; - -FHoudiniApi::CreateInputNodeFuncPtr -FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; - -FHoudiniApi::CreateNodeFuncPtr -FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; - -FHoudiniApi::CreateThriftNamedPipeSessionFuncPtr -FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; - -FHoudiniApi::CreateThriftSocketSessionFuncPtr -FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; - -FHoudiniApi::CreateWorkitemFuncPtr -FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; - -FHoudiniApi::CurveInfo_CreateFuncPtr -FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; - -FHoudiniApi::CurveInfo_InitFuncPtr -FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; - -FHoudiniApi::DeleteAttributeFuncPtr -FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; - -FHoudiniApi::DeleteGroupFuncPtr -FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; - -FHoudiniApi::DeleteNodeFuncPtr -FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; - -FHoudiniApi::DirtyPDGNodeFuncPtr -FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; - -FHoudiniApi::DisconnectNodeInputFuncPtr -FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; - -FHoudiniApi::DisconnectNodeOutputsAtFuncPtr -FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; - -FHoudiniApi::ExtractImageToFileFuncPtr -FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; - -FHoudiniApi::ExtractImageToMemoryFuncPtr -FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; - -FHoudiniApi::GeoInfo_CreateFuncPtr -FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; - -FHoudiniApi::GeoInfo_GetGroupCountByTypeFuncPtr -FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; - -FHoudiniApi::GeoInfo_InitFuncPtr -FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; - -FHoudiniApi::GetActiveCacheCountFuncPtr -FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; - -FHoudiniApi::GetActiveCacheNamesFuncPtr -FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmCountsFuncPtr -FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmInfosFuncPtr -FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; - -FHoudiniApi::GetAssetDefinitionParmValuesFuncPtr -FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; - -FHoudiniApi::GetAssetInfoFuncPtr -FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; - -FHoudiniApi::GetAttributeFloat64ArrayDataFuncPtr -FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeFloat64DataFuncPtr -FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; - -FHoudiniApi::GetAttributeFloatArrayDataFuncPtr -FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; - -FHoudiniApi::GetAttributeFloatDataFuncPtr -FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; - -FHoudiniApi::GetAttributeInfoFuncPtr -FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; - -FHoudiniApi::GetAttributeInt16ArrayDataFuncPtr -FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeInt16DataFuncPtr -FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; - -FHoudiniApi::GetAttributeInt64ArrayDataFuncPtr -FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeInt64DataFuncPtr -FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; - -FHoudiniApi::GetAttributeInt8ArrayDataFuncPtr -FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeInt8DataFuncPtr -FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; - -FHoudiniApi::GetAttributeIntArrayDataFuncPtr -FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; - -FHoudiniApi::GetAttributeIntDataFuncPtr -FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; - -FHoudiniApi::GetAttributeNamesFuncPtr -FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; - -FHoudiniApi::GetAttributeStringArrayDataFuncPtr -FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; - -FHoudiniApi::GetAttributeStringDataFuncPtr -FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; - -FHoudiniApi::GetAttributeUInt8ArrayDataFuncPtr -FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; - -FHoudiniApi::GetAttributeUInt8DataFuncPtr -FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; - -FHoudiniApi::GetAvailableAssetCountFuncPtr -FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; - -FHoudiniApi::GetAvailableAssetsFuncPtr -FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; - -FHoudiniApi::GetBoxInfoFuncPtr -FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; - -FHoudiniApi::GetCachePropertyFuncPtr -FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; - -FHoudiniApi::GetComposedChildNodeListFuncPtr -FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; - -FHoudiniApi::GetComposedNodeCookResultFuncPtr -FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; - -FHoudiniApi::GetComposedObjectListFuncPtr -FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; - -FHoudiniApi::GetComposedObjectTransformsFuncPtr -FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; - -FHoudiniApi::GetCompositorOptionsFuncPtr -FHoudiniApi::GetCompositorOptions = &FHoudiniApi::GetCompositorOptionsEmptyStub; - -FHoudiniApi::GetConnectionErrorFuncPtr -FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; - -FHoudiniApi::GetConnectionErrorLengthFuncPtr -FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; - -FHoudiniApi::GetCookingCurrentCountFuncPtr -FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; - -FHoudiniApi::GetCookingTotalCountFuncPtr -FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; - -FHoudiniApi::GetCurveCountsFuncPtr -FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; - -FHoudiniApi::GetCurveInfoFuncPtr -FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; - -FHoudiniApi::GetCurveKnotsFuncPtr -FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; - -FHoudiniApi::GetCurveOrdersFuncPtr -FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; - -FHoudiniApi::GetDisplayGeoInfoFuncPtr -FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; - -FHoudiniApi::GetEdgeCountOfEdgeGroupFuncPtr -FHoudiniApi::GetEdgeCountOfEdgeGroup = &FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub; - -FHoudiniApi::GetEnvIntFuncPtr -FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; - -FHoudiniApi::GetFaceCountsFuncPtr -FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; - -FHoudiniApi::GetFirstVolumeTileFuncPtr -FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; - -FHoudiniApi::GetGeoInfoFuncPtr -FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; - -FHoudiniApi::GetGeoSizeFuncPtr -FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; - -FHoudiniApi::GetGroupCountOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetGroupMembershipFuncPtr -FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; - -FHoudiniApi::GetGroupMembershipOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetGroupNamesFuncPtr -FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; - -FHoudiniApi::GetGroupNamesOnPackedInstancePartFuncPtr -FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; - -FHoudiniApi::GetHIPFileNodeCountFuncPtr -FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; - -FHoudiniApi::GetHIPFileNodeIdsFuncPtr -FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; - -FHoudiniApi::GetHandleBindingInfoFuncPtr -FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; - -FHoudiniApi::GetHandleInfoFuncPtr -FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; - -FHoudiniApi::GetHeightFieldDataFuncPtr -FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; - -FHoudiniApi::GetImageFilePathFuncPtr -FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; - -FHoudiniApi::GetImageInfoFuncPtr -FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; - -FHoudiniApi::GetImageMemoryBufferFuncPtr -FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; - -FHoudiniApi::GetImagePlaneCountFuncPtr -FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; - -FHoudiniApi::GetImagePlanesFuncPtr -FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; - -FHoudiniApi::GetInstanceTransformsOnPartFuncPtr -FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; - -FHoudiniApi::GetInstancedObjectIdsFuncPtr -FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; - -FHoudiniApi::GetInstancedPartIdsFuncPtr -FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; - -FHoudiniApi::GetInstancerPartTransformsFuncPtr -FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; - -FHoudiniApi::GetManagerNodeIdFuncPtr -FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; - -FHoudiniApi::GetMaterialInfoFuncPtr -FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; - -FHoudiniApi::GetMaterialNodeIdsOnFacesFuncPtr -FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; - -FHoudiniApi::GetNextVolumeTileFuncPtr -FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; - -FHoudiniApi::GetNodeInfoFuncPtr -FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; - -FHoudiniApi::GetNodeInputNameFuncPtr -FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; - -FHoudiniApi::GetNodeOutputNameFuncPtr -FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; - -FHoudiniApi::GetNodePathFuncPtr -FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; - -FHoudiniApi::GetNumWorkitemsFuncPtr -FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; - -FHoudiniApi::GetObjectInfoFuncPtr -FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; - -FHoudiniApi::GetObjectTransformFuncPtr -FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; - -FHoudiniApi::GetOutputGeoCountFuncPtr -FHoudiniApi::GetOutputGeoCount = &FHoudiniApi::GetOutputGeoCountEmptyStub; - -FHoudiniApi::GetOutputGeoInfosFuncPtr -FHoudiniApi::GetOutputGeoInfos = &FHoudiniApi::GetOutputGeoInfosEmptyStub; - -FHoudiniApi::GetOutputNodeIdFuncPtr -FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; - -FHoudiniApi::GetPDGEventsFuncPtr -FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; - -FHoudiniApi::GetPDGGraphContextIdFuncPtr -FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; - -FHoudiniApi::GetPDGGraphContextsFuncPtr -FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; - -FHoudiniApi::GetPDGStateFuncPtr -FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; - -FHoudiniApi::GetParametersFuncPtr -FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; - -FHoudiniApi::GetParmChoiceListsFuncPtr -FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; - -FHoudiniApi::GetParmExpressionFuncPtr -FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; - -FHoudiniApi::GetParmFileFuncPtr -FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; - -FHoudiniApi::GetParmFloatValueFuncPtr -FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; - -FHoudiniApi::GetParmFloatValuesFuncPtr -FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; - -FHoudiniApi::GetParmIdFromNameFuncPtr -FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; - -FHoudiniApi::GetParmInfoFuncPtr -FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; - -FHoudiniApi::GetParmInfoFromNameFuncPtr -FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; - -FHoudiniApi::GetParmIntValueFuncPtr -FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; - -FHoudiniApi::GetParmIntValuesFuncPtr -FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; - -FHoudiniApi::GetParmNodeValueFuncPtr -FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; - -FHoudiniApi::GetParmStringValueFuncPtr -FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; - -FHoudiniApi::GetParmStringValuesFuncPtr -FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; - -FHoudiniApi::GetParmTagNameFuncPtr -FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; - -FHoudiniApi::GetParmTagValueFuncPtr -FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; - -FHoudiniApi::GetParmWithTagFuncPtr -FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; - -FHoudiniApi::GetPartInfoFuncPtr -FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; - -FHoudiniApi::GetPresetFuncPtr -FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; - -FHoudiniApi::GetPresetBufLengthFuncPtr -FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; - -FHoudiniApi::GetServerEnvIntFuncPtr -FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; - -FHoudiniApi::GetServerEnvStringFuncPtr -FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; - -FHoudiniApi::GetServerEnvVarCountFuncPtr -FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; - -FHoudiniApi::GetServerEnvVarListFuncPtr -FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; - -FHoudiniApi::GetSessionEnvIntFuncPtr -FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; - -FHoudiniApi::GetSessionSyncInfoFuncPtr -FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; - -FHoudiniApi::GetSphereInfoFuncPtr -FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; - -FHoudiniApi::GetStatusFuncPtr -FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; - -FHoudiniApi::GetStatusStringFuncPtr -FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; - -FHoudiniApi::GetStatusStringBufLengthFuncPtr -FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; - -FHoudiniApi::GetStringFuncPtr -FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; - -FHoudiniApi::GetStringBatchFuncPtr -FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; - -FHoudiniApi::GetStringBatchSizeFuncPtr -FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; - -FHoudiniApi::GetStringBufLengthFuncPtr -FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; - -FHoudiniApi::GetSupportedImageFileFormatCountFuncPtr -FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; - -FHoudiniApi::GetSupportedImageFileFormatsFuncPtr -FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; - -FHoudiniApi::GetTimeFuncPtr -FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; - -FHoudiniApi::GetTimelineOptionsFuncPtr -FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; - -FHoudiniApi::GetTotalCookCountFuncPtr -FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; - -FHoudiniApi::GetUseHoudiniTimeFuncPtr -FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; - -FHoudiniApi::GetVertexListFuncPtr -FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; - -FHoudiniApi::GetViewportFuncPtr -FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; - -FHoudiniApi::GetVolumeBoundsFuncPtr -FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; - -FHoudiniApi::GetVolumeInfoFuncPtr -FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; - -FHoudiniApi::GetVolumeTileFloatDataFuncPtr -FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; - -FHoudiniApi::GetVolumeTileIntDataFuncPtr -FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; - -FHoudiniApi::GetVolumeVisualInfoFuncPtr -FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; - -FHoudiniApi::GetVolumeVoxelFloatDataFuncPtr -FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; - -FHoudiniApi::GetVolumeVoxelIntDataFuncPtr -FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; - -FHoudiniApi::GetWorkitemDataLengthFuncPtr -FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; - -FHoudiniApi::GetWorkitemFloatDataFuncPtr -FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; - -FHoudiniApi::GetWorkitemInfoFuncPtr -FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; - -FHoudiniApi::GetWorkitemIntDataFuncPtr -FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; - -FHoudiniApi::GetWorkitemResultInfoFuncPtr -FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; - -FHoudiniApi::GetWorkitemStringDataFuncPtr -FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; - -FHoudiniApi::GetWorkitemsFuncPtr -FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; - -FHoudiniApi::HandleBindingInfo_CreateFuncPtr -FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; - -FHoudiniApi::HandleBindingInfo_InitFuncPtr -FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; - -FHoudiniApi::HandleInfo_CreateFuncPtr -FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; - -FHoudiniApi::HandleInfo_InitFuncPtr -FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; - -FHoudiniApi::ImageFileFormat_CreateFuncPtr -FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; - -FHoudiniApi::ImageFileFormat_InitFuncPtr -FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; - -FHoudiniApi::ImageInfo_CreateFuncPtr -FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; - -FHoudiniApi::ImageInfo_InitFuncPtr -FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; - -FHoudiniApi::InitializeFuncPtr -FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; - -FHoudiniApi::InsertMultiparmInstanceFuncPtr -FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; - -FHoudiniApi::InterruptFuncPtr -FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; - -FHoudiniApi::IsInitializedFuncPtr -FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; - -FHoudiniApi::IsNodeValidFuncPtr -FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; - -FHoudiniApi::IsSessionValidFuncPtr -FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; - -FHoudiniApi::Keyframe_CreateFuncPtr -FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; - -FHoudiniApi::Keyframe_InitFuncPtr -FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; - -FHoudiniApi::LoadAssetLibraryFromFileFuncPtr -FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; - -FHoudiniApi::LoadAssetLibraryFromMemoryFuncPtr -FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; - -FHoudiniApi::LoadGeoFromFileFuncPtr -FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; - -FHoudiniApi::LoadGeoFromMemoryFuncPtr -FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; - -FHoudiniApi::LoadHIPFileFuncPtr -FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; - -FHoudiniApi::LoadNodeFromFileFuncPtr -FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; - -FHoudiniApi::MaterialInfo_CreateFuncPtr -FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; - -FHoudiniApi::MaterialInfo_InitFuncPtr -FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; - -FHoudiniApi::MergeHIPFileFuncPtr -FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; - -FHoudiniApi::NodeInfo_CreateFuncPtr -FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; - -FHoudiniApi::NodeInfo_InitFuncPtr -FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; - -FHoudiniApi::ObjectInfo_CreateFuncPtr -FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; - -FHoudiniApi::ObjectInfo_InitFuncPtr -FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; - -FHoudiniApi::ParmChoiceInfo_CreateFuncPtr -FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; - -FHoudiniApi::ParmChoiceInfo_InitFuncPtr -FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; - -FHoudiniApi::ParmHasExpressionFuncPtr -FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; - -FHoudiniApi::ParmHasTagFuncPtr -FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; - -FHoudiniApi::ParmInfo_CreateFuncPtr -FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; - -FHoudiniApi::ParmInfo_GetFloatValueCountFuncPtr -FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; - -FHoudiniApi::ParmInfo_GetIntValueCountFuncPtr -FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; - -FHoudiniApi::ParmInfo_GetStringValueCountFuncPtr -FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; - -FHoudiniApi::ParmInfo_InitFuncPtr -FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; - -FHoudiniApi::ParmInfo_IsFloatFuncPtr -FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; - -FHoudiniApi::ParmInfo_IsIntFuncPtr -FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; - -FHoudiniApi::ParmInfo_IsNodeFuncPtr -FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; - -FHoudiniApi::ParmInfo_IsNonValueFuncPtr -FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; - -FHoudiniApi::ParmInfo_IsPathFuncPtr -FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; - -FHoudiniApi::ParmInfo_IsStringFuncPtr -FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; - -FHoudiniApi::PartInfo_CreateFuncPtr -FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; - -FHoudiniApi::PartInfo_GetAttributeCountByOwnerFuncPtr -FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; - -FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerFuncPtr -FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; - -FHoudiniApi::PartInfo_GetElementCountByGroupTypeFuncPtr -FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; - -FHoudiniApi::PartInfo_InitFuncPtr -FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; - -FHoudiniApi::PausePDGCookFuncPtr -FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; - -FHoudiniApi::PythonThreadInterpreterLockFuncPtr -FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; - -FHoudiniApi::QueryNodeInputFuncPtr -FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; - -FHoudiniApi::QueryNodeOutputConnectedCountFuncPtr -FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; - -FHoudiniApi::QueryNodeOutputConnectedNodesFuncPtr -FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; - -FHoudiniApi::RemoveCustomStringFuncPtr -FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; - -FHoudiniApi::RemoveMultiparmInstanceFuncPtr -FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; - -FHoudiniApi::RemoveParmExpressionFuncPtr -FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; - -FHoudiniApi::RenameNodeFuncPtr -FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; - -FHoudiniApi::RenderCOPToImageFuncPtr -FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; - -FHoudiniApi::RenderTextureToImageFuncPtr -FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; - -FHoudiniApi::ResetSimulationFuncPtr -FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; - -FHoudiniApi::RevertGeoFuncPtr -FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; - -FHoudiniApi::RevertParmToDefaultFuncPtr -FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; - -FHoudiniApi::RevertParmToDefaultsFuncPtr -FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; - -FHoudiniApi::SaveGeoToFileFuncPtr -FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; - -FHoudiniApi::SaveGeoToMemoryFuncPtr -FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; - -FHoudiniApi::SaveHIPFileFuncPtr -FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; - -FHoudiniApi::SaveNodeToFileFuncPtr -FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; - -FHoudiniApi::SessionSyncInfo_CreateFuncPtr -FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; - -FHoudiniApi::SetAnimCurveFuncPtr -FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; - -FHoudiniApi::SetAttributeFloat64DataFuncPtr -FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; - -FHoudiniApi::SetAttributeFloatDataFuncPtr -FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; - -FHoudiniApi::SetAttributeInt16DataFuncPtr -FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; - -FHoudiniApi::SetAttributeInt64DataFuncPtr -FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; - -FHoudiniApi::SetAttributeInt8DataFuncPtr -FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; - -FHoudiniApi::SetAttributeIntDataFuncPtr -FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; - -FHoudiniApi::SetAttributeStringDataFuncPtr -FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; - -FHoudiniApi::SetAttributeUInt8DataFuncPtr -FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; - -FHoudiniApi::SetCachePropertyFuncPtr -FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; - -FHoudiniApi::SetCompositorOptionsFuncPtr -FHoudiniApi::SetCompositorOptions = &FHoudiniApi::SetCompositorOptionsEmptyStub; - -FHoudiniApi::SetCurveCountsFuncPtr -FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; - -FHoudiniApi::SetCurveInfoFuncPtr -FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; - -FHoudiniApi::SetCurveKnotsFuncPtr -FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; - -FHoudiniApi::SetCurveOrdersFuncPtr -FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; - -FHoudiniApi::SetCustomStringFuncPtr -FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; - -FHoudiniApi::SetFaceCountsFuncPtr -FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; - -FHoudiniApi::SetGroupMembershipFuncPtr -FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; - -FHoudiniApi::SetHeightFieldDataFuncPtr -FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; - -FHoudiniApi::SetImageInfoFuncPtr -FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; - -FHoudiniApi::SetNodeDisplayFuncPtr -FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; - -FHoudiniApi::SetObjectTransformFuncPtr -FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; - -FHoudiniApi::SetParmExpressionFuncPtr -FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; - -FHoudiniApi::SetParmFloatValueFuncPtr -FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; - -FHoudiniApi::SetParmFloatValuesFuncPtr -FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; - -FHoudiniApi::SetParmIntValueFuncPtr -FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; - -FHoudiniApi::SetParmIntValuesFuncPtr -FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; - -FHoudiniApi::SetParmNodeValueFuncPtr -FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; - -FHoudiniApi::SetParmStringValueFuncPtr -FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; - -FHoudiniApi::SetPartInfoFuncPtr -FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; - -FHoudiniApi::SetPresetFuncPtr -FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; - -FHoudiniApi::SetServerEnvIntFuncPtr -FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; - -FHoudiniApi::SetServerEnvStringFuncPtr -FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; - -FHoudiniApi::SetSessionSyncFuncPtr -FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; - -FHoudiniApi::SetSessionSyncInfoFuncPtr -FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; - -FHoudiniApi::SetTimeFuncPtr -FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; - -FHoudiniApi::SetTimelineOptionsFuncPtr -FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; - -FHoudiniApi::SetTransformAnimCurveFuncPtr -FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; - -FHoudiniApi::SetUseHoudiniTimeFuncPtr -FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; - -FHoudiniApi::SetVertexListFuncPtr -FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; - -FHoudiniApi::SetViewportFuncPtr -FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; - -FHoudiniApi::SetVolumeInfoFuncPtr -FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; - -FHoudiniApi::SetVolumeTileFloatDataFuncPtr -FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; - -FHoudiniApi::SetVolumeTileIntDataFuncPtr -FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; - -FHoudiniApi::SetVolumeVoxelFloatDataFuncPtr -FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; - -FHoudiniApi::SetVolumeVoxelIntDataFuncPtr -FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; - -FHoudiniApi::SetWorkitemFloatDataFuncPtr -FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; - -FHoudiniApi::SetWorkitemIntDataFuncPtr -FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; - -FHoudiniApi::SetWorkitemStringDataFuncPtr -FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; - -FHoudiniApi::StartThriftNamedPipeServerFuncPtr -FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; - -FHoudiniApi::StartThriftSocketServerFuncPtr -FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; - -FHoudiniApi::ThriftServerOptions_CreateFuncPtr -FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; - -FHoudiniApi::ThriftServerOptions_InitFuncPtr -FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; - -FHoudiniApi::TimelineOptions_CreateFuncPtr -FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; - -FHoudiniApi::TimelineOptions_InitFuncPtr -FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; - -FHoudiniApi::TransformEuler_CreateFuncPtr -FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; - -FHoudiniApi::TransformEuler_InitFuncPtr -FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; - -FHoudiniApi::Transform_CreateFuncPtr -FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; - -FHoudiniApi::Transform_InitFuncPtr -FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; - -FHoudiniApi::Viewport_CreateFuncPtr -FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; - -FHoudiniApi::VolumeInfo_CreateFuncPtr -FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; - -FHoudiniApi::VolumeInfo_InitFuncPtr -FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; - -FHoudiniApi::VolumeTileInfo_CreateFuncPtr -FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; - -FHoudiniApi::VolumeTileInfo_InitFuncPtr -FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; - - -void -FHoudiniApi::InitializeHAPI(void* LibraryHandle) -{ - if(!LibraryHandle) return; - - FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute")); - FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup")); - FHoudiniApi::AssetInfo_Create = (AssetInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Create")); - FHoudiniApi::AssetInfo_Init = (AssetInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Init")); - FHoudiniApi::AttributeInfo_Create = (AttributeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Create")); - FHoudiniApi::AttributeInfo_Init = (AttributeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Init")); - FHoudiniApi::BindCustomImplementation = (BindCustomImplementationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_BindCustomImplementation")); - FHoudiniApi::CancelPDGCook = (CancelPDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CancelPDGCook")); - FHoudiniApi::CheckForSpecificErrors = (CheckForSpecificErrorsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CheckForSpecificErrors")); - FHoudiniApi::Cleanup = (CleanupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Cleanup")); - FHoudiniApi::ClearConnectionError = (ClearConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ClearConnectionError")); - FHoudiniApi::CloseSession = (CloseSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CloseSession")); - FHoudiniApi::CommitGeo = (CommitGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitGeo")); - FHoudiniApi::CommitWorkitems = (CommitWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitWorkitems")); - FHoudiniApi::ComposeChildNodeList = (ComposeChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeChildNodeList")); - FHoudiniApi::ComposeNodeCookResult = (ComposeNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeNodeCookResult")); - FHoudiniApi::ComposeObjectList = (ComposeObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeObjectList")); - FHoudiniApi::CompositorOptions_Create = (CompositorOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CompositorOptions_Create")); - FHoudiniApi::CompositorOptions_Init = (CompositorOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CompositorOptions_Init")); - FHoudiniApi::ConnectNodeInput = (ConnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConnectNodeInput")); - FHoudiniApi::ConvertMatrixToEuler = (ConvertMatrixToEulerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToEuler")); - FHoudiniApi::ConvertMatrixToQuat = (ConvertMatrixToQuatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToQuat")); - FHoudiniApi::ConvertTransform = (ConvertTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransform")); - FHoudiniApi::ConvertTransformEulerToMatrix = (ConvertTransformEulerToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformEulerToMatrix")); - FHoudiniApi::ConvertTransformQuatToMatrix = (ConvertTransformQuatToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformQuatToMatrix")); - FHoudiniApi::CookNode = (CookNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookNode")); - FHoudiniApi::CookOptions_AreEqual = (CookOptions_AreEqualFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_AreEqual")); - FHoudiniApi::CookOptions_Create = (CookOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Create")); - FHoudiniApi::CookOptions_Init = (CookOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Init")); - FHoudiniApi::CookPDG = (CookPDGFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookPDG")); - FHoudiniApi::CreateCustomSession = (CreateCustomSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateCustomSession")); - FHoudiniApi::CreateHeightFieldInput = (CreateHeightFieldInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightFieldInput")); - FHoudiniApi::CreateHeightfieldInputVolumeNode = (CreateHeightfieldInputVolumeNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightfieldInputVolumeNode")); - FHoudiniApi::CreateInProcessSession = (CreateInProcessSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInProcessSession")); - FHoudiniApi::CreateInputNode = (CreateInputNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInputNode")); - FHoudiniApi::CreateNode = (CreateNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateNode")); - FHoudiniApi::CreateThriftNamedPipeSession = (CreateThriftNamedPipeSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftNamedPipeSession")); - FHoudiniApi::CreateThriftSocketSession = (CreateThriftSocketSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftSocketSession")); - FHoudiniApi::CreateWorkitem = (CreateWorkitemFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateWorkitem")); - FHoudiniApi::CurveInfo_Create = (CurveInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Create")); - FHoudiniApi::CurveInfo_Init = (CurveInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Init")); - FHoudiniApi::DeleteAttribute = (DeleteAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteAttribute")); - FHoudiniApi::DeleteGroup = (DeleteGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteGroup")); - FHoudiniApi::DeleteNode = (DeleteNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteNode")); - FHoudiniApi::DirtyPDGNode = (DirtyPDGNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DirtyPDGNode")); - FHoudiniApi::DisconnectNodeInput = (DisconnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeInput")); - FHoudiniApi::DisconnectNodeOutputsAt = (DisconnectNodeOutputsAtFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeOutputsAt")); - FHoudiniApi::ExtractImageToFile = (ExtractImageToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToFile")); - FHoudiniApi::ExtractImageToMemory = (ExtractImageToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToMemory")); - FHoudiniApi::GeoInfo_Create = (GeoInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Create")); - FHoudiniApi::GeoInfo_GetGroupCountByType = (GeoInfo_GetGroupCountByTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_GetGroupCountByType")); - FHoudiniApi::GeoInfo_Init = (GeoInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Init")); - FHoudiniApi::GetActiveCacheCount = (GetActiveCacheCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheCount")); - FHoudiniApi::GetActiveCacheNames = (GetActiveCacheNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheNames")); - FHoudiniApi::GetAssetDefinitionParmCounts = (GetAssetDefinitionParmCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmCounts")); - FHoudiniApi::GetAssetDefinitionParmInfos = (GetAssetDefinitionParmInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmInfos")); - FHoudiniApi::GetAssetDefinitionParmValues = (GetAssetDefinitionParmValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmValues")); - FHoudiniApi::GetAssetInfo = (GetAssetInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetInfo")); - FHoudiniApi::GetAttributeFloat64ArrayData = (GetAttributeFloat64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64ArrayData")); - FHoudiniApi::GetAttributeFloat64Data = (GetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64Data")); - FHoudiniApi::GetAttributeFloatArrayData = (GetAttributeFloatArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatArrayData")); - FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); - FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); - FHoudiniApi::GetAttributeInt16ArrayData = (GetAttributeInt16ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16ArrayData")); - FHoudiniApi::GetAttributeInt16Data = (GetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16Data")); - FHoudiniApi::GetAttributeInt64ArrayData = (GetAttributeInt64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64ArrayData")); - FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); - FHoudiniApi::GetAttributeInt8ArrayData = (GetAttributeInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8ArrayData")); - FHoudiniApi::GetAttributeInt8Data = (GetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8Data")); - FHoudiniApi::GetAttributeIntArrayData = (GetAttributeIntArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntArrayData")); - FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); - FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); - FHoudiniApi::GetAttributeStringArrayData = (GetAttributeStringArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringArrayData")); - FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); - FHoudiniApi::GetAttributeUInt8ArrayData = (GetAttributeUInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8ArrayData")); - FHoudiniApi::GetAttributeUInt8Data = (GetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8Data")); - FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); - FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); - FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); - FHoudiniApi::GetCacheProperty = (GetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCacheProperty")); - FHoudiniApi::GetComposedChildNodeList = (GetComposedChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedChildNodeList")); - FHoudiniApi::GetComposedNodeCookResult = (GetComposedNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedNodeCookResult")); - FHoudiniApi::GetComposedObjectList = (GetComposedObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectList")); - FHoudiniApi::GetComposedObjectTransforms = (GetComposedObjectTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectTransforms")); - FHoudiniApi::GetCompositorOptions = (GetCompositorOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCompositorOptions")); - FHoudiniApi::GetConnectionError = (GetConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionError")); - FHoudiniApi::GetConnectionErrorLength = (GetConnectionErrorLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionErrorLength")); - FHoudiniApi::GetCookingCurrentCount = (GetCookingCurrentCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingCurrentCount")); - FHoudiniApi::GetCookingTotalCount = (GetCookingTotalCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingTotalCount")); - FHoudiniApi::GetCurveCounts = (GetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveCounts")); - FHoudiniApi::GetCurveInfo = (GetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveInfo")); - FHoudiniApi::GetCurveKnots = (GetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveKnots")); - FHoudiniApi::GetCurveOrders = (GetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveOrders")); - FHoudiniApi::GetDisplayGeoInfo = (GetDisplayGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetDisplayGeoInfo")); - FHoudiniApi::GetEdgeCountOfEdgeGroup = (GetEdgeCountOfEdgeGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEdgeCountOfEdgeGroup")); - FHoudiniApi::GetEnvInt = (GetEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEnvInt")); - FHoudiniApi::GetFaceCounts = (GetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFaceCounts")); - FHoudiniApi::GetFirstVolumeTile = (GetFirstVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFirstVolumeTile")); - FHoudiniApi::GetGeoInfo = (GetGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoInfo")); - FHoudiniApi::GetGeoSize = (GetGeoSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoSize")); - FHoudiniApi::GetGroupCountOnPackedInstancePart = (GetGroupCountOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupCountOnPackedInstancePart")); - FHoudiniApi::GetGroupMembership = (GetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembership")); - FHoudiniApi::GetGroupMembershipOnPackedInstancePart = (GetGroupMembershipOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembershipOnPackedInstancePart")); - FHoudiniApi::GetGroupNames = (GetGroupNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNames")); - FHoudiniApi::GetGroupNamesOnPackedInstancePart = (GetGroupNamesOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNamesOnPackedInstancePart")); - FHoudiniApi::GetHIPFileNodeCount = (GetHIPFileNodeCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeCount")); - FHoudiniApi::GetHIPFileNodeIds = (GetHIPFileNodeIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeIds")); - FHoudiniApi::GetHandleBindingInfo = (GetHandleBindingInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleBindingInfo")); - FHoudiniApi::GetHandleInfo = (GetHandleInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleInfo")); - FHoudiniApi::GetHeightFieldData = (GetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHeightFieldData")); - FHoudiniApi::GetImageFilePath = (GetImageFilePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageFilePath")); - FHoudiniApi::GetImageInfo = (GetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageInfo")); - FHoudiniApi::GetImageMemoryBuffer = (GetImageMemoryBufferFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageMemoryBuffer")); - FHoudiniApi::GetImagePlaneCount = (GetImagePlaneCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlaneCount")); - FHoudiniApi::GetImagePlanes = (GetImagePlanesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlanes")); - FHoudiniApi::GetInstanceTransformsOnPart = (GetInstanceTransformsOnPartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstanceTransformsOnPart")); - FHoudiniApi::GetInstancedObjectIds = (GetInstancedObjectIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedObjectIds")); - FHoudiniApi::GetInstancedPartIds = (GetInstancedPartIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedPartIds")); - FHoudiniApi::GetInstancerPartTransforms = (GetInstancerPartTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancerPartTransforms")); - FHoudiniApi::GetManagerNodeId = (GetManagerNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetManagerNodeId")); - FHoudiniApi::GetMaterialInfo = (GetMaterialInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialInfo")); - FHoudiniApi::GetMaterialNodeIdsOnFaces = (GetMaterialNodeIdsOnFacesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialNodeIdsOnFaces")); - FHoudiniApi::GetNextVolumeTile = (GetNextVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNextVolumeTile")); - FHoudiniApi::GetNodeInfo = (GetNodeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInfo")); - FHoudiniApi::GetNodeInputName = (GetNodeInputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInputName")); - FHoudiniApi::GetNodeOutputName = (GetNodeOutputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeOutputName")); - FHoudiniApi::GetNodePath = (GetNodePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodePath")); - FHoudiniApi::GetNumWorkitems = (GetNumWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNumWorkitems")); - FHoudiniApi::GetObjectInfo = (GetObjectInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectInfo")); - FHoudiniApi::GetObjectTransform = (GetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectTransform")); - FHoudiniApi::GetOutputGeoCount = (GetOutputGeoCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputGeoCount")); - FHoudiniApi::GetOutputGeoInfos = (GetOutputGeoInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputGeoInfos")); - FHoudiniApi::GetOutputNodeId = (GetOutputNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputNodeId")); - FHoudiniApi::GetPDGEvents = (GetPDGEventsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGEvents")); - FHoudiniApi::GetPDGGraphContextId = (GetPDGGraphContextIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContextId")); - FHoudiniApi::GetPDGGraphContexts = (GetPDGGraphContextsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContexts")); - FHoudiniApi::GetPDGState = (GetPDGStateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGState")); - FHoudiniApi::GetParameters = (GetParametersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParameters")); - FHoudiniApi::GetParmChoiceLists = (GetParmChoiceListsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmChoiceLists")); - FHoudiniApi::GetParmExpression = (GetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmExpression")); - FHoudiniApi::GetParmFile = (GetParmFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFile")); - FHoudiniApi::GetParmFloatValue = (GetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValue")); - FHoudiniApi::GetParmFloatValues = (GetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValues")); - FHoudiniApi::GetParmIdFromName = (GetParmIdFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIdFromName")); - FHoudiniApi::GetParmInfo = (GetParmInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfo")); - FHoudiniApi::GetParmInfoFromName = (GetParmInfoFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfoFromName")); - FHoudiniApi::GetParmIntValue = (GetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValue")); - FHoudiniApi::GetParmIntValues = (GetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValues")); - FHoudiniApi::GetParmNodeValue = (GetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmNodeValue")); - FHoudiniApi::GetParmStringValue = (GetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValue")); - FHoudiniApi::GetParmStringValues = (GetParmStringValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValues")); - FHoudiniApi::GetParmTagName = (GetParmTagNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagName")); - FHoudiniApi::GetParmTagValue = (GetParmTagValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagValue")); - FHoudiniApi::GetParmWithTag = (GetParmWithTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmWithTag")); - FHoudiniApi::GetPartInfo = (GetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPartInfo")); - FHoudiniApi::GetPreset = (GetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPreset")); - FHoudiniApi::GetPresetBufLength = (GetPresetBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPresetBufLength")); - FHoudiniApi::GetServerEnvInt = (GetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvInt")); - FHoudiniApi::GetServerEnvString = (GetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvString")); - FHoudiniApi::GetServerEnvVarCount = (GetServerEnvVarCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarCount")); - FHoudiniApi::GetServerEnvVarList = (GetServerEnvVarListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarList")); - FHoudiniApi::GetSessionEnvInt = (GetSessionEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionEnvInt")); - FHoudiniApi::GetSessionSyncInfo = (GetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionSyncInfo")); - FHoudiniApi::GetSphereInfo = (GetSphereInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSphereInfo")); - FHoudiniApi::GetStatus = (GetStatusFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatus")); - FHoudiniApi::GetStatusString = (GetStatusStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusString")); - FHoudiniApi::GetStatusStringBufLength = (GetStatusStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusStringBufLength")); - FHoudiniApi::GetString = (GetStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetString")); - FHoudiniApi::GetStringBatch = (GetStringBatchFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatch")); - FHoudiniApi::GetStringBatchSize = (GetStringBatchSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatchSize")); - FHoudiniApi::GetStringBufLength = (GetStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBufLength")); - FHoudiniApi::GetSupportedImageFileFormatCount = (GetSupportedImageFileFormatCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormatCount")); - FHoudiniApi::GetSupportedImageFileFormats = (GetSupportedImageFileFormatsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormats")); - FHoudiniApi::GetTime = (GetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTime")); - FHoudiniApi::GetTimelineOptions = (GetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTimelineOptions")); - FHoudiniApi::GetTotalCookCount = (GetTotalCookCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTotalCookCount")); - FHoudiniApi::GetUseHoudiniTime = (GetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetUseHoudiniTime")); - FHoudiniApi::GetVertexList = (GetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVertexList")); - FHoudiniApi::GetViewport = (GetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetViewport")); - FHoudiniApi::GetVolumeBounds = (GetVolumeBoundsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeBounds")); - FHoudiniApi::GetVolumeInfo = (GetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeInfo")); - FHoudiniApi::GetVolumeTileFloatData = (GetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileFloatData")); - FHoudiniApi::GetVolumeTileIntData = (GetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileIntData")); - FHoudiniApi::GetVolumeVisualInfo = (GetVolumeVisualInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVisualInfo")); - FHoudiniApi::GetVolumeVoxelFloatData = (GetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelFloatData")); - FHoudiniApi::GetVolumeVoxelIntData = (GetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelIntData")); - FHoudiniApi::GetWorkitemDataLength = (GetWorkitemDataLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemDataLength")); - FHoudiniApi::GetWorkitemFloatData = (GetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemFloatData")); - FHoudiniApi::GetWorkitemInfo = (GetWorkitemInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemInfo")); - FHoudiniApi::GetWorkitemIntData = (GetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemIntData")); - FHoudiniApi::GetWorkitemResultInfo = (GetWorkitemResultInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemResultInfo")); - FHoudiniApi::GetWorkitemStringData = (GetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemStringData")); - FHoudiniApi::GetWorkitems = (GetWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitems")); - FHoudiniApi::HandleBindingInfo_Create = (HandleBindingInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Create")); - FHoudiniApi::HandleBindingInfo_Init = (HandleBindingInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Init")); - FHoudiniApi::HandleInfo_Create = (HandleInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Create")); - FHoudiniApi::HandleInfo_Init = (HandleInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Init")); - FHoudiniApi::ImageFileFormat_Create = (ImageFileFormat_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Create")); - FHoudiniApi::ImageFileFormat_Init = (ImageFileFormat_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Init")); - FHoudiniApi::ImageInfo_Create = (ImageInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Create")); - FHoudiniApi::ImageInfo_Init = (ImageInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Init")); - FHoudiniApi::Initialize = (InitializeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Initialize")); - FHoudiniApi::InsertMultiparmInstance = (InsertMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_InsertMultiparmInstance")); - FHoudiniApi::Interrupt = (InterruptFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Interrupt")); - FHoudiniApi::IsInitialized = (IsInitializedFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsInitialized")); - FHoudiniApi::IsNodeValid = (IsNodeValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsNodeValid")); - FHoudiniApi::IsSessionValid = (IsSessionValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsSessionValid")); - FHoudiniApi::Keyframe_Create = (Keyframe_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Create")); - FHoudiniApi::Keyframe_Init = (Keyframe_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Init")); - FHoudiniApi::LoadAssetLibraryFromFile = (LoadAssetLibraryFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromFile")); - FHoudiniApi::LoadAssetLibraryFromMemory = (LoadAssetLibraryFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromMemory")); - FHoudiniApi::LoadGeoFromFile = (LoadGeoFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromFile")); - FHoudiniApi::LoadGeoFromMemory = (LoadGeoFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromMemory")); - FHoudiniApi::LoadHIPFile = (LoadHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadHIPFile")); - FHoudiniApi::LoadNodeFromFile = (LoadNodeFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadNodeFromFile")); - FHoudiniApi::MaterialInfo_Create = (MaterialInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Create")); - FHoudiniApi::MaterialInfo_Init = (MaterialInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Init")); - FHoudiniApi::MergeHIPFile = (MergeHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MergeHIPFile")); - FHoudiniApi::NodeInfo_Create = (NodeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Create")); - FHoudiniApi::NodeInfo_Init = (NodeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Init")); - FHoudiniApi::ObjectInfo_Create = (ObjectInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Create")); - FHoudiniApi::ObjectInfo_Init = (ObjectInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Init")); - FHoudiniApi::ParmChoiceInfo_Create = (ParmChoiceInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Create")); - FHoudiniApi::ParmChoiceInfo_Init = (ParmChoiceInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Init")); - FHoudiniApi::ParmHasExpression = (ParmHasExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasExpression")); - FHoudiniApi::ParmHasTag = (ParmHasTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasTag")); - FHoudiniApi::ParmInfo_Create = (ParmInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Create")); - FHoudiniApi::ParmInfo_GetFloatValueCount = (ParmInfo_GetFloatValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetFloatValueCount")); - FHoudiniApi::ParmInfo_GetIntValueCount = (ParmInfo_GetIntValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetIntValueCount")); - FHoudiniApi::ParmInfo_GetStringValueCount = (ParmInfo_GetStringValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetStringValueCount")); - FHoudiniApi::ParmInfo_Init = (ParmInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Init")); - FHoudiniApi::ParmInfo_IsFloat = (ParmInfo_IsFloatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsFloat")); - FHoudiniApi::ParmInfo_IsInt = (ParmInfo_IsIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsInt")); - FHoudiniApi::ParmInfo_IsNode = (ParmInfo_IsNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNode")); - FHoudiniApi::ParmInfo_IsNonValue = (ParmInfo_IsNonValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNonValue")); - FHoudiniApi::ParmInfo_IsPath = (ParmInfo_IsPathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsPath")); - FHoudiniApi::ParmInfo_IsString = (ParmInfo_IsStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsString")); - FHoudiniApi::PartInfo_Create = (PartInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Create")); - FHoudiniApi::PartInfo_GetAttributeCountByOwner = (PartInfo_GetAttributeCountByOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetAttributeCountByOwner")); - FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = (PartInfo_GetElementCountByAttributeOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByAttributeOwner")); - FHoudiniApi::PartInfo_GetElementCountByGroupType = (PartInfo_GetElementCountByGroupTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByGroupType")); - FHoudiniApi::PartInfo_Init = (PartInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Init")); - FHoudiniApi::PausePDGCook = (PausePDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PausePDGCook")); - FHoudiniApi::PythonThreadInterpreterLock = (PythonThreadInterpreterLockFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PythonThreadInterpreterLock")); - FHoudiniApi::QueryNodeInput = (QueryNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeInput")); - FHoudiniApi::QueryNodeOutputConnectedCount = (QueryNodeOutputConnectedCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedCount")); - FHoudiniApi::QueryNodeOutputConnectedNodes = (QueryNodeOutputConnectedNodesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedNodes")); - FHoudiniApi::RemoveCustomString = (RemoveCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveCustomString")); - FHoudiniApi::RemoveMultiparmInstance = (RemoveMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveMultiparmInstance")); - FHoudiniApi::RemoveParmExpression = (RemoveParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveParmExpression")); - FHoudiniApi::RenameNode = (RenameNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenameNode")); - FHoudiniApi::RenderCOPToImage = (RenderCOPToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderCOPToImage")); - FHoudiniApi::RenderTextureToImage = (RenderTextureToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderTextureToImage")); - FHoudiniApi::ResetSimulation = (ResetSimulationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ResetSimulation")); - FHoudiniApi::RevertGeo = (RevertGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertGeo")); - FHoudiniApi::RevertParmToDefault = (RevertParmToDefaultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefault")); - FHoudiniApi::RevertParmToDefaults = (RevertParmToDefaultsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefaults")); - FHoudiniApi::SaveGeoToFile = (SaveGeoToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToFile")); - FHoudiniApi::SaveGeoToMemory = (SaveGeoToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToMemory")); - FHoudiniApi::SaveHIPFile = (SaveHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveHIPFile")); - FHoudiniApi::SaveNodeToFile = (SaveNodeToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveNodeToFile")); - FHoudiniApi::SessionSyncInfo_Create = (SessionSyncInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SessionSyncInfo_Create")); - FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); - FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); - FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); - FHoudiniApi::SetAttributeInt16Data = (SetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt16Data")); - FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); - FHoudiniApi::SetAttributeInt8Data = (SetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt8Data")); - FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); - FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); - FHoudiniApi::SetAttributeUInt8Data = (SetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeUInt8Data")); - FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); - FHoudiniApi::SetCompositorOptions = (SetCompositorOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCompositorOptions")); - FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); - FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); - FHoudiniApi::SetCurveKnots = (SetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveKnots")); - FHoudiniApi::SetCurveOrders = (SetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveOrders")); - FHoudiniApi::SetCustomString = (SetCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCustomString")); - FHoudiniApi::SetFaceCounts = (SetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetFaceCounts")); - FHoudiniApi::SetGroupMembership = (SetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetGroupMembership")); - FHoudiniApi::SetHeightFieldData = (SetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetHeightFieldData")); - FHoudiniApi::SetImageInfo = (SetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetImageInfo")); - FHoudiniApi::SetNodeDisplay = (SetNodeDisplayFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetNodeDisplay")); - FHoudiniApi::SetObjectTransform = (SetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetObjectTransform")); - FHoudiniApi::SetParmExpression = (SetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmExpression")); - FHoudiniApi::SetParmFloatValue = (SetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValue")); - FHoudiniApi::SetParmFloatValues = (SetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValues")); - FHoudiniApi::SetParmIntValue = (SetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValue")); - FHoudiniApi::SetParmIntValues = (SetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValues")); - FHoudiniApi::SetParmNodeValue = (SetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmNodeValue")); - FHoudiniApi::SetParmStringValue = (SetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmStringValue")); - FHoudiniApi::SetPartInfo = (SetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPartInfo")); - FHoudiniApi::SetPreset = (SetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPreset")); - FHoudiniApi::SetServerEnvInt = (SetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvInt")); - FHoudiniApi::SetServerEnvString = (SetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvString")); - FHoudiniApi::SetSessionSync = (SetSessionSyncFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSync")); - FHoudiniApi::SetSessionSyncInfo = (SetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSyncInfo")); - FHoudiniApi::SetTime = (SetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTime")); - FHoudiniApi::SetTimelineOptions = (SetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTimelineOptions")); - FHoudiniApi::SetTransformAnimCurve = (SetTransformAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTransformAnimCurve")); - FHoudiniApi::SetUseHoudiniTime = (SetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetUseHoudiniTime")); - FHoudiniApi::SetVertexList = (SetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVertexList")); - FHoudiniApi::SetViewport = (SetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetViewport")); - FHoudiniApi::SetVolumeInfo = (SetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeInfo")); - FHoudiniApi::SetVolumeTileFloatData = (SetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileFloatData")); - FHoudiniApi::SetVolumeTileIntData = (SetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileIntData")); - FHoudiniApi::SetVolumeVoxelFloatData = (SetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelFloatData")); - FHoudiniApi::SetVolumeVoxelIntData = (SetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelIntData")); - FHoudiniApi::SetWorkitemFloatData = (SetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemFloatData")); - FHoudiniApi::SetWorkitemIntData = (SetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemIntData")); - FHoudiniApi::SetWorkitemStringData = (SetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemStringData")); - FHoudiniApi::StartThriftNamedPipeServer = (StartThriftNamedPipeServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftNamedPipeServer")); - FHoudiniApi::StartThriftSocketServer = (StartThriftSocketServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftSocketServer")); - FHoudiniApi::ThriftServerOptions_Create = (ThriftServerOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Create")); - FHoudiniApi::ThriftServerOptions_Init = (ThriftServerOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Init")); - FHoudiniApi::TimelineOptions_Create = (TimelineOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Create")); - FHoudiniApi::TimelineOptions_Init = (TimelineOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Init")); - FHoudiniApi::TransformEuler_Create = (TransformEuler_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Create")); - FHoudiniApi::TransformEuler_Init = (TransformEuler_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Init")); - FHoudiniApi::Transform_Create = (Transform_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Create")); - FHoudiniApi::Transform_Init = (Transform_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Init")); - FHoudiniApi::Viewport_Create = (Viewport_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Viewport_Create")); - FHoudiniApi::VolumeInfo_Create = (VolumeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Create")); - FHoudiniApi::VolumeInfo_Init = (VolumeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Init")); - FHoudiniApi::VolumeTileInfo_Create = (VolumeTileInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Create")); - FHoudiniApi::VolumeTileInfo_Init = (VolumeTileInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Init")); -} - - -void -FHoudiniApi::FinalizeHAPI() -{ - FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; - FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; - FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; - FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; - FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; - FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; - FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; - FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; - FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; - FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; - FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; - FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; - FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; - FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; - FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; - FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; - FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; - FHoudiniApi::CompositorOptions_Create = &FHoudiniApi::CompositorOptions_CreateEmptyStub; - FHoudiniApi::CompositorOptions_Init = &FHoudiniApi::CompositorOptions_InitEmptyStub; - FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; - FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; - FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; - FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; - FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; - FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; - FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; - FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; - FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; - FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; - FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; - FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; - FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; - FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; - FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; - FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; - FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; - FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; - FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; - FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; - FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; - FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; - FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; - FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; - FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; - FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; - FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; - FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; - FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; - FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; - FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; - FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; - FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; - FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; - FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; - FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; - FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; - FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; - FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; - FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; - FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; - FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; - FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; - FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; - FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; - FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; - FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; - FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; - FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; - FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; - FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; - FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; - FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; - FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; - FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; - FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; - FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; - FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; - FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; - FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; - FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; - FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; - FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; - FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; - FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; - FHoudiniApi::GetCompositorOptions = &FHoudiniApi::GetCompositorOptionsEmptyStub; - FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; - FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; - FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; - FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; - FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; - FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; - FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; - FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; - FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; - FHoudiniApi::GetEdgeCountOfEdgeGroup = &FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub; - FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; - FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; - FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; - FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; - FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; - FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; - FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; - FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; - FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; - FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; - FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; - FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; - FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; - FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; - FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; - FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; - FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; - FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; - FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; - FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; - FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; - FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; - FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; - FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; - FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; - FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; - FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; - FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; - FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; - FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; - FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; - FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; - FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; - FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; - FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; - FHoudiniApi::GetOutputGeoCount = &FHoudiniApi::GetOutputGeoCountEmptyStub; - FHoudiniApi::GetOutputGeoInfos = &FHoudiniApi::GetOutputGeoInfosEmptyStub; - FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; - FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; - FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; - FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; - FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; - FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; - FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; - FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; - FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; - FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; - FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; - FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; - FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; - FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; - FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; - FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; - FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; - FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; - FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; - FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; - FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; - FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; - FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; - FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; - FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; - FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; - FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; - FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; - FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; - FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; - FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; - FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; - FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; - FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; - FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; - FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; - FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; - FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; - FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; - FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; - FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; - FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; - FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; - FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; - FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; - FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; - FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; - FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; - FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; - FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; - FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; - FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; - FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; - FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; - FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; - FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; - FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; - FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; - FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; - FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; - FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; - FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; - FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; - FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; - FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; - FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; - FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; - FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; - FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; - FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; - FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; - FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; - FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; - FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; - FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; - FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; - FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; - FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; - FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; - FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; - FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; - FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; - FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; - FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; - FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; - FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; - FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; - FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; - FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; - FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; - FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; - FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; - FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; - FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; - FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; - FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; - FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; - FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; - FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; - FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; - FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; - FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; - FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; - FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; - FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; - FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; - FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; - FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; - FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; - FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; - FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; - FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; - FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; - FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; - FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; - FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; - FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; - FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; - FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; - FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; - FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; - FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; - FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; - FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; - FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; - FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; - FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; - FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; - FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; - FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; - FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; - FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; - FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; - FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; - FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; - FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; - FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; - FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; - FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; - FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; - FHoudiniApi::SetCompositorOptions = &FHoudiniApi::SetCompositorOptionsEmptyStub; - FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; - FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; - FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; - FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; - FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; - FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; - FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; - FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; - FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; - FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; - FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; - FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; - FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; - FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; - FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; - FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; - FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; - FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; - FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; - FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; - FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; - FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; - FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; - FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; - FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; - FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; - FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; - FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; - FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; - FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; - FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; - FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; - FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; - FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; - FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; - FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; - FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; - FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; - FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; - FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; - FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; - FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; - FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; - FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; - FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; - FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; - FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; - FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; - FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; - FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; - FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; - FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; - FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; -} - - -bool -FHoudiniApi::IsHAPIInitialized() -{ - return ( FHoudiniApi::IsInitialized != &FHoudiniApi::IsInitializedEmptyStub ); -} - - -HAPI_Result -FHoudiniApi::AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_AssetInfo -FHoudiniApi::AssetInfo_CreateEmptyStub() -{ - return HAPI_AssetInfo(); -} - - -void -FHoudiniApi::AssetInfo_InitEmptyStub(HAPI_AssetInfo * in) -{ - return; -} - - -HAPI_AttributeInfo -FHoudiniApi::AttributeInfo_CreateEmptyStub() -{ - return HAPI_AttributeInfo(); -} - - -void -FHoudiniApi::AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CleanupEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ClearConnectionErrorEmptyStub() -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CloseSessionEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_CompositorOptions -FHoudiniApi::CompositorOptions_CreateEmptyStub() -{ - return HAPI_CompositorOptions(); -} - - -void -FHoudiniApi::CompositorOptions_InitEmptyStub(HAPI_CompositorOptions * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Bool -FHoudiniApi::CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right) -{ - return HAPI_Bool(); -} - - -HAPI_CookOptions -FHoudiniApi::CookOptions_CreateEmptyStub() -{ - return HAPI_CookOptions(); -} - - -void -FHoudiniApi::CookOptions_InitEmptyStub(HAPI_CookOptions * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateInProcessSessionEmptyStub(HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_CurveInfo -FHoudiniApi::CurveInfo_CreateEmptyStub() -{ - return HAPI_CurveInfo(); -} - - -void -FHoudiniApi::CurveInfo_InitEmptyStub(HAPI_CurveInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_GeoInfo -FHoudiniApi::GeoInfo_CreateEmptyStub() -{ - return HAPI_GeoInfo(); -} - - -int -FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type) -{ - return -1; -} - - -void -FHoudiniApi::GeoInfo_InitEmptyStub(HAPI_GeoInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCompositorOptionsEmptyStub(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetConnectionErrorLengthEmptyStub(int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetOutputGeoCountEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, int* count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetOutputGeoInfosEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTimeEmptyStub(const HAPI_Session * session, float * time) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_HandleBindingInfo -FHoudiniApi::HandleBindingInfo_CreateEmptyStub() -{ - return HAPI_HandleBindingInfo(); -} - - -void -FHoudiniApi::HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in) -{ - return; -} - - -HAPI_HandleInfo -FHoudiniApi::HandleInfo_CreateEmptyStub() -{ - return HAPI_HandleInfo(); -} - - -void -FHoudiniApi::HandleInfo_InitEmptyStub(HAPI_HandleInfo * in) -{ - return; -} - - -HAPI_ImageFileFormat -FHoudiniApi::ImageFileFormat_CreateEmptyStub() -{ - return HAPI_ImageFileFormat(); -} - - -void -FHoudiniApi::ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in) -{ - return; -} - - -HAPI_ImageInfo -FHoudiniApi::ImageInfo_CreateEmptyStub() -{ - return HAPI_ImageInfo(); -} - - -void -FHoudiniApi::ImageInfo_InitEmptyStub(HAPI_ImageInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::InterruptEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsInitializedEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::IsSessionValidEmptyStub(const HAPI_Session * session) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Keyframe -FHoudiniApi::Keyframe_CreateEmptyStub() -{ - return HAPI_Keyframe(); -} - - -void -FHoudiniApi::Keyframe_InitEmptyStub(HAPI_Keyframe * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_MaterialInfo -FHoudiniApi::MaterialInfo_CreateEmptyStub() -{ - return HAPI_MaterialInfo(); -} - - -void -FHoudiniApi::MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_NodeInfo -FHoudiniApi::NodeInfo_CreateEmptyStub() -{ - return HAPI_NodeInfo(); -} - - -void -FHoudiniApi::NodeInfo_InitEmptyStub(HAPI_NodeInfo * in) -{ - return; -} - - -HAPI_ObjectInfo -FHoudiniApi::ObjectInfo_CreateEmptyStub() -{ - return HAPI_ObjectInfo(); -} - - -void -FHoudiniApi::ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in) -{ - return; -} - - -HAPI_ParmChoiceInfo -FHoudiniApi::ParmChoiceInfo_CreateEmptyStub() -{ - return HAPI_ParmChoiceInfo(); -} - - -void -FHoudiniApi::ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_ParmInfo -FHoudiniApi::ParmInfo_CreateEmptyStub() -{ - return HAPI_ParmInfo(); -} - - -int -FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in) -{ - return -1; -} - - -int -FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in) -{ - return -1; -} - - -int -FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in) -{ - return -1; -} - - -void -FHoudiniApi::ParmInfo_InitEmptyStub(HAPI_ParmInfo * in) -{ - return; -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_Bool -FHoudiniApi::ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in) -{ - return HAPI_Bool(); -} - - -HAPI_PartInfo -FHoudiniApi::PartInfo_CreateEmptyStub() -{ - return HAPI_PartInfo(); -} - - -int -FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) -{ - return -1; -} - - -int -FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) -{ - return -1; -} - - -int -FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type) -{ - return -1; -} - - -void -FHoudiniApi::PartInfo_InitEmptyStub(HAPI_PartInfo * in) -{ - return; -} - - -HAPI_Result -FHoudiniApi::PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_SessionSyncInfo -FHoudiniApi::SessionSyncInfo_CreateEmptyStub() -{ - return HAPI_SessionSyncInfo(); -} - - -HAPI_Result -FHoudiniApi::SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCompositorOptionsEmptyStub(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTimeEmptyStub(const HAPI_Session * session, float time) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_Result -FHoudiniApi::StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id) -{ - return HAPI_RESULT_FAILURE; -} - - -HAPI_ThriftServerOptions -FHoudiniApi::ThriftServerOptions_CreateEmptyStub() -{ - return HAPI_ThriftServerOptions(); -} - - -void -FHoudiniApi::ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in) -{ - return; -} - - -HAPI_TimelineOptions -FHoudiniApi::TimelineOptions_CreateEmptyStub() -{ - return HAPI_TimelineOptions(); -} - - -void -FHoudiniApi::TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in) -{ - return; -} - - -HAPI_TransformEuler -FHoudiniApi::TransformEuler_CreateEmptyStub() -{ - return HAPI_TransformEuler(); -} - - -void -FHoudiniApi::TransformEuler_InitEmptyStub(HAPI_TransformEuler * in) -{ - return; -} - - -HAPI_Transform -FHoudiniApi::Transform_CreateEmptyStub() -{ - return HAPI_Transform(); -} - - -void -FHoudiniApi::Transform_InitEmptyStub(HAPI_Transform * in) -{ - return; -} - - -HAPI_Viewport -FHoudiniApi::Viewport_CreateEmptyStub() -{ - return HAPI_Viewport(); -} - - -HAPI_VolumeInfo -FHoudiniApi::VolumeInfo_CreateEmptyStub() -{ - return HAPI_VolumeInfo(); -} - - -void -FHoudiniApi::VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in) -{ - return; -} - - -HAPI_VolumeTileInfo -FHoudiniApi::VolumeTileInfo_CreateEmptyStub() -{ - return HAPI_VolumeTileInfo(); -} - - -void -FHoudiniApi::VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in) -{ - return; -} - - +/* + * Copyright (c) <2021> 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. + * + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +#include "HoudiniApi.h" +#include "HoudiniEnginePrivatePCH.h" + + +FHoudiniApi::AddAttributeFuncPtr +FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; + +FHoudiniApi::AddGroupFuncPtr +FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; + +FHoudiniApi::AssetInfo_CreateFuncPtr +FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; + +FHoudiniApi::AssetInfo_InitFuncPtr +FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; + +FHoudiniApi::AttributeInfo_CreateFuncPtr +FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; + +FHoudiniApi::AttributeInfo_InitFuncPtr +FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; + +FHoudiniApi::BindCustomImplementationFuncPtr +FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; + +FHoudiniApi::CancelPDGCookFuncPtr +FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; + +FHoudiniApi::CheckForSpecificErrorsFuncPtr +FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; + +FHoudiniApi::CleanupFuncPtr +FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; + +FHoudiniApi::ClearConnectionErrorFuncPtr +FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; + +FHoudiniApi::CloseSessionFuncPtr +FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; + +FHoudiniApi::CommitGeoFuncPtr +FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; + +FHoudiniApi::CommitWorkitemsFuncPtr +FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; + +FHoudiniApi::ComposeChildNodeListFuncPtr +FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; + +FHoudiniApi::ComposeNodeCookResultFuncPtr +FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; + +FHoudiniApi::ComposeObjectListFuncPtr +FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + +FHoudiniApi::CompositorOptions_CreateFuncPtr +FHoudiniApi::CompositorOptions_Create = &FHoudiniApi::CompositorOptions_CreateEmptyStub; + +FHoudiniApi::CompositorOptions_InitFuncPtr +FHoudiniApi::CompositorOptions_Init = &FHoudiniApi::CompositorOptions_InitEmptyStub; + +FHoudiniApi::ConnectNodeInputFuncPtr +FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; + +FHoudiniApi::ConvertMatrixToEulerFuncPtr +FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; + +FHoudiniApi::ConvertMatrixToQuatFuncPtr +FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; + +FHoudiniApi::ConvertTransformFuncPtr +FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; + +FHoudiniApi::ConvertTransformEulerToMatrixFuncPtr +FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; + +FHoudiniApi::ConvertTransformQuatToMatrixFuncPtr +FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; + +FHoudiniApi::CookNodeFuncPtr +FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; + +FHoudiniApi::CookOptions_AreEqualFuncPtr +FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; + +FHoudiniApi::CookOptions_CreateFuncPtr +FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; + +FHoudiniApi::CookOptions_InitFuncPtr +FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; + +FHoudiniApi::CookPDGFuncPtr +FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; + +FHoudiniApi::CreateCustomSessionFuncPtr +FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; + +FHoudiniApi::CreateHeightFieldInputFuncPtr +FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; + +FHoudiniApi::CreateHeightfieldInputVolumeNodeFuncPtr +FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; + +FHoudiniApi::CreateInProcessSessionFuncPtr +FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; + +FHoudiniApi::CreateInputNodeFuncPtr +FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; + +FHoudiniApi::CreateNodeFuncPtr +FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; + +FHoudiniApi::CreateThriftNamedPipeSessionFuncPtr +FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; + +FHoudiniApi::CreateThriftSocketSessionFuncPtr +FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; + +FHoudiniApi::CreateWorkitemFuncPtr +FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; + +FHoudiniApi::CurveInfo_CreateFuncPtr +FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; + +FHoudiniApi::CurveInfo_InitFuncPtr +FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; + +FHoudiniApi::DeleteAttributeFuncPtr +FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; + +FHoudiniApi::DeleteGroupFuncPtr +FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; + +FHoudiniApi::DeleteNodeFuncPtr +FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; + +FHoudiniApi::DirtyPDGNodeFuncPtr +FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; + +FHoudiniApi::DisconnectNodeInputFuncPtr +FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; + +FHoudiniApi::DisconnectNodeOutputsAtFuncPtr +FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; + +FHoudiniApi::ExtractImageToFileFuncPtr +FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; + +FHoudiniApi::ExtractImageToMemoryFuncPtr +FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; + +FHoudiniApi::GeoInfo_CreateFuncPtr +FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; + +FHoudiniApi::GeoInfo_GetGroupCountByTypeFuncPtr +FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; + +FHoudiniApi::GeoInfo_InitFuncPtr +FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; + +FHoudiniApi::GetActiveCacheCountFuncPtr +FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; + +FHoudiniApi::GetActiveCacheNamesFuncPtr +FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmCountsFuncPtr +FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmInfosFuncPtr +FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; + +FHoudiniApi::GetAssetDefinitionParmValuesFuncPtr +FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; + +FHoudiniApi::GetAssetInfoFuncPtr +FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; + +FHoudiniApi::GetAttributeFloat64ArrayDataFuncPtr +FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeFloat64DataFuncPtr +FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; + +FHoudiniApi::GetAttributeFloatArrayDataFuncPtr +FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; + +FHoudiniApi::GetAttributeFloatDataFuncPtr +FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; + +FHoudiniApi::GetAttributeInfoFuncPtr +FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + +FHoudiniApi::GetAttributeInt16ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt16DataFuncPtr +FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; + +FHoudiniApi::GetAttributeInt64ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt64DataFuncPtr +FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + +FHoudiniApi::GetAttributeInt8ArrayDataFuncPtr +FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeInt8DataFuncPtr +FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; + +FHoudiniApi::GetAttributeIntArrayDataFuncPtr +FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; + +FHoudiniApi::GetAttributeIntDataFuncPtr +FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; + +FHoudiniApi::GetAttributeNamesFuncPtr +FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; + +FHoudiniApi::GetAttributeStringArrayDataFuncPtr +FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; + +FHoudiniApi::GetAttributeStringDataFuncPtr +FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + +FHoudiniApi::GetAttributeUInt8ArrayDataFuncPtr +FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; + +FHoudiniApi::GetAttributeUInt8DataFuncPtr +FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; + +FHoudiniApi::GetAvailableAssetCountFuncPtr +FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; + +FHoudiniApi::GetAvailableAssetsFuncPtr +FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; + +FHoudiniApi::GetBoxInfoFuncPtr +FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; + +FHoudiniApi::GetCachePropertyFuncPtr +FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; + +FHoudiniApi::GetComposedChildNodeListFuncPtr +FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; + +FHoudiniApi::GetComposedNodeCookResultFuncPtr +FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; + +FHoudiniApi::GetComposedObjectListFuncPtr +FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; + +FHoudiniApi::GetComposedObjectTransformsFuncPtr +FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + +FHoudiniApi::GetCompositorOptionsFuncPtr +FHoudiniApi::GetCompositorOptions = &FHoudiniApi::GetCompositorOptionsEmptyStub; + +FHoudiniApi::GetConnectionErrorFuncPtr +FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; + +FHoudiniApi::GetConnectionErrorLengthFuncPtr +FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; + +FHoudiniApi::GetCookingCurrentCountFuncPtr +FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; + +FHoudiniApi::GetCookingTotalCountFuncPtr +FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; + +FHoudiniApi::GetCurveCountsFuncPtr +FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; + +FHoudiniApi::GetCurveInfoFuncPtr +FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; + +FHoudiniApi::GetCurveKnotsFuncPtr +FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; + +FHoudiniApi::GetCurveOrdersFuncPtr +FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; + +FHoudiniApi::GetDisplayGeoInfoFuncPtr +FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + +FHoudiniApi::GetEdgeCountOfEdgeGroupFuncPtr +FHoudiniApi::GetEdgeCountOfEdgeGroup = &FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub; + +FHoudiniApi::GetEnvIntFuncPtr +FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; + +FHoudiniApi::GetFaceCountsFuncPtr +FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; + +FHoudiniApi::GetFirstVolumeTileFuncPtr +FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; + +FHoudiniApi::GetGeoInfoFuncPtr +FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; + +FHoudiniApi::GetGeoSizeFuncPtr +FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; + +FHoudiniApi::GetGroupCountOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetGroupMembershipFuncPtr +FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; + +FHoudiniApi::GetGroupMembershipOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetGroupNamesFuncPtr +FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; + +FHoudiniApi::GetGroupNamesOnPackedInstancePartFuncPtr +FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; + +FHoudiniApi::GetHIPFileNodeCountFuncPtr +FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; + +FHoudiniApi::GetHIPFileNodeIdsFuncPtr +FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; + +FHoudiniApi::GetHandleBindingInfoFuncPtr +FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; + +FHoudiniApi::GetHandleInfoFuncPtr +FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; + +FHoudiniApi::GetHeightFieldDataFuncPtr +FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; + +FHoudiniApi::GetImageFilePathFuncPtr +FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; + +FHoudiniApi::GetImageInfoFuncPtr +FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; + +FHoudiniApi::GetImageMemoryBufferFuncPtr +FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; + +FHoudiniApi::GetImagePlaneCountFuncPtr +FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; + +FHoudiniApi::GetImagePlanesFuncPtr +FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; + +FHoudiniApi::GetInstanceTransformsOnPartFuncPtr +FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; + +FHoudiniApi::GetInstancedObjectIdsFuncPtr +FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; + +FHoudiniApi::GetInstancedPartIdsFuncPtr +FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; + +FHoudiniApi::GetInstancerPartTransformsFuncPtr +FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; + +FHoudiniApi::GetManagerNodeIdFuncPtr +FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; + +FHoudiniApi::GetMaterialInfoFuncPtr +FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; + +FHoudiniApi::GetMaterialNodeIdsOnFacesFuncPtr +FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; + +FHoudiniApi::GetNextVolumeTileFuncPtr +FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; + +FHoudiniApi::GetNodeInfoFuncPtr +FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; + +FHoudiniApi::GetNodeInputNameFuncPtr +FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; + +FHoudiniApi::GetNodeOutputNameFuncPtr +FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; + +FHoudiniApi::GetNodePathFuncPtr +FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; + +FHoudiniApi::GetNumWorkitemsFuncPtr +FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; + +FHoudiniApi::GetObjectInfoFuncPtr +FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; + +FHoudiniApi::GetObjectTransformFuncPtr +FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + +FHoudiniApi::GetOutputGeoCountFuncPtr +FHoudiniApi::GetOutputGeoCount = &FHoudiniApi::GetOutputGeoCountEmptyStub; + +FHoudiniApi::GetOutputGeoInfosFuncPtr +FHoudiniApi::GetOutputGeoInfos = &FHoudiniApi::GetOutputGeoInfosEmptyStub; + +FHoudiniApi::GetOutputNodeIdFuncPtr +FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; + +FHoudiniApi::GetPDGEventsFuncPtr +FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; + +FHoudiniApi::GetPDGGraphContextIdFuncPtr +FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; + +FHoudiniApi::GetPDGGraphContextsFuncPtr +FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; + +FHoudiniApi::GetPDGStateFuncPtr +FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; + +FHoudiniApi::GetParametersFuncPtr +FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; + +FHoudiniApi::GetParmChoiceListsFuncPtr +FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; + +FHoudiniApi::GetParmExpressionFuncPtr +FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; + +FHoudiniApi::GetParmFileFuncPtr +FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; + +FHoudiniApi::GetParmFloatValueFuncPtr +FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; + +FHoudiniApi::GetParmFloatValuesFuncPtr +FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; + +FHoudiniApi::GetParmIdFromNameFuncPtr +FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; + +FHoudiniApi::GetParmInfoFuncPtr +FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; + +FHoudiniApi::GetParmInfoFromNameFuncPtr +FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; + +FHoudiniApi::GetParmIntValueFuncPtr +FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; + +FHoudiniApi::GetParmIntValuesFuncPtr +FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; + +FHoudiniApi::GetParmNodeValueFuncPtr +FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; + +FHoudiniApi::GetParmStringValueFuncPtr +FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; + +FHoudiniApi::GetParmStringValuesFuncPtr +FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; + +FHoudiniApi::GetParmTagNameFuncPtr +FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; + +FHoudiniApi::GetParmTagValueFuncPtr +FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; + +FHoudiniApi::GetParmWithTagFuncPtr +FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; + +FHoudiniApi::GetPartInfoFuncPtr +FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; + +FHoudiniApi::GetPresetFuncPtr +FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; + +FHoudiniApi::GetPresetBufLengthFuncPtr +FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; + +FHoudiniApi::GetServerEnvIntFuncPtr +FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; + +FHoudiniApi::GetServerEnvStringFuncPtr +FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; + +FHoudiniApi::GetServerEnvVarCountFuncPtr +FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; + +FHoudiniApi::GetServerEnvVarListFuncPtr +FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; + +FHoudiniApi::GetSessionEnvIntFuncPtr +FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; + +FHoudiniApi::GetSessionSyncInfoFuncPtr +FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; + +FHoudiniApi::GetSphereInfoFuncPtr +FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; + +FHoudiniApi::GetStatusFuncPtr +FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; + +FHoudiniApi::GetStatusStringFuncPtr +FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; + +FHoudiniApi::GetStatusStringBufLengthFuncPtr +FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; + +FHoudiniApi::GetStringFuncPtr +FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; + +FHoudiniApi::GetStringBatchFuncPtr +FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; + +FHoudiniApi::GetStringBatchSizeFuncPtr +FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; + +FHoudiniApi::GetStringBufLengthFuncPtr +FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; + +FHoudiniApi::GetSupportedImageFileFormatCountFuncPtr +FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; + +FHoudiniApi::GetSupportedImageFileFormatsFuncPtr +FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; + +FHoudiniApi::GetTimeFuncPtr +FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; + +FHoudiniApi::GetTimelineOptionsFuncPtr +FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; + +FHoudiniApi::GetTotalCookCountFuncPtr +FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; + +FHoudiniApi::GetUseHoudiniTimeFuncPtr +FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; + +FHoudiniApi::GetVertexListFuncPtr +FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; + +FHoudiniApi::GetViewportFuncPtr +FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; + +FHoudiniApi::GetVolumeBoundsFuncPtr +FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; + +FHoudiniApi::GetVolumeInfoFuncPtr +FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; + +FHoudiniApi::GetVolumeTileFloatDataFuncPtr +FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; + +FHoudiniApi::GetVolumeTileIntDataFuncPtr +FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; + +FHoudiniApi::GetVolumeVisualInfoFuncPtr +FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; + +FHoudiniApi::GetVolumeVoxelFloatDataFuncPtr +FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; + +FHoudiniApi::GetVolumeVoxelIntDataFuncPtr +FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; + +FHoudiniApi::GetWorkitemDataLengthFuncPtr +FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; + +FHoudiniApi::GetWorkitemFloatDataFuncPtr +FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; + +FHoudiniApi::GetWorkitemInfoFuncPtr +FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; + +FHoudiniApi::GetWorkitemIntDataFuncPtr +FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; + +FHoudiniApi::GetWorkitemResultInfoFuncPtr +FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; + +FHoudiniApi::GetWorkitemStringDataFuncPtr +FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; + +FHoudiniApi::GetWorkitemsFuncPtr +FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; + +FHoudiniApi::HandleBindingInfo_CreateFuncPtr +FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; + +FHoudiniApi::HandleBindingInfo_InitFuncPtr +FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; + +FHoudiniApi::HandleInfo_CreateFuncPtr +FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; + +FHoudiniApi::HandleInfo_InitFuncPtr +FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; + +FHoudiniApi::ImageFileFormat_CreateFuncPtr +FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; + +FHoudiniApi::ImageFileFormat_InitFuncPtr +FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; + +FHoudiniApi::ImageInfo_CreateFuncPtr +FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; + +FHoudiniApi::ImageInfo_InitFuncPtr +FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; + +FHoudiniApi::InitializeFuncPtr +FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; + +FHoudiniApi::InsertMultiparmInstanceFuncPtr +FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; + +FHoudiniApi::InterruptFuncPtr +FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; + +FHoudiniApi::IsInitializedFuncPtr +FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; + +FHoudiniApi::IsNodeValidFuncPtr +FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; + +FHoudiniApi::IsSessionValidFuncPtr +FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; + +FHoudiniApi::Keyframe_CreateFuncPtr +FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; + +FHoudiniApi::Keyframe_InitFuncPtr +FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; + +FHoudiniApi::LoadAssetLibraryFromFileFuncPtr +FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; + +FHoudiniApi::LoadAssetLibraryFromMemoryFuncPtr +FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; + +FHoudiniApi::LoadGeoFromFileFuncPtr +FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; + +FHoudiniApi::LoadGeoFromMemoryFuncPtr +FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; + +FHoudiniApi::LoadHIPFileFuncPtr +FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; + +FHoudiniApi::LoadNodeFromFileFuncPtr +FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; + +FHoudiniApi::MaterialInfo_CreateFuncPtr +FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; + +FHoudiniApi::MaterialInfo_InitFuncPtr +FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; + +FHoudiniApi::MergeHIPFileFuncPtr +FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; + +FHoudiniApi::NodeInfo_CreateFuncPtr +FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; + +FHoudiniApi::NodeInfo_InitFuncPtr +FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; + +FHoudiniApi::ObjectInfo_CreateFuncPtr +FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; + +FHoudiniApi::ObjectInfo_InitFuncPtr +FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; + +FHoudiniApi::ParmChoiceInfo_CreateFuncPtr +FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; + +FHoudiniApi::ParmChoiceInfo_InitFuncPtr +FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; + +FHoudiniApi::ParmHasExpressionFuncPtr +FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; + +FHoudiniApi::ParmHasTagFuncPtr +FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; + +FHoudiniApi::ParmInfo_CreateFuncPtr +FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; + +FHoudiniApi::ParmInfo_GetFloatValueCountFuncPtr +FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; + +FHoudiniApi::ParmInfo_GetIntValueCountFuncPtr +FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; + +FHoudiniApi::ParmInfo_GetStringValueCountFuncPtr +FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; + +FHoudiniApi::ParmInfo_InitFuncPtr +FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; + +FHoudiniApi::ParmInfo_IsFloatFuncPtr +FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; + +FHoudiniApi::ParmInfo_IsIntFuncPtr +FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; + +FHoudiniApi::ParmInfo_IsNodeFuncPtr +FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; + +FHoudiniApi::ParmInfo_IsNonValueFuncPtr +FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; + +FHoudiniApi::ParmInfo_IsPathFuncPtr +FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; + +FHoudiniApi::ParmInfo_IsStringFuncPtr +FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; + +FHoudiniApi::PartInfo_CreateFuncPtr +FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; + +FHoudiniApi::PartInfo_GetAttributeCountByOwnerFuncPtr +FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; + +FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerFuncPtr +FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; + +FHoudiniApi::PartInfo_GetElementCountByGroupTypeFuncPtr +FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; + +FHoudiniApi::PartInfo_InitFuncPtr +FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; + +FHoudiniApi::PausePDGCookFuncPtr +FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; + +FHoudiniApi::PythonThreadInterpreterLockFuncPtr +FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; + +FHoudiniApi::QueryNodeInputFuncPtr +FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; + +FHoudiniApi::QueryNodeOutputConnectedCountFuncPtr +FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; + +FHoudiniApi::QueryNodeOutputConnectedNodesFuncPtr +FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; + +FHoudiniApi::RemoveCustomStringFuncPtr +FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; + +FHoudiniApi::RemoveMultiparmInstanceFuncPtr +FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; + +FHoudiniApi::RemoveParmExpressionFuncPtr +FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; + +FHoudiniApi::RenameNodeFuncPtr +FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; + +FHoudiniApi::RenderCOPToImageFuncPtr +FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; + +FHoudiniApi::RenderTextureToImageFuncPtr +FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; + +FHoudiniApi::ResetSimulationFuncPtr +FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; + +FHoudiniApi::RevertGeoFuncPtr +FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; + +FHoudiniApi::RevertParmToDefaultFuncPtr +FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; + +FHoudiniApi::RevertParmToDefaultsFuncPtr +FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; + +FHoudiniApi::SaveGeoToFileFuncPtr +FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; + +FHoudiniApi::SaveGeoToMemoryFuncPtr +FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; + +FHoudiniApi::SaveHIPFileFuncPtr +FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; + +FHoudiniApi::SaveNodeToFileFuncPtr +FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; + +FHoudiniApi::SessionSyncInfo_CreateFuncPtr +FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; + +FHoudiniApi::SetAnimCurveFuncPtr +FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; + +FHoudiniApi::SetAttributeFloat64DataFuncPtr +FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; + +FHoudiniApi::SetAttributeFloatDataFuncPtr +FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + +FHoudiniApi::SetAttributeInt16DataFuncPtr +FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; + +FHoudiniApi::SetAttributeInt64DataFuncPtr +FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + +FHoudiniApi::SetAttributeInt8DataFuncPtr +FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; + +FHoudiniApi::SetAttributeIntDataFuncPtr +FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; + +FHoudiniApi::SetAttributeStringDataFuncPtr +FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + +FHoudiniApi::SetAttributeUInt8DataFuncPtr +FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; + +FHoudiniApi::SetCachePropertyFuncPtr +FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + +FHoudiniApi::SetCompositorOptionsFuncPtr +FHoudiniApi::SetCompositorOptions = &FHoudiniApi::SetCompositorOptionsEmptyStub; + +FHoudiniApi::SetCurveCountsFuncPtr +FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; + +FHoudiniApi::SetCurveInfoFuncPtr +FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; + +FHoudiniApi::SetCurveKnotsFuncPtr +FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; + +FHoudiniApi::SetCurveOrdersFuncPtr +FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; + +FHoudiniApi::SetCustomStringFuncPtr +FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; + +FHoudiniApi::SetFaceCountsFuncPtr +FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; + +FHoudiniApi::SetGroupMembershipFuncPtr +FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; + +FHoudiniApi::SetHeightFieldDataFuncPtr +FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; + +FHoudiniApi::SetImageInfoFuncPtr +FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; + +FHoudiniApi::SetNodeDisplayFuncPtr +FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; + +FHoudiniApi::SetObjectTransformFuncPtr +FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; + +FHoudiniApi::SetParmExpressionFuncPtr +FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; + +FHoudiniApi::SetParmFloatValueFuncPtr +FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; + +FHoudiniApi::SetParmFloatValuesFuncPtr +FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; + +FHoudiniApi::SetParmIntValueFuncPtr +FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; + +FHoudiniApi::SetParmIntValuesFuncPtr +FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; + +FHoudiniApi::SetParmNodeValueFuncPtr +FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; + +FHoudiniApi::SetParmStringValueFuncPtr +FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; + +FHoudiniApi::SetPartInfoFuncPtr +FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; + +FHoudiniApi::SetPresetFuncPtr +FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; + +FHoudiniApi::SetServerEnvIntFuncPtr +FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; + +FHoudiniApi::SetServerEnvStringFuncPtr +FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; + +FHoudiniApi::SetSessionSyncFuncPtr +FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; + +FHoudiniApi::SetSessionSyncInfoFuncPtr +FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; + +FHoudiniApi::SetTimeFuncPtr +FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; + +FHoudiniApi::SetTimelineOptionsFuncPtr +FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; + +FHoudiniApi::SetTransformAnimCurveFuncPtr +FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; + +FHoudiniApi::SetUseHoudiniTimeFuncPtr +FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; + +FHoudiniApi::SetVertexListFuncPtr +FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; + +FHoudiniApi::SetViewportFuncPtr +FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; + +FHoudiniApi::SetVolumeInfoFuncPtr +FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; + +FHoudiniApi::SetVolumeTileFloatDataFuncPtr +FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; + +FHoudiniApi::SetVolumeTileIntDataFuncPtr +FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; + +FHoudiniApi::SetVolumeVoxelFloatDataFuncPtr +FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; + +FHoudiniApi::SetVolumeVoxelIntDataFuncPtr +FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; + +FHoudiniApi::SetWorkitemFloatDataFuncPtr +FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; + +FHoudiniApi::SetWorkitemIntDataFuncPtr +FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; + +FHoudiniApi::SetWorkitemStringDataFuncPtr +FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; + +FHoudiniApi::StartThriftNamedPipeServerFuncPtr +FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; + +FHoudiniApi::StartThriftSocketServerFuncPtr +FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; + +FHoudiniApi::ThriftServerOptions_CreateFuncPtr +FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; + +FHoudiniApi::ThriftServerOptions_InitFuncPtr +FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; + +FHoudiniApi::TimelineOptions_CreateFuncPtr +FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; + +FHoudiniApi::TimelineOptions_InitFuncPtr +FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; + +FHoudiniApi::TransformEuler_CreateFuncPtr +FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; + +FHoudiniApi::TransformEuler_InitFuncPtr +FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; + +FHoudiniApi::Transform_CreateFuncPtr +FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; + +FHoudiniApi::Transform_InitFuncPtr +FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; + +FHoudiniApi::Viewport_CreateFuncPtr +FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; + +FHoudiniApi::VolumeInfo_CreateFuncPtr +FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; + +FHoudiniApi::VolumeInfo_InitFuncPtr +FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; + +FHoudiniApi::VolumeTileInfo_CreateFuncPtr +FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; + +FHoudiniApi::VolumeTileInfo_InitFuncPtr +FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; + + +void +FHoudiniApi::InitializeHAPI(void* LibraryHandle) +{ + if(!LibraryHandle) return; + + FHoudiniApi::AddAttribute = (AddAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddAttribute")); + FHoudiniApi::AddGroup = (AddGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AddGroup")); + FHoudiniApi::AssetInfo_Create = (AssetInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Create")); + FHoudiniApi::AssetInfo_Init = (AssetInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AssetInfo_Init")); + FHoudiniApi::AttributeInfo_Create = (AttributeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Create")); + FHoudiniApi::AttributeInfo_Init = (AttributeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_AttributeInfo_Init")); + FHoudiniApi::BindCustomImplementation = (BindCustomImplementationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_BindCustomImplementation")); + FHoudiniApi::CancelPDGCook = (CancelPDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CancelPDGCook")); + FHoudiniApi::CheckForSpecificErrors = (CheckForSpecificErrorsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CheckForSpecificErrors")); + FHoudiniApi::Cleanup = (CleanupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Cleanup")); + FHoudiniApi::ClearConnectionError = (ClearConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ClearConnectionError")); + FHoudiniApi::CloseSession = (CloseSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CloseSession")); + FHoudiniApi::CommitGeo = (CommitGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitGeo")); + FHoudiniApi::CommitWorkitems = (CommitWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CommitWorkitems")); + FHoudiniApi::ComposeChildNodeList = (ComposeChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeChildNodeList")); + FHoudiniApi::ComposeNodeCookResult = (ComposeNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeNodeCookResult")); + FHoudiniApi::ComposeObjectList = (ComposeObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ComposeObjectList")); + FHoudiniApi::CompositorOptions_Create = (CompositorOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CompositorOptions_Create")); + FHoudiniApi::CompositorOptions_Init = (CompositorOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CompositorOptions_Init")); + FHoudiniApi::ConnectNodeInput = (ConnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConnectNodeInput")); + FHoudiniApi::ConvertMatrixToEuler = (ConvertMatrixToEulerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToEuler")); + FHoudiniApi::ConvertMatrixToQuat = (ConvertMatrixToQuatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertMatrixToQuat")); + FHoudiniApi::ConvertTransform = (ConvertTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransform")); + FHoudiniApi::ConvertTransformEulerToMatrix = (ConvertTransformEulerToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformEulerToMatrix")); + FHoudiniApi::ConvertTransformQuatToMatrix = (ConvertTransformQuatToMatrixFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ConvertTransformQuatToMatrix")); + FHoudiniApi::CookNode = (CookNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookNode")); + FHoudiniApi::CookOptions_AreEqual = (CookOptions_AreEqualFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_AreEqual")); + FHoudiniApi::CookOptions_Create = (CookOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Create")); + FHoudiniApi::CookOptions_Init = (CookOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookOptions_Init")); + FHoudiniApi::CookPDG = (CookPDGFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CookPDG")); + FHoudiniApi::CreateCustomSession = (CreateCustomSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateCustomSession")); + FHoudiniApi::CreateHeightFieldInput = (CreateHeightFieldInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightFieldInput")); + FHoudiniApi::CreateHeightfieldInputVolumeNode = (CreateHeightfieldInputVolumeNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateHeightfieldInputVolumeNode")); + FHoudiniApi::CreateInProcessSession = (CreateInProcessSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInProcessSession")); + FHoudiniApi::CreateInputNode = (CreateInputNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateInputNode")); + FHoudiniApi::CreateNode = (CreateNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateNode")); + FHoudiniApi::CreateThriftNamedPipeSession = (CreateThriftNamedPipeSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftNamedPipeSession")); + FHoudiniApi::CreateThriftSocketSession = (CreateThriftSocketSessionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateThriftSocketSession")); + FHoudiniApi::CreateWorkitem = (CreateWorkitemFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CreateWorkitem")); + FHoudiniApi::CurveInfo_Create = (CurveInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Create")); + FHoudiniApi::CurveInfo_Init = (CurveInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_CurveInfo_Init")); + FHoudiniApi::DeleteAttribute = (DeleteAttributeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteAttribute")); + FHoudiniApi::DeleteGroup = (DeleteGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteGroup")); + FHoudiniApi::DeleteNode = (DeleteNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DeleteNode")); + FHoudiniApi::DirtyPDGNode = (DirtyPDGNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DirtyPDGNode")); + FHoudiniApi::DisconnectNodeInput = (DisconnectNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeInput")); + FHoudiniApi::DisconnectNodeOutputsAt = (DisconnectNodeOutputsAtFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_DisconnectNodeOutputsAt")); + FHoudiniApi::ExtractImageToFile = (ExtractImageToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToFile")); + FHoudiniApi::ExtractImageToMemory = (ExtractImageToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ExtractImageToMemory")); + FHoudiniApi::GeoInfo_Create = (GeoInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Create")); + FHoudiniApi::GeoInfo_GetGroupCountByType = (GeoInfo_GetGroupCountByTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_GetGroupCountByType")); + FHoudiniApi::GeoInfo_Init = (GeoInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GeoInfo_Init")); + FHoudiniApi::GetActiveCacheCount = (GetActiveCacheCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheCount")); + FHoudiniApi::GetActiveCacheNames = (GetActiveCacheNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetActiveCacheNames")); + FHoudiniApi::GetAssetDefinitionParmCounts = (GetAssetDefinitionParmCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmCounts")); + FHoudiniApi::GetAssetDefinitionParmInfos = (GetAssetDefinitionParmInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmInfos")); + FHoudiniApi::GetAssetDefinitionParmValues = (GetAssetDefinitionParmValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetDefinitionParmValues")); + FHoudiniApi::GetAssetInfo = (GetAssetInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAssetInfo")); + FHoudiniApi::GetAttributeFloat64ArrayData = (GetAttributeFloat64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64ArrayData")); + FHoudiniApi::GetAttributeFloat64Data = (GetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloat64Data")); + FHoudiniApi::GetAttributeFloatArrayData = (GetAttributeFloatArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatArrayData")); + FHoudiniApi::GetAttributeFloatData = (GetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeFloatData")); + FHoudiniApi::GetAttributeInfo = (GetAttributeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInfo")); + FHoudiniApi::GetAttributeInt16ArrayData = (GetAttributeInt16ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16ArrayData")); + FHoudiniApi::GetAttributeInt16Data = (GetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt16Data")); + FHoudiniApi::GetAttributeInt64ArrayData = (GetAttributeInt64ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64ArrayData")); + FHoudiniApi::GetAttributeInt64Data = (GetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt64Data")); + FHoudiniApi::GetAttributeInt8ArrayData = (GetAttributeInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8ArrayData")); + FHoudiniApi::GetAttributeInt8Data = (GetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeInt8Data")); + FHoudiniApi::GetAttributeIntArrayData = (GetAttributeIntArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntArrayData")); + FHoudiniApi::GetAttributeIntData = (GetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeIntData")); + FHoudiniApi::GetAttributeNames = (GetAttributeNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeNames")); + FHoudiniApi::GetAttributeStringArrayData = (GetAttributeStringArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringArrayData")); + FHoudiniApi::GetAttributeStringData = (GetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeStringData")); + FHoudiniApi::GetAttributeUInt8ArrayData = (GetAttributeUInt8ArrayDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8ArrayData")); + FHoudiniApi::GetAttributeUInt8Data = (GetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAttributeUInt8Data")); + FHoudiniApi::GetAvailableAssetCount = (GetAvailableAssetCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssetCount")); + FHoudiniApi::GetAvailableAssets = (GetAvailableAssetsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetAvailableAssets")); + FHoudiniApi::GetBoxInfo = (GetBoxInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetBoxInfo")); + FHoudiniApi::GetCacheProperty = (GetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCacheProperty")); + FHoudiniApi::GetComposedChildNodeList = (GetComposedChildNodeListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedChildNodeList")); + FHoudiniApi::GetComposedNodeCookResult = (GetComposedNodeCookResultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedNodeCookResult")); + FHoudiniApi::GetComposedObjectList = (GetComposedObjectListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectList")); + FHoudiniApi::GetComposedObjectTransforms = (GetComposedObjectTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetComposedObjectTransforms")); + FHoudiniApi::GetCompositorOptions = (GetCompositorOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCompositorOptions")); + FHoudiniApi::GetConnectionError = (GetConnectionErrorFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionError")); + FHoudiniApi::GetConnectionErrorLength = (GetConnectionErrorLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetConnectionErrorLength")); + FHoudiniApi::GetCookingCurrentCount = (GetCookingCurrentCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingCurrentCount")); + FHoudiniApi::GetCookingTotalCount = (GetCookingTotalCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCookingTotalCount")); + FHoudiniApi::GetCurveCounts = (GetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveCounts")); + FHoudiniApi::GetCurveInfo = (GetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveInfo")); + FHoudiniApi::GetCurveKnots = (GetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveKnots")); + FHoudiniApi::GetCurveOrders = (GetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetCurveOrders")); + FHoudiniApi::GetDisplayGeoInfo = (GetDisplayGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetDisplayGeoInfo")); + FHoudiniApi::GetEdgeCountOfEdgeGroup = (GetEdgeCountOfEdgeGroupFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEdgeCountOfEdgeGroup")); + FHoudiniApi::GetEnvInt = (GetEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetEnvInt")); + FHoudiniApi::GetFaceCounts = (GetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFaceCounts")); + FHoudiniApi::GetFirstVolumeTile = (GetFirstVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetFirstVolumeTile")); + FHoudiniApi::GetGeoInfo = (GetGeoInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoInfo")); + FHoudiniApi::GetGeoSize = (GetGeoSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGeoSize")); + FHoudiniApi::GetGroupCountOnPackedInstancePart = (GetGroupCountOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupCountOnPackedInstancePart")); + FHoudiniApi::GetGroupMembership = (GetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembership")); + FHoudiniApi::GetGroupMembershipOnPackedInstancePart = (GetGroupMembershipOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupMembershipOnPackedInstancePart")); + FHoudiniApi::GetGroupNames = (GetGroupNamesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNames")); + FHoudiniApi::GetGroupNamesOnPackedInstancePart = (GetGroupNamesOnPackedInstancePartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetGroupNamesOnPackedInstancePart")); + FHoudiniApi::GetHIPFileNodeCount = (GetHIPFileNodeCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeCount")); + FHoudiniApi::GetHIPFileNodeIds = (GetHIPFileNodeIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHIPFileNodeIds")); + FHoudiniApi::GetHandleBindingInfo = (GetHandleBindingInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleBindingInfo")); + FHoudiniApi::GetHandleInfo = (GetHandleInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHandleInfo")); + FHoudiniApi::GetHeightFieldData = (GetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetHeightFieldData")); + FHoudiniApi::GetImageFilePath = (GetImageFilePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageFilePath")); + FHoudiniApi::GetImageInfo = (GetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageInfo")); + FHoudiniApi::GetImageMemoryBuffer = (GetImageMemoryBufferFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImageMemoryBuffer")); + FHoudiniApi::GetImagePlaneCount = (GetImagePlaneCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlaneCount")); + FHoudiniApi::GetImagePlanes = (GetImagePlanesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetImagePlanes")); + FHoudiniApi::GetInstanceTransformsOnPart = (GetInstanceTransformsOnPartFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstanceTransformsOnPart")); + FHoudiniApi::GetInstancedObjectIds = (GetInstancedObjectIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedObjectIds")); + FHoudiniApi::GetInstancedPartIds = (GetInstancedPartIdsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancedPartIds")); + FHoudiniApi::GetInstancerPartTransforms = (GetInstancerPartTransformsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetInstancerPartTransforms")); + FHoudiniApi::GetManagerNodeId = (GetManagerNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetManagerNodeId")); + FHoudiniApi::GetMaterialInfo = (GetMaterialInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialInfo")); + FHoudiniApi::GetMaterialNodeIdsOnFaces = (GetMaterialNodeIdsOnFacesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetMaterialNodeIdsOnFaces")); + FHoudiniApi::GetNextVolumeTile = (GetNextVolumeTileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNextVolumeTile")); + FHoudiniApi::GetNodeInfo = (GetNodeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInfo")); + FHoudiniApi::GetNodeInputName = (GetNodeInputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeInputName")); + FHoudiniApi::GetNodeOutputName = (GetNodeOutputNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodeOutputName")); + FHoudiniApi::GetNodePath = (GetNodePathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNodePath")); + FHoudiniApi::GetNumWorkitems = (GetNumWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetNumWorkitems")); + FHoudiniApi::GetObjectInfo = (GetObjectInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectInfo")); + FHoudiniApi::GetObjectTransform = (GetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetObjectTransform")); + FHoudiniApi::GetOutputGeoCount = (GetOutputGeoCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputGeoCount")); + FHoudiniApi::GetOutputGeoInfos = (GetOutputGeoInfosFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputGeoInfos")); + FHoudiniApi::GetOutputNodeId = (GetOutputNodeIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetOutputNodeId")); + FHoudiniApi::GetPDGEvents = (GetPDGEventsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGEvents")); + FHoudiniApi::GetPDGGraphContextId = (GetPDGGraphContextIdFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContextId")); + FHoudiniApi::GetPDGGraphContexts = (GetPDGGraphContextsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGGraphContexts")); + FHoudiniApi::GetPDGState = (GetPDGStateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPDGState")); + FHoudiniApi::GetParameters = (GetParametersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParameters")); + FHoudiniApi::GetParmChoiceLists = (GetParmChoiceListsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmChoiceLists")); + FHoudiniApi::GetParmExpression = (GetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmExpression")); + FHoudiniApi::GetParmFile = (GetParmFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFile")); + FHoudiniApi::GetParmFloatValue = (GetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValue")); + FHoudiniApi::GetParmFloatValues = (GetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmFloatValues")); + FHoudiniApi::GetParmIdFromName = (GetParmIdFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIdFromName")); + FHoudiniApi::GetParmInfo = (GetParmInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfo")); + FHoudiniApi::GetParmInfoFromName = (GetParmInfoFromNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmInfoFromName")); + FHoudiniApi::GetParmIntValue = (GetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValue")); + FHoudiniApi::GetParmIntValues = (GetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmIntValues")); + FHoudiniApi::GetParmNodeValue = (GetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmNodeValue")); + FHoudiniApi::GetParmStringValue = (GetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValue")); + FHoudiniApi::GetParmStringValues = (GetParmStringValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmStringValues")); + FHoudiniApi::GetParmTagName = (GetParmTagNameFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagName")); + FHoudiniApi::GetParmTagValue = (GetParmTagValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmTagValue")); + FHoudiniApi::GetParmWithTag = (GetParmWithTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetParmWithTag")); + FHoudiniApi::GetPartInfo = (GetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPartInfo")); + FHoudiniApi::GetPreset = (GetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPreset")); + FHoudiniApi::GetPresetBufLength = (GetPresetBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetPresetBufLength")); + FHoudiniApi::GetServerEnvInt = (GetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvInt")); + FHoudiniApi::GetServerEnvString = (GetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvString")); + FHoudiniApi::GetServerEnvVarCount = (GetServerEnvVarCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarCount")); + FHoudiniApi::GetServerEnvVarList = (GetServerEnvVarListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetServerEnvVarList")); + FHoudiniApi::GetSessionEnvInt = (GetSessionEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionEnvInt")); + FHoudiniApi::GetSessionSyncInfo = (GetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSessionSyncInfo")); + FHoudiniApi::GetSphereInfo = (GetSphereInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSphereInfo")); + FHoudiniApi::GetStatus = (GetStatusFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatus")); + FHoudiniApi::GetStatusString = (GetStatusStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusString")); + FHoudiniApi::GetStatusStringBufLength = (GetStatusStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStatusStringBufLength")); + FHoudiniApi::GetString = (GetStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetString")); + FHoudiniApi::GetStringBatch = (GetStringBatchFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatch")); + FHoudiniApi::GetStringBatchSize = (GetStringBatchSizeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBatchSize")); + FHoudiniApi::GetStringBufLength = (GetStringBufLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetStringBufLength")); + FHoudiniApi::GetSupportedImageFileFormatCount = (GetSupportedImageFileFormatCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormatCount")); + FHoudiniApi::GetSupportedImageFileFormats = (GetSupportedImageFileFormatsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetSupportedImageFileFormats")); + FHoudiniApi::GetTime = (GetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTime")); + FHoudiniApi::GetTimelineOptions = (GetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTimelineOptions")); + FHoudiniApi::GetTotalCookCount = (GetTotalCookCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetTotalCookCount")); + FHoudiniApi::GetUseHoudiniTime = (GetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetUseHoudiniTime")); + FHoudiniApi::GetVertexList = (GetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVertexList")); + FHoudiniApi::GetViewport = (GetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetViewport")); + FHoudiniApi::GetVolumeBounds = (GetVolumeBoundsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeBounds")); + FHoudiniApi::GetVolumeInfo = (GetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeInfo")); + FHoudiniApi::GetVolumeTileFloatData = (GetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileFloatData")); + FHoudiniApi::GetVolumeTileIntData = (GetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeTileIntData")); + FHoudiniApi::GetVolumeVisualInfo = (GetVolumeVisualInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVisualInfo")); + FHoudiniApi::GetVolumeVoxelFloatData = (GetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelFloatData")); + FHoudiniApi::GetVolumeVoxelIntData = (GetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetVolumeVoxelIntData")); + FHoudiniApi::GetWorkitemDataLength = (GetWorkitemDataLengthFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemDataLength")); + FHoudiniApi::GetWorkitemFloatData = (GetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemFloatData")); + FHoudiniApi::GetWorkitemInfo = (GetWorkitemInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemInfo")); + FHoudiniApi::GetWorkitemIntData = (GetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemIntData")); + FHoudiniApi::GetWorkitemResultInfo = (GetWorkitemResultInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemResultInfo")); + FHoudiniApi::GetWorkitemStringData = (GetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitemStringData")); + FHoudiniApi::GetWorkitems = (GetWorkitemsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_GetWorkitems")); + FHoudiniApi::HandleBindingInfo_Create = (HandleBindingInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Create")); + FHoudiniApi::HandleBindingInfo_Init = (HandleBindingInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleBindingInfo_Init")); + FHoudiniApi::HandleInfo_Create = (HandleInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Create")); + FHoudiniApi::HandleInfo_Init = (HandleInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_HandleInfo_Init")); + FHoudiniApi::ImageFileFormat_Create = (ImageFileFormat_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Create")); + FHoudiniApi::ImageFileFormat_Init = (ImageFileFormat_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageFileFormat_Init")); + FHoudiniApi::ImageInfo_Create = (ImageInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Create")); + FHoudiniApi::ImageInfo_Init = (ImageInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ImageInfo_Init")); + FHoudiniApi::Initialize = (InitializeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Initialize")); + FHoudiniApi::InsertMultiparmInstance = (InsertMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_InsertMultiparmInstance")); + FHoudiniApi::Interrupt = (InterruptFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Interrupt")); + FHoudiniApi::IsInitialized = (IsInitializedFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsInitialized")); + FHoudiniApi::IsNodeValid = (IsNodeValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsNodeValid")); + FHoudiniApi::IsSessionValid = (IsSessionValidFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_IsSessionValid")); + FHoudiniApi::Keyframe_Create = (Keyframe_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Create")); + FHoudiniApi::Keyframe_Init = (Keyframe_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Keyframe_Init")); + FHoudiniApi::LoadAssetLibraryFromFile = (LoadAssetLibraryFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromFile")); + FHoudiniApi::LoadAssetLibraryFromMemory = (LoadAssetLibraryFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadAssetLibraryFromMemory")); + FHoudiniApi::LoadGeoFromFile = (LoadGeoFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromFile")); + FHoudiniApi::LoadGeoFromMemory = (LoadGeoFromMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadGeoFromMemory")); + FHoudiniApi::LoadHIPFile = (LoadHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadHIPFile")); + FHoudiniApi::LoadNodeFromFile = (LoadNodeFromFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_LoadNodeFromFile")); + FHoudiniApi::MaterialInfo_Create = (MaterialInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Create")); + FHoudiniApi::MaterialInfo_Init = (MaterialInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MaterialInfo_Init")); + FHoudiniApi::MergeHIPFile = (MergeHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_MergeHIPFile")); + FHoudiniApi::NodeInfo_Create = (NodeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Create")); + FHoudiniApi::NodeInfo_Init = (NodeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_NodeInfo_Init")); + FHoudiniApi::ObjectInfo_Create = (ObjectInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Create")); + FHoudiniApi::ObjectInfo_Init = (ObjectInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ObjectInfo_Init")); + FHoudiniApi::ParmChoiceInfo_Create = (ParmChoiceInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Create")); + FHoudiniApi::ParmChoiceInfo_Init = (ParmChoiceInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmChoiceInfo_Init")); + FHoudiniApi::ParmHasExpression = (ParmHasExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasExpression")); + FHoudiniApi::ParmHasTag = (ParmHasTagFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmHasTag")); + FHoudiniApi::ParmInfo_Create = (ParmInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Create")); + FHoudiniApi::ParmInfo_GetFloatValueCount = (ParmInfo_GetFloatValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetFloatValueCount")); + FHoudiniApi::ParmInfo_GetIntValueCount = (ParmInfo_GetIntValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetIntValueCount")); + FHoudiniApi::ParmInfo_GetStringValueCount = (ParmInfo_GetStringValueCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_GetStringValueCount")); + FHoudiniApi::ParmInfo_Init = (ParmInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_Init")); + FHoudiniApi::ParmInfo_IsFloat = (ParmInfo_IsFloatFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsFloat")); + FHoudiniApi::ParmInfo_IsInt = (ParmInfo_IsIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsInt")); + FHoudiniApi::ParmInfo_IsNode = (ParmInfo_IsNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNode")); + FHoudiniApi::ParmInfo_IsNonValue = (ParmInfo_IsNonValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsNonValue")); + FHoudiniApi::ParmInfo_IsPath = (ParmInfo_IsPathFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsPath")); + FHoudiniApi::ParmInfo_IsString = (ParmInfo_IsStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ParmInfo_IsString")); + FHoudiniApi::PartInfo_Create = (PartInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Create")); + FHoudiniApi::PartInfo_GetAttributeCountByOwner = (PartInfo_GetAttributeCountByOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetAttributeCountByOwner")); + FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = (PartInfo_GetElementCountByAttributeOwnerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByAttributeOwner")); + FHoudiniApi::PartInfo_GetElementCountByGroupType = (PartInfo_GetElementCountByGroupTypeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_GetElementCountByGroupType")); + FHoudiniApi::PartInfo_Init = (PartInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PartInfo_Init")); + FHoudiniApi::PausePDGCook = (PausePDGCookFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PausePDGCook")); + FHoudiniApi::PythonThreadInterpreterLock = (PythonThreadInterpreterLockFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_PythonThreadInterpreterLock")); + FHoudiniApi::QueryNodeInput = (QueryNodeInputFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeInput")); + FHoudiniApi::QueryNodeOutputConnectedCount = (QueryNodeOutputConnectedCountFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedCount")); + FHoudiniApi::QueryNodeOutputConnectedNodes = (QueryNodeOutputConnectedNodesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_QueryNodeOutputConnectedNodes")); + FHoudiniApi::RemoveCustomString = (RemoveCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveCustomString")); + FHoudiniApi::RemoveMultiparmInstance = (RemoveMultiparmInstanceFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveMultiparmInstance")); + FHoudiniApi::RemoveParmExpression = (RemoveParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RemoveParmExpression")); + FHoudiniApi::RenameNode = (RenameNodeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenameNode")); + FHoudiniApi::RenderCOPToImage = (RenderCOPToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderCOPToImage")); + FHoudiniApi::RenderTextureToImage = (RenderTextureToImageFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RenderTextureToImage")); + FHoudiniApi::ResetSimulation = (ResetSimulationFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ResetSimulation")); + FHoudiniApi::RevertGeo = (RevertGeoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertGeo")); + FHoudiniApi::RevertParmToDefault = (RevertParmToDefaultFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefault")); + FHoudiniApi::RevertParmToDefaults = (RevertParmToDefaultsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_RevertParmToDefaults")); + FHoudiniApi::SaveGeoToFile = (SaveGeoToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToFile")); + FHoudiniApi::SaveGeoToMemory = (SaveGeoToMemoryFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveGeoToMemory")); + FHoudiniApi::SaveHIPFile = (SaveHIPFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveHIPFile")); + FHoudiniApi::SaveNodeToFile = (SaveNodeToFileFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SaveNodeToFile")); + FHoudiniApi::SessionSyncInfo_Create = (SessionSyncInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SessionSyncInfo_Create")); + FHoudiniApi::SetAnimCurve = (SetAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAnimCurve")); + FHoudiniApi::SetAttributeFloat64Data = (SetAttributeFloat64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloat64Data")); + FHoudiniApi::SetAttributeFloatData = (SetAttributeFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeFloatData")); + FHoudiniApi::SetAttributeInt16Data = (SetAttributeInt16DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt16Data")); + FHoudiniApi::SetAttributeInt64Data = (SetAttributeInt64DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt64Data")); + FHoudiniApi::SetAttributeInt8Data = (SetAttributeInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeInt8Data")); + FHoudiniApi::SetAttributeIntData = (SetAttributeIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeIntData")); + FHoudiniApi::SetAttributeStringData = (SetAttributeStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeStringData")); + FHoudiniApi::SetAttributeUInt8Data = (SetAttributeUInt8DataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetAttributeUInt8Data")); + FHoudiniApi::SetCacheProperty = (SetCachePropertyFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCacheProperty")); + FHoudiniApi::SetCompositorOptions = (SetCompositorOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCompositorOptions")); + FHoudiniApi::SetCurveCounts = (SetCurveCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveCounts")); + FHoudiniApi::SetCurveInfo = (SetCurveInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveInfo")); + FHoudiniApi::SetCurveKnots = (SetCurveKnotsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveKnots")); + FHoudiniApi::SetCurveOrders = (SetCurveOrdersFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCurveOrders")); + FHoudiniApi::SetCustomString = (SetCustomStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetCustomString")); + FHoudiniApi::SetFaceCounts = (SetFaceCountsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetFaceCounts")); + FHoudiniApi::SetGroupMembership = (SetGroupMembershipFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetGroupMembership")); + FHoudiniApi::SetHeightFieldData = (SetHeightFieldDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetHeightFieldData")); + FHoudiniApi::SetImageInfo = (SetImageInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetImageInfo")); + FHoudiniApi::SetNodeDisplay = (SetNodeDisplayFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetNodeDisplay")); + FHoudiniApi::SetObjectTransform = (SetObjectTransformFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetObjectTransform")); + FHoudiniApi::SetParmExpression = (SetParmExpressionFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmExpression")); + FHoudiniApi::SetParmFloatValue = (SetParmFloatValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValue")); + FHoudiniApi::SetParmFloatValues = (SetParmFloatValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmFloatValues")); + FHoudiniApi::SetParmIntValue = (SetParmIntValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValue")); + FHoudiniApi::SetParmIntValues = (SetParmIntValuesFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmIntValues")); + FHoudiniApi::SetParmNodeValue = (SetParmNodeValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmNodeValue")); + FHoudiniApi::SetParmStringValue = (SetParmStringValueFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetParmStringValue")); + FHoudiniApi::SetPartInfo = (SetPartInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPartInfo")); + FHoudiniApi::SetPreset = (SetPresetFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetPreset")); + FHoudiniApi::SetServerEnvInt = (SetServerEnvIntFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvInt")); + FHoudiniApi::SetServerEnvString = (SetServerEnvStringFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetServerEnvString")); + FHoudiniApi::SetSessionSync = (SetSessionSyncFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSync")); + FHoudiniApi::SetSessionSyncInfo = (SetSessionSyncInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetSessionSyncInfo")); + FHoudiniApi::SetTime = (SetTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTime")); + FHoudiniApi::SetTimelineOptions = (SetTimelineOptionsFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTimelineOptions")); + FHoudiniApi::SetTransformAnimCurve = (SetTransformAnimCurveFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetTransformAnimCurve")); + FHoudiniApi::SetUseHoudiniTime = (SetUseHoudiniTimeFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetUseHoudiniTime")); + FHoudiniApi::SetVertexList = (SetVertexListFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVertexList")); + FHoudiniApi::SetViewport = (SetViewportFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetViewport")); + FHoudiniApi::SetVolumeInfo = (SetVolumeInfoFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeInfo")); + FHoudiniApi::SetVolumeTileFloatData = (SetVolumeTileFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileFloatData")); + FHoudiniApi::SetVolumeTileIntData = (SetVolumeTileIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeTileIntData")); + FHoudiniApi::SetVolumeVoxelFloatData = (SetVolumeVoxelFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelFloatData")); + FHoudiniApi::SetVolumeVoxelIntData = (SetVolumeVoxelIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetVolumeVoxelIntData")); + FHoudiniApi::SetWorkitemFloatData = (SetWorkitemFloatDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemFloatData")); + FHoudiniApi::SetWorkitemIntData = (SetWorkitemIntDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemIntData")); + FHoudiniApi::SetWorkitemStringData = (SetWorkitemStringDataFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_SetWorkitemStringData")); + FHoudiniApi::StartThriftNamedPipeServer = (StartThriftNamedPipeServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftNamedPipeServer")); + FHoudiniApi::StartThriftSocketServer = (StartThriftSocketServerFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_StartThriftSocketServer")); + FHoudiniApi::ThriftServerOptions_Create = (ThriftServerOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Create")); + FHoudiniApi::ThriftServerOptions_Init = (ThriftServerOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_ThriftServerOptions_Init")); + FHoudiniApi::TimelineOptions_Create = (TimelineOptions_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Create")); + FHoudiniApi::TimelineOptions_Init = (TimelineOptions_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TimelineOptions_Init")); + FHoudiniApi::TransformEuler_Create = (TransformEuler_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Create")); + FHoudiniApi::TransformEuler_Init = (TransformEuler_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_TransformEuler_Init")); + FHoudiniApi::Transform_Create = (Transform_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Create")); + FHoudiniApi::Transform_Init = (Transform_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Transform_Init")); + FHoudiniApi::Viewport_Create = (Viewport_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_Viewport_Create")); + FHoudiniApi::VolumeInfo_Create = (VolumeInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Create")); + FHoudiniApi::VolumeInfo_Init = (VolumeInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeInfo_Init")); + FHoudiniApi::VolumeTileInfo_Create = (VolumeTileInfo_CreateFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Create")); + FHoudiniApi::VolumeTileInfo_Init = (VolumeTileInfo_InitFuncPtr) FPlatformProcess::GetDllExport(LibraryHandle, TEXT("HAPI_VolumeTileInfo_Init")); +} + + +void +FHoudiniApi::FinalizeHAPI() +{ + FHoudiniApi::AddAttribute = &FHoudiniApi::AddAttributeEmptyStub; + FHoudiniApi::AddGroup = &FHoudiniApi::AddGroupEmptyStub; + FHoudiniApi::AssetInfo_Create = &FHoudiniApi::AssetInfo_CreateEmptyStub; + FHoudiniApi::AssetInfo_Init = &FHoudiniApi::AssetInfo_InitEmptyStub; + FHoudiniApi::AttributeInfo_Create = &FHoudiniApi::AttributeInfo_CreateEmptyStub; + FHoudiniApi::AttributeInfo_Init = &FHoudiniApi::AttributeInfo_InitEmptyStub; + FHoudiniApi::BindCustomImplementation = &FHoudiniApi::BindCustomImplementationEmptyStub; + FHoudiniApi::CancelPDGCook = &FHoudiniApi::CancelPDGCookEmptyStub; + FHoudiniApi::CheckForSpecificErrors = &FHoudiniApi::CheckForSpecificErrorsEmptyStub; + FHoudiniApi::Cleanup = &FHoudiniApi::CleanupEmptyStub; + FHoudiniApi::ClearConnectionError = &FHoudiniApi::ClearConnectionErrorEmptyStub; + FHoudiniApi::CloseSession = &FHoudiniApi::CloseSessionEmptyStub; + FHoudiniApi::CommitGeo = &FHoudiniApi::CommitGeoEmptyStub; + FHoudiniApi::CommitWorkitems = &FHoudiniApi::CommitWorkitemsEmptyStub; + FHoudiniApi::ComposeChildNodeList = &FHoudiniApi::ComposeChildNodeListEmptyStub; + FHoudiniApi::ComposeNodeCookResult = &FHoudiniApi::ComposeNodeCookResultEmptyStub; + FHoudiniApi::ComposeObjectList = &FHoudiniApi::ComposeObjectListEmptyStub; + FHoudiniApi::CompositorOptions_Create = &FHoudiniApi::CompositorOptions_CreateEmptyStub; + FHoudiniApi::CompositorOptions_Init = &FHoudiniApi::CompositorOptions_InitEmptyStub; + FHoudiniApi::ConnectNodeInput = &FHoudiniApi::ConnectNodeInputEmptyStub; + FHoudiniApi::ConvertMatrixToEuler = &FHoudiniApi::ConvertMatrixToEulerEmptyStub; + FHoudiniApi::ConvertMatrixToQuat = &FHoudiniApi::ConvertMatrixToQuatEmptyStub; + FHoudiniApi::ConvertTransform = &FHoudiniApi::ConvertTransformEmptyStub; + FHoudiniApi::ConvertTransformEulerToMatrix = &FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub; + FHoudiniApi::ConvertTransformQuatToMatrix = &FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub; + FHoudiniApi::CookNode = &FHoudiniApi::CookNodeEmptyStub; + FHoudiniApi::CookOptions_AreEqual = &FHoudiniApi::CookOptions_AreEqualEmptyStub; + FHoudiniApi::CookOptions_Create = &FHoudiniApi::CookOptions_CreateEmptyStub; + FHoudiniApi::CookOptions_Init = &FHoudiniApi::CookOptions_InitEmptyStub; + FHoudiniApi::CookPDG = &FHoudiniApi::CookPDGEmptyStub; + FHoudiniApi::CreateCustomSession = &FHoudiniApi::CreateCustomSessionEmptyStub; + FHoudiniApi::CreateHeightFieldInput = &FHoudiniApi::CreateHeightFieldInputEmptyStub; + FHoudiniApi::CreateHeightfieldInputVolumeNode = &FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub; + FHoudiniApi::CreateInProcessSession = &FHoudiniApi::CreateInProcessSessionEmptyStub; + FHoudiniApi::CreateInputNode = &FHoudiniApi::CreateInputNodeEmptyStub; + FHoudiniApi::CreateNode = &FHoudiniApi::CreateNodeEmptyStub; + FHoudiniApi::CreateThriftNamedPipeSession = &FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub; + FHoudiniApi::CreateThriftSocketSession = &FHoudiniApi::CreateThriftSocketSessionEmptyStub; + FHoudiniApi::CreateWorkitem = &FHoudiniApi::CreateWorkitemEmptyStub; + FHoudiniApi::CurveInfo_Create = &FHoudiniApi::CurveInfo_CreateEmptyStub; + FHoudiniApi::CurveInfo_Init = &FHoudiniApi::CurveInfo_InitEmptyStub; + FHoudiniApi::DeleteAttribute = &FHoudiniApi::DeleteAttributeEmptyStub; + FHoudiniApi::DeleteGroup = &FHoudiniApi::DeleteGroupEmptyStub; + FHoudiniApi::DeleteNode = &FHoudiniApi::DeleteNodeEmptyStub; + FHoudiniApi::DirtyPDGNode = &FHoudiniApi::DirtyPDGNodeEmptyStub; + FHoudiniApi::DisconnectNodeInput = &FHoudiniApi::DisconnectNodeInputEmptyStub; + FHoudiniApi::DisconnectNodeOutputsAt = &FHoudiniApi::DisconnectNodeOutputsAtEmptyStub; + FHoudiniApi::ExtractImageToFile = &FHoudiniApi::ExtractImageToFileEmptyStub; + FHoudiniApi::ExtractImageToMemory = &FHoudiniApi::ExtractImageToMemoryEmptyStub; + FHoudiniApi::GeoInfo_Create = &FHoudiniApi::GeoInfo_CreateEmptyStub; + FHoudiniApi::GeoInfo_GetGroupCountByType = &FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub; + FHoudiniApi::GeoInfo_Init = &FHoudiniApi::GeoInfo_InitEmptyStub; + FHoudiniApi::GetActiveCacheCount = &FHoudiniApi::GetActiveCacheCountEmptyStub; + FHoudiniApi::GetActiveCacheNames = &FHoudiniApi::GetActiveCacheNamesEmptyStub; + FHoudiniApi::GetAssetDefinitionParmCounts = &FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub; + FHoudiniApi::GetAssetDefinitionParmInfos = &FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub; + FHoudiniApi::GetAssetDefinitionParmValues = &FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub; + FHoudiniApi::GetAssetInfo = &FHoudiniApi::GetAssetInfoEmptyStub; + FHoudiniApi::GetAttributeFloat64ArrayData = &FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub; + FHoudiniApi::GetAttributeFloat64Data = &FHoudiniApi::GetAttributeFloat64DataEmptyStub; + FHoudiniApi::GetAttributeFloatArrayData = &FHoudiniApi::GetAttributeFloatArrayDataEmptyStub; + FHoudiniApi::GetAttributeFloatData = &FHoudiniApi::GetAttributeFloatDataEmptyStub; + FHoudiniApi::GetAttributeInfo = &FHoudiniApi::GetAttributeInfoEmptyStub; + FHoudiniApi::GetAttributeInt16ArrayData = &FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt16Data = &FHoudiniApi::GetAttributeInt16DataEmptyStub; + FHoudiniApi::GetAttributeInt64ArrayData = &FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt64Data = &FHoudiniApi::GetAttributeInt64DataEmptyStub; + FHoudiniApi::GetAttributeInt8ArrayData = &FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub; + FHoudiniApi::GetAttributeInt8Data = &FHoudiniApi::GetAttributeInt8DataEmptyStub; + FHoudiniApi::GetAttributeIntArrayData = &FHoudiniApi::GetAttributeIntArrayDataEmptyStub; + FHoudiniApi::GetAttributeIntData = &FHoudiniApi::GetAttributeIntDataEmptyStub; + FHoudiniApi::GetAttributeNames = &FHoudiniApi::GetAttributeNamesEmptyStub; + FHoudiniApi::GetAttributeStringArrayData = &FHoudiniApi::GetAttributeStringArrayDataEmptyStub; + FHoudiniApi::GetAttributeStringData = &FHoudiniApi::GetAttributeStringDataEmptyStub; + FHoudiniApi::GetAttributeUInt8ArrayData = &FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub; + FHoudiniApi::GetAttributeUInt8Data = &FHoudiniApi::GetAttributeUInt8DataEmptyStub; + FHoudiniApi::GetAvailableAssetCount = &FHoudiniApi::GetAvailableAssetCountEmptyStub; + FHoudiniApi::GetAvailableAssets = &FHoudiniApi::GetAvailableAssetsEmptyStub; + FHoudiniApi::GetBoxInfo = &FHoudiniApi::GetBoxInfoEmptyStub; + FHoudiniApi::GetCacheProperty = &FHoudiniApi::GetCachePropertyEmptyStub; + FHoudiniApi::GetComposedChildNodeList = &FHoudiniApi::GetComposedChildNodeListEmptyStub; + FHoudiniApi::GetComposedNodeCookResult = &FHoudiniApi::GetComposedNodeCookResultEmptyStub; + FHoudiniApi::GetComposedObjectList = &FHoudiniApi::GetComposedObjectListEmptyStub; + FHoudiniApi::GetComposedObjectTransforms = &FHoudiniApi::GetComposedObjectTransformsEmptyStub; + FHoudiniApi::GetCompositorOptions = &FHoudiniApi::GetCompositorOptionsEmptyStub; + FHoudiniApi::GetConnectionError = &FHoudiniApi::GetConnectionErrorEmptyStub; + FHoudiniApi::GetConnectionErrorLength = &FHoudiniApi::GetConnectionErrorLengthEmptyStub; + FHoudiniApi::GetCookingCurrentCount = &FHoudiniApi::GetCookingCurrentCountEmptyStub; + FHoudiniApi::GetCookingTotalCount = &FHoudiniApi::GetCookingTotalCountEmptyStub; + FHoudiniApi::GetCurveCounts = &FHoudiniApi::GetCurveCountsEmptyStub; + FHoudiniApi::GetCurveInfo = &FHoudiniApi::GetCurveInfoEmptyStub; + FHoudiniApi::GetCurveKnots = &FHoudiniApi::GetCurveKnotsEmptyStub; + FHoudiniApi::GetCurveOrders = &FHoudiniApi::GetCurveOrdersEmptyStub; + FHoudiniApi::GetDisplayGeoInfo = &FHoudiniApi::GetDisplayGeoInfoEmptyStub; + FHoudiniApi::GetEdgeCountOfEdgeGroup = &FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub; + FHoudiniApi::GetEnvInt = &FHoudiniApi::GetEnvIntEmptyStub; + FHoudiniApi::GetFaceCounts = &FHoudiniApi::GetFaceCountsEmptyStub; + FHoudiniApi::GetFirstVolumeTile = &FHoudiniApi::GetFirstVolumeTileEmptyStub; + FHoudiniApi::GetGeoInfo = &FHoudiniApi::GetGeoInfoEmptyStub; + FHoudiniApi::GetGeoSize = &FHoudiniApi::GetGeoSizeEmptyStub; + FHoudiniApi::GetGroupCountOnPackedInstancePart = &FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub; + FHoudiniApi::GetGroupMembership = &FHoudiniApi::GetGroupMembershipEmptyStub; + FHoudiniApi::GetGroupMembershipOnPackedInstancePart = &FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub; + FHoudiniApi::GetGroupNames = &FHoudiniApi::GetGroupNamesEmptyStub; + FHoudiniApi::GetGroupNamesOnPackedInstancePart = &FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub; + FHoudiniApi::GetHIPFileNodeCount = &FHoudiniApi::GetHIPFileNodeCountEmptyStub; + FHoudiniApi::GetHIPFileNodeIds = &FHoudiniApi::GetHIPFileNodeIdsEmptyStub; + FHoudiniApi::GetHandleBindingInfo = &FHoudiniApi::GetHandleBindingInfoEmptyStub; + FHoudiniApi::GetHandleInfo = &FHoudiniApi::GetHandleInfoEmptyStub; + FHoudiniApi::GetHeightFieldData = &FHoudiniApi::GetHeightFieldDataEmptyStub; + FHoudiniApi::GetImageFilePath = &FHoudiniApi::GetImageFilePathEmptyStub; + FHoudiniApi::GetImageInfo = &FHoudiniApi::GetImageInfoEmptyStub; + FHoudiniApi::GetImageMemoryBuffer = &FHoudiniApi::GetImageMemoryBufferEmptyStub; + FHoudiniApi::GetImagePlaneCount = &FHoudiniApi::GetImagePlaneCountEmptyStub; + FHoudiniApi::GetImagePlanes = &FHoudiniApi::GetImagePlanesEmptyStub; + FHoudiniApi::GetInstanceTransformsOnPart = &FHoudiniApi::GetInstanceTransformsOnPartEmptyStub; + FHoudiniApi::GetInstancedObjectIds = &FHoudiniApi::GetInstancedObjectIdsEmptyStub; + FHoudiniApi::GetInstancedPartIds = &FHoudiniApi::GetInstancedPartIdsEmptyStub; + FHoudiniApi::GetInstancerPartTransforms = &FHoudiniApi::GetInstancerPartTransformsEmptyStub; + FHoudiniApi::GetManagerNodeId = &FHoudiniApi::GetManagerNodeIdEmptyStub; + FHoudiniApi::GetMaterialInfo = &FHoudiniApi::GetMaterialInfoEmptyStub; + FHoudiniApi::GetMaterialNodeIdsOnFaces = &FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub; + FHoudiniApi::GetNextVolumeTile = &FHoudiniApi::GetNextVolumeTileEmptyStub; + FHoudiniApi::GetNodeInfo = &FHoudiniApi::GetNodeInfoEmptyStub; + FHoudiniApi::GetNodeInputName = &FHoudiniApi::GetNodeInputNameEmptyStub; + FHoudiniApi::GetNodeOutputName = &FHoudiniApi::GetNodeOutputNameEmptyStub; + FHoudiniApi::GetNodePath = &FHoudiniApi::GetNodePathEmptyStub; + FHoudiniApi::GetNumWorkitems = &FHoudiniApi::GetNumWorkitemsEmptyStub; + FHoudiniApi::GetObjectInfo = &FHoudiniApi::GetObjectInfoEmptyStub; + FHoudiniApi::GetObjectTransform = &FHoudiniApi::GetObjectTransformEmptyStub; + FHoudiniApi::GetOutputGeoCount = &FHoudiniApi::GetOutputGeoCountEmptyStub; + FHoudiniApi::GetOutputGeoInfos = &FHoudiniApi::GetOutputGeoInfosEmptyStub; + FHoudiniApi::GetOutputNodeId = &FHoudiniApi::GetOutputNodeIdEmptyStub; + FHoudiniApi::GetPDGEvents = &FHoudiniApi::GetPDGEventsEmptyStub; + FHoudiniApi::GetPDGGraphContextId = &FHoudiniApi::GetPDGGraphContextIdEmptyStub; + FHoudiniApi::GetPDGGraphContexts = &FHoudiniApi::GetPDGGraphContextsEmptyStub; + FHoudiniApi::GetPDGState = &FHoudiniApi::GetPDGStateEmptyStub; + FHoudiniApi::GetParameters = &FHoudiniApi::GetParametersEmptyStub; + FHoudiniApi::GetParmChoiceLists = &FHoudiniApi::GetParmChoiceListsEmptyStub; + FHoudiniApi::GetParmExpression = &FHoudiniApi::GetParmExpressionEmptyStub; + FHoudiniApi::GetParmFile = &FHoudiniApi::GetParmFileEmptyStub; + FHoudiniApi::GetParmFloatValue = &FHoudiniApi::GetParmFloatValueEmptyStub; + FHoudiniApi::GetParmFloatValues = &FHoudiniApi::GetParmFloatValuesEmptyStub; + FHoudiniApi::GetParmIdFromName = &FHoudiniApi::GetParmIdFromNameEmptyStub; + FHoudiniApi::GetParmInfo = &FHoudiniApi::GetParmInfoEmptyStub; + FHoudiniApi::GetParmInfoFromName = &FHoudiniApi::GetParmInfoFromNameEmptyStub; + FHoudiniApi::GetParmIntValue = &FHoudiniApi::GetParmIntValueEmptyStub; + FHoudiniApi::GetParmIntValues = &FHoudiniApi::GetParmIntValuesEmptyStub; + FHoudiniApi::GetParmNodeValue = &FHoudiniApi::GetParmNodeValueEmptyStub; + FHoudiniApi::GetParmStringValue = &FHoudiniApi::GetParmStringValueEmptyStub; + FHoudiniApi::GetParmStringValues = &FHoudiniApi::GetParmStringValuesEmptyStub; + FHoudiniApi::GetParmTagName = &FHoudiniApi::GetParmTagNameEmptyStub; + FHoudiniApi::GetParmTagValue = &FHoudiniApi::GetParmTagValueEmptyStub; + FHoudiniApi::GetParmWithTag = &FHoudiniApi::GetParmWithTagEmptyStub; + FHoudiniApi::GetPartInfo = &FHoudiniApi::GetPartInfoEmptyStub; + FHoudiniApi::GetPreset = &FHoudiniApi::GetPresetEmptyStub; + FHoudiniApi::GetPresetBufLength = &FHoudiniApi::GetPresetBufLengthEmptyStub; + FHoudiniApi::GetServerEnvInt = &FHoudiniApi::GetServerEnvIntEmptyStub; + FHoudiniApi::GetServerEnvString = &FHoudiniApi::GetServerEnvStringEmptyStub; + FHoudiniApi::GetServerEnvVarCount = &FHoudiniApi::GetServerEnvVarCountEmptyStub; + FHoudiniApi::GetServerEnvVarList = &FHoudiniApi::GetServerEnvVarListEmptyStub; + FHoudiniApi::GetSessionEnvInt = &FHoudiniApi::GetSessionEnvIntEmptyStub; + FHoudiniApi::GetSessionSyncInfo = &FHoudiniApi::GetSessionSyncInfoEmptyStub; + FHoudiniApi::GetSphereInfo = &FHoudiniApi::GetSphereInfoEmptyStub; + FHoudiniApi::GetStatus = &FHoudiniApi::GetStatusEmptyStub; + FHoudiniApi::GetStatusString = &FHoudiniApi::GetStatusStringEmptyStub; + FHoudiniApi::GetStatusStringBufLength = &FHoudiniApi::GetStatusStringBufLengthEmptyStub; + FHoudiniApi::GetString = &FHoudiniApi::GetStringEmptyStub; + FHoudiniApi::GetStringBatch = &FHoudiniApi::GetStringBatchEmptyStub; + FHoudiniApi::GetStringBatchSize = &FHoudiniApi::GetStringBatchSizeEmptyStub; + FHoudiniApi::GetStringBufLength = &FHoudiniApi::GetStringBufLengthEmptyStub; + FHoudiniApi::GetSupportedImageFileFormatCount = &FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub; + FHoudiniApi::GetSupportedImageFileFormats = &FHoudiniApi::GetSupportedImageFileFormatsEmptyStub; + FHoudiniApi::GetTime = &FHoudiniApi::GetTimeEmptyStub; + FHoudiniApi::GetTimelineOptions = &FHoudiniApi::GetTimelineOptionsEmptyStub; + FHoudiniApi::GetTotalCookCount = &FHoudiniApi::GetTotalCookCountEmptyStub; + FHoudiniApi::GetUseHoudiniTime = &FHoudiniApi::GetUseHoudiniTimeEmptyStub; + FHoudiniApi::GetVertexList = &FHoudiniApi::GetVertexListEmptyStub; + FHoudiniApi::GetViewport = &FHoudiniApi::GetViewportEmptyStub; + FHoudiniApi::GetVolumeBounds = &FHoudiniApi::GetVolumeBoundsEmptyStub; + FHoudiniApi::GetVolumeInfo = &FHoudiniApi::GetVolumeInfoEmptyStub; + FHoudiniApi::GetVolumeTileFloatData = &FHoudiniApi::GetVolumeTileFloatDataEmptyStub; + FHoudiniApi::GetVolumeTileIntData = &FHoudiniApi::GetVolumeTileIntDataEmptyStub; + FHoudiniApi::GetVolumeVisualInfo = &FHoudiniApi::GetVolumeVisualInfoEmptyStub; + FHoudiniApi::GetVolumeVoxelFloatData = &FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub; + FHoudiniApi::GetVolumeVoxelIntData = &FHoudiniApi::GetVolumeVoxelIntDataEmptyStub; + FHoudiniApi::GetWorkitemDataLength = &FHoudiniApi::GetWorkitemDataLengthEmptyStub; + FHoudiniApi::GetWorkitemFloatData = &FHoudiniApi::GetWorkitemFloatDataEmptyStub; + FHoudiniApi::GetWorkitemInfo = &FHoudiniApi::GetWorkitemInfoEmptyStub; + FHoudiniApi::GetWorkitemIntData = &FHoudiniApi::GetWorkitemIntDataEmptyStub; + FHoudiniApi::GetWorkitemResultInfo = &FHoudiniApi::GetWorkitemResultInfoEmptyStub; + FHoudiniApi::GetWorkitemStringData = &FHoudiniApi::GetWorkitemStringDataEmptyStub; + FHoudiniApi::GetWorkitems = &FHoudiniApi::GetWorkitemsEmptyStub; + FHoudiniApi::HandleBindingInfo_Create = &FHoudiniApi::HandleBindingInfo_CreateEmptyStub; + FHoudiniApi::HandleBindingInfo_Init = &FHoudiniApi::HandleBindingInfo_InitEmptyStub; + FHoudiniApi::HandleInfo_Create = &FHoudiniApi::HandleInfo_CreateEmptyStub; + FHoudiniApi::HandleInfo_Init = &FHoudiniApi::HandleInfo_InitEmptyStub; + FHoudiniApi::ImageFileFormat_Create = &FHoudiniApi::ImageFileFormat_CreateEmptyStub; + FHoudiniApi::ImageFileFormat_Init = &FHoudiniApi::ImageFileFormat_InitEmptyStub; + FHoudiniApi::ImageInfo_Create = &FHoudiniApi::ImageInfo_CreateEmptyStub; + FHoudiniApi::ImageInfo_Init = &FHoudiniApi::ImageInfo_InitEmptyStub; + FHoudiniApi::Initialize = &FHoudiniApi::InitializeEmptyStub; + FHoudiniApi::InsertMultiparmInstance = &FHoudiniApi::InsertMultiparmInstanceEmptyStub; + FHoudiniApi::Interrupt = &FHoudiniApi::InterruptEmptyStub; + FHoudiniApi::IsInitialized = &FHoudiniApi::IsInitializedEmptyStub; + FHoudiniApi::IsNodeValid = &FHoudiniApi::IsNodeValidEmptyStub; + FHoudiniApi::IsSessionValid = &FHoudiniApi::IsSessionValidEmptyStub; + FHoudiniApi::Keyframe_Create = &FHoudiniApi::Keyframe_CreateEmptyStub; + FHoudiniApi::Keyframe_Init = &FHoudiniApi::Keyframe_InitEmptyStub; + FHoudiniApi::LoadAssetLibraryFromFile = &FHoudiniApi::LoadAssetLibraryFromFileEmptyStub; + FHoudiniApi::LoadAssetLibraryFromMemory = &FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub; + FHoudiniApi::LoadGeoFromFile = &FHoudiniApi::LoadGeoFromFileEmptyStub; + FHoudiniApi::LoadGeoFromMemory = &FHoudiniApi::LoadGeoFromMemoryEmptyStub; + FHoudiniApi::LoadHIPFile = &FHoudiniApi::LoadHIPFileEmptyStub; + FHoudiniApi::LoadNodeFromFile = &FHoudiniApi::LoadNodeFromFileEmptyStub; + FHoudiniApi::MaterialInfo_Create = &FHoudiniApi::MaterialInfo_CreateEmptyStub; + FHoudiniApi::MaterialInfo_Init = &FHoudiniApi::MaterialInfo_InitEmptyStub; + FHoudiniApi::MergeHIPFile = &FHoudiniApi::MergeHIPFileEmptyStub; + FHoudiniApi::NodeInfo_Create = &FHoudiniApi::NodeInfo_CreateEmptyStub; + FHoudiniApi::NodeInfo_Init = &FHoudiniApi::NodeInfo_InitEmptyStub; + FHoudiniApi::ObjectInfo_Create = &FHoudiniApi::ObjectInfo_CreateEmptyStub; + FHoudiniApi::ObjectInfo_Init = &FHoudiniApi::ObjectInfo_InitEmptyStub; + FHoudiniApi::ParmChoiceInfo_Create = &FHoudiniApi::ParmChoiceInfo_CreateEmptyStub; + FHoudiniApi::ParmChoiceInfo_Init = &FHoudiniApi::ParmChoiceInfo_InitEmptyStub; + FHoudiniApi::ParmHasExpression = &FHoudiniApi::ParmHasExpressionEmptyStub; + FHoudiniApi::ParmHasTag = &FHoudiniApi::ParmHasTagEmptyStub; + FHoudiniApi::ParmInfo_Create = &FHoudiniApi::ParmInfo_CreateEmptyStub; + FHoudiniApi::ParmInfo_GetFloatValueCount = &FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub; + FHoudiniApi::ParmInfo_GetIntValueCount = &FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub; + FHoudiniApi::ParmInfo_GetStringValueCount = &FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub; + FHoudiniApi::ParmInfo_Init = &FHoudiniApi::ParmInfo_InitEmptyStub; + FHoudiniApi::ParmInfo_IsFloat = &FHoudiniApi::ParmInfo_IsFloatEmptyStub; + FHoudiniApi::ParmInfo_IsInt = &FHoudiniApi::ParmInfo_IsIntEmptyStub; + FHoudiniApi::ParmInfo_IsNode = &FHoudiniApi::ParmInfo_IsNodeEmptyStub; + FHoudiniApi::ParmInfo_IsNonValue = &FHoudiniApi::ParmInfo_IsNonValueEmptyStub; + FHoudiniApi::ParmInfo_IsPath = &FHoudiniApi::ParmInfo_IsPathEmptyStub; + FHoudiniApi::ParmInfo_IsString = &FHoudiniApi::ParmInfo_IsStringEmptyStub; + FHoudiniApi::PartInfo_Create = &FHoudiniApi::PartInfo_CreateEmptyStub; + FHoudiniApi::PartInfo_GetAttributeCountByOwner = &FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub; + FHoudiniApi::PartInfo_GetElementCountByAttributeOwner = &FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub; + FHoudiniApi::PartInfo_GetElementCountByGroupType = &FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub; + FHoudiniApi::PartInfo_Init = &FHoudiniApi::PartInfo_InitEmptyStub; + FHoudiniApi::PausePDGCook = &FHoudiniApi::PausePDGCookEmptyStub; + FHoudiniApi::PythonThreadInterpreterLock = &FHoudiniApi::PythonThreadInterpreterLockEmptyStub; + FHoudiniApi::QueryNodeInput = &FHoudiniApi::QueryNodeInputEmptyStub; + FHoudiniApi::QueryNodeOutputConnectedCount = &FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub; + FHoudiniApi::QueryNodeOutputConnectedNodes = &FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub; + FHoudiniApi::RemoveCustomString = &FHoudiniApi::RemoveCustomStringEmptyStub; + FHoudiniApi::RemoveMultiparmInstance = &FHoudiniApi::RemoveMultiparmInstanceEmptyStub; + FHoudiniApi::RemoveParmExpression = &FHoudiniApi::RemoveParmExpressionEmptyStub; + FHoudiniApi::RenameNode = &FHoudiniApi::RenameNodeEmptyStub; + FHoudiniApi::RenderCOPToImage = &FHoudiniApi::RenderCOPToImageEmptyStub; + FHoudiniApi::RenderTextureToImage = &FHoudiniApi::RenderTextureToImageEmptyStub; + FHoudiniApi::ResetSimulation = &FHoudiniApi::ResetSimulationEmptyStub; + FHoudiniApi::RevertGeo = &FHoudiniApi::RevertGeoEmptyStub; + FHoudiniApi::RevertParmToDefault = &FHoudiniApi::RevertParmToDefaultEmptyStub; + FHoudiniApi::RevertParmToDefaults = &FHoudiniApi::RevertParmToDefaultsEmptyStub; + FHoudiniApi::SaveGeoToFile = &FHoudiniApi::SaveGeoToFileEmptyStub; + FHoudiniApi::SaveGeoToMemory = &FHoudiniApi::SaveGeoToMemoryEmptyStub; + FHoudiniApi::SaveHIPFile = &FHoudiniApi::SaveHIPFileEmptyStub; + FHoudiniApi::SaveNodeToFile = &FHoudiniApi::SaveNodeToFileEmptyStub; + FHoudiniApi::SessionSyncInfo_Create = &FHoudiniApi::SessionSyncInfo_CreateEmptyStub; + FHoudiniApi::SetAnimCurve = &FHoudiniApi::SetAnimCurveEmptyStub; + FHoudiniApi::SetAttributeFloat64Data = &FHoudiniApi::SetAttributeFloat64DataEmptyStub; + FHoudiniApi::SetAttributeFloatData = &FHoudiniApi::SetAttributeFloatDataEmptyStub; + FHoudiniApi::SetAttributeInt16Data = &FHoudiniApi::SetAttributeInt16DataEmptyStub; + FHoudiniApi::SetAttributeInt64Data = &FHoudiniApi::SetAttributeInt64DataEmptyStub; + FHoudiniApi::SetAttributeInt8Data = &FHoudiniApi::SetAttributeInt8DataEmptyStub; + FHoudiniApi::SetAttributeIntData = &FHoudiniApi::SetAttributeIntDataEmptyStub; + FHoudiniApi::SetAttributeStringData = &FHoudiniApi::SetAttributeStringDataEmptyStub; + FHoudiniApi::SetAttributeUInt8Data = &FHoudiniApi::SetAttributeUInt8DataEmptyStub; + FHoudiniApi::SetCacheProperty = &FHoudiniApi::SetCachePropertyEmptyStub; + FHoudiniApi::SetCompositorOptions = &FHoudiniApi::SetCompositorOptionsEmptyStub; + FHoudiniApi::SetCurveCounts = &FHoudiniApi::SetCurveCountsEmptyStub; + FHoudiniApi::SetCurveInfo = &FHoudiniApi::SetCurveInfoEmptyStub; + FHoudiniApi::SetCurveKnots = &FHoudiniApi::SetCurveKnotsEmptyStub; + FHoudiniApi::SetCurveOrders = &FHoudiniApi::SetCurveOrdersEmptyStub; + FHoudiniApi::SetCustomString = &FHoudiniApi::SetCustomStringEmptyStub; + FHoudiniApi::SetFaceCounts = &FHoudiniApi::SetFaceCountsEmptyStub; + FHoudiniApi::SetGroupMembership = &FHoudiniApi::SetGroupMembershipEmptyStub; + FHoudiniApi::SetHeightFieldData = &FHoudiniApi::SetHeightFieldDataEmptyStub; + FHoudiniApi::SetImageInfo = &FHoudiniApi::SetImageInfoEmptyStub; + FHoudiniApi::SetNodeDisplay = &FHoudiniApi::SetNodeDisplayEmptyStub; + FHoudiniApi::SetObjectTransform = &FHoudiniApi::SetObjectTransformEmptyStub; + FHoudiniApi::SetParmExpression = &FHoudiniApi::SetParmExpressionEmptyStub; + FHoudiniApi::SetParmFloatValue = &FHoudiniApi::SetParmFloatValueEmptyStub; + FHoudiniApi::SetParmFloatValues = &FHoudiniApi::SetParmFloatValuesEmptyStub; + FHoudiniApi::SetParmIntValue = &FHoudiniApi::SetParmIntValueEmptyStub; + FHoudiniApi::SetParmIntValues = &FHoudiniApi::SetParmIntValuesEmptyStub; + FHoudiniApi::SetParmNodeValue = &FHoudiniApi::SetParmNodeValueEmptyStub; + FHoudiniApi::SetParmStringValue = &FHoudiniApi::SetParmStringValueEmptyStub; + FHoudiniApi::SetPartInfo = &FHoudiniApi::SetPartInfoEmptyStub; + FHoudiniApi::SetPreset = &FHoudiniApi::SetPresetEmptyStub; + FHoudiniApi::SetServerEnvInt = &FHoudiniApi::SetServerEnvIntEmptyStub; + FHoudiniApi::SetServerEnvString = &FHoudiniApi::SetServerEnvStringEmptyStub; + FHoudiniApi::SetSessionSync = &FHoudiniApi::SetSessionSyncEmptyStub; + FHoudiniApi::SetSessionSyncInfo = &FHoudiniApi::SetSessionSyncInfoEmptyStub; + FHoudiniApi::SetTime = &FHoudiniApi::SetTimeEmptyStub; + FHoudiniApi::SetTimelineOptions = &FHoudiniApi::SetTimelineOptionsEmptyStub; + FHoudiniApi::SetTransformAnimCurve = &FHoudiniApi::SetTransformAnimCurveEmptyStub; + FHoudiniApi::SetUseHoudiniTime = &FHoudiniApi::SetUseHoudiniTimeEmptyStub; + FHoudiniApi::SetVertexList = &FHoudiniApi::SetVertexListEmptyStub; + FHoudiniApi::SetViewport = &FHoudiniApi::SetViewportEmptyStub; + FHoudiniApi::SetVolumeInfo = &FHoudiniApi::SetVolumeInfoEmptyStub; + FHoudiniApi::SetVolumeTileFloatData = &FHoudiniApi::SetVolumeTileFloatDataEmptyStub; + FHoudiniApi::SetVolumeTileIntData = &FHoudiniApi::SetVolumeTileIntDataEmptyStub; + FHoudiniApi::SetVolumeVoxelFloatData = &FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub; + FHoudiniApi::SetVolumeVoxelIntData = &FHoudiniApi::SetVolumeVoxelIntDataEmptyStub; + FHoudiniApi::SetWorkitemFloatData = &FHoudiniApi::SetWorkitemFloatDataEmptyStub; + FHoudiniApi::SetWorkitemIntData = &FHoudiniApi::SetWorkitemIntDataEmptyStub; + FHoudiniApi::SetWorkitemStringData = &FHoudiniApi::SetWorkitemStringDataEmptyStub; + FHoudiniApi::StartThriftNamedPipeServer = &FHoudiniApi::StartThriftNamedPipeServerEmptyStub; + FHoudiniApi::StartThriftSocketServer = &FHoudiniApi::StartThriftSocketServerEmptyStub; + FHoudiniApi::ThriftServerOptions_Create = &FHoudiniApi::ThriftServerOptions_CreateEmptyStub; + FHoudiniApi::ThriftServerOptions_Init = &FHoudiniApi::ThriftServerOptions_InitEmptyStub; + FHoudiniApi::TimelineOptions_Create = &FHoudiniApi::TimelineOptions_CreateEmptyStub; + FHoudiniApi::TimelineOptions_Init = &FHoudiniApi::TimelineOptions_InitEmptyStub; + FHoudiniApi::TransformEuler_Create = &FHoudiniApi::TransformEuler_CreateEmptyStub; + FHoudiniApi::TransformEuler_Init = &FHoudiniApi::TransformEuler_InitEmptyStub; + FHoudiniApi::Transform_Create = &FHoudiniApi::Transform_CreateEmptyStub; + FHoudiniApi::Transform_Init = &FHoudiniApi::Transform_InitEmptyStub; + FHoudiniApi::Viewport_Create = &FHoudiniApi::Viewport_CreateEmptyStub; + FHoudiniApi::VolumeInfo_Create = &FHoudiniApi::VolumeInfo_CreateEmptyStub; + FHoudiniApi::VolumeInfo_Init = &FHoudiniApi::VolumeInfo_InitEmptyStub; + FHoudiniApi::VolumeTileInfo_Create = &FHoudiniApi::VolumeTileInfo_CreateEmptyStub; + FHoudiniApi::VolumeTileInfo_Init = &FHoudiniApi::VolumeTileInfo_InitEmptyStub; +} + + +bool +FHoudiniApi::IsHAPIInitialized() +{ + return ( FHoudiniApi::IsInitialized != &FHoudiniApi::IsInitializedEmptyStub ); +} + + +HAPI_Result +FHoudiniApi::AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_AssetInfo +FHoudiniApi::AssetInfo_CreateEmptyStub() +{ + return HAPI_AssetInfo(); +} + + +void +FHoudiniApi::AssetInfo_InitEmptyStub(HAPI_AssetInfo * in) +{ + return; +} + + +HAPI_AttributeInfo +FHoudiniApi::AttributeInfo_CreateEmptyStub() +{ + return HAPI_AttributeInfo(); +} + + +void +FHoudiniApi::AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CleanupEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ClearConnectionErrorEmptyStub() +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CloseSessionEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_CompositorOptions +FHoudiniApi::CompositorOptions_CreateEmptyStub() +{ + return HAPI_CompositorOptions(); +} + + +void +FHoudiniApi::CompositorOptions_InitEmptyStub(HAPI_CompositorOptions * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Bool +FHoudiniApi::CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right) +{ + return HAPI_Bool(); +} + + +HAPI_CookOptions +FHoudiniApi::CookOptions_CreateEmptyStub() +{ + return HAPI_CookOptions(); +} + + +void +FHoudiniApi::CookOptions_InitEmptyStub(HAPI_CookOptions * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateInProcessSessionEmptyStub(HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_CurveInfo +FHoudiniApi::CurveInfo_CreateEmptyStub() +{ + return HAPI_CurveInfo(); +} + + +void +FHoudiniApi::CurveInfo_InitEmptyStub(HAPI_CurveInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_GeoInfo +FHoudiniApi::GeoInfo_CreateEmptyStub() +{ + return HAPI_GeoInfo(); +} + + +int +FHoudiniApi::GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type) +{ + return -1; +} + + +void +FHoudiniApi::GeoInfo_InitEmptyStub(HAPI_GeoInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCompositorOptionsEmptyStub(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetConnectionErrorLengthEmptyStub(int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetEdgeCountOfEdgeGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetOutputGeoCountEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, int* count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetOutputGeoInfosEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTimeEmptyStub(const HAPI_Session * session, float * time) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_HandleBindingInfo +FHoudiniApi::HandleBindingInfo_CreateEmptyStub() +{ + return HAPI_HandleBindingInfo(); +} + + +void +FHoudiniApi::HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in) +{ + return; +} + + +HAPI_HandleInfo +FHoudiniApi::HandleInfo_CreateEmptyStub() +{ + return HAPI_HandleInfo(); +} + + +void +FHoudiniApi::HandleInfo_InitEmptyStub(HAPI_HandleInfo * in) +{ + return; +} + + +HAPI_ImageFileFormat +FHoudiniApi::ImageFileFormat_CreateEmptyStub() +{ + return HAPI_ImageFileFormat(); +} + + +void +FHoudiniApi::ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in) +{ + return; +} + + +HAPI_ImageInfo +FHoudiniApi::ImageInfo_CreateEmptyStub() +{ + return HAPI_ImageInfo(); +} + + +void +FHoudiniApi::ImageInfo_InitEmptyStub(HAPI_ImageInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::InterruptEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsInitializedEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::IsSessionValidEmptyStub(const HAPI_Session * session) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Keyframe +FHoudiniApi::Keyframe_CreateEmptyStub() +{ + return HAPI_Keyframe(); +} + + +void +FHoudiniApi::Keyframe_InitEmptyStub(HAPI_Keyframe * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_MaterialInfo +FHoudiniApi::MaterialInfo_CreateEmptyStub() +{ + return HAPI_MaterialInfo(); +} + + +void +FHoudiniApi::MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_NodeInfo +FHoudiniApi::NodeInfo_CreateEmptyStub() +{ + return HAPI_NodeInfo(); +} + + +void +FHoudiniApi::NodeInfo_InitEmptyStub(HAPI_NodeInfo * in) +{ + return; +} + + +HAPI_ObjectInfo +FHoudiniApi::ObjectInfo_CreateEmptyStub() +{ + return HAPI_ObjectInfo(); +} + + +void +FHoudiniApi::ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in) +{ + return; +} + + +HAPI_ParmChoiceInfo +FHoudiniApi::ParmChoiceInfo_CreateEmptyStub() +{ + return HAPI_ParmChoiceInfo(); +} + + +void +FHoudiniApi::ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_ParmInfo +FHoudiniApi::ParmInfo_CreateEmptyStub() +{ + return HAPI_ParmInfo(); +} + + +int +FHoudiniApi::ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in) +{ + return -1; +} + + +int +FHoudiniApi::ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in) +{ + return -1; +} + + +int +FHoudiniApi::ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in) +{ + return -1; +} + + +void +FHoudiniApi::ParmInfo_InitEmptyStub(HAPI_ParmInfo * in) +{ + return; +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_Bool +FHoudiniApi::ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in) +{ + return HAPI_Bool(); +} + + +HAPI_PartInfo +FHoudiniApi::PartInfo_CreateEmptyStub() +{ + return HAPI_PartInfo(); +} + + +int +FHoudiniApi::PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) +{ + return -1; +} + + +int +FHoudiniApi::PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner) +{ + return -1; +} + + +int +FHoudiniApi::PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type) +{ + return -1; +} + + +void +FHoudiniApi::PartInfo_InitEmptyStub(HAPI_PartInfo * in) +{ + return; +} + + +HAPI_Result +FHoudiniApi::PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_SessionSyncInfo +FHoudiniApi::SessionSyncInfo_CreateEmptyStub() +{ + return HAPI_SessionSyncInfo(); +} + + +HAPI_Result +FHoudiniApi::SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCompositorOptionsEmptyStub(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTimeEmptyStub(const HAPI_Session * session, float time) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_Result +FHoudiniApi::StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id) +{ + return HAPI_RESULT_FAILURE; +} + + +HAPI_ThriftServerOptions +FHoudiniApi::ThriftServerOptions_CreateEmptyStub() +{ + return HAPI_ThriftServerOptions(); +} + + +void +FHoudiniApi::ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in) +{ + return; +} + + +HAPI_TimelineOptions +FHoudiniApi::TimelineOptions_CreateEmptyStub() +{ + return HAPI_TimelineOptions(); +} + + +void +FHoudiniApi::TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in) +{ + return; +} + + +HAPI_TransformEuler +FHoudiniApi::TransformEuler_CreateEmptyStub() +{ + return HAPI_TransformEuler(); +} + + +void +FHoudiniApi::TransformEuler_InitEmptyStub(HAPI_TransformEuler * in) +{ + return; +} + + +HAPI_Transform +FHoudiniApi::Transform_CreateEmptyStub() +{ + return HAPI_Transform(); +} + + +void +FHoudiniApi::Transform_InitEmptyStub(HAPI_Transform * in) +{ + return; +} + + +HAPI_Viewport +FHoudiniApi::Viewport_CreateEmptyStub() +{ + return HAPI_Viewport(); +} + + +HAPI_VolumeInfo +FHoudiniApi::VolumeInfo_CreateEmptyStub() +{ + return HAPI_VolumeInfo(); +} + + +void +FHoudiniApi::VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in) +{ + return; +} + + +HAPI_VolumeTileInfo +FHoudiniApi::VolumeTileInfo_CreateEmptyStub() +{ + return HAPI_VolumeTileInfo(); +} + + +void +FHoudiniApi::VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in) +{ + return; +} + + diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.cpp b/Source/HoudiniEngine/Private/HoudiniEngine.cpp index 7d949ea14..336d8c106 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngine.cpp @@ -1,1315 +1,1336 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngine.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "HoudiniApi.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineScheduler.h" -#include "HoudiniEngineManager.h" -#include "HoudiniEngineTask.h" -#include "HoudiniEngineTaskInfo.h" -#include "HoudiniAssetComponent.h" -#include "HAPI/HAPI_Version.h" - -#include "Modules/ModuleManager.h" -#include "Misc/ScopeLock.h" -#include "Engine/StaticMesh.h" -#include "Materials/Material.h" -#include "ISettingsModule.h" -#include "HAL/PlatformFilemanager.h" -#include "Async/Async.h" -#include "Logging/LogMacros.h" - -#if WITH_EDITOR - #include "Widgets/Notifications/SNotificationList.h" - #include "Framework/Notifications/NotificationManager.h" -#endif - -#define LOCTEXT_NAMESPACE "HoudiniEngine" - -IMPLEMENT_MODULE(FHoudiniEngine, HoudiniEngine) -DEFINE_LOG_CATEGORY( LogHoudiniEngine ); - -FHoudiniEngine * -FHoudiniEngine::HoudiniEngineInstance = nullptr; - -FHoudiniEngine::FHoudiniEngine() - : LicenseType(HAPI_LICENSE_NONE) - , HoudiniEngineSchedulerThread(nullptr) - , HoudiniEngineScheduler(nullptr) - , HoudiniEngineManagerThread(nullptr) - , HoudiniEngineManager(nullptr) - //, bHAPIVersionMismatch(false) - , bEnableCookingGlobal(true) - , UIRefreshCountWhenPauseCooking(0) - , bFirstSessionCreated(false) - , bEnableSessionSync(false) - , bCookUsingHoudiniTime(true) - , bSyncViewport(false) - , bSyncHoudiniViewport(true) - , bSyncUnrealViewport(false) - , HoudiniLogoStaticMesh(nullptr) - , HoudiniDefaultMaterial(nullptr) - , HoudiniTemplateMaterial(nullptr) - , HoudiniLogoBrush(nullptr) - , HoudiniDefaultReferenceMesh(nullptr) - , HoudiniDefaultReferenceMeshMaterial(nullptr) -{ - Session.type = HAPI_SESSION_MAX; - Session.id = -1; - - SetSessionStatus(EHoudiniSessionStatus::Invalid); - -#if WITH_EDITOR - HapiNotificationStarted = 0.0; - TimeSinceLastPersistentNotification = 0.0; -#endif -} - -FHoudiniEngine& -FHoudiniEngine::Get() -{ - check(FHoudiniEngine::HoudiniEngineInstance); - return *FHoudiniEngine::HoudiniEngineInstance; -} - -bool -FHoudiniEngine::IsInitialized() -{ - return FHoudiniEngine::HoudiniEngineInstance != nullptr && FHoudiniEngineUtils::IsInitialized(); -} - -void -FHoudiniEngine::StartupModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine module...")); - -#if WITH_EDITOR - // Register settings. - if (ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings")) - { - SettingsModule->RegisterSettings( - "Project", "Plugins", "HoudiniEngine", - LOCTEXT("RuntimeSettingsName", "Houdini Engine"), - LOCTEXT("RuntimeSettingsDescription", "Configure the HoudiniEngine plugin"), - GetMutableDefault< UHoudiniRuntimeSettings >()); - } -#endif - - // Before starting the module, we need to locate and load HAPI library. - { - void * HAPILibraryHandle = FHoudiniEngineUtils::LoadLibHAPI(LibHAPILocation); - if ( HAPILibraryHandle ) - { - FHoudiniApi::InitializeHAPI( HAPILibraryHandle ); - } - else - { - // Get platform specific name of libHAPI. - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - HOUDINI_LOG_MESSAGE(TEXT("Failed locating or loading %s"), *LibHAPIName); - } - } - - // Create static mesh Houdini logo. - HoudiniLogoStaticMesh = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_LOGO, nullptr, LOAD_None, nullptr); - if (HoudiniLogoStaticMesh.IsValid()) - HoudiniLogoStaticMesh->AddToRoot(); - - // Create default material. - HoudiniDefaultMaterial = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultMaterial.IsValid()) - HoudiniDefaultMaterial->AddToRoot(); - - HoudiniTemplateMaterial = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniTemplateMaterial.IsValid()) - HoudiniTemplateMaterial->AddToRoot(); - - // Houdini Logo Brush - FString Icon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icon128.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) - { - const FName BrushName(*Icon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Logo Brush - FString HEIcon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_logo_128.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) - { - const FName BrushName(*HEIcon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Create Houdini default reference mesh - HoudiniDefaultReferenceMesh = LoadObject( - nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultReferenceMesh.IsValid()) - HoudiniDefaultReferenceMesh->AddToRoot(); - - // Create Houdini default reference mesh material - HoudiniDefaultReferenceMeshMaterial = LoadObject - (nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL, nullptr, LOAD_None, nullptr); - if (HoudiniDefaultReferenceMeshMaterial.IsValid()) - HoudiniDefaultReferenceMeshMaterial->AddToRoot(); - - // We do not automatically try to start a session when starting up the module now. - bFirstSessionCreated = false; - - // Create HAPI scheduler and processing thread. - HoudiniEngineScheduler = new FHoudiniEngineScheduler(); - HoudiniEngineSchedulerThread = FRunnableThread::Create( - HoudiniEngineScheduler, TEXT("HoudiniSchedulerThread"), 0, TPri_Normal); - - // Create Houdini Asset Manager - HoudiniEngineManager = new FHoudiniEngineManager(); - - // Set the session status to Not Started - SetSessionStatus(EHoudiniSessionStatus::NotStarted); - - // Set the default value for pausing houdini engine cooking - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bEnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; - - // Check if a null session is set - bool bNoneSession = (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None); - if (bNoneSession) - SetSessionStatus(EHoudiniSessionStatus::None); - - // Initialize the singleton with this instance - FHoudiniEngine::HoudiniEngineInstance = this; - - // See if we need to start the manager ticking if needed - // Dont tick if we failed to load HAPI, if cooking is disabled or if we're using a null session - if (FHoudiniApi::IsHAPIInitialized()) - { - if (bEnableCookingGlobal && !bNoneSession) - { - PostEngineInitCallback = FCoreDelegates::OnPostEngineInit.AddLambda([]() - { - FHoudiniEngine& HEngine = FHoudiniEngine::Get(); - HEngine.UnregisterPostEngineInitCallback(); - FHoudiniEngineManager* const Manager = HEngine.GetHoudiniEngineManager(); - if (Manager) - Manager->StartHoudiniTicking(); - }); - } - } -} - -void -FHoudiniEngine::ShutdownModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine module.")); - - // We no longer need the Houdini logo static mesh. - if (HoudiniLogoStaticMesh.IsValid()) - { - HoudiniLogoStaticMesh->RemoveFromRoot(); - HoudiniLogoStaticMesh = nullptr; - } - - // We no longer need the Houdini default material. - if (HoudiniDefaultMaterial.IsValid()) - { - HoudiniDefaultMaterial->RemoveFromRoot(); - HoudiniDefaultMaterial = nullptr; - } - - // We no longer need the Houdini default material. - if (HoudiniTemplateMaterial.IsValid()) - { - HoudiniTemplateMaterial->RemoveFromRoot(); - HoudiniTemplateMaterial = nullptr; - } - - // We no longer need the Houdini default reference mesh - if (HoudiniDefaultReferenceMesh.IsValid()) - { - HoudiniDefaultReferenceMesh->RemoveFromRoot(); - HoudiniDefaultReferenceMesh = nullptr; - } - - // We no longer need the Houdini default reference mesh material - if (HoudiniDefaultReferenceMeshMaterial.IsValid()) - { - HoudiniDefaultReferenceMeshMaterial->RemoveFromRoot(); - HoudiniDefaultReferenceMeshMaterial = nullptr; - } - /* - // We no longer need Houdini digital asset used for loading bgeo files. - if (HoudiniBgeoAsset.IsValid()) - { - HoudiniBgeoAsset->RemoveFromRoot(); - HoudiniBgeoAsset = nullptr; - } - */ - -#if WITH_EDITOR - // Unregister settings. - ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings"); - if (SettingsModule) - SettingsModule->UnregisterSettings("Project", "Plugins", "HoudiniEngine"); -#endif - - // Do scheduler and thread clean up. - if (HoudiniEngineScheduler) - HoudiniEngineScheduler->Stop(); - - if (HoudiniEngineSchedulerThread) - { - //HoudiniEngineSchedulerThread->Kill( true ); - HoudiniEngineSchedulerThread->WaitForCompletion(); - - delete HoudiniEngineSchedulerThread; - HoudiniEngineSchedulerThread = nullptr; - } - - if ( HoudiniEngineScheduler ) - { - delete HoudiniEngineScheduler; - HoudiniEngineScheduler = nullptr; - } - - // Do manager clean up. - if (HoudiniEngineManager) - HoudiniEngineManager->StopHoudiniTicking(); - - if (HoudiniEngineManager) - { - delete HoudiniEngineManager; - HoudiniEngineManager = nullptr; - } - - // Perform HAPI finalization. - if ( FHoudiniApi::IsHAPIInitialized() ) - { - FHoudiniApi::Cleanup(GetSession()); - FHoudiniApi::CloseSession(GetSession()); - SessionStatus = EHoudiniSessionStatus::Invalid; - } - - FHoudiniApi::FinalizeHAPI(); - - FHoudiniEngine::HoudiniEngineInstance = nullptr; -} - -void -FHoudiniEngine::AddTask(const FHoudiniEngineTask & InTask) -{ - if ( HoudiniEngineScheduler ) - HoudiniEngineScheduler->AddTask(InTask); - - FScopeLock ScopeLock(&CriticalSection); - FHoudiniEngineTaskInfo TaskInfo; - TaskInfo.TaskType = InTask.TaskType; - TaskInfo.TaskState = EHoudiniEngineTaskState::Working; - - TaskInfos.Add(InTask.HapiGUID, TaskInfo); -} - -void -FHoudiniEngine::AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo) -{ - FScopeLock ScopeLock(&CriticalSection); - TaskInfos.Add(InHapiGUID, InTaskInfo); -} - -void -FHoudiniEngine::RemoveTaskInfo(const FGuid& InHapiGUID) -{ - FScopeLock ScopeLock(&CriticalSection); - TaskInfos.Remove(InHapiGUID); -} - -bool -FHoudiniEngine::RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo) -{ - FScopeLock ScopeLock(&CriticalSection); - - if (TaskInfos.Contains(InHapiGUID)) - { - OutTaskInfo = TaskInfos[InHapiGUID]; - return true; - } - - return false; -} - -/* -void -FHoudiniEngine::AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - if (HoudiniEngineManager) - HoudiniEngineManager->AddComponent(HAC); -} -*/ - -const FString & -FHoudiniEngine::GetLibHAPILocation() const -{ - return LibHAPILocation; -} - -const FString -FHoudiniEngine::GetHoudiniExecutable() -{ - FString HoudiniExecutable = TEXT("houdini"); - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - { - switch (HoudiniRuntimeSettings->HoudiniExecutable) - { - case EHoudiniExecutableType::HRSHE_HoudiniFX: - HoudiniExecutable = TEXT("houdinifx"); - break; - - case EHoudiniExecutableType::HRSHE_HoudiniCore: - HoudiniExecutable = TEXT("houdinicore"); - break; - - case EHoudiniExecutableType::HRSHE_HoudiniIndie: - HoudiniExecutable = TEXT("hindie"); - break; - - default: - case EHoudiniExecutableType::HRSHE_Houdini: - HoudiniExecutable = TEXT("houdini"); - break; - - } - } - - return HoudiniExecutable; -} - -const HAPI_Session * -FHoudiniEngine::GetSession() const -{ - return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; -} - -const EHoudiniSessionStatus& -FHoudiniEngine::GetSessionStatus() const -{ - return SessionStatus; -} - -void -FHoudiniEngine::SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus) -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None) - { - // Check for none sessions first - SessionStatus = EHoudiniSessionStatus::None; - return; - } - - if (!bFirstSessionCreated) - { - // Don't change the status unless we've attempted to start the session once - SessionStatus = EHoudiniSessionStatus::NotStarted; - return; - } - - switch (InSessionStatus) - { - case EHoudiniSessionStatus::NotStarted: - case EHoudiniSessionStatus::NoLicense: - case EHoudiniSessionStatus::Lost: - case EHoudiniSessionStatus::None: - case EHoudiniSessionStatus::Invalid: - case EHoudiniSessionStatus::Connected: - { - SessionStatus = InSessionStatus; - } - break; - - case EHoudiniSessionStatus::Stopped: - { - // Only set to stop status if the session was valid - if (SessionStatus == EHoudiniSessionStatus::Connected) - SessionStatus = EHoudiniSessionStatus::Stopped; - } - break; - - case EHoudiniSessionStatus::Failed: - { - // Preserve No License / Lost status - if (SessionStatus != EHoudiniSessionStatus::NoLicense && SessionStatus != EHoudiniSessionStatus::Lost) - SessionStatus = EHoudiniSessionStatus::Failed; - } - break; - } -} - -HAPI_CookOptions -FHoudiniEngine::GetDefaultCookOptions() -{ - // Default CookOptions - HAPI_CookOptions CookOptions; - FHoudiniApi::CookOptions_Init(&CookOptions); - - CookOptions.curveRefineLOD = 8.0f; - CookOptions.clearErrorsAndWarnings = false; - CookOptions.maxVerticesPerPrimitive = 3; - CookOptions.splitGeosByGroup = false; - CookOptions.splitGeosByAttribute = false; - CookOptions.splitAttrSH = 0; - CookOptions.refineCurveToLinear = true; - CookOptions.handleBoxPartTypes = false; - CookOptions.handleSpherePartTypes = false; - CookOptions.splitPointsByVertexAttributes = false; - CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; - CookOptions.cookTemplatedGeos = true; - - return CookOptions; -} - -bool -FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, - const bool& StartAutomaticServer, - const float& AutomaticServerTimeout, - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const int32& ServerPort, - const FString& ServerHost) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - // Only start a new Session if we dont already have a valid one - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) - return true; - - // Set the HAPI_CLIENT_NAME environment variable to "unreal" - // We need to do this before starting HARS. - FPlatformMisc::SetEnvironmentVar(TEXT("HAPI_CLIENT_NAME"), TEXT("unreal")); - - HAPI_Result SessionResult = HAPI_RESULT_FAILURE; - - HAPI_ThriftServerOptions ServerOptions; - FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); - ServerOptions.autoClose = true; - ServerOptions.timeoutMs = AutomaticServerTimeout; - - // Unless we automatically start the server, - // consider we're in SessionSync mode - bEnableSessionSync = true; - - auto UpdatePathForServer = [&] - { - // Modify our PATH so that HARC will find HARS.exe - const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); - - FString OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); - - FString ModifiedPath = -#if PLATFORM_MAC - // On Mac our binaries are split between two folders - LibHAPILocation + TEXT("/../Resources/bin") + PathDelimiter + -#endif - LibHAPILocation + PathDelimiter + OrigPathVar; - - FPlatformMisc::SetEnvironmentVar(TEXT("PATH"), *ModifiedPath); - }; - - switch ( SessionType ) - { - case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: - { - // Try to connect to an existing socket session first - SessionResult = FHoudiniApi::CreateThriftSocketSession( - SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort ); - - // Start a session and try to connect to it if we failed - if ( StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS ) - { - UpdatePathForServer(); - FHoudiniApi::StartThriftSocketServer( - &ServerOptions, ServerPort, nullptr); - - // We've started the server manually, disable session sync - bEnableSessionSync = false; - - SessionResult = FHoudiniApi::CreateThriftSocketSession( - SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort); - } - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: - { - // Try to connect to an existing pipe session first - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - SessionPtr, TCHAR_TO_UTF8(*ServerPipeName) ); - - // Start a session and try to connect to it if we failed - if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) - { - UpdatePathForServer(); - FHoudiniApi::StartThriftNamedPipeServer( - &ServerOptions, TCHAR_TO_UTF8(*ServerPipeName), nullptr); - - // We've started the server manually, disable session sync - bEnableSessionSync = false; - - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - SessionPtr, TCHAR_TO_UTF8(*ServerPipeName)); - } - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_None: - { - HOUDINI_LOG_MESSAGE(TEXT("Session type set to None, Cooking is disabled.")); - // Disable session sync - bEnableSessionSync = false; - break; - } - - // As of Unreal 4.19, InProcess sessions are not supported anymore - case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: - default: - HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine session type")); - // Disable session sync - bEnableSessionSync = false; - break; - } - - if(SessionType != EHoudiniRuntimeSettingsSessionType::HRSST_None) - FHoudiniEngine::Get().SetFirstSessionCreated(true); - - if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) - { - // Disable session sync as well? - bEnableSessionSync = false; - return false; - } - - // Update this session's license type - HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( - SessionPtr, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); - - return true; -} - -bool -FHoudiniEngine::SessionSyncConnect( - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const FString& ServerHost, - const int32& ServerPort) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - // Only start a new Session if we dont already have a valid one - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(&Session)) - return true; - - // Consider the session failed as long as we dont connect - SetSessionStatus(EHoudiniSessionStatus::Failed); - - HAPI_Result SessionResult = HAPI_RESULT_FAILURE; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - - HAPI_ThriftServerOptions ServerOptions; - FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); - ServerOptions.autoClose = true; - ServerOptions.timeoutMs = HoudiniRuntimeSettings->AutomaticServerTimeout; - - switch (SessionType) - { - case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: - { - // Try to connect to an existing socket session first - SessionResult = FHoudiniApi::CreateThriftSocketSession( - &Session, TCHAR_TO_UTF8(*ServerHost), ServerPort); - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: - { - // Try to connect to an existing pipe session first - SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( - &Session, TCHAR_TO_UTF8(*ServerPipeName)); - } - break; - - case EHoudiniRuntimeSettingsSessionType::HRSST_None: - case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: - default: - HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine Session Sync Type!!")); - bEnableSessionSync = false; - break; - } - - if (SessionResult != HAPI_RESULT_SUCCESS) - return false; - - // Enable session sync - bEnableSessionSync = true; - SetSessionStatus(EHoudiniSessionStatus::Connected); - - // Update this session's license type - HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( - &Session, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); - - // Update the default viewport sync settings - bSyncViewport = HoudiniRuntimeSettings->bSyncViewport; - bSyncHoudiniViewport = HoudiniRuntimeSettings->bSyncHoudiniViewport; - bSyncUnrealViewport = HoudiniRuntimeSettings->bSyncUnrealViewport; - - return true; -} - -bool -FHoudiniEngine::InitializeHAPISession() -{ - // The HAPI stubs needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The Houdini API stubs have not been properly initialized.")); - return false; - } - - // We need a Valid Session - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(GetSession())) - { - HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The session is invalid.")); - return false; - } - - // Now, initialize HAPI with the new session - // We need to make sure HAPI version is correct. - int32 RunningEngineMajor = 0; - int32 RunningEngineMinor = 0; - int32 RunningEngineApi = 0; - - // 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); - - // Compare defined and running versions. - if (RunningEngineMajor != HAPI_VERSION_HOUDINI_ENGINE_MAJOR - || RunningEngineMinor != HAPI_VERSION_HOUDINI_ENGINE_MINOR) - { - // Major or minor HAPI version differs, stop here - HOUDINI_LOG_ERROR( - TEXT("Starting up the Houdini Engine module failed: built and running versions do not match.")); - HOUDINI_LOG_ERROR( - TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), - HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, - RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - - // Display an error message - - // - return false; - - } - else if (RunningEngineApi != HAPI_VERSION_HOUDINI_ENGINE_API) - { - // Major/minor HAPIversions match, but only the API version differs, - // Allow the user to continue but warn him of possible instabilities - HOUDINI_LOG_WARNING( - TEXT("Starting up the Houdini Engine module: built and running versions do not match.")); - HOUDINI_LOG_WARNING( - TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), - HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, - RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - HOUDINI_LOG_WARNING( - TEXT("This could cause instabilities and crashes when using the Houdini Engine plugin")); - } - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Default CookOptions - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - - bool bUseCookingThread = true; - HAPI_Result Result = FHoudiniApi::Initialize( - &Session, - &CookOptions, - bUseCookingThread, - HoudiniRuntimeSettings->CookingThreadStackSize, - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->HoudiniEnvironmentFiles), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->OtlSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->DsoSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->ImageDsoSearchPath), - TCHAR_TO_UTF8(*HoudiniRuntimeSettings->AudioDsoSearchPath)); - - if (Result == HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module.")); - } - else if (Result == HAPI_RESULT_ALREADY_INITIALIZED) - { - // Reused session? just notify the user - HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module - HAPI was already initialzed.")); - } - else - { - HOUDINI_LOG_ERROR( - TEXT("Houdini Engine API initialization failed: %s"), - *FHoudiniEngineUtils::GetErrorDescription(Result)); - - return false; - } - - // Let HAPI know we are running inside UE4 - FHoudiniApi::SetServerEnvString(&Session, HAPI_ENV_CLIENT_NAME, HAPI_UNREAL_CLIENT_NAME); - - if (bEnableSessionSync) - { - // Set the session sync infos if needed - UploadSessionSyncInfoToHoudini(); - - // Indicate that Session Sync is enabled - FString Notification = TEXT("Houdini Engine Session Sync enabled."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Session Sync enabled.")); - } - - return true; -} - - -void -FHoudiniEngine::OnSessionLost() -{ - // Mark the session as invalid - Session.id = -1; - Session.type = HAPI_SESSION_MAX; - SetSessionStatus(EHoudiniSessionStatus::Lost); - - bEnableSessionSync = false; - HoudiniEngineManager->StopHoudiniTicking(); - - // This indicates that we likely have lost the session due to a crash in HARS/Houdini - FString Notification = TEXT("Houdini Engine Session lost!"); - FHoudiniEngineUtils::CreateSlateNotification(Notification, 2.0, 4.0); - - HOUDINI_LOG_ERROR(TEXT("Houdini Engine Session lost! This could be caused by a crash in HARS.")); -} - -bool -FHoudiniEngine::StopSession() -{ - HAPI_Session* SessionPtr = &Session; - return StopSession(SessionPtr); -} - -bool -FHoudiniEngine::StopSession(HAPI_Session*& SessionPtr) -{ - // HAPI needs to be initialized - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) - { - // SessionPtr is valid, clean up and close the session - FHoudiniApi::Cleanup(SessionPtr); - FHoudiniApi::CloseSession(SessionPtr); - } - - Session.id = -1; - Session.type = HAPI_SESSION_MAX; - SetSessionStatus(EHoudiniSessionStatus::Stopped); - bEnableSessionSync = false; - - HoudiniEngineManager->StopHoudiniTicking(); - - return true; -} - -bool -FHoudiniEngine::RestartSession() -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Starting the Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - if (!StopSession(SessionPtr)) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); - } - else - { - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - HoudiniRuntimeSettings->bStartAutomaticServer, - HoudiniRuntimeSettings->AutomaticServerTimeout, - HoudiniRuntimeSettings->SessionType, - HoudiniRuntimeSettings->ServerPipeName, - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to start the new Session")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - bSuccess = true; - SetSessionStatus(EHoudiniSessionStatus::Connected); - } - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -bool -FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName) -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Create the Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - true, - HoudiniRuntimeSettings->AutomaticServerTimeout, - SessionType, - OverrideServerPipeName == NAME_None ? HoudiniRuntimeSettings->ServerPipeName : OverrideServerPipeName.ToString(), - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine Session")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine session - Failed to initialize HAPI")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - bSuccess = true; - SetSessionStatus(EHoudiniSessionStatus::Connected); - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -bool -FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType) -{ - HAPI_Session* SessionPtr = &Session; - - FString StatusText = TEXT("Connecting to a Houdini Engine session..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // Make sure we stop the current session if it is still valid - bool bSuccess = false; - - // Try to reconnect/start a new session - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (!StartSession( - SessionPtr, - false, - HoudiniRuntimeSettings->AutomaticServerTimeout, - SessionType, - HoudiniRuntimeSettings->ServerPipeName, - HoudiniRuntimeSettings->ServerPort, - HoudiniRuntimeSettings->ServerHost)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine Session")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - // Now initialize HAPI with this session - if (!InitializeHAPISession()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine session - Failed to initialize HAPI")); - SetSessionStatus(EHoudiniSessionStatus::Failed); - } - else - { - bSuccess = true; - SetSessionStatus(EHoudiniSessionStatus::Connected); - } - } - - // Start ticking only if we successfully started the session - if (bSuccess) - { - StartTicking(); - return true; - } - else - { - StopTicking(); - return false; - } -} - -void -FHoudiniEngine::StartTicking() -{ - // Finish the notification and display the results - FString StatusText = TEXT("Houdini Engine session connected."); - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - - HoudiniEngineManager->StartHoudiniTicking(); -} - -void -FHoudiniEngine::StopTicking() -{ - // Finish the notification and display the results - FString StatusText = TEXT("Failed to start the Houdini Engine session..."); - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - - HoudiniEngineManager->StopHoudiniTicking(); - - HAPI_Session* SessionPtr = &Session; - StopSession(SessionPtr); -} - -bool -FHoudiniEngine::IsCookingEnabled() const -{ - return bEnableCookingGlobal; -} - -void -FHoudiniEngine::SetCookingEnabled(const bool& bInEnableCooking) -{ - bEnableCookingGlobal = bInEnableCooking; -} - -bool -FHoudiniEngine::GetFirstSessionCreated() const -{ - return bFirstSessionCreated; -} - -bool -FHoudiniEngine::CreateTaskSlateNotification( - const FText& InText, const bool& bForceNow, const float& NotificationExpire, const float& NotificationFadeOut) -{ -#if WITH_EDITOR - static double NotificationUpdateFrequency = 2.0f; - - // Check whether we want to display Slate cooking and instantiation notifications. - bool bDisplaySlateCookingNotifications = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (!bDisplaySlateCookingNotifications) - return false; - - if (!bForceNow) - { - if ((FPlatformTime::Seconds() - HapiNotificationStarted) < NotificationUpdateFrequency) - return false; - } - - if (!NotificationPtr.IsValid()) - { - FNotificationInfo Info(InText); - Info.bFireAndForget = false; - Info.FadeOutDuration = NotificationFadeOut; - Info.ExpireDuration = NotificationExpire; - TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); - if (HoudiniBrush.IsValid()) - Info.Image = HoudiniBrush.Get(); - - /* - if (!IsPIEActive()) - */ - - NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); - //FSlateNotificationManager::Get().Tick(); - } -#endif - - return true; -} - -bool -FHoudiniEngine::UpdateTaskSlateNotification(const FText& InText) -{ -#if WITH_EDITOR - // task is till running - // Just update the slate notification - TSharedPtr NotificationItem = NotificationPtr.Pin(); - if (NotificationItem.IsValid()) - NotificationItem->SetText(InText); - - //FSlateNotificationManager::Get().Tick(); -#endif - - return true; -} - -bool -FHoudiniEngine::FinishTaskSlateNotification(const FText& InText) -{ -#if WITH_EDITOR - if (NotificationPtr.IsValid()) - { - TSharedPtr NotificationItem = NotificationPtr.Pin(); - if (NotificationItem.IsValid()) - { - NotificationItem->SetText(InText); - NotificationItem->ExpireAndFadeout(); - - NotificationPtr.Reset(); - } - } -#endif - - return true; -} - -bool FHoudiniEngine::UpdateCookingNotification(const FText& InText, const bool bExpireAndFade) -{ -#if WITH_EDITOR - // Check whether we want to display Slate cooking and instantiation notifications. - bool bDisplaySlateCookingNotifications = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (!bDisplaySlateCookingNotifications) - return false; - - UpdatePersistentNotification(InText, bExpireAndFade); - -#endif - return true; -} - -bool -FHoudiniEngine::UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade) -{ -#if WITH_EDITOR - TimeSinceLastPersistentNotification = 0.0; - - if (!PersistentNotificationPtr.IsValid()) - { - FNotificationInfo Info(InText); - Info.bFireAndForget = false; - Info.FadeOutDuration = HAPI_UNREAL_NOTIFICATION_FADEOUT; - Info.ExpireDuration = HAPI_UNREAL_NOTIFICATION_EXPIRE; - const TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); - if (HoudiniBrush.IsValid()) - Info.Image = HoudiniBrush.Get(); - - - PersistentNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); - //FSlateNotificationManager::Get().Tick(); - } - - TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); - - if (NotificationItem.IsValid()) - { - // Update the persistent notification. - NotificationItem->SetText(InText); - bPersistentAllowExpiry = bExpireAndFade; - } - - //FSlateNotificationManager::Get().Tick(); -#endif - - return true; -} - -void FHoudiniEngine::TickPersistentNotification(const float DeltaTime) -{ - if (PersistentNotificationPtr.IsValid() && DeltaTime > 0.0f) - { - TimeSinceLastPersistentNotification += DeltaTime; - if (bPersistentAllowExpiry && TimeSinceLastPersistentNotification > HAPI_UNREAL_NOTIFICATION_EXPIRE) - { - TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); - if (NotificationItem.IsValid()) - { - NotificationItem->Fadeout(); - PersistentNotificationPtr.Reset(); - } - } - } - - // Tick the notification manager - //FSlateNotificationManager::Get().Tick(); -} - -void -FHoudiniEngine::UpdateSessionSyncInfoFromHoudini() -{ - if (!bEnableSessionSync) - return; - - // Set the Session Sync settings to Houdini - HAPI_SessionSyncInfo SessionSyncInfo; - //FHoudiniApi::SessionSyncInfo_Create(&SessionSyncInfo); - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetSessionSyncInfo(&Session, &SessionSyncInfo)) - { - bCookUsingHoudiniTime = SessionSyncInfo.cookUsingHoudiniTime; - bSyncViewport = SessionSyncInfo.syncViewport; - } -} - -void -FHoudiniEngine::UploadSessionSyncInfoToHoudini() -{ - // No need to set sessionsync info if we're not using session sync - if (!bEnableSessionSync) - return; - - // Set the Session Sync settings to Houdini - HAPI_SessionSyncInfo SessionSyncInfo; - SessionSyncInfo.cookUsingHoudiniTime = bCookUsingHoudiniTime; - SessionSyncInfo.syncViewport = bSyncViewport; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetSessionSyncInfo(&Session, &SessionSyncInfo)) - HOUDINI_LOG_WARNING(TEXT("Failed to set the SessionSync Infos.")); -} - -void -FHoudiniEngine::StartPDGCommandlet() -{ - if (HoudiniEngineManager) - HoudiniEngineManager->StartPDGCommandlet(); -} - -void -FHoudiniEngine::StopPDGCommandlet() -{ - if (HoudiniEngineManager) - HoudiniEngineManager->StopPDGCommandlet(); -} - -bool -FHoudiniEngine::IsPDGCommandletRunningOrConnected() -{ - if (HoudiniEngineManager) - return HoudiniEngineManager->IsPDGCommandletRunningOrConnected(); - return false; -} - -EHoudiniBGEOCommandletStatus -FHoudiniEngine::GetPDGCommandletStatus() -{ - if (HoudiniEngineManager) - return HoudiniEngineManager->GetPDGCommandletStatus(); - return EHoudiniBGEOCommandletStatus::NotStarted; -} - -void -FHoudiniEngine::UnregisterPostEngineInitCallback() -{ - if (PostEngineInitCallback.IsValid()) - FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitCallback); -} - -bool FHoudiniEngine::IsSyncWithHoudiniCookEnabled() const -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - return HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bSyncWithHoudiniCook : false; -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngine.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineScheduler.h" +#include "HoudiniEngineManager.h" +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniAssetComponent.h" +#include "HAPI/HAPI_Version.h" + +#include "Modules/ModuleManager.h" +#include "Misc/ScopeLock.h" +#include "Engine/StaticMesh.h" +#include "Materials/Material.h" +#include "ISettingsModule.h" +#include "HAL/PlatformFilemanager.h" +#include "Async/Async.h" +#include "Logging/LogMacros.h" + +#if WITH_EDITOR + #include "Widgets/Notifications/SNotificationList.h" + #include "Framework/Notifications/NotificationManager.h" +#endif + +#define LOCTEXT_NAMESPACE "HoudiniEngine" + +IMPLEMENT_MODULE(FHoudiniEngine, HoudiniEngine) +DEFINE_LOG_CATEGORY( LogHoudiniEngine ); + +FHoudiniEngine * +FHoudiniEngine::HoudiniEngineInstance = nullptr; + +FHoudiniEngine::FHoudiniEngine() + : LicenseType(HAPI_LICENSE_NONE) + , HoudiniEngineSchedulerThread(nullptr) + , HoudiniEngineScheduler(nullptr) + , HoudiniEngineManagerThread(nullptr) + , HoudiniEngineManager(nullptr) + //, bHAPIVersionMismatch(false) + , bEnableCookingGlobal(true) + , UIRefreshCountWhenPauseCooking(0) + , bFirstSessionCreated(false) + , bEnableSessionSync(false) + , bCookUsingHoudiniTime(true) + , bSyncViewport(false) + , bSyncHoudiniViewport(true) + , bSyncUnrealViewport(false) + , HoudiniLogoStaticMesh(nullptr) + , HoudiniDefaultMaterial(nullptr) + , HoudiniTemplateMaterial(nullptr) + , HoudiniLogoBrush(nullptr) + , HoudiniDefaultReferenceMesh(nullptr) + , HoudiniDefaultReferenceMeshMaterial(nullptr) +{ + Session.type = HAPI_SESSION_MAX; + Session.id = -1; + + SetSessionStatus(EHoudiniSessionStatus::Invalid); + +#if WITH_EDITOR + HapiNotificationStarted = 0.0; + TimeSinceLastPersistentNotification = 0.0; +#endif +} + +FHoudiniEngine& +FHoudiniEngine::Get() +{ + check(FHoudiniEngine::HoudiniEngineInstance); + return *FHoudiniEngine::HoudiniEngineInstance; +} + +bool +FHoudiniEngine::IsInitialized() +{ + return FHoudiniEngine::HoudiniEngineInstance != nullptr && FHoudiniEngineUtils::IsInitialized(); +} + +void +FHoudiniEngine::StartupModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine module...")); + +#if WITH_EDITOR + // Register settings. + if (ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->RegisterSettings( + "Project", "Plugins", "HoudiniEngine", + LOCTEXT("RuntimeSettingsName", "Houdini Engine"), + LOCTEXT("RuntimeSettingsDescription", "Configure the HoudiniEngine plugin"), + GetMutableDefault< UHoudiniRuntimeSettings >()); + } +#endif + + // Before starting the module, we need to locate and load HAPI library. + { + void * HAPILibraryHandle = FHoudiniEngineUtils::LoadLibHAPI(LibHAPILocation); + if ( HAPILibraryHandle ) + { + FHoudiniApi::InitializeHAPI( HAPILibraryHandle ); + } + else + { + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + HOUDINI_LOG_MESSAGE(TEXT("Failed locating or loading %s"), *LibHAPIName); + } + } + + // Create static mesh Houdini logo. + HoudiniLogoStaticMesh = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_LOGO, nullptr, LOAD_None, nullptr); + if (HoudiniLogoStaticMesh.IsValid()) + HoudiniLogoStaticMesh->AddToRoot(); + + // Create default material. + HoudiniDefaultMaterial = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultMaterial.IsValid()) + HoudiniDefaultMaterial->AddToRoot(); + + HoudiniTemplateMaterial = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_TEMPLATE_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniTemplateMaterial.IsValid()) + HoudiniTemplateMaterial->AddToRoot(); + + // Houdini Logo Brush + FString Icon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icon128.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) + { + const FName BrushName(*Icon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Logo Brush + FString HEIcon128FilePath = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_logo_128.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) + { + const FName BrushName(*HEIcon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Create Houdini default reference mesh + HoudiniDefaultReferenceMesh = LoadObject( + nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultReferenceMesh.IsValid()) + HoudiniDefaultReferenceMesh->AddToRoot(); + + // Create Houdini default reference mesh material + HoudiniDefaultReferenceMeshMaterial = LoadObject + (nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_DEFAULT_REFERENCE_MESH_MATERIAL, nullptr, LOAD_None, nullptr); + if (HoudiniDefaultReferenceMeshMaterial.IsValid()) + HoudiniDefaultReferenceMeshMaterial->AddToRoot(); + + // We do not automatically try to start a session when starting up the module now. + bFirstSessionCreated = false; + + // Create HAPI scheduler and processing thread. + HoudiniEngineScheduler = new FHoudiniEngineScheduler(); + HoudiniEngineSchedulerThread = FRunnableThread::Create( + HoudiniEngineScheduler, TEXT("HoudiniSchedulerThread"), 0, TPri_Normal); + + // Create Houdini Asset Manager + HoudiniEngineManager = new FHoudiniEngineManager(); + + // Set the session status to Not Started + SetSessionStatus(EHoudiniSessionStatus::NotStarted); + + // Set the default value for pausing houdini engine cooking + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bEnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; + + // Check if a null session is set + bool bNoneSession = (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None); + if (bNoneSession) + SetSessionStatus(EHoudiniSessionStatus::None); + + // Initialize the singleton with this instance + FHoudiniEngine::HoudiniEngineInstance = this; + + // See if we need to start the manager ticking if needed + // Dont tick if we failed to load HAPI, if cooking is disabled or if we're using a null session + if (FHoudiniApi::IsHAPIInitialized()) + { + if (bEnableCookingGlobal && !bNoneSession) + { + PostEngineInitCallback = FCoreDelegates::OnPostEngineInit.AddLambda([]() + { + FHoudiniEngine& HEngine = FHoudiniEngine::Get(); + HEngine.UnregisterPostEngineInitCallback(); + FHoudiniEngineManager* const Manager = HEngine.GetHoudiniEngineManager(); + if (Manager) + Manager->StartHoudiniTicking(); + }); + } + } +} + +void +FHoudiniEngine::ShutdownModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine module.")); + + // We no longer need the Houdini logo static mesh. + if (HoudiniLogoStaticMesh.IsValid()) + { + HoudiniLogoStaticMesh->RemoveFromRoot(); + HoudiniLogoStaticMesh = nullptr; + } + + // We no longer need the Houdini default material. + if (HoudiniDefaultMaterial.IsValid()) + { + HoudiniDefaultMaterial->RemoveFromRoot(); + HoudiniDefaultMaterial = nullptr; + } + + // We no longer need the Houdini default material. + if (HoudiniTemplateMaterial.IsValid()) + { + HoudiniTemplateMaterial->RemoveFromRoot(); + HoudiniTemplateMaterial = nullptr; + } + + // We no longer need the Houdini default reference mesh + if (HoudiniDefaultReferenceMesh.IsValid()) + { + HoudiniDefaultReferenceMesh->RemoveFromRoot(); + HoudiniDefaultReferenceMesh = nullptr; + } + + // We no longer need the Houdini default reference mesh material + if (HoudiniDefaultReferenceMeshMaterial.IsValid()) + { + HoudiniDefaultReferenceMeshMaterial->RemoveFromRoot(); + HoudiniDefaultReferenceMeshMaterial = nullptr; + } + /* + // We no longer need Houdini digital asset used for loading bgeo files. + if (HoudiniBgeoAsset.IsValid()) + { + HoudiniBgeoAsset->RemoveFromRoot(); + HoudiniBgeoAsset = nullptr; + } + */ + +#if WITH_EDITOR + // Unregister settings. + ISettingsModule * SettingsModule = FModuleManager::GetModulePtr("Settings"); + if (SettingsModule) + SettingsModule->UnregisterSettings("Project", "Plugins", "HoudiniEngine"); +#endif + + // Do scheduler and thread clean up. + if (HoudiniEngineScheduler) + HoudiniEngineScheduler->Stop(); + + if (HoudiniEngineSchedulerThread) + { + //HoudiniEngineSchedulerThread->Kill( true ); + HoudiniEngineSchedulerThread->WaitForCompletion(); + + delete HoudiniEngineSchedulerThread; + HoudiniEngineSchedulerThread = nullptr; + } + + if ( HoudiniEngineScheduler ) + { + delete HoudiniEngineScheduler; + HoudiniEngineScheduler = nullptr; + } + + // Do manager clean up. + if (HoudiniEngineManager) + HoudiniEngineManager->StopHoudiniTicking(); + + if (HoudiniEngineManager) + { + delete HoudiniEngineManager; + HoudiniEngineManager = nullptr; + } + + // Perform HAPI finalization. + if ( FHoudiniApi::IsHAPIInitialized() ) + { + FHoudiniApi::Cleanup(GetSession()); + FHoudiniApi::CloseSession(GetSession()); + SessionStatus = EHoudiniSessionStatus::Invalid; + } + + FHoudiniApi::FinalizeHAPI(); + + FHoudiniEngine::HoudiniEngineInstance = nullptr; +} + +void +FHoudiniEngine::AddTask(const FHoudiniEngineTask & InTask) +{ + if ( HoudiniEngineScheduler ) + HoudiniEngineScheduler->AddTask(InTask); + + FScopeLock ScopeLock(&CriticalSection); + FHoudiniEngineTaskInfo TaskInfo; + TaskInfo.TaskType = InTask.TaskType; + TaskInfo.TaskState = EHoudiniEngineTaskState::Working; + + TaskInfos.Add(InTask.HapiGUID, TaskInfo); +} + +void +FHoudiniEngine::AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo) +{ + FScopeLock ScopeLock(&CriticalSection); + TaskInfos.Add(InHapiGUID, InTaskInfo); +} + +void +FHoudiniEngine::RemoveTaskInfo(const FGuid& InHapiGUID) +{ + FScopeLock ScopeLock(&CriticalSection); + TaskInfos.Remove(InHapiGUID); +} + +bool +FHoudiniEngine::RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo) +{ + FScopeLock ScopeLock(&CriticalSection); + + if (TaskInfos.Contains(InHapiGUID)) + { + OutTaskInfo = TaskInfos[InHapiGUID]; + return true; + } + + return false; +} + +/* +void +FHoudiniEngine::AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + if (HoudiniEngineManager) + HoudiniEngineManager->AddComponent(HAC); +} +*/ + +const FString & +FHoudiniEngine::GetLibHAPILocation() const +{ + return LibHAPILocation; +} + +const FString +FHoudiniEngine::GetHoudiniExecutable() +{ + FString HoudiniExecutable = TEXT("houdini"); + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + switch (HoudiniRuntimeSettings->HoudiniExecutable) + { + case EHoudiniExecutableType::HRSHE_HoudiniFX: + HoudiniExecutable = TEXT("houdinifx"); + break; + + case EHoudiniExecutableType::HRSHE_HoudiniCore: + HoudiniExecutable = TEXT("houdinicore"); + break; + + case EHoudiniExecutableType::HRSHE_HoudiniIndie: + HoudiniExecutable = TEXT("hindie"); + break; + + default: + case EHoudiniExecutableType::HRSHE_Houdini: + HoudiniExecutable = TEXT("houdini"); + break; + + } + } + + return HoudiniExecutable; +} + +const HAPI_Session * +FHoudiniEngine::GetSession() const +{ + return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; +} + +const EHoudiniSessionStatus& +FHoudiniEngine::GetSessionStatus() const +{ + return SessionStatus; +} + +void +FHoudiniEngine::SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus) +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings->SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None) + { + // Check for none sessions first + SessionStatus = EHoudiniSessionStatus::None; + return; + } + + if (!bFirstSessionCreated) + { + // Don't change the status unless we've attempted to start the session once + SessionStatus = EHoudiniSessionStatus::NotStarted; + return; + } + + switch (InSessionStatus) + { + case EHoudiniSessionStatus::NotStarted: + case EHoudiniSessionStatus::NoLicense: + case EHoudiniSessionStatus::Lost: + case EHoudiniSessionStatus::None: + case EHoudiniSessionStatus::Invalid: + case EHoudiniSessionStatus::Connected: + { + SessionStatus = InSessionStatus; + } + break; + + case EHoudiniSessionStatus::Stopped: + { + // Only set to stop status if the session was valid + if (SessionStatus == EHoudiniSessionStatus::Connected) + SessionStatus = EHoudiniSessionStatus::Stopped; + } + break; + + case EHoudiniSessionStatus::Failed: + { + // Preserve No License / Lost status + if (SessionStatus != EHoudiniSessionStatus::NoLicense && SessionStatus != EHoudiniSessionStatus::Lost) + SessionStatus = EHoudiniSessionStatus::Failed; + } + break; + } +} + +HAPI_CookOptions +FHoudiniEngine::GetDefaultCookOptions() +{ + // Default CookOptions + HAPI_CookOptions CookOptions; + FHoudiniApi::CookOptions_Init(&CookOptions); + + CookOptions.curveRefineLOD = 8.0f; + CookOptions.clearErrorsAndWarnings = false; + CookOptions.maxVerticesPerPrimitive = 3; + CookOptions.splitGeosByGroup = false; + CookOptions.splitGeosByAttribute = false; + CookOptions.splitAttrSH = 0; + CookOptions.refineCurveToLinear = true; + CookOptions.handleBoxPartTypes = false; + CookOptions.handleSpherePartTypes = false; + CookOptions.splitPointsByVertexAttributes = false; + CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; + CookOptions.cookTemplatedGeos = true; + + return CookOptions; +} + +bool +FHoudiniEngine::StartSession(HAPI_Session*& SessionPtr, + const bool& StartAutomaticServer, + const float& AutomaticServerTimeout, + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const int32& ServerPort, + const FString& ServerHost) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + // Only start a new Session if we dont already have a valid one + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) + return true; + + // Set the HAPI_CLIENT_NAME environment variable to "unreal" + // We need to do this before starting HARS. + FPlatformMisc::SetEnvironmentVar(TEXT("HAPI_CLIENT_NAME"), TEXT("unreal")); + + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; + + HAPI_ThriftServerOptions ServerOptions; + FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); + ServerOptions.autoClose = true; + ServerOptions.timeoutMs = AutomaticServerTimeout; + + // Unless we automatically start the server, + // consider we're in SessionSync mode + bEnableSessionSync = true; + + auto UpdatePathForServer = [&] + { + // Modify our PATH so that HARC will find HARS.exe + const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); + + FString OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); + + FString ModifiedPath = +#if PLATFORM_MAC + // On Mac our binaries are split between two folders + LibHAPILocation + TEXT("/../Resources/bin") + PathDelimiter + +#endif + LibHAPILocation + PathDelimiter + OrigPathVar; + + FPlatformMisc::SetEnvironmentVar(TEXT("PATH"), *ModifiedPath); + }; + + + // Clear the connection error before starting a new session + if(SessionType != EHoudiniRuntimeSettingsSessionType::HRSST_None) + FHoudiniApi::ClearConnectionError(); + + switch (SessionType) + { + case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: + { + // Try to connect to an existing socket session first + SessionResult = FHoudiniApi::CreateThriftSocketSession( + SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort ); + + // Start a session and try to connect to it if we failed + if ( StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS ) + { + UpdatePathForServer(); + FHoudiniApi::StartThriftSocketServer( + &ServerOptions, ServerPort, nullptr); + + // We've started the server manually, disable session sync + bEnableSessionSync = false; + + SessionResult = FHoudiniApi::CreateThriftSocketSession( + SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort); + } + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: + { + // Try to connect to an existing pipe session first + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8(*ServerPipeName) ); + + // Start a session and try to connect to it if we failed + if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) + { + UpdatePathForServer(); + FHoudiniApi::StartThriftNamedPipeServer( + &ServerOptions, TCHAR_TO_UTF8(*ServerPipeName), nullptr); + + // We've started the server manually, disable session sync + bEnableSessionSync = false; + + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + SessionPtr, TCHAR_TO_UTF8(*ServerPipeName)); + } + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_None: + { + HOUDINI_LOG_MESSAGE(TEXT("Session type set to None, Cooking is disabled.")); + // Disable session sync + bEnableSessionSync = false; + break; + } + + case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + // As of Unreal 4.19, InProcess sessions are not supported anymore + SessionResult = FHoudiniApi::CreateInProcessSession(SessionPtr); + // Disable session sync + bEnableSessionSync = false; + break; + + default: + HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine session type")); + // Disable session sync + bEnableSessionSync = false; + break; + } + + // Stop here if we used a none session + if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_None) + return false; + + FHoudiniEngine::Get().SetFirstSessionCreated(true); + + if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) + { + // Disable session sync as well? + bEnableSessionSync = false; + + if (SessionType != EHoudiniRuntimeSettingsSessionType::HRSST_InProcess) + { + FString ConnectionError = FHoudiniEngineUtils::GetConnectionError(); + if(!ConnectionError.IsEmpty()) + HOUDINI_LOG_ERROR(TEXT("Houdini Engine Session failed to connect - %s"), *ConnectionError); + } + + return false; + } + + // Update this session's license type + HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( + SessionPtr, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); + + return true; +} + +bool +FHoudiniEngine::SessionSyncConnect( + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const FString& ServerHost, + const int32& ServerPort) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + // Only start a new Session if we dont already have a valid one + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(&Session)) + return true; + + // Consider the session failed as long as we dont connect + SetSessionStatus(EHoudiniSessionStatus::Failed); + + HAPI_Result SessionResult = HAPI_RESULT_FAILURE; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + + HAPI_ThriftServerOptions ServerOptions; + FMemory::Memzero< HAPI_ThriftServerOptions >(ServerOptions); + ServerOptions.autoClose = true; + ServerOptions.timeoutMs = HoudiniRuntimeSettings->AutomaticServerTimeout; + + switch (SessionType) + { + case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: + { + // Try to connect to an existing socket session first + SessionResult = FHoudiniApi::CreateThriftSocketSession( + &Session, TCHAR_TO_UTF8(*ServerHost), ServerPort); + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: + { + // Try to connect to an existing pipe session first + SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( + &Session, TCHAR_TO_UTF8(*ServerPipeName)); + } + break; + + case EHoudiniRuntimeSettingsSessionType::HRSST_None: + case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: + default: + HOUDINI_LOG_ERROR(TEXT("Unsupported Houdini Engine Session Sync Type!!")); + bEnableSessionSync = false; + break; + } + + if (SessionResult != HAPI_RESULT_SUCCESS) + return false; + + // Enable session sync + bEnableSessionSync = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); + + // Update this session's license type + HOUDINI_CHECK_ERROR(FHoudiniApi::GetSessionEnvInt( + &Session, HAPI_SESSIONENVINT_LICENSE, (int32 *)&LicenseType)); + + // Update the default viewport sync settings + bSyncViewport = HoudiniRuntimeSettings->bSyncViewport; + bSyncHoudiniViewport = HoudiniRuntimeSettings->bSyncHoudiniViewport; + bSyncUnrealViewport = HoudiniRuntimeSettings->bSyncUnrealViewport; + + return true; +} + +bool +FHoudiniEngine::InitializeHAPISession() +{ + // The HAPI stubs needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The Houdini API stubs have not been properly initialized.")); + return false; + } + + // We need a Valid Session + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(GetSession())) + { + HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The session is invalid.")); + return false; + } + + // Now, initialize HAPI with the new session + // We need to make sure HAPI version is correct. + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + // 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); + + // Compare defined and running versions. + if (RunningEngineMajor != HAPI_VERSION_HOUDINI_ENGINE_MAJOR + || RunningEngineMinor != HAPI_VERSION_HOUDINI_ENGINE_MINOR) + { + // Major or minor HAPI version differs, stop here + HOUDINI_LOG_ERROR( + TEXT("Starting up the Houdini Engine module failed: built and running versions do not match.")); + HOUDINI_LOG_ERROR( + TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), + HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, + RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + + // Display an error message + + // + return false; + + } + else if (RunningEngineApi != HAPI_VERSION_HOUDINI_ENGINE_API) + { + // Major/minor HAPIversions match, but only the API version differs, + // Allow the user to continue but warn him of possible instabilities + HOUDINI_LOG_WARNING( + TEXT("Starting up the Houdini Engine module: built and running versions do not match.")); + HOUDINI_LOG_WARNING( + TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), + HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, + RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + HOUDINI_LOG_WARNING( + TEXT("This could cause instabilities and crashes when using the Houdini Engine plugin")); + } + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Default CookOptions + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + + bool bUseCookingThread = true; + HAPI_Result Result = FHoudiniApi::Initialize( + &Session, + &CookOptions, + bUseCookingThread, + HoudiniRuntimeSettings->CookingThreadStackSize, + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->HoudiniEnvironmentFiles), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->OtlSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->DsoSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->ImageDsoSearchPath), + TCHAR_TO_UTF8(*HoudiniRuntimeSettings->AudioDsoSearchPath)); + + if (Result == HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module.")); + } + else if (Result == HAPI_RESULT_ALREADY_INITIALIZED) + { + // Reused session? just notify the user + HOUDINI_LOG_MESSAGE(TEXT("Successfully intialized the Houdini Engine module - HAPI was already initialzed.")); + } + else + { + HOUDINI_LOG_ERROR( + TEXT("Houdini Engine API initialization failed: %s"), + *FHoudiniEngineUtils::GetErrorDescription(Result)); + + return false; + } + + // Let HAPI know we are running inside UE4 + FHoudiniApi::SetServerEnvString(&Session, HAPI_ENV_CLIENT_NAME, HAPI_UNREAL_CLIENT_NAME); + + if (bEnableSessionSync) + { + // Set the session sync infos if needed + UploadSessionSyncInfoToHoudini(); + + // Indicate that Session Sync is enabled + FString Notification = TEXT("Houdini Engine Session Sync enabled."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Session Sync enabled.")); + } + + return true; +} + + +void +FHoudiniEngine::OnSessionLost() +{ + // Mark the session as invalid + Session.id = -1; + Session.type = HAPI_SESSION_MAX; + SetSessionStatus(EHoudiniSessionStatus::Lost); + + bEnableSessionSync = false; + HoudiniEngineManager->StopHoudiniTicking(); + + // This indicates that we likely have lost the session due to a crash in HARS/Houdini + FString Notification = TEXT("Houdini Engine Session lost!"); + FHoudiniEngineUtils::CreateSlateNotification(Notification, 2.0, 4.0); + + HOUDINI_LOG_ERROR(TEXT("Houdini Engine Session lost! This could be caused by a crash in HARS.")); +} + +bool +FHoudiniEngine::StopSession() +{ + HAPI_Session* SessionPtr = &Session; + return StopSession(SessionPtr); +} + +bool +FHoudiniEngine::StopSession(HAPI_Session*& SessionPtr) +{ + // HAPI needs to be initialized + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid(SessionPtr)) + { + // SessionPtr is valid, clean up and close the session + FHoudiniApi::Cleanup(SessionPtr); + FHoudiniApi::CloseSession(SessionPtr); + } + + Session.id = -1; + Session.type = HAPI_SESSION_MAX; + SetSessionStatus(EHoudiniSessionStatus::Stopped); + bEnableSessionSync = false; + + HoudiniEngineManager->StopHoudiniTicking(); + + return true; +} + +bool +FHoudiniEngine::RestartSession() +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Starting the Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + if (!StopSession(SessionPtr)) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); + } + else + { + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + HoudiniRuntimeSettings->bStartAutomaticServer, + HoudiniRuntimeSettings->AutomaticServerTimeout, + HoudiniRuntimeSettings->SessionType, + HoudiniRuntimeSettings->ServerPipeName, + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to start the new Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); + } + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +bool +FHoudiniEngine::CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName) +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Create the Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + true, + HoudiniRuntimeSettings->AutomaticServerTimeout, + SessionType, + OverrideServerPipeName == NAME_None ? HoudiniRuntimeSettings->ServerPipeName : OverrideServerPipeName.ToString(), + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +bool +FHoudiniEngine::ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType) +{ + HAPI_Session* SessionPtr = &Session; + + FString StatusText = TEXT("Connecting to a Houdini Engine session..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // Make sure we stop the current session if it is still valid + bool bSuccess = false; + + // Try to reconnect/start a new session + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (!StartSession( + SessionPtr, + false, + HoudiniRuntimeSettings->AutomaticServerTimeout, + SessionType, + HoudiniRuntimeSettings->ServerPipeName, + HoudiniRuntimeSettings->ServerPort, + HoudiniRuntimeSettings->ServerHost)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine Session")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + // Now initialize HAPI with this session + if (!InitializeHAPISession()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to connect to the Houdini Engine session - Failed to initialize HAPI")); + SetSessionStatus(EHoudiniSessionStatus::Failed); + } + else + { + bSuccess = true; + SetSessionStatus(EHoudiniSessionStatus::Connected); + } + } + + // Start ticking only if we successfully started the session + if (bSuccess) + { + StartTicking(); + return true; + } + else + { + StopTicking(); + return false; + } +} + +void +FHoudiniEngine::StartTicking() +{ + // Finish the notification and display the results + FString StatusText = TEXT("Houdini Engine session connected."); + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + + HoudiniEngineManager->StartHoudiniTicking(); +} + +void +FHoudiniEngine::StopTicking() +{ + // Finish the notification and display the results + FString StatusText = TEXT("Failed to start the Houdini Engine session..."); + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + + HoudiniEngineManager->StopHoudiniTicking(); + + HAPI_Session* SessionPtr = &Session; + StopSession(SessionPtr); +} + +bool +FHoudiniEngine::IsCookingEnabled() const +{ + return bEnableCookingGlobal; +} + +void +FHoudiniEngine::SetCookingEnabled(const bool& bInEnableCooking) +{ + bEnableCookingGlobal = bInEnableCooking; +} + +bool +FHoudiniEngine::GetFirstSessionCreated() const +{ + return bFirstSessionCreated; +} + +bool +FHoudiniEngine::CreateTaskSlateNotification( + const FText& InText, const bool& bForceNow, const float& NotificationExpire, const float& NotificationFadeOut) +{ +#if WITH_EDITOR + static double NotificationUpdateFrequency = 2.0f; + + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return false; + + if (!bForceNow) + { + if ((FPlatformTime::Seconds() - HapiNotificationStarted) < NotificationUpdateFrequency) + return false; + } + + if (!NotificationPtr.IsValid()) + { + FNotificationInfo Info(InText); + Info.bFireAndForget = false; + Info.FadeOutDuration = NotificationFadeOut; + Info.ExpireDuration = NotificationExpire; + TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + /* + if (!IsPIEActive()) + */ + + NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + //FSlateNotificationManager::Get().Tick(); + } +#endif + + return true; +} + +bool +FHoudiniEngine::UpdateTaskSlateNotification(const FText& InText) +{ +#if WITH_EDITOR + // task is till running + // Just update the slate notification + TSharedPtr NotificationItem = NotificationPtr.Pin(); + if (NotificationItem.IsValid()) + NotificationItem->SetText(InText); + + //FSlateNotificationManager::Get().Tick(); +#endif + + return true; +} + +bool +FHoudiniEngine::FinishTaskSlateNotification(const FText& InText) +{ +#if WITH_EDITOR + if (NotificationPtr.IsValid()) + { + TSharedPtr NotificationItem = NotificationPtr.Pin(); + if (NotificationItem.IsValid()) + { + NotificationItem->SetText(InText); + NotificationItem->ExpireAndFadeout(); + + NotificationPtr.Reset(); + } + } +#endif + + return true; +} + +bool FHoudiniEngine::UpdateCookingNotification(const FText& InText, const bool bExpireAndFade) +{ +#if WITH_EDITOR + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return false; + + UpdatePersistentNotification(InText, bExpireAndFade); + +#endif + return true; +} + +bool +FHoudiniEngine::UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade) +{ +#if WITH_EDITOR + TimeSinceLastPersistentNotification = 0.0; + + if (!PersistentNotificationPtr.IsValid()) + { + FNotificationInfo Info(InText); + Info.bFireAndForget = false; + Info.FadeOutDuration = HAPI_UNREAL_NOTIFICATION_FADEOUT; + Info.ExpireDuration = HAPI_UNREAL_NOTIFICATION_EXPIRE; + const TSharedPtr< FSlateDynamicImageBrush > HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + + PersistentNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); + //FSlateNotificationManager::Get().Tick(); + } + + TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); + + if (NotificationItem.IsValid()) + { + // Update the persistent notification. + NotificationItem->SetText(InText); + bPersistentAllowExpiry = bExpireAndFade; + } + + //FSlateNotificationManager::Get().Tick(); +#endif + + return true; +} + +void FHoudiniEngine::TickPersistentNotification(const float DeltaTime) +{ + if (PersistentNotificationPtr.IsValid() && DeltaTime > 0.0f) + { + TimeSinceLastPersistentNotification += DeltaTime; + if (bPersistentAllowExpiry && TimeSinceLastPersistentNotification > HAPI_UNREAL_NOTIFICATION_EXPIRE) + { + TSharedPtr NotificationItem = PersistentNotificationPtr.Pin(); + if (NotificationItem.IsValid()) + { + NotificationItem->Fadeout(); + PersistentNotificationPtr.Reset(); + } + } + } + + // Tick the notification manager + //FSlateNotificationManager::Get().Tick(); +} + +void +FHoudiniEngine::UpdateSessionSyncInfoFromHoudini() +{ + if (!bEnableSessionSync) + return; + + // Set the Session Sync settings to Houdini + HAPI_SessionSyncInfo SessionSyncInfo; + //FHoudiniApi::SessionSyncInfo_Create(&SessionSyncInfo); + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetSessionSyncInfo(&Session, &SessionSyncInfo)) + { + bCookUsingHoudiniTime = SessionSyncInfo.cookUsingHoudiniTime; + bSyncViewport = SessionSyncInfo.syncViewport; + } +} + +void +FHoudiniEngine::UploadSessionSyncInfoToHoudini() +{ + // No need to set sessionsync info if we're not using session sync + if (!bEnableSessionSync) + return; + + // Set the Session Sync settings to Houdini + HAPI_SessionSyncInfo SessionSyncInfo; + SessionSyncInfo.cookUsingHoudiniTime = bCookUsingHoudiniTime; + SessionSyncInfo.syncViewport = bSyncViewport; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetSessionSyncInfo(&Session, &SessionSyncInfo)) + HOUDINI_LOG_WARNING(TEXT("Failed to set the SessionSync Infos.")); +} + +void +FHoudiniEngine::StartPDGCommandlet() +{ + if (HoudiniEngineManager) + HoudiniEngineManager->StartPDGCommandlet(); +} + +void +FHoudiniEngine::StopPDGCommandlet() +{ + if (HoudiniEngineManager) + HoudiniEngineManager->StopPDGCommandlet(); +} + +bool +FHoudiniEngine::IsPDGCommandletRunningOrConnected() +{ + if (HoudiniEngineManager) + return HoudiniEngineManager->IsPDGCommandletRunningOrConnected(); + return false; +} + +EHoudiniBGEOCommandletStatus +FHoudiniEngine::GetPDGCommandletStatus() +{ + if (HoudiniEngineManager) + return HoudiniEngineManager->GetPDGCommandletStatus(); + return EHoudiniBGEOCommandletStatus::NotStarted; +} + +void +FHoudiniEngine::UnregisterPostEngineInitCallback() +{ + if (PostEngineInitCallback.IsValid()) + FCoreDelegates::OnPostEngineInit.Remove(PostEngineInitCallback); +} + +bool FHoudiniEngine::IsSyncWithHoudiniCookEnabled() const +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + return HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bSyncWithHoudiniCook : false; +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.h b/Source/HoudiniEngine/Private/HoudiniEngine.h index 264aa78a7..ebe53eed8 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.h +++ b/Source/HoudiniEngine/Private/HoudiniEngine.h @@ -1,347 +1,347 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineTaskInfo.h" -#include "HoudiniRuntimeSettings.h" - -#include "Modules/ModuleInterface.h" - -class FRunnableThread; -class FHoudiniEngineScheduler; -class FHoudiniEngineManager; -class UHoudiniAssetComponent; -class UStaticMesh; -class UMaterial; - -struct FSlateDynamicImageBrush; - -enum class EHoudiniBGEOCommandletStatus : uint8; - -UENUM() -enum class EHoudiniSessionStatus : int8 -{ - Invalid = -1, - - NotStarted, // Session not initialized yet - Connected, // Session successfully started - None, // Session type set to None - Stopped, // Session stopped - Failed, // Session failed to connect - Lost, // Session Lost (HARS/Houdini Crash?) - NoLicense, // Failed to acquire a license -}; - -// Not using the IHoudiniEngine interface for now -class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface -{ - public: - - FHoudiniEngine(); - - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // Return singleton instance of Houdini Engine, used internally. - static FHoudiniEngine & Get(); - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // Return the location of the currently loaded LibHAPI - virtual const FString& GetLibHAPILocation() const; - - // Return the houdini executable to use - static const FString GetHoudiniExecutable(); - - // Session accessor - virtual const HAPI_Session* GetSession() const; - - virtual const EHoudiniSessionStatus& GetSessionStatus() const; - - virtual void SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus); - - // Default cook options - static HAPI_CookOptions GetDefaultCookOptions(); - - // Creates a new session - bool StartSession( - HAPI_Session*& SessionPtr, - const bool& StartAutomaticServer, - const float& AutomaticServerTimeout, - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const int32& ServerPort, - const FString& ServerHost); - - // Stop the current session if it is valid - bool StopSession(HAPI_Session*& SessionPtr); - - // Creates a session sync session - bool SessionSyncConnect( - const EHoudiniRuntimeSettingsSessionType& SessionType, - const FString& ServerPipeName, - const FString& ServerHost, - const int32& ServerPort); - - // Stops the current session - bool StopSession(); - // Stops, then creates a new session - bool RestartSession(); - // Creates a session, start HARS - bool CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName=NAME_None); - // Connect to an existing HE session - bool ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType); - - // Starts the HoudiniEngineManager ticking - void StartTicking(); - // Stops the HoudiniEngineManager ticking and invalidate the session - void StopTicking(); - - // Initialize HAPI - bool InitializeHAPISession(); - - // Indicate to the plugin that the session is now invalid (HAPI has likely crashed...) - void OnSessionLost(); - - bool CreateTaskSlateNotification( - const FText& InText, - const bool& bForceNow = false, - const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, - const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); - - bool UpdateTaskSlateNotification(const FText& InText); - bool FinishTaskSlateNotification(const FText& InText); - - // Only update persistent notification if cooking notification has been enabled in the settings. - bool UpdateCookingNotification(const FText& InText, const bool bExpireAndFade); - - // Update persistent notification irrespective of any notification enable/disable settings. - bool UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade); - - // If the time since last persistent notification has expired, fade out the persistent notification. - void TickPersistentNotification(float DeltaTime); - - void SetHapiNotificationStartedTime(const double& InTime) { HapiNotificationStarted = InTime; }; - - // Register task for execution. - virtual void AddTask(const FHoudiniEngineTask & InTask); - // Register task info. - virtual void AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo); - // Remove task info. - virtual void RemoveTaskInfo(const FGuid& InHapiGUID); - // Remove task info. - virtual bool RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo); - // Register asset to the manager - //virtual void AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC); - - // Indicates whether or not cooking is currently enabled - bool IsCookingEnabled() const; - // Sets whether or not cooking is currently enabled - void SetCookingEnabled(const bool& bInEnableCooking); - - // Check if we need to refresh UI when cooking is paused - bool HasUIFinishRefreshingWhenPausingCooking() const { return UIRefreshCountWhenPauseCooking <= 0; }; - - // Reset number of registered HACs when cooking is paused - void SetUIRefreshCountWhenPauseCooking(const int32& bInCount) { UIRefreshCountWhenPauseCooking = bInCount; }; - // Reduce the count by 1 when an HAC UI is refreshed when cooking is paused - void RefreshUIDisplayedWhenPauseCooking() { UIRefreshCountWhenPauseCooking -= 1; }; - - // Indicates whether or not the first attempt to create a Houdini session was made - bool GetFirstSessionCreated() const; - // Sets whether or not the first attempt to create a Houdini session was made - void SetFirstSessionCreated(const bool& bInStarted) { bFirstSessionCreated = bInStarted; }; - - bool IsSessionSyncEnabled() const { return bEnableSessionSync; }; - - bool IsSyncWithHoudiniCookEnabled() const; - - bool IsCookUsingHoudiniTimeEnabled() const { return bCookUsingHoudiniTime; }; - - bool IsSyncViewportEnabled() const { return bSyncViewport; }; - - bool IsSyncHoudiniViewportEnabled() const { return bSyncHoudiniViewport; }; - - bool IsSyncUnrealViewportEnabled() const { return bSyncUnrealViewport; }; - - // Helper function to update our session sync infos from Houdini's - void UpdateSessionSyncInfoFromHoudini(); - - // Helper function to update Houdini's Session sync infos from ours - void UploadSessionSyncInfoToHoudini(); - - // Sets whether or not viewport sync is enabled - void SetSyncViewportEnabled(const bool& bInSync) { bSyncViewport = bInSync; }; - // Sets whether or not we want to sync the houdini viewport to unreal's - void SetSyncHoudiniViewportEnabled(const bool& bInSync) { bSyncHoudiniViewport = bInSync; }; - // Sets whether or not we want to sync unreal's viewport to Houdini's - void SetSyncUnrealViewportEnabled(const bool& bInSync) { bSyncUnrealViewport = bInSync; }; - - // Returns the default Houdini Logo Static Mesh - virtual TWeakObjectPtr GetHoudiniLogoStaticMesh() const { return HoudiniLogoStaticMesh; }; - - // Returns either the default Houdini material or the default template material - virtual TWeakObjectPtr GetHoudiniDefaultMaterial(const bool& bIsTemplate) const { return bIsTemplate ? HoudiniTemplateMaterial : HoudiniDefaultMaterial; }; - - // Returns the default Houdini material - virtual TWeakObjectPtr GetHoudiniDefaultMaterial() const { return HoudiniDefaultMaterial; }; - // Returns the default template Houdini material - virtual TWeakObjectPtr GetHoudiniTemplatedMaterial() const { return HoudiniTemplateMaterial; }; - // Returns a shared Ptr to the houdini logo - TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; - // Returns a shared Ptr to the houdini engine logo - TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; - - // Returns the default Houdini reference mesh - virtual TWeakObjectPtr GetHoudiniDefaultReferenceMesh() const { return HoudiniDefaultReferenceMesh; }; - // Returns the default Houdini reference mesh material - virtual TWeakObjectPtr GetHoudiniDefaultReferenceMeshMaterial() const { return HoudiniDefaultReferenceMeshMaterial; }; - - const HAPI_License GetLicenseType() const { return LicenseType; }; - - const bool IsLicenseIndie() const { return (LicenseType == HAPI_LICENSE_HOUDINI_ENGINE_INDIE || LicenseType == HAPI_LICENSE_HOUDINI_INDIE); }; - - // Session Sync ProcHandle accessor - FProcHandle GetHESSProcHandle() const { return HESS_ProcHandle; }; - void SetHESSProcHandle(const FProcHandle& InProcHandle) { HESS_ProcHandle = InProcHandle; }; - - void StartPDGCommandlet(); - - void StopPDGCommandlet(); - - bool IsPDGCommandletRunningOrConnected(); - - EHoudiniBGEOCommandletStatus GetPDGCommandletStatus(); - - FHoudiniEngineManager* GetHoudiniEngineManager() { return HoudiniEngineManager; } - - const FHoudiniEngineManager* GetHoudiniEngineManager() const { return HoudiniEngineManager; } - - void UnregisterPostEngineInitCallback(); - - private: - - // Singleton instance of Houdini Engine. - static FHoudiniEngine * HoudiniEngineInstance; - - // Location of libHAPI binary. - FString LibHAPILocation; - - // The Houdini Engine session. - HAPI_Session Session; - - // The Houdini Engine session's status - EHoudiniSessionStatus SessionStatus; - - // The type of HE license used by the current session - HAPI_License LicenseType; - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // Map of task statuses. - TMap TaskInfos; - - // Thread used to execute the scheduler. - FRunnableThread * HoudiniEngineSchedulerThread; - // Scheduler used to schedule HAPI instantiation and cook tasks. - FHoudiniEngineScheduler * HoudiniEngineScheduler; - - // Thread used to execute the manager. - FRunnableThread * HoudiniEngineManagerThread; - // Scheduler used to monitor and process Houdini Asset Components - FHoudiniEngineManager * HoudiniEngineManager; - - // Process Handle for session sync - FProcHandle HESS_ProcHandle; - - // Is set to true when mismatch between defined and running HAPI versions is detected. - //bool bHAPIVersionMismatch; - - // Global cooking flag, used to pause HEngine while using the editor - bool bEnableCookingGlobal; - // Counter of HACs that need to be refreshed when pause cooking - int32 UIRefreshCountWhenPauseCooking; - - // Indicates that the first attempt to create a session has been done - // This is to delay the first "automatic" session creation for the first cook - // or instantiation rather than when the module started. - bool bFirstSessionCreated; - - // Indicates if the current session is a SessionSync one - bool bEnableSessionSync; - - // If true and we're in SessionSync, keeps the assets on the plugin side synchronized with changes on the Houdini side. - //bool bSyncWithHoudiniCook; - - // If true and we're in SessionSync, use the Houdini Timeline time to cook assets. - bool bCookUsingHoudiniTime; - - // If true and we're in Session Sync, the Houdini and Unreal viewport will be synchronized. - bool bSyncViewport; - // If true and we're in Session Sync, the Houdini viewport will be synchronized to Unreal's. - bool bSyncHoudiniViewport; - // If true and we're in Session Sync, the Unreal viewport will be synchronized to Houdini's. - bool bSyncUnrealViewport; - - // Static mesh used for Houdini logo rendering. - TWeakObjectPtr HoudiniLogoStaticMesh; - - // Material used as default material. - TWeakObjectPtr HoudiniDefaultMaterial; - - // Material used as default template material. - TWeakObjectPtr HoudiniTemplateMaterial; - - // Houdini logo brush. - TSharedPtr HoudiniLogoBrush; - // Houdini logo brush. - TSharedPtr HoudiniEngineLogoBrush; - - // Static mesh used for default mesh reference - TWeakObjectPtr HoudiniDefaultReferenceMesh; - - // Material used for default mesh reference - TWeakObjectPtr HoudiniDefaultReferenceMeshMaterial; - - FDelegateHandle PostEngineInitCallback; - -#if WITH_EDITOR - /** Notification used by this component. **/ - TWeakPtr NotificationPtr; - - /** Persistent notification. **/ - bool bPersistentAllowExpiry; - TWeakPtr PersistentNotificationPtr; - float TimeSinceLastPersistentNotification; - - /** Used to delay notification updates for HAPI asynchronous work. **/ - double HapiNotificationStarted; -#endif +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineTaskInfo.h" +#include "HoudiniRuntimeSettings.h" + +#include "Modules/ModuleInterface.h" + +class FRunnableThread; +class FHoudiniEngineScheduler; +class FHoudiniEngineManager; +class UHoudiniAssetComponent; +class UStaticMesh; +class UMaterial; + +struct FSlateDynamicImageBrush; + +enum class EHoudiniBGEOCommandletStatus : uint8; + +UENUM() +enum class EHoudiniSessionStatus : int8 +{ + Invalid = -1, + + NotStarted, // Session not initialized yet + Connected, // Session successfully started + None, // Session type set to None + Stopped, // Session stopped + Failed, // Session failed to connect + Lost, // Session Lost (HARS/Houdini Crash?) + NoLicense, // Failed to acquire a license +}; + +// Not using the IHoudiniEngine interface for now +class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface +{ + public: + + FHoudiniEngine(); + + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // Return singleton instance of Houdini Engine, used internally. + static FHoudiniEngine & Get(); + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // Return the location of the currently loaded LibHAPI + virtual const FString& GetLibHAPILocation() const; + + // Return the houdini executable to use + static const FString GetHoudiniExecutable(); + + // Session accessor + virtual const HAPI_Session* GetSession() const; + + virtual const EHoudiniSessionStatus& GetSessionStatus() const; + + virtual void SetSessionStatus(const EHoudiniSessionStatus& InSessionStatus); + + // Default cook options + static HAPI_CookOptions GetDefaultCookOptions(); + + // Creates a new session + bool StartSession( + HAPI_Session*& SessionPtr, + const bool& StartAutomaticServer, + const float& AutomaticServerTimeout, + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const int32& ServerPort, + const FString& ServerHost); + + // Stop the current session if it is valid + bool StopSession(HAPI_Session*& SessionPtr); + + // Creates a session sync session + bool SessionSyncConnect( + const EHoudiniRuntimeSettingsSessionType& SessionType, + const FString& ServerPipeName, + const FString& ServerHost, + const int32& ServerPort); + + // Stops the current session + bool StopSession(); + // Stops, then creates a new session + bool RestartSession(); + // Creates a session, start HARS + bool CreateSession(const EHoudiniRuntimeSettingsSessionType& SessionType, FName OverrideServerPipeName=NAME_None); + // Connect to an existing HE session + bool ConnectSession(const EHoudiniRuntimeSettingsSessionType& SessionType); + + // Starts the HoudiniEngineManager ticking + void StartTicking(); + // Stops the HoudiniEngineManager ticking and invalidate the session + void StopTicking(); + + // Initialize HAPI + bool InitializeHAPISession(); + + // Indicate to the plugin that the session is now invalid (HAPI has likely crashed...) + void OnSessionLost(); + + bool CreateTaskSlateNotification( + const FText& InText, + const bool& bForceNow = false, + const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, + const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); + + bool UpdateTaskSlateNotification(const FText& InText); + bool FinishTaskSlateNotification(const FText& InText); + + // Only update persistent notification if cooking notification has been enabled in the settings. + bool UpdateCookingNotification(const FText& InText, const bool bExpireAndFade); + + // Update persistent notification irrespective of any notification enable/disable settings. + bool UpdatePersistentNotification(const FText& InText, const bool bExpireAndFade); + + // If the time since last persistent notification has expired, fade out the persistent notification. + void TickPersistentNotification(float DeltaTime); + + void SetHapiNotificationStartedTime(const double& InTime) { HapiNotificationStarted = InTime; }; + + // Register task for execution. + virtual void AddTask(const FHoudiniEngineTask & InTask); + // Register task info. + virtual void AddTaskInfo(const FGuid& InHapiGUID, const FHoudiniEngineTaskInfo & InTaskInfo); + // Remove task info. + virtual void RemoveTaskInfo(const FGuid& InHapiGUID); + // Remove task info. + virtual bool RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo & OutTaskInfo); + // Register asset to the manager + //virtual void AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC); + + // Indicates whether or not cooking is currently enabled + bool IsCookingEnabled() const; + // Sets whether or not cooking is currently enabled + void SetCookingEnabled(const bool& bInEnableCooking); + + // Check if we need to refresh UI when cooking is paused + bool HasUIFinishRefreshingWhenPausingCooking() const { return UIRefreshCountWhenPauseCooking <= 0; }; + + // Reset number of registered HACs when cooking is paused + void SetUIRefreshCountWhenPauseCooking(const int32& bInCount) { UIRefreshCountWhenPauseCooking = bInCount; }; + // Reduce the count by 1 when an HAC UI is refreshed when cooking is paused + void RefreshUIDisplayedWhenPauseCooking() { UIRefreshCountWhenPauseCooking -= 1; }; + + // Indicates whether or not the first attempt to create a Houdini session was made + bool GetFirstSessionCreated() const; + // Sets whether or not the first attempt to create a Houdini session was made + void SetFirstSessionCreated(const bool& bInStarted) { bFirstSessionCreated = bInStarted; }; + + bool IsSessionSyncEnabled() const { return bEnableSessionSync; }; + + bool IsSyncWithHoudiniCookEnabled() const; + + bool IsCookUsingHoudiniTimeEnabled() const { return bCookUsingHoudiniTime; }; + + bool IsSyncViewportEnabled() const { return bSyncViewport; }; + + bool IsSyncHoudiniViewportEnabled() const { return bSyncHoudiniViewport; }; + + bool IsSyncUnrealViewportEnabled() const { return bSyncUnrealViewport; }; + + // Helper function to update our session sync infos from Houdini's + void UpdateSessionSyncInfoFromHoudini(); + + // Helper function to update Houdini's Session sync infos from ours + void UploadSessionSyncInfoToHoudini(); + + // Sets whether or not viewport sync is enabled + void SetSyncViewportEnabled(const bool& bInSync) { bSyncViewport = bInSync; }; + // Sets whether or not we want to sync the houdini viewport to unreal's + void SetSyncHoudiniViewportEnabled(const bool& bInSync) { bSyncHoudiniViewport = bInSync; }; + // Sets whether or not we want to sync unreal's viewport to Houdini's + void SetSyncUnrealViewportEnabled(const bool& bInSync) { bSyncUnrealViewport = bInSync; }; + + // Returns the default Houdini Logo Static Mesh + virtual TWeakObjectPtr GetHoudiniLogoStaticMesh() const { return HoudiniLogoStaticMesh; }; + + // Returns either the default Houdini material or the default template material + virtual TWeakObjectPtr GetHoudiniDefaultMaterial(const bool& bIsTemplate) const { return bIsTemplate ? HoudiniTemplateMaterial : HoudiniDefaultMaterial; }; + + // Returns the default Houdini material + virtual TWeakObjectPtr GetHoudiniDefaultMaterial() const { return HoudiniDefaultMaterial; }; + // Returns the default template Houdini material + virtual TWeakObjectPtr GetHoudiniTemplatedMaterial() const { return HoudiniTemplateMaterial; }; + // Returns a shared Ptr to the houdini logo + TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; + // Returns a shared Ptr to the houdini engine logo + TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; + + // Returns the default Houdini reference mesh + virtual TWeakObjectPtr GetHoudiniDefaultReferenceMesh() const { return HoudiniDefaultReferenceMesh; }; + // Returns the default Houdini reference mesh material + virtual TWeakObjectPtr GetHoudiniDefaultReferenceMeshMaterial() const { return HoudiniDefaultReferenceMeshMaterial; }; + + const HAPI_License GetLicenseType() const { return LicenseType; }; + + const bool IsLicenseIndie() const { return (LicenseType == HAPI_LICENSE_HOUDINI_ENGINE_INDIE || LicenseType == HAPI_LICENSE_HOUDINI_INDIE); }; + + // Session Sync ProcHandle accessor + FProcHandle GetHESSProcHandle() const { return HESS_ProcHandle; }; + void SetHESSProcHandle(const FProcHandle& InProcHandle) { HESS_ProcHandle = InProcHandle; }; + + void StartPDGCommandlet(); + + void StopPDGCommandlet(); + + bool IsPDGCommandletRunningOrConnected(); + + EHoudiniBGEOCommandletStatus GetPDGCommandletStatus(); + + FHoudiniEngineManager* GetHoudiniEngineManager() { return HoudiniEngineManager; } + + const FHoudiniEngineManager* GetHoudiniEngineManager() const { return HoudiniEngineManager; } + + void UnregisterPostEngineInitCallback(); + + private: + + // Singleton instance of Houdini Engine. + static FHoudiniEngine * HoudiniEngineInstance; + + // Location of libHAPI binary. + FString LibHAPILocation; + + // The Houdini Engine session. + HAPI_Session Session; + + // The Houdini Engine session's status + EHoudiniSessionStatus SessionStatus; + + // The type of HE license used by the current session + HAPI_License LicenseType; + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // Map of task statuses. + TMap TaskInfos; + + // Thread used to execute the scheduler. + FRunnableThread * HoudiniEngineSchedulerThread; + // Scheduler used to schedule HAPI instantiation and cook tasks. + FHoudiniEngineScheduler * HoudiniEngineScheduler; + + // Thread used to execute the manager. + FRunnableThread * HoudiniEngineManagerThread; + // Scheduler used to monitor and process Houdini Asset Components + FHoudiniEngineManager * HoudiniEngineManager; + + // Process Handle for session sync + FProcHandle HESS_ProcHandle; + + // Is set to true when mismatch between defined and running HAPI versions is detected. + //bool bHAPIVersionMismatch; + + // Global cooking flag, used to pause HEngine while using the editor + bool bEnableCookingGlobal; + // Counter of HACs that need to be refreshed when pause cooking + int32 UIRefreshCountWhenPauseCooking; + + // Indicates that the first attempt to create a session has been done + // This is to delay the first "automatic" session creation for the first cook + // or instantiation rather than when the module started. + bool bFirstSessionCreated; + + // Indicates if the current session is a SessionSync one + bool bEnableSessionSync; + + // If true and we're in SessionSync, keeps the assets on the plugin side synchronized with changes on the Houdini side. + //bool bSyncWithHoudiniCook; + + // If true and we're in SessionSync, use the Houdini Timeline time to cook assets. + bool bCookUsingHoudiniTime; + + // If true and we're in Session Sync, the Houdini and Unreal viewport will be synchronized. + bool bSyncViewport; + // If true and we're in Session Sync, the Houdini viewport will be synchronized to Unreal's. + bool bSyncHoudiniViewport; + // If true and we're in Session Sync, the Unreal viewport will be synchronized to Houdini's. + bool bSyncUnrealViewport; + + // Static mesh used for Houdini logo rendering. + TWeakObjectPtr HoudiniLogoStaticMesh; + + // Material used as default material. + TWeakObjectPtr HoudiniDefaultMaterial; + + // Material used as default template material. + TWeakObjectPtr HoudiniTemplateMaterial; + + // Houdini logo brush. + TSharedPtr HoudiniLogoBrush; + // Houdini logo brush. + TSharedPtr HoudiniEngineLogoBrush; + + // Static mesh used for default mesh reference + TWeakObjectPtr HoudiniDefaultReferenceMesh; + + // Material used for default mesh reference + TWeakObjectPtr HoudiniDefaultReferenceMeshMaterial; + + FDelegateHandle PostEngineInitCallback; + +#if WITH_EDITOR + /** Notification used by this component. **/ + TWeakPtr NotificationPtr; + + /** Persistent notification. **/ + bool bPersistentAllowExpiry; + TWeakPtr PersistentNotificationPtr; + float TimeSinceLastPersistentNotification; + + /** Used to delay notification updates for HAPI asynchronous work. **/ + double HapiNotificationStarted; +#endif }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp index c6f308dfd..22be802fc 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp @@ -1,1724 +1,1735 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineManager.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineString.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniParameterTranslator.h" -#include "HoudiniPDGManager.h" -#include "HoudiniInputTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniHandleTranslator.h" -#include "HoudiniSplineTranslator.h" - -#include "Misc/MessageDialog.h" -#include "Misc/ScopedSlowTask.h" -#include "Containers/Ticker.h" -#include "HAL/IConsoleManager.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "EditorViewportClient.h" - #include "Kismet/KismetMathLibrary.h" - - //#include "UnrealEd.h" - #include "UnrealEdGlobals.h" - #include "Editor/UnrealEdEngine.h" - #include "IPackageAutoSaver.h" -#endif - -static TAutoConsoleVariable CVarHoudiniEngineTickTimeLimit( - TEXT("HoudiniEngine.TickTimeLimit"), - 1.0, - TEXT("Time limit after which HDA processing will be stopped, until the next tick of the Houdini Engine Manager.\n") - TEXT("<= 0.0: No Limit\n") - TEXT("1.0: Default\n") -); - -FHoudiniEngineManager::FHoudiniEngineManager() - : CurrentIndex(0) - , ComponentCount(0) - , bMustStopTicking(false) - , SyncedHoudiniViewportPivotPosition(FVector::ZeroVector) - , SyncedHoudiniViewportQuat(FQuat::Identity) - , SyncedHoudiniViewportOffset(0.0f) - , SyncedUnrealViewportPosition(FVector::ZeroVector) - , SyncedUnrealViewportRotation(FRotator::ZeroRotator) - , SyncedUnrealViewportLookatPosition(FVector::ZeroVector) - , ZeroOffsetValue(0.f) - , bOffsetZeroed(false) -{ - -} - -FHoudiniEngineManager::~FHoudiniEngineManager() -{ - PDGManager.StopBGEOCommandletAndEndpoint(); -} - -void -FHoudiniEngineManager::StartHoudiniTicking() -{ - // If we have no timer delegate spawned, spawn one. - if (!TickerHandle.IsValid() && GEditor) - { - // We use the ticker manager so we get ticked once per frame, no more. - TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FHoudiniEngineManager::Tick)); - - // Grab current time for delayed notification. - FHoudiniEngine::Get().SetHapiNotificationStartedTime(FPlatformTime::Seconds()); - } -} - -void -FHoudiniEngineManager::StopHoudiniTicking() -{ - if (TickerHandle.IsValid() && GEditor) - { - if (IsInGameThread()) - { - FTicker::GetCoreTicker().RemoveTicker(TickerHandle); - TickerHandle.Reset(); - - // Reset time for delayed notification. - FHoudiniEngine::Get().SetHapiNotificationStartedTime(0.0); - - bMustStopTicking = false; - } - else - { - // We can't stop ticking now as we're not in the game Thread, - // and accessing the timer would crash, indicate that we want to stop ticking asap - // This can happen when loosing a session due to a Houdini crash - bMustStopTicking = true; - } - } -} - -bool -FHoudiniEngineManager::Tick(float DeltaTime) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::Tick); - - EnableEditorAutoSave(nullptr); - - FHoudiniEngine::Get().TickPersistentNotification(DeltaTime); - - if (bMustStopTicking) - { - // Ticking should be stopped immediately - StopHoudiniTicking(); - return true; - } - - // Build a set of components that need to be processed - // 1 - selected HACs - // 2 - "Active" HACs - // 3 - The "next" inactive HAC - TArray ComponentsToProcess; - if (FHoudiniEngineRuntime::IsInitialized()) - { - FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); - - //FScopeLock ScopeLock(&CriticalSection); - ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); - - // Wrap around if needed - if (CurrentIndex >= ComponentCount) - CurrentIndex = 0; - - for (uint32 nIdx = 0; nIdx < ComponentCount; nIdx++) - { - UHoudiniAssetComponent * CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(nIdx); - if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) - { - // Invalid component, do not process - continue; - } - else if (CurrentComponent->IsPendingKill() - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) - { - // Component being deleted, do not process - continue; - } - - if (!CurrentComponent->IsFullyLoaded()) - { - // Let the component figure out whether it's fully loaded or not. - CurrentComponent->HoudiniEngineTick(); - if (!CurrentComponent->IsFullyLoaded()) - continue; // We need to wait some more. - } - - if (!CurrentComponent->IsValidComponent()) - { - // This component is no longer valid. Prevent it from being processed, and remove it. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); - continue; - } - - AActor* Owner = CurrentComponent->GetOwner(); - if (Owner && Owner->IsSelectedInEditor()) - { - // 1. Add selected HACs - // If the component's owner is selected, add it to the set - ComponentsToProcess.Add(CurrentComponent); - } - else if (CurrentComponent->GetAssetState() != EHoudiniAssetState::NeedInstantiation - && CurrentComponent->GetAssetState() != EHoudiniAssetState::None) - { - // 2. Add "Active" HACs, the only two non-active states are: - // NeedInstantiation (loaded, not instantiated in H yet, not modified) - // None (no processing currently) - ComponentsToProcess.Add(CurrentComponent); - } - else if(nIdx == CurrentIndex) - { - // 3. Add the "Current" HAC - ComponentsToProcess.Add(CurrentComponent); - } - - // Set the LastTickTime on the "current" HAC to 0 to ensure it's treated first - if (nIdx == CurrentIndex) - { - CurrentComponent->LastTickTime = 0.0; - } - } - - // Increment the current index for the next tick - CurrentIndex++; - } - - // Sort the components by last tick time - ComponentsToProcess.Sort([](const UHoudiniAssetComponent& A, const UHoudiniAssetComponent& B) { return A.LastTickTime < B.LastTickTime; }); - - // Time limit for processing - double dProcessTimeLimit = CVarHoudiniEngineTickTimeLimit.GetValueOnAnyThread(); - double dProcessStartTime = FPlatformTime::Seconds(); - - // Process all the components in the list - for(UHoudiniAssetComponent* CurrentComponent : ComponentsToProcess) - { - // Tick the notification manager - //FHoudiniEngine::Get().TickPersistentNotification(0.0f); - - double dNow = FPlatformTime::Seconds(); - if (dProcessTimeLimit > 0.0 - && dNow - dProcessStartTime > dProcessTimeLimit) - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); - break; - } - - // Update the tick time for this component - CurrentComponent->LastTickTime = dNow; - - // Handle template processing (for BP) first - // We don't want to the template component processing to trigger session creation - if (CurrentComponent->GetAssetState() == EHoudiniAssetState::ProcessTemplate) - { - if (CurrentComponent->IsTemplate() && !CurrentComponent->HasOpenEditor()) - { - // This component template no longer has an open editor and can be deregistered. - // TODO: Replace this polling mechanism with an "On Asset Closed" event if we - // can find one that actually works. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); - continue; - } - - if (CurrentComponent->NeedBlueprintStructureUpdate()) - { - CurrentComponent->OnBlueprintStructureModified(); - } - - if (CurrentComponent->NeedBlueprintUpdate()) - { - CurrentComponent->OnBlueprintModified(); - } - - if (FHoudiniEngine::Get().IsCookingEnabled()) - { - // Only process component template parameter updates when cooking is enabled. - if (CurrentComponent->NeedUpdateParameters() || CurrentComponent->NeedUpdateInputs()) - { - CurrentComponent->OnTemplateParametersChanged(); - } - } - - if (CurrentComponent->NeedOutputUpdate()) - { - // TODO: Transfer template output changes over to the preview instance. - } - continue; - } - - // Process the component - bool bKeepProcessing = true; - while (bKeepProcessing) - { - // Tick the notification manager - FHoudiniEngine::Get().TickPersistentNotification(0.0f); - - // See if we should start the default "first" session - AutoStartFirstSessionIfNeeded(CurrentComponent); - - EHoudiniAssetState PrevState = CurrentComponent->GetAssetState(); - ProcessComponent(CurrentComponent); - EHoudiniAssetState NewState = CurrentComponent->GetAssetState(); - - // In order to process components faster / with less ticks, - // we may continue processing the component if it ends up in certain states - switch (NewState) - { - case EHoudiniAssetState::NewHDA: - case EHoudiniAssetState::PreInstantiation: - case EHoudiniAssetState::PreCook: - case EHoudiniAssetState::PostCook: - case EHoudiniAssetState::PreProcess: - case EHoudiniAssetState::Processing: - bKeepProcessing = true; - break; - - case EHoudiniAssetState::NeedInstantiation: - case EHoudiniAssetState::Instantiating: - case EHoudiniAssetState::Cooking: - case EHoudiniAssetState::None: - case EHoudiniAssetState::ProcessTemplate: - case EHoudiniAssetState::NeedRebuild: - case EHoudiniAssetState::NeedDelete: - case EHoudiniAssetState::Deleting: - bKeepProcessing = false; - break; - } - - // Safeguard, useless? - // Stop processing if the state hasn't changed - if (PrevState == NewState) - bKeepProcessing = false; - - dNow = FPlatformTime::Seconds(); - if (dProcessTimeLimit > 0.0 && dNow - dProcessStartTime > dProcessTimeLimit) - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); - break; - } - - // Update the tick time for this component - CurrentComponent->LastTickTime = dNow; - } - } - - // Handle Asset delete - if (FHoudiniEngineRuntime::IsInitialized()) - { - int32 PendingDeleteCount = FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteCount(); - for (int32 DeleteIdx = PendingDeleteCount - 1; DeleteIdx >= 0; DeleteIdx--) - { - HAPI_NodeId NodeIdToDelete = (HAPI_NodeId)FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteAt(DeleteIdx); - FGuid HapiDeletionGUID; - bool bShouldDeleteParent = FHoudiniEngineRuntime::Get().IsParentNodePendingDelete(NodeIdToDelete); - if (StartTaskAssetDelete(NodeIdToDelete, HapiDeletionGUID, bShouldDeleteParent)) - { - FHoudiniEngineRuntime::Get().RemoveNodeIdPendingDeleteAt(DeleteIdx); - if (bShouldDeleteParent) - FHoudiniEngineRuntime::Get().RemoveParentNodePendingDelete(NodeIdToDelete); - } - } - } - - // Update PDG Contexts and asset link if needed - PDGManager.Update(); - - // Session Sync Updates - if (FHoudiniEngine::Get().IsSessionSyncEnabled()) - { - // See if the session sync settings have changed on the houdini side, update ours if they did - FHoudiniEngine::Get().UpdateSessionSyncInfoFromHoudini(); -#if WITH_EDITOR - // Update the Houdini viewport from unreal if needed - if (FHoudiniEngine::Get().IsSyncViewportEnabled()) - { - // Sync the Houdini viewport to Unreal - if (!SyncHoudiniViewportToUnreal()) - { - // If the unreal viewport hasnt changed, - // See if we need to sync the Unreal viewport from Houdini's - SyncUnrealViewportToHoudini(); - } - } -#endif - } - else - { - // reset zero offset variables when session sync is off - if (ZeroOffsetValue != 0.f) - ZeroOffsetValue = 0.f; - - if (bOffsetZeroed) - bOffsetZeroed = false; - } - - // Tick the notification manager - FHoudiniEngine::Get().TickPersistentNotification(0.0f); - - return true; -} - -void -FHoudiniEngineManager::AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC) -{ - // See if we should start the default "first" session - if (FHoudiniEngine::Get().GetSession() - || FHoudiniEngine::Get().GetFirstSessionCreated() - || !InCurrentHAC) - return; - - // Only try to start the default session if we have an "active" HAC - const EHoudiniAssetState CurrentState = InCurrentHAC->GetAssetState(); - if (CurrentState == EHoudiniAssetState::NewHDA - || CurrentState == EHoudiniAssetState::PreInstantiation - || CurrentState == EHoudiniAssetState::Instantiating - || CurrentState == EHoudiniAssetState::PreCook - || CurrentState == EHoudiniAssetState::Cooking) - { - FString StatusText = TEXT("Initializing Houdini Engine..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); - - // We want to yield for a bit. - //FPlatformProcess::Sleep(0.5f); - - // Indicates that we've tried to start the session once no matter if it failed or succeed - FHoudiniEngine::Get().SetFirstSessionCreated(true); - - // Attempt to restart the session - if (!FHoudiniEngine::Get().RestartSession()) - { - // We failed to start the session - // Stop ticking until it's manually restarted - StopHoudiniTicking(); - - StatusText = TEXT("Houdini Engine failed to initialize."); - } - else - { - StatusText = TEXT("Houdini Engine successfully initialized."); - } - - // Finish the notification and display the results - FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); - } -} - -void -FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::ProcessComponent); - - if (!HAC || HAC->IsPendingKill()) - return; - - // No need to process component not tied to an asset - if (!HAC->GetHoudiniAsset()) - return; - - const EHoudiniAssetState AssetStateToProcess = HAC->GetAssetState(); - - // If cooking is paused, stay in the current state until cooking's resumed, unless we are in NewHDA - if (!FHoudiniEngine::Get().IsCookingEnabled() && AssetStateToProcess != EHoudiniAssetState::NewHDA) - { - // We can only handle output updates - if (HAC->GetAssetState() == EHoudiniAssetState::None && HAC->NeedOutputUpdate()) - { - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Refresh UI when pause cooking - if (!FHoudiniEngine::Get().HasUIFinishRefreshingWhenPausingCooking()) - { - // Trigger a details panel update if the Houdini asset actor is selected - if (HAC->IsOwnerSelected()) - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // Finished refreshing UI of one HDA. - FHoudiniEngine::Get().RefreshUIDisplayedWhenPauseCooking(); - } - - // Prevent any other state change to happen - return; - } - - switch (AssetStateToProcess) - { - case EHoudiniAssetState::NeedInstantiation: - { - // Do nothing unless the HAC has been updated - if (HAC->NeedUpdate()) - { - HAC->OnPrePreInstantiation(); - HAC->bForceNeedUpdate = false; - // Update the HAC's state - HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); - } - else if (HAC->NeedOutputUpdate()) - { - // Output updates do not recquire the HDA to be instantiated - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Update world input if we have any - FHoudiniInputTranslator::UpdateWorldInputs(HAC); - - break; - } - - case EHoudiniAssetState::NewHDA: - { - // Update parameters. Since there is no instantiated node yet, this will only fetch the defaults from - // the asset definition. - FHoudiniParameterTranslator::UpdateParameters(HAC); - // Since the HAC only has the asset definition's default parameter interface, without any asset or node ids, - // we mark it has requiring a parameter definition sync. This will be carried out pre-cook. - HAC->bParameterDefinitionUpdateNeeded = true; - - HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); - break; - } - - case EHoudiniAssetState::PreInstantiation: - { - // Only proceed forward if we don't need to wait for our input HoudiniAssets to finish cooking/instantiating - if (HAC->NeedsToWaitForInputHoudiniAssets()) - break; - - FGuid TaskGuid; - FString HapiAssetName; - UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (StartTaskAssetInstantiation(HoudiniAsset, HAC->GetDisplayName(), TaskGuid, HapiAssetName)) - { - // Update the HAC's state - HAC->SetAssetState(EHoudiniAssetState::Instantiating); - - // Update the Task GUID - HAC->HapiGUID = TaskGuid; - - // Update the HapiAssetName - HAC->HapiAssetName = HapiAssetName; - } - else - { - // If we couldnt instantiate the asset - // Change the state back to NeedInstantiating - HAC->SetAssetState(EHoudiniAssetState::NeedInstantiation); - } - break; - } - - case EHoudiniAssetState::Instantiating: - { - EHoudiniAssetState NewState = EHoudiniAssetState::Instantiating; - if (UpdateInstantiating(HAC, NewState)) - { - // We need to update the HAC's state - HAC->SetAssetState(NewState); - EnableEditorAutoSave(HAC); - } - else - { - DisableEditorAutoSave(HAC); - } - break; - } - - case EHoudiniAssetState::PreCook: - { - // Only proceed forward if we don't need to wait for our input - // HoudiniAssets to finish cooking/instantiating - if (HAC->NeedsToWaitForInputHoudiniAssets()) - break; - - HAC->OnPrePreCook(); - // Update all the HAPI nodes, parameters, inputs etc... - PreCook(HAC); - HAC->OnPostPreCook(); - - // Create a Cooking task only if necessary - bool bCookStarted = false; - if (IsCookingEnabledForHoudiniAsset(HAC)) - { - FGuid TaskGUID = HAC->GetHapiGUID(); - if ( StartTaskAssetCooking(HAC->GetAssetId(), HAC->GetDisplayName(), TaskGUID) ) - { - // Updates the HAC's state - HAC->SetAssetState(EHoudiniAssetState::Cooking); - HAC->HapiGUID = TaskGUID; - bCookStarted = true; - } - } - - if(!bCookStarted) - { - // Just refresh editor properties? - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // TODO: Check! update state? - HAC->SetAssetState(EHoudiniAssetState::None); - } - break; - } - - case EHoudiniAssetState::Cooking: - { - EHoudiniAssetState NewState = EHoudiniAssetState::Cooking; - bool state = UpdateCooking(HAC, NewState); - if (state) - { - // We need to update the HAC's state - HAC->SetAssetState(NewState); - EnableEditorAutoSave(HAC); - } - else - { - DisableEditorAutoSave(HAC); - } - break; - } - - case EHoudiniAssetState::PostCook: - { - // Handle PostCook - EHoudiniAssetState NewState = EHoudiniAssetState::None; - bool bSuccess = HAC->bLastCookSuccess; - HAC->OnPreOutputProcessing(); - if (PostCook(HAC, bSuccess, HAC->GetAssetId())) - { - // Cook was successful, process the results - NewState = EHoudiniAssetState::PreProcess; - } - else - { - // Cook failed, skip output processing - NewState = EHoudiniAssetState::None; - } - HAC->SetAssetState(NewState); - break; - } - - case EHoudiniAssetState::PreProcess: - { - StartTaskAssetProcess(HAC); - break; - } - - case EHoudiniAssetState::Processing: - { - UpdateProcess(HAC); - - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - HAC->SetAssetCookCount(CookCount); - - HAC->OnPostOutputProcessing(); - FHoudiniEngineUtils::UpdateBlueprintEditor(HAC); - break; - } - - case EHoudiniAssetState::None: - { - // Do nothing unless the HAC has been updated - if (HAC->NeedUpdate()) - { - HAC->bForceNeedUpdate = false; - // Update the HAC's state - HAC->SetAssetState(EHoudiniAssetState::PreCook); - } - else if (HAC->NeedTransformUpdate()) - { - FHoudiniEngineUtils::UploadHACTransform(HAC); - } - else if (HAC->NeedOutputUpdate()) - { - FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); - } - - // Update world inputs if we have any - FHoudiniInputTranslator::UpdateWorldInputs(HAC); - - // See if we need to get an update from Session Sync - if(FHoudiniEngine::Get().IsSessionSyncEnabled() - && FHoudiniEngine::Get().IsSyncWithHoudiniCookEnabled() - && HAC->GetAssetState() == EHoudiniAssetState::None) - { - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - if (CookCount >= 0 && CookCount != HAC->GetAssetCookCount()) - { - // The cook count has changed on the Houdini side, - // this indicates that the user has changed something in Houdini so we need to trigger an update - HAC->SetAssetState(EHoudiniAssetState::PreCook); - } - } - break; - } - - case EHoudiniAssetState::NeedRebuild: - { - StartTaskAssetRebuild(HAC->AssetId, HAC->HapiGUID); - - HAC->MarkAsNeedCook(); - HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); - break; - } - - case EHoudiniAssetState::NeedDelete: - { - FGuid HapiDeletionGUID; - StartTaskAssetDelete(HAC->GetAssetId(), HapiDeletionGUID, true); - //HAC->AssetId = -1; - - // Update the HAC's state - HAC->SetAssetState(EHoudiniAssetState::Deleting); - break; - } - - case EHoudiniAssetState::Deleting: - { - break; - } - } -} - - - -bool -FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID, FString& OutHAPIAssetName) -{ - // Make sure we have a valid session before attempting anything - if (!FHoudiniEngine::Get().GetSession()) - return false; - - OutTaskGUID.Invalidate(); - - // Load the HDA file - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - null or invalid Houdini Asset.")); - return false; - } - - HAPI_AssetLibraryId AssetLibraryId = -1; - if (!FHoudiniEngineUtils::LoadHoudiniAsset(HoudiniAsset, AssetLibraryId) ) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - could not load Houdini Asset.")); - return false; - } - - // Handle hda files that contain multiple assets - TArray< HAPI_StringHandle > AssetNames; - if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - unable to retrieve asset names.")); - return false; - } - - // By default, assume we want to load the first Asset - HAPI_StringHandle PickedAssetName = AssetNames[0]; - -#if WITH_EDITOR - // Should we show the multi asset dialog? - bool bShowMultiAssetDialog = false; - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && AssetNames.Num() > 1) - bShowMultiAssetDialog = HoudiniRuntimeSettings->bShowMultiAssetDialog; - - // TODO: Add multi selection dialog - if (bShowMultiAssetDialog ) - { - // TODO: Implement - FHoudiniEngineUtils::OpenSubassetSelectionWindow(AssetNames, PickedAssetName); - } -#endif - - // Give the HAC a new GUID to identify this request. - OutTaskGUID = FGuid::NewGuid(); - - // Create a new instantiation task - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetInstantiation, OutTaskGUID); - Task.Asset = HoudiniAsset; - Task.ActorName = DisplayName; - //Task.bLoadedComponent = bLocalLoadedComponent; - Task.AssetLibraryId = AssetLibraryId; - Task.AssetHapiName = PickedAssetName; - - FHoudiniEngineString(PickedAssetName).ToFString(OutHAPIAssetName); - - // Add the task to the stack - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState ) -{ - check(HAC); - - // Will return true if the asset's state need to be updated - NewState = HAC->GetAssetState(); - bool bUpdateState = false; - - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - // Get the current task's progress - FHoudiniEngineTaskInfo TaskInfo; - if ( !UpdateTaskStatus(HAC->HapiGUID, TaskInfo) - || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetInstantiation) - { - // Couldnt get a valid task info - HOUDINI_LOG_ERROR(TEXT(" %s Failed to instantiate - invalid task"), *DisplayName); - NewState = EHoudiniAssetState::NeedInstantiation; - bUpdateState = true; - return bUpdateState; - } - - bool bSuccess = false; - bool bFinished = false; - switch (TaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Success: - { - bSuccess = true; - bFinished = true; - break; - } - - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::FinishedWithError: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - bSuccess = false; - bFinished = true; - break; - } - - case EHoudiniEngineTaskState::None: - case EHoudiniEngineTaskState::Working: - { - bFinished = false; - break; - } - } - - if ( !bFinished ) - { - // Task is still in progress, nothing to do for now - return false; - } - - if ( bSuccess && (TaskInfo.AssetId < 0) ) - { - // Task finished successfully but we received an invalid asset ID, error out - HOUDINI_LOG_ERROR(TEXT(" %s Finished Instantiation but received invalid asset id."), *DisplayName); - bSuccess = false; - } - - if ( bSuccess ) - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedInstantiation."), *DisplayName); - - // Set the new Asset ID - HAC->AssetId = TaskInfo.AssetId; - - // Assign a unique name to the actor if needed - FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(HAC); - - // TODO: Create default preset buffer. - /*TArray< char > DefaultPresetBuffer; - if (!FHoudiniEngineUtils::GetAssetPreset(TaskInfo.AssetId, DefaultPresetBuffer)) - DefaultPresetBuffer.Empty();*/ - - // Reset the cook counter. - HAC->SetAssetCookCount(0); - - // If necessary, set asset transform. - if (HAC->bUploadTransformsToHoudiniEngine) - { - // Retrieve the current component-to-world transform for this component. - if (!FHoudiniEngineUtils::HapiSetAssetTransform(TaskInfo.AssetId, HAC->GetComponentTransform())) - HOUDINI_LOG_MESSAGE(TEXT("Failed to upload the initial Transform back to HAPI.")); - } - - // Only initalize the PDG Asset Link if this Asset is a PDG Asset - // InitializePDGAssetLink may take a while to execute on non PDG HDA, - // So we want to avoid calling it if possible - if (FHoudiniPDGManager::IsPDGAsset(HAC->AssetId)) - { - PDGManager.InitializePDGAssetLink(HAC); - } - - // Initial update/create of inputs - if (HAC->HasBeenLoaded()) - { - FHoudiniInputTranslator::UpdateLoadedInputs(HAC); - } - else - { - FHoudiniInputTranslator::UpdateInputs(HAC); - } - - // Update the HAC's state - NewState = EHoudiniAssetState::PreCook; - return true; - } - else - { - 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); - - FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); - - FMessageDialog::Debugf(FText::FromString(WarningMessage), &WarningTitleText); - } - - // Reset the cook counter. - HAC->SetAssetCookCount(0); - - // Make sure the asset ID is invalid - HAC->AssetId = -1; - - // Update the HAC's state - HAC->SetAssetState(EHoudiniAssetState::NeedInstantiation); - //HAC->AssetStateResult = EHoudiniAssetStateResult::Success; - - return true; - } -} - -bool -FHoudiniEngineManager::StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID) -{ - // Make sure we have a valid session before attempting anything - if (!FHoudiniEngine::Get().GetSession()) - return false; - - // Check we have a valid AssetId - if (AssetId < 0) - return false; - - // Check this HAC doesn't already have a running task - if (OutTaskGUID.IsValid()) - return false; - - // Generate a GUID for our new task. - OutTaskGUID = FGuid::NewGuid(); - - // Add a new cook task - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetCooking, OutTaskGUID); - Task.ActorName = DisplayName; - Task.AssetId = AssetId; - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState) -{ - check(HAC); - - // Will return true if the asset's state need to be updated - NewState = HAC->GetAssetState(); - bool bUpdateState = false; - - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - // Get the current task's progress - FHoudiniEngineTaskInfo TaskInfo; - if (!UpdateTaskStatus(HAC->HapiGUID, TaskInfo) - || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetCooking) - { - // Couldnt get a valid task info - HOUDINI_LOG_ERROR(TEXT(" %s Failed to cook - invalid task"), *DisplayName); - NewState = EHoudiniAssetState::None; - bUpdateState = true; - return bUpdateState; - } - - bool bSuccess = false; - switch (TaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Success: - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking."), *DisplayName); - bSuccess = true; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::FinishedWithError: - { - // We finished with cook error, will still try to process the results - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with errors - will try to process the available results."), *DisplayName); - bSuccess = true; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with fatal errors - aborting."), *DisplayName); - bSuccess = false; - bUpdateState = true; - } - break; - - case EHoudiniEngineTaskState::None: - case EHoudiniEngineTaskState::Working: - { - // Task is still in progress, nothing to do for now - // return false so we do not update the state - bUpdateState = false; - } - break; - } - - // If the task is still in progress, return now - if (!bUpdateState) - return false; - - // Handle PostCook - NewState = EHoudiniAssetState::PostCook; - HAC->bLastCookSuccess = bSuccess; - - //if (PostCook(HAC, bSuccess, TaskInfo.AssetId)) - //{ - // // Cook was successfull, process the results - // NewState = EHoudiniAssetState::PreProcess; - // HAC->BroadcastCookFinished(); - //} - //else - //{ - // // Cook failed, skip output processing - // NewState = EHoudiniAssetState::None; - //} - - return true; -} - -bool -FHoudiniEngineManager::PreCook(UHoudiniAssetComponent* HAC) -{ - // Handle duplicated HAC - // We need to clean/duplicate some of the HAC's output data manually here - if (HAC->HasBeenDuplicated()) - { - HAC->UpdatePostDuplicate(); - } - - FHoudiniParameterTranslator::OnPreCookParameters(HAC); - - if (HAC->HasBeenLoaded() || HAC->IsParameterDefinitionUpdateNeeded()) - { - // This will sync parameter definitions but not upload values to HAPI or fetch values for existing parameters - // in Unreal. It will creating missing parameters in Unreal. - FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); - HAC->bParameterDefinitionUpdateNeeded = false; - } - - // Upload the changed/parameters back to HAPI - // If cooking is disabled, we still try to upload parameters - if (HAC->HasBeenLoaded()) - { - // // Handle loaded parameters - // FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); - - // Handle loaded inputs - FHoudiniInputTranslator::UpdateLoadedInputs(HAC); - - // Handle loaded outputs - FHoudiniOutputTranslator::UpdateLoadedOutputs(HAC); - - // TODO: Handle loaded curve - // TODO: Handle editable node - // TODO: Restore parameter preset data - } - - // Try to upload changed parameters - FHoudiniParameterTranslator::UploadChangedParameters(HAC); - - // Try to upload changed inputs - FHoudiniInputTranslator::UploadChangedInputs(HAC); - - // Try to upload changed editable nodes - FHoudiniOutputTranslator::UploadChangedEditableOutput(HAC, false); - - // Upload the asset's transform if needed - if (HAC->NeedTransformUpdate()) - FHoudiniEngineUtils::UploadHACTransform(HAC); - - HAC->ClearRefineMeshesTimer(); - - return true; -} - -bool -FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId) -{ - // Get the HAC display name for the logs - FString DisplayName = HAC->GetDisplayName(); - - bool bCookSuccess = bSuccess; - if (bCookSuccess && (TaskAssetId < 0)) - { - // Task finished successfully but we received an invalid asset ID, error out - HOUDINI_LOG_ERROR(TEXT(" %s received an invalid asset id - aborting."), *DisplayName); - bCookSuccess = false; - } - - // Update the asset cook count using the node infos - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); - HAC->SetAssetCookCount(CookCount); - /* - if(CookCount >= 0 ) - HAC->SetAssetCookCount(CookCount); - else - HAC->SetAssetCookCount(HAC->GetAssetCookCount()+1); - */ - - bool bNeedsToTriggerViewportUpdate = false; - if (bCookSuccess) - { - FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Processing outputs..."), false); - - // Set new asset id. - HAC->AssetId = TaskAssetId; - - FHoudiniParameterTranslator::UpdateParameters(HAC); - - FHoudiniInputTranslator::UpdateInputs(HAC); - - bool bHasHoudiniStaticMeshOutput = false; - bool ForceUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); - FHoudiniOutputTranslator::UpdateOutputs(HAC, ForceUpdate, bHasHoudiniStaticMeshOutput); - HAC->SetNoProxyMeshNextCookRequested(false); - - // Handles have to be updated after parameters - FHoudiniHandleTranslator::UpdateHandles(HAC); - - // Clear the HasBeenLoaded flag - if (HAC->HasBeenLoaded()) - { - HAC->SetHasBeenLoaded(false); - } - - // Clear the HasBeenDuplicated flag - if (HAC->HasBeenDuplicated()) - { - HAC->SetHasBeenDuplicated(false); - } - - // Update rendering information. - HAC->UpdateRenderingInformation(); - - // Since we have new asset, we need to update bounds. - HAC->UpdateBounds(); - - FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Finished processing outputs"), true); - - // Trigger a details panel update - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - - // If any outputs have HoudiniStaticMeshes, and if timer based refinement is enabled on the HAC, - // set the RefineMeshesTimer and ensure BuildStaticMeshesForAllHoudiniStaticMeshes is bound to - // the RefineMeshesTimerFired delegate of the HAC - if (bHasHoudiniStaticMeshOutput && HAC->IsProxyStaticMeshRefinementByTimerEnabled()) - { - if (!HAC->GetOnRefineMeshesTimerDelegate().IsBoundToObject(this)) - HAC->GetOnRefineMeshesTimerDelegate().AddRaw(this, &FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes); - HAC->SetRefineMeshesTimer(); - } - - if (bHasHoudiniStaticMeshOutput) - bNeedsToTriggerViewportUpdate = true; - - UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); - if (OnPostCookBakeDelegate.IsBound()) - { - OnPostCookBakeDelegate.Execute(HAC); - if (!HAC->IsBakeAfterNextCookEnabled()) - OnPostCookBakeDelegate.Unbind(); - } - } - else - { - // TODO: Create parameters inputs and handles inputs. - //CreateParameters(); - //CreateInputs(); - //CreateHandles(); - - // Clear the bake after cook delegate if - UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); - if (OnPostCookBakeDelegate.IsBound() && !HAC->IsBakeAfterNextCookEnabled()) - { - OnPostCookBakeDelegate.Unbind(); - // Notify the user that the bake failed since the cook failed. - FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Cook failed, therefore the bake also failed..."), true); - } - } - - if (HAC->InputPresets.Num() > 0) - { - HAC->ApplyInputPresets(); - } - - // If we have downstream HDAs, we need to tell them we're done cooking - HAC->NotifyCookedToDownstreamAssets(); - - // Notify the PDG manager that the HDA is done cooking - FHoudiniPDGManager::NotifyAssetCooked(HAC->PDGAssetLink, bSuccess); - - if (bNeedsToTriggerViewportUpdate && GEditor) - { - // We need to manually update the vieport with HoudiniMeshProxies - // if not, modification made in H with the two way debugger wont be visible in Unreal until the vieports gets focus - GEditor->RedrawAllViewports(false); - } - - // Clear the rebuild/recook flags - HAC->SetRecookRequested(false); - HAC->SetRebuildRequested(false); - - //HAC->SyncToBlueprintGeneratedClass(); - - return bCookSuccess; -} - -bool -FHoudiniEngineManager::StartTaskAssetProcess(UHoudiniAssetComponent* HAC) -{ - HAC->SetAssetState(EHoudiniAssetState::Processing); - - return true; -} - -bool -FHoudiniEngineManager::UpdateProcess(UHoudiniAssetComponent* HAC) -{ - HAC->SetAssetState(EHoudiniAssetState::None); - - return true; -} - -bool -FHoudiniEngineManager::StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID) -{ - // Check this HAC doesn't already have a running task - if (OutTaskGUID.IsValid()) - return false; - - if (InAssetId >= 0) - { - /* TODO: Handle Asset Preset - if (!FHoudiniEngineUtils::GetAssetPreset(AssetId, PresetBuffer)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to get the asset's preset, rebuilt asset may have lost its parameters.")); - } - */ - // Delete the asset - if (!StartTaskAssetDelete(InAssetId, OutTaskGUID, true)) - { - return false; - } - } - - // Create a new task GUID for this asset - OutTaskGUID = FGuid::NewGuid(); - - return true; -} - -bool -FHoudiniEngineManager::StartTaskAssetDelete(const HAPI_NodeId& InNodeId, FGuid& OutTaskGUID, bool bShouldDeleteParent) -{ - if (InNodeId < 0) - return false; - - // Get the Asset's NodeInfo - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, &AssetNodeInfo)); - - HAPI_NodeId OBJNodeToDelete = InNodeId; - if (AssetNodeInfo.type == HAPI_NODETYPE_SOP) - { - // For SOP Asset, we want to delete their parent's OBJ node - if (bShouldDeleteParent) - { - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(OBJNodeToDelete); - OBJNodeToDelete = ParentId != -1 ? ParentId : OBJNodeToDelete; - } - } - - // Generate GUID for our new task. - OutTaskGUID = FGuid::NewGuid(); - - // Create asset deletion task object and submit it for processing. - FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetDeletion, OutTaskGUID); - Task.AssetId = OBJNodeToDelete; - FHoudiniEngine::Get().AddTask(Task); - - return true; -} - -bool -FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo) -{ - if (!OutTaskGUID.IsValid()) - return false; - - if (!FHoudiniEngine::Get().RetrieveTaskInfo(OutTaskGUID, OutTaskInfo)) - { - // Task information does not exist - OutTaskGUID.Invalidate(); - return false; - } - - // Check whether we want to display Slate cooking and instantiation notifications. - bool bDisplaySlateCookingNotifications = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (EHoudiniEngineTaskState::None != OutTaskInfo.TaskState && bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); - } - - switch (OutTaskInfo.TaskState) - { - case EHoudiniEngineTaskState::Aborted: - case EHoudiniEngineTaskState::Success: - case EHoudiniEngineTaskState::FinishedWithError: - case EHoudiniEngineTaskState::FinishedWithFatalError: - { - // If the current task is finished - // Terminate the slate notification if they exist and delete/invalidate the task - if (bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, true); - } - - FHoudiniEngine::Get().RemoveTaskInfo(OutTaskGUID); - OutTaskGUID.Invalidate(); - } - break; - - case EHoudiniEngineTaskState::Working: - { - // The current task is still running, simply update the current notification - if (bDisplaySlateCookingNotifications) - { - FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); - } - } - break; - - case EHoudiniEngineTaskState::None: - default: - { - break; - } - } - - return true; -} - -bool -FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC) -{ - bool bManualRecook = false; - bool bComponentEnable = false; - if (HAC && !HAC->IsPendingKill()) - { - bManualRecook = HAC->HasRecookBeenRequested(); - bComponentEnable = HAC->IsCookingEnabled(); - } - - if (bManualRecook) - return true; - - if (bComponentEnable && FHoudiniEngine::Get().IsCookingEnabled()) - return true; - - return false; -} - -void -FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes called with HAC=nullptr")); - return; - } - -#if WITH_EDITOR - AActor *Owner = HAC->GetOwner(); - FString Name = Owner ? Owner->GetName() : HAC->GetName(); - - FScopedSlowTask Progress(2.0f, FText::FromString(FString::Printf(TEXT("Refining Proxy Mesh to Static Mesh on %s"), *Name))); - Progress.MakeDialog(); - Progress.EnterProgressFrame(1.0f); -#endif - - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); - -#if WITH_EDITOR - Progress.EnterProgressFrame(1.0f); -#endif -} - - -/* Unreal's viewport representation rules: - Viewport location is the actual camera location; - Lookat position is always right in front of the camera, which means the camera is looking at; - The rotator rotates the forward vector to a direction & orientation, and this dir and orientation is the camera's; - The identity direction and orientation of the camera is facing positive X-axis. -*/ - -/* Hapi's viewport representation rules: - The camera is located at a point on the sphere, which the center is the pivot position and the radius is offset; - Quat determines the location on the sphere and which direction the camera is facing towards, as well as the camera orientation; - The identity location, direction and orientation of the camera is facing positive Z-axis (in Hapi coords); -*/ - - -bool -FHoudiniEngineManager::SyncHoudiniViewportToUnreal() -{ - if (!FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled()) - return false; - -#if WITH_EDITOR - // Get the editor viewport LookAt position to spawn the new objects - if (!GEditor || !GEditor->GetActiveViewport()) - return false; - - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (!ViewportClient) - return false; - - // Get the current UE viewport location, lookat position, and rotation - FVector UnrealViewportPosition = ViewportClient->GetViewLocation(); - FRotator UnrealViewportRotation = ViewportClient->GetViewRotation(); - FVector UnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - /* Check if the Unreal viewport has changed */ - if (UnrealViewportPosition.Equals(SyncedUnrealViewportPosition) && - UnrealViewportRotation.Equals(SyncedUnrealViewportRotation) && - UnrealViewportLookatPosition.Equals(SyncedUnrealViewportLookatPosition)) - { - // No need to sync if the viewport camera hasn't changed - return false; - } - - /* Calculate Hapi Quaternion */ - // Initialize Hapi Quat with Unreal Quat. - // Note that rotations are in general non-commutative *** - FQuat HapiQuat = UnrealViewportRotation.Quaternion(); - - // We're in orbit mode, forward vector is Y-axis - if (ViewportClient->bUsingOrbitCamera) - { - // The forward vector is Y-negative direction when on orbiting mode - HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 180.f)); - - // rotations around X and Y axis are reversed - float TempX = HapiQuat.X; - HapiQuat.X = HapiQuat.Y; - HapiQuat.Y = TempX; - HapiQuat.W = -HapiQuat.W; - - } - // We're not in orbiting mode, forward vector is X-axis - else - { - // Rotate the Quat arount Z-axis by 90 degree. - HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 90.f)); - } - - - /* Update Hapi H_View */ - // Note: There are infinte number of H_View representation for current viewport - // Each choice of pivot point determines an equivalent representation. - // We just find an equivalent when the pivot position is the view position, and offset is 0 - - HAPI_Viewport H_View; - H_View.position[0] = UnrealViewportPosition.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - H_View.position[1] = UnrealViewportPosition.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - H_View.position[2] = UnrealViewportPosition.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Set HAPI_Offset always 0 when syncing Houdini to UE viewport - H_View.offset = 0.f; - - H_View.rotationQuaternion[0] = -HapiQuat.X; - H_View.rotationQuaternion[1] = -HapiQuat.Z; - H_View.rotationQuaternion[2] = -HapiQuat.Y; - H_View.rotationQuaternion[3] = HapiQuat.W; - - FHoudiniApi::SetViewport(FHoudiniEngine::Get().GetSession(), &H_View); - - /* Update the Synced viewport values - We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. - Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. */ - - // We need to get the H_Viewport again, since it is possible the value is a different equivalence of what we set. - HAPI_Viewport Cur_H_View; - FHoudiniApi::GetViewport( - FHoudiniEngine::Get().GetSession(), &Cur_H_View); - - // Hapi values are in Houdini coordinate and scale - SyncedHoudiniViewportPivotPosition = FVector(Cur_H_View.position[0], Cur_H_View.position[1], Cur_H_View.position[2]); - SyncedHoudiniViewportQuat = FQuat(Cur_H_View.rotationQuaternion[0], Cur_H_View.rotationQuaternion[1], Cur_H_View.rotationQuaternion[2], Cur_H_View.rotationQuaternion[3]); - SyncedHoudiniViewportOffset = Cur_H_View.offset; - - SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); - SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); - SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - // When sync Houdini to UE, we set offset to be 0. - // So we need to zero out offset for the next time syncing UE to Houdini - bOffsetZeroed = true; - - return true; -#endif - - return false; -} - - -bool -FHoudiniEngineManager::SyncUnrealViewportToHoudini() -{ - if (!FHoudiniEngine::Get().IsSyncUnrealViewportEnabled()) - return false; - -#if WITH_EDITOR - // Get the editor viewport LookAt position to spawn the new objects - if (!GEditor || !GEditor->GetActiveViewport()) - return false; - - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (!ViewportClient) - return false; - - // Get the current HAPI_Viewport - HAPI_Viewport H_View; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetViewport( - FHoudiniEngine::Get().GetSession(), &H_View)) - { - return false; - } - - - // Get Hapi viewport's PivotPosition, Offset and Quat, w.r.t Houdini's coordinate and scale. - FVector HapiViewportPivotPosition = FVector(H_View.position[0], H_View.position[1], H_View.position[2]); - float HapiViewportOffset = H_View.offset; - FQuat HapiViewportQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[1], H_View.rotationQuaternion[2], H_View.rotationQuaternion[3]); - - /* Check if the Houdini viewport has changed */ - if (SyncedHoudiniViewportPivotPosition.Equals(HapiViewportPivotPosition) && - SyncedHoudiniViewportQuat.Equals(HapiViewportQuat) && - SyncedHoudiniViewportOffset == HapiViewportOffset) - { - // Houdini viewport hasn't changed, nothing to do - return false; - } - - // Set zero value of offset when needed - if (bOffsetZeroed) - { - ZeroOffsetValue = H_View.offset; - bOffsetZeroed = false; - } - - - /* Translate the hapi camera transfrom to Unreal's representation system */ - - // Get pivot point in UE's coordinate and scale - FVector UnrealViewportPivotPosition = FVector(H_View.position[0], H_View.position[2], H_View.position[1]) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // HAPI bug? After we set the H_View, offset becomes a lot bigger when move the viewport just a little bit in Houdini. - // But the pivot point doesn't change. Which caused UE viewport jumping far suddenly. - // So we get rid of this problem by setting the first HAPI_offset value after syncing Houdini viewport as the base. - - // Get offset in UE's scale. The actual offset after 'zero out' - float UnrealOffset = (H_View.offset - ZeroOffsetValue) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - /* Calculate Quaternion in UE */ - // Rotate the resulting Quat around Z-axis by -90 degree. - // Note that rotation is in general non-commutative *** - FQuat UnrealQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[2], H_View.rotationQuaternion[1], -H_View.rotationQuaternion[3]); - UnrealQuat = UnrealQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)); - - FVector UnrealBaseVector(1.f, 0.f, 0.f); // Forward vector in Unreal viewport - - /* Get UE viewport location*/ - FVector UnrealViewPosition = - UnrealQuat.RotateVector(UnrealBaseVector) * UnrealOffset + UnrealViewportPivotPosition; - - /* Set the viewport's value */ - ViewportClient->SetViewLocation(UnrealViewPosition); - ViewportClient->SetViewRotation(UnrealQuat.Rotator()); - - // Invalidate the viewport - ViewportClient->Invalidate(); - - /* Update the synced viewport values */ - // We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. - // Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. - - // Hapi values are in Houdini coordinate and scale - SyncedHoudiniViewportPivotPosition = HapiViewportPivotPosition; - SyncedHoudiniViewportQuat = HapiViewportQuat; - SyncedHoudiniViewportOffset = HapiViewportOffset; - - SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); - SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); - SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); - - return true; -#endif - - return false; -} - - -void -FHoudiniEngineManager::DisableEditorAutoSave(const UHoudiniAssetComponent* HAC) -{ -#if WITH_EDITOR - if (!HAC || HAC->IsPendingKill()) - return; - - if (!GUnrealEd) - return; - - if (DisableAutoSavingHACs.Contains(HAC)) - return; - // Add the HAC to the set - DisableAutoSavingHACs.Add(HAC); - - // Return if auto-saving has been disabled by some other HACs. - if (DisableAutoSavingHACs.Num() > 1) - return; - - // Disable auto-saving by setting min time till auto-save to max float value - IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); - AutoSaver.ForceMinimumTimeTillAutoSave(TNumericLimits::Max()); -#endif -} - - -void -FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = nullptr) -{ -#if WITH_EDITOR - if (!GUnrealEd) - return; - - if (!HAC) - { - // When HAC is nullptr, go through all HACs in the set, - // remove it if the HAC has been deleted. - if (DisableAutoSavingHACs.Num() <= 0) - return; - - TSet ValidComponents; - for (auto& CurHAC : DisableAutoSavingHACs) - { - if (CurHAC && !CurHAC->IsPendingKill()) - { - ValidComponents.Add(CurHAC); - } - } - DisableAutoSavingHACs = MoveTemp(ValidComponents); - } - else - { - // Otherwise, remove the HAC from the set - if (DisableAutoSavingHACs.Contains(HAC)) - DisableAutoSavingHACs.Remove(HAC); - } - - if (DisableAutoSavingHACs.Num() > 0) - return; - - // When no HAC disables cooking, reset min time till auto-save to default value, then reset the timer - IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); - AutoSaver.ResetAutoSaveTimer(); -#endif -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineManager.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniParameterTranslator.h" +#include "HoudiniPDGManager.h" +#include "HoudiniInputTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniHandleTranslator.h" +#include "HoudiniSplineTranslator.h" + +#include "Misc/MessageDialog.h" +#include "Misc/ScopedSlowTask.h" +#include "Containers/Ticker.h" +#include "HAL/IConsoleManager.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "EditorViewportClient.h" + #include "Kismet/KismetMathLibrary.h" + + //#include "UnrealEd.h" + #include "UnrealEdGlobals.h" + #include "Editor/UnrealEdEngine.h" + #include "IPackageAutoSaver.h" +#endif + +static TAutoConsoleVariable CVarHoudiniEngineTickTimeLimit( + TEXT("HoudiniEngine.TickTimeLimit"), + 1.0, + TEXT("Time limit after which HDA processing will be stopped, until the next tick of the Houdini Engine Manager.\n") + TEXT("<= 0.0: No Limit\n") + TEXT("1.0: Default\n") +); + +FHoudiniEngineManager::FHoudiniEngineManager() + : CurrentIndex(0) + , ComponentCount(0) + , bMustStopTicking(false) + , SyncedHoudiniViewportPivotPosition(FVector::ZeroVector) + , SyncedHoudiniViewportQuat(FQuat::Identity) + , SyncedHoudiniViewportOffset(0.0f) + , SyncedUnrealViewportPosition(FVector::ZeroVector) + , SyncedUnrealViewportRotation(FRotator::ZeroRotator) + , SyncedUnrealViewportLookatPosition(FVector::ZeroVector) + , ZeroOffsetValue(0.f) + , bOffsetZeroed(false) +{ + +} + +FHoudiniEngineManager::~FHoudiniEngineManager() +{ + PDGManager.StopBGEOCommandletAndEndpoint(); +} + +void +FHoudiniEngineManager::StartHoudiniTicking() +{ + // If we have no timer delegate spawned, spawn one. + if (!TickerHandle.IsValid() && GEditor) + { + // We use the ticker manager so we get ticked once per frame, no more. + TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FHoudiniEngineManager::Tick)); + + // Grab current time for delayed notification. + FHoudiniEngine::Get().SetHapiNotificationStartedTime(FPlatformTime::Seconds()); + } +} + +void +FHoudiniEngineManager::StopHoudiniTicking() +{ + if (TickerHandle.IsValid() && GEditor) + { + if (IsInGameThread()) + { + FTicker::GetCoreTicker().RemoveTicker(TickerHandle); + TickerHandle.Reset(); + + // Reset time for delayed notification. + FHoudiniEngine::Get().SetHapiNotificationStartedTime(0.0); + + bMustStopTicking = false; + } + else + { + // We can't stop ticking now as we're not in the game Thread, + // and accessing the timer would crash, indicate that we want to stop ticking asap + // This can happen when loosing a session due to a Houdini crash + bMustStopTicking = true; + } + } +} + +bool +FHoudiniEngineManager::Tick(float DeltaTime) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::Tick); + + EnableEditorAutoSave(nullptr); + + FHoudiniEngine::Get().TickPersistentNotification(DeltaTime); + + if (bMustStopTicking) + { + // Ticking should be stopped immediately + StopHoudiniTicking(); + return true; + } + + // Build a set of components that need to be processed + // 1 - selected HACs + // 2 - "Active" HACs + // 3 - The "next" inactive HAC + TArray ComponentsToProcess; + if (FHoudiniEngineRuntime::IsInitialized()) + { + FHoudiniEngineRuntime::Get().CleanUpRegisteredHoudiniComponents(); + + //FScopeLock ScopeLock(&CriticalSection); + ComponentCount = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount(); + + // Wrap around if needed + if (CurrentIndex >= ComponentCount) + CurrentIndex = 0; + + for (uint32 nIdx = 0; nIdx < ComponentCount; nIdx++) + { + UHoudiniAssetComponent * CurrentComponent = FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentAt(nIdx); + if (!CurrentComponent || !CurrentComponent->IsValidLowLevelFast()) + { + // Invalid component, do not process + continue; + } + else if (CurrentComponent->IsPendingKill() + || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) + { + // Component being deleted, do not process + continue; + } + + if (!CurrentComponent->IsFullyLoaded()) + { + // Let the component figure out whether it's fully loaded or not. + CurrentComponent->HoudiniEngineTick(); + if (!CurrentComponent->IsFullyLoaded()) + continue; // We need to wait some more. + } + + if (!CurrentComponent->IsValidComponent()) + { + // This component is no longer valid. Prevent it from being processed, and remove it. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); + continue; + } + + AActor* Owner = CurrentComponent->GetOwner(); + if (Owner && Owner->IsSelectedInEditor()) + { + // 1. Add selected HACs + // If the component's owner is selected, add it to the set + ComponentsToProcess.Add(CurrentComponent); + } + else if (CurrentComponent->GetAssetState() != EHoudiniAssetState::NeedInstantiation + && CurrentComponent->GetAssetState() != EHoudiniAssetState::None) + { + // 2. Add "Active" HACs, the only two non-active states are: + // NeedInstantiation (loaded, not instantiated in H yet, not modified) + // None (no processing currently) + ComponentsToProcess.Add(CurrentComponent); + } + else if(nIdx == CurrentIndex) + { + // 3. Add the "Current" HAC + ComponentsToProcess.Add(CurrentComponent); + } + + // Set the LastTickTime on the "current" HAC to 0 to ensure it's treated first + if (nIdx == CurrentIndex) + { + CurrentComponent->LastTickTime = 0.0; + } + } + + // Increment the current index for the next tick + CurrentIndex++; + } + + // Sort the components by last tick time + ComponentsToProcess.Sort([](const UHoudiniAssetComponent& A, const UHoudiniAssetComponent& B) { return A.LastTickTime < B.LastTickTime; }); + + // Time limit for processing + double dProcessTimeLimit = CVarHoudiniEngineTickTimeLimit.GetValueOnAnyThread(); + double dProcessStartTime = FPlatformTime::Seconds(); + + // Process all the components in the list + for(UHoudiniAssetComponent* CurrentComponent : ComponentsToProcess) + { + // Tick the notification manager + //FHoudiniEngine::Get().TickPersistentNotification(0.0f); + + double dNow = FPlatformTime::Seconds(); + if (dProcessTimeLimit > 0.0 + && dNow - dProcessStartTime > dProcessTimeLimit) + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); + break; + } + + // Update the tick time for this component + CurrentComponent->LastTickTime = dNow; + + // Handle template processing (for BP) first + // We don't want to the template component processing to trigger session creation + if (CurrentComponent->GetAssetState() == EHoudiniAssetState::ProcessTemplate) + { + if (CurrentComponent->IsTemplate() && !CurrentComponent->HasOpenEditor()) + { + // This component template no longer has an open editor and can be deregistered. + // TODO: Replace this polling mechanism with an "On Asset Closed" event if we + // can find one that actually works. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(CurrentComponent); + continue; + } + + if (CurrentComponent->NeedBlueprintStructureUpdate()) + { + CurrentComponent->OnBlueprintStructureModified(); + } + + if (CurrentComponent->NeedBlueprintUpdate()) + { + CurrentComponent->OnBlueprintModified(); + } + + if (FHoudiniEngine::Get().IsCookingEnabled()) + { + // Only process component template parameter updates when cooking is enabled. + if (CurrentComponent->NeedUpdateParameters() || CurrentComponent->NeedUpdateInputs()) + { + CurrentComponent->OnTemplateParametersChanged(); + } + } + + if (CurrentComponent->NeedOutputUpdate()) + { + // TODO: Transfer template output changes over to the preview instance. + } + continue; + } + + // Process the component + bool bKeepProcessing = true; + while (bKeepProcessing) + { + // Tick the notification manager + FHoudiniEngine::Get().TickPersistentNotification(0.0f); + + // See if we should start the default "first" session + AutoStartFirstSessionIfNeeded(CurrentComponent); + + EHoudiniAssetState PrevState = CurrentComponent->GetAssetState(); + ProcessComponent(CurrentComponent); + EHoudiniAssetState NewState = CurrentComponent->GetAssetState(); + + // In order to process components faster / with less ticks, + // we may continue processing the component if it ends up in certain states + switch (NewState) + { + case EHoudiniAssetState::NewHDA: + case EHoudiniAssetState::PreInstantiation: + case EHoudiniAssetState::PreCook: + case EHoudiniAssetState::PostCook: + case EHoudiniAssetState::PreProcess: + case EHoudiniAssetState::Processing: + bKeepProcessing = true; + break; + + case EHoudiniAssetState::NeedInstantiation: + case EHoudiniAssetState::Instantiating: + case EHoudiniAssetState::Cooking: + case EHoudiniAssetState::None: + case EHoudiniAssetState::ProcessTemplate: + case EHoudiniAssetState::NeedRebuild: + case EHoudiniAssetState::NeedDelete: + case EHoudiniAssetState::Deleting: + bKeepProcessing = false; + break; + } + + // Safeguard, useless? + // Stop processing if the state hasn't changed + if (PrevState == NewState) + bKeepProcessing = false; + + dNow = FPlatformTime::Seconds(); + if (dProcessTimeLimit > 0.0 && dNow - dProcessStartTime > dProcessTimeLimit) + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Manager: Stopped processing after %F seconds."), (dNow - dProcessStartTime)); + break; + } + + // Update the tick time for this component + CurrentComponent->LastTickTime = dNow; + } + } + + // Handle Asset delete + if (FHoudiniEngineRuntime::IsInitialized()) + { + int32 PendingDeleteCount = FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteCount(); + for (int32 DeleteIdx = PendingDeleteCount - 1; DeleteIdx >= 0; DeleteIdx--) + { + HAPI_NodeId NodeIdToDelete = (HAPI_NodeId)FHoudiniEngineRuntime::Get().GetNodeIdsPendingDeleteAt(DeleteIdx); + FGuid HapiDeletionGUID; + bool bShouldDeleteParent = FHoudiniEngineRuntime::Get().IsParentNodePendingDelete(NodeIdToDelete); + if (StartTaskAssetDelete(NodeIdToDelete, HapiDeletionGUID, bShouldDeleteParent)) + { + FHoudiniEngineRuntime::Get().RemoveNodeIdPendingDeleteAt(DeleteIdx); + if (bShouldDeleteParent) + FHoudiniEngineRuntime::Get().RemoveParentNodePendingDelete(NodeIdToDelete); + } + } + } + + // Update PDG Contexts and asset link if needed + PDGManager.Update(); + + // Session Sync Updates + if (FHoudiniEngine::Get().IsSessionSyncEnabled()) + { + // See if the session sync settings have changed on the houdini side, update ours if they did + FHoudiniEngine::Get().UpdateSessionSyncInfoFromHoudini(); +#if WITH_EDITOR + // Update the Houdini viewport from unreal if needed + if (FHoudiniEngine::Get().IsSyncViewportEnabled()) + { + // Sync the Houdini viewport to Unreal + if (!SyncHoudiniViewportToUnreal()) + { + // If the unreal viewport hasnt changed, + // See if we need to sync the Unreal viewport from Houdini's + SyncUnrealViewportToHoudini(); + } + } +#endif + } + else + { + // reset zero offset variables when session sync is off + if (ZeroOffsetValue != 0.f) + ZeroOffsetValue = 0.f; + + if (bOffsetZeroed) + bOffsetZeroed = false; + } + + // Tick the notification manager + FHoudiniEngine::Get().TickPersistentNotification(0.0f); + + return true; +} + +void +FHoudiniEngineManager::AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC) +{ + // See if we should start the default "first" session + if (FHoudiniEngine::Get().GetSession() + || FHoudiniEngine::Get().GetFirstSessionCreated() + || !InCurrentHAC) + return; + + // Only try to start the default session if we have an "active" HAC + const EHoudiniAssetState CurrentState = InCurrentHAC->GetAssetState(); + if (CurrentState == EHoudiniAssetState::NewHDA + || CurrentState == EHoudiniAssetState::PreInstantiation + || CurrentState == EHoudiniAssetState::Instantiating + || CurrentState == EHoudiniAssetState::PreCook + || CurrentState == EHoudiniAssetState::Cooking) + { + FString StatusText = TEXT("Initializing Houdini Engine..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(StatusText), true, 4.0f); + + // We want to yield for a bit. + //FPlatformProcess::Sleep(0.5f); + + // Indicates that we've tried to start the session once no matter if it failed or succeed + FHoudiniEngine::Get().SetFirstSessionCreated(true); + + // Attempt to restart the session + if (!FHoudiniEngine::Get().RestartSession()) + { + // We failed to start the session + // Stop ticking until it's manually restarted + StopHoudiniTicking(); + + StatusText = TEXT("Houdini Engine failed to initialize."); + } + else + { + StatusText = TEXT("Houdini Engine successfully initialized."); + } + + // Finish the notification and display the results + FHoudiniEngine::Get().FinishTaskSlateNotification(FText::FromString(StatusText)); + } +} + +void +FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::ProcessComponent); + + if (!HAC || HAC->IsPendingKill()) + return; + + // No need to process component not tied to an asset + if (!HAC->GetHoudiniAsset()) + return; + + const EHoudiniAssetState AssetStateToProcess = HAC->GetAssetState(); + + // If cooking is paused, stay in the current state until cooking's resumed, unless we are in NewHDA + if (!FHoudiniEngine::Get().IsCookingEnabled() && AssetStateToProcess != EHoudiniAssetState::NewHDA) + { + // We can only handle output updates + if (HAC->GetAssetState() == EHoudiniAssetState::None && HAC->NeedOutputUpdate()) + { + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Refresh UI when pause cooking + if (!FHoudiniEngine::Get().HasUIFinishRefreshingWhenPausingCooking()) + { + // Trigger a details panel update if the Houdini asset actor is selected + if (HAC->IsOwnerSelected()) + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // Finished refreshing UI of one HDA. + FHoudiniEngine::Get().RefreshUIDisplayedWhenPauseCooking(); + } + + // Prevent any other state change to happen + return; + } + + switch (AssetStateToProcess) + { + case EHoudiniAssetState::NeedInstantiation: + { + // Do nothing unless the HAC has been updated + if (HAC->NeedUpdate()) + { + HAC->OnPrePreInstantiation(); + HAC->bForceNeedUpdate = false; + // Update the HAC's state + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + } + else if (HAC->NeedOutputUpdate()) + { + // Output updates do not recquire the HDA to be instantiated + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Update world input if we have any + FHoudiniInputTranslator::UpdateWorldInputs(HAC); + + break; + } + + case EHoudiniAssetState::NewHDA: + { + // Update parameters. Since there is no instantiated node yet, this will only fetch the defaults from + // the asset definition. + FHoudiniParameterTranslator::UpdateParameters(HAC); + // Since the HAC only has the asset definition's default parameter interface, without any asset or node ids, + // we mark it has requiring a parameter definition sync. This will be carried out pre-cook. + HAC->bParameterDefinitionUpdateNeeded = true; + + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + break; + } + + case EHoudiniAssetState::PreInstantiation: + { + // Only proceed forward if we don't need to wait for our input HoudiniAssets to finish cooking/instantiating + if (HAC->NeedsToWaitForInputHoudiniAssets()) + break; + + FGuid TaskGuid; + FString HapiAssetName; + UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); + if (StartTaskAssetInstantiation(HoudiniAsset, HAC->GetDisplayName(), TaskGuid, HapiAssetName)) + { + // Update the HAC's state + HAC->SetAssetState(EHoudiniAssetState::Instantiating); + + // Update the Task GUID + HAC->HapiGUID = TaskGuid; + + // Update the HapiAssetName + HAC->HapiAssetName = HapiAssetName; + } + else + { + // If we couldnt instantiate the asset + // Change the state back to NeedInstantiating + HAC->SetAssetState(EHoudiniAssetState::NeedInstantiation); + } + break; + } + + case EHoudiniAssetState::Instantiating: + { + EHoudiniAssetState NewState = EHoudiniAssetState::Instantiating; + if (UpdateInstantiating(HAC, NewState)) + { + // We need to update the HAC's state + HAC->SetAssetState(NewState); + EnableEditorAutoSave(HAC); + } + else + { + DisableEditorAutoSave(HAC); + } + break; + } + + case EHoudiniAssetState::PreCook: + { + // Only proceed forward if we don't need to wait for our input + // HoudiniAssets to finish cooking/instantiating + if (HAC->NeedsToWaitForInputHoudiniAssets()) + break; + + HAC->OnPrePreCook(); + // Update all the HAPI nodes, parameters, inputs etc... + PreCook(HAC); + HAC->OnPostPreCook(); + + // Create a Cooking task only if necessary + bool bCookStarted = false; + if (IsCookingEnabledForHoudiniAsset(HAC)) + { + FGuid TaskGUID = HAC->GetHapiGUID(); + if ( StartTaskAssetCooking(HAC->GetAssetId(), HAC->NodeIdsToCook, HAC->GetDisplayName(), TaskGUID) ) + { + // Updates the HAC's state + HAC->SetAssetState(EHoudiniAssetState::Cooking); + HAC->HapiGUID = TaskGUID; + bCookStarted = true; + } + } + + if(!bCookStarted) + { + // Just refresh editor properties? + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // TODO: Check! update state? + HAC->SetAssetState(EHoudiniAssetState::None); + } + break; + } + + case EHoudiniAssetState::Cooking: + { + EHoudiniAssetState NewState = EHoudiniAssetState::Cooking; + bool state = UpdateCooking(HAC, NewState); + if (state) + { + // We need to update the HAC's state + HAC->SetAssetState(NewState); + EnableEditorAutoSave(HAC); + } + else + { + DisableEditorAutoSave(HAC); + } + break; + } + + case EHoudiniAssetState::PostCook: + { + // Handle PostCook + EHoudiniAssetState NewState = EHoudiniAssetState::None; + bool bSuccess = HAC->bLastCookSuccess; + HAC->OnPreOutputProcessing(); + if (PostCook(HAC, bSuccess, HAC->GetAssetId())) + { + // Cook was successful, process the results + NewState = EHoudiniAssetState::PreProcess; + } + else + { + // Cook failed, skip output processing + NewState = EHoudiniAssetState::None; + } + HAC->SetAssetState(NewState); + break; + } + + case EHoudiniAssetState::PreProcess: + { + StartTaskAssetProcess(HAC); + break; + } + + case EHoudiniAssetState::Processing: + { + UpdateProcess(HAC); + + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + HAC->SetAssetCookCount(CookCount); + + HAC->OnPostOutputProcessing(); + FHoudiniEngineUtils::UpdateBlueprintEditor(HAC); + break; + } + + case EHoudiniAssetState::None: + { + // Do nothing unless the HAC has been updated + if (HAC->NeedUpdate()) + { + HAC->bForceNeedUpdate = false; + // Update the HAC's state + HAC->SetAssetState(EHoudiniAssetState::PreCook); + } + else if (HAC->NeedTransformUpdate()) + { + FHoudiniEngineUtils::UploadHACTransform(HAC); + } + else if (HAC->NeedOutputUpdate()) + { + FHoudiniOutputTranslator::UpdateChangedOutputs(HAC); + } + + // Update world inputs if we have any + FHoudiniInputTranslator::UpdateWorldInputs(HAC); + + // See if we need to get an update from Session Sync + if(FHoudiniEngine::Get().IsSessionSyncEnabled() + && FHoudiniEngine::Get().IsSyncWithHoudiniCookEnabled() + && HAC->GetAssetState() == EHoudiniAssetState::None) + { + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + if (CookCount >= 0 && CookCount != HAC->GetAssetCookCount()) + { + // The cook count has changed on the Houdini side, + // this indicates that the user has changed something in Houdini so we need to trigger an update + HAC->SetAssetState(EHoudiniAssetState::PreCook); + } + } + break; + } + + case EHoudiniAssetState::NeedRebuild: + { + StartTaskAssetRebuild(HAC->AssetId, HAC->HapiGUID); + + HAC->MarkAsNeedCook(); + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + break; + } + + case EHoudiniAssetState::NeedDelete: + { + FGuid HapiDeletionGUID; + StartTaskAssetDelete(HAC->GetAssetId(), HapiDeletionGUID, true); + //HAC->AssetId = -1; + + // Update the HAC's state + HAC->SetAssetState(EHoudiniAssetState::Deleting); + break; + } + + case EHoudiniAssetState::Deleting: + { + break; + } + } +} + + + +bool +FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID, FString& OutHAPIAssetName) +{ + // Make sure we have a valid session before attempting anything + if (!FHoudiniEngine::Get().GetSession()) + return false; + + OutTaskGUID.Invalidate(); + + // Load the HDA file + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - null or invalid Houdini Asset.")); + return false; + } + + HAPI_AssetLibraryId AssetLibraryId = -1; + if (!FHoudiniEngineUtils::LoadHoudiniAsset(HoudiniAsset, AssetLibraryId) ) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - could not load Houdini Asset.")); + return false; + } + + // Handle hda files that contain multiple assets + TArray< HAPI_StringHandle > AssetNames; + if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - unable to retrieve asset names.")); + return false; + } + + // By default, assume we want to load the first Asset + HAPI_StringHandle PickedAssetName = AssetNames[0]; + +#if WITH_EDITOR + // Should we show the multi asset dialog? + bool bShowMultiAssetDialog = false; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && AssetNames.Num() > 1) + bShowMultiAssetDialog = HoudiniRuntimeSettings->bShowMultiAssetDialog; + + // TODO: Add multi selection dialog + if (bShowMultiAssetDialog ) + { + // TODO: Implement + FHoudiniEngineUtils::OpenSubassetSelectionWindow(AssetNames, PickedAssetName); + } +#endif + + // Give the HAC a new GUID to identify this request. + OutTaskGUID = FGuid::NewGuid(); + + // Create a new instantiation task + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetInstantiation, OutTaskGUID); + Task.Asset = HoudiniAsset; + Task.ActorName = DisplayName; + //Task.bLoadedComponent = bLocalLoadedComponent; + Task.AssetLibraryId = AssetLibraryId; + Task.AssetHapiName = PickedAssetName; + + FHoudiniEngineString(PickedAssetName).ToFString(OutHAPIAssetName); + + // Add the task to the stack + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState ) +{ + check(HAC); + + // Will return true if the asset's state need to be updated + NewState = HAC->GetAssetState(); + bool bUpdateState = false; + + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + // Get the current task's progress + FHoudiniEngineTaskInfo TaskInfo; + if (!UpdateTaskStatus(HAC->HapiGUID, TaskInfo) + || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetInstantiation) + { + // Couldnt get a valid task info + HOUDINI_LOG_ERROR(TEXT(" %s Failed to instantiate - invalid task"), *DisplayName); + NewState = EHoudiniAssetState::NeedInstantiation; + bUpdateState = true; + return bUpdateState; + } + + bool bSuccess = false; + bool bFinished = false; + switch (TaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Success: + { + bSuccess = true; + bFinished = true; + break; + } + + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::FinishedWithError: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + bSuccess = false; + bFinished = true; + break; + } + + case EHoudiniEngineTaskState::None: + case EHoudiniEngineTaskState::Working: + { + bFinished = false; + break; + } + } + + if (!bFinished) + { + // Task is still in progress, nothing to do for now + return false; + } + + if (bSuccess && (TaskInfo.AssetId < 0)) + { + // Task finished successfully but we received an invalid asset ID, error out + HOUDINI_LOG_ERROR(TEXT(" %s Finished Instantiation but received invalid asset id."), *DisplayName); + bSuccess = false; + } + + if (bSuccess) + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedInstantiation."), *DisplayName); + + // Set the new Asset ID + HAC->AssetId = TaskInfo.AssetId; + + // Assign a unique name to the actor if needed + FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(HAC); + + // TODO: Create default preset buffer. + /*TArray< char > DefaultPresetBuffer; + if (!FHoudiniEngineUtils::GetAssetPreset(TaskInfo.AssetId, DefaultPresetBuffer)) + DefaultPresetBuffer.Empty();*/ + + // Reset the cook counter. + HAC->SetAssetCookCount(0); + + // If necessary, set asset transform. + if (HAC->bUploadTransformsToHoudiniEngine) + { + // Retrieve the current component-to-world transform for this component. + if (!FHoudiniEngineUtils::HapiSetAssetTransform(TaskInfo.AssetId, HAC->GetComponentTransform())) + HOUDINI_LOG_MESSAGE(TEXT("Failed to upload the initial Transform back to HAPI.")); + } + + // Only initalize the PDG Asset Link if this Asset is a PDG Asset + // InitializePDGAssetLink may take a while to execute on non PDG HDA, + // So we want to avoid calling it if possible + if (FHoudiniPDGManager::IsPDGAsset(HAC->AssetId)) + { + PDGManager.InitializePDGAssetLink(HAC); + } + + // Initial update/create of inputs + if (HAC->HasBeenLoaded()) + { + FHoudiniInputTranslator::UpdateLoadedInputs(HAC); + } + else + { + FHoudiniInputTranslator::UpdateInputs(HAC); + } + + // Update the HAC's state + NewState = EHoudiniAssetState::PreCook; + return true; + } + else + { + HOUDINI_LOG_ERROR(TEXT(" %s FinishedInstantiationWithErrors."), *DisplayName); + + bool bLicensingIssue = false; + switch (TaskInfo.Result) + { + case HAPI_RESULT_NO_LICENSE_FOUND: + case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: + { + // No license / Apprentice license found + //FHoudiniEngine::Get().SetHapiState(HAPI_RESULT_NO_LICENSE_FOUND); + FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); + bLicensingIssue = true; + break; + } + + 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); + } + + // Reset the cook counter. + HAC->SetAssetCookCount(0); + + // Make sure the asset ID is invalid + HAC->AssetId = -1; + + // Prevent the HAC from triggering updates in its current state + HAC->PreventAutoUpdates(); + + // Update the HAC's state + HAC->SetAssetState(EHoudiniAssetState::NeedInstantiation); + //HAC->AssetStateResult = EHoudiniAssetStateResult::Success; + + return true; + } +} + +bool +FHoudiniEngineManager::StartTaskAssetCooking( + const HAPI_NodeId& AssetId, + const TArray& NodeIdsToCook, + const FString& DisplayName, + FGuid& OutTaskGUID) +{ + // Make sure we have a valid session before attempting anything + if (!FHoudiniEngine::Get().GetSession()) + return false; + + // Check we have a valid AssetId + if (AssetId < 0) + return false; + + // Check this HAC doesn't already have a running task + if (OutTaskGUID.IsValid()) + return false; + + // Generate a GUID for our new task. + OutTaskGUID = FGuid::NewGuid(); + + // Add a new cook task + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetCooking, OutTaskGUID); + Task.ActorName = DisplayName; + Task.AssetId = AssetId; + + if (NodeIdsToCook.Num() > 0) + Task.OtherNodeIds = NodeIdsToCook; + + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState) +{ + check(HAC); + + // Will return true if the asset's state need to be updated + NewState = HAC->GetAssetState(); + bool bUpdateState = false; + + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + // Get the current task's progress + FHoudiniEngineTaskInfo TaskInfo; + if (!UpdateTaskStatus(HAC->HapiGUID, TaskInfo) + || TaskInfo.TaskType != EHoudiniEngineTaskType::AssetCooking) + { + // Couldnt get a valid task info + HOUDINI_LOG_ERROR(TEXT(" %s Failed to cook - invalid task"), *DisplayName); + NewState = EHoudiniAssetState::None; + bUpdateState = true; + return bUpdateState; + } + + bool bSuccess = false; + switch (TaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Success: + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking."), *DisplayName); + bSuccess = true; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::FinishedWithError: + { + // We finished with cook error, will still try to process the results + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with errors - will try to process the available results."), *DisplayName); + bSuccess = true; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + HOUDINI_LOG_MESSAGE(TEXT(" %s FinishedCooking with fatal errors - aborting."), *DisplayName); + bSuccess = false; + bUpdateState = true; + } + break; + + case EHoudiniEngineTaskState::None: + case EHoudiniEngineTaskState::Working: + { + // Task is still in progress, nothing to do for now + // return false so we do not update the state + bUpdateState = false; + } + break; + } + + // If the task is still in progress, return now + if (!bUpdateState) + return false; + + // Handle PostCook + NewState = EHoudiniAssetState::PostCook; + HAC->bLastCookSuccess = bSuccess; + + //if (PostCook(HAC, bSuccess, TaskInfo.AssetId)) + //{ + // // Cook was successfull, process the results + // NewState = EHoudiniAssetState::PreProcess; + // HAC->BroadcastCookFinished(); + //} + //else + //{ + // // Cook failed, skip output processing + // NewState = EHoudiniAssetState::None; + //} + + return true; +} + +bool +FHoudiniEngineManager::PreCook(UHoudiniAssetComponent* HAC) +{ + // Handle duplicated HAC + // We need to clean/duplicate some of the HAC's output data manually here + if (HAC->HasBeenDuplicated()) + { + HAC->UpdatePostDuplicate(); + } + + FHoudiniParameterTranslator::OnPreCookParameters(HAC); + + if (HAC->HasBeenLoaded() || HAC->IsParameterDefinitionUpdateNeeded()) + { + // This will sync parameter definitions but not upload values to HAPI or fetch values for existing parameters + // in Unreal. It will creating missing parameters in Unreal. + FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); + HAC->bParameterDefinitionUpdateNeeded = false; + } + + // Upload the changed/parameters back to HAPI + // If cooking is disabled, we still try to upload parameters + if (HAC->HasBeenLoaded()) + { + // // Handle loaded parameters + // FHoudiniParameterTranslator::UpdateLoadedParameters(HAC); + + // Handle loaded inputs + FHoudiniInputTranslator::UpdateLoadedInputs(HAC); + + // Handle loaded outputs + FHoudiniOutputTranslator::UpdateLoadedOutputs(HAC); + + // TODO: Handle loaded curve + // TODO: Handle editable node + // TODO: Restore parameter preset data + } + + // Try to upload changed parameters + FHoudiniParameterTranslator::UploadChangedParameters(HAC); + + // Try to upload changed inputs + FHoudiniInputTranslator::UploadChangedInputs(HAC); + + // Try to upload changed editable nodes + FHoudiniOutputTranslator::UploadChangedEditableOutput(HAC, false); + + // Upload the asset's transform if needed + if (HAC->NeedTransformUpdate()) + FHoudiniEngineUtils::UploadHACTransform(HAC); + + HAC->ClearRefineMeshesTimer(); + + return true; +} + +bool +FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId) +{ + // Get the HAC display name for the logs + FString DisplayName = HAC->GetDisplayName(); + + bool bCookSuccess = bSuccess; + if (bCookSuccess && (TaskAssetId < 0)) + { + // Task finished successfully but we received an invalid asset ID, error out + HOUDINI_LOG_ERROR(TEXT(" %s received an invalid asset id - aborting."), *DisplayName); + bCookSuccess = false; + } + + // Update the asset cook count using the node infos + int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + HAC->SetAssetCookCount(CookCount); + /* + if(CookCount >= 0 ) + HAC->SetAssetCookCount(CookCount); + else + HAC->SetAssetCookCount(HAC->GetAssetCookCount()+1); + */ + + bool bNeedsToTriggerViewportUpdate = false; + if (bCookSuccess) + { + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Processing outputs..."), false); + + // Set new asset id. + HAC->AssetId = TaskAssetId; + + FHoudiniParameterTranslator::UpdateParameters(HAC); + + FHoudiniInputTranslator::UpdateInputs(HAC); + + bool bHasHoudiniStaticMeshOutput = false; + bool ForceUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested(); + FHoudiniOutputTranslator::UpdateOutputs(HAC, ForceUpdate, bHasHoudiniStaticMeshOutput); + HAC->SetNoProxyMeshNextCookRequested(false); + + // Handles have to be updated after parameters + FHoudiniHandleTranslator::UpdateHandles(HAC); + + // Clear the HasBeenLoaded flag + if (HAC->HasBeenLoaded()) + { + HAC->SetHasBeenLoaded(false); + } + + // Clear the HasBeenDuplicated flag + if (HAC->HasBeenDuplicated()) + { + HAC->SetHasBeenDuplicated(false); + } + + // Update rendering information. + HAC->UpdateRenderingInformation(); + + // Since we have new asset, we need to update bounds. + HAC->UpdateBounds(); + + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Finished processing outputs"), true); + + // Trigger a details panel update + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + + // If any outputs have HoudiniStaticMeshes, and if timer based refinement is enabled on the HAC, + // set the RefineMeshesTimer and ensure BuildStaticMeshesForAllHoudiniStaticMeshes is bound to + // the RefineMeshesTimerFired delegate of the HAC + if (bHasHoudiniStaticMeshOutput && HAC->IsProxyStaticMeshRefinementByTimerEnabled()) + { + if (!HAC->GetOnRefineMeshesTimerDelegate().IsBoundToObject(this)) + HAC->GetOnRefineMeshesTimerDelegate().AddRaw(this, &FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes); + HAC->SetRefineMeshesTimer(); + } + + if (bHasHoudiniStaticMeshOutput) + bNeedsToTriggerViewportUpdate = true; + + UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); + if (OnPostCookBakeDelegate.IsBound()) + { + OnPostCookBakeDelegate.Execute(HAC); + if (!HAC->IsBakeAfterNextCookEnabled()) + OnPostCookBakeDelegate.Unbind(); + } + } + else + { + // TODO: Create parameters inputs and handles inputs. + //CreateParameters(); + //CreateInputs(); + //CreateHandles(); + + // Clear the bake after cook delegate if + UHoudiniAssetComponent::FOnPostCookBakeDelegate& OnPostCookBakeDelegate = HAC->GetOnPostCookBakeDelegate(); + if (OnPostCookBakeDelegate.IsBound() && !HAC->IsBakeAfterNextCookEnabled()) + { + OnPostCookBakeDelegate.Unbind(); + // Notify the user that the bake failed since the cook failed. + FHoudiniEngine::Get().UpdateCookingNotification(FText::FromString("Cook failed, therefore the bake also failed..."), true); + } + } + + if (HAC->InputPresets.Num() > 0) + { + HAC->ApplyInputPresets(); + } + + // If we have downstream HDAs, we need to tell them we're done cooking + HAC->NotifyCookedToDownstreamAssets(); + + // Notify the PDG manager that the HDA is done cooking + FHoudiniPDGManager::NotifyAssetCooked(HAC->PDGAssetLink, bSuccess); + + if (bNeedsToTriggerViewportUpdate && GEditor) + { + // We need to manually update the vieport with HoudiniMeshProxies + // if not, modification made in H with the two way debugger wont be visible in Unreal until the vieports gets focus + GEditor->RedrawAllViewports(false); + } + + // Clear the rebuild/recook flags + HAC->SetRecookRequested(false); + HAC->SetRebuildRequested(false); + + //HAC->SyncToBlueprintGeneratedClass(); + + return bCookSuccess; +} + +bool +FHoudiniEngineManager::StartTaskAssetProcess(UHoudiniAssetComponent* HAC) +{ + HAC->SetAssetState(EHoudiniAssetState::Processing); + + return true; +} + +bool +FHoudiniEngineManager::UpdateProcess(UHoudiniAssetComponent* HAC) +{ + HAC->SetAssetState(EHoudiniAssetState::None); + + return true; +} + +bool +FHoudiniEngineManager::StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID) +{ + // Check this HAC doesn't already have a running task + if (OutTaskGUID.IsValid()) + return false; + + if (InAssetId >= 0) + { + /* TODO: Handle Asset Preset + if (!FHoudiniEngineUtils::GetAssetPreset(AssetId, PresetBuffer)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get the asset's preset, rebuilt asset may have lost its parameters.")); + } + */ + // Delete the asset + if (!StartTaskAssetDelete(InAssetId, OutTaskGUID, true)) + { + return false; + } + } + + // Create a new task GUID for this asset + OutTaskGUID = FGuid::NewGuid(); + + return true; +} + +bool +FHoudiniEngineManager::StartTaskAssetDelete(const HAPI_NodeId& InNodeId, FGuid& OutTaskGUID, bool bShouldDeleteParent) +{ + if (InNodeId < 0) + return false; + + // Get the Asset's NodeInfo + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, &AssetNodeInfo)); + + HAPI_NodeId OBJNodeToDelete = InNodeId; + if (AssetNodeInfo.type == HAPI_NODETYPE_SOP) + { + // For SOP Asset, we want to delete their parent's OBJ node + if (bShouldDeleteParent) + { + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(OBJNodeToDelete); + OBJNodeToDelete = ParentId != -1 ? ParentId : OBJNodeToDelete; + } + } + + // Generate GUID for our new task. + OutTaskGUID = FGuid::NewGuid(); + + // Create asset deletion task object and submit it for processing. + FHoudiniEngineTask Task(EHoudiniEngineTaskType::AssetDeletion, OutTaskGUID); + Task.AssetId = OBJNodeToDelete; + FHoudiniEngine::Get().AddTask(Task); + + return true; +} + +bool +FHoudiniEngineManager::UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo) +{ + if (!OutTaskGUID.IsValid()) + return false; + + if (!FHoudiniEngine::Get().RetrieveTaskInfo(OutTaskGUID, OutTaskInfo)) + { + // Task information does not exist + OutTaskGUID.Invalidate(); + return false; + } + + // Check whether we want to display Slate cooking and instantiation notifications. + bool bDisplaySlateCookingNotifications = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (EHoudiniEngineTaskState::None != OutTaskInfo.TaskState && bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); + } + + switch (OutTaskInfo.TaskState) + { + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::Success: + case EHoudiniEngineTaskState::FinishedWithError: + case EHoudiniEngineTaskState::FinishedWithFatalError: + { + // If the current task is finished + // Terminate the slate notification if they exist and delete/invalidate the task + if (bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, true); + } + + FHoudiniEngine::Get().RemoveTaskInfo(OutTaskGUID); + OutTaskGUID.Invalidate(); + } + break; + + case EHoudiniEngineTaskState::Working: + { + // The current task is still running, simply update the current notification + if (bDisplaySlateCookingNotifications) + { + FHoudiniEngine::Get().UpdateCookingNotification(OutTaskInfo.StatusText, false); + } + } + break; + + case EHoudiniEngineTaskState::None: + default: + { + break; + } + } + + return true; +} + +bool +FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC) +{ + bool bManualRecook = false; + bool bComponentEnable = false; + if (HAC && !HAC->IsPendingKill()) + { + bManualRecook = HAC->HasRecookBeenRequested(); + bComponentEnable = HAC->IsCookingEnabled(); + } + + if (bManualRecook) + return true; + + if (bComponentEnable && FHoudiniEngine::Get().IsCookingEnabled()) + return true; + + return false; +} + +void +FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes called with HAC=nullptr")); + return; + } + +#if WITH_EDITOR + AActor *Owner = HAC->GetOwner(); + FString Name = Owner ? Owner->GetName() : HAC->GetName(); + + FScopedSlowTask Progress(2.0f, FText::FromString(FString::Printf(TEXT("Refining Proxy Mesh to Static Mesh on %s"), *Name))); + Progress.MakeDialog(); + Progress.EnterProgressFrame(1.0f); +#endif + + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); + +#if WITH_EDITOR + Progress.EnterProgressFrame(1.0f); +#endif +} + + +/* Unreal's viewport representation rules: + Viewport location is the actual camera location; + Lookat position is always right in front of the camera, which means the camera is looking at; + The rotator rotates the forward vector to a direction & orientation, and this dir and orientation is the camera's; + The identity direction and orientation of the camera is facing positive X-axis. +*/ + +/* Hapi's viewport representation rules: + The camera is located at a point on the sphere, which the center is the pivot position and the radius is offset; + Quat determines the location on the sphere and which direction the camera is facing towards, as well as the camera orientation; + The identity location, direction and orientation of the camera is facing positive Z-axis (in Hapi coords); +*/ + + +bool +FHoudiniEngineManager::SyncHoudiniViewportToUnreal() +{ + if (!FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled()) + return false; + +#if WITH_EDITOR + // Get the editor viewport LookAt position to spawn the new objects + if (!GEditor || !GEditor->GetActiveViewport()) + return false; + + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (!ViewportClient) + return false; + + // Get the current UE viewport location, lookat position, and rotation + FVector UnrealViewportPosition = ViewportClient->GetViewLocation(); + FRotator UnrealViewportRotation = ViewportClient->GetViewRotation(); + FVector UnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + /* Check if the Unreal viewport has changed */ + if (UnrealViewportPosition.Equals(SyncedUnrealViewportPosition) && + UnrealViewportRotation.Equals(SyncedUnrealViewportRotation) && + UnrealViewportLookatPosition.Equals(SyncedUnrealViewportLookatPosition)) + { + // No need to sync if the viewport camera hasn't changed + return false; + } + + /* Calculate Hapi Quaternion */ + // Initialize Hapi Quat with Unreal Quat. + // Note that rotations are in general non-commutative *** + FQuat HapiQuat = UnrealViewportRotation.Quaternion(); + + // We're in orbit mode, forward vector is Y-axis + if (ViewportClient->bUsingOrbitCamera) + { + // The forward vector is Y-negative direction when on orbiting mode + HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 180.f)); + + // rotations around X and Y axis are reversed + float TempX = HapiQuat.X; + HapiQuat.X = HapiQuat.Y; + HapiQuat.Y = TempX; + HapiQuat.W = -HapiQuat.W; + + } + // We're not in orbiting mode, forward vector is X-axis + else + { + // Rotate the Quat arount Z-axis by 90 degree. + HapiQuat = HapiQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, 90.f)); + } + + + /* Update Hapi H_View */ + // Note: There are infinte number of H_View representation for current viewport + // Each choice of pivot point determines an equivalent representation. + // We just find an equivalent when the pivot position is the view position, and offset is 0 + + HAPI_Viewport H_View; + H_View.position[0] = UnrealViewportPosition.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + H_View.position[1] = UnrealViewportPosition.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + H_View.position[2] = UnrealViewportPosition.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Set HAPI_Offset always 0 when syncing Houdini to UE viewport + H_View.offset = 0.f; + + H_View.rotationQuaternion[0] = -HapiQuat.X; + H_View.rotationQuaternion[1] = -HapiQuat.Z; + H_View.rotationQuaternion[2] = -HapiQuat.Y; + H_View.rotationQuaternion[3] = HapiQuat.W; + + FHoudiniApi::SetViewport(FHoudiniEngine::Get().GetSession(), &H_View); + + /* Update the Synced viewport values + We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. + Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. */ + + // We need to get the H_Viewport again, since it is possible the value is a different equivalence of what we set. + HAPI_Viewport Cur_H_View; + FHoudiniApi::GetViewport( + FHoudiniEngine::Get().GetSession(), &Cur_H_View); + + // Hapi values are in Houdini coordinate and scale + SyncedHoudiniViewportPivotPosition = FVector(Cur_H_View.position[0], Cur_H_View.position[1], Cur_H_View.position[2]); + SyncedHoudiniViewportQuat = FQuat(Cur_H_View.rotationQuaternion[0], Cur_H_View.rotationQuaternion[1], Cur_H_View.rotationQuaternion[2], Cur_H_View.rotationQuaternion[3]); + SyncedHoudiniViewportOffset = Cur_H_View.offset; + + SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); + SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); + SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + // When sync Houdini to UE, we set offset to be 0. + // So we need to zero out offset for the next time syncing UE to Houdini + bOffsetZeroed = true; + + return true; +#endif + + return false; +} + + +bool +FHoudiniEngineManager::SyncUnrealViewportToHoudini() +{ + if (!FHoudiniEngine::Get().IsSyncUnrealViewportEnabled()) + return false; + +#if WITH_EDITOR + // Get the editor viewport LookAt position to spawn the new objects + if (!GEditor || !GEditor->GetActiveViewport()) + return false; + + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (!ViewportClient) + return false; + + // Get the current HAPI_Viewport + HAPI_Viewport H_View; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetViewport( + FHoudiniEngine::Get().GetSession(), &H_View)) + { + return false; + } + + + // Get Hapi viewport's PivotPosition, Offset and Quat, w.r.t Houdini's coordinate and scale. + FVector HapiViewportPivotPosition = FVector(H_View.position[0], H_View.position[1], H_View.position[2]); + float HapiViewportOffset = H_View.offset; + FQuat HapiViewportQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[1], H_View.rotationQuaternion[2], H_View.rotationQuaternion[3]); + + /* Check if the Houdini viewport has changed */ + if (SyncedHoudiniViewportPivotPosition.Equals(HapiViewportPivotPosition) && + SyncedHoudiniViewportQuat.Equals(HapiViewportQuat) && + SyncedHoudiniViewportOffset == HapiViewportOffset) + { + // Houdini viewport hasn't changed, nothing to do + return false; + } + + // Set zero value of offset when needed + if (bOffsetZeroed) + { + ZeroOffsetValue = H_View.offset; + bOffsetZeroed = false; + } + + + /* Translate the hapi camera transfrom to Unreal's representation system */ + + // Get pivot point in UE's coordinate and scale + FVector UnrealViewportPivotPosition = FVector(H_View.position[0], H_View.position[2], H_View.position[1]) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // HAPI bug? After we set the H_View, offset becomes a lot bigger when move the viewport just a little bit in Houdini. + // But the pivot point doesn't change. Which caused UE viewport jumping far suddenly. + // So we get rid of this problem by setting the first HAPI_offset value after syncing Houdini viewport as the base. + + // Get offset in UE's scale. The actual offset after 'zero out' + float UnrealOffset = (H_View.offset - ZeroOffsetValue) * HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + /* Calculate Quaternion in UE */ + // Rotate the resulting Quat around Z-axis by -90 degree. + // Note that rotation is in general non-commutative *** + FQuat UnrealQuat = FQuat(H_View.rotationQuaternion[0], H_View.rotationQuaternion[2], H_View.rotationQuaternion[1], -H_View.rotationQuaternion[3]); + UnrealQuat = UnrealQuat * FQuat::MakeFromEuler(FVector(0.f, 0.f, -90.f)); + + FVector UnrealBaseVector(1.f, 0.f, 0.f); // Forward vector in Unreal viewport + + /* Get UE viewport location*/ + FVector UnrealViewPosition = - UnrealQuat.RotateVector(UnrealBaseVector) * UnrealOffset + UnrealViewportPivotPosition; + + /* Set the viewport's value */ + ViewportClient->SetViewLocation(UnrealViewPosition); + ViewportClient->SetViewRotation(UnrealQuat.Rotator()); + + // Invalidate the viewport + ViewportClient->Invalidate(); + + /* Update the synced viewport values */ + // We need to syced both the viewport representation values in Hapi and UE whenever the viewport is changed. + // Since the 2 representations are multi-multi correspondence, the values could be changed even though the viewport is not changing. + + // Hapi values are in Houdini coordinate and scale + SyncedHoudiniViewportPivotPosition = HapiViewportPivotPosition; + SyncedHoudiniViewportQuat = HapiViewportQuat; + SyncedHoudiniViewportOffset = HapiViewportOffset; + + SyncedUnrealViewportPosition = ViewportClient->GetViewLocation(); + SyncedUnrealViewportRotation = ViewportClient->GetViewRotation(); + SyncedUnrealViewportLookatPosition = ViewportClient->GetLookAtLocation(); + + return true; +#endif + + return false; +} + + +void +FHoudiniEngineManager::DisableEditorAutoSave(const UHoudiniAssetComponent* HAC) +{ +#if WITH_EDITOR + if (!HAC || HAC->IsPendingKill()) + return; + + if (!GUnrealEd) + return; + + if (DisableAutoSavingHACs.Contains(HAC)) + return; + // Add the HAC to the set + DisableAutoSavingHACs.Add(HAC); + + // Return if auto-saving has been disabled by some other HACs. + if (DisableAutoSavingHACs.Num() > 1) + return; + + // Disable auto-saving by setting min time till auto-save to max float value + IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); + AutoSaver.ForceMinimumTimeTillAutoSave(TNumericLimits::Max()); +#endif +} + + +void +FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = nullptr) +{ +#if WITH_EDITOR + if (!GUnrealEd) + return; + + if (!HAC) + { + // When HAC is nullptr, go through all HACs in the set, + // remove it if the HAC has been deleted. + if (DisableAutoSavingHACs.Num() <= 0) + return; + + TSet ValidComponents; + for (auto& CurHAC : DisableAutoSavingHACs) + { + if (CurHAC && !CurHAC->IsPendingKill()) + { + ValidComponents.Add(CurHAC); + } + } + DisableAutoSavingHACs = MoveTemp(ValidComponents); + } + else + { + // Otherwise, remove the HAC from the set + if (DisableAutoSavingHACs.Contains(HAC)) + DisableAutoSavingHACs.Remove(HAC); + } + + if (DisableAutoSavingHACs.Num() > 0) + return; + + // When no HAC disables cooking, reset min time till auto-save to default value, then reset the timer + IPackageAutoSaver &AutoSaver = GUnrealEd->GetPackageAutoSaver(); + AutoSaver.ResetAutoSaveTimer(); +#endif +} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.h b/Source/HoudiniEngine/Private/HoudiniEngineManager.h index 5d9610f6f..8693f1671 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.h @@ -1,182 +1,204 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "TimerManager.h" - -//#include "HAL/Runnable.h" -//#include "HAL/RunnableThread.h" -//#include "Misc/SingleThreadRunnable.h" - -#include "HoudiniPDGManager.h" - -class UHoudiniAsset; -class UHoudiniAssetComponent; - -struct FHoudiniEngineTaskInfo; -struct FGuid; - -enum class EHoudiniAssetState : uint8; - -class FHoudiniEngineManager -{ -public: - - FHoudiniEngineManager(); - virtual ~FHoudiniEngineManager(); - - void StartHoudiniTicking(); - void StopHoudiniTicking(); - bool Tick(float DeltaTime); - - // Updates / Process a component - void ProcessComponent(UHoudiniAssetComponent* HAC); - - // Build UStaticMesh for all UHoudiniStaticMesh in a HAC. - // This is fired by the OnRefinedMeshesTimerDelegate on a HAC - void BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC); - - void StartPDGCommandlet() - { - if (!IsPDGCommandletRunningOrConnected()) - PDGManager.CreateBGEOCommandletAndEndpoint(); - } - - void StopPDGCommandlet() - { - if (IsPDGCommandletRunningOrConnected()) - PDGManager.StopBGEOCommandletAndEndpoint(); - } - - bool IsPDGCommandletRunningOrConnected() - { - const EHoudiniBGEOCommandletStatus Status = PDGManager.UpdateAndGetBGEOCommandletStatus(); - return Status == EHoudiniBGEOCommandletStatus::Running || Status == EHoudiniBGEOCommandletStatus::Connected; - } - - EHoudiniBGEOCommandletStatus GetPDGCommandletStatus() { return PDGManager.UpdateAndGetBGEOCommandletStatus(); } - - -protected: - - // Updates a given task's status - // Returns true if the given task's status was properly found - bool UpdateTaskStatus(FGuid& OutTaskGUID, FHoudiniEngineTaskInfo& OutTaskInfo); - - // Start a task to instantiate the given HoudiniAsset - // Return true if the task was successfully created - bool StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, const FString& DisplayName, FGuid& OutTaskGUID, FString& OutHAPIAssetName); - - // Updates progress of the instantiation task - // Returns true if a state change should be made - bool UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); - - // Start a task to instantiate the Houdini Asset with the given node Id - // Returns true if the task was successfully created - bool StartTaskAssetCooking(const HAPI_NodeId& AssetId, const FString& DisplayName, FGuid& OutTaskGUID); - - // Updates progress of the cooking task - // Returns true if a state change should be made - bool UpdateCooking(UHoudiniAssetComponent* HAC, EHoudiniAssetState& NewState); - - // Called to update template components. - bool PreCookTemplate(UHoudiniAssetComponent* HAC); - - // Called to update all houdini nodes/params/inputs before a cook has started - bool PreCook(UHoudiniAssetComponent* HAC); - - // Called after a cook has finished - bool PostCook(UHoudiniAssetComponent* HAC, const bool& bSuccess, const HAPI_NodeId& TaskAssetId); - - bool StartTaskAssetProcess(UHoudiniAssetComponent* HAC); - - bool UpdateProcess(UHoudiniAssetComponent* HAC); - - // Starts a rebuild task (delete then re instantiate) - // The NodeID should be invalidated after a successful call - bool StartTaskAssetRebuild(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID); - - // Starts a node delete task - // The NodeID should be invalidated after a successful call - bool StartTaskAssetDelete(const HAPI_NodeId& InAssetId, FGuid& OutTaskGUID, bool bShouldDeleteParent); - - bool IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC); - - // Syncs the houdini viewport to Unreal's viewport - // Returns true if the Houdini viewport has been modified - bool SyncHoudiniViewportToUnreal(); - - // Syncs the unreal viewport to Houdini's viewport - // Returns true if the Unreal viewport has been modified - bool SyncUnrealViewportToHoudini(); - - // Disable auto save by setting min time till auto save to the max value - void DisableEditorAutoSave(const UHoudiniAssetComponent* HAC); - - void EnableEditorAutoSave(const UHoudiniAssetComponent* HAC); - - // Automatically try to start the First HE session if needed - void AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC); - -private: - - // Ticker handle, used for processing HAC. - FDelegateHandle TickerHandle; - - // Current position in the array - uint32 CurrentIndex; - - // Current number of components in the array - uint32 ComponentCount; - - // Stopping flag. - // Indicates that we should stop ticking asap - bool bMustStopTicking; - - // The PDG Manager, handles all registered PDG Asset Links - FHoudiniPDGManager PDGManager; - - // For ViewportSync: The camera transform that Hapi and Unreal currently agree with. - FVector SyncedHoudiniViewportPivotPosition; - FQuat SyncedHoudiniViewportQuat; - float SyncedHoudiniViewportOffset; - - FVector SyncedUnrealViewportPosition; - FRotator SyncedUnrealViewportRotation; - FVector SyncedUnrealViewportLookatPosition; - - // We need these two variables to get rid of a HAPI bug - // Note: When sync Houdini to UE, we set the pivot position to be the view position, and offset to be 0.0. - // but when we switch to control viewport in Houdini, HAPI returns an H_View with a large offset, but pivot unchanged. - // so, we need these two variables to 'zero out' offset. - float ZeroOffsetValue; // in HAPI scale - bool bOffsetZeroed; - - // Indicates which HACs disable auto-saving - TSet DisableAutoSavingHACs; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "TimerManager.h" + +//#include "HAL/Runnable.h" +//#include "HAL/RunnableThread.h" +//#include "Misc/SingleThreadRunnable.h" + +#include "HoudiniPDGManager.h" + +class UHoudiniAsset; +class UHoudiniAssetComponent; + +struct FHoudiniEngineTaskInfo; +struct FGuid; + +enum class EHoudiniAssetState : uint8; + +class FHoudiniEngineManager +{ +public: + + FHoudiniEngineManager(); + virtual ~FHoudiniEngineManager(); + + void StartHoudiniTicking(); + void StopHoudiniTicking(); + bool Tick(float DeltaTime); + + // Updates / Process a component + void ProcessComponent(UHoudiniAssetComponent* HAC); + + // Build UStaticMesh for all UHoudiniStaticMesh in a HAC. + // This is fired by the OnRefinedMeshesTimerDelegate on a HAC + void BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC); + + void StartPDGCommandlet() + { + if (!IsPDGCommandletRunningOrConnected()) + PDGManager.CreateBGEOCommandletAndEndpoint(); + } + + void StopPDGCommandlet() + { + if (IsPDGCommandletRunningOrConnected()) + PDGManager.StopBGEOCommandletAndEndpoint(); + } + + bool IsPDGCommandletRunningOrConnected() + { + const EHoudiniBGEOCommandletStatus Status = PDGManager.UpdateAndGetBGEOCommandletStatus(); + return Status == EHoudiniBGEOCommandletStatus::Running || Status == EHoudiniBGEOCommandletStatus::Connected; + } + + EHoudiniBGEOCommandletStatus GetPDGCommandletStatus() { return PDGManager.UpdateAndGetBGEOCommandletStatus(); } + + +protected: + + // Updates a given task's status + // Returns true if the given task's status was properly found + bool UpdateTaskStatus( + FGuid& OutTaskGUID, + FHoudiniEngineTaskInfo& OutTaskInfo); + + // Start a task to instantiate the given HoudiniAsset + // Return true if the task was successfully created + bool StartTaskAssetInstantiation( + UHoudiniAsset* HoudiniAsset, + const FString& DisplayName, + FGuid& OutTaskGUID, + FString& OutHAPIAssetName); + + // Updates progress of the instantiation task + // Returns true if a state change should be made + bool UpdateInstantiating( + UHoudiniAssetComponent* HAC, + EHoudiniAssetState& NewState); + + // Start a task to instantiate the Houdini Asset with the given node Id + // Returns true if the task was successfully created + bool StartTaskAssetCooking( + const HAPI_NodeId& AssetId, + const TArray& NodeIdsToCook, + const FString& DisplayName, + FGuid& OutTaskGUID); + + // Updates progress of the cooking task + // Returns true if a state change should be made + bool UpdateCooking( + UHoudiniAssetComponent* HAC, + EHoudiniAssetState& NewState); + + // Called to update template components. + bool PreCookTemplate(UHoudiniAssetComponent* HAC); + + // Called to update all houdini nodes/params/inputs before a cook has started + bool PreCook(UHoudiniAssetComponent* HAC); + + // Called after a cook has finished + bool PostCook( + UHoudiniAssetComponent* HAC, + const bool& bSuccess, + const HAPI_NodeId& TaskAssetId); + + bool StartTaskAssetProcess(UHoudiniAssetComponent* HAC); + + bool UpdateProcess(UHoudiniAssetComponent* HAC); + + // Starts a rebuild task (delete then re instantiate) + // The NodeID should be invalidated after a successful call + bool StartTaskAssetRebuild( + const HAPI_NodeId& InAssetId, + FGuid& OutTaskGUID); + + // Starts a node delete task + // The NodeID should be invalidated after a successful call + bool StartTaskAssetDelete( + const HAPI_NodeId& InAssetId, + FGuid& OutTaskGUID, + bool bShouldDeleteParent); + + bool IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* HAC); + + // Syncs the houdini viewport to Unreal's viewport + // Returns true if the Houdini viewport has been modified + bool SyncHoudiniViewportToUnreal(); + + // Syncs the unreal viewport to Houdini's viewport + // Returns true if the Unreal viewport has been modified + bool SyncUnrealViewportToHoudini(); + + // Disable auto save by setting min time till auto save to the max value + void DisableEditorAutoSave(const UHoudiniAssetComponent* HAC); + + void EnableEditorAutoSave(const UHoudiniAssetComponent* HAC); + + // Automatically try to start the First HE session if needed + void AutoStartFirstSessionIfNeeded(UHoudiniAssetComponent* InCurrentHAC); + +private: + + // Ticker handle, used for processing HAC. + FDelegateHandle TickerHandle; + + // Current position in the array + uint32 CurrentIndex; + + // Current number of components in the array + uint32 ComponentCount; + + // Stopping flag. + // Indicates that we should stop ticking asap + bool bMustStopTicking; + + // The PDG Manager, handles all registered PDG Asset Links + FHoudiniPDGManager PDGManager; + + // For ViewportSync: The camera transform that Hapi and Unreal currently agree with. + FVector SyncedHoudiniViewportPivotPosition; + FQuat SyncedHoudiniViewportQuat; + float SyncedHoudiniViewportOffset; + + FVector SyncedUnrealViewportPosition; + FRotator SyncedUnrealViewportRotation; + FVector SyncedUnrealViewportLookatPosition; + + // We need these two variables to get rid of a HAPI bug + // Note: When sync Houdini to UE, we set the pivot position to be the view position, and offset to be 0.0. + // but when we switch to control viewport in Houdini, HAPI returns an H_View with a large offset, but pivot unchanged. + // so, we need these two variables to 'zero out' offset. + float ZeroOffsetValue; // in HAPI scale + bool bOffsetZeroed; + + // Indicates which HACs disable auto-saving + TSet DisableAutoSavingHACs; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp index 957845f18..ce7231f85 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.cpp @@ -1,60 +1,60 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineOutputStats.h" - -FHoudiniEngineOutputStats::FHoudiniEngineOutputStats() - : NumPackagesCreated(0) - , NumPackagesUpdated(0) -{ } - -void FHoudiniEngineOutputStats::NotifyPackageCreated(int32 NumCreated) -{ - NumPackagesCreated += NumCreated; -} - -void FHoudiniEngineOutputStats::NotifyPackageUpdated(int32 NumUpdated) -{ - NumPackagesUpdated += NumUpdated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated) -{ - const int32 Count = OutputObjectsCreated.FindOrAdd(ObjectTypeName, 0); - OutputObjectsCreated[ObjectTypeName] = Count + NumCreated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated) -{ - const int32 Count = OutputObjectsUpdated.FindOrAdd(ObjectTypeName, 0); - OutputObjectsUpdated[ObjectTypeName] = Count + NumUpdated; -} - -void FHoudiniEngineOutputStats::NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced) -{ - const int32 Count = OutputObjectsReplaced.FindOrAdd(ObjectTypeName, 0); - OutputObjectsReplaced[ObjectTypeName] = Count + NumReplaced; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineOutputStats.h" + +FHoudiniEngineOutputStats::FHoudiniEngineOutputStats() + : NumPackagesCreated(0) + , NumPackagesUpdated(0) +{ } + +void FHoudiniEngineOutputStats::NotifyPackageCreated(int32 NumCreated) +{ + NumPackagesCreated += NumCreated; +} + +void FHoudiniEngineOutputStats::NotifyPackageUpdated(int32 NumUpdated) +{ + NumPackagesUpdated += NumUpdated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated) +{ + const int32 Count = OutputObjectsCreated.FindOrAdd(ObjectTypeName, 0); + OutputObjectsCreated[ObjectTypeName] = Count + NumCreated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated) +{ + const int32 Count = OutputObjectsUpdated.FindOrAdd(ObjectTypeName, 0); + OutputObjectsUpdated[ObjectTypeName] = Count + NumUpdated; +} + +void FHoudiniEngineOutputStats::NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced) +{ + const int32 Count = OutputObjectsReplaced.FindOrAdd(ObjectTypeName, 0); + OutputObjectsReplaced[ObjectTypeName] = Count + NumReplaced; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h index b973c18d1..6234dca1b 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineOutputStats.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Class.h" - -struct HOUDINIENGINE_API FHoudiniEngineOutputStats -{ - FHoudiniEngineOutputStats(); - - int32 NumPackagesCreated; - int32 NumPackagesUpdated; - - // These FStrings should preferably be EHoudiniOutputType enum - // Move the OUtput enums into a separate header to avoid circular dependencies. - TMap OutputObjectsCreated; - TMap OutputObjectsUpdated; - TMap OutputObjectsReplaced; - - void NotifyPackageCreated(int32 NumCreated); - void NotifyPackageUpdated(int32 NumUpdated); - - // Objects created - void NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated); - template - void NotifyObjectsCreated(EnumT EnumValue, int32 NumCreated) - { - NotifyObjectsCreated( UEnum::GetValueAsString(EnumValue), NumCreated ); - } - - // Object updated - void NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated); - template - void NotifyObjectsUpdated(EnumT EnumValue, int32 NumUpdated) - { - NotifyObjectsUpdated( UEnum::GetValueAsString(EnumValue), NumUpdated ); - } - - // Objects replaced - void NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced); - template - void NotifyObjectsReplaced(EnumT EnumValue, int32 NumReplaced) - { - NotifyObjectsReplaced( UEnum::GetValueAsString(EnumValue), NumReplaced ); - } -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Class.h" + +struct HOUDINIENGINE_API FHoudiniEngineOutputStats +{ + FHoudiniEngineOutputStats(); + + int32 NumPackagesCreated; + int32 NumPackagesUpdated; + + // These FStrings should preferably be EHoudiniOutputType enum + // Move the OUtput enums into a separate header to avoid circular dependencies. + TMap OutputObjectsCreated; + TMap OutputObjectsUpdated; + TMap OutputObjectsReplaced; + + void NotifyPackageCreated(int32 NumCreated); + void NotifyPackageUpdated(int32 NumUpdated); + + // Objects created + void NotifyObjectsCreated(const FString& ObjectTypeName, int32 NumCreated); + template + void NotifyObjectsCreated(EnumT EnumValue, int32 NumCreated) + { + NotifyObjectsCreated( UEnum::GetValueAsString(EnumValue), NumCreated ); + } + + // Object updated + void NotifyObjectsUpdated(const FString& ObjectTypeName, int32 NumUpdated); + template + void NotifyObjectsUpdated(EnumT EnumValue, int32 NumUpdated) + { + NotifyObjectsUpdated( UEnum::GetValueAsString(EnumValue), NumUpdated ); + } + + // Objects replaced + void NotifyObjectsReplaced(const FString& ObjectTypeName, int32 NumReplaced); + template + void NotifyObjectsReplaced(EnumT EnumValue, int32 NumReplaced) + { + NotifyObjectsReplaced( UEnum::GetValueAsString(EnumValue), NumReplaced ); + } +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp index 772259c55..478cdec79 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.cpp @@ -1,625 +1,677 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineScheduler.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngineString.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngine.h" - -const uint32 -FHoudiniEngineScheduler::InitialTaskSize = 256u; - -const float -FHoudiniEngineScheduler::UpdateFrequency = 0.1f; - -FHoudiniEngineScheduler::FHoudiniEngineScheduler() - : Tasks(nullptr) - , PositionWrite(0u) - , PositionRead(0u) - , bStopping(false) -{ - // Make sure size is power of two. - TaskCount = FPlatformMath::RoundUpToPowerOfTwo(FHoudiniEngineScheduler::InitialTaskSize); - - if (TaskCount) - { - // Allocate buffer to store all tasks. - Tasks = static_cast(FMemory::Malloc(TaskCount * sizeof(FHoudiniEngineTask))); - - if (Tasks) - { - // Zero memory. - FMemory::Memset(Tasks, 0x0, TaskCount * sizeof(FHoudiniEngineTask)); - } - } -} - -FHoudiniEngineScheduler::~FHoudiniEngineScheduler() -{ - if (TaskCount) - { - FMemory::Free(Tasks); - Tasks = nullptr; - } -} - -void -FHoudiniEngineScheduler::TaskDescription( - FHoudiniEngineTaskInfo & TaskInfo, - const FString & ActorName, - const FString & StatusString) -{ - FFormatNamedArguments Args; - - if (!ActorName.IsEmpty()) - { - Args.Add(TEXT("AssetName"), FText::FromString(ActorName)); - Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); - TaskInfo.StatusText = - FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetName}) : ({AssetStatus})"), Args); - } - else - { - Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); - TaskInfo.StatusText = - FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetStatus})"), Args); - } -} - -void -FHoudiniEngineScheduler::TaskInstantiateAsset(const FHoudiniEngineTask & Task) -{ - FString AssetN; - FHoudiniEngineString(Task.AssetHapiName).ToFString(AssetN); - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Instantiation Started for %s: Asset=%s, HoudiniAsset = 0x%x"), - *Task.ActorName, *AssetN, Task.Asset.Get()); - - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskInstantiateAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - if (!Task.Asset.IsValid()) - { - // Asset is no longer valid, return. - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset is no longer valid.")); - - return; - } - - if (Task.AssetHapiName < 0) - { - // Asset is no longer valid, return. - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset name is invalid.")); - - return; - } - - HAPI_Result Result = HAPI_RESULT_SUCCESS; - int32 AssetCount = 0; - HAPI_NodeId AssetId = -1; - std::string AssetNameString; - double LastUpdateTime; - - FHoudiniEngineString HoudiniEngineString(Task.AssetHapiName); - if (!HoudiniEngineString.ToStdString(AssetNameString)) - { - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Error retrieving asset name.")); - - return; - } - - // Translate asset name into Unreal string. - FString AssetName = ANSI_TO_TCHAR(AssetNameString.c_str()); - - // Initialize last update time. - LastUpdateTime = FPlatformTime::Seconds(); - - // We instantiate without cooking. - Result = FHoudiniApi::CreateNode( - FHoudiniEngine::Get().GetSession(), -1, &AssetNameString[0], nullptr, false, &AssetId); - if (Result != HAPI_RESULT_SUCCESS) - { - AddResponseMessageTaskInfo( - Result, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Error instantiating asset.")); - - return; - } - - // Add processing notification. - FHoudiniEngineTaskInfo TaskInfo( - HAPI_RESULT_SUCCESS, -1, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Working); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - TaskDescription(TaskInfo, Task.ActorName, TEXT("Started Instantiation")); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); - - // We need to spin until instantiation is finished. - while (true) - { - int Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // Cooking has been successful. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Success, AssetId, Task, - TEXT("Finished Instantiation.")); - - break; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // There was an error while instantiating. - FString CookResultString = FHoudiniEngineUtils::GetCookResult(); - int32 CookResult = static_cast(HAPI_RESULT_SUCCESS); - FHoudiniApi::GetStatus(FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_RESULT, &CookResult); - - EHoudiniEngineTaskState TaskStateResult = EHoudiniEngineTaskState::FinishedWithFatalError; - if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - TaskStateResult = EHoudiniEngineTaskState::FinishedWithError; - - AddResponseMessageTaskInfo( - static_cast(CookResult), - EHoudiniEngineTaskType::AssetInstantiation, - TaskStateResult, - AssetId, Task, - FString::Printf(TEXT("Finished Instantiation with Errors: %s"), *CookResultString)); - - break; - } - - static const double NotificationUpdateFrequency = 0.5; - if ((FPlatformTime::Seconds() - LastUpdateTime) >= NotificationUpdateFrequency) - { - // Reset update time. - LastUpdateTime = FPlatformTime::Seconds(); - const FString& CookStateMessage = FHoudiniEngineUtils::GetCookState(); - - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetInstantiation, - EHoudiniEngineTaskState::Working, - AssetId, Task, CookStateMessage); - } - - // We want to yield. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } -} - -void -FHoudiniEngineScheduler::TaskCookAsset(const FHoudiniEngineTask & Task) -{ - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskCookAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - // Retrieve asset id. - HAPI_NodeId AssetId = Task.AssetId; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Cooking Started for %s., AssetId = %d"), - *Task.ActorName, AssetId); - - if (AssetId == -1) - { - // We have an invalid asset id. - HOUDINI_LOG_ERROR(TEXT("TaskCookAsset failed for %s: Invalid Asset Id."), *Task.ActorName); - - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset has invalid id.")); - - return; - } - - // Default CookOptions - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - Result = FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), AssetId, &CookOptions); - if (Result != HAPI_RESULT_SUCCESS) - { - AddResponseMessageTaskInfo( - Result, EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::FinishedWithFatalError, - AssetId, Task, TEXT("Error cooking asset.")); - - return; - } - - // Add processing notification. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Working, - AssetId, Task, TEXT("Started Cooking")); - - // Initialize last update time. - double LastUpdateTime = FPlatformTime::Seconds(); - - // We need to spin until cooking is finished. - while (true) - { - int32 Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET( &Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // Cooking has been successful. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Success, - AssetId, Task, TEXT("Finished Cooking")); - - break; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - EHoudiniEngineTaskState TaskResult = EHoudiniEngineTaskState::FinishedWithFatalError; - if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - TaskResult = EHoudiniEngineTaskState::FinishedWithError; - - // There was an error while instantiating. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - TaskResult, - AssetId, Task, - TEXT("Finished Cooking with Errors")); - - break; - } - - static const double NotificationUpdateFrequency = 0.5; - if (FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency) - { - // Reset update time. - LastUpdateTime = FPlatformTime::Seconds(); - - // Retrieve status string. - const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); - - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetCooking, - EHoudiniEngineTaskState::Working, - AssetId, Task, CookStateMessage); - } - - // We want to yield. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } -} - -void -FHoudiniEngineScheduler::TaskDeleteAsset(const FHoudiniEngineTask & Task) -{ - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Destruction Started for %s. ") - TEXT("AssetId = %d"), - *Task.ActorName, Task.AssetId); - - if (FHoudiniEngineUtils::IsHoudiniNodeValid(Task.AssetId)) - FHoudiniEngineUtils::DestroyHoudiniAsset(Task.AssetId); - - // We do not insert task info as this is a fire and forget operation. - // At this point component most likely does not exist. -} - -void -FHoudiniEngineScheduler::AddResponseTaskInfo( - HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, const FHoudiniEngineTask & Task) -{ - FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); - FString StatusString = FHoudiniEngineUtils::GetErrorDescription(); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - - TaskDescription(TaskInfo, Task.ActorName, StatusString); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); -} - -void -FHoudiniEngineScheduler::AddResponseMessageTaskInfo( - HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, const FString & ErrorMessage) -{ - FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); - - //TaskInfo.bLoadedComponent = Task.bLoadedComponent; - - TaskDescription(TaskInfo, Task.ActorName, ErrorMessage); - FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); -} - -void -FHoudiniEngineScheduler::ProcessQueuedTasks() -{ - while (!bStopping) - { - while (true) - { - FHoudiniEngineTask Task; - - { - FScopeLock ScopeLock(&CriticalSection); - - // We have no tasks left. - if (PositionWrite == PositionRead) - break; - - // Retrieve task. - Task = Tasks[PositionRead]; - PositionRead++; - - // Wrap around if required. - PositionRead &= (TaskCount - 1); - } - - bool bTaskProcessed = true; - - switch (Task.TaskType) - { - case EHoudiniEngineTaskType::AssetInstantiation: - { - TaskInstantiateAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetCooking: - { - TaskCookAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetDeletion: - { - TaskDeleteAsset(Task); - break; - } - - case EHoudiniEngineTaskType::AssetProcess: - { - TaskProccessAsset(Task); - break; - } - - default: - { - bTaskProcessed = false; - break; - } - } - - if (!bTaskProcessed) - break; - } - - if (FPlatformProcess::SupportsMultithreading()) - { - // We want to yield for a bit. - FPlatformProcess::SleepNoStats(UpdateFrequency); - } - else - { - // If we are running in single threaded mode, return so we don't block everything else. - return; - } - } -} - -void -FHoudiniEngineScheduler::TaskProccessAsset(const FHoudiniEngineTask & Task) -{ - if (!FHoudiniEngineUtils::IsInitialized()) - { - HOUDINI_LOG_ERROR( - TEXT("TaskProccessAsset failed for %s: %s"), - *Task.ActorName, - *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); - - AddResponseMessageTaskInfo( - HAPI_RESULT_NOT_INITIALIZED, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("HAPI is not initialized.")); - - return; - } - - // Retrieve asset id. - HAPI_NodeId AssetId = Task.AssetId; - if (AssetId == -1) - { - // We have an invalid asset id. - HOUDINI_LOG_ERROR(TEXT("TaskProcessAsset failed for %s: Invalid Asset Id."), *Task.ActorName); - - AddResponseMessageTaskInfo( - HAPI_RESULT_FAILURE, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::FinishedWithFatalError, - -1, Task, TEXT("Asset has invalid id.")); - - return; - } - - HOUDINI_LOG_MESSAGE( - TEXT("HAPI Asynchronous Processing started for %s., AssetId = %d"), - *Task.ActorName, AssetId); - - // Add processing notification. - AddResponseMessageTaskInfo( - HAPI_RESULT_SUCCESS, - EHoudiniEngineTaskType::AssetProcess, - EHoudiniEngineTaskState::Working, - AssetId, Task, TEXT("Started Cooking")); - - // - - - // TODO: Process results! -} - -bool FHoudiniEngineScheduler::HasPendingTasks() -{ - FScopeLock ScopeLock(&CriticalSection); - return (PositionWrite != PositionRead); -} - -void -FHoudiniEngineScheduler::AddTask(const FHoudiniEngineTask & Task) -{ - FScopeLock ScopeLock(&CriticalSection); - - // Check if we need to grow our circular buffer. - if (PositionWrite + 1 == PositionRead) - { - // Calculate next size (next power of two). - uint32 NextTaskCount = FPlatformMath::RoundUpToPowerOfTwo(TaskCount + 1); - - // Allocate new buffer. - FHoudiniEngineTask * Buffer = static_cast( - FMemory::Malloc(NextTaskCount * sizeof(FHoudiniEngineTask))); - - if (!Buffer) - return; - - // Zero memory. - FMemory::Memset(Buffer, 0x0, NextTaskCount * sizeof(FHoudiniEngineTask)); - - // Copy elements from old buffer to new one. - if (PositionRead < PositionWrite) - { - FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (PositionWrite - PositionRead)); - - // Update index positions. - PositionRead = 0; - PositionWrite = PositionWrite - PositionRead; - } - else - { - FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (TaskCount - PositionRead)); - FMemory::Memcpy(Buffer + TaskCount - PositionRead, Tasks, sizeof(FHoudiniEngineTask) * PositionWrite); - - // Update index positions. - PositionRead = 0; - PositionWrite = TaskCount - PositionRead + PositionWrite; - } - - // Deallocate old buffer. - FMemory::Free(Tasks); - - // Bookkeeping. - Tasks = Buffer; - TaskCount = NextTaskCount; - } - - // Store task. - Tasks[PositionWrite] = Task; - PositionWrite++; - - // Wrap around if required. - PositionWrite &= (TaskCount - 1); -} - -uint32 -FHoudiniEngineScheduler::Run() -{ - ProcessQueuedTasks(); - return 0; -} - -void -FHoudiniEngineScheduler::Stop() -{ - bStopping = true; -} - -void -FHoudiniEngineScheduler::Tick() -{ - ProcessQueuedTasks(); -} - -FSingleThreadRunnable * -FHoudiniEngineScheduler::GetSingleThreadInterface() -{ - return this; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineScheduler.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngine.h" + +const uint32 +FHoudiniEngineScheduler::InitialTaskSize = 256u; + +const float +FHoudiniEngineScheduler::UpdateFrequency = 0.1f; + +FHoudiniEngineScheduler::FHoudiniEngineScheduler() + : Tasks(nullptr) + , PositionWrite(0u) + , PositionRead(0u) + , bStopping(false) +{ + // Make sure size is power of two. + TaskCount = FPlatformMath::RoundUpToPowerOfTwo(FHoudiniEngineScheduler::InitialTaskSize); + + if (TaskCount) + { + // Allocate buffer to store all tasks. + Tasks = static_cast(FMemory::Malloc(TaskCount * sizeof(FHoudiniEngineTask))); + + if (Tasks) + { + // Zero memory. + FMemory::Memset(Tasks, 0x0, TaskCount * sizeof(FHoudiniEngineTask)); + } + } +} + +FHoudiniEngineScheduler::~FHoudiniEngineScheduler() +{ + if (TaskCount) + { + FMemory::Free(Tasks); + Tasks = nullptr; + } +} + +void +FHoudiniEngineScheduler::TaskDescription( + FHoudiniEngineTaskInfo & TaskInfo, + const FString & ActorName, + const FString & StatusString) +{ + FFormatNamedArguments Args; + + if (!ActorName.IsEmpty()) + { + Args.Add(TEXT("AssetName"), FText::FromString(ActorName)); + Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); + TaskInfo.StatusText = + FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetName}) : ({AssetStatus})"), Args); + } + else + { + Args.Add(TEXT("AssetStatus"), FText::FromString(StatusString)); + TaskInfo.StatusText = + FText::Format(NSLOCTEXT("TaskDescription", "TaskDescriptionProgress", "({AssetStatus})"), Args); + } +} + +void +FHoudiniEngineScheduler::TaskInstantiateAsset(const FHoudiniEngineTask & Task) +{ + FString AssetN; + FHoudiniEngineString(Task.AssetHapiName).ToFString(AssetN); + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Instantiation Started for %s: Asset=%s, HoudiniAsset = 0x%x"), + *Task.ActorName, *AssetN, Task.Asset.Get()); + + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskInstantiateAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + if (!Task.Asset.IsValid()) + { + // Asset is no longer valid, return. + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset is no longer valid.")); + + return; + } + + if (Task.AssetHapiName < 0) + { + // Asset is no longer valid, return. + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset name is invalid.")); + + return; + } + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + int32 AssetCount = 0; + HAPI_NodeId AssetId = -1; + std::string AssetNameString; + double LastUpdateTime; + + FHoudiniEngineString HoudiniEngineString(Task.AssetHapiName); + if (!HoudiniEngineString.ToStdString(AssetNameString)) + { + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Error retrieving asset name.")); + + return; + } + + // Translate asset name into Unreal string. + FString AssetName = ANSI_TO_TCHAR(AssetNameString.c_str()); + + // Initialize last update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // We instantiate without cooking. + Result = FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), -1, &AssetNameString[0], nullptr, false, &AssetId); + if (Result != HAPI_RESULT_SUCCESS) + { + AddResponseMessageTaskInfo( + Result, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Error instantiating asset.")); + + return; + } + + // Add processing notification. + FHoudiniEngineTaskInfo TaskInfo( + HAPI_RESULT_SUCCESS, -1, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Working); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + TaskDescription(TaskInfo, Task.ActorName, TEXT("Started Instantiation")); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); + + // We need to spin until instantiation is finished. + while (true) + { + int Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // Cooking has been successful. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Success, AssetId, Task, + TEXT("Finished Instantiation.")); + + break; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // There was an error while instantiating. + FString CookResultString = FHoudiniEngineUtils::GetCookResult(); + int32 CookResult = static_cast(HAPI_RESULT_SUCCESS); + FHoudiniApi::GetStatus(FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_RESULT, &CookResult); + + EHoudiniEngineTaskState TaskStateResult = EHoudiniEngineTaskState::FinishedWithFatalError; + if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + TaskStateResult = EHoudiniEngineTaskState::FinishedWithError; + + AddResponseMessageTaskInfo( + static_cast(CookResult), + EHoudiniEngineTaskType::AssetInstantiation, + TaskStateResult, + AssetId, Task, + FString::Printf(TEXT("Finished Instantiation with Errors: %s"), *CookResultString)); + + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if ((FPlatformTime::Seconds() - LastUpdateTime) >= NotificationUpdateFrequency) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + const FString& CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetInstantiation, + EHoudiniEngineTaskState::Working, + AssetId, Task, CookStateMessage); + } + + // We want to yield. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } +} + +void +FHoudiniEngineScheduler::TaskCookAsset(const FHoudiniEngineTask & Task) +{ + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskCookAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + // Retrieve asset id. + HAPI_NodeId AssetId = Task.AssetId; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Cooking Started for %s., AssetId = %d"), + *Task.ActorName, AssetId); + + if (AssetId == -1) + { + // We have an invalid asset id. + HOUDINI_LOG_ERROR(TEXT("TaskCookAsset failed for %s: Invalid Asset Id."), *Task.ActorName); + + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset has invalid id.")); + + return; + } + + // Get the extra node Ids that we want to process if needed + TArray NodesToCook; + NodesToCook.Add(AssetId); + for (auto& CurrentNodeId : Task.OtherNodeIds) + { + if (CurrentNodeId < 0) + continue; + + NodesToCook.AddUnique(CurrentNodeId); + } + + // Default CookOptions + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + + EHoudiniEngineTaskState GlobalTaskResult = EHoudiniEngineTaskState::Success; + for (auto& CurrentNodeId : NodesToCook) + { + Result = FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CookOptions); + if (Result != HAPI_RESULT_SUCCESS) + { + AddResponseMessageTaskInfo( + Result, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + AssetId, + Task, + TEXT("Error cooking asset.")); + + return; + } + + // Add processing notification. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Working, + AssetId, Task, TEXT("Started Cooking")); + + // Initialize last update time. + double LastUpdateTime = FPlatformTime::Seconds(); + + // We need to spin until cooking is finished. + while (true) + { + int32 Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // Cooking has been successful. + // Break to process the next node + break; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + GlobalTaskResult = EHoudiniEngineTaskState::FinishedWithFatalError; + + if (Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + GlobalTaskResult = EHoudiniEngineTaskState::FinishedWithError; + + break; + } + + static const double NotificationUpdateFrequency = 0.5; + if (FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency) + { + // Reset update time. + LastUpdateTime = FPlatformTime::Seconds(); + + // Retrieve status string. + const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); + + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Working, + AssetId, Task, CookStateMessage); + } + + // We want to yield. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } + } + + switch (GlobalTaskResult) + { + case EHoudiniEngineTaskState::Success: + { + // Cooking has been successful + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::Success, + AssetId, + Task, + TEXT("Finished Cooking")); + } + break; + + case EHoudiniEngineTaskState::FinishedWithError: + { + // There was an error while Cooking. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithError, + AssetId, + Task, + TEXT("Finished Cooking with Errors")); + } + break; + + case EHoudiniEngineTaskState::FinishedWithFatalError: + case EHoudiniEngineTaskState::Aborted: + case EHoudiniEngineTaskState::None: + case EHoudiniEngineTaskState::Working: + { + // There was an error while cooking. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetCooking, + EHoudiniEngineTaskState::FinishedWithFatalError, + AssetId, + Task, + TEXT("Finished Cooking with Fatal Errors")); + } + break; + } +} + +void +FHoudiniEngineScheduler::TaskDeleteAsset(const FHoudiniEngineTask & Task) +{ + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Destruction Started for %s. ") + TEXT("AssetId = %d"), + *Task.ActorName, Task.AssetId); + + if (FHoudiniEngineUtils::IsHoudiniNodeValid(Task.AssetId)) + FHoudiniEngineUtils::DestroyHoudiniAsset(Task.AssetId); + + // We do not insert task info as this is a fire and forget operation. + // At this point component most likely does not exist. +} + +void +FHoudiniEngineScheduler::AddResponseTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task) +{ + FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); + FString StatusString = FHoudiniEngineUtils::GetErrorDescription(); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + + TaskDescription(TaskInfo, Task.ActorName, StatusString); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); +} + +void +FHoudiniEngineScheduler::AddResponseMessageTaskInfo( + HAPI_Result Result, EHoudiniEngineTaskType TaskType, EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, const FString & ErrorMessage) +{ + FHoudiniEngineTaskInfo TaskInfo(Result, AssetId, TaskType, TaskState); + + //TaskInfo.bLoadedComponent = Task.bLoadedComponent; + + TaskDescription(TaskInfo, Task.ActorName, ErrorMessage); + FHoudiniEngine::Get().AddTaskInfo(Task.HapiGUID, TaskInfo); +} + +void +FHoudiniEngineScheduler::ProcessQueuedTasks() +{ + while (!bStopping) + { + while (true) + { + FHoudiniEngineTask Task; + + { + FScopeLock ScopeLock(&CriticalSection); + + // We have no tasks left. + if (PositionWrite == PositionRead) + break; + + // Retrieve task. + Task = Tasks[PositionRead]; + PositionRead++; + + // Wrap around if required. + PositionRead &= (TaskCount - 1); + } + + bool bTaskProcessed = true; + + switch (Task.TaskType) + { + case EHoudiniEngineTaskType::AssetInstantiation: + { + TaskInstantiateAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetCooking: + { + TaskCookAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetDeletion: + { + TaskDeleteAsset(Task); + break; + } + + case EHoudiniEngineTaskType::AssetProcess: + { + TaskProccessAsset(Task); + break; + } + + default: + { + bTaskProcessed = false; + break; + } + } + + if (!bTaskProcessed) + break; + } + + if (FPlatformProcess::SupportsMultithreading()) + { + // We want to yield for a bit. + FPlatformProcess::SleepNoStats(UpdateFrequency); + } + else + { + // If we are running in single threaded mode, return so we don't block everything else. + return; + } + } +} + +void +FHoudiniEngineScheduler::TaskProccessAsset(const FHoudiniEngineTask & Task) +{ + if (!FHoudiniEngineUtils::IsInitialized()) + { + HOUDINI_LOG_ERROR( + TEXT("TaskProccessAsset failed for %s: %s"), + *Task.ActorName, + *FHoudiniEngineUtils::GetErrorDescription(HAPI_RESULT_NOT_INITIALIZED)); + + AddResponseMessageTaskInfo( + HAPI_RESULT_NOT_INITIALIZED, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("HAPI is not initialized.")); + + return; + } + + // Retrieve asset id. + HAPI_NodeId AssetId = Task.AssetId; + if (AssetId == -1) + { + // We have an invalid asset id. + HOUDINI_LOG_ERROR(TEXT("TaskProcessAsset failed for %s: Invalid Asset Id."), *Task.ActorName); + + AddResponseMessageTaskInfo( + HAPI_RESULT_FAILURE, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::FinishedWithFatalError, + -1, Task, TEXT("Asset has invalid id.")); + + return; + } + + HOUDINI_LOG_MESSAGE( + TEXT("HAPI Asynchronous Processing started for %s., AssetId = %d"), + *Task.ActorName, AssetId); + + // Add processing notification. + AddResponseMessageTaskInfo( + HAPI_RESULT_SUCCESS, + EHoudiniEngineTaskType::AssetProcess, + EHoudiniEngineTaskState::Working, + AssetId, Task, TEXT("Started Cooking")); + + // + + + // TODO: Process results! +} + +bool FHoudiniEngineScheduler::HasPendingTasks() +{ + FScopeLock ScopeLock(&CriticalSection); + return (PositionWrite != PositionRead); +} + +void +FHoudiniEngineScheduler::AddTask(const FHoudiniEngineTask & Task) +{ + FScopeLock ScopeLock(&CriticalSection); + + // Check if we need to grow our circular buffer. + if (PositionWrite + 1 == PositionRead) + { + // Calculate next size (next power of two). + uint32 NextTaskCount = FPlatformMath::RoundUpToPowerOfTwo(TaskCount + 1); + + // Allocate new buffer. + FHoudiniEngineTask * Buffer = static_cast( + FMemory::Malloc(NextTaskCount * sizeof(FHoudiniEngineTask))); + + if (!Buffer) + return; + + // Zero memory. + FMemory::Memset(Buffer, 0x0, NextTaskCount * sizeof(FHoudiniEngineTask)); + + // Copy elements from old buffer to new one. + if (PositionRead < PositionWrite) + { + FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (PositionWrite - PositionRead)); + + // Update index positions. + PositionRead = 0; + PositionWrite = PositionWrite - PositionRead; + } + else + { + FMemory::Memcpy(Buffer, Tasks + PositionRead, sizeof(FHoudiniEngineTask) * (TaskCount - PositionRead)); + FMemory::Memcpy(Buffer + TaskCount - PositionRead, Tasks, sizeof(FHoudiniEngineTask) * PositionWrite); + + // Update index positions. + PositionRead = 0; + PositionWrite = TaskCount - PositionRead + PositionWrite; + } + + // Deallocate old buffer. + FMemory::Free(Tasks); + + // Bookkeeping. + Tasks = Buffer; + TaskCount = NextTaskCount; + } + + // Store task. + Tasks[PositionWrite] = Task; + PositionWrite++; + + // Wrap around if required. + PositionWrite &= (TaskCount - 1); +} + +uint32 +FHoudiniEngineScheduler::Run() +{ + ProcessQueuedTasks(); + return 0; +} + +void +FHoudiniEngineScheduler::Stop() +{ + bStopping = true; +} + +void +FHoudiniEngineScheduler::Tick() +{ + ProcessQueuedTasks(); +} + +FSingleThreadRunnable * +FHoudiniEngineScheduler::GetSingleThreadInterface() +{ + return this; +} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h index a0540d1ea..2c9354540 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineScheduler.h @@ -1,119 +1,119 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineTask.h" -#include "HoudiniEngineTaskInfo.h" -#include "HAL/Runnable.h" -#include "HAL/RunnableThread.h" -#include "Misc/SingleThreadRunnable.h" - -class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable -{ -public: - - FHoudiniEngineScheduler(); - virtual ~FHoudiniEngineScheduler(); - - // FRunnable methods. - virtual uint32 Run() override; - virtual void Stop() override; - FSingleThreadRunnable * GetSingleThreadInterface() override; - - // FSingleThreadRunnable methods. - virtual void Tick() override; - - // Adds a task. - void AddTask(const FHoudiniEngineTask & Task); - - bool HasPendingTasks(); - - // Adds instantiation response task info. - void AddResponseTaskInfo( - HAPI_Result Result, - EHoudiniEngineTaskType TaskType, - EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, - const FHoudiniEngineTask & Task); - - void AddResponseMessageTaskInfo( - HAPI_Result Result, - EHoudiniEngineTaskType TaskType, - EHoudiniEngineTaskState TaskState, - HAPI_NodeId AssetId, - const FHoudiniEngineTask & Task, - const FString & ErrorMessage); - -protected: - - // Process queued tasks. - void ProcessQueuedTasks(); - - // Task : instantiate an asset. - void TaskInstantiateAsset(const FHoudiniEngineTask & Task); - - // Task : cook an asset. - void TaskCookAsset(const FHoudiniEngineTask & Task); - - // Create description of task's state. - void TaskDescription( - FHoudiniEngineTaskInfo & Task, - const FString & ActorName, - const FString & StatusString); - - // Delete an asset. - void TaskDeleteAsset(const FHoudiniEngineTask & Task); - - // Process the result of a sucesfull cook - void TaskProccessAsset(const FHoudiniEngineTask & Task); - -private: - - // Initial number of tasks in our circular queue. - static const uint32 InitialTaskSize; - - // Frequency update (sleep time between each update) - static const float UpdateFrequency; - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // List of scheduled tasks. - FHoudiniEngineTask* Tasks; - - // Head of the circular queue. - uint32 PositionWrite; - - // Tail of the circular queue. - uint32 PositionRead; - - // Size of the circular queue. - uint32 TaskCount; - - // Stopping flag. - bool bStopping; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineTask.h" +#include "HoudiniEngineTaskInfo.h" +#include "HAL/Runnable.h" +#include "HAL/RunnableThread.h" +#include "Misc/SingleThreadRunnable.h" + +class FHoudiniEngineScheduler : public FRunnable, FSingleThreadRunnable +{ +public: + + FHoudiniEngineScheduler(); + virtual ~FHoudiniEngineScheduler(); + + // FRunnable methods. + virtual uint32 Run() override; + virtual void Stop() override; + FSingleThreadRunnable * GetSingleThreadInterface() override; + + // FSingleThreadRunnable methods. + virtual void Tick() override; + + // Adds a task. + void AddTask(const FHoudiniEngineTask & Task); + + bool HasPendingTasks(); + + // Adds instantiation response task info. + void AddResponseTaskInfo( + HAPI_Result Result, + EHoudiniEngineTaskType TaskType, + EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, + const FHoudiniEngineTask & Task); + + void AddResponseMessageTaskInfo( + HAPI_Result Result, + EHoudiniEngineTaskType TaskType, + EHoudiniEngineTaskState TaskState, + HAPI_NodeId AssetId, + const FHoudiniEngineTask & Task, + const FString & ErrorMessage); + +protected: + + // Process queued tasks. + void ProcessQueuedTasks(); + + // Task : instantiate an asset. + void TaskInstantiateAsset(const FHoudiniEngineTask & Task); + + // Task : cook an asset. + void TaskCookAsset(const FHoudiniEngineTask & Task); + + // Create description of task's state. + void TaskDescription( + FHoudiniEngineTaskInfo & Task, + const FString & ActorName, + const FString & StatusString); + + // Delete an asset. + void TaskDeleteAsset(const FHoudiniEngineTask & Task); + + // Process the result of a sucesfull cook + void TaskProccessAsset(const FHoudiniEngineTask & Task); + +private: + + // Initial number of tasks in our circular queue. + static const uint32 InitialTaskSize; + + // Frequency update (sleep time between each update) + static const float UpdateFrequency; + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // List of scheduled tasks. + FHoudiniEngineTask* Tasks; + + // Head of the circular queue. + uint32 PositionWrite; + + // Tail of the circular queue. + uint32 PositionRead; + + // Size of the circular queue. + uint32 TaskCount; + + // Stopping flag. + bool bStopping; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineString.cpp b/Source/HoudiniEngine/Private/HoudiniEngineString.cpp index 69214fbd6..78d9871f8 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineString.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineString.cpp @@ -1,215 +1,285 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineString.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include - -FHoudiniEngineString::FHoudiniEngineString() - : StringId(-1) -{} - -FHoudiniEngineString::FHoudiniEngineString(int32 InStringId) - : StringId(InStringId) -{} - -FHoudiniEngineString::FHoudiniEngineString(const FHoudiniEngineString& Other) - : StringId(Other.StringId) -{} - -FHoudiniEngineString & -FHoudiniEngineString::operator=(const FHoudiniEngineString& Other) -{ - if (this != &Other) - StringId = Other.StringId; - - return *this; -} - -bool -FHoudiniEngineString::operator==(const FHoudiniEngineString& Other) const -{ - return Other.StringId == StringId; -} - -bool -FHoudiniEngineString::operator!=(const FHoudiniEngineString& Other) const -{ - return Other.StringId != StringId; -} - -int32 -FHoudiniEngineString::GetId() const -{ - return StringId; -} - -bool -FHoudiniEngineString::HasValidId() const -{ - return StringId > 0; -} - -bool -FHoudiniEngineString::ToStdString(std::string& String) const -{ - String = ""; - - // Null string ID / zero should be considered invalid - // (or we'd get the "null string, should never see this!" text) - if (StringId <= 0) - { - return false; - } - - int32 NameLength = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBufLength( - FHoudiniEngine::Get().GetSession(), StringId, &NameLength)) - { - return false; - } - - if (NameLength <= 0) - return false; - - std::vector< char > NameBuffer(NameLength, '\0'); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( - FHoudiniEngine::Get().GetSession(), - StringId, &NameBuffer[0], NameLength ) ) - { - return false; - } - - String = std::string(NameBuffer.begin(), NameBuffer.end()); - - return true; -} - -bool -FHoudiniEngineString::ToFName(FName& Name) const -{ - Name = NAME_None; - FString NameString = TEXT(""); - if (ToFString(NameString)) - { - Name = FName(*NameString); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToFString(FString& String) const -{ - String = TEXT(""); - std::string NamePlain = ""; - - if (ToStdString(NamePlain)) - { - String = UTF8_TO_TCHAR(NamePlain.c_str()); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToFText(FText& Text) const -{ - Text = FText::GetEmpty(); - FString NameString = TEXT(""); - - if (ToFString(NameString)) - { - Text = FText::FromString(NameString); - return true; - } - - return false; -} - -bool -FHoudiniEngineString::ToStdString(const int32& InStringId, std::string& OutStdString) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToStdString(OutStdString); -} - -bool -FHoudiniEngineString::ToFName(const int32& InStringId, FName& OutName) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFName(OutName); -} - -bool -FHoudiniEngineString::ToFString(const int32& InStringId, FString& OutString) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFString(OutString); -} - -bool -FHoudiniEngineString::ToFText(const int32& InStringId, FText& OutText) -{ - FHoudiniEngineString HAPIString(InStringId); - return HAPIString.ToFText(OutText); -} - -bool -FHoudiniEngineString::SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray) -{ - bool bReturn = true; - OutStringArray.SetNumZeroed(InStringIdArray.Num()); - - // Avoid calling HAPI to resolve the same strings again and again - TMap ResolvedStrings; - for (int32 IdxSH = 0; IdxSH < InStringIdArray.Num(); IdxSH++) - { - const int32* ResolvedString = ResolvedStrings.Find(InStringIdArray[IdxSH]); - if (ResolvedString) - { - // Already resolved earlier, copy the string instead of calling HAPI. - OutStringArray[IdxSH] = OutStringArray[*ResolvedString]; - } - else - { - FString CurrentString = FString(); - if(!FHoudiniEngineString::ToFString(InStringIdArray[IdxSH], CurrentString)) - bReturn = false; - - OutStringArray[IdxSH] = CurrentString; - ResolvedStrings.Add(InStringIdArray[IdxSH], IdxSH); - } - } - - return bReturn; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineString.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include + +FHoudiniEngineString::FHoudiniEngineString() + : StringId(-1) +{} + +FHoudiniEngineString::FHoudiniEngineString(int32 InStringId) + : StringId(InStringId) +{} + +FHoudiniEngineString::FHoudiniEngineString(const FHoudiniEngineString& Other) + : StringId(Other.StringId) +{} + +FHoudiniEngineString & +FHoudiniEngineString::operator=(const FHoudiniEngineString& Other) +{ + if (this != &Other) + StringId = Other.StringId; + + return *this; +} + +bool +FHoudiniEngineString::operator==(const FHoudiniEngineString& Other) const +{ + return Other.StringId == StringId; +} + +bool +FHoudiniEngineString::operator!=(const FHoudiniEngineString& Other) const +{ + return Other.StringId != StringId; +} + +int32 +FHoudiniEngineString::GetId() const +{ + return StringId; +} + +bool +FHoudiniEngineString::HasValidId() const +{ + return StringId > 0; +} + +bool +FHoudiniEngineString::ToStdString(std::string& String) const +{ + String = ""; + + // Null string ID / zero should be considered invalid + // (or we'd get the "null string, should never see this!" text) + if (StringId <= 0) + { + return false; + } + + int32 NameLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBufLength( + FHoudiniEngine::Get().GetSession(), StringId, &NameLength)) + { + return false; + } + + if (NameLength <= 0) + return false; + + std::vector NameBuffer(NameLength, '\0'); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( + FHoudiniEngine::Get().GetSession(), + StringId, &NameBuffer[0], NameLength ) ) + { + return false; + } + + String = std::string(NameBuffer.begin(), NameBuffer.end()); + + return true; +} + +bool +FHoudiniEngineString::ToFName(FName& Name) const +{ + Name = NAME_None; + FString NameString = TEXT(""); + if (ToFString(NameString)) + { + Name = FName(*NameString); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToFString(FString& String) const +{ + String = TEXT(""); + std::string NamePlain = ""; + + if (ToStdString(NamePlain)) + { + String = UTF8_TO_TCHAR(NamePlain.c_str()); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToFText(FText& Text) const +{ + Text = FText::GetEmpty(); + FString NameString = TEXT(""); + + if (ToFString(NameString)) + { + Text = FText::FromString(NameString); + return true; + } + + return false; +} + +bool +FHoudiniEngineString::ToStdString(const int32& InStringId, std::string& OutStdString) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToStdString(OutStdString); +} + +bool +FHoudiniEngineString::ToFName(const int32& InStringId, FName& OutName) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFName(OutName); +} + +bool +FHoudiniEngineString::ToFString(const int32& InStringId, FString& OutString) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFString(OutString); +} + +bool +FHoudiniEngineString::ToFText(const int32& InStringId, FText& OutText) +{ + FHoudiniEngineString HAPIString(InStringId); + return HAPIString.ToFText(OutText); +} + +bool +FHoudiniEngineString::SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray) +{ + if (SHArrayToFStringArray_Batch(InStringIdArray, OutStringArray)) + return true; + + return SHArrayToFStringArray_Singles(InStringIdArray, OutStringArray); +} + +bool +FHoudiniEngineString::SHArrayToFStringArray_Batch(const TArray& InStringIdArray, TArray& OutStringArray) +{ + bool bReturn = true; + OutStringArray.SetNumZeroed(InStringIdArray.Num()); + + TArray UniqueSH; + for (const auto& CurrentSH : InStringIdArray) + { + UniqueSH.AddUnique(CurrentSH); + } + + int32 BufferSize = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBatchSize( + FHoudiniEngine::Get().GetSession(), UniqueSH.GetData(), UniqueSH.Num(), &BufferSize)) + return false; + + if (BufferSize <= 0) + return false; + + std::vector Buffer(BufferSize, '\0'); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBatch( + FHoudiniEngine::Get().GetSession(), &Buffer[0], BufferSize)) + return false; + + // Parse the buffer to a string array + TArray ConvertedString; + std::vector::iterator CurrentBegin = Buffer.begin(); + for (std::vector::iterator it = Buffer.begin(); it != Buffer.end(); it++) + { + if (*it != '\0') + continue; + + std::string stdString = std::string(CurrentBegin, it); + ConvertedString.Add(UTF8_TO_TCHAR(stdString.c_str())); + + CurrentBegin = it; + CurrentBegin++; + } + + if (ConvertedString.Num() != UniqueSH.Num()) + return false; + + // Build a map to map string handles to indices + TMap SHToIndexMap; + for (int32 Idx = 0; Idx < UniqueSH.Num(); Idx++) + SHToIndexMap.Add(UniqueSH[Idx], Idx); + + // Fill the output array using the map + for (int32 IdxSH = 0; IdxSH < InStringIdArray.Num(); IdxSH++) + { + const int32* FoundIndex = SHToIndexMap.Find(InStringIdArray[IdxSH]); + if (!FoundIndex || !ConvertedString.IsValidIndex(*FoundIndex)) + return false; + + // Already resolved earlier, copy the string instead of calling HAPI. + OutStringArray[IdxSH] = ConvertedString[*FoundIndex]; + } + + return true; +} +bool +FHoudiniEngineString::SHArrayToFStringArray_Singles(const TArray& InStringIdArray, TArray& OutStringArray) +{ + bool bReturn = true; + OutStringArray.SetNumZeroed(InStringIdArray.Num()); + + // Avoid calling HAPI to resolve the same strings again and again + TMap ResolvedStrings; + for (int32 IdxSH = 0; IdxSH < InStringIdArray.Num(); IdxSH++) + { + const int32* ResolvedString = ResolvedStrings.Find(InStringIdArray[IdxSH]); + if (ResolvedString) + { + // Already resolved earlier, copy the string instead of calling HAPI. + OutStringArray[IdxSH] = OutStringArray[*ResolvedString]; + } + else + { + FString CurrentString = FString(); + if(!FHoudiniEngineString::ToFString(InStringIdArray[IdxSH], CurrentString)) + bReturn = false; + + OutStringArray[IdxSH] = CurrentString; + ResolvedStrings.Add(InStringIdArray[IdxSH], IdxSH); + } + } + + return bReturn; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineString.h b/Source/HoudiniEngine/Private/HoudiniEngineString.h index c1f40fea0..c6872eda5 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineString.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineString.h @@ -1,74 +1,80 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include -#include "HoudiniApi.h" - -class FText; -class FString; -class FName; - -class HOUDINIENGINE_API FHoudiniEngineString -{ - public: - - FHoudiniEngineString(); - FHoudiniEngineString(int32 InStringId); - FHoudiniEngineString(const FHoudiniEngineString & Other); - - FHoudiniEngineString & operator=(const FHoudiniEngineString & Other); - - bool operator==(const FHoudiniEngineString & Other) const; - bool operator!=(const FHoudiniEngineString & Other) const; - - // Conversion functions - bool ToStdString(std::string & String) const; - bool ToFName(FName & Name) const; - bool ToFString(FString & String) const; - bool ToFText(FText & Text) const; - - // Static converters - static bool ToStdString(const int32& InStringId, std::string & String); - static bool ToFName(const int32& InStringId, FName & Name); - static bool ToFString(const int32& InStringId, FString & String); - static bool ToFText(const int32& InStringId, FText & Text); - - // Array converter, uses a map to avoid redudant calls to HAPI - static bool SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray); - - // Return id of this string. - int32 GetId() const; - - // Return true if this string has a valid id. - bool HasValidId() const; - - protected: - - // Id of the underlying Houdini Engine string. - int32 StringId; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include +#include "HoudiniApi.h" + +class FText; +class FString; +class FName; + +class HOUDINIENGINE_API FHoudiniEngineString +{ + public: + + FHoudiniEngineString(); + FHoudiniEngineString(int32 InStringId); + FHoudiniEngineString(const FHoudiniEngineString & Other); + + FHoudiniEngineString & operator=(const FHoudiniEngineString & Other); + + bool operator==(const FHoudiniEngineString & Other) const; + bool operator!=(const FHoudiniEngineString & Other) const; + + // Conversion functions + bool ToStdString(std::string & String) const; + bool ToFName(FName & Name) const; + bool ToFString(FString & String) const; + bool ToFText(FText & Text) const; + + // Static converters + static bool ToStdString(const int32& InStringId, std::string & String); + static bool ToFName(const int32& InStringId, FName & Name); + static bool ToFString(const int32& InStringId, FString & String); + static bool ToFText(const int32& InStringId, FText & Text); + + // Array converter, uses a map to avoid redudant calls to HAPI + static bool SHArrayToFStringArray(const TArray& InStringIdArray, TArray& OutStringArray); + + // Array converter, uses string batches and a map to reduce HAPI calls + static bool SHArrayToFStringArray_Batch(const TArray& InStringIdArray, TArray& OutStringArray); + + // Array converter, uses a map to reduce HAPI calls + static bool SHArrayToFStringArray_Singles(const TArray& InStringIdArray, TArray& OutStringArray); + + // Return id of this string. + int32 GetId() const; + + // Return true if this string has a valid id. + bool HasValidId() const; + + protected: + + // Id of the underlying Houdini Engine string. + int32 StringId; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp index a1d759193..e9feda76f 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp @@ -1,48 +1,51 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineTask.h" - -#include "HoudiniApi.h" - -FHoudiniEngineTask::FHoudiniEngineTask() - : TaskType(EHoudiniEngineTaskType::None) - , ActorName(TEXT("")) - , AssetId(-1) - , AssetLibraryId(-1) - , AssetHapiName(-1) -{ - HapiGUID.Invalidate(); -} - -FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID) - : HapiGUID(InHapiGUID) - , TaskType(InTaskType) - , ActorName(TEXT("")) - , AssetId(-1) - , AssetLibraryId(-1) - , AssetHapiName(-1) -{} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineTask.h" + +#include "HoudiniApi.h" + +FHoudiniEngineTask::FHoudiniEngineTask() + : TaskType(EHoudiniEngineTaskType::None) + , ActorName(TEXT("")) + , AssetId(-1) + , AssetLibraryId(-1) + , AssetHapiName(-1) +{ + HapiGUID.Invalidate(); + OtherNodeIds.Empty(); +} + +FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID) + : HapiGUID(InHapiGUID) + , TaskType(InTaskType) + , ActorName(TEXT("")) + , AssetId(-1) + , AssetLibraryId(-1) + , AssetHapiName(-1) +{ + OtherNodeIds.Empty(); +} diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.h b/Source/HoudiniEngine/Private/HoudiniEngineTask.h index f17c9712d..861c277ca 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.h @@ -1,100 +1,104 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniApi.h" -#include "CoreMinimal.h" -#include "Misc/Guid.h" -#include "UObject/WeakObjectPtr.h" - -/* -namespace EHoudiniEngineTaskType -{ - enum Type - { - None, - - // This type corresponds to Houdini asset instantiation (without cooking). - AssetInstantiation, - - // This type corresponds to Houdini asset cooking request. - AssetCooking, - - // This type is used for asynchronous asset deletion. - AssetDeletion - }; -} -*/ - -UENUM() -enum class EHoudiniEngineTaskType : uint8 -{ - None, - - // This type corresponds to Houdini asset instantiation (without cooking). - AssetInstantiation, - - // This type corresponds to Houdini asset cooking request. - AssetCooking, - - // This type is used for asynchronous asset deletion. - AssetDeletion, - - // This type is used when processing the results of a sucessful cook - AssetProcess, -}; - -struct HOUDINIENGINE_API FHoudiniEngineTask -{ - // Constructors. - FHoudiniEngineTask(); - FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID); - - // GUID of this request. - FGuid HapiGUID; - - // Type of this task. - EHoudiniEngineTaskType TaskType; - - // Houdini asset for instantiation. - TWeakObjectPtr< class UHoudiniAsset > Asset; - - // Name of the actor requesting this task. - FString ActorName; - - // Asset Id. - HAPI_NodeId AssetId; - - // Library Id. - HAPI_AssetLibraryId AssetLibraryId; - - // HAPI name of the asset. - int32 AssetHapiName; - - // Is set to true if component has been loaded. - //bool bLoadedComponent; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniApi.h" +#include "CoreMinimal.h" +#include "Misc/Guid.h" +#include "UObject/WeakObjectPtr.h" + +/* +namespace EHoudiniEngineTaskType +{ + enum Type + { + None, + + // This type corresponds to Houdini asset instantiation (without cooking). + AssetInstantiation, + + // This type corresponds to Houdini asset cooking request. + AssetCooking, + + // This type is used for asynchronous asset deletion. + AssetDeletion + }; +} +*/ + +UENUM() +enum class EHoudiniEngineTaskType : uint8 +{ + None, + + // This type corresponds to Houdini asset instantiation (without cooking). + AssetInstantiation, + + // This type corresponds to Houdini asset cooking request. + AssetCooking, + + // This type is used for asynchronous asset deletion. + AssetDeletion, + + // This type is used when processing the results of a sucessful cook + AssetProcess, +}; + +struct HOUDINIENGINE_API FHoudiniEngineTask +{ + // Constructors. + FHoudiniEngineTask(); + FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid InHapiGUID); + + // GUID of this request. + FGuid HapiGUID; + + // Type of this task. + EHoudiniEngineTaskType TaskType; + + // Houdini asset for instantiation. + TWeakObjectPtr< class UHoudiniAsset > Asset; + + // Name of the actor requesting this task. + FString ActorName; + + // Asset Id. + HAPI_NodeId AssetId; + + // Additional Node Id for the task + // Can be used to apply a task to multiple nodes in the same HDA + TArray OtherNodeIds; + + // Library Id. + HAPI_AssetLibraryId AssetLibraryId; + + // HAPI name of the asset. + int32 AssetHapiName; + + // Is set to true if component has been loaded. + //bool bLoadedComponent; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp index d63feab38..f104eacde 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.cpp @@ -1,47 +1,47 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineTaskInfo.h" - -#include "HAPI/HAPI_Common.h" - -FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo() - : Result(HAPI_RESULT_SUCCESS) - , AssetId(-1) - , TaskType(EHoudiniEngineTaskType::None) - , TaskState(EHoudiniEngineTaskState::None) -{} - -FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo( - HAPI_Result InResult, - HAPI_NodeId InAssetId, - EHoudiniEngineTaskType InTaskType, - EHoudiniEngineTaskState InTaskState) - : Result(InResult) - , AssetId(InAssetId) - , TaskType(InTaskType) - , TaskState(InTaskState) +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineTaskInfo.h" + +#include "HAPI/HAPI_Common.h" + +FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo() + : Result(HAPI_RESULT_SUCCESS) + , AssetId(-1) + , TaskType(EHoudiniEngineTaskType::None) + , TaskState(EHoudiniEngineTaskState::None) +{} + +FHoudiniEngineTaskInfo::FHoudiniEngineTaskInfo( + HAPI_Result InResult, + HAPI_NodeId InAssetId, + EHoudiniEngineTaskType InTaskType, + EHoudiniEngineTaskState InTaskState) + : Result(InResult) + , AssetId(InAssetId) + , TaskType(InTaskType) + , TaskState(InTaskState) {} \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h index 79e6aceb1..c5831d8a6 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTaskInfo.h @@ -1,78 +1,78 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineTask.h" - -UENUM() -enum class EHoudiniEngineTaskState : uint8 -{ - None, - - // Indicates the current task is still running - Working, - - // Indicates the task has successfully finished - Success, - - // Indicates the task has finished with non fatal errors - FinishedWithError, - - // Indicates the task has finished with fatal errors and should be terminated - FinishedWithFatalError, - - // Indicates the task has been aborted (unused) - Aborted -}; - -struct HOUDINIENGINE_API FHoudiniEngineTaskInfo -{ - // Constructors. - FHoudiniEngineTaskInfo(); - FHoudiniEngineTaskInfo( - HAPI_Result InResult, HAPI_NodeId InAssetId, - EHoudiniEngineTaskType InTaskType, - EHoudiniEngineTaskState InTaskState); - - // Current HAPI result. - HAPI_Result Result; - - // Current Asset Id. - HAPI_NodeId AssetId; - - // Type of task. - EHoudiniEngineTaskType TaskType; - - // Current status. - EHoudiniEngineTaskState TaskState; - - // String used for status / progress bar. - FText StatusText; - - // Is set to true if corresponding task was issued for loaded component. - //bool bLoadedComponent; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineTask.h" + +UENUM() +enum class EHoudiniEngineTaskState : uint8 +{ + None, + + // Indicates the current task is still running + Working, + + // Indicates the task has successfully finished + Success, + + // Indicates the task has finished with non fatal errors + FinishedWithError, + + // Indicates the task has finished with fatal errors and should be terminated + FinishedWithFatalError, + + // Indicates the task has been aborted (unused) + Aborted +}; + +struct HOUDINIENGINE_API FHoudiniEngineTaskInfo +{ + // Constructors. + FHoudiniEngineTaskInfo(); + FHoudiniEngineTaskInfo( + HAPI_Result InResult, HAPI_NodeId InAssetId, + EHoudiniEngineTaskType InTaskType, + EHoudiniEngineTaskState InTaskState); + + // Current HAPI result. + HAPI_Result Result; + + // Current Asset Id. + HAPI_NodeId AssetId; + + // Type of task. + EHoudiniEngineTaskType TaskType; + + // Current status. + EHoudiniEngineTaskState TaskState; + + // String used for status / progress bar. + FText StatusText; + + // Is set to true if corresponding task was issued for loaded component. + //bool bLoadedComponent; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp index b22f1f07d..78f29a118 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp @@ -1,5351 +1,5470 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineUtils.h" -#include "Misc/StringFormatArg.h" - -#if PLATFORM_WINDOWS - #include "Windows/WindowsHWrapper.h" - - // Of course, Windows defines its own GetGeoInfo, - // So we need to undefine that before including HoudiniApi.h to avoid collision... - #ifdef GetGeoInfo - #undef GetGeoInfo - #endif -#endif - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniEngineString.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInput.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniEngineRuntime.h" - -#if WITH_EDITOR - #include "SAssetSelectionWidget.h" -#endif - -#include "HAPI/HAPI_Version.h" - -#include "Misc/Paths.h" -#include "Editor/EditorEngine.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" -#include "PropertyEditorModule.h" -#include "Modules/ModuleManager.h" -#include "Engine/StaticMeshSocket.h" -#include "Async/Async.h" -#include "BlueprintEditor.h" -#include "Toolkits/AssetEditorManager.h" -#include "Engine/BlueprintGeneratedClass.h" -#include "UObject/MetaData.h" -#include "RawMesh.h" -#include "Widgets/Notifications/SNotificationList.h" -#include "Framework/Notifications/NotificationManager.h" -#include "Interfaces/IPluginManager.h" -//#include "Kismet/BlueprintEditor.h" -#include "SSCSEditor.h" -#include "Engine/WorldComposition.h" - -#if WITH_EDITOR - #include "Interfaces/IMainFrameModule.h" -#endif - -#include - -#include "AssetRegistryModule.h" -#include "FileHelpers.h" -#include "Factories/WorldFactory.h" -#include "HAL/FileManager.h" - -#if WITH_EDITOR - #include "EditorModeManager.h" - #include "EditorModes.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// HAPI_Result strings -const FString kResultStringSuccess(TEXT("Success")); -const FString kResultStringFailure(TEXT("Generic Failure")); -const FString kResultStringAlreadyInitialized(TEXT("Already Initialized")); -const FString kResultStringNotInitialized(TEXT("Not Initialized")); -const FString kResultStringCannotLoadFile(TEXT("Unable to Load File")); -const FString kResultStringParmSetFailed(TEXT("Failed Setting Parameter")); -const FString kResultStringInvalidArgument(TEXT("Invalid Argument")); -const FString kResultStringCannotLoadGeo(TEXT("Uneable to Load Geometry")); -const FString kResultStringCannotGeneratePreset(TEXT("Uneable to Generate Preset")); -const FString kResultStringCannotLoadPreset(TEXT("Uneable to Load Preset")); -const FString kResultStringAssetDefAlrealdyLoaded(TEXT("Asset definition already loaded")); -const FString kResultStringNoLicenseFound(TEXT("No License Found")); -const FString kResultStringDisallowedNCLicenseFound(TEXT("Disallowed Non Commercial License found")); -const FString kResultStringDisallowedNCAssetWithCLicense(TEXT("Disallowed Non Commercial Asset With Commercial License")); -const FString kResultStringDisallowedNCAssetWithLCLicense(TEXT("Disallowed Non Commercial Asset With Limited Commercial License")); -const FString kResultStringDisallowedLCAssetWithCLicense(TEXT("Disallowed Limited Commercial Asset With Commercial License")); -const FString kResultStringDisallowedHengineIndieWith3PartyPlugin(TEXT("Disallowed Houdini Engine Indie With 3rd Party Plugin")); -const FString kResultStringAssetInvalid(TEXT("Invalid Asset")); -const FString kResultStringNodeInvalid(TEXT("Invalid Node")); -const FString kResultStringUserInterrupted(TEXT("User Interrupt")); -const FString kResultStringInvalidSession(TEXT("Invalid Session")); -const FString kResultStringUnknowFailure(TEXT("Unknown Failure")); - -#define DebugTextLine TEXT("===================================") - -const int32 -FHoudiniEngineUtils::PackageGUIDComponentNameLength = 12; - -const int32 -FHoudiniEngineUtils::PackageGUIDItemNameLength = 8; - -const FString -FHoudiniEngineUtils::GetErrorDescription(HAPI_Result Result) -{ - if (Result == HAPI_RESULT_SUCCESS) - { - return kResultStringSuccess; - } - else - { - switch (Result) - { - case HAPI_RESULT_FAILURE: - { - return kResultStringFailure; - } - - case HAPI_RESULT_ALREADY_INITIALIZED: - { - return kResultStringAlreadyInitialized; - } - - case HAPI_RESULT_NOT_INITIALIZED: - { - return kResultStringNotInitialized; - } - - case HAPI_RESULT_CANT_LOADFILE: - { - return kResultStringCannotLoadFile; - } - - case HAPI_RESULT_PARM_SET_FAILED: - { - return kResultStringParmSetFailed; - } - - case HAPI_RESULT_INVALID_ARGUMENT: - { - return kResultStringInvalidArgument; - } - - case HAPI_RESULT_CANT_LOAD_GEO: - { - return kResultStringCannotLoadGeo; - } - - case HAPI_RESULT_CANT_GENERATE_PRESET: - { - return kResultStringCannotGeneratePreset; - } - - case HAPI_RESULT_CANT_LOAD_PRESET: - { - return kResultStringCannotLoadPreset; - } - - case HAPI_RESULT_ASSET_DEF_ALREADY_LOADED: - { - return kResultStringAssetDefAlrealdyLoaded; - } - - case HAPI_RESULT_NO_LICENSE_FOUND: - { - return kResultStringNoLicenseFound; - } - - case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: - { - return kResultStringDisallowedNCLicenseFound; - } - - case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: - { - return kResultStringDisallowedNCAssetWithCLicense; - } - - case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: - { - return kResultStringDisallowedNCAssetWithLCLicense; - } - - case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: - { - return kResultStringDisallowedLCAssetWithCLicense; - } - - case HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN: - { - return kResultStringDisallowedHengineIndieWith3PartyPlugin; - } - - case HAPI_RESULT_ASSET_INVALID: - { - return kResultStringAssetInvalid; - } - - case HAPI_RESULT_NODE_INVALID: - { - return kResultStringNodeInvalid; - } - - case HAPI_RESULT_USER_INTERRUPTED: - { - return kResultStringUserInterrupted; - } - - case HAPI_RESULT_INVALID_SESSION: - { - return kResultStringInvalidSession; - } - - default: - { - return kResultStringUnknowFailure; - } - }; - } -} - -const FString -FHoudiniEngineUtils::GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity) -{ - const HAPI_Session* SessionPtr = FHoudiniEngine::Get().GetSession(); - if (!SessionPtr) - { - // No valid session - return FString(TEXT("No valid Houdini Engine session.")); - } - - int32 StatusBufferLength = 0; - HAPI_Result Result = FHoudiniApi::GetStatusStringBufLength( - SessionPtr, status_type, verbosity, &StatusBufferLength); - - if (Result == HAPI_RESULT_INVALID_SESSION) - { - // Let FHoudiniEngine know that the sesion is now invalid to "Stop" the invalid session - // and clean things up - FHoudiniEngine::Get().OnSessionLost(); - } - - if (StatusBufferLength > 0) - { - TArray< char > StatusStringBuffer; - StatusStringBuffer.SetNumZeroed(StatusBufferLength); - FHoudiniApi::GetStatusString( - SessionPtr, status_type, &StatusStringBuffer[0], StatusBufferLength); - - return FString(UTF8_TO_TCHAR(&StatusStringBuffer[0])); - } - - return FString(TEXT("")); -} - -const FString -FHoudiniEngineUtils::GetCookResult() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_MESSAGES); -} - -const FString -FHoudiniEngineUtils::GetCookState() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); -} - -const FString -FHoudiniEngineUtils::GetErrorDescription() -{ - return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS); -} - -const FString -FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId) -{ - int32 NodeErrorLength = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeNodeCookResult( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_StatusVerbosity::HAPI_STATUSVERBOSITY_ALL, &NodeErrorLength)) - { - NodeErrorLength = 0; - } - - FString NodeError; - if (NodeErrorLength > 0) - { - TArray NodeErrorBuffer; - NodeErrorBuffer.SetNumZeroed(NodeErrorLength); - FHoudiniApi::GetComposedNodeCookResult( - FHoudiniEngine::Get().GetSession(), &NodeErrorBuffer[0], NodeErrorLength); - - NodeError = FString(UTF8_TO_TCHAR(&NodeErrorBuffer[0])); - } - - return NodeError; -} - -const FString -FHoudiniEngineUtils::GetCookLog(TArray& InHACs) -{ - FString CookLog; - - // Get fetch cook status. - FString CookResult = FHoudiniEngineUtils::GetCookResult(); - if (!CookResult.IsEmpty()) - CookLog += TEXT("Cook Results:\n") + CookResult + TEXT("\n\n"); - - // Add the cook state - FString CookState = FHoudiniEngineUtils::GetCookState(); - if (!CookState.IsEmpty()) - CookLog += TEXT("Cook State:\n") + CookState + TEXT("\n\n"); - - // Error Description - FString Error = FHoudiniEngineUtils::GetErrorDescription(); - if (!Error.IsEmpty()) - CookLog += TEXT("Error Description:\n") + Error + TEXT("\n\n"); - - // Iterates on all the selected HAC and get their node errors - for (auto& HAC : InHACs) - { - if (!HAC || HAC->IsPendingKill()) - continue; - - // Get the node errors, warnings and messages - FString NodeErrors = FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(HAC->GetAssetId()); - if (NodeErrors.IsEmpty()) - continue; - - CookLog += NodeErrors; - } - - if (CookLog.IsEmpty()) - { - // See if a failed HAPI initialization / invalid session is preventing us from getting the cook log - if (!FHoudiniApi::IsHAPIInitialized()) - { - CookLog += TEXT("\n\nThe Houdini Engine API Library (HAPI) has not been initialized properly.\n\n"); - } - else - { - const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) - { - CookLog += TEXT("\n\nThe current Houdini Engine Session is not valid.\n\n"); - } - else if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) - { - CookLog += TEXT("\n\nThe current Houdini Engine Session has not been initialized properly.\n\n"); - } - } - - if (!CookLog.IsEmpty()) - { - CookLog += TEXT("Please try to restart the current Houdini Engine session via File > Restart Houdini Engine Session.\n\n"); - } - else - { - CookLog = TEXT("\n\nThe cook log is empty...\n\n"); - } - } - - return CookLog; -} - -const FString -FHoudiniEngineUtils::GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - FString HelpString = TEXT(""); - if (!HoudiniAssetComponent) - return HelpString; - - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HAPI_NodeId AssetId = HoudiniAssetComponent->GetAssetId(); - if (AssetId < 0) - return HelpString; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), HelpString); - - if (!FHoudiniEngineString::ToFString(AssetInfo.helpTextSH, HelpString)) - return HelpString; - - if (!FHoudiniEngineString::ToFString(AssetInfo.helpURLSH, HelpString)) - return HelpString; - - if (HelpString.IsEmpty()) - HelpString = TEXT("No Asset Help Found"); - - return HelpString; -} - -void -FHoudiniEngineUtils::ConvertUnrealString(const FString & UnrealString, std::string & String) -{ - String = TCHAR_TO_UTF8(*UnrealString); -} - -UWorld* -FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreateMissingPackage, bool& bOutCreatedPackage) -{ - AActor* Result = nullptr; - UWorld* PackageWorld = nullptr; - - bOutCreatedPackage = false; - - // Try to load existing UWorld from the tile package path. - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!Package) - Package = LoadPackage(nullptr, *PackagePath, LOAD_None); - if (Package) - { - // If the package is not valid (pending kill) rename it - if (Package->IsPendingKill()) - { - if (bCreateMissingPackage) - { - Package->Rename( - *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); - } - } - else - { - PackageWorld = UWorld::FindWorldInPackage(Package); - } - } - - if (!IsValid(PackageWorld) && bCreateMissingPackage) - { - // The map for this tile does not exist. Create one - UWorldFactory* Factory = NewObject(); - Factory->WorldType = EWorldType::Inactive; // World that is being loaded but not currently edited by editor. - PackageWorld = CastChecked(Factory->FactoryCreateNew(UWorld::StaticClass(), Package, NAME_None, RF_Public | RF_Standalone, NULL, GWarn)); - - if (IsValid(PackageWorld)) - { - PackageWorld->PostEditChange(); - PackageWorld->MarkPackageDirty(); - - if(FPackageName::IsValidLongPackageName(PackagePath)) - { - const FString PackageFilename = FPackageName::LongPackageNameToFilename(PackagePath); - bool bSaved = FEditorFileUtils::SaveLevel(PackageWorld->PersistentLevel, *PackageFilename); - } - - FAssetRegistryModule::AssetCreated(PackageWorld); - - bOutCreatedPackage = true; - } - } - - return PackageWorld; -} - -bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - UWorld* CurrentWorld, - const FString& PackagePath, - bool bCreateMissingPackage, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bOutPackageCreated, - bool& bPackageInWorld) -{ - UWorld* PackageWorld = FindWorldInPackage(PackagePath, bCreateMissingPackage, bOutPackageCreated); - if (!IsValid(PackageWorld)) - return false; - - if (PackageWorld->PersistentLevel == CurrentWorld->PersistentLevel) - { - // The loaded world and the package world is one and the same. - OutWorld = CurrentWorld; - OutLevel = CurrentWorld->PersistentLevel; - bPackageInWorld = true; - return true; - } - - if (CurrentWorld->GetLevels().Contains(PackageWorld->PersistentLevel)) - { - // The package level is loaded into CurrentWorld. - OutWorld = CurrentWorld; - OutLevel = PackageWorld->PersistentLevel; - bPackageInWorld = true; - return true; - } - - // The package level is not loaded at all. Send back the on-disk assets. - OutWorld = PackageWorld; - OutLevel = PackageWorld->PersistentLevel; - bPackageInWorld = false; - return true; -} - -void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) -{ - FString WorldPath = FPaths::GetPath(InWorld->GetPathName()); - IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); - TArray Packages; - Packages.Add(WorldPath); - AssetRegistry.ScanPathsSynchronous(Packages, true); -} - -AActor* FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) -{ - // AActor* NamedActor = FindObject(Outer, *InName, false); - // Find ANY actor in the world matching the given name. - AActor* NamedActor = FindActorInWorld(InWorld, FName(InName)); - OutFoundActor = NamedActor; - bool bShouldRename = false; - if (NamedActor) - { - if (NamedActor->GetClass()->IsChildOf(InClass) && !NamedActor->IsPendingKill()) - { - return NamedActor; - } - else - { - FString Suffix; - bool bShouldUpdateLabel = false; - if (NamedActor->IsPendingKill()) - Suffix = "_pendingkill"; - else - Suffix = "_0"; // A previous actor that had the same name. - const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName+Suffix); - } - } - return nullptr; -} - -void FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) -{ - LogPackageInfo( LoadPackage(nullptr, *InLongPackageName, 0) ); -} - -void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) -{ - HOUDINI_LOG_MESSAGE(DebugTextLine); - HOUDINI_LOG_MESSAGE(TEXT("= LogPackageInfo")); - if (!IsValid(InPackage)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Invalid package.")); - HOUDINI_LOG_MESSAGE(DebugTextLine); - return; - } - - HOUDINI_LOG_MESSAGE(TEXT(" = Filename: %s"), *(InPackage->FileName.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = Package Id: %d"), InPackage->GetPackageId().ValueForDebugging()); - HOUDINI_LOG_MESSAGE(TEXT(" = File size: %d"), InPackage->FileSize); - HOUDINI_LOG_MESSAGE(TEXT(" = Contains map: %d"), InPackage->ContainsMap()); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Fully Loaded: %d"), InPackage->IsFullyLoaded()); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Dirty: %d"), InPackage->IsDirty()); - - if (InPackage->WorldTileInfo.IsValid()) - { - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Position: %s"), *(InPackage->WorldTileInfo->Position.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Absolute Position: %s"), *(InPackage->WorldTileInfo->AbsolutePosition.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Bounds: %s"), *(InPackage->WorldTileInfo->Bounds.ToString())); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - HidInTileView: %d"), InPackage->WorldTileInfo->bHideInTileView); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - ZOrder: %d"), InPackage->WorldTileInfo->ZOrder); - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Parent tile package: %s"), *(InPackage->WorldTileInfo->ParentTilePackageName)); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo: NULL")); - } - - HOUDINI_LOG_MESSAGE(DebugTextLine); -} - -void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) -{ - UPackage* Package = LoadPackage(nullptr, *InLongPackageName, 0); - UWorld* World = nullptr; - - if (IsValid(Package)) - { - World = UWorld::FindWorldInPackage(Package); - } - - LogWorldInfo(World); -} - -void FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) -{ - - HOUDINI_LOG_MESSAGE(DebugTextLine); - HOUDINI_LOG_MESSAGE(TEXT("= LogWorldInfo")); - if (!IsValid(InWorld)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Invalid world.")); - HOUDINI_LOG_MESSAGE(DebugTextLine); - return; - } - - // UWorld lacks const-correctness on certain accessors - UWorld* NonConstWorld = const_cast(InWorld); - - HOUDINI_LOG_MESSAGE(TEXT(" = Path Name: %s"), *(InWorld->GetPathName())); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Editor World: %d"), InWorld->IsEditorWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Game World: %d"), InWorld->IsGameWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Is Preview World: %d"), InWorld->IsPreviewWorld() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Actor Count: %d"), NonConstWorld->GetActorCount() ); - HOUDINI_LOG_MESSAGE(TEXT(" = Num Levels: %d"), InWorld->GetNumLevels() ); - - if (IsValid(InWorld->WorldComposition)) - { - HOUDINI_LOG_MESSAGE(TEXT(" = Composition - Num Tiles: %d"), InWorld->WorldComposition->GetTilesList().Num() ); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT(" = World Composition NULL") ); - } - - - - HOUDINI_LOG_MESSAGE(DebugTextLine); -} - -FString -FHoudiniEngineUtils::HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType) -{ - switch (InEventType) - { - case HAPI_PDG_EVENT_NULL: - return TEXT("HAPI_PDG_EVENT_NULL"); - - case HAPI_PDG_EVENT_WORKITEM_ADD: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE"); - case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_DEP"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_PARENT"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT"); - - case HAPI_PDG_EVENT_NODE_CLEAR: - return TEXT("HAPI_PDG_EVENT_NODE_CLEAR"); - - case HAPI_PDG_EVENT_COOK_ERROR: - return TEXT("HAPI_PDG_EVENT_COOK_ERROR"); - case HAPI_PDG_EVENT_COOK_WARNING: - return TEXT("HAPI_PDG_EVENT_COOK_WARNING"); - - case HAPI_PDG_EVENT_COOK_COMPLETE: - return TEXT("HAPI_PDG_EVENT_COOK_COMPLETE"); - - case HAPI_PDG_EVENT_DIRTY_START: - return TEXT("HAPI_PDG_EVENT_DIRTY_START"); - case HAPI_PDG_EVENT_DIRTY_STOP: - return TEXT("HAPI_PDG_EVENT_DIRTY_STOP"); - - case HAPI_PDG_EVENT_DIRTY_ALL: - return TEXT("HAPI_PDG_EVENT_DIRTY_ALL"); - - case HAPI_PDG_EVENT_UI_SELECT: - return TEXT("HAPI_PDG_EVENT_UI_SELECT"); - - case HAPI_PDG_EVENT_NODE_CREATE: - return TEXT("HAPI_PDG_EVENT_NODE_CREATE"); - case HAPI_PDG_EVENT_NODE_REMOVE: - return TEXT("HAPI_PDG_EVENT_NODE_REMOVE"); - case HAPI_PDG_EVENT_NODE_RENAME: - return TEXT("HAPI_PDG_EVENT_NODE_RENAME"); - case HAPI_PDG_EVENT_NODE_CONNECT: - return TEXT("HAPI_PDG_EVENT_NODE_CONNECT"); - case HAPI_PDG_EVENT_NODE_DISCONNECT: - return TEXT("HAPI_PDG_EVENT_NODE_DISCONNECT"); - - case HAPI_PDG_EVENT_WORKITEM_SET_INT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_INT"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FLOAT"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_STRING: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_STRING"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_FILE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FILE"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: - return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_MERGE: - return TEXT("HAPI_PDG_EVENT_WORKITEM_MERGE"); // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_RESULT: - return TEXT("HAPI_PDG_EVENT_WORKITEM_RESULT"); - - case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED - return TEXT("HAPI_PDG_EVENT_WORKITEM_PRIORITY"); - - case HAPI_PDG_EVENT_COOK_START: - return TEXT("HAPI_PDG_EVENT_COOK_START"); - - case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: - return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR"); - case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: - return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR"); - - case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: - return TEXT("HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE"); - - case HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED: - return TEXT("HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED"); - - case HAPI_PDG_EVENT_ALL: - return TEXT("HAPI_PDG_EVENT_ALL"); - case HAPI_PDG_EVENT_LOG: - return TEXT("HAPI_PDG_EVENT_LOG"); - - case HAPI_PDG_EVENT_SCHEDULER_ADDED: - return TEXT("HAPI_PDG_EVENT_SCHEDULER_ADDED"); - case HAPI_PDG_EVENT_SCHEDULER_REMOVED: - return TEXT("HAPI_PDG_EVENT_SCHEDULER_REMOVED"); - case HAPI_PDG_EVENT_SET_SCHEDULER: - return TEXT("HAPI_PDG_EVENT_SET_SCHEDULER"); - - case HAPI_PDG_EVENT_SERVICE_MANAGER_ALL: - return TEXT("HAPI_PDG_EVENT_SERVICE_MANAGER_ALL"); - - case HAPI_PDG_CONTEXT_EVENTS: - return TEXT("HAPI_PDG_CONTEXT_EVENTS"); - default: - break; - } - - return FString::Printf(TEXT("Unknown HAPI_PDG_EventType %d"), InEventType); -} - -FString -FHoudiniEngineUtils::HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState) -{ - switch (InWorkitemState) - { - case HAPI_PDG_WORKITEM_UNDEFINED: - return TEXT("HAPI_PDG_WORKITEM_UNDEFINED"); - case HAPI_PDG_WORKITEM_UNCOOKED: - return TEXT("HAPI_PDG_WORKITEM_UNCOOKED"); - case HAPI_PDG_WORKITEM_WAITING: - return TEXT("HAPI_PDG_WORKITEM_WAITING"); - case HAPI_PDG_WORKITEM_SCHEDULED: - return TEXT("HAPI_PDG_WORKITEM_SCHEDULED"); - case HAPI_PDG_WORKITEM_COOKING: - return TEXT("HAPI_PDG_WORKITEM_COOKING"); - case HAPI_PDG_WORKITEM_COOKED_SUCCESS: - return TEXT("HAPI_PDG_WORKITEM_COOKED_SUCCESS"); - case HAPI_PDG_WORKITEM_COOKED_CACHE: - return TEXT("HAPI_PDG_WORKITEM_COOKED_CACHE"); - case HAPI_PDG_WORKITEM_COOKED_FAIL: - return TEXT("HAPI_PDG_WORKITEM_COOKED_FAIL"); - case HAPI_PDG_WORKITEM_COOKED_CANCEL: - return TEXT("HAPI_PDG_WORKITEM_COOKED_CANCEL"); - case HAPI_PDG_WORKITEM_DIRTY: - return TEXT("HAPI_PDG_WORKITEM_DIRTY"); - default: - break; - } - - return FString::Printf(TEXT("Unknown HAPI_PDG_WorkitemState %d"), InWorkitemState); -} - - -// Centralized call to track renaming of objects -bool FHoudiniEngineUtils::RenameObject(UObject* Object, const TCHAR* NewName /*= nullptr*/, UObject* NewOuter /*= nullptr*/, ERenameFlags Flags /*= REN_None*/) -{ - check(Object); - if (AActor* Actor = Cast(Object)) - { - if (Actor->IsPackageExternal()) - { - // There should be no need to choose a specific name for an actor in Houdini Engine, instead setting its label should be enough. - if (FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, NewName)) - { - HOUDINI_LOG_WARNING(TEXT("Called SetActorLabel(%s) on external actor %s instead of Rename : Explicit naming of an actor that is saved in its own external package is prone to cause name clashes when submitting the file.)"), NewName, *Actor->GetName()); - } - // Force to return false (make sure nothing in Houdini Engine plugin relies on actor being renamed to provided name) - return false; - } - } - return Object->Rename(NewName, NewOuter, Flags); -} - -FName -FHoudiniEngineUtils::RenameToUniqueActor(AActor* InActor, const FString& InName) -{ - const FName NewName = MakeUniqueObjectName(InActor->GetOuter(), InActor->GetClass(), FName(InName)); - - FHoudiniEngineUtils::RenameObject(InActor, *(NewName.ToString())); - FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName.ToString()); - - return NewName; -} - -UObject* -FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) -{ - check(InActor); - - UObject* PrevObj = nullptr; - UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, InActor->GetOuter(), *InName, true); - if (ExistingObject && ExistingObject != InActor) - { - // Rename the existing object - const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName + TEXT("_old"))); - FHoudiniEngineUtils::RenameObject(ExistingObject, *(NewName.ToString())); - PrevObj = ExistingObject; - } - - FHoudiniEngineUtils::RenameObject(InActor, *InName); - - if (UpdateLabel) - { - //InActor->SetActorLabel(InName, true); - FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, InName); - InActor->Modify(true); - } - - return PrevObj; -} - -void -FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - FHoudiniPackageParams& OutPackageParams, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FString &BakeFolder, - const FString &ObjectName, - const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode, - bool bAutomaticallySetAttemptToLoadMissingPackages) -{ - OutPackageParams.GeoId = InIdentifier.GeoId; - OutPackageParams.ObjectId = InIdentifier.ObjectId; - OutPackageParams.PartId = InIdentifier.PartId; - OutPackageParams.BakeFolder = BakeFolder; - OutPackageParams.PackageMode = EPackageMode::Bake; - OutPackageParams.ReplaceMode = InReplaceMode; - OutPackageParams.HoudiniAssetName = HoudiniAssetName; - OutPackageParams.HoudiniAssetActorName = HoudiniAssetName; - OutPackageParams.ObjectName = ObjectName; -} - -void -FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - UWorld* const InWorldContext, - const UHoudiniAssetComponent* HoudiniAssetComponent, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FHoudiniOutputObject& InOutputObject, - const FString &InDefaultObjectName, - const FString &InHoudiniAssetName, - FHoudiniPackageParams& OutPackageParams, - FHoudiniAttributeResolver& OutResolver, - const FString &InDefaultBakeFolder, - EPackageReplaceMode InReplaceMode, - bool bAutomaticallySetAttemptToLoadMissingPackages, - bool bInSkipObjectNameResolutionAndUseDefault, - bool bInSkipBakeFolderResolutionAndUseDefault) -{ - // Configure OutPackageParams with the default (UI value first then fallback to default from settings) object name - // and bake folder. We use the "initial" PackageParams as a helper to populate tokens for the resolver. - // - // User specified attributes (eg unreal_bake_folder) are then resolved, with the defaults being those tokens configured - // from the initial PackageParams. Once resolved, we updated the relevant fields in PackageParams - // (ObjectName and BakeFolder), and update the resolver tokens with these final values. - // - // The resolver is then ready to be used to resolve the rest of the user attributes, such as unreal_level_path. - // - const FString DefaultBakeFolder = !InDefaultBakeFolder.IsEmpty() ? InDefaultBakeFolder : - FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - const bool bHasBakeNameUIOverride = !InOutputObject.BakeName.IsEmpty(); - FillInPackageParamsForBakingOutput( - OutPackageParams, - InIdentifier, - DefaultBakeFolder, - bHasBakeNameUIOverride ? InOutputObject.BakeName : InDefaultObjectName, - InHoudiniAssetName, - InReplaceMode, - bAutomaticallySetAttemptToLoadMissingPackages); - - const TMap& CachedAttributes = InOutputObject.CachedAttributes; - TMap Tokens = InOutputObject.CachedTokens; - OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); - OutResolver.SetCachedAttributes(CachedAttributes); - OutResolver.SetTokensFromStringMap(Tokens); - -#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING - // Log the cached attributes and tokens for debugging - OutResolver.LogCachedAttributesAndTokens(); -#endif - - if (!bInSkipObjectNameResolutionAndUseDefault) - { - // Resolve the object name - // TODO: currently the UI override is checked first (this should probably change so that attributes are used first) - FString ObjectName; - if (bHasBakeNameUIOverride) - { - ObjectName = InOutputObject.BakeName; - } - else - { - ObjectName = OutResolver.ResolveOutputName(); - if (ObjectName.IsEmpty()) - ObjectName = InDefaultObjectName; - } - // Update the object name in the package params and also update its token - OutPackageParams.ObjectName = ObjectName; - OutResolver.SetToken("object_name", OutPackageParams.ObjectName); - } - - if (!bInSkipBakeFolderResolutionAndUseDefault) - { - // Now resolve the bake folder - const FString BakeFolder = OutResolver.ResolveBakeFolder(); - if (!BakeFolder.IsEmpty()) - OutPackageParams.BakeFolder = BakeFolder; - } - - if (!bInSkipObjectNameResolutionAndUseDefault || !bInSkipBakeFolderResolutionAndUseDefault) - { - // Update the tokens from the package params - OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); - OutResolver.SetTokensFromStringMap(Tokens); - -#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING - // Log the final tokens - OutResolver.LogCachedAttributesAndTokens(); -#endif - } -} - - -bool -FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() -{ - // Update / repopulate the foliage editor mode's mesh list if the foliage editor mode is active. - // TODO: find a better way to do this, the relevant functions are in FEdModeFoliage and FFoliageEdModeToolkit are not API exported - FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); - if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) - { - EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); - EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); - return true; - } - - return false; -} - -void -FHoudiniEngineUtils::GatherLandscapeInputs( - UHoudiniAssetComponent* HAC, - TArray& AllInputLandscapes, - TArray& InputLandscapesToUpdate) -{ - if (!IsValid(HAC)) - return; - - int32 NumInputs = HAC->GetNumInputs(); - - for (int32 InputIndex = 0; InputIndex < NumInputs; InputIndex++ ) - { - UHoudiniInput* CurrentInput = HAC->GetInputAt(InputIndex); - if (!CurrentInput) - continue; - - if (CurrentInput->GetInputType() == EHoudiniInputType::World) - { - // Check if we have any landscapes as world inputs. - CurrentInput->ForAllHoudiniInputObjects([&AllInputLandscapes](UHoudiniInputObject* InputObject) - { - UHoudiniInputLandscape* InputLandscape = Cast(InputObject); - if (InputLandscape) - { - ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); - if (IsValid(LandscapeProxy)) - { - AllInputLandscapes.Add(LandscapeProxy); - } - } - }); - } - - if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) - continue; - - // Get the landscape input's landscape - ALandscapeProxy* InputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); - if (!InputLandscape) - continue; - - AllInputLandscapes.Add(InputLandscape); - - if (CurrentInput->GetUpdateInputLandscape()) - InputLandscapesToUpdate.Add(InputLandscape); - } -} - - -UHoudiniAssetComponent* -FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(const UObject* Obj) -{ - if (!IsValid(Obj)) - return nullptr; - - // Check the direct Outer - UHoudiniAssetComponent* OuterHAC = Cast(Obj->GetOuter()); - if(IsValid(OuterHAC)) - return OuterHAC; - - // Check the whole outer chain - OuterHAC = Obj->GetTypedOuter(); - if (IsValid(OuterHAC)) - return OuterHAC; - - // Finally check if the Object itself is a HaC - UObject* NonConstObj = const_cast(Obj); - OuterHAC = Cast(NonConstObj); - if (IsValid(OuterHAC)) - return OuterHAC; - - return nullptr; -} - - -FString -FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) -{ - // Compute Houdini version string. - FString HoudiniVersionString = FString::Printf( - TEXT("%d.%d.%s%d"), HAPI_VERSION_HOUDINI_MAJOR, - HAPI_VERSION_HOUDINI_MINOR, - (ExtraDigit ? (TEXT("0.")) : TEXT("")), - HAPI_VERSION_HOUDINI_BUILD); - - // If we have a patch version, we need to append it. - if (HAPI_VERSION_HOUDINI_PATCH > 0) - HoudiniVersionString = FString::Printf(TEXT("%s.%d"), *HoudiniVersionString, HAPI_VERSION_HOUDINI_PATCH); - return HoudiniVersionString; -} - - -void * -FHoudiniEngineUtils::LoadLibHAPI(FString & StoredLibHAPILocation) -{ - FString HFSPath = TEXT(""); - void * HAPILibraryHandle = nullptr; - - // Look up HAPI_PATH environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . - FString HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HAPI_PATH")); - if (!HFS_ENV_VAR.IsEmpty()) - HFSPath = HFS_ENV_VAR; - - // Look up environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . - HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HFS")); - if (!HFS_ENV_VAR.IsEmpty()) - HFSPath = HFS_ENV_VAR; - - // Get platform specific name of libHAPI. - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - - // If we have a custom location specified through settings, attempt to use that. - bool bCustomPathFound = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->bUseCustomHoudiniLocation) - { - // Create full path to libHAPI binary. - FString CustomHoudiniLocationPath = HoudiniRuntimeSettings->CustomHoudiniLocation.Path; - if (!CustomHoudiniLocationPath.IsEmpty()) - { - // Convert path to absolute if it is relative. - if (FPaths::IsRelative(CustomHoudiniLocationPath)) - CustomHoudiniLocationPath = FPaths::ConvertRelativePathToFull(CustomHoudiniLocationPath); - - FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPICustomPath)) - { - HFSPath = CustomHoudiniLocationPath; - bCustomPathFound = true; - } - } - } - - // We have HFS environment variable defined (or custom location), attempt to load libHAPI from it. - if (!HFSPath.IsEmpty()) - { - if (!bCustomPathFound) - { -#if PLATFORM_WINDOWS - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_WINDOWS); -#elif PLATFORM_MAC - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_MAC); -#elif PLATFORM_LINUX - HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_LINUX); -#endif - } - - // Create full path to libHAPI binary. - FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - // libHAPI binary exists at specified location, attempt to load it. - FPlatformProcess::PushDllDirectory(*HFSPath); -#if PLATFORM_WINDOWS - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); -#elif PLATFORM_MAC || PLATFORM_LINUX - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); -#endif - FPlatformProcess::PopDllDirectory(*HFSPath); - - // If library has been loaded successfully we can stop. - if ( HAPILibraryHandle ) - { - if (bCustomPathFound) - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from custom path %s"), *LibHAPIName, *HFSPath); - else - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from HFS environment path %s"), *LibHAPIName, *HFSPath); - - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - } - - // Otherwise, we will attempt to detect Houdini installation. - FString HoudiniLocation = TEXT(HOUDINI_ENGINE_HFS_PATH); - FString LibHAPIPath; - - // Compute Houdini version string. - FString HoudiniVersionString = ComputeVersionString(false); - -#if PLATFORM_WINDOWS - - // On Windows, we have also hardcoded HFS path in plugin configuration file; attempt to load from it. - HFSPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, HAPI_HFS_SUBFOLDER_WINDOWS); - - // Create full path to libHAPI binary. - LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HFSPath); - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); - FPlatformProcess::PopDllDirectory(*HFSPath); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from Plugin defined HFS path %s"), *LibHAPIName, *HFSPath); - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - - // As a second attempt, on Windows, we try to look up location of Houdini Engine in the registry. - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini Engine"), StoredLibHAPILocation, false); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // As a third attempt, we try to look up location of Houdini installation (not Houdini Engine) in the registry. - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini"), StoredLibHAPILocation, false); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // Do similar registry lookups for the 32 bits registry - // Look for the Houdini Engine registry install path - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini Engine"), StoredLibHAPILocation, true); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // ... and for the Houdini registry install path - HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( - TEXT("Houdini"), StoredLibHAPILocation, true); - if (HAPILibraryHandle) - return HAPILibraryHandle; - - // Finally, try to load from a hardcoded program files path. - HoudiniLocation = FString::Printf( - TEXT("C:\\Program Files\\Side Effects Software\\Houdini %s\\%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_WINDOWS); - -#else - -# if PLATFORM_MAC - - // Attempt to load from standard Mac OS X installation. - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/Current/Libraries"), *HoudiniVersionString); - - // Fallback in case the previous one doesnt exist - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/%s/Libraries"), *HoudiniVersionString, *HoudiniVersionString); - - // Fallback in case we're using the steam version - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Applications/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); - - // Backup Fallback in case we're using the steam version - // (this could probably be removed as paths have changed) - if (!FPaths::DirectoryExists(HoudiniLocation)) - HoudiniLocation = FString::Printf( - TEXT("/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); - -# elif PLATFORM_LINUX - - // Attempt to load from standard Linux installation. - HoudiniLocation = FString::Printf( - TEXT("/opt/hfs%s/%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_LINUX); - -# endif - -#endif - - // Create full path to libHAPI binary. - LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, *LibHAPIName); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HoudiniLocation); - HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); - FPlatformProcess::PopDllDirectory(*HoudiniLocation); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from expected installation %s"), *LibHAPIName, *HoudiniLocation); - StoredLibHAPILocation = HoudiniLocation; - return HAPILibraryHandle; - } - } - - StoredLibHAPILocation = TEXT(""); - return HAPILibraryHandle; -} - -bool -FHoudiniEngineUtils::IsInitialized() -{ - if (!FHoudiniApi::IsHAPIInitialized()) - return false; - - const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) - return false; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) - return false; - - return true; -} - -bool -FHoudiniEngineUtils::IsHoudiniNodeValid(const HAPI_NodeId& NodeId) -{ - if (NodeId < 0) - return false; - - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - bool ValidationAnswer = 0; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) - { - return false; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsNodeValid( - FHoudiniEngine::Get().GetSession(), NodeId, - NodeInfo.uniqueHoudiniNodeId, &ValidationAnswer)) - { - return false; - } - - return ValidationAnswer; -} - -bool -FHoudiniEngineUtils::HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex) -{ - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), HostAssetId, InputIndex), false); - - return true; -} - -bool -FHoudiniEngineUtils::DestroyHoudiniAsset(const HAPI_NodeId& AssetId) -{ - if (HAPI_RESULT_SUCCESS == FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), AssetId)) - { - return true; - } - - return false; -} - -#if PLATFORM_WINDOWS -void * -FHoudiniEngineUtils::LocateLibHAPIInRegistry( - const FString & HoudiniInstallationType, - FString & StoredLibHAPILocation, - bool LookIn32bitRegistry) -{ - auto FindDll = [&](const FString& InHoudiniInstallationPath) - { - FString HFSPath = FString::Printf(TEXT("%s/%s"), *InHoudiniInstallationPath, HAPI_HFS_SUBFOLDER_WINDOWS); - - // Create full path to libHAPI binary. - FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, HAPI_LIB_OBJECT_WINDOWS); - - if (FPaths::FileExists(LibHAPIPath)) - { - FPlatformProcess::PushDllDirectory(*HFSPath); - void* HAPILibraryHandle = FPlatformProcess::GetDllHandle(HAPI_LIB_OBJECT_WINDOWS); - FPlatformProcess::PopDllDirectory(*HFSPath); - - if (HAPILibraryHandle) - { - HOUDINI_LOG_MESSAGE( - TEXT("Loaded %s from Registry path %s"), HAPI_LIB_OBJECT_WINDOWS, - *HFSPath); - - StoredLibHAPILocation = HFSPath; - return HAPILibraryHandle; - } - } - return (void*)0; - }; - - FString HoudiniInstallationPath; - FString HoudiniVersionString = ComputeVersionString(true); - FString RegistryKey = FString::Printf( - TEXT("Software\\%sSide Effects Software\\%s"), - (LookIn32bitRegistry ? TEXT("WOW6432Node\\") : TEXT("")), *HoudiniInstallationType); - - if (FWindowsPlatformMisc::QueryRegKey( - HKEY_LOCAL_MACHINE, *RegistryKey, *HoudiniVersionString, HoudiniInstallationPath)) - { - FPaths::NormalizeDirectoryName(HoudiniInstallationPath); - return FindDll(HoudiniInstallationPath); - } - - return nullptr; -} -#endif - -bool -FHoudiniEngineUtils::LoadHoudiniAsset(const UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId& OutAssetLibraryId) -{ - OutAssetLibraryId = -1; - - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return false; - - if (!FHoudiniEngineUtils::IsInitialized()) - { - // If we're not initialized now, it likely means the session has been lost - FHoudiniEngine::Get().OnSessionLost(); - return false; - } - - // Get the preferences - bool bMemoryCopyFirst = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bMemoryCopyFirst = HoudiniRuntimeSettings->bPreferHdaMemoryCopyOverHdaSourceFile; - - // Get the HDA's file path - // We need to convert relative file path to absolute - FString AssetFileName = HoudiniAsset->GetAssetFileName(); - if (FPaths::IsRelative(AssetFileName)) - AssetFileName = FPaths::ConvertRelativePathToFull(AssetFileName); - - // We need to modify the file name for expanded .hdas - FString FileExtension = FPaths::GetExtension(AssetFileName); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) - { - // the .hda directory is what we should be loading - AssetFileName = FPaths::GetPath(AssetFileName); - } - - //Check whether we can Load from file/memory - bool bCanLoadFromMemory = (!HoudiniAsset->IsExpandedHDA() && HoudiniAsset->GetAssetBytesCount() > 0); - - // If the hda file exists, we can simply load it directly - bool bCanLoadFromFile = false; - if ( !AssetFileName.IsEmpty() ) - { - if (FPaths::FileExists(AssetFileName) - || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName))) - { - bCanLoadFromFile = true; - } - } - - HAPI_Result Result = HAPI_RESULT_FAILURE; - - // Lambda to detect license issues - auto CheckLicenseValid = [&AssetFileName](const HAPI_Result& Result) - { - // HoudiniEngine acquires a license when creating/loading a node, not when creating a session - if (Result >= HAPI_RESULT_NO_LICENSE_FOUND && Result < HAPI_RESULT_ASSET_INVALID) - { - FString ErrorDesc = GetErrorDescription(Result); - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: License failed: %s."), *AssetFileName, *ErrorDesc); - - // We must stop the session to prevent further attempts at loading an HDA - // as this could lead to unreal becoming stuck and unresponsive due to license timeout - FHoudiniEngine::Get().StopSession(); - - // Set the HE status to "no license" - FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); - - return false; - } - else - { - return true; - } - }; - - // Lambda to load an HDA from file - auto LoadAssetFromFile = [&Result, &OutAssetLibraryId](const FString& InAssetFileName) - { - // Load the asset from file. - std::string AssetFileNamePlain; - FHoudiniEngineUtils::ConvertUnrealString(InAssetFileName, AssetFileNamePlain); - Result = FHoudiniApi::LoadAssetLibraryFromFile( - FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); - - }; - - // Lambda to load an HDA from memory - auto LoadAssetFromMemory = [&Result, &OutAssetLibraryId](const UHoudiniAsset* InHoudiniAsset) - { - // Load the asset from the cached memory buffer - Result = FHoudiniApi::LoadAssetLibraryFromMemory( - FHoudiniEngine::Get().GetSession(), - reinterpret_cast(InHoudiniAsset->GetAssetBytes()), - InHoudiniAsset->GetAssetBytesCount(), - true, - &OutAssetLibraryId); - }; - - if (!bMemoryCopyFirst) - { - // Load from File first - if (bCanLoadFromFile) - { - LoadAssetFromFile(AssetFileName); - - // Detect license issues when loading the HDA - if (!CheckLicenseValid(Result)) - return false; - } - - // If we failed to load from file ... - if (Result != HAPI_RESULT_SUCCESS) - { - // ... warn the user that we will be loading from memory. - HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from Memory: source asset file not found."), *AssetFileName); - - // Attempt to load from memory - if (bCanLoadFromMemory) - { - LoadAssetFromMemory(HoudiniAsset); - - // Detect license issues when loading the HDA - if (!CheckLicenseValid(Result)) - return false; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); - return false; - } - } - } - else - { - // Load from Memory first - if(bCanLoadFromMemory) - { - LoadAssetFromMemory(HoudiniAsset); - - // Detect license issues when loading the HDA - if (!CheckLicenseValid(Result)) - return false; - } - - // If we failed to load from memory ... - if (Result != HAPI_RESULT_SUCCESS) - { - // ... warn the user that we will be loading from file - HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from File: no memory copy available."), *AssetFileName); - - // Attempt to load from file - if (bCanLoadFromFile) - { - LoadAssetFromFile(AssetFileName); - - // Detect license issues when loading the HDA - if (!CheckLicenseValid(Result)) - return false; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); - return false; - } - } - } - - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_MESSAGE(TEXT("Error loading asset library for %s: %s"), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - return true; -} - -bool -FHoudiniEngineUtils::GetSubAssetNames( - const HAPI_AssetLibraryId& AssetLibraryId, - TArray< HAPI_StringHandle >& OutAssetNames) -{ - if (AssetLibraryId < 0) - return false; - - int32 AssetCount = 0; - HAPI_Result Result = HAPI_RESULT_FAILURE; - Result = FHoudiniApi::GetAvailableAssetCount(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetCount); - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Error getting asset count: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - if (AssetCount <= 0) - { - HOUDINI_LOG_ERROR(TEXT("Could not find an asset.")); - return false; - } - - OutAssetNames.SetNumUninitialized(AssetCount); - Result = FHoudiniApi::GetAvailableAssets(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &OutAssetNames[0], AssetCount); - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Unable to retrieve sub asset names: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - if (!AssetCount) - { - HOUDINI_LOG_ERROR(TEXT("No assets found")); - return false; - } - - return true; -} - - -bool -FHoudiniEngineUtils::OpenSubassetSelectionWindow(TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ) -{ - OutPickedAssetName = -1; - - if (AssetNames.Num() <= 0) - return false; - - // Default to the first asset - OutPickedAssetName = AssetNames[0]; - -#if WITH_EDITOR - // Present the user with a dialog for choosing which asset to instantiate. - TSharedPtr ParentWindow; - if (FModuleManager::Get().IsModuleLoaded("MainFrame")) - { - // Check if the main frame is loaded. When using the old main frame it may not be. - IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); - ParentWindow = MainFrame.GetParentWindow(); - } - - if (!ParentWindow.IsValid()) - { - return false; - } - - TSharedPtr AssetSelectionWidget; - TSharedRef 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()) - { - return false; - } - - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - - int32 DialogPickedAssetName = AssetSelectionWidget->GetSelectedAssetName(); - if (DialogPickedAssetName != -1) - { - OutPickedAssetName = DialogPickedAssetName; - return true; - } - else - { - return false; - } -#endif - - return true; -} - -/* -bool -FHoudiniEngineUtils::IsValidNodeId(HAPI_NodeId NodeId) -{ - return NodeId != -1; -} -*/ - -bool -FHoudiniEngineUtils::GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString) -{ - HAPI_AssetInfo AssetInfo; - if (FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo) == HAPI_RESULT_SUCCESS) - { - FHoudiniEngineString HoudiniEngineString(AssetInfo.nameSH); - return HoudiniEngineString.ToFString(NameString); - } - - return false; -} - -bool -FHoudiniEngineUtils::GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer) -{ - PresetBuffer.Empty(); - - HAPI_NodeId NodeId; - HAPI_AssetInfo AssetInfo; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo)) - { - NodeId = AssetInfo.nodeId; - } - else - NodeId = AssetNodeId; - - int32 BufferLength = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPresetBufLength( - FHoudiniEngine::Get().GetSession(), NodeId, - HAPI_PRESETTYPE_BINARY, NULL, &BufferLength), false); - - PresetBuffer.SetNumZeroed(BufferLength); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPreset( - FHoudiniEngine::Get().GetSession(), NodeId, - &PresetBuffer[0], PresetBuffer.Num()), false); - - return true; -} - - -bool -FHoudiniEngineUtils::HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath) -{ - // Retrieve Path to the given Node, relative to the other given Node - if ((InNodeId < 0) || (InRelativeToNodeId < 0)) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InNodeId)) - return false; - - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodePath( - FHoudiniEngine::Get().GetSession(), - InNodeId, InRelativeToNodeId, &StringHandle)) - { - if(FHoudiniEngineString::ToFString(StringHandle, OutPath)) - { - return true; - } - } - return false; -} - -bool -FHoudiniEngineUtils::HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath) -{ - // Do the HAPI query only on first-use - if (!InHGPO.NodePath.IsEmpty()) - return true; - - FString NodePathTemp; - if (InHGPO.AssetId == InHGPO.GeoId) - { - // This is a SOP asset, just return the asset name in this case - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InHGPO.AssetId, &AssetInfo)) - { - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo)) - { - if (FHoudiniEngineString::ToFString(AssetNodeInfo.nameSH, NodePathTemp)) - { - OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); - } - } - } - } - else - { - // This is an OBJ asset, return the path to this geo relative to the asset - if (FHoudiniEngineUtils::HapiGetNodePath(InHGPO.GeoId, InHGPO.AssetId, NodePathTemp)) - { - OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); - } - } - - /*if (OutPath.IsEmpty()) - { - OutPath = TEXT("Empty"); - } - - return NodePath; - */ - - return !OutPath.IsEmpty(); -} - - -bool -FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), - InNodeId, &NodeInfo), false); - - int32 ObjectCount = 0; - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - ObjectCount = 1; - OutObjectInfos.SetNumUninitialized(1); - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( - FHoudiniEngine::Get().GetSession(), - NodeInfo.parentId, &OutObjectInfos[0]), false); - - // Use the identity transform - OutObjectTransforms.SetNumUninitialized(1); - FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); - - OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; - OutObjectTransforms[0].scale[0] = 1.0f; - OutObjectTransforms[0].scale[1] = 1.0f; - OutObjectTransforms[0].scale[2] = 1.0f; - OutObjectTransforms[0].rstOrder = HAPI_SRT; - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( - FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); - - if (ObjectCount <= 0) - { - // This asset is an OBJ that has no object as children, use the object itself - ObjectCount = 1; - OutObjectInfos.SetNumUninitialized(1); - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[0]), false); - - // Use the identity transform - OutObjectTransforms.SetNumUninitialized(1); - FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); - - OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; - OutObjectTransforms[0].scale[0] = 1.0f; - OutObjectTransforms[0].scale[1] = 1.0f; - OutObjectTransforms[0].scale[2] = 1.0f; - OutObjectTransforms[0].rstOrder = HAPI_SRT; - } - else - { - // This OBJ has children - // See if we should add ourself by looking for immediate display SOP - int32 ImmediateSOP = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), NodeInfo.id, - HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_DISPLAY, - false, &ImmediateSOP), false); - - bool bAddSelf = ImmediateSOP > 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( - FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); - - // Increment the object count by one if we should add ourself - OutObjectInfos.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); - OutObjectTransforms.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); - for (int32 Idx = 0; Idx < OutObjectInfos.Num(); Idx++) - { - FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[Idx])); - FHoudiniApi::Transform_Init(&(OutObjectTransforms[Idx])); - } - - // Get our object info in 0 if needed - if (bAddSelf) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[0]), false); - - // Use the identity transform - OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; - OutObjectTransforms[0].scale[0] = 1.0f; - OutObjectTransforms[0].scale[1] = 1.0f; - OutObjectTransforms[0].scale[2] = 1.0f; - OutObjectTransforms[0].rstOrder = HAPI_SRT; - } - - // Get the other object infos - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( - FHoudiniEngine::Get().GetSession(), InNodeId, - &OutObjectInfos[bAddSelf ? 1 : 0], 0, ObjectCount), false); - - // Get the composed object transforms for the others (1 - Count) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_SRT, &OutObjectTransforms[bAddSelf ? 1 : 0], 0, ObjectCount), false); - } - } - else - return false; - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform) -{ - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InNodeId, - &NodeInfo), false); - - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - - if (NodeInfo.type == HAPI_NODETYPE_SOP) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - NodeInfo.parentId, -1, HAPI_SRT, &HapiTransform), false); - } - else if (NodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InNodeId, -1, HAPI_SRT, &HapiTransform), false); - } - else - return false; - - // Convert HAPI transform to Unreal one. - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, OutTransform); - - return true; -} - -void -FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform) -{ - if ( HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM ) - { - // Swap Y/Z, invert W - FQuat ObjectRotation( - HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[2], - HapiTransform.rotationQuaternion[1], -HapiTransform.rotationQuaternion[3]); - - // Swap Y/Z and scale - FVector ObjectTranslation(HapiTransform.position[0], HapiTransform.position[2], HapiTransform.position[1]); - ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[2], HapiTransform.scale[1]); - - UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); - } - else - { - FQuat ObjectRotation( - HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[1], - HapiTransform.rotationQuaternion[2], HapiTransform.rotationQuaternion[3]); - - FVector ObjectTranslation( - HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2]); - ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[1], HapiTransform.scale[2]); - - UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); - } -} - -void -FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform) -{ - float HapiMatrix[16]; - FHoudiniApi::ConvertTransformEulerToMatrix(FHoudiniEngine::Get().GetSession(), &HapiTransformEuler, HapiMatrix); - - HAPI_Transform HapiTransformQuat; - FMemory::Memzero< HAPI_Transform >(HapiTransformQuat); - FHoudiniApi::ConvertMatrixToQuat(FHoudiniEngine::Get().GetSession(), HapiMatrix, HAPI_SRT, &HapiTransformQuat); - - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransformQuat, UnrealTransform); -} - -void -FHoudiniEngineUtils::TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform) -{ - FMemory::Memzero< HAPI_Transform >(HapiTransform); - HapiTransform.rstOrder = HAPI_SRT; - - FQuat UnrealRotation = UnrealTransform.GetRotation(); - FVector UnrealTranslation = UnrealTransform.GetTranslation(); - FVector UnrealScale = UnrealTransform.GetScale3D(); - - if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) - { - // Swap Y/Z, invert XYZ - HapiTransform.rotationQuaternion[0] = -UnrealRotation.X; - HapiTransform.rotationQuaternion[1] = -UnrealRotation.Z; - HapiTransform.rotationQuaternion[2] = -UnrealRotation.Y; - HapiTransform.rotationQuaternion[3] = UnrealRotation.W; - - // Swap Y/Z, scale - HapiTransform.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransform.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransform.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - HapiTransform.scale[0] = UnrealScale.X; - HapiTransform.scale[1] = UnrealScale.Z; - HapiTransform.scale[2] = UnrealScale.Y; - } - else - { - HapiTransform.rotationQuaternion[0] = UnrealRotation.X; - HapiTransform.rotationQuaternion[1] = UnrealRotation.Y; - HapiTransform.rotationQuaternion[2] = UnrealRotation.Z; - HapiTransform.rotationQuaternion[3] = UnrealRotation.W; - - HapiTransform.position[0] = UnrealTranslation.X; - HapiTransform.position[1] = UnrealTranslation.Y; - HapiTransform.position[2] = UnrealTranslation.Z; - - HapiTransform.scale[0] = UnrealScale.X; - HapiTransform.scale[1] = UnrealScale.Y; - HapiTransform.scale[2] = UnrealScale.Z; - } -} - -void -FHoudiniEngineUtils::TranslateUnrealTransform( - const FTransform & UnrealTransform, - HAPI_TransformEuler & HapiTransformEuler) -{ - FMemory::Memzero< HAPI_TransformEuler >(HapiTransformEuler); - - HapiTransformEuler.rstOrder = HAPI_SRT; - HapiTransformEuler.rotationOrder = HAPI_XYZ; - - FQuat UnrealRotation = UnrealTransform.GetRotation(); - FVector UnrealTranslation = UnrealTransform.GetTranslation(); - FVector UnrealScale = UnrealTransform.GetScale3D(); - - if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) - { - // switch the quaternion to Y-up, LHR by Swapping Y/Z and negating W - Swap(UnrealRotation.Y, UnrealRotation.Z); - UnrealRotation.W = -UnrealRotation.W; - const FRotator Rotator = UnrealRotation.Rotator(); - - // Negate roll and pitch since they are actually RHR - HapiTransformEuler.rotationEuler[0] = -Rotator.Roll; - HapiTransformEuler.rotationEuler[1] = -Rotator.Pitch; - HapiTransformEuler.rotationEuler[2] = Rotator.Yaw; - - // Swap Y/Z, scale - HapiTransformEuler.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransformEuler.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - HapiTransformEuler.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; - - // Swap Y/Z - HapiTransformEuler.scale[0] = UnrealScale.X; - HapiTransformEuler.scale[1] = UnrealScale.Z; - HapiTransformEuler.scale[2] = UnrealScale.Y; - } - else - { - const FRotator Rotator = UnrealRotation.Rotator(); - HapiTransformEuler.rotationEuler[0] = Rotator.Roll; - HapiTransformEuler.rotationEuler[1] = Rotator.Yaw; - HapiTransformEuler.rotationEuler[2] = Rotator.Pitch; - - HapiTransformEuler.position[0] = UnrealTranslation.X; - HapiTransformEuler.position[1] = UnrealTranslation.Y; - HapiTransformEuler.position[2] = UnrealTranslation.Z; - - HapiTransformEuler.scale[0] = UnrealScale.X; - HapiTransformEuler.scale[1] = UnrealScale.Y; - HapiTransformEuler.scale[2] = UnrealScale.Z; - } -} - -bool -FHoudiniEngineUtils::UploadHACTransform(UHoudiniAssetComponent* HAC) -{ - if (!HAC || !HAC->bUploadTransformsToHoudiniEngine) - return false; - - // Indicates the HAC has been fully loaded - // TODO: Check! (replaces fullyloaded) - if (!HAC->IsFullyLoaded()) - return false; - - if (HAC->GetAssetCookCount() > 0 && HAC->GetAssetId() >= 0) - { - if (!FHoudiniEngineUtils::HapiSetAssetTransform(HAC->GetAssetId(), HAC->GetComponentTransform())) - return false; - } - - HAC->SetHasComponentTransformChanged(false); - - return true; -} - -bool -FHoudiniEngineUtils::HapiSetAssetTransform(const HAPI_NodeId& AssetId, const FTransform & Transform) -{ - if (AssetId < 0) - return false; - - // Translate Unreal transform to HAPI Euler one. - HAPI_TransformEuler TransformEuler; - FMemory::Memzero< HAPI_TransformEuler >(TransformEuler); - FHoudiniEngineUtils::TranslateUnrealTransform(Transform, TransformEuler); - - // Get the NodeInfo - HAPI_NodeInfo LocalAssetNodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetId, - &LocalAssetNodeInfo), false); - - if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - LocalAssetNodeInfo.parentId, - &TransformEuler), false); - } - else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - AssetId, &TransformEuler), false); - } - else - return false; - - return true; -} - -HAPI_NodeId -FHoudiniEngineUtils::HapiGetParentNodeId(const HAPI_NodeId& NodeId) -{ - HAPI_NodeId ParentId = -1; - if (NodeId >= 0) - { - HAPI_NodeInfo NodeInfo; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) - ParentId = NodeInfo.parentId; - } - - return ParentId; -} - - -// Assign a unique Actor Label if needed -void -FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - // TODO: Necessary?? - -#if WITH_EDITOR - HAPI_NodeId AssetId = HAC->GetAssetId(); - if (AssetId < 0) - return; - - AActor* OwnerActor = HAC->GetOwner(); - if (!OwnerActor) - return; - - if (!OwnerActor->GetName().StartsWith(AHoudiniAssetActor::StaticClass()->GetName())) - return; - - // Assign unique actor label based on asset name if it seems to have not been renamed already - FString UniqueName; - if (FHoudiniEngineUtils::GetHoudiniAssetName(AssetId, UniqueName)) - FActorLabelUtilities::SetActorLabelUnique(OwnerActor, UniqueName); -#endif -} - -bool -FHoudiniEngineUtils::GetLicenseType(FString & LicenseType) -{ - LicenseType = TEXT(""); - HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetSessionEnvInt( - FHoudiniEngine::Get().GetSession(), HAPI_SESSIONENVINT_LICENSE, - (int32 *)&LicenseTypeValue), false); - - switch (LicenseTypeValue) - { - case HAPI_LICENSE_NONE: - { - LicenseType = TEXT("No License Acquired"); - break; - } - - case HAPI_LICENSE_HOUDINI_ENGINE: - { - LicenseType = TEXT("Houdini Engine"); - break; - } - - case HAPI_LICENSE_HOUDINI: - { - LicenseType = TEXT("Houdini"); - break; - } - - case HAPI_LICENSE_HOUDINI_FX: - { - LicenseType = TEXT("Houdini FX"); - break; - } - - case HAPI_LICENSE_HOUDINI_ENGINE_INDIE: - { - LicenseType = TEXT("Houdini Engine Indie"); - break; - } - - case HAPI_LICENSE_HOUDINI_INDIE: - { - LicenseType = TEXT("Houdini Indie"); - break; - } - - case HAPI_LICENSE_MAX: - default: - { - return false; - } - } - - return true; -} - -// Check if the Houdini asset component (or parent HAC of a parameter) is being cooked -bool -FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(UObject* InObj) -{ - if (!InObj) - return false; - - UHoudiniAssetComponent* HoudiniAssetComponent = nullptr; - - if (InObj->IsA()) - { - HoudiniAssetComponent = Cast(InObj); - } - else if (InObj->IsA()) - { - UHoudiniParameter* Parameter = Cast(InObj); - if (!Parameter) - return false; - - HoudiniAssetComponent = Cast(Parameter->GetOuter()); - } - - if (!HoudiniAssetComponent) - return false; - - EHoudiniAssetState AssetState = HoudiniAssetComponent->GetAssetState(); - - return AssetState >= EHoudiniAssetState::PreCook && AssetState <= EHoudiniAssetState::PostCook; -} - -void -FHoudiniEngineUtils::UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate) -{ - TArray ObjectsToUpdate; - ObjectsToUpdate.Add(InObjectToUpdate); - - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() - { - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - } -} - -void -FHoudiniEngineUtils::UpdateEditorProperties(TArray ObjectsToUpdate, const bool& InForceFullUpdate) -{ - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() - { - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); - } -} - -void FHoudiniEngineUtils::UpdateBlueprintEditor(UHoudiniAssetComponent* HAC) -{ - if (!IsInGameThread()) - { - // We need to be in the game thread to trigger editor properties update - AsyncTask(ENamedThreads::GameThread, [HAC]() - { - FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); - }); - } - else - { - // We're in the game thread, no need for an async task - FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); - } -} - -void -FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate) -{ - // TODO: Don't use this method. Prefer using IDetailLayoutBuilder::ForceRefreshDetails(). - // Example to correctly update details panel through IDetailCategoryBuilder / IDetailLayoutBuilder - // IDetailCategoryBuilder &CategoryBuilder = StructBuilder.GetParentCategory(); - // IDetailLayoutBuilder &LayoutBuilder = CategoryBuilder.GetParentLayout(); - // LayoutBuilder.ForceRefreshDetails(); - -#if WITH_EDITOR - if (!bInForceFullUpdate) - { - // bNeedFullUpdate is false only when small changes (parameters value) have been made - // We do not reselect the actor to avoid loosing the currently selected parameter - if(GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); - - return; - } - - // We now want to get all the components/actors owning the objects to update - TArray AllSceneComponents; - for (auto CurrentObject : ObjectsToUpdate) - { - if (!CurrentObject || CurrentObject->IsPendingKill()) - continue; - - // In some case, the object itself is the component - USceneComponent* SceneComp = Cast(CurrentObject); - if (!SceneComp) - { - SceneComp = Cast(CurrentObject->GetOuter()); - } - - if (SceneComp && !SceneComp->IsPendingKill()) - { - AllSceneComponents.Add(SceneComp); - continue; - } - } - - TArray AllActors; - for (auto CurrentSceneComp : AllSceneComponents) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill()) - continue; - - AActor* Actor = CurrentSceneComp->GetOwner(); - if (Actor && !Actor->IsPendingKill()) - AllActors.Add(Actor); - } - - // Updating the editor properties can be done in two ways, depending if we're in the BP editor or not - // If we have a parent actor, we're not in the BP Editor, so update via the property editor module - if (AllActors.Num() > 0) - { - // Get the property editor module - FPropertyEditorModule& PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // This will actually force a refresh of all the details view - //PropertyModule.NotifyCustomizationModuleChanged(); - - TArray SelectedActors; - for (auto Actor : AllActors) - { - if (Actor && Actor->IsSelected()) - SelectedActors.Add(Actor); - } - - if (SelectedActors.Num() > 0) - { - PropertyModule.UpdatePropertyViews(SelectedActors); - } - - // 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. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - { - // We have no details panel, nothing to update. - continue; - } - - // Get the selected actors for this details panels and check if one of ours belongs to it - const TArray>& SelectedDetailActors = DetailsView->GetSelectedActors(); - bool bFoundActor = false; - for (int32 ActorIdx = 0; ActorIdx < SelectedDetailActors.Num(); ActorIdx++) - { - TWeakObjectPtr SelectedActor = SelectedDetailActors[ActorIdx]; - if (SelectedActor.IsValid() && AllActors.Contains(SelectedActor.Get())) - { - bFoundActor = true; - break; - } - } - - // None of our actors belongs to this detail panel, no need to update it - if (!bFoundActor) - continue; - - // Refresh that details panels using its current selection - TArray Selection; - for (auto DetailsActor : SelectedDetailActors) - { - if (DetailsActor.IsValid()) - Selection.Add(DetailsActor.Get()); - } - - // Reset selected actors, force refresh and override the lock. - DetailsView->SetObjects(SelectedActors, bInForceFullUpdate, true); - - if (GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); - } - } - else - { - // TODO: Do we need to do Blueprint Editor updates here or can we confine it to "post output processing"? - - } - - /* - // Reset the full update flag - if (bNeedFullUpdate) - HAC->SetEditorPropertiesNeedFullUpdate(false); - */ - - return; -#endif -} - -void FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC) -{ - //UHoudiniAssetComponent* HACTemplate = HAC->GetCachedTemplate(); - //UBlueprintGeneratedClass* OwnerBPClass = Cast(HACTemplate->GetOuter()); - //if (!OwnerBPClass) - // return; - - ///* - //FBlueprintEditor* BlueprintEditor = static_cast(FAssetEditorManager::Get().FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); - //if (!BlueprintEditor) - // return; - //*/ - - //// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built. - //UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - //FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); - //if (!BlueprintEditor) - // return; - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(HAC); - if (!BlueprintEditor) - return; - - TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); - if (SCSEditor.IsValid()) - { - SCSEditor->UpdateTree(true); - SCSEditor->DumpTree(); - } - BlueprintEditor->RefreshMyBlueprint(); - - //BlueprintEditor->RefreshMyBlueprint(); - //BlueprintEditor->RefreshInspector(); - //BlueprintEditor->RefreshEditors(); - - // Also somehow reselect ? -} - -HAPI_Result -FHoudiniEngineUtils::SetAttributeStringData( - const FString& InString, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo) -{ - TArray StringArray; - StringArray.Add(InString); - - return SetAttributeStringData(StringArray, InNodeId, InPartId, InAttributeName, InAttributeInfo); -} - -HAPI_Result -FHoudiniEngineUtils::SetAttributeStringData( - const TArray& InStringArray, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo ) -{ - TArray StringDataArray; - for (auto CurrentString : InStringArray) - { - // Append the converted string to the string array - StringDataArray.Add(FHoudiniEngineUtils::ExtractRawString(CurrentString)); - } - - // Set the attribute's string data - HAPI_Result result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - TCHAR_TO_ANSI(*InAttributeName), &InAttributeInfo, - StringDataArray.GetData(), 0, InAttributeInfo.count); - - // ExtractRawString allocates memory using malloc, free it! - FreeRawStringMemory(StringDataArray); - - return result; -} - -char * -FHoudiniEngineUtils::ExtractRawString(const FString& InString) -{ - if (InString.IsEmpty()) - return nullptr; - - std::string ConvertedString = TCHAR_TO_UTF8(*InString); - - // Allocate space for unique string. - int32 UniqueStringBytes = ConvertedString.size() + 1; - char * UniqueString = static_cast(FMemory::Malloc(UniqueStringBytes)); - - FMemory::Memzero(UniqueString, UniqueStringBytes); - FMemory::Memcpy(UniqueString, ConvertedString.c_str(), ConvertedString.size()); - - return UniqueString; -} - -void -FHoudiniEngineUtils::FreeRawStringMemory(const char*& InRawString) -{ - if (InRawString == nullptr) - return; - - // Do not attempt to free empty strings! - if (!InRawString[0]) - return; - - FMemory::Free((void*)InRawString); - InRawString = nullptr; -} - -void -FHoudiniEngineUtils::FreeRawStringMemory(TArray& InRawStringArray) -{ - // ExtractRawString allocates memory using malloc, free it! - for (auto CurrentStrPtr : InRawStringArray) - { - FreeRawStringMemory(CurrentStrPtr); - } - InRawStringArray.Empty(); -} - -bool -FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // No need to add another component if we already show the logo - if (FHoudiniEngineUtils::HasHoudiniLogo(HAC)) - return true; - - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - UStaticMeshComponent * HoudiniLogoSMC = NewObject< UStaticMeshComponent >( - HAC, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!HoudiniLogoSMC) - return false; - - HoudiniLogoSMC->SetStaticMesh(HoudiniLogoSM); - HoudiniLogoSMC->SetVisibility(true); - HoudiniLogoSMC->SetHiddenInGame(true); - // Attach created static mesh component to our Houdini component. - HoudiniLogoSMC->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniLogoSMC->RegisterComponent(); - - return true; -} - -bool -FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the Houdini Logo SM - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - // Iterate on the HAC's component - for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) - continue; - - // Get the static mesh component - UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) - continue; - - // Check if the SMC is the Houdini Logo - if (SMC->GetStaticMesh() != HoudiniLogoSM) - continue; - - SMC->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SMC->UnregisterComponent(); - SMC->DestroyComponent(); - - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the Houdini Logo SM - UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); - if (!HoudiniLogoSM) - return false; - - // Iterate on the HAC's component - for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) - { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) - continue; - - // Get the static mesh component - UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) - continue; - - // Check if the SMC is the Houdini Logo - if (SMC->GetStaticMesh() == HoudiniLogoSM) - return true; - } - - return false; -} - -int32 -FHoudiniEngineUtils::HapiGetVertexListForGroup( - const HAPI_NodeId& GeoId, - const HAPI_PartInfo& PartInfo, - const FString& GroupName, - const TArray& FullVertexList, - TArray& NewVertexList, - TArray& AllVertexList, - TArray& AllFaceList, - TArray& AllGroupFaceIndices, - int32& FirstValidVertex, - int32& FirstValidPrim, - const bool& isPackedPrim) -{ - int32 ProcessedWedges = 0; - AllFaceList.Empty(); - FirstValidPrim = 0; - FirstValidVertex = 0; - NewVertexList.Init(-1, FullVertexList.Num()); - - // Get the faces membership for this group - bool bAllEquals = false; - TArray PartGroupMembership; - if (!FHoudiniEngineUtils::HapiGetGroupMembership( - GeoId, PartInfo, HAPI_GROUPTYPE_PRIM, GroupName, PartGroupMembership, bAllEquals)) - return false; - - // Go through all primitives. - for (int32 FaceIdx = 0; FaceIdx < PartGroupMembership.Num(); ++FaceIdx) - { - if (PartGroupMembership[FaceIdx] <= 0) - { - // The face is not in the group, skip - continue; - } - - // Add the face's index. - AllFaceList.Add(FaceIdx); - - // Get the index of this face's vertices - int32 FirstVertexIdx = FaceIdx * 3; - int32 SecondVertexIdx = FirstVertexIdx + 1; - int32 LastVertexIdx = FirstVertexIdx + 2; - - // This face is a member of specified group. - // Add all 3 vertices - if (FullVertexList.IsValidIndex(LastVertexIdx)) - { - NewVertexList[FirstVertexIdx] = FullVertexList[FirstVertexIdx]; - NewVertexList[SecondVertexIdx] = FullVertexList[SecondVertexIdx]; - NewVertexList[LastVertexIdx] = FullVertexList[LastVertexIdx]; - } - - // Mark these vertex indices as used. - if (AllVertexList.IsValidIndex(LastVertexIdx)) - { - AllVertexList[FirstVertexIdx] = 1; - AllVertexList[SecondVertexIdx] = 1; - AllVertexList[LastVertexIdx] = 1; - } - - // Mark this face as used. - if (AllGroupFaceIndices.IsValidIndex(FaceIdx)) - AllGroupFaceIndices[FaceIdx] = 1; - - if (ProcessedWedges == 0) - { - // Keep track of the first valid vertex/face indices for this group - // This will be useful later on when extracting attributes - FirstValidVertex = FirstVertexIdx; - FirstValidPrim = FaceIdx; - } - - ProcessedWedges += 3; - } - - return ProcessedWedges; -} - -bool -FHoudiniEngineUtils::HapiGetGroupNames( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const HAPI_GroupType& GroupType, const bool& isPackedPrim, - TArray& OutGroupNames) -{ - int32 GroupCount = 0; - if (!isPackedPrim) - { - // Get group count on the geo - HAPI_GeoInfo GeoInfo; - FHoudiniApi::GeoInfo_Init(&GeoInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo), false); - - if (GroupType == HAPI_GROUPTYPE_POINT) - GroupCount = GeoInfo.pointGroupCount; - else if (GroupType == HAPI_GROUPTYPE_PRIM) - GroupCount = GeoInfo.primitiveGroupCount; - } - else - { - // We need the group count for this packed prim - int32 PointGroupCount = 0, PrimGroupCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupCountOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PointGroupCount, &PrimGroupCount), false); - - if (GroupType == HAPI_GROUPTYPE_POINT) - GroupCount = PointGroupCount; - else if (GroupType == HAPI_GROUPTYPE_PRIM) - GroupCount = PrimGroupCount; - } - - if (GroupCount <= 0) - return true; - - TArray GroupNameStringHandles; - GroupNameStringHandles.SetNumZeroed(GroupCount); - if (!isPackedPrim) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNames( - FHoudiniEngine::Get().GetSession(), - GeoId, GroupType, &GroupNameStringHandles[0], GroupCount), false); - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNamesOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, GroupType, &GroupNameStringHandles[0], GroupCount), false); - } - - /* - OutGroupNames.SetNum(GroupCount); - for (int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx) - { - FString CurrentGroupName = TEXT(""); - FHoudiniEngineString::ToFString(GroupNameStringHandles[NameIdx], CurrentGroupName); - OutGroupNames[NameIdx] = CurrentGroupName; - } - */ - - FHoudiniEngineString::SHArrayToFStringArray(GroupNameStringHandles, OutGroupNames); - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetGroupMembership( - const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, - const HAPI_GroupType& GroupType, const FString & GroupName, - TArray& OutGroupMembership, bool& OutAllEquals) -{ - int32 ElementCount = (GroupType == HAPI_GROUPTYPE_POINT) ? PartInfo.pointCount : PartInfo.faceCount; - if (ElementCount < 1) - return false; - OutGroupMembership.SetNum(ElementCount); - - OutAllEquals = false; - std::string ConvertedGroupName = TCHAR_TO_UTF8(*GroupName); - if (!PartInfo.isInstanced) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembership( - FHoudiniEngine::Get().GetSession(), - GeoId, PartInfo.id, GroupType,ConvertedGroupName.c_str(), - &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembershipOnPackedInstancePart( - FHoudiniEngine::Get().GetSession(), GeoId, PartInfo.id, GroupType, - ConvertedGroupName.c_str(), &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); - } - - return true; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize, - HAPI_AttributeOwner InOwner) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniEngineUtils::HapiGetAttributeDataAsFloat")); - - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Allocate sufficient buffer for data. - OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); - - return true; - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Expected Float, found an int, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray IntData; - IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(IntData.Num()); - for (int32 Idx = 0; Idx < IntData.Num(); Idx++) - { - OutData[Idx] = (float)IntData[Idx]; - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from integer."), *FString(InAttribName)); - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - // Expected Float, found a string, try to convert the attribute - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) - { - bool bConversionError = false; - OutData.SetNum(StringData.Num()); - for (int32 Idx = 0; Idx < StringData.Num(); Idx++) - { - if (StringData[Idx].IsNumeric()) - OutData[Idx] = FCString::Atof(*StringData[Idx]); - else - bConversionError = true; - } - - if (!bConversionError) - { - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from string."), *FString(InAttribName)); - return true; - } - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a float attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - const int32& InTupleSize, - const HAPI_AttributeOwner& InOwner) -{ - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Allocate sufficient buffer for data. - OutData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &OutData[0], 0, AttributeInfo.count), false); - - return true; - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Expected Int, found a float, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray FloatData; - FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the float values - if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(FloatData.Num()); - for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) - { - OutData[Idx] = (int32)FloatData[Idx]; - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from float."), *FString(InAttribName)); - - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - // Expected Int, found a string, try to convert the attribute - TArray StringData; - if(FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, StringData)) - { - bool bConversionError = false; - OutData.SetNum(StringData.Num()); - for (int32 Idx = 0; Idx < StringData.Num(); Idx++) - { - if (StringData[Idx].IsNumeric()) - OutData[Idx] = FCString::Atoi(*StringData[Idx]); - else - bConversionError = true; - } - - if (!bConversionError) - { - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from string."), *FString(InAttribName)); - return true; - } - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be an integer attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsString( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize, - HAPI_AttributeOwner InOwner) -{ - OutAttributeInfo.exists = false; - - // Reset container size. - OutData.SetNumUninitialized(0); - - int32 OriginalTupleSize = InTupleSize; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (InOwner == HAPI_ATTROWNER_INVALID) - { - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); - - if (AttributeInfo.exists) - break; - } - } - else - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - InOwner, &AttributeInfo), false); - } - - if (!AttributeInfo.exists) - return false; - - // Store the retrieved attribute information. - OutAttributeInfo = AttributeInfo; - - if (OriginalTupleSize > 0) - AttributeInfo.tupleSize = OriginalTupleSize; - - if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) - { - return FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo(InGeoId, InPartId, InAttribName, AttributeInfo, OutData); - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - // Expected string, found a float, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray FloatData; - FloatData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the float values - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &FloatData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(FloatData.Num()); - for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) - { - OutData[Idx] = FString::SanitizeFloat(FloatData[Idx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from float."), *FString(InAttribName)); - return true; - } - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - // Expected String, found an int, try to convert the attribute - - // Allocate sufficient buffer for data. - TArray IntData; - IntData.SetNum(AttributeInfo.count * AttributeInfo.tupleSize); - - // Fetch the values - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, - &AttributeInfo, -1, &IntData[0], 0, AttributeInfo.count)) - { - OutData.SetNum(IntData.Num()); - for (int32 Idx = 0; Idx < IntData.Num(); Idx++) - { - OutData[Idx] = FString::FromInt(IntData[Idx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from integer."), *FString(InAttribName)); - return true; - } - } - - HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a string attribute and is of an invalid type."), *FString(InAttribName)); - return false; -} - -bool -FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& InAttributeInfo, - TArray& OutData) -{ - if (!InAttributeInfo.exists) - return false; - - // Extract the StringHandles - TArray StringHandles; - StringHandles.Init(-1, InAttributeInfo.count * InAttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, InAttribName, &InAttributeInfo, - &StringHandles[0], 0, InAttributeInfo.count), false); - - // Set the output data size - OutData.SetNum(StringHandles.Num()); - - // Convert the StringHandles to FString. - // using a map to minimize the number of HAPI calls - FHoudiniEngineString::SHArrayToFStringArray(StringHandles, OutData); - - return true; -} - - -bool -FHoudiniEngineUtils::HapiCheckAttributeExists( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const char * AttribName, HAPI_AttributeOwner Owner) -{ - if (Owner == HAPI_ATTROWNER_INVALID) - { - for (int32 OwnerIdx = 0; OwnerIdx < HAPI_ATTROWNER_MAX; OwnerIdx++) - { - if (HapiCheckAttributeExists(GeoId, PartId, AttribName, (HAPI_AttributeOwner)OwnerIdx)) - { - return true; - } - } - } - else - { - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, AttribName, Owner, &AttribInfo), false); - - return AttribInfo.exists; - } - - return false; -} - -bool -FHoudiniEngineUtils::IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType) -{ - // Check for - // - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE (unreal_instance) on points/detail - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT)) - { - OutInstancerType = EHoudiniInstancerType::AttributeInstancer; - return true; - } - - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL)) - { - OutInstancerType = EHoudiniInstancerType::AttributeInstancer; - return true; - } - - // - HAPI_UNREAL_ATTRIB_INSTANCE (instance) on points - if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT)) - { - OutInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsString( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const FString& DefaultValue, - FString& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParamInfo; - FHoudiniApi::ParmInfo_Init(&FoundParamInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParamInfo), false); - - // .. and value - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), NodeId, false, - &StringHandle, FoundParamInfo.stringValuesIndex, 1), false); - - // Convert the string handle to FString - return FHoudiniEngineString::ToFString(StringHandle, OutValue); -} - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const int32& DefaultValue, - int32& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParmInfo; - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), false); - - // .. and value - int32 Value = DefaultValue; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), NodeId, &Value, - FoundParmInfo.intValuesIndex, 1), false); - - OutValue = Value; - - return true; -} - - -bool -FHoudiniEngineUtils::HapiGetParameterDataAsFloat( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const float& DefaultValue, - float& OutValue) -{ - OutValue = DefaultValue; - - // Try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmName.c_str(), &ParmId), false); - - if (ParmId < 0) - return false; - - // Get the param info... - HAPI_ParmInfo FoundParmInfo; - FHoudiniApi::ParmInfo_Init(&FoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, ParmId, &FoundParmInfo), false); - - // .. and value - float Value = DefaultValue; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), NodeId, &Value, - FoundParmInfo.floatValuesIndex, 1), false); - - OutValue = Value; - - return true; -} - -HAPI_ParmId -FHoudiniEngineUtils::HapiFindParameterByName(const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo) -{ - // Try to find the parameter by its name - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - InNodeId, InParmName.c_str(), &ParmId), -1); - - if (ParmId < 0) - return -1; - - FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmId, &OutFoundParmInfo), -1); - - return ParmId; -} - -HAPI_ParmId -FHoudiniEngineUtils::HapiFindParameterByTag(const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo) -{ - // Try to find the parameter by its tag - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmWithTag( - FHoudiniEngine::Get().GetSession(), - InNodeId, InParmTag.c_str(), &ParmId), -1); - - if (ParmId < 0) - return -1; - - FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmId, &OutFoundParmInfo), -1); - - return ParmId; -} - -int32 -FHoudiniEngineUtils::HapiGetAttributeOfType( - const HAPI_NodeId& GeoId, - const HAPI_NodeId& PartId, - const HAPI_AttributeOwner& AttributeOwner, - const HAPI_AttributeTypeInfo& AttributeType, - TArray& MatchingAttributesInfo, - TArray& MatchingAttributesName) -{ - int32 NumberOfAttributeFound = 0; - - // Get the part infos - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, &PartInfo), NumberOfAttributeFound); - - // Get All attribute names for that part - int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; - - TArray AttribNameSHArray; - AttribNameSHArray.SetNum(nAttribCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, AttributeOwner, - AttribNameSHArray.GetData(), nAttribCount), NumberOfAttributeFound); - - TArray AttribNameArray; - FHoudiniEngineString::SHArrayToFStringArray(AttribNameSHArray, AttribNameArray); - - // Iterate on all the attributes, and get their part infos to get their type - for (int32 Idx = 0; Idx < AttribNameArray.Num(); Idx++) - { - FString HapiString = AttribNameArray[Idx]; - - // ... then the attribute info - HAPI_AttributeInfo AttrInfo; - FHoudiniApi::AttributeInfo_Init(&AttrInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - GeoId, PartId, TCHAR_TO_UTF8(*HapiString), - AttributeOwner, &AttrInfo)) - continue; - - if (!AttrInfo.exists) - continue; - - // ... check the type - if (AttrInfo.typeInfo != AttributeType) - continue; - - MatchingAttributesInfo.Add(AttrInfo); - MatchingAttributesName.Add(HapiString); - - NumberOfAttributeFound++; - } - - return NumberOfAttributeFound; -} - -HAPI_PartInfo -FHoudiniEngineUtils::ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo) -{ - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - - PartInfo.id = InHPartInfo.PartId; - //PartInfo.nameSH = InHPartInfo.Name; - - switch (InHPartInfo.Type) - { - case EHoudiniPartType::Mesh: - PartInfo.type = HAPI_PARTTYPE_MESH; - break; - case EHoudiniPartType::Curve: - PartInfo.type = HAPI_PARTTYPE_CURVE; - break; - case EHoudiniPartType::Instancer: - PartInfo.type = HAPI_PARTTYPE_INSTANCER; - break; - case EHoudiniPartType::Volume: - PartInfo.type = HAPI_PARTTYPE_VOLUME; - break; - default: - case EHoudiniPartType::Invalid: - PartInfo.type = HAPI_PARTTYPE_INVALID; - break; - } - - PartInfo.faceCount = InHPartInfo.FaceCount; - PartInfo.vertexCount = InHPartInfo.VertexCount; - PartInfo.pointCount = InHPartInfo.PointCount; - - PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = InHPartInfo.PointAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX] = InHPartInfo.VertexAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_PRIM] = InHPartInfo.PrimitiveAttributeCounts; - PartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL] = InHPartInfo.DetailAttributeCounts; - - PartInfo.isInstanced = InHPartInfo.bIsInstanced; - - PartInfo.instancedPartCount = InHPartInfo.InstancedPartCount; - PartInfo.instanceCount = InHPartInfo.InstanceCount; - - PartInfo.hasChanged = InHPartInfo.bHasChanged; - - return PartInfo; -} - -int32 -FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - TArray< FHoudiniMeshSocket >& AllSockets, - const bool& isPackedPrim) -{ - int32 FoundSocketCount = 0; - - // Attributes we are interested in. - // Position - TArray Positions; - HAPI_AttributeInfo AttribInfoPositions; - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bool bHasRotation = false; - TArray Rotations; - HAPI_AttributeInfo AttribInfoRotations; - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bool bHasScale = false; - TArray Scales; - HAPI_AttributeInfo AttribInfoScales; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // Socket Name - bool bHasNames = false; - TArray Names; - HAPI_AttributeInfo AttribInfoNames; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bool bHasActors = false; - TArray Actors; - HAPI_AttributeInfo AttribInfoActors; - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bool bHasTags = false; - TArray Tags; - HAPI_AttributeInfo AttribInfoTags; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - - // Lambda function for creating the socket and adding it to the array - // Shared between the by Attribute / by Group methods - auto AddSocketToArray = [&](const int32& PointIdx) - { - FHoudiniMeshSocket CurrentSocket; - FVector currentPosition = FVector::ZeroVector; - if (Positions.IsValidIndex(PointIdx * 3 + 2)) - { - currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - FVector currentScale = FVector::OneVector; - if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) - { - currentScale.X = Scales[PointIdx * 3]; - currentScale.Y = Scales[PointIdx * 3 + 2]; - currentScale.Z = Scales[PointIdx * 3 + 1]; - } - - FQuat currentRotation = FQuat::Identity; - if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) - { - currentRotation.X = Rotations[PointIdx * 4]; - currentRotation.Y = Rotations[PointIdx * 4 + 2]; - currentRotation.Z = Rotations[PointIdx * 4 + 1]; - currentRotation.W = -Rotations[PointIdx * 4 + 3]; - } - - if (bHasNames && Names.IsValidIndex(PointIdx)) - CurrentSocket.Name = Names[PointIdx]; - - if (bHasActors && Actors.IsValidIndex(PointIdx)) - CurrentSocket.Actor = Actors[PointIdx]; - - if (bHasTags && Tags.IsValidIndex(PointIdx)) - CurrentSocket.Tag = Tags[PointIdx]; - - // If the scale attribute wasn't set on all socket, we might end up - // with a zero scale socket, avoid that. - if (currentScale == FVector::ZeroVector) - currentScale = FVector::OneVector; - - CurrentSocket.Transform.SetLocation(currentPosition); - CurrentSocket.Transform.SetRotation(currentRotation); - CurrentSocket.Transform.SetScale3D(currentScale); - - // We want to make sure we're not adding the same socket multiple times - AllSockets.AddUnique(CurrentSocket); - - FoundSocketCount++; - - return true; - }; - - - // Lambda function for reseting the arrays/attributes - auto ResetArraysAndAttr = [&]() - { - // Position - Positions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bHasRotation = false; - Rotations.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bHasScale = false; - Scales.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // Socket Name - bHasNames = false; - Names.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bHasActors = false; - Actors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bHasTags = false; - Tags.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - }; - - //------------------------------------------------------------------------- - // FIND SOCKETS BY DETAIL ATTRIBUTES - //------------------------------------------------------------------------- - - int32 SocketIdx = 0; - bool HasSocketAttributes = true; - while (HasSocketAttributes) - { - // Build the current socket's prefix - FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(SocketIdx); - - // Reset the arrays and attributes - ResetArraysAndAttr(); - - // Retrieve position data. - FString SocketPosAttr = SocketAttrPrefix + TEXT("_pos"); - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, TCHAR_TO_ANSI(*SocketPosAttr), - AttribInfoPositions, Positions, 0, HAPI_ATTROWNER_DETAIL)) - break; - - if (!AttribInfoPositions.exists) - { - // No need to keep looking for socket attributes - HasSocketAttributes = false; - break; - } - - // Retrieve rotation data. - FString SocketRotAttr = SocketAttrPrefix + TEXT("_rot"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketRotAttr), AttribInfoRotations, Rotations, 0, HAPI_ATTROWNER_DETAIL)) - bHasRotation = true; - - // Retrieve scale data. - FString SocketScaleAttr = SocketAttrPrefix + TEXT("_scale"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketScaleAttr), AttribInfoScales, Scales, 0, HAPI_ATTROWNER_DETAIL)) - bHasScale = true; - - // Retrieve mesh socket names. - FString SocketNameAttr = SocketAttrPrefix + TEXT("_name"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketNameAttr), AttribInfoNames, Names)) - bHasNames = true; - - // Retrieve mesh socket actor. - FString SocketActorAttr = SocketAttrPrefix + TEXT("_actor"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketActorAttr), AttribInfoActors, Actors)) - bHasActors = true; - - // Retrieve mesh socket tags. - FString SocketTagAttr = SocketAttrPrefix + TEXT("_tag"); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, - TCHAR_TO_ANSI(*SocketTagAttr), AttribInfoTags, Tags)) - bHasTags = true; - - // Add the socket to the array - AddSocketToArray(0); - - // Try to find the next socket - SocketIdx++; - } - - return FoundSocketCount; -} - - -int32 -FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - TArray& AllSockets, - const bool& isPackedPrim) -{ - // Attributes we are interested in. - // Position - TArray Positions; - HAPI_AttributeInfo AttribInfoPositions; - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bool bHasRotation = false; - TArray Rotations; - HAPI_AttributeInfo AttribInfoRotations; - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bool bHasScale = false; - TArray Scales; - HAPI_AttributeInfo AttribInfoScales; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // We can also get the sockets rotation from the normal - bool bHasNormals = false; - TArray Normals; - HAPI_AttributeInfo AttribInfoNormals; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Socket Name - bool bHasNames = false; - TArray Names; - HAPI_AttributeInfo AttribInfoNames; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bool bHasActors = false; - TArray Actors; - HAPI_AttributeInfo AttribInfoActors; - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bool bHasTags = false; - TArray Tags; - HAPI_AttributeInfo AttribInfoTags; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - - // Lambda function for creating the socket and adding it to the array - // Shared between the by Attribute / by Group methods - int32 FoundSocketCount = 0; - auto AddSocketToArray = [&](const int32& PointIdx) - { - FHoudiniMeshSocket CurrentSocket; - FVector currentPosition = FVector::ZeroVector; - if (Positions.IsValidIndex(PointIdx * 3 + 2)) - { - currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - FVector currentScale = FVector::OneVector; - if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) - { - currentScale.X = Scales[PointIdx * 3]; - currentScale.Y = Scales[PointIdx * 3 + 2]; - currentScale.Z = Scales[PointIdx * 3 + 1]; - } - - FQuat currentRotation = FQuat::Identity; - if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) - { - currentRotation.X = Rotations[PointIdx * 4]; - currentRotation.Y = Rotations[PointIdx * 4 + 2]; - currentRotation.Z = Rotations[PointIdx * 4 + 1]; - currentRotation.W = -Rotations[PointIdx * 4 + 3]; - } - else if (bHasNormals && Normals.IsValidIndex(PointIdx * 3 + 2)) - { - FVector vNormal; - vNormal.X = Normals[PointIdx * 3]; - vNormal.Y = Normals[PointIdx * 3 + 2]; - vNormal.Z = Normals[PointIdx * 3 + 1]; - - if (vNormal != FVector::ZeroVector) - currentRotation = FQuat::FindBetween(FVector::UpVector, vNormal); - } - - if (bHasNames && Names.IsValidIndex(PointIdx)) - CurrentSocket.Name = Names[PointIdx]; - - if (bHasActors && Actors.IsValidIndex(PointIdx)) - CurrentSocket.Actor = Actors[PointIdx]; - - if (bHasTags && Tags.IsValidIndex(PointIdx)) - CurrentSocket.Tag = Tags[PointIdx]; - - // If the scale attribute wasn't set on all socket, we might end up - // with a zero scale socket, avoid that. - if (currentScale == FVector::ZeroVector) - currentScale = FVector::OneVector; - - CurrentSocket.Transform.SetLocation(currentPosition); - CurrentSocket.Transform.SetRotation(currentRotation); - CurrentSocket.Transform.SetScale3D(currentScale); - - // We want to make sure we're not adding the same socket multiple times - AllSockets.AddUnique(CurrentSocket); - - FoundSocketCount++; - - return true; - }; - - - // Lambda function for reseting the arrays/attributes - auto ResetArraysAndAttr = [&]() - { - // Position - Positions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Rotation - bHasRotation = false; - Rotations.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); - - // Scale - bHasScale = false; - Scales.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); - - // When using socket groups, we can also get the sockets rotation from the normal - bHasNormals = false; - Normals.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Socket Name - bHasNames = false; - Names.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); - - // Socket Actor - bHasActors = false; - Actors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); - - // Socket Tags - bHasTags = false; - Tags.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); - }; - - //------------------------------------------------------------------------- - // FIND SOCKETS BY POINT GROUPS - //------------------------------------------------------------------------- - - // Get object / geo group memberships for primitives. - TArray GroupNames; - if (!FHoudiniEngineUtils::HapiGetGroupNames( - GeoId, PartId, HAPI_GROUPTYPE_POINT, isPackedPrim, GroupNames)) - { - HOUDINI_LOG_MESSAGE(TEXT("GetMeshSocketList: Geo [%d] Part [%d] non-fatal error reading point group names"), GeoId, PartId); - } - - // First, we want to make sure we have at least one socket group before continuing - bool bHasSocketGroup = false; - for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) - { - const FString & GroupName = GroupNames[GeoGroupNameIdx]; - if (GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) - || GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) - { - bHasSocketGroup = true; - break; - } - } - - if (!bHasSocketGroup) - return FoundSocketCount; - - // Get the part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PartInfo)) - return false; - - // Reset the data arrays and attributes - ResetArraysAndAttr(); - - // Retrieve position data. - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, Positions)) - return false; - - // Retrieve rotation data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_ROTATION, AttribInfoRotations, Rotations)) - bHasRotation = true; - - // Retrieve normal data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, Normals)) - bHasNormals = true; - - // Retrieve scale data. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - GeoId, PartId, HAPI_UNREAL_ATTRIB_SCALE, AttribInfoScales, Scales)) - bHasScale = true; - - // Retrieve mesh socket names. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, AttribInfoNames, Names)) - bHasNames = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD, AttribInfoNames, Names)) - bHasNames = true; - - // Retrieve mesh socket actor. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR, AttribInfoActors, Actors)) - bHasActors = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD, AttribInfoActors, Actors)) - bHasActors = true; - - // Retrieve mesh socket tags. - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, AttribInfoTags, Tags)) - bHasTags = true; - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD, AttribInfoTags, Tags)) - bHasTags = true; - - // Extracting Sockets vertices - for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) - { - const FString & GroupName = GroupNames[GeoGroupNameIdx]; - if (!GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) - && !GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) - continue; - - bool AllEquals = false; - TArray< int32 > PointGroupMembership; - FHoudiniEngineUtils::HapiGetGroupMembership( - GeoId, PartInfo, HAPI_GROUPTYPE_POINT, GroupName, PointGroupMembership, AllEquals); - - // Go through all primitives. - for (int32 PointIdx = 0; PointIdx < PointGroupMembership.Num(); ++PointIdx) - { - if (PointGroupMembership[PointIdx] == 0) - { - if (AllEquals) - break; - else - continue; - } - - // Add the corresponding socket to the array - AddSocketToArray(PointIdx); - } - } - - return FoundSocketCount; -} - -bool -FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( - UStaticMesh* StaticMesh, - TArray& AllSockets, - const bool& CleanImportSockets) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Remove the sockets from the previous cook! - if (CleanImportSockets) - { - StaticMesh->Sockets.RemoveAll([=](UStaticMeshSocket* Socket) { return Socket ? Socket->bSocketCreatedAtImport : true; }); - } - - if (AllSockets.Num() <= 0) - return true; - - // Having sockets with empty names can lead to various issues, so we'll create one now - for (int32 Idx = 0; Idx < AllSockets.Num(); ++Idx) - { - // Assign the unnamed sockets with default names - if (AllSockets[Idx].Name.IsEmpty()) - AllSockets[Idx].Name = TEXT("Socket ") + FString::FromInt(Idx); - } - - // ensure the socket names are unique. (Unreal will use the first socket if multiple socket have the same name) - for (int32 Idx_i = 0; Idx_i < AllSockets.Num(); ++Idx_i) - { - int32 Count = 0; - for (int32 Idx_j = Idx_i + 1; Idx_j < AllSockets.Num(); ++Idx_j) - { - if (AllSockets[Idx_i].Name.Equals(AllSockets[Idx_j].Name)) - { - Count += 1; - AllSockets[Idx_j].Name = AllSockets[Idx_j].Name + "_" + FString::FromInt(Count); - } - } - } - - // Clear all the sockets of the output static mesh. - StaticMesh->Sockets.Empty(); - - for (int32 nSocket = 0; nSocket < AllSockets.Num(); nSocket++) - { - // Create a new Socket - UStaticMeshSocket* Socket = NewObject(StaticMesh); - if (!Socket || Socket->IsPendingKill()) - continue; - - Socket->RelativeLocation = AllSockets[nSocket].Transform.GetLocation(); - Socket->RelativeRotation = FRotator(AllSockets[nSocket].Transform.GetRotation()); - Socket->RelativeScale = AllSockets[nSocket].Transform.GetScale3D(); - Socket->SocketName = FName(*AllSockets[nSocket].Name); - - // Socket Tag - FString Tag; - if (!AllSockets[nSocket].Tag.IsEmpty()) - Tag = AllSockets[nSocket].Tag; - - // The actor will be stored temporarily in the socket's Tag as we need a StaticMeshComponent to add an actor to the socket - Tag += TEXT("|") + AllSockets[nSocket].Actor; - - Socket->Tag = Tag; - Socket->bSocketCreatedAtImport = true; - - StaticMesh->Sockets.Add(Socket); - } - - return true; -} - -bool -FHoudiniEngineUtils::CreateAttributesFromTags( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const TArray& Tags ) -{ - if (Tags.Num() <= 0) - return false; - - HAPI_Result Result = HAPI_RESULT_FAILURE; - - // Get the destination part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); - - bool NeedToCommitGeo = false; - for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) - { - FString TagString; - Tags[TagIdx].ToString(TagString); - SanitizeHAPIVariableName(TagString); - - // Create a primitive attribute for the tag - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - AttributeInfo.count = PartInfo.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; - - FString AttributeName = TEXT(HAPI_UNREAL_ATTRIB_TAG_PREFIX) + FString::FromInt(TagIdx); - AttributeName.RemoveSpacesInline(); - - Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo); - - if (Result != HAPI_RESULT_SUCCESS) - continue; - - TArray TagStr; - TagStr.Add(FHoudiniEngineUtils::ExtractRawString(TagString)); - - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo, - TagStr.GetData(), 0, AttributeInfo.count); - - if (HAPI_RESULT_SUCCESS == Result) - NeedToCommitGeo = true; - - // Free memory for allocated by ExtractRawString - FHoudiniEngineUtils::FreeRawStringMemory(TagStr); - } - - return NeedToCommitGeo; -} - -bool -FHoudiniEngineUtils::CreateGroupsFromTags( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const TArray& Tags ) -{ - if (Tags.Num() <= 0) - return true; - - HAPI_Result Result = HAPI_RESULT_FAILURE; - - // Get the destination part info - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); - - bool NeedToCommitGeo = false; - for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) - { - FString TagString; - Tags[TagIdx].ToString(TagString); - SanitizeHAPIVariableName(TagString); - - const char * TagStr = FHoudiniEngineUtils::ExtractRawString(TagString); - - // Create a primitive group for this tag - if ( HAPI_RESULT_SUCCESS == FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, TagStr) ) - { - // Set the group's Memberships - TArray GroupArray; - GroupArray.Init(1, PartInfo.faceCount); - - if ( HAPI_RESULT_SUCCESS == FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_GROUPTYPE_PRIM, TagStr, - GroupArray.GetData(), 0, PartInfo.faceCount) ) - { - NeedToCommitGeo = true; - } - } - - // Free memory allocated by ExtractRawString() - FHoudiniEngineUtils::FreeRawStringMemory(TagStr); - } - - return NeedToCommitGeo; -} - - -bool -FHoudiniEngineUtils::SanitizeHAPIVariableName(FString& String) -{ - // Only keep alphanumeric characters, underscores - // Also, if the first character is a digit, append an underscore at the beginning - TArray& StrArray = String.GetCharArray(); - if (StrArray.Num() <= 0) - return false; - - for (auto& CurChar : StrArray) - { - const bool bIsValid = (CurChar >= TEXT('A') && CurChar <= TEXT('Z')) - || (CurChar >= TEXT('a') && CurChar <= TEXT('z')) - || (CurChar >= TEXT('0') && CurChar <= TEXT('9')) - || (CurChar == TEXT('_')) || (CurChar == TEXT('\0')); - - if(bIsValid) - continue; - - CurChar = TEXT('_'); - } - - if (StrArray.Num() > 0) - { - TCHAR FirstChar = StrArray[0]; - if (FirstChar >= TEXT('0') && FirstChar <= TEXT('9')) - StrArray.Insert(TEXT('_'), 0); - } - - return true; -} - -bool -FHoudiniEngineUtils::GetUnrealTagAttributes( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags) -{ - FString TagAttribBase = TEXT("unreal_tag_"); - bool bAttributeFound = true; - int32 TagIdx = 0; - while (bAttributeFound) - { - FString CurrentTagAttr = TagAttribBase + FString::FromInt(TagIdx++); - bAttributeFound = HapiCheckAttributeExists(GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), HAPI_ATTROWNER_PRIM); - if (!bAttributeFound) - break; - - // found the unreal_tag_X attribute, get its value and add it to the array - FString TagValue = FString(); - - // Create an AttributeInfo - { - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) - { - TagValue = StringData[0]; - } - } - - FName NameTag = *TagValue; - OutTags.Add(NameTag); - } - - return true; -} - - -int32 -FHoudiniEngineUtils::GetGenericAttributeList( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FString& InGenericAttributePrefix, - TArray& OutFoundAttributes, - const HAPI_AttributeOwner& AttributeOwner, - const int32& InAttribIndex) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineUtils::GetGenericAttributeList); - - // Get the part info to get the attribute counts for the specified owner - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), InGeoNodeId, InPartId, &PartInfo), false); - - int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; - - // Get all attribute names for that part - TArray AttribNameSHArray; - AttribNameSHArray.SetNum(nAttribCount); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, AttributeOwner, - AttribNameSHArray.GetData(), nAttribCount)) - { - return 0; - } - - // For everything but detail attribute, - // if an attribute index was specified, only extract the attribute value for that specific index - // if not, extract all values for the given attribute - bool HandleSplit = false; - int32 AttribIndex = -1; - if ((AttributeOwner != HAPI_ATTROWNER_DETAIL) && (InAttribIndex != -1)) - { - // The index has already been specified so we'll use it - HandleSplit = true; - AttribIndex = InAttribIndex; - } - - int32 FoundCount = 0; - for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) - { - int32 AttribNameSH = (int32)AttribNameSHArray[Idx]; - FString AttribName = TEXT(""); - FHoudiniEngineString::ToFString(AttribNameSH, AttribName); - if (!AttribName.StartsWith(InGenericAttributePrefix, ESearchCase::IgnoreCase)) - continue; - - // Get the Attribute Info - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), AttributeOwner, &AttribInfo)) - { - // failed to get that attribute's info - continue; - } - - int32 AttribStart = 0; - int32 AttribCount = AttribInfo.count; - if (HandleSplit) - { - // For split primitives, we need to only get only one value for the proper split prim - // Make sure that the split index is valid - if (AttribIndex >= 0 && AttribIndex < AttribInfo.count) - { - AttribStart = AttribIndex; - AttribCount = 1; - } - } - - // - FHoudiniGenericAttribute CurrentGenericAttribute; - // Remove the generic attribute prefix - CurrentGenericAttribute.AttributeName = AttribName.Right(AttribName.Len() - InGenericAttributePrefix.Len()); - - CurrentGenericAttribute.AttributeOwner = (EAttribOwner)AttribInfo.owner; - - // Get the attribute type and tuple size - CurrentGenericAttribute.AttributeType = (EAttribStorageType)AttribInfo.storage; - CurrentGenericAttribute.AttributeCount = AttribInfo.count; - CurrentGenericAttribute.AttributeTupleSize = AttribInfo.tupleSize; - - if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT64) - { - // Initialize the value array - CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloat64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, 0, - CurrentGenericAttribute.DoubleValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT) - { - // Initialize the value array - TArray FloatValues; - FloatValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, FloatValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to double - CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - for (int32 n = 0; n < FloatValues.Num(); n++) - CurrentGenericAttribute.DoubleValues[n] = (double)FloatValues[n]; - - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT64) - { -#if PLATFORM_LINUX - // On Linux, we unfortunately cannot guarantee that int64 and HAPI_Int64 - // are of the same type, to properly read the value, we must first check the - // size, then either cast them (if sizes match) or convert the values (if sizes don't match) - if (sizeof(int64) != sizeof(HAPI_Int64)) - { - // int64 and HAPI_Int64 are of different size, we need to cast - TArray HAPIIntValues; - HAPIIntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, HAPIIntValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to int64 - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - for (int32 n = 0; n < HAPIIntValues.Num(); n++) - CurrentGenericAttribute.IntValues[n] = (int64)HAPIIntValues[n]; - } - else - { - // Initialize the value array - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) with a reinterpret_cast since sizes match - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, reinterpret_cast(CurrentGenericAttribute.IntValues.GetData()), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - } -#else - // Initialize the value array - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, CurrentGenericAttribute.IntValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } -#endif - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT) - { - // Initialize the value array - TArray IntValues; - IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the value(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - 0, IntValues.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert them to int64 - CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - for (int32 n = 0; n < IntValues.Num(); n++) - CurrentGenericAttribute.IntValues[n] = (int64)IntValues[n]; - - } - else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - // Initialize a string handle array - TArray HapiSHArray; - HapiSHArray.SetNumZeroed(AttribCount * AttribInfo.tupleSize); - - // Get the string handle(s) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, - TCHAR_TO_UTF8(*AttribName), &AttribInfo, - HapiSHArray.GetData(), - AttribStart, AttribCount)) - { - // failed to get that attribute's data - continue; - } - - // Convert the String Handles to FStrings - // using a map to minimize the number of HAPI calls - FHoudiniEngineString::SHArrayToFStringArray(HapiSHArray, CurrentGenericAttribute.StringValues); - } - else - { - // Unsupported type, skipping! - continue; - } - - // We can add the UPropertyAttribute to the array - OutFoundAttributes.Add(CurrentGenericAttribute); - FoundCount++; - } - - return FoundCount; -} - - -bool -FHoudiniEngineUtils::GetGenericPropertiesAttributes(const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, - const bool InbFindDetailAttributes, const int32& InFirstValidPrimIndex, const int32& InFirstValidVertexIndex, const int32& InFirstValidPointIndex, - TArray& OutPropertyAttributes) -{ - int32 FoundCount = 0; - - // List all the generic property detail attributes ... - if (InbFindDetailAttributes) - { - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - } - - // .. then the primitive property attributes for the given prim - if (InFirstValidPrimIndex != INDEX_NONE) - { - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); - } - - if (InFirstValidVertexIndex != INDEX_NONE) - { - // .. then finally, point uprop attributes for the given point - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_VERTEX, InFirstValidVertexIndex); - } - - if (InFirstValidPointIndex != INDEX_NONE) - { - // .. then finally, point uprop attributes for the given point - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidPointIndex); - } - - return FoundCount > 0; -} - -bool -FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(UObject* InObject, - const TArray& InAllPropertyAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (const auto& CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current Property Attribute - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - NumSuccess++; -#if defined(HOUDINI_ENGINE_LOGGING) - const FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - const FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); -#endif - } - - return (NumSuccess > 0); -} - -bool -FHoudiniEngineUtils::SetGenericPropertyAttribute( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FHoudiniGenericAttribute& InPropertyAttribute) -{ - HAPI_AttributeOwner AttribOwner; - switch (InPropertyAttribute.AttributeOwner) - { - case EAttribOwner::Point: - AttribOwner = HAPI_ATTROWNER_POINT; - break; - case EAttribOwner::Vertex: - AttribOwner = HAPI_ATTROWNER_VERTEX; - break; - case EAttribOwner::Prim: - AttribOwner = HAPI_ATTROWNER_PRIM; - break; - case EAttribOwner::Detail: - AttribOwner = HAPI_ATTROWNER_DETAIL; - break; - case EAttribOwner::Invalid: - default: - HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InPropertyAttribute.AttributeOwner); - return false; - } - - // Create the attribute via HAPI - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.tupleSize = InPropertyAttribute.AttributeTupleSize; - AttributeInfo.count = InPropertyAttribute.AttributeCount; - AttributeInfo.exists = true; - AttributeInfo.owner = AttribOwner; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - switch(InPropertyAttribute.AttributeType) - { - case (EAttribStorageType::INT): - AttributeInfo.storage = HAPI_STORAGETYPE_INT; - break; - case (EAttribStorageType::INT64): - AttributeInfo.storage = HAPI_STORAGETYPE_INT64; - break; - case (EAttribStorageType::FLOAT): - AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT; - break; - case (EAttribStorageType::FLOAT64): - AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT64; - break; - case (EAttribStorageType::STRING): - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - break; - case (EAttribStorageType::Invalid): - default: - HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Storage Type: %d"), InPropertyAttribute.AttributeType); - return false; - } - - // Create the new attribute - if (HAPI_RESULT_SUCCESS != FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo)) - { - return false; - } - - // The New attribute has been successfully created, set its value - switch (InPropertyAttribute.AttributeType) - { - case EAttribStorageType::INT: - { - TArray TempArray; - TempArray.Reserve(InPropertyAttribute.IntValues.Num()); - for (auto Value : InPropertyAttribute.IntValues) - { - TempArray.Add(static_cast(Value)); - } - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, - TempArray.GetData(), 0, AttributeInfo.count)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } - break; - } - - case EAttribStorageType::INT64: - { -#if PLATFORM_LINUX - // On Linux, we unfortunately cannot guarantee that int64 and HAPI_Int64 are of the same type, - TArray HAPIIntValues; - HAPIIntValues.SetNumZeroed(InPropertyAttribute.IntValues.Num()); - for (int32 n = 0; n < HAPIIntValues.Num(); n++) - HAPIIntValues[n] = (HAPI_Int64)InPropertyAttribute.IntValues[n]; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, - HAPIIntValues.GetData(), 0, AttributeInfo.count)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } -#else - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, - InPropertyAttribute.IntValues.GetData(), 0, AttributeInfo.count)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } -#endif - break; - } - - case EAttribStorageType::FLOAT: - { - - TArray TempArray; - TempArray.Reserve(InPropertyAttribute.DoubleValues.Num()); - for (auto Value : InPropertyAttribute.DoubleValues) - { - TempArray.Add(static_cast(Value)); - } - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, - TempArray.GetData(), 0, AttributeInfo.count)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } - break; - } - - case EAttribStorageType::FLOAT64: - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloat64Data( - FHoudiniEngine::Get().GetSession(), - InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, - InPropertyAttribute.DoubleValues.GetData(), 0, AttributeInfo.count)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } - break; - } - - case EAttribStorageType::STRING: - { - if (HAPI_RESULT_SUCCESS != FHoudiniEngineUtils::SetAttributeStringData( - InPropertyAttribute.StringValues, - InGeoNodeId, - InPartId, - InPropertyAttribute.AttributeName, - AttributeInfo)) - { - HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); - } - break; - } - - default: - // Unsupported storage type - HOUDINI_LOG_WARNING(TEXT("Unsupported storage type: %d"), InPropertyAttribute.AttributeType); - break; - } - - return true; -} - -void -FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const FString& Key, const FString& Value) -{ - if (!Package || Package->IsPendingKill()) - return; - - UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) - MetaData->SetValue(Object, *Key, *Value); -} - - -bool -FHoudiniEngineUtils::AddLevelPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - ULevel* InLevel, - const int32& InCount) -{ - if (InNodeId < 0 || InCount <= 0) - return false; - - if (!InLevel || InLevel->IsPendingKill()) - return false; - - // Extract the level path from the level - FString LevelPath = InLevel->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = InCount; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(InCount); - for (int32 Idx = 0; Idx < InCount; ++Idx) - { - PrimitiveAttrs[Idx] = LevelPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} - - -bool -FHoudiniEngineUtils::AddActorPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - AActor* InActor, - const int32& InCount) -{ - if (InNodeId < 0 || InCount <= 0) - return false; - - if (!InActor || InActor->IsPendingKill()) - return false; - - // Extract the actor path - FString ActorPath = InActor->GetPathName(); - - // Get name of attribute used for Actor path - std::string MarshallingAttributeActorPath = HAPI_UNREAL_ATTRIB_ACTOR_PATH; - - // Marshall in Actor path. - HAPI_AttributeInfo AttributeInfoActorPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoActorPath); - AttributeInfoActorPath.count = InCount; - AttributeInfoActorPath.tupleSize = 1; - AttributeInfoActorPath.exists = true; - AttributeInfoActorPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoActorPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoActorPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, - MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string ActorPathCStr = TCHAR_TO_ANSI(*ActorPath); - const char* ActorPathCStrRaw = ActorPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(InCount); - for (int32 Idx = 0; Idx < InCount; ++Idx) - { - PrimitiveAttrs[Idx] = ActorPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, - MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoActorPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_actor_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} - - -bool -FHoudiniEngineUtils::ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx) -{ - const TArray< FVector2D > & LightmapUVs = RawMesh.WedgeTexCoords[LightmapSourceIdx]; - const TArray< uint32 > & Indices = RawMesh.WedgeIndices; - - if (LightmapUVs.Num() != Indices.Num()) - { - // This is invalid raw mesh; by design we consider that it contains invalid lightmap faces. - return true; - } - - for (int32 Idx = 0; Idx < Indices.Num(); Idx += 3) - { - const FVector2D & uv0 = LightmapUVs[Idx + 0]; - const FVector2D & uv1 = LightmapUVs[Idx + 1]; - const FVector2D & uv2 = LightmapUVs[Idx + 2]; - - if (uv0 == uv1 && uv1 == uv2) - { - // Detect invalid lightmap face, can stop. - return true; - } - } - - // Otherwise there are no invalid lightmap faces. - return false; -} - -void -FHoudiniEngineUtils::CreateSlateNotification( - const FString& NotificationString, const float& NotificationExpire, const float& NotificationFadeOut ) -{ -#if WITH_EDITOR - // Trying to display SlateNotifications while in a background thread will crash UE - if (!IsInGameThread() && !IsInSlateThread() && !IsInAsyncLoadingThread()) - return; - - // Check whether we want to display Slate notifications. - bool bDisplaySlateCookingNotifications = true; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; - - if (!bDisplaySlateCookingNotifications) - return; - - FText NotificationText = FText::FromString(NotificationString); - FNotificationInfo Info(NotificationText); - - Info.bFireAndForget = true; - Info.FadeOutDuration = NotificationFadeOut; - Info.ExpireDuration = NotificationExpire; - - TSharedPtr HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); - if (HoudiniBrush.IsValid()) - Info.Image = HoudiniBrush.Get(); - - FSlateNotificationManager::Get().AddNotification(Info); -#endif - - return; -} - -FString -FHoudiniEngineUtils::GetHoudiniEnginePluginDir() -{ - FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(EnginePluginDir)) - return EnginePluginDir; - - FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(ProjectPluginDir)) - return ProjectPluginDir; - - TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); - FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; - if (FPaths::DirectoryExists(PluginBaseDir)) - return PluginBaseDir; - - HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); - - return EnginePluginDir; -} - - -HAPI_Result -FHoudiniEngineUtils::CreateNode( - const HAPI_NodeId& InParentNodeId, - const FString& InOperatorName, - const FString& InNodeLabel, - const HAPI_Bool& bInCookOnCreation, - HAPI_NodeId* OutNewNodeId) -{ - // Call HAPI::CreateNode - HAPI_Result Result = FHoudiniApi::CreateNode( - FHoudiniEngine::Get().GetSession(), - InParentNodeId, TCHAR_TO_UTF8(*InOperatorName), TCHAR_TO_UTF8(*InNodeLabel), bInCookOnCreation, OutNewNodeId); - - // Return now if CreateNode fialed - if (Result != HAPI_RESULT_SUCCESS) - return Result; - - // Loop on the cook_state status until it's ready - int CurrentStatus = HAPI_State::HAPI_STATE_STARTING_LOAD; - while (CurrentStatus > HAPI_State::HAPI_STATE_MAX_READY_STATE) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), - HAPI_StatusType::HAPI_STATUS_COOK_STATE, &CurrentStatus)) - { - // Exit the loop if GetStatus somehow fails - break; - } - } - - if (CurrentStatus == HAPI_STATE_READY_WITH_FATAL_ERRORS) - { - // Fatal errors - failed - HOUDINI_LOG_ERROR(TEXT("Failed to create node %s - %s"), *InOperatorName, *InNodeLabel); - return HAPI_RESULT_FAILURE; - } - else if (CurrentStatus == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // Mention the errors - still return success - HOUDINI_LOG_WARNING(TEXT("Cook errors when creating node %s - %s"), *InOperatorName, *InNodeLabel); - } - - return HAPI_RESULT_SUCCESS; -} - - -int32 -FHoudiniEngineUtils::HapiGetCookCount(const HAPI_NodeId& InNodeId) -{ - int32 CookCount = -1; - - FHoudiniApi::GetTotalCookCount( - FHoudiniEngine::Get().GetSession(), - InNodeId, HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_ANY, true, &CookCount); - - /* - // TODO: - // Use HAPI_GetCookingTotalCount() when available - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - - int32 CookCount = -1; - HAPI_Result Result = FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), InNodeId, &NodeInfo); - - if (Result != HAPI_RESULT_FAILURE) - { - if (NodeInfo.type != HAPI_NODETYPE_OBJ) - { - // For SOP assets, get the cook count straight from the Asset Node - CookCount = NodeInfo.totalCookCount; - } - else - { - // For OBJ nodes, get the cook count from the display geos - // Retrieve information about each object contained within our asset. - TArray< HAPI_ObjectInfo > ObjectInfos; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(InNodeId, ObjectInfos)) - return false; - - for (auto CurrentHapiObjectInfo : ObjectInfos) - { - // Get the Display Geo's info - HAPI_GeoInfo DisplayHapiGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) - { - continue; - } - - HAPI_NodeInfo DisplayNodeInfo; - FHoudiniApi::NodeInfo_Init(&DisplayNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), DisplayHapiGeoInfo.nodeId, &DisplayNodeInfo)) - { - continue; - } - - CookCount += DisplayNodeInfo.totalCookCount; - } - } - } - */ - - return CookCount; -} - -bool -FHoudiniEngineUtils::GetLevelPathAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutLevelPaths, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_level_path - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_LEVEL_PATH, AttributeInfo, OutLevelPaths, 1, InAttributeOwner)) - { - if (OutLevelPaths.Num() > 0) - return true; - } - - OutLevelPaths.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetOutputNameAttribute(const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, TArray& OutOutputNames) -{ - // --------------------------------------------- - // Attribute: unreal_output_name - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, AttributeInfo, OutOutputNames, 1)) - { - if (OutOutputNames.Num() > 0) - return true; - } - - OutOutputNames.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, AttributeInfo, OutOutputNames, 1)) - { - if (OutOutputNames.Num() > 0) - return true; - } - - OutOutputNames.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetTileAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutTileValues, - const HAPI_AttributeOwner& InAttribOwner) -{ - // --------------------------------------------- - // Attribute: tile - // --------------------------------------------- - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, - AttribInfoTile, - OutTileValues, - 0, - InAttribOwner)) - { - if (OutTileValues.Num() > 0) - return true; - } - - OutTileValues.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - HAPI_AttributeOwner InAttributeOwner, - TArray& OutBakeFolder, - HAPI_PartId InPartId) -{ - OutBakeFolder.Empty(); - - HAPI_AttributeInfo BakeFolderAttribInfo; - FHoudiniApi::AttributeInfo_Init(&BakeFolderAttribInfo); - if (HapiGetAttributeDataAsString( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolderAttribInfo, OutBakeFolder, 1, InAttributeOwner)) - { - if (OutBakeFolder.Num() > 0) - return true; - } - - OutBakeFolder.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - TArray& OutBakeFolder, - HAPI_PartId InPartId) -{ - OutBakeFolder.Empty(); - - if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_PRIM, OutBakeFolder, InPartId)) - { - if (OutBakeFolder.Num() > 0) - return true; - } - - if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_DETAIL, OutBakeFolder, InPartId)) - { - if (OutBakeFolder.Num() > 0) - return true; - } - - OutBakeFolder.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeActorAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeActorNames, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_bake_actor - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_BAKE_ACTOR, AttributeInfo, OutBakeActorNames, 1, InAttributeOwner)) - { - if (OutBakeActorNames.Num() > 0) - return true; - } - - OutBakeActorNames.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeOutlinerFolders, - HAPI_AttributeOwner InAttributeOwner) -{ - // --------------------------------------------- - // Attribute: unreal_bake_outliner_folder - // --------------------------------------------- - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, AttributeInfo, OutBakeOutlinerFolders, 1, InAttributeOwner)) - { - if (OutBakeOutlinerFolders.Num() > 0) - return true; - } - - OutBakeOutlinerFolders.Empty(); - return false; -} - -bool -FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) -{ - if (!InActor || !InDesiredLevel) - return false; - - ULevel* PreviousLevel = InActor->GetLevel(); - if (PreviousLevel == InDesiredLevel) - return true; - - UWorld* CurrentWorld = InActor->GetWorld(); - if(CurrentWorld) - CurrentWorld->RemoveActor(InActor, true); - - //Set the outer of Actor to NewLevel - FHoudiniEngineUtils::RenameObject(InActor, (const TCHAR*)0, InDesiredLevel); - InDesiredLevel->Actors.Add(InActor); - - return true; -} - -bool -FHoudiniEngineUtils::HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions, const bool& bWaitForCompletion) -{ - // Check for an invalid node id - if (InNodeId < 0) - return false; - - // No Cook Options were specified, use the default one - if (InCookOptions == nullptr) - { - // Use the default cook options - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); - } - else - { - // Use the provided CookOptions - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, InCookOptions), false); - } - - // If we don't need to wait for completion, return now - if (!bWaitForCompletion) - return true; - - // Wait for the cook to finish - HAPI_Result Result = HAPI_RESULT_SUCCESS; - while (true) - { - // Get the current cook status - int Status = HAPI_STATE_STARTING_COOK; - HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); - - if (Status == HAPI_STATE_READY) - { - // The cook has been successful. - return true; - } - else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) - { - // There was an error while cooking the node. - //FString CookResultString = FHoudiniEngineUtils::GetCookResult(); - //HOUDINI_LOG_ERROR(); - return false; - } - - // We want to yield a bit. - FPlatformProcess::Sleep(0.1f); - } -} - -#undef LOCTEXT_NAMESPACE \ No newline at end of file +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineUtils.h" +#include "Misc/StringFormatArg.h" + +#if PLATFORM_WINDOWS + #include "Windows/WindowsHWrapper.h" + + // Of course, Windows defines its own GetGeoInfo, + // So we need to undefine that before including HoudiniApi.h to avoid collision... + #ifdef GetGeoInfo + #undef GetGeoInfo + #endif +#endif + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniEngineString.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInput.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntime.h" + +#if WITH_EDITOR + #include "SAssetSelectionWidget.h" +#endif + +#include "HAPI/HAPI_Version.h" + +#include "Misc/Paths.h" +#include "Editor/EditorEngine.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "PropertyEditorModule.h" +#include "Modules/ModuleManager.h" +#include "Engine/StaticMeshSocket.h" +#include "Async/Async.h" +#include "BlueprintEditor.h" +#include "Toolkits/AssetEditorManager.h" +#include "Engine/BlueprintGeneratedClass.h" +#include "UObject/MetaData.h" +#include "RawMesh.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Interfaces/IPluginManager.h" +//#include "Kismet/BlueprintEditor.h" +#include "SSCSEditor.h" +#include "Engine/WorldComposition.h" + +#if WITH_EDITOR + #include "Interfaces/IMainFrameModule.h" +#endif + +#include + +#include "AssetRegistryModule.h" +#include "FileHelpers.h" +#include "Factories/WorldFactory.h" +#include "HAL/FileManager.h" + +#if WITH_EDITOR + #include "EditorModeManager.h" + #include "EditorModes.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// HAPI_Result strings +const FString kResultStringSuccess(TEXT("Success")); +const FString kResultStringFailure(TEXT("Generic Failure")); +const FString kResultStringAlreadyInitialized(TEXT("Already Initialized")); +const FString kResultStringNotInitialized(TEXT("Not Initialized")); +const FString kResultStringCannotLoadFile(TEXT("Unable to Load File")); +const FString kResultStringParmSetFailed(TEXT("Failed Setting Parameter")); +const FString kResultStringInvalidArgument(TEXT("Invalid Argument")); +const FString kResultStringCannotLoadGeo(TEXT("Uneable to Load Geometry")); +const FString kResultStringCannotGeneratePreset(TEXT("Uneable to Generate Preset")); +const FString kResultStringCannotLoadPreset(TEXT("Uneable to Load Preset")); +const FString kResultStringAssetDefAlrealdyLoaded(TEXT("Asset definition already loaded")); +const FString kResultStringNoLicenseFound(TEXT("No License Found")); +const FString kResultStringDisallowedNCLicenseFound(TEXT("Disallowed Non Commercial License found")); +const FString kResultStringDisallowedNCAssetWithCLicense(TEXT("Disallowed Non Commercial Asset With Commercial License")); +const FString kResultStringDisallowedNCAssetWithLCLicense(TEXT("Disallowed Non Commercial Asset With Limited Commercial License")); +const FString kResultStringDisallowedLCAssetWithCLicense(TEXT("Disallowed Limited Commercial Asset With Commercial License")); +const FString kResultStringDisallowedHengineIndieWith3PartyPlugin(TEXT("Disallowed Houdini Engine Indie With 3rd Party Plugin")); +const FString kResultStringAssetInvalid(TEXT("Invalid Asset")); +const FString kResultStringNodeInvalid(TEXT("Invalid Node")); +const FString kResultStringUserInterrupted(TEXT("User Interrupt")); +const FString kResultStringInvalidSession(TEXT("Invalid Session")); +const FString kResultStringUnknowFailure(TEXT("Unknown Failure")); + +#define DebugTextLine TEXT("===================================") + +const int32 +FHoudiniEngineUtils::PackageGUIDComponentNameLength = 12; + +const int32 +FHoudiniEngineUtils::PackageGUIDItemNameLength = 8; + +const FString +FHoudiniEngineUtils::GetErrorDescription(HAPI_Result Result) +{ + if (Result == HAPI_RESULT_SUCCESS) + { + return kResultStringSuccess; + } + else + { + switch (Result) + { + case HAPI_RESULT_FAILURE: + { + return kResultStringFailure; + } + + case HAPI_RESULT_ALREADY_INITIALIZED: + { + return kResultStringAlreadyInitialized; + } + + case HAPI_RESULT_NOT_INITIALIZED: + { + return kResultStringNotInitialized; + } + + case HAPI_RESULT_CANT_LOADFILE: + { + return kResultStringCannotLoadFile; + } + + case HAPI_RESULT_PARM_SET_FAILED: + { + return kResultStringParmSetFailed; + } + + case HAPI_RESULT_INVALID_ARGUMENT: + { + return kResultStringInvalidArgument; + } + + case HAPI_RESULT_CANT_LOAD_GEO: + { + return kResultStringCannotLoadGeo; + } + + case HAPI_RESULT_CANT_GENERATE_PRESET: + { + return kResultStringCannotGeneratePreset; + } + + case HAPI_RESULT_CANT_LOAD_PRESET: + { + return kResultStringCannotLoadPreset; + } + + case HAPI_RESULT_ASSET_DEF_ALREADY_LOADED: + { + return kResultStringAssetDefAlrealdyLoaded; + } + + case HAPI_RESULT_NO_LICENSE_FOUND: + { + return kResultStringNoLicenseFound; + } + + case HAPI_RESULT_DISALLOWED_NC_LICENSE_FOUND: + { + return kResultStringDisallowedNCLicenseFound; + } + + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_C_LICENSE: + { + return kResultStringDisallowedNCAssetWithCLicense; + } + + case HAPI_RESULT_DISALLOWED_NC_ASSET_WITH_LC_LICENSE: + { + return kResultStringDisallowedNCAssetWithLCLicense; + } + + case HAPI_RESULT_DISALLOWED_LC_ASSET_WITH_C_LICENSE: + { + return kResultStringDisallowedLCAssetWithCLicense; + } + + case HAPI_RESULT_DISALLOWED_HENGINEINDIE_W_3PARTY_PLUGIN: + { + return kResultStringDisallowedHengineIndieWith3PartyPlugin; + } + + case HAPI_RESULT_ASSET_INVALID: + { + return kResultStringAssetInvalid; + } + + case HAPI_RESULT_NODE_INVALID: + { + return kResultStringNodeInvalid; + } + + case HAPI_RESULT_USER_INTERRUPTED: + { + return kResultStringUserInterrupted; + } + + case HAPI_RESULT_INVALID_SESSION: + { + return kResultStringInvalidSession; + } + + default: + { + return kResultStringUnknowFailure; + } + }; + } +} + +const FString +FHoudiniEngineUtils::GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity) +{ + const HAPI_Session* SessionPtr = FHoudiniEngine::Get().GetSession(); + if (!SessionPtr) + { + // No valid session + return FString(TEXT("No valid Houdini Engine session.")); + } + + int32 StatusBufferLength = 0; + HAPI_Result Result = FHoudiniApi::GetStatusStringBufLength( + SessionPtr, status_type, verbosity, &StatusBufferLength); + + if (Result == HAPI_RESULT_INVALID_SESSION) + { + // Let FHoudiniEngine know that the sesion is now invalid to "Stop" the invalid session + // and clean things up + FHoudiniEngine::Get().OnSessionLost(); + } + + if (StatusBufferLength > 0) + { + TArray< char > StatusStringBuffer; + StatusStringBuffer.SetNumZeroed(StatusBufferLength); + FHoudiniApi::GetStatusString( + SessionPtr, status_type, &StatusStringBuffer[0], StatusBufferLength); + + return FString(UTF8_TO_TCHAR(&StatusStringBuffer[0])); + } + + return FString(TEXT("")); +} + +const FString +FHoudiniEngineUtils::GetCookResult() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_RESULT, HAPI_STATUSVERBOSITY_MESSAGES); +} + +const FString +FHoudiniEngineUtils::GetCookState() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); +} + +const FString +FHoudiniEngineUtils::GetErrorDescription() +{ + return FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_CALL_RESULT, HAPI_STATUSVERBOSITY_ERRORS); +} + +const FString +FHoudiniEngineUtils::GetConnectionError() +{ + int32 ErrorLength = 0; + FHoudiniApi::GetConnectionErrorLength(&ErrorLength); + + if(ErrorLength <= 0) + return FString(TEXT("")); + + TArray ConnectionStringBuffer; + ConnectionStringBuffer.SetNumZeroed(ErrorLength); + FHoudiniApi::GetConnectionError(&ConnectionStringBuffer[0], ErrorLength, true); + + return FString(UTF8_TO_TCHAR(&ConnectionStringBuffer[0])); +} + +const FString +FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId) +{ + int32 NodeErrorLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeNodeCookResult( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_StatusVerbosity::HAPI_STATUSVERBOSITY_ALL, &NodeErrorLength)) + { + NodeErrorLength = 0; + } + + FString NodeError; + if (NodeErrorLength > 0) + { + TArray NodeErrorBuffer; + NodeErrorBuffer.SetNumZeroed(NodeErrorLength); + FHoudiniApi::GetComposedNodeCookResult( + FHoudiniEngine::Get().GetSession(), &NodeErrorBuffer[0], NodeErrorLength); + + NodeError = FString(UTF8_TO_TCHAR(&NodeErrorBuffer[0])); + } + + return NodeError; +} + +const FString +FHoudiniEngineUtils::GetCookLog(TArray& InHACs) +{ + FString CookLog; + + // Get fetch cook status. + FString CookResult = FHoudiniEngineUtils::GetCookResult(); + if (!CookResult.IsEmpty()) + CookLog += TEXT("Cook Results:\n") + CookResult + TEXT("\n\n"); + + // Add the cook state + FString CookState = FHoudiniEngineUtils::GetCookState(); + if (!CookState.IsEmpty()) + CookLog += TEXT("Cook State:\n") + CookState + TEXT("\n\n"); + + // Error Description + FString Error = FHoudiniEngineUtils::GetErrorDescription(); + if (!Error.IsEmpty()) + CookLog += TEXT("Error Description:\n") + Error + TEXT("\n\n"); + + // Iterates on all the selected HAC and get their node errors + for (auto& HAC : InHACs) + { + if (!HAC || HAC->IsPendingKill()) + continue; + + // Get the node errors, warnings and messages + FString NodeErrors = FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(HAC->GetAssetId()); + if (NodeErrors.IsEmpty()) + continue; + + CookLog += NodeErrors; + } + + if (CookLog.IsEmpty()) + { + // See if a failed HAPI initialization / invalid session is preventing us from getting the cook log + if (!FHoudiniApi::IsHAPIInitialized()) + { + CookLog += TEXT("\n\nThe Houdini Engine API Library (HAPI) has not been initialized properly.\n\n"); + } + else + { + const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) + { + CookLog += TEXT("\n\nThe current Houdini Engine Session is not valid.\n\n"); + } + else if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) + { + CookLog += TEXT("\n\nThe current Houdini Engine Session has not been initialized properly.\n\n"); + } + } + + if (!CookLog.IsEmpty()) + { + CookLog += TEXT("Please try to restart the current Houdini Engine session via File > Restart Houdini Engine Session.\n\n"); + } + else + { + CookLog = TEXT("\n\nThe cook log is empty...\n\n"); + } + } + + return CookLog; +} + +const FString +FHoudiniEngineUtils::GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + FString HelpString = TEXT(""); + if (!HoudiniAssetComponent) + return HelpString; + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HAPI_NodeId AssetId = HoudiniAssetComponent->GetAssetId(); + if (AssetId < 0) + return HelpString; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), HelpString); + + if (!FHoudiniEngineString::ToFString(AssetInfo.helpTextSH, HelpString)) + return HelpString; + + if (!FHoudiniEngineString::ToFString(AssetInfo.helpURLSH, HelpString)) + return HelpString; + + if (HelpString.IsEmpty()) + HelpString = TEXT("No Asset Help Found"); + + return HelpString; +} + +void +FHoudiniEngineUtils::ConvertUnrealString(const FString & UnrealString, std::string & String) +{ + String = TCHAR_TO_UTF8(*UnrealString); +} + +UWorld* +FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreateMissingPackage, bool& bOutCreatedPackage) +{ + AActor* Result = nullptr; + UWorld* PackageWorld = nullptr; + + bOutCreatedPackage = false; + + // Try to load existing UWorld from the tile package path. + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!Package) + Package = LoadPackage(nullptr, *PackagePath, LOAD_None); + if (Package) + { + // If the package is not valid (pending kill) rename it + if (Package->IsPendingKill()) + { + if (bCreateMissingPackage) + { + Package->Rename( + *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); + } + } + else + { + PackageWorld = UWorld::FindWorldInPackage(Package); + } + } + + if (!IsValid(PackageWorld) && bCreateMissingPackage) + { + // The map for this tile does not exist. Create one + UWorldFactory* Factory = NewObject(); + Factory->WorldType = EWorldType::Inactive; // World that is being loaded but not currently edited by editor. + PackageWorld = CastChecked(Factory->FactoryCreateNew(UWorld::StaticClass(), Package, NAME_None, RF_Public | RF_Standalone, NULL, GWarn)); + + if (IsValid(PackageWorld)) + { + PackageWorld->PostEditChange(); + PackageWorld->MarkPackageDirty(); + + if(FPackageName::IsValidLongPackageName(PackagePath)) + { + const FString PackageFilename = FPackageName::LongPackageNameToFilename(PackagePath); + bool bSaved = FEditorFileUtils::SaveLevel(PackageWorld->PersistentLevel, *PackageFilename); + } + + FAssetRegistryModule::AssetCreated(PackageWorld); + + bOutCreatedPackage = true; + } + } + + return PackageWorld; +} + +bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + UWorld* CurrentWorld, + const FString& PackagePath, + bool bCreateMissingPackage, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bOutPackageCreated, + bool& bPackageInWorld) +{ + UWorld* PackageWorld = FindWorldInPackage(PackagePath, bCreateMissingPackage, bOutPackageCreated); + if (!IsValid(PackageWorld)) + return false; + + if (PackageWorld->PersistentLevel == CurrentWorld->PersistentLevel) + { + // The loaded world and the package world is one and the same. + OutWorld = CurrentWorld; + OutLevel = CurrentWorld->PersistentLevel; + bPackageInWorld = true; + return true; + } + + if (CurrentWorld->GetLevels().Contains(PackageWorld->PersistentLevel)) + { + // The package level is loaded into CurrentWorld. + OutWorld = CurrentWorld; + OutLevel = PackageWorld->PersistentLevel; + bPackageInWorld = true; + return true; + } + + // The package level is not loaded at all. Send back the on-disk assets. + OutWorld = PackageWorld; + OutLevel = PackageWorld->PersistentLevel; + bPackageInWorld = false; + return true; +} + +void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) +{ + FString WorldPath = FPaths::GetPath(InWorld->GetPathName()); + IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); + TArray Packages; + Packages.Add(WorldPath); + AssetRegistry.ScanPathsSynchronous(Packages, true); +} + +AActor* FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) +{ + // AActor* NamedActor = FindObject(Outer, *InName, false); + // Find ANY actor in the world matching the given name. + AActor* NamedActor = FindActorInWorld(InWorld, FName(InName)); + OutFoundActor = NamedActor; + bool bShouldRename = false; + if (NamedActor) + { + if (NamedActor->GetClass()->IsChildOf(InClass) && !NamedActor->IsPendingKill()) + { + return NamedActor; + } + else + { + FString Suffix; + bool bShouldUpdateLabel = false; + if (NamedActor->IsPendingKill()) + Suffix = "_pendingkill"; + else + Suffix = "_0"; // A previous actor that had the same name. + const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName+Suffix); + } + } + return nullptr; +} + +void FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) +{ + LogPackageInfo( LoadPackage(nullptr, *InLongPackageName, 0) ); +} + +void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) +{ + HOUDINI_LOG_MESSAGE(DebugTextLine); + HOUDINI_LOG_MESSAGE(TEXT("= LogPackageInfo")); + if (!IsValid(InPackage)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Invalid package.")); + HOUDINI_LOG_MESSAGE(DebugTextLine); + return; + } + + HOUDINI_LOG_MESSAGE(TEXT(" = Filename: %s"), *(InPackage->FileName.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = Package Id: %d"), InPackage->GetPackageId().ValueForDebugging()); + HOUDINI_LOG_MESSAGE(TEXT(" = File size: %d"), InPackage->FileSize); + HOUDINI_LOG_MESSAGE(TEXT(" = Contains map: %d"), InPackage->ContainsMap()); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Fully Loaded: %d"), InPackage->IsFullyLoaded()); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Dirty: %d"), InPackage->IsDirty()); + + if (InPackage->WorldTileInfo.IsValid()) + { + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Position: %s"), *(InPackage->WorldTileInfo->Position.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Absolute Position: %s"), *(InPackage->WorldTileInfo->AbsolutePosition.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Bounds: %s"), *(InPackage->WorldTileInfo->Bounds.ToString())); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - HidInTileView: %d"), InPackage->WorldTileInfo->bHideInTileView); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - ZOrder: %d"), InPackage->WorldTileInfo->ZOrder); + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo - Parent tile package: %s"), *(InPackage->WorldTileInfo->ParentTilePackageName)); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT(" = WorldTileInfo: NULL")); + } + + HOUDINI_LOG_MESSAGE(DebugTextLine); +} + +void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) +{ + UPackage* Package = LoadPackage(nullptr, *InLongPackageName, 0); + UWorld* World = nullptr; + + if (IsValid(Package)) + { + World = UWorld::FindWorldInPackage(Package); + } + + LogWorldInfo(World); +} + +void FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) +{ + + HOUDINI_LOG_MESSAGE(DebugTextLine); + HOUDINI_LOG_MESSAGE(TEXT("= LogWorldInfo")); + if (!IsValid(InWorld)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Invalid world.")); + HOUDINI_LOG_MESSAGE(DebugTextLine); + return; + } + + // UWorld lacks const-correctness on certain accessors + UWorld* NonConstWorld = const_cast(InWorld); + + HOUDINI_LOG_MESSAGE(TEXT(" = Path Name: %s"), *(InWorld->GetPathName())); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Editor World: %d"), InWorld->IsEditorWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Game World: %d"), InWorld->IsGameWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Is Preview World: %d"), InWorld->IsPreviewWorld() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Actor Count: %d"), NonConstWorld->GetActorCount() ); + HOUDINI_LOG_MESSAGE(TEXT(" = Num Levels: %d"), InWorld->GetNumLevels() ); + + if (IsValid(InWorld->WorldComposition)) + { + HOUDINI_LOG_MESSAGE(TEXT(" = Composition - Num Tiles: %d"), InWorld->WorldComposition->GetTilesList().Num() ); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT(" = World Composition NULL") ); + } + + + + HOUDINI_LOG_MESSAGE(DebugTextLine); +} + +FString +FHoudiniEngineUtils::HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType) +{ + switch (InEventType) + { + case HAPI_PDG_EVENT_NULL: + return TEXT("HAPI_PDG_EVENT_NULL"); + + case HAPI_PDG_EVENT_WORKITEM_ADD: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE"); + case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_DEP"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_PARENT"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT"); + + case HAPI_PDG_EVENT_NODE_CLEAR: + return TEXT("HAPI_PDG_EVENT_NODE_CLEAR"); + + case HAPI_PDG_EVENT_COOK_ERROR: + return TEXT("HAPI_PDG_EVENT_COOK_ERROR"); + case HAPI_PDG_EVENT_COOK_WARNING: + return TEXT("HAPI_PDG_EVENT_COOK_WARNING"); + + case HAPI_PDG_EVENT_COOK_COMPLETE: + return TEXT("HAPI_PDG_EVENT_COOK_COMPLETE"); + + case HAPI_PDG_EVENT_DIRTY_START: + return TEXT("HAPI_PDG_EVENT_DIRTY_START"); + case HAPI_PDG_EVENT_DIRTY_STOP: + return TEXT("HAPI_PDG_EVENT_DIRTY_STOP"); + + case HAPI_PDG_EVENT_DIRTY_ALL: + return TEXT("HAPI_PDG_EVENT_DIRTY_ALL"); + + case HAPI_PDG_EVENT_UI_SELECT: + return TEXT("HAPI_PDG_EVENT_UI_SELECT"); + + case HAPI_PDG_EVENT_NODE_CREATE: + return TEXT("HAPI_PDG_EVENT_NODE_CREATE"); + case HAPI_PDG_EVENT_NODE_REMOVE: + return TEXT("HAPI_PDG_EVENT_NODE_REMOVE"); + case HAPI_PDG_EVENT_NODE_RENAME: + return TEXT("HAPI_PDG_EVENT_NODE_RENAME"); + case HAPI_PDG_EVENT_NODE_CONNECT: + return TEXT("HAPI_PDG_EVENT_NODE_CONNECT"); + case HAPI_PDG_EVENT_NODE_DISCONNECT: + return TEXT("HAPI_PDG_EVENT_NODE_DISCONNECT"); + + case HAPI_PDG_EVENT_WORKITEM_SET_INT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_INT"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FLOAT"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_STRING: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_STRING"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FILE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_FILE"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: + return TEXT("HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_MERGE: + return TEXT("HAPI_PDG_EVENT_WORKITEM_MERGE"); // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_RESULT: + return TEXT("HAPI_PDG_EVENT_WORKITEM_RESULT"); + + case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED + return TEXT("HAPI_PDG_EVENT_WORKITEM_PRIORITY"); + + case HAPI_PDG_EVENT_COOK_START: + return TEXT("HAPI_PDG_EVENT_COOK_START"); + + case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: + return TEXT("HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR"); + case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: + return TEXT("HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR"); + + case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: + return TEXT("HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE"); + + case HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED: + return TEXT("HAPI_PDG_EVENT_BATCH_ITEM_INITIALIZED"); + + case HAPI_PDG_EVENT_ALL: + return TEXT("HAPI_PDG_EVENT_ALL"); + case HAPI_PDG_EVENT_LOG: + return TEXT("HAPI_PDG_EVENT_LOG"); + + case HAPI_PDG_EVENT_SCHEDULER_ADDED: + return TEXT("HAPI_PDG_EVENT_SCHEDULER_ADDED"); + case HAPI_PDG_EVENT_SCHEDULER_REMOVED: + return TEXT("HAPI_PDG_EVENT_SCHEDULER_REMOVED"); + case HAPI_PDG_EVENT_SET_SCHEDULER: + return TEXT("HAPI_PDG_EVENT_SET_SCHEDULER"); + + case HAPI_PDG_EVENT_SERVICE_MANAGER_ALL: + return TEXT("HAPI_PDG_EVENT_SERVICE_MANAGER_ALL"); + + case HAPI_PDG_CONTEXT_EVENTS: + return TEXT("HAPI_PDG_CONTEXT_EVENTS"); + default: + break; + } + + return FString::Printf(TEXT("Unknown HAPI_PDG_EventType %d"), InEventType); +} + +FString +FHoudiniEngineUtils::HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState) +{ + switch (InWorkitemState) + { + case HAPI_PDG_WORKITEM_UNDEFINED: + return TEXT("HAPI_PDG_WORKITEM_UNDEFINED"); + case HAPI_PDG_WORKITEM_UNCOOKED: + return TEXT("HAPI_PDG_WORKITEM_UNCOOKED"); + case HAPI_PDG_WORKITEM_WAITING: + return TEXT("HAPI_PDG_WORKITEM_WAITING"); + case HAPI_PDG_WORKITEM_SCHEDULED: + return TEXT("HAPI_PDG_WORKITEM_SCHEDULED"); + case HAPI_PDG_WORKITEM_COOKING: + return TEXT("HAPI_PDG_WORKITEM_COOKING"); + case HAPI_PDG_WORKITEM_COOKED_SUCCESS: + return TEXT("HAPI_PDG_WORKITEM_COOKED_SUCCESS"); + case HAPI_PDG_WORKITEM_COOKED_CACHE: + return TEXT("HAPI_PDG_WORKITEM_COOKED_CACHE"); + case HAPI_PDG_WORKITEM_COOKED_FAIL: + return TEXT("HAPI_PDG_WORKITEM_COOKED_FAIL"); + case HAPI_PDG_WORKITEM_COOKED_CANCEL: + return TEXT("HAPI_PDG_WORKITEM_COOKED_CANCEL"); + case HAPI_PDG_WORKITEM_DIRTY: + return TEXT("HAPI_PDG_WORKITEM_DIRTY"); + default: + break; + } + + return FString::Printf(TEXT("Unknown HAPI_PDG_WorkitemState %d"), InWorkitemState); +} + + +// Centralized call to track renaming of objects +bool FHoudiniEngineUtils::RenameObject(UObject* Object, const TCHAR* NewName /*= nullptr*/, UObject* NewOuter /*= nullptr*/, ERenameFlags Flags /*= REN_None*/) +{ + check(Object); + if (AActor* Actor = Cast(Object)) + { + if (Actor->IsPackageExternal()) + { + // There should be no need to choose a specific name for an actor in Houdini Engine, instead setting its label should be enough. + if (FHoudiniEngineRuntimeUtils::SetActorLabel(Actor, NewName)) + { + HOUDINI_LOG_WARNING(TEXT("Called SetActorLabel(%s) on external actor %s instead of Rename : Explicit naming of an actor that is saved in its own external package is prone to cause name clashes when submitting the file.)"), NewName, *Actor->GetName()); + } + // Force to return false (make sure nothing in Houdini Engine plugin relies on actor being renamed to provided name) + return false; + } + } + return Object->Rename(NewName, NewOuter, Flags); +} + +FName +FHoudiniEngineUtils::RenameToUniqueActor(AActor* InActor, const FString& InName) +{ + const FName NewName = MakeUniqueObjectName(InActor->GetOuter(), InActor->GetClass(), FName(InName)); + + FHoudiniEngineUtils::RenameObject(InActor, *(NewName.ToString())); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName.ToString()); + + return NewName; +} + +UObject* +FHoudiniEngineUtils::SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel) +{ + check(InActor); + + UObject* PrevObj = nullptr; + UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, InActor->GetOuter(), *InName, true); + if (ExistingObject && ExistingObject != InActor) + { + // Rename the existing object + const FName NewName = MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), FName(InName + TEXT("_old"))); + FHoudiniEngineUtils::RenameObject(ExistingObject, *(NewName.ToString())); + PrevObj = ExistingObject; + } + + FHoudiniEngineUtils::RenameObject(InActor, *InName); + + if (UpdateLabel) + { + //InActor->SetActorLabel(InName, true); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, InName); + InActor->Modify(true); + } + + return PrevObj; +} + +void +FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + FHoudiniPackageParams& OutPackageParams, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FString &BakeFolder, + const FString &ObjectName, + const FString &HoudiniAssetName, + EPackageReplaceMode InReplaceMode, + bool bAutomaticallySetAttemptToLoadMissingPackages) +{ + OutPackageParams.GeoId = InIdentifier.GeoId; + OutPackageParams.ObjectId = InIdentifier.ObjectId; + OutPackageParams.PartId = InIdentifier.PartId; + OutPackageParams.BakeFolder = BakeFolder; + OutPackageParams.PackageMode = EPackageMode::Bake; + OutPackageParams.ReplaceMode = InReplaceMode; + OutPackageParams.HoudiniAssetName = HoudiniAssetName; + OutPackageParams.HoudiniAssetActorName = HoudiniAssetName; + OutPackageParams.ObjectName = ObjectName; +} + +void +FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + UWorld* const InWorldContext, + const UHoudiniAssetComponent* HoudiniAssetComponent, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FString &InDefaultObjectName, + const FString &InHoudiniAssetName, + FHoudiniPackageParams& OutPackageParams, + FHoudiniAttributeResolver& OutResolver, + const FString &InDefaultBakeFolder, + EPackageReplaceMode InReplaceMode, + bool bAutomaticallySetAttemptToLoadMissingPackages, + bool bInSkipObjectNameResolutionAndUseDefault, + bool bInSkipBakeFolderResolutionAndUseDefault) +{ + // Configure OutPackageParams with the default (UI value first then fallback to default from settings) object name + // and bake folder. We use the "initial" PackageParams as a helper to populate tokens for the resolver. + // + // User specified attributes (eg unreal_bake_folder) are then resolved, with the defaults being those tokens configured + // from the initial PackageParams. Once resolved, we updated the relevant fields in PackageParams + // (ObjectName and BakeFolder), and update the resolver tokens with these final values. + // + // The resolver is then ready to be used to resolve the rest of the user attributes, such as unreal_level_path. + // + const FString DefaultBakeFolder = !InDefaultBakeFolder.IsEmpty() ? InDefaultBakeFolder : + FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + const bool bHasBakeNameUIOverride = !InOutputObject.BakeName.IsEmpty(); + FillInPackageParamsForBakingOutput( + OutPackageParams, + InIdentifier, + DefaultBakeFolder, + bHasBakeNameUIOverride ? InOutputObject.BakeName : InDefaultObjectName, + InHoudiniAssetName, + InReplaceMode, + bAutomaticallySetAttemptToLoadMissingPackages); + + const TMap& CachedAttributes = InOutputObject.CachedAttributes; + TMap Tokens = InOutputObject.CachedTokens; + OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); + OutResolver.SetCachedAttributes(CachedAttributes); + OutResolver.SetTokensFromStringMap(Tokens); + +#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING + // Log the cached attributes and tokens for debugging + OutResolver.LogCachedAttributesAndTokens(); +#endif + + if (!bInSkipObjectNameResolutionAndUseDefault) + { + // Resolve the object name + // TODO: currently the UI override is checked first (this should probably change so that attributes are used first) + FString ObjectName; + if (bHasBakeNameUIOverride) + { + ObjectName = InOutputObject.BakeName; + } + else + { + ObjectName = OutResolver.ResolveOutputName(); + if (ObjectName.IsEmpty()) + ObjectName = InDefaultObjectName; + } + // Update the object name in the package params and also update its token + OutPackageParams.ObjectName = ObjectName; + OutResolver.SetToken("object_name", OutPackageParams.ObjectName); + } + + if (!bInSkipBakeFolderResolutionAndUseDefault) + { + // Now resolve the bake folder + const FString BakeFolder = OutResolver.ResolveBakeFolder(); + if (!BakeFolder.IsEmpty()) + OutPackageParams.BakeFolder = BakeFolder; + } + + if (!bInSkipObjectNameResolutionAndUseDefault || !bInSkipBakeFolderResolutionAndUseDefault) + { + // Update the tokens from the package params + OutPackageParams.UpdateTokensFromParams(InWorldContext, HoudiniAssetComponent, Tokens); + OutResolver.SetTokensFromStringMap(Tokens); + +#if defined(HOUDINI_ENGINE_DEBUG_BAKING) && HOUDINI_ENGINE_DEBUG_BAKING + // Log the final tokens + OutResolver.LogCachedAttributesAndTokens(); +#endif + } +} + + +bool +FHoudiniEngineUtils::RepopulateFoliageTypeListInUI() +{ + // Update / repopulate the foliage editor mode's mesh list if the foliage editor mode is active. + // TODO: find a better way to do this, the relevant functions are in FEdModeFoliage and FFoliageEdModeToolkit are not API exported + FEditorModeTools& EditorModeTools = GLevelEditorModeTools(); + if (EditorModeTools.IsModeActive(FBuiltinEditorModes::EM_Foliage)) + { + EditorModeTools.DeactivateMode(FBuiltinEditorModes::EM_Foliage); + EditorModeTools.ActivateMode(FBuiltinEditorModes::EM_Foliage); + return true; + } + + return false; +} + +void +FHoudiniEngineUtils::GatherLandscapeInputs( + UHoudiniAssetComponent* HAC, + TArray& AllInputLandscapes, + TArray& InputLandscapesToUpdate) +{ + if (!IsValid(HAC)) + return; + + int32 NumInputs = HAC->GetNumInputs(); + + for (int32 InputIndex = 0; InputIndex < NumInputs; InputIndex++ ) + { + UHoudiniInput* CurrentInput = HAC->GetInputAt(InputIndex); + if (!CurrentInput) + continue; + + if (CurrentInput->GetInputType() == EHoudiniInputType::World) + { + // Check if we have any landscapes as world inputs. + CurrentInput->ForAllHoudiniInputObjects([&AllInputLandscapes](UHoudiniInputObject* InputObject) + { + UHoudiniInputLandscape* InputLandscape = Cast(InputObject); + if (InputLandscape) + { + ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); + if (IsValid(LandscapeProxy)) + { + AllInputLandscapes.Add(LandscapeProxy); + } + } + }); + } + + if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) + continue; + + // Get the landscape input's landscape + ALandscapeProxy* InputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); + if (!InputLandscape) + continue; + + AllInputLandscapes.Add(InputLandscape); + + if (CurrentInput->GetUpdateInputLandscape()) + InputLandscapesToUpdate.Add(InputLandscape); + } +} + + +UHoudiniAssetComponent* +FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(const UObject* Obj) +{ + if (!IsValid(Obj)) + return nullptr; + + // Check the direct Outer + UHoudiniAssetComponent* OuterHAC = Cast(Obj->GetOuter()); + if(IsValid(OuterHAC)) + return OuterHAC; + + // Check the whole outer chain + OuterHAC = Obj->GetTypedOuter(); + if (IsValid(OuterHAC)) + return OuterHAC; + + // Finally check if the Object itself is a HaC + UObject* NonConstObj = const_cast(Obj); + OuterHAC = Cast(NonConstObj); + if (IsValid(OuterHAC)) + return OuterHAC; + + return nullptr; +} + + +FString +FHoudiniEngineUtils::ComputeVersionString(bool ExtraDigit) +{ + // Compute Houdini version string. + FString HoudiniVersionString = FString::Printf( + TEXT("%d.%d.%s%d"), HAPI_VERSION_HOUDINI_MAJOR, + HAPI_VERSION_HOUDINI_MINOR, + (ExtraDigit ? (TEXT("0.")) : TEXT("")), + HAPI_VERSION_HOUDINI_BUILD); + + // If we have a patch version, we need to append it. + if (HAPI_VERSION_HOUDINI_PATCH > 0) + HoudiniVersionString = FString::Printf(TEXT("%s.%d"), *HoudiniVersionString, HAPI_VERSION_HOUDINI_PATCH); + return HoudiniVersionString; +} + + +void * +FHoudiniEngineUtils::LoadLibHAPI(FString & StoredLibHAPILocation) +{ + FString HFSPath = TEXT(""); + void * HAPILibraryHandle = nullptr; + + // Look up HAPI_PATH environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . + FString HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HAPI_PATH")); + if (!HFS_ENV_VAR.IsEmpty()) + HFSPath = HFS_ENV_VAR; + + // Look up environment variable; if it is not defined, 0 will stored in HFS_ENV_VARIABLE . + HFS_ENV_VAR = FPlatformMisc::GetEnvironmentVariable(TEXT("HFS")); + if (!HFS_ENV_VAR.IsEmpty()) + HFSPath = HFS_ENV_VAR; + + // Get platform specific name of libHAPI. + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + + // If we have a custom location specified through settings, attempt to use that. + bool bCustomPathFound = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->bUseCustomHoudiniLocation) + { + // Create full path to libHAPI binary. + FString CustomHoudiniLocationPath = HoudiniRuntimeSettings->CustomHoudiniLocation.Path; + if (!CustomHoudiniLocationPath.IsEmpty()) + { + // Convert path to absolute if it is relative. + if (FPaths::IsRelative(CustomHoudiniLocationPath)) + CustomHoudiniLocationPath = FPaths::ConvertRelativePathToFull(CustomHoudiniLocationPath); + + FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPICustomPath)) + { + HFSPath = CustomHoudiniLocationPath; + bCustomPathFound = true; + } + } + } + + // We have HFS environment variable defined (or custom location), attempt to load libHAPI from it. + if (!HFSPath.IsEmpty()) + { + if (!bCustomPathFound) + { +#if PLATFORM_WINDOWS + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_WINDOWS); +#elif PLATFORM_MAC + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_MAC); +#elif PLATFORM_LINUX + HFSPath += FString::Printf(TEXT("/%s"), HAPI_HFS_SUBFOLDER_LINUX); +#endif + } + + // Create full path to libHAPI binary. + FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + // libHAPI binary exists at specified location, attempt to load it. + FPlatformProcess::PushDllDirectory(*HFSPath); +#if PLATFORM_WINDOWS + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); +#elif PLATFORM_MAC || PLATFORM_LINUX + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); +#endif + FPlatformProcess::PopDllDirectory(*HFSPath); + + // If library has been loaded successfully we can stop. + if ( HAPILibraryHandle ) + { + if (bCustomPathFound) + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from custom path %s"), *LibHAPIName, *HFSPath); + else + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from HFS environment path %s"), *LibHAPIName, *HFSPath); + + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + } + + // Otherwise, we will attempt to detect Houdini installation. + FString HoudiniLocation = TEXT(HOUDINI_ENGINE_HFS_PATH); + FString LibHAPIPath; + + // Compute Houdini version string. + FString HoudiniVersionString = ComputeVersionString(false); + +#if PLATFORM_WINDOWS + + // On Windows, we have also hardcoded HFS path in plugin configuration file; attempt to load from it. + HFSPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, HAPI_HFS_SUBFOLDER_WINDOWS); + + // Create full path to libHAPI binary. + LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HFSPath); + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIName); + FPlatformProcess::PopDllDirectory(*HFSPath); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from Plugin defined HFS path %s"), *LibHAPIName, *HFSPath); + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + + // As a second attempt, on Windows, we try to look up location of Houdini Engine in the registry. + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini Engine"), StoredLibHAPILocation, false); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // As a third attempt, we try to look up location of Houdini installation (not Houdini Engine) in the registry. + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini"), StoredLibHAPILocation, false); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // Do similar registry lookups for the 32 bits registry + // Look for the Houdini Engine registry install path + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini Engine"), StoredLibHAPILocation, true); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // ... and for the Houdini registry install path + HAPILibraryHandle = FHoudiniEngineUtils::LocateLibHAPIInRegistry( + TEXT("Houdini"), StoredLibHAPILocation, true); + if (HAPILibraryHandle) + return HAPILibraryHandle; + + // Finally, try to load from a hardcoded program files path. + HoudiniLocation = FString::Printf( + TEXT("C:\\Program Files\\Side Effects Software\\Houdini %s\\%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_WINDOWS); + +#else + +# if PLATFORM_MAC + + // Attempt to load from standard Mac OS X installation. + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/Current/Libraries"), *HoudiniVersionString); + + // Fallback in case the previous one doesnt exist + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/Houdini%s/Frameworks/Houdini.framework/Versions/%s/Libraries"), *HoudiniVersionString, *HoudiniVersionString); + + // Fallback in case we're using the steam version + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Applications/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); + + // Backup Fallback in case we're using the steam version + // (this could probably be removed as paths have changed) + if (!FPaths::DirectoryExists(HoudiniLocation)) + HoudiniLocation = FString::Printf( + TEXT("/Users/Shared/Houdini/HoudiniIndieSteam/Frameworks/Houdini.framework/Versions/Current/Libraries")); + +# elif PLATFORM_LINUX + + // Attempt to load from standard Linux installation. + HoudiniLocation = FString::Printf( + TEXT("/opt/hfs%s/%s"), *HoudiniVersionString, HAPI_HFS_SUBFOLDER_LINUX); + +# endif + +#endif + + // Create full path to libHAPI binary. + LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HoudiniLocation, *LibHAPIName); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HoudiniLocation); + HAPILibraryHandle = FPlatformProcess::GetDllHandle(*LibHAPIPath); + FPlatformProcess::PopDllDirectory(*HoudiniLocation); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE(TEXT("Loaded %s from expected installation %s"), *LibHAPIName, *HoudiniLocation); + StoredLibHAPILocation = HoudiniLocation; + return HAPILibraryHandle; + } + } + + StoredLibHAPILocation = TEXT(""); + return HAPILibraryHandle; +} + +bool +FHoudiniEngineUtils::IsInitialized() +{ + if (!FHoudiniApi::IsHAPIInitialized()) + return false; + + const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr)) + return false; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr)) + return false; + + return true; +} + +bool +FHoudiniEngineUtils::IsHoudiniNodeValid(const HAPI_NodeId& NodeId) +{ + if (NodeId < 0) + return false; + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + bool ValidationAnswer = 0; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) + { + return false; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsNodeValid( + FHoudiniEngine::Get().GetSession(), NodeId, + NodeInfo.uniqueHoudiniNodeId, &ValidationAnswer)) + { + return false; + } + + return ValidationAnswer; +} + +bool +FHoudiniEngineUtils::HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex) +{ + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), HostAssetId, InputIndex), false); + + return true; +} + +bool +FHoudiniEngineUtils::DestroyHoudiniAsset(const HAPI_NodeId& AssetId) +{ + if (HAPI_RESULT_SUCCESS == FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), AssetId)) + { + return true; + } + + return false; +} + +#if PLATFORM_WINDOWS +void * +FHoudiniEngineUtils::LocateLibHAPIInRegistry( + const FString & HoudiniInstallationType, + FString & StoredLibHAPILocation, + bool LookIn32bitRegistry) +{ + auto FindDll = [&](const FString& InHoudiniInstallationPath) + { + FString HFSPath = FString::Printf(TEXT("%s/%s"), *InHoudiniInstallationPath, HAPI_HFS_SUBFOLDER_WINDOWS); + + // Create full path to libHAPI binary. + FString LibHAPIPath = FString::Printf(TEXT("%s/%s"), *HFSPath, HAPI_LIB_OBJECT_WINDOWS); + + if (FPaths::FileExists(LibHAPIPath)) + { + FPlatformProcess::PushDllDirectory(*HFSPath); + void* HAPILibraryHandle = FPlatformProcess::GetDllHandle(HAPI_LIB_OBJECT_WINDOWS); + FPlatformProcess::PopDllDirectory(*HFSPath); + + if (HAPILibraryHandle) + { + HOUDINI_LOG_MESSAGE( + TEXT("Loaded %s from Registry path %s"), HAPI_LIB_OBJECT_WINDOWS, + *HFSPath); + + StoredLibHAPILocation = HFSPath; + return HAPILibraryHandle; + } + } + return (void*)0; + }; + + FString HoudiniInstallationPath; + FString HoudiniVersionString = ComputeVersionString(true); + FString RegistryKey = FString::Printf( + TEXT("Software\\%sSide Effects Software\\%s"), + (LookIn32bitRegistry ? TEXT("WOW6432Node\\") : TEXT("")), *HoudiniInstallationType); + + if (FWindowsPlatformMisc::QueryRegKey( + HKEY_LOCAL_MACHINE, *RegistryKey, *HoudiniVersionString, HoudiniInstallationPath)) + { + FPaths::NormalizeDirectoryName(HoudiniInstallationPath); + return FindDll(HoudiniInstallationPath); + } + + return nullptr; +} +#endif + +bool +FHoudiniEngineUtils::LoadHoudiniAsset(const UHoudiniAsset * HoudiniAsset, HAPI_AssetLibraryId& OutAssetLibraryId) +{ + OutAssetLibraryId = -1; + + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return false; + + if (!FHoudiniEngineUtils::IsInitialized()) + { + // If we're not initialized now, it likely means the session has been lost + FHoudiniEngine::Get().OnSessionLost(); + return false; + } + + // Get the preferences + bool bMemoryCopyFirst = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bMemoryCopyFirst = HoudiniRuntimeSettings->bPreferHdaMemoryCopyOverHdaSourceFile; + + // Get the HDA's file path + // We need to convert relative file path to absolute + FString AssetFileName = HoudiniAsset->GetAssetFileName(); + if (FPaths::IsRelative(AssetFileName)) + AssetFileName = FPaths::ConvertRelativePathToFull(AssetFileName); + + // We need to modify the file name for expanded .hdas + FString FileExtension = FPaths::GetExtension(AssetFileName); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) + { + // the .hda directory is what we should be loading + AssetFileName = FPaths::GetPath(AssetFileName); + } + + //Check whether we can Load from file/memory + bool bCanLoadFromMemory = (!HoudiniAsset->IsExpandedHDA() && HoudiniAsset->GetAssetBytesCount() > 0); + + // If the hda file exists, we can simply load it directly + bool bCanLoadFromFile = false; + if ( !AssetFileName.IsEmpty() ) + { + if (FPaths::FileExists(AssetFileName) + || (HoudiniAsset->IsExpandedHDA() && FPaths::DirectoryExists(AssetFileName))) + { + bCanLoadFromFile = true; + } + } + + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Lambda to detect license issues + auto CheckLicenseValid = [&AssetFileName](const HAPI_Result& Result) + { + // HoudiniEngine acquires a license when creating/loading a node, not when creating a session + if (Result >= HAPI_RESULT_NO_LICENSE_FOUND && Result < HAPI_RESULT_ASSET_INVALID) + { + FString ErrorDesc = GetErrorDescription(Result); + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: License failed: %s."), *AssetFileName, *ErrorDesc); + + // We must stop the session to prevent further attempts at loading an HDA + // as this could lead to unreal becoming stuck and unresponsive due to license timeout + FHoudiniEngine::Get().StopSession(); + + // Set the HE status to "no license" + FHoudiniEngine::Get().SetSessionStatus(EHoudiniSessionStatus::NoLicense); + + return false; + } + else + { + return true; + } + }; + + // Lambda to load an HDA from file + auto LoadAssetFromFile = [&Result, &OutAssetLibraryId](const FString& InAssetFileName) + { + // Load the asset from file. + std::string AssetFileNamePlain; + FHoudiniEngineUtils::ConvertUnrealString(InAssetFileName, AssetFileNamePlain); + Result = FHoudiniApi::LoadAssetLibraryFromFile( + FHoudiniEngine::Get().GetSession(), AssetFileNamePlain.c_str(), true, &OutAssetLibraryId); + + }; + + // Lambda to load an HDA from memory + auto LoadAssetFromMemory = [&Result, &OutAssetLibraryId](const UHoudiniAsset* InHoudiniAsset) + { + // Load the asset from the cached memory buffer + Result = FHoudiniApi::LoadAssetLibraryFromMemory( + FHoudiniEngine::Get().GetSession(), + reinterpret_cast(InHoudiniAsset->GetAssetBytes()), + InHoudiniAsset->GetAssetBytesCount(), + true, + &OutAssetLibraryId); + }; + + if (!bMemoryCopyFirst) + { + // Load from File first + if (bCanLoadFromFile) + { + LoadAssetFromFile(AssetFileName); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + + // If we failed to load from file ... + if (Result != HAPI_RESULT_SUCCESS) + { + // ... warn the user that we will be loading from memory. + HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from Memory: source asset file not found."), *AssetFileName); + + // Attempt to load from memory + if (bCanLoadFromMemory) + { + LoadAssetFromMemory(HoudiniAsset); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); + return false; + } + } + } + else + { + // Load from Memory first + if(bCanLoadFromMemory) + { + LoadAssetFromMemory(HoudiniAsset); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + + // If we failed to load from memory ... + if (Result != HAPI_RESULT_SUCCESS) + { + // ... warn the user that we will be loading from file + HOUDINI_LOG_WARNING(TEXT("Asset %s, loading from File: no memory copy available."), *AssetFileName); + + // Attempt to load from file + if (bCanLoadFromFile) + { + LoadAssetFromFile(AssetFileName); + + // Detect license issues when loading the HDA + if (!CheckLicenseValid(Result)) + return false; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Error loading Asset %s: source asset file not found and no memory copy available."), *AssetFileName); + return false; + } + } + } + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_MESSAGE(TEXT("Error loading asset library for %s: %s"), *AssetFileName, *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + return true; +} + +bool +FHoudiniEngineUtils::GetSubAssetNames( + const HAPI_AssetLibraryId& AssetLibraryId, + TArray< HAPI_StringHandle >& OutAssetNames) +{ + if (AssetLibraryId < 0) + return false; + + int32 AssetCount = 0; + HAPI_Result Result = HAPI_RESULT_FAILURE; + Result = FHoudiniApi::GetAvailableAssetCount(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &AssetCount); + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Error getting asset count: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + if (AssetCount <= 0) + { + HOUDINI_LOG_ERROR(TEXT("Could not find an asset.")); + return false; + } + + OutAssetNames.SetNumUninitialized(AssetCount); + Result = FHoudiniApi::GetAvailableAssets(FHoudiniEngine::Get().GetSession(), AssetLibraryId, &OutAssetNames[0], AssetCount); + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Unable to retrieve sub asset names: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + if (!AssetCount) + { + HOUDINI_LOG_ERROR(TEXT("No assets found")); + return false; + } + + return true; +} + + +bool +FHoudiniEngineUtils::OpenSubassetSelectionWindow(TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ) +{ + OutPickedAssetName = -1; + + if (AssetNames.Num() <= 0) + return false; + + // Default to the first asset + OutPickedAssetName = AssetNames[0]; + +#if WITH_EDITOR + // Present the user with a dialog for choosing which asset to instantiate. + TSharedPtr ParentWindow; + if (FModuleManager::Get().IsModuleLoaded("MainFrame")) + { + // Check if the main frame is loaded. When using the old main frame it may not be. + IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (!ParentWindow.IsValid()) + { + return false; + } + + TSharedPtr AssetSelectionWidget; + TSharedRef 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()) + { + return false; + } + + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + + int32 DialogPickedAssetName = AssetSelectionWidget->GetSelectedAssetName(); + if (DialogPickedAssetName != -1) + { + OutPickedAssetName = DialogPickedAssetName; + return true; + } + else + { + return false; + } +#endif + + return true; +} + +/* +bool +FHoudiniEngineUtils::IsValidNodeId(HAPI_NodeId NodeId) +{ + return NodeId != -1; +} +*/ + +bool +FHoudiniEngineUtils::GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString) +{ + HAPI_AssetInfo AssetInfo; + if (FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo) == HAPI_RESULT_SUCCESS) + { + FHoudiniEngineString HoudiniEngineString(AssetInfo.nameSH); + return HoudiniEngineString.ToFString(NameString); + } + + return false; +} + +bool +FHoudiniEngineUtils::GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer) +{ + PresetBuffer.Empty(); + + HAPI_NodeId NodeId; + HAPI_AssetInfo AssetInfo; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetNodeId, &AssetInfo)) + { + NodeId = AssetInfo.nodeId; + } + else + NodeId = AssetNodeId; + + int32 BufferLength = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPresetBufLength( + FHoudiniEngine::Get().GetSession(), NodeId, + HAPI_PRESETTYPE_BINARY, NULL, &BufferLength), false); + + PresetBuffer.SetNumZeroed(BufferLength); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPreset( + FHoudiniEngine::Get().GetSession(), NodeId, + &PresetBuffer[0], PresetBuffer.Num()), false); + + return true; +} + + +bool +FHoudiniEngineUtils::HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath) +{ + // Retrieve Path to the given Node, relative to the other given Node + if ((InNodeId < 0) || (InRelativeToNodeId < 0)) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InNodeId)) + return false; + + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodePath( + FHoudiniEngine::Get().GetSession(), + InNodeId, InRelativeToNodeId, &StringHandle)) + { + if(FHoudiniEngineString::ToFString(StringHandle, OutPath)) + { + return true; + } + } + return false; +} + +bool +FHoudiniEngineUtils::HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath) +{ + // Do the HAPI query only on first-use + if (!InHGPO.NodePath.IsEmpty()) + return true; + + FString NodePathTemp; + if (InHGPO.AssetId == InHGPO.GeoId) + { + // This is a SOP asset, just return the asset name in this case + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), InHGPO.AssetId, &AssetInfo)) + { + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo)) + { + if (FHoudiniEngineString::ToFString(AssetNodeInfo.nameSH, NodePathTemp)) + { + OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); + } + } + } + } + else + { + // This is an OBJ asset, return the path to this geo relative to the asset + if (FHoudiniEngineUtils::HapiGetNodePath(InHGPO.GeoId, InHGPO.AssetId, NodePathTemp)) + { + OutPath = FString::Printf(TEXT("%s_%d"), *NodePathTemp, InHGPO.PartId); + } + } + + /*if (OutPath.IsEmpty()) + { + OutPath = TEXT("Empty"); + } + + return NodePath; + */ + + return !OutPath.IsEmpty(); +} + + +bool +FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + InNodeId, &NodeInfo), false); + + int32 ObjectCount = 0; + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + ObjectCount = 1; + OutObjectInfos.SetNumUninitialized(1); + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), + NodeInfo.parentId, &OutObjectInfos[0]), false); + + // Use the identity transform + OutObjectTransforms.SetNumUninitialized(1); + FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); + + OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; + OutObjectTransforms[0].scale[0] = 1.0f; + OutObjectTransforms[0].scale[1] = 1.0f; + OutObjectTransforms[0].scale[2] = 1.0f; + OutObjectTransforms[0].rstOrder = HAPI_SRT; + } + else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); + + if (ObjectCount <= 0) + { + // This asset is an OBJ that has no object as children, use the object itself + ObjectCount = 1; + OutObjectInfos.SetNumUninitialized(1); + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[0])); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[0]), false); + + // Use the identity transform + OutObjectTransforms.SetNumUninitialized(1); + FHoudiniApi::Transform_Init(&(OutObjectTransforms[0])); + + OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; + OutObjectTransforms[0].scale[0] = 1.0f; + OutObjectTransforms[0].scale[1] = 1.0f; + OutObjectTransforms[0].scale[2] = 1.0f; + OutObjectTransforms[0].rstOrder = HAPI_SRT; + } + else + { + // This OBJ has children + // See if we should add ourself by looking for immediate display SOP + int32 ImmediateSOP = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), NodeInfo.id, + HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_DISPLAY, + false, &ImmediateSOP), false); + + bool bAddSelf = ImmediateSOP > 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, nullptr, &ObjectCount), false); + + // Increment the object count by one if we should add ourself + OutObjectInfos.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); + OutObjectTransforms.SetNumUninitialized(bAddSelf ? ObjectCount + 1 : ObjectCount); + for (int32 Idx = 0; Idx < OutObjectInfos.Num(); Idx++) + { + FHoudiniApi::ObjectInfo_Init(&(OutObjectInfos[Idx])); + FHoudiniApi::Transform_Init(&(OutObjectTransforms[Idx])); + } + + // Get our object info in 0 if needed + if (bAddSelf) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[0]), false); + + // Use the identity transform + OutObjectTransforms[0].rotationQuaternion[3] = 1.0f; + OutObjectTransforms[0].scale[0] = 1.0f; + OutObjectTransforms[0].scale[1] = 1.0f; + OutObjectTransforms[0].scale[2] = 1.0f; + OutObjectTransforms[0].rstOrder = HAPI_SRT; + } + + // Get the other object infos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectList( + FHoudiniEngine::Get().GetSession(), InNodeId, + &OutObjectInfos[bAddSelf ? 1 : 0], 0, ObjectCount), false); + + // Get the composed object transforms for the others (1 - Count) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedObjectTransforms( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_SRT, &OutObjectTransforms[bAddSelf ? 1 : 0], 0, ObjectCount), false); + } + } + else + return false; + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InNodeId, + &NodeInfo), false); + + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + NodeInfo.parentId, -1, HAPI_SRT, &HapiTransform), false); + } + else if (NodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InNodeId, -1, HAPI_SRT, &HapiTransform), false); + } + else + return false; + + // Convert HAPI transform to Unreal one. + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, OutTransform); + + return true; +} + +void +FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform) +{ + if ( HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM ) + { + // Swap Y/Z, invert W + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[2], + HapiTransform.rotationQuaternion[1], -HapiTransform.rotationQuaternion[3]); + + // Swap Y/Z and scale + FVector ObjectTranslation(HapiTransform.position[0], HapiTransform.position[2], HapiTransform.position[1]); + ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[2], HapiTransform.scale[1]); + + UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); + } + else + { + FQuat ObjectRotation( + HapiTransform.rotationQuaternion[0], HapiTransform.rotationQuaternion[1], + HapiTransform.rotationQuaternion[2], HapiTransform.rotationQuaternion[3]); + + FVector ObjectTranslation( + HapiTransform.position[0], HapiTransform.position[1], HapiTransform.position[2]); + ObjectTranslation *= HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + FVector ObjectScale3D(HapiTransform.scale[0], HapiTransform.scale[1], HapiTransform.scale[2]); + + UnrealTransform.SetComponents(ObjectRotation, ObjectTranslation, ObjectScale3D); + } +} + +void +FHoudiniEngineUtils::TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform) +{ + float HapiMatrix[16]; + FHoudiniApi::ConvertTransformEulerToMatrix(FHoudiniEngine::Get().GetSession(), &HapiTransformEuler, HapiMatrix); + + HAPI_Transform HapiTransformQuat; + FMemory::Memzero< HAPI_Transform >(HapiTransformQuat); + FHoudiniApi::ConvertMatrixToQuat(FHoudiniEngine::Get().GetSession(), HapiMatrix, HAPI_SRT, &HapiTransformQuat); + + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransformQuat, UnrealTransform); +} + +void +FHoudiniEngineUtils::TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform) +{ + FMemory::Memzero< HAPI_Transform >(HapiTransform); + HapiTransform.rstOrder = HAPI_SRT; + + FQuat UnrealRotation = UnrealTransform.GetRotation(); + FVector UnrealTranslation = UnrealTransform.GetTranslation(); + FVector UnrealScale = UnrealTransform.GetScale3D(); + + if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) + { + // Swap Y/Z, invert XYZ + HapiTransform.rotationQuaternion[0] = -UnrealRotation.X; + HapiTransform.rotationQuaternion[1] = -UnrealRotation.Z; + HapiTransform.rotationQuaternion[2] = -UnrealRotation.Y; + HapiTransform.rotationQuaternion[3] = UnrealRotation.W; + + // Swap Y/Z, scale + HapiTransform.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransform.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransform.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + HapiTransform.scale[0] = UnrealScale.X; + HapiTransform.scale[1] = UnrealScale.Z; + HapiTransform.scale[2] = UnrealScale.Y; + } + else + { + HapiTransform.rotationQuaternion[0] = UnrealRotation.X; + HapiTransform.rotationQuaternion[1] = UnrealRotation.Y; + HapiTransform.rotationQuaternion[2] = UnrealRotation.Z; + HapiTransform.rotationQuaternion[3] = UnrealRotation.W; + + HapiTransform.position[0] = UnrealTranslation.X; + HapiTransform.position[1] = UnrealTranslation.Y; + HapiTransform.position[2] = UnrealTranslation.Z; + + HapiTransform.scale[0] = UnrealScale.X; + HapiTransform.scale[1] = UnrealScale.Y; + HapiTransform.scale[2] = UnrealScale.Z; + } +} + +void +FHoudiniEngineUtils::TranslateUnrealTransform( + const FTransform & UnrealTransform, + HAPI_TransformEuler & HapiTransformEuler) +{ + FMemory::Memzero< HAPI_TransformEuler >(HapiTransformEuler); + + HapiTransformEuler.rstOrder = HAPI_SRT; + HapiTransformEuler.rotationOrder = HAPI_XYZ; + + FQuat UnrealRotation = UnrealTransform.GetRotation(); + FVector UnrealTranslation = UnrealTransform.GetTranslation(); + FVector UnrealScale = UnrealTransform.GetScale3D(); + + if (HAPI_UNREAL_CONVERT_COORDINATE_SYSTEM) + { + // switch the quaternion to Y-up, LHR by Swapping Y/Z and negating W + Swap(UnrealRotation.Y, UnrealRotation.Z); + UnrealRotation.W = -UnrealRotation.W; + const FRotator Rotator = UnrealRotation.Rotator(); + + // Negate roll and pitch since they are actually RHR + HapiTransformEuler.rotationEuler[0] = -Rotator.Roll; + HapiTransformEuler.rotationEuler[1] = -Rotator.Pitch; + HapiTransformEuler.rotationEuler[2] = Rotator.Yaw; + + // Swap Y/Z, scale + HapiTransformEuler.position[0] = UnrealTranslation.X / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransformEuler.position[1] = UnrealTranslation.Z / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + HapiTransformEuler.position[2] = UnrealTranslation.Y / HAPI_UNREAL_SCALE_FACTOR_TRANSLATION; + + // Swap Y/Z + HapiTransformEuler.scale[0] = UnrealScale.X; + HapiTransformEuler.scale[1] = UnrealScale.Z; + HapiTransformEuler.scale[2] = UnrealScale.Y; + } + else + { + const FRotator Rotator = UnrealRotation.Rotator(); + HapiTransformEuler.rotationEuler[0] = Rotator.Roll; + HapiTransformEuler.rotationEuler[1] = Rotator.Yaw; + HapiTransformEuler.rotationEuler[2] = Rotator.Pitch; + + HapiTransformEuler.position[0] = UnrealTranslation.X; + HapiTransformEuler.position[1] = UnrealTranslation.Y; + HapiTransformEuler.position[2] = UnrealTranslation.Z; + + HapiTransformEuler.scale[0] = UnrealScale.X; + HapiTransformEuler.scale[1] = UnrealScale.Y; + HapiTransformEuler.scale[2] = UnrealScale.Z; + } +} + +bool +FHoudiniEngineUtils::UploadHACTransform(UHoudiniAssetComponent* HAC) +{ + if (!HAC || !HAC->bUploadTransformsToHoudiniEngine) + return false; + + // Indicates the HAC has been fully loaded + // TODO: Check! (replaces fullyloaded) + if (!HAC->IsFullyLoaded()) + return false; + + if (HAC->GetAssetCookCount() > 0 && HAC->GetAssetId() >= 0) + { + if (!FHoudiniEngineUtils::HapiSetAssetTransform(HAC->GetAssetId(), HAC->GetComponentTransform())) + return false; + } + + HAC->SetHasComponentTransformChanged(false); + + return true; +} + +bool +FHoudiniEngineUtils::HapiSetAssetTransform(const HAPI_NodeId& AssetId, const FTransform & Transform) +{ + if (AssetId < 0) + return false; + + // Translate Unreal transform to HAPI Euler one. + HAPI_TransformEuler TransformEuler; + FMemory::Memzero< HAPI_TransformEuler >(TransformEuler); + FHoudiniEngineUtils::TranslateUnrealTransform(Transform, TransformEuler); + + // Get the NodeInfo + HAPI_NodeInfo LocalAssetNodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, + &LocalAssetNodeInfo), false); + + if (LocalAssetNodeInfo.type == HAPI_NODETYPE_SOP) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + LocalAssetNodeInfo.parentId, + &TransformEuler), false); + } + else if (LocalAssetNodeInfo.type == HAPI_NODETYPE_OBJ) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + AssetId, &TransformEuler), false); + } + else + return false; + + return true; +} + +HAPI_NodeId +FHoudiniEngineUtils::HapiGetParentNodeId(const HAPI_NodeId& NodeId) +{ + HAPI_NodeId ParentId = -1; + if (NodeId >= 0) + { + HAPI_NodeInfo NodeInfo; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), NodeId, &NodeInfo)) + ParentId = NodeInfo.parentId; + } + + return ParentId; +} + + +// Assign a unique Actor Label if needed +void +FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + // TODO: Necessary?? + +#if WITH_EDITOR + HAPI_NodeId AssetId = HAC->GetAssetId(); + if (AssetId < 0) + return; + + AActor* OwnerActor = HAC->GetOwner(); + if (!OwnerActor) + return; + + if (!OwnerActor->GetName().StartsWith(AHoudiniAssetActor::StaticClass()->GetName())) + return; + + // Assign unique actor label based on asset name if it seems to have not been renamed already + FString UniqueName; + if (FHoudiniEngineUtils::GetHoudiniAssetName(AssetId, UniqueName)) + FActorLabelUtilities::SetActorLabelUnique(OwnerActor, UniqueName); +#endif +} + +bool +FHoudiniEngineUtils::GetLicenseType(FString & LicenseType) +{ + LicenseType = TEXT(""); + HAPI_License LicenseTypeValue = HAPI_LICENSE_NONE; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetSessionEnvInt( + FHoudiniEngine::Get().GetSession(), HAPI_SESSIONENVINT_LICENSE, + (int32 *)&LicenseTypeValue), false); + + switch (LicenseTypeValue) + { + case HAPI_LICENSE_NONE: + { + LicenseType = TEXT("No License Acquired"); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE: + { + LicenseType = TEXT("Houdini Engine"); + break; + } + + case HAPI_LICENSE_HOUDINI: + { + LicenseType = TEXT("Houdini"); + break; + } + + case HAPI_LICENSE_HOUDINI_FX: + { + LicenseType = TEXT("Houdini FX"); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE_INDIE: + { + LicenseType = TEXT("Houdini Engine Indie"); + break; + } + + case HAPI_LICENSE_HOUDINI_INDIE: + { + LicenseType = TEXT("Houdini Indie"); + break; + } + + case HAPI_LICENSE_HOUDINI_ENGINE_UNITY_UNREAL: + { + LicenseType = TEXT("Houdini Engine for Unity/Unreal"); + break; + } + + case HAPI_LICENSE_MAX: + default: + { + return false; + } + } + + return true; +} + +// Check if the Houdini asset component (or parent HAC of a parameter) is being cooked +bool +FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(UObject* InObj) +{ + if (!InObj) + return false; + + UHoudiniAssetComponent* HoudiniAssetComponent = nullptr; + + if (InObj->IsA()) + { + HoudiniAssetComponent = Cast(InObj); + } + else if (InObj->IsA()) + { + UHoudiniParameter* Parameter = Cast(InObj); + if (!Parameter) + return false; + + HoudiniAssetComponent = Cast(Parameter->GetOuter()); + } + + if (!HoudiniAssetComponent) + return false; + + EHoudiniAssetState AssetState = HoudiniAssetComponent->GetAssetState(); + + return AssetState >= EHoudiniAssetState::PreCook && AssetState <= EHoudiniAssetState::PostCook; +} + +void +FHoudiniEngineUtils::UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate) +{ + TArray ObjectsToUpdate; + ObjectsToUpdate.Add(InObjectToUpdate); + + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() + { + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + } +} + +void +FHoudiniEngineUtils::UpdateEditorProperties(TArray ObjectsToUpdate, const bool& InForceFullUpdate) +{ + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [ObjectsToUpdate, InForceFullUpdate]() + { + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateEditorProperties_Internal(ObjectsToUpdate, InForceFullUpdate); + } +} + +void FHoudiniEngineUtils::UpdateBlueprintEditor(UHoudiniAssetComponent* HAC) +{ + if (!IsInGameThread()) + { + // We need to be in the game thread to trigger editor properties update + AsyncTask(ENamedThreads::GameThread, [HAC]() + { + FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); + }); + } + else + { + // We're in the game thread, no need for an async task + FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(HAC); + } +} + +void +FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate) +{ + // TODO: Don't use this method. Prefer using IDetailLayoutBuilder::ForceRefreshDetails(). + // Example to correctly update details panel through IDetailCategoryBuilder / IDetailLayoutBuilder + // IDetailCategoryBuilder &CategoryBuilder = StructBuilder.GetParentCategory(); + // IDetailLayoutBuilder &LayoutBuilder = CategoryBuilder.GetParentLayout(); + // LayoutBuilder.ForceRefreshDetails(); + +#if WITH_EDITOR + if (!bInForceFullUpdate) + { + // bNeedFullUpdate is false only when small changes (parameters value) have been made + // We do not reselect the actor to avoid loosing the currently selected parameter + if(GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); + + return; + } + + // We now want to get all the components/actors owning the objects to update + TArray AllSceneComponents; + for (auto CurrentObject : ObjectsToUpdate) + { + if (!CurrentObject || CurrentObject->IsPendingKill()) + continue; + + // In some case, the object itself is the component + USceneComponent* SceneComp = Cast(CurrentObject); + if (!SceneComp) + { + SceneComp = Cast(CurrentObject->GetOuter()); + } + + if (SceneComp && !SceneComp->IsPendingKill()) + { + AllSceneComponents.Add(SceneComp); + continue; + } + } + + TArray AllActors; + for (auto CurrentSceneComp : AllSceneComponents) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill()) + continue; + + AActor* Actor = CurrentSceneComp->GetOwner(); + if (Actor && !Actor->IsPendingKill()) + AllActors.Add(Actor); + } + + // Updating the editor properties can be done in two ways, depending if we're in the BP editor or not + // If we have a parent actor, we're not in the BP Editor, so update via the property editor module + if (AllActors.Num() > 0) + { + // Get the property editor module + FPropertyEditorModule& PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // This will actually force a refresh of all the details view + //PropertyModule.NotifyCustomizationModuleChanged(); + + TArray SelectedActors; + for (auto Actor : AllActors) + { + if (Actor && Actor->IsSelected()) + SelectedActors.Add(Actor); + } + + if (SelectedActors.Num() > 0) + { + PropertyModule.UpdatePropertyViews(SelectedActors); + } + + // 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. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + { + // We have no details panel, nothing to update. + continue; + } + + // Get the selected actors for this details panels and check if one of ours belongs to it + const TArray>& SelectedDetailActors = DetailsView->GetSelectedActors(); + bool bFoundActor = false; + for (int32 ActorIdx = 0; ActorIdx < SelectedDetailActors.Num(); ActorIdx++) + { + TWeakObjectPtr SelectedActor = SelectedDetailActors[ActorIdx]; + if (SelectedActor.IsValid() && AllActors.Contains(SelectedActor.Get())) + { + bFoundActor = true; + break; + } + } + + // None of our actors belongs to this detail panel, no need to update it + if (!bFoundActor) + continue; + + // Refresh that details panels using its current selection + TArray Selection; + for (auto DetailsActor : SelectedDetailActors) + { + if (DetailsActor.IsValid()) + Selection.Add(DetailsActor.Get()); + } + + // Reset selected actors, force refresh and override the lock. + DetailsView->SetObjects(SelectedActors, bInForceFullUpdate, true); + + if (GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); + } + } + else + { + // TODO: Do we need to do Blueprint Editor updates here or can we confine it to "post output processing"? + + } + + /* + // Reset the full update flag + if (bNeedFullUpdate) + HAC->SetEditorPropertiesNeedFullUpdate(false); + */ + + return; +#endif +} + +void FHoudiniEngineUtils::UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC) +{ + //UHoudiniAssetComponent* HACTemplate = HAC->GetCachedTemplate(); + //UBlueprintGeneratedClass* OwnerBPClass = Cast(HACTemplate->GetOuter()); + //if (!OwnerBPClass) + // return; + + ///* + //FBlueprintEditor* BlueprintEditor = static_cast(FAssetEditorManager::Get().FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); + //if (!BlueprintEditor) + // return; + //*/ + + //// Close the mesh editor to prevent crashing. Reopen it after the mesh has been built. + //UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + //FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(OwnerBPClass->ClassGeneratedBy, false)); + //if (!BlueprintEditor) + // return; + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(HAC); + if (!BlueprintEditor) + return; + + TSharedPtr SCSEditor = BlueprintEditor->GetSCSEditor(); + if (SCSEditor.IsValid()) + { + SCSEditor->UpdateTree(true); + SCSEditor->DumpTree(); + } + BlueprintEditor->RefreshMyBlueprint(); + + //BlueprintEditor->RefreshMyBlueprint(); + //BlueprintEditor->RefreshInspector(); + //BlueprintEditor->RefreshEditors(); + + // Also somehow reselect ? +} + +HAPI_Result +FHoudiniEngineUtils::SetAttributeStringData( + const FString& InString, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo) +{ + TArray StringArray; + StringArray.Add(InString); + + return SetAttributeStringData(StringArray, InNodeId, InPartId, InAttributeName, InAttributeInfo); +} + +HAPI_Result +FHoudiniEngineUtils::SetAttributeStringData( + const TArray& InStringArray, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo ) +{ + TArray StringDataArray; + for (auto CurrentString : InStringArray) + { + // Append the converted string to the string array + StringDataArray.Add(FHoudiniEngineUtils::ExtractRawString(CurrentString)); + } + + // Set the attribute's string data + HAPI_Result result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + TCHAR_TO_ANSI(*InAttributeName), &InAttributeInfo, + StringDataArray.GetData(), 0, InAttributeInfo.count); + + // ExtractRawString allocates memory using malloc, free it! + FreeRawStringMemory(StringDataArray); + + return result; +} + +char * +FHoudiniEngineUtils::ExtractRawString(const FString& InString) +{ + if (InString.IsEmpty()) + return nullptr; + + std::string ConvertedString = TCHAR_TO_UTF8(*InString); + + // Allocate space for unique string. + int32 UniqueStringBytes = ConvertedString.size() + 1; + char * UniqueString = static_cast(FMemory::Malloc(UniqueStringBytes)); + + FMemory::Memzero(UniqueString, UniqueStringBytes); + FMemory::Memcpy(UniqueString, ConvertedString.c_str(), ConvertedString.size()); + + return UniqueString; +} + +void +FHoudiniEngineUtils::FreeRawStringMemory(const char*& InRawString) +{ + if (InRawString == nullptr) + return; + + // Do not attempt to free empty strings! + if (!InRawString[0]) + return; + + FMemory::Free((void*)InRawString); + InRawString = nullptr; +} + +void +FHoudiniEngineUtils::FreeRawStringMemory(TArray& InRawStringArray) +{ + // ExtractRawString allocates memory using malloc, free it! + for (auto CurrentStrPtr : InRawStringArray) + { + FreeRawStringMemory(CurrentStrPtr); + } + InRawStringArray.Empty(); +} + +bool +FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // No need to add another component if we already show the logo + if (FHoudiniEngineUtils::HasHoudiniLogo(HAC)) + return true; + + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + UStaticMeshComponent * HoudiniLogoSMC = NewObject< UStaticMeshComponent >( + HAC, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!HoudiniLogoSMC) + return false; + + HoudiniLogoSMC->SetStaticMesh(HoudiniLogoSM); + HoudiniLogoSMC->SetVisibility(true); + HoudiniLogoSMC->SetHiddenInGame(true); + // Attach created static mesh component to our Houdini component. + HoudiniLogoSMC->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniLogoSMC->RegisterComponent(); + + return true; +} + +bool +FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the Houdini Logo SM + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + // Iterate on the HAC's component + for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + continue; + + // Get the static mesh component + UStaticMeshComponent* SMC = Cast(CurrentSceneComp); + if (!SMC || SMC->IsPendingKill()) + continue; + + // Check if the SMC is the Houdini Logo + if (SMC->GetStaticMesh() != HoudiniLogoSM) + continue; + + SMC->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SMC->UnregisterComponent(); + SMC->DestroyComponent(); + + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the Houdini Logo SM + UStaticMesh* HoudiniLogoSM = FHoudiniEngine::Get().GetHoudiniLogoStaticMesh().Get(); + if (!HoudiniLogoSM) + return false; + + // Iterate on the HAC's component + for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) + { + if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + continue; + + // Get the static mesh component + UStaticMeshComponent* SMC = Cast(CurrentSceneComp); + if (!SMC || SMC->IsPendingKill()) + continue; + + // Check if the SMC is the Houdini Logo + if (SMC->GetStaticMesh() == HoudiniLogoSM) + return true; + } + + return false; +} + +int32 +FHoudiniEngineUtils::HapiGetVertexListForGroup( + const HAPI_NodeId& GeoId, + const HAPI_PartInfo& PartInfo, + const FString& GroupName, + const TArray& FullVertexList, + TArray& NewVertexList, + TArray& AllVertexList, + TArray& AllFaceList, + TArray& AllGroupFaceIndices, + int32& FirstValidVertex, + int32& FirstValidPrim, + const bool& isPackedPrim) +{ + int32 ProcessedWedges = 0; + AllFaceList.Empty(); + FirstValidPrim = 0; + FirstValidVertex = 0; + NewVertexList.Init(-1, FullVertexList.Num()); + + // Get the faces membership for this group + bool bAllEquals = false; + TArray PartGroupMembership; + if (!FHoudiniEngineUtils::HapiGetGroupMembership( + GeoId, PartInfo, HAPI_GROUPTYPE_PRIM, GroupName, PartGroupMembership, bAllEquals)) + return false; + + // Go through all primitives. + for (int32 FaceIdx = 0; FaceIdx < PartGroupMembership.Num(); ++FaceIdx) + { + if (PartGroupMembership[FaceIdx] <= 0) + { + // The face is not in the group, skip + continue; + } + + // Add the face's index. + AllFaceList.Add(FaceIdx); + + // Get the index of this face's vertices + int32 FirstVertexIdx = FaceIdx * 3; + int32 SecondVertexIdx = FirstVertexIdx + 1; + int32 LastVertexIdx = FirstVertexIdx + 2; + + // This face is a member of specified group. + // Add all 3 vertices + if (FullVertexList.IsValidIndex(LastVertexIdx)) + { + NewVertexList[FirstVertexIdx] = FullVertexList[FirstVertexIdx]; + NewVertexList[SecondVertexIdx] = FullVertexList[SecondVertexIdx]; + NewVertexList[LastVertexIdx] = FullVertexList[LastVertexIdx]; + } + + // Mark these vertex indices as used. + if (AllVertexList.IsValidIndex(LastVertexIdx)) + { + AllVertexList[FirstVertexIdx] = 1; + AllVertexList[SecondVertexIdx] = 1; + AllVertexList[LastVertexIdx] = 1; + } + + // Mark this face as used. + if (AllGroupFaceIndices.IsValidIndex(FaceIdx)) + AllGroupFaceIndices[FaceIdx] = 1; + + if (ProcessedWedges == 0) + { + // Keep track of the first valid vertex/face indices for this group + // This will be useful later on when extracting attributes + FirstValidVertex = FirstVertexIdx; + FirstValidPrim = FaceIdx; + } + + ProcessedWedges += 3; + } + + return ProcessedWedges; +} + +bool +FHoudiniEngineUtils::HapiGetGroupNames( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const HAPI_GroupType& GroupType, const bool& isPackedPrim, + TArray& OutGroupNames) +{ + int32 GroupCount = 0; + if (!isPackedPrim) + { + // Get group count on the geo + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), GeoId, &GeoInfo), false); + + if (GroupType == HAPI_GROUPTYPE_POINT) + GroupCount = GeoInfo.pointGroupCount; + else if (GroupType == HAPI_GROUPTYPE_PRIM) + GroupCount = GeoInfo.primitiveGroupCount; + } + else + { + // We need the group count for this packed prim + int32 PointGroupCount = 0, PrimGroupCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupCountOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PointGroupCount, &PrimGroupCount), false); + + if (GroupType == HAPI_GROUPTYPE_POINT) + GroupCount = PointGroupCount; + else if (GroupType == HAPI_GROUPTYPE_PRIM) + GroupCount = PrimGroupCount; + } + + if (GroupCount <= 0) + return true; + + TArray GroupNameStringHandles; + GroupNameStringHandles.SetNumZeroed(GroupCount); + if (!isPackedPrim) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNames( + FHoudiniEngine::Get().GetSession(), + GeoId, GroupType, &GroupNameStringHandles[0], GroupCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupNamesOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, GroupType, &GroupNameStringHandles[0], GroupCount), false); + } + + /* + OutGroupNames.SetNum(GroupCount); + for (int32 NameIdx = 0; NameIdx < GroupCount; ++NameIdx) + { + FString CurrentGroupName = TEXT(""); + FHoudiniEngineString::ToFString(GroupNameStringHandles[NameIdx], CurrentGroupName); + OutGroupNames[NameIdx] = CurrentGroupName; + } + */ + + FHoudiniEngineString::SHArrayToFStringArray(GroupNameStringHandles, OutGroupNames); + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetGroupMembership( + const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, + const HAPI_GroupType& GroupType, const FString & GroupName, + TArray& OutGroupMembership, bool& OutAllEquals) +{ + int32 ElementCount = (GroupType == HAPI_GROUPTYPE_POINT) ? PartInfo.pointCount : PartInfo.faceCount; + if (ElementCount < 1) + return false; + OutGroupMembership.SetNum(ElementCount); + + OutAllEquals = false; + std::string ConvertedGroupName = TCHAR_TO_UTF8(*GroupName); + if (!PartInfo.isInstanced) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembership( + FHoudiniEngine::Get().GetSession(), + GeoId, PartInfo.id, GroupType,ConvertedGroupName.c_str(), + &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGroupMembershipOnPackedInstancePart( + FHoudiniEngine::Get().GetSession(), GeoId, PartInfo.id, GroupType, + ConvertedGroupName.c_str(), &OutAllEquals, &OutGroupMembership[0], 0, ElementCount), false); + } + + return true; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize, + HAPI_AttributeOwner InOwner, + const int32& InStartIndex, + const int32& InCount) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniEngineUtils::HapiGetAttributeDataAsFloat")); + + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + // Handle partial reading of attributes + int32 Start = 0; + if (InStartIndex > 0 && InStartIndex < AttributeInfo.count) + Start = InStartIndex; + + int32 Count = AttributeInfo.count; + if (InCount > 0) + { + if ((Start + InCount) <= AttributeInfo.count) + Count = InCount; + else + Count = AttributeInfo.count - Start; + } + + if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Allocate sufficient buffer for data. + OutData.SetNum(Count * AttributeInfo.tupleSize); + + // Fetch the values + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &OutData[0], + Start, Count), false); + + return true; + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Expected Float, found an int, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray IntData; + IntData.SetNum(Count * AttributeInfo.tupleSize); + + // Fetch the values + if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &IntData[0], + Start, Count)) + { + OutData.SetNum(IntData.Num()); + for (int32 Idx = 0; Idx < IntData.Num(); Idx++) + { + OutData[Idx] = (float)IntData[Idx]; + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from integer."), *FString(InAttribName)); + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + // Expected Float, found a string, try to convert the attribute + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InGeoId, InPartId, InAttribName, + AttributeInfo, StringData, + Start, Count)) + { + bool bConversionError = false; + OutData.SetNum(StringData.Num()); + for (int32 Idx = 0; Idx < StringData.Num(); Idx++) + { + if (StringData[Idx].IsNumeric()) + OutData[Idx] = FCString::Atof(*StringData[Idx]); + else + bConversionError = true; + } + + if (!bConversionError) + { + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a float attribute, its value had to be converted from string."), *FString(InAttribName)); + return true; + } + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a float attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + const int32& InTupleSize, + const HAPI_AttributeOwner& InOwner, + const int32& InStartIndex, + const int32& InCount) +{ + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + // Handle partial reading of attributes + int32 Start = 0; + if (InStartIndex > 0 && InStartIndex < AttributeInfo.count) + Start = InStartIndex; + + int32 Count = AttributeInfo.count; + if (InCount > 0) + { + if ((Start + InCount) <= AttributeInfo.count) + Count = InCount; + else + Count = AttributeInfo.count - Start; + } + + if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Allocate sufficient buffer for data. + OutData.SetNum(Count * AttributeInfo.tupleSize); + + // Fetch the values + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &OutData[0], Start, Count), false); + + return true; + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Expected Int, found a float, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray FloatData; + FloatData.SetNum(Count * AttributeInfo.tupleSize); + + // Fetch the float values + if(HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &FloatData[0], Start, Count)) + { + OutData.SetNum(FloatData.Num()); + for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) + { + OutData[Idx] = (int32)FloatData[Idx]; + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from float."), *FString(InAttribName)); + + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + // Expected Int, found a string, try to convert the attribute + TArray StringData; + if(FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InGeoId, InPartId, InAttribName, + AttributeInfo, StringData, + Start, Count)) + { + bool bConversionError = false; + OutData.SetNum(StringData.Num()); + for (int32 Idx = 0; Idx < StringData.Num(); Idx++) + { + if (StringData[Idx].IsNumeric()) + OutData[Idx] = FCString::Atoi(*StringData[Idx]); + else + bConversionError = true; + } + + if (!bConversionError) + { + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be an integer attribute, its value had to be converted from string."), *FString(InAttribName)); + return true; + } + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be an integer attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsString( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize, + HAPI_AttributeOwner InOwner, + const int32& InStartIndex, + const int32& InCount) +{ + OutAttributeInfo.exists = false; + + // Reset container size. + OutData.SetNumUninitialized(0); + + int32 OriginalTupleSize = InTupleSize; + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (InOwner == HAPI_ATTROWNER_INVALID) + { + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + (HAPI_AttributeOwner)AttrIdx, &AttributeInfo), false); + + if (AttributeInfo.exists) + break; + } + } + else + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + InOwner, &AttributeInfo), false); + } + + if (!AttributeInfo.exists) + return false; + + // Store the retrieved attribute information. + OutAttributeInfo = AttributeInfo; + + if (OriginalTupleSize > 0) + AttributeInfo.tupleSize = OriginalTupleSize; + + // Handle partial reading of attributes + int32 Start = 0; + if (InStartIndex > 0 && InStartIndex < AttributeInfo.count) + Start = InStartIndex; + + int32 Count = AttributeInfo.count; + if (InCount > 0) + { + if ((Start + InCount) <= AttributeInfo.count) + Count = InCount; + else + Count = AttributeInfo.count - Start; + } + + if (AttributeInfo.storage == HAPI_STORAGETYPE_STRING) + { + return FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InGeoId, InPartId, InAttribName, + AttributeInfo, OutData, + Start, Count); + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) + { + // Expected string, found a float, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray FloatData; + FloatData.SetNum(Count * AttributeInfo.tupleSize); + + // Fetch the float values + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &FloatData[0], + Start, Count)) + { + OutData.SetNum(FloatData.Num()); + for (int32 Idx = 0; Idx < FloatData.Num(); Idx++) + { + OutData[Idx] = FString::SanitizeFloat(FloatData[Idx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from float."), *FString(InAttribName)); + return true; + } + } + else if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) + { + // Expected String, found an int, try to convert the attribute + + // Allocate sufficient buffer for data. + TArray IntData; + IntData.SetNum(Count * AttributeInfo.tupleSize); + + // Fetch the values + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &AttributeInfo, -1, &IntData[0], + Start, Count)) + { + OutData.SetNum(IntData.Num()); + for (int32 Idx = 0; Idx < IntData.Num(); Idx++) + { + OutData[Idx] = FString::FromInt(IntData[Idx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("Attribute %s was expected to be a string attribute, its value had to be converted from integer."), *FString(InAttribName)); + return true; + } + } + + HOUDINI_LOG_WARNING(TEXT("Found attribute %s, but it was expected to be a string attribute and is of an invalid type."), *FString(InAttribName)); + return false; +} + +bool +FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& InAttributeInfo, + TArray& OutData, + const int32& InStartIndex, + const int32& InCount) +{ + if (!InAttributeInfo.exists) + return false; + + // Handle partial reading of attributes + int32 Start = 0; + if (InStartIndex > 0 && InStartIndex < InAttributeInfo.count) + Start = InStartIndex; + + int32 Count = InAttributeInfo.count; + if (InCount > 0) + { + if ((Start + InCount) <= InAttributeInfo.count) + Count = InCount; + else + Count = InAttributeInfo.count - Start; + } + + // Extract the StringHandles + TArray StringHandles; + StringHandles.Init(-1, Count * InAttributeInfo.tupleSize); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InGeoId, InPartId, InAttribName, + &InAttributeInfo, &StringHandles[0], + Start, Count), false); + + // Set the output data size + OutData.SetNum(StringHandles.Num()); + + // Convert the StringHandles to FString. + // using a map to minimize the number of HAPI calls + FHoudiniEngineString::SHArrayToFStringArray(StringHandles, OutData); + + return true; +} + + +bool +FHoudiniEngineUtils::HapiCheckAttributeExists( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const char * AttribName, HAPI_AttributeOwner Owner) +{ + if (Owner == HAPI_ATTROWNER_INVALID) + { + for (int32 OwnerIdx = 0; OwnerIdx < HAPI_ATTROWNER_MAX; OwnerIdx++) + { + if (HapiCheckAttributeExists(GeoId, PartId, AttribName, (HAPI_AttributeOwner)OwnerIdx)) + { + return true; + } + } + } + else + { + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, AttribName, Owner, &AttribInfo), false); + + return AttribInfo.exists; + } + + return false; +} + +bool +FHoudiniEngineUtils::IsAttributeInstancer(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType) +{ + // Check for + // - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE (unreal_instance) on points/detail + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT)) + { + OutInstancerType = EHoudiniInstancerType::AttributeInstancer; + return true; + } + + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL)) + { + OutInstancerType = EHoudiniInstancerType::AttributeInstancer; + return true; + } + + // - HAPI_UNREAL_ATTRIB_INSTANCE (instance) on points + if (FHoudiniEngineUtils::HapiCheckAttributeExists(GeoId, PartId, HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT)) + { + OutInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsString( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const FString& DefaultValue, + FString& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParamInfo), false); + + // .. and value + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), NodeId, false, + &StringHandle, FoundParamInfo.stringValuesIndex, 1), false); + + // Convert the string handle to FString + return FHoudiniEngineString::ToFString(StringHandle, OutValue); +} + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const int32& DefaultValue, + int32& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParmInfo; + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo), false); + + // .. and value + int32 Value = DefaultValue; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Value, + FoundParmInfo.intValuesIndex, 1), false); + + OutValue = Value; + + return true; +} + + +bool +FHoudiniEngineUtils::HapiGetParameterDataAsFloat( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const float& DefaultValue, + float& OutValue) +{ + OutValue = DefaultValue; + + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmName.c_str(), &ParmId), false); + + if (ParmId < 0) + return false; + + // Get the param info... + HAPI_ParmInfo FoundParmInfo; + FHoudiniApi::ParmInfo_Init(&FoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, ParmId, &FoundParmInfo), false); + + // .. and value + float Value = DefaultValue; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), NodeId, &Value, + FoundParmInfo.floatValuesIndex, 1), false); + + OutValue = Value; + + return true; +} + +HAPI_ParmId +FHoudiniEngineUtils::HapiFindParameterByName(const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo) +{ + // Try to find the parameter by its name + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + InNodeId, InParmName.c_str(), &ParmId), -1); + + if (ParmId < 0) + return -1; + + FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmId, &OutFoundParmInfo), -1); + + return ParmId; +} + +HAPI_ParmId +FHoudiniEngineUtils::HapiFindParameterByTag(const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo) +{ + // Try to find the parameter by its tag + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmWithTag( + FHoudiniEngine::Get().GetSession(), + InNodeId, InParmTag.c_str(), &ParmId), -1); + + if (ParmId < 0) + return -1; + + FHoudiniApi::ParmInfo_Init(&OutFoundParmInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmId, &OutFoundParmInfo), -1); + + return ParmId; +} + +int32 +FHoudiniEngineUtils::HapiGetAttributeOfType( + const HAPI_NodeId& GeoId, + const HAPI_NodeId& PartId, + const HAPI_AttributeOwner& AttributeOwner, + const HAPI_AttributeTypeInfo& AttributeType, + TArray& MatchingAttributesInfo, + TArray& MatchingAttributesName) +{ + int32 NumberOfAttributeFound = 0; + + // Get the part infos + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, &PartInfo), NumberOfAttributeFound); + + // Get All attribute names for that part + int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; + + TArray AttribNameSHArray; + AttribNameSHArray.SetNum(nAttribCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, AttributeOwner, + AttribNameSHArray.GetData(), nAttribCount), NumberOfAttributeFound); + + TArray AttribNameArray; + FHoudiniEngineString::SHArrayToFStringArray(AttribNameSHArray, AttribNameArray); + + // Iterate on all the attributes, and get their part infos to get their type + for (int32 Idx = 0; Idx < AttribNameArray.Num(); Idx++) + { + FString HapiString = AttribNameArray[Idx]; + + // ... then the attribute info + HAPI_AttributeInfo AttrInfo; + FHoudiniApi::AttributeInfo_Init(&AttrInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + GeoId, PartId, TCHAR_TO_UTF8(*HapiString), + AttributeOwner, &AttrInfo)) + continue; + + if (!AttrInfo.exists) + continue; + + // ... check the type + if (AttrInfo.typeInfo != AttributeType) + continue; + + MatchingAttributesInfo.Add(AttrInfo); + MatchingAttributesName.Add(HapiString); + + NumberOfAttributeFound++; + } + + return NumberOfAttributeFound; +} + +HAPI_PartInfo +FHoudiniEngineUtils::ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo) +{ + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + + PartInfo.id = InHPartInfo.PartId; + //PartInfo.nameSH = InHPartInfo.Name; + + switch (InHPartInfo.Type) + { + case EHoudiniPartType::Mesh: + PartInfo.type = HAPI_PARTTYPE_MESH; + break; + case EHoudiniPartType::Curve: + PartInfo.type = HAPI_PARTTYPE_CURVE; + break; + case EHoudiniPartType::Instancer: + PartInfo.type = HAPI_PARTTYPE_INSTANCER; + break; + case EHoudiniPartType::Volume: + PartInfo.type = HAPI_PARTTYPE_VOLUME; + break; + default: + case EHoudiniPartType::Invalid: + PartInfo.type = HAPI_PARTTYPE_INVALID; + break; + } + + PartInfo.faceCount = InHPartInfo.FaceCount; + PartInfo.vertexCount = InHPartInfo.VertexCount; + PartInfo.pointCount = InHPartInfo.PointCount; + + PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = InHPartInfo.PointAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX] = InHPartInfo.VertexAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_PRIM] = InHPartInfo.PrimitiveAttributeCounts; + PartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL] = InHPartInfo.DetailAttributeCounts; + + PartInfo.isInstanced = InHPartInfo.bIsInstanced; + + PartInfo.instancedPartCount = InHPartInfo.InstancedPartCount; + PartInfo.instanceCount = InHPartInfo.InstanceCount; + + PartInfo.hasChanged = InHPartInfo.bHasChanged; + + return PartInfo; +} + +int32 +FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + TArray< FHoudiniMeshSocket >& AllSockets, + const bool& isPackedPrim) +{ + int32 FoundSocketCount = 0; + + // Attributes we are interested in. + // Position + TArray Positions; + HAPI_AttributeInfo AttribInfoPositions; + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bool bHasRotation = false; + TArray Rotations; + HAPI_AttributeInfo AttribInfoRotations; + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bool bHasScale = false; + TArray Scales; + HAPI_AttributeInfo AttribInfoScales; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // Socket Name + bool bHasNames = false; + TArray Names; + HAPI_AttributeInfo AttribInfoNames; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bool bHasActors = false; + TArray Actors; + HAPI_AttributeInfo AttribInfoActors; + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bool bHasTags = false; + TArray Tags; + HAPI_AttributeInfo AttribInfoTags; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + + // Lambda function for creating the socket and adding it to the array + // Shared between the by Attribute / by Group methods + auto AddSocketToArray = [&](const int32& PointIdx) + { + FHoudiniMeshSocket CurrentSocket; + FVector currentPosition = FVector::ZeroVector; + if (Positions.IsValidIndex(PointIdx * 3 + 2)) + { + currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + FVector currentScale = FVector::OneVector; + if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) + { + currentScale.X = Scales[PointIdx * 3]; + currentScale.Y = Scales[PointIdx * 3 + 2]; + currentScale.Z = Scales[PointIdx * 3 + 1]; + } + + FQuat currentRotation = FQuat::Identity; + if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) + { + currentRotation.X = Rotations[PointIdx * 4]; + currentRotation.Y = Rotations[PointIdx * 4 + 2]; + currentRotation.Z = Rotations[PointIdx * 4 + 1]; + currentRotation.W = -Rotations[PointIdx * 4 + 3]; + } + + if (bHasNames && Names.IsValidIndex(PointIdx)) + CurrentSocket.Name = Names[PointIdx]; + + if (bHasActors && Actors.IsValidIndex(PointIdx)) + CurrentSocket.Actor = Actors[PointIdx]; + + if (bHasTags && Tags.IsValidIndex(PointIdx)) + CurrentSocket.Tag = Tags[PointIdx]; + + // If the scale attribute wasn't set on all socket, we might end up + // with a zero scale socket, avoid that. + if (currentScale == FVector::ZeroVector) + currentScale = FVector::OneVector; + + CurrentSocket.Transform.SetLocation(currentPosition); + CurrentSocket.Transform.SetRotation(currentRotation); + CurrentSocket.Transform.SetScale3D(currentScale); + + // We want to make sure we're not adding the same socket multiple times + AllSockets.AddUnique(CurrentSocket); + + FoundSocketCount++; + + return true; + }; + + + // Lambda function for reseting the arrays/attributes + auto ResetArraysAndAttr = [&]() + { + // Position + Positions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bHasRotation = false; + Rotations.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bHasScale = false; + Scales.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // Socket Name + bHasNames = false; + Names.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bHasActors = false; + Actors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bHasTags = false; + Tags.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + }; + + //------------------------------------------------------------------------- + // FIND SOCKETS BY DETAIL ATTRIBUTES + //------------------------------------------------------------------------- + + int32 SocketIdx = 0; + bool HasSocketAttributes = true; + while (HasSocketAttributes) + { + // Build the current socket's prefix + FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(SocketIdx); + + // Reset the arrays and attributes + ResetArraysAndAttr(); + + // Retrieve position data. + FString SocketPosAttr = SocketAttrPrefix + TEXT("_pos"); + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, TCHAR_TO_ANSI(*SocketPosAttr), + AttribInfoPositions, Positions, 0, HAPI_ATTROWNER_DETAIL)) + break; + + if (!AttribInfoPositions.exists) + { + // No need to keep looking for socket attributes + HasSocketAttributes = false; + break; + } + + // Retrieve rotation data. + FString SocketRotAttr = SocketAttrPrefix + TEXT("_rot"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketRotAttr), AttribInfoRotations, Rotations, 0, HAPI_ATTROWNER_DETAIL)) + bHasRotation = true; + + // Retrieve scale data. + FString SocketScaleAttr = SocketAttrPrefix + TEXT("_scale"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketScaleAttr), AttribInfoScales, Scales, 0, HAPI_ATTROWNER_DETAIL)) + bHasScale = true; + + // Retrieve mesh socket names. + FString SocketNameAttr = SocketAttrPrefix + TEXT("_name"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketNameAttr), AttribInfoNames, Names)) + bHasNames = true; + + // Retrieve mesh socket actor. + FString SocketActorAttr = SocketAttrPrefix + TEXT("_actor"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketActorAttr), AttribInfoActors, Actors)) + bHasActors = true; + + // Retrieve mesh socket tags. + FString SocketTagAttr = SocketAttrPrefix + TEXT("_tag"); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, + TCHAR_TO_ANSI(*SocketTagAttr), AttribInfoTags, Tags)) + bHasTags = true; + + // Add the socket to the array + AddSocketToArray(0); + + // Try to find the next socket + SocketIdx++; + } + + return FoundSocketCount; +} + + +int32 +FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + TArray& AllSockets, + const bool& isPackedPrim) +{ + // Attributes we are interested in. + // Position + TArray Positions; + HAPI_AttributeInfo AttribInfoPositions; + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bool bHasRotation = false; + TArray Rotations; + HAPI_AttributeInfo AttribInfoRotations; + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bool bHasScale = false; + TArray Scales; + HAPI_AttributeInfo AttribInfoScales; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // We can also get the sockets rotation from the normal + bool bHasNormals = false; + TArray Normals; + HAPI_AttributeInfo AttribInfoNormals; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Socket Name + bool bHasNames = false; + TArray Names; + HAPI_AttributeInfo AttribInfoNames; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bool bHasActors = false; + TArray Actors; + HAPI_AttributeInfo AttribInfoActors; + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bool bHasTags = false; + TArray Tags; + HAPI_AttributeInfo AttribInfoTags; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + + // Lambda function for creating the socket and adding it to the array + // Shared between the by Attribute / by Group methods + int32 FoundSocketCount = 0; + auto AddSocketToArray = [&](const int32& PointIdx) + { + FHoudiniMeshSocket CurrentSocket; + FVector currentPosition = FVector::ZeroVector; + if (Positions.IsValidIndex(PointIdx * 3 + 2)) + { + currentPosition.X = Positions[PointIdx * 3] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Y = Positions[PointIdx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + currentPosition.Z = Positions[PointIdx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + FVector currentScale = FVector::OneVector; + if (bHasScale && Scales.IsValidIndex(PointIdx * 3 + 2)) + { + currentScale.X = Scales[PointIdx * 3]; + currentScale.Y = Scales[PointIdx * 3 + 2]; + currentScale.Z = Scales[PointIdx * 3 + 1]; + } + + FQuat currentRotation = FQuat::Identity; + if (bHasRotation && Rotations.IsValidIndex(PointIdx * 4 + 3)) + { + currentRotation.X = Rotations[PointIdx * 4]; + currentRotation.Y = Rotations[PointIdx * 4 + 2]; + currentRotation.Z = Rotations[PointIdx * 4 + 1]; + currentRotation.W = -Rotations[PointIdx * 4 + 3]; + } + else if (bHasNormals && Normals.IsValidIndex(PointIdx * 3 + 2)) + { + FVector vNormal; + vNormal.X = Normals[PointIdx * 3]; + vNormal.Y = Normals[PointIdx * 3 + 2]; + vNormal.Z = Normals[PointIdx * 3 + 1]; + + if (vNormal != FVector::ZeroVector) + currentRotation = FQuat::FindBetween(FVector::UpVector, vNormal); + } + + if (bHasNames && Names.IsValidIndex(PointIdx)) + CurrentSocket.Name = Names[PointIdx]; + + if (bHasActors && Actors.IsValidIndex(PointIdx)) + CurrentSocket.Actor = Actors[PointIdx]; + + if (bHasTags && Tags.IsValidIndex(PointIdx)) + CurrentSocket.Tag = Tags[PointIdx]; + + // If the scale attribute wasn't set on all socket, we might end up + // with a zero scale socket, avoid that. + if (currentScale == FVector::ZeroVector) + currentScale = FVector::OneVector; + + CurrentSocket.Transform.SetLocation(currentPosition); + CurrentSocket.Transform.SetRotation(currentRotation); + CurrentSocket.Transform.SetScale3D(currentScale); + + // We want to make sure we're not adding the same socket multiple times + AllSockets.AddUnique(CurrentSocket); + + FoundSocketCount++; + + return true; + }; + + + // Lambda function for reseting the arrays/attributes + auto ResetArraysAndAttr = [&]() + { + // Position + Positions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Rotation + bHasRotation = false; + Rotations.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoRotations); + + // Scale + bHasScale = false; + Scales.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoScales); + + // When using socket groups, we can also get the sockets rotation from the normal + bHasNormals = false; + Normals.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Socket Name + bHasNames = false; + Names.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNames); + + // Socket Actor + bHasActors = false; + Actors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoActors); + + // Socket Tags + bHasTags = false; + Tags.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTags); + }; + + //------------------------------------------------------------------------- + // FIND SOCKETS BY POINT GROUPS + //------------------------------------------------------------------------- + + // Get object / geo group memberships for primitives. + TArray GroupNames; + if (!FHoudiniEngineUtils::HapiGetGroupNames( + GeoId, PartId, HAPI_GROUPTYPE_POINT, isPackedPrim, GroupNames)) + { + HOUDINI_LOG_MESSAGE(TEXT("GetMeshSocketList: Geo [%d] Part [%d] non-fatal error reading point group names"), GeoId, PartId); + } + + // First, we want to make sure we have at least one socket group before continuing + bool bHasSocketGroup = false; + for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) + { + const FString & GroupName = GroupNames[GeoGroupNameIdx]; + if (GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) + || GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) + { + bHasSocketGroup = true; + break; + } + } + + if (!bHasSocketGroup) + return FoundSocketCount; + + // Get the part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), GeoId, PartId, &PartInfo)) + return false; + + // Reset the data arrays and attributes + ResetArraysAndAttr(); + + // Retrieve position data. + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, Positions)) + return false; + + // Retrieve rotation data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_ROTATION, AttribInfoRotations, Rotations)) + bHasRotation = true; + + // Retrieve normal data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, Normals)) + bHasNormals = true; + + // Retrieve scale data. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + GeoId, PartId, HAPI_UNREAL_ATTRIB_SCALE, AttribInfoScales, Scales)) + bHasScale = true; + + // Retrieve mesh socket names. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, AttribInfoNames, Names)) + bHasNames = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME_OLD, AttribInfoNames, Names)) + bHasNames = true; + + // Retrieve mesh socket actor. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR, AttribInfoActors, Actors)) + bHasActors = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_ACTOR_OLD, AttribInfoActors, Actors)) + bHasActors = true; + + // Retrieve mesh socket tags. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, AttribInfoTags, Tags)) + bHasTags = true; + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG_OLD, AttribInfoTags, Tags)) + bHasTags = true; + + // Extracting Sockets vertices + for (int32 GeoGroupNameIdx = 0; GeoGroupNameIdx < GroupNames.Num(); ++GeoGroupNameIdx) + { + const FString & GroupName = GroupNames[GeoGroupNameIdx]; + if (!GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX, ESearchCase::IgnoreCase) + && !GroupName.StartsWith(HAPI_UNREAL_GROUP_SOCKET_PREFIX_OLD, ESearchCase::IgnoreCase)) + continue; + + bool AllEquals = false; + TArray< int32 > PointGroupMembership; + FHoudiniEngineUtils::HapiGetGroupMembership( + GeoId, PartInfo, HAPI_GROUPTYPE_POINT, GroupName, PointGroupMembership, AllEquals); + + // Go through all primitives. + for (int32 PointIdx = 0; PointIdx < PointGroupMembership.Num(); ++PointIdx) + { + if (PointGroupMembership[PointIdx] == 0) + { + if (AllEquals) + break; + else + continue; + } + + // Add the corresponding socket to the array + AddSocketToArray(PointIdx); + } + } + + return FoundSocketCount; +} + +bool +FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( + UStaticMesh* StaticMesh, + TArray& AllSockets, + const bool& CleanImportSockets) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Remove the sockets from the previous cook! + if (CleanImportSockets) + { + StaticMesh->Sockets.RemoveAll([=](UStaticMeshSocket* Socket) { return Socket ? Socket->bSocketCreatedAtImport : true; }); + } + + if (AllSockets.Num() <= 0) + return true; + + // Having sockets with empty names can lead to various issues, so we'll create one now + for (int32 Idx = 0; Idx < AllSockets.Num(); ++Idx) + { + // Assign the unnamed sockets with default names + if (AllSockets[Idx].Name.IsEmpty()) + AllSockets[Idx].Name = TEXT("Socket ") + FString::FromInt(Idx); + } + + // ensure the socket names are unique. (Unreal will use the first socket if multiple socket have the same name) + for (int32 Idx_i = 0; Idx_i < AllSockets.Num(); ++Idx_i) + { + int32 Count = 0; + for (int32 Idx_j = Idx_i + 1; Idx_j < AllSockets.Num(); ++Idx_j) + { + if (AllSockets[Idx_i].Name.Equals(AllSockets[Idx_j].Name)) + { + Count += 1; + AllSockets[Idx_j].Name = AllSockets[Idx_j].Name + "_" + FString::FromInt(Count); + } + } + } + + // Clear all the sockets of the output static mesh. + StaticMesh->Sockets.Empty(); + + for (int32 nSocket = 0; nSocket < AllSockets.Num(); nSocket++) + { + // Create a new Socket + UStaticMeshSocket* Socket = NewObject(StaticMesh); + if (!Socket || Socket->IsPendingKill()) + continue; + + Socket->RelativeLocation = AllSockets[nSocket].Transform.GetLocation(); + Socket->RelativeRotation = FRotator(AllSockets[nSocket].Transform.GetRotation()); + Socket->RelativeScale = AllSockets[nSocket].Transform.GetScale3D(); + Socket->SocketName = FName(*AllSockets[nSocket].Name); + + // Socket Tag + FString Tag; + if (!AllSockets[nSocket].Tag.IsEmpty()) + Tag = AllSockets[nSocket].Tag; + + // The actor will be stored temporarily in the socket's Tag as we need a StaticMeshComponent to add an actor to the socket + Tag += TEXT("|") + AllSockets[nSocket].Actor; + + Socket->Tag = Tag; + Socket->bSocketCreatedAtImport = true; + + StaticMesh->Sockets.Add(Socket); + } + + return true; +} + +bool +FHoudiniEngineUtils::CreateAttributesFromTags( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const TArray& Tags ) +{ + if (Tags.Num() <= 0) + return false; + + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Get the destination part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); + + bool NeedToCommitGeo = false; + for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) + { + FString TagString; + Tags[TagIdx].ToString(TagString); + SanitizeHAPIVariableName(TagString); + + // Create a primitive attribute for the tag + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + AttributeInfo.count = PartInfo.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; + + FString AttributeName = TEXT(HAPI_UNREAL_ATTRIB_TAG_PREFIX) + FString::FromInt(TagIdx); + AttributeName.RemoveSpacesInline(); + + Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo); + + if (Result != HAPI_RESULT_SUCCESS) + continue; + + TArray TagStr; + TagStr.Add(FHoudiniEngineUtils::ExtractRawString(TagString)); + + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, TCHAR_TO_ANSI(*AttributeName), &AttributeInfo, + TagStr.GetData(), 0, AttributeInfo.count); + + if (HAPI_RESULT_SUCCESS == Result) + NeedToCommitGeo = true; + + // Free memory for allocated by ExtractRawString + FHoudiniEngineUtils::FreeRawStringMemory(TagStr); + } + + return NeedToCommitGeo; +} + +bool +FHoudiniEngineUtils::CreateGroupsFromTags( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const TArray& Tags ) +{ + if (Tags.Num() <= 0) + return true; + + HAPI_Result Result = HAPI_RESULT_FAILURE; + + // Get the destination part info + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, &PartInfo), false); + + bool NeedToCommitGeo = false; + for (int32 TagIdx = 0; TagIdx < Tags.Num(); TagIdx++) + { + FString TagString; + Tags[TagIdx].ToString(TagString); + SanitizeHAPIVariableName(TagString); + + const char * TagStr = FHoudiniEngineUtils::ExtractRawString(TagString); + + // Create a primitive group for this tag + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, TagStr) ) + { + // Set the group's Memberships + TArray GroupArray; + GroupArray.Init(1, PartInfo.faceCount); + + if ( HAPI_RESULT_SUCCESS == FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_GROUPTYPE_PRIM, TagStr, + GroupArray.GetData(), 0, PartInfo.faceCount) ) + { + NeedToCommitGeo = true; + } + } + + // Free memory allocated by ExtractRawString() + FHoudiniEngineUtils::FreeRawStringMemory(TagStr); + } + + return NeedToCommitGeo; +} + + +bool +FHoudiniEngineUtils::SanitizeHAPIVariableName(FString& String) +{ + // Only keep alphanumeric characters, underscores + // Also, if the first character is a digit, append an underscore at the beginning + TArray& StrArray = String.GetCharArray(); + if (StrArray.Num() <= 0) + return false; + + for (auto& CurChar : StrArray) + { + const bool bIsValid = (CurChar >= TEXT('A') && CurChar <= TEXT('Z')) + || (CurChar >= TEXT('a') && CurChar <= TEXT('z')) + || (CurChar >= TEXT('0') && CurChar <= TEXT('9')) + || (CurChar == TEXT('_')) || (CurChar == TEXT('\0')); + + if(bIsValid) + continue; + + CurChar = TEXT('_'); + } + + if (StrArray.Num() > 0) + { + TCHAR FirstChar = StrArray[0]; + if (FirstChar >= TEXT('0') && FirstChar <= TEXT('9')) + StrArray.Insert(TEXT('_'), 0); + } + + return true; +} + +bool +FHoudiniEngineUtils::GetUnrealTagAttributes( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags) +{ + FString TagAttribBase = TEXT("unreal_tag_"); + bool bAttributeFound = true; + int32 TagIdx = 0; + while (bAttributeFound) + { + FString CurrentTagAttr = TagAttribBase + FString::FromInt(TagIdx++); + bAttributeFound = HapiCheckAttributeExists(GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), HAPI_ATTROWNER_PRIM); + if (!bAttributeFound) + break; + + // found the unreal_tag_X attribute, get its value and add it to the array + FString TagValue = FString(); + + // Create an AttributeInfo + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, TCHAR_TO_UTF8(*CurrentTagAttr), + AttributeInfo, StringData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) + { + if (StringData.Num() > 0) + TagValue = StringData[0]; + } + + FName NameTag = *TagValue; + OutTags.Add(NameTag); + } + + return true; +} + + +int32 +FHoudiniEngineUtils::GetGenericAttributeList( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FString& InGenericAttributePrefix, + TArray& OutFoundAttributes, + const HAPI_AttributeOwner& AttributeOwner, + const int32& InAttribIndex) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineUtils::GetGenericAttributeList); + + // Get the part info to get the attribute counts for the specified owner + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), InGeoNodeId, InPartId, &PartInfo), false); + + int32 nAttribCount = PartInfo.attributeCounts[AttributeOwner]; + + // Get all attribute names for that part + TArray AttribNameSHArray; + AttribNameSHArray.SetNum(nAttribCount); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, AttributeOwner, + AttribNameSHArray.GetData(), nAttribCount)) + { + return 0; + } + + // For everything but detail attribute, + // if an attribute index was specified, only extract the attribute value for that specific index + // if not, extract all values for the given attribute + bool HandleSplit = false; + int32 AttribIndex = -1; + if ((AttributeOwner != HAPI_ATTROWNER_DETAIL) && (InAttribIndex != -1)) + { + // The index has already been specified so we'll use it + HandleSplit = true; + AttribIndex = InAttribIndex; + } + + int32 FoundCount = 0; + for (int32 Idx = 0; Idx < AttribNameSHArray.Num(); ++Idx) + { + int32 AttribNameSH = (int32)AttribNameSHArray[Idx]; + FString AttribName = TEXT(""); + FHoudiniEngineString::ToFString(AttribNameSH, AttribName); + if (!AttribName.StartsWith(InGenericAttributePrefix, ESearchCase::IgnoreCase)) + continue; + + // Get the Attribute Info + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), AttributeOwner, &AttribInfo)) + { + // failed to get that attribute's info + continue; + } + + int32 AttribStart = 0; + int32 AttribCount = AttribInfo.count; + if (HandleSplit) + { + // For split primitives, we need to only get only one value for the proper split prim + // Make sure that the split index is valid + if (AttribIndex >= 0 && AttribIndex < AttribInfo.count) + { + AttribStart = AttribIndex; + AttribCount = 1; + } + } + + // + FHoudiniGenericAttribute CurrentGenericAttribute; + // Remove the generic attribute prefix + CurrentGenericAttribute.AttributeName = AttribName.Right(AttribName.Len() - InGenericAttributePrefix.Len()); + + CurrentGenericAttribute.AttributeOwner = (EAttribOwner)AttribInfo.owner; + + // Get the attribute type and tuple size + CurrentGenericAttribute.AttributeType = (EAttribStorageType)AttribInfo.storage; + CurrentGenericAttribute.AttributeCount = AttribInfo.count; + CurrentGenericAttribute.AttributeTupleSize = AttribInfo.tupleSize; + + if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT64) + { + // Initialize the value array + CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloat64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, 0, + CurrentGenericAttribute.DoubleValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::FLOAT) + { + // Initialize the value array + TArray FloatValues; + FloatValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, FloatValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to double + CurrentGenericAttribute.DoubleValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < FloatValues.Num(); n++) + CurrentGenericAttribute.DoubleValues[n] = (double)FloatValues[n]; + + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT64) + { +#if PLATFORM_LINUX + // On Linux, we unfortunately cannot guarantee that int64 and HAPI_Int64 + // are of the same type, to properly read the value, we must first check the + // size, then either cast them (if sizes match) or convert the values (if sizes don't match) + if (sizeof(int64) != sizeof(HAPI_Int64)) + { + // int64 and HAPI_Int64 are of different size, we need to cast + TArray HAPIIntValues; + HAPIIntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, HAPIIntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to int64 + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < HAPIIntValues.Num(); n++) + CurrentGenericAttribute.IntValues[n] = (int64)HAPIIntValues[n]; + } + else + { + // Initialize the value array + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) with a reinterpret_cast since sizes match + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, reinterpret_cast(CurrentGenericAttribute.IntValues.GetData()), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + } +#else + // Initialize the value array + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, CurrentGenericAttribute.IntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } +#endif + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::INT) + { + // Initialize the value array + TArray IntValues; + IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the value(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + 0, IntValues.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert them to int64 + CurrentGenericAttribute.IntValues.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + for (int32 n = 0; n < IntValues.Num(); n++) + CurrentGenericAttribute.IntValues[n] = (int64)IntValues[n]; + + } + else if (CurrentGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + // Initialize a string handle array + TArray HapiSHArray; + HapiSHArray.SetNumZeroed(AttribCount * AttribInfo.tupleSize); + + // Get the string handle(s) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, + TCHAR_TO_UTF8(*AttribName), &AttribInfo, + HapiSHArray.GetData(), + AttribStart, AttribCount)) + { + // failed to get that attribute's data + continue; + } + + // Convert the String Handles to FStrings + // using a map to minimize the number of HAPI calls + FHoudiniEngineString::SHArrayToFStringArray(HapiSHArray, CurrentGenericAttribute.StringValues); + } + else + { + // Unsupported type, skipping! + continue; + } + + // We can add the UPropertyAttribute to the array + OutFoundAttributes.Add(CurrentGenericAttribute); + FoundCount++; + } + + return FoundCount; +} + + +bool +FHoudiniEngineUtils::GetGenericPropertiesAttributes(const HAPI_NodeId& InGeoNodeId, const HAPI_PartId& InPartId, + const bool InbFindDetailAttributes, const int32& InFirstValidPrimIndex, const int32& InFirstValidVertexIndex, const int32& InFirstValidPointIndex, + TArray& OutPropertyAttributes) +{ + int32 FoundCount = 0; + + // List all the generic property detail attributes ... + if (InbFindDetailAttributes) + { + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + } + + // .. then the primitive property attributes for the given prim + if (InFirstValidPrimIndex != INDEX_NONE) + { + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, InFirstValidPrimIndex); + } + + if (InFirstValidVertexIndex != INDEX_NONE) + { + // .. then finally, point uprop attributes for the given point + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_VERTEX, InFirstValidVertexIndex); + } + + if (InFirstValidPointIndex != INDEX_NONE) + { + // .. then finally, point uprop attributes for the given point + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, InFirstValidPointIndex); + } + + return FoundCount > 0; +} + +bool +FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(UObject* InObject, + const TArray& InAllPropertyAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current Property Attribute + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) + continue; + + // Success! + NumSuccess++; +#if defined(HOUDINI_ENGINE_LOGGING) + const FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + const FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); +#endif + } + + return (NumSuccess > 0); +} + +bool +FHoudiniEngineUtils::SetGenericPropertyAttribute( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FHoudiniGenericAttribute& InPropertyAttribute) +{ + HAPI_AttributeOwner AttribOwner; + switch (InPropertyAttribute.AttributeOwner) + { + case EAttribOwner::Point: + AttribOwner = HAPI_ATTROWNER_POINT; + break; + case EAttribOwner::Vertex: + AttribOwner = HAPI_ATTROWNER_VERTEX; + break; + case EAttribOwner::Prim: + AttribOwner = HAPI_ATTROWNER_PRIM; + break; + case EAttribOwner::Detail: + AttribOwner = HAPI_ATTROWNER_DETAIL; + break; + case EAttribOwner::Invalid: + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InPropertyAttribute.AttributeOwner); + return false; + } + + // Create the attribute via HAPI + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.tupleSize = InPropertyAttribute.AttributeTupleSize; + AttributeInfo.count = InPropertyAttribute.AttributeCount; + AttributeInfo.exists = true; + AttributeInfo.owner = AttribOwner; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + switch(InPropertyAttribute.AttributeType) + { + case (EAttribStorageType::INT): + AttributeInfo.storage = HAPI_STORAGETYPE_INT; + break; + case (EAttribStorageType::INT64): + AttributeInfo.storage = HAPI_STORAGETYPE_INT64; + break; + case (EAttribStorageType::FLOAT): + AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT; + break; + case (EAttribStorageType::FLOAT64): + AttributeInfo.storage = HAPI_STORAGETYPE_FLOAT64; + break; + case (EAttribStorageType::STRING): + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + break; + case (EAttribStorageType::Invalid): + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Storage Type: %d"), InPropertyAttribute.AttributeType); + return false; + } + + // Create the new attribute + if (HAPI_RESULT_SUCCESS != FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo)) + { + return false; + } + + // The New attribute has been successfully created, set its value + switch (InPropertyAttribute.AttributeType) + { + case EAttribStorageType::INT: + { + TArray TempArray; + TempArray.Reserve(InPropertyAttribute.IntValues.Num()); + for (auto Value : InPropertyAttribute.IntValues) + { + TempArray.Add(static_cast(Value)); + } + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + TempArray.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + case EAttribStorageType::INT64: + { +#if PLATFORM_LINUX + // On Linux, we unfortunately cannot guarantee that int64 and HAPI_Int64 are of the same type, + TArray HAPIIntValues; + HAPIIntValues.SetNumZeroed(InPropertyAttribute.IntValues.Num()); + for (int32 n = 0; n < HAPIIntValues.Num(); n++) + HAPIIntValues[n] = (HAPI_Int64)InPropertyAttribute.IntValues[n]; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + HAPIIntValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } +#else + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + InPropertyAttribute.IntValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } +#endif + break; + } + + case EAttribStorageType::FLOAT: + { + + TArray TempArray; + TempArray.Reserve(InPropertyAttribute.DoubleValues.Num()); + for (auto Value : InPropertyAttribute.DoubleValues) + { + TempArray.Add(static_cast(Value)); + } + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + TempArray.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + case EAttribStorageType::FLOAT64: + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloat64Data( + FHoudiniEngine::Get().GetSession(), + InGeoNodeId, InPartId, TCHAR_TO_ANSI(*InPropertyAttribute.AttributeName), &AttributeInfo, + InPropertyAttribute.DoubleValues.GetData(), 0, AttributeInfo.count)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + case EAttribStorageType::STRING: + { + if (HAPI_RESULT_SUCCESS != FHoudiniEngineUtils::SetAttributeStringData( + InPropertyAttribute.StringValues, + InGeoNodeId, + InPartId, + InPropertyAttribute.AttributeName, + AttributeInfo)) + { + HOUDINI_LOG_WARNING(TEXT("Could not set attribute %s"), *InPropertyAttribute.AttributeName); + } + break; + } + + default: + // Unsupported storage type + HOUDINI_LOG_WARNING(TEXT("Unsupported storage type: %d"), InPropertyAttribute.AttributeType); + break; + } + + return true; +} + +void +FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const FString& Key, const FString& Value) +{ + if (!Package || Package->IsPendingKill()) + return; + + UMetaData * MetaData = Package->GetMetaData(); + if (MetaData && !MetaData->IsPendingKill()) + MetaData->SetValue(Object, *Key, *Value); +} + + +bool +FHoudiniEngineUtils::AddLevelPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + ULevel* InLevel, + const int32& InCount) +{ + if (InNodeId < 0 || InCount <= 0) + return false; + + if (!InLevel || InLevel->IsPendingKill()) + return false; + + // Extract the level path from the level + FString LevelPath = InLevel->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = InCount; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(InCount); + for (int32 Idx = 0; Idx < InCount; ++Idx) + { + PrimitiveAttrs[Idx] = LevelPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} + + +bool +FHoudiniEngineUtils::AddActorPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + AActor* InActor, + const int32& InCount) +{ + if (InNodeId < 0 || InCount <= 0) + return false; + + if (!InActor || InActor->IsPendingKill()) + return false; + + // Extract the actor path + FString ActorPath = InActor->GetPathName(); + + // Get name of attribute used for Actor path + std::string MarshallingAttributeActorPath = HAPI_UNREAL_ATTRIB_ACTOR_PATH; + + // Marshall in Actor path. + HAPI_AttributeInfo AttributeInfoActorPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoActorPath); + AttributeInfoActorPath.count = InCount; + AttributeInfoActorPath.tupleSize = 1; + AttributeInfoActorPath.exists = true; + AttributeInfoActorPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoActorPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoActorPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InNodeId, InPartId, + MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string ActorPathCStr = TCHAR_TO_ANSI(*ActorPath); + const char* ActorPathCStrRaw = ActorPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(InCount); + for (int32 Idx = 0; Idx < InCount; ++Idx) + { + PrimitiveAttrs[Idx] = ActorPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, + MarshallingAttributeActorPath.c_str(), &AttributeInfoActorPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoActorPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_actor_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} + + +bool +FHoudiniEngineUtils::ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx) +{ + const TArray< FVector2D > & LightmapUVs = RawMesh.WedgeTexCoords[LightmapSourceIdx]; + const TArray< uint32 > & Indices = RawMesh.WedgeIndices; + + if (LightmapUVs.Num() != Indices.Num()) + { + // This is invalid raw mesh; by design we consider that it contains invalid lightmap faces. + return true; + } + + for (int32 Idx = 0; Idx < Indices.Num(); Idx += 3) + { + const FVector2D & uv0 = LightmapUVs[Idx + 0]; + const FVector2D & uv1 = LightmapUVs[Idx + 1]; + const FVector2D & uv2 = LightmapUVs[Idx + 2]; + + if (uv0 == uv1 && uv1 == uv2) + { + // Detect invalid lightmap face, can stop. + return true; + } + } + + // Otherwise there are no invalid lightmap faces. + return false; +} + +void +FHoudiniEngineUtils::CreateSlateNotification( + const FString& NotificationString, const float& NotificationExpire, const float& NotificationFadeOut ) +{ +#if WITH_EDITOR + // Trying to display SlateNotifications while in a background thread will crash UE + if (!IsInGameThread() && !IsInSlateThread() && !IsInAsyncLoadingThread()) + return; + + // Check whether we want to display Slate notifications. + bool bDisplaySlateCookingNotifications = true; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + bDisplaySlateCookingNotifications = HoudiniRuntimeSettings->bDisplaySlateCookingNotifications; + + if (!bDisplaySlateCookingNotifications) + return; + + FText NotificationText = FText::FromString(NotificationString); + FNotificationInfo Info(NotificationText); + + Info.bFireAndForget = true; + Info.FadeOutDuration = NotificationFadeOut; + Info.ExpireDuration = NotificationExpire; + + TSharedPtr HoudiniBrush = FHoudiniEngine::Get().GetHoudiniEngineLogoBrush(); + if (HoudiniBrush.IsValid()) + Info.Image = HoudiniBrush.Get(); + + FSlateNotificationManager::Get().AddNotification(Info); +#endif + + return; +} + +FString +FHoudiniEngineUtils::GetHoudiniEnginePluginDir() +{ + FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(EnginePluginDir)) + return EnginePluginDir; + + FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(ProjectPluginDir)) + return ProjectPluginDir; + + TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); + FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; + if (FPaths::DirectoryExists(PluginBaseDir)) + return PluginBaseDir; + + HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); + + return EnginePluginDir; +} + + +HAPI_Result +FHoudiniEngineUtils::CreateNode( + const HAPI_NodeId& InParentNodeId, + const FString& InOperatorName, + const FString& InNodeLabel, + const HAPI_Bool& bInCookOnCreation, + HAPI_NodeId* OutNewNodeId) +{ + // Call HAPI::CreateNode + HAPI_Result Result = FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), + InParentNodeId, TCHAR_TO_UTF8(*InOperatorName), TCHAR_TO_UTF8(*InNodeLabel), bInCookOnCreation, OutNewNodeId); + + // Return now if CreateNode fialed + if (Result != HAPI_RESULT_SUCCESS) + return Result; + + // Loop on the cook_state status until it's ready + int CurrentStatus = HAPI_State::HAPI_STATE_STARTING_LOAD; + while (CurrentStatus > HAPI_State::HAPI_STATE_MAX_READY_STATE) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), + HAPI_StatusType::HAPI_STATUS_COOK_STATE, &CurrentStatus)) + { + // Exit the loop if GetStatus somehow fails + break; + } + } + + if (CurrentStatus == HAPI_STATE_READY_WITH_FATAL_ERRORS) + { + // Fatal errors - failed + HOUDINI_LOG_ERROR(TEXT("Failed to create node %s - %s"), *InOperatorName, *InNodeLabel); + return HAPI_RESULT_FAILURE; + } + else if (CurrentStatus == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // Mention the errors - still return success + HOUDINI_LOG_WARNING(TEXT("Cook errors when creating node %s - %s"), *InOperatorName, *InNodeLabel); + } + + return HAPI_RESULT_SUCCESS; +} + + +int32 +FHoudiniEngineUtils::HapiGetCookCount(const HAPI_NodeId& InNodeId) +{ + int32 CookCount = -1; + + FHoudiniApi::GetTotalCookCount( + FHoudiniEngine::Get().GetSession(), + InNodeId, HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_ANY, true, &CookCount); + + /* + // TODO: + // Use HAPI_GetCookingTotalCount() when available + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + + int32 CookCount = -1; + HAPI_Result Result = FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), InNodeId, &NodeInfo); + + if (Result != HAPI_RESULT_FAILURE) + { + if (NodeInfo.type != HAPI_NODETYPE_OBJ) + { + // For SOP assets, get the cook count straight from the Asset Node + CookCount = NodeInfo.totalCookCount; + } + else + { + // For OBJ nodes, get the cook count from the display geos + // Retrieve information about each object contained within our asset. + TArray< HAPI_ObjectInfo > ObjectInfos; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(InNodeId, ObjectInfos)) + return false; + + for (auto CurrentHapiObjectInfo : ObjectInfos) + { + // Get the Display Geo's info + HAPI_GeoInfo DisplayHapiGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) + { + continue; + } + + HAPI_NodeInfo DisplayNodeInfo; + FHoudiniApi::NodeInfo_Init(&DisplayNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo(FHoudiniEngine::Get().GetSession(), DisplayHapiGeoInfo.nodeId, &DisplayNodeInfo)) + { + continue; + } + + CookCount += DisplayNodeInfo.totalCookCount; + } + } + } + */ + + return CookCount; +} + +bool +FHoudiniEngineUtils::GetLevelPathAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutLevelPaths, + HAPI_AttributeOwner InAttributeOwner, + const int32& InStartIndex, + const int32& InCount) +{ + // --------------------------------------------- + // Attribute: unreal_level_path + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_LEVEL_PATH, + AttributeInfo, OutLevelPaths, 1, InAttributeOwner, InStartIndex, InCount)) + { + if (OutLevelPaths.Num() > 0) + return true; + } + + OutLevelPaths.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetOutputNameAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutOutputNames, + const int32& InStartIndex, + const int32& InCount) +{ + // --------------------------------------------- + // Attribute: unreal_output_name + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, + AttributeInfo, OutOutputNames, 1, HAPI_ATTROWNER_INVALID, InStartIndex, InCount)) + { + if (OutOutputNames.Num() > 0) + return true; + } + + OutOutputNames.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, + AttributeInfo, OutOutputNames, 1, HAPI_ATTROWNER_INVALID, InStartIndex, InCount)) + { + if (OutOutputNames.Num() > 0) + return true; + } + + OutOutputNames.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetTileAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutTileValues, + const HAPI_AttributeOwner& InAttribOwner, + const int32& InStart, + const int32& InCount) +{ + // --------------------------------------------- + // Attribute: tile + // --------------------------------------------- + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, + AttribInfoTile, OutTileValues, 0, InAttribOwner, InStart, InCount)) + { + if (OutTileValues.Num() > 0) + return true; + } + + OutTileValues.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_AttributeOwner& InAttributeOwner, + TArray& OutBakeFolder, + const HAPI_PartId& InPartId, + const int32& InStart, + const int32& InCount) +{ + OutBakeFolder.Empty(); + + HAPI_AttributeInfo BakeFolderAttribInfo; + FHoudiniApi::AttributeInfo_Init(&BakeFolderAttribInfo); + if (HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_FOLDER, + BakeFolderAttribInfo, OutBakeFolder, 1, InAttributeOwner, + InStart, InCount)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + + OutBakeFolder.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + TArray& OutBakeFolder, + const HAPI_PartId& InPartId, + const int32& InStart, + const int32& InCount) +{ + OutBakeFolder.Empty(); + + if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_PRIM, OutBakeFolder, InPartId, InStart, InCount)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + + if (GetBakeFolderAttribute(InGeoId, HAPI_ATTROWNER_DETAIL, OutBakeFolder, InPartId, InStart, InCount)) + { + if (OutBakeFolder.Num() > 0) + return true; + } + + OutBakeFolder.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeActorAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeActorNames, + const HAPI_AttributeOwner& InAttributeOwner, + const int32& InStart, + const int32& InCount) +{ + // --------------------------------------------- + // Attribute: unreal_bake_actor + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_ACTOR, + AttributeInfo, OutBakeActorNames, 1, InAttributeOwner, InStart, InCount)) + { + if (OutBakeActorNames.Num() > 0) + return true; + } + + OutBakeActorNames.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeOutlinerFolders, + const HAPI_AttributeOwner& InAttributeOwner, + const int32& InStart, + const int32& InCount) +{ + // --------------------------------------------- + // Attribute: unreal_bake_outliner_folder + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, + AttributeInfo, OutBakeOutlinerFolders, 1, InAttributeOwner, InStart, InCount)) + { + if (OutBakeOutlinerFolders.Num() > 0) + return true; + } + + OutBakeOutlinerFolders.Empty(); + return false; +} + +bool +FHoudiniEngineUtils::MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel) +{ + if (!InActor || !InDesiredLevel) + return false; + + ULevel* PreviousLevel = InActor->GetLevel(); + if (PreviousLevel == InDesiredLevel) + return true; + + UWorld* CurrentWorld = InActor->GetWorld(); + if(CurrentWorld) + CurrentWorld->RemoveActor(InActor, true); + + //Set the outer of Actor to NewLevel + FHoudiniEngineUtils::RenameObject(InActor, (const TCHAR*)0, InDesiredLevel); + InDesiredLevel->Actors.Add(InActor); + + return true; +} + +bool +FHoudiniEngineUtils::HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions, const bool& bWaitForCompletion) +{ + // Check for an invalid node id + if (InNodeId < 0) + return false; + + // No Cook Options were specified, use the default one + if (InCookOptions == nullptr) + { + // Use the default cook options + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); + } + else + { + // Use the provided CookOptions + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, InCookOptions), false); + } + + // If we don't need to wait for completion, return now + if (!bWaitForCompletion) + return true; + + // Wait for the cook to finish + HAPI_Result Result = HAPI_RESULT_SUCCESS; + while (true) + { + // Get the current cook status + int Status = HAPI_STATE_STARTING_COOK; + HOUDINI_CHECK_ERROR_GET(&Result, FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status)); + + if (Status == HAPI_STATE_READY) + { + // The cook has been successful. + return true; + } + else if (Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS) + { + // There was an error while cooking the node. + //FString CookResultString = FHoudiniEngineUtils::GetCookResult(); + //HOUDINI_LOG_ERROR(); + return false; + } + + // We want to yield a bit. + FPlatformProcess::Sleep(0.1f); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h index 64428e2f4..2888f962f 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h @@ -1,676 +1,701 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniEnginePrivatePCH.h" -#include "EngineUtils.h" -#include - -#include "HoudiniOutput.h" -#include "HoudiniPackageParams.h" -#include "Containers/UnrealString.h" - -class FString; -class UStaticMesh; -class UHoudiniAsset; -class UHoudiniAssetComponent; - -struct FHoudiniPartInfo; -struct FHoudiniMeshSocket; -struct FHoudiniGeoPartObject; -struct FHoudiniGenericAttribute; - -struct FRawMesh; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniInstancerType : uint8; - -struct HOUDINIENGINE_API FHoudiniEngineUtils -{ - friend struct FUnrealMeshTranslator; - - public: - // Load libHAPI and return handle to it, also store location of loaded libHAPI in passed argument. - static void* LoadLibHAPI(FString& StoredLibHAPILocation); - - // Return true if module has been properly initialized. - static bool IsInitialized(); - - // Return type of license used. - static bool GetLicenseType(FString & LicenseType); - - // Cook the specified node id - // if the cook options are null, the defualt one will be used - // if bWaitForCompletion is true, this call will be blocking until the cook is finished - static bool HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions = nullptr, const bool& bWaitForCompletion = false); - - // Return a specified HAPI status string. - static const FString GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity); - - // Return a string representing cooking result. - static const FString GetCookResult(); - - // Return a string indicating cook state. - static const FString GetCookState(); - - // Return a string error description. - static const FString GetErrorDescription(); - - // Return a string description of error from a given error code. - static const FString GetErrorDescription(HAPI_Result Result); - - // Return the errors, warning and messages on a specified node - static const FString GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId); - - static const FString GetCookLog(TArray& InHACs); - - static const FString GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent); - - // Updates the Object transform of a Houdini Asset Component - static bool UploadHACTransform(UHoudiniAssetComponent* HAC); - - // Convert FString to std::string - static void ConvertUnrealString(const FString & UnrealString, std::string& String); - - // Wrapper for the CreateNode function - // As HAPI_CreateNode is an async call, this function actually waits for the node creation to be done before returning - static HAPI_Result CreateNode( - const HAPI_NodeId& InParentNodeId, - const FString& InOperatorName, - const FString& InNodeLabel, - const HAPI_Bool& bInCookOnCreation, - HAPI_NodeId* OutNewNodeId); - - static int32 HapiGetCookCount(const HAPI_NodeId& InNodeId); - - // HAPI : Retrieve the asset node's object transform. **/ - static bool HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform); - - // HAPI : Translate HAPI transform to Unreal one. - static void TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform); - - // HAPI : Translate HAPI Euler transform to Unreal one. - static void TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform); - - // HAPI : Translate Unreal transform to HAPI one. - static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform); - - // HAPI : Translate Unreal transform to HAPI Euler one. - static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_TransformEuler & HapiTransformEuler); - - // Return true if asset is valid. - static bool IsHoudiniNodeValid(const HAPI_NodeId& AssetId); - - // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. - static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms); - - // HAPI: Retrieve Path to the given Node, relative to the given Node - static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); - - // HAPI: Retrieve the relative for the given HGPO Node - static bool HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath); - - // HAPI : Return all group names for a given Geo. - static bool HapiGetGroupNames( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - const HAPI_GroupType& GroupType, const bool& isPackedPrim, - TArray& OutGroupNames ); - - // HAPI : Retrieve group membership. - static bool HapiGetGroupMembership( - const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, - const HAPI_GroupType& GroupType, const FString & GroupName, - TArray& OutGroupMembership, bool& OutAllEquals); - - // HAPI : Given vertex list, retrieve new vertex list for a specified group. - // Return number of processed valid index vertices for this split. - static int32 HapiGetVertexListForGroup( - const HAPI_NodeId& GeoId, - const HAPI_PartInfo& PartInfo, - const FString& GroupName, - const TArray& FullVertexList, - TArray& NewVertexList, - TArray& AllVertexList, - TArray& AllFaceList, - TArray& AllGroupFaceIndices, - int32& FirstValidVertex, - int32& FirstValidPrim, - const bool& isPackedPrim); - - // HAPI : Get attribute data as float. - static bool HapiGetAttributeDataAsFloat( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize = 0, - HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as Integer. - static bool HapiGetAttributeDataAsInteger( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - const int32& InTupleSize = 0, - const HAPI_AttributeOwner& InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as strings. - static bool HapiGetAttributeDataAsString( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& OutAttributeInfo, - TArray& OutData, - int32 InTupleSize = 0, - HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID); - - // HAPI : Get attribute data as strings. - static bool HapiGetAttributeDataAsStringFromInfo( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - const char * InAttribName, - HAPI_AttributeInfo& InAttributeInfo, - TArray& OutData); - - // HAPI : Check if given attribute exists. - static bool HapiCheckAttributeExists( - const HAPI_NodeId& GeoId, - const HAPI_PartId& PartId, - const char * AttribName, - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID); - - // HAPI: Returns all the attributes of a given type for a given owner - static int32 HapiGetAttributeOfType( - const HAPI_NodeId& GeoId, - const HAPI_NodeId& PartId, - const HAPI_AttributeOwner& AttributeOwner, - const HAPI_AttributeTypeInfo& AttributeType, - TArray& MatchingAttributesInfo, - TArray& MatchingAttributesName); - - // HAPI : Look for a parameter by name and returns its index. Returns -1 if not found. - static HAPI_ParmId HapiFindParameterByName( - const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo); - - // HAPI : Look for a parameter by tag and returns its index. Returns -1 if not found. - static HAPI_ParmId HapiFindParameterByTag( - const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo); - - // Returns true is the given Geo-Part is an attribute instancer - static bool IsAttributeInstancer( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); - - // HAPI : Return a give node's parent ID, -1 if none - static HAPI_NodeId HapiGetParentNodeId(const HAPI_NodeId& NodeId); - - // HAPI : Marshaling, disconnect input asset from a given slot. - static bool HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex); - - // Destroy asset, returns the status. - static bool DestroyHoudiniAsset(const HAPI_NodeId& AssetId); - - // Loads an HDA file and returns its AssetLibraryId - static bool LoadHoudiniAsset( - const UHoudiniAsset * HoudiniAsset, - HAPI_AssetLibraryId & OutAssetLibraryId); - - // Returns the name of the available subassets in a loaded HDA - static bool GetSubAssetNames( - const HAPI_AssetLibraryId& AssetLibraryId, - TArray< HAPI_StringHandle > & OutAssetNames); - - static bool OpenSubassetSelectionWindow( - TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ); - - // Returns the name of a Houdini asset. - static bool GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString); - - // Gets preset data for a given asset. - static bool GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer); - - // HAPI : Set asset transform. - static bool HapiSetAssetTransform(const HAPI_NodeId& AssetNodeId, const FTransform & Transform); - - // TODO: Move me somewhere else - static void AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC); - - // Triggers an update the details panel - // Will use an AsyncTask if we're not in the game thread - // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. - static void UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate); - - // Triggers an update the details panel - // Will use an AsyncTask if we're not in the game thread - // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. - static void UpdateEditorProperties(TArray InObjectsToUpdate, const bool& InForceFullUpdate); - - // Triggers an update the details panel - static void UpdateBlueprintEditor(UHoudiniAssetComponent* HAC); - - // Check if the Houdini asset component is being cooked - static bool IsHoudiniAssetComponentCooking(UObject* InObj); - - // Helper function to set attribute string data for a single FString - static HAPI_Result SetAttributeStringData( - const FString& InString, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo); - - // Helper function to set attribute string data for a FString array - static HAPI_Result SetAttributeStringData( - const TArray& InStringArray, - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - const FString& InAttributeName, - const HAPI_AttributeInfo& InAttributeInfo); - - static bool HapiGetParameterDataAsString( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const FString& DefaultValue, - FString& OutValue); - - static bool HapiGetParameterDataAsInteger( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const int32& DefaultValue, - int32 & OutValue); - - static bool HapiGetParameterDataAsFloat( - const HAPI_NodeId& NodeId, - const std::string& ParmName, - const float& DefaultValue, - float& OutValue); - - // Returns a list of all the generic attributes for a given attribute owner - static int32 GetGenericAttributeList( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FString& InGenericAttributePrefix, - TArray& OutFoundAttributes, - const HAPI_AttributeOwner& AttributeOwner, - const int32& InAttribIndex = -1); - - // Helper functions for generic property attributes - static bool GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const bool InFindDetailAttributes, // if true, find default attributes - const int32& InFirstValidPrimIndex, // If not INDEX_NONE, look for primitive attribute - const int32& InFirstValidVertexIndex, // If this is not INDEX_NONE, look for vertex attribute - const int32& InFirstValidPointIndex, // If this is not INDEX_NONE, look for point attribute - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes); - - // Helper function for setting a generic attribute on geo (UE -> HAPI) - static bool SetGenericPropertyAttribute( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const FHoudiniGenericAttribute& InPropertyAttribute); - - /* - // Tries to update values for all the UProperty attributes to apply on the object. - static void ApplyUPropertyAttributesOnObject( - UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify ); - */ - /* - static bool TryToFindInStructProperty( - UObject* Object, FString UPropertyNameToFind, UStructProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); - */ - /* - static bool TryToFindInArrayProperty( - UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); - */ - - static void AddHoudiniMetaInformationToPackage( - UPackage* Package, UObject* Object, const FString& Key, const FString& Value); - - // Adds the HoudiniLogo mesh to a Houdini Asset Component - static bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); - - // Removes the default Houdini logo mesh from a HAC - static bool RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC); - - // Indicates if a HAC has the Houdini logo mesh - static bool HasHoudiniLogo(UHoudiniAssetComponent* HAC); - - // - static HAPI_PartInfo ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo); - - // - static int32 AddMeshSocketsToArray_Group( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - TArray& AllSockets, const bool& isPackedPrim); - - // - static int32 AddMeshSocketsToArray_DetailAttribute( - const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, - TArray& AllSockets, const bool& isPackedPrim); - - static bool AddMeshSocketsToStaticMesh( - UStaticMesh* StaticMesh, - TArray& AllSockets, - const bool& CleanImportSockets); - - // - static bool CreateGroupsFromTags( - const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); - - // - static bool CreateAttributesFromTags( - const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); - - static bool GetUnrealTagAttributes(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags); - - // Helper function to access the "unreal_level_path" attribute - static bool GetLevelPathAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutLevelPath, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to access the custom output name attribute - static bool GetOutputNameAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutOutputName); - - // Helper function to access the "tile" attribute - static bool GetTileAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutTileValue, - const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); - - // Helper function to access the "unreal_bake_folder" attribute - static bool GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - HAPI_AttributeOwner InAttributeOwner, - TArray& OutBakeFolder, - HAPI_PartId InPartId=0); - - // Helper function to access the "unreal_bake_folder" attribute - // We check for a primitive attribute first, if the primitive attribute does not exist, we check for a - // detail attribute. - static bool GetBakeFolderAttribute( - const HAPI_NodeId& InGeoId, - TArray& OutBakeFolder, - HAPI_PartId InPartId=0); - - // Helper function to access the bake output actor attribute (unreal_bake_actor) - static bool GetBakeActorAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeActorNames, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Helper function to access the bake output actor attribute (unreal_bake_outliner_folder) - static bool GetBakeOutlinerFolderAttribute( - const HAPI_NodeId& InGeoId, - const HAPI_PartId& InPartId, - TArray& OutBakeOutlinerFolders, - HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID); - - // Adds the "unreal_level_path" primitive attribute - static bool AddLevelPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - ULevel* InLevel, - const int32& InCount); - - // Adds the "unreal_actor_path" primitive attribute - static bool AddActorPathAttribute( - const HAPI_NodeId& InNodeId, - const HAPI_PartId& InPartId, - AActor* InActor, - const int32& InCount); - - // Helper function used to extract a const char* from a FString - // !! Allocates memory using malloc that will need to be freed afterwards! - static char * ExtractRawString(const FString& Name); - - // Frees memory allocated by ExtractRawString() - static void FreeRawStringMemory(const char*& InRawString); - - // Frees memory allocated by ExtractRawString() - static void FreeRawStringMemory(TArray& InRawStringArray); - - // Make sure a string complies with Houdini's internal variable naming convention (group, attr etc..) - static bool SanitizeHAPIVariableName(FString& String); - - /** How many GUID symbols are used for package component name generation. **/ - static const int32 PackageGUIDComponentNameLength; - - /** How many GUID symbols are used for package item name generation. **/ - static const int32 PackageGUIDItemNameLength; - - /** Helper routine to check invalid lightmap faces. **/ - static bool ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx); - - // Helper function for creating a temporary Slate notification. - static void CreateSlateNotification( - const FString& NotificationString, - const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, - const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); - - static FString GetHoudiniEnginePluginDir(); - - // ------------------------------------------------- - // UWorld and UPackage utilities - // ------------------------------------------------- - - // Find actor in a given world by label - template - static T* FindActorInWorldByLabel(UWorld* InWorld, FString ActorLabel, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) - { - T* OutActor = nullptr; - for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) - { - OutActor = *ActorIt; - if (!OutActor) - continue; - if (OutActor->GetActorLabel() == ActorLabel) - return OutActor; - } - return nullptr; - } - - // Find actor in a given world by name - template - static T* FindActorInWorld(UWorld* InWorld, FName ActorName, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) - { - T* OutActor = nullptr; - for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) - { - OutActor = *ActorIt; - if (!OutActor) - continue; - if (OutActor->GetFName().Compare(ActorName)==0) - return OutActor; - } - return nullptr; - } - - // Find an actor by name - static UWorld* FindWorldInPackage(const FString& PackagePath, bool bCreatedMissingPackage, bool& bOutPackageCreated); - - // Determine the appropriate world and level in which to spawn a new actor. - static bool FindWorldAndLevelForSpawning( - UWorld* CurrentWorld, - const FString& PackagePath, - bool bCreateMissingPackage, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bOutPackageCreated, - bool& bPackageInWorld); - - template - static T* SpawnActorInLevel(UWorld* InWorld, ULevel* InLevel) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = InLevel; - return InWorld->SpawnActor(SpawnParams); - } - - // Force the AssetRegistry to recursively rescan a path for - // any new packages that it may not know about, starting at the directory - // in which the given world package is located. This is typically useful - // for WorldComposition to detect new packages immediately after they - // were created. - static void RescanWorldPath(UWorld* InWorld); - - // ------------------------------------------------- - // Actor Utilities - // ------------------------------------------------- - - // Find in actor that belongs to the given outer matching the specified name. - // If the actor doesn't match the type, or is in a PendingKill state, rename it - // so that a new actor can be created with the given name. - // Note that if an actor with the give name was found, it will be returned via `OutFoundActor`. - static AActor* FindOrRenameInvalidActorGeneric(UClass* Class, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor); - - template - static T* FindOrRenameInvalidActor(UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) - { - return Cast( FindOrRenameInvalidActorGeneric(T::StaticClass(), InWorld, InName, OutFoundActor) ); - } - - // Moves an actor to the specified level - static bool MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel); - - // ------------------------------------------------- - // Debug Utilities - // ------------------------------------------------- - - // Log debug info for the given package - static void LogPackageInfo(const FString& InLongPackageName); - static void LogPackageInfo(const UPackage* InPackage); - - static void LogWorldInfo(const FString& InLongPackageName); - static void LogWorldInfo(const UWorld* InWorld); - - static FString HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType); - static FString HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState); - - // ------------------------------------------------- - // Generic naming / pathing utilities - // ------------------------------------------------- - - static bool RenameObject(UObject* Object, const TCHAR* NewName = nullptr, UObject* NewOuter = nullptr, ERenameFlags Flags = REN_None); - - // Rename the actor to a unique / generated name. - static FName RenameToUniqueActor(AActor* InActor, const FString& InName); - - // Safely rename the actor by ensuring that there aren't any existing objects left - // in the actor's outer with the same name. If an existing object was found, rename it and return it. - static UObject* SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel=true); - - // ------------------------------------------------- - // PackageParam utilities - // ------------------------------------------------- - - // Helper for populating FHoudiniPackageParams. - // If bAutomaticallySetAttemptToLoadMissingPackages is true, then - // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. - static void FillInPackageParamsForBakingOutput( - FHoudiniPackageParams& OutPackageParams, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FString &BakeFolder, - const FString &ObjectName, - const FString &HoudiniAssetName, - EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, - bool bAutomaticallySetAttemptToLoadMissingPackages=true); - - // Helper for populating FHoudiniPackageParams when baking. This includes configuring the resolver to - // resolve the object name and unreal_bake_folder and setting these resolved values on the PackageParams. - // If bAutomaticallySetAttemptToLoadMissingPackages is true, then - // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. - static void FillInPackageParamsForBakingOutputWithResolver( - UWorld* const InWorldContext, - const UHoudiniAssetComponent* HoudiniAssetComponent, - const FHoudiniOutputObjectIdentifier& InIdentifier, - const FHoudiniOutputObject& InOutputObject, - const FString &InDefaultObjectName, - const FString &InHoudiniAssetName, - FHoudiniPackageParams& OutPackageParams, - FHoudiniAttributeResolver& OutResolver, - const FString &InDefaultBakeFolder=FString(), - EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, - bool bAutomaticallySetAttemptToLoadMissingPackages=true, - bool bInSkipObjectNameResolutionAndUseDefault=false, - bool bInSkipBakeFolderResolutionAndUseDefault=false); - - // ------------------------------------------------- - // Foliage utilities - // ------------------------------------------------- - - // If the foliage editor mode is active, repopulate the list of foliage types in the UI. - // NOTE: this is a currently a bit of a hack: we deactive and reactive the foliage mode (if it was active), - // since the relevant functions are not API exported. - // Returns true if the list was repopulated. - static bool RepopulateFoliageTypeListInUI(); - - // ------------------------------------------------- - // Landscape utilities - // ------------------------------------------------- - - // Iterate over the input objects and gather only the landscape inputs. - static void GatherLandscapeInputs(UHoudiniAssetComponent* HAC, TArray& AllInputLandscapes, TArray& InputLandscapesToUpdate); - - - static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(const UObject* Obj); - - protected: - - // Computes the XX.YY.ZZZ version string using HAPI_Version - static FString ComputeVersionString(bool ExtraDigit); - -#if PLATFORM_WINDOWS - // Attempt to locate libHAPI on Windows in the registry. Return handle if located and return location. - static void* LocateLibHAPIInRegistry( - const FString& HoudiniInstallationType, FString& StoredLibHAPILocation, bool LookIn32bitRegistry); -#endif - - // Triggers an update the details panel - //static void UpdateEditorProperties_Internal(UObject* ObjectToUpdate, const bool& bInForceFullUpdate); - - // Triggers an update the details panel - static void UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate); - - // Trigger an update of the Blueprint Editor on the game thread - static void UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniEnginePrivatePCH.h" +#include "EngineUtils.h" +#include + +#include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" +#include "Containers/UnrealString.h" + +class FString; +class UStaticMesh; +class UHoudiniAsset; +class UHoudiniAssetComponent; + +struct FHoudiniPartInfo; +struct FHoudiniMeshSocket; +struct FHoudiniGeoPartObject; +struct FHoudiniGenericAttribute; + +struct FRawMesh; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniInstancerType : uint8; + +struct HOUDINIENGINE_API FHoudiniEngineUtils +{ + friend struct FUnrealMeshTranslator; + + public: + // Load libHAPI and return handle to it, also store location of loaded libHAPI in passed argument. + static void* LoadLibHAPI(FString& StoredLibHAPILocation); + + // Return true if module has been properly initialized. + static bool IsInitialized(); + + // Return type of license used. + static bool GetLicenseType(FString & LicenseType); + + // Cook the specified node id + // if the cook options are null, the defualt one will be used + // if bWaitForCompletion is true, this call will be blocking until the cook is finished + static bool HapiCookNode(const HAPI_NodeId& InNodeId, HAPI_CookOptions* InCookOptions = nullptr, const bool& bWaitForCompletion = false); + + // Return a specified HAPI status string. + static const FString GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity); + + // Return a string representing cooking result. + static const FString GetCookResult(); + + // Return a string indicating cook state. + static const FString GetCookState(); + + // Return a string error description. + static const FString GetErrorDescription(); + + // Return a string description of error from a given error code. + static const FString GetErrorDescription(HAPI_Result Result); + + // Return a string description for a Houdini Engine session connection error. + static const FString GetConnectionError(); + + // Return the errors, warning and messages on a specified node + static const FString GetNodeErrorsWarningsAndMessages(const HAPI_NodeId& InNodeId); + + static const FString GetCookLog(TArray& InHACs); + + static const FString GetAssetHelp(UHoudiniAssetComponent* HoudiniAssetComponent); + + // Updates the Object transform of a Houdini Asset Component + static bool UploadHACTransform(UHoudiniAssetComponent* HAC); + + // Convert FString to std::string + static void ConvertUnrealString(const FString & UnrealString, std::string& String); + + // Wrapper for the CreateNode function + // As HAPI_CreateNode is an async call, this function actually waits for the node creation to be done before returning + static HAPI_Result CreateNode( + const HAPI_NodeId& InParentNodeId, + const FString& InOperatorName, + const FString& InNodeLabel, + const HAPI_Bool& bInCookOnCreation, + HAPI_NodeId* OutNewNodeId); + + static int32 HapiGetCookCount(const HAPI_NodeId& InNodeId); + + // HAPI : Retrieve the asset node's object transform. **/ + static bool HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform); + + // HAPI : Translate HAPI transform to Unreal one. + static void TranslateHapiTransform(const HAPI_Transform & HapiTransform, FTransform & UnrealTransform); + + // HAPI : Translate HAPI Euler transform to Unreal one. + static void TranslateHapiTransform(const HAPI_TransformEuler & HapiTransformEuler, FTransform & UnrealTransform); + + // HAPI : Translate Unreal transform to HAPI one. + static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_Transform & HapiTransform); + + // HAPI : Translate Unreal transform to HAPI Euler one. + static void TranslateUnrealTransform(const FTransform & UnrealTransform, HAPI_TransformEuler & HapiTransformEuler); + + // Return true if asset is valid. + static bool IsHoudiniNodeValid(const HAPI_NodeId& AssetId); + + // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. + static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms); + + // HAPI: Retrieve Path to the given Node, relative to the given Node + static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); + + // HAPI: Retrieve the relative for the given HGPO Node + static bool HapiGetNodePath(const FHoudiniGeoPartObject& InHGPO, FString& OutPath); + + // HAPI : Return all group names for a given Geo. + static bool HapiGetGroupNames( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + const HAPI_GroupType& GroupType, const bool& isPackedPrim, + TArray& OutGroupNames ); + + // HAPI : Retrieve group membership. + static bool HapiGetGroupMembership( + const HAPI_NodeId& GeoId, const HAPI_PartInfo& PartInfo, + const HAPI_GroupType& GroupType, const FString & GroupName, + TArray& OutGroupMembership, bool& OutAllEquals); + + // HAPI : Given vertex list, retrieve new vertex list for a specified group. + // Return number of processed valid index vertices for this split. + static int32 HapiGetVertexListForGroup( + const HAPI_NodeId& GeoId, + const HAPI_PartInfo& PartInfo, + const FString& GroupName, + const TArray& FullVertexList, + TArray& NewVertexList, + TArray& AllVertexList, + TArray& AllFaceList, + TArray& AllGroupFaceIndices, + int32& FirstValidVertex, + int32& FirstValidPrim, + const bool& isPackedPrim); + + // HAPI : Get attribute data as float. + static bool HapiGetAttributeDataAsFloat( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize = 0, + HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID, + const int32& InStartIndex = 0, + const int32& InCount = -1); + + // HAPI : Get attribute data as Integer. + static bool HapiGetAttributeDataAsInteger( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + const int32& InTupleSize = 0, + const HAPI_AttributeOwner& InOwner = HAPI_ATTROWNER_INVALID, + const int32& InStartIndex = 0, + const int32& InCount = -1); + + // HAPI : Get attribute data as strings. + static bool HapiGetAttributeDataAsString( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& OutAttributeInfo, + TArray& OutData, + int32 InTupleSize = 0, + HAPI_AttributeOwner InOwner = HAPI_ATTROWNER_INVALID, + const int32& InStartIndex = 0, + const int32& InCount = -1); + + // HAPI : Get attribute data as strings. + static bool HapiGetAttributeDataAsStringFromInfo( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const char * InAttribName, + HAPI_AttributeInfo& InAttributeInfo, + TArray& OutData, + const int32& InStartIndex = 0, + const int32& InCount = -1); + + // HAPI : Check if given attribute exists. + static bool HapiCheckAttributeExists( + const HAPI_NodeId& GeoId, + const HAPI_PartId& PartId, + const char * AttribName, + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID); + + // HAPI: Returns all the attributes of a given type for a given owner + static int32 HapiGetAttributeOfType( + const HAPI_NodeId& GeoId, + const HAPI_NodeId& PartId, + const HAPI_AttributeOwner& AttributeOwner, + const HAPI_AttributeTypeInfo& AttributeType, + TArray& MatchingAttributesInfo, + TArray& MatchingAttributesName); + + // HAPI : Look for a parameter by name and returns its index. Returns -1 if not found. + static HAPI_ParmId HapiFindParameterByName( + const HAPI_NodeId& InNodeId, const std::string& InParmName, HAPI_ParmInfo& OutFoundParmInfo); + + // HAPI : Look for a parameter by tag and returns its index. Returns -1 if not found. + static HAPI_ParmId HapiFindParameterByTag( + const HAPI_NodeId& InNodeId, const std::string& InParmTag, HAPI_ParmInfo& OutFoundParmInfo); + + // Returns true is the given Geo-Part is an attribute instancer + static bool IsAttributeInstancer( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, EHoudiniInstancerType& OutInstancerType); + + // HAPI : Return a give node's parent ID, -1 if none + static HAPI_NodeId HapiGetParentNodeId(const HAPI_NodeId& NodeId); + + // HAPI : Marshaling, disconnect input asset from a given slot. + static bool HapiDisconnectAsset(HAPI_NodeId HostAssetId, int32 InputIndex); + + // Destroy asset, returns the status. + static bool DestroyHoudiniAsset(const HAPI_NodeId& AssetId); + + // Loads an HDA file and returns its AssetLibraryId + static bool LoadHoudiniAsset( + const UHoudiniAsset * HoudiniAsset, + HAPI_AssetLibraryId & OutAssetLibraryId); + + // Returns the name of the available subassets in a loaded HDA + static bool GetSubAssetNames( + const HAPI_AssetLibraryId& AssetLibraryId, + TArray< HAPI_StringHandle > & OutAssetNames); + + static bool OpenSubassetSelectionWindow( + TArray& AssetNames, HAPI_StringHandle& OutPickedAssetName ); + + // Returns the name of a Houdini asset. + static bool GetHoudiniAssetName(const HAPI_NodeId& AssetNodeId, FString & NameString); + + // Gets preset data for a given asset. + static bool GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char > & PresetBuffer); + + // HAPI : Set asset transform. + static bool HapiSetAssetTransform(const HAPI_NodeId& AssetNodeId, const FTransform & Transform); + + // TODO: Move me somewhere else + static void AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC); + + // Triggers an update the details panel + // Will use an AsyncTask if we're not in the game thread + // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. + static void UpdateEditorProperties(UObject* InObjectToUpdate, const bool& InForceFullUpdate); + + // Triggers an update the details panel + // Will use an AsyncTask if we're not in the game thread + // NOTE: Prefer using IDetailLayoutBuilder::ForceRefreshDetails() instead. + static void UpdateEditorProperties(TArray InObjectsToUpdate, const bool& InForceFullUpdate); + + // Triggers an update the details panel + static void UpdateBlueprintEditor(UHoudiniAssetComponent* HAC); + + // Check if the Houdini asset component is being cooked + static bool IsHoudiniAssetComponentCooking(UObject* InObj); + + // Helper function to set attribute string data for a single FString + static HAPI_Result SetAttributeStringData( + const FString& InString, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo); + + // Helper function to set attribute string data for a FString array + static HAPI_Result SetAttributeStringData( + const TArray& InStringArray, + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + const FString& InAttributeName, + const HAPI_AttributeInfo& InAttributeInfo); + + static bool HapiGetParameterDataAsString( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const FString& DefaultValue, + FString& OutValue); + + static bool HapiGetParameterDataAsInteger( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const int32& DefaultValue, + int32 & OutValue); + + static bool HapiGetParameterDataAsFloat( + const HAPI_NodeId& NodeId, + const std::string& ParmName, + const float& DefaultValue, + float& OutValue); + + // Returns a list of all the generic attributes for a given attribute owner + static int32 GetGenericAttributeList( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FString& InGenericAttributePrefix, + TArray& OutFoundAttributes, + const HAPI_AttributeOwner& AttributeOwner, + const int32& InAttribIndex = -1); + + // Helper functions for generic property attributes + static bool GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const bool InFindDetailAttributes, // if true, find default attributes + const int32& InFirstValidPrimIndex, // If not INDEX_NONE, look for primitive attribute + const int32& InFirstValidVertexIndex, // If this is not INDEX_NONE, look for vertex attribute + const int32& InFirstValidPointIndex, // If this is not INDEX_NONE, look for point attribute + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes); + + // Helper function for setting a generic attribute on geo (UE -> HAPI) + static bool SetGenericPropertyAttribute( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const FHoudiniGenericAttribute& InPropertyAttribute); + + /* + // Tries to update values for all the UProperty attributes to apply on the object. + static void ApplyUPropertyAttributesOnObject( + UObject* MeshComponent, const TArray< UGenericAttribute >& UPropertiesToModify ); + */ + /* + static bool TryToFindInStructProperty( + UObject* Object, FString UPropertyNameToFind, UStructProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); + */ + /* + static bool TryToFindInArrayProperty( + UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ); + */ + + static void AddHoudiniMetaInformationToPackage( + UPackage* Package, UObject* Object, const FString& Key, const FString& Value); + + // Adds the HoudiniLogo mesh to a Houdini Asset Component + static bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); + + // Removes the default Houdini logo mesh from a HAC + static bool RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC); + + // Indicates if a HAC has the Houdini logo mesh + static bool HasHoudiniLogo(UHoudiniAssetComponent* HAC); + + // + static HAPI_PartInfo ToHAPIPartInfo(const FHoudiniPartInfo& InHPartInfo); + + // + static int32 AddMeshSocketsToArray_Group( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + TArray& AllSockets, const bool& isPackedPrim); + + // + static int32 AddMeshSocketsToArray_DetailAttribute( + const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, + TArray& AllSockets, const bool& isPackedPrim); + + static bool AddMeshSocketsToStaticMesh( + UStaticMesh* StaticMesh, + TArray& AllSockets, + const bool& CleanImportSockets); + + // + static bool CreateGroupsFromTags( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); + + // + static bool CreateAttributesFromTags( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const TArray& Tags); + + static bool GetUnrealTagAttributes(const HAPI_NodeId& GeoId, const HAPI_PartId& PartId, TArray& OutTags); + + // Helper function to access the "unreal_level_path" attribute + static bool GetLevelPathAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutLevelPath, + HAPI_AttributeOwner InAttributeOwner=HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID, + const int32& InStart = 0, + const int32& InCount = -1); + + // Helper function to access the custom output name attribute + static bool GetOutputNameAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutOutputName, + const int32& InStart = 0, + const int32& InCount = -1); + + // Helper function to access the "tile" attribute + static bool GetTileAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutTileValue, + const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID, + const int32& InStart = 0, + const int32& InCount = -1); + + // Helper function to access the "unreal_bake_folder" attribute + static bool GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_AttributeOwner& InAttributeOwner, + TArray& OutBakeFolder, + const HAPI_PartId& InPartId = 0, + const int32& InStart = 0, + const int32& InCount = -1); + + // Helper function to access the "unreal_bake_folder" attribute + // We check for a primitive attribute first, if the primitive attribute does not exist, we check for a + // detail attribute. + static bool GetBakeFolderAttribute( + const HAPI_NodeId& InGeoId, + TArray& OutBakeFolder, + const HAPI_PartId& InPartId = 0, + const int32& InStart = 0, + const int32& InCount = -1); + + // Helper function to access the bake output actor attribute (unreal_bake_actor) + static bool GetBakeActorAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeActorNames, + const HAPI_AttributeOwner& InAttributeOwner = HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID, + const int32& InStart = 0, + const int32& InCount = -1); + + // Helper function to access the bake output actor attribute (unreal_bake_outliner_folder) + static bool GetBakeOutlinerFolderAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeOutlinerFolders, + const HAPI_AttributeOwner& InAttributeOwner = HAPI_AttributeOwner::HAPI_ATTROWNER_INVALID, + const int32& InStart = 0, + const int32& InCount = -1); + + // Adds the "unreal_level_path" primitive attribute + static bool AddLevelPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + ULevel* InLevel, + const int32& InCount); + + // Adds the "unreal_actor_path" primitive attribute + static bool AddActorPathAttribute( + const HAPI_NodeId& InNodeId, + const HAPI_PartId& InPartId, + AActor* InActor, + const int32& InCount); + + // Helper function used to extract a const char* from a FString + // !! Allocates memory using malloc that will need to be freed afterwards! + static char * ExtractRawString(const FString& Name); + + // Frees memory allocated by ExtractRawString() + static void FreeRawStringMemory(const char*& InRawString); + + // Frees memory allocated by ExtractRawString() + static void FreeRawStringMemory(TArray& InRawStringArray); + + // Make sure a string complies with Houdini's internal variable naming convention (group, attr etc..) + static bool SanitizeHAPIVariableName(FString& String); + + /** How many GUID symbols are used for package component name generation. **/ + static const int32 PackageGUIDComponentNameLength; + + /** How many GUID symbols are used for package item name generation. **/ + static const int32 PackageGUIDItemNameLength; + + /** Helper routine to check invalid lightmap faces. **/ + static bool ContainsInvalidLightmapFaces(const FRawMesh & RawMesh, int32 LightmapSourceIdx); + + // Helper function for creating a temporary Slate notification. + static void CreateSlateNotification( + const FString& NotificationString, + const float& NotificationExpire = HAPI_UNREAL_NOTIFICATION_EXPIRE, + const float& NotificationFadeOut = HAPI_UNREAL_NOTIFICATION_FADEOUT); + + static FString GetHoudiniEnginePluginDir(); + + // ------------------------------------------------- + // UWorld and UPackage utilities + // ------------------------------------------------- + + // Find actor in a given world by label + template + static T* FindActorInWorldByLabel(UWorld* InWorld, FString ActorLabel, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) + { + T* OutActor = nullptr; + for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) + { + OutActor = *ActorIt; + if (!OutActor) + continue; + if (OutActor->GetActorLabel() == ActorLabel) + return OutActor; + } + return nullptr; + } + + // Find actor in a given world by name + template + static T* FindActorInWorld(UWorld* InWorld, FName ActorName, EActorIteratorFlags Flags = EActorIteratorFlags::AllActors) + { + T* OutActor = nullptr; + for (TActorIterator ActorIt(InWorld, T::StaticClass(), Flags); ActorIt; ++ActorIt) + { + OutActor = *ActorIt; + if (!OutActor) + continue; + if (OutActor->GetFName().Compare(ActorName)==0) + return OutActor; + } + return nullptr; + } + + // Find an actor by name + static UWorld* FindWorldInPackage(const FString& PackagePath, bool bCreatedMissingPackage, bool& bOutPackageCreated); + + // Determine the appropriate world and level in which to spawn a new actor. + static bool FindWorldAndLevelForSpawning( + UWorld* CurrentWorld, + const FString& PackagePath, + bool bCreateMissingPackage, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bOutPackageCreated, + bool& bPackageInWorld); + + template + static T* SpawnActorInLevel(UWorld* InWorld, ULevel* InLevel) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = InLevel; + return InWorld->SpawnActor(SpawnParams); + } + + // Force the AssetRegistry to recursively rescan a path for + // any new packages that it may not know about, starting at the directory + // in which the given world package is located. This is typically useful + // for WorldComposition to detect new packages immediately after they + // were created. + static void RescanWorldPath(UWorld* InWorld); + + // ------------------------------------------------- + // Actor Utilities + // ------------------------------------------------- + + // Find in actor that belongs to the given outer matching the specified name. + // If the actor doesn't match the type, or is in a PendingKill state, rename it + // so that a new actor can be created with the given name. + // Note that if an actor with the give name was found, it will be returned via `OutFoundActor`. + static AActor* FindOrRenameInvalidActorGeneric(UClass* Class, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor); + + template + static T* FindOrRenameInvalidActor(UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) + { + return Cast( FindOrRenameInvalidActorGeneric(T::StaticClass(), InWorld, InName, OutFoundActor) ); + } + + // Moves an actor to the specified level + static bool MoveActorToLevel(AActor* InActor, ULevel* InDesiredLevel); + + // ------------------------------------------------- + // Debug Utilities + // ------------------------------------------------- + + // Log debug info for the given package + static void LogPackageInfo(const FString& InLongPackageName); + static void LogPackageInfo(const UPackage* InPackage); + + static void LogWorldInfo(const FString& InLongPackageName); + static void LogWorldInfo(const UWorld* InWorld); + + static FString HapiGetEventTypeAsString(const HAPI_PDG_EventType& InEventType); + static FString HapiGetWorkitemStateAsString(const HAPI_PDG_WorkitemState& InWorkitemState); + + // ------------------------------------------------- + // Generic naming / pathing utilities + // ------------------------------------------------- + + static bool RenameObject(UObject* Object, const TCHAR* NewName = nullptr, UObject* NewOuter = nullptr, ERenameFlags Flags = REN_None); + + // Rename the actor to a unique / generated name. + static FName RenameToUniqueActor(AActor* InActor, const FString& InName); + + // Safely rename the actor by ensuring that there aren't any existing objects left + // in the actor's outer with the same name. If an existing object was found, rename it and return it. + static UObject* SafeRenameActor(AActor* InActor, const FString& InName, bool UpdateLabel=true); + + // ------------------------------------------------- + // PackageParam utilities + // ------------------------------------------------- + + // Helper for populating FHoudiniPackageParams. + // If bAutomaticallySetAttemptToLoadMissingPackages is true, then + // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. + static void FillInPackageParamsForBakingOutput( + FHoudiniPackageParams& OutPackageParams, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FString &BakeFolder, + const FString &ObjectName, + const FString &HoudiniAssetName, + EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, + bool bAutomaticallySetAttemptToLoadMissingPackages=true); + + // Helper for populating FHoudiniPackageParams when baking. This includes configuring the resolver to + // resolve the object name and unreal_bake_folder and setting these resolved values on the PackageParams. + // If bAutomaticallySetAttemptToLoadMissingPackages is true, then + // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. + static void FillInPackageParamsForBakingOutputWithResolver( + UWorld* const InWorldContext, + const UHoudiniAssetComponent* HoudiniAssetComponent, + const FHoudiniOutputObjectIdentifier& InIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FString &InDefaultObjectName, + const FString &InHoudiniAssetName, + FHoudiniPackageParams& OutPackageParams, + FHoudiniAttributeResolver& OutResolver, + const FString &InDefaultBakeFolder=FString(), + EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, + bool bAutomaticallySetAttemptToLoadMissingPackages=true, + bool bInSkipObjectNameResolutionAndUseDefault=false, + bool bInSkipBakeFolderResolutionAndUseDefault=false); + + // ------------------------------------------------- + // Foliage utilities + // ------------------------------------------------- + + // If the foliage editor mode is active, repopulate the list of foliage types in the UI. + // NOTE: this is a currently a bit of a hack: we deactive and reactive the foliage mode (if it was active), + // since the relevant functions are not API exported. + // Returns true if the list was repopulated. + static bool RepopulateFoliageTypeListInUI(); + + // ------------------------------------------------- + // Landscape utilities + // ------------------------------------------------- + + // Iterate over the input objects and gather only the landscape inputs. + static void GatherLandscapeInputs(UHoudiniAssetComponent* HAC, TArray& AllInputLandscapes, TArray& InputLandscapesToUpdate); + + + static UHoudiniAssetComponent* GetOuterHoudiniAssetComponent(const UObject* Obj); + + protected: + + // Computes the XX.YY.ZZZ version string using HAPI_Version + static FString ComputeVersionString(bool ExtraDigit); + +#if PLATFORM_WINDOWS + // Attempt to locate libHAPI on Windows in the registry. Return handle if located and return location. + static void* LocateLibHAPIInRegistry( + const FString& HoudiniInstallationType, FString& StoredLibHAPILocation, bool LookIn32bitRegistry); +#endif + + // Triggers an update the details panel + //static void UpdateEditorProperties_Internal(UObject* ObjectToUpdate, const bool& bInForceFullUpdate); + + // Triggers an update the details panel + static void UpdateEditorProperties_Internal(TArray ObjectsToUpdate, const bool& bInForceFullUpdate); + + // Trigger an update of the Blueprint Editor on the game thread + static void UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp index 02a090d77..eaeedd373 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.cpp @@ -1,775 +1,778 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoImportCommandlet.h" - -#include "DirectoryWatcherModule.h" -#include "Modules/ModuleManager.h" -#include "Misc/Guid.h" -#include "EditorFramework/AssetImportData.h" - -#include "Editor.h" -#include "FileHelpers.h" - -#include "MessageEndpointBuilder.h" - -#include "PackageTools.h" - -#include "IDirectoryWatcher.h" - -#include "Internationalization/Regex.h" - -#include "Interfaces/ISlateNullRendererModule.h" -#include "Rendering/SlateRenderer.h" -#include "Framework/Application/SlateApplication.h" - -#include "HoudiniPackageParams.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniOutput.h" -#include "HoudiniPDGImporterMessages.h" -#include "HoudiniMeshTranslator.h" -#include "HAL/ThreadManager.h" - - -UHoudiniGeoImportCommandlet::UHoudiniGeoImportCommandlet() -{ - HelpDescription = TEXT("Import BGEOs as UAssets. Includes an option to watch a directories and include new .bgeos created there."); - - HelpUsage = TEXT("HoudiniGeoImport Usage: HoudiniGeoImport {options} [filename.bgeo]"); - // "Options:\n" - // "\t-help or -?\n" - // "\t\tDisplays this help.\n\n" - // "\t-listen=manager_messaging_address\n\n" - // "\t\tListen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager.\n\n" - // "\t-watch=directory\n\n" - // "\t\tA directory to watch for new .bgeo files to import.\n\n" - // "\t-managerpid=owner_pid\n\n" - // "\t\tThe PID of the owner/manager process. If the manager process dies the commandlet also quits.\n\n" - // "\t-bake\n\n" - // "\t\tBake generated assets. Instancers are baked to blueprints. Not supported in -listen mode.\n\n" - // "\t[filename.bgeo]\n" - // "\t\tWhen not using -listen or -watch, the path to a .bgeo file must be specified for import.\n" - //); - - HelpParamNames = { - "help", - "listen", - "guid", - "watch", - "managerpid", - "bake" - }; - - HelpParamDescriptions = { - "Displays this help.", - "Listen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager. Expects the owning process' PID.", - "Specify a GUID for the commandlet. Useful to identify the commandlet when the messaging system is used.", - "A directory to watch for new .bgeo files to import.", - "The PID of the owner/manager process. If the manager process dies the commandlet also quits.", - "Bake generated assets. Instancers are baked to blueprints. Not supported in -listen mode." - }; - - IsClient = false; - IsEditor = true; - IsServer = false; - LogToConsole = true; - ShowProgress = false; - ShowErrorCount = false; - - // LogToConsole = false; - - Mode = EHoudiniGeoImportCommandletMode::None; - bBakeOutputs = false; -} - -void UHoudiniGeoImportCommandlet::PrintUsage() const -{ - HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpDescription); - HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpUsage); - const int32 NumOptions = HelpParamNames.Num(); - for (int32 Idx = 0; Idx < NumOptions; ++Idx) - { - HOUDINI_LOG_DISPLAY(TEXT("-%s\t%s"), *HelpParamNames[Idx], *HelpParamDescriptions[Idx]); - } -} - -void UHoudiniGeoImportCommandlet::PopulatePackageParams(const FString &InBGEOFilename, FHoudiniPackageParams& OutPackageParams) -{ - UObject* InParent = this; - - if (bBakeOutputs) - { - OutPackageParams.PackageMode = EPackageMode::Bake; - } - else - { - OutPackageParams.PackageMode = EPackageMode::CookToTemp; - } - OutPackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - OutPackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - OutPackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - OutPackageParams.HoudiniAssetName = FPaths::GetBaseFilename(InBGEOFilename); - OutPackageParams.HoudiniAssetActorName = FString(); - OutPackageParams.ObjectName = FPaths::GetBaseFilename(InBGEOFilename); - - if (!OutPackageParams.OuterPackage) - { - OutPackageParams.OuterPackage = InParent; - } - - if (!OutPackageParams.ComponentGUID.IsValid()) - { - // TODO: will need to reuse the GUID when reimporting? - OutPackageParams.ComponentGUID = FGuid::NewGuid(); - } -} - -void UHoudiniGeoImportCommandlet::TickDiscoveredFiles() -{ - for (auto &FileDataEntry : DiscoveredFiles) - { - FDiscoveredFileData &FileData = FileDataEntry.Value; - if (FileData.bImportNextTick && !FileData.bImported) - { - FileData.bImportNextTick = false; - FileData.ImportAttempts++; - - FHoudiniPackageParams PackageParams; - PopulatePackageParams(FileData.FileName, PackageParams); - TArray Outputs; - int32 Error = ImportBGEO(FileData.FileName, PackageParams, Outputs); - if (Error == 0) - { - FileData.bImported = true; - HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Done"), *FileData.FileName); - } - else - { - FileData.bImported = false; - HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Failed (%d)"), *FileData.FileName, Error); - } - } - } -} - -int32 UHoudiniGeoImportCommandlet::MainLoop() -{ - GIsRunning = true; - - IDirectoryWatcher* DirectoryWatcher = nullptr; - - if (Mode == EHoudiniGeoImportCommandletMode::Listen) - { - PDGEndpoint = FMessageEndpoint::Builder("PDG/BGEO Commandlet") - .Handling(this, &UHoudiniGeoImportCommandlet::HandleImportBGEOMessage) - .ReceivingOnThread(ENamedThreads::GameThread); - if (!PDGEndpoint.IsValid()) - { - GIsRunning = false; - return 3; - } - // Notify the manager that we are running - HOUDINI_LOG_DISPLAY(TEXT("Notifying the manager (%s) that we are running"), *ManagerAddress.ToString()); - // Try to send directly to the manager - // TODO: this initially direct message does not work, the address looks to be correct, perhaps there is some - // additional set up needed to connect / discover the endpoints? - PDGEndpoint->Send(new FHoudiniPDGImportBGEODiscoverMessage(Guid), ManagerAddress); - } - else if (Mode == EHoudiniGeoImportCommandletMode::Watch) - { - FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::Get().LoadModuleChecked(TEXT("DirectoryWatcher")); - DirectoryWatcher = DirectoryWatcherModule.Get(); - } - - // In UnrealEngine 4.25 and older we cannot tick the editor engine without slate being initialized. - if (!FSlateApplication::IsInitialized()) - { - FSlateApplication::InitHighDPI(false); - FSlateApplication::Create(); - } - - // If slate is initialized, make sure it has a renderer. If we have to create a renderer, create the null renderer. - if (FSlateApplication::IsInitialized() && !FSlateApplication::Get().GetRenderer()) - { - const TSharedPtr SlateRenderer = FModuleManager::Get().LoadModuleChecked("SlateNullRenderer").CreateSlateNullRenderer(); - const TSharedRef SlateRendererSharedRef = SlateRenderer.ToSharedRef(); - FSlateApplication::Get().InitializeRenderer(SlateRendererSharedRef); - } - - // in listen mode broadcast our presence every 60 seconds - // This is an attempt to test if it solves a rare issue where the endpoints appear to get - // "disconnected" and sending a message to a previously valid message address stops working, even though - // both processes are still running (happens especially when debugging with breakpoints) - const float BroadcastIntervalSeconds = 60.0f; - float LastbroadcastTimeSeconds = 0.0f; - - // main loop - while (GIsRunning && !IsEngineExitRequested()) - { - GEngine->UpdateTimeAndHandleMaxTickRate(); - GEngine->Tick(FApp::GetDeltaTime(), false); - - if (FSlateApplication::IsInitialized()) - { - FSlateApplication::Get().PumpMessages(); - FSlateApplication::Get().Tick(); - } - - // Required for FTimerManager to function - as it blocks ticks, if the frame counter doesn't change - GFrameCounter++; - - // update task graph - FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); - - FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()); - FThreadManager::Get().Tick(); - GEngine->TickDeferredCommands(); - - if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) - { - // DirectoryWatcher->Tick(FApp::GetDeltaTime()); - - // Process the discovered files - TickDiscoveredFiles(); - } - - if (OwnerProcHandle.IsValid() && !FPlatformProcess::IsProcRunning(OwnerProcHandle)) - { - // Our once valid owner has disappeared, so quit. - RequestEngineExit(TEXT("OwnerDisappeared")); - } - - if (Mode == EHoudiniGeoImportCommandletMode::Listen && PDGEndpoint.IsValid()) - { - const float TimeSeconds = FPlatformTime::Seconds(); - if (TimeSeconds - LastbroadcastTimeSeconds >= BroadcastIntervalSeconds) - { - LastbroadcastTimeSeconds = TimeSeconds; - // Broadcast a discover message to notify that we are still available - PDGEndpoint->Publish(new FHoudiniPDGImportBGEODiscoverMessage(Guid)); - - HOUDINI_LOG_MESSAGE(TEXT("Publishing FHoudiniPDGImportBGEODiscoverMessage(%s)"), *Guid.ToString()); - } - } - - FPlatformProcess::Sleep(0); - } - - PDGEndpoint.Reset(); - if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) - { - DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(DirectoryToWatch, DirectoryWatcherHandle); - } - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Shutdown(); - - GIsRunning = false; - - return 0; -} - -void UHoudiniGeoImportCommandlet::HandleImportBGEOMessage( - const FHoudiniPDGImportBGEOMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_DISPLAY(TEXT("Received BGEO import request from %s"), *InContext->GetSender().ToString()); - - FHoudiniPackageParams PackageParams; - InMessage.PopulatePackageParams(PackageParams); - - TArray Outputs; - TMap> OutputObjectAttributes; - TMap InstancedOutputPartData; - if (ImportBGEO(InMessage.FilePath, PackageParams, Outputs, &OutputObjectAttributes, &InstancedOutputPartData) == 0) - { - FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); - (*Reply) = InMessage; - // Reply->PopulateFromPackageParams(PackageParams); - Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Success; - - const int32 NumOutputs = Outputs.Num(); - Reply->Outputs.Init(FHoudiniPDGImportNodeOutput(), NumOutputs); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - FHoudiniPDGImportNodeOutput &MessageOutput = Reply->Outputs[Index]; - UHoudiniOutput* Output = Outputs[Index]; - for (const FHoudiniGeoPartObject& HGPO : Output->GetHoudiniGeoPartObjects()) - { - HOUDINI_LOG_WARNING(TEXT("HGPO %d %d %d"), HGPO.ObjectId, HGPO.GeoId, HGPO.PartId); - MessageOutput.HoudiniGeoPartObjects.Add(HGPO); - - // Get instancer data if this is an instancer output - if (Output->GetType() == EHoudiniOutputType::Instancer) - { - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = HGPO.ObjectId; - OutputIdentifier.GeoId = HGPO.GeoId; - OutputIdentifier.PartId = HGPO.PartId; - OutputIdentifier.PartName = HGPO.PartName; - - FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = InstancedOutputPartData.Find(OutputIdentifier); - if (InstancedOutputPartDataPtr) - { - InstancedOutputPartDataPtr->BuildFlatInstancedTransformsAndObjectPaths(); - MessageOutput.InstancedOutputPartData.Add(*InstancedOutputPartDataPtr); - } - else - { - MessageOutput.InstancedOutputPartData.Add(FHoudiniInstancedOutputPartData()); - } - } - } - for (const auto& Entry : Output->GetOutputObjects()) - { - HOUDINI_LOG_WARNING(TEXT("Identifier %d %d %d"), Entry.Key.ObjectId, Entry.Key.GeoId, Entry.Key.PartId); - - MessageOutput.OutputObjects.AddDefaulted(); - FHoudiniPDGImportNodeOutputObject& MessageOutputObject = MessageOutput.OutputObjects.Last(); - - FString PackagePath = IsValid(Entry.Value.OutputObject) ? Entry.Value.OutputObject->GetPathName() : ""; - MessageOutputObject.Identifier = Entry.Key; - MessageOutputObject.PackagePath = PackagePath; - const TArray* PropertyAttributes = OutputObjectAttributes.Find(Entry.Key); - if (PropertyAttributes) - MessageOutputObject.GenericAttributes = *PropertyAttributes; - MessageOutputObject.CachedAttributes = Entry.Value.CachedAttributes; - } - } - - PDGEndpoint->Send(Reply, InContext->GetSender()); - } - else - { - HOUDINI_LOG_WARNING(TEXT("BGEO import failed.")); - FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); - Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Failed; - PDGEndpoint->Send(Reply, InContext->GetSender()); - } - - // Cleanup the outputs (remove from root) - TArray PackagesToUnload; - for (UHoudiniOutput *CurOutput : Outputs) - { - if (!IsValid(CurOutput)) - continue; - - for (const auto& Entry : CurOutput->GetOutputObjects()) - { - if (IsValid(Entry.Value.OutputObject)) - { - UPackage *Outermost = Entry.Value.OutputObject->GetOutermost(); - if (IsValid(Outermost)) - { - PackagesToUnload.Add(Outermost); - } - - Entry.Value.OutputObject->RemoveFromRoot(); - } - } - - CurOutput->RemoveFromRoot(); - } - Outputs.Empty(); - OutputObjectAttributes.Empty(); - - if (PackagesToUnload.Num() > 0) - { - HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ..."), PackagesToUnload.Num()); - FText ErrorMessage; - if (!UPackageTools::UnloadPackages(PackagesToUnload, ErrorMessage)) - { - HOUDINI_LOG_WARNING(TEXT("Unload packages failed: %s"), *ErrorMessage.ToString()); - } - else - { - HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ... Success"), PackagesToUnload.Num()); - } - PackagesToUnload.Empty(); - } - - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); -} - -bool UHoudiniGeoImportCommandlet::StartHoudiniEngineSession() -{ - // Start Houdini Engine session - HOUDINI_LOG_DISPLAY(TEXT("Starting Houdini Engine session...")); - FHoudiniEngine& HoudiniEngine = FHoudiniEngine::Get(); - if (!HoudiniEngine.CreateSession( - EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe, - "hapi_bgeo_cmdlet")) - { - HOUDINI_LOG_ERROR(TEXT("Failed to start Houdini Engine session.")); - return false; - } - - return true; -} - -int32 UHoudiniGeoImportCommandlet::ImportBGEO( - const FString &InFilename, - const FHoudiniPackageParams &InPackageParams, - TArray& OutOutputs, - TMap>* OutGenericAttributes, - TMap* OutInstancedOutputPartData) -{ - if (!IsHoudiniEngineSessionRunning() && !StartHoudiniEngineSession()) - { - return 2; - } - - FHoudiniPackageParams PackageParams = InPackageParams; - UHoudiniGeoImporter* GeoImporter = NewObject(this); - - TArray OldOutputs; - OutOutputs.Empty(); - - // 2. Update the file paths - HOUDINI_LOG_DISPLAY(TEXT("SetFilePath %s"), *InFilename); - if (!GeoImporter->SetFilePath(InFilename)) - return 1; - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - HOUDINI_LOG_DISPLAY(TEXT("LoadBGEOFileInHAPI")); - if (!GeoImporter->LoadBGEOFileInHAPI(NodeId)) - return 1; - - // Look for a bake folder override in the BGEO file - if (PackageParams.PackageMode == EPackageMode::Bake) - { - HOUDINI_LOG_DISPLAY(TEXT("Looking for bake folder override attribute...")); - // Get the geo id for the node id - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), NodeId, &DisplayGeoInfo)) - { - TArray BakeFolderOverrideArray; - FString BakeFolderOverride; - const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderAttribute(DisplayGeoInfo.nodeId, HAPI_ATTROWNER_DETAIL,BakeFolderOverrideArray); - if (bFoundOverride && BakeFolderOverrideArray.Num() > 0) - BakeFolderOverride = BakeFolderOverrideArray[0]; - if (!BakeFolderOverride.IsEmpty()) - { - PackageParams.BakeFolder = BakeFolderOverride; - HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override (detail attrib): %s"), *PackageParams.BakeFolder); - } - else - { - HOUDINI_LOG_DISPLAY(TEXT("No bake folder override, using: %s"), *PackageParams.BakeFolder); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could not find display geo node id (when looking for bake folder override).")); - } - } - - auto CleanUpAndExit = [&OutOutputs, GeoImporter, NodeId](int32 InExitCode) - { - GeoImporter->GetOutputObjects().Empty(); - for (UHoudiniOutput* Output : OutOutputs) - { - Output->RemoveFromRoot(); - } - OutOutputs.Empty(); - - if (NodeId >= 0) - GeoImporter->DeleteCreatedNode(NodeId); - - return InExitCode; - }; - - // 4. Get the output from the file node - HOUDINI_LOG_DISPLAY(TEXT("BuildOutputsForNode %d"), NodeId); - if (!GeoImporter->BuildOutputsForNode(NodeId, OldOutputs, OutOutputs)) - return CleanUpAndExit(1); - - // Create uniquely named packages, commandlet runs in conjunction - // with a main editor instance, so we cannot modify existing files - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - // FString PackageName; - // UPackage* Outer = PackageParams.CreatePackageForObject(PackageName); - UObject* Outer = this; - - // 5. Create the static meshes in the outputs - HOUDINI_LOG_DISPLAY(TEXT("Create Static Meshes")); - if (!GeoImporter->CreateStaticMeshes(OutOutputs, Outer, PackageParams)) - return CleanUpAndExit(1); - - //// 6. Create the landscape in the outputs - //if (!GeoImporter->CreateLandscapes(NewOutputs, Outer, PackageParams)) - // return CleanUpAndExit(1); - - // 7. Create the instancers in the outputs - if (OutInstancedOutputPartData) - { - if (!GeoImporter->CreateInstancerOutputPartData(OutOutputs, *OutInstancedOutputPartData)) - return CleanUpAndExit(1); - } - else - { - if (!GeoImporter->CreateInstancers(OutOutputs, Outer, PackageParams)) - return CleanUpAndExit(1); - } - - if (OutGenericAttributes) - { - // Collect all generic properties from Houdini, we need to pass these - // through to PDG manager - HOUDINI_LOG_DISPLAY(TEXT("Get Generic Attributes for static meshes")); - for (UHoudiniOutput* CurOutput : OutOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (const auto& Entry : CurOutput->GetOutputObjects()) - { - const FHoudiniOutputObjectIdentifier OutputIdentifier = Entry.Key; - TArray PropertyAttributes; - FHoudiniEngineUtils::GetGenericPropertiesAttributes( - OutputIdentifier.GeoId, OutputIdentifier.PartId, - true, OutputIdentifier.PrimitiveIndex, INDEX_NONE, OutputIdentifier.PointIndex, - PropertyAttributes); - OutGenericAttributes->Add(OutputIdentifier, PropertyAttributes); - } - } - } - - // 8. Delete the created node in Houdini - HOUDINI_LOG_DISPLAY(TEXT("DeleteCreatedNode %d"), NodeId); - if (!GeoImporter->DeleteCreatedNode(NodeId)) - { - // Not good, but not fatal.. - //return false; - } - - TArray PackagesToSave; - TArray& OutputObjects = GeoImporter->GetOutputObjects(); - for (UObject* Object : OutputObjects) - { - if (!IsValid(Object)) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("Created object: %s"), *Object->GetFullName()); - - UAssetImportData* AssetImportData = nullptr; - if (Object->IsA()) - { - UStaticMesh* SM = Cast(Object); - AssetImportData = SM->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); - SM->AssetImportData = AssetImportData; - } - } - - if (AssetImportData) - AssetImportData->Update(InFilename); - - Object->MarkPackageDirty(); - Object->PostEditChange(); - - UPackage* Package = Object->GetOutermost(); - if (IsValid(Package)) - { - PackagesToSave.AddUnique(Package); - } - } - - if (PackagesToSave.Num() > 0) - { - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); - } - - PackagesToSave.Empty(); - OutputObjects.Empty(); - - return 0; -} - -void UHoudiniGeoImportCommandlet::HandleDirectoryChanged(const TArray& InFileChangeDatas) -{ - const FRegexPattern BGEOPattern(TEXT(R"((.*)\.(bgeo(\.[^\.]*)?)$)")); - - for (const FFileChangeData& FileChangeData : InFileChangeDatas) - { - HOUDINI_LOG_MESSAGE(TEXT("HandleDirectoryChanged %d %s"), FileChangeData.Action, *FileChangeData.Filename); - - FRegexMatcher BGEOMatcher(BGEOPattern, FileChangeData.Filename.ToLower()); - if (BGEOMatcher.FindNext() && BGEOMatcher.GetCaptureGroup(2).StartsWith(TEXT("bgeo"))) - { - HOUDINI_LOG_DISPLAY(TEXT("Updating entry for %s..."), *FileChangeData.Filename); - const uint32 MaxImportAttempts = 3; - switch(FileChangeData.Action) - { - case FFileChangeData::FCA_Added: - case FFileChangeData::FCA_Modified: - if (DiscoveredFiles.Contains(FileChangeData.Filename)) - { - FDiscoveredFileData &FileData = DiscoveredFiles[FileChangeData.Filename]; - if (!FileData.bImported && FileData.ImportAttempts < MaxImportAttempts) - FileData.bImportNextTick = true; - else if (FileData.ImportAttempts >= MaxImportAttempts) - HOUDINI_LOG_WARNING(TEXT("Not importing %s, max attempts exceeded %d"), *FileData.FileName, FileData.ImportAttempts); - } - else - { - DiscoveredFiles.Add(FileChangeData.Filename, FDiscoveredFileData(FileChangeData.Filename, true)); - } - break; - case FFileChangeData::FCA_Removed: - DiscoveredFiles.Remove(FileChangeData.Filename); - break; - default: - HOUDINI_LOG_WARNING(TEXT("Unknown file change event %d for %s"), FileChangeData.Action, *FileChangeData.Filename); - } - } - } -} - -int32 UHoudiniGeoImportCommandlet::Main(const FString& InParams) -{ - TArray Tokens; - TArray Switches; - TMap Params; - ParseCommandLine(*InParams, Tokens, Switches, Params); - - if (Switches.Contains(TEXT("help")) || Switches.Contains(TEXT("?"))) - { - PrintUsage(); - return 0; - } - - if (Params.Contains(TEXT("guid"))) - { - const FString GuidStr = Params.FindChecked(TEXT("guid")); - FGuid::Parse(GuidStr, Guid); - - HOUDINI_LOG_DISPLAY(TEXT("GUID received: %s"), *Guid.ToString()); - } - else - { - Guid = FGuid::NewGuid(); - } - - // Set bake mode - if (Switches.Contains(TEXT("bake"))) - bBakeOutputs = true; - else - bBakeOutputs = false; - - if (Params.Contains(TEXT("listen"))) - { - Mode = EHoudiniGeoImportCommandletMode::Listen; - - if (!Params.Contains(TEXT("managerpid"))) - { - HOUDINI_LOG_ERROR(TEXT("'managerpid' is required when in -listen mode.")); - return 1; - } - - if (bBakeOutputs) - { - HOUDINI_LOG_ERROR(TEXT("'listen' mode does not support baking outputs (-bake).")); - return 1; - } - - // Get the manager's messaging address from the -listen param - const FString ManagerAddressStr = Params.FindChecked(TEXT("listen")); - if (!FMessageAddress::Parse(ManagerAddressStr, ManagerAddress)) - { - HOUDINI_LOG_ERROR(TEXT("The manager messaging address passed to -listen=%s is invalid."), *ManagerAddressStr); - return 1; - } - - // Get the manager pid and proc handle - uint32 OwnerProcessId = FCString::Strtoi(*Params.FindChecked(TEXT("managerpid")), nullptr, 10); - HOUDINI_LOG_DISPLAY(TEXT("Owner process Id: %d"), OwnerProcessId); - OwnerProcHandle = FPlatformProcess::OpenProcess(OwnerProcessId); - - return MainLoop(); - } - else if (Params.Contains(TEXT("watch"))) - { - Mode = EHoudiniGeoImportCommandletMode::Watch; - - HOUDINI_LOG_DISPLAY(TEXT("directory watch mode")); - FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); - IDirectoryWatcher* DirectoryWatcher = DirectoryWatcherModule.Get(); - if (DirectoryWatcher) - { - DirectoryToWatch = Params.FindChecked(TEXT("watch")); - if (FPaths::IsRelative(DirectoryToWatch)) - DirectoryToWatch = FPaths::ConvertRelativePathToFull(DirectoryToWatch); - - HOUDINI_LOG_DISPLAY(TEXT("Watching %s"), *DirectoryToWatch); - - DirectoryWatcher->RegisterDirectoryChangedCallback_Handle( - DirectoryToWatch, - IDirectoryWatcher::FDirectoryChanged::CreateUObject(this, &UHoudiniGeoImportCommandlet::HandleDirectoryChanged), - DirectoryWatcherHandle); - - return MainLoop(); - } - else - { - return 10; - } - } - else if (Tokens.Num() > 0) - { - Mode = EHoudiniGeoImportCommandletMode::SpecifiedFiles; - - if (!StartHoudiniEngineSession()) - return 2; - - const FString Filename = FPaths::IsRelative(Tokens[0]) ? FPaths::ConvertRelativePathToFull(Tokens[0]) : Tokens[0]; - FHoudiniPackageParams PackageParams; - PopulatePackageParams(Filename, PackageParams); - - TArray Outputs; - const int32 Result = ImportBGEO(Tokens[0], PackageParams, Outputs); - - for (UHoudiniOutput* Output : Outputs) - { - Output->RemoveFromRoot(); - } - Outputs.Empty(); - - return Result; - } - - return 0; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoImportCommandlet.h" + +#include "DirectoryWatcherModule.h" +#include "Modules/ModuleManager.h" +#include "Misc/Guid.h" +#include "EditorFramework/AssetImportData.h" + +#include "Editor.h" +#include "FileHelpers.h" + +#include "MessageEndpointBuilder.h" + +#include "PackageTools.h" + +#include "IDirectoryWatcher.h" + +#include "Internationalization/Regex.h" + +#include "Interfaces/ISlateNullRendererModule.h" +#include "Rendering/SlateRenderer.h" +#include "Framework/Application/SlateApplication.h" + +#include "HoudiniPackageParams.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniOutput.h" +#include "HoudiniPDGImporterMessages.h" +#include "HoudiniMeshTranslator.h" +#include "HAL/ThreadManager.h" + + +UHoudiniGeoImportCommandlet::UHoudiniGeoImportCommandlet() +{ + HelpDescription = TEXT("Import BGEOs as UAssets. Includes an option to watch a directories and include new .bgeos created there."); + + HelpUsage = TEXT("HoudiniGeoImport Usage: HoudiniGeoImport {options} [filename.bgeo]"); + // "Options:\n" + // "\t-help or -?\n" + // "\t\tDisplays this help.\n\n" + // "\t-listen=manager_messaging_address\n\n" + // "\t\tListen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager.\n\n" + // "\t-watch=directory\n\n" + // "\t\tA directory to watch for new .bgeo files to import.\n\n" + // "\t-managerpid=owner_pid\n\n" + // "\t\tThe PID of the owner/manager process. If the manager process dies the commandlet also quits.\n\n" + // "\t-bake\n\n" + // "\t\tBake generated assets. Instancers are baked to blueprints. Not supported in -listen mode.\n\n" + // "\t[filename.bgeo]\n" + // "\t\tWhen not using -listen or -watch, the path to a .bgeo file must be specified for import.\n" + //); + + HelpParamNames = { + "help", + "listen", + "guid", + "watch", + "managerpid", + "bake" + }; + + HelpParamDescriptions = { + "Displays this help.", + "Listen for connections from the HoudiniEngine plug-in's PDG manager, and import bgeo files/assets requested by the PDG manager. Expects the owning process' PID.", + "Specify a GUID for the commandlet. Useful to identify the commandlet when the messaging system is used.", + "A directory to watch for new .bgeo files to import.", + "The PID of the owner/manager process. If the manager process dies the commandlet also quits.", + "Bake generated assets. Instancers are baked to blueprints. Not supported in -listen mode." + }; + + IsClient = false; + IsEditor = true; + IsServer = false; + LogToConsole = true; + ShowProgress = false; + ShowErrorCount = false; + + // LogToConsole = false; + + Mode = EHoudiniGeoImportCommandletMode::None; + bBakeOutputs = false; +} + +void UHoudiniGeoImportCommandlet::PrintUsage() const +{ + HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpDescription); + HOUDINI_LOG_DISPLAY(TEXT("%s"), *HelpUsage); + const int32 NumOptions = HelpParamNames.Num(); + for (int32 Idx = 0; Idx < NumOptions; ++Idx) + { + HOUDINI_LOG_DISPLAY(TEXT("-%s\t%s"), *HelpParamNames[Idx], *HelpParamDescriptions[Idx]); + } +} + +void UHoudiniGeoImportCommandlet::PopulatePackageParams(const FString &InBGEOFilename, FHoudiniPackageParams& OutPackageParams) +{ + UObject* InParent = this; + + if (bBakeOutputs) + { + OutPackageParams.PackageMode = EPackageMode::Bake; + } + else + { + OutPackageParams.PackageMode = EPackageMode::CookToTemp; + } + OutPackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + OutPackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + OutPackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + OutPackageParams.HoudiniAssetName = FPaths::GetBaseFilename(InBGEOFilename); + OutPackageParams.HoudiniAssetActorName = FString(); + OutPackageParams.ObjectName = FPaths::GetBaseFilename(InBGEOFilename); + + if (!OutPackageParams.OuterPackage) + { + OutPackageParams.OuterPackage = InParent; + } + + if (!OutPackageParams.ComponentGUID.IsValid()) + { + // TODO: will need to reuse the GUID when reimporting? + OutPackageParams.ComponentGUID = FGuid::NewGuid(); + } +} + +void UHoudiniGeoImportCommandlet::TickDiscoveredFiles() +{ + for (auto &FileDataEntry : DiscoveredFiles) + { + FDiscoveredFileData &FileData = FileDataEntry.Value; + if (FileData.bImportNextTick && !FileData.bImported) + { + FileData.bImportNextTick = false; + FileData.ImportAttempts++; + + FHoudiniPackageParams PackageParams; + PopulatePackageParams(FileData.FileName, PackageParams); + TArray Outputs; + int32 Error = ImportBGEO(FileData.FileName, PackageParams, Outputs); + if (Error == 0) + { + FileData.bImported = true; + HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Done"), *FileData.FileName); + } + else + { + FileData.bImported = false; + HOUDINI_LOG_DISPLAY(TEXT("Importing %s... Failed (%d)"), *FileData.FileName, Error); + } + } + } +} + +int32 UHoudiniGeoImportCommandlet::MainLoop() +{ + GIsRunning = true; + + IDirectoryWatcher* DirectoryWatcher = nullptr; + + if (Mode == EHoudiniGeoImportCommandletMode::Listen) + { + PDGEndpoint = FMessageEndpoint::Builder("PDG/BGEO Commandlet") + .Handling(this, &UHoudiniGeoImportCommandlet::HandleImportBGEOMessage) + .ReceivingOnThread(ENamedThreads::GameThread); + if (!PDGEndpoint.IsValid()) + { + GIsRunning = false; + return 3; + } + // Notify the manager that we are running + HOUDINI_LOG_DISPLAY(TEXT("Notifying the manager (%s) that we are running"), *ManagerAddress.ToString()); + // Try to send directly to the manager + // TODO: this initially direct message does not work, the address looks to be correct, perhaps there is some + // additional set up needed to connect / discover the endpoints? + PDGEndpoint->Send(new FHoudiniPDGImportBGEODiscoverMessage(Guid), ManagerAddress); + } + else if (Mode == EHoudiniGeoImportCommandletMode::Watch) + { + FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::Get().LoadModuleChecked(TEXT("DirectoryWatcher")); + DirectoryWatcher = DirectoryWatcherModule.Get(); + } + + // In UnrealEngine 4.25 and older we cannot tick the editor engine without slate being initialized. + if (!FSlateApplication::IsInitialized()) + { + FSlateApplication::InitHighDPI(false); + FSlateApplication::Create(); + } + + // If slate is initialized, make sure it has a renderer. If we have to create a renderer, create the null renderer. + if (FSlateApplication::IsInitialized() && !FSlateApplication::Get().GetRenderer()) + { + const TSharedPtr SlateRenderer = FModuleManager::Get().LoadModuleChecked("SlateNullRenderer").CreateSlateNullRenderer(); + const TSharedRef SlateRendererSharedRef = SlateRenderer.ToSharedRef(); + FSlateApplication::Get().InitializeRenderer(SlateRendererSharedRef); + } + + // in listen mode broadcast our presence every 60 seconds + // This is an attempt to test if it solves a rare issue where the endpoints appear to get + // "disconnected" and sending a message to a previously valid message address stops working, even though + // both processes are still running (happens especially when debugging with breakpoints) + const float BroadcastIntervalSeconds = 60.0f; + float LastbroadcastTimeSeconds = 0.0f; + + // main loop + while (GIsRunning && !IsEngineExitRequested()) + { + GEngine->UpdateTimeAndHandleMaxTickRate(); + GEngine->Tick(FApp::GetDeltaTime(), false); + + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().PumpMessages(); + FSlateApplication::Get().Tick(); + } + + // Required for FTimerManager to function - as it blocks ticks, if the frame counter doesn't change + GFrameCounter++; + + // update task graph + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + + FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()); + FThreadManager::Get().Tick(); + GEngine->TickDeferredCommands(); + + if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) + { + // DirectoryWatcher->Tick(FApp::GetDeltaTime()); + + // Process the discovered files + TickDiscoveredFiles(); + } + + if (OwnerProcHandle.IsValid() && !FPlatformProcess::IsProcRunning(OwnerProcHandle)) + { + // Our once valid owner has disappeared, so quit. + RequestEngineExit(TEXT("OwnerDisappeared")); + } + + if (Mode == EHoudiniGeoImportCommandletMode::Listen && PDGEndpoint.IsValid()) + { + const float TimeSeconds = FPlatformTime::Seconds(); + if (TimeSeconds - LastbroadcastTimeSeconds >= BroadcastIntervalSeconds) + { + LastbroadcastTimeSeconds = TimeSeconds; + // Broadcast a discover message to notify that we are still available + PDGEndpoint->Publish(new FHoudiniPDGImportBGEODiscoverMessage(Guid)); + + HOUDINI_LOG_MESSAGE(TEXT("Publishing FHoudiniPDGImportBGEODiscoverMessage(%s)"), *Guid.ToString()); + } + } + + FPlatformProcess::Sleep(0); + } + + PDGEndpoint.Reset(); + if (DirectoryWatcherHandle.IsValid() && DirectoryWatcher) + { + DirectoryWatcher->UnregisterDirectoryChangedCallback_Handle(DirectoryToWatch, DirectoryWatcherHandle); + } + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Shutdown(); + + GIsRunning = false; + + return 0; +} + +void UHoudiniGeoImportCommandlet::HandleImportBGEOMessage( + const FHoudiniPDGImportBGEOMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_DISPLAY(TEXT("Received BGEO import request from %s"), *InContext->GetSender().ToString()); + + FHoudiniPackageParams PackageParams; + InMessage.PopulatePackageParams(PackageParams); + + TArray Outputs; + TMap> OutputObjectAttributes; + TMap InstancedOutputPartData; + if (ImportBGEO(InMessage.FilePath, PackageParams, Outputs, &OutputObjectAttributes, &InstancedOutputPartData) == 0) + { + FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); + (*Reply) = InMessage; + // Reply->PopulateFromPackageParams(PackageParams); + Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Success; + + const int32 NumOutputs = Outputs.Num(); + Reply->Outputs.Init(FHoudiniPDGImportNodeOutput(), NumOutputs); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + FHoudiniPDGImportNodeOutput &MessageOutput = Reply->Outputs[Index]; + UHoudiniOutput* Output = Outputs[Index]; + for (const FHoudiniGeoPartObject& HGPO : Output->GetHoudiniGeoPartObjects()) + { + HOUDINI_LOG_WARNING(TEXT("HGPO %d %d %d"), HGPO.ObjectId, HGPO.GeoId, HGPO.PartId); + MessageOutput.HoudiniGeoPartObjects.Add(HGPO); + + // Get instancer data if this is an instancer output + if (Output->GetType() == EHoudiniOutputType::Instancer) + { + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = HGPO.ObjectId; + OutputIdentifier.GeoId = HGPO.GeoId; + OutputIdentifier.PartId = HGPO.PartId; + OutputIdentifier.PartName = HGPO.PartName; + + FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = InstancedOutputPartData.Find(OutputIdentifier); + if (InstancedOutputPartDataPtr) + { + InstancedOutputPartDataPtr->BuildFlatInstancedTransformsAndObjectPaths(); + MessageOutput.InstancedOutputPartData.Add(*InstancedOutputPartDataPtr); + } + else + { + MessageOutput.InstancedOutputPartData.Add(FHoudiniInstancedOutputPartData()); + } + } + } + for (const auto& Entry : Output->GetOutputObjects()) + { + HOUDINI_LOG_WARNING(TEXT("Identifier %d %d %d"), Entry.Key.ObjectId, Entry.Key.GeoId, Entry.Key.PartId); + + MessageOutput.OutputObjects.AddDefaulted(); + FHoudiniPDGImportNodeOutputObject& MessageOutputObject = MessageOutput.OutputObjects.Last(); + + FString PackagePath = IsValid(Entry.Value.OutputObject) ? Entry.Value.OutputObject->GetPathName() : ""; + MessageOutputObject.Identifier = Entry.Key; + MessageOutputObject.PackagePath = PackagePath; + const TArray* PropertyAttributes = OutputObjectAttributes.Find(Entry.Key); + if (PropertyAttributes) + MessageOutputObject.GenericAttributes = *PropertyAttributes; + MessageOutputObject.CachedAttributes = Entry.Value.CachedAttributes; + } + } + + PDGEndpoint->Send(Reply, InContext->GetSender()); + } + else + { + HOUDINI_LOG_WARNING(TEXT("BGEO import failed.")); + FHoudiniPDGImportBGEOResultMessage* Reply = new FHoudiniPDGImportBGEOResultMessage(); + Reply->ImportResult = EHoudiniPDGImportBGEOResult::HPIBR_Failed; + PDGEndpoint->Send(Reply, InContext->GetSender()); + } + + // Cleanup the outputs (remove from root) + TArray PackagesToUnload; + for (UHoudiniOutput *CurOutput : Outputs) + { + if (!IsValid(CurOutput)) + continue; + + for (const auto& Entry : CurOutput->GetOutputObjects()) + { + if (IsValid(Entry.Value.OutputObject)) + { + UPackage *Outermost = Entry.Value.OutputObject->GetOutermost(); + if (IsValid(Outermost)) + { + PackagesToUnload.Add(Outermost); + } + + Entry.Value.OutputObject->RemoveFromRoot(); + } + } + + CurOutput->RemoveFromRoot(); + } + Outputs.Empty(); + OutputObjectAttributes.Empty(); + + if (PackagesToUnload.Num() > 0) + { + HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ..."), PackagesToUnload.Num()); + FText ErrorMessage; + if (!UPackageTools::UnloadPackages(PackagesToUnload, ErrorMessage)) + { + HOUDINI_LOG_WARNING(TEXT("Unload packages failed: %s"), *ErrorMessage.ToString()); + } + else + { + HOUDINI_LOG_DISPLAY(TEXT("Unloading %d packages ... Success"), PackagesToUnload.Num()); + } + PackagesToUnload.Empty(); + } + + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); +} + +bool UHoudiniGeoImportCommandlet::StartHoudiniEngineSession() +{ + // Start Houdini Engine session + HOUDINI_LOG_DISPLAY(TEXT("Starting Houdini Engine session...")); + FHoudiniEngine& HoudiniEngine = FHoudiniEngine::Get(); + if (!HoudiniEngine.CreateSession( + EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe, + "hapi_bgeo_cmdlet")) + { + HOUDINI_LOG_ERROR(TEXT("Failed to start Houdini Engine session.")); + return false; + } + + return true; +} + +int32 UHoudiniGeoImportCommandlet::ImportBGEO( + const FString &InFilename, + const FHoudiniPackageParams &InPackageParams, + TArray& OutOutputs, + TMap>* OutGenericAttributes, + TMap* OutInstancedOutputPartData) +{ + if (!IsHoudiniEngineSessionRunning() && !StartHoudiniEngineSession()) + { + return 2; + } + + FHoudiniPackageParams PackageParams = InPackageParams; + UHoudiniGeoImporter* GeoImporter = NewObject(this); + + TArray OldOutputs; + OutOutputs.Empty(); + + // 2. Update the file paths + HOUDINI_LOG_DISPLAY(TEXT("SetFilePath %s"), *InFilename); + if (!GeoImporter->SetFilePath(InFilename)) + return 1; + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + HOUDINI_LOG_DISPLAY(TEXT("LoadBGEOFileInHAPI")); + if (!GeoImporter->LoadBGEOFileInHAPI(NodeId)) + return 1; + + // Look for a bake folder override in the BGEO file + if (PackageParams.PackageMode == EPackageMode::Bake) + { + HOUDINI_LOG_DISPLAY(TEXT("Looking for bake folder override attribute...")); + // Get the geo id for the node id + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), NodeId, &DisplayGeoInfo)) + { + TArray BakeFolderOverrideArray; + FString BakeFolderOverride; + const bool bFoundOverride = FHoudiniEngineUtils::GetBakeFolderAttribute( + DisplayGeoInfo.nodeId, HAPI_ATTROWNER_DETAIL, BakeFolderOverrideArray, 0, 1); + + if (bFoundOverride && BakeFolderOverrideArray.Num() > 0) + BakeFolderOverride = BakeFolderOverrideArray[0]; + + if (!BakeFolderOverride.IsEmpty()) + { + PackageParams.BakeFolder = BakeFolderOverride; + HOUDINI_LOG_DISPLAY(TEXT("Found bake folder override (detail attrib): %s"), *PackageParams.BakeFolder); + } + else + { + HOUDINI_LOG_DISPLAY(TEXT("No bake folder override, using: %s"), *PackageParams.BakeFolder); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could not find display geo node id (when looking for bake folder override).")); + } + } + + auto CleanUpAndExit = [&OutOutputs, GeoImporter, NodeId](int32 InExitCode) + { + GeoImporter->GetOutputObjects().Empty(); + for (UHoudiniOutput* Output : OutOutputs) + { + Output->RemoveFromRoot(); + } + OutOutputs.Empty(); + + if (NodeId >= 0) + GeoImporter->DeleteCreatedNode(NodeId); + + return InExitCode; + }; + + // 4. Get the output from the file node + HOUDINI_LOG_DISPLAY(TEXT("BuildOutputsForNode %d"), NodeId); + if (!GeoImporter->BuildOutputsForNode(NodeId, OldOutputs, OutOutputs)) + return CleanUpAndExit(1); + + // Create uniquely named packages, commandlet runs in conjunction + // with a main editor instance, so we cannot modify existing files + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + // FString PackageName; + // UPackage* Outer = PackageParams.CreatePackageForObject(PackageName); + UObject* Outer = this; + + // 5. Create the static meshes in the outputs + HOUDINI_LOG_DISPLAY(TEXT("Create Static Meshes")); + if (!GeoImporter->CreateStaticMeshes(OutOutputs, Outer, PackageParams)) + return CleanUpAndExit(1); + + //// 6. Create the landscape in the outputs + //if (!GeoImporter->CreateLandscapes(NewOutputs, Outer, PackageParams)) + // return CleanUpAndExit(1); + + // 7. Create the instancers in the outputs + if (OutInstancedOutputPartData) + { + if (!GeoImporter->CreateInstancerOutputPartData(OutOutputs, *OutInstancedOutputPartData)) + return CleanUpAndExit(1); + } + else + { + if (!GeoImporter->CreateInstancers(OutOutputs, Outer, PackageParams)) + return CleanUpAndExit(1); + } + + if (OutGenericAttributes) + { + // Collect all generic properties from Houdini, we need to pass these + // through to PDG manager + HOUDINI_LOG_DISPLAY(TEXT("Get Generic Attributes for static meshes")); + for (UHoudiniOutput* CurOutput : OutOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (const auto& Entry : CurOutput->GetOutputObjects()) + { + const FHoudiniOutputObjectIdentifier OutputIdentifier = Entry.Key; + TArray PropertyAttributes; + FHoudiniEngineUtils::GetGenericPropertiesAttributes( + OutputIdentifier.GeoId, OutputIdentifier.PartId, + true, OutputIdentifier.PrimitiveIndex, INDEX_NONE, OutputIdentifier.PointIndex, + PropertyAttributes); + OutGenericAttributes->Add(OutputIdentifier, PropertyAttributes); + } + } + } + + // 8. Delete the created node in Houdini + HOUDINI_LOG_DISPLAY(TEXT("DeleteCreatedNode %d"), NodeId); + if (!GeoImporter->DeleteCreatedNode(NodeId)) + { + // Not good, but not fatal.. + //return false; + } + + TArray PackagesToSave; + TArray& OutputObjects = GeoImporter->GetOutputObjects(); + for (UObject* Object : OutputObjects) + { + if (!IsValid(Object)) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("Created object: %s"), *Object->GetFullName()); + + UAssetImportData* AssetImportData = nullptr; + if (Object->IsA()) + { + UStaticMesh* SM = Cast(Object); + AssetImportData = SM->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); + SM->AssetImportData = AssetImportData; + } + } + + if (AssetImportData) + AssetImportData->Update(InFilename); + + Object->MarkPackageDirty(); + Object->PostEditChange(); + + UPackage* Package = Object->GetOutermost(); + if (IsValid(Package)) + { + PackagesToSave.AddUnique(Package); + } + } + + if (PackagesToSave.Num() > 0) + { + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); + } + + PackagesToSave.Empty(); + OutputObjects.Empty(); + + return 0; +} + +void UHoudiniGeoImportCommandlet::HandleDirectoryChanged(const TArray& InFileChangeDatas) +{ + const FRegexPattern BGEOPattern(TEXT(R"((.*)\.(bgeo(\.[^\.]*)?)$)")); + + for (const FFileChangeData& FileChangeData : InFileChangeDatas) + { + HOUDINI_LOG_MESSAGE(TEXT("HandleDirectoryChanged %d %s"), FileChangeData.Action, *FileChangeData.Filename); + + FRegexMatcher BGEOMatcher(BGEOPattern, FileChangeData.Filename.ToLower()); + if (BGEOMatcher.FindNext() && BGEOMatcher.GetCaptureGroup(2).StartsWith(TEXT("bgeo"))) + { + HOUDINI_LOG_DISPLAY(TEXT("Updating entry for %s..."), *FileChangeData.Filename); + const uint32 MaxImportAttempts = 3; + switch(FileChangeData.Action) + { + case FFileChangeData::FCA_Added: + case FFileChangeData::FCA_Modified: + if (DiscoveredFiles.Contains(FileChangeData.Filename)) + { + FDiscoveredFileData &FileData = DiscoveredFiles[FileChangeData.Filename]; + if (!FileData.bImported && FileData.ImportAttempts < MaxImportAttempts) + FileData.bImportNextTick = true; + else if (FileData.ImportAttempts >= MaxImportAttempts) + HOUDINI_LOG_WARNING(TEXT("Not importing %s, max attempts exceeded %d"), *FileData.FileName, FileData.ImportAttempts); + } + else + { + DiscoveredFiles.Add(FileChangeData.Filename, FDiscoveredFileData(FileChangeData.Filename, true)); + } + break; + case FFileChangeData::FCA_Removed: + DiscoveredFiles.Remove(FileChangeData.Filename); + break; + default: + HOUDINI_LOG_WARNING(TEXT("Unknown file change event %d for %s"), FileChangeData.Action, *FileChangeData.Filename); + } + } + } +} + +int32 UHoudiniGeoImportCommandlet::Main(const FString& InParams) +{ + TArray Tokens; + TArray Switches; + TMap Params; + ParseCommandLine(*InParams, Tokens, Switches, Params); + + if (Switches.Contains(TEXT("help")) || Switches.Contains(TEXT("?"))) + { + PrintUsage(); + return 0; + } + + if (Params.Contains(TEXT("guid"))) + { + const FString GuidStr = Params.FindChecked(TEXT("guid")); + FGuid::Parse(GuidStr, Guid); + + HOUDINI_LOG_DISPLAY(TEXT("GUID received: %s"), *Guid.ToString()); + } + else + { + Guid = FGuid::NewGuid(); + } + + // Set bake mode + if (Switches.Contains(TEXT("bake"))) + bBakeOutputs = true; + else + bBakeOutputs = false; + + if (Params.Contains(TEXT("listen"))) + { + Mode = EHoudiniGeoImportCommandletMode::Listen; + + if (!Params.Contains(TEXT("managerpid"))) + { + HOUDINI_LOG_ERROR(TEXT("'managerpid' is required when in -listen mode.")); + return 1; + } + + if (bBakeOutputs) + { + HOUDINI_LOG_ERROR(TEXT("'listen' mode does not support baking outputs (-bake).")); + return 1; + } + + // Get the manager's messaging address from the -listen param + const FString ManagerAddressStr = Params.FindChecked(TEXT("listen")); + if (!FMessageAddress::Parse(ManagerAddressStr, ManagerAddress)) + { + HOUDINI_LOG_ERROR(TEXT("The manager messaging address passed to -listen=%s is invalid."), *ManagerAddressStr); + return 1; + } + + // Get the manager pid and proc handle + uint32 OwnerProcessId = FCString::Strtoi(*Params.FindChecked(TEXT("managerpid")), nullptr, 10); + HOUDINI_LOG_DISPLAY(TEXT("Owner process Id: %d"), OwnerProcessId); + OwnerProcHandle = FPlatformProcess::OpenProcess(OwnerProcessId); + + return MainLoop(); + } + else if (Params.Contains(TEXT("watch"))) + { + Mode = EHoudiniGeoImportCommandletMode::Watch; + + HOUDINI_LOG_DISPLAY(TEXT("directory watch mode")); + FDirectoryWatcherModule& DirectoryWatcherModule = FModuleManager::LoadModuleChecked(TEXT("DirectoryWatcher")); + IDirectoryWatcher* DirectoryWatcher = DirectoryWatcherModule.Get(); + if (DirectoryWatcher) + { + DirectoryToWatch = Params.FindChecked(TEXT("watch")); + if (FPaths::IsRelative(DirectoryToWatch)) + DirectoryToWatch = FPaths::ConvertRelativePathToFull(DirectoryToWatch); + + HOUDINI_LOG_DISPLAY(TEXT("Watching %s"), *DirectoryToWatch); + + DirectoryWatcher->RegisterDirectoryChangedCallback_Handle( + DirectoryToWatch, + IDirectoryWatcher::FDirectoryChanged::CreateUObject(this, &UHoudiniGeoImportCommandlet::HandleDirectoryChanged), + DirectoryWatcherHandle); + + return MainLoop(); + } + else + { + return 10; + } + } + else if (Tokens.Num() > 0) + { + Mode = EHoudiniGeoImportCommandletMode::SpecifiedFiles; + + if (!StartHoudiniEngineSession()) + return 2; + + const FString Filename = FPaths::IsRelative(Tokens[0]) ? FPaths::ConvertRelativePathToFull(Tokens[0]) : Tokens[0]; + FHoudiniPackageParams PackageParams; + PopulatePackageParams(Filename, PackageParams); + + TArray Outputs; + const int32 Result = ImportBGEO(Tokens[0], PackageParams, Outputs); + + for (UHoudiniOutput* Output : Outputs) + { + Output->RemoveFromRoot(); + } + Outputs.Empty(); + + return Result; + } + + return 0; +} diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h index 23734611c..b6bc33ced 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h +++ b/Source/HoudiniEngine/Private/HoudiniGeoImportCommandlet.h @@ -1,153 +1,153 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "Commandlets/Commandlet.h" -#include "MessageEndpoint.h" - -#include "HoudiniEngine.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPDGImporterMessages.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniGeoImportCommandlet.generated.h" - -class FSocket; - -class UHoudiniGeoImporter; -class UHoudiniOutput; - -struct FHoudiniPackageParams; - -enum class EHoudiniGeoImportCommandletMode : uint8 -{ - // Unspecified - None, - // Import of specified file - SpecifiedFiles, - // Directory watch mode - Watch, - // Listen mode (via PDGManager) - Listen -}; - -struct FDiscoveredFileData -{ -public: - FDiscoveredFileData() : FileName(), bImportNextTick(false), ImportAttempts(0), bImported(false) {} - - FDiscoveredFileData(const FString& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} - - FDiscoveredFileData(FString&& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} - - // Full/absolute file path - FString FileName; - - // Try to import this file on the next tick - bool bImportNextTick; - - // Number of attempts at importing this file - uint32 ImportAttempts; - - // The file has been imported successfully - bool bImported; -}; - -UCLASS() -class HOUDINIENGINE_API UHoudiniGeoImportCommandlet : public UCommandlet -{ - GENERATED_BODY() - -public: - - UHoudiniGeoImportCommandlet(); - - void PrintUsage() const; - - /** - * Entry point for your commandlet - * - * @param Params the string containing the parameters for the commandlet - */ - virtual int32 Main(const FString& Params) override; - - void HandleImportBGEOMessage( - const struct FHoudiniPDGImportBGEOMessage& InMessage, - const TSharedRef& InContext); - - void HandleDirectoryChanged(const TArray& InFileChangeDatas); - -protected: - - void PopulatePackageParams(const FString& InBGEOFilename, FHoudiniPackageParams& OutPackageParams); - - bool StartHoudiniEngineSession(); - - bool IsHoudiniEngineSessionRunning() { return FHoudiniEngine::Get().GetSession() != nullptr; }; - - int32 MainLoop(); - - int32 ImportBGEO( - const FString& InFilename, - const FHoudiniPackageParams& InPackageParams, - TArray& OutOutputs, - TMap>* OutGenericAttributes=nullptr, - TMap* OutInstancedOutputPartData=nullptr); - - void TickDiscoveredFiles(); - -private: - - // Messaging end point for receiving messages from PDG manager - TSharedPtr PDGEndpoint; - - // The messaging address of the manager - FMessageAddress ManagerAddress; - - // Unique ID of the commandlet. - FGuid Guid; - - // The proc handle of our owner (if in listen mode, quit when the owner stops running). - FProcHandle OwnerProcHandle; - - // TODO: Map so that we can watch multiple directories? - // Directory to watch - FString DirectoryToWatch; - // Handle if we are watching a directory for changes. - FDelegateHandle DirectoryWatcherHandle; - - // Keep track of files discovered by the watcher, and their state - TMap DiscoveredFiles; - - // Mode in which commandlet is running - EHoudiniGeoImportCommandletMode Mode; - - // Bake outputs via FHoudiniEngineBakeUtils - bool bBakeOutputs; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Commandlets/Commandlet.h" +#include "MessageEndpoint.h" + +#include "HoudiniEngine.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPDGImporterMessages.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniGeoImportCommandlet.generated.h" + +class FSocket; + +class UHoudiniGeoImporter; +class UHoudiniOutput; + +struct FHoudiniPackageParams; + +enum class EHoudiniGeoImportCommandletMode : uint8 +{ + // Unspecified + None, + // Import of specified file + SpecifiedFiles, + // Directory watch mode + Watch, + // Listen mode (via PDGManager) + Listen +}; + +struct FDiscoveredFileData +{ +public: + FDiscoveredFileData() : FileName(), bImportNextTick(false), ImportAttempts(0), bImported(false) {} + + FDiscoveredFileData(const FString& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} + + FDiscoveredFileData(FString&& InFileName, bool bInImportNextTick=false) : FileName(InFileName), bImportNextTick(bInImportNextTick), ImportAttempts(0), bImported(false) {} + + // Full/absolute file path + FString FileName; + + // Try to import this file on the next tick + bool bImportNextTick; + + // Number of attempts at importing this file + uint32 ImportAttempts; + + // The file has been imported successfully + bool bImported; +}; + +UCLASS() +class HOUDINIENGINE_API UHoudiniGeoImportCommandlet : public UCommandlet +{ + GENERATED_BODY() + +public: + + UHoudiniGeoImportCommandlet(); + + void PrintUsage() const; + + /** + * Entry point for your commandlet + * + * @param Params the string containing the parameters for the commandlet + */ + virtual int32 Main(const FString& Params) override; + + void HandleImportBGEOMessage( + const struct FHoudiniPDGImportBGEOMessage& InMessage, + const TSharedRef& InContext); + + void HandleDirectoryChanged(const TArray& InFileChangeDatas); + +protected: + + void PopulatePackageParams(const FString& InBGEOFilename, FHoudiniPackageParams& OutPackageParams); + + bool StartHoudiniEngineSession(); + + bool IsHoudiniEngineSessionRunning() { return FHoudiniEngine::Get().GetSession() != nullptr; }; + + int32 MainLoop(); + + int32 ImportBGEO( + const FString& InFilename, + const FHoudiniPackageParams& InPackageParams, + TArray& OutOutputs, + TMap>* OutGenericAttributes=nullptr, + TMap* OutInstancedOutputPartData=nullptr); + + void TickDiscoveredFiles(); + +private: + + // Messaging end point for receiving messages from PDG manager + TSharedPtr PDGEndpoint; + + // The messaging address of the manager + FMessageAddress ManagerAddress; + + // Unique ID of the commandlet. + FGuid Guid; + + // The proc handle of our owner (if in listen mode, quit when the owner stops running). + FProcHandle OwnerProcHandle; + + // TODO: Map so that we can watch multiple directories? + // Directory to watch + FString DirectoryToWatch; + // Handle if we are watching a directory for changes. + FDelegateHandle DirectoryWatcherHandle; + + // Keep track of files discovered by the watcher, and their state + TMap DiscoveredFiles; + + // Mode in which commandlet is running + EHoudiniGeoImportCommandletMode Mode; + + // Bake outputs via FHoudiniEngineBakeUtils + bool bBakeOutputs; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp index d588b3e6a..d57c08e42 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp @@ -1,905 +1,918 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoImporter.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniPackageParams.h" -#include "HoudiniOutput.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniSplineComponent.h" - -#include "CoreMinimal.h" -#include "Misc/Paths.h" -#include "Misc/PackageName.h" -#include "UObject/Package.h" -#include "PackageTools.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "Editor.h" - -#include "Materials/MaterialInterface.h" -#include "Materials/Material.h" - - -UHoudiniGeoImporter::UHoudiniGeoImporter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , SourceFilePath() - , AbsoluteFilePath() - , AbsoluteFileDirectory() - , FileName() - , FileExtension() - , BakeRootFolder(TEXT("/Game/HoudiniEngine/Bake/")) -{ - /* - SourceFilePath = FString(); - - AbsoluteFilePath = FString(); - AbsoluteFileDirectory = FString(); - FileName = FString(); - FileExtension = FString(); - - OutputFilename = FString(); - BakeRootFolder = TEXT("/Game/HoudiniEngine/Bake/"); - */ -} - -bool -UHoudiniGeoImporter::SetFilePath(const FString& InFilePath) -{ - SourceFilePath = InFilePath; - if (!FPaths::FileExists(SourceFilePath)) - { - // Cant find BGEO file - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InFilePath); - return false; - } - - // Make sure we're using absolute path! - AbsoluteFilePath = FPaths::ConvertRelativePathToFull(SourceFilePath); - - // Split the file path - FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); - - // Handle .bgeo.sc correctly - if (FileExtension.Equals(TEXT("sc"))) - { - // append the bgeo to .sc - FileExtension = FPaths::GetExtension(FileName) +TEXT(".") + FileExtension; - // update the filename - FileName = FPaths::GetBaseFilename(FileName); - } - - if (FileExtension.IsEmpty()) - FileExtension = TEXT("bgeo"); - - if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) - { - // Not a bgeo file! - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *SourceFilePath); - return false; - } - - //BGEOFilePath = BGEOPath + TEXT("/") + BGEOFileName + TEXT(".") + BGEOExtension; - - // Only use "/" for the output file path - BakeRootFolder.ReplaceInline(TEXT("\\"), TEXT("/")); - // Make sure the output folder ends with a "/" - if (!BakeRootFolder.EndsWith("/")) - BakeRootFolder += TEXT("/"); - - // If we have't specified an outpout file name yet, use the input file name - if (OutputFilename.IsEmpty()) - OutputFilename = FileName; - - return true; -} - -bool -UHoudiniGeoImporter::AutoStartHoudiniEngineSessionIfNeeded() -{ - if (FHoudiniEngine::Get().GetSession()) - return true; - - // Default first session already attempted to be created ? stop here? - /* - if (FHoudiniEngine::Get().GetFirstSessionCreated()) - return false; - */ - - // Indicates that we've tried to start a session once no matter if it failed or succeed - FHoudiniEngine::Get().SetFirstSessionCreated(true); - if (!FHoudiniEngine::Get().RestartSession()) - { - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Couldn't start the default HoudiniEngine session!")); - return false; - } - - return true; -} - -bool -UHoudiniGeoImporter::BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs) -{ - FString Notification = TEXT("BGEO Importer: Getting output geos..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - const bool bInAddOutputsToRootSet = true; - return BuildAllOutputsForNode(InNodeId, this, InOldOutputs, OutNewOutputs, bInAddOutputsToRootSet); -} - -bool -UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - TMap AllOutputMaterials; - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - FString Notification = TEXT("BGEO Importer: Creating Static Meshes..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - //FHoudiniMeshTranslator::CreateAllMeshesFromHoudiniOutput(CurOutput, OuterPackage, OuterComponent, OuterAsset); - - TMap NewOutputObjects; - TMap OldOutputObjects = CurOutput->GetOutputObjects(); - TMap& AssignementMaterials = CurOutput->GetAssignementMaterials(); - TMap& ReplacementMaterials = CurOutput->GetReplacementMaterials(); - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - // Not a mesh, skip - if (CurHGPO.Type != EHoudiniPartType::Mesh) - continue; - - // Check for a unreal_output_name if we are in bake mode - FHoudiniPackageParams PackageParams(InPackageParams); - if (PackageParams.PackageMode == EPackageMode::Bake) - { - TArray OutputNames; - if (FHoudiniEngineUtils::GetOutputNameAttribute(CurHGPO.GeoId, CurHGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - PackageParams.ObjectName = OutputNames[0]; - } - } - // Could have prim attribute unreal_bake_folder override - TArray BakeFolderNames; - if (FHoudiniEngineUtils::GetBakeFolderAttribute(CurHGPO.GeoId, BakeFolderNames, CurHGPO.PartId)) - { - if (BakeFolderNames.Num() > 0 && !BakeFolderNames[0].IsEmpty()) - { - PackageParams.BakeFolder = BakeFolderNames[0]; - } - } - } - - FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); - FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); - FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( - CurHGPO, - PackageParams, - OldOutputObjects, - NewOutputObjects, - AssignementMaterials, - ReplacementMaterials, - AllOutputMaterials, - true, - EHoudiniStaticMeshMethod::RawMesh, - SMGP, - MBS); - - for (auto& CurMat : AssignementMaterials) - { - // Adds the newly generated materials to the output materials array - // This is to avoid recreating those same materials again - if (!AllOutputMaterials.Contains(CurMat.Key)) - AllOutputMaterials.Add(CurMat); - } - } - - // Add all output objects and materials - for (auto CurOutputPair : NewOutputObjects) - { - UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - - // Do the same for materials - for (auto CurAssignmentMatPair : AssignementMaterials) - { - UObject* CurObj = CurAssignmentMatPair.Value; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - - // Also assign to the output objects map as we may need the meshes to create instancers later - CurOutput->SetOutputObjects(NewOutputObjects); - } - - return true; -} - - -bool -UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - TArray CurveOutputs; - CurveOutputs.Reserve(InOutputs.Num()); - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Curve) - continue; - - CurveOutputs.Add(CurOutput); - break; - } - - FString Notification = TEXT("BGEO Importer: Creating Curves..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - // Look for the first unreal_output_name attribute on the curve outputs and use that - // for ObjectName - FHoudiniPackageParams PackageParams(InPackageParams); - for (auto& CurOutput : CurveOutputs) - { - bool bFoundOutputName = false; - bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Curve) - continue; - - if (!bFoundOutputName) - { - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - } - } - } - - if (!bFoundBakeFolder) - { - TArray Strings; - if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.BakeFolder = Strings[0]; - bFoundBakeFolder = true; - } - } - } - - if (bFoundOutputName && bFoundBakeFolder) - break; - } - - if (bFoundOutputName && bFoundBakeFolder) - break; - } - - // Create a Package for the BP - PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - FString PackageName; - UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); - check(BPPackage); - - // Create and init a new Blueprint Actor - UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); - if (!Blueprint) - return false; - - // Create a fake outer component that we'll use as a temporary outer for our curves - UWorld* TempWorld = UWorld::CreateWorld(EWorldType::Inactive, false, TEXT("BGEOImporterTemp"), GetTransientPackage(), false); - const FActorSpawnParameters ActorSpawnParameters; - AActor* OuterActor = TempWorld->SpawnActor(ActorSpawnParameters); - USceneComponent* OuterComponent = - NewObject(OuterActor, USceneComponent::GetDefaultSceneRootVariableName()); - - for (auto& CurOutput : CurveOutputs) - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); - - // Prepare an ActorComponent array for AddComponentsToBlueprint() - TArray OutputComp; - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputComp.Add(CurObj); - } - - // Transfer all the instancer components to the BP - if (OutputComp.Num() > 0) - { - FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; - Params.OptionalNewRootNode = nullptr; - Params.bKeepMobility = false; - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); - } - } - - // Compile the blueprint - FKismetEditorUtilities::CompileBlueprint(Blueprint); - - // Add it to our output objects - OutputObjects.Add(Blueprint); - - return true; -} - - -bool -UHoudiniGeoImporter::CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - HOUDINI_LOG_WARNING(TEXT("Importing a landscape directly from BGEOs is not currently supported.")); - return false; - } - - return true; - - /* - // Before processing any of the output, - // we need to get the min/max value for all Height volumes in this output (if any) - float HoudiniHeightfieldOutputsGlobalMin = 0.f; - float HoudiniHeightfieldOutputsGlobalMax = 0.f; - FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax(InOutputs, HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax); - - UWorld* PersistentWorld = InParent->GetWorld(); - if(!PersistentWorld) - PersistentWorld = GEditor ? GEditor->GetEditorWorldContext(false).World() : nullptr; - - if (!PersistentWorld) - return false; - - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - FString Notification = TEXT("BGEO Importer: Creating Landscapes..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - TArray EmptyInputLandscapes; - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(CurOutput); - - bool bCreatedNewMaps = false; - ERuntimePackageMode RuntimePackageMode = ERuntimePackageMode::CookToTemp; - switch(InPackageParams.PackageMode) - { - - case EPackageMode::Bake: - RuntimePackageMode = ERuntimePackageMode::Bake; - break; - case EPackageMode::CookToLevel: - case EPackageMode::CookToTemp: - default: - RuntimePackageMode = ERuntimePackageMode::CookToTemp; - break; - } - TArray> CreatedUntrackedOutputs; - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - CreatedUntrackedOutputs, - EmptyInputLandscapes, - EmptyInputLandscapes, - HAC, - TEXT("{object_name}_"), - PersistentWorld, - HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax, - InPackageParams, bCreatedNewMaps, - RuntimePackageMode); - - // Add all output objects - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputObjects.Add(CurObj); - } - } - - return true; - */ -} - - -bool -UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) -{ - bool HasInstancer = false; - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - HasInstancer = true; - break; - } - - if (!HasInstancer) - return true; - - FString Notification = TEXT("BGEO Importer: Creating Instancers..."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - // Look for the first unreal_output_name attribute on the instancer outputs and use that - // for ObjectName - FHoudiniPackageParams PackageParams(InPackageParams); - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - bool bFoundOutputName = false; - bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Instancer) - continue; - - if (!bFoundOutputName) - { - TArray Strings; - if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.ObjectName = Strings[0]; - bFoundOutputName = true; - break; - } - } - } - - if (!bFoundBakeFolder) - { - TArray Strings; - if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId)) - { - if (Strings.Num() > 0 && !Strings[0].IsEmpty()) - { - PackageParams.BakeFolder = Strings[0]; - bFoundBakeFolder = true; - break; - } - } - } - - if (bFoundOutputName && bFoundBakeFolder) - break; - } - - if (bFoundOutputName && bFoundBakeFolder) - break; - } - - // Create a Package for the BP - PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - - FString PackageName; - UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); - check(BPPackage); - - // Create and init a new Blueprint Actor - UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); - if (!Blueprint) - return false; - - // Create a fake outer component that we'll use as a temporary outer for our instancers - USceneComponent* OuterComponent = NewObject(); - - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - // Create all the instancers and attach them to a fake outer component - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - CurOutput, InOutputs, OuterComponent); - - // Prepare an ActorComponent array for AddComponentsToBlueprint() - TArray OutputComp; - for (auto CurOutputPair : CurOutput->GetOutputObjects()) - { - UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) - continue; - - OutputComp.Add(CurObj); - } - - // Transfer all the instancer components to the BP - if (OutputComp.Num() > 0) - { - FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; - Params.OptionalNewRootNode = nullptr; - Params.bKeepMobility = false; - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); - } - } - - // Compile the blueprint - FKismetEditorUtilities::CompileBlueprint(Blueprint); - - // Add it to our output objects - OutputObjects.Add(Blueprint); - - return true; -} - -bool -UHoudiniGeoImporter::CreateInstancerOutputPartData( - TArray& InOutputs, - TMap& OutInstancedOutputPartData) -{ - for (auto& CurOutput : InOutputs) - { - if (CurOutput->GetType() != EHoudiniOutputType::Instancer) - continue; - - for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = HGPO.ObjectId; - OutputIdentifier.GeoId = HGPO.GeoId; - OutputIdentifier.PartId = HGPO.PartId; - OutputIdentifier.PartName = HGPO.PartName; - - OutInstancedOutputPartData.Add(OutputIdentifier, FHoudiniInstancedOutputPartData()); - FHoudiniInstancedOutputPartData *InstancedOutputData = OutInstancedOutputPartData.Find(OutputIdentifier); - // Create all the instancers and attach them to a fake outer component - if (!FHoudiniInstanceTranslator::PopulateInstancedOutputPartData(HGPO, InOutputs, *InstancedOutputData)) - return false; - } - } - - return true; -} - -bool -UHoudiniGeoImporter::DeleteCreatedNode(const HAPI_NodeId& InNodeId) -{ - if (InNodeId < 0) - return false; - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), InNodeId)) - { - // Could not delete the bgeo's file sop ! - HOUDINI_LOG_WARNING(TEXT("Houdini GEO Importer: Could not delete HAPI File SOP.")); - return false; - } - - return true; -} - -bool -UHoudiniGeoImporter::ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams) -{ - if (InBGEOFile.IsEmpty()) - return false; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!AutoStartHoudiniEngineSessionIfNeeded()) - return false; - - // 2. Update the file paths - if (!SetFilePath(InBGEOFile)) - return false; - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - if (!LoadBGEOFileInHAPI(NodeId)) - return false; - - // 4. Get the output from the file node - TArray NewOutputs; - TArray OldOutputs; - if (!BuildOutputsForNode(NodeId, OldOutputs, NewOutputs)) - return false; - - // Failure lambda - auto CleanUpAndReturn = [&NewOutputs](const bool& bReturnValue) - { - // Remove the output objects from the root set before returning false - for (auto Out : NewOutputs) - Out->RemoveFromRoot(); - - return bReturnValue; - }; - - // Prepare the package used for creating the mesh, landscape and instancer pacakges - FHoudiniPackageParams PackageParams; - if (InPackageParams) - { - PackageParams = *InPackageParams; - } - else - { - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.HoudiniAssetName = FString(); - PackageParams.HoudiniAssetActorName = FString(); - PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); - } - - if (!PackageParams.OuterPackage) - { - PackageParams.OuterPackage = InParent; - } - - if (!PackageParams.ComponentGUID.IsValid()) - { - // TODO: will need to reuse the GUID when reimporting? - PackageParams.ComponentGUID = FGuid::NewGuid(); - } - - // 5. Create the static meshes in the outputs - if (!CreateStaticMeshes(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 6. Create the static meshes in the outputs - if (!CreateCurves(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 7. Create the landscape in the outputs - if (!CreateLandscapes(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 8. Create the instancers in the outputs - if (!CreateInstancers(NewOutputs, InParent, PackageParams)) - return CleanUpAndReturn(false); - - // 9. Delete the created node in Houdini - if (!DeleteCreatedNode(NodeId)) - return CleanUpAndReturn(false); - - // Clean up and return true - return CleanUpAndReturn(true); -} - -bool -UHoudiniGeoImporter::OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition) -{ - if (InBGEOFile.IsEmpty()) - return false; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!AutoStartHoudiniEngineSessionIfNeeded()) - return false; - - if (!FPaths::FileExists(InBGEOFile)) - { - // Cant find BGEO file - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InBGEOFile); - return false; - } - - // Make sure we're using absolute path! - const FString AbsoluteFilePath = FPaths::ConvertRelativePathToFull(InBGEOFile); - - if (AbsoluteFilePath.IsEmpty()) - return false; - - FString AbsoluteFileDirectory; - FString FileName; - FString FileExtension; - - // Split the file path - FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); - - // Handle .bgeo.sc correctly - if (FileExtension.Equals(TEXT("sc"))) - { - // append the bgeo to .sc - FileExtension = FPaths::GetExtension(FileName) + TEXT(".") + FileExtension; - // update the filename - FileName = FPaths::GetBaseFilename(FileName); - } - - if (FileExtension.IsEmpty()) - FileExtension = TEXT("bgeo"); - - if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) - { - // Not a bgeo file! - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *InBGEOFile); - return false; - } - - OutNodeId = -1; - - // Check HoudiniEngine / HAPI init? - if (!FHoudiniEngine::IsInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); - return false; - } - - FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); - - // Create a file SOP - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, "SOP/file", "bgeo", true, &OutNodeId), false); - - // Set the file path parameter - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - OutNodeId, "file", &ParmId), false); - - const std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str(), ParmId, 0), false); - - return true; -} - -bool -UHoudiniGeoImporter::CloseBGEOFile(const HAPI_NodeId& InNodeId) -{ - // 8. Delete the created node in Houdini - if (!DeleteCreatedNode(InNodeId)) - return false; - - return true; -} - -bool -UHoudiniGeoImporter::LoadBGEOFileInHAPI(HAPI_NodeId& NodeId) -{ - NodeId = -1; - - if (AbsoluteFilePath.IsEmpty()) - return false; - - // Check HoudiniEngine / HAPI init? - if (!FHoudiniEngine::IsInitialized()) - { - HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); - return false; - } - - FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); - - // Create a file SOP - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, "SOP/file", "bgeo", true, &NodeId), false); - - // Set the file path parameter - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - NodeId, "file", &ParmId), false); - - std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, 0), false); - - return CookFileNode(NodeId); -} - -bool -UHoudiniGeoImporter::CookFileNode(const HAPI_NodeId& InNodeId) -{ - // Cook the node - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); - - // Wait for the cook to finish - int32 status = HAPI_STATE_MAX_READY_STATE + 1; - while (status > HAPI_STATE_MAX_READY_STATE) - { - // Retrieve the status - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetStatus( - FHoudiniEngine::Get().GetSession(), - HAPI_STATUS_COOK_STATE, &status), false); - - FString StatusString = FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); - HOUDINI_LOG_MESSAGE(TEXT("Still Cooking, current status: %s."), *StatusString); - - // Go to bed.. - if (status > HAPI_STATE_MAX_READY_STATE) - FPlatformProcess::Sleep(0.5f); - } - - if (status != HAPI_STATE_READY) - { - // There was some cook errors - HOUDINI_LOG_ERROR(TEXT("Finished Cooking with errors!")); - return false; - } - - HOUDINI_LOG_MESSAGE(TEXT("Finished Cooking!")); - - return true; -} - -bool -UHoudiniGeoImporter::BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet) -{ - // TArray OldOutputs; - if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, InOldOutputs, OutNewOutputs, false)) - { - // Couldn't create the package - HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Failed to process the File SOP's outputs!")); - return false; - } - - if (bInAddOutputsToRootSet) - { - // Add the output objects to the RootSet to prevent them from being GCed - for (auto& Out : OutNewOutputs) - Out->AddToRoot(); - } - - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoImporter.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniPackageParams.h" +#include "HoudiniOutput.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniSplineComponent.h" + +#include "CoreMinimal.h" +#include "Misc/Paths.h" +#include "Misc/PackageName.h" +#include "UObject/Package.h" +#include "PackageTools.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "Editor.h" + +#include "Materials/MaterialInterface.h" +#include "Materials/Material.h" + + +UHoudiniGeoImporter::UHoudiniGeoImporter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , SourceFilePath() + , AbsoluteFilePath() + , AbsoluteFileDirectory() + , FileName() + , FileExtension() + , BakeRootFolder(TEXT("/Game/HoudiniEngine/Bake/")) +{ + /* + SourceFilePath = FString(); + + AbsoluteFilePath = FString(); + AbsoluteFileDirectory = FString(); + FileName = FString(); + FileExtension = FString(); + + OutputFilename = FString(); + BakeRootFolder = TEXT("/Game/HoudiniEngine/Bake/"); + */ +} + +bool +UHoudiniGeoImporter::SetFilePath(const FString& InFilePath) +{ + SourceFilePath = InFilePath; + if (!FPaths::FileExists(SourceFilePath)) + { + // Cant find BGEO file + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InFilePath); + return false; + } + + // Make sure we're using absolute path! + AbsoluteFilePath = FPaths::ConvertRelativePathToFull(SourceFilePath); + + // Split the file path + FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); + + // Handle .bgeo.sc correctly + if (FileExtension.Equals(TEXT("sc"))) + { + // append the bgeo to .sc + FileExtension = FPaths::GetExtension(FileName) +TEXT(".") + FileExtension; + // update the filename + FileName = FPaths::GetBaseFilename(FileName); + } + + if (FileExtension.IsEmpty()) + FileExtension = TEXT("bgeo"); + + if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) + { + // Not a bgeo file! + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *SourceFilePath); + return false; + } + + //BGEOFilePath = BGEOPath + TEXT("/") + BGEOFileName + TEXT(".") + BGEOExtension; + + // Only use "/" for the output file path + BakeRootFolder.ReplaceInline(TEXT("\\"), TEXT("/")); + // Make sure the output folder ends with a "/" + if (!BakeRootFolder.EndsWith("/")) + BakeRootFolder += TEXT("/"); + + // If we have't specified an outpout file name yet, use the input file name + if (OutputFilename.IsEmpty()) + OutputFilename = FileName; + + return true; +} + +bool +UHoudiniGeoImporter::AutoStartHoudiniEngineSessionIfNeeded() +{ + if (FHoudiniEngine::Get().GetSession()) + return true; + + // Default first session already attempted to be created ? stop here? + /* + if (FHoudiniEngine::Get().GetFirstSessionCreated()) + return false; + */ + + // Indicates that we've tried to start a session once no matter if it failed or succeed + FHoudiniEngine::Get().SetFirstSessionCreated(true); + if (!FHoudiniEngine::Get().RestartSession()) + { + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Couldn't start the default HoudiniEngine session!")); + return false; + } + + return true; +} + +bool +UHoudiniGeoImporter::BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs) +{ + FString Notification = TEXT("BGEO Importer: Getting output geos..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + const bool bInAddOutputsToRootSet = true; + return BuildAllOutputsForNode(InNodeId, this, InOldOutputs, OutNewOutputs, bInAddOutputsToRootSet); +} + +bool +UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + TMap AllOutputMaterials; + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + FString Notification = TEXT("BGEO Importer: Creating Static Meshes..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + //FHoudiniMeshTranslator::CreateAllMeshesFromHoudiniOutput(CurOutput, OuterPackage, OuterComponent, OuterAsset); + + TMap NewOutputObjects; + TMap OldOutputObjects = CurOutput->GetOutputObjects(); + TMap& AssignementMaterials = CurOutput->GetAssignementMaterials(); + TMap& ReplacementMaterials = CurOutput->GetReplacementMaterials(); + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + // Not a mesh, skip + if (CurHGPO.Type != EHoudiniPartType::Mesh) + continue; + + // Check for a unreal_output_name if we are in bake mode + FHoudiniPackageParams PackageParams(InPackageParams); + if (PackageParams.PackageMode == EPackageMode::Bake) + { + TArray OutputNames; + if (FHoudiniEngineUtils::GetOutputNameAttribute(CurHGPO.GeoId, CurHGPO.PartId, OutputNames, 0, 1)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + PackageParams.ObjectName = OutputNames[0]; + } + } + // Could have prim attribute unreal_bake_folder override + TArray BakeFolderNames; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(CurHGPO.GeoId, BakeFolderNames, CurHGPO.PartId, 0, 1)) + { + if (BakeFolderNames.Num() > 0 && !BakeFolderNames[0].IsEmpty()) + { + PackageParams.BakeFolder = BakeFolderNames[0]; + } + } + } + + FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); + FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); + FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( + CurHGPO, + PackageParams, + OldOutputObjects, + NewOutputObjects, + AssignementMaterials, + ReplacementMaterials, + AllOutputMaterials, + true, + EHoudiniStaticMeshMethod::RawMesh, + SMGP, + MBS); + + for (auto& CurMat : AssignementMaterials) + { + // Adds the newly generated materials to the output materials array + // This is to avoid recreating those same materials again + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } + } + + // Add all output objects and materials + for (auto CurOutputPair : NewOutputObjects) + { + UObject* CurObj = CurOutputPair.Value.OutputObject; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + + // Do the same for materials + for (auto CurAssignmentMatPair : AssignementMaterials) + { + UObject* CurObj = CurAssignmentMatPair.Value; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + + // Also assign to the output objects map as we may need the meshes to create instancers later + CurOutput->SetOutputObjects(NewOutputObjects); + } + + return true; +} + + +bool +UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + TArray CurveOutputs; + CurveOutputs.Reserve(InOutputs.Num()); + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Curve) + continue; + + CurveOutputs.Add(CurOutput); + break; + } + + FString Notification = TEXT("BGEO Importer: Creating Curves..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + // Look for the first unreal_output_name attribute on the curve outputs and use that + // for ObjectName + FHoudiniPackageParams PackageParams(InPackageParams); + for (auto& CurOutput : CurveOutputs) + { + bool bFoundOutputName = false; + bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Curve) + continue; + + if (!bFoundOutputName) + { + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings, 0, 1)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + } + } + } + + if (!bFoundBakeFolder) + { + TArray Strings; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId, 0, 1)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.BakeFolder = Strings[0]; + bFoundBakeFolder = true; + } + } + } + + if (bFoundOutputName && bFoundBakeFolder) + break; + } + + if (bFoundOutputName && bFoundBakeFolder) + break; + } + + // Create a Package for the BP + PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + FString PackageName; + UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); + check(BPPackage); + + // Create and init a new Blueprint Actor + UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); + if (!Blueprint) + return false; + + // Create a fake outer component that we'll use as a temporary outer for our curves + UWorld* TempWorld = UWorld::CreateWorld(EWorldType::Inactive, false, TEXT("BGEOImporterTemp"), GetTransientPackage(), false); + const FActorSpawnParameters ActorSpawnParameters; + AActor* OuterActor = TempWorld->SpawnActor(ActorSpawnParameters); + USceneComponent* OuterComponent = + NewObject(OuterActor, USceneComponent::GetDefaultSceneRootVariableName()); + + for (auto& CurOutput : CurveOutputs) + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); + + // Prepare an ActorComponent array for AddComponentsToBlueprint() + TArray OutputComp; + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputComp.Add(CurObj); + } + + // Transfer all the instancer components to the BP + if (OutputComp.Num() > 0) + { + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; + Params.OptionalNewRootNode = nullptr; + Params.bKeepMobility = false; + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); + } + } + + // Compile the blueprint + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + // Add it to our output objects + OutputObjects.Add(Blueprint); + + return true; +} + + +bool +UHoudiniGeoImporter::CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + HOUDINI_LOG_WARNING(TEXT("Importing a landscape directly from BGEOs is not currently supported.")); + return false; + } + + return true; + + /* + // Before processing any of the output, + // we need to get the min/max value for all Height volumes in this output (if any) + float HoudiniHeightfieldOutputsGlobalMin = 0.f; + float HoudiniHeightfieldOutputsGlobalMax = 0.f; + FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax(InOutputs, HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax); + + UWorld* PersistentWorld = InParent->GetWorld(); + if(!PersistentWorld) + PersistentWorld = GEditor ? GEditor->GetEditorWorldContext(false).World() : nullptr; + + if (!PersistentWorld) + return false; + + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + FString Notification = TEXT("BGEO Importer: Creating Landscapes..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + TArray EmptyInputLandscapes; + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(CurOutput); + + bool bCreatedNewMaps = false; + ERuntimePackageMode RuntimePackageMode = ERuntimePackageMode::CookToTemp; + switch(InPackageParams.PackageMode) + { + + case EPackageMode::Bake: + RuntimePackageMode = ERuntimePackageMode::Bake; + break; + case EPackageMode::CookToLevel: + case EPackageMode::CookToTemp: + default: + RuntimePackageMode = ERuntimePackageMode::CookToTemp; + break; + } + TArray> CreatedUntrackedOutputs; + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + CreatedUntrackedOutputs, + EmptyInputLandscapes, + EmptyInputLandscapes, + HAC, + TEXT("{object_name}_"), + PersistentWorld, + HoudiniHeightfieldOutputsGlobalMin, HoudiniHeightfieldOutputsGlobalMax, + InPackageParams, bCreatedNewMaps, + RuntimePackageMode); + + // Add all output objects + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UObject* CurObj = CurOutputPair.Value.OutputObject; + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputObjects.Add(CurObj); + } + } + + return true; + */ +} + + +bool +UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams) +{ + bool HasInstancer = false; + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + HasInstancer = true; + break; + } + + if (!HasInstancer) + return true; + + FString Notification = TEXT("BGEO Importer: Creating Instancers..."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + // Look for the first unreal_output_name attribute on the instancer outputs and use that + // for ObjectName + FHoudiniPackageParams PackageParams(InPackageParams); + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + bool bFoundOutputName = false; + bool bFoundBakeFolder = PackageParams.PackageMode != EPackageMode::Bake; + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Instancer) + continue; + + if (!bFoundOutputName) + { + TArray Strings; + if (FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, Strings, 0, 1)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.ObjectName = Strings[0]; + bFoundOutputName = true; + break; + } + } + } + + if (!bFoundBakeFolder) + { + TArray Strings; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, Strings, HGPO.PartId, 0, 1)) + { + if (Strings.Num() > 0 && !Strings[0].IsEmpty()) + { + PackageParams.BakeFolder = Strings[0]; + bFoundBakeFolder = true; + break; + } + } + } + + if (bFoundOutputName && bFoundBakeFolder) + break; + } + + if (bFoundOutputName && bFoundBakeFolder) + break; + } + + // Create a Package for the BP + PackageParams.ObjectName = TEXT("BP_") + PackageParams.ObjectName; + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + + FString PackageName; + UPackage* BPPackage = PackageParams.CreatePackageForObject(PackageName); + check(BPPackage); + + // Create and init a new Blueprint Actor + UBlueprint* Blueprint = FKismetEditorUtilities::CreateBlueprint(AActor::StaticClass(), BPPackage, *PackageName, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), FName("HoudiniGeoImporter")); + if (!Blueprint) + return false; + + // Create a fake outer component that we'll use as a temporary outer for our instancers + USceneComponent* OuterComponent = NewObject(); + + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + // Create all the instancers and attach them to a fake outer component + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + CurOutput, InOutputs, OuterComponent, InPackageParams); + + // Prepare an ActorComponent array for AddComponentsToBlueprint() + TArray OutputComp; + for (auto CurOutputPair : CurOutput->GetOutputObjects()) + { + UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); + if (!CurObj || CurObj->IsPendingKill()) + continue; + + OutputComp.Add(CurObj); + } + + // Transfer all the instancer components to the BP + if (OutputComp.Num() > 0) + { + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.HarvestMode = FKismetEditorUtilities::EAddComponentToBPHarvestMode::None; + Params.OptionalNewRootNode = nullptr; + Params.bKeepMobility = false; + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, OutputComp, Params); + } + } + + // Compile the blueprint + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + // Add it to our output objects + OutputObjects.Add(Blueprint); + + return true; +} + +bool +UHoudiniGeoImporter::CreateInstancerOutputPartData( + TArray& InOutputs, + TMap& OutInstancedOutputPartData) +{ + for (auto& CurOutput : InOutputs) + { + if (CurOutput->GetType() != EHoudiniOutputType::Instancer) + continue; + + for (auto& HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = HGPO.ObjectId; + OutputIdentifier.GeoId = HGPO.GeoId; + OutputIdentifier.PartId = HGPO.PartId; + OutputIdentifier.PartName = HGPO.PartName; + + OutInstancedOutputPartData.Add(OutputIdentifier, FHoudiniInstancedOutputPartData()); + FHoudiniInstancedOutputPartData *InstancedOutputData = OutInstancedOutputPartData.Find(OutputIdentifier); + // Create all the instancers and attach them to a fake outer component + if (!FHoudiniInstanceTranslator::PopulateInstancedOutputPartData(HGPO, InOutputs, *InstancedOutputData)) + return false; + } + } + + return true; +} + +bool +UHoudiniGeoImporter::DeleteCreatedNode(const HAPI_NodeId& InNodeId) +{ + if (InNodeId < 0) + return false; + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), InNodeId)) + { + // Could not delete the bgeo's file sop ! + HOUDINI_LOG_WARNING(TEXT("Houdini GEO Importer: Could not delete HAPI File SOP.")); + return false; + } + + return true; +} + +bool +UHoudiniGeoImporter::ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams) +{ + if (InBGEOFile.IsEmpty()) + return false; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!AutoStartHoudiniEngineSessionIfNeeded()) + return false; + + // 2. Update the file paths + if (!SetFilePath(InBGEOFile)) + return false; + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + if (!LoadBGEOFileInHAPI(NodeId)) + return false; + + // 4. Get the output from the file node + TArray NewOutputs; + TArray OldOutputs; + if (!BuildOutputsForNode(NodeId, OldOutputs, NewOutputs)) + return false; + + // Failure lambda + auto CleanUpAndReturn = [&NewOutputs](const bool& bReturnValue) + { + // Remove the output objects from the root set before returning false + for (auto Out : NewOutputs) + Out->RemoveFromRoot(); + + return bReturnValue; + }; + + // Prepare the package used for creating the mesh, landscape and instancer pacakges + FHoudiniPackageParams PackageParams; + if (InPackageParams) + { + PackageParams = *InPackageParams; + } + else + { + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.HoudiniAssetName = FString(); + PackageParams.HoudiniAssetActorName = FString(); + PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); + } + + if (!PackageParams.OuterPackage) + { + PackageParams.OuterPackage = InParent; + } + + if (!PackageParams.ComponentGUID.IsValid()) + { + // TODO: will need to reuse the GUID when reimporting? + PackageParams.ComponentGUID = FGuid::NewGuid(); + } + + // 5. Create the static meshes in the outputs + if (!CreateStaticMeshes(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 6. Create the static meshes in the outputs + if (!CreateCurves(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 7. Create the landscape in the outputs + if (!CreateLandscapes(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 8. Create the instancers in the outputs + if (!CreateInstancers(NewOutputs, InParent, PackageParams)) + return CleanUpAndReturn(false); + + // 9. Delete the created node in Houdini + if (!DeleteCreatedNode(NodeId)) + return CleanUpAndReturn(false); + + // Clean up and return true + return CleanUpAndReturn(true); +} + +bool +UHoudiniGeoImporter::OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition) +{ + if (InBGEOFile.IsEmpty()) + return false; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!AutoStartHoudiniEngineSessionIfNeeded()) + return false; + + if (!FPaths::FileExists(InBGEOFile)) + { + // Cant find BGEO file + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: could not find file %s!"), *InBGEOFile); + return false; + } + + // Make sure we're using absolute path! + const FString AbsoluteFilePath = FPaths::ConvertRelativePathToFull(InBGEOFile); + + if (AbsoluteFilePath.IsEmpty()) + return false; + + FString AbsoluteFileDirectory; + FString FileName; + FString FileExtension; + + // Split the file path + FPaths::Split(AbsoluteFilePath, AbsoluteFileDirectory, FileName, FileExtension); + + // Handle .bgeo.sc correctly + if (FileExtension.Equals(TEXT("sc"))) + { + // append the bgeo to .sc + FileExtension = FPaths::GetExtension(FileName) + TEXT(".") + FileExtension; + // update the filename + FileName = FPaths::GetBaseFilename(FileName); + } + + if (FileExtension.IsEmpty()) + FileExtension = TEXT("bgeo"); + + if (!FileExtension.StartsWith(TEXT("bgeo"), ESearchCase::IgnoreCase)) + { + // Not a bgeo file! + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: File %s is not a .bgeo or .bgeo.sc file!"), *InBGEOFile); + return false; + } + + OutNodeId = -1; + + // Check HoudiniEngine / HAPI init? + if (!FHoudiniEngine::IsInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); + return false; + } + + FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); + + // Create a file SOP + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, "SOP/file", "bgeo", true, &OutNodeId), false); + + /* + // Set the file path parameter + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + OutNodeId, "file", &ParmId), false); + + const std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str(), ParmId, 0), false); + */ + + // Simply use LoadGeoFrom file + std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + FHoudiniApi::LoadGeoFromFile(FHoudiniEngine::Get().GetSession(), OutNodeId, ConvertedString.c_str()); + + return true; +} + +bool +UHoudiniGeoImporter::CloseBGEOFile(const HAPI_NodeId& InNodeId) +{ + // 8. Delete the created node in Houdini + if (!DeleteCreatedNode(InNodeId)) + return false; + + return true; +} + +bool +UHoudiniGeoImporter::LoadBGEOFileInHAPI(HAPI_NodeId& NodeId) +{ + NodeId = -1; + + if (AbsoluteFilePath.IsEmpty()) + return false; + + // Check HoudiniEngine / HAPI init? + if (!FHoudiniEngine::IsInitialized()) + { + HOUDINI_LOG_ERROR(TEXT("Couldn't initialize HoudiniEngine!")); + return false; + } + + FString Notification = TEXT("BGEO Importer: Loading bgeo file..."); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Notification), true); + + // Create a file SOP + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, "SOP/file", "bgeo", true, &NodeId), false); + + /* + // Set the file path parameter + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + NodeId, "file", &ParmId), false); + + std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str(), ParmId, 0), false); + */ + + // Simply use LoadGeoFrom file + std::string ConvertedString = TCHAR_TO_UTF8(*AbsoluteFilePath); + FHoudiniApi::LoadGeoFromFile(FHoudiniEngine::Get().GetSession(), NodeId, ConvertedString.c_str()); + + return CookFileNode(NodeId); +} + +bool +UHoudiniGeoImporter::CookFileNode(const HAPI_NodeId& InNodeId) +{ + // Cook the node + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InNodeId, &CookOptions), false); + + // Wait for the cook to finish + int32 status = HAPI_STATE_MAX_READY_STATE + 1; + while (status > HAPI_STATE_MAX_READY_STATE) + { + // Retrieve the status + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetStatus( + FHoudiniEngine::Get().GetSession(), + HAPI_STATUS_COOK_STATE, &status), false); + + FString StatusString = FHoudiniEngineUtils::GetStatusString(HAPI_STATUS_COOK_STATE, HAPI_STATUSVERBOSITY_ERRORS); + HOUDINI_LOG_MESSAGE(TEXT("Still Cooking, current status: %s."), *StatusString); + + // Go to bed.. + if (status > HAPI_STATE_MAX_READY_STATE) + FPlatformProcess::Sleep(0.5f); + } + + if (status != HAPI_STATE_READY) + { + // There was some cook errors + HOUDINI_LOG_ERROR(TEXT("Finished Cooking with errors!")); + return false; + } + + HOUDINI_LOG_MESSAGE(TEXT("Finished Cooking!")); + + return true; +} + +bool +UHoudiniGeoImporter::BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet) +{ + // TArray OldOutputs; + TArray NodeIdsToCook; + if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, InOldOutputs, OutNewOutputs, NodeIdsToCook, false, true)) + { + // Couldn't create the package + HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Failed to process the File SOP's outputs!")); + return false; + } + + if (bInAddOutputsToRootSet) + { + // Add the output objects to the RootSet to prevent them from being GCed + for (auto& Out : OutNewOutputs) + Out->AddToRoot(); + } + + return true; +} diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.h b/Source/HoudiniEngine/Private/HoudiniGeoImporter.h index 33ed94820..0ca0a5965 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.h +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.h @@ -1,122 +1,122 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniGeoImporter.generated.h" - -class UHoudiniOutput; - -struct FHoudiniPackageParams; - -UCLASS() -class HOUDINIENGINE_API UHoudiniGeoImporter : public UObject -{ -public: - - GENERATED_UCLASS_BODY() - - public: - //UHoudiniGeoImporter(); - //~UHoudiniGeoImporter(); - - void SetBakeRootFolder(const FString& InFolder) { BakeRootFolder = InFolder; }; - void SetOutputFilename(const FString& InOutFilename) { OutputFilename = InOutFilename; }; - - TArray& GetOutputObjects() { return OutputObjects; }; - - // BEGIN: Static API - // Open a BGEO file: create a file node in HAPI and cook it - static bool OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition=false); - // Cook the file node specified by the valid NodeId. - static bool CookFileNode(const HAPI_NodeId& InNodeId); - // Extract the outputs for a given node ID - static bool BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet=false); - // Delete the HAPI node and remove InOutputs from the root set. - static bool CloseBGEOFile(const HAPI_NodeId& InNodeId); - // END: Static API - - // Import the BGEO file - bool ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams=nullptr); - - // 1. Start a HE session if needed - static bool AutoStartHoudiniEngineSessionIfNeeded(); - // 2. Update our file members fromn the input file path - bool SetFilePath(const FString& InFilePath); - // 3. Creates a new file node and loads the bgeo file in HAPI - bool LoadBGEOFileInHAPI(HAPI_NodeId& NodeId); - // 4. Extract the outputs for a given node ID - bool BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs); - // 5. Creates the static meshes object found in the output - bool CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 6. Create the output curves - bool CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 7. Create the output landscapes - bool CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 8. Create the output instancers - bool CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); - // 9. Clean up the created node - static bool DeleteCreatedNode(const HAPI_NodeId& InNodeId); - - static bool CreateInstancerOutputPartData( - TArray& InOutputs, - TMap& OutInstancedOutputPartData); - - private: - - // - // Input file - // - // Path how the file we're currently loading - FString SourceFilePath; - // Absolute Path to the file - FString AbsoluteFilePath; - FString AbsoluteFileDirectory; - // File Name / Extension - FString FileName; - FString FileExtension; - - - // - // Output file - // - // Output filename, if empty, will be set to the input filename - FString OutputFilename; - // Root Folder for storing the created files - FString BakeRootFolder; - - // - // Output Objects - // - TArray OutputObjects; - - //TArray OutputStaticMeshes; - //TArray OutputLandscapes; - //TArray OutputInstancers; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniGeoImporter.generated.h" + +class UHoudiniOutput; + +struct FHoudiniPackageParams; + +UCLASS() +class HOUDINIENGINE_API UHoudiniGeoImporter : public UObject +{ +public: + + GENERATED_UCLASS_BODY() + + public: + //UHoudiniGeoImporter(); + //~UHoudiniGeoImporter(); + + void SetBakeRootFolder(const FString& InFolder) { BakeRootFolder = InFolder; }; + void SetOutputFilename(const FString& InOutFilename) { OutputFilename = InOutFilename; }; + + TArray& GetOutputObjects() { return OutputObjects; }; + + // BEGIN: Static API + // Open a BGEO file: create a file node in HAPI and cook it + static bool OpenBGEOFile(const FString& InBGEOFile, HAPI_NodeId& OutNodeId, bool bInUseWorldComposition=false); + // Cook the file node specified by the valid NodeId. + static bool CookFileNode(const HAPI_NodeId& InNodeId); + // Extract the outputs for a given node ID + static bool BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject* InOuter, TArray& InOldOutputs, TArray& OutNewOutputs, bool bInAddOutputsToRootSet=false); + // Delete the HAPI node and remove InOutputs from the root set. + static bool CloseBGEOFile(const HAPI_NodeId& InNodeId); + // END: Static API + + // Import the BGEO file + bool ImportBGEOFile(const FString& InBGEOFile, UObject* InParent, const FHoudiniPackageParams* InPackageParams=nullptr); + + // 1. Start a HE session if needed + static bool AutoStartHoudiniEngineSessionIfNeeded(); + // 2. Update our file members fromn the input file path + bool SetFilePath(const FString& InFilePath); + // 3. Creates a new file node and loads the bgeo file in HAPI + bool LoadBGEOFileInHAPI(HAPI_NodeId& NodeId); + // 4. Extract the outputs for a given node ID + bool BuildOutputsForNode(const HAPI_NodeId& InNodeId, TArray& InOldOutputs, TArray& OutNewOutputs); + // 5. Creates the static meshes object found in the output + bool CreateStaticMeshes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 6. Create the output curves + bool CreateCurves(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 7. Create the output landscapes + bool CreateLandscapes(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 8. Create the output instancers + bool CreateInstancers(TArray& InOutputs, UObject* InParent, FHoudiniPackageParams InPackageParams); + // 9. Clean up the created node + static bool DeleteCreatedNode(const HAPI_NodeId& InNodeId); + + static bool CreateInstancerOutputPartData( + TArray& InOutputs, + TMap& OutInstancedOutputPartData); + + private: + + // + // Input file + // + // Path how the file we're currently loading + FString SourceFilePath; + // Absolute Path to the file + FString AbsoluteFilePath; + FString AbsoluteFileDirectory; + // File Name / Extension + FString FileName; + FString FileExtension; + + + // + // Output file + // + // Output filename, if empty, will be set to the input filename + FString OutputFilename; + // Root Folder for storing the created files + FString BakeRootFolder; + + // + // Output Objects + // + TArray OutputObjects; + + //TArray OutputStaticMeshes; + //TArray OutputLandscapes; + //TArray OutputInstancers; + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp index b50e986b6..e5cafb264 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp @@ -1,370 +1,371 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniHandleComponent.h" - - -bool -FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray NewHandles; - - if (FHoudiniHandleTranslator::BuildAllHandles(HAC->GetAssetId(), HAC, HAC->HandleComponents, NewHandles)) - { - - HAC->HandleComponents = NewHandles; - - } - - return true; -} - -bool -FHoudiniHandleTranslator::BuildAllHandles( - const HAPI_NodeId& AssetId, - UHoudiniAssetComponent* OuterObject, - TArray& CurrentHandles, - TArray& NewHandles) -{ - if (AssetId < 0) - return false; - - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo)) - return false; - - TMap CurrentHandlesByName; - - for (auto& Handle : CurrentHandles) - { - if (!Handle) - continue; - - CurrentHandlesByName.Add(Handle->GetHandleName(), Handle); - } - - // If there are handles - if (AssetInfo.handleCount > 0) - { - TArray 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) - { - 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"), - OuterObject ? *(OuterObject->GetName()) : TEXT("?"), *TypeName, *HandleName); - continue; - } - - UHoudiniHandleComponent* HandleComponent = nullptr; - UHoudiniHandleComponent** FoundHandleComponent = CurrentHandlesByName.Find(HandleName); - if (FoundHandleComponent) - { - HandleComponent = *FoundHandleComponent; - - CurrentHandles.Remove(*FoundHandleComponent); - CurrentHandlesByName.Remove(HandleName); - } - else - { - HandleComponent = NewObject( - OuterObject, - UHoudiniHandleComponent::StaticClass(), - NAME_None, RF_Public | RF_Transactional); - - HandleComponent->SetHandleName(HandleName); - HandleComponent->SetHandleType(HandleType); - - // Change the creation method so the component is listed in the details panels - HandleComponent->CreationMethod = EComponentCreationMethod::Instance; - } - - if (!HandleComponent) - continue; - - // If we have no parent, we need to re-attach. - if (!HandleComponent->GetAttachParent()) - { - HandleComponent->AttachToComponent(OuterObject, FAttachmentTransformRules::KeepRelativeTransform); - } - - HandleComponent->SetVisibility(true); - - // If component is not registered, register it - if (!HandleComponent->IsRegistered()) - HandleComponent->RegisterComponent(); - - // Get handle's bindings - TArray BindingInfos; - BindingInfos.SetNumZeroed(HandleInfo.bindingsCount); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetHandleBindingInfo(FHoudiniEngine::Get().GetSession(), - AssetId, HandleIdx, &BindingInfos[0], 0, HandleInfo.bindingsCount)) - continue; - - HAPI_TransformEuler HapiEulerXform; - FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); - HapiEulerXform.position[0] = HapiEulerXform.position[1] = HapiEulerXform.position[2] = 0.0f; - HapiEulerXform.rotationEuler[0] = HapiEulerXform.rotationEuler[1] = HapiEulerXform.rotationEuler[2] = 0.0f; - HapiEulerXform.scale[0] = HapiEulerXform.scale[1] = HapiEulerXform.scale[2] = 1.0f; - - TSharedPtr< FString > RSTOrderStrPtr, XYZOrderStrPtr; - - for (const auto& BindingInfo : BindingInfos) - { - FString HandleParmName = TEXT(""); - FHoudiniEngineString HoudiniEngineString(BindingInfo.handleParmNameSH); - HoudiniEngineString.ToFString(HandleParmName); - - const HAPI_ParmId ParamId = BindingInfo.assetParmId; - - TArray &XformParms = HandleComponent->XformParms; - - UHoudiniParameter* FoundParam = nullptr; - - for (auto Param : OuterObject->Parameters) - { - if (Param->GetParmId() == ParamId) - { - FoundParam = Param; - break; - } - } - - HandleComponent->InitializeHandleParameters(); - - if (!HandleComponent->CheckHandleValid()) - continue; - - (void)(XformParms[int32(EXformParameter::TX)]->Bind(HapiEulerXform.position[0], "tx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::TY)]->Bind(HapiEulerXform.position[1], "ty", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::TZ)]->Bind(HapiEulerXform.position[2], "tz", 2, HandleParmName, FoundParam) - - || XformParms[int32(EXformParameter::RX)]->Bind(HapiEulerXform.rotationEuler[0], "rx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::RY)]->Bind(HapiEulerXform.rotationEuler[1], "ry", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::RZ)]->Bind(HapiEulerXform.rotationEuler[2], "rz", 2, HandleParmName, FoundParam) - - || XformParms[int32(EXformParameter::SX)]->Bind(HapiEulerXform.scale[0], "sx", 0, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::SY)]->Bind(HapiEulerXform.scale[1], "sy", 1, HandleParmName, FoundParam) - || XformParms[int32(EXformParameter::SZ)]->Bind(HapiEulerXform.scale[2], "sz", 2, HandleParmName, FoundParam) - - || HandleComponent->RSTParm->Bind(RSTOrderStrPtr, "trs_order", 0, HandleParmName, FoundParam) - || HandleComponent->RotOrderParm->Bind(XYZOrderStrPtr, "xyz_order", 0, HandleParmName, FoundParam) - ); - } - - HapiEulerXform.rstOrder = GetHapiRSTOrder(RSTOrderStrPtr); - HapiEulerXform.rotationOrder = GetHapiXYZOrder(XYZOrderStrPtr); - - constexpr float MaxFloat = TNumericLimits::Max(); - constexpr float MinFloat = TNumericLimits::Min(); - - HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); - HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); - HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); - - FTransform UnrealXform; - FHoudiniEngineUtils::TranslateHapiTransform(HapiEulerXform, UnrealXform); - - //HandleComponent->SetRelativeTransform(UnrealXform); - - NewHandles.Add(HandleComponent); - - } - } - - return true; -} - - -void -FHoudiniHandleTranslator::ClearHandles(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - for (auto& HandleComponent : HAC->HandleComponents) - { - if (!HandleComponent) - continue; - - HandleComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - HandleComponent->UnregisterComponent(); - HandleComponent->DestroyComponent(); - } - - HAC->HandleComponents.Empty(); -} - -HAPI_RSTOrder -FHoudiniHandleTranslator::GetHapiRSTOrder(const TSharedPtr & StrPtr) -{ - if (StrPtr.Get()) - { - FString & Str = *StrPtr; - - if (Str == "trs") return HAPI_TRS; - else if (Str == "tsr") return HAPI_TSR; - else if (Str == "rts") return HAPI_RTS; - else if (Str == "rst") return HAPI_RST; - else if (Str == "str") return HAPI_STR; - else if (Str == "srt") return HAPI_SRT; - } - - return HAPI_SRT; -} - -HAPI_XYZOrder -FHoudiniHandleTranslator::GetHapiXYZOrder(const TSharedPtr & StrPtr) -{ - if (StrPtr.Get()) - { - FString & Str = *StrPtr; - - if (Str == "xyz") return HAPI_XYZ; - else if (Str == "xzy") return HAPI_XZY; - else if (Str == "yxz") return HAPI_YXZ; - else if (Str == "yzx") return HAPI_YZX; - else if (Str == "zxy") return HAPI_ZXY; - else if (Str == "zyx") return HAPI_ZYX; - } - - return HAPI_XYZ; -} - - -void -FHoudiniHandleTranslator::UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent) -{ - if (!HandleComponent || HandleComponent->IsPendingKill()) - return; - - if (!HandleComponent->CheckHandleValid()) - return; - - TArray &XformParms = HandleComponent->XformParms; - - if (XformParms.Num() < (int32)EXformParameter::COUNT) - return; - - HAPI_Transform HapiXform; - FMemory::Memzero< HAPI_Transform >(HapiXform); - FHoudiniEngineUtils::TranslateUnrealTransform(HandleComponent->GetRelativeTransform(), HapiXform); - - const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); - - float HapiMatrix[16]; - FHoudiniApi::ConvertTransformQuatToMatrix(Session, &HapiXform, HapiMatrix); - - HAPI_TransformEuler HapiEulerXform; - FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); - FHoudiniApi::ConvertMatrixToEuler( - Session, - HapiMatrix, - GetHapiRSTOrder(HandleComponent->RSTParm->Get(TSharedPtr< FString >())), - GetHapiXYZOrder(HandleComponent->RotOrderParm->Get(TSharedPtr< FString >())), - &HapiEulerXform - ); - - *XformParms[int32(EXformParameter::TX)] = HapiEulerXform.position[0]; - *XformParms[int32(EXformParameter::TY)] = HapiEulerXform.position[1]; - *XformParms[int32(EXformParameter::TZ)] = HapiEulerXform.position[2]; - - *XformParms[int32(EXformParameter::RX)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[0]); - *XformParms[int32(EXformParameter::RY)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[1]); - *XformParms[int32(EXformParameter::RZ)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[2]); - - constexpr float MaxFloat = TNumericLimits::Max(); - constexpr float MinFloat = TNumericLimits::Min(); - HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); - HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); - HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); - - *XformParms[int32(EXformParameter::SX)] = HapiEulerXform.scale[0]; - *XformParms[int32(EXformParameter::SY)] = HapiEulerXform.scale[1]; - *XformParms[int32(EXformParameter::SZ)] = HapiEulerXform.scale[2]; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniHandleComponent.h" + + +bool +FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray NewHandles; + + if (FHoudiniHandleTranslator::BuildAllHandles(HAC->GetAssetId(), HAC, HAC->HandleComponents, NewHandles)) + { + + HAC->HandleComponents = NewHandles; + + } + + return true; +} + +bool +FHoudiniHandleTranslator::BuildAllHandles( + const HAPI_NodeId& AssetId, + UHoudiniAssetComponent* OuterObject, + TArray& CurrentHandles, + TArray& NewHandles) +{ + if (AssetId < 0) + return false; + + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo)) + return false; + + TMap CurrentHandlesByName; + + for (auto& Handle : CurrentHandles) + { + if (!Handle) + continue; + + CurrentHandlesByName.Add(Handle->GetHandleName(), Handle); + } + + // If there are handles + if (AssetInfo.handleCount > 0) + { + TArray 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) + { + 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"), + OuterObject ? *(OuterObject->GetName()) : TEXT("?"), *TypeName, *HandleName); + continue; + } + + UHoudiniHandleComponent* HandleComponent = nullptr; + UHoudiniHandleComponent** FoundHandleComponent = CurrentHandlesByName.Find(HandleName); + if (FoundHandleComponent) + { + HandleComponent = *FoundHandleComponent; + + CurrentHandles.Remove(*FoundHandleComponent); + CurrentHandlesByName.Remove(HandleName); + } + else + { + HandleComponent = NewObject( + OuterObject, + UHoudiniHandleComponent::StaticClass(), + NAME_None, RF_Public | RF_Transactional); + + HandleComponent->SetHandleName(HandleName); + HandleComponent->SetHandleType(HandleType); + + // Change the creation method so the component is listed in the details panels + HandleComponent->CreationMethod = EComponentCreationMethod::Instance; + } + + if (!HandleComponent) + continue; + + // If we have no parent, we need to re-attach. + if (!HandleComponent->GetAttachParent()) + { + HandleComponent->AttachToComponent(OuterObject, FAttachmentTransformRules::KeepRelativeTransform); + } + + HandleComponent->SetVisibility(true); + + // If component is not registered, register it + if (!HandleComponent->IsRegistered()) + HandleComponent->RegisterComponent(); + + // Get handle's bindings + TArray BindingInfos; + BindingInfos.SetNumZeroed(HandleInfo.bindingsCount); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetHandleBindingInfo( + FHoudiniEngine::Get().GetSession(), + AssetId, HandleIdx, &BindingInfos[0], 0, HandleInfo.bindingsCount)) + continue; + + HAPI_TransformEuler HapiEulerXform; + FMemory::Memzero(HapiEulerXform); + HapiEulerXform.position[0] = HapiEulerXform.position[1] = HapiEulerXform.position[2] = 0.0f; + HapiEulerXform.rotationEuler[0] = HapiEulerXform.rotationEuler[1] = HapiEulerXform.rotationEuler[2] = 0.0f; + HapiEulerXform.scale[0] = HapiEulerXform.scale[1] = HapiEulerXform.scale[2] = 1.0f; + + TSharedPtr RSTOrderStrPtr, XYZOrderStrPtr; + + for (const auto& BindingInfo : BindingInfos) + { + FString HandleParmName = TEXT(""); + FHoudiniEngineString HoudiniEngineString(BindingInfo.handleParmNameSH); + HoudiniEngineString.ToFString(HandleParmName); + + const HAPI_ParmId ParamId = BindingInfo.assetParmId; + + TArray &XformParms = HandleComponent->XformParms; + + UHoudiniParameter* FoundParam = nullptr; + + for (auto Param : OuterObject->Parameters) + { + if (Param->GetParmId() == ParamId) + { + FoundParam = Param; + break; + } + } + + HandleComponent->InitializeHandleParameters(); + + if (!HandleComponent->CheckHandleValid()) + continue; + + (void)(XformParms[int32(EXformParameter::TX)]->Bind(HapiEulerXform.position[0], "tx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::TY)]->Bind(HapiEulerXform.position[1], "ty", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::TZ)]->Bind(HapiEulerXform.position[2], "tz", 2, HandleParmName, FoundParam) + + || XformParms[int32(EXformParameter::RX)]->Bind(HapiEulerXform.rotationEuler[0], "rx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::RY)]->Bind(HapiEulerXform.rotationEuler[1], "ry", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::RZ)]->Bind(HapiEulerXform.rotationEuler[2], "rz", 2, HandleParmName, FoundParam) + + || XformParms[int32(EXformParameter::SX)]->Bind(HapiEulerXform.scale[0], "sx", 0, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::SY)]->Bind(HapiEulerXform.scale[1], "sy", 1, HandleParmName, FoundParam) + || XformParms[int32(EXformParameter::SZ)]->Bind(HapiEulerXform.scale[2], "sz", 2, HandleParmName, FoundParam) + + || HandleComponent->RSTParm->Bind(RSTOrderStrPtr, "trs_order", 0, HandleParmName, FoundParam) + || HandleComponent->RotOrderParm->Bind(XYZOrderStrPtr, "xyz_order", 0, HandleParmName, FoundParam) + ); + } + + HapiEulerXform.rstOrder = GetHapiRSTOrder(RSTOrderStrPtr); + HapiEulerXform.rotationOrder = GetHapiXYZOrder(XYZOrderStrPtr); + + constexpr float MaxFloat = TNumericLimits::Max(); + constexpr float MinFloat = TNumericLimits::Min(); + + HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); + HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); + HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); + + FTransform UnrealXform; + FHoudiniEngineUtils::TranslateHapiTransform(HapiEulerXform, UnrealXform); + + //HandleComponent->SetRelativeTransform(UnrealXform); + + NewHandles.Add(HandleComponent); + + } + } + + return true; +} + + +void +FHoudiniHandleTranslator::ClearHandles(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + for (auto& HandleComponent : HAC->HandleComponents) + { + if (!HandleComponent) + continue; + + HandleComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + HandleComponent->UnregisterComponent(); + HandleComponent->DestroyComponent(); + } + + HAC->HandleComponents.Empty(); +} + +HAPI_RSTOrder +FHoudiniHandleTranslator::GetHapiRSTOrder(const TSharedPtr & StrPtr) +{ + if (StrPtr.Get()) + { + FString & Str = *StrPtr; + + if (Str == "trs") return HAPI_TRS; + else if (Str == "tsr") return HAPI_TSR; + else if (Str == "rts") return HAPI_RTS; + else if (Str == "rst") return HAPI_RST; + else if (Str == "str") return HAPI_STR; + else if (Str == "srt") return HAPI_SRT; + } + + return HAPI_SRT; +} + +HAPI_XYZOrder +FHoudiniHandleTranslator::GetHapiXYZOrder(const TSharedPtr & StrPtr) +{ + if (StrPtr.Get()) + { + FString & Str = *StrPtr; + + if (Str == "xyz") return HAPI_XYZ; + else if (Str == "xzy") return HAPI_XZY; + else if (Str == "yxz") return HAPI_YXZ; + else if (Str == "yzx") return HAPI_YZX; + else if (Str == "zxy") return HAPI_ZXY; + else if (Str == "zyx") return HAPI_ZYX; + } + + return HAPI_XYZ; +} + + +void +FHoudiniHandleTranslator::UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent) +{ + if (!HandleComponent || HandleComponent->IsPendingKill()) + return; + + if (!HandleComponent->CheckHandleValid()) + return; + + TArray &XformParms = HandleComponent->XformParms; + + if (XformParms.Num() < (int32)EXformParameter::COUNT) + return; + + HAPI_Transform HapiXform; + FMemory::Memzero< HAPI_Transform >(HapiXform); + FHoudiniEngineUtils::TranslateUnrealTransform(HandleComponent->GetRelativeTransform(), HapiXform); + + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + + float HapiMatrix[16]; + FHoudiniApi::ConvertTransformQuatToMatrix(Session, &HapiXform, HapiMatrix); + + HAPI_TransformEuler HapiEulerXform; + FMemory::Memzero< HAPI_TransformEuler >(HapiEulerXform); + FHoudiniApi::ConvertMatrixToEuler( + Session, + HapiMatrix, + GetHapiRSTOrder(HandleComponent->RSTParm->Get(TSharedPtr< FString >())), + GetHapiXYZOrder(HandleComponent->RotOrderParm->Get(TSharedPtr< FString >())), + &HapiEulerXform + ); + + *XformParms[int32(EXformParameter::TX)] = HapiEulerXform.position[0]; + *XformParms[int32(EXformParameter::TY)] = HapiEulerXform.position[1]; + *XformParms[int32(EXformParameter::TZ)] = HapiEulerXform.position[2]; + + *XformParms[int32(EXformParameter::RX)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[0]); + *XformParms[int32(EXformParameter::RY)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[1]); + *XformParms[int32(EXformParameter::RZ)] = FMath::RadiansToDegrees(HapiEulerXform.rotationEuler[2]); + + constexpr float MaxFloat = TNumericLimits::Max(); + constexpr float MinFloat = TNumericLimits::Min(); + HapiEulerXform.scale[0] = FMath::Clamp(HapiEulerXform.scale[0], MinFloat, MaxFloat); + HapiEulerXform.scale[1] = FMath::Clamp(HapiEulerXform.scale[1], MinFloat, MaxFloat); + HapiEulerXform.scale[2] = FMath::Clamp(HapiEulerXform.scale[2], MinFloat, MaxFloat); + + *XformParms[int32(EXformParameter::SX)] = HapiEulerXform.scale[0]; + *XformParms[int32(EXformParameter::SY)] = HapiEulerXform.scale[1]; + *XformParms[int32(EXformParameter::SZ)] = HapiEulerXform.scale[2]; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h index f362bd7e9..93bf71fbe 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.h @@ -1,55 +1,55 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniApi.h" - -#include "Templates/SharedPointer.h" - -class UHoudiniAssetComponent; -class UHoudiniHandleComponent; - -struct HOUDINIENGINE_API FHoudiniHandleTranslator -{ - static bool UpdateHandles(UHoudiniAssetComponent* HAC); - - - static bool BuildAllHandles(const HAPI_NodeId& AssetId, - UHoudiniAssetComponent* OuterObject, - TArray& CurrentHandles, - TArray& NewHandles); - - static void ClearHandles(UHoudiniAssetComponent* HAC); - - static HAPI_RSTOrder GetHapiRSTOrder(const TSharedPtr & StrPtr); - - static HAPI_XYZOrder GetHapiXYZOrder(const TSharedPtr & StrPtr); - - static void UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniApi.h" + +#include "Templates/SharedPointer.h" + +class UHoudiniAssetComponent; +class UHoudiniHandleComponent; + +struct HOUDINIENGINE_API FHoudiniHandleTranslator +{ + static bool UpdateHandles(UHoudiniAssetComponent* HAC); + + + static bool BuildAllHandles(const HAPI_NodeId& AssetId, + UHoudiniAssetComponent* OuterObject, + TArray& CurrentHandles, + TArray& NewHandles); + + static void ClearHandles(UHoudiniAssetComponent* HAC); + + static HAPI_RSTOrder GetHapiRSTOrder(const TSharedPtr & StrPtr); + + static HAPI_XYZOrder GetHapiXYZOrder(const TSharedPtr & StrPtr); + + static void UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp index d2ad57df6..bf8778a4a 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp @@ -1,3047 +1,3111 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInputTranslator.h" - -#include "HoudiniInput.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInputObject.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniAssetActor.h" -#include "HoudiniOutputTranslator.h" -#include "UnrealBrushTranslator.h" -#include "UnrealSplineTranslator.h" -#include "UnrealMeshTranslator.h" -#include "UnrealInstanceTranslator.h" -#include "UnrealLandscapeTranslator.h" -#include "UnrealFoliageTypeTranslator.h" - -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "Engine/Brush.h" -#include "Engine/DataTable.h" -#include "Camera/CameraComponent.h" -#include "FoliageType_InstancedStaticMesh.h" - -#include "Engine/SimpleConstructionScript.h" -#include "Engine/SCS_Node.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Editor/UnrealEdEngine.h" - #include "UnrealEdGlobals.h" -#endif - -#include "HCsgUtils.h" - -#include "Async/Async.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#if WITH_EDITOR -// Allows checking of objects currently being dragged around -struct FHoudiniMoveTracker -{ - FHoudiniMoveTracker() : IsObjectMoving(false) - { - GEditor->OnBeginObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = true; }); - GEditor->OnEndObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - - GEditor->OnBeginCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - GEditor->OnEndCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); - } - static FHoudiniMoveTracker& Get() { static FHoudiniMoveTracker Instance; return Instance; } - - bool IsObjectMoving; -}; -#endif - -// -bool -FHoudiniInputTranslator::UpdateInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) - { - // Failed to create the inputs - return false; - } - - return true; -} - -bool -FHoudiniInputTranslator::BuildAllInputs( - const HAPI_NodeId& AssetId, - class UObject* InOuterObject, - TArray& Inputs, - TArray& Parameters) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Start by getting the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - // Get the number of geo (SOP) inputs - int32 InputCount = AssetInfo.geoInputCount; - /* - // It's best to update the input count even if the hda hasnt cooked - // as it can cause loaded geo inputs to disappear upon loading the level - if ( AssetInfo.hasEverCooked ) - { - InputCount = AssetInfo.geoInputCount; - } - */ - // Also look for object path parameters inputs - TArray> InputParameters; - for (auto Param : Parameters) - { - if (Param->GetParameterType() == EHoudiniParameterType::Input) - InputParameters.Add(Param); - } - - InputCount += InputParameters.Num(); - - // Append new inputs as needed - if (InputCount > Inputs.Num()) - { - int32 NumNewInputs = InputCount - Inputs.Num(); - for (int32 InputIdx = Inputs.Num(); InputIdx < InputCount; ++InputIdx) - { - FString InputObjectName = TEXT("Input") + FString::FromInt(InputIdx + 1); - UHoudiniInput * NewInput = NewObject< UHoudiniInput >( - InOuterObject, - UHoudiniInput::StaticClass(), - FName(*InputObjectName), - RF_Transactional); - - if (!NewInput || NewInput->IsPendingKill()) - { - //HOUDINI_LOG_WARNING("Failed to create asset input"); - continue; - } - // Create a default curve object here to avoid Transaction issue - //NewInput->CreateDefaultCurveInputObject(); - - Inputs.Add(NewInput); - } - } - else if (InputCount < Inputs.Num()) - { - // TODO: Properly clean up the input object + created nodes? - for (int32 InputIdx = Inputs.Num() - 1; InputIdx >= InputCount; InputIdx--) - { - UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - FHoudiniInputTranslator::DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); - - // DO NOT MANUALLY DESTROY THE OLD/DANGLING INPUTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - //CurrentInput->ConditionalBeginDestroy(); - //CurrentInput = nullptr; - } - - Inputs.SetNum(InputCount); - } - - // Now, check the inputs in the array match the geo inputs - //for (int32 GeoInIdx = 0; GeoInIdx < AssetInfo.geoInputCount; GeoInIdx++) - bool bBlueprintStructureChanged = false; - for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) - { - UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // Create default Name/Label/Help - FString CurrentInputName = TEXT("Input") + FString::FromInt(InputIdx + 1); - FString CurrentInputLabel = CurrentInputName; - FString CurrentInputHelp; - - // Set the nodeId - CurrentInput->SetAssetNodeId(AssetId); - - // Is this an object path parameter input? - bool bIsObjectPath = InputIdx >= AssetInfo.geoInputCount; - if (!bIsObjectPath) - { - // Mark this input as a SOP input - CurrentInput->SetSOPInput(InputIdx); - - // Get and set the name - HAPI_StringHandle InputStringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInputName( - FHoudiniEngine::Get().GetSession(), - AssetId, InputIdx, &InputStringHandle)) - { - FHoudiniEngineString HoudiniEngineString(InputStringHandle); - HoudiniEngineString.ToFString(CurrentInputLabel); - } - } - else - { - // Get this input's parameter index in the objpath param array - int32 CurrentParmIdx = InputIdx - AssetInfo.geoInputCount; - - UHoudiniParameter* CurrentParm = nullptr; - if (InputParameters.IsValidIndex(CurrentParmIdx)) - { - if (InputParameters[CurrentParmIdx].IsValid()) - CurrentParm = InputParameters[CurrentParmIdx].Get(); - } - - int32 ParmId = -1; - if (CurrentParm && !CurrentParm->IsPendingKill()) - { - ParmId = CurrentParm->GetParmId(); - CurrentInputName = CurrentParm->GetParameterName(); - CurrentInputLabel = CurrentParm->GetParameterLabel(); - CurrentInputHelp = CurrentParm->GetParameterHelp(); - } - - UHoudiniParameterOperatorPath* CurrentObjPathParm = Cast(CurrentParm); - if (CurrentObjPathParm && !CurrentObjPathParm->IsPendingKill()) - { - CurrentObjPathParm->HoudiniInput = CurrentInput; - } - - // Mark this input as an object path parameter input - CurrentInput->SetObjectPathParameter(ParmId); - } - - CurrentInput->SetName(CurrentInputName); - CurrentInput->SetLabel(CurrentInputLabel); - - if ( CurrentInputHelp.IsEmpty() ) - { - CurrentInputHelp = CurrentInputLabel + TEXT("(") + CurrentInputName + TEXT(")"); - } - CurrentInput->SetHelp(CurrentInputHelp); - - // If the input type is invalid, - // We need to initialize its default - if (CurrentInput->GetInputType() == EHoudiniInputType::Invalid) - { - // Initialize it to the default corresponding to its name - CurrentInput->SetInputType(GetDefaultInputTypeFromLabel(CurrentInputLabel), bBlueprintStructureChanged); - - // Preset the default HDA for objpath input - SetDefaultAssetFromHDA(CurrentInput, bBlueprintStructureChanged); - } - - // Update input objects data on UE side for all types of inputs. - switch (CurrentInput->GetInputType()) - { - case EHoudiniInputType::Curve: - FHoudiniSplineTranslator::UpdateHoudiniInputCurves(CurrentInput); - break; - case EHoudiniInputType::Landscape: - //FUnrealLandscapeTranslator::UpdateHoudiniInputLandscapes(CurrentInput); - break; - case EHoudiniInputType::Asset: - break; - case EHoudiniInputType::Geometry: - break; - case EHoudiniInputType::Skeletal: - break; - case EHoudiniInputType::World: - break; - default: - break; - } - } - - return true; -} - -bool -FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - if (!InputToDestroy || InputToDestroy->IsPendingKill()) - return false; - - // Start by disconnecting the input / nullifying the object path parameter - if (InputToDestroy->IsObjectPathParameter()) - { - // Just set the objpath parameter to null - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - InputToDestroy->GetAssetNodeId(), "", - InputToDestroy->GetParameterId(), 0); - } - else - { - // Get the asset / created input node ID - HAPI_NodeId HostAssetId = InputToDestroy->GetAssetNodeId(); - HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); - - // Only disconnect if both are valid - if (HostAssetId >= 0 && CreatedInputId >= 0) - { - FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), - HostAssetId, InputToDestroy->GetInputIndex()); - } - } - - if (InputType == EHoudiniInputType::Asset) - { - // TODO: - // If we're an asset input, just remove us from the downstream connection on the input HDA - // then reset this input's flag - - // TODO: Check this? Clean our DS assets?? why?? likely uneeded - UHoudiniAssetComponent* OuterHAC = Cast(InputToDestroy->GetOuter()); - if (OuterHAC) - OuterHAC->ClearDownstreamHoudiniAsset(); - - InputToDestroy->SetInputNodeId(-1); - } - - return true; -} - -bool -FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - if (!InputToDestroy || InputToDestroy->IsPendingKill()) - return false; - - if (!InputToDestroy->CanDeleteHoudiniNodes()) - return false; - - // If we're destroying an asset input, don't destroy anything as we don't want to destroy the input HDA - // a simple disconnect is sufficient - if (InputType == EHoudiniInputType::Asset) - return true; - - // Destroy the nodes created by all the input objects - TArray CreatedInputDataAssetIds = InputToDestroy->GetCreatedDataNodeIds(); - TArray* InputObjectNodes = InputToDestroy->GetHoudiniInputObjectArray(InputType); - if (InputObjectNodes) - { - for (auto CurInputObject : *InputObjectNodes) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - if (CurInputObject->Type == EHoudiniInputObjectType::HoudiniAssetComponent) - { - // Remove this input object's node Id from the - // CreatedInputDataAssetIds array to avoid its deletion further down - CreatedInputDataAssetIds.Remove(CurInputObject->InputNodeId); - CurInputObject->InputNodeId = -1; - CurInputObject->InputObjectNodeId = -1; - continue; - } - - // For Actor input objects, set the input node id for all component objects to -1, - if (CurInputObject->Type == EHoudiniInputObjectType::Actor) - { - UHoudiniInputActor* CurActorInputObject = Cast(CurInputObject); - if (CurActorInputObject) - { - for (auto & CurActorComponent : CurActorInputObject->GetActorComponents()) - { - if (!CurActorComponent || CurActorComponent->IsPendingKill()) - continue; - - // No need to delete the nodes created for an asset component manually here, - // As they will be deleted when we clean up the CreateNodeIds array - CurActorComponent->InputNodeId = -1; - } - } - } - // No need to delete the nodes created for an asset component manually here, - // As they will be deleted when we clean up the CreateNodeIds array - - if (CurInputObject->InputNodeId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputNodeId); - CurInputObject->InputNodeId = -1; - } - - if(CurInputObject->InputObjectNodeId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputObjectNodeId); - CurInputObject->InputObjectNodeId = -1; - - // TODO: CHECK ME! - HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CurInputObject->InputObjectNodeId); - - // Delete its parent node as well - if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentNodeId)) - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentNodeId); - } - - // Also directly invalidate HoudiniSplineComponent's node IDs. - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast(CurInputObject); - if (IsValid(HoudiniSplineInputObject) && !IsGarbageCollecting()) - { - UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (SplineComponent && !SplineComponent->IsPendingKill()) - { - SplineComponent->SetNodeId(-1); - } - } - - CurInputObject->MarkChanged(true); - } - } - - // Destroy all the input assets - for (HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds) - { - if (AssetNodeId < 0) - continue; - - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), AssetNodeId); - } - CreatedInputDataAssetIds.Empty(); - - // Then simply destroy the input's parent OBJ node - if (InputToDestroy->GetInputNodeId() >= 0) - { - HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedInputId); - - if (CreatedInputId >= 0) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CreatedInputId); - InputToDestroy->SetInputNodeId(-1); - } - - if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentId)) - { - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentId); - } - } - - return true; -} - -bool -FHoudiniInputTranslator::DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) -{ - // Start by disconnecting the input/object merge - bool bSuccess = DisconnectInput(InputToDestroy, InputType); - - // Then destroy the created input nodes - bSuccess &= DestroyInputNodes(InputToDestroy, InputType); - - return bSuccess; -} - - -EHoudiniInputType -FHoudiniInputTranslator::GetDefaultInputTypeFromLabel(const FString& InputName) -{ - // We'll try to find these magic words to try to detect the default input type - //FString geoPrefix = TEXT("geo"); - FString curvePrefix = TEXT("curve"); - - FString landscapePrefix = TEXT("landscape"); - FString landscapePrefix2 = TEXT("terrain"); - FString landscapePrefix3 = TEXT("heightfield"); - - FString worldPrefix = TEXT("world"); - FString worldPrefix2 = TEXT("outliner"); - - FString assetPrefix = TEXT("asset"); - FString assetPrefix2 = TEXT("hda"); - - // By default, geometry input is chosen. - EHoudiniInputType InputType = EHoudiniInputType::Geometry; - - if (InputName.Contains(curvePrefix, ESearchCase::IgnoreCase)) - InputType = EHoudiniInputType::Curve; - - else if ((InputName.Contains(landscapePrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(landscapePrefix2, ESearchCase::IgnoreCase)) - || (InputName.Contains(landscapePrefix3, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::Landscape; - - else if ((InputName.Contains(worldPrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(worldPrefix2, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::World; - - else if ((InputName.Contains(assetPrefix, ESearchCase::IgnoreCase)) - || (InputName.Contains(assetPrefix2, ESearchCase::IgnoreCase))) - InputType = EHoudiniInputType::Asset; - - return InputType; -} - -bool -FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bForce) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (!InInput->HasInputTypeChanged() && !bForce) - return true; - - // - Handle switching AWAY from an input type - DisconnectAndDestroyInput(InInput, InInput->GetPreviousInputType()); - - // Invalidate the previous input type now that we've actually changed - //InInput->SetPreviousInputType(EHoudiniInputType::Invalid); - - //ChangeInputType(InInput, NewType); - - // TODO: - // - Handle updating to the new input type - // downstream asset connection, static mesh update, curve creation... - - // Mark all the objects from this input has changed so they upload themselves - InInput->MarkAllInputObjectsChanged(true); - - return true; -} - -bool -FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified) -{ - // - if (!Input || Input->IsPendingKill()) - return false; - - // Make sure we're linked to a valid object path parameter - if (Input->GetParameterId() < 0) - return false; - - // Get our ParmInfo - HAPI_ParmInfo FoundParamInfo; - FHoudiniApi::ParmInfo_Init(&FoundParamInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmInfo( - FHoudiniEngine::Get().GetSession(), - Input->GetAssetNodeId(), Input->GetParameterId(), &FoundParamInfo)) - { - return false; - } - - // Get our string value - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - Input->GetAssetNodeId(), - false, - &StringHandle, - FoundParamInfo.stringValuesIndex, - 1)) - { - return false; - } - - FString ParamValue; - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (!HoudiniEngineString.ToFString(ParamValue)) - { - return false; - } - - if (ParamValue.Len() <= 0) - { - return false; - } - - // Chop the default value using semi-colons as separators - TArray Tokens; - ParamValue.ParseIntoArray(Tokens, TEXT(";"), true); - - // Start by setting geometry input objects - int32 GeoIdx = 0; - for (auto& CurToken : Tokens) - { - if (CurToken.IsEmpty()) - continue; - - // Set default objects on the HDA instance - will override the parameter string - // and apply the object input local-path thing for the HDA cook. - UObject * pObject = LoadObject(nullptr, *CurToken); - if (!pObject) - continue; - - Input->SetInputObjectAt(EHoudiniInputType::Geometry, GeoIdx++, pObject); - } - - // See if we can preset world objects as well - int32 WorldIdx = 0; - int32 LandscapedIdx = 0; - int32 HDAIdx = 0; - for (TActorIterator ActorIt(Input->GetWorld(), AActor::StaticClass(), EActorIteratorFlags::SkipPendingKill); ActorIt; ++ActorIt) - { - AActor* CurActor = *ActorIt; - if (!CurActor) - continue; - - AActor* FoundActor = nullptr; - int32 FoundIdx = Tokens.Find(CurActor->GetFName().ToString()); - if (FoundIdx == INDEX_NONE) - FoundIdx = Tokens.Find(CurActor->GetActorLabel()); - - if(FoundIdx != INDEX_NONE) - FoundActor = CurActor; - - if (!FoundActor) - continue; - - // Select the found actor in the world input - Input->SetInputObjectAt(EHoudiniInputType::World, WorldIdx++, FoundActor); - - if (FoundActor->IsA()) - { - // Select the HDA in the asset input - Input->SetInputObjectAt(EHoudiniInputType::Asset, HDAIdx++, FoundActor); - } - else if (FoundActor->IsA()) - { - // Select the landscape in the landscape input - Input->SetInputObjectAt(EHoudiniInputType::Landscape, LandscapedIdx++, FoundActor); - } - - // Remove the Found Token - Tokens.RemoveAt(FoundIdx); - } - - // See if we should change the default input type - if (Input->GetInputType() == EHoudiniInputType::Geometry && WorldIdx > 0 && GeoIdx == 0) - { - if (LandscapedIdx == WorldIdx) - { - // We've only selected landscapes, set to landscape IN - Input->SetInputType(EHoudiniInputType::Landscape, bOutBlueprintStructureModified); - } - else if (HDAIdx == WorldIdx) - { - // We've only selected Houdini Assets, set to Asset IN - Input->SetInputType(EHoudiniInputType::Asset, bOutBlueprintStructureModified); - } - else - { - // Set to world input - Input->SetInputType(EHoudiniInputType::World, bOutBlueprintStructureModified); - } - } - - return true; -} - -bool -FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - //for (auto CurrentInput : HAC->Inputs) - for(int32 InputIdx = 0; InputIdx < HAC->GetNumInputs(); InputIdx++) - { - UHoudiniInput*& CurrentInput = HAC->Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill() || !CurrentInput->HasChanged()) - continue; - - // First thing, see if we need to change the input type - if (CurrentInput->HasInputTypeChanged()) - { - ChangeInputType(CurrentInput, false); - } - - if (CurrentInput->GetInputType() == EHoudiniInputType::Landscape && CurrentInput->HasLandscapeExportTypeChanged()) - { - DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); - CurrentInput->MarkAllInputObjectsChanged(true); - CurrentInput->SetHasLandscapeExportTypeChanged(false); - } - - bool bSuccess = true; - if (CurrentInput->IsDataUploadNeeded()) - { - bSuccess &= UploadInputData(CurrentInput); - CurrentInput->MarkDataUploadNeeded(!bSuccess); - } - - if (CurrentInput->IsTransformUploadNeeded()) - { - bSuccess &= UploadInputTransform(CurrentInput); - } - - // Update the input properties AFTER eventually uploading it - bSuccess = UpdateInputProperties(CurrentInput); - - if (bSuccess) - { - CurrentInput->MarkChanged(false); - CurrentInput->MarkAllInputObjectsChanged(false); - } - - if (CurrentInput->HasInputTypeChanged()) - CurrentInput->SetPreviousInputType(EHoudiniInputType::Invalid); - - // Even if we failed, no need to try updating again. - CurrentInput->SetNeedsToTriggerUpdate(false); - } - - return true; -} - -bool -FHoudiniInputTranslator::UpdateInputProperties(UHoudiniInput* InInput) -{ - bool bSucess = UpdateTransformType(InInput); - - bSucess &= UpdatePackBeforeMerge(InInput); - - bSucess &= UpdateTransformOffset(InInput); - - return bSucess; -} - -bool -FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - bool nTransformType = InInput->GetKeepWorldTransform(); - - // Geometry inputs are always set to none - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType == EHoudiniInputType::Geometry) - nTransformType = 0; - - // Get the Input node ID from the host ID - HAPI_NodeId InputNodeId = -1; - HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); - - bool bSuccess = true; - const std::string sXformType = "xformtype"; - if (InInput->IsObjectPathParameter()) - { - // Directly change the Parameter xformtype - // (This will only work if the object merge is editable/unlocked) - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), - HostAssetId, sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - else - { - // Query the object merge's node ID via the input - if (HAPI_RESULT_SUCCESS == FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - HostAssetId, InInput->GetInputIndex(), &InputNodeId)) - { - // Change its Parameter xformtype - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), - InputNodeId, sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - } - - // Since our input objects are all plugged into a merge node - // We want to also update the transform type on the object merge plugged into the merge node - HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); - if ((ParentNodeId >= 0) && (InputType != EHoudiniInputType::Geometry) && (InputType != EHoudiniInputType::Asset)) - { - HAPI_NodeId InputObjectNodeId = -1; - int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); - for (int n = 0; n < NumberOfInputMeshes; n++) - { - // Get the Input node ID from the host ID - InputObjectNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ParentNodeId, n, &InputObjectNodeId)) - continue; - - if (InputObjectNodeId == -1) - continue; - - // Change the xformtype parameter on the object merge - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), InputObjectNodeId, - sXformType.c_str(), 0, nTransformType)) - bSuccess = false; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - // Pack before merge is only available for Geo/World input - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType != EHoudiniInputType::World - && InputType != EHoudiniInputType::Geometry) - { - // Nothing to change - return true; - } - - uint32 nPackValue = InInput->GetPackBeforeMerge() ? 1 : 0; - - // Get the Input node ID from the host ID - HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); - - bool bSuccess = true; - const std::string sPack = "pack"; - - // We'll be going through each input object plugged in the input's merge node - // and change the pack parameter there - HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); - if (ParentNodeId >= 0) - { - HAPI_NodeId InputObjectNodeId = -1; - int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); - for (int n = 0; n < NumberOfInputMeshes; n++) - { - // Get the Input node ID from the host ID - InputObjectNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ParentNodeId, n, &InputObjectNodeId)) - continue; - - if (InputObjectNodeId == -1) - continue; - - // Change the pack parameter on the object merge - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), InputObjectNodeId, - sPack.c_str(), 0, nPackValue)) - bSuccess = false; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - // Transform offsets are only for geometry inputs - EHoudiniInputType InputType = InInput->GetInputType(); - if (InputType != EHoudiniInputType::Geometry) - { - // Nothing to change - return true; - } - - // Get the input objects - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Update each object's transform offset - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - // If the Input mesh has a Transform offset - FTransform TransformOffset = CurrentInputObject->Transform; - if (!TransformOffset.Equals(FTransform::Identity)) - { - // Updating the Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CurrentInputObject->InputObjectNodeId, &HapiTransform), false); - } - } - - return true; -} - -bool -FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - EHoudiniInputType InputType = InInput->GetInputType(); - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Iterate on all the input objects and see if they need to be uploaded - bool bSuccess = true; - TArray CreatedNodeIds; - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; - if (!CurrentInputObject->HasChanged() && CurrentInputObjectNodeId >= 0) - { - // If this object hasn't changed, no need to upload it - // but we need to keep its created input node - if (CurrentInputObject->Type == EHoudiniInputObjectType::Actor) - { - // If this input object is an actor, it actually contains other input - // objects for each of his components, keep them as well - UHoudiniInputActor* InputActor = Cast(CurrentInputObject); - if (InputActor && !InputActor->IsPendingKill()) - { - for (auto CurrentComp : InputActor->GetActorComponents()) - { - if (!CurrentComp || CurrentComp->IsPendingKill()) - continue; - - int32& CurrentCompNodeId = CurrentComp->InputObjectNodeId; - if (!CurrentComp->HasChanged() && CurrentCompNodeId >= 0) - { - // If the component hasnt changed and is valid, keep it - CreatedNodeIds.Add(CurrentCompNodeId); - } - else - { - // Upload the component input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentComp, CreatedNodeIds)) - bSuccess = false; - } - } - } - } - else - { - // No changes, keep it - CreatedNodeIds.Add(CurrentInputObjectNodeId); - } - } - else - { - // Upload the current input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentInputObject, CreatedNodeIds)) - bSuccess = false; - } - } - - // If we haven't created any input, invalidate our input node id - if (CreatedNodeIds.Num() == 0) - { - if (!InInput->HasInputTypeChanged()) - { - int32 InputNodeId = InInput->GetInputNodeId(); - TArray PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); - - if (InInput->GetInputType() == EHoudiniInputType::Asset) - { - UHoudiniAssetComponent * OuterHAC = Cast(InInput->GetOuter()); - HAPI_NodeId AssetId = OuterHAC->GetAssetId(); - - // Disconnect the asset input - if (InputNodeId >= 0 && InInput->GetInputIndex() >= 0) - { - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), AssetId, InInput->GetInputIndex())); - } - } - else if (InInput->GetInputType() == EHoudiniInputType::World) - { - // World nodes are handled by InputObjects () (with FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete) - } - else - { - if (InputNodeId >= 0) - { - for (int32 Idx = 0; Idx < PreviousInputObjectNodeIds.Num(); Idx++) - { - - // Get the object merge connected to the merge node - HAPI_NodeId InputObjectMergeId = -1; - HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); - - // Disconnect the two nodes - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); - - // Destroy the object merge node, do not delete other HDA (Asset input type) - HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); - } - } - } - } - InInput->GetCreatedDataNodeIds().Empty(); - InInput->SetInputNodeId(-1); - return bSuccess; - } - - // Get the current input's NodeId - HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); - // Check that the current input's node ID is still valid - if (InputNodeId < 0 || !FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - { - // This input doesn't have a valid NodeId yet, - // we need to create this input's merge node and update this input's node ID - FString MergeName = InInput->GetNodeBaseName() + TEXT("_Merge"); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/merge"), MergeName, true, &InputNodeId), false); - - InInput->SetInputNodeId(InputNodeId); - } - - //TODO: - // Do we want to update the input's transform? - if (false) - { - FTransform ComponentTransform = FTransform::Identity; - USceneComponent* OuterComp = Cast(InInput->GetOuter()); - if (OuterComp && !OuterComp->IsPendingKill()) - ComponentTransform = OuterComp->GetComponentTransform(); - - FHoudiniEngineUtils::HapiSetAssetTransform(InputNodeId, ComponentTransform); - //HapiUpdateInputNodeTransform(InputNodeId, ComponentTransform); - } - - // Connect all the input objects to the merge node now - int32 InputIndex = 0; - for (auto CurrentNodeId : CreatedNodeIds) - { - if (CurrentNodeId < 0) - continue; - - if (InputNodeId == CurrentNodeId) - continue; - - // Connect the current input object to the merge node - HOUDINI_CHECK_ERROR(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - InputNodeId, InputIndex++, CurrentNodeId, 0)); - } - - // Check if we need to disconnect extra input objects nodes from the merge - // This can be needed when the input had more input objects on the previous cook - TArray& PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); - if (!InInput->HasInputTypeChanged()) - { - for (int32 Idx = CreatedNodeIds.Num(); Idx < PreviousInputObjectNodeIds.Num(); Idx++) - { - // Get the object merge connected to the merge node - HAPI_NodeId InputObjectMergeId = -1; - if (InInput->GetInputType() != EHoudiniInputType::Asset) - HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); - - // Disconnect the two nodes - HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( - FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); - - // Destroy the object merge node, do not destroy other HDA (Asset input type) - if (InInput->GetInputType() != EHoudiniInputType::Asset) - { - HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); - } - } - } - - // Keep track of all the nodes plugged into our input's merge - PreviousInputObjectNodeIds = CreatedNodeIds; - - // Finally, connect our main input node to the asset - bSuccess = ConnectInputNode(InInput); - - return bSuccess; -} - -bool -FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - EHoudiniInputType InputType = InInput->GetInputType(); - TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); - if (!ensure(InputObjectsArray)) - return false; - - // Iterate on all the input objects and see if their transform needs to be uploaded - bool bSuccess = true; - for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; - if (!CurrentInputObject->HasTransformChanged()) - continue; - - // Upload the current input object's transform to Houdini - if (!UploadHoudiniInputTransform(InInput, CurrentInputObject)) - { - bSuccess = false; - continue; - } - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::ConnectInputNode(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - HAPI_NodeId AssetNodeId = InInput->GetAssetNodeId(); - if (AssetNodeId < 0) - return false; - - HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); - if (InputNodeId < 0) - return false; - - // Helper for connecting our input or setting the object path parameter - if (InInput->IsObjectPathParameter()) - { - // Now we can assign the input node path to the parameter - std::string ParamNameString = TCHAR_TO_UTF8(*(InInput->GetName())); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue( - FHoudiniEngine::Get().GetSession(), AssetNodeId, - ParamNameString.c_str(), InputNodeId), false); - } - else - { - // TODO: CHECK ME! - //if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - // return false; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), AssetNodeId, - InInput->GetInputIndex(), InputNodeId, 0), false); - } - - return true; -} - -bool -FHoudiniInputTranslator::UploadHoudiniInputObject( - UHoudiniInput* InInput, - UHoudiniInputObject* InInputObject, - TArray& OutCreatedNodeIds) -{ - if (!InInput || !InInputObject) - return false; - - FString ObjBaseName = InInput->GetNodeBaseName(); - - bool bSuccess = true; - switch (InInputObject->Type) - { - case EHoudiniInputObjectType::Object: - { - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForObject(ObjBaseName, InInputObject); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::StaticMesh: - { - UHoudiniInputStaticMesh* InputSM = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( - ObjBaseName, InputSM, InInput->GetExportLODs(), InInput->GetExportSockets(), - InInput->GetExportColliders(), InInput->GetImportAsReference()); - - if (bSuccess) - { - // If this SM input object takes in a BP, add all its BP StaticMesh components input object node id to the created id list. - if (InputSM->bIsBlueprint()) - { - for (auto & CurSMObj : InputSM->BlueprintStaticMeshes) - OutCreatedNodeIds.Add(CurSMObj->InputObjectNodeId); - } - else - { - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - } - } - - break; - } - - case EHoudiniInputObjectType::SkeletalMesh: - { - UHoudiniInputSkeletalMesh* InputSkelMes = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(ObjBaseName, InputSkelMes); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::SceneComponent: - { - UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(ObjBaseName, InputSceneComp); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::StaticMeshComponent: - { - UHoudiniInputMeshComponent* InputSMC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( - ObjBaseName, InputSMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders(), InInput->GetImportAsReference()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - { - UHoudiniInputInstancedMeshComponent* InputISMC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( - ObjBaseName, InputISMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::SplineComponent: - { - UHoudiniInputSplineComponent* InputSpline = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(ObjBaseName, InputSpline, InInput->GetUnrealSplineResolution()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::HoudiniSplineComponent: - { - UHoudiniInputHoudiniSplineComponent* InputCurve = Cast(InInputObject); - - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve, InInput->IsAddRotAndScaleAttributesEnabled()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::HoudiniAssetActor: - case EHoudiniInputObjectType::HoudiniAssetComponent: - { - UHoudiniInputHoudiniAsset* InputHAC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetImportAsReference()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Actor: - { - UHoudiniInputActor* InputActor = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, OutCreatedNodeIds); - break; - } - - case EHoudiniInputObjectType::Landscape: - { - UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForLandscape(ObjBaseName, InputLandscape, InInput); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Brush: - { - UHoudiniInputBrush* InputBrush = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForBrush(ObjBaseName, InputBrush, InInput->GetBoundSelectorObjectArray()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::CameraComponent: - { - UHoudiniInputCameraComponent* InputCamera = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForCamera(ObjBaseName, InputCamera); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::DataTable: - { - UHoudiniInputDataTable* InputDT = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(ObjBaseName, InputDT); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: - { - UHoudiniInputFoliageType_InstancedStaticMesh* const InputFoliageTypeSM = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - ObjBaseName, InputFoliageTypeSM, InInput->GetExportLODs(), InInput->GetExportSockets(), - InInput->GetExportColliders(), InInput->GetImportAsReference()); - - if (bSuccess) - OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); - - break; - } - - case EHoudiniInputObjectType::Invalid: - //default: - break; - } - - // Mark that input object as not changed - if (bSuccess) - { - InInputObject->MarkChanged(false); - InInputObject->SetNeedsToTriggerUpdate(false); - } - else - { - // We couldn't update/create that input object, keep it changed but prevent it from trigger updates - InInputObject->SetNeedsToTriggerUpdate(false); - } - - return bSuccess; -} - - -// Upload transform for an input's InputObject -bool -FHoudiniInputTranslator::UploadHoudiniInputTransform( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject) -{ - if (!InInput || !InInputObject) - return false; - - auto UpdateTransform = [](const FTransform& InTransform, const HAPI_NodeId& InNodeId) - { - // Translate the Transform to HAPI - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(InTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InNodeId, &HapiTransform), false); - - return true; - }; - - bool bSuccess = true; - switch (InInputObject->Type) - { - case EHoudiniInputObjectType::StaticMesh: - { - // Simply update the Input mesh's Transform offset - if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) - bSuccess = false; - - break; - } - - case EHoudiniInputObjectType::StaticMeshComponent: - { - // Update using the static mesh component's transform - UHoudiniInputMeshComponent* InSMC = Cast(InInputObject); - if (!InSMC || InSMC->IsPendingKill()) - { - bSuccess = false; - break; - } - - FTransform NewTransform = InSMC->GetStaticMeshComponent() ? InSMC->GetStaticMeshComponent()->GetComponentTransform() : InInputObject->Transform; - if(!UpdateTransform(NewTransform, InInputObject->InputObjectNodeId)) - bSuccess = false; - - // Update the InputObject's transform - InInputObject->Transform = NewTransform; - - break; - } - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - { - // TODO: Only update the instances transform - break; - } - - case EHoudiniInputObjectType::HoudiniSplineComponent: - { - // TODO: Simply update the curve's transform? - break; - } - - case EHoudiniInputObjectType::HoudiniAssetActor: - case EHoudiniInputObjectType::HoudiniAssetComponent: - { - // TODO: Check, nothing to do? - break; - } - - case EHoudiniInputObjectType::Actor: - { - UHoudiniInputActor* InputActor = Cast(InInputObject); - if (!InputActor || InputActor->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Update the actor's transform - // To avoid further updates - if (InputActor->GetActor()) - InputActor->Transform = InputActor->GetActor()->GetTransform(); - - // Iterate on all the actor input objects and see if their transform needs to be uploaded - // TODO? Also update the component's actor transform?? - for (auto& CurrentComponent : InputActor->GetActorComponents()) - { - if (!CurrentComponent || CurrentComponent->IsPendingKill()) - continue; - - if (!CurrentComponent->HasTransformChanged()) - continue; - - // Upload the current input object's transform to Houdini - if (!UploadHoudiniInputTransform(InInput, CurrentComponent)) - { - bSuccess = false; - continue; - } - } - break; - } - - case EHoudiniInputObjectType::SceneComponent: - { - UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); - if (!InputSceneComp || InputSceneComp->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Update the component transform to avoid further updates - if (InputSceneComp->GetSceneComponent()) - InputSceneComp->Transform = InputSceneComp->GetSceneComponent()->GetComponentTransform(); - - break; - } - - case EHoudiniInputObjectType::Landscape: - { - // - UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) - { - bSuccess = false; - break; - } - - // - ALandscapeProxy* Landscape = InputLandscape->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Only apply diff for landscape since the HF's transform is used for value conversion as well - FTransform CurrentTransform = InputLandscape->Transform; - FTransform NewTransform = Landscape->ActorToWorld(); - - // Only handle position/rotation differences - FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); - FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); - - // Now get the HF's current transform - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - - if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) - { - bSuccess = false; - break; - } - - // Convert it to unreal - FTransform HFTransform; - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); - - // Apply the position offset if needed - if (!PosDiff.IsZero()) - HFTransform.AddToTranslation(PosDiff); - - // Apply the rotation offset if needed - if (!RotDiff.IsIdentity()) - HFTransform.ConcatenateRotation(RotDiff); - - // Convert back to a HAPI Transform and update the HF's transform - HAPI_TransformEuler NewHAPITransform; - FHoudiniApi::TransformEuler_Init(&NewHAPITransform); - FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); - NewHAPITransform.position[1] = 0.0f; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InputLandscape->InputObjectNodeId, &NewHAPITransform)) - { - bSuccess = false; - break; - } - - // Update the cached transform - InputLandscape->Transform = NewTransform; - } - - case EHoudiniInputObjectType::Brush: - { - // TODO: Update the Brush's transform - break; - } - - case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: - { - // Simply update the Input mesh's Transform offset - if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) - bSuccess = false; - - break; - } - - // Unsupported - case EHoudiniInputObjectType::Object: - case EHoudiniInputObjectType::SkeletalMesh: - case EHoudiniInputObjectType::SplineComponent: - { - break; - } - - case EHoudiniInputObjectType::Invalid: - default: - break; - } - - // Mark that input object as not changed - if (bSuccess) - { - InInputObject->MarkTransformChanged(false); - InInputObject->SetNeedsToTriggerUpdate(false); - } - else - { - // We couldn't update/create that input object, keep it changed but prevent it from trigger updates - InInputObject->SetNeedsToTriggerUpdate(false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForObject(const FString& InObjNodeName, UHoudiniInputObject* InObject) -{ - if (!InObject) - return false; - - UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) - return true; - - FString NodeName = InObjNodeName + TEXT("_") + Object->GetName(); - - // For UObjects we can't upload much, but can still create an input node - // with a single point, with an attribute pointing to the input object's path - HAPI_NodeId InputNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); - - // Update this input object's NodeId and ObjectNodeId - InObject->InputNodeId = (int32)InputNodeId; - InObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 2; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = 1; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); - - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - FVector ObjectPosition = InObject->Transform.GetLocation(); - TArray Position = - { - ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION - }; - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Position.GetData(), 0, - AttributeInfoPoint.count), false); - } - - { - // Create point attribute info for the path. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); - - // Set the point's path attribute - FString ObjectPathName = Object->GetPathName(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - ObjectPathName, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InputNodeId), false); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( - const FString& InObjNodeName, - UHoudiniInputStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UBlueprint* BP = nullptr; - UStaticMesh* SM = nullptr; - - FString SMName = InObjNodeName + TEXT("_"); - - // Get Blueprint or StaticMesh - if (InObject->bIsBlueprint()) - { - BP = InObject->GetBlueprint(); - if (!BP || BP->IsPendingKill()) - return true; - - SMName += BP->GetName(); - } - else - { - SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - SMName += SM->GetName(); - } - - // Marshall the Static Mesh to Houdini - bool bSuccess = true; - - if (bImportAsReference) - { - // Start by getting the Object's full name - FString AssetReference; - if (SM) - AssetReference += SM->GetFullName(); - - if (BP) - AssetReference += BP->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, SMName, InObject->Transform); - } - else - { - TArray StaticMeshComponents; - - // The input object is a Blueprint, Get all its StaticMeshes - if (BP) - { - USimpleConstructionScript* SCS = BP->SimpleConstructionScript; - if (SCS && !SCS->IsPendingKill()) - { - const TArray& Nodes = SCS->GetAllNodes(); - for (auto & CurNode : Nodes) - { - if (!CurNode || CurNode->IsPendingKill()) - continue; - - UActorComponent * CurComp = CurNode->ComponentTemplate; - if (!CurComp || CurComp->IsPendingKill()) - continue; - - UStaticMeshComponent* CurSMC = Cast(CurComp); - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - UStaticMesh* CurSM = CurSMC->GetStaticMesh(); - if (CurSM && !CurSM->IsPendingKill()) - StaticMeshComponents.Add(CurSMC); - - } - } - } - - // Clear previous Blueprint Static Mesh Comps (if there is any) - InObject->BlueprintStaticMeshes.Empty(); - - // This is a BP, add all the BP SM comps to its BlueprintStaticMeshes list. - if (InObject->bIsBlueprint()) - { - for (auto & CurSMC : StaticMeshComponents) - { - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - UHoudiniInputStaticMesh* SMObject = Cast( - UHoudiniInputObject::CreateTypedInputObject(CurSMC->GetStaticMesh(), InObject, InObject->GetName() + TEXT("_") + CurSMC->GetName())); - - if (!SMObject || SMObject->IsPendingKill()) - continue; - - bSuccess &= FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - CurSMC->GetStaticMesh(), SMObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); - - InObject->SetImportAsReference(false); - - // Update this input object's OBJ NodeId - SMObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(SMObject->InputNodeId); - - // Update the component's transform - FTransform ComponentTransform = CurSMC->GetRelativeTransform(); - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), SMObject->InputObjectNodeId, &HapiTransform), false); - } - - InObject->BlueprintStaticMeshes.Add(SMObject); - } - - return true; - } - // This is a normal static mesh input, process it normally as a static mesh Input Object - else - { - bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, InObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); - } - } - - InObject->SetImportAsReference(bImportAsReference); - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - // If the Input mesh has a Transform offset - FTransform TransformOffset = InObject->Transform; - if (!TransformOffset.Equals(FTransform::Identity)) - { - // Updating the Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USkeletalMesh* SkelMesh = InObject->GetSkeletalMesh(); - if (!SkelMesh || SkelMesh->IsPendingKill()) - return true; - - // Get the SM's transform offset - FTransform TransformOffset = InObject->Transform; - - // TODO - // Support this type of input object - // FUnrealMeshTranslator::CreateInputNodeForSkeletalMesh(...) - - return HapiCreateInputNodeForObject(InObjNodeName, InObject); -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USceneComponent* SceneComp = InObject->GetSceneComponent(); - if (!SceneComp || SceneComp->IsPendingKill()) - return true; - - // Get the Scene Component's transform - FTransform TransformOffset = InObject->Transform; - - // Get the parent Actor's transform - FTransform ParentTransform = InObject->ActorTransform; - - // Dont do that! - return false; - - // TODO - // Support this type of input object - return HapiCreateInputNodeForObject(InObjNodeName, InObject); -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UStaticMeshComponent* SMC = InObject->GetStaticMeshComponent(); - if (!SMC || SMC->IsPendingKill()) - return true; - - // Get the component's Static Mesh - UStaticMesh* SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - // Marshall the Static Mesh to Houdini - FString SMCName = InObjNodeName + TEXT("_") + SMC->GetName(); - - bool bSuccess = true; - if (bImportAsReference) - { - // Start by getting the Object's full name - FString AssetReference = SM->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, AssetReference, SMCName, InObject->Transform); - - } - else - { - bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, InObject->InputNodeId, SMCName, SMC, bExportLODs, bExportSockets, bExportColliders); - } - - InObject->SetImportAsReference(bImportAsReference); - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - // Update this input object's cache data - InObject->Update(SMC); - - // Update the component's transform - FTransform ComponentTransform = InObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - return bSuccess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputInstancedMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) - return true; - - // Get the ISMC - UInstancedStaticMeshComponent* ISMC = InObject->GetInstancedStaticMeshComponent(); - if (!ISMC || ISMC->IsPendingKill()) - return true; - - HAPI_NodeId NewNodeId = -1; - if (!FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( - ISMC, InObjNodeName, NewNodeId, bExportLODs, bExportSockets, bExportColliders, false)) - return false; - - // Update this input object's node IDs - InObject->InputNodeId = NewNodeId; - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); - - // Update the component's cached instances - InObject->Update(ISMC); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - USplineComponent* Spline = InObject->GetSplineComponent(); - if (!Spline || Spline->IsPendingKill()) - return true; - - - int32 NumberOfSplineControlPoints = InObject->NumberOfSplineControlPoints; - - TArray SplineControlPoints = InObject->SplineControlPoints; - - FString NodeName = InObjNodeName + TEXT("_") + InObject->GetName(); - - if (!FUnrealSplineTranslator::CreateInputNodeForSplineComponent(Spline, SplineResolution, InObject->InputNodeId, NodeName)) - return false; - - // Cache the exported curve's data to the input object - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - InObject->MarkChanged(true); - - //if (!HapiCreateInputNodeForObject(InObjNodeName, InObject)) - // return false; - - // Update the component's cached data - InObject->Update(Spline); - - // Update the component's transform - FTransform ComponentTransform = InObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject, bool bInAddRotAndScaleAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UHoudiniSplineComponent* Curve = InObject->GetCurveComponent(); - if (!Curve || Curve->IsPendingKill()) - return true; - - if (!FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(Curve, bInAddRotAndScaleAttributes)) - return false; - - // See if the component needs it node Id invalidated - //if (InObject->InputNodeId < 0) - // Curve->SetNodeId(InObject->InputNodeId); - - // Cache the exported curve's data to the input object - InObject->InputNodeId = Curve->GetNodeId(); - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - //InObject->CurveType = Curve->GetCurveType(); - //InObject->CurveMethod = Curve->GetCurveMethod(); - //InObject->Reversed = Curve->IsReversed(); - InObject->Update(Curve); - - InObject->MarkChanged(true); - - return true; -} - -bool -FHoudiniInputTranslator:: -HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - UHoudiniAssetComponent* InputHAC = InObject->GetHoudiniAssetComponent(); - if (!InputHAC || InputHAC->IsPendingKill()) - return true; - - if (!InputHAC->CanDeleteHoudiniNodes()) - return true; - - UHoudiniInput* HoudiniInput = Cast(InObject->GetOuter()); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - return true; - - UHoudiniAssetComponent* OuterHAC = Cast(HoudiniInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return true; - - // Do not allow using ourself as an input, terrible things would happen - if (InputHAC->GetAssetId() == OuterHAC->GetAssetId()) - return false; - - // If previously imported as ref, delete the input node. - if (InObject->InputNodeId > -1 && InObject->GetImportAsReference()) - { - int32 PreviousInputNodeId = InObject->InputNodeId; - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InObject->GetName()); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InObject->GetName()); - } - } - - InObject->SetImportAsReference(bImportAsReference); - - // If this object is in an Asset input, we need to set the InputNodeId directl - // to avoid creating extra merge nodes. World inputs should not do that! - bool bIsAssetInput = HoudiniInput->GetInputType() == EHoudiniInputType::Asset; - - if (bImportAsReference) - { - InObject->InputNodeId = -1; - InObject->InputObjectNodeId = -1; - - if(bIsAssetInput) - HoudiniInput->SetInputNodeId(-1); - - // Start by getting the Object's full name - FString AssetReference = InputHAC->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - if (!FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform)) // do not delete previous node if it was HAC - return false; - - if (bIsAssetInput) - HoudiniInput->SetInputNodeId(InObject->InputNodeId); - } - - InputHAC->AddDownstreamHoudiniAsset(OuterHAC); - - //if (HAC->NeedsInitialization()) - // HAC->MarkAsNeedInstantiation(); - - //HoudiniInput->SetAssetNodeId(HAC->GetAssetId()); - - // TODO: This might be uneeded as this function should only be called - // after we're not wiating on the input asset... - if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) - { - // If the input HAC needs to be instantiated, tell it do so - InputHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); - // Mark this object's input as changed so we can properly update after the input HDA's done instantiating/cooking - HoudiniInput->MarkChanged(true); - } - - if (InputHAC->NeedsInitialization() || InputHAC->NeedUpdate()) - return false; - - if (!bImportAsReference) - { - if (bIsAssetInput) - HoudiniInput->SetInputNodeId(InputHAC->GetAssetId()); - - InObject->InputNodeId = InputHAC->GetAssetId(); - } - - InObject->InputObjectNodeId = InObject->InputNodeId; - - bool bReturn = InObject->InputNodeId > -1; - - if(bIsAssetInput) - bReturn = FHoudiniInputTranslator::ConnectInputNode(HoudiniInput); - - return bReturn; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (!InObject || InObject->IsPendingKill()) - return false; - - AActor* Actor = InObject->GetActor(); - if (!Actor || Actor->IsPendingKill()) - return true; - - // Check if this is a world input and if this is a HoudiniAssetActor - // If so we need to build static meshes for any proxy meshes - if (InInput->GetInputType() == EHoudiniInputType::World && Actor->IsA()) - { - AHoudiniAssetActor *HAA = Cast(Actor); - UHoudiniAssetComponent *HAC = HAA->GetHoudiniAssetComponent(); - if (HAC && !HAC->IsPendingKill()) - { - if (HAC->HasAnyCurrentProxyOutput()) - { - bool bPendingDeleteOrRebuild = false; - bool bInvalidState = false; - const bool bIsHoudiniCookedDataAvailable = HAC->IsHoudiniCookedDataAvailable(bPendingDeleteOrRebuild, bInvalidState); - if (bIsHoudiniCookedDataAvailable) - { - // Build the static mesh - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); - // Update the input object since a new StaticMeshComponent could have been created - UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) - { - InObject->Update(InputObject); - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - } - } - else if (!bPendingDeleteOrRebuild && !bInvalidState) - { - // Request a cook with no proxy output - HAC->MarkAsNeedCook(); - HAC->SetNoProxyMeshNextCookRequested(true); - } - } - else if (InObject->GetActorComponents().Num() == 0 && HAC->HasAnyOutputComponent()) - { - // The HAC has non-proxy output components, but the InObject does not have any - // actor components. This can arise after a cook if previously there were only - // proxies and the input was created when there were only proxies - // Try to update the input to find new components - UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) - { - InObject->Update(InputObject); - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - } - } - } - } - - // Now, commit all of this actor's component - int32 ComponentIdx = 0; - for (UHoudiniInputSceneComponent* CurComponent : InObject->GetActorComponents()) - { - if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) - ComponentIdx++; - } - - // TODO: We should call Update here... - // needs to be fixed - - // Cache our transformn - InObject->Transform = Actor->GetTransform(); - - // Do something for our actor's transform? - /* - // TODO - // Support this type of input object - FString ObjNodeName = InInput->GetNodeBaseName(); - return HapiCreateInputNodeForObject(ObjNodeName, InObject); - */ - - //TODO? Check - // return true if we have at least uploaded one component - // return (ComponentIdx > 0); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( - const FString& InObjNodeName, UHoudiniInputLandscape* InObject, UHoudiniInput* InInput) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - if (!InInput || InInput->IsPendingKill()) - return false; - - ALandscapeProxy* Landscape = InObject->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) - return true; - - EHoudiniLandscapeExportType ExportType = InInput->GetLandscapeExportType(); - - bool bSucess = false; - if (ExportType == EHoudiniLandscapeExportType::Heightfield) - { - // Ensure we destroy any (Houdini) input nodes before clobbering this object with a new heightfield. - //DestroyInputNodes(InInput, InInput->GetInputType()); - bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); - } - else - { - bool bExportLighting = InInput->bLandscapeExportLighting; - bool bExportMaterials = InInput->bLandscapeExportMaterials; - bool bExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; - bool bExportTileUVs = InInput->bLandscapeExportTileUVs; - bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; - bool bExportAsMesh = InInput->LandscapeExportType == EHoudiniLandscapeExportType::Mesh; - - bSucess = FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( - Landscape, InObject->InputNodeId, InObjNodeName, - bExportAsMesh, bExportTileUVs, bExportNormalizedUVs, bExportLighting, bExportMaterials); - } - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - InObject->Update(Landscape); - - return bSucess; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForBrush(const FString& InObjNodeName, UHoudiniInputBrush* InObject, TArray* ExcludeActors) -{ - if (!IsValid(InObject)) - return false; - - ABrush* BrushActor = InObject->GetBrush(); - if (!IsValid(BrushActor)) - return true; - - if (!FUnrealBrushTranslator::CreateInputNodeForBrush(InObject, BrushActor, ExcludeActors, InObject->InputNodeId, InObjNodeName)) - return false; - - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - InObject->Update(BrushActor); - - return true; -} - - - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, UHoudiniInputCameraComponent* InInputObject) -{ - if (!InInputObject || InInputObject->IsPendingKill()) - return false; - - UCameraComponent* Camera = InInputObject->GetCameraComponent(); - if (!Camera || Camera->IsPendingKill()) - return true; - - FString NodeName = InNodeName + TEXT("_") + Camera->GetName(); - - // Create the camera OBJ. - int32 CameraNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( - -1, TEXT("Object/cam"), InNodeName, true, &CameraNodeId), false); - - // set "Pixel Aspect Ratio" (aspect) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "aspect", 0, InInputObject->AspectRatio), false); - - // set "Projection" (projection) (0 persp, 1 ortho) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "projection", 0, InInputObject->bIsOrthographic ? 1 : 0), false); - - // set Ortho Width (orthowidth) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "orthowidth", 0, InInputObject->OrthoWidth), false); - - // set Near Clippin (near) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "near", 0, InInputObject->OrthoNearClipPlane), false); - - // set far clipping (far) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), CameraNodeId, "far", 0, InInputObject->OrthoFarClipPlane), false); - - // Set the transform - HAPI_TransformEuler H_Transform; - FHoudiniApi::TransformEuler_Init(&H_Transform); - FHoudiniEngineUtils::TranslateUnrealTransform(Camera->GetComponentTransform(), H_Transform); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CameraNodeId, &H_Transform), false); - - // Update the component's transform - FTransform ComponentTransform = InInputObject->Transform; - if (!ComponentTransform.Equals(FTransform::Identity)) - { - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); - - // Camera orientation need to be adjusted - HapiTransform.rotationEuler[1] += -90.0f; - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), CameraNodeId, &HapiTransform), false); - } - - // Update this input's NodeId and ObjectNodeId - InInputObject->InputNodeId = -1;// (int32)CameraNodeId; - InInputObject->InputObjectNodeId = (int32)CameraNodeId; - - // Update this input object's cache data - InInputObject->Update(Camera); - - return true; -} - -bool -FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // We need to call BuildAllInputs here to update all the inputs, - // and make sure that the object path parameter inputs' parameter ids are up to date - if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) - return false; - - // We need to update the AssetID stored on all the inputs - // and mark all the input objects for this input type as changed - int32 HACAssetId = HAC->GetAssetId(); - for (auto CurrentInput : HAC->Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // - CurrentInput->SetAssetNodeId(HACAssetId); - - // We need to delete the nodes created for the input objects if they are valid - // (since the node IDs are transients, this likely means we're handling a recook/rebuild - // and therefore expect to recreate the input nodes) - DestroyInputNodes(CurrentInput, CurrentInput->GetInputType()); - } - - return true; -} - - - -bool -FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Only tick/cook when in Editor - // This prevents PIE cooks or runtime cooks due to inputs moving - AActor* ActorOwner = HAC->GetOwner(); - if (ActorOwner) - { - if (!ActorOwner->GetWorld() || (ActorOwner->GetWorld()->WorldType != EWorldType::Editor)) - return false; - } - -#if WITH_EDITOR - // Stop outliner objects from causing recooks while input objects are dragged around - if (FHoudiniMoveTracker::Get().IsObjectMoving) - { - //HOUDINI_LOG_MESSAGE(TEXT("Object moving, not updating world inputs!")); - return false; - } -#endif - - for (auto CurrentInput : HAC->Inputs) - { - if (!CurrentInput) - continue; - if (CurrentInput->GetInputType() != EHoudiniInputType::World) - continue; - - UpdateWorldInput(CurrentInput); - } - - return true; -} - -bool -FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) -{ - if (!InInput || InInput->IsPendingKill()) - return false; - - if (InInput->GetInputType() != EHoudiniInputType::World) - return false; - - TArray* InputObjectsPtr = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjectsPtr) - return false; - - bool bHasChanged = false; - if (InInput->IsWorldInputBoundSelector() && InInput->GetWorldInputBoundSelectorAutoUpdates()) - { - // If the input is in bound selector mode, and auto-update is enabled - // update the actors selected by the bounds first - bHasChanged = InInput->UpdateWorldSelectionFromBoundSelectors(); - } - - // See if we need to update the components for this input - // look for deleted actors/components - TArray ObjectToDeleteIndices; - for(int32 InputObjIdx = 0; InputObjIdx < InputObjectsPtr->Num(); InputObjIdx++) - { - UHoudiniInputActor* ActorObject = Cast((*InputObjectsPtr)[InputObjIdx]); - if (!ActorObject || ActorObject->IsPendingKill()) - continue; - - // Make sure the actor is still valid - AActor* const Actor = ActorObject->GetActor(); - bool bValidActorObject = Actor && !Actor->IsPendingKill(); - - // For BrushActors, the brush and actors must be valid as well - UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); - if (bValidActorObject && BrushActorObject) - { - ABrush* BrushActor = BrushActorObject->GetBrush(); - if (!IsValid(BrushActor)) - bValidActorObject = false; - else if (!IsValid(BrushActor->Brush)) - bValidActorObject = false; - } - - // The actor is no longer valid, mark it for deletion - if (!bValidActorObject) - { - if ((ActorObject->InputNodeId > 0) || (ActorObject->InputObjectNodeId > 0)) - { - ActorObject->InvalidateData(); - // We only need to update the input if the actors nodes were created in Houdini - bHasChanged = true; - } - - // Delete the Actor object - ObjectToDeleteIndices.Add(InputObjIdx); - continue; - } - - if (ActorObject->HasActorTransformChanged()) - { - ActorObject->MarkTransformChanged(true); - bHasChanged = true; - } - - if (ActorObject->HasContentChanged()) - { - ActorObject->MarkChanged(true); - bHasChanged = true; - } - - // Ensure we are aware of all the components of the actor - ActorObject->Update(Actor); - - // Check if any components have content or transform changes - for (auto CurActorComp : ActorObject->GetActorComponents()) - { - if (CurActorComp->HasComponentTransformChanged()) - { - CurActorComp->MarkTransformChanged(true); - bHasChanged = true; - } - - if (CurActorComp->HasComponentChanged()) - { - CurActorComp->MarkChanged(true); - bHasChanged = true; - } - } - - // Check if we added/removed any components in the call to update - if (ActorObject->GetLastUpdateNumComponentsAdded() > 0 || ActorObject->GetLastUpdateNumComponentsRemoved() > 0) - { - bHasChanged = true; - if (ActorObject->GetLastUpdateNumComponentsRemoved() > 0) - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - } - } - - // Delete the actor objects that were marked for deletion - for (int32 ToDeleteIdx = ObjectToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) - InputObjectsPtr->RemoveAt(ObjectToDeleteIndices[ToDeleteIdx]); - - // Mark the input as changed if need so it will trigger an upload - if (bHasChanged) - InInput->MarkChanged(true); - - return true; -} - - -bool -FHoudiniInputTranslator::CreateInputNodeForReference( - HAPI_NodeId& InputNodeId, - const FString& InRef, - const FString& InputNodeName, - const FTransform& InTransform) -{ - HAPI_NodeId NewNodeId = -1; - - // Create a single input node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_UTF8(*InputNodeName)), false); - - /* - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, nullptr), false); - */ - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) - return false; - - // We have now created a valid new input node, delete the previous one - HAPI_NodeId PreviousInputNodeId = InputNodeId; - if (PreviousInputNodeId >= 0) - { - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); - } - } - - // Create and initialize a part containing one point with a point attribute - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - - PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = 1; - PartInfo.vertexCount = 0; - PartInfo.faceCount = 0; - PartInfo.pointCount = 1; - PartInfo.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, &PartInfo), false); - - // Point Position Attribute - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - FVector ObjectPosition = InTransform.GetLocation(); - TArray Position = - { - ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION - }; - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Position.GetData(), 0, - AttributeInfoPoint.count), false); - } - - // String Attribute - { - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - - AttributeInfoPoint.count = 1; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NewNodeId, 0, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint), false); - - // Set string attribute - std::string AttriString = TCHAR_TO_ANSI(*InRef); - const char* AttriStringRaw = AttriString.c_str(); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NewNodeId, 0, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint, - &AttriStringRaw, 0, 1), false); - } - - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NewNodeId), false); - - InputNodeId = NewNodeId; - return true; -} - - - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeName, UHoudiniInputDataTable* InInputObject) -{ - //TODO - if (!InInputObject || InInputObject->IsPendingKill()) - return false; - - UDataTable* DataTable = InInputObject->GetDataTable(); - if (!DataTable || DataTable->IsPendingKill()) - return true; - - // Get the DataTable data as string - TArray> TableData = DataTable->GetTableData(EDataTableExportFlags::None); - if (TableData.Num() <= 1) - return true; - - int32 NumRows = TableData.Num() - 1; - int32 NumAttributes = TableData[0].Num(); - if (NumRows <= 0 || NumAttributes <= 0) - return true; - - // Create the input node - FString NodeName = InNodeName + TEXT("_") + DataTable->GetName(); - HAPI_NodeId InputNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); - - // Update this input object's NodeId and ObjectNodeId - InInputObject->InputNodeId = (int32)InputNodeId; - InInputObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = NumAttributes; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = NumRows; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); - - { - // Create point attribute info for P. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Set the point's position - TArray Positions; - Positions.SetNum(NumRows * 3); - for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) - { - Positions[RowIdx * 3] = 0.0f; - Positions[RowIdx * 3 + 1] = (float)RowIdx; - Positions[RowIdx * 3 + 2] = 0.0f; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Positions.GetData(), 0, - AttributeInfoPoint.count), false); - } - - { - // Create point attribute info for the path. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); - - // Get the object path - FString ObjectPathName = DataTable->GetPathName(); - - // Create an array - TArray ObjectPaths; - ObjectPaths.Init(ObjectPathName, NumRows); - - // Set the point's path attribute - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - ObjectPaths, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); - } - - { - // Create point attribute info for data table RowTable class name - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = NumRows; - AttributeInfoPoint.tupleSize = 1; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, &AttributeInfoPoint), false); - - // Get the object path - FString RowStructName = DataTable->GetRowStructName().ToString(); - - // Create an array - TArray RowStructNames; - RowStructNames.Init(RowStructName, NumRows); - - // Set the point's path attribute - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - RowStructNames, InputNodeId, 0, - HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, AttributeInfoPoint), false); - } - - // Now set the attributes values for each "point" of the data table - for (int32 ColIdx = 0; ColIdx < NumAttributes; ColIdx++) - { - // attribute name is "unreal_data_table_COL_NAME" - FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + TableData[0][ColIdx]; - - // We need to gt all values for that attribute - TArray AttributeValues; - AttributeValues.SetNum(NumRows); - for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) - { - AttributeValues[RowIdx] = TableData[RowIdx + 1][ColIdx]; - } - - // Create a point attribute info - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = NumRows; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_POINT; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), InputNodeId, 0, - TCHAR_TO_ANSI(*CurAttrName), &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( - AttributeValues, InputNodeId, 0, - CurAttrName, AttributeInfo), false); - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InputNodeId), false); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr), false); - - return true; -} - -bool -FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - const FString& InObjNodeName, - UHoudiniInputFoliageType_InstancedStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference) -{ - if (!IsValid(InObject)) - return false; - - FString FTName = InObjNodeName + TEXT("_"); - - UFoliageType_InstancedStaticMesh* FoliageType = Cast(InObject->GetObject()); - if (!IsValid(FoliageType)) - return true; - - UStaticMesh* const SM = FoliageType->GetStaticMesh(); - if (!IsValid(SM)) - return true; - - FTName += FoliageType->GetName(); - - // Marshall the Static Mesh to Houdini - bool bSuccess = true; - - if (bImportAsReference) - { - // Start by getting the Object's full name - FString AssetReference; - AssetReference += SM->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - bSuccess = FUnrealFoliageTypeTranslator::CreateInputNodeForReference( - FoliageType, InObject->InputNodeId, AssetReference, FTName, InObject->Transform); - } - else - { - bSuccess = FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - FoliageType, InObject->InputNodeId, FTName, bExportLODs, bExportSockets, bExportColliders); - } - - InObject->SetImportAsReference(bImportAsReference); - - // Update this input object's OBJ NodeId - InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); - - // If the Input mesh has a Transform offset - const FTransform TransformOffset = InObject->Transform; - if (!TransformOffset.Equals(FTransform::Identity)) - { - // Updating the Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); - } - - return bSuccess; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputTranslator.h" + +#include "HoudiniInput.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInputObject.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniAssetActor.h" +#include "HoudiniOutputTranslator.h" +#include "UnrealBrushTranslator.h" +#include "UnrealSplineTranslator.h" +#include "UnrealMeshTranslator.h" +#include "UnrealInstanceTranslator.h" +#include "UnrealLandscapeTranslator.h" +#include "UnrealFoliageTypeTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "Engine/Brush.h" +#include "Engine/DataTable.h" +#include "Camera/CameraComponent.h" +#include "FoliageType_InstancedStaticMesh.h" + +#include "Engine/SimpleConstructionScript.h" +#include "Engine/SCS_Node.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Editor/UnrealEdEngine.h" + #include "UnrealEdGlobals.h" +#endif + +#include "HCsgUtils.h" + +#include "Async/Async.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#if WITH_EDITOR +// Allows checking of objects currently being dragged around +struct FHoudiniMoveTracker +{ + FHoudiniMoveTracker() : IsObjectMoving(false) + { + GEditor->OnBeginObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = true; }); + GEditor->OnEndObjectMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + + GEditor->OnBeginCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + GEditor->OnEndCameraMovement().AddLambda([this](UObject&) { IsObjectMoving = false; }); + } + static FHoudiniMoveTracker& Get() { static FHoudiniMoveTracker Instance; return Instance; } + + bool IsObjectMoving; +}; +#endif + +// +bool +FHoudiniInputTranslator::UpdateInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) + { + // Failed to create the inputs + return false; + } + + return true; +} + +bool +FHoudiniInputTranslator::BuildAllInputs( + const HAPI_NodeId& AssetId, + class UObject* InOuterObject, + TArray& Inputs, + TArray& Parameters) +{ + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Start by getting the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // Get the number of geo (SOP) inputs + int32 InputCount = AssetInfo.geoInputCount; + /* + // It's best to update the input count even if the hda hasnt cooked + // as it can cause loaded geo inputs to disappear upon loading the level + if ( AssetInfo.hasEverCooked ) + { + InputCount = AssetInfo.geoInputCount; + } + */ + // Also look for object path parameters inputs + + // Helper map to get the parameter index, given the parameter name + TMap ParameterNameToIndexMap; + + TArray> InputParameters; + for (auto Param : Parameters) + { + if (Param->GetParameterType() == EHoudiniParameterType::Input) + { + int InsertionIndex = InputParameters.Num(); + ParameterNameToIndexMap.Add(Param->GetParameterName(), InsertionIndex); + InputParameters.Add(Param); + } + } + + InputCount += InputParameters.Num(); + + // Append new inputs as needed + if (InputCount > Inputs.Num()) + { + int32 NumNewInputs = InputCount - Inputs.Num(); + for (int32 InputIdx = Inputs.Num(); InputIdx < InputCount; ++InputIdx) + { + FString InputObjectName = TEXT("Input") + FString::FromInt(InputIdx + 1); + UHoudiniInput * NewInput = NewObject< UHoudiniInput >( + InOuterObject, + UHoudiniInput::StaticClass(), + FName(*InputObjectName), + RF_Transactional); + + if (!NewInput || NewInput->IsPendingKill()) + { + //HOUDINI_LOG_WARNING("Failed to create asset input"); + continue; + } + // Create a default curve object here to avoid Transaction issue + //NewInput->CreateDefaultCurveInputObject(); + + Inputs.Add(NewInput); + } + } + else if (InputCount < Inputs.Num()) + { + // TODO: Properly clean up the input object + created nodes? + for (int32 InputIdx = Inputs.Num() - 1; InputIdx >= InputCount; InputIdx--) + { + UHoudiniInput* CurrentInput = Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + FHoudiniInputTranslator::DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); + + // DO NOT MANUALLY DESTROY THE OLD/DANGLING INPUTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + //CurrentInput->ConditionalBeginDestroy(); + //CurrentInput = nullptr; + } + + Inputs.SetNum(InputCount); + } + + // Input index -> InputParameter index + // Special values: -1 = SOP input. Ignore completely. -2 = To be determined later + // Used to preserve inputs after insertion/deletion + TArray InputIdxToInputParamIndex; + InputIdxToInputParamIndex.SetNum(Inputs.Num()); + + // Keep a set of used indices, to figure out the unused indices later + TSet UsedParameterIndices; + + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) + { + // SOP input -> Parameter map doesn't make sense - ignore this + if (InputIdx < AssetInfo.geoInputCount) + { + // Ignore completely + InputIdxToInputParamIndex[InputIdx] = -1; + } + else + { + UHoudiniInput* CurrentInput = Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (ParameterNameToIndexMap.Contains(CurrentInput->GetName())) + { + const int32 ParameterIndex = ParameterNameToIndexMap[CurrentInput->GetName()]; + InputIdxToInputParamIndex[InputIdx] = ParameterIndex; + UsedParameterIndices.Add(ParameterIndex); + } + else + { + // To be determined in the second pass + InputIdxToInputParamIndex[InputIdx] = -2; + } + } + } + + // Second pass for InputIdxToInputParamIndex + // Fill in the inputs that could not be mapped onto old inputs. Used when inserting a new element. + for (int32 NewInputIndex = 0; NewInputIndex < Inputs.Num(); NewInputIndex++) + { + if (InputIdxToInputParamIndex[NewInputIndex] == -2) + { + // Find the first free index + for (int32 FreeIdx = 0; FreeIdx < InputParameters.Num(); FreeIdx++) + { + if (!UsedParameterIndices.Contains(FreeIdx)) + { + InputIdxToInputParamIndex[NewInputIndex] = FreeIdx; + UsedParameterIndices.Add(FreeIdx); + break; + } + } + } + } + + // Now, check the inputs in the array match the geo inputs + //for (int32 GeoInIdx = 0; GeoInIdx < AssetInfo.geoInputCount; GeoInIdx++) + bool bBlueprintStructureChanged = false; + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) + { + UHoudiniInput* CurrentInput = Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // Create default Name/Label/Help + FString CurrentInputName = TEXT("Input") + FString::FromInt(InputIdx + 1); + FString CurrentInputLabel = CurrentInputName; + FString CurrentInputHelp; + + // Set the nodeId + CurrentInput->SetAssetNodeId(AssetId); + + // Is this an object path parameter input? + bool bIsObjectPath = InputIdx >= AssetInfo.geoInputCount; + if (!bIsObjectPath) + { + // Mark this input as a SOP input + CurrentInput->SetSOPInput(InputIdx); + + // Get and set the name + HAPI_StringHandle InputStringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodeInputName( + FHoudiniEngine::Get().GetSession(), + AssetId, InputIdx, &InputStringHandle)) + { + FHoudiniEngineString HoudiniEngineString(InputStringHandle); + HoudiniEngineString.ToFString(CurrentInputLabel); + } + } + else + { + // Get this input's parameter index in the objpath param array + int32 CurrentParmIdx = InputIdxToInputParamIndex[InputIdx]; + + UHoudiniParameter* CurrentParm = nullptr; + if (InputParameters.IsValidIndex(CurrentParmIdx)) + { + if (InputParameters[CurrentParmIdx].IsValid()) + CurrentParm = InputParameters[CurrentParmIdx].Get(); + } + + int32 ParmId = -1; + if (CurrentParm && !CurrentParm->IsPendingKill()) + { + ParmId = CurrentParm->GetParmId(); + CurrentInputName = CurrentParm->GetParameterName(); + CurrentInputLabel = CurrentParm->GetParameterLabel(); + CurrentInputHelp = CurrentParm->GetParameterHelp(); + } + + UHoudiniParameterOperatorPath* CurrentObjPathParm = Cast(CurrentParm); + if (CurrentObjPathParm && !CurrentObjPathParm->IsPendingKill()) + { + CurrentObjPathParm->HoudiniInput = CurrentInput; + } + + // Mark this input as an object path parameter input + CurrentInput->SetObjectPathParameter(ParmId); + } + + CurrentInput->SetName(CurrentInputName); + CurrentInput->SetLabel(CurrentInputLabel); + + if ( CurrentInputHelp.IsEmpty() ) + { + CurrentInputHelp = CurrentInputLabel + TEXT("(") + CurrentInputName + TEXT(")"); + } + CurrentInput->SetHelp(CurrentInputHelp); + + // If the input type is invalid, + // We need to initialize its default + if (CurrentInput->GetInputType() == EHoudiniInputType::Invalid) + { + // Initialize it to the default corresponding to its name + CurrentInput->SetInputType(GetDefaultInputTypeFromLabel(CurrentInputLabel), bBlueprintStructureChanged); + + // Preset the default HDA for objpath input + SetDefaultAssetFromHDA(CurrentInput, bBlueprintStructureChanged); + } + + // Update input objects data on UE side for all types of inputs. + switch (CurrentInput->GetInputType()) + { + case EHoudiniInputType::Curve: + FHoudiniSplineTranslator::UpdateHoudiniInputCurves(CurrentInput); + break; + case EHoudiniInputType::Landscape: + //FUnrealLandscapeTranslator::UpdateHoudiniInputLandscapes(CurrentInput); + break; + case EHoudiniInputType::Asset: + break; + case EHoudiniInputType::Geometry: + break; + case EHoudiniInputType::Skeletal: + break; + case EHoudiniInputType::World: + break; + default: + break; + } + } + + return true; +} + +bool +FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + if (!InputToDestroy || InputToDestroy->IsPendingKill()) + return false; + + // Start by disconnecting the input / nullifying the object path parameter + if (InputToDestroy->IsObjectPathParameter()) + { + // Just set the objpath parameter to null + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + InputToDestroy->GetAssetNodeId(), "", + InputToDestroy->GetParameterId(), 0); + } + else + { + // Get the asset / created input node ID + HAPI_NodeId HostAssetId = InputToDestroy->GetAssetNodeId(); + HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); + + // Only disconnect if both are valid + if (HostAssetId >= 0 && CreatedInputId >= 0) + { + FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), + HostAssetId, InputToDestroy->GetInputIndex()); + } + } + + if (InputType == EHoudiniInputType::Asset) + { + // TODO: + // If we're an asset input, just remove us from the downstream connection on the input HDA + // then reset this input's flag + + // TODO: Check this? Clean our DS assets?? why?? likely uneeded + UHoudiniAssetComponent* OuterHAC = Cast(InputToDestroy->GetOuter()); + if (OuterHAC) + OuterHAC->ClearDownstreamHoudiniAsset(); + + InputToDestroy->SetInputNodeId(-1); + } + + return true; +} + +bool +FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + if (!InputToDestroy || InputToDestroy->IsPendingKill()) + return false; + + if (!InputToDestroy->CanDeleteHoudiniNodes()) + return false; + + // If we're destroying an asset input, don't destroy anything as we don't want to destroy the input HDA + // a simple disconnect is sufficient + if (InputType == EHoudiniInputType::Asset) + return true; + + // Destroy the nodes created by all the input objects + TArray CreatedInputDataAssetIds = InputToDestroy->GetCreatedDataNodeIds(); + TArray* InputObjectNodes = InputToDestroy->GetHoudiniInputObjectArray(InputType); + if (InputObjectNodes) + { + for (auto CurInputObject : *InputObjectNodes) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + if (CurInputObject->Type == EHoudiniInputObjectType::HoudiniAssetComponent) + { + // Remove this input object's node Id from the + // CreatedInputDataAssetIds array to avoid its deletion further down + CreatedInputDataAssetIds.Remove(CurInputObject->InputNodeId); + CurInputObject->InputNodeId = -1; + CurInputObject->InputObjectNodeId = -1; + continue; + } + + // For Actor input objects, set the input node id for all component objects to -1, + if (CurInputObject->Type == EHoudiniInputObjectType::Actor) + { + UHoudiniInputActor* CurActorInputObject = Cast(CurInputObject); + if (CurActorInputObject) + { + for (auto & CurActorComponent : CurActorInputObject->GetActorComponents()) + { + if (!CurActorComponent || CurActorComponent->IsPendingKill()) + continue; + + // No need to delete the nodes created for an asset component manually here, + // As they will be deleted when we clean up the CreateNodeIds array + CurActorComponent->InputNodeId = -1; + } + } + } + // No need to delete the nodes created for an asset component manually here, + // As they will be deleted when we clean up the CreateNodeIds array + + if (CurInputObject->InputNodeId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputNodeId); + CurInputObject->InputNodeId = -1; + } + + if(CurInputObject->InputObjectNodeId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CurInputObject->InputObjectNodeId); + CurInputObject->InputObjectNodeId = -1; + + // TODO: CHECK ME! + HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CurInputObject->InputObjectNodeId); + + // Delete its parent node as well + if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentNodeId)) + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentNodeId); + } + + // Also directly invalidate HoudiniSplineComponent's node IDs. + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast(CurInputObject); + if (IsValid(HoudiniSplineInputObject) && !IsGarbageCollecting()) + { + UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (SplineComponent && !SplineComponent->IsPendingKill()) + { + SplineComponent->SetNodeId(-1); + } + } + + CurInputObject->MarkChanged(true); + } + } + + // Destroy all the input assets + for (HAPI_NodeId AssetNodeId : CreatedInputDataAssetIds) + { + if (AssetNodeId < 0) + continue; + + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), AssetNodeId); + } + CreatedInputDataAssetIds.Empty(); + + // Then simply destroy the input's parent OBJ node + if (InputToDestroy->GetInputNodeId() >= 0) + { + HAPI_NodeId CreatedInputId = InputToDestroy->GetInputNodeId(); + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedInputId); + + if (CreatedInputId >= 0) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), CreatedInputId); + InputToDestroy->SetInputNodeId(-1); + } + + if (FHoudiniEngineUtils::IsHoudiniNodeValid(ParentId)) + { + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), ParentId); + } + } + + return true; +} + +bool +FHoudiniInputTranslator::DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) +{ + // Start by disconnecting the input/object merge + bool bSuccess = DisconnectInput(InputToDestroy, InputType); + + // Then destroy the created input nodes + bSuccess &= DestroyInputNodes(InputToDestroy, InputType); + + return bSuccess; +} + + +EHoudiniInputType +FHoudiniInputTranslator::GetDefaultInputTypeFromLabel(const FString& InputName) +{ + // We'll try to find these magic words to try to detect the default input type + //FString geoPrefix = TEXT("geo"); + FString curvePrefix = TEXT("curve"); + + FString landscapePrefix = TEXT("landscape"); + FString landscapePrefix2 = TEXT("terrain"); + FString landscapePrefix3 = TEXT("heightfield"); + + FString worldPrefix = TEXT("world"); + FString worldPrefix2 = TEXT("outliner"); + + FString assetPrefix = TEXT("asset"); + FString assetPrefix2 = TEXT("hda"); + + // By default, geometry input is chosen. + EHoudiniInputType InputType = EHoudiniInputType::Geometry; + + if (InputName.Contains(curvePrefix, ESearchCase::IgnoreCase)) + InputType = EHoudiniInputType::Curve; + + else if ((InputName.Contains(landscapePrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(landscapePrefix2, ESearchCase::IgnoreCase)) + || (InputName.Contains(landscapePrefix3, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::Landscape; + + else if ((InputName.Contains(worldPrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(worldPrefix2, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::World; + + else if ((InputName.Contains(assetPrefix, ESearchCase::IgnoreCase)) + || (InputName.Contains(assetPrefix2, ESearchCase::IgnoreCase))) + InputType = EHoudiniInputType::Asset; + + return InputType; +} + +bool +FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bForce) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (!InInput->HasInputTypeChanged() && !bForce) + return true; + + // - Handle switching AWAY from an input type + DisconnectAndDestroyInput(InInput, InInput->GetPreviousInputType()); + + // Invalidate the previous input type now that we've actually changed + //InInput->SetPreviousInputType(EHoudiniInputType::Invalid); + + //ChangeInputType(InInput, NewType); + + // TODO: + // - Handle updating to the new input type + // downstream asset connection, static mesh update, curve creation... + + // Mark all the objects from this input has changed so they upload themselves + InInput->MarkAllInputObjectsChanged(true); + + return true; +} + +bool +FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified) +{ + // + if (!Input || Input->IsPendingKill()) + return false; + + // Make sure we're linked to a valid object path parameter + if (Input->GetParameterId() < 0) + return false; + + // Get our ParmInfo + HAPI_ParmInfo FoundParamInfo; + FHoudiniApi::ParmInfo_Init(&FoundParamInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmInfo( + FHoudiniEngine::Get().GetSession(), + Input->GetAssetNodeId(), Input->GetParameterId(), &FoundParamInfo)) + { + return false; + } + + // Get our string value + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + Input->GetAssetNodeId(), + false, + &StringHandle, + FoundParamInfo.stringValuesIndex, + 1)) + { + return false; + } + + FString ParamValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (!HoudiniEngineString.ToFString(ParamValue)) + { + return false; + } + + if (ParamValue.Len() <= 0) + { + return false; + } + + // Chop the default value using semi-colons as separators + TArray Tokens; + ParamValue.ParseIntoArray(Tokens, TEXT(";"), true); + + // Start by setting geometry input objects + int32 GeoIdx = 0; + for (auto& CurToken : Tokens) + { + if (CurToken.IsEmpty()) + continue; + + // Set default objects on the HDA instance - will override the parameter string + // and apply the object input local-path thing for the HDA cook. + UObject * pObject = LoadObject(nullptr, *CurToken); + if (!pObject) + continue; + + Input->SetInputObjectAt(EHoudiniInputType::Geometry, GeoIdx++, pObject); + } + + // See if we can preset world objects as well + int32 WorldIdx = 0; + int32 LandscapedIdx = 0; + int32 HDAIdx = 0; + for (TActorIterator ActorIt(Input->GetWorld(), AActor::StaticClass(), EActorIteratorFlags::SkipPendingKill); ActorIt; ++ActorIt) + { + AActor* CurActor = *ActorIt; + if (!CurActor) + continue; + + AActor* FoundActor = nullptr; + int32 FoundIdx = Tokens.Find(CurActor->GetFName().ToString()); + if (FoundIdx == INDEX_NONE) + FoundIdx = Tokens.Find(CurActor->GetActorLabel()); + + if(FoundIdx != INDEX_NONE) + FoundActor = CurActor; + + if (!FoundActor) + continue; + + // Select the found actor in the world input + Input->SetInputObjectAt(EHoudiniInputType::World, WorldIdx++, FoundActor); + + if (FoundActor->IsA()) + { + // Select the HDA in the asset input + Input->SetInputObjectAt(EHoudiniInputType::Asset, HDAIdx++, FoundActor); + } + else if (FoundActor->IsA()) + { + // Select the landscape in the landscape input + Input->SetInputObjectAt(EHoudiniInputType::Landscape, LandscapedIdx++, FoundActor); + } + + // Remove the Found Token + Tokens.RemoveAt(FoundIdx); + } + + // See if we should change the default input type + if (Input->GetInputType() == EHoudiniInputType::Geometry && WorldIdx > 0 && GeoIdx == 0) + { + if (LandscapedIdx == WorldIdx) + { + // We've only selected landscapes, set to landscape IN + Input->SetInputType(EHoudiniInputType::Landscape, bOutBlueprintStructureModified); + } + else if (HDAIdx == WorldIdx) + { + // We've only selected Houdini Assets, set to Asset IN + Input->SetInputType(EHoudiniInputType::Asset, bOutBlueprintStructureModified); + } + else + { + // Set to world input + Input->SetInputType(EHoudiniInputType::World, bOutBlueprintStructureModified); + } + } + + return true; +} + +bool +FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + //for (auto CurrentInput : HAC->Inputs) + for(int32 InputIdx = 0; InputIdx < HAC->GetNumInputs(); InputIdx++) + { + UHoudiniInput*& CurrentInput = HAC->Inputs[InputIdx]; + if (!CurrentInput || CurrentInput->IsPendingKill() || !CurrentInput->HasChanged()) + continue; + + // First thing, see if we need to change the input type + if (CurrentInput->HasInputTypeChanged()) + { + ChangeInputType(CurrentInput, false); + } + + if (CurrentInput->GetInputType() == EHoudiniInputType::Landscape && CurrentInput->HasLandscapeExportTypeChanged()) + { + DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); + CurrentInput->MarkAllInputObjectsChanged(true); + CurrentInput->SetHasLandscapeExportTypeChanged(false); + } + + bool bSuccess = true; + if (CurrentInput->IsDataUploadNeeded()) + { + bSuccess &= UploadInputData(CurrentInput); + CurrentInput->MarkDataUploadNeeded(!bSuccess); + } + + if (CurrentInput->IsTransformUploadNeeded()) + { + bSuccess &= UploadInputTransform(CurrentInput); + } + + // Update the input properties AFTER eventually uploading it + bSuccess = UpdateInputProperties(CurrentInput); + + if (bSuccess) + { + CurrentInput->MarkChanged(false); + CurrentInput->MarkAllInputObjectsChanged(false); + } + + if (CurrentInput->HasInputTypeChanged()) + CurrentInput->SetPreviousInputType(EHoudiniInputType::Invalid); + + // Even if we failed, no need to try updating again. + CurrentInput->SetNeedsToTriggerUpdate(false); + } + + return true; +} + +bool +FHoudiniInputTranslator::UpdateInputProperties(UHoudiniInput* InInput) +{ + bool bSucess = UpdateTransformType(InInput); + + bSucess &= UpdatePackBeforeMerge(InInput); + + bSucess &= UpdateTransformOffset(InInput); + + return bSucess; +} + +bool +FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + bool nTransformType = InInput->GetKeepWorldTransform(); + + // Geometry inputs are always set to none + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType == EHoudiniInputType::Geometry) + nTransformType = 0; + + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); + + bool bSuccess = true; + const std::string sXformType = "xformtype"; + if (InInput->IsObjectPathParameter()) + { + // Directly change the Parameter xformtype + // (This will only work if the object merge is editable/unlocked) + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + HostAssetId, sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + else + { + // Query the object merge's node ID via the input + if (HAPI_RESULT_SUCCESS == FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + HostAssetId, InInput->GetInputIndex(), &InputNodeId)) + { + // Change its Parameter xformtype + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + InputNodeId, sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + } + + // Since our input objects are all plugged into a merge node + // We want to also update the transform type on the object merge plugged into the merge node + HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); + if ((ParentNodeId >= 0) && (InputType != EHoudiniInputType::Geometry) && (InputType != EHoudiniInputType::Asset)) + { + HAPI_NodeId InputObjectNodeId = -1; + int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); + for (int n = 0; n < NumberOfInputMeshes; n++) + { + // Get the Input node ID from the host ID + InputObjectNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ParentNodeId, n, &InputObjectNodeId)) + continue; + + if (InputObjectNodeId == -1) + continue; + + // Change the xformtype parameter on the object merge + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId, + sXformType.c_str(), 0, nTransformType)) + bSuccess = false; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + // Pack before merge is only available for Geo/World input + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType != EHoudiniInputType::World + && InputType != EHoudiniInputType::Geometry) + { + // Nothing to change + return true; + } + + uint32 nPackValue = InInput->GetPackBeforeMerge() ? 1 : 0; + + // Get the Input node ID from the host ID + HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); + + bool bSuccess = true; + const std::string sPack = "pack"; + + // We'll be going through each input object plugged in the input's merge node + // and change the pack parameter there + HAPI_NodeId ParentNodeId = InInput->GetInputNodeId(); + if (ParentNodeId >= 0) + { + HAPI_NodeId InputObjectNodeId = -1; + int32 NumberOfInputMeshes = InInput->GetNumberOfInputMeshes(InputType); + for (int n = 0; n < NumberOfInputMeshes; n++) + { + // Get the Input node ID from the host ID + InputObjectNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ParentNodeId, n, &InputObjectNodeId)) + continue; + + if (InputObjectNodeId == -1) + continue; + + // Change the pack parameter on the object merge + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId, + sPack.c_str(), 0, nPackValue)) + bSuccess = false; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + // Transform offsets are only for geometry inputs + EHoudiniInputType InputType = InInput->GetInputType(); + if (InputType != EHoudiniInputType::Geometry) + { + // Nothing to change + return true; + } + + // Get the input objects + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Update each object's transform offset + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + // If the Input mesh has a Transform offset + FTransform TransformOffset = CurrentInputObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CurrentInputObject->InputObjectNodeId, &HapiTransform), false); + } + } + + return true; +} + +bool +FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + EHoudiniInputType InputType = InInput->GetInputType(); + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Iterate on all the input objects and see if they need to be uploaded + bool bSuccess = true; + TArray CreatedNodeIds; + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; + if (!CurrentInputObject->HasChanged() && CurrentInputObjectNodeId >= 0) + { + // If this object hasn't changed, no need to upload it + // but we need to keep its created input node + if (CurrentInputObject->Type == EHoudiniInputObjectType::Actor) + { + // If this input object is an actor, it actually contains other input + // objects for each of his components, keep them as well + UHoudiniInputActor* InputActor = Cast(CurrentInputObject); + if (InputActor && !InputActor->IsPendingKill()) + { + for (auto CurrentComp : InputActor->GetActorComponents()) + { + if (!CurrentComp || CurrentComp->IsPendingKill()) + continue; + + int32& CurrentCompNodeId = CurrentComp->InputObjectNodeId; + if (!CurrentComp->HasChanged() && CurrentCompNodeId >= 0) + { + // If the component hasnt changed and is valid, keep it + CreatedNodeIds.Add(CurrentCompNodeId); + } + else + { + // Upload the component input object to Houdini + if (!UploadHoudiniInputObject(InInput, CurrentComp, CreatedNodeIds)) + bSuccess = false; + } + } + } + } + else + { + // No changes, keep it + CreatedNodeIds.Add(CurrentInputObjectNodeId); + } + } + else + { + // Upload the current input object to Houdini + if (!UploadHoudiniInputObject(InInput, CurrentInputObject, CreatedNodeIds)) + bSuccess = false; + } + } + + // If we haven't created any input, invalidate our input node id + if (CreatedNodeIds.Num() == 0) + { + if (!InInput->HasInputTypeChanged()) + { + int32 InputNodeId = InInput->GetInputNodeId(); + TArray PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); + + if (InInput->GetInputType() == EHoudiniInputType::Asset) + { + UHoudiniAssetComponent * OuterHAC = Cast(InInput->GetOuter()); + HAPI_NodeId AssetId = OuterHAC->GetAssetId(); + + // Disconnect the asset input + if (InputNodeId >= 0 && InInput->GetInputIndex() >= 0) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), AssetId, InInput->GetInputIndex())); + } + } + else if (InInput->GetInputType() == EHoudiniInputType::World) + { + // World nodes are handled by InputObjects () (with FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete) + } + else + { + if (InputNodeId >= 0) + { + for (int32 Idx = 0; Idx < PreviousInputObjectNodeIds.Num(); Idx++) + { + + // Get the object merge connected to the merge node + HAPI_NodeId InputObjectMergeId = -1; + HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); + + // Disconnect the two nodes + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); + + // Destroy the object merge node, do not delete other HDA (Asset input type) + HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); + } + } + } + } + InInput->GetCreatedDataNodeIds().Empty(); + InInput->SetInputNodeId(-1); + return bSuccess; + } + + // Get the current input's NodeId + HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); + // Check that the current input's node ID is still valid + if (InputNodeId < 0 || !FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + { + // This input doesn't have a valid NodeId yet, + // we need to create this input's merge node and update this input's node ID + FString MergeName = InInput->GetNodeBaseName() + TEXT("_Merge"); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/merge"), MergeName, true, &InputNodeId), false); + + InInput->SetInputNodeId(InputNodeId); + } + + //TODO: + // Do we want to update the input's transform? + if (false) + { + FTransform ComponentTransform = FTransform::Identity; + USceneComponent* OuterComp = Cast(InInput->GetOuter()); + if (OuterComp && !OuterComp->IsPendingKill()) + ComponentTransform = OuterComp->GetComponentTransform(); + + FHoudiniEngineUtils::HapiSetAssetTransform(InputNodeId, ComponentTransform); + //HapiUpdateInputNodeTransform(InputNodeId, ComponentTransform); + } + + // Connect all the input objects to the merge node now + int32 InputIndex = 0; + for (auto CurrentNodeId : CreatedNodeIds) + { + if (CurrentNodeId < 0) + continue; + + if (InputNodeId == CurrentNodeId) + continue; + + // Connect the current input object to the merge node + HOUDINI_CHECK_ERROR(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + InputNodeId, InputIndex++, CurrentNodeId, 0)); + } + + // Check if we need to disconnect extra input objects nodes from the merge + // This can be needed when the input had more input objects on the previous cook + TArray& PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); + if (!InInput->HasInputTypeChanged()) + { + for (int32 Idx = CreatedNodeIds.Num(); Idx < PreviousInputObjectNodeIds.Num(); Idx++) + { + // Get the object merge connected to the merge node + HAPI_NodeId InputObjectMergeId = -1; + if (InInput->GetInputType() != EHoudiniInputType::Asset) + HOUDINI_CHECK_ERROR(FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx, &InputObjectMergeId)); + + // Disconnect the two nodes + HOUDINI_CHECK_ERROR(FHoudiniApi::DisconnectNodeInput( + FHoudiniEngine::Get().GetSession(), InputNodeId, Idx)); + + // Destroy the object merge node, do not destroy other HDA (Asset input type) + if (InInput->GetInputType() != EHoudiniInputType::Asset) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), InputObjectMergeId)); + } + } + } + + // Keep track of all the nodes plugged into our input's merge + PreviousInputObjectNodeIds = CreatedNodeIds; + + // Finally, connect our main input node to the asset + bSuccess = ConnectInputNode(InInput); + + return bSuccess; +} + +bool +FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + EHoudiniInputType InputType = InInput->GetInputType(); + TArray* InputObjectsArray = InInput->GetHoudiniInputObjectArray(InInput->GetInputType()); + if (!ensure(InputObjectsArray)) + return false; + + // Iterate on all the input objects and see if their transform needs to be uploaded + bool bSuccess = true; + for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; + if (!CurrentInputObject->HasTransformChanged()) + continue; + + // Upload the current input object's transform to Houdini + if (!UploadHoudiniInputTransform(InInput, CurrentInputObject)) + { + bSuccess = false; + continue; + } + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::ConnectInputNode(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + HAPI_NodeId AssetNodeId = InInput->GetAssetNodeId(); + if (AssetNodeId < 0) + return false; + + HAPI_NodeId InputNodeId = InInput->GetInputNodeId(); + if (InputNodeId < 0) + return false; + + // Helper for connecting our input or setting the object path parameter + if (InInput->IsObjectPathParameter()) + { + // Now we can assign the input node path to the parameter + std::string ParamNameString = TCHAR_TO_UTF8(*(InInput->GetName())); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmNodeValue( + FHoudiniEngine::Get().GetSession(), AssetNodeId, + ParamNameString.c_str(), InputNodeId), false); + } + else + { + // TODO: CHECK ME! + //if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + // return false; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), AssetNodeId, + InInput->GetInputIndex(), InputNodeId, 0), false); + } + + return true; +} + +bool +FHoudiniInputTranslator::UploadHoudiniInputObject( + UHoudiniInput* InInput, + UHoudiniInputObject* InInputObject, + TArray& OutCreatedNodeIds) +{ + if (!InInput || !InInputObject) + return false; + + FString ObjBaseName = InInput->GetNodeBaseName(); + + bool bSuccess = true; + switch (InInputObject->Type) + { + case EHoudiniInputObjectType::Object: + { + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForObject(ObjBaseName, InInputObject); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::StaticMesh: + { + UHoudiniInputStaticMesh* InputSM = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( + ObjBaseName, InputSM, InInput->GetExportLODs(), InInput->GetExportSockets(), + InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + { + // If this SM input object takes in a BP, add all its BP StaticMesh components input object node id to the created id list. + if (InputSM->bIsBlueprint()) + { + for (auto & CurSMObj : InputSM->BlueprintStaticMeshes) + OutCreatedNodeIds.Add(CurSMObj->InputObjectNodeId); + } + else + { + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + } + } + + break; + } + + case EHoudiniInputObjectType::SkeletalMesh: + { + UHoudiniInputSkeletalMesh* InputSkelMes = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(ObjBaseName, InputSkelMes); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::SceneComponent: + { + UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(ObjBaseName, InputSceneComp); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::StaticMeshComponent: + { + UHoudiniInputMeshComponent* InputSMC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( + ObjBaseName, InputSMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + { + UHoudiniInputInstancedMeshComponent* InputISMC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( + ObjBaseName, InputISMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::SplineComponent: + { + UHoudiniInputSplineComponent* InputSpline = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(ObjBaseName, InputSpline, InInput->GetUnrealSplineResolution()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::HoudiniSplineComponent: + { + UHoudiniInputHoudiniSplineComponent* InputCurve = Cast(InInputObject); + + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent(ObjBaseName, InputCurve, InInput->IsAddRotAndScaleAttributesEnabled()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::HoudiniAssetActor: + case EHoudiniInputObjectType::HoudiniAssetComponent: + { + UHoudiniInputHoudiniAsset* InputHAC = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Actor: + { + UHoudiniInputActor* InputActor = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, OutCreatedNodeIds); + break; + } + + case EHoudiniInputObjectType::Landscape: + { + UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForLandscape(ObjBaseName, InputLandscape, InInput); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Brush: + { + UHoudiniInputBrush* InputBrush = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForBrush(ObjBaseName, InputBrush, InInput->GetBoundSelectorObjectArray()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::CameraComponent: + { + UHoudiniInputCameraComponent* InputCamera = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForCamera(ObjBaseName, InputCamera); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::DataTable: + { + UHoudiniInputDataTable* InputDT = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(ObjBaseName, InputDT); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + { + UHoudiniInputFoliageType_InstancedStaticMesh* const InputFoliageTypeSM = Cast(InInputObject); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + ObjBaseName, InputFoliageTypeSM, InInput->GetExportLODs(), InInput->GetExportSockets(), + InInput->GetExportColliders(), InInput->GetImportAsReference()); + + if (bSuccess) + OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); + + break; + } + + case EHoudiniInputObjectType::Invalid: + //default: + break; + } + + // Mark that input object as not changed + if (bSuccess) + { + InInputObject->MarkChanged(false); + InInputObject->SetNeedsToTriggerUpdate(false); + } + else + { + // We couldn't update/create that input object, keep it changed but prevent it from trigger updates + InInputObject->SetNeedsToTriggerUpdate(false); + } + + return bSuccess; +} + + +// Upload transform for an input's InputObject +bool +FHoudiniInputTranslator::UploadHoudiniInputTransform( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject) +{ + if (!InInput || !InInputObject) + return false; + + auto UpdateTransform = [](const FTransform& InTransform, const HAPI_NodeId& InNodeId) + { + // Translate the Transform to HAPI + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(InTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InNodeId, &HapiTransform), false); + + return true; + }; + + bool bSuccess = true; + switch (InInputObject->Type) + { + case EHoudiniInputObjectType::StaticMesh: + { + // Simply update the Input mesh's Transform offset + if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + break; + } + + case EHoudiniInputObjectType::StaticMeshComponent: + { + // Update using the static mesh component's transform + UHoudiniInputMeshComponent* InSMC = Cast(InInputObject); + if (!InSMC || InSMC->IsPendingKill()) + { + bSuccess = false; + break; + } + + FTransform NewTransform = InSMC->GetStaticMeshComponent() ? InSMC->GetStaticMeshComponent()->GetComponentTransform() : InInputObject->Transform; + if(!UpdateTransform(NewTransform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + // Update the InputObject's transform + InInputObject->Transform = NewTransform; + + break; + } + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + { + // TODO: Only update the instances transform + break; + } + + case EHoudiniInputObjectType::HoudiniSplineComponent: + { + // TODO: Simply update the curve's transform? + break; + } + + case EHoudiniInputObjectType::HoudiniAssetActor: + case EHoudiniInputObjectType::HoudiniAssetComponent: + { + // TODO: Check, nothing to do? + break; + } + + case EHoudiniInputObjectType::Actor: + { + UHoudiniInputActor* InputActor = Cast(InInputObject); + if (!InputActor || InputActor->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Update the actor's transform + // To avoid further updates + if (InputActor->GetActor()) + InputActor->Transform = InputActor->GetActor()->GetTransform(); + + // Iterate on all the actor input objects and see if their transform needs to be uploaded + // TODO? Also update the component's actor transform?? + for (auto& CurrentComponent : InputActor->GetActorComponents()) + { + if (!CurrentComponent || CurrentComponent->IsPendingKill()) + continue; + + if (!CurrentComponent->HasTransformChanged()) + continue; + + // Upload the current input object's transform to Houdini + if (!UploadHoudiniInputTransform(InInput, CurrentComponent)) + { + bSuccess = false; + continue; + } + } + break; + } + + case EHoudiniInputObjectType::SceneComponent: + { + UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); + if (!InputSceneComp || InputSceneComp->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Update the component transform to avoid further updates + if (InputSceneComp->GetSceneComponent()) + InputSceneComp->Transform = InputSceneComp->GetSceneComponent()->GetComponentTransform(); + + break; + } + + case EHoudiniInputObjectType::Landscape: + { + // + UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); + if (!InputLandscape || InputLandscape->IsPendingKill()) + { + bSuccess = false; + break; + } + + // + ALandscapeProxy* Landscape = InputLandscape->GetLandscapeProxy(); + if (!Landscape || Landscape->IsPendingKill()) + { + bSuccess = false; + break; + } + + // Only apply diff for landscape since the HF's transform is used for value conversion as well + FTransform CurrentTransform = InputLandscape->Transform; + FTransform NewTransform = Landscape->ActorToWorld(); + + // Only handle position/rotation differences + FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); + FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); + + // Now get the HF's current transform + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + + if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) + { + bSuccess = false; + break; + } + + // Convert it to unreal + FTransform HFTransform; + FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); + + // Apply the position offset if needed + if (!PosDiff.IsZero()) + HFTransform.AddToTranslation(PosDiff); + + // Apply the rotation offset if needed + if (!RotDiff.IsIdentity()) + HFTransform.ConcatenateRotation(RotDiff); + + // Convert back to a HAPI Transform and update the HF's transform + HAPI_TransformEuler NewHAPITransform; + FHoudiniApi::TransformEuler_Init(&NewHAPITransform); + FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); + NewHAPITransform.position[1] = 0.0f; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), + InputLandscape->InputObjectNodeId, &NewHAPITransform)) + { + bSuccess = false; + break; + } + + // Update the cached transform + InputLandscape->Transform = NewTransform; + } + + case EHoudiniInputObjectType::Brush: + { + // TODO: Update the Brush's transform + break; + } + + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + { + // Simply update the Input mesh's Transform offset + if (!UpdateTransform(InInputObject->Transform, InInputObject->InputObjectNodeId)) + bSuccess = false; + + break; + } + + // Unsupported + case EHoudiniInputObjectType::Object: + case EHoudiniInputObjectType::SkeletalMesh: + case EHoudiniInputObjectType::SplineComponent: + { + break; + } + + case EHoudiniInputObjectType::Invalid: + default: + break; + } + + // Mark that input object as not changed + if (bSuccess) + { + InInputObject->MarkTransformChanged(false); + InInputObject->SetNeedsToTriggerUpdate(false); + } + else + { + // We couldn't update/create that input object, keep it changed but prevent it from trigger updates + InInputObject->SetNeedsToTriggerUpdate(false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForObject(const FString& InObjNodeName, UHoudiniInputObject* InObject) +{ + if (!InObject) + return false; + + UObject* Object = InObject->GetObject(); + if (!Object || Object->IsPendingKill()) + return true; + + FString NodeName = InObjNodeName + TEXT("_") + Object->GetName(); + + // For UObjects we can't upload much, but can still create an input node + // with a single point, with an attribute pointing to the input object's path + HAPI_NodeId InputNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); + + // Update this input object's NodeId and ObjectNodeId + InObject->InputNodeId = (int32)InputNodeId; + InObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 2; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = 1; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); + + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + FVector ObjectPosition = InObject->Transform.GetLocation(); + TArray Position = + { + ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION + }; + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Position.GetData(), 0, + AttributeInfoPoint.count), false); + } + + { + // Create point attribute info for the path. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); + + // Set the point's path attribute + FString ObjectPathName = Object->GetPathName(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + ObjectPathName, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputNodeId), false); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( + const FString& InObjNodeName, + UHoudiniInputStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UBlueprint* BP = nullptr; + UStaticMesh* SM = nullptr; + + FString SMName = InObjNodeName + TEXT("_"); + + // Get Blueprint or StaticMesh + if (InObject->bIsBlueprint()) + { + BP = InObject->GetBlueprint(); + if (!BP || BP->IsPendingKill()) + return true; + + SMName += BP->GetName(); + } + else + { + SM = InObject->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + SMName += SM->GetName(); + } + + // Marshall the Static Mesh to Houdini + bool bSuccess = true; + + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference; + if (SM) + AssetReference += SM->GetFullName(); + + if (BP) + AssetReference += BP->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference( + InObject->InputNodeId, AssetReference, SMName, InObject->Transform); + } + else + { + TArray StaticMeshComponents; + + // The input object is a Blueprint, Get all its StaticMeshes + if (BP) + { + USimpleConstructionScript* SCS = BP->SimpleConstructionScript; + if (SCS && !SCS->IsPendingKill()) + { + const TArray& Nodes = SCS->GetAllNodes(); + for (auto & CurNode : Nodes) + { + if (!CurNode || CurNode->IsPendingKill()) + continue; + + UActorComponent * CurComp = CurNode->ComponentTemplate; + if (!CurComp || CurComp->IsPendingKill()) + continue; + + UStaticMeshComponent* CurSMC = Cast(CurComp); + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + UStaticMesh* CurSM = CurSMC->GetStaticMesh(); + if (CurSM && !CurSM->IsPendingKill()) + StaticMeshComponents.Add(CurSMC); + + } + } + } + + // Clear previous Blueprint Static Mesh Comps (if there is any) + InObject->BlueprintStaticMeshes.Empty(); + + // This is a BP, add all the BP SM comps to its BlueprintStaticMeshes list. + if (InObject->bIsBlueprint()) + { + for (auto & CurSMC : StaticMeshComponents) + { + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + UHoudiniInputStaticMesh* SMObject = Cast( + UHoudiniInputObject::CreateTypedInputObject(CurSMC->GetStaticMesh(), InObject, InObject->GetName() + TEXT("_") + CurSMC->GetName())); + + if (!SMObject || SMObject->IsPendingKill()) + continue; + + bSuccess &= FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + CurSMC->GetStaticMesh(), SMObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); + + InObject->SetImportAsReference(false); + + // Update this input object's OBJ NodeId + SMObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(SMObject->InputNodeId); + + // Update the component's transform + FTransform ComponentTransform = CurSMC->GetRelativeTransform(); + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), SMObject->InputObjectNodeId, &HapiTransform), false); + } + + InObject->BlueprintStaticMeshes.Add(SMObject); + } + + return true; + } + // This is a normal static mesh input, process it normally as a static mesh Input Object + else + { + bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, InObject->InputNodeId, SMName, nullptr, bExportLODs, bExportSockets, bExportColliders); + } + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // If the Input mesh has a Transform offset + FTransform TransformOffset = InObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USkeletalMesh* SkelMesh = InObject->GetSkeletalMesh(); + if (!SkelMesh || SkelMesh->IsPendingKill()) + return true; + + // Get the SM's transform offset + FTransform TransformOffset = InObject->Transform; + + // TODO + // Support this type of input object + // FUnrealMeshTranslator::CreateInputNodeForSkeletalMesh(...) + + return HapiCreateInputNodeForObject(InObjNodeName, InObject); +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USceneComponent* SceneComp = InObject->GetSceneComponent(); + if (!SceneComp || SceneComp->IsPendingKill()) + return true; + + // Get the Scene Component's transform + FTransform TransformOffset = InObject->Transform; + + // Get the parent Actor's transform + FTransform ParentTransform = InObject->ActorTransform; + + // Dont do that! + return false; + + // TODO + // Support this type of input object + return HapiCreateInputNodeForObject(InObjNodeName, InObject); +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UStaticMeshComponent* SMC = InObject->GetStaticMeshComponent(); + if (!SMC || SMC->IsPendingKill()) + return true; + + // Get the component's Static Mesh + UStaticMesh* SM = InObject->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + // Marshall the Static Mesh to Houdini + FString SMCName = InObjNodeName + TEXT("_") + SMC->GetName(); + + bool bSuccess = true; + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference = SM->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, AssetReference, SMCName, InObject->Transform); + + } + else + { + bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, InObject->InputNodeId, SMCName, SMC, bExportLODs, bExportSockets, bExportColliders); + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // Update this input object's cache data + InObject->Update(SMC); + + // Update the component's transform + FTransform ComponentTransform = InObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputInstancedMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UObject* Object = InObject->GetObject(); + if (!Object || Object->IsPendingKill()) + return true; + + // Get the ISMC + UInstancedStaticMeshComponent* ISMC = InObject->GetInstancedStaticMeshComponent(); + if (!ISMC || ISMC->IsPendingKill()) + return true; + + HAPI_NodeId NewNodeId = -1; + if (!FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( + ISMC, InObjNodeName, NewNodeId, bExportLODs, bExportSockets, bExportColliders, false)) + return false; + + // Update this input object's node IDs + InObject->InputNodeId = NewNodeId; + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); + + // Update the component's cached instances + InObject->Update(ISMC); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + USplineComponent* Spline = InObject->GetSplineComponent(); + if (!Spline || Spline->IsPendingKill()) + return true; + + + int32 NumberOfSplineControlPoints = InObject->NumberOfSplineControlPoints; + + TArray SplineControlPoints = InObject->SplineControlPoints; + + FString NodeName = InObjNodeName + TEXT("_") + InObject->GetName(); + + if (!FUnrealSplineTranslator::CreateInputNodeForSplineComponent(Spline, SplineResolution, InObject->InputNodeId, NodeName)) + return false; + + // Cache the exported curve's data to the input object + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + InObject->MarkChanged(true); + + //if (!HapiCreateInputNodeForObject(InObjNodeName, InObject)) + // return false; + + // Update the component's cached data + InObject->Update(Spline); + + // Update the component's transform + FTransform ComponentTransform = InObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject, bool bInAddRotAndScaleAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UHoudiniSplineComponent* Curve = InObject->GetCurveComponent(); + if (!Curve || Curve->IsPendingKill()) + return true; + + if (!FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(Curve, bInAddRotAndScaleAttributes)) + return false; + + // See if the component needs it node Id invalidated + //if (InObject->InputNodeId < 0) + // Curve->SetNodeId(InObject->InputNodeId); + + // Cache the exported curve's data to the input object + InObject->InputNodeId = Curve->GetNodeId(); + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + //InObject->CurveType = Curve->GetCurveType(); + //InObject->CurveMethod = Curve->GetCurveMethod(); + //InObject->Reversed = Curve->IsReversed(); + InObject->Update(Curve); + + InObject->MarkChanged(true); + + return true; +} + +bool +FHoudiniInputTranslator:: +HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + UHoudiniAssetComponent* InputHAC = InObject->GetHoudiniAssetComponent(); + if (!InputHAC || InputHAC->IsPendingKill()) + return true; + + if (!InputHAC->CanDeleteHoudiniNodes()) + return true; + + UHoudiniInput* HoudiniInput = Cast(InObject->GetOuter()); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + return true; + + UHoudiniAssetComponent* OuterHAC = Cast(HoudiniInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return true; + + // Do not allow using ourself as an input, terrible things would happen + if (InputHAC->GetAssetId() == OuterHAC->GetAssetId()) + return false; + + // If previously imported as ref, delete the input node. + if (InObject->InputNodeId > -1 && InObject->GetImportAsReference()) + { + int32 PreviousInputNodeId = InObject->InputNodeId; + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InObject->GetName()); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InObject->GetName()); + } + } + + InObject->SetImportAsReference(bImportAsReference); + + // If this object is in an Asset input, we need to set the InputNodeId directl + // to avoid creating extra merge nodes. World inputs should not do that! + bool bIsAssetInput = HoudiniInput->GetInputType() == EHoudiniInputType::Asset; + + if (bImportAsReference) + { + InObject->InputNodeId = -1; + InObject->InputObjectNodeId = -1; + + if(bIsAssetInput) + HoudiniInput->SetInputNodeId(-1); + + // Start by getting the Object's full name + FString AssetReference = InputHAC->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + if (!FHoudiniInputTranslator::CreateInputNodeForReference( + InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform)) // do not delete previous node if it was HAC + return false; + + if (bIsAssetInput) + HoudiniInput->SetInputNodeId(InObject->InputNodeId); + } + + InputHAC->AddDownstreamHoudiniAsset(OuterHAC); + + //if (HAC->NeedsInitialization()) + // HAC->MarkAsNeedInstantiation(); + + //HoudiniInput->SetAssetNodeId(HAC->GetAssetId()); + + // TODO: This might be uneeded as this function should only be called + // after we're not wiating on the input asset... + if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) + { + // If the input HAC needs to be instantiated, tell it do so + InputHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + // Mark this object's input as changed so we can properly update after the input HDA's done instantiating/cooking + HoudiniInput->MarkChanged(true); + } + + if (InputHAC->NeedsInitialization() || InputHAC->NeedUpdate()) + return false; + + if (!bImportAsReference) + { + if (bIsAssetInput) + HoudiniInput->SetInputNodeId(InputHAC->GetAssetId()); + + InObject->InputNodeId = InputHAC->GetAssetId(); + } + + InObject->InputObjectNodeId = InObject->InputNodeId; + + bool bReturn = InObject->InputNodeId > -1; + + if(bIsAssetInput) + bReturn = FHoudiniInputTranslator::ConnectInputNode(HoudiniInput); + + return bReturn; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForActor( + UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (!InObject || InObject->IsPendingKill()) + return false; + + AActor* Actor = InObject->GetActor(); + if (!Actor || Actor->IsPendingKill()) + return true; + + // Check if this is a world input and if this is a HoudiniAssetActor + // If so we need to build static meshes for any proxy meshes + if (InInput->GetInputType() == EHoudiniInputType::World && Actor->IsA()) + { + AHoudiniAssetActor *HAA = Cast(Actor); + UHoudiniAssetComponent *HAC = HAA->GetHoudiniAssetComponent(); + if (HAC && !HAC->IsPendingKill()) + { + if (HAC->HasAnyCurrentProxyOutput()) + { + bool bPendingDeleteOrRebuild = false; + bool bInvalidState = false; + const bool bIsHoudiniCookedDataAvailable = HAC->IsHoudiniCookedDataAvailable(bPendingDeleteOrRebuild, bInvalidState); + if (bIsHoudiniCookedDataAvailable) + { + // Build the static mesh + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); + // Update the input object since a new StaticMeshComponent could have been created + UObject *InputObject = InObject->GetObject(); + if (InputObject && !InputObject->IsPendingKill()) + { + InObject->Update(InputObject); + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } + } + else if (!bPendingDeleteOrRebuild && !bInvalidState) + { + // Request a cook with no proxy output + HAC->MarkAsNeedCook(); + HAC->SetNoProxyMeshNextCookRequested(true); + } + } + else if (InObject->GetActorComponents().Num() == 0 && HAC->HasAnyOutputComponent()) + { + // The HAC has non-proxy output components, but the InObject does not have any + // actor components. This can arise after a cook if previously there were only + // proxies and the input was created when there were only proxies + // Try to update the input to find new components + UObject *InputObject = InObject->GetObject(); + if (InputObject && !InputObject->IsPendingKill()) + { + InObject->Update(InputObject); + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } + } + } + } + + // Now, commit all of this actor's component + int32 ComponentIdx = 0; + for (UHoudiniInputSceneComponent* CurComponent : InObject->GetActorComponents()) + { + if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) + ComponentIdx++; + } + + // TODO: We should call Update here... + // needs to be fixed + + // Cache our transformn + InObject->Transform = Actor->GetTransform(); + + // Do something for our actor's transform? + /* + // TODO + // Support this type of input object + FString ObjNodeName = InInput->GetNodeBaseName(); + return HapiCreateInputNodeForObject(ObjNodeName, InObject); + */ + + //TODO? Check + // return true if we have at least uploaded one component + // return (ComponentIdx > 0); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( + const FString& InObjNodeName, UHoudiniInputLandscape* InObject, UHoudiniInput* InInput) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + if (!InInput || InInput->IsPendingKill()) + return false; + + ALandscapeProxy* Landscape = InObject->GetLandscapeProxy(); + if (!Landscape || Landscape->IsPendingKill()) + return true; + + EHoudiniLandscapeExportType ExportType = InInput->GetLandscapeExportType(); + + bool bSucess = false; + if (ExportType == EHoudiniLandscapeExportType::Heightfield) + { + // Ensure we destroy any (Houdini) input nodes before clobbering this object with a new heightfield. + //DestroyInputNodes(InInput, InInput->GetInputType()); + bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); + } + else + { + bool bExportLighting = InInput->bLandscapeExportLighting; + bool bExportMaterials = InInput->bLandscapeExportMaterials; + bool bExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; + bool bExportTileUVs = InInput->bLandscapeExportTileUVs; + bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; + bool bExportAsMesh = InInput->LandscapeExportType == EHoudiniLandscapeExportType::Mesh; + + bSucess = FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( + Landscape, InObject->InputNodeId, InObjNodeName, + bExportAsMesh, bExportTileUVs, bExportNormalizedUVs, bExportLighting, bExportMaterials); + } + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + InObject->Update(Landscape); + + return bSucess; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForBrush(const FString& InObjNodeName, UHoudiniInputBrush* InObject, TArray* ExcludeActors) +{ + if (!IsValid(InObject)) + return false; + + ABrush* BrushActor = InObject->GetBrush(); + if (!IsValid(BrushActor)) + return true; + + if (!FUnrealBrushTranslator::CreateInputNodeForBrush(InObject, BrushActor, ExcludeActors, InObject->InputNodeId, InObjNodeName)) + return false; + + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + InObject->Update(BrushActor); + + return true; +} + + + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, UHoudiniInputCameraComponent* InInputObject) +{ + if (!InInputObject || InInputObject->IsPendingKill()) + return false; + + UCameraComponent* Camera = InInputObject->GetCameraComponent(); + if (!Camera || Camera->IsPendingKill()) + return true; + + FString NodeName = InNodeName + TEXT("_") + Camera->GetName(); + + // Create the camera OBJ. + int32 CameraNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( + -1, TEXT("Object/cam"), InNodeName, true, &CameraNodeId), false); + + // set "Pixel Aspect Ratio" (aspect) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "aspect", 0, InInputObject->AspectRatio), false); + + // set "Projection" (projection) (0 persp, 1 ortho) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "projection", 0, InInputObject->bIsOrthographic ? 1 : 0), false); + + // set Ortho Width (orthowidth) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "orthowidth", 0, InInputObject->OrthoWidth), false); + + // set Near Clippin (near) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "near", 0, InInputObject->OrthoNearClipPlane), false); + + // set far clipping (far) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), CameraNodeId, "far", 0, InInputObject->OrthoFarClipPlane), false); + + // Set the transform + HAPI_TransformEuler H_Transform; + FHoudiniApi::TransformEuler_Init(&H_Transform); + FHoudiniEngineUtils::TranslateUnrealTransform(Camera->GetComponentTransform(), H_Transform); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CameraNodeId, &H_Transform), false); + + // Update the component's transform + FTransform ComponentTransform = InInputObject->Transform; + if (!ComponentTransform.Equals(FTransform::Identity)) + { + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ComponentTransform, HapiTransform); + + // Camera orientation need to be adjusted + HapiTransform.rotationEuler[1] += -90.0f; + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), CameraNodeId, &HapiTransform), false); + } + + // Update this input's NodeId and ObjectNodeId + InInputObject->InputNodeId = -1;// (int32)CameraNodeId; + InInputObject->InputObjectNodeId = (int32)CameraNodeId; + + // Update this input object's cache data + InInputObject->Update(Camera); + + return true; +} + +bool +FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // We need to call BuildAllInputs here to update all the inputs, + // and make sure that the object path parameter inputs' parameter ids are up to date + if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) + return false; + + // We need to update the AssetID stored on all the inputs + // and mark all the input objects for this input type as changed + int32 HACAssetId = HAC->GetAssetId(); + for (auto CurrentInput : HAC->Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // + CurrentInput->SetAssetNodeId(HACAssetId); + + // We need to delete the nodes created for the input objects if they are valid + // (since the node IDs are transients, this likely means we're handling a recook/rebuild + // and therefore expect to recreate the input nodes) + DestroyInputNodes(CurrentInput, CurrentInput->GetInputType()); + } + + return true; +} + + + +bool +FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Only tick/cook when in Editor + // This prevents PIE cooks or runtime cooks due to inputs moving + AActor* ActorOwner = HAC->GetOwner(); + if (ActorOwner) + { + if (!ActorOwner->GetWorld() || (ActorOwner->GetWorld()->WorldType != EWorldType::Editor)) + return false; + } + +#if WITH_EDITOR + // Stop outliner objects from causing recooks while input objects are dragged around + if (FHoudiniMoveTracker::Get().IsObjectMoving) + { + //HOUDINI_LOG_MESSAGE(TEXT("Object moving, not updating world inputs!")); + return false; + } +#endif + + for (auto CurrentInput : HAC->Inputs) + { + if (!CurrentInput) + continue; + if (CurrentInput->GetInputType() != EHoudiniInputType::World) + continue; + + UpdateWorldInput(CurrentInput); + } + + return true; +} + +bool +FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) +{ + if (!InInput || InInput->IsPendingKill()) + return false; + + if (InInput->GetInputType() != EHoudiniInputType::World) + return false; + + TArray* InputObjectsPtr = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjectsPtr) + return false; + + bool bHasChanged = false; + if (InInput->IsWorldInputBoundSelector() && InInput->GetWorldInputBoundSelectorAutoUpdates()) + { + // If the input is in bound selector mode, and auto-update is enabled + // update the actors selected by the bounds first + bHasChanged = InInput->UpdateWorldSelectionFromBoundSelectors(); + } + + // See if we need to update the components for this input + // look for deleted actors/components + TArray ObjectToDeleteIndices; + for(int32 InputObjIdx = 0; InputObjIdx < InputObjectsPtr->Num(); InputObjIdx++) + { + UHoudiniInputActor* ActorObject = Cast((*InputObjectsPtr)[InputObjIdx]); + if (!ActorObject || ActorObject->IsPendingKill()) + continue; + + // Make sure the actor is still valid + AActor* const Actor = ActorObject->GetActor(); + bool bValidActorObject = Actor && !Actor->IsPendingKill(); + + // For BrushActors, the brush and actors must be valid as well + UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); + if (bValidActorObject && BrushActorObject) + { + ABrush* BrushActor = BrushActorObject->GetBrush(); + if (!IsValid(BrushActor)) + bValidActorObject = false; + else if (!IsValid(BrushActor->Brush)) + bValidActorObject = false; + } + + // The actor is no longer valid, mark it for deletion + if (!bValidActorObject) + { + if ((ActorObject->InputNodeId > 0) || (ActorObject->InputObjectNodeId > 0)) + { + ActorObject->InvalidateData(); + // We only need to update the input if the actors nodes were created in Houdini + bHasChanged = true; + } + + // Delete the Actor object + ObjectToDeleteIndices.Add(InputObjIdx); + continue; + } + + if (ActorObject->HasActorTransformChanged()) + { + ActorObject->MarkTransformChanged(true); + bHasChanged = true; + } + + if (ActorObject->HasContentChanged()) + { + ActorObject->MarkChanged(true); + bHasChanged = true; + } + + // Ensure we are aware of all the components of the actor + ActorObject->Update(Actor); + + // Check if any components have content or transform changes + for (auto CurActorComp : ActorObject->GetActorComponents()) + { + if (CurActorComp->HasComponentTransformChanged()) + { + CurActorComp->MarkTransformChanged(true); + bHasChanged = true; + } + + if (CurActorComp->HasComponentChanged()) + { + CurActorComp->MarkChanged(true); + bHasChanged = true; + } + } + + // Check if we added/removed any components in the call to update + if (ActorObject->GetLastUpdateNumComponentsAdded() > 0 || ActorObject->GetLastUpdateNumComponentsRemoved() > 0) + { + bHasChanged = true; + if (ActorObject->GetLastUpdateNumComponentsRemoved() > 0) + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + } + } + + // Delete the actor objects that were marked for deletion + for (int32 ToDeleteIdx = ObjectToDeleteIndices.Num() - 1; ToDeleteIdx >= 0; ToDeleteIdx--) + InputObjectsPtr->RemoveAt(ObjectToDeleteIndices[ToDeleteIdx]); + + // Mark the input as changed if need so it will trigger an upload + if (bHasChanged) + InInput->MarkChanged(true); + + return true; +} + + +bool +FHoudiniInputTranslator::CreateInputNodeForReference( + HAPI_NodeId& InputNodeId, + const FString& InRef, + const FString& InputNodeName, + const FTransform& InTransform) +{ + HAPI_NodeId NewNodeId = -1; + + // Create a single input node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_UTF8(*InputNodeName)), false); + + /* + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, nullptr), false); + */ + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) + return false; + + // We have now created a valid new input node, delete the previous one + HAPI_NodeId PreviousInputNodeId = InputNodeId; + if (PreviousInputNodeId >= 0) + { + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); + } + } + + // Create and initialize a part containing one point with a point attribute + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + + PartInfo.attributeCounts[HAPI_ATTROWNER_POINT] = 1; + PartInfo.vertexCount = 0; + PartInfo.faceCount = 0; + PartInfo.pointCount = 1; + PartInfo.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, &PartInfo), false); + + // Point Position Attribute + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + FVector ObjectPosition = InTransform.GetLocation(); + TArray Position = + { + ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION + }; + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Position.GetData(), 0, + AttributeInfoPoint.count), false); + } + + // String Attribute + { + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + + AttributeInfoPoint.count = 1; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NewNodeId, 0, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint), false); + + // Set string attribute + std::string AttriString = TCHAR_TO_ANSI(*InRef); + const char* AttriStringRaw = AttriString.c_str(); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, &AttributeInfoPoint, + &AttriStringRaw, 0, 1), false); + } + + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NewNodeId), false); + + InputNodeId = NewNodeId; + return true; +} + + + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeName, UHoudiniInputDataTable* InInputObject) +{ + //TODO + if (!InInputObject || InInputObject->IsPendingKill()) + return false; + + UDataTable* DataTable = InInputObject->GetDataTable(); + if (!DataTable || DataTable->IsPendingKill()) + return true; + + // Get the DataTable data as string + TArray> TableData = DataTable->GetTableData(EDataTableExportFlags::None); + if (TableData.Num() <= 1) + return true; + + int32 NumRows = TableData.Num() - 1; + int32 NumAttributes = TableData[0].Num(); + if (NumRows <= 0 || NumAttributes <= 0) + return true; + + // Create the input node + FString NodeName = InNodeName + TEXT("_") + DataTable->GetName(); + HAPI_NodeId InputNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, TCHAR_TO_UTF8(*NodeName)), false); + + // Update this input object's NodeId and ObjectNodeId + InInputObject->InputNodeId = (int32)InputNodeId; + InInputObject->InputObjectNodeId = (int32)FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeId); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = NumAttributes; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = NumRows; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, &Part), false); + + { + // Create point attribute info for P. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Set the point's position + TArray Positions; + Positions.SetNum(NumRows * 3); + for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) + { + Positions[RowIdx * 3] = 0.0f; + Positions[RowIdx * 3 + 1] = (float)RowIdx; + Positions[RowIdx * 3 + 2] = 0.0f; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Positions.GetData(), 0, + AttributeInfoPoint.count), false); + } + + { + // Create point attribute info for the path. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_OBJECT_PATH, &AttributeInfoPoint), false); + + // Get the object path + FString ObjectPathName = DataTable->GetPathName(); + + // Create an array + TArray ObjectPaths; + ObjectPaths.Init(ObjectPathName, NumRows); + + // Set the point's path attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + ObjectPaths, InputNodeId, 0, HAPI_UNREAL_ATTRIB_OBJECT_PATH, AttributeInfoPoint), false); + } + + { + // Create point attribute info for data table RowTable class name + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = NumRows; + AttributeInfoPoint.tupleSize = 1; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, &AttributeInfoPoint), false); + + // Get the object path + FString RowStructName = DataTable->GetRowStructName().ToString(); + + // Create an array + TArray RowStructNames; + RowStructNames.Init(RowStructName, NumRows); + + // Set the point's path attribute + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + RowStructNames, InputNodeId, 0, + HAPI_UNREAL_ATTRIB_DATA_TABLE_ROWSTRUCT, AttributeInfoPoint), false); + } + + // Now set the attributes values for each "point" of the data table + for (int32 ColIdx = 0; ColIdx < NumAttributes; ColIdx++) + { + // attribute name is "unreal_data_table_COL_NAME" + FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + TableData[0][ColIdx]; + + // We need to gt all values for that attribute + TArray AttributeValues; + AttributeValues.SetNum(NumRows); + for (int32 RowIdx = 0; RowIdx < NumRows; RowIdx++) + { + AttributeValues[RowIdx] = TableData[RowIdx + 1][ColIdx]; + } + + // Create a point attribute info + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = NumRows; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_POINT; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + AttributeInfo.typeInfo = HAPI_ATTRIBUTE_TYPE_NONE; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), InputNodeId, 0, + TCHAR_TO_ANSI(*CurAttrName), &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::SetAttributeStringData( + AttributeValues, InputNodeId, 0, + CurAttrName, AttributeInfo), false); + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputNodeId), false); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, nullptr), false); + + return true; +} + +bool +FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + const FString& InObjNodeName, + UHoudiniInputFoliageType_InstancedStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference) +{ + if (!IsValid(InObject)) + return false; + + FString FTName = InObjNodeName + TEXT("_"); + + UFoliageType_InstancedStaticMesh* FoliageType = Cast(InObject->GetObject()); + if (!IsValid(FoliageType)) + return true; + + UStaticMesh* const SM = FoliageType->GetStaticMesh(); + if (!IsValid(SM)) + return true; + + FTName += FoliageType->GetName(); + + // Marshall the Static Mesh to Houdini + bool bSuccess = true; + + if (bImportAsReference) + { + // Start by getting the Object's full name + FString AssetReference; + AssetReference += SM->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + bSuccess = FUnrealFoliageTypeTranslator::CreateInputNodeForReference( + FoliageType, InObject->InputNodeId, AssetReference, FTName, InObject->Transform); + } + else + { + bSuccess = FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + FoliageType, InObject->InputNodeId, FTName, bExportLODs, bExportSockets, bExportColliders); + } + + InObject->SetImportAsReference(bImportAsReference); + + // Update this input object's OBJ NodeId + InObject->InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(InObject->InputNodeId); + + // If the Input mesh has a Transform offset + const FTransform TransformOffset = InObject->Transform; + if (!TransformOffset.Equals(FTransform::Identity)) + { + // Updating the Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(TransformOffset, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), InObject->InputObjectNodeId, &HapiTransform), false); + } + + return bSuccess; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h index 3450c4ae8..553a5bb8c 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h @@ -1,215 +1,215 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "CoreMinimal.h" - -class AActor; - -class UHoudiniInput; -class UHoudiniParameter; -class UHoudiniAssetComponent; - -class UHoudiniInputObject; -class UHoudiniInputStaticMesh; -class UHoudiniInputSkeletalMesh; -class UHoudiniInputSceneComponent; -class UHoudiniInputMeshComponent; -class UHoudiniInputInstancedMeshComponent; -class UHoudiniInputSplineComponent; -class UHoudiniInputHoudiniSplineComponent; -class UHoudiniInputHoudiniAsset; -class UHoudiniInputActor; -class UHoudiniInputLandscape; -class UHoudiniInputBrush; -class UHoudiniSplineComponent; -class UHoudiniInputCameraComponent; -class UHoudiniInputDataTable; -class UHoudiniInputFoliageType_InstancedStaticMesh; - -class AActor; - -enum class EHoudiniInputType : uint8; -enum class EHoudiniLandscapeExportType : uint8; - -struct HOUDINIENGINE_API FHoudiniInputTranslator -{ - // - static bool UpdateInputs(UHoudiniAssetComponent* HAC); - - // Update inputs from the asset - // @AssetId: NodeId of the digital asset - // @OuterObject: Object to use for transactions and as Outer for new inputs - // @CurrentInputs: pre: current & post: invalid inputs - // @NewParameters: pre: empty & post: new inputs - // On Return: CurrentInputs are the old inputs that are no longer valid, - // NewInputs are new and re-used inputs. - static bool BuildAllInputs( - const HAPI_NodeId& AssetId, - class UObject* OuterObject, - TArray& Inputs, - TArray& Parameters); - - // Update loaded inputs and their input objects so they can be uploaded properly - static bool UpdateLoadedInputs(UHoudiniAssetComponent * HAC); - - // Update all the inputs that have been marked as change - static bool UploadChangedInputs(UHoudiniAssetComponent * HAC); - - // Only update simple input properties - static bool UpdateInputProperties(UHoudiniInput* InInput); - - // Update the KeepWorldTransform / Object merge transform type property - static bool UpdateTransformType(UHoudiniInput* InInput); - - // Update the pack before merge parameter for World/Geometry inputs - static bool UpdatePackBeforeMerge(UHoudiniInput* InInput); - - // Update the transform offset for geometry inputs - static bool UpdateTransformOffset(UHoudiniInput* InInput); - - // Upload all the input's data to Houdini - static bool UploadInputData(UHoudiniInput* InInput); - - // Upload all the input's transforms to Houdini - static bool UploadInputTransform(UHoudiniInput* InInput); - - // Upload data for an input's InputObject - static bool UploadHoudiniInputObject( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, TArray& OutCreatedNodeIds); - - // Upload transform for an input's InputObject - static bool UploadHoudiniInputTransform( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject); - - // Updates/ticks world inputs in the given HAC - static bool UpdateWorldInputs(UHoudiniAssetComponent* HAC); - - // Updates/ticks the given world input - static bool UpdateWorldInput(UHoudiniInput* InInput); - - // Connect an input's nodes to its linked HDA node - static bool ConnectInputNode(UHoudiniInput* InInput); - - // Destroys an input - static bool DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - static bool DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - static bool DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); - - static EHoudiniInputType GetDefaultInputTypeFromLabel(const FString& InputName); - - static bool SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified); - - static bool ChangeInputType(UHoudiniInput* Input, const bool& bForce); - - static bool HapiCreateInputNodeForObject( - const FString& InObjNodeName, UHoudiniInputObject* InObject); - - static bool HapiCreateInputNodeForStaticMesh( - const FString& InObjNodeName, - UHoudiniInputStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference = false); - - static bool HapiCreateInputNodeForHoudiniSplineComponent( - const FString& InObjNodeName, - UHoudiniInputHoudiniSplineComponent* InObject, - bool bInSetRotAndScaleAttributes); - - static bool HapiCreateInputNodeForLandscape( - const FString& InObjNodeName, - UHoudiniInputLandscape* InObject, - UHoudiniInput* InInput); - - static bool HapiCreateInputNodeForSkeletalMesh( - const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject); - - static bool HapiCreateInputNodeForSceneComponent( - const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject); - - static bool HapiCreateInputNodeForStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference); - - static bool HapiCreateInputNodeForInstancedStaticMeshComponent( - const FString& InObjNodeName, - UHoudiniInputInstancedMeshComponent* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders); - - static bool HapiCreateInputNodeForSplineComponent( - const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution); - - static bool HapiCreateInputNodeForHoudiniAssetComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference); - - static bool HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds); - - static bool HapiCreateInputNodeForCamera( - const FString& InObjNodeName, UHoudiniInputCameraComponent* InObject); - - // Create input node for Brush. Optionally exclude actors when combining - // brush with other intersecting brushes. This is typically used to - // exclude Selector objects. - static bool HapiCreateInputNodeForBrush( - const FString& InObjNodeName, - UHoudiniInputBrush* InObject, - TArray* ExcludeActors - ); - - static bool HapiCreateInputNodeForDataTable( - const FString& InNodeName, UHoudiniInputDataTable* InInputObject); - - static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - const FString& InObjNodeName, - UHoudiniInputFoliageType_InstancedStaticMesh* InObject, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bImportAsReference = false); - - // HAPI: Create an input node for reference - static bool CreateInputNodeForReference( - HAPI_NodeId& InputNodeId, - const FString & InRef, - const FString & InputNodeName, - const FTransform & InTransform); - - //static bool HapiUpdateInputNodeTransform(const HAPI_NodeId InputNodeId, const FTransform& Transform); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "CoreMinimal.h" + +class AActor; + +class UHoudiniInput; +class UHoudiniParameter; +class UHoudiniAssetComponent; + +class UHoudiniInputObject; +class UHoudiniInputStaticMesh; +class UHoudiniInputSkeletalMesh; +class UHoudiniInputSceneComponent; +class UHoudiniInputMeshComponent; +class UHoudiniInputInstancedMeshComponent; +class UHoudiniInputSplineComponent; +class UHoudiniInputHoudiniSplineComponent; +class UHoudiniInputHoudiniAsset; +class UHoudiniInputActor; +class UHoudiniInputLandscape; +class UHoudiniInputBrush; +class UHoudiniSplineComponent; +class UHoudiniInputCameraComponent; +class UHoudiniInputDataTable; +class UHoudiniInputFoliageType_InstancedStaticMesh; + +class AActor; + +enum class EHoudiniInputType : uint8; +enum class EHoudiniLandscapeExportType : uint8; + +struct HOUDINIENGINE_API FHoudiniInputTranslator +{ + // + static bool UpdateInputs(UHoudiniAssetComponent* HAC); + + // Update inputs from the asset + // @AssetId: NodeId of the digital asset + // @OuterObject: Object to use for transactions and as Outer for new inputs + // @CurrentInputs: pre: current & post: invalid inputs + // @NewParameters: pre: empty & post: new inputs + // On Return: CurrentInputs are the old inputs that are no longer valid, + // NewInputs are new and re-used inputs. + static bool BuildAllInputs( + const HAPI_NodeId& AssetId, + class UObject* OuterObject, + TArray& Inputs, + TArray& Parameters); + + // Update loaded inputs and their input objects so they can be uploaded properly + static bool UpdateLoadedInputs(UHoudiniAssetComponent * HAC); + + // Update all the inputs that have been marked as change + static bool UploadChangedInputs(UHoudiniAssetComponent * HAC); + + // Only update simple input properties + static bool UpdateInputProperties(UHoudiniInput* InInput); + + // Update the KeepWorldTransform / Object merge transform type property + static bool UpdateTransformType(UHoudiniInput* InInput); + + // Update the pack before merge parameter for World/Geometry inputs + static bool UpdatePackBeforeMerge(UHoudiniInput* InInput); + + // Update the transform offset for geometry inputs + static bool UpdateTransformOffset(UHoudiniInput* InInput); + + // Upload all the input's data to Houdini + static bool UploadInputData(UHoudiniInput* InInput); + + // Upload all the input's transforms to Houdini + static bool UploadInputTransform(UHoudiniInput* InInput); + + // Upload data for an input's InputObject + static bool UploadHoudiniInputObject( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, TArray& OutCreatedNodeIds); + + // Upload transform for an input's InputObject + static bool UploadHoudiniInputTransform( + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject); + + // Updates/ticks world inputs in the given HAC + static bool UpdateWorldInputs(UHoudiniAssetComponent* HAC); + + // Updates/ticks the given world input + static bool UpdateWorldInput(UHoudiniInput* InInput); + + // Connect an input's nodes to its linked HDA node + static bool ConnectInputNode(UHoudiniInput* InInput); + + // Destroys an input + static bool DisconnectAndDestroyInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + static bool DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + static bool DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType); + + static EHoudiniInputType GetDefaultInputTypeFromLabel(const FString& InputName); + + static bool SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified); + + static bool ChangeInputType(UHoudiniInput* Input, const bool& bForce); + + static bool HapiCreateInputNodeForObject( + const FString& InObjNodeName, UHoudiniInputObject* InObject); + + static bool HapiCreateInputNodeForStaticMesh( + const FString& InObjNodeName, + UHoudiniInputStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference = false); + + static bool HapiCreateInputNodeForHoudiniSplineComponent( + const FString& InObjNodeName, + UHoudiniInputHoudiniSplineComponent* InObject, + bool bInSetRotAndScaleAttributes); + + static bool HapiCreateInputNodeForLandscape( + const FString& InObjNodeName, + UHoudiniInputLandscape* InObject, + UHoudiniInput* InInput); + + static bool HapiCreateInputNodeForSkeletalMesh( + const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject); + + static bool HapiCreateInputNodeForSceneComponent( + const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject); + + static bool HapiCreateInputNodeForStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference); + + static bool HapiCreateInputNodeForInstancedStaticMeshComponent( + const FString& InObjNodeName, + UHoudiniInputInstancedMeshComponent* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders); + + static bool HapiCreateInputNodeForSplineComponent( + const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution); + + static bool HapiCreateInputNodeForHoudiniAssetComponent( + const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference); + + static bool HapiCreateInputNodeForActor( + UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds); + + static bool HapiCreateInputNodeForCamera( + const FString& InObjNodeName, UHoudiniInputCameraComponent* InObject); + + // Create input node for Brush. Optionally exclude actors when combining + // brush with other intersecting brushes. This is typically used to + // exclude Selector objects. + static bool HapiCreateInputNodeForBrush( + const FString& InObjNodeName, + UHoudiniInputBrush* InObject, + TArray* ExcludeActors + ); + + static bool HapiCreateInputNodeForDataTable( + const FString& InNodeName, UHoudiniInputDataTable* InInputObject); + + static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + const FString& InObjNodeName, + UHoudiniInputFoliageType_InstancedStaticMesh* InObject, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bImportAsReference = false); + + // HAPI: Create an input node for reference + static bool CreateInputNodeForReference( + HAPI_NodeId& InputNodeId, + const FString & InRef, + const FString & InputNodeName, + const FTransform & InTransform); + + //static bool HapiUpdateInputNodeTransform(const HAPI_NodeId InputNodeId, const FTransform& Transform); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp index ef86a0c7c..3ec7df54e 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp @@ -1,3488 +1,3523 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInstanceTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniStaticMesh.h" - -//#include "HAPI/HAPI_Common.h" - -#include "Engine/StaticMesh.h" -#include "ComponentReregisterContext.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#if WITH_EDITOR - //#include "ScopedTransaction.h" - #include "LevelEditorViewport.h" - #include "MeshPaintHelpers.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// Fastrand is a faster alternative to std::rand() -// and doesn't oscillate when looking for 2 values like Unreal's. -inline int fastrand(int& nSeed) -{ - nSeed = (214013 * nSeed + 2531011); - return (nSeed >> 16) & 0x7FFF; -} - -// -bool -FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) -{ - // Get if force to use HISM from attribute - OutInstancedOutputPartData.bForceHISM = HasHISMAttribute(InHGPO.GeoId, InHGPO.PartId); - - // Extract the object and transforms for this instancer - if (!GetInstancerObjectsAndTransforms( - InHGPO, - InAllOutputs, - OutInstancedOutputPartData.OriginalInstancedObjects, - OutInstancedOutputPartData.OriginalInstancedTransforms, - OutInstancedOutputPartData.OriginalInstancedIndices, - OutInstancedOutputPartData.SplitAttributeName, - OutInstancedOutputPartData.SplitAttributeValues, - OutInstancedOutputPartData.PerSplitAttributes)) - return false; - - // Check if this is a No-Instancers ( unreal_split_instances ) - OutInstancedOutputPartData.bSplitMeshInstancer = IsSplitInstancer(InHGPO.GeoId, InHGPO.PartId); - - OutInstancedOutputPartData.bIsFoliageInstancer = IsFoliageInstancer(InHGPO.GeoId, InHGPO.PartId); - - // Extract the generic attributes - GetGenericPropertiesAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllPropertyAttributes); - - // Check for per instance custom data - GetPerInstanceCustomData(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData); - - //Get the level path attribute on the instancer - if (!FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllLevelPaths)) - { - // No attribute specified - OutInstancedOutputPartData.AllLevelPaths.Empty(); - } - - // Get the output name attribute - if (!FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.OutputNames)) - { - // No attribute specified - OutInstancedOutputPartData.OutputNames.Empty(); - } - - // See if we have a tile attribute - if (!FHoudiniEngineUtils::GetTileAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.TileValues)) - { - // No attribute specified - OutInstancedOutputPartData.TileValues.Empty(); - } - - // Get the bake actor attribute - if (!FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeActorNames)) - { - // No attribute specified - OutInstancedOutputPartData.AllBakeActorNames.Empty(); - } - - // Get the unreal_bake_folder attribute - if (!FHoudiniEngineUtils::GetBakeFolderAttribute(InHGPO.GeoId, OutInstancedOutputPartData.AllBakeFolders, InHGPO.PartId)) - { - // No attribute specified - OutInstancedOutputPartData.AllBakeFolders.Empty(); - } - - // Get the bake outliner folder attribute - if (!FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeOutlinerFolders)) - { - // No attribute specified - OutInstancedOutputPartData.AllBakeOutlinerFolders.Empty(); - } - - // See if we have instancer material overrides - if (!GetMaterialOverridesFromAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.MaterialAttributes)) - OutInstancedOutputPartData.MaterialAttributes.Empty(); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - UHoudiniOutput* InOutput, - const TArray& InAllOutputs, - UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - // Keep track of the previous cook's component to clean them up after - TMap NewOutputObjects; - TMap OldOutputObjects = InOutput->GetOutputObjects(); - - TMap& InstancedOutputs = InOutput->GetInstancedOutputs(); - // Mark all the current instanced output as stale - for (auto& InstOut : InstancedOutputs) - InstOut.Value.bStale = true; - - USceneComponent* ParentComponent = Cast(InOuterComponent); - if (!ParentComponent) - return false; - - // Keep track of if we remove, create or update any foliage, so that we can repopulate the foliage type list in - // the UI (foliage mode) at the end - bool bHaveAnyFoliageInstancers = false; - - // We also need to cleanup the previous foliages instances (if we have any) - for (auto& CurrentPair : OldOutputObjects) - { - // Foliage instancers store a HISMC in the components - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentPair.Value.OutputComponent); - if (!IsValid(FoliageHISMC)) - continue; - - CleanupFoliageInstances(FoliageHISMC, CurrentPair.Value.OutputObject, ParentComponent); - bHaveAnyFoliageInstancers = true; - } - - // The default SM to be used if the instanced object has not been found (when using attribute instancers) - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) - { - // Not an instancer, skip - if (CurHGPO.Type != EHoudiniPartType::Instancer) - continue; - - // Prepare this output object's output identifier - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = CurHGPO.ObjectId; - OutputIdentifier.GeoId = CurHGPO.GeoId; - OutputIdentifier.PartId = CurHGPO.PartId; - OutputIdentifier.PartName = CurHGPO.PartName; - - FHoudiniInstancedOutputPartData InstancedOutputPartDataTmp; - const FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = nullptr; - if (InPreBuiltInstancedOutputPartData) - { - InstancedOutputPartDataPtr = InPreBuiltInstancedOutputPartData->Find(OutputIdentifier); - } - if (!InstancedOutputPartDataPtr) - { - if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs, InstancedOutputPartDataTmp)) - continue; - InstancedOutputPartDataPtr = &InstancedOutputPartDataTmp; - } - - const FHoudiniInstancedOutputPartData& InstancedOutputPartData = *InstancedOutputPartDataPtr; - - TArray InstancerMaterials; - if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes, InstancerMaterials)) - InstancerMaterials.Empty(); - - if (InstancedOutputPartData.bIsFoliageInstancer) - bHaveAnyFoliageInstancers = true; - - // - // TODO: REFACTOR THIS! - // - // We create an instanced output per original object - // These original object can then potentially be replaced by variations - // Each variations will create a instance component / OutputObject - // Currently we process all original objects AND their variations at the same time - // we should instead loop on the original objects - // - get their variations objects/transform - // - create the appropriate instancer - // This means modifying UpdateInstanceVariationsObjects so that it works using - // a single OriginalObject instead of using an array - // Also, apply the same logic to UpdateChangedInstanceOutput - // - - // Array containing all the variations objects for all the original objects - TArray> VariationInstancedObjects; - // Array containing all the variations transforms - TArray> VariationInstancedTransforms; - // Array indicate the original object index for each variation - TArray VariationOriginalObjectIndices; - // Array indicate the variation number for each variation - TArray VariationIndices; - // Update our variations using the instanced outputs - UpdateInstanceVariationObjects( - OutputIdentifier, - InstancedOutputPartData.OriginalInstancedObjects, - InstancedOutputPartData.OriginalInstancedTransforms, - InstancedOutputPartData.OriginalInstancedIndices, - InOutput->GetInstancedOutputs(), - VariationInstancedObjects, - VariationInstancedTransforms, - VariationOriginalObjectIndices, - VariationIndices); - - // Preload objects so we can benefit from async compilation as much as possible - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) - { - VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); - } - - // Create the instancer components now - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) - { - UObject* InstancedObject = VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - continue; - - if (!VariationInstancedTransforms.IsValidIndex(InstanceObjectIdx)) - continue; - - const TArray& InstancedObjectTransforms = VariationInstancedTransforms[InstanceObjectIdx]; - if (InstancedObjectTransforms.Num() <= 0) - continue; - - // Get the original Index of that variations - int32 VariationOriginalIndex = VariationOriginalObjectIndices[InstanceObjectIdx]; - - // Find the matching instance output now - FHoudiniInstancedOutput* FoundInstancedOutput = nullptr; - { - // Instanced output only use the original object index for their split identifier - FHoudiniOutputObjectIdentifier InstancedOutputIdentifier = OutputIdentifier; - InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalIndex); - FoundInstancedOutput = InstancedOutputs.Find(InstancedOutputIdentifier); - } - - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - OutputIdentifier.SplitIdentifier = - FString::FromInt(VariationOriginalIndex) - + TEXT("_") - + FString::FromInt(VariationIndices[InstanceObjectIdx]); - - // Get the OutputObj for this variation - FHoudiniOutputObject* FoundOutputObject = OldOutputObjects.Find(OutputIdentifier); - // See if we can find an preexisting component for this obj to try to reuse it - USceneComponent* OldInstancerComponent = nullptr; - const bool bIsProxyMesh = InstancedObject->IsA(); - if (FoundOutputObject) - { - if (bIsProxyMesh) - { - OldInstancerComponent = Cast(FoundOutputObject->ProxyComponent); - } - else - { - OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); - } - } - - // Extract the material for this variation - TArray VariationMaterials; - if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex], InstancerMaterials, VariationMaterials)) - VariationMaterials.Empty(); - - USceneComponent* NewInstancerComponent = nullptr; - if (!CreateOrUpdateInstanceComponent( - InstancedObject, - InstancedObjectTransforms, - InstancedOutputPartData.AllPropertyAttributes, - CurHGPO, - ParentComponent, - OldInstancerComponent, - NewInstancerComponent, - InstancedOutputPartData.bSplitMeshInstancer, - InstancedOutputPartData.bIsFoliageInstancer, - VariationMaterials, - InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex], - InstanceObjectIdx, - InstancedOutputPartData.bForceHISM)) - { - // TODO?? - continue; - } - - if (!NewInstancerComponent) - continue; - - // Copy the per-instance custom data if we have any - if (InstancedOutputPartData.PerInstanceCustomData.Num() > 0) - { - UpdateChangedPerInstanceCustomData( - InstancedOutputPartData.PerInstanceCustomData[VariationOriginalIndex], NewInstancerComponent); - } - - // If the instanced object (by ref) wasn't found, hide the component - if(InstancedObject == DefaultReferenceSM) - NewInstancerComponent->SetHiddenInGame(true); - else - NewInstancerComponent->SetHiddenInGame(false); - - FHoudiniOutputObject& NewOutputObject = NewOutputObjects.FindOrAdd(OutputIdentifier); - if (bIsProxyMesh) - { - NewOutputObject.ProxyComponent = NewInstancerComponent; - NewOutputObject.ProxyObject = InstancedObject; - } - else - { - NewOutputObject.OutputComponent = NewInstancerComponent; - NewOutputObject.OutputObject = InstancedObject; - } - - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - NewOutputObject.CachedAttributes.Empty(); - NewOutputObject.CachedTokens.Empty(); - - // Cache the level path, output name and tile attributes on the output object So they can be reused for baking - int32 FirstOriginalInstanceIndex = 0; - if(InstancedOutputPartData.OriginalInstancedIndices.IsValidIndex(VariationOriginalIndex) && InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex].Num() > 0) - FirstOriginalInstanceIndex = InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex][0]; - - if(InstancedOutputPartData.AllLevelPaths.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllLevelPaths[FirstOriginalInstanceIndex].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[FirstOriginalInstanceIndex]); - - if(InstancedOutputPartData.OutputNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.OutputNames[FirstOriginalInstanceIndex].IsEmpty()) - NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[FirstOriginalInstanceIndex]); - - // TODO: Check! maybe accessed with just VariationOriginalIndex - if(InstancedOutputPartData.TileValues.IsValidIndex(FirstOriginalInstanceIndex) && InstancedOutputPartData.TileValues[FirstOriginalInstanceIndex] >= 0) - { - // cache the tile attribute as a token on the output object - NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[FirstOriginalInstanceIndex])); - } - - if(InstancedOutputPartData.AllBakeActorNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeActorNames[FirstOriginalInstanceIndex].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[FirstOriginalInstanceIndex]); - - if(InstancedOutputPartData.AllBakeFolders.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeFolders[FirstOriginalInstanceIndex].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, InstancedOutputPartData.AllBakeFolders[FirstOriginalInstanceIndex]); - - if(InstancedOutputPartData.AllBakeOutlinerFolders.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeOutlinerFolders[FirstOriginalInstanceIndex].IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[FirstOriginalInstanceIndex]); - - if(InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalIndex) - && !InstancedOutputPartData.SplitAttributeName.IsEmpty()) - { - FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalIndex]; - - // Cache the split attribute both as attribute and token - NewOutputObject.CachedAttributes.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); - NewOutputObject.CachedTokens.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); - - // If we have a split name that is non-empty, override attributes that can differ by split based - // on the split name - if (!SplitValue.IsEmpty()) - { - const FHoudiniInstancedOutputPerSplitAttributes* PerSplitAttributes = InstancedOutputPartData.PerSplitAttributes.Find(SplitValue); - if (PerSplitAttributes) - { - if (!PerSplitAttributes->LevelPath.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, PerSplitAttributes->LevelPath); - if (!PerSplitAttributes->BakeActorName.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, PerSplitAttributes->BakeActorName); - if (!PerSplitAttributes->BakeOutlinerFolder.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, PerSplitAttributes->BakeOutlinerFolder); - if (!PerSplitAttributes->BakeFolder.IsEmpty()) - NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, PerSplitAttributes->BakeFolder); - } - } - } - } - } - - // Remove reused components from the old map to avoid their deletion - for (const auto& CurNewPair : NewOutputObjects) - { - // Get the new Identifier / StaticMesh - const FHoudiniOutputObjectIdentifier& OutputIdentifier = CurNewPair.Key; - - // See if we already had that pair in the old map - FHoudiniOutputObject* FoundOldOutputObject = OldOutputObjects.Find(OutputIdentifier); - if (!FoundOldOutputObject) - continue; - - bool bKeep = false; - - UObject* NewComponent = CurNewPair.Value.OutputComponent; - if (NewComponent) - { - UObject* FoundOldComponent = FoundOldOutputObject->OutputComponent; - if (FoundOldComponent && !FoundOldComponent->IsPendingKill()) - { - bKeep = (FoundOldComponent == NewComponent); - } - } - - UObject* NewProxyComponent = CurNewPair.Value.ProxyComponent; - if (NewProxyComponent) - { - UObject* FoundOldProxyComponent = FoundOldOutputObject->ProxyComponent; - if (FoundOldProxyComponent && !FoundOldProxyComponent->IsPendingKill()) - { - bKeep = (FoundOldProxyComponent == NewProxyComponent); - } - } - - if (bKeep) - { - // Remove the reused component from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - - // The Old map now only contains unused/stale components, delete them - for (auto& OldPair : OldOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; - UObject* OldComponent = OldPair.Value.OutputComponent; - if (OldComponent) - { - bool bDestroy = true; - if (OldComponent->IsA()) - { - // When destroying a component, we have to be sure it's not an HISMC owned by an InstanceFoliageActor - UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OldComponent); - if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) - bDestroy = false; - } - - if(bDestroy) - RemoveAndDestroyComponent(OldComponent, OldPair.Value.OutputObject); - - OldPair.Value.OutputComponent = nullptr; - OldPair.Value.OutputObject = nullptr; - } - - UObject* OldProxyComponent = OldPair.Value.ProxyComponent; - if (OldProxyComponent) - { - RemoveAndDestroyComponent(OldProxyComponent, OldPair.Value.ProxyObject); - OldPair.Value.ProxyComponent = nullptr; - OldPair.Value.ProxyObject = nullptr; - } - } - OldOutputObjects.Empty(); - - // Update the output's object map - // Instancer do not create objects, clean the map - InOutput->SetOutputObjects(NewOutputObjects); - - // If we removed, created or updated any foliage instancers, repopulate the list of foliage types in the UI (foliage - // mode) - if (bHaveAnyFoliageInstancers) - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - - return true; -} - - -bool -FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( - FHoudiniInstancedOutput& InInstancedOutput, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - UHoudiniOutput* InParentOutput, - USceneComponent* InParentComponent) -{ - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = InOutputIdentifier.ObjectId; - OutputIdentifier.GeoId = InOutputIdentifier.GeoId; - OutputIdentifier.PartId = InOutputIdentifier.PartId; - OutputIdentifier.SplitIdentifier = InOutputIdentifier.SplitIdentifier; - OutputIdentifier.PartName = InOutputIdentifier.PartName; - - // Get if force using HISM from attribute - bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); - - TArray OriginalInstancedObjects; - OriginalInstancedObjects.Add(InInstancedOutput.OriginalObject.LoadSynchronous()); - - TArray> OriginalInstancedTransforms; - OriginalInstancedTransforms.Add(InInstancedOutput.OriginalTransforms); - - TArray> OriginalInstanceIndices; - OriginalInstanceIndices.Add(InInstancedOutput.OriginalInstanceIndices); - - // Update our variations using the changed instancedoutputs objects - TArray> InstancedObjects; - TArray> InstancedTransforms; - TArray VariationOriginalObjectIndices; - TArray VariationIndices; - UpdateInstanceVariationObjects( - OutputIdentifier, - OriginalInstancedObjects, - OriginalInstancedTransforms, - OriginalInstanceIndices, - InParentOutput->GetInstancedOutputs(), - InstancedObjects, - InstancedTransforms, - VariationOriginalObjectIndices, - VariationIndices); - - // Find the HGPO for this instanced output - bool FoundHGPO = false; - FHoudiniGeoPartObject HGPO; - for (const auto& curHGPO : InParentOutput->GetHoudiniGeoPartObjects()) - { - if (OutputIdentifier.Matches(curHGPO)) - { - HGPO = curHGPO; - FoundHGPO = true; - break; - } - } - - if (!FoundHGPO) - { - // TODO check failure - ensure(FoundHGPO); - } - - // Extract the generic attributes for that HGPO - TArray AllPropertyAttributes; - GetGenericPropertiesAttributes(OutputIdentifier.GeoId, OutputIdentifier.PartId, AllPropertyAttributes); - - // Check if this is a No-Instancers ( unreal_split_instances ) - bool bSplitMeshInstancer = IsSplitInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); - - bool bIsFoliageInstancer = IsFoliageInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); - - // See if we have instancer material overrides - TArray InstancerMaterials; - if (!GetInstancerMaterials(OutputIdentifier.GeoId, OutputIdentifier.PartId, InstancerMaterials)) - InstancerMaterials.Empty(); - - // Preload objects so we can benefit from async compilation as much as possible - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) - { - InstancedObjects[InstanceObjectIdx].LoadSynchronous(); - } - - // Keep track of the new instancer component in order to be able to clean up the unused/stale ones after. - TMap& OutputObjects = InParentOutput->GetOutputObjects(); - TMap ToDeleteOutputObjects = InParentOutput->GetOutputObjects(); - - // Create the instancer components now - for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) - { - UObject* InstancedObject = InstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - continue; - - if (!InstancedTransforms.IsValidIndex(InstanceObjectIdx)) - continue; - - const TArray& InstancedObjectTransforms = InstancedTransforms[InstanceObjectIdx]; - if (InstancedObjectTransforms.Num() <= 0) - continue; - - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - // the original object index is used for the instanced outputs split identifier - OutputIdentifier.SplitIdentifier = - InOutputIdentifier.SplitIdentifier - + TEXT("_") - + FString::FromInt(VariationIndices[InstanceObjectIdx]); - - // See if we can find an preexisting component for this obj to try to reuse it - USceneComponent* OldInstancerComponent = nullptr; - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(OutputIdentifier); - if (FoundOutputObject) - { - OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); - } - - // Extract the material for this variation -// FHoudiniInstancedOutput* FoundInstancedOutput = InstancedOutputs.Find(OutputIdentifier); - TArray VariationMaterials; - if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, OriginalInstanceIndices[0], InstancerMaterials, VariationMaterials)) - VariationMaterials.Empty(); - - USceneComponent* NewInstancerComponent = nullptr; - if (!CreateOrUpdateInstanceComponent( - InstancedObject, - InstancedObjectTransforms, - AllPropertyAttributes, - HGPO, - InParentComponent, - OldInstancerComponent, - NewInstancerComponent, - bSplitMeshInstancer, - bIsFoliageInstancer, - InstancerMaterials, - OriginalInstanceIndices[0], - InstanceObjectIdx, - bForceHISM)) - { - // TODO?? - continue; - } - - if (!NewInstancerComponent) - continue; - - if (OldInstancerComponent != NewInstancerComponent) - { - // Previous component wasn't reused, detach and delete it - RemoveAndDestroyComponent(OldInstancerComponent, nullptr); - - // Replace it with the new component - if (FoundOutputObject) - { - FoundOutputObject->OutputComponent = NewInstancerComponent; - } - else - { - FHoudiniOutputObject& NewOutputObject = OutputObjects.Add(OutputIdentifier); - NewOutputObject.OutputComponent = NewInstancerComponent; - } - } - - // Remove this output object from the todelete map - ToDeleteOutputObjects.Remove(OutputIdentifier); - } - - // Clean up the output objects that are not "reused" by the instanced outs - // The ToDelete map now only contains unused/stale components, delete them - for (auto& ToDeletePair : ToDeleteOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& ToDeleteIdentifier = ToDeletePair.Key; - UObject* OldComponent = ToDeletePair.Value.OutputComponent; - if (OldComponent) - { - RemoveAndDestroyComponent(OldComponent, ToDeletePair.Value.OutputObject); - ToDeletePair.Value.OutputComponent = nullptr; - } - - UObject* OldProxyComponent = ToDeletePair.Value.ProxyComponent; - if (OldProxyComponent) - { - RemoveAndDestroyComponent(OldProxyComponent, ToDeletePair.Value.ProxyObject); - ToDeletePair.Value.ProxyComponent = nullptr; - } - - // Make sure the stale output object is not in the output map anymore - OutputObjects.Remove(ToDeleteIdentifier); - } - ToDeleteOutputObjects.Empty(); - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - TArray>& OutInstancedIndices, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValues, - TMap& OutPerSplitAttributes) -{ - TArray InstancedObjects; - TArray> InstancedTransforms; - TArray> InstancedIndices; - - TArray InstancedHGPOs; - TArray> InstancedHGPOTransforms; - TArray> InstancedHGPOIndices; - - bool bSuccess = false; - switch (InHGPO.InstancerType) - { - case EHoudiniInstancerType::PackedPrimitive: - { - // Packed primitives instances - bSuccess = GetPackedPrimitiveInstancerHGPOsAndTransforms( - InHGPO, - InstancedHGPOs, - InstancedHGPOTransforms, - InstancedHGPOIndices, - OutSplitAttributeName, - OutSplitAttributeValues, - OutPerSplitAttributes); - } - break; - - case EHoudiniInstancerType::AttributeInstancer: - { - // "Modern" attribute instancer - "unreal_instance" - bSuccess = GetAttributeInstancerObjectsAndTransforms( - InHGPO, - InstancedObjects, - InstancedTransforms, - InstancedIndices, - OutSplitAttributeName, - OutSplitAttributeValues, - OutPerSplitAttributes); - } - break; - - case EHoudiniInstancerType::OldSchoolAttributeInstancer: - { - // Old school attribute override instancer - instance attribute w/ a HoudiniPath - bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms, InstancedHGPOIndices); - } - break; - - case EHoudiniInstancerType::ObjectInstancer: - { - // Old School object instancer - bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms, InstancedHGPOIndices); - } - break; - } - - if (!bSuccess) - return false; - - // Fetch the UOBject that correspond to the instanced parts - // Attribute instancers don't need to do this since they refer UObjects directly - if (InstancedHGPOs.Num() > 0) - { - for (int32 HGPOIdx = 0; HGPOIdx < InstancedHGPOs.Num(); HGPOIdx++) - { - const FHoudiniGeoPartObject& CurrentHGPO = InstancedHGPOs[HGPOIdx]; - - // Get the UObject that was generated for that HGPO - TArray ObjectsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - if (Output->OutputObjects.Num() <= 0) - continue; - - for (const auto& OutObjPair : Output->OutputObjects) - { - if (!OutObjPair.Key.Matches(CurrentHGPO)) - continue; - - const FHoudiniOutputObject& CurrentOutputObject = OutObjPair.Value; - - // In the case of a single-instance we can use the proxy (if it is current) - // FHoudiniOutputTranslator::UpdateOutputs doesn't allow proxies if there is more than one instance in an output - if (InstancedHGPOTransforms[HGPOIdx].Num() <= 1 && CurrentOutputObject.bProxyIsCurrent - && CurrentOutputObject.ProxyObject && !CurrentOutputObject.ProxyObject->IsPendingKill()) - { - ObjectsToInstance.Add(CurrentOutputObject.ProxyObject); - } - else if (CurrentOutputObject.OutputObject && !CurrentOutputObject.OutputObject->IsPendingKill()) - { - ObjectsToInstance.Add(CurrentOutputObject.OutputObject); - } - } - } - - // Add the UObject and the HGPO transforms to the output arrays - for (const auto& MatchingOutputObj : ObjectsToInstance) - { - InstancedObjects.Add(MatchingOutputObj); - InstancedTransforms.Add(InstancedHGPOTransforms[HGPOIdx]); - InstancedIndices.Add(InstancedHGPOIndices[HGPOIdx]); - } - } - } - - // - if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() || InstancedIndices.Num() != InstancedObjects.Num()) - { - // TODO - // Error / warning - return false; - } - - OutInstancedObjects = InstancedObjects; - OutInstancedTransforms = InstancedTransforms; - OutInstancedIndices = InstancedIndices; - - return true; -} - - -void -FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TArray& InOriginalObjects, - const TArray>& InOriginalTransforms, - const TArray>& InOriginalInstancedIndices, - TMap& InstancedOutputs, - TArray>& OutVariationsInstancedObjects, - TArray>& OutVariationsInstancedTransforms, - TArray& OutVariationOriginalObjectIdx, - TArray& OutVariationIndices) -{ - FHoudiniOutputObjectIdentifier Identifier = InOutputIdentifier; - for (int32 InstObjIdx = 0; InstObjIdx < InOriginalObjects.Num(); InstObjIdx++) - { - UObject* OriginalObj = InOriginalObjects[InstObjIdx]; - if (!OriginalObj || OriginalObj->IsPendingKill()) - continue; - - // Build this output object's split identifier - Identifier.SplitIdentifier = FString::FromInt(InstObjIdx); - - // Do we have an instanced output object for this one? - FHoudiniInstancedOutput * FoundInstancedOutput = nullptr; - for (auto& Iter : InstancedOutputs) - { - FHoudiniOutputObjectIdentifier& FoundIdentifier = Iter.Key; - if (!(FoundIdentifier == Identifier)) - continue; - - // We found an existing instanced output for this identifier - FoundInstancedOutput = &(Iter.Value); - - if (FoundIdentifier.bLoaded) - { - // The output object identifier we found is marked as loaded, - // so uses old node IDs, we must update them, or the next cook - // will fail to locate the output back - FoundIdentifier.ObjectId = Identifier.ObjectId; - FoundIdentifier.GeoId = Identifier.GeoId; - FoundIdentifier.PartId = Identifier.PartId; - } - } - - if (!FoundInstancedOutput) - { - // Create a new one - FHoudiniInstancedOutput CurInstancedOutput; - CurInstancedOutput.OriginalObject = OriginalObj; - CurInstancedOutput.OriginalObjectIndex = InstObjIdx; - CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; - CurInstancedOutput.OriginalInstanceIndices = InOriginalInstancedIndices[InstObjIdx]; - - CurInstancedOutput.VariationObjects.Add(OriginalObj); - CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); - CurInstancedOutput.TransformVariationIndices.SetNumZeroed(InOriginalTransforms[InstObjIdx].Num()); - CurInstancedOutput.MarkChanged(false); - CurInstancedOutput.bStale = false; - - // No variations, simply assign the object/transforms - OutVariationsInstancedObjects.Add(OriginalObj); - OutVariationsInstancedTransforms.Add(InOriginalTransforms[InstObjIdx]); - OutVariationOriginalObjectIdx.Add(InstObjIdx); - OutVariationIndices.Add(0); - - InstancedOutputs.Add(Identifier, CurInstancedOutput); - } - else - { - // Process the potential variations - FHoudiniInstancedOutput& CurInstancedOutput = *FoundInstancedOutput; - UObject *ReplacedOriginalObject = nullptr; - if (CurInstancedOutput.OriginalObject != OriginalObj) - { - ReplacedOriginalObject = CurInstancedOutput.OriginalObject.LoadSynchronous(); - CurInstancedOutput.OriginalObject = OriginalObj; - } - - CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; - CurInstancedOutput.OriginalInstanceIndices = InOriginalInstancedIndices[InstObjIdx]; - - // Shouldnt be needed... - CurInstancedOutput.OriginalObjectIndex = InstObjIdx; - - // Remove any null or deleted variation objects - TArray ObjsToRemove; - for (int32 VarIdx = CurInstancedOutput.VariationObjects.Num() - 1; VarIdx >= 0; --VarIdx) - { - UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill() || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) - { - ObjsToRemove.Add(VarIdx); - } - } - if (ObjsToRemove.Num() > 0) - { - for (const int32 &VarIdx : ObjsToRemove) - { - CurInstancedOutput.VariationObjects.RemoveAt(VarIdx); - CurInstancedOutput.VariationTransformOffsets.RemoveAt(VarIdx); - } - // Force a recompute of variation assignments - CurInstancedOutput.TransformVariationIndices.SetNum(0); - } - - // If we don't have variations, simply use the original object - if (CurInstancedOutput.VariationObjects.Num() <= 0) - { - // No variations? add the original one - CurInstancedOutput.VariationObjects.Add(OriginalObj); - CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); - CurInstancedOutput.TransformVariationIndices.SetNum(0); - } - - // If the number of transforms has changed since the previous cook, - // we need to recompute the variation assignments - if (CurInstancedOutput.TransformVariationIndices.Num() != CurInstancedOutput.OriginalTransforms.Num()) - UpdateVariationAssignements(CurInstancedOutput); - - // Assign variations and their transforms - for (int32 VarIdx = 0; VarIdx < CurInstancedOutput.VariationObjects.Num(); VarIdx++) - { - UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill()) - continue; - - // Get the transforms assigned to that variation - TArray ProcessedTransforms; - ProcessInstanceTransforms(CurInstancedOutput, VarIdx, ProcessedTransforms); - if (ProcessedTransforms.Num() > 0) - { - OutVariationsInstancedObjects.Add(CurrentVariationObject); - OutVariationsInstancedTransforms.Add(ProcessedTransforms); - OutVariationOriginalObjectIdx.Add(InstObjIdx); - OutVariationIndices.Add(VarIdx); - } - } - - CurInstancedOutput.MarkChanged(false); - CurInstancedOutput.bStale = false; - } - } -} - - -void -FHoudiniInstanceTranslator::UpdateVariationAssignements(FHoudiniInstancedOutput& InstancedOutput) -{ - int32 TransformCount = InstancedOutput.OriginalTransforms.Num(); - InstancedOutput.TransformVariationIndices.SetNumZeroed(TransformCount); - - int32 VariationCount = InstancedOutput.VariationObjects.Num(); - if (VariationCount <= 1) - return; - - int nSeed = 1234; - for (int32 Idx = 0; Idx < TransformCount; Idx++) - { - InstancedOutput.TransformVariationIndices[Idx] = fastrand(nSeed) % VariationCount; - } -} - -void -FHoudiniInstanceTranslator::ProcessInstanceTransforms( - FHoudiniInstancedOutput& InstancedOutput, const int32& VariationIdx, TArray& OutProcessedTransforms) -{ - if (!InstancedOutput.VariationObjects.IsValidIndex(VariationIdx)) - return; - - if (!InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx)) - return; - - bool bHasVariations = InstancedOutput.VariationObjects.Num() > 1; - bool bHasTransformOffset = InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx) - ? !InstancedOutput.VariationTransformOffsets[VariationIdx].Equals(FTransform::Identity) - : false; - - if (!bHasVariations && !bHasTransformOffset) - { - // We dont have variations or transform offset, so we can reuse the original transforms as is - OutProcessedTransforms = InstancedOutput.OriginalTransforms; - return; - } - - if (bHasVariations) - { - // We simply need to extract the transforms for this variation - for (int32 TransformIndex = 0; TransformIndex < InstancedOutput.TransformVariationIndices.Num(); TransformIndex++) - { - if (InstancedOutput.TransformVariationIndices[TransformIndex] != VariationIdx) - continue; - - OutProcessedTransforms.Add(InstancedOutput.OriginalTransforms[TransformIndex]); - } - } - else - { - // No variations, we can reuse the original transforms - OutProcessedTransforms = InstancedOutput.OriginalTransforms; - } - - if (bHasTransformOffset) - { - // Get the transform offset for this variation - FVector PositionOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetLocation(); - FQuat RotationOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetRotation(); - FVector ScaleOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetScale3D(); - - FTransform CurrentTransform = FTransform::Identity; - for (int32 TransformIndex = 0; TransformIndex < OutProcessedTransforms.Num(); TransformIndex++) - { - CurrentTransform = OutProcessedTransforms[TransformIndex]; - - // Compute new rotation and scale. - FVector Position = CurrentTransform.GetLocation() + PositionOffset; - FQuat TransformRotation = CurrentTransform.GetRotation() * RotationOffset; - FVector TransformScale3D = CurrentTransform.GetScale3D() * ScaleOffset; - - // Make sure inverse matrix exists - seems to be a bug in Unreal when submitting instances. - // Happens in blueprint as well. - // We want to make sure the scale is not too small, but keep negative values! (Bug 90876) - if (FMath::Abs(TransformScale3D.X) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.X = (TransformScale3D.X > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - if (FMath::Abs(TransformScale3D.Y) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.Y = (TransformScale3D.Y > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - if (FMath::Abs(TransformScale3D.Z) < HAPI_UNREAL_SCALE_SMALL_VALUE) - TransformScale3D.Z = (TransformScale3D.Z > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; - - CurrentTransform.SetLocation(Position); - CurrentTransform.SetRotation(TransformRotation); - CurrentTransform.SetScale3D(TransformScale3D); - - if (CurrentTransform.IsValid()) - OutProcessedTransforms[TransformIndex] = CurrentTransform; - } - } -} - -bool -FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - TArray>& OutInstancedIndices, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::PackedPrimitive) - return false; - - // Get transforms for each instance - TArray InstancerPartTransforms; - InstancerPartTransforms.SetNumZeroed(InHGPO.PartInfo.InstanceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancerPartTransforms( - FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, - HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, InHGPO.PartInfo.InstanceCount), false); - - // Convert the transform to Unreal's coordinate system - TArray InstancerUnrealTransforms; - InstancerUnrealTransforms.SetNumUninitialized(InstancerPartTransforms.Num()); - for (int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); InstanceIdx++) - { - const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; - FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, InstancerUnrealTransforms[InstanceIdx]); - } - - // Get the part ids for parts being instanced - TArray InstancedPartIds; - InstancedPartIds.SetNumZeroed(InHGPO.PartInfo.InstancedPartCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancedPartIds( - FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, - InstancedPartIds.GetData(), 0, InHGPO.PartInfo.InstancedPartCount), false); - - // See if the user has specified an attribute for splitting the instances - // and get the values - FString SplitAttribName = FString(); - TArray AllSplitAttributeValues; - bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( - InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_PRIM, SplitAttribName, AllSplitAttributeValues); - - // Get the level path attribute on the instancer - TArray AllLevelPaths; - const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( - InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_PRIM); - - // Get the bake actor attribute - TArray AllBakeActorNames; - const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( - InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_PRIM); - - // Get the unreal_bake_folder attribute - TArray AllBakeFolders; - const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( - InHGPO.GeoId, HAPI_ATTROWNER_PRIM, AllBakeFolders, InHGPO.PartId); - - // Get the bake outliner folder attribute - TArray AllBakeOutlinerFolders; - const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_PRIM); - - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; - - for (const auto& InstancedPartId : InstancedPartIds) - { - // Create a GeoPartObject corresponding to the instanced part - FHoudiniGeoPartObject InstancedHGPO; - InstancedHGPO.AssetId = InHGPO.AssetId; - InstancedHGPO.AssetName = InHGPO.AssetName; - InstancedHGPO.ObjectId = InHGPO.ObjectId; - InstancedHGPO.ObjectName = InHGPO.ObjectName; - InstancedHGPO.GeoId = InHGPO.GeoId; - InstancedHGPO.PartId = InstancedPartId; - InstancedHGPO.PartName = InHGPO.PartName; - InstancedHGPO.TransformMatrix = InHGPO.TransformMatrix; - - // TODO: Copy more cached data? - - OutInstancedHGPO.Add(InstancedHGPO); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - - TArray Indices; - Indices.SetNum(InstancerUnrealTransforms.Num()); - for (int32 Index = 0; Index < Indices.Num(); ++Index) - { - Indices[Index] = Index; - } - - OutInstancedIndices.Add(Indices); - } - - // If we don't need to split the instances, we're done - if (!bHasSplitAttribute) - return true; - - // TODO: Optimize this! - // Split the instances using the split attribute's values - - // Move the output arrays to temp arrays - TArray UnsplitInstancedHGPOs = OutInstancedHGPO; - TArray> UnsplitInstancedTransforms = OutInstancedTransforms; - TArray> UnsplitInstancedIndices = OutInstancedIndices; - - // Empty the output arrays - OutInstancedHGPO.Empty(); - OutInstancedTransforms.Empty(); - OutInstancedIndices.Empty(); - OutSplitAttributeValue.Empty(); - for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedHGPOs.Num(); ObjIdx++) - { - // Map of split values to transform arrays - TMap> SplitTransformMap; - TMap> SplitIndicesMap; - - TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; - TArray& CurrentIndices = UnsplitInstancedIndices[ObjIdx]; - - int32 NumInstances = CurrentTransforms.Num(); - if (AllSplitAttributeValues.Num() != NumInstances || CurrentIndices.Num() != NumInstances) - continue; - - // Split the transforms using the split values - for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) - { - const FString& SplitAttrValue = AllSplitAttributeValues[InstIdx]; - SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); - SplitIndicesMap.FindOrAdd(SplitAttrValue).Add(CurrentIndices[InstIdx]); - - // Record attributes for any split value we have not yet seen - if (bHasAnyPerSplitAttributes) - { - FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); - if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) - { - PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; - } - if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; - } - if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; - } - if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; - } - } - } - - // Add the objects, transform, split values to the final arrays - for (auto& Iterator : SplitTransformMap) - { - OutSplitAttributeValue.Add(Iterator.Key); - OutInstancedHGPO.Add(UnsplitInstancedHGPOs[ObjIdx]); - OutInstancedTransforms.Add(Iterator.Value); - OutInstancedIndices.Add(SplitIndicesMap[Iterator.Key]); - } - } - - OutSplitAttributeName = SplitAttribName; - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - TArray>& OutInstancedIndices, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::AttributeInstancer) - return false; - - // Look for the unreal instance attribute - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - - // instance attribute on points - bool is_override_attr = false; - HAPI_Result Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT, &AttribInfo); - - // unreal_instance attribute on points - if (Result != HAPI_RESULT_SUCCESS || AttribInfo.exists == false) - { - is_override_attr = true; - Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT, &AttribInfo); - } - - // unreal_instance attribute on detail - if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) - { - is_override_attr = true; - Result = FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL, &AttribInfo); - } - - // Attribute does not exist. - if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the settings indicating if we want to use a default object when the referenced mesh is invalid - bool bDefaultObjectEnabled = true; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bDefaultObjectEnabled = HoudiniRuntimeSettings->bShowDefaultMesh; - } - - // See if the user has specified an attribute for splitting the instances, and get the values - FString SplitAttribName = FString(); - TArray AllSplitAttributeValues; - bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( - InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_POINT, SplitAttribName, AllSplitAttributeValues); - - // Get the level path attribute on the instancer - TArray AllLevelPaths; - const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( - InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_POINT); - - // Get the bake actor attribute - TArray AllBakeActorNames; - const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( - InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_POINT); - - // Get the unreal_bake_folder attribute - TArray AllBakeFolders; - const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( - InHGPO.GeoId, HAPI_ATTROWNER_POINT, AllBakeFolders, InHGPO.PartId); - - // Get the bake outliner folder attribute - TArray AllBakeOutlinerFolders; - const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( - InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_POINT); - - const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; - - // Array used to store the split values per objects - // Will only be used if we have a split attribute - TArray> SplitAttributeValuesPerObject; - - if (AttribInfo.owner == HAPI_ATTROWNER_DETAIL) - { - // If the attribute is on the detail, then its value is applied to all points - TArray DetailInstanceValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InHGPO.GeoId, - InHGPO.PartId, - is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, - AttribInfo, - DetailInstanceValues)) - { - // This should not happen - attribute exists, but there was an error retrieving it. - return false; - } - - if (DetailInstanceValues.Num() <= 0) - { - // No values specified. - return false; - } - - // Attempt to load specified asset. - const FString & AssetName = DetailInstanceValues[0]; - UObject * AttributeObject = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetName, nullptr, LOAD_None, nullptr); - - if (!AttributeObject) - { - // See if the ref is a class that we can instantiate - UClass * FoundClass = FindObject(ANY_PACKAGE, *AssetName); - if (FoundClass != nullptr) - { - // TODO: ensure we'll be able to create an actor from this class! - AttributeObject = FoundClass; - } - } - - if (!AttributeObject && bDefaultObjectEnabled) - { - HOUDINI_LOG_WARNING(TEXT("Failed to load instanced object '%s', using default instance mesh (hidden in game)."), *(AssetName)); - - // Couldn't load the referenced object, use the default reference mesh - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (!DefaultReferenceSM || DefaultReferenceSM->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to load the default instance mesh.")); - return false; - } - AttributeObject = DefaultReferenceSM; - } - - // Attach the objectPtr/transforms/bHiddenInGame if the attributeObject is created successfully - // (with either the actual referenced object or the default placeholder object) - if (AttributeObject) - { - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - - TArray Indices; - Indices.SetNum(InstancerUnrealTransforms.Num()); - for (int32 Index = 0; Index < Indices.Num(); ++Index) - { - Indices[Index] = Index; - } - - OutInstancedIndices.Add(Indices); - - if(bHasSplitAttribute) - SplitAttributeValuesPerObject.Add(AllSplitAttributeValues); - } - } - else - { - // Attribute is on points, so we may have different values for each of them - TArray PointInstanceValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( - InHGPO.GeoId, - InHGPO.PartId, - is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, - AttribInfo, - PointInstanceValues)) - { - // This should not happen - attribute exists, but there was an error retrieving it. - return false; - } - - // The attribute is on points, so the number of points must match number of transforms. - if (!ensure(PointInstanceValues.Num() == InstancerUnrealTransforms.Num())) - { - // This should not happen, we have mismatch between number of instance values and transforms. - return false; - } - - // If instance attribute exists on points, we need to get all the unique values. - // This will give us all the unique object we want to instance - TMap ObjectsToInstance; - for (const auto& Iter : PointInstanceValues) - { - if (!ObjectsToInstance.Contains(Iter)) - { - // To avoid trying to load an object that fails multiple times, - // still add it to the array if null so we can still skip further attempts - UObject * AttributeObject = StaticLoadObject( - UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); - - if (!AttributeObject) - { - // See if the ref is a class that we can instantiate - UClass * FoundClass = FindObject(ANY_PACKAGE, *Iter); - if (FoundClass != nullptr) - { - // TODO: ensure we'll be able to create an actor from this class! - AttributeObject = FoundClass; - } - } - - ObjectsToInstance.Add(Iter, AttributeObject); - } - } - - // Iterates through all the unique objects and get their corresponding transforms - bool Success = false; - for (auto Iter : ObjectsToInstance) - { - bool bHiddenInGame = false; - // Check that we managed to load this object - UObject * AttributeObject = Iter.Value; - if (!AttributeObject && bDefaultObjectEnabled) - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load instanced object '%s', use default mesh (hidden in game)."), *(Iter.Key)); - - // If failed to load this object, add default reference mesh - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) - { - AttributeObject = DefaultReferenceSM; - bHiddenInGame = true; - } - else// Failed to load default reference mesh object - { - HOUDINI_LOG_WARNING(TEXT("Failed to load default mesh.")); - continue; - } - } - - if (!AttributeObject) - continue; - - if (!bHasSplitAttribute) - { - // No Split attribute: - // Extract the transform values that correspond to this object, and add them to the output arrays - const FString & InstancePath = Iter.Key; - TArray ObjectTransforms; - TArray ObjectIndices; - - for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) - { - if (InstancePath.Equals(PointInstanceValues[Idx])) - { - ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); - ObjectIndices.Add(Idx); - } - } - - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(ObjectTransforms); - OutInstancedIndices.Add(ObjectIndices); - Success = true; - } - else - { - // We have a split attribute: - // Extract the transform values and split attribute values for this object, - // add them to the output arrays, and we will process the splits after - const FString & InstancePath = Iter.Key; - TArray ObjectTransforms; - TArray ObjectIndices; - TArray ObjectSplitValues; - for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) - { - if (InstancePath.Equals(PointInstanceValues[Idx])) - { - ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); - ObjectIndices.Add(Idx); - ObjectSplitValues.Add(AllSplitAttributeValues[Idx]); - } - } - - OutInstancedObjects.Add(AttributeObject); - OutInstancedTransforms.Add(ObjectTransforms); - OutInstancedIndices.Add(ObjectIndices); - SplitAttributeValuesPerObject.Add(ObjectSplitValues); - Success = true; - } - } - - if (!Success) - return false; - } - - // If we don't need to split the instances, we're done - if (!bHasSplitAttribute) - return true; - - // Split the instances one more time, this time using the split values - - // Move the output arrays to temp arrays - TArray UnsplitInstancedObjects = OutInstancedObjects; - TArray> UnsplitInstancedTransforms = OutInstancedTransforms; - TArray> UnsplitInstancedIndices = OutInstancedIndices; - - // Empty the output arrays - OutInstancedObjects.Empty(); - OutInstancedTransforms.Empty(); - OutInstancedIndices.Empty(); - - // TODO: Output the split values as well! - OutSplitAttributeValue.Empty(); - for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedObjects.Num(); ObjIdx++) - { - UObject* InstancedObject = UnsplitInstancedObjects[ObjIdx]; - - // Map of split values to transform arrays - TMap> SplitTransformMap; - TMap> SplitIndicesMap; - - TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; - TArray& CurrentIndices = UnsplitInstancedIndices[ObjIdx]; - TArray& CurrentSplits = SplitAttributeValuesPerObject[ObjIdx]; - - int32 NumInstances = CurrentTransforms.Num(); - if (CurrentSplits.Num() != NumInstances || CurrentIndices.Num() != NumInstances) - continue; - - // Split the transforms using the split values - for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) - { - const FString& SplitAttrValue = CurrentSplits[InstIdx]; - SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); - SplitIndicesMap.FindOrAdd(SplitAttrValue).Add(CurrentIndices[InstIdx]); - - // Record attributes for any split value we have not yet seen - if (bHasAnyPerSplitAttributes) - { - FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); - if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) - { - PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; - } - if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; - } - if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; - } - if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) - { - PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; - } - } - } - - // Add the objects, transform, split values to the final arrays - for (auto& Iterator : SplitTransformMap) - { - OutSplitAttributeValue.Add(Iterator.Key); - OutInstancedObjects.Add(InstancedObject); - OutInstancedTransforms.Add(Iterator.Value); - OutInstancedIndices.Add(SplitIndicesMap[Iterator.Key]); - } - } - - OutSplitAttributeName = SplitAttribName; - - return true; -} - - -bool -FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - TArray>& OutInstancedIndices) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::OldSchoolAttributeInstancer) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the objects IDs to instanciate - int32 NumPoints = InHGPO.PartInfo.PointCount; - TArray InstancedObjectIds; - InstancedObjectIds.SetNumUninitialized(NumPoints); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedObjectIds( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InstancedObjectIds.GetData(), 0, NumPoints), false); - - // Find the set of instanced object ids and locate the corresponding parts - TSet UniqueInstancedObjectIds(InstancedObjectIds); - - // Locate all the HoudiniGeoPartObject that corresponds to the instanced object IDs - for (int32 InstancedObjectId : UniqueInstancedObjectIds) - { - // Get the parts that correspond to that object Id - TArray PartsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) - { - if (OutHGPO.Type != EHoudiniPartType::Mesh) - continue; - - if (OutHGPO.bIsInstanced) - continue; - - if (InstancedObjectId != OutHGPO.ObjectId) - continue; - - PartsToInstance.Add(OutHGPO); - } - } - - // Extract only the transforms that correspond to that specific object ID - TArray InstanceTransforms; - TArray InstanceIndices; - for (int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix) - { - if ((InstancedObjectIds[Ix] == InstancedObjectId) && (InstancerUnrealTransforms.IsValidIndex(Ix))) - { - InstanceTransforms.Add(InstancerUnrealTransforms[Ix]); - InstanceIndices.Add(Ix); - } - } - - // Add the instanced parts and their transforms to the output arrays - for (const auto& PartToInstance : PartsToInstance) - { - OutInstancedHGPO.Add(PartToInstance); - OutInstancedTransforms.Add(InstanceTransforms); - OutInstancedIndices.Add(InstanceIndices); - } - } - - if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0 && OutInstancedIndices.Num() > 0) - return true; - - return false; -} - - -bool -FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - TArray>& OutInstancedIndices) -{ - if (InHGPO.InstancerType != EHoudiniInstancerType::ObjectInstancer) - return false; - - if (InHGPO.ObjectInfo.ObjectToInstanceID < 0) - return false; - - // Get the instance transforms - TArray InstancerUnrealTransforms; - if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) - { - // failed to get instance transform - return false; - } - - // Get the parts that correspond to that Object Id - TArray PartsToInstance; - for (const auto& Output : InAllOutputs) - { - if (!Output || Output->Type != EHoudiniOutputType::Mesh) - continue; - - for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) - { - if (OutHGPO.Type != EHoudiniPartType::Mesh) - continue; - - /* - // But the instanced geo is actually not marked as instanced - if (!OutHGPO.bIsInstanced) - continue; - */ - - if (InHGPO.ObjectInfo.ObjectToInstanceID != OutHGPO.ObjectId) - continue; - - PartsToInstance.Add(OutHGPO); - } - } - - // Add found HGPO and transforms to the output arrays - for (auto& InstanceHGPO : PartsToInstance) - { - InstanceHGPO.TransformMatrix = InHGPO.TransformMatrix; - - // TODO: - //InstanceHGPO.UpdateCustomName(); - - OutInstancedHGPO.Add(InstanceHGPO); - OutInstancedTransforms.Add(InstancerUnrealTransforms); - - TArray Indices; - Indices.SetNum(InstancerUnrealTransforms.Num()); - for (int32 Index = 0; Index < Indices.Num(); ++Index) - { - Indices[Index] = Index; - } - - OutInstancedIndices.Add(Indices); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent* OldComponent, - USceneComponent*& NewComponent, - const bool& InIsSplitMeshInstancer, - const bool& InIsFoliageInstancer, - const TArray& InstancerMaterials, - const TArray& OriginalInstancerObjectIndices, - const int32& InstancerObjectIdx, - const bool& bForceHISM) -{ - enum InstancerComponentType - { - Invalid = -1, - InstancedStaticMeshComponent = 0, - HierarchicalInstancedStaticMeshComponent = 1, - MeshSplitInstancerComponent = 2, - HoudiniInstancedActorComponent = 3, - StaticMeshComponent = 4, - HoudiniStaticMeshComponent = 5, - Foliage = 6 - }; - - // See if we can reuse the old component - InstancerComponentType OldType = InstancerComponentType::Invalid; - if (OldComponent/*&& !OldComponent->IsPendingKill()*/) // The old component could be marked as pending kill - { - if(OldComponent->IsA()) - OldType = Foliage; - else if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) - OldType = Foliage; - else if (OldComponent->IsA()) - OldType = HierarchicalInstancedStaticMeshComponent; - else if (OldComponent->IsA()) - OldType = InstancedStaticMeshComponent; - else if (OldComponent->IsA()) - OldType = MeshSplitInstancerComponent; - else if (OldComponent->IsA()) - OldType = HoudiniInstancedActorComponent; - else if (OldComponent->IsA()) - OldType = StaticMeshComponent; - else if (OldComponent->IsA()) - OldType = HoudiniStaticMeshComponent; - } - - // See what type of component we want to create - InstancerComponentType NewType = InstancerComponentType::Invalid; - - UStaticMesh * StaticMesh = Cast(InstancedObject); - UFoliageType * FoliageType = Cast(InstancedObject); - - UHoudiniStaticMesh * HSM = nullptr; - if (!StaticMesh && !FoliageType) - HSM = Cast(InstancedObject); - - if (StaticMesh) - { - if (InstancedObjectTransforms.Num() == 1) - NewType = StaticMeshComponent; - else if (InIsFoliageInstancer) - NewType = Foliage; - else if (InIsSplitMeshInstancer) - NewType = MeshSplitInstancerComponent; - else if(StaticMesh->GetNumLODs() > 1 || bForceHISM) - NewType = HierarchicalInstancedStaticMeshComponent; - else - NewType = InstancedStaticMeshComponent; - } - else if (HSM) - { - if (InstancedObjectTransforms.Num() == 1) - NewType = HoudiniStaticMeshComponent; - else - { - HOUDINI_LOG_ERROR(TEXT("More than one instance transform encountered for UHoudiniStaticMesh: %s"), *(HSM->GetPathName())); - NewType = Invalid; - return false; - } - } - else if (FoliageType) - { - NewType = Foliage; - } - else - { - NewType = HoudiniInstancedActorComponent; - } - - if (OldType == NewType) - NewComponent = OldComponent; - - // First valid index in the original instancer part - // This should be used to access attributes that are store for the whole part, not split - // (ie, GenericProperty Attributes) - int32 FirstOriginalIndex = OriginalInstancerObjectIndices.Num() > 0 ? OriginalInstancerObjectIndices[0] : 0; - - // InstancerMaterials has all the material assignement per instance, - // Fetch the first one for the components that can only use one material - UMaterialInterface* InstancerMaterial = InstancerMaterials.Num() > 0 ? InstancerMaterials[0] : nullptr; - - bool bSuccess = false; - switch (NewType) - { - case InstancedStaticMeshComponent: - case HierarchicalInstancedStaticMeshComponent: - { - // Create an Instanced Static Mesh Component - bSuccess = CreateOrUpdateInstancedStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM, FirstOriginalIndex); - } - break; - - case MeshSplitInstancerComponent: - { - bSuccess = CreateOrUpdateMeshSplitInstancerComponent( - StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterials); - } - break; - - case HoudiniInstancedActorComponent: - { - bSuccess = CreateOrUpdateInstancedActorComponent( - InstancedObject, InstancedObjectTransforms, OriginalInstancerObjectIndices, AllPropertyAttributes, ParentComponent, NewComponent); - } - break; - - case StaticMeshComponent: - { - // Create a Static Mesh Component - bSuccess = CreateOrUpdateStaticMeshComponent( - StaticMesh, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - break; - - case HoudiniStaticMeshComponent: - { - // Create a Houdini Static Mesh Component - bSuccess = CreateOrUpdateHoudiniStaticMeshComponent( - HSM, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - break; - - case Foliage: - { - bSuccess = CreateOrUpdateFoliageInstances( - StaticMesh, FoliageType, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); - } - } - - if (!NewComponent) - return false; - - NewComponent->SetMobility(ParentComponent->Mobility); - NewComponent->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepRelativeTransform); - - // For single instance, that generates a SMC, the transform is already set on the component - // TODO: Should cumulate transform in that case? - if(NewType != StaticMeshComponent && NewType != HoudiniStaticMeshComponent) - NewComponent->SetRelativeTransform(InstancerGeoPartObject.TransformMatrix); - - // Only register if we have a valid component - if (NewComponent->GetOwner() && NewComponent->GetWorld()) - NewComponent->RegisterComponent(); - - // If the old component couldn't be reused, dettach/ destroy it - if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) - { - RemoveAndDestroyComponent(OldComponent, nullptr); - } - - return bSuccess; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial, /*=nullptr*/ - const bool & bForceHISM, - const int32& InstancerObjectIdx) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast(CreatedInstancedComponent); - if (!InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill()) - { - if (InstancedStaticMesh->GetNumLODs() > 1 || bForceHISM) - { - // If the mesh has LODs, use Hierarchical ISMC - InstancedStaticMeshComponent = NewObject( - ComponentOuter, UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - } - else - { - // If the mesh doesnt have LOD, we can use a regular ISMC - InstancedStaticMeshComponent = NewObject( - ComponentOuter, UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - } - - // Change the creation method so the component is listed in the details panels - if (InstancedStaticMeshComponent) - { - InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; - } - - bCreatedNewComponent = true; - } - - if (!InstancedStaticMeshComponent) - return false; - - InstancedStaticMeshComponent->SetStaticMesh(InstancedStaticMesh); - - if (InstancedStaticMeshComponent->GetBodyInstance()) - { - InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; - } - - InstancedStaticMeshComponent->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances themselves - InstancedStaticMeshComponent->ClearInstances(); - InstancedStaticMeshComponent->AddInstances(InstancedObjectTransforms, false); - - // Apply generic attributes if we have any - UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, InstancerObjectIdx); - - // Assign the new ISMC / HISMC to the output component if we created a new one - if(bCreatedNewComponent) - CreatedInstancedComponent = InstancedStaticMeshComponent; - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& OriginalInstancerObjectIndices, - const TArray& AllPropertyAttributes, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent) -{ - if (!InstancedObject) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniInstancedActorComponent* InstancedActorComponent = Cast(CreatedInstancedComponent); - if (!InstancedActorComponent || InstancedActorComponent->IsPendingKill()) - { - // If the mesh doesnt have LOD, we can use a regular ISMC - InstancedActorComponent = NewObject( - ComponentOuter, UHoudiniInstancedActorComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - InstancedActorComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!InstancedActorComponent) - return false; - - // See if the instanced object has changed - bool bInstancedObjectHasChanged = (InstancedObject != InstancedActorComponent->GetInstancedObject()); - if (bInstancedObjectHasChanged) - { - // All actors will need to be respawned, invalidate all of them - InstancedActorComponent->ClearAllInstances(); - - // Update the HIAC's instanced asset - InstancedActorComponent->SetInstancedObject(InstancedObject); - } - - // Get the level where we want to spawn the actors - ULevel* SpawnLevel = ParentComponent->GetOwner() ? ParentComponent->GetOwner()->GetLevel() : nullptr; - if (!SpawnLevel) - return false; - - // Set the number of needed instances - InstancedActorComponent->SetNumberOfInstances(InstancedObjectTransforms.Num()); - - for (int32 Idx = 0; Idx < InstancedObjectTransforms.Num(); Idx++) - { - // if we already have an actor, we can reuse it - const FTransform& CurTransform = InstancedObjectTransforms[Idx]; - - // Get the current instance - // If null, we need to create a new one, else we can reuse the actor - AActor* CurInstance = InstancedActorComponent->GetInstancedActorAt(Idx); - if (!CurInstance || CurInstance->IsPendingKill()) - { - CurInstance = SpawnInstanceActor(CurTransform, SpawnLevel, InstancedActorComponent); - InstancedActorComponent->SetInstanceAt(Idx, CurTransform, CurInstance); - } - else - { - // We can simply update the actor's transform - InstancedActorComponent->SetInstanceTransformAt(Idx, CurTransform); - } - - // Update the generic properties for that instance if any - UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, OriginalInstancerObjectIndices[Idx]); - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - { - CreatedInstancedComponent = InstancedActorComponent; - } - - return true; -} - -// Create or update a MSIC -bool -FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - const TArray& InInstancerMaterials) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniMeshSplitInstancerComponent* MeshSplitComponent = Cast(CreatedInstancedComponent); - if (!MeshSplitComponent || MeshSplitComponent->IsPendingKill()) - { - // If the mesh doesn't have LOD, we can use a regular ISMC - MeshSplitComponent = NewObject( - ComponentOuter, UHoudiniMeshSplitInstancerComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - MeshSplitComponent->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!MeshSplitComponent) - return false; - - MeshSplitComponent->SetStaticMesh(InstancedStaticMesh); - MeshSplitComponent->SetOverrideMaterials(InInstancerMaterials); - - // Now add the instances - MeshSplitComponent->SetInstanceTransforms(InstancedObjectTransforms); - - // Check for instance colors - TArray InstanceColorOverrides; - bool ColorOverrideAttributeFound = false; - - // Look for the unreal_instance_color attribute on points - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_POINT, &AttributeInfo)) - { - ColorOverrideAttributeFound = AttributeInfo.exists; - } - - // Look for the unreal_instance_color attribute on prims? (why? original code) - if (!ColorOverrideAttributeFound) - { - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_PRIM, &AttributeInfo)) - { - ColorOverrideAttributeFound = AttributeInfo.exists; - } - } - - if (ColorOverrideAttributeFound) - { - if (AttributeInfo.tupleSize == 4) - { - // Allocate sufficient buffer for data. - InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)InstanceColorOverrides.GetData(), 0, AttributeInfo.count)) - { - InstanceColorOverrides.Empty(); - } - } - else if (AttributeInfo.tupleSize == 3) - { - // Allocate sufficient buffer for data. - TArray FloatValues; - FloatValues.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, - HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)FloatValues.GetData(), 0, AttributeInfo.count)) - { - - // Allocate sufficient buffer for data. - InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); - - // Convert float to FLinearColors - for (int32 ColorIdx = 0; ColorIdx < InstanceColorOverrides.Num(); ColorIdx++) - { - InstanceColorOverrides[ColorIdx].R = FloatValues[ColorIdx * AttributeInfo.tupleSize + 0]; - InstanceColorOverrides[ColorIdx].G = FloatValues[ColorIdx * AttributeInfo.tupleSize + 1]; - InstanceColorOverrides[ColorIdx].B = FloatValues[ColorIdx * AttributeInfo.tupleSize + 2]; - InstanceColorOverrides[ColorIdx].A = 1.0; - } - FloatValues.Empty(); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_COLOR " must be a float[4] or float[3] prim/point attribute")); - } - } - - // if we have vertex color overrides, apply them now -#if WITH_EDITOR - if (InstanceColorOverrides.Num() > 0) - { - // Convert the color attribute to FColor - TArray InstanceColors; - InstanceColors.SetNumUninitialized(InstanceColorOverrides.Num()); - for (int32 ix = 0; ix < InstanceColors.Num(); ++ix) - { - InstanceColors[ix] = InstanceColorOverrides[ix].GetClamped().ToFColor(false); - } - - // Apply them to the instances - TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); - for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) - { - UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - if (!InstanceColors.IsValidIndex(InstIndex)) - continue; - - MeshPaintHelpers::FillStaticMeshVertexColors(CurSMC, -1, InstanceColors[InstIndex], FColor::White); - - //CurSMC->UnregisterComponent(); - //CurSMC->ReregisterComponent(); - - { - // We're only changing instanced vertices on this specific mesh component, so we - // only need to detach our mesh component - FComponentReregisterContext ComponentReregisterContext(CurSMC); - for (auto& CurLODData : CurSMC->LODData) - { - BeginInitResource(CurLODData.OverrideVertexColors); - } - } - - //FIXME: How to get rid of the warning about fixup vertex colors on load? - //SMC->FixupOverrideColorsIfNecessary(); - } - } -#endif - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - // TODO: Optimize - // Loop on attributes first, then components, - // if failing to find the attrib on a component, skip the rest - if (AllPropertyAttributes.Num() > 0) - { - TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); - for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) - { - UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) - continue; - - UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); - } - } - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = MeshSplitComponent; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const int32& InOriginalIndex, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - if (!InstancedStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UStaticMeshComponent* SMC = Cast(CreatedInstancedComponent); - if (!SMC || SMC->IsPendingKill()) - { - // Create a new StaticMeshComponent - SMC = NewObject( - ComponentOuter, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - SMC->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!SMC) - return false; - - SMC->SetStaticMesh(InstancedStaticMesh); - SMC->GetBodyInstance()->bAutoWeld = false; - - SMC->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedStaticMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - SMC->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances Transform - if (InstancedObjectTransforms.Num() > 0) - { - SMC->SetRelativeTransform(InstancedObjectTransforms[0]); - } - - // Apply generic attributes if we have any - UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, InOriginalIndex); - - // Assign the new ISMC / HISMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = SMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( - UHoudiniStaticMesh* InstancedProxyStaticMesh, - const TArray& InstancedObjectTransforms, - const int32& InOriginalIndex, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - if (!InstancedProxyStaticMesh) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - bool bCreatedNewComponent = false; - UHoudiniStaticMeshComponent* HSMC = Cast(CreatedInstancedComponent); - if (!HSMC || HSMC->IsPendingKill()) - { - // Create a new StaticMeshComponent - HSMC = NewObject( - ComponentOuter, UHoudiniStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - // Change the creation method so the component is listed in the details panels - HSMC->CreationMethod = EComponentCreationMethod::Instance; - - bCreatedNewComponent = true; - } - - if (!HSMC) - return false; - - HSMC->SetMesh(InstancedProxyStaticMesh); - - HSMC->OverrideMaterials.Empty(); - if (InstancerMaterial) - { - int32 MeshMaterialCount = InstancedProxyStaticMesh->GetNumStaticMaterials(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - HSMC->SetMaterial(Idx, InstancerMaterial); - } - - // Now add the instances Transform - HSMC->SetRelativeTransform(InstancedObjectTransforms[0]); - - // Apply generic attributes if we have any - // TODO: Handle variations w/ index - UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, InOriginalIndex); - - // Assign the new HSMC to the output component if we created a new one - if (bCreatedNewComponent) - CreatedInstancedComponent = HSMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - - -bool -FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( - UStaticMesh* InstancedStaticMesh, - UFoliageType* InFoliageType, - const TArray& InstancedObjectTransforms, - const int32& FirstOriginalIndex, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& NewInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/) -{ - // We need either a valid SM or a valid Foliage Type - if ((!InstancedStaticMesh || InstancedStaticMesh->IsPendingKill()) - && (!InFoliageType || InFoliageType->IsPendingKill())) - return false; - - if (!ParentComponent || ParentComponent->IsPendingKill()) - return false; - - UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) - ComponentOuter = ParentComponent->GetOwner(); - - AActor* OwnerActor = ParentComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return false; - - // See if we already have a FoliageType for that static mesh - bool bCreatedNew = false; - UFoliageType *FoliageType = InFoliageType; - if (!FoliageType || FoliageType->IsPendingKill()) - { - // Foliage Type wasnt specified, only the mesh, try to find an existing foliage for that SM - FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InstancedStaticMesh); - } - else - { - // Foliage Type was specified, see if we can get its static mesh - UFoliageType_InstancedStaticMesh* FoliageISM = Cast(InFoliageType); - if (FoliageISM) - { - InstancedStaticMesh = FoliageISM->GetStaticMesh(); - } - - // See a component already exist on the actor - // If we cant find Foliage info for that foliage type, a new one will be created. - // when we call FindOrAddMesh - bCreatedNew = InstancedFoliageActor->FindInfo(FoliageType) == nullptr; - } - - if (!FoliageType || FoliageType->IsPendingKill()) - { - // We need to create a new FoliageType for this Static Mesh - // TODO: Add foliage default settings - InstancedFoliageActor->AddMesh(InstancedStaticMesh, &FoliageType); - bCreatedNew = true; - } - - if (!bCreatedNew && NewInstancedComponent) - { - // TODO: Shouldnt be needed anymore - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); - } - - // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it - FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); - if (!FoliageInfo) - return false; - - FTransform HoudiniAssetTransform = ParentComponent->GetComponentTransform(); - FFoliageInstance FoliageInstance; - int32 CurrentInstanceCount = 0; - - FoliageInfo->ReserveAdditionalInstances(InstancedFoliageActor, FoliageType, InstancedObjectTransforms.Num()); - for (auto CurrentTransform : InstancedObjectTransforms) - { - // Use our parent component for the base component of the instances, - // this will allow us to clean the instances by component - FoliageInstance.BaseComponent = ParentComponent; - - // TODO: FIX ME! - // Somehow, the first time when we create the Foliage type, instances need to be added with relative transform - // On subsequent cooks, they are actually expecting world transform - if (bCreatedNew) - { - FoliageInstance.Location = CurrentTransform.GetLocation(); - FoliageInstance.Rotation = CurrentTransform.GetRotation().Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D(); - } - else - { - FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); - FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); - FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); - } - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - CurrentInstanceCount++; - } - - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); - if (IsValid(FoliageHISMC)) - { - // TODO: This was due to a bug in UE4.22-20, check if still needed! - FoliageHISMC->BuildTreeIfOutdated(true, true); - - if (InstancerMaterial) - { - FoliageHISMC->OverrideMaterials.Empty(); - int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->StaticMaterials.Num() : 1; - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - FoliageHISMC->SetMaterial(Idx, InstancerMaterial); - } - } - - // Try to apply generic properties attributes - // either on the instancer, mesh or foliage type - // TODO: Use proper atIndex!! - UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, FirstOriginalIndex); - UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, FirstOriginalIndex); - UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, FirstOriginalIndex); - - if (IsValid(FoliageHISMC)) - NewInstancedComponent = FoliageHISMC; - - // TODO: - // We want to make this invisible if it's a collision instancer. - //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); - - return true; -} - -bool -FHoudiniInstanceTranslator::HapiGetInstanceTransforms( - const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancerUnrealTransforms) -{ - // Get the instance transforms - int32 PointCount = InHGPO.PartInfo.PointCount; - if (PointCount <= 0) - return false; - - TArray InstanceTransforms; - InstanceTransforms.SetNum(PointCount); - for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++) - FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx])); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetInstanceTransformsOnPart( - FHoudiniEngine::Get().GetSession(), - InHGPO.GeoId, InHGPO.PartId, HAPI_SRT, - &InstanceTransforms[0], 0, PointCount)) - { - InstanceTransforms.SetNum(0); - - // TODO: Warning? error? - return false; - } - - // Convert the transform to Unreal's coordinate system - OutInstancerUnrealTransforms.SetNumZeroed(InstanceTransforms.Num()); - for (int32 InstanceIdx = 0; InstanceIdx < InstanceTransforms.Num(); InstanceIdx++) - { - const auto& InstanceTransform = InstanceTransforms[InstanceIdx]; - FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, OutInstancerUnrealTransforms[InstanceIdx]); - } - - return true; -} - -bool -FHoudiniInstanceTranslator::GetGenericPropertiesAttributes( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutPropertyAttributes) -{ - // List all the generic property detail attributes ... - int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); - - // .. then get all the values for the primitive property attributes - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, -1); - - // .. then finally, all values for point uproperty attributes - // TODO: !! get the correct Index here? - FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( - (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, -1); - - return FoundCount > 0; -} - -bool -FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes, const int32& AtIndex) -{ - if (!IsValid(InObject)) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (const auto& CurrentPropAttribute : InAllPropertyAttributes) - { - if (CurrentPropAttribute.AttributeName.Equals(TEXT("NumCustomDataFloats"), ESearchCase::IgnoreCase)) - { - // Skip, as setting NumCustomDataFloats this way causes Unreal to crash! - HOUDINI_LOG_WARNING( - TEXT("Skipping UProperty %s on %s, custom data floats should be modified via the unreal_num_custom_floats and unreal_per_instance_custom_dataX attributes"), - *CurrentPropAttribute.AttributeName, *InObject->GetName()); - continue; - } - - // Update the current property for the given instance index - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute, AtIndex)) - continue; - - // Success! - NumSuccess++; - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, InObject->GetClass() ? *InObject->GetClass()->GetName() : TEXT("Object"), *InObject->GetName()); - } - - return (NumSuccess > 0); -} - -bool -FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent, UObject* InFoliageObject) -{ - if (!InComponent || InComponent->IsPendingKill()) - return false; - - UFoliageInstancedStaticMeshComponent* FISMC = Cast(InComponent); - if (FISMC && !FISMC->IsPendingKill()) - { - // Make sure foliage our foliage instances have been removed - USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) - CleanupFoliageInstances(FISMC, InFoliageObject, ParentComponent); - - // do not delete FISMC that still have instances left - // as we have cleaned up our instances before, these have been hand-placed - if (FISMC->GetInstanceCount() > 0) - return false; - } - - USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - return true; - } - - return false; -} - - -bool -FHoudiniInstanceTranslator::GetMaterialOverridesFromAttributes( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutMaterialAttributes) -{ - HAPI_AttributeInfo MaterialAttributeInfo; - FHoudiniApi::AttributeInfo_Init(&MaterialAttributeInfo); - - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL, MaterialAttributeInfo, OutMaterialAttributes); - - /* - // TODO: Support material instances on instancers... - // see FHoudiniMaterialTranslator::CreateMaterialInstances() - // If material attribute and fallbacks were not found, check the material instance attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, MaterialAttributeInfo, OutMaterialAttributes); - } - */ - - if (!MaterialAttributeInfo.exists - /*&& MaterialAttributeInfo.owner != HAPI_ATTROWNER_PRIM - && MaterialAttributeInfo.owner != HAPI_ATTROWNER_DETAIL*/) - { - //HOUDINI_LOG_WARNING(TEXT("Instancer: the unreal_material attribute must be a primitive or detail attribute, ignoring the attribute.")); - OutMaterialAttributes.Empty(); - return false; - } - - return true; -} - -bool -FHoudiniInstanceTranslator::GetInstancerMaterials( - const TArray& MaterialAttributes, TArray& OutInstancerMaterials) -{ - // Use a map to avoid attempting to load the object for each instance - TMap MaterialMap; - - bool bHasValidMaterial = false; - for (auto& CurrentMatString : MaterialAttributes) - { - UMaterialInterface* CurrentMaterialInterface = nullptr; - UMaterialInterface** FoundMaterial = MaterialMap.Find(CurrentMatString); - if (!FoundMaterial) - { - // See if we can find a material interface that matches the attribute - CurrentMaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentMatString, nullptr, LOAD_NoWarn, nullptr)); - - // Check validity - if (!CurrentMaterialInterface || CurrentMaterialInterface->IsPendingKill()) - CurrentMaterialInterface = nullptr; - else - bHasValidMaterial = true; - - // Add what we found to the material map to avoid unnecessary loads - MaterialMap.Add(CurrentMatString, CurrentMaterialInterface); - } - else - { - // Reuse what we previously found - CurrentMaterialInterface = *FoundMaterial; - } - - OutInstancerMaterials.Add(CurrentMaterialInterface); - } - - // IF we couldn't find at least one valid material interface, empty the array - if (!bHasValidMaterial) - OutInstancerMaterials.Empty(); - - return true; -} - -bool -FHoudiniInstanceTranslator::GetInstancerMaterials( - const int32& InGeoNodeId, const int32& InPartId, TArray& OutInstancerMaterials) -{ - TArray MaterialAttributes; - if (!GetMaterialOverridesFromAttributes(InGeoNodeId, InPartId, MaterialAttributes)) - MaterialAttributes.Empty(); - - return GetInstancerMaterials(MaterialAttributes, OutInstancerMaterials); -} - -bool -FHoudiniInstanceTranslator::GetVariationMaterials( - FHoudiniInstancedOutput* InInstancedOutput, - const int32& InVariationIndex, - const TArray& InOriginalIndices, - const TArray& InInstancerMaterials, - TArray& OutVariationMaterials) -{ - if (!InInstancedOutput || InInstancerMaterials.Num() <= 0) - return false; - - // No variations, reuse the full array - if (InInstancedOutput->VariationObjects.Num() == 1) - { - for (int32 Idx = 0; Idx < InOriginalIndices.Num(); Idx++) - { - if (InInstancerMaterials.IsValidIndex(InOriginalIndices[Idx])) - OutVariationMaterials.Add(InInstancerMaterials[InOriginalIndices[Idx]]); - else - OutVariationMaterials.Add(InInstancerMaterials[0]); - } - - return true; - } - - // If we have variations, see if we can use the instancer mat array - // TODO: FIX ME! this wont work if we have split the instancer and added variations at the same time! - if (InInstancedOutput->TransformVariationIndices.Num() == InInstancerMaterials.Num()) - { - for (int32 Idx = 0; Idx < InInstancedOutput->TransformVariationIndices.Num(); Idx++) - { - int32 VariationAssignment = InInstancedOutput->TransformVariationIndices[Idx]; - if (VariationAssignment != InVariationIndex) - continue; - - OutVariationMaterials.Add(InInstancerMaterials[Idx]); - } - } - else - { - for (int32 Idx = 0; Idx < InOriginalIndices.Num(); Idx++) - { - if (InInstancerMaterials.IsValidIndex(InOriginalIndices[Idx])) - OutVariationMaterials.Add(InInstancerMaterials[InOriginalIndices[Idx]]); - else - OutVariationMaterials.Add(InInstancerMaterials[0]); - } - } - - return true; -} - -bool -FHoudiniInstanceTranslator::IsSplitInstancer(const int32& InGeoId, const int32& InPartId) -{ - bool bSplitMeshInstancer = false; - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; - bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); - - if (!bSplitMeshInstancer) - { - // Try on primitive - Owner = HAPI_ATTROWNER_PRIM; - bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); - } - - if (!bSplitMeshInstancer) - return false; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, - Owner, &AttributeInfo), false); - - if (!AttributeInfo.exists || AttributeInfo.count <= 0) - return false; - - TArray IntData; - // Allocate sufficient buffer for data. - IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, - &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); - - return (IntData[0] != 0); -} - -bool -FHoudiniInstanceTranslator::IsFoliageInstancer(const int32& InGeoId, const int32& InPartId) -{ - bool bIsFoliageInstancer = false; - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - - if (!bIsFoliageInstancer) - { - // Try on primitive - Owner = HAPI_ATTROWNER_PRIM; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - } - - if (!bIsFoliageInstancer) - { - // Finally, try on points - Owner = HAPI_ATTROWNER_POINT; - bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); - } - - if (!bIsFoliageInstancer) - return false; - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - Owner, &AttributeInfo), false); - - if (!AttributeInfo.exists || AttributeInfo.count <= 0) - return false; - - // We only support int/float attributes - if (AttributeInfo.storage == HAPI_STORAGETYPE_INT) - { - TArray IntData; - // Allocate sufficient buffer for data. - IntData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - &AttributeInfo, 0, &IntData[0], 0, AttributeInfo.count), false); - - return (IntData[0] != 0); - } - else if (AttributeInfo.storage == HAPI_STORAGETYPE_FLOAT) - { - TArray FloatData; - // Allocate sufficient buffer for data. - FloatData.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, - &AttributeInfo, 0, &FloatData[0], 0, AttributeInfo.count), false); - - return (FloatData[0] != 0); - } - - return false; -} - - -AActor* -FHoudiniInstanceTranslator::SpawnInstanceActor( - const FTransform& InTransform, - ULevel* InSpawnLevel, - UHoudiniInstancedActorComponent* InIAC) -{ - if (!InIAC || InIAC->IsPendingKill()) - return nullptr; - - UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - return nullptr; - - AActor* NewActor = nullptr; - -#if WITH_EDITOR - // Try to spawn a new actor for the given transform - GEditor->ClickLocation = InTransform.GetTranslation(); - GEditor->ClickPlane = FPlane(GEditor->ClickLocation, FVector::UpVector); - - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(InSpawnLevel, InstancedObject, false, RF_Transactional, nullptr); - if (NewActors.Num() > 0) - { - if (NewActors[0] && !NewActors[0]->IsPendingKill()) - { - NewActor = NewActors[0]; - } - } -#endif - - // Make sure that the actor was spawned in the proper level - FHoudiniEngineUtils::MoveActorToLevel(NewActor, InSpawnLevel); - - return NewActor; -} - - -void -FHoudiniInstanceTranslator::CleanupFoliageInstances( - UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, - UObject* InInstancedObject, - USceneComponent* InParentComponent) -{ - if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) - return; - - UStaticMesh* FoliageSM = InFoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - return; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(InFoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - return; - - // Get the Foliage Type - UFoliageType *FoliageType = Cast(InInstancedObject); - if (!FoliageType || FoliageType->IsPendingKill()) - { - // Try to get the foliage type for the instanced mesh from the actor - FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InInstancedObject); - - if (!FoliageType || FoliageType->IsPendingKill()) - return; - } - - // Clean up the instances previously generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(InParentComponent, FoliageType); - - // Remove the foliage type if it doesn't have any more instances - if(InFoliageHISMC->GetInstanceCount() == 0) - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - - return; -} - - -FString -FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(UObject* InObject) -{ - USceneComponent* InComponent = Cast(InObject); - - FString InstancerType = TEXT("Instancer"); - if (InComponent && !InComponent->IsPendingKill()) - { - if (InComponent->IsA()) - { - InstancerType = TEXT("(Split Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Actor Instancer)"); - } - else if (InComponent->IsA()) - { - if (InComponent->GetOwner() && InComponent->GetOwner()->IsA()) - InstancerType = TEXT("(Foliage Instancer)"); - else - InstancerType = TEXT("(Hierarchical Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Mesh Instancer)"); - } - else if (InComponent->IsA()) - { - InstancerType = TEXT("(Static Mesh Component)"); - } - } - - return InstancerType; -} - -bool -FHoudiniInstanceTranslator::GetInstancerSplitAttributesAndValues( - const int32& InGeoId, - const int32& InPartId, - const HAPI_AttributeOwner& InSplitAttributeOwner, - FString& OutSplitAttributeName, - TArray& OutAllSplitAttributeValues) -{ - // See if the user has specified an attribute to split the instancers. - bool bHasSplitAttribute = false; - //FString SplitAttribName = FString(); - OutSplitAttributeName = FString(); - - // Look for the unreal_split_attr attribute - // This attribute indicates the name of the point attribute that we'll use to split the instances further - HAPI_AttributeInfo SplitAttribInfo; - FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); - - TArray StringData; - bHasSplitAttribute = FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoId, InPartId, - HAPI_UNREAL_ATTRIB_SPLIT_ATTR, SplitAttribInfo, StringData, 1); - - if (!bHasSplitAttribute || !SplitAttribInfo.exists || StringData.Num() <= 0) - return false; - - OutSplitAttributeName = StringData[0]; - - // We have specified a split attribute, try to get its values. - OutAllSplitAttributeValues.Empty(); - if (!OutSplitAttributeName.IsEmpty()) - { - //HAPI_AttributeInfo SplitAttribInfo; - FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); - bool bSplitAttrFound = FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InGeoId, - InPartId, - TCHAR_TO_ANSI(*OutSplitAttributeName), - SplitAttribInfo, - OutAllSplitAttributeValues, - 1, - InSplitAttributeOwner); - - if (!bSplitAttrFound || OutAllSplitAttributeValues.Num() <= 0) - { - // We couldn't properly get the point values - bHasSplitAttribute = false; - } - } - else - { - // We couldn't properly get the split attribute - bHasSplitAttribute = false; - } - - if (!bHasSplitAttribute) - { - // Clean up everything to ensure that we'll ignore the split attribute - OutAllSplitAttributeValues.Empty(); - OutSplitAttributeName = FString(); - } - - return bHasSplitAttribute; -} - -bool -FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId) -{ - bool bHISM = false; - HAPI_AttributeInfo AttriInfo; - FHoudiniApi::AttributeInfo_Init(&AttriInfo); - TArray IntData; - IntData.Empty(); - - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM, AttriInfo, IntData, 1)) - { - if (IntData.Num() > 0) - bHISM = IntData[0] == 1; - } - - return bHISM; -} - -void -FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() -{ - NumInstancedTransformsPerObject.Empty(OriginalInstancedTransforms.Num()); - // We expect to have one or more entries per object - OriginalInstancedTransformsFlat.Empty(OriginalInstancedTransforms.Num()); - for (const TArray& Transforms : OriginalInstancedTransforms) - { - NumInstancedTransformsPerObject.Add(Transforms.Num()); - OriginalInstancedTransformsFlat.Append(Transforms); - } - - OriginalInstanceObjectPackagePaths.Empty(OriginalInstancedObjects.Num()); - for (const UObject* Obj : OriginalInstancedObjects) - { - if (IsValid(Obj)) - { - OriginalInstanceObjectPackagePaths.Add(Obj->GetPathName()); - } - else - { - OriginalInstanceObjectPackagePaths.Add(FString()); - } - } - - NumInstancedIndicesPerObject.Empty(OriginalInstancedIndices.Num()); - // We expect to have one or more entries per object - OriginalInstancedIndicesFlat.Empty(OriginalInstancedIndices.Num()); - for (const TArray& InstancedIndices : OriginalInstancedIndices) - { - NumInstancedIndicesPerObject.Add(InstancedIndices.Num()); - OriginalInstancedIndicesFlat.Append(InstancedIndices); - } - - NumPerInstanceCustomDataPerObject.Empty(PerInstanceCustomData.Num()); - // We expect to have one or more entries per object - PerInstanceCustomDataFlat.Empty(PerInstanceCustomData.Num()); - for (const TArray& PerInstanceCustomDataArray : PerInstanceCustomData) - { - NumPerInstanceCustomDataPerObject.Add(PerInstanceCustomDataArray.Num()); - PerInstanceCustomDataFlat.Append(PerInstanceCustomDataArray); - } -} - -void -FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() -{ - { - const int32 NumObjects = NumInstancedTransformsPerObject.Num(); - OriginalInstancedTransforms.Init(TArray(), NumObjects); - int32 ObjectIndexOffset = 0; - for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) - { - TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; - const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; - Transforms.Reserve(NumInstances); - for (int32 Index = 0; Index < NumInstances; ++Index) - { - Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); - } - ObjectIndexOffset += NumInstances; - } - NumInstancedTransformsPerObject.Empty(); - OriginalInstancedTransformsFlat.Empty(); - } - - OriginalInstancedObjects.Empty(OriginalInstanceObjectPackagePaths.Num()); - for (const FString& PackageFullPath : OriginalInstanceObjectPackagePaths) - { - FString PackagePath; - FString PackageName; - const bool bDidSplit = PackageFullPath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); - if (!bDidSplit) - PackagePath = PackageFullPath; - - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!IsValid(Package)) - { - // Editor might have picked up the package yet, try to load it - Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); - } - if (IsValid(Package)) - { - OriginalInstancedObjects.Add(FindObject(Package, *PackageName)); - } - else - { - OriginalInstancedObjects.Add(nullptr); - } - } - OriginalInstanceObjectPackagePaths.Empty(); - - { - const int32 NumObjects = NumInstancedIndicesPerObject.Num(); - OriginalInstancedIndices.Init(TArray(), NumObjects); - int32 ObjectIndexOffset = 0; - for (int32 EntryIndex = 0; EntryIndex < NumObjects; ++EntryIndex) - { - TArray& InstancedIndices = OriginalInstancedIndices[EntryIndex]; - const int32 NumInstancedIndices = NumInstancedIndicesPerObject[EntryIndex]; - InstancedIndices.Reserve(NumInstancedIndices); - for (int32 Index = 0; Index < NumInstancedIndices; ++Index) - { - InstancedIndices.Add(OriginalInstancedIndicesFlat[ObjectIndexOffset + Index]); - } - ObjectIndexOffset += NumInstancedIndices; - } - NumInstancedIndicesPerObject.Empty(); - OriginalInstancedIndicesFlat.Empty(); - } - - { - const int32 NumObjects = NumPerInstanceCustomDataPerObject.Num(); - PerInstanceCustomData.Init(TArray(), NumObjects); - int32 ObjectIndexOffset = 0; - for (int32 EntryIndex = 0; EntryIndex < NumObjects; ++EntryIndex) - { - TArray& PerInstanceCustomDataArray = PerInstanceCustomData[EntryIndex]; - const int32 NumPerInstanceCustomData = NumPerInstanceCustomDataPerObject[EntryIndex]; - PerInstanceCustomDataArray.Reserve(NumPerInstanceCustomData); - for (int32 Index = 0; Index < NumPerInstanceCustomData; ++Index) - { - PerInstanceCustomDataArray.Add(PerInstanceCustomDataFlat[ObjectIndexOffset + Index]); - } - ObjectIndexOffset += NumPerInstanceCustomData; - } - NumPerInstanceCustomDataPerObject.Empty(); - PerInstanceCustomDataFlat.Empty(); - } -} - -bool -FHoudiniInstanceTranslator::GetPerInstanceCustomData( - const int32& InGeoNodeId, - const int32& InPartId, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) -{ - // Initialize sizes to zero - OutInstancedOutputPartData.PerInstanceCustomData.SetNum(0); - - // First look for the number of custom floats - // If we dont have the attribute, or it is set to zero, we dont have PerInstanceCustomData - // HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" - HAPI_AttributeInfo AttribInfoNumCustomFloats; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNumCustomFloats); - - TArray CustomFloatsArray; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - InGeoNodeId, InPartId, - HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS, - AttribInfoNumCustomFloats, - CustomFloatsArray)) - { - return false; - } - - if (CustomFloatsArray.Num() <= 0) - return false; - - int32 NumCustomFloats = 0; - - for (int32 CustomFloatCount : CustomFloatsArray) - { - NumCustomFloats = FMath::Max(NumCustomFloats, CustomFloatCount); - } - - if (NumCustomFloats <= 0) - return false; - - // We do have custom float, now read the per instance custom data - // They are stored in attributes that uses the "unreal_per_instance_custom" prefix - // ie, unreal_per_instance_custom0, unreal_per_instance_custom1 etc... - // We do not supprot tuples/arrays attributes for now. - TArray> AllCustomDataAttributeValues; - AllCustomDataAttributeValues.SetNum(NumCustomFloats); - - // Read the custom data attributes - int32 NumInstance = 0; - for (int32 nIdx = 0; nIdx < NumCustomFloats; nIdx++) - { - // Build the custom data attribute - FString CurrentAttr = TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX) + FString::FromInt(nIdx); - - // TODO? Tuple values Array attributes? - HAPI_AttributeInfo AttribInfo; - FHoudiniApi::AttributeInfo_Init(&AttribInfo); - - // Retrieve the custom data values - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - InGeoNodeId, InPartId, - TCHAR_TO_ANSI(*CurrentAttr), - AttribInfo, - AllCustomDataAttributeValues[nIdx], - 1)) - { - // Skip, we'll fill the values with zeros later on - continue; - } - - if (NumInstance < AllCustomDataAttributeValues[nIdx].Num()) - NumInstance = AllCustomDataAttributeValues[nIdx].Num(); - - if (NumInstance != AllCustomDataAttributeValues[nIdx].Num()) - { - HOUDINI_LOG_ERROR(TEXT("Instancer: Invalid number of Per-Instance Custom data attributes, ignoring...")); - return false; - } - } - - // Check sizes - if (AllCustomDataAttributeValues.Num() != NumCustomFloats) - { - HOUDINI_LOG_ERROR(TEXT("Instancer: Number of Per-Instance Custom data attributes don't match the number of custom floats, ignoring...")); - return false; - } - - OutInstancedOutputPartData.PerInstanceCustomData.SetNum(OutInstancedOutputPartData.OriginalInstancedObjects.Num()); - - for (int32 ObjIdx = 0; ObjIdx < OutInstancedOutputPartData.OriginalInstancedObjects.Num(); ++ObjIdx) - { - OutInstancedOutputPartData.PerInstanceCustomData[ObjIdx].Reset(); - } - - for(int32 ObjIdx = 0; ObjIdx < OutInstancedOutputPartData.OriginalInstancedObjects.Num(); ++ObjIdx) - { - const TArray& InstanceIndices = OutInstancedOutputPartData.OriginalInstancedIndices[ObjIdx]; - - if (InstanceIndices.Num() == 0) - { - continue; - } - - // Perform some validation - int32 NumCustomFloatsForInstance = CustomFloatsArray[InstanceIndices[0]]; - for (int32 InstIdx : InstanceIndices) - { - if (CustomFloatsArray[InstIdx] != NumCustomFloatsForInstance) - { - NumCustomFloatsForInstance = -1; - break; - } - } - - if (NumCustomFloatsForInstance == -1) - { - continue; - } - - // Now that we have read all the custom data values, we need to "interlace" them - // in the final per-instance custom data array, fill missing values with zeroes - TArray& PerInstanceCustomData = OutInstancedOutputPartData.PerInstanceCustomData[ObjIdx]; - PerInstanceCustomData.Reserve(InstanceIndices.Num() * NumCustomFloatsForInstance); - - if(NumCustomFloatsForInstance == 0) - { - continue; - } - - for (int32 InstIdx : InstanceIndices) - { - for (int32 nCustomIdx = 0; nCustomIdx < NumCustomFloatsForInstance; ++nCustomIdx) - { - float CustomData = (InstIdx < AllCustomDataAttributeValues[nCustomIdx].Num() ? AllCustomDataAttributeValues[nCustomIdx][InstIdx] : 0.0f); - PerInstanceCustomData.Add(CustomData); - } - } - } - - return true; -} - - -bool -FHoudiniInstanceTranslator::UpdateChangedPerInstanceCustomData( - const TArray& InPerInstanceCustomData, - USceneComponent* InComponentToUpdate) -{ - // Checks - UInstancedStaticMeshComponent* ISMC = Cast(InComponentToUpdate); - if (!IsValid(ISMC)) - return false; - - // No Custom data to add/remove - if (ISMC->NumCustomDataFloats == 0 && InPerInstanceCustomData.Num() == 0) - return false; - - // We can copy the per instance custom data if we have any - // TODO: Properly extract only needed values! - int32 InstanceCount = ISMC->GetInstanceCount(); - int32 NumCustomFloats = InPerInstanceCustomData.Num() / InstanceCount; - - if (NumCustomFloats * InstanceCount != InPerInstanceCustomData.Num()) - { - ISMC->NumCustomDataFloats = 0; - ISMC->PerInstanceSMCustomData.Reset(); - return false; - } - - ISMC->NumCustomDataFloats = NumCustomFloats; - - // Clear out and reinit to 0 the PerInstanceCustomData array - ISMC->PerInstanceSMCustomData.SetNumZeroed(InstanceCount * NumCustomFloats); - - // Behaviour copied From UInstancedStaticMeshComponent::SetCustomData() - // except we modify all the instance/custom values at once - ISMC->Modify(); - - // MemCopy - const int32 NumToCopy = FMath::Min(ISMC->PerInstanceSMCustomData.Num(), InPerInstanceCustomData.Num()); - if (NumToCopy > 0) - { - FMemory::Memcpy(&ISMC->PerInstanceSMCustomData[0], InPerInstanceCustomData.GetData(), NumToCopy * InPerInstanceCustomData.GetTypeSize()); - } - - // Force recreation of the render data when proxy is created - //NewISMC->InstanceUpdateCmdBuffer.Edit(); - // Cant call the edit function above because the function is defined in a different cpp file than the .h it is declared in... - ISMC->InstanceUpdateCmdBuffer.NumEdits++; - - ISMC->MarkRenderStateDirty(); - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInstanceTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniStaticMesh.h" + +//#include "HAPI/HAPI_Common.h" + +#include "Engine/StaticMesh.h" +#include "ComponentReregisterContext.h" +#include "HoudiniMaterialTranslator.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#if WITH_EDITOR + //#include "ScopedTransaction.h" + #include "LevelEditorViewport.h" + #include "MeshPaintHelpers.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// Fastrand is a faster alternative to std::rand() +// and doesn't oscillate when looking for 2 values like Unreal's. +inline int fastrand(int& nSeed) +{ + nSeed = (214013 * nSeed + 2531011); + return (nSeed >> 16) & 0x7FFF; +} + +// +bool +FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) +{ + // Get if force to use HISM from attribute + OutInstancedOutputPartData.bForceHISM = HasHISMAttribute(InHGPO.GeoId, InHGPO.PartId); + + // Extract the object and transforms for this instancer + if (!GetInstancerObjectsAndTransforms( + InHGPO, + InAllOutputs, + OutInstancedOutputPartData.OriginalInstancedObjects, + OutInstancedOutputPartData.OriginalInstancedTransforms, + OutInstancedOutputPartData.OriginalInstancedIndices, + OutInstancedOutputPartData.SplitAttributeName, + OutInstancedOutputPartData.SplitAttributeValues, + OutInstancedOutputPartData.PerSplitAttributes)) + return false; + + // Check if this is a No-Instancers ( unreal_split_instances ) + OutInstancedOutputPartData.bSplitMeshInstancer = IsSplitInstancer(InHGPO.GeoId, InHGPO.PartId); + + OutInstancedOutputPartData.bIsFoliageInstancer = IsFoliageInstancer(InHGPO.GeoId, InHGPO.PartId); + + // Extract the generic attributes + GetGenericPropertiesAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllPropertyAttributes); + + // Check for per instance custom data + GetPerInstanceCustomData(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData); + + //Get the level path attribute on the instancer + if (!FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllLevelPaths)) + { + // No attribute specified + OutInstancedOutputPartData.AllLevelPaths.Empty(); + } + + // Get the output name attribute + if (!FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.OutputNames)) + { + // No attribute specified + OutInstancedOutputPartData.OutputNames.Empty(); + } + + // See if we have a tile attribute + if (!FHoudiniEngineUtils::GetTileAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.TileValues)) + { + // No attribute specified + OutInstancedOutputPartData.TileValues.Empty(); + } + + // Get the bake actor attribute + if (!FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeActorNames)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeActorNames.Empty(); + } + + // Get the unreal_bake_folder attribute + if (!FHoudiniEngineUtils::GetBakeFolderAttribute(InHGPO.GeoId, OutInstancedOutputPartData.AllBakeFolders, InHGPO.PartId)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeFolders.Empty(); + } + + // Get the bake outliner folder attribute + if (!FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.AllBakeOutlinerFolders)) + { + // No attribute specified + OutInstancedOutputPartData.AllBakeOutlinerFolders.Empty(); + } + + // See if we have instancer material overrides + if (!GetMaterialOverridesFromAttributes(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.MaterialAttributes, OutInstancedOutputPartData.bMaterialOverrideNeedsCreateInstance)) + OutInstancedOutputPartData.MaterialAttributes.Empty(); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + UHoudiniOutput* InOutput, + const TArray& InAllOutputs, + UObject* InOuterComponent, + const FHoudiniPackageParams& InPackageParms, + const TMap* InPreBuiltInstancedOutputPartData +) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + // Keep track of the previous cook's component to clean them up after + TMap NewOutputObjects; + TMap OldOutputObjects = InOutput->GetOutputObjects(); + + TMap& InstancedOutputs = InOutput->GetInstancedOutputs(); + // Mark all the current instanced output as stale + for (auto& InstOut : InstancedOutputs) + InstOut.Value.bStale = true; + + USceneComponent* ParentComponent = Cast(InOuterComponent); + if (!ParentComponent) + return false; + + // Keep track of if we remove, create or update any foliage, so that we can repopulate the foliage type list in + // the UI (foliage mode) at the end + bool bHaveAnyFoliageInstancers = false; + + // We also need to cleanup the previous foliages instances (if we have any) + for (auto& CurrentPair : OldOutputObjects) + { + // Foliage instancers store a HISMC in the components + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentPair.Value.OutputComponent); + if (!IsValid(FoliageHISMC)) + continue; + + CleanupFoliageInstances(FoliageHISMC, CurrentPair.Value.OutputObject, ParentComponent); + bHaveAnyFoliageInstancers = true; + } + + // The default SM to be used if the instanced object has not been found (when using attribute instancers) + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) + { + // Not an instancer, skip + if (CurHGPO.Type != EHoudiniPartType::Instancer) + continue; + + // Prepare this output object's output identifier + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = CurHGPO.ObjectId; + OutputIdentifier.GeoId = CurHGPO.GeoId; + OutputIdentifier.PartId = CurHGPO.PartId; + OutputIdentifier.PartName = CurHGPO.PartName; + + FHoudiniInstancedOutputPartData InstancedOutputPartDataTmp; + const FHoudiniInstancedOutputPartData* InstancedOutputPartDataPtr = nullptr; + if (InPreBuiltInstancedOutputPartData) + { + InstancedOutputPartDataPtr = InPreBuiltInstancedOutputPartData->Find(OutputIdentifier); + } + if (!InstancedOutputPartDataPtr) + { + if (!PopulateInstancedOutputPartData(CurHGPO, InAllOutputs, InstancedOutputPartDataTmp)) + continue; + InstancedOutputPartDataPtr = &InstancedOutputPartDataTmp; + } + + const FHoudiniInstancedOutputPartData& InstancedOutputPartData = *InstancedOutputPartDataPtr; + + TArray InstancerMaterials; + if (!InstancedOutputPartData.bMaterialOverrideNeedsCreateInstance) + { + if (!GetInstancerMaterials(InstancedOutputPartData.MaterialAttributes, InstancerMaterials)) + InstancerMaterials.Empty(); + } + else + { + if (!GetInstancerMaterialInstances(InstancedOutputPartData.MaterialAttributes, CurHGPO, InPackageParms, InstancerMaterials)) + InstancerMaterials.Empty(); + } + + + if (InstancedOutputPartData.bIsFoliageInstancer) + bHaveAnyFoliageInstancers = true; + + // + // TODO: REFACTOR THIS! + // + // We create an instanced output per original object + // These original object can then potentially be replaced by variations + // Each variations will create a instance component / OutputObject + // Currently we process all original objects AND their variations at the same time + // we should instead loop on the original objects + // - get their variations objects/transform + // - create the appropriate instancer + // This means modifying UpdateInstanceVariationsObjects so that it works using + // a single OriginalObject instead of using an array + // Also, apply the same logic to UpdateChangedInstanceOutput + // + + // Array containing all the variations objects for all the original objects + TArray> VariationInstancedObjects; + // Array containing all the variations transforms + TArray> VariationInstancedTransforms; + // Array indicate the original object index for each variation + TArray VariationOriginalObjectIndices; + // Array indicate the variation number for each variation + TArray VariationIndices; + // Update our variations using the instanced outputs + UpdateInstanceVariationObjects( + OutputIdentifier, + InstancedOutputPartData.OriginalInstancedObjects, + InstancedOutputPartData.OriginalInstancedTransforms, + InstancedOutputPartData.OriginalInstancedIndices, + InOutput->GetInstancedOutputs(), + VariationInstancedObjects, + VariationInstancedTransforms, + VariationOriginalObjectIndices, + VariationIndices); + + // Preload objects so we can benefit from async compilation as much as possible + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) + { + VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); + } + + // Create the instancer components now + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) + { + UObject* InstancedObject = VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + continue; + + if (!VariationInstancedTransforms.IsValidIndex(InstanceObjectIdx)) + continue; + + const TArray& InstancedObjectTransforms = VariationInstancedTransforms[InstanceObjectIdx]; + if (InstancedObjectTransforms.Num() <= 0) + continue; + + // Get the original Index of that variations + int32 VariationOriginalIndex = VariationOriginalObjectIndices[InstanceObjectIdx]; + + // Find the matching instance output now + FHoudiniInstancedOutput* FoundInstancedOutput = nullptr; + { + // Instanced output only use the original object index for their split identifier + FHoudiniOutputObjectIdentifier InstancedOutputIdentifier = OutputIdentifier; + InstancedOutputIdentifier.SplitIdentifier = FString::FromInt(VariationOriginalIndex); + FoundInstancedOutput = InstancedOutputs.Find(InstancedOutputIdentifier); + } + + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + OutputIdentifier.SplitIdentifier = + FString::FromInt(VariationOriginalIndex) + + TEXT("_") + + FString::FromInt(VariationIndices[InstanceObjectIdx]); + + // Get the OutputObj for this variation + FHoudiniOutputObject* FoundOutputObject = OldOutputObjects.Find(OutputIdentifier); + // See if we can find an preexisting component for this obj to try to reuse it + USceneComponent* OldInstancerComponent = nullptr; + const bool bIsProxyMesh = InstancedObject->IsA(); + if (FoundOutputObject) + { + if (bIsProxyMesh) + { + OldInstancerComponent = Cast(FoundOutputObject->ProxyComponent); + } + else + { + OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); + } + } + + // Extract the material for this variation + TArray VariationMaterials; + if (!GetVariationMaterials(FoundInstancedOutput, InstanceObjectIdx, InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex], InstancerMaterials, VariationMaterials)) + VariationMaterials.Empty(); + + USceneComponent* NewInstancerComponent = nullptr; + if (!CreateOrUpdateInstanceComponent( + InstancedObject, + InstancedObjectTransforms, + InstancedOutputPartData.AllPropertyAttributes, + CurHGPO, + ParentComponent, + OldInstancerComponent, + NewInstancerComponent, + InstancedOutputPartData.bSplitMeshInstancer, + InstancedOutputPartData.bIsFoliageInstancer, + VariationMaterials, + InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex], + InstanceObjectIdx, + InstancedOutputPartData.bForceHISM)) + { + // TODO?? + continue; + } + + if (!NewInstancerComponent) + continue; + + // Copy the per-instance custom data if we have any + if (InstancedOutputPartData.PerInstanceCustomData.Num() > 0) + { + UpdateChangedPerInstanceCustomData( + InstancedOutputPartData.PerInstanceCustomData[VariationOriginalIndex], NewInstancerComponent); + } + + // If the instanced object (by ref) wasn't found, hide the component + if(InstancedObject == DefaultReferenceSM) + NewInstancerComponent->SetHiddenInGame(true); + else + NewInstancerComponent->SetHiddenInGame(false); + + FHoudiniOutputObject& NewOutputObject = NewOutputObjects.FindOrAdd(OutputIdentifier); + if (bIsProxyMesh) + { + NewOutputObject.ProxyComponent = NewInstancerComponent; + NewOutputObject.ProxyObject = InstancedObject; + } + else + { + NewOutputObject.OutputComponent = NewInstancerComponent; + NewOutputObject.OutputObject = InstancedObject; + } + + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + NewOutputObject.CachedAttributes.Empty(); + NewOutputObject.CachedTokens.Empty(); + + // Cache the level path, output name and tile attributes on the output object So they can be reused for baking + int32 FirstOriginalInstanceIndex = 0; + if(InstancedOutputPartData.OriginalInstancedIndices.IsValidIndex(VariationOriginalIndex) && InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex].Num() > 0) + FirstOriginalInstanceIndex = InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex][0]; + + if(InstancedOutputPartData.AllLevelPaths.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllLevelPaths[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, InstancedOutputPartData.AllLevelPaths[FirstOriginalInstanceIndex]); + + if(InstancedOutputPartData.OutputNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.OutputNames[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[FirstOriginalInstanceIndex]); + + // TODO: Check! maybe accessed with just VariationOriginalIndex + if(InstancedOutputPartData.TileValues.IsValidIndex(FirstOriginalInstanceIndex) && InstancedOutputPartData.TileValues[FirstOriginalInstanceIndex] >= 0) + { + // cache the tile attribute as a token on the output object + NewOutputObject.CachedTokens.Add(TEXT("tile"), FString::FromInt(InstancedOutputPartData.TileValues[FirstOriginalInstanceIndex])); + } + + if(InstancedOutputPartData.AllBakeActorNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeActorNames[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, InstancedOutputPartData.AllBakeActorNames[FirstOriginalInstanceIndex]); + + if(InstancedOutputPartData.AllBakeFolders.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeFolders[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, InstancedOutputPartData.AllBakeFolders[FirstOriginalInstanceIndex]); + + if(InstancedOutputPartData.AllBakeOutlinerFolders.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.AllBakeOutlinerFolders[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, InstancedOutputPartData.AllBakeOutlinerFolders[FirstOriginalInstanceIndex]); + + if(InstancedOutputPartData.SplitAttributeValues.IsValidIndex(VariationOriginalIndex) + && !InstancedOutputPartData.SplitAttributeName.IsEmpty()) + { + FString SplitValue = InstancedOutputPartData.SplitAttributeValues[VariationOriginalIndex]; + + // Cache the split attribute both as attribute and token + NewOutputObject.CachedAttributes.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); + NewOutputObject.CachedTokens.Add(InstancedOutputPartData.SplitAttributeName, SplitValue); + + // If we have a split name that is non-empty, override attributes that can differ by split based + // on the split name + if (!SplitValue.IsEmpty()) + { + const FHoudiniInstancedOutputPerSplitAttributes* PerSplitAttributes = InstancedOutputPartData.PerSplitAttributes.Find(SplitValue); + if (PerSplitAttributes) + { + if (!PerSplitAttributes->LevelPath.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, PerSplitAttributes->LevelPath); + if (!PerSplitAttributes->BakeActorName.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, PerSplitAttributes->BakeActorName); + if (!PerSplitAttributes->BakeOutlinerFolder.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, PerSplitAttributes->BakeOutlinerFolder); + if (!PerSplitAttributes->BakeFolder.IsEmpty()) + NewOutputObject.CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, PerSplitAttributes->BakeFolder); + } + } + } + } + } + + // Remove reused components from the old map to avoid their deletion + for (const auto& CurNewPair : NewOutputObjects) + { + // Get the new Identifier / StaticMesh + const FHoudiniOutputObjectIdentifier& OutputIdentifier = CurNewPair.Key; + + // See if we already had that pair in the old map + FHoudiniOutputObject* FoundOldOutputObject = OldOutputObjects.Find(OutputIdentifier); + if (!FoundOldOutputObject) + continue; + + bool bKeep = false; + + UObject* NewComponent = CurNewPair.Value.OutputComponent; + if (NewComponent) + { + UObject* FoundOldComponent = FoundOldOutputObject->OutputComponent; + if (FoundOldComponent && !FoundOldComponent->IsPendingKill()) + { + bKeep = (FoundOldComponent == NewComponent); + } + } + + UObject* NewProxyComponent = CurNewPair.Value.ProxyComponent; + if (NewProxyComponent) + { + UObject* FoundOldProxyComponent = FoundOldOutputObject->ProxyComponent; + if (FoundOldProxyComponent && !FoundOldProxyComponent->IsPendingKill()) + { + bKeep = (FoundOldProxyComponent == NewProxyComponent); + } + } + + if (bKeep) + { + // Remove the reused component from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + + // The Old map now only contains unused/stale components, delete them + for (auto& OldPair : OldOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; + UObject* OldComponent = OldPair.Value.OutputComponent; + if (OldComponent) + { + bool bDestroy = true; + if (OldComponent->IsA()) + { + // When destroying a component, we have to be sure it's not an HISMC owned by an InstanceFoliageActor + UHierarchicalInstancedStaticMeshComponent* HISMC = Cast(OldComponent); + if (HISMC->GetOwner() && HISMC->GetOwner()->IsA()) + bDestroy = false; + } + + if(bDestroy) + RemoveAndDestroyComponent(OldComponent, OldPair.Value.OutputObject); + + OldPair.Value.OutputComponent = nullptr; + OldPair.Value.OutputObject = nullptr; + } + + UObject* OldProxyComponent = OldPair.Value.ProxyComponent; + if (OldProxyComponent) + { + RemoveAndDestroyComponent(OldProxyComponent, OldPair.Value.ProxyObject); + OldPair.Value.ProxyComponent = nullptr; + OldPair.Value.ProxyObject = nullptr; + } + } + OldOutputObjects.Empty(); + + // Update the output's object map + // Instancer do not create objects, clean the map + InOutput->SetOutputObjects(NewOutputObjects); + + // If we removed, created or updated any foliage instancers, repopulate the list of foliage types in the UI (foliage + // mode) + if (bHaveAnyFoliageInstancers) + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + + return true; +} + + +bool +FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( + FHoudiniInstancedOutput& InInstancedOutput, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + UHoudiniOutput* InParentOutput, + USceneComponent* InParentComponent, + const FHoudiniPackageParams& InPackageParams) +{ + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = InOutputIdentifier.ObjectId; + OutputIdentifier.GeoId = InOutputIdentifier.GeoId; + OutputIdentifier.PartId = InOutputIdentifier.PartId; + OutputIdentifier.SplitIdentifier = InOutputIdentifier.SplitIdentifier; + OutputIdentifier.PartName = InOutputIdentifier.PartName; + + // Get if force using HISM from attribute + bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); + + TArray OriginalInstancedObjects; + OriginalInstancedObjects.Add(InInstancedOutput.OriginalObject.LoadSynchronous()); + + TArray> OriginalInstancedTransforms; + OriginalInstancedTransforms.Add(InInstancedOutput.OriginalTransforms); + + TArray> OriginalInstanceIndices; + OriginalInstanceIndices.Add(InInstancedOutput.OriginalInstanceIndices); + + // Update our variations using the changed instancedoutputs objects + TArray> InstancedObjects; + TArray> InstancedTransforms; + TArray VariationOriginalObjectIndices; + TArray VariationIndices; + UpdateInstanceVariationObjects( + OutputIdentifier, + OriginalInstancedObjects, + OriginalInstancedTransforms, + OriginalInstanceIndices, + InParentOutput->GetInstancedOutputs(), + InstancedObjects, + InstancedTransforms, + VariationOriginalObjectIndices, + VariationIndices); + + // Find the HGPO for this instanced output + bool FoundHGPO = false; + FHoudiniGeoPartObject HGPO; + for (const auto& curHGPO : InParentOutput->GetHoudiniGeoPartObjects()) + { + if (OutputIdentifier.Matches(curHGPO)) + { + HGPO = curHGPO; + FoundHGPO = true; + break; + } + } + + if (!FoundHGPO) + { + // TODO check failure + ensure(FoundHGPO); + } + + // Extract the generic attributes for that HGPO + TArray AllPropertyAttributes; + GetGenericPropertiesAttributes(OutputIdentifier.GeoId, OutputIdentifier.PartId, AllPropertyAttributes); + + // Check if this is a No-Instancers ( unreal_split_instances ) + bool bSplitMeshInstancer = IsSplitInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); + + bool bIsFoliageInstancer = IsFoliageInstancer(OutputIdentifier.GeoId, OutputIdentifier.PartId); + + // See if we have instancer material overrides + TArray InstancerMaterials; + if (!GetInstancerMaterials(OutputIdentifier.GeoId, OutputIdentifier.PartId, HGPO, InPackageParams, InstancerMaterials)) + InstancerMaterials.Empty(); + + // Preload objects so we can benefit from async compilation as much as possible + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) + { + InstancedObjects[InstanceObjectIdx].LoadSynchronous(); + } + + // Keep track of the new instancer component in order to be able to clean up the unused/stale ones after. + TMap& OutputObjects = InParentOutput->GetOutputObjects(); + TMap ToDeleteOutputObjects = InParentOutput->GetOutputObjects(); + + // Create the instancer components now + for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) + { + UObject* InstancedObject = InstancedObjects[InstanceObjectIdx].LoadSynchronous(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + continue; + + if (!InstancedTransforms.IsValidIndex(InstanceObjectIdx)) + continue; + + const TArray& InstancedObjectTransforms = InstancedTransforms[InstanceObjectIdx]; + if (InstancedObjectTransforms.Num() <= 0) + continue; + + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + // the original object index is used for the instanced outputs split identifier + OutputIdentifier.SplitIdentifier = + InOutputIdentifier.SplitIdentifier + + TEXT("_") + + FString::FromInt(VariationIndices[InstanceObjectIdx]); + + // See if we can find an preexisting component for this obj to try to reuse it + USceneComponent* OldInstancerComponent = nullptr; + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(OutputIdentifier); + if (FoundOutputObject) + { + OldInstancerComponent = Cast(FoundOutputObject->OutputComponent); + } + + // Extract the material for this variation +// FHoudiniInstancedOutput* FoundInstancedOutput = InstancedOutputs.Find(OutputIdentifier); + TArray VariationMaterials; + if (!GetVariationMaterials(&InInstancedOutput, InstanceObjectIdx, OriginalInstanceIndices[0], InstancerMaterials, VariationMaterials)) + VariationMaterials.Empty(); + + USceneComponent* NewInstancerComponent = nullptr; + if (!CreateOrUpdateInstanceComponent( + InstancedObject, + InstancedObjectTransforms, + AllPropertyAttributes, + HGPO, + InParentComponent, + OldInstancerComponent, + NewInstancerComponent, + bSplitMeshInstancer, + bIsFoliageInstancer, + InstancerMaterials, + OriginalInstanceIndices[0], + InstanceObjectIdx, + bForceHISM)) + { + // TODO?? + continue; + } + + if (!NewInstancerComponent) + continue; + + if (OldInstancerComponent != NewInstancerComponent) + { + // Previous component wasn't reused, detach and delete it + RemoveAndDestroyComponent(OldInstancerComponent, nullptr); + + // Replace it with the new component + if (FoundOutputObject) + { + FoundOutputObject->OutputComponent = NewInstancerComponent; + } + else + { + FHoudiniOutputObject& NewOutputObject = OutputObjects.Add(OutputIdentifier); + NewOutputObject.OutputComponent = NewInstancerComponent; + } + } + + // Remove this output object from the todelete map + ToDeleteOutputObjects.Remove(OutputIdentifier); + } + + // Clean up the output objects that are not "reused" by the instanced outs + // The ToDelete map now only contains unused/stale components, delete them + for (auto& ToDeletePair : ToDeleteOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& ToDeleteIdentifier = ToDeletePair.Key; + UObject* OldComponent = ToDeletePair.Value.OutputComponent; + if (OldComponent) + { + RemoveAndDestroyComponent(OldComponent, ToDeletePair.Value.OutputObject); + ToDeletePair.Value.OutputComponent = nullptr; + } + + UObject* OldProxyComponent = ToDeletePair.Value.ProxyComponent; + if (OldProxyComponent) + { + RemoveAndDestroyComponent(OldProxyComponent, ToDeletePair.Value.ProxyObject); + ToDeletePair.Value.ProxyComponent = nullptr; + } + + // Make sure the stale output object is not in the output map anymore + OutputObjects.Remove(ToDeleteIdentifier); + } + ToDeleteOutputObjects.Empty(); + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValues, + TMap& OutPerSplitAttributes) +{ + TArray InstancedObjects; + TArray> InstancedTransforms; + TArray> InstancedIndices; + + TArray InstancedHGPOs; + TArray> InstancedHGPOTransforms; + TArray> InstancedHGPOIndices; + + bool bSuccess = false; + switch (InHGPO.InstancerType) + { + case EHoudiniInstancerType::PackedPrimitive: + { + // Packed primitives instances + bSuccess = GetPackedPrimitiveInstancerHGPOsAndTransforms( + InHGPO, + InstancedHGPOs, + InstancedHGPOTransforms, + InstancedHGPOIndices, + OutSplitAttributeName, + OutSplitAttributeValues, + OutPerSplitAttributes); + } + break; + + case EHoudiniInstancerType::AttributeInstancer: + { + // "Modern" attribute instancer - "unreal_instance" + bSuccess = GetAttributeInstancerObjectsAndTransforms( + InHGPO, + InstancedObjects, + InstancedTransforms, + InstancedIndices, + OutSplitAttributeName, + OutSplitAttributeValues, + OutPerSplitAttributes); + } + break; + + case EHoudiniInstancerType::OldSchoolAttributeInstancer: + { + // Old school attribute override instancer - instance attribute w/ a HoudiniPath + bSuccess = GetOldSchoolAttributeInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms, InstancedHGPOIndices); + } + break; + + case EHoudiniInstancerType::ObjectInstancer: + { + // Old School object instancer + bSuccess = GetObjectInstancerHGPOsAndTransforms(InHGPO, InAllOutputs, InstancedHGPOs, InstancedHGPOTransforms, InstancedHGPOIndices); + } + break; + } + + if (!bSuccess) + return false; + + // Fetch the UOBject that correspond to the instanced parts + // Attribute instancers don't need to do this since they refer UObjects directly + if (InstancedHGPOs.Num() > 0) + { + for (int32 HGPOIdx = 0; HGPOIdx < InstancedHGPOs.Num(); HGPOIdx++) + { + const FHoudiniGeoPartObject& CurrentHGPO = InstancedHGPOs[HGPOIdx]; + + // Get the UObject that was generated for that HGPO + TArray ObjectsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + if (Output->OutputObjects.Num() <= 0) + continue; + + for (const auto& OutObjPair : Output->OutputObjects) + { + if (!OutObjPair.Key.Matches(CurrentHGPO)) + continue; + + const FHoudiniOutputObject& CurrentOutputObject = OutObjPair.Value; + + // In the case of a single-instance we can use the proxy (if it is current) + // FHoudiniOutputTranslator::UpdateOutputs doesn't allow proxies if there is more than one instance in an output + if (InstancedHGPOTransforms[HGPOIdx].Num() <= 1 && CurrentOutputObject.bProxyIsCurrent + && CurrentOutputObject.ProxyObject && !CurrentOutputObject.ProxyObject->IsPendingKill()) + { + ObjectsToInstance.Add(CurrentOutputObject.ProxyObject); + } + else if (CurrentOutputObject.OutputObject && !CurrentOutputObject.OutputObject->IsPendingKill()) + { + ObjectsToInstance.Add(CurrentOutputObject.OutputObject); + } + } + } + + // Add the UObject and the HGPO transforms to the output arrays + for (const auto& MatchingOutputObj : ObjectsToInstance) + { + InstancedObjects.Add(MatchingOutputObj); + InstancedTransforms.Add(InstancedHGPOTransforms[HGPOIdx]); + InstancedIndices.Add(InstancedHGPOIndices[HGPOIdx]); + } + } + } + + // + if (InstancedObjects.Num() <= 0 || InstancedTransforms.Num() != InstancedObjects.Num() || InstancedIndices.Num() != InstancedObjects.Num()) + { + // TODO + // Error / warning + return false; + } + + OutInstancedObjects = InstancedObjects; + OutInstancedTransforms = InstancedTransforms; + OutInstancedIndices = InstancedIndices; + + return true; +} + + +void +FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TArray& InOriginalObjects, + const TArray>& InOriginalTransforms, + const TArray>& InOriginalInstancedIndices, + TMap& InstancedOutputs, + TArray>& OutVariationsInstancedObjects, + TArray>& OutVariationsInstancedTransforms, + TArray& OutVariationOriginalObjectIdx, + TArray& OutVariationIndices) +{ + FHoudiniOutputObjectIdentifier Identifier = InOutputIdentifier; + for (int32 InstObjIdx = 0; InstObjIdx < InOriginalObjects.Num(); InstObjIdx++) + { + UObject* OriginalObj = InOriginalObjects[InstObjIdx]; + if (!OriginalObj || OriginalObj->IsPendingKill()) + continue; + + // Build this output object's split identifier + Identifier.SplitIdentifier = FString::FromInt(InstObjIdx); + + // Do we have an instanced output object for this one? + FHoudiniInstancedOutput * FoundInstancedOutput = nullptr; + for (auto& Iter : InstancedOutputs) + { + FHoudiniOutputObjectIdentifier& FoundIdentifier = Iter.Key; + if (!(FoundIdentifier == Identifier)) + continue; + + // We found an existing instanced output for this identifier + FoundInstancedOutput = &(Iter.Value); + + if (FoundIdentifier.bLoaded) + { + // The output object identifier we found is marked as loaded, + // so uses old node IDs, we must update them, or the next cook + // will fail to locate the output back + FoundIdentifier.ObjectId = Identifier.ObjectId; + FoundIdentifier.GeoId = Identifier.GeoId; + FoundIdentifier.PartId = Identifier.PartId; + } + } + + if (!FoundInstancedOutput) + { + // Create a new one + FHoudiniInstancedOutput CurInstancedOutput; + CurInstancedOutput.OriginalObject = OriginalObj; + CurInstancedOutput.OriginalObjectIndex = InstObjIdx; + CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + CurInstancedOutput.OriginalInstanceIndices = InOriginalInstancedIndices[InstObjIdx]; + + CurInstancedOutput.VariationObjects.Add(OriginalObj); + CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); + CurInstancedOutput.TransformVariationIndices.SetNumZeroed(InOriginalTransforms[InstObjIdx].Num()); + CurInstancedOutput.MarkChanged(false); + CurInstancedOutput.bStale = false; + + // No variations, simply assign the object/transforms + OutVariationsInstancedObjects.Add(OriginalObj); + OutVariationsInstancedTransforms.Add(InOriginalTransforms[InstObjIdx]); + OutVariationOriginalObjectIdx.Add(InstObjIdx); + OutVariationIndices.Add(0); + + InstancedOutputs.Add(Identifier, CurInstancedOutput); + } + else + { + // Process the potential variations + FHoudiniInstancedOutput& CurInstancedOutput = *FoundInstancedOutput; + UObject *ReplacedOriginalObject = nullptr; + if (CurInstancedOutput.OriginalObject != OriginalObj) + { + ReplacedOriginalObject = CurInstancedOutput.OriginalObject.LoadSynchronous(); + CurInstancedOutput.OriginalObject = OriginalObj; + } + + CurInstancedOutput.OriginalTransforms = InOriginalTransforms[InstObjIdx]; + CurInstancedOutput.OriginalInstanceIndices = InOriginalInstancedIndices[InstObjIdx]; + + // Shouldnt be needed... + CurInstancedOutput.OriginalObjectIndex = InstObjIdx; + + // Remove any null or deleted variation objects + TArray ObjsToRemove; + for (int32 VarIdx = CurInstancedOutput.VariationObjects.Num() - 1; VarIdx >= 0; --VarIdx) + { + UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); + if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill() || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) + { + ObjsToRemove.Add(VarIdx); + } + } + if (ObjsToRemove.Num() > 0) + { + for (const int32 &VarIdx : ObjsToRemove) + { + CurInstancedOutput.VariationObjects.RemoveAt(VarIdx); + CurInstancedOutput.VariationTransformOffsets.RemoveAt(VarIdx); + } + // Force a recompute of variation assignments + CurInstancedOutput.TransformVariationIndices.SetNum(0); + } + + // If we don't have variations, simply use the original object + if (CurInstancedOutput.VariationObjects.Num() <= 0) + { + // No variations? add the original one + CurInstancedOutput.VariationObjects.Add(OriginalObj); + CurInstancedOutput.VariationTransformOffsets.Add(FTransform::Identity); + CurInstancedOutput.TransformVariationIndices.SetNum(0); + } + + // If the number of transforms has changed since the previous cook, + // we need to recompute the variation assignments + if (CurInstancedOutput.TransformVariationIndices.Num() != CurInstancedOutput.OriginalTransforms.Num()) + UpdateVariationAssignements(CurInstancedOutput); + + // Assign variations and their transforms + for (int32 VarIdx = 0; VarIdx < CurInstancedOutput.VariationObjects.Num(); VarIdx++) + { + UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); + if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill()) + continue; + + // Get the transforms assigned to that variation + TArray ProcessedTransforms; + ProcessInstanceTransforms(CurInstancedOutput, VarIdx, ProcessedTransforms); + if (ProcessedTransforms.Num() > 0) + { + OutVariationsInstancedObjects.Add(CurrentVariationObject); + OutVariationsInstancedTransforms.Add(ProcessedTransforms); + OutVariationOriginalObjectIdx.Add(InstObjIdx); + OutVariationIndices.Add(VarIdx); + } + } + + CurInstancedOutput.MarkChanged(false); + CurInstancedOutput.bStale = false; + } + } +} + + +void +FHoudiniInstanceTranslator::UpdateVariationAssignements(FHoudiniInstancedOutput& InstancedOutput) +{ + int32 TransformCount = InstancedOutput.OriginalTransforms.Num(); + InstancedOutput.TransformVariationIndices.SetNumZeroed(TransformCount); + + int32 VariationCount = InstancedOutput.VariationObjects.Num(); + if (VariationCount <= 1) + return; + + int nSeed = 1234; + for (int32 Idx = 0; Idx < TransformCount; Idx++) + { + InstancedOutput.TransformVariationIndices[Idx] = fastrand(nSeed) % VariationCount; + } +} + +void +FHoudiniInstanceTranslator::ProcessInstanceTransforms( + FHoudiniInstancedOutput& InstancedOutput, const int32& VariationIdx, TArray& OutProcessedTransforms) +{ + if (!InstancedOutput.VariationObjects.IsValidIndex(VariationIdx)) + return; + + if (!InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx)) + return; + + bool bHasVariations = InstancedOutput.VariationObjects.Num() > 1; + bool bHasTransformOffset = InstancedOutput.VariationTransformOffsets.IsValidIndex(VariationIdx) + ? !InstancedOutput.VariationTransformOffsets[VariationIdx].Equals(FTransform::Identity) + : false; + + if (!bHasVariations && !bHasTransformOffset) + { + // We dont have variations or transform offset, so we can reuse the original transforms as is + OutProcessedTransforms = InstancedOutput.OriginalTransforms; + return; + } + + if (bHasVariations) + { + // We simply need to extract the transforms for this variation + for (int32 TransformIndex = 0; TransformIndex < InstancedOutput.TransformVariationIndices.Num(); TransformIndex++) + { + if (InstancedOutput.TransformVariationIndices[TransformIndex] != VariationIdx) + continue; + + OutProcessedTransforms.Add(InstancedOutput.OriginalTransforms[TransformIndex]); + } + } + else + { + // No variations, we can reuse the original transforms + OutProcessedTransforms = InstancedOutput.OriginalTransforms; + } + + if (bHasTransformOffset) + { + // Get the transform offset for this variation + FVector PositionOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetLocation(); + FQuat RotationOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetRotation(); + FVector ScaleOffset = InstancedOutput.VariationTransformOffsets[VariationIdx].GetScale3D(); + + FTransform CurrentTransform = FTransform::Identity; + for (int32 TransformIndex = 0; TransformIndex < OutProcessedTransforms.Num(); TransformIndex++) + { + CurrentTransform = OutProcessedTransforms[TransformIndex]; + + // Compute new rotation and scale. + FVector Position = CurrentTransform.GetLocation() + PositionOffset; + FQuat TransformRotation = CurrentTransform.GetRotation() * RotationOffset; + FVector TransformScale3D = CurrentTransform.GetScale3D() * ScaleOffset; + + // Make sure inverse matrix exists - seems to be a bug in Unreal when submitting instances. + // Happens in blueprint as well. + // We want to make sure the scale is not too small, but keep negative values! (Bug 90876) + if (FMath::Abs(TransformScale3D.X) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.X = (TransformScale3D.X > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + if (FMath::Abs(TransformScale3D.Y) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.Y = (TransformScale3D.Y > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + if (FMath::Abs(TransformScale3D.Z) < HAPI_UNREAL_SCALE_SMALL_VALUE) + TransformScale3D.Z = (TransformScale3D.Z > 0) ? HAPI_UNREAL_SCALE_SMALL_VALUE : -HAPI_UNREAL_SCALE_SMALL_VALUE; + + CurrentTransform.SetLocation(Position); + CurrentTransform.SetRotation(TransformRotation); + CurrentTransform.SetScale3D(TransformScale3D); + + if (CurrentTransform.IsValid()) + OutProcessedTransforms[TransformIndex] = CurrentTransform; + } + } +} + +bool +FHoudiniInstanceTranslator::GetPackedPrimitiveInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::PackedPrimitive) + return false; + + // Get transforms for each instance + TArray InstancerPartTransforms; + InstancerPartTransforms.SetNumZeroed(InHGPO.PartInfo.InstanceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancerPartTransforms( + FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, + HAPI_RSTORDER_DEFAULT, InstancerPartTransforms.GetData(), 0, InHGPO.PartInfo.InstanceCount), false); + + // Convert the transform to Unreal's coordinate system + TArray InstancerUnrealTransforms; + InstancerUnrealTransforms.SetNumUninitialized(InstancerPartTransforms.Num()); + for (int32 InstanceIdx = 0; InstanceIdx < InstancerPartTransforms.Num(); InstanceIdx++) + { + const auto& InstanceTransform = InstancerPartTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, InstancerUnrealTransforms[InstanceIdx]); + } + + // Get the part ids for parts being instanced + TArray InstancedPartIds; + InstancedPartIds.SetNumZeroed(InHGPO.PartInfo.InstancedPartCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetInstancedPartIds( + FHoudiniEngine::Get().GetSession(), InHGPO.GeoId, InHGPO.PartInfo.PartId, + InstancedPartIds.GetData(), 0, InHGPO.PartInfo.InstancedPartCount), false); + + // See if the user has specified an attribute for splitting the instances + // and get the values + FString SplitAttribName = FString(); + TArray AllSplitAttributeValues; + bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( + InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_PRIM, SplitAttribName, AllSplitAttributeValues); + + // Get the level path attribute on the instancer + TArray AllLevelPaths; + const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( + InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_PRIM); + + // Get the bake actor attribute + TArray AllBakeActorNames; + const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( + InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_PRIM); + + // Get the unreal_bake_folder attribute + TArray AllBakeFolders; + const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( + InHGPO.GeoId, HAPI_ATTROWNER_PRIM, AllBakeFolders, InHGPO.PartId); + + // Get the bake outliner folder attribute + TArray AllBakeOutlinerFolders; + const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_PRIM); + + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; + + for (const auto& InstancedPartId : InstancedPartIds) + { + // Create a GeoPartObject corresponding to the instanced part + FHoudiniGeoPartObject InstancedHGPO; + InstancedHGPO.AssetId = InHGPO.AssetId; + InstancedHGPO.AssetName = InHGPO.AssetName; + InstancedHGPO.ObjectId = InHGPO.ObjectId; + InstancedHGPO.ObjectName = InHGPO.ObjectName; + InstancedHGPO.GeoId = InHGPO.GeoId; + InstancedHGPO.PartId = InstancedPartId; + InstancedHGPO.PartName = InHGPO.PartName; + InstancedHGPO.TransformMatrix = InHGPO.TransformMatrix; + + // TODO: Copy more cached data? + + OutInstancedHGPO.Add(InstancedHGPO); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + + TArray Indices; + Indices.SetNum(InstancerUnrealTransforms.Num()); + for (int32 Index = 0; Index < Indices.Num(); ++Index) + { + Indices[Index] = Index; + } + + OutInstancedIndices.Add(Indices); + } + + // If we don't need to split the instances, we're done + if (!bHasSplitAttribute) + return true; + + // TODO: Optimize this! + // Split the instances using the split attribute's values + + // Move the output arrays to temp arrays + TArray UnsplitInstancedHGPOs = OutInstancedHGPO; + TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + TArray> UnsplitInstancedIndices = OutInstancedIndices; + + // Empty the output arrays + OutInstancedHGPO.Empty(); + OutInstancedTransforms.Empty(); + OutInstancedIndices.Empty(); + OutSplitAttributeValue.Empty(); + for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedHGPOs.Num(); ObjIdx++) + { + // Map of split values to transform arrays + TMap> SplitTransformMap; + TMap> SplitIndicesMap; + + TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + TArray& CurrentIndices = UnsplitInstancedIndices[ObjIdx]; + + int32 NumInstances = CurrentTransforms.Num(); + if (AllSplitAttributeValues.Num() != NumInstances || CurrentIndices.Num() != NumInstances) + continue; + + // Split the transforms using the split values + for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) + { + const FString& SplitAttrValue = AllSplitAttributeValues[InstIdx]; + SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + SplitIndicesMap.FindOrAdd(SplitAttrValue).Add(CurrentIndices[InstIdx]); + + // Record attributes for any split value we have not yet seen + if (bHasAnyPerSplitAttributes) + { + FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); + if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) + { + PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; + } + if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; + } + if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; + } + if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; + } + } + } + + // Add the objects, transform, split values to the final arrays + for (auto& Iterator : SplitTransformMap) + { + OutSplitAttributeValue.Add(Iterator.Key); + OutInstancedHGPO.Add(UnsplitInstancedHGPOs[ObjIdx]); + OutInstancedTransforms.Add(Iterator.Value); + OutInstancedIndices.Add(SplitIndicesMap[Iterator.Key]); + } + } + + OutSplitAttributeName = SplitAttribName; + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::AttributeInstancer) + return false; + + // Look for the unreal instance attribute + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + // instance attribute on points + bool is_override_attr = false; + HAPI_Result Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE, HAPI_ATTROWNER_POINT, &AttribInfo); + + // unreal_instance attribute on points + if (Result != HAPI_RESULT_SUCCESS || AttribInfo.exists == false) + { + is_override_attr = true; + Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_POINT, &AttribInfo); + } + + // unreal_instance attribute on detail + if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) + { + is_override_attr = true; + Result = FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE, HAPI_ATTROWNER_DETAIL, &AttribInfo); + } + + // Attribute does not exist. + if (Result != HAPI_RESULT_SUCCESS || !AttribInfo.exists) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the settings indicating if we want to use a default object when the referenced mesh is invalid + bool bDefaultObjectEnabled = true; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bDefaultObjectEnabled = HoudiniRuntimeSettings->bShowDefaultMesh; + } + + // See if the user has specified an attribute for splitting the instances, and get the values + FString SplitAttribName = FString(); + TArray AllSplitAttributeValues; + bool bHasSplitAttribute = GetInstancerSplitAttributesAndValues( + InHGPO.GeoId, InHGPO.PartId, HAPI_ATTROWNER_POINT, SplitAttribName, AllSplitAttributeValues); + + // Get the level path attribute on the instancer + TArray AllLevelPaths; + const bool bHasLevelPaths = FHoudiniEngineUtils::GetLevelPathAttribute( + InHGPO.GeoId, InHGPO.PartId, AllLevelPaths, HAPI_ATTROWNER_POINT); + + // Get the bake actor attribute + TArray AllBakeActorNames; + const bool bHasBakeActorNames = FHoudiniEngineUtils::GetBakeActorAttribute( + InHGPO.GeoId, InHGPO.PartId, AllBakeActorNames, HAPI_ATTROWNER_POINT); + + // Get the unreal_bake_folder attribute + TArray AllBakeFolders; + const bool bHasBakeFolders = FHoudiniEngineUtils::GetBakeFolderAttribute( + InHGPO.GeoId, HAPI_ATTROWNER_POINT, AllBakeFolders, InHGPO.PartId); + + // Get the bake outliner folder attribute + TArray AllBakeOutlinerFolders; + const bool bHasBakeOutlinerFolders = FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + InHGPO.GeoId, InHGPO.PartId,AllBakeOutlinerFolders, HAPI_ATTROWNER_POINT); + + const bool bHasAnyPerSplitAttributes = bHasLevelPaths || bHasBakeActorNames || bHasBakeOutlinerFolders || bHasBakeFolders; + + // Array used to store the split values per objects + // Will only be used if we have a split attribute + TArray> SplitAttributeValuesPerObject; + + if (AttribInfo.owner == HAPI_ATTROWNER_DETAIL) + { + // If the attribute is on the detail, then its value is applied to all points + TArray DetailInstanceValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InHGPO.GeoId, + InHGPO.PartId, + is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, + AttribInfo, + DetailInstanceValues)) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + if (DetailInstanceValues.Num() <= 0) + { + // No values specified. + return false; + } + + // Attempt to load specified asset. + const FString & AssetName = DetailInstanceValues[0]; + UObject * AttributeObject = StaticLoadObject(UObject::StaticClass(), nullptr, *AssetName, nullptr, LOAD_None, nullptr); + + if (!AttributeObject) + { + // See if the ref is a class that we can instantiate + UClass * FoundClass = FindObject(ANY_PACKAGE, *AssetName); + if (FoundClass != nullptr) + { + // TODO: ensure we'll be able to create an actor from this class! + AttributeObject = FoundClass; + } + } + + if (!AttributeObject && bDefaultObjectEnabled) + { + HOUDINI_LOG_WARNING(TEXT("Failed to load instanced object '%s', using default instance mesh (hidden in game)."), *(AssetName)); + + // Couldn't load the referenced object, use the default reference mesh + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (!DefaultReferenceSM || DefaultReferenceSM->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to load the default instance mesh.")); + return false; + } + AttributeObject = DefaultReferenceSM; + } + + // Attach the objectPtr/transforms/bHiddenInGame if the attributeObject is created successfully + // (with either the actual referenced object or the default placeholder object) + if (AttributeObject) + { + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + + TArray Indices; + Indices.SetNum(InstancerUnrealTransforms.Num()); + for (int32 Index = 0; Index < Indices.Num(); ++Index) + { + Indices[Index] = Index; + } + + OutInstancedIndices.Add(Indices); + + if(bHasSplitAttribute) + SplitAttributeValuesPerObject.Add(AllSplitAttributeValues); + } + } + else + { + // Attribute is on points, so we may have different values for each of them + TArray PointInstanceValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsStringFromInfo( + InHGPO.GeoId, + InHGPO.PartId, + is_override_attr ? HAPI_UNREAL_ATTRIB_INSTANCE_OVERRIDE : HAPI_UNREAL_ATTRIB_INSTANCE, + AttribInfo, + PointInstanceValues)) + { + // This should not happen - attribute exists, but there was an error retrieving it. + return false; + } + + // The attribute is on points, so the number of points must match number of transforms. + if (!ensure(PointInstanceValues.Num() == InstancerUnrealTransforms.Num())) + { + // This should not happen, we have mismatch between number of instance values and transforms. + return false; + } + + // If instance attribute exists on points, we need to get all the unique values. + // This will give us all the unique object we want to instance + TMap ObjectsToInstance; + for (const auto& Iter : PointInstanceValues) + { + if (!ObjectsToInstance.Contains(Iter)) + { + // To avoid trying to load an object that fails multiple times, + // still add it to the array if null so we can still skip further attempts + UObject * AttributeObject = StaticLoadObject( + UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); + + if (!AttributeObject) + { + // See if the ref is a class that we can instantiate + UClass * FoundClass = FindObject(ANY_PACKAGE, *Iter); + if (FoundClass != nullptr) + { + // TODO: ensure we'll be able to create an actor from this class! + AttributeObject = FoundClass; + } + } + + ObjectsToInstance.Add(Iter, AttributeObject); + } + } + + // Iterates through all the unique objects and get their corresponding transforms + bool Success = false; + for (auto Iter : ObjectsToInstance) + { + bool bHiddenInGame = false; + // Check that we managed to load this object + UObject * AttributeObject = Iter.Value; + if (!AttributeObject && bDefaultObjectEnabled) + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load instanced object '%s', use default mesh (hidden in game)."), *(Iter.Key)); + + // If failed to load this object, add default reference mesh + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + { + AttributeObject = DefaultReferenceSM; + bHiddenInGame = true; + } + else// Failed to load default reference mesh object + { + HOUDINI_LOG_WARNING(TEXT("Failed to load default mesh.")); + continue; + } + } + + if (!AttributeObject) + continue; + + if (!bHasSplitAttribute) + { + // No Split attribute: + // Extract the transform values that correspond to this object, and add them to the output arrays + const FString & InstancePath = Iter.Key; + TArray ObjectTransforms; + TArray ObjectIndices; + + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) + { + if (InstancePath.Equals(PointInstanceValues[Idx])) + { + ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + ObjectIndices.Add(Idx); + } + } + + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(ObjectTransforms); + OutInstancedIndices.Add(ObjectIndices); + Success = true; + } + else + { + // We have a split attribute: + // Extract the transform values and split attribute values for this object, + // add them to the output arrays, and we will process the splits after + const FString & InstancePath = Iter.Key; + TArray ObjectTransforms; + TArray ObjectIndices; + TArray ObjectSplitValues; + for (int32 Idx = 0; Idx < PointInstanceValues.Num(); ++Idx) + { + if (InstancePath.Equals(PointInstanceValues[Idx])) + { + ObjectTransforms.Add(InstancerUnrealTransforms[Idx]); + ObjectIndices.Add(Idx); + ObjectSplitValues.Add(AllSplitAttributeValues[Idx]); + } + } + + OutInstancedObjects.Add(AttributeObject); + OutInstancedTransforms.Add(ObjectTransforms); + OutInstancedIndices.Add(ObjectIndices); + SplitAttributeValuesPerObject.Add(ObjectSplitValues); + Success = true; + } + } + + if (!Success) + return false; + } + + // If we don't need to split the instances, we're done + if (!bHasSplitAttribute) + return true; + + // Split the instances one more time, this time using the split values + + // Move the output arrays to temp arrays + TArray UnsplitInstancedObjects = OutInstancedObjects; + TArray> UnsplitInstancedTransforms = OutInstancedTransforms; + TArray> UnsplitInstancedIndices = OutInstancedIndices; + + // Empty the output arrays + OutInstancedObjects.Empty(); + OutInstancedTransforms.Empty(); + OutInstancedIndices.Empty(); + + // TODO: Output the split values as well! + OutSplitAttributeValue.Empty(); + for (int32 ObjIdx = 0; ObjIdx < UnsplitInstancedObjects.Num(); ObjIdx++) + { + UObject* InstancedObject = UnsplitInstancedObjects[ObjIdx]; + + // Map of split values to transform arrays + TMap> SplitTransformMap; + TMap> SplitIndicesMap; + + TArray& CurrentTransforms = UnsplitInstancedTransforms[ObjIdx]; + TArray& CurrentIndices = UnsplitInstancedIndices[ObjIdx]; + TArray& CurrentSplits = SplitAttributeValuesPerObject[ObjIdx]; + + int32 NumInstances = CurrentTransforms.Num(); + if (CurrentSplits.Num() != NumInstances || CurrentIndices.Num() != NumInstances) + continue; + + // Split the transforms using the split values + for (int32 InstIdx = 0; InstIdx < NumInstances; InstIdx++) + { + const FString& SplitAttrValue = CurrentSplits[InstIdx]; + SplitTransformMap.FindOrAdd(SplitAttrValue).Add(CurrentTransforms[InstIdx]); + SplitIndicesMap.FindOrAdd(SplitAttrValue).Add(CurrentIndices[InstIdx]); + + // Record attributes for any split value we have not yet seen + if (bHasAnyPerSplitAttributes) + { + FHoudiniInstancedOutputPerSplitAttributes& PerSplitAttributes = OutPerSplitAttributes.FindOrAdd(SplitAttrValue); + if (bHasLevelPaths && PerSplitAttributes.LevelPath.IsEmpty() && AllLevelPaths.IsValidIndex(InstIdx)) + { + PerSplitAttributes.LevelPath = AllLevelPaths[InstIdx]; + } + if (bHasBakeActorNames && PerSplitAttributes.BakeActorName.IsEmpty() && AllBakeActorNames.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeActorName = AllBakeActorNames[InstIdx]; + } + if (bHasBakeFolders && PerSplitAttributes.BakeFolder.IsEmpty() && AllBakeFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeFolder = AllBakeFolders[InstIdx]; + } + if (bHasBakeOutlinerFolders && PerSplitAttributes.BakeOutlinerFolder.IsEmpty() && AllBakeOutlinerFolders.IsValidIndex(InstIdx)) + { + PerSplitAttributes.BakeOutlinerFolder = AllBakeOutlinerFolders[InstIdx]; + } + } + } + + // Add the objects, transform, split values to the final arrays + for (auto& Iterator : SplitTransformMap) + { + OutSplitAttributeValue.Add(Iterator.Key); + OutInstancedObjects.Add(InstancedObject); + OutInstancedTransforms.Add(Iterator.Value); + OutInstancedIndices.Add(SplitIndicesMap[Iterator.Key]); + } + } + + OutSplitAttributeName = SplitAttribName; + + return true; +} + + +bool +FHoudiniInstanceTranslator::GetOldSchoolAttributeInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::OldSchoolAttributeInstancer) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the objects IDs to instanciate + int32 NumPoints = InHGPO.PartInfo.PointCount; + TArray InstancedObjectIds; + InstancedObjectIds.SetNumUninitialized(NumPoints); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetInstancedObjectIds( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InstancedObjectIds.GetData(), 0, NumPoints), false); + + // Find the set of instanced object ids and locate the corresponding parts + TSet UniqueInstancedObjectIds(InstancedObjectIds); + + // Locate all the HoudiniGeoPartObject that corresponds to the instanced object IDs + for (int32 InstancedObjectId : UniqueInstancedObjectIds) + { + // Get the parts that correspond to that object Id + TArray PartsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) + { + if (OutHGPO.Type != EHoudiniPartType::Mesh) + continue; + + if (OutHGPO.bIsInstanced) + continue; + + if (InstancedObjectId != OutHGPO.ObjectId) + continue; + + PartsToInstance.Add(OutHGPO); + } + } + + // Extract only the transforms that correspond to that specific object ID + TArray InstanceTransforms; + TArray InstanceIndices; + for (int32 Ix = 0; Ix < InstancedObjectIds.Num(); ++Ix) + { + if ((InstancedObjectIds[Ix] == InstancedObjectId) && (InstancerUnrealTransforms.IsValidIndex(Ix))) + { + InstanceTransforms.Add(InstancerUnrealTransforms[Ix]); + InstanceIndices.Add(Ix); + } + } + + // Add the instanced parts and their transforms to the output arrays + for (const auto& PartToInstance : PartsToInstance) + { + OutInstancedHGPO.Add(PartToInstance); + OutInstancedTransforms.Add(InstanceTransforms); + OutInstancedIndices.Add(InstanceIndices); + } + } + + if(OutInstancedHGPO.Num() > 0 && OutInstancedTransforms.Num() > 0 && OutInstancedIndices.Num() > 0) + return true; + + return false; +} + + +bool +FHoudiniInstanceTranslator::GetObjectInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices) +{ + if (InHGPO.InstancerType != EHoudiniInstancerType::ObjectInstancer) + return false; + + if (InHGPO.ObjectInfo.ObjectToInstanceID < 0) + return false; + + // Get the instance transforms + TArray InstancerUnrealTransforms; + if (!HapiGetInstanceTransforms(InHGPO, InstancerUnrealTransforms)) + { + // failed to get instance transform + return false; + } + + // Get the parts that correspond to that Object Id + TArray PartsToInstance; + for (const auto& Output : InAllOutputs) + { + if (!Output || Output->Type != EHoudiniOutputType::Mesh) + continue; + + for (const auto& OutHGPO : Output->HoudiniGeoPartObjects) + { + if (OutHGPO.Type != EHoudiniPartType::Mesh) + continue; + + /* + // But the instanced geo is actually not marked as instanced + if (!OutHGPO.bIsInstanced) + continue; + */ + + if (InHGPO.ObjectInfo.ObjectToInstanceID != OutHGPO.ObjectId) + continue; + + PartsToInstance.Add(OutHGPO); + } + } + + // Add found HGPO and transforms to the output arrays + for (auto& InstanceHGPO : PartsToInstance) + { + InstanceHGPO.TransformMatrix = InHGPO.TransformMatrix; + + // TODO: + //InstanceHGPO.UpdateCustomName(); + + OutInstancedHGPO.Add(InstanceHGPO); + OutInstancedTransforms.Add(InstancerUnrealTransforms); + + TArray Indices; + Indices.SetNum(InstancerUnrealTransforms.Num()); + for (int32 Index = 0; Index < Indices.Num(); ++Index) + { + Indices[Index] = Index; + } + + OutInstancedIndices.Add(Indices); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent* OldComponent, + USceneComponent*& NewComponent, + const bool& InIsSplitMeshInstancer, + const bool& InIsFoliageInstancer, + const TArray& InstancerMaterials, + const TArray& OriginalInstancerObjectIndices, + const int32& InstancerObjectIdx, + const bool& bForceHISM) +{ + enum InstancerComponentType + { + Invalid = -1, + InstancedStaticMeshComponent = 0, + HierarchicalInstancedStaticMeshComponent = 1, + MeshSplitInstancerComponent = 2, + HoudiniInstancedActorComponent = 3, + StaticMeshComponent = 4, + HoudiniStaticMeshComponent = 5, + Foliage = 6 + }; + + // See if we can reuse the old component + InstancerComponentType OldType = InstancerComponentType::Invalid; + if (OldComponent/*&& !OldComponent->IsPendingKill()*/) // The old component could be marked as pending kill + { + if(OldComponent->IsA()) + OldType = Foliage; + else if (OldComponent->GetOwner() && OldComponent->GetOwner()->IsA()) + OldType = Foliage; + else if (OldComponent->IsA()) + OldType = HierarchicalInstancedStaticMeshComponent; + else if (OldComponent->IsA()) + OldType = InstancedStaticMeshComponent; + else if (OldComponent->IsA()) + OldType = MeshSplitInstancerComponent; + else if (OldComponent->IsA()) + OldType = HoudiniInstancedActorComponent; + else if (OldComponent->IsA()) + OldType = StaticMeshComponent; + else if (OldComponent->IsA()) + OldType = HoudiniStaticMeshComponent; + } + + // See what type of component we want to create + InstancerComponentType NewType = InstancerComponentType::Invalid; + + UStaticMesh * StaticMesh = Cast(InstancedObject); + UFoliageType * FoliageType = Cast(InstancedObject); + + UHoudiniStaticMesh * HSM = nullptr; + if (!StaticMesh && !FoliageType) + HSM = Cast(InstancedObject); + + if (StaticMesh) + { + if (InstancedObjectTransforms.Num() == 1) + NewType = StaticMeshComponent; + else if (InIsFoliageInstancer) + NewType = Foliage; + else if (InIsSplitMeshInstancer) + NewType = MeshSplitInstancerComponent; + else if(StaticMesh->GetNumLODs() > 1 || bForceHISM) + NewType = HierarchicalInstancedStaticMeshComponent; + else + NewType = InstancedStaticMeshComponent; + } + else if (HSM) + { + if (InstancedObjectTransforms.Num() == 1) + NewType = HoudiniStaticMeshComponent; + else + { + HOUDINI_LOG_ERROR(TEXT("More than one instance transform encountered for UHoudiniStaticMesh: %s"), *(HSM->GetPathName())); + NewType = Invalid; + return false; + } + } + else if (FoliageType) + { + NewType = Foliage; + } + else + { + NewType = HoudiniInstancedActorComponent; + } + + if (OldType == NewType) + NewComponent = OldComponent; + + // First valid index in the original instancer part + // This should be used to access attributes that are store for the whole part, not split + // (ie, GenericProperty Attributes) + int32 FirstOriginalIndex = OriginalInstancerObjectIndices.Num() > 0 ? OriginalInstancerObjectIndices[0] : 0; + + // InstancerMaterials has all the material assignement per instance, + // Fetch the first one for the components that can only use one material + UMaterialInterface* InstancerMaterial = InstancerMaterials.Num() > 0 ? InstancerMaterials[0] : nullptr; + + bool bSuccess = false; + switch (NewType) + { + case InstancedStaticMeshComponent: + case HierarchicalInstancedStaticMeshComponent: + { + // Create an Instanced Static Mesh Component + bSuccess = CreateOrUpdateInstancedStaticMeshComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial, bForceHISM, FirstOriginalIndex); + } + break; + + case MeshSplitInstancerComponent: + { + bSuccess = CreateOrUpdateMeshSplitInstancerComponent( + StaticMesh, InstancedObjectTransforms, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterials); + } + break; + + case HoudiniInstancedActorComponent: + { + bSuccess = CreateOrUpdateInstancedActorComponent( + InstancedObject, InstancedObjectTransforms, OriginalInstancerObjectIndices, AllPropertyAttributes, ParentComponent, NewComponent); + } + break; + + case StaticMeshComponent: + { + // Create a Static Mesh Component + bSuccess = CreateOrUpdateStaticMeshComponent( + StaticMesh, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + break; + + case HoudiniStaticMeshComponent: + { + // Create a Houdini Static Mesh Component + bSuccess = CreateOrUpdateHoudiniStaticMeshComponent( + HSM, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + break; + + case Foliage: + { + bSuccess = CreateOrUpdateFoliageInstances( + StaticMesh, FoliageType, InstancedObjectTransforms, FirstOriginalIndex, AllPropertyAttributes, InstancerGeoPartObject, ParentComponent, NewComponent, InstancerMaterial); + } + } + + if (!NewComponent) + return false; + + NewComponent->SetMobility(ParentComponent->Mobility); + NewComponent->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepRelativeTransform); + + // For single instance, that generates a SMC, the transform is already set on the component + // TODO: Should cumulate transform in that case? + if(NewType != StaticMeshComponent && NewType != HoudiniStaticMeshComponent) + NewComponent->SetRelativeTransform(InstancerGeoPartObject.TransformMatrix); + + // Only register if we have a valid component + if (NewComponent->GetOwner() && NewComponent->GetWorld()) + NewComponent->RegisterComponent(); + + // If the old component couldn't be reused, dettach/ destroy it + if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) + { + RemoveAndDestroyComponent(OldComponent, nullptr); + } + + return bSuccess; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial, /*=nullptr*/ + const bool & bForceHISM, + const int32& InstancerObjectIdx) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast(CreatedInstancedComponent); + if (!InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill()) + { + if (InstancedStaticMesh->GetNumLODs() > 1 || bForceHISM) + { + // If the mesh has LODs, use Hierarchical ISMC + InstancedStaticMeshComponent = NewObject( + ComponentOuter, UHierarchicalInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + else + { + // If the mesh doesnt have LOD, we can use a regular ISMC + InstancedStaticMeshComponent = NewObject( + ComponentOuter, UInstancedStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + } + + // Change the creation method so the component is listed in the details panels + if (InstancedStaticMeshComponent) + { + InstancedStaticMeshComponent->CreationMethod = EComponentCreationMethod::Instance; + } + + bCreatedNewComponent = true; + } + + if (!InstancedStaticMeshComponent) + return false; + + InstancedStaticMeshComponent->SetStaticMesh(InstancedStaticMesh); + + if (InstancedStaticMeshComponent->GetBodyInstance()) + { + InstancedStaticMeshComponent->GetBodyInstance()->bAutoWeld = false; + } + + InstancedStaticMeshComponent->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedStaticMesh->GetStaticMaterials().Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + InstancedStaticMeshComponent->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances themselves + InstancedStaticMeshComponent->ClearInstances(); + InstancedStaticMeshComponent->AddInstances(InstancedObjectTransforms, false); + + // Apply generic attributes if we have any + UpdateGenericPropertiesAttributes(InstancedStaticMeshComponent, AllPropertyAttributes, InstancerObjectIdx); + + // Assign the new ISMC / HISMC to the output component if we created a new one + if(bCreatedNewComponent) + CreatedInstancedComponent = InstancedStaticMeshComponent; + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& OriginalInstancerObjectIndices, + const TArray& AllPropertyAttributes, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent) +{ + if (!InstancedObject) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniInstancedActorComponent* InstancedActorComponent = Cast(CreatedInstancedComponent); + if (!InstancedActorComponent || InstancedActorComponent->IsPendingKill()) + { + // If the mesh doesnt have LOD, we can use a regular ISMC + InstancedActorComponent = NewObject( + ComponentOuter, UHoudiniInstancedActorComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + InstancedActorComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!InstancedActorComponent) + return false; + + // See if the instanced object has changed + bool bInstancedObjectHasChanged = (InstancedObject != InstancedActorComponent->GetInstancedObject()); + if (bInstancedObjectHasChanged) + { + // All actors will need to be respawned, invalidate all of them + InstancedActorComponent->ClearAllInstances(); + + // Update the HIAC's instanced asset + InstancedActorComponent->SetInstancedObject(InstancedObject); + } + + // Get the level where we want to spawn the actors + ULevel* SpawnLevel = ParentComponent->GetOwner() ? ParentComponent->GetOwner()->GetLevel() : nullptr; + if (!SpawnLevel) + return false; + + // Set the number of needed instances + InstancedActorComponent->SetNumberOfInstances(InstancedObjectTransforms.Num()); + + for (int32 Idx = 0; Idx < InstancedObjectTransforms.Num(); Idx++) + { + // if we already have an actor, we can reuse it + const FTransform& CurTransform = InstancedObjectTransforms[Idx]; + + // Get the current instance + // If null, we need to create a new one, else we can reuse the actor + AActor* CurInstance = InstancedActorComponent->GetInstancedActorAt(Idx); + if (!CurInstance || CurInstance->IsPendingKill()) + { + CurInstance = SpawnInstanceActor(CurTransform, SpawnLevel, InstancedActorComponent); + InstancedActorComponent->SetInstanceAt(Idx, CurTransform, CurInstance); + } + else + { + // We can simply update the actor's transform + InstancedActorComponent->SetInstanceTransformAt(Idx, CurTransform); + } + + // Update the generic properties for that instance if any + UpdateGenericPropertiesAttributes(CurInstance, AllPropertyAttributes, OriginalInstancerObjectIndices[Idx]); + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + { + CreatedInstancedComponent = InstancedActorComponent; + } + + return true; +} + +// Create or update a MSIC +bool +FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + const TArray& InInstancerMaterials) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniMeshSplitInstancerComponent* MeshSplitComponent = Cast(CreatedInstancedComponent); + if (!MeshSplitComponent || MeshSplitComponent->IsPendingKill()) + { + // If the mesh doesn't have LOD, we can use a regular ISMC + MeshSplitComponent = NewObject( + ComponentOuter, UHoudiniMeshSplitInstancerComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + MeshSplitComponent->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!MeshSplitComponent) + return false; + + MeshSplitComponent->SetStaticMesh(InstancedStaticMesh); + MeshSplitComponent->SetOverrideMaterials(InInstancerMaterials); + + // Now add the instances + MeshSplitComponent->SetInstanceTransforms(InstancedObjectTransforms); + + // Check for instance colors + TArray InstanceColorOverrides; + bool ColorOverrideAttributeFound = false; + + // Look for the unreal_instance_color attribute on points + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_POINT, &AttributeInfo)) + { + ColorOverrideAttributeFound = AttributeInfo.exists; + } + + // Look for the unreal_instance_color attribute on prims? (why? original code) + if (!ColorOverrideAttributeFound) + { + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, HAPI_AttributeOwner::HAPI_ATTROWNER_PRIM, &AttributeInfo)) + { + ColorOverrideAttributeFound = AttributeInfo.exists; + } + } + + if (ColorOverrideAttributeFound) + { + if (AttributeInfo.tupleSize == 4) + { + // Allocate sufficient buffer for data. + InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)InstanceColorOverrides.GetData(), 0, AttributeInfo.count)) + { + InstanceColorOverrides.Empty(); + } + } + else if (AttributeInfo.tupleSize == 3) + { + // Allocate sufficient buffer for data. + TArray FloatValues; + FloatValues.SetNumZeroed(AttributeInfo.count * AttributeInfo.tupleSize); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), InstancerGeoPartObject.GeoId, InstancerGeoPartObject.PartId, + HAPI_UNREAL_ATTRIB_INSTANCE_COLOR, &AttributeInfo, -1, (float*)FloatValues.GetData(), 0, AttributeInfo.count)) + { + + // Allocate sufficient buffer for data. + InstanceColorOverrides.SetNumZeroed(AttributeInfo.count); + + // Convert float to FLinearColors + for (int32 ColorIdx = 0; ColorIdx < InstanceColorOverrides.Num(); ColorIdx++) + { + InstanceColorOverrides[ColorIdx].R = FloatValues[ColorIdx * AttributeInfo.tupleSize + 0]; + InstanceColorOverrides[ColorIdx].G = FloatValues[ColorIdx * AttributeInfo.tupleSize + 1]; + InstanceColorOverrides[ColorIdx].B = FloatValues[ColorIdx * AttributeInfo.tupleSize + 2]; + InstanceColorOverrides[ColorIdx].A = 1.0; + } + FloatValues.Empty(); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_COLOR " must be a float[4] or float[3] prim/point attribute")); + } + } + + // if we have vertex color overrides, apply them now +#if WITH_EDITOR + if (InstanceColorOverrides.Num() > 0) + { + // Convert the color attribute to FColor + TArray InstanceColors; + InstanceColors.SetNumUninitialized(InstanceColorOverrides.Num()); + for (int32 ix = 0; ix < InstanceColors.Num(); ++ix) + { + InstanceColors[ix] = InstanceColorOverrides[ix].GetClamped().ToFColor(false); + } + + // Apply them to the instances + TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); + for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) + { + UStaticMeshComponent* CurSMC = Instances[InstIndex]; + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + if (!InstanceColors.IsValidIndex(InstIndex)) + continue; + + MeshPaintHelpers::FillStaticMeshVertexColors(CurSMC, -1, InstanceColors[InstIndex], FColor::White); + + //CurSMC->UnregisterComponent(); + //CurSMC->ReregisterComponent(); + + { + // We're only changing instanced vertices on this specific mesh component, so we + // only need to detach our mesh component + FComponentReregisterContext ComponentReregisterContext(CurSMC); + for (auto& CurLODData : CurSMC->LODData) + { + BeginInitResource(CurLODData.OverrideVertexColors); + } + } + + //FIXME: How to get rid of the warning about fixup vertex colors on load? + //SMC->FixupOverrideColorsIfNecessary(); + } + } +#endif + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + // TODO: Optimize + // Loop on attributes first, then components, + // if failing to find the attrib on a component, skip the rest + if (AllPropertyAttributes.Num() > 0) + { + TArray& Instances = MeshSplitComponent->GetInstancesForWrite(); + for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) + { + UStaticMeshComponent* CurSMC = Instances[InstIndex]; + if (!CurSMC || CurSMC->IsPendingKill()) + continue; + + UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); + } + } + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = MeshSplitComponent; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + if (!InstancedStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UStaticMeshComponent* SMC = Cast(CreatedInstancedComponent); + if (!SMC || SMC->IsPendingKill()) + { + // Create a new StaticMeshComponent + SMC = NewObject( + ComponentOuter, UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + SMC->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!SMC) + return false; + + SMC->SetStaticMesh(InstancedStaticMesh); + SMC->GetBodyInstance()->bAutoWeld = false; + + SMC->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedStaticMesh->GetStaticMaterials().Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + SMC->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances Transform + if (InstancedObjectTransforms.Num() > 0) + { + SMC->SetRelativeTransform(InstancedObjectTransforms[0]); + } + + // Apply generic attributes if we have any + UpdateGenericPropertiesAttributes(SMC, AllPropertyAttributes, InOriginalIndex); + + // Assign the new ISMC / HISMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = SMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( + UHoudiniStaticMesh* InstancedProxyStaticMesh, + const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + if (!InstancedProxyStaticMesh) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + bool bCreatedNewComponent = false; + UHoudiniStaticMeshComponent* HSMC = Cast(CreatedInstancedComponent); + if (!HSMC || HSMC->IsPendingKill()) + { + // Create a new StaticMeshComponent + HSMC = NewObject( + ComponentOuter, UHoudiniStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + // Change the creation method so the component is listed in the details panels + HSMC->CreationMethod = EComponentCreationMethod::Instance; + + bCreatedNewComponent = true; + } + + if (!HSMC) + return false; + + HSMC->SetMesh(InstancedProxyStaticMesh); + + HSMC->OverrideMaterials.Empty(); + if (InstancerMaterial) + { + int32 MeshMaterialCount = InstancedProxyStaticMesh->GetNumStaticMaterials(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + HSMC->SetMaterial(Idx, InstancerMaterial); + } + + // Now add the instances Transform + HSMC->SetRelativeTransform(InstancedObjectTransforms[0]); + + // Apply generic attributes if we have any + // TODO: Handle variations w/ index + UpdateGenericPropertiesAttributes(HSMC, AllPropertyAttributes, InOriginalIndex); + + // Assign the new HSMC to the output component if we created a new one + if (bCreatedNewComponent) + CreatedInstancedComponent = HSMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + + +bool +FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( + UStaticMesh* InstancedStaticMesh, + UFoliageType* InFoliageType, + const TArray& InstancedObjectTransforms, + const int32& FirstOriginalIndex, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& NewInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/) +{ + // We need either a valid SM or a valid Foliage Type + if ((!InstancedStaticMesh || InstancedStaticMesh->IsPendingKill()) + && (!InFoliageType || InFoliageType->IsPendingKill())) + return false; + + if (!ParentComponent || ParentComponent->IsPendingKill()) + return false; + + UObject* ComponentOuter = ParentComponent; + if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + ComponentOuter = ParentComponent->GetOwner(); + + AActor* OwnerActor = ParentComponent->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return false; + + // See if we already have a FoliageType for that static mesh + bool bCreatedNew = false; + UFoliageType *FoliageType = InFoliageType; + if (!FoliageType || FoliageType->IsPendingKill()) + { + // Foliage Type wasnt specified, only the mesh, try to find an existing foliage for that SM + FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InstancedStaticMesh); + } + else + { + // Foliage Type was specified, see if we can get its static mesh + UFoliageType_InstancedStaticMesh* FoliageISM = Cast(InFoliageType); + if (FoliageISM) + { + InstancedStaticMesh = FoliageISM->GetStaticMesh(); + } + + // See a component already exist on the actor + // If we cant find Foliage info for that foliage type, a new one will be created. + // when we call FindOrAddMesh + bCreatedNew = InstancedFoliageActor->FindInfo(FoliageType) == nullptr; + } + + if (!FoliageType || FoliageType->IsPendingKill()) + { + // We need to create a new FoliageType for this Static Mesh + // TODO: Add foliage default settings + InstancedFoliageActor->AddMesh(InstancedStaticMesh, &FoliageType); + bCreatedNew = true; + } + + if (!bCreatedNew && NewInstancedComponent) + { + // TODO: Shouldnt be needed anymore + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(ParentComponent, FoliageType); + } + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if (!FoliageInfo) + return false; + + FTransform HoudiniAssetTransform = ParentComponent->GetComponentTransform(); + FFoliageInstance FoliageInstance; + int32 CurrentInstanceCount = 0; + + FoliageInfo->ReserveAdditionalInstances(InstancedFoliageActor, FoliageType, InstancedObjectTransforms.Num()); + for (auto CurrentTransform : InstancedObjectTransforms) + { + // Use our parent component for the base component of the instances, + // this will allow us to clean the instances by component + FoliageInstance.BaseComponent = ParentComponent; + + // TODO: FIX ME! + // Somehow, the first time when we create the Foliage type, instances need to be added with relative transform + // On subsequent cooks, they are actually expecting world transform + if (bCreatedNew) + { + FoliageInstance.Location = CurrentTransform.GetLocation(); + FoliageInstance.Rotation = CurrentTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D(); + } + else + { + FoliageInstance.Location = HoudiniAssetTransform.TransformPosition(CurrentTransform.GetLocation()); + FoliageInstance.Rotation = HoudiniAssetTransform.TransformRotation(CurrentTransform.GetRotation()).Rotator(); + FoliageInstance.DrawScale3D = CurrentTransform.GetScale3D() * HoudiniAssetTransform.GetScale3D(); + } + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + CurrentInstanceCount++; + } + + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = FoliageInfo->GetComponent(); + if (IsValid(FoliageHISMC)) + { + // TODO: This was due to a bug in UE4.22-20, check if still needed! + FoliageHISMC->BuildTreeIfOutdated(true, true); + + if (InstancerMaterial) + { + FoliageHISMC->OverrideMaterials.Empty(); + int32 MeshMaterialCount = InstancedStaticMesh ? InstancedStaticMesh->GetStaticMaterials().Num() : 1; + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + FoliageHISMC->SetMaterial(Idx, InstancerMaterial); + } + } + + // Try to apply generic properties attributes + // either on the instancer, mesh or foliage type + // TODO: Use proper atIndex!! + UpdateGenericPropertiesAttributes(FoliageHISMC, AllPropertyAttributes, FirstOriginalIndex); + UpdateGenericPropertiesAttributes(InstancedStaticMesh, AllPropertyAttributes, FirstOriginalIndex); + UpdateGenericPropertiesAttributes(FoliageType, AllPropertyAttributes, FirstOriginalIndex); + + if (IsValid(FoliageHISMC)) + NewInstancedComponent = FoliageHISMC; + + // TODO: + // We want to make this invisible if it's a collision instancer. + //CreatedInstancedComponent->SetVisibility(!InstancerGeoPartObject.bIsCollidable); + + return true; +} + +bool +FHoudiniInstanceTranslator::HapiGetInstanceTransforms( + const FHoudiniGeoPartObject& InHGPO, TArray& OutInstancerUnrealTransforms) +{ + // Get the instance transforms + int32 PointCount = InHGPO.PartInfo.PointCount; + if (PointCount <= 0) + return false; + + TArray InstanceTransforms; + InstanceTransforms.SetNum(PointCount); + for (int32 Idx = 0; Idx < InstanceTransforms.Num(); Idx++) + FHoudiniApi::Transform_Init(&(InstanceTransforms[Idx])); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetInstanceTransformsOnPart( + FHoudiniEngine::Get().GetSession(), + InHGPO.GeoId, InHGPO.PartId, HAPI_SRT, + &InstanceTransforms[0], 0, PointCount)) + { + InstanceTransforms.SetNum(0); + + // TODO: Warning? error? + return false; + } + + // Convert the transform to Unreal's coordinate system + OutInstancerUnrealTransforms.SetNumZeroed(InstanceTransforms.Num()); + for (int32 InstanceIdx = 0; InstanceIdx < InstanceTransforms.Num(); InstanceIdx++) + { + const auto& InstanceTransform = InstanceTransforms[InstanceIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(InstanceTransform, OutInstancerUnrealTransforms[InstanceIdx]); + } + + return true; +} + +bool +FHoudiniInstanceTranslator::GetGenericPropertiesAttributes( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutPropertyAttributes) +{ + // List all the generic property detail attributes ... + int32 FoundCount = FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_DETAIL); + + // .. then get all the values for the primitive property attributes + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_PRIM, -1); + + // .. then finally, all values for point uproperty attributes + // TODO: !! get the correct Index here? + FoundCount += FHoudiniEngineUtils::GetGenericAttributeList( + (HAPI_NodeId)InGeoNodeId, (HAPI_PartId)InPartId, HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, OutPropertyAttributes, HAPI_ATTROWNER_POINT, -1); + + return FoundCount > 0; +} + +bool +FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes, const int32& AtIndex) +{ + if (!IsValid(InObject)) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + if (CurrentPropAttribute.AttributeName.Equals(TEXT("NumCustomDataFloats"), ESearchCase::IgnoreCase)) + { + // Skip, as setting NumCustomDataFloats this way causes Unreal to crash! + HOUDINI_LOG_WARNING( + TEXT("Skipping UProperty %s on %s, custom data floats should be modified via the unreal_num_custom_floats and unreal_per_instance_custom_dataX attributes"), + *CurrentPropAttribute.AttributeName, *InObject->GetName()); + continue; + } + + // Update the current property for the given instance index + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute, AtIndex)) + continue; + + // Success! + NumSuccess++; + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, InObject->GetClass() ? *InObject->GetClass()->GetName() : TEXT("Object"), *InObject->GetName()); + } + + return (NumSuccess > 0); +} + +bool +FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent, UObject* InFoliageObject) +{ + if (!InComponent || InComponent->IsPendingKill()) + return false; + + UFoliageInstancedStaticMeshComponent* FISMC = Cast(InComponent); + if (FISMC && !FISMC->IsPendingKill()) + { + // Make sure foliage our foliage instances have been removed + USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); + if (ParentComponent && !ParentComponent->IsPendingKill()) + CleanupFoliageInstances(FISMC, InFoliageObject, ParentComponent); + + // do not delete FISMC that still have instances left + // as we have cleaned up our instances before, these have been hand-placed + if (FISMC->GetInstanceCount() > 0) + return false; + } + + USceneComponent* SceneComponent = Cast(InComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + return true; + } + + return false; +} + + +bool +FHoudiniInstanceTranslator::GetMaterialOverridesFromAttributes( + const int32& InGeoNodeId, const int32& InPartId, TArray& OutMaterialAttributes, bool& OutMaterialOverrideNeedsCreateInstance) +{ + HAPI_AttributeInfo MaterialAttributeInfo; + FHoudiniApi::AttributeInfo_Init(&MaterialAttributeInfo); + + OutMaterialOverrideNeedsCreateInstance = false; + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoNodeId, InPartId, HAPI_UNREAL_ATTRIB_MATERIAL, MaterialAttributeInfo, OutMaterialAttributes); + + // If material attribute was not found, check fallback compatibility attribute. + if (!MaterialAttributeInfo.exists) + { + OutMaterialAttributes.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoNodeId, InPartId, + HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK, + MaterialAttributeInfo, OutMaterialAttributes); + } + + // If material attribute and fallbacks were not found, check the material instance attribute. + if (!MaterialAttributeInfo.exists) + { + OutMaterialAttributes.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoNodeId, InPartId, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, + MaterialAttributeInfo, OutMaterialAttributes); + + OutMaterialOverrideNeedsCreateInstance = true; + } + + if (!MaterialAttributeInfo.exists + /*&& MaterialAttributeInfo.owner != HAPI_ATTROWNER_PRIM + && MaterialAttributeInfo.owner != HAPI_ATTROWNER_DETAIL*/) + { + //HOUDINI_LOG_WARNING(TEXT("Instancer: the unreal_material attribute must be a primitive or detail attribute, ignoring the attribute.")); + OutMaterialAttributes.Empty(); + OutMaterialOverrideNeedsCreateInstance = false; + return false; + } + + return true; +} + +bool +FHoudiniInstanceTranslator::GetInstancerMaterials( + const TArray& MaterialAttributes, TArray& OutInstancerMaterials) +{ + // Use a map to avoid attempting to load the object for each instance + TMap MaterialMap; + + bool bHasValidMaterial = false; + + // Non-instanced materials check material attributes one by one + for (auto& CurrentMatString : MaterialAttributes) + { + UMaterialInterface* CurrentMaterialInterface = nullptr; + UMaterialInterface** FoundMaterial = MaterialMap.Find(CurrentMatString); + if (!FoundMaterial) + { + // See if we can find a material interface that matches the attribute + CurrentMaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentMatString, nullptr, LOAD_NoWarn, nullptr)); + + // Check validity + if (!CurrentMaterialInterface || CurrentMaterialInterface->IsPendingKill()) + CurrentMaterialInterface = nullptr; + else + bHasValidMaterial = true; + + // Add what we found to the material map to avoid unnecessary loads + MaterialMap.Add(CurrentMatString, CurrentMaterialInterface); + } + else + { + // Reuse what we previously found + CurrentMaterialInterface = *FoundMaterial; + } + + OutInstancerMaterials.Add(CurrentMaterialInterface); + } + + // IF we couldn't find at least one valid material interface, empty the array + if (!bHasValidMaterial) + OutInstancerMaterials.Empty(); + + return true; +} + +bool FHoudiniInstanceTranslator::GetInstancerMaterialInstances(const TArray& MaterialAttribute, + const FHoudiniGeoPartObject& InHGPO, const FHoudiniPackageParams& InPackageParams, + TArray& OutInstancerMaterials) +{ + TArray MaterialAndTexturePackages; + + // Purposefully empty material since it is satisfied by the override parameter + TMap InputAssignmentMaterials; + TMap OutputAssignmentMaterials; + + // The function in FHoudiniMaterialTranslator should already do the duplicate checks + if (FHoudiniMaterialTranslator::SortUniqueFaceMaterialOverridesAndCreateMaterialInstances(MaterialAttribute, InHGPO, InPackageParams, + MaterialAndTexturePackages, + InputAssignmentMaterials, OutputAssignmentMaterials, + false)) + { + OutputAssignmentMaterials.GenerateValueArray(OutInstancerMaterials); + if (OutInstancerMaterials.Num() > 0) + { + return true; + } + } + + return false; +} + +bool +FHoudiniInstanceTranslator::GetInstancerMaterials( + const int32& InGeoNodeId, const int32& InPartId, const FHoudiniGeoPartObject& InHGPO, const FHoudiniPackageParams& InPackageParams, TArray& OutInstancerMaterials) +{ + TArray MaterialAttributes; + bool bMaterialOverrideNeedsCreateInstance = false; + if (!GetMaterialOverridesFromAttributes(InGeoNodeId, InPartId, MaterialAttributes, bMaterialOverrideNeedsCreateInstance)) + MaterialAttributes.Empty(); + + if (!bMaterialOverrideNeedsCreateInstance) + { + return GetInstancerMaterials(MaterialAttributes, OutInstancerMaterials); + } + else + { + return GetInstancerMaterialInstances(MaterialAttributes, InHGPO, InPackageParams, OutInstancerMaterials); + } +} + +bool +FHoudiniInstanceTranslator::GetVariationMaterials( + FHoudiniInstancedOutput* InInstancedOutput, + const int32& InVariationIndex, + const TArray& InOriginalIndices, + const TArray& InInstancerMaterials, + TArray& OutVariationMaterials) +{ + if (!InInstancedOutput || InInstancerMaterials.Num() <= 0) + return false; + + // No variations, reuse the full array + if (InInstancedOutput->VariationObjects.Num() == 1) + { + for (int32 Idx = 0; Idx < InOriginalIndices.Num(); Idx++) + { + if (InInstancerMaterials.IsValidIndex(InOriginalIndices[Idx])) + OutVariationMaterials.Add(InInstancerMaterials[InOriginalIndices[Idx]]); + else + OutVariationMaterials.Add(InInstancerMaterials[0]); + } + + return true; + } + + // If we have variations, see if we can use the instancer mat array + // TODO: FIX ME! this wont work if we have split the instancer and added variations at the same time! + if (InInstancedOutput->TransformVariationIndices.Num() == InInstancerMaterials.Num()) + { + for (int32 Idx = 0; Idx < InInstancedOutput->TransformVariationIndices.Num(); Idx++) + { + int32 VariationAssignment = InInstancedOutput->TransformVariationIndices[Idx]; + if (VariationAssignment != InVariationIndex) + continue; + + OutVariationMaterials.Add(InInstancerMaterials[Idx]); + } + } + else + { + for (int32 Idx = 0; Idx < InOriginalIndices.Num(); Idx++) + { + if (InInstancerMaterials.IsValidIndex(InOriginalIndices[Idx])) + OutVariationMaterials.Add(InInstancerMaterials[InOriginalIndices[Idx]]); + else + OutVariationMaterials.Add(InInstancerMaterials[0]); + } + } + + return true; +} + +bool +FHoudiniInstanceTranslator::IsSplitInstancer(const int32& InGeoId, const int32& InPartId) +{ + bool bSplitMeshInstancer = false; + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; + bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); + + if (!bSplitMeshInstancer) + { + // Try on primitive + Owner = HAPI_ATTROWNER_PRIM; + bSplitMeshInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, Owner); + } + + if (!bSplitMeshInstancer) + return false; + + HAPI_AttributeInfo AttributeInfo; + TArray IntData; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_INSTANCES, + AttributeInfo, IntData, 0, Owner, 0, 1)) + { + return false; + } + + if (!AttributeInfo.exists || AttributeInfo.count <= 0) + return false; + + return (IntData[0] != 0); +} + +bool +FHoudiniInstanceTranslator::IsFoliageInstancer(const int32& InGeoId, const int32& InPartId) +{ + bool bIsFoliageInstancer = false; + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_DETAIL; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + + if (!bIsFoliageInstancer) + { + // Try on primitive + Owner = HAPI_ATTROWNER_PRIM; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + } + + if (!bIsFoliageInstancer) + { + // Finally, try on points + Owner = HAPI_ATTROWNER_POINT; + bIsFoliageInstancer = FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, Owner); + } + + if (!bIsFoliageInstancer) + return false; + + TArray IntData; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + // Get the first attribute value as Int + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, + AttributeInfo, IntData, 0, Owner, 0, 1); + + if (!AttributeInfo.exists || AttributeInfo.count <= 0) + return false; + + return (IntData[0] != 0); +} + + +AActor* +FHoudiniInstanceTranslator::SpawnInstanceActor( + const FTransform& InTransform, + ULevel* InSpawnLevel, + UHoudiniInstancedActorComponent* InIAC) +{ + if (!InIAC || InIAC->IsPendingKill()) + return nullptr; + + UObject* InstancedObject = InIAC->GetInstancedObject(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + return nullptr; + + AActor* NewActor = nullptr; + +#if WITH_EDITOR + // Try to spawn a new actor for the given transform + GEditor->ClickLocation = InTransform.GetTranslation(); + GEditor->ClickPlane = FPlane(GEditor->ClickLocation, FVector::UpVector); + + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(InSpawnLevel, InstancedObject, false, RF_Transactional, nullptr); + if (NewActors.Num() > 0) + { + if (NewActors[0] && !NewActors[0]->IsPendingKill()) + { + NewActor = NewActors[0]; + } + } +#endif + + // Make sure that the actor was spawned in the proper level + FHoudiniEngineUtils::MoveActorToLevel(NewActor, InSpawnLevel); + + return NewActor; +} + + +void +FHoudiniInstanceTranslator::CleanupFoliageInstances( + UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, + UObject* InInstancedObject, + USceneComponent* InParentComponent) +{ + if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) + return; + + UStaticMesh* FoliageSM = InFoliageHISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + return; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(InFoliageHISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + return; + + // Get the Foliage Type + UFoliageType *FoliageType = Cast(InInstancedObject); + if (!FoliageType || FoliageType->IsPendingKill()) + { + // Try to get the foliage type for the instanced mesh from the actor + FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InInstancedObject); + + if (!FoliageType || FoliageType->IsPendingKill()) + return; + } + + // Clean up the instances previously generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(InParentComponent, FoliageType); + + // Remove the foliage type if it doesn't have any more instances + if(InFoliageHISMC->GetInstanceCount() == 0) + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + + return; +} + + +FString +FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(UObject* InObject) +{ + USceneComponent* InComponent = Cast(InObject); + + FString InstancerType = TEXT("Instancer"); + if (InComponent && !InComponent->IsPendingKill()) + { + if (InComponent->IsA()) + { + InstancerType = TEXT("(Split Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Actor Instancer)"); + } + else if (InComponent->IsA()) + { + if (InComponent->GetOwner() && InComponent->GetOwner()->IsA()) + InstancerType = TEXT("(Foliage Instancer)"); + else + InstancerType = TEXT("(Hierarchical Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Mesh Instancer)"); + } + else if (InComponent->IsA()) + { + InstancerType = TEXT("(Static Mesh Component)"); + } + } + + return InstancerType; +} + +bool +FHoudiniInstanceTranslator::GetInstancerSplitAttributesAndValues( + const int32& InGeoId, + const int32& InPartId, + const HAPI_AttributeOwner& InSplitAttributeOwner, + FString& OutSplitAttributeName, + TArray& OutAllSplitAttributeValues) +{ + // See if the user has specified an attribute to split the instancers. + bool bHasSplitAttribute = false; + //FString SplitAttribName = FString(); + OutSplitAttributeName = FString(); + + // Look for the unreal_split_attr attribute + // This attribute indicates the name of the point attribute that we'll use to split the instances further + HAPI_AttributeInfo SplitAttribInfo; + FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); + + TArray StringData; + bHasSplitAttribute = FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_SPLIT_ATTR, + SplitAttribInfo, StringData, 1, InSplitAttributeOwner, 0, 1); + + if (!bHasSplitAttribute || !SplitAttribInfo.exists || StringData.Num() <= 0) + return false; + + OutSplitAttributeName = StringData[0]; + + // We have specified a split attribute, try to get its values. + OutAllSplitAttributeValues.Empty(); + if (!OutSplitAttributeName.IsEmpty()) + { + //HAPI_AttributeInfo SplitAttribInfo; + FHoudiniApi::AttributeInfo_Init(&SplitAttribInfo); + bool bSplitAttrFound = FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, + InPartId, + TCHAR_TO_ANSI(*OutSplitAttributeName), + SplitAttribInfo, + OutAllSplitAttributeValues, + 1, + InSplitAttributeOwner); + + if (!bSplitAttrFound || OutAllSplitAttributeValues.Num() <= 0) + { + // We couldn't properly get the point values + bHasSplitAttribute = false; + } + } + else + { + // We couldn't properly get the split attribute + bHasSplitAttribute = false; + } + + if (!bHasSplitAttribute) + { + // Clean up everything to ensure that we'll ignore the split attribute + OutAllSplitAttributeValues.Empty(); + OutSplitAttributeName = FString(); + } + + return bHasSplitAttribute; +} + +bool +FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId) +{ + bool bHISM = false; + HAPI_AttributeInfo AttriInfo; + FHoudiniApi::AttributeInfo_Init(&AttriInfo); + + TArray IntData; + IntData.Empty(); + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM, + AttriInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + return false; + } + + if (!AttriInfo.exists || IntData.Num() <= 0) + return false; + + return IntData[0] != 0; +} + +void +FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() +{ + NumInstancedTransformsPerObject.Empty(OriginalInstancedTransforms.Num()); + // We expect to have one or more entries per object + OriginalInstancedTransformsFlat.Empty(OriginalInstancedTransforms.Num()); + for (const TArray& Transforms : OriginalInstancedTransforms) + { + NumInstancedTransformsPerObject.Add(Transforms.Num()); + OriginalInstancedTransformsFlat.Append(Transforms); + } + + OriginalInstanceObjectPackagePaths.Empty(OriginalInstancedObjects.Num()); + for (const UObject* Obj : OriginalInstancedObjects) + { + if (IsValid(Obj)) + { + OriginalInstanceObjectPackagePaths.Add(Obj->GetPathName()); + } + else + { + OriginalInstanceObjectPackagePaths.Add(FString()); + } + } + + NumInstancedIndicesPerObject.Empty(OriginalInstancedIndices.Num()); + // We expect to have one or more entries per object + OriginalInstancedIndicesFlat.Empty(OriginalInstancedIndices.Num()); + for (const TArray& InstancedIndices : OriginalInstancedIndices) + { + NumInstancedIndicesPerObject.Add(InstancedIndices.Num()); + OriginalInstancedIndicesFlat.Append(InstancedIndices); + } + + NumPerInstanceCustomDataPerObject.Empty(PerInstanceCustomData.Num()); + // We expect to have one or more entries per object + PerInstanceCustomDataFlat.Empty(PerInstanceCustomData.Num()); + for (const TArray& PerInstanceCustomDataArray : PerInstanceCustomData) + { + NumPerInstanceCustomDataPerObject.Add(PerInstanceCustomDataArray.Num()); + PerInstanceCustomDataFlat.Append(PerInstanceCustomDataArray); + } +} + +void +FHoudiniInstancedOutputPartData::BuildOriginalInstancedTransformsAndObjectArrays() +{ + { + const int32 NumObjects = NumInstancedTransformsPerObject.Num(); + OriginalInstancedTransforms.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 ObjIndex = 0; ObjIndex < NumObjects; ++ObjIndex) + { + TArray& Transforms = OriginalInstancedTransforms[ObjIndex]; + const int32 NumInstances = NumInstancedTransformsPerObject[ObjIndex]; + Transforms.Reserve(NumInstances); + for (int32 Index = 0; Index < NumInstances; ++Index) + { + Transforms.Add(OriginalInstancedTransformsFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumInstances; + } + NumInstancedTransformsPerObject.Empty(); + OriginalInstancedTransformsFlat.Empty(); + } + + OriginalInstancedObjects.Empty(OriginalInstanceObjectPackagePaths.Num()); + for (const FString& PackageFullPath : OriginalInstanceObjectPackagePaths) + { + FString PackagePath; + FString PackageName; + const bool bDidSplit = PackageFullPath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bDidSplit) + PackagePath = PackageFullPath; + + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!IsValid(Package)) + { + // Editor might have picked up the package yet, try to load it + Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); + } + if (IsValid(Package)) + { + OriginalInstancedObjects.Add(FindObject(Package, *PackageName)); + } + else + { + OriginalInstancedObjects.Add(nullptr); + } + } + OriginalInstanceObjectPackagePaths.Empty(); + + { + const int32 NumObjects = NumInstancedIndicesPerObject.Num(); + OriginalInstancedIndices.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 EntryIndex = 0; EntryIndex < NumObjects; ++EntryIndex) + { + TArray& InstancedIndices = OriginalInstancedIndices[EntryIndex]; + const int32 NumInstancedIndices = NumInstancedIndicesPerObject[EntryIndex]; + InstancedIndices.Reserve(NumInstancedIndices); + for (int32 Index = 0; Index < NumInstancedIndices; ++Index) + { + InstancedIndices.Add(OriginalInstancedIndicesFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumInstancedIndices; + } + NumInstancedIndicesPerObject.Empty(); + OriginalInstancedIndicesFlat.Empty(); + } + + { + const int32 NumObjects = NumPerInstanceCustomDataPerObject.Num(); + PerInstanceCustomData.Init(TArray(), NumObjects); + int32 ObjectIndexOffset = 0; + for (int32 EntryIndex = 0; EntryIndex < NumObjects; ++EntryIndex) + { + TArray& PerInstanceCustomDataArray = PerInstanceCustomData[EntryIndex]; + const int32 NumPerInstanceCustomData = NumPerInstanceCustomDataPerObject[EntryIndex]; + PerInstanceCustomDataArray.Reserve(NumPerInstanceCustomData); + for (int32 Index = 0; Index < NumPerInstanceCustomData; ++Index) + { + PerInstanceCustomDataArray.Add(PerInstanceCustomDataFlat[ObjectIndexOffset + Index]); + } + ObjectIndexOffset += NumPerInstanceCustomData; + } + NumPerInstanceCustomDataPerObject.Empty(); + PerInstanceCustomDataFlat.Empty(); + } +} + +bool +FHoudiniInstanceTranslator::GetPerInstanceCustomData( + const int32& InGeoNodeId, + const int32& InPartId, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData) +{ + // Initialize sizes to zero + OutInstancedOutputPartData.PerInstanceCustomData.SetNum(0); + + // First look for the number of custom floats + // If we dont have the attribute, or it is set to zero, we dont have PerInstanceCustomData + // HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" + HAPI_AttributeInfo AttribInfoNumCustomFloats; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNumCustomFloats); + + TArray CustomFloatsArray; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + InGeoNodeId, InPartId, + HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS, + AttribInfoNumCustomFloats, + CustomFloatsArray)) + { + return false; + } + + if (CustomFloatsArray.Num() <= 0) + return false; + + int32 NumCustomFloats = 0; + for (int32 CustomFloatCount : CustomFloatsArray) + { + NumCustomFloats = FMath::Max(NumCustomFloats, CustomFloatCount); + } + + if (NumCustomFloats <= 0) + return false; + + // We do have custom float, now read the per instance custom data + // They are stored in attributes that uses the "unreal_per_instance_custom" prefix + // ie, unreal_per_instance_custom0, unreal_per_instance_custom1 etc... + // We do not supprot tuples/arrays attributes for now. + TArray> AllCustomDataAttributeValues; + AllCustomDataAttributeValues.SetNum(NumCustomFloats); + + // Read the custom data attributes + int32 NumInstance = 0; + for (int32 nIdx = 0; nIdx < NumCustomFloats; nIdx++) + { + // Build the custom data attribute + FString CurrentAttr = TEXT(HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX) + FString::FromInt(nIdx); + + // TODO? Tuple values Array attributes? + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + // Retrieve the custom data values + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + InGeoNodeId, InPartId, + TCHAR_TO_ANSI(*CurrentAttr), + AttribInfo, + AllCustomDataAttributeValues[nIdx], + 1)) + { + // Skip, we'll fill the values with zeros later on + continue; + } + + if (NumInstance < AllCustomDataAttributeValues[nIdx].Num()) + NumInstance = AllCustomDataAttributeValues[nIdx].Num(); + + if (NumInstance != AllCustomDataAttributeValues[nIdx].Num()) + { + HOUDINI_LOG_ERROR(TEXT("Instancer: Invalid number of Per-Instance Custom data attributes, ignoring...")); + return false; + } + } + + // Check sizes + if (AllCustomDataAttributeValues.Num() != NumCustomFloats) + { + HOUDINI_LOG_ERROR(TEXT("Instancer: Number of Per-Instance Custom data attributes don't match the number of custom floats, ignoring...")); + return false; + } + + OutInstancedOutputPartData.PerInstanceCustomData.SetNum(OutInstancedOutputPartData.OriginalInstancedObjects.Num()); + + for (int32 ObjIdx = 0; ObjIdx < OutInstancedOutputPartData.OriginalInstancedObjects.Num(); ++ObjIdx) + { + OutInstancedOutputPartData.PerInstanceCustomData[ObjIdx].Reset(); + } + + for(int32 ObjIdx = 0; ObjIdx < OutInstancedOutputPartData.OriginalInstancedObjects.Num(); ++ObjIdx) + { + const TArray& InstanceIndices = OutInstancedOutputPartData.OriginalInstancedIndices[ObjIdx]; + + if (InstanceIndices.Num() == 0) + { + continue; + } + + // Perform some validation + int32 NumCustomFloatsForInstance = CustomFloatsArray[InstanceIndices[0]]; + for (int32 InstIdx : InstanceIndices) + { + if (CustomFloatsArray[InstIdx] != NumCustomFloatsForInstance) + { + NumCustomFloatsForInstance = -1; + break; + } + } + + if (NumCustomFloatsForInstance == -1) + { + continue; + } + + // Now that we have read all the custom data values, we need to "interlace" them + // in the final per-instance custom data array, fill missing values with zeroes + TArray& PerInstanceCustomData = OutInstancedOutputPartData.PerInstanceCustomData[ObjIdx]; + PerInstanceCustomData.Reserve(InstanceIndices.Num() * NumCustomFloatsForInstance); + + if(NumCustomFloatsForInstance == 0) + { + continue; + } + + for (int32 InstIdx : InstanceIndices) + { + for (int32 nCustomIdx = 0; nCustomIdx < NumCustomFloatsForInstance; ++nCustomIdx) + { + float CustomData = (InstIdx < AllCustomDataAttributeValues[nCustomIdx].Num() ? AllCustomDataAttributeValues[nCustomIdx][InstIdx] : 0.0f); + PerInstanceCustomData.Add(CustomData); + } + } + } + + return true; +} + + +bool +FHoudiniInstanceTranslator::UpdateChangedPerInstanceCustomData( + const TArray& InPerInstanceCustomData, + USceneComponent* InComponentToUpdate) +{ + // Checks + UInstancedStaticMeshComponent* ISMC = Cast(InComponentToUpdate); + if (!IsValid(ISMC)) + return false; + + // No Custom data to add/remove + if (ISMC->NumCustomDataFloats == 0 && InPerInstanceCustomData.Num() == 0) + return false; + + // We can copy the per instance custom data if we have any + // TODO: Properly extract only needed values! + int32 InstanceCount = ISMC->GetInstanceCount(); + int32 NumCustomFloats = InPerInstanceCustomData.Num() / InstanceCount; + + if (NumCustomFloats * InstanceCount != InPerInstanceCustomData.Num()) + { + ISMC->NumCustomDataFloats = 0; + ISMC->PerInstanceSMCustomData.Reset(); + return false; + } + + ISMC->NumCustomDataFloats = NumCustomFloats; + + // Clear out and reinit to 0 the PerInstanceCustomData array + ISMC->PerInstanceSMCustomData.SetNumZeroed(InstanceCount * NumCustomFloats); + + // Behaviour copied From UInstancedStaticMeshComponent::SetCustomData() + // except we modify all the instance/custom values at once + ISMC->Modify(); + + // MemCopy + const int32 NumToCopy = FMath::Min(ISMC->PerInstanceSMCustomData.Num(), InPerInstanceCustomData.Num()); + if (NumToCopy > 0) + { + FMemory::Memcpy(&ISMC->PerInstanceSMCustomData[0], InPerInstanceCustomData.GetData(), NumToCopy * InPerInstanceCustomData.GetTypeSize()); + } + + // Force recreation of the render data when proxy is created + //NewISMC->InstanceUpdateCmdBuffer.Edit(); + // Cant call the edit function above because the function is defined in a different cpp file than the .h it is declared in... + ISMC->InstanceUpdateCmdBuffer.NumEdits++; + + ISMC->MarkRenderStateDirty(); + + return true; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h index 50a9a7013..82e6b29c6 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h @@ -1,439 +1,454 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniOutput.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniGenericAttribute.h" - -#include "HoudiniInstanceTranslator.generated.h" - -class UStaticMesh; -class UFoliageType; -class UHoudiniStaticMesh; -class UHoudiniInstancedActorComponent; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes -{ -public: - - GENERATED_BODY() - - // level path attribute value - UPROPERTY() - FString LevelPath; - - // Bake actor name attribute value - UPROPERTY() - FString BakeActorName; - - // bake outliner folder attribute value - UPROPERTY() - FString BakeOutlinerFolder; - - // unreal_bake_folder attribute value - UPROPERTY() - FString BakeFolder; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData -{ -public: - - GENERATED_BODY() - - UPROPERTY() - bool bForceHISM = false; - - UPROPERTY() - TArray OriginalInstancedObjects; - - // Object paths of OriginalInstancedObjects. Used by message passing system - // when sending messages from the async importer to the PDG manager. UObject*/references - // are not directly supported by the messaging system. See BuildFlatInstancedTransformsAndObjectPaths(). - UPROPERTY() - TArray OriginalInstanceObjectPackagePaths; - - TArray> OriginalInstancedTransforms; - - TArray> OriginalInstancedIndices; - - // Number of entries in OriginalInstancedTransforms. Populated when building - // OriginalInstancedTransformsFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding - // OriginalInstancedTransforms from OriginalInstancedTransformsFlat in BuildOriginalInstancedTransformsAndObjectArrays(). - UPROPERTY() - TArray NumInstancedTransformsPerObject; - - // Flattened version of OriginalInstancedTransforms. Used by message passing system - // when sending messages from the async importer to the PDG manager. Nested arrays - // are not supported by UPROPERTIES and thus not by the messaging system. - // See BuildFlatInstancedTransformsAndObjectPaths(). - UPROPERTY() - TArray OriginalInstancedTransformsFlat; - - // Number of entries in OriginalInstancedIndices. Populated when building - // OriginalInstancedIndicesFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding - // OriginalInstancedIndices from OriginalInstancedIndicesFlat in BuildOriginalInstancedTransformsAndObjectArrays(). - UPROPERTY() - TArray NumInstancedIndicesPerObject; - - // Flattened version of OriginalInstancedIndices. Used by message passing system - // when sending messages from the async importer to the PDG manager. Nested arrays - // are not supported by UPROPERTIES and thus not by the messaging system. See - // BuildFlatInstancedTransformsAndObjectPaths(). - UPROPERTY() - TArray OriginalInstancedIndicesFlat; - - UPROPERTY() - FString SplitAttributeName; - - UPROPERTY() - TArray SplitAttributeValues; - - UPROPERTY() - bool bSplitMeshInstancer = false; - - UPROPERTY() - bool bIsFoliageInstancer = false; - - UPROPERTY() - TArray AllPropertyAttributes; - - // All level path attributes from the first attribute owner we could find - UPROPERTY() - TArray AllLevelPaths; - - // All bake actor name attributes from the first attribute owner we could find - UPROPERTY() - TArray AllBakeActorNames; - - // All unreal_bake_folder attributes (prim attr is checked first then detail) - UPROPERTY() - TArray AllBakeFolders; - - // All bake outliner folder attributes from the first attribute owner we could find - UPROPERTY() - TArray AllBakeOutlinerFolders; - - // A map of split value to attribute values that are valid per split (unreal_bake_actor, unreal_level_path, - // unreal_bake_outliner_folder) - UPROPERTY() - TMap PerSplitAttributes; - - UPROPERTY() - TArray OutputNames; - - UPROPERTY() - TArray TileValues; - - UPROPERTY() - TArray MaterialAttributes; - - // Custom float array per original instanced object - // Size is NumCustomFloat * NumberOfInstances - TArray> PerInstanceCustomData; - - // Number of entries in PerInstanceCustomData. Populated when building - // PerInstanceCustomDataFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding - // PerInstanceCustomData from PerInstanceCustomDataFlat in BuildOriginalInstancedTransformsAndObjectArrays(). - UPROPERTY() - TArray NumPerInstanceCustomDataPerObject; - - // Flattened version of OriginalInstancedTransforms. Used by message passing system - // when sending messages from the async importer to the PDG manager. Nested arrays - // are not supported by UPROPERTIES and thus not by the messaging system. - // See BuildFlatInstancedTransformsAndObjectPaths(). - UPROPERTY() - TArray PerInstanceCustomDataFlat; - - void BuildFlatInstancedTransformsAndObjectPaths(); - - void BuildOriginalInstancedTransformsAndObjectArrays(); -}; - -struct HOUDINIENGINE_API FHoudiniInstanceTranslator -{ - public: - - static bool PopulateInstancedOutputPartData( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); - - static bool CreateAllInstancersFromHoudiniOutput( - UHoudiniOutput* InOutput, - const TArray& InAllOutputs, - UObject* InOuterComponent, - const TMap* InPreBuiltInstancedOutputPartData = nullptr); - - static bool GetInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - TArray>& OutInstancedIndices, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValues, - TMap& OutPerSplitAttributes); - - static bool GetPackedPrimitiveInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - TArray>& OutInstancedIndices, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes); - - static bool GetAttributeInstancerObjectsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancedObjects, - TArray>& OutInstancedTransforms, - TArray>& OutInstancedIndices, - FString& OutSplitAttributeName, - TArray& OutSplitAttributeValue, - TMap& OutPerSplitAttributes); - - static bool GetOldSchoolAttributeInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - TArray>& OutInstancedIndices); - - static bool GetObjectInstancerHGPOsAndTransforms( - const FHoudiniGeoPartObject& InHGPO, - const TArray& InAllOutputs, - TArray& OutInstancedHGPO, - TArray>& OutInstancedTransforms, - TArray>& OutInstancedIndices); - - // Updates the variations array using the instanced outputs - static void UpdateInstanceVariationObjects( - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TArray& InOriginalObjects, - const TArray>& InOriginalTransforms, - const TArray>& OriginalInstancedIndices, - TMap& InstancedOutputs, - TArray>& OutVariationsInstancedObjects, - TArray>& OutVariationsInstancedTransforms, - TArray& OutVariationOriginalObjectIdx, - TArray& OutVariationIndices); - - // Recreates the components after an instanced outputs has been changed - static bool UpdateChangedInstancedOutput( - FHoudiniInstancedOutput& InInstancedOutput, - const FHoudiniOutputObjectIdentifier& OutputIdentifier, - UHoudiniOutput* InParentOutput, - USceneComponent* InParentComponent); - - // Recomputes the variation assignements for a given instanced output - static void UpdateVariationAssignements( - FHoudiniInstancedOutput& InstancedOutput); - - // Extracts the final transforms (with the transform offset applied) for a given variation - static void ProcessInstanceTransforms( - FHoudiniInstancedOutput& InstancedOutput, - const int32& VariationIdx, - TArray& OutProcessedTransforms); - - // Creates a new component or updates the previous one if possible - static bool CreateOrUpdateInstanceComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent* OldComponent, - USceneComponent*& NewComponent, - const bool& InIsSplitMeshInstancer, - const bool& InIsFoliageInstancer, - const TArray& InstancerMaterials, - const TArray& OriginalInstancerObjectIndices, - const int32& InstancerObjectIdx = 0, - const bool& bForceHISM = false); - - // Create or update an ISMC / HISMC - static bool CreateOrUpdateInstancedStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr, - const bool& bForceHISM = false, - const int32& InstancerObjectIdx = 0); - - // Create or update an IAC - static bool CreateOrUpdateInstancedActorComponent( - UObject* InstancedObject, - const TArray& InstancedObjectTransforms, - const TArray& OriginalInstancerObjectIndices, - const TArray& AllPropertyAttributes, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent); - - // Create or update a MeshSplitInstancer - static bool CreateOrUpdateMeshSplitInstancerComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - const TArray& InstancerMaterials); - - // Create or update a StaticMeshComponent (when we have only one instance) - static bool CreateOrUpdateStaticMeshComponent( - UStaticMesh* InstancedStaticMesh, - const TArray& InstancedObjectTransforms, - const int32& InOriginalIndex, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr); - - // Create or update a HoudiniStaticMeshComponent (when we have only one instance) - static bool CreateOrUpdateHoudiniStaticMeshComponent( - UHoudiniStaticMesh* InstancedProxyStaticMesh, - const TArray& InstancedObjectTransforms, - const int32& InOriginalIndex, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& CreatedInstancedComponent, - UMaterialInterface * InstancerMaterial = nullptr); - - // Create or update a Foliage instances - static bool CreateOrUpdateFoliageInstances( - UStaticMesh* InstancedStaticMesh, - UFoliageType* InFoliageType, - const TArray& InstancedObjectTransforms, - const int32& FirstOriginalIndex, - const TArray& AllPropertyAttributes, - const FHoudiniGeoPartObject& InstancerGeoPartObject, - USceneComponent* ParentComponent, - USceneComponent*& NewInstancedComponent, - UMaterialInterface * InstancerMaterial /*=nullptr*/); - - // Helper fumction to properly remove/destroy a component - static bool RemoveAndDestroyComponent( - UObject* InComponent, - UObject* InFoliageObject); - - // Utility function - // Fetches instance transforms and convert them to ue4 coordinates - static bool HapiGetInstanceTransforms( - const FHoudiniGeoPartObject& InHGPO, - TArray& OutInstancerUnrealTransforms); - - // Helper function used to spawn a new Actor for UHoudiniInstancedActorComponent - // Relies on editor-only functionalities, so this function is not on the IAC itself - static AActor* SpawnInstanceActor( - const FTransform& InTransform, - ULevel* InSpawnLevel, - UHoudiniInstancedActorComponent* InIAC); - - // Helper functions for generic property attributes - static bool GetGenericPropertiesAttributes( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, - const TArray& InAllPropertyAttributes, - const int32& AtIndex); - - static bool GetMaterialOverridesFromAttributes( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutMaterialAttributes); - - static bool GetInstancerMaterials( - const TArray& MaterialAttributes, - TArray& OutInstancerMaterials); - - static bool GetInstancerMaterials( - const int32& InGeoNodeId, - const int32& InPartId, - TArray& OutInstancerMaterials); - - static bool GetVariationMaterials( - FHoudiniInstancedOutput* InInstancedOutput, - const int32& InVariationIndex, - const TArray& InOriginalIndices, - const TArray& InInstancerMaterials, - TArray& OutVariationMaterials); - - static bool IsSplitInstancer( - const int32& InGeoId, - const int32& InPartId); - - static bool IsFoliageInstancer( - const int32& InGeoId, - const int32& InPartId); - - static void CleanupFoliageInstances( - UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, - UObject* InInstancedObject, - USceneComponent* InParentComponent); - - static FString GetInstancerTypeFromComponent( - UObject* InComponent); - - // Returns the name and values of the attribute that has been specified to split the instances - // returns false if the attribute is invalid or hasn't been specified - static bool GetInstancerSplitAttributesAndValues( - const int32& InGeoId, - const int32& InPartId, - const HAPI_AttributeOwner& InSplitAttributeOwner, - FString& OutSplitAttributeName, - TArray& OutAllSplitAttributeValues); - - // Get if force using HISM from attribute - static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); - - // Checks for PerInstanceCustomData on the instancer part - static bool GetPerInstanceCustomData( - const int32& InGeoNodeId, - const int32& InPartId, - FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); - - // Update PerInstanceCustom data on the given component if possible - static bool UpdateChangedPerInstanceCustomData( - const TArray& InPerInstanceCustomData, - USceneComponent* InComponentToUpdate); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniOutput.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniGenericAttribute.h" + +#include "HoudiniInstanceTranslator.generated.h" + +class UStaticMesh; +class UFoliageType; +class UHoudiniStaticMesh; +class UHoudiniInstancedActorComponent; +struct FHoudiniPackageParams; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniInstancedOutputPerSplitAttributes +{ +public: + + GENERATED_BODY() + + // level path attribute value + UPROPERTY() + FString LevelPath; + + // Bake actor name attribute value + UPROPERTY() + FString BakeActorName; + + // bake outliner folder attribute value + UPROPERTY() + FString BakeOutlinerFolder; + + // unreal_bake_folder attribute value + UPROPERTY() + FString BakeFolder; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData +{ +public: + + GENERATED_BODY() + + UPROPERTY() + bool bForceHISM = false; + + UPROPERTY() + TArray OriginalInstancedObjects; + + // Object paths of OriginalInstancedObjects. Used by message passing system + // when sending messages from the async importer to the PDG manager. UObject*/references + // are not directly supported by the messaging system. See BuildFlatInstancedTransformsAndObjectPaths(). + UPROPERTY() + TArray OriginalInstanceObjectPackagePaths; + + TArray> OriginalInstancedTransforms; + + TArray> OriginalInstancedIndices; + + // Number of entries in OriginalInstancedTransforms. Populated when building + // OriginalInstancedTransformsFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding + // OriginalInstancedTransforms from OriginalInstancedTransformsFlat in BuildOriginalInstancedTransformsAndObjectArrays(). + UPROPERTY() + TArray NumInstancedTransformsPerObject; + + // Flattened version of OriginalInstancedTransforms. Used by message passing system + // when sending messages from the async importer to the PDG manager. Nested arrays + // are not supported by UPROPERTIES and thus not by the messaging system. + // See BuildFlatInstancedTransformsAndObjectPaths(). + UPROPERTY() + TArray OriginalInstancedTransformsFlat; + + // Number of entries in OriginalInstancedIndices. Populated when building + // OriginalInstancedIndicesFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding + // OriginalInstancedIndices from OriginalInstancedIndicesFlat in BuildOriginalInstancedTransformsAndObjectArrays(). + UPROPERTY() + TArray NumInstancedIndicesPerObject; + + // Flattened version of OriginalInstancedIndices. Used by message passing system + // when sending messages from the async importer to the PDG manager. Nested arrays + // are not supported by UPROPERTIES and thus not by the messaging system. See + // BuildFlatInstancedTransformsAndObjectPaths(). + UPROPERTY() + TArray OriginalInstancedIndicesFlat; + + UPROPERTY() + FString SplitAttributeName; + + UPROPERTY() + TArray SplitAttributeValues; + + UPROPERTY() + bool bSplitMeshInstancer = false; + + UPROPERTY() + bool bIsFoliageInstancer = false; + + UPROPERTY() + TArray AllPropertyAttributes; + + // All level path attributes from the first attribute owner we could find + UPROPERTY() + TArray AllLevelPaths; + + // All bake actor name attributes from the first attribute owner we could find + UPROPERTY() + TArray AllBakeActorNames; + + // All unreal_bake_folder attributes (prim attr is checked first then detail) + UPROPERTY() + TArray AllBakeFolders; + + // All bake outliner folder attributes from the first attribute owner we could find + UPROPERTY() + TArray AllBakeOutlinerFolders; + + // A map of split value to attribute values that are valid per split (unreal_bake_actor, unreal_level_path, + // unreal_bake_outliner_folder) + UPROPERTY() + TMap PerSplitAttributes; + + UPROPERTY() + TArray OutputNames; + + UPROPERTY() + TArray TileValues; + + UPROPERTY() + TArray MaterialAttributes; + + // Specifies that the materials in MaterialAttributes are to be created as an instance + UPROPERTY() + bool bMaterialOverrideNeedsCreateInstance; + + // Custom float array per original instanced object + // Size is NumCustomFloat * NumberOfInstances + TArray> PerInstanceCustomData; + + // Number of entries in PerInstanceCustomData. Populated when building + // PerInstanceCustomDataFlat in BuildFlatInstancedTransformsAndObjectPaths() and used when rebuilding + // PerInstanceCustomData from PerInstanceCustomDataFlat in BuildOriginalInstancedTransformsAndObjectArrays(). + UPROPERTY() + TArray NumPerInstanceCustomDataPerObject; + + // Flattened version of OriginalInstancedTransforms. Used by message passing system + // when sending messages from the async importer to the PDG manager. Nested arrays + // are not supported by UPROPERTIES and thus not by the messaging system. + // See BuildFlatInstancedTransformsAndObjectPaths(). + UPROPERTY() + TArray PerInstanceCustomDataFlat; + + void BuildFlatInstancedTransformsAndObjectPaths(); + + void BuildOriginalInstancedTransformsAndObjectArrays(); +}; + +struct HOUDINIENGINE_API FHoudiniInstanceTranslator +{ + public: + + static bool PopulateInstancedOutputPartData( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); + + static bool CreateAllInstancersFromHoudiniOutput( + UHoudiniOutput* InOutput, + const TArray& InAllOutputs, + UObject* InOuterComponent, + const FHoudiniPackageParams& InPackageParms, + const TMap* InPreBuiltInstancedOutputPartData = nullptr); + + static bool GetInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValues, + TMap& OutPerSplitAttributes); + + static bool GetPackedPrimitiveInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes); + + static bool GetAttributeInstancerObjectsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancedObjects, + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices, + FString& OutSplitAttributeName, + TArray& OutSplitAttributeValue, + TMap& OutPerSplitAttributes); + + static bool GetOldSchoolAttributeInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices); + + static bool GetObjectInstancerHGPOsAndTransforms( + const FHoudiniGeoPartObject& InHGPO, + const TArray& InAllOutputs, + TArray& OutInstancedHGPO, + TArray>& OutInstancedTransforms, + TArray>& OutInstancedIndices); + + // Updates the variations array using the instanced outputs + static void UpdateInstanceVariationObjects( + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TArray& InOriginalObjects, + const TArray>& InOriginalTransforms, + const TArray>& OriginalInstancedIndices, + TMap& InstancedOutputs, + TArray>& OutVariationsInstancedObjects, + TArray>& OutVariationsInstancedTransforms, + TArray& OutVariationOriginalObjectIdx, + TArray& OutVariationIndices); + + // Recreates the components after an instanced outputs has been changed + static bool UpdateChangedInstancedOutput( + FHoudiniInstancedOutput& InInstancedOutput, + const FHoudiniOutputObjectIdentifier& OutputIdentifier, + UHoudiniOutput* InParentOutput, + USceneComponent* InParentComponent, + const FHoudiniPackageParams& InPackageParams); + + // Recomputes the variation assignements for a given instanced output + static void UpdateVariationAssignements( + FHoudiniInstancedOutput& InstancedOutput); + + // Extracts the final transforms (with the transform offset applied) for a given variation + static void ProcessInstanceTransforms( + FHoudiniInstancedOutput& InstancedOutput, + const int32& VariationIdx, + TArray& OutProcessedTransforms); + + // Creates a new component or updates the previous one if possible + static bool CreateOrUpdateInstanceComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent* OldComponent, + USceneComponent*& NewComponent, + const bool& InIsSplitMeshInstancer, + const bool& InIsFoliageInstancer, + const TArray& InstancerMaterials, + const TArray& OriginalInstancerObjectIndices, + const int32& InstancerObjectIdx = 0, + const bool& bForceHISM = false); + + // Create or update an ISMC / HISMC + static bool CreateOrUpdateInstancedStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr, + const bool& bForceHISM = false, + const int32& InstancerObjectIdx = 0); + + // Create or update an IAC + static bool CreateOrUpdateInstancedActorComponent( + UObject* InstancedObject, + const TArray& InstancedObjectTransforms, + const TArray& OriginalInstancerObjectIndices, + const TArray& AllPropertyAttributes, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent); + + // Create or update a MeshSplitInstancer + static bool CreateOrUpdateMeshSplitInstancerComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + const TArray& InstancerMaterials); + + // Create or update a StaticMeshComponent (when we have only one instance) + static bool CreateOrUpdateStaticMeshComponent( + UStaticMesh* InstancedStaticMesh, + const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr); + + // Create or update a HoudiniStaticMeshComponent (when we have only one instance) + static bool CreateOrUpdateHoudiniStaticMeshComponent( + UHoudiniStaticMesh* InstancedProxyStaticMesh, + const TArray& InstancedObjectTransforms, + const int32& InOriginalIndex, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& CreatedInstancedComponent, + UMaterialInterface * InstancerMaterial = nullptr); + + // Create or update a Foliage instances + static bool CreateOrUpdateFoliageInstances( + UStaticMesh* InstancedStaticMesh, + UFoliageType* InFoliageType, + const TArray& InstancedObjectTransforms, + const int32& FirstOriginalIndex, + const TArray& AllPropertyAttributes, + const FHoudiniGeoPartObject& InstancerGeoPartObject, + USceneComponent* ParentComponent, + USceneComponent*& NewInstancedComponent, + UMaterialInterface * InstancerMaterial /*=nullptr*/); + + // Helper fumction to properly remove/destroy a component + static bool RemoveAndDestroyComponent( + UObject* InComponent, + UObject* InFoliageObject); + + // Utility function + // Fetches instance transforms and convert them to ue4 coordinates + static bool HapiGetInstanceTransforms( + const FHoudiniGeoPartObject& InHGPO, + TArray& OutInstancerUnrealTransforms); + + // Helper function used to spawn a new Actor for UHoudiniInstancedActorComponent + // Relies on editor-only functionalities, so this function is not on the IAC itself + static AActor* SpawnInstanceActor( + const FTransform& InTransform, + ULevel* InSpawnLevel, + UHoudiniInstancedActorComponent* InIAC); + + // Helper functions for generic property attributes + static bool GetGenericPropertiesAttributes( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, + const TArray& InAllPropertyAttributes, + const int32& AtIndex); + + static bool GetMaterialOverridesFromAttributes( + const int32& InGeoNodeId, + const int32& InPartId, + TArray& OutMaterialAttributes, + bool& OutMaterialOverrideNeedsCreateInstance); + + static bool GetInstancerMaterials( + const TArray& MaterialAttribute, + TArray& OutInstancerMaterials); + + static bool GetInstancerMaterialInstances( + const TArray& MaterialAttribute, + const FHoudiniGeoPartObject& InHGPO, const FHoudiniPackageParams& InPackageParams, + TArray& OutInstancerMaterials); + + static bool GetInstancerMaterials( + const int32& InGeoNodeId, + const int32& InPartId, + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + TArray& OutInstancerMaterials); + + static bool GetVariationMaterials( + FHoudiniInstancedOutput* InInstancedOutput, + const int32& InVariationIndex, + const TArray& InOriginalIndices, + const TArray& InInstancerMaterials, + TArray& OutVariationMaterials); + + static bool IsSplitInstancer( + const int32& InGeoId, + const int32& InPartId); + + static bool IsFoliageInstancer( + const int32& InGeoId, + const int32& InPartId); + + static void CleanupFoliageInstances( + UHierarchicalInstancedStaticMeshComponent* InFoliageHISMC, + UObject* InInstancedObject, + USceneComponent* InParentComponent); + + static FString GetInstancerTypeFromComponent( + UObject* InComponent); + + // Returns the name and values of the attribute that has been specified to split the instances + // returns false if the attribute is invalid or hasn't been specified + static bool GetInstancerSplitAttributesAndValues( + const int32& InGeoId, + const int32& InPartId, + const HAPI_AttributeOwner& InSplitAttributeOwner, + FString& OutSplitAttributeName, + TArray& OutAllSplitAttributeValues); + + // Get if force using HISM from attribute + static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); + + // Checks for PerInstanceCustomData on the instancer part + static bool GetPerInstanceCustomData( + const int32& InGeoNodeId, + const int32& InPartId, + FHoudiniInstancedOutputPartData& OutInstancedOutputPartData); + + // Update PerInstanceCustom data on the given component if possible + static bool UpdateChangedPerInstanceCustomData( + const TArray& InPerInstanceCustomData, + USceneComponent* InComponentToUpdate); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp index 1dfa7a380..7c3a2776d 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp @@ -1,4476 +1,4524 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniLandscapeTranslator.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniEngineString.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPackageParams.h" -#include "HoudiniStringResolver.h" -#include "HoudiniInput.h" - -#include "ObjectTools.h" -#include "FileHelpers.h" -#include "Editor.h" -#include "LandscapeLayerInfoObject.h" -#include "LandscapeStreamingProxy.h" -#include "LandscapeInfo.h" -#include "LandscapeEdit.h" -#include "AssetRegistryModule.h" -#include "PackageTools.h" -#include "PhysicalMaterials/PhysicalMaterial.h" -#include "UObject/UnrealType.h" - -#include "GameFramework/WorldSettings.h" -#include "Misc/Paths.h" -#include "Modules/ModuleManager.h" -#include "AssetToolsModule.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Factories/WorldFactory.h" -#include "Misc/Guid.h" -#include "Engine/LevelBounds.h" - -#include "HAL/IConsoleManager.h" -#include "Engine/AssetManager.h" -#include "Misc/ScopedSlowTask.h" - -#if WITH_EDITOR - #include "EditorLevelUtils.h" - #include "LandscapeEditorModule.h" - #include "LandscapeFileFormatInterface.h" -#endif - -static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( - TEXT("HoudiniEngine.ExportLandscapeTextures"), - 0, - TEXT("If enabled, landscape layers and heightmap will be exported as textures in the temp directory when converting a Heightfield to a Landscape.\n") - TEXT("0: Disabled\n") - TEXT("1: Enabled\n") -); - -typedef FHoudiniEngineUtils FHUtils; - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY(); - -bool -FHoudiniLandscapeTranslator::CreateLandscape( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedOutputs, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* InWorld, // Persistent / root world for the landscape - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniLandscapeExtent& LandscapeExtent, - FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, - FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, - FHoudiniPackageParams InPackageParams, - TSet& ClearedLayers, - TArray& OutCreatedPackages -) -{ - // Do the absolute minimum in order to determine which output mode we're dealing with (Temp or Editable Layers). - - if (!InOutput || InOutput->IsPendingKill()) - return false; - - // Get the height map. - const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); - if (!Heightfield) - return false; - - if (Heightfield->Type != EHoudiniPartType::Volume) - return false; - - const HAPI_NodeId GeoId = Heightfield->GeoId; - const HAPI_PartId PartId = Heightfield->PartId; - - // Check whether we're running in edit layer mode, or the usual temp mode - - TArray IntData; - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - // --------------------------------------------- - // Attribute: unreal_landscape_output_mode - // --------------------------------------------- - IntData.Empty(); - int32 LandscapeOutputMode = 0; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_OUTPUT_MODE, AttributeInfo, IntData, 1)) - { - if (IntData.Num() > 0) - { - LandscapeOutputMode = IntData[0]; - } - } - - switch (LandscapeOutputMode) - { - case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_EDITABLE_LAYER: - { - return OutputLandscape_EditableLayer(InOutput, - CreatedUntrackedOutputs, - InputLandscapesToUpdate, - InAllInputLandscapes, - SharedLandscapeActorParent, - DefaultLandscapeActorPrefix, - InWorld, - LayerMinimums, - LayerMaximums, - LandscapeExtent, - LandscapeTileSizeInfo, - LandscapeReferenceLocation, - InPackageParams, - ClearedLayers, - OutCreatedPackages); - } - break; - case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_DEFAULT: - default: - { - return OutputLandscape_Temp(InOutput, - CreatedUntrackedOutputs, - InputLandscapesToUpdate, - InAllInputLandscapes, - SharedLandscapeActorParent, - DefaultLandscapeActorPrefix, - InWorld, - LayerMinimums, - LayerMaximums, - LandscapeExtent, - LandscapeTileSizeInfo, - LandscapeReferenceLocation, - InPackageParams, - OutCreatedPackages - ); - } - break; - } -} - -bool -FHoudiniLandscapeTranslator::OutputLandscape_Temp( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedOutputs, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* InWorld, // Persistent / root world for the landscape - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniLandscapeExtent& LandscapeExtent, - FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, - FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, - FHoudiniPackageParams InPackageParams, - TArray& OutCreatedPackages -) -{ - check(LayerMinimums.Contains(TEXT("height"))); - check(LayerMaximums.Contains(TEXT("height"))); - - float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); - float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); - - if (!InOutput || InOutput->IsPendingKill()) - return false; - - // Get the height map. - const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); - if (!Heightfield) - return false; - - if (Heightfield->Type != EHoudiniPartType::Volume) - return false; - - const HAPI_NodeId GeoId = Heightfield->GeoId; - const HAPI_PartId PartId = Heightfield->PartId; - - // Construct the identifier of the Heightfield geo part. - FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); - HeightfieldIdentifier.PartName = Heightfield->PartName; - - FString NodeNameSuffix = GetActorNameSuffix(InPackageParams.PackageMode); - bool bAddLandscapeNameSuffix = true; - bool bAddLandscapeTileNameSuffix = true; - - const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - - TArray IntData; - TArray StrData; - // Output attributes will be stored on the Output object and will be used again during baking to determine - // where content should be baked to and what they should be named, etc. - // At the end of this function, the output attributes and tokens will be copied to the output object. - TMap OutputAttributes; - TMap OutputTokens; - FHoudiniAttributeResolver Resolver; - InPackageParams.UpdateTokensFromParams(InWorld, HoudiniAssetComponent, OutputTokens); - - bool bHasTile = Heightfield->VolumeTileIndex >= 0; - - // --------------------------------------------- - // Attribute: unreal_landscape_tile_actor_type, unreal_landscape_streaming_proxy (v1) - // --------------------------------------------- - // Determine the actor type for the tile - bool bCreateLandscapeStreamingProxy = false; - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - LandscapeActorType TileActorType = LandscapeActorType::LandscapeActor; - IntData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, AttributeInfo, IntData, 1)) - { - if (IntData.Num() > 0) - { - TileActorType = static_cast(IntData[0]); - } - } - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY, AttributeInfo, IntData, 1)) - { - if (IntData.Num() > 0 && IntData[0] != 0) - TileActorType = LandscapeActorType::LandscapeStreamingProxy; - } - - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, FString::FromInt(static_cast(TileActorType))); - - // --------------------------------------------- - // Attribute: unreal_landscape_actor_name - // --------------------------------------------- - // Retrieve the name of the main Landscape actor to look for - FString SharedLandscapeActorName = DefaultLandscapeActorPrefix + "SharedLandscape"; // If this is an empty string, don't affirm a root landscape actor? - StrData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, AttributeInfo, StrData, 1)) - { - if (StrData.Num() > 0 && !StrData[0].IsEmpty()) - SharedLandscapeActorName = StrData[0]; - } - - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); - - // --------------------------------------------- - // Attribute: unreal_level_path - // --------------------------------------------- - // FString LevelPath = bHasTile ? "{world}/Landscape/Tile{tile}" : "{world}/Landscape"; - FString LevelPath; - TArray LevelPaths; - if (FHoudiniEngineUtils::GetLevelPathAttribute(GeoId, PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - LevelPath = LevelPaths[0]; - } - if (!LevelPath.IsEmpty()) - OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); - - // --------------------------------------------- - // Attribute: unreal_output_name - // --------------------------------------------- - FString LandscapeTileActorName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; - TArray AllOutputNames; - if (FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames)) - { - if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) - LandscapeTileActorName = AllOutputNames[0]; - } - OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); - - // --------------------------------------------- - // Attribute: unreal_bake_folder - // --------------------------------------------- - TArray AllBakeFolders; - if (FHoudiniEngineUtils::GetBakeFolderAttribute(GeoId, AllBakeFolders, PartId)) - { - FString BakeFolder; - if (AllBakeFolders.Num() > 0 && !AllBakeFolders[0].IsEmpty()) - BakeFolder = AllBakeFolders[0]; - OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_BAKE_FOLDER), BakeFolder); - } - - // Streaming proxy actors/tiles requires a "main" landscape actor - // that contains the shared landscape state. - bool bRequiresSharedLandscape = false; - if (TileActorType == LandscapeActorType::LandscapeStreamingProxy) - bRequiresSharedLandscape = true; - - // ---------------------------------- - // Inject landscape specific tokens - // ---------------------------------- - if (bHasTile) - { - const FString TileValue = FString::FromInt(Heightfield->VolumeTileIndex); - // Tile value needs to go into Output arguments to be available during the bake. - OutputTokens.Add(TEXT("tile"), TileValue); - } - - // ---------------------------------- - // Expand string arguments for various landscape naming aspects. - // ---------------------------------- - - // Update resolver attributes and tokens before we start resolving attributes. - Resolver.SetCachedAttributes(OutputAttributes); - Resolver.SetTokensFromStringMap(OutputTokens); - - SharedLandscapeActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); - SharedLandscapeActorName += NodeNameSuffix; - - LandscapeTileActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, LandscapeTileActorName); - LandscapeTileActorName += NodeNameSuffix; - - LevelPath = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); - - FString TileName = LandscapeTileActorName; - - // Note that relative level paths are always interpreted as relative to the default output directory (temp / bake). - // FString TilePackagePath = FPaths::Combine(DefaultOutputPath, LevelPath); - FString TilePackagePath = Resolver.ResolveFullLevelPath(); - - // This crashes UE if the package name does not resolve - //FString TileMapFileName = FPackageName::LongPackageNameToFilename(TilePackagePath, FPackageName::GetMapPackageExtension()); - - FText NotValidReason; - bool bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); - if (!bIsValidLongName) - { - // Try a more naive approach - TilePackagePath = FPaths::Combine(InPackageParams.BakeFolder, LevelPath); - bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); - } - - if (!bIsValidLongName) - { - HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] TilePackagePath is not a valid long name. Reason: %s"), *(NotValidReason.ToString())); - return false; - } - - FString TileMapFileName; - if (!FPackageName::TryConvertLongPackageNameToFilename(TilePackagePath, TileMapFileName, FPackageName::GetMapPackageExtension())) - { - // Rather stop here than crash! - HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] Failed to resolve the TilePackagePath: %s"), *(TilePackagePath)); - return false; - } - - // Find the package for both the world and the tile. - // The world should contain the main landscape actor while - // the tile will contain a Landscape, LandscapeProxy or LandscapeStreamingProxy depending on user settings. - - bool bTileisStreamingProxy = (TileActorType == LandscapeActorType::LandscapeStreamingProxy); - UWorld* TileWorld = nullptr; // World from which to spawn tile actor - ULevel* TileLevel = nullptr; // Level in which to spawn tile actor - ALandscapeProxy* TileActor = nullptr; // Spawned tile actor. - - // ---------------------------------- - // Update package parameters for this tile - // ---------------------------------- - - // NOTE: we don't manually inject a tile number in the object name. This should - // already be encoded in the TileName string. - FHoudiniPackageParams TilePackageParams = InPackageParams; - TilePackageParams.ObjectName = TileName; - - FHoudiniPackageParams LayerPackageParams = InPackageParams; - if (bRequiresSharedLandscape) - { - // Note that layers are shared amongst all the tiles for a given landscape. - LayerPackageParams.ObjectName = SharedLandscapeActorName; - } - else - { - // This landscape tile is a standalone landscape and should have its own material layers. - LayerPackageParams.ObjectName = TileName; - } - - // See if the current heightfield has an unreal_material or unreal_hole_material assigned to it - UMaterialInterface* LandscapeMaterial = nullptr; - UMaterialInterface* LandscapeHoleMaterial = nullptr; - UPhysicalMaterial* LandscapePhysicalMaterial = nullptr; - FHoudiniLandscapeTranslator::GetLandscapeMaterials(*Heightfield, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial); - - // Extract the float data from the Heightfield. - const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; - TArray FloatValues; - float FloatMin, FloatMax; - if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) - return false; - - // Heightfield conversions should always use the global float min/max - // since they need to be calculated externally, potentially across multiple tiles. - FloatMin = fGlobalMin; - FloatMax = fGlobalMax; - - // Get the Unreal landscape size - const int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; - const int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; - - if (!LandscapeTileSizeInfo.bIsCached) - { - // Calculate a landscape size info from this heightfield to be - // used by subsequent tiles on the same landscape - if (FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - HoudiniHeightfieldXSize, - HoudiniHeightfieldYSize, - LandscapeTileSizeInfo.UnrealSizeX, - LandscapeTileSizeInfo.UnrealSizeY, - LandscapeTileSizeInfo.NumSectionsPerComponent, - LandscapeTileSizeInfo.NumQuadsPerSection)) - { - LandscapeTileSizeInfo.bIsCached = true; - } - else - { - return false; - } - } - - const int32 UnrealTileSizeX = LandscapeTileSizeInfo.UnrealSizeX; - const int32 UnrealTileSizeY = LandscapeTileSizeInfo.UnrealSizeY; - const int32 NumSectionPerLandscapeComponent = LandscapeTileSizeInfo.NumSectionsPerComponent; - const int32 NumQuadsPerLandscapeSection = LandscapeTileSizeInfo.NumQuadsPerSection; - - // ---------------------------------------------------- - // Export of layer textures - // ---------------------------------------------------- - // Export textures, if enabled. Mostly used for debugging at the moment. - bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; - if (bExportTexture) - { - // Export raw height data to texture - FString TextureName = TilePackageParams.ObjectName + TEXT("_height_raw"); - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - HoudiniHeightfieldXSize, - HoudiniHeightfieldYSize, - FloatValues, - FloatMin, - FloatMax); - } - - // Look for all the layers/masks corresponding to the current heightfield. - TArray< const FHoudiniGeoPartObject* > FoundLayers; - FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); - - // Get the updated layers. - TArray LayerInfos; - - if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, - LayerMinimums, LayerMaximums, LayerInfos, false, - TilePackageParams, - LayerPackageParams, - OutCreatedPackages)) - return false; - - // Convert Houdini's heightfield data to Unreal's landscape data - TArray IntHeightData; - FTransform TileTransform; - if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( - FloatValues, VolumeInfo, - UnrealTileSizeX, UnrealTileSizeY, - FloatMin, FloatMax, - IntHeightData, TileTransform)) - return false; - - // ---------------------------------------------------- - // Property changes that we want to track - // ---------------------------------------------------- - - bool bModifiedLandscapeActor = false; - bool bModifiedSharedLandscapeActor = false; - bool bSharedLandscapeMaterialChanged = false; - bool bSharedLandscapeHoleMaterialChanged = false; - bool bSharedPhysicalMaterialChanged = false; - bool bTileLandscapeMaterialChanged = false; - bool bTileLandscapeHoleMaterialChanged = false; - bool bTilePhysicalMaterialChanged = false; - bool bCreatedMap = false; - bool bCreatedTileActor = false; - bool bHeightLayerDataChanged = false; - bool bCustomLayerDataChanged = false; - - // ---------------------------------------------------- - // Calculate Tile location and landscape offset - // ---------------------------------------------------- - FTransform LandscapeTransform; - FIntPoint TileLoc; - - // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate - // for any landscape shifts due to section base alignment offsets. - CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeReferenceLocation, LandscapeTransform, TileLoc); - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Transform: %s"), *TileTransform.ToString()); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape Transform: %s"), *LandscapeTransform.ToString()); - - - // ---------------------------------------------------- - // Find or create *shared* landscape - // ---------------------------------------------------- - - ALandscape* SharedLandscapeActor = nullptr; - bool bCreatedSharedLandscape = false; - - if (bRequiresSharedLandscape) - { - // Streaming proxy tiles always require a "shared landscape" that contains the - // various landscape properties to be shared amongst all the tiles. - AActor* FoundActor = nullptr; - SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, SharedLandscapeActorName, FoundActor); - - bool bIsValidSharedLandscape = IsValid(SharedLandscapeActor); - - if (bIsValidSharedLandscape) - { - // We have a target landscape. Check whether it is compatible with the Houdini volume. - ULandscapeInfo* LandscapeInfo = SharedLandscapeActor->GetLandscapeInfo(); - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape: %s"), *(SharedLandscapeActor->GetPathName())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape with num proxies: %d"), LandscapeInfo->Proxies.Num()); - - if (!LandscapeExtent.bIsCached) - { - LandscapeInfo->FixupProxiesTransform(); - // Cache the landscape extents. Note that GetLandscapeExtent() will only take into account the currently loaded landscape tiles. - PopulateLandscapeExtents(LandscapeExtent, LandscapeInfo); - } - - bool bIsCompatible = IsLandscapeInfoCompatible( - LandscapeInfo, - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Checking landscape compatibility ...")); - bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); - if (!bIsCompatible) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Shared landscape is incompatible. Cannot reuse.")); - // We can't resize the landscape in-place. We have to create a new one. - DestroyLandscape(SharedLandscapeActor); - SharedLandscapeActor = nullptr; - bIsValidSharedLandscape = false; - } - else - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Existing shared landscape is compatible.")); - } - } - - if (!bIsValidSharedLandscape) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Create new shared landscape...")); - // Create and configure the main landscape actor. - // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos - SharedLandscapeActor = InWorld->SpawnActor(); - if (SharedLandscapeActor) - { - CreatedUntrackedOutputs.Add( SharedLandscapeActor ); - - // NOTE that shared landscape is always located at the origin, but not the tile actors. The - // tiles are properly transformed. - - // If we working with landscape tiles, this actor will become the "Main landscape" actor but - // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. - SharedLandscapeActor->bCanHaveLayersContent = false; - SharedLandscapeActor->ComponentSizeQuads = NumQuadsPerLandscapeSection*NumSectionPerLandscapeComponent; - SharedLandscapeActor->NumSubsections = NumSectionPerLandscapeComponent; - SharedLandscapeActor->SubsectionSizeQuads = NumQuadsPerLandscapeSection; - SharedLandscapeActor->SetLandscapeGuid( FGuid::NewGuid() ); - SharedLandscapeActor->bCastStaticShadow = false; - for (const auto& ImportLayerInfo : LayerInfos) - { - SharedLandscapeActor->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(ImportLayerInfo.LayerInfo)); - } - SharedLandscapeActor->CreateLandscapeInfo(); - bCreatedSharedLandscape = true; - - // NOTE: It is important to set Landscape materials BEFORE blitting layer data. For example, setting - // data in the visibility layer (on tiles) will have no effect until Landscape materials have been applied / processed. - SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; - SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - - // Ensure the landscape actor name and label matches `LandscapeActorName`. - FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, SharedLandscapeActorName); - - SharedLandscapeActor->MarkPackageDirty(); - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(InWorld->GetPathName()) ); - return false; - } - } - // else -- Reusing shared landscape - } - - if (SharedLandscapeActor) - { - // Ensure the existing landscape actor transform is correct. - SharedLandscapeActor->SetActorRelativeTransform(LandscapeTransform); - - bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; - if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) - { - DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - } - - if (bSharedLandscapeMaterialChanged) - { - SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; - - } - if (bSharedLandscapeHoleMaterialChanged) - { - SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - - } - - bSharedPhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? (SharedLandscapeActor->DefaultPhysMaterial != LandscapePhysicalMaterial) : false; - if (bSharedPhysicalMaterialChanged) - { - DoPreEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); - SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - SharedLandscapeActor->ChangedPhysMaterial(); - } - } - - // ---------------------------------------------------- - // Find Landscape actor / tile - // ---------------------------------------------------- - - // Find an actor with the given name. The TileWorld and TileLevel returned should be - // used to spawn the new actor, if the actor itself could not be found. - //bool bCreatedPackage = false; - // TileActor = FindExistingLandscapeActor( - // InWorld, InOutput, ValidLandscapes, - // UnrealLandscapeSizeX, UnrealLandscapeSizeY, LandscapeTileActorName, - // LevelPath, TileWorld, TileLevel, bCreatedPackage); - - // Currently the Temp Cook mode is not concerned with creating packages. This will, at the time of writing, - // exclusively be dealt with during Bake mode so don't bother with searching / creating other packages. - - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (IsValid(HAC)) - { - TileWorld = HAC->GetWorld(); - TileLevel = HAC->GetComponentLevel(); - } - else - { - TileWorld = InWorld; - TileLevel = InWorld->PersistentLevel; - } - - check(TileWorld); - check(TileLevel); - - AActor* FoundActor = nullptr; - if (InPackageParams.PackageMode == EPackageMode::Bake) - { - // When baking, See if we can find any landscape / proxy actors for this tile in the TileLevel. - // If we find any actors that match the name but not the type, or the actors are pending kill, then - // rename them so that we can spawn new actors. - switch (TileActorType) - { - case LandscapeActorType::LandscapeActor: - TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); - break; - case LandscapeActorType::LandscapeStreamingProxy: - TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); - break; - default: - TileActor = nullptr; - } - } - else - { - // In temp mode, only consider our previous output landscapes, - // or our input landscapes that have the "update input landscape" option enabled - ALandscapeProxy* FoundLandscapeProxy = nullptr; - - // Try to see if we have an input landscape that matches the size of the current HGPO - for (int nIdx = 0; nIdx < InputLandscapesToUpdate.Num(); nIdx++) - { - ALandscapeProxy* CurrentInputLandscape = InputLandscapesToUpdate[nIdx]; - if (!CurrentInputLandscape) - continue; - - if (SharedLandscapeActor && CurrentInputLandscape->GetLandscapeActor() != SharedLandscapeActor) - // This tile actor no longer associated with the current shared landscape - continue; - - ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); - if (!CurrentInfo) - continue; - - int32 InputMinX = 0; - int32 InputMinY = 0; - int32 InputMaxX = 0; - int32 InputMaxY = 0; - if (!LandscapeExtent.bIsCached) - { - PopulateLandscapeExtents(LandscapeExtent, CurrentInfo); - } - - if (!LandscapeExtent.bIsCached) - { - HOUDINI_LOG_WARNING(TEXT("Warning: Could not determine landscape extents. Cannot re-use input landscape actor.")); - continue; - } - - InputMinX = LandscapeExtent.MinY; - InputMinY = LandscapeExtent.MinY; - InputMaxX = LandscapeExtent.MaxX; - InputMaxY = LandscapeExtent.MaxY; - - // If the full size matches, we'll update that input landscape - bool SizeMatch = false; - if ((InputMaxX - InputMinX + 1) == UnrealTileSizeX && (InputMaxY - InputMinY + 1) == UnrealTileSizeY) - SizeMatch = true; - - // HF and landscape don't match, try another one - if (!SizeMatch) - continue; - - // Replace FoundLandscape by that input landscape - FoundLandscapeProxy = CurrentInputLandscape; - - // We've found a valid input landscape, remove it from the input array so we don't try to update it multiple times - InputLandscapesToUpdate.RemoveAt(nIdx); - break; - } - - if (!FoundLandscapeProxy) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Could not find input landscape to update. Searching output objects...")); - - // Try to see if we can reuse one of our previous output landscape. - // Keep track of the previous cook's landscapes - TMap& OldOutputObjects = InOutput->GetOutputObjects(); - for (auto& CurrentLandscape : OldOutputObjects) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentLandscape.Value.OutputObject); - if (!LandscapePtr) - continue; - - FoundLandscapeProxy = LandscapePtr->GetRawPtr(); - if (!FoundLandscapeProxy) - { - // We may need to manually load the object - //OldLandscapeProxy = LandscapePtr->GetSoftPtr().LoadSynchronous(); - FoundLandscapeProxy = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); - } - - if (!IsValid(FoundLandscapeProxy)) - continue; - - // We need to make sure that this landscape is not one of our input landscape - // This would happen if we were previously updating it, but just turned the option off - // In that case, the landscape would be in both our inputs and outputs, - // but with the "Update Input Data" option off - if (InAllInputLandscapes.Contains(FoundLandscapeProxy)) - { - FoundLandscapeProxy = nullptr; - continue; - } - - if (SharedLandscapeActor && FoundLandscapeProxy->GetLandscapeActor() != SharedLandscapeActor) - { - // This landscape proxy is no longer part of the shared landscape. - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Output landscape proxy is no longer part of the landscape. Skipping")); - FoundLandscapeProxy = nullptr; - continue; - } - - // If we found a possible candidate, make sure that its size matches ours as we can only update a landscape tile of the same size - if (!IsLandscapeTileCompatible( - FoundLandscapeProxy, - UnrealTileSizeX-1, - UnrealTileSizeY-1, - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection)) - { - FoundLandscapeProxy = nullptr; - continue; - } - - if (SharedLandscapeActor) - { - if (FoundLandscapeProxy->GetLandscapeGuid() != SharedLandscapeActor->GetLandscapeGuid()) - { - FoundLandscapeProxy = nullptr; - continue; - } - } - - // TODO: we probably need to do some more checks with tiled landscapes as well? - - // We found a valid Candidate! - if (FoundLandscapeProxy) - { - break; - } - } - } - - - - if (IsValid(FoundLandscapeProxy)) - { - TileActor = FoundLandscapeProxy; - if (TileActor->GetName() != LandscapeTileActorName) - { - // Ensure the TileActor is named correctly - FHoudiniEngineUtils::SafeRenameActor(TileActor, LandscapeTileActorName); - } - } - } - - // NOTE: We don't need to delete old landscape tiles (FoundActor != TileActor) here. That is an old - // output that should get cleaned up automatically. - - if (IsValid(TileActor)) - { - check(!(TileActor->IsPendingKill())); - - // ---------------------------------------------------- - // Check landscape compatibility - // ---------------------------------------------------- - - bool bIsCompatible = IsLandscapeTileCompatible( - TileActor, - UnrealTileSizeX-1, - UnrealTileSizeY-1, - NumSectionPerLandscapeComponent, - NumQuadsPerLandscapeSection); - - bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(TileActor, TileActorType); - - if (!bIsCompatible) - { - // Can't reuse this tile actor since the landscape dimensions doesn't match or the actor type has changed. - if (TileActor->IsA()) - { - // This landscape tile needs to be unregistered from the landscape info. - ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); - if (IsValid(LandscapeInfo)) - { - LandscapeInfo->UnregisterActor(TileActor); - } - } - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Incompatible tile actor. Destroying: %s"), *(TileActor->GetPathName())); - TileActor->Destroy(); - TileActor = nullptr; - } - else - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape tile is compatible: %s"), *(TileActor->GetPathName())); - } - } - - // ---------------------------------------------------- - // Create or update landscape / tile. - // ---------------------------------------------------- - // Note that a single heightfield generated in Houdini can be treated - // as either a landscape tile (LandscapeStreamingProxy) or a standalone - // landscape (ALandscape). This determination is made purely from user specified - // attributes. No "clever logic" in here, please! - - ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; - ALandscape* CachedLandscapeActor = nullptr; - ULandscapeInfo *LandscapeInfo; - -#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Loc: %d, %d"), TileLoc.X, TileLoc.Y); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Size: %d, %d"), UnrealTileSizeX, UnrealTileSizeY); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Quads/Section: %d"), NumQuadsPerLandscapeSection); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Sections/Component: %d"), NumSectionPerLandscapeComponent); -#endif - - if (!TileActor) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Creating new tile actor: %s"), *(LandscapeTileActorName)); - // Create a new Landscape tile in the TileWorld - TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( - IntHeightData, LayerInfos, TileTransform, TileLoc, - UnrealTileSizeX, UnrealTileSizeY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, - LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, - LandscapeTileActorName, - TileActorType, - SharedLandscapeActor, - TileWorld, - TileLevel, - InPackageParams); - - if (!TileActor || !TileActor->IsValidLowLevel()) - return false; - - LandscapeInfo = TileActor->GetLandscapeInfo(); - - bCreatedTileActor = true; - bTileLandscapeMaterialChanged = true; - bTileLandscapeHoleMaterialChanged = true; - bTilePhysicalMaterialChanged = true; - bHeightLayerDataChanged = true; - bCustomLayerDataChanged = true; - } - else - { - LandscapeInfo = TileActor->GetLandscapeInfo(); - - // Always update the transform, even if the HGPO transform hasn't changed, - // If we change the number of tiles, or switch from outputting single tile to multiple, - // then its fairly likely that the unreal transform has changed even if the - // Houdini Transform remained the same - bool bUpdateTransform = !TileActor->GetActorTransform().Equals(TileTransform); - - // Update existing landscape / tile - if (SharedLandscapeActor && TileActorType == LandscapeActorType::LandscapeStreamingProxy) - { - TileActor->FixupSharedData(SharedLandscapeActor); - if (bUpdateTransform) - { - TileActor->SetAbsoluteSectionBase(TileLoc); - LandscapeInfo->FixupProxiesTransform(); - LandscapeInfo->RecreateLandscapeInfo(InWorld,true); - } - - // This is a tile with a shared landscape. - // Check whether the LandscapeActor changed. If so, update the proxy's shared properties. - CachedStreamingProxyActor = Cast(TileActor); - if (SharedLandscapeActor) - { - if (CachedStreamingProxyActor) - bModifiedLandscapeActor = CachedStreamingProxyActor->LandscapeActor != SharedLandscapeActor; - else - bModifiedLandscapeActor = true; - - if (bModifiedLandscapeActor) - { - CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; - // We need to force a state update through PostEditChangeProperty here in order to initialize - // since we're about to perform additional data updates on this tile. - DoPostEditChangeProperty(CachedStreamingProxyActor, "LandscapeActor"); - } - } - else - { - CachedStreamingProxyActor->LandscapeActor = nullptr; - } - - } - else - { - // This is a standalone tile / landscape actor. - if (bUpdateTransform) - { - TileActor->SetActorRelativeTransform(TileTransform); - TileActor->SetAbsoluteSectionBase(TileLoc); - } - } - - CachedLandscapeActor = TileActor->GetLandscapeActor(); - - ULandscapeInfo* PreviousInfo = TileActor->GetLandscapeInfo(); - if (!PreviousInfo) - return false; - - FIntRect BoundingRect = TileActor->GetBoundingRect(); - FIntPoint SectionOffset = TileActor->GetSectionBaseOffset(); - - // Landscape region to update - const int32 MinX = TileLoc.X; - const int32 MaxX = TileLoc.X + UnrealTileSizeX - 1; - const int32 MinY = TileLoc.Y; - const int32 MaxY = TileLoc.Y + UnrealTileSizeY - 1; - - // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. - // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools - // though the *Accessors do additional things like update normals and foliage. - - // Update height if it has been changed. - if (Heightfield->bHasGeoChanged) - { - // It is important to update the heightmap through HeightmapAccessor this since it will properly - // update normals and foliage. - FHeightmapAccessor HeightmapAccessor(LandscapeInfo); - HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); - bHeightLayerDataChanged = true; - } - - // Update the layers on the landscape. - for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) - { - if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. - FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); - AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - } - else - { - FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); - AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - } - - bCustomLayerDataChanged = true; - } - - bModifiedLandscapeActor = true; - } - - // ---------------------------------------------------- - // Update tile materials - // ---------------------------------------------------- - // TODO: These material updates can possibly be skipped if we have already performed this - // check on a SharedLandscape. - bTileLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (TileActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bTileLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (TileActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); - - if (bTileLandscapeMaterialChanged) - TileActor->LandscapeMaterial = LandscapeMaterial; - - if (bTileLandscapeHoleMaterialChanged) - TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - - bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; - if (bTilePhysicalMaterialChanged) - { - DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); - TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - //TileActor->ChangedPhysMaterial(); - } - - // ---------------------------------------------------- - // Apply actor tags - // ---------------------------------------------------- - - // See if we have unreal_tag_ attribute - TArray Tags; - if (TileActor && FHoudiniEngineUtils::GetUnrealTagAttributes(GeoId, PartId, Tags)) - { - TileActor->Tags = Tags; - } - - // ---------------------------------------------------- - // Update actor states based on data updates - // ---------------------------------------------------- - // Based on ALandscape and ALandscapeStreamingProxy PostEditChangeProperty() implementations, - // effect appropriate state updates based on the property updates that was performed in - // the above code. - - if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) - { - check(SharedLandscapeActor); - DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - } - - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - { - check(TileActor); - // Tile material changes are only processed if it wasn't already done for a shared - // landscape since the shared landscape should have already propagated the changes to associated proxies. - DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); - } - - if (bSharedPhysicalMaterialChanged) - { - check(SharedLandscapeActor); - DoPostEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); - } - - if (bTilePhysicalMaterialChanged) - { - check(TileActor); - DoPostEditChangeProperty(TileActor, "DefaultPhysMaterial"); - } - - if (bModifiedSharedLandscapeActor) - { - SharedLandscapeActor->PostEditChange(); - } - - if (bModifiedLandscapeActor) - { - TileActor->PostEditChange(); - } - - { - FLandscapeEditDataInterface LandscapeEdit(TileActor->GetLandscapeInfo()); - LandscapeEdit.RecalculateNormals(); - } - - if (LandscapeInfo) - { - LandscapeInfo->RecreateLandscapeInfo(InWorld, true); - LandscapeInfo->RecreateCollisionComponents(); - } - - { - // Update UProperties - - // Apply detail attributes to both the Shared Landscape and the Landscape Tile actor - TArray PropertyAttributes; - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - GeoId, PartId, - true, - INDEX_NONE, INDEX_NONE, INDEX_NONE, - PropertyAttributes)) - { - if (IsValid(TileActor)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); - } - if (IsValid(SharedLandscapeActor)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(SharedLandscapeActor, PropertyAttributes); - } - } - - // Apply point attributes only to the Shared Landscape and the Landscape Tile actor - PropertyAttributes.Empty(); - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - GeoId, PartId, - false, - 0, INDEX_NONE, 0, - PropertyAttributes)) - { - if (IsValid(TileActor)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); - } - } - } - - // Add objects to the HAC output. - SetLandscapeActorAsOutput( - InOutput, - InAllInputLandscapes, - OutputAttributes, - OutputTokens, - SharedLandscapeActor, - SharedLandscapeActorParent, - bCreatedSharedLandscape, - HeightfieldIdentifier, - TileActor, - InPackageParams.PackageMode); - -#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) - if (LandscapeInfo) - { - int32 MinX, MinY, MaxX, MaxY; - LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape extent: %d, %d -> %d, %d"), MinX, MinY, MaxX, MaxY); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Cached extent: %d, %d -> %d, %d"), LandscapeExtent.MinX, LandscapeExtent.MinY, LandscapeExtent.MaxX, LandscapeExtent.MaxY); - } - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Ending with num of output objects: %d"), InOutput->GetOutputObjects().Num()); -#endif - - return true; -} - -bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedActors, TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, UWorld* World, const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniLandscapeExtent& LandscapeExtent, - FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, - FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, FHoudiniPackageParams InPackageParams, - TSet& ClearedLayers, - TArray& OutCreatedPackages) -{ - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::OutputLandscape_EditableLayer] =======================================================================")); - - check(LayerMinimums.Contains(TEXT("height"))); - check(LayerMaximums.Contains(TEXT("height"))); - - float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); - float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); - - if (!InOutput || InOutput->IsPendingKill()) - return false; - - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (!IsValid(HAC)) - return false; - - // Get the height map. - const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); - if (!Heightfield) - return false; - - if (Heightfield->Type != EHoudiniPartType::Volume) - return false; - - const HAPI_NodeId GeoId = Heightfield->GeoId; - const HAPI_PartId PartId = Heightfield->PartId; - - TArray StrData; - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - // --------------------------------------------- - // Attribute: unreal_landscape_editlayer_name - // --------------------------------------------- - StrData.Empty(); - FString EditableLayerName; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME, AttributeInfo, StrData, 1)) - { - if (StrData.Num() > 0) - { - EditableLayerName = StrData[0]; - } - } - if (EditableLayerName.IsEmpty()) - return false; - - // Construct the identifier of the Heightfield geo part. - FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); - HeightfieldIdentifier.PartName = Heightfield->PartName; - - // Extract the float data from the Heightfield. - const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; - TArray FloatValues; - float FloatMin, FloatMax; - if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) - return false; - - // Get the Unreal landscape size - const int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; - const int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; - - if (!LandscapeTileSizeInfo.bIsCached) - { - // Calculate a landscape size info from this heightfield to be - // used by subsequent tiles on the same landscape - if (FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - HoudiniHeightfieldXSize, - HoudiniHeightfieldYSize, - LandscapeTileSizeInfo.UnrealSizeX, - LandscapeTileSizeInfo.UnrealSizeY, - LandscapeTileSizeInfo.NumSectionsPerComponent, - LandscapeTileSizeInfo.NumQuadsPerSection)) - { - LandscapeTileSizeInfo.bIsCached = true; - } - else - { - return false; - } - } - - TMap OutputTokens; - FHoudiniAttributeResolver Resolver; - // Update resolver attributes and tokens before we start resolving attributes. - InPackageParams.UpdateTokensFromParams(World, HAC, OutputTokens); - - // --------------------------------------------- - // Attribute: unreal_landscape_actor_name - // --------------------------------------------- - // Retrieve the name of the main Landscape actor to look for - FString TargetLandscapeName = "Input0"; - StrData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, AttributeInfo, StrData, 1)) - { - if (StrData.Num() > 0 && !StrData[0].IsEmpty()) - TargetLandscapeName = StrData[0]; - } - - Resolver.SetAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, TargetLandscapeName); - Resolver.SetTokensFromStringMap(OutputTokens); - TargetLandscapeName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, TargetLandscapeName); - - // --------------------------------------------- - // Find the landscape that we're targeting for output - // --------------------------------------------- - ALandscapeProxy* TargetLandscapeProxy = FindTargetLandscapeProxy(TargetLandscapeName, World, InAllInputLandscapes); - if (!IsValid(TargetLandscapeProxy)) - { - HOUDINI_LOG_WARNING(TEXT("Could not find landscape actor: %s"), *(TargetLandscapeName)); - return false; - } - ALandscape* TargetLandscape = TargetLandscapeProxy->GetLandscapeActor(); - check(TargetLandscape); - - ULandscapeInfo* TargetLandscapeInfo = TargetLandscapeProxy->GetLandscapeInfo(); - const FTransform TargetLandscapeTransform = TargetLandscape->GetActorTransform(); - - const float DestHeightScale = TargetLandscapeProxy->LandscapeActorToWorld().GetScale3D().Z; - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Height Scale: %f"), DestHeightScale); - - // Create the layer if it doesn't exist - int32 EditLayerIndex = TargetLandscape->GetLayerIndex(FName(EditableLayerName)); - const FLandscapeLayer* TargetLayer = TargetLandscape->GetLayer(EditLayerIndex); - if (!TargetLayer) - { - // Create new layer - EditLayerIndex = TargetLandscape->CreateLayer(FName(EditableLayerName)); - TargetLayer = TargetLandscape->GetLayer(FName(EditableLayerName)); - } - - if (!TargetLayer) - { - HOUDINI_LOG_WARNING(TEXT("Could not find or create target layer: %s"), *(TargetLandscapeName)); - return false; - } - - { - // --------------------------------------------- - // Attribute: unreal_landscape_editlayer_after - // --------------------------------------------- - StrData.Empty(); - FString AfterLayerName; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER, AttributeInfo, StrData, 1)) - { - if (StrData.Num() > 0) - { - AfterLayerName = StrData[0]; - } - } - if (!AfterLayerName.IsEmpty()) - { - // If we have an "after layer", move the output layer into position. - int32 NewLayerIndex = TargetLandscape->GetLayerIndex(FName(AfterLayerName)); - if (NewLayerIndex != INDEX_NONE && EditLayerIndex != NewLayerIndex) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Moving layer from %d to %d"), EditLayerIndex, NewLayerIndex); - if (NewLayerIndex < EditLayerIndex) - { - NewLayerIndex += 1; - } - TargetLandscape->ReorderLayer(EditLayerIndex, NewLayerIndex); - - // Ensure we have the correct layer/index - EditLayerIndex = TargetLandscape->GetLayerIndex(FName(EditableLayerName)); - TargetLayer = TargetLandscape->GetLayer(EditLayerIndex); - } - } - } - - // ---------------------------------------------------- - // Convert Heightfield data - // ---------------------------------------------------- - // Convert Houdini's heightfield data to Unreal's landscape data - TArray IntHeightData; - FTransform TileTransform; - if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( - FloatValues, VolumeInfo, - LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, - FloatMin, FloatMax, - IntHeightData, TileTransform, - false, true, DestHeightScale)) - return false; - - - // ---------------------------------------------------- - // Calculate Tile location and landscape offset - // ---------------------------------------------------- - FTransform SrcLandscapeTransform, HACTransform; - FIntPoint TileLoc; - - // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate - // for any landscape shifts due to section base alignment offsets. - CalculateTileLocation(LandscapeTileSizeInfo.NumSectionsPerComponent, LandscapeTileSizeInfo.NumQuadsPerSection, TileTransform, LandscapeReferenceLocation, SrcLandscapeTransform, TileLoc); - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Landscape Transform: %s"), *(TargetLandscapeTransform.ToString())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Tile Transform: %s"), *(TileTransform.ToString())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Tile Size: %d, %d"), LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY); - - HACTransform = HAC->GetComponentTransform(); - SrcLandscapeTransform = HACTransform; - - // ---------------------------------------------------- - // Convert the tile coordinates to quad space on the target Landscape. - // ---------------------------------------------------- - FVector RelTileLoc = (TileTransform*HACTransform).GetLocation(); - RelTileLoc = TargetLandscapeTransform.InverseTransformPosition(RelTileLoc); - - TileLoc.X = FMath::RoundFromZero(RelTileLoc.X); - TileLoc.Y = FMath::RoundFromZero(RelTileLoc.Y); - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Sections per component: %d"), (TargetLandscapeInfo->ComponentNumSubsections)); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Quads per component: %d"), (TargetLandscapeInfo->ComponentSizeQuads)); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Relative Tile Position: %s"), *(RelTileLoc.ToString())); - - FVector TileMin, TileMax; - TileMin.X = TileLoc.X; - TileMin.Y = TileLoc.Y; - TileMax.X = TileLoc.X + LandscapeTileSizeInfo.UnrealSizeX - 1; - TileMax.Y = TileLoc.Y + LandscapeTileSizeInfo.UnrealSizeY - 1; - TileMin.Z = TileMax.Z = 0.f; - - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Src Landscape Transform: %s"), *(SrcLandscapeTransform.ToString())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Src Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); - - FTransform DestLandscapeTransform = TargetLandscapeProxy->LandscapeActorToWorld(); - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Landscape Transform: %s"), *(DestLandscapeTransform.ToString())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Actor Transform: %s"), *(TargetLandscape->GetTransform().ToString())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); - - // NOTE: we don't manually inject a tile number in the object name. This should - // already be encoded in the TileName string. - FHoudiniPackageParams TilePackageParams = InPackageParams; - FHoudiniPackageParams LayerPackageParams = InPackageParams; - - TilePackageParams.ObjectName = TargetLandscapeName; - LayerPackageParams.ObjectName = TargetLandscapeName; - - // Look for all the layers/masks corresponding to the current heightfield. - TArray< const FHoudiniGeoPartObject* > FoundLayers; - FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Found %d output layers."), FoundLayers.Num()); - - // Get the updated layers. - TArray LayerInfos; - if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, - LayerMinimums, LayerMaximums, LayerInfos, false, - TilePackageParams, - LayerPackageParams, - OutCreatedPackages)) - return false; - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Generated %d layer infos."), LayerInfos.Num()); - - // Collect existing layers on the landscape - TMap ExistingLayers; - int32 NumTargetLayers = TargetLandscape->EditorLayerSettings.Num(); - for(int32 LayerIndex = 0; LayerIndex < NumTargetLayers; LayerIndex++) - { - FLandscapeEditorLayerSettings& Settings = TargetLandscape->EditorLayerSettings[LayerIndex]; - if (!Settings.LayerInfoObj) - continue; - ExistingLayers.Add(Settings.LayerInfoObj->LayerName, LayerIndex); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Found existing landscape material layer: %s"), *(Settings.LayerInfoObj->LayerName.ToString())); - } - - bool bLayerHasChanged = false; - for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) - { - // Ensure weight blending is disabled for all layers coming from Houdini otherwise material layer outputs - // won't blend correctly on landscapes in Editable Layer mode. - InLayerInfo.LayerInfo->bNoWeightBlend = true; - - if (ExistingLayers.Contains(InLayerInfo.LayerName)) - { - - int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); - // NOTE: If we hot-swap existing Layer Info objects here, it leads to errors about landscape drawing resources that can't be released. - // For now, just modify any existing layers in place until we can figure out how to properly swap Layer Info objects. - - // // The landscape already contains this layer. Ensure it is pointing to the correct layer info object. - // bLayerHasChanged = TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj != InLayerInfo.LayerInfo; - // if (bLayerHasChanged) - // { - // HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Updating existing layer: %s"), *(InLayerInfo.LayerName.ToString())); - // TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj = InLayerInfo.LayerInfo; - // } - TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj->bNoWeightBlend = true; - } - else - { - // Landscape does not contain this layer. Add it. - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Adding new layer: %s"), *(InLayerInfo.LayerName.ToString())); - TargetLandscape->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(InLayerInfo.LayerInfo)); - bLayerHasChanged = true; - } - } - - // Clear layers - if (!ClearedLayers.Contains(EditableLayerName)) - { - bool bClearLayer = false; - // --------------------------------------------- - // Attribute: unreal_landscape_editlayer_clear - // --------------------------------------------- - // Check whether we should clear the target edit layer. - TArray IntData; - IntData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR, AttributeInfo, IntData, 1)) - { - if (IntData.Num() > 0) - { - bClearLayer = IntData[0] != 0; - } - } - - if (bClearLayer) - { - if (TargetLayer) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Clearing layer heightmap: %s"), *EditableLayerName); - ClearedLayers.Add(EditableLayerName); - - // Clear the heightmap - TargetLandscape->ClearLayer(TargetLayer->Guid, nullptr, ELandscapeClearMode::Clear_Heightmap); - - // Clear the paint layers, but only the ones that are being output from Houdini. - for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) - { - if (!ExistingLayers.Contains(InLayerInfo.LayerName)) - continue; - - int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); - TargetLandscape->ClearPaintLayer(TargetLayer->Guid, InLayerInfo.LayerInfo); - } - } - } - } - - - { - // Scope the Edit Layer before we start drawing on ANY of the layers - FScopedSetLandscapeEditingLayer Scope(TargetLandscape, TargetLayer->Guid, [=] { TargetLandscape->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); }); - FLandscapeEditDataInterface LandscapeEdit(TargetLandscapeInfo); - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Drawing heightmap..")); - // Draw Heightmap - FHeightmapAccessor HeightmapAccessor(TargetLandscapeInfo); - HeightmapAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, IntHeightData.GetData()); - - // Draw material layers on the landscape - // Update the layers on the landscape. - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target has layers content: %d"), TargetLandscape->HasLayersContent()); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] IsEditingLayer? %d"), TargetLandscape->GetEditingLayer().IsValid()); - - for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Trying to draw on layer: %s"), *(InLayerInfo.LayerName.ToString())); - - if (InLayerInfo.LayerInfo && InLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. - FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, ALandscapeProxy::VisibilityLayer); - AlphaAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, InLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - } - else - { - if (!ExistingLayers.Contains(InLayerInfo.LayerName)) - continue; - - int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); - FLandscapeEditorLayerSettings& CurLayer = TargetLandscape->EditorLayerSettings[LayerIndex]; - // Draw on the current layer, if it is valid. - if (CurLayer.LayerInfoObj) - { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Drawing using Alpha accessor. Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); - FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, CurLayer.LayerInfoObj); - AlphaAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, InLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - } - } - } - - } // Landscape layer drawing scope - - // Only keep output the output object that corresponds to this layer. Everything else should be removed. - TMap OutputObjects = InOutput->GetOutputObjects(); - TSet StaleOutputs; - OutputObjects.GetKeys(StaleOutputs); - bool bFoundOutputObject = false; - for(auto& Entry : OutputObjects) - { - if (bFoundOutputObject) - continue; // We already have a matching layer output object. Anything else is stale. - - FHoudiniOutputObjectIdentifier& OutputId = Entry.Key; - FHoudiniOutputObject& Object = Entry.Value; - UHoudiniLandscapeEditLayer* EditLayer = Cast(Object.OutputObject); - if (!IsValid(EditLayer)) - continue; - StaleOutputs.Remove(OutputId); - } - - // Clean up stale outputs - for(FHoudiniOutputObjectIdentifier& StaleId : StaleOutputs) - { - FHoudiniOutputObject& OutputObject = OutputObjects.FindChecked(StaleId); - if (UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject)) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - - if (LandscapeProxy) - { - // We shouldn't destroy any input landscapes - if (!InAllInputLandscapes.Contains(LandscapeProxy)) - { - LandscapeProxy->Destroy(); - } - } - } - - OutputObjects.Remove(StaleId); - } - - // Update the output object - FHoudiniOutputObjectIdentifier OutputObjectIdentifier(Heightfield->ObjectId, GeoId, PartId, "EditableLayer"); - FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(OutputObjectIdentifier); - UHoudiniLandscapeEditLayer* LayerPtr = NewObject(InOutput); - LayerPtr->SetSoftPtr(TargetLandscape); - LayerPtr->LayerName = EditableLayerName; - OutputObj.OutputObject = LayerPtr; - // Editable layers doesn't currently require any attributes / tokens to be cached. - // OutputObj.CachedAttributes = OutputAttributes; - // OutputObj.CachedTokens = OutputTokens; - - return true; -} - - -bool -FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( - const ULandscapeInfo* LandscapeInfo, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection - ) -{ - if (!IsValid(LandscapeInfo)) - return false; - - - if (LandscapeInfo->ComponentNumSubsections != InNumSectionsPerComponent) - return false; - - if (LandscapeInfo->SubsectionSizeQuads != InNumQuadsPerSection) - return false; - - int32 MinX, MinY, MaxX, MaxY; - if (LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - { - const int32 NumComponentsX = (MaxX - MinX) / (InNumQuadsPerSection*InNumSectionsPerComponent); - const int32 NumComponentsY = (MaxY - MinY) / (InNumQuadsPerSection*InNumSectionsPerComponent); - } - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsLandscapeTileCompatible( - const ALandscapeProxy* TileActor, - const int32 InTileSizeX, - const int32 InTileSizeY, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection -) -{ - check(TileActor); - - // NOTE: We can't compare landscape extents here since the Houdini only knows about the size for single tile. - // and LandscapeInfo will only return extents for the *loaded* landscape tiles. - - // TODO: Add more robust checks to determine landscape compatibility. - - if (!IsLandscapeInfoCompatible(TileActor->GetLandscapeInfo(), InNumSectionsPerComponent, InNumQuadsPerSection)) - return false; - - const FIntRect Bounds = TileActor->GetBoundingRect(); - const FIntPoint Size = Bounds.Size(); - if (Size.X != InTileSizeX && Size.Y != InTileSizeY) - return false; - - return true; -} - - -bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, LandscapeActorType ActorType) -{ - if (!IsValid(Actor)) - return false; - - switch (ActorType) - { - case LandscapeActorType::LandscapeActor: - return Actor->IsA(); - break; - case LandscapeActorType::LandscapeStreamingProxy: - return Actor->IsA(); - break; - default: - break; - } - - return false; -} - -bool FHoudiniLandscapeTranslator::PopulateLandscapeExtents(FHoudiniLandscapeExtent& Extent, - const ULandscapeInfo* LandscapeInfo) -{ - if (LandscapeInfo->GetLandscapeExtent(Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY)) - { - Extent.ExtentsX = Extent.MaxX - Extent.MinX; - Extent.ExtentsY = Extent.MaxY - Extent.MinY; - Extent.bIsCached = true; - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Cached extent: %d, %d -> %d, %d"), Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY); - - return true; - } - return false; -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage, - const EPackageMode& InPackageMode) -{ - if (InPackageMode == EPackageMode::Bake) - return FindExistingLandscapeActor_Bake(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); - else - return FindExistingLandscapeActor_Temp(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - bCreatedPackage = false; - - // // Locate landscape proxy actor when running in baked mode - // AActor* FoundActor = nullptr; - ALandscapeProxy* OutActor = nullptr; - // OutActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); - // // OutActor = FHoudiniEngineUtils::FindActorInWorld(InWorld, FName(InActorName)); - // if (FoundActor && FoundActor != OutActor) - // FoundActor->Destroy(); // nuke it! - // - // if (OutActor) - // { - // // TODO: make sure that the found is actor is actually assigned to the level defined by package path. - // // If the found actor is not from that level, it should be moved there. - // - // OutWorld = OutActor->GetWorld(); - // OutLevel = OutActor->GetLevel(); - // } - // else - { - // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. - bool bActorInWorld = false; - const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - InWorld, - InPackagePath, - true, - OutWorld, - OutLevel, - bCreatedPackage, - bActorInWorld); - - if (!bResult) - { - return nullptr; - } - - // if (!bActorInWorld) - // { - // // The OutLevel is not present in the current world which means we might - // // still find the tile actor in OutWorld. - OutActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); - // } - } - - return OutActor; -} - - -ALandscapeProxy* FHoudiniLandscapeTranslator::FindTargetLandscapeProxy(const FString& ActorName, UWorld* World, - const TArray& LandscapeInputs) -{ - int32 InputIndex = INDEX_NONE; - if (ActorName.StartsWith(TEXT("Input"))) - { - // Extract the numeric value after 'Input'. - FString IndexStr; - ActorName.Split(TEXT("Input"), nullptr, &IndexStr); - if (IndexStr.IsNumeric()) - { - InputIndex = FPlatformString::Atoi(*IndexStr); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FindTargetLandscapeProxy] Extract index %d from actor name: %s"), InputIndex, *ActorName); - } - } - - if (InputIndex != INDEX_NONE) - { - if (!LandscapeInputs.IsValidIndex(InputIndex)) - return nullptr; - return LandscapeInputs[InputIndex]; - } - - return FHoudiniEngineUtils::FindActorInWorldByLabel(World, ActorName); -} - -ALandscapeProxy* -FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - ALandscapeProxy* OutActor = nullptr; - FString ActorName = InActorName + TEXT("_Temp"); - TMap& PrevCookObjects = InOutput->GetOutputObjects(); - - OutWorld = InWorld; - OutLevel = InWorld->PersistentLevel; - - bCreatedPackage = false; - - // Find Landscape proxy for output when running in Temp mode - for(auto& PrevObject : PrevCookObjects) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(PrevObject.Value.OutputObject); - if (!LandscapePtr) - continue; - - OutActor = LandscapePtr->GetRawPtr(); - if (!OutActor) - { - // We may need to manually load the object - OutActor = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); - } - - if (!OutActor) - continue; - - // If we were updating the input landscape before, but arent anymore, - // we could still find it here in the output, ignore them now as we're only looking for previous output - if (ValidLandscapes.Contains(OutActor)) - continue; - - if (OutActor->GetName() != ActorName) - // This is not the droid we're looking for - continue; - - if (OutActor->IsPendingKill()) - { - FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + "_pendingkill"); - continue; - } - - // If we found a possible candidate, make sure that its size matches ours - // as we can only update a landscape of the same size - ULandscapeInfo* PreviousInfo = OutActor->GetLandscapeInfo(); - if (PreviousInfo) - { - int32 PrevMinX = 0; - int32 PrevMinY = 0; - int32 PrevMaxX = 0; - int32 PrevMaxY = 0; - PreviousInfo->GetLandscapeExtent(PrevMinX, PrevMinY, PrevMaxX, PrevMaxY); - - if ((PrevMaxX - PrevMinX + 1) == UnrealLandscapeSizeX && (PrevMaxY - PrevMinY + 1) == UnrealLandscapeSizeY) - { - // The size matches, we can reuse the old landscape. - break; - } - else - { - // We can't reuse this actor. The dimensions does not match. - // We need to rename this actor in order to create a new one with the specified name. - FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + TEXT("_old") ); - OutActor = nullptr; - } - } - } - - return OutActor; -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor, - const EPackageMode InPackageMode) -{ - if (InPackageMode == EPackageMode::Bake) - return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); - else - return SetLandscapeActorAsOutput_Temp(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor) -{ - // We are in bake mode. No outputs to register / add here. - // Do nothing, for now. -} - -void -FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputTokens, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedSharedLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor) -{ - // The main landscape is a special case here. It cannot be registered with the - // output object here, since it is possibly shared by *multiple* outputs so - // we have to deal with the attached and cleanup of the actor manually. - if (bCreatedSharedLandscape && IsValid(SharedLandscapeActorParent)) - { - AttachActorToHAC(InOutput, SharedLandscapeActor); - } - - // TODO: The OutputObject cleanup being performed here should really be part of - // the output object itself (or at the very least be encapsulated in a reusable - // static function somewhere) so that individual output objects can be cleaned - // when necessary without having to duplicate code when its needed. - - // Cleanup any stale output objects - TMap& Outputs = InOutput->GetOutputObjects(); - TArray StaleOutputs; - for (auto& Elem : Outputs) - { - UHoudiniLandscapePtr* LandscapePtr = nullptr; - bool bIsStale = false; - - if (!(Elem.Key == Identifier)) - { - // Identifiers doesn't match so this is definitely a stale output. - StaleOutputs.Add(Elem.Key); - bIsStale = true; - } - - LandscapePtr = Cast(Elem.Value.OutputObject); - if (LandscapePtr) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - - if (LandscapeProxy) - { - // We shouldn't destroy any input landscape, - // or the landscape that we are currently trying to set as output.. - if (!InAllInputLandscapes.Contains(LandscapeProxy) && (LandscapeProxy != LandscapeActor)) - { - // This landscape proxy either doesn't match the landscape identifier - // or it doesn't match the actor we're about to output. Either way, - // get rid of it. - LandscapeProxy->Destroy(); - } - } - } - - if (bIsStale) - { - Elem.Value.OutputObject = nullptr; - } - } - - for (FHoudiniOutputObjectIdentifier& StaleOutput : StaleOutputs) - { - Outputs.Remove(StaleOutput); - } - - - // Send a landscape pointer back to the Output Object for this landscape tile. - FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); - UHoudiniLandscapePtr* LandscapePtr = NewObject(InOutput); - LandscapePtr->SetSoftPtr(LandscapeActor); - OutputObj.OutputObject = LandscapePtr; - OutputObj.CachedAttributes = OutputAttributes; - OutputObj.CachedTokens = OutputTokens; -} - - -bool -FHoudiniLandscapeTranslator::AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor) -{ - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (IsValid(HAC)) - { - InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); - - return true; - } - return false; -} - - -FString -FHoudiniLandscapeTranslator::GetActorNameSuffix(const EPackageMode& InPackageMode) -{ - if(InPackageMode == EPackageMode::CookToTemp) - return "_Temp"; - else - return FString(); -} - -void -FHoudiniLandscapeTranslator::DoPreEditChangeProperty(UObject* Obj, FName PropertyName) -{ - Obj->PreEditChange(FindFProperty(Obj->GetClass(), PropertyName)); -} - -void -FHoudiniLandscapeTranslator::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) -{ - FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); - Obj->PostEditChangeProperty(Evt); -} - -bool -FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( - const TArray< float >& HeightfieldFloatValues, - const FHoudiniVolumeInfo& HeightfieldVolumeInfo, - const int32& FinalXSize, const int32& FinalYSize, - float FloatMin, float FloatMax, - TArray< uint16 >& IntHeightData, - FTransform& LandscapeTransform, - const bool NoResize, - const bool bOverrideZScale, - const float CustomZScale) -{ - IntHeightData.Empty(); - LandscapeTransform.SetIdentity(); - - // HF sizes needs an X/Y swap - // NOPE.. not anymore - int32 HoudiniXSize = HeightfieldVolumeInfo.YLength; - int32 HoudiniYSize = HeightfieldVolumeInfo.XLength; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - // Test for potential special cases... - // Just print a warning for now - if (HeightfieldVolumeInfo.MinX != 0) - HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min X is not zero.")); - - if (HeightfieldVolumeInfo.MinY != 0) - HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min Y is not zero.")); - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to uint16 using doubles to get the maximum precision during the conversion - //-------------------------------------------------------------------------------------------------- - - FTransform CurrentVolumeTransform = HeightfieldVolumeInfo.Transform; - - // The ZRange in Houdini (in m) - double MeterZRange = (double)(FloatMax - FloatMin); - - // The corresponding unreal digit range (as unreal uses uint16, max is 65535) - // We may want to not use the full range in order to be able to sculpt the landscape past the min/max values after. - const double dUINT16_MAX = (double)UINT16_MAX; - double DigitZRange = 49152.0; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution) - DigitZRange = dUINT16_MAX - 1.0; - - // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down - double DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); - - // The factor used to convert from Houdini's ZRange to the desired digit range - double ZSpacing = (MeterZRange != 0.0) ? (DigitZRange / MeterZRange) : 0.0; - - // Changes these values if the user wants to loose a lot of precision - // just to keep the same transform as the landscape input - bool bUseDefaultUE4Scaling = false; - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) - bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; - - bUseDefaultUE4Scaling |= bOverrideZScale; - - if (bUseDefaultUE4Scaling) - { - //Check that our values are compatible with UE4's default scale values - if (FloatMin < -256.0f || FloatMin > 256.0f || FloatMax < -256.0f || FloatMax > 256.0f) - { - // Warn the user that the landscape conversion will have issues - // invite him to change that setting - HOUDINI_LOG_WARNING( - TEXT("The heightfield's min and max height values are too large for being used with the \"Use Default UE4 scaling\" option.\n \ - The generated Heightfield will likely be incorrectly converted to landscape unless you disable that option in the project settings and recook the asset.")); - } - - DigitZRange = dUINT16_MAX - 1.0; - DigitCenterOffset = 0; - - // Default unreal landscape scaling is -256m:256m at Scale = 100, - // We need to apply the scale back, and swap Y/Z axis - FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f * CustomZScale/100.f; - FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f * CustomZScale/100.f; - - MeterZRange = (double)(FloatMax - FloatMin); - - ZSpacing = ((double)DigitZRange) / MeterZRange; - } - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] DigitCenterOffset: %f"), DigitZRange); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] DigitZRange: %f"), DigitZRange); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] MeterZRange: %f"), MeterZRange); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] ZSpacing: %f"), ZSpacing); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Volume YScale: %f"), CurrentVolumeTransform.GetScale3D().Y); - - // Converting the data from Houdini to Unreal - // For correct orientation in unreal, the point matrix has to be transposed. - IntHeightData.SetNumUninitialized(SizeInPoints); - - int32 nUnreal = 0; - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y - int32 nHoudini = nY + nX * HoudiniYSize; - - // Get the double values in [0 - ZRange] - double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; - - // Then convert it to [0 - DesiredRange] and center it - DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; - IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); - } - } - - //-------------------------------------------------------------------------------------------------- - // 2. Resample / Pad the int data so that if fits unreal size requirements - //-------------------------------------------------------------------------------------------------- - - // UE has specific size requirements for landscape, - // so we might need to pad/resample the heightfield data - FVector LandscapeResizeFactor = FVector::OneVector; - FVector LandscapePositionOffsetInPixels = FVector::ZeroVector; - if (!NoResize) - { - // Try to resize the data - if (!FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( - IntHeightData, - HoudiniXSize, HoudiniYSize, FinalXSize, FinalYSize, - LandscapeResizeFactor, LandscapePositionOffsetInPixels)) - return false; - } - - //-------------------------------------------------------------------------------------------------- - // 3. Calculating the proper transform for the landscape to be sized and positionned properly - //-------------------------------------------------------------------------------------------------- - - // Scale: - // Calculating the equivalent scale to match Houdini's Terrain Size in Unreal - FVector LandscapeScale; - - // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing - // Swap Y/Z axis from H to UE - LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; - LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Z * 2.0f; - - // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini - // Unreal has a default Z range is 512m for a scale of a 100% - LandscapeScale.Z = (float)((double)(dUINT16_MAX / DigitZRange) * MeterZRange / 512.0); - if (bUseDefaultUE4Scaling) - { - // Swap Y/Z axis from H to UE - LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Y * 2.0f; - } - LandscapeScale *= 100.f; - - // If the data was resized and not expanded, we need to modify the landscape's scale - LandscapeScale *= LandscapeResizeFactor; - - // Don't allow a zero scale, as this results in divide by 0 operations in FMatrix::InverseFast in the landscape component. - if (FMath::IsNearlyZero(LandscapeScale.Z)) - LandscapeScale.Z = 1.0f; - - // We'll use the position from Houdini, but we will need to offset the Z Position to center the - // values properly as the data has been offset by the conversion to uint16 - FVector LandscapePosition = CurrentVolumeTransform.GetLocation(); - //LandscapePosition.Z = 0.0f; - - // We need to calculate the position offset so that Houdini and Unreal have the same Zero position - // In Unreal, zero has a height value of 32768. - // These values are then divided by 128 internally, and then multiplied by the Landscape's Z scale - // ( DIGIT - 32768 ) / 128 * ZScale = ZOffset - - // We need the Digit (Unreal) value of Houdini's zero for the scale calculation - // ( float and int32 are used for this because 0 might be out of the landscape Z range! - // when using the full range, this would cause an overflow for a uint16!! ) - float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); - float ZOffset = -(HoudiniZeroValueInDigit - 32768.0f) / 128.0f * LandscapeScale.Z; - - LandscapePosition.Z += ZOffset; - - // If we have padded the data when resizing the landscape, we need to offset the position because of - // the added values on the topLeft Corner of the Landscape - if (LandscapePositionOffsetInPixels != FVector::ZeroVector) - { - FVector LandscapeOffset = LandscapePositionOffsetInPixels * LandscapeScale; - LandscapeOffset.Z = 0.0f; - - LandscapePosition += LandscapeOffset; - } - - /* - FTransform TempTransform; - TempTransform.SetIdentity(); - { - // Houdini Pivot (center of the Landscape) - FVector HoudiniPivot = FVector((FinalXSize-1) * 100.0f / 2.0f, (FinalYSize-1) * 100.0f / 2.0f, 0.0f); - - // Center the landscape - FVector CenterLocation = LandscapePosition - HoudiniPivot; - - // Rotate the vector using the H rotation - // We need to compensate for the "default" HF Transform - FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); - Rotator.Yaw -= 90.0f; - Rotator.Roll += 90.0f; - FVector RotatedLocation = Rotator.RotateVector(CenterLocation); - - FQuat LandscapeRotation = FQuat(Rotator) * FQuat::Identity; - - // Return to previous origin - FVector Uncentered = RotatedLocation + HoudiniPivot; - TempTransform = FTransform(LandscapeRotation, Uncentered, LandscapeScale); - } - - LandscapeTransform = TempTransform; - */ - - // We can now set the Landscape position - LandscapeTransform.SetLocation(LandscapePosition); - LandscapeTransform.SetScale3D(LandscapeScale); - - // Rotate the vector using the H rotation - FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); - // We need to compensate for the "default" HF Transform - Rotator.Yaw -= 90.0f; - Rotator.Roll += 90.0f; - - // Only rotate if the rotator is far from zero - if(!Rotator.IsNearlyZero()) - LandscapeTransform.SetRotation(FQuat(Rotator)); - - return true; -} - -template -TArray ResampleData(const TArray& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight) -{ - TArray Result; - Result.Empty(NewWidth * NewHeight); - Result.AddUninitialized(NewWidth * NewHeight); - - const float XScale = (float)(OldWidth - 1) / (NewWidth - 1); - const float YScale = (float)(OldHeight - 1) / (NewHeight - 1); - for (int32 Y = 0; Y < NewHeight; ++Y) - { - for (int32 X = 0; X < NewWidth; ++X) - { - const float OldY = Y * YScale; - const float OldX = X * XScale; - const int32 X0 = FMath::FloorToInt(OldX); - const int32 X1 = FMath::Min(FMath::FloorToInt(OldX) + 1, OldWidth - 1); - const int32 Y0 = FMath::FloorToInt(OldY); - const int32 Y1 = FMath::Min(FMath::FloorToInt(OldY) + 1, OldHeight - 1); - const T& Original00 = Data[Y0 * OldWidth + X0]; - const T& Original10 = Data[Y0 * OldWidth + X1]; - const T& Original01 = Data[Y1 * OldWidth + X0]; - const T& Original11 = Data[Y1 * OldWidth + X1]; - Result[Y * NewWidth + X] = FMath::BiLerp(Original00, Original10, Original01, Original11, FMath::Fractional(OldX), FMath::Fractional(OldY)); - } - } - - return Result; -} - -template -void ExpandData(T* OutData, const T* InData, - int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, - int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY) -{ - const int32 OldWidth = OldMaxX - OldMinX + 1; - const int32 OldHeight = OldMaxY - OldMinY + 1; - const int32 NewWidth = NewMaxX - NewMinX + 1; - const int32 NewHeight = NewMaxY - NewMinY + 1; - const int32 OffsetX = NewMinX - OldMinX; - const int32 OffsetY = NewMinY - OldMinY; - - for (int32 Y = 0; Y < NewHeight; ++Y) - { - const int32 OldY = FMath::Clamp(Y + OffsetY, 0, OldHeight - 1); - - // Pad anything to the left - const T PadLeft = InData[OldY * OldWidth + 0]; - for (int32 X = 0; X < -OffsetX; ++X) - { - OutData[Y * NewWidth + X] = PadLeft; - } - - // Copy one row of the old data - { - const int32 X = FMath::Max(0, -OffsetX); - const int32 OldX = FMath::Clamp(X + OffsetX, 0, OldWidth - 1); - FMemory::Memcpy(&OutData[Y * NewWidth + X], &InData[OldY * OldWidth + OldX], FMath::Min(OldWidth, NewWidth) * sizeof(T)); - } - - const T PadRight = InData[OldY * OldWidth + OldWidth - 1]; - for (int32 X = -OffsetX + OldWidth; X < NewWidth; ++X) - { - OutData[Y * NewWidth + X] = PadRight; - } - } -} - -template -TArray ExpandData(const TArray& Data, - int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, - int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY, - int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr) -{ - const int32 NewWidth = NewMaxX - NewMinX + 1; - const int32 NewHeight = NewMaxY - NewMinY + 1; - - TArray Result; - Result.Empty(NewWidth * NewHeight); - Result.AddUninitialized(NewWidth * NewHeight); - - ExpandData(Result.GetData(), Data.GetData(), - OldMinX, OldMinY, OldMaxX, OldMaxY, - NewMinX, NewMinY, NewMaxX, NewMaxY); - - // Return the padding so we can offset the terrain position after - if (PadOffsetX) - *PadOffsetX = NewMinX; - - if (PadOffsetY) - *PadOffsetY = NewMinY; - - return Result; -} - -bool -FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( - TArray& HeightData, - const int32& SizeX, const int32& SizeY, - const int32& NewSizeX, const int32& NewSizeY, - FVector& LandscapeResizeFactor, - FVector& LandscapePositionOffset) -{ - LandscapeResizeFactor = FVector::OneVector; - LandscapePositionOffset = FVector::ZeroVector; - - if (HeightData.Num() <= 4) - return false; - - if ((SizeX < 2) || (SizeY < 2)) - return false; - - // No need to resize anything - if (SizeX == NewSizeX && SizeY == NewSizeY) - return true; - - // Always resample, for now. We may enable padding functionality again at some point via - // a plugin setting. - bool bForceResample = true; - bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); - - TArray NewData; - if (!bResample) - { - // Expanding the data by padding - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - - const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; - const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; - - // Store the offset in pixel due to the padding - int32 PadOffsetX = 0; - int32 PadOffsetY = 0; - - // Expanding the Data - NewData = ExpandData( - HeightData, 0, 0, SizeX - 1, SizeY - 1, - -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1, - &PadOffsetX, &PadOffsetY); - - // We will need to offset the landscape position due to the value added by the padding - LandscapePositionOffset.X = (float)PadOffsetX; - LandscapePositionOffset.Y = (float)PadOffsetY; - - // Notify the user that the data was padded - HOUDINI_LOG_WARNING( - TEXT("Landscape data was padded from ( %d x %d ) to ( %d x %d )."), - SizeX, SizeY, NewSizeX, NewSizeY); - } - else - { - // Resampling the data - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - NewData = ResampleData(HeightData, SizeX, SizeY, NewSizeX, NewSizeY); - - // The landscape has been resized, we'll need to take that into account when sizing it - LandscapeResizeFactor.X = (float)SizeX / (float)NewSizeX; - LandscapeResizeFactor.Y = (float)SizeY / (float)NewSizeY; - LandscapeResizeFactor.Z = 1.0f; - - // Notify the user if the heightfield data was resized - HOUDINI_LOG_WARNING( - TEXT("Landscape data was resized from ( %d x %d ) to ( %d x %d )."), - SizeX, SizeY, NewSizeX, NewSizeY); - } - - // Replaces Old data with the new one - HeightData = NewData; - - return true; -} - - -bool -FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( - const int32& HoudiniSizeX, const int32& HoudiniSizeY, - int32& UnrealSizeX, int32& UnrealSizeY, - int32& NumSectionsPerComponent, int32& NumQuadsPerSection) -{ - if ((HoudiniSizeX < 2) || (HoudiniSizeY < 2)) - return false; - - NumSectionsPerComponent = 1; - NumQuadsPerSection = 1; - UnrealSizeX = -1; - UnrealSizeY = -1; - - // Unreal's default sizes - int32 SectionSizes[] = { 7, 15, 31, 63, 127, 255 }; - int32 NumSections[] = { 1, 2 }; - - // Component count used to calculate the final size of the landscape - int32 ComponentsCountX = 1; - int32 ComponentsCountY = 1; - - // Lambda for clamping the number of component in X/Y - auto ClampLandscapeSize = [&]() - { - // Max size is either whole components below 8192 verts, or 32 components - ComponentsCountX = FMath::Clamp(ComponentsCountX, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); - ComponentsCountY = FMath::Clamp(ComponentsCountY, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); - }; - - // Try to find a section size and number of sections that exactly matches the dimensions of the heightfield - bool bFoundMatch = false; - for (int32 SectionSizesIdx = UE_ARRAY_COUNT(SectionSizes) - 1; SectionSizesIdx >= 0; SectionSizesIdx--) - { - for (int32 NumSectionsIdx = UE_ARRAY_COUNT(NumSections) - 1; NumSectionsIdx >= 0; NumSectionsIdx--) - { - int32 ss = SectionSizes[SectionSizesIdx]; - int32 ns = NumSections[NumSectionsIdx]; - - if (((HoudiniSizeX - 1) % (ss * ns)) == 0 && ((HoudiniSizeX - 1) / (ss * ns)) <= 32 && - ((HoudiniSizeY - 1) % (ss * ns)) == 0 && ((HoudiniSizeY - 1) / (ss * ns)) <= 32) - { - bFoundMatch = true; - NumQuadsPerSection = ss; - NumSectionsPerComponent = ns; - ComponentsCountX = (HoudiniSizeX - 1) / (ss * ns); - ComponentsCountY = (HoudiniSizeY - 1) / (ss * ns); - ClampLandscapeSize(); - break; - } - } - if (bFoundMatch) - { - break; - } - } - - if (!bFoundMatch) - { - // if there was no exact match, try increasing the section size until we encompass the whole heightmap - const int32 CurrentSectionSize = NumQuadsPerSection; - const int32 CurrentNumSections = NumSectionsPerComponent; - for (int32 SectionSizesIdx = 0; SectionSizesIdx < UE_ARRAY_COUNT(SectionSizes); SectionSizesIdx++) - { - if (SectionSizes[SectionSizesIdx] < CurrentSectionSize) - { - continue; - } - - const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); - const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); - if (ComponentsX <= 32 && ComponentsY <= 32) - { - bFoundMatch = true; - NumQuadsPerSection = SectionSizes[SectionSizesIdx]; - ComponentsCountX = ComponentsX; - ComponentsCountY = ComponentsY; - ClampLandscapeSize(); - break; - } - } - } - - if (!bFoundMatch) - { - // if the heightmap is very large, fall back to using the largest values we support - const int32 MaxSectionSize = SectionSizes[UE_ARRAY_COUNT(SectionSizes) - 1]; - const int32 MaxNumSubSections = NumSections[UE_ARRAY_COUNT(NumSections) - 1]; - const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), MaxSectionSize * MaxNumSubSections); - const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), MaxSectionSize * MaxNumSubSections); - - bFoundMatch = true; - NumQuadsPerSection = MaxSectionSize; - NumSectionsPerComponent = MaxNumSubSections; - ComponentsCountX = ComponentsX; - ComponentsCountY = ComponentsY; - ClampLandscapeSize(); - } - - if (!bFoundMatch) - { - // Using default size just to not crash.. - UnrealSizeX = 512; - UnrealSizeY = 512; - NumSectionsPerComponent = 1; - NumQuadsPerSection = 511; - ComponentsCountX = 1; - ComponentsCountY = 1; - } - else - { - // Calculating the desired size - int32 QuadsPerComponent = NumSectionsPerComponent * NumQuadsPerSection; - - UnrealSizeX = ComponentsCountX * QuadsPerComponent + 1; - UnrealSizeY = ComponentsCountY * QuadsPerComponent + 1; - } - - return bFoundMatch; -} - -const FHoudiniGeoPartObject* -FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return nullptr; - - if (InOutput->GetHoudiniGeoPartObjects().Num() < 1) - return nullptr; - - for (const FHoudiniGeoPartObject& HGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type != EHoudiniPartType::Volume) - continue; - - FHoudiniVolumeInfo CurVolumeInfo = HGPO.VolumeInfo; - if (!CurVolumeInfo.Name.Contains("height")) - continue; - - // We're only handling single values for now - if (CurVolumeInfo.TupleSize != 1) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume has an invalide tuple size!")); - return nullptr; - } - - // Terrains always have a ZSize of 1. - if (CurVolumeInfo.ZLength != 1) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume's z length is not 1!")); - return nullptr; - } - - // Values should be float - if (!CurVolumeInfo.bIsFloat) - { - HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output, the height volume's data is not stored as floats!")); - return nullptr; - } - - return &HGPO; - } - - return nullptr; -} - -void -FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers) -{ - FoundLayers.Empty(); - - // Get node id - HAPI_NodeId HeightFieldNodeId = Heightfield.GeoId; - - // We need the tile attribute if the height has it - bool bParentHeightfieldHasTile = false; - int32 HeightFieldTile = -1; - { - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - TArray< int32 > TileValues; - - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HeightFieldNodeId, Heightfield.PartId, "tile", AttribInfoTile, TileValues); - - if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) - { - HeightFieldTile = TileValues[0]; - bParentHeightfieldHasTile = true; - } - } - - for (TArray< FHoudiniGeoPartObject >::TConstIterator IterLayers(InOutput->GetHoudiniGeoPartObjects()); IterLayers; ++IterLayers) - { - const FHoudiniGeoPartObject & HoudiniGeoPartObject = *IterLayers; - - HAPI_NodeId NodeId = HoudiniGeoPartObject.GeoId; - if (NodeId == -1 || NodeId != HeightFieldNodeId) - continue; - - if (bParentHeightfieldHasTile) - { - int32 CurrentTile = -1; - - HAPI_AttributeInfo AttribInfoTile; - FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); - TArray TileValues; - - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "tile", AttribInfoTile, TileValues); - - - if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) - { - CurrentTile = TileValues[0]; - } - - // Does this layer come from the same tile as the height? - if ((CurrentTile != HeightFieldTile) || (CurrentTile == -1)) - continue; - } - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, HoudiniGeoPartObject.PartId, - &CurrentVolumeInfo)) - continue; - - // We're interesting in anything but height data - FString CurrentVolumeName; - FHoudiniEngineString(CurrentVolumeInfo.nameSH).ToFString(CurrentVolumeName); - if (CurrentVolumeName.Contains("height")) - continue; - - // We're only handling single values for now - if (CurrentVolumeInfo.tupleSize != 1) - continue; - - // Terrains always have a ZSize of 1. - if (CurrentVolumeInfo.zLength != 1) - continue; - - // Values should be float - if (CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - continue; - - FoundLayers.Add(&HoudiniGeoPartObject); - } -} - -bool FHoudiniLandscapeTranslator::GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo) -{ - if (HGPO->Type != EHoudiniPartType::Volume) - return false; - - FHoudiniApi::VolumeInfo_Init(&VolumeInfo); - - HAPI_Result Result = FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - HGPO->GeoId, HGPO->PartId, &VolumeInfo); - - // We're only handling single values for now - if (VolumeInfo.tupleSize != 1) - return false; - - // Terrains always have a ZSize of 1. - if (VolumeInfo.zLength != 1) - return false; - - // Values must be float - if (VolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - return false; - - if ((VolumeInfo.xLength < 2) || (VolumeInfo.yLength < 2)) - return false; - - return true; -} - -bool -FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) -{ - OutFloatArr.Empty(); - OutFloatMin = 0.f; - OutFloatMax = 0.f; - - HAPI_VolumeInfo VolumeInfo; - if (!GetHoudiniHeightfieldVolumeInfo(HGPO, VolumeInfo)) - return false; - - const int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; - - OutFloatArr.SetNum(SizeInPoints); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetHeightFieldData( - FHoudiniEngine::Get().GetSession(), - HGPO->GeoId, HGPO->PartId, - OutFloatArr.GetData(), - 0, SizeInPoints), false); - - OutFloatMin = OutFloatArr[0]; - OutFloatMax = OutFloatMin; - - for (float NextFloatVal : OutFloatArr) - { - if (NextFloatVal > OutFloatMax) - { - OutFloatMax = NextFloatVal; - } - else if (NextFloatVal < OutFloatMin) - OutFloatMin = NextFloatVal; - } - - return true; -} - -bool -FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(const FHoudiniGeoPartObject& InHGPO, TArray& NonWeightBlendedLayerNames) -{ - // Check the attribute exists on primitive or detail - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; - if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_PRIM)) - Owner = HAPI_ATTROWNER_PRIM; - else if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_DETAIL)) - Owner = HAPI_ATTROWNER_DETAIL; - else - return false; - - // Get the values - HAPI_AttributeInfo AttribInfoNonWBLayer; - FHoudiniApi::AttributeInfo_Init(&AttribInfoNonWBLayer); - TArray AttribValues; - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, AttribInfoNonWBLayer, AttribValues, 1, Owner); - - if (AttribValues.Num() <= 0) - return false; - - // Convert them to FString - for (int32 Idx = 0; Idx < AttribValues.Num(); Idx++) - { - TArray Tokens; - AttribValues[Idx].ParseIntoArray(Tokens, TEXT(" "), true); - - for (int32 n = 0; n < Tokens.Num(); n++) - NonWeightBlendedLayerNames.AddUnique(Tokens[n]); - } - - return true; -} - -bool -FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) -{ - // Check the attribute exists on primitive or detail - HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; - if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_PRIM)) - Owner = HAPI_ATTROWNER_PRIM; - else if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_DETAIL)) - Owner = HAPI_ATTROWNER_DETAIL; - else - return false; - - // Check the value - HAPI_AttributeInfo AttribInfoUnitLayer; - FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer); - TArray< int32 > AttribValues; - FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, AttribInfoUnitLayer, AttribValues, 1, Owner); - - if (AttribValues.Num() > 0 && AttribValues[0] == 1) - return true; - - return false; -} - -bool -FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( - const TArray& FoundLayers, - const FHoudiniGeoPartObject& Heightfield, - const int32& LandscapeXSize, const int32& LandscapeYSize, - const TMap& GlobalMinimums, - const TMap& GlobalMaximums, - TArray& OutLayerInfos, - bool bIsUpdate, - const FHoudiniPackageParams& InTilePackageParams, - const FHoudiniPackageParams& InLayerPackageParams, - TArray& OutCreatedPackages - ) -{ - OutLayerInfos.Empty(); - - // Get the names of all non weight blended layers - TArray NonWeightBlendedLayerNames; - FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(Heightfield, NonWeightBlendedLayerNames); - - // Used for exporting layer info objects (per landscape layer) - FHoudiniPackageParams LayerPackageParams = InLayerPackageParams; - // Used for exporting textures (per landscape tile) - FHoudiniPackageParams TilePackageParams = InTilePackageParams; - - // For Debugging, do we want to export layers as textures? - bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; - - // Try to create all the layers - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - for (TArray::TConstIterator IterLayers(FoundLayers); IterLayers; ++IterLayers) - { - const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers; - if (!LayerGeoPartObject) - continue; - - if (!LayerGeoPartObject->IsValid()) - continue; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerGeoPartObject->AssetId)) - continue; - - if (bIsUpdate && !LayerGeoPartObject->bHasGeoChanged) - { - continue; - } - - TArray FloatLayerData; - float LayerMin = 0; - float LayerMax = 0; - if (!FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(LayerGeoPartObject, FloatLayerData, LayerMin, LayerMax)) - continue; - - // No need to create flat layers as Unreal will remove them afterwards.. - if (LayerMin == LayerMax) - continue; - - const FHoudiniVolumeInfo& LayerVolumeInfo = LayerGeoPartObject->VolumeInfo; - - // Get the layer's name - FString LayerName = LayerVolumeInfo.Name; - const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerName); - - TilePackageParams.ObjectName = InTilePackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; - LayerPackageParams.ObjectName = InLayerPackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; - - if (bExportTexture) - { - // Create a raw texture export of the layer on this tile - FString TextureName = TilePackageParams.ObjectName + "_raw"; - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - LayerVolumeInfo.YLength, // Y and X inverted?? why? - LayerVolumeInfo.XLength, - FloatLayerData, - LayerMin, - LayerMax); - } - - // Check if that landscape layer has been marked as unit (range in [0-1] - if (IsUnitLandscapeLayer(*LayerGeoPartObject)) - { - LayerMin = 0.0f; - LayerMax = 1.0f; - } - else - { - // We want to convert the layer using the global Min/Max - if (GlobalMaximums.Contains(LayerName)) - LayerMax = GlobalMaximums[LayerName]; - - if (GlobalMinimums.Contains(LayerName)) - LayerMin = GlobalMinimums[LayerName]; - } - - // Get the layer package path - // FString LayerNameString = FString::Printf(TEXT("%s_%d"), LayerString.GetCharArray().GetData(), (int32)LayerGeoPartObject->PartId); - // LayerNameString = UPackageTools::SanitizePackageName(LayerNameString); - - // Build an object name for the current layer - LayerPackageParams.SplitStr = SanitizedLayerName; - - // Creating the ImportLayerInfo and LayerInfo objects - FLandscapeImportLayerInfo ImportLayerInfo(*LayerName); - - // See if the user has assigned a layer info object via attribute - UPackage * Package = nullptr; - ULandscapeLayerInfoObject* LayerInfo = GetLandscapeLayerInfoForLayer(*LayerGeoPartObject, *LayerName); - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - // No assignment, try to find or create a landscape layer info object for that layer - LayerInfo = FindOrCreateLandscapeLayerInfoObject(LayerName, LayerPackageParams.GetPackagePath(), LayerPackageParams.GetPackageName(), Package); - } - - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - continue; - } - - // Convert the float data to uint8 - // HF masks need their X/Y sizes swapped - if (!FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( - FloatLayerData, LayerVolumeInfo.YLength, LayerVolumeInfo.XLength, - LayerMin, LayerMax, - LandscapeXSize, LandscapeYSize, - ImportLayerInfo.LayerData)) - continue; - - // We will store the data used to convert from Houdini values to int in the DebugColor - // This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards... - // R = Min, G = Max, B = Spacing, A = ? - LayerInfo->LayerUsageDebugColor.R = LayerMin; - LayerInfo->LayerUsageDebugColor.G = LayerMax; - LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; - LayerInfo->LayerUsageDebugColor.A = PI; - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers] Processing layer: %s"), *(LayerName)); - - // Visibility are by default non weight blended - if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) - LayerInfo->bNoWeightBlend = true; - else - LayerInfo->bNoWeightBlend = false; - - if (!bIsUpdate && Package && !Package->IsPendingKill()) - { - // Mark the package dirty... - Package->MarkPackageDirty(); - OutCreatedPackages.Add(Package); - } - - if (bExportTexture) - { - // Create an export of the converted data to texture - // FString TextureName = LayerString; - // if (LayerGeoPartObject->VolumeTileIndex >= 0) - // TextureName = TEXT("Tile") + FString::FromInt(LayerGeoPartObject->VolumeTileIndex) + TEXT("_") + LayerString; - // TextureName += TEXT("_conv"); - - const FString TextureName = TilePackageParams.ObjectName + TEXT("_conv"); - - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - LandscapeXSize, LandscapeYSize, - ImportLayerInfo.LayerData); - } - - // See if there is a physical material assigned via attribute for that landscape layer - UPhysicalMaterial* PhysMaterial = FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(*LayerGeoPartObject); - if (PhysMaterial && !PhysMaterial->IsPendingKill()) - { - LayerInfo->PhysMaterial = PhysMaterial; - } - - // Assign the layer info object to the import layer infos - ImportLayerInfo.LayerInfo = LayerInfo; - OutLayerInfos.Add(ImportLayerInfo); - } - - // Autosaving the layers prevents them for being deleted with the Asset - // Save the packages created for the LayerInfos - // Do this only for when creating layers. - /* - if (!bIsUpdate) - FEditorFileUtils::PromptForCheckoutAndSave(CreatedLandscapeLayerPackage, true, false); - */ - - return true; -} - -void -FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums, - bool bShouldEmptyMaps) -{ - if (bShouldEmptyMaps) - { - GlobalMinimums.Empty(); - GlobalMaximums.Empty(); - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray FloatData; - - for (const FHoudiniGeoPartObject& CurrentHeightfield: InHeightfieldArray) - { - // Get the current Heightfield GeoPartObject - if ( CurrentHeightfield.VolumeInfo.TupleSize != 1) - continue; - - // Retrieve node id from geo part. - HAPI_NodeId NodeId = CurrentHeightfield.GeoId; - if (NodeId == -1) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - &CurrentVolumeInfo)) - continue; - - // Retrieve the volume name. - FString VolumeName; - FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); - HoudiniEngineStringPartName.ToFString(VolumeName); - - bool bHasMinAttr = false; - bool bHasMaxAttr = false; - - // If this volume has an attribute defining a minimum value use it as is. - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - GlobalMinimums.Add(VolumeName, FloatData[0]); - bHasMinAttr = true; - } - } - - // If this volume has an attribute defining maximum value use it as is. - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - GlobalMaximums.Add(VolumeName, FloatData[0]); - bHasMaxAttr = true; - } - } - - if (!(bHasMinAttr && bHasMaxAttr)) - { - // Unreal's Z values are Y in Houdini - float ymin, ymax; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - nullptr, &ymin, nullptr, - nullptr, &ymax, nullptr, - nullptr, nullptr, nullptr)) - continue; - - - if (!bHasMinAttr) - { - // Read the global min value for this volume - if (!GlobalMinimums.Contains(VolumeName)) - { - GlobalMinimums.Add(VolumeName, ymin); - } - else - { - // Update the min if necessary - if (ymin < GlobalMinimums[VolumeName]) - GlobalMinimums[VolumeName] = ymin; - } - } - - if (!bHasMaxAttr) - { - // Read the global max value for this volume - if (!GlobalMaximums.Contains(VolumeName)) - { - GlobalMaximums.Add(VolumeName, ymax); - } - else - { - // Update the max if necessary - if (ymax > GlobalMaximums[VolumeName]) - GlobalMaximums[VolumeName] = ymax; - } - } - } - } -} - -void -FHoudiniLandscapeTranslator::GetLayersZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums) - -{ - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - TArray FloatData; - - for (const FHoudiniGeoPartObject& CurrentHeightfield : InHeightfieldArray) - { - // Get the current Heightfield GeoPartObject - if (CurrentHeightfield.VolumeInfo.TupleSize != 1) - continue; - - // Retrieve node id from geo part. - HAPI_NodeId NodeId = CurrentHeightfield.GeoId; - if (NodeId == -1) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - NodeId, CurrentHeightfield.PartId, - &CurrentVolumeInfo)) - continue; - - // Retrieve the volume name. - FString VolumeName; - FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); - HoudiniEngineStringPartName.ToFString(VolumeName); - - // Read the global min value for this volume - - float MinValue; - float MaxValue; - bool bHasMin = false; - bool bHasMax = false; - - if (!GlobalMinimums.Contains(VolumeName)) - { - // Extract min value - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - MinValue = FloatData[0]; - bHasMin = true; - } - } - if (!bHasMin) - { - if (VolumeName == TEXT("height")) - { - MinValue = -1000.f; - } - else - { - MinValue = 0.f; - } - } - GlobalMinimums.Add(VolumeName, MinValue); - } - - if (!GlobalMaximums.Contains(VolumeName)) - { - // Extract max value - FloatData.Empty(); - if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat(CurrentHeightfield.GeoId, CurrentHeightfield.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, AttributeInfo, FloatData, 1)) - { - if (FloatData.Num() > 0) - { - MaxValue = FloatData[0]; - bHasMax = true; - } - } - if (!bHasMax) - { - if (VolumeName == TEXT("height")) - { - MaxValue = 1000.f; - } - else - { - MaxValue = 1.f; - } - } - GlobalMaximums.Add(VolumeName, MaxValue); - } - - - - } -} - -bool -FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( - const TArray& FloatLayerData, - const int32& HoudiniXSize, const int32& HoudiniYSize, - const float& LayerMin, const float& LayerMax, - const int32& LandscapeXSize, const int32& LandscapeYSize, - TArray& LayerData, const bool& NoResize) -{ - // Convert the float data to uint8 - LayerData.SetNumUninitialized(HoudiniXSize * HoudiniYSize); - - // Calculating the factor used to convert from Houdini's ZRange to [0 255] - double LayerZRange = (LayerMax - LayerMin); - double LayerZSpacing = (LayerZRange != 0.0) ? (255.0 / (double)(LayerZRange)) : 0.0; - - int32 nUnrealIndex = 0; - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y - int32 nHoudini = nY + nX * HoudiniYSize; - - // Get the double values in [0 - ZRange] - double DoubleValue = (double)FMath::Clamp(FloatLayerData[nHoudini], LayerMin, LayerMax) - (double)LayerMin; - - // Then convert it to [0 - 255] - DoubleValue *= LayerZSpacing; - - LayerData[nUnrealIndex++] = FMath::RoundToInt(DoubleValue); - } - } - - // Finally, resize the data to fit with the new landscape size if needed - if (NoResize) - return true; - - return FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( - LayerData, HoudiniXSize, HoudiniYSize, - LandscapeXSize, LandscapeYSize); -} - -bool -FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( - TArray< uint8 >& LayerData, - const int32& SizeX, const int32& SizeY, - const int32& NewSizeX, const int32& NewSizeY) -{ - if ((NewSizeX == SizeX) && (NewSizeY == SizeY)) - return true; - - bool bForceResample = true; - bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); - - TArray NewData; - if (!bResample) - { - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - - const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; - const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; - - // Expanding the Data - NewData = ExpandData( - LayerData, - 0, 0, SizeX - 1, SizeY - 1, - -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1); - } - else - { - // Resampling the data - NewData.SetNumUninitialized(NewSizeX * NewSizeY); - NewData = ResampleData(LayerData, SizeX, SizeY, NewSizeX, NewSizeY); - } - - LayerData = NewData; - - return true; -} - -ALandscapeProxy * -FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( - const TArray< uint16 >& IntHeightData, - const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - const FTransform& TileTransform, - const FIntPoint& TileLocation, - const int32& XSize, - const int32& YSize, - const int32& NumSectionPerLandscapeComponent, - const int32& NumQuadsPerLandscapeSection, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhysicalMaterial, - const FString& LandscapeTileActorName, - LandscapeActorType ActorType, - ALandscape* SharedLandscapeActor, - UWorld* InWorld, - ULevel* InLevel, - FHoudiniPackageParams InPackageParams) -{ - if (!IsValid(InWorld)) - return nullptr; - - // if (!IsValid(MainLandscapeActor)) - // return nullptr; - - if ((XSize < 2) || (YSize < 2)) - return nullptr; - - if (IntHeightData.Num() != (XSize * YSize)) - return nullptr; - - if (!GEditor) - return nullptr; - - ALandscapeProxy* LandscapeTile = nullptr; - UPackage *CreatedPackage = nullptr; - - ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; - ALandscape* CachedLandscapeActor = nullptr; - - UWorld* NewWorld = nullptr; - FString MapFileName; - bool bBroadcastMaterialUpdate = false; - //... Create landscape tile ...// - { - // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos - if (ActorType == LandscapeActorType::LandscapeStreamingProxy) - { - CachedStreamingProxyActor = InWorld->SpawnActor(); - if (CachedStreamingProxyActor) - { - check(SharedLandscapeActor); - CachedStreamingProxyActor->PreEditChange(nullptr); - - // Update landscape tile properties from the main landscape actor. - CachedStreamingProxyActor->GetSharedProperties(SharedLandscapeActor); - CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; - CachedStreamingProxyActor->bCastStaticShadow = false; - - LandscapeTile = CachedStreamingProxyActor; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not spawn ALandscapeStreamingProxy with name: %s"), *(LandscapeTileActorName) ); - return nullptr; - } - } - else - { - // Create a normal landscape actor - CachedLandscapeActor = InWorld->SpawnActor(); - if (CachedLandscapeActor) - { - CachedLandscapeActor->PreEditChange(nullptr); - CachedLandscapeActor->SetLandscapeGuid(FGuid::NewGuid()); - CachedLandscapeActor->LandscapeMaterial = LandscapeMaterial; - CachedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - CachedLandscapeActor->bCastStaticShadow = false; - bBroadcastMaterialUpdate = true; - LandscapeTile = CachedLandscapeActor; - } - } - } - - - if (!LandscapeTile) - return nullptr; - - // Only import non-visibility layers. Visibility will be handled explicitly. - TArray CustomImportLayerInfos; - for (const FLandscapeImportLayerInfo& LayerInfo : ImportLayerInfos) - { - if (LayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - continue; - CustomImportLayerInfos.Add(LayerInfo); - } - - // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. - - // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue - LandscapeTile->bCastStaticShadow = false; - - // TODO: Check me? - //if (LandscapePhsyicalMaterial) - // LandscapeTile->DefaultPhysMaterial = LandscapePhsyicalMaterial; - - // Setting the layer type here. - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - - TMap> HeightmapDataPerLayers; - TMap> MaterialLayerDataPerLayer; - HeightmapDataPerLayers.Add(FGuid(), IntHeightData); - TArray& MaterialImportLayerInfos = MaterialLayerDataPerLayer.Add(FGuid(), CustomImportLayerInfos); - - // Before we import new tile data, remove previous tile data that will be overlapped by the new tile. - TSet OverlappingComponents; - const int32 DestMinX = TileLocation.X; - const int32 DestMinY = TileLocation.Y; - const int32 DestMaxX = TileLocation.X + XSize - 1; - const int32 DestMaxY = TileLocation.Y + YSize - 1; - - ULandscapeInfo* LandscapeInfo = LandscapeTile->GetLandscapeInfo(); - - if (LandscapeInfo) - { - // If there is a preexisting LandscapeInfo object, check for overlapping components. - - // Shrink bounds by 1,1 to avoid GetComponentsInRegion picking up extra components on all sides due to the overlap between components - LandscapeInfo->GetComponentsInRegion(DestMinX+1, DestMinY+1, DestMaxX-1, DestMaxY-1, OverlappingComponents); - TSet StaleActors; - - for (ULandscapeComponent* Component : OverlappingComponents) - { - // Remove the overlapped component from the LandscapeInfo and then from - LandscapeInfo->Modify(); - - ALandscapeProxy* Proxy = Component->GetLandscapeProxy(); - if (!IsValid(Proxy)) - continue; - check(Proxy); - FIntRect Bounds = Proxy->GetBoundingRect(); - // If this landscape proxy has no more components left, remove it from the LandscapeInfo. - LandscapeInfo->UnregisterActor(Proxy); - Proxy->Destroy(); - } - - ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); - } - - // Import tile data - // The Import function will correctly compute the tile section locations. No need to set it explicitly. - // TODO: Verify this with world composition!! - - bool bIsStandalone = LandscapeTile->GetLandscapeActor() == LandscapeTile; - - // We set the actor transform and absolute section base before importing heightfield data. This allows us to - // use the correct (quad-space) blitting region without causing overlaps during import. - - // NOTE: The following SetAbsoluteSectionBase() calls are very important. This tells the Landscape system - // where on the landscape, in quad space, a specific tile is located. This is used by various - // mechanisms and tools that tie into the Landscape system such as World Composition and Landscape editor tools. - // If the Section Offset for a landscape tile is wrong, it will appear in the wrong place in the World Composition - // viewer. Worse than that, none of Landscape Editor tools will work properly because it won't be able to - // locate the correct Landscape component when calculating the "Landscape Component Key" for the given world position. - // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blitting functions use the - // same underlying Landscape Component Key calculation to find the correct landscape component to draw to. So if the - // Section Offsets are wrong ... all manner of chaos will follow. - // It is also super important to call ULandscapeInfo::RecreateLandscapeInfo() after change a LandscapeProxy's - // section offset in order to update the landscape's internal caches (more specifically the component keys, which - // are based on the section offsets) otherwise component key calculations won't work correctly. - - LandscapeTile->SetActorRelativeTransform(TileTransform); - LandscapeTile->SetAbsoluteSectionBase(TileLocation); - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Importing tile for actor: %s "), *(LandscapeTile->GetPathName())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Dest region: %d, %d -> %d, %d"), DestMinX, DestMinY, DestMaxX, DestMaxY); - - LandscapeTile->Import( - LandscapeTile->GetLandscapeGuid(), - DestMinX, DestMinY, DestMaxX, DestMaxY, - NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, - HeightmapDataPerLayers, NULL, - MaterialLayerDataPerLayer, ImportLayerType); - - - if (!LandscapeInfo) - { - LandscapeInfo = LandscapeTile->GetLandscapeInfo(); - } - - check(LandscapeInfo); - - // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so - // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo component keys. - // Only then are we able to "blit" the new alpha data into the correct place on the landscape. - - ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); - LandscapeTile->RecreateComponentsState(); - - // TODO: Verify whether we still need to manually set the LandscapeLightingLOD setting or whether - // calling PostEditChange() will properly fix the state. - - // Copied straight from UE source code to avoid crash after importing the landscape: - // automatically calculate a lighting LOD that won't crash lightmass (hopefully) - // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 - LandscapeTile->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); - - // ---------------------------------------------------- - // Update visibility layer - // ---------------------------------------------------- - - // Update the visibility mask / layer if we have any (TileImport does not update the visibility layer). - for (auto CurLayerInfo : ImportLayerInfos) - { - if (CurLayerInfo.LayerInfo && CurLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) - { - FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); - AlphaAccessor.SetData(DestMinX, DestMinY, DestMaxX, DestMaxY, CurLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); - } - } - - // ---------------------------------------------------- - // Rename the actor - // ---------------------------------------------------- - - // NOTE: The LandscapeProxy needs to be properly initialized before renaming (which is why the rename is taking - // place at the end) since the rename will trigger PostEditChange and can crash if the actor has not been - // correctly setup. - FHoudiniEngineUtils::SafeRenameActor(LandscapeTile, LandscapeTileActorName); - - if (!LandscapeTile->MarkPackageDirty()) - { - HOUDINI_LOG_WARNING(TEXT("Editor suppressed MarkPackageDirty() for landscape tile: %s"), *(LandscapeTile->GetPathName())); - } - -#if WITH_EDITOR - GEngine->BroadcastOnActorMoved(LandscapeTile); -#endif - - return LandscapeTile; -} - - -void -FHoudiniLandscapeTranslator::DestroyLandscape(ALandscape* Landscape) -{ - if (!IsValid(Landscape)) - return; - - ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); - if (!IsValid(Info)) - return; - - TArray Proxies = Info->Proxies; - for(ALandscapeStreamingProxy* Proxy : Proxies) - { - Info->UnregisterActor(Proxy); - Proxy->Destroy(); - } - Landscape->Destroy(); -} - - -void -FHoudiniLandscapeTranslator::CalculateTileLocation( - int32 NumSectionsPerComponent, - int32 NumQuadsPerSection, - const FTransform& TileTransformWS, - FHoudiniLandscapeReferenceLocation& RefLoc, - FTransform& OutLandscapeTransform, - FIntPoint& OutTileLocation) -{ - // IMPORTANT: Tile Location (Section Base) needs to be in multiples of the landscape component's quad size - const int32 ComponentSize = NumSectionsPerComponent * NumQuadsPerSection; - - OutLandscapeTransform = FTransform::Identity; - const FTransform TileSR = FTransform(TileTransformWS.GetRotation(), FVector::ZeroVector, TileTransformWS.GetScale3D()); - - const FVector BaseLoc = TileSR.InverseTransformPosition(TileTransformWS.GetLocation()); - - const FVector TileScale = TileTransformWS.GetScale3D(); - const float TileLocX = BaseLoc.X; // / TileScale.X; - const float TileLocY = BaseLoc.Y; // / TileScale.Y; - - if (!RefLoc.bIsCached) - { - // If there is no landscape reference location yet, calculate one now. - - // We cache this tile as a reference point for the other landscape tiles so that they can calculate - // section base offsets in a consistent manner, relative to this tile. - const float NearestMultipleX = FMath::RoundHalfFromZero(TileLocX / ComponentSize) * ComponentSize; - const float NearestMultipleY = FMath::RoundHalfFromZero(TileLocY / ComponentSize) * ComponentSize; - - RefLoc.SectionCoordX = FMath::RoundHalfFromZero(NearestMultipleX); - RefLoc.SectionCoordY = FMath::RoundHalfFromZero(NearestMultipleY); - RefLoc.TileLocationX = TileLocX; - RefLoc.TileLocationY = TileLocY; - } - - // Calculate the section coordinate for this tile - const float DeltaLocX = TileLocX - RefLoc.TileLocationX; - const float DeltaLocY = TileLocY - RefLoc.TileLocationY; - - const float DeltaCoordX = FMath::RoundHalfFromZero(DeltaLocX / ComponentSize) * ComponentSize; - const float DeltaCoordY = FMath::RoundHalfFromZero(DeltaLocY / ComponentSize) * ComponentSize; - - OutTileLocation.X = RefLoc.SectionCoordX + DeltaCoordX; - OutTileLocation.Y = RefLoc.SectionCoordY + DeltaCoordY; - - // Adjust landscape offset to compensate for tile location / section base shifting. - if (!RefLoc.bIsCached) - { - FVector Offset((TileLocX - OutTileLocation.X), (TileLocY - OutTileLocation.Y), BaseLoc.Z); - Offset = TileSR.TransformPosition(Offset); - - RefLoc.MainTransform = TileTransformWS; - RefLoc.MainTransform.SetTranslation(Offset); - // Reference locations are now fully cached. - RefLoc.bIsCached = true; - } - - OutLandscapeTransform = RefLoc.MainTransform; -} - - -void -FHoudiniLandscapeTranslator::GetLandscapeMaterials( - const FHoudiniGeoPartObject& InHeightHGPO, - UMaterialInterface*& OutLandscapeMaterial, - UMaterialInterface*& OutLandscapeHoleMaterial, - UPhysicalMaterial*& OutLandscapePhysicalMaterial) -{ - OutLandscapeMaterial = nullptr; - OutLandscapeHoleMaterial = nullptr; - OutLandscapePhysicalMaterial = nullptr; - - if (InHeightHGPO.Type != EHoudiniPartType::Volume) - return; - - TArray Materials; - HAPI_AttributeInfo AttribMaterials; - FHoudiniApi::AttributeInfo_Init(&AttribMaterials); - - // First, look for landscape material - { - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL, - AttribMaterials, Materials); - - // If the material attribute was not found, check the material instance attribute. - if (!AttribMaterials.exists) - { - Materials.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, - AttribMaterials, Materials); - } - - // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. - //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) - if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) - { - HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a point, primitive or detail attribute, ignoring attribute.")); - AttribMaterials.exists = false; - Materials.Empty(); - } - - if (AttribMaterials.exists && Materials.Num() > 0) - { - // Load the material - OutLandscapeMaterial = Cast(StaticLoadObject( - UMaterialInterface::StaticClass(), - nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); - } - } - - Materials.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribMaterials); - - // Then, for the hole_material - { - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - AttribMaterials, Materials); - - // If the material attribute was not found, check the material instance attribute. - if (!AttribMaterials.exists) - { - Materials.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InHeightHGPO.GeoId, InHeightHGPO.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE, - AttribMaterials, Materials); - } - - // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. - //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) - if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) - { - HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute.")); - AttribMaterials.exists = false; - Materials.Empty(); - } - - if (AttribMaterials.exists && Materials.Num() > 0) - { - // Load the material - OutLandscapeHoleMaterial = Cast< UMaterialInterface >(StaticLoadObject( - UMaterialInterface::StaticClass(), - nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); - } - } - - // Then for the physical material - OutLandscapePhysicalMaterial = GetLandscapePhysicalMaterial(InHeightHGPO); -} - -// Read the landscape component extent attribute from a heightfield -bool -FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( - const FHoudiniGeoPartObject& HoudiniGeoPartObject, - int32& MinX, int32& MaxX, - int32& MinY, int32& MaxY) -{ - // If we dont have minX, we likely dont have the others too - if (!FHoudiniEngineUtils::HapiCheckAttributeExists( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", HAPI_ATTROWNER_PRIM)) - return false; - - // Create an AttributeInfo - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfo); - - // Get MinX - TArray IntData; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MinX = IntData[0]; - - // Get MaxX - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_X", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MaxX = IntData[0]; - - // Get MinY - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MinY = IntData[0]; - - // Get MaxX - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_Y", AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - return false; - - if (IntData.Num() > 0) - MaxY = IntData[0]; - - return true; -} - -ULandscapeLayerInfoObject * -FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& InLayerName, const FString& InPackagePath, const FString& InPackageName, UPackage*& OutPackage) -{ - FString PackageFullName = InPackagePath + TEXT("/") + InPackageName; - - // See if package exists, if it does, reuse it - bool bCreatedPackage = false; - OutPackage = FindPackage(nullptr, *PackageFullName); - if (!OutPackage || OutPackage->IsPendingKill()) - { - // We need to create a new package - OutPackage = CreatePackage(*PackageFullName); - bCreatedPackage = true; - } - - if (!OutPackage || OutPackage->IsPendingKill()) - return nullptr; - - if (!OutPackage->IsFullyLoaded()) - OutPackage->FullyLoad(); - - ULandscapeLayerInfoObject* LayerInfo = nullptr; - if (!bCreatedPackage) - { - // See if we can load the layer info instead of creating a new one - LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), OutPackage, FName(*InPackageName)); - } - - if (!LayerInfo || LayerInfo->IsPendingKill()) - { - // Create a new LandscapeLayerInfoObject in the package - LayerInfo = NewObject(OutPackage, FName(*InPackageName), RF_Public | RF_Standalone /*| RF_Transactional*/); - - // Notify the asset registry - FAssetRegistryModule::AssetCreated(LayerInfo); - } - - if (LayerInfo && !LayerInfo->IsPendingKill()) - { - LayerInfo->LayerName = FName(*InLayerName); - - // Trigger update of the Layer Info - LayerInfo->PreEditChange(nullptr); - LayerInfo->PostEditChange(); - LayerInfo->MarkPackageDirty(); - - // Mark the package dirty... - OutPackage->MarkPackageDirty(); - } - - return LayerInfo; -} - -bool -FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax( - const TArray& AllOutputs, float& OutGlobalMin, float& OutGlobalMax) -{ - OutGlobalMin = 0.f; - OutGlobalMax = 0.f; - - for (const auto& CurrentOutput : AllOutputs) - { - if (!CurrentOutput) - continue; - - if (CurrentOutput->GetType() != EHoudiniOutputType::Landscape) - continue; - - const TArray& HGPOs = CurrentOutput->GetHoudiniGeoPartObjects(); - for (const FHoudiniGeoPartObject& CurrentHGPO : HGPOs) - { - if (CurrentHGPO.Type != EHoudiniPartType::Volume) - continue; - - if (!CurrentHGPO.VolumeInfo.Name.Contains("height")) - continue; - - // We're only handling single values for now - if (CurrentHGPO.VolumeInfo.TupleSize != 1) - continue; - - // Terrains always have a ZSize of 1. - if (CurrentHGPO.VolumeInfo.ZLength != 1) - continue; - - // Values should be float - if (!CurrentHGPO.VolumeInfo.bIsFloat) - continue; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CurrentHGPO.GeoId)) - continue; - - // Retrieve the VolumeInfo - HAPI_VolumeInfo CurrentVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHGPO.GeoId, CurrentHGPO.PartId, &CurrentVolumeInfo)) - continue; - - // Unreal's Z values are Y in Houdini - float yMin = OutGlobalMin, yMax = OutGlobalMax; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), - CurrentHGPO.GeoId, CurrentHGPO.PartId, - nullptr, &yMin, nullptr, - nullptr, &yMax, nullptr, - nullptr, nullptr, nullptr)) - continue; - - if (yMin < OutGlobalMin) - OutGlobalMin = yMin; - - if (yMax > OutGlobalMax) - OutGlobalMax = yMax; - } - - if (OutGlobalMin > OutGlobalMax) - { - OutGlobalMin = 0.f; - OutGlobalMax = 0.f; - } - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::EnableWorldComposition() -{ - HOUDINI_LOG_WARNING(TEXT("[FHoudiniLandscapeTranslator::EnableWorldComposition] We should never enable world composition from within the plugin.")); - // Get the world - UWorld* MyWorld = nullptr; - { - // We want to create the landscape in the landscape editor mode's world - FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); - MyWorld = EditorWorldContext.World(); - } - - if (!MyWorld) - return false; - - ULevel* CurrentLevel = MyWorld->GetCurrentLevel(); - - if (!CurrentLevel) - return false; - - AWorldSettings* WorldSettings = CurrentLevel->GetWorldSettings(); - if (!WorldSettings) - return false; - - // Enable world composition in WorldSettings - WorldSettings->bEnableWorldComposition = true; - - CurrentLevel->PostEditChange(); - - return true; -} - -bool -FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( - UObject* InObject, const TArray& InAllPropertyAttributes) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Iterate over the found Property attributes - int32 NumSuccess = 0; - for (const auto& CurrentPropAttribute : InAllPropertyAttributes) - { - // Update the current Property Attribute - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) - continue; - - // Success! - NumSuccess++; - FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); - FString ObjectName = InObject->GetName(); - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); - } - - return (NumSuccess > 0); -} - - -bool -FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(const FString& BaseName, ALandscapeProxy* Landscape) -{ - // We need to cache the input landscape to a file - if (!Landscape) - return false; - - ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Save Height data to file - //FString HeightSave = TEXT("/Game/HoudiniEngine/Temp/HeightCache.png"); - FString HeightSave = BaseName + TEXT("_height.png"); - LandscapeInfo->ExportHeightmap(HeightSave); - Landscape->ReimportHeightmapFilePath = HeightSave; - - // Save each layer to a file - for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) - { - FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); - //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) - continue; - - FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); - LandscapeInfo->ExportLayer(CurrentLayerInfo, LayerSave); - - // Update the file reimport path on the input landscape for this layer - LandscapeInfo->GetLayerEditorSettings(CurrentLayerInfo).ReimportLayerFilePath = LayerSave; - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy) -{ - if (!LandscapeProxy) - return false; - - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Restore Height data from the backup file - FString ReimportFile = LandscapeProxy->ReimportHeightmapFilePath; - if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, TEXT("height"))) - HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); - - // Restore each layer from the backup file - TArray< ULandscapeLayerInfoObject* > SourceLayers; - for (int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++) - { - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) - continue; - - FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); - ReimportFile = LandscapeProxy->EditorLayerSettings[LayerIndex].ReimportLayerFilePath; - - if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, CurrentLayerName, CurrentLayerInfo)) - HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); - - SourceLayers.Add(CurrentLayerInfo); - } - - // Iterate on the landscape info's layer to remove any layer that could have been added by Houdini - for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) - { - ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; - if (SourceLayers.Contains(CurrentLayerInfo)) - continue; - - // Delete the added layer - FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName; - LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName); - } - - return true; -} - - -bool -FHoudiniLandscapeTranslator::ImportLandscapeData( - ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject) -{ - // - // Code copied/edited from FEdModeLandscape::ImportData as we cannot access that function - // - if (!LandscapeInfo) - return false; - - bool IsHeight = LayerName.Equals(TEXT("height"), ESearchCase::IgnoreCase); - - int32 MinX, MinY, MaxX, MaxY; - if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - { - const FLandscapeFileResolution LandscapeResolution = { (uint32)(1 + MaxX - MinX), (uint32)(1 + MaxY - MinY) }; - - ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); - - if (IsHeight) - { - const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); - - if (!HeightmapFormat) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); - return false; - } - - FLandscapeFileResolution ImportResolution = { 0, 0 }; - - const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename); - - // display error message if there is one, and abort the import - if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); - return false; - } - - // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape - if (HeightmapInfo.PossibleResolutions.Num() > 1) - { - if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined")); - return false; - } - else - { - ImportResolution = LandscapeResolution; - } - } - - // display warning message if there is one and allow user to cancel - if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); - - // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape - // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is - if (HeightmapInfo.PossibleResolutions.Num() == 1) - { - ImportResolution = HeightmapInfo.PossibleResolutions[0]; - if (ImportResolution != LandscapeResolution) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); - } - - FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution); - if (ImportData.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); - return false; - } - - TArray Data; - if (ImportResolution != LandscapeResolution) - { - // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked - // so that reimports behave the same as the initial import :) - - const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; - const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; - - Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16)); - - ExpandData(Data.GetData(), ImportData.Data.GetData(), - 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, - -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); - } - else - { - Data = MoveTemp(ImportData.Data); - } - - //FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap")); - - FHeightmapAccessor HeightmapAccessor(LandscapeInfo); - HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData()); - } - else - { - // We're importing a Landscape layer - if (!LayerInfoObject || LayerInfoObject->IsPendingKill()) - return false; - - const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); - if (!WeightmapFormat) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); - return false; - } - - FLandscapeFileResolution ImportResolution = { 0, 0 }; - - const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName)); - - // display error message if there is one, and abort the import - if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); - return false; - } - - // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape - if (WeightmapInfo.PossibleResolutions.Num() > 1) - { - if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined")); - return false; - } - else - { - ImportResolution = LandscapeResolution; - } - } - - // display warning message if there is one and allow user to cancel - if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); - - // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape - // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is - if (WeightmapInfo.PossibleResolutions.Num() == 1) - { - ImportResolution = WeightmapInfo.PossibleResolutions[0]; - if (ImportResolution != LandscapeResolution) - HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); - } - - FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution); - - if (ImportData.ResultCode == ELandscapeImportResult::Error) - { - HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); - return false; - } - - TArray Data; - if (ImportResolution != LandscapeResolution) - { - // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked - // so that reimports behave the same as the initial import :) - const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; - const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; - - Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8)); - - ExpandData(Data.GetData(), ImportData.Data.GetData(), - 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, - -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); - } - else - { - Data = MoveTemp(ImportData.Data); - } - - //FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer")); - FAlphamapAccessor AlphamapAccessor(LandscapeInfo, LayerInfoObject); - AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None); - } - } - - return true; -} - -UTexture2D* -FHoudiniLandscapeTranslator::CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& InFloatBuffer, - const float& InMin, - const float& InMax) -{ - - // Convert the float values to uint8 - double Range = (double)InMax - (double)InMin; - TArray IntBuffer; - IntBuffer.SetNum(InFloatBuffer.Num()); - for(int32 i = 0; i < InFloatBuffer.Num(); i++) - { - double dNormalizedValue = ((double)InFloatBuffer[i] - (double)InMin) / (double)Range; - IntBuffer[i] = (uint8)(dNormalizedValue * 255.0); - } - - return FHoudiniLandscapeTranslator::CreateUnrealTexture( - InPackageParams, LayerName, InXSize, InYSize, IntBuffer); -} - -UTexture2D* -FHoudiniLandscapeTranslator::CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& IntBuffer) -{ - FHoudiniPackageParams MyPackageParams = InPackageParams; - MyPackageParams.ObjectName = LayerName; - MyPackageParams.PackageMode = EPackageMode::CookToTemp; - MyPackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - FString CreatedPackageName; - UPackage* Package = MyPackageParams.CreatePackageForObject(CreatedPackageName); - if (!Package || Package->IsPendingKill()) - return nullptr; - - // Create new texture object. - UTexture2D * Texture = NewObject(Package, UTexture2D::StaticClass(), *LayerName, RF_Public | RF_Standalone); - - // Add/Update meta information to package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *LayerName); - - /*// Texture Settings - Texture->PlatformData = new FTexturePlatformData(); - Texture->PlatformData->SizeX = InXSize; - Texture->PlatformData->SizeY = InYSize; - Texture->PlatformData->PixelFormat = PF_R8G8B8A8;*/ - - // Initialize texture source. - Texture->Source.Init(InXSize, InYSize, 1, 1, TSF_BGRA8); - - // Lock the texture. - uint8 * MipData = Texture->Source.LockMip(0); - - // Create base map. - uint8* DestPtr = nullptr; - uint32 SrcWidth = InXSize; - uint32 SrcHeight = InYSize; - const uint8 * SrcData = &IntBuffer[0]; - - for (uint32 y = 0; y < SrcHeight; y++) - { - DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; - - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth + x; - - *DestPtr++ = *(SrcData + DataOffset); // B greyscale, same value 3 times - *DestPtr++ = *(SrcData + DataOffset); // G - *DestPtr++ = *(SrcData + DataOffset); // R - - *DestPtr++ = 0xFF; // A to 1 - } - } - - // Unlock the texture. - Texture->Source.UnlockMip(0); - - // Texture creation parameters. - //Texture->SRGB = TextureParameters.bSRGB; - Texture->CompressionSettings = TC_Grayscale; - Texture->CompressionNoAlpha = true; - Texture->MipGenSettings = TMGS_NoMipmaps; - - // Set the Source Guid/Hash if specified. - /* - if ( TextureParameters.SourceGuidHash.IsValid() ) - { - Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); - } - */ - - // Updating Texture & mark it as unsaved - //Texture->AddToRoot(); - //Texture->UpdateResource(); - Package->MarkPackageDirty(); - - Texture->PostEditChange(); - - FString PathName = Texture->GetPathName(); - HOUDINI_LOG_MESSAGE(TEXT("Created texture when for %s in %s"), *LayerName, *PathName); - - return Texture; -} - -UPhysicalMaterial* -FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO) -{ - // See if we have assigned a physical material to this layer via attribute - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - TArray AttributeValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InLayerHGPO.GeoId, InLayerHGPO.PartId, - HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL, - AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) - return nullptr; - - if (AttributeValues.Num() > 0) - { - return LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); - } - - return nullptr; -} - -ULandscapeLayerInfoObject* -FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName) -{ - // See if we have assigned a landscape layer info object to this layer via attribute - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - - TArray AttributeValues; - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( - InLayerHGPO.GeoId, InLayerHGPO.PartId, - HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO, - AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM)) - return nullptr; - - if (AttributeValues.Num() > 0) - { - ULandscapeLayerInfoObject* FoundLayerInfo = LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); - if (!FoundLayerInfo || FoundLayerInfo->IsPendingKill()) - return nullptr; - - // The layer info's name must match this layer's name or Unreal will not like this! - if (!FoundLayerInfo->LayerName.IsEqual(InLayerName)) - { - FString NameStr = InLayerName.ToString(); - HOUDINI_LOG_WARNING(TEXT("Failed to use the assigned layer info object for %s by the unreal_landscape_layer_info attribute as the found layer info object's layer name does not match."), *NameStr); - } - - return FoundLayerInfo; - } - - return nullptr; -} - - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniLandscapeTranslator.h" + +#include "HoudiniMaterialTranslator.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniEngineString.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPackageParams.h" +#include "HoudiniStringResolver.h" +#include "HoudiniInput.h" + +#include "ObjectTools.h" +#include "FileHelpers.h" +#include "Editor.h" +#include "LandscapeLayerInfoObject.h" +#include "LandscapeStreamingProxy.h" +#include "LandscapeInfo.h" +#include "LandscapeEdit.h" +#include "AssetRegistryModule.h" +#include "PackageTools.h" +#include "PhysicalMaterials/PhysicalMaterial.h" +#include "UObject/UnrealType.h" + +#include "GameFramework/WorldSettings.h" +#include "Misc/Paths.h" +#include "Modules/ModuleManager.h" +#include "AssetToolsModule.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Factories/WorldFactory.h" +#include "Misc/Guid.h" +#include "Engine/LevelBounds.h" + +#include "HAL/IConsoleManager.h" +#include "Engine/AssetManager.h" +#include "Misc/ScopedSlowTask.h" + +#if WITH_EDITOR + #include "EditorLevelUtils.h" + #include "LandscapeEditorModule.h" + #include "LandscapeFileFormatInterface.h" +#endif + +static TAutoConsoleVariable CVarHoudiniEngineExportLandscapeTextures( + TEXT("HoudiniEngine.ExportLandscapeTextures"), + 0, + TEXT("If enabled, landscape layers and heightmap will be exported as textures in the temp directory when converting a Heightfield to a Landscape.\n") + TEXT("0: Disabled\n") + TEXT("1: Enabled\n") +); + +typedef FHoudiniEngineUtils FHUtils; + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +HOUDINI_LANDSCAPE_DEFINE_LOG_CATEGORY(); + +bool +FHoudiniLandscapeTranslator::CreateLandscape( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedOutputs, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* InWorld, // Persistent / root world for the landscape + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TSet& ClearedLayers, + TArray& OutCreatedPackages +) +{ + // Do the absolute minimum in order to determine which output mode we're dealing with (Temp or Editable Layers). + + if (!InOutput || InOutput->IsPendingKill()) + return false; + + // Get the height map. + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + if (!Heightfield) + return false; + + if (Heightfield->Type != EHoudiniPartType::Volume) + return false; + + const HAPI_NodeId GeoId = Heightfield->GeoId; + const HAPI_PartId PartId = Heightfield->PartId; + + // Check whether we're running in edit layer mode, or the usual temp mode + + TArray IntData; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + // --------------------------------------------- + // Attribute: unreal_landscape_output_mode + // --------------------------------------------- + IntData.Empty(); + int32 LandscapeOutputMode = 0; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_OUTPUT_MODE, + AttributeInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (IntData.Num() > 0) + { + LandscapeOutputMode = IntData[0]; + } + } + + switch (LandscapeOutputMode) + { + case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_EDITABLE_LAYER: + { + return OutputLandscape_EditableLayer( + InOutput, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + InAllInputLandscapes, + SharedLandscapeActorParent, + DefaultLandscapeActorPrefix, + InWorld, + LayerMinimums, + LayerMaximums, + LandscapeExtent, + LandscapeTileSizeInfo, + LandscapeReferenceLocation, + InPackageParams, + ClearedLayers, + OutCreatedPackages); + } + break; + case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_DEFAULT: + default: + { + return OutputLandscape_Temp(InOutput, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + InAllInputLandscapes, + SharedLandscapeActorParent, + DefaultLandscapeActorPrefix, + InWorld, + LayerMinimums, + LayerMaximums, + LandscapeExtent, + LandscapeTileSizeInfo, + LandscapeReferenceLocation, + InPackageParams, + OutCreatedPackages + ); + } + break; + } +} + +bool +FHoudiniLandscapeTranslator::OutputLandscape_Temp( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedOutputs, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* InWorld, // Persistent / root world for the landscape + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TArray& OutCreatedPackages +) +{ + check(LayerMinimums.Contains(TEXT("height"))); + check(LayerMaximums.Contains(TEXT("height"))); + + float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); + float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); + + if (!InOutput || InOutput->IsPendingKill()) + return false; + + // Get the height map. + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + if (!Heightfield) + return false; + + if (Heightfield->Type != EHoudiniPartType::Volume) + return false; + + const HAPI_NodeId GeoId = Heightfield->GeoId; + const HAPI_PartId PartId = Heightfield->PartId; + + // Construct the identifier of the Heightfield geo part. + FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); + HeightfieldIdentifier.PartName = Heightfield->PartName; + + FString NodeNameSuffix = GetActorNameSuffix(InPackageParams.PackageMode); + bool bAddLandscapeNameSuffix = true; + bool bAddLandscapeTileNameSuffix = true; + + const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + + TArray IntData; + TArray StrData; + // Output attributes will be stored on the Output object and will be used again during baking to determine + // where content should be baked to and what they should be named, etc. + // At the end of this function, the output attributes and tokens will be copied to the output object. + TMap OutputAttributes; + TMap OutputTokens; + FHoudiniAttributeResolver Resolver; + InPackageParams.UpdateTokensFromParams(InWorld, HoudiniAssetComponent, OutputTokens); + + bool bHasTile = Heightfield->VolumeTileIndex >= 0; + + // --------------------------------------------- + // Attribute: unreal_landscape_tile_actor_type, unreal_landscape_streaming_proxy (v1) + // --------------------------------------------- + // Determine the actor type for the tile + bool bCreateLandscapeStreamingProxy = false; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + LandscapeActorType TileActorType = LandscapeActorType::LandscapeActor; + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, + AttributeInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (IntData.Num() > 0) + { + TileActorType = static_cast(IntData[0]); + } + } + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY, + AttributeInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (IntData.Num() > 0 && IntData[0] != 0) + TileActorType = LandscapeActorType::LandscapeStreamingProxy; + } + + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_ACTOR_TYPE, FString::FromInt(static_cast(TileActorType))); + + // --------------------------------------------- + // Attribute: unreal_landscape_actor_name + // --------------------------------------------- + // Retrieve the name of the main Landscape actor to look for + FString SharedLandscapeActorName = DefaultLandscapeActorPrefix + "SharedLandscape"; // If this is an empty string, don't affirm a root landscape actor? + StrData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (StrData.Num() > 0 && !StrData[0].IsEmpty()) + SharedLandscapeActorName = StrData[0]; + } + + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); + + // --------------------------------------------- + // Attribute: unreal_level_path + // --------------------------------------------- + // FString LevelPath = bHasTile ? "{world}/Landscape/Tile{tile}" : "{world}/Landscape"; + FString LevelPath; + TArray LevelPaths; + if (FHoudiniEngineUtils::GetLevelPathAttribute(GeoId, PartId, LevelPaths, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + LevelPath = LevelPaths[0]; + } + if (!LevelPath.IsEmpty()) + OutputAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); + + // --------------------------------------------- + // Attribute: unreal_output_name + // --------------------------------------------- + FString LandscapeTileActorName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; + TArray AllOutputNames; + if (FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames, 0, 1)) + { + if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) + LandscapeTileActorName = AllOutputNames[0]; + } + OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); + + // --------------------------------------------- + // Attribute: unreal_bake_folder + // --------------------------------------------- + TArray AllBakeFolders; + if (FHoudiniEngineUtils::GetBakeFolderAttribute(GeoId, AllBakeFolders, PartId, 0, 1)) + { + FString BakeFolder; + if (AllBakeFolders.Num() > 0 && !AllBakeFolders[0].IsEmpty()) + BakeFolder = AllBakeFolders[0]; + OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_BAKE_FOLDER), BakeFolder); + } + + // Streaming proxy actors/tiles requires a "main" landscape actor + // that contains the shared landscape state. + bool bRequiresSharedLandscape = false; + if (TileActorType == LandscapeActorType::LandscapeStreamingProxy) + bRequiresSharedLandscape = true; + + // ---------------------------------- + // Inject landscape specific tokens + // ---------------------------------- + if (bHasTile) + { + const FString TileValue = FString::FromInt(Heightfield->VolumeTileIndex); + // Tile value needs to go into Output arguments to be available during the bake. + OutputTokens.Add(TEXT("tile"), TileValue); + } + + // ---------------------------------- + // Expand string arguments for various landscape naming aspects. + // ---------------------------------- + + // Update resolver attributes and tokens before we start resolving attributes. + Resolver.SetCachedAttributes(OutputAttributes); + Resolver.SetTokensFromStringMap(OutputTokens); + + SharedLandscapeActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, SharedLandscapeActorName); + SharedLandscapeActorName += NodeNameSuffix; + + LandscapeTileActorName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, LandscapeTileActorName); + LandscapeTileActorName += NodeNameSuffix; + + LevelPath = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPath); + + FString TileName = LandscapeTileActorName; + + // Note that relative level paths are always interpreted as relative to the default output directory (temp / bake). + // FString TilePackagePath = FPaths::Combine(DefaultOutputPath, LevelPath); + FString TilePackagePath = Resolver.ResolveFullLevelPath(); + + // This crashes UE if the package name does not resolve + //FString TileMapFileName = FPackageName::LongPackageNameToFilename(TilePackagePath, FPackageName::GetMapPackageExtension()); + + FText NotValidReason; + bool bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); + if (!bIsValidLongName) + { + // Try a more naive approach + TilePackagePath = FPaths::Combine(InPackageParams.BakeFolder, LevelPath); + bIsValidLongName = FPackageName::IsValidLongPackageName(TilePackagePath, false, &NotValidReason); + } + + if (!bIsValidLongName) + { + HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] TilePackagePath is not a valid long name. Reason: %s"), *(NotValidReason.ToString())); + return false; + } + + FString TileMapFileName; + if (!FPackageName::TryConvertLongPackageNameToFilename(TilePackagePath, TileMapFileName, FPackageName::GetMapPackageExtension())) + { + // Rather stop here than crash! + HOUDINI_LOG_ERROR(TEXT("[CreateOrUpdateLandscapeOutputHoudini] Failed to resolve the TilePackagePath: %s"), *(TilePackagePath)); + return false; + } + + // Find the package for both the world and the tile. + // The world should contain the main landscape actor while + // the tile will contain a Landscape, LandscapeProxy or LandscapeStreamingProxy depending on user settings. + + bool bTileisStreamingProxy = (TileActorType == LandscapeActorType::LandscapeStreamingProxy); + UWorld* TileWorld = nullptr; // World from which to spawn tile actor + ULevel* TileLevel = nullptr; // Level in which to spawn tile actor + ALandscapeProxy* TileActor = nullptr; // Spawned tile actor. + + // ---------------------------------- + // Update package parameters for this tile + // ---------------------------------- + + // NOTE: we don't manually inject a tile number in the object name. This should + // already be encoded in the TileName string. + FHoudiniPackageParams TilePackageParams = InPackageParams; + TilePackageParams.ObjectName = TileName; + + FHoudiniPackageParams LayerPackageParams = InPackageParams; + if (bRequiresSharedLandscape) + { + // Note that layers are shared amongst all the tiles for a given landscape. + LayerPackageParams.ObjectName = SharedLandscapeActorName; + } + else + { + // This landscape tile is a standalone landscape and should have its own material layers. + LayerPackageParams.ObjectName = TileName; + } + + // See if the current heightfield has an unreal_material or unreal_hole_material assigned to it + UMaterialInterface* LandscapeMaterial = nullptr; + UMaterialInterface* LandscapeHoleMaterial = nullptr; + UPhysicalMaterial* LandscapePhysicalMaterial = nullptr; + FHoudiniLandscapeTranslator::GetLandscapeMaterials(*Heightfield, InPackageParams, LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial); + + // Extract the float data from the Heightfield. + const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; + TArray FloatValues; + float FloatMin, FloatMax; + if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) + return false; + + // Heightfield conversions should always use the global float min/max + // since they need to be calculated externally, potentially across multiple tiles. + FloatMin = fGlobalMin; + FloatMax = fGlobalMax; + + // Get the Unreal landscape size + const int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; + const int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; + + if (!LandscapeTileSizeInfo.bIsCached) + { + // Calculate a landscape size info from this heightfield to be + // used by subsequent tiles on the same landscape + if (FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + LandscapeTileSizeInfo.UnrealSizeX, + LandscapeTileSizeInfo.UnrealSizeY, + LandscapeTileSizeInfo.NumSectionsPerComponent, + LandscapeTileSizeInfo.NumQuadsPerSection)) + { + LandscapeTileSizeInfo.bIsCached = true; + } + else + { + return false; + } + } + + const int32 UnrealTileSizeX = LandscapeTileSizeInfo.UnrealSizeX; + const int32 UnrealTileSizeY = LandscapeTileSizeInfo.UnrealSizeY; + const int32 NumSectionPerLandscapeComponent = LandscapeTileSizeInfo.NumSectionsPerComponent; + const int32 NumQuadsPerLandscapeSection = LandscapeTileSizeInfo.NumQuadsPerSection; + + // ---------------------------------------------------- + // Export of layer textures + // ---------------------------------------------------- + // Export textures, if enabled. Mostly used for debugging at the moment. + bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; + if (bExportTexture) + { + // Export raw height data to texture + FString TextureName = TilePackageParams.ObjectName + TEXT("_height_raw"); + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + FloatValues, + FloatMin, + FloatMax); + } + + // Look for all the layers/masks corresponding to the current heightfield. + TArray< const FHoudiniGeoPartObject* > FoundLayers; + FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); + + // Get the updated layers. + TArray LayerInfos; + + if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, + LayerMinimums, LayerMaximums, LayerInfos, false, + TilePackageParams, + LayerPackageParams, + OutCreatedPackages)) + return false; + + // Convert Houdini's heightfield data to Unreal's landscape data + TArray IntHeightData; + FTransform TileTransform; + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + FloatValues, VolumeInfo, + UnrealTileSizeX, UnrealTileSizeY, + FloatMin, FloatMax, + IntHeightData, TileTransform)) + return false; + + // ---------------------------------------------------- + // Property changes that we want to track + // ---------------------------------------------------- + + bool bModifiedLandscapeActor = false; + bool bModifiedSharedLandscapeActor = false; + bool bSharedLandscapeMaterialChanged = false; + bool bSharedLandscapeHoleMaterialChanged = false; + bool bSharedPhysicalMaterialChanged = false; + bool bTileLandscapeMaterialChanged = false; + bool bTileLandscapeHoleMaterialChanged = false; + bool bTilePhysicalMaterialChanged = false; + bool bCreatedMap = false; + bool bCreatedTileActor = false; + bool bHeightLayerDataChanged = false; + bool bCustomLayerDataChanged = false; + + // ---------------------------------------------------- + // Calculate Tile location and landscape offset + // ---------------------------------------------------- + FTransform LandscapeTransform; + FIntPoint TileLoc; + + // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate + // for any landscape shifts due to section base alignment offsets. + CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeReferenceLocation, LandscapeTransform, TileLoc); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Transform: %s"), *TileTransform.ToString()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape Transform: %s"), *LandscapeTransform.ToString()); + + + // ---------------------------------------------------- + // Find or create *shared* landscape + // ---------------------------------------------------- + + ALandscape* SharedLandscapeActor = nullptr; + bool bCreatedSharedLandscape = false; + + if (bRequiresSharedLandscape) + { + // Streaming proxy tiles always require a "shared landscape" that contains the + // various landscape properties to be shared amongst all the tiles. + AActor* FoundActor = nullptr; + SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, SharedLandscapeActorName, FoundActor); + + bool bIsValidSharedLandscape = IsValid(SharedLandscapeActor); + + if (bIsValidSharedLandscape) + { + // We have a target landscape. Check whether it is compatible with the Houdini volume. + ULandscapeInfo* LandscapeInfo = SharedLandscapeActor->GetLandscapeInfo(); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape: %s"), *(SharedLandscapeActor->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape with num proxies: %d"), LandscapeInfo->Proxies.Num()); + + if (!LandscapeExtent.bIsCached) + { + LandscapeInfo->FixupProxiesTransform(); + // Cache the landscape extents. Note that GetLandscapeExtent() will only take into account the currently loaded landscape tiles. + PopulateLandscapeExtents(LandscapeExtent, LandscapeInfo); + } + + bool bIsCompatible = IsLandscapeInfoCompatible( + LandscapeInfo, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Checking landscape compatibility ...")); + bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); + if (!bIsCompatible) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Shared landscape is incompatible. Cannot reuse.")); + // We can't resize the landscape in-place. We have to create a new one. + DestroyLandscape(SharedLandscapeActor); + SharedLandscapeActor = nullptr; + bIsValidSharedLandscape = false; + } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Existing shared landscape is compatible.")); + } + } + + if (!bIsValidSharedLandscape) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Create new shared landscape...")); + // Create and configure the main landscape actor. + // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos + SharedLandscapeActor = InWorld->SpawnActor(); + if (SharedLandscapeActor) + { + CreatedUntrackedOutputs.Add( SharedLandscapeActor ); + + // NOTE that shared landscape is always located at the origin, but not the tile actors. The + // tiles are properly transformed. + + // If we working with landscape tiles, this actor will become the "Main landscape" actor but + // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. + SharedLandscapeActor->bCanHaveLayersContent = false; + SharedLandscapeActor->ComponentSizeQuads = NumQuadsPerLandscapeSection*NumSectionPerLandscapeComponent; + SharedLandscapeActor->NumSubsections = NumSectionPerLandscapeComponent; + SharedLandscapeActor->SubsectionSizeQuads = NumQuadsPerLandscapeSection; + SharedLandscapeActor->SetLandscapeGuid( FGuid::NewGuid() ); + SharedLandscapeActor->bCastStaticShadow = false; + for (const auto& ImportLayerInfo : LayerInfos) + { + SharedLandscapeActor->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(ImportLayerInfo.LayerInfo)); + } + SharedLandscapeActor->CreateLandscapeInfo(); + bCreatedSharedLandscape = true; + + // NOTE: It is important to set Landscape materials BEFORE blitting layer data. For example, setting + // data in the visibility layer (on tiles) will have no effect until Landscape materials have been applied / processed. + SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + + // Ensure the landscape actor name and label matches `LandscapeActorName`. + FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, SharedLandscapeActorName); + + SharedLandscapeActor->MarkPackageDirty(); + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(InWorld->GetPathName()) ); + return false; + } + } + // else -- Reusing shared landscape + } + + if (SharedLandscapeActor) + { + // Ensure the existing landscape actor transform is correct. + SharedLandscapeActor->SetActorRelativeTransform(LandscapeTransform); + + bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; + bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; + if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + { + DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + } + + if (bSharedLandscapeMaterialChanged) + { + SharedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + + } + if (bSharedLandscapeHoleMaterialChanged) + { + SharedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + + } + + bSharedPhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? (SharedLandscapeActor->DefaultPhysMaterial != LandscapePhysicalMaterial) : false; + if (bSharedPhysicalMaterialChanged) + { + DoPreEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); + SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; + SharedLandscapeActor->ChangedPhysMaterial(); + } + } + + // ---------------------------------------------------- + // Find Landscape actor / tile + // ---------------------------------------------------- + + // Find an actor with the given name. The TileWorld and TileLevel returned should be + // used to spawn the new actor, if the actor itself could not be found. + //bool bCreatedPackage = false; + // TileActor = FindExistingLandscapeActor( + // InWorld, InOutput, ValidLandscapes, + // UnrealLandscapeSizeX, UnrealLandscapeSizeY, LandscapeTileActorName, + // LevelPath, TileWorld, TileLevel, bCreatedPackage); + + // Currently the Temp Cook mode is not concerned with creating packages. This will, at the time of writing, + // exclusively be dealt with during Bake mode so don't bother with searching / creating other packages. + + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (IsValid(HAC)) + { + TileWorld = HAC->GetWorld(); + TileLevel = HAC->GetComponentLevel(); + } + else + { + TileWorld = InWorld; + TileLevel = InWorld->PersistentLevel; + } + + check(TileWorld); + check(TileLevel); + + AActor* FoundActor = nullptr; + if (InPackageParams.PackageMode == EPackageMode::Bake) + { + // When baking, See if we can find any landscape / proxy actors for this tile in the TileLevel. + // If we find any actors that match the name but not the type, or the actors are pending kill, then + // rename them so that we can spawn new actors. + switch (TileActorType) + { + case LandscapeActorType::LandscapeActor: + TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); + break; + case LandscapeActorType::LandscapeStreamingProxy: + TileActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, LandscapeTileActorName, FoundActor); + break; + default: + TileActor = nullptr; + } + } + else + { + // In temp mode, only consider our previous output landscapes, + // or our input landscapes that have the "update input landscape" option enabled + ALandscapeProxy* FoundLandscapeProxy = nullptr; + + // Try to see if we have an input landscape that matches the size of the current HGPO + for (int nIdx = 0; nIdx < InputLandscapesToUpdate.Num(); nIdx++) + { + ALandscapeProxy* CurrentInputLandscape = InputLandscapesToUpdate[nIdx]; + if (!CurrentInputLandscape) + continue; + + if (SharedLandscapeActor && CurrentInputLandscape->GetLandscapeActor() != SharedLandscapeActor) + // This tile actor no longer associated with the current shared landscape + continue; + + ULandscapeInfo* CurrentInfo = CurrentInputLandscape->GetLandscapeInfo(); + if (!CurrentInfo) + continue; + + int32 InputMinX = 0; + int32 InputMinY = 0; + int32 InputMaxX = 0; + int32 InputMaxY = 0; + if (!LandscapeExtent.bIsCached) + { + PopulateLandscapeExtents(LandscapeExtent, CurrentInfo); + } + + if (!LandscapeExtent.bIsCached) + { + HOUDINI_LOG_WARNING(TEXT("Warning: Could not determine landscape extents. Cannot re-use input landscape actor.")); + continue; + } + + InputMinX = LandscapeExtent.MinY; + InputMinY = LandscapeExtent.MinY; + InputMaxX = LandscapeExtent.MaxX; + InputMaxY = LandscapeExtent.MaxY; + + // If the full size matches, we'll update that input landscape + bool SizeMatch = false; + if ((InputMaxX - InputMinX + 1) == UnrealTileSizeX && (InputMaxY - InputMinY + 1) == UnrealTileSizeY) + SizeMatch = true; + + // HF and landscape don't match, try another one + if (!SizeMatch) + continue; + + // Replace FoundLandscape by that input landscape + FoundLandscapeProxy = CurrentInputLandscape; + + // We've found a valid input landscape, remove it from the input array so we don't try to update it multiple times + InputLandscapesToUpdate.RemoveAt(nIdx); + break; + } + + if (!FoundLandscapeProxy) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Could not find input landscape to update. Searching output objects...")); + + // Try to see if we can reuse one of our previous output landscape. + // Keep track of the previous cook's landscapes + TMap& OldOutputObjects = InOutput->GetOutputObjects(); + for (auto& CurrentLandscape : OldOutputObjects) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentLandscape.Value.OutputObject); + if (!LandscapePtr) + continue; + + FoundLandscapeProxy = LandscapePtr->GetRawPtr(); + if (!FoundLandscapeProxy) + { + // We may need to manually load the object + //OldLandscapeProxy = LandscapePtr->GetSoftPtr().LoadSynchronous(); + FoundLandscapeProxy = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); + } + + if (!IsValid(FoundLandscapeProxy)) + continue; + + // We need to make sure that this landscape is not one of our input landscape + // This would happen if we were previously updating it, but just turned the option off + // In that case, the landscape would be in both our inputs and outputs, + // but with the "Update Input Data" option off + if (InAllInputLandscapes.Contains(FoundLandscapeProxy)) + { + FoundLandscapeProxy = nullptr; + continue; + } + + if (SharedLandscapeActor && FoundLandscapeProxy->GetLandscapeActor() != SharedLandscapeActor) + { + // This landscape proxy is no longer part of the shared landscape. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Output landscape proxy is no longer part of the landscape. Skipping")); + FoundLandscapeProxy = nullptr; + continue; + } + + // If we found a possible candidate, make sure that its size matches ours as we can only update a landscape tile of the same size + if (!IsLandscapeTileCompatible( + FoundLandscapeProxy, + UnrealTileSizeX-1, + UnrealTileSizeY-1, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection)) + { + FoundLandscapeProxy = nullptr; + continue; + } + + if (SharedLandscapeActor) + { + if (FoundLandscapeProxy->GetLandscapeGuid() != SharedLandscapeActor->GetLandscapeGuid()) + { + FoundLandscapeProxy = nullptr; + continue; + } + } + + // TODO: we probably need to do some more checks with tiled landscapes as well? + + // We found a valid Candidate! + if (FoundLandscapeProxy) + { + break; + } + } + } + + if (IsValid(FoundLandscapeProxy)) + { + TileActor = FoundLandscapeProxy; + if (TileActor->GetName() != LandscapeTileActorName) + { + // Ensure the TileActor is named correctly + FHoudiniEngineUtils::SafeRenameActor(TileActor, LandscapeTileActorName); + } + } + } + + // NOTE: We don't need to delete old landscape tiles (FoundActor != TileActor) here. That is an old + // output that should get cleaned up automatically. + + if (IsValid(TileActor)) + { + check(!(TileActor->IsPendingKill())); + + // ---------------------------------------------------- + // Check landscape compatibility + // ---------------------------------------------------- + + bool bIsCompatible = IsLandscapeTileCompatible( + TileActor, + UnrealTileSizeX-1, + UnrealTileSizeY-1, + NumSectionPerLandscapeComponent, + NumQuadsPerLandscapeSection); + + bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(TileActor, TileActorType); + + if (!bIsCompatible) + { + // Can't reuse this tile actor since the landscape dimensions doesn't match or the actor type has changed. + if (TileActor->IsA()) + { + // This landscape tile needs to be unregistered from the landscape info. + ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); + if (IsValid(LandscapeInfo)) + { + LandscapeInfo->UnregisterActor(TileActor); + } + } + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Incompatible tile actor. Destroying: %s"), *(TileActor->GetPathName())); + TileActor->Destroy(); + TileActor = nullptr; + } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape tile is compatible: %s"), *(TileActor->GetPathName())); + } + } + + // ---------------------------------------------------- + // Create or update landscape / tile. + // ---------------------------------------------------- + // Note that a single heightfield generated in Houdini can be treated + // as either a landscape tile (LandscapeStreamingProxy) or a standalone + // landscape (ALandscape). This determination is made purely from user specified + // attributes. No "clever logic" in here, please! + + ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; + ALandscape* CachedLandscapeActor = nullptr; + ULandscapeInfo *LandscapeInfo; + +#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Loc: %d, %d"), TileLoc.X, TileLoc.Y); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Size: %d, %d"), UnrealTileSizeX, UnrealTileSizeY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Quads/Section: %d"), NumQuadsPerLandscapeSection); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Sections/Component: %d"), NumSectionPerLandscapeComponent); +#endif + + if (!TileActor) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Creating new tile actor: %s"), *(LandscapeTileActorName)); + // Create a new Landscape tile in the TileWorld + TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( + IntHeightData, LayerInfos, TileTransform, TileLoc, + UnrealTileSizeX, UnrealTileSizeY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + LandscapeMaterial, LandscapeHoleMaterial, LandscapePhysicalMaterial, + LandscapeTileActorName, + TileActorType, + SharedLandscapeActor, + TileWorld, + TileLevel, + InPackageParams); + + if (!TileActor || !TileActor->IsValidLowLevel()) + return false; + + LandscapeInfo = TileActor->GetLandscapeInfo(); + + bCreatedTileActor = true; + bTileLandscapeMaterialChanged = true; + bTileLandscapeHoleMaterialChanged = true; + bTilePhysicalMaterialChanged = true; + bHeightLayerDataChanged = true; + bCustomLayerDataChanged = true; + } + else + { + LandscapeInfo = TileActor->GetLandscapeInfo(); + + // Always update the transform, even if the HGPO transform hasn't changed, + // If we change the number of tiles, or switch from outputting single tile to multiple, + // then its fairly likely that the unreal transform has changed even if the + // Houdini Transform remained the same + bool bUpdateTransform = !TileActor->GetActorTransform().Equals(TileTransform); + + // Update existing landscape / tile + if (SharedLandscapeActor && TileActorType == LandscapeActorType::LandscapeStreamingProxy) + { + TileActor->FixupSharedData(SharedLandscapeActor); + if (bUpdateTransform) + { + TileActor->SetAbsoluteSectionBase(TileLoc); + LandscapeInfo->FixupProxiesTransform(); + LandscapeInfo->RecreateLandscapeInfo(InWorld,true); + } + + // This is a tile with a shared landscape. + // Check whether the LandscapeActor changed. If so, update the proxy's shared properties. + CachedStreamingProxyActor = Cast(TileActor); + if (SharedLandscapeActor) + { + if (CachedStreamingProxyActor) + bModifiedLandscapeActor = CachedStreamingProxyActor->LandscapeActor != SharedLandscapeActor; + else + bModifiedLandscapeActor = true; + + if (bModifiedLandscapeActor) + { + CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; + // We need to force a state update through PostEditChangeProperty here in order to initialize + // since we're about to perform additional data updates on this tile. + DoPostEditChangeProperty(CachedStreamingProxyActor, "LandscapeActor"); + } + } + else + { + CachedStreamingProxyActor->LandscapeActor = nullptr; + } + + } + else + { + // This is a standalone tile / landscape actor. + if (bUpdateTransform) + { + TileActor->SetActorRelativeTransform(TileTransform); + TileActor->SetAbsoluteSectionBase(TileLoc); + } + } + + CachedLandscapeActor = TileActor->GetLandscapeActor(); + + ULandscapeInfo* PreviousInfo = TileActor->GetLandscapeInfo(); + if (!PreviousInfo) + return false; + + FIntRect BoundingRect = TileActor->GetBoundingRect(); + FIntPoint SectionOffset = TileActor->GetSectionBaseOffset(); + + // Landscape region to update + const int32 MinX = TileLoc.X; + const int32 MaxX = TileLoc.X + UnrealTileSizeX - 1; + const int32 MinY = TileLoc.Y; + const int32 MaxY = TileLoc.Y + UnrealTileSizeY - 1; + + // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. + // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools + // though the *Accessors do additional things like update normals and foliage. + + // Update height if it has been changed. + if (Heightfield->bHasGeoChanged) + { + // It is important to update the heightmap through HeightmapAccessor this since it will properly + // update normals and foliage. + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); + bHeightLayerDataChanged = true; + } + + // Update the layers on the landscape. + for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) + { + if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. + FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + + bCustomLayerDataChanged = true; + } + + bModifiedLandscapeActor = true; + } + + // ---------------------------------------------------- + // Update tile materials + // ---------------------------------------------------- + // TODO: These material updates can possibly be skipped if we have already performed this + // check on a SharedLandscape. + bTileLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (TileActor->GetLandscapeMaterial() != LandscapeMaterial) : false; + bTileLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (TileActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); + + if (bTileLandscapeMaterialChanged) + TileActor->LandscapeMaterial = LandscapeMaterial; + + if (bTileLandscapeHoleMaterialChanged) + TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + + bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; + if (bTilePhysicalMaterialChanged) + { + DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); + TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; + //TileActor->ChangedPhysMaterial(); + } + + // ---------------------------------------------------- + // Apply actor tags + // ---------------------------------------------------- + + // See if we have unreal_tag_ attribute + TArray Tags; + if (TileActor && FHoudiniEngineUtils::GetUnrealTagAttributes(GeoId, PartId, Tags)) + { + TileActor->Tags = Tags; + } + + // ---------------------------------------------------- + // Update actor states based on data updates + // ---------------------------------------------------- + // Based on ALandscape and ALandscapeStreamingProxy PostEditChangeProperty() implementations, + // effect appropriate state updates based on the property updates that was performed in + // the above code. + + if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + { + check(SharedLandscapeActor); + DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + } + + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + { + check(TileActor); + // Tile material changes are only processed if it wasn't already done for a shared + // landscape since the shared landscape should have already propagated the changes to associated proxies. + DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); + } + + if (bSharedPhysicalMaterialChanged) + { + check(SharedLandscapeActor); + DoPostEditChangeProperty(SharedLandscapeActor, "DefaultPhysMaterial"); + } + + if (bTilePhysicalMaterialChanged) + { + check(TileActor); + DoPostEditChangeProperty(TileActor, "DefaultPhysMaterial"); + } + + if (bModifiedSharedLandscapeActor) + { + SharedLandscapeActor->PostEditChange(); + } + + if (bModifiedLandscapeActor) + { + TileActor->PostEditChange(); + } + + { + FLandscapeEditDataInterface LandscapeEdit(TileActor->GetLandscapeInfo()); + LandscapeEdit.RecalculateNormals(); + } + + if (LandscapeInfo) + { + LandscapeInfo->RecreateLandscapeInfo(InWorld, true); + LandscapeInfo->RecreateCollisionComponents(); + } + + { + // Update UProperties + + // Apply detail attributes to both the Shared Landscape and the Landscape Tile actor + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + GeoId, PartId, + true, + INDEX_NONE, INDEX_NONE, INDEX_NONE, + PropertyAttributes)) + { + if (IsValid(TileActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); + } + if (IsValid(SharedLandscapeActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(SharedLandscapeActor, PropertyAttributes); + } + } + + // Apply point attributes only to the Shared Landscape and the Landscape Tile actor + PropertyAttributes.Empty(); + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + GeoId, PartId, + false, + 0, INDEX_NONE, 0, + PropertyAttributes)) + { + if (IsValid(TileActor)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(TileActor, PropertyAttributes); + } + } + } + + // Add objects to the HAC output. + SetLandscapeActorAsOutput( + InOutput, + InAllInputLandscapes, + OutputAttributes, + OutputTokens, + SharedLandscapeActor, + SharedLandscapeActorParent, + bCreatedSharedLandscape, + HeightfieldIdentifier, + TileActor, + InPackageParams.PackageMode); + +#if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) + if (LandscapeInfo) + { + int32 MinX, MinY, MaxX, MaxY; + LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape extent: %d, %d -> %d, %d"), MinX, MinY, MaxX, MaxY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Cached extent: %d, %d -> %d, %d"), LandscapeExtent.MinX, LandscapeExtent.MinY, LandscapeExtent.MaxX, LandscapeExtent.MaxY); + } + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Ending with num of output objects: %d"), InOutput->GetOutputObjects().Num()); +#endif + + return true; +} + +bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, UWorld* World, const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, FHoudiniPackageParams InPackageParams, + TSet& ClearedLayers, + TArray& OutCreatedPackages) +{ + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::OutputLandscape_EditableLayer] =======================================================================")); + + check(LayerMinimums.Contains(TEXT("height"))); + check(LayerMaximums.Contains(TEXT("height"))); + + float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); + float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); + + if (!InOutput || InOutput->IsPendingKill()) + return false; + + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (!IsValid(HAC)) + return false; + + // Get the height map. + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + if (!Heightfield) + return false; + + if (Heightfield->Type != EHoudiniPartType::Volume) + return false; + + const HAPI_NodeId GeoId = Heightfield->GeoId; + const HAPI_PartId PartId = Heightfield->PartId; + + TArray StrData; + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_name + // --------------------------------------------- + StrData.Empty(); + FString EditableLayerName; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME, + AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (StrData.Num() > 0) + { + EditableLayerName = StrData[0]; + } + } + if (EditableLayerName.IsEmpty()) + return false; + + // Construct the identifier of the Heightfield geo part. + FHoudiniOutputObjectIdentifier HeightfieldIdentifier(Heightfield->ObjectId, GeoId, PartId, "Heightfield"); + HeightfieldIdentifier.PartName = Heightfield->PartName; + + // Extract the float data from the Heightfield. + const FHoudiniVolumeInfo &VolumeInfo = Heightfield->VolumeInfo; + TArray FloatValues; + float FloatMin, FloatMax; + if (!GetHoudiniHeightfieldFloatData(Heightfield, FloatValues, FloatMin, FloatMax)) + return false; + + // Get the Unreal landscape size + const int32 HoudiniHeightfieldXSize = VolumeInfo.YLength; + const int32 HoudiniHeightfieldYSize = VolumeInfo.XLength; + + if (!LandscapeTileSizeInfo.bIsCached) + { + // Calculate a landscape size info from this heightfield to be + // used by subsequent tiles on the same landscape + if (FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + HoudiniHeightfieldXSize, + HoudiniHeightfieldYSize, + LandscapeTileSizeInfo.UnrealSizeX, + LandscapeTileSizeInfo.UnrealSizeY, + LandscapeTileSizeInfo.NumSectionsPerComponent, + LandscapeTileSizeInfo.NumQuadsPerSection)) + { + LandscapeTileSizeInfo.bIsCached = true; + } + else + { + return false; + } + } + + TMap OutputTokens; + FHoudiniAttributeResolver Resolver; + // Update resolver attributes and tokens before we start resolving attributes. + InPackageParams.UpdateTokensFromParams(World, HAC, OutputTokens); + + // --------------------------------------------- + // Attribute: unreal_landscape_actor_name + // --------------------------------------------- + // Retrieve the name of the main Landscape actor to look for + FString TargetLandscapeName = "Input0"; + StrData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (StrData.Num() > 0 && !StrData[0].IsEmpty()) + TargetLandscapeName = StrData[0]; + } + + Resolver.SetAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, TargetLandscapeName); + Resolver.SetTokensFromStringMap(OutputTokens); + TargetLandscapeName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, TargetLandscapeName); + + // --------------------------------------------- + // Find the landscape that we're targeting for output + // --------------------------------------------- + ALandscapeProxy* TargetLandscapeProxy = FindTargetLandscapeProxy(TargetLandscapeName, World, InAllInputLandscapes); + if (!IsValid(TargetLandscapeProxy)) + { + HOUDINI_LOG_WARNING(TEXT("Could not find landscape actor: %s"), *(TargetLandscapeName)); + return false; + } + ALandscape* TargetLandscape = TargetLandscapeProxy->GetLandscapeActor(); + check(TargetLandscape); + + ULandscapeInfo* TargetLandscapeInfo = TargetLandscapeProxy->GetLandscapeInfo(); + const FTransform TargetLandscapeTransform = TargetLandscape->GetActorTransform(); + + const float DestHeightScale = TargetLandscapeProxy->LandscapeActorToWorld().GetScale3D().Z; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Height Scale: %f"), DestHeightScale); + + // Create the layer if it doesn't exist + int32 EditLayerIndex = TargetLandscape->GetLayerIndex(FName(EditableLayerName)); + const FLandscapeLayer* TargetLayer = TargetLandscape->GetLayer(EditLayerIndex); + if (!TargetLayer) + { + // Create new layer + EditLayerIndex = TargetLandscape->CreateLayer(FName(EditableLayerName)); + TargetLayer = TargetLandscape->GetLayer(FName(EditableLayerName)); + } + + if (!TargetLayer) + { + HOUDINI_LOG_WARNING(TEXT("Could not find or create target layer: %s"), *(TargetLandscapeName)); + return false; + } + + { + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_after + // --------------------------------------------- + StrData.Empty(); + FString AfterLayerName; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER, + AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (StrData.Num() > 0) + { + AfterLayerName = StrData[0]; + } + } + if (!AfterLayerName.IsEmpty()) + { + // If we have an "after layer", move the output layer into position. + int32 NewLayerIndex = TargetLandscape->GetLayerIndex(FName(AfterLayerName)); + if (NewLayerIndex != INDEX_NONE && EditLayerIndex != NewLayerIndex) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Moving layer from %d to %d"), EditLayerIndex, NewLayerIndex); + if (NewLayerIndex < EditLayerIndex) + { + NewLayerIndex += 1; + } + TargetLandscape->ReorderLayer(EditLayerIndex, NewLayerIndex); + + // Ensure we have the correct layer/index + EditLayerIndex = TargetLandscape->GetLayerIndex(FName(EditableLayerName)); + TargetLayer = TargetLandscape->GetLayer(EditLayerIndex); + } + } + } + + // ---------------------------------------------------- + // Convert Heightfield data + // ---------------------------------------------------- + // Convert Houdini's heightfield data to Unreal's landscape data + TArray IntHeightData; + FTransform TileTransform; + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + FloatValues, VolumeInfo, + LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, + FloatMin, FloatMax, + IntHeightData, TileTransform, + false, true, DestHeightScale)) + return false; + + + // ---------------------------------------------------- + // Calculate Tile location and landscape offset + // ---------------------------------------------------- + FTransform SrcLandscapeTransform, HACTransform; + FIntPoint TileLoc; + + // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate + // for any landscape shifts due to section base alignment offsets. + CalculateTileLocation(LandscapeTileSizeInfo.NumSectionsPerComponent, LandscapeTileSizeInfo.NumQuadsPerSection, TileTransform, LandscapeReferenceLocation, SrcLandscapeTransform, TileLoc); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Landscape Transform: %s"), *(TargetLandscapeTransform.ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Tile Transform: %s"), *(TileTransform.ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Tile Size: %d, %d"), LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY); + + HACTransform = HAC->GetComponentTransform(); + SrcLandscapeTransform = HACTransform; + + // ---------------------------------------------------- + // Convert the tile coordinates to quad space on the target Landscape. + // ---------------------------------------------------- + FVector RelTileLoc = (TileTransform*HACTransform).GetLocation(); + RelTileLoc = TargetLandscapeTransform.InverseTransformPosition(RelTileLoc); + + TileLoc.X = FMath::RoundFromZero(RelTileLoc.X); + TileLoc.Y = FMath::RoundFromZero(RelTileLoc.Y); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Sections per component: %d"), (TargetLandscapeInfo->ComponentNumSubsections)); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Quads per component: %d"), (TargetLandscapeInfo->ComponentSizeQuads)); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Relative Tile Position: %s"), *(RelTileLoc.ToString())); + + FVector TileMin, TileMax; + TileMin.X = TileLoc.X; + TileMin.Y = TileLoc.Y; + TileMax.X = TileLoc.X + LandscapeTileSizeInfo.UnrealSizeX - 1; + TileMax.Y = TileLoc.Y + LandscapeTileSizeInfo.UnrealSizeY - 1; + TileMin.Z = TileMax.Z = 0.f; + + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Src Landscape Transform: %s"), *(SrcLandscapeTransform.ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Src Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); + + FTransform DestLandscapeTransform = TargetLandscapeProxy->LandscapeActorToWorld(); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Landscape Transform: %s"), *(DestLandscapeTransform.ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Actor Transform: %s"), *(TargetLandscape->GetTransform().ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); + + // NOTE: we don't manually inject a tile number in the object name. This should + // already be encoded in the TileName string. + FHoudiniPackageParams TilePackageParams = InPackageParams; + FHoudiniPackageParams LayerPackageParams = InPackageParams; + + TilePackageParams.ObjectName = TargetLandscapeName; + LayerPackageParams.ObjectName = TargetLandscapeName; + + // Look for all the layers/masks corresponding to the current heightfield. + TArray< const FHoudiniGeoPartObject* > FoundLayers; + FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Found %d output layers."), FoundLayers.Num()); + + // Get the updated layers. + TArray LayerInfos; + if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, + LayerMinimums, LayerMaximums, LayerInfos, false, + TilePackageParams, + LayerPackageParams, + OutCreatedPackages)) + return false; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Generated %d layer infos."), LayerInfos.Num()); + + // Collect existing layers on the landscape + TMap ExistingLayers; + int32 NumTargetLayers = TargetLandscape->EditorLayerSettings.Num(); + for(int32 LayerIndex = 0; LayerIndex < NumTargetLayers; LayerIndex++) + { + FLandscapeEditorLayerSettings& Settings = TargetLandscape->EditorLayerSettings[LayerIndex]; + if (!Settings.LayerInfoObj) + continue; + ExistingLayers.Add(Settings.LayerInfoObj->LayerName, LayerIndex); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Found existing landscape material layer: %s"), *(Settings.LayerInfoObj->LayerName.ToString())); + } + + bool bLayerHasChanged = false; + for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) + { + // Ensure weight blending is disabled for all layers coming from Houdini otherwise material layer outputs + // won't blend correctly on landscapes in Editable Layer mode. + InLayerInfo.LayerInfo->bNoWeightBlend = true; + + if (ExistingLayers.Contains(InLayerInfo.LayerName)) + { + + int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); + // NOTE: If we hot-swap existing Layer Info objects here, it leads to errors about landscape drawing resources that can't be released. + // For now, just modify any existing layers in place until we can figure out how to properly swap Layer Info objects. + + // // The landscape already contains this layer. Ensure it is pointing to the correct layer info object. + // bLayerHasChanged = TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj != InLayerInfo.LayerInfo; + // if (bLayerHasChanged) + // { + // HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Updating existing layer: %s"), *(InLayerInfo.LayerName.ToString())); + // TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj = InLayerInfo.LayerInfo; + // } + TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj->bNoWeightBlend = true; + } + else + { + // Landscape does not contain this layer. Add it. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Adding new layer: %s"), *(InLayerInfo.LayerName.ToString())); + TargetLandscape->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(InLayerInfo.LayerInfo)); + bLayerHasChanged = true; + } + } + + // Clear layers + if (!ClearedLayers.Contains(EditableLayerName)) + { + bool bClearLayer = false; + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_clear + // --------------------------------------------- + // Check whether we should clear the target edit layer. + TArray IntData; + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR, + AttributeInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (IntData.Num() > 0) + { + bClearLayer = IntData[0] != 0; + } + } + + if (bClearLayer) + { + if (TargetLayer) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Clearing layer heightmap: %s"), *EditableLayerName); + ClearedLayers.Add(EditableLayerName); + + // Clear the heightmap + TargetLandscape->ClearLayer(TargetLayer->Guid, nullptr, ELandscapeClearMode::Clear_Heightmap); + + // Clear the paint layers, but only the ones that are being output from Houdini. + for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) + { + if (!ExistingLayers.Contains(InLayerInfo.LayerName)) + continue; + + int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); + TargetLandscape->ClearPaintLayer(TargetLayer->Guid, InLayerInfo.LayerInfo); + } + } + } + } + + + { + // Scope the Edit Layer before we start drawing on ANY of the layers + FScopedSetLandscapeEditingLayer Scope(TargetLandscape, TargetLayer->Guid, [=] { TargetLandscape->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); }); + FLandscapeEditDataInterface LandscapeEdit(TargetLandscapeInfo); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Drawing heightmap..")); + // Draw Heightmap + FHeightmapAccessor HeightmapAccessor(TargetLandscapeInfo); + HeightmapAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, IntHeightData.GetData()); + + // Draw material layers on the landscape + // Update the layers on the landscape. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target has layers content: %d"), TargetLandscape->HasLayersContent()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] IsEditingLayer? %d"), TargetLandscape->GetEditingLayer().IsValid()); + + for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Trying to draw on layer: %s"), *(InLayerInfo.LayerName.ToString())); + + if (InLayerInfo.LayerInfo && InLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. + FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, InLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + if (!ExistingLayers.Contains(InLayerInfo.LayerName)) + continue; + + int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); + FLandscapeEditorLayerSettings& CurLayer = TargetLandscape->EditorLayerSettings[LayerIndex]; + // Draw on the current layer, if it is valid. + if (CurLayer.LayerInfoObj) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Drawing using Alpha accessor. Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); + FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, CurLayer.LayerInfoObj); + AlphaAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, InLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + } + + } // Landscape layer drawing scope + + // Only keep output the output object that corresponds to this layer. Everything else should be removed. + TMap OutputObjects = InOutput->GetOutputObjects(); + TSet StaleOutputs; + OutputObjects.GetKeys(StaleOutputs); + bool bFoundOutputObject = false; + for(auto& Entry : OutputObjects) + { + if (bFoundOutputObject) + continue; // We already have a matching layer output object. Anything else is stale. + + FHoudiniOutputObjectIdentifier& OutputId = Entry.Key; + FHoudiniOutputObject& Object = Entry.Value; + UHoudiniLandscapeEditLayer* EditLayer = Cast(Object.OutputObject); + if (!IsValid(EditLayer)) + continue; + StaleOutputs.Remove(OutputId); + } + + // Clean up stale outputs + for(FHoudiniOutputObjectIdentifier& StaleId : StaleOutputs) + { + FHoudiniOutputObject& OutputObject = OutputObjects.FindChecked(StaleId); + if (UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject)) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + + if (LandscapeProxy) + { + // We shouldn't destroy any input landscapes + if (!InAllInputLandscapes.Contains(LandscapeProxy)) + { + LandscapeProxy->Destroy(); + } + } + } + + OutputObjects.Remove(StaleId); + } + + // Update the output object + FHoudiniOutputObjectIdentifier OutputObjectIdentifier(Heightfield->ObjectId, GeoId, PartId, "EditableLayer"); + FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(OutputObjectIdentifier); + UHoudiniLandscapeEditLayer* LayerPtr = NewObject(InOutput); + LayerPtr->SetSoftPtr(TargetLandscape); + LayerPtr->LayerName = EditableLayerName; + OutputObj.OutputObject = LayerPtr; + // Editable layers doesn't currently require any attributes / tokens to be cached. + // OutputObj.CachedAttributes = OutputAttributes; + // OutputObj.CachedTokens = OutputTokens; + + return true; +} + + +bool +FHoudiniLandscapeTranslator::IsLandscapeInfoCompatible( + const ULandscapeInfo* LandscapeInfo, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection + ) +{ + if (!IsValid(LandscapeInfo)) + return false; + + + if (LandscapeInfo->ComponentNumSubsections != InNumSectionsPerComponent) + return false; + + if (LandscapeInfo->SubsectionSizeQuads != InNumQuadsPerSection) + return false; + + int32 MinX, MinY, MaxX, MaxY; + if (LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + { + const int32 NumComponentsX = (MaxX - MinX) / (InNumQuadsPerSection*InNumSectionsPerComponent); + const int32 NumComponentsY = (MaxY - MinY) / (InNumQuadsPerSection*InNumSectionsPerComponent); + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::IsLandscapeTileCompatible( + const ALandscapeProxy* TileActor, + const int32 InTileSizeX, + const int32 InTileSizeY, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection +) +{ + check(TileActor); + + // NOTE: We can't compare landscape extents here since the Houdini only knows about the size for single tile. + // and LandscapeInfo will only return extents for the *loaded* landscape tiles. + + // TODO: Add more robust checks to determine landscape compatibility. + + if (!IsLandscapeInfoCompatible(TileActor->GetLandscapeInfo(), InNumSectionsPerComponent, InNumQuadsPerSection)) + return false; + + const FIntRect Bounds = TileActor->GetBoundingRect(); + const FIntPoint Size = Bounds.Size(); + if (Size.X != InTileSizeX && Size.Y != InTileSizeY) + return false; + + return true; +} + + +bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, LandscapeActorType ActorType) +{ + if (!IsValid(Actor)) + return false; + + switch (ActorType) + { + case LandscapeActorType::LandscapeActor: + return Actor->IsA(); + break; + case LandscapeActorType::LandscapeStreamingProxy: + return Actor->IsA(); + break; + default: + break; + } + + return false; +} + +bool FHoudiniLandscapeTranslator::PopulateLandscapeExtents(FHoudiniLandscapeExtent& Extent, + const ULandscapeInfo* LandscapeInfo) +{ + if (LandscapeInfo->GetLandscapeExtent(Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY)) + { + Extent.ExtentsX = Extent.MaxX - Extent.MinX; + Extent.ExtentsY = Extent.MaxY - Extent.MinY; + Extent.bIsCached = true; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Cached extent: %d, %d -> %d, %d"), Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY); + + return true; + } + return false; +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage, + const EPackageMode& InPackageMode) +{ + if (InPackageMode == EPackageMode::Bake) + return FindExistingLandscapeActor_Bake(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); + else + return FindExistingLandscapeActor_Temp(InWorld, InOutput, ValidLandscapes, UnrealLandscapeSizeX, UnrealLandscapeSizeY, InActorName, InPackagePath, OutWorld, OutLevel, bCreatedPackage); +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + bCreatedPackage = false; + + // // Locate landscape proxy actor when running in baked mode + // AActor* FoundActor = nullptr; + ALandscapeProxy* OutActor = nullptr; + // OutActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); + // // OutActor = FHoudiniEngineUtils::FindActorInWorld(InWorld, FName(InActorName)); + // if (FoundActor && FoundActor != OutActor) + // FoundActor->Destroy(); // nuke it! + // + // if (OutActor) + // { + // // TODO: make sure that the found is actor is actually assigned to the level defined by package path. + // // If the found actor is not from that level, it should be moved there. + // + // OutWorld = OutActor->GetWorld(); + // OutLevel = OutActor->GetLevel(); + // } + // else + { + // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. + bool bActorInWorld = false; + const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + InWorld, + InPackagePath, + true, + OutWorld, + OutLevel, + bCreatedPackage, + bActorInWorld); + + if (!bResult) + { + return nullptr; + } + + // if (!bActorInWorld) + // { + // // The OutLevel is not present in the current world which means we might + // // still find the tile actor in OutWorld. + OutActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); + // } + } + + return OutActor; +} + + +ALandscapeProxy* FHoudiniLandscapeTranslator::FindTargetLandscapeProxy(const FString& ActorName, UWorld* World, + const TArray& LandscapeInputs) +{ + int32 InputIndex = INDEX_NONE; + if (ActorName.StartsWith(TEXT("Input"))) + { + // Extract the numeric value after 'Input'. + FString IndexStr; + ActorName.Split(TEXT("Input"), nullptr, &IndexStr); + if (IndexStr.IsNumeric()) + { + InputIndex = FPlatformString::Atoi(*IndexStr); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FindTargetLandscapeProxy] Extract index %d from actor name: %s"), InputIndex, *ActorName); + } + } + + if (InputIndex != INDEX_NONE) + { + if (!LandscapeInputs.IsValidIndex(InputIndex)) + return nullptr; + return LandscapeInputs[InputIndex]; + } + + return FHoudiniEngineUtils::FindActorInWorldByLabel(World, ActorName); +} + +ALandscapeProxy* +FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + ALandscapeProxy* OutActor = nullptr; + FString ActorName = InActorName + TEXT("_Temp"); + TMap& PrevCookObjects = InOutput->GetOutputObjects(); + + OutWorld = InWorld; + OutLevel = InWorld->PersistentLevel; + + bCreatedPackage = false; + + // Find Landscape proxy for output when running in Temp mode + for(auto& PrevObject : PrevCookObjects) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(PrevObject.Value.OutputObject); + if (!LandscapePtr) + continue; + + OutActor = LandscapePtr->GetRawPtr(); + if (!OutActor) + { + // We may need to manually load the object + OutActor = LandscapePtr->LandscapeSoftPtr.LoadSynchronous(); + } + + if (!OutActor) + continue; + + // If we were updating the input landscape before, but arent anymore, + // we could still find it here in the output, ignore them now as we're only looking for previous output + if (ValidLandscapes.Contains(OutActor)) + continue; + + if (OutActor->GetName() != ActorName) + // This is not the droid we're looking for + continue; + + if (OutActor->IsPendingKill()) + { + FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + "_pendingkill"); + continue; + } + + // If we found a possible candidate, make sure that its size matches ours + // as we can only update a landscape of the same size + ULandscapeInfo* PreviousInfo = OutActor->GetLandscapeInfo(); + if (PreviousInfo) + { + int32 PrevMinX = 0; + int32 PrevMinY = 0; + int32 PrevMaxX = 0; + int32 PrevMaxY = 0; + PreviousInfo->GetLandscapeExtent(PrevMinX, PrevMinY, PrevMaxX, PrevMaxY); + + if ((PrevMaxX - PrevMinX + 1) == UnrealLandscapeSizeX && (PrevMaxY - PrevMinY + 1) == UnrealLandscapeSizeY) + { + // The size matches, we can reuse the old landscape. + break; + } + else + { + // We can't reuse this actor. The dimensions does not match. + // We need to rename this actor in order to create a new one with the specified name. + FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + TEXT("_old") ); + OutActor = nullptr; + } + } + } + + return OutActor; +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor, + const EPackageMode InPackageMode) +{ + if (InPackageMode == EPackageMode::Bake) + return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); + else + return SetLandscapeActorAsOutput_Temp(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor) +{ + // We are in bake mode. No outputs to register / add here. + // Do nothing, for now. +} + +void +FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputTokens, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedSharedLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor) +{ + // The main landscape is a special case here. It cannot be registered with the + // output object here, since it is possibly shared by *multiple* outputs so + // we have to deal with the attached and cleanup of the actor manually. + if (bCreatedSharedLandscape && IsValid(SharedLandscapeActorParent)) + { + AttachActorToHAC(InOutput, SharedLandscapeActor); + } + + // TODO: The OutputObject cleanup being performed here should really be part of + // the output object itself (or at the very least be encapsulated in a reusable + // static function somewhere) so that individual output objects can be cleaned + // when necessary without having to duplicate code when its needed. + + // Cleanup any stale output objects + TMap& Outputs = InOutput->GetOutputObjects(); + TArray StaleOutputs; + for (auto& Elem : Outputs) + { + UHoudiniLandscapePtr* LandscapePtr = nullptr; + bool bIsStale = false; + + if (!(Elem.Key == Identifier)) + { + // Identifiers doesn't match so this is definitely a stale output. + StaleOutputs.Add(Elem.Key); + bIsStale = true; + } + + LandscapePtr = Cast(Elem.Value.OutputObject); + if (LandscapePtr) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + + if (LandscapeProxy) + { + // We shouldn't destroy any input landscape, + // or the landscape that we are currently trying to set as output.. + if (!InAllInputLandscapes.Contains(LandscapeProxy) && (LandscapeProxy != LandscapeActor)) + { + // This landscape proxy either doesn't match the landscape identifier + // or it doesn't match the actor we're about to output. Either way, + // get rid of it. + LandscapeProxy->Destroy(); + } + } + } + + if (bIsStale) + { + Elem.Value.OutputObject = nullptr; + } + } + + for (FHoudiniOutputObjectIdentifier& StaleOutput : StaleOutputs) + { + Outputs.Remove(StaleOutput); + } + + + // Send a landscape pointer back to the Output Object for this landscape tile. + FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); + UHoudiniLandscapePtr* LandscapePtr = NewObject(InOutput); + LandscapePtr->SetSoftPtr(LandscapeActor); + OutputObj.OutputObject = LandscapePtr; + OutputObj.CachedAttributes = OutputAttributes; + OutputObj.CachedTokens = OutputTokens; +} + + +bool +FHoudiniLandscapeTranslator::AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor) +{ + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (IsValid(HAC)) + { + InActor->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); + + return true; + } + return false; +} + + +FString +FHoudiniLandscapeTranslator::GetActorNameSuffix(const EPackageMode& InPackageMode) +{ + if(InPackageMode == EPackageMode::CookToTemp) + return "_Temp"; + else + return FString(); +} + +void +FHoudiniLandscapeTranslator::DoPreEditChangeProperty(UObject* Obj, FName PropertyName) +{ + Obj->PreEditChange(FindFProperty(Obj->GetClass(), PropertyName)); +} + +void +FHoudiniLandscapeTranslator::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) +{ + FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); + Obj->PostEditChangeProperty(Evt); +} + +bool +FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( + const TArray< float >& HeightfieldFloatValues, + const FHoudiniVolumeInfo& HeightfieldVolumeInfo, + const int32& FinalXSize, const int32& FinalYSize, + float FloatMin, float FloatMax, + TArray< uint16 >& IntHeightData, + FTransform& LandscapeTransform, + const bool NoResize, + const bool bOverrideZScale, + const float CustomZScale) +{ + IntHeightData.Empty(); + LandscapeTransform.SetIdentity(); + + // HF sizes needs an X/Y swap + // NOPE.. not anymore + int32 HoudiniXSize = HeightfieldVolumeInfo.YLength; + int32 HoudiniYSize = HeightfieldVolumeInfo.XLength; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + // Test for potential special cases... + // Just print a warning for now + if (HeightfieldVolumeInfo.MinX != 0) + HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min X is not zero.")); + + if (HeightfieldVolumeInfo.MinY != 0) + HOUDINI_LOG_WARNING(TEXT("Converting Landscape: heightfield's min Y is not zero.")); + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to uint16 using doubles to get the maximum precision during the conversion + //-------------------------------------------------------------------------------------------------- + + FTransform CurrentVolumeTransform = HeightfieldVolumeInfo.Transform; + + // The ZRange in Houdini (in m) + double MeterZRange = (double)(FloatMax - FloatMin); + + // The corresponding unreal digit range (as unreal uses uint16, max is 65535) + // We may want to not use the full range in order to be able to sculpt the landscape past the min/max values after. + const double dUINT16_MAX = (double)UINT16_MAX; + double DigitZRange = 49152.0; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution) + DigitZRange = dUINT16_MAX - 1.0; + + // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down + double DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); + + // The factor used to convert from Houdini's ZRange to the desired digit range + double ZSpacing = (MeterZRange != 0.0) ? (DigitZRange / MeterZRange) : 0.0; + + // Changes these values if the user wants to loose a lot of precision + // just to keep the same transform as the landscape input + bool bUseDefaultUE4Scaling = false; + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) + bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + + bUseDefaultUE4Scaling |= bOverrideZScale; + + if (bUseDefaultUE4Scaling) + { + //Check that our values are compatible with UE4's default scale values + if (FloatMin < -256.0f || FloatMin > 256.0f || FloatMax < -256.0f || FloatMax > 256.0f) + { + // Warn the user that the landscape conversion will have issues + // invite him to change that setting + HOUDINI_LOG_WARNING( + TEXT("The heightfield's min and max height values are too large for being used with the \"Use Default UE4 scaling\" option.\n \ + The generated Heightfield will likely be incorrectly converted to landscape unless you disable that option in the project settings and recook the asset.")); + } + + DigitZRange = dUINT16_MAX - 1.0; + DigitCenterOffset = 0; + + // Default unreal landscape scaling is -256m:256m at Scale = 100, + // We need to apply the scale back, and swap Y/Z axis + FloatMin = -256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f * CustomZScale/100.f; + FloatMax = 256.0f * CurrentVolumeTransform.GetScale3D().Y * 2.0f * CustomZScale/100.f; + + MeterZRange = (double)(FloatMax - FloatMin); + + ZSpacing = ((double)DigitZRange) / MeterZRange; + } + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] DigitCenterOffset: %f"), DigitZRange); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] DigitZRange: %f"), DigitZRange); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] MeterZRange: %f"), MeterZRange); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] ZSpacing: %f"), ZSpacing); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Volume YScale: %f"), CurrentVolumeTransform.GetScale3D().Y); + + // Converting the data from Houdini to Unreal + // For correct orientation in unreal, the point matrix has to be transposed. + IntHeightData.SetNumUninitialized(SizeInPoints); + + int32 nUnreal = 0; + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y + int32 nHoudini = nY + nX * HoudiniYSize; + + // Get the double values in [0 - ZRange] + double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; + + // Then convert it to [0 - DesiredRange] and center it + DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; + IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); + } + } + + //-------------------------------------------------------------------------------------------------- + // 2. Resample / Pad the int data so that if fits unreal size requirements + //-------------------------------------------------------------------------------------------------- + + // UE has specific size requirements for landscape, + // so we might need to pad/resample the heightfield data + FVector LandscapeResizeFactor = FVector::OneVector; + FVector LandscapePositionOffsetInPixels = FVector::ZeroVector; + if (!NoResize) + { + // Try to resize the data + if (!FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( + IntHeightData, + HoudiniXSize, HoudiniYSize, FinalXSize, FinalYSize, + LandscapeResizeFactor, LandscapePositionOffsetInPixels)) + return false; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Calculating the proper transform for the landscape to be sized and positionned properly + //-------------------------------------------------------------------------------------------------- + + // Scale: + // Calculating the equivalent scale to match Houdini's Terrain Size in Unreal + FVector LandscapeScale; + + // Unreal has a X/Y resolution of 1m per point while Houdini is dependant on the heighfield's grid spacing + // Swap Y/Z axis from H to UE + LandscapeScale.X = CurrentVolumeTransform.GetScale3D().X * 2.0f; + LandscapeScale.Y = CurrentVolumeTransform.GetScale3D().Z * 2.0f; + + // Calculating the Z Scale so that the Z values in Unreal are the same as in Houdini + // Unreal has a default Z range is 512m for a scale of a 100% + LandscapeScale.Z = (float)((double)(dUINT16_MAX / DigitZRange) * MeterZRange / 512.0); + if (bUseDefaultUE4Scaling) + { + // Swap Y/Z axis from H to UE + LandscapeScale.Z = CurrentVolumeTransform.GetScale3D().Y * 2.0f; + } + LandscapeScale *= 100.f; + + // If the data was resized and not expanded, we need to modify the landscape's scale + LandscapeScale *= LandscapeResizeFactor; + + // Don't allow a zero scale, as this results in divide by 0 operations in FMatrix::InverseFast in the landscape component. + if (FMath::IsNearlyZero(LandscapeScale.Z)) + LandscapeScale.Z = 1.0f; + + // We'll use the position from Houdini, but we will need to offset the Z Position to center the + // values properly as the data has been offset by the conversion to uint16 + FVector LandscapePosition = CurrentVolumeTransform.GetLocation(); + //LandscapePosition.Z = 0.0f; + + // We need to calculate the position offset so that Houdini and Unreal have the same Zero position + // In Unreal, zero has a height value of 32768. + // These values are then divided by 128 internally, and then multiplied by the Landscape's Z scale + // ( DIGIT - 32768 ) / 128 * ZScale = ZOffset + + // We need the Digit (Unreal) value of Houdini's zero for the scale calculation + // ( float and int32 are used for this because 0 might be out of the landscape Z range! + // when using the full range, this would cause an overflow for a uint16!! ) + float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); + float ZOffset = -(HoudiniZeroValueInDigit - 32768.0f) / 128.0f * LandscapeScale.Z; + + LandscapePosition.Z += ZOffset; + + // If we have padded the data when resizing the landscape, we need to offset the position because of + // the added values on the topLeft Corner of the Landscape + if (LandscapePositionOffsetInPixels != FVector::ZeroVector) + { + FVector LandscapeOffset = LandscapePositionOffsetInPixels * LandscapeScale; + LandscapeOffset.Z = 0.0f; + + LandscapePosition += LandscapeOffset; + } + + /* + FTransform TempTransform; + TempTransform.SetIdentity(); + { + // Houdini Pivot (center of the Landscape) + FVector HoudiniPivot = FVector((FinalXSize-1) * 100.0f / 2.0f, (FinalYSize-1) * 100.0f / 2.0f, 0.0f); + + // Center the landscape + FVector CenterLocation = LandscapePosition - HoudiniPivot; + + // Rotate the vector using the H rotation + // We need to compensate for the "default" HF Transform + FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); + Rotator.Yaw -= 90.0f; + Rotator.Roll += 90.0f; + FVector RotatedLocation = Rotator.RotateVector(CenterLocation); + + FQuat LandscapeRotation = FQuat(Rotator) * FQuat::Identity; + + // Return to previous origin + FVector Uncentered = RotatedLocation + HoudiniPivot; + TempTransform = FTransform(LandscapeRotation, Uncentered, LandscapeScale); + } + + LandscapeTransform = TempTransform; + */ + + // We can now set the Landscape position + LandscapeTransform.SetLocation(LandscapePosition); + LandscapeTransform.SetScale3D(LandscapeScale); + + // Rotate the vector using the H rotation + FRotator Rotator = CurrentVolumeTransform.GetRotation().Rotator(); + // We need to compensate for the "default" HF Transform + Rotator.Yaw -= 90.0f; + Rotator.Roll += 90.0f; + + // Only rotate if the rotator is far from zero + if(!Rotator.IsNearlyZero()) + LandscapeTransform.SetRotation(FQuat(Rotator)); + + return true; +} + +template +TArray ResampleData(const TArray& Data, int32 OldWidth, int32 OldHeight, int32 NewWidth, int32 NewHeight) +{ + TArray Result; + Result.Empty(NewWidth * NewHeight); + Result.AddUninitialized(NewWidth * NewHeight); + + const float XScale = (float)(OldWidth - 1) / (NewWidth - 1); + const float YScale = (float)(OldHeight - 1) / (NewHeight - 1); + for (int32 Y = 0; Y < NewHeight; ++Y) + { + for (int32 X = 0; X < NewWidth; ++X) + { + const float OldY = Y * YScale; + const float OldX = X * XScale; + const int32 X0 = FMath::FloorToInt(OldX); + const int32 X1 = FMath::Min(FMath::FloorToInt(OldX) + 1, OldWidth - 1); + const int32 Y0 = FMath::FloorToInt(OldY); + const int32 Y1 = FMath::Min(FMath::FloorToInt(OldY) + 1, OldHeight - 1); + const T& Original00 = Data[Y0 * OldWidth + X0]; + const T& Original10 = Data[Y0 * OldWidth + X1]; + const T& Original01 = Data[Y1 * OldWidth + X0]; + const T& Original11 = Data[Y1 * OldWidth + X1]; + Result[Y * NewWidth + X] = FMath::BiLerp(Original00, Original10, Original01, Original11, FMath::Fractional(OldX), FMath::Fractional(OldY)); + } + } + + return Result; +} + +template +void ExpandData(T* OutData, const T* InData, + int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, + int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY) +{ + const int32 OldWidth = OldMaxX - OldMinX + 1; + const int32 OldHeight = OldMaxY - OldMinY + 1; + const int32 NewWidth = NewMaxX - NewMinX + 1; + const int32 NewHeight = NewMaxY - NewMinY + 1; + const int32 OffsetX = NewMinX - OldMinX; + const int32 OffsetY = NewMinY - OldMinY; + + for (int32 Y = 0; Y < NewHeight; ++Y) + { + const int32 OldY = FMath::Clamp(Y + OffsetY, 0, OldHeight - 1); + + // Pad anything to the left + const T PadLeft = InData[OldY * OldWidth + 0]; + for (int32 X = 0; X < -OffsetX; ++X) + { + OutData[Y * NewWidth + X] = PadLeft; + } + + // Copy one row of the old data + { + const int32 X = FMath::Max(0, -OffsetX); + const int32 OldX = FMath::Clamp(X + OffsetX, 0, OldWidth - 1); + FMemory::Memcpy(&OutData[Y * NewWidth + X], &InData[OldY * OldWidth + OldX], FMath::Min(OldWidth, NewWidth) * sizeof(T)); + } + + const T PadRight = InData[OldY * OldWidth + OldWidth - 1]; + for (int32 X = -OffsetX + OldWidth; X < NewWidth; ++X) + { + OutData[Y * NewWidth + X] = PadRight; + } + } +} + +template +TArray ExpandData(const TArray& Data, + int32 OldMinX, int32 OldMinY, int32 OldMaxX, int32 OldMaxY, + int32 NewMinX, int32 NewMinY, int32 NewMaxX, int32 NewMaxY, + int32* PadOffsetX = nullptr, int32* PadOffsetY = nullptr) +{ + const int32 NewWidth = NewMaxX - NewMinX + 1; + const int32 NewHeight = NewMaxY - NewMinY + 1; + + TArray Result; + Result.Empty(NewWidth * NewHeight); + Result.AddUninitialized(NewWidth * NewHeight); + + ExpandData(Result.GetData(), Data.GetData(), + OldMinX, OldMinY, OldMaxX, OldMaxY, + NewMinX, NewMinY, NewMaxX, NewMaxY); + + // Return the padding so we can offset the terrain position after + if (PadOffsetX) + *PadOffsetX = NewMinX; + + if (PadOffsetY) + *PadOffsetY = NewMinY; + + return Result; +} + +bool +FHoudiniLandscapeTranslator::ResizeHeightDataForLandscape( + TArray& HeightData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY, + FVector& LandscapeResizeFactor, + FVector& LandscapePositionOffset) +{ + LandscapeResizeFactor = FVector::OneVector; + LandscapePositionOffset = FVector::ZeroVector; + + if (HeightData.Num() <= 4) + return false; + + if ((SizeX < 2) || (SizeY < 2)) + return false; + + // No need to resize anything + if (SizeX == NewSizeX && SizeY == NewSizeY) + return true; + + // Always resample, for now. We may enable padding functionality again at some point via + // a plugin setting. + bool bForceResample = true; + bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); + + TArray NewData; + if (!bResample) + { + // Expanding the data by padding + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + + const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; + const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; + + // Store the offset in pixel due to the padding + int32 PadOffsetX = 0; + int32 PadOffsetY = 0; + + // Expanding the Data + NewData = ExpandData( + HeightData, 0, 0, SizeX - 1, SizeY - 1, + -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1, + &PadOffsetX, &PadOffsetY); + + // We will need to offset the landscape position due to the value added by the padding + LandscapePositionOffset.X = (float)PadOffsetX; + LandscapePositionOffset.Y = (float)PadOffsetY; + + // Notify the user that the data was padded + HOUDINI_LOG_WARNING( + TEXT("Landscape data had to be padded from ( %d x %d ) to ( %d x %d )."), + SizeX, SizeY, NewSizeX, NewSizeY); + } + else + { + // Resampling the data + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + NewData = ResampleData(HeightData, SizeX, SizeY, NewSizeX, NewSizeY); + + // The landscape has been resized, we'll need to take that into account when sizing it + LandscapeResizeFactor.X = (float)SizeX / (float)NewSizeX; + LandscapeResizeFactor.Y = (float)SizeY / (float)NewSizeY; + LandscapeResizeFactor.Z = 1.0f; + + // Notify the user if the heightfield data was resized + HOUDINI_LOG_WARNING( + TEXT("Landscape data had to be resized from ( %d x %d ) to ( %d x %d )."), + SizeX, SizeY, NewSizeX, NewSizeY); + } + + // Replaces Old data with the new one + HeightData = NewData; + + return true; +} + + +bool +FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( + const int32& HoudiniSizeX, const int32& HoudiniSizeY, + int32& UnrealSizeX, int32& UnrealSizeY, + int32& NumSectionsPerComponent, int32& NumQuadsPerSection) +{ + if ((HoudiniSizeX < 2) || (HoudiniSizeY < 2)) + return false; + + NumSectionsPerComponent = 1; + NumQuadsPerSection = 1; + UnrealSizeX = -1; + UnrealSizeY = -1; + + // Unreal's default sizes + int32 SectionSizes[] = { 7, 15, 31, 63, 127, 255 }; + int32 NumSections[] = { 1, 2 }; + + // Component count used to calculate the final size of the landscape + int32 ComponentsCountX = 1; + int32 ComponentsCountY = 1; + + // Lambda for clamping the number of component in X/Y + auto ClampLandscapeSize = [&]() + { + // Max size is either whole components below 8192 verts, or 32 components + ComponentsCountX = FMath::Clamp(ComponentsCountX, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); + ComponentsCountY = FMath::Clamp(ComponentsCountY, 1, FMath::Min(32, FMath::FloorToInt(8191 / (NumSectionsPerComponent * NumQuadsPerSection)))); + }; + + // Try to find a section size and number of sections that exactly matches the dimensions of the heightfield + bool bFoundMatch = false; + for (int32 SectionSizesIdx = UE_ARRAY_COUNT(SectionSizes) - 1; SectionSizesIdx >= 0; SectionSizesIdx--) + { + for (int32 NumSectionsIdx = UE_ARRAY_COUNT(NumSections) - 1; NumSectionsIdx >= 0; NumSectionsIdx--) + { + int32 ss = SectionSizes[SectionSizesIdx]; + int32 ns = NumSections[NumSectionsIdx]; + + if (((HoudiniSizeX - 1) % (ss * ns)) == 0 && ((HoudiniSizeX - 1) / (ss * ns)) <= 32 && + ((HoudiniSizeY - 1) % (ss * ns)) == 0 && ((HoudiniSizeY - 1) / (ss * ns)) <= 32) + { + bFoundMatch = true; + NumQuadsPerSection = ss; + NumSectionsPerComponent = ns; + ComponentsCountX = (HoudiniSizeX - 1) / (ss * ns); + ComponentsCountY = (HoudiniSizeY - 1) / (ss * ns); + ClampLandscapeSize(); + break; + } + } + if (bFoundMatch) + { + break; + } + } + + if (!bFoundMatch) + { + // if there was no exact match, try increasing the section size until we encompass the whole heightmap + const int32 CurrentSectionSize = NumQuadsPerSection; + const int32 CurrentNumSections = NumSectionsPerComponent; + for (int32 SectionSizesIdx = 0; SectionSizesIdx < UE_ARRAY_COUNT(SectionSizes); SectionSizesIdx++) + { + if (SectionSizes[SectionSizesIdx] < CurrentSectionSize) + { + continue; + } + + const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); + const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), SectionSizes[SectionSizesIdx] * CurrentNumSections); + if (ComponentsX <= 32 && ComponentsY <= 32) + { + bFoundMatch = true; + NumQuadsPerSection = SectionSizes[SectionSizesIdx]; + ComponentsCountX = ComponentsX; + ComponentsCountY = ComponentsY; + ClampLandscapeSize(); + break; + } + } + } + + if (!bFoundMatch) + { + // if the heightmap is very large, fall back to using the largest values we support + const int32 MaxSectionSize = SectionSizes[UE_ARRAY_COUNT(SectionSizes) - 1]; + const int32 MaxNumSubSections = NumSections[UE_ARRAY_COUNT(NumSections) - 1]; + const int32 ComponentsX = FMath::DivideAndRoundUp((HoudiniSizeX - 1), MaxSectionSize * MaxNumSubSections); + const int32 ComponentsY = FMath::DivideAndRoundUp((HoudiniSizeY - 1), MaxSectionSize * MaxNumSubSections); + + bFoundMatch = true; + NumQuadsPerSection = MaxSectionSize; + NumSectionsPerComponent = MaxNumSubSections; + ComponentsCountX = ComponentsX; + ComponentsCountY = ComponentsY; + ClampLandscapeSize(); + } + + if (!bFoundMatch) + { + // Using default size just to not crash.. + UnrealSizeX = 512; + UnrealSizeY = 512; + NumSectionsPerComponent = 1; + NumQuadsPerSection = 511; + ComponentsCountX = 1; + ComponentsCountY = 1; + } + else + { + // Calculating the desired size + int32 QuadsPerComponent = NumSectionsPerComponent * NumQuadsPerSection; + + UnrealSizeX = ComponentsCountX * QuadsPerComponent + 1; + UnrealSizeY = ComponentsCountY * QuadsPerComponent + 1; + } + + return bFoundMatch; +} + +const FHoudiniGeoPartObject* +FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return nullptr; + + if (InOutput->GetHoudiniGeoPartObjects().Num() < 1) + return nullptr; + + for (const FHoudiniGeoPartObject& HGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type != EHoudiniPartType::Volume) + continue; + + FHoudiniVolumeInfo CurVolumeInfo = HGPO.VolumeInfo; + if (!CurVolumeInfo.Name.Contains("height")) + continue; + + // We're only handling single values for now + if (CurVolumeInfo.TupleSize != 1) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume has an invalide tuple size!")); + return nullptr; + } + + // Terrains always have a ZSize of 1. + if (CurVolumeInfo.ZLength != 1) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output: the height volume's z length is not 1!")); + return nullptr; + } + + // Values should be float + if (!CurVolumeInfo.bIsFloat) + { + HOUDINI_LOG_ERROR(TEXT("Failed to create landscape output, the height volume's data is not stored as floats!")); + return nullptr; + } + + return &HGPO; + } + + return nullptr; +} + +void +FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers) +{ + FoundLayers.Empty(); + + // Get node id + HAPI_NodeId HeightFieldNodeId = Heightfield.GeoId; + + // We need the tile attribute if the height has it + bool bParentHeightfieldHasTile = false; + int32 HeightFieldTile = -1; + { + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + TArray< int32 > TileValues; + + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HeightFieldNodeId, Heightfield.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, + AttribInfoTile, TileValues, 0, HAPI_ATTROWNER_INVALID, 0, 1); + + if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) + { + HeightFieldTile = TileValues[0]; + bParentHeightfieldHasTile = true; + } + } + + for (TArray< FHoudiniGeoPartObject >::TConstIterator IterLayers(InOutput->GetHoudiniGeoPartObjects()); IterLayers; ++IterLayers) + { + const FHoudiniGeoPartObject & HoudiniGeoPartObject = *IterLayers; + + HAPI_NodeId NodeId = HoudiniGeoPartObject.GeoId; + if (NodeId == -1 || NodeId != HeightFieldNodeId) + continue; + + if (bParentHeightfieldHasTile) + { + int32 CurrentTile = -1; + + HAPI_AttributeInfo AttribInfoTile; + FHoudiniApi::AttributeInfo_Init(&AttribInfoTile); + TArray TileValues; + + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, + AttribInfoTile, TileValues, 0, HAPI_ATTROWNER_INVALID, 0, 1); + + if (AttribInfoTile.exists && AttribInfoTile.owner == HAPI_ATTROWNER_PRIM && TileValues.Num() > 0) + { + CurrentTile = TileValues[0]; + } + + // Does this layer come from the same tile as the height? + if ((CurrentTile != HeightFieldTile) || (CurrentTile == -1)) + continue; + } + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, HoudiniGeoPartObject.PartId, + &CurrentVolumeInfo)) + continue; + + // We're interesting in anything but height data + FString CurrentVolumeName; + FHoudiniEngineString(CurrentVolumeInfo.nameSH).ToFString(CurrentVolumeName); + if (CurrentVolumeName.Contains("height")) + continue; + + // We're only handling single values for now + if (CurrentVolumeInfo.tupleSize != 1) + continue; + + // Terrains always have a ZSize of 1. + if (CurrentVolumeInfo.zLength != 1) + continue; + + // Values should be float + if (CurrentVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + continue; + + FoundLayers.Add(&HoudiniGeoPartObject); + } +} + +bool FHoudiniLandscapeTranslator::GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo) +{ + if (HGPO->Type != EHoudiniPartType::Volume) + return false; + + FHoudiniApi::VolumeInfo_Init(&VolumeInfo); + + HAPI_Result Result = FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + HGPO->GeoId, HGPO->PartId, &VolumeInfo); + + // We're only handling single values for now + if (VolumeInfo.tupleSize != 1) + return false; + + // Terrains always have a ZSize of 1. + if (VolumeInfo.zLength != 1) + return false; + + // Values must be float + if (VolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + return false; + + if ((VolumeInfo.xLength < 2) || (VolumeInfo.yLength < 2)) + return false; + + return true; +} + +bool +FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(const FHoudiniGeoPartObject* HGPO, TArray &OutFloatArr, float &OutFloatMin, float &OutFloatMax) +{ + OutFloatArr.Empty(); + OutFloatMin = 0.f; + OutFloatMax = 0.f; + + HAPI_VolumeInfo VolumeInfo; + if (!GetHoudiniHeightfieldVolumeInfo(HGPO, VolumeInfo)) + return false; + + const int32 SizeInPoints = VolumeInfo.xLength * VolumeInfo.yLength; + + OutFloatArr.SetNum(SizeInPoints); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetHeightFieldData( + FHoudiniEngine::Get().GetSession(), + HGPO->GeoId, HGPO->PartId, + OutFloatArr.GetData(), + 0, SizeInPoints), false); + + OutFloatMin = OutFloatArr[0]; + OutFloatMax = OutFloatMin; + + for (float NextFloatVal : OutFloatArr) + { + if (NextFloatVal > OutFloatMax) + { + OutFloatMax = NextFloatVal; + } + else if (NextFloatVal < OutFloatMin) + OutFloatMin = NextFloatVal; + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(const FHoudiniGeoPartObject& InHGPO, TArray& NonWeightBlendedLayerNames) +{ + // Check the attribute exists on primitive or detail + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; + if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_PRIM)) + Owner = HAPI_ATTROWNER_PRIM; + else if (FHoudiniEngineUtils::HapiCheckAttributeExists(InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, HAPI_ATTROWNER_DETAIL)) + Owner = HAPI_ATTROWNER_DETAIL; + else + return false; + + // Get the values + HAPI_AttributeInfo AttribInfoNonWBLayer; + FHoudiniApi::AttributeInfo_Init(&AttribInfoNonWBLayer); + TArray AttribValues; + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS, AttribInfoNonWBLayer, AttribValues, 1, Owner); + + if (AttribValues.Num() <= 0) + return false; + + // Convert them to FString + for (int32 Idx = 0; Idx < AttribValues.Num(); Idx++) + { + TArray Tokens; + AttribValues[Idx].ParseIntoArray(Tokens, TEXT(" "), true); + + for (int32 n = 0; n < Tokens.Num(); n++) + NonWeightBlendedLayerNames.AddUnique(Tokens[n]); + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) +{ + // Check the attribute exists on primitive or detail + HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; + if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_PRIM)) + Owner = HAPI_ATTROWNER_PRIM; + else if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_DETAIL)) + Owner = HAPI_ATTROWNER_DETAIL; + else + return false; + + // Check the value + HAPI_AttributeInfo AttribInfoUnitLayer; + FHoudiniApi::AttributeInfo_Init(&AttribInfoUnitLayer); + TArray< int32 > AttribValues; + FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, + AttribInfoUnitLayer, AttribValues, 1, Owner, 0, 1); + + if (AttribValues.Num() > 0 && AttribValues[0] == 1) + return true; + + return false; +} + +bool +FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( + const TArray& FoundLayers, + const FHoudiniGeoPartObject& Heightfield, + const int32& LandscapeXSize, const int32& LandscapeYSize, + const TMap& GlobalMinimums, + const TMap& GlobalMaximums, + TArray& OutLayerInfos, + bool bIsUpdate, + const FHoudiniPackageParams& InTilePackageParams, + const FHoudiniPackageParams& InLayerPackageParams, + TArray& OutCreatedPackages + ) +{ + OutLayerInfos.Empty(); + + // Get the names of all non weight blended layers + TArray NonWeightBlendedLayerNames; + FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(Heightfield, NonWeightBlendedLayerNames); + + // Used for exporting layer info objects (per landscape layer) + FHoudiniPackageParams LayerPackageParams = InLayerPackageParams; + // Used for exporting textures (per landscape tile) + FHoudiniPackageParams TilePackageParams = InTilePackageParams; + + // For Debugging, do we want to export layers as textures? + bool bExportTexture = CVarHoudiniEngineExportLandscapeTextures.GetValueOnAnyThread() == 1 ? true : false; + + // Try to create all the layers + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + for (TArray::TConstIterator IterLayers(FoundLayers); IterLayers; ++IterLayers) + { + const FHoudiniGeoPartObject * LayerGeoPartObject = *IterLayers; + if (!LayerGeoPartObject) + continue; + + if (!LayerGeoPartObject->IsValid()) + continue; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerGeoPartObject->AssetId)) + continue; + + if (bIsUpdate && !LayerGeoPartObject->bHasGeoChanged) + { + continue; + } + + TArray FloatLayerData; + float LayerMin = 0; + float LayerMax = 0; + if (!FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(LayerGeoPartObject, FloatLayerData, LayerMin, LayerMax)) + continue; + + // No need to create flat layers as Unreal will remove them afterwards.. + if (LayerMin == LayerMax) + continue; + + const FHoudiniVolumeInfo& LayerVolumeInfo = LayerGeoPartObject->VolumeInfo; + + // Get the layer's name + FString LayerName = LayerVolumeInfo.Name; + const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerName); + + TilePackageParams.ObjectName = InTilePackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; + LayerPackageParams.ObjectName = InLayerPackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; + + if (bExportTexture) + { + // Create a raw texture export of the layer on this tile + FString TextureName = TilePackageParams.ObjectName + "_raw"; + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + LayerVolumeInfo.YLength, // Y and X inverted?? why? + LayerVolumeInfo.XLength, + FloatLayerData, + LayerMin, + LayerMax); + } + + // Check if that landscape layer has been marked as unit (range in [0-1] + if (IsUnitLandscapeLayer(*LayerGeoPartObject)) + { + LayerMin = 0.0f; + LayerMax = 1.0f; + } + else + { + // We want to convert the layer using the global Min/Max + if (GlobalMaximums.Contains(LayerName)) + LayerMax = GlobalMaximums[LayerName]; + + if (GlobalMinimums.Contains(LayerName)) + LayerMin = GlobalMinimums[LayerName]; + } + + // Get the layer package path + // FString LayerNameString = FString::Printf(TEXT("%s_%d"), LayerString.GetCharArray().GetData(), (int32)LayerGeoPartObject->PartId); + // LayerNameString = UPackageTools::SanitizePackageName(LayerNameString); + + // Build an object name for the current layer + LayerPackageParams.SplitStr = SanitizedLayerName; + + // Creating the ImportLayerInfo and LayerInfo objects + FLandscapeImportLayerInfo ImportLayerInfo(*LayerName); + + // See if the user has assigned a layer info object via attribute + UPackage * Package = nullptr; + ULandscapeLayerInfoObject* LayerInfo = GetLandscapeLayerInfoForLayer(*LayerGeoPartObject, *LayerName); + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + // No assignment, try to find or create a landscape layer info object for that layer + LayerInfo = FindOrCreateLandscapeLayerInfoObject(LayerName, LayerPackageParams.GetPackagePath(), LayerPackageParams.GetPackageName(), Package); + } + + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + continue; + } + + // Convert the float data to uint8 + // HF masks need their X/Y sizes swapped + if (!FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( + FloatLayerData, LayerVolumeInfo.YLength, LayerVolumeInfo.XLength, + LayerMin, LayerMax, + LandscapeXSize, LandscapeYSize, + ImportLayerInfo.LayerData)) + continue; + + // We will store the data used to convert from Houdini values to int in the DebugColor + // This is the only way we'll be able to reconvert those values back to their houdini equivalent afterwards... + // R = Min, G = Max, B = Spacing, A = ? + LayerInfo->LayerUsageDebugColor.R = LayerMin; + LayerInfo->LayerUsageDebugColor.G = LayerMax; + LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; + LayerInfo->LayerUsageDebugColor.A = PI; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers] Processing layer: %s"), *(LayerName)); + + // Visibility are by default non weight blended + if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) + LayerInfo->bNoWeightBlend = true; + else + LayerInfo->bNoWeightBlend = false; + + if (!bIsUpdate && Package && !Package->IsPendingKill()) + { + // Mark the package dirty... + Package->MarkPackageDirty(); + OutCreatedPackages.Add(Package); + } + + if (bExportTexture) + { + // Create an export of the converted data to texture + // FString TextureName = LayerString; + // if (LayerGeoPartObject->VolumeTileIndex >= 0) + // TextureName = TEXT("Tile") + FString::FromInt(LayerGeoPartObject->VolumeTileIndex) + TEXT("_") + LayerString; + // TextureName += TEXT("_conv"); + + const FString TextureName = TilePackageParams.ObjectName + TEXT("_conv"); + + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + LandscapeXSize, LandscapeYSize, + ImportLayerInfo.LayerData); + } + + // See if there is a physical material assigned via attribute for that landscape layer + UPhysicalMaterial* PhysMaterial = FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(*LayerGeoPartObject); + if (PhysMaterial && !PhysMaterial->IsPendingKill()) + { + LayerInfo->PhysMaterial = PhysMaterial; + } + + // Assign the layer info object to the import layer infos + ImportLayerInfo.LayerInfo = LayerInfo; + OutLayerInfos.Add(ImportLayerInfo); + } + + // Autosaving the layers prevents them for being deleted with the Asset + // Save the packages created for the LayerInfos + // Do this only for when creating layers. + /* + if (!bIsUpdate) + FEditorFileUtils::PromptForCheckoutAndSave(CreatedLandscapeLayerPackage, true, false); + */ + + return true; +} + +void +FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums, + bool bShouldEmptyMaps) +{ + if (bShouldEmptyMaps) + { + GlobalMinimums.Empty(); + GlobalMaximums.Empty(); + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray FloatData; + + for (const FHoudiniGeoPartObject& CurrentHeightfield: InHeightfieldArray) + { + // Get the current Heightfield GeoPartObject + if ( CurrentHeightfield.VolumeInfo.TupleSize != 1) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = CurrentHeightfield.GeoId; + if (NodeId == -1) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + &CurrentVolumeInfo)) + continue; + + // Retrieve the volume name. + FString VolumeName; + FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); + HoudiniEngineStringPartName.ToFString(VolumeName); + + bool bHasMinAttr = false; + bool bHasMaxAttr = false; + + // If this volume has an attribute defining a minimum value use it as is. + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurrentHeightfield.GeoId, CurrentHeightfield.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, + AttributeInfo, FloatData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (FloatData.Num() > 0) + { + GlobalMinimums.Add(VolumeName, FloatData[0]); + bHasMinAttr = true; + } + } + + // If this volume has an attribute defining maximum value use it as is. + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurrentHeightfield.GeoId, CurrentHeightfield.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, + AttributeInfo, FloatData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (FloatData.Num() > 0) + { + GlobalMaximums.Add(VolumeName, FloatData[0]); + bHasMaxAttr = true; + } + } + + if (!(bHasMinAttr && bHasMaxAttr)) + { + // Unreal's Z values are Y in Houdini + float ymin, ymax; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + nullptr, &ymin, nullptr, + nullptr, &ymax, nullptr, + nullptr, nullptr, nullptr)) + continue; + + + if (!bHasMinAttr) + { + // Read the global min value for this volume + if (!GlobalMinimums.Contains(VolumeName)) + { + GlobalMinimums.Add(VolumeName, ymin); + } + else + { + // Update the min if necessary + if (ymin < GlobalMinimums[VolumeName]) + GlobalMinimums[VolumeName] = ymin; + } + } + + if (!bHasMaxAttr) + { + // Read the global max value for this volume + if (!GlobalMaximums.Contains(VolumeName)) + { + GlobalMaximums.Add(VolumeName, ymax); + } + else + { + // Update the max if necessary + if (ymax > GlobalMaximums[VolumeName]) + GlobalMaximums[VolumeName] = ymax; + } + } + } + } +} + +void +FHoudiniLandscapeTranslator::GetLayersZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums) + +{ + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + TArray FloatData; + + for (const FHoudiniGeoPartObject& CurrentHeightfield : InHeightfieldArray) + { + // Get the current Heightfield GeoPartObject + if (CurrentHeightfield.VolumeInfo.TupleSize != 1) + continue; + + // Retrieve node id from geo part. + HAPI_NodeId NodeId = CurrentHeightfield.GeoId; + if (NodeId == -1) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, CurrentHeightfield.PartId, + &CurrentVolumeInfo)) + continue; + + // Retrieve the volume name. + FString VolumeName; + FHoudiniEngineString HoudiniEngineStringPartName(CurrentVolumeInfo.nameSH); + HoudiniEngineStringPartName.ToFString(VolumeName); + + // Read the global min value for this volume + + float MinValue; + float MaxValue; + bool bHasMin = false; + bool bHasMax = false; + + if (!GlobalMinimums.Contains(VolumeName)) + { + // Extract min value + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurrentHeightfield.GeoId, CurrentHeightfield.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MIN, + AttributeInfo, FloatData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (FloatData.Num() > 0) + { + MinValue = FloatData[0]; + bHasMin = true; + } + } + if (!bHasMin) + { + if (VolumeName == TEXT("height")) + { + MinValue = -1000.f; + } + else + { + MinValue = 0.f; + } + } + GlobalMinimums.Add(VolumeName, MinValue); + } + + if (!GlobalMaximums.Contains(VolumeName)) + { + // Extract max value + FloatData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurrentHeightfield.GeoId, CurrentHeightfield.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_MAX, + AttributeInfo, FloatData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (FloatData.Num() > 0) + { + MaxValue = FloatData[0]; + bHasMax = true; + } + } + if (!bHasMax) + { + if (VolumeName == TEXT("height")) + { + MaxValue = 1000.f; + } + else + { + MaxValue = 1.f; + } + } + GlobalMaximums.Add(VolumeName, MaxValue); + } + + + + } +} + +bool +FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( + const TArray& FloatLayerData, + const int32& HoudiniXSize, const int32& HoudiniYSize, + const float& LayerMin, const float& LayerMax, + const int32& LandscapeXSize, const int32& LandscapeYSize, + TArray& LayerData, const bool& NoResize) +{ + // Convert the float data to uint8 + LayerData.SetNumUninitialized(HoudiniXSize * HoudiniYSize); + + // Calculating the factor used to convert from Houdini's ZRange to [0 255] + double LayerZRange = (LayerMax - LayerMin); + double LayerZSpacing = (LayerZRange != 0.0) ? (255.0 / (double)(LayerZRange)) : 0.0; + + int32 nUnrealIndex = 0; + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // Copying values X then Y in Unreal but reading them Y then X in Houdini due to swapped X/Y + int32 nHoudini = nY + nX * HoudiniYSize; + + // Get the double values in [0 - ZRange] + double DoubleValue = (double)FMath::Clamp(FloatLayerData[nHoudini], LayerMin, LayerMax) - (double)LayerMin; + + // Then convert it to [0 - 255] + DoubleValue *= LayerZSpacing; + + LayerData[nUnrealIndex++] = FMath::RoundToInt(DoubleValue); + } + } + + // Finally, resize the data to fit with the new landscape size if needed + if (NoResize) + return true; + + return FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( + LayerData, HoudiniXSize, HoudiniYSize, + LandscapeXSize, LandscapeYSize); +} + +bool +FHoudiniLandscapeTranslator::ResizeLayerDataForLandscape( + TArray< uint8 >& LayerData, + const int32& SizeX, const int32& SizeY, + const int32& NewSizeX, const int32& NewSizeY) +{ + if ((NewSizeX == SizeX) && (NewSizeY == SizeY)) + return true; + + bool bForceResample = true; + bool bResample = bForceResample ? true : ((NewSizeX <= SizeX) && (NewSizeY <= SizeY)); + + TArray NewData; + if (!bResample) + { + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + + const int32 OffsetX = (int32)(NewSizeX - SizeX) / 2; + const int32 OffsetY = (int32)(NewSizeY - SizeY) / 2; + + // Expanding the Data + NewData = ExpandData( + LayerData, + 0, 0, SizeX - 1, SizeY - 1, + -OffsetX, -OffsetY, NewSizeX - OffsetX - 1, NewSizeY - OffsetY - 1); + } + else + { + // Resampling the data + NewData.SetNumUninitialized(NewSizeX * NewSizeY); + NewData = ResampleData(LayerData, SizeX, SizeY, NewSizeX, NewSizeY); + } + + LayerData = NewData; + + return true; +} + +ALandscapeProxy * +FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( + const TArray< uint16 >& IntHeightData, + const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + const FTransform& TileTransform, + const FIntPoint& TileLocation, + const int32& XSize, + const int32& YSize, + const int32& NumSectionPerLandscapeComponent, + const int32& NumQuadsPerLandscapeSection, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* LandscapePhysicalMaterial, + const FString& LandscapeTileActorName, + LandscapeActorType ActorType, + ALandscape* SharedLandscapeActor, + UWorld* InWorld, + ULevel* InLevel, + FHoudiniPackageParams InPackageParams) +{ + if (!IsValid(InWorld)) + return nullptr; + + // if (!IsValid(MainLandscapeActor)) + // return nullptr; + + if ((XSize < 2) || (YSize < 2)) + return nullptr; + + if (IntHeightData.Num() != (XSize * YSize)) + return nullptr; + + if (!GEditor) + return nullptr; + + ALandscapeProxy* LandscapeTile = nullptr; + UPackage *CreatedPackage = nullptr; + + ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; + ALandscape* CachedLandscapeActor = nullptr; + + UWorld* NewWorld = nullptr; + FString MapFileName; + bool bBroadcastMaterialUpdate = false; + //... Create landscape tile ...// + { + // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos + if (ActorType == LandscapeActorType::LandscapeStreamingProxy) + { + CachedStreamingProxyActor = InWorld->SpawnActor(); + if (CachedStreamingProxyActor) + { + check(SharedLandscapeActor); + CachedStreamingProxyActor->PreEditChange(nullptr); + + // Update landscape tile properties from the main landscape actor. + CachedStreamingProxyActor->GetSharedProperties(SharedLandscapeActor); + CachedStreamingProxyActor->LandscapeActor = SharedLandscapeActor; + CachedStreamingProxyActor->bCastStaticShadow = false; + + LandscapeTile = CachedStreamingProxyActor; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not spawn ALandscapeStreamingProxy with name: %s"), *(LandscapeTileActorName) ); + return nullptr; + } + } + else + { + // Create a normal landscape actor + CachedLandscapeActor = InWorld->SpawnActor(); + if (CachedLandscapeActor) + { + CachedLandscapeActor->PreEditChange(nullptr); + CachedLandscapeActor->SetLandscapeGuid(FGuid::NewGuid()); + CachedLandscapeActor->LandscapeMaterial = LandscapeMaterial; + CachedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + CachedLandscapeActor->bCastStaticShadow = false; + bBroadcastMaterialUpdate = true; + LandscapeTile = CachedLandscapeActor; + } + } + } + + + if (!LandscapeTile) + return nullptr; + + // Only import non-visibility layers. Visibility will be handled explicitly. + TArray CustomImportLayerInfos; + for (const FLandscapeImportLayerInfo& LayerInfo : ImportLayerInfos) + { + if (LayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + continue; + CustomImportLayerInfos.Add(LayerInfo); + } + + // At this point we either has a ALandscapeStreamingProxy or ALandscape actor for the landscape tile. + + // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue + LandscapeTile->bCastStaticShadow = false; + + // TODO: Check me? + //if (LandscapePhsyicalMaterial) + // LandscapeTile->DefaultPhysMaterial = LandscapePhsyicalMaterial; + + // Setting the layer type here. + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + + TMap> HeightmapDataPerLayers; + TMap> MaterialLayerDataPerLayer; + HeightmapDataPerLayers.Add(FGuid(), IntHeightData); + TArray& MaterialImportLayerInfos = MaterialLayerDataPerLayer.Add(FGuid(), CustomImportLayerInfos); + + // Before we import new tile data, remove previous tile data that will be overlapped by the new tile. + TSet OverlappingComponents; + const int32 DestMinX = TileLocation.X; + const int32 DestMinY = TileLocation.Y; + const int32 DestMaxX = TileLocation.X + XSize - 1; + const int32 DestMaxY = TileLocation.Y + YSize - 1; + + ULandscapeInfo* LandscapeInfo = LandscapeTile->GetLandscapeInfo(); + + if (LandscapeInfo) + { + // If there is a preexisting LandscapeInfo object, check for overlapping components. + + // Shrink bounds by 1,1 to avoid GetComponentsInRegion picking up extra components on all sides due to the overlap between components + LandscapeInfo->GetComponentsInRegion(DestMinX+1, DestMinY+1, DestMaxX-1, DestMaxY-1, OverlappingComponents); + TSet StaleActors; + + for (ULandscapeComponent* Component : OverlappingComponents) + { + // Remove the overlapped component from the LandscapeInfo and then from + LandscapeInfo->Modify(); + + ALandscapeProxy* Proxy = Component->GetLandscapeProxy(); + if (!IsValid(Proxy)) + continue; + check(Proxy); + FIntRect Bounds = Proxy->GetBoundingRect(); + // If this landscape proxy has no more components left, remove it from the LandscapeInfo. + LandscapeInfo->UnregisterActor(Proxy); + Proxy->Destroy(); + } + + ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); + } + + // Import tile data + // The Import function will correctly compute the tile section locations. No need to set it explicitly. + // TODO: Verify this with world composition!! + + bool bIsStandalone = LandscapeTile->GetLandscapeActor() == LandscapeTile; + + // We set the actor transform and absolute section base before importing heightfield data. This allows us to + // use the correct (quad-space) blitting region without causing overlaps during import. + + // NOTE: The following SetAbsoluteSectionBase() calls are very important. This tells the Landscape system + // where on the landscape, in quad space, a specific tile is located. This is used by various + // mechanisms and tools that tie into the Landscape system such as World Composition and Landscape editor tools. + // If the Section Offset for a landscape tile is wrong, it will appear in the wrong place in the World Composition + // viewer. Worse than that, none of Landscape Editor tools will work properly because it won't be able to + // locate the correct Landscape component when calculating the "Landscape Component Key" for the given world position. + // HeightmapAccessor and the AlphamapAccessor as well as the FLandscapeEditDataInterface blitting functions use the + // same underlying Landscape Component Key calculation to find the correct landscape component to draw to. So if the + // Section Offsets are wrong ... all manner of chaos will follow. + // It is also super important to call ULandscapeInfo::RecreateLandscapeInfo() after change a LandscapeProxy's + // section offset in order to update the landscape's internal caches (more specifically the component keys, which + // are based on the section offsets) otherwise component key calculations won't work correctly. + + LandscapeTile->SetActorRelativeTransform(TileTransform); + LandscapeTile->SetAbsoluteSectionBase(TileLocation); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Importing tile for actor: %s "), *(LandscapeTile->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Dest region: %d, %d -> %d, %d"), DestMinX, DestMinY, DestMaxX, DestMaxY); + + LandscapeTile->Import( + LandscapeTile->GetLandscapeGuid(), + DestMinX, DestMinY, DestMaxX, DestMaxY, + NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, + HeightmapDataPerLayers, NULL, + MaterialLayerDataPerLayer, ImportLayerType); + + + if (!LandscapeInfo) + { + LandscapeInfo = LandscapeTile->GetLandscapeInfo(); + } + + check(LandscapeInfo); + + // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so + // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo component keys. + // Only then are we able to "blit" the new alpha data into the correct place on the landscape. + + ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); + LandscapeTile->RecreateComponentsState(); + + // TODO: Verify whether we still need to manually set the LandscapeLightingLOD setting or whether + // calling PostEditChange() will properly fix the state. + + // Copied straight from UE source code to avoid crash after importing the landscape: + // automatically calculate a lighting LOD that won't crash lightmass (hopefully) + // < 2048x2048 -> LOD0, >=2048x2048 -> LOD1, >= 4096x4096 -> LOD2, >= 8192x8192 -> LOD3 + LandscapeTile->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + + // ---------------------------------------------------- + // Update visibility layer + // ---------------------------------------------------- + + // Update the visibility mask / layer if we have any (TileImport does not update the visibility layer). + for (auto CurLayerInfo : ImportLayerInfos) + { + if (CurLayerInfo.LayerInfo && CurLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + { + FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(DestMinX, DestMinY, DestMaxX, DestMaxY, CurLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + + // ---------------------------------------------------- + // Rename the actor + // ---------------------------------------------------- + + // NOTE: The LandscapeProxy needs to be properly initialized before renaming (which is why the rename is taking + // place at the end) since the rename will trigger PostEditChange and can crash if the actor has not been + // correctly setup. + FHoudiniEngineUtils::SafeRenameActor(LandscapeTile, LandscapeTileActorName); + + if (!LandscapeTile->MarkPackageDirty()) + { + HOUDINI_LOG_WARNING(TEXT("Editor suppressed MarkPackageDirty() for landscape tile: %s"), *(LandscapeTile->GetPathName())); + } + +#if WITH_EDITOR + GEngine->BroadcastOnActorMoved(LandscapeTile); +#endif + + return LandscapeTile; +} + + +void +FHoudiniLandscapeTranslator::DestroyLandscape(ALandscape* Landscape) +{ + if (!IsValid(Landscape)) + return; + + ULandscapeInfo* Info = Landscape->GetLandscapeInfo(); + if (!IsValid(Info)) + return; + + TArray Proxies = Info->Proxies; + for(ALandscapeStreamingProxy* Proxy : Proxies) + { + Info->UnregisterActor(Proxy); + Proxy->Destroy(); + } + Landscape->Destroy(); +} + + +void +FHoudiniLandscapeTranslator::CalculateTileLocation( + int32 NumSectionsPerComponent, + int32 NumQuadsPerSection, + const FTransform& TileTransformWS, + FHoudiniLandscapeReferenceLocation& RefLoc, + FTransform& OutLandscapeTransform, + FIntPoint& OutTileLocation) +{ + // IMPORTANT: Tile Location (Section Base) needs to be in multiples of the landscape component's quad size + const int32 ComponentSize = NumSectionsPerComponent * NumQuadsPerSection; + + OutLandscapeTransform = FTransform::Identity; + const FTransform TileSR = FTransform(TileTransformWS.GetRotation(), FVector::ZeroVector, TileTransformWS.GetScale3D()); + + const FVector BaseLoc = TileSR.InverseTransformPosition(TileTransformWS.GetLocation()); + + const FVector TileScale = TileTransformWS.GetScale3D(); + const float TileLocX = BaseLoc.X; // / TileScale.X; + const float TileLocY = BaseLoc.Y; // / TileScale.Y; + + if (!RefLoc.bIsCached) + { + // If there is no landscape reference location yet, calculate one now. + + // We cache this tile as a reference point for the other landscape tiles so that they can calculate + // section base offsets in a consistent manner, relative to this tile. + const float NearestMultipleX = FMath::RoundHalfFromZero(TileLocX / ComponentSize) * ComponentSize; + const float NearestMultipleY = FMath::RoundHalfFromZero(TileLocY / ComponentSize) * ComponentSize; + + RefLoc.SectionCoordX = FMath::RoundHalfFromZero(NearestMultipleX); + RefLoc.SectionCoordY = FMath::RoundHalfFromZero(NearestMultipleY); + RefLoc.TileLocationX = TileLocX; + RefLoc.TileLocationY = TileLocY; + } + + // Calculate the section coordinate for this tile + const float DeltaLocX = TileLocX - RefLoc.TileLocationX; + const float DeltaLocY = TileLocY - RefLoc.TileLocationY; + + const float DeltaCoordX = FMath::RoundHalfFromZero(DeltaLocX / ComponentSize) * ComponentSize; + const float DeltaCoordY = FMath::RoundHalfFromZero(DeltaLocY / ComponentSize) * ComponentSize; + + OutTileLocation.X = RefLoc.SectionCoordX + DeltaCoordX; + OutTileLocation.Y = RefLoc.SectionCoordY + DeltaCoordY; + + // Adjust landscape offset to compensate for tile location / section base shifting. + if (!RefLoc.bIsCached) + { + FVector Offset((TileLocX - OutTileLocation.X), (TileLocY - OutTileLocation.Y), BaseLoc.Z); + Offset = TileSR.TransformPosition(Offset); + + RefLoc.MainTransform = TileTransformWS; + RefLoc.MainTransform.SetTranslation(Offset); + // Reference locations are now fully cached. + RefLoc.bIsCached = true; + } + + OutLandscapeTransform = RefLoc.MainTransform; +} + + +void +FHoudiniLandscapeTranslator::GetLandscapeMaterials( + const FHoudiniGeoPartObject& InHeightHGPO, + const FHoudiniPackageParams& InPackageParams, + UMaterialInterface*& OutLandscapeMaterial, + UMaterialInterface*& OutLandscapeHoleMaterial, + UPhysicalMaterial*& OutLandscapePhysicalMaterial) +{ + OutLandscapeMaterial = nullptr; + OutLandscapeHoleMaterial = nullptr; + OutLandscapePhysicalMaterial = nullptr; + + if (InHeightHGPO.Type != EHoudiniPartType::Volume) + return; + + TArray Materials; + HAPI_AttributeInfo AttribMaterials; + FHoudiniApi::AttributeInfo_Init(&AttribMaterials); + + // First, look for landscape material + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL, + AttribMaterials, Materials); + + bool bMaterialOverrideNeedsCreateInstance = false; + + // If the material attribute was not found, check the material instance attribute. + if (!AttribMaterials.exists) + { + Materials.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, + AttribMaterials, Materials); + + bMaterialOverrideNeedsCreateInstance = true; + } + + // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. + //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) + if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) + { + HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a point, primitive or detail attribute, ignoring attribute.")); + AttribMaterials.exists = false; + Materials.Empty(); + } + + if (AttribMaterials.exists && Materials.Num() > 0) + { + // Load the material + + if (!bMaterialOverrideNeedsCreateInstance) + { + OutLandscapeMaterial = Cast(StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); + } + else + { + TArray MaterialAndTexturePackages; + + // Purposefully empty material since it is satisfied by the override parameter + TMap InputAssignmentMaterials; + TMap OutputAssignmentMaterials; + + if (FHoudiniMaterialTranslator::SortUniqueFaceMaterialOverridesAndCreateMaterialInstances(Materials, InHeightHGPO, InPackageParams, + MaterialAndTexturePackages, + InputAssignmentMaterials, OutputAssignmentMaterials, + false)) + { + TArray Values; + OutputAssignmentMaterials.GenerateValueArray(Values); + if (Values.Num() > 0) + { + OutLandscapeMaterial = Values[0]; + } + } + + + } + } + } + + Materials.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribMaterials); + + // Then, for the hole_material + { + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + AttribMaterials, Materials); + + // If the material attribute was not found, check the material instance attribute. + if (!AttribMaterials.exists) + { + Materials.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InHeightHGPO.GeoId, InHeightHGPO.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_HOLE_INSTANCE, + AttribMaterials, Materials); + } + + // For some reason, HF attributes come as Vertex attrib, we should check the original owner instead.. + //if (AttribMaterials.exists && AttribMaterials.owner != HAPI_ATTROWNER_PRIM && AttribMaterials.owner != HAPI_ATTROWNER_DETAIL) + if (AttribMaterials.exists && AttribMaterials.originalOwner == HAPI_ATTROWNER_VERTEX) + { + HOUDINI_LOG_WARNING(TEXT("Landscape: unreal_material must be a primitive or detail attribute, ignoring attribute.")); + AttribMaterials.exists = false; + Materials.Empty(); + } + + if (AttribMaterials.exists && Materials.Num() > 0) + { + // Load the material + OutLandscapeHoleMaterial = Cast< UMaterialInterface >(StaticLoadObject( + UMaterialInterface::StaticClass(), + nullptr, *(Materials[0]), nullptr, LOAD_NoWarn, nullptr)); + } + } + + // Then for the physical material + OutLandscapePhysicalMaterial = GetLandscapePhysicalMaterial(InHeightHGPO); +} + +// Read the landscape component extent attribute from a heightfield +bool +FHoudiniLandscapeTranslator::GetLandscapeComponentExtentAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + int32& MinX, int32& MaxX, + int32& MinY, int32& MaxY) +{ + // If we dont have minX, we likely dont have the others too + if (!FHoudiniEngineUtils::HapiCheckAttributeExists( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", HAPI_ATTROWNER_PRIM)) + return false; + + // Create an AttributeInfo + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + // Get MinX + TArray IntData; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_X", + AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) + return false; + + if (IntData.Num() > 0) + MinX = IntData[0]; + + // Get MaxX + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_X", + AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) + return false; + + if (IntData.Num() > 0) + MaxX = IntData[0]; + + // Get MinY + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_min_Y", + AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) + return false; + + if (IntData.Num() > 0) + MinY = IntData[0]; + + // Get MaxX + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, "landscape_component_max_Y", + AttributeInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) + return false; + + if (IntData.Num() > 0) + MaxY = IntData[0]; + + return true; +} + +ULandscapeLayerInfoObject * +FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& InLayerName, const FString& InPackagePath, const FString& InPackageName, UPackage*& OutPackage) +{ + FString PackageFullName = InPackagePath + TEXT("/") + InPackageName; + + // See if package exists, if it does, reuse it + bool bCreatedPackage = false; + OutPackage = FindPackage(nullptr, *PackageFullName); + if (!OutPackage || OutPackage->IsPendingKill()) + { + // We need to create a new package + OutPackage = CreatePackage( *PackageFullName); + bCreatedPackage = true; + } + + if (!OutPackage || OutPackage->IsPendingKill()) + return nullptr; + + if (!OutPackage->IsFullyLoaded()) + OutPackage->FullyLoad(); + + ULandscapeLayerInfoObject* LayerInfo = nullptr; + if (!bCreatedPackage) + { + // See if we can load the layer info instead of creating a new one + LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), OutPackage, FName(*InPackageName)); + } + + if (!LayerInfo || LayerInfo->IsPendingKill()) + { + // Create a new LandscapeLayerInfoObject in the package + LayerInfo = NewObject(OutPackage, FName(*InPackageName), RF_Public | RF_Standalone /*| RF_Transactional*/); + + // Notify the asset registry + FAssetRegistryModule::AssetCreated(LayerInfo); + } + + if (LayerInfo && !LayerInfo->IsPendingKill()) + { + LayerInfo->LayerName = FName(*InLayerName); + + // Trigger update of the Layer Info + LayerInfo->PreEditChange(nullptr); + LayerInfo->PostEditChange(); + LayerInfo->MarkPackageDirty(); + + // Mark the package dirty... + OutPackage->MarkPackageDirty(); + } + + return LayerInfo; +} + +bool +FHoudiniLandscapeTranslator::CalcHeightGlobalZminZMax( + const TArray& AllOutputs, float& OutGlobalMin, float& OutGlobalMax) +{ + OutGlobalMin = 0.f; + OutGlobalMax = 0.f; + + for (const auto& CurrentOutput : AllOutputs) + { + if (!CurrentOutput) + continue; + + if (CurrentOutput->GetType() != EHoudiniOutputType::Landscape) + continue; + + const TArray& HGPOs = CurrentOutput->GetHoudiniGeoPartObjects(); + for (const FHoudiniGeoPartObject& CurrentHGPO : HGPOs) + { + if (CurrentHGPO.Type != EHoudiniPartType::Volume) + continue; + + if (!CurrentHGPO.VolumeInfo.Name.Contains("height")) + continue; + + // We're only handling single values for now + if (CurrentHGPO.VolumeInfo.TupleSize != 1) + continue; + + // Terrains always have a ZSize of 1. + if (CurrentHGPO.VolumeInfo.ZLength != 1) + continue; + + // Values should be float + if (!CurrentHGPO.VolumeInfo.bIsFloat) + continue; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CurrentHGPO.GeoId)) + continue; + + // Retrieve the VolumeInfo + HAPI_VolumeInfo CurrentVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentVolumeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHGPO.GeoId, CurrentHGPO.PartId, &CurrentVolumeInfo)) + continue; + + // Unreal's Z values are Y in Houdini + float yMin = OutGlobalMin, yMax = OutGlobalMax; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeBounds(FHoudiniEngine::Get().GetSession(), + CurrentHGPO.GeoId, CurrentHGPO.PartId, + nullptr, &yMin, nullptr, + nullptr, &yMax, nullptr, + nullptr, nullptr, nullptr)) + continue; + + if (yMin < OutGlobalMin) + OutGlobalMin = yMin; + + if (yMax > OutGlobalMax) + OutGlobalMax = yMax; + } + + if (OutGlobalMin > OutGlobalMax) + { + OutGlobalMin = 0.f; + OutGlobalMax = 0.f; + } + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::EnableWorldComposition() +{ + HOUDINI_LOG_WARNING(TEXT("[FHoudiniLandscapeTranslator::EnableWorldComposition] We should never enable world composition from within the plugin.")); + // Get the world + UWorld* MyWorld = nullptr; + { + // We want to create the landscape in the landscape editor mode's world + FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext(); + MyWorld = EditorWorldContext.World(); + } + + if (!MyWorld) + return false; + + ULevel* CurrentLevel = MyWorld->GetCurrentLevel(); + + if (!CurrentLevel) + return false; + + AWorldSettings* WorldSettings = CurrentLevel->GetWorldSettings(); + if (!WorldSettings) + return false; + + // Enable world composition in WorldSettings + WorldSettings->bEnableWorldComposition = true; + + CurrentLevel->PostEditChange(); + + return true; +} + +bool +FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( + UObject* InObject, const TArray& InAllPropertyAttributes) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Iterate over the found Property attributes + int32 NumSuccess = 0; + for (const auto& CurrentPropAttribute : InAllPropertyAttributes) + { + // Update the current Property Attribute + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(InObject, CurrentPropAttribute)) + continue; + + // Success! + NumSuccess++; + FString ClassName = InObject->GetClass() ? InObject->GetClass()->GetName() : TEXT("Object"); + FString ObjectName = InObject->GetName(); + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on %s named %s"), *CurrentPropAttribute.AttributeName, *ClassName, *ObjectName); + } + + return (NumSuccess > 0); +} + + +bool +FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(const FString& BaseName, ALandscapeProxy* Landscape) +{ + // We need to cache the input landscape to a file + if (!Landscape) + return false; + + ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Save Height data to file + //FString HeightSave = TEXT("/Game/HoudiniEngine/Temp/HeightCache.png"); + FString HeightSave = BaseName + TEXT("_height.png"); + LandscapeInfo->ExportHeightmap(HeightSave); + Landscape->ReimportHeightmapFilePath = HeightSave; + + // Save each layer to a file + for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) + { + FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); + //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; + if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + continue; + + FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); + LandscapeInfo->ExportLayer(CurrentLayerInfo, LayerSave); + + // Update the file reimport path on the input landscape for this layer + LandscapeInfo->GetLayerEditorSettings(CurrentLayerInfo).ReimportLayerFilePath = LayerSave; + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy) +{ + if (!LandscapeProxy) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Restore Height data from the backup file + FString ReimportFile = LandscapeProxy->ReimportHeightmapFilePath; + if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, TEXT("height"))) + HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); + + // Restore each layer from the backup file + TArray< ULandscapeLayerInfoObject* > SourceLayers; + for (int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++) + { + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; + if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + continue; + + FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); + ReimportFile = LandscapeProxy->EditorLayerSettings[LayerIndex].ReimportLayerFilePath; + + if (!FHoudiniLandscapeTranslator::ImportLandscapeData(LandscapeInfo, ReimportFile, CurrentLayerName, CurrentLayerInfo)) + HOUDINI_LOG_ERROR(TEXT("Could not restore the landscape actor's source height data.")); + + SourceLayers.Add(CurrentLayerInfo); + } + + // Iterate on the landscape info's layer to remove any layer that could have been added by Houdini + for (int LayerIndex = 0; LayerIndex < LandscapeInfo->Layers.Num(); LayerIndex++) + { + ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; + if (SourceLayers.Contains(CurrentLayerInfo)) + continue; + + // Delete the added layer + FName LayerName = LandscapeInfo->Layers[LayerIndex].LayerName; + LandscapeInfo->DeleteLayer(CurrentLayerInfo, LayerName); + } + + return true; +} + + +bool +FHoudiniLandscapeTranslator::ImportLandscapeData( + ULandscapeInfo* LandscapeInfo, const FString& Filename, const FString& LayerName, ULandscapeLayerInfoObject* LayerInfoObject) +{ + // + // Code copied/edited from FEdModeLandscape::ImportData as we cannot access that function + // + if (!LandscapeInfo) + return false; + + bool IsHeight = LayerName.Equals(TEXT("height"), ESearchCase::IgnoreCase); + + int32 MinX, MinY, MaxX, MaxY; + if (LandscapeInfo && LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + { + const FLandscapeFileResolution LandscapeResolution = { (uint32)(1 + MaxX - MinX), (uint32)(1 + MaxY - MinY) }; + + ILandscapeEditorModule& LandscapeEditorModule = FModuleManager::GetModuleChecked("LandscapeEditor"); + + if (IsHeight) + { + const ILandscapeHeightmapFileFormat* HeightmapFormat = LandscapeEditorModule.GetHeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); + + if (!HeightmapFormat) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); + return false; + } + + FLandscapeFileResolution ImportResolution = { 0, 0 }; + + const FLandscapeHeightmapInfo HeightmapInfo = HeightmapFormat->Validate(*Filename); + + // display error message if there is one, and abort the import + if (HeightmapInfo.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); + return false; + } + + // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape + if (HeightmapInfo.PossibleResolutions.Num() > 1) + { + if (!HeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The heightmap file does not match the Landscape extent and its exact resolution could not be determined")); + return false; + } + else + { + ImportResolution = LandscapeResolution; + } + } + + // display warning message if there is one and allow user to cancel + if (HeightmapInfo.ResultCode == ELandscapeImportResult::Warning) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(HeightmapInfo.ErrorMessage.ToString())); + + // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape + // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is + if (HeightmapInfo.PossibleResolutions.Num() == 1) + { + ImportResolution = HeightmapInfo.PossibleResolutions[0]; + if (ImportResolution != LandscapeResolution) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); + } + + FLandscapeHeightmapImportData ImportData = HeightmapFormat->Import(*Filename, ImportResolution); + if (ImportData.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); + return false; + } + + TArray Data; + if (ImportResolution != LandscapeResolution) + { + // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked + // so that reimports behave the same as the initial import :) + + const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; + const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; + + Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint16)); + + ExpandData(Data.GetData(), ImportData.Data.GetData(), + 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, + -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); + } + else + { + Data = MoveTemp(ImportData.Data); + } + + //FScopedTransaction Transaction(TEXT("Undo_ImportHeightmap", "Importing Landscape Heightmap")); + + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData()); + } + else + { + // We're importing a Landscape layer + if (!LayerInfoObject || LayerInfoObject->IsPendingKill()) + return false; + + const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); + if (!WeightmapFormat) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, File type not recognised"), *LayerName); + return false; + } + + FLandscapeFileResolution ImportResolution = { 0, 0 }; + + const FLandscapeWeightmapInfo WeightmapInfo = WeightmapFormat->Validate(*Filename, FName(*LayerName)); + + // display error message if there is one, and abort the import + if (WeightmapInfo.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s, %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); + return false; + } + + // if the file is a raw format with multiple possibly resolutions, only attempt import if one matches the current landscape + if (WeightmapInfo.PossibleResolutions.Num() > 1) + { + if (!WeightmapInfo.PossibleResolutions.Contains(LandscapeResolution)) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. The weightmap file does not match the Landscape extent and its exact resolution could not be determined")); + return false; + } + else + { + ImportResolution = LandscapeResolution; + } + } + + // display warning message if there is one and allow user to cancel + if (WeightmapInfo.ResultCode == ELandscapeImportResult::Warning) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. %s"), *LayerName, *(WeightmapInfo.ErrorMessage.ToString())); + + // if the file is a format with resolution information, warn the user if the resolution doesn't match the current landscape + // unlike for raw this is only a warning as we can pad/clip the data if we know what resolution it is + if (WeightmapInfo.PossibleResolutions.Num() == 1) + { + ImportResolution = WeightmapInfo.PossibleResolutions[0]; + if (ImportResolution != LandscapeResolution) + HOUDINI_LOG_WARNING(TEXT("When reimporting the input heightfield's source data for %s. The heightmap file's size does not match the current Landscape extent, data will be padded/clipped"), *LayerName); + } + + FLandscapeWeightmapImportData ImportData = WeightmapFormat->Import(*Filename, FName(*LayerName), ImportResolution); + + if (ImportData.ResultCode == ELandscapeImportResult::Error) + { + HOUDINI_LOG_ERROR(TEXT("Could not reimport the input heightfield's source data for %s. %s"), *LayerName, *(ImportData.ErrorMessage.ToString())); + return false; + } + + TArray Data; + if (ImportResolution != LandscapeResolution) + { + // Cloned from FLandscapeEditorDetailCustomization_NewLandscape.OnCreateButtonClicked + // so that reimports behave the same as the initial import :) + const int32 OffsetX = (int32)(LandscapeResolution.Width - ImportResolution.Width) / 2; + const int32 OffsetY = (int32)(LandscapeResolution.Height - ImportResolution.Height) / 2; + + Data.SetNumUninitialized(LandscapeResolution.Width * LandscapeResolution.Height * sizeof(uint8)); + + ExpandData(Data.GetData(), ImportData.Data.GetData(), + 0, 0, ImportResolution.Width - 1, ImportResolution.Height - 1, + -OffsetX, -OffsetY, LandscapeResolution.Width - OffsetX - 1, LandscapeResolution.Height - OffsetY - 1); + } + else + { + Data = MoveTemp(ImportData.Data); + } + + //FScopedTransaction Transaction(LOCTEXT("Undo_ImportWeightmap", "Importing Landscape Layer")); + FAlphamapAccessor AlphamapAccessor(LandscapeInfo, LayerInfoObject); + AlphamapAccessor.SetData(MinX, MinY, MaxX, MaxY, Data.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } + + return true; +} + +UTexture2D* +FHoudiniLandscapeTranslator::CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& InFloatBuffer, + const float& InMin, + const float& InMax) +{ + + // Convert the float values to uint8 + double Range = (double)InMax - (double)InMin; + TArray IntBuffer; + IntBuffer.SetNum(InFloatBuffer.Num()); + for(int32 i = 0; i < InFloatBuffer.Num(); i++) + { + double dNormalizedValue = ((double)InFloatBuffer[i] - (double)InMin) / (double)Range; + IntBuffer[i] = (uint8)(dNormalizedValue * 255.0); + } + + return FHoudiniLandscapeTranslator::CreateUnrealTexture( + InPackageParams, LayerName, InXSize, InYSize, IntBuffer); +} + +UTexture2D* +FHoudiniLandscapeTranslator::CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& IntBuffer) +{ + FHoudiniPackageParams MyPackageParams = InPackageParams; + MyPackageParams.ObjectName = LayerName; + MyPackageParams.PackageMode = EPackageMode::CookToTemp; + MyPackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + FString CreatedPackageName; + UPackage* Package = MyPackageParams.CreatePackageForObject(CreatedPackageName); + if (!Package || Package->IsPendingKill()) + return nullptr; + + // Create new texture object. + UTexture2D * Texture = NewObject(Package, UTexture2D::StaticClass(), *LayerName, RF_Public | RF_Standalone); + + // Add/Update meta information to package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *LayerName); + + /*// Texture Settings + Texture->PlatformData = new FTexturePlatformData(); + Texture->PlatformData->SizeX = InXSize; + Texture->PlatformData->SizeY = InYSize; + Texture->PlatformData->PixelFormat = PF_R8G8B8A8;*/ + + // Initialize texture source. + Texture->Source.Init(InXSize, InYSize, 1, 1, TSF_BGRA8); + + // Lock the texture. + uint8 * MipData = Texture->Source.LockMip(0); + + // Create base map. + uint8* DestPtr = nullptr; + uint32 SrcWidth = InXSize; + uint32 SrcHeight = InYSize; + const uint8 * SrcData = &IntBuffer[0]; + + for (uint32 y = 0; y < SrcHeight; y++) + { + DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; + + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth + x; + + *DestPtr++ = *(SrcData + DataOffset); // B greyscale, same value 3 times + *DestPtr++ = *(SrcData + DataOffset); // G + *DestPtr++ = *(SrcData + DataOffset); // R + + *DestPtr++ = 0xFF; // A to 1 + } + } + + // Unlock the texture. + Texture->Source.UnlockMip(0); + + // Texture creation parameters. + //Texture->SRGB = TextureParameters.bSRGB; + Texture->CompressionSettings = TC_Grayscale; + Texture->CompressionNoAlpha = true; + Texture->MipGenSettings = TMGS_NoMipmaps; + + // Set the Source Guid/Hash if specified. + /* + if ( TextureParameters.SourceGuidHash.IsValid() ) + { + Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); + } + */ + + // Updating Texture & mark it as unsaved + //Texture->AddToRoot(); + //Texture->UpdateResource(); + Package->MarkPackageDirty(); + + Texture->PostEditChange(); + + FString PathName = Texture->GetPathName(); + HOUDINI_LOG_MESSAGE(TEXT("Created texture when for %s in %s"), *LayerName, *PathName); + + return Texture; +} + +UPhysicalMaterial* +FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO) +{ + // See if we have assigned a physical material to this layer via attribute + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + TArray AttributeValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InLayerHGPO.GeoId, InLayerHGPO.PartId, HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL, + AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM, 0, 1)) + return nullptr; + + if (AttributeValues.Num() > 0) + { + return LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); + } + + return nullptr; +} + +ULandscapeLayerInfoObject* +FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName) +{ + // See if we have assigned a landscape layer info object to this layer via attribute + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + TArray AttributeValues; + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InLayerHGPO.GeoId, InLayerHGPO.PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO, + AttributeInfo, AttributeValues, 1, HAPI_ATTROWNER_PRIM, 0, 1)) + return nullptr; + + if (AttributeValues.Num() > 0) + { + ULandscapeLayerInfoObject* FoundLayerInfo = LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); + if (!FoundLayerInfo || FoundLayerInfo->IsPendingKill()) + return nullptr; + + // The layer info's name must match this layer's name or Unreal will not like this! + if (!FoundLayerInfo->LayerName.IsEqual(InLayerName)) + { + FString NameStr = InLayerName.ToString(); + HOUDINI_LOG_WARNING(TEXT("Failed to use the assigned layer info object for %s by the unreal_landscape_layer_info attribute as the found layer info object's layer name does not match."), *NameStr); + } + + return FoundLayerInfo; + } + + return nullptr; +} + + + diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h index 07ffb3c84..2bd8c3c75 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h @@ -1,459 +1,460 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "Landscape.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "Engine/World.h" -#include "EngineUtils.h" -#include "HoudiniEngineOutputStats.h" -#include "HoudiniPackageParams.h" -#include "HoudiniTranslatorTypes.h" - -class UHoudiniAssetComponent; -class ULandscapeLayerInfoObject; -struct FHoudiniGenericAttribute; -struct FHoudiniPackageParams; - -struct HOUDINIENGINE_API FHoudiniLandscapeTranslator -{ - public: - enum class LandscapeActorType : uint8 - { - LandscapeActor = 0, - LandscapeStreamingProxy = 1, - }; - - static bool CreateLandscape( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedActors, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* World, - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniLandscapeExtent& LandscapeExtent, - FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, - FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, - FHoudiniPackageParams InPackageParams, - TSet& ClearedLayers, - TArray& OutCreatedPackages); - - static bool OutputLandscape_Temp( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedActors, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* World, - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniLandscapeExtent& LandscapeExtent, - FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, - FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, - FHoudiniPackageParams InPackageParams, - TArray& OutCreatedPackages); - - // Outputting landscape as "editable layers" differs significantly from - // landscape outputs in "temp mode". To avoid a bigger spaghetti mess, we're - // dealing with editable layers completely separately. - static bool OutputLandscape_EditableLayer( - UHoudiniOutput* InOutput, - TArray>& CreatedUntrackedActors, - TArray& InputLandscapesToUpdate, - const TArray& InAllInputLandscapes, - USceneComponent* SharedLandscapeActorParent, - const FString& DefaultLandscapeActorPrefix, - UWorld* World, - const TMap& LayerMinimums, - const TMap& LayerMaximums, - FHoudiniLandscapeExtent& LandscapeExtent, - FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, - FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, - FHoudiniPackageParams InPackageParams, - TSet& ClearedLayers, - TArray& OutCreatedPackages); - - static ALandscapeProxy* FindExistingLandscapeActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - static ALandscapeProxy* FindTargetLandscapeProxy( - const FString& ActorName, - UWorld* World, - const TArray& LandscapeInputs - ); - - protected: - - static bool IsLandscapeInfoCompatible( - const ULandscapeInfo* LandscapeInfo, - const int32 NumSectionsX, - const int32 NumSectionsY); - - static bool IsLandscapeTileCompatible( - const ALandscapeProxy* TileActor, - const int32 InTileSizeX, - const int32 InTileSizeY, - const int32 InNumSectionsPerComponent, - const int32 InNumQuadsPerSection); - - static bool IsLandscapeTypeCompatible( - const AActor* Actor, - LandscapeActorType ActorType); - - static bool PopulateLandscapeExtents( - FHoudiniLandscapeExtent& Extent, - const ULandscapeInfo* LandscapeInfo - ); - - /** - * Find a ALandscapeProxy actor that can be reused. It is important - * to note that the request landscape actor could not be found, - * `OutWorld` and `OutLevel` should be used to spawn the - * new landscape actor. - * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. - */ - static ALandscapeProxy* FindExistingLandscapeActor( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage, - const EPackageMode& InPackageMode); - - static ALandscapeProxy* FindExistingLandscapeActor_Temp( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const TArray& ValidLandscapes, - int32 UnrealLandscapeSizeX, - int32 UnrealLandscapeSizeY, - const FString& InActorName, - const FString& InPackagePath, // Package path to search if not found in the world - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - /** - * Attempt the given ALandscapeActor to the outer HAC. Note - * that certain package modes (such as Bake) may choose not to do so. - * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. - */ - static void SetLandscapeActorAsOutput( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor, - const EPackageMode InPackageMode); - - static void SetLandscapeActorAsOutput_Bake( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor); - - static void SetLandscapeActorAsOutput_Temp( - UHoudiniOutput* InOutput, - const TArray& InAllInputLandscapes, - const TMap& OutputAttributes, - const TMap& OutputArguments, - ALandscape* SharedLandscapeActor, - USceneComponent* SharedLandscapeActorParent, - bool bCreatedMainLandscape, - const FHoudiniOutputObjectIdentifier& Identifier, - ALandscapeProxy* LandscapeActor); - - /** - * Attach the given actor the HoudiniAssetComponent that - * owns `InOutput`, if any. - * @returns True if the actor was attached. Otherwise, return false. - */ - static bool AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor); - - /** - * Get the actor name suffix to be used in the specific packaging mode. - * @returns Suffix for actor names, return as an FString. - */ - static FString GetActorNameSuffix(const EPackageMode& InPackageMode); - - - // Helpers to get rid of repetitive boilerplate. - static void DoPreEditChangeProperty(UObject* Obj, FName PropertyName); - static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); - - public: - - - static const FHoudiniGeoPartObject* GetHoudiniHeightFieldFromOutput( - UHoudiniOutput* InOutput); - - static void GetHeightfieldsLayersFromOutput( - const UHoudiniOutput* InOutput, - const FHoudiniGeoPartObject& Heightfield, - TArray< const FHoudiniGeoPartObject* >& FoundLayers); - - static bool GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo); - - static bool GetHoudiniHeightfieldFloatData( - const FHoudiniGeoPartObject* HGPO, - TArray &OutFloatArr, - float &OutFloatMin, - float &OutFloatMax); - - static bool CalcLandscapeSizeFromHeightfieldSize( - const int32& HoudiniSizeX, - const int32& HoudiniSizeY, - int32& UnrealSizeX, - int32& UnrealSizeY, - int32& NumSectionsPerComponent, - int32& NumQuadsPerSection); - - static bool ConvertHeightfieldDataToLandscapeData( - const TArray< float >& HeightfieldFloatValues, - const FHoudiniVolumeInfo& HeightfieldVolumeInfo, - const int32& FinalXSize, - const int32& FinalYSize, - float FloatMin, - float FloatMax, - TArray< uint16 >& IntHeightData, - FTransform& LandscapeTransform, - const bool NoResize = false, - const bool bOverrideZScale = false, - const float CustomZScale = 100.f); - - static bool ResizeHeightDataForLandscape( - TArray& HeightData, - const int32& SizeX, - const int32& SizeY, - const int32& NewSizeX, - const int32& NewSizeY, - FVector& LandscapeResizeFactor, - FVector& LandscapePositionOffset); - - static bool CreateOrUpdateLandscapeLayers( - const TArray& FoundLayers, - const FHoudiniGeoPartObject& HeightField, - const int32& LandscapeXSize, - const int32& LandscapeYSize, - const TMap &GlobalMinimums, - const TMap &GlobalMaximums, - TArray& OutLayerInfos, - bool bIsUpdate, - const FHoudiniPackageParams& InTilePackageParams, - const FHoudiniPackageParams& InLayerPackageParams, - TArray& OutCreatedPackages); - - static bool GetNonWeightBlendedLayerNames( - const FHoudiniGeoPartObject& HeightfieldGeoPartObject, - TArray& NonWeightBlendedLayerNames); - - static bool IsUnitLandscapeLayer( - const FHoudiniGeoPartObject& LayerGeoPartObject); - - // Return the height min/max values for all - static bool CalcHeightGlobalZminZMax( - const TArray& AllOutputs, - float& OutGlobalMin, - float& OutGlobalMax); - - // Returns the min/max values per layer/volume for an array of volumes/heightfields - static void CalcHeightfieldsArrayGlobalZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums, - bool bShouldEmptyMaps=true); - - // Iterate over layers for the heightfields and retrieve min/max values - // from attributes, otherwise return default values. - static void GetLayersZMinZMax( - const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, - TMap& GlobalMinimums, - TMap& GlobalMaximums); - - static bool ConvertHeightfieldLayerToLandscapeLayer( - const TArray& FloatLayerData, - const int32& HoudiniXSize, - const int32& HoudiniYSize, - const float& LayerMin, - const float& LayerMax, - const int32& LandscapeXSize, - const int32& LandscapeYSize, - TArray& LayerData, - const bool& NoResize = false); - - static bool ResizeLayerDataForLandscape( - TArray< uint8 >& LayerData, - const int32& SizeX, - const int32& SizeY, - const int32& NewSizeX, - const int32& NewSizeY); - - // static ALandscapeProxy * CreateLandscape( - // const TArray< uint16 >& IntHeightData, - // const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - // const FTransform& LandscapeTransform, - // const int32& XSize, - // const int32& YSize, - // const int32& NumSectionPerLandscapeComponent, - // const int32& NumQuadsPerLandscapeSection, - // UMaterialInterface* LandscapeMaterial, - // UMaterialInterface* LandscapeHoleMaterial, - // const bool& CreateLandscapeStreamingProxy, - // bool bNeedCreateNewWorld, - // UWorld* SpawnWorld, - // FHoudiniPackageParams InPackageParams, - // bool& bOutCreatedNewMap); - - static ALandscapeProxy* CreateLandscapeTileInWorld( - const TArray< uint16 >& IntHeightData, - const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, - const FTransform& TileTransform, - const FIntPoint& TileLocation, - const int32& XSize, - const int32& YSize, - const int32& NumSectionPerLandscapeComponent, - const int32& NumQuadsPerLandscapeSection, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* LandscapePhysicalMaterial, - const FString& LandscapeTileActorName, - LandscapeActorType ActorType, - ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies - UWorld* InWorld, // World in which to spawn - ULevel* InLevel, // Level, contained in World, in which to spawn. - FHoudiniPackageParams InPackageParams); - - // Destroy the given landscape and all its proxies - static void DestroyLandscape(ALandscape* Landscape); - -protected: - /** - * Calculate the location of a landscape tile. - * This location is typically used in conjunction with ALandscapeProxy::SetAbsoluteSectionBase(). - */ - static void CalculateTileLocation( - int32 NumSectionsPerComponent, - int32 NumQuadsPerSection, - const FTransform& InTileTransform, - FHoudiniLandscapeReferenceLocation& RefLoc, - FTransform& OutLandscapeTransform, - FIntPoint& OutTileLocation); - -public: - - static void GetLandscapeMaterials( - const FHoudiniGeoPartObject& InHeightHGPO, - UMaterialInterface*& OutLandscapeMaterial, - UMaterialInterface*& OutLandscapeHoleMaterial, - UPhysicalMaterial*& OutLandscapePhysicalMaterial); - - static bool GetLandscapeComponentExtentAttributes( - const FHoudiniGeoPartObject& HoudiniGeoPartObject, - int32& MinX, - int32& MaxX, - int32& MinY, - int32& MaxY); - - static ULandscapeLayerInfoObject* FindOrCreateLandscapeLayerInfoObject( - const FString& InLayerName, - const FString& InPackagePath, - const FString& InPackageName, - UPackage*& OutPackage); - - static bool EnableWorldComposition(); - - static bool GetGenericPropertiesAttributes( - const HAPI_NodeId& InGeoNodeId, - const HAPI_PartId& InPartId, - const int32& InPrimIndex, - TArray& OutPropertyAttributes); - - static bool UpdateGenericPropertiesAttributes( - UObject* InObject, - const TArray& InAllPropertyAttributes); - - static bool BackupLandscapeToImageFiles( - const FString& BaseName, ALandscapeProxy* Landscape); - - static bool RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy); - - static UPhysicalMaterial* GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO); - - static ULandscapeLayerInfoObject* GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName); - - private: - - static bool ImportLandscapeData( - ULandscapeInfo* LandscapeInfo, - const FString& Filename, - const FString& LayerName, - ULandscapeLayerInfoObject* LayerInfoObject = nullptr); - - static UTexture2D* CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& InFloatBuffer, - const float& InMin, - const float& InMax); - - static UTexture2D* CreateUnrealTexture( - const FHoudiniPackageParams& InPackageParams, - const FString& LayerName, - const int32& InXSize, - const int32& InYSize, - const TArray& IntBuffer); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "Landscape.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "Engine/World.h" +#include "EngineUtils.h" +#include "HoudiniEngineOutputStats.h" +#include "HoudiniPackageParams.h" +#include "HoudiniTranslatorTypes.h" + +class UHoudiniAssetComponent; +class ULandscapeLayerInfoObject; +struct FHoudiniGenericAttribute; +struct FHoudiniPackageParams; + +struct HOUDINIENGINE_API FHoudiniLandscapeTranslator +{ + public: + enum class LandscapeActorType : uint8 + { + LandscapeActor = 0, + LandscapeStreamingProxy = 1, + }; + + static bool CreateLandscape( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TSet& ClearedLayers, + TArray& OutCreatedPackages); + + static bool OutputLandscape_Temp( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TArray& OutCreatedPackages); + + // Outputting landscape as "editable layers" differs significantly from + // landscape outputs in "temp mode". To avoid a bigger spaghetti mess, we're + // dealing with editable layers completely separately. + static bool OutputLandscape_EditableLayer( + UHoudiniOutput* InOutput, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + TSet& ClearedLayers, + TArray& OutCreatedPackages); + + static ALandscapeProxy* FindExistingLandscapeActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + static ALandscapeProxy* FindTargetLandscapeProxy( + const FString& ActorName, + UWorld* World, + const TArray& LandscapeInputs + ); + + protected: + + static bool IsLandscapeInfoCompatible( + const ULandscapeInfo* LandscapeInfo, + const int32 NumSectionsX, + const int32 NumSectionsY); + + static bool IsLandscapeTileCompatible( + const ALandscapeProxy* TileActor, + const int32 InTileSizeX, + const int32 InTileSizeY, + const int32 InNumSectionsPerComponent, + const int32 InNumQuadsPerSection); + + static bool IsLandscapeTypeCompatible( + const AActor* Actor, + LandscapeActorType ActorType); + + static bool PopulateLandscapeExtents( + FHoudiniLandscapeExtent& Extent, + const ULandscapeInfo* LandscapeInfo + ); + + /** + * Find a ALandscapeProxy actor that can be reused. It is important + * to note that the request landscape actor could not be found, + * `OutWorld` and `OutLevel` should be used to spawn the + * new landscape actor. + * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. + */ + static ALandscapeProxy* FindExistingLandscapeActor( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage, + const EPackageMode& InPackageMode); + + static ALandscapeProxy* FindExistingLandscapeActor_Temp( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const TArray& ValidLandscapes, + int32 UnrealLandscapeSizeX, + int32 UnrealLandscapeSizeY, + const FString& InActorName, + const FString& InPackagePath, // Package path to search if not found in the world + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + /** + * Attempt the given ALandscapeActor to the outer HAC. Note + * that certain package modes (such as Bake) may choose not to do so. + * @returns ALandscapeProxy* if found. Otherwise, returns nullptr. + */ + static void SetLandscapeActorAsOutput( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor, + const EPackageMode InPackageMode); + + static void SetLandscapeActorAsOutput_Bake( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor); + + static void SetLandscapeActorAsOutput_Temp( + UHoudiniOutput* InOutput, + const TArray& InAllInputLandscapes, + const TMap& OutputAttributes, + const TMap& OutputArguments, + ALandscape* SharedLandscapeActor, + USceneComponent* SharedLandscapeActorParent, + bool bCreatedMainLandscape, + const FHoudiniOutputObjectIdentifier& Identifier, + ALandscapeProxy* LandscapeActor); + + /** + * Attach the given actor the HoudiniAssetComponent that + * owns `InOutput`, if any. + * @returns True if the actor was attached. Otherwise, return false. + */ + static bool AttachActorToHAC(UHoudiniOutput* InOutput, AActor* InActor); + + /** + * Get the actor name suffix to be used in the specific packaging mode. + * @returns Suffix for actor names, return as an FString. + */ + static FString GetActorNameSuffix(const EPackageMode& InPackageMode); + + + // Helpers to get rid of repetitive boilerplate. + static void DoPreEditChangeProperty(UObject* Obj, FName PropertyName); + static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); + + public: + + + static const FHoudiniGeoPartObject* GetHoudiniHeightFieldFromOutput( + UHoudiniOutput* InOutput); + + static void GetHeightfieldsLayersFromOutput( + const UHoudiniOutput* InOutput, + const FHoudiniGeoPartObject& Heightfield, + TArray< const FHoudiniGeoPartObject* >& FoundLayers); + + static bool GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo); + + static bool GetHoudiniHeightfieldFloatData( + const FHoudiniGeoPartObject* HGPO, + TArray &OutFloatArr, + float &OutFloatMin, + float &OutFloatMax); + + static bool CalcLandscapeSizeFromHeightfieldSize( + const int32& HoudiniSizeX, + const int32& HoudiniSizeY, + int32& UnrealSizeX, + int32& UnrealSizeY, + int32& NumSectionsPerComponent, + int32& NumQuadsPerSection); + + static bool ConvertHeightfieldDataToLandscapeData( + const TArray< float >& HeightfieldFloatValues, + const FHoudiniVolumeInfo& HeightfieldVolumeInfo, + const int32& FinalXSize, + const int32& FinalYSize, + float FloatMin, + float FloatMax, + TArray< uint16 >& IntHeightData, + FTransform& LandscapeTransform, + const bool NoResize = false, + const bool bOverrideZScale = false, + const float CustomZScale = 100.f); + + static bool ResizeHeightDataForLandscape( + TArray& HeightData, + const int32& SizeX, + const int32& SizeY, + const int32& NewSizeX, + const int32& NewSizeY, + FVector& LandscapeResizeFactor, + FVector& LandscapePositionOffset); + + static bool CreateOrUpdateLandscapeLayers( + const TArray& FoundLayers, + const FHoudiniGeoPartObject& HeightField, + const int32& LandscapeXSize, + const int32& LandscapeYSize, + const TMap &GlobalMinimums, + const TMap &GlobalMaximums, + TArray& OutLayerInfos, + bool bIsUpdate, + const FHoudiniPackageParams& InTilePackageParams, + const FHoudiniPackageParams& InLayerPackageParams, + TArray& OutCreatedPackages); + + static bool GetNonWeightBlendedLayerNames( + const FHoudiniGeoPartObject& HeightfieldGeoPartObject, + TArray& NonWeightBlendedLayerNames); + + static bool IsUnitLandscapeLayer( + const FHoudiniGeoPartObject& LayerGeoPartObject); + + // Return the height min/max values for all + static bool CalcHeightGlobalZminZMax( + const TArray& AllOutputs, + float& OutGlobalMin, + float& OutGlobalMax); + + // Returns the min/max values per layer/volume for an array of volumes/heightfields + static void CalcHeightfieldsArrayGlobalZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums, + bool bShouldEmptyMaps=true); + + // Iterate over layers for the heightfields and retrieve min/max values + // from attributes, otherwise return default values. + static void GetLayersZMinZMax( + const TArray< FHoudiniGeoPartObject > & InHeightfieldArray, + TMap& GlobalMinimums, + TMap& GlobalMaximums); + + static bool ConvertHeightfieldLayerToLandscapeLayer( + const TArray& FloatLayerData, + const int32& HoudiniXSize, + const int32& HoudiniYSize, + const float& LayerMin, + const float& LayerMax, + const int32& LandscapeXSize, + const int32& LandscapeYSize, + TArray& LayerData, + const bool& NoResize = false); + + static bool ResizeLayerDataForLandscape( + TArray< uint8 >& LayerData, + const int32& SizeX, + const int32& SizeY, + const int32& NewSizeX, + const int32& NewSizeY); + + // static ALandscapeProxy * CreateLandscape( + // const TArray< uint16 >& IntHeightData, + // const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + // const FTransform& LandscapeTransform, + // const int32& XSize, + // const int32& YSize, + // const int32& NumSectionPerLandscapeComponent, + // const int32& NumQuadsPerLandscapeSection, + // UMaterialInterface* LandscapeMaterial, + // UMaterialInterface* LandscapeHoleMaterial, + // const bool& CreateLandscapeStreamingProxy, + // bool bNeedCreateNewWorld, + // UWorld* SpawnWorld, + // FHoudiniPackageParams InPackageParams, + // bool& bOutCreatedNewMap); + + static ALandscapeProxy* CreateLandscapeTileInWorld( + const TArray< uint16 >& IntHeightData, + const TArray< FLandscapeImportLayerInfo >& ImportLayerInfos, + const FTransform& TileTransform, + const FIntPoint& TileLocation, + const int32& XSize, + const int32& YSize, + const int32& NumSectionPerLandscapeComponent, + const int32& NumQuadsPerLandscapeSection, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* LandscapePhysicalMaterial, + const FString& LandscapeTileActorName, + LandscapeActorType ActorType, + ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies + UWorld* InWorld, // World in which to spawn + ULevel* InLevel, // Level, contained in World, in which to spawn. + FHoudiniPackageParams InPackageParams); + + // Destroy the given landscape and all its proxies + static void DestroyLandscape(ALandscape* Landscape); + +protected: + /** + * Calculate the location of a landscape tile. + * This location is typically used in conjunction with ALandscapeProxy::SetAbsoluteSectionBase(). + */ + static void CalculateTileLocation( + int32 NumSectionsPerComponent, + int32 NumQuadsPerSection, + const FTransform& InTileTransform, + FHoudiniLandscapeReferenceLocation& RefLoc, + FTransform& OutLandscapeTransform, + FIntPoint& OutTileLocation); + +public: + + static void GetLandscapeMaterials( + const FHoudiniGeoPartObject& InHeightHGPO, + const FHoudiniPackageParams& InPackageParams, + UMaterialInterface*& OutLandscapeMaterial, + UMaterialInterface*& OutLandscapeHoleMaterial, + UPhysicalMaterial*& OutLandscapePhysicalMaterial); + + static bool GetLandscapeComponentExtentAttributes( + const FHoudiniGeoPartObject& HoudiniGeoPartObject, + int32& MinX, + int32& MaxX, + int32& MinY, + int32& MaxY); + + static ULandscapeLayerInfoObject* FindOrCreateLandscapeLayerInfoObject( + const FString& InLayerName, + const FString& InPackagePath, + const FString& InPackageName, + UPackage*& OutPackage); + + static bool EnableWorldComposition(); + + static bool GetGenericPropertiesAttributes( + const HAPI_NodeId& InGeoNodeId, + const HAPI_PartId& InPartId, + const int32& InPrimIndex, + TArray& OutPropertyAttributes); + + static bool UpdateGenericPropertiesAttributes( + UObject* InObject, + const TArray& InAllPropertyAttributes); + + static bool BackupLandscapeToImageFiles( + const FString& BaseName, ALandscapeProxy* Landscape); + + static bool RestoreLandscapeFromImageFiles(ALandscapeProxy* LandscapeProxy); + + static UPhysicalMaterial* GetLandscapePhysicalMaterial(const FHoudiniGeoPartObject& InLayerHGPO); + + static ULandscapeLayerInfoObject* GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName); + + private: + + static bool ImportLandscapeData( + ULandscapeInfo* LandscapeInfo, + const FString& Filename, + const FString& LayerName, + ULandscapeLayerInfoObject* LayerInfoObject = nullptr); + + static UTexture2D* CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& InFloatBuffer, + const float& InMin, + const float& InMax); + + static UTexture2D* CreateUnrealTexture( + const FHoudiniPackageParams& InPackageParams, + const FString& LayerName, + const int32& InXSize, + const int32& InYSize, + const TArray& IntBuffer); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp index 3603b38f2..4ccc62672 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp @@ -1,3428 +1,3455 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniMaterialTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniPackageParams.h" - -#include "Materials/Material.h" -#include "Materials/MaterialInstance.h" -#include "Materials/MaterialInstanceConstant.h" -#include "PhysicalMaterials/PhysicalMaterial.h" - -#include "Materials/MaterialExpressionTextureSample.h" -#include "Materials/MaterialExpressionTextureCoordinate.h" -#include "Materials/MaterialExpressionConstant4Vector.h" -#include "Materials/MaterialExpressionConstant.h" -#include "Materials/MaterialExpressionMultiply.h" -#include "Materials/MaterialExpressionVertexColor.h" -#include "Materials/MaterialExpressionTextureSampleParameter2D.h" -#include "Materials/MaterialExpressionVectorParameter.h" -#include "Materials/MaterialExpressionScalarParameter.h" -#include "ImageUtils.h" -#include "PackageTools.h" -#include "AssetRegistryModule.h" -#include "UObject/MetaData.h" - -#if WITH_EDITOR - #include "Factories/MaterialFactoryNew.h" - #include "Factories/MaterialInstanceConstantFactoryNew.h" -#endif - -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeX = -400; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeY = -150; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepX = 220; -const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepY = 220; - -bool -FHoudiniMaterialTranslator::CreateHoudiniMaterials( - const HAPI_NodeId& InAssetId, - const FHoudiniPackageParams& InPackageParams, - const TArray& InUniqueMaterialIds, - const TArray& InUniqueMaterialInfos, - const TMap& InMaterials, - const TMap& InAllOutputMaterials, - TMap& OutMaterials, - TArray& OutPackages, - const bool& bForceRecookAll, - bool bInTreatExistingMaterialsAsUpToDate) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMaterialTranslator::CreateHoudiniMaterials")); - - if (InUniqueMaterialIds.Num() <= 0) - return false; - - if (InUniqueMaterialInfos.Num() != InUniqueMaterialIds.Num()) - return false; - - // Empty returned materials. - OutMaterials.Empty(); - - // Update context for generated materials (will trigger when object goes out of scope). - FMaterialUpdateContext MaterialUpdateContext; - - // Default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - OutMaterials.Add(HAPI_UNREAL_DEFAULT_MATERIAL_NAME, DefaultMaterial); - - // Factory to create materials. - UMaterialFactoryNew * MaterialFactory = NewObject(); - MaterialFactory->AddToRoot(); - - for (int32 MaterialIdx = 0; MaterialIdx < InUniqueMaterialIds.Num(); MaterialIdx++) - { - HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; - - const HAPI_MaterialInfo& MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; - if (!MaterialInfo.exists) - { - // The material does not exist, - // we will use the default Houdini material in this case. - continue; - } - - // Get the material node's node information. - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &NodeInfo)) - { - continue; - } - - FString MaterialName = TEXT(""); - if (!FHoudiniEngineString::ToFString(NodeInfo.nameSH, MaterialName)) - { - // shouldnt happen, give a generic name - HOUDINI_LOG_WARNING(TEXT("Failed to retrieve material name!")); - MaterialName = TEXT("Material_") + FString::FromInt(MaterialInfo.nodeId); - } - - FString MaterialPathName = TEXT(""); - if (!FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, MaterialInfo, MaterialPathName)) - continue; - - // Check first in the existing material map - UMaterial * Material = nullptr; - UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); - bool bCanReuseExistingMaterial = false; - if (FoundMaterial) - { - bCanReuseExistingMaterial = (bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll; - Material = Cast(*FoundMaterial); - } - - if(!Material || !bCanReuseExistingMaterial) - { - // Try to see if another output/part of this HDA has already recreated this material - // Since those materials have just been recreated, they are considered up to date and can always be reused. - FoundMaterial = InAllOutputMaterials.Find(MaterialPathName); - if (FoundMaterial) - { - Material = Cast(*FoundMaterial); - bCanReuseExistingMaterial = true; - } - } - - bool bCreatedNewMaterial = false; - if (Material && !Material->IsPendingKill()) - { - // If the cached material exists and is up to date, we can reuse it. - if (bCanReuseExistingMaterial) - { - OutMaterials.Add(MaterialPathName, Material); - continue; - } - } - else - { - // Previous Material was not found, we need to create a new one. - EObjectFlags ObjFlags = RF_Public | RF_Standalone; - - // Create material package and get material name. - FString MaterialPackageName; - UPackage * MaterialPackage = FHoudiniMaterialTranslator::CreatePackageForMaterial( - MaterialInfo.nodeId, MaterialName, InPackageParams, MaterialPackageName); - - Material = (UMaterial *)MaterialFactory->FactoryCreateNew( - UMaterial::StaticClass(), MaterialPackage, *MaterialPackageName, ObjFlags, NULL, GWarn); - - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialName); - - bCreatedNewMaterial = true; - } - - if (!Material || Material->IsPendingKill()) - continue; - - // Get the asset name from the package params - FString AssetName = InPackageParams.HoudiniAssetName.IsEmpty() ? TEXT("HoudiniAsset") : InPackageParams.HoudiniAssetName; - - // Get the package and add it to our list - UPackage* Package = Material->GetOutermost(); - OutPackages.AddUnique(Package); - - /* - // TODO: This should be handled in the mesh/instance translator - // If this is an instancer material, enable the instancing flag. - if (UniqueInstancerMaterialIds.Contains(MaterialId)) - Material->bUsedWithInstancedStaticMeshes = true; - */ - - // Reset material expressions. - Material->Expressions.Empty(); - - // Generate various components for this material. - bool bMaterialComponentCreated = false; - int32 MaterialNodeY = FHoudiniMaterialTranslator::MaterialExpressionNodeY; - - // By default we mark material as opaque. Some of component creators can change this. - Material->BlendMode = BLEND_Opaque; - - // Extract diffuse plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract opacity plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract opacity mask plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract normal plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentNormal( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract specular plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract roughness plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract metallic plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Extract emissive plane. - bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( - InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); - - // Set other material properties. - Material->TwoSided = true; - Material->SetShadingModel(MSM_DefaultLit); - - // Schedule this material for update. - MaterialUpdateContext.AddMaterial(Material); - - // Cache material. - OutMaterials.Add(MaterialPathName, Material); - - // Propagate and trigger material updates. - if (bCreatedNewMaterial) - FAssetRegistryModule::AssetCreated(Material); - - Material->PreEditChange(nullptr); - Material->PostEditChange(); - Material->MarkPackageDirty(); - } - - MaterialFactory->RemoveFromRoot(); - - return true; -} - -// -bool -FHoudiniMaterialTranslator::CreateMaterialInstances( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& UniqueMaterialInstanceOverrides, - const TArray& InPackages, - const TMap& InMaterials, - TMap& OutMaterials, - const bool& bForceRecookAll) -{ - // Check the node ID is valid - if (InHGPO.AssetId < 0) - return false; - - // No material instance attributes - if (UniqueMaterialInstanceOverrides.Num() <= 0) - return false; - - // TODO: Improve! - // Get the material name from the material_instance attribute - // Since the material instance attribute can be set per primitive, it's going to be very difficult to know - // exactly where to look for the nth material instance. In order for the material slot to be created, - // we used the fact that the material instance attribute had to be different - // This is pretty hacky and we should probably require an extra material_instance_index attribute instead. - // as we can only create one instance of the same material, and cant get two slots for the same "source" material. - int32 MaterialIndex = 0; - for (TMap::TConstIterator Iter(UniqueMaterialInstanceOverrides); Iter; ++Iter) - { - FString CurrentSourceMaterial = Iter->Key; - if (CurrentSourceMaterial.IsEmpty()) - continue; - - // Try to find the material we want to create an instance of - UMaterialInterface* CurrentSourceMaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentSourceMaterial, nullptr, LOAD_NoWarn, nullptr)); - - if (!CurrentSourceMaterialInterface || CurrentSourceMaterialInterface->IsPendingKill()) - { - // Couldn't find the source material - HOUDINI_LOG_WARNING(TEXT("Couldn't find the source material %s to create a material instance."), *CurrentSourceMaterial); - continue; - } - - // Create/Retrieve the package for the MI - FString MaterialInstanceName; - FString MaterialInstanceNamePrefix = UPackageTools::SanitizePackageName( - CurrentSourceMaterialInterface->GetName() + TEXT("_instance_") + FString::FromInt(MaterialIndex)); - - // Increase the material index - MaterialIndex++; - - // See if we can find an existing package for that instance - UPackage * MaterialInstancePackage = nullptr; - UMaterialInterface * const * FoundMatPtr = InMaterials.Find(MaterialInstanceNamePrefix); - if (FoundMatPtr && *FoundMatPtr) - { - // We found an already existing MI, get its package - MaterialInstancePackage = Cast((*FoundMatPtr)->GetOuter()); - } - - if (MaterialInstancePackage) - { - MaterialInstanceName = MaterialInstancePackage->GetName(); - } - else - { - // We couldnt find the corresponding M_I package, so create a new one - MaterialInstancePackage = CreatePackageForMaterial(InHGPO.AssetId, MaterialInstanceNamePrefix, InPackageParams, MaterialInstanceName); - } - - // Couldn't create a package for that Material Instance - if (!MaterialInstancePackage) - continue; - - bool bNewMaterialCreated = false; - UMaterialInstanceConstant* NewMaterialInstance = LoadObject(MaterialInstancePackage, *MaterialInstanceName, nullptr, LOAD_None, nullptr); - if (!NewMaterialInstance) - { - // Factory to create materials. - UMaterialInstanceConstantFactoryNew* MaterialInstanceFactory = NewObject< UMaterialInstanceConstantFactoryNew >(); - if (!MaterialInstanceFactory) - continue; - - // Create the new material instance - MaterialInstanceFactory->AddToRoot(); - MaterialInstanceFactory->InitialParent = CurrentSourceMaterialInterface; - NewMaterialInstance = (UMaterialInstanceConstant*)MaterialInstanceFactory->FactoryCreateNew( - UMaterialInstanceConstant::StaticClass(), MaterialInstancePackage, FName(*MaterialInstanceName), - RF_Public | RF_Standalone, NULL, GWarn); - - if (NewMaterialInstance) - bNewMaterialCreated = true; - - MaterialInstanceFactory->RemoveFromRoot(); - } - - if (!NewMaterialInstance) - { - HOUDINI_LOG_WARNING(TEXT("Couldn't access the material instance for %s"), *CurrentSourceMaterial); - continue; - } - - // Update context for generated materials (will trigger when the object goes out of scope). - FMaterialUpdateContext MaterialUpdateContext; - - bool bModifiedMaterialParameters = false; - // See if we need to override some of the material instance's parameters - TArray AllMatParams; - // Get the detail material parameters - int32 ParamCount = FHoudiniEngineUtils::GetGenericAttributeList( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, - AllMatParams, HAPI_ATTROWNER_DETAIL, -1); - - // Then the primitive material parameters - int32 MaterialIndexToAttributeIndex = Iter->Value; - ParamCount += FHoudiniEngineUtils::GetGenericAttributeList( - InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, - AllMatParams, HAPI_ATTROWNER_PRIM, MaterialIndexToAttributeIndex); - - for (int32 ParamIdx = 0; ParamIdx < AllMatParams.Num(); ParamIdx++) - { - // Try to update the material instance parameter corresponding to the attribute - if (UpdateMaterialInstanceParameter(AllMatParams[ParamIdx], NewMaterialInstance, InPackages)) - bModifiedMaterialParameters = true; - } - - // Schedule this material for update if needed. - if (bNewMaterialCreated || bModifiedMaterialParameters) - MaterialUpdateContext.AddMaterialInstance(NewMaterialInstance); - - if (bNewMaterialCreated) - { - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialInstanceName); - // Notify registry that we have created a new material. - FAssetRegistryModule::AssetCreated(NewMaterialInstance); - } - - if (bNewMaterialCreated || bModifiedMaterialParameters) - { - // Dirty the material - NewMaterialInstance->MarkPackageDirty(); - - // Update the material instance - NewMaterialInstance->InitStaticPermutation(); - NewMaterialInstance->PreEditChange(nullptr); - NewMaterialInstance->PostEditChange(); - /* - // Automatically save the package to avoid further issue - MaterialInstancePackage->SetDirtyFlag( true ); - MaterialInstancePackage->FullyLoad(); - UPackage::SavePackage( - MaterialInstancePackage, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, - *FPackageName::LongPackageNameToFilename( MaterialInstancePackage->GetName(), FPackageName::GetAssetPackageExtension() ) ); - */ - } - - // Add the created material to the output assignement map - // Use the "source" material name as we want the instance to replace it - OutMaterials.Add(CurrentSourceMaterial, NewMaterialInstance); - } - - return true; -} - -bool -FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath) -{ - HAPI_MaterialInfo MaterialInfo; - FHoudiniApi::MaterialInfo_Init(&MaterialInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetMaterialInfo( - FHoudiniEngine::Get().GetSession(), InMaterialNodeId, - &MaterialInfo), false); - - return GetMaterialRelativePath(InAssetId, MaterialInfo, OutRelativePath); -} -bool -FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialInfo, FString& OutRelativePath) -{ - if (InAssetId < 0 || !InMaterialInfo.exists) - return false; - - // We want to get the asset node path so we can remove it from the material name - FString AssetNodeName = TEXT(""); - { - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); - - HAPI_NodeInfo AssetNodeInfo; - FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo), false); - - FHoudiniEngineString::ToFString(AssetNodeInfo.internalNodePathSH, AssetNodeName); - } - - // Get the material name from the info - FString MaterialNodeName = TEXT(""); - { - HAPI_NodeInfo MaterialNodeInfo; - FHoudiniApi::NodeInfo_Init(&MaterialNodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &MaterialNodeInfo), false); - - FHoudiniEngineString::ToFString(MaterialNodeInfo.internalNodePathSH, MaterialNodeName); - } - - if (AssetNodeName.Len() > 0 && MaterialNodeName.Len() > 0) - { - // Remove AssetNodeName part from MaterialNodeName. Extra position is for separator. - OutRelativePath = MaterialNodeName.Mid(AssetNodeName.Len() + 1); - return true; - } - - return false; -} - - -UPackage* -FHoudiniMaterialTranslator::CreatePackageForMaterial( - const HAPI_NodeId& InMaterialNodeId, - const FString& InMaterialName, - const FHoudiniPackageParams& InPackageParams, - FString& OutMaterialName) -{ - FString MaterialDescriptor = TEXT("_material_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InMaterialName; - - FHoudiniPackageParams MyPackageParams = InPackageParams; - if (!MyPackageParams.ObjectName.IsEmpty()) - { - MyPackageParams.ObjectName += MaterialDescriptor; - } - else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) - { - MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + MaterialDescriptor; - } - else - { - MyPackageParams.ObjectName = MaterialDescriptor; - } - MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); - - return MyPackageParams.CreatePackageForObject(OutMaterialName); -} - - -UPackage* -FHoudiniMaterialTranslator::CreatePackageForTexture( - const HAPI_NodeId& InMaterialNodeId, - const FString& InTextureType, - const FHoudiniPackageParams& InPackageParams, - FString& OutTextureName) -{ - FString TextureInfoDescriptor = TEXT("_texture_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InTextureType; - FHoudiniPackageParams MyPackageParams = InPackageParams; - if (!MyPackageParams.ObjectName.IsEmpty()) - { - MyPackageParams.ObjectName += TextureInfoDescriptor; - } - else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) - { - MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + TextureInfoDescriptor; - } - else - { - MyPackageParams.ObjectName = TextureInfoDescriptor; - } - MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); - - return MyPackageParams.CreatePackageForObject(OutTextureName); -} - - -UTexture2D * -FHoudiniMaterialTranslator::CreateUnrealTexture( - UTexture2D* ExistingTexture, - const HAPI_ImageInfo& ImageInfo, - UPackage* Package, - const FString& TextureName, - const TArray& ImageBuffer, - const FCreateTexture2DParameters& TextureParameters, - const TextureGroup& LODGroup, - const FString& TextureType, - const FString& NodePath) -{ - if (!Package || Package->IsPendingKill()) - return nullptr; - - UTexture2D * Texture = nullptr; - if (ExistingTexture) - { - Texture = ExistingTexture; - } - else - { - // Create new texture object. - Texture = NewObject< UTexture2D >( - Package, UTexture2D::StaticClass(), *TextureName, - RF_Transactional); - - // Assign texture group. - Texture->LODGroup = LODGroup; - } - - // Add/Update meta information to package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *TextureName); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Texture, HAPI_UNREAL_PACKAGE_META_NODE_PATH, *NodePath); - - // Initialize texture source. - Texture->Source.Init(ImageInfo.xRes, ImageInfo.yRes, 1, 1, TSF_BGRA8); - - // Lock the texture. - uint8 * MipData = Texture->Source.LockMip(0); - - // Create base map. - uint8* DestPtr = nullptr; - uint32 SrcWidth = ImageInfo.xRes; - uint32 SrcHeight = ImageInfo.yRes; - const char * SrcData = &ImageBuffer[0]; - - for (uint32 y = 0; y < SrcHeight; y++) - { - DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; - - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth * 4 + x * 4; - - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 2); // B - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 1); // G - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 0); // R - - if (TextureParameters.bUseAlpha) - *DestPtr++ = *(uint8*)(SrcData + DataOffset + 3); // A - else - *DestPtr++ = 0xFF; - } - } - - bool bHasAlphaValue = false; - if (TextureParameters.bUseAlpha) - { - // See if there is an actual alpha value in the texture or if we can ignore the texture alpha - for (uint32 y = 0; y < SrcHeight; y++) - { - for (uint32 x = 0; x < SrcWidth; x++) - { - uint32 DataOffset = y * SrcWidth * 4 + x * 4; - if (*(uint8*)(SrcData + DataOffset + 3) != 0xFF) - { - bHasAlphaValue = true; - break; - } - } - - if (bHasAlphaValue) - break; - } - } - - // Unlock the texture. - Texture->Source.UnlockMip(0); - - // Texture creation parameters. - Texture->SRGB = TextureParameters.bSRGB; - Texture->CompressionSettings = TextureParameters.CompressionSettings; - Texture->CompressionNoAlpha = !bHasAlphaValue; - Texture->DeferCompression = TextureParameters.bDeferCompression; - - // Set the Source Guid/Hash if specified. - /* - if ( TextureParameters.SourceGuidHash.IsValid() ) - { - Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); - } - */ - - Texture->PostEditChange(); - - return Texture; -} - - - -bool -FHoudiniMaterialTranslator::HapiExtractImage( - const HAPI_ParmId& NodeParmId, - const HAPI_MaterialInfo& MaterialInfo, - const char * PlaneType, - const HAPI_ImageDataFormat& ImageDataFormat, - HAPI_ImagePacking ImagePacking, - bool bRenderToImage, - TArray& OutImageBuffer ) -{ - if (bRenderToImage) - { - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, NodeParmId), false); - } - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImageInfo), false); - - ImageInfo.dataFormat = ImageDataFormat; - ImageInfo.interleaved = true; - ImageInfo.packing = ImagePacking; - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetImageInfo( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImageInfo), false); - - int32 ImageBufferSize = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ExtractImageToMemory( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, HAPI_RAW_FORMAT_NAME, - PlaneType, &ImageBufferSize), false); - - if (ImageBufferSize <= 0) - return false; - - OutImageBuffer.SetNumUninitialized(ImageBufferSize); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImageMemoryBuffer( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &OutImageBuffer[0], - ImageBufferSize), false); - - return true; -} - -bool -FHoudiniMaterialTranslator::HapiGetImagePlanes( - const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes) -{ - OutImagePlanes.Empty(); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, NodeParmId), false); - - int32 ImagePlaneCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlaneCount( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImagePlaneCount), false); - - if (ImagePlaneCount <= 0) - return true; - - TArray ImagePlaneStringHandles; - ImagePlaneStringHandles.SetNumZeroed(ImagePlaneCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlanes( - FHoudiniEngine::Get().GetSession(), - MaterialInfo.nodeId, &ImagePlaneStringHandles[0], ImagePlaneCount), false); - - FHoudiniEngineString::SHArrayToFStringArray(ImagePlaneStringHandles, OutImagePlanes); - - return true; -} - - -UMaterialExpression * -FHoudiniMaterialTranslator::MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass) -{ - if (!Expression) - return nullptr; - -#if WITH_EDITOR - if (ExpressionClass == Expression->GetClass()) - return Expression; - - // If this is a channel multiply expression, we can recurse. - UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast< UMaterialExpressionMultiply >(Expression); - if (MaterialExpressionMultiply) - { - { - UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->A.Expression; - if (MaterialExpression) - { - if (MaterialExpression->GetClass() == ExpressionClass) - return MaterialExpression; - - MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( - Cast(MaterialExpression), ExpressionClass); - - if (MaterialExpression) - return MaterialExpression; - } - } - - { - UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->B.Expression; - if (MaterialExpression) - { - if (MaterialExpression->GetClass() == ExpressionClass) - return MaterialExpression; - - MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( - Cast(MaterialExpression), ExpressionClass); - - if (MaterialExpression) - return MaterialExpression; - } - } - } -#endif - - return nullptr; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Names of generating Houdini parameters. - FString GeneratingParameterNameDiffuseTexture = TEXT(""); - FString GeneratingParameterNameUniformColor = TEXT(""); - FString GeneratingParameterNameVertexColor = TEXT(HAPI_UNREAL_ATTRIB_COLOR); - - // Diffuse texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Default; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // Attempt to look up previously created expressions. - UMaterialExpression * MaterialExpression = Material->BaseColor.Expression; - - // Locate sampling expression. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureSample = - Cast< UMaterialExpressionTextureSampleParameter2D >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // If texture sampling expression does exist, attempt to look up corresponding texture. - UTexture2D * TextureDiffuse = nullptr; - if (IsValid(ExpressionTextureSample)) - TextureDiffuse = Cast< UTexture2D >(ExpressionTextureSample->Texture); - - // Locate uniform color expression. - UMaterialExpressionVectorParameter * ExpressionConstant4Vector = - Cast< UMaterialExpressionVectorParameter >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionVectorParameter::StaticClass())); - - // If uniform color expression does not exist, create it. - if (!ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill()) - { - ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( - Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); - ExpressionConstant4Vector->DefaultValue = FLinearColor::White; - } - - // Add expression. - Material->Expressions.Add(ExpressionConstant4Vector); - - // Locate vertex color expression. - UMaterialExpressionVertexColor * ExpressionVertexColor = - Cast< UMaterialExpressionVertexColor >(FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionVertexColor::StaticClass())); - - // If vertex color expression does not exist, create it. - if (!ExpressionVertexColor || ExpressionVertexColor->IsPendingKill()) - { - ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( - Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag); - ExpressionVertexColor->Desc = GeneratingParameterNameVertexColor; - } - - // Add expression. - Material->Expressions.Add(ExpressionVertexColor); - - // Material should have at least one multiply expression. - UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast(MaterialExpression); - if (!MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill()) - MaterialExpressionMultiply = NewObject( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - // Add expression. - Material->Expressions.Add(MaterialExpressionMultiply); - - // See if primary multiplication has secondary multiplication as A input. - UMaterialExpressionMultiply * MaterialExpressionMultiplySecondary = nullptr; - if (MaterialExpressionMultiply->A.Expression) - MaterialExpressionMultiplySecondary = - Cast(MaterialExpressionMultiply->A.Expression); - - // See if a diffuse texture is available. - HAPI_ParmInfo ParmDiffuseTextureInfo; - HAPI_ParmId ParmDiffuseTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, - true, - ParmDiffuseTextureId, - ParmDiffuseTextureInfo)) - { - // Found via OGL tag - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_DIFFUSE, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, - false, - ParmDiffuseTextureId, - ParmDiffuseTextureInfo)) - { - // Found via Parm name - GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); - } - else - { - // failed to find the texture - ParmDiffuseTextureId = -1; - } - - // If we have diffuse texture parameter. - if (ParmDiffuseTextureId >= 0) - { - TArray ImageBuffer; - - // Get image planes of diffuse map. - TArray DiffuseImagePlanes; - bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( - ParmDiffuseTextureId, InMaterialInfo, DiffuseImagePlanes); - - HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; - const char * PlaneType = ""; - - if (bFoundImagePlanes && DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))) - { - if (DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA))) - { - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - - // Material does use alpha. - CreateTexture2DParameters.bUseAlpha = true; - } - else - { - // We still need to have the Alpha plane, just not the CreateTexture2DParameters - // alpha option. This is because all texture data from Houdini Engine contains - // the alpha plane by default. - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - } - } - else - { - bFoundImagePlanes = false; - } - - // Retrieve color plane. - if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( - ParmDiffuseTextureId, InMaterialInfo, PlaneType, - HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) - { - UPackage * TextureDiffusePackage = nullptr; - if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) - TextureDiffusePackage = Cast(TextureDiffuse->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureDiffuseName; - bool bCreatedNewTextureDiffuse = false; - - // Create diffuse texture package, if this is a new diffuse texture. - if (!TextureDiffusePackage) - { - TextureDiffusePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, - InPackageParams, - TextureDiffuseName); - } - else if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureDiffuseName = TextureDiffuse->GetName(); - } - else - { - TextureDiffuseName = FPaths::GetBaseFilename(TextureDiffusePackage->GetName(), true); - } - - // Create diffuse texture, if we need to create one. - if (!TextureDiffuse || TextureDiffuse->IsPendingKill()) - bCreatedNewTextureDiffuse = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing diffuse texture, or create new one. - TextureDiffuse = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureDiffuse, - ImageInfo, - TextureDiffusePackage, - TextureDiffuseName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureDiffuse->SetFlags(RF_Public | RF_Standalone); - - // Create diffuse sampling expression, if needed. - if (!ExpressionTextureSample) - { - ExpressionTextureSample = NewObject( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionTextureSample->Desc = GeneratingParameterNameDiffuseTexture; - ExpressionTextureSample->ParameterName = *GeneratingParameterNameDiffuseTexture; - ExpressionTextureSample->Texture = TextureDiffuse; - ExpressionTextureSample->SamplerType = SAMPLERTYPE_Color; - - // Add expression. - Material->Expressions.Add(ExpressionTextureSample); - - // Propagate and trigger diffuse texture updates. - if (bCreatedNewTextureDiffuse) - FAssetRegistryModule::AssetCreated(TextureDiffuse); - - TextureDiffuse->PreEditChange(nullptr); - TextureDiffuse->PostEditChange(); - TextureDiffuse->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureDiffusePackage); - } - } - - // See if uniform color is available. - HAPI_ParmInfo ParmDiffuseColorInfo; - HAPI_ParmId ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL, ParmDiffuseColorInfo); - - if (ParmDiffuseColorId >= 0) - { - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL); - } - else - { - ParmDiffuseColorId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE, ParmDiffuseColorInfo); - - if (ParmDiffuseColorId >= 0) - GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE); - } - - // If we have uniform color parameter. - if (ParmDiffuseColorId >= 0) - { - FLinearColor Color = FLinearColor::White; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&Color.R, - ParmDiffuseColorInfo.floatValuesIndex, ParmDiffuseColorInfo.size) == HAPI_RESULT_SUCCESS) - { - if (ParmDiffuseColorInfo.size == 3) - Color.A = 1.0f; - - // Record generating parameter. - ExpressionConstant4Vector->Desc = GeneratingParameterNameUniformColor; - ExpressionConstant4Vector->ParameterName = *GeneratingParameterNameUniformColor; - ExpressionConstant4Vector->DefaultValue = Color; - } - } - - // If we have have texture sample expression present, we need a secondary multiplication expression. - if (ExpressionTextureSample) - { - if (!MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary = NewObject( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - // Add expression. - Material->Expressions.Add(MaterialExpressionMultiplySecondary); - } - } - else - { - // If secondary multiplication exists, but we have no sampling, we can free it. - if (MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary->A.Expression = nullptr; - MaterialExpressionMultiplySecondary->B.Expression = nullptr; - MaterialExpressionMultiplySecondary->ConditionalBeginDestroy(); - } - } - - float SecondaryExpressionScale = 1.0f; - if (MaterialExpressionMultiplySecondary) - SecondaryExpressionScale = 1.5f; - - // Create multiplication expression which has uniform color and vertex color. - MaterialExpressionMultiply->A.Expression = ExpressionConstant4Vector; - MaterialExpressionMultiply->B.Expression = ExpressionVertexColor; - - ExpressionConstant4Vector->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionConstant4Vector->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - ExpressionVertexColor->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionVertexColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiply->MaterialExpressionEditorY = - (ExpressionVertexColor->MaterialExpressionEditorY - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; - - // Hook up secondary multiplication expression to first one. - if (MaterialExpressionMultiplySecondary) - { - MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; - MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; - - if (ExpressionTextureSample) - { - ExpressionTextureSample->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; - ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; - } - - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiplySecondary->MaterialExpressionEditorY = - MaterialExpressionMultiply->MaterialExpressionEditorY + FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression. - Material->BaseColor.Expression = MaterialExpressionMultiplySecondary; - } - else - { - // Assign expression. - Material->BaseColor.Expression = MaterialExpressionMultiply; - - MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - MaterialExpressionMultiply->MaterialExpressionEditorY = - (ExpressionVertexColor->MaterialExpressionEditorY - - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; - } - - return true; -} - - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - // Name of generating Houdini parameters. - FString GeneratingParameterNameTexture = TEXT(""); - - UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Opacity expressions. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; - UTexture2D * TextureOpacity = nullptr; - - // Opacity texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // See if opacity texture is available. - HAPI_ParmInfo ParmOpacityTextureInfo; - HAPI_ParmId ParmOpacityTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_OPACITY_OGL, - HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED, - true, - ParmOpacityTextureId, - ParmOpacityTextureInfo)) - { - // Found via OGL tag - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_OPACITY, - HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED, - false, - ParmOpacityTextureId, - ParmOpacityTextureInfo)) - { - // Found via Parm name - GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY); - } - else - { - // failed to find the texture - ParmOpacityTextureId = -1; - } - - // If we have opacity texture parameter. - if (ParmOpacityTextureId >= 0) - { - TArray< char > ImageBuffer; - - // Get image planes of opacity map. - TArray< FString > OpacityImagePlanes; - bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( - ParmOpacityTextureId, InMaterialInfo, OpacityImagePlanes); - - HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; - const char * PlaneType = ""; - - bool bColorAlphaFound = (OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA)) && OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))); - - if (bFoundImagePlanes && bColorAlphaFound) - { - ImagePacking = HAPI_IMAGE_PACKING_RGBA; - PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; - CreateTexture2DParameters.bUseAlpha = true; - } - else - { - bFoundImagePlanes = false; - } - - if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( - ParmOpacityTextureId, InMaterialInfo, PlaneType, - HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) - { - // Locate sampling expression. - ExpressionTextureOpacitySample = Cast< UMaterialExpressionTextureSampleParameter2D >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // Locate opacity texture, if valid. - if (ExpressionTextureOpacitySample) - TextureOpacity = Cast< UTexture2D >(ExpressionTextureOpacitySample->Texture); - - UPackage * TextureOpacityPackage = nullptr; - if (TextureOpacity) - TextureOpacityPackage = Cast< UPackage >(TextureOpacity->GetOuter()); - - HAPI_ImageInfo ImageInfo; - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureOpacityName; - bool bCreatedNewTextureOpacity = false; - - // Create opacity texture package, if this is a new opacity texture. - if (!TextureOpacityPackage) - { - TextureOpacityPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, - InPackageParams, - TextureOpacityName); - } - else if (TextureOpacity && !TextureOpacity->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureOpacityName = TextureOpacity->GetName(); - } - else - { - TextureOpacityName = FPaths::GetBaseFilename(TextureOpacityPackage->GetName(), true); - } - - // Create opacity texture, if we need to create one. - if (!TextureOpacity) - bCreatedNewTextureOpacity = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing opacity texture, or create new one. - TextureOpacity = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureOpacity, - ImageInfo, - TextureOpacityPackage, - TextureOpacityName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, - NodePath); - - // if (BakeMode == EBakeMode::CookToTemp) - TextureOpacity->SetFlags(RF_Public | RF_Standalone); - - // Create opacity sampling expression, if needed. - if (!ExpressionTextureOpacitySample) - { - ExpressionTextureOpacitySample = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionTextureOpacitySample->Desc = GeneratingParameterNameTexture; - ExpressionTextureOpacitySample->ParameterName = *GeneratingParameterNameTexture; - ExpressionTextureOpacitySample->Texture = TextureOpacity; - ExpressionTextureOpacitySample->SamplerType = SAMPLERTYPE_Grayscale; - - // Offset node placement. - ExpressionTextureOpacitySample->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionTextureOpacitySample->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Add expression. - Material->Expressions.Add(ExpressionTextureOpacitySample); - - // We need to set material type to masked. - TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput* ExpressionOutput = ExpressionOutputs.GetData(); - - Material->OpacityMask.Expression = ExpressionTextureOpacitySample; - Material->BlendMode = BLEND_Masked; - - Material->OpacityMask.Mask = ExpressionOutput->Mask; - Material->OpacityMask.MaskR = 1; - Material->OpacityMask.MaskG = 0; - Material->OpacityMask.MaskB = 0; - Material->OpacityMask.MaskA = 0; - - // Propagate and trigger opacity texture updates. - if (bCreatedNewTextureOpacity) - FAssetRegistryModule::AssetCreated(TextureOpacity); - - TextureOpacity->PreEditChange(nullptr); - TextureOpacity->PostEditChange(); - TextureOpacity->MarkPackageDirty(); - - bExpressionCreated = true; - } - - // Cache the texture package - OutPackages.AddUnique(TextureOpacityPackage); - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - float OpacityValue = 1.0f; - bool bNeedsTranslucency = false; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameters. - FString GeneratingParameterNameScalar = TEXT(""); - FString GeneratingParameterNameTexture = TEXT(""); - - UMaterialExpression * MaterialExpression = Material->Opacity.Expression; - - // Opacity expressions. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; - UMaterialExpressionScalarParameter * ExpressionScalarOpacity = nullptr; - UTexture2D * TextureOpacity = nullptr; - - // Opacity texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = true; - - // If opacity sampling expression was not created, check if diffuse contains an alpha plane. - if (!ExpressionTextureOpacitySample) - { - UMaterialExpression * MaterialExpressionDiffuse = Material->BaseColor.Expression; - if (MaterialExpressionDiffuse) - { - // Locate diffuse sampling expression. - UMaterialExpressionTextureSampleParameter2D * ExpressionTextureDiffuseSample = - Cast< UMaterialExpressionTextureSampleParameter2D >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpressionDiffuse, - UMaterialExpressionTextureSampleParameter2D::StaticClass())); - - // See if there's an alpha plane in this expression's texture. - if (ExpressionTextureDiffuseSample) - { - UTexture2D * DiffuseTexture = Cast< UTexture2D >(ExpressionTextureDiffuseSample->Texture); - if (DiffuseTexture && !DiffuseTexture->CompressionNoAlpha) - { - // The diffuse texture has an alpha channel (that wasn't discarded), so we can use it - ExpressionTextureOpacitySample = ExpressionTextureDiffuseSample; - bNeedsTranslucency = true; - } - } - } - } - - // Retrieve opacity value - HAPI_ParmInfo ParmOpacityValueInfo; - HAPI_ParmId ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_OGL, ParmOpacityValueInfo); - - if (ParmOpacityValueId >= 0) - { - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_OGL); - } - else - { - ParmOpacityValueId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA, ParmOpacityValueInfo); - - if (ParmOpacityValueId >= 0) - GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA); - } - - if (ParmOpacityValueId >= 0) - { - if (ParmOpacityValueInfo.size > 0 && ParmOpacityValueInfo.floatValuesIndex >= 0) - { - float OpacityValueRetrieved = 1.0f; - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, - (float *)&OpacityValue, ParmOpacityValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - if (!ExpressionScalarOpacity) - { - ExpressionScalarOpacity = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Clamp retrieved value. - OpacityValueRetrieved = FMath::Clamp< float >(OpacityValueRetrieved, 0.0f, 1.0f); - OpacityValue = OpacityValueRetrieved; - - // Set expression fields. - ExpressionScalarOpacity->DefaultValue = OpacityValue; - ExpressionScalarOpacity->SliderMin = 0.0f; - ExpressionScalarOpacity->SliderMax = 1.0f; - ExpressionScalarOpacity->Desc = GeneratingParameterNameScalar; - ExpressionScalarOpacity->ParameterName = *GeneratingParameterNameScalar; - - // Add expression. - Material->Expressions.Add(ExpressionScalarOpacity); - - // If alpha is less than 1, we need translucency. - bNeedsTranslucency |= (OpacityValue != 1.0f); - } - } - } - - if (bNeedsTranslucency) - Material->BlendMode = BLEND_Translucent; - - if (ExpressionScalarOpacity && ExpressionTextureOpacitySample) - { - // We have both alpha and alpha uniform, attempt to locate multiply expression. - UMaterialExpressionMultiply * ExpressionMultiply = - Cast< UMaterialExpressionMultiply >( - FHoudiniMaterialTranslator::MaterialLocateExpression( - MaterialExpression, - UMaterialExpressionMultiply::StaticClass())); - - if (!ExpressionMultiply) - ExpressionMultiply = NewObject< UMaterialExpressionMultiply >( - Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); - - Material->Expressions.Add(ExpressionMultiply); - - TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); - - ExpressionMultiply->A.Expression = ExpressionTextureOpacitySample; - ExpressionMultiply->B.Expression = ExpressionScalarOpacity; - - Material->Opacity.Expression = ExpressionMultiply; - Material->Opacity.Mask = ExpressionOutput->Mask; - Material->Opacity.MaskR = 0; - Material->Opacity.MaskG = 0; - Material->Opacity.MaskB = 0; - Material->Opacity.MaskA = 1; - - ExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMultiply->MaterialExpressionEditorY = MaterialNodeY; - - ExpressionScalarOpacity->MaterialExpressionEditorX = - FHoudiniMaterialTranslator::MaterialExpressionNodeX - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX; - ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - bExpressionCreated = true; - } - else if (ExpressionScalarOpacity) - { - Material->Opacity.Expression = ExpressionScalarOpacity; - - ExpressionScalarOpacity->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - bExpressionCreated = true; - } - else if (ExpressionTextureOpacitySample) - { - TArray ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); - FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); - - Material->Opacity.Expression = ExpressionTextureOpacitySample; - Material->Opacity.Mask = ExpressionOutput->Mask; - Material->Opacity.MaskR = 0; - Material->Opacity.MaskG = 0; - Material->Opacity.MaskB = 0; - Material->Opacity.MaskA = 1; - - bExpressionCreated = true; - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentNormal( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - bool bTangentSpaceNormal = true; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Normal texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Normalmap; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if separate normal texture is available. - HAPI_ParmInfo ParmNormalTextureInfo; - HAPI_ParmId ParmNormalTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_NORMAL, - HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED, - false, - ParmNormalTextureId, - ParmNormalTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_NORMAL_OGL, - "", - true, - ParmNormalTextureId, - ParmNormalTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_OGL); - } - else - { - // failed to find the texture - ParmNormalTextureId = -1; - } - - if (ParmNormalTextureId >= 0) - { - // Retrieve space for this normal texture. - HAPI_ParmInfo ParmInfoNormalType; - int32 ParmNormalTypeId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); - - // Retrieve value for normal type choice list (if exists). - if (ParmNormalTypeId >= 0) - { - FString NormalType = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT); - if (ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0) - { - HAPI_StringHandle StringHandle; - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, false, &StringHandle, ParmInfoNormalType.stringValuesIndex, ParmInfoNormalType.size) == HAPI_RESULT_SUCCESS) - { - // Get the actual string value. - FString NormalTypeString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(NormalTypeString)) - NormalType = NormalTypeString; - } - } - - // Check if we require world space normals. - if (NormalType.Equals(TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD), ESearchCase::IgnoreCase)) - bTangentSpaceNormal = false; - } - - // Retrieve color plane. - TArray ImageBuffer; - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmNormalTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); - - UTexture2D * TextureNormal = nullptr; - if (ExpressionNormal) - { - TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Normal.Expression) - { - Material->Normal.Expression->ConditionalBeginDestroy(); - Material->Normal.Expression = nullptr; - } - } - - UPackage * TextureNormalPackage = nullptr; - if (TextureNormal) - TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureNormalName; - bool bCreatedNewTextureNormal = false; - - // Create normal texture package, if this is a new normal texture. - if (!TextureNormalPackage) - { - TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - InPackageParams, - TextureNormalName); - } - else if (TextureNormal && !TextureNormal->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureNormalName = TextureNormal->GetName(); - } - else - { - TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); - } - - // Create normal texture, if we need to create one. - if (!TextureNormal) - bCreatedNewTextureNormal = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing normal texture, or create new one. - TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureNormal, - ImageInfo, - TextureNormalPackage, - TextureNormalName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_WorldNormalMap, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureNormal->SetFlags(RF_Public | RF_Standalone); - - // Create normal sampling expression, if needed. - if (!ExpressionNormal) - ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionNormal->Desc = GeneratingParameterName; - ExpressionNormal->ParameterName = *GeneratingParameterName; - - ExpressionNormal->Texture = TextureNormal; - ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; - - // Offset node placement. - ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Set normal space. - Material->bTangentSpaceNormal = bTangentSpaceNormal; - - // Assign expression to material. - Material->Expressions.Add(ExpressionNormal); - Material->Normal.Expression = ExpressionNormal; - - bExpressionCreated = true; - - // Propagate and trigger normal texture updates. - if (bCreatedNewTextureNormal) - FAssetRegistryModule::AssetCreated(TextureNormal); - - TextureNormal->PreEditChange(nullptr); - TextureNormal->PostEditChange(); - TextureNormal->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureNormalPackage); - } - } - - // If separate normal map was not found, see if normal plane exists in diffuse map. - if (!bExpressionCreated) - { - // See if diffuse texture is available. - HAPI_ParmInfo ParmDiffuseTextureInfo; - HAPI_ParmId ParmDiffuseTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, - true, - ParmDiffuseTextureId, - ParmDiffuseTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_DIFFUSE, - HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, - false, - ParmDiffuseTextureId, - ParmDiffuseTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); - } - else - { - // failed to find the texture - ParmDiffuseTextureId = -1; - } - - if (ParmDiffuseTextureId >= 0) - { - // Normal plane is available in diffuse map. - TArray ImageBuffer; - - // Retrieve color plane - this will contain normal data. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmDiffuseTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = - Cast(Material->Normal.Expression); - - UTexture2D* TextureNormal = nullptr; - if (ExpressionNormal) - { - TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Normal.Expression) - { - Material->Normal.Expression->ConditionalBeginDestroy(); - Material->Normal.Expression = nullptr; - } - } - - UPackage* TextureNormalPackage = nullptr; - if (TextureNormal) - TextureNormalPackage = Cast(TextureNormal->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureNormalName; - bool bCreatedNewTextureNormal = false; - - // Create normal texture package, if this is a new normal texture. - if (!TextureNormalPackage) - { - TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - InPackageParams, - TextureNormalName); - } - else if (TextureNormal && !TextureNormal->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureNormalName = TextureNormal->GetName(); - } - else - { - TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); - } - - // Create normal texture, if we need to create one. - if (!TextureNormal) - bCreatedNewTextureNormal = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing normal texture, or create new one. - TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureNormal, - ImageInfo, - TextureNormalPackage, - TextureNormalName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_WorldNormalMap, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureNormal->SetFlags(RF_Public | RF_Standalone); - - // Create normal sampling expression, if needed. - if (!ExpressionNormal) - ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionNormal->Desc = GeneratingParameterName; - ExpressionNormal->ParameterName = *GeneratingParameterName; - - ExpressionNormal->Texture = TextureNormal; - ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; - - // Offset node placement. - ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Set normal space. - Material->bTangentSpaceNormal = bTangentSpaceNormal; - - // Assign expression to material. - Material->Expressions.Add(ExpressionNormal); - Material->Normal.Expression = ExpressionNormal; - - // Propagate and trigger diffuse texture updates. - if (bCreatedNewTextureNormal) - FAssetRegistryModule::AssetCreated(TextureNormal); - - TextureNormal->PreEditChange(nullptr); - TextureNormal->PostEditChange(); - TextureNormal->MarkPackageDirty(); - - bExpressionCreated = true; - } - - // Cache the texture package - OutPackages.AddUnique(TextureNormalPackage); - } - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Specular texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if specular texture is available. - HAPI_ParmInfo ParmSpecularTextureInfo; - HAPI_ParmId ParmSpecularTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL, - HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED, - true, - ParmSpecularTextureId, - ParmSpecularTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_SPECULAR, - HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED, - false, - ParmSpecularTextureId, - ParmSpecularTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR); - } - else - { - // failed to find the texture - ParmSpecularTextureId = -1; - } - - if (ParmSpecularTextureId >= 0) - { - TArray ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmSpecularTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Specular.Expression); - - UTexture2D * TextureSpecular = nullptr; - if (ExpressionSpecular) - { - TextureSpecular = Cast< UTexture2D >(ExpressionSpecular->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Specular.Expression) - { - Material->Specular.Expression->ConditionalBeginDestroy(); - Material->Specular.Expression = nullptr; - } - } - - UPackage * TextureSpecularPackage = nullptr; - if (TextureSpecular) - TextureSpecularPackage = Cast< UPackage >(TextureSpecular->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureSpecularName; - bool bCreatedNewTextureSpecular = false; - - // Create specular texture package, if this is a new specular texture. - if (!TextureSpecularPackage) - { - TextureSpecularPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, - InPackageParams, - TextureSpecularName); - } - else if (TextureSpecular && !TextureSpecular->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureSpecularName = TextureSpecular->GetName(); - } - else - { - TextureSpecularName = FPaths::GetBaseFilename(TextureSpecularPackage->GetName(), true); - } - - // Create specular texture, if we need to create one. - if (!TextureSpecular) - bCreatedNewTextureSpecular = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing specular texture, or create new one. - TextureSpecular = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureSpecular, - ImageInfo, - TextureSpecularPackage, - TextureSpecularName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureSpecular->SetFlags(RF_Public | RF_Standalone); - - // Create specular sampling expression, if needed. - if (!ExpressionSpecular) - { - ExpressionSpecular = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionSpecular->Desc = GeneratingParameterName; - ExpressionSpecular->ParameterName = *GeneratingParameterName; - - ExpressionSpecular->Texture = TextureSpecular; - ExpressionSpecular->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionSpecular->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionSpecular->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionSpecular); - Material->Specular.Expression = ExpressionSpecular; - - bExpressionCreated = true; - - // Propagate and trigger specular texture updates. - if (bCreatedNewTextureSpecular) - FAssetRegistryModule::AssetCreated(TextureSpecular); - - TextureSpecular->PreEditChange(nullptr); - TextureSpecular->PostEditChange(); - TextureSpecular->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureSpecularPackage); - } - } - - // See if we have a specular color - HAPI_ParmInfo ParmSpecularColorInfo; - HAPI_ParmId ParmSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL, ParmSpecularColorInfo); - - if (ParmSpecularColorId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL); - } - else - { - ParmSpecularColorId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR, ParmSpecularColorInfo); - - if (ParmSpecularColorId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR); - } - - if (!bExpressionCreated && ParmSpecularColorId >= 0) - { - // Specular color is available. - FLinearColor Color = FLinearColor::White; - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmSpecularColorInfo.floatValuesIndex, ParmSpecularColorInfo.size) == HAPI_RESULT_SUCCESS) - { - if (ParmSpecularColorInfo.size == 3) - Color.A = 1.0f; - - UMaterialExpressionVectorParameter * ExpressionSpecularColor = - Cast< UMaterialExpressionVectorParameter >(Material->Specular.Expression); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionSpecularColor) - { - // Otherwise new expression is of a different type. - if (Material->Specular.Expression) - { - Material->Specular.Expression->ConditionalBeginDestroy(); - Material->Specular.Expression = nullptr; - } - - ExpressionSpecularColor = NewObject< UMaterialExpressionVectorParameter >( - Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionSpecularColor->Desc = GeneratingParameterName; - ExpressionSpecularColor->ParameterName = *GeneratingParameterName; - - ExpressionSpecularColor->DefaultValue = Color; - - // Offset node placement. - ExpressionSpecularColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionSpecularColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionSpecularColor); - Material->Specular.Expression = ExpressionSpecularColor; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Roughness texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if roughness texture is available. - HAPI_ParmInfo ParmRoughnessTextureInfo; - HAPI_ParmId ParmRoughnessTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL, - HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED, - true, - ParmRoughnessTextureId, - ParmRoughnessTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_ROUGHNESS, - HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED, - false, - ParmRoughnessTextureId, - ParmRoughnessTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS); - } - else - { - // failed to find the texture - ParmRoughnessTextureId = -1; - } - - if (ParmRoughnessTextureId >= 0) - { - TArray ImageBuffer; - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmRoughnessTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer ) ) - { - UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Roughness.Expression); - - UTexture2D* TextureRoughness = nullptr; - if (ExpressionRoughness) - { - TextureRoughness = Cast< UTexture2D >(ExpressionRoughness->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Roughness.Expression) - { - Material->Roughness.Expression->ConditionalBeginDestroy(); - Material->Roughness.Expression = nullptr; - } - } - - UPackage * TextureRoughnessPackage = nullptr; - if (TextureRoughness) - TextureRoughnessPackage = Cast< UPackage >(TextureRoughness->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureRoughnessName; - bool bCreatedNewTextureRoughness = false; - - // Create roughness texture package, if this is a new roughness texture. - if (!TextureRoughnessPackage) - { - TextureRoughnessPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, - InPackageParams, - TextureRoughnessName); - } - else if (TextureRoughness && !TextureRoughness->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureRoughnessName = TextureRoughness->GetName(); - } - else - { - TextureRoughnessName = FPaths::GetBaseFilename(TextureRoughnessPackage->GetName(), true); - } - - // Create roughness texture, if we need to create one. - if (!TextureRoughness) - bCreatedNewTextureRoughness = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing roughness texture, or create new one. - TextureRoughness = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureRoughness, - ImageInfo, - TextureRoughnessPackage, - TextureRoughnessName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureRoughness->SetFlags(RF_Public | RF_Standalone); - - // Create roughness sampling expression, if needed. - if (!ExpressionRoughness) - ExpressionRoughness = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionRoughness->Desc = GeneratingParameterName; - ExpressionRoughness->ParameterName = *GeneratingParameterName; - - ExpressionRoughness->Texture = TextureRoughness; - ExpressionRoughness->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionRoughness->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionRoughness->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionRoughness); - Material->Roughness.Expression = ExpressionRoughness; - - bExpressionCreated = true; - - // Propagate and trigger roughness texture updates. - if (bCreatedNewTextureRoughness) - FAssetRegistryModule::AssetCreated(TextureRoughness); - - TextureRoughness->PreEditChange(nullptr); - TextureRoughness->PostEditChange(); - TextureRoughness->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureRoughnessPackage); - } - } - - // See if we have a roughness value - HAPI_ParmInfo ParmRoughnessValueInfo; - HAPI_ParmId ParmRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL, ParmRoughnessValueInfo); - - if (ParmRoughnessValueId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL); - } - else - { - ParmRoughnessValueId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS, ParmRoughnessValueInfo); - - if (ParmRoughnessValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS); - } - - if (!bExpressionCreated && ParmRoughnessValueId >= 0) - { - // Roughness value is available. - - float RoughnessValue = 0.0f; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&RoughnessValue, - ParmRoughnessValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - UMaterialExpressionScalarParameter * ExpressionRoughnessValue = - Cast< UMaterialExpressionScalarParameter >(Material->Roughness.Expression); - - // Clamp retrieved value. - RoughnessValue = FMath::Clamp< float >(RoughnessValue, 0.0f, 1.0f); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionRoughnessValue) - { - // Otherwise new expression is of a different type. - if (Material->Roughness.Expression) - { - Material->Roughness.Expression->ConditionalBeginDestroy(); - Material->Roughness.Expression = nullptr; - } - - ExpressionRoughnessValue = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionRoughnessValue->Desc = GeneratingParameterName; - ExpressionRoughnessValue->ParameterName = *GeneratingParameterName; - - ExpressionRoughnessValue->DefaultValue = RoughnessValue; - ExpressionRoughnessValue->SliderMin = 0.0f; - ExpressionRoughnessValue->SliderMax = 1.0f; - - // Offset node placement. - ExpressionRoughnessValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionRoughnessValue->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionRoughnessValue); - Material->Roughness.Expression = ExpressionRoughnessValue; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Metallic texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if metallic texture is available. - HAPI_ParmInfo ParmMetallicTextureInfo; - HAPI_ParmId ParmMetallicTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_METALLIC_OGL, - HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED, - true, - ParmMetallicTextureId, - ParmMetallicTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_METALLIC, - HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED, - false, - ParmMetallicTextureId, - ParmMetallicTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC); - } - else - { - // failed to find the texture - ParmMetallicTextureId = -1; - } - - if (ParmMetallicTextureId >= 0) - { - TArray ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmMetallicTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Metallic.Expression); - - UTexture2D * TextureMetallic = nullptr; - if (ExpressionMetallic) - { - TextureMetallic = Cast(ExpressionMetallic->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->Metallic.Expression) - { - Material->Metallic.Expression->ConditionalBeginDestroy(); - Material->Metallic.Expression = nullptr; - } - } - - UPackage * TextureMetallicPackage = nullptr; - if (TextureMetallic) - TextureMetallicPackage = Cast< UPackage >(TextureMetallic->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureMetallicName; - bool bCreatedNewTextureMetallic = false; - - // Create metallic texture package, if this is a new metallic texture. - if (!TextureMetallicPackage) - { - TextureMetallicPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, - InPackageParams, - TextureMetallicName); - } - else if (TextureMetallic && !TextureMetallic->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureMetallicName = TextureMetallic->GetName(); - } - else - { - TextureMetallicName = FPaths::GetBaseFilename(TextureMetallicPackage->GetName(), true); - } - - // Create metallic texture, if we need to create one. - if (!TextureMetallic) - bCreatedNewTextureMetallic = true; - - // Get the node path to add it to the meta data - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing metallic texture, or create new one. - TextureMetallic = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureMetallic, - ImageInfo, - TextureMetallicPackage, - TextureMetallicName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureMetallic->SetFlags(RF_Public | RF_Standalone); - - // Create metallic sampling expression, if needed. - if (!ExpressionMetallic) - ExpressionMetallic = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionMetallic->Desc = GeneratingParameterName; - ExpressionMetallic->ParameterName = *GeneratingParameterName; - - ExpressionMetallic->Texture = TextureMetallic; - ExpressionMetallic->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionMetallic->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMetallic->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionMetallic); - Material->Metallic.Expression = ExpressionMetallic; - - bExpressionCreated = true; - - // Propagate and trigger metallic texture updates. - if (bCreatedNewTextureMetallic) - FAssetRegistryModule::AssetCreated(TextureMetallic); - - TextureMetallic->PreEditChange(nullptr); - TextureMetallic->PostEditChange(); - TextureMetallic->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureMetallicPackage); - } - } - - // Get the metallic value - HAPI_ParmInfo ParmMetallicValueInfo; - HAPI_ParmId ParmMetallicValueId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL, ParmMetallicValueInfo); - - if (ParmMetallicValueId >= 0) - { - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL); - } - else - { - ParmMetallicValueId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmMetallicValueInfo); - - if (ParmMetallicValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); - } - - if (!bExpressionCreated && ParmMetallicValueId >= 0) - { - // Metallic value is available. - float MetallicValue = 0.0f; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&MetallicValue, - ParmMetallicTextureInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) - { - UMaterialExpressionScalarParameter * ExpressionMetallicValue = - Cast< UMaterialExpressionScalarParameter >(Material->Metallic.Expression); - - // Clamp retrieved value. - MetallicValue = FMath::Clamp< float >(MetallicValue, 0.0f, 1.0f); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionMetallicValue) - { - // Otherwise new expression is of a different type. - if (Material->Metallic.Expression) - { - Material->Metallic.Expression->ConditionalBeginDestroy(); - Material->Metallic.Expression = nullptr; - } - - ExpressionMetallicValue = NewObject< UMaterialExpressionScalarParameter >( - Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionMetallicValue->Desc = GeneratingParameterName; - ExpressionMetallicValue->ParameterName = *GeneratingParameterName; - - ExpressionMetallicValue->DefaultValue = MetallicValue; - ExpressionMetallicValue->SliderMin = 0.0f; - ExpressionMetallicValue->SliderMax = 1.0f; - - // Offset node placement. - ExpressionMetallicValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionMetallicValue->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionMetallicValue); - Material->Metallic.Expression = ExpressionMetallicValue; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - -bool -FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( - const HAPI_NodeId& InAssetId, - const FString& InHoudiniAssetName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY) -{ - if (!Material || Material->IsPendingKill()) - return false; - - bool bExpressionCreated = false; - HAPI_Result Result = HAPI_RESULT_SUCCESS; - - EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; - - // Name of generating Houdini parameter. - FString GeneratingParameterName = TEXT(""); - - // Emissive texture creation parameters. - FCreateTexture2DParameters CreateTexture2DParameters; - CreateTexture2DParameters.SourceGuidHash = FGuid(); - CreateTexture2DParameters.bUseAlpha = false; - CreateTexture2DParameters.CompressionSettings = TC_Grayscale; - CreateTexture2DParameters.bDeferCompression = true; - CreateTexture2DParameters.bSRGB = false; - - // See if emissive texture is available. - HAPI_ParmInfo ParmEmissiveTextureInfo; - HAPI_ParmId ParmEmissiveTextureId = -1; - if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL, - HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED, - true, - ParmEmissiveTextureId, - ParmEmissiveTextureInfo)) - { - // Found via OGL tag - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL); - } - else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - InMaterialInfo.nodeId, - HAPI_UNREAL_PARAM_MAP_EMISSIVE, - HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED, - false, - ParmEmissiveTextureId, - ParmEmissiveTextureInfo)) - { - // Found via Parm name - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE); - } - else - { - // failed to find the texture - ParmEmissiveTextureId = -1; - } - - if (ParmEmissiveTextureId >= 0) - { - TArray< char > ImageBuffer; - - // Retrieve color plane. - if (FHoudiniMaterialTranslator::HapiExtractImage( - ParmEmissiveTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, - HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) - { - UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = - Cast< UMaterialExpressionTextureSampleParameter2D >(Material->EmissiveColor.Expression); - - UTexture2D * TextureEmissive = nullptr; - if (ExpressionEmissive) - { - TextureEmissive = Cast< UTexture2D >(ExpressionEmissive->Texture); - } - else - { - // Otherwise new expression is of a different type. - if (Material->EmissiveColor.Expression) - { - Material->EmissiveColor.Expression->ConditionalBeginDestroy(); - Material->EmissiveColor.Expression = nullptr; - } - } - - UPackage * TextureEmissivePackage = nullptr; - if (TextureEmissive) - TextureEmissivePackage = Cast< UPackage >(TextureEmissive->GetOuter()); - - HAPI_ImageInfo ImageInfo; - FHoudiniApi::ImageInfo_Init(&ImageInfo); - Result = FHoudiniApi::GetImageInfo( - FHoudiniEngine::Get().GetSession(), - InMaterialInfo.nodeId, &ImageInfo); - - if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) - { - // Create texture. - FString TextureEmissiveName; - bool bCreatedNewTextureEmissive = false; - - // Create emissive texture package, if this is a new emissive texture. - if (!TextureEmissivePackage) - { - TextureEmissivePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( - InMaterialInfo.nodeId, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, - InPackageParams, - TextureEmissiveName); - } - else if (TextureEmissive && !TextureEmissive->IsPendingKill()) - { - // Get the name of the texture if we are overwriting the exist asset - TextureEmissiveName = TextureEmissive->GetName(); - } - else - { - TextureEmissiveName = FPaths::GetBaseFilename(TextureEmissivePackage->GetName(), true); - } - - // Create emissive texture, if we need to create one. - if (!TextureEmissive) - bCreatedNewTextureEmissive = true; - - FString NodePath; - FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); - - // Reuse existing emissive texture, or create new one. - TextureEmissive = FHoudiniMaterialTranslator::CreateUnrealTexture( - TextureEmissive, - ImageInfo, - TextureEmissivePackage, - TextureEmissiveName, - ImageBuffer, - CreateTexture2DParameters, - TEXTUREGROUP_World, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, - NodePath); - - //if (BakeMode == EBakeMode::CookToTemp) - TextureEmissive->SetFlags(RF_Public | RF_Standalone); - - // Create emissive sampling expression, if needed. - if (!ExpressionEmissive) - ExpressionEmissive = NewObject< UMaterialExpressionTextureSampleParameter2D >( - Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); - - // Record generating parameter. - ExpressionEmissive->Desc = GeneratingParameterName; - ExpressionEmissive->ParameterName = *GeneratingParameterName; - - ExpressionEmissive->Texture = TextureEmissive; - ExpressionEmissive->SamplerType = SAMPLERTYPE_LinearGrayscale; - - // Offset node placement. - ExpressionEmissive->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionEmissive->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionEmissive); - Material->EmissiveColor.Expression = ExpressionEmissive; - - bExpressionCreated = true; - - // Propagate and trigger metallic texture updates. - if (bCreatedNewTextureEmissive) - FAssetRegistryModule::AssetCreated(TextureEmissive); - - TextureEmissive->PreEditChange(nullptr); - TextureEmissive->PostEditChange(); - TextureEmissive->MarkPackageDirty(); - } - - // Cache the texture package - OutPackages.AddUnique(TextureEmissivePackage); - } - } - - HAPI_ParmInfo ParmEmissiveValueInfo; - HAPI_ParmId ParmEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL, ParmEmissiveValueInfo); - - if (ParmEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL); - else - { - ParmEmissiveValueId = - FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE, ParmEmissiveValueInfo); - - if (ParmEmissiveValueId >= 0) - GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE); - } - - if (!bExpressionCreated && ParmEmissiveValueId >= 0) - { - // Emissive color is available. - - FLinearColor Color = FLinearColor::White; - - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, - ParmEmissiveValueInfo.floatValuesIndex, ParmEmissiveValueInfo.size) == HAPI_RESULT_SUCCESS) - { - if (ParmEmissiveValueInfo.size == 3) - Color.A = 1.0f; - - UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = - Cast< UMaterialExpressionConstant4Vector >(Material->EmissiveColor.Expression); - - // Create color const expression and add it to material, if we don't have one. - if (!ExpressionEmissiveColor) - { - // Otherwise new expression is of a different type. - if (Material->EmissiveColor.Expression) - { - Material->EmissiveColor.Expression->ConditionalBeginDestroy(); - Material->EmissiveColor.Expression = nullptr; - } - - ExpressionEmissiveColor = NewObject< UMaterialExpressionConstant4Vector >( - Material, UMaterialExpressionConstant4Vector::StaticClass(), NAME_None, ObjectFlag); - } - - // Record generating parameter. - ExpressionEmissiveColor->Desc = GeneratingParameterName; - if (ExpressionEmissiveColor->CanRenameNode()) - ExpressionEmissiveColor->SetEditableName(*GeneratingParameterName); - - ExpressionEmissiveColor->Constant = Color; - - // Offset node placement. - ExpressionEmissiveColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; - ExpressionEmissiveColor->MaterialExpressionEditorY = MaterialNodeY; - MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; - - // Assign expression to material. - Material->Expressions.Add(ExpressionEmissiveColor); - Material->EmissiveColor.Expression = ExpressionEmissiveColor; - - bExpressionCreated = true; - } - } - - return bExpressionCreated; -} - - -bool -FHoudiniMaterialTranslator::UpdateMaterialInstanceParameter( - FHoudiniGenericAttribute MaterialParameter, - UMaterialInstanceConstant* MaterialInstance, - const TArray& InPackages) -{ - bool bParameterUpdated = false; - -#if WITH_EDITOR - if (!MaterialInstance) - return false; - - if (MaterialParameter.AttributeName.IsEmpty()) - return false; - - // The default material instance parameters needs to be handled manually as they cant be changed via generic SetParameters functions - if (MaterialParameter.AttributeName.Compare("CastShadowAsMasked", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideCastShadowAsMasked() && (MaterialInstance->GetCastShadowAsMasked() == Value)) - return false; - - MaterialInstance->SetOverrideCastShadowAsMasked(true); - MaterialInstance->SetCastShadowAsMasked(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("EmissiveBoost", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideEmissiveBoost() && (MaterialInstance->GetEmissiveBoost() == Value)) - return false; - - MaterialInstance->SetOverrideEmissiveBoost(true); - MaterialInstance->SetEmissiveBoost(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("DiffuseBoost", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideDiffuseBoost() && (MaterialInstance->GetDiffuseBoost() == Value)) - return false; - - MaterialInstance->SetOverrideDiffuseBoost(true); - MaterialInstance->SetDiffuseBoost(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("ExportResolutionScale", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->GetOverrideExportResolutionScale() && (MaterialInstance->GetExportResolutionScale() == Value)) - return false; - - MaterialInstance->SetOverrideExportResolutionScale(true); - MaterialInstance->SetExportResolutionScale(Value); - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("OpacityMaskClipValue", ESearchCase::IgnoreCase) == 0) - { - float Value = (float)MaterialParameter.GetDoubleValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue && (MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue = true; - MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("BlendMode", ESearchCase::IgnoreCase) == 0) - { - EBlendMode EnumValue = (EBlendMode)MaterialParameter.GetIntValue(); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - FString StringValue = MaterialParameter.GetStringValue(); - if (StringValue.Compare("Opaque", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Opaque; - else if (StringValue.Compare("Masked", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Masked; - else if (StringValue.Compare("Translucent", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Translucent; - else if (StringValue.Compare("Additive", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Additive; - else if (StringValue.Compare("Modulate", ESearchCase::IgnoreCase) == 0) - EnumValue = EBlendMode::BLEND_Modulate; - else if (StringValue.StartsWith("Alpha", ESearchCase::IgnoreCase)) - EnumValue = EBlendMode::BLEND_AlphaComposite; - } - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_BlendMode && (MaterialInstance->BasePropertyOverrides.BlendMode == EnumValue)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = true; - MaterialInstance->BasePropertyOverrides.BlendMode = EnumValue; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("ShadingModel", ESearchCase::IgnoreCase) == 0) - { - EMaterialShadingModel EnumValue = (EMaterialShadingModel)MaterialParameter.GetIntValue(); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - FString StringValue = MaterialParameter.GetStringValue(); - if (StringValue.Compare("Unlit", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Unlit; - else if (StringValue.StartsWith("Default", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_DefaultLit; - else if (StringValue.Compare("Subsurface", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Subsurface; - else if (StringValue.StartsWith("Preintegrated", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_PreintegratedSkin; - else if (StringValue.StartsWith("Clear", ESearchCase::IgnoreCase)) - EnumValue = EMaterialShadingModel::MSM_ClearCoat; - else if (StringValue.Compare("SubsurfaceProfile", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_SubsurfaceProfile; - else if (StringValue.Compare("TwoSidedFoliage", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_TwoSidedFoliage; - else if (StringValue.Compare("Hair", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Hair; - else if (StringValue.Compare("Cloth", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Cloth; - else if (StringValue.Compare("Eye", ESearchCase::IgnoreCase) == 0) - EnumValue = EMaterialShadingModel::MSM_Eye; - } - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel && (MaterialInstance->BasePropertyOverrides.ShadingModel == EnumValue)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel = true; - MaterialInstance->BasePropertyOverrides.ShadingModel = EnumValue; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("TwoSided", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_TwoSided && (MaterialInstance->BasePropertyOverrides.TwoSided == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_TwoSided = true; - MaterialInstance->BasePropertyOverrides.TwoSided = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("DitheredLODTransition", ESearchCase::IgnoreCase) == 0) - { - bool Value = MaterialParameter.GetBoolValue(); - - // Update the parameter value only if necessary - if (MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition && (MaterialInstance->BasePropertyOverrides.DitheredLODTransition == Value)) - return false; - - MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition = true; - MaterialInstance->BasePropertyOverrides.DitheredLODTransition = Value; - bParameterUpdated = true; - } - else if (MaterialParameter.AttributeName.Compare("PhysMaterial", ESearchCase::IgnoreCase) == 0) - { - // Try to load a Material corresponding to the parameter value - FString ParamValue = MaterialParameter.GetStringValue(); - UPhysicalMaterial* FoundPhysMaterial = Cast< UPhysicalMaterial >( - StaticLoadObject(UPhysicalMaterial::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); - - // Update the parameter value if necessary - if (!FoundPhysMaterial || (MaterialInstance->PhysMaterial == FoundPhysMaterial)) - return false; - - MaterialInstance->PhysMaterial = FoundPhysMaterial; - bParameterUpdated = true; - } - - if (bParameterUpdated) - return true; - - // Handling custom parameters - FName CurrentMatParamName = FName(*MaterialParameter.AttributeName); - if (MaterialParameter.AttributeType == EAttribStorageType::STRING) - { - // String attributes are used for textures parameters - // We need to find the texture corresponding to the param - UTexture* FoundTexture = nullptr; - FString ParamValue = MaterialParameter.GetStringValue(); - - // Texture can either be already existing texture assets in UE4, or a newly generated textures by this asset - // Try to find the texture corresponding to the param value in the existing assets first. - FoundTexture = Cast( - StaticLoadObject(UTexture::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); - - if (!FoundTexture) - { - // We couldn't find a texture corresponding to the parameter in the existing UE4 assets - // Try to find the corresponding texture in the cooked temporary package we just generated - FoundTexture = FHoudiniMaterialTranslator::FindGeneratedTexture(ParamValue, InPackages); - } - - // Do not update if unnecessary - if (FoundTexture) - { - // Do not update if unnecessary - UTexture* OldTexture = nullptr; - bool FoundOldParam = MaterialInstance->GetTextureParameterValue(CurrentMatParamName, OldTexture); - if (FoundOldParam && (OldTexture == FoundTexture)) - return false; - - MaterialInstance->SetTextureParameterValueEditorOnly(CurrentMatParamName, FoundTexture); - bParameterUpdated = true; - } - } - else if (MaterialParameter.AttributeTupleSize == 1) - { - // Single attributes are either for scalar parameters or static switches - float OldValue; - bool FoundOldScalarParam = MaterialInstance->GetScalarParameterValue(CurrentMatParamName, OldValue); - if (FoundOldScalarParam) - { - // The material parameter is a scalar - float NewValue = (float)MaterialParameter.GetDoubleValue(); - - // Do not update if unnecessary - if (OldValue == NewValue) - return false; - - MaterialInstance->SetScalarParameterValueEditorOnly(CurrentMatParamName, NewValue); - bParameterUpdated = true; - } - else - { - // See if the underlying parameter is a static switch - bool NewBoolValue = MaterialParameter.GetBoolValue(); - - // We need to iterate over the material's static parameter set - FStaticParameterSet StaticParameters; - MaterialInstance->GetStaticParameterValues(StaticParameters); - - for (int32 SwitchParameterIdx = 0; SwitchParameterIdx < StaticParameters.StaticSwitchParameters.Num(); ++SwitchParameterIdx) - { - FStaticSwitchParameter& SwitchParameter = StaticParameters.StaticSwitchParameters[SwitchParameterIdx]; - if (SwitchParameter.ParameterInfo.Name != CurrentMatParamName) - continue; - - if (SwitchParameter.Value == NewBoolValue) - return false; - - SwitchParameter.Value = NewBoolValue; - SwitchParameter.bOverride = true; - - MaterialInstance->UpdateStaticPermutation(StaticParameters); - bParameterUpdated = true; - break; - } - } - } - else - { - // Tuple attributes are for vector parameters - FLinearColor NewLinearColor; - // if the attribute is stored in an int, we'll have to convert a color to a linear color - if (MaterialParameter.AttributeType == EAttribStorageType::INT || MaterialParameter.AttributeType == EAttribStorageType::INT64) - { - FColor IntColor; - IntColor.R = (int8)MaterialParameter.GetIntValue(0); - IntColor.G = (int8)MaterialParameter.GetIntValue(1); - IntColor.B = (int8)MaterialParameter.GetIntValue(2); - if (MaterialParameter.AttributeTupleSize >= 4) - IntColor.A = (int8)MaterialParameter.GetIntValue(3); - else - IntColor.A = 1; - - NewLinearColor = FLinearColor(IntColor); - } - else - { - NewLinearColor.R = (float)MaterialParameter.GetDoubleValue(0); - NewLinearColor.G = (float)MaterialParameter.GetDoubleValue(1); - NewLinearColor.B = (float)MaterialParameter.GetDoubleValue(2); - if (MaterialParameter.AttributeTupleSize >= 4) - NewLinearColor.A = (float)MaterialParameter.GetDoubleValue(3); - } - - // Do not update if unnecessary - FLinearColor OldValue; - bool FoundOldParam = MaterialInstance->GetVectorParameterValue(CurrentMatParamName, OldValue); - if (FoundOldParam && (OldValue == NewLinearColor)) - return false; - - MaterialInstance->SetVectorParameterValueEditorOnly(CurrentMatParamName, NewLinearColor); - bParameterUpdated = true; - } -#endif - - return bParameterUpdated; -} - - -UTexture* -FHoudiniMaterialTranslator::FindGeneratedTexture(const FString& TextureString, const TArray& InPackages) -{ - if (TextureString.IsEmpty()) - return nullptr; - - // Try to find the corresponding texture in the cooked temporary package generated by an HDA -UTexture* FoundTexture = nullptr; -for (const auto& CurrentPackage : InPackages) -{ - // Iterate through the cooked packages - if (!CurrentPackage || CurrentPackage->IsPendingKill()) - continue; - - // First, check if the package contains a texture - FString CurrentPackageName = CurrentPackage->GetName(); - UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); - if (!PackageTexture) - continue; - - // Then check if the package's metadata match what we're looking for - // Make sure this texture was generated by Houdini Engine - UMetaData* MetaData = CurrentPackage->GetMetaData(); - if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - continue; - - // Get the texture type from the meta data - // Texture type store has meta data will be C_A, N, S, R etc.. - const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); - if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // Convert the texture type to a "friendly" version - // C_A to diffuse, N to Normal, S to Specular etc... - FString TextureTypeFriendlyString = TextureTypeString; - FString TextureTypeFriendlyAlternateString = TEXT(""); - if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) - { - TextureTypeFriendlyString = TEXT("diffuse"); - TextureTypeFriendlyAlternateString = TEXT("basecolor"); - } - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("normal"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("emissive"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("specular"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("roughness"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("metallic"); - else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) - TextureTypeFriendlyString = TEXT("opacity"); - - // See if we have a match between the texture string and the friendly name - if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) - { - FoundTexture = PackageTexture; - break; - } - - // Get the node path from the meta data - const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); - if (NodePath.IsEmpty()) - continue; - - // See if we have a match with the path and texture type - FString PathAndType = NodePath + TEXT("/") + TextureTypeString; - if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // See if we have a match with the friendly path and texture type - FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; - if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - - // Try the alternate friendly string - if (!TextureTypeFriendlyAlternateString.IsEmpty()) - { - PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; - if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) - { - FoundTexture = PackageTexture; - break; - } - } -} - -return FoundTexture; -} - - -bool -FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( - const HAPI_NodeId& InNodeId, - const std::string& InTextureParmName, - const std::string& InUseTextureParmName, - const bool& bFindByTag, - HAPI_ParmId& OutParmId, - HAPI_ParmInfo& OutParmInfo) -{ - OutParmId = -1; - - if(bFindByTag) - OutParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InTextureParmName, OutParmInfo); - else - OutParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InTextureParmName, OutParmInfo); - - if (OutParmId < 0) - { - // Failed to find the texture - return false; - } - - // We found a valid parameter, check if the matching "use" parameter exists - HAPI_ParmInfo FoundUseParmInfo; - HAPI_ParmId FoundUseParmId = -1; - if(bFindByTag) - FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InUseTextureParmName, FoundUseParmInfo); - else - FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InUseTextureParmName, FoundUseParmInfo); - - if (FoundUseParmId >= 0) - { - // We found a valid "use" parameter, check if it is disabled - // Get the param value - int32 UseValue = 0; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &UseValue, FoundUseParmInfo.intValuesIndex, 1)) - { - if (UseValue == 0) - { - // We found the texture parm, but the "use" param/tag is disabled, so don't use it! - // We still return true as we found the parameter, this will prevent looking for other parms - OutParmId = -1; - return true; - } - } - } - - // Finally, make sure that the found texture Parm is not empty! - FString ParmValue = FString(); - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandle, OutParmInfo.stringValuesIndex, 1)) - { - // Convert the string handle to FString - FHoudiniEngineString::ToFString(StringHandle, ParmValue); - } - - if (ParmValue.IsEmpty()) - { - // We found the parm, but it's empty, don't use it! - // We still return true as we found the parameter, this will prevent looking for other parms - OutParmId = -1; - return true; - } - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniMaterialTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniPackageParams.h" + +#include "Materials/Material.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceConstant.h" +#include "PhysicalMaterials/PhysicalMaterial.h" + +#include "Materials/MaterialExpressionTextureSample.h" +#include "Materials/MaterialExpressionTextureCoordinate.h" +#include "Materials/MaterialExpressionConstant4Vector.h" +#include "Materials/MaterialExpressionConstant.h" +#include "Materials/MaterialExpressionMultiply.h" +#include "Materials/MaterialExpressionVertexColor.h" +#include "Materials/MaterialExpressionTextureSampleParameter2D.h" +#include "Materials/MaterialExpressionVectorParameter.h" +#include "Materials/MaterialExpressionScalarParameter.h" +#include "ImageUtils.h" +#include "PackageTools.h" +#include "AssetRegistryModule.h" +#include "UObject/MetaData.h" + +#if WITH_EDITOR + #include "Factories/MaterialFactoryNew.h" + #include "Factories/MaterialInstanceConstantFactoryNew.h" +#endif + +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeX = -400; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeY = -150; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepX = 220; +const int32 FHoudiniMaterialTranslator::MaterialExpressionNodeStepY = 220; + +bool +FHoudiniMaterialTranslator::CreateHoudiniMaterials( + const HAPI_NodeId& InAssetId, + const FHoudiniPackageParams& InPackageParams, + const TArray& InUniqueMaterialIds, + const TArray& InUniqueMaterialInfos, + const TMap& InMaterials, + const TMap& InAllOutputMaterials, + TMap& OutMaterials, + TArray& OutPackages, + const bool& bForceRecookAll, + bool bInTreatExistingMaterialsAsUpToDate) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMaterialTranslator::CreateHoudiniMaterials")); + + if (InUniqueMaterialIds.Num() <= 0) + return false; + + if (InUniqueMaterialInfos.Num() != InUniqueMaterialIds.Num()) + return false; + + // Empty returned materials. + OutMaterials.Empty(); + + // Update context for generated materials (will trigger when object goes out of scope). + FMaterialUpdateContext MaterialUpdateContext; + + // Default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + OutMaterials.Add(HAPI_UNREAL_DEFAULT_MATERIAL_NAME, DefaultMaterial); + + // Factory to create materials. + UMaterialFactoryNew * MaterialFactory = NewObject(); + MaterialFactory->AddToRoot(); + + for (int32 MaterialIdx = 0; MaterialIdx < InUniqueMaterialIds.Num(); MaterialIdx++) + { + HAPI_NodeId MaterialId = (HAPI_NodeId)InUniqueMaterialIds[MaterialIdx]; + + const HAPI_MaterialInfo& MaterialInfo = InUniqueMaterialInfos[MaterialIdx]; + if (!MaterialInfo.exists) + { + // The material does not exist, + // we will use the default Houdini material in this case. + continue; + } + + // Get the material node's node information. + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &NodeInfo)) + { + continue; + } + + FString MaterialName = TEXT(""); + if (!FHoudiniEngineString::ToFString(NodeInfo.nameSH, MaterialName)) + { + // shouldnt happen, give a generic name + HOUDINI_LOG_WARNING(TEXT("Failed to retrieve material name!")); + MaterialName = TEXT("Material_") + FString::FromInt(MaterialInfo.nodeId); + } + + FString MaterialPathName = TEXT(""); + if (!FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, MaterialInfo, MaterialPathName)) + continue; + + // Check first in the existing material map + UMaterial * Material = nullptr; + UMaterialInterface* const * FoundMaterial = InMaterials.Find(MaterialPathName); + bool bCanReuseExistingMaterial = false; + if (FoundMaterial) + { + bCanReuseExistingMaterial = (bInTreatExistingMaterialsAsUpToDate || !MaterialInfo.hasChanged) && !bForceRecookAll; + Material = Cast(*FoundMaterial); + } + + if(!Material || !bCanReuseExistingMaterial) + { + // Try to see if another output/part of this HDA has already recreated this material + // Since those materials have just been recreated, they are considered up to date and can always be reused. + FoundMaterial = InAllOutputMaterials.Find(MaterialPathName); + if (FoundMaterial) + { + Material = Cast(*FoundMaterial); + bCanReuseExistingMaterial = true; + } + } + + bool bCreatedNewMaterial = false; + if (Material && !Material->IsPendingKill()) + { + // If the cached material exists and is up to date, we can reuse it. + if (bCanReuseExistingMaterial) + { + OutMaterials.Add(MaterialPathName, Material); + continue; + } + } + else + { + // Previous Material was not found, we need to create a new one. + EObjectFlags ObjFlags = RF_Public | RF_Standalone; + + // Create material package and get material name. + FString MaterialPackageName; + UPackage * MaterialPackage = FHoudiniMaterialTranslator::CreatePackageForMaterial( + MaterialInfo.nodeId, MaterialName, InPackageParams, MaterialPackageName); + + Material = (UMaterial *)MaterialFactory->FactoryCreateNew( + UMaterial::StaticClass(), MaterialPackage, *MaterialPackageName, ObjFlags, NULL, GWarn); + + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, Material, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialName); + + bCreatedNewMaterial = true; + } + + if (!Material || Material->IsPendingKill()) + continue; + + // Get the asset name from the package params + FString AssetName = InPackageParams.HoudiniAssetName.IsEmpty() ? TEXT("HoudiniAsset") : InPackageParams.HoudiniAssetName; + + // Get the package and add it to our list + UPackage* Package = Material->GetOutermost(); + OutPackages.AddUnique(Package); + + /* + // TODO: This should be handled in the mesh/instance translator + // If this is an instancer material, enable the instancing flag. + if (UniqueInstancerMaterialIds.Contains(MaterialId)) + Material->bUsedWithInstancedStaticMeshes = true; + */ + + // Reset material expressions. + Material->Expressions.Empty(); + + // Generate various components for this material. + bool bMaterialComponentCreated = false; + int32 MaterialNodeY = FHoudiniMaterialTranslator::MaterialExpressionNodeY; + + // By default we mark material as opaque. Some of component creators can change this. + Material->BlendMode = BLEND_Opaque; + + // Extract diffuse plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract opacity plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract opacity mask plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract normal plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentNormal( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract specular plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract roughness plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract metallic plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Extract emissive plane. + bMaterialComponentCreated |= FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( + InAssetId, AssetName, MaterialInfo, InPackageParams, Material, OutPackages, MaterialNodeY); + + // Set other material properties. + Material->TwoSided = true; + Material->SetShadingModel(MSM_DefaultLit); + + // Schedule this material for update. + MaterialUpdateContext.AddMaterial(Material); + + // Cache material. + OutMaterials.Add(MaterialPathName, Material); + + // Propagate and trigger material updates. + if (bCreatedNewMaterial) + FAssetRegistryModule::AssetCreated(Material); + + Material->PreEditChange(nullptr); + Material->PostEditChange(); + Material->MarkPackageDirty(); + } + + MaterialFactory->RemoveFromRoot(); + + return true; +} + +// +bool +FHoudiniMaterialTranslator::CreateMaterialInstances( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& UniqueMaterialInstanceOverrides, + const TArray& InPackages, + const TMap& InMaterials, + TMap& OutMaterials, + const bool& bForceRecookAll) +{ + // Check the node ID is valid + if (InHGPO.AssetId < 0) + return false; + + // No material instance attributes + if (UniqueMaterialInstanceOverrides.Num() <= 0) + return false; + + // TODO: Improve! + // Get the material name from the material_instance attribute + // Since the material instance attribute can be set per primitive, it's going to be very difficult to know + // exactly where to look for the nth material instance. In order for the material slot to be created, + // we used the fact that the material instance attribute had to be different + // This is pretty hacky and we should probably require an extra material_instance_index attribute instead. + // as we can only create one instance of the same material, and cant get two slots for the same "source" material. + int32 MaterialIndex = 0; + for (TMap::TConstIterator Iter(UniqueMaterialInstanceOverrides); Iter; ++Iter) + { + FString CurrentSourceMaterial = Iter->Key; + if (CurrentSourceMaterial.IsEmpty()) + continue; + + // Try to find the material we want to create an instance of + UMaterialInterface* CurrentSourceMaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentSourceMaterial, nullptr, LOAD_NoWarn, nullptr)); + + if (!CurrentSourceMaterialInterface || CurrentSourceMaterialInterface->IsPendingKill()) + { + // Couldn't find the source material + HOUDINI_LOG_WARNING(TEXT("Couldn't find the source material %s to create a material instance."), *CurrentSourceMaterial); + continue; + } + + // Create/Retrieve the package for the MI + FString MaterialInstanceName; + FString MaterialInstanceNamePrefix = UPackageTools::SanitizePackageName( + CurrentSourceMaterialInterface->GetName() + TEXT("_instance_") + FString::FromInt(MaterialIndex)); + + // Increase the material index + MaterialIndex++; + + // See if we can find an existing package for that instance + UPackage * MaterialInstancePackage = nullptr; + UMaterialInterface * const * FoundMatPtr = InMaterials.Find(MaterialInstanceNamePrefix); + if (FoundMatPtr && *FoundMatPtr) + { + // We found an already existing MI, get its package + MaterialInstancePackage = Cast((*FoundMatPtr)->GetOuter()); + } + + if (MaterialInstancePackage) + { + MaterialInstanceName = MaterialInstancePackage->GetName(); + } + else + { + // We couldnt find the corresponding M_I package, so create a new one + MaterialInstancePackage = CreatePackageForMaterial(InHGPO.AssetId, MaterialInstanceNamePrefix, InPackageParams, MaterialInstanceName); + } + + // Couldn't create a package for that Material Instance + if (!MaterialInstancePackage) + continue; + + bool bNewMaterialCreated = false; + UMaterialInstanceConstant* NewMaterialInstance = LoadObject(MaterialInstancePackage, *MaterialInstanceName, nullptr, LOAD_None, nullptr); + if (!NewMaterialInstance) + { + // Factory to create materials. + UMaterialInstanceConstantFactoryNew* MaterialInstanceFactory = NewObject< UMaterialInstanceConstantFactoryNew >(); + if (!MaterialInstanceFactory) + continue; + + // Create the new material instance + MaterialInstanceFactory->AddToRoot(); + MaterialInstanceFactory->InitialParent = CurrentSourceMaterialInterface; + NewMaterialInstance = (UMaterialInstanceConstant*)MaterialInstanceFactory->FactoryCreateNew( + UMaterialInstanceConstant::StaticClass(), MaterialInstancePackage, FName(*MaterialInstanceName), + RF_Public | RF_Standalone, NULL, GWarn); + + if (NewMaterialInstance) + bNewMaterialCreated = true; + + MaterialInstanceFactory->RemoveFromRoot(); + } + + if (!NewMaterialInstance) + { + HOUDINI_LOG_WARNING(TEXT("Couldn't access the material instance for %s"), *CurrentSourceMaterial); + continue; + } + + // Update context for generated materials (will trigger when the object goes out of scope). + FMaterialUpdateContext MaterialUpdateContext; + + bool bModifiedMaterialParameters = false; + // See if we need to override some of the material instance's parameters + TArray AllMatParams; + // Get the detail material parameters + int32 ParamCount = FHoudiniEngineUtils::GetGenericAttributeList( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, + AllMatParams, HAPI_ATTROWNER_DETAIL, -1); + + // Then the primitive material parameters + int32 MaterialIndexToAttributeIndex = Iter->Value; + ParamCount += FHoudiniEngineUtils::GetGenericAttributeList( + InHGPO.GeoId, InHGPO.PartId, HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX, + AllMatParams, HAPI_ATTROWNER_PRIM, MaterialIndexToAttributeIndex); + + for (int32 ParamIdx = 0; ParamIdx < AllMatParams.Num(); ParamIdx++) + { + // Try to update the material instance parameter corresponding to the attribute + if (UpdateMaterialInstanceParameter(AllMatParams[ParamIdx], NewMaterialInstance, InPackages)) + bModifiedMaterialParameters = true; + } + + // Schedule this material for update if needed. + if (bNewMaterialCreated || bModifiedMaterialParameters) + MaterialUpdateContext.AddMaterialInstance(NewMaterialInstance); + + if (bNewMaterialCreated) + { + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + MaterialInstancePackage, NewMaterialInstance, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *MaterialInstanceName); + // Notify registry that we have created a new material. + FAssetRegistryModule::AssetCreated(NewMaterialInstance); + } + + if (bNewMaterialCreated || bModifiedMaterialParameters) + { + // Dirty the material + NewMaterialInstance->MarkPackageDirty(); + + // Update the material instance + NewMaterialInstance->InitStaticPermutation(); + NewMaterialInstance->PreEditChange(nullptr); + NewMaterialInstance->PostEditChange(); + /* + // Automatically save the package to avoid further issue + MaterialInstancePackage->SetDirtyFlag( true ); + MaterialInstancePackage->FullyLoad(); + UPackage::SavePackage( + MaterialInstancePackage, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, + *FPackageName::LongPackageNameToFilename( MaterialInstancePackage->GetName(), FPackageName::GetAssetPackageExtension() ) ); + */ + } + + // Add the created material to the output assignement map + // Use the "source" material name as we want the instance to replace it + OutMaterials.Add(CurrentSourceMaterial, NewMaterialInstance); + } + + return true; +} + +bool FHoudiniMaterialTranslator::SortUniqueFaceMaterialOverridesAndCreateMaterialInstances( + const TArray& Materials, + const FHoudiniGeoPartObject& InHGPO, const FHoudiniPackageParams& InPackageParams, + const TArray& InPackages, const TMap& InMaterials, + TMap& OutMaterials, const bool& bForceRecookAll) +{ + // Map containing unique face materials override attribute + // and their first valid prim index + // We create only one material instance per attribute + TMap UniqueFaceMaterialOverrides; + for (int FaceIdx = 0; FaceIdx < Materials.Num(); FaceIdx++) + { + FString MatOverrideAttr = Materials[FaceIdx]; + if (UniqueFaceMaterialOverrides.Contains(MatOverrideAttr)) + continue; + + // Add the material override and face index to the map + UniqueFaceMaterialOverrides.Add(MatOverrideAttr, FaceIdx); + } + + return FHoudiniMaterialTranslator::CreateMaterialInstances( + InHGPO, InPackageParams, + UniqueFaceMaterialOverrides, InPackages, + InMaterials, OutMaterials, + bForceRecookAll); +} + +bool +FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath) +{ + HAPI_MaterialInfo MaterialInfo; + FHoudiniApi::MaterialInfo_Init(&MaterialInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), InMaterialNodeId, + &MaterialInfo), false); + + return GetMaterialRelativePath(InAssetId, MaterialInfo, OutRelativePath); +} +bool +FHoudiniMaterialTranslator::GetMaterialRelativePath(const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialInfo, FString& OutRelativePath) +{ + if (InAssetId < 0 || !InMaterialInfo.exists) + return false; + + // We want to get the asset node path so we can remove it from the material name + FString AssetNodeName = TEXT(""); + { + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), InAssetId, &AssetInfo), false); + + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &AssetNodeInfo), false); + + FHoudiniEngineString::ToFString(AssetNodeInfo.internalNodePathSH, AssetNodeName); + } + + // Get the material name from the info + FString MaterialNodeName = TEXT(""); + { + HAPI_NodeInfo MaterialNodeInfo; + FHoudiniApi::NodeInfo_Init(&MaterialNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &MaterialNodeInfo), false); + + FHoudiniEngineString::ToFString(MaterialNodeInfo.internalNodePathSH, MaterialNodeName); + } + + if (AssetNodeName.Len() > 0 && MaterialNodeName.Len() > 0) + { + // Remove AssetNodeName part from MaterialNodeName. Extra position is for separator. + OutRelativePath = MaterialNodeName.Mid(AssetNodeName.Len() + 1); + return true; + } + + return false; +} + + +UPackage* +FHoudiniMaterialTranslator::CreatePackageForMaterial( + const HAPI_NodeId& InMaterialNodeId, + const FString& InMaterialName, + const FHoudiniPackageParams& InPackageParams, + FString& OutMaterialName) +{ + FString MaterialDescriptor = TEXT("_material_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InMaterialName; + + FHoudiniPackageParams MyPackageParams = InPackageParams; + if (!MyPackageParams.ObjectName.IsEmpty()) + { + MyPackageParams.ObjectName += MaterialDescriptor; + } + else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) + { + MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + MaterialDescriptor; + } + else + { + MyPackageParams.ObjectName = MaterialDescriptor; + } + MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); + + return MyPackageParams.CreatePackageForObject(OutMaterialName); +} + + +UPackage* +FHoudiniMaterialTranslator::CreatePackageForTexture( + const HAPI_NodeId& InMaterialNodeId, + const FString& InTextureType, + const FHoudiniPackageParams& InPackageParams, + FString& OutTextureName) +{ + FString TextureInfoDescriptor = TEXT("_texture_") + FString::FromInt(InMaterialNodeId) + TEXT("_") + InTextureType; + FHoudiniPackageParams MyPackageParams = InPackageParams; + if (!MyPackageParams.ObjectName.IsEmpty()) + { + MyPackageParams.ObjectName += TextureInfoDescriptor; + } + else if (!MyPackageParams.HoudiniAssetName.IsEmpty()) + { + MyPackageParams.ObjectName = MyPackageParams.HoudiniAssetName + TextureInfoDescriptor; + } + else + { + MyPackageParams.ObjectName = TextureInfoDescriptor; + } + MyPackageParams.PackageMode = FHoudiniPackageParams::GetDefaultMaterialAndTextureCookMode(); + + return MyPackageParams.CreatePackageForObject(OutTextureName); +} + + +UTexture2D * +FHoudiniMaterialTranslator::CreateUnrealTexture( + UTexture2D* ExistingTexture, + const HAPI_ImageInfo& ImageInfo, + UPackage* Package, + const FString& TextureName, + const TArray& ImageBuffer, + const FCreateTexture2DParameters& TextureParameters, + const TextureGroup& LODGroup, + const FString& TextureType, + const FString& NodePath) +{ + if (!Package || Package->IsPendingKill()) + return nullptr; + + UTexture2D * Texture = nullptr; + if (ExistingTexture) + { + Texture = ExistingTexture; + } + else + { + // Create new texture object. + Texture = NewObject< UTexture2D >( + Package, UTexture2D::StaticClass(), *TextureName, + RF_Transactional); + + // Assign texture group. + Texture->LODGroup = LODGroup; + } + + // Add/Update meta information to package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *TextureName); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Texture, HAPI_UNREAL_PACKAGE_META_NODE_PATH, *NodePath); + + // Initialize texture source. + Texture->Source.Init(ImageInfo.xRes, ImageInfo.yRes, 1, 1, TSF_BGRA8); + + // Lock the texture. + uint8 * MipData = Texture->Source.LockMip(0); + + // Create base map. + uint8* DestPtr = nullptr; + uint32 SrcWidth = ImageInfo.xRes; + uint32 SrcHeight = ImageInfo.yRes; + const char * SrcData = &ImageBuffer[0]; + + for (uint32 y = 0; y < SrcHeight; y++) + { + DestPtr = &MipData[(SrcHeight - 1 - y) * SrcWidth * sizeof(FColor)]; + + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth * 4 + x * 4; + + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 2); // B + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 1); // G + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 0); // R + + if (TextureParameters.bUseAlpha) + *DestPtr++ = *(uint8*)(SrcData + DataOffset + 3); // A + else + *DestPtr++ = 0xFF; + } + } + + bool bHasAlphaValue = false; + if (TextureParameters.bUseAlpha) + { + // See if there is an actual alpha value in the texture or if we can ignore the texture alpha + for (uint32 y = 0; y < SrcHeight; y++) + { + for (uint32 x = 0; x < SrcWidth; x++) + { + uint32 DataOffset = y * SrcWidth * 4 + x * 4; + if (*(uint8*)(SrcData + DataOffset + 3) != 0xFF) + { + bHasAlphaValue = true; + break; + } + } + + if (bHasAlphaValue) + break; + } + } + + // Unlock the texture. + Texture->Source.UnlockMip(0); + + // Texture creation parameters. + Texture->SRGB = TextureParameters.bSRGB; + Texture->CompressionSettings = TextureParameters.CompressionSettings; + Texture->CompressionNoAlpha = !bHasAlphaValue; + Texture->DeferCompression = TextureParameters.bDeferCompression; + + // Set the Source Guid/Hash if specified. + /* + if ( TextureParameters.SourceGuidHash.IsValid() ) + { + Texture->Source.SetId( TextureParameters.SourceGuidHash, true ); + } + */ + + Texture->PostEditChange(); + + return Texture; +} + + + +bool +FHoudiniMaterialTranslator::HapiExtractImage( + const HAPI_ParmId& NodeParmId, + const HAPI_MaterialInfo& MaterialInfo, + const char * PlaneType, + const HAPI_ImageDataFormat& ImageDataFormat, + HAPI_ImagePacking ImagePacking, + bool bRenderToImage, + TArray& OutImageBuffer ) +{ + if (bRenderToImage) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, NodeParmId), false); + } + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo), false); + + ImageInfo.dataFormat = ImageDataFormat; + ImageInfo.interleaved = true; + ImageInfo.packing = ImagePacking; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetImageInfo( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImageInfo), false); + + int32 ImageBufferSize = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ExtractImageToMemory( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, HAPI_RAW_FORMAT_NAME, + PlaneType, &ImageBufferSize), false); + + if (ImageBufferSize <= 0) + return false; + + OutImageBuffer.SetNumUninitialized(ImageBufferSize); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImageMemoryBuffer( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &OutImageBuffer[0], + ImageBufferSize), false); + + return true; +} + +bool +FHoudiniMaterialTranslator::HapiGetImagePlanes( + const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes) +{ + OutImagePlanes.Empty(); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::RenderTextureToImage( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, NodeParmId), false); + + int32 ImagePlaneCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlaneCount( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImagePlaneCount), false); + + if (ImagePlaneCount <= 0) + return true; + + TArray ImagePlaneStringHandles; + ImagePlaneStringHandles.SetNumZeroed(ImagePlaneCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetImagePlanes( + FHoudiniEngine::Get().GetSession(), + MaterialInfo.nodeId, &ImagePlaneStringHandles[0], ImagePlaneCount), false); + + FHoudiniEngineString::SHArrayToFStringArray(ImagePlaneStringHandles, OutImagePlanes); + + return true; +} + + +UMaterialExpression * +FHoudiniMaterialTranslator::MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass) +{ + if (!Expression) + return nullptr; + +#if WITH_EDITOR + if (ExpressionClass == Expression->GetClass()) + return Expression; + + // If this is a channel multiply expression, we can recurse. + UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast< UMaterialExpressionMultiply >(Expression); + if (MaterialExpressionMultiply) + { + { + UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->A.Expression; + if (MaterialExpression) + { + if (MaterialExpression->GetClass() == ExpressionClass) + return MaterialExpression; + + MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( + Cast(MaterialExpression), ExpressionClass); + + if (MaterialExpression) + return MaterialExpression; + } + } + + { + UMaterialExpression * MaterialExpression = MaterialExpressionMultiply->B.Expression; + if (MaterialExpression) + { + if (MaterialExpression->GetClass() == ExpressionClass) + return MaterialExpression; + + MaterialExpression = FHoudiniMaterialTranslator::MaterialLocateExpression( + Cast(MaterialExpression), ExpressionClass); + + if (MaterialExpression) + return MaterialExpression; + } + } + } +#endif + + return nullptr; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Names of generating Houdini parameters. + FString GeneratingParameterNameDiffuseTexture = TEXT(""); + FString GeneratingParameterNameUniformColor = TEXT(""); + FString GeneratingParameterNameVertexColor = TEXT(HAPI_UNREAL_ATTRIB_COLOR); + + // Diffuse texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Default; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // Attempt to look up previously created expressions. + UMaterialExpression * MaterialExpression = Material->BaseColor.Expression; + + // Locate sampling expression. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureSample = + Cast< UMaterialExpressionTextureSampleParameter2D >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // If texture sampling expression does exist, attempt to look up corresponding texture. + UTexture2D * TextureDiffuse = nullptr; + if (IsValid(ExpressionTextureSample)) + TextureDiffuse = Cast< UTexture2D >(ExpressionTextureSample->Texture); + + // Locate uniform color expression. + UMaterialExpressionVectorParameter * ExpressionConstant4Vector = + Cast< UMaterialExpressionVectorParameter >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionVectorParameter::StaticClass())); + + // If uniform color expression does not exist, create it. + if (!ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill()) + { + ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( + Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); + ExpressionConstant4Vector->DefaultValue = FLinearColor::White; + } + + // Add expression. + Material->Expressions.Add(ExpressionConstant4Vector); + + // Locate vertex color expression. + UMaterialExpressionVertexColor * ExpressionVertexColor = + Cast< UMaterialExpressionVertexColor >(FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionVertexColor::StaticClass())); + + // If vertex color expression does not exist, create it. + if (!ExpressionVertexColor || ExpressionVertexColor->IsPendingKill()) + { + ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( + Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag); + ExpressionVertexColor->Desc = GeneratingParameterNameVertexColor; + } + + // Add expression. + Material->Expressions.Add(ExpressionVertexColor); + + // Material should have at least one multiply expression. + UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast(MaterialExpression); + if (!MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill()) + MaterialExpressionMultiply = NewObject( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + // Add expression. + Material->Expressions.Add(MaterialExpressionMultiply); + + // See if primary multiplication has secondary multiplication as A input. + UMaterialExpressionMultiply * MaterialExpressionMultiplySecondary = nullptr; + if (MaterialExpressionMultiply->A.Expression) + MaterialExpressionMultiplySecondary = + Cast(MaterialExpressionMultiply->A.Expression); + + // See if a diffuse texture is available. + HAPI_ParmInfo ParmDiffuseTextureInfo; + HAPI_ParmId ParmDiffuseTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, + true, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via OGL tag + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, + false, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via Parm name + GeneratingParameterNameDiffuseTexture = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); + } + else + { + // failed to find the texture + ParmDiffuseTextureId = -1; + } + + // If we have diffuse texture parameter. + if (ParmDiffuseTextureId >= 0) + { + TArray ImageBuffer; + + // Get image planes of diffuse map. + TArray DiffuseImagePlanes; + bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( + ParmDiffuseTextureId, InMaterialInfo, DiffuseImagePlanes); + + HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; + const char * PlaneType = ""; + + if (bFoundImagePlanes && DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))) + { + if (DiffuseImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA))) + { + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + + // Material does use alpha. + CreateTexture2DParameters.bUseAlpha = true; + } + else + { + // We still need to have the Alpha plane, just not the CreateTexture2DParameters + // alpha option. This is because all texture data from Houdini Engine contains + // the alpha plane by default. + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + } + } + else + { + bFoundImagePlanes = false; + } + + // Retrieve color plane. + if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( + ParmDiffuseTextureId, InMaterialInfo, PlaneType, + HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) + { + UPackage * TextureDiffusePackage = nullptr; + if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + TextureDiffusePackage = Cast(TextureDiffuse->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureDiffuseName; + bool bCreatedNewTextureDiffuse = false; + + // Create diffuse texture package, if this is a new diffuse texture. + if (!TextureDiffusePackage) + { + TextureDiffusePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, + InPackageParams, + TextureDiffuseName); + } + else if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureDiffuseName = TextureDiffuse->GetName(); + } + else + { + TextureDiffuseName = FPaths::GetBaseFilename(TextureDiffusePackage->GetName(), true); + } + + // Create diffuse texture, if we need to create one. + if (!TextureDiffuse || TextureDiffuse->IsPendingKill()) + bCreatedNewTextureDiffuse = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing diffuse texture, or create new one. + TextureDiffuse = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureDiffuse, + ImageInfo, + TextureDiffusePackage, + TextureDiffuseName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureDiffuse->SetFlags(RF_Public | RF_Standalone); + + // Create diffuse sampling expression, if needed. + if (!ExpressionTextureSample) + { + ExpressionTextureSample = NewObject( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionTextureSample->Desc = GeneratingParameterNameDiffuseTexture; + ExpressionTextureSample->ParameterName = *GeneratingParameterNameDiffuseTexture; + ExpressionTextureSample->Texture = TextureDiffuse; + ExpressionTextureSample->SamplerType = SAMPLERTYPE_Color; + + // Add expression. + Material->Expressions.Add(ExpressionTextureSample); + + // Propagate and trigger diffuse texture updates. + if (bCreatedNewTextureDiffuse) + FAssetRegistryModule::AssetCreated(TextureDiffuse); + + TextureDiffuse->PreEditChange(nullptr); + TextureDiffuse->PostEditChange(); + TextureDiffuse->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureDiffusePackage); + } + } + + // See if uniform color is available. + HAPI_ParmInfo ParmDiffuseColorInfo; + HAPI_ParmId ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL, ParmDiffuseColorInfo); + + if (ParmDiffuseColorId >= 0) + { + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE_OGL); + } + else + { + ParmDiffuseColorId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_DIFFUSE, ParmDiffuseColorInfo); + + if (ParmDiffuseColorId >= 0) + GeneratingParameterNameUniformColor = TEXT(HAPI_UNREAL_PARAM_COLOR_DIFFUSE); + } + + // If we have uniform color parameter. + if (ParmDiffuseColorId >= 0) + { + FLinearColor Color = FLinearColor::White; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&Color.R, + ParmDiffuseColorInfo.floatValuesIndex, ParmDiffuseColorInfo.size) == HAPI_RESULT_SUCCESS) + { + if (ParmDiffuseColorInfo.size == 3) + Color.A = 1.0f; + + // Record generating parameter. + ExpressionConstant4Vector->Desc = GeneratingParameterNameUniformColor; + ExpressionConstant4Vector->ParameterName = *GeneratingParameterNameUniformColor; + ExpressionConstant4Vector->DefaultValue = Color; + } + } + + // If we have have texture sample expression present, we need a secondary multiplication expression. + if (ExpressionTextureSample) + { + if (!MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary = NewObject( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + // Add expression. + Material->Expressions.Add(MaterialExpressionMultiplySecondary); + } + } + else + { + // If secondary multiplication exists, but we have no sampling, we can free it. + if (MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary->A.Expression = nullptr; + MaterialExpressionMultiplySecondary->B.Expression = nullptr; + MaterialExpressionMultiplySecondary->ConditionalBeginDestroy(); + } + } + + float SecondaryExpressionScale = 1.0f; + if (MaterialExpressionMultiplySecondary) + SecondaryExpressionScale = 1.5f; + + // Create multiplication expression which has uniform color and vertex color. + MaterialExpressionMultiply->A.Expression = ExpressionConstant4Vector; + MaterialExpressionMultiply->B.Expression = ExpressionVertexColor; + + ExpressionConstant4Vector->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionConstant4Vector->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + ExpressionVertexColor->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionVertexColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiply->MaterialExpressionEditorY = + (ExpressionVertexColor->MaterialExpressionEditorY - ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; + + // Hook up secondary multiplication expression to first one. + if (MaterialExpressionMultiplySecondary) + { + MaterialExpressionMultiplySecondary->A.Expression = MaterialExpressionMultiply; + MaterialExpressionMultiplySecondary->B.Expression = ExpressionTextureSample; + + if (ExpressionTextureSample) + { + ExpressionTextureSample->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - + FHoudiniMaterialTranslator::MaterialExpressionNodeStepX * SecondaryExpressionScale; + ExpressionTextureSample->MaterialExpressionEditorY = MaterialNodeY; + } + + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + MaterialExpressionMultiplySecondary->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiplySecondary->MaterialExpressionEditorY = + MaterialExpressionMultiply->MaterialExpressionEditorY + FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression. + Material->BaseColor.Expression = MaterialExpressionMultiplySecondary; + } + else + { + // Assign expression. + Material->BaseColor.Expression = MaterialExpressionMultiply; + + MaterialExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + MaterialExpressionMultiply->MaterialExpressionEditorY = + (ExpressionVertexColor->MaterialExpressionEditorY - + ExpressionConstant4Vector->MaterialExpressionEditorY) / 2; + } + + return true; +} + + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + // Name of generating Houdini parameters. + FString GeneratingParameterNameTexture = TEXT(""); + + UMaterialExpression * MaterialExpression = Material->OpacityMask.Expression; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Opacity expressions. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; + UTexture2D * TextureOpacity = nullptr; + + // Opacity texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // See if opacity texture is available. + HAPI_ParmInfo ParmOpacityTextureInfo; + HAPI_ParmId ParmOpacityTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_OPACITY_OGL, + HAPI_UNREAL_PARAM_MAP_OPACITY_OGL_ENABLED, + true, + ParmOpacityTextureId, + ParmOpacityTextureInfo)) + { + // Found via OGL tag + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_OPACITY, + HAPI_UNREAL_PARAM_MAP_OPACITY_ENABLED, + false, + ParmOpacityTextureId, + ParmOpacityTextureInfo)) + { + // Found via Parm name + GeneratingParameterNameTexture = TEXT(HAPI_UNREAL_PARAM_MAP_OPACITY); + } + else + { + // failed to find the texture + ParmOpacityTextureId = -1; + } + + // If we have opacity texture parameter. + if (ParmOpacityTextureId >= 0) + { + TArray< char > ImageBuffer; + + // Get image planes of opacity map. + TArray< FString > OpacityImagePlanes; + bool bFoundImagePlanes = FHoudiniMaterialTranslator::HapiGetImagePlanes( + ParmOpacityTextureId, InMaterialInfo, OpacityImagePlanes); + + HAPI_ImagePacking ImagePacking = HAPI_IMAGE_PACKING_UNKNOWN; + const char * PlaneType = ""; + + bool bColorAlphaFound = (OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_ALPHA)) && OpacityImagePlanes.Contains(TEXT(HAPI_UNREAL_MATERIAL_TEXTURE_COLOR))); + + if (bFoundImagePlanes && bColorAlphaFound) + { + ImagePacking = HAPI_IMAGE_PACKING_RGBA; + PlaneType = HAPI_UNREAL_MATERIAL_TEXTURE_COLOR_ALPHA; + CreateTexture2DParameters.bUseAlpha = true; + } + else + { + bFoundImagePlanes = false; + } + + if (bFoundImagePlanes && FHoudiniMaterialTranslator::HapiExtractImage( + ParmOpacityTextureId, InMaterialInfo, PlaneType, + HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) + { + // Locate sampling expression. + ExpressionTextureOpacitySample = Cast< UMaterialExpressionTextureSampleParameter2D >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // Locate opacity texture, if valid. + if (ExpressionTextureOpacitySample) + TextureOpacity = Cast< UTexture2D >(ExpressionTextureOpacitySample->Texture); + + UPackage * TextureOpacityPackage = nullptr; + if (TextureOpacity) + TextureOpacityPackage = Cast< UPackage >(TextureOpacity->GetOuter()); + + HAPI_ImageInfo ImageInfo; + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureOpacityName; + bool bCreatedNewTextureOpacity = false; + + // Create opacity texture package, if this is a new opacity texture. + if (!TextureOpacityPackage) + { + TextureOpacityPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, + InPackageParams, + TextureOpacityName); + } + else if (TextureOpacity && !TextureOpacity->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureOpacityName = TextureOpacity->GetName(); + } + else + { + TextureOpacityName = FPaths::GetBaseFilename(TextureOpacityPackage->GetName(), true); + } + + // Create opacity texture, if we need to create one. + if (!TextureOpacity) + bCreatedNewTextureOpacity = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing opacity texture, or create new one. + TextureOpacity = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureOpacity, + ImageInfo, + TextureOpacityPackage, + TextureOpacityName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, + NodePath); + + // if (BakeMode == EBakeMode::CookToTemp) + TextureOpacity->SetFlags(RF_Public | RF_Standalone); + + // Create opacity sampling expression, if needed. + if (!ExpressionTextureOpacitySample) + { + ExpressionTextureOpacitySample = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionTextureOpacitySample->Desc = GeneratingParameterNameTexture; + ExpressionTextureOpacitySample->ParameterName = *GeneratingParameterNameTexture; + ExpressionTextureOpacitySample->Texture = TextureOpacity; + ExpressionTextureOpacitySample->SamplerType = SAMPLERTYPE_Grayscale; + + // Offset node placement. + ExpressionTextureOpacitySample->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionTextureOpacitySample->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Add expression. + Material->Expressions.Add(ExpressionTextureOpacitySample); + + // We need to set material type to masked. + TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput* ExpressionOutput = ExpressionOutputs.GetData(); + + Material->OpacityMask.Expression = ExpressionTextureOpacitySample; + Material->BlendMode = BLEND_Masked; + + Material->OpacityMask.Mask = ExpressionOutput->Mask; + Material->OpacityMask.MaskR = 1; + Material->OpacityMask.MaskG = 0; + Material->OpacityMask.MaskB = 0; + Material->OpacityMask.MaskA = 0; + + // Propagate and trigger opacity texture updates. + if (bCreatedNewTextureOpacity) + FAssetRegistryModule::AssetCreated(TextureOpacity); + + TextureOpacity->PreEditChange(nullptr); + TextureOpacity->PostEditChange(); + TextureOpacity->MarkPackageDirty(); + + bExpressionCreated = true; + } + + // Cache the texture package + OutPackages.AddUnique(TextureOpacityPackage); + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + float OpacityValue = 1.0f; + bool bNeedsTranslucency = false; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameters. + FString GeneratingParameterNameScalar = TEXT(""); + FString GeneratingParameterNameTexture = TEXT(""); + + UMaterialExpression * MaterialExpression = Material->Opacity.Expression; + + // Opacity expressions. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureOpacitySample = nullptr; + UMaterialExpressionScalarParameter * ExpressionScalarOpacity = nullptr; + UTexture2D * TextureOpacity = nullptr; + + // Opacity texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = true; + + // If opacity sampling expression was not created, check if diffuse contains an alpha plane. + if (!ExpressionTextureOpacitySample) + { + UMaterialExpression * MaterialExpressionDiffuse = Material->BaseColor.Expression; + if (MaterialExpressionDiffuse) + { + // Locate diffuse sampling expression. + UMaterialExpressionTextureSampleParameter2D * ExpressionTextureDiffuseSample = + Cast< UMaterialExpressionTextureSampleParameter2D >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpressionDiffuse, + UMaterialExpressionTextureSampleParameter2D::StaticClass())); + + // See if there's an alpha plane in this expression's texture. + if (ExpressionTextureDiffuseSample) + { + UTexture2D * DiffuseTexture = Cast< UTexture2D >(ExpressionTextureDiffuseSample->Texture); + if (DiffuseTexture && !DiffuseTexture->CompressionNoAlpha) + { + // The diffuse texture has an alpha channel (that wasn't discarded), so we can use it + ExpressionTextureOpacitySample = ExpressionTextureDiffuseSample; + bNeedsTranslucency = true; + } + } + } + } + + // Retrieve opacity value + HAPI_ParmInfo ParmOpacityValueInfo; + HAPI_ParmId ParmOpacityValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA_OGL, ParmOpacityValueInfo); + + if (ParmOpacityValueId >= 0) + { + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA_OGL); + } + else + { + ParmOpacityValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_ALPHA, ParmOpacityValueInfo); + + if (ParmOpacityValueId >= 0) + GeneratingParameterNameScalar = TEXT(HAPI_UNREAL_PARAM_ALPHA); + } + + if (ParmOpacityValueId >= 0) + { + if (ParmOpacityValueInfo.size > 0 && ParmOpacityValueInfo.floatValuesIndex >= 0) + { + float OpacityValueRetrieved = 1.0f; + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, + (float *)&OpacityValue, ParmOpacityValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + if (!ExpressionScalarOpacity) + { + ExpressionScalarOpacity = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Clamp retrieved value. + OpacityValueRetrieved = FMath::Clamp< float >(OpacityValueRetrieved, 0.0f, 1.0f); + OpacityValue = OpacityValueRetrieved; + + // Set expression fields. + ExpressionScalarOpacity->DefaultValue = OpacityValue; + ExpressionScalarOpacity->SliderMin = 0.0f; + ExpressionScalarOpacity->SliderMax = 1.0f; + ExpressionScalarOpacity->Desc = GeneratingParameterNameScalar; + ExpressionScalarOpacity->ParameterName = *GeneratingParameterNameScalar; + + // Add expression. + Material->Expressions.Add(ExpressionScalarOpacity); + + // If alpha is less than 1, we need translucency. + bNeedsTranslucency |= (OpacityValue != 1.0f); + } + } + } + + if (bNeedsTranslucency) + Material->BlendMode = BLEND_Translucent; + + if (ExpressionScalarOpacity && ExpressionTextureOpacitySample) + { + // We have both alpha and alpha uniform, attempt to locate multiply expression. + UMaterialExpressionMultiply * ExpressionMultiply = + Cast< UMaterialExpressionMultiply >( + FHoudiniMaterialTranslator::MaterialLocateExpression( + MaterialExpression, + UMaterialExpressionMultiply::StaticClass())); + + if (!ExpressionMultiply) + ExpressionMultiply = NewObject< UMaterialExpressionMultiply >( + Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); + + Material->Expressions.Add(ExpressionMultiply); + + TArray< FExpressionOutput > ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); + + ExpressionMultiply->A.Expression = ExpressionTextureOpacitySample; + ExpressionMultiply->B.Expression = ExpressionScalarOpacity; + + Material->Opacity.Expression = ExpressionMultiply; + Material->Opacity.Mask = ExpressionOutput->Mask; + Material->Opacity.MaskR = 0; + Material->Opacity.MaskG = 0; + Material->Opacity.MaskB = 0; + Material->Opacity.MaskA = 1; + + ExpressionMultiply->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMultiply->MaterialExpressionEditorY = MaterialNodeY; + + ExpressionScalarOpacity->MaterialExpressionEditorX = + FHoudiniMaterialTranslator::MaterialExpressionNodeX - FHoudiniMaterialTranslator::MaterialExpressionNodeStepX; + ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + bExpressionCreated = true; + } + else if (ExpressionScalarOpacity) + { + Material->Opacity.Expression = ExpressionScalarOpacity; + + ExpressionScalarOpacity->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionScalarOpacity->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + bExpressionCreated = true; + } + else if (ExpressionTextureOpacitySample) + { + TArray ExpressionOutputs = ExpressionTextureOpacitySample->GetOutputs(); + FExpressionOutput * ExpressionOutput = ExpressionOutputs.GetData(); + + Material->Opacity.Expression = ExpressionTextureOpacitySample; + Material->Opacity.Mask = ExpressionOutput->Mask; + Material->Opacity.MaskR = 0; + Material->Opacity.MaskG = 0; + Material->Opacity.MaskB = 0; + Material->Opacity.MaskA = 1; + + bExpressionCreated = true; + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentNormal( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + bool bTangentSpaceNormal = true; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Normal texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Normalmap; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if separate normal texture is available. + HAPI_ParmInfo ParmNormalTextureInfo; + HAPI_ParmId ParmNormalTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_NORMAL, + HAPI_UNREAL_PARAM_MAP_NORMAL_ENABLED, + false, + ParmNormalTextureId, + ParmNormalTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_NORMAL_OGL, + "", + true, + ParmNormalTextureId, + ParmNormalTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_OGL); + } + else + { + // failed to find the texture + ParmNormalTextureId = -1; + } + + if (ParmNormalTextureId >= 0) + { + // Retrieve space for this normal texture. + HAPI_ParmInfo ParmInfoNormalType; + int32 ParmNormalTypeId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE, ParmInfoNormalType); + + // Retrieve value for normal type choice list (if exists). + if (ParmNormalTypeId >= 0) + { + FString NormalType = TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_TANGENT); + if (ParmInfoNormalType.size > 0 && ParmInfoNormalType.stringValuesIndex >= 0) + { + HAPI_StringHandle StringHandle; + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, false, &StringHandle, ParmInfoNormalType.stringValuesIndex, ParmInfoNormalType.size) == HAPI_RESULT_SUCCESS) + { + // Get the actual string value. + FString NormalTypeString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(NormalTypeString)) + NormalType = NormalTypeString; + } + } + + // Check if we require world space normals. + if (NormalType.Equals(TEXT(HAPI_UNREAL_PARAM_MAP_NORMAL_TYPE_WORLD), ESearchCase::IgnoreCase)) + bTangentSpaceNormal = false; + } + + // Retrieve color plane. + TArray ImageBuffer; + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmNormalTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Normal.Expression); + + UTexture2D * TextureNormal = nullptr; + if (ExpressionNormal) + { + TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Normal.Expression) + { + Material->Normal.Expression->ConditionalBeginDestroy(); + Material->Normal.Expression = nullptr; + } + } + + UPackage * TextureNormalPackage = nullptr; + if (TextureNormal) + TextureNormalPackage = Cast< UPackage >(TextureNormal->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureNormalName; + bool bCreatedNewTextureNormal = false; + + // Create normal texture package, if this is a new normal texture. + if (!TextureNormalPackage) + { + TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + InPackageParams, + TextureNormalName); + } + else if (TextureNormal && !TextureNormal->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureNormalName = TextureNormal->GetName(); + } + else + { + TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); + } + + // Create normal texture, if we need to create one. + if (!TextureNormal) + bCreatedNewTextureNormal = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing normal texture, or create new one. + TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureNormal, + ImageInfo, + TextureNormalPackage, + TextureNormalName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_WorldNormalMap, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureNormal->SetFlags(RF_Public | RF_Standalone); + + // Create normal sampling expression, if needed. + if (!ExpressionNormal) + ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionNormal->Desc = GeneratingParameterName; + ExpressionNormal->ParameterName = *GeneratingParameterName; + + ExpressionNormal->Texture = TextureNormal; + ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; + + // Offset node placement. + ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Set normal space. + Material->bTangentSpaceNormal = bTangentSpaceNormal; + + // Assign expression to material. + Material->Expressions.Add(ExpressionNormal); + Material->Normal.Expression = ExpressionNormal; + + bExpressionCreated = true; + + // Propagate and trigger normal texture updates. + if (bCreatedNewTextureNormal) + FAssetRegistryModule::AssetCreated(TextureNormal); + + TextureNormal->PreEditChange(nullptr); + TextureNormal->PostEditChange(); + TextureNormal->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureNormalPackage); + } + } + + // If separate normal map was not found, see if normal plane exists in diffuse map. + if (!bExpressionCreated) + { + // See if diffuse texture is available. + HAPI_ParmInfo ParmDiffuseTextureInfo; + HAPI_ParmId ParmDiffuseTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL_ENABLED, + true, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_DIFFUSE, + HAPI_UNREAL_PARAM_MAP_DIFFUSE_ENABLED, + false, + ParmDiffuseTextureId, + ParmDiffuseTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_DIFFUSE); + } + else + { + // failed to find the texture + ParmDiffuseTextureId = -1; + } + + if (ParmDiffuseTextureId >= 0) + { + // Normal plane is available in diffuse map. + TArray ImageBuffer; + + // Retrieve color plane - this will contain normal data. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmDiffuseTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_NORMAL, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGB, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionNormal = + Cast(Material->Normal.Expression); + + UTexture2D* TextureNormal = nullptr; + if (ExpressionNormal) + { + TextureNormal = Cast< UTexture2D >(ExpressionNormal->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Normal.Expression) + { + Material->Normal.Expression->ConditionalBeginDestroy(); + Material->Normal.Expression = nullptr; + } + } + + UPackage* TextureNormalPackage = nullptr; + if (TextureNormal) + TextureNormalPackage = Cast(TextureNormal->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureNormalName; + bool bCreatedNewTextureNormal = false; + + // Create normal texture package, if this is a new normal texture. + if (!TextureNormalPackage) + { + TextureNormalPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + InPackageParams, + TextureNormalName); + } + else if (TextureNormal && !TextureNormal->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureNormalName = TextureNormal->GetName(); + } + else + { + TextureNormalName = FPaths::GetBaseFilename(TextureNormalPackage->GetName(), true); + } + + // Create normal texture, if we need to create one. + if (!TextureNormal) + bCreatedNewTextureNormal = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing normal texture, or create new one. + TextureNormal = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureNormal, + ImageInfo, + TextureNormalPackage, + TextureNormalName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_WorldNormalMap, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureNormal->SetFlags(RF_Public | RF_Standalone); + + // Create normal sampling expression, if needed. + if (!ExpressionNormal) + ExpressionNormal = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionNormal->Desc = GeneratingParameterName; + ExpressionNormal->ParameterName = *GeneratingParameterName; + + ExpressionNormal->Texture = TextureNormal; + ExpressionNormal->SamplerType = SAMPLERTYPE_Normal; + + // Offset node placement. + ExpressionNormal->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionNormal->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Set normal space. + Material->bTangentSpaceNormal = bTangentSpaceNormal; + + // Assign expression to material. + Material->Expressions.Add(ExpressionNormal); + Material->Normal.Expression = ExpressionNormal; + + // Propagate and trigger diffuse texture updates. + if (bCreatedNewTextureNormal) + FAssetRegistryModule::AssetCreated(TextureNormal); + + TextureNormal->PreEditChange(nullptr); + TextureNormal->PostEditChange(); + TextureNormal->MarkPackageDirty(); + + bExpressionCreated = true; + } + + // Cache the texture package + OutPackages.AddUnique(TextureNormalPackage); + } + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Specular texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if specular texture is available. + HAPI_ParmInfo ParmSpecularTextureInfo; + HAPI_ParmId ParmSpecularTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL, + HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL_ENABLED, + true, + ParmSpecularTextureId, + ParmSpecularTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_SPECULAR, + HAPI_UNREAL_PARAM_MAP_SPECULAR_ENABLED, + false, + ParmSpecularTextureId, + ParmSpecularTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_SPECULAR); + } + else + { + // failed to find the texture + ParmSpecularTextureId = -1; + } + + if (ParmSpecularTextureId >= 0) + { + TArray ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmSpecularTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionSpecular = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Specular.Expression); + + UTexture2D * TextureSpecular = nullptr; + if (ExpressionSpecular) + { + TextureSpecular = Cast< UTexture2D >(ExpressionSpecular->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Specular.Expression) + { + Material->Specular.Expression->ConditionalBeginDestroy(); + Material->Specular.Expression = nullptr; + } + } + + UPackage * TextureSpecularPackage = nullptr; + if (TextureSpecular) + TextureSpecularPackage = Cast< UPackage >(TextureSpecular->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureSpecularName; + bool bCreatedNewTextureSpecular = false; + + // Create specular texture package, if this is a new specular texture. + if (!TextureSpecularPackage) + { + TextureSpecularPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, + InPackageParams, + TextureSpecularName); + } + else if (TextureSpecular && !TextureSpecular->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureSpecularName = TextureSpecular->GetName(); + } + else + { + TextureSpecularName = FPaths::GetBaseFilename(TextureSpecularPackage->GetName(), true); + } + + // Create specular texture, if we need to create one. + if (!TextureSpecular) + bCreatedNewTextureSpecular = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing specular texture, or create new one. + TextureSpecular = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureSpecular, + ImageInfo, + TextureSpecularPackage, + TextureSpecularName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureSpecular->SetFlags(RF_Public | RF_Standalone); + + // Create specular sampling expression, if needed. + if (!ExpressionSpecular) + { + ExpressionSpecular = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionSpecular->Desc = GeneratingParameterName; + ExpressionSpecular->ParameterName = *GeneratingParameterName; + + ExpressionSpecular->Texture = TextureSpecular; + ExpressionSpecular->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionSpecular->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionSpecular->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionSpecular); + Material->Specular.Expression = ExpressionSpecular; + + bExpressionCreated = true; + + // Propagate and trigger specular texture updates. + if (bCreatedNewTextureSpecular) + FAssetRegistryModule::AssetCreated(TextureSpecular); + + TextureSpecular->PreEditChange(nullptr); + TextureSpecular->PostEditChange(); + TextureSpecular->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureSpecularPackage); + } + } + + // See if we have a specular color + HAPI_ParmInfo ParmSpecularColorInfo; + HAPI_ParmId ParmSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL, ParmSpecularColorInfo); + + if (ParmSpecularColorId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR_OGL); + } + else + { + ParmSpecularColorId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_COLOR_SPECULAR, ParmSpecularColorInfo); + + if (ParmSpecularColorId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_COLOR_SPECULAR); + } + + if (!bExpressionCreated && ParmSpecularColorId >= 0) + { + // Specular color is available. + FLinearColor Color = FLinearColor::White; + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, + ParmSpecularColorInfo.floatValuesIndex, ParmSpecularColorInfo.size) == HAPI_RESULT_SUCCESS) + { + if (ParmSpecularColorInfo.size == 3) + Color.A = 1.0f; + + UMaterialExpressionVectorParameter * ExpressionSpecularColor = + Cast< UMaterialExpressionVectorParameter >(Material->Specular.Expression); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionSpecularColor) + { + // Otherwise new expression is of a different type. + if (Material->Specular.Expression) + { + Material->Specular.Expression->ConditionalBeginDestroy(); + Material->Specular.Expression = nullptr; + } + + ExpressionSpecularColor = NewObject< UMaterialExpressionVectorParameter >( + Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionSpecularColor->Desc = GeneratingParameterName; + ExpressionSpecularColor->ParameterName = *GeneratingParameterName; + + ExpressionSpecularColor->DefaultValue = Color; + + // Offset node placement. + ExpressionSpecularColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionSpecularColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionSpecularColor); + Material->Specular.Expression = ExpressionSpecularColor; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Roughness texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if roughness texture is available. + HAPI_ParmInfo ParmRoughnessTextureInfo; + HAPI_ParmId ParmRoughnessTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL_ENABLED, + true, + ParmRoughnessTextureId, + ParmRoughnessTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS, + HAPI_UNREAL_PARAM_MAP_ROUGHNESS_ENABLED, + false, + ParmRoughnessTextureId, + ParmRoughnessTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_ROUGHNESS); + } + else + { + // failed to find the texture + ParmRoughnessTextureId = -1; + } + + if (ParmRoughnessTextureId >= 0) + { + TArray ImageBuffer; + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmRoughnessTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer ) ) + { + UMaterialExpressionTextureSampleParameter2D* ExpressionRoughness = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Roughness.Expression); + + UTexture2D* TextureRoughness = nullptr; + if (ExpressionRoughness) + { + TextureRoughness = Cast< UTexture2D >(ExpressionRoughness->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Roughness.Expression) + { + Material->Roughness.Expression->ConditionalBeginDestroy(); + Material->Roughness.Expression = nullptr; + } + } + + UPackage * TextureRoughnessPackage = nullptr; + if (TextureRoughness) + TextureRoughnessPackage = Cast< UPackage >(TextureRoughness->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureRoughnessName; + bool bCreatedNewTextureRoughness = false; + + // Create roughness texture package, if this is a new roughness texture. + if (!TextureRoughnessPackage) + { + TextureRoughnessPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, + InPackageParams, + TextureRoughnessName); + } + else if (TextureRoughness && !TextureRoughness->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureRoughnessName = TextureRoughness->GetName(); + } + else + { + TextureRoughnessName = FPaths::GetBaseFilename(TextureRoughnessPackage->GetName(), true); + } + + // Create roughness texture, if we need to create one. + if (!TextureRoughness) + bCreatedNewTextureRoughness = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing roughness texture, or create new one. + TextureRoughness = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureRoughness, + ImageInfo, + TextureRoughnessPackage, + TextureRoughnessName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureRoughness->SetFlags(RF_Public | RF_Standalone); + + // Create roughness sampling expression, if needed. + if (!ExpressionRoughness) + ExpressionRoughness = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionRoughness->Desc = GeneratingParameterName; + ExpressionRoughness->ParameterName = *GeneratingParameterName; + + ExpressionRoughness->Texture = TextureRoughness; + ExpressionRoughness->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionRoughness->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionRoughness->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionRoughness); + Material->Roughness.Expression = ExpressionRoughness; + + bExpressionCreated = true; + + // Propagate and trigger roughness texture updates. + if (bCreatedNewTextureRoughness) + FAssetRegistryModule::AssetCreated(TextureRoughness); + + TextureRoughness->PreEditChange(nullptr); + TextureRoughness->PostEditChange(); + TextureRoughness->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureRoughnessPackage); + } + } + + // See if we have a roughness value + HAPI_ParmInfo ParmRoughnessValueInfo; + HAPI_ParmId ParmRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL, ParmRoughnessValueInfo); + + if (ParmRoughnessValueId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS_OGL); + } + else + { + ParmRoughnessValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_ROUGHNESS, ParmRoughnessValueInfo); + + if (ParmRoughnessValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_ROUGHNESS); + } + + if (!bExpressionCreated && ParmRoughnessValueId >= 0) + { + // Roughness value is available. + + float RoughnessValue = 0.0f; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&RoughnessValue, + ParmRoughnessValueInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + UMaterialExpressionScalarParameter * ExpressionRoughnessValue = + Cast< UMaterialExpressionScalarParameter >(Material->Roughness.Expression); + + // Clamp retrieved value. + RoughnessValue = FMath::Clamp< float >(RoughnessValue, 0.0f, 1.0f); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionRoughnessValue) + { + // Otherwise new expression is of a different type. + if (Material->Roughness.Expression) + { + Material->Roughness.Expression->ConditionalBeginDestroy(); + Material->Roughness.Expression = nullptr; + } + + ExpressionRoughnessValue = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionRoughnessValue->Desc = GeneratingParameterName; + ExpressionRoughnessValue->ParameterName = *GeneratingParameterName; + + ExpressionRoughnessValue->DefaultValue = RoughnessValue; + ExpressionRoughnessValue->SliderMin = 0.0f; + ExpressionRoughnessValue->SliderMax = 1.0f; + + // Offset node placement. + ExpressionRoughnessValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionRoughnessValue->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionRoughnessValue); + Material->Roughness.Expression = ExpressionRoughnessValue; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Metallic texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if metallic texture is available. + HAPI_ParmInfo ParmMetallicTextureInfo; + HAPI_ParmId ParmMetallicTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_METALLIC_OGL, + HAPI_UNREAL_PARAM_MAP_METALLIC_OGL_ENABLED, + true, + ParmMetallicTextureId, + ParmMetallicTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_METALLIC, + HAPI_UNREAL_PARAM_MAP_METALLIC_ENABLED, + false, + ParmMetallicTextureId, + ParmMetallicTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_METALLIC); + } + else + { + // failed to find the texture + ParmMetallicTextureId = -1; + } + + if (ParmMetallicTextureId >= 0) + { + TArray ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmMetallicTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionMetallic = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->Metallic.Expression); + + UTexture2D * TextureMetallic = nullptr; + if (ExpressionMetallic) + { + TextureMetallic = Cast(ExpressionMetallic->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->Metallic.Expression) + { + Material->Metallic.Expression->ConditionalBeginDestroy(); + Material->Metallic.Expression = nullptr; + } + } + + UPackage * TextureMetallicPackage = nullptr; + if (TextureMetallic) + TextureMetallicPackage = Cast< UPackage >(TextureMetallic->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureMetallicName; + bool bCreatedNewTextureMetallic = false; + + // Create metallic texture package, if this is a new metallic texture. + if (!TextureMetallicPackage) + { + TextureMetallicPackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, + InPackageParams, + TextureMetallicName); + } + else if (TextureMetallic && !TextureMetallic->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureMetallicName = TextureMetallic->GetName(); + } + else + { + TextureMetallicName = FPaths::GetBaseFilename(TextureMetallicPackage->GetName(), true); + } + + // Create metallic texture, if we need to create one. + if (!TextureMetallic) + bCreatedNewTextureMetallic = true; + + // Get the node path to add it to the meta data + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing metallic texture, or create new one. + TextureMetallic = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureMetallic, + ImageInfo, + TextureMetallicPackage, + TextureMetallicName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureMetallic->SetFlags(RF_Public | RF_Standalone); + + // Create metallic sampling expression, if needed. + if (!ExpressionMetallic) + ExpressionMetallic = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionMetallic->Desc = GeneratingParameterName; + ExpressionMetallic->ParameterName = *GeneratingParameterName; + + ExpressionMetallic->Texture = TextureMetallic; + ExpressionMetallic->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionMetallic->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMetallic->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionMetallic); + Material->Metallic.Expression = ExpressionMetallic; + + bExpressionCreated = true; + + // Propagate and trigger metallic texture updates. + if (bCreatedNewTextureMetallic) + FAssetRegistryModule::AssetCreated(TextureMetallic); + + TextureMetallic->PreEditChange(nullptr); + TextureMetallic->PostEditChange(); + TextureMetallic->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureMetallicPackage); + } + } + + // Get the metallic value + HAPI_ParmInfo ParmMetallicValueInfo; + HAPI_ParmId ParmMetallicValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL, ParmMetallicValueInfo); + + if (ParmMetallicValueId >= 0) + { + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC_OGL); + } + else + { + ParmMetallicValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_METALLIC, ParmMetallicValueInfo); + + if (ParmMetallicValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_METALLIC); + } + + if (!bExpressionCreated && ParmMetallicValueId >= 0) + { + // Metallic value is available. + float MetallicValue = 0.0f; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float *)&MetallicValue, + ParmMetallicTextureInfo.floatValuesIndex, 1) == HAPI_RESULT_SUCCESS) + { + UMaterialExpressionScalarParameter * ExpressionMetallicValue = + Cast< UMaterialExpressionScalarParameter >(Material->Metallic.Expression); + + // Clamp retrieved value. + MetallicValue = FMath::Clamp< float >(MetallicValue, 0.0f, 1.0f); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionMetallicValue) + { + // Otherwise new expression is of a different type. + if (Material->Metallic.Expression) + { + Material->Metallic.Expression->ConditionalBeginDestroy(); + Material->Metallic.Expression = nullptr; + } + + ExpressionMetallicValue = NewObject< UMaterialExpressionScalarParameter >( + Material, UMaterialExpressionScalarParameter::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionMetallicValue->Desc = GeneratingParameterName; + ExpressionMetallicValue->ParameterName = *GeneratingParameterName; + + ExpressionMetallicValue->DefaultValue = MetallicValue; + ExpressionMetallicValue->SliderMin = 0.0f; + ExpressionMetallicValue->SliderMax = 1.0f; + + // Offset node placement. + ExpressionMetallicValue->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionMetallicValue->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionMetallicValue); + Material->Metallic.Expression = ExpressionMetallicValue; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + +bool +FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( + const HAPI_NodeId& InAssetId, + const FString& InHoudiniAssetName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY) +{ + if (!Material || Material->IsPendingKill()) + return false; + + bool bExpressionCreated = false; + HAPI_Result Result = HAPI_RESULT_SUCCESS; + + EObjectFlags ObjectFlag = (InPackageParams.PackageMode == EPackageMode::Bake) ? RF_Standalone : RF_NoFlags; + + // Name of generating Houdini parameter. + FString GeneratingParameterName = TEXT(""); + + // Emissive texture creation parameters. + FCreateTexture2DParameters CreateTexture2DParameters; + CreateTexture2DParameters.SourceGuidHash = FGuid(); + CreateTexture2DParameters.bUseAlpha = false; + CreateTexture2DParameters.CompressionSettings = TC_Grayscale; + CreateTexture2DParameters.bDeferCompression = true; + CreateTexture2DParameters.bSRGB = false; + + // See if emissive texture is available. + HAPI_ParmInfo ParmEmissiveTextureInfo; + HAPI_ParmId ParmEmissiveTextureId = -1; + if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL_ENABLED, + true, + ParmEmissiveTextureId, + ParmEmissiveTextureInfo)) + { + // Found via OGL tag + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE_OGL); + } + else if (FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + InMaterialInfo.nodeId, + HAPI_UNREAL_PARAM_MAP_EMISSIVE, + HAPI_UNREAL_PARAM_MAP_EMISSIVE_ENABLED, + false, + ParmEmissiveTextureId, + ParmEmissiveTextureInfo)) + { + // Found via Parm name + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_MAP_EMISSIVE); + } + else + { + // failed to find the texture + ParmEmissiveTextureId = -1; + } + + if (ParmEmissiveTextureId >= 0) + { + TArray< char > ImageBuffer; + + // Retrieve color plane. + if (FHoudiniMaterialTranslator::HapiExtractImage( + ParmEmissiveTextureId, InMaterialInfo, HAPI_UNREAL_MATERIAL_TEXTURE_COLOR, + HAPI_IMAGE_DATA_INT8, HAPI_IMAGE_PACKING_RGBA, true, ImageBuffer)) + { + UMaterialExpressionTextureSampleParameter2D * ExpressionEmissive = + Cast< UMaterialExpressionTextureSampleParameter2D >(Material->EmissiveColor.Expression); + + UTexture2D * TextureEmissive = nullptr; + if (ExpressionEmissive) + { + TextureEmissive = Cast< UTexture2D >(ExpressionEmissive->Texture); + } + else + { + // Otherwise new expression is of a different type. + if (Material->EmissiveColor.Expression) + { + Material->EmissiveColor.Expression->ConditionalBeginDestroy(); + Material->EmissiveColor.Expression = nullptr; + } + } + + UPackage * TextureEmissivePackage = nullptr; + if (TextureEmissive) + TextureEmissivePackage = Cast< UPackage >(TextureEmissive->GetOuter()); + + HAPI_ImageInfo ImageInfo; + FHoudiniApi::ImageInfo_Init(&ImageInfo); + Result = FHoudiniApi::GetImageInfo( + FHoudiniEngine::Get().GetSession(), + InMaterialInfo.nodeId, &ImageInfo); + + if (Result == HAPI_RESULT_SUCCESS && ImageInfo.xRes > 0 && ImageInfo.yRes > 0) + { + // Create texture. + FString TextureEmissiveName; + bool bCreatedNewTextureEmissive = false; + + // Create emissive texture package, if this is a new emissive texture. + if (!TextureEmissivePackage) + { + TextureEmissivePackage = FHoudiniMaterialTranslator::CreatePackageForTexture( + InMaterialInfo.nodeId, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, + InPackageParams, + TextureEmissiveName); + } + else if (TextureEmissive && !TextureEmissive->IsPendingKill()) + { + // Get the name of the texture if we are overwriting the exist asset + TextureEmissiveName = TextureEmissive->GetName(); + } + else + { + TextureEmissiveName = FPaths::GetBaseFilename(TextureEmissivePackage->GetName(), true); + } + + // Create emissive texture, if we need to create one. + if (!TextureEmissive) + bCreatedNewTextureEmissive = true; + + FString NodePath; + FHoudiniMaterialTranslator::GetMaterialRelativePath(InAssetId, InMaterialInfo.nodeId, NodePath); + + // Reuse existing emissive texture, or create new one. + TextureEmissive = FHoudiniMaterialTranslator::CreateUnrealTexture( + TextureEmissive, + ImageInfo, + TextureEmissivePackage, + TextureEmissiveName, + ImageBuffer, + CreateTexture2DParameters, + TEXTUREGROUP_World, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, + NodePath); + + //if (BakeMode == EBakeMode::CookToTemp) + TextureEmissive->SetFlags(RF_Public | RF_Standalone); + + // Create emissive sampling expression, if needed. + if (!ExpressionEmissive) + ExpressionEmissive = NewObject< UMaterialExpressionTextureSampleParameter2D >( + Material, UMaterialExpressionTextureSampleParameter2D::StaticClass(), NAME_None, ObjectFlag); + + // Record generating parameter. + ExpressionEmissive->Desc = GeneratingParameterName; + ExpressionEmissive->ParameterName = *GeneratingParameterName; + + ExpressionEmissive->Texture = TextureEmissive; + ExpressionEmissive->SamplerType = SAMPLERTYPE_LinearGrayscale; + + // Offset node placement. + ExpressionEmissive->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionEmissive->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionEmissive); + Material->EmissiveColor.Expression = ExpressionEmissive; + + bExpressionCreated = true; + + // Propagate and trigger metallic texture updates. + if (bCreatedNewTextureEmissive) + FAssetRegistryModule::AssetCreated(TextureEmissive); + + TextureEmissive->PreEditChange(nullptr); + TextureEmissive->PostEditChange(); + TextureEmissive->MarkPackageDirty(); + } + + // Cache the texture package + OutPackages.AddUnique(TextureEmissivePackage); + } + } + + HAPI_ParmInfo ParmEmissiveValueInfo; + HAPI_ParmId ParmEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByTag(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL, ParmEmissiveValueInfo); + + if (ParmEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE_OGL); + else + { + ParmEmissiveValueId = + FHoudiniEngineUtils::HapiFindParameterByName(InMaterialInfo.nodeId, HAPI_UNREAL_PARAM_VALUE_EMISSIVE, ParmEmissiveValueInfo); + + if (ParmEmissiveValueId >= 0) + GeneratingParameterName = TEXT(HAPI_UNREAL_PARAM_VALUE_EMISSIVE); + } + + if (!bExpressionCreated && ParmEmissiveValueId >= 0) + { + // Emissive color is available. + + FLinearColor Color = FLinearColor::White; + + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InMaterialInfo.nodeId, (float*)&Color.R, + ParmEmissiveValueInfo.floatValuesIndex, ParmEmissiveValueInfo.size) == HAPI_RESULT_SUCCESS) + { + if (ParmEmissiveValueInfo.size == 3) + Color.A = 1.0f; + + UMaterialExpressionConstant4Vector * ExpressionEmissiveColor = + Cast< UMaterialExpressionConstant4Vector >(Material->EmissiveColor.Expression); + + // Create color const expression and add it to material, if we don't have one. + if (!ExpressionEmissiveColor) + { + // Otherwise new expression is of a different type. + if (Material->EmissiveColor.Expression) + { + Material->EmissiveColor.Expression->ConditionalBeginDestroy(); + Material->EmissiveColor.Expression = nullptr; + } + + ExpressionEmissiveColor = NewObject< UMaterialExpressionConstant4Vector >( + Material, UMaterialExpressionConstant4Vector::StaticClass(), NAME_None, ObjectFlag); + } + + // Record generating parameter. + ExpressionEmissiveColor->Desc = GeneratingParameterName; + if (ExpressionEmissiveColor->CanRenameNode()) + ExpressionEmissiveColor->SetEditableName(*GeneratingParameterName); + + ExpressionEmissiveColor->Constant = Color; + + // Offset node placement. + ExpressionEmissiveColor->MaterialExpressionEditorX = FHoudiniMaterialTranslator::MaterialExpressionNodeX; + ExpressionEmissiveColor->MaterialExpressionEditorY = MaterialNodeY; + MaterialNodeY += FHoudiniMaterialTranslator::MaterialExpressionNodeStepY; + + // Assign expression to material. + Material->Expressions.Add(ExpressionEmissiveColor); + Material->EmissiveColor.Expression = ExpressionEmissiveColor; + + bExpressionCreated = true; + } + } + + return bExpressionCreated; +} + + +bool +FHoudiniMaterialTranslator::UpdateMaterialInstanceParameter( + FHoudiniGenericAttribute MaterialParameter, + UMaterialInstanceConstant* MaterialInstance, + const TArray& InPackages) +{ + bool bParameterUpdated = false; + +#if WITH_EDITOR + if (!MaterialInstance) + return false; + + if (MaterialParameter.AttributeName.IsEmpty()) + return false; + + // The default material instance parameters needs to be handled manually as they cant be changed via generic SetParameters functions + if (MaterialParameter.AttributeName.Compare("CastShadowAsMasked", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideCastShadowAsMasked() && (MaterialInstance->GetCastShadowAsMasked() == Value)) + return false; + + MaterialInstance->SetOverrideCastShadowAsMasked(true); + MaterialInstance->SetCastShadowAsMasked(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("EmissiveBoost", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideEmissiveBoost() && (MaterialInstance->GetEmissiveBoost() == Value)) + return false; + + MaterialInstance->SetOverrideEmissiveBoost(true); + MaterialInstance->SetEmissiveBoost(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("DiffuseBoost", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideDiffuseBoost() && (MaterialInstance->GetDiffuseBoost() == Value)) + return false; + + MaterialInstance->SetOverrideDiffuseBoost(true); + MaterialInstance->SetDiffuseBoost(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("ExportResolutionScale", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->GetOverrideExportResolutionScale() && (MaterialInstance->GetExportResolutionScale() == Value)) + return false; + + MaterialInstance->SetOverrideExportResolutionScale(true); + MaterialInstance->SetExportResolutionScale(Value); + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("OpacityMaskClipValue", ESearchCase::IgnoreCase) == 0) + { + float Value = (float)MaterialParameter.GetDoubleValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue && (MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_OpacityMaskClipValue = true; + MaterialInstance->BasePropertyOverrides.OpacityMaskClipValue = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("BlendMode", ESearchCase::IgnoreCase) == 0) + { + EBlendMode EnumValue = (EBlendMode)MaterialParameter.GetIntValue(); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + FString StringValue = MaterialParameter.GetStringValue(); + if (StringValue.Compare("Opaque", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Opaque; + else if (StringValue.Compare("Masked", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Masked; + else if (StringValue.Compare("Translucent", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Translucent; + else if (StringValue.Compare("Additive", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Additive; + else if (StringValue.Compare("Modulate", ESearchCase::IgnoreCase) == 0) + EnumValue = EBlendMode::BLEND_Modulate; + else if (StringValue.StartsWith("Alpha", ESearchCase::IgnoreCase)) + EnumValue = EBlendMode::BLEND_AlphaComposite; + } + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_BlendMode && (MaterialInstance->BasePropertyOverrides.BlendMode == EnumValue)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_BlendMode = true; + MaterialInstance->BasePropertyOverrides.BlendMode = EnumValue; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("ShadingModel", ESearchCase::IgnoreCase) == 0) + { + EMaterialShadingModel EnumValue = (EMaterialShadingModel)MaterialParameter.GetIntValue(); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + FString StringValue = MaterialParameter.GetStringValue(); + if (StringValue.Compare("Unlit", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Unlit; + else if (StringValue.StartsWith("Default", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_DefaultLit; + else if (StringValue.Compare("Subsurface", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Subsurface; + else if (StringValue.StartsWith("Preintegrated", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_PreintegratedSkin; + else if (StringValue.StartsWith("Clear", ESearchCase::IgnoreCase)) + EnumValue = EMaterialShadingModel::MSM_ClearCoat; + else if (StringValue.Compare("SubsurfaceProfile", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_SubsurfaceProfile; + else if (StringValue.Compare("TwoSidedFoliage", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_TwoSidedFoliage; + else if (StringValue.Compare("Hair", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Hair; + else if (StringValue.Compare("Cloth", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Cloth; + else if (StringValue.Compare("Eye", ESearchCase::IgnoreCase) == 0) + EnumValue = EMaterialShadingModel::MSM_Eye; + } + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel && (MaterialInstance->BasePropertyOverrides.ShadingModel == EnumValue)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_ShadingModel = true; + MaterialInstance->BasePropertyOverrides.ShadingModel = EnumValue; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("TwoSided", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_TwoSided && (MaterialInstance->BasePropertyOverrides.TwoSided == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_TwoSided = true; + MaterialInstance->BasePropertyOverrides.TwoSided = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("DitheredLODTransition", ESearchCase::IgnoreCase) == 0) + { + bool Value = MaterialParameter.GetBoolValue(); + + // Update the parameter value only if necessary + if (MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition && (MaterialInstance->BasePropertyOverrides.DitheredLODTransition == Value)) + return false; + + MaterialInstance->BasePropertyOverrides.bOverride_DitheredLODTransition = true; + MaterialInstance->BasePropertyOverrides.DitheredLODTransition = Value; + bParameterUpdated = true; + } + else if (MaterialParameter.AttributeName.Compare("PhysMaterial", ESearchCase::IgnoreCase) == 0) + { + // Try to load a Material corresponding to the parameter value + FString ParamValue = MaterialParameter.GetStringValue(); + UPhysicalMaterial* FoundPhysMaterial = Cast< UPhysicalMaterial >( + StaticLoadObject(UPhysicalMaterial::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); + + // Update the parameter value if necessary + if (!FoundPhysMaterial || (MaterialInstance->PhysMaterial == FoundPhysMaterial)) + return false; + + MaterialInstance->PhysMaterial = FoundPhysMaterial; + bParameterUpdated = true; + } + + if (bParameterUpdated) + return true; + + // Handling custom parameters + FName CurrentMatParamName = FName(*MaterialParameter.AttributeName); + if (MaterialParameter.AttributeType == EAttribStorageType::STRING) + { + // String attributes are used for textures parameters + // We need to find the texture corresponding to the param + UTexture* FoundTexture = nullptr; + FString ParamValue = MaterialParameter.GetStringValue(); + + // Texture can either be already existing texture assets in UE4, or a newly generated textures by this asset + // Try to find the texture corresponding to the param value in the existing assets first. + FoundTexture = Cast( + StaticLoadObject(UTexture::StaticClass(), nullptr, *ParamValue, nullptr, LOAD_NoWarn, nullptr)); + + if (!FoundTexture) + { + // We couldn't find a texture corresponding to the parameter in the existing UE4 assets + // Try to find the corresponding texture in the cooked temporary package we just generated + FoundTexture = FHoudiniMaterialTranslator::FindGeneratedTexture(ParamValue, InPackages); + } + + // Do not update if unnecessary + if (FoundTexture) + { + // Do not update if unnecessary + UTexture* OldTexture = nullptr; + bool FoundOldParam = MaterialInstance->GetTextureParameterValue(CurrentMatParamName, OldTexture); + if (FoundOldParam && (OldTexture == FoundTexture)) + return false; + + MaterialInstance->SetTextureParameterValueEditorOnly(CurrentMatParamName, FoundTexture); + bParameterUpdated = true; + } + } + else if (MaterialParameter.AttributeTupleSize == 1) + { + // Single attributes are either for scalar parameters or static switches + float OldValue; + bool FoundOldScalarParam = MaterialInstance->GetScalarParameterValue(CurrentMatParamName, OldValue); + if (FoundOldScalarParam) + { + // The material parameter is a scalar + float NewValue = (float)MaterialParameter.GetDoubleValue(); + + // Do not update if unnecessary + if (OldValue == NewValue) + return false; + + MaterialInstance->SetScalarParameterValueEditorOnly(CurrentMatParamName, NewValue); + bParameterUpdated = true; + } + else + { + // See if the underlying parameter is a static switch + bool NewBoolValue = MaterialParameter.GetBoolValue(); + + // We need to iterate over the material's static parameter set + FStaticParameterSet StaticParameters; + MaterialInstance->GetStaticParameterValues(StaticParameters); + + for (int32 SwitchParameterIdx = 0; SwitchParameterIdx < StaticParameters.StaticSwitchParameters.Num(); ++SwitchParameterIdx) + { + FStaticSwitchParameter& SwitchParameter = StaticParameters.StaticSwitchParameters[SwitchParameterIdx]; + if (SwitchParameter.ParameterInfo.Name != CurrentMatParamName) + continue; + + if (SwitchParameter.Value == NewBoolValue) + return false; + + SwitchParameter.Value = NewBoolValue; + SwitchParameter.bOverride = true; + + MaterialInstance->UpdateStaticPermutation(StaticParameters); + bParameterUpdated = true; + break; + } + } + } + else + { + // Tuple attributes are for vector parameters + FLinearColor NewLinearColor; + // if the attribute is stored in an int, we'll have to convert a color to a linear color + if (MaterialParameter.AttributeType == EAttribStorageType::INT || MaterialParameter.AttributeType == EAttribStorageType::INT64) + { + FColor IntColor; + IntColor.R = (int8)MaterialParameter.GetIntValue(0); + IntColor.G = (int8)MaterialParameter.GetIntValue(1); + IntColor.B = (int8)MaterialParameter.GetIntValue(2); + if (MaterialParameter.AttributeTupleSize >= 4) + IntColor.A = (int8)MaterialParameter.GetIntValue(3); + else + IntColor.A = 1; + + NewLinearColor = FLinearColor(IntColor); + } + else + { + NewLinearColor.R = (float)MaterialParameter.GetDoubleValue(0); + NewLinearColor.G = (float)MaterialParameter.GetDoubleValue(1); + NewLinearColor.B = (float)MaterialParameter.GetDoubleValue(2); + if (MaterialParameter.AttributeTupleSize >= 4) + NewLinearColor.A = (float)MaterialParameter.GetDoubleValue(3); + } + + // Do not update if unnecessary + FLinearColor OldValue; + bool FoundOldParam = MaterialInstance->GetVectorParameterValue(CurrentMatParamName, OldValue); + if (FoundOldParam && (OldValue == NewLinearColor)) + return false; + + MaterialInstance->SetVectorParameterValueEditorOnly(CurrentMatParamName, NewLinearColor); + bParameterUpdated = true; + } +#endif + + return bParameterUpdated; +} + + +UTexture* +FHoudiniMaterialTranslator::FindGeneratedTexture(const FString& TextureString, const TArray& InPackages) +{ + if (TextureString.IsEmpty()) + return nullptr; + + // Try to find the corresponding texture in the cooked temporary package generated by an HDA +UTexture* FoundTexture = nullptr; +for (const auto& CurrentPackage : InPackages) +{ + // Iterate through the cooked packages + if (!CurrentPackage || CurrentPackage->IsPendingKill()) + continue; + + // First, check if the package contains a texture + FString CurrentPackageName = CurrentPackage->GetName(); + UTexture* PackageTexture = LoadObject(CurrentPackage, *CurrentPackageName, nullptr, LOAD_None, nullptr); + if (!PackageTexture) + continue; + + // Then check if the package's metadata match what we're looking for + // Make sure this texture was generated by Houdini Engine + UMetaData* MetaData = CurrentPackage->GetMetaData(); + if (!MetaData || !MetaData->HasValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + continue; + + // Get the texture type from the meta data + // Texture type store has meta data will be C_A, N, S, R etc.. + const FString TextureTypeString = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); + if (TextureTypeString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // Convert the texture type to a "friendly" version + // C_A to diffuse, N to Normal, S to Specular etc... + FString TextureTypeFriendlyString = TextureTypeString; + FString TextureTypeFriendlyAlternateString = TEXT(""); + if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_DIFFUSE, ESearchCase::IgnoreCase) == 0) + { + TextureTypeFriendlyString = TEXT("diffuse"); + TextureTypeFriendlyAlternateString = TEXT("basecolor"); + } + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_NORMAL, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("normal"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_EMISSIVE, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("emissive"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_SPECULAR, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("specular"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_ROUGHNESS, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("roughness"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_METALLIC, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("metallic"); + else if (TextureTypeString.Compare(HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_OPACITY_MASK, ESearchCase::IgnoreCase) == 0) + TextureTypeFriendlyString = TEXT("opacity"); + + // See if we have a match between the texture string and the friendly name + if ((TextureTypeFriendlyString.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + || (!TextureTypeFriendlyAlternateString.IsEmpty() && TextureTypeFriendlyAlternateString.Compare(TextureString, ESearchCase::IgnoreCase) == 0)) + { + FoundTexture = PackageTexture; + break; + } + + // Get the node path from the meta data + const FString NodePath = MetaData->GetValue(PackageTexture, HAPI_UNREAL_PACKAGE_META_NODE_PATH); + if (NodePath.IsEmpty()) + continue; + + // See if we have a match with the path and texture type + FString PathAndType = NodePath + TEXT("/") + TextureTypeString; + if (PathAndType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // See if we have a match with the friendly path and texture type + FString PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyString; + if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + + // Try the alternate friendly string + if (!TextureTypeFriendlyAlternateString.IsEmpty()) + { + PathAndFriendlyType = NodePath + TEXT("/") + TextureTypeFriendlyAlternateString; + if (PathAndFriendlyType.Compare(TextureString, ESearchCase::IgnoreCase) == 0) + { + FoundTexture = PackageTexture; + break; + } + } +} + +return FoundTexture; +} + + +bool +FHoudiniMaterialTranslator::FindTextureParamByNameOrTag( + const HAPI_NodeId& InNodeId, + const std::string& InTextureParmName, + const std::string& InUseTextureParmName, + const bool& bFindByTag, + HAPI_ParmId& OutParmId, + HAPI_ParmInfo& OutParmInfo) +{ + OutParmId = -1; + + if(bFindByTag) + OutParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InTextureParmName, OutParmInfo); + else + OutParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InTextureParmName, OutParmInfo); + + if (OutParmId < 0) + { + // Failed to find the texture + return false; + } + + // We found a valid parameter, check if the matching "use" parameter exists + HAPI_ParmInfo FoundUseParmInfo; + HAPI_ParmId FoundUseParmId = -1; + if(bFindByTag) + FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByTag(InNodeId, InUseTextureParmName, FoundUseParmInfo); + else + FoundUseParmId = FHoudiniEngineUtils::HapiFindParameterByName(InNodeId, InUseTextureParmName, FoundUseParmInfo); + + if (FoundUseParmId >= 0) + { + // We found a valid "use" parameter, check if it is disabled + // Get the param value + int32 UseValue = 0; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &UseValue, FoundUseParmInfo.intValuesIndex, 1)) + { + if (UseValue == 0) + { + // We found the texture parm, but the "use" param/tag is disabled, so don't use it! + // We still return true as we found the parameter, this will prevent looking for other parms + OutParmId = -1; + return true; + } + } + } + + // Finally, make sure that the found texture Parm is not empty! + FString ParmValue = FString(); + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandle, OutParmInfo.stringValuesIndex, 1)) + { + // Convert the string handle to FString + FHoudiniEngineString::ToFString(StringHandle, ParmValue); + } + + if (ParmValue.IsEmpty()) + { + // We found the parm, but it's empty, don't use it! + // We still return true as we found the parameter, this will prevent looking for other parms + OutParmId = -1; + return true; + } + + return true; +} + diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h index 485bb1ad6..a8127af06 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.h @@ -1,231 +1,242 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniGeoPartObject.h" -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "Engine/TextureDefines.h" - -#include - -class UMaterial; -class UMaterialInterface; -class UMaterialExpression; -class UMaterialInstanceConstant; -class UTexture2D; -class UTexture; -class UPackage; - -struct FHoudiniPackageParams; -struct FCreateTexture2DParameters; -struct FHoudiniGenericAttribute; - -// Forward declared enums do not work with 4.24 builds on Linux with the Clang 8.0.1 toolchain: ISO C++ forbids forward references to 'enum' types -// enum TextureGroup; - -struct HOUDINIENGINE_API FHoudiniMaterialTranslator -{ -public: - - // - static bool CreateHoudiniMaterials( - const HAPI_NodeId& InNodeId, - const FHoudiniPackageParams& InPackageParams, - const TArray& InUniqueMaterialIds, - const TArray& InUniqueMaterialInfos, - const TMap& InMaterials, - const TMap& InAllOutputMaterials, - TMap& OutMaterials, - TArray& OutPackages, - const bool& bForceRecookAll, - bool bInTreatExistingMaterialsAsUpToDate=false); - - // - static bool CreateMaterialInstances( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& UniqueMaterialInstanceOverrides, - const TArray& InPackages, - const TMap& InMaterials, - TMap& OutMaterials, - const bool& bForceRecookAll); - - // - static bool UpdateMaterialInstanceParameter( - FHoudiniGenericAttribute MaterialParameter, - UMaterialInstanceConstant* MaterialInstance, - const TArray& InPackages); - - static UTexture* FindGeneratedTexture( - const FString& TextureString, - const TArray& InPackages); - - // - static UPackage* CreatePackageForTexture( - const HAPI_NodeId& InMaterialNodeId, - const FString& InTextureType, - const FHoudiniPackageParams& InPackageParams, - FString& OutTextureName); - - // - static UPackage* CreatePackageForMaterial( - const HAPI_NodeId& InMaterialNodeId, - const FString& InMaterialName, - const FHoudiniPackageParams& InPackageParams, - FString& OutMaterialName); - - - // Create a texture from given information. - static UTexture2D* CreateUnrealTexture( - UTexture2D* ExistingTexture, - const HAPI_ImageInfo& ImageInfo, - UPackage* Package, - const FString& TextureName, - const TArray& ImageBuffer, - const FCreateTexture2DParameters& TextureParameters, - const TextureGroup& LODGroup, - const FString& TextureType, - const FString& NodePath); - - // HAPI : Retrieve a list of image planes. - static bool HapiExtractImage( - const HAPI_ParmId& NodeParmId, - const HAPI_MaterialInfo& MaterialInfo, - const char * PlaneType, - const HAPI_ImageDataFormat& ImageDataFormat, - HAPI_ImagePacking ImagePacking, - bool bRenderToImage, - TArray& OutImageBuffer); - - // HAPI : Extract image data. - static bool HapiGetImagePlanes( - const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes); - - // Returns a unique name for a given material, its relative path (to the asset) - static bool GetMaterialRelativePath( - const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialNodeInfo, FString& OutRelativePath); - static bool GetMaterialRelativePath( - const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath); - - // Returns true if a texture parameter was found - // Ensures that the texture is not disabled via the "UseTexture" Parm name/tag - static bool FindTextureParamByNameOrTag( - const HAPI_NodeId& InNodeId, - const std::string& InTextureParmName, - const std::string& InUseTextureParmName, - const bool& bFindByTag, - HAPI_ParmId& OutParmId, - HAPI_ParmInfo& OutParmInfo); - -protected: - - // Helper function to locate first Material expression of given class within given expression subgraph. - static UMaterialExpression * MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass); - - // Create various material components. - static bool CreateMaterialComponentDiffuse( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentNormal( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentSpecular( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentRoughness( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentMetallic( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentEmissive( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentOpacity( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - - static bool CreateMaterialComponentOpacityMask( - const HAPI_NodeId& InAssetId, - const FString& InMaterialName, - const HAPI_MaterialInfo& InMaterialInfo, - const FHoudiniPackageParams& InPackageParams, - UMaterial* Material, - TArray& OutPackages, - int32& MaterialNodeY); - -public: - - // Material node construction offsets. - static const int32 MaterialExpressionNodeX; - static const int32 MaterialExpressionNodeY; - static const int32 MaterialExpressionNodeStepX; - static const int32 MaterialExpressionNodeStepY; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniGeoPartObject.h" +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Engine/TextureDefines.h" + +#include + +class UMaterial; +class UMaterialInterface; +class UMaterialExpression; +class UMaterialInstanceConstant; +class UTexture2D; +class UTexture; +class UPackage; + +struct FHoudiniPackageParams; +struct FCreateTexture2DParameters; +struct FHoudiniGenericAttribute; + +// Forward declared enums do not work with 4.24 builds on Linux with the Clang 8.0.1 toolchain: ISO C++ forbids forward references to 'enum' types +// enum TextureGroup; + +struct HOUDINIENGINE_API FHoudiniMaterialTranslator +{ +public: + + // + static bool CreateHoudiniMaterials( + const HAPI_NodeId& InNodeId, + const FHoudiniPackageParams& InPackageParams, + const TArray& InUniqueMaterialIds, + const TArray& InUniqueMaterialInfos, + const TMap& InMaterials, + const TMap& InAllOutputMaterials, + TMap& OutMaterials, + TArray& OutPackages, + const bool& bForceRecookAll, + bool bInTreatExistingMaterialsAsUpToDate=false); + + // + static bool CreateMaterialInstances( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& UniqueMaterialInstanceOverrides, + const TArray& InPackages, + const TMap& InMaterials, + TMap& OutMaterials, + const bool& bForceRecookAll); + + + // Helper for CreateMaterialInstances so you don't have to sort unique face materials overrides + static bool SortUniqueFaceMaterialOverridesAndCreateMaterialInstances( + const TArray& Materials, + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TArray& InPackages, + const TMap& InMaterials, + TMap& OutMaterials, + const bool& bForceRecookAll); + + // + static bool UpdateMaterialInstanceParameter( + FHoudiniGenericAttribute MaterialParameter, + UMaterialInstanceConstant* MaterialInstance, + const TArray& InPackages); + + static UTexture* FindGeneratedTexture( + const FString& TextureString, + const TArray& InPackages); + + // + static UPackage* CreatePackageForTexture( + const HAPI_NodeId& InMaterialNodeId, + const FString& InTextureType, + const FHoudiniPackageParams& InPackageParams, + FString& OutTextureName); + + // + static UPackage* CreatePackageForMaterial( + const HAPI_NodeId& InMaterialNodeId, + const FString& InMaterialName, + const FHoudiniPackageParams& InPackageParams, + FString& OutMaterialName); + + + // Create a texture from given information. + static UTexture2D* CreateUnrealTexture( + UTexture2D* ExistingTexture, + const HAPI_ImageInfo& ImageInfo, + UPackage* Package, + const FString& TextureName, + const TArray& ImageBuffer, + const FCreateTexture2DParameters& TextureParameters, + const TextureGroup& LODGroup, + const FString& TextureType, + const FString& NodePath); + + // HAPI : Retrieve a list of image planes. + static bool HapiExtractImage( + const HAPI_ParmId& NodeParmId, + const HAPI_MaterialInfo& MaterialInfo, + const char * PlaneType, + const HAPI_ImageDataFormat& ImageDataFormat, + HAPI_ImagePacking ImagePacking, + bool bRenderToImage, + TArray& OutImageBuffer); + + // HAPI : Extract image data. + static bool HapiGetImagePlanes( + const HAPI_ParmId& NodeParmId, const HAPI_MaterialInfo& MaterialInfo, TArray& OutImagePlanes); + + // Returns a unique name for a given material, its relative path (to the asset) + static bool GetMaterialRelativePath( + const HAPI_NodeId& InAssetId, const HAPI_MaterialInfo& InMaterialNodeInfo, FString& OutRelativePath); + static bool GetMaterialRelativePath( + const HAPI_NodeId& InAssetId, const HAPI_NodeId& InMaterialNodeId, FString& OutRelativePath); + + // Returns true if a texture parameter was found + // Ensures that the texture is not disabled via the "UseTexture" Parm name/tag + static bool FindTextureParamByNameOrTag( + const HAPI_NodeId& InNodeId, + const std::string& InTextureParmName, + const std::string& InUseTextureParmName, + const bool& bFindByTag, + HAPI_ParmId& OutParmId, + HAPI_ParmInfo& OutParmInfo); + +protected: + + // Helper function to locate first Material expression of given class within given expression subgraph. + static UMaterialExpression * MaterialLocateExpression(UMaterialExpression* Expression, UClass* ExpressionClass); + + // Create various material components. + static bool CreateMaterialComponentDiffuse( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentNormal( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentSpecular( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentRoughness( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentMetallic( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentEmissive( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentOpacity( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + + static bool CreateMaterialComponentOpacityMask( + const HAPI_NodeId& InAssetId, + const FString& InMaterialName, + const HAPI_MaterialInfo& InMaterialInfo, + const FHoudiniPackageParams& InPackageParams, + UMaterial* Material, + TArray& OutPackages, + int32& MaterialNodeY); + +public: + + // Material node construction offsets. + static const int32 MaterialExpressionNodeX; + static const int32 MaterialExpressionNodeY; + static const int32 MaterialExpressionNodeStepX; + static const int32 MaterialExpressionNodeStepY; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp index 128836c91..4331f5827 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp @@ -1,6785 +1,6778 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniMeshTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniOutput.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniMaterialTranslator.h" -#include "HoudiniAssetActor.h" - -#include "HoudiniStaticMesh.h" -#include "HoudiniStaticMeshComponent.h" -#include "Engine/StaticMeshSocket.h" - -#include "PhysicsEngine/BodySetup.h" -#include "Engine/StaticMesh.h" -#include "PackageTools.h" -#include "RawMesh.h" -#include "Materials/MaterialInterface.h" -#include "Materials/Material.h" -#include "MeshDescription.h" -#include "StaticMeshAttributes.h" -#include "MeshDescriptionOperations.h" - -#include "BSPOps.h" -#include "Model.h" -#include "Engine/Polys.h" -#include "AssetRegistryModule.h" -#include "Interfaces/ITargetPlatform.h" -#include "Interfaces/ITargetPlatformManagerModule.h" -#include "AI/Navigation/NavCollisionBase.h" -#include "ObjectTools.h" - -// #include "Async/ParallelFor.h" - -#include "ProfilingDebugging/CpuProfilerTrace.h" - -#include "EditorSupportDelegates.h" - -#if WITH_EDITOR - #include "UnrealEd/Private/ConvexDecompTool.h" - #include "Editor/UnrealEd/Private/GeomFitUtils.h" - #include "LevelEditorViewport.h" - #include "FileHelpers.h" -#endif - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -static TAutoConsoleVariable CVarHoudiniEngineMeshBuildTimer( - TEXT("HoudiniEngine.MeshBuildTimer"), - 0.0, - TEXT("When enabled, the plugin will output timings during the Mesh creation.\n") -); - -// -bool -FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - UHoudiniOutput* InOutput, - const FHoudiniPackageParams& InPackageParams, - const EHoudiniStaticMeshMethod& InStaticMeshMethod, - const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, - const FMeshBuildSettings& InMeshBuildSettings, - const TMap& InAllOutputMaterials, - UObject* InOuterComponent, - bool bInTreatExistingMaterialsAsUpToDate, - bool bInDestroyProxies) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InPackageParams.OuterPackage || InPackageParams.OuterPackage->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - TMap NewOutputObjects; - TMap OldOutputObjects = InOutput->GetOutputObjects(); - TMap& AssignementMaterials = InOutput->GetAssignementMaterials(); - TMap& ReplacementMaterials = InOutput->GetReplacementMaterials(); - - bool InForceRebuild = false; - if (InOutput->HasAnyCurrentProxy() && InStaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh) - { - // Make sure we're not preventing refinement - InForceRebuild = true; - } - - // Iterate on all of the output's HGPO, creating meshes as we go - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) - { - // Not a mesh, skip - if (CurHGPO.Type != EHoudiniPartType::Mesh) - continue; - - // See if we have some uproperty attributes to update on - // the outer component (in most case, the HAC) - TArray PropertyAttributes; - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - CurHGPO.GeoId, CurHGPO.PartId, - true, 0, 0, 0, - PropertyAttributes)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( - InOuterComponent, PropertyAttributes); - } - - CreateStaticMeshFromHoudiniGeoPartObject( - CurHGPO, - InPackageParams, - OldOutputObjects, - NewOutputObjects, - AssignementMaterials, - ReplacementMaterials, - InAllOutputMaterials, - InForceRebuild, - InStaticMeshMethod, - InSMGenerationProperties, - InMeshBuildSettings, - bInTreatExistingMaterialsAsUpToDate); - } - - return FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - InOutput, - InOuterComponent, - NewOutputObjects, - bInDestroyProxies); -} - -bool -FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - UHoudiniOutput* InOutput, - UObject* InOuterComponent, - TMap& InNewOutputObjects, - bool bInDestroyProxies, - bool bInApplyGenericProperties) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - TMap OldOutputObjects = InOutput->GetOutputObjects(); - - // Remove Static Meshes and their components from the old map - // to avoid their deletion if new proxies were created for them - for (auto& NewOutputObj : InNewOutputObjects) - { - FHoudiniOutputObjectIdentifier OutputIdentifier = NewOutputObj.Key; - - // See if we already had that pair in the old map of static mesh - FHoudiniOutputObject* FoundOldOutputObj = OldOutputObjects.Find(NewOutputObj.Key); - if (!FoundOldOutputObj) - continue; - - UObject* NewStaticMesh = NewOutputObj.Value.OutputObject; - UObject* NewProxyMesh = NewOutputObj.Value.ProxyObject; - - UObject* OldStaticMesh = FoundOldOutputObj->OutputObject; - if (OldStaticMesh && !OldStaticMesh->IsPendingKill()) - { - // If a proxy was created for an existing static mesh, keep the existing static - // mesh (will be hidden) - if (NewProxyMesh && NewOutputObj.Value.bProxyIsCurrent) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - else if (NewStaticMesh && NewStaticMesh == OldStaticMesh) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - - UObject* OldProxyMesh = FoundOldOutputObj->ProxyObject; - if (OldProxyMesh && !OldProxyMesh->IsPendingKill()) - { - // If a new static mesh was created for a proxy, keep the proxy (will be hidden) - // ... unless we want to explicitly destroy proxies - if (NewStaticMesh && !bInDestroyProxies) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - else if (NewProxyMesh && (NewProxyMesh == OldProxyMesh)) - { - // Remove it from the old map to avoid its destruction - OldOutputObjects.Remove(OutputIdentifier); - } - } - } - - // The old map now only contains unused/stale Meshes/Components, delete them - for (auto& OldPair : OldOutputObjects) - { - // Get the old Identifier / StaticMesh - FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; - FHoudiniOutputObject& OldOutputObject = OldPair.Value; - - // Remove the old component from the map - RemoveAndDestroyComponent(OldOutputObject.OutputComponent); - OldOutputObject.OutputComponent = nullptr; - // Remove the old proxy component from the map - RemoveAndDestroyComponent(OldOutputObject.ProxyComponent); - OldOutputObject.ProxyComponent = nullptr; - - if (OldOutputObject.OutputObject && !OldOutputObject.OutputObject->IsPendingKill()) - { - OldOutputObject.OutputObject->MarkPendingKill(); - } - - if (OldOutputObject.ProxyObject && !OldOutputObject.ProxyObject->IsPendingKill()) - { - OldOutputObject.ProxyObject->MarkPendingKill(); - } - } - OldOutputObjects.Empty(); - - /* - // Remove any stale components, these are components with OutputIdentifiers that are not - // in NewOutputObjects. This seems to happen mostly with the first or second cook after a - // "Rebuild Asset" - if (OutputComponents.Num() > 0 || OutputProxyComponents.Num() > 0) - { - TArray> StaleComponents; - const uint32 MaxNumStale = FMath::Max(OutputComponents.Num(), OutputProxyComponents.Num()); - StaleComponents.Reserve(MaxNumStale); - for (auto& ComponentPair : OutputComponents) - { - if (!NewOutputObjects.Contains(ComponentPair.Key) && !OldOutputObjectsReplacedByProxy.Contains(ComponentPair.Key)) - { - StaleComponents.Add(ComponentPair); - } - } - for (auto& ComponentPair : StaleComponents) - { - RemoveAndDestroyComponent(ComponentPair.Key, OutputComponents); - } - StaleComponents.Empty(MaxNumStale); - - for (auto& ComponentPair : OutputProxyComponents) - { - if (!NewOutputProxyObjects.Contains(ComponentPair.Key) && !OldOutputProxyObjectsReplacedByStaticMesh.Contains(ComponentPair.Key)) - { - StaleComponents.Add(ComponentPair); - } - } - for (auto& ComponentPair : StaleComponents) - { - RemoveAndDestroyComponent(ComponentPair.Key, OutputProxyComponents); - } - StaleComponents.Empty(); - } - */ - - // Now create/update the new static mesh components - for (auto& NewPair : InNewOutputObjects) - { - // Get the old Identifier / StaticMesh - const FHoudiniOutputObjectIdentifier& OutputIdentifier = NewPair.Key; - FHoudiniOutputObject& OutputObject = NewPair.Value; - - if (OutputObject.bIsImplicit) - { - // This output is implicit and shouldn't have a representative component/proxy in the scene - // Remove the old component from the map - if (OutputObject.OutputComponent) - { - RemoveAndDestroyComponent(OutputObject.OutputComponent); - OutputObject.OutputComponent = nullptr; - } - - // Remove the old proxy component from the map - RemoveAndDestroyComponent(OutputObject.ProxyComponent); - OutputObject.ProxyComponent = nullptr; - - continue; // Skip any proxy / component creation below - } - - // Check if we should create a Proxy/SMC - if (OutputObject.bProxyIsCurrent) - { - UObject *Mesh = OutputObject.ProxyObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) - { - HOUDINI_LOG_ERROR(TEXT("Proxy Mesh is invalid (wrong type or pending kill)...")); - continue; - } - - // Create or update a new proxy component - TSubclassOf ComponentType = UHoudiniStaticMeshComponent::StaticClass(); - const FHoudiniGeoPartObject *FoundHGPO = nullptr; - bool bCreated = false; - UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); - if (MeshComponent) - { - UHoudiniStaticMeshComponent *HSMC = Cast(MeshComponent); - - if (bCreated) - { - PostCreateHoudiniStaticMeshComponent(HSMC, Mesh); - } - else if (HSMC && !HSMC->IsPendingKill() && HSMC->GetMesh() != Mesh) - { - // We need to reassign the HSM to the component - UHoudiniStaticMesh* HSM = Cast(Mesh); - HSMC->SetMesh(HSM); - } - - UpdateMeshComponent( - MeshComponent, - OutputIdentifier, - FoundHGPO, - InOutput->HoudiniCreatedSocketActors, - InOutput->HoudiniAttachedSocketActors, - bInApplyGenericProperties); - - if (!bCreated) - { - // For proxy meshes: notify that the mesh has been updated - HSMC->NotifyMeshUpdated(); - HSMC->SetHoudiniIconVisible(true); - } - } - - // Now, ensure that meshes replaced by proxies are still kept but hidden - USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent) - { - SceneComponent->SetVisibility(false); - SceneComponent->SetHiddenInGame(true); - } - - // If the proxy mesh we just created is templated, hide it in game - if (FoundHGPO->bIsTemplated) - { - MeshComponent->SetHiddenInGame(true); - } - } - else - { - // Create a new SMC if needed - UObject* Mesh = OutputObject.OutputObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) - { - HOUDINI_LOG_ERROR(TEXT("Mesh is invalid (wrong type or pending kill)...")); - continue; - } - - TSubclassOf ComponentType = UStaticMeshComponent::StaticClass(); - const FHoudiniGeoPartObject *FoundHGPO = nullptr; - bool bCreated = false; - UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); - if (MeshComponent) - { - if (bCreated) - { - PostCreateStaticMeshComponent(Cast(MeshComponent), Mesh); - } - UpdateMeshComponent( - MeshComponent, - OutputIdentifier, - FoundHGPO, - InOutput->HoudiniCreatedSocketActors, - InOutput->HoudiniAttachedSocketActors, - bInApplyGenericProperties); - } - - // Now, ensure that proxies replaced by meshes are still kept but hidden - UHoudiniStaticMeshComponent *HSMC = Cast(OutputObject.ProxyComponent); - if (HSMC) - { - HSMC->SetVisibility(false); - HSMC->SetHiddenInGame(true); - HSMC->SetHoudiniIconVisible(false); - } - - // If the mesh we just created is templated, hide it in game - if (FoundHGPO->bIsTemplated) - { - MeshComponent->SetHiddenInGame(true); - } - } - } - - // Assign the new output objects to the output - InOutput->SetOutputObjects(InNewOutputObjects); - - return true; -} - -void -FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, - const FHoudiniGeoPartObject *InHGPO, TArray &HoudiniCreatedSocketActors, TArray &HoudiniAttachedSocketActors, - bool bInApplyGenericProperties) -{ - // Update collision/visibility - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(InOutputIdentifier.SplitIdentifier); - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider) - { - // Invisible complex collider should not be seen - InMeshComponent->SetVisibility(false); - InMeshComponent->SetHiddenInGame(true); - InMeshComponent->SetCollisionProfileName(FName(TEXT("InvisibleWall"))); - InMeshComponent->SetCastShadow(false); - } - else - { - // Update visiblity - bool bVisible = InHGPO ? InHGPO->bIsVisible : true; - InMeshComponent->SetVisibility(bVisible); - InMeshComponent->SetHiddenInGame(!bVisible); - } - - // TODO: - // Update navmesh? - - // Transform the component by transformation provided by HAPI. - InMeshComponent->SetRelativeTransform(InHGPO ? InHGPO->TransformMatrix : FTransform::Identity); - - // If the static mesh had sockets, we can assign the desired actor to them now - UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); - UStaticMesh * StaticMesh = nullptr; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - StaticMesh = StaticMeshComponent->GetStaticMesh(); - - if (StaticMesh && !StaticMesh->IsPendingKill()) - { - 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; - - AddActorsToMeshSocket(StaticMesh->Sockets[nSocket], StaticMeshComponent, HoudiniCreatedSocketActors, HoudiniAttachedSocketActors); - } - - // Iterate all remaining created socket actors, destroy the ones that are not assigned to socket after re-cook - { - for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - continue; - } - - bool bFoundSocket = false; - for (auto & CurSocket : StaticMesh->Sockets) - { - if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) - { - bFoundSocket = true; - break; - } - } - // cur actor's attaching socket is found, skip - if (bFoundSocket) - continue; - - // Destroy the previous created socket actor if not found - HoudiniCreatedSocketActors.RemoveAt(Idx); - CurActor->Destroy(); - } - } - - // Detach the in level actors which is not attached to any socket now - { - for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor* CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniAttachedSocketActors.RemoveAt(Idx); - continue; - } - - bool bFoundSocket = false; - for (auto & CurSocket : StaticMesh->Sockets) - { - if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) - { - bFoundSocket = true; - break; - } - } - - if (bFoundSocket) - continue; - - // If the attached socket name is not found in current socket, detach it and remove from the array - CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - HoudiniAttachedSocketActors.RemoveAt(Idx); - } - } - - } - - if (bInApplyGenericProperties) - { - // Clear the component tags as generic properties only add them - InMeshComponent->ComponentTags.Empty(); - // Update the property attributes on the component - TArray PropertyAttributes; - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - InOutputIdentifier.GeoId, InOutputIdentifier.PartId, - true, - InOutputIdentifier.PrimitiveIndex, - INDEX_NONE, - InOutputIdentifier.PointIndex, - PropertyAttributes)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); - } - } -} - -bool -FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& InOutputObjects, - TMap& OutOutputObjects, - TMap& AssignmentMaterialMap, - TMap& ReplacementMaterialMap, - const TMap& InAllOutputMaterials, - const bool& InForceRebuild, - const EHoudiniStaticMeshMethod& InStaticMeshMethod, - const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, - const FMeshBuildSettings& InSMBuildSettings, - bool bInTreatExistingMaterialsAsUpToDate) -{ - // If we're not forcing the rebuild - // No need to recreate something that hasn't changed - if (!InForceRebuild && !InHGPO.bHasGeoChanged && !InHGPO.bHasPartChanged && InOutputObjects.Num() > 0) - { - // Simply reuse the existing meshes - OutOutputObjects = InOutputObjects; - return true; - } - - FHoudiniMeshTranslator CurrentTranslator; - CurrentTranslator.ForceRebuild = InForceRebuild; - CurrentTranslator.SetHoudiniGeoPartObject(InHGPO); - CurrentTranslator.SetInputObjects(InOutputObjects); - CurrentTranslator.SetOutputObjects(OutOutputObjects); - CurrentTranslator.SetInputAssignmentMaterials(AssignmentMaterialMap); - CurrentTranslator.SetAllOutputMaterials(InAllOutputMaterials); - CurrentTranslator.SetReplacementMaterials(ReplacementMaterialMap); - CurrentTranslator.SetPackageParams(InPackageParams, true); - CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); - CurrentTranslator.SetStaticMeshGenerationProperties(InSMGenerationProperties); - CurrentTranslator.SetStaticMeshBuildSettings(InSMBuildSettings); - - // TODO: Fetch from settings/HAC - CurrentTranslator.DefaultMeshSmoothing = 1; - if (false) - CurrentTranslator.DefaultMeshSmoothing = 0; - - // TODO: mechanism to determine when to use dynamic mesh for fast updates, and when to switch to - // baking the full static mesh - switch (InStaticMeshMethod) - { - case EHoudiniStaticMeshMethod::RawMesh: - CurrentTranslator.CreateStaticMesh_RawMesh(); - break; - case EHoudiniStaticMeshMethod::FMeshDescription: - CurrentTranslator.CreateStaticMesh_MeshDescription(); - break; - case EHoudiniStaticMeshMethod::UHoudiniStaticMesh: - CurrentTranslator.CreateHoudiniStaticMesh(); - break; - } - - // Copy the output objects/materials - OutOutputObjects = CurrentTranslator.OutputObjects; - AssignmentMaterialMap = CurrentTranslator.OutputAssignmentMaterials; - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartVertexList() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartVertexList")); - - if (HGPO.PartInfo.VertexCount <= 0) - return false; - - // Get the vertex List - PartVertexList.SetNumUninitialized(HGPO.PartInfo.VertexCount); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVertexList( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, &PartVertexList[0], 0, HGPO.PartInfo.VertexCount)) - { - // Error getting the vertex list. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - - return false; - } - - return true; -} - -void -FHoudiniMeshTranslator::SortSplitGroups() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::SortSplitGroups")); - - // Sort the splits in the order that we want to process them: - // Simple/Convex invisible colliders should be treated first as they will need to be attached to the visible meshes - TArray First; - - // The main geo and its LODs should be created after. - TArray Main; - TArray LODs; - - // Finally, visible colliders and invisible complex colliders as they need their own static mesh - TArray Last; - - for (auto& curSplit : HGPO.SplitGroups) - { - EHoudiniSplitType curSplitType = GetSplitTypeFromSplitName(curSplit); - switch (curSplitType) - { - case EHoudiniSplitType::InvisibleSimpleCollider: - case EHoudiniSplitType::InvisibleUCXCollider: - First.Add(curSplit); - break; - - case EHoudiniSplitType::Normal: - Main.Add(curSplit); - break; - - case EHoudiniSplitType::LOD: - LODs.Add(curSplit); - break; - - case EHoudiniSplitType::RenderedSimpleCollider: - case EHoudiniSplitType::RenderedUCXCollider: - case EHoudiniSplitType::RenderedComplexCollider: - case EHoudiniSplitType::InvisibleComplexCollider: - Last.Add(curSplit); - break; - } - } - - // Make sure LODs are order by name - LODs.Sort(); - - // Copy the split names in order - AllSplitGroups.Empty(); - for (auto& splitName : First) - AllSplitGroups.Add(splitName); - - for (auto& splitName : Main) - AllSplitGroups.Add(splitName); - - for (auto& splitName : LODs) - AllSplitGroups.Add(splitName); - - for (auto& splitName : Last) - AllSplitGroups.Add(splitName); -} - -bool -FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices")); - - // Reset the splits faces/indices arrays - AllSplitVertexLists.Empty(); - AllSplitVertexCounts.Empty(); - AllSplitFaceIndices.Empty(); - AllSplitFirstValidVertexIndex.Empty(); - AllSplitFirstValidPrimIndex.Empty(); - - bool bHasSplit = AllSplitGroups.Num() > 0; - if (bHasSplit) - { - HAPI_PartInfo PartInfo = FHoudiniEngineUtils::ToHAPIPartInfo(HGPO.PartInfo); - - // Buffer for all vertex indices used for split groups. - // We need this to figure out all vertex indices that are not part of them. - TArray AllVertexList; - AllVertexList.SetNumZeroed(PartVertexList.Num()); - - // Buffer for all face indices used for split groups. - // We need this to figure out all face indices that are not part of them. - TArray AllGroupFaceIndices; - AllGroupFaceIndices.SetNumZeroed(HGPO.PartInfo.FaceCount); - - // Some of the groups may contain invalid geometry - // Store them here so we can remove them afterwards - TArray InvalidGroupNameIndices; - - // Extract the vertices/faces for each of the split groups - for (int32 SplitIdx = 0; SplitIdx < AllSplitGroups.Num(); SplitIdx++) - { - const FString& GroupName = AllSplitGroups[SplitIdx]; - - // New vertex list just for this group. - TArray< int32 > GroupVertexList; - TArray< int32 > AllFaceList; - - int32 FirstValidPrimIndex = 0; - int32 FirstValidVertexIndex = 0; - // Extract vertex indices for this split. - int32 GroupVertexListCount = FHoudiniEngineUtils::HapiGetVertexListForGroup( - HGPO.GeoId, PartInfo, GroupName, - PartVertexList, GroupVertexList, - AllVertexList, AllFaceList, AllGroupFaceIndices, - FirstValidVertexIndex, FirstValidPrimIndex, - HGPO.PartInfo.bIsInstanced); - - if (GroupVertexListCount <= 0) - { - // This group doesn't have vertices/faces, mark it as invalid - InvalidGroupNameIndices.Add(SplitIdx); - - // Error getting the vertex list. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list for group %s - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, *GroupName); - - continue; - } - - // If list is not empty, we store it for this group - this will define new mesh. - AllSplitVertexLists.Add(GroupName, GroupVertexList); - AllSplitVertexCounts.Add(GroupName, GroupVertexListCount); - AllSplitFaceIndices.Add(GroupName, AllFaceList); - AllSplitFirstValidVertexIndex.Add(GroupName, FirstValidVertexIndex); - AllSplitFirstValidPrimIndex.Add(GroupName, FirstValidPrimIndex); - } - - if (InvalidGroupNameIndices.Num() > 0) - { - // Remove all invalid split groups - for (int32 InvalIdx = InvalidGroupNameIndices.Num() - 1; InvalIdx >= 0; InvalIdx--) - { - int32 Index = InvalidGroupNameIndices[InvalIdx]; - AllSplitGroups.RemoveAt(Index); - } - } - - // We also need to figure out / construct the vertex list for everything that's not in a split group - TArray GroupSplitFacesRemaining; - GroupSplitFacesRemaining.Init(-1, PartVertexList.Num()); - - int32 GroupVertexListCount = 0; - bool bHasMainSplitGroup = false; - TArray< int32 > GroupSplitFaceIndicesRemaining; - int32 FistUnusedVertexIndex = -1; - for (int32 SplitVertexIdx = 0; SplitVertexIdx < AllVertexList.Num(); SplitVertexIdx++) - { - if (AllVertexList[SplitVertexIdx] == 0) - { - // This is an unused index, we need to add it to unused vertex list. - FistUnusedVertexIndex = SplitVertexIdx; - GroupSplitFacesRemaining[SplitVertexIdx] = PartVertexList[SplitVertexIdx]; - bHasMainSplitGroup = true; - GroupVertexListCount++; - } - } - - int32 FistUnusedPrimIndex = -1; - for (int32 SplitFaceIdx = 0; SplitFaceIdx < AllGroupFaceIndices.Num(); SplitFaceIdx++) - { - if (AllGroupFaceIndices[SplitFaceIdx] == 0) - { - // This is unused face, we need to add it to unused faces list. - GroupSplitFaceIndicesRemaining.Add(SplitFaceIdx); - FistUnusedPrimIndex = SplitFaceIdx; - } - } - - // We store the remaining geo vertex list as a special split named "main geo" - // and make sure its treated before the collider meshes - if (bHasMainSplitGroup) - { - static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - AllSplitGroups.Add(RemainingGroupName); - AllSplitVertexLists.Add(RemainingGroupName, GroupSplitFacesRemaining); - AllSplitVertexCounts.Add(RemainingGroupName, GroupVertexListCount); - AllSplitFaceIndices.Add(RemainingGroupName, GroupSplitFaceIndicesRemaining); - AllSplitFirstValidPrimIndex.Add(RemainingGroupName, FistUnusedPrimIndex); - AllSplitFirstValidVertexIndex.Add(RemainingGroupName, FistUnusedVertexIndex); - } - } - else - { - // No splitting required - // Mark everything as the main geo group - static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - AllSplitGroups.Add(RemainingGroupName); - AllSplitVertexLists.Add(RemainingGroupName, PartVertexList); - AllSplitVertexCounts.Add(RemainingGroupName, PartVertexList.Num()); - AllSplitFirstValidPrimIndex.Add(RemainingGroupName, 0); - AllSplitFirstValidVertexIndex.Add(RemainingGroupName, 0); - - TArray AllFaces; - for (int32 FaceIdx = 0; FaceIdx < HGPO.PartInfo.FaceCount; ++FaceIdx) - AllFaces.Add(FaceIdx); - - AllSplitFaceIndices.Add(RemainingGroupName, AllFaces); - } - - return true; -} - -void -FHoudiniMeshTranslator::ResetPartCache() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::ResetPartCache")); - - // Vertex Positions - PartPositions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); - - // Vertex Normals - PartNormals.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); - - // Vertex TangentU - PartTangentU.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentU); - - // Vertex TangentV - PartTangentV.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentV); - - // Vertex Colors - PartColors.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoColors); - - // Vertex Alpha values - PartAlphas.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoAlpha); - - // FaceSmoothing values - PartFaceSmoothingMasks.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceSmoothingMasks); - - // UVs - PartUVSets.Empty(); - AttribInfoUVSets.Empty(); - - // UVs - PartLightMapResolutions.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoLightmapResolution); - - // Material IDs per face - PartFaceMaterialIds.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialIds); - // Unique material IDs - PartUniqueMaterialIds.Empty(); - // Material infos for each unique Material - PartUniqueMaterialInfos.Empty(); - // - bOnlyOneFaceMaterial = false; - - // Face Materials override - PartFaceMaterialOverrides.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialOverrides); - bMaterialOverrideNeedsCreateInstance = false; - - // LOD Screensize - PartLODScreensize.Empty(); - FHoudiniApi::AttributeInfo_Init(&AttribInfoLODScreensize); -} - -bool -FHoudiniMeshTranslator::UpdatePartPositionIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartPositionIfNeeded")); - - // Only Retrieve the vertices positions if necessary - if (PartPositions.Num() > 0) - return true; - - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve position data") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded")); - - // No need to read the normals if we want unreal to recompute them after - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - bool bReadNormals = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; - if (!bReadNormals) - return true; - - // Only Retrieve the normals if we haven't already - if (PartNormals.Num() > 0) - return true; - - // Retrieve normal data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, PartNormals); - - // There is no normals to fetch - if (!AttribInfoNormals.exists) - return true; - - if (!Success && AttribInfoNormals.exists) - { - // Error retrieving normals. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve normal data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded")) - - bool bReturn = true; - if (PartTangentU.Num() <= 0) - { - // Retrieve TangentU data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_TANGENTU, AttribInfoTangentU, PartTangentU); - - if (!Success && AttribInfoTangentU.exists) - { - // Error retrieving tangent. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentU data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - bReturn = false; - } - } - - if (PartTangentV.Num() <= 0) - { - // Retrieve TangentV data for this part - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_TANGENTV, AttribInfoTangentV, PartTangentV); - - if (!Success && AttribInfoTangentV.exists) - { - // Error retrieving tangent. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentV data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - bReturn = false; - } - } - - return bReturn; -} - -bool -FHoudiniMeshTranslator::UpdatePartColorsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartColorsIfNeeded")); - - // Only Retrieve the vertices colors if necessary - if (PartColors.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_COLOR, AttribInfoColors, PartColors); - - if (!Success && AttribInfoColors.exists) - { - // Error retrieving colors. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve color data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded")); - - // Only Retrieve the vertices alphas if necessary - if (PartAlphas.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_ALPHA, AttribInfoAlpha, PartAlphas); - - if (!Success && AttribInfoAlpha.exists) - { - // Error retrieving alpha values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve alpha data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceSmoothingIfNeeded() -{ - // Only Retrieve the vertices FaceSmoothing if necessary - if (PartFaceSmoothingMasks.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, - AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks); - - if (!Success && AttribInfoFaceSmoothingMasks.exists) - { - // Error retrieving FaceSmoothing values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve FaceSmoothing data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded")); - - // Only Retrieve uvs if necessary - if (PartUVSets.Num() > 0) - return true; - - PartUVSets.SetNum(MAX_STATIC_TEXCOORDS); - AttribInfoUVSets.SetNum(MAX_STATIC_TEXCOORDS); - - // The second UV set should be called uv2, but we will still check if need to look for a uv1 set. - // If uv1 exists, we'll look for uv, uv1, uv2 etc.. if not we'll look for uv, uv2, uv3 etc.. - bool bUV1Exists = FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, "uv1"); - - // Retrieve UVs. - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (TexCoordIdx > 0) - UVAttributeName += FString::Printf(TEXT("%d"), bUV1Exists ? TexCoordIdx : TexCoordIdx + 1); - - FHoudiniApi::AttributeInfo_Init(&AttribInfoUVSets[TexCoordIdx]); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*UVAttributeName), - AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], 2); - } - - // Also look for 16.5 uvs (attributes with a Texture type) - // For that, we'll have to iterate through ALL the attributes and check their types - TArray< FString > FoundAttributeNames; - TArray< HAPI_AttributeInfo > FoundAttributeInfos; - - for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) - { - FHoudiniEngineUtils::HapiGetAttributeOfType( - HGPO.GeoId, HGPO.PartId, (HAPI_AttributeOwner)AttrIdx, - HAPI_ATTRIBUTE_TYPE_TEXTURE, FoundAttributeInfos, FoundAttributeNames); - } - - if (FoundAttributeInfos.Num() <= 0) - return true; - - // We found some additionnal uv attributes - int32 AvailableIdx = 0; - for (int32 attrIdx = 0; attrIdx < FoundAttributeInfos.Num(); attrIdx++) - { - // Ignore the old uvs - if (FoundAttributeNames[attrIdx] == TEXT("uv") - || FoundAttributeNames[attrIdx] == TEXT("uv1") - || FoundAttributeNames[attrIdx] == TEXT("uv2") - || FoundAttributeNames[attrIdx] == TEXT("uv3") - || FoundAttributeNames[attrIdx] == TEXT("uv4") - || FoundAttributeNames[attrIdx] == TEXT("uv5") - || FoundAttributeNames[attrIdx] == TEXT("uv6") - || FoundAttributeNames[attrIdx] == TEXT("uv7") - || FoundAttributeNames[attrIdx] == TEXT("uv8")) - continue; - - HAPI_AttributeInfo CurrentAttrInfo = FoundAttributeInfos[attrIdx]; - if (!CurrentAttrInfo.exists) - continue; - - // Look for the next available index in the return arrays - for (; AvailableIdx < AttribInfoUVSets.Num(); AvailableIdx++) - { - if (!AttribInfoUVSets[AvailableIdx].exists) - break; - } - - // We are limited to MAX_STATIC_TEXCOORDS uv sets! - // If we already have too many uv sets, skip the rest - if ((AvailableIdx >= MAX_STATIC_TEXCOORDS) || (AvailableIdx >= AttribInfoUVSets.Num())) - { - HOUDINI_LOG_WARNING(TEXT("Too many UV sets found. Unreal only supports %d , skipping the remaining uv sets."), (int32)MAX_STATIC_TEXCOORDS); - break; - } - - // Force the tuple size to 2 ? - CurrentAttrInfo.tupleSize = 2; - - // Add the attribute infos we found - AttribInfoUVSets[AvailableIdx] = CurrentAttrInfo; - - // Allocate sufficient buffer for the attribute's data. - PartUVSets[AvailableIdx].SetNumUninitialized(CurrentAttrInfo.count * CurrentAttrInfo.tupleSize); - - // Get the texture coordinates - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, TCHAR_TO_UTF8(*(FoundAttributeNames[attrIdx])), - &AttribInfoUVSets[AvailableIdx], -1, - &PartUVSets[AvailableIdx][0], 0, CurrentAttrInfo.count)) - { - // Something went wrong when trying to access the uv values, invalidate this set - AttribInfoUVSets[AvailableIdx].exists = false; - } - } - - // Remove unused UV sets - if (bRemoveUnused) - { - for (int32 Idx = PartUVSets.Num() - 1; Idx >= 0; Idx--) - { - if (PartUVSets[Idx].Num() > 0) - continue; - - PartUVSets.RemoveAt(Idx); - } - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartLightmapResolutionsIfNeeded() -{ - // Only Retrieve the vertices lightmap resolution if necessary - if (PartLightMapResolutions.Num() > 0) - return true; - - // Get lightmap resolution (if present). - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, - AttribInfoLightmapResolution, PartLightMapResolutions); - - if (!Success && AttribInfoLightmapResolution.exists) - { - // Error retrieving lightmap resolution values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve lightmap resolution data"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded")); - - // Only Retrieve the material IDs if necessary - if (PartFaceMaterialIds.Num() > 0) - return true; - - int32 NumFaces = HGPO.PartInfo.FaceCount; - if (NumFaces <= 0) - return true; - - PartFaceMaterialIds.SetNum(NumFaces); - - // Get the materials IDs per face - HAPI_Bool bSingleFaceMaterial = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( - FHoudiniEngine::Get().GetSession(), - HGPO.GeoId, HGPO.PartId, &bSingleFaceMaterial, - &PartFaceMaterialIds[0], 0, NumFaces)) - { - // Error retrieving material face assignments. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material face assignments"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - bOnlyOneFaceMaterial = bSingleFaceMaterial; - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded")); - - // Only Retrieve the material overrides if necessary - if (PartFaceMaterialOverrides.Num() > 0) - return true; - - bMaterialOverrideNeedsCreateInstance = false; - - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - - // If material attribute was not found, check fallback compatibility attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - } - - // If material attribute and fallbacks were not found, check the material instance attribute. - if (!AttribInfoFaceMaterialOverrides.exists) - { - PartFaceMaterialOverrides.Empty(); - FHoudiniEngineUtils::HapiGetAttributeDataAsString( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, - AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); - - // We will we need to create material instances from the override attributes - bMaterialOverrideNeedsCreateInstance = AttribInfoFaceMaterialOverrides.exists; - } - - if (AttribInfoFaceMaterialOverrides.exists - && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_PRIM - && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_DETAIL) - { - HOUDINI_LOG_WARNING(TEXT("Static Mesh [%d %s], Geo [%d], Part [%d %s]: unreal_material must be a primitive or detail attribute, ignoring attribute."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - AttribInfoFaceMaterialOverrides.exists = false; - bMaterialOverrideNeedsCreateInstance = false; - PartFaceMaterialOverrides.Empty(); - return false; - } - - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartNeededMaterials() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials")); - - // Update the per face material IDs - UpdatePartFaceMaterialIDsIfNeeded(); - - // See if we have some material overides - UpdatePartFaceMaterialOverridesIfNeeded(); - - // If we have houdini materials AND overrides: - // We want to only create the Houdini materials that are not "covered" by overrides - // If we have material instance attributes, create all the houdini material anyway - // as their textures could be referenced by the material instance parameters - if (PartFaceMaterialOverrides.Num() > 0 && !bMaterialOverrideNeedsCreateInstance) - { - // If the material override was set on the detail, no need to look for houdini material IDs, as only the override will be used - if (AttribInfoFaceMaterialOverrides.exists && AttribInfoFaceMaterialOverrides.owner == HAPI_ATTROWNER_PRIM) - { - for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) - { - // Add a material ID to the unique array only if that face is not using the override - if (PartFaceMaterialOverrides[MaterialIdx].IsEmpty()) - PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); - } - } - } - else - { - // No material overrides, simply update the unique material array - for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) - PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); - } - - // Remove the invalid material ID from the unique array - PartUniqueMaterialIds.RemoveSingle(-1); - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials - Get the unique material infos")); - // Get the unique material infos - PartUniqueMaterialInfos.SetNum(PartUniqueMaterialIds.Num()); - for (int32 MaterialIdx = 0; MaterialIdx < PartUniqueMaterialIds.Num(); MaterialIdx++) - { - - FHoudiniApi::MaterialInfo_Init(&PartUniqueMaterialInfos[MaterialIdx]); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialInfo( - FHoudiniEngine::Get().GetSession(), - PartUniqueMaterialIds[MaterialIdx], - &PartUniqueMaterialInfos[MaterialIdx])) - { - // Error retrieving material face assignments. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material info for material %d"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, PartUniqueMaterialIds[MaterialIdx]); - continue; - } - } - } - return true; -} - -bool -FHoudiniMeshTranslator::UpdatePartLODScreensizeIfNeeded() -{ - // Only retrieve LOD screensizes if necessary - if (PartLODScreensize.Num() > 0) - return true; - - bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, - HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE, - AttribInfoLODScreensize, PartLODScreensize); - - if (!Success && AttribInfoLODScreensize.exists) - { - // Error retrieving FaceSmoothing values. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve LOD screensizes"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); - return false; - } - - return true; -} - - -UStaticMesh* -FHoudiniMeshTranslator::CreateNewStaticMesh(const FString& InSplitIdentifier) -{ - // Update the current Obj/Geo/Part/Split IDs - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.GeoId; - PackageParams.PartId = HGPO.PartId; - PackageParams.SplitStr = InSplitIdentifier; - - UStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) - return nullptr; - - return NewStaticMesh; -} - -UHoudiniStaticMesh* -FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentifier) -{ - // Update the current Obj/Geo/Part/Split IDs - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.GeoId; - PackageParams.PartId = HGPO.PartId; - // Add _HSM suffix to the split str, to distinguish the temporary HoudiniStaticMesh - // from the UStaticMesh - PackageParams.SplitStr = InSplitIdentifier + "_HSM"; - - UHoudiniStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) - return nullptr; - - return NewStaticMesh; -} - -bool -FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() -{ - // Time limit for processing - bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; - - double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Prepare the object that will store UCX and simple colliders - AllAggregateCollisions.Empty(); - - // We need to know the number of LODs that will be needed for this part - int32 NumberOfLODs = 0; - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) - NumberOfLODs++; - else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - bHasMainGeo = true; - } - - // Update the part's material's IDS and info now - CreateNeededMaterials(); - - // Check now if they were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Get the current target platform for default lod policies - ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); - check(CurrentPlatform); - - // New mesh list - TMap StaticMeshToBuild; - - // Map of Houdini Material IDs to Unreal Material Interface - TMap MapHoudiniMatIdToUnrealInterface; - // Map of Houdini Material Attributes to Unreal Material Interface - TMap MapHoudiniMatAttributesToUnrealInterface; - // Map of Unreal Material Interface to Unreal Material Index, per visible mesh - TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; - - // bool MeshMaterialsHaveBeenReset = false; - - double tick = FPlatformTime::Seconds(); - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre Split-Loop in %f seconds."), tick - time_start); - } - - UStaticMesh* MainStaticMesh = nullptr; - bool bAssignedCustomCollisionMesh = false; - ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; - - // Iterate through all detected split groups we care about and split geometry. - // The split are ordered in the following way: - // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - double split_tick = FPlatformTime::Seconds(); - - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], - OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - - // Get/Create the Aggregate Collisions for this mesh identifier - FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); - - // Handle UCX / Convex Hull colliders - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) - { - MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the convex hull colliders and add them to the Aggregate - if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) - continue; - } - else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) - { - MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the simple colliders and add them to the aggregate - if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) - continue; - } - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing SM from a previous cook - UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - // Prepare LOD Group data for this static mesh - FStaticMeshLODGroup LODGroup; - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing static mesh, create a new one - FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - - // Use the platform's default LODGroup policy - // TODO? Add setting for default LOD Group? - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); - } - else - { - // Try to reuse the existing SM's LOD group instead of the default one - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); - } - - if (SplitType == EHoudiniSplitType::Normal && !MainStaticMesh) - { - MainStaticMesh = FoundStaticMesh; - MainStaticMesh->ComplexCollisionMesh = nullptr; - MainStaticMesh->bCustomizedCollision = false; - // NOTE: The main static mesh collision trace flag will be set after all splits have been processed. - } - - if (!FoundOutputObject) - { - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - InputObjects.Remove(OutputObjectIdentifier); - } - else - { - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - FoundOutputObject->CachedAttributes.Empty(); - FoundOutputObject->CachedTokens.Empty(); - } - FoundOutputObject->bProxyIsCurrent = false; - - // TODO: Needed? - // Free any RHI resources for existing mesh before we re-create in place. - FoundStaticMesh->PreEditChange(NULL); - - // Check that the Static Mesh we found has the appropriate number of Source models/LODs - int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); - - // LODs are only for the "main" mesh, not for complex colliders! - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); - - if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) - { - while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) - FoundStaticMesh->AddSourceModel(); - - // We may have to remove excessive LOD levels - if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) - FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); - - // Initialize their default reduction setting - for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) - { - FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); - } - FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); - } - - // By default, always work on the first source model, unless we're a LOD - int32 SrcModelIndex = 0; - int32 LODIndex = 0; - if (SplitType == EHoudiniSplitType::LOD) - { - for (auto& curSplit : AllSplitGroups) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); - if (CurrentSplitType == EHoudiniSplitType::LOD - || CurrentSplitType == EHoudiniSplitType::Normal) - { - LODIndex++; - } - - if (curSplit == SplitGroupName) - break; - } - - // Fix for the case where we don't have a main geo - if(!bHasMainGeo) - LODIndex--; - } - - // Grab the appropriate SourceModel - FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; - if (!SrcModel) - { - HOUDINI_LOG_ERROR( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); - continue; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Load existing raw model. This will be empty as we are constructing a new mesh. - FRawMesh RawMesh; - if (!bRebuildStaticMesh) - { - // We dont need to rebuild the mesh itself: - // the geometry hasn't changed, but the materials have. - // We can just load the old data into the Raw mesh and reuse it. - SrcModel->LoadRawMesh(RawMesh); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - LoadRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - } - else - { - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's normal if needed - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - // Check that the number of normal we retrieved is correct - int32 WedgeNormalCount = SplitNormals.Num() / 3; - if (SplitNormals.Num() < 0 || !SplitNormals.IsValidIndex((WedgeNormalCount - 1) * 3 + 2)) - { - // Ignore normals - WedgeNormalCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); - } - - // Transfer the normals to the raw mesh - RawMesh.WedgeTangentZ.SetNumZeroed(WedgeNormalCount); - for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) - { - // Swap Y/Z for Coordinates conversion - RawMesh.WedgeTangentZ[WedgeTangentZIdx].X = SplitNormals[WedgeTangentZIdx * 3 + 0]; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].Y = SplitNormals[WedgeTangentZIdx * 3 + 2]; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].Z = SplitNormals[WedgeTangentZIdx * 3 + 1]; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Normals in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENTS - //--------------------------------------------------------------------------------------------------------------------- - - // No need to read the tangents if we want unreal to recompute them after - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - TArray< float > SplitTangentU; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - TArray< float > SplitTangentV; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - bool bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - - // Check that the number of tangents read matches the number of normals - int32 WedgeTangentUCount = SplitTangentU.Num() / 3; - int32 WedgeTangentVCount = SplitTangentV.Num() / 3; - if (WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount) - bGenerateTangents = true; - - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - - // Generate the tangents if needed - if (bGenerateTangents) - { - RawMesh.WedgeTangentX.SetNumZeroed(WedgeNormalCount); - RawMesh.WedgeTangentY.SetNumZeroed(WedgeNormalCount); - for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) - { - FVector TangentX, TangentY; - RawMesh.WedgeTangentZ[WedgeTangentZIdx].FindBestAxisVectors(TangentX, TangentY); - - RawMesh.WedgeTangentX[WedgeTangentZIdx] = TangentX; - RawMesh.WedgeTangentY[WedgeTangentZIdx] = TangentY; - } - } - else - { - // Transfer the tangents we have read them and they're valid - RawMesh.WedgeTangentX.SetNumZeroed(WedgeTangentUCount); - for (int32 WedgeTangentUIdx = 0; WedgeTangentUIdx < WedgeTangentUCount; ++WedgeTangentUIdx) - { - // We need to flip Z and Y - RawMesh.WedgeTangentX[WedgeTangentUIdx].X = SplitTangentU[WedgeTangentUIdx * 3 + 0]; - RawMesh.WedgeTangentX[WedgeTangentUIdx].Y = SplitTangentU[WedgeTangentUIdx * 3 + 2]; - RawMesh.WedgeTangentX[WedgeTangentUIdx].Z = SplitTangentU[WedgeTangentUIdx * 3 + 1]; - } - - RawMesh.WedgeTangentY.SetNumZeroed(WedgeTangentVCount); - for (int32 WedgeTangentVIdx = 0; WedgeTangentVIdx < WedgeTangentVCount; ++WedgeTangentVIdx) - { - // We need to flip Z and Y - RawMesh.WedgeTangentY[WedgeTangentVIdx].X = SplitTangentV[WedgeTangentVIdx * 3 + 0]; - RawMesh.WedgeTangentY[WedgeTangentVIdx].Y = SplitTangentV[WedgeTangentVIdx * 3 + 2]; - RawMesh.WedgeTangentY[WedgeTangentVIdx].Z = SplitTangentV[WedgeTangentVIdx * 3 + 1]; - } - } - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Tangents in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // VERTEX COLORS AND ALPHAS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's colors if needed - UpdatePartColorsIfNeeded(); - - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract this part's alpha values if needed - UpdatePartAlphasIfNeeded(); - - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - - // Transfer colors and alphas if possible - int32 WedgeColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; - bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && WedgeColorsCount > 0; - bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == WedgeColorsCount); - if (bSplitColorValid) - { - RawMesh.WedgeColors.SetNumZeroed(WedgeColorsCount); - for (int32 WedgeColorIdx = 0; WedgeColorIdx < WedgeColorsCount; WedgeColorIdx++) - { - FLinearColor WedgeColor; - WedgeColor.R = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); - WedgeColor.G = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); - WedgeColor.B = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); - - if (bSplitAlphaValid) - { - // Use the Alpha attribute value - WedgeColor.A = FMath::Clamp(SplitAlphas[WedgeColorIdx], 0.0f, 1.0f); - } - else if (AttribInfoColors.tupleSize >= 4) - { - // Use the alpha value from the color attribute - WedgeColor.A = FMath::Clamp( - SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); - } - else - { - WedgeColor.A = 1.0f; - } - - // Convert linear color to fixed color. - RawMesh.WedgeColors[WedgeColorIdx] = WedgeColor.ToFColor(false); - } - } - else - { - // TODO? Needed? New meshes wont have WedgeIndices yet!? - // No Colors or Alphas, init colors to White - FColor DefaultWedgeColor = FLinearColor::White.ToFColor(false); - WedgeColorsCount = RawMesh.WedgeIndices.Num(); - if (WedgeColorsCount > 0) - RawMesh.WedgeColors.Init(DefaultWedgeColor, WedgeColorsCount); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Cd and Alpha in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's FaceSmoothing values if needed - UpdatePartFaceSmoothingIfNeeded(); - - // Get the FaceSmoothing values for this split - TArray SplitFaceSmoothingMasks; - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); - - // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! - RawMesh.FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); - - // Check that the number of face smoothing values we retrieved is correct - int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; - if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) - { - // Ignore our face smoothing values - WedgeFaceSmoothCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); - } - - // Transfer the face smoothing masks to the raw mesh if we have any - for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) - { - RawMesh.FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - FaceSmoothing in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // UVS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's UV sets if needed - UpdatePartUVSetsIfNeeded(); - - // See if we need to transfer uv point attributes to vertex attributes. - TArray> SplitUVSets; - SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - } - - // Transfer UVs to the Raw Mesh - int32 UVChannelCount = 0; - int32 LightMapUVChannel = 0; - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; - - int32 WedgeUVCount = SplitUVs.Num() / 2; - if (SplitUVs.Num() > 0 && SplitUVs.IsValidIndex((WedgeUVCount - 1) * 2 + 1)) - { - RawMesh.WedgeTexCoords[TexCoordIdx].SetNumZeroed(WedgeUVCount); - for (int32 WedgeUVIdx = 0; WedgeUVIdx < WedgeUVCount; ++WedgeUVIdx) - { - // We need to flip V coordinate when it's coming from HAPI. - RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].X = SplitUVs[WedgeUVIdx * 2 + 0]; - RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].Y = 1.0f - SplitUVs[WedgeUVIdx * 2 + 1]; - } - - UVChannelCount++; - if (UVChannelCount <= 2) - LightMapUVChannel = TexCoordIdx; - } - else - { - RawMesh.WedgeTexCoords[TexCoordIdx].Empty(); - } - } - - // We must have at least one UV channel. If there's none, create one filled with zero data. - if (UVChannelCount == 0) - RawMesh.WedgeTexCoords[0].SetNumZeroed(SplitVertexCount); - - // Set the lightmap Coordinate Index - // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention - // If not, the first UV set will be used - FoundStaticMesh->LightMapCoordinateIndex = LightMapUVChannel; - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - UVs in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's LightmapResolution values if needed - UpdatePartLightmapResolutionsIfNeeded(); - - // make sure the mesh has a new lighting guid - FoundStaticMesh->LightingGuid = FGuid::NewGuid(); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Lightmap Resolutions in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" - // So that IndicesMapper[ oldIndex ] => newIndex - TArray IndicesMapper; - IndicesMapper.Init(-1, SplitVertexList.Num()); - int32 CurrentMapperIndex = 0; - - // NeededVertices: - // Array containing the old index of the needed vertices for the current split - // NeededVertices[ newIndex ] => oldIndex - TArray< int32 > NeededVertices; - RawMesh.WedgeIndices.SetNumZeroed(SplitVertexCount); - - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) - || !IndicesMapper.IsValidIndex(WedgeIndices[1]) - || !IndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (IndicesMapper[WedgeIndices[i]] < 0) - { - // This old index has not yet been "converted" to a new index - NeededVertices.Add(WedgeIndices[i]); - IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; - CurrentMapperIndex++; - } - - // Replace the old index with the new one - WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; - } - - if (!RawMesh.WedgeIndices.IsValidIndex(ValidVertexId + 2)) - break; - - // Flip wedge indices to fix the winding order. - RawMesh.WedgeIndices[ValidVertexId + 0] = WedgeIndices[0]; - RawMesh.WedgeIndices[ValidVertexId + 1] = WedgeIndices[2]; - RawMesh.WedgeIndices[ValidVertexId + 2] = WedgeIndices[1]; - - // Check if we need to patch UVs. - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - if (RawMesh.WedgeTexCoords[TexCoordIdx].IsValidIndex(ValidVertexId + 2)) - { - Swap(RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 1], - RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 2]); - } - } - - // Check if we need to patch colors. - if (RawMesh.WedgeColors.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeColors[ValidVertexId + 1], RawMesh.WedgeColors[ValidVertexId + 2]); - - // Check if we need to patch Normals and tangents. - if (RawMesh.WedgeTangentZ.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentZ[ValidVertexId + 1], RawMesh.WedgeTangentZ[ValidVertexId + 2]); - - if (RawMesh.WedgeTangentX.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentX[ValidVertexId + 1], RawMesh.WedgeTangentX[ValidVertexId + 2]); - - if (RawMesh.WedgeTangentY.IsValidIndex(ValidVertexId + 2)) - Swap(RawMesh.WedgeTangentY[ValidVertexId + 1], RawMesh.WedgeTangentY[ValidVertexId + 2]); - - ValidVertexId += 3; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - UpdatePartPositionIfNeeded(); - - // - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - int32 VertexPositionsCount = NeededVertices.Num(); - RawMesh.VertexPositions.SetNumZeroed(VertexPositionsCount); - - for (int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx) - { - int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; - if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // We need to swap Z and Y coordinate here, and convert from m to cm. - RawMesh.VertexPositions[VertexPositionIdx].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - RawMesh.VertexPositions[VertexPositionIdx].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - RawMesh.VertexPositions[VertexPositionIdx].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - /* - // TODO: - // Check if this mesh contains only degenerate triangles. - if (FHoudiniEngineUtils::CountDegenerateTriangles(RawMesh) == SplitGroupFaceCount) - { - // This mesh contains only degenerate triangles, there's nothing we can do. - if (bStaticMeshCreated) - StaticMesh->MarkPendingKill(); - - continue; - } - */ - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL ATTRIBUTE OVERRIDES - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: These are actually per faces, not per vertices... - // Need to update!! - UpdatePartFaceMaterialOverridesIfNeeded(); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Material Overrides in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Handle Materials!!!! - - // Get face indices for this split. - TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; - - // // We need to reset the Static Mesh's materials once per SM: - // // so, for the first lod, or the main geo... - // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) - // { - // FoundStaticMesh->StaticMaterials.Empty(); - // MeshMaterialsHaveBeenReset = true; - // } - // - // // .. or for each visible complex collider - // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - // FoundStaticMesh->StaticMaterials.Empty(); - - // Clear the materials array of the mesh the first time we encounter it - if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) - { - FoundStaticMesh->StaticMaterials.Empty(); - } - TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); - - // Process material overrides first - if (PartFaceMaterialOverrides.Num() > 0) - { - // Array used to avoid constantly attempting to load invalid materials - TArray InvalidMaterials; - - // If the part has material overrides - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - continue; - - UMaterialInterface * MaterialInterface = nullptr; - int32 CurrentFaceMaterialIdx = 0; - const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface) - { - // Try to locate the corresponding material interface - - // Start by looking in our assignment map - FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) - { - // Only try to load a material if has a chance to be valid! - MaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - - if (!MaterialInterface) - InvalidMaterials.Add(MaterialName); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); - } - else - { - // The Attribute Material and its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - if (!MaterialInterface) - { - // If everything fails, we'll use the default material - MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - } - } - - if (MaterialInterface) - { - int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); - } - - // Update the Face Material on the mesh - RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; - } - } - } - else if (PartUniqueMaterialIds.Num() > 0) - { - // The part has houdini materials - if (bOnlyOneFaceMaterial) - { - // We have only one material. - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - else - { - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - // Reset Rawmesh material face assignments. - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - UMaterialInterface* MaterialInterface = nullptr; - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (MaterialInterface) - { - int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; - } - } - else - { - MaterialInterface = Cast(DefaultMaterial); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - - if (MaterialInterface) - { - // Add the material to the Static mesh - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - - // Map the houdini ID to the unreal one - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); - - // Update the face index - RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; - } - } - } - } - else - { - // No materials were found, we need to use default Houdini material. - int32 SplitFaceCount = SplitFaceIndices.Num(); - RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceCount); - - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Face Materials in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Update the Build Settings using the default setting values - UpdateMeshBuildSettings( - SrcModel->BuildSettings, - RawMesh.WedgeTangentZ.Num() > 0, - (RawMesh.WedgeTangentX.Num() > 0 && RawMesh.WedgeTangentY.Num() > 0), - RawMesh.WedgeTexCoords->Num() > 0); - - // Check for a lightmap resolution override - int32 LightMapResolutionOverride = -1; - if (PartLightMapResolutions.Num() > 0) - LightMapResolutionOverride = PartLightMapResolutions[0]; - - if (LightMapResolutionOverride > 0) - FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; - else - FoundStaticMesh->LightMapResolution = 64; - - // TODO - //StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; - //StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; - //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings; - - // TODO: - // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - - // This was required due to the impeding deprecation of FRawMesh - // If we dont update this UE4 will crash upon deleting an asset. - //SrcModel->StaticMeshOwner = FoundStaticMesh; - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreSaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Store the new raw mesh if it is valid - if (RawMesh.IsValid()) - { - SrcModel->SaveRawMesh(RawMesh); - } - else - { - HOUDINI_LOG_WARNING( - TEXT("[CreateStaticMesh_RawMesh]: Invalid StaticMesh data for %s LOD %i in cook output! Please check the log."), - *FoundStaticMesh->GetName(), LODIndex); - // Create an "empty" valid raw mesh (single zero-area triangle) - // TODO: is there a cleaner way to do this? Perhaps committing an empty mesh description? Empty RawMesh is - // a no-op on SrcModel->SaveRawMesh (leaves previous data in place). - // TODO: perhaps we can use an alternative "error" mesh? - RawMesh.Empty(); - RawMesh.VertexPositions.Add(FVector::ZeroVector); - RawMesh.WedgeIndices.SetNumZeroed(3); - RawMesh.WedgeTexCoords[0].Init(FVector2D::ZeroVector, RawMesh.WedgeIndices.Num()); - SrcModel->SaveRawMesh(RawMesh); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - SaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - { - // Patch the MeshDescription data structure that is being output from SaveRawMesh. SaveRawMesh leaves invalid entries - // in the PolyGroups / MaterialSlotNames arrays that causes issues later when the static mesh is built and LOD material assignments - // are being done (materials aren't correctly assigned to LODs if LODs use different materials). - - // Create a Polygon Group for each material slot - TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = - SrcModel->MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - - // We must use the number of assignment materials found to reserve the number of material slots - // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials - int32 NumberOfMaterials = FoundStaticMesh->StaticMaterials.Num(); - if (NumberOfMaterials <= 0) - { - // No materials, create a polygon group for the default one - const FPolygonGroupID& PolygonGroupID = SrcModel->MeshDescription->CreatePolygonGroup(); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - } - else - { - FPolygonGroupArray& PolyGroups = SrcModel->MeshDescription->PolygonGroups(); - for (auto& CurrentMatAssignment : OutputAssignmentMaterials) - { - const FPolygonGroupID& PolygonGroupID = SrcModel->MeshDescription->CreatePolygonGroup(); - - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = - FName(CurrentMatAssignment.Value ? *(CurrentMatAssignment.Value->GetName()) : *(CurrentMatAssignment.Key)); - } - } - } - - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = GetLODSCreensizeForSplit(SplitGroupName); - if (screensize >= 0.0f) - { - // Only apply the LOD screensize if it's valid - SrcModel->ScreenSize = screensize; - //FoundStaticMesh->GetSourceModel(LODIndex).ScreenSize = screensize; - FoundStaticMesh->bAutoComputeLODScreenSize = false; - } - - // TODO: - // SET STATIC MESH GENERATION PARAM - // HANDLE COLLIDERS - // REMOVE OLD COLLIDERS - // CUSTOM BAKE NAME OVERRIDE - - // Update property attributes on the SM - TArray PropertyAttributes; - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - HGPO.GeoId, HGPO.PartId, - true, - AllSplitFirstValidPrimIndex[SplitGroupName], - INDEX_NONE, - AllSplitFirstValidVertexIndex[SplitGroupName], - PropertyAttributes)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( - FoundStaticMesh, PropertyAttributes); - } - - TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - { - // cache the level path attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); - } - } - - TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - // cache the output name attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); - } - } - - TArray TileValues; - if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); - } - } - - TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) - { - if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); - } - } - - TArray BakeFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId)) - { - if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) - { - // cache the unreal_bake_folder attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); - } - } - - TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) - { - if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); - } - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Attributes in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Notify that we created a new Static Mesh if needed - if (bNewStaticMeshCreated) - FAssetRegistryModule::AssetCreated(FoundStaticMesh); - - // Add the Static mesh to the output maps and the build map if we haven't already - if (FoundOutputObject) - { - FoundOutputObject->OutputObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = false; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - - StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Total Split time: %f seconds."), tick - split_tick); - } - } - - // Look if we only have colliders - // If we do, we'll allow attaching sockets to the collider meshes - bool bCollidersOnly = true; - for (auto& Current : StaticMeshToBuild) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) - { - bCollidersOnly = false; - break; - } - } - - FHoudiniScopedGlobalSilence ScopedGlobalSilence; - for (auto& Current : StaticMeshToBuild) - { - tick = FPlatformTime::Seconds(); - - UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) - continue; - - UBodySetup * BodySetup = SM->BodySetup; - if (!BodySetup) - { - SM->CreateBodySetup(); - BodySetup = SM->BodySetup; - } - - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - - // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) - { - // Make sure rendering is done - so we are not changing data being used by collision drawing. - FlushRenderingCommands(); - - // Clean up old colliders from a previous cook - BodySetup->Modify(); - BodySetup->RemoveSimpleCollision(); - - FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; - FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); - if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) - { - BodySetup->AddCollisionFrom(*CurrentAggColl); - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; - } - - // See if we need to enable collisions on the whole mesh - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - // Complex collider, enable collisions for this (collider) static mesh. - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - - // Apply the collider to the Main static mesh, if relevant. - ApplyComplexColliderHelper( - MainStaticMesh, - SM, - SplitType, - bAssignedCustomCollisionMesh, - OutputObjects.Find(Current.Key)); - } - else - { - // TODO - // if the LODForCollision uproperty attribute is set, we need to activate complex collision - // on the static mesh for that lod to be picked up properly as a collider - if (FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, - "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) - { - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - } - } - - // Add the Sockets to the StaticMesh - // We only add them to the main geo, or to the colliders if we only generate colliders - bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; - if (bAddSocket) - { - if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, HGPO.AllMeshSockets, true)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); - } - } - - if (MainStaticMesh) - { - UBodySetup* MainBodySetup = MainStaticMesh->BodySetup; - if (!IsValid(MainBodySetup)) - { - MainStaticMesh->CreateBodySetup(); - MainBodySetup = MainStaticMesh->BodySetup; - } - - check(MainBodySetup); - // Set the main static mesh to whatever the final CTF should be. - MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // BUILD the Static Mesh - // bSilent doesnt add the Build Errors... - double build_start = FPlatformTime::Seconds(); - TArray SMBuildErrors; - SM->Build(true, &SMBuildErrors); - if (bDoTiming) - { - tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - StaticMesh->Build() executed in %f seconds."), tick - build_start); - } - - // This replaces the call to RefreshCollision below, but without CreateNavCollision - // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, - // and can be expensive depending on the vert/poly count of the mesh - // RefreshCollisionChange(*SM); - { - for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) - { - UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); - if (StaticMeshComponent->GetStaticMesh() == SM) - { - // it needs to recreate IF it already has been created - if (StaticMeshComponent->IsPhysicsStateCreated()) - { - StaticMeshComponent->RecreatePhysicsState(); - } - } - } - - FEditorSupportDelegates::RedrawAllViewports.Broadcast(); - } - - SM->GetOnMeshChanged().Broadcast(); - - UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); - } - } - - // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup - // Here as it has already been handled by the StaticMesh Build call - - double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() executed in %f seconds."), time_end - time_start); - - return true; -} - -bool -FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() -{ - // Time limit for processing - bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; - - double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - // Simple colliders first, lods and finally, invisible colliders (that are separate Static Mesh) - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Prepare the object that will store UCX and simple colliders - AllAggregateCollisions.Empty(); - - // We need to know the number of LODs that will be needed for this part - int32 NumberOfLODs = 0; - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) - NumberOfLODs++; - else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - bHasMainGeo = true; - } - - // Update the part's material's IDS and info now - CreateNeededMaterials(); - - // Check if the materials were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Get the current target platform for default lod policies - ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); - check(CurrentPlatform); - - // New mesh list - TMap StaticMeshToBuild; - - // Map of Houdini Material IDs to Unreal Material Interface - TMap MapHoudiniMatIdToUnrealInterface; - // Map of Houdini Material Attributes to Unreal Material Interface - TMap MapHoudiniMatAttributesToUnrealInterface; - // Map of Unreal Material Interface to Unreal Material Index, per visible mesh - TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; - - bool MeshMaterialsHaveBeenReset = false; - - double tick = FPlatformTime::Seconds(); - if (bDoTiming) - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); - - UStaticMesh* MainStaticMesh = nullptr; - bool bAssignedCustomCollisionMesh = false; - ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; - - // Iterate through all detected split groups we care about and split geometry. - // The split are ordered in the following way: - // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - double split_tick = FPlatformTime::Seconds(); - - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], - OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - - // Get/Create the Aggregate Collisions for this mesh identifier - FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); - - // Handle UCX / Convex Hull colliders - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) - { - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the convex hull colliders and add them to the Aggregate - if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) - continue; - } - else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) - { - MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; - // Get the part position if needed - UpdatePartPositionIfNeeded(); - - // Create the simple colliders and add them to the aggregate - if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) - { - // Failed to generate a convex collider - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - } - - // If the collider is not visible, stop here - if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) - continue; - } - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing SM from a previous cook - UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - // Prepare LOD Group data for this static mesh - FStaticMeshLODGroup LODGroup; - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing static mesh, create a new one - FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - - // Use the platform's default LODGroup policy - // TODO? Add setting for default LOD Group? - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); - } - else - { - // Try to reuse the existing SM's LOD group instead of the default one - LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); - } - - if (SplitType == EHoudiniSplitType::Normal) - { - MainStaticMesh = FoundStaticMesh; - MainStaticMesh->ComplexCollisionMesh = nullptr; - MainStaticMesh->bCustomizedCollision = false; - } - - if (!FoundOutputObject) - { - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - } - else - { - // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before - // setting the new values (so that we do not re-use any values from the previous cook) - FoundOutputObject->CachedAttributes.Empty(); - FoundOutputObject->CachedTokens.Empty(); - } - FoundOutputObject->bProxyIsCurrent = false; - - // TODO: Needed? - // Free any RHI resources for existing mesh before we re-create in place. - FoundStaticMesh->PreEditChange(NULL); - - // Check that the Static Mesh we found has the appropriate number of Source models/LODs - int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); - - // LODs are only for the "main" mesh, not for complex colliders! - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); - - if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) - { - while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) - FoundStaticMesh->AddSourceModel(); - - // We may have to remove excessive LOD levels - if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) - FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); - - // Initialize their default reduction setting - for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) - { - FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); - } - FoundStaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); - } - - // By default, always work on the first source model, unless we're a LOD - int32 SrcModelIndex = 0; - int32 LODIndex = 0; - if (SplitType == EHoudiniSplitType::LOD) - { - for (auto& curSplit : AllSplitGroups) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); - if (CurrentSplitType == EHoudiniSplitType::LOD - || CurrentSplitType == EHoudiniSplitType::Normal) - { - LODIndex++; - } - - if (curSplit == SplitGroupName) - break; - } - - // Fix for the case where we don't have a main geo - if(!bHasMainGeo) - LODIndex--; - } - - // Grab the appropriate SourceModel - FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; - if (!SrcModel) - { - HOUDINI_LOG_ERROR( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); - continue; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - bool bHasNormal = false; - bool bHasTangents = false; - - // Load the existing mesh description if we don't need to rebuild the mesh - FMeshDescription* MeshDescription; - if (!bRebuildStaticMesh) - { - // We dont need to rebuild the mesh itself: - // the geometry hasn't changed, but the materials have. - // We can just reuse the old MeshDescription and reuse it. - MeshDescription = FoundStaticMesh->GetMeshDescription(LODIndex); - } - else - { - // Extract all the data needed for this split - // Start by initializing the MeshDescription for this LOD - MeshDescription = FoundStaticMesh->CreateMeshDescription(LODIndex); - FStaticMeshAttributes(*MeshDescription).Register(); - - // Mesh description uses material to create its PolygonGroups, - // so we first need to know how many different materials we have for this split - // and what vertices/indices belong to each material for remapping - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // SplitNeededVertices - // Array containing the (unique) part indices for the vertices that are needed for this split - // SplitNeededVertices[splitIndex] = PartIndex - TArray SplitNeededVertices; - //SplitNeededVertices.SetNumZeroed(SplitVertexCount); - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" so that IndicesMapper[ partIndex ] => splitIndex - TArray PartToSplitIndicesMapper; - PartToSplitIndicesMapper.Init(-1, SplitVertexList.Num()); - //TMap SplitToPartIndicesMapper; - - // SplitIndices - // Array of SplitIndices used to describe this split's polygons - TArray SplitIndices; - SplitIndices.SetNumZeroed(SplitVertexCount); - - int32 CurrentSplitIndex = 0; - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[0]) - || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[1]) - || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (PartToSplitIndicesMapper[WedgeIndices[i]] < 0) - { - // This part index has not yet been "converted" to a new split index - SplitNeededVertices.Add(WedgeIndices[i]); - PartToSplitIndicesMapper[WedgeIndices[i]] = CurrentSplitIndex; - //SplitToPartIndicesMapper.Add(CurrentSplitIndex, WedgeIndices[i]); - CurrentSplitIndex++; - } - - // Replace the old part index with the new split index - WedgeIndices[i] = PartToSplitIndicesMapper[WedgeIndices[i]]; - } - - if (!SplitIndices.IsValidIndex(ValidVertexId + 2)) - break; - - // Flip wedge indices to fix the winding order. - SplitIndices[ValidVertexId + 0] = WedgeIndices[0]; - SplitIndices[ValidVertexId + 1] = WedgeIndices[2]; - SplitIndices[ValidVertexId + 2] = WedgeIndices[1]; - - ValidVertexId += 3; - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract position for this part - UpdatePartPositionIfNeeded(); - - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - TVertexAttributesRef VertexPositions = - MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - - MeshDescription->ReserveNewVertices(SplitNeededVertices.Num()); - for ( const int32& NeededVertexIndex : SplitNeededVertices) - { - // Create a new Vertex - FVertexID VertexID = MeshDescription->CreateVertex(); - if (PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexPositions[VertexID].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexPositions[VertexID].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexPositions[VertexID].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - else - { - // Error when retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // // TODO: Check if still needed for MeshDescription - // // We need to reset the Static Mesh's materials once per SM: - // // so, for the first lod, or the main geo... - // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) - // { - // FoundStaticMesh->StaticMaterials.Empty(); - // MeshMaterialsHaveBeenReset = true; - // } - // - // // .. or for each visible complex collider - // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - // FoundStaticMesh->StaticMaterials.Empty(); - - // Clear the materials array of the mesh the first time we encounter it - if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) - { - FoundStaticMesh->StaticMaterials.Empty(); - } - TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); - - // Get this split's faces - TArray& SplitGroupFaceIndices = AllSplitFaceIndices[SplitGroupName]; - // Array holding the materials needed for this split - //TArray SplitMaterials; - // Split Material indices per face, by default all faces are set to use the first Material - TArray SplitFaceMaterialIndices; - SplitFaceMaterialIndices.SetNumZeroed(SplitGroupFaceIndices.Num()); - - bool HasHoudiniMaterials = PartUniqueMaterialIds.Num() > 0; - bool HasMaterialOverrides = PartFaceMaterialOverrides.Num() > 0; - if (!HasHoudiniMaterials && !HasMaterialOverrides) - { - // We don't have any material override or houdini material - // we just need one polygon group using the default Houdini material. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // TODO: ? Add default mat to the assignement map? - } - else if (HasHoudiniMaterials && !HasMaterialOverrides) - { - // We have Houdini Material but no overrides - if (bOnlyOneFaceMaterial || PartUniqueMaterialIds.Num() == 1) - { - // We have only one Houdini material. - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMesh->StaticMaterials.Empty(); - FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // TODO: ? Add the mat to the assignement map? - } - else - { - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * MaterialDefault = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - // Reset Rawmesh material face assignments. - for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - UMaterialInterface* MaterialInterface = nullptr; - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (MaterialInterface) - { - int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; - continue; - } - } - else - { - MaterialInterface = Cast(MaterialDefault); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - - if (MaterialInterface) - { - // Add the material to the Static mesh - //int32 UnrealMatIndex = SplitMaterials.Add(Material); - int32 UnrealMatIndex = FoundStaticMesh->StaticMaterials.Add(MaterialInterface); - - // Map the houdini ID to the unreal one - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); - - // Update the face index - SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; - } - } - } - } - else - { - // Array used to avoid constantly attempting to load invalid materials - TArray InvalidMaterials; - - // If we have material overrides - for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; - - UMaterialInterface * MaterialInterface = nullptr; - int32 CurrentFaceMaterialIdx = -1; - if (PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - { - const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface) - { - // Try to locate the corresponding material interface - - // Start by looking in our assignment map - FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) - { - // Only try to load a material if has a chance to be valid! - MaterialInterface = Cast< UMaterialInterface >( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - - if (!MaterialInterface) - InvalidMaterials.Add(MaterialName); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add this material to the map - MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); - } - } - - if (!MaterialInterface) - { - // The attribute Material or its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface) - { - // If everything else fails, we'll use the default material - MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - } - } - - int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMesh->StaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); - } - - // Update the Face Material on the mesh - SplitFaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; - } - } - - // Create a Polygon Group for each material slot - TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = - MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - - // We must use the number of assignment materials found to reserve the number of material slots - // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials - int32 NumberOfMaterials = OutputAssignmentMaterials.Num(); - if (NumberOfMaterials <= 0) - { - // No materials, create a polygon group for the default one - const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - } - else - { - MeshDescription->ReserveNewPolygonGroups(NumberOfMaterials); - //for (int32 MatIndex = 0; MatIndex < NumberOfMaterials; ++MatIndex) - for (auto& CurrentMatAssignement : OutputAssignmentMaterials) - { - const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); - PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = - FName(CurrentMatAssignement.Value ? *(CurrentMatAssignement.Value->GetName()) : *(CurrentMatAssignement.Key)); - } - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // - // VERTEX INSTANCE ATTRIBUTES - // NORMALS, TANGENTS, COLORS, UVS, Alpha - // - - // Extract the normals - UpdatePartNormalsIfNeeded(); - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); - - // No need to read the tangents if we want unreal to recompute them after - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; - - // Extract the tangents - TArray SplitTangentU; - TArray SplitTangentV; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - int32 NormalCount = SplitNormals.Num(); - bool bGenerateTangents = (NormalCount > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); - // Check that the number of tangents read matches the number of normals - if (SplitTangentU.Num() != NormalCount || SplitTangentV.Num() != NormalCount) - bGenerateTangents = true; - - if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangents = false; - } - - // Generate the tangents if needed - if (bGenerateTangents) - { - SplitTangentU.SetNumZeroed(NormalCount); - SplitTangentV.SetNumZeroed(NormalCount); - for (int32 Idx = 0; Idx + 2 < NormalCount; Idx += 3) - { - FVector TangentZ; - TangentZ.X = SplitNormals[Idx + 0]; - TangentZ.Y = SplitNormals[Idx + 2]; - TangentZ.Z = SplitNormals[Idx + 1]; - - FVector TangentX, TangentY; - TangentZ.FindBestAxisVectors(TangentX, TangentY); - - SplitTangentU[Idx + 0] = TangentX.X; - SplitTangentU[Idx + 2] = TangentX.Y; - SplitTangentU[Idx + 1] = TangentX.Z; - - SplitTangentV[Idx + 0] = TangentY.X; - SplitTangentV[Idx + 2] = TangentY.Y; - SplitTangentV[Idx + 1] = TangentY.Z; - } - } - } - TVertexInstanceAttributesRef VertexInstanceTangents = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); - TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); - - // Extract the color values - UpdatePartColorsIfNeeded(); - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract the alpha values - UpdatePartAlphasIfNeeded(); - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - TVertexInstanceAttributesRef VertexInstanceColors = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); - - // Extract UVs - UpdatePartUVSetsIfNeeded(true); - // See if we need to transfer uv point attributes to vertex attributes. - int32 UVSetCount = PartUVSets.Num(); - TArray> SplitUVSets; - SplitUVSets.SetNum(UVSetCount); - for (int32 TexCoordIdx = 0; TexCoordIdx < UVSetCount; TexCoordIdx++) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - } - TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); - VertexInstanceUVs.SetNumIndices(UVSetCount); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // Allocate space for the vertex instances and polygons - MeshDescription->ReserveNewVertexInstances(SplitIndices.Num()); - MeshDescription->ReserveNewPolygons(SplitIndices.Num() / 3); - //Approximately 2.5 edges per polygons - MeshDescription->ReserveNewEdges(SplitIndices.Num() * 2.5f / 3); - - bHasNormal = SplitNormals.Num() > 0; - bHasTangents = SplitTangentU.Num() > 0 && SplitTangentV.Num() > 0; - bool bHasRGB = SplitColors.Num() > 0; - bool bHasRGBA = bHasRGB && AttribInfoColors.tupleSize == 4; - bool bHasAlpha = SplitAlphas.Num() > 0; - - TArray HasUVSets; - HasUVSets.SetNumZeroed(PartUVSets.Num()); - for (int32 Idx = 0; Idx < PartUVSets.Num(); Idx++) - HasUVSets[Idx] = PartUVSets[Idx].Num() > 0; - - uint32 FaceCount = SplitIndices.Num() / 3; - for (uint32 FaceIndex = 0; FaceIndex < FaceCount; FaceIndex++) - { - TArray FaceVertexInstanceIDs; - FaceVertexInstanceIDs.SetNum(3); - - // Ignore degenerate triangles - FVertexID VertexIDs[3]; - for (int32 Corner = 0; Corner < 3; ++Corner) - { - VertexIDs[Corner] = FVertexID(SplitIndices[(FaceIndex * 3) + Corner]); - } - if (VertexIDs[0] == VertexIDs[1] || VertexIDs[0] == VertexIDs[2] || VertexIDs[1] == VertexIDs[2]) - continue; - - //FVertexID FaceVertexIDs[3]; - for (int32 Corner = 0; Corner < 3; Corner++) - { - uint32 SplitIndex = (FaceIndex * 3) + Corner; - uint32 SplitVertexIndex = SplitIndices[SplitIndex]; - const FVertexInstanceID& VertexInstanceID = MeshDescription->CreateVertexInstance(FVertexID(SplitVertexIndex)); - - // Fix the winding order by updating the SplitIndex (invert corner 1 and 2) - // instead of going 0 1 2 go 0 2 1 - // TODO; this slows down StaticMesh->Build() considerably! - Corner == 1 ? SplitIndex++ : Corner == 2 ? SplitIndex-- : SplitIndex; - - const uint32 SplitVertexIndex_X = SplitIndex * 3 + 0; - const uint32 SplitVertexIndex_Y = SplitIndex * 3 + 2; - const uint32 SplitVertexIndex_Z = SplitIndex * 3 + 1; - // Normals - if (bHasNormal) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexInstanceNormals[VertexInstanceID].X = SplitNormals[SplitVertexIndex_X]; - VertexInstanceNormals[VertexInstanceID].Y = SplitNormals[SplitVertexIndex_Y]; - VertexInstanceNormals[VertexInstanceID].Z = SplitNormals[SplitVertexIndex_Z]; - } - - // Tangents and binormals - if (bHasTangents) - { - // We need to swap Z and Y coordinate here, and convert from m to cm. - VertexInstanceTangents[VertexInstanceID].X = SplitTangentU[SplitVertexIndex_X]; - VertexInstanceTangents[VertexInstanceID].Y = SplitTangentU[SplitVertexIndex_Y]; - VertexInstanceTangents[VertexInstanceID].Z = SplitTangentU[SplitVertexIndex_Z]; - - FVector TangentY; - TangentY.X = SplitTangentV[SplitVertexIndex_X]; - TangentY.Y = SplitTangentV[SplitVertexIndex_Y]; - TangentY.Z = SplitTangentV[SplitVertexIndex_Z]; - - VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign( - VertexInstanceTangents[VertexInstanceID].GetSafeNormal(), - TangentY.GetSafeNormal(), - VertexInstanceNormals[VertexInstanceID].GetSafeNormal()); - } - - // Color - FLinearColor Color = FLinearColor::White; - if (bHasRGB) - { - Color.R = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); - Color.G = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); - Color.B = FMath::Clamp( - SplitColors[SplitIndex * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); - } - // Alpha - if (bHasAlpha) - { - Color.A = FMath::Clamp(SplitAlphas[SplitIndex], 0.0f, 1.0f); - } - else if (bHasRGBA) - { - Color.A = FMath::Clamp(SplitColors[SplitIndex * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); - } - VertexInstanceColors[VertexInstanceID] = FVector4(Color); - - // UVs - for (int32 UVIndex = 0; UVIndex < SplitUVSets.Num(); UVIndex++) - { - if (HasUVSets[UVIndex]) - { - // We need to flip V coordinate when it's coming from HAPI. - FVector2D CurrentUV; - CurrentUV.X = SplitUVSets[UVIndex][SplitIndex * 2 + 0]; - CurrentUV.Y = 1.0f - SplitUVSets[UVIndex][SplitIndex * 2 + 1]; - - VertexInstanceUVs.Set(VertexInstanceID, UVIndex, CurrentUV); - } - } - - FaceVertexInstanceIDs[Corner] = VertexInstanceID; - } - - const FPolygonGroupID PolygonGroupID(SplitFaceMaterialIndices[FaceIndex]); - - // Insert a triangle into the mesh - MeshDescription->CreateTriangle(PolygonGroupID, FaceVertexInstanceIDs); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's FaceSmoothing values if needed - UpdatePartFaceSmoothingIfNeeded(); - - // Get the FaceSmoothing values for this split - TArray SplitFaceSmoothingMasks; - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); - - // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! - // TODO: Expose the default FaceSmoothing value - // 0 will make hard face - TArray FaceSmoothingMasks; - FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); - - // Check that the number of face smoothing values we retrieved is correct - int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; - if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) - { - // Ignore our face smoothing values - WedgeFaceSmoothCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); - } - - // Transfer the face smoothing masks to the raw mesh if we have any - for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) - { - FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; - } - - // TODO - // Check - FStaticMeshOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, *MeshDescription); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - // Extract this part's LightmapResolution values if needed - UpdatePartLightmapResolutionsIfNeeded(); - - // make sure the mesh has a new lighting guid - FoundStaticMesh->LightingGuid = FGuid::NewGuid(); - } - - // Update the Build Settings using the default setting values - UpdateMeshBuildSettings( - SrcModel->BuildSettings, - bHasNormal, - bHasTangents, - PartUVSets.Num() > 0); - - // Set the lightmap Coordinate Index - // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention - FoundStaticMesh->LightMapCoordinateIndex = PartUVSets.Num() > 1 ? 1 : 0; - - // Check for a lightmapa resolution override - int32 LightMapResolutionOverride = -1; - if ( PartLightMapResolutions.Num() > 0) - LightMapResolutionOverride = PartLightMapResolutions[0]; - - if (LightMapResolutionOverride > 0) - FoundStaticMesh->LightMapResolution = LightMapResolutionOverride; - else - FoundStaticMesh->LightMapResolution = 64; - - // TODO: - // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? - - // RAW MESH CHECKS - - // TODO: Check not needed w/ FMeshDesc - // This is required due to the impeding deprecation of FRawMesh - // If we dont update this UE4 will crash upon deleting an asset. - //SrcModel->StaticMeshOwner = FoundStaticMesh; - - // Check if the mesh has at least one triangle, if not, log a message - if (MeshDescription->Triangles().Num() == 0) - { - HOUDINI_LOG_WARNING( - TEXT("[CreateStaticMesh_MeshDescription]: 0 valid triangles in StaticMesh data for %s LOD %i! Please check the log."), - *FoundStaticMesh->GetName(), LODIndex); - } - - // Store the new MeshDescription - FoundStaticMesh->CommitMeshDescription(LODIndex); - //Set the Imported version before calling the build - FoundStaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; - - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = GetLODSCreensizeForSplit(SplitGroupName); - if (screensize >= 0.0f) - { - // Only apply the LOD screensize if it's valid - SrcModel->ScreenSize = screensize; - //FoundStaticMesh->GetSourceModel(LodIndex).ScreenSize = screensize; - FoundStaticMesh->bAutoComputeLODScreenSize = false; - } - - // SET STATIC MESH GENERATION PARAM - // HANDLE COLLIDERS - // REMOVE OLD COLLIDERS - // CUSTOM BAKE NAME OVERRIDE - - // UPDATE UPROPERTY ATTRIBS - // Update property attributes on the SM - TArray PropertyAttributes; - if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( - HGPO.GeoId, HGPO.PartId, - true, - AllSplitFirstValidPrimIndex[SplitGroupName], - INDEX_NONE, - AllSplitFirstValidVertexIndex[SplitGroupName], - PropertyAttributes)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( - FoundStaticMesh, PropertyAttributes); - } - - TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - { - // cache the level path attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); - } - } - - TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - // cache the output name attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); - } - } - - TArray TileValues; - if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - { - // cache the tile attribute as a token on the output object - FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); - } - } - - TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames)) - { - if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); - } - } - - TArray BakeFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId)) - { - if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) - { - // cache the unreal_bake_folder attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); - } - } - - TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders)) - { - if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); - } - } - - // Notify that we created a new Static Mesh if needed - if(bNewStaticMeshCreated) - FAssetRegistryModule::AssetCreated(FoundStaticMesh); - - // Add the Static mesh to the output maps and the build map if we haven't already - if (FoundOutputObject) - { - FoundOutputObject->OutputObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = false; - FoundOutputObject->bIsImplicit = false; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - - StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Total Split time: %f seconds."), tick - split_tick); - } - } - - // Look if we only have colliders - // If we do, we'll allow attaching sockets to the collider meshes - bool bCollidersOnly = true; - for (auto& Current : StaticMeshToBuild) - { - EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) - { - bCollidersOnly = false; - break; - } - } - - FHoudiniScopedGlobalSilence ScopedGlobalSilence; - for (auto& Current : StaticMeshToBuild) - { - tick = FPlatformTime::Seconds(); - - UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) - continue; - - UBodySetup * BodySetup = SM->BodySetup; - if (!BodySetup) - { - SM->CreateBodySetup(); - BodySetup = SM->BodySetup; - } - - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); - - // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) - { - // Make sure rendering is done - so we are not changing data being used by collision drawing. - FlushRenderingCommands(); - - // Clean up old colliders from a previous cook - BodySetup->Modify(); - BodySetup->RemoveSimpleCollision(); - // Create new GUID - BodySetup->InvalidatePhysicsData(); - - FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; - FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); - if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) - { - BodySetup->AddCollisionFrom(*CurrentAggColl); - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; - } - - // Moved RefreshCollisionChange to after the SM->Build call - // RefreshCollisionChange(*SM); - // SM->bCustomizedCollision = true; - - // See if we need to enable collisions on the whole mesh - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - // Complex collider, enable collisions for this static mesh. - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - ApplyComplexColliderHelper( - MainStaticMesh, - SM, - SplitType, - bAssignedCustomCollisionMesh, - OutputObjects.Find(Current.Key)); - } - else - { - // TODO - // if the LODForCollision uproperty attribute is set, we need to activate complex collision - // on the static mesh for that lod to be picked up properly as a collider - if ( FHoudiniEngineUtils::HapiCheckAttributeExists( HGPO.GeoId, HGPO.PartId, - "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) - { - BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; - } - } - } - - // Add the Sockets to the StaticMesh - // We only add them to the main geo, or to the colliders if we only generate colliders - bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; - if (bAddSocket) - { - if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, HGPO.AllMeshSockets, true)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); - } - } - - if (MainStaticMesh) - { - UBodySetup* MainBodySetup = MainStaticMesh->BodySetup; - if (!IsValid(MainBodySetup)) - { - MainStaticMesh->CreateBodySetup(); - MainBodySetup = MainStaticMesh->BodySetup; - } - - check(MainBodySetup); - // Set the main static mesh to whatever the final CTF should be. - MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; - } - - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - // BUILD the Static Mesh - // bSilent doesnt add the Build Errors... - double build_start = FPlatformTime::Seconds(); - TArray SMBuildErrors; - SM->Build(true, &SMBuildErrors); - - if (bDoTiming) - { - tick = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - StaticMesh->Build() executed in %f seconds."), tick - build_start); - } - - // This replaces the call to RefreshCollision below, but without CreateNavCollision - // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, - // and can be expensive depending on the vert/poly count of the mesh - // RefreshCollisionChange(*SM); - { - for (FObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) - { - UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); - if (StaticMeshComponent->GetStaticMesh() == SM) - { - // it needs to recreate IF it already has been created - if (StaticMeshComponent->IsPhysicsStateCreated()) - { - StaticMeshComponent->RecreatePhysicsState(); - } - } - } - - FEditorSupportDelegates::RedrawAllViewports.Broadcast(); - } - - SM->GetOnMeshChanged().Broadcast(); - - UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - } - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); - } - } - - // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup - // Here as it has already been handled by the StaticMesh Build call - - double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() executed in %f seconds."), time_end - time_start); - - return true; -} - -bool -FHoudiniMeshTranslator::CreateHoudiniStaticMesh() -{ - // Time limit for processing - bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; - - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh")); - - const double time_start = FPlatformTime::Seconds(); - - // Start by updating the vertex list - if (!UpdatePartVertexList()) - return false; - - // Sort the split groups - SortSplitGroups(); - - // Handles the split groups found in the part - // and builds the corresponding faces and indices arrays - if (!UpdateSplitsFacesAndIndices()) - return true; - - // Resets the containers used for the raw data extraction. - ResetPartCache(); - - // Determine if there is "main" geo, if not we'll use the first LOD - // as main geo - bool bHasMainGeo = false; - for (auto& curSplit : AllSplitGroups) - { - if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) - { - bHasMainGeo = true; - break; - } - } - - // Update the part's material's IDS and info now - //UpdatePartFaceMaterialsIfNeeded(); - CreateNeededMaterials(); - - // Check if the materials were updated - bool bMaterialHasChanged = false; - for (const auto& MatInfo : PartUniqueMaterialInfos) - { - if (MatInfo.hasChanged) - { - bMaterialHasChanged = true; - break; - } - } - - // Map of Houdini Material IDs to Unreal Material Interface - TMap MapHoudiniMatIdToUnrealInterface; - // Map of Houdini Material Attributes to Unreal Material Interface - TMap MapHoudiniMatAttributesToUnrealInterface; - // Map of Unreal Material Interface to Unreal Material Index, per visible mesh - TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; - - // bool MeshMaterialsHaveBeenReset = false; - - double tick = FPlatformTime::Seconds(); - if(bDoTiming) - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); - - // Iterate through all detected split groups we care about and split geometry. - bool bMainGeoOrFirstLODFound = false; - for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Per Split")); - - // Get split group name - const FString& SplitGroupName = AllSplitGroups[SplitId]; - - // Get the current split type - EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); - if (SplitType == EHoudiniSplitType::Invalid) - { - // Invalid split, skip - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // We are only interested in the Normal/main geo and visible colliders - if (SplitType != EHoudiniSplitType::Normal && - SplitType != EHoudiniSplitType::LOD && - SplitType != EHoudiniSplitType::RenderedComplexCollider && - SplitType != EHoudiniSplitType::RenderedSimpleCollider && - SplitType != EHoudiniSplitType::RenderedUCXCollider) - { - continue; - } - - // We only use LOD if there is no Normal geo - if (SplitType == EHoudiniSplitType::Normal) - { - bMainGeoOrFirstLODFound = true; - HOUDINI_LOG_MESSAGE(TEXT("Found Normal geo for mesh.")); - } - else if (SplitType == EHoudiniSplitType::LOD) - { - if (bHasMainGeo) - { - HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since the mesh has Normal geo.")); - continue; - } - else if (bMainGeoOrFirstLODFound) - { - HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since we have already processed the first LOD.")); - continue; - } - else - { - bMainGeoOrFirstLODFound = true; - HOUDINI_LOG_MESSAGE(TEXT("Mesh does not have Normal geo, found first LOD.")); - } - } - - // Get the vertex indices for this group - TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; - - // Get valid count of vertex indices for this split. - const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; - - // Make sure we have a valid vertex count for this split - if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) - { - // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - - continue; - } - - // Get the output identifer for this split - FHoudiniOutputObjectIdentifier OutputObjectIdentifier( - HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); - OutputObjectIdentifier.PartName = HGPO.PartName; - OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName]; - OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - - // Try to find existing properties for this identifier - FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); - // Try to find an existing DM from a previous cook - UHoudiniStaticMesh* FoundStaticMesh = FindExistingHoudiniStaticMesh(OutputObjectIdentifier); - - // Flag whether or not we need to rebuild the mesh - bool bRebuildStaticMesh = false; - if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) - bRebuildStaticMesh = true; - - // TODO: Handle materials - if (!bRebuildStaticMesh && !bMaterialHasChanged) - { - // We can simply reuse the found static mesh - OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); - continue; - } - - bool bNewStaticMeshCreated = false; - if (!FoundStaticMesh) - { - // If we couldn't find a valid existing dynamic mesh, create a new one - FoundStaticMesh = CreateNewHoudiniStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - continue; - - bNewStaticMeshCreated = true; - } - - if (!FoundOutputObject) - { - // If we couldnt find a previous output object, create a new one - FHoudiniOutputObject NewOutputObject; - FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); - } - FoundOutputObject->bProxyIsCurrent = true; - - if (bDoTiming) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); - tick = FPlatformTime::Seconds(); - } - - if (bRebuildStaticMesh) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build/Rebuild UHoudiniStaticMesh")); - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES - //--------------------------------------------------------------------------------------------------------------------- - - // - // Because of the splits, we don't need to declare all the vertices in the Part, - // but only the one that are currently used by the split's faces. - // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. - // We also keep track of the needed vertices index to declare them easily afterwards. - // - - // IndicesMapper: - // Maps index values for all vertices in the Part: - // - Vertices unused by the split will be set to -1 - // - Used vertices will have their value set to the "NewIndex" - // So that IndicesMapper[ oldIndex ] => newIndex - TArray IndicesMapper; - IndicesMapper.Init(-1, SplitVertexList.Num()); - int32 CurrentMapperIndex = 0; - - // NeededVertices: - // Array containing the old index of the needed vertices for the current split - // NeededVertices[ newIndex ] => oldIndex - TArray< int32 > NeededVertices; - NeededVertices.Reserve(SplitVertexList.Num() / 3); - TArray< int32 > TriangleIndices; - TriangleIndices.Reserve(SplitVertexList.Num()); - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build IndicesMapper and NeededVertices")); - - int32 ValidVertexId = 0; - for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) - { - int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; - if (WedgeCheck == -1) - continue; - - int32 WedgeIndices[3] = - { - SplitVertexList[VertexIdx + 0], - SplitVertexList[VertexIdx + 1], - SplitVertexList[VertexIdx + 2] - }; - - // Ensure the indices are valid - if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) - || !IndicesMapper.IsValidIndex(WedgeIndices[1]) - || !IndicesMapper.IsValidIndex(WedgeIndices[2])) - { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // Converting Old (Part) Indices to New (Split) Indices: - for (int32 i = 0; i < 3; i++) - { - if (IndicesMapper[WedgeIndices[i]] < 0) - { - // This old index has not yet been "converted" to a new index - NeededVertices.Add(WedgeIndices[i]); - IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; - CurrentMapperIndex++; - } - - // Replace the old index with the new one - WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; - } - - // Flip wedge indices to fix the winding order. - TriangleIndices.Add(WedgeIndices[0]); - TriangleIndices.Add(WedgeIndices[2]); - TriangleIndices.Add(WedgeIndices[1]); - - ValidVertexId += 3; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's normal if needed - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray SplitNormals; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); - - // Check that the number of normal we retrieved is correct - int32 NormalCount = SplitNormals.Num() / 3; - if (NormalCount < 0 || NormalCount < NeededVertices.Num()) - { - // Ignore normals - NormalCount = 0; - HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENTS - //--------------------------------------------------------------------------------------------------------------------- - - TArray SplitTangentU; - TArray SplitTangentV; - int32 TangentUCount = 0; - int32 TangentVCount = 0; - // No need to read the tangents if we want unreal to recompute them after - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; - - bool bGenerateTangentsFromNormalAttribute = false; - if (bReadTangents) - { - // Extract this part's Tangents if needed - UpdatePartTangentsIfNeeded(); - - // Get the Tangents for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); - - // Get the binormals for this split - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); - - if ((SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0)) - bReadTangents = false; - - // We need to manually generate tangents if: - // - we have normals but dont have tangentu or tangentv attributes - // - we have not specified that we wanted unreal to generate them - bGenerateTangentsFromNormalAttribute = (NormalCount > 0) && !bReadTangents; - - // Check that the number of tangents read matches the number of normals - TangentUCount = SplitTangentU.Num() / 3; - TangentVCount = SplitTangentV.Num() / 3; - if (NormalCount > 0 && (TangentUCount != NormalCount || TangentVCount != NormalCount)) - { - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh: Generate tangents due to count mismatch (# U Tangents = %d; # V Tangents = %d; # Normals = %d)"), TangentUCount, TangentVCount, NormalCount); - bGenerateTangentsFromNormalAttribute = true; - bReadTangents = false; - } - - if (bGenerateTangentsFromNormalAttribute && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) - { - // No need to generate tangents if we want unreal to recompute them after - bGenerateTangentsFromNormalAttribute = false; - } - } - else - { - bGenerateTangentsFromNormalAttribute = (NormalCount > 0); - } - - //--------------------------------------------------------------------------------------------------------------------- - // VERTEX COLORS AND ALPHAS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's colors if needed - UpdatePartColorsIfNeeded(); - - // Get the colors values for this split - TArray SplitColors; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoColors, PartColors, SplitColors); - - // Extract this part's alpha values if needed - UpdatePartAlphasIfNeeded(); - - // Get the colors values for this split - TArray SplitAlphas; - FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); - - const int32 ColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; - const bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && ColorsCount > 0; - const bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == ColorsCount); - - //--------------------------------------------------------------------------------------------------------------------- - // UVS - //--------------------------------------------------------------------------------------------------------------------- - - // Extract this part's UV sets if needed - UpdatePartUVSetsIfNeeded(); - - // See if we need to transfer uv point attributes to vertex attributes. - int32 NumUVLayers = 0; - TArray> SplitUVSets; - SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); - for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) - { - FHoudiniMeshTranslator::TransferPartAttributesToSplit( - SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); - if (SplitUVSets[TexCoordIdx].Num() > 0) - { - NumUVLayers++; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL ATTRIBUTE OVERRIDES - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: These are actually per faces, not per vertices... - // Need to update!! - UpdatePartFaceMaterialOverridesIfNeeded(); - - // - // Initialize mesh - // - const int32 NumVertexPositions = NeededVertices.Num(); - const int32 NumTriangles = TriangleIndices.Num() / 3; - const bool bHasPerFaceMaterials = PartFaceMaterialOverrides.Num() > 0 || (PartUniqueMaterialIds.Num() > 0 && !bOnlyOneFaceMaterial); - - FoundStaticMesh->Initialize( - NumVertexPositions, - NumTriangles, - NumUVLayers, // NumUVLayers - 0, // InitialNumStaticMaterials - NormalCount > 0, // HasNormals - bReadTangents || bGenerateTangentsFromNormalAttribute, // HasTangents - bSplitColorValid, // HasColors - bHasPerFaceMaterials // HasPerFaceMaterials - ); - - //--------------------------------------------------------------------------------------------------------------------- - // POSITIONS - //--------------------------------------------------------------------------------------------------------------------- - UpdatePartPositionIfNeeded(); - - // - // Transfer vertex positions: - // - // Because of the split, we're only interested in the needed vertices. - // Instead of declaring all the Positions, we'll only declare the vertices - // needed by the current split. - // - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Vertex Positions")); - - for (int32 VertexPositionIdx = 0; VertexPositionIdx < NumVertexPositions; ++VertexPositionIdx) - //ParallelFor(NumVertexPositions, [&](uint32 VertexPositionIdx) - { - int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; - if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) - { - // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); - continue; - } - - // We need to swap Z and Y coordinate here, and convert from m to cm. - FoundStaticMesh->SetVertexPosition(VertexPositionIdx, FVector( - PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION, - PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION, - PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION - )); - }//); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACES / TRIS - // Now set Normals, UVs and Colors on mesh points and AttributeSet - //--------------------------------------------------------------------------------------------------------------------- - - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Triangle Indices & Per Vertex Instance Attribute Values")); - - // Now add the triangles to the mesh - for (int32 TriangleIdx = 0; TriangleIdx < NumTriangles; ++TriangleIdx) - // ParallelFor(NumTriangles, [&](uint32 TriangleIdx) - { - // TODO: add some additional intermediate consts for index calculations to make the indexing - // TODO: code a bit more readable - const int32 TriVertIdx0 = TriangleIdx * 3; - FoundStaticMesh->SetTriangleVertexIndices(TriangleIdx, FIntVector( - TriangleIndices[TriVertIdx0 + 0], - TriangleIndices[TriVertIdx0 + 1], - TriangleIndices[TriVertIdx0 + 2] - )); - - const int32 TriWindingIndex[3] = { 0, 2, 1 }; - // Normals and tangents (either getting tangents from attributes or generating tangents from the - // normals - if (NormalCount > 0 || bReadTangents) - { - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - const bool bHasNormal = (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)); - FVector Normal = FVector::ZeroVector; - if (bHasNormal) - { - // Flip Z and Y coordinate for normal, but don't scale - Normal.Set( - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], - SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] - ); - - FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); - } - - if (bReadTangents || bGenerateTangentsFromNormalAttribute) - { - FVector TangentU, TangentV; - if (bGenerateTangentsFromNormalAttribute) - { - if (bHasNormal) - { - // Generate the tangents if needed - Normal.FindBestAxisVectors(TangentU, TangentV); - - FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); - FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); - } - } - else - { - // Transfer the tangents from Houdini - TangentU.X = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; - TangentU.Y = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; - TangentU.Z = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; - - TangentU.X = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; - TangentU.Y = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; - TangentU.Z = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; - - FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); - FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); - } - } - } - } - - // Vertex Colors - if (bSplitColorValid && SplitColors.IsValidIndex(TriVertIdx0 * AttribInfoColors.tupleSize + 3 * AttribInfoColors.tupleSize - 1)) - { - FLinearColor VertexLinearColor; - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - VertexLinearColor.R = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 0], 0.0f, 1.0f); - VertexLinearColor.G = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 1], 0.0f, 1.0f); - VertexLinearColor.B = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 2], 0.0f, 1.0f); - - if (bSplitAlphaValid) - { - VertexLinearColor.A = FMath::Clamp(SplitAlphas[TriVertIdx0 + ElementIdx], 0.0f, 1.0f); - } - else if (AttribInfoColors.tupleSize >= 4) - { - VertexLinearColor.A = FMath::Clamp( - SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 3], 0.0f, 1.0f); - } - else - { - VertexLinearColor.A = 1.0f; - } - const FColor VertexColor = VertexLinearColor.ToFColor(false); - FoundStaticMesh->SetTriangleVertexColor(TriangleIdx, TriWindingIndex[ElementIdx], VertexColor); - } - } - - // UVs - if (NumUVLayers > 0) - { - // Dynamic mesh supports only 1 UV layer on the mesh it self. So we set the first layer - // on the mesh itself only, and we set all layers on the AttributeSet - for (int32 TexCoordIdx = 0; TexCoordIdx < NumUVLayers; ++TexCoordIdx) - { - const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; - if (SplitUVs.IsValidIndex(TriVertIdx0 * 2 + 3 * 2 - 1)) - { - for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) - { - const int32 UVIdx = TriVertIdx0 * 2 + ElementIdx * 2; - // We need to flip V coordinate when it's coming from HAPI. - const FVector2D UV(SplitUVs[UVIdx + 0], 1.0f - SplitUVs[UVIdx + 1]); - // Set the UV on the vertex instance in the UVLayer - FoundStaticMesh->SetTriangleVertexUV(TriangleIdx, TriWindingIndex[ElementIdx], TexCoordIdx, UV); - } - } - } - } - } - } - - FMeshBuildSettings BuildSettings; - UpdateMeshBuildSettings( - BuildSettings, - FoundStaticMesh->HasNormals(), - FoundStaticMesh->HasTangents(), - false); - // Compute normals if requested or needed/missing - if (BuildSettings.bRecomputeNormals) - { - FoundStaticMesh->CalculateNormals(BuildSettings.bComputeWeightedNormals); - } - - // Compute tangents if requested or needed/missing - if (BuildSettings.bRecomputeTangents) - { - FoundStaticMesh->CalculateTangents(BuildSettings.bComputeWeightedNormals); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIALS / FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - - // Get face indices for this split. - TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; - - // Fetch the FoundMesh's Static Materials array - TArray& FoundStaticMaterials = FoundStaticMesh->GetStaticMaterials(); - - // Clear the materials array of the mesh the first time we encounter it - if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) - { - FoundStaticMaterials.Empty(); - } - TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); - - // Process material overrides first - if (PartFaceMaterialOverrides.Num() > 0) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Material Overrides")); - - // Array used to avoid constantly attempting to load invalid materials - TArray InvalidMaterials; - - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) - continue; - - UMaterialInterface * MaterialInterface = nullptr; - int32 CurrentFaceMaterialIdx = 0; - const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (!MaterialInterface) - { - // Try to locate the corresponding material interface - - // Start by looking in our assignment map - FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - // Only try to load a material if it has a chance to be valid! - if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) - { - MaterialInterface = Cast( - StaticLoadObject(UMaterialInterface::StaticClass(), - nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); - - if (!MaterialInterface) - InvalidMaterials.Add(MaterialName); - } - - if (MaterialInterface) - { - // We managed to load the UE4 material - // Make sure this material is in the assignments before replacing it. - OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); - if (ReplacementMaterialInterface && *ReplacementMaterialInterface) - MaterialInterface = *ReplacementMaterialInterface; - - // Add this material to the map - MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); - } - else - { - // The Attribute Material and its replacement do not exist - // See if we can fallback to the Houdini material assigned on the face - - // Get the unreal material corresponding to this houdini one - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - if (!MaterialInterface) - { - // If everything fails, we'll use the default material - MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // We need to add this material to the map - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Map the Houdini ID to the unreal one - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - } - } - - if (MaterialInterface) - { - int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundFaceMaterialIdx) - { - // We already know what material index to use for that override - CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; - } - else - { - // Add the material to the Static mesh - CurrentFaceMaterialIdx = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); - } - // Update the Face Material on the mesh - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); - } - } - } - else if (PartUniqueMaterialIds.Num() > 0) - { - // The part has houdini materials - if (bOnlyOneFaceMaterial) - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Single Material")); - - // Use default Houdini material if no valid material is assigned to any of the faces. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // Get id of this single material. - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMaterials.Empty(); - FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - else - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Materials")); - - // We have multiple houdini materials - // Get default Houdini material. - UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); - - for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) - { - int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; - if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) - continue; - - // Get material id for this face. - HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; - - // See if we have already treated that material - UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); - UMaterialInterface* MaterialInterface = nullptr; - if (FoundMaterialInterface) - MaterialInterface = *FoundMaterialInterface; - - if (MaterialInterface) - { - int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); - if (FoundUnrealMatIndex) - { - // This material has been mapped already, just assign the mat index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); - continue; - } - } - else - { - MaterialInterface = Cast(DefaultMaterial); - - FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); - UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); - if (FoundMaterial) - MaterialInterface = *FoundMaterial; - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - // Map the houdini ID to the unreal one - MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); - } - - if (MaterialInterface) - { - // Add the material to the Static mesh - int32 UnrealMatIndex = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - - // Map the houdini ID to the unreal one - MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); - - // Update the face index - FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); - } - } - } - } - else - { - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Default Material")); - - // No materials were found, we need to use default Houdini material. - UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); - - // See if we have a replacement material and use it on the mesh instead - UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); - if (ReplacementMaterial && *ReplacementMaterial) - MaterialInterface = *ReplacementMaterial; - - FoundStaticMaterials.Empty(); - FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); - } - - //// Update property attributes on the mesh - //TArray PropertyAttributes; - //if (GetGenericPropertiesAttributes( - // HGPO.GeoId, HGPO.PartId, - // AllSplitFirstValidVertexIndex[SplitGroupName], - // AllSplitFirstValidPrimIndex[SplitGroupName], - // PropertyAttributes)) - //{ - // UpdateGenericPropertiesAttributes( - // FoundStaticMesh, PropertyAttributes); - //} - - FoundStaticMesh->Optimize(); - - // Check if the mesh is valid (check all the counts (vertex, triangles, vertex instances, UVs etc) but skip - // looping over each individual triangle vertex index to check if the value is valid). - const bool bSkipVertexIndicesCheck = true; - if (!FoundStaticMesh->IsValid(bSkipVertexIndicesCheck)) - { - HOUDINI_LOG_WARNING( - TEXT("[CreateHoudiniStaticMesh]: Invalid StaticMesh data for %s in cook output! Please check the log."), - *FoundStaticMesh->GetName()); - } - - //// Try to find the outer package so we can dirty it up - //if (FoundStaticMesh->GetOuter()) - //{ - // FoundStaticMesh->GetOuter()->MarkPackageDirty(); - //} - //else - //{ - // FoundStaticMesh->MarkPackageDirty(); - //} - UPackage *MeshPackage = FoundStaticMesh->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) - { - MeshPackage->MarkPackageDirty(); - - /* - // DPT: deactivated auto saving mesh/material package - // only dirty for now, as we'll save them when saving the world. - // Save the created/updated package - FEditorFileUtils::PromptForCheckoutAndSave({ MeshPackage }, false, false); - */ - } - - // Add the Proxy mesh to the output maps - if (FoundOutputObject) - { - FoundOutputObject->ProxyObject = FoundStaticMesh; - FoundOutputObject->bProxyIsCurrent = true; - OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); - } - } - - const double time_end = FPlatformTime::Seconds(); - HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() executed in %f seconds."), time_end - time_start); - - return true; -} - -void -FHoudiniMeshTranslator::ApplyComplexColliderHelper( - UStaticMesh* TargetStaticMesh, - UStaticMesh* ComplexStaticMesh, - const EHoudiniSplitType SplitType, - bool& bAssignedCustomCollisionMesh, - FHoudiniOutputObject* OutputObject) -{ - if (SplitType == EHoudiniSplitType::InvisibleComplexCollider && TargetStaticMesh) - { - if (!bAssignedCustomCollisionMesh) - { - bAssignedCustomCollisionMesh = true; - TargetStaticMesh->ComplexCollisionMesh = ComplexStaticMesh; - TargetStaticMesh->bCustomizedCollision = true; - bAssignedCustomCollisionMesh = true; - // We don't want an actor/component for this object in the scene, so flag it as an implicit output. - if (OutputObject) - { - OutputObject->bIsImplicit = true; - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("More than one (invisible) complex collision mesh found. Static Mesh assets only support a single complex collision mesh. Creating additional collision geo as Static Mesh Components.")); - } - } -} - - -bool -FHoudiniMeshTranslator::CreateNeededMaterials() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateNeededMaterials")); - - UpdatePartNeededMaterials(); - - TArray MaterialAndTexturePackages; - FHoudiniMaterialTranslator::CreateHoudiniMaterials( - HGPO.AssetId, - PackageParams, - PartUniqueMaterialIds, - PartUniqueMaterialInfos, - InputAssignmentMaterials, - AllOutputMaterials, - OutputAssignmentMaterials, - MaterialAndTexturePackages, - false, - bTreatExistingMaterialsAsUpToDate); - - /* - // Save the created packages if needed - // DPT: deactivated, only dirty for now, as we'll save them when saving the world. - if (MaterialAndTexturePackages.Num() > 0) - FEditorFileUtils::PromptForCheckoutAndSave(MaterialAndTexturePackages, true, false); - */ - - if (bMaterialOverrideNeedsCreateInstance && PartFaceMaterialOverrides.Num() > 0) - { - // Map containing unique face materials override attribute - // and their first valid prim index - // We create only one material instance per attribute - TMap UniqueFaceMaterialOverrides; - for (int FaceIdx = 0; FaceIdx < PartFaceMaterialOverrides.Num(); FaceIdx++) - { - FString MatOverrideAttr = PartFaceMaterialOverrides[FaceIdx]; - if (UniqueFaceMaterialOverrides.Contains(MatOverrideAttr)) - continue; - - // Add the material override and face index to the map - UniqueFaceMaterialOverrides.Add(MatOverrideAttr, FaceIdx); - } - - FHoudiniMaterialTranslator::CreateMaterialInstances( - HGPO, PackageParams, - UniqueFaceMaterialOverrides, MaterialAndTexturePackages, - InputAssignmentMaterials, OutputAssignmentMaterials, - false); - } - - return true; -} - -FString -FHoudiniMeshTranslator::GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType) -{ - FString MeshIdentifier = TEXT(""); - switch (InSplitType) - { - case EHoudiniSplitType::Normal: - case EHoudiniSplitType::LOD: - case EHoudiniSplitType::InvisibleUCXCollider: - case EHoudiniSplitType::InvisibleSimpleCollider: - // LODs and Invisible simple colliders use the main mesh - MeshIdentifier = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - break; - - case EHoudiniSplitType::InvisibleComplexCollider: - case EHoudiniSplitType::RenderedComplexCollider: - case EHoudiniSplitType::RenderedUCXCollider: - case EHoudiniSplitType::RenderedSimpleCollider: - // Rendered colliders or invisible complex colliders have their own static mesh - MeshIdentifier = InSplitName; - break; - - default: - break; - } - - return MeshIdentifier; -} - -UStaticMesh* -FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - // See if we already have an input object for that output identifier - FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); - UStaticMesh* FoundStaticMesh = nullptr; - if (FoundOutputObjectPtr) - { - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - FoundStaticMesh = nullptr; - } - - if (!FoundStaticMesh) - { - // No input object matching this identifier, see if we have created an output object that matches - FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); - if (!FoundOutputObjectPtr) - return nullptr; - - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - return nullptr; - } - - if (FoundStaticMesh) - { - UObject* OuterMost = FoundStaticMesh->GetOutermostObject(); - if (OuterMost->IsA()) - { - // The Outermost for this static mesh is a level - // This is likely a SM created by V1, and we should not reuse it. - // This will force the plugin to recreate a "proper" SM in the temp folder. - FoundStaticMesh->MarkPendingKill(); - FoundStaticMesh = nullptr; - } - } - - return FoundStaticMesh; -} - -UHoudiniStaticMesh* -FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - // See if we already have an input object for that output identifier - FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); - UHoudiniStaticMesh* FoundStaticMesh = nullptr; - if (FoundOutputObjectPtr) - { - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - FoundStaticMesh = nullptr; - } - - if (!FoundStaticMesh) - { - // No input object matching this identifier, see if we have created an output object that matches - FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); - if (!FoundOutputObjectPtr) - return nullptr; - - // Make sure it's a valid static mesh - FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) - return nullptr; - } - - return FoundStaticMesh; -} - -EHoudiniSplitType -FHoudiniMeshTranslator::GetSplitTypeFromSplitName(const FString& InSplitName) -{ - const FString MainGroup = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; - if (InSplitName.StartsWith(MainGroup, ESearchCase::IgnoreCase)) - return EHoudiniSplitType::Normal; - - const FString LODGroupPrefix = HAPI_UNREAL_GROUP_LOD_PREFIX; - if (InSplitName.StartsWith(LODGroupPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::LOD; - } - - const FString RenderedCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; - if (InSplitName.StartsWith(RenderedCollisionPrefix, ESearchCase::IgnoreCase)) - { - // Rendered colliders - // See if it is a simple/ucx/complex - const FString RenderedUCXCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX; - const FString RenderedSimpleCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(RenderedUCXCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::RenderedUCXCollider; - } - else if (InSplitName.StartsWith(RenderedSimpleCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::RenderedSimpleCollider; - } - else - { - return EHoudiniSplitType::RenderedComplexCollider; - } - } - - const FString InvisibleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(InvisibleCollisionPrefix, ESearchCase::IgnoreCase)) - { - // Invisible colliders - // See if it is a simple/ucx/complex - const FString InvisibleUCXCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX; - const FString InvisibleSimpleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX; - if (InSplitName.StartsWith(InvisibleUCXCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::InvisibleUCXCollider; - } - else if (InSplitName.StartsWith(InvisibleSimpleCollisionPrefix, ESearchCase::IgnoreCase)) - { - return EHoudiniSplitType::InvisibleSimpleCollider; - } - else - { - return EHoudiniSplitType::InvisibleComplexCollider; - } - } - - // ? - return EHoudiniSplitType::Invalid; - //return EHoudiniSplitType::Normal; -} - -bool -FHoudiniMeshTranslator::AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) -{ - // Get the vertex indices for the split group - TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; - - // We're only interested in unique vertices - TArray UniqueVertexIndexes; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - UniqueVertexIndexes.AddUnique(Index); - } - - // Extract the collision geo's vertices - TArray< FVector > VertexArray; - VertexArray.SetNum(UniqueVertexIndexes.Num()); - for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) - { - int32 VertexIndex = UniqueVertexIndexes[Idx]; - if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) - continue; - - VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - -#if WITH_EDITOR - // Do we want to create multiple convex hulls? - bool bDoMultiHullDecomp = false; - if (SplitGroupName.Contains(TEXT("ucx_multi"), ESearchCase::IgnoreCase)) - bDoMultiHullDecomp = true; - - uint32 HullCount = 8; - int32 MaxHullVerts = 16; - if (bDoMultiHullDecomp) - { - // TODO: - // Look for extra attributes for the decomposition parameters? (HullCount/MaxHullVerts) - } - - if (bDoMultiHullDecomp && (VertexArray.Num() >= 3 || UniqueVertexIndexes.Num() >= 3)) - { - // creating multiple convex hull collision - // ... this might take a while - - // We're only interested in the valid indices! - TArray Indices; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - Indices.Add(Index); - } - - // But we need all the positions as vertex - TArray< FVector > Vertices; - Vertices.SetNum(PartPositions.Num() / 3); - - for (int32 Idx = 0; Idx < Vertices.Num(); Idx++) - { - Vertices[Idx].X = PartPositions[Idx * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[Idx].Y = PartPositions[Idx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[Idx].Z = PartPositions[Idx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - // We are using Unreal's DecomposeMeshToHulls() - // We need a BodySetup so create a fake/transient one - UBodySetup* BodySetup = NewObject(); - - // Run actual util to do the work (if we have some valid input) - DecomposeMeshToHulls(BodySetup, Vertices, Indices, HullCount, MaxHullVerts); - - // If we succeed, return here - // If not, keep going and we'll try to do a single hull decomposition - if (BodySetup->AggGeom.ConvexElems.Num() > 0) - { - // Copy the convex elem to our aggregate - for (int32 n = 0; n < BodySetup->AggGeom.ConvexElems.Num(); n++) - AggCollisions.ConvexElems.Add(BodySetup->AggGeom.ConvexElems[n]); - - return true; - } - } -#endif - - // Creating a single Convex collision - FKConvexElem ConvexCollision; - ConvexCollision.VertexData = VertexArray; - ConvexCollision.UpdateElemBox(); - - AggCollisions.ConvexElems.Add(ConvexCollision); - - return true; -} - -bool -FHoudiniMeshTranslator::AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) -{ - // Get the vertex indices for the split group - TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; - - // We're only interested in unique vertices - TArray UniqueVertexIndexes; - for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) - { - int32 Index = SplitGroupVertexList[VertexIdx]; - if (!PartPositions.IsValidIndex(Index)) - continue; - - UniqueVertexIndexes.AddUnique(Index); - } - - // Extract the collision geo's vertices - TArray< FVector > VertexArray; - VertexArray.SetNum(UniqueVertexIndexes.Num()); - for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) - { - int32 VertexIndex = UniqueVertexIndexes[Idx]; - if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) - continue; - - VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - int32 NewColliders = 0; - if (SplitGroupName.Contains("Box")) - { - NewColliders = FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(VertexArray, AggCollisions); - } - else if (SplitGroupName.Contains("Sphere")) - { - NewColliders = FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(VertexArray, AggCollisions); - } - else if (SplitGroupName.Contains("Capsule")) - { - NewColliders = FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(VertexArray, AggCollisions); - } - else - { - // We need to see what type of collision the user wants - // by default, a kdop26 will be created - uint32 NumDirections = 26; - const FVector* Directions = KDopDir26; - if (SplitGroupName.Contains("kdop10X")) - { - NumDirections = 10; - Directions = KDopDir10X; - } - else if (SplitGroupName.Contains("kdop10Y")) - { - NumDirections = 10; - Directions = KDopDir10Y; - } - else if (SplitGroupName.Contains("kdop10Z")) - { - NumDirections = 10; - Directions = KDopDir10Z; - } - else if (SplitGroupName.Contains("kdop18")) - { - NumDirections = 18; - Directions = KDopDir18; - } - - // Converting the directions to a TArray - TArray DirArray; - DirArray.SetNum(NumDirections); - for (uint32 DirectionIndex = 0; DirectionIndex < NumDirections; DirectionIndex++) - { - DirArray[DirectionIndex] = Directions[DirectionIndex]; - } - - NewColliders = FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(VertexArray, DirArray, AggCollisions); - } - - return (NewColliders > 0); -} - -int32 -FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData) -{ - return FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, OutVertexData); -} - -/* -int32 -FHoudiniMeshTranslator::GetSplitNormals( - const TArray& InSplitVertexList, TArray& OutNormals) -{ - // Extract the normals - UpdatePartNormalsIfNeeded(); - - // Get the normals for this split - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutNormals.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; - OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; - OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; - - OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; - OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; - OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; - - OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; - OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; - OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; - } - - return WedgeCount; -} - -int32 -FHoudiniMeshTranslator::GetSplitUVs( - const TArray& InSplitVertexList, TArray& OutUVs) -{ - // Extract the normals - UpdatePartUVSetsIfNeeded(); - - // Get the normals for this split - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutNormals.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; - OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; - OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; - - OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; - OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; - OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; - - OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; - OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; - OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; - } - - return WedgeCount; -} - - -int32 -FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutData, - const float& ScaleFactor) -{ - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutData.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; - OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; - - OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; - OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; - - OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; - OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; - } - - return WedgeCount; -} - -int32 -FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutData, - const float& ScaleFactor) -{ - TArray VertexData; - int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( - InVertexList, InAttribInfo, InData, VertexData); - - // Convert the float data to Vectors and fix the winding order - OutData.SetNum(WedgeCount); - for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) - { - OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; - OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; - - OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; - OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; - - OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; - OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; - } - - return WedgeCount; -} -*/ - - -template -int32 FHoudiniMeshTranslator::TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::TransferPartAttributesToSplit")); - - if (!InAttribInfo.exists || InAttribInfo.tupleSize <= 0) - return 0; - - if (InData.Num() <= 0) - return 0; - - int32 ValidWedgeCount = 0; - - // Future optimization - see if we can do direct vertex transfer. - int32 WedgeCount = InVertexList.Num(); - int32 LastValidWedgeIdx = 0; - if (InAttribInfo.owner == HAPI_ATTROWNER_POINT) - { - // Point attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - int32 VertexIdx = InVertexList[WedgeIdx]; - if (VertexIdx < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[VertexIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_VERTEX) - { - // Vertex attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[WedgeIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_PRIM) - { - // Primitive attribute transfer - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 PrimIdx = WedgeIdx / 3; - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[PrimIdx * InAttribInfo.tupleSize + TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - else if (InAttribInfo.owner == HAPI_ATTROWNER_DETAIL) - { - // Detail attribute transfer - // We have one value to copy for all output split vertices - // if the attribute is a single value (not a tuple) - // then we can simply use the array init function instead of looping - if (InAttribInfo.tupleSize == 1) - { - OutVertexData.Init(InData[0], WedgeCount); - } - else - { - OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); - for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) - { - if (InVertexList[WedgeIdx] < 0) - { - // This is an index/wedge we are skipping due to split. - continue; - } - - int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; - for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) - { - OutVertexData[OutIdx + TupleIdx] = InData[TupleIdx]; - } - - // We are re-indexing wedges. - LastValidWedgeIdx++; - // Increment wedge count, since this is a valid wedge. - ValidWedgeCount++; - } - } - } - else - { - // Invalid attribute owner, shouldn't happen - check(false); - } - - OutVertexData.SetNumZeroed(ValidWedgeCount * InAttribInfo.tupleSize); - - return ValidWedgeCount; -} - -float -FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) -{ - // LOD Screensize - // default values has already been set, see if we have any attribute override for this - float screensize = -1.0f; - - // Start by looking at the lod_screensize primitive attribute - bool bAttribValid = false; - UpdatePartLODScreensizeIfNeeded(); - - if (PartLODScreensize.Num() > 0) - { - // use the "lod_screensize" primitive attribute - int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - if (PartLODScreensize.IsValidIndex(FirstValidPrimIndex)) - screensize = PartLODScreensize[FirstValidPrimIndex]; - } - - if (screensize < 0.0f) - { - // We couldn't find the primitive attribute, look for a "lodX_screensize" detail attribute - FString LODAttributeName = SplitGroupName + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX; - - TArray LODScreenSizes; - HAPI_AttributeInfo AttribInfoScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); - - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*LODAttributeName), - AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_DETAIL); - - if (AttribInfoScreenSize.exists && LODScreenSizes.Num() > 0) - { - screensize = LODScreenSizes[0]; - } - } - - if (screensize < 0.0f) - { - // finally, look for a potential uproperty style attribute - // aka, "unreal_uproperty_screensize" - TArray LODScreenSizes; - HAPI_AttributeInfo AttribInfoScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); - - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - HGPO.GeoId, HGPO.PartId, "unreal_uproperty_screensize", - AttribInfoScreenSize, LODScreenSizes); - - if (AttribInfoScreenSize.exists) - { - if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_DETAIL && LODScreenSizes.Num() > 0) - { - screensize = LODScreenSizes[0]; - } - else if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_PRIM) - { - int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; - if (LODScreenSizes.IsValidIndex(FirstValidPrimIndex)) - screensize = LODScreenSizes[FirstValidPrimIndex]; - } - } - } - - // Make sure the screensize is in percent, so if its above 1, divide by 100 - if (screensize > 1.0f) - screensize /= 100.0f; - - return screensize; -} - -int32 -FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - // Calculate bounding Box. - FVector Center, Extents; - FVector unitVec = FVector::OneVector;// bs->BuildScale3D; - CalcBoundingBox(InPositionArray, Center, Extents, unitVec); - - FKBoxElem BoxElem; - BoxElem.Center = Center; - BoxElem.X = Extents.X * 2.0f; - BoxElem.Y = Extents.Y * 2.0f; - BoxElem.Z = Extents.Z * 2.0f; - OutAggregateCollisions.BoxElems.Add(BoxElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FBox Box(ForceInit); - for (const FVector& CurPos : PositionArray) - { - Box += CurPos; - } - Box.GetCenterAndExtents(Center, Extents); -} - -int32 -FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FSphere bSphere, bSphere2, bestSphere; - FVector unitVec = FVector::OneVector; - - // Calculate bounding sphere. - CalcBoundingSphere(InPositionArray, bSphere, unitVec); - CalcBoundingSphere2(InPositionArray, bSphere2, unitVec); - - if (bSphere.W < bSphere2.W) - bestSphere = bSphere; - else - bestSphere = bSphere2; - - // Don't use if radius is zero. - if (bestSphere.W <= 0.f) - { - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Sphere collider.")); - return 0; - } - - FKSphereElem SphereElem; - SphereElem.Center = bestSphere.Center; - SphereElem.Radius = bestSphere.W; - OutAggregateCollisions.SphereElems.Add(SphereElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - if (PositionArray.Num() == 0) - return; - - FBox Box; - FVector MinIx[3]; - FVector MaxIx[3]; - - bool bFirstVertex = true; - for (const FVector& CurPosition : PositionArray) - { - FVector p = CurPosition * LimitVec; - if (bFirstVertex) - { - // First, find AABB, remembering furthest points in each dir. - Box.Min = p; - Box.Max = Box.Min; - - MinIx[0] = CurPosition; - MinIx[1] = CurPosition; - MinIx[2] = CurPosition; - - MaxIx[0] = CurPosition; - MaxIx[1] = CurPosition; - MaxIx[2] = CurPosition; - bFirstVertex = false; - continue; - } - - // X // - if (p.X < Box.Min.X) - { - Box.Min.X = p.X; - MinIx[0] = CurPosition; - } - else if (p.X > Box.Max.X) - { - Box.Max.X = p.X; - MaxIx[0] = CurPosition; - } - - // Y // - if (p.Y < Box.Min.Y) - { - Box.Min.Y = p.Y; - MinIx[1] = CurPosition; - } - else if (p.Y > Box.Max.Y) - { - Box.Max.Y = p.Y; - MaxIx[1] = CurPosition; - } - - // Z // - if (p.Z < Box.Min.Z) - { - Box.Min.Z = p.Z; - MinIx[2] = CurPosition; - } - else if (p.Z > Box.Max.Z) - { - Box.Max.Z = p.Z; - MaxIx[2] = CurPosition; - } - } - - const FVector Extremes[3] = { (MaxIx[0] - MinIx[0]) * LimitVec, - (MaxIx[1] - MinIx[1]) * LimitVec, - (MaxIx[2] - MinIx[2]) * LimitVec }; - - // Now find extreme points furthest apart, and initial center and radius of sphere. - float d2 = 0.f; - for (int32 i = 0; i < 3; i++) - { - const float tmpd2 = Extremes[i].SizeSquared(); - if (tmpd2 > d2) - { - d2 = tmpd2; - sphere.Center = (MinIx[i] + (0.5f * Extremes[i])) * LimitVec; - sphere.W = 0.f; - } - } - - const FVector Extents = FVector(Extremes[0].X, Extremes[1].Y, Extremes[2].Z); - - // radius and radius squared - float r = 0.5f * Extents.GetMax(); - float r2 = FMath::Square(r); - - // Now check each point lies within this sphere. If not - expand it a bit. - for (const FVector& curPos : PositionArray) - { - const FVector cToP = (curPos * LimitVec) - sphere.Center; - - const float pr2 = cToP.SizeSquared(); - - // If this point is outside our current bounding sphere's radius - if (pr2 > r2) - { - // ..expand radius just enough to include this point. - const float pr = FMath::Sqrt(pr2); - r = 0.5f * (r + pr); - r2 = FMath::Square(r); - - sphere.Center += ((pr - r) / pr * cToP); - } - } - - sphere.W = r; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FVector Center, Extents; - CalcBoundingBox(PositionArray, Center, Extents, LimitVec); - - sphere.Center = Center; - sphere.W = 0.0f; - - for (const FVector& curPos : PositionArray) - { - float Dist = FVector::DistSquared(curPos * LimitVec, sphere.Center); - if (Dist > sphere.W) - sphere.W = Dist; - } - sphere.W = FMath::Sqrt(sphere.W); -} - -int32 -FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - FSphere sphere; - float length; - FRotator rotation; - FVector unitVec = FVector::OneVector; - - // Calculate bounding sphyl. - CalcBoundingSphyl(InPositionArray, sphere, length, rotation, unitVec); - - // Dont use if radius is zero. - if (sphere.W <= 0.f) - { - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Capsule collider.")); - return 0; - } - - // If height is zero, then a sphere would be better (should we just create one instead?) - if (length <= 0.f) - { - length = SMALL_NUMBER; - } - - FKSphylElem SphylElem; - SphylElem.Center = sphere.Center; - SphylElem.Rotation = rotation; - SphylElem.Radius = sphere.W; - SphylElem.Length = length; - OutAggregateCollisions.SphylElems.Add(SphylElem); - - return 1; -} - -void -FHoudiniMeshTranslator::CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - if (PositionArray.Num() == 0) - return; - - FVector Center, Extents; - CalcBoundingBox(PositionArray, Center, Extents, LimitVec); - - // @todo sphere.Center could perhaps be adjusted to best fit if model is non-symmetric on it's longest axis - sphere.Center = Center; - - // Work out best axis aligned orientation (longest side) - float Extent = Extents.GetMax(); - if (Extent == Extents.X) - { - rotation = FRotator(90.f, 0.f, 0.f); - Extents.X = 0.0f; - } - else if (Extent == Extents.Y) - { - rotation = FRotator(0.f, 0.f, 90.f); - Extents.Y = 0.0f; - } - else - { - rotation = FRotator(0.f, 0.f, 0.f); - Extents.Z = 0.0f; - } - - // Cleared the largest axis above, remaining determines the radius - float r = Extents.GetMax(); - float r2 = FMath::Square(r); - - // Now check each point lies within this the radius. If not - expand it a bit. - for (const FVector& CurPos : PositionArray) - { - FVector cToP = (CurPos * LimitVec) - sphere.Center; - cToP = rotation.UnrotateVector(cToP); - - const float pr2 = cToP.SizeSquared2D(); // Ignore Z here... - - // If this point is outside our current bounding sphere's radius - if (pr2 > r2) - { - // ..expand radius just enough to include this point. - const float pr = FMath::Sqrt(pr2); - r = 0.5f * (r + pr); - r2 = FMath::Square(r); - } - } - - // The length is the longest side minus the radius. - float hl = FMath::Max(0.0f, Extent - r); - - // Now check each point lies within the length. If not - expand it a bit. - for (const FVector& CurPos : PositionArray) - { - FVector cToP = (CurPos * LimitVec) - sphere.Center; - cToP = rotation.UnrotateVector(cToP); - - // If this point is outside our current bounding sphyl's length - if (FMath::Abs(cToP.Z) > hl) - { - const bool bFlip = (cToP.Z < 0.f ? true : false); - const FVector cOrigin(0.f, 0.f, (bFlip ? -hl : hl)); - - const float pr2 = (cOrigin - cToP).SizeSquared(); - - // If this point is outside our current bounding sphyl's radius - if (pr2 > r2) - { - FVector cPoint; - FMath::SphereDistToLine(cOrigin, r, cToP, (bFlip ? FVector(0.f, 0.f, 1.f) : FVector(0.f, 0.f, -1.f)), cPoint); - - // Don't accept zero as a valid diff when we know it's outside the sphere (saves needless retest on further iterations of like points) - hl += FMath::Max(FMath::Abs(cToP.Z - cPoint.Z), 1.e-6f); - } - } - } - - sphere.W = r; - length = hl * 2.0f; -} - -int32 -FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions) -{ - // - // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp - // - - const float my_flt_max = 3.402823466e+38F; - - // Do k- specific stuff. - int32 kCount = Dirs.Num(); - - TArray maxDist; - maxDist.Init(-my_flt_max, kCount); - /* - for (int32 i = 0; i < kCount; i++) - maxDist.Add(my_flt_max); - */ - - // Construct temporary UModel for kdop creation. We keep no refs to it, so it can be GC'd. - auto TempModel = NewObject(); - TempModel->Initialize(nullptr, 1); - - // For each vertex, project along each kdop direction, to find the max in that direction. - for (int32 i = 0; i < InPositionArray.Num(); i++) - { - for (int32 j = 0; j < kCount; j++) - { - float dist = InPositionArray[i] | Dirs[j]; - maxDist[j] = FMath::Max(dist, maxDist[j]); - } - } - - // Inflate kdop to ensure it is no degenerate - const float MinSize = 0.1f; - for (int32 i = 0; i < kCount; i++) - { - maxDist[i] += MinSize; - } - - // Now we have the planes of the kdop, we work out the face polygons. - TArray planes; - for (int32 i = 0; i < kCount; i++) - planes.Add(FPlane(Dirs[i], maxDist[i])); - - for (int32 i = 0; i < planes.Num(); i++) - { - FPoly* Polygon = new(TempModel->Polys->Element) FPoly(); - FVector Base, AxisX, AxisY; - - Polygon->Init(); - Polygon->Normal = planes[i]; - Polygon->Normal.FindBestAxisVectors(AxisX, AxisY); - - Base = planes[i] * planes[i].W; - - new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); - new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); - - for (int32 j = 0; j < planes.Num(); j++) - { - if (i != j) - { - if (!Polygon->Split(-FVector(planes[j]), planes[j] * planes[j].W)) - { - Polygon->Vertices.Empty(); - break; - } - } - } - - if (Polygon->Vertices.Num() < 3) - { - // If poly resulted in no verts, remove from array - TempModel->Polys->Element.RemoveAt(TempModel->Polys->Element.Num() - 1); - } - else - { - // Other stuff... - Polygon->iLink = i; - Polygon->CalcNormal(1); - } - } - - if (TempModel->Polys->Element.Num() < 4) - { - TempModel = NULL; - HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple KDOP collider.")); - return 0; - } - - // Build bounding box. - TempModel->BuildBound(); - - // Build BSP for the brush. - FBSPOps::bspBuild(TempModel, FBSPOps::BSP_Good, 15, 70, 1, 0); - FBSPOps::bspRefresh(TempModel, 1); - FBSPOps::bspBuildBounds(TempModel); - - // Now, create a temporary BodySetup to build the colliders - UBodySetup* TempBS = NewObject(); - TempBS->CreateFromModel(TempModel, false); - - // Copy the convex elements back to our aggregate - int32 NewConvexElems = 0; - if (TempBS && TempBS->AggGeom.ConvexElems.Num() > 0) - { - for (const auto& CurConvexElem : TempBS->AggGeom.ConvexElems) - { - OutAggregateCollisions.ConvexElems.Add(CurConvexElem); - NewConvexElems++; - } - } - - return NewConvexElems; -} - - -void -FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO) -{ - PackageParams = InPackageParams; - - if (bUpdateHGPO) - { - PackageParams.ObjectId = HGPO.ObjectId; - PackageParams.GeoId = HGPO.ObjectId; - PackageParams.PartId = HGPO.ObjectId; - } -} - -bool -FHoudiniMeshTranslator::RemoveAndDestroyComponent(UObject* InComponent) -{ - if (!InComponent || InComponent->IsPendingKill()) - return false; - - USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (SceneComponent->GetOwner()) - SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); - - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComponent->UnregisterComponent(); - SceneComponent->DestroyComponent(); - - return true; - } - - return false; -} - -UMeshComponent* -FHoudiniMeshTranslator::CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf &InComponentType) -{ - // Create a new SMC as we couldn't find an existing one - USceneComponent* OuterSceneComponent = Cast(InOuterComponent); - UObject * Outer = nullptr; - if (OuterSceneComponent && !OuterSceneComponent->IsPendingKill()) - Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); - - UMeshComponent *MeshComponent = NewObject(Outer, InComponentType, NAME_None, RF_Transactional); - - // Initialize it - MeshComponent->SetVisibility(true); - //MeshComponent->SetMobility(Mobility); - - // TODO: - // Property propagation: set the new SMC's properties to the HAC's current settings - //CopyComponentPropertiesTo(MeshComponent); - - // Change the creation method so the component is listed in the details panels - MeshComponent->CreationMethod = EComponentCreationMethod::Instance; - - // Attach created static mesh component to our Houdini component. - MeshComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - MeshComponent->OnComponentCreated(); - MeshComponent->RegisterComponent(); - - return MeshComponent; -} - -bool -FHoudiniMeshTranslator::PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh) -{ - UStaticMesh *Mesh = Cast(InMesh); - if (Mesh) - { - InComponent->SetStaticMesh(Mesh); - - return true; - } - - return false; -} - -bool -FHoudiniMeshTranslator::PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh) -{ - UHoudiniStaticMesh *Mesh = Cast(InMesh); - if (Mesh) - { - InComponent->SetMesh(Mesh); - - return true; - } - - return false; -} - -UMeshComponent* -FHoudiniMeshTranslator::CreateOrUpdateMeshComponent( - const UHoudiniOutput *InOutput, - UObject *InOuterComponent, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TSubclassOf& InComponentType, - FHoudiniOutputObject& OutputObject, - FHoudiniGeoPartObject const *& OutFoundHGPO, - bool& bCreated) -{ - bCreated = false; - OutFoundHGPO = nullptr; - - // Find the HGPO that matches this mesh - for (auto& curHGPO : InOutput->HoudiniGeoPartObjects) - { - if (curHGPO.ObjectId != InOutputIdentifier.ObjectId - || curHGPO.GeoId != InOutputIdentifier.GeoId - || curHGPO.PartId != InOutputIdentifier.PartId) - { - continue; - } - - if (InOutputIdentifier.SplitIdentifier.Equals(HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION) - || curHGPO.SplitGroups.Contains(InOutputIdentifier.SplitIdentifier)) - { - OutFoundHGPO = &curHGPO; - } - } - - // No need to create a component for instanced meshes! - if (OutFoundHGPO && OutFoundHGPO->bIsInstanced) - return nullptr; - - bool bIsProxyComponent = InComponentType == UHoudiniStaticMeshComponent::StaticClass(); - - // See if we already have a component for that mesh - UMeshComponent* MeshComponent = nullptr; - if (bIsProxyComponent) - MeshComponent = Cast(OutputObject.ProxyComponent); - else - MeshComponent = Cast(OutputObject.OutputComponent); - - // If there is an existing component, but it is pending kill, then it was likely - // deleted by some other process, such as by the user in the editor, so don't use it - if (!MeshComponent || MeshComponent->IsPendingKill() || !MeshComponent->IsA(InComponentType)) - { - // If the component is not of type InComponentType, or the found component is pending kill, destroy - // the existing component (a new one is then created below) - RemoveAndDestroyComponent(MeshComponent); - MeshComponent = nullptr; - } - - if (!MeshComponent) - { - // Create a new SMC/HSMC as we couldn't find an existing one - MeshComponent = CreateMeshComponent(InOuterComponent, InComponentType); - - if (MeshComponent) - { - // Add to the output object - if (bIsProxyComponent) - OutputObject.ProxyComponent = MeshComponent; - else - OutputObject.OutputComponent = MeshComponent; - - bCreated = true; - } - } - - return MeshComponent; -} - -bool -FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, - TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors) -{ - if (!Socket || Socket->IsPendingKill() || !StaticMeshComponent || StaticMeshComponent->IsPendingKill()) - return false; - - // The actor to assign is stored is the socket's tag - FString ActorString = Socket->Tag; - if (ActorString.IsEmpty()) - return false; - - // The actor to assign are listed after a | - TArray ActorStringArray; - ActorString.ParseIntoArray(ActorStringArray, TEXT("|"), false); - - // The "real" Tag is the first - if (ActorStringArray.Num() > 0) - Socket->Tag = ActorStringArray[0]; - - // We just add a Tag, no Actor - if (ActorStringArray.Num() == 1) - return false; - - // Extract the parsed actor string to split it further - ActorString = ActorStringArray[1]; - - // Converting the string to a string array using delimiters - const TCHAR* Delims[] = { TEXT(","), TEXT(";") }; - ActorString.ParseIntoArray(ActorStringArray, Delims, 2); - - // And try to find the corresponding HoudiniAssetActor in the editor world - // to avoid finding "deleted" assets with the same name - //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); -#if WITH_EDITOR - UWorld* EditorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; - if (!EditorWorld || EditorWorld->IsPendingKill()) - return false; - - // Remove the previously created actors which were attached to this socket - { - for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - continue; - } - - if (CurActor->GetAttachParentSocketName() == Socket->SocketName) - { - HoudiniCreatedSocketActors.RemoveAt(Idx); - CurActor->Destroy(); - } - } - } - - // Detach the previous in level actors which was attached to this socket - { - for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) - { - AActor * CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) - { - HoudiniAttachedSocketActors.RemoveAt(Idx); - continue; - } - - if (CurActor->GetAttachParentSocketName() == Socket->SocketName) - { - CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - HoudiniAttachedSocketActors.RemoveAt(Idx); - } - } - } - - auto CreateDefaultActor = [EditorWorld, StaticMeshComponent, Socket, HoudiniCreatedSocketActors]() - { - AActor * CreatedDefaultActor = nullptr; - - UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) - { - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( - EditorWorld->GetCurrentLevel(), DefaultReferenceSM, false, RF_Transactional, nullptr); - - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load default mesh.")); - } - else - { - - // Set the default mesh actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : NewActors[0]->GetComponents()) - { - UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) - CurSMC->SetMobility(OutputSMCMobility); - } - - // Set the default mesh actor hidden in game. - NewActors[0]->SetActorHiddenInGame(true); - - Socket->AttachActor(NewActors[0], StaticMeshComponent); - CreatedDefaultActor = NewActors[0]; - //HoudiniCreatedSocketActors.Add(NewActors[0]); - } - } - else - { - HOUDINI_LOG_WARNING( - TEXT("Failed to load default mesh.")); - } - - return CreatedDefaultActor; - }; - - // If nothing was specified, we're done - if (ActorStringArray.Num() <= 0) - return true; - - bool bUseDefaultActor = true; - // Get from the Houdini runtime setting if use default object when the reference is invalid - // true by default if fail to access HoudiniRuntimeSettings - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bUseDefaultActor = HoudiniRuntimeSettings->bShowDefaultMesh; - } - - /* - // !! Only use the default mesh if we failed to find/spawn the actor to attach - // not if we didn't specify any actor to attach! - if (ActorStringArray.Num() <= 0) - { - if (!bUseDefaultActor) - return true; - - HOUDINI_LOG_WARNING( - TEXT("Output static mesh: Socket '%s' actor is not specified. Spawn a default mesh (hidden in game)."), *(Socket->GetName())); - - AActor * DefaultActor = CreateDefaultActor(); - if (DefaultActor && !DefaultActor->IsPendingKill()) - HoudiniCreatedSocketActors.Add(DefaultActor); - - return true; - } - */ - - // try to find the actor in level first - for (TActorIterator ActorItr(EditorWorld); ActorItr; ++ActorItr) - { - // Same as with the Object Iterator, access the subclass instance with the * or -> operators. - AActor *Actor = *ActorItr; - if (!Actor || Actor->IsPendingKillOrUnreachable()) - continue; - - for (int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++) - { - if (Actor->GetName() != ActorStringArray[StringIdx] - && Actor->GetActorLabel() != ActorStringArray[StringIdx]) - continue; - - // Set the actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : Actor->GetComponents()) - { - UStaticMeshComponent * SMC = Cast(CurComp); - if (SMC && !SMC->IsPendingKill()) - SMC->SetMobility(OutputSMCMobility); - } - - Socket->AttachActor(Actor, StaticMeshComponent); - HoudiniAttachedSocketActors.Add(Actor); - - // Remove the string if the actor is found in the editor level - ActorStringArray.RemoveAt(StringIdx); - break; - } - } - - bool bSuccess = true; - // If some of the actors are not found in the level, try to find them in the content browser. Spawn one if existed - for (int32 Idx = ActorStringArray.Num() - 1; Idx>= 0; --Idx) - { - UObject * Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *ActorStringArray[Idx]); - if (!Obj || Obj->IsPendingKill()) - { - bSuccess = false; - continue; - } - - // Spawn a new actor with the found object - TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( - EditorWorld->GetCurrentLevel(), Obj, false, RF_Transactional, nullptr); - - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) - { - bSuccess = false; - continue; - } - - // Set the new actor components mobility to the same as output SMC's - EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; - for (auto & CurComp : NewActors[0]->GetComponents()) - { - UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) - CurSMC->SetMobility(OutputSMCMobility); - } - - Socket->AttachActor(NewActors[0], StaticMeshComponent); - HoudiniCreatedSocketActors.Add(NewActors[0]); - - ActorStringArray.RemoveAt(Idx); - } - - // Failed to find actors in both level and content browser - // Spawn default actors if enabled - if (bUseDefaultActor) - { - for (int32 Idx = ActorStringArray.Num() - 1; Idx >= 0; --Idx) - { - HOUDINI_LOG_WARNING( - TEXT("Output static mesh: Failed to attach '%s' to socket '%s', spawn a default mesh (hidden in game)."), *(ActorStringArray[Idx]), *(Socket->GetName())); - - // If failed to load this object, spawn a default mesh - AActor * CurDefaultActor = CreateDefaultActor(); - if (CurDefaultActor && !CurDefaultActor->IsPendingKill()) - HoudiniCreatedSocketActors.Add(CurDefaultActor); - } - } - - if (ActorStringArray.Num() > 0) - return false; -#endif - - return bSuccess; -} - -void -FHoudiniMeshTranslator::UpdateMeshBuildSettings( - FMeshBuildSettings& OutMeshBuildSettings, - const bool& bHasNormals, - const bool& bHasTangents, - const bool& bHasLightmapUVSet) -{ - // Use the values provided to the translator - OutMeshBuildSettings = StaticMeshBuildSettings; - - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - - // Recomputing normals. - EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag : HRSRF_OnlyIfMissing; - if(RecomputeNormalFlag == HRSRF_OnlyIfMissing) - OutMeshBuildSettings.bRecomputeNormals = !bHasNormals; - - // Recomputing tangents. - EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; - if (RecomputeTangentFlag == HRSRF_OnlyIfMissing) - OutMeshBuildSettings.bRecomputeTangents = !bHasTangents; - - // Lightmap UV generation. - EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; - if (GenerateLightmapUVFlag == HRSRF_OnlyIfMissing) - OutMeshBuildSettings.bGenerateLightmapUVs = !bHasLightmapUVSet; -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniMeshTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniOutput.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniMaterialTranslator.h" +#include "HoudiniAssetActor.h" + +#include "HoudiniStaticMesh.h" +#include "HoudiniStaticMeshComponent.h" +#include "Engine/StaticMeshSocket.h" + +#include "PhysicsEngine/BodySetup.h" +#include "Engine/StaticMesh.h" +#include "PackageTools.h" +#include "RawMesh.h" +#include "Materials/MaterialInterface.h" +#include "Materials/Material.h" +#include "MeshDescription.h" +#include "StaticMeshAttributes.h" +#include "MeshDescriptionOperations.h" + +#include "BSPOps.h" +#include "Model.h" +#include "Engine/Polys.h" +#include "AssetRegistryModule.h" +#include "Interfaces/ITargetPlatform.h" +#include "Interfaces/ITargetPlatformManagerModule.h" +#include "AI/Navigation/NavCollisionBase.h" +#include "ObjectTools.h" + +// #include "Async/ParallelFor.h" + +#include "ProfilingDebugging/CpuProfilerTrace.h" + +#include "EditorSupportDelegates.h" + +#if WITH_EDITOR + #include "UnrealEd/Private/ConvexDecompTool.h" + #include "Editor/UnrealEd/Private/GeomFitUtils.h" + #include "LevelEditorViewport.h" + #include "FileHelpers.h" +#endif + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +static TAutoConsoleVariable CVarHoudiniEngineMeshBuildTimer( + TEXT("HoudiniEngine.MeshBuildTimer"), + 0.0, + TEXT("When enabled, the plugin will output timings during the Mesh creation.\n") +); + +// +bool +FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + UHoudiniOutput* InOutput, + const FHoudiniPackageParams& InPackageParams, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, + const TMap& InAllOutputMaterials, + UObject* InOuterComponent, + bool bInTreatExistingMaterialsAsUpToDate, + bool bInDestroyProxies) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InPackageParams.OuterPackage || InPackageParams.OuterPackage->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + TMap NewOutputObjects; + TMap OldOutputObjects = InOutput->GetOutputObjects(); + TMap& AssignementMaterials = InOutput->GetAssignementMaterials(); + TMap& ReplacementMaterials = InOutput->GetReplacementMaterials(); + + bool InForceRebuild = false; + if (InOutput->HasAnyCurrentProxy() && InStaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh) + { + // Make sure we're not preventing refinement + InForceRebuild = true; + } + + // Iterate on all of the output's HGPO, creating meshes as we go + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->HoudiniGeoPartObjects) + { + // Not a mesh, skip + if (CurHGPO.Type != EHoudiniPartType::Mesh) + continue; + + // See if we have some uproperty attributes to update on + // the outer component (in most case, the HAC) + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + CurHGPO.GeoId, CurHGPO.PartId, + true, 0, 0, 0, + PropertyAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + InOuterComponent, PropertyAttributes); + } + + CreateStaticMeshFromHoudiniGeoPartObject( + CurHGPO, + InPackageParams, + OldOutputObjects, + NewOutputObjects, + AssignementMaterials, + ReplacementMaterials, + InAllOutputMaterials, + InForceRebuild, + InStaticMeshMethod, + InSMGenerationProperties, + InMeshBuildSettings, + bInTreatExistingMaterialsAsUpToDate); + } + + return FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + InOutput, + InOuterComponent, + NewOutputObjects, + bInDestroyProxies); +} + +bool +FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + UHoudiniOutput* InOutput, + UObject* InOuterComponent, + TMap& InNewOutputObjects, + bool bInDestroyProxies, + bool bInApplyGenericProperties) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + TMap OldOutputObjects = InOutput->GetOutputObjects(); + + // Remove Static Meshes and their components from the old map + // to avoid their deletion if new proxies were created for them + for (auto& NewOutputObj : InNewOutputObjects) + { + FHoudiniOutputObjectIdentifier OutputIdentifier = NewOutputObj.Key; + + // See if we already had that pair in the old map of static mesh + FHoudiniOutputObject* FoundOldOutputObj = OldOutputObjects.Find(NewOutputObj.Key); + if (!FoundOldOutputObj) + continue; + + UObject* NewStaticMesh = NewOutputObj.Value.OutputObject; + UObject* NewProxyMesh = NewOutputObj.Value.ProxyObject; + + UObject* OldStaticMesh = FoundOldOutputObj->OutputObject; + if (OldStaticMesh && !OldStaticMesh->IsPendingKill()) + { + // If a proxy was created for an existing static mesh, keep the existing static + // mesh (will be hidden) + if (NewProxyMesh && NewOutputObj.Value.bProxyIsCurrent) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + else if (NewStaticMesh && NewStaticMesh == OldStaticMesh) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + + UObject* OldProxyMesh = FoundOldOutputObj->ProxyObject; + if (OldProxyMesh && !OldProxyMesh->IsPendingKill()) + { + // If a new static mesh was created for a proxy, keep the proxy (will be hidden) + // ... unless we want to explicitly destroy proxies + if (NewStaticMesh && !bInDestroyProxies) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + else if (NewProxyMesh && (NewProxyMesh == OldProxyMesh)) + { + // Remove it from the old map to avoid its destruction + OldOutputObjects.Remove(OutputIdentifier); + } + } + } + + // The old map now only contains unused/stale Meshes/Components, delete them + for (auto& OldPair : OldOutputObjects) + { + // Get the old Identifier / StaticMesh + FHoudiniOutputObjectIdentifier& OutputIdentifier = OldPair.Key; + FHoudiniOutputObject& OldOutputObject = OldPair.Value; + + // Remove the old component from the map + RemoveAndDestroyComponent(OldOutputObject.OutputComponent); + OldOutputObject.OutputComponent = nullptr; + // Remove the old proxy component from the map + RemoveAndDestroyComponent(OldOutputObject.ProxyComponent); + OldOutputObject.ProxyComponent = nullptr; + + if (OldOutputObject.OutputObject && !OldOutputObject.OutputObject->IsPendingKill()) + { + OldOutputObject.OutputObject->MarkPendingKill(); + } + + if (OldOutputObject.ProxyObject && !OldOutputObject.ProxyObject->IsPendingKill()) + { + OldOutputObject.ProxyObject->MarkPendingKill(); + } + } + OldOutputObjects.Empty(); + + /* + // Remove any stale components, these are components with OutputIdentifiers that are not + // in NewOutputObjects. This seems to happen mostly with the first or second cook after a + // "Rebuild Asset" + if (OutputComponents.Num() > 0 || OutputProxyComponents.Num() > 0) + { + TArray> StaleComponents; + const uint32 MaxNumStale = FMath::Max(OutputComponents.Num(), OutputProxyComponents.Num()); + StaleComponents.Reserve(MaxNumStale); + for (auto& ComponentPair : OutputComponents) + { + if (!NewOutputObjects.Contains(ComponentPair.Key) && !OldOutputObjectsReplacedByProxy.Contains(ComponentPair.Key)) + { + StaleComponents.Add(ComponentPair); + } + } + for (auto& ComponentPair : StaleComponents) + { + RemoveAndDestroyComponent(ComponentPair.Key, OutputComponents); + } + StaleComponents.Empty(MaxNumStale); + + for (auto& ComponentPair : OutputProxyComponents) + { + if (!NewOutputProxyObjects.Contains(ComponentPair.Key) && !OldOutputProxyObjectsReplacedByStaticMesh.Contains(ComponentPair.Key)) + { + StaleComponents.Add(ComponentPair); + } + } + for (auto& ComponentPair : StaleComponents) + { + RemoveAndDestroyComponent(ComponentPair.Key, OutputProxyComponents); + } + StaleComponents.Empty(); + } + */ + + // Now create/update the new static mesh components + for (auto& NewPair : InNewOutputObjects) + { + // Get the old Identifier / StaticMesh + const FHoudiniOutputObjectIdentifier& OutputIdentifier = NewPair.Key; + FHoudiniOutputObject& OutputObject = NewPair.Value; + + if (OutputObject.bIsImplicit) + { + // This output is implicit and shouldn't have a representative component/proxy in the scene + // Remove the old component from the map + if (OutputObject.OutputComponent) + { + RemoveAndDestroyComponent(OutputObject.OutputComponent); + OutputObject.OutputComponent = nullptr; + } + + // Remove the old proxy component from the map + RemoveAndDestroyComponent(OutputObject.ProxyComponent); + OutputObject.ProxyComponent = nullptr; + + continue; // Skip any proxy / component creation below + } + + // Check if we should create a Proxy/SMC + if (OutputObject.bProxyIsCurrent) + { + UObject *Mesh = OutputObject.ProxyObject; + if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + { + HOUDINI_LOG_ERROR(TEXT("Proxy Mesh is invalid (wrong type or pending kill)...")); + continue; + } + + // Create or update a new proxy component + TSubclassOf ComponentType = UHoudiniStaticMeshComponent::StaticClass(); + const FHoudiniGeoPartObject *FoundHGPO = nullptr; + bool bCreated = false; + UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); + if (MeshComponent) + { + UHoudiniStaticMeshComponent *HSMC = Cast(MeshComponent); + + if (bCreated) + { + PostCreateHoudiniStaticMeshComponent(HSMC, Mesh); + } + else if (HSMC && !HSMC->IsPendingKill() && HSMC->GetMesh() != Mesh) + { + // We need to reassign the HSM to the component + UHoudiniStaticMesh* HSM = Cast(Mesh); + HSMC->SetMesh(HSM); + } + + UpdateMeshComponent( + MeshComponent, + OutputIdentifier, + FoundHGPO, + InOutput->HoudiniCreatedSocketActors, + InOutput->HoudiniAttachedSocketActors, + bInApplyGenericProperties); + + if (!bCreated) + { + // For proxy meshes: notify that the mesh has been updated + HSMC->NotifyMeshUpdated(); + HSMC->SetHoudiniIconVisible(true); + } + } + + // Now, ensure that meshes replaced by proxies are still kept but hidden + USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); + if (SceneComponent) + { + SceneComponent->SetVisibility(false); + SceneComponent->SetHiddenInGame(true); + } + + // If the proxy mesh we just created is templated, hide it in game + if (FoundHGPO->bIsTemplated) + { + MeshComponent->SetHiddenInGame(true); + } + } + else + { + // Create a new SMC if needed + UObject* Mesh = OutputObject.OutputObject; + if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + { + HOUDINI_LOG_ERROR(TEXT("Mesh is invalid (wrong type or pending kill)...")); + continue; + } + + TSubclassOf ComponentType = UStaticMeshComponent::StaticClass(); + const FHoudiniGeoPartObject *FoundHGPO = nullptr; + bool bCreated = false; + UMeshComponent *MeshComponent = CreateOrUpdateMeshComponent(InOutput, InOuterComponent, OutputIdentifier, ComponentType, OutputObject, FoundHGPO, bCreated); + if (MeshComponent) + { + if (bCreated) + { + PostCreateStaticMeshComponent(Cast(MeshComponent), Mesh); + } + UpdateMeshComponent( + MeshComponent, + OutputIdentifier, + FoundHGPO, + InOutput->HoudiniCreatedSocketActors, + InOutput->HoudiniAttachedSocketActors, + bInApplyGenericProperties); + } + + // Now, ensure that proxies replaced by meshes are still kept but hidden + UHoudiniStaticMeshComponent *HSMC = Cast(OutputObject.ProxyComponent); + if (HSMC) + { + HSMC->SetVisibility(false); + HSMC->SetHiddenInGame(true); + HSMC->SetHoudiniIconVisible(false); + } + + // If the mesh we just created is templated, hide it in game + if (FoundHGPO->bIsTemplated) + { + MeshComponent->SetHiddenInGame(true); + } + } + } + + // Assign the new output objects to the output + InOutput->SetOutputObjects(InNewOutputObjects); + + return true; +} + +void +FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, + const FHoudiniGeoPartObject *InHGPO, TArray &HoudiniCreatedSocketActors, TArray &HoudiniAttachedSocketActors, + bool bInApplyGenericProperties) +{ + // Update collision/visibility + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(InOutputIdentifier.SplitIdentifier); + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider) + { + // Invisible complex collider should not be seen + InMeshComponent->SetVisibility(false); + InMeshComponent->SetHiddenInGame(true); + InMeshComponent->SetCollisionProfileName(FName(TEXT("InvisibleWall"))); + InMeshComponent->SetCastShadow(false); + } + else + { + // Update visiblity + bool bVisible = InHGPO ? InHGPO->bIsVisible : true; + InMeshComponent->SetVisibility(bVisible); + InMeshComponent->SetHiddenInGame(!bVisible); + } + + // TODO: + // Update navmesh? + + // Transform the component by transformation provided by HAPI. + InMeshComponent->SetRelativeTransform(InHGPO ? InHGPO->TransformMatrix : FTransform::Identity); + + // If the static mesh had sockets, we can assign the desired actor to them now + UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); + UStaticMesh * StaticMesh = nullptr; + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + StaticMesh = StaticMeshComponent->GetStaticMesh(); + + if (StaticMesh && !StaticMesh->IsPendingKill()) + { + 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; + + AddActorsToMeshSocket(StaticMesh->Sockets[nSocket], StaticMeshComponent, HoudiniCreatedSocketActors, HoudiniAttachedSocketActors); + } + + // Iterate all remaining created socket actors, destroy the ones that are not assigned to socket after re-cook + { + for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniCreatedSocketActors[Idx]; + + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + continue; + } + + bool bFoundSocket = false; + for (auto & CurSocket : StaticMesh->Sockets) + { + if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) + { + bFoundSocket = true; + break; + } + } + // cur actor's attaching socket is found, skip + if (bFoundSocket) + continue; + + // Destroy the previous created socket actor if not found + HoudiniCreatedSocketActors.RemoveAt(Idx); + CurActor->Destroy(); + } + } + + // Detach the in level actors which is not attached to any socket now + { + for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor* CurActor = HoudiniAttachedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniAttachedSocketActors.RemoveAt(Idx); + continue; + } + + bool bFoundSocket = false; + for (auto & CurSocket : StaticMesh->Sockets) + { + if (CurSocket->SocketName == CurActor->GetAttachParentSocketName()) + { + bFoundSocket = true; + break; + } + } + + if (bFoundSocket) + continue; + + // If the attached socket name is not found in current socket, detach it and remove from the array + CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + HoudiniAttachedSocketActors.RemoveAt(Idx); + } + } + + } + + if (bInApplyGenericProperties) + { + // Clear the component tags as generic properties only add them + InMeshComponent->ComponentTags.Empty(); + // Update the property attributes on the component + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + InOutputIdentifier.GeoId, InOutputIdentifier.PartId, + true, + InOutputIdentifier.PrimitiveIndex, + INDEX_NONE, + InOutputIdentifier.PointIndex, + PropertyAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(InMeshComponent, PropertyAttributes); + } + } +} + +bool +FHoudiniMeshTranslator::CreateStaticMeshFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& InOutputObjects, + TMap& OutOutputObjects, + TMap& AssignmentMaterialMap, + TMap& ReplacementMaterialMap, + const TMap& InAllOutputMaterials, + const bool& InForceRebuild, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InSMBuildSettings, + bool bInTreatExistingMaterialsAsUpToDate) +{ + // If we're not forcing the rebuild + // No need to recreate something that hasn't changed + if (!InForceRebuild && !InHGPO.bHasGeoChanged && !InHGPO.bHasPartChanged && InOutputObjects.Num() > 0) + { + // Simply reuse the existing meshes + OutOutputObjects = InOutputObjects; + return true; + } + + FHoudiniMeshTranslator CurrentTranslator; + CurrentTranslator.ForceRebuild = InForceRebuild; + CurrentTranslator.SetHoudiniGeoPartObject(InHGPO); + CurrentTranslator.SetInputObjects(InOutputObjects); + CurrentTranslator.SetOutputObjects(OutOutputObjects); + CurrentTranslator.SetInputAssignmentMaterials(AssignmentMaterialMap); + CurrentTranslator.SetAllOutputMaterials(InAllOutputMaterials); + CurrentTranslator.SetReplacementMaterials(ReplacementMaterialMap); + CurrentTranslator.SetPackageParams(InPackageParams, true); + CurrentTranslator.SetTreatExistingMaterialsAsUpToDate(bInTreatExistingMaterialsAsUpToDate); + CurrentTranslator.SetStaticMeshGenerationProperties(InSMGenerationProperties); + CurrentTranslator.SetStaticMeshBuildSettings(InSMBuildSettings); + + // TODO: Fetch from settings/HAC + CurrentTranslator.DefaultMeshSmoothing = 1; + if (false) + CurrentTranslator.DefaultMeshSmoothing = 0; + + // TODO: mechanism to determine when to use dynamic mesh for fast updates, and when to switch to + // baking the full static mesh + switch (InStaticMeshMethod) + { + case EHoudiniStaticMeshMethod::RawMesh: + CurrentTranslator.CreateStaticMesh_RawMesh(); + break; + case EHoudiniStaticMeshMethod::FMeshDescription: + CurrentTranslator.CreateStaticMesh_MeshDescription(); + break; + case EHoudiniStaticMeshMethod::UHoudiniStaticMesh: + CurrentTranslator.CreateHoudiniStaticMesh(); + break; + } + + // Copy the output objects/materials + OutOutputObjects = CurrentTranslator.OutputObjects; + AssignmentMaterialMap = CurrentTranslator.OutputAssignmentMaterials; + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartVertexList() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartVertexList")); + + if (HGPO.PartInfo.VertexCount <= 0) + return false; + + // Get the vertex List + PartVertexList.SetNumUninitialized(HGPO.PartInfo.VertexCount); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, &PartVertexList[0], 0, HGPO.PartInfo.VertexCount)) + { + // Error getting the vertex list. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + + return false; + } + + return true; +} + +void +FHoudiniMeshTranslator::SortSplitGroups() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::SortSplitGroups")); + + // Sort the splits in the order that we want to process them: + // Simple/Convex invisible colliders should be treated first as they will need to be attached to the visible meshes + TArray First; + + // The main geo and its LODs should be created after. + TArray Main; + TArray LODs; + + // Finally, visible colliders and invisible complex colliders as they need their own static mesh + TArray Last; + + for (auto& curSplit : HGPO.SplitGroups) + { + EHoudiniSplitType curSplitType = GetSplitTypeFromSplitName(curSplit); + switch (curSplitType) + { + case EHoudiniSplitType::InvisibleSimpleCollider: + case EHoudiniSplitType::InvisibleUCXCollider: + First.Add(curSplit); + break; + + case EHoudiniSplitType::Normal: + Main.Add(curSplit); + break; + + case EHoudiniSplitType::LOD: + LODs.Add(curSplit); + break; + + case EHoudiniSplitType::RenderedSimpleCollider: + case EHoudiniSplitType::RenderedUCXCollider: + case EHoudiniSplitType::RenderedComplexCollider: + case EHoudiniSplitType::InvisibleComplexCollider: + Last.Add(curSplit); + break; + } + } + + // Make sure LODs are order by name + LODs.Sort(); + + // Copy the split names in order + AllSplitGroups.Empty(); + for (auto& splitName : First) + AllSplitGroups.Add(splitName); + + for (auto& splitName : Main) + AllSplitGroups.Add(splitName); + + for (auto& splitName : LODs) + AllSplitGroups.Add(splitName); + + for (auto& splitName : Last) + AllSplitGroups.Add(splitName); +} + +bool +FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdateSplitsFacesAndIndices")); + + // Reset the splits faces/indices arrays + AllSplitVertexLists.Empty(); + AllSplitVertexCounts.Empty(); + AllSplitFaceIndices.Empty(); + AllSplitFirstValidVertexIndex.Empty(); + AllSplitFirstValidPrimIndex.Empty(); + + bool bHasSplit = AllSplitGroups.Num() > 0; + if (bHasSplit) + { + HAPI_PartInfo PartInfo = FHoudiniEngineUtils::ToHAPIPartInfo(HGPO.PartInfo); + + // Buffer for all vertex indices used for split groups. + // We need this to figure out all vertex indices that are not part of them. + TArray AllVertexList; + AllVertexList.SetNumZeroed(PartVertexList.Num()); + + // Buffer for all face indices used for split groups. + // We need this to figure out all face indices that are not part of them. + TArray AllGroupFaceIndices; + AllGroupFaceIndices.SetNumZeroed(HGPO.PartInfo.FaceCount); + + // Some of the groups may contain invalid geometry + // Store them here so we can remove them afterwards + TArray InvalidGroupNameIndices; + + // Extract the vertices/faces for each of the split groups + for (int32 SplitIdx = 0; SplitIdx < AllSplitGroups.Num(); SplitIdx++) + { + const FString& GroupName = AllSplitGroups[SplitIdx]; + + // New vertex list just for this group. + TArray< int32 > GroupVertexList; + TArray< int32 > AllFaceList; + + int32 FirstValidPrimIndex = 0; + int32 FirstValidVertexIndex = 0; + // Extract vertex indices for this split. + int32 GroupVertexListCount = FHoudiniEngineUtils::HapiGetVertexListForGroup( + HGPO.GeoId, PartInfo, GroupName, + PartVertexList, GroupVertexList, + AllVertexList, AllFaceList, AllGroupFaceIndices, + FirstValidVertexIndex, FirstValidPrimIndex, + HGPO.PartInfo.bIsInstanced); + + if (GroupVertexListCount <= 0) + { + // This group doesn't have vertices/faces, mark it as invalid + InvalidGroupNameIndices.Add(SplitIdx); + + // Error getting the vertex list. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve vertex list for group %s - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, *GroupName); + + continue; + } + + // If list is not empty, we store it for this group - this will define new mesh. + AllSplitVertexLists.Add(GroupName, GroupVertexList); + AllSplitVertexCounts.Add(GroupName, GroupVertexListCount); + AllSplitFaceIndices.Add(GroupName, AllFaceList); + AllSplitFirstValidVertexIndex.Add(GroupName, FirstValidVertexIndex); + AllSplitFirstValidPrimIndex.Add(GroupName, FirstValidPrimIndex); + } + + if (InvalidGroupNameIndices.Num() > 0) + { + // Remove all invalid split groups + for (int32 InvalIdx = InvalidGroupNameIndices.Num() - 1; InvalIdx >= 0; InvalIdx--) + { + int32 Index = InvalidGroupNameIndices[InvalIdx]; + AllSplitGroups.RemoveAt(Index); + } + } + + // We also need to figure out / construct the vertex list for everything that's not in a split group + TArray GroupSplitFacesRemaining; + GroupSplitFacesRemaining.Init(-1, PartVertexList.Num()); + + int32 GroupVertexListCount = 0; + bool bHasMainSplitGroup = false; + TArray< int32 > GroupSplitFaceIndicesRemaining; + int32 FistUnusedVertexIndex = -1; + for (int32 SplitVertexIdx = 0; SplitVertexIdx < AllVertexList.Num(); SplitVertexIdx++) + { + if (AllVertexList[SplitVertexIdx] == 0) + { + // This is an unused index, we need to add it to unused vertex list. + FistUnusedVertexIndex = SplitVertexIdx; + GroupSplitFacesRemaining[SplitVertexIdx] = PartVertexList[SplitVertexIdx]; + bHasMainSplitGroup = true; + GroupVertexListCount++; + } + } + + int32 FistUnusedPrimIndex = -1; + for (int32 SplitFaceIdx = 0; SplitFaceIdx < AllGroupFaceIndices.Num(); SplitFaceIdx++) + { + if (AllGroupFaceIndices[SplitFaceIdx] == 0) + { + // This is unused face, we need to add it to unused faces list. + GroupSplitFaceIndicesRemaining.Add(SplitFaceIdx); + FistUnusedPrimIndex = SplitFaceIdx; + } + } + + // We store the remaining geo vertex list as a special split named "main geo" + // and make sure its treated before the collider meshes + if (bHasMainSplitGroup) + { + static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + AllSplitGroups.Add(RemainingGroupName); + AllSplitVertexLists.Add(RemainingGroupName, GroupSplitFacesRemaining); + AllSplitVertexCounts.Add(RemainingGroupName, GroupVertexListCount); + AllSplitFaceIndices.Add(RemainingGroupName, GroupSplitFaceIndicesRemaining); + AllSplitFirstValidPrimIndex.Add(RemainingGroupName, FistUnusedPrimIndex); + AllSplitFirstValidVertexIndex.Add(RemainingGroupName, FistUnusedVertexIndex); + } + } + else + { + // No splitting required + // Mark everything as the main geo group + static const FString RemainingGroupName = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + AllSplitGroups.Add(RemainingGroupName); + AllSplitVertexLists.Add(RemainingGroupName, PartVertexList); + AllSplitVertexCounts.Add(RemainingGroupName, PartVertexList.Num()); + AllSplitFirstValidPrimIndex.Add(RemainingGroupName, 0); + AllSplitFirstValidVertexIndex.Add(RemainingGroupName, 0); + + TArray AllFaces; + for (int32 FaceIdx = 0; FaceIdx < HGPO.PartInfo.FaceCount; ++FaceIdx) + AllFaces.Add(FaceIdx); + + AllSplitFaceIndices.Add(RemainingGroupName, AllFaces); + } + + return true; +} + +void +FHoudiniMeshTranslator::ResetPartCache() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::ResetPartCache")); + + // Vertex Positions + PartPositions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoPositions); + + // Vertex Normals + PartNormals.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoNormals); + + // Vertex TangentU + PartTangentU.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentU); + + // Vertex TangentV + PartTangentV.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoTangentV); + + // Vertex Colors + PartColors.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoColors); + + // Vertex Alpha values + PartAlphas.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoAlpha); + + // FaceSmoothing values + PartFaceSmoothingMasks.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceSmoothingMasks); + + // UVs + PartUVSets.Empty(); + AttribInfoUVSets.Empty(); + + // UVs + PartLightMapResolutions.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoLightmapResolution); + + // Material IDs per face + PartFaceMaterialIds.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialIds); + // Unique material IDs + PartUniqueMaterialIds.Empty(); + // Material infos for each unique Material + PartUniqueMaterialInfos.Empty(); + // + bOnlyOneFaceMaterial = false; + + // Face Materials override + PartFaceMaterialOverrides.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoFaceMaterialOverrides); + bMaterialOverrideNeedsCreateInstance = false; + + // LOD Screensize + PartLODScreensize.Empty(); + FHoudiniApi::AttributeInfo_Init(&AttribInfoLODScreensize); +} + +bool +FHoudiniMeshTranslator::UpdatePartPositionIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartPositionIfNeeded")); + + // Only Retrieve the vertices positions if necessary + if (PartPositions.Num() > 0) + return true; + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_POSITION, AttribInfoPositions, PartPositions)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve position data") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNormalsIfNeeded")); + + // No need to read the normals if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadNormals = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeNormalsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + if (!bReadNormals) + return true; + + // Only Retrieve the normals if we haven't already + if (PartNormals.Num() > 0) + return true; + + // Retrieve normal data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_NORMAL, AttribInfoNormals, PartNormals); + + // There is no normals to fetch + if (!AttribInfoNormals.exists) + return true; + + if (!Success && AttribInfoNormals.exists) + { + // Error retrieving normals. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve normal data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartTangentsIfNeeded")) + + bool bReturn = true; + if (PartTangentU.Num() <= 0) + { + // Retrieve TangentU data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_TANGENTU, AttribInfoTangentU, PartTangentU); + + if (!Success && AttribInfoTangentU.exists) + { + // Error retrieving tangent. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentU data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + bReturn = false; + } + } + + if (PartTangentV.Num() <= 0) + { + // Retrieve TangentV data for this part + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_TANGENTV, AttribInfoTangentV, PartTangentV); + + if (!Success && AttribInfoTangentV.exists) + { + // Error retrieving tangent. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve tangentV data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + bReturn = false; + } + } + + return bReturn; +} + +bool +FHoudiniMeshTranslator::UpdatePartColorsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartColorsIfNeeded")); + + // Only Retrieve the vertices colors if necessary + if (PartColors.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_COLOR, AttribInfoColors, PartColors); + + if (!Success && AttribInfoColors.exists) + { + // Error retrieving colors. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve color data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartAlphasIfNeeded")); + + // Only Retrieve the vertices alphas if necessary + if (PartAlphas.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_ALPHA, AttribInfoAlpha, PartAlphas); + + if (!Success && AttribInfoAlpha.exists) + { + // Error retrieving alpha values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve alpha data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceSmoothingIfNeeded() +{ + // Only Retrieve the vertices FaceSmoothing if necessary + if (PartFaceSmoothingMasks.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, + AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks); + + if (!Success && AttribInfoFaceSmoothingMasks.exists) + { + // Error retrieving FaceSmoothing values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve FaceSmoothing data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartUVSetsIfNeeded")); + + // Only Retrieve uvs if necessary + if (PartUVSets.Num() > 0) + return true; + + PartUVSets.SetNum(MAX_STATIC_TEXCOORDS); + AttribInfoUVSets.SetNum(MAX_STATIC_TEXCOORDS); + + // The second UV set should be called uv2, but we will still check if need to look for a uv1 set. + // If uv1 exists, we'll look for uv, uv1, uv2 etc.. if not we'll look for uv, uv2, uv3 etc.. + bool bUV1Exists = FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, "uv1"); + + // Retrieve UVs. + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (TexCoordIdx > 0) + UVAttributeName += FString::Printf(TEXT("%d"), bUV1Exists ? TexCoordIdx : TexCoordIdx + 1); + + FHoudiniApi::AttributeInfo_Init(&AttribInfoUVSets[TexCoordIdx]); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*UVAttributeName), + AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], 2); + } + + // Also look for 16.5 uvs (attributes with a Texture type) + // For that, we'll have to iterate through ALL the attributes and check their types + TArray< FString > FoundAttributeNames; + TArray< HAPI_AttributeInfo > FoundAttributeInfos; + + for (int32 AttrIdx = 0; AttrIdx < HAPI_ATTROWNER_MAX; ++AttrIdx) + { + FHoudiniEngineUtils::HapiGetAttributeOfType( + HGPO.GeoId, HGPO.PartId, (HAPI_AttributeOwner)AttrIdx, + HAPI_ATTRIBUTE_TYPE_TEXTURE, FoundAttributeInfos, FoundAttributeNames); + } + + if (FoundAttributeInfos.Num() <= 0) + return true; + + // We found some additionnal uv attributes + int32 AvailableIdx = 0; + for (int32 attrIdx = 0; attrIdx < FoundAttributeInfos.Num(); attrIdx++) + { + // Ignore the old uvs + if (FoundAttributeNames[attrIdx] == TEXT("uv") + || FoundAttributeNames[attrIdx] == TEXT("uv1") + || FoundAttributeNames[attrIdx] == TEXT("uv2") + || FoundAttributeNames[attrIdx] == TEXT("uv3") + || FoundAttributeNames[attrIdx] == TEXT("uv4") + || FoundAttributeNames[attrIdx] == TEXT("uv5") + || FoundAttributeNames[attrIdx] == TEXT("uv6") + || FoundAttributeNames[attrIdx] == TEXT("uv7") + || FoundAttributeNames[attrIdx] == TEXT("uv8")) + continue; + + HAPI_AttributeInfo CurrentAttrInfo = FoundAttributeInfos[attrIdx]; + if (!CurrentAttrInfo.exists) + continue; + + // Look for the next available index in the return arrays + for (; AvailableIdx < AttribInfoUVSets.Num(); AvailableIdx++) + { + if (!AttribInfoUVSets[AvailableIdx].exists) + break; + } + + // We are limited to MAX_STATIC_TEXCOORDS uv sets! + // If we already have too many uv sets, skip the rest + if ((AvailableIdx >= MAX_STATIC_TEXCOORDS) || (AvailableIdx >= AttribInfoUVSets.Num())) + { + HOUDINI_LOG_WARNING(TEXT("Too many UV sets found. Unreal only supports %d , skipping the remaining uv sets."), (int32)MAX_STATIC_TEXCOORDS); + break; + } + + // Force the tuple size to 2 ? + CurrentAttrInfo.tupleSize = 2; + + // Add the attribute infos we found + AttribInfoUVSets[AvailableIdx] = CurrentAttrInfo; + + // Allocate sufficient buffer for the attribute's data. + PartUVSets[AvailableIdx].SetNumUninitialized(CurrentAttrInfo.count * CurrentAttrInfo.tupleSize); + + // Get the texture coordinates + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, TCHAR_TO_UTF8(*(FoundAttributeNames[attrIdx])), + &AttribInfoUVSets[AvailableIdx], -1, + &PartUVSets[AvailableIdx][0], 0, CurrentAttrInfo.count)) + { + // Something went wrong when trying to access the uv values, invalidate this set + AttribInfoUVSets[AvailableIdx].exists = false; + } + } + + // Remove unused UV sets + if (bRemoveUnused) + { + for (int32 Idx = PartUVSets.Num() - 1; Idx >= 0; Idx--) + { + if (PartUVSets[Idx].Num() > 0) + continue; + + PartUVSets.RemoveAt(Idx); + } + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartLightmapResolutionsIfNeeded() +{ + // Only Retrieve the vertices lightmap resolution if necessary + if (PartLightMapResolutions.Num() > 0) + return true; + + // Get lightmap resolution (if present). + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, + AttribInfoLightmapResolution, PartLightMapResolutions); + + if (!Success && AttribInfoLightmapResolution.exists) + { + // Error retrieving lightmap resolution values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve lightmap resolution data"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialIDsIfNeeded")); + + // Only Retrieve the material IDs if necessary + if (PartFaceMaterialIds.Num() > 0) + return true; + + int32 NumFaces = HGPO.PartInfo.FaceCount; + if (NumFaces <= 0) + return true; + + PartFaceMaterialIds.SetNum(NumFaces); + + // Get the materials IDs per face + HAPI_Bool bSingleFaceMaterial = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialNodeIdsOnFaces( + FHoudiniEngine::Get().GetSession(), + HGPO.GeoId, HGPO.PartId, &bSingleFaceMaterial, + &PartFaceMaterialIds[0], 0, NumFaces)) + { + // Error retrieving material face assignments. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material face assignments"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + bOnlyOneFaceMaterial = bSingleFaceMaterial; + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartFaceMaterialOverridesIfNeeded")); + + // Only Retrieve the material overrides if necessary + if (PartFaceMaterialOverrides.Num() > 0) + return true; + + bMaterialOverrideNeedsCreateInstance = false; + + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + + // If material attribute was not found, check fallback compatibility attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_FALLBACK, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + } + + // If material attribute and fallbacks were not found, check the material instance attribute. + if (!AttribInfoFaceMaterialOverrides.exists) + { + PartFaceMaterialOverrides.Empty(); + FHoudiniEngineUtils::HapiGetAttributeDataAsString( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_MATERIAL_INSTANCE, + AttribInfoFaceMaterialOverrides, PartFaceMaterialOverrides); + + // We will we need to create material instances from the override attributes + bMaterialOverrideNeedsCreateInstance = AttribInfoFaceMaterialOverrides.exists; + } + + if (AttribInfoFaceMaterialOverrides.exists + && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_PRIM + && AttribInfoFaceMaterialOverrides.owner != HAPI_ATTROWNER_DETAIL) + { + HOUDINI_LOG_WARNING(TEXT("Static Mesh [%d %s], Geo [%d], Part [%d %s]: unreal_material must be a primitive or detail attribute, ignoring attribute."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + AttribInfoFaceMaterialOverrides.exists = false; + bMaterialOverrideNeedsCreateInstance = false; + PartFaceMaterialOverrides.Empty(); + return false; + } + + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartNeededMaterials() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials")); + + // Update the per face material IDs + UpdatePartFaceMaterialIDsIfNeeded(); + + // See if we have some material overides + UpdatePartFaceMaterialOverridesIfNeeded(); + + // If we have houdini materials AND overrides: + // We want to only create the Houdini materials that are not "covered" by overrides + // If we have material instance attributes, create all the houdini material anyway + // as their textures could be referenced by the material instance parameters + if (PartFaceMaterialOverrides.Num() > 0 && !bMaterialOverrideNeedsCreateInstance) + { + // If the material override was set on the detail, no need to look for houdini material IDs, as only the override will be used + if (AttribInfoFaceMaterialOverrides.exists && AttribInfoFaceMaterialOverrides.owner == HAPI_ATTROWNER_PRIM) + { + for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) + { + // Add a material ID to the unique array only if that face is not using the override + if (PartFaceMaterialOverrides[MaterialIdx].IsEmpty()) + PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); + } + } + } + else + { + // No material overrides, simply update the unique material array + for (int32 MaterialIdx = 0; MaterialIdx < PartFaceMaterialIds.Num(); ++MaterialIdx) + PartUniqueMaterialIds.AddUnique(PartFaceMaterialIds[MaterialIdx]); + } + + // Remove the invalid material ID from the unique array + PartUniqueMaterialIds.RemoveSingle(-1); + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::UpdatePartNeededMaterials - Get the unique material infos")); + // Get the unique material infos + PartUniqueMaterialInfos.SetNum(PartUniqueMaterialIds.Num()); + for (int32 MaterialIdx = 0; MaterialIdx < PartUniqueMaterialIds.Num(); MaterialIdx++) + { + + FHoudiniApi::MaterialInfo_Init(&PartUniqueMaterialInfos[MaterialIdx]); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetMaterialInfo( + FHoudiniEngine::Get().GetSession(), + PartUniqueMaterialIds[MaterialIdx], + &PartUniqueMaterialInfos[MaterialIdx])) + { + // Error retrieving material face assignments. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] unable to retrieve material info for material %d"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, PartUniqueMaterialIds[MaterialIdx]); + continue; + } + } + } + return true; +} + +bool +FHoudiniMeshTranslator::UpdatePartLODScreensizeIfNeeded() +{ + // Only retrieve LOD screensizes if necessary + if (PartLODScreensize.Num() > 0) + return true; + + bool Success = FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoInfo.NodeId, HGPO.PartInfo.PartId, + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE, + AttribInfoLODScreensize, PartLODScreensize); + + if (!Success && AttribInfoLODScreensize.exists) + { + // Error retrieving FaceSmoothing values. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], unable to retrieve LOD screensizes"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName); + return false; + } + + return true; +} + + +UStaticMesh* +FHoudiniMeshTranslator::CreateNewStaticMesh(const FString& InSplitIdentifier) +{ + // Update the current Obj/Geo/Part/Split IDs + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.GeoId; + PackageParams.PartId = HGPO.PartId; + PackageParams.SplitStr = InSplitIdentifier; + + UStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); + if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + return nullptr; + + return NewStaticMesh; +} + +UHoudiniStaticMesh* +FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentifier) +{ + // Update the current Obj/Geo/Part/Split IDs + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.GeoId; + PackageParams.PartId = HGPO.PartId; + // Add _HSM suffix to the split str, to distinguish the temporary HoudiniStaticMesh + // from the UStaticMesh + PackageParams.SplitStr = InSplitIdentifier + "_HSM"; + + UHoudiniStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); + if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + return nullptr; + + return NewStaticMesh; +} + +bool +FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() +{ + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + + double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Prepare the object that will store UCX and simple colliders + AllAggregateCollisions.Empty(); + + // We need to know the number of LODs that will be needed for this part + int32 NumberOfLODs = 0; + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) + NumberOfLODs++; + else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + bHasMainGeo = true; + } + + // Update the part's material's IDS and info now + CreateNeededMaterials(); + + // Check now if they were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Get the current target platform for default lod policies + ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + check(CurrentPlatform); + + // New mesh list + TMap StaticMeshToBuild; + + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; + + // bool MeshMaterialsHaveBeenReset = false; + + double tick = FPlatformTime::Seconds(); + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre Split-Loop in %f seconds."), tick - time_start); + } + + UStaticMesh* MainStaticMesh = nullptr; + bool bAssignedCustomCollisionMesh = false; + ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; + + // Iterate through all detected split groups we care about and split geometry. + // The split are ordered in the following way: + // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + double split_tick = FPlatformTime::Seconds(); + + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + + // Get/Create the Aggregate Collisions for this mesh identifier + FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); + + // Handle UCX / Convex Hull colliders + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) + { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the convex hull colliders and add them to the Aggregate + if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) + continue; + } + else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) + { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the simple colliders and add them to the aggregate + if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) + continue; + } + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing SM from a previous cook + UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + // Prepare LOD Group data for this static mesh + FStaticMeshLODGroup LODGroup; + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing static mesh, create a new one + FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + + // Use the platform's default LODGroup policy + // TODO? Add setting for default LOD Group? + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); + } + else + { + // Try to reuse the existing SM's LOD group instead of the default one + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); + } + + if (SplitType == EHoudiniSplitType::Normal && !MainStaticMesh) + { + MainStaticMesh = FoundStaticMesh; + MainStaticMesh->ComplexCollisionMesh = nullptr; + MainStaticMesh->bCustomizedCollision = false; + // NOTE: The main static mesh collision trace flag will be set after all splits have been processed. + } + + if (!FoundOutputObject) + { + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + InputObjects.Remove(OutputObjectIdentifier); + } + else + { + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + FoundOutputObject->CachedAttributes.Empty(); + FoundOutputObject->CachedTokens.Empty(); + } + FoundOutputObject->bProxyIsCurrent = false; + + // TODO: Needed? + // Free any RHI resources for existing mesh before we re-create in place. + FoundStaticMesh->PreEditChange(NULL); + + // Check that the Static Mesh we found has the appropriate number of Source models/LODs + int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); + + // LODs are only for the "main" mesh, not for complex colliders! + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); + + if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) + { + while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) + FoundStaticMesh->AddSourceModel(); + + // We may have to remove excessive LOD levels + if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) + FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); + + // Initialize their default reduction setting + for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) + { + FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); + } + FoundStaticMesh->SetLightMapResolution(LODGroup.GetDefaultLightMapResolution()); + } + + // By default, always work on the first source model, unless we're a LOD + int32 SrcModelIndex = 0; + int32 LODIndex = 0; + if (SplitType == EHoudiniSplitType::LOD) + { + for (auto& curSplit : AllSplitGroups) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); + if (CurrentSplitType == EHoudiniSplitType::LOD + || CurrentSplitType == EHoudiniSplitType::Normal) + { + LODIndex++; + } + + if (curSplit == SplitGroupName) + break; + } + + // Fix for the case where we don't have a main geo + if(!bHasMainGeo) + LODIndex--; + } + + // Grab the appropriate SourceModel + FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; + if (!SrcModel) + { + HOUDINI_LOG_ERROR( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); + continue; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Load existing raw model. This will be empty as we are constructing a new mesh. + FRawMesh RawMesh; + if (!bRebuildStaticMesh) + { + // We dont need to rebuild the mesh itself: + // the geometry hasn't changed, but the materials have. + // We can just load the old data into the Raw mesh and reuse it. + SrcModel->LoadRawMesh(RawMesh); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - LoadRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + } + else + { + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's normal if needed + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + // Check that the number of normal we retrieved is correct + int32 WedgeNormalCount = SplitNormals.Num() / 3; + if (SplitNormals.Num() < 0 || !SplitNormals.IsValidIndex((WedgeNormalCount - 1) * 3 + 2)) + { + // Ignore normals + WedgeNormalCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); + } + + // Transfer the normals to the raw mesh + RawMesh.WedgeTangentZ.SetNumZeroed(WedgeNormalCount); + for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) + { + // Swap Y/Z for Coordinates conversion + RawMesh.WedgeTangentZ[WedgeTangentZIdx].X = SplitNormals[WedgeTangentZIdx * 3 + 0]; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].Y = SplitNormals[WedgeTangentZIdx * 3 + 2]; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].Z = SplitNormals[WedgeTangentZIdx * 3 + 1]; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Normals in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENTS + //--------------------------------------------------------------------------------------------------------------------- + + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + TArray< float > SplitTangentU; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + TArray< float > SplitTangentV; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + bool bGenerateTangents = (SplitNormals.Num() > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + + // Check that the number of tangents read matches the number of normals + int32 WedgeTangentUCount = SplitTangentU.Num() / 3; + int32 WedgeTangentVCount = SplitTangentV.Num() / 3; + if (WedgeTangentUCount != WedgeNormalCount || WedgeTangentVCount != WedgeNormalCount) + bGenerateTangents = true; + + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + + // Generate the tangents if needed + if (bGenerateTangents) + { + RawMesh.WedgeTangentX.SetNumZeroed(WedgeNormalCount); + RawMesh.WedgeTangentY.SetNumZeroed(WedgeNormalCount); + for (int32 WedgeTangentZIdx = 0; WedgeTangentZIdx < WedgeNormalCount; ++WedgeTangentZIdx) + { + FVector TangentX, TangentY; + RawMesh.WedgeTangentZ[WedgeTangentZIdx].FindBestAxisVectors(TangentX, TangentY); + + RawMesh.WedgeTangentX[WedgeTangentZIdx] = TangentX; + RawMesh.WedgeTangentY[WedgeTangentZIdx] = TangentY; + } + } + else + { + // Transfer the tangents we have read them and they're valid + RawMesh.WedgeTangentX.SetNumZeroed(WedgeTangentUCount); + for (int32 WedgeTangentUIdx = 0; WedgeTangentUIdx < WedgeTangentUCount; ++WedgeTangentUIdx) + { + // We need to flip Z and Y + RawMesh.WedgeTangentX[WedgeTangentUIdx].X = SplitTangentU[WedgeTangentUIdx * 3 + 0]; + RawMesh.WedgeTangentX[WedgeTangentUIdx].Y = SplitTangentU[WedgeTangentUIdx * 3 + 2]; + RawMesh.WedgeTangentX[WedgeTangentUIdx].Z = SplitTangentU[WedgeTangentUIdx * 3 + 1]; + } + + RawMesh.WedgeTangentY.SetNumZeroed(WedgeTangentVCount); + for (int32 WedgeTangentVIdx = 0; WedgeTangentVIdx < WedgeTangentVCount; ++WedgeTangentVIdx) + { + // We need to flip Z and Y + RawMesh.WedgeTangentY[WedgeTangentVIdx].X = SplitTangentV[WedgeTangentVIdx * 3 + 0]; + RawMesh.WedgeTangentY[WedgeTangentVIdx].Y = SplitTangentV[WedgeTangentVIdx * 3 + 2]; + RawMesh.WedgeTangentY[WedgeTangentVIdx].Z = SplitTangentV[WedgeTangentVIdx * 3 + 1]; + } + } + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Tangents in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // VERTEX COLORS AND ALPHAS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's colors if needed + UpdatePartColorsIfNeeded(); + + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract this part's alpha values if needed + UpdatePartAlphasIfNeeded(); + + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + + // Transfer colors and alphas if possible + int32 WedgeColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; + bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && WedgeColorsCount > 0; + bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == WedgeColorsCount); + if (bSplitColorValid) + { + RawMesh.WedgeColors.SetNumZeroed(WedgeColorsCount); + for (int32 WedgeColorIdx = 0; WedgeColorIdx < WedgeColorsCount; WedgeColorIdx++) + { + FLinearColor WedgeColor; + WedgeColor.R = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); + WedgeColor.G = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); + WedgeColor.B = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); + + if (bSplitAlphaValid) + { + // Use the Alpha attribute value + WedgeColor.A = FMath::Clamp(SplitAlphas[WedgeColorIdx], 0.0f, 1.0f); + } + else if (AttribInfoColors.tupleSize >= 4) + { + // Use the alpha value from the color attribute + WedgeColor.A = FMath::Clamp( + SplitColors[WedgeColorIdx * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); + } + else + { + WedgeColor.A = 1.0f; + } + + // Convert linear color to fixed color. + RawMesh.WedgeColors[WedgeColorIdx] = WedgeColor.ToFColor(false); + } + } + else + { + // TODO? Needed? New meshes wont have WedgeIndices yet!? + // No Colors or Alphas, init colors to White + FColor DefaultWedgeColor = FLinearColor::White.ToFColor(false); + WedgeColorsCount = RawMesh.WedgeIndices.Num(); + if (WedgeColorsCount > 0) + RawMesh.WedgeColors.Init(DefaultWedgeColor, WedgeColorsCount); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Cd and Alpha in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's FaceSmoothing values if needed + UpdatePartFaceSmoothingIfNeeded(); + + // Get the FaceSmoothing values for this split + TArray SplitFaceSmoothingMasks; + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); + + // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! + RawMesh.FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); + + // Check that the number of face smoothing values we retrieved is correct + int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; + if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) + { + // Ignore our face smoothing values + WedgeFaceSmoothCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); + } + + // Transfer the face smoothing masks to the raw mesh if we have any + for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) + { + RawMesh.FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - FaceSmoothing in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // UVS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's UV sets if needed + UpdatePartUVSetsIfNeeded(); + + // See if we need to transfer uv point attributes to vertex attributes. + TArray> SplitUVSets; + SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + } + + // Transfer UVs to the Raw Mesh + int32 UVChannelCount = 0; + int32 LightMapUVChannel = 0; + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; + + int32 WedgeUVCount = SplitUVs.Num() / 2; + if (SplitUVs.Num() > 0 && SplitUVs.IsValidIndex((WedgeUVCount - 1) * 2 + 1)) + { + RawMesh.WedgeTexCoords[TexCoordIdx].SetNumZeroed(WedgeUVCount); + for (int32 WedgeUVIdx = 0; WedgeUVIdx < WedgeUVCount; ++WedgeUVIdx) + { + // We need to flip V coordinate when it's coming from HAPI. + RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].X = SplitUVs[WedgeUVIdx * 2 + 0]; + RawMesh.WedgeTexCoords[TexCoordIdx][WedgeUVIdx].Y = 1.0f - SplitUVs[WedgeUVIdx * 2 + 1]; + } + + UVChannelCount++; + if (UVChannelCount <= 2) + LightMapUVChannel = TexCoordIdx; + } + else + { + RawMesh.WedgeTexCoords[TexCoordIdx].Empty(); + } + } + + // We must have at least one UV channel. If there's none, create one filled with zero data. + if (UVChannelCount == 0) + RawMesh.WedgeTexCoords[0].SetNumZeroed(SplitVertexCount); + + // Set the lightmap Coordinate Index + // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention + // If not, the first UV set will be used + FoundStaticMesh->SetLightMapCoordinateIndex(LightMapUVChannel); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - UVs in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's LightmapResolution values if needed + UpdatePartLightmapResolutionsIfNeeded(); + + // make sure the mesh has a new lighting guid + FoundStaticMesh->SetLightingGuid(FGuid::NewGuid()); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Lightmap Resolutions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" + // So that IndicesMapper[ oldIndex ] => newIndex + TArray IndicesMapper; + IndicesMapper.Init(-1, SplitVertexList.Num()); + int32 CurrentMapperIndex = 0; + + // NeededVertices: + // Array containing the old index of the needed vertices for the current split + // NeededVertices[ newIndex ] => oldIndex + TArray< int32 > NeededVertices; + RawMesh.WedgeIndices.SetNumZeroed(SplitVertexCount); + + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) + || !IndicesMapper.IsValidIndex(WedgeIndices[1]) + || !IndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (IndicesMapper[WedgeIndices[i]] < 0) + { + // This old index has not yet been "converted" to a new index + NeededVertices.Add(WedgeIndices[i]); + IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; + CurrentMapperIndex++; + } + + // Replace the old index with the new one + WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; + } + + if (!RawMesh.WedgeIndices.IsValidIndex(ValidVertexId + 2)) + break; + + // Flip wedge indices to fix the winding order. + RawMesh.WedgeIndices[ValidVertexId + 0] = WedgeIndices[0]; + RawMesh.WedgeIndices[ValidVertexId + 1] = WedgeIndices[2]; + RawMesh.WedgeIndices[ValidVertexId + 2] = WedgeIndices[1]; + + // Check if we need to patch UVs. + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + if (RawMesh.WedgeTexCoords[TexCoordIdx].IsValidIndex(ValidVertexId + 2)) + { + Swap(RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 1], + RawMesh.WedgeTexCoords[TexCoordIdx][ValidVertexId + 2]); + } + } + + // Check if we need to patch colors. + if (RawMesh.WedgeColors.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeColors[ValidVertexId + 1], RawMesh.WedgeColors[ValidVertexId + 2]); + + // Check if we need to patch Normals and tangents. + if (RawMesh.WedgeTangentZ.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentZ[ValidVertexId + 1], RawMesh.WedgeTangentZ[ValidVertexId + 2]); + + if (RawMesh.WedgeTangentX.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentX[ValidVertexId + 1], RawMesh.WedgeTangentX[ValidVertexId + 2]); + + if (RawMesh.WedgeTangentY.IsValidIndex(ValidVertexId + 2)) + Swap(RawMesh.WedgeTangentY[ValidVertexId + 1], RawMesh.WedgeTangentY[ValidVertexId + 2]); + + ValidVertexId += 3; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + UpdatePartPositionIfNeeded(); + + // + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + int32 VertexPositionsCount = NeededVertices.Num(); + RawMesh.VertexPositions.SetNumZeroed(VertexPositionsCount); + + for (int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx) + { + int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; + if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // We need to swap Z and Y coordinate here, and convert from m to cm. + RawMesh.VertexPositions[VertexPositionIdx].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + RawMesh.VertexPositions[VertexPositionIdx].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + RawMesh.VertexPositions[VertexPositionIdx].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + /* + // TODO: + // Check if this mesh contains only degenerate triangles. + if (FHoudiniEngineUtils::CountDegenerateTriangles(RawMesh) == SplitGroupFaceCount) + { + // This mesh contains only degenerate triangles, there's nothing we can do. + if (bStaticMeshCreated) + StaticMesh->MarkPendingKill(); + + continue; + } + */ + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL ATTRIBUTE OVERRIDES + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: These are actually per faces, not per vertices... + // Need to update!! + UpdatePartFaceMaterialOverridesIfNeeded(); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Material Overrides in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Handle Materials!!!! + + // Get face indices for this split. + TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; + + // Fetch the FoundMesh's Static Materials array + TArray& FoundStaticMaterials = FoundStaticMesh->GetStaticMaterials(); + + // // We need to reset the Static Mesh's materials once per SM: + // // so, for the first lod, or the main geo... + // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // { + // FoundStaticMaterials.Empty(); + // MeshMaterialsHaveBeenReset = true; + // } + // + // // .. or for each visible complex collider + // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + // FoundStaticMaterials.Empty(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) + { + FoundStaticMaterials.Empty(); + } + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); + + // Process material overrides first + if (PartFaceMaterialOverrides.Num() > 0) + { + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + + // If the part has material overrides + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + continue; + + UMaterialInterface * MaterialInterface = nullptr; + int32 CurrentFaceMaterialIdx = 0; + const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) + { + // Try to locate the corresponding material interface + + // Start by looking in our assignment map + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); + } + else + { + // The Attribute Material and its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + if (!MaterialInterface) + { + // If everything fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + } + } + + if (MaterialInterface) + { + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + + // Update the Face Material on the mesh + RawMesh.FaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + } + } + } + else if (PartUniqueMaterialIds.Num() > 0) + { + // The part has houdini materials + if (bOnlyOneFaceMaterial) + { + // We have only one material. + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + else + { + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + // Reset Rawmesh material face assignments. + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceIndices.Num()); + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) + { + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + RawMesh.FaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } + } + else + { + MaterialInterface = Cast(DefaultMaterial); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + + if (MaterialInterface) + { + // Add the material to the Static mesh + int32 UnrealMatIndex = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); + + // Update the face index + RawMesh.FaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } + } + } + } + else + { + // No materials were found, we need to use default Houdini material. + int32 SplitFaceCount = SplitFaceIndices.Num(); + RawMesh.FaceMaterialIndices.SetNumZeroed(SplitFaceCount); + + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Face Materials in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Update the Build Settings using the default setting values + UpdateMeshBuildSettings( + SrcModel->BuildSettings, + RawMesh.WedgeTangentZ.Num() > 0, + (RawMesh.WedgeTangentX.Num() > 0 && RawMesh.WedgeTangentY.Num() > 0), + RawMesh.WedgeTexCoords->Num() > 0); + + // Check for a lightmap resolution override + int32 LightMapResolutionOverride = -1; + if (PartLightMapResolutions.Num() > 0) + LightMapResolutionOverride = PartLightMapResolutions[0]; + + if (LightMapResolutionOverride > 0) + FoundStaticMesh->SetLightMapResolution(LightMapResolutionOverride); + else + FoundStaticMesh->SetLightMapResolution(64); + + // TODO + //StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; + //StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; + //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings; + + // TODO: + // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? + + // This was required due to the impeding deprecation of FRawMesh + // If we dont update this UE4 will crash upon deleting an asset. + //SrcModel->StaticMeshOwner = FoundStaticMesh; + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - PreSaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Store the new raw mesh if it is valid + if (RawMesh.IsValid()) + { + SrcModel->SaveRawMesh(RawMesh); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("[CreateStaticMesh_RawMesh]: Invalid StaticMesh data for %s LOD %i in cook output! Please check the log."), + *FoundStaticMesh->GetName(), LODIndex); + // Create an "empty" valid raw mesh (single zero-area triangle) + // TODO: is there a cleaner way to do this? Perhaps committing an empty mesh description? Empty RawMesh is + // a no-op on SrcModel->SaveRawMesh (leaves previous data in place). + // TODO: perhaps we can use an alternative "error" mesh? + RawMesh.Empty(); + RawMesh.VertexPositions.Add(FVector::ZeroVector); + RawMesh.WedgeIndices.SetNumZeroed(3); + RawMesh.WedgeTexCoords[0].Init(FVector2D::ZeroVector, RawMesh.WedgeIndices.Num()); + SrcModel->SaveRawMesh(RawMesh); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - SaveRawMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + { + // Patch the MeshDescription data structure that is being output from SaveRawMesh. SaveRawMesh leaves invalid entries + // in the PolyGroups / MaterialSlotNames arrays that causes issues later when the static mesh is built and LOD material assignments + // are being done (materials aren't correctly assigned to LODs if LODs use different materials). + + // Create a Polygon Group for each material slot + TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = + SrcModel->MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); + + // We must use the number of assignment materials found to reserve the number of material slots + // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials + int32 NumberOfMaterials = FoundStaticMaterials.Num(); + if (NumberOfMaterials <= 0) + { + // No materials, create a polygon group for the default one + const FPolygonGroupID& PolygonGroupID = SrcModel->MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + } + else + { + FPolygonGroupArray& PolyGroups = SrcModel->MeshDescription->PolygonGroups(); + for (auto& CurrentMatAssignment : OutputAssignmentMaterials) + { + const FPolygonGroupID& PolygonGroupID = SrcModel->MeshDescription->CreatePolygonGroup(); + + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = + FName(CurrentMatAssignment.Value ? *(CurrentMatAssignment.Value->GetName()) : *(CurrentMatAssignment.Key)); + } + } + } + + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = GetLODSCreensizeForSplit(SplitGroupName); + if (screensize >= 0.0f) + { + // Only apply the LOD screensize if it's valid + SrcModel->ScreenSize = screensize; + //FoundStaticMesh->GetSourceModel(LODIndex).ScreenSize = screensize; + FoundStaticMesh->bAutoComputeLODScreenSize = false; + } + + // TODO: + // SET STATIC MESH GENERATION PARAM + // HANDLE COLLIDERS + // REMOVE OLD COLLIDERS + // CUSTOM BAKE NAME OVERRIDE + + // Update property attributes on the SM + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + HGPO.GeoId, HGPO.PartId, + true, + AllSplitFirstValidPrimIndex[SplitGroupName], + INDEX_NONE, + AllSplitFirstValidVertexIndex[SplitGroupName], + PropertyAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + FoundStaticMesh, PropertyAttributes); + } + + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames, 0, 1)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray TileValues; + if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId, 0, 1)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Attributes in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Notify that we created a new Static Mesh if needed + if (bNewStaticMeshCreated) + FAssetRegistryModule::AssetCreated(FoundStaticMesh); + + // Add the Static mesh to the output maps and the build map if we haven't already + if (FoundOutputObject) + { + FoundOutputObject->OutputObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = false; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + + StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Total Split time: %f seconds."), tick - split_tick); + } + } + + // Look if we only have colliders + // If we do, we'll allow attaching sockets to the collider meshes + bool bCollidersOnly = true; + for (auto& Current : StaticMeshToBuild) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) + { + bCollidersOnly = false; + break; + } + } + + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + for (auto& Current : StaticMeshToBuild) + { + tick = FPlatformTime::Seconds(); + + UStaticMesh* SM = Current.Value; + if (!SM || SM->IsPendingKill()) + continue; + + UBodySetup * BodySetup = SM->GetBodySetup(); + if (!BodySetup) + { + SM->CreateBodySetup(); + BodySetup = SM->GetBodySetup(); + } + + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + + // Handle the Static Mesh's colliders + if (BodySetup && !BodySetup->IsPendingKill()) + { + // Make sure rendering is done - so we are not changing data being used by collision drawing. + FlushRenderingCommands(); + + // Clean up old colliders from a previous cook + BodySetup->Modify(); + BodySetup->RemoveSimpleCollision(); + + FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; + FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); + if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) + { + BodySetup->AddCollisionFrom(*CurrentAggColl); + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; + } + + // See if we need to enable collisions on the whole mesh + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + // Complex collider, enable collisions for this (collider) static mesh. + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + + // Apply the collider to the Main static mesh, if relevant. + ApplyComplexColliderHelper( + MainStaticMesh, + SM, + SplitType, + bAssignedCustomCollisionMesh, + OutputObjects.Find(Current.Key)); + } + else + { + // TODO + // if the LODForCollision uproperty attribute is set, we need to activate complex collision + // on the static mesh for that lod to be picked up properly as a collider + if (FHoudiniEngineUtils::HapiCheckAttributeExists(HGPO.GeoId, HGPO.PartId, + "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) + { + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + } + } + + // Add the Sockets to the StaticMesh + // We only add them to the main geo, or to the colliders if we only generate colliders + bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; + if (bAddSocket) + { + if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, HGPO.AllMeshSockets, true)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); + } + } + + if (MainStaticMesh) + { + UBodySetup* MainBodySetup = MainStaticMesh->GetBodySetup(); + if (!IsValid(MainBodySetup)) + { + MainStaticMesh->CreateBodySetup(); + MainBodySetup = MainStaticMesh->GetBodySetup(); + } + + check(MainBodySetup); + // Set the main static mesh to whatever the final CTF should be. + MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // BUILD the Static Mesh + // bSilent doesnt add the Build Errors... + double build_start = FPlatformTime::Seconds(); + TArray SMBuildErrors; + SM->Build(true, &SMBuildErrors); + if (bDoTiming) + { + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - StaticMesh->Build() executed in %f seconds."), tick - build_start); + } + + // This replaces the call to RefreshCollision below, but without CreateNavCollision + // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, + // and can be expensive depending on the vert/poly count of the mesh + // RefreshCollisionChange(*SM); + { + for (FThreadSafeObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + { + UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); + if (StaticMeshComponent->GetStaticMesh() == SM) + { + // it needs to recreate IF it already has been created + if (StaticMeshComponent->IsPhysicsStateCreated()) + { + StaticMeshComponent->RecreatePhysicsState(); + } + } + } + + FEditorSupportDelegates::RedrawAllViewports.Broadcast(); + } + + SM->GetOnMeshChanged().Broadcast(); + + UPackage* MeshPackage = SM->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + } + } + + // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup + // Here as it has already been handled by the StaticMesh Build call + + double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() executed in %f seconds."), time_end - time_start); + + return true; +} + +bool +FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() +{ + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + + double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + // Simple colliders first, lods and finally, invisible colliders (that are separate Static Mesh) + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Prepare the object that will store UCX and simple colliders + AllAggregateCollisions.Empty(); + + // We need to know the number of LODs that will be needed for this part + int32 NumberOfLODs = 0; + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::LOD) + NumberOfLODs++; + else if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + bHasMainGeo = true; + } + + // Update the part's material's IDS and info now + CreateNeededMaterials(); + + // Check if the materials were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Get the current target platform for default lod policies + ITargetPlatform * CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); + check(CurrentPlatform); + + // New mesh list + TMap StaticMeshToBuild; + + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; + + bool MeshMaterialsHaveBeenReset = false; + + double tick = FPlatformTime::Seconds(); + if (bDoTiming) + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre Split-Loop in %f seconds."), tick - time_start); + + UStaticMesh* MainStaticMesh = nullptr; + bool bAssignedCustomCollisionMesh = false; + ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; + + // Iterate through all detected split groups we care about and split geometry. + // The split are ordered in the following way: + // Invisible Simple/Convex Colliders > LODs > MainGeo > Visible Colliders > Invisible Colliders + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + double split_tick = FPlatformTime::Seconds(); + + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName], + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + + // Get/Create the Aggregate Collisions for this mesh identifier + FKAggregateGeom& AggregateCollisions = AllAggregateCollisions.FindOrAdd(OutputObjectIdentifier); + + // Handle UCX / Convex Hull colliders + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider || SplitType == EHoudiniSplitType::RenderedUCXCollider) + { + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the convex hull colliders and add them to the Aggregate + if (!AddConvexCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create convex collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleUCXCollider) + continue; + } + else if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider || SplitType == EHoudiniSplitType::RenderedSimpleCollider) + { + MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseDefault; + // Get the part position if needed + UpdatePartPositionIfNeeded(); + + // Create the simple colliders and add them to the aggregate + if (!AddSimpleCollisionToAggregate(SplitGroupName, AggregateCollisions)) + { + // Failed to generate a convex collider + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] failed to create simple collider."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + + // If the collider is not visible, stop here + if (SplitType == EHoudiniSplitType::InvisibleSimpleCollider) + continue; + } + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing SM from a previous cook + UStaticMesh* FoundStaticMesh = FindExistingStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + // Prepare LOD Group data for this static mesh + FStaticMeshLODGroup LODGroup; + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing static mesh, create a new one + FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + + // Use the platform's default LODGroup policy + // TODO? Add setting for default LOD Group? + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(NAME_None); + } + else + { + // Try to reuse the existing SM's LOD group instead of the default one + LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(FoundStaticMesh->LODGroup); + } + + if (SplitType == EHoudiniSplitType::Normal) + { + MainStaticMesh = FoundStaticMesh; + MainStaticMesh->ComplexCollisionMesh = nullptr; + MainStaticMesh->bCustomizedCollision = false; + } + + if (!FoundOutputObject) + { + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + } + else + { + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + FoundOutputObject->CachedAttributes.Empty(); + FoundOutputObject->CachedTokens.Empty(); + } + FoundOutputObject->bProxyIsCurrent = false; + + // TODO: Needed? + // Free any RHI resources for existing mesh before we re-create in place. + FoundStaticMesh->PreEditChange(NULL); + + // Check that the Static Mesh we found has the appropriate number of Source models/LODs + int32 NeededNumberOfLODs = FMath::Max(NumberOfLODs + (bHasMainGeo ? 1 : 0), LODGroup.GetDefaultNumLODs()); + + // LODs are only for the "main" mesh, not for complex colliders! + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + NeededNumberOfLODs = FMath::Max(1, LODGroup.GetDefaultNumLODs()); + + if (FoundStaticMesh->GetNumSourceModels() != NeededNumberOfLODs) + { + while (FoundStaticMesh->GetNumSourceModels() < NeededNumberOfLODs) + FoundStaticMesh->AddSourceModel(); + + // We may have to remove excessive LOD levels + if (FoundStaticMesh->GetNumSourceModels() > NeededNumberOfLODs) + FoundStaticMesh->SetNumSourceModels(NeededNumberOfLODs); + + // Initialize their default reduction setting + for (int32 ModelLODIndex = 0; ModelLODIndex < NeededNumberOfLODs; ModelLODIndex++) + { + FoundStaticMesh->GetSourceModel(ModelLODIndex).ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); + } + FoundStaticMesh->SetLightMapResolution(LODGroup.GetDefaultLightMapResolution()); + } + + // By default, always work on the first source model, unless we're a LOD + int32 SrcModelIndex = 0; + int32 LODIndex = 0; + if (SplitType == EHoudiniSplitType::LOD) + { + for (auto& curSplit : AllSplitGroups) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(curSplit); + if (CurrentSplitType == EHoudiniSplitType::LOD + || CurrentSplitType == EHoudiniSplitType::Normal) + { + LODIndex++; + } + + if (curSplit == SplitGroupName) + break; + } + + // Fix for the case where we don't have a main geo + if(!bHasMainGeo) + LODIndex--; + } + + // Grab the appropriate SourceModel + FStaticMeshSourceModel* SrcModel = (FoundStaticMesh->IsSourceModelValid(LODIndex)) ? &(FoundStaticMesh->GetSourceModel(LODIndex)) : nullptr; + if (!SrcModel) + { + HOUDINI_LOG_ERROR( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d, %s] Could not access SourceModel for the LOD %d - skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName, LODIndex); + continue; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - PreMeshDescription in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + bool bHasNormal = false; + bool bHasTangents = false; + + // Load the existing mesh description if we don't need to rebuild the mesh + FMeshDescription* MeshDescription; + if (!bRebuildStaticMesh) + { + // We dont need to rebuild the mesh itself: + // the geometry hasn't changed, but the materials have. + // We can just reuse the old MeshDescription and reuse it. + MeshDescription = FoundStaticMesh->GetMeshDescription(LODIndex); + } + else + { + // Extract all the data needed for this split + // Start by initializing the MeshDescription for this LOD + MeshDescription = FoundStaticMesh->CreateMeshDescription(LODIndex); + FStaticMeshAttributes(*MeshDescription).Register(); + + // Mesh description uses material to create its PolygonGroups, + // so we first need to know how many different materials we have for this split + // and what vertices/indices belong to each material for remapping + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // SplitNeededVertices + // Array containing the (unique) part indices for the vertices that are needed for this split + // SplitNeededVertices[splitIndex] = PartIndex + TArray SplitNeededVertices; + //SplitNeededVertices.SetNumZeroed(SplitVertexCount); + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" so that IndicesMapper[ partIndex ] => splitIndex + TArray PartToSplitIndicesMapper; + PartToSplitIndicesMapper.Init(-1, SplitVertexList.Num()); + //TMap SplitToPartIndicesMapper; + + // SplitIndices + // Array of SplitIndices used to describe this split's polygons + TArray SplitIndices; + SplitIndices.SetNumZeroed(SplitVertexCount); + + int32 CurrentSplitIndex = 0; + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[0]) + || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[1]) + || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (PartToSplitIndicesMapper[WedgeIndices[i]] < 0) + { + // This part index has not yet been "converted" to a new split index + SplitNeededVertices.Add(WedgeIndices[i]); + PartToSplitIndicesMapper[WedgeIndices[i]] = CurrentSplitIndex; + //SplitToPartIndicesMapper.Add(CurrentSplitIndex, WedgeIndices[i]); + CurrentSplitIndex++; + } + + // Replace the old part index with the new split index + WedgeIndices[i] = PartToSplitIndicesMapper[WedgeIndices[i]]; + } + + if (!SplitIndices.IsValidIndex(ValidVertexId + 2)) + break; + + // Flip wedge indices to fix the winding order. + SplitIndices[ValidVertexId + 0] = WedgeIndices[0]; + SplitIndices[ValidVertexId + 1] = WedgeIndices[2]; + SplitIndices[ValidVertexId + 2] = WedgeIndices[1]; + + ValidVertexId += 3; + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract position for this part + UpdatePartPositionIfNeeded(); + + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + TVertexAttributesRef VertexPositions = + MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); + + MeshDescription->ReserveNewVertices(SplitNeededVertices.Num()); + for ( const int32& NeededVertexIndex : SplitNeededVertices) + { + // Create a new Vertex + FVertexID VertexID = MeshDescription->CreateVertex(); + if (PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexPositions[VertexID].X = PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexPositions[VertexID].Y = PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexPositions[VertexID].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + else + { + // Error when retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + TArray& FoundStaticMaterials = FoundStaticMesh->GetStaticMaterials(); + + // // TODO: Check if still needed for MeshDescription + // // We need to reset the Static Mesh's materials once per SM: + // // so, for the first lod, or the main geo... + // if (!MeshMaterialsHaveBeenReset && (SplitType == EHoudiniSplitType::LOD || SplitType == EHoudiniSplitType::Normal)) + // { + // FoundStaticMaterials.Empty(); + // MeshMaterialsHaveBeenReset = true; + // } + // + // // .. or for each visible complex collider + // if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + // FoundStaticMaterials.Empty(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) + { + FoundStaticMaterials.Empty(); + } + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); + + // Get this split's faces + TArray& SplitGroupFaceIndices = AllSplitFaceIndices[SplitGroupName]; + // Array holding the materials needed for this split + //TArray SplitMaterials; + // Split Material indices per face, by default all faces are set to use the first Material + TArray SplitFaceMaterialIndices; + SplitFaceMaterialIndices.SetNumZeroed(SplitGroupFaceIndices.Num()); + + bool HasHoudiniMaterials = PartUniqueMaterialIds.Num() > 0; + bool HasMaterialOverrides = PartFaceMaterialOverrides.Num() > 0; + if (!HasHoudiniMaterials && !HasMaterialOverrides) + { + // We don't have any material override or houdini material + // we just need one polygon group using the default Houdini material. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(MaterialInterface); + + // TODO: ? Add default mat to the assignement map? + } + else if (HasHoudiniMaterials && !HasMaterialOverrides) + { + // We have Houdini Material but no overrides + if (bOnlyOneFaceMaterial || PartUniqueMaterialIds.Num() == 1) + { + // We have only one Houdini material. + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(MaterialInterface); + + // TODO: ? Add the mat to the assignement map? + } + else + { + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * MaterialDefault = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + // Reset Rawmesh material face assignments. + for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) + { + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + SplitFaceMaterialIndices[FaceIdx] = *FoundUnrealMatIndex; + continue; + } + } + else + { + MaterialInterface = Cast(MaterialDefault); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + + if (MaterialInterface) + { + // Add the material to the Static mesh + //int32 UnrealMatIndex = SplitMaterials.Add(Material); + int32 UnrealMatIndex = FoundStaticMaterials.Add(MaterialInterface); + + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); + + // Update the face index + SplitFaceMaterialIndices[FaceIdx] = UnrealMatIndex; + } + } + } + } + else + { + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + + // If we have material overrides + for (int32 FaceIdx = 0; FaceIdx < SplitGroupFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitGroupFaceIndices[FaceIdx]; + + UMaterialInterface * MaterialInterface = nullptr; + int32 CurrentFaceMaterialIdx = -1; + if (PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + { + const FString & MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) + { + // Try to locate the corresponding material interface + + // Start by looking in our assignment map + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) + { + // Only try to load a material if has a chance to be valid! + MaterialInterface = Cast< UMaterialInterface >( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add this material to the map + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); + } + } + + if (!MaterialInterface) + { + // The attribute Material or its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) + { + // If everything else fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + } + } + + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + + // Update the Face Material on the mesh + SplitFaceMaterialIndices[FaceIdx] = CurrentFaceMaterialIdx; + } + } + + // Create a Polygon Group for each material slot + TPolygonGroupAttributesRef PolygonGroupImportedMaterialSlotNames = + MeshDescription->PolygonGroupAttributes().GetAttributesRef(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); + + // We must use the number of assignment materials found to reserve the number of material slots + // Don't use the SM's StaticMaterials here as we may not reserve enough polygon groups when adding more materials + int32 NumberOfMaterials = OutputAssignmentMaterials.Num(); + if (NumberOfMaterials <= 0) + { + // No materials, create a polygon group for the default one + const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = FName(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + } + else + { + MeshDescription->ReserveNewPolygonGroups(NumberOfMaterials); + //for (int32 MatIndex = 0; MatIndex < NumberOfMaterials; ++MatIndex) + for (auto& CurrentMatAssignement : OutputAssignmentMaterials) + { + const FPolygonGroupID& PolygonGroupID = MeshDescription->CreatePolygonGroup(); + PolygonGroupImportedMaterialSlotNames[PolygonGroupID] = + FName(CurrentMatAssignement.Value ? *(CurrentMatAssignement.Value->GetName()) : *(CurrentMatAssignement.Key)); + } + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Materials in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // + // VERTEX INSTANCE ATTRIBUTES + // NORMALS, TANGENTS, COLORS, UVS, Alpha + // + + // Extract the normals + UpdatePartNormalsIfNeeded(); + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + TVertexInstanceAttributesRef VertexInstanceNormals = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Normal); + + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + + // Extract the tangents + TArray SplitTangentU; + TArray SplitTangentV; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + int32 NormalCount = SplitNormals.Num(); + bool bGenerateTangents = (NormalCount > 0) && (SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0); + // Check that the number of tangents read matches the number of normals + if (SplitTangentU.Num() != NormalCount || SplitTangentV.Num() != NormalCount) + bGenerateTangents = true; + + if (bGenerateTangents && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangents = false; + } + + // Generate the tangents if needed + if (bGenerateTangents) + { + SplitTangentU.SetNumZeroed(NormalCount); + SplitTangentV.SetNumZeroed(NormalCount); + for (int32 Idx = 0; Idx + 2 < NormalCount; Idx += 3) + { + FVector TangentZ; + TangentZ.X = SplitNormals[Idx + 0]; + TangentZ.Y = SplitNormals[Idx + 2]; + TangentZ.Z = SplitNormals[Idx + 1]; + + FVector TangentX, TangentY; + TangentZ.FindBestAxisVectors(TangentX, TangentY); + + SplitTangentU[Idx + 0] = TangentX.X; + SplitTangentU[Idx + 2] = TangentX.Y; + SplitTangentU[Idx + 1] = TangentX.Z; + + SplitTangentV[Idx + 0] = TangentY.X; + SplitTangentV[Idx + 2] = TangentY.Y; + SplitTangentV[Idx + 1] = TangentY.Z; + } + } + } + TVertexInstanceAttributesRef VertexInstanceTangents = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Tangent); + TVertexInstanceAttributesRef VertexInstanceBinormalSigns = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::BinormalSign); + + // Extract the color values + UpdatePartColorsIfNeeded(); + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract the alpha values + UpdatePartAlphasIfNeeded(); + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + TVertexInstanceAttributesRef VertexInstanceColors = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::Color); + + // Extract UVs + UpdatePartUVSetsIfNeeded(true); + // See if we need to transfer uv point attributes to vertex attributes. + int32 UVSetCount = PartUVSets.Num(); + TArray> SplitUVSets; + SplitUVSets.SetNum(UVSetCount); + for (int32 TexCoordIdx = 0; TexCoordIdx < UVSetCount; TexCoordIdx++) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + } + TVertexInstanceAttributesRef VertexInstanceUVs = MeshDescription->VertexInstanceAttributes().GetAttributesRef(MeshAttribute::VertexInstance::TextureCoordinate); + VertexInstanceUVs.SetNumIndices(UVSetCount); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr extracted in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // Allocate space for the vertex instances and polygons + MeshDescription->ReserveNewVertexInstances(SplitIndices.Num()); + MeshDescription->ReserveNewPolygons(SplitIndices.Num() / 3); + //Approximately 2.5 edges per polygons + MeshDescription->ReserveNewEdges(SplitIndices.Num() * 2.5f / 3); + + bHasNormal = SplitNormals.Num() > 0; + bHasTangents = SplitTangentU.Num() > 0 && SplitTangentV.Num() > 0; + bool bHasRGB = SplitColors.Num() > 0; + bool bHasRGBA = bHasRGB && AttribInfoColors.tupleSize == 4; + bool bHasAlpha = SplitAlphas.Num() > 0; + + TArray HasUVSets; + HasUVSets.SetNumZeroed(PartUVSets.Num()); + for (int32 Idx = 0; Idx < PartUVSets.Num(); Idx++) + HasUVSets[Idx] = PartUVSets[Idx].Num() > 0; + + uint32 FaceCount = SplitIndices.Num() / 3; + for (uint32 FaceIndex = 0; FaceIndex < FaceCount; FaceIndex++) + { + TArray FaceVertexInstanceIDs; + FaceVertexInstanceIDs.SetNum(3); + + // Ignore degenerate triangles + FVertexID VertexIDs[3]; + for (int32 Corner = 0; Corner < 3; ++Corner) + { + VertexIDs[Corner] = FVertexID(SplitIndices[(FaceIndex * 3) + Corner]); + } + if (VertexIDs[0] == VertexIDs[1] || VertexIDs[0] == VertexIDs[2] || VertexIDs[1] == VertexIDs[2]) + continue; + + //FVertexID FaceVertexIDs[3]; + for (int32 Corner = 0; Corner < 3; Corner++) + { + uint32 SplitIndex = (FaceIndex * 3) + Corner; + uint32 SplitVertexIndex = SplitIndices[SplitIndex]; + const FVertexInstanceID& VertexInstanceID = MeshDescription->CreateVertexInstance(FVertexID(SplitVertexIndex)); + + // Fix the winding order by updating the SplitIndex (invert corner 1 and 2) + // instead of going 0 1 2 go 0 2 1 + // TODO; this slows down StaticMesh->Build() considerably! + Corner == 1 ? SplitIndex++ : Corner == 2 ? SplitIndex-- : SplitIndex; + + const uint32 SplitVertexIndex_X = SplitIndex * 3 + 0; + const uint32 SplitVertexIndex_Y = SplitIndex * 3 + 2; + const uint32 SplitVertexIndex_Z = SplitIndex * 3 + 1; + // Normals + if (bHasNormal) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexInstanceNormals[VertexInstanceID].X = SplitNormals[SplitVertexIndex_X]; + VertexInstanceNormals[VertexInstanceID].Y = SplitNormals[SplitVertexIndex_Y]; + VertexInstanceNormals[VertexInstanceID].Z = SplitNormals[SplitVertexIndex_Z]; + } + + // Tangents and binormals + if (bHasTangents) + { + // We need to swap Z and Y coordinate here, and convert from m to cm. + VertexInstanceTangents[VertexInstanceID].X = SplitTangentU[SplitVertexIndex_X]; + VertexInstanceTangents[VertexInstanceID].Y = SplitTangentU[SplitVertexIndex_Y]; + VertexInstanceTangents[VertexInstanceID].Z = SplitTangentU[SplitVertexIndex_Z]; + + FVector TangentY; + TangentY.X = SplitTangentV[SplitVertexIndex_X]; + TangentY.Y = SplitTangentV[SplitVertexIndex_Y]; + TangentY.Z = SplitTangentV[SplitVertexIndex_Z]; + + VertexInstanceBinormalSigns[VertexInstanceID] = GetBasisDeterminantSign( + VertexInstanceTangents[VertexInstanceID].GetSafeNormal(), + TangentY.GetSafeNormal(), + VertexInstanceNormals[VertexInstanceID].GetSafeNormal()); + } + + // Color + FLinearColor Color = FLinearColor::White; + if (bHasRGB) + { + Color.R = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 0], 0.0f, 1.0f); + Color.G = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 1], 0.0f, 1.0f); + Color.B = FMath::Clamp( + SplitColors[SplitIndex * AttribInfoColors.tupleSize + 2], 0.0f, 1.0f); + } + // Alpha + if (bHasAlpha) + { + Color.A = FMath::Clamp(SplitAlphas[SplitIndex], 0.0f, 1.0f); + } + else if (bHasRGBA) + { + Color.A = FMath::Clamp(SplitColors[SplitIndex * AttribInfoColors.tupleSize + 3], 0.0f, 1.0f); + } + VertexInstanceColors[VertexInstanceID] = FVector4(Color); + + // UVs + for (int32 UVIndex = 0; UVIndex < SplitUVSets.Num(); UVIndex++) + { + if (HasUVSets[UVIndex]) + { + // We need to flip V coordinate when it's coming from HAPI. + FVector2D CurrentUV; + CurrentUV.X = SplitUVSets[UVIndex][SplitIndex * 2 + 0]; + CurrentUV.Y = 1.0f - SplitUVSets[UVIndex][SplitIndex * 2 + 1]; + + VertexInstanceUVs.Set(VertexInstanceID, UVIndex, CurrentUV); + } + } + + FaceVertexInstanceIDs[Corner] = VertexInstanceID; + } + + const FPolygonGroupID PolygonGroupID(SplitFaceMaterialIndices[FaceIndex]); + + // Insert a triangle into the mesh + MeshDescription->CreateTriangle(PolygonGroupID, FaceVertexInstanceIDs); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - VertexAttr filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's FaceSmoothing values if needed + UpdatePartFaceSmoothingIfNeeded(); + + // Get the FaceSmoothing values for this split + TArray SplitFaceSmoothingMasks; + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoFaceSmoothingMasks, PartFaceSmoothingMasks, SplitFaceSmoothingMasks); + + // FaceSmoothing masks must be initialized even if we don't have a value from Houdini! + // TODO: Expose the default FaceSmoothing value + // 0 will make hard face + TArray FaceSmoothingMasks; + FaceSmoothingMasks.Init(DefaultMeshSmoothing, SplitVertexCount / 3); + + // Check that the number of face smoothing values we retrieved is correct + int32 WedgeFaceSmoothCount = SplitFaceSmoothingMasks.Num() / 3; + if (SplitFaceSmoothingMasks.Num() != 0 && !SplitFaceSmoothingMasks.IsValidIndex((WedgeFaceSmoothCount - 1) * 3 + 2)) + { + // Ignore our face smoothing values + WedgeFaceSmoothCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid face smoothing mask count detected - Skipping them.")); + } + + // Transfer the face smoothing masks to the raw mesh if we have any + for (int32 WedgeFaceSmoothIdx = 0; WedgeFaceSmoothIdx < WedgeFaceSmoothCount; WedgeFaceSmoothIdx += 3) + { + FaceSmoothingMasks[WedgeFaceSmoothIdx] = SplitFaceSmoothingMasks[WedgeFaceSmoothIdx * 3]; + } + + // TODO + // Check + FStaticMeshOperations::ConvertSmoothGroupToHardEdges(FaceSmoothingMasks, *MeshDescription); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - FaceSoothing filled in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + // Extract this part's LightmapResolution values if needed + UpdatePartLightmapResolutionsIfNeeded(); + + // make sure the mesh has a new lighting guid + FoundStaticMesh->SetLightingGuid(FGuid::NewGuid()); + } + + // Update the Build Settings using the default setting values + UpdateMeshBuildSettings( + SrcModel->BuildSettings, + bHasNormal, + bHasTangents, + PartUVSets.Num() > 0); + + // Set the lightmap Coordinate Index + // If we have more than one UV set, the 2nd valid set is used for lightmaps by convention + FoundStaticMesh->SetLightMapCoordinateIndex(PartUVSets.Num() > 1 ? 1 : 0); + + // Check for a lightmapa resolution override + int32 LightMapResolutionOverride = -1; + if ( PartLightMapResolutions.Num() > 0) + LightMapResolutionOverride = PartLightMapResolutions[0]; + + if (LightMapResolutionOverride > 0) + FoundStaticMesh->SetLightMapResolution(LightMapResolutionOverride); + else + FoundStaticMesh->SetLightMapResolution(64); + + // TODO: + // Turnoff bGenerateLightmapUVs if lightmap uv sets has bad uvs ? + + // RAW MESH CHECKS + + // TODO: Check not needed w/ FMeshDesc + // This is required due to the impeding deprecation of FRawMesh + // If we dont update this UE4 will crash upon deleting an asset. + //SrcModel->StaticMeshOwner = FoundStaticMesh; + + // Check if the mesh has at least one triangle, if not, log a message + if (MeshDescription->Triangles().Num() == 0) + { + HOUDINI_LOG_WARNING( + TEXT("[CreateStaticMesh_MeshDescription]: 0 valid triangles in StaticMesh data for %s LOD %i! Please check the log."), + *FoundStaticMesh->GetName(), LODIndex); + } + + // Store the new MeshDescription + FoundStaticMesh->CommitMeshDescription(LODIndex); + //Set the Imported version before calling the build + FoundStaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion; + + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = GetLODSCreensizeForSplit(SplitGroupName); + if (screensize >= 0.0f) + { + // Only apply the LOD screensize if it's valid + SrcModel->ScreenSize = screensize; + //FoundStaticMesh->GetSourceModel(LodIndex).ScreenSize = screensize; + FoundStaticMesh->bAutoComputeLODScreenSize = false; + } + + // SET STATIC MESH GENERATION PARAM + // HANDLE COLLIDERS + // REMOVE OLD COLLIDERS + // CUSTOM BAKE NAME OVERRIDE + + // UPDATE UPROPERTY ATTRIBS + // Update property attributes on the SM + TArray PropertyAttributes; + if (FHoudiniEngineUtils::GetGenericPropertiesAttributes( + HGPO.GeoId, HGPO.PartId, + true, + AllSplitFirstValidPrimIndex[SplitGroupName], + INDEX_NONE, + AllSplitFirstValidVertexIndex[SplitGroupName], + PropertyAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + FoundStaticMesh, PropertyAttributes); + } + + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(HGPO.GeoId, HGPO.PartId, LevelPaths, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(HGPO.GeoId, HGPO.PartId, OutputNames, 0, 1)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray TileValues; + if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + { + // cache the tile attribute as a token on the output object + FoundOutputObject->CachedTokens.Add(TEXT("tile"), FString::FromInt(TileValues[0])); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(HGPO.GeoId, HGPO.PartId, BakeOutputActorNames, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(HGPO.GeoId, BakeFolders, HGPO.PartId, 0, 1)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(HGPO.GeoId, HGPO.PartId, BakeOutlinerFolders, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + // Notify that we created a new Static Mesh if needed + if(bNewStaticMeshCreated) + FAssetRegistryModule::AssetCreated(FoundStaticMesh); + + // Add the Static mesh to the output maps and the build map if we haven't already + if (FoundOutputObject) + { + FoundOutputObject->OutputObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = false; + FoundOutputObject->bIsImplicit = false; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + + StaticMeshToBuild.FindOrAdd(OutputObjectIdentifier, FoundStaticMesh); + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Finished Split in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Total Split time: %f seconds."), tick - split_tick); + } + } + + // Look if we only have colliders + // If we do, we'll allow attaching sockets to the collider meshes + bool bCollidersOnly = true; + for (auto& Current : StaticMeshToBuild) + { + EHoudiniSplitType CurrentSplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + if (CurrentSplitType == EHoudiniSplitType::LOD || CurrentSplitType == EHoudiniSplitType::Normal) + { + bCollidersOnly = false; + break; + } + } + + FHoudiniScopedGlobalSilence ScopedGlobalSilence; + for (auto& Current : StaticMeshToBuild) + { + tick = FPlatformTime::Seconds(); + + UStaticMesh* SM = Current.Value; + if (!SM || SM->IsPendingKill()) + continue; + + UBodySetup * BodySetup = SM->GetBodySetup(); + if (!BodySetup) + { + SM->CreateBodySetup(); + BodySetup = SM->GetBodySetup(); + } + + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); + + // Handle the Static Mesh's colliders + if (BodySetup && !BodySetup->IsPendingKill()) + { + // Make sure rendering is done - so we are not changing data being used by collision drawing. + FlushRenderingCommands(); + + // Clean up old colliders from a previous cook + BodySetup->Modify(); + BodySetup->RemoveSimpleCollision(); + // Create new GUID + BodySetup->InvalidatePhysicsData(); + + FHoudiniOutputObjectIdentifier CurrentObjId = Current.Key; + FKAggregateGeom* CurrentAggColl = AllAggregateCollisions.Find(Current.Key); + if (CurrentAggColl && CurrentAggColl->GetElementCount() > 0) + { + BodySetup->AddCollisionFrom(*CurrentAggColl); + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseDefault; + } + + // Moved RefreshCollisionChange to after the SM->Build call + // RefreshCollisionChange(*SM); + // SM->bCustomizedCollision = true; + + // See if we need to enable collisions on the whole mesh + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider || SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + // Complex collider, enable collisions for this static mesh. + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + ApplyComplexColliderHelper( + MainStaticMesh, + SM, + SplitType, + bAssignedCustomCollisionMesh, + OutputObjects.Find(Current.Key)); + } + else + { + // TODO + // if the LODForCollision uproperty attribute is set, we need to activate complex collision + // on the static mesh for that lod to be picked up properly as a collider + if ( FHoudiniEngineUtils::HapiCheckAttributeExists( HGPO.GeoId, HGPO.PartId, + "unreal_uproperty_LODForCollision", HAPI_ATTROWNER_DETAIL)) + { + BodySetup->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple; + } + } + } + + // Add the Sockets to the StaticMesh + // We only add them to the main geo, or to the colliders if we only generate colliders + bool bAddSocket = SplitType == EHoudiniSplitType::Normal ? true : bCollidersOnly ? true : false; + if (bAddSocket) + { + if (!FHoudiniEngineUtils::AddMeshSocketsToStaticMesh(SM, HGPO.AllMeshSockets, true)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import sockets for StaticMesh %s."), *(SM->GetName())); + } + } + + if (MainStaticMesh) + { + UBodySetup* MainBodySetup = MainStaticMesh->GetBodySetup(); + if (!IsValid(MainBodySetup)) + { + MainStaticMesh->CreateBodySetup(); + MainBodySetup = MainStaticMesh->GetBodySetup(); + } + + check(MainBodySetup); + // Set the main static mesh to whatever the final CTF should be. + MainBodySetup->CollisionTraceFlag = MainStaticMeshCTF; + } + + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Pre SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + // BUILD the Static Mesh + // bSilent doesnt add the Build Errors... + double build_start = FPlatformTime::Seconds(); + TArray SMBuildErrors; + SM->Build(true, &SMBuildErrors); + + if (bDoTiming) + { + tick = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - StaticMesh->Build() executed in %f seconds."), tick - build_start); + } + + // This replaces the call to RefreshCollision below, but without CreateNavCollision + // as it is already called by UStaticMesh::PostBuildInternal as part of the ::Build call, + // and can be expensive depending on the vert/poly count of the mesh + // RefreshCollisionChange(*SM); + { + for (FThreadSafeObjectIterator Iter(UStaticMeshComponent::StaticClass()); Iter; ++Iter) + { + UStaticMeshComponent* StaticMeshComponent = Cast(*Iter); + if (StaticMeshComponent->GetStaticMesh() == SM) + { + // it needs to recreate IF it already has been created + if (StaticMeshComponent->IsPhysicsStateCreated()) + { + StaticMeshComponent->RecreatePhysicsState(); + } + } + } + + FEditorSupportDelegates::RedrawAllViewports.Broadcast(); + } + + SM->GetOnMeshChanged().Broadcast(); + + UPackage* MeshPackage = SM->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + } + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Post SM->Build() in %f seconds."), FPlatformTime::Seconds() - tick); + } + } + + // !!! No need to call InvalidatePhysicsData / CreatePhysicsMeshes / GetNavCollision()->Setup + // Here as it has already been handled by the StaticMesh Build call + + double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() executed in %f seconds."), time_end - time_start); + + return true; +} + +bool +FHoudiniMeshTranslator::CreateHoudiniStaticMesh() +{ + // Time limit for processing + bool bDoTiming = CVarHoudiniEngineMeshBuildTimer.GetValueOnAnyThread() != 0.0; + + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh")); + + const double time_start = FPlatformTime::Seconds(); + + // Start by updating the vertex list + if (!UpdatePartVertexList()) + return false; + + // Sort the split groups + SortSplitGroups(); + + // Handles the split groups found in the part + // and builds the corresponding faces and indices arrays + if (!UpdateSplitsFacesAndIndices()) + return true; + + // Resets the containers used for the raw data extraction. + ResetPartCache(); + + // Determine if there is "main" geo, if not we'll use the first LOD + // as main geo + bool bHasMainGeo = false; + for (auto& curSplit : AllSplitGroups) + { + if (GetSplitTypeFromSplitName(curSplit) == EHoudiniSplitType::Normal) + { + bHasMainGeo = true; + break; + } + } + + // Update the part's material's IDS and info now + //UpdatePartFaceMaterialsIfNeeded(); + CreateNeededMaterials(); + + // Check if the materials were updated + bool bMaterialHasChanged = false; + for (const auto& MatInfo : PartUniqueMaterialInfos) + { + if (MatInfo.hasChanged) + { + bMaterialHasChanged = true; + break; + } + } + + // Map of Houdini Material IDs to Unreal Material Interface + TMap MapHoudiniMatIdToUnrealInterface; + // Map of Houdini Material Attributes to Unreal Material Interface + TMap MapHoudiniMatAttributesToUnrealInterface; + // Map of Unreal Material Interface to Unreal Material Index, per visible mesh + TMap> MapUnrealMaterialInterfaceToUnrealIndexPerMesh; + + // bool MeshMaterialsHaveBeenReset = false; + + double tick = FPlatformTime::Seconds(); + if(bDoTiming) + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - Pre Split-Loop in %f seconds."), tick - time_start); + + // Iterate through all detected split groups we care about and split geometry. + bool bMainGeoOrFirstLODFound = false; + for (int32 SplitId = 0; SplitId < AllSplitGroups.Num(); SplitId++) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Per Split")); + + // Get split group name + const FString& SplitGroupName = AllSplitGroups[SplitId]; + + // Get the current split type + EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(SplitGroupName); + if (SplitType == EHoudiniSplitType::Invalid) + { + // Invalid split, skip + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] unknown split type.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // We are only interested in the Normal/main geo and visible colliders + if (SplitType != EHoudiniSplitType::Normal && + SplitType != EHoudiniSplitType::LOD && + SplitType != EHoudiniSplitType::RenderedComplexCollider && + SplitType != EHoudiniSplitType::RenderedSimpleCollider && + SplitType != EHoudiniSplitType::RenderedUCXCollider) + { + continue; + } + + // We only use LOD if there is no Normal geo + if (SplitType == EHoudiniSplitType::Normal) + { + bMainGeoOrFirstLODFound = true; + HOUDINI_LOG_MESSAGE(TEXT("Found Normal geo for mesh.")); + } + else if (SplitType == EHoudiniSplitType::LOD) + { + if (bHasMainGeo) + { + HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since the mesh has Normal geo.")); + continue; + } + else if (bMainGeoOrFirstLODFound) + { + HOUDINI_LOG_MESSAGE(TEXT("Skipping LOD since we have already processed the first LOD.")); + continue; + } + else + { + bMainGeoOrFirstLODFound = true; + HOUDINI_LOG_MESSAGE(TEXT("Mesh does not have Normal geo, found first LOD.")); + } + } + + // Get the vertex indices for this group + TArray& SplitVertexList = AllSplitVertexLists[SplitGroupName]; + + // Get valid count of vertex indices for this split. + const int32& SplitVertexCount = AllSplitVertexCounts[SplitGroupName]; + + // Make sure we have a valid vertex count for this split + if (SplitVertexCount % 3 != 0 || SplitVertexList.Num() % 3 != 0) + { + // Invalid vertex count, skip this split or we'd crash trying to create a mesh for it. + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid vertex count.") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + + continue; + } + + // Get the output identifer for this split + FHoudiniOutputObjectIdentifier OutputObjectIdentifier( + HGPO.ObjectId, HGPO.GeoId, HGPO.PartId, GetMeshIdentifierFromSplit(SplitGroupName, SplitType)); + OutputObjectIdentifier.PartName = HGPO.PartName; + OutputObjectIdentifier.PrimitiveIndex = AllSplitFirstValidVertexIndex[SplitGroupName]; + OutputObjectIdentifier.PointIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + + // Try to find existing properties for this identifier + FHoudiniOutputObject* FoundOutputObject = InputObjects.Find(OutputObjectIdentifier); + // Try to find an existing DM from a previous cook + UHoudiniStaticMesh* FoundStaticMesh = FindExistingHoudiniStaticMesh(OutputObjectIdentifier); + + // Flag whether or not we need to rebuild the mesh + bool bRebuildStaticMesh = false; + if (HGPO.GeoInfo.bHasGeoChanged || HGPO.PartInfo.bHasChanged || ForceRebuild || !FoundStaticMesh || !FoundOutputObject) + bRebuildStaticMesh = true; + + // TODO: Handle materials + if (!bRebuildStaticMesh && !bMaterialHasChanged) + { + // We can simply reuse the found static mesh + OutputObjects.Add(OutputObjectIdentifier, *FoundOutputObject); + continue; + } + + bool bNewStaticMeshCreated = false; + if (!FoundStaticMesh) + { + // If we couldn't find a valid existing dynamic mesh, create a new one + FoundStaticMesh = CreateNewHoudiniStaticMesh(OutputObjectIdentifier.SplitIdentifier); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + continue; + + bNewStaticMeshCreated = true; + } + + if (!FoundOutputObject) + { + // If we couldnt find a previous output object, create a new one + FHoudiniOutputObject NewOutputObject; + FoundOutputObject = &OutputObjects.Add(OutputObjectIdentifier, NewOutputObject); + } + FoundOutputObject->bProxyIsCurrent = true; + + if (bDoTiming) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() - PreBuildMesh in %f seconds."), FPlatformTime::Seconds() - tick); + tick = FPlatformTime::Seconds(); + } + + if (bRebuildStaticMesh) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build/Rebuild UHoudiniStaticMesh")); + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES + //--------------------------------------------------------------------------------------------------------------------- + + // + // Because of the splits, we don't need to declare all the vertices in the Part, + // but only the one that are currently used by the split's faces. + // The indicesMapper array is used to map those indices from Part Vertices to Split Vertices. + // We also keep track of the needed vertices index to declare them easily afterwards. + // + + // IndicesMapper: + // Maps index values for all vertices in the Part: + // - Vertices unused by the split will be set to -1 + // - Used vertices will have their value set to the "NewIndex" + // So that IndicesMapper[ oldIndex ] => newIndex + TArray IndicesMapper; + IndicesMapper.Init(-1, SplitVertexList.Num()); + int32 CurrentMapperIndex = 0; + + // NeededVertices: + // Array containing the old index of the needed vertices for the current split + // NeededVertices[ newIndex ] => oldIndex + TArray< int32 > NeededVertices; + NeededVertices.Reserve(SplitVertexList.Num() / 3); + TArray< int32 > TriangleIndices; + TriangleIndices.Reserve(SplitVertexList.Num()); + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build IndicesMapper and NeededVertices")); + + int32 ValidVertexId = 0; + for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) + { + int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; + if (WedgeCheck == -1) + continue; + + int32 WedgeIndices[3] = + { + SplitVertexList[VertexIdx + 0], + SplitVertexList[VertexIdx + 1], + SplitVertexList[VertexIdx + 2] + }; + + // Ensure the indices are valid + if (!IndicesMapper.IsValidIndex(WedgeIndices[0]) + || !IndicesMapper.IsValidIndex(WedgeIndices[1]) + || !IndicesMapper.IsValidIndex(WedgeIndices[2])) + { + // Invalid face index. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // Converting Old (Part) Indices to New (Split) Indices: + for (int32 i = 0; i < 3; i++) + { + if (IndicesMapper[WedgeIndices[i]] < 0) + { + // This old index has not yet been "converted" to a new index + NeededVertices.Add(WedgeIndices[i]); + IndicesMapper[WedgeIndices[i]] = CurrentMapperIndex; + CurrentMapperIndex++; + } + + // Replace the old index with the new one + WedgeIndices[i] = IndicesMapper[WedgeIndices[i]]; + } + + // Flip wedge indices to fix the winding order. + TriangleIndices.Add(WedgeIndices[0]); + TriangleIndices.Add(WedgeIndices[2]); + TriangleIndices.Add(WedgeIndices[1]); + + ValidVertexId += 3; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's normal if needed + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray SplitNormals; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoNormals, PartNormals, SplitNormals); + + // Check that the number of normal we retrieved is correct + int32 NormalCount = SplitNormals.Num() / 3; + if (NormalCount < 0 || NormalCount < NeededVertices.Num()) + { + // Ignore normals + NormalCount = 0; + HOUDINI_LOG_WARNING(TEXT("Invalid normal count detected - Skipping normals.")); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENTS + //--------------------------------------------------------------------------------------------------------------------- + + TArray SplitTangentU; + TArray SplitTangentV; + int32 TangentUCount = 0; + int32 TangentVCount = 0; + // No need to read the tangents if we want unreal to recompute them after + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + bool bReadTangents = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->RecomputeTangentsFlag != EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always : true; + + bool bGenerateTangentsFromNormalAttribute = false; + if (bReadTangents) + { + // Extract this part's Tangents if needed + UpdatePartTangentsIfNeeded(); + + // Get the Tangents for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentU, PartTangentU, SplitTangentU); + + // Get the binormals for this split + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoTangentV, PartTangentV, SplitTangentV); + + if ((SplitTangentU.Num() <= 0 || SplitTangentV.Num() <= 0)) + bReadTangents = false; + + // We need to manually generate tangents if: + // - we have normals but dont have tangentu or tangentv attributes + // - we have not specified that we wanted unreal to generate them + bGenerateTangentsFromNormalAttribute = (NormalCount > 0) && !bReadTangents; + + // Check that the number of tangents read matches the number of normals + TangentUCount = SplitTangentU.Num() / 3; + TangentVCount = SplitTangentV.Num() / 3; + if (NormalCount > 0 && (TangentUCount != NormalCount || TangentVCount != NormalCount)) + { + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh: Generate tangents due to count mismatch (# U Tangents = %d; # V Tangents = %d; # Normals = %d)"), TangentUCount, TangentVCount, NormalCount); + bGenerateTangentsFromNormalAttribute = true; + bReadTangents = false; + } + + if (bGenerateTangentsFromNormalAttribute && (HoudiniRuntimeSettings->RecomputeTangentsFlag == EHoudiniRuntimeSettingsRecomputeFlag::HRSRF_Always)) + { + // No need to generate tangents if we want unreal to recompute them after + bGenerateTangentsFromNormalAttribute = false; + } + } + else + { + bGenerateTangentsFromNormalAttribute = (NormalCount > 0); + } + + //--------------------------------------------------------------------------------------------------------------------- + // VERTEX COLORS AND ALPHAS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's colors if needed + UpdatePartColorsIfNeeded(); + + // Get the colors values for this split + TArray SplitColors; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoColors, PartColors, SplitColors); + + // Extract this part's alpha values if needed + UpdatePartAlphasIfNeeded(); + + // Get the colors values for this split + TArray SplitAlphas; + FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + SplitVertexList, AttribInfoAlpha, PartAlphas, SplitAlphas); + + const int32 ColorsCount = AttribInfoColors.exists ? SplitColors.Num() / AttribInfoColors.tupleSize : 0; + const bool bSplitColorValid = AttribInfoColors.exists && (AttribInfoColors.tupleSize >= 3) && ColorsCount > 0; + const bool bSplitAlphaValid = AttribInfoAlpha.exists && (SplitAlphas.Num() == ColorsCount); + + //--------------------------------------------------------------------------------------------------------------------- + // UVS + //--------------------------------------------------------------------------------------------------------------------- + + // Extract this part's UV sets if needed + UpdatePartUVSetsIfNeeded(); + + // See if we need to transfer uv point attributes to vertex attributes. + int32 NumUVLayers = 0; + TArray> SplitUVSets; + SplitUVSets.SetNum(MAX_STATIC_TEXCOORDS); + for (int32 TexCoordIdx = 0; TexCoordIdx < MAX_STATIC_TEXCOORDS; ++TexCoordIdx) + { + FHoudiniMeshTranslator::TransferPartAttributesToSplit( + SplitVertexList, AttribInfoUVSets[TexCoordIdx], PartUVSets[TexCoordIdx], SplitUVSets[TexCoordIdx]); + if (SplitUVSets[TexCoordIdx].Num() > 0) + { + NumUVLayers++; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL ATTRIBUTE OVERRIDES + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: These are actually per faces, not per vertices... + // Need to update!! + UpdatePartFaceMaterialOverridesIfNeeded(); + + // + // Initialize mesh + // + const int32 NumVertexPositions = NeededVertices.Num(); + const int32 NumTriangles = TriangleIndices.Num() / 3; + const bool bHasPerFaceMaterials = PartFaceMaterialOverrides.Num() > 0 || (PartUniqueMaterialIds.Num() > 0 && !bOnlyOneFaceMaterial); + + FoundStaticMesh->Initialize( + NumVertexPositions, + NumTriangles, + NumUVLayers, // NumUVLayers + 0, // InitialNumStaticMaterials + NormalCount > 0, // HasNormals + bReadTangents || bGenerateTangentsFromNormalAttribute, // HasTangents + bSplitColorValid, // HasColors + bHasPerFaceMaterials // HasPerFaceMaterials + ); + + //--------------------------------------------------------------------------------------------------------------------- + // POSITIONS + //--------------------------------------------------------------------------------------------------------------------- + UpdatePartPositionIfNeeded(); + + // + // Transfer vertex positions: + // + // Because of the split, we're only interested in the needed vertices. + // Instead of declaring all the Positions, we'll only declare the vertices + // needed by the current split. + // + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Vertex Positions")); + + for (int32 VertexPositionIdx = 0; VertexPositionIdx < NumVertexPositions; ++VertexPositionIdx) + //ParallelFor(NumVertexPositions, [&](uint32 VertexPositionIdx) + { + int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; + if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) + { + // Error retrieving positions. + HOUDINI_LOG_WARNING( + TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + continue; + } + + // We need to swap Z and Y coordinate here, and convert from m to cm. + FoundStaticMesh->SetVertexPosition(VertexPositionIdx, FVector( + PartPositions[NeededVertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION, + PartPositions[NeededVertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION, + PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION + )); + }//); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACES / TRIS + // Now set Normals, UVs and Colors on mesh points and AttributeSet + //--------------------------------------------------------------------------------------------------------------------- + + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Triangle Indices & Per Vertex Instance Attribute Values")); + + // Now add the triangles to the mesh + for (int32 TriangleIdx = 0; TriangleIdx < NumTriangles; ++TriangleIdx) + // ParallelFor(NumTriangles, [&](uint32 TriangleIdx) + { + // TODO: add some additional intermediate consts for index calculations to make the indexing + // TODO: code a bit more readable + const int32 TriVertIdx0 = TriangleIdx * 3; + FoundStaticMesh->SetTriangleVertexIndices(TriangleIdx, FIntVector( + TriangleIndices[TriVertIdx0 + 0], + TriangleIndices[TriVertIdx0 + 1], + TriangleIndices[TriVertIdx0 + 2] + )); + + const int32 TriWindingIndex[3] = { 0, 2, 1 }; + // Normals and tangents (either getting tangents from attributes or generating tangents from the + // normals + if (NormalCount > 0 || bReadTangents) + { + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + const bool bHasNormal = (NormalCount > 0 && SplitNormals.IsValidIndex(TriVertIdx0 * 3 + 3 * 3 - 1)); + FVector Normal = FVector::ZeroVector; + if (bHasNormal) + { + // Flip Z and Y coordinate for normal, but don't scale + Normal.Set( + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 0], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 2], + SplitNormals[TriVertIdx0 * 3 + 3 * ElementIdx + 1] + ); + + FoundStaticMesh->SetTriangleVertexNormal(TriangleIdx, TriWindingIndex[ElementIdx], Normal); + } + + if (bReadTangents || bGenerateTangentsFromNormalAttribute) + { + FVector TangentU, TangentV; + if (bGenerateTangentsFromNormalAttribute) + { + if (bHasNormal) + { + // Generate the tangents if needed + Normal.FindBestAxisVectors(TangentU, TangentV); + + FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); + FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + } + } + else + { + // Transfer the tangents from Houdini + TangentU.X = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; + TangentU.Y = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; + TangentU.Z = SplitTangentU[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; + + TangentU.X = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 0]; + TangentU.Y = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 2]; + TangentU.Z = SplitTangentV[TriVertIdx0 * 3 + 3 * ElementIdx + 1]; + + FoundStaticMesh->SetTriangleVertexUTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentU); + FoundStaticMesh->SetTriangleVertexVTangent(TriangleIdx, TriWindingIndex[ElementIdx], TangentV); + } + } + } + } + + // Vertex Colors + if (bSplitColorValid && SplitColors.IsValidIndex(TriVertIdx0 * AttribInfoColors.tupleSize + 3 * AttribInfoColors.tupleSize - 1)) + { + FLinearColor VertexLinearColor; + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + VertexLinearColor.R = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 0], 0.0f, 1.0f); + VertexLinearColor.G = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 1], 0.0f, 1.0f); + VertexLinearColor.B = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 2], 0.0f, 1.0f); + + if (bSplitAlphaValid) + { + VertexLinearColor.A = FMath::Clamp(SplitAlphas[TriVertIdx0 + ElementIdx], 0.0f, 1.0f); + } + else if (AttribInfoColors.tupleSize >= 4) + { + VertexLinearColor.A = FMath::Clamp( + SplitColors[TriVertIdx0 * AttribInfoColors.tupleSize + AttribInfoColors.tupleSize * ElementIdx + 3], 0.0f, 1.0f); + } + else + { + VertexLinearColor.A = 1.0f; + } + const FColor VertexColor = VertexLinearColor.ToFColor(false); + FoundStaticMesh->SetTriangleVertexColor(TriangleIdx, TriWindingIndex[ElementIdx], VertexColor); + } + } + + // UVs + if (NumUVLayers > 0) + { + // Dynamic mesh supports only 1 UV layer on the mesh it self. So we set the first layer + // on the mesh itself only, and we set all layers on the AttributeSet + for (int32 TexCoordIdx = 0; TexCoordIdx < NumUVLayers; ++TexCoordIdx) + { + const TArray& SplitUVs = SplitUVSets[TexCoordIdx]; + if (SplitUVs.IsValidIndex(TriVertIdx0 * 2 + 3 * 2 - 1)) + { + for (int32 ElementIdx = 0; ElementIdx < 3; ++ElementIdx) + { + const int32 UVIdx = TriVertIdx0 * 2 + ElementIdx * 2; + // We need to flip V coordinate when it's coming from HAPI. + const FVector2D UV(SplitUVs[UVIdx + 0], 1.0f - SplitUVs[UVIdx + 1]); + // Set the UV on the vertex instance in the UVLayer + FoundStaticMesh->SetTriangleVertexUV(TriangleIdx, TriWindingIndex[ElementIdx], TexCoordIdx, UV); + } + } + } + } + } + } + + FMeshBuildSettings BuildSettings; + UpdateMeshBuildSettings( + BuildSettings, + FoundStaticMesh->HasNormals(), + FoundStaticMesh->HasTangents(), + false); + // Compute normals if requested or needed/missing + if (BuildSettings.bRecomputeNormals) + { + FoundStaticMesh->CalculateNormals(BuildSettings.bComputeWeightedNormals); + } + + // Compute tangents if requested or needed/missing + if (BuildSettings.bRecomputeTangents) + { + FoundStaticMesh->CalculateTangents(BuildSettings.bComputeWeightedNormals); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIALS / FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + + // Get face indices for this split. + TArray& SplitFaceIndices = AllSplitFaceIndices[SplitGroupName]; + + // Fetch the FoundMesh's Static Materials array + TArray& FoundStaticMaterials = FoundStaticMesh->GetStaticMaterials(); + + // Clear the materials array of the mesh the first time we encounter it + if (!MapUnrealMaterialInterfaceToUnrealIndexPerMesh.Contains(FoundStaticMesh)) + { + FoundStaticMaterials.Empty(); + } + TMap& MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh = MapUnrealMaterialInterfaceToUnrealIndexPerMesh.FindOrAdd(FoundStaticMesh); + + // Process material overrides first + if (PartFaceMaterialOverrides.Num() > 0) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Material Overrides")); + + // Array used to avoid constantly attempting to load invalid materials + TArray InvalidMaterials; + + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialOverrides.IsValidIndex(SplitFaceIndex)) + continue; + + UMaterialInterface * MaterialInterface = nullptr; + int32 CurrentFaceMaterialIdx = 0; + const FString& MaterialName = PartFaceMaterialOverrides[SplitFaceIndex]; + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatAttributesToUnrealInterface.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (!MaterialInterface) + { + // Try to locate the corresponding material interface + + // Start by looking in our assignment map + FoundMaterialInterface = OutputAssignmentMaterials.Find(MaterialName); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + // Only try to load a material if it has a chance to be valid! + if (!MaterialInterface && !MaterialName.IsEmpty() && !InvalidMaterials.Contains(MaterialName)) + { + MaterialInterface = Cast( + StaticLoadObject(UMaterialInterface::StaticClass(), + nullptr, *MaterialName, nullptr, LOAD_NoWarn, nullptr)); + + if (!MaterialInterface) + InvalidMaterials.Add(MaterialName); + } + + if (MaterialInterface) + { + // We managed to load the UE4 material + // Make sure this material is in the assignments before replacing it. + OutputAssignmentMaterials.Add(MaterialName, MaterialInterface); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const *ReplacementMaterialInterface = ReplacementMaterials.Find(MaterialName); + if (ReplacementMaterialInterface && *ReplacementMaterialInterface) + MaterialInterface = *ReplacementMaterialInterface; + + // Add this material to the map + MapHoudiniMatAttributesToUnrealInterface.Add(MaterialName, MaterialInterface); + } + else + { + // The Attribute Material and its replacement do not exist + // See if we can fallback to the Houdini material assigned on the face + + // Get the unreal material corresponding to this houdini one + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + if (!MaterialInterface) + { + // If everything fails, we'll use the default material + MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // We need to add this material to the map + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Map the Houdini ID to the unreal one + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + } + } + + if (MaterialInterface) + { + int32 const * FoundFaceMaterialIdx = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundFaceMaterialIdx) + { + // We already know what material index to use for that override + CurrentFaceMaterialIdx = *FoundFaceMaterialIdx; + } + else + { + // Add the material to the Static mesh + CurrentFaceMaterialIdx = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, CurrentFaceMaterialIdx); + } + // Update the Face Material on the mesh + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, CurrentFaceMaterialIdx); + } + } + } + else if (PartUniqueMaterialIds.Num() > 0) + { + // The part has houdini materials + if (bOnlyOneFaceMaterial) + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Single Material")); + + // Use default Houdini material if no valid material is assigned to any of the faces. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // Get id of this single material. + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, PartFaceMaterialIds[0], MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + else + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Per Face Materials")); + + // We have multiple houdini materials + // Get default Houdini material. + UMaterial * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get(); + + for (int32 FaceIdx = 0; FaceIdx < SplitFaceIndices.Num(); ++FaceIdx) + { + int32 SplitFaceIndex = SplitFaceIndices[FaceIdx]; + if (!PartFaceMaterialIds.IsValidIndex(SplitFaceIndex)) + continue; + + // Get material id for this face. + HAPI_NodeId MaterialId = PartFaceMaterialIds[SplitFaceIndex]; + + // See if we have already treated that material + UMaterialInterface** FoundMaterialInterface = MapHoudiniMatIdToUnrealInterface.Find(MaterialId); + UMaterialInterface* MaterialInterface = nullptr; + if (FoundMaterialInterface) + MaterialInterface = *FoundMaterialInterface; + + if (MaterialInterface) + { + int32 const * FoundUnrealMatIndex = MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Find(MaterialInterface); + if (FoundUnrealMatIndex) + { + // This material has been mapped already, just assign the mat index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, *FoundUnrealMatIndex); + continue; + } + } + else + { + MaterialInterface = Cast(DefaultMaterial); + + FString MaterialPathName = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + FHoudiniMaterialTranslator::GetMaterialRelativePath(HGPO.AssetId, MaterialId, MaterialPathName); + UMaterialInterface * const * FoundMaterial = OutputAssignmentMaterials.Find(MaterialPathName); + if (FoundMaterial) + MaterialInterface = *FoundMaterial; + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(MaterialPathName); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + // Map the houdini ID to the unreal one + MapHoudiniMatIdToUnrealInterface.Add(MaterialId, MaterialInterface); + } + + if (MaterialInterface) + { + // Add the material to the Static mesh + int32 UnrealMatIndex = FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + + // Map the houdini ID to the unreal one + MapUnrealMaterialInterfaceToUnrealMaterialIndexThisMesh.Add(MaterialInterface, UnrealMatIndex); + + // Update the face index + FoundStaticMesh->SetTriangleMaterialID(FaceIdx, UnrealMatIndex); + } + } + } + } + else + { + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Default Material")); + + // No materials were found, we need to use default Houdini material. + UMaterialInterface * MaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial(HGPO.bIsTemplated).Get()); + + // See if we have a replacement material and use it on the mesh instead + UMaterialInterface * const * ReplacementMaterial = ReplacementMaterials.Find(HAPI_UNREAL_DEFAULT_MATERIAL_NAME); + if (ReplacementMaterial && *ReplacementMaterial) + MaterialInterface = *ReplacementMaterial; + + FoundStaticMaterials.Empty(); + FoundStaticMaterials.Add(FStaticMaterial(MaterialInterface)); + } + + //// Update property attributes on the mesh + //TArray PropertyAttributes; + //if (GetGenericPropertiesAttributes( + // HGPO.GeoId, HGPO.PartId, + // AllSplitFirstValidVertexIndex[SplitGroupName], + // AllSplitFirstValidPrimIndex[SplitGroupName], + // PropertyAttributes)) + //{ + // UpdateGenericPropertiesAttributes( + // FoundStaticMesh, PropertyAttributes); + //} + + FoundStaticMesh->Optimize(); + + // Check if the mesh is valid (check all the counts (vertex, triangles, vertex instances, UVs etc) but skip + // looping over each individual triangle vertex index to check if the value is valid). + const bool bSkipVertexIndicesCheck = true; + if (!FoundStaticMesh->IsValid(bSkipVertexIndicesCheck)) + { + HOUDINI_LOG_WARNING( + TEXT("[CreateHoudiniStaticMesh]: Invalid StaticMesh data for %s in cook output! Please check the log."), + *FoundStaticMesh->GetName()); + } + + //// Try to find the outer package so we can dirty it up + //if (FoundStaticMesh->GetOuter()) + //{ + // FoundStaticMesh->GetOuter()->MarkPackageDirty(); + //} + //else + //{ + // FoundStaticMesh->MarkPackageDirty(); + //} + UPackage *MeshPackage = FoundStaticMesh->GetOutermost(); + if (MeshPackage && !MeshPackage->IsPendingKill()) + { + MeshPackage->MarkPackageDirty(); + + /* + // DPT: deactivated auto saving mesh/material package + // only dirty for now, as we'll save them when saving the world. + // Save the created/updated package + FEditorFileUtils::PromptForCheckoutAndSave({ MeshPackage }, false, false); + */ + } + + // Add the Proxy mesh to the output maps + if (FoundOutputObject) + { + FoundOutputObject->ProxyObject = FoundStaticMesh; + FoundOutputObject->bProxyIsCurrent = true; + OutputObjects.FindOrAdd(OutputObjectIdentifier, *FoundOutputObject); + } + } + + const double time_end = FPlatformTime::Seconds(); + HOUDINI_LOG_MESSAGE(TEXT("CreateHoudiniStaticMesh() executed in %f seconds."), time_end - time_start); + + return true; +} + +void +FHoudiniMeshTranslator::ApplyComplexColliderHelper( + UStaticMesh* TargetStaticMesh, + UStaticMesh* ComplexStaticMesh, + const EHoudiniSplitType SplitType, + bool& bAssignedCustomCollisionMesh, + FHoudiniOutputObject* OutputObject) +{ + if (SplitType == EHoudiniSplitType::InvisibleComplexCollider && TargetStaticMesh) + { + if (!bAssignedCustomCollisionMesh) + { + bAssignedCustomCollisionMesh = true; + TargetStaticMesh->ComplexCollisionMesh = ComplexStaticMesh; + TargetStaticMesh->bCustomizedCollision = true; + bAssignedCustomCollisionMesh = true; + // We don't want an actor/component for this object in the scene, so flag it as an implicit output. + if (OutputObject) + { + OutputObject->bIsImplicit = true; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("More than one (invisible) complex collision mesh found. Static Mesh assets only support a single complex collision mesh. Creating additional collision geo as Static Mesh Components.")); + } + } +} + + +bool +FHoudiniMeshTranslator::CreateNeededMaterials() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateNeededMaterials")); + + UpdatePartNeededMaterials(); + + TArray MaterialAndTexturePackages; + FHoudiniMaterialTranslator::CreateHoudiniMaterials( + HGPO.AssetId, + PackageParams, + PartUniqueMaterialIds, + PartUniqueMaterialInfos, + InputAssignmentMaterials, + AllOutputMaterials, + OutputAssignmentMaterials, + MaterialAndTexturePackages, + false, + bTreatExistingMaterialsAsUpToDate); + + /* + // Save the created packages if needed + // DPT: deactivated, only dirty for now, as we'll save them when saving the world. + if (MaterialAndTexturePackages.Num() > 0) + FEditorFileUtils::PromptForCheckoutAndSave(MaterialAndTexturePackages, true, false); + */ + + if (bMaterialOverrideNeedsCreateInstance && PartFaceMaterialOverrides.Num() > 0) + { + // Map containing unique face materials override attribute + // and their first valid prim index + // We create only one material instance per attribute + + FHoudiniMaterialTranslator::SortUniqueFaceMaterialOverridesAndCreateMaterialInstances(PartFaceMaterialOverrides, HGPO, PackageParams, MaterialAndTexturePackages, + InputAssignmentMaterials, OutputAssignmentMaterials, + false); + } + + return true; +} + +FString +FHoudiniMeshTranslator::GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType) +{ + FString MeshIdentifier = TEXT(""); + switch (InSplitType) + { + case EHoudiniSplitType::Normal: + case EHoudiniSplitType::LOD: + case EHoudiniSplitType::InvisibleUCXCollider: + case EHoudiniSplitType::InvisibleSimpleCollider: + // LODs and Invisible simple colliders use the main mesh + MeshIdentifier = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + break; + + case EHoudiniSplitType::InvisibleComplexCollider: + case EHoudiniSplitType::RenderedComplexCollider: + case EHoudiniSplitType::RenderedUCXCollider: + case EHoudiniSplitType::RenderedSimpleCollider: + // Rendered colliders or invisible complex colliders have their own static mesh + MeshIdentifier = InSplitName; + break; + + default: + break; + } + + return MeshIdentifier; +} + +UStaticMesh* +FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + // See if we already have an input object for that output identifier + FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); + UStaticMesh* FoundStaticMesh = nullptr; + if (FoundOutputObjectPtr) + { + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + FoundStaticMesh = nullptr; + } + + if (!FoundStaticMesh) + { + // No input object matching this identifier, see if we have created an output object that matches + FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); + if (!FoundOutputObjectPtr) + return nullptr; + + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + return nullptr; + } + + if (FoundStaticMesh) + { + UObject* OuterMost = FoundStaticMesh->GetOutermostObject(); + if (OuterMost->IsA()) + { + // The Outermost for this static mesh is a level + // This is likely a SM created by V1, and we should not reuse it. + // This will force the plugin to recreate a "proper" SM in the temp folder. + FoundStaticMesh->MarkPendingKill(); + FoundStaticMesh = nullptr; + } + } + + return FoundStaticMesh; +} + +UHoudiniStaticMesh* +FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + // See if we already have an input object for that output identifier + FHoudiniOutputObject const * FoundOutputObjectPtr = InputObjects.Find(InIdentifier); + UHoudiniStaticMesh* FoundStaticMesh = nullptr; + if (FoundOutputObjectPtr) + { + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + FoundStaticMesh = nullptr; + } + + if (!FoundStaticMesh) + { + // No input object matching this identifier, see if we have created an output object that matches + FoundOutputObjectPtr = OutputObjects.Find(InIdentifier); + if (!FoundOutputObjectPtr) + return nullptr; + + // Make sure it's a valid static mesh + FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); + if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + return nullptr; + } + + return FoundStaticMesh; +} + +EHoudiniSplitType +FHoudiniMeshTranslator::GetSplitTypeFromSplitName(const FString& InSplitName) +{ + const FString MainGroup = HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION; + if (InSplitName.StartsWith(MainGroup, ESearchCase::IgnoreCase)) + return EHoudiniSplitType::Normal; + + const FString LODGroupPrefix = HAPI_UNREAL_GROUP_LOD_PREFIX; + if (InSplitName.StartsWith(LODGroupPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::LOD; + } + + const FString RenderedCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; + if (InSplitName.StartsWith(RenderedCollisionPrefix, ESearchCase::IgnoreCase)) + { + // Rendered colliders + // See if it is a simple/ucx/complex + const FString RenderedUCXCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_UCX_COLLISION_PREFIX; + const FString RenderedSimpleCollisionPrefix = HAPI_UNREAL_GROUP_RENDERED_SIMPLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(RenderedUCXCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::RenderedUCXCollider; + } + else if (InSplitName.StartsWith(RenderedSimpleCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::RenderedSimpleCollider; + } + else + { + return EHoudiniSplitType::RenderedComplexCollider; + } + } + + const FString InvisibleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(InvisibleCollisionPrefix, ESearchCase::IgnoreCase)) + { + // Invisible colliders + // See if it is a simple/ucx/complex + const FString InvisibleUCXCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_UCX_COLLISION_PREFIX; + const FString InvisibleSimpleCollisionPrefix = HAPI_UNREAL_GROUP_INVISIBLE_SIMPLE_COLLISION_PREFIX; + if (InSplitName.StartsWith(InvisibleUCXCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::InvisibleUCXCollider; + } + else if (InSplitName.StartsWith(InvisibleSimpleCollisionPrefix, ESearchCase::IgnoreCase)) + { + return EHoudiniSplitType::InvisibleSimpleCollider; + } + else + { + return EHoudiniSplitType::InvisibleComplexCollider; + } + } + + // ? + return EHoudiniSplitType::Invalid; + //return EHoudiniSplitType::Normal; +} + +bool +FHoudiniMeshTranslator::AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) +{ + // Get the vertex indices for the split group + TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; + + // We're only interested in unique vertices + TArray UniqueVertexIndexes; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + UniqueVertexIndexes.AddUnique(Index); + } + + // Extract the collision geo's vertices + TArray< FVector > VertexArray; + VertexArray.SetNum(UniqueVertexIndexes.Num()); + for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) + { + int32 VertexIndex = UniqueVertexIndexes[Idx]; + if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) + continue; + + VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + +#if WITH_EDITOR + // Do we want to create multiple convex hulls? + bool bDoMultiHullDecomp = false; + if (SplitGroupName.Contains(TEXT("ucx_multi"), ESearchCase::IgnoreCase)) + bDoMultiHullDecomp = true; + + uint32 HullCount = 8; + int32 MaxHullVerts = 16; + if (bDoMultiHullDecomp) + { + // TODO: + // Look for extra attributes for the decomposition parameters? (HullCount/MaxHullVerts) + } + + if (bDoMultiHullDecomp && (VertexArray.Num() >= 3 || UniqueVertexIndexes.Num() >= 3)) + { + // creating multiple convex hull collision + // ... this might take a while + + // We're only interested in the valid indices! + TArray Indices; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + Indices.Add(Index); + } + + // But we need all the positions as vertex + TArray< FVector > Vertices; + Vertices.SetNum(PartPositions.Num() / 3); + + for (int32 Idx = 0; Idx < Vertices.Num(); Idx++) + { + Vertices[Idx].X = PartPositions[Idx * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[Idx].Y = PartPositions[Idx * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[Idx].Z = PartPositions[Idx * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + // We are using Unreal's DecomposeMeshToHulls() + // We need a BodySetup so create a fake/transient one + UBodySetup* BodySetup = NewObject(); + + // Run actual util to do the work (if we have some valid input) + DecomposeMeshToHulls(BodySetup, Vertices, Indices, HullCount, MaxHullVerts); + + // If we succeed, return here + // If not, keep going and we'll try to do a single hull decomposition + if (BodySetup->AggGeom.ConvexElems.Num() > 0) + { + // Copy the convex elem to our aggregate + for (int32 n = 0; n < BodySetup->AggGeom.ConvexElems.Num(); n++) + AggCollisions.ConvexElems.Add(BodySetup->AggGeom.ConvexElems[n]); + + return true; + } + } +#endif + + // Creating a single Convex collision + FKConvexElem ConvexCollision; + ConvexCollision.VertexData = VertexArray; + ConvexCollision.UpdateElemBox(); + + AggCollisions.ConvexElems.Add(ConvexCollision); + + return true; +} + +bool +FHoudiniMeshTranslator::AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions) +{ + // Get the vertex indices for the split group + TArray& SplitGroupVertexList = AllSplitVertexLists[SplitGroupName]; + + // We're only interested in unique vertices + TArray UniqueVertexIndexes; + for (int32 VertexIdx = 0; VertexIdx < SplitGroupVertexList.Num(); VertexIdx++) + { + int32 Index = SplitGroupVertexList[VertexIdx]; + if (!PartPositions.IsValidIndex(Index)) + continue; + + UniqueVertexIndexes.AddUnique(Index); + } + + // Extract the collision geo's vertices + TArray< FVector > VertexArray; + VertexArray.SetNum(UniqueVertexIndexes.Num()); + for (int32 Idx = 0; Idx < UniqueVertexIndexes.Num(); Idx++) + { + int32 VertexIndex = UniqueVertexIndexes[Idx]; + if (!PartPositions.IsValidIndex(VertexIndex * 3 + 2)) + continue; + + VertexArray[Idx].X = PartPositions[VertexIndex * 3 + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Y = PartPositions[VertexIndex * 3 + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + VertexArray[Idx].Z = PartPositions[VertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + int32 NewColliders = 0; + if (SplitGroupName.Contains("Box")) + { + NewColliders = FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(VertexArray, AggCollisions); + } + else if (SplitGroupName.Contains("Sphere")) + { + NewColliders = FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(VertexArray, AggCollisions); + } + else if (SplitGroupName.Contains("Capsule")) + { + NewColliders = FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(VertexArray, AggCollisions); + } + else + { + // We need to see what type of collision the user wants + // by default, a kdop26 will be created + uint32 NumDirections = 26; + const FVector* Directions = KDopDir26; + if (SplitGroupName.Contains("kdop10X")) + { + NumDirections = 10; + Directions = KDopDir10X; + } + else if (SplitGroupName.Contains("kdop10Y")) + { + NumDirections = 10; + Directions = KDopDir10Y; + } + else if (SplitGroupName.Contains("kdop10Z")) + { + NumDirections = 10; + Directions = KDopDir10Z; + } + else if (SplitGroupName.Contains("kdop18")) + { + NumDirections = 18; + Directions = KDopDir18; + } + + // Converting the directions to a TArray + TArray DirArray; + DirArray.SetNum(NumDirections); + for (uint32 DirectionIndex = 0; DirectionIndex < NumDirections; DirectionIndex++) + { + DirArray[DirectionIndex] = Directions[DirectionIndex]; + } + + NewColliders = FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(VertexArray, DirArray, AggCollisions); + } + + return (NewColliders > 0); +} + +int32 +FHoudiniMeshTranslator::TransferRegularPointAttributesToVertices( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData) +{ + return FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, OutVertexData); +} + +/* +int32 +FHoudiniMeshTranslator::GetSplitNormals( + const TArray& InSplitVertexList, TArray& OutNormals) +{ + // Extract the normals + UpdatePartNormalsIfNeeded(); + + // Get the normals for this split + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutNormals.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; + OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; + OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; + + OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; + OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; + OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; + + OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; + OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; + OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; + } + + return WedgeCount; +} + +int32 +FHoudiniMeshTranslator::GetSplitUVs( + const TArray& InSplitVertexList, TArray& OutUVs) +{ + // Extract the normals + UpdatePartUVSetsIfNeeded(); + + // Get the normals for this split + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InSplitVertexList, AttribInfoNormals, PartNormals, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutNormals.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutNormals[WedgeIdx].X = VertexData[WedgeIdx * 3 + 0]; + OutNormals[WedgeIdx].Y = VertexData[WedgeIdx * 3 + 2]; + OutNormals[WedgeIdx].Z = VertexData[WedgeIdx * 3 + 1]; + + OutNormals[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 3 + 0]; + OutNormals[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 3 + 2]; + OutNormals[WedgeIdx + 2].Z = VertexData[(WedgeIdx + 1) * 3 + 1]; + + OutNormals[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 3 + 0]; + OutNormals[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 3 + 2]; + OutNormals[WedgeIdx + 1].Z = VertexData[(WedgeIdx + 2) * 3 + 1]; + } + + return WedgeCount; +} + + +int32 +FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutData, + const float& ScaleFactor) +{ + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutData.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; + OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; + + OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; + OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; + + OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; + OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; + } + + return WedgeCount; +} + +int32 +FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutData, + const float& ScaleFactor) +{ + TArray VertexData; + int32 WedgeCount = FHoudiniMeshTranslator::TransferPartAttributesToSplit( + InVertexList, InAttribInfo, InData, VertexData); + + // Convert the float data to Vectors and fix the winding order + OutData.SetNum(WedgeCount); + for (int32 WedgeIdx = 0; WedgeIdx + 2 < WedgeCount; WedgeIdx += 3) + { + OutData[WedgeIdx].X = VertexData[WedgeIdx * 2 + 0]; + OutData[WedgeIdx].Y = VertexData[WedgeIdx * 2 + 1]; + + OutData[WedgeIdx + 2].X = VertexData[(WedgeIdx + 1) * 2 + 0]; + OutData[WedgeIdx + 2].Y = VertexData[(WedgeIdx + 1) * 2 + 1]; + + OutData[WedgeIdx + 1].X = VertexData[(WedgeIdx + 2) * 2 + 0]; + OutData[WedgeIdx + 1].Y = VertexData[(WedgeIdx + 2) * 2 + 1]; + } + + return WedgeCount; +} +*/ + + +template +int32 FHoudiniMeshTranslator::TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::TransferPartAttributesToSplit")); + + if (!InAttribInfo.exists || InAttribInfo.tupleSize <= 0) + return 0; + + if (InData.Num() <= 0) + return 0; + + int32 ValidWedgeCount = 0; + + // Future optimization - see if we can do direct vertex transfer. + int32 WedgeCount = InVertexList.Num(); + int32 LastValidWedgeIdx = 0; + if (InAttribInfo.owner == HAPI_ATTROWNER_POINT) + { + // Point attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + int32 VertexIdx = InVertexList[WedgeIdx]; + if (VertexIdx < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[VertexIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_VERTEX) + { + // Vertex attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[WedgeIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_PRIM) + { + // Primitive attribute transfer + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 PrimIdx = WedgeIdx / 3; + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[PrimIdx * InAttribInfo.tupleSize + TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + else if (InAttribInfo.owner == HAPI_ATTROWNER_DETAIL) + { + // Detail attribute transfer + // We have one value to copy for all output split vertices + // if the attribute is a single value (not a tuple) + // then we can simply use the array init function instead of looping + if (InAttribInfo.tupleSize == 1) + { + OutVertexData.Init(InData[0], WedgeCount); + } + else + { + OutVertexData.SetNumZeroed(WedgeCount * InAttribInfo.tupleSize); + for (int32 WedgeIdx = 0; WedgeIdx < WedgeCount; ++WedgeIdx) + { + if (InVertexList[WedgeIdx] < 0) + { + // This is an index/wedge we are skipping due to split. + continue; + } + + int32 OutIdx = LastValidWedgeIdx * InAttribInfo.tupleSize; + for (int32 TupleIdx = 0; TupleIdx < InAttribInfo.tupleSize; TupleIdx++) + { + OutVertexData[OutIdx + TupleIdx] = InData[TupleIdx]; + } + + // We are re-indexing wedges. + LastValidWedgeIdx++; + // Increment wedge count, since this is a valid wedge. + ValidWedgeCount++; + } + } + } + else + { + // Invalid attribute owner, shouldn't happen + check(false); + } + + OutVertexData.SetNumZeroed(ValidWedgeCount * InAttribInfo.tupleSize); + + return ValidWedgeCount; +} + +float +FHoudiniMeshTranslator::GetLODSCreensizeForSplit(const FString& SplitGroupName) +{ + // LOD Screensize + // default values has already been set, see if we have any attribute override for this + float screensize = -1.0f; + + // Start by looking at the lod_screensize primitive attribute + bool bAttribValid = false; + UpdatePartLODScreensizeIfNeeded(); + + if (PartLODScreensize.Num() > 0) + { + // use the "lod_screensize" primitive attribute + int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + if (PartLODScreensize.IsValidIndex(FirstValidPrimIndex)) + screensize = PartLODScreensize[FirstValidPrimIndex]; + } + + if (screensize < 0.0f) + { + // We couldn't find the primitive attribute, look for a "lodX_screensize" detail attribute + FString LODAttributeName = SplitGroupName + HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX; + + TArray LODScreenSizes; + HAPI_AttributeInfo AttribInfoScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); + + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, TCHAR_TO_ANSI(*LODAttributeName), + AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_DETAIL, 0, 1); + + if (AttribInfoScreenSize.exists && LODScreenSizes.Num() > 0) + { + screensize = LODScreenSizes[0]; + } + } + + if (screensize < 0.0f) + { + // finally, look for a potential uproperty style attribute + // aka, "unreal_uproperty_screensize" + TArray LODScreenSizes; + HAPI_AttributeInfo AttribInfoScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttribInfoScreenSize); + + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + HGPO.GeoId, HGPO.PartId, "unreal_uproperty_screensize", + AttribInfoScreenSize, LODScreenSizes, 0, HAPI_ATTROWNER_INVALID, 0, 1); + + if (AttribInfoScreenSize.exists) + { + if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_DETAIL && LODScreenSizes.Num() > 0) + { + screensize = LODScreenSizes[0]; + } + else if (AttribInfoScreenSize.owner == HAPI_ATTROWNER_PRIM) + { + int32 FirstValidPrimIndex = AllSplitFirstValidPrimIndex[SplitGroupName]; + if (LODScreenSizes.IsValidIndex(FirstValidPrimIndex)) + screensize = LODScreenSizes[FirstValidPrimIndex]; + } + } + } + + // Make sure the screensize is in percent, so if its above 1, divide by 100 + if (screensize > 1.0f) + screensize /= 100.0f; + + return screensize; +} + +int32 +FHoudiniMeshTranslator::GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + // Calculate bounding Box. + FVector Center, Extents; + FVector unitVec = FVector::OneVector;// bs->BuildScale3D; + CalcBoundingBox(InPositionArray, Center, Extents, unitVec); + + FKBoxElem BoxElem; + BoxElem.Center = Center; + BoxElem.X = Extents.X * 2.0f; + BoxElem.Y = Extents.Y * 2.0f; + BoxElem.Z = Extents.Z * 2.0f; + OutAggregateCollisions.BoxElems.Add(BoxElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FBox Box(ForceInit); + for (const FVector& CurPos : PositionArray) + { + Box += CurPos; + } + Box.GetCenterAndExtents(Center, Extents); +} + +int32 +FHoudiniMeshTranslator::GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FSphere bSphere, bSphere2, bestSphere; + FVector unitVec = FVector::OneVector; + + // Calculate bounding sphere. + CalcBoundingSphere(InPositionArray, bSphere, unitVec); + CalcBoundingSphere2(InPositionArray, bSphere2, unitVec); + + if (bSphere.W < bSphere2.W) + bestSphere = bSphere; + else + bestSphere = bSphere2; + + // Don't use if radius is zero. + if (bestSphere.W <= 0.f) + { + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Sphere collider.")); + return 0; + } + + FKSphereElem SphereElem; + SphereElem.Center = bestSphere.Center; + SphereElem.Radius = bestSphere.W; + OutAggregateCollisions.SphereElems.Add(SphereElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + if (PositionArray.Num() == 0) + return; + + FBox Box; + FVector MinIx[3]; + FVector MaxIx[3]; + + bool bFirstVertex = true; + for (const FVector& CurPosition : PositionArray) + { + FVector p = CurPosition * LimitVec; + if (bFirstVertex) + { + // First, find AABB, remembering furthest points in each dir. + Box.Min = p; + Box.Max = Box.Min; + + MinIx[0] = CurPosition; + MinIx[1] = CurPosition; + MinIx[2] = CurPosition; + + MaxIx[0] = CurPosition; + MaxIx[1] = CurPosition; + MaxIx[2] = CurPosition; + bFirstVertex = false; + continue; + } + + // X // + if (p.X < Box.Min.X) + { + Box.Min.X = p.X; + MinIx[0] = CurPosition; + } + else if (p.X > Box.Max.X) + { + Box.Max.X = p.X; + MaxIx[0] = CurPosition; + } + + // Y // + if (p.Y < Box.Min.Y) + { + Box.Min.Y = p.Y; + MinIx[1] = CurPosition; + } + else if (p.Y > Box.Max.Y) + { + Box.Max.Y = p.Y; + MaxIx[1] = CurPosition; + } + + // Z // + if (p.Z < Box.Min.Z) + { + Box.Min.Z = p.Z; + MinIx[2] = CurPosition; + } + else if (p.Z > Box.Max.Z) + { + Box.Max.Z = p.Z; + MaxIx[2] = CurPosition; + } + } + + const FVector Extremes[3] = { (MaxIx[0] - MinIx[0]) * LimitVec, + (MaxIx[1] - MinIx[1]) * LimitVec, + (MaxIx[2] - MinIx[2]) * LimitVec }; + + // Now find extreme points furthest apart, and initial center and radius of sphere. + float d2 = 0.f; + for (int32 i = 0; i < 3; i++) + { + const float tmpd2 = Extremes[i].SizeSquared(); + if (tmpd2 > d2) + { + d2 = tmpd2; + sphere.Center = (MinIx[i] + (0.5f * Extremes[i])) * LimitVec; + sphere.W = 0.f; + } + } + + const FVector Extents = FVector(Extremes[0].X, Extremes[1].Y, Extremes[2].Z); + + // radius and radius squared + float r = 0.5f * Extents.GetMax(); + float r2 = FMath::Square(r); + + // Now check each point lies within this sphere. If not - expand it a bit. + for (const FVector& curPos : PositionArray) + { + const FVector cToP = (curPos * LimitVec) - sphere.Center; + + const float pr2 = cToP.SizeSquared(); + + // If this point is outside our current bounding sphere's radius + if (pr2 > r2) + { + // ..expand radius just enough to include this point. + const float pr = FMath::Sqrt(pr2); + r = 0.5f * (r + pr); + r2 = FMath::Square(r); + + sphere.Center += ((pr - r) / pr * cToP); + } + } + + sphere.W = r; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FVector Center, Extents; + CalcBoundingBox(PositionArray, Center, Extents, LimitVec); + + sphere.Center = Center; + sphere.W = 0.0f; + + for (const FVector& curPos : PositionArray) + { + float Dist = FVector::DistSquared(curPos * LimitVec, sphere.Center); + if (Dist > sphere.W) + sphere.W = Dist; + } + sphere.W = FMath::Sqrt(sphere.W); +} + +int32 +FHoudiniMeshTranslator::GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + FSphere sphere; + float length; + FRotator rotation; + FVector unitVec = FVector::OneVector; + + // Calculate bounding sphyl. + CalcBoundingSphyl(InPositionArray, sphere, length, rotation, unitVec); + + // Dont use if radius is zero. + if (sphere.W <= 0.f) + { + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple Capsule collider.")); + return 0; + } + + // If height is zero, then a sphere would be better (should we just create one instead?) + if (length <= 0.f) + { + length = SMALL_NUMBER; + } + + FKSphylElem SphylElem; + SphylElem.Center = sphere.Center; + SphylElem.Rotation = rotation; + SphylElem.Radius = sphere.W; + SphylElem.Length = length; + OutAggregateCollisions.SphylElems.Add(SphylElem); + + return 1; +} + +void +FHoudiniMeshTranslator::CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + if (PositionArray.Num() == 0) + return; + + FVector Center, Extents; + CalcBoundingBox(PositionArray, Center, Extents, LimitVec); + + // @todo sphere.Center could perhaps be adjusted to best fit if model is non-symmetric on it's longest axis + sphere.Center = Center; + + // Work out best axis aligned orientation (longest side) + float Extent = Extents.GetMax(); + if (Extent == Extents.X) + { + rotation = FRotator(90.f, 0.f, 0.f); + Extents.X = 0.0f; + } + else if (Extent == Extents.Y) + { + rotation = FRotator(0.f, 0.f, 90.f); + Extents.Y = 0.0f; + } + else + { + rotation = FRotator(0.f, 0.f, 0.f); + Extents.Z = 0.0f; + } + + // Cleared the largest axis above, remaining determines the radius + float r = Extents.GetMax(); + float r2 = FMath::Square(r); + + // Now check each point lies within this the radius. If not - expand it a bit. + for (const FVector& CurPos : PositionArray) + { + FVector cToP = (CurPos * LimitVec) - sphere.Center; + cToP = rotation.UnrotateVector(cToP); + + const float pr2 = cToP.SizeSquared2D(); // Ignore Z here... + + // If this point is outside our current bounding sphere's radius + if (pr2 > r2) + { + // ..expand radius just enough to include this point. + const float pr = FMath::Sqrt(pr2); + r = 0.5f * (r + pr); + r2 = FMath::Square(r); + } + } + + // The length is the longest side minus the radius. + float hl = FMath::Max(0.0f, Extent - r); + + // Now check each point lies within the length. If not - expand it a bit. + for (const FVector& CurPos : PositionArray) + { + FVector cToP = (CurPos * LimitVec) - sphere.Center; + cToP = rotation.UnrotateVector(cToP); + + // If this point is outside our current bounding sphyl's length + if (FMath::Abs(cToP.Z) > hl) + { + const bool bFlip = (cToP.Z < 0.f ? true : false); + const FVector cOrigin(0.f, 0.f, (bFlip ? -hl : hl)); + + const float pr2 = (cOrigin - cToP).SizeSquared(); + + // If this point is outside our current bounding sphyl's radius + if (pr2 > r2) + { + FVector cPoint; + FMath::SphereDistToLine(cOrigin, r, cToP, (bFlip ? FVector(0.f, 0.f, 1.f) : FVector(0.f, 0.f, -1.f)), cPoint); + + // Don't accept zero as a valid diff when we know it's outside the sphere (saves needless retest on further iterations of like points) + hl += FMath::Max(FMath::Abs(cToP.Z - cPoint.Z), 1.e-6f); + } + } + } + + sphere.W = r; + length = hl * 2.0f; +} + +int32 +FHoudiniMeshTranslator::GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions) +{ + // + // Code simplified and adapted to work with a simple vector array from GeomFitUtils.cpp + // + + const float my_flt_max = 3.402823466e+38F; + + // Do k- specific stuff. + int32 kCount = Dirs.Num(); + + TArray maxDist; + maxDist.Init(-my_flt_max, kCount); + /* + for (int32 i = 0; i < kCount; i++) + maxDist.Add(my_flt_max); + */ + + // Construct temporary UModel for kdop creation. We keep no refs to it, so it can be GC'd. + auto TempModel = NewObject(); + TempModel->Initialize(nullptr, 1); + + // For each vertex, project along each kdop direction, to find the max in that direction. + for (int32 i = 0; i < InPositionArray.Num(); i++) + { + for (int32 j = 0; j < kCount; j++) + { + float dist = InPositionArray[i] | Dirs[j]; + maxDist[j] = FMath::Max(dist, maxDist[j]); + } + } + + // Inflate kdop to ensure it is no degenerate + const float MinSize = 0.1f; + for (int32 i = 0; i < kCount; i++) + { + maxDist[i] += MinSize; + } + + // Now we have the planes of the kdop, we work out the face polygons. + TArray planes; + for (int32 i = 0; i < kCount; i++) + planes.Add(FPlane(Dirs[i], maxDist[i])); + + for (int32 i = 0; i < planes.Num(); i++) + { + FPoly* Polygon = new(TempModel->Polys->Element) FPoly(); + FVector Base, AxisX, AxisY; + + Polygon->Init(); + Polygon->Normal = planes[i]; + Polygon->Normal.FindBestAxisVectors(AxisX, AxisY); + + Base = planes[i] * planes[i].W; + + new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base + AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX - AxisY * HALF_WORLD_MAX); + new(Polygon->Vertices) FVector(Base - AxisX * HALF_WORLD_MAX + AxisY * HALF_WORLD_MAX); + + for (int32 j = 0; j < planes.Num(); j++) + { + if (i != j) + { + if (!Polygon->Split(-FVector(planes[j]), planes[j] * planes[j].W)) + { + Polygon->Vertices.Empty(); + break; + } + } + } + + if (Polygon->Vertices.Num() < 3) + { + // If poly resulted in no verts, remove from array + TempModel->Polys->Element.RemoveAt(TempModel->Polys->Element.Num() - 1); + } + else + { + // Other stuff... + Polygon->iLink = i; + Polygon->CalcNormal(1); + } + } + + if (TempModel->Polys->Element.Num() < 4) + { + TempModel = NULL; + HOUDINI_LOG_WARNING(TEXT("Failed to generate a simple KDOP collider.")); + return 0; + } + + // Build bounding box. + TempModel->BuildBound(); + + // Build BSP for the brush. + FBSPOps::bspBuild(TempModel, FBSPOps::BSP_Good, 15, 70, 1, 0); + FBSPOps::bspRefresh(TempModel, 1); + FBSPOps::bspBuildBounds(TempModel); + + // Now, create a temporary BodySetup to build the colliders + UBodySetup* TempBS = NewObject(); + TempBS->CreateFromModel(TempModel, false); + + // Copy the convex elements back to our aggregate + int32 NewConvexElems = 0; + if (TempBS && TempBS->AggGeom.ConvexElems.Num() > 0) + { + for (const auto& CurConvexElem : TempBS->AggGeom.ConvexElems) + { + OutAggregateCollisions.ConvexElems.Add(CurConvexElem); + NewConvexElems++; + } + } + + return NewConvexElems; +} + + +void +FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO) +{ + PackageParams = InPackageParams; + + if (bUpdateHGPO) + { + PackageParams.ObjectId = HGPO.ObjectId; + PackageParams.GeoId = HGPO.ObjectId; + PackageParams.PartId = HGPO.ObjectId; + } +} + +bool +FHoudiniMeshTranslator::RemoveAndDestroyComponent(UObject* InComponent) +{ + if (!InComponent || InComponent->IsPendingKill()) + return false; + + USceneComponent* SceneComponent = Cast(InComponent); + if (SceneComponent && !SceneComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (SceneComponent->GetOwner()) + SceneComponent->GetOwner()->RemoveOwnedComponent(SceneComponent); + + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComponent->UnregisterComponent(); + SceneComponent->DestroyComponent(); + + return true; + } + + return false; +} + +UMeshComponent* +FHoudiniMeshTranslator::CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf &InComponentType) +{ + // Create a new SMC as we couldn't find an existing one + USceneComponent* OuterSceneComponent = Cast(InOuterComponent); + UObject * Outer = nullptr; + if (OuterSceneComponent && !OuterSceneComponent->IsPendingKill()) + Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); + + UMeshComponent *MeshComponent = NewObject(Outer, InComponentType, NAME_None, RF_Transactional); + + // Initialize it + MeshComponent->SetVisibility(true); + //MeshComponent->SetMobility(Mobility); + + // TODO: + // Property propagation: set the new SMC's properties to the HAC's current settings + //CopyComponentPropertiesTo(MeshComponent); + + // Change the creation method so the component is listed in the details panels + MeshComponent->CreationMethod = EComponentCreationMethod::Instance; + + // Attach created static mesh component to our Houdini component. + MeshComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + MeshComponent->OnComponentCreated(); + MeshComponent->RegisterComponent(); + + return MeshComponent; +} + +bool +FHoudiniMeshTranslator::PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh) +{ + UStaticMesh *Mesh = Cast(InMesh); + if (Mesh) + { + InComponent->SetStaticMesh(Mesh); + + return true; + } + + return false; +} + +bool +FHoudiniMeshTranslator::PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh) +{ + UHoudiniStaticMesh *Mesh = Cast(InMesh); + if (Mesh) + { + InComponent->SetMesh(Mesh); + + return true; + } + + return false; +} + +UMeshComponent* +FHoudiniMeshTranslator::CreateOrUpdateMeshComponent( + const UHoudiniOutput *InOutput, + UObject *InOuterComponent, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TSubclassOf& InComponentType, + FHoudiniOutputObject& OutputObject, + FHoudiniGeoPartObject const *& OutFoundHGPO, + bool& bCreated) +{ + bCreated = false; + OutFoundHGPO = nullptr; + + // Find the HGPO that matches this mesh + for (auto& curHGPO : InOutput->HoudiniGeoPartObjects) + { + if (curHGPO.ObjectId != InOutputIdentifier.ObjectId + || curHGPO.GeoId != InOutputIdentifier.GeoId + || curHGPO.PartId != InOutputIdentifier.PartId) + { + continue; + } + + if (InOutputIdentifier.SplitIdentifier.Equals(HAPI_UNREAL_GROUP_GEOMETRY_NOT_COLLISION) + || curHGPO.SplitGroups.Contains(InOutputIdentifier.SplitIdentifier)) + { + OutFoundHGPO = &curHGPO; + } + } + + // No need to create a component for instanced meshes! + if (OutFoundHGPO && OutFoundHGPO->bIsInstanced) + return nullptr; + + bool bIsProxyComponent = InComponentType == UHoudiniStaticMeshComponent::StaticClass(); + + // See if we already have a component for that mesh + UMeshComponent* MeshComponent = nullptr; + if (bIsProxyComponent) + MeshComponent = Cast(OutputObject.ProxyComponent); + else + MeshComponent = Cast(OutputObject.OutputComponent); + + // If there is an existing component, but it is pending kill, then it was likely + // deleted by some other process, such as by the user in the editor, so don't use it + if (!MeshComponent || MeshComponent->IsPendingKill() || !MeshComponent->IsA(InComponentType)) + { + // If the component is not of type InComponentType, or the found component is pending kill, destroy + // the existing component (a new one is then created below) + RemoveAndDestroyComponent(MeshComponent); + MeshComponent = nullptr; + } + + if (!MeshComponent) + { + // Create a new SMC/HSMC as we couldn't find an existing one + MeshComponent = CreateMeshComponent(InOuterComponent, InComponentType); + + if (MeshComponent) + { + // Add to the output object + if (bIsProxyComponent) + OutputObject.ProxyComponent = MeshComponent; + else + OutputObject.OutputComponent = MeshComponent; + + bCreated = true; + } + } + + return MeshComponent; +} + +bool +FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, + TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors) +{ + if (!Socket || Socket->IsPendingKill() || !StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + return false; + + // The actor to assign is stored is the socket's tag + FString ActorString = Socket->Tag; + if (ActorString.IsEmpty()) + return false; + + // The actor to assign are listed after a | + TArray ActorStringArray; + ActorString.ParseIntoArray(ActorStringArray, TEXT("|"), false); + + // The "real" Tag is the first + if (ActorStringArray.Num() > 0) + Socket->Tag = ActorStringArray[0]; + + // We just add a Tag, no Actor + if (ActorStringArray.Num() == 1) + return false; + + // Extract the parsed actor string to split it further + ActorString = ActorStringArray[1]; + + // Converting the string to a string array using delimiters + const TCHAR* Delims[] = { TEXT(","), TEXT(";") }; + ActorString.ParseIntoArray(ActorStringArray, Delims, 2); + + // And try to find the corresponding HoudiniAssetActor in the editor world + // to avoid finding "deleted" assets with the same name + //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); +#if WITH_EDITOR + UWorld* EditorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; + if (!EditorWorld || EditorWorld->IsPendingKill()) + return false; + + // Remove the previously created actors which were attached to this socket + { + for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniCreatedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + continue; + } + + if (CurActor->GetAttachParentSocketName() == Socket->SocketName) + { + HoudiniCreatedSocketActors.RemoveAt(Idx); + CurActor->Destroy(); + } + } + } + + // Detach the previous in level actors which was attached to this socket + { + for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) + { + AActor * CurActor = HoudiniAttachedSocketActors[Idx]; + if (!CurActor || CurActor->IsPendingKill()) + { + HoudiniAttachedSocketActors.RemoveAt(Idx); + continue; + } + + if (CurActor->GetAttachParentSocketName() == Socket->SocketName) + { + CurActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + HoudiniAttachedSocketActors.RemoveAt(Idx); + } + } + } + + auto CreateDefaultActor = [EditorWorld, StaticMeshComponent, Socket, HoudiniCreatedSocketActors]() + { + AActor * CreatedDefaultActor = nullptr; + + UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); + if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + { + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( + EditorWorld->GetCurrentLevel(), DefaultReferenceSM, false, RF_Transactional, nullptr); + + if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load default mesh.")); + } + else + { + + // Set the default mesh actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : NewActors[0]->GetComponents()) + { + UStaticMeshComponent * CurSMC = Cast(CurComp); + if (CurSMC && !CurSMC->IsPendingKill()) + CurSMC->SetMobility(OutputSMCMobility); + } + + // Set the default mesh actor hidden in game. + NewActors[0]->SetActorHiddenInGame(true); + + Socket->AttachActor(NewActors[0], StaticMeshComponent); + CreatedDefaultActor = NewActors[0]; + //HoudiniCreatedSocketActors.Add(NewActors[0]); + } + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Failed to load default mesh.")); + } + + return CreatedDefaultActor; + }; + + // If nothing was specified, we're done + if (ActorStringArray.Num() <= 0) + return true; + + bool bUseDefaultActor = true; + // Get from the Houdini runtime setting if use default object when the reference is invalid + // true by default if fail to access HoudiniRuntimeSettings + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bUseDefaultActor = HoudiniRuntimeSettings->bShowDefaultMesh; + } + + /* + // !! Only use the default mesh if we failed to find/spawn the actor to attach + // not if we didn't specify any actor to attach! + if (ActorStringArray.Num() <= 0) + { + if (!bUseDefaultActor) + return true; + + HOUDINI_LOG_WARNING( + TEXT("Output static mesh: Socket '%s' actor is not specified. Spawn a default mesh (hidden in game)."), *(Socket->GetName())); + + AActor * DefaultActor = CreateDefaultActor(); + if (DefaultActor && !DefaultActor->IsPendingKill()) + HoudiniCreatedSocketActors.Add(DefaultActor); + + return true; + } + */ + + // try to find the actor in level first + for (TActorIterator ActorItr(EditorWorld); ActorItr; ++ActorItr) + { + // Same as with the Object Iterator, access the subclass instance with the * or -> operators. + AActor *Actor = *ActorItr; + if (!Actor || Actor->IsPendingKillOrUnreachable()) + continue; + + for (int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++) + { + if (Actor->GetName() != ActorStringArray[StringIdx] + && Actor->GetActorLabel() != ActorStringArray[StringIdx]) + continue; + + // Set the actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : Actor->GetComponents()) + { + UStaticMeshComponent * SMC = Cast(CurComp); + if (SMC && !SMC->IsPendingKill()) + SMC->SetMobility(OutputSMCMobility); + } + + Socket->AttachActor(Actor, StaticMeshComponent); + HoudiniAttachedSocketActors.Add(Actor); + + // Remove the string if the actor is found in the editor level + ActorStringArray.RemoveAt(StringIdx); + break; + } + } + + bool bSuccess = true; + // If some of the actors are not found in the level, try to find them in the content browser. Spawn one if existed + for (int32 Idx = ActorStringArray.Num() - 1; Idx>= 0; --Idx) + { + UObject * Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *ActorStringArray[Idx]); + if (!Obj || Obj->IsPendingKill()) + { + bSuccess = false; + continue; + } + + // Spawn a new actor with the found object + TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( + EditorWorld->GetCurrentLevel(), Obj, false, RF_Transactional, nullptr); + + if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + { + bSuccess = false; + continue; + } + + // Set the new actor components mobility to the same as output SMC's + EComponentMobility::Type OutputSMCMobility = StaticMeshComponent->Mobility; + for (auto & CurComp : NewActors[0]->GetComponents()) + { + UStaticMeshComponent * CurSMC = Cast(CurComp); + if (CurSMC && !CurSMC->IsPendingKill()) + CurSMC->SetMobility(OutputSMCMobility); + } + + Socket->AttachActor(NewActors[0], StaticMeshComponent); + HoudiniCreatedSocketActors.Add(NewActors[0]); + + ActorStringArray.RemoveAt(Idx); + } + + // Failed to find actors in both level and content browser + // Spawn default actors if enabled + if (bUseDefaultActor) + { + for (int32 Idx = ActorStringArray.Num() - 1; Idx >= 0; --Idx) + { + HOUDINI_LOG_WARNING( + TEXT("Output static mesh: Failed to attach '%s' to socket '%s', spawn a default mesh (hidden in game)."), *(ActorStringArray[Idx]), *(Socket->GetName())); + + // If failed to load this object, spawn a default mesh + AActor * CurDefaultActor = CreateDefaultActor(); + if (CurDefaultActor && !CurDefaultActor->IsPendingKill()) + HoudiniCreatedSocketActors.Add(CurDefaultActor); + } + } + + if (ActorStringArray.Num() > 0) + return false; +#endif + + return bSuccess; +} + +void +FHoudiniMeshTranslator::UpdateMeshBuildSettings( + FMeshBuildSettings& OutMeshBuildSettings, + const bool& bHasNormals, + const bool& bHasTangents, + const bool& bHasLightmapUVSet) +{ + // Use the values provided to the translator + OutMeshBuildSettings = StaticMeshBuildSettings; + + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + + // Recomputing normals. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag : HRSRF_OnlyIfMissing; + if(RecomputeNormalFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bRecomputeNormals = !bHasNormals; + + // Recomputing tangents. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; + if (RecomputeTangentFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bRecomputeTangents = !bHasTangents; + + // Lightmap UV generation. + EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = HoudiniRuntimeSettings ? (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag : HRSRF_OnlyIfMissing; + if (GenerateLightmapUVFlag == HRSRF_OnlyIfMissing) + OutMeshBuildSettings.bGenerateLightmapUVs = !bHasLightmapUVSet; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h index 740a7c491..365a5f841 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.h @@ -1,425 +1,425 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "HoudiniPackageParams.h" -#include "HoudiniAssetComponent.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "PhysicsEngine/AggregateGeom.h" - -//#include "HoudiniMeshTranslator.generated.h" - -class UStaticMesh; -class UStaticMeshSocket; -class UMaterialInterface; -class UMeshComponent; -class UStaticMeshComponent; -class UHoudiniStaticMesh; -class UHoudiniStaticMeshComponent; - -struct FKAggregateGeom; -struct FHoudiniGenericAttribute; - - -UENUM() -enum class EHoudiniSplitType : uint8 -{ - Invalid, - - Normal, - - LOD, - - RenderedComplexCollider, - InvisibleComplexCollider, - - RenderedUCXCollider, - InvisibleUCXCollider, - - RenderedSimpleCollider, - InvisibleSimpleCollider -}; - -struct HOUDINIENGINE_API FHoudiniMeshTranslator -{ - public: - - //----------------------------------------------------------------------------------------------------------------------------- - // HOUDINI TO UNREAL - //----------------------------------------------------------------------------------------------------------------------------- - - // - static bool CreateAllMeshesAndComponentsFromHoudiniOutput( - UHoudiniOutput* InOutput, - const FHoudiniPackageParams& InPackageParams, - const EHoudiniStaticMeshMethod& InStaticMeshMethod, - const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, - const FMeshBuildSettings& InMeshBuildSettings, - const TMap& InAllOutputMaterials, - UObject* InOuterComponent, - bool bInTreatExistingMaterialsAsUpToDate=false, - bool bInDestroyProxies=false); - - static bool CreateStaticMeshFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - const FHoudiniPackageParams& InPackageParams, - const TMap& InOutputObjects, - TMap& OutOutputObjects, - TMap& InAssignmentMaterialMap, - TMap& InReplacementMaterialMap, - const TMap& InAllOutputMaterials, - const bool& InForceRebuild, - const EHoudiniStaticMeshMethod& InStaticMeshMethod, - const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, - const FMeshBuildSettings& InMeshBuildSettings, - bool bInTreatExistingMaterialsAsUpToDate = false); - - static bool CreateOrUpdateAllComponents( - UHoudiniOutput* InOutput, - UObject* InOuterComponent, - TMap& InNewOutputObjects, - bool bInDestroyProxies=false, - bool bInApplyGenericProperties=true); - - - //----------------------------------------------------------------------------------------------------------------------------- - // HELPERS - //----------------------------------------------------------------------------------------------------------------------------- - static EHoudiniSplitType GetSplitTypeFromSplitName(const FString& InSplitName); - - static FString GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType); - - // TODO: Rename me! and template me! float/int/string ? - // TransferPartAttributesToSplitVertices - static int32 TransferRegularPointAttributesToVertices( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutVertexData); - - template - static int32 TransferPartAttributesToSplit( - const TArray& InVertexList, - const HAPI_AttributeInfo& InAttribInfo, - const TArray& InData, - TArray& OutSplitData); - - // Update the MeshBuild Settings using the values from the runtime settings/overrides on the HAC - void UpdateMeshBuildSettings( - FMeshBuildSettings& OutMeshBuildSettings, - const bool& bHasNormals, - const bool& bHasTangents, - const bool& bHasLightmapUVSet); - - - //----------------------------------------------------------------------------------------------------------------------------- - // ACCESSORS - //----------------------------------------------------------------------------------------------------------------------------- - - //----------------------------------------------------------------------------------------------------------------------------- - // MUTATORS - //----------------------------------------------------------------------------------------------------------------------------- - void SetHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) { HGPO = InHGPO; }; - void SetOuterComponent(UObject* InOuter) { OuterComponent = InOuter; }; - void SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO = false); - - void SetInputObjects(const TMap& InInputObjects) { InputObjects = InInputObjects; }; - void SetOutputObjects(TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; - - void SetInputAssignmentMaterials(const TMap& InInputMaterials) { InputAssignmentMaterials = InInputMaterials; }; - void SetReplacementMaterials(const TMap& InReplacementMaterials) { ReplacementMaterials = InReplacementMaterials; }; - void SetAllOutputMaterials(const TMap& InAllOutputMaterials) { AllOutputMaterials = InAllOutputMaterials; }; - - //void SetInputObjectProperties(const TMap& InInputObjectProperties) { InputObjectProperties = InInputObjectProperties; }; - //void SetOutputObjectProperties(TMap& InOutputObjectProperties) { OutputObjectProperties = InOutputObjectProperties; }; - - void SetTreatExistingMaterialsAsUpToDate(bool bInTreatExistingMaterialsAsUpToDate) { bTreatExistingMaterialsAsUpToDate = bInTreatExistingMaterialsAsUpToDate; } - - void SetStaticMeshGenerationProperties(const FHoudiniStaticMeshGenerationProperties& InStaticMeshGenerationProperties) { StaticMeshGenerationProperties = InStaticMeshGenerationProperties; }; - - void SetStaticMeshBuildSettings(const FMeshBuildSettings& InMBS) { StaticMeshBuildSettings = InMBS; }; - - protected: - - // Create a StaticMesh using the MeshDescription format - bool CreateStaticMesh_MeshDescription(); - - // Legacy function using RawMesh for static Mesh creation - bool CreateStaticMesh_RawMesh(); - - // Create a UHoudiniStaticMesh - bool CreateHoudiniStaticMesh(); - - static void ApplyComplexColliderHelper( - UStaticMesh* TargetStaticMesh, - UStaticMesh* ComplexStaticMesh, - const EHoudiniSplitType SplitType, - bool& AssignedCustomCollisionMesh, - FHoudiniOutputObject* OutputObject); - - void ResetPartCache(); - - bool UpdatePartVertexList(); - - void SortSplitGroups(); - - bool UpdateSplitsFacesAndIndices(); - - // Update this part's position cache if we haven't already - bool UpdatePartPositionIfNeeded(); - - // Update this part's normal cache if we haven't already - bool UpdatePartNormalsIfNeeded(); - - // Update this part's tangent and binormal caches if we haven't already - bool UpdatePartTangentsIfNeeded(); - - // Update this part's color cache if we haven't already - bool UpdatePartColorsIfNeeded(); - - // Update this part's alpha if we haven't already - bool UpdatePartAlphasIfNeeded(); - - // Update this part's face smoothing values if we haven't already - bool UpdatePartFaceSmoothingIfNeeded(); - - // Update this part's UV sets if we haven't already - bool UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused = false); - - // Update this part;s lightmap resolution cache if we haven't already - bool UpdatePartLightmapResolutionsIfNeeded(); - - // Update this part's lod screensize attribute cache if we haven't already - bool UpdatePartLODScreensizeIfNeeded(); - - // Update th unique materials ids and infos needed for this part using the face materials and overrides - bool UpdatePartNeededMaterials(); - - // Update this part's face material IDs, unique material IDs and material Infos caches if we haven't already - bool UpdatePartFaceMaterialIDsIfNeeded(); - - // Update this part's material overrides cache if we haven't already - bool UpdatePartFaceMaterialOverridesIfNeeded(); - - // Updates and create the material that are needed for this part - bool CreateNeededMaterials(); - - UStaticMesh* CreateNewStaticMesh(const FString& InMeshIdentifierString); - - UStaticMesh* FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); - - UHoudiniStaticMesh* CreateNewHoudiniStaticMesh(const FString& InMeshIdentifierString); - - UHoudiniStaticMesh* FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); - - float GetLODSCreensizeForSplit(const FString& SplitGroupName); - - // Create convex/UCX collider for a split and add to the aggregate - bool AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); - // Create simple colliders for a split and add to the aggregate - bool AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); - - // Helper functions to generate the simple colliders and add them to the aggregate - static int32 GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); - static int32 GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions); - - // Helper functions for the simple colliders generation - static void CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec); - static void CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); - static void CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); - static void CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec); - - // Helper functions to remove unused/stale components - static bool RemoveAndDestroyComponent(UObject* InComponent); - - // Helper to create a new mesh component - static UMeshComponent* CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf& InComponentType); - - // Helper to update an existing mesh component - static void UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, - const FHoudiniGeoPartObject *InHGPO, TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors, - bool bInApplyGenericProperties=true); - - // Helper to create or update a mesh component for a UStaticMesh or proxy mesh output - static UMeshComponent* CreateOrUpdateMeshComponent( - const UHoudiniOutput* InOutput, - UObject* InOuterComponent, - const FHoudiniOutputObjectIdentifier& InOutputIdentifier, - const TSubclassOf& InComponentType, - FHoudiniOutputObject& OutOutputObject, - FHoudiniGeoPartObject const *& OutFoundHGPO, - bool &bCreated); - - // Helper to initialize a UStaticMeshComponent after it was created. - static bool PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh); - - // Helper to initialize a UHoudiniStaticMeshComponent after it was created. - static bool PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh); - - static bool AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, - TArray& HoudiniCreatedSocketActors, TArray& HoudiniAttachedSocketActors); - - protected: - - // Data cache for this translator - - // The HoudiniGeoPartObject we're working on - FHoudiniGeoPartObject HGPO; - - // Outer object for attaching components to - UObject* OuterComponent; - - // Structure that handles cooking/baking package creation parameters - FHoudiniPackageParams PackageParams; - - - // Previous output objects - TMap InputObjects; - - // New Output objects - TMap OutputObjects; - - - // Input Material Map - TMap InputAssignmentMaterials; - // Output Material Map - TMap OutputAssignmentMaterials; - // Input Replacement Materials maps - TMap ReplacementMaterials; - // All the materials that have been generated by this Houdini Asset - // Used to avoid generating the same houdini material over and over again - TMap AllOutputMaterials; - - // Input mesh properties - //TMap InputObjectProperties; - // Output mesh properties - //TMap OutputObjectProperties; - - // Indicates the update is forced - bool ForceRebuild; - - // The generated simple/UCX colliders - TMap AllAggregateCollisions; - - // Names of the groups used for splitting the geometry - TArray AllSplitGroups; - - // Per-split lists of faces - TMap> AllSplitVertexLists; - - // Per-split number of faces - TMap AllSplitVertexCounts; - - // Per-split indices arrays - TMap> AllSplitFaceIndices; - - // Per-split first valid vertex index - TMap AllSplitFirstValidVertexIndex; - - // Per-split first valid prim index - TMap AllSplitFirstValidPrimIndex; - - // Vertex Indices for the part - TArray PartVertexList; - - // Positions - TArray PartPositions; - HAPI_AttributeInfo AttribInfoPositions; - - // Vertex Normals - TArray PartNormals; - HAPI_AttributeInfo AttribInfoNormals; - - // Vertex TangentU - TArray PartTangentU; - HAPI_AttributeInfo AttribInfoTangentU; - - // Vertex TangentV - TArray PartTangentV; - HAPI_AttributeInfo AttribInfoTangentV; - - // Vertex Colors - TArray PartColors; - HAPI_AttributeInfo AttribInfoColors; - - // Vertex Alpha values - TArray PartAlphas; - HAPI_AttributeInfo AttribInfoAlpha; - - // Face Smoothing masks - TArray PartFaceSmoothingMasks; - HAPI_AttributeInfo AttribInfoFaceSmoothingMasks; - - // UVs - TArray> PartUVSets; - TArray AttribInfoUVSets; - - // Lightmap resolution - TArray PartLightMapResolutions; - HAPI_AttributeInfo AttribInfoLightmapResolution; - - // Material IDs per face - TArray PartFaceMaterialIds; - HAPI_AttributeInfo AttribInfoFaceMaterialIds; - // Unique material IDs - TArray PartUniqueMaterialIds; - //TSet PartUniqueMaterialIds; - // Material infos for each unique Material - TArray PartUniqueMaterialInfos; - //TSet PartUniqueMaterialInfos; - // Indicates we only have a single face material - bool bOnlyOneFaceMaterial; - - // Material Overrides per face - TArray PartFaceMaterialOverrides; - HAPI_AttributeInfo AttribInfoFaceMaterialOverrides; - // Indicates that material overides attributes need an instance to be created - bool bMaterialOverrideNeedsCreateInstance; - - // LOD Screensize - TArray PartLODScreensize; - HAPI_AttributeInfo AttribInfoLODScreensize; - - int32 DefaultMeshSmoothing; - - // When building a mesh, if an associated material already exists, treat - // it as up to date, regardless of the MaterialInfo.bHasChanged flag - bool bTreatExistingMaterialsAsUpToDate; - - // Default properties to be used when generating Static Meshes - FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; - - // Default Mesh Build settings to be used when generating Static Meshes - FMeshBuildSettings StaticMeshBuildSettings; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" +#include "HoudiniAssetComponent.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "PhysicsEngine/AggregateGeom.h" + +//#include "HoudiniMeshTranslator.generated.h" + +class UStaticMesh; +class UStaticMeshSocket; +class UMaterialInterface; +class UMeshComponent; +class UStaticMeshComponent; +class UHoudiniStaticMesh; +class UHoudiniStaticMeshComponent; + +struct FKAggregateGeom; +struct FHoudiniGenericAttribute; + + +UENUM() +enum class EHoudiniSplitType : uint8 +{ + Invalid, + + Normal, + + LOD, + + RenderedComplexCollider, + InvisibleComplexCollider, + + RenderedUCXCollider, + InvisibleUCXCollider, + + RenderedSimpleCollider, + InvisibleSimpleCollider +}; + +struct HOUDINIENGINE_API FHoudiniMeshTranslator +{ + public: + + //----------------------------------------------------------------------------------------------------------------------------- + // HOUDINI TO UNREAL + //----------------------------------------------------------------------------------------------------------------------------- + + // + static bool CreateAllMeshesAndComponentsFromHoudiniOutput( + UHoudiniOutput* InOutput, + const FHoudiniPackageParams& InPackageParams, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, + const TMap& InAllOutputMaterials, + UObject* InOuterComponent, + bool bInTreatExistingMaterialsAsUpToDate=false, + bool bInDestroyProxies=false); + + static bool CreateStaticMeshFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + const FHoudiniPackageParams& InPackageParams, + const TMap& InOutputObjects, + TMap& OutOutputObjects, + TMap& InAssignmentMaterialMap, + TMap& InReplacementMaterialMap, + const TMap& InAllOutputMaterials, + const bool& InForceRebuild, + const EHoudiniStaticMeshMethod& InStaticMeshMethod, + const FHoudiniStaticMeshGenerationProperties& InSMGenerationProperties, + const FMeshBuildSettings& InMeshBuildSettings, + bool bInTreatExistingMaterialsAsUpToDate = false); + + static bool CreateOrUpdateAllComponents( + UHoudiniOutput* InOutput, + UObject* InOuterComponent, + TMap& InNewOutputObjects, + bool bInDestroyProxies=false, + bool bInApplyGenericProperties=true); + + + //----------------------------------------------------------------------------------------------------------------------------- + // HELPERS + //----------------------------------------------------------------------------------------------------------------------------- + static EHoudiniSplitType GetSplitTypeFromSplitName(const FString& InSplitName); + + static FString GetMeshIdentifierFromSplit(const FString& InSplitName, const EHoudiniSplitType& InSplitType); + + // TODO: Rename me! and template me! float/int/string ? + // TransferPartAttributesToSplitVertices + static int32 TransferRegularPointAttributesToVertices( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutVertexData); + + template + static int32 TransferPartAttributesToSplit( + const TArray& InVertexList, + const HAPI_AttributeInfo& InAttribInfo, + const TArray& InData, + TArray& OutSplitData); + + // Update the MeshBuild Settings using the values from the runtime settings/overrides on the HAC + void UpdateMeshBuildSettings( + FMeshBuildSettings& OutMeshBuildSettings, + const bool& bHasNormals, + const bool& bHasTangents, + const bool& bHasLightmapUVSet); + + + //----------------------------------------------------------------------------------------------------------------------------- + // ACCESSORS + //----------------------------------------------------------------------------------------------------------------------------- + + //----------------------------------------------------------------------------------------------------------------------------- + // MUTATORS + //----------------------------------------------------------------------------------------------------------------------------- + void SetHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) { HGPO = InHGPO; }; + void SetOuterComponent(UObject* InOuter) { OuterComponent = InOuter; }; + void SetPackageParams(const FHoudiniPackageParams& InPackageParams, const bool& bUpdateHGPO = false); + + void SetInputObjects(const TMap& InInputObjects) { InputObjects = InInputObjects; }; + void SetOutputObjects(TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; + + void SetInputAssignmentMaterials(const TMap& InInputMaterials) { InputAssignmentMaterials = InInputMaterials; }; + void SetReplacementMaterials(const TMap& InReplacementMaterials) { ReplacementMaterials = InReplacementMaterials; }; + void SetAllOutputMaterials(const TMap& InAllOutputMaterials) { AllOutputMaterials = InAllOutputMaterials; }; + + //void SetInputObjectProperties(const TMap& InInputObjectProperties) { InputObjectProperties = InInputObjectProperties; }; + //void SetOutputObjectProperties(TMap& InOutputObjectProperties) { OutputObjectProperties = InOutputObjectProperties; }; + + void SetTreatExistingMaterialsAsUpToDate(bool bInTreatExistingMaterialsAsUpToDate) { bTreatExistingMaterialsAsUpToDate = bInTreatExistingMaterialsAsUpToDate; } + + void SetStaticMeshGenerationProperties(const FHoudiniStaticMeshGenerationProperties& InStaticMeshGenerationProperties) { StaticMeshGenerationProperties = InStaticMeshGenerationProperties; }; + + void SetStaticMeshBuildSettings(const FMeshBuildSettings& InMBS) { StaticMeshBuildSettings = InMBS; }; + + protected: + + // Create a StaticMesh using the MeshDescription format + bool CreateStaticMesh_MeshDescription(); + + // Legacy function using RawMesh for static Mesh creation + bool CreateStaticMesh_RawMesh(); + + // Create a UHoudiniStaticMesh + bool CreateHoudiniStaticMesh(); + + static void ApplyComplexColliderHelper( + UStaticMesh* TargetStaticMesh, + UStaticMesh* ComplexStaticMesh, + const EHoudiniSplitType SplitType, + bool& AssignedCustomCollisionMesh, + FHoudiniOutputObject* OutputObject); + + void ResetPartCache(); + + bool UpdatePartVertexList(); + + void SortSplitGroups(); + + bool UpdateSplitsFacesAndIndices(); + + // Update this part's position cache if we haven't already + bool UpdatePartPositionIfNeeded(); + + // Update this part's normal cache if we haven't already + bool UpdatePartNormalsIfNeeded(); + + // Update this part's tangent and binormal caches if we haven't already + bool UpdatePartTangentsIfNeeded(); + + // Update this part's color cache if we haven't already + bool UpdatePartColorsIfNeeded(); + + // Update this part's alpha if we haven't already + bool UpdatePartAlphasIfNeeded(); + + // Update this part's face smoothing values if we haven't already + bool UpdatePartFaceSmoothingIfNeeded(); + + // Update this part's UV sets if we haven't already + bool UpdatePartUVSetsIfNeeded(const bool& bRemoveUnused = false); + + // Update this part;s lightmap resolution cache if we haven't already + bool UpdatePartLightmapResolutionsIfNeeded(); + + // Update this part's lod screensize attribute cache if we haven't already + bool UpdatePartLODScreensizeIfNeeded(); + + // Update th unique materials ids and infos needed for this part using the face materials and overrides + bool UpdatePartNeededMaterials(); + + // Update this part's face material IDs, unique material IDs and material Infos caches if we haven't already + bool UpdatePartFaceMaterialIDsIfNeeded(); + + // Update this part's material overrides cache if we haven't already + bool UpdatePartFaceMaterialOverridesIfNeeded(); + + // Updates and create the material that are needed for this part + bool CreateNeededMaterials(); + + UStaticMesh* CreateNewStaticMesh(const FString& InMeshIdentifierString); + + UStaticMesh* FindExistingStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); + + UHoudiniStaticMesh* CreateNewHoudiniStaticMesh(const FString& InMeshIdentifierString); + + UHoudiniStaticMesh* FindExistingHoudiniStaticMesh(const FHoudiniOutputObjectIdentifier& InIdentifier); + + float GetLODSCreensizeForSplit(const FString& SplitGroupName); + + // Create convex/UCX collider for a split and add to the aggregate + bool AddConvexCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); + // Create simple colliders for a split and add to the aggregate + bool AddSimpleCollisionToAggregate(const FString& SplitGroupName, FKAggregateGeom& AggCollisions); + + // Helper functions to generate the simple colliders and add them to the aggregate + static int32 GenerateBoxAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateSphereAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateSphylAsSimpleCollision(const TArray& InPositionArray, FKAggregateGeom& OutAggregateCollisions); + static int32 GenerateKDopAsSimpleCollision(const TArray& InPositionArray, const TArray &Dirs, FKAggregateGeom& OutAggregateCollisions); + + // Helper functions for the simple colliders generation + static void CalcBoundingBox(const TArray& PositionArray, FVector& Center, FVector& Extents, FVector& LimitVec); + static void CalcBoundingSphere(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); + static void CalcBoundingSphere2(const TArray& PositionArray, FSphere& sphere, FVector& LimitVec); + static void CalcBoundingSphyl(const TArray& PositionArray, FSphere& sphere, float& length, FRotator& rotation, FVector& LimitVec); + + // Helper functions to remove unused/stale components + static bool RemoveAndDestroyComponent(UObject* InComponent); + + // Helper to create a new mesh component + static UMeshComponent* CreateMeshComponent(UObject *InOuterComponent, const TSubclassOf& InComponentType); + + // Helper to update an existing mesh component + static void UpdateMeshComponent(UMeshComponent *InMeshComponent, const FHoudiniOutputObjectIdentifier &InOutputIdentifier, + const FHoudiniGeoPartObject *InHGPO, TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors, + bool bInApplyGenericProperties=true); + + // Helper to create or update a mesh component for a UStaticMesh or proxy mesh output + static UMeshComponent* CreateOrUpdateMeshComponent( + const UHoudiniOutput* InOutput, + UObject* InOuterComponent, + const FHoudiniOutputObjectIdentifier& InOutputIdentifier, + const TSubclassOf& InComponentType, + FHoudiniOutputObject& OutOutputObject, + FHoudiniGeoPartObject const *& OutFoundHGPO, + bool &bCreated); + + // Helper to initialize a UStaticMeshComponent after it was created. + static bool PostCreateStaticMeshComponent(UStaticMeshComponent *InComponent, UObject *InMesh); + + // Helper to initialize a UHoudiniStaticMeshComponent after it was created. + static bool PostCreateHoudiniStaticMeshComponent(UHoudiniStaticMeshComponent *InComponent, UObject *InMesh); + + static bool AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, + TArray& HoudiniCreatedSocketActors, TArray& HoudiniAttachedSocketActors); + + protected: + + // Data cache for this translator + + // The HoudiniGeoPartObject we're working on + FHoudiniGeoPartObject HGPO; + + // Outer object for attaching components to + UObject* OuterComponent; + + // Structure that handles cooking/baking package creation parameters + FHoudiniPackageParams PackageParams; + + + // Previous output objects + TMap InputObjects; + + // New Output objects + TMap OutputObjects; + + + // Input Material Map + TMap InputAssignmentMaterials; + // Output Material Map + TMap OutputAssignmentMaterials; + // Input Replacement Materials maps + TMap ReplacementMaterials; + // All the materials that have been generated by this Houdini Asset + // Used to avoid generating the same houdini material over and over again + TMap AllOutputMaterials; + + // Input mesh properties + //TMap InputObjectProperties; + // Output mesh properties + //TMap OutputObjectProperties; + + // Indicates the update is forced + bool ForceRebuild; + + // The generated simple/UCX colliders + TMap AllAggregateCollisions; + + // Names of the groups used for splitting the geometry + TArray AllSplitGroups; + + // Per-split lists of faces + TMap> AllSplitVertexLists; + + // Per-split number of faces + TMap AllSplitVertexCounts; + + // Per-split indices arrays + TMap> AllSplitFaceIndices; + + // Per-split first valid vertex index + TMap AllSplitFirstValidVertexIndex; + + // Per-split first valid prim index + TMap AllSplitFirstValidPrimIndex; + + // Vertex Indices for the part + TArray PartVertexList; + + // Positions + TArray PartPositions; + HAPI_AttributeInfo AttribInfoPositions; + + // Vertex Normals + TArray PartNormals; + HAPI_AttributeInfo AttribInfoNormals; + + // Vertex TangentU + TArray PartTangentU; + HAPI_AttributeInfo AttribInfoTangentU; + + // Vertex TangentV + TArray PartTangentV; + HAPI_AttributeInfo AttribInfoTangentV; + + // Vertex Colors + TArray PartColors; + HAPI_AttributeInfo AttribInfoColors; + + // Vertex Alpha values + TArray PartAlphas; + HAPI_AttributeInfo AttribInfoAlpha; + + // Face Smoothing masks + TArray PartFaceSmoothingMasks; + HAPI_AttributeInfo AttribInfoFaceSmoothingMasks; + + // UVs + TArray> PartUVSets; + TArray AttribInfoUVSets; + + // Lightmap resolution + TArray PartLightMapResolutions; + HAPI_AttributeInfo AttribInfoLightmapResolution; + + // Material IDs per face + TArray PartFaceMaterialIds; + HAPI_AttributeInfo AttribInfoFaceMaterialIds; + // Unique material IDs + TArray PartUniqueMaterialIds; + //TSet PartUniqueMaterialIds; + // Material infos for each unique Material + TArray PartUniqueMaterialInfos; + //TSet PartUniqueMaterialInfos; + // Indicates we only have a single face material + bool bOnlyOneFaceMaterial; + + // Material Overrides per face + TArray PartFaceMaterialOverrides; + HAPI_AttributeInfo AttribInfoFaceMaterialOverrides; + // Indicates that material overides attributes need an instance to be created + bool bMaterialOverrideNeedsCreateInstance; + + // LOD Screensize + TArray PartLODScreensize; + HAPI_AttributeInfo AttribInfoLODScreensize; + + int32 DefaultMeshSmoothing; + + // When building a mesh, if an associated material already exists, treat + // it as up to date, regardless of the MaterialInfo.bHasChanged flag + bool bTreatExistingMaterialsAsUpToDate; + + // Default properties to be used when generating Static Meshes + FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; + + // Default Mesh Build settings to be used when generating Static Meshes + FMeshBuildSettings StaticMeshBuildSettings; +}; diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp index b483582d2..3a91f8325 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp @@ -1,2199 +1,2271 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniOutputTranslator.h" - -#include "HoudiniOutput.h" -#include "HoudiniApi.h" -#include "HoudiniEngine.h" - -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniInput.h" -#include "HoudiniStaticMesh.h" - -#include "HoudiniMeshTranslator.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" - -#include "Editor.h" -#include "EditorSupportDelegates.h" -#include "FileHelpers.h" -#include "LandscapeInfo.h" -#include "HAL/PlatformFilemanager.h" -#include "HAL/FileManager.h" -#include "Engine/WorldComposition.h" -#include "Modules/ModuleManager.h" -#include "WorldBrowserModule.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// -bool -FHoudiniOutputTranslator::UpdateOutputs( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate, - bool& bOutHasHoudiniStaticMeshOutput) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Get the temp folder override - FHoudiniOutputTranslator::GetTempFolderFromAttribute(HAC); - - // Outputs that should be cleared, but only AFTER new output processing have taken place. - // This is needed for landscape resizing where the new landscape needs to copy data from the original landscape - // before the original landscape gets destroyed. - TArray DeferredClearOutputs; - - // Check if the HDA has been marked as not producing outputs - if (!HAC->bOutputless) - { - // Check if we want to convert legacy v1 data - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility && HAC->Version1CompatibilityHAC) - { - // Do not reuse legacy outputs! - for (auto& OldOutput : HAC->Outputs) - { - ClearOutput(OldOutput); - } - } - - TArray NewOutputs; - if (FHoudiniOutputTranslator::BuildAllOutputs(HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos)) - { - // NOTE: For now we are currently forcing all outputs to be cleared here. There is still an issue where, in some - // circumstances, landscape tiles disappear when clearing outputs after processing. - // The reason we may need to defer landscape clearing is to allow the landscape creation code to - // capture the extent of the landscape. The extent of the landscape can only be calculated if all landscape - // tiles are still present in the map. If we find that we don't need this for updating of Input landscapes, - // we can safely remove this feature. - ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); - // Replace with the new parameters - HAC->Outputs = NewOutputs; - } - } - else - { - // This HDA is marked as not supposed to produce any output - ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); - } - - // Look for details generic property attributes on the outputs, - // and try to apply them to the HAC. - // This can be used to preset some of the HDA's uproperty via attribute - TArray GenericAttributes; - for (auto& CurrentOutput : HAC->Outputs) - { - const TArray& CurrentOutputHGPO = CurrentOutput->GetHoudiniGeoPartObjects(); - for (auto& CurrentHGPO : CurrentOutputHGPO) - { - FHoudiniEngineUtils::GetGenericAttributeList( - CurrentHGPO.GeoId, - CurrentHGPO.PartId, - HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, - GenericAttributes, - HAPI_ATTROWNER_DETAIL); - } - } - - // Attempt to apply the attributes to the HAC if we have any - for (const auto& CurrentPropAttribute : GenericAttributes) - { - // Get the current Property Attribute - const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; - if (CurrentPropertyName.IsEmpty()) - continue; - - if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(HAC, CurrentPropAttribute)) - continue; - - // Success! - HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on Houdini Asset Component named %s"), *CurrentPropertyName, *HAC->GetName()); - } - - // NOTE: PersistentWorld can be NULL when, for example, working with - // HoudiniAssetComponents in Blueprints. - UWorld* PersistentWorld = HAC->GetWorld(); - UWorldComposition* WorldComposition = nullptr; - if (PersistentWorld) - { - WorldComposition = PersistentWorld->WorldComposition; - } - - if (IsValid(WorldComposition)) - { - // We don't want the origin to shift as we're potentially updating levels. - WorldComposition->bTemporarilyDisableOriginTracking = true; - } - - // "Process" the mesh. - // TODO: Move this to the actual processing stage, - // And see if some of this could be threaded - UObject* OuterComponent = HAC; - - FString HoudiniAssetPath = FPaths::GetPath(HAC->GetPathName()); - FString ComponentGUIDString = HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); - FString HoudiniAssetNameString = HAC->GetDisplayName(); - - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - PackageParams.ObjectName = FString(); - - // ---------------------------------------------------- - // Outputs prepass - // ---------------------------------------------------- - - TArray CreatedWorldCompositionPackages; - bool bCreatedNewMaps = false; - //... for heightfield outputs ...// - - // Collect all the landscape layers' global min/max values. - TMap LandscapeLayerGlobalMinimums; - TMap LandscapeLayerGlobalMaximums; - - // Store the instancer outputs separately so we can process them later, after all mesh output are processed. - // Determine the total number of instances, if we have more than 1 then mesh parts with instanced geo we will not create proxy meshes - // Also if we have object instancer (or oldschool attribute instancers), we won't be creating any proxy at all - TArray InstancerOutputs; - int32 NumInstances = 0; - bool bHasObjectInstancer = false; - - for (auto& CurOutput : HAC->Outputs) - { - if (CurOutput->GetType() == EHoudiniOutputType::Instancer) - { - // InstancerOutputs.Add(CurOutput); - for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.Type == EHoudiniPartType::Instancer) - { - if (HGPO.InstancerType == EHoudiniInstancerType::PackedPrimitive) - { - NumInstances += HGPO.PartInfo.InstanceCount; - } - else - { - NumInstances += HGPO.PartInfo.PointCount; - } - - if ((HGPO.InstancerType == EHoudiniInstancerType::ObjectInstancer) - || (HGPO.InstancerType == EHoudiniInstancerType::OldSchoolAttributeInstancer)) - { - bHasObjectInstancer = true; - } - } - } - } - else if (CurOutput->GetType() == EHoudiniOutputType::Landscape) - { - FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, false); - } - } - - bOutHasHoudiniStaticMeshOutput = false; - int32 NumVisibleOutputs = 0; - int32 NumOutputs = HAC->Outputs.Num(); - bool bHasLandscape = false; - - // Before processing all the outputs, - // See if we have any landscape input that have "Update Input Landscape" enabled - // And make an array of all our input landscapes as well. - TArray AllInputLandscapes; - TArray InputLandscapesToUpdate; - - FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); - - // ---------------------------------------------------- - // Process outputs - // ---------------------------------------------------- - // Landscape creation will cache the first tile as a reference location - // in this struct to be used by during construction of subsequent tiles. - FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; - // Landscape Size info will be cached by the first tile, similar to LandscapeReferenceLocation - FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; - FHoudiniLandscapeExtent LandscapeExtent; - TSet ClearedLandscapeLayers; - - // The houdini materials that have been generated by this HDA. - // We track them to prevent recreate the same houdini material over and over if it is assigned to multiple parts. - // (this can easily happen when using packed prims) - TMap AllOutputMaterials; - - TArray CreatedPackages; - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* CurOutput = HAC->GetOutputAt(OutputIdx); - if (!CurOutput || CurOutput->IsPendingKill()) - continue; - - FString Notification = FString::Format(TEXT("Processing output {0} / {1}..."), {FString::FromInt(OutputIdx + 1), FString::FromInt(NumOutputs)}); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - if (!HAC->IsOutputTypeSupported(CurOutput->GetType())) - continue; - - switch (CurOutput->GetType()) - { - case EHoudiniOutputType::Mesh: - { - bool bIsProxyStaticMeshEnabled = ( - HAC->IsProxyStaticMeshEnabled() && - !HAC->HasNoProxyMeshNextCookBeenRequested() && - !HAC->IsBakeAfterNextCookEnabled()); - if (bIsProxyStaticMeshEnabled && NumInstances > 1) - { - if (bHasObjectInstancer) - { - // Completely disable proxies if we have object instancers/old school attribute instancers - // as they rely on having a static mesh created (and the instanced mesh HGPO is not marked as instanced...) - bIsProxyStaticMeshEnabled = false; - } - else - { - // If we dont have proxy instancer, enable proxy only for non-instanced mesh - for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) - { - if (HGPO.bIsInstanced && HGPO.Type == EHoudiniPartType::Mesh) - { - bIsProxyStaticMeshEnabled = false; - break; - } - } - } - } - - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - PackageParams, - bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, - HAC->StaticMeshGenerationProperties, - HAC->StaticMeshBuildSettings, - AllOutputMaterials, - OuterComponent); - - NumVisibleOutputs++; - - // Look for UHoudiniStaticMesh in the output, and set bOutHasHoudiniStaticMeshOutput accordingly - if (bIsProxyStaticMeshEnabled && !bOutHasHoudiniStaticMeshOutput) - { - bOutHasHoudiniStaticMeshOutput &= CurOutput->HasAnyCurrentProxy(); - } - break; - } - - case EHoudiniOutputType::Curve: - { - const TArray &GeoPartObjects = CurOutput->GetHoudiniGeoPartObjects(); - - if (GeoPartObjects.Num() <= 0) - continue; - - const FHoudiniGeoPartObject & CurHGPO = GeoPartObjects[0]; - - if (CurOutput->IsEditableNode()) - { - if (!CurOutput->HasEditableNodeBuilt()) - { - // Editable curve, only need to be built once. - UHoudiniSplineComponent* HoudiniSplineComponent = FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode( - CurHGPO.GeoId, - CurHGPO.PartName, - HAC); - - HoudiniSplineComponent->SetIsEditableOutputCurve(true); - - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; - EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; - EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; - EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; - - TMap& OutputObjects = CurOutput->GetOutputObjects(); - FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - FoundOutputObject.OutputComponent = HoudiniSplineComponent; - - CurOutput->SetHasEditableNodeBuilt(true); - } - } - else - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); - NumVisibleOutputs += CurOutput->GetOutputObjects().Num(); - break; - } - } - break; - - case EHoudiniOutputType::Instancer: - InstancerOutputs.Add(CurOutput); - break; - - case EHoudiniOutputType::Landscape: - { - NumVisibleOutputs++; - - // This gets called for each heightfield primitive from Houdini, i.e., each "tile". - bool bNewMapCreated = false; - // Registering of untracked actors is not currently used in the HDA - // workflow. HDA cleanup will manually search for shared landscapes - // and remove them. That aforementioned behaviour should really be updated to - // make use of untracked actors on the HAC (similar to PDG Asset Link). - TArray> UntrackedActors; - - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - UntrackedActors, - InputLandscapesToUpdate, - AllInputLandscapes, - HAC, - TEXT("{hda_actor_name}_"), - PersistentWorld, - LandscapeLayerGlobalMinimums, - LandscapeLayerGlobalMaximums, - LandscapeExtent, - LandscapeSizeInfo, - LandscapeReferenceLocation, - PackageParams, - ClearedLandscapeLayers, - CreatedPackages); - - bHasLandscape = true; - - // Attach the created landscape to the parent HAC. - ALandscapeProxy* OutputLandscape = nullptr; - for (auto& Pair : CurOutput->GetOutputObjects()) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(Pair.Value.OutputObject); - if (IsValid(LandscapePtr)) - { - OutputLandscape = LandscapePtr->GetRawPtr(); - } - break; - } - - if (OutputLandscape) - { - // Attach the created landscapes to HAC - // Output Transforms are always relative to the HDA - HAC->SetMobility(EComponentMobility::Static); - OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); - // Note that the above attach will cause the collision components to crap out. This manifests - // itself via the Landscape editor tools not being able to trace Landscape collision components. - // By recreating collision components here, it appears to put things back into working order. - OutputLandscape->GetLandscapeInfo()->FixupProxiesTransform(); - OutputLandscape->GetLandscapeInfo()->RecreateLandscapeInfo(PersistentWorld, true); - OutputLandscape->RecreateCollisionComponents(); - FEditorDelegates::PostLandscapeLayerUpdated.Broadcast(); - } - - bCreatedNewMaps |= bNewMapCreated; - break; - } - default: - // Do Nothing for now - break; - } - - for (auto& CurMat : CurOutput->AssignementMaterials) - { - // Add the newly generated materials if any - if (!AllOutputMaterials.Contains(CurMat.Key)) - AllOutputMaterials.Add(CurMat); - } - } - - // Now that all meshes have been created, process the instancers - for (auto& CurOutput : InstancerOutputs) - { - if (!FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent)) - continue; - - NumVisibleOutputs++; - } - - if (NumVisibleOutputs > 0) - { - // If we have valid outputs, we don't need to display the houdini logo anymore... - FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(HAC); - } - else - { - // ... if we don't have any valid outputs however, we should - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HAC); - } - - // Clear any old outputs that was marked as "Should Defer Clear". - // This should happen before SharedLandscapeActor cleanup - // since this needs to remove old landscape proxies so that empty SharedLandscapeActors - // can be removed afterward. - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniOutputTranslator::UpdateOutputs] Clearing old outputs: %d"), DeferredClearOutputs.Num()); - for(UHoudiniOutput* OldOutput : DeferredClearOutputs) - { - ClearOutput(OldOutput); - } - - // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) - // { - // LandscapeExtents.IntermediateResizeLandscape->Destroy(); - // LandscapeExtents.IntermediateResizeLandscape = nullptr; - // } - - if (bHasLandscape) - { - // ---------------------------------------------------- - // Cleanup untracked shared landscape actors - // ---------------------------------------------------- - // This is a nasty hack to clean up SharedLandscape actors generated by the - // Landscape translator but aren't tracked by an HoudiniOutputObject, since the - // translators can't dynamically create outputs. - - { - // First collect all the landscapes that is being tracked by the HAC. - TSet TrackedLandscapes; - for(UHoudiniOutput* Output : HAC->Outputs) - { - if (Output->GetType() == EHoudiniOutputType::Landscape) - { - for(auto& Elem : Output->GetOutputObjects()) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(Elem.Value.OutputObject); - if (!IsValid(LandscapePtr)) - continue; - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - if (IsValid(LandscapeProxy)) - { - TrackedLandscapes.Add(LandscapeProxy); - - // We need to recreate component states for landscapes if a tile was created, moved, or resized - // otherwise the landscape will exhibit render artifacts (such as only rendering every other - // component.) - LandscapeProxy->RecreateComponentsState(); - } - } - } - } - - // Iterate over Houdini asset child assets in order to find dangling Landscape actors - TArray AttachedComponents = HAC->GetAttachChildren(); - for(USceneComponent* Component : AttachedComponents) - { - if (!IsValid(Component)) - continue; - AActor* Actor = Component->GetOwner(); - ALandscape* Landscape = Cast(Actor); - if (!Landscape) - continue; - if (TrackedLandscapes.Contains(Landscape)) - continue; - - if (Landscape->GetLandscapeInfo()->Proxies.Num() == 0) - Landscape->Destroy(); - } - } - - // Recreate Landscape Info calls WorldChange, so no need to do it manually. - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - -#if WITH_EDITOR - if (GEditor) - { - // We force a viewport refresh since some actions, such as updating landscape - // edit layers will not reflect until the user moves the viewport camera. - GEditor->RedrawLevelEditingViewports(true); - } -#endif - } - - // Destroy the intermediate resize landscape, if there is one. - // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) - // { - // FHoudiniLandscapeTranslator::DestroyLandscape(LandscapeExtents.IntermediateResizeLandscape); - // } - - if (IsValid(WorldComposition)) - { - // Disable the flag that we set before starting the import process. - WorldComposition->bTemporarilyDisableOriginTracking = false; - } - - // If the owner component was marked as loaded, unmark all outputs - if (HAC->HasBeenLoaded()) - { - for (auto& CurrentOutput : HAC->Outputs) - { - CurrentOutput->MarkAsLoaded(false); - } - } - - if (bCreatedNewMaps) - { - // Force the asset registry to update its cache of packages paths - // recursively for this world, otherwise world composition won't - // pick them up during the WorldComposition::Rescan(). - FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); - - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - - FHoudiniEngineUtils::LogWorldInfo(PersistentWorld); - if (WorldComposition) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); - } - - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); - } - - if (CreatedPackages.Num() > 0) - { - // Save created packages. For example, we don't want landscape layers deleted - // along with the HDA. - FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); - } - - return true; -} - -bool -FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - UObject* OuterComponent = HAC; - - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - PackageParams.ObjectName = FString(); - - // Keep track of all generated houdini materials to avoid recreating them over and over - TMap AllOutputMaterials; - - bool bFoundProxies = false; - TArray InstancerOutputs; - for (auto& CurOutput : HAC->Outputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType == EHoudiniOutputType::Mesh) - { - if (CurOutput->HasAnyCurrentProxy()) - { - bFoundProxies = true; - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - PackageParams, - HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, - HAC->StaticMeshGenerationProperties, - HAC->StaticMeshBuildSettings, - AllOutputMaterials, - OuterComponent, - true, // bInTreatExistingMaterialsAsUpToDate - bInDestroyProxies - ); - } - } - else if (OutputType == EHoudiniOutputType::Instancer) - { - InstancerOutputs.Add(CurOutput); - } - - for (auto& CurMat : CurOutput->AssignementMaterials) - { - //Adds the generated materials if any - if (!AllOutputMaterials.Contains(CurMat.Key)) - AllOutputMaterials.Add(CurMat); - } - } - - // Rebuild instancers if we built any static meshes from proxies - if (bFoundProxies) - { - for (auto& CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent); - } - } - - return true; -} - -// -bool -FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) -{ - HAPI_NodeId & AssetId = HAC->AssetId; - // Get the AssetInfo - 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 ObjectInfos; - TArray ObjectTransforms; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) - return false; - - TArray EditableCurveObjIds; - TArray EditableCurveGeoIds; - TArray EditableCurvePartIds; - TArray EditableCurvePartNames; - - // Iterate through all objects to get all editable curve's object geo and part Ids. - - for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) - { - // Retrieve the object info - const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; - - // Cache/convert them - FHoudiniObjectInfo CurrentObjectInfo; - CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); - - // Start by getting the number of editable nodes - int32 EditableNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, - true, &EditableNodeCount)); - - if (EditableNodeCount > 0) - { - TArray< HAPI_NodeId > EditableNodeIds; - EditableNodeIds.SetNumUninitialized(EditableNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - - for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) - { - HAPI_GeoInfo CurrentEditableGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentEditableGeoInfo.isDisplayGeo) - continue; - - // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GeoType::HAPI_GEOTYPE_CURVE) - continue; - - // Check if the curve is closed (-1 unknown, could not find parameter on node). A closed curve will - // be returned as a mesh by HAPI instead of a curve - int32 CurveClosed = -1; - if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - EditableNodeIds[nEditable], HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) - { - CurveClosed = -1; - } - else - { - if (CurveClosed) - CurveClosed = 1; - else - CurveClosed = 0; - } - - // Cook the editable node to get its parts - if (CurrentEditableGeoInfo.partCount <= 0) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentEditableGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentEditableGeoInfo.nodeId, - &CurrentEditableGeoInfo)); - } - - // Iterate on this geo's parts - for (int32 PartId = 0; PartId < CurrentEditableGeoInfo.partCount; ++PartId) - { - // Get part information. - HAPI_PartInfo CurrentHapiPartInfo; - FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - continue; - - // A closed curve will be returned as a mesh in HAPI - if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE && - (CurveClosed <= 0 || CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_MESH)) - continue; - - // Get the editable curve's part name - FHoudiniEngineString hapiSTR(CurrentHapiPartInfo.nameSH); - FString PartName; - hapiSTR.ToFString(PartName); - - EditableCurveObjIds.Add(CurrentHapiObjectInfo.nodeId); - EditableCurveGeoIds.Add(CurrentEditableGeoInfo.nodeId); - EditableCurvePartIds.Add(CurrentHapiPartInfo.id); - EditableCurvePartNames.Add(PartName); - } - } - } - } - - int32 Idx = 0; - for (auto& CurrentOutput : HAC->Outputs) - { - if (CurrentOutput->IsEditableNode()) - { - // The HAC is Loaded, re-assign node id to its editable curves - if (CurrentOutput->HasEditableNodeBuilt()) - { - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - for (auto& Pair : OutputObjects) - { - if (Idx >= EditableCurvePartIds.Num()) - break; - - UHoudiniSplineComponent * HoudiniSplineComponent = Cast(Pair.Value.OutputComponent); - if (HoudiniSplineComponent && !HoudiniSplineComponent->IsPendingKill()) - { - HoudiniSplineComponent->SetNodeId(EditableCurveGeoIds[Idx]); - - Pair.Key.ObjectId = EditableCurveObjIds[Idx]; - Pair.Key.GeoId = EditableCurveGeoIds[Idx]; - Pair.Key.PartId = EditableCurvePartIds[Idx]; - Pair.Key.PartName = EditableCurvePartNames[Idx]; - - Idx += 1; - } - } - } - // The HAC is a Duplication, re-construct output objects with attached duplicated editable curves, matching by part name - else - { - const TArray &Children = HAC->GetAttachChildren(); - for (auto & CurAttachedComp : Children) - { - if (!CurAttachedComp || CurAttachedComp->IsPendingKill()) - continue; - - if (!CurAttachedComp->IsA()) - continue; - - UHoudiniSplineComponent * CurAttachedSplineComp = Cast(CurAttachedComp); - if (!CurAttachedSplineComp) - continue; - - if (!CurAttachedSplineComp->IsEditableOutputCurve()) - continue; - - if (Idx >= EditableCurvePartIds.Num()) - break; - - // Found a match - if (CurAttachedSplineComp->GetGeoPartName().Equals(EditableCurvePartNames[Idx])) - { - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = EditableCurveObjIds[Idx]; - EditableSplineComponentIdentifier.GeoId = EditableCurveGeoIds[Idx]; - EditableSplineComponentIdentifier.PartId = EditableCurvePartIds[Idx]; - EditableSplineComponentIdentifier.PartName = EditableCurvePartNames[Idx]; - - CurAttachedSplineComp->SetNodeId(EditableSplineComponentIdentifier.GeoId); - - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - FHoudiniOutputObject& NewOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - NewOutputObject.OutputComponent = CurAttachedSplineComp; - - CurrentOutput->SetHasEditableNodeBuilt(true); - - // Never add additional rot/scale attributes on editable curves as this crashes HAPI - FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( - CurAttachedSplineComp, false); - - Idx += 1; - break; - } - } - } - } - else - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); - } - - // Mark our outputs as loaded so they can be matched for potential reuse - // This indicates that the HGPO's ids are invalid and that HGPO should be matched using partnames instead - CurrentOutput->MarkAsLoaded(true); - } - - return true; -} - -// -bool -FHoudiniOutputTranslator::UploadChangedEditableOutput( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray &Outputs = HAC->Outputs; - - // Iterate through the outputs array of HAC. - for (auto& CurrentOutput : HAC->Outputs) - { - if (!CurrentOutput) - continue; - - // Only update the editable nodes that have been built before. - if (!CurrentOutput->IsEditableNode() || !CurrentOutput->HasEditableNodeBuilt()) - continue; - - for (auto& CurrentOutputObj : CurrentOutput->GetOutputObjects()) - { - UHoudiniSplineComponent* HoudiniSplineComponent = Cast(CurrentOutputObj.Value.OutputComponent); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (!HoudiniSplineComponent->HasChanged()) - continue; - - // Dont add rot/scale on editable curves as this crashes HAPI - if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( - HoudiniSplineComponent, false)) - HoudiniSplineComponent->MarkChanged(false); - else - HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); - } - } - - return true; -} - - -bool -FHoudiniOutputTranslator::BuildAllOutputs( - const HAPI_NodeId& AssetId, - UObject* InOuterObject, - TArray& InOldOutputs, - TArray& OutNewOutputs, - const bool& InOutputTemplatedGeos) -{ - // Ensure the asset has a valid node ID - if (AssetId < 0) - { - return false; - } - - // Get the AssetInfo - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - FString CurrentAssetName; - { - FHoudiniEngineString hapiSTR(AssetInfo.nameSH); - hapiSTR.ToFString(CurrentAssetName); - } - - // Retrieve the asset's transform. - // TODO: Unused?! - //FTransform AssetUnrealTransform; - //if (!FHoudiniEngineUtils::HapiGetAssetTransform(AssetId, AssetUnrealTransform)) - // return false; - - // Retrieve information about each object contained within our asset. - TArray ObjectInfos; - TArray ObjectTransforms; - if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) - return false; - - // Mark all the previous HGPOs on the outputs as stale - // This indicates that they were from a previous cook and should then be deleted - for (auto& CurOutput : InOldOutputs) - { - if (CurOutput) - CurOutput->MarkAllHGPOsAsStale(true); - } - - // For HF / Volumes, we only create new Outputs for height volume - // Store all the other volumes (masks etc) on the side and we will - // match them with theit corresponding height volume after - TArray UnassignedVolumeParts; - - // Iterate through all objects. - int32 OutputIdx = 1; - for (int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ObjectIdx++) - { - // Retrieve the object info - const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectIdx]; - - // Cache/convert them - FHoudiniObjectInfo CurrentObjectInfo; - CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); - - // Retrieve object name. - FString CurrentObjectName = CurrentObjectInfo.Name; - - // Get transformation for this object. - FTransform TransformMatrix = FTransform::Identity; - if (ObjectTransforms.IsValidIndex(ObjectIdx)) - { - const HAPI_Transform & ObjectTransform = ObjectTransforms[ObjectIdx]; - FHoudiniEngineUtils::TranslateHapiTransform(ObjectTransform, TransformMatrix); - } - else - { - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: No HAPI transform for Object [%d %s] - using identity."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName); - } - - // Build an array of the geos we'll need to process - // In most case, it will only be the display geo, - // but we may also want to process editable geos as well - TArray GeoInfos; - - // Get the Display Geo's info - HAPI_GeoInfo DisplayHapiGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName); - } - else - { - // Add the display geo info to the array - GeoInfos.Add(DisplayHapiGeoInfo); - } - - // Handle the editable nodes for this geo - // Start by getting the number of editable nodes - int32 EditableNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, - true, &EditableNodeCount)); - - if (EditableNodeCount > 0) - { - TArray< HAPI_NodeId > EditableNodeIds; - EditableNodeIds.SetNumUninitialized(EditableNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - - for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) - { - HAPI_GeoInfo CurrentEditableGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentEditableGeoInfo.isDisplayGeo) - continue; - - // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) - continue; - - // Add this geo to the geo info array - GeoInfos.Add(CurrentEditableGeoInfo); - } - } - - // Handle the templated nodes if desired - if (InOutputTemplatedGeos) - { - // Start by getting the number of templated nodes - int32 TemplatedNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, - HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, - true, &TemplatedNodeCount)); - - if (TemplatedNodeCount > 0) - { - TArray TemplatedNodeIds; - TemplatedNodeIds.SetNumUninitialized(TemplatedNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, TemplatedNodeIds.GetData(), TemplatedNodeCount)); - - for (int32 nTemplated = 0; nTemplated < TemplatedNodeCount; nTemplated++) - { - HAPI_GeoInfo CurrentTemplatedGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentTemplatedGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - TemplatedNodeIds[nTemplated], &CurrentTemplatedGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentTemplatedGeoInfo.isDisplayGeo) - continue; - - // We don't want all the nested template node IDs, - // as our HDA could potentially be using other HDAs with nested template flags - // Make sure the parent of the templated node is either the HDA, the current OBJ or the Display SOP - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurrentTemplatedGeoInfo.nodeId); - if (ParentId != CurrentHapiObjectInfo.nodeId - && ParentId != DisplayHapiGeoInfo.nodeId - && ParentId != AssetId) - { - continue; - } - - // Add this geo to the geo info array - GeoInfos.Add(CurrentTemplatedGeoInfo); - } - } - } - - // Iterates through the geos we want to process - for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) - { - // Cook editable/templated nodes to get their parts. - const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; - if ((CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) - || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0)) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, - &GeoInfos[GeoIdx])); - } - - // Cache/convert the display geo's info - FHoudiniGeoInfo CurrentGeoInfo; - CacheGeoInfo(CurrentHapiGeoInfo, CurrentGeoInfo); - - // Simply create an empty array for this geo's group names - // We might need it later for splitting - TArray GeoGroupNames; - - // Store all the sockets found for this geo's part - TArray GeoMeshSockets; - - // Iterate on this geo's parts - for (int32 PartId = 0; PartId < CurrentGeoInfo.PartCount; ++PartId) - { - // Get part information. - HAPI_PartInfo CurrentHapiPartInfo; - FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); - - // If the geo is templated, cook it manually - if (CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - } - - bool bPartInfoFailed = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - { - bPartInfoFailed = true; - - // If the geo is templated, attempt to cook it manually - if(CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) - { - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, nullptr); - FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); - - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, - &GeoInfos[GeoIdx])); - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) - { - // We managed to get the templated part infos after cooking - bPartInfoFailed = false; - } - } - } - - if (bPartInfoFailed) - { - // Error retrieving part info. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d] unable to retrieve PartInfo - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId); - continue; - } - - // Convert/cache the part info - FHoudiniPartInfo CurrentPartInfo; - CachePartInfo(CurrentHapiPartInfo, CurrentPartInfo); - - // Retrieve part name. - FString CurrentPartName = CurrentPartInfo.Name; - - // Unsupported/Invalid part - if (CurrentPartInfo.Type == EHoudiniPartType::Invalid) - continue; - - // Update part/instancer type from the part infos - EHoudiniPartType CurrentPartType = EHoudiniPartType::Invalid; - EHoudiniInstancerType CurrentInstancerType = EHoudiniInstancerType::Invalid; - switch (CurrentHapiPartInfo.type) - { - case HAPI_PARTTYPE_BOX: - case HAPI_PARTTYPE_SPHERE: - case HAPI_PARTTYPE_MESH: - { - if (CurrentHapiGeoInfo.type == HAPI_GEOTYPE_CURVE) - { - // Closed curve will be seen as mesh - CurrentPartType = EHoudiniPartType::Curve; - } - else - { - CurrentPartType = EHoudiniPartType::Mesh; - - if (CurrentHapiObjectInfo.isInstancer) - { - if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // That part is actually an attribute instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - } - else - { - // That part is actually an instancer - CurrentPartType = EHoudiniPartType::Instancer; - CurrentInstancerType = EHoudiniInstancerType::ObjectInstancer; - } - - } - else if (CurrentHapiPartInfo.vertexCount <= 0 && CurrentHapiPartInfo.pointCount <= 0) - { - // No points, no vertices, we're likely invalid - CurrentPartType = EHoudiniPartType::Invalid; - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a mesh with no points or vertices - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - } - else if (CurrentHapiPartInfo.vertexCount <= 0) - { - // This is not an instancer, we do not have vertices, but we have points - // Maybe this is a point cloud with attribute override instancing - if(FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // Mark it as an instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - } - else - { - // No vertices, not an instancer, just a point cloud, consider ourself as invalid - CurrentPartType = EHoudiniPartType::Invalid; - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a point cloud mesh - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - } - } - } - } - break; - - case HAPI_PARTTYPE_CURVE: - { - // Make sure that this curve is not an an attribute instancer! - if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) - { - // Mark the part as an instancer it as an instancer - CurrentPartType = EHoudiniPartType::Instancer; - // Instancer type is set by IsAttributeInstancer - //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - } - else - { - // The curve is a curve! - CurrentPartType = EHoudiniPartType::Curve; - } - } - break; - - case HAPI_PARTTYPE_INSTANCER: - // This is a packed primitive instancer - CurrentPartType = EHoudiniPartType::Instancer; - CurrentInstancerType = EHoudiniInstancerType::PackedPrimitive; - break; - - case HAPI_PARTTYPE_VOLUME: - // Volume data, likely a Heightfield height / mask - CurrentPartType = EHoudiniPartType::Volume; - break; - - default: - // Unsupported Part Type - break; - } - - // There are no vertices AND no points and this part is not a packed prim instancer - if ((CurrentPartInfo.VertexCount <= 0 && CurrentPartInfo.PointCount <= 0) - && (CurrentPartType != EHoudiniPartType::Instancer || CurrentInstancerType != EHoudiniInstancerType::PackedPrimitive)) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] no points or vertices found - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - continue; - } - - // This is an instancer with no points. - if (CurrentHapiObjectInfo.isInstancer && CurrentHapiPartInfo.pointCount <= 0) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is instancer but has 0 points - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); - continue; - } - - // Extract Mesh sockets - // Do this before ignoring invalid parts, as socket groups/attributes could be set on parts - // that don't have any mesh, just points! Those would be be considered "invalid" parts but - // could still have valid sockets! - TArray PartMeshSockets; - FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, PartMeshSockets, CurrentHapiPartInfo.isInstanced); - FHoudiniEngineUtils::AddMeshSocketsToArray_Group( - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, PartMeshSockets, CurrentHapiPartInfo.isInstanced); - - // Ignore invalid parts - if (CurrentPartType == EHoudiniPartType::Invalid) - { - if(PartMeshSockets.Num() > 0) - { - // Store these Part sockets for the Geo - // We'll copy them to the outputs produced by this Geo later - GeoMeshSockets.Append(PartMeshSockets); - } - - continue; - } - - // Build the HGPO corresponding to this part - FHoudiniGeoPartObject currentHGPO; - currentHGPO.AssetId = AssetId; - currentHGPO.AssetName = CurrentAssetName; - - currentHGPO.ObjectId = CurrentHapiObjectInfo.nodeId; - currentHGPO.ObjectName = CurrentObjectName; - - currentHGPO.GeoId = CurrentHapiGeoInfo.nodeId; - - currentHGPO.PartId = CurrentHapiPartInfo.id; - - currentHGPO.Type = CurrentPartType; - currentHGPO.InstancerType = CurrentInstancerType; - - currentHGPO.TransformMatrix = TransformMatrix; - - currentHGPO.NodePath = TEXT(""); - - currentHGPO.bIsVisible = CurrentHapiObjectInfo.isVisible && !CurrentHapiPartInfo.isInstanced; - currentHGPO.bIsEditable = CurrentHapiGeoInfo.isEditable; - currentHGPO.bIsInstanced = CurrentHapiPartInfo.isInstanced; - // Never consider a display geo as templated! - currentHGPO.bIsTemplated = CurrentHapiGeoInfo.isDisplayGeo ? false : CurrentHapiGeoInfo.isTemplated; - - currentHGPO.bHasGeoChanged = CurrentHapiGeoInfo.hasGeoChanged; - currentHGPO.bHasPartChanged = CurrentHapiPartInfo.hasChanged; - currentHGPO.bHasMaterialsChanged = CurrentHapiGeoInfo.hasMaterialChanged; - currentHGPO.bHasTransformChanged = CurrentHapiObjectInfo.hasTransformChanged; - - // Copy the HAPI info caches - currentHGPO.ObjectInfo = CurrentObjectInfo; - currentHGPO.GeoInfo = CurrentGeoInfo; - currentHGPO.PartInfo = CurrentPartInfo; - - currentHGPO.AllMeshSockets = PartMeshSockets; - - // We only support meshes for templated geos - if (currentHGPO.bIsTemplated && (CurrentPartType != EHoudiniPartType::Mesh)) - continue; - - // Update the HGPO's node path - FHoudiniEngineUtils::HapiGetNodePath(currentHGPO, currentHGPO.NodePath); - - // Try to get the custom part name from attribute - FString CustomPartName; - if (FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CustomPartName)) - currentHGPO.SetCustomPartName(CustomPartName); - else - currentHGPO.PartName = CurrentPartName; - - // - // Mesh Only - Extract split groups - // - // Extract the group names used by this part to see if it will require splitting - // Only meshes can be split, via their primitive groups - TArray SplitGroupNames; - if (CurrentPartType == EHoudiniPartType::Mesh) - { - if (!CurrentHapiPartInfo.isInstanced && GeoGroupNames.Num() > 0) - { - // We are not instanced and already have extracted the geo's group names - // We can simply reuse the Geo group names / socket groups - currentHGPO.SplitGroups = GeoGroupNames; - } - else - { - // We need to get the primitive group names from HAPI - int32 GroupCount = 0; - TArray GroupNames; - if (!FHoudiniEngineUtils::HapiGetGroupNames( - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - HAPI_GROUPTYPE_PRIM, CurrentHapiPartInfo.isInstanced, - GroupNames)) - { - GroupCount = 0; - GroupNames.Empty(); - } - - // Convert the string handles to FStrings - for (const FString& GroupName : GroupNames) - { - FString LodGroup = HAPI_UNREAL_GROUP_LOD_PREFIX; - FString CollisionGroup = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; - FString RenderedCollisionGroup = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; - if (GroupName.StartsWith(LodGroup, ESearchCase::IgnoreCase) - || GroupName.StartsWith(CollisionGroup, ESearchCase::IgnoreCase) - || GroupName.StartsWith(RenderedCollisionGroup, ESearchCase::IgnoreCase)) - //|| GroupName.StartsWith(HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX, ESearchCase::IgnoreCase)) - { - // Split by collisions / lods - currentHGPO.SplitGroups.Add(GroupName); - } - } - - // Sort the Group name array by name, - // this will order the LODs and other incremental group names - currentHGPO.SplitGroups.Sort(); - - // If this part is not instanced, we can copy the geo - // group names so we can reuse them for another part - if (!CurrentHapiPartInfo.isInstanced) - { - GeoGroupNames = currentHGPO.SplitGroups; - } - } - } - - // - // Volume Only - Extract volume name/tile index - // - // Extract the volume's name, and see if a tile attribute is present - FHoudiniVolumeInfo CurrentVolumeInfo; - if (CurrentPartType == EHoudiniPartType::Volume) - { - // Get this volume's info - HAPI_VolumeInfo CurrentHapiVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentHapiVolumeInfo); - - bool bVolumeValid = true; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - &CurrentHapiVolumeInfo)) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.tupleSize != 1) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.zLength != 1) - { - bVolumeValid = false; - } - else if (CurrentHapiVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) - { - bVolumeValid = false; - } - - // Only cache valid volumes - if (bVolumeValid) - { - // Convert/Cache the volume info - CacheVolumeInfo(CurrentHapiVolumeInfo, CurrentVolumeInfo); - - // Get the volume's name - currentHGPO.VolumeName = CurrentVolumeInfo.Name; - - // Now see if this volume has a tile attribute - TArray TileValues; - if (FHoudiniEngineUtils::GetTileAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, TileValues, HAPI_ATTROWNER_PRIM)) - { - if (TileValues.Num() > 0 && TileValues[0] >= 0) - currentHGPO.VolumeTileIndex = TileValues[0]; - else - currentHGPO.VolumeTileIndex = -1; - } - } - } - currentHGPO.VolumeInfo = CurrentVolumeInfo; - - // Cache the curve info as well - // !!! Only call GetCurveInfo if the PartType is Curve - // !!! Closed curves are actually Meshes, and calling GetCurveInfo on a Mesh will crash HAPI! - FHoudiniCurveInfo CurrentCurveInfo; - if (CurrentPartType == EHoudiniPartType::Curve && CurrentPartInfo.Type == EHoudiniPartType::Curve) - { - HAPI_CurveInfo CurrentHapiCurveInfo; - FHoudiniApi::CurveInfo_Init(&CurrentHapiCurveInfo); - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, - &CurrentHapiCurveInfo)) - { - // Cache/Convert this part's curve info - CacheCurveInfo(CurrentHapiCurveInfo, CurrentCurveInfo); - } - } - currentHGPO.CurveInfo = CurrentCurveInfo; - - - // TODO: - // DONE? bake folders are handled out of this loop? - // See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute - //TArray BakeFolderOverrides; - - // See if we have an existing output that matches this HGPO or if we need to create a new one - bool IsFoundOutputValid = false; - UHoudiniOutput ** FoundHoudiniOutput = nullptr; - // We handle volumes differently than other outputs types, as a single HF output has multiple HGPOs - if (currentHGPO.Type != EHoudiniPartType::Volume) - { - // Look in the previous output if we have a match - FoundHoudiniOutput = InOldOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(currentHGPO) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - - } - else - { - // Look in the previous outputs if we have a match - FoundHoudiniOutput = InOldOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, true) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - - // If we dont have a match in the old maps, also look in the newly created outputs - if (!IsFoundOutputValid) - { - FoundHoudiniOutput = OutNewOutputs.FindByPredicate( - [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, false) : false; }); - - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; - } - } - - UHoudiniOutput * HoudiniOutput = nullptr; - if (IsFoundOutputValid) - { - // We can reuse the existing output - HoudiniOutput = *FoundHoudiniOutput; - HoudiniOutput->SetIsUpdating(true); - // Transfer this output from the old array to the new one - InOldOutputs.Remove(HoudiniOutput); - } - else - { - // We couldn't find a valid output object, so create a new one - - // If the current part is a volume, only create a new output object - // if the volume's name is "height", if not store the HGPO aside - if (currentHGPO.Type == EHoudiniPartType::Volume - && !currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) - { - UnassignedVolumeParts.Add(currentHGPO); - continue; - } - - // Create a new output object - //FString OutputName = TEXT("Output") + FString::FromInt(OutputIdx++); - HoudiniOutput = NewObject( - InOuterObject, - UHoudiniOutput::StaticClass(), - NAME_None,//FName(*OutputName), - RF_NoFlags); - - // Make sure the created object is valid - if (!HoudiniOutput || HoudiniOutput->IsPendingKill()) - { - //HOUDINI_LOG_WARNING("Failed to create asset output"); - continue; - } - - // Mark if the HoudiniOutput is editable - HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); - } - - // Add the HGPO to the output - HoudiniOutput->AddNewHGPO(currentHGPO); - // Add this output object to the new ouput array - OutNewOutputs.AddUnique(HoudiniOutput); - } - // END: for Part - - if (GeoMeshSockets.Num() > 0) - { - // If we have any mesh socket, assign them to the HGPO for this geo - for (auto& CurNewOutput : OutNewOutputs) - { - if (!IsValid(CurNewOutput)) - continue; - - int32 FirstValidIdx = CurNewOutput->StaleCount; - if (!CurNewOutput->HoudiniGeoPartObjects.IsValidIndex(FirstValidIdx)) - FirstValidIdx = 0; - - for (int32 Idx = FirstValidIdx; Idx < CurNewOutput->HoudiniGeoPartObjects.Num(); Idx++) - { - // Only add sockets to valid/non stale HGPOs - FHoudiniGeoPartObject& CurHGPO = CurNewOutput->HoudiniGeoPartObjects[Idx]; - if (CurHGPO.ObjectId != CurrentHapiObjectInfo.nodeId) - continue; - - if (CurHGPO.GeoId != CurrentHapiGeoInfo.nodeId) - continue; - - CurHGPO.AllMeshSockets.Append(GeoMeshSockets); - } - } - } - } - // END: for GEO - } - // END: for OBJ - - // Update the output/HGPO associations from the map - // Clear the old HGPO since we don't need them anymore - for (auto& CurrentOuput : OutNewOutputs) - { - if (!IsValid(CurrentOuput)) - continue; - - CurrentOuput->DeleteAllStaleHGPOs(); - } - - // If we have unassigned volumes, - // try to find their corresponding output - if (UnassignedVolumeParts.Num() > 0) - { - for (auto& currentVolumeHGPO : UnassignedVolumeParts) - { - UHoudiniOutput ** FoundHoudiniOutput = OutNewOutputs.FindByPredicate( - [currentVolumeHGPO](UHoudiniOutput* Output) - { - return Output ? Output->HeightfieldMatch(currentVolumeHGPO, false) : false; - }); - - if (!FoundHoudiniOutput || !(*FoundHoudiniOutput) || (*FoundHoudiniOutput)->IsPendingKill()) - { - // Skip - consider this volume as invalid - continue; - } - - // Add this HGPO to the output - (*FoundHoudiniOutput)->AddNewHGPO(currentVolumeHGPO); - } - } - - // All our output objects now have their HGPO assigned - // We can now parse them to update the output type - for (auto& Output : OutNewOutputs) - { - Output->UpdateOutputType(); - } - - return true; -} - -bool -FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - TArray& Outputs = HAC->Outputs; - - // Iterate through the outputs array of HAC. - for (int32 Index = 0; Index < HAC->GetNumOutputs(); ++Index) - { - UHoudiniOutput* CurrentOutput = HAC->GetOutputAt(Index); - if (!CurrentOutput) - continue; - - if (!HAC->IsOutputTypeSupported(CurrentOutput->GetType())) - continue; - - switch (CurrentOutput->GetType()) - { - case EHoudiniOutputType::Instancer: - { - bool bNeedToRecreateInstancers = false; - for (auto& Iter : CurrentOutput->GetInstancedOutputs()) - { - FHoudiniInstancedOutput& InstOutput = Iter.Value; - if (!InstOutput.bChanged) - continue; - - /* - FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( - InstOutput, Iter.Key, CurrentOutput, HAC); - */ - - // TODO: - // UpdateChangedInstancedOutput needs some improvements - // as it currently destroy too many components. - // For now, we'll update all the instancers - bNeedToRecreateInstancers = true; - - InstOutput.MarkChanged(false); - } - - if (bNeedToRecreateInstancers) - { - if (HAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation || HAC->HasBeenLoaded()) - { - // Instantiate the HDA if it's not been - // This is because CreateAllInstancersFromHoudiniOutput() actually reads the transform from HAPI - // Calling it on a HDA not yet instantiated causes a crash... - HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); - } - else - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurrentOutput, Outputs, HAC); - } - } - } - break; - - case EHoudiniOutputType::Curve: - { - //FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); - } - break; - - default: - break; - } - } - - return true; -} - -void -FHoudiniOutputTranslator::CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache) -{ - FHoudiniEngineString hapiSTR(InObjInfo.nameSH); - hapiSTR.ToFString(OutObjInfoCache.Name); - //OutObjInfoCache.Name = InObjInfo.nameSH; - - OutObjInfoCache.NodeId = InObjInfo.nodeId; - OutObjInfoCache.ObjectToInstanceID = InObjInfo.objectToInstanceId; - - OutObjInfoCache.bHasTransformChanged = InObjInfo.hasTransformChanged; - OutObjInfoCache.bHaveGeosChanged = InObjInfo.haveGeosChanged; - - OutObjInfoCache.bIsVisible = InObjInfo.isVisible; - OutObjInfoCache.bIsInstancer = InObjInfo.isInstancer; - OutObjInfoCache.bIsInstanced = InObjInfo.isInstanced; - - OutObjInfoCache.GeoCount = InObjInfo.geoCount; -}; - -EHoudiniGeoType -FHoudiniOutputTranslator::ConvertHapiGeoType(const HAPI_GeoType& InType) -{ - EHoudiniGeoType OutType = EHoudiniGeoType::Invalid; - switch (InType) - { - case HAPI_GEOTYPE_DEFAULT: - OutType = EHoudiniGeoType::Default; - break; - - case HAPI_GEOTYPE_INTERMEDIATE: - OutType = EHoudiniGeoType::Intermediate; - break; - - case HAPI_GEOTYPE_INPUT: - OutType = EHoudiniGeoType::Input; - break; - - case HAPI_GEOTYPE_CURVE: - OutType = EHoudiniGeoType::Curve; - break; - - default: - OutType = EHoudiniGeoType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache) -{ - OutGeoInfoCache.Type = ConvertHapiGeoType(InGeoInfo.type); - - FHoudiniEngineString hapiSTR(InGeoInfo.nameSH); - hapiSTR.ToFString(OutGeoInfoCache.Name); - - OutGeoInfoCache.NodeId = InGeoInfo.nodeId; - - OutGeoInfoCache.bIsEditable = InGeoInfo.isEditable; - OutGeoInfoCache.bIsTemplated = InGeoInfo.isTemplated; - OutGeoInfoCache.bIsDisplayGeo = InGeoInfo.isDisplayGeo; - OutGeoInfoCache.bHasGeoChanged = InGeoInfo.hasGeoChanged; - OutGeoInfoCache.bHasMaterialChanged = InGeoInfo.hasMaterialChanged; - - OutGeoInfoCache.PartCount = InGeoInfo.partCount; - OutGeoInfoCache.PointGroupCount = InGeoInfo.pointGroupCount; - OutGeoInfoCache.PrimitiveGroupCount = InGeoInfo.primitiveGroupCount; -}; - - -EHoudiniPartType -FHoudiniOutputTranslator::ConvertHapiPartType(const HAPI_PartType& InType) -{ - EHoudiniPartType OutType = EHoudiniPartType::Invalid; - switch (InType) - { - case HAPI_PARTTYPE_BOX: - case HAPI_PARTTYPE_SPHERE: - case HAPI_PARTTYPE_MESH: - OutType = EHoudiniPartType::Mesh; - break; - - case HAPI_PARTTYPE_CURVE: - OutType = EHoudiniPartType::Curve; - break; - - case HAPI_PARTTYPE_INSTANCER: - OutType = EHoudiniPartType::Instancer; - break; - - case HAPI_PARTTYPE_VOLUME: - OutType = EHoudiniPartType::Volume; - break; - - default: - OutType = EHoudiniPartType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache) -{ - OutPartInfoCache.PartId = InPartInfo.id; - - FHoudiniEngineString hapiSTR(InPartInfo.nameSH); - hapiSTR.ToFString(OutPartInfoCache.Name); - - OutPartInfoCache.Type = ConvertHapiPartType(InPartInfo.type); - - OutPartInfoCache.FaceCount = InPartInfo.faceCount; - OutPartInfoCache.VertexCount = InPartInfo.vertexCount; - OutPartInfoCache.PointCount = InPartInfo.pointCount; - - OutPartInfoCache.PointAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_POINT]; - OutPartInfoCache.VertexAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX]; - OutPartInfoCache.PrimitiveAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_PRIM]; - OutPartInfoCache.DetailAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL]; - - OutPartInfoCache.bIsInstanced = InPartInfo.isInstanced; - - OutPartInfoCache.InstancedPartCount = InPartInfo.instancedPartCount; - OutPartInfoCache.InstanceCount = InPartInfo.instanceCount; - - OutPartInfoCache.bHasChanged = InPartInfo.hasChanged; -}; - -void -FHoudiniOutputTranslator::CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache) -{ - FHoudiniEngineString hapiSTR(InVolumeInfo.nameSH); - hapiSTR.ToFString(OutVolumeInfoCache.Name); - - OutVolumeInfoCache.bIsVDB = (InVolumeInfo.type == HAPI_VOLUMETYPE_VDB); // replaces VolumeType Type; - - OutVolumeInfoCache.TupleSize = InVolumeInfo.tupleSize; - OutVolumeInfoCache.bIsFloat = (InVolumeInfo.storage == HAPI_STORAGETYPE_FLOAT); // replaces StorageType StorageType; - OutVolumeInfoCache.TileSize = InVolumeInfo.tileSize; - - FHoudiniEngineUtils::TranslateHapiTransform(InVolumeInfo.transform, OutVolumeInfoCache.Transform); - OutVolumeInfoCache.bHasTaper = InVolumeInfo.hasTaper; - - OutVolumeInfoCache.XLength = InVolumeInfo.xLength; - OutVolumeInfoCache.YLength = InVolumeInfo.yLength; - OutVolumeInfoCache.ZLength = InVolumeInfo.zLength; - - OutVolumeInfoCache.MinX = InVolumeInfo.minX; - OutVolumeInfoCache.MinY = InVolumeInfo.minY; - OutVolumeInfoCache.MinZ = InVolumeInfo.minZ; - - OutVolumeInfoCache.XTaper = InVolumeInfo.xTaper; - OutVolumeInfoCache.YTaper = InVolumeInfo.yTaper; -}; - -EHoudiniCurveType -FHoudiniOutputTranslator::ConvertHapiCurveType(const HAPI_CurveType& InType) -{ - EHoudiniCurveType OutType = EHoudiniCurveType::Invalid; - switch (InType) - { - case HAPI_CURVETYPE_LINEAR: - OutType = EHoudiniCurveType::Polygon; - break; - - case HAPI_CURVETYPE_NURBS: - OutType = EHoudiniCurveType::Nurbs; - break; - - case HAPI_CURVETYPE_BEZIER: - OutType = EHoudiniCurveType::Bezier; - break; - - case HAPI_CURVETYPE_MAX: - OutType = EHoudiniCurveType::Points; - break; - - default: - OutType = EHoudiniCurveType::Invalid; - break; - } - - return OutType; -} - -void -FHoudiniOutputTranslator::CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache) -{ - OutCurveInfoCache.Type = ConvertHapiCurveType(InCurveInfo.curveType); - - OutCurveInfoCache.CurveCount = InCurveInfo.curveCount; - OutCurveInfoCache.VertexCount = InCurveInfo.vertexCount; - OutCurveInfoCache.KnotCount = InCurveInfo.knotCount; - - OutCurveInfoCache.bIsPeriodic = InCurveInfo.isPeriodic; - OutCurveInfoCache.bIsRational = InCurveInfo.isRational; - - OutCurveInfoCache.Order = InCurveInfo.order; - - OutCurveInfoCache.bHasKnots = InCurveInfo.hasKnots; -}; - - -void -FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll) -{ - if (!IsValid(InHAC)) - return; - - // DO NOT MANUALLY DESTROY THE OLD/DANGLING OUTPUTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - // Simply clearing the array is enough - for (auto& OldOutput : InHAC->Outputs) - { - if (OldOutput->ShouldDeferClear() && !bForceClearAll) - { - OutputsPendingClear.Add(OldOutput); - } - else - { - ClearOutput(OldOutput); - } - } - - InHAC->Outputs.Empty(); -} - -void -FHoudiniOutputTranslator::ClearOutput(UHoudiniOutput* Output) -{ - switch (Output->GetType()) - { - case EHoudiniOutputType::Landscape: - { - for (auto& OutputObject : Output->GetOutputObjects()) - { - // Currently, any Landscape managed by an HDA is always present in the current level. - // Only when it gets baked will Landscapes be serialized to other levels so for now - // assume that a landscape should be available, unless explicitly deleted the user. - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.Value.OutputObject); - if (!LandscapePtr) - continue; - - ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); - - if (!IsValid(Landscape) || !Landscape->IsValidLowLevel()) - continue; - - Landscape->UnregisterAllComponents(); - Landscape->Destroy(); - - // if (Output->IsLandscapeWorldComposition()) - // { - // TSoftObjectPtr LandscapeSoftPtr = LandscapePtr->GetSoftPtr(); - // - // FString SoftPtrPath = LandscapeSoftPtr.ToSoftObjectPath().ToString(); - // - // FString FileName = FPaths::GetBaseFilename(SoftPtrPath); - // FString FileDirectory = FPaths::GetPath(SoftPtrPath); - // - // FString ContentPath = FPaths::ProjectContentDir(); - // FString ContentFullPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*ContentPath); - // - // FString AbsoluteFilePath = ContentFullPath + FileDirectory.Mid(5, FileDirectory.Len() - 5) + "/" + FPaths::GetBaseFilename(FileName) + ".umap"; - // - // FPlatformFileManager::Get().GetPlatformFile().FileExists(*(AbsoluteFilePath)); - // - // FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*(AbsoluteFilePath)); - // } - // else - // { - - // } - } - } - break; - - case EHoudiniOutputType::Instancer: - { - for (auto& OutputObject : Output->GetOutputObjects()) - { - // Is this a foliage instancer? Check if the component is owned by an AInstancedFoliageActor - UActorComponent* const Component = Cast(OutputObject.Value.OutputComponent); - if (!IsValid(Component)) - continue; - AActor* const OwnerActor = Component->GetOwner(); - if (!IsValid(OwnerActor) || !OwnerActor->IsA()) - continue; - - UHierarchicalInstancedStaticMeshComponent* const FoliageHISMC = Cast(Component); - if (IsValid(FoliageHISMC)) - { - // Find the parent component: the output is typically owned by an HAC. - USceneComponent* ParentComponent = nullptr; - UObject* const OutputOuter = Output->GetOuter(); - if (IsValid(OutputOuter)) - { - if (OutputOuter->IsA()) - { - ParentComponent = Cast(OutputOuter); - } - // other possibilities? - } - - // fallback to trying the owner of the HISMC - if (!ParentComponent) - { - ParentComponent = Cast(FoliageHISMC); - } - - if (IsValid(ParentComponent)) - { - FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, OutputObject.Value.OutputObject, ParentComponent); - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - } - } - } - } - break; - // ... Other output types ...// - - default: - break; - - } - - Output->Clear(); -} - - -bool -FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName) -{ - HAPI_AttributeInfo CustomPartNameInfo; - FHoudiniApi::AttributeInfo_Init(&CustomPartNameInfo); - - bool bHasCustomName = false; - TArray CustomNames; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, CustomPartNameInfo, CustomNames)) - { - // Look for the v2 attribute (unreal_output_name) - bHasCustomName = true; - } - else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, CustomPartNameInfo, CustomNames)) - { - // If we couldnt find the new attribute, use the legacy v1 attribute (unreal_generated_mesh_name) - bHasCustomName = true; - } - - if (!bHasCustomName) - return false; - - if (CustomNames.Num() <= 0) - return false; - - OutCustomPartName = CustomNames[0]; - - if (OutCustomPartName.IsEmpty()) - return false; - - return true; -} - -void -FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return; - - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) - return; - - FString TempFolderOverride = FString(); - - HAPI_AttributeInfo TempFolderAttriInfo; - FHoudiniApi::AttributeInfo_Init(&TempFolderAttriInfo); - TArray StringData; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_DETAIL)) - { - TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - else - { - if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) - { - TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); - } - } - - if (TempFolderOverride.StartsWith("Game/")) - { - TempFolderOverride = "/" + TempFolderOverride; - } - - FString AbsoluteOverridePath; - if (TempFolderOverride.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + TempFolderOverride.Mid(6, TempFolderOverride.Len() - 6); - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - if (!TempFolderOverride.IsEmpty()) - AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*TempFolderOverride); - } - - // Check Validity of the path - if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) - { - // Only display a warning if the path is invalid, empty is fine - if(!AbsoluteOverridePath.IsEmpty()) - HOUDINI_LOG_WARNING(TEXT("Invalid override temporary cook path: %s"), *TempFolderOverride); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - TempFolderOverride = HoudiniRuntimeSettings->DefaultTemporaryCookFolder; - } - - // If the TempCookFolder of the HAC is non-empty and is different from the override path. - // do not override it if the current temp cook path is valid. (it was user specified) - if (!HAC->TemporaryCookFolder.Path.IsEmpty() && !HAC->TemporaryCookFolder.Path.Equals(TempFolderOverride)) - return; - - HAC->TemporaryCookFolder.Path = TempFolderOverride; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniOutputTranslator.h" + +#include "HoudiniOutput.h" +#include "HoudiniApi.h" +#include "HoudiniEngine.h" + +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniInput.h" +#include "HoudiniStaticMesh.h" + +#include "HoudiniMeshTranslator.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" + +#include "Editor.h" +#include "EditorSupportDelegates.h" +#include "FileHelpers.h" +#include "LandscapeInfo.h" +#include "HAL/PlatformFilemanager.h" +#include "HAL/FileManager.h" +#include "Engine/WorldComposition.h" +#include "Modules/ModuleManager.h" +#include "WorldBrowserModule.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// +bool +FHoudiniOutputTranslator::UpdateOutputs( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate, + bool& bOutHasHoudiniStaticMeshOutput) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Get the temp folder override + FHoudiniOutputTranslator::GetTempFolderFromAttribute(HAC); + + // Outputs that should be cleared, but only AFTER new output processing have taken place. + // This is needed for landscape resizing where the new landscape needs to copy data from the original landscape + // before the original landscape gets destroyed. + TArray DeferredClearOutputs; + + // Check if the HDA has been marked as not producing outputs + if (!HAC->bOutputless) + { + // Check if we want to convert legacy v1 data + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility && HAC->Version1CompatibilityHAC) + { + // Do not reuse legacy outputs! + for (auto& OldOutput : HAC->Outputs) + { + ClearOutput(OldOutput); + } + } + + TArray NewOutputs; + if (FHoudiniOutputTranslator::BuildAllOutputs( + HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->NodeIdsToCook, HAC->bOutputTemplateGeos, HAC->bUseOutputNodes)) + { + // NOTE: For now we are currently forcing all outputs to be cleared here. There is still an issue where, in some + // circumstances, landscape tiles disappear when clearing outputs after processing. + // The reason we may need to defer landscape clearing is to allow the landscape creation code to + // capture the extent of the landscape. The extent of the landscape can only be calculated if all landscape + // tiles are still present in the map. If we find that we don't need this for updating of Input landscapes, + // we can safely remove this feature. + ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); + // Replace with the new parameters + HAC->Outputs = NewOutputs; + } + } + else + { + // This HDA is marked as not supposed to produce any output + ClearAndRemoveOutputs(HAC, DeferredClearOutputs, true); + } + + // Look for details generic property attributes on the outputs, + // and try to apply them to the HAC. + // This can be used to preset some of the HDA's uproperty via attribute + TArray GenericAttributes; + for (auto& CurrentOutput : HAC->Outputs) + { + const TArray& CurrentOutputHGPO = CurrentOutput->GetHoudiniGeoPartObjects(); + for (auto& CurrentHGPO : CurrentOutputHGPO) + { + FHoudiniEngineUtils::GetGenericAttributeList( + CurrentHGPO.GeoId, + CurrentHGPO.PartId, + HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX, + GenericAttributes, + HAPI_ATTROWNER_DETAIL); + } + } + + // Attempt to apply the attributes to the HAC if we have any + for (const auto& CurrentPropAttribute : GenericAttributes) + { + // Get the current Property Attribute + const FString& CurrentPropertyName = CurrentPropAttribute.AttributeName; + if (CurrentPropertyName.IsEmpty()) + continue; + + if (!FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(HAC, CurrentPropAttribute)) + continue; + + // Success! + HOUDINI_LOG_MESSAGE(TEXT("Modified UProperty %s on Houdini Asset Component named %s"), *CurrentPropertyName, *HAC->GetName()); + } + + // NOTE: PersistentWorld can be NULL when, for example, working with + // HoudiniAssetComponents in Blueprints. + UWorld* PersistentWorld = HAC->GetWorld(); + UWorldComposition* WorldComposition = nullptr; + if (PersistentWorld) + { + WorldComposition = PersistentWorld->WorldComposition; + } + + if (IsValid(WorldComposition)) + { + // We don't want the origin to shift as we're potentially updating levels. + WorldComposition->bTemporarilyDisableOriginTracking = true; + } + + // "Process" the mesh. + // TODO: Move this to the actual processing stage, + // And see if some of this could be threaded + UObject* OuterComponent = HAC; + + FString HoudiniAssetPath = FPaths::GetPath(HAC->GetPathName()); + FString ComponentGUIDString = HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); + FString HoudiniAssetNameString = HAC->GetDisplayName(); + + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + PackageParams.ObjectName = FString(); + + // ---------------------------------------------------- + // Outputs prepass + // ---------------------------------------------------- + + TArray CreatedWorldCompositionPackages; + bool bCreatedNewMaps = false; + //... for heightfield outputs ...// + + // Collect all the landscape layers' global min/max values. + TMap LandscapeLayerGlobalMinimums; + TMap LandscapeLayerGlobalMaximums; + + // Store the instancer outputs separately so we can process them later, after all mesh output are processed. + // Determine the total number of instances, if we have more than 1 then mesh parts with instanced geo we will not create proxy meshes + // Also if we have object instancer (or oldschool attribute instancers), we won't be creating any proxy at all + TArray InstancerOutputs; + int32 NumInstances = 0; + bool bHasObjectInstancer = false; + + for (auto& CurOutput : HAC->Outputs) + { + if (CurOutput->GetType() == EHoudiniOutputType::Instancer) + { + // InstancerOutputs.Add(CurOutput); + for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.Type == EHoudiniPartType::Instancer) + { + if (HGPO.InstancerType == EHoudiniInstancerType::PackedPrimitive) + { + NumInstances += HGPO.PartInfo.InstanceCount; + } + else + { + NumInstances += HGPO.PartInfo.PointCount; + } + + if ((HGPO.InstancerType == EHoudiniInstancerType::ObjectInstancer) + || (HGPO.InstancerType == EHoudiniInstancerType::OldSchoolAttributeInstancer)) + { + bHasObjectInstancer = true; + } + } + } + } + else if (CurOutput->GetType() == EHoudiniOutputType::Landscape) + { + FHoudiniLandscapeTranslator::CalcHeightfieldsArrayGlobalZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums, false); + } + } + + bOutHasHoudiniStaticMeshOutput = false; + int32 NumVisibleOutputs = 0; + int32 NumOutputs = HAC->Outputs.Num(); + bool bHasLandscape = false; + + // Before processing all the outputs, + // See if we have any landscape input that have "Update Input Landscape" enabled + // And make an array of all our input landscapes as well. + TArray AllInputLandscapes; + TArray InputLandscapesToUpdate; + + FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); + + // ---------------------------------------------------- + // Process outputs + // ---------------------------------------------------- + // Landscape creation will cache the first tile as a reference location + // in this struct to be used by during construction of subsequent tiles. + FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; + // Landscape Size info will be cached by the first tile, similar to LandscapeReferenceLocation + FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; + FHoudiniLandscapeExtent LandscapeExtent; + TSet ClearedLandscapeLayers; + + // The houdini materials that have been generated by this HDA. + // We track them to prevent recreate the same houdini material over and over if it is assigned to multiple parts. + // (this can easily happen when using packed prims) + TMap AllOutputMaterials; + + TArray CreatedPackages; + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) + { + UHoudiniOutput* CurOutput = HAC->GetOutputAt(OutputIdx); + if (!CurOutput || CurOutput->IsPendingKill()) + continue; + + FString Notification = FString::Format(TEXT("Processing output {0} / {1}..."), {FString::FromInt(OutputIdx + 1), FString::FromInt(NumOutputs)}); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + if (!HAC->IsOutputTypeSupported(CurOutput->GetType())) + continue; + + switch (CurOutput->GetType()) + { + case EHoudiniOutputType::Mesh: + { + bool bIsProxyStaticMeshEnabled = ( + HAC->IsProxyStaticMeshEnabled() && + !HAC->HasNoProxyMeshNextCookBeenRequested() && + !HAC->IsBakeAfterNextCookEnabled()); + if (bIsProxyStaticMeshEnabled && NumInstances > 1) + { + if (bHasObjectInstancer) + { + // Completely disable proxies if we have object instancers/old school attribute instancers + // as they rely on having a static mesh created (and the instanced mesh HGPO is not marked as instanced...) + bIsProxyStaticMeshEnabled = false; + } + else + { + // If we dont have proxy instancer, enable proxy only for non-instanced mesh + for (const FHoudiniGeoPartObject &HGPO : CurOutput->GetHoudiniGeoPartObjects()) + { + if (HGPO.bIsInstanced && HGPO.Type == EHoudiniPartType::Mesh) + { + bIsProxyStaticMeshEnabled = false; + break; + } + } + } + } + + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + PackageParams, + bIsProxyStaticMeshEnabled ? EHoudiniStaticMeshMethod::UHoudiniStaticMesh : HAC->StaticMeshMethod, + HAC->StaticMeshGenerationProperties, + HAC->StaticMeshBuildSettings, + AllOutputMaterials, + OuterComponent); + + NumVisibleOutputs++; + + // Look for UHoudiniStaticMesh in the output, and set bOutHasHoudiniStaticMeshOutput accordingly + if (bIsProxyStaticMeshEnabled && !bOutHasHoudiniStaticMeshOutput) + { + bOutHasHoudiniStaticMeshOutput &= CurOutput->HasAnyCurrentProxy(); + } + break; + } + + case EHoudiniOutputType::Curve: + { + const TArray &GeoPartObjects = CurOutput->GetHoudiniGeoPartObjects(); + + if (GeoPartObjects.Num() <= 0) + continue; + + const FHoudiniGeoPartObject & CurHGPO = GeoPartObjects[0]; + + if (CurOutput->IsEditableNode()) + { + if (!CurOutput->HasEditableNodeBuilt()) + { + // Editable curve, only need to be built once. + UHoudiniSplineComponent* HoudiniSplineComponent = FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode( + CurHGPO.GeoId, + CurHGPO.PartName, + HAC); + + HoudiniSplineComponent->SetIsEditableOutputCurve(true); + + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; + EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; + EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; + EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; + + TMap& OutputObjects = CurOutput->GetOutputObjects(); + FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + FoundOutputObject.OutputComponent = HoudiniSplineComponent; + + CurOutput->SetHasEditableNodeBuilt(true); + } + } + else + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, OuterComponent); + NumVisibleOutputs += CurOutput->GetOutputObjects().Num(); + break; + } + } + break; + + case EHoudiniOutputType::Instancer: + InstancerOutputs.Add(CurOutput); + break; + + case EHoudiniOutputType::Landscape: + { + NumVisibleOutputs++; + + // This gets called for each heightfield primitive from Houdini, i.e., each "tile". + bool bNewMapCreated = false; + // Registering of untracked actors is not currently used in the HDA + // workflow. HDA cleanup will manually search for shared landscapes + // and remove them. That aforementioned behaviour should really be updated to + // make use of untracked actors on the HAC (similar to PDG Asset Link). + TArray> UntrackedActors; + + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + UntrackedActors, + InputLandscapesToUpdate, + AllInputLandscapes, + HAC, + TEXT("{hda_actor_name}_"), + PersistentWorld, + LandscapeLayerGlobalMinimums, + LandscapeLayerGlobalMaximums, + LandscapeExtent, + LandscapeSizeInfo, + LandscapeReferenceLocation, + PackageParams, + ClearedLandscapeLayers, + CreatedPackages); + + bHasLandscape = true; + + // Attach the created landscape to the parent HAC. + ALandscapeProxy* OutputLandscape = nullptr; + for (auto& Pair : CurOutput->GetOutputObjects()) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(Pair.Value.OutputObject); + if (IsValid(LandscapePtr)) + { + OutputLandscape = LandscapePtr->GetRawPtr(); + } + break; + } + + if (OutputLandscape) + { + // Attach the created landscapes to HAC + // Output Transforms are always relative to the HDA + HAC->SetMobility(EComponentMobility::Static); + OutputLandscape->AttachToComponent(HAC, FAttachmentTransformRules::KeepRelativeTransform); + // Note that the above attach will cause the collision components to crap out. This manifests + // itself via the Landscape editor tools not being able to trace Landscape collision components. + // By recreating collision components here, it appears to put things back into working order. + OutputLandscape->GetLandscapeInfo()->FixupProxiesTransform(); + OutputLandscape->GetLandscapeInfo()->RecreateLandscapeInfo(PersistentWorld, true); + OutputLandscape->RecreateCollisionComponents(); + FEditorDelegates::PostLandscapeLayerUpdated.Broadcast(); + } + + bCreatedNewMaps |= bNewMapCreated; + break; + } + default: + // Do Nothing for now + break; + } + + for (auto& CurMat : CurOutput->AssignementMaterials) + { + // Add the newly generated materials if any + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } + } + + // Now that all meshes have been created, process the instancers + for (auto& CurOutput : InstancerOutputs) + { + if (!FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent, PackageParams)) + continue; + + NumVisibleOutputs++; + } + + if (NumVisibleOutputs > 0) + { + // If we have valid outputs, we don't need to display the houdini logo anymore... + FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(HAC); + } + else + { + // ... if we don't have any valid outputs however, we should + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HAC); + } + + // Clear any old outputs that was marked as "Should Defer Clear". + // This should happen before SharedLandscapeActor cleanup + // since this needs to remove old landscape proxies so that empty SharedLandscapeActors + // can be removed afterward. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniOutputTranslator::UpdateOutputs] Clearing old outputs: %d"), DeferredClearOutputs.Num()); + for(UHoudiniOutput* OldOutput : DeferredClearOutputs) + { + ClearOutput(OldOutput); + } + + // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) + // { + // LandscapeExtents.IntermediateResizeLandscape->Destroy(); + // LandscapeExtents.IntermediateResizeLandscape = nullptr; + // } + + if (bHasLandscape) + { + // ---------------------------------------------------- + // Cleanup untracked shared landscape actors + // ---------------------------------------------------- + // This is a nasty hack to clean up SharedLandscape actors generated by the + // Landscape translator but aren't tracked by an HoudiniOutputObject, since the + // translators can't dynamically create outputs. + + { + // First collect all the landscapes that is being tracked by the HAC. + TSet TrackedLandscapes; + for(UHoudiniOutput* Output : HAC->Outputs) + { + if (Output->GetType() == EHoudiniOutputType::Landscape) + { + for(auto& Elem : Output->GetOutputObjects()) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(Elem.Value.OutputObject); + if (!IsValid(LandscapePtr)) + continue; + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapeProxy)) + { + TrackedLandscapes.Add(LandscapeProxy); + + // We need to recreate component states for landscapes if a tile was created, moved, or resized + // otherwise the landscape will exhibit render artifacts (such as only rendering every other + // component.) + LandscapeProxy->RecreateComponentsState(); + } + } + } + } + + // Iterate over Houdini asset child assets in order to find dangling Landscape actors + TArray AttachedComponents = HAC->GetAttachChildren(); + for(USceneComponent* Component : AttachedComponents) + { + if (!IsValid(Component)) + continue; + AActor* Actor = Component->GetOwner(); + ALandscape* Landscape = Cast(Actor); + if (!Landscape) + continue; + if (TrackedLandscapes.Contains(Landscape)) + continue; + + if (Landscape->GetLandscapeInfo()->Proxies.Num() == 0) + Landscape->Destroy(); + } + } + + // Recreate Landscape Info calls WorldChange, so no need to do it manually. + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + +#if WITH_EDITOR + if (GEditor) + { + // We force a viewport refresh since some actions, such as updating landscape + // edit layers will not reflect until the user moves the viewport camera. + GEditor->RedrawLevelEditingViewports(true); + } +#endif + } + + // Destroy the intermediate resize landscape, if there is one. + // if (IsValid(LandscapeExtents.IntermediateResizeLandscape)) + // { + // FHoudiniLandscapeTranslator::DestroyLandscape(LandscapeExtents.IntermediateResizeLandscape); + // } + + if (IsValid(WorldComposition)) + { + // Disable the flag that we set before starting the import process. + WorldComposition->bTemporarilyDisableOriginTracking = false; + } + + // If the owner component was marked as loaded, unmark all outputs + if (HAC->HasBeenLoaded()) + { + for (auto& CurrentOutput : HAC->Outputs) + { + CurrentOutput->MarkAsLoaded(false); + } + } + + if (bCreatedNewMaps) + { + // Force the asset registry to update its cache of packages paths + // recursively for this world, otherwise world composition won't + // pick them up during the WorldComposition::Rescan(). + FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); + + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + + FHoudiniEngineUtils::LogWorldInfo(PersistentWorld); + if (WorldComposition) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); + } + + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); + } + + if (CreatedPackages.Num() > 0) + { + // Save created packages. For example, we don't want landscape layers deleted + // along with the HDA. + FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); + } + + return true; +} + +bool +FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + UObject* OuterComponent = HAC; + + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + PackageParams.ObjectName = FString(); + + // Keep track of all generated houdini materials to avoid recreating them over and over + TMap AllOutputMaterials; + + bool bFoundProxies = false; + TArray InstancerOutputs; + for (auto& CurOutput : HAC->Outputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType == EHoudiniOutputType::Mesh) + { + if (CurOutput->HasAnyCurrentProxy()) + { + bFoundProxies = true; + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + PackageParams, + HAC->StaticMeshMethod != EHoudiniStaticMeshMethod::UHoudiniStaticMesh ? HAC->StaticMeshMethod : EHoudiniStaticMeshMethod::RawMesh, + HAC->StaticMeshGenerationProperties, + HAC->StaticMeshBuildSettings, + AllOutputMaterials, + OuterComponent, + true, // bInTreatExistingMaterialsAsUpToDate + bInDestroyProxies + ); + } + } + else if (OutputType == EHoudiniOutputType::Instancer) + { + InstancerOutputs.Add(CurOutput); + } + + for (auto& CurMat : CurOutput->AssignementMaterials) + { + //Adds the generated materials if any + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } + } + + // Rebuild instancers if we built any static meshes from proxies + if (bFoundProxies) + { + for (auto& CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurOutput, HAC->Outputs, OuterComponent, PackageParams); + } + } + + return true; +} + +// +bool +FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) +{ + HAPI_NodeId & AssetId = HAC->AssetId; + // Get the AssetInfo + 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 ObjectInfos; + TArray ObjectTransforms; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) + return false; + + TArray EditableCurveObjIds; + TArray EditableCurveGeoIds; + TArray EditableCurvePartIds; + TArray EditableCurvePartNames; + + // Iterate through all objects to get all editable curve's object geo and part Ids. + + for (int32 ObjectId = 0; ObjectId < ObjectInfos.Num(); ++ObjectId) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectId]; + + // Cache/convert them + FHoudiniObjectInfo CurrentObjectInfo; + CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); + + // Start by getting the number of editable nodes + int32 EditableNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + + if (EditableNodeCount > 0) + { + TArray< HAPI_NodeId > EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GeoType::HAPI_GEOTYPE_CURVE) + continue; + + // Check if the curve is closed (-1 unknown, could not find parameter on node). A closed curve will + // be returned as a mesh by HAPI instead of a curve + int32 CurveClosed = -1; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + EditableNodeIds[nEditable], HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) + { + CurveClosed = -1; + } + else + { + if (CurveClosed) + CurveClosed = 1; + else + CurveClosed = 0; + } + + // Cook the editable node to get its parts + if (CurrentEditableGeoInfo.partCount <= 0) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentEditableGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentEditableGeoInfo.nodeId, + &CurrentEditableGeoInfo)); + } + + // Iterate on this geo's parts + for (int32 PartId = 0; PartId < CurrentEditableGeoInfo.partCount; ++PartId) + { + // Get part information. + HAPI_PartInfo CurrentHapiPartInfo; + FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentEditableGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + continue; + + // A closed curve will be returned as a mesh in HAPI + if (CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_CURVE && + (CurveClosed <= 0 || CurrentHapiPartInfo.type != HAPI_PartType::HAPI_PARTTYPE_MESH)) + continue; + + // Get the editable curve's part name + FHoudiniEngineString hapiSTR(CurrentHapiPartInfo.nameSH); + FString PartName; + hapiSTR.ToFString(PartName); + + EditableCurveObjIds.Add(CurrentHapiObjectInfo.nodeId); + EditableCurveGeoIds.Add(CurrentEditableGeoInfo.nodeId); + EditableCurvePartIds.Add(CurrentHapiPartInfo.id); + EditableCurvePartNames.Add(PartName); + } + } + } + } + + int32 Idx = 0; + for (auto& CurrentOutput : HAC->Outputs) + { + if (CurrentOutput->IsEditableNode()) + { + // The HAC is Loaded, re-assign node id to its editable curves + if (CurrentOutput->HasEditableNodeBuilt()) + { + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + for (auto& Pair : OutputObjects) + { + if (Idx >= EditableCurvePartIds.Num()) + break; + + UHoudiniSplineComponent * HoudiniSplineComponent = Cast(Pair.Value.OutputComponent); + if (HoudiniSplineComponent && !HoudiniSplineComponent->IsPendingKill()) + { + HoudiniSplineComponent->SetNodeId(EditableCurveGeoIds[Idx]); + + Pair.Key.ObjectId = EditableCurveObjIds[Idx]; + Pair.Key.GeoId = EditableCurveGeoIds[Idx]; + Pair.Key.PartId = EditableCurvePartIds[Idx]; + Pair.Key.PartName = EditableCurvePartNames[Idx]; + + Idx += 1; + } + } + } + // The HAC is a Duplication, re-construct output objects with attached duplicated editable curves, matching by part name + else + { + const TArray &Children = HAC->GetAttachChildren(); + for (auto & CurAttachedComp : Children) + { + if (!CurAttachedComp || CurAttachedComp->IsPendingKill()) + continue; + + if (!CurAttachedComp->IsA()) + continue; + + UHoudiniSplineComponent * CurAttachedSplineComp = Cast(CurAttachedComp); + if (!CurAttachedSplineComp) + continue; + + if (!CurAttachedSplineComp->IsEditableOutputCurve()) + continue; + + if (Idx >= EditableCurvePartIds.Num()) + break; + + // Found a match + if (CurAttachedSplineComp->GetGeoPartName().Equals(EditableCurvePartNames[Idx])) + { + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = EditableCurveObjIds[Idx]; + EditableSplineComponentIdentifier.GeoId = EditableCurveGeoIds[Idx]; + EditableSplineComponentIdentifier.PartId = EditableCurvePartIds[Idx]; + EditableSplineComponentIdentifier.PartName = EditableCurvePartNames[Idx]; + + CurAttachedSplineComp->SetNodeId(EditableSplineComponentIdentifier.GeoId); + + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + FHoudiniOutputObject& NewOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + NewOutputObject.OutputComponent = CurAttachedSplineComp; + + CurrentOutput->SetHasEditableNodeBuilt(true); + + // Never add additional rot/scale attributes on editable curves as this crashes HAPI + FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + CurAttachedSplineComp, false); + + Idx += 1; + break; + } + } + } + } + else + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); + } + + // Mark our outputs as loaded so they can be matched for potential reuse + // This indicates that the HGPO's ids are invalid and that HGPO should be matched using partnames instead + CurrentOutput->MarkAsLoaded(true); + } + + return true; +} + +// +bool +FHoudiniOutputTranslator::UploadChangedEditableOutput( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + TArray &Outputs = HAC->Outputs; + + // Iterate through the outputs array of HAC. + for (auto& CurrentOutput : HAC->Outputs) + { + if (!CurrentOutput) + continue; + + // Only update the editable nodes that have been built before. + if (!CurrentOutput->IsEditableNode() || !CurrentOutput->HasEditableNodeBuilt()) + continue; + + for (auto& CurrentOutputObj : CurrentOutput->GetOutputObjects()) + { + UHoudiniSplineComponent* HoudiniSplineComponent = Cast(CurrentOutputObj.Value.OutputComponent); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (!HoudiniSplineComponent->HasChanged()) + continue; + + // Dont add rot/scale on editable curves as this crashes HAPI + if (FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + HoudiniSplineComponent, false)) + HoudiniSplineComponent->MarkChanged(false); + else + HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); + } + } + + return true; +} + + +bool +FHoudiniOutputTranslator::BuildAllOutputs( + const HAPI_NodeId& AssetId, + UObject* InOuterObject, + TArray& InOldOutputs, + TArray& OutNewOutputs, + TArray& OutNodeIdsToCook, + const bool& InOutputTemplatedGeos, + const bool& InUseOutputNodes) +{ + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Get the AssetInfo + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + FString CurrentAssetName; + { + FHoudiniEngineString hapiSTR(AssetInfo.nameSH); + hapiSTR.ToFString(CurrentAssetName); + } + + // Retrieve the asset's transform. + // TODO: Unused?! + //FTransform AssetUnrealTransform; + //if (!FHoudiniEngineUtils::HapiGetAssetTransform(AssetId, AssetUnrealTransform)) + // return false; + + // Retrieve information about each object contained within our asset. + TArray ObjectInfos; + TArray ObjectTransforms; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) + return false; + + // Mark all the previous HGPOs on the outputs as stale + // This indicates that they were from a previous cook and should then be deleted + for (auto& CurOutput : InOldOutputs) + { + if (CurOutput) + CurOutput->MarkAllHGPOsAsStale(true); + } + + // For HF / Volumes, we only create new Outputs for height volume + // Store all the other volumes (masks etc) on the side and we will + // match them with theit corresponding height volume after + TArray UnassignedVolumeParts; + + // Iterate through all objects. + int32 OutputIdx = 1; + for (int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ObjectIdx++) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectIdx]; + + // Cache/convert them + FHoudiniObjectInfo CurrentObjectInfo; + CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); + + // Retrieve object name. + FString CurrentObjectName = CurrentObjectInfo.Name; + + // Get transformation for this object. + FTransform TransformMatrix = FTransform::Identity; + if (ObjectTransforms.IsValidIndex(ObjectIdx)) + { + const HAPI_Transform & ObjectTransform = ObjectTransforms[ObjectIdx]; + FHoudiniEngineUtils::TranslateHapiTransform(ObjectTransform, TransformMatrix); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: No HAPI transform for Object [%d %s] - using identity."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName); + } + + // Build an array of the geos we'll need to process + // In most case, it will only be the display geo, + // but we may also want to process editable geos as well + TArray GeoInfos; + + // Get the Display Geo's info + HAPI_GeoInfo DisplayHapiGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName); + } + else + { + // Add the display geo info to the array + GeoInfos.Add(DisplayHapiGeoInfo); + } + + // If desired, also get the output node's info + if (InUseOutputNodes) + { + int32 OutputCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetOutputGeoCount( + FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &OutputCount)) + { + OutputCount = 0; + } + + if (OutputCount > 0) + { + // Get all the output node's geo infos + TArray OutputGeoInfos; + OutputGeoInfos.SetNum(OutputCount); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetOutputGeoInfos( + FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, OutputGeoInfos.GetData(), OutputCount)) + { + OutputGeoInfos.Empty(); + } + + // Make sure all those output nodes are valid, + // ie, not inside the Display Geo + for (const auto& CurOutGeoInfo : OutputGeoInfos) + { + if (CurOutGeoInfo.nodeId == DisplayHapiGeoInfo.nodeId) + continue; + + bool bValidOutput = true; + int32 ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurOutGeoInfo.nodeId); + while (ParentId >= 0) + { + if (ParentId == CurOutGeoInfo.nodeId) + { + // This output node is inside the Display Geo + // Do not use this output to avoid duplicates + bValidOutput = false; + break; + } + + // Recurse + ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(ParentId); + } + + // If this output node is valid, add to the output Geos + if (bValidOutput) + GeoInfos.Add(CurOutGeoInfo); + } + } + } + + // Handle the editable nodes for this geo + // Start by getting the number of editable nodes + int32 EditableNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + + if (EditableNodeCount > 0) + { + TArray EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) + continue; + + // Add this geo to the geo info array + GeoInfos.Add(CurrentEditableGeoInfo); + } + } + + // Handle the templated nodes if desired + if (InOutputTemplatedGeos) + { + // Start by getting the number of templated nodes + int32 TemplatedNodeCount = 0; + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, + HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, + true, &TemplatedNodeCount)); + + if (TemplatedNodeCount > 0) + { + TArray TemplatedNodeIds; + TemplatedNodeIds.SetNumUninitialized(TemplatedNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + CurrentHapiObjectInfo.nodeId, TemplatedNodeIds.GetData(), TemplatedNodeCount)); + + for (int32 nTemplated = 0; nTemplated < TemplatedNodeCount; nTemplated++) + { + HAPI_GeoInfo CurrentTemplatedGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentTemplatedGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + TemplatedNodeIds[nTemplated], &CurrentTemplatedGeoInfo)); + + // Do not process the main display geo twice! + if (CurrentTemplatedGeoInfo.isDisplayGeo) + continue; + + // We don't want all the nested template node IDs, + // as our HDA could potentially be using other HDAs with nested template flags + // Make sure the parent of the templated node is either the HDA, the current OBJ or the Display SOP + HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurrentTemplatedGeoInfo.nodeId); + if (ParentId != CurrentHapiObjectInfo.nodeId + && ParentId != DisplayHapiGeoInfo.nodeId + && ParentId != AssetId) + { + continue; + } + + // Add this geo to the geo info array + GeoInfos.Add(CurrentTemplatedGeoInfo); + } + } + } + + // Iterates through the geos we want to process + for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) + { + // Cache the geo nodes ids for this asset + const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; + OutNodeIdsToCook.Add(CurrentHapiGeoInfo.nodeId); + + // Cook editable/templated nodes to get their parts. + if ((CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) + || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0) + || (!CurrentHapiGeoInfo.isDisplayGeo && CurrentHapiGeoInfo.partCount <= 0)) + { + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, + &GeoInfos[GeoIdx])); + } + + // Cache/convert the display geo's info + FHoudiniGeoInfo CurrentGeoInfo; + CacheGeoInfo(CurrentHapiGeoInfo, CurrentGeoInfo); + + // Simply create an empty array for this geo's group names + // We might need it later for splitting + TArray GeoGroupNames; + + // Store all the sockets found for this geo's part + TArray GeoMeshSockets; + + // Iterate on this geo's parts + for (int32 PartId = 0; PartId < CurrentGeoInfo.PartCount; ++PartId) + { + // Get part information. + HAPI_PartInfo CurrentHapiPartInfo; + FHoudiniApi::PartInfo_Init(&CurrentHapiPartInfo); + + // If the geo is templated, cook it manually + if (CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, &CookOptions); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + } + + bool bPartInfoFailed = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + { + bPartInfoFailed = true; + + // If the geo is templated, attempt to cook it manually + if(CurrentHapiGeoInfo.isTemplated && InOutputTemplatedGeos) + { + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, nullptr); + FHoudiniEngineUtils::HapiCookNode(CurrentHapiGeoInfo.nodeId, nullptr, true); + + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, + &GeoInfos[GeoIdx])); + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurrentHapiGeoInfo.nodeId, PartId, &CurrentHapiPartInfo)) + { + // We managed to get the templated part infos after cooking + bPartInfoFailed = false; + } + } + } + + if (bPartInfoFailed) + { + // Error retrieving part info. + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d] unable to retrieve PartInfo - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId); + continue; + } + + // Convert/cache the part info + FHoudiniPartInfo CurrentPartInfo; + CachePartInfo(CurrentHapiPartInfo, CurrentPartInfo); + + // Retrieve part name. + FString CurrentPartName = CurrentPartInfo.Name; + + // Unsupported/Invalid part + if (CurrentPartInfo.Type == EHoudiniPartType::Invalid) + continue; + + // Update part/instancer type from the part infos + EHoudiniPartType CurrentPartType = EHoudiniPartType::Invalid; + EHoudiniInstancerType CurrentInstancerType = EHoudiniInstancerType::Invalid; + switch (CurrentHapiPartInfo.type) + { + case HAPI_PARTTYPE_BOX: + case HAPI_PARTTYPE_SPHERE: + case HAPI_PARTTYPE_MESH: + { + if (CurrentHapiGeoInfo.type == HAPI_GEOTYPE_CURVE) + { + // Closed curve will be seen as mesh + CurrentPartType = EHoudiniPartType::Curve; + } + else + { + CurrentPartType = EHoudiniPartType::Mesh; + + if (CurrentHapiObjectInfo.isInstancer) + { + if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // That part is actually an attribute instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + } + else + { + // That part is actually an instancer + CurrentPartType = EHoudiniPartType::Instancer; + CurrentInstancerType = EHoudiniInstancerType::ObjectInstancer; + } + + } + else if (CurrentHapiPartInfo.vertexCount <= 0 && CurrentHapiPartInfo.pointCount <= 0) + { + // No points, no vertices, we're likely invalid + CurrentPartType = EHoudiniPartType::Invalid; + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a mesh with no points or vertices - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + } + else if (CurrentHapiPartInfo.vertexCount <= 0) + { + // This is not an instancer, we do not have vertices, but we have points + // Maybe this is a point cloud with attribute override instancing + if(FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // Mark it as an instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + } + else + { + // No vertices, not an instancer, just a point cloud, consider ourself as invalid + CurrentPartType = EHoudiniPartType::Invalid; + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is a point cloud mesh - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + } + } + } + } + break; + + case HAPI_PARTTYPE_CURVE: + { + // Make sure that this curve is not an an attribute instancer! + if (FHoudiniEngineUtils::IsAttributeInstancer(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CurrentInstancerType)) + { + // Mark the part as an instancer it as an instancer + CurrentPartType = EHoudiniPartType::Instancer; + // Instancer type is set by IsAttributeInstancer + //CurrentInstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + } + else + { + // The curve is a curve! + CurrentPartType = EHoudiniPartType::Curve; + } + } + break; + + case HAPI_PARTTYPE_INSTANCER: + // This is a packed primitive instancer + CurrentPartType = EHoudiniPartType::Instancer; + CurrentInstancerType = EHoudiniInstancerType::PackedPrimitive; + break; + + case HAPI_PARTTYPE_VOLUME: + // Volume data, likely a Heightfield height / mask + CurrentPartType = EHoudiniPartType::Volume; + break; + + default: + // Unsupported Part Type + break; + } + + // There are no vertices AND no points and this part is not a packed prim instancer + if ((CurrentPartInfo.VertexCount <= 0 && CurrentPartInfo.PointCount <= 0) + && (CurrentPartType != EHoudiniPartType::Instancer || CurrentInstancerType != EHoudiniInstancerType::PackedPrimitive)) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] no points or vertices found - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + continue; + } + + // This is an instancer with no points. + if (CurrentHapiObjectInfo.isInstancer && CurrentHapiPartInfo.pointCount <= 0) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s] is instancer but has 0 points - skipping."), + CurrentHapiObjectInfo.nodeId, *CurrentObjectName, CurrentHapiGeoInfo.nodeId, PartId, *CurrentPartName); + continue; + } + + // Extract Mesh sockets + // Do this before ignoring invalid parts, as socket groups/attributes could be set on parts + // that don't have any mesh, just points! Those would be be considered "invalid" parts but + // could still have valid sockets! + TArray PartMeshSockets; + FHoudiniEngineUtils::AddMeshSocketsToArray_DetailAttribute( + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, PartMeshSockets, CurrentHapiPartInfo.isInstanced); + FHoudiniEngineUtils::AddMeshSocketsToArray_Group( + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, PartMeshSockets, CurrentHapiPartInfo.isInstanced); + + // Ignore invalid parts + if (CurrentPartType == EHoudiniPartType::Invalid) + { + if(PartMeshSockets.Num() > 0) + { + // Store these Part sockets for the Geo + // We'll copy them to the outputs produced by this Geo later + GeoMeshSockets.Append(PartMeshSockets); + } + + continue; + } + + // Build the HGPO corresponding to this part + FHoudiniGeoPartObject currentHGPO; + currentHGPO.AssetId = AssetId; + currentHGPO.AssetName = CurrentAssetName; + + currentHGPO.ObjectId = CurrentHapiObjectInfo.nodeId; + currentHGPO.ObjectName = CurrentObjectName; + + currentHGPO.GeoId = CurrentHapiGeoInfo.nodeId; + + currentHGPO.PartId = CurrentHapiPartInfo.id; + + currentHGPO.Type = CurrentPartType; + currentHGPO.InstancerType = CurrentInstancerType; + + currentHGPO.TransformMatrix = TransformMatrix; + + currentHGPO.NodePath = TEXT(""); + + currentHGPO.bIsVisible = CurrentHapiObjectInfo.isVisible && !CurrentHapiPartInfo.isInstanced; + currentHGPO.bIsEditable = CurrentHapiGeoInfo.isEditable; + currentHGPO.bIsInstanced = CurrentHapiPartInfo.isInstanced; + // Never consider a display geo as templated! + currentHGPO.bIsTemplated = CurrentHapiGeoInfo.isDisplayGeo ? false : CurrentHapiGeoInfo.isTemplated; + + currentHGPO.bHasGeoChanged = CurrentHapiGeoInfo.hasGeoChanged; + currentHGPO.bHasPartChanged = CurrentHapiPartInfo.hasChanged; + currentHGPO.bHasMaterialsChanged = CurrentHapiGeoInfo.hasMaterialChanged; + currentHGPO.bHasTransformChanged = CurrentHapiObjectInfo.hasTransformChanged; + + // Copy the HAPI info caches + currentHGPO.ObjectInfo = CurrentObjectInfo; + currentHGPO.GeoInfo = CurrentGeoInfo; + currentHGPO.PartInfo = CurrentPartInfo; + + currentHGPO.AllMeshSockets = PartMeshSockets; + + // We only support meshes for templated geos + if (currentHGPO.bIsTemplated && (CurrentPartType != EHoudiniPartType::Mesh)) + continue; + + // Update the HGPO's node path + FHoudiniEngineUtils::HapiGetNodePath(currentHGPO, currentHGPO.NodePath); + + // Try to get the custom part name from attribute + FString CustomPartName; + if (FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, CustomPartName)) + currentHGPO.SetCustomPartName(CustomPartName); + else + currentHGPO.PartName = CurrentPartName; + + // + // Mesh Only - Extract split groups + // + // Extract the group names used by this part to see if it will require splitting + // Only meshes can be split, via their primitive groups + TArray SplitGroupNames; + if (CurrentPartType == EHoudiniPartType::Mesh) + { + if (!CurrentHapiPartInfo.isInstanced && GeoGroupNames.Num() > 0) + { + // We are not instanced and already have extracted the geo's group names + // We can simply reuse the Geo group names / socket groups + currentHGPO.SplitGroups = GeoGroupNames; + } + else + { + // We need to get the primitive group names from HAPI + int32 GroupCount = 0; + TArray GroupNames; + if (!FHoudiniEngineUtils::HapiGetGroupNames( + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + HAPI_GROUPTYPE_PRIM, CurrentHapiPartInfo.isInstanced, + GroupNames)) + { + GroupCount = 0; + GroupNames.Empty(); + } + + // Convert the string handles to FStrings + for (const FString& GroupName : GroupNames) + { + FString LodGroup = HAPI_UNREAL_GROUP_LOD_PREFIX; + FString CollisionGroup = HAPI_UNREAL_GROUP_INVISIBLE_COLLISION_PREFIX; + FString RenderedCollisionGroup = HAPI_UNREAL_GROUP_RENDERED_COLLISION_PREFIX; + if (GroupName.StartsWith(LodGroup, ESearchCase::IgnoreCase) + || GroupName.StartsWith(CollisionGroup, ESearchCase::IgnoreCase) + || GroupName.StartsWith(RenderedCollisionGroup, ESearchCase::IgnoreCase)) + //|| GroupName.StartsWith(HAPI_UNREAL_GROUP_USER_SPLIT_PREFIX, ESearchCase::IgnoreCase)) + { + // Split by collisions / lods + currentHGPO.SplitGroups.Add(GroupName); + } + } + + // Sort the Group name array by name, + // this will order the LODs and other incremental group names + currentHGPO.SplitGroups.Sort(); + + // If this part is not instanced, we can copy the geo + // group names so we can reuse them for another part + if (!CurrentHapiPartInfo.isInstanced) + { + GeoGroupNames = currentHGPO.SplitGroups; + } + } + } + + // + // Volume Only - Extract volume name/tile index + // + // Extract the volume's name, and see if a tile attribute is present + FHoudiniVolumeInfo CurrentVolumeInfo; + if (CurrentPartType == EHoudiniPartType::Volume) + { + // Get this volume's info + HAPI_VolumeInfo CurrentHapiVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentHapiVolumeInfo); + + bool bVolumeValid = true; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + &CurrentHapiVolumeInfo)) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.tupleSize != 1) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.zLength != 1) + { + bVolumeValid = false; + } + else if (CurrentHapiVolumeInfo.storage != HAPI_STORAGETYPE_FLOAT) + { + bVolumeValid = false; + } + + // Only cache valid volumes + if (bVolumeValid) + { + // Convert/Cache the volume info + CacheVolumeInfo(CurrentHapiVolumeInfo, CurrentVolumeInfo); + + // Get the volume's name + currentHGPO.VolumeName = CurrentVolumeInfo.Name; + + // Now see if this volume has a tile attribute + TArray TileValues; + if (FHoudiniEngineUtils::GetTileAttribute(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, TileValues, HAPI_ATTROWNER_PRIM, 0, 1)) + { + if (TileValues.Num() > 0 && TileValues[0] >= 0) + currentHGPO.VolumeTileIndex = TileValues[0]; + else + currentHGPO.VolumeTileIndex = -1; + } + } + } + currentHGPO.VolumeInfo = CurrentVolumeInfo; + + // Cache the curve info as well + // !!! Only call GetCurveInfo if the PartType is Curve + // !!! Closed curves are actually Meshes, and calling GetCurveInfo on a Mesh will crash HAPI! + FHoudiniCurveInfo CurrentCurveInfo; + if (CurrentPartType == EHoudiniPartType::Curve && CurrentPartInfo.Type == EHoudiniPartType::Curve) + { + HAPI_CurveInfo CurrentHapiCurveInfo; + FHoudiniApi::CurveInfo_Init(&CurrentHapiCurveInfo); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, + &CurrentHapiCurveInfo)) + { + // Cache/Convert this part's curve info + CacheCurveInfo(CurrentHapiCurveInfo, CurrentCurveInfo); + } + } + currentHGPO.CurveInfo = CurrentCurveInfo; + + + // TODO: + // DONE? bake folders are handled out of this loop? + // See if a custom bake folder override for the mesh was assigned via the "unreal_bake_folder" attribute + //TArray BakeFolderOverrides; + + // See if we have an existing output that matches this HGPO or if we need to create a new one + bool IsFoundOutputValid = false; + UHoudiniOutput ** FoundHoudiniOutput = nullptr; + // We handle volumes differently than other outputs types, as a single HF output has multiple HGPOs + if (currentHGPO.Type != EHoudiniPartType::Volume) + { + // Look in the previous output if we have a match + FoundHoudiniOutput = InOldOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(currentHGPO) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + + } + else + { + // Look in the previous outputs if we have a match + FoundHoudiniOutput = InOldOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, true) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + + // If we dont have a match in the old maps, also look in the newly created outputs + if (!IsFoundOutputValid) + { + FoundHoudiniOutput = OutNewOutputs.FindByPredicate( + [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, false) : false; }); + + if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + IsFoundOutputValid = true; + } + } + + UHoudiniOutput * HoudiniOutput = nullptr; + if (IsFoundOutputValid) + { + // We can reuse the existing output + HoudiniOutput = *FoundHoudiniOutput; + HoudiniOutput->SetIsUpdating(true); + // Transfer this output from the old array to the new one + InOldOutputs.Remove(HoudiniOutput); + } + else + { + // We couldn't find a valid output object, so create a new one + + // If the current part is a volume, only create a new output object + // if the volume's name is "height", if not store the HGPO aside + if (currentHGPO.Type == EHoudiniPartType::Volume + && !currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) + { + UnassignedVolumeParts.Add(currentHGPO); + continue; + } + + // Create a new output object + //FString OutputName = TEXT("Output") + FString::FromInt(OutputIdx++); + HoudiniOutput = NewObject( + InOuterObject, + UHoudiniOutput::StaticClass(), + NAME_None,//FName(*OutputName), + RF_NoFlags); + + // Make sure the created object is valid + if (!HoudiniOutput || HoudiniOutput->IsPendingKill()) + { + //HOUDINI_LOG_WARNING("Failed to create asset output"); + continue; + } + + // Mark if the HoudiniOutput is editable + HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); + } + + // Add the HGPO to the output + HoudiniOutput->AddNewHGPO(currentHGPO); + // Add this output object to the new ouput array + OutNewOutputs.AddUnique(HoudiniOutput); + } + // END: for Part + + if (GeoMeshSockets.Num() > 0) + { + // If we have any mesh socket, assign them to the HGPO for this geo + for (auto& CurNewOutput : OutNewOutputs) + { + if (!IsValid(CurNewOutput)) + continue; + + int32 FirstValidIdx = CurNewOutput->StaleCount; + if (!CurNewOutput->HoudiniGeoPartObjects.IsValidIndex(FirstValidIdx)) + FirstValidIdx = 0; + + for (int32 Idx = FirstValidIdx; Idx < CurNewOutput->HoudiniGeoPartObjects.Num(); Idx++) + { + // Only add sockets to valid/non stale HGPOs + FHoudiniGeoPartObject& CurHGPO = CurNewOutput->HoudiniGeoPartObjects[Idx]; + if (CurHGPO.ObjectId != CurrentHapiObjectInfo.nodeId) + continue; + + if (CurHGPO.GeoId != CurrentHapiGeoInfo.nodeId) + continue; + + CurHGPO.AllMeshSockets.Append(GeoMeshSockets); + } + } + } + } + // END: for GEO + } + // END: for OBJ + + // Update the output/HGPO associations from the map + // Clear the old HGPO since we don't need them anymore + for (auto& CurrentOuput : OutNewOutputs) + { + if (!IsValid(CurrentOuput)) + continue; + + CurrentOuput->DeleteAllStaleHGPOs(); + } + + // If we have unassigned volumes, + // try to find their corresponding output + if (UnassignedVolumeParts.Num() > 0) + { + for (auto& currentVolumeHGPO : UnassignedVolumeParts) + { + UHoudiniOutput ** FoundHoudiniOutput = OutNewOutputs.FindByPredicate( + [currentVolumeHGPO](UHoudiniOutput* Output) + { + return Output ? Output->HeightfieldMatch(currentVolumeHGPO, false) : false; + }); + + if (!FoundHoudiniOutput || !(*FoundHoudiniOutput) || (*FoundHoudiniOutput)->IsPendingKill()) + { + // Skip - consider this volume as invalid + continue; + } + + // Add this HGPO to the output + (*FoundHoudiniOutput)->AddNewHGPO(currentVolumeHGPO); + } + } + + // All our output objects now have their HGPO assigned + // We can now parse them to update the output type + for (auto& Output : OutNewOutputs) + { + Output->UpdateOutputType(); + } + + return true; +} + +bool +FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + + UObject* OuterComponent = HAC; + + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + PackageParams.ObjectName = FString(); + + TArray& Outputs = HAC->Outputs; + + // Iterate through the outputs array of HAC. + for (int32 Index = 0; Index < HAC->GetNumOutputs(); ++Index) + { + UHoudiniOutput* CurrentOutput = HAC->GetOutputAt(Index); + if (!CurrentOutput) + continue; + + if (!HAC->IsOutputTypeSupported(CurrentOutput->GetType())) + continue; + + switch (CurrentOutput->GetType()) + { + case EHoudiniOutputType::Instancer: + { + bool bNeedToRecreateInstancers = false; + for (auto& Iter : CurrentOutput->GetInstancedOutputs()) + { + FHoudiniInstancedOutput& InstOutput = Iter.Value; + if (!InstOutput.bChanged) + continue; + + /* + FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( + InstOutput, Iter.Key, CurrentOutput, HAC); + */ + + // TODO: + // UpdateChangedInstancedOutput needs some improvements + // as it currently destroy too many components. + // For now, we'll update all the instancers + bNeedToRecreateInstancers = true; + + InstOutput.MarkChanged(false); + } + + if (bNeedToRecreateInstancers) + { + if (HAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation || HAC->HasBeenLoaded()) + { + // Instantiate the HDA if it's not been + // This is because CreateAllInstancersFromHoudiniOutput() actually reads the transform from HAPI + // Calling it on a HDA not yet instantiated causes a crash... + HAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + } + else + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput(CurrentOutput, Outputs, HAC, PackageParams); + } + } + } + break; + + case EHoudiniOutputType::Curve: + { + //FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurrentOutput, HAC); + } + break; + + default: + break; + } + } + + return true; +} + +void +FHoudiniOutputTranslator::CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache) +{ + FHoudiniEngineString hapiSTR(InObjInfo.nameSH); + hapiSTR.ToFString(OutObjInfoCache.Name); + //OutObjInfoCache.Name = InObjInfo.nameSH; + + OutObjInfoCache.NodeId = InObjInfo.nodeId; + OutObjInfoCache.ObjectToInstanceID = InObjInfo.objectToInstanceId; + + OutObjInfoCache.bHasTransformChanged = InObjInfo.hasTransformChanged; + OutObjInfoCache.bHaveGeosChanged = InObjInfo.haveGeosChanged; + + OutObjInfoCache.bIsVisible = InObjInfo.isVisible; + OutObjInfoCache.bIsInstancer = InObjInfo.isInstancer; + OutObjInfoCache.bIsInstanced = InObjInfo.isInstanced; + + OutObjInfoCache.GeoCount = InObjInfo.geoCount; +}; + +EHoudiniGeoType +FHoudiniOutputTranslator::ConvertHapiGeoType(const HAPI_GeoType& InType) +{ + EHoudiniGeoType OutType = EHoudiniGeoType::Invalid; + switch (InType) + { + case HAPI_GEOTYPE_DEFAULT: + OutType = EHoudiniGeoType::Default; + break; + + case HAPI_GEOTYPE_INTERMEDIATE: + OutType = EHoudiniGeoType::Intermediate; + break; + + case HAPI_GEOTYPE_INPUT: + OutType = EHoudiniGeoType::Input; + break; + + case HAPI_GEOTYPE_CURVE: + OutType = EHoudiniGeoType::Curve; + break; + + default: + OutType = EHoudiniGeoType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache) +{ + OutGeoInfoCache.Type = ConvertHapiGeoType(InGeoInfo.type); + + FHoudiniEngineString hapiSTR(InGeoInfo.nameSH); + hapiSTR.ToFString(OutGeoInfoCache.Name); + + OutGeoInfoCache.NodeId = InGeoInfo.nodeId; + + OutGeoInfoCache.bIsEditable = InGeoInfo.isEditable; + OutGeoInfoCache.bIsTemplated = InGeoInfo.isTemplated; + OutGeoInfoCache.bIsDisplayGeo = InGeoInfo.isDisplayGeo; + OutGeoInfoCache.bHasGeoChanged = InGeoInfo.hasGeoChanged; + OutGeoInfoCache.bHasMaterialChanged = InGeoInfo.hasMaterialChanged; + + OutGeoInfoCache.PartCount = InGeoInfo.partCount; + OutGeoInfoCache.PointGroupCount = InGeoInfo.pointGroupCount; + OutGeoInfoCache.PrimitiveGroupCount = InGeoInfo.primitiveGroupCount; +}; + + +EHoudiniPartType +FHoudiniOutputTranslator::ConvertHapiPartType(const HAPI_PartType& InType) +{ + EHoudiniPartType OutType = EHoudiniPartType::Invalid; + switch (InType) + { + case HAPI_PARTTYPE_BOX: + case HAPI_PARTTYPE_SPHERE: + case HAPI_PARTTYPE_MESH: + OutType = EHoudiniPartType::Mesh; + break; + + case HAPI_PARTTYPE_CURVE: + OutType = EHoudiniPartType::Curve; + break; + + case HAPI_PARTTYPE_INSTANCER: + OutType = EHoudiniPartType::Instancer; + break; + + case HAPI_PARTTYPE_VOLUME: + OutType = EHoudiniPartType::Volume; + break; + + default: + OutType = EHoudiniPartType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache) +{ + OutPartInfoCache.PartId = InPartInfo.id; + + FHoudiniEngineString hapiSTR(InPartInfo.nameSH); + hapiSTR.ToFString(OutPartInfoCache.Name); + + OutPartInfoCache.Type = ConvertHapiPartType(InPartInfo.type); + + OutPartInfoCache.FaceCount = InPartInfo.faceCount; + OutPartInfoCache.VertexCount = InPartInfo.vertexCount; + OutPartInfoCache.PointCount = InPartInfo.pointCount; + + OutPartInfoCache.PointAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_POINT]; + OutPartInfoCache.VertexAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_VERTEX]; + OutPartInfoCache.PrimitiveAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_PRIM]; + OutPartInfoCache.DetailAttributeCounts = InPartInfo.attributeCounts[HAPI_ATTROWNER_DETAIL]; + + OutPartInfoCache.bIsInstanced = InPartInfo.isInstanced; + + OutPartInfoCache.InstancedPartCount = InPartInfo.instancedPartCount; + OutPartInfoCache.InstanceCount = InPartInfo.instanceCount; + + OutPartInfoCache.bHasChanged = InPartInfo.hasChanged; +}; + +void +FHoudiniOutputTranslator::CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache) +{ + FHoudiniEngineString hapiSTR(InVolumeInfo.nameSH); + hapiSTR.ToFString(OutVolumeInfoCache.Name); + + OutVolumeInfoCache.bIsVDB = (InVolumeInfo.type == HAPI_VOLUMETYPE_VDB); // replaces VolumeType Type; + + OutVolumeInfoCache.TupleSize = InVolumeInfo.tupleSize; + OutVolumeInfoCache.bIsFloat = (InVolumeInfo.storage == HAPI_STORAGETYPE_FLOAT); // replaces StorageType StorageType; + OutVolumeInfoCache.TileSize = InVolumeInfo.tileSize; + + FHoudiniEngineUtils::TranslateHapiTransform(InVolumeInfo.transform, OutVolumeInfoCache.Transform); + OutVolumeInfoCache.bHasTaper = InVolumeInfo.hasTaper; + + OutVolumeInfoCache.XLength = InVolumeInfo.xLength; + OutVolumeInfoCache.YLength = InVolumeInfo.yLength; + OutVolumeInfoCache.ZLength = InVolumeInfo.zLength; + + OutVolumeInfoCache.MinX = InVolumeInfo.minX; + OutVolumeInfoCache.MinY = InVolumeInfo.minY; + OutVolumeInfoCache.MinZ = InVolumeInfo.minZ; + + OutVolumeInfoCache.XTaper = InVolumeInfo.xTaper; + OutVolumeInfoCache.YTaper = InVolumeInfo.yTaper; +}; + +EHoudiniCurveType +FHoudiniOutputTranslator::ConvertHapiCurveType(const HAPI_CurveType& InType) +{ + EHoudiniCurveType OutType = EHoudiniCurveType::Invalid; + switch (InType) + { + case HAPI_CURVETYPE_LINEAR: + OutType = EHoudiniCurveType::Polygon; + break; + + case HAPI_CURVETYPE_NURBS: + OutType = EHoudiniCurveType::Nurbs; + break; + + case HAPI_CURVETYPE_BEZIER: + OutType = EHoudiniCurveType::Bezier; + break; + + case HAPI_CURVETYPE_MAX: + OutType = EHoudiniCurveType::Points; + break; + + default: + OutType = EHoudiniCurveType::Invalid; + break; + } + + return OutType; +} + +void +FHoudiniOutputTranslator::CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache) +{ + OutCurveInfoCache.Type = ConvertHapiCurveType(InCurveInfo.curveType); + + OutCurveInfoCache.CurveCount = InCurveInfo.curveCount; + OutCurveInfoCache.VertexCount = InCurveInfo.vertexCount; + OutCurveInfoCache.KnotCount = InCurveInfo.knotCount; + + OutCurveInfoCache.bIsPeriodic = InCurveInfo.isPeriodic; + OutCurveInfoCache.bIsRational = InCurveInfo.isRational; + + OutCurveInfoCache.Order = InCurveInfo.order; + + OutCurveInfoCache.bHasKnots = InCurveInfo.hasKnots; +}; + + +void +FHoudiniOutputTranslator::ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll) +{ + if (!IsValid(InHAC)) + return; + + // DO NOT MANUALLY DESTROY THE OLD/DANGLING OUTPUTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + // Simply clearing the array is enough + for (auto& OldOutput : InHAC->Outputs) + { + if (OldOutput->ShouldDeferClear() && !bForceClearAll) + { + OutputsPendingClear.Add(OldOutput); + } + else + { + ClearOutput(OldOutput); + } + } + + InHAC->Outputs.Empty(); +} + +void +FHoudiniOutputTranslator::ClearOutput(UHoudiniOutput* Output) +{ + switch (Output->GetType()) + { + case EHoudiniOutputType::Landscape: + { + for (auto& OutputObject : Output->GetOutputObjects()) + { + // Currently, any Landscape managed by an HDA is always present in the current level. + // Only when it gets baked will Landscapes be serialized to other levels so for now + // assume that a landscape should be available, unless explicitly deleted the user. + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.Value.OutputObject); + if (!LandscapePtr) + continue; + + ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); + + if (!IsValid(Landscape) || !Landscape->IsValidLowLevel()) + continue; + + Landscape->UnregisterAllComponents(); + Landscape->Destroy(); + + // if (Output->IsLandscapeWorldComposition()) + // { + // TSoftObjectPtr LandscapeSoftPtr = LandscapePtr->GetSoftPtr(); + // + // FString SoftPtrPath = LandscapeSoftPtr.ToSoftObjectPath().ToString(); + // + // FString FileName = FPaths::GetBaseFilename(SoftPtrPath); + // FString FileDirectory = FPaths::GetPath(SoftPtrPath); + // + // FString ContentPath = FPaths::ProjectContentDir(); + // FString ContentFullPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*ContentPath); + // + // FString AbsoluteFilePath = ContentFullPath + FileDirectory.Mid(5, FileDirectory.Len() - 5) + "/" + FPaths::GetBaseFilename(FileName) + ".umap"; + // + // FPlatformFileManager::Get().GetPlatformFile().FileExists(*(AbsoluteFilePath)); + // + // FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*(AbsoluteFilePath)); + // } + // else + // { + + // } + } + } + break; + + case EHoudiniOutputType::Instancer: + { + for (auto& OutputObject : Output->GetOutputObjects()) + { + // Is this a foliage instancer? Check if the component is owned by an AInstancedFoliageActor + UActorComponent* const Component = Cast(OutputObject.Value.OutputComponent); + if (!IsValid(Component)) + continue; + AActor* const OwnerActor = Component->GetOwner(); + if (!IsValid(OwnerActor) || !OwnerActor->IsA()) + continue; + + UHierarchicalInstancedStaticMeshComponent* const FoliageHISMC = Cast(Component); + if (IsValid(FoliageHISMC)) + { + // Find the parent component: the output is typically owned by an HAC. + USceneComponent* ParentComponent = nullptr; + UObject* const OutputOuter = Output->GetOuter(); + if (IsValid(OutputOuter)) + { + if (OutputOuter->IsA()) + { + ParentComponent = Cast(OutputOuter); + } + // other possibilities? + } + + // fallback to trying the owner of the HISMC + if (!ParentComponent) + { + ParentComponent = Cast(FoliageHISMC); + } + + if (IsValid(ParentComponent)) + { + FHoudiniInstanceTranslator::CleanupFoliageInstances(FoliageHISMC, OutputObject.Value.OutputObject, ParentComponent); + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + } + } + } + } + break; + // ... Other output types ...// + + default: + break; + + } + + Output->Clear(); +} + + +bool +FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName) +{ + HAPI_AttributeInfo CustomPartNameInfo; + FHoudiniApi::AttributeInfo_Init(&CustomPartNameInfo); + + bool bHasCustomName = false; + TArray CustomNames; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, CustomPartNameInfo, CustomNames)) + { + // Look for the v2 attribute (unreal_output_name) + bHasCustomName = true; + } + else if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(NodeId, PartId, HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1, CustomPartNameInfo, CustomNames)) + { + // If we couldnt find the new attribute, use the legacy v1 attribute (unreal_generated_mesh_name) + bHasCustomName = true; + } + + if (!bHasCustomName) + return false; + + if (CustomNames.Num() <= 0) + return false; + + OutCustomPartName = CustomNames[0]; + + if (OutCustomPartName.IsEmpty()) + return false; + + return true; +} + +void +FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return; + + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &DisplayGeoInfo)) + return; + + FString TempFolderOverride = FString(); + + HAPI_AttributeInfo TempFolderAttriInfo; + FHoudiniApi::AttributeInfo_Init(&TempFolderAttriInfo); + TArray StringData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_DETAIL)) + { + TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); + } + else + { + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + DisplayGeoInfo.nodeId, 0, HAPI_UNREAL_ATTRIB_TEMP_FOLDER, TempFolderAttriInfo, StringData, 1, HAPI_ATTROWNER_PRIM)) + { + TempFolderOverride = StringData.IsValidIndex(0) ? StringData[0] : FString(); + } + } + + if (TempFolderOverride.StartsWith("Game/")) + { + TempFolderOverride = "/" + TempFolderOverride; + } + + FString AbsoluteOverridePath; + if (TempFolderOverride.StartsWith("/Game/")) + { + FString RelativePath = FPaths::ProjectContentDir() + TempFolderOverride.Mid(6, TempFolderOverride.Len() - 6); + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + if (!TempFolderOverride.IsEmpty()) + AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*TempFolderOverride); + } + + // Check Validity of the path + if (AbsoluteOverridePath.IsEmpty() || !FPaths::DirectoryExists(AbsoluteOverridePath)) + { + // Only display a warning if the path is invalid, empty is fine + if(!AbsoluteOverridePath.IsEmpty()) + HOUDINI_LOG_WARNING(TEXT("Invalid override temporary cook path: %s"), *TempFolderOverride); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + TempFolderOverride = HoudiniRuntimeSettings->DefaultTemporaryCookFolder; + } + + // If the TempCookFolder of the HAC is non-empty and is different from the override path. + // do not override it if the current temp cook path is valid. (it was user specified) + if (!HAC->TemporaryCookFolder.Path.IsEmpty() && !HAC->TemporaryCookFolder.Path.Equals(TempFolderOverride)) + return; + + HAC->TemporaryCookFolder.Path = TempFolderOverride; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h index a0ee54472..2c4e84c2a 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h @@ -1,104 +1,106 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" - -class UHoudiniOutput; -class UHoudiniAssetComponent; - -struct FHoudiniObjectInfo; -struct FHoudiniGeoInfo; -struct FHoudiniPartInfo; -struct FHoudiniVolumeInfo; -struct FHoudiniCurveInfo; - -enum class EHoudiniOutputType : uint8; -enum class EHoudiniGeoType : uint8; -enum class EHoudiniPartType : uint8; -enum class EHoudiniCurveType : int8; - -struct HOUDINIENGINE_API FHoudiniOutputTranslator -{ - // - static bool UpdateOutputs( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate, - bool& bOutHasHoudiniStaticMeshOutput); - - // - static bool BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies=false); - - // - static bool UpdateLoadedOutputs(UHoudiniAssetComponent* HAC); - - // - static bool UploadChangedEditableOutput( - UHoudiniAssetComponent* HAC, - const bool& bInForceUpdate); - // - static bool BuildAllOutputs( - const HAPI_NodeId& AssetId, - UObject* InOuterObject, - TArray& InOldOutputs, - TArray& OutNewOutputs, - const bool& InOutputTemplatedGeos); - - static bool UpdateChangedOutputs( - UHoudiniAssetComponent* HAC); - - // Helpers functions used to convert HAPI types - static EHoudiniGeoType ConvertHapiGeoType(const HAPI_GeoType& InType); - static EHoudiniPartType ConvertHapiPartType(const HAPI_PartType& InType); - static EHoudiniCurveType ConvertHapiCurveType(const HAPI_CurveType& InType); - - // Helper functions used to cache HAPI infos - static void CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache); - static void CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache); - static void CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache); - static void CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache); - static void CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache); - - /** - * Helper to clear the outputs of the houdini asset component - * - * Some outputs (such as landscapes) need "deferred clearing". This means that - * these outputs should only be destroyed AFTER the new outputs have been processed. - * - * @param InHAC All outputs for this Houdini Asset Component will be cleared. - * @param OutputsPendingClear Any outputs that is "pending" clear. These outputs should typically be cleared AFTER the new outputs have been fully processed. - * @param bForceClearAll Setting this flag will force outputs to be cleared here and not take into account outputs requested a deferred clear. - */ - static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll = false); - // Helper to clear an individual UHoudiniOutput - static void ClearOutput(UHoudiniOutput* Output); - - static bool GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName); - static void GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" + +class UHoudiniOutput; +class UHoudiniAssetComponent; + +struct FHoudiniObjectInfo; +struct FHoudiniGeoInfo; +struct FHoudiniPartInfo; +struct FHoudiniVolumeInfo; +struct FHoudiniCurveInfo; + +enum class EHoudiniOutputType : uint8; +enum class EHoudiniGeoType : uint8; +enum class EHoudiniPartType : uint8; +enum class EHoudiniCurveType : int8; + +struct HOUDINIENGINE_API FHoudiniOutputTranslator +{ + // + static bool UpdateOutputs( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate, + bool& bOutHasHoudiniStaticMeshOutput); + + // + static bool BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies=false); + + // + static bool UpdateLoadedOutputs(UHoudiniAssetComponent* HAC); + + // + static bool UploadChangedEditableOutput( + UHoudiniAssetComponent* HAC, + const bool& bInForceUpdate); + // + static bool BuildAllOutputs( + const HAPI_NodeId& AssetId, + UObject* InOuterObject, + TArray& InOldOutputs, + TArray& OutNewOutputs, + TArray& OutNodeIdsToCook, + const bool& InOutputTemplatedGeos, + const bool& InUseOutputNodes); + + static bool UpdateChangedOutputs( + UHoudiniAssetComponent* HAC); + + // Helpers functions used to convert HAPI types + static EHoudiniGeoType ConvertHapiGeoType(const HAPI_GeoType& InType); + static EHoudiniPartType ConvertHapiPartType(const HAPI_PartType& InType); + static EHoudiniCurveType ConvertHapiCurveType(const HAPI_CurveType& InType); + + // Helper functions used to cache HAPI infos + static void CacheObjectInfo(const HAPI_ObjectInfo& InObjInfo, FHoudiniObjectInfo& OutObjInfoCache); + static void CacheGeoInfo(const HAPI_GeoInfo& InGeoInfo, FHoudiniGeoInfo& OutGeoInfoCache); + static void CachePartInfo(const HAPI_PartInfo& InPartInfo, FHoudiniPartInfo& OutPartInfoCache); + static void CacheVolumeInfo(const HAPI_VolumeInfo& InVolumeInfo, FHoudiniVolumeInfo& OutVolumeInfoCache); + static void CacheCurveInfo(const HAPI_CurveInfo& InCurveInfo, FHoudiniCurveInfo& OutCurveInfoCache); + + /** + * Helper to clear the outputs of the houdini asset component + * + * Some outputs (such as landscapes) need "deferred clearing". This means that + * these outputs should only be destroyed AFTER the new outputs have been processed. + * + * @param InHAC All outputs for this Houdini Asset Component will be cleared. + * @param OutputsPendingClear Any outputs that is "pending" clear. These outputs should typically be cleared AFTER the new outputs have been fully processed. + * @param bForceClearAll Setting this flag will force outputs to be cleared here and not take into account outputs requested a deferred clear. + */ + static void ClearAndRemoveOutputs(UHoudiniAssetComponent *InHAC, TArray& OutputsPendingClear, bool bForceClearAll = false); + // Helper to clear an individual UHoudiniOutput + static void ClearOutput(UHoudiniOutput* Output); + + static bool GetCustomPartNameFromAttribute(const HAPI_NodeId & NodeId, const HAPI_PartId & PartId, FString & OutCustomPartName); + static void GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp index dfcb7b46a..1ae043f6c 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.cpp @@ -1,107 +1,107 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGImporterMessages.h" - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage() - : FilePath() - , Name() - , TOPNodeId(-1) - , WorkItemId(-1) -{ - -} - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams -) - : FilePath(InFilePath) - , Name(InName) - , TOPNodeId(-1) - , WorkItemId(-1) -{ - SetPackageParams(InPackageParams); -} - -FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - HAPI_NodeId InTOPNodeId, - HAPI_PDG_WorkitemId InWorkItemId -) - : FilePath(InFilePath) - , Name(InName) - , TOPNodeId(InTOPNodeId) - , WorkItemId(InWorkItemId) -{ - SetPackageParams(InPackageParams); -} - -void FHoudiniPDGImportBGEOMessage::SetPackageParams(const FHoudiniPackageParams& InPackageParams) -{ - PackageParams = InPackageParams; - PackageParams.OuterPackage = nullptr; -} - -void FHoudiniPDGImportBGEOMessage::PopulatePackageParams(FHoudiniPackageParams& OutPackageParams) const -{ - UObject* KeepOuter = OutPackageParams.OuterPackage; - OutPackageParams = PackageParams; - OutPackageParams.OuterPackage = KeepOuter; -} - -FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage() - : ImportResult(EHoudiniPDGImportBGEOResult::HPIBR_Failed) -{ - -} - -FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - const EHoudiniPDGImportBGEOResult& InImportResult -) - : FHoudiniPDGImportBGEOMessage(InFilePath, InName, InPackageParams) - , ImportResult(InImportResult) -{ -} - -FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage() - : CommandletGuid() -{ - -} - -FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid) - : CommandletGuid(InCommandletGuid) -{ - -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGImporterMessages.h" + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage() + : FilePath() + , Name() + , TOPNodeId(-1) + , WorkItemId(-1) +{ + +} + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams +) + : FilePath(InFilePath) + , Name(InName) + , TOPNodeId(-1) + , WorkItemId(-1) +{ + SetPackageParams(InPackageParams); +} + +FHoudiniPDGImportBGEOMessage::FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + HAPI_NodeId InTOPNodeId, + HAPI_PDG_WorkitemId InWorkItemId +) + : FilePath(InFilePath) + , Name(InName) + , TOPNodeId(InTOPNodeId) + , WorkItemId(InWorkItemId) +{ + SetPackageParams(InPackageParams); +} + +void FHoudiniPDGImportBGEOMessage::SetPackageParams(const FHoudiniPackageParams& InPackageParams) +{ + PackageParams = InPackageParams; + PackageParams.OuterPackage = nullptr; +} + +void FHoudiniPDGImportBGEOMessage::PopulatePackageParams(FHoudiniPackageParams& OutPackageParams) const +{ + UObject* KeepOuter = OutPackageParams.OuterPackage; + OutPackageParams = PackageParams; + OutPackageParams.OuterPackage = KeepOuter; +} + +FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage() + : ImportResult(EHoudiniPDGImportBGEOResult::HPIBR_Failed) +{ + +} + +FHoudiniPDGImportBGEOResultMessage::FHoudiniPDGImportBGEOResultMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + const EHoudiniPDGImportBGEOResult& InImportResult +) + : FHoudiniPDGImportBGEOMessage(InFilePath, InName, InPackageParams) + , ImportResult(InImportResult) +{ +} + +FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage() + : CommandletGuid() +{ + +} + +FHoudiniPDGImportBGEODiscoverMessage::FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid) + : CommandletGuid(InCommandletGuid) +{ + +} + diff --git a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h index fa2f6f685..d9dc0b1b1 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGImporterMessages.h @@ -1,188 +1,188 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Misc/Guid.h" - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniPackageParams.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniOutput.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInstanceTranslator.h" - -#include "HoudiniPDGImporterMessages.generated.h" - -// Message used to find/discover running commandlets -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEODiscoverMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEODiscoverMessage(); - - FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid); - - // The GUID of the commandlet we are looking for - UPROPERTY() - FGuid CommandletGuid; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEOMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEOMessage(); - - FHoudiniPDGImportBGEOMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams); - - FHoudiniPDGImportBGEOMessage( - const FString& InFilePath, - const FString& InName, - const FHoudiniPackageParams& InPackageParams, - HAPI_NodeId InTOPNodeId, - HAPI_PDG_WorkitemId InWorkItemId); - - void SetPackageParams(const FHoudiniPackageParams& InPackageParams); - - void PopulatePackageParams(FHoudiniPackageParams &OutPackageParams) const; - - // BGEO file path - UPROPERTY() - FString FilePath; - - // PDG work item name - UPROPERTY() - FString Name; - - // TOP/PDG info - // TOP node ID - UPROPERTY() - // HAPI_NodeId TOPNodeId; - int32 TOPNodeId; - - // Work item id - UPROPERTY() - // HAPI_PDG_WorkitemId WorkItemId; - int32 WorkItemId; - - // Package params for the asset - UPROPERTY() - FHoudiniPackageParams PackageParams; -}; - - -UENUM() -enum class EHoudiniPDGImportBGEOResult : uint8 -{ - // Create uassets from the bgeo completely failed. - HPIBR_Failed UMETA(DisplayName="Failed"), - - // Successfully created uassets for all content in the bgeo - HPIBR_Success UMETA(DisplayName = "Success"), - - // Some uassets were created, but there were unsupported objects in the bgeo as well - HPIBR_PartialSuccess UMETA(DisplayName = "Partial Success"), - - HIBPR_MAX -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniGenericAttributes -{ -public: - GENERATED_BODY() - - FHoudiniGenericAttributes() {}; - FHoudiniGenericAttributes(const TArray& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; - FHoudiniGenericAttributes(TArray&& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; - - UPROPERTY() - TArray PropertyAttributes; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutputObject -{ -public: - GENERATED_BODY(); - - UPROPERTY() - FHoudiniOutputObjectIdentifier Identifier; - - UPROPERTY() - FString PackagePath; - - UPROPERTY() - FHoudiniGenericAttributes GenericAttributes; - - UPROPERTY() - TMap CachedAttributes; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutput -{ -public: - GENERATED_BODY(); - - UPROPERTY() - TArray HoudiniGeoPartObjects; - - UPROPERTY() - TArray OutputObjects; - - UPROPERTY() - TArray InstancedOutputPartData; -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPDGImportBGEOResultMessage : public FHoudiniPDGImportBGEOMessage -{ -public: - GENERATED_BODY(); - - FHoudiniPDGImportBGEOResultMessage(); - - FHoudiniPDGImportBGEOResultMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams, const EHoudiniPDGImportBGEOResult& InImportResult); - - void operator=(const FHoudiniPDGImportBGEOMessage& InRHS) { (*static_cast(this)) = InRHS; } - - // Result of the bgeo import -> uassets - UPROPERTY() - EHoudiniPDGImportBGEOResult ImportResult; - - UPROPERTY() - TArray Outputs; - -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Guid.h" + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniPackageParams.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniOutput.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInstanceTranslator.h" + +#include "HoudiniPDGImporterMessages.generated.h" + +// Message used to find/discover running commandlets +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEODiscoverMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEODiscoverMessage(); + + FHoudiniPDGImportBGEODiscoverMessage(const FGuid& InCommandletGuid); + + // The GUID of the commandlet we are looking for + UPROPERTY() + FGuid CommandletGuid; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEOMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEOMessage(); + + FHoudiniPDGImportBGEOMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams); + + FHoudiniPDGImportBGEOMessage( + const FString& InFilePath, + const FString& InName, + const FHoudiniPackageParams& InPackageParams, + HAPI_NodeId InTOPNodeId, + HAPI_PDG_WorkitemId InWorkItemId); + + void SetPackageParams(const FHoudiniPackageParams& InPackageParams); + + void PopulatePackageParams(FHoudiniPackageParams &OutPackageParams) const; + + // BGEO file path + UPROPERTY() + FString FilePath; + + // PDG work item name + UPROPERTY() + FString Name; + + // TOP/PDG info + // TOP node ID + UPROPERTY() + // HAPI_NodeId TOPNodeId; + int32 TOPNodeId; + + // Work item id + UPROPERTY() + // HAPI_PDG_WorkitemId WorkItemId; + int32 WorkItemId; + + // Package params for the asset + UPROPERTY() + FHoudiniPackageParams PackageParams; +}; + + +UENUM() +enum class EHoudiniPDGImportBGEOResult : uint8 +{ + // Create uassets from the bgeo completely failed. + HPIBR_Failed UMETA(DisplayName="Failed"), + + // Successfully created uassets for all content in the bgeo + HPIBR_Success UMETA(DisplayName = "Success"), + + // Some uassets were created, but there were unsupported objects in the bgeo as well + HPIBR_PartialSuccess UMETA(DisplayName = "Partial Success"), + + HIBPR_MAX +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniGenericAttributes +{ +public: + GENERATED_BODY() + + FHoudiniGenericAttributes() {}; + FHoudiniGenericAttributes(const TArray& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; + FHoudiniGenericAttributes(TArray&& InPropertyAttributes) : PropertyAttributes(InPropertyAttributes) {}; + + UPROPERTY() + TArray PropertyAttributes; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutputObject +{ +public: + GENERATED_BODY(); + + UPROPERTY() + FHoudiniOutputObjectIdentifier Identifier; + + UPROPERTY() + FString PackagePath; + + UPROPERTY() + FHoudiniGenericAttributes GenericAttributes; + + UPROPERTY() + TMap CachedAttributes; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportNodeOutput +{ +public: + GENERATED_BODY(); + + UPROPERTY() + TArray HoudiniGeoPartObjects; + + UPROPERTY() + TArray OutputObjects; + + UPROPERTY() + TArray InstancedOutputPartData; +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPDGImportBGEOResultMessage : public FHoudiniPDGImportBGEOMessage +{ +public: + GENERATED_BODY(); + + FHoudiniPDGImportBGEOResultMessage(); + + FHoudiniPDGImportBGEOResultMessage(const FString& InFilePath, const FString& InName, const FHoudiniPackageParams& InPackageParams, const EHoudiniPDGImportBGEOResult& InImportResult); + + void operator=(const FHoudiniPDGImportBGEOMessage& InRHS) { (*static_cast(this)) = InRHS; } + + // Result of the bgeo import -> uassets + UPROPERTY() + EHoudiniPDGImportBGEOResult ImportResult; + + UPROPERTY() + TArray Outputs; + +}; + diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp index 66639e72c..91d16d8c3 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp @@ -1,2264 +1,2264 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGManager.h" - -#include "Modules/ModuleManager.h" -#include "MessageEndpointBuilder.h" -#include "HAL/FileManager.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPackageParams.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniPDGTranslator.h" -#include "HoudiniPDGImporterMessages.h" - -#include "HAPI/HAPI_Common.h" - -HOUDINI_PDG_DEFINE_LOG_CATEGORY(); - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniPDGManager::FHoudiniPDGManager() -{ -} - -FHoudiniPDGManager::~FHoudiniPDGManager() -{ -} - -bool -FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) -{ - if (!InHAC || InHAC->IsPendingKill()) - return false; - - int32 AssetId = InHAC->GetAssetId(); - if (AssetId < 0) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) - return false; - - // Create a new PDG Asset Link Object - bool bRegisterPDGAssetLink = false; - UHoudiniPDGAssetLink* PDGAssetLink = InHAC->GetPDGAssetLink(); - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - { - PDGAssetLink = NewObject(InHAC, UHoudiniPDGAssetLink::StaticClass(), NAME_None, RF_Transactional); - bRegisterPDGAssetLink = true; - } - - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - PDGAssetLink->AssetID = AssetId; - - // Get the HDA's info - HAPI_NodeInfo AssetInfo; - FHoudiniApi::NodeInfo_Init(&AssetInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, &AssetInfo), false); - - // Get the node path - FString AssetNodePath; - FHoudiniEngineString::ToFString(AssetInfo.internalNodePathSH, PDGAssetLink->AssetNodePath); - - // Get the node name - FString AssetName; - FHoudiniEngineString::ToFString(AssetInfo.nameSH, PDGAssetLink->AssetName); - - const bool bZeroWorkItemTallys = true; - if (!FHoudiniPDGManager::PopulateTOPNetworks(PDGAssetLink, bZeroWorkItemTallys)) - { - // We couldn't find any valid TOPNet/TOPNode, this is not a PDG Asset - // Make sure the HDA doesn't have a PDGAssetLink - InHAC->SetPDGAssetLink(nullptr); - return false; - } - - // If the PDG asset link comes from a loaded asset, we also need to register it - if (InHAC->HasBeenLoaded()) - { - bRegisterPDGAssetLink = true; - } - - // We have found valid TOPNetworks and TOPNodes, - // This is a PDG HDA, so add the AssetLink to it - PDGAssetLink->LinkState = EPDGLinkState::Linked; - - if (PDGAssetLink->SelectedTOPNetworkIndex < 0) - PDGAssetLink->SelectedTOPNetworkIndex = 0; - - InHAC->SetPDGAssetLink(PDGAssetLink); - - if (bRegisterPDGAssetLink) - { - // Register this PDG Asset Link to the PDG Manager - TWeakObjectPtr AssetLinkPtr(PDGAssetLink); - PDGAssetLinks.Add(AssetLinkPtr); - } - - // If the commandlet is enabled, check if we have started and established communication with the commandlet yet - // if not, try to start the commandlet - bool bCommandletIsEnabled = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (IsValid(HoudiniRuntimeSettings)) - { - bCommandletIsEnabled = HoudiniRuntimeSettings->bPDGAsyncCommandletImportEnabled; - } - - if (bCommandletIsEnabled) - { - const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); - if (CommandletStatus == EHoudiniBGEOCommandletStatus::NotStarted && bCommandletIsEnabled) - { - CreateBGEOCommandletAndEndpoint(); - } - } - - return true; -} - -bool -FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) -{ - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - // If the PDG Asset link is inactive, indicate that our HDA must be instantiated - if (PDGAssetLink->LinkState == EPDGLinkState::Inactive) - { - UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); - if(!ParentHAC) - { - // No valid parent HAC, error! - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("No valid Houdini Asset Component parent for PDG Asset Link!")); - } - else if (ParentHAC && ParentHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) - { - PDGAssetLink->LinkState = EPDGLinkState::Linking; - ParentHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); - } - else - { - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("Unable to link the PDG Asset link! Try to rebuild the HDA.")); - } - } - - if (PDGAssetLink->LinkState == EPDGLinkState::Linking) - { - return true; - } - - if (PDGAssetLink->LinkState != EPDGLinkState::Linked) - { - UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); - int32 AssetId = ParentHAC->GetAssetId(); - if (AssetId < 0) - return false; - - if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) - return false; - - PDGAssetLink->AssetID = AssetId; - } - - if(!PopulateTOPNetworks(PDGAssetLink)) - { - PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - HOUDINI_LOG_ERROR(TEXT("Failed to populte the PDG Asset Link.")); - return false; - } - - return true; -} - - -bool -FHoudiniPDGManager::PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys) -{ - // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) - return false; - - // Get all the network nodes within the asset, recursively. - // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type - int32 NetworkNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); - - if (NetworkNodeCount <= 0) - return false; - - TArray AllNetworkNodeIDs; - AllNetworkNodeIDs.SetNum(NetworkNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); - - // There is currently no way to only get non bypassed nodes via HAPI - // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... - TArray AllBypassedTOPNetNodeIDs; - { - int32 BypassedTOPNetNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); - - if (BypassedTOPNetNodeCount > 0) - { - // Get the list of all bypassed TOP Net... - AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, - AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); - - // ... and remove them from the network list - for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) - AllNetworkNodeIDs.RemoveAt(Idx); - } - } - } - - - // For each Network we found earlier, only add those with TOP child nodes - // Therefore guaranteeing that we only add TOP networks - TArray AllTOPNetworks; - for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) - { - if (CurrentNodeId < 0) - { - continue; - } - - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) - { - continue; - } - - // Skip non TOP or SOP networks - if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP - && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) - { - continue; - } - - // Check that this TOP Net is not nested in another TOP Net... - // This will happen with ROP Geometry TOPs for example... - bool bIsNestedInTOPNet = false; - HAPI_NodeId CurrentParentId = CurrentNodeInfo.parentId; - while (CurrentParentId > 0) - { - if (AllNetworkNodeIDs.Contains(CurrentParentId)) - { - bIsNestedInTOPNet = true; - break; - } - - if(AllBypassedTOPNetNodeIDs.Contains(CurrentParentId)) - { - bIsNestedInTOPNet = true; - break; - } - - HAPI_NodeInfo ParentNodeInfo; - FHoudiniApi::NodeInfo_Init(&ParentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentParentId, &ParentNodeInfo)) - { - break; - } - - // Get our parent's parent - CurrentParentId = ParentNodeInfo.parentId; - } - - // Skip nested TOP nets - if (bIsNestedInTOPNet) - continue; - - // Get the list of all TOP nodes within the current network (ignoring schedulers) - int32 TOPNodeCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) - { - continue; - } - - TArray AllTOPNodeIDs; - AllTOPNodeIDs.SetNum(TOPNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, AllTOPNodeIDs.GetData(), TOPNodeCount), false); - - // Skip networks without TOP nodes - if (AllTOPNodeIDs.Num() <= 0) - { - continue; - } - - // Since there is currently no way to get only non-bypassed nodes via HAPI - // we need to get a list of all the bypassed top nodes to remove them from the previous list - { - int32 BypassedTOPNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_TOP_NONSCHEDULER | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNodeCount), false); - - if (BypassedTOPNodeCount > 0) - { - // Get the list of all bypassed TOP Nodes... - TArray AllBypassedTOPNodes; - AllBypassedTOPNodes.SetNum(BypassedTOPNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - AllBypassedTOPNodes.GetData(), BypassedTOPNodeCount), false); - - // ... and remove them from the top node list - for (int32 Idx = AllTOPNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNodes.Contains(AllTOPNodeIDs[Idx])) - AllTOPNodeIDs.RemoveAt(Idx); - } - } - } - - // TODO: - // Apply the show and output filter on that node - bool bShow = true; - - // Get the node path - FString CurrentNodePath; - FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, PDGAssetLink->AssetID, CurrentNodePath); - - // Get the node name - FString CurrentNodeName; - FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, CurrentNodeName); - - UTOPNetwork* CurrentTOPNetwork = nullptr; - int32 FoundTOPNetIndex = INDEX_NONE; - UTOPNetwork* FoundTOPNet = PDGAssetLink->GetTOPNetworkByNodePath(CurrentNodeName, PDGAssetLink->AllTOPNetworks, FoundTOPNetIndex); - if (IsValid(FoundTOPNet)) - { - // Reuse the existing corresponding TOP NET - CurrentTOPNetwork = FoundTOPNet; - PDGAssetLink->AllTOPNetworks.RemoveAt(FoundTOPNetIndex); - } - else - { - // Create a new instance for the TOP NET - CurrentTOPNetwork = NewObject(PDGAssetLink, UTOPNetwork::StaticClass(), NAME_None, RF_Transactional); - } - - // Update the TOP NET - CurrentTOPNetwork->NodeId = CurrentNodeId; - CurrentTOPNetwork->NodeName = CurrentNodeName; - CurrentTOPNetwork->NodePath = CurrentNodePath; - CurrentTOPNetwork->ParentName = PDGAssetLink->AssetName; - CurrentTOPNetwork->bShowResults = bShow; - - // Only add network that have valid TOP Nodes - if (PopulateTOPNodes(AllTOPNodeIDs, CurrentTOPNetwork, PDGAssetLink, bInZeroWorkItemTallys)) - { - // See if we need to select a new TOP node - bool bReselectValidTOP = false; - if (CurrentTOPNetwork->SelectedTOPIndex < 0) - bReselectValidTOP = true; - else if (!CurrentTOPNetwork->AllTOPNodes.IsValidIndex(CurrentTOPNetwork->SelectedTOPIndex)) - bReselectValidTOP = true; - else if (!IsValid(CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]) || - CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]->bHidden) - bReselectValidTOP = true; - - if (bReselectValidTOP) - { - // Select the first valid TOP node (not hidden) - for (int Idx = 0; Idx < CurrentTOPNetwork->AllTOPNodes.Num(); Idx++) - { - UTOPNode *TOPNode = CurrentTOPNetwork->AllTOPNodes[Idx]; - if (!IsValid(TOPNode) || TOPNode->bHidden) - continue; - - CurrentTOPNetwork->SelectedTOPIndex = Idx; - break; - } - } - - AllTOPNetworks.Add(CurrentTOPNetwork); - } - } - - // Clear previous TOP networks, nodes and generated data - for (UTOPNetwork* CurrentTOPNet : PDGAssetLink->AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - PDGAssetLink->ClearTOPNetworkWorkItemResults(CurrentTOPNet); - } - //PDGAssetLink->ClearAllTOPData(); - PDGAssetLink->AllTOPNetworks = AllTOPNetworks; - - return (AllTOPNetworks.Num() > 0); -} - - -bool -FHoudiniPDGManager::PopulateTOPNodes( - const TArray& InTopNodeIDs, UTOPNetwork* InTOPNetwork, UHoudiniPDGAssetLink* InPDGAssetLink, bool bInZeroWorkItemTallys) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InTOPNetwork)) - return false; - - // - int32 TOPNodeCount = 0; - - // Holds list of found TOP nodes - TArray AllTOPNodes; - for(const HAPI_NodeId& CurrentTOPNodeID : InTopNodeIDs) - { - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentTOPNodeID, &CurrentNodeInfo)) - { - continue; - } - - // Increase the number of valid TOP Node - // (before applying the node filter) - TOPNodeCount++; - - // Get the node path - FString NodePath; - FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, InTOPNetwork->NodeId, NodePath); - - // Get the node name - FString NodeName; - FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, NodeName); - - // See if we can find an existing version of this TOPNOde - UTOPNode* CurrentTOPNode = nullptr; - int32 FoundNodeIndex = INDEX_NONE; - UTOPNode* FoundNode = InPDGAssetLink->GetTOPNodeByNodePath(NodePath, InTOPNetwork->AllTOPNodes, FoundNodeIndex); - if (IsValid(FoundNode)) - { - CurrentTOPNode = FoundNode; - InTOPNetwork->AllTOPNodes.RemoveAt(FoundNodeIndex); - - if (bInZeroWorkItemTallys) - { - CurrentTOPNode->ZeroWorkItemTally(); - CurrentTOPNode->NodeState = EPDGNodeState::None; - } - } - else - { - // Didn't find an existing UTOPNode for this node, create a new UTOPNode instance - CurrentTOPNode = NewObject(InPDGAssetLink, UTOPNode::StaticClass(), NAME_None, RF_Transactional); - } - - CurrentTOPNode->NodeId = CurrentTOPNodeID; - CurrentTOPNode->NodeName = NodeName; - CurrentTOPNode->NodePath = NodePath; - CurrentTOPNode->ParentName = InTOPNetwork->ParentName + TEXT("_") + InTOPNetwork->NodeName; - CurrentTOPNode->bHasChildNodes = CurrentNodeInfo.childNodeCount > 0; - - // Filter display/autoload using name - CurrentTOPNode->bHidden = false; - if (InPDGAssetLink->bUseTOPNodeFilter && !InPDGAssetLink->TOPNodeFilter.IsEmpty()) - { - // Only display nodes that matches the filter - if (!NodeName.StartsWith(InPDGAssetLink->TOPNodeFilter)) - CurrentTOPNode->bHidden = true; - } - - // Automatically load results for nodes that match the filter - if (InPDGAssetLink->bUseTOPOutputFilter) - { - bool bAutoLoad = false; - if (InPDGAssetLink->TOPOutputFilter.IsEmpty()) - bAutoLoad = true; - else if (NodeName.StartsWith(InPDGAssetLink->TOPOutputFilter)) - bAutoLoad = true; - - CurrentTOPNode->bAutoLoad = bAutoLoad; - - // Show autoloaded results by default - CurrentTOPNode->SetVisibleInLevel(bAutoLoad); - } - - AllTOPNodes.Add(CurrentTOPNode); - } - - for (UTOPNode* CurTOPNode : InTOPNetwork->AllTOPNodes) - { - if (!IsValid(CurTOPNode)) - continue; - - InPDGAssetLink->ClearTOPNodeWorkItemResults(CurTOPNode); - } - - InTOPNetwork->AllTOPNodes = AllTOPNodes; - - return (TOPNodeCount > 0); -} - - -void -FHoudiniPDGManager::DirtyTOPNode(UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - // Dirty the specified TOP node... - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( - FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, true)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *(InTOPNode->NodeName)); - } - - // ... and clear its work item results. - UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(InTOPNode); -} - -// void -// FHoudiniPDGManager::DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode) -// { -// // Dirty the specified TOP node... -// if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( -// FHoudiniEngine::Get().GetSession(), InTOPNode.NodeId, true)) -// { -// HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *InTOPNode.NodeName); -// } -// } - -void -FHoudiniPDGManager::CookTOPNode(UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - if (InTOPNode->NodeState == EPDGNodeState::Cooking || InTOPNode->AnyWorkItemsPending()) - { - HOUDINI_LOG_WARNING(TEXT("PDG Cook TOP Node - %s is already/still cooking, ignoring 'Cook TOP Node' request."), *(InTOPNode->NodePath)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( - FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, 0, 0)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook TOP Node - Failed to cook %s!"), *(InTOPNode->NodeName)); - } -} - - -void -FHoudiniPDGManager::DirtyAll(UTOPNetwork* InTOPNet) -{ - if (!IsValid(InTOPNet)) - return; - - // Dirty the specified TOP network... - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, true)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Dirty All - Failed to dirty all of %s's TOP nodes!"), *(InTOPNet->NodeName)); - return; - } - - // ... and clear its work item results. - UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(InTOPNet); -} - - -void -FHoudiniPDGManager::CookOutput(UTOPNetwork* InTOPNet) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::CookOutput); - - // Cook the output TOP node of the currently selected TOP network. - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - bool bAlreadyCooking = InTOPNet->AnyWorkItemsPending(); - - if (!bAlreadyCooking) - { - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - int32 PDGState = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( - FHoudiniEngine::Get().GetSession(), GraphContextId, &PDGState)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's PDG state."), *(InTOPNet->NodeName)); - return; - } - bAlreadyCooking = ((HAPI_PDG_State) PDGState == HAPI_PDG_STATE_COOKING); - } - - if (bAlreadyCooking) - { - HOUDINI_LOG_WARNING(TEXT("PDG Cook Output - %s is already/still cooking, ignoring 'Cook Output' request."), *(InTOPNet->NodeName)); - return; - } - - // TODO: ??? - // Cancel all cooks. This is required as otherwise the graph gets into an infinite cook state (bug?) - if(HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, 0, 0)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to cook %s's output!"), *(InTOPNet->NodeName)); - } -} - - -void -FHoudiniPDGManager::PauseCook(UTOPNetwork* InTOPNet) -{ - // Pause the PDG cook of the currently selected TOP network - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::PausePDGCook( - FHoudiniEngine::Get().GetSession(), GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to pause %s!"), *(InTOPNet->NodeName)); - return; - } -} - - -void -FHoudiniPDGManager::CancelCook(UTOPNetwork* InTOPNet) -{ - // Cancel the PDG cook of the currently selected TOP network - //WorkItemTally.ZeroAll(); - //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); - - if (!IsValid(InTOPNet)) - return; - - if (!FHoudiniEngine::Get().GetSession()) - return; - - HAPI_PDG_GraphContextId GraphContextId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( - FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); - return; - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CancelPDGCook( - FHoudiniEngine::Get().GetSession(), GraphContextId)) - { - HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to cancel cook for %s!"), *(InTOPNet->NodeName)); - return; - } -} - -void -FHoudiniPDGManager::Update() -{ - // Clean up registered PDG Asset Links - for(int32 Idx = PDGAssetLinks.Num() - 1; Idx >= 0; Idx--) - { - TWeakObjectPtr Ptr = PDGAssetLinks[Idx]; - if (!Ptr.IsValid() || Ptr.IsStale()) - { - PDGAssetLinks.RemoveAt(Idx); - continue; - } - - UHoudiniPDGAssetLink* CurPDGAssetLink = PDGAssetLinks[Idx].Get(); - if (!CurPDGAssetLink || CurPDGAssetLink->IsPendingKill()) - { - PDGAssetLinks.RemoveAt(Idx); - continue; - } - } - - // Do nothing if we dont have any valid PDG asset Link - if (PDGAssetLinks.Num() <= 0) - return; - - // Update the PDG contexts and handle all pdg events and work item status updates - UpdatePDGContexts(); - - // Prcoess any workitem result if we have any - ProcessWorkItemResults(); -} - -// Query all the PDG graph context in the current Houdini Engine session. -// Handle PDG events, work item status updates. -// Forward relevant events to PDGAssetLink objects. -void -FHoudiniPDGManager::UpdatePDGContexts() -{ - // Get current PDG graph contexts - ReinitializePDGContext(); - - // Process next set of events for each graph context - if (PDGContextIDs.Num() > 0) - { - // Only initialize event array if not valid, or user resized max size - if(PDGEventInfos.Num() != MaxNumberOfPDGEvents) - PDGEventInfos.SetNum(MaxNumberOfPDGEvents); - - // TODO: member? - //HAPI_PDG_State PDGState; - for(const HAPI_PDG_GraphContextId& CurrentContextID : PDGContextIDs) - { - /* - // TODO: No need to reset events at each tick - int32 PDGStateInt; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( - FHoudiniEngine::Get().GetSession(), CurrentContextID, &PDGStateInt)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get PDG state")); - continue; - } - - PDGState = (HAPI_PDG_State)PDGStateInt; - - for (int32 Idx = 0; Idx < PDGEventInfos.Num(); Idx++) - { - ResetPDGEventInfo(PDGEventInfos[Idx]); - } - */ - - int32 PDGEventCount = 0; - int32 RemainingPDGEventCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGEvents( - FHoudiniEngine::Get().GetSession(), CurrentContextID, PDGEventInfos.GetData(), - MaxNumberOfPDGEvents, &PDGEventCount, &RemainingPDGEventCount)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get PDG events")); - continue; - } - - if (PDGEventCount < 1) - continue; - - for (int32 EventIdx = 0; EventIdx < PDGEventCount; EventIdx++) - { - ProcessPDGEvent(CurrentContextID, PDGEventInfos[EventIdx]); - } - - HOUDINI_LOG_MESSAGE(TEXT("PDG: Tick processed %d events, %d remaining."), PDGEventCount, RemainingPDGEventCount); - } - } - - // Refresh UI if necessary - for (auto CurAssetLink : PDGAssetLinks) - { - UHoudiniPDGAssetLink* AssetLink = CurAssetLink.Get(); - if (AssetLink) - { - if (AssetLink->bNeedsUIRefresh) - { - FHoudiniPDGManager::RefreshPDGAssetLinkUI(AssetLink); - AssetLink->bNeedsUIRefresh = false; - } - else - { - AssetLink->UpdateWorkItemTally(); - } - } - } -} - -// Query the currently active PDG graph contexts in the Houdini Engine session. -// Should be done each time to get latest set of graph contexts. -void -FHoudiniPDGManager::ReinitializePDGContext() -{ - int32 NumContexts = 0; - - PDGContextNames.SetNum(MaxNumberOPDGContexts); - PDGContextIDs.SetNum(MaxNumberOPDGContexts); - - if(HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContexts( - FHoudiniEngine::Get().GetSession(), - &NumContexts, PDGContextNames.GetData(), PDGContextIDs.GetData(), MaxNumberOPDGContexts) || NumContexts <= 0) - { - PDGContextNames.SetNum(0); - PDGContextIDs.SetNum(0); - return; - } - - if(PDGContextIDs.Num() != NumContexts) - PDGContextIDs.SetNum(NumContexts); - - if (PDGContextNames.Num() != NumContexts) - PDGContextNames.SetNum(NumContexts); -} - -// Process a PDG event. Notify the relevant PDGAssetLink object. -void -FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo) -{ - UHoudiniPDGAssetLink* PDGAssetLink = nullptr; - UTOPNetwork* TOPNetwork = nullptr; - UTOPNode* TOPNode = nullptr; - - HAPI_PDG_EventType EventType = (HAPI_PDG_EventType)EventInfo.eventType; - HAPI_PDG_WorkitemState CurrentWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.currentState; - HAPI_PDG_WorkitemState LastWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.lastState; - - // Debug: get the HAPI_PDG_EventType as a string - const FString EventName = FHoudiniEngineUtils::HapiGetEventTypeAsString(EventType); - const FString CurrentWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(CurrentWorkItemState); - const FString LastWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(LastWorkItemState); - - if(!GetTOPAssetLinkNetworkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNetwork, TOPNode) - || PDGAssetLink == nullptr || PDGAssetLink->IsPendingKill() - || TOPNetwork == nullptr || TOPNetwork->IsPendingKill() - || TOPNode == nullptr || TOPNode->IsPendingKill() - || TOPNode->NodeId != EventInfo.nodeId) - { - - HOUDINI_LOG_WARNING(TEXT("[ProcessPDGEvent]: Could not find matching TOPNode for event %s, workitem id %d, node id %d"), *EventName, EventInfo.workitemId, EventInfo.nodeId); - return; - } - - HOUDINI_PDG_MESSAGE( - TEXT("[ProcessPDGEvent]: TOPNode: %s, WorkItem ID: %d, Event Type: %s, Current State: %s, Last State %s"), - *(TOPNode->NodePath), EventInfo.workitemId, *EventName, *CurrentWorkitemStateName, *LastWorkitemStateName); - - FLinearColor MsgColor = FLinearColor::White; - - bool bUpdatePDGNodeState = false; - switch (EventType) - { - case HAPI_PDG_EVENT_NULL: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::None); - break; - - case HAPI_PDG_EVENT_NODE_CLEAR: - NotifyTOPNodePDGStateClear(PDGAssetLink, TOPNode); - break; - - case HAPI_PDG_EVENT_WORKITEM_ADD: - CreateOrRelinkWorkItem(TOPNode, InContextID, EventInfo.workitemId); - bUpdatePDGNodeState = true; - NotifyTOPNodeCreatedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - break; - - case HAPI_PDG_EVENT_WORKITEM_REMOVE: - RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, TOPNode); - bUpdatePDGNodeState = true; - NotifyTOPNodeRemovedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - break; - - case HAPI_PDG_EVENT_COOK_WARNING: - MsgColor = FLinearColor::Yellow; - break; - - case HAPI_PDG_EVENT_COOK_ERROR: - MsgColor = FLinearColor::Red; - break; - - case HAPI_PDG_EVENT_COOK_COMPLETE: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); - TOPNode->HandleOnPDGEventCookComplete(); - TOPNetwork->HandleOnPDGEventCookCompleteReceivedByChildNode(PDGAssetLink, TOPNode); - break; - - case HAPI_PDG_EVENT_DIRTY_START: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtying); - break; - - case HAPI_PDG_EVENT_DIRTY_STOP: - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtied); - break; - - case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: - { - // Last states - bUpdatePDGNodeState = true; - if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) - { - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) - { - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) - { - } - else if ( (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE || LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) - && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) - { - // Handled previously cooked WI - } - else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) - { - } - else - { - // TODO: - // unhandled state change - HOUDINI_PDG_WARNING(TEXT("Unhandled PDG state change! Node: %s, WorkItemID %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId); - } - - if (LastWorkItemState == CurrentWorkItemState) - { - // TODO: - // Not a change!! shouldnt happen! - HOUDINI_PDG_WARNING(TEXT("Last state == current state! Node: %s, WorkItemID %d, state %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId, EventInfo.lastState); - } - - // New states - if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) - { - NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNCOOKED) - { - - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_DIRTY) - { - // ClearWorkItemResult(InContextID, EventInfo, *TOPNode); - ClearWorkItemResult(PDGAssetLink, EventInfo.workitemId, TOPNode); - // RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, *TOPNode); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) - { - NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) - { - NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS - || CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE) - { - NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - - // On cook success, handle results - CreateOrRelinkWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) - { - // TODO: on cook failure, get log path? - NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - MsgColor = FLinearColor::Red; - } - else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CANCEL) - { - NotifyTOPNodeCookCancelledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); - } - } - break; - - case HAPI_PDG_EVENT_COOK_START: - TOPNode->HandleOnPDGEventCookStart(); - break; - // Unhandled events - case HAPI_PDG_EVENT_DIRTY_ALL: - case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: - case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: - case HAPI_PDG_EVENT_UI_SELECT: - case HAPI_PDG_EVENT_NODE_CREATE: - case HAPI_PDG_EVENT_NODE_REMOVE: - case HAPI_PDG_EVENT_NODE_RENAME: - case HAPI_PDG_EVENT_NODE_CONNECT: - case HAPI_PDG_EVENT_NODE_DISCONNECT: - case HAPI_PDG_EVENT_WORKITEM_SET_INT: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_STRING: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_FILE: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_RESULT: - case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED - case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: - case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: - case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: - case HAPI_PDG_EVENT_ALL: - case HAPI_PDG_EVENT_LOG: - case HAPI_PDG_CONTEXT_EVENTS: - break; - } - - if (bUpdatePDGNodeState) - { - // Work item events - EPDGNodeState CurrentTOPNodeState = TOPNode->NodeState; - if (CurrentTOPNodeState == EPDGNodeState::Cooking) - { - if (TOPNode->AreAllWorkItemsComplete()) - { - // At the end of a node/net cook, ensure that the work items are in sync with HAPI and remove any - // work items with invalid ids or that don't exist on the HAPI side anymore. - SyncAndPruneWorkItems(TOPNode); - // Check that all work items are still complete after the sync - if (TOPNode->AreAllWorkItemsComplete()) - { - if (TOPNode->AnyWorkItemsFailed()) - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); - } - else - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); - } - } - } - } - else if (TOPNode->AnyWorkItemsPending()) - { - SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cooking); - } - } - - if (EventInfo.msgSH >= 0) - { - FString EventMsg; - FHoudiniEngineString::ToFString(EventInfo.msgSH, EventMsg); - if (!EventMsg.IsEmpty()) - { - // TODO: Event MSG? - // Somehow update the PDG event msg UI ?? - // Simply log for now... - if (MsgColor == FLinearColor::Red) - { - HOUDINI_LOG_ERROR(TEXT("%s"), *EventMsg); - } - else if (MsgColor == FLinearColor::Yellow) - { - HOUDINI_LOG_WARNING(TEXT("%s"), *EventMsg); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT("%s"), *EventMsg); - } - } - } -} - -void -FHoudiniPDGManager::ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo) -{ - InEventInfo.nodeId = -1; - InEventInfo.workitemId = -1; - InEventInfo.dependencyId = -1; - InEventInfo.currentState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; - InEventInfo.lastState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; - InEventInfo.eventType = HAPI_PDG_EventType::HAPI_PDG_EVENT_NULL; -} - - -bool -FHoudiniPDGManager::GetTOPAssetLinkNetworkAndNode( - const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNetwork*& OutTOPNetwork, UTOPNode*& OutTOPNode) -{ - // Returns the PDGAssetLink and FTOPNode data associated with this TOP node ID - OutAssetLink = nullptr; - OutTOPNetwork = nullptr; - OutTOPNode = nullptr; - for (TWeakObjectPtr& CurAssetLinkPtr : PDGAssetLinks) - { - if (!CurAssetLinkPtr.IsValid() || CurAssetLinkPtr.IsStale()) - continue; - - UHoudiniPDGAssetLink* CurAssetLink = CurAssetLinkPtr.Get(); - if (!CurAssetLink || CurAssetLink->IsPendingKill()) - continue; - - if (CurAssetLink->GetTOPNodeAndNetworkByNodeId((int32)InNodeID, OutTOPNetwork, OutTOPNode)) - { - if (OutTOPNetwork != nullptr && OutTOPNode != nullptr) - { - OutAssetLink = CurAssetLink; - return true; - } - } - } - - OutAssetLink = nullptr; - OutTOPNetwork = nullptr; - OutTOPNode = nullptr; - - return false; -} - -void -FHoudiniPDGManager::SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->NodeState = InPDGState; - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode) -{ - if (!IsValid(InTOPNode)) - return; - - //Debug.LogFormat("NotifyTOPNodePDGStateClear:: {0}", topNode._nodeName); - InTOPNode->NodeState = EPDGNodeState::None; - InTOPNode->ZeroWorkItemTally(); - InTOPNode->OnDirtyNode(); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ZeroAll"), *(InTOPNode->NodePath)); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); - -} - -void -FHoudiniPDGManager::NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemCreated(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Created WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemRemoved(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Removed WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemCooked(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookedWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemErrored(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemWaiting(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemScheduled(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumScheduledWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemCooking(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookingWorkItems()); - - // InPDGAssetLink->bNeedsUIRefresh = true; - //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); -} - -void -FHoudiniPDGManager::NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) -{ - if (!IsValid(InTOPNode)) - return; - - InTOPNode->OnWorkItemCookCancelled(InWorkItemID); - - HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookCancelledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookCancelledWorkItems()); -} - -void -FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // TODO!!! - // Clear all work items' results for the specified TOP node. - // This destroys any loaded results (geometry etc). - //session.LogErrorOverride = false; - InAssetLink->ClearWorkItemResultByID(InWorkItemID, InTOPNode); - // session.LogErrorOverride = true; -} - -void -FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // Clear all of the work item's results for the specified TOP node and also remove the work item itself from - // the TOP node. - InAssetLink->DestroyWorkItemByID(InWorkItemID, InTOPNode); -} - -void -FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - // Only update the editor properties if the PDG asset link's Actor is selected - // else, just update the workitemtally - InAssetLink->UpdateWorkItemTally(); - - UHoudiniAssetComponent* HAC = Cast(InAssetLink->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor* ActorOwner = HAC->GetOwner(); - if (ActorOwner != nullptr && ActorOwner->IsSelected()) - { - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - } -} - -void -FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess) -{ - if (!InAssetLink || InAssetLink->IsPendingKill()) - return; - - if (bSuccess) - { - if (InAssetLink->LinkState == EPDGLinkState::Linked) - { - if (InAssetLink->bAutoCook) - { - FHoudiniPDGManager::CookOutput(InAssetLink->GetSelectedTOPNetwork()); - } - } - else - { - UpdatePDGAssetLink(InAssetLink); - } - } - else - { - InAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; - } -} - -int32 -FHoudiniPDGManager::CreateOrRelinkWorkItem( - UTOPNode* InTOPNode, - const HAPI_PDG_GraphContextId& InContextID, - HAPI_PDG_WorkitemId InWorkItemID) -{ - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); - return INDEX_NONE; - } - - HAPI_PDG_WorkitemInfo WorkItemInfo; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( - FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); - // TODO? continue? - return INDEX_NONE; - } - - // Try to find the existing WorkItem by ID. - int32 Index = InTOPNode->IndexOfWorkResultByID(InWorkItemID); - if (Index == INDEX_NONE) - { - // Try to find an entry with WorkItemID == INDEX_NONE but where workItemIndex matches. The WorkItemIDs are - // transient, so not saved when the map / asset link is saved. So when loading a map containing the asset - // link all the IDs are INDEX_NONE and the WorkItemIndex is the best way to find an entry to re-use. - const bool bWithInvalidWorkItemID = true; - Index = InTOPNode->IndexOfWorkResultByHAPIIndex(WorkItemInfo.index, bWithInvalidWorkItemID); - if (Index == INDEX_NONE) - { - // If we couldn't find an existing entry, or a stale entry to re-use, create a new one - FTOPWorkResult LocalWorkResult; - LocalWorkResult.WorkItemID = InWorkItemID; - LocalWorkResult.WorkItemIndex = WorkItemInfo.index; - Index = InTOPNode->WorkResult.Add(LocalWorkResult); - } - else - { - InTOPNode->WorkResult[Index].WorkItemID = InWorkItemID; - } - } - - return Index; -} - -bool -FHoudiniPDGManager::CreateOrRelinkWorkItemResult( - UTOPNode* InTOPNode, - const HAPI_PDG_GraphContextId& InContextID, - HAPI_PDG_WorkitemId InWorkItemID, - bool bInLoadResultObjects) -{ - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); - return false; - } - - HAPI_PDG_WorkitemInfo WorkItemInfo; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( - FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); - // TODO? continue? - return false; - } - - // Try to find the existing WorkResult by ID. - FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); - if (!WorkResult) - { - // TODO: This shouldn't really happen, it means a work item finished cooking and generated a result before - // we received an event that the work item was added/generated. - int32 ArrayIndex = CreateOrRelinkWorkItem(InTOPNode, InContextID, InWorkItemID); - if (ArrayIndex != INDEX_NONE) - { - WorkResult = InTOPNode->GetWorkResultByArrayIndex(ArrayIndex); - } - } - - if (!WorkResult) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get or add a FTOPWorkResult for WorkItemID %d for %s"), InWorkItemID, *(InTOPNode->NodeName)); - return false; - } - - TArray NewResultObjects; - TSet ResultIndicesThatWereReused; - if (WorkItemInfo.numResults > 0) - { - TArray ResultInfos; - ResultInfos.SetNum(WorkItemInfo.numResults); - const int32 resultCount = WorkItemInfo.numResults; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemResultInfo( - FHoudiniEngine::Get().GetSession(), - InTOPNode->NodeId, InWorkItemID, ResultInfos.GetData(), resultCount)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d result info for %s"), InWorkItemID, *(InTOPNode->NodeName)); - // TODO? continue? - return false; - } - - FString WorkItemName; - FHoudiniEngineString::ToFString(WorkItemInfo.nameSH, WorkItemName); - - // Load each result geometry - const int32 NumResults = ResultInfos.Num(); - for (int32 Idx = 0; Idx < NumResults; Idx++) - { - const HAPI_PDG_WorkitemResultInfo& ResultInfo = ResultInfos[Idx]; - if (ResultInfo.resultTagSH <= 0 || ResultInfo.resultSH <= 0) - continue; - - FString CurrentTag; - FHoudiniEngineString::ToFString(ResultInfo.resultTagSH, CurrentTag); - if(CurrentTag.IsEmpty() || !CurrentTag.StartsWith(TEXT("file"))) - continue; - - FString CurrentPath = FString(); - FHoudiniEngineString::ToFString(ResultInfo.resultSH, CurrentPath); - - // Construct the name and look for an existing work result, re-use it if found, otherwise create a new one - const FString WorkResultName = FString::Printf( - TEXT("%s_%s_%d_%d"), - *(InTOPNode->ParentName), - *WorkItemName, - WorkItemInfo.index, - Idx); - - // int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) - // { - // return InResultObject.Name == WorkResultName; - // }); - int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([Idx](const FTOPWorkResultObject& InResultObject) - { - return InResultObject.WorkItemResultInfoIndex == Idx; - }); - if (WorkResult->ResultObjects.IsValidIndex(ExistingObjectIndex)) - { - FTOPWorkResultObject& ExistingResultObject = WorkResult->ResultObjects[ExistingObjectIndex]; - - ExistingResultObject.Name = WorkResultName; - ExistingResultObject.FilePath = CurrentPath; - ExistingResultObject.SetAutoBakedSinceLastLoad(false); - if (ExistingResultObject.State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) - { - ExistingResultObject.State = EPDGWorkResultState::ToDelete; - } - else - { - // When the commandlet is not being used, we could leave the outputs in place and have - // translators try to re-use objects/components. When the commandlet is being used, the packages - // are always saved and standalone, so if we want to automatically clean up old results then we - // need to destroy the existing outputs - if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - ExistingResultObject.DestroyResultOutputs(); - - if ((ExistingResultObject.State == EPDGWorkResultState::Loaded || - ExistingResultObject.State == EPDGWorkResultState::ToDelete || - ExistingResultObject.State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) - { - ExistingResultObject.State = EPDGWorkResultState::ToLoad; - } - else - { - ExistingResultObject.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; - } - } - - NewResultObjects.Add(ExistingResultObject); - ResultIndicesThatWereReused.Add(ExistingObjectIndex); - } - else - { - FTOPWorkResultObject ResultObj; - ResultObj.Name = WorkResultName; - ResultObj.FilePath = CurrentPath; - ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; - ResultObj.WorkItemResultInfoIndex = Idx; - ResultObj.SetAutoBakedSinceLastLoad(false); - - NewResultObjects.Add(ResultObj); - } - } - } - // Destroy any old ResultObjects that were not re-used - const int32 NumPrevResultObjects = WorkResult->ResultObjects.Num(); - for (int32 ResultObjectIndex = 0; ResultObjectIndex < NumPrevResultObjects; ++ResultObjectIndex) - { - if (ResultIndicesThatWereReused.Contains(ResultObjectIndex)) - continue; - FTOPWorkResultObject& ResultObject = WorkResult->ResultObjects[ResultObjectIndex]; - ResultObject.DestroyResultOutputsAndRemoveOutputActor(); - } - WorkResult->ResultObjects = NewResultObjects; - - return true; -} - -int32 -FHoudiniPDGManager::SyncAndPruneWorkItems(UTOPNode* InTOPNode) -{ - TSet WorkItemIDSet; - TArray WorkItemIDs; - int NumWorkItems = -1; - - if (!IsValid(InTOPNode)) - return -1; - - HAPI_Session const * const HAPISession = FHoudiniEngine::Get().GetSession(); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNumWorkitems(HAPISession, InTOPNode->NodeId, &NumWorkItems)) - { - HOUDINI_LOG_WARNING(TEXT("GetNumWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); - return -1; - } - - WorkItemIDs.SetNum(NumWorkItems); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitems(HAPISession, InTOPNode->NodeId, WorkItemIDs.GetData(), NumWorkItems)) - { - HOUDINI_LOG_WARNING(TEXT("GetWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); - return -1; - } - - HAPI_PDG_GraphContextId ContextId; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId(HAPISession, InTOPNode->NodeId, &ContextId)) - { - HOUDINI_LOG_WARNING(TEXT("GetPDGGraphContextId call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); - return -1; - } - - for (const HAPI_PDG_WorkitemId& WorkItemID : WorkItemIDs) - { - WorkItemIDSet.Add(static_cast(WorkItemID)); - - // If the WorkItemID is not present in FTOPWorkResult array, sync from HAPI - if (InTOPNode->GetWorkResultByID(WorkItemID) == nullptr) - { - CreateOrRelinkWorkItemResult(InTOPNode, ContextId, WorkItemID); - InTOPNode->OnWorkItemCreated(WorkItemID); - } - } - - // TODO: refactor functions that access the TOPNode's array and properties directly to rather be functions on the - // UTOPNode and make access to these arrays protected/private - - // Remove any work result entries with invalid IDs or where the WorkItemID is not in the set of ids returned by - // HAPI (only if we could get the IDs from HAPI). - int32 NumRemoved = 0; - const int32 NumWorkItemsInArray = InTOPNode->WorkResult.Num(); - for (int32 Index = NumWorkItemsInArray - 1; Index >= 0; --Index) - { - FTOPWorkResult& WorkResult = InTOPNode->WorkResult[Index]; - if (WorkResult.WorkItemID == INDEX_NONE || !WorkItemIDSet.Contains(WorkResult.WorkItemID)) - { - HOUDINI_PDG_WARNING( - TEXT("Pruning a FTOPWorkResult entry from TOP Node %d, WorkItemID %d, WorkItemIndex %d, Array Index %d"), - InTOPNode->NodeId, WorkResult.WorkItemID, WorkResult.WorkItemIndex, Index); - WorkResult.ClearAndDestroyResultObjects(); - InTOPNode->WorkResult.RemoveAt(Index); - InTOPNode->OnWorkItemRemoved(WorkResult.WorkItemID); - NumRemoved++; - } - } - - return NumRemoved; -} - -void -FHoudiniPDGManager::ProcessWorkItemResults() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::ProcessWorkItemResults); - - const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); - for (auto& CurrentPDGAssetLink : PDGAssetLinks) - { - // Iterate through all PDG Asset Link - UHoudiniPDGAssetLink* AssetLink = CurrentPDGAssetLink.Get(); - if (!AssetLink) - continue; - - // Set up package parameters to: - // Cook to temp houdini engine directory - // and if the PDG asset link is associated with a Houdini Asset Component (HAC): - // set the outer package to the HAC - // set the HoudiniAssetName according to the HAC - // set the ComponentGUID according to the HAC - // otherwise we set the outer to the asset link's parent and leave naming and GUID blank - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); - PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); - - PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - // AActor* ParentActor = nullptr; - UObject* AssetLinkParent = AssetLink->GetOuter(); - UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; - if (HAC) - { - PackageParams.OuterPackage = HAC->GetComponentLevel(); - PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); - PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); - PackageParams.ComponentGUID = HAC->GetComponentGUID(); - - // ParentActor = HAC->GetOwner(); - } - else - { - PackageParams.OuterPackage = AssetLinkParent ? AssetLinkParent->GetOutermost() : nullptr; - PackageParams.HoudiniAssetName = FString(); - PackageParams.HoudiniAssetActorName = FString(); - // PackageParams.ComponentGUID = HAC->GetComponentGUID(); - - // // Try to find a parent actor - // UObject* Parent = AssetLinkParent; - // while (Parent && !ParentActor) - // { - // ParentActor = Cast(Parent); - // if (!ParentActor) - // Parent = ParentActor->GetOuter(); - // } - } - PackageParams.ObjectName = FString(); - - // UWorld *World = ParentActor ? ParentActor->GetWorld() : AssetLink->GetWorld(); - UWorld *World = AssetLink->GetWorld(); - - // .. All TOP Nets - for (UTOPNetwork* CurrentTOPNet : AssetLink->AllTOPNetworks) - { - if (!IsValid(CurrentTOPNet)) - continue; - - // .. All TOP Nodes - for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) - { - if (!IsValid(CurrentTOPNode)) - continue; - - // ... All WorkResult - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; - CurrentTOPNode->bCachedHaveLoadedWorkResults = false; - for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) - { - // ... All WorkResultObjects - for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) - { - if (CurrentWorkResultObj.State == EPDGWorkResultState::ToLoad) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Loading; - - // Load this WRObj - PackageParams.PDGTOPNetworkName = CurrentTOPNet->NodeName; - PackageParams.PDGTOPNodeName = CurrentTOPNode->NodeName; - PackageParams.PDGWorkItemIndex = CurrentWorkResult.WorkItemIndex; - - if (CommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - { - BGEOCommandletEndpoint->Send(new FHoudiniPDGImportBGEOMessage( - CurrentWorkResultObj.FilePath, - CurrentWorkResultObj.Name, - PackageParams, - CurrentTOPNode->NodeId, - CurrentWorkResult.WorkItemID - ), BGEOCommandletAddress); - } - else - { - if (FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - AssetLink, - CurrentTOPNode, - CurrentWorkResultObj, - PackageParams)) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Loaded; - CurrentWorkResultObj.SetAutoBakedSinceLastLoad(false); - CurrentTOPNode->bCachedHaveLoadedWorkResults = true; - - // Broadcast that we have loaded the work result object to those interested - AssetLink->OnWorkResultObjectLoaded.Broadcast( - AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemIndex, - CurrentWorkResultObj.WorkItemResultInfoIndex); - } - else - { - CurrentWorkResultObj.State = EPDGWorkResultState::None; - } - } - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::Loaded) - { - // If the work item result obj is in the "Loaded" state, confirm that the output actor - // is still valid (the user could have manually deleted the output - if (!IsValid(CurrentWorkResultObj.GetOutputActorOwner().GetOutputActor())) - { - // If the output actor is invalid, set the state to ToDelete to complete the - // unload/deletion process - CurrentWorkResultObj.State = EPDGWorkResultState::ToDelete; - } - else - { - CurrentTOPNode->bCachedHaveLoadedWorkResults = true; - } - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::ToDelete) - { - CurrentWorkResultObj.State = EPDGWorkResultState::Deleting; - - // Delete and clean up that WRObj - CurrentWorkResultObj.DestroyResultOutputs(); - CurrentWorkResultObj.GetOutputActorOwner().DestroyOutputActor(); - CurrentWorkResultObj.State = EPDGWorkResultState::Deleted; - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::Deleted) - { - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - else if (CurrentWorkResultObj.State == EPDGWorkResultState::NotLoaded) - { - CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; - } - } - } - } - } - } -} - -void FHoudiniPDGManager::HandleImportBGEODiscoverMessage( - const FHoudiniPDGImportBGEODiscoverMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_DISPLAY(TEXT("Received Discover from %s"), *InContext->GetSender().ToString()); - // Ignore any discover acks received if we already have a valid local address - // for the commandlet - if (BGEOCommandletAddress.IsValid()) - return; - - if (BGEOCommandletProcHandle.IsValid() && InMessage.CommandletGuid.IsValid() && BGEOCommandletGuid == InMessage.CommandletGuid) - { - BGEOCommandletAddress = InContext->GetSender(); - } -} - -void FHoudiniPDGManager::HandleImportBGEOResultMessage( - const FHoudiniPDGImportBGEOResultMessage& InMessage, - const TSharedRef& InContext) -{ - HOUDINI_LOG_MESSAGE(TEXT("Received BGEO import result message")); - if (InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_Success || InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_PartialSuccess) - { - FHoudiniPackageParams PackageParams; - InMessage.PopulatePackageParams(PackageParams); - - // Find asset link and work result object - UHoudiniPDGAssetLink *AssetLink = nullptr; - UTOPNetwork *TOPNetwork = nullptr; - UTOPNode *TOPNode = nullptr; - if (!GetTOPAssetLinkNetworkAndNode(InMessage.TOPNodeId, AssetLink, TOPNetwork, TOPNode) || - !IsValid(AssetLink) || !IsValid(TOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP node with id %d, aborting output object creation."), InMessage.TOPNodeId); - return; - } - - FTOPWorkResult* WorkResult = AssetLink->GetWorkResultByID(InMessage.WorkItemId, TOPNode); - if (WorkResult == nullptr) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result with id %d, aborting output object creation."), InMessage.WorkItemId); - return; - } - const FString& WorkResultObjectName = InMessage.Name; - FTOPWorkResultObject* WorkResultObject = WorkResult->ResultObjects.FindByPredicate( - [&WorkResultObjectName](const FTOPWorkResultObject& WorkResultObject) - { - return WorkResultObject.Name == WorkResultObjectName; - } - ); - if (WorkResultObject == nullptr) - { - HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result object with name %s, aborting output object creation."), *InMessage.Name); - return; - } - - if (WorkResultObject->State != EPDGWorkResultState::Loading) - { - HOUDINI_LOG_WARNING(TEXT("TOP work result object (%s) not in Loading state, aborting output object creation."), *InMessage.Name); - return; - } - - // Set package params outer - UObject* AssetLinkParent = AssetLink->GetOuter(); - UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; - if (HAC) - { - PackageParams.OuterPackage = HAC->GetComponentLevel(); - } - else - { - PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); - } - - // Construct UHoudiniOutputs - bool bHasUnsupportedOutputs = false; - TArray NewOutputs; - TMap InstancedOutputPartData; - NewOutputs.Reserve(InMessage.Outputs.Num()); - for (const FHoudiniPDGImportNodeOutput& Output : InMessage.Outputs) - { - UHoudiniOutput* NewOutput = NewObject( - AssetLink, - UHoudiniOutput::StaticClass(), - NAME_None,//FName(*OutputName), - RF_NoFlags); - NewOutputs.Add(NewOutput); - const int32 NumHGPO = Output.HoudiniGeoPartObjects.Num(); - for (int32 Index = 0; Index < NumHGPO; ++Index) - { - const FHoudiniGeoPartObject& HGPO = Output.HoudiniGeoPartObjects[Index]; - NewOutput->AddNewHGPO(HGPO); - - if (Output.InstancedOutputPartData.IsValidIndex(Index)) - { - FHoudiniOutputObjectIdentifier Identifier; - Identifier.ObjectId = HGPO.ObjectId; - Identifier.GeoId = HGPO.GeoId; - Identifier.PartId = HGPO.PartId; - Identifier.PartName = HGPO.PartName; - FHoudiniInstancedOutputPartData InstancedPartData = Output.InstancedOutputPartData[Index]; - InstancedPartData.BuildOriginalInstancedTransformsAndObjectArrays(); - InstancedOutputPartData.Add(Identifier, InstancedPartData); - } - } - const int32 NumObjects = Output.OutputObjects.Num(); - for (int32 Index = 0; Index < NumObjects; ++Index) - { - const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; - FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; - - const FString& FullPackagePath = ImportOutputObject.PackagePath; - FString PackagePath; - FString PackageName; - const bool bDidSplit = FullPackagePath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); - if (!bDidSplit) - PackagePath = FullPackagePath; - - FHoudiniOutputObject OutputObject; - UPackage* Package = FindPackage(nullptr, *PackagePath); - if (!IsValid(Package)) - { - // Editor might have picked up the package yet, try to load it - Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); - } - if (IsValid(Package)) - { - OutputObject.OutputObject = FindObject(Package, *PackageName); - } - Identifier.bLoaded = true; - NewOutput->GetOutputObjects().Add(Identifier, OutputObject); - } - NewOutput->UpdateOutputType(); - const EHoudiniOutputType OutputType = NewOutput->GetType(); - if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) - { - bHasUnsupportedOutputs = true; - } - } - - bool bSuccess = true; - if (bHasUnsupportedOutputs) - { - HOUDINI_LOG_MESSAGE(TEXT("Processing output types not supported by commandlet for %s"), *InMessage.Name); - bSuccess = FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - AssetLink, TOPNode, *WorkResultObject, PackageParams, - { - EHoudiniOutputType::Landscape, - EHoudiniOutputType::Curve, - EHoudiniOutputType::Skeletal - } - ); - - if (bSuccess) - { - // Clear/remove the outputs on WorkResultObject that are supported by the commandlet, since we - // are going to replace them with NewOutputs now - TArray& CurrentOutputs = WorkResultObject->GetResultOutputs(); - const int32 NumCurrentOutputs = CurrentOutputs.Num(); - for (int32 Index = 0; Index < NumCurrentOutputs; ++Index) - { - UHoudiniOutput* CurOutput = CurrentOutputs[Index]; - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) - { - // Was created in editor, override the dummy one in NewOutputs with CurOutput - if (NewOutputs.IsValidIndex(Index)) - { - if (OutputType == NewOutputs[Index]->GetType()) - { - UHoudiniOutput* TempOutput = NewOutputs[Index]; - FHoudiniOutputTranslator::ClearOutput(TempOutput); - NewOutputs[Index] = CurOutput; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Unexpected commandlet output type at index %d!"), Index); - } - } - else - { - HOUDINI_LOG_ERROR(TEXT("Expected output index %d from commandlet to be exist!"), Index); - } - } - } - } - } - - if (bSuccess && FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( - AssetLink, - TOPNode, - *WorkResultObject, - PackageParams, - NewOutputs, - {EHoudiniOutputType::Mesh, EHoudiniOutputType::Instancer}, - &InstancedOutputPartData)) - { - const int32 NumOutputs = NewOutputs.Num(); - for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex) - { - UHoudiniOutput *NewOutput = NewOutputs[OutputIndex]; - - if (NewOutput->GetType() != EHoudiniOutputType::Mesh) - continue; - - const FHoudiniPDGImportNodeOutput& Output = InMessage.Outputs[OutputIndex]; - int32 NumObjects = Output.OutputObjects.Num(); - for (int32 Index = 0; Index < NumObjects; ++Index) - { - const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; - FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; - FHoudiniOutputObject *OutputObject = NewOutput->GetOutputObjects().Find(Identifier); - if (OutputObject) - { - if (IsValid(OutputObject->OutputComponent)) - { - // Update generic property attributes - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( - OutputObject->OutputComponent, - ImportOutputObject.GenericAttributes.PropertyAttributes); - } - - // Copy cached attributes - OutputObject->CachedAttributes.Append(ImportOutputObject.CachedAttributes); - } - } - } - } - else - { - bSuccess = false; - } - - if (bSuccess) - { - WorkResultObject->State = EPDGWorkResultState::Loaded; - WorkResultObject->SetAutoBakedSinceLastLoad(false); - HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); - // Broadcast that we have loaded the work result object to those interested - AssetLink->OnWorkResultObjectLoaded.Broadcast( - AssetLink, TOPNode, WorkResult->WorkItemIndex, WorkResultObject->WorkItemResultInfoIndex); - } - else - { - WorkResultObject->State = EPDGWorkResultState::None; - HOUDINI_LOG_WARNING(TEXT("Failed to process loaded assets for %s"), *InMessage.Name); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Commandlet failed to import bgeo for %s"), *InMessage.Name); - } -} - -bool FHoudiniPDGManager::CreateBGEOCommandletAndEndpoint() -{ - if (!BGEOCommandletEndpoint.IsValid()) - { - BGEOCommandletAddress.Invalidate(); - BGEOCommandletEndpoint = FMessageEndpoint::Builder(TEXT("Houdini BGEO Commandlet")) - .Handling(this, &FHoudiniPDGManager::HandleImportBGEOResultMessage) - .Handling(this, &FHoudiniPDGManager::HandleImportBGEODiscoverMessage) - .ReceivingOnThread(ENamedThreads::GameThread); - - if (!BGEOCommandletEndpoint.IsValid()) - { - HOUDINI_LOG_WARNING(TEXT("Could not set up messaging end point for BGEO commandlet")); - return false; - } - - BGEOCommandletEndpoint->Subscribe(); - } - - if (!BGEOCommandletProcHandle.IsValid() || !FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - { - // Start the bgeo commandlet - static const FString BGEOCommandletName = TEXT("HoudiniGeoImport"); - BGEOCommandletGuid = FGuid::NewGuid(); - BGEOCommandletAddress.Invalidate(); - - // Get the absolute path to the project file, if known, otherwise get - // the project name. For the path: quote it for the command line. - IFileManager& FileManager = IFileManager::Get(); - FString ProjectPathOrName = FApp::GetProjectName(); - if (FPaths::IsProjectFilePathSet()) - { - const FString ProjectPath = FPaths::GetProjectFilePath(); - if (!ProjectPath.IsEmpty()) - { - ProjectPathOrName = FString::Printf( - TEXT("\"%s\""), - *FileManager.ConvertToAbsolutePathForExternalAppForRead(*ProjectPath) - ); - } - } - - if (ProjectPathOrName.IsEmpty()) - return false; - - // Get the executable path for the app/editor - FString ExePath = FPlatformProcess::GenerateApplicationPath(FApp::GetName(), FApp::GetBuildConfiguration()); - if (!ExePath.IsEmpty()) - ExePath = FileManager.ConvertToAbsolutePathForExternalAppForRead(*ExePath); - - if (ExePath.IsEmpty()) - return false; - - const FString CommandLineParameters = FString::Printf( - TEXT("%s -messaging -run=%s -guid=%s -listen=%s -managerpid=%d"), - *ProjectPathOrName, - *BGEOCommandletName, - *BGEOCommandletGuid.ToString(), - *BGEOCommandletEndpoint->GetAddress().ToString(), - FPlatformProcess::GetCurrentProcessId()); - - BGEOCommandletProcHandle = FPlatformProcess::CreateProc( - *ExePath, - *CommandLineParameters, - false, - true, - false, - &BGEOCommandletProcessId, - 0, - NULL, - NULL); - if (!BGEOCommandletProcHandle.IsValid()) - { - return false; - } - } - - return true; -} - -void FHoudiniPDGManager::StopBGEOCommandletAndEndpoint() -{ - BGEOCommandletEndpoint.Reset(); - BGEOCommandletAddress.Invalidate(); - BGEOCommandletGuid.Invalidate(); - - if (BGEOCommandletProcHandle.IsValid() && FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - { - FPlatformProcess::TerminateProc(BGEOCommandletProcHandle, true); - if (BGEOCommandletProcHandle.IsValid()) - { - FPlatformProcess::WaitForProc(BGEOCommandletProcHandle); - FPlatformProcess::CloseProc(BGEOCommandletProcHandle); - } - } -} - -EHoudiniBGEOCommandletStatus FHoudiniPDGManager::UpdateAndGetBGEOCommandletStatus() -{ - if (BGEOCommandletProcHandle.IsValid()) - { - if (!FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Crashed; - else if (BGEOCommandletAddress.IsValid()) - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Connected; - else - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Running; - } - else - BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::NotStarted; - - return BGEOCommandletStatus; -} - - -bool -FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) -{ - if (InAssetId < 0) - return false; - - // Get all the network nodes within the asset, recursively. - // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type - int32 NetworkNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); - - if (NetworkNodeCount <= 0) - return false; - - TArray AllNetworkNodeIDs; - AllNetworkNodeIDs.SetNum(NetworkNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); - - // There is currently no way to only get non bypassed nodes via HAPI - // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... - TArray AllBypassedTOPNetNodeIDs; - { - int32 BypassedTOPNetNodeCount = 0; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); - - if (BypassedTOPNetNodeCount > 0) - { - // Get the list of all bypassed TOP Net... - AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), InAssetId, - AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); - - // ... and remove them from the network list - for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) - { - if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) - AllNetworkNodeIDs.RemoveAt(Idx); - } - } - } - - // For each Network we found earlier, only consider those with TOP child nodes - // If we find TOP nodes in a valid network, then consider this HDA a PDG HDA - for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) - { - if (CurrentNodeId < 0) - { - continue; - } - - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) - { - continue; - } - - // Skip non TOP or SOP networks - if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP - && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) - { - continue; - } - - // Get the list of all TOP nodes within the current network (ignoring schedulers) - int32 TOPNodeCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), CurrentNodeId, - HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) - { - continue; - } - - // We found valid TOP Nodes, this is a PDG HDA - if (TOPNodeCount > 0) - return true; - } - - // No valid TOP node found in SOP/TOP Networks, this is not a PDG HDA - return false; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGManager.h" + +#include "Modules/ModuleManager.h" +#include "MessageEndpointBuilder.h" +#include "HAL/FileManager.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPackageParams.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniPDGTranslator.h" +#include "HoudiniPDGImporterMessages.h" + +#include "HAPI/HAPI_Common.h" + +HOUDINI_PDG_DEFINE_LOG_CATEGORY(); + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniPDGManager::FHoudiniPDGManager() +{ +} + +FHoudiniPDGManager::~FHoudiniPDGManager() +{ +} + +bool +FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) +{ + if (!InHAC || InHAC->IsPendingKill()) + return false; + + int32 AssetId = InHAC->GetAssetId(); + if (AssetId < 0) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) + return false; + + // Create a new PDG Asset Link Object + bool bRegisterPDGAssetLink = false; + UHoudiniPDGAssetLink* PDGAssetLink = InHAC->GetPDGAssetLink(); + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + { + PDGAssetLink = NewObject(InHAC, UHoudiniPDGAssetLink::StaticClass(), NAME_None, RF_Transactional); + bRegisterPDGAssetLink = true; + } + + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + PDGAssetLink->AssetID = AssetId; + + // Get the HDA's info + HAPI_NodeInfo AssetInfo; + FHoudiniApi::NodeInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, &AssetInfo), false); + + // Get the node path + FString AssetNodePath; + FHoudiniEngineString::ToFString(AssetInfo.internalNodePathSH, PDGAssetLink->AssetNodePath); + + // Get the node name + FString AssetName; + FHoudiniEngineString::ToFString(AssetInfo.nameSH, PDGAssetLink->AssetName); + + const bool bZeroWorkItemTallys = true; + if (!FHoudiniPDGManager::PopulateTOPNetworks(PDGAssetLink, bZeroWorkItemTallys)) + { + // We couldn't find any valid TOPNet/TOPNode, this is not a PDG Asset + // Make sure the HDA doesn't have a PDGAssetLink + InHAC->SetPDGAssetLink(nullptr); + return false; + } + + // If the PDG asset link comes from a loaded asset, we also need to register it + if (InHAC->HasBeenLoaded()) + { + bRegisterPDGAssetLink = true; + } + + // We have found valid TOPNetworks and TOPNodes, + // This is a PDG HDA, so add the AssetLink to it + PDGAssetLink->LinkState = EPDGLinkState::Linked; + + if (PDGAssetLink->SelectedTOPNetworkIndex < 0) + PDGAssetLink->SelectedTOPNetworkIndex = 0; + + InHAC->SetPDGAssetLink(PDGAssetLink); + + if (bRegisterPDGAssetLink) + { + // Register this PDG Asset Link to the PDG Manager + TWeakObjectPtr AssetLinkPtr(PDGAssetLink); + PDGAssetLinks.Add(AssetLinkPtr); + } + + // If the commandlet is enabled, check if we have started and established communication with the commandlet yet + // if not, try to start the commandlet + bool bCommandletIsEnabled = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (IsValid(HoudiniRuntimeSettings)) + { + bCommandletIsEnabled = HoudiniRuntimeSettings->bPDGAsyncCommandletImportEnabled; + } + + if (bCommandletIsEnabled) + { + const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); + if (CommandletStatus == EHoudiniBGEOCommandletStatus::NotStarted && bCommandletIsEnabled) + { + CreateBGEOCommandletAndEndpoint(); + } + } + + return true; +} + +bool +FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) +{ + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + // If the PDG Asset link is inactive, indicate that our HDA must be instantiated + if (PDGAssetLink->LinkState == EPDGLinkState::Inactive) + { + UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); + if(!ParentHAC) + { + // No valid parent HAC, error! + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("No valid Houdini Asset Component parent for PDG Asset Link!")); + } + else if (ParentHAC && ParentHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) + { + PDGAssetLink->LinkState = EPDGLinkState::Linking; + ParentHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + } + else + { + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("Unable to link the PDG Asset link! Try to rebuild the HDA.")); + } + } + + if (PDGAssetLink->LinkState == EPDGLinkState::Linking) + { + return true; + } + + if (PDGAssetLink->LinkState != EPDGLinkState::Linked) + { + UHoudiniAssetComponent* ParentHAC = Cast(PDGAssetLink->GetOuter()); + int32 AssetId = ParentHAC->GetAssetId(); + if (AssetId < 0) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid((HAPI_NodeId)AssetId)) + return false; + + PDGAssetLink->AssetID = AssetId; + } + + if(!PopulateTOPNetworks(PDGAssetLink)) + { + PDGAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + HOUDINI_LOG_ERROR(TEXT("Failed to populte the PDG Asset Link.")); + return false; + } + + return true; +} + + +bool +FHoudiniPDGManager::PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys) +{ + // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. + if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + return false; + + // Get all the network nodes within the asset, recursively. + // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type + int32 NetworkNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); + + if (NetworkNodeCount <= 0) + return false; + + TArray AllNetworkNodeIDs; + AllNetworkNodeIDs.SetNum(NetworkNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); + + // There is currently no way to only get non bypassed nodes via HAPI + // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... + TArray AllBypassedTOPNetNodeIDs; + { + int32 BypassedTOPNetNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); + + if (BypassedTOPNetNodeCount > 0) + { + // Get the list of all bypassed TOP Net... + AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), (HAPI_NodeId)PDGAssetLink->AssetID, + AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); + + // ... and remove them from the network list + for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) + AllNetworkNodeIDs.RemoveAt(Idx); + } + } + } + + + // For each Network we found earlier, only add those with TOP child nodes + // Therefore guaranteeing that we only add TOP networks + TArray AllTOPNetworks; + for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) + { + if (CurrentNodeId < 0) + { + continue; + } + + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) + { + continue; + } + + // Skip non TOP or SOP networks + if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP + && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) + { + continue; + } + + // Check that this TOP Net is not nested in another TOP Net... + // This will happen with ROP Geometry TOPs for example... + bool bIsNestedInTOPNet = false; + HAPI_NodeId CurrentParentId = CurrentNodeInfo.parentId; + while (CurrentParentId > 0) + { + if (AllNetworkNodeIDs.Contains(CurrentParentId)) + { + bIsNestedInTOPNet = true; + break; + } + + if(AllBypassedTOPNetNodeIDs.Contains(CurrentParentId)) + { + bIsNestedInTOPNet = true; + break; + } + + HAPI_NodeInfo ParentNodeInfo; + FHoudiniApi::NodeInfo_Init(&ParentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentParentId, &ParentNodeInfo)) + { + break; + } + + // Get our parent's parent + CurrentParentId = ParentNodeInfo.parentId; + } + + // Skip nested TOP nets + if (bIsNestedInTOPNet) + continue; + + // Get the list of all TOP nodes within the current network (ignoring schedulers) + int32 TOPNodeCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) + { + continue; + } + + TArray AllTOPNodeIDs; + AllTOPNodeIDs.SetNum(TOPNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, AllTOPNodeIDs.GetData(), TOPNodeCount), false); + + // Skip networks without TOP nodes + if (AllTOPNodeIDs.Num() <= 0) + { + continue; + } + + // Since there is currently no way to get only non-bypassed nodes via HAPI + // we need to get a list of all the bypassed top nodes to remove them from the previous list + { + int32 BypassedTOPNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_TOP_NONSCHEDULER | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNodeCount), false); + + if (BypassedTOPNodeCount > 0) + { + // Get the list of all bypassed TOP Nodes... + TArray AllBypassedTOPNodes; + AllBypassedTOPNodes.SetNum(BypassedTOPNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + AllBypassedTOPNodes.GetData(), BypassedTOPNodeCount), false); + + // ... and remove them from the top node list + for (int32 Idx = AllTOPNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNodes.Contains(AllTOPNodeIDs[Idx])) + AllTOPNodeIDs.RemoveAt(Idx); + } + } + } + + // TODO: + // Apply the show and output filter on that node + bool bShow = true; + + // Get the node path + FString CurrentNodePath; + FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, PDGAssetLink->AssetID, CurrentNodePath); + + // Get the node name + FString CurrentNodeName; + FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, CurrentNodeName); + + UTOPNetwork* CurrentTOPNetwork = nullptr; + int32 FoundTOPNetIndex = INDEX_NONE; + UTOPNetwork* FoundTOPNet = PDGAssetLink->GetTOPNetworkByNodePath(CurrentNodeName, PDGAssetLink->AllTOPNetworks, FoundTOPNetIndex); + if (IsValid(FoundTOPNet)) + { + // Reuse the existing corresponding TOP NET + CurrentTOPNetwork = FoundTOPNet; + PDGAssetLink->AllTOPNetworks.RemoveAt(FoundTOPNetIndex); + } + else + { + // Create a new instance for the TOP NET + CurrentTOPNetwork = NewObject(PDGAssetLink, UTOPNetwork::StaticClass(), NAME_None, RF_Transactional); + } + + // Update the TOP NET + CurrentTOPNetwork->NodeId = CurrentNodeId; + CurrentTOPNetwork->NodeName = CurrentNodeName; + CurrentTOPNetwork->NodePath = CurrentNodePath; + CurrentTOPNetwork->ParentName = PDGAssetLink->AssetName; + CurrentTOPNetwork->bShowResults = bShow; + + // Only add network that have valid TOP Nodes + if (PopulateTOPNodes(AllTOPNodeIDs, CurrentTOPNetwork, PDGAssetLink, bInZeroWorkItemTallys)) + { + // See if we need to select a new TOP node + bool bReselectValidTOP = false; + if (CurrentTOPNetwork->SelectedTOPIndex < 0) + bReselectValidTOP = true; + else if (!CurrentTOPNetwork->AllTOPNodes.IsValidIndex(CurrentTOPNetwork->SelectedTOPIndex)) + bReselectValidTOP = true; + else if (!IsValid(CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]) || + CurrentTOPNetwork->AllTOPNodes[CurrentTOPNetwork->SelectedTOPIndex]->bHidden) + bReselectValidTOP = true; + + if (bReselectValidTOP) + { + // Select the first valid TOP node (not hidden) + for (int Idx = 0; Idx < CurrentTOPNetwork->AllTOPNodes.Num(); Idx++) + { + UTOPNode *TOPNode = CurrentTOPNetwork->AllTOPNodes[Idx]; + if (!IsValid(TOPNode) || TOPNode->bHidden) + continue; + + CurrentTOPNetwork->SelectedTOPIndex = Idx; + break; + } + } + + AllTOPNetworks.Add(CurrentTOPNetwork); + } + } + + // Clear previous TOP networks, nodes and generated data + for (UTOPNetwork* CurrentTOPNet : PDGAssetLink->AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + PDGAssetLink->ClearTOPNetworkWorkItemResults(CurrentTOPNet); + } + //PDGAssetLink->ClearAllTOPData(); + PDGAssetLink->AllTOPNetworks = AllTOPNetworks; + + return (AllTOPNetworks.Num() > 0); +} + + +bool +FHoudiniPDGManager::PopulateTOPNodes( + const TArray& InTopNodeIDs, UTOPNetwork* InTOPNetwork, UHoudiniPDGAssetLink* InPDGAssetLink, bool bInZeroWorkItemTallys) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InTOPNetwork)) + return false; + + // + int32 TOPNodeCount = 0; + + // Holds list of found TOP nodes + TArray AllTOPNodes; + for(const HAPI_NodeId& CurrentTOPNodeID : InTopNodeIDs) + { + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentTOPNodeID, &CurrentNodeInfo)) + { + continue; + } + + // Increase the number of valid TOP Node + // (before applying the node filter) + TOPNodeCount++; + + // Get the node path + FString NodePath; + FHoudiniEngineUtils::HapiGetNodePath(CurrentNodeInfo.id, InTOPNetwork->NodeId, NodePath); + + // Get the node name + FString NodeName; + FHoudiniEngineString::ToFString(CurrentNodeInfo.nameSH, NodeName); + + // See if we can find an existing version of this TOPNOde + UTOPNode* CurrentTOPNode = nullptr; + int32 FoundNodeIndex = INDEX_NONE; + UTOPNode* FoundNode = InPDGAssetLink->GetTOPNodeByNodePath(NodePath, InTOPNetwork->AllTOPNodes, FoundNodeIndex); + if (IsValid(FoundNode)) + { + CurrentTOPNode = FoundNode; + InTOPNetwork->AllTOPNodes.RemoveAt(FoundNodeIndex); + + if (bInZeroWorkItemTallys) + { + CurrentTOPNode->ZeroWorkItemTally(); + CurrentTOPNode->NodeState = EPDGNodeState::None; + } + } + else + { + // Didn't find an existing UTOPNode for this node, create a new UTOPNode instance + CurrentTOPNode = NewObject(InPDGAssetLink, UTOPNode::StaticClass(), NAME_None, RF_Transactional); + } + + CurrentTOPNode->NodeId = CurrentTOPNodeID; + CurrentTOPNode->NodeName = NodeName; + CurrentTOPNode->NodePath = NodePath; + CurrentTOPNode->ParentName = InTOPNetwork->ParentName + TEXT("_") + InTOPNetwork->NodeName; + CurrentTOPNode->bHasChildNodes = CurrentNodeInfo.childNodeCount > 0; + + // Filter display/autoload using name + CurrentTOPNode->bHidden = false; + if (InPDGAssetLink->bUseTOPNodeFilter && !InPDGAssetLink->TOPNodeFilter.IsEmpty()) + { + // Only display nodes that matches the filter + if (!NodeName.StartsWith(InPDGAssetLink->TOPNodeFilter)) + CurrentTOPNode->bHidden = true; + } + + // Automatically load results for nodes that match the filter + if (InPDGAssetLink->bUseTOPOutputFilter) + { + bool bAutoLoad = false; + if (InPDGAssetLink->TOPOutputFilter.IsEmpty()) + bAutoLoad = true; + else if (NodeName.StartsWith(InPDGAssetLink->TOPOutputFilter)) + bAutoLoad = true; + + CurrentTOPNode->bAutoLoad = bAutoLoad; + + // Show autoloaded results by default + CurrentTOPNode->SetVisibleInLevel(bAutoLoad); + } + + AllTOPNodes.Add(CurrentTOPNode); + } + + for (UTOPNode* CurTOPNode : InTOPNetwork->AllTOPNodes) + { + if (!IsValid(CurTOPNode)) + continue; + + InPDGAssetLink->ClearTOPNodeWorkItemResults(CurTOPNode); + } + + InTOPNetwork->AllTOPNodes = AllTOPNodes; + + return (TOPNodeCount > 0); +} + + +void +FHoudiniPDGManager::DirtyTOPNode(UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + // Dirty the specified TOP node... + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( + FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, true)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *(InTOPNode->NodeName)); + } + + // ... and clear its work item results. + UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(InTOPNode); +} + +// void +// FHoudiniPDGManager::DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode) +// { +// // Dirty the specified TOP node... +// if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( +// FHoudiniEngine::Get().GetSession(), InTOPNode.NodeId, true)) +// { +// HOUDINI_LOG_ERROR(TEXT("PDG Dirty TOP Node - Failed to dirty %s!"), *InTOPNode.NodeName); +// } +// } + +void +FHoudiniPDGManager::CookTOPNode(UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + if (InTOPNode->NodeState == EPDGNodeState::Cooking || InTOPNode->AnyWorkItemsPending()) + { + HOUDINI_LOG_WARNING(TEXT("PDG Cook TOP Node - %s is already/still cooking, ignoring 'Cook TOP Node' request."), *(InTOPNode->NodePath)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( + FHoudiniEngine::Get().GetSession(), InTOPNode->NodeId, 0, 0)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook TOP Node - Failed to cook %s!"), *(InTOPNode->NodeName)); + } +} + + +void +FHoudiniPDGManager::DirtyAll(UTOPNetwork* InTOPNet) +{ + if (!IsValid(InTOPNet)) + return; + + // Dirty the specified TOP network... + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DirtyPDGNode( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, true)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Dirty All - Failed to dirty all of %s's TOP nodes!"), *(InTOPNet->NodeName)); + return; + } + + // ... and clear its work item results. + UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(InTOPNet); +} + + +void +FHoudiniPDGManager::CookOutput(UTOPNetwork* InTOPNet) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::CookOutput); + + // Cook the output TOP node of the currently selected TOP network. + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + bool bAlreadyCooking = InTOPNet->AnyWorkItemsPending(); + + if (!bAlreadyCooking) + { + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + int32 PDGState = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( + FHoudiniEngine::Get().GetSession(), GraphContextId, &PDGState)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to get %s's PDG state."), *(InTOPNet->NodeName)); + return; + } + bAlreadyCooking = ((HAPI_PDG_State) PDGState == HAPI_PDG_STATE_COOKING); + } + + if (bAlreadyCooking) + { + HOUDINI_LOG_WARNING(TEXT("PDG Cook Output - %s is already/still cooking, ignoring 'Cook Output' request."), *(InTOPNet->NodeName)); + return; + } + + // TODO: ??? + // Cancel all cooks. This is required as otherwise the graph gets into an infinite cook state (bug?) + if(HAPI_RESULT_SUCCESS != FHoudiniApi::CookPDG( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, 0, 0)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cook Output - Failed to cook %s's output!"), *(InTOPNet->NodeName)); + } +} + + +void +FHoudiniPDGManager::PauseCook(UTOPNetwork* InTOPNet) +{ + // Pause the PDG cook of the currently selected TOP network + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::PausePDGCook( + FHoudiniEngine::Get().GetSession(), GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Pause Cook - Failed to pause %s!"), *(InTOPNet->NodeName)); + return; + } +} + + +void +FHoudiniPDGManager::CancelCook(UTOPNetwork* InTOPNet) +{ + // Cancel the PDG cook of the currently selected TOP network + //WorkItemTally.ZeroAll(); + //UHoudiniPDGAssetLink::ResetTOPNetworkWorkItemTally(InTOPNet); + + if (!IsValid(InTOPNet)) + return; + + if (!FHoudiniEngine::Get().GetSession()) + return; + + HAPI_PDG_GraphContextId GraphContextId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId( + FHoudiniEngine::Get().GetSession(), InTOPNet->NodeId, &GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to get %s's graph context ID!"), *(InTOPNet->NodeName)); + return; + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CancelPDGCook( + FHoudiniEngine::Get().GetSession(), GraphContextId)) + { + HOUDINI_LOG_ERROR(TEXT("PDG Cancel Cook - Failed to cancel cook for %s!"), *(InTOPNet->NodeName)); + return; + } +} + +void +FHoudiniPDGManager::Update() +{ + // Clean up registered PDG Asset Links + for(int32 Idx = PDGAssetLinks.Num() - 1; Idx >= 0; Idx--) + { + TWeakObjectPtr Ptr = PDGAssetLinks[Idx]; + if (!Ptr.IsValid() || Ptr.IsStale()) + { + PDGAssetLinks.RemoveAt(Idx); + continue; + } + + UHoudiniPDGAssetLink* CurPDGAssetLink = PDGAssetLinks[Idx].Get(); + if (!CurPDGAssetLink || CurPDGAssetLink->IsPendingKill()) + { + PDGAssetLinks.RemoveAt(Idx); + continue; + } + } + + // Do nothing if we dont have any valid PDG asset Link + if (PDGAssetLinks.Num() <= 0) + return; + + // Update the PDG contexts and handle all pdg events and work item status updates + UpdatePDGContexts(); + + // Prcoess any workitem result if we have any + ProcessWorkItemResults(); +} + +// Query all the PDG graph context in the current Houdini Engine session. +// Handle PDG events, work item status updates. +// Forward relevant events to PDGAssetLink objects. +void +FHoudiniPDGManager::UpdatePDGContexts() +{ + // Get current PDG graph contexts + ReinitializePDGContext(); + + // Process next set of events for each graph context + if (PDGContextIDs.Num() > 0) + { + // Only initialize event array if not valid, or user resized max size + if(PDGEventInfos.Num() != MaxNumberOfPDGEvents) + PDGEventInfos.SetNum(MaxNumberOfPDGEvents); + + // TODO: member? + //HAPI_PDG_State PDGState; + for(const HAPI_PDG_GraphContextId& CurrentContextID : PDGContextIDs) + { + /* + // TODO: No need to reset events at each tick + int32 PDGStateInt; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGState( + FHoudiniEngine::Get().GetSession(), CurrentContextID, &PDGStateInt)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get PDG state")); + continue; + } + + PDGState = (HAPI_PDG_State)PDGStateInt; + + for (int32 Idx = 0; Idx < PDGEventInfos.Num(); Idx++) + { + ResetPDGEventInfo(PDGEventInfos[Idx]); + } + */ + + int32 PDGEventCount = 0; + int32 RemainingPDGEventCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGEvents( + FHoudiniEngine::Get().GetSession(), CurrentContextID, PDGEventInfos.GetData(), + MaxNumberOfPDGEvents, &PDGEventCount, &RemainingPDGEventCount)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get PDG events")); + continue; + } + + if (PDGEventCount < 1) + continue; + + for (int32 EventIdx = 0; EventIdx < PDGEventCount; EventIdx++) + { + ProcessPDGEvent(CurrentContextID, PDGEventInfos[EventIdx]); + } + + HOUDINI_LOG_MESSAGE(TEXT("PDG: Tick processed %d events, %d remaining."), PDGEventCount, RemainingPDGEventCount); + } + } + + // Refresh UI if necessary + for (auto CurAssetLink : PDGAssetLinks) + { + UHoudiniPDGAssetLink* AssetLink = CurAssetLink.Get(); + if (AssetLink) + { + if (AssetLink->bNeedsUIRefresh) + { + FHoudiniPDGManager::RefreshPDGAssetLinkUI(AssetLink); + AssetLink->bNeedsUIRefresh = false; + } + else + { + AssetLink->UpdateWorkItemTally(); + } + } + } +} + +// Query the currently active PDG graph contexts in the Houdini Engine session. +// Should be done each time to get latest set of graph contexts. +void +FHoudiniPDGManager::ReinitializePDGContext() +{ + int32 NumContexts = 0; + + PDGContextNames.SetNum(MaxNumberOPDGContexts); + PDGContextIDs.SetNum(MaxNumberOPDGContexts); + + if(HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContexts( + FHoudiniEngine::Get().GetSession(), + &NumContexts, PDGContextNames.GetData(), PDGContextIDs.GetData(), MaxNumberOPDGContexts) || NumContexts <= 0) + { + PDGContextNames.SetNum(0); + PDGContextIDs.SetNum(0); + return; + } + + if(PDGContextIDs.Num() != NumContexts) + PDGContextIDs.SetNum(NumContexts); + + if (PDGContextNames.Num() != NumContexts) + PDGContextNames.SetNum(NumContexts); +} + +// Process a PDG event. Notify the relevant PDGAssetLink object. +void +FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo) +{ + UHoudiniPDGAssetLink* PDGAssetLink = nullptr; + UTOPNetwork* TOPNetwork = nullptr; + UTOPNode* TOPNode = nullptr; + + HAPI_PDG_EventType EventType = (HAPI_PDG_EventType)EventInfo.eventType; + HAPI_PDG_WorkitemState CurrentWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.currentState; + HAPI_PDG_WorkitemState LastWorkItemState = (HAPI_PDG_WorkitemState)EventInfo.lastState; + + // Debug: get the HAPI_PDG_EventType as a string + const FString EventName = FHoudiniEngineUtils::HapiGetEventTypeAsString(EventType); + const FString CurrentWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(CurrentWorkItemState); + const FString LastWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(LastWorkItemState); + + if(!GetTOPAssetLinkNetworkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNetwork, TOPNode) + || PDGAssetLink == nullptr || PDGAssetLink->IsPendingKill() + || TOPNetwork == nullptr || TOPNetwork->IsPendingKill() + || TOPNode == nullptr || TOPNode->IsPendingKill() + || TOPNode->NodeId != EventInfo.nodeId) + { + + HOUDINI_LOG_WARNING(TEXT("[ProcessPDGEvent]: Could not find matching TOPNode for event %s, workitem id %d, node id %d"), *EventName, EventInfo.workitemId, EventInfo.nodeId); + return; + } + + HOUDINI_PDG_MESSAGE( + TEXT("[ProcessPDGEvent]: TOPNode: %s, WorkItem ID: %d, Event Type: %s, Current State: %s, Last State %s"), + *(TOPNode->NodePath), EventInfo.workitemId, *EventName, *CurrentWorkitemStateName, *LastWorkitemStateName); + + FLinearColor MsgColor = FLinearColor::White; + + bool bUpdatePDGNodeState = false; + switch (EventType) + { + case HAPI_PDG_EVENT_NULL: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::None); + break; + + case HAPI_PDG_EVENT_NODE_CLEAR: + NotifyTOPNodePDGStateClear(PDGAssetLink, TOPNode); + break; + + case HAPI_PDG_EVENT_WORKITEM_ADD: + CreateOrRelinkWorkItem(TOPNode, InContextID, EventInfo.workitemId); + bUpdatePDGNodeState = true; + NotifyTOPNodeCreatedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + break; + + case HAPI_PDG_EVENT_WORKITEM_REMOVE: + RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, TOPNode); + bUpdatePDGNodeState = true; + NotifyTOPNodeRemovedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + break; + + case HAPI_PDG_EVENT_COOK_WARNING: + MsgColor = FLinearColor::Yellow; + break; + + case HAPI_PDG_EVENT_COOK_ERROR: + MsgColor = FLinearColor::Red; + break; + + case HAPI_PDG_EVENT_COOK_COMPLETE: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + TOPNode->HandleOnPDGEventCookComplete(); + TOPNetwork->HandleOnPDGEventCookCompleteReceivedByChildNode(PDGAssetLink, TOPNode); + break; + + case HAPI_PDG_EVENT_DIRTY_START: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtying); + break; + + case HAPI_PDG_EVENT_DIRTY_STOP: + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Dirtied); + break; + + case HAPI_PDG_EVENT_WORKITEM_STATE_CHANGE: + { + // Last states + bUpdatePDGNodeState = true; + if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) + { + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) + { + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) + { + } + else if ( (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE || LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) + && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS) + { + // Handled previously cooked WI + } + else if (LastWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL && CurrentWorkItemState != HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) + { + } + else + { + // TODO: + // unhandled state change + HOUDINI_PDG_WARNING(TEXT("Unhandled PDG state change! Node: %s, WorkItemID %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId); + } + + if (LastWorkItemState == CurrentWorkItemState) + { + // TODO: + // Not a change!! shouldnt happen! + HOUDINI_PDG_WARNING(TEXT("Last state == current state! Node: %s, WorkItemID %d, state %d"), IsValid(TOPNode) ? *TOPNode->NodePath : TEXT(""), EventInfo.workitemId, EventInfo.lastState); + } + + // New states + if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_WAITING) + { + NotifyTOPNodeWaitingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNCOOKED) + { + + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_DIRTY) + { + // ClearWorkItemResult(InContextID, EventInfo, *TOPNode); + ClearWorkItemResult(PDGAssetLink, EventInfo.workitemId, TOPNode); + // RemoveWorkItem(PDGAssetLink, EventInfo.workitemId, *TOPNode); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_SCHEDULED) + { + NotifyTOPNodeScheduledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKING) + { + NotifyTOPNodeCookingWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_SUCCESS + || CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CACHE) + { + NotifyTOPNodeCookedWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + + // On cook success, handle results + CreateOrRelinkWorkItemResult(TOPNode, InContextID, EventInfo.workitemId, TOPNode->bAutoLoad); + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_FAIL) + { + // TODO: on cook failure, get log path? + NotifyTOPNodeErrorWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + MsgColor = FLinearColor::Red; + } + else if (CurrentWorkItemState == HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_COOKED_CANCEL) + { + NotifyTOPNodeCookCancelledWorkItem(PDGAssetLink, TOPNode, EventInfo.workitemId); + } + } + break; + + case HAPI_PDG_EVENT_COOK_START: + TOPNode->HandleOnPDGEventCookStart(); + break; + // Unhandled events + case HAPI_PDG_EVENT_DIRTY_ALL: + case HAPI_PDG_EVENT_WORKITEM_ADD_DEP: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_DEP: + case HAPI_PDG_EVENT_WORKITEM_ADD_PARENT: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_PARENT: + case HAPI_PDG_EVENT_UI_SELECT: + case HAPI_PDG_EVENT_NODE_CREATE: + case HAPI_PDG_EVENT_NODE_REMOVE: + case HAPI_PDG_EVENT_NODE_RENAME: + case HAPI_PDG_EVENT_NODE_CONNECT: + case HAPI_PDG_EVENT_NODE_DISCONNECT: + case HAPI_PDG_EVENT_WORKITEM_SET_INT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FLOAT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_STRING: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_FILE: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_PYOBJECT: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_SET_GEOMETRY: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_RESULT: + case HAPI_PDG_EVENT_WORKITEM_PRIORITY: // DEPRECATED + case HAPI_PDG_EVENT_WORKITEM_ADD_STATIC_ANCESTOR: + case HAPI_PDG_EVENT_WORKITEM_REMOVE_STATIC_ANCESTOR: + case HAPI_PDG_EVENT_NODE_PROGRESS_UPDATE: + case HAPI_PDG_EVENT_ALL: + case HAPI_PDG_EVENT_LOG: + case HAPI_PDG_CONTEXT_EVENTS: + break; + } + + if (bUpdatePDGNodeState) + { + // Work item events + EPDGNodeState CurrentTOPNodeState = TOPNode->NodeState; + if (CurrentTOPNodeState == EPDGNodeState::Cooking) + { + if (TOPNode->AreAllWorkItemsComplete()) + { + // At the end of a node/net cook, ensure that the work items are in sync with HAPI and remove any + // work items with invalid ids or that don't exist on the HAPI side anymore. + SyncAndPruneWorkItems(TOPNode); + // Check that all work items are still complete after the sync + if (TOPNode->AreAllWorkItemsComplete()) + { + if (TOPNode->AnyWorkItemsFailed()) + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Failed); + } + else + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cook_Complete); + } + } + } + } + else if (TOPNode->AnyWorkItemsPending()) + { + SetTOPNodePDGState(PDGAssetLink, TOPNode, EPDGNodeState::Cooking); + } + } + + if (EventInfo.msgSH >= 0) + { + FString EventMsg; + FHoudiniEngineString::ToFString(EventInfo.msgSH, EventMsg); + if (!EventMsg.IsEmpty()) + { + // TODO: Event MSG? + // Somehow update the PDG event msg UI ?? + // Simply log for now... + if (MsgColor == FLinearColor::Red) + { + HOUDINI_LOG_ERROR(TEXT("%s"), *EventMsg); + } + else if (MsgColor == FLinearColor::Yellow) + { + HOUDINI_LOG_WARNING(TEXT("%s"), *EventMsg); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT("%s"), *EventMsg); + } + } + } +} + +void +FHoudiniPDGManager::ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo) +{ + InEventInfo.nodeId = -1; + InEventInfo.workitemId = -1; + InEventInfo.dependencyId = -1; + InEventInfo.currentState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; + InEventInfo.lastState = HAPI_PDG_WorkitemState::HAPI_PDG_WORKITEM_UNDEFINED; + InEventInfo.eventType = HAPI_PDG_EventType::HAPI_PDG_EVENT_NULL; +} + + +bool +FHoudiniPDGManager::GetTOPAssetLinkNetworkAndNode( + const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNetwork*& OutTOPNetwork, UTOPNode*& OutTOPNode) +{ + // Returns the PDGAssetLink and FTOPNode data associated with this TOP node ID + OutAssetLink = nullptr; + OutTOPNetwork = nullptr; + OutTOPNode = nullptr; + for (TWeakObjectPtr& CurAssetLinkPtr : PDGAssetLinks) + { + if (!CurAssetLinkPtr.IsValid() || CurAssetLinkPtr.IsStale()) + continue; + + UHoudiniPDGAssetLink* CurAssetLink = CurAssetLinkPtr.Get(); + if (!CurAssetLink || CurAssetLink->IsPendingKill()) + continue; + + if (CurAssetLink->GetTOPNodeAndNetworkByNodeId((int32)InNodeID, OutTOPNetwork, OutTOPNode)) + { + if (OutTOPNetwork != nullptr && OutTOPNode != nullptr) + { + OutAssetLink = CurAssetLink; + return true; + } + } + } + + OutAssetLink = nullptr; + OutTOPNetwork = nullptr; + OutTOPNode = nullptr; + + return false; +} + +void +FHoudiniPDGManager::SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->NodeState = InPDGState; + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode) +{ + if (!IsValid(InTOPNode)) + return; + + //Debug.LogFormat("NotifyTOPNodePDGStateClear:: {0}", topNode._nodeName); + InTOPNode->NodeState = EPDGNodeState::None; + InTOPNode->ZeroWorkItemTally(); + InTOPNode->OnDirtyNode(); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ZeroAll"), *(InTOPNode->NodePath)); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); + +} + +void +FHoudiniPDGManager::NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemCreated(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Created WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemRemoved(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally Removed WorkItem, Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemCooked(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookedWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookedWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemErrored(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ErroredWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemWaiting(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally WaitingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumErroredWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemScheduled(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally ScheduledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumScheduledWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemCooking(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookingWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookingWorkItems()); + + // InPDGAssetLink->bNeedsUIRefresh = true; + //FHoudiniPDGManager::RefreshPDGAssetLinkUI(InPDGAssetLink); +} + +void +FHoudiniPDGManager::NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID) +{ + if (!IsValid(InTOPNode)) + return; + + InTOPNode->OnWorkItemCookCancelled(InWorkItemID); + + HOUDINI_PDG_MESSAGE(TEXT("PDG: %s: WorkItemTally CookCancelledWorkItems Total %d"), *(InTOPNode->NodePath), InTOPNode->GetWorkItemTally().NumCookCancelledWorkItems()); +} + +void +FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // TODO!!! + // Clear all work items' results for the specified TOP node. + // This destroys any loaded results (geometry etc). + //session.LogErrorOverride = false; + InAssetLink->ClearWorkItemResultByID(InWorkItemID, InTOPNode); + // session.LogErrorOverride = true; +} + +void +FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // Clear all of the work item's results for the specified TOP node and also remove the work item itself from + // the TOP node. + InAssetLink->DestroyWorkItemByID(InWorkItemID, InTOPNode); +} + +void +FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + // Only update the editor properties if the PDG asset link's Actor is selected + // else, just update the workitemtally + InAssetLink->UpdateWorkItemTally(); + + UHoudiniAssetComponent* HAC = Cast(InAssetLink->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor* ActorOwner = HAC->GetOwner(); + if (ActorOwner != nullptr && ActorOwner->IsSelected()) + { + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + } +} + +void +FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess) +{ + if (!InAssetLink || InAssetLink->IsPendingKill()) + return; + + if (bSuccess) + { + if (InAssetLink->LinkState == EPDGLinkState::Linked) + { + if (InAssetLink->bAutoCook) + { + FHoudiniPDGManager::CookOutput(InAssetLink->GetSelectedTOPNetwork()); + } + } + else + { + UpdatePDGAssetLink(InAssetLink); + } + } + else + { + InAssetLink->LinkState = EPDGLinkState::Error_Not_Linked; + } +} + +int32 +FHoudiniPDGManager::CreateOrRelinkWorkItem( + UTOPNode* InTOPNode, + const HAPI_PDG_GraphContextId& InContextID, + HAPI_PDG_WorkitemId InWorkItemID) +{ + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); + return INDEX_NONE; + } + + HAPI_PDG_WorkitemInfo WorkItemInfo; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( + FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return INDEX_NONE; + } + + // Try to find the existing WorkItem by ID. + int32 Index = InTOPNode->IndexOfWorkResultByID(InWorkItemID); + if (Index == INDEX_NONE) + { + // Try to find an entry with WorkItemID == INDEX_NONE but where workItemIndex matches. The WorkItemIDs are + // transient, so not saved when the map / asset link is saved. So when loading a map containing the asset + // link all the IDs are INDEX_NONE and the WorkItemIndex is the best way to find an entry to re-use. + const bool bWithInvalidWorkItemID = true; + Index = InTOPNode->IndexOfWorkResultByHAPIIndex(WorkItemInfo.index, bWithInvalidWorkItemID); + if (Index == INDEX_NONE) + { + // If we couldn't find an existing entry, or a stale entry to re-use, create a new one + FTOPWorkResult LocalWorkResult; + LocalWorkResult.WorkItemID = InWorkItemID; + LocalWorkResult.WorkItemIndex = WorkItemInfo.index; + Index = InTOPNode->WorkResult.Add(LocalWorkResult); + } + else + { + InTOPNode->WorkResult[Index].WorkItemID = InWorkItemID; + } + } + + return Index; +} + +bool +FHoudiniPDGManager::CreateOrRelinkWorkItemResult( + UTOPNode* InTOPNode, + const HAPI_PDG_GraphContextId& InContextID, + HAPI_PDG_WorkitemId InWorkItemID, + bool bInLoadResultObjects) +{ + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to get work %d info: InTOPNode is null."), InWorkItemID); + return false; + } + + HAPI_PDG_WorkitemInfo WorkItemInfo; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemInfo( + FHoudiniEngine::Get().GetSession(), InContextID, InWorkItemID, &WorkItemInfo)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return false; + } + + // Try to find the existing WorkResult by ID. + FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); + if (!WorkResult) + { + // TODO: This shouldn't really happen, it means a work item finished cooking and generated a result before + // we received an event that the work item was added/generated. + int32 ArrayIndex = CreateOrRelinkWorkItem(InTOPNode, InContextID, InWorkItemID); + if (ArrayIndex != INDEX_NONE) + { + WorkResult = InTOPNode->GetWorkResultByArrayIndex(ArrayIndex); + } + } + + if (!WorkResult) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get or add a FTOPWorkResult for WorkItemID %d for %s"), InWorkItemID, *(InTOPNode->NodeName)); + return false; + } + + TArray NewResultObjects; + TSet ResultIndicesThatWereReused; + if (WorkItemInfo.numResults > 0) + { + TArray ResultInfos; + ResultInfos.SetNum(WorkItemInfo.numResults); + const int32 resultCount = WorkItemInfo.numResults; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitemResultInfo( + FHoudiniEngine::Get().GetSession(), + InTOPNode->NodeId, InWorkItemID, ResultInfos.GetData(), resultCount)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to get work item %d result info for %s"), InWorkItemID, *(InTOPNode->NodeName)); + // TODO? continue? + return false; + } + + FString WorkItemName; + FHoudiniEngineString::ToFString(WorkItemInfo.nameSH, WorkItemName); + + // Load each result geometry + const int32 NumResults = ResultInfos.Num(); + for (int32 Idx = 0; Idx < NumResults; Idx++) + { + const HAPI_PDG_WorkitemResultInfo& ResultInfo = ResultInfos[Idx]; + if (ResultInfo.resultTagSH <= 0 || ResultInfo.resultSH <= 0) + continue; + + FString CurrentTag; + FHoudiniEngineString::ToFString(ResultInfo.resultTagSH, CurrentTag); + if(CurrentTag.IsEmpty() || !CurrentTag.StartsWith(TEXT("file"))) + continue; + + FString CurrentPath = FString(); + FHoudiniEngineString::ToFString(ResultInfo.resultSH, CurrentPath); + + // Construct the name and look for an existing work result, re-use it if found, otherwise create a new one + const FString WorkResultName = FString::Printf( + TEXT("%s_%s_%d_%d"), + *(InTOPNode->ParentName), + *WorkItemName, + WorkItemInfo.index, + Idx); + + // int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) + // { + // return InResultObject.Name == WorkResultName; + // }); + int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([Idx](const FTOPWorkResultObject& InResultObject) + { + return InResultObject.WorkItemResultInfoIndex == Idx; + }); + if (WorkResult->ResultObjects.IsValidIndex(ExistingObjectIndex)) + { + FTOPWorkResultObject& ExistingResultObject = WorkResult->ResultObjects[ExistingObjectIndex]; + + ExistingResultObject.Name = WorkResultName; + ExistingResultObject.FilePath = CurrentPath; + ExistingResultObject.SetAutoBakedSinceLastLoad(false); + if (ExistingResultObject.State == EPDGWorkResultState::Loaded && !bInLoadResultObjects) + { + ExistingResultObject.State = EPDGWorkResultState::ToDelete; + } + else + { + // When the commandlet is not being used, we could leave the outputs in place and have + // translators try to re-use objects/components. When the commandlet is being used, the packages + // are always saved and standalone, so if we want to automatically clean up old results then we + // need to destroy the existing outputs + if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) + ExistingResultObject.DestroyResultOutputs(); + + if ((ExistingResultObject.State == EPDGWorkResultState::Loaded || + ExistingResultObject.State == EPDGWorkResultState::ToDelete || + ExistingResultObject.State == EPDGWorkResultState::Deleting) && bInLoadResultObjects) + { + ExistingResultObject.State = EPDGWorkResultState::ToLoad; + } + else + { + ExistingResultObject.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + } + } + + NewResultObjects.Add(ExistingResultObject); + ResultIndicesThatWereReused.Add(ExistingObjectIndex); + } + else + { + FTOPWorkResultObject ResultObj; + ResultObj.Name = WorkResultName; + ResultObj.FilePath = CurrentPath; + ResultObj.State = bInLoadResultObjects ? EPDGWorkResultState::ToLoad : EPDGWorkResultState::NotLoaded; + ResultObj.WorkItemResultInfoIndex = Idx; + ResultObj.SetAutoBakedSinceLastLoad(false); + + NewResultObjects.Add(ResultObj); + } + } + } + // Destroy any old ResultObjects that were not re-used + const int32 NumPrevResultObjects = WorkResult->ResultObjects.Num(); + for (int32 ResultObjectIndex = 0; ResultObjectIndex < NumPrevResultObjects; ++ResultObjectIndex) + { + if (ResultIndicesThatWereReused.Contains(ResultObjectIndex)) + continue; + FTOPWorkResultObject& ResultObject = WorkResult->ResultObjects[ResultObjectIndex]; + ResultObject.DestroyResultOutputsAndRemoveOutputActor(); + } + WorkResult->ResultObjects = NewResultObjects; + + return true; +} + +int32 +FHoudiniPDGManager::SyncAndPruneWorkItems(UTOPNode* InTOPNode) +{ + TSet WorkItemIDSet; + TArray WorkItemIDs; + int NumWorkItems = -1; + + if (!IsValid(InTOPNode)) + return -1; + + HAPI_Session const * const HAPISession = FHoudiniEngine::Get().GetSession(); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNumWorkitems(HAPISession, InTOPNode->NodeId, &NumWorkItems)) + { + HOUDINI_LOG_WARNING(TEXT("GetNumWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + WorkItemIDs.SetNum(NumWorkItems); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetWorkitems(HAPISession, InTOPNode->NodeId, WorkItemIDs.GetData(), NumWorkItems)) + { + HOUDINI_LOG_WARNING(TEXT("GetWorkitems call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + HAPI_PDG_GraphContextId ContextId; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetPDGGraphContextId(HAPISession, InTOPNode->NodeId, &ContextId)) + { + HOUDINI_LOG_WARNING(TEXT("GetPDGGraphContextId call failed on TOP Node %s (%d)"), *(InTOPNode->NodeName), InTOPNode->NodeId); + return -1; + } + + for (const HAPI_PDG_WorkitemId& WorkItemID : WorkItemIDs) + { + WorkItemIDSet.Add(static_cast(WorkItemID)); + + // If the WorkItemID is not present in FTOPWorkResult array, sync from HAPI + if (InTOPNode->GetWorkResultByID(WorkItemID) == nullptr) + { + CreateOrRelinkWorkItemResult(InTOPNode, ContextId, WorkItemID); + InTOPNode->OnWorkItemCreated(WorkItemID); + } + } + + // TODO: refactor functions that access the TOPNode's array and properties directly to rather be functions on the + // UTOPNode and make access to these arrays protected/private + + // Remove any work result entries with invalid IDs or where the WorkItemID is not in the set of ids returned by + // HAPI (only if we could get the IDs from HAPI). + int32 NumRemoved = 0; + const int32 NumWorkItemsInArray = InTOPNode->WorkResult.Num(); + for (int32 Index = NumWorkItemsInArray - 1; Index >= 0; --Index) + { + FTOPWorkResult& WorkResult = InTOPNode->WorkResult[Index]; + if (WorkResult.WorkItemID == INDEX_NONE || !WorkItemIDSet.Contains(WorkResult.WorkItemID)) + { + HOUDINI_PDG_WARNING( + TEXT("Pruning a FTOPWorkResult entry from TOP Node %d, WorkItemID %d, WorkItemIndex %d, Array Index %d"), + InTOPNode->NodeId, WorkResult.WorkItemID, WorkResult.WorkItemIndex, Index); + WorkResult.ClearAndDestroyResultObjects(); + InTOPNode->WorkResult.RemoveAt(Index); + InTOPNode->OnWorkItemRemoved(WorkResult.WorkItemID); + NumRemoved++; + } + } + + return NumRemoved; +} + +void +FHoudiniPDGManager::ProcessWorkItemResults() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGManager::ProcessWorkItemResults); + + const EHoudiniBGEOCommandletStatus CommandletStatus = UpdateAndGetBGEOCommandletStatus(); + for (auto& CurrentPDGAssetLink : PDGAssetLinks) + { + // Iterate through all PDG Asset Link + UHoudiniPDGAssetLink* AssetLink = CurrentPDGAssetLink.Get(); + if (!AssetLink) + continue; + + // Set up package parameters to: + // Cook to temp houdini engine directory + // and if the PDG asset link is associated with a Houdini Asset Component (HAC): + // set the outer package to the HAC + // set the HoudiniAssetName according to the HAC + // set the ComponentGUID according to the HAC + // otherwise we set the outer to the asset link's parent and leave naming and GUID blank + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = FHoudiniPackageParams::GetDefaultStaticMeshesCookMode(); + PackageParams.ReplaceMode = FHoudiniPackageParams::GetDefaultReplaceMode(); + + PackageParams.BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + // AActor* ParentActor = nullptr; + UObject* AssetLinkParent = AssetLink->GetOuter(); + UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; + if (HAC) + { + PackageParams.OuterPackage = HAC->GetComponentLevel(); + PackageParams.HoudiniAssetName = HAC->GetHoudiniAsset() ? HAC->GetHoudiniAsset()->GetName() : FString(); + PackageParams.HoudiniAssetActorName = HAC->GetOwner()->GetName(); + PackageParams.ComponentGUID = HAC->GetComponentGUID(); + + // ParentActor = HAC->GetOwner(); + } + else + { + PackageParams.OuterPackage = AssetLinkParent ? AssetLinkParent->GetOutermost() : nullptr; + PackageParams.HoudiniAssetName = FString(); + PackageParams.HoudiniAssetActorName = FString(); + // PackageParams.ComponentGUID = HAC->GetComponentGUID(); + + // // Try to find a parent actor + // UObject* Parent = AssetLinkParent; + // while (Parent && !ParentActor) + // { + // ParentActor = Cast(Parent); + // if (!ParentActor) + // Parent = ParentActor->GetOuter(); + // } + } + PackageParams.ObjectName = FString(); + + // UWorld *World = ParentActor ? ParentActor->GetWorld() : AssetLink->GetWorld(); + UWorld *World = AssetLink->GetWorld(); + + // .. All TOP Nets + for (UTOPNetwork* CurrentTOPNet : AssetLink->AllTOPNetworks) + { + if (!IsValid(CurrentTOPNet)) + continue; + + // .. All TOP Nodes + for (UTOPNode* CurrentTOPNode : CurrentTOPNet->AllTOPNodes) + { + if (!IsValid(CurrentTOPNode)) + continue; + + // ... All WorkResult + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; + CurrentTOPNode->bCachedHaveLoadedWorkResults = false; + for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) + { + // ... All WorkResultObjects + for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) + { + if (CurrentWorkResultObj.State == EPDGWorkResultState::ToLoad) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Loading; + + // Load this WRObj + PackageParams.PDGTOPNetworkName = CurrentTOPNet->NodeName; + PackageParams.PDGTOPNodeName = CurrentTOPNode->NodeName; + PackageParams.PDGWorkItemIndex = CurrentWorkResult.WorkItemIndex; + + if (CommandletStatus == EHoudiniBGEOCommandletStatus::Connected) + { + BGEOCommandletEndpoint->Send(new FHoudiniPDGImportBGEOMessage( + CurrentWorkResultObj.FilePath, + CurrentWorkResultObj.Name, + PackageParams, + CurrentTOPNode->NodeId, + CurrentWorkResult.WorkItemID + ), BGEOCommandletAddress); + } + else + { + if (FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + AssetLink, + CurrentTOPNode, + CurrentWorkResultObj, + PackageParams)) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Loaded; + CurrentWorkResultObj.SetAutoBakedSinceLastLoad(false); + CurrentTOPNode->bCachedHaveLoadedWorkResults = true; + + // Broadcast that we have loaded the work result object to those interested + AssetLink->OnWorkResultObjectLoaded.Broadcast( + AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemIndex, + CurrentWorkResultObj.WorkItemResultInfoIndex); + } + else + { + CurrentWorkResultObj.State = EPDGWorkResultState::None; + } + } + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::Loaded) + { + // If the work item result obj is in the "Loaded" state, confirm that the output actor + // is still valid (the user could have manually deleted the output + if (!IsValid(CurrentWorkResultObj.GetOutputActorOwner().GetOutputActor())) + { + // If the output actor is invalid, set the state to ToDelete to complete the + // unload/deletion process + CurrentWorkResultObj.State = EPDGWorkResultState::ToDelete; + } + else + { + CurrentTOPNode->bCachedHaveLoadedWorkResults = true; + } + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::ToDelete) + { + CurrentWorkResultObj.State = EPDGWorkResultState::Deleting; + + // Delete and clean up that WRObj + CurrentWorkResultObj.DestroyResultOutputs(); + CurrentWorkResultObj.GetOutputActorOwner().DestroyOutputActor(); + CurrentWorkResultObj.State = EPDGWorkResultState::Deleted; + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::Deleted) + { + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + else if (CurrentWorkResultObj.State == EPDGWorkResultState::NotLoaded) + { + CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; + } + } + } + } + } + } +} + +void FHoudiniPDGManager::HandleImportBGEODiscoverMessage( + const FHoudiniPDGImportBGEODiscoverMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_DISPLAY(TEXT("Received Discover from %s"), *InContext->GetSender().ToString()); + // Ignore any discover acks received if we already have a valid local address + // for the commandlet + if (BGEOCommandletAddress.IsValid()) + return; + + if (BGEOCommandletProcHandle.IsValid() && InMessage.CommandletGuid.IsValid() && BGEOCommandletGuid == InMessage.CommandletGuid) + { + BGEOCommandletAddress = InContext->GetSender(); + } +} + +void FHoudiniPDGManager::HandleImportBGEOResultMessage( + const FHoudiniPDGImportBGEOResultMessage& InMessage, + const TSharedRef& InContext) +{ + HOUDINI_LOG_MESSAGE(TEXT("Received BGEO import result message")); + if (InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_Success || InMessage.ImportResult == EHoudiniPDGImportBGEOResult::HPIBR_PartialSuccess) + { + FHoudiniPackageParams PackageParams; + InMessage.PopulatePackageParams(PackageParams); + + // Find asset link and work result object + UHoudiniPDGAssetLink *AssetLink = nullptr; + UTOPNetwork *TOPNetwork = nullptr; + UTOPNode *TOPNode = nullptr; + if (!GetTOPAssetLinkNetworkAndNode(InMessage.TOPNodeId, AssetLink, TOPNetwork, TOPNode) || + !IsValid(AssetLink) || !IsValid(TOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP node with id %d, aborting output object creation."), InMessage.TOPNodeId); + return; + } + + FTOPWorkResult* WorkResult = AssetLink->GetWorkResultByID(InMessage.WorkItemId, TOPNode); + if (WorkResult == nullptr) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result with id %d, aborting output object creation."), InMessage.WorkItemId); + return; + } + const FString& WorkResultObjectName = InMessage.Name; + FTOPWorkResultObject* WorkResultObject = WorkResult->ResultObjects.FindByPredicate( + [&WorkResultObjectName](const FTOPWorkResultObject& WorkResultObject) + { + return WorkResultObject.Name == WorkResultObjectName; + } + ); + if (WorkResultObject == nullptr) + { + HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result object with name %s, aborting output object creation."), *InMessage.Name); + return; + } + + if (WorkResultObject->State != EPDGWorkResultState::Loading) + { + HOUDINI_LOG_WARNING(TEXT("TOP work result object (%s) not in Loading state, aborting output object creation."), *InMessage.Name); + return; + } + + // Set package params outer + UObject* AssetLinkParent = AssetLink->GetOuter(); + UHoudiniAssetComponent* HAC = AssetLinkParent != nullptr ? Cast(AssetLinkParent) : nullptr; + if (HAC) + { + PackageParams.OuterPackage = HAC->GetComponentLevel(); + } + else + { + PackageParams.OuterPackage = AssetLinkParent->GetOutermost(); + } + + // Construct UHoudiniOutputs + bool bHasUnsupportedOutputs = false; + TArray NewOutputs; + TMap InstancedOutputPartData; + NewOutputs.Reserve(InMessage.Outputs.Num()); + for (const FHoudiniPDGImportNodeOutput& Output : InMessage.Outputs) + { + UHoudiniOutput* NewOutput = NewObject( + AssetLink, + UHoudiniOutput::StaticClass(), + NAME_None,//FName(*OutputName), + RF_NoFlags); + NewOutputs.Add(NewOutput); + const int32 NumHGPO = Output.HoudiniGeoPartObjects.Num(); + for (int32 Index = 0; Index < NumHGPO; ++Index) + { + const FHoudiniGeoPartObject& HGPO = Output.HoudiniGeoPartObjects[Index]; + NewOutput->AddNewHGPO(HGPO); + + if (Output.InstancedOutputPartData.IsValidIndex(Index)) + { + FHoudiniOutputObjectIdentifier Identifier; + Identifier.ObjectId = HGPO.ObjectId; + Identifier.GeoId = HGPO.GeoId; + Identifier.PartId = HGPO.PartId; + Identifier.PartName = HGPO.PartName; + FHoudiniInstancedOutputPartData InstancedPartData = Output.InstancedOutputPartData[Index]; + InstancedPartData.BuildOriginalInstancedTransformsAndObjectArrays(); + InstancedOutputPartData.Add(Identifier, InstancedPartData); + } + } + const int32 NumObjects = Output.OutputObjects.Num(); + for (int32 Index = 0; Index < NumObjects; ++Index) + { + const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; + FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; + + const FString& FullPackagePath = ImportOutputObject.PackagePath; + FString PackagePath; + FString PackageName; + const bool bDidSplit = FullPackagePath.Split(TEXT("."), &PackagePath, &PackageName, ESearchCase::IgnoreCase, ESearchDir::FromEnd); + if (!bDidSplit) + PackagePath = FullPackagePath; + + FHoudiniOutputObject OutputObject; + UPackage* Package = FindPackage(nullptr, *PackagePath); + if (!IsValid(Package)) + { + // Editor might have picked up the package yet, try to load it + Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn); + } + if (IsValid(Package)) + { + OutputObject.OutputObject = FindObject(Package, *PackageName); + } + Identifier.bLoaded = true; + NewOutput->GetOutputObjects().Add(Identifier, OutputObject); + } + NewOutput->UpdateOutputType(); + const EHoudiniOutputType OutputType = NewOutput->GetType(); + if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) + { + bHasUnsupportedOutputs = true; + } + } + + bool bSuccess = true; + if (bHasUnsupportedOutputs) + { + HOUDINI_LOG_MESSAGE(TEXT("Processing output types not supported by commandlet for %s"), *InMessage.Name); + bSuccess = FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + AssetLink, TOPNode, *WorkResultObject, PackageParams, + { + EHoudiniOutputType::Landscape, + EHoudiniOutputType::Curve, + EHoudiniOutputType::Skeletal + } + ); + + if (bSuccess) + { + // Clear/remove the outputs on WorkResultObject that are supported by the commandlet, since we + // are going to replace them with NewOutputs now + TArray& CurrentOutputs = WorkResultObject->GetResultOutputs(); + const int32 NumCurrentOutputs = CurrentOutputs.Num(); + for (int32 Index = 0; Index < NumCurrentOutputs; ++Index) + { + UHoudiniOutput* CurOutput = CurrentOutputs[Index]; + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType != EHoudiniOutputType::Mesh && OutputType != EHoudiniOutputType::Instancer) + { + // Was created in editor, override the dummy one in NewOutputs with CurOutput + if (NewOutputs.IsValidIndex(Index)) + { + if (OutputType == NewOutputs[Index]->GetType()) + { + UHoudiniOutput* TempOutput = NewOutputs[Index]; + FHoudiniOutputTranslator::ClearOutput(TempOutput); + NewOutputs[Index] = CurOutput; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Unexpected commandlet output type at index %d!"), Index); + } + } + else + { + HOUDINI_LOG_ERROR(TEXT("Expected output index %d from commandlet to be exist!"), Index); + } + } + } + } + } + + if (bSuccess && FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( + AssetLink, + TOPNode, + *WorkResultObject, + PackageParams, + NewOutputs, + {EHoudiniOutputType::Mesh, EHoudiniOutputType::Instancer}, + &InstancedOutputPartData)) + { + const int32 NumOutputs = NewOutputs.Num(); + for (int32 OutputIndex = 0; OutputIndex < NumOutputs; ++OutputIndex) + { + UHoudiniOutput *NewOutput = NewOutputs[OutputIndex]; + + if (NewOutput->GetType() != EHoudiniOutputType::Mesh) + continue; + + const FHoudiniPDGImportNodeOutput& Output = InMessage.Outputs[OutputIndex]; + int32 NumObjects = Output.OutputObjects.Num(); + for (int32 Index = 0; Index < NumObjects; ++Index) + { + const FHoudiniPDGImportNodeOutputObject& ImportOutputObject = Output.OutputObjects[Index]; + FHoudiniOutputObjectIdentifier Identifier = ImportOutputObject.Identifier; + FHoudiniOutputObject *OutputObject = NewOutput->GetOutputObjects().Find(Identifier); + if (OutputObject) + { + if (IsValid(OutputObject->OutputComponent)) + { + // Update generic property attributes + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes( + OutputObject->OutputComponent, + ImportOutputObject.GenericAttributes.PropertyAttributes); + } + + // Copy cached attributes + OutputObject->CachedAttributes.Append(ImportOutputObject.CachedAttributes); + } + } + } + } + else + { + bSuccess = false; + } + + if (bSuccess) + { + WorkResultObject->State = EPDGWorkResultState::Loaded; + WorkResultObject->SetAutoBakedSinceLastLoad(false); + HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); + // Broadcast that we have loaded the work result object to those interested + AssetLink->OnWorkResultObjectLoaded.Broadcast( + AssetLink, TOPNode, WorkResult->WorkItemIndex, WorkResultObject->WorkItemResultInfoIndex); + } + else + { + WorkResultObject->State = EPDGWorkResultState::None; + HOUDINI_LOG_WARNING(TEXT("Failed to process loaded assets for %s"), *InMessage.Name); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Commandlet failed to import bgeo for %s"), *InMessage.Name); + } +} + +bool FHoudiniPDGManager::CreateBGEOCommandletAndEndpoint() +{ + if (!BGEOCommandletEndpoint.IsValid()) + { + BGEOCommandletAddress.Invalidate(); + BGEOCommandletEndpoint = FMessageEndpoint::Builder(TEXT("Houdini BGEO Commandlet")) + .Handling(this, &FHoudiniPDGManager::HandleImportBGEOResultMessage) + .Handling(this, &FHoudiniPDGManager::HandleImportBGEODiscoverMessage) + .ReceivingOnThread(ENamedThreads::GameThread); + + if (!BGEOCommandletEndpoint.IsValid()) + { + HOUDINI_LOG_WARNING(TEXT("Could not set up messaging end point for BGEO commandlet")); + return false; + } + + BGEOCommandletEndpoint->Subscribe(); + } + + if (!BGEOCommandletProcHandle.IsValid() || !FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + { + // Start the bgeo commandlet + static const FString BGEOCommandletName = TEXT("HoudiniGeoImport"); + BGEOCommandletGuid = FGuid::NewGuid(); + BGEOCommandletAddress.Invalidate(); + + // Get the absolute path to the project file, if known, otherwise get + // the project name. For the path: quote it for the command line. + IFileManager& FileManager = IFileManager::Get(); + FString ProjectPathOrName = FApp::GetProjectName(); + if (FPaths::IsProjectFilePathSet()) + { + const FString ProjectPath = FPaths::GetProjectFilePath(); + if (!ProjectPath.IsEmpty()) + { + ProjectPathOrName = FString::Printf( + TEXT("\"%s\""), + *FileManager.ConvertToAbsolutePathForExternalAppForRead(*ProjectPath) + ); + } + } + + if (ProjectPathOrName.IsEmpty()) + return false; + + // Get the executable path for the app/editor + FString ExePath = FPlatformProcess::GenerateApplicationPath(FApp::GetName(), FApp::GetBuildConfiguration()); + if (!ExePath.IsEmpty()) + ExePath = FileManager.ConvertToAbsolutePathForExternalAppForRead(*ExePath); + + if (ExePath.IsEmpty()) + return false; + + const FString CommandLineParameters = FString::Printf( + TEXT("%s -messaging -run=%s -guid=%s -listen=%s -managerpid=%d"), + *ProjectPathOrName, + *BGEOCommandletName, + *BGEOCommandletGuid.ToString(), + *BGEOCommandletEndpoint->GetAddress().ToString(), + FPlatformProcess::GetCurrentProcessId()); + + BGEOCommandletProcHandle = FPlatformProcess::CreateProc( + *ExePath, + *CommandLineParameters, + false, + true, + false, + &BGEOCommandletProcessId, + 0, + NULL, + NULL); + if (!BGEOCommandletProcHandle.IsValid()) + { + return false; + } + } + + return true; +} + +void FHoudiniPDGManager::StopBGEOCommandletAndEndpoint() +{ + BGEOCommandletEndpoint.Reset(); + BGEOCommandletAddress.Invalidate(); + BGEOCommandletGuid.Invalidate(); + + if (BGEOCommandletProcHandle.IsValid() && FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + { + FPlatformProcess::TerminateProc(BGEOCommandletProcHandle, true); + if (BGEOCommandletProcHandle.IsValid()) + { + FPlatformProcess::WaitForProc(BGEOCommandletProcHandle); + FPlatformProcess::CloseProc(BGEOCommandletProcHandle); + } + } +} + +EHoudiniBGEOCommandletStatus FHoudiniPDGManager::UpdateAndGetBGEOCommandletStatus() +{ + if (BGEOCommandletProcHandle.IsValid()) + { + if (!FPlatformProcess::IsProcRunning(BGEOCommandletProcHandle)) + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Crashed; + else if (BGEOCommandletAddress.IsValid()) + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Connected; + else + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::Running; + } + else + BGEOCommandletStatus = EHoudiniBGEOCommandletStatus::NotStarted; + + return BGEOCommandletStatus; +} + + +bool +FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) +{ + if (InAssetId < 0) + return false; + + // Get all the network nodes within the asset, recursively. + // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type + int32 NetworkNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); + + if (NetworkNodeCount <= 0) + return false; + + TArray AllNetworkNodeIDs; + AllNetworkNodeIDs.SetNum(NetworkNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + AllNetworkNodeIDs.GetData(), NetworkNodeCount), false); + + // There is currently no way to only get non bypassed nodes via HAPI + // So we now need to get a list of all the bypassed top nets, in order to remove them from the previous list... + TArray AllBypassedTOPNetNodeIDs; + { + int32 BypassedTOPNetNodeCount = 0; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK | HAPI_NODEFLAGS_BYPASS, true, &BypassedTOPNetNodeCount), false); + + if (BypassedTOPNetNodeCount > 0) + { + // Get the list of all bypassed TOP Net... + AllBypassedTOPNetNodeIDs.SetNum(BypassedTOPNetNodeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + AllBypassedTOPNetNodeIDs.GetData(), BypassedTOPNetNodeCount), false); + + // ... and remove them from the network list + for (int32 Idx = AllNetworkNodeIDs.Num() - 1; Idx >= 0; Idx--) + { + if (AllBypassedTOPNetNodeIDs.Contains(AllNetworkNodeIDs[Idx])) + AllNetworkNodeIDs.RemoveAt(Idx); + } + } + } + + // For each Network we found earlier, only consider those with TOP child nodes + // If we find TOP nodes in a valid network, then consider this HDA a PDG HDA + for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) + { + if (CurrentNodeId < 0) + { + continue; + } + + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) + { + continue; + } + + // Skip non TOP or SOP networks + if (CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_TOP + && CurrentNodeInfo.type != HAPI_NodeType::HAPI_NODETYPE_SOP) + { + continue; + } + + // Get the list of all TOP nodes within the current network (ignoring schedulers) + int32 TOPNodeCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), CurrentNodeId, + HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, &TOPNodeCount)) + { + continue; + } + + // We found valid TOP Nodes, this is a PDG HDA + if (TOPNodeCount > 0) + return true; + } + + // No valid TOP node found in SOP/TOP Networks, this is not a PDG HDA + return false; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.h b/Source/HoudiniEngine/Private/HoudiniPDGManager.h index 0b0b9ba1d..10564ec1e 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.h @@ -1,215 +1,215 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HAL/PlatformProcess.h" - -#include "MessageEndpoint.h" - -class UHoudiniAssetComponent; -class UHoudiniPDGAssetLink; -class UTOPNetwork; -class UTOPNode; -class FSocket; - -enum class EPDGNodeState : uint8; - -// BGEO commandlet status -enum class HOUDINIENGINE_API EHoudiniBGEOCommandletStatus : uint8 -{ - // PDG manager has not tried to start the commandlet - NotStarted, - // PDG manager has PID for the commandlet and the host OS indicates it is running, but no message has been - // received from it yet - Running, - // PDG manager has PID for the commandlet, the host OS indicates it is running, and a discover message has been - // received - Connected, - // After being in the Connected state, the commandlet stopped running (host OS indicates PID is not valid) - Crashed -}; - -struct HOUDINIENGINE_API FHoudiniPDGManager -{ - -public: - - FHoudiniPDGManager(); - - virtual ~FHoudiniPDGManager(); - - // Initialize the PDG Asset Link for a HoudiniAssetComponent - // returns true if the HAC uses a PDG asset, and a PDGAssetLink was successfully created - bool InitializePDGAssetLink(UHoudiniAssetComponent* InHAC); - - // Updates an existing PDG AssetLink - static bool UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink); - - // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. - static bool PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys=false); - - static void RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink); - - // Indicates if the Asset is a PDG Asset - // This will look for TOP nodes in all SOP/TOP net in the HDA. - static bool IsPDGAsset(const HAPI_NodeId& InAssetId); - - // Given TOP nodes from a TOP network, populate internal state from each TOP node. - static bool PopulateTOPNodes( - const TArray& InTopNodeIDs, - UTOPNetwork* InTOPNetwork, - UHoudiniPDGAssetLink* InPDGAssetLink, - bool bInZeroWorkItemTallys=false); - - // Cook the specified TOP node. - static void CookTOPNode(UTOPNode* InTOPNode); - - // Dirty the specified TOP node and clear its work item results. - static void DirtyTOPNode(UTOPNode* InTOPNode); - - // // Dirty all the tasks/work items of the specified TOP node. Does not - // // clear its work item results. - // static void DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode); - - // Dirty the TOP network and clear all work item results. - static void DirtyAll(UTOPNetwork* InTOPNet); - - // Cook the output TOP node of the currently selected TOP network. - static void CookOutput(UTOPNetwork* InTOPNet); - - // Pause the PDG cook of the currently selected TOP network - static void PauseCook(UTOPNetwork* InTOPNet); - - // Cancel the PDG cook of the currently selected TOP network - static void CancelCook(UTOPNetwork* InTOPNet); - - static void NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess); - - // Update all registered PDG Asset links - void Update(); - - void ReinitializePDGContext(); - - // Clear all of the specified work item's results from the specified TOP node. This destroys any loaded results - // (geometry etc), but keeps the work item struct. - //void ClearWorkItemResult(const HAPI_PDG_GraphContextId& InContextID, const HAPI_PDG_EventInfo& InEventInfo, FTOPNode& TOPNode); - void ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); - - // Clear the specified work item's results from the specified TOP node and remove the work item struct from the TOP - // node. This destroys any loaded results (geometry etc), and the work item struct. - void RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); - - // Create a (or re-use an existing) FTOPWorkResult for a given TOPNode and the specified work item ID, without - // creating its FTOPWorkResultObjects. - // Returns INDEX_NONE if an entry could not be created or data could not be retrieved from HAPI. - int32 CreateOrRelinkWorkItem(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID); - - // Ensure that FTOPWorkResult exists, and create its FTOPWorkResultObjects for a given TOP node and work item id, - // and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. - // Geometry is not directly loaded by this function, the FTOPWorkResultObjects' states will be set to ToLoad and - // the ProcessWorkItemResults function will take care of loading the geo. - // Results must be tagged with 'file', and must have a file path, otherwise will not included. - bool CreateOrRelinkWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); - - // First Create or re-link FTOPWorkResults based on the work items that exist on InTOPNode in HAPI. - // Then remove any FTOPWorkResults (and clean up their output) from the WorkResults of InTOPNode if: - // WorkResult.WorkItemID is INDEX_NONE - // WorkResult.WorkItemID is not in the list of work item ids that HAPI returns for this node - int32 SyncAndPruneWorkItems(UTOPNode* InTOPNode); - - // Handles replies from commandlets in response to a FHoudiniPDGImportBGEODiscoverMessage - void HandleImportBGEODiscoverMessage( - const struct FHoudiniPDGImportBGEODiscoverMessage& InMessage, - const TSharedRef& InContext); - - // Handles messages sent by the commandlet once an import of a bgeo is complete, and uassets have been created. - void HandleImportBGEOResultMessage( - const struct FHoudiniPDGImportBGEOResultMessage& InMessage, - const TSharedRef& InContext); - - // Create the bgeo commandlet endpoint and start the commandlet (if not already running). - bool CreateBGEOCommandletAndEndpoint(); - - void StopBGEOCommandletAndEndpoint(); - - // Updates and returns the BGEO commandlet status - EHoudiniBGEOCommandletStatus UpdateAndGetBGEOCommandletStatus(); - -private: - - void UpdatePDGContexts(); - - void ProcessWorkItemResults(); - - void ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo); - - static void ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo); - - // Returns the PDGAssetLink and FTOPNode associated with this TOP node ID - bool GetTOPAssetLinkNetworkAndNode(const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNetwork*& OutTOPNetwork, UTOPNode*& OutTOPNode); - - void SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState); - - void NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode); - - void NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - - void NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); - -private: - - TArray PDGContextNames; - TArray PDGContextIDs; - TArray PDGEventInfos; - - TArray> PDGAssetLinks; - - int32 MaxNumberOfPDGEvents = 20; - int32 MaxNumberOPDGContexts = 20; - - TSharedPtr BGEOCommandletEndpoint; - FMessageAddress BGEOCommandletAddress; - FProcHandle BGEOCommandletProcHandle; - FGuid BGEOCommandletGuid; - uint32 BGEOCommandletProcessId; - // Keep track of the BGEO commandlet status - EHoudiniBGEOCommandletStatus BGEOCommandletStatus; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HAL/PlatformProcess.h" + +#include "MessageEndpoint.h" + +class UHoudiniAssetComponent; +class UHoudiniPDGAssetLink; +class UTOPNetwork; +class UTOPNode; +class FSocket; + +enum class EPDGNodeState : uint8; + +// BGEO commandlet status +enum class HOUDINIENGINE_API EHoudiniBGEOCommandletStatus : uint8 +{ + // PDG manager has not tried to start the commandlet + NotStarted, + // PDG manager has PID for the commandlet and the host OS indicates it is running, but no message has been + // received from it yet + Running, + // PDG manager has PID for the commandlet, the host OS indicates it is running, and a discover message has been + // received + Connected, + // After being in the Connected state, the commandlet stopped running (host OS indicates PID is not valid) + Crashed +}; + +struct HOUDINIENGINE_API FHoudiniPDGManager +{ + +public: + + FHoudiniPDGManager(); + + virtual ~FHoudiniPDGManager(); + + // Initialize the PDG Asset Link for a HoudiniAssetComponent + // returns true if the HAC uses a PDG asset, and a PDGAssetLink was successfully created + bool InitializePDGAssetLink(UHoudiniAssetComponent* InHAC); + + // Updates an existing PDG AssetLink + static bool UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink); + + // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. + static bool PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys=false); + + static void RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink); + + // Indicates if the Asset is a PDG Asset + // This will look for TOP nodes in all SOP/TOP net in the HDA. + static bool IsPDGAsset(const HAPI_NodeId& InAssetId); + + // Given TOP nodes from a TOP network, populate internal state from each TOP node. + static bool PopulateTOPNodes( + const TArray& InTopNodeIDs, + UTOPNetwork* InTOPNetwork, + UHoudiniPDGAssetLink* InPDGAssetLink, + bool bInZeroWorkItemTallys=false); + + // Cook the specified TOP node. + static void CookTOPNode(UTOPNode* InTOPNode); + + // Dirty the specified TOP node and clear its work item results. + static void DirtyTOPNode(UTOPNode* InTOPNode); + + // // Dirty all the tasks/work items of the specified TOP node. Does not + // // clear its work item results. + // static void DirtyAllTasksOfTOPNode(FTOPNode& InTOPNode); + + // Dirty the TOP network and clear all work item results. + static void DirtyAll(UTOPNetwork* InTOPNet); + + // Cook the output TOP node of the currently selected TOP network. + static void CookOutput(UTOPNetwork* InTOPNet); + + // Pause the PDG cook of the currently selected TOP network + static void PauseCook(UTOPNetwork* InTOPNet); + + // Cancel the PDG cook of the currently selected TOP network + static void CancelCook(UTOPNetwork* InTOPNet); + + static void NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess); + + // Update all registered PDG Asset links + void Update(); + + void ReinitializePDGContext(); + + // Clear all of the specified work item's results from the specified TOP node. This destroys any loaded results + // (geometry etc), but keeps the work item struct. + //void ClearWorkItemResult(const HAPI_PDG_GraphContextId& InContextID, const HAPI_PDG_EventInfo& InEventInfo, FTOPNode& TOPNode); + void ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); + + // Clear the specified work item's results from the specified TOP node and remove the work item struct from the TOP + // node. This destroys any loaded results (geometry etc), and the work item struct. + void RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode); + + // Create a (or re-use an existing) FTOPWorkResult for a given TOPNode and the specified work item ID, without + // creating its FTOPWorkResultObjects. + // Returns INDEX_NONE if an entry could not be created or data could not be retrieved from HAPI. + int32 CreateOrRelinkWorkItem(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID); + + // Ensure that FTOPWorkResult exists, and create its FTOPWorkResultObjects for a given TOP node and work item id, + // and optionally (via bInLoadResultObjects) create its FTOPWorkResultObjects. + // Geometry is not directly loaded by this function, the FTOPWorkResultObjects' states will be set to ToLoad and + // the ProcessWorkItemResults function will take care of loading the geo. + // Results must be tagged with 'file', and must have a file path, otherwise will not included. + bool CreateOrRelinkWorkItemResult(UTOPNode* InTOPNode, const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_WorkitemId InWorkItemID, bool bInLoadResultObjects=false); + + // First Create or re-link FTOPWorkResults based on the work items that exist on InTOPNode in HAPI. + // Then remove any FTOPWorkResults (and clean up their output) from the WorkResults of InTOPNode if: + // WorkResult.WorkItemID is INDEX_NONE + // WorkResult.WorkItemID is not in the list of work item ids that HAPI returns for this node + int32 SyncAndPruneWorkItems(UTOPNode* InTOPNode); + + // Handles replies from commandlets in response to a FHoudiniPDGImportBGEODiscoverMessage + void HandleImportBGEODiscoverMessage( + const struct FHoudiniPDGImportBGEODiscoverMessage& InMessage, + const TSharedRef& InContext); + + // Handles messages sent by the commandlet once an import of a bgeo is complete, and uassets have been created. + void HandleImportBGEOResultMessage( + const struct FHoudiniPDGImportBGEOResultMessage& InMessage, + const TSharedRef& InContext); + + // Create the bgeo commandlet endpoint and start the commandlet (if not already running). + bool CreateBGEOCommandletAndEndpoint(); + + void StopBGEOCommandletAndEndpoint(); + + // Updates and returns the BGEO commandlet status + EHoudiniBGEOCommandletStatus UpdateAndGetBGEOCommandletStatus(); + +private: + + void UpdatePDGContexts(); + + void ProcessWorkItemResults(); + + void ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, HAPI_PDG_EventInfo& EventInfo); + + static void ResetPDGEventInfo(HAPI_PDG_EventInfo& InEventInfo); + + // Returns the PDGAssetLink and FTOPNode associated with this TOP node ID + bool GetTOPAssetLinkNetworkAndNode(const HAPI_NodeId& InNodeID, UHoudiniPDGAssetLink*& OutAssetLink, UTOPNetwork*& OutTOPNetwork, UTOPNode*& OutTOPNode); + + void SetTOPNodePDGState(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const EPDGNodeState& InPDGState); + + void NotifyTOPNodePDGStateClear(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode); + + void NotifyTOPNodeCreatedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeRemovedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeCookedWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeErrorWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeWaitingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeScheduledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeCookingWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + + void NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, const int32& InWorkItemID); + +private: + + TArray PDGContextNames; + TArray PDGContextIDs; + TArray PDGEventInfos; + + TArray> PDGAssetLinks; + + int32 MaxNumberOfPDGEvents = 20; + int32 MaxNumberOPDGContexts = 20; + + TSharedPtr BGEOCommandletEndpoint; + FMessageAddress BGEOCommandletAddress; + FProcHandle BGEOCommandletProcHandle; + FGuid BGEOCommandletGuid; + uint32 BGEOCommandletProcessId; + // Keep track of the BGEO commandlet status + EHoudiniBGEOCommandletStatus BGEOCommandletStatus; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp index c41a590c7..7a4352cb6 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp @@ -1,515 +1,517 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGTranslator.h" - - -#include "Editor.h" -#include "Containers/Array.h" -#include "FileHelpers.h" -#include "LandscapeInfo.h" -// #include "Engine/WorldComposition.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniPackageParams.h" -#include "HoudiniOutput.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniSplineTranslator.h" -#include "HoudiniTranslatorTypes.h" - -#define LOCTEXT_NAMESPACE "HoudiniEngine" - -bool -FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray InOutputTypesToProcess, - bool bInTreatExistingMaterialsAsUpToDate) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem); - - if (!IsValid(InAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InAssetLink is null.")); - return false; - } - - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InTOPNode is null.")); - return false; - } - - TArray OldTOPOutputs = InWorkResultObject.GetResultOutputs(); - TArray NewTOPOutputs; - - FHoudiniEngine::Get().CreateTaskSlateNotification(LOCTEXT("LoadPDGBGEO", "Loading PDG Output BGEO File...")); - - bool bResult = false; - // Create a new file node in HAPI for the bgeo and cook it - HAPI_NodeId FileNodeId = -1; - bResult = UHoudiniGeoImporter::OpenBGEOFile(InWorkResultObject.FilePath, FileNodeId); - if (bResult) - bResult = UHoudiniGeoImporter::CookFileNode(FileNodeId); - - // If the cook was successful, build outputs - if (bResult) - { - FHoudiniEngine::Get().UpdateTaskSlateNotification( - LOCTEXT("BuildPDGBGEOOutputs", "Building Ouputs from BGEO File...")); - - const bool bAddOutputsToRootSet = false; - bResult = UHoudiniGeoImporter::BuildAllOutputsForNode( - FileNodeId, - InAssetLink, - OldTOPOutputs, - NewTOPOutputs, - bAddOutputsToRootSet); - } - - if (bResult) - { - FHoudiniEngine::Get().UpdateTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); - - // If we successfully received outputs from the BGEO file, process the outputs - FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); - AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - if (!IsValid(WorkItemOutputActor)) - { - UWorld* World = InAssetLink->GetWorld(); - FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); - AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - if (!IsValid(TOPNodeOutputActor)) - { - if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) - TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - } - if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) - WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - } - - for (auto& OldOutput : OldTOPOutputs) - { - FHoudiniOutputTranslator::ClearOutput(OldOutput); - } - OldTOPOutputs.Empty(); - InWorkResultObject.GetResultOutputs().Empty(); - InWorkResultObject.SetResultOutputs(NewTOPOutputs); - - // Gather landscape actors from inputs. - // NOTE: If performance becomes a problem, cache these on the TOPNode along with all the other cached landscape - // data. - TArray AllInputLandscapes; - TArray InputLandscapesToUpdate; - UHoudiniAssetComponent* HAC = InAssetLink->GetOuterHoudiniAssetComponent(); - FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); - - bResult = CreateAllResultObjectsFromPDGOutputs( - NewTOPOutputs, - InPackageParams, - WorkItemOutputActor->GetRootComponent(), - InTOPNode->GetLandscapeExtent(), - InTOPNode->GetLandscapeReferenceLocation(), - InTOPNode->GetLandscapeSizeInfo(), - InTOPNode->ClearedLandscapeLayers, - AllInputLandscapes, - InputLandscapesToUpdate, - InOutputTypesToProcess, - bInTreatExistingMaterialsAsUpToDate); - - if (!bResult) - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); - else - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); - - InTOPNode->UpdateOutputVisibilityInLevel(); - } - else - { - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("BuildPDGBGEOOutputsFail", "Failed building outputs from BGEO file...")); - } - - // Delete the file node used to load the BGEO via HAPI - if (FileNodeId >= 0) - { - UHoudiniGeoImporter::CloseBGEOFile(FileNodeId); - FileNodeId = -1; - } - - return bResult; -} - -bool -FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray& InOutputs, - TArray InOutputTypesToProcess, - const TMap* InPreBuiltInstancedOutputPartData) -{ - if (!IsValid(InAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InAssetLink is null.")); - return false; - } - - if (!IsValid(InTOPNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InTOPNode is null.")); - return false; - } - - FHoudiniEngine::Get().CreateTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); - - // If we successfully received outputs from the BGEO file, process the outputs - FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); - AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - if (!IsValid(WorkItemOutputActor)) - { - UWorld* World = InAssetLink->GetWorld(); - FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); - AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - if (!IsValid(TOPNodeOutputActor)) - { - if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) - TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); - } - if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) - WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); - } - - InWorkResultObject.SetResultOutputs(InOutputs); - - // Gather landscape actors from inputs. - // NOTE: If performance becomes a problem, cache these on the TOPNode along with all the other cached landscape - // data. - TArray AllInputLandscapes; - TArray InputLandscapesToUpdate; - UHoudiniAssetComponent* HAC = InAssetLink->GetOuterHoudiniAssetComponent(); - FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); - - const bool bInTreatExistingMaterialsAsUpToDate = true; - const bool bOnlyUseExistingAssets = true; - const bool bResult = CreateAllResultObjectsFromPDGOutputs( - InOutputs, - InPackageParams, - WorkItemOutputActor->GetRootComponent(), - InTOPNode->GetLandscapeExtent(), - InTOPNode->GetLandscapeReferenceLocation(), - InTOPNode->GetLandscapeSizeInfo(), - InTOPNode->ClearedLandscapeLayers, - AllInputLandscapes, - InputLandscapesToUpdate, - InOutputTypesToProcess, - bInTreatExistingMaterialsAsUpToDate, - bOnlyUseExistingAssets, - InPreBuiltInstancedOutputPartData); - - if (!bResult) - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); - else - FHoudiniEngine::Get().FinishTaskSlateNotification( - LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); - - InTOPNode->UpdateOutputVisibilityInLevel(); - - return bResult; -} - -bool -FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( - TArray& InOutputs, - const FHoudiniPackageParams& InPackageParams, - UObject* InOuterComponent, - FHoudiniLandscapeExtent& CachedLandscapeExtent, - FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, - FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, - TSet& ClearedLandscapeLayers, - TArray AllInputLandscapes, - TArray InputLandscapesToUpdate, - TArray InOutputTypesToProcess, - bool bInTreatExistingMaterialsAsUpToDate, - bool bInOnlyUseExistingAssets, - const TMap* InPreBuiltInstancedOutputPartData - ) -{ - // Process the new/updated outputs via the various translators - // We try to maintain as much parity with the existing HoudiniAssetComponent workflow - // as possible. - - // // Before processing any of the output, - // // we need to get the min/max value for all Height volumes in this output (if any) - TMap LandscapeLayerGlobalMinimums; - TMap LandscapeLayerGlobalMaximums; - - for (UHoudiniOutput* CurOutput : InOutputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (OutputType == EHoudiniOutputType::Landscape) - { - // Populate all layer minimums/maximums with default values since, in PDG mode, - // we can't collect the values across all tiles. The user would have to do this - // manually in the Topnet. - FHoudiniLandscapeTranslator::GetLayersZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums); - } - } - - TArray InstancerOutputs; - TArray LandscapeOutputs; - TArray CreatedPackages; - - //bool bCreatedNewMaps = false; - UWorld* PersistentWorld = InOuterComponent->GetTypedOuter(); - check(PersistentWorld); - - // Keep track of all generated houdini materials to avoid recreating them over and over - TMap AllOutputMaterials; - - for (UHoudiniOutput* CurOutput : InOutputs) - { - const EHoudiniOutputType OutputType = CurOutput->GetType(); - if (InOutputTypesToProcess.Num() > 0 && !InOutputTypesToProcess.Contains(OutputType)) - { - continue; - } - switch (OutputType) - { - case EHoudiniOutputType::Mesh: - { - const bool bInDestroyProxies = false; - if (bInOnlyUseExistingAssets) - { - const bool bInApplyGenericProperties = false; - TMap NewOutputObjects(CurOutput->GetOutputObjects()); - FHoudiniMeshTranslator::CreateOrUpdateAllComponents( - CurOutput, - InOuterComponent, - NewOutputObjects, - bInDestroyProxies, - bInApplyGenericProperties - ); - } - else - { - // TODO: If Outer is an HAC, get SMGP/MBS from it ?? - FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); - FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); - FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( - CurOutput, - InPackageParams, - EHoudiniStaticMeshMethod::RawMesh, - SMGP, - MBS, - AllOutputMaterials, - InOuterComponent, - bInTreatExistingMaterialsAsUpToDate, - bInDestroyProxies - ); - } - } - break; - - case EHoudiniOutputType::Curve: - { - // Output curve - FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, InOuterComponent); - break; - } - - case EHoudiniOutputType::Instancer: - { - InstancerOutputs.Add(CurOutput); - } - break; - - case EHoudiniOutputType::Landscape: - { - // Retrieve the topnet parent to which Sharedlandscapes will be attached. - AActor* WorkItemActor = InOuterComponent->GetTypedOuter(); - USceneComponent* TopnetParent = nullptr; - if (WorkItemActor) - { - AActor* TopnetParentActor = WorkItemActor->GetAttachParentActor(); - if (TopnetParentActor) - { - TopnetParent = TopnetParentActor->GetRootComponent(); - } - } - TArray> CreatedUntrackedOutputs; - - FHoudiniLandscapeTranslator::CreateLandscape( - CurOutput, - CreatedUntrackedOutputs, - InputLandscapesToUpdate, - AllInputLandscapes, - TopnetParent, - TEXT("{hda_actor_name}_{pdg_topnet_name}_"), - PersistentWorld, - LandscapeLayerGlobalMinimums, - LandscapeLayerGlobalMaximums, - CachedLandscapeExtent, - CachedLandscapeSizeInfo, - CachedLandscapeRefLoc, - InPackageParams, - //bCreatedNewMaps, - ClearedLandscapeLayers, - CreatedPackages); - // Attach any landscape actors to InOuterComponent - LandscapeOutputs.Add(CurOutput); - } - break; - - default: - { - HOUDINI_LOG_WARNING(TEXT("[FTOPWorkResultObject::UpdateResultOutputs]: Unsupported output type: %s"), *UHoudiniOutput::OutputTypeToString(OutputType)); - } - break; - } - - for (auto& CurMat : CurOutput->GetAssignementMaterials()) - { - //Adds the generated materials if any - if (!AllOutputMaterials.Contains(CurMat.Key)) - AllOutputMaterials.Add(CurMat); - } - } - - // Process instancer outputs after all other outputs have been processed, since it - // might depend on meshes etc from other outputs - if (InstancerOutputs.Num() > 0) - { - for (UHoudiniOutput* CurOutput : InstancerOutputs) - { - FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( - CurOutput, - InOutputs, - InOuterComponent, - InPreBuiltInstancedOutputPartData); - } - } - - USceneComponent* ParentComponent = Cast(InOuterComponent); - - if (ParentComponent) - { - AActor* ParentActor = ParentComponent->GetOwner(); - for (UHoudiniOutput* LandscapeOutput : LandscapeOutputs) - { - for (auto& Pair : LandscapeOutput->GetOutputObjects()) - { - FHoudiniOutputObject &OutputObject = Pair.Value; - - // If the OutputObject has an OutputComponent, try to attach it to ParentComponent - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) - { - USceneComponent* Component = Cast(OutputObject.OutputComponent); - if (IsValid(Component) && !Component->IsAttachedTo(ParentComponent)) - { - Component->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepWorldTransform); - continue; - } - } - - // If OutputObject does not have an OutputComponent, check if OutputObject is an AActor and attach - // it to ParentComponent - if (IsValid(OutputObject.OutputObject)) - { - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - if (IsValid(LandscapePtr)) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); - if (IsValid(LandscapeProxy)) - { - if (!LandscapeProxy->IsAttachedTo(ParentActor)) - { - LandscapeProxy->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform); - LandscapeProxy->RecreateCollisionComponents(); - } - - if (LandscapeProxy) - { - // We need to recreate component states for landscapes if a tile was created, moved, or resized - // otherwise the landscape will exhibit render artifacts (such as only rendering every other - // component.) - LandscapeProxy->RecreateComponentsState(); - } - } - - } - } - } - } - } - - /* - if (bCreatedNewMaps && PersistentWorld) - { - // Force the asset registry to update its cache of packages paths - // recursively for this world, otherwise world composition won't - // pick them up during the WorldComposition::Rescan(). - FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); - - ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); - - if (IsValid(PersistentWorld->WorldComposition)) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); - } - - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); - } - */ - - if (CreatedPackages.Num() > 0) - { - // Save created packages. For example, we don't want landscape layers deleted - // along with the HDA. - FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); - } - - return true; -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGTranslator.h" + + +#include "Editor.h" +#include "Containers/Array.h" +#include "FileHelpers.h" +#include "LandscapeInfo.h" +// #include "Engine/WorldComposition.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniPackageParams.h" +#include "HoudiniOutput.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniSplineTranslator.h" +#include "HoudiniTranslatorTypes.h" + +#define LOCTEXT_NAMESPACE "HoudiniEngine" + +bool +FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray InOutputTypesToProcess, + bool bInTreatExistingMaterialsAsUpToDate) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem); + + if (!IsValid(InAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InAssetLink is null.")); + return false; + } + + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::CreateAllResultObjectsForPDGWorkItem]: InTOPNode is null.")); + return false; + } + + TArray OldTOPOutputs = InWorkResultObject.GetResultOutputs(); + TArray NewTOPOutputs; + + FHoudiniEngine::Get().CreateTaskSlateNotification(LOCTEXT("LoadPDGBGEO", "Loading PDG Output BGEO File...")); + + bool bResult = false; + // Create a new file node in HAPI for the bgeo and cook it + HAPI_NodeId FileNodeId = -1; + bResult = UHoudiniGeoImporter::OpenBGEOFile(InWorkResultObject.FilePath, FileNodeId); + if (bResult) + bResult = UHoudiniGeoImporter::CookFileNode(FileNodeId); + + // If the cook was successful, build outputs + if (bResult) + { + FHoudiniEngine::Get().UpdateTaskSlateNotification( + LOCTEXT("BuildPDGBGEOOutputs", "Building Ouputs from BGEO File...")); + + const bool bAddOutputsToRootSet = false; + bResult = UHoudiniGeoImporter::BuildAllOutputsForNode( + FileNodeId, + InAssetLink, + OldTOPOutputs, + NewTOPOutputs, + bAddOutputsToRootSet); + } + + if (bResult) + { + FHoudiniEngine::Get().UpdateTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); + + // If we successfully received outputs from the BGEO file, process the outputs + FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); + AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + if (!IsValid(WorkItemOutputActor)) + { + UWorld* World = InAssetLink->GetWorld(); + FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); + AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + if (!IsValid(TOPNodeOutputActor)) + { + if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) + TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + } + if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) + WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + } + + for (auto& OldOutput : OldTOPOutputs) + { + FHoudiniOutputTranslator::ClearOutput(OldOutput); + } + OldTOPOutputs.Empty(); + InWorkResultObject.GetResultOutputs().Empty(); + InWorkResultObject.SetResultOutputs(NewTOPOutputs); + + // Gather landscape actors from inputs. + // NOTE: If performance becomes a problem, cache these on the TOPNode along with all the other cached landscape + // data. + TArray AllInputLandscapes; + TArray InputLandscapesToUpdate; + UHoudiniAssetComponent* HAC = InAssetLink->GetOuterHoudiniAssetComponent(); + FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); + + bResult = CreateAllResultObjectsFromPDGOutputs( + NewTOPOutputs, + InPackageParams, + WorkItemOutputActor->GetRootComponent(), + InTOPNode->GetLandscapeExtent(), + InTOPNode->GetLandscapeReferenceLocation(), + InTOPNode->GetLandscapeSizeInfo(), + InTOPNode->ClearedLandscapeLayers, + AllInputLandscapes, + InputLandscapesToUpdate, + InOutputTypesToProcess, + bInTreatExistingMaterialsAsUpToDate); + + if (!bResult) + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); + else + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); + + InTOPNode->UpdateOutputVisibilityInLevel(); + } + else + { + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("BuildPDGBGEOOutputsFail", "Failed building outputs from BGEO file...")); + } + + // Delete the file node used to load the BGEO via HAPI + if (FileNodeId >= 0) + { + UHoudiniGeoImporter::CloseBGEOFile(FileNodeId); + FileNodeId = -1; + } + + return bResult; +} + +bool +FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray& InOutputs, + TArray InOutputTypesToProcess, + const TMap* InPreBuiltInstancedOutputPartData) +{ + if (!IsValid(InAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InAssetLink is null.")); + return false; + } + + if (!IsValid(InTOPNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniPDGTranslator::LoadExistingAssetsAsResultObjectsForPDGWorkItem]: InTOPNode is null.")); + return false; + } + + FHoudiniEngine::Get().CreateTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputs", "Translating PDG/BGEO Outputs...")); + + // If we successfully received outputs from the BGEO file, process the outputs + FOutputActorOwner& WROOutputActorOwner = InWorkResultObject.GetOutputActorOwner(); + AActor* WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + if (!IsValid(WorkItemOutputActor)) + { + UWorld* World = InAssetLink->GetWorld(); + FOutputActorOwner& NodeOutputActorOwner = InTOPNode->GetOutputActorOwner(); + AActor* TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + if (!IsValid(TOPNodeOutputActor)) + { + if (NodeOutputActorOwner.CreateOutputActor(World, InAssetLink, InAssetLink->OutputParentActor, FName(InTOPNode->NodeName))) + TOPNodeOutputActor = NodeOutputActorOwner.GetOutputActor(); + } + if (WROOutputActorOwner.CreateOutputActor(World, InAssetLink, TOPNodeOutputActor, FName(InWorkResultObject.Name))) + WorkItemOutputActor = WROOutputActorOwner.GetOutputActor(); + } + + InWorkResultObject.SetResultOutputs(InOutputs); + + // Gather landscape actors from inputs. + // NOTE: If performance becomes a problem, cache these on the TOPNode along with all the other cached landscape + // data. + TArray AllInputLandscapes; + TArray InputLandscapesToUpdate; + UHoudiniAssetComponent* HAC = InAssetLink->GetOuterHoudiniAssetComponent(); + FHoudiniEngineUtils::GatherLandscapeInputs(HAC, AllInputLandscapes, InputLandscapesToUpdate); + + const bool bInTreatExistingMaterialsAsUpToDate = true; + const bool bOnlyUseExistingAssets = true; + const bool bResult = CreateAllResultObjectsFromPDGOutputs( + InOutputs, + InPackageParams, + WorkItemOutputActor->GetRootComponent(), + InTOPNode->GetLandscapeExtent(), + InTOPNode->GetLandscapeReferenceLocation(), + InTOPNode->GetLandscapeSizeInfo(), + InTOPNode->ClearedLandscapeLayers, + AllInputLandscapes, + InputLandscapesToUpdate, + InOutputTypesToProcess, + bInTreatExistingMaterialsAsUpToDate, + bOnlyUseExistingAssets, + InPreBuiltInstancedOutputPartData); + + if (!bResult) + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsFail", "Failed to translate all PDG/BGEO Outputs...")); + else + FHoudiniEngine::Get().FinishTaskSlateNotification( + LOCTEXT("TranslatePDGBGEOOutputsDone", "Done: Translating PDG/BGEO Outputs.")); + + InTOPNode->UpdateOutputVisibilityInLevel(); + + return bResult; +} + +bool +FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( + TArray& InOutputs, + const FHoudiniPackageParams& InPackageParams, + UObject* InOuterComponent, + FHoudiniLandscapeExtent& CachedLandscapeExtent, + FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, + FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, + TSet& ClearedLandscapeLayers, + TArray AllInputLandscapes, + TArray InputLandscapesToUpdate, + TArray InOutputTypesToProcess, + bool bInTreatExistingMaterialsAsUpToDate, + bool bInOnlyUseExistingAssets, + const TMap* InPreBuiltInstancedOutputPartData + ) +{ + // Process the new/updated outputs via the various translators + // We try to maintain as much parity with the existing HoudiniAssetComponent workflow + // as possible. + + + // // Before processing any of the output, + // // we need to get the min/max value for all Height volumes in this output (if any) + TMap LandscapeLayerGlobalMinimums; + TMap LandscapeLayerGlobalMaximums; + + for (UHoudiniOutput* CurOutput : InOutputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (OutputType == EHoudiniOutputType::Landscape) + { + // Populate all layer minimums/maximums with default values since, in PDG mode, + // we can't collect the values across all tiles. The user would have to do this + // manually in the Topnet. + FHoudiniLandscapeTranslator::GetLayersZMinZMax(CurOutput->GetHoudiniGeoPartObjects(), LandscapeLayerGlobalMinimums, LandscapeLayerGlobalMaximums); + } + } + + TArray InstancerOutputs; + TArray LandscapeOutputs; + TArray CreatedPackages; + + //bool bCreatedNewMaps = false; + UWorld* PersistentWorld = InOuterComponent->GetTypedOuter(); + check(PersistentWorld); + + // Keep track of all generated houdini materials to avoid recreating them over and over + TMap AllOutputMaterials; + + for (UHoudiniOutput* CurOutput : InOutputs) + { + const EHoudiniOutputType OutputType = CurOutput->GetType(); + if (InOutputTypesToProcess.Num() > 0 && !InOutputTypesToProcess.Contains(OutputType)) + { + continue; + } + switch (OutputType) + { + case EHoudiniOutputType::Mesh: + { + const bool bInDestroyProxies = false; + if (bInOnlyUseExistingAssets) + { + const bool bInApplyGenericProperties = false; + TMap NewOutputObjects(CurOutput->GetOutputObjects()); + FHoudiniMeshTranslator::CreateOrUpdateAllComponents( + CurOutput, + InOuterComponent, + NewOutputObjects, + bInDestroyProxies, + bInApplyGenericProperties + ); + } + else + { + // TODO: If Outer is an HAC, get SMGP/MBS from it ?? + FHoudiniStaticMeshGenerationProperties SMGP = FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties(); + FMeshBuildSettings MBS = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); + FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( + CurOutput, + InPackageParams, + EHoudiniStaticMeshMethod::RawMesh, + SMGP, + MBS, + AllOutputMaterials, + InOuterComponent, + bInTreatExistingMaterialsAsUpToDate, + bInDestroyProxies + ); + } + } + break; + + case EHoudiniOutputType::Curve: + { + // Output curve + FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(CurOutput, InOuterComponent); + break; + } + + case EHoudiniOutputType::Instancer: + { + InstancerOutputs.Add(CurOutput); + } + break; + + case EHoudiniOutputType::Landscape: + { + // Retrieve the topnet parent to which Sharedlandscapes will be attached. + AActor* WorkItemActor = InOuterComponent->GetTypedOuter(); + USceneComponent* TopnetParent = nullptr; + if (WorkItemActor) + { + AActor* TopnetParentActor = WorkItemActor->GetAttachParentActor(); + if (TopnetParentActor) + { + TopnetParent = TopnetParentActor->GetRootComponent(); + } + } + TArray> CreatedUntrackedOutputs; + + FHoudiniLandscapeTranslator::CreateLandscape( + CurOutput, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + AllInputLandscapes, + TopnetParent, + TEXT("{hda_actor_name}_{pdg_topnet_name}_"), + PersistentWorld, + LandscapeLayerGlobalMinimums, + LandscapeLayerGlobalMaximums, + CachedLandscapeExtent, + CachedLandscapeSizeInfo, + CachedLandscapeRefLoc, + InPackageParams, + //bCreatedNewMaps, + ClearedLandscapeLayers, + CreatedPackages); + // Attach any landscape actors to InOuterComponent + LandscapeOutputs.Add(CurOutput); + } + break; + + default: + { + HOUDINI_LOG_WARNING(TEXT("[FTOPWorkResultObject::UpdateResultOutputs]: Unsupported output type: %s"), *UHoudiniOutput::OutputTypeToString(OutputType)); + } + break; + } + + for (auto& CurMat : CurOutput->GetAssignementMaterials()) + { + //Adds the generated materials if any + if (!AllOutputMaterials.Contains(CurMat.Key)) + AllOutputMaterials.Add(CurMat); + } + } + + // Process instancer outputs after all other outputs have been processed, since it + // might depend on meshes etc from other outputs + if (InstancerOutputs.Num() > 0) + { + for (UHoudiniOutput* CurOutput : InstancerOutputs) + { + FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( + CurOutput, + InOutputs, + InOuterComponent, + InPackageParams, + InPreBuiltInstancedOutputPartData); + } + } + + USceneComponent* ParentComponent = Cast(InOuterComponent); + + if (ParentComponent) + { + AActor* ParentActor = ParentComponent->GetOwner(); + for (UHoudiniOutput* LandscapeOutput : LandscapeOutputs) + { + for (auto& Pair : LandscapeOutput->GetOutputObjects()) + { + FHoudiniOutputObject &OutputObject = Pair.Value; + + // If the OutputObject has an OutputComponent, try to attach it to ParentComponent + if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) + { + USceneComponent* Component = Cast(OutputObject.OutputComponent); + if (IsValid(Component) && !Component->IsAttachedTo(ParentComponent)) + { + Component->AttachToComponent(ParentComponent, FAttachmentTransformRules::KeepWorldTransform); + continue; + } + } + + // If OutputObject does not have an OutputComponent, check if OutputObject is an AActor and attach + // it to ParentComponent + if (IsValid(OutputObject.OutputObject)) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + if (IsValid(LandscapePtr)) + { + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapeProxy)) + { + if (!LandscapeProxy->IsAttachedTo(ParentActor)) + { + LandscapeProxy->AttachToActor(ParentActor, FAttachmentTransformRules::KeepWorldTransform); + LandscapeProxy->RecreateCollisionComponents(); + } + + if (LandscapeProxy) + { + // We need to recreate component states for landscapes if a tile was created, moved, or resized + // otherwise the landscape will exhibit render artifacts (such as only rendering every other + // component.) + LandscapeProxy->RecreateComponentsState(); + } + } + + } + } + } + } + } + + /* + if (bCreatedNewMaps && PersistentWorld) + { + // Force the asset registry to update its cache of packages paths + // recursively for this world, otherwise world composition won't + // pick them up during the WorldComposition::Rescan(). + FHoudiniEngineUtils::RescanWorldPath(PersistentWorld); + + ULandscapeInfo::RecreateLandscapeInfo(PersistentWorld, true); + + if (IsValid(PersistentWorld->WorldComposition)) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(PersistentWorld); + } + + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); + } + */ + + if (CreatedPackages.Num() > 0) + { + // Save created packages. For example, we don't want landscape layers deleted + // along with the HDA. + FEditorFileUtils::PromptForCheckoutAndSave(CreatedPackages, true, false); + } + + return true; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h index 689b1e9dd..8d68e31d9 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.h @@ -1,87 +1,87 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -class UHoudiniPDGAssetLink; -class UHoudiniOutput; -class AActor; -class UTOPNode; -class ALandscapeProxy; - -enum class EHoudiniOutputType : uint8; - -struct FHoudiniPackageParams; -struct FTOPWorkResultObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniInstancedOutputPartData; -struct FHoudiniLandscapeExtent; -struct FHoudiniLandscapeReferenceLocation; -struct FHoudiniLandscapeTileSizeInfo; - -struct HOUDINIENGINE_API FHoudiniPDGTranslator -{ - public: - // Create/update assets/geometry for all PDG outputs of InWorkResultObject. This will use - // InWorkResultObject.FilePath to load the BGEO file and update/build outputs. - static bool CreateAllResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray InOutputTypesToProcess={}, - bool bInTreatExistingMaterialsAsUpToDate=false); - - static bool LoadExistingAssetsAsResultObjectsForPDGWorkItem( - UHoudiniPDGAssetLink* InAssetLink, - UTOPNode* InTOPNode, - FTOPWorkResultObject& InWorkResultObject, - const FHoudiniPackageParams& InPackageParams, - TArray& InOutputs, - TArray InOutputTypesToProcess={}, - const TMap* InPreBuiltInstancedOutputPartData=nullptr); - - // Use the relevant translators to create assets/geometry for all PDG outputs (InOutputs). - // InOuterComponent is the component to attach the created output objects/components to. - static bool CreateAllResultObjectsFromPDGOutputs( - TArray& InOutputs, - const FHoudiniPackageParams& InPackageParams, - UObject* InOuterComponent, - FHoudiniLandscapeExtent& CachedLandscapeExtent, - FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, - FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, - TSet& ClearedLandscapeLayers, - TArray AllInputLandscapes, - TArray InputLandscapesToUpdate, - TArray InOutputTypesToProcess={}, - bool bInTreatExistingMaterialsAsUpToDate=false, - bool bInOnlyUseExistingAssets=false, - const TMap* InPreBuiltInstancedOutputPartData=nullptr - ); -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +class UHoudiniPDGAssetLink; +class UHoudiniOutput; +class AActor; +class UTOPNode; +class ALandscapeProxy; + +enum class EHoudiniOutputType : uint8; + +struct FHoudiniPackageParams; +struct FTOPWorkResultObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniInstancedOutputPartData; +struct FHoudiniLandscapeExtent; +struct FHoudiniLandscapeReferenceLocation; +struct FHoudiniLandscapeTileSizeInfo; + +struct HOUDINIENGINE_API FHoudiniPDGTranslator +{ + public: + // Create/update assets/geometry for all PDG outputs of InWorkResultObject. This will use + // InWorkResultObject.FilePath to load the BGEO file and update/build outputs. + static bool CreateAllResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray InOutputTypesToProcess={}, + bool bInTreatExistingMaterialsAsUpToDate=false); + + static bool LoadExistingAssetsAsResultObjectsForPDGWorkItem( + UHoudiniPDGAssetLink* InAssetLink, + UTOPNode* InTOPNode, + FTOPWorkResultObject& InWorkResultObject, + const FHoudiniPackageParams& InPackageParams, + TArray& InOutputs, + TArray InOutputTypesToProcess={}, + const TMap* InPreBuiltInstancedOutputPartData=nullptr); + + // Use the relevant translators to create assets/geometry for all PDG outputs (InOutputs). + // InOuterComponent is the component to attach the created output objects/components to. + static bool CreateAllResultObjectsFromPDGOutputs( + TArray& InOutputs, + const FHoudiniPackageParams& InPackageParams, + UObject* InOuterComponent, + FHoudiniLandscapeExtent& CachedLandscapeExtent, + FHoudiniLandscapeReferenceLocation& CachedLandscapeRefLoc, + FHoudiniLandscapeTileSizeInfo& CachedLandscapeSizeInfo, + TSet& ClearedLandscapeLayers, + TArray AllInputLandscapes, + TArray InputLandscapesToUpdate, + TArray InOutputTypesToProcess={}, + bool bInTreatExistingMaterialsAsUpToDate=false, + bool bInOnlyUseExistingAssets=false, + const TMap* InPreBuiltInstancedOutputPartData=nullptr + ); +}; + diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp index 70be4b32e..7c7c7e4bc 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp @@ -1,437 +1,425 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPackageParams.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniStringResolver.h" - -#include "PackageTools.h" -#include "ObjectTools.h" -#include "Engine/StaticMesh.h" -#include "UObject/MetaData.h" - -// -FHoudiniPackageParams::FHoudiniPackageParams() -{ - PackageMode = EPackageMode::CookToTemp; - ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - - TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - OuterPackage = nullptr; - ObjectName = FString(); - HoudiniAssetName = FString(); - HoudiniAssetActorName = FString(); - - ObjectId = 0; - GeoId = 0; - PartId = 0; - SplitStr = 0; - - ComponentGUID.Invalidate(); - - PDGTOPNetworkName.Empty(); - PDGTOPNodeName.Empty(); - PDGWorkItemIndex = INDEX_NONE; -} - - -// -FHoudiniPackageParams::~FHoudiniPackageParams() -{ - - -} - - -// Returns the object flags corresponding to the current package mode -EObjectFlags -FHoudiniPackageParams::GetObjectFlags() const -{ - if (PackageMode == EPackageMode::CookToTemp) - return RF_Public | RF_Standalone; - else if (PackageMode == EPackageMode::Bake) - return RF_Public | RF_Standalone; - else - return RF_NoFlags; -} - -FString -FHoudiniPackageParams::GetPackageName() const -{ - if (!ObjectName.IsEmpty()) - return ObjectName; - - // If we have PDG infos, generate a name including them - if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkItemIndex >= 0) - { - return FString::Printf( - TEXT("%s_%s_%s_%d_%d_%s"), - *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkItemIndex, PartId, *SplitStr); - } - else - { - // Generate an object name using the HGPO IDs and the HDA name - return FString::Printf(TEXT("%s_%d_%d_%d_%s"), *HoudiniAssetName, ObjectId, GeoId, PartId, *SplitStr); - } -} - -FString -FHoudiniPackageParams::GetPackagePath() const -{ - FString PackagePath = FString(); - switch (PackageMode) - { - case EPackageMode::CookToLevel: - { - // Path to the persistent level - //PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()); - - // In this mode, we'll use the persistent level as our package's outer - // simply use the hda + component guid for the path - // Add a subdir for the HDA - if (!HoudiniAssetName.IsEmpty()) - PackagePath += TEXT("/") + HoudiniAssetName; - // Add a subdir using the owner component GUID if possible - if(ComponentGUID.IsValid()) - PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); - - // TODO: FIX ME!!! - // Old version - // Build the package name - PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()) + - TEXT("/") + - HoudiniAssetName; - } - break; - - case EPackageMode::CookToTemp: - { - // Temporary Folder - PackagePath = TempCookFolder; - // Add a subdir for the HDA - if (!HoudiniAssetName.IsEmpty()) - PackagePath += TEXT("/") + HoudiniAssetName; - // Add a subdir using the owner component GUID if possible - if (ComponentGUID.IsValid()) - PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); - } - break; - - case EPackageMode::Bake: - { - PackagePath = BakeFolder; - } - break; - } - - return PackagePath; -} - -bool -FHoudiniPackageParams::GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter) -{ - OutBakeCounter = 0; - - if (!IsValid(InAsset)) - return false; - - UPackage* Package = InAsset->GetPackage(); - // const FString PackagePathName = Package->GetPathName(); - // FString PackagePathNamePrefix; - // FString BakeCountOrGUID; - // if (!GetPackageNameWithoutBakeCounterOrGUIDSuffix(PackagePathName, PackagePathNamePrefix, BakeCountOrGUID)) - // PackagePathNamePrefix = PackagePathName; - // - // const FString ThisPackageNameBase = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); - // if (!PackagePathNamePrefix.Equals(ThisPackageNameBase)) - // return false; - // - // // Not a valid counter suffix, could be a GUID suffix. Return true since the prefixes match. - // if (BakeCountOrGUID.IsNumeric()) - // OutBakeCounter = FCString::Atoi(*BakeCountOrGUID); - // - // return true; - - if (!IsValid(Package)) - return false; - - UMetaData* MetaData = Package->GetMetaData(); - if (!IsValid(MetaData)) - return false; - - if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER)) - { - FString BakeCounterStr = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER); - BakeCounterStr.TrimStartAndEndInline(); - if (BakeCounterStr.IsNumeric()) - { - OutBakeCounter = FCString::Atoi(*BakeCounterStr); - return true; - } - } - - return false; -} - -bool -FHoudiniPackageParams::GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID) -{ - if (!InAsset) - return false; - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return false; - - UMetaData* MetaData = Package->GetMetaData(); - if (!IsValid(MetaData)) - return false; - - if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) - { - OutGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); - OutGUID.TrimStartAndEndInline(); - if (!OutGUID.IsEmpty()) - return true; - } - - return false; -} - -FString -FHoudiniPackageParams::GetPackageNameExcludingBakeCounter(const UObject* InAsset) -{ - if (!IsValid(InAsset)) - return FString(); - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return FString(); - - FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); - int32 BakeCounter = 0; - if (GetBakeCounterFromBakedAsset(InAsset, BakeCounter)) - { - const FString BakeCounterSuffix = FString::Printf(TEXT("_%d"), BakeCounter); - if (PackageName.EndsWith(BakeCounterSuffix)) - PackageName = PackageName.Mid(0, PackageName.Len() - BakeCounterSuffix.Len()); - } - - return PackageName; -} - -bool -FHoudiniPackageParams::MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const -{ - if (!IsValid(InAsset)) - return false; - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return false; - - const FString InAssetPackagePathName = FPaths::GetPath(Package->GetPathName()) + TEXT("/") + GetPackageNameExcludingBakeCounter(InAsset); - const FString ThisPackagePathName = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); - return InAssetPackagePathName.Equals(ThisPackagePathName); -} - -FString -FHoudiniPackageParams::GetPackageNameExcludingGUID(const UObject* InAsset) -{ - if (!IsValid(InAsset)) - return FString(); - - UPackage* Package = InAsset->GetPackage(); - if (!IsValid(Package)) - return FString(); - - FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); - FString GUIDStr; - if (GetGUIDFromTempAsset(InAsset, GUIDStr)) - { - if (PackageName.EndsWith(TEXT("_") + GUIDStr)) - PackageName = PackageName.Mid(0, PackageName.Len() - GUIDStr.Len() - 1); - } - - return PackageName; -} - -UPackage* -FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart) const -{ - // GUID/counter used to differentiate with existing package - int32 BakeCounter = InBakeCounterStart; - FGuid CurrentGuid = FGuid::NewGuid(); - - // Get the appropriate package path/name for this object - FString PackageName = GetPackageName(); - FString PackagePath = GetPackagePath(); - - // Iterate until we find a suitable name for the package - UPackage * NewPackage = nullptr; - while (true) - { - OutPackageName = PackageName; - - // Append the Bake guid/counter to the object name if needed - if (BakeCounter > 0) - { - OutPackageName += (PackageMode == EPackageMode::Bake) - ? TEXT("_") + FString::FromInt(BakeCounter) - : TEXT("_") + CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); - } - - // Build the final package name - FString FinalPackageName = PackagePath + TEXT("/") + OutPackageName; - // Sanitize package name. - FinalPackageName = UPackageTools::SanitizePackageName(FinalPackageName); - - UObject * PackageOuter = nullptr; - /* - // As of UE4.26, it is not possible anymore to create package with a non null outer - // CookToLevel is, anyway, no logner supported in v2. - if (PackageMode == EPackageMode::CookToLevel) - { - // If we are not baking, then use outermost package, since objects within our package - // need to be visible to external operations, such as copy paste. - PackageOuter = OuterPackage; - } - */ - - // If we are set to create new assets, check if a package named similarly already exists - if (ReplaceMode == EPackageReplaceMode::CreateNewAssets) - { - UPackage* FoundPackage = FindPackage(PackageOuter, *FinalPackageName); - if (FoundPackage == nullptr) - { - // Package might not be in memory, check if it exists on disk - FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_Verify | LOAD_NoWarn); - } - - if (FoundPackage && !FoundPackage->IsPendingKill()) - { - // we need to generate a new name for it - CurrentGuid = FGuid::NewGuid(); - BakeCounter++; - continue; - } - } - - // Create actual package. - NewPackage = CreatePackage(*FinalPackageName); - if (IsValid(NewPackage)) - { - // Record bake counter / temp GUID in package metadata - UMetaData* MetaData = NewPackage->GetMetaData(); - if (IsValid(MetaData)) - { - if (PackageMode == EPackageMode::Bake) - { - // HOUDINI_LOG_MESSAGE(TEXT("Recording bake counter in package metadata: %d"), BakeCounter); - MetaData->RootMetaDataMap.Add( - HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER, FString::FromInt(BakeCounter)); - } - else if (CurrentGuid.IsValid()) - { - const FString GuidStr = CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); - // HOUDINI_LOG_MESSAGE(TEXT("Recording temp guid in package metadata: %s"), *GuidStr); - MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_TEMP_GUID, GuidStr); - } - } - } - - break; - } - - return NewPackage; -} - - -// Fixes link error with the template function under -void TemplateFixer() -{ - FHoudiniPackageParams PP; - UStaticMesh* SM = PP.CreateObjectAndPackage(); - UHoudiniStaticMesh* HSM = PP.CreateObjectAndPackage(); - //UMaterial* Mat = PP.CreateObjectAndPackage(); - //UTexture2D* Text = PP.CreateObjectAndPackage(); -} - -template -T* FHoudiniPackageParams::CreateObjectAndPackage() -{ - // Create the package for the object - FString NewObjectName; - UPackage* Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) - return nullptr; - - const FString SanitizedObjectName = ObjectTools::SanitizeObjectName(NewObjectName); - - T* ExistingTypedObject = FindObject(Package, *NewObjectName); - UObject* ExistingObject = FindObject(Package, *NewObjectName); - - if (ExistingTypedObject != nullptr && !ExistingTypedObject->IsPendingKill()) - { - // An object of the appropriate type already exists, update it! - ExistingTypedObject->PreEditChange(nullptr); - } - else if (ExistingObject != nullptr) - { - // Replacing an object of a different type, Delete it first. - const bool bDeleteSucceeded = ObjectTools::DeleteSingleObject(ExistingObject); - if (bDeleteSucceeded) - { - // Force GC so we can cleanly create a new asset (and not do an 'in place' replacement) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - // Create a package for each mesh - Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) - return nullptr; - } - else - { - // failed to delete - return nullptr; - } - } - - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); - - return NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPackageParams.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniStringResolver.h" + +#include "PackageTools.h" +#include "ObjectTools.h" +#include "Engine/StaticMesh.h" +#include "UObject/MetaData.h" + +// +FHoudiniPackageParams::FHoudiniPackageParams() +{ + PackageMode = EPackageMode::CookToTemp; + ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + + TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + BakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + OuterPackage = nullptr; + ObjectName = FString(); + HoudiniAssetName = FString(); + HoudiniAssetActorName = FString(); + + ObjectId = 0; + GeoId = 0; + PartId = 0; + SplitStr = 0; + + ComponentGUID.Invalidate(); + + PDGTOPNetworkName.Empty(); + PDGTOPNodeName.Empty(); + PDGWorkItemIndex = INDEX_NONE; +} + + +// +FHoudiniPackageParams::~FHoudiniPackageParams() +{ + + +} + + +// Returns the object flags corresponding to the current package mode +EObjectFlags +FHoudiniPackageParams::GetObjectFlags() const +{ + if (PackageMode == EPackageMode::CookToTemp) + return RF_Public | RF_Standalone; + else if (PackageMode == EPackageMode::Bake) + return RF_Public | RF_Standalone; + else + return RF_NoFlags; +} + +FString +FHoudiniPackageParams::GetPackageName() const +{ + if (!ObjectName.IsEmpty()) + return ObjectName; + + // If we have PDG infos, generate a name including them + if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkItemIndex >= 0) + { + return FString::Printf( + TEXT("%s_%s_%s_%d_%d_%s"), + *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkItemIndex, PartId, *SplitStr); + } + else + { + // Generate an object name using the HGPO IDs and the HDA name + return FString::Printf(TEXT("%s_%d_%d_%d_%s"), *HoudiniAssetName, ObjectId, GeoId, PartId, *SplitStr); + } +} + +FString +FHoudiniPackageParams::GetPackagePath() const +{ + FString PackagePath = FString(); + switch (PackageMode) + { + case EPackageMode::CookToLevel: + { + // Path to the persistent level + //PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()); + + // In this mode, we'll use the persistent level as our package's outer + // simply use the hda + component guid for the path + // Add a subdir for the HDA + if (!HoudiniAssetName.IsEmpty()) + PackagePath += TEXT("/") + HoudiniAssetName; + // Add a subdir using the owner component GUID if possible + if(ComponentGUID.IsValid()) + PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); + + // TODO: FIX ME!!! + // Old version + // Build the package name + PackagePath = FPackageName::GetLongPackagePath(OuterPackage->GetOuter()->GetName()) + + TEXT("/") + + HoudiniAssetName; + } + break; + + case EPackageMode::CookToTemp: + { + // Temporary Folder + PackagePath = TempCookFolder; + // Add a subdir for the HDA + if (!HoudiniAssetName.IsEmpty()) + PackagePath += TEXT("/") + HoudiniAssetName; + // Add a subdir using the owner component GUID if possible + if (ComponentGUID.IsValid()) + PackagePath += TEXT("/") + ComponentGUID.ToString().Left(PACKAGE_GUID_LENGTH); + } + break; + + case EPackageMode::Bake: + { + PackagePath = BakeFolder; + } + break; + } + + return PackagePath; +} + +bool +FHoudiniPackageParams::GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter) +{ + OutBakeCounter = 0; + + if (!IsValid(InAsset)) + return false; + + UPackage* Package = InAsset->GetPackage(); + // const FString PackagePathName = Package->GetPathName(); + // FString PackagePathNamePrefix; + // FString BakeCountOrGUID; + // if (!GetPackageNameWithoutBakeCounterOrGUIDSuffix(PackagePathName, PackagePathNamePrefix, BakeCountOrGUID)) + // PackagePathNamePrefix = PackagePathName; + // + // const FString ThisPackageNameBase = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); + // if (!PackagePathNamePrefix.Equals(ThisPackageNameBase)) + // return false; + // + // // Not a valid counter suffix, could be a GUID suffix. Return true since the prefixes match. + // if (BakeCountOrGUID.IsNumeric()) + // OutBakeCounter = FCString::Atoi(*BakeCountOrGUID); + // + // return true; + + if (!IsValid(Package)) + return false; + + UMetaData* MetaData = Package->GetMetaData(); + if (!IsValid(MetaData)) + return false; + + if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER)) + { + FString BakeCounterStr = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER); + BakeCounterStr.TrimStartAndEndInline(); + if (BakeCounterStr.IsNumeric()) + { + OutBakeCounter = FCString::Atoi(*BakeCounterStr); + return true; + } + } + + return false; +} + +bool +FHoudiniPackageParams::GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID) +{ + if (!InAsset) + return false; + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return false; + + UMetaData* MetaData = Package->GetMetaData(); + if (!IsValid(MetaData)) + return false; + + if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) + { + OutGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); + OutGUID.TrimStartAndEndInline(); + if (!OutGUID.IsEmpty()) + return true; + } + + return false; +} + +FString +FHoudiniPackageParams::GetPackageNameExcludingBakeCounter(const UObject* InAsset) +{ + if (!IsValid(InAsset)) + return FString(); + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return FString(); + + FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); + int32 BakeCounter = 0; + if (GetBakeCounterFromBakedAsset(InAsset, BakeCounter)) + { + const FString BakeCounterSuffix = FString::Printf(TEXT("_%d"), BakeCounter); + if (PackageName.EndsWith(BakeCounterSuffix)) + PackageName = PackageName.Mid(0, PackageName.Len() - BakeCounterSuffix.Len()); + } + + return PackageName; +} + +bool +FHoudiniPackageParams::MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const +{ + if (!IsValid(InAsset)) + return false; + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return false; + + const FString InAssetPackagePathName = FPaths::GetPath(Package->GetPathName()) + TEXT("/") + GetPackageNameExcludingBakeCounter(InAsset); + const FString ThisPackagePathName = UPackageTools::SanitizePackageName(GetPackagePath() + TEXT("/") + GetPackageName()); + return InAssetPackagePathName.Equals(ThisPackagePathName); +} + +FString +FHoudiniPackageParams::GetPackageNameExcludingGUID(const UObject* InAsset) +{ + if (!IsValid(InAsset)) + return FString(); + + UPackage* Package = InAsset->GetPackage(); + if (!IsValid(Package)) + return FString(); + + FString PackageName = FPaths::GetCleanFilename(Package->GetPathName()); + FString GUIDStr; + if (GetGUIDFromTempAsset(InAsset, GUIDStr)) + { + if (PackageName.EndsWith(TEXT("_") + GUIDStr)) + PackageName = PackageName.Mid(0, PackageName.Len() - GUIDStr.Len() - 1); + } + + return PackageName; +} + +UPackage* +FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart) const +{ + // GUID/counter used to differentiate with existing package + int32 BakeCounter = InBakeCounterStart; + FGuid CurrentGuid = FGuid::NewGuid(); + + // Get the appropriate package path/name for this object + FString PackageName = GetPackageName(); + FString PackagePath = GetPackagePath(); + + // Iterate until we find a suitable name for the package + UPackage * NewPackage = nullptr; + while (true) + { + OutPackageName = PackageName; + + // Append the Bake guid/counter to the object name if needed + if (BakeCounter > 0) + { + OutPackageName += (PackageMode == EPackageMode::Bake) + ? TEXT("_") + FString::FromInt(BakeCounter) + : TEXT("_") + CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); + } + + // Build the final package name + FString FinalPackageName = PackagePath + TEXT("/") + OutPackageName; + // Sanitize package name. + FinalPackageName = UPackageTools::SanitizePackageName(FinalPackageName); + + // If we are set to create new assets, check if a package named similarly already exists + if (ReplaceMode == EPackageReplaceMode::CreateNewAssets) + { + UPackage* FoundPackage = FindPackage(nullptr, *FinalPackageName); + if (FoundPackage == nullptr) + { + // Package might not be in memory, check if it exists on disk + FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_Verify | LOAD_NoWarn); + } + + if (FoundPackage && !FoundPackage->IsPendingKill()) + { + // we need to generate a new name for it + CurrentGuid = FGuid::NewGuid(); + BakeCounter++; + continue; + } + } + + // Create actual package. + NewPackage = CreatePackage(*FinalPackageName); + if (IsValid(NewPackage)) + { + // Record bake counter / temp GUID in package metadata + UMetaData* MetaData = NewPackage->GetMetaData(); + if (IsValid(MetaData)) + { + if (PackageMode == EPackageMode::Bake) + { + // HOUDINI_LOG_MESSAGE(TEXT("Recording bake counter in package metadata: %d"), BakeCounter); + MetaData->RootMetaDataMap.Add( + HAPI_UNREAL_PACKAGE_META_BAKE_COUNTER, FString::FromInt(BakeCounter)); + } + else if (CurrentGuid.IsValid()) + { + const FString GuidStr = CurrentGuid.ToString().Left(PACKAGE_GUID_LENGTH); + // HOUDINI_LOG_MESSAGE(TEXT("Recording temp guid in package metadata: %s"), *GuidStr); + MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_TEMP_GUID, GuidStr); + } + } + } + + break; + } + + return NewPackage; +} + + +// Fixes link error with the template function under +void TemplateFixer() +{ + FHoudiniPackageParams PP; + UStaticMesh* SM = PP.CreateObjectAndPackage(); + UHoudiniStaticMesh* HSM = PP.CreateObjectAndPackage(); + //UMaterial* Mat = PP.CreateObjectAndPackage(); + //UTexture2D* Text = PP.CreateObjectAndPackage(); +} + +template +T* FHoudiniPackageParams::CreateObjectAndPackage() +{ + // Create the package for the object + FString NewObjectName; + UPackage* Package = CreatePackageForObject(NewObjectName); + if (!Package || Package->IsPendingKill()) + return nullptr; + + const FString SanitizedObjectName = ObjectTools::SanitizeObjectName(NewObjectName); + + T* ExistingTypedObject = FindObject(Package, *NewObjectName); + UObject* ExistingObject = FindObject(Package, *NewObjectName); + + if (ExistingTypedObject != nullptr && !ExistingTypedObject->IsPendingKill()) + { + // An object of the appropriate type already exists, update it! + ExistingTypedObject->PreEditChange(nullptr); + } + else if (ExistingObject != nullptr) + { + // Replacing an object of a different type, Delete it first. + const bool bDeleteSucceeded = ObjectTools::DeleteSingleObject(ExistingObject); + if (bDeleteSucceeded) + { + // Force GC so we can cleanly create a new asset (and not do an 'in place' replacement) + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Create a package for each mesh + Package = CreatePackageForObject(NewObjectName); + if (!Package || Package->IsPendingKill()) + return nullptr; + } + else + { + // failed to delete + return nullptr; + } + } + + // Add meta information to this package. + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); + + return NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); +} + diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.h b/Source/HoudiniEngine/Private/HoudiniPackageParams.h index d4b947635..380035125 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.h +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.h @@ -1,240 +1,240 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" -#include "UObject/ObjectMacros.h" -#include "Engine/World.h" -#include "Misc/Paths.h" - -#include "HoudiniStringResolver.h" - -#include "HoudiniPackageParams.generated.h" - -class UStaticMesh; - -UENUM() -enum class EPackageMode : int8 -{ - CookToLevel, - CookToTemp, - Bake -}; - -UENUM() -enum class EPackageReplaceMode : int8 -{ - CreateNewAssets, - ReplaceExistingAssets -}; - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniPackageParams -{ -public: - GENERATED_BODY(); - - // - FHoudiniPackageParams(); - // - ~FHoudiniPackageParams(); - - // Helper functions returning the default behavior expected when cooking mesh - static EPackageMode GetDefaultStaticMeshesCookMode() { return EPackageMode::CookToTemp; }; - // Helper functions returning the default behavior expected when cooking materials or textures - static EPackageMode GetDefaultMaterialAndTextureCookMode() { return EPackageMode::CookToTemp; }; - // Helper functions returning the default behavior for replacing existing package - static EPackageReplaceMode GetDefaultReplaceMode() { return EPackageReplaceMode::ReplaceExistingAssets; }; - - // Returns the name for the package depending on the mode - FString GetPackageName() const; - // Returns the package's path depending on the mode - FString GetPackagePath() const; - // Returns the object flags corresponding to the current package mode - EObjectFlags GetObjectFlags() const; - - // Get the bake counter for InAsset's package metadata. Return true if the counter was found, false otherwise. - static bool GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter); - - // Get the GUID for a temp asset. - static bool GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID); - - // Get package name without bake counter - static FString GetPackageNameExcludingBakeCounter(const UObject* InAsset); - - // Get package name without temp GUID suffix - static FString GetPackageNameExcludingGUID(const UObject* InAsset); - - // Returns true if these package params generate the same package path and name as InAsset's package path name (with - // any potential bake counters stripped during comparison) - bool MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const; - - // Helper function to create a Package for a given object - UPackage* CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart=0) const; - - // Helper function to create an object and its package - template T* CreateObjectAndPackage(); - - - // The current cook/baking mode - UPROPERTY() - EPackageMode PackageMode; - // How to handle existing assets? replace or rename? - UPROPERTY() - EPackageReplaceMode ReplaceMode; - - // When cooking in bake mode - folder to create assets in - UPROPERTY() - FString BakeFolder; - // When cooking in temp mode - folder to create assets in - UPROPERTY() - FString TempCookFolder; - - // Package to save to - UPROPERTY() - UObject* OuterPackage; - - // Name of the package we want to create - // If null, we'll generate one from: - // (without PDG) ASSET_OBJ_GEO_PART_SPLIT, - // (with PDG) ASSET_TOPNET_TOPNODE_WORKITEMINDEX_PART_SPLIT - UPROPERTY() - FString ObjectName; - - // Name of the HDA - UPROPERTY() - FString HoudiniAssetName; - - // Name of actor that is managing an instance of the HDA - UPROPERTY() - FString HoudiniAssetActorName; - - // - UPROPERTY() - int32 ObjectId; - // - UPROPERTY() - int32 GeoId; - // - UPROPERTY() - int32 PartId; - // - UPROPERTY() - FString SplitStr; - - // GUID used for the owner - UPROPERTY() - FGuid ComponentGUID; - - // For PDG temporary outputs: the TOP network name - UPROPERTY() - FString PDGTOPNetworkName; - // For PDG temporary outputs: the TOP node name - UPROPERTY() - FString PDGTOPNodeName; - // For PDG temporary outputs: the work item index of the TOP node - UPROPERTY() - int32 PDGWorkItemIndex; - - ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. - //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; - //FString GetBakeFolderArgument(ERuntimePackageMode PackageMode) const; - - //// Return the output path as either the temp or bake path, depending on the package mode. - //FString GetOutputFolderForPackageMode(ERuntimePackageMode PackageMode) const; - - /* - * Build a "standard" set of string formatting arguments that - * is typically used across HoudiniEngine path naming outputs. - * Note that each output type may contain additional named arguments - * that are not listed here. - * {out} - The output directory (varies depending on the package mode). - * {pkg} - The path to the destination package (varies depending on the package mode). - * {world} - Path the directory that contains the world. - * {hda_name} - Name of the HDA - * {guid} - guid of the HDA component - * @param PackageParams The output path for the current build mode (Temp / Bake). - * @param HACWorld The world in which the HDA component lives (typically Editor world). - * @param OutArgs The generated named arguments to be used for string formatting. - */ - - // Populate a map of named arguments from this FHoudiniPackageParams. - template - void UpdateTokensFromParams( - const UWorld* WorldContext, - const UHoudiniAssetComponent* HAC, - TMap& OutTokens) const - { - UpdateOutputPathTokens(PackageMode, OutTokens); - - if(IsValid(WorldContext)) - OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); - - if(HAC && HAC->GetOutermost()) - OutTokens.Add("hda_level", ValueT( HAC->GetOutermost()->GetPathName() )); - - OutTokens.Add("object_name", ValueT( ObjectName )); - OutTokens.Add("object_id", ValueT( FString::FromInt(ObjectId) )); - OutTokens.Add("geo_id", ValueT( FString::FromInt(GeoId) )); - OutTokens.Add("part_id", ValueT( FString::FromInt(PartId) )); - OutTokens.Add("split_str", ValueT( SplitStr)); - OutTokens.Add("hda_name", ValueT( HoudiniAssetName )); - OutTokens.Add("hda_actor_name", ValueT( HoudiniAssetActorName )); - OutTokens.Add("pdg_topnet_name", ValueT( PDGTOPNetworkName )); - OutTokens.Add("pdg_topnode_name", ValueT( PDGTOPNodeName )); - OutTokens.Add("pdg_workitem_index", ValueT( FString::FromInt(PDGWorkItemIndex) )); - OutTokens.Add("guid", ValueT( ComponentGUID.ToString() )); - } - - template - void UpdateOutputPathTokens(EPackageMode InPackageMode, TMap& OutTokens) const - { - const FString PackagePath = GetPackagePath(); - - OutTokens.Add("temp", ValueT(TempCookFolder)); - OutTokens.Add("bake", ValueT(BakeFolder)); - - // `out_basepath` is useful if users want to organize their cook/bake assets - // different to the convention defined by GetPackagePath(). This would typically - // be combined with `unreal_level_path` during level path resolves. - switch (InPackageMode) - { - case EPackageMode::CookToTemp: - case EPackageMode::CookToLevel: - OutTokens.Add("out_basepath", ValueT(TempCookFolder)); - break; - case EPackageMode::Bake: - OutTokens.Add("out_basepath", ValueT(BakeFolder)); - break; - } - - OutTokens.Add("out", ValueT(PackagePath)); - } - -}; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" +#include "UObject/ObjectMacros.h" +#include "Engine/World.h" +#include "Misc/Paths.h" + +#include "HoudiniStringResolver.h" + +#include "HoudiniPackageParams.generated.h" + +class UStaticMesh; + +UENUM() +enum class EPackageMode : int8 +{ + CookToLevel, + CookToTemp, + Bake +}; + +UENUM() +enum class EPackageReplaceMode : int8 +{ + CreateNewAssets, + ReplaceExistingAssets +}; + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniPackageParams +{ +public: + GENERATED_BODY(); + + // + FHoudiniPackageParams(); + // + ~FHoudiniPackageParams(); + + // Helper functions returning the default behavior expected when cooking mesh + static EPackageMode GetDefaultStaticMeshesCookMode() { return EPackageMode::CookToTemp; }; + // Helper functions returning the default behavior expected when cooking materials or textures + static EPackageMode GetDefaultMaterialAndTextureCookMode() { return EPackageMode::CookToTemp; }; + // Helper functions returning the default behavior for replacing existing package + static EPackageReplaceMode GetDefaultReplaceMode() { return EPackageReplaceMode::ReplaceExistingAssets; }; + + // Returns the name for the package depending on the mode + FString GetPackageName() const; + // Returns the package's path depending on the mode + FString GetPackagePath() const; + // Returns the object flags corresponding to the current package mode + EObjectFlags GetObjectFlags() const; + + // Get the bake counter for InAsset's package metadata. Return true if the counter was found, false otherwise. + static bool GetBakeCounterFromBakedAsset(const UObject* InAsset, int32& OutBakeCounter); + + // Get the GUID for a temp asset. + static bool GetGUIDFromTempAsset(const UObject* InAsset, FString& OutGUID); + + // Get package name without bake counter + static FString GetPackageNameExcludingBakeCounter(const UObject* InAsset); + + // Get package name without temp GUID suffix + static FString GetPackageNameExcludingGUID(const UObject* InAsset); + + // Returns true if these package params generate the same package path and name as InAsset's package path name (with + // any potential bake counters stripped during comparison) + bool MatchesPackagePathNameExcludingBakeCounter(const UObject* InAsset) const; + + // Helper function to create a Package for a given object + UPackage* CreatePackageForObject(FString& OutPackageName, int32 InBakeCounterStart=0) const; + + // Helper function to create an object and its package + template T* CreateObjectAndPackage(); + + + // The current cook/baking mode + UPROPERTY() + EPackageMode PackageMode; + // How to handle existing assets? replace or rename? + UPROPERTY() + EPackageReplaceMode ReplaceMode; + + // When cooking in bake mode - folder to create assets in + UPROPERTY() + FString BakeFolder; + // When cooking in temp mode - folder to create assets in + UPROPERTY() + FString TempCookFolder; + + // Package to save to + UPROPERTY() + UObject* OuterPackage; + + // Name of the package we want to create + // If null, we'll generate one from: + // (without PDG) ASSET_OBJ_GEO_PART_SPLIT, + // (with PDG) ASSET_TOPNET_TOPNODE_WORKITEMINDEX_PART_SPLIT + UPROPERTY() + FString ObjectName; + + // Name of the HDA + UPROPERTY() + FString HoudiniAssetName; + + // Name of actor that is managing an instance of the HDA + UPROPERTY() + FString HoudiniAssetActorName; + + // + UPROPERTY() + int32 ObjectId; + // + UPROPERTY() + int32 GeoId; + // + UPROPERTY() + int32 PartId; + // + UPROPERTY() + FString SplitStr; + + // GUID used for the owner + UPROPERTY() + FGuid ComponentGUID; + + // For PDG temporary outputs: the TOP network name + UPROPERTY() + FString PDGTOPNetworkName; + // For PDG temporary outputs: the TOP node name + UPROPERTY() + FString PDGTOPNodeName; + // For PDG temporary outputs: the work item index of the TOP node + UPROPERTY() + int32 PDGWorkItemIndex; + + ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. + //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; + //FString GetBakeFolderArgument(ERuntimePackageMode PackageMode) const; + + //// Return the output path as either the temp or bake path, depending on the package mode. + //FString GetOutputFolderForPackageMode(ERuntimePackageMode PackageMode) const; + + /* + * Build a "standard" set of string formatting arguments that + * is typically used across HoudiniEngine path naming outputs. + * Note that each output type may contain additional named arguments + * that are not listed here. + * {out} - The output directory (varies depending on the package mode). + * {pkg} - The path to the destination package (varies depending on the package mode). + * {world} - Path the directory that contains the world. + * {hda_name} - Name of the HDA + * {guid} - guid of the HDA component + * @param PackageParams The output path for the current build mode (Temp / Bake). + * @param HACWorld The world in which the HDA component lives (typically Editor world). + * @param OutArgs The generated named arguments to be used for string formatting. + */ + + // Populate a map of named arguments from this FHoudiniPackageParams. + template + void UpdateTokensFromParams( + const UWorld* WorldContext, + const UHoudiniAssetComponent* HAC, + TMap& OutTokens) const + { + UpdateOutputPathTokens(PackageMode, OutTokens); + + if(IsValid(WorldContext)) + OutTokens.Add("world", ValueT( FPaths::GetPath(WorldContext->GetPathName()) )); + + if(HAC && HAC->GetOutermost()) + OutTokens.Add("hda_level", ValueT( HAC->GetOutermost()->GetPathName() )); + + OutTokens.Add("object_name", ValueT( ObjectName )); + OutTokens.Add("object_id", ValueT( FString::FromInt(ObjectId) )); + OutTokens.Add("geo_id", ValueT( FString::FromInt(GeoId) )); + OutTokens.Add("part_id", ValueT( FString::FromInt(PartId) )); + OutTokens.Add("split_str", ValueT( SplitStr)); + OutTokens.Add("hda_name", ValueT( HoudiniAssetName )); + OutTokens.Add("hda_actor_name", ValueT( HoudiniAssetActorName )); + OutTokens.Add("pdg_topnet_name", ValueT( PDGTOPNetworkName )); + OutTokens.Add("pdg_topnode_name", ValueT( PDGTOPNodeName )); + OutTokens.Add("pdg_workitem_index", ValueT( FString::FromInt(PDGWorkItemIndex) )); + OutTokens.Add("guid", ValueT( ComponentGUID.ToString() )); + } + + template + void UpdateOutputPathTokens(EPackageMode InPackageMode, TMap& OutTokens) const + { + const FString PackagePath = GetPackagePath(); + + OutTokens.Add("temp", ValueT(TempCookFolder)); + OutTokens.Add("bake", ValueT(BakeFolder)); + + // `out_basepath` is useful if users want to organize their cook/bake assets + // different to the convention defined by GetPackagePath(). This would typically + // be combined with `unreal_level_path` during level path resolves. + switch (InPackageMode) + { + case EPackageMode::CookToTemp: + case EPackageMode::CookToLevel: + OutTokens.Add("out_basepath", ValueT(TempCookFolder)); + break; + case EPackageMode::Bake: + OutTokens.Add("out_basepath", ValueT(BakeFolder)); + break; + } + + OutTokens.Add("out", ValueT(PackagePath)); + } + +}; + + diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp index 048a622f9..6443de9bc 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp @@ -1,3516 +1,3516 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "HoudiniAsset.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniInput.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniParameter.h" -#include "HoudiniAssetComponent.h" - - -// Default values for certain UI min and max parameter values -#define HAPI_UNREAL_PARAM_INT_UI_MIN 0 -#define HAPI_UNREAL_PARAM_INT_UI_MAX 10 -#define HAPI_UNREAL_PARAM_FLOAT_UI_MIN 0.0f -#define HAPI_UNREAL_PARAM_FLOAT_UI_MAX 10.0f - -// Some default parameter name -#define HAPI_UNREAL_PARAM_TRANSLATE "t" -#define HAPI_UNREAL_PARAM_ROTATE "r" -#define HAPI_UNREAL_PARAM_SCALE "s" -#define HAPI_UNREAL_PARAM_PIVOT "p" -#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" - -// -bool -FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // When recooking/rebuilding the HDA, force a full update of all params - const bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested() || HAC->IsParameterDefinitionUpdateNeeded(); - - TArray NewParameters; - if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, true, bForceFullUpdate, HAC->GetHoudiniAsset(), HAC->GetHapiAssetName())) - { - /* - // DO NOT MANUALLY DESTROY THE OLD/DANGLING PARAMETERS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - - // Destroy old/dangling parameters - for (auto& OldParm : HAC->Parameters) - { - if (!OldParm || OldParm->IsPendingKill()) - continue; - - OldParm->ConditionalBeginDestroy(); - OldParm = nullptr; - } - */ - - // Replace with the new parameters - HAC->Parameters = NewParameters; - - // Update the details panel after the parameter changes/updates - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - } - - - return true; -} - -bool -FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) -{ - // Call OnPreCook for all parameters. - // Parameters can use this to ensure that any cached / non-cooking state is properly - // synced before the cook starts (Looking at you, ramp parameters!) - for (UHoudiniParameter* Param : HAC->Parameters) - { - if (!Param || Param->IsPendingKill()) - continue; - - Param->OnPreCook(); - } - - return true; -} - -// -bool -FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) -{ - if (!HAC || HAC->IsPendingKill()) - return false; - - // Update all the parameters using the loaded parameter object - // We set "UpdateValues" to false because we do not want to "read" the parameter value - // from Houdini but keep the loaded value - - // Share AssetInfo if needed - bool bNeedToFetchAssetInfo = true; - HAPI_AssetInfo AssetInfo; - - // This is the first cook on loading after a save or duplication - for (int32 Idx = 0; Idx < HAC->Parameters.Num(); ++Idx) - { - UHoudiniParameter* Param = HAC->Parameters[Idx]; - - if (!Param || Param->IsPendingKill()) - continue; - - switch(Param->GetParameterType()) - { - case EHoudiniParameterType::ColorRamp: - case EHoudiniParameterType::FloatRamp: - case EHoudiniParameterType::MultiParm: - { - // We need to sync the Ramp parameters first, so that their child parameters can be kept - if (bNeedToFetchAssetInfo) - { - FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &AssetInfo); - bNeedToFetchAssetInfo = false; - } - - // TODO: Simplify this, should be handled in BuildAllParameters - SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, AssetInfo); - } - break; - - case EHoudiniParameterType::Button: - case EHoudiniParameterType::ButtonStrip: - { - // Do not trigger buttons upon loading - Param->MarkChanged(false); - } - break; - - default: - break; - } - } - - // When recooking/rebuilding the HDA, force a full update of all params - const bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested() || HAC->IsParameterDefinitionUpdateNeeded(); - - // This call to BuildAllParameters will keep all the loaded parameters (in the HAC's Parameters array) - // that are still present in the HDA, and keep their loaded value. - TArray NewParameters; - // We don't need to fetch defaults from the asset definition for a loaded HAC - const UHoudiniAsset* const HoudiniAsset = nullptr; - const FString HoudiniAssetName = FString(); - if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, false, bForceFullUpdate, HoudiniAsset, HoudiniAssetName)) - { - /* - // DO NOT DESTROY OLD PARAMS MANUALLY HERE - // This causes crashes upon duplication due to uncollected zombie objects... - // GC is supposed to handle this by itself - // Destroy old/dangling parameters - for (auto& OldParm : HAC->Parameters) - { - if (!OldParm || OldParm->IsPendingKill()) - continue; - - OldParm->ConditionalBeginDestroy(); - OldParm = nullptr; - } - */ - - // Simply replace with the new parameters - HAC->Parameters = NewParameters; - - // Update the details panel after the parameter changes/updates - FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); - } - - return true; -} - -bool -FHoudiniParameterTranslator::BuildAllParameters( - const HAPI_NodeId& AssetId, - class UObject* Outer, - TArray& CurrentParameters, - TArray& NewParameters, - const bool& bUpdateValues, - const bool& InForceFullUpdate, - const UHoudiniAsset* InHoudiniAsset, - const FString& InHoudiniAssetName) -{ - const bool bIsAssetValid = IsValid(InHoudiniAsset); - - // Ensure the asset has a valid node ID - if (AssetId < 0 && !bIsAssetValid) - { - return false; - } - - int32 ParmCount = 0; - - // Default value counts and arrays for if we need to fetch those from Houdini. - int DefaultIntValueCount = 0; - int DefaultFloatValueCount = 0; - int DefaultStringValueCount = 0; - int DefaultChoiceValueCount = 0; - TArray DefaultIntValues; - TArray DefaultFloatValues; - TArray DefaultStringValues; - TArray DefaultChoiceValues; - - HAPI_NodeId NodeId = -1; - HAPI_AssetLibraryId AssetLibraryId = -1; - FString HoudiniAssetName; - - if (AssetId >= 0) - { - // Get the asset's info - HAPI_AssetInfo AssetInfo; - FHoudiniApi::AssetInfo_Init(&AssetInfo); - HAPI_Result Result = FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo); - - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - NodeId = AssetInfo.nodeId; - - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); - - ParmCount = NodeInfo.parmCount; - } - else - { - if (!FHoudiniEngineUtils::LoadHoudiniAsset(InHoudiniAsset, AssetLibraryId) ) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - could not load Houdini Asset.")); - return false; - } - - // Handle hda files that contain multiple assets - TArray AssetNames; - if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - unable to retrieve asset names.")); - return false; - } - - if (AssetNames.Num() == 0) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - unable to retrieve asset names.")); - return false; - } - - // If no InHoudiniAssetName was specified, pick the first asset from the library - if (InHoudiniAssetName.IsEmpty()) - { - const FHoudiniEngineString HoudiniEngineString(AssetNames[0]); - HoudiniEngineString.ToFString(HoudiniAssetName); - } - else - { - // Ensure that the specified asset name is in the library - for (const HAPI_StringHandle& Handle : AssetNames) - { - const FHoudiniEngineString HoudiniEngineString(Handle); - FString AssetNameStr; - HoudiniEngineString.ToFString(AssetNameStr); - if (AssetNameStr == InHoudiniAssetName) - { - HoudiniAssetName = AssetNameStr; - break; - } - } - } - - if (HoudiniAssetName.IsEmpty()) - { - HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParametersFromAssetDefinition - could not find asset in library.")); - return false; - } - - HAPI_Result Result = FHoudiniApi::GetAssetDefinitionParmCounts( - FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), &ParmCount, - &DefaultIntValueCount, &DefaultFloatValueCount, &DefaultStringValueCount, &DefaultChoiceValueCount); - - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - - if (ParmCount > 0) - { - // Allocate space in the default value arrays - // Fetch default values from HAPI - DefaultIntValues.SetNumZeroed(DefaultIntValueCount); - DefaultFloatValues.SetNumZeroed(DefaultFloatValueCount); - DefaultStringValues.SetNumZeroed(DefaultStringValueCount); - DefaultChoiceValues.SetNumZeroed(DefaultChoiceValueCount); - - Result = FHoudiniApi::GetAssetDefinitionParmValues( - FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), - DefaultIntValues.GetData(), 0, DefaultIntValueCount, - DefaultFloatValues.GetData(), 0, DefaultFloatValueCount, - false, DefaultStringValues.GetData(), 0, DefaultStringValueCount, - DefaultChoiceValues.GetData(), 0, DefaultChoiceValueCount); - - if (Result != HAPI_RESULT_SUCCESS) - { - HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); - return false; - } - } - } - - NewParameters.Empty(); - if (ParmCount == 0) - { - // The asset doesnt have any parameter, we're done. - return true; - } - else if (ParmCount < 0) - { - // Invalid parm count - return false; - } - - // Retrieve all the parameter infos either from instantiated node or from asset definition. - TArray ParmInfos; - ParmInfos.SetNumUninitialized(ParmCount); - - if (AssetId >= 0) - { - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), NodeId, &ParmInfos[0], 0, ParmCount), false); - } - else - { - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetDefinitionParmInfos( - FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), &ParmInfos[0], 0, ParmCount), false); - } - - // Create a name lookup cache for the current parameters - // Use an array has in some cases, multiple parameters can have the same name! - TMap> CurrentParametersByName; - CurrentParametersByName.Reserve(CurrentParameters.Num()); - for (const auto& Parm : CurrentParameters) - { - if (!IsValid(Parm)) - continue; - - FString ParmName = Parm->GetParameterName(); - TArray* FoundParmArray = CurrentParametersByName.Find(ParmName); - if (!FoundParmArray) - { - // Create a new array - TArray ParmArray; - ParmArray.Add(Parm); - - // add the new array to the map - CurrentParametersByName.Add(ParmName, ParmArray); - } - else - { - // add this parameter to the existing array - FoundParmArray->Add(Parm); - } - } - - // Create properties for parameters. - TMap FloatRampsToIndex; - TMap ColorRampsToIndex; - TArray NewParmIds; - TArray AllMultiParams; - for (int32 ParamIdx = 0; ParamIdx < ParmCount; ++ParamIdx) - { - // Retrieve param info at this index. - const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; - - // If the parameter is corrupt, skip it - if (ParmInfo.id < 0 || ParmInfo.childIndex < 0) - { - HOUDINI_LOG_WARNING(TEXT("Corrupt parameter %d detected, skipping."), ParamIdx); - continue; - } - - // If the parameter is invisible, skip it. - //if (ParmInfo.invisible) - // continue; - - // Check if any parent folder of this parameter is invisible - bool SkipParm = false; - bool ParentFolderVisible = true; - HAPI_ParmId ParentId = ParmInfo.parentId; - while (ParentId > 0 && !SkipParm) - { - if (const HAPI_ParmInfo* ParentInfoPtr = ParmInfos.FindByPredicate([=](const HAPI_ParmInfo& Info) { - return Info.id == ParentId; - })) - { - // We now keep invisible parameters but show/hid them in UpdateParameterFromInfo(). - if (ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER) - ParentFolderVisible = false; - ParentId = ParentInfoPtr->parentId; - } - else - { - HOUDINI_LOG_ERROR(TEXT("Could not find parent of parameter %d"), ParmInfo.id); - SkipParm = true; - } - } - - if (SkipParm) - continue; - - // See if this parameter has already been created. - // We can't use the HAPI_ParmId because it is not unique to parameter instances, - // so instead, try to find the existing parameter by name using the lookup table - FString NewParmName; - FHoudiniEngineString(ParmInfo.nameSH).ToFString(NewParmName); - - EHoudiniParameterType ParmType = EHoudiniParameterType::Invalid; - FHoudiniParameterTranslator::GetParmTypeFromParmInfo(ParmInfo, ParmType); - - // Not using the name lookup map! - UHoudiniParameter* FoundHoudiniParameter = nullptr; - TArray* MatchingParameters = CurrentParametersByName.Find(NewParmName); - if ((ParmType != EHoudiniParameterType::Invalid) && MatchingParameters) - { - //for (auto& CurrentParm : *MatchingParameters) - for(int32 Idx = MatchingParameters->Num() - 1; Idx >= 0; Idx--) - { - UHoudiniParameter* CurrentParm = (*MatchingParameters)[Idx]; - if (!CurrentParm) - continue; - - // First Check the parameter types match - if (ParmType != CurrentParm->GetParameterType()) - { - // Types do not match - continue; - } - - // Then, make sure the tuple size hasn't changed - if (CurrentParm->GetTupleSize() != ParmInfo.size) - { - // Tuple do not match - continue; - } - - if (!CheckParameterTypeAndClassMatch(CurrentParm, ParmType)) - { - // Wrong class - continue; - } - - // We can reuse this parameter - FoundHoudiniParameter = CurrentParm; - - // Remove it from the array/map - MatchingParameters->RemoveAt(Idx); - if (MatchingParameters->Num() <= 0) - CurrentParametersByName.Remove(NewParmName); - - break; - } - } - - UHoudiniParameter * HoudiniAssetParameter = nullptr; - if (FoundHoudiniParameter) - { - // We can reuse the parameter we found - HoudiniAssetParameter = FoundHoudiniParameter; - - // Transfer param object from current map to new map - CurrentParameters.Remove(HoudiniAssetParameter); - - // Do a fast update of this parameter - if (!FHoudiniParameterTranslator::UpdateParameterFromInfo( - HoudiniAssetParameter, NodeId, ParmInfo, InForceFullUpdate, bUpdateValues, - AssetId >= 0 ? nullptr : &DefaultIntValues, - AssetId >= 0 ? nullptr : &DefaultFloatValues, - AssetId >= 0 ? nullptr : &DefaultStringValues, - AssetId >= 0 ? nullptr : &DefaultChoiceValues)) - continue; - - // Reset the states of ramp parameters. - switch (HoudiniAssetParameter->GetParameterType()) - { - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); - if (FloatRampParam) - { - // Record float and color ramps for further processing (creating their Points arrays) - FloatRampsToIndex.Add(FloatRampParam, NewParameters.Num()); - UHoudiniAssetComponent* ParentHAC = Cast(FloatRampParam->GetOuter()); - if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) - FloatRampParam->bCaching = false; - } - - break; - } - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); - if (ColorRampParam) - { - // Record float and color ramps for further processing (creating their Points arrays) - ColorRampsToIndex.Add(ColorRampParam, NewParameters.Num()); - UHoudiniAssetComponent* ParentHAC = Cast(ColorRampParam->GetOuter()); - if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) - ColorRampParam->bCaching = false; - } - - break; - } - } - - } - else - { - // Create a new parameter object of the appropriate type - HoudiniAssetParameter = CreateTypedParameter(Outer, ParmType, NewParmName); - // Fully update this parameter - if (!FHoudiniParameterTranslator::UpdateParameterFromInfo( - HoudiniAssetParameter, NodeId, ParmInfo, true, true, - AssetId >= 0 ? nullptr : &DefaultIntValues, - AssetId >= 0 ? nullptr : &DefaultFloatValues, - AssetId >= 0 ? nullptr : &DefaultStringValues, - AssetId >= 0 ? nullptr : &DefaultChoiceValues)) - continue; - - // Record float and color ramps for further processing (creating their Points arrays) - const EHoudiniParameterType NewParamType = HoudiniAssetParameter->GetParameterType(); - if (NewParamType == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); - if (FloatRampParam) - FloatRampsToIndex.Add(FloatRampParam, NewParameters.Num()); - } - else if (NewParamType == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); - if (ColorRampParam) - ColorRampsToIndex.Add(ColorRampParam, NewParameters.Num()); - } - } - - HoudiniAssetParameter->SetVisibleParent(ParentFolderVisible); - - // Add the new parameters - NewParameters.Add(HoudiniAssetParameter); - NewParmIds.Add(ParmInfo.id); - - // Check if the parameter is a direct child of a multiparam. - if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::MultiParm) - AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); - - if (AllMultiParams.Contains(HoudiniAssetParameter->GetParentParmId())) - { - HoudiniAssetParameter->SetIsDirectChildOfMultiParm(true); - - // Treat the folderlist whose direct parent is a multi param as a multi param too. - if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::FolderList) - AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); - } - } - - // Assign folder type to all folderlists, - // if the first child of the folderlist is Tab or Radio button, set the bIsTabMenu of the folderlistParam to be true, otherwise false - for (int32 Idx = 0; Idx < NewParameters.Num(); ++Idx) - { - UHoudiniParameter * CurParam = NewParameters[Idx]; - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (CurParam->GetParameterType() == EHoudiniParameterType::FolderList) - { - UHoudiniParameterFolderList* CurFolderList = Cast(CurParam); - if (!CurFolderList || CurFolderList->IsPendingKill()) - continue; - - int32 FirstChildIdx = Idx + 1; - if (!NewParameters.IsValidIndex(FirstChildIdx)) - continue; - - UHoudiniParameterFolder* FirstChildFolder = Cast(NewParameters[FirstChildIdx]); - if (!FirstChildFolder || FirstChildFolder->IsPendingKill()) - continue; - - if (FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Radio || - FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Tabs) - { - // If this is the first time build - if (!CurFolderList->IsTabMenu()) - { - // Set the folderlist to be tabs - CurFolderList->SetIsTabMenu(true); - // Select the first child tab folder by default. - FirstChildFolder->SetChosen(true); - } - } - else - CurFolderList->SetIsTabMenu(false); - } - } - - // Create / update the Points arrays for the ramp parameters - if (FloatRampsToIndex.Num() > 0) - { - for (TPair const& Entry : FloatRampsToIndex) - { - UHoudiniParameterRampFloat* const RampFloatParam = Entry.Key; - const int32 ParamIndex = Entry.Value; - if (!IsValid(RampFloatParam)) - continue; - - RampFloatParam->UpdatePointsArray(NewParameters, ParamIndex + 1); - } - } - if (ColorRampsToIndex.Num() > 0) - { - for (TPair const& Entry : ColorRampsToIndex) - { - UHoudiniParameterRampColor* const RampColorParam = Entry.Key; - const int32 ParamIndex = Entry.Value; - if (!IsValid(RampColorParam)) - continue; - - RampColorParam->UpdatePointsArray(NewParameters, ParamIndex + 1); - } - } - - return true; -} - - -void -FHoudiniParameterTranslator::GetParmTypeFromParmInfo( - const HAPI_ParmInfo& ParmInfo, - EHoudiniParameterType& ParmType) -{ - ParmType = EHoudiniParameterType::Invalid; - //ParmValueType = EHoudiniParameterValueType::Invalid; - - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_BUTTON: - ParmType = EHoudiniParameterType::Button; - //ParmValueType = EHoudiniParameterValueType::Int; - break; - - case HAPI_PARMTYPE_STRING: - { - if (ParmInfo.choiceCount > 0) - { - ParmType = EHoudiniParameterType::StringChoice; - //ParmValueType = EHoudiniParameterValueType::String; - } - else - { - ParmType = EHoudiniParameterType::String; - //ParmValueType = EHoudiniParameterValueType::String; - } - break; - } - - case HAPI_PARMTYPE_INT: - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_BUTTONSTRIP) - { - ParmType = EHoudiniParameterType::ButtonStrip; - break; - } - - if (ParmInfo.choiceCount > 0) - { - ParmType = EHoudiniParameterType::IntChoice; - //ParmValueType = EHoudiniParameterValueType::Int; - } - else - { - ParmType = EHoudiniParameterType::Int; - //ParmValueType = EHoudiniParameterValueType::Int; - } - break; - } - - case HAPI_PARMTYPE_FLOAT: - { - ParmType = EHoudiniParameterType::Float; - //ParmValueType = EHoudiniParameterValueType::Float; - break; - } - - case HAPI_PARMTYPE_TOGGLE: - { - ParmType = EHoudiniParameterType::Toggle; - //ParmValueType = EHoudiniParameterValueType::Int; - break; - } - - case HAPI_PARMTYPE_COLOR: - { - ParmType = EHoudiniParameterType::Color; - //ParmValueType = EHoudiniParameterValueType::Float; - break; - } - - case HAPI_PARMTYPE_LABEL: - { - ParmType = EHoudiniParameterType::Label; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_SEPARATOR: - { - ParmType = EHoudiniParameterType::Separator; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - case HAPI_PARMTYPE_FOLDERLIST: - { - ParmType = EHoudiniParameterType::FolderList; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - // Treat radio folders as tab folders for now - case HAPI_PARMTYPE_FOLDERLIST_RADIO: - { - ParmType = EHoudiniParameterType::FolderList; - break; - } - - case HAPI_PARMTYPE_FOLDER: - { - ParmType = EHoudiniParameterType::Folder; - //ParmValueType = EHoudiniParameterValueType::None; - break; - } - - case HAPI_PARMTYPE_MULTIPARMLIST: - { - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType) - { - ParmType = EHoudiniParameterType::FloatRamp; - //ParmValueType = EHoudiniParameterValueType::Float; - } - else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - { - ParmType = EHoudiniParameterType::ColorRamp; - //ParmValueType = EHoudiniParameterValueType::Float; - } - else - { - ParmType = EHoudiniParameterType::MultiParm; - //ParmValueType = EHoudiniParameterValueType::Int; - } - break; - } - - case HAPI_PARMTYPE_PATH_FILE: - { - ParmType = EHoudiniParameterType::File; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_DIR: - { - ParmType = EHoudiniParameterType::FileDir; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_GEO: - { - ParmType = EHoudiniParameterType::FileGeo; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - { - ParmType = EHoudiniParameterType::FileImage; - //ParmValueType = EHoudiniParameterValueType::String; - break; - } - - case HAPI_PARMTYPE_NODE: - { - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - ParmType = EHoudiniParameterType::Input; - } - else - { - ParmType = EHoudiniParameterType::String; - } - break; - } - - default: - { - // Just ignore unsupported types for now. - HOUDINI_LOG_WARNING(TEXT("Parameter Type (%d) is unsupported"), static_cast(ParmInfo.type)); - break; - } - } -} - -UClass* -FHoudiniParameterTranslator::GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo) -{ - UClass* FoundClass = nullptr; - - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_STRING: - if (!ParmInfo.choiceCount) - FoundClass = UHoudiniParameterString::StaticClass(); - else - FoundClass = UHoudiniParameterChoice ::StaticClass(); - break; - - case HAPI_PARMTYPE_INT: - if (!ParmInfo.choiceCount) - FoundClass = UHoudiniParameterInt::StaticClass(); - else - FoundClass = UHoudiniParameterChoice::StaticClass(); - break; - - case HAPI_PARMTYPE_FLOAT: - FoundClass = UHoudiniParameterFloat::StaticClass(); - break; - - case HAPI_PARMTYPE_TOGGLE: - FoundClass = UHoudiniParameterToggle::StaticClass(); - break; - - case HAPI_PARMTYPE_COLOR: - FoundClass = UHoudiniParameterColor::StaticClass(); - break; - - case HAPI_PARMTYPE_LABEL: - FoundClass = UHoudiniParameterLabel::StaticClass(); - break; - - case HAPI_PARMTYPE_BUTTON: - FoundClass = UHoudiniParameterButton::StaticClass(); - break; - - case HAPI_PARMTYPE_SEPARATOR: - FoundClass = UHoudiniParameterSeparator::StaticClass(); - break; - - case HAPI_PARMTYPE_FOLDERLIST: - FoundClass = UHoudiniParameterFolderList::StaticClass(); - break; - - case HAPI_PARMTYPE_FOLDER: - FoundClass = UHoudiniParameterFolder::StaticClass(); - break; - - case HAPI_PARMTYPE_MULTIPARMLIST: - { - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - FoundClass = UHoudiniParameterRampFloat::StaticClass(); - else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - FoundClass = UHoudiniParameterRampColor::StaticClass(); - } - break; - - case HAPI_PARMTYPE_PATH_FILE: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_DIR: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_GEO: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - FoundClass = UHoudiniParameterFile::StaticClass(); - break; - - case HAPI_PARMTYPE_NODE: - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - FoundClass = UHoudiniParameter::StaticClass(); - } - else - { - FoundClass = UHoudiniParameterString::StaticClass(); - } - break; - } - - if (FoundClass == nullptr) - return UHoudiniParameter::StaticClass(); - - return FoundClass; -} - -bool -FHoudiniParameterTranslator::CheckParameterTypeAndClassMatch(UHoudiniParameter* Parameter, const EHoudiniParameterType& ParmType) -{ - UClass* FoundClass = Parameter->GetClass(); - bool FailedTypeCheck = true; - - switch (ParmType) - { - case EHoudiniParameterType::Invalid: - { - FailedTypeCheck = true; - break; - } - - case EHoudiniParameterType::Button: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterButton >(); - break; - } - - case EHoudiniParameterType::Color: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterColor >(); - break; - } - - case EHoudiniParameterType::ColorRamp: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampColor >(); - break; - } - case EHoudiniParameterType::FloatRamp: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampFloat >(); - break; - } - - case EHoudiniParameterType::File: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileDir: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileGeo: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - case EHoudiniParameterType::FileImage: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); - break; - } - - case EHoudiniParameterType::Float: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFloat >(); - break; - } - - case EHoudiniParameterType::Folder: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolder >(); - break; - } - - case EHoudiniParameterType::FolderList: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolderList >(); - break; - } - - case EHoudiniParameterType::Input: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterOperatorPath >(); - break; - } - - case EHoudiniParameterType::Int: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterInt >(); - break; - } - - case EHoudiniParameterType::IntChoice: - case EHoudiniParameterType::StringChoice: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); - break; - } - - case EHoudiniParameterType::Label: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterLabel >(); - break; - } - - case EHoudiniParameterType::MultiParm: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterMultiParm >(); - break; - } - - case EHoudiniParameterType::Separator: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterSeparator >(); - break; - } - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); - break; - } - - case EHoudiniParameterType::Toggle: - { - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterToggle >(); - break; - } - }; - - return !FailedTypeCheck; -} -/* -bool -FHoudiniParameterTranslator::CheckParameterClassAndInfoMatch(UHoudiniParameter* Parameter, const HAPI_ParmInfo& ParmInfo) -{ - if (!Parameter || Parameter->IsPendingKill()) - return false; - - UClass* FoundClass = Parameter->GetClass(); - bool FailedTypeCheck = true; - switch (ParmInfo.type) - { - case HAPI_PARMTYPE_STRING: - if (!ParmInfo.choiceCount) - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); - else - FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); - break; - - case HAPI_PARMTYPE_INT: - if (!ParmInfo.choiceCount) - FailedTypeCheck &= !FoundClass->IsChildOf(); - else - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FLOAT: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_TOGGLE: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_COLOR: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_LABEL: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_BUTTON: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_SEPARATOR: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FOLDERLIST: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_FOLDER: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_MULTIPARMLIST: - if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - break; - - case HAPI_PARMTYPE_PATH_FILE: - case HAPI_PARMTYPE_PATH_FILE_DIR: - case HAPI_PARMTYPE_PATH_FILE_GEO: - case HAPI_PARMTYPE_PATH_FILE_IMAGE: - FailedTypeCheck &= !FoundClass->IsChildOf(); - break; - - case HAPI_PARMTYPE_NODE: - if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || - ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || - ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - else - { - FailedTypeCheck &= !FoundClass->IsChildOf(); - } - break; - } - - return FailedTypeCheck; -} -*/ - -UHoudiniParameter * -FHoudiniParameterTranslator::CreateTypedParameter(UObject * Outer, const EHoudiniParameterType& ParmType, const FString& ParmName) -{ - UHoudiniParameter* HoudiniParameter = nullptr; - // Create a parameter of the desired type - switch (ParmType) - { - case EHoudiniParameterType::Button: - HoudiniParameter = UHoudiniParameterButton::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::ButtonStrip: - HoudiniParameter = UHoudiniParameterButtonStrip::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Color: - HoudiniParameter = UHoudiniParameterColor::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::ColorRamp: - HoudiniParameter = UHoudiniParameterRampColor::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FloatRamp: - HoudiniParameter = UHoudiniParameterRampFloat::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::File: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FileDir: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileDir); - break; - - case EHoudiniParameterType::FileGeo: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileGeo); - break; - - case EHoudiniParameterType::FileImage: - HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(EHoudiniParameterType::FileImage); - break; - - case EHoudiniParameterType::Float: - HoudiniParameter = UHoudiniParameterFloat::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Folder: - HoudiniParameter = UHoudiniParameterFolder::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::FolderList: - HoudiniParameter = UHoudiniParameterFolderList::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Input: - // Input parameter simply use the base class as all the processingsince is handled by UHoudiniInput - HoudiniParameter = UHoudiniParameterOperatorPath::Create(Outer, ParmName); - HoudiniParameter->SetParameterType(ParmType); - break; - - case EHoudiniParameterType::Int: - HoudiniParameter = UHoudiniParameterInt::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::IntChoice: - HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::IntChoice); - break; - - case EHoudiniParameterType::StringChoice: - HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::StringChoice); - break; - - case EHoudiniParameterType::Label: - HoudiniParameter = UHoudiniParameterLabel::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::MultiParm: - HoudiniParameter = UHoudiniParameterMultiParm::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Separator: - HoudiniParameter = UHoudiniParameterSeparator::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - HoudiniParameter = UHoudiniParameterString::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Toggle: - HoudiniParameter = UHoudiniParameterToggle::Create(Outer, ParmName); - break; - - case EHoudiniParameterType::Invalid: - // TODO handle invalid params - HoudiniParameter = UHoudiniParameter::Create(Outer, ParmName); - break; - } - - return HoudiniParameter; -} - -bool -FHoudiniParameterTranslator::UpdateParameterFromInfo( - UHoudiniParameter * HoudiniParameter, const HAPI_NodeId& InNodeId, const HAPI_ParmInfo& ParmInfo, - const bool& bFullUpdate, const bool& bUpdateValue, - const TArray* DefaultIntValues, - const TArray* DefaultFloatValues, - const TArray* DefaultStringValues, - const TArray* DefaultChoiceValues) -{ - if (!HoudiniParameter || HoudiniParameter->IsPendingKill()) - return false; - - // Copy values from the ParmInfos - const bool bHasValidNodeId = InNodeId >= 0; - if (bHasValidNodeId) - HoudiniParameter->SetNodeId(InNodeId); - HoudiniParameter->SetParmId(ParmInfo.id); - HoudiniParameter->SetParentParmId(ParmInfo.parentId); - - HoudiniParameter->SetChildIndex(ParmInfo.childIndex); - HoudiniParameter->SetTagCount(ParmInfo.tagCount); - HoudiniParameter->SetTupleSize(ParmInfo.size); - - HoudiniParameter->SetVisible(!ParmInfo.invisible); - HoudiniParameter->SetDisabled(ParmInfo.disabled); - HoudiniParameter->SetSpare(ParmInfo.spare); - HoudiniParameter->SetJoinNext(ParmInfo.joinNext); - - HoudiniParameter->SetTagCount(ParmInfo.tagCount); - HoudiniParameter->SetIsChildOfMultiParm(ParmInfo.isChildOfMultiParm); - - UHoudiniParameterMultiParm* MultiParm = Cast(HoudiniParameter); - if(MultiParm) - MultiParm->InstanceStartOffset = ParmInfo.instanceStartOffset; - - - - // Get the parameter type - EHoudiniParameterType ParmType = HoudiniParameter->GetParameterType(); - - // We need to set string values from the parmInfo - if (bFullUpdate) - { - FString Name; - { - // Name - FHoudiniEngineString HoudiniEngineStringName(ParmInfo.nameSH); - if (HoudiniEngineStringName.ToFString(Name)) - HoudiniParameter->SetParameterName(Name); - } - - { - // Label - FString Label; - FHoudiniEngineString HoudiniEngineStringLabel(ParmInfo.labelSH); - if (HoudiniEngineStringLabel.ToFString(Label)) - HoudiniParameter->SetParameterLabel(Label); - } - - { - // Help - FString Help; - FHoudiniEngineString HoudiniEngineStringHelp(ParmInfo.helpSH); - if (HoudiniEngineStringHelp.ToFString(Help)) - HoudiniParameter->SetParameterHelp(Help); - } - - if (bHasValidNodeId && - (ParmType == EHoudiniParameterType::String - || ParmType == EHoudiniParameterType::Int - || ParmType == EHoudiniParameterType::Float - || ParmType == EHoudiniParameterType::Toggle - || ParmType == EHoudiniParameterType::Color)) - { - // See if the parm has an expression - int32 TupleIdx = ParmInfo.intValuesIndex; - bool bHasExpression = false; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::ParmHasExpression( - FHoudiniEngine::Get().GetSession(), InNodeId, - TCHAR_TO_UTF8(*Name), TupleIdx, &bHasExpression)) - { - // ? - } - - FString ParmExprString = TEXT(""); - if (bHasExpression) - { - // Try to get the expression's value - HAPI_StringHandle StringHandle; - if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmExpression( - FHoudiniEngine::Get().GetSession(), InNodeId, - TCHAR_TO_UTF8(*Name), TupleIdx, &StringHandle)) - { - FHoudiniEngineString HoudiniEngineString(StringHandle); - HoudiniEngineString.ToFString(ParmExprString); - } - - // Check if we actually have an expression - // String parameters return true even if they do not have one - bHasExpression = ParmExprString.Len() > 0; - - } - - HoudiniParameter->SetHasExpression(bHasExpression); - HoudiniParameter->SetExpression(ParmExprString); - } - else - { - HoudiniParameter->SetHasExpression(false); - HoudiniParameter->SetExpression(FString()); - } - - // Get parameter tags. - if (bHasValidNodeId) - { - int32 TagCount = HoudiniParameter->GetTagCount(); - for (int32 Idx = 0; Idx < TagCount; ++Idx) - { - HAPI_StringHandle TagNameSH; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagName( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmInfo.id, Idx, &TagNameSH)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); - continue; - } - - FString NameString = TEXT(""); - FHoudiniEngineString::ToFString(TagNameSH, NameString); - if (NameString.IsEmpty()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); - continue; - } - - HAPI_StringHandle TagValueSH; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagValue( - FHoudiniEngine::Get().GetSession(), - InNodeId, ParmInfo.id, TCHAR_TO_ANSI(*NameString), &TagValueSH)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag value: parmId: %d, tag: %s"), ParmInfo.id, *NameString); - } - - FString ValueString = TEXT(""); - FHoudiniEngineString::ToFString(TagValueSH, ValueString); - - HoudiniParameter->GetTags().Add(NameString, ValueString); - } - } - } - - // - // Update properties specific to parameter classes - switch (ParmType) - { - case EHoudiniParameterType::Button: - { - UHoudiniParameterButton* HoudiniParameterButton = Cast(HoudiniParameter); - if (HoudiniParameterButton && !HoudiniParameterButton->IsPendingKill()) - { - HoudiniParameterButton->SetValueIndex(ParmInfo.intValuesIndex); - } - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - UHoudiniParameterButtonStrip* HoudiniParameterButtonStrip = Cast(HoudiniParameter); - if (HoudiniParameterButtonStrip && !HoudiniParameterButtonStrip->IsPendingKill()) - { - HoudiniParameterButtonStrip->SetValueIndex(ParmInfo.intValuesIndex); - HoudiniParameterButtonStrip->Count = ParmInfo.choiceCount; - } - - if (bFullUpdate) - { - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - - ParmChoices.SetNum(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - if (bHasValidNodeId) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - } - else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && - DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) - { - FPlatformMemory::Memcpy( - ParmChoices.GetData(), - DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, - sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); - } - else - { - return false; - } - - HoudiniParameterButtonStrip->InitializeLabels(ParmInfo.choiceCount); - - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ButtonLabel = HoudiniParameterButtonStrip->GetStringLabelAt(ChoiceIdx); - if (ButtonLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ButtonLabel)) - return false; - } - } - - if (bHasValidNodeId) - { - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterButtonStrip->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.choiceCount) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && - DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.choiceCount - 1)) - { - for (int32 Index = 0; Index < ParmInfo.choiceCount; ++Index) - { - HoudiniParameterButtonStrip->SetValueAt( - Index, (*DefaultIntValues)[ParmInfo.intValuesIndex + Index]); - } - } - else - { - return false; - } - } - } - break; - - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* HoudiniParameterColor = Cast(HoudiniParameter); - if (HoudiniParameterColor && !HoudiniParameterColor->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterColor->SetValueIndex(ParmInfo.floatValuesIndex); - - // Update the Parameter value if we want to - if (bUpdateValue) - { - // Get the actual value for this property. - FLinearColor Color = FLinearColor::White; - if (bHasValidNodeId) - { - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - (float *)&Color.R, ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - else if (DefaultFloatValues && DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex) && - DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex + ParmInfo.size - 1)) - { - FPlatformMemory::Memcpy( - &Color.R, - DefaultFloatValues->GetData() + ParmInfo.floatValuesIndex, - sizeof(float) * ParmInfo.size); - } - else - { - return false; - } - - HoudiniParameterColor->SetColorValue(Color); - } - - if (bFullUpdate) - { - // Set the default value at parameter created. - HoudiniParameterColor->SetDefaultValue(); - } - } - } - break; - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* HoudiniParameterRampColor = Cast(HoudiniParameter); - if (HoudiniParameterRampColor && !HoudiniParameterRampColor->IsPendingKill()) - { - HoudiniParameterRampColor->SetInstanceCount(ParmInfo.instanceCount); - HoudiniParameterRampColor->MultiParmInstanceLength = ParmInfo.instanceLength; - } - } - break; - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* HoudiniParameterRampFloat = Cast(HoudiniParameter); - if (HoudiniParameterRampFloat && !HoudiniParameterRampFloat->IsPendingKill()) - { - HoudiniParameterRampFloat->SetInstanceCount(ParmInfo.instanceCount); - HoudiniParameterRampFloat->MultiParmInstanceLength = ParmInfo.instanceLength; - } - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - UHoudiniParameterFile* HoudiniParameterFile = Cast(HoudiniParameter); - if (HoudiniParameterFile && !HoudiniParameterFile->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFile->SetValueIndex(ParmInfo.stringValuesIndex); - - // Update the file filter and read only tag only for full updates - if (bFullUpdate) - { - // Check if we are read-only - bool bIsReadOnly = false; - FString FileChooserTag; - if (bHasValidNodeId && FHoudiniParameterTranslator::HapiGetParameterTagValue(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_FILE_READONLY, FileChooserTag)) - { - if (FileChooserTag.Equals(TEXT("read"), ESearchCase::IgnoreCase)) - bIsReadOnly = true; - } - HoudiniParameterFile->SetReadOnly(bIsReadOnly); - - // Update the file type using the typeInfo string. - if (ParmInfo.typeInfoSH > 0) - { - FString Filters; - FHoudiniEngineString HoudiniEngineString(ParmInfo.typeInfoSH); - if (HoudiniEngineString.ToFString(Filters)) - { - if (!Filters.IsEmpty()) - HoudiniParameterFile->SetFileFilters(Filters); - } - } - } - - if (bUpdateValue) - { - // Get the actual values for this property. - TArray< HAPI_StringHandle > StringHandles; - - if (bHasValidNodeId) - { - StringHandles.SetNumZeroed(ParmInfo.size); - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), InNodeId, false, - &StringHandles[0], ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && - DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) - { - StringHandles.SetNumZeroed(ParmInfo.size); - FPlatformMemory::Memcpy( - &StringHandles[0], - DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, - sizeof(HAPI_StringHandle) * ParmInfo.size); - } - else - { - return false; - } - - // Convert HAPI string handles to Unreal strings. - HoudiniParameterFile->SetNumberOfValues(ParmInfo.size); - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - - // Update the parameter value - HoudiniParameterFile->SetValueAt(ValueString, Idx); - } - } - - if (bFullUpdate) - { - HoudiniParameterFile->SetDefaultValues(); - } - } - } - break; - - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* HoudiniParameterFloat = Cast(HoudiniParameter); - if (HoudiniParameterFloat && !HoudiniParameterFloat->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFloat->SetValueIndex(ParmInfo.floatValuesIndex); - - if (bUpdateValue) - { - // Update the parameter's value - HoudiniParameterFloat->SetNumberOfValues(ParmInfo.size); - - if (bHasValidNodeId) - { - if (FHoudiniApi::GetParmFloatValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterFloat->GetValuesPtr(), - ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - else if (DefaultFloatValues && DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex) && - DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex + ParmInfo.size - 1)) - { - FPlatformMemory::Memcpy( - HoudiniParameterFloat->GetValuesPtr(), - DefaultFloatValues->GetData() + ParmInfo.floatValuesIndex, - sizeof(float) * ParmInfo.size); - } - else - { - return false; - } - } - - if (bFullUpdate) - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_FLOAT_LOG) - { - HoudiniParameterFloat->SetIsLogarithmic(true); - } - - // set the default float values. - HoudiniParameterFloat->SetDefaultValues(); - - // Only update Unit, no swap, and Min/Max values when doing a full update - - // Get the parameter's unit from the "unit" tag - FString ParamUnit; - if (bHasValidNodeId) - { - FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); - HoudiniParameterFloat->SetUnit(ParamUnit); - // Get the parameter's no swap tag (hengine_noswap) - HoudiniParameterFloat->SetNoSwap(HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_NOSWAP)); - } - - // Set the min and max for this parameter - if (ParmInfo.hasMin) - { - HoudiniParameterFloat->SetHasMin(true); - HoudiniParameterFloat->SetMin(ParmInfo.min); - } - else - { - HoudiniParameterFloat->SetHasMin(false); - HoudiniParameterFloat->SetMin(TNumericLimits::Lowest()); - } - - if (ParmInfo.hasMax) - { - HoudiniParameterFloat->SetHasMax(true); - HoudiniParameterFloat->SetMax(ParmInfo.max); - } - else - { - HoudiniParameterFloat->SetHasMax(false); - HoudiniParameterFloat->SetMax(TNumericLimits::Max()); - } - - // Set min and max for UI for this property. - bool bUsesDefaultMin = false; - if (ParmInfo.hasUIMin) - { - HoudiniParameterFloat->SetHasUIMin(true); - HoudiniParameterFloat->SetUIMin(ParmInfo.UIMin); - } - else if (ParmInfo.hasMin) - { - // If it is not set, use supplied min. - HoudiniParameterFloat->SetUIMin(ParmInfo.min); - } - else - { - // Min value Houdini uses by default. - HoudiniParameterFloat->SetUIMin(HAPI_UNREAL_PARAM_FLOAT_UI_MIN); - bUsesDefaultMin = true; - } - - bool bUsesDefaultMax = false; - if (ParmInfo.hasUIMax) - { - HoudiniParameterFloat->SetHasUIMax(true); - HoudiniParameterFloat->SetUIMax(ParmInfo.UIMax); - } - else if (ParmInfo.hasMax) - { - // If it is not set, use supplied max. - HoudiniParameterFloat->SetUIMax(ParmInfo.max); - } - else - { - // Max value Houdini uses by default. - HoudiniParameterFloat->SetUIMax(HAPI_UNREAL_PARAM_FLOAT_UI_MAX); - bUsesDefaultMax = true; - } - - if (bUsesDefaultMin || bUsesDefaultMax) - { - // If we are using defaults, we can detect some most common parameter names and alter defaults. - FString LocalParameterName = HoudiniParameterFloat->GetParameterName(); - FHoudiniEngineString HoudiniEngineString(ParmInfo.nameSH); - HoudiniEngineString.ToFString(LocalParameterName); - - static const FString ParameterNameTranslate(TEXT(HAPI_UNREAL_PARAM_TRANSLATE)); - static const FString ParameterNameRotate(TEXT(HAPI_UNREAL_PARAM_ROTATE)); - static const FString ParameterNameScale(TEXT(HAPI_UNREAL_PARAM_SCALE)); - static const FString ParameterNamePivot(TEXT(HAPI_UNREAL_PARAM_PIVOT)); - - if (!LocalParameterName.IsEmpty()) - { - if (LocalParameterName.Equals(ParameterNameTranslate) - || LocalParameterName.Equals(ParameterNameScale) - || LocalParameterName.Equals(ParameterNamePivot)) - { - if (bUsesDefaultMin) - { - HoudiniParameterFloat->SetUIMin(-1.0f); - } - if (bUsesDefaultMax) - { - HoudiniParameterFloat->SetUIMax(1.0f); - } - } - else if (LocalParameterName.Equals(ParameterNameRotate)) - { - if (bUsesDefaultMin) - { - HoudiniParameterFloat->SetUIMin(0.0f); - } - if (bUsesDefaultMax) - { - HoudiniParameterFloat->SetUIMax(360.0f); - } - } - } - } - } - } - } - break; - - case EHoudiniParameterType::Folder: - { - UHoudiniParameterFolder* HoudiniParameterFolder = Cast(HoudiniParameter); - if (HoudiniParameterFolder && !HoudiniParameterFolder->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFolder->SetValueIndex(ParmInfo.intValuesIndex); - HoudiniParameterFolder->SetFolderType(GetFolderTypeFromParamInfo(&ParmInfo)); - } - } - break; - - case EHoudiniParameterType::FolderList: - { - UHoudiniParameterFolderList* HoudiniParameterFolderList = Cast(HoudiniParameter); - if (HoudiniParameterFolderList && !HoudiniParameterFolderList->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterFolderList->SetValueIndex(ParmInfo.intValuesIndex); - } - } - break; - - case EHoudiniParameterType::Input: - { - // Inputs parameters are just stored, and handled separately by UHoudiniInputs - UHoudiniParameterOperatorPath* HoudiniParameterOperatorPath = Cast(HoudiniParameter); - if (HoudiniParameterOperatorPath && !HoudiniParameterOperatorPath->IsPendingKill()) - { - /* - // DO NOT CREATE A DUPLICATE INPUT HERE! - // Inputs are created by the input translator, and will be tied to this parameter there - UHoudiniInput * NewInput = NewObject< UHoudiniInput >( - HoudiniParameterOperatorPath, - UHoudiniInput::StaticClass()); - - UHoudiniAssetComponent *ParentHAC = Cast(HoudiniParameterOperatorPath->GetOuter()); - - if (!ParentHAC) - return false; - - if (!NewInput || NewInput->IsPendingKill()) - return false; - - // Set the nodeId - NewInput->SetAssetNodeId(ParentHAC->GetAssetId()); - NewInput->SetInputType(EHoudiniInputType::Geometry); - HoudiniParameterOperatorPath->HoudiniInputs.Add(NewInput); - */ - // Set the valueIndex - HoudiniParameterOperatorPath->SetValueIndex(ParmInfo.stringValuesIndex); - } - } - break; - - case EHoudiniParameterType::Int: - { - UHoudiniParameterInt* HoudiniParameterInt = Cast(HoudiniParameter); - if (HoudiniParameterInt && !HoudiniParameterInt->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterInt->SetValueIndex(ParmInfo.intValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - HoudiniParameterInt->SetNumberOfValues(ParmInfo.size); - - if (bHasValidNodeId) - { - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterInt->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && - DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.size - 1)) - { - for (int32 Index = 0; Index < ParmInfo.size; ++Index) - { - // TODO: cannot use SetValueAt: Min/Max has not yet been configured and defaults to 0,0 - // so the value is clamped to 0 - // HoudiniParameterInt->SetValueAt( - // (*DefaultIntValues)[ParmInfo.intValuesIndex + Index], Index); - *(HoudiniParameterInt->GetValuesPtr() + Index) = (*DefaultIntValues)[ParmInfo.intValuesIndex + Index]; - } - } - else - { - return false; - } - } - - if (bFullUpdate) - { - if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_INT_LOG) - { - HoudiniParameterInt->SetIsLogarithmic(true); - } - - // Set the default int values at created - HoudiniParameterInt->SetDefaultValues(); - // Only update unit and Min/Max values for a full update - - // Get the parameter's unit from the "unit" tag - FString ParamUnit; - if (bHasValidNodeId) - { - FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); - HoudiniParameterInt->SetUnit(ParamUnit); - } - - // Set the min and max for this parameter - if (ParmInfo.hasMin) - { - HoudiniParameterInt->SetHasMin(true); - HoudiniParameterInt->SetMin((int32)ParmInfo.min); - } - else - { - HoudiniParameterInt->SetHasMin(false); - HoudiniParameterInt->SetMin(TNumericLimits::Lowest()); - } - - if (ParmInfo.hasMax) - { - HoudiniParameterInt->SetHasMax(true); - HoudiniParameterInt->SetMax((int32)ParmInfo.max); - } - else - { - HoudiniParameterInt->SetHasMax(false); - HoudiniParameterInt->SetMax(TNumericLimits::Max()); - } - - // Set min and max for UI for this property. - if (ParmInfo.hasUIMin) - { - HoudiniParameterInt->SetHasUIMin(true); - HoudiniParameterInt->SetUIMin((int32)ParmInfo.UIMin); - } - else if (ParmInfo.hasMin) - { - // If it is not set, use supplied min. - HoudiniParameterInt->SetUIMin((int32)ParmInfo.min); - } - else - { - // Min value Houdini uses by default. - HoudiniParameterInt->SetUIMin(HAPI_UNREAL_PARAM_INT_UI_MIN); - } - - if (ParmInfo.hasUIMax) - { - HoudiniParameterInt->SetHasUIMax(true); - HoudiniParameterInt->SetUIMax((int32)ParmInfo.UIMax); - } - else if (ParmInfo.hasMax) - { - // If it is not set, use supplied max. - HoudiniParameterInt->SetUIMax((int32)ParmInfo.max); - } - else - { - // Max value Houdini uses by default. - HoudiniParameterInt->SetUIMax(HAPI_UNREAL_PARAM_INT_UI_MAX); - } - } - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* HoudiniParameterIntChoice = Cast(HoudiniParameter); - if (HoudiniParameterIntChoice && !HoudiniParameterIntChoice->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterIntChoice->SetValueIndex(ParmInfo.intValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - int32 CurrentIntValue = 0; - - if (bHasValidNodeId) - { - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &CurrentIntValue, - ParmInfo.intValuesIndex, 1/*ParmInfo.size*/), false); - } - else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex)) - { - CurrentIntValue = (*DefaultIntValues)[ParmInfo.intValuesIndex]; - } - else - { - return false; - } - - CurrentIntValue = HoudiniParameterIntChoice->GetIndexFromValueArray(CurrentIntValue); - - // Check the value is valid - if (CurrentIntValue >= ParmInfo.choiceCount) - { - HOUDINI_LOG_WARNING(TEXT("parm '%s' has an invalid value %d, menu tokens are not supported for choice menus"), - *HoudiniParameterIntChoice->GetParameterName(), CurrentIntValue); - CurrentIntValue = 0; - } - - HoudiniParameterIntChoice->SetIntValue(CurrentIntValue); - } - - // Get the choice descriptors - if (bFullUpdate) - { - // Set the default value at created - HoudiniParameterIntChoice->SetDefaultIntValue(); - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - - ParmChoices.SetNum(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - if (bHasValidNodeId) - { - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - } - else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && - DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) - { - FPlatformMemory::Memcpy( - ParmChoices.GetData(), - DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, - sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); - } - else - { - return false; - } - - // Set the array sizes - HoudiniParameterIntChoice->SetNumChoices(ParmInfo.choiceCount); - - bool bMatchedSelectionLabel = false; - int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValueIndex(); - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ChoiceLabel = HoudiniParameterIntChoice->GetStringChoiceLabelAt(ChoiceIdx); - if (ChoiceLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ChoiceLabel)) - return false; - //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); - } - - // Match our string value to the corresponding selection label. - if (ChoiceIdx == CurrentIntValue) - { - HoudiniParameterIntChoice->SetStringValue(*ChoiceLabel); - } - - int32 IntValue = ChoiceIdx; - /* - // If useMenuItemTokenAsValue is set, then the value is not the index. Find the value using the token, if possible. - if (ParmInfo.useMenuItemTokenAsValue) - { - if (ChoiceIdx < ParmChoices.Num()) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - FString Token; - - if (HoudiniEngineString.ToFString(Token)) - { - try - { - int32 Value = FCString::Atoi(*Token); - IntValue = Value; - } - catch (...) - { - } - } - } - } - */ - - HoudiniParameterIntChoice->SetIntValueArray(ChoiceIdx, IntValue); - } - } - else if (bUpdateValue) - { - // We still need to match the string value to the label - HoudiniParameterIntChoice->UpdateStringValueFromInt(); - } - } - } - break; - - case EHoudiniParameterType::StringChoice: - { - UHoudiniParameterChoice* HoudiniParameterStringChoice = Cast(HoudiniParameter); - if (HoudiniParameterStringChoice && !HoudiniParameterStringChoice->IsPendingKill()) - { - // Set the valueIndex - HoudiniParameterStringChoice->SetValueIndex(ParmInfo.stringValuesIndex); - - if (bUpdateValue) - { - // Get the actual values for this property. - HAPI_StringHandle StringHandle; - - if (bHasValidNodeId) - { - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandle, - ParmInfo.stringValuesIndex, 1/*ParmInfo.size*/), false); - } - else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex)) - { - StringHandle = (*DefaultStringValues)[ParmInfo.stringValuesIndex]; - } - else - { - return false; - } - - // Get the string value - FString StringValue; - FHoudiniEngineString HoudiniEngineString(StringHandle); - HoudiniEngineString.ToFString(StringValue); - - HoudiniParameterStringChoice->SetStringValue(StringValue); - } - - // Get the choice descriptors - if (bFullUpdate) - { - // Set default value at created. - HoudiniParameterStringChoice->SetDefaultStringValue(); - // Get the choice descriptors. - TArray< HAPI_ParmChoiceInfo > ParmChoices; - - ParmChoices.SetNum(ParmInfo.choiceCount); - for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) - FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); - - if (bHasValidNodeId) - { - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( - FHoudiniEngine::Get().GetSession(), - InNodeId, &ParmChoices[0], - ParmInfo.choiceIndex, ParmInfo.choiceCount), false); - } - else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && - DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) - { - FPlatformMemory::Memcpy( - ParmChoices.GetData(), - DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, - sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); - } - else - { - return false; - } - - // Set the array sizes - HoudiniParameterStringChoice->SetNumChoices(ParmInfo.choiceCount); - - bool bMatchedSelectionLabel = false; - FString CurrentStringValue = HoudiniParameterStringChoice->GetStringValue(); - for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) - { - FString * ChoiceValue = HoudiniParameterStringChoice->GetStringChoiceValueAt(ChoiceIdx); - if (ChoiceValue) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].valueSH); - if (!HoudiniEngineString.ToFString(*ChoiceValue)) - return false; - //StringChoiceValues.Add(TSharedPtr< FString >(ChoiceValue)); - } - - FString * ChoiceLabel = HoudiniParameterStringChoice->GetStringChoiceLabelAt(ChoiceIdx); - if (ChoiceLabel) - { - FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); - if (!HoudiniEngineString.ToFString(*ChoiceLabel)) - return false; - //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); - } - - // If this is a string choice list, we need to match name with corresponding selection label. - if (!bMatchedSelectionLabel && ChoiceValue->Equals(CurrentStringValue)) - { - bMatchedSelectionLabel = true; - HoudiniParameterStringChoice->SetIntValue(ChoiceIdx); - } - } - } - else if (bUpdateValue) - { - // We still need to match the string value to the label - HoudiniParameterStringChoice->UpdateIntValueFromString(); - } - } - } - break; - - case EHoudiniParameterType::Label: - { - UHoudiniParameterLabel* HoudiniParameterLabel = Cast(HoudiniParameter); - if (HoudiniParameterLabel && !HoudiniParameterLabel->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_LABEL) - return false; - - // Set the valueIndex - HoudiniParameterLabel->SetValueIndex(ParmInfo.stringValuesIndex); - - // Get the actual value for this property. - TArray StringHandles; - - if (bHasValidNodeId) - { - StringHandles.SetNumZeroed(ParmInfo.size); - FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandles[0], - ParmInfo.stringValuesIndex, ParmInfo.size); - } - else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && - DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) - { - StringHandles.SetNumZeroed(ParmInfo.size); - FPlatformMemory::Memcpy( - StringHandles.GetData(), - DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, - sizeof(HAPI_StringHandle) * ParmInfo.size); - } - else - { - return false; - } - - HoudiniParameterLabel->EmptyLabelString(); - - // Convert HAPI string handles to Unreal strings. - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - HoudiniParameterLabel->AddLabelString(ValueString); - } - } - } - break; - - case EHoudiniParameterType::MultiParm: - { - UHoudiniParameterMultiParm* HoudiniParameterMulti = Cast(HoudiniParameter); - if (HoudiniParameterMulti && !HoudiniParameterMulti->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST) - return false; - - // Set the valueIndex - HoudiniParameterMulti->SetValueIndex(ParmInfo.intValuesIndex); - - // Set the multiparm value - int32 MultiParmValue = 0; - - if (bHasValidNodeId) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, &MultiParmValue, ParmInfo.intValuesIndex, 1), false); - } - else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex)) - { - MultiParmValue = (*DefaultIntValues)[ParmInfo.intValuesIndex]; - } - else - { - return false; - } - - HoudiniParameterMulti->SetValue(MultiParmValue); - HoudiniParameterMulti->MultiParmInstanceCount = ParmInfo.instanceCount; - HoudiniParameterMulti->MultiParmInstanceLength = ParmInfo.instanceLength; - - } - - if (bFullUpdate) - { - HoudiniParameterMulti->SetDefaultInstanceCount(ParmInfo.instanceCount); - } - } - break; - - case EHoudiniParameterType::Separator: - { - UHoudiniParameterSeparator* HoudiniParameterSeparator = Cast(HoudiniParameter); - if (HoudiniParameterSeparator && !HoudiniParameterSeparator->IsPendingKill()) - { - // We can only handle separator type. - if (ParmInfo.type != HAPI_PARMTYPE_SEPARATOR) - return false; - - // Set the valueIndex - HoudiniParameterSeparator->SetValueIndex(ParmInfo.stringValuesIndex); - } - } - break; - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringAssetRef: - { - UHoudiniParameterString* HoudiniParameterString = Cast(HoudiniParameter); - if (HoudiniParameterString && !HoudiniParameterString->IsPendingKill()) - { - // We can only handle string type. - if (ParmInfo.type != HAPI_PARMTYPE_STRING && ParmInfo.type != HAPI_PARMTYPE_NODE) - return false; - - // Set the valueIndex - HoudiniParameterString->SetValueIndex(ParmInfo.stringValuesIndex); - - // Stop if we don't want to update the value - if (bUpdateValue) - { - // Get the actual value for this property. - TArray< HAPI_StringHandle > StringHandles; - - if (bHasValidNodeId) - { - StringHandles.SetNumZeroed(ParmInfo.size); - if (FHoudiniApi::GetParmStringValues( - FHoudiniEngine::Get().GetSession(), - InNodeId, false, &StringHandles[0], - ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && - DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) - { - StringHandles.SetNumZeroed(ParmInfo.size); - FPlatformMemory::Memcpy( - StringHandles.GetData(), - DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, - sizeof(HAPI_StringHandle) * ParmInfo.size); - } - else - { - return false; - } - - // Convert HAPI string handles to Unreal strings. - HoudiniParameterString->SetNumberOfValues(ParmInfo.size); - for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) - { - FString ValueString = TEXT(""); - FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); - HoudiniEngineString.ToFString(ValueString); - HoudiniParameterString->SetValueAt(ValueString, Idx); - } - } - - if (bFullUpdate) - { - // Set default string values on created - HoudiniParameterString->SetDefaultValues(); - // Check if the parameter has the "asset_ref" tag - if (bHasValidNodeId) - { - HoudiniParameterString->SetIsAssetRef( - FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_ASSET_REF)); - } - } - } - } - break; - - case EHoudiniParameterType::Toggle: - { - UHoudiniParameterToggle* HoudiniParameterToggle = Cast(HoudiniParameter); - if (HoudiniParameterToggle && !HoudiniParameterToggle->IsPendingKill()) - { - if (ParmInfo.type != HAPI_PARMTYPE_TOGGLE) - return false; - - // Set the valueIndex - HoudiniParameterToggle->SetValueIndex(ParmInfo.intValuesIndex); - - // Stop if we don't want to update the value - if (bUpdateValue) - { - // Get the actual values for this property. - HoudiniParameterToggle->SetNumberOfValues(ParmInfo.size); - - if (bHasValidNodeId) - { - if (FHoudiniApi::GetParmIntValues( - FHoudiniEngine::Get().GetSession(), InNodeId, - HoudiniParameterToggle->GetValuesPtr(), - ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) - { - return false; - } - } - else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && - DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.size - 1)) - { - for (int32 Index = 0; Index < ParmInfo.size; ++Index) - { - HoudiniParameterToggle->SetValueAt( - (*DefaultIntValues)[ParmInfo.intValuesIndex + Index] != 0, Index); - } - } - else - { - return false; - } - } - } - - if (bFullUpdate) - { - HoudiniParameterToggle->SetDefaultValues(); - } - } - break; - - case EHoudiniParameterType::Invalid: - { - // TODO - } - break; - } - - return true; -} - -bool -FHoudiniParameterTranslator::HapiGetParameterTagValue(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag, FString& TagValue) -{ - // Default - TagValue = FString(); - - // Does the parameter has the tag? - bool HasTag = false; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ParmHasTag( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &HasTag), false); - - if (!HasTag) - return false; - - // Get the tag string value - HAPI_StringHandle StringHandle; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmTagValue( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &StringHandle), false); - - FHoudiniEngineString HoudiniEngineString(StringHandle); - if (HoudiniEngineString.ToFString(TagValue)) - { - return true; - } - - return false; -} - - -bool -FHoudiniParameterTranslator::HapiGetParameterUnit(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString) -{ - // - OutUnitString = TEXT(""); - - // We're looking for the parameter unit tag - //FString UnitTag = "units"; - - // Get the actual string value. - FString UnitString = TEXT(""); - if (!FHoudiniParameterTranslator::HapiGetParameterTagValue(NodeId, ParmId, "units", UnitString)) - return false; - - // We need to do some replacement in the string here in order to be able to get the - // proper unit type when calling FUnitConversion::UnitFromString(...) after. - - // Per second and per hour are the only "per" unit that unreal recognize - UnitString.ReplaceInline(TEXT("s-1"), TEXT("/s")); - UnitString.ReplaceInline(TEXT("h-1"), TEXT("/h")); - - // Houdini likes to add '1' on all the unit, so we'll remove all of them - // except the '-1' that still needs to remain. - UnitString.ReplaceInline(TEXT("-1"), TEXT("--")); - UnitString.ReplaceInline(TEXT("1"), TEXT("")); - UnitString.ReplaceInline(TEXT("--"), TEXT("-1")); - - OutUnitString = UnitString; - - return true; -} - -bool -FHoudiniParameterTranslator::HapiGetParameterHasTag(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag) -{ - // Does the parameter has the tag we're looking for? - bool HasTag = false; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( - FHoudiniEngine::Get().GetSession(), NodeId, ParmId, - TCHAR_TO_ANSI(*Tag), &HasTag), false); - - return HasTag; -} - - -bool -FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * HAC ) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniParameterTranslator::UploadChangedParameters); - - if (!HAC || HAC->IsPendingKill()) - return false; - - TMap RampsToRevert; - // First upload all parameters, including the current child parameters/points of ramps, and then process - // the ramp parameters themselves (delete and insert operations of ramp points) - // This is so that the initial upload of parameter values use the correct parameter value/tuple array indices - // (which will change after potential insert/delete operations). Insert operations will upload their new - // parameter values after the insert. - TArray RampsToUpload; - - for (int32 ParmIdx = 0; ParmIdx < HAC->GetNumParameters(); ParmIdx++) - { - UHoudiniParameter*& CurrentParm = HAC->Parameters[ParmIdx]; - if (!CurrentParm || CurrentParm->IsPendingKill() || !CurrentParm->HasChanged()) - continue; - - bool bSuccess = false; - - const EHoudiniParameterType CurrentParmType = CurrentParm->GetParameterType(); - if (CurrentParm->IsPendingRevertToDefault()) - { - bSuccess = RevertParameterToDefault(CurrentParm); - - if (CurrentParmType == EHoudiniParameterType::FloatRamp || - CurrentParmType == EHoudiniParameterType::ColorRamp) - { - RampsToRevert.Add(CurrentParm->GetParameterName(), CurrentParm); - } - } - else - { - if (CurrentParmType == EHoudiniParameterType::FloatRamp || - CurrentParmType == EHoudiniParameterType::ColorRamp) - { - RampsToUpload.Add(CurrentParm); - } - else - { - bSuccess = UploadParameterValue(CurrentParm); - } - } - - - if (bSuccess) - { - CurrentParm->MarkChanged(false); - //CurrentParm->SetNeedsToTriggerUpdate(false); - } - else - { - // Keep this param marked as changed but prevent it from generating updates - CurrentParm->SetNeedsToTriggerUpdate(false); - } - } - - FHoudiniParameterTranslator::RevertRampParameters(RampsToRevert, HAC->GetAssetId()); - - for (UHoudiniParameter* const RampParam : RampsToUpload) - { - if (!IsValid(RampParam)) - continue; - - if (UploadParameterValue(RampParam)) - RampParam->MarkChanged(false); - } - - return true; -} - -bool -FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return false; - - switch (InParam->GetParameterType()) - { - case EHoudiniParameterType::Float: - { - UHoudiniParameterFloat* FloatParam = Cast(InParam); - if (!FloatParam || FloatParam->IsPendingKill()) - { - return false; - } - - float* DataPtr = FloatParam->GetValuesPtr(); - if (!DataPtr) - { - return false; - } - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - FloatParam->GetNodeId(), DataPtr, FloatParam->GetValueIndex(), FloatParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::Int: - { - UHoudiniParameterInt* IntParam = Cast(InParam); - if (!IntParam || IntParam->IsPendingKill()) - { - return false; - } - - int32* DataPtr = IntParam->GetValuesPtr(); - if (!DataPtr) - { - return false; - } - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - IntParam->GetNodeId(), DataPtr, IntParam->GetValueIndex(), IntParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::String: - { - UHoudiniParameterString* StringParam = Cast(InParam); - if (!StringParam || StringParam->IsPendingKill()) - { - return false; - } - - int32 NumValues = StringParam->GetNumberOfValues(); - if (NumValues <= 0) - { - return false; - } - - for (int32 Idx = 0; Idx < NumValues; Idx++) - { - std::string ConvertedString = TCHAR_TO_UTF8(*(StringParam->GetValueAt(Idx))); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - StringParam->GetNodeId(), ConvertedString.c_str(), StringParam->GetParmId(), Idx), false); - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) - return false; - - // Set the parameter's int value. - const int32 IntValueIndex = ChoiceParam->GetIntValueIndex(); - const int32 IntValue = ChoiceParam->GetIntValue(IntValueIndex); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); - } - break; - case EHoudiniParameterType::StringChoice: - { - UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) - { - return false; - } - - if (ChoiceParam->IsStringChoice()) - { - // Set the parameter's string value. - std::string ConvertedString = TCHAR_TO_UTF8(*(ChoiceParam->GetStringValue())); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), ConvertedString.c_str(), ChoiceParam->GetParmId(), 0), false); - } - else - { - // Set the parameter's int value. - int32 IntValue = ChoiceParam->GetIntValueIndex(); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); - } - } - break; - - case EHoudiniParameterType::Color: - { - UHoudiniParameterColor* ColorParam = Cast(InParam); - if (!ColorParam || ColorParam->IsPendingKill()) - return false; - - bool bHasAlpha = ColorParam->GetTupleSize() == 4 ? true : false; - FLinearColor Color = ColorParam->GetColorValue(); - - // Set the color value - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - ColorParam->GetNodeId(), - (float*)(&Color.R), ColorParam->GetValueIndex(), bHasAlpha ? 4 : 3), false); - - } - break; - - case EHoudiniParameterType::Button: - { - UHoudiniParameterButton* ButtonParam = Cast(InParam); - if (!ButtonParam) - return false; - - TArray DataArray; - DataArray.Add(1); - - // Set the button parameter value to 1, (setting button param to any value will call the callback function.) - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ButtonParam->GetNodeId(), - DataArray.GetData(), - ButtonParam->GetValueIndex(), 1), false); - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - UHoudiniParameterButtonStrip* ButtonStripParam = Cast(InParam); - if (!ButtonStripParam) - return false; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ButtonStripParam->GetNodeId(), - ButtonStripParam->Values.GetData(), - ButtonStripParam->GetValueIndex(), ButtonStripParam->Count), false); - } - break; - - case EHoudiniParameterType::Toggle: - { - UHoudiniParameterToggle* ToggleParam = Cast(InParam); - if (!ToggleParam) - return false; - - // Set the toggle parameter values. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - ToggleParam->GetNodeId(), - ToggleParam->GetValuesPtr(), - ToggleParam->GetValueIndex(), ToggleParam->GetTupleSize()), false); - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - UHoudiniParameterFile* FileParam = Cast(InParam); - - if (!UploadDirectoryPath(FileParam)) - return false; - } - break; - - case EHoudiniParameterType::MultiParm: - { - if (!UploadMultiParmValues(InParam)) - return false; - } - - break; - - case EHoudiniParameterType::FloatRamp: - { - if (!UploadRampParameter(InParam)) - return false; - } - break; - - case EHoudiniParameterType::ColorRamp: - { - if (!UploadRampParameter(InParam)) - return false; - } - break; - - default: - { - // TODO: implement other parameter types! - return false; - } - break; - } - - // The parameter is no longer considered as changed - InParam->MarkChanged(false); - - return true; -} - -bool -FHoudiniParameterTranslator::RevertParameterToDefault(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return false; - - if (!InParam->IsPendingRevertToDefault()) - return false; - - TArray TupleToRevert; - InParam->GetTuplePendingRevertToDefault(TupleToRevert); - if (TupleToRevert.Num() <= 0) - return false; - - FString ParameterName = InParam->GetParameterName(); - - bool bReverted = true; - for (auto CurrentIdx : TupleToRevert ) - { - if (!TupleToRevert.IsValidIndex(CurrentIdx)) - { - // revert the whole parameter to its default value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefaults( - FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName))) - { - HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s to its default value."), *ParameterName); - bReverted = false; - } - } - else - { - // revert a tuple to its default value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefault( - FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName), CurrentIdx)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s - %d to its default value."), *ParameterName, CurrentIdx); - bReverted = false; - } - } - } - - if (!bReverted) - return false; - - // The parameter no longer needs to be reverted - InParam->MarkDefault(true); - - return true; -} - -EHoudiniFolderParameterType -FHoudiniParameterTranslator::GetFolderTypeFromParamInfo(const HAPI_ParmInfo* ParamInfo) -{ - EHoudiniFolderParameterType Type = EHoudiniFolderParameterType::Invalid; - - switch (ParamInfo->scriptType) - { - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPSIMPLE: - Type = EHoudiniFolderParameterType::Simple; - break; - - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPCOLLAPSIBLE: - Type = EHoudiniFolderParameterType::Collapsible; - break; - - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUP: - Type = EHoudiniFolderParameterType::Tabs; - break; - - // Treat Radio folders as tabs for now - case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPRADIO: - Type = EHoudiniFolderParameterType::Radio; - break; - - default: - Type = EHoudiniFolderParameterType::Other; - break; - - } - - return Type; - -} - -bool -FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad( - UHoudiniParameter* InParam, TArray& OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo) -{ - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam || MultiParam->IsPendingKill()) - return false; - - UHoudiniParameterRampFloat* FloatRampParameter = nullptr; - UHoudiniParameterRampColor* ColorRampParameter = nullptr; - - if (MultiParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - FloatRampParameter = Cast(MultiParam); - else if (MultiParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - ColorRampParameter = Cast(MultiParam); - - HAPI_NodeId NodeId = AssetInfo.nodeId; - - int32 Idx = 0; - int32 InstanceCount = -1; - HAPI_ParmId ParmId = -1; - TArray ParmInfos; - if (!GetMultiParmInstanceStartIdx(AssetInfo, MultiParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - const int32 InstanceCountInUnreal = FMath::Max(MultiParam->GetInstanceCount(), 0); - if (InstanceCount > InstanceCountInUnreal) - { - // The multiparm has more instances on the Houdini side, remove instances from the end until it has the same - // number as in Unreal. - // NOTE: Initially this code always removed the first instance. But that causes an issue if HAPI/Houdini does - // not immediately update the parameter names (param1, param2, param3, when param1 is removed, 2 -> 1, - // 3 -> 2) so that could result in GetParameters returning parameters with unique IDs, but where the names - // are not up to date, so in the above example, the last param could still be named param3 when it should - // be named param2. - const int32 Delta = InstanceCount - InstanceCountInUnreal; - for (int32 n = 0; n < Delta; ++n) - { - FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset + InstanceCount - 1 - n); - } - } - else if (InstanceCountInUnreal > InstanceCount) - { - // The multiparm has fewer instances on the Houdini side, add instances at the end until it has the same - // number as in Unreal. - // NOTE: Initially this code always inserted before the first instance. But that causes an issue if HAPI/Houdini - // does not immediately update the parameter names (param1, param2, param3, when a param is inserted - // before 1, then 1->2, 2->3, 3->4 so that could result in GetParameters returning parameters with unique - // IDs, but where the names are not up to date, so in the above example, the now second param could still - // be named param1 when it should be named param2. - const int32 Delta = InstanceCountInUnreal - InstanceCount; - for (int32 n = 0; n < Delta; ++n) - { - FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), NodeId, - ParmId, MultiParam->InstanceStartOffset + InstanceCount + n); - } - } - - // We are going to Sync nested multi-params recursively - int32 MyParmId = InParam->GetParmId(); - // First, we need to manually look for our index in the old map - // Since there is a possibility that the parameter interface has changed since our previous cook - int32 MyIndex = -1; - for (int32 ParamIdx = 0; ParamIdx < OldParams.Num(); ParamIdx++) - { - UHoudiniParameter* CurrentOldParm = OldParams[ParamIdx]; - if (!IsValid(CurrentOldParm)) - continue; - - if (CurrentOldParm->GetParmId() != MyParmId) - continue; - - // We found ourself, exit now - MyIndex = ParamIdx; - break; - } - - if (MyIndex >= 0) - { - // Now Sync nested multi-params recursively - for (int32 ParamIdx = MyIndex + 1; ParamIdx < OldParams.Num(); ParamIdx++) - { - UHoudiniParameter* NextParm = OldParams[ParamIdx]; - if (!IsValid(NextParm)) - continue; - - if (NextParm->GetParentParmId() != MyParmId) - continue; - - if (NextParm->GetParameterType() != EHoudiniParameterType::MultiParm) - continue; - - // Always make sure to NOT recurse on ourselves! - // This could happen if parms have been deleted... - if (NextParm->GetParmId() == MyParmId) - continue; - - SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, AssetInfo); - } - } - - // The multiparm is a ramp, Get the param infos again, since the number of param instances is changed - if (!GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - // Step 3: Set values of the inserted points - if (FloatRampParameter) - { - for (auto & Point : FloatRampParameter->Points) - { - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); - - // 2: update float value at param Idx + 1 - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Value), ParmInfos[Idx + 1].floatValuesIndex, 1); - - // 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Point->Interpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - else if (ColorRampParameter) - { - for (auto& Point : ColorRampParameter->Points) - { - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); - - // 2: update color value at param Idx + 1 - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeId, (float*)(&Point->Value.R), ParmInfos[Idx + 1].floatValuesIndex, 3); - - // 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Point->Interpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - - return true; -} - - -bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam) -{ - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam || MultiParam->IsPendingKill()) - return false; - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InParam->GetOuter()); - if (!HoudiniAssetComponent) - return false; - - int32 InsertIndexStart = -1; - UHoudiniParameterRampFloat* RampFloatParam = Cast(InParam); - UHoudiniParameterRampColor* RampColorParam = Cast(InParam); - - TArray *Events = nullptr; - if (RampFloatParam) - { - Events = &(RampFloatParam->ModificationEvents); - InsertIndexStart = RampFloatParam->GetInstanceCount(); - } - else if (RampColorParam) - { - Events = &(RampColorParam->ModificationEvents); - InsertIndexStart = RampColorParam->GetInstanceCount(); - } - else - return false; - - // Handle All Events - Events->Sort([](const UHoudiniParameterRampModificationEvent& A, const UHoudiniParameterRampModificationEvent& B) - { - return A.DeleteInstanceIndex > B.DeleteInstanceIndex; - }); - - - // Step 1: Handle all delete events first - for (auto& Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsDeleteEvent()) - continue; - - FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Event->DeleteInstanceIndex + MultiParam->InstanceStartOffset); - - InsertIndexStart -= 1; - } - - int32 InsertIndex = InsertIndexStart; - - - // Step 2: Handle all insert events - for (auto& Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsInsertEvent()) - continue; - - FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), InsertIndex + MultiParam->InstanceStartOffset); - - InsertIndex += 1; - } - - // Step 3: Set inserted parameter values (only if there are instances inserted) - if (InsertIndex > InsertIndexStart) - { - if (HoudiniAssetComponent) - { - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), HoudiniAssetComponent->AssetId, &AssetInfo), false); - - int32 Idx = 0; - int32 InstanceCount = -1; - HAPI_ParmId ParmId = -1; - TArray ParmInfos; - if (!FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), - Idx, InstanceCount, ParmId, ParmInfos)) - return false; - - if (InstanceCount < 0) - return false; - - // Instance count doesn't match, - if (InsertIndex != InstanceCount) - return false; - - // Starting index of parameters which we just inserted - Idx += 3 * InsertIndexStart; - - if (!ParmInfos.IsValidIndex(Idx + 2)) - return false; - - for (auto & Event : *Events) - { - if (!Event) - continue; - - if (!Event->IsInsertEvent()) - continue; - - // 1: update position float at param Idx - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &(Event->InsertPosition), ParmInfos[Idx].floatValuesIndex, 1); - - // step 2: update value at param Idx + 1 - if (Event->IsFloatRampEvent()) - { - // float value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &(Event->InsertFloat), ParmInfos[Idx + 1].floatValuesIndex, 1); - } - else - { - // color value - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, (float*)(&Event->InsertColor.R), ParmInfos[Idx + 1].floatValuesIndex, 3); - } - - // step 3: update interpolation type at param Idx + 2 - int32 IntValue = (int32)(Event->InsertInterpolation); - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - AssetInfo.nodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); - - Idx += 3; - } - } - } - - // Step 4: clear all events - Events->Empty(); - - return true; -} - -bool FHoudiniParameterTranslator::UploadMultiParmValues(UHoudiniParameter* InParam) -{ - UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam) - return false; - - TArray &LastModificationArray = MultiParam->MultiParmInstanceLastModifyArray; - - int32 Size = MultiParam->MultiParmInstanceLastModifyArray.Num(); - - for (int32 Index = 0; Index < Size; ++Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Inserted) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::InsertMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) - return false; - - } - } - - for (int32 Index = Size - 1; Index >= 0; --Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) - { - if (HAPI_RESULT_SUCCESS != FHoudiniApi::RemoveMultiparmInstance( - FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), - MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) - return false; - } - } - - // Remove all removal events. - for (int32 Index = Size - 1; Index >= 0; --Index) - { - if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) - LastModificationArray.RemoveAt(Index); - } - - // The last modification array is resized. - Size = LastModificationArray.Num(); - - // Reset the last modification array - for (int32 Itr =Size - 1; Itr >= 0; --Itr) - { - LastModificationArray[Itr] = EHoudiniMultiParmModificationType::None; - } - - MultiParam->MultiParmInstanceCount = Size; - - return true; -} - -bool -FHoudiniParameterTranslator::UploadDirectoryPath(UHoudiniParameterFile* InParam) -{ - if(!InParam) - return false; - - for (int32 Index = 0; Index < InParam->GetNumValues(); ++Index) - { - std::string ConvertedString = TCHAR_TO_UTF8(*(InParam->GetValueAt(Index))); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue(FHoudiniEngine::Get().GetSession(), - InParam->GetNodeId(), ConvertedString.c_str(), InParam->GetParmId(), Index), false); - } - - return true; -} - -bool -FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, - int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos) -{ - // TODO: FIX/IMPROVE THIS! - // This is bad, that function can be called recursively, fetches all parameters, - // iterates on them, and fetches their name!! WTF! - // TODO: Slightly better now, at least we dont fetch every parameter's name! - - // Reset outputs - OutStartIdx = -1; - OutInstanceCount = -1; - OutParmId = -1; - OutParmInfos.Empty(); - - // Try to find the parameter by its name - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, TCHAR_TO_UTF8(*InParmName), &OutParmId), false); - - if (OutParmId < 0) - return false; - - // Get the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &NodeInfo), false); - - // Get all parameters - OutParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &OutParmInfos[0], 0, NodeInfo.parmCount), false); - - OutStartIdx = 0; - for (const auto& CurrentParmInfo : OutParmInfos) - { - if (OutParmId == CurrentParmInfo.id) - { - OutInstanceCount = OutParmInfos[OutStartIdx].instanceCount; - - // Increment, to get the Start index of the ramp children parameters - OutStartIdx++; - return true; - } - - OutStartIdx++; - } - - // We failed to find the parm - OutStartIdx = -1; - - return false; -} - -bool -FHoudiniParameterTranslator::RevertRampParameters(TMap & InRampParams, const int32 & AssetId) -{ - if (InRampParams.Num() <= 0) - return true; - - // Get the asset's info - HAPI_AssetInfo AssetInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); - - // .. the asset's node info - HAPI_NodeInfo NodeInfo; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); - - // Retrieve all the parameter infos. - TArray< HAPI_ParmInfo > ParmInfos; - ParmInfos.SetNumUninitialized(NodeInfo.parmCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( - FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); - - int32 ParamIdx = 0; - while (ParamIdx < ParmInfos.Num()) - { - const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; - FString ParmName; - FHoudiniEngineString(ParmInfo.nameSH).ToFString(ParmName); - - if (InRampParams.Contains(ParmName)) - { - if (!InRampParams[ParmName]) - { - ParamIdx += 1; - continue; - } - - if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat * FloatRamp = Cast(InRampParams[ParmName]); - if (!FloatRamp) - { - ParamIdx += 1; - continue; - } - - if (ParmInfo.instanceCount != FloatRamp->NumDefaultPoints) - { - ParamIdx += 1; - continue; - } - - for (int32 PtIdx = 0; PtIdx < FloatRamp->NumDefaultPoints; ++PtIdx) - { - const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; - const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; - const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultValues.GetData() + PtIdx, ValueParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, FloatRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); - - ParamIdx += 3; - } - } - - if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor * ColorRamp = Cast(InRampParams[ParmName]); - if (!ColorRamp) - { - ParamIdx += 1; - continue; - } - - if (ParmInfo.instanceCount != ColorRamp->NumDefaultPoints) - { - ParamIdx += 1; - continue; - } - - for (int32 PtIdx = 0; PtIdx < ColorRamp->NumDefaultPoints; ++PtIdx) - { - const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; - const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; - const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, ColorRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); - - FHoudiniApi::SetParmFloatValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, (float*)(&ColorRamp->DefaultValues[PtIdx].R), ValueParmInfo.floatValuesIndex, 3); - - FHoudiniApi::SetParmIntValues( - FHoudiniEngine::Get().GetSession(), - NodeInfo.id, ColorRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); - - ParamIdx += 3; - } - } - } - - ParamIdx += 1; - } - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "HoudiniAsset.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniInput.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniParameter.h" +#include "HoudiniAssetComponent.h" + + +// Default values for certain UI min and max parameter values +#define HAPI_UNREAL_PARAM_INT_UI_MIN 0 +#define HAPI_UNREAL_PARAM_INT_UI_MAX 10 +#define HAPI_UNREAL_PARAM_FLOAT_UI_MIN 0.0f +#define HAPI_UNREAL_PARAM_FLOAT_UI_MAX 10.0f + +// Some default parameter name +#define HAPI_UNREAL_PARAM_TRANSLATE "t" +#define HAPI_UNREAL_PARAM_ROTATE "r" +#define HAPI_UNREAL_PARAM_SCALE "s" +#define HAPI_UNREAL_PARAM_PIVOT "p" +#define HAPI_UNREAL_PARAM_UNIFORMSCALE "scale" + +// +bool +FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // When recooking/rebuilding the HDA, force a full update of all params + const bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested() || HAC->IsParameterDefinitionUpdateNeeded(); + + TArray NewParameters; + if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, true, bForceFullUpdate, HAC->GetHoudiniAsset(), HAC->GetHapiAssetName())) + { + /* + // DO NOT MANUALLY DESTROY THE OLD/DANGLING PARAMETERS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + + // Destroy old/dangling parameters + for (auto& OldParm : HAC->Parameters) + { + if (!OldParm || OldParm->IsPendingKill()) + continue; + + OldParm->ConditionalBeginDestroy(); + OldParm = nullptr; + } + */ + + // Replace with the new parameters + HAC->Parameters = NewParameters; + + // Update the details panel after the parameter changes/updates + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + } + + + return true; +} + +bool +FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) +{ + // Call OnPreCook for all parameters. + // Parameters can use this to ensure that any cached / non-cooking state is properly + // synced before the cook starts (Looking at you, ramp parameters!) + for (UHoudiniParameter* Param : HAC->Parameters) + { + if (!Param || Param->IsPendingKill()) + continue; + + Param->OnPreCook(); + } + + return true; +} + +// +bool +FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) +{ + if (!HAC || HAC->IsPendingKill()) + return false; + + // Update all the parameters using the loaded parameter object + // We set "UpdateValues" to false because we do not want to "read" the parameter value + // from Houdini but keep the loaded value + + // Share AssetInfo if needed + bool bNeedToFetchAssetInfo = true; + HAPI_AssetInfo AssetInfo; + + // This is the first cook on loading after a save or duplication + for (int32 Idx = 0; Idx < HAC->Parameters.Num(); ++Idx) + { + UHoudiniParameter* Param = HAC->Parameters[Idx]; + + if (!Param || Param->IsPendingKill()) + continue; + + switch(Param->GetParameterType()) + { + case EHoudiniParameterType::ColorRamp: + case EHoudiniParameterType::FloatRamp: + case EHoudiniParameterType::MultiParm: + { + // We need to sync the Ramp parameters first, so that their child parameters can be kept + if (bNeedToFetchAssetInfo) + { + FHoudiniApi::GetAssetInfo(FHoudiniEngine::Get().GetSession(), HAC->AssetId, &AssetInfo); + bNeedToFetchAssetInfo = false; + } + + // TODO: Simplify this, should be handled in BuildAllParameters + SyncMultiParmValuesAtLoad(Param, HAC->Parameters, HAC->AssetId, AssetInfo); + } + break; + + case EHoudiniParameterType::Button: + case EHoudiniParameterType::ButtonStrip: + { + // Do not trigger buttons upon loading + Param->MarkChanged(false); + } + break; + + default: + break; + } + } + + // When recooking/rebuilding the HDA, force a full update of all params + const bool bForceFullUpdate = HAC->HasRebuildBeenRequested() || HAC->HasRecookBeenRequested() || HAC->IsParameterDefinitionUpdateNeeded(); + + // This call to BuildAllParameters will keep all the loaded parameters (in the HAC's Parameters array) + // that are still present in the HDA, and keep their loaded value. + TArray NewParameters; + // We don't need to fetch defaults from the asset definition for a loaded HAC + const UHoudiniAsset* const HoudiniAsset = nullptr; + const FString HoudiniAssetName = FString(); + if (FHoudiniParameterTranslator::BuildAllParameters(HAC->GetAssetId(), HAC, HAC->Parameters, NewParameters, false, bForceFullUpdate, HoudiniAsset, HoudiniAssetName)) + { + /* + // DO NOT DESTROY OLD PARAMS MANUALLY HERE + // This causes crashes upon duplication due to uncollected zombie objects... + // GC is supposed to handle this by itself + // Destroy old/dangling parameters + for (auto& OldParm : HAC->Parameters) + { + if (!OldParm || OldParm->IsPendingKill()) + continue; + + OldParm->ConditionalBeginDestroy(); + OldParm = nullptr; + } + */ + + // Simply replace with the new parameters + HAC->Parameters = NewParameters; + + // Update the details panel after the parameter changes/updates + FHoudiniEngineUtils::UpdateEditorProperties(HAC, true); + } + + return true; +} + +bool +FHoudiniParameterTranslator::BuildAllParameters( + const HAPI_NodeId& AssetId, + class UObject* Outer, + TArray& CurrentParameters, + TArray& NewParameters, + const bool& bUpdateValues, + const bool& InForceFullUpdate, + const UHoudiniAsset* InHoudiniAsset, + const FString& InHoudiniAssetName) +{ + const bool bIsAssetValid = IsValid(InHoudiniAsset); + + // Ensure the asset has a valid node ID + if (AssetId < 0 && !bIsAssetValid) + { + return false; + } + + int32 ParmCount = 0; + + // Default value counts and arrays for if we need to fetch those from Houdini. + int DefaultIntValueCount = 0; + int DefaultFloatValueCount = 0; + int DefaultStringValueCount = 0; + int DefaultChoiceValueCount = 0; + TArray DefaultIntValues; + TArray DefaultFloatValues; + TArray DefaultStringValues; + TArray DefaultChoiceValues; + + HAPI_NodeId NodeId = -1; + HAPI_AssetLibraryId AssetLibraryId = -1; + FString HoudiniAssetName; + + if (AssetId >= 0) + { + // Get the asset's info + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HAPI_Result Result = FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo); + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + NodeId = AssetInfo.nodeId; + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); + + ParmCount = NodeInfo.parmCount; + } + else + { + if (!FHoudiniEngineUtils::LoadHoudiniAsset(InHoudiniAsset, AssetLibraryId) ) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - could not load Houdini Asset.")); + return false; + } + + // Handle hda files that contain multiple assets + TArray AssetNames; + if (!FHoudiniEngineUtils::GetSubAssetNames(AssetLibraryId, AssetNames)) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - unable to retrieve asset names.")); + return false; + } + + if (AssetNames.Num() == 0) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParameters - unable to retrieve asset names.")); + return false; + } + + // If no InHoudiniAssetName was specified, pick the first asset from the library + if (InHoudiniAssetName.IsEmpty()) + { + const FHoudiniEngineString HoudiniEngineString(AssetNames[0]); + HoudiniEngineString.ToFString(HoudiniAssetName); + } + else + { + // Ensure that the specified asset name is in the library + for (const HAPI_StringHandle& Handle : AssetNames) + { + const FHoudiniEngineString HoudiniEngineString(Handle); + FString AssetNameStr; + HoudiniEngineString.ToFString(AssetNameStr); + if (AssetNameStr == InHoudiniAssetName) + { + HoudiniAssetName = AssetNameStr; + break; + } + } + } + + if (HoudiniAssetName.IsEmpty()) + { + HOUDINI_LOG_ERROR(TEXT("Cancelling BuildAllParametersFromAssetDefinition - could not find asset in library.")); + return false; + } + + HAPI_Result Result = FHoudiniApi::GetAssetDefinitionParmCounts( + FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), &ParmCount, + &DefaultIntValueCount, &DefaultFloatValueCount, &DefaultStringValueCount, &DefaultChoiceValueCount); + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + + if (ParmCount > 0) + { + // Allocate space in the default value arrays + // Fetch default values from HAPI + DefaultIntValues.SetNumZeroed(DefaultIntValueCount); + DefaultFloatValues.SetNumZeroed(DefaultFloatValueCount); + DefaultStringValues.SetNumZeroed(DefaultStringValueCount); + DefaultChoiceValues.SetNumZeroed(DefaultChoiceValueCount); + + Result = FHoudiniApi::GetAssetDefinitionParmValues( + FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), + DefaultIntValues.GetData(), 0, DefaultIntValueCount, + DefaultFloatValues.GetData(), 0, DefaultFloatValueCount, + false, DefaultStringValues.GetData(), 0, DefaultStringValueCount, + DefaultChoiceValues.GetData(), 0, DefaultChoiceValueCount); + + if (Result != HAPI_RESULT_SUCCESS) + { + HOUDINI_LOG_ERROR(TEXT("Hapi failed: %s"), *FHoudiniEngineUtils::GetErrorDescription()); + return false; + } + } + } + + NewParameters.Empty(); + if (ParmCount == 0) + { + // The asset doesnt have any parameter, we're done. + return true; + } + else if (ParmCount < 0) + { + // Invalid parm count + return false; + } + + // Retrieve all the parameter infos either from instantiated node or from asset definition. + TArray ParmInfos; + ParmInfos.SetNumUninitialized(ParmCount); + + if (AssetId >= 0) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), NodeId, &ParmInfos[0], 0, ParmCount), false); + } + else + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetAssetDefinitionParmInfos( + FHoudiniEngine::Get().GetSession(), AssetLibraryId, TCHAR_TO_UTF8(*HoudiniAssetName), &ParmInfos[0], 0, ParmCount), false); + } + + // Create a name lookup cache for the current parameters + // Use an array has in some cases, multiple parameters can have the same name! + TMap> CurrentParametersByName; + CurrentParametersByName.Reserve(CurrentParameters.Num()); + for (const auto& Parm : CurrentParameters) + { + if (!IsValid(Parm)) + continue; + + FString ParmName = Parm->GetParameterName(); + TArray* FoundParmArray = CurrentParametersByName.Find(ParmName); + if (!FoundParmArray) + { + // Create a new array + TArray ParmArray; + ParmArray.Add(Parm); + + // add the new array to the map + CurrentParametersByName.Add(ParmName, ParmArray); + } + else + { + // add this parameter to the existing array + FoundParmArray->Add(Parm); + } + } + + // Create properties for parameters. + TMap FloatRampsToIndex; + TMap ColorRampsToIndex; + TArray NewParmIds; + TArray AllMultiParams; + for (int32 ParamIdx = 0; ParamIdx < ParmCount; ++ParamIdx) + { + // Retrieve param info at this index. + const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; + + // If the parameter is corrupt, skip it + if (ParmInfo.id < 0 || ParmInfo.childIndex < 0) + { + HOUDINI_LOG_WARNING(TEXT("Corrupt parameter %d detected, skipping."), ParamIdx); + continue; + } + + // If the parameter is invisible, skip it. + //if (ParmInfo.invisible) + // continue; + + // Check if any parent folder of this parameter is invisible + bool SkipParm = false; + bool ParentFolderVisible = true; + HAPI_ParmId ParentId = ParmInfo.parentId; + while (ParentId > 0 && !SkipParm) + { + if (const HAPI_ParmInfo* ParentInfoPtr = ParmInfos.FindByPredicate([=](const HAPI_ParmInfo& Info) { + return Info.id == ParentId; + })) + { + // We now keep invisible parameters but show/hid them in UpdateParameterFromInfo(). + if (ParentInfoPtr->invisible && ParentInfoPtr->type == HAPI_PARMTYPE_FOLDER) + ParentFolderVisible = false; + + // Prevent endless loops! + if (ParentId != ParentInfoPtr->parentId) + ParentId = ParentInfoPtr->parentId; + else + ParentId = -1; + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not find parent of parameter %d"), ParmInfo.id); + SkipParm = true; + } + } + + if (SkipParm) + continue; + + // See if this parameter has already been created. + // We can't use the HAPI_ParmId because it is not unique to parameter instances, + // so instead, try to find the existing parameter by name using the lookup table + FString NewParmName; + FHoudiniEngineString(ParmInfo.nameSH).ToFString(NewParmName); + + EHoudiniParameterType ParmType = EHoudiniParameterType::Invalid; + FHoudiniParameterTranslator::GetParmTypeFromParmInfo(ParmInfo, ParmType); + + // Not using the name lookup map! + UHoudiniParameter* FoundHoudiniParameter = nullptr; + TArray* MatchingParameters = CurrentParametersByName.Find(NewParmName); + if ((ParmType != EHoudiniParameterType::Invalid) && MatchingParameters) + { + //for (auto& CurrentParm : *MatchingParameters) + for(int32 Idx = MatchingParameters->Num() - 1; Idx >= 0; Idx--) + { + UHoudiniParameter* CurrentParm = (*MatchingParameters)[Idx]; + if (!CurrentParm) + continue; + + // First Check the parameter types match + if (ParmType != CurrentParm->GetParameterType()) + { + // Types do not match + continue; + } + + // Then, make sure the tuple size hasn't changed + if (CurrentParm->GetTupleSize() != ParmInfo.size) + { + // Tuple do not match + continue; + } + + if (!CheckParameterTypeAndClassMatch(CurrentParm, ParmType)) + { + // Wrong class + continue; + } + + // We can reuse this parameter + FoundHoudiniParameter = CurrentParm; + + // Remove it from the array/map + MatchingParameters->RemoveAt(Idx); + if (MatchingParameters->Num() <= 0) + CurrentParametersByName.Remove(NewParmName); + + break; + } + } + + UHoudiniParameter * HoudiniAssetParameter = nullptr; + if (FoundHoudiniParameter) + { + // We can reuse the parameter we found + HoudiniAssetParameter = FoundHoudiniParameter; + + // Transfer param object from current map to new map + CurrentParameters.Remove(HoudiniAssetParameter); + + // Do a fast update of this parameter + if (!FHoudiniParameterTranslator::UpdateParameterFromInfo( + HoudiniAssetParameter, NodeId, ParmInfo, InForceFullUpdate, bUpdateValues, + AssetId >= 0 ? nullptr : &DefaultIntValues, + AssetId >= 0 ? nullptr : &DefaultFloatValues, + AssetId >= 0 ? nullptr : &DefaultStringValues, + AssetId >= 0 ? nullptr : &DefaultChoiceValues)) + continue; + + // Reset the states of ramp parameters. + switch (HoudiniAssetParameter->GetParameterType()) + { + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); + if (FloatRampParam) + { + // Record float and color ramps for further processing (creating their Points arrays) + FloatRampsToIndex.Add(FloatRampParam, NewParameters.Num()); + UHoudiniAssetComponent* ParentHAC = Cast(FloatRampParam->GetOuter()); + if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) + FloatRampParam->bCaching = false; + } + + break; + } + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); + if (ColorRampParam) + { + // Record float and color ramps for further processing (creating their Points arrays) + ColorRampsToIndex.Add(ColorRampParam, NewParameters.Num()); + UHoudiniAssetComponent* ParentHAC = Cast(ColorRampParam->GetOuter()); + if (ParentHAC && !ParentHAC->HasBeenLoaded() && !ParentHAC->HasBeenDuplicated()) + ColorRampParam->bCaching = false; + } + + break; + } + } + + } + else + { + // Create a new parameter object of the appropriate type + HoudiniAssetParameter = CreateTypedParameter(Outer, ParmType, NewParmName); + // Fully update this parameter + if (!FHoudiniParameterTranslator::UpdateParameterFromInfo( + HoudiniAssetParameter, NodeId, ParmInfo, true, true, + AssetId >= 0 ? nullptr : &DefaultIntValues, + AssetId >= 0 ? nullptr : &DefaultFloatValues, + AssetId >= 0 ? nullptr : &DefaultStringValues, + AssetId >= 0 ? nullptr : &DefaultChoiceValues)) + continue; + + // Record float and color ramps for further processing (creating their Points arrays) + const EHoudiniParameterType NewParamType = HoudiniAssetParameter->GetParameterType(); + if (NewParamType == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat* FloatRampParam = Cast(HoudiniAssetParameter); + if (FloatRampParam) + FloatRampsToIndex.Add(FloatRampParam, NewParameters.Num()); + } + else if (NewParamType == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* ColorRampParam = Cast(HoudiniAssetParameter); + if (ColorRampParam) + ColorRampsToIndex.Add(ColorRampParam, NewParameters.Num()); + } + } + + HoudiniAssetParameter->SetVisibleParent(ParentFolderVisible); + + // Add the new parameters + NewParameters.Add(HoudiniAssetParameter); + NewParmIds.Add(ParmInfo.id); + + // Check if the parameter is a direct child of a multiparam. + if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::MultiParm) + AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); + + if (AllMultiParams.Contains(HoudiniAssetParameter->GetParentParmId())) + { + HoudiniAssetParameter->SetIsDirectChildOfMultiParm(true); + + // Treat the folderlist whose direct parent is a multi param as a multi param too. + if (HoudiniAssetParameter->GetParameterType() == EHoudiniParameterType::FolderList) + AllMultiParams.Add(HoudiniAssetParameter->GetParmId()); + } + } + + // Assign folder type to all folderlists, + // if the first child of the folderlist is Tab or Radio button, set the bIsTabMenu of the folderlistParam to be true, otherwise false + for (int32 Idx = 0; Idx < NewParameters.Num(); ++Idx) + { + UHoudiniParameter * CurParam = NewParameters[Idx]; + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (CurParam->GetParameterType() == EHoudiniParameterType::FolderList) + { + UHoudiniParameterFolderList* CurFolderList = Cast(CurParam); + if (!CurFolderList || CurFolderList->IsPendingKill()) + continue; + + int32 FirstChildIdx = Idx + 1; + if (!NewParameters.IsValidIndex(FirstChildIdx)) + continue; + + UHoudiniParameterFolder* FirstChildFolder = Cast(NewParameters[FirstChildIdx]); + if (!FirstChildFolder || FirstChildFolder->IsPendingKill()) + continue; + + if (FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Radio || + FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Tabs) + { + // If this is the first time build + if (!CurFolderList->IsTabMenu()) + { + // Set the folderlist to be tabs + CurFolderList->SetIsTabMenu(true); + // Select the first child tab folder by default. + FirstChildFolder->SetChosen(true); + } + } + else + CurFolderList->SetIsTabMenu(false); + } + } + + // Create / update the Points arrays for the ramp parameters + if (FloatRampsToIndex.Num() > 0) + { + for (TPair const& Entry : FloatRampsToIndex) + { + UHoudiniParameterRampFloat* const RampFloatParam = Entry.Key; + const int32 ParamIndex = Entry.Value; + if (!IsValid(RampFloatParam)) + continue; + + RampFloatParam->UpdatePointsArray(NewParameters, ParamIndex + 1); + } + } + if (ColorRampsToIndex.Num() > 0) + { + for (TPair const& Entry : ColorRampsToIndex) + { + UHoudiniParameterRampColor* const RampColorParam = Entry.Key; + const int32 ParamIndex = Entry.Value; + if (!IsValid(RampColorParam)) + continue; + + RampColorParam->UpdatePointsArray(NewParameters, ParamIndex + 1); + } + } + + return true; +} + + +void +FHoudiniParameterTranslator::GetParmTypeFromParmInfo( + const HAPI_ParmInfo& ParmInfo, + EHoudiniParameterType& ParmType) +{ + ParmType = EHoudiniParameterType::Invalid; + //ParmValueType = EHoudiniParameterValueType::Invalid; + + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_BUTTON: + ParmType = EHoudiniParameterType::Button; + //ParmValueType = EHoudiniParameterValueType::Int; + break; + + case HAPI_PARMTYPE_STRING: + { + if (ParmInfo.choiceCount > 0) + { + ParmType = EHoudiniParameterType::StringChoice; + //ParmValueType = EHoudiniParameterValueType::String; + } + else + { + ParmType = EHoudiniParameterType::String; + //ParmValueType = EHoudiniParameterValueType::String; + } + break; + } + + case HAPI_PARMTYPE_INT: + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_BUTTONSTRIP) + { + ParmType = EHoudiniParameterType::ButtonStrip; + break; + } + + if (ParmInfo.choiceCount > 0) + { + ParmType = EHoudiniParameterType::IntChoice; + //ParmValueType = EHoudiniParameterValueType::Int; + } + else + { + ParmType = EHoudiniParameterType::Int; + //ParmValueType = EHoudiniParameterValueType::Int; + } + break; + } + + case HAPI_PARMTYPE_FLOAT: + { + ParmType = EHoudiniParameterType::Float; + //ParmValueType = EHoudiniParameterValueType::Float; + break; + } + + case HAPI_PARMTYPE_TOGGLE: + { + ParmType = EHoudiniParameterType::Toggle; + //ParmValueType = EHoudiniParameterValueType::Int; + break; + } + + case HAPI_PARMTYPE_COLOR: + { + ParmType = EHoudiniParameterType::Color; + //ParmValueType = EHoudiniParameterValueType::Float; + break; + } + + case HAPI_PARMTYPE_LABEL: + { + ParmType = EHoudiniParameterType::Label; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_SEPARATOR: + { + ParmType = EHoudiniParameterType::Separator; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + case HAPI_PARMTYPE_FOLDERLIST: + { + ParmType = EHoudiniParameterType::FolderList; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + // Treat radio folders as tab folders for now + case HAPI_PARMTYPE_FOLDERLIST_RADIO: + { + ParmType = EHoudiniParameterType::FolderList; + break; + } + + case HAPI_PARMTYPE_FOLDER: + { + ParmType = EHoudiniParameterType::Folder; + //ParmValueType = EHoudiniParameterValueType::None; + break; + } + + case HAPI_PARMTYPE_MULTIPARMLIST: + { + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType) + { + ParmType = EHoudiniParameterType::FloatRamp; + //ParmValueType = EHoudiniParameterValueType::Float; + } + else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + { + ParmType = EHoudiniParameterType::ColorRamp; + //ParmValueType = EHoudiniParameterValueType::Float; + } + else + { + ParmType = EHoudiniParameterType::MultiParm; + //ParmValueType = EHoudiniParameterValueType::Int; + } + break; + } + + case HAPI_PARMTYPE_PATH_FILE: + { + ParmType = EHoudiniParameterType::File; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_DIR: + { + ParmType = EHoudiniParameterType::FileDir; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_GEO: + { + ParmType = EHoudiniParameterType::FileGeo; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + { + ParmType = EHoudiniParameterType::FileImage; + //ParmValueType = EHoudiniParameterValueType::String; + break; + } + + case HAPI_PARMTYPE_NODE: + { + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + ParmType = EHoudiniParameterType::Input; + } + else + { + ParmType = EHoudiniParameterType::String; + } + break; + } + + default: + { + // Just ignore unsupported types for now. + HOUDINI_LOG_WARNING(TEXT("Parameter Type (%d) is unsupported"), static_cast(ParmInfo.type)); + break; + } + } +} + +UClass* +FHoudiniParameterTranslator::GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo) +{ + UClass* FoundClass = nullptr; + + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_STRING: + if (!ParmInfo.choiceCount) + FoundClass = UHoudiniParameterString::StaticClass(); + else + FoundClass = UHoudiniParameterChoice ::StaticClass(); + break; + + case HAPI_PARMTYPE_INT: + if (!ParmInfo.choiceCount) + FoundClass = UHoudiniParameterInt::StaticClass(); + else + FoundClass = UHoudiniParameterChoice::StaticClass(); + break; + + case HAPI_PARMTYPE_FLOAT: + FoundClass = UHoudiniParameterFloat::StaticClass(); + break; + + case HAPI_PARMTYPE_TOGGLE: + FoundClass = UHoudiniParameterToggle::StaticClass(); + break; + + case HAPI_PARMTYPE_COLOR: + FoundClass = UHoudiniParameterColor::StaticClass(); + break; + + case HAPI_PARMTYPE_LABEL: + FoundClass = UHoudiniParameterLabel::StaticClass(); + break; + + case HAPI_PARMTYPE_BUTTON: + FoundClass = UHoudiniParameterButton::StaticClass(); + break; + + case HAPI_PARMTYPE_SEPARATOR: + FoundClass = UHoudiniParameterSeparator::StaticClass(); + break; + + case HAPI_PARMTYPE_FOLDERLIST: + FoundClass = UHoudiniParameterFolderList::StaticClass(); + break; + + case HAPI_PARMTYPE_FOLDER: + FoundClass = UHoudiniParameterFolder::StaticClass(); + break; + + case HAPI_PARMTYPE_MULTIPARMLIST: + { + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + FoundClass = UHoudiniParameterRampFloat::StaticClass(); + else if (HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + FoundClass = UHoudiniParameterRampColor::StaticClass(); + } + break; + + case HAPI_PARMTYPE_PATH_FILE: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_DIR: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_GEO: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + FoundClass = UHoudiniParameterFile::StaticClass(); + break; + + case HAPI_PARMTYPE_NODE: + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + FoundClass = UHoudiniParameter::StaticClass(); + } + else + { + FoundClass = UHoudiniParameterString::StaticClass(); + } + break; + } + + if (FoundClass == nullptr) + return UHoudiniParameter::StaticClass(); + + return FoundClass; +} + +bool +FHoudiniParameterTranslator::CheckParameterTypeAndClassMatch(UHoudiniParameter* Parameter, const EHoudiniParameterType& ParmType) +{ + UClass* FoundClass = Parameter->GetClass(); + bool FailedTypeCheck = true; + + switch (ParmType) + { + case EHoudiniParameterType::Invalid: + { + FailedTypeCheck = true; + break; + } + + case EHoudiniParameterType::Button: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterButton >(); + break; + } + + case EHoudiniParameterType::Color: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterColor >(); + break; + } + + case EHoudiniParameterType::ColorRamp: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampColor >(); + break; + } + case EHoudiniParameterType::FloatRamp: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterRampFloat >(); + break; + } + + case EHoudiniParameterType::File: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileDir: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileGeo: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + case EHoudiniParameterType::FileImage: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFile >(); + break; + } + + case EHoudiniParameterType::Float: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFloat >(); + break; + } + + case EHoudiniParameterType::Folder: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolder >(); + break; + } + + case EHoudiniParameterType::FolderList: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterFolderList >(); + break; + } + + case EHoudiniParameterType::Input: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterOperatorPath >(); + break; + } + + case EHoudiniParameterType::Int: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterInt >(); + break; + } + + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::StringChoice: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); + break; + } + + case EHoudiniParameterType::Label: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterLabel >(); + break; + } + + case EHoudiniParameterType::MultiParm: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterMultiParm >(); + break; + } + + case EHoudiniParameterType::Separator: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterSeparator >(); + break; + } + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); + break; + } + + case EHoudiniParameterType::Toggle: + { + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterToggle >(); + break; + } + }; + + return !FailedTypeCheck; +} +/* +bool +FHoudiniParameterTranslator::CheckParameterClassAndInfoMatch(UHoudiniParameter* Parameter, const HAPI_ParmInfo& ParmInfo) +{ + if (!Parameter || Parameter->IsPendingKill()) + return false; + + UClass* FoundClass = Parameter->GetClass(); + bool FailedTypeCheck = true; + switch (ParmInfo.type) + { + case HAPI_PARMTYPE_STRING: + if (!ParmInfo.choiceCount) + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterString >(); + else + FailedTypeCheck &= !FoundClass->IsChildOf< UHoudiniParameterChoice >(); + break; + + case HAPI_PARMTYPE_INT: + if (!ParmInfo.choiceCount) + FailedTypeCheck &= !FoundClass->IsChildOf(); + else + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FLOAT: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_TOGGLE: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_COLOR: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_LABEL: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_BUTTON: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_SEPARATOR: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FOLDERLIST: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_FOLDER: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_MULTIPARMLIST: + if (HAPI_RAMPTYPE_FLOAT == ParmInfo.rampType || HAPI_RAMPTYPE_COLOR == ParmInfo.rampType) + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + break; + + case HAPI_PARMTYPE_PATH_FILE: + case HAPI_PARMTYPE_PATH_FILE_DIR: + case HAPI_PARMTYPE_PATH_FILE_GEO: + case HAPI_PARMTYPE_PATH_FILE_IMAGE: + FailedTypeCheck &= !FoundClass->IsChildOf(); + break; + + case HAPI_PARMTYPE_NODE: + if (ParmInfo.inputNodeType == HAPI_NODETYPE_ANY || + ParmInfo.inputNodeType == HAPI_NODETYPE_SOP || + ParmInfo.inputNodeType == HAPI_NODETYPE_OBJ) + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + else + { + FailedTypeCheck &= !FoundClass->IsChildOf(); + } + break; + } + + return FailedTypeCheck; +} +*/ + +UHoudiniParameter * +FHoudiniParameterTranslator::CreateTypedParameter(UObject * Outer, const EHoudiniParameterType& ParmType, const FString& ParmName) +{ + UHoudiniParameter* HoudiniParameter = nullptr; + // Create a parameter of the desired type + switch (ParmType) + { + case EHoudiniParameterType::Button: + HoudiniParameter = UHoudiniParameterButton::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::ButtonStrip: + HoudiniParameter = UHoudiniParameterButtonStrip::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Color: + HoudiniParameter = UHoudiniParameterColor::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::ColorRamp: + HoudiniParameter = UHoudiniParameterRampColor::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FloatRamp: + HoudiniParameter = UHoudiniParameterRampFloat::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::File: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FileDir: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileDir); + break; + + case EHoudiniParameterType::FileGeo: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileGeo); + break; + + case EHoudiniParameterType::FileImage: + HoudiniParameter = UHoudiniParameterFile::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(EHoudiniParameterType::FileImage); + break; + + case EHoudiniParameterType::Float: + HoudiniParameter = UHoudiniParameterFloat::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Folder: + HoudiniParameter = UHoudiniParameterFolder::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::FolderList: + HoudiniParameter = UHoudiniParameterFolderList::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Input: + // Input parameter simply use the base class as all the processingsince is handled by UHoudiniInput + HoudiniParameter = UHoudiniParameterOperatorPath::Create(Outer, ParmName); + HoudiniParameter->SetParameterType(ParmType); + break; + + case EHoudiniParameterType::Int: + HoudiniParameter = UHoudiniParameterInt::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::IntChoice: + HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::IntChoice); + break; + + case EHoudiniParameterType::StringChoice: + HoudiniParameter = UHoudiniParameterChoice::Create(Outer, ParmName, EHoudiniParameterType::StringChoice); + break; + + case EHoudiniParameterType::Label: + HoudiniParameter = UHoudiniParameterLabel::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::MultiParm: + HoudiniParameter = UHoudiniParameterMultiParm::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Separator: + HoudiniParameter = UHoudiniParameterSeparator::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + HoudiniParameter = UHoudiniParameterString::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Toggle: + HoudiniParameter = UHoudiniParameterToggle::Create(Outer, ParmName); + break; + + case EHoudiniParameterType::Invalid: + // TODO handle invalid params + HoudiniParameter = UHoudiniParameter::Create(Outer, ParmName); + break; + } + + return HoudiniParameter; +} + +bool +FHoudiniParameterTranslator::UpdateParameterFromInfo( + UHoudiniParameter * HoudiniParameter, const HAPI_NodeId& InNodeId, const HAPI_ParmInfo& ParmInfo, + const bool& bFullUpdate, const bool& bUpdateValue, + const TArray* DefaultIntValues, + const TArray* DefaultFloatValues, + const TArray* DefaultStringValues, + const TArray* DefaultChoiceValues) +{ + if (!HoudiniParameter || HoudiniParameter->IsPendingKill()) + return false; + + // Copy values from the ParmInfos + const bool bHasValidNodeId = InNodeId >= 0; + if (bHasValidNodeId) + HoudiniParameter->SetNodeId(InNodeId); + HoudiniParameter->SetParmId(ParmInfo.id); + HoudiniParameter->SetParentParmId(ParmInfo.parentId); + + HoudiniParameter->SetChildIndex(ParmInfo.childIndex); + HoudiniParameter->SetTagCount(ParmInfo.tagCount); + HoudiniParameter->SetTupleSize(ParmInfo.size); + + HoudiniParameter->SetVisible(!ParmInfo.invisible); + HoudiniParameter->SetDisabled(ParmInfo.disabled); + HoudiniParameter->SetSpare(ParmInfo.spare); + HoudiniParameter->SetJoinNext(ParmInfo.joinNext); + + HoudiniParameter->SetTagCount(ParmInfo.tagCount); + HoudiniParameter->SetIsChildOfMultiParm(ParmInfo.isChildOfMultiParm); + + UHoudiniParameterMultiParm* MultiParm = Cast(HoudiniParameter); + if(MultiParm) + MultiParm->InstanceStartOffset = ParmInfo.instanceStartOffset; + + + + // Get the parameter type + EHoudiniParameterType ParmType = HoudiniParameter->GetParameterType(); + + // We need to set string values from the parmInfo + if (bFullUpdate) + { + FString Name; + { + // Name + FHoudiniEngineString HoudiniEngineStringName(ParmInfo.nameSH); + if (HoudiniEngineStringName.ToFString(Name)) + HoudiniParameter->SetParameterName(Name); + } + + { + // Label + FString Label; + FHoudiniEngineString HoudiniEngineStringLabel(ParmInfo.labelSH); + if (HoudiniEngineStringLabel.ToFString(Label)) + HoudiniParameter->SetParameterLabel(Label); + } + + { + // Help + FString Help; + FHoudiniEngineString HoudiniEngineStringHelp(ParmInfo.helpSH); + if (HoudiniEngineStringHelp.ToFString(Help)) + HoudiniParameter->SetParameterHelp(Help); + } + + if (bHasValidNodeId && + (ParmType == EHoudiniParameterType::String + || ParmType == EHoudiniParameterType::Int + || ParmType == EHoudiniParameterType::Float + || ParmType == EHoudiniParameterType::Toggle + || ParmType == EHoudiniParameterType::Color)) + { + // See if the parm has an expression + int32 TupleIdx = ParmInfo.intValuesIndex; + bool bHasExpression = false; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ParmHasExpression( + FHoudiniEngine::Get().GetSession(), InNodeId, + TCHAR_TO_UTF8(*Name), TupleIdx, &bHasExpression)) + { + // ? + } + + FString ParmExprString = TEXT(""); + if (bHasExpression) + { + // Try to get the expression's value + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmExpression( + FHoudiniEngine::Get().GetSession(), InNodeId, + TCHAR_TO_UTF8(*Name), TupleIdx, &StringHandle)) + { + FHoudiniEngineString HoudiniEngineString(StringHandle); + HoudiniEngineString.ToFString(ParmExprString); + } + + // Check if we actually have an expression + // String parameters return true even if they do not have one + bHasExpression = ParmExprString.Len() > 0; + + } + + HoudiniParameter->SetHasExpression(bHasExpression); + HoudiniParameter->SetExpression(ParmExprString); + } + else + { + HoudiniParameter->SetHasExpression(false); + HoudiniParameter->SetExpression(FString()); + } + + // Get parameter tags. + if (bHasValidNodeId) + { + int32 TagCount = HoudiniParameter->GetTagCount(); + for (int32 Idx = 0; Idx < TagCount; ++Idx) + { + HAPI_StringHandle TagNameSH; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagName( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmInfo.id, Idx, &TagNameSH)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); + continue; + } + + FString NameString = TEXT(""); + FHoudiniEngineString::ToFString(TagNameSH, NameString); + if (NameString.IsEmpty()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag name: parmId: %d, tag index: %d"), ParmInfo.id, Idx); + continue; + } + + HAPI_StringHandle TagValueSH; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), + InNodeId, ParmInfo.id, TCHAR_TO_ANSI(*NameString), &TagValueSH)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to retrive parameter tag value: parmId: %d, tag: %s"), ParmInfo.id, *NameString); + } + + FString ValueString = TEXT(""); + FHoudiniEngineString::ToFString(TagValueSH, ValueString); + + HoudiniParameter->GetTags().Add(NameString, ValueString); + } + } + } + + // + // Update properties specific to parameter classes + switch (ParmType) + { + case EHoudiniParameterType::Button: + { + UHoudiniParameterButton* HoudiniParameterButton = Cast(HoudiniParameter); + if (HoudiniParameterButton && !HoudiniParameterButton->IsPendingKill()) + { + HoudiniParameterButton->SetValueIndex(ParmInfo.intValuesIndex); + } + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + UHoudiniParameterButtonStrip* HoudiniParameterButtonStrip = Cast(HoudiniParameter); + if (HoudiniParameterButtonStrip && !HoudiniParameterButtonStrip->IsPendingKill()) + { + HoudiniParameterButtonStrip->SetValueIndex(ParmInfo.intValuesIndex); + HoudiniParameterButtonStrip->Count = ParmInfo.choiceCount; + } + + if (bFullUpdate) + { + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + + ParmChoices.SetNum(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + } + else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && + DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) + { + FPlatformMemory::Memcpy( + ParmChoices.GetData(), + DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, + sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); + } + else + { + return false; + } + + HoudiniParameterButtonStrip->InitializeLabels(ParmInfo.choiceCount); + + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ButtonLabel = HoudiniParameterButtonStrip->GetStringLabelAt(ChoiceIdx); + if (ButtonLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ButtonLabel)) + return false; + } + } + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterButtonStrip->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.choiceCount) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && + DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.choiceCount - 1)) + { + for (int32 Index = 0; Index < ParmInfo.choiceCount; ++Index) + { + HoudiniParameterButtonStrip->SetValueAt( + Index, (*DefaultIntValues)[ParmInfo.intValuesIndex + Index]); + } + } + else + { + return false; + } + } + } + break; + + case EHoudiniParameterType::Color: + { + UHoudiniParameterColor* HoudiniParameterColor = Cast(HoudiniParameter); + if (HoudiniParameterColor && !HoudiniParameterColor->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterColor->SetValueIndex(ParmInfo.floatValuesIndex); + + // Update the Parameter value if we want to + if (bUpdateValue) + { + // Get the actual value for this property. + FLinearColor Color = FLinearColor::White; + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + (float *)&Color.R, ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultFloatValues && DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex) && + DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex + ParmInfo.size - 1)) + { + FPlatformMemory::Memcpy( + &Color.R, + DefaultFloatValues->GetData() + ParmInfo.floatValuesIndex, + sizeof(float) * ParmInfo.size); + } + else + { + return false; + } + + HoudiniParameterColor->SetColorValue(Color); + } + + if (bFullUpdate) + { + // Set the default value at parameter created. + HoudiniParameterColor->SetDefaultValue(); + } + } + } + break; + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* HoudiniParameterRampColor = Cast(HoudiniParameter); + if (HoudiniParameterRampColor && !HoudiniParameterRampColor->IsPendingKill()) + { + HoudiniParameterRampColor->SetInstanceCount(ParmInfo.instanceCount); + HoudiniParameterRampColor->MultiParmInstanceLength = ParmInfo.instanceLength; + } + } + break; + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* HoudiniParameterRampFloat = Cast(HoudiniParameter); + if (HoudiniParameterRampFloat && !HoudiniParameterRampFloat->IsPendingKill()) + { + HoudiniParameterRampFloat->SetInstanceCount(ParmInfo.instanceCount); + HoudiniParameterRampFloat->MultiParmInstanceLength = ParmInfo.instanceLength; + } + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + UHoudiniParameterFile* HoudiniParameterFile = Cast(HoudiniParameter); + if (HoudiniParameterFile && !HoudiniParameterFile->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFile->SetValueIndex(ParmInfo.stringValuesIndex); + + // Update the file filter and read only tag only for full updates + if (bFullUpdate) + { + // Check if we are read-only + bool bIsReadOnly = false; + FString FileChooserTag; + if (bHasValidNodeId && FHoudiniParameterTranslator::HapiGetParameterTagValue(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_FILE_READONLY, FileChooserTag)) + { + if (FileChooserTag.Equals(TEXT("read"), ESearchCase::IgnoreCase)) + bIsReadOnly = true; + } + HoudiniParameterFile->SetReadOnly(bIsReadOnly); + + // Update the file type using the typeInfo string. + if (ParmInfo.typeInfoSH > 0) + { + FString Filters; + FHoudiniEngineString HoudiniEngineString(ParmInfo.typeInfoSH); + if (HoudiniEngineString.ToFString(Filters)) + { + if (!Filters.IsEmpty()) + HoudiniParameterFile->SetFileFilters(Filters); + } + } + } + + if (bUpdateValue) + { + // Get the actual values for this property. + TArray< HAPI_StringHandle > StringHandles; + + if (bHasValidNodeId) + { + StringHandles.SetNumZeroed(ParmInfo.size); + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), InNodeId, false, + &StringHandles[0], ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && + DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FPlatformMemory::Memcpy( + &StringHandles[0], + DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, + sizeof(HAPI_StringHandle) * ParmInfo.size); + } + else + { + return false; + } + + // Convert HAPI string handles to Unreal strings. + HoudiniParameterFile->SetNumberOfValues(ParmInfo.size); + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + + // Update the parameter value + HoudiniParameterFile->SetValueAt(ValueString, Idx); + } + } + + if (bFullUpdate) + { + HoudiniParameterFile->SetDefaultValues(); + } + } + } + break; + + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* HoudiniParameterFloat = Cast(HoudiniParameter); + if (HoudiniParameterFloat && !HoudiniParameterFloat->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFloat->SetValueIndex(ParmInfo.floatValuesIndex); + + if (bUpdateValue) + { + // Update the parameter's value + HoudiniParameterFloat->SetNumberOfValues(ParmInfo.size); + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmFloatValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterFloat->GetValuesPtr(), + ParmInfo.floatValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultFloatValues && DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex) && + DefaultFloatValues->IsValidIndex(ParmInfo.floatValuesIndex + ParmInfo.size - 1)) + { + FPlatformMemory::Memcpy( + HoudiniParameterFloat->GetValuesPtr(), + DefaultFloatValues->GetData() + ParmInfo.floatValuesIndex, + sizeof(float) * ParmInfo.size); + } + else + { + return false; + } + } + + if (bFullUpdate) + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_FLOAT_LOG) + { + HoudiniParameterFloat->SetIsLogarithmic(true); + } + + // set the default float values. + HoudiniParameterFloat->SetDefaultValues(); + + // Only update Unit, no swap, and Min/Max values when doing a full update + + // Get the parameter's unit from the "unit" tag + FString ParamUnit; + if (bHasValidNodeId) + { + FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); + HoudiniParameterFloat->SetUnit(ParamUnit); + // Get the parameter's no swap tag (hengine_noswap) + HoudiniParameterFloat->SetNoSwap(HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_NOSWAP)); + } + + // Set the min and max for this parameter + if (ParmInfo.hasMin) + { + HoudiniParameterFloat->SetHasMin(true); + HoudiniParameterFloat->SetMin(ParmInfo.min); + } + else + { + HoudiniParameterFloat->SetHasMin(false); + HoudiniParameterFloat->SetMin(TNumericLimits::Lowest()); + } + + if (ParmInfo.hasMax) + { + HoudiniParameterFloat->SetHasMax(true); + HoudiniParameterFloat->SetMax(ParmInfo.max); + } + else + { + HoudiniParameterFloat->SetHasMax(false); + HoudiniParameterFloat->SetMax(TNumericLimits::Max()); + } + + // Set min and max for UI for this property. + bool bUsesDefaultMin = false; + if (ParmInfo.hasUIMin) + { + HoudiniParameterFloat->SetHasUIMin(true); + HoudiniParameterFloat->SetUIMin(ParmInfo.UIMin); + } + else if (ParmInfo.hasMin) + { + // If it is not set, use supplied min. + HoudiniParameterFloat->SetUIMin(ParmInfo.min); + } + else + { + // Min value Houdini uses by default. + HoudiniParameterFloat->SetUIMin(HAPI_UNREAL_PARAM_FLOAT_UI_MIN); + bUsesDefaultMin = true; + } + + bool bUsesDefaultMax = false; + if (ParmInfo.hasUIMax) + { + HoudiniParameterFloat->SetHasUIMax(true); + HoudiniParameterFloat->SetUIMax(ParmInfo.UIMax); + } + else if (ParmInfo.hasMax) + { + // If it is not set, use supplied max. + HoudiniParameterFloat->SetUIMax(ParmInfo.max); + } + else + { + // Max value Houdini uses by default. + HoudiniParameterFloat->SetUIMax(HAPI_UNREAL_PARAM_FLOAT_UI_MAX); + bUsesDefaultMax = true; + } + + if (bUsesDefaultMin || bUsesDefaultMax) + { + // If we are using defaults, we can detect some most common parameter names and alter defaults. + FString LocalParameterName = HoudiniParameterFloat->GetParameterName(); + FHoudiniEngineString HoudiniEngineString(ParmInfo.nameSH); + HoudiniEngineString.ToFString(LocalParameterName); + + static const FString ParameterNameTranslate(TEXT(HAPI_UNREAL_PARAM_TRANSLATE)); + static const FString ParameterNameRotate(TEXT(HAPI_UNREAL_PARAM_ROTATE)); + static const FString ParameterNameScale(TEXT(HAPI_UNREAL_PARAM_SCALE)); + static const FString ParameterNamePivot(TEXT(HAPI_UNREAL_PARAM_PIVOT)); + + if (!LocalParameterName.IsEmpty()) + { + if (LocalParameterName.Equals(ParameterNameTranslate) + || LocalParameterName.Equals(ParameterNameScale) + || LocalParameterName.Equals(ParameterNamePivot)) + { + if (bUsesDefaultMin) + { + HoudiniParameterFloat->SetUIMin(-1.0f); + } + if (bUsesDefaultMax) + { + HoudiniParameterFloat->SetUIMax(1.0f); + } + } + else if (LocalParameterName.Equals(ParameterNameRotate)) + { + if (bUsesDefaultMin) + { + HoudiniParameterFloat->SetUIMin(0.0f); + } + if (bUsesDefaultMax) + { + HoudiniParameterFloat->SetUIMax(360.0f); + } + } + } + } + } + } + } + break; + + case EHoudiniParameterType::Folder: + { + UHoudiniParameterFolder* HoudiniParameterFolder = Cast(HoudiniParameter); + if (HoudiniParameterFolder && !HoudiniParameterFolder->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFolder->SetValueIndex(ParmInfo.intValuesIndex); + HoudiniParameterFolder->SetFolderType(GetFolderTypeFromParamInfo(&ParmInfo)); + } + } + break; + + case EHoudiniParameterType::FolderList: + { + UHoudiniParameterFolderList* HoudiniParameterFolderList = Cast(HoudiniParameter); + if (HoudiniParameterFolderList && !HoudiniParameterFolderList->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterFolderList->SetValueIndex(ParmInfo.intValuesIndex); + } + } + break; + + case EHoudiniParameterType::Input: + { + // Inputs parameters are just stored, and handled separately by UHoudiniInputs + UHoudiniParameterOperatorPath* HoudiniParameterOperatorPath = Cast(HoudiniParameter); + if (HoudiniParameterOperatorPath && !HoudiniParameterOperatorPath->IsPendingKill()) + { + /* + // DO NOT CREATE A DUPLICATE INPUT HERE! + // Inputs are created by the input translator, and will be tied to this parameter there + UHoudiniInput * NewInput = NewObject< UHoudiniInput >( + HoudiniParameterOperatorPath, + UHoudiniInput::StaticClass()); + + UHoudiniAssetComponent *ParentHAC = Cast(HoudiniParameterOperatorPath->GetOuter()); + + if (!ParentHAC) + return false; + + if (!NewInput || NewInput->IsPendingKill()) + return false; + + // Set the nodeId + NewInput->SetAssetNodeId(ParentHAC->GetAssetId()); + NewInput->SetInputType(EHoudiniInputType::Geometry); + HoudiniParameterOperatorPath->HoudiniInputs.Add(NewInput); + */ + // Set the valueIndex + HoudiniParameterOperatorPath->SetValueIndex(ParmInfo.stringValuesIndex); + } + } + break; + + case EHoudiniParameterType::Int: + { + UHoudiniParameterInt* HoudiniParameterInt = Cast(HoudiniParameter); + if (HoudiniParameterInt && !HoudiniParameterInt->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterInt->SetValueIndex(ParmInfo.intValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + HoudiniParameterInt->SetNumberOfValues(ParmInfo.size); + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterInt->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && + DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.size - 1)) + { + for (int32 Index = 0; Index < ParmInfo.size; ++Index) + { + // TODO: cannot use SetValueAt: Min/Max has not yet been configured and defaults to 0,0 + // so the value is clamped to 0 + // HoudiniParameterInt->SetValueAt( + // (*DefaultIntValues)[ParmInfo.intValuesIndex + Index], Index); + *(HoudiniParameterInt->GetValuesPtr() + Index) = (*DefaultIntValues)[ParmInfo.intValuesIndex + Index]; + } + } + else + { + return false; + } + } + + if (bFullUpdate) + { + if (ParmInfo.scriptType == HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_INT_LOG) + { + HoudiniParameterInt->SetIsLogarithmic(true); + } + + // Set the default int values at created + HoudiniParameterInt->SetDefaultValues(); + // Only update unit and Min/Max values for a full update + + // Get the parameter's unit from the "unit" tag + FString ParamUnit; + if (bHasValidNodeId) + { + FHoudiniParameterTranslator::HapiGetParameterUnit(InNodeId, ParmInfo.id, ParamUnit); + HoudiniParameterInt->SetUnit(ParamUnit); + } + + // Set the min and max for this parameter + if (ParmInfo.hasMin) + { + HoudiniParameterInt->SetHasMin(true); + HoudiniParameterInt->SetMin((int32)ParmInfo.min); + } + else + { + HoudiniParameterInt->SetHasMin(false); + HoudiniParameterInt->SetMin(TNumericLimits::Lowest()); + } + + if (ParmInfo.hasMax) + { + HoudiniParameterInt->SetHasMax(true); + HoudiniParameterInt->SetMax((int32)ParmInfo.max); + } + else + { + HoudiniParameterInt->SetHasMax(false); + HoudiniParameterInt->SetMax(TNumericLimits::Max()); + } + + // Set min and max for UI for this property. + if (ParmInfo.hasUIMin) + { + HoudiniParameterInt->SetHasUIMin(true); + HoudiniParameterInt->SetUIMin((int32)ParmInfo.UIMin); + } + else if (ParmInfo.hasMin) + { + // If it is not set, use supplied min. + HoudiniParameterInt->SetUIMin((int32)ParmInfo.min); + } + else + { + // Min value Houdini uses by default. + HoudiniParameterInt->SetUIMin(HAPI_UNREAL_PARAM_INT_UI_MIN); + } + + if (ParmInfo.hasUIMax) + { + HoudiniParameterInt->SetHasUIMax(true); + HoudiniParameterInt->SetUIMax((int32)ParmInfo.UIMax); + } + else if (ParmInfo.hasMax) + { + // If it is not set, use supplied max. + HoudiniParameterInt->SetUIMax((int32)ParmInfo.max); + } + else + { + // Max value Houdini uses by default. + HoudiniParameterInt->SetUIMax(HAPI_UNREAL_PARAM_INT_UI_MAX); + } + } + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* HoudiniParameterIntChoice = Cast(HoudiniParameter); + if (HoudiniParameterIntChoice && !HoudiniParameterIntChoice->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterIntChoice->SetValueIndex(ParmInfo.intValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + int32 CurrentIntValue = 0; + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &CurrentIntValue, + ParmInfo.intValuesIndex, 1/*ParmInfo.size*/), false); + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex)) + { + CurrentIntValue = (*DefaultIntValues)[ParmInfo.intValuesIndex]; + } + else + { + return false; + } + + // Get the value from the index array, if applicable. + if (CurrentIntValue < HoudiniParameterIntChoice->GetNumChoices()) + CurrentIntValue = HoudiniParameterIntChoice->GetIndexFromValueArray(CurrentIntValue); + + // Check the value is valid + if (CurrentIntValue >= ParmInfo.choiceCount) + { + HOUDINI_LOG_WARNING(TEXT("parm '%s' has an invalid value %d, menu tokens are not supported for choice menus"), + *HoudiniParameterIntChoice->GetParameterName(), CurrentIntValue); + CurrentIntValue = 0; + } + + HoudiniParameterIntChoice->SetIntValue(CurrentIntValue); + } + + // Get the choice descriptors + if (bFullUpdate) + { + // Set the default value at created + HoudiniParameterIntChoice->SetDefaultIntValue(); + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + + ParmChoices.SetNum(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + } + else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && + DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) + { + FPlatformMemory::Memcpy( + ParmChoices.GetData(), + DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, + sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); + } + else + { + return false; + } + + // Set the array sizes + HoudiniParameterIntChoice->SetNumChoices(ParmInfo.choiceCount); + + bool bMatchedSelectionLabel = false; + int32 CurrentIntValue = HoudiniParameterIntChoice->GetIntValueIndex(); + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ChoiceLabel = HoudiniParameterIntChoice->GetStringChoiceLabelAt(ChoiceIdx); + if (ChoiceLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ChoiceLabel)) + return false; + //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); + } + + // Match our string value to the corresponding selection label. + if (ChoiceIdx == CurrentIntValue) + { + HoudiniParameterIntChoice->SetStringValue(*ChoiceLabel); + } + + int32 IntValue = ChoiceIdx; + + // If useMenuItemTokenAsValue is set, then the value is not the index. Find the value using the token, if possible. + if (ParmInfo.useMenuItemTokenAsValue) + { + if (ChoiceIdx < ParmChoices.Num()) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + FString Token; + + if (HoudiniEngineString.ToFString(Token)) + { + int32 Value = FCString::Atoi(*Token); + IntValue = Value; + } + } + } + + HoudiniParameterIntChoice->SetIntValueArray(ChoiceIdx, IntValue); + } + } + else if (bUpdateValue) + { + // We still need to match the string value to the label + HoudiniParameterIntChoice->UpdateStringValueFromInt(); + } + } + } + break; + + case EHoudiniParameterType::StringChoice: + { + UHoudiniParameterChoice* HoudiniParameterStringChoice = Cast(HoudiniParameter); + if (HoudiniParameterStringChoice && !HoudiniParameterStringChoice->IsPendingKill()) + { + // Set the valueIndex + HoudiniParameterStringChoice->SetValueIndex(ParmInfo.stringValuesIndex); + + if (bUpdateValue) + { + // Get the actual values for this property. + HAPI_StringHandle StringHandle; + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandle, + ParmInfo.stringValuesIndex, 1/*ParmInfo.size*/), false); + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex)) + { + StringHandle = (*DefaultStringValues)[ParmInfo.stringValuesIndex]; + } + else + { + return false; + } + + // Get the string value + FString StringValue; + FHoudiniEngineString HoudiniEngineString(StringHandle); + HoudiniEngineString.ToFString(StringValue); + + HoudiniParameterStringChoice->SetStringValue(StringValue); + } + + // Get the choice descriptors + if (bFullUpdate) + { + // Set default value at created. + HoudiniParameterStringChoice->SetDefaultStringValue(); + // Get the choice descriptors. + TArray< HAPI_ParmChoiceInfo > ParmChoices; + + ParmChoices.SetNum(ParmInfo.choiceCount); + for (int32 Idx = 0; Idx < ParmChoices.Num(); Idx++) + FHoudiniApi::ParmChoiceInfo_Init(&(ParmChoices[Idx])); + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetParmChoiceLists( + FHoudiniEngine::Get().GetSession(), + InNodeId, &ParmChoices[0], + ParmInfo.choiceIndex, ParmInfo.choiceCount), false); + } + else if (DefaultChoiceValues && DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex) && + DefaultChoiceValues->IsValidIndex(ParmInfo.choiceIndex + ParmInfo.choiceCount - 1)) + { + FPlatformMemory::Memcpy( + ParmChoices.GetData(), + DefaultChoiceValues->GetData() + ParmInfo.choiceIndex, + sizeof(HAPI_ParmChoiceInfo) * ParmInfo.choiceCount); + } + else + { + return false; + } + + // Set the array sizes + HoudiniParameterStringChoice->SetNumChoices(ParmInfo.choiceCount); + + bool bMatchedSelectionLabel = false; + FString CurrentStringValue = HoudiniParameterStringChoice->GetStringValue(); + for (int32 ChoiceIdx = 0; ChoiceIdx < ParmChoices.Num(); ++ChoiceIdx) + { + FString * ChoiceValue = HoudiniParameterStringChoice->GetStringChoiceValueAt(ChoiceIdx); + if (ChoiceValue) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].valueSH); + if (!HoudiniEngineString.ToFString(*ChoiceValue)) + return false; + //StringChoiceValues.Add(TSharedPtr< FString >(ChoiceValue)); + } + + FString * ChoiceLabel = HoudiniParameterStringChoice->GetStringChoiceLabelAt(ChoiceIdx); + if (ChoiceLabel) + { + FHoudiniEngineString HoudiniEngineString(ParmChoices[ChoiceIdx].labelSH); + if (!HoudiniEngineString.ToFString(*ChoiceLabel)) + return false; + //StringChoiceLabels.Add(TSharedPtr< FString >(ChoiceLabel)); + } + + // If this is a string choice list, we need to match name with corresponding selection label. + if (!bMatchedSelectionLabel && ChoiceValue->Equals(CurrentStringValue)) + { + bMatchedSelectionLabel = true; + HoudiniParameterStringChoice->SetIntValue(ChoiceIdx); + } + } + } + else if (bUpdateValue) + { + // We still need to match the string value to the label + HoudiniParameterStringChoice->UpdateIntValueFromString(); + } + } + } + break; + + case EHoudiniParameterType::Label: + { + UHoudiniParameterLabel* HoudiniParameterLabel = Cast(HoudiniParameter); + if (HoudiniParameterLabel && !HoudiniParameterLabel->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_LABEL) + return false; + + // Set the valueIndex + HoudiniParameterLabel->SetValueIndex(ParmInfo.stringValuesIndex); + + // Get the actual value for this property. + TArray StringHandles; + + if (bHasValidNodeId) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandles[0], + ParmInfo.stringValuesIndex, ParmInfo.size); + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && + DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FPlatformMemory::Memcpy( + StringHandles.GetData(), + DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, + sizeof(HAPI_StringHandle) * ParmInfo.size); + } + else + { + return false; + } + + HoudiniParameterLabel->EmptyLabelString(); + + // Convert HAPI string handles to Unreal strings. + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + HoudiniParameterLabel->AddLabelString(ValueString); + } + } + } + break; + + case EHoudiniParameterType::MultiParm: + { + UHoudiniParameterMultiParm* HoudiniParameterMulti = Cast(HoudiniParameter); + if (HoudiniParameterMulti && !HoudiniParameterMulti->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST) + return false; + + // Set the valueIndex + HoudiniParameterMulti->SetValueIndex(ParmInfo.intValuesIndex); + + // Set the multiparm value + int32 MultiParmValue = 0; + + if (bHasValidNodeId) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, &MultiParmValue, ParmInfo.intValuesIndex, 1), false); + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex)) + { + MultiParmValue = (*DefaultIntValues)[ParmInfo.intValuesIndex]; + } + else + { + return false; + } + + HoudiniParameterMulti->SetValue(MultiParmValue); + HoudiniParameterMulti->MultiParmInstanceCount = ParmInfo.instanceCount; + HoudiniParameterMulti->MultiParmInstanceLength = ParmInfo.instanceLength; + + } + + if (bFullUpdate) + { + HoudiniParameterMulti->SetDefaultInstanceCount(ParmInfo.instanceCount); + } + } + break; + + case EHoudiniParameterType::Separator: + { + UHoudiniParameterSeparator* HoudiniParameterSeparator = Cast(HoudiniParameter); + if (HoudiniParameterSeparator && !HoudiniParameterSeparator->IsPendingKill()) + { + // We can only handle separator type. + if (ParmInfo.type != HAPI_PARMTYPE_SEPARATOR) + return false; + + // Set the valueIndex + HoudiniParameterSeparator->SetValueIndex(ParmInfo.stringValuesIndex); + } + } + break; + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringAssetRef: + { + UHoudiniParameterString* HoudiniParameterString = Cast(HoudiniParameter); + if (HoudiniParameterString && !HoudiniParameterString->IsPendingKill()) + { + // We can only handle string type. + if (ParmInfo.type != HAPI_PARMTYPE_STRING && ParmInfo.type != HAPI_PARMTYPE_NODE) + return false; + + // Set the valueIndex + HoudiniParameterString->SetValueIndex(ParmInfo.stringValuesIndex); + + // Stop if we don't want to update the value + if (bUpdateValue) + { + // Get the actual value for this property. + TArray< HAPI_StringHandle > StringHandles; + + if (bHasValidNodeId) + { + StringHandles.SetNumZeroed(ParmInfo.size); + if (FHoudiniApi::GetParmStringValues( + FHoudiniEngine::Get().GetSession(), + InNodeId, false, &StringHandles[0], + ParmInfo.stringValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultStringValues && DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex) && + DefaultStringValues->IsValidIndex(ParmInfo.stringValuesIndex + ParmInfo.size - 1)) + { + StringHandles.SetNumZeroed(ParmInfo.size); + FPlatformMemory::Memcpy( + StringHandles.GetData(), + DefaultStringValues->GetData() + ParmInfo.stringValuesIndex, + sizeof(HAPI_StringHandle) * ParmInfo.size); + } + else + { + return false; + } + + // Convert HAPI string handles to Unreal strings. + HoudiniParameterString->SetNumberOfValues(ParmInfo.size); + for (int32 Idx = 0; Idx < StringHandles.Num(); ++Idx) + { + FString ValueString = TEXT(""); + FHoudiniEngineString HoudiniEngineString(StringHandles[Idx]); + HoudiniEngineString.ToFString(ValueString); + HoudiniParameterString->SetValueAt(ValueString, Idx); + } + } + + if (bFullUpdate) + { + // Set default string values on created + HoudiniParameterString->SetDefaultValues(); + // Check if the parameter has the "asset_ref" tag + if (bHasValidNodeId) + { + HoudiniParameterString->SetIsAssetRef( + FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_ASSET_REF)); + } + } + } + } + break; + + case EHoudiniParameterType::Toggle: + { + UHoudiniParameterToggle* HoudiniParameterToggle = Cast(HoudiniParameter); + if (HoudiniParameterToggle && !HoudiniParameterToggle->IsPendingKill()) + { + if (ParmInfo.type != HAPI_PARMTYPE_TOGGLE) + return false; + + // Set the valueIndex + HoudiniParameterToggle->SetValueIndex(ParmInfo.intValuesIndex); + + // Stop if we don't want to update the value + if (bUpdateValue) + { + // Get the actual values for this property. + HoudiniParameterToggle->SetNumberOfValues(ParmInfo.size); + + if (bHasValidNodeId) + { + if (FHoudiniApi::GetParmIntValues( + FHoudiniEngine::Get().GetSession(), InNodeId, + HoudiniParameterToggle->GetValuesPtr(), + ParmInfo.intValuesIndex, ParmInfo.size) != HAPI_RESULT_SUCCESS) + { + return false; + } + } + else if (DefaultIntValues && DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex) && + DefaultIntValues->IsValidIndex(ParmInfo.intValuesIndex + ParmInfo.size - 1)) + { + for (int32 Index = 0; Index < ParmInfo.size; ++Index) + { + HoudiniParameterToggle->SetValueAt( + (*DefaultIntValues)[ParmInfo.intValuesIndex + Index] != 0, Index); + } + } + else + { + return false; + } + } + } + + if (bFullUpdate) + { + HoudiniParameterToggle->SetDefaultValues(); + } + } + break; + + case EHoudiniParameterType::Invalid: + { + // TODO + } + break; + } + + return true; +} + +bool +FHoudiniParameterTranslator::HapiGetParameterTagValue(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag, FString& TagValue) +{ + // Default + TagValue = FString(); + + // Does the parameter has the tag? + bool HasTag = false; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &HasTag), false); + + if (!HasTag) + return false; + + // Get the tag string value + HAPI_StringHandle StringHandle; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmTagValue( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &StringHandle), false); + + FHoudiniEngineString HoudiniEngineString(StringHandle); + if (HoudiniEngineString.ToFString(TagValue)) + { + return true; + } + + return false; +} + + +bool +FHoudiniParameterTranslator::HapiGetParameterUnit(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, FString& OutUnitString) +{ + // + OutUnitString = TEXT(""); + + // We're looking for the parameter unit tag + //FString UnitTag = "units"; + + // Get the actual string value. + FString UnitString = TEXT(""); + if (!FHoudiniParameterTranslator::HapiGetParameterTagValue(NodeId, ParmId, "units", UnitString)) + return false; + + // We need to do some replacement in the string here in order to be able to get the + // proper unit type when calling FUnitConversion::UnitFromString(...) after. + + // Per second and per hour are the only "per" unit that unreal recognize + UnitString.ReplaceInline(TEXT("s-1"), TEXT("/s")); + UnitString.ReplaceInline(TEXT("h-1"), TEXT("/h")); + + // Houdini likes to add '1' on all the unit, so we'll remove all of them + // except the '-1' that still needs to remain. + UnitString.ReplaceInline(TEXT("-1"), TEXT("--")); + UnitString.ReplaceInline(TEXT("1"), TEXT("")); + UnitString.ReplaceInline(TEXT("--"), TEXT("-1")); + + OutUnitString = UnitString; + + return true; +} + +bool +FHoudiniParameterTranslator::HapiGetParameterHasTag(const HAPI_NodeId& NodeId, const HAPI_ParmId& ParmId, const FString& Tag) +{ + // Does the parameter has the tag we're looking for? + bool HasTag = false; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ParmHasTag( + FHoudiniEngine::Get().GetSession(), NodeId, ParmId, + TCHAR_TO_ANSI(*Tag), &HasTag), false); + + return HasTag; +} + + +bool +FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * HAC ) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniParameterTranslator::UploadChangedParameters); + + if (!HAC || HAC->IsPendingKill()) + return false; + + TMap RampsToRevert; + // First upload all parameters, including the current child parameters/points of ramps, and then process + // the ramp parameters themselves (delete and insert operations of ramp points) + // This is so that the initial upload of parameter values use the correct parameter value/tuple array indices + // (which will change after potential insert/delete operations). Insert operations will upload their new + // parameter values after the insert. + TArray RampsToUpload; + + for (int32 ParmIdx = 0; ParmIdx < HAC->GetNumParameters(); ParmIdx++) + { + UHoudiniParameter*& CurrentParm = HAC->Parameters[ParmIdx]; + if (!CurrentParm || CurrentParm->IsPendingKill() || !CurrentParm->HasChanged()) + continue; + + bool bSuccess = false; + + const EHoudiniParameterType CurrentParmType = CurrentParm->GetParameterType(); + if (CurrentParm->IsPendingRevertToDefault()) + { + bSuccess = RevertParameterToDefault(CurrentParm); + + if (CurrentParmType == EHoudiniParameterType::FloatRamp || + CurrentParmType == EHoudiniParameterType::ColorRamp) + { + RampsToRevert.Add(CurrentParm->GetParameterName(), CurrentParm); + } + } + else + { + if (CurrentParmType == EHoudiniParameterType::FloatRamp || + CurrentParmType == EHoudiniParameterType::ColorRamp) + { + RampsToUpload.Add(CurrentParm); + } + else + { + bSuccess = UploadParameterValue(CurrentParm); + } + } + + + if (bSuccess) + { + CurrentParm->MarkChanged(false); + //CurrentParm->SetNeedsToTriggerUpdate(false); + } + else + { + // Keep this param marked as changed but prevent it from generating updates + CurrentParm->SetNeedsToTriggerUpdate(false); + } + } + + FHoudiniParameterTranslator::RevertRampParameters(RampsToRevert, HAC->GetAssetId()); + + for (UHoudiniParameter* const RampParam : RampsToUpload) + { + if (!IsValid(RampParam)) + continue; + + if (UploadParameterValue(RampParam)) + RampParam->MarkChanged(false); + } + + return true; +} + +bool +FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return false; + + switch (InParam->GetParameterType()) + { + case EHoudiniParameterType::Float: + { + UHoudiniParameterFloat* FloatParam = Cast(InParam); + if (!FloatParam || FloatParam->IsPendingKill()) + { + return false; + } + + float* DataPtr = FloatParam->GetValuesPtr(); + if (!DataPtr) + { + return false; + } + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + FloatParam->GetNodeId(), DataPtr, FloatParam->GetValueIndex(), FloatParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::Int: + { + UHoudiniParameterInt* IntParam = Cast(InParam); + if (!IntParam || IntParam->IsPendingKill()) + { + return false; + } + + int32* DataPtr = IntParam->GetValuesPtr(); + if (!DataPtr) + { + return false; + } + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + IntParam->GetNodeId(), DataPtr, IntParam->GetValueIndex(), IntParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::String: + { + UHoudiniParameterString* StringParam = Cast(InParam); + if (!StringParam || StringParam->IsPendingKill()) + { + return false; + } + + int32 NumValues = StringParam->GetNumberOfValues(); + if (NumValues <= 0) + { + return false; + } + + for (int32 Idx = 0; Idx < NumValues; Idx++) + { + std::string ConvertedString = TCHAR_TO_UTF8(*(StringParam->GetValueAt(Idx))); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + StringParam->GetNodeId(), ConvertedString.c_str(), StringParam->GetParmId(), Idx), false); + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + UHoudiniParameterChoice* ChoiceParam = Cast(InParam); + if (!ChoiceParam || ChoiceParam->IsPendingKill()) + return false; + + // Set the parameter's int value. + const int32 IntValueIndex = ChoiceParam->GetIntValueIndex(); + const int32 IntValue = ChoiceParam->GetIntValue(IntValueIndex); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); + } + break; + case EHoudiniParameterType::StringChoice: + { + UHoudiniParameterChoice* ChoiceParam = Cast(InParam); + if (!ChoiceParam || ChoiceParam->IsPendingKill()) + { + return false; + } + + if (ChoiceParam->IsStringChoice()) + { + // Set the parameter's string value. + std::string ConvertedString = TCHAR_TO_UTF8(*(ChoiceParam->GetStringValue())); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), ConvertedString.c_str(), ChoiceParam->GetParmId(), 0), false); + } + else + { + // Set the parameter's int value. + int32 IntValue = ChoiceParam->GetIntValueIndex(); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ChoiceParam->GetNodeId(), &IntValue, ChoiceParam->GetValueIndex(), ChoiceParam->GetTupleSize()), false); + } + } + break; + + case EHoudiniParameterType::Color: + { + UHoudiniParameterColor* ColorParam = Cast(InParam); + if (!ColorParam || ColorParam->IsPendingKill()) + return false; + + bool bHasAlpha = ColorParam->GetTupleSize() == 4 ? true : false; + FLinearColor Color = ColorParam->GetColorValue(); + + // Set the color value + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + ColorParam->GetNodeId(), + (float*)(&Color.R), ColorParam->GetValueIndex(), bHasAlpha ? 4 : 3), false); + + } + break; + + case EHoudiniParameterType::Button: + { + UHoudiniParameterButton* ButtonParam = Cast(InParam); + if (!ButtonParam) + return false; + + TArray DataArray; + DataArray.Add(1); + + // Set the button parameter value to 1, (setting button param to any value will call the callback function.) + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ButtonParam->GetNodeId(), + DataArray.GetData(), + ButtonParam->GetValueIndex(), 1), false); + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + UHoudiniParameterButtonStrip* ButtonStripParam = Cast(InParam); + if (!ButtonStripParam) + return false; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ButtonStripParam->GetNodeId(), + ButtonStripParam->Values.GetData(), + ButtonStripParam->GetValueIndex(), ButtonStripParam->Count), false); + } + break; + + case EHoudiniParameterType::Toggle: + { + UHoudiniParameterToggle* ToggleParam = Cast(InParam); + if (!ToggleParam) + return false; + + // Set the toggle parameter values. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + ToggleParam->GetNodeId(), + ToggleParam->GetValuesPtr(), + ToggleParam->GetValueIndex(), ToggleParam->GetTupleSize()), false); + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + UHoudiniParameterFile* FileParam = Cast(InParam); + + if (!UploadDirectoryPath(FileParam)) + return false; + } + break; + + case EHoudiniParameterType::MultiParm: + { + if (!UploadMultiParmValues(InParam)) + return false; + } + + break; + + case EHoudiniParameterType::FloatRamp: + { + if (!UploadRampParameter(InParam)) + return false; + } + break; + + case EHoudiniParameterType::ColorRamp: + { + if (!UploadRampParameter(InParam)) + return false; + } + break; + + default: + { + // TODO: implement other parameter types! + return false; + } + break; + } + + // The parameter is no longer considered as changed + InParam->MarkChanged(false); + + return true; +} + +bool +FHoudiniParameterTranslator::RevertParameterToDefault(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return false; + + if (!InParam->IsPendingRevertToDefault()) + return false; + + TArray TupleToRevert; + InParam->GetTuplePendingRevertToDefault(TupleToRevert); + if (TupleToRevert.Num() <= 0) + return false; + + FString ParameterName = InParam->GetParameterName(); + + bool bReverted = true; + for (auto CurrentIdx : TupleToRevert ) + { + if (!TupleToRevert.IsValidIndex(CurrentIdx)) + { + // revert the whole parameter to its default value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefaults( + FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName))) + { + HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s to its default value."), *ParameterName); + bReverted = false; + } + } + else + { + // revert a tuple to its default value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RevertParmToDefault( + FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), TCHAR_TO_UTF8(*ParameterName), CurrentIdx)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to revert parameter %s - %d to its default value."), *ParameterName, CurrentIdx); + bReverted = false; + } + } + } + + if (!bReverted) + return false; + + // The parameter no longer needs to be reverted + InParam->MarkDefault(true); + + return true; +} + +EHoudiniFolderParameterType +FHoudiniParameterTranslator::GetFolderTypeFromParamInfo(const HAPI_ParmInfo* ParamInfo) +{ + EHoudiniFolderParameterType Type = EHoudiniFolderParameterType::Invalid; + + switch (ParamInfo->scriptType) + { + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPSIMPLE: + Type = EHoudiniFolderParameterType::Simple; + break; + + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPCOLLAPSIBLE: + Type = EHoudiniFolderParameterType::Collapsible; + break; + + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUP: + Type = EHoudiniFolderParameterType::Tabs; + break; + + // Treat Radio folders as tabs for now + case HAPI_PrmScriptType::HAPI_PRM_SCRIPT_TYPE_GROUPRADIO: + Type = EHoudiniFolderParameterType::Radio; + break; + + default: + Type = EHoudiniFolderParameterType::Other; + break; + + } + + return Type; + +} + +bool +FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad( + UHoudiniParameter* InParam, TArray& OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo) +{ + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + if (!MultiParam || MultiParam->IsPendingKill()) + return false; + + UHoudiniParameterRampFloat* FloatRampParameter = nullptr; + UHoudiniParameterRampColor* ColorRampParameter = nullptr; + + if (MultiParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + FloatRampParameter = Cast(MultiParam); + else if (MultiParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + ColorRampParameter = Cast(MultiParam); + + HAPI_NodeId NodeId = AssetInfo.nodeId; + + int32 Idx = 0; + int32 InstanceCount = -1; + HAPI_ParmId ParmId = -1; + TArray ParmInfos; + if (!GetMultiParmInstanceStartIdx(AssetInfo, MultiParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + const int32 InstanceCountInUnreal = FMath::Max(MultiParam->GetInstanceCount(), 0); + if (InstanceCount > InstanceCountInUnreal) + { + // The multiparm has more instances on the Houdini side, remove instances from the end until it has the same + // number as in Unreal. + // NOTE: Initially this code always removed the first instance. But that causes an issue if HAPI/Houdini does + // not immediately update the parameter names (param1, param2, param3, when param1 is removed, 2 -> 1, + // 3 -> 2) so that could result in GetParameters returning parameters with unique IDs, but where the names + // are not up to date, so in the above example, the last param could still be named param3 when it should + // be named param2. + const int32 Delta = InstanceCount - InstanceCountInUnreal; + for (int32 n = 0; n < Delta; ++n) + { + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset + InstanceCount - 1 - n); + } + } + else if (InstanceCountInUnreal > InstanceCount) + { + // The multiparm has fewer instances on the Houdini side, add instances at the end until it has the same + // number as in Unreal. + // NOTE: Initially this code always inserted before the first instance. But that causes an issue if HAPI/Houdini + // does not immediately update the parameter names (param1, param2, param3, when a param is inserted + // before 1, then 1->2, 2->3, 3->4 so that could result in GetParameters returning parameters with unique + // IDs, but where the names are not up to date, so in the above example, the now second param could still + // be named param1 when it should be named param2. + const int32 Delta = InstanceCountInUnreal - InstanceCount; + for (int32 n = 0; n < Delta; ++n) + { + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), NodeId, + ParmId, MultiParam->InstanceStartOffset + InstanceCount + n); + } + } + + // We are going to Sync nested multi-params recursively + int32 MyParmId = InParam->GetParmId(); + // First, we need to manually look for our index in the old map + // Since there is a possibility that the parameter interface has changed since our previous cook + int32 MyIndex = -1; + for (int32 ParamIdx = 0; ParamIdx < OldParams.Num(); ParamIdx++) + { + UHoudiniParameter* CurrentOldParm = OldParams[ParamIdx]; + if (!IsValid(CurrentOldParm)) + continue; + + if (CurrentOldParm->GetParmId() != MyParmId) + continue; + + // We found ourself, exit now + MyIndex = ParamIdx; + break; + } + + if (MyIndex >= 0) + { + // Now Sync nested multi-params recursively + for (int32 ParamIdx = MyIndex + 1; ParamIdx < OldParams.Num(); ParamIdx++) + { + UHoudiniParameter* NextParm = OldParams[ParamIdx]; + if (!IsValid(NextParm)) + continue; + + if (NextParm->GetParentParmId() != MyParmId) + continue; + + if (NextParm->GetParameterType() != EHoudiniParameterType::MultiParm) + continue; + + // Always make sure to NOT recurse on ourselves! + // This could happen if parms have been deleted... + if (NextParm->GetParmId() == MyParmId) + continue; + + SyncMultiParmValuesAtLoad(NextParm, OldParams, InAssetId, AssetInfo); + } + } + + // The multiparm is a ramp, Get the param infos again, since the number of param instances is changed + if (!GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + // Step 3: Set values of the inserted points + if (FloatRampParameter) + { + for (auto & Point : FloatRampParameter->Points) + { + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); + + // 2: update float value at param Idx + 1 + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Value), ParmInfos[Idx + 1].floatValuesIndex, 1); + + // 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Point->Interpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + else if (ColorRampParameter) + { + for (auto& Point : ColorRampParameter->Points) + { + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &(Point->Position), ParmInfos[Idx].floatValuesIndex, 1); + + // 2: update color value at param Idx + 1 + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeId, (float*)(&Point->Value.R), ParmInfos[Idx + 1].floatValuesIndex, 3); + + // 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Point->Interpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + + return true; +} + + +bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam) +{ + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + if (!MultiParam || MultiParam->IsPendingKill()) + return false; + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InParam->GetOuter()); + if (!HoudiniAssetComponent) + return false; + + int32 InsertIndexStart = -1; + UHoudiniParameterRampFloat* RampFloatParam = Cast(InParam); + UHoudiniParameterRampColor* RampColorParam = Cast(InParam); + + TArray *Events = nullptr; + if (RampFloatParam) + { + Events = &(RampFloatParam->ModificationEvents); + InsertIndexStart = RampFloatParam->GetInstanceCount(); + } + else if (RampColorParam) + { + Events = &(RampColorParam->ModificationEvents); + InsertIndexStart = RampColorParam->GetInstanceCount(); + } + else + return false; + + // Handle All Events + Events->Sort([](const UHoudiniParameterRampModificationEvent& A, const UHoudiniParameterRampModificationEvent& B) + { + return A.DeleteInstanceIndex > B.DeleteInstanceIndex; + }); + + + // Step 1: Handle all delete events first + for (auto& Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsDeleteEvent()) + continue; + + FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Event->DeleteInstanceIndex + MultiParam->InstanceStartOffset); + + InsertIndexStart -= 1; + } + + int32 InsertIndex = InsertIndexStart; + + + // Step 2: Handle all insert events + for (auto& Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsInsertEvent()) + continue; + + FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), InsertIndex + MultiParam->InstanceStartOffset); + + InsertIndex += 1; + } + + // Step 3: Set inserted parameter values (only if there are instances inserted) + if (InsertIndex > InsertIndexStart) + { + if (HoudiniAssetComponent) + { + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), HoudiniAssetComponent->AssetId, &AssetInfo), false); + + int32 Idx = 0; + int32 InstanceCount = -1; + HAPI_ParmId ParmId = -1; + TArray ParmInfos; + if (!FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(AssetInfo, InParam->GetParameterName(), + Idx, InstanceCount, ParmId, ParmInfos)) + return false; + + if (InstanceCount < 0) + return false; + + // Instance count doesn't match, + if (InsertIndex != InstanceCount) + return false; + + // Starting index of parameters which we just inserted + Idx += 3 * InsertIndexStart; + + if (!ParmInfos.IsValidIndex(Idx + 2)) + return false; + + for (auto & Event : *Events) + { + if (!Event) + continue; + + if (!Event->IsInsertEvent()) + continue; + + // 1: update position float at param Idx + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &(Event->InsertPosition), ParmInfos[Idx].floatValuesIndex, 1); + + // step 2: update value at param Idx + 1 + if (Event->IsFloatRampEvent()) + { + // float value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &(Event->InsertFloat), ParmInfos[Idx + 1].floatValuesIndex, 1); + } + else + { + // color value + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, (float*)(&Event->InsertColor.R), ParmInfos[Idx + 1].floatValuesIndex, 3); + } + + // step 3: update interpolation type at param Idx + 2 + int32 IntValue = (int32)(Event->InsertInterpolation); + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + AssetInfo.nodeId, &IntValue, ParmInfos[Idx + 2].intValuesIndex, 1); + + Idx += 3; + } + } + } + + // Step 4: clear all events + Events->Empty(); + + return true; +} + +bool FHoudiniParameterTranslator::UploadMultiParmValues(UHoudiniParameter* InParam) +{ + UHoudiniParameterMultiParm* MultiParam = Cast(InParam); + if (!MultiParam) + return false; + + TArray &LastModificationArray = MultiParam->MultiParmInstanceLastModifyArray; + + int32 Size = MultiParam->MultiParmInstanceLastModifyArray.Num(); + + for (int32 Index = 0; Index < Size; ++Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Inserted) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::InsertMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) + return false; + + } + } + + for (int32 Index = Size - 1; Index >= 0; --Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::RemoveMultiparmInstance( + FHoudiniEngine::Get().GetSession(), MultiParam->GetNodeId(), + MultiParam->GetParmId(), Index + MultiParam->InstanceStartOffset)) + return false; + } + } + + // Remove all removal events. + for (int32 Index = Size - 1; Index >= 0; --Index) + { + if (LastModificationArray[Index] == EHoudiniMultiParmModificationType::Removed) + LastModificationArray.RemoveAt(Index); + } + + // The last modification array is resized. + Size = LastModificationArray.Num(); + + // Reset the last modification array + for (int32 Itr =Size - 1; Itr >= 0; --Itr) + { + LastModificationArray[Itr] = EHoudiniMultiParmModificationType::None; + } + + MultiParam->MultiParmInstanceCount = Size; + + return true; +} + +bool +FHoudiniParameterTranslator::UploadDirectoryPath(UHoudiniParameterFile* InParam) +{ + if(!InParam) + return false; + + for (int32 Index = 0; Index < InParam->GetNumValues(); ++Index) + { + std::string ConvertedString = TCHAR_TO_UTF8(*(InParam->GetValueAt(Index))); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue(FHoudiniEngine::Get().GetSession(), + InParam->GetNodeId(), ConvertedString.c_str(), InParam->GetParmId(), Index), false); + } + + return true; +} + +bool +FHoudiniParameterTranslator::GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, + int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos) +{ + // TODO: FIX/IMPROVE THIS! + // This is bad, that function can be called recursively, fetches all parameters, + // iterates on them, and fetches their name!! WTF! + // TODO: Slightly better now, at least we dont fetch every parameter's name! + + // Reset outputs + OutStartIdx = -1; + OutInstanceCount = -1; + OutParmId = -1; + OutParmInfos.Empty(); + + // Try to find the parameter by its name + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, TCHAR_TO_UTF8(*InParmName), &OutParmId), false); + + if (OutParmId < 0) + return false; + + // Get the asset's node info + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &NodeInfo), false); + + // Get all parameters + OutParmInfos.SetNumUninitialized(NodeInfo.parmCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), InAssetInfo.nodeId, &OutParmInfos[0], 0, NodeInfo.parmCount), false); + + OutStartIdx = 0; + for (const auto& CurrentParmInfo : OutParmInfos) + { + if (OutParmId == CurrentParmInfo.id) + { + OutInstanceCount = OutParmInfos[OutStartIdx].instanceCount; + + // Increment, to get the Start index of the ramp children parameters + OutStartIdx++; + return true; + } + + OutStartIdx++; + } + + // We failed to find the parm + OutStartIdx = -1; + + return false; +} + +bool +FHoudiniParameterTranslator::RevertRampParameters(TMap & InRampParams, const int32 & AssetId) +{ + if (InRampParams.Num() <= 0) + return true; + + // Get the asset's info + HAPI_AssetInfo AssetInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // .. the asset's node info + HAPI_NodeInfo NodeInfo; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &NodeInfo), false); + + // Retrieve all the parameter infos. + TArray< HAPI_ParmInfo > ParmInfos; + ParmInfos.SetNumUninitialized(NodeInfo.parmCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParameters( + FHoudiniEngine::Get().GetSession(), AssetInfo.nodeId, &ParmInfos[0], 0, NodeInfo.parmCount), false); + + int32 ParamIdx = 0; + while (ParamIdx < ParmInfos.Num()) + { + const HAPI_ParmInfo & ParmInfo = ParmInfos[ParamIdx]; + FString ParmName; + FHoudiniEngineString(ParmInfo.nameSH).ToFString(ParmName); + + if (InRampParams.Contains(ParmName)) + { + if (!InRampParams[ParmName]) + { + ParamIdx += 1; + continue; + } + + if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat * FloatRamp = Cast(InRampParams[ParmName]); + if (!FloatRamp) + { + ParamIdx += 1; + continue; + } + + if (ParmInfo.instanceCount != FloatRamp->NumDefaultPoints) + { + ParamIdx += 1; + continue; + } + + for (int32 PtIdx = 0; PtIdx < FloatRamp->NumDefaultPoints; ++PtIdx) + { + const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; + const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; + const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultValues.GetData() + PtIdx, ValueParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, FloatRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); + + ParamIdx += 3; + } + } + + if (InRampParams[ParmName]->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor * ColorRamp = Cast(InRampParams[ParmName]); + if (!ColorRamp) + { + ParamIdx += 1; + continue; + } + + if (ParmInfo.instanceCount != ColorRamp->NumDefaultPoints) + { + ParamIdx += 1; + continue; + } + + for (int32 PtIdx = 0; PtIdx < ColorRamp->NumDefaultPoints; ++PtIdx) + { + const HAPI_ParmInfo & PositionParmInfo = ParmInfos[ParamIdx + 1]; + const HAPI_ParmInfo & ValueParmInfo = ParmInfos[ParamIdx + 2]; + const HAPI_ParmInfo & InterpolationParmInfo = ParmInfos[ParamIdx + 3]; + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, ColorRamp->DefaultPositions.GetData() + PtIdx, PositionParmInfo.floatValuesIndex, 1); + + FHoudiniApi::SetParmFloatValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, (float*)(&ColorRamp->DefaultValues[PtIdx].R), ValueParmInfo.floatValuesIndex, 3); + + FHoudiniApi::SetParmIntValues( + FHoudiniEngine::Get().GetSession(), + NodeInfo.id, ColorRamp->DefaultChoices.GetData() + PtIdx, InterpolationParmInfo.intValuesIndex, 1); + + ParamIdx += 3; + } + } + } + + ParamIdx += 1; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h index aadbf85f7..539ce7021 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.h @@ -1,158 +1,158 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -class UHoudiniAsset; -class UHoudiniAssetComponent; -class UHoudiniParameter; -class UHoudiniParameterFile; - -enum class EHoudiniFolderParameterType : uint8; -enum class EHoudiniParameterType : uint8; - -struct HOUDINIENGINE_API FHoudiniParameterTranslator -{ - // - static bool UpdateParameters(UHoudiniAssetComponent* HAC); - - static bool OnPreCookParameters(UHoudiniAssetComponent* HAC); - - // - static bool UpdateLoadedParameters(UHoudiniAssetComponent* HAC); - - // - static bool UploadChangedParameters(UHoudiniAssetComponent* HAC); - - // - static bool UploadParameterValue(UHoudiniParameter* InParam); - - // - static bool UploadMultiParmValues(UHoudiniParameter* InParam); - - // - static bool UploadRampParameter(UHoudiniParameter* InParam); - - // - static bool UploadDirectoryPath(UHoudiniParameterFile* InParam); - - // - static bool RevertParameterToDefault(UHoudiniParameter* InParam); - - // - static bool SyncMultiParmValuesAtLoad( - UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo); - - // - static bool GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, - int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos); - - /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. - @AssetId: Id of the digital asset - @PrimaryObject: Object to use for transactions and as Outer for new top-level parameters - @CurrentParameters: pre: current & post: invalid parameters - @NewParameters: new params added to this - - On Return: CurrentParameters are the old parameters that are no longer valid, - NewParameters are new and re-used parameters. - */ - static bool BuildAllParameters( - const HAPI_NodeId& AssetId, - class UObject* OuterObject, - TArray& CurrentParameters, - TArray& NewParameters, - const bool& bUpdateValues, - const bool& InForceFullUpdate, - const UHoudiniAsset* InHoudiniAsset, - const FString& InHoudiniAssetName); - - // Parameter creation - static UHoudiniParameter * CreateTypedParameter( - class UObject * Outer, - const EHoudiniParameterType& ParmType, - const FString& ParmName ); - - // Parameter update - // bFullUpdate should be set to false after a minor update (change/recook) of the parameter - // and set to true when creating a new parameter - // bUpdateValue should be set to false when updating loaded parameters - // as the internal parameter's value from HAPI - static bool UpdateParameterFromInfo( - UHoudiniParameter * HoudiniParameter, - const HAPI_NodeId& InNodeId, - const HAPI_ParmInfo& ParmInfo, - const bool& bFullUpdate = true, - const bool& bUpdateValue = true, - const TArray* DefaultIntValues = nullptr, - const TArray* DefaultFloatValues = nullptr, - const TArray* DefaultStringValues = nullptr, - const TArray* DefaultChoiceValues = nullptr); - - static UClass* GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo); - - static void GetParmTypeFromParmInfo( - const HAPI_ParmInfo& ParmInfo, - EHoudiniParameterType& ParmType); - - static bool CheckParameterTypeAndClassMatch( - UHoudiniParameter* Parameter, - const EHoudiniParameterType& ParmType); - - /* - static bool CheckParameterClassAndInfoMatch( - UHoudiniParameter* Parameter, - const HAPI_ParmInfo& ParmInfo ); - */ - - // HAPI: Get a parameter's tag value. - static bool HapiGetParameterTagValue( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - const FString& Tag, - FString& TagValue); - - // HAPI: Get a parameter's unit. - static bool HapiGetParameterUnit( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - FString& OutUnitString ); - - // HAPI: Indicates if a parameter has a given tag - static bool HapiGetParameterHasTag( - const HAPI_NodeId& NodeId, - const HAPI_ParmId& ParmId, - const FString& Tag); - - // Get folder parameter type from HAPI_ParmInfo struct - static EHoudiniFolderParameterType GetFolderTypeFromParamInfo( - const HAPI_ParmInfo* ParamInfo); - - static bool RevertRampParameters(TMap & InRampParams, const int32 & AssetId); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +class UHoudiniAsset; +class UHoudiniAssetComponent; +class UHoudiniParameter; +class UHoudiniParameterFile; + +enum class EHoudiniFolderParameterType : uint8; +enum class EHoudiniParameterType : uint8; + +struct HOUDINIENGINE_API FHoudiniParameterTranslator +{ + // + static bool UpdateParameters(UHoudiniAssetComponent* HAC); + + static bool OnPreCookParameters(UHoudiniAssetComponent* HAC); + + // + static bool UpdateLoadedParameters(UHoudiniAssetComponent* HAC); + + // + static bool UploadChangedParameters(UHoudiniAssetComponent* HAC); + + // + static bool UploadParameterValue(UHoudiniParameter* InParam); + + // + static bool UploadMultiParmValues(UHoudiniParameter* InParam); + + // + static bool UploadRampParameter(UHoudiniParameter* InParam); + + // + static bool UploadDirectoryPath(UHoudiniParameterFile* InParam); + + // + static bool RevertParameterToDefault(UHoudiniParameter* InParam); + + // + static bool SyncMultiParmValuesAtLoad( + UHoudiniParameter* MultiParam, TArray &OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo); + + // + static bool GetMultiParmInstanceStartIdx(const HAPI_AssetInfo& InAssetInfo, const FString InParmName, + int32& OutStartIdx, int32& OutInstanceCount, HAPI_ParmId& OutParmId, TArray &OutParmInfos); + + /** Update parameters from the asset, re-uses parameters passed into CurrentParameters. + @AssetId: Id of the digital asset + @PrimaryObject: Object to use for transactions and as Outer for new top-level parameters + @CurrentParameters: pre: current & post: invalid parameters + @NewParameters: new params added to this + + On Return: CurrentParameters are the old parameters that are no longer valid, + NewParameters are new and re-used parameters. + */ + static bool BuildAllParameters( + const HAPI_NodeId& AssetId, + class UObject* OuterObject, + TArray& CurrentParameters, + TArray& NewParameters, + const bool& bUpdateValues, + const bool& InForceFullUpdate, + const UHoudiniAsset* InHoudiniAsset, + const FString& InHoudiniAssetName); + + // Parameter creation + static UHoudiniParameter * CreateTypedParameter( + class UObject * Outer, + const EHoudiniParameterType& ParmType, + const FString& ParmName ); + + // Parameter update + // bFullUpdate should be set to false after a minor update (change/recook) of the parameter + // and set to true when creating a new parameter + // bUpdateValue should be set to false when updating loaded parameters + // as the internal parameter's value from HAPI + static bool UpdateParameterFromInfo( + UHoudiniParameter * HoudiniParameter, + const HAPI_NodeId& InNodeId, + const HAPI_ParmInfo& ParmInfo, + const bool& bFullUpdate = true, + const bool& bUpdateValue = true, + const TArray* DefaultIntValues = nullptr, + const TArray* DefaultFloatValues = nullptr, + const TArray* DefaultStringValues = nullptr, + const TArray* DefaultChoiceValues = nullptr); + + static UClass* GetDesiredParameterClass(const HAPI_ParmInfo& ParmInfo); + + static void GetParmTypeFromParmInfo( + const HAPI_ParmInfo& ParmInfo, + EHoudiniParameterType& ParmType); + + static bool CheckParameterTypeAndClassMatch( + UHoudiniParameter* Parameter, + const EHoudiniParameterType& ParmType); + + /* + static bool CheckParameterClassAndInfoMatch( + UHoudiniParameter* Parameter, + const HAPI_ParmInfo& ParmInfo ); + */ + + // HAPI: Get a parameter's tag value. + static bool HapiGetParameterTagValue( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + const FString& Tag, + FString& TagValue); + + // HAPI: Get a parameter's unit. + static bool HapiGetParameterUnit( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + FString& OutUnitString ); + + // HAPI: Indicates if a parameter has a given tag + static bool HapiGetParameterHasTag( + const HAPI_NodeId& NodeId, + const HAPI_ParmId& ParmId, + const FString& Tag); + + // Get folder parameter type from HAPI_ParmInfo struct + static EHoudiniFolderParameterType GetFolderTypeFromParamInfo( + const HAPI_ParmInfo* ParamInfo); + + static bool RevertRampParameters(TMap & InRampParams, const int32 & AssetId); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp index 9b7f01f20..c5dca7692 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp @@ -1,1714 +1,1720 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineTranslator.h" - -#include "HoudiniApi.h" -#include "HoudiniEngine.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniGeoPartObject.h" -#include "Components/SplineComponent.h" - -#include "EditorViewportClient.h" -#include "Engine/Selection.h" - -#include "HoudiniEnginePrivatePCH.h" - -void -FHoudiniSplineTranslator::ExtractStringPositions(const FString& Positions, TArray& OutPositions) -{ - TArray< FString > PointStrings; - static const TCHAR * PositionSeparators[] = - { - TEXT(" "), - TEXT(","), - }; - - int32 NumCoords = Positions.ParseIntoArray(PointStrings, PositionSeparators, 2); - OutPositions.SetNum(NumCoords / 3); - for (int32 OutIndex = 0; OutIndex < OutPositions.Num(); OutIndex++) - { - const int32& CoordIndex = OutIndex * 3; - OutPositions[OutIndex].X = FCString::Atof(*(PointStrings[CoordIndex + 0])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutPositions[OutIndex].Y = FCString::Atof(*(PointStrings[CoordIndex + 2])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutPositions[OutIndex].Z = FCString::Atof(*(PointStrings[CoordIndex + 1])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } -} - -void -FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData) -{ - OutVectorData.SetNum(InRawData.Num() / 3); - - for (int32 OutIndex = 0; OutIndex < OutVectorData.Num(); OutIndex++) - { - const int32& InIndex = OutIndex * 3; - OutVectorData[OutIndex].X = InRawData[InIndex + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutVectorData[OutIndex].Y = InRawData[InIndex + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - OutVectorData[OutIndex].Z = InRawData[InIndex + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - } -} - -void -FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts) -{ - OutVectorData.SetNum(CurveCounts.Num()); - - int32 TotalNumPoints = 0; - for (const int32 & NextCount : CurveCounts) - TotalNumPoints += NextCount; - - // Do not fill the output array, if the total number of points does not match - if (InRawData.Num() < TotalNumPoints * 3) - return; - - - int32 Itr = 0; - - for (int32 n = 0; n < CurveCounts.Num(); ++n) - { - TArray & NextVectorDataArray = OutVectorData[n]; - NextVectorDataArray.SetNumZeroed(CurveCounts[n]); - - for (int32 PtIdx = 0; PtIdx < CurveCounts[n]; ++PtIdx) - { - if (Itr + 2 >= InRawData.Num()) - return; - - NextVectorDataArray[PtIdx].X = InRawData[Itr] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - NextVectorDataArray[PtIdx].Y = InRawData[Itr + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - NextVectorDataArray[PtIdx].Z = InRawData[Itr + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; - - Itr += 3; - } - } -} -void -FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC) -{ - for (UHoudiniInput * NextInput : HAC->Inputs) - UpdateHoudiniInputCurves(NextInput); -} - -void -FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniInput* Input) -{ - if (!Input || Input->GetInputType() != EHoudiniInputType::Curve) - return; - - TArray *InputObjectArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!InputObjectArray) - return; - - for (UHoudiniInputObject * NextInputObject : *InputObjectArray) - { - UHoudiniInputHoudiniSplineComponent * HoudiniSplineInput = Cast(NextInputObject); - if (!HoudiniSplineInput) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInput->GetCurveComponent(); - FHoudiniSplineTranslator::UpdateHoudiniCurve(HoudiniSplineComponent); - } -} - -bool -FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent* HoudiniSplineComponent) -{ - if (!IsValid(HoudiniSplineComponent)) - return false; - - int32 CurveNode_id = HoudiniSplineComponent->GetNodeId(); - if (CurveNode_id < 0) - return false; - - FString CurvePointsString = FString(); - if (!FHoudiniEngineUtils::HapiGetParameterDataAsString( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString)) - { - return false; - } - - int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; - if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue)) - { - return false; - } - HoudiniSplineComponent->SetCurveType((EHoudiniCurveType)CurveTypeValue); - - int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; - if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue)) - { - return false; - } - HoudiniSplineComponent->SetCurveMethod((EHoudiniCurveMethod)CurveMethodValue); - - int32 CurveClosed = 0; - if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) - { - return false; - } - HoudiniSplineComponent->SetClosedCurve(CurveClosed == 1); - - int32 CurveReversed = 0; - if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed)) - { - return false; - } - HoudiniSplineComponent->SetReversed(CurveReversed == 1); - - // We need to get the NodeInfo to get the parent id - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), CurveNode_id, &NodeInfo), false); - - TArray RefinedCurvePositions; - HAPI_AttributeInfo AttributeRefinedCurvePositions; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions)) - { - return false; - } - - // Process coords string and extract positions. - TArray CurvePoints; - FHoudiniSplineTranslator::ExtractStringPositions(CurvePointsString, CurvePoints); - - TArray CurveDisplayPoints; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurveDisplayPoints); - - // Build curve points for editable curves. - if (HoudiniSplineComponent->CurvePoints.Num() != CurvePoints.Num()) - { - HoudiniSplineComponent->CurvePoints.SetNum(CurvePoints.Num()); - for(int32 Idx = 0; Idx < CurvePoints.Num(); Idx++) - { - FTransform Transform = FTransform::Identity; - Transform.SetLocation(CurvePoints[Idx]); - HoudiniSplineComponent->CurvePoints[Idx] = Transform; - } - } - - // Update the display point on the curve - HoudiniSplineComponent->Construct(CurveDisplayPoints); - - HoudiniSplineComponent->MarkChanged(false); - - return true; -} - - -bool -FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( - UHoudiniSplineComponent* HoudiniSplineComponent, - bool bInAddRotAndScaleAttributes) -{ - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return true; - - TArray PositionArray; - TArray RotationArray; - TArray Scales3dArray; - for (FTransform& CurrentTransform : HoudiniSplineComponent->CurvePoints) - { - PositionArray.Add(CurrentTransform.GetLocation()); - if (bInAddRotAndScaleAttributes) - { - RotationArray.Add(CurrentTransform.GetRotation()); - Scales3dArray.Add(CurrentTransform.GetScale3D()); - } - } - - HAPI_NodeId CurveNode_id = HoudiniSplineComponent->GetNodeId(); - FTransform ParentTransform = HoudiniSplineComponent->GetComponentTransform(); - - FString InputNodeNameString = HoudiniSplineComponent->GetName(); - UHoudiniInputHoudiniSplineComponent* InputObject = Cast(HoudiniSplineComponent->GetOuter()); - if (InputObject) - { - UHoudiniInput* Input = Cast(InputObject->GetOuter()); - if (Input) - { - InputNodeNameString = Input->GetNodeBaseName(); - } - } - InputNodeNameString += TEXT("_curve"); - - bool Success = FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( - CurveNode_id, - InputNodeNameString, - &PositionArray, - bInAddRotAndScaleAttributes ? &RotationArray : nullptr, - bInAddRotAndScaleAttributes ? &Scales3dArray : nullptr, - HoudiniSplineComponent->GetCurveType(), - HoudiniSplineComponent->GetCurveMethod(), - HoudiniSplineComponent->IsClosedCurve(), - HoudiniSplineComponent->IsReversed(), - false, - ParentTransform); - - HoudiniSplineComponent->SetNodeId(CurveNode_id); - Success &= UpdateHoudiniCurve(HoudiniSplineComponent); - - return Success; -} - -bool -FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( - HAPI_NodeId& CurveNodeId, - const FString& InputNodeName, - TArray* Positions, - TArray* Rotations, - TArray* Scales3d, - EHoudiniCurveType InCurveType, - EHoudiniCurveMethod InCurveMethod, - const bool& InClosed, - const bool& InReversed, - const bool& InForceClose, - const FTransform& ParentTransform ) -{ -#if WITH_EDITOR - // Positions are required - if (!Positions) - return false; - - // We also need a valid host asset and 2 points to make a curve - int32 NumberOfCVs = Positions->Num(); - if (NumberOfCVs < 2) - return false; - - // Check if connected asset id is valid, if it is not, we need to create an input asset. - if (CurveNodeId < 0) - { - HAPI_NodeId NodeId = -1; - // Create the curve SOP Node - if (!FHoudiniSplineTranslator::HapiCreateCurveInputNode(NodeId, InputNodeName)) - return false; - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NodeId)) - return false; - - // We now have a valid id. - CurveNodeId = NodeId; - } - else - { - // We have to revert the Geo to its original state so we can use the Curve SOP: - // adding parameters to the Curve SOP locked it, preventing its parameters (type, method, isClosed) from working - FHoudiniApi::RevertGeo(FHoudiniEngine::Get().GetSession(), CurveNodeId); - } - - // - // In order to be able to add rotations and scale attributes to the curve SOP, we need to cook it twice: - // - // - First, we send the positions string to it, and cook it without refinement. - // this will allow us to get the proper curve CVs, part attributes and curve info to create the desired curve. - // - // - We then need to send back all the info extracted from the curve SOP to it, and add the rotation - // and scale attributes to it. This will lock the curve SOP, and prevent the curve type and method - // parameters from functioning properly (hence why we needed the first cook to set that up) - // - - // Set the curve type and curve method parameters for the curve node - int32 CurveTypeValue = (int32)InCurveType; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - - int32 CurveMethodValue = (int32)InCurveMethod; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - - int32 CurveClosed = InClosed ? 1 : 0; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - - int32 CurveReversed = InReversed ? 1 : 0; - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); - - // Reading the curve parameters - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); - FHoudiniEngineUtils::HapiGetParameterDataAsInteger( - CurveNodeId, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); - - if (InForceClose) - { - // We need to update the closed parameter - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); - - CurveClosed = 1; - } - - // For closed NURBS (CVs and Breakpoints), we have to close the curve manually, by duplicating its last point - // in order to be able to set the rotations and scales attributes properly. - bool bCloseCurveManually = false; - if (CurveClosed && (CurveTypeValue == HAPI_CURVETYPE_NURBS) && (CurveMethodValue != 2)) - { - // The curve is not closed anymore - if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 0)) - { - bCloseCurveManually = true; - - // Duplicating the first point to the end of the curve - // This needs to be done before sending the position string - FVector pos = (*Positions)[0]; - Positions->Add(pos); - - CurveClosed = false; - } - } - - // Creating the position string - FString PositionString = TEXT(""); - FHoudiniSplineTranslator::CreatePositionsString(*Positions, PositionString); - - // Get param id for the PositionString and modify it - HAPI_ParmId ParmId = -1; - if (FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId) != HAPI_RESULT_SUCCESS) - { - return false; - } - - std::string ConvertedString = TCHAR_TO_UTF8(*PositionString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - ConvertedString.c_str(), ParmId, 0), false); - - // If we don't want to add rotations or scale attributes to the curve, - // we can just cook the node normally and stop here. - bool bAddRotations = (Rotations != nullptr); - bool bAddScales3d = (Scales3d != nullptr); - if (!bAddRotations && !bAddScales3d) - { - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); - */ - - // Cook the node, no need to wait for completion - return FHoudiniEngineUtils::HapiCookNode(CurveNodeId, nullptr, false); - } - - // Setting up the first cook, without the curve refinement - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - CookOptions.maxVerticesPerPrimitive = -1; - CookOptions.refineCurveToLinear = false; - if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, true)) - return false; - - // We can now read back the Part infos from the cooked curve. - HAPI_PartInfo PartInfos; - FHoudiniApi::PartInfo_Init(&PartInfos); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), CurveNodeId, 0, &PartInfos), false); - - // - // Depending on the curve type and method, additionnal control points might have been created. - // We now have to interpolate the rotations and scale attributes for these. - // - - // Lambda function that interpolates rotation, scale and uniform scales values - // between two points using fCoeff as a weight, and insert the interpolated value at nInsertIndex - auto InterpolateRotScaleUScale = [&](const int32& nIndex1, const int32& nIndex2, const float& fCoeff, const int32& nInsertIndex) - { - if (Rotations && Rotations->IsValidIndex(nIndex1) && Rotations->IsValidIndex(nIndex2)) - { - FQuat interpolation = FQuat::Slerp((*Rotations)[nIndex1], (*Rotations)[nIndex2], fCoeff); - if (Rotations->IsValidIndex(nInsertIndex)) - Rotations->Insert(interpolation, nInsertIndex); - else - Rotations->Add(interpolation); - } - - if (Scales3d && Scales3d->IsValidIndex(nIndex1) && Scales3d->IsValidIndex(nIndex2)) - { - FVector interpolation = fCoeff * (*Scales3d)[nIndex1] + (1.0f - fCoeff) * (*Scales3d)[nIndex2]; - if (Scales3d->IsValidIndex(nInsertIndex)) - Scales3d->Insert(interpolation, nInsertIndex); - else - Scales3d->Add(interpolation); - } - }; - - // Lambda function that duplicates rotation and scale values - // at nIndex and insert/adds it at nInsertIndex - auto DuplicateRotScale = [&](const int32& nIndex, const int32& nInsertIndex) - { - if (Rotations && Rotations->IsValidIndex(nIndex)) - { - FQuat value = (*Rotations)[nIndex]; - if (Rotations->IsValidIndex(nInsertIndex)) - Rotations->Insert(value, nInsertIndex); - else - Rotations->Add(value); - } - - if (Scales3d && Scales3d->IsValidIndex(nIndex)) - { - FVector value = (*Scales3d)[nIndex]; - if (Scales3d->IsValidIndex(nInsertIndex)) - Scales3d->Insert(value, nInsertIndex); - else - Scales3d->Add(value); - } - }; - - // Do we want to close the curve by ourselves? - if (bCloseCurveManually) - { - // We need to duplicate the info of the first point to the last - DuplicateRotScale(0, NumberOfCVs++); - - // We need to update the closed parameter - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CurveNodeId, - HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); - } - - // INTERPOLATION - if (CurveTypeValue == HAPI_CURVETYPE_NURBS) - { - // Closed NURBS have additional points reproducing the first ones - if (InClosed) - { - // Only the first one if the method is freehand ... - DuplicateRotScale(0, NumberOfCVs++); - - if (CurveMethodValue != 2) - { - // ... but also the 2nd and 3rd if the method is CVs or Breakpoints. - DuplicateRotScale(1, NumberOfCVs++); - DuplicateRotScale(2, NumberOfCVs++); - } - } - else if (CurveMethodValue == 1) - { - // Open NURBS have 2 new points if the method is breakpoint: - // One between the 1st and 2nd ... - InterpolateRotScaleUScale(0, 1, 0.5f, 1); - - // ... and one before the last one. - InterpolateRotScaleUScale(NumberOfCVs, NumberOfCVs - 1, 0.5f, NumberOfCVs); - NumberOfCVs += 2; - } - } - else if (CurveTypeValue == HAPI_CURVETYPE_BEZIER) - { - // Bezier curves requires additional point if the method is Breakpoints - if (CurveMethodValue == 1) - { - // 2 interpolated control points are added per points (except the last one) - int32 nOffset = 0; - for (int32 n = 0; n < NumberOfCVs - 1; n++) - { - int nIndex1 = n + nOffset; - int nIndex2 = n + nOffset + 1; - - InterpolateRotScaleUScale(nIndex1, nIndex2, 0.33f, nIndex2); - nIndex2++; - InterpolateRotScaleUScale(nIndex1, nIndex2, 0.66f, nIndex2); - - nOffset += 2; - } - NumberOfCVs += nOffset; - - if (CurveClosed) - { - // If the curve is closed, we need to add 2 points after the last, - // interpolated between the last and the first one - int nIndex = NumberOfCVs - 1; - InterpolateRotScaleUScale(nIndex, 0, 0.33f, NumberOfCVs++); - InterpolateRotScaleUScale(nIndex, 0, 0.66f, NumberOfCVs++); - - // and finally, the last point is the first.. - DuplicateRotScale(0, NumberOfCVs++); - } - } - else if (CurveClosed) - { - // For the other methods, if the bezier curve is closed, the last point is the 1st - DuplicateRotScale(0, NumberOfCVs++); - } - } - - // Even after interpolation, additional points might still be missing: - // Bezier curves require a certain number of points regarding their order, - // if points are lacking then HAPI duplicates the last one. - if (NumberOfCVs < PartInfos.pointCount) - { - int nToAdd = PartInfos.pointCount - NumberOfCVs; - for (int n = 0; n < nToAdd; n++) - { - DuplicateRotScale(NumberOfCVs - 1, NumberOfCVs); - NumberOfCVs++; - } - } - - // To avoid crashes, attributes will only be added if we now have the correct number of them - bAddRotations = bAddRotations && (Rotations->Num() == PartInfos.pointCount); - bAddScales3d = bAddScales3d && (Scales3d->Num() == PartInfos.pointCount); - - // We need to increase the point attributes count for points in the Part Infos - HAPI_AttributeOwner NewAttributesOwner = HAPI_ATTROWNER_POINT; - HAPI_AttributeOwner OriginalAttributesOwner = HAPI_ATTROWNER_POINT; - - int OriginalPointParametersCount = PartInfos.attributeCounts[NewAttributesOwner]; - if (bAddRotations) - PartInfos.attributeCounts[NewAttributesOwner] += 1; - if (bAddScales3d) - PartInfos.attributeCounts[NewAttributesOwner] += 1; - - // Sending the updated PartInfos - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, &PartInfos), false); - - // We need now to reproduce ALL the curves attributes for ALL the Owners.. - for (int nOwner = 0; nOwner < HAPI_ATTROWNER_MAX; nOwner++) - { - int nOwnerAttributeCount = nOwner == NewAttributesOwner ? OriginalPointParametersCount : PartInfos.attributeCounts[nOwner]; - if (nOwnerAttributeCount == 0) - continue; - - TArray AttributeNamesSH; - AttributeNamesSH.SetNum(nOwnerAttributeCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, (HAPI_AttributeOwner)nOwner, - AttributeNamesSH.GetData(), AttributeNamesSH.Num()), false); - - for (int nAttribute = 0; nAttribute < AttributeNamesSH.Num(); nAttribute++) - { - const HAPI_StringHandle sh = AttributeNamesSH[nAttribute]; - if (sh == 0) - continue; - - // Get the attribute name - std::string attr_name; - FHoudiniEngineString::ToStdString(sh, attr_name); - if (strcmp(attr_name.c_str(), "__topology") == 0) - continue; - - // and the attribute infos - HAPI_AttributeInfo attr_info; - FHoudiniApi::AttributeInfo_Init(&attr_info); - //FMemory::Memzero< HAPI_AttributeInfo >( attr_info ); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, attr_name.c_str(), - (HAPI_AttributeOwner)nOwner, &attr_info), false); - - switch (attr_info.storage) - { - case HAPI_STORAGETYPE_INT: - { - // Storing IntData - TArray< int > IntData; - IntData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, -1, - IntData.GetData(), 0, attr_info.count), false); - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info, IntData.GetData(), - 0, attr_info.count), false); - } - break; - - case HAPI_STORAGETYPE_INT64: - { - // Storing IntData - TArray Int64Data; - Int64Data.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, -1, - Int64Data.GetData(), 0, attr_info.count), false); - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeInt64Data( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info, Int64Data.GetData(), - 0, attr_info.count), false); - } - break; - - case HAPI_STORAGETYPE_FLOAT: - { - // Storing Float Data - TArray< float > FloatData; - FloatData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, -1, - FloatData.GetData(), - 0, attr_info.count), false); - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - FloatData.GetData(), - 0, attr_info.count), false); - } - break; - - case HAPI_STORAGETYPE_STRING: - { - // Storing String Data - TArray StringHandleData; - StringHandleData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); - - // GET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - StringHandleData.GetData(), - 0, attr_info.count), false); - - // Convert the SH to const char * - TArray StringData; - StringData.SetNumUninitialized(attr_info.count); - for (int n = 0; n < StringHandleData.Num(); n++) - { - // Converting the string - std::string strSTD; - FHoudiniEngineString::ToStdString(sh, strSTD); - - StringData[n] = strSTD.c_str(); - } - - // ADD - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), - &attr_info), false); - - // SET - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - attr_name.c_str(), &attr_info, - StringData.GetData(), - 0, attr_info.count), false); - } - break; - - default: - { - // Unhandled attribute type - warn - HOUDINI_LOG_WARNING( - TEXT("HapiCreateCurveInputNodeForData() - Unhandled attribute type - skipping") - TEXT("- consider disabling additionnal rot/scale if having issues with the HDA")); - continue; - } - - } - } - } - - // Only GET/SET curve infos if the part is a curve... - // (Closed linear curves are actually not considered as curves...) - if (PartInfos.type == HAPI_PARTTYPE_CURVE) - { - // We need to read the curve infos ... - HAPI_CurveInfo CurveInfo; - FHoudiniApi::CurveInfo_Init(&CurveInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - &CurveInfo), false); - - // ... the curve counts - TArray< int > CurveCounts; - CurveCounts.SetNumUninitialized(CurveInfo.curveCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveCounts.GetData(), - 0, CurveInfo.curveCount), false); - - // .. the curve orders - TArray< int > CurveOrders; - CurveOrders.SetNumUninitialized(CurveInfo.curveCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveOrders( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveOrders.GetData(), - 0, CurveInfo.curveCount), false); - - // .. And the Knots if they exist. - TArray< float > KnotsArray; - if (CurveInfo.hasKnots) - { - KnotsArray.SetNumUninitialized(CurveInfo.knotCount); - HOUDINI_CHECK_ERROR_RETURN( - FHoudiniApi::GetCurveKnots( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - KnotsArray.GetData(), - 0, CurveInfo.knotCount), false); - } - - // To set them back in HAPI - // CurveInfo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveInfo( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - &CurveInfo), false); - - // CurveCounts - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveCounts.GetData(), - 0, CurveInfo.curveCount), false); - - // CurveOrders - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveOrders( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - CurveOrders.GetData(), - 0, CurveInfo.curveCount), false); - - // And Knots if they exist - if (CurveInfo.hasKnots) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveKnots( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - KnotsArray.GetData(), - 0, CurveInfo.knotCount), false); - } - } - - if (PartInfos.faceCount > 0) - { - // getting the face counts - TArray< int > FaceCounts; - FaceCounts.SetNumUninitialized(PartInfos.faceCount); - - if (FHoudiniApi::GetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - FaceCounts.GetData(), 0, - PartInfos.faceCount) == HAPI_RESULT_SUCCESS) - { - // Set the face count - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - FaceCounts.GetData(), - 0, PartInfos.faceCount), false); - } - } - - if (PartInfos.vertexCount > 0) - { - // the vertex list - TArray< int > VertexList; - VertexList.SetNumUninitialized(PartInfos.vertexCount); - - if (FHoudiniApi::GetVertexList( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - VertexList.GetData(), - 0, PartInfos.vertexCount) == HAPI_RESULT_SUCCESS) - { - // setting the vertex list - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - VertexList.GetData(), - 0, PartInfos.vertexCount), false); - } - } - - // We can add attributes to the curve now that all the curves attributes - // and properties have been reset. - if (bAddRotations) - { - // Create ROTATION attribute info - HAPI_AttributeInfo AttributeInfoRotation; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); - AttributeInfoRotation.count = NumberOfCVs; - AttributeInfoRotation.tupleSize = 4; - AttributeInfoRotation.exists = true; - AttributeInfoRotation.owner = NewAttributesOwner; - AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRotation.originalOwner = OriginalAttributesOwner; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_ROTATION, - &AttributeInfoRotation), false); - - // Convert the rotation infos - TArray< float > CurveRotations; - CurveRotations.SetNumZeroed(NumberOfCVs * 4); - for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) - { - // Get current quaternion - const FQuat& RotationQuaternion = (*Rotations)[Idx]; - - CurveRotations[Idx * 4 + 0] = RotationQuaternion.X; - CurveRotations[Idx * 4 + 1] = RotationQuaternion.Z; - CurveRotations[Idx * 4 + 2] = RotationQuaternion.Y; - CurveRotations[Idx * 4 + 3] = -RotationQuaternion.W; - } - - //we can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_ROTATION, - &AttributeInfoRotation, - CurveRotations.GetData(), - 0, AttributeInfoRotation.count), false); - } - - // Create SCALE attribute info. - if (bAddScales3d) - { - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = NumberOfCVs; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = NewAttributesOwner; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = OriginalAttributesOwner; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_SCALE, - &AttributeInfoScale), false); - - // Convert the scale - TArray< float > CurveScales; - CurveScales.SetNumZeroed(NumberOfCVs * 3); - for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) - { - // Get current scale - FVector ScaleVector = (*Scales3d)[Idx]; - CurveScales[Idx * 3 + 0] = ScaleVector.X; - CurveScales[Idx * 3 + 1] = ScaleVector.Z; - CurveScales[Idx * 3 + 2] = ScaleVector.Y; - } - - // We can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CurveNodeId, 0, - HAPI_UNREAL_ATTRIB_SCALE, - &AttributeInfoScale, - CurveScales.GetData(), - 0, AttributeInfoScale.count), false); - } - - // Finally, commit the geo ... - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), CurveNodeId), false); - - // And cook it with refinement enabled - CookOptions.refineCurveToLinear = true; - if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, false)) - return false; -#endif - - return true; -} - -void -FHoudiniSplineTranslator::CreatePositionsString(const TArray& InPositions, FString& OutPositionString) -{ - OutPositionString = TEXT(""); - for (int32 Idx = 0; Idx < InPositions.Num(); ++Idx) - { - FVector Position = InPositions[Idx]; - // Convert to meters - Position /= HAPI_UNREAL_SCALE_FACTOR_POSITION; - // Swap Y/Z - OutPositionString += FString::Printf(TEXT("%f, %f, %f "), Position.X, Position.Z, Position.Y); - } -} - -bool -FHoudiniSplineTranslator::HapiCreateCurveInputNode(HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName) -{ - // Create the curve SOP Node - HAPI_NodeId NewNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/curve"), InputNodeName, false, &NewNodeId), false); - - OutCurveNodeId = NewNodeId; - - // Submit default points to curve. - HAPI_ParmId ParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), NewNodeId, - HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), NewNodeId, - HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT, ParmId, 0), false); - - // Cook the newly created node - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); - */ - - return FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true); -} - -UHoudiniSplineComponent* -FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent) -{ - if (GeoId < 0) - return nullptr; - - if (!OuterComponent || OuterComponent->IsPendingKill()) - return nullptr; - - USceneComponent* const SceneComponent = Cast(OuterComponent); - if (!IsValid(SceneComponent)) - return nullptr; - - // Create a HoudiniSplineComponent for the editable curve. - UHoudiniSplineComponent* HoudiniSplineComponent = NewObject( - OuterComponent, - UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Public | RF_Transactional); - - HoudiniSplineComponent->SetNodeId(GeoId); - HoudiniSplineComponent->SetGeoPartName(PartName); - - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(SceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - - // Delete the curve points so that UpdateHoudiniCurves initializes them from HAPI - HoudiniSplineComponent->CurvePoints.Empty(); - HoudiniSplineComponent->DisplayPoints.Empty(); - UpdateHoudiniCurve(HoudiniSplineComponent); - - ReselectSelectedActors(); - - return HoudiniSplineComponent; - -} - -UHoudiniSplineComponent* -FHoudiniSplineTranslator::CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC) -{ - if (!OuterHAC || OuterHAC->IsPendingKill()) - return nullptr; - - UObject* Outer = nullptr; - if (OuterHAC && !OuterHAC->IsPendingKill()) - Outer = OuterHAC->GetOwner() ? OuterHAC->GetOwner() : OuterHAC->GetOuter(); - - UHoudiniSplineComponent *NewHoudiniSplineComponent = NewObject(Outer, UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!NewHoudiniSplineComponent) - return nullptr; - - NewHoudiniSplineComponent->Construct(CurvePoints); - - bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); - bool bHasScales = CurveScales.Num() == CurvePoints.Num(); - - TArray Transforms; - for (int32 n = 0; n < CurvePoints.Num(); ++n) - { - FTransform NextTransform = FTransform::Identity; - NextTransform.SetLocation(CurvePoints[n]); - - if (bHasRotations) - NextTransform.SetRotation(CurveRotations[n].Rotation().Quaternion()); - - if (bHasScales) - NextTransform.SetScale3D(CurveScales[n]); - - Transforms.Add(NextTransform); - } - - NewHoudiniSplineComponent->CurveType = EHoudiniCurveType::Polygon; - NewHoudiniSplineComponent->bIsOutputCurve = true; - - NewHoudiniSplineComponent->AttachToComponent(OuterHAC, FAttachmentTransformRules::KeepRelativeTransform); - NewHoudiniSplineComponent->RegisterComponent(); - - ReselectSelectedActors(); - - return NewHoudiniSplineComponent; -} - -USplineComponent* -FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, - UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed) -{ - if (!OuterComponent || OuterComponent->IsPendingKill()) - return nullptr; - - USceneComponent* OuterSceneComponent = Cast(OuterComponent); - if (!IsValid(OuterSceneComponent)) - return nullptr; - - UObject* Outer = nullptr; - Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); - - USplineComponent* NewSplineComponent = NewObject(Outer, USplineComponent::StaticClass(), NAME_None, RF_Transactional); - - if (!NewSplineComponent) - return nullptr; - - // Clear default USplineComponent's points - NewSplineComponent->ClearSplinePoints(); - NewSplineComponent->bEditableWhenInherited = false; - - //bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); - //bool bHasScales = CurveScales.Num() == CurvePoints.Num(); - - for (int32 n = 0; n < CurvePoints.Num(); ++n) - { - NewSplineComponent->AddSplinePoint(CurvePoints[n], ESplineCoordinateSpace::Local); - - //FSplinePoint NewSplinePoint; - //NewSplinePoint.Position = CurvePoints[n]; - //if (bHasRotations) - // NewSplinePoint.Rotation = CurveRotations[n].Rotation(); - - //if (bHasScales) - // NewSplinePoint.Scale = CurveScales[n]; - //NewSplineComponent->AddPoint(NewSplinePoint, false); - } - - if (bIsLinear) - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - else - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); - } - - - NewSplineComponent->SetClosedLoop(bIsClosed); - - /* - NewSplineComponent->SetClosedLoop(bClosed); - - if (Type == int32(EHoudiniCurveType::Linear)) - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - else - { - for (int32 n = 0; n < CurvePoints.Num(); ++n) - NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); - } - */ - - NewSplineComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); - NewSplineComponent->RegisterComponent(); - AActor *OwnerActor = Cast(Outer); - if (IsValid(OwnerActor)) - OwnerActor->AddInstanceComponent(NewSplineComponent); - - ReselectSelectedActors(); - - return NewSplineComponent; -} - -bool -FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed) -{ - if (!EditedSplineComponent || EditedSplineComponent->IsPendingKill()) - return false; - - if (CurvePoints.Num() < 2) - return false; - - int MinCount = FMath::Min(CurvePoints.Num(), EditedSplineComponent->GetNumberOfSplinePoints()); - - for (int32 Idx = EditedSplineComponent->GetNumberOfSplinePoints() - 1; Idx >= 0; --Idx) - { - EditedSplineComponent->RemoveSplinePoint(Idx, false); - } - - for (int32 Idx = 0; Idx < CurvePoints.Num(); ++Idx) - { - EditedSplineComponent->AddSplinePoint(CurvePoints[Idx], ESplineCoordinateSpace::Local, false); - - if (CurveType == EHoudiniCurveType::Polygon) - EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Linear, false); - else - EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Curve, false); - } - - EditedSplineComponent->SetClosedLoop(bClosed, true); - - return true; -} - -bool -FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent) -{ - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - if (CurvePoints.Num() < 2) - return false; - - int MinCount = FMath::Min(CurvePoints.Num(), EditedHoudiniSplineComponent->CurvePoints.Num()); - - int Idx = 0; - // modify existing points - for (; Idx < MinCount; ++Idx) - { - FTransform CurTrans = EditedHoudiniSplineComponent->CurvePoints[Idx]; - if (CurTrans.GetLocation() == CurvePoints[Idx]) - continue; - - CurTrans.SetLocation(CurvePoints[Idx]); - } - - // remove extra points - if (Idx < EditedHoudiniSplineComponent->CurvePoints.Num()-1) - { - for (int32 n = EditedHoudiniSplineComponent->CurvePoints.Num() - 1; n >= Idx; --n) - { - EditedHoudiniSplineComponent->RemovePointAtIndex(n); - } - } - - - // append extra points - for (; Idx < CurvePoints.Num(); ++Idx) - { - FTransform NewPoint = FTransform::Identity; - NewPoint.SetLocation(CurvePoints[Idx]); - EditedHoudiniSplineComponent->CurvePoints.Add(NewPoint); - } - - return true; -} - - -bool -FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( - const FHoudiniGeoPartObject& InHGPO, - UObject* InOuterComponent, - TMap& InSplines, - TMap& OutSplines, - const bool& InForceRebuild, - const EHoudiniCurveOutputType& OutputCurveType, - const bool& bIsLinear, - const bool& bIsClosed) -{ - // If we're not forcing the rebuild - // No need to recreate something that hasn't changed - if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged)) - { - // Simply reuse the existing meshes - OutSplines = InSplines; - return true; - } - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - int32 CurveNodeId = InHGPO.GeoId; - int32 CurvePartId = InHGPO.PartId; - if (CurveNodeId < 0 || CurvePartId < 0) - return false; - - // Extract all curve points from this HGPO - TArray RefinedCurvePositions; - HAPI_AttributeInfo AttributeRefinedCurvePositions; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); - - TArray RefinedCurveRotations; - HAPI_AttributeInfo AttributeRefinedCurveRotations; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveRotations); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_ROTATION, AttributeRefinedCurveRotations, RefinedCurveRotations); - - TArray RefinedCurveScales; - HAPI_AttributeInfo AttributeRefinedCurveScales; - FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveScales); - FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( - CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_SCALE, AttributeRefinedCurveScales, RefinedCurveScales); - - HAPI_CurveInfo CurveInfo; - FHoudiniApi::CurveInfo_Init(&CurveInfo); - FHoudiniApi::GetCurveInfo(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, &CurveInfo); - - int32 NumOfCurves = CurveInfo.curveCount; - TArray CurvePointsCounts; - CurvePointsCounts.SetNumZeroed(NumOfCurves); - FHoudiniApi::GetCurveCounts(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, CurvePointsCounts.GetData(), 0, NumOfCurves); - - TArray> CurvesDisplayPoints; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurvesDisplayPoints, CurvePointsCounts); - - TArray> CurvesRotations; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveRotations, CurvesRotations, CurvePointsCounts); - - TArray> CurvesScales; - FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveScales, CurvesScales, CurvePointsCounts); - - // Extract all curve points from this HGPO - FString GeoName = InHGPO.PartName; - int32 CurveIdx = 1; - - // Iterate through all curves found in this HGPO - for (int32 n = 0; n < CurvesDisplayPoints.Num(); ++n) - { - FString CurveName = FString::Printf(TEXT("%s curve %d"), *GeoName, CurveIdx); - CurveIdx += 1; - - if (CurvePointsCounts[n] < 2) - { - // Invalid vertex count, skip this curve. - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d] invalid vertex count.") - TEXT("- skipping."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx); - continue; - } - - FHoudiniOutputObjectIdentifier CurveIdentifier(InHGPO.ObjectId, InHGPO.GeoId, InHGPO.PartId, CurveName); - FHoudiniOutputObject* FoundOutputObject = InSplines.Find(CurveIdentifier); - - bool bNeedToRebuildSpline = false; - if (!FoundOutputObject) - bNeedToRebuildSpline = true; - - USceneComponent* FoundComponent = Cast(FoundOutputObject ? FoundOutputObject->OutputComponent : nullptr); - if (FoundComponent && !FoundComponent->IsPendingKill()) - { - // Only support output to Unreal Spline for now... - //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::UnrealSpline) - // bNeedToRebuildSpline = true; - - //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) - // bNeedToRebuildSpline = true; - - if (InHGPO.bHasGeoChanged || InHGPO.PartInfo.bHasChanged || InForceRebuild) - bNeedToRebuildSpline = true; - } - else - { - bNeedToRebuildSpline = true; - } - - // The curve has not changed, no need to go through the rest - if (!bNeedToRebuildSpline) - { - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - continue; - } - - bool bReusedPreviousOutput = false; - if (!FoundOutputObject) - { - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline (default): Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - // If not found (at initialize), create an Unreal spline - // We only support unreal spline for now.. - // May support Houdini spline too later - USplineComponent* CreatedSplineComponent = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); - if (!CreatedSplineComponent) - continue; - - // Create a new output object - FHoudiniOutputObject NewOutputObject; - NewOutputObject.OutputComponent = CreatedSplineComponent; - - NewOutputObject.CurveOutputProperty.CurveOutputType = OutputCurveType; - NewOutputObject.CurveOutputProperty.NumPoints = CurvePointsCounts[n]; - - // TODO: Need a way to access info of the output curve - NewOutputObject.CurveOutputProperty.CurveMethod = EHoudiniCurveMethod::Breakpoints; - NewOutputObject.CurveOutputProperty.CurveType = bIsLinear ? EHoudiniCurveType::Polygon : EHoudiniCurveType::Bezier; - NewOutputObject.CurveOutputProperty.bClosed = false; - // Fill in the rest of output curve properties - - OutSplines.Add(CurveIdentifier, NewOutputObject); - - // Update FOundOutputObject so we can cache attributes after - FoundOutputObject = OutSplines.Find(CurveIdentifier); - } - else - { - // - if (FoundOutputObject->CurveOutputProperty.CurveOutputType == EHoudiniCurveOutputType::UnrealSpline) - { - // See if we can simply update the previous Spline Component - bool bCanUpdateUnrealSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); - if (bCanUpdateUnrealSpline) - { - // Update the existing unreal spline component - bReusedPreviousOutput = true; - HOUDINI_LOG_WARNING( - TEXT("Updating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - USplineComponent* FoundUnrealSpline = Cast(FoundOutputObject->OutputComponent); - if (!FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(CurvesDisplayPoints[n], FoundUnrealSpline, FoundOutputObject->CurveOutputProperty.CurveType, FoundOutputObject->CurveOutputProperty.bClosed)) - continue; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - else - { - // Create a new Unreal spline component - // We support unreal spline only for now... - bReusedPreviousOutput = false; - FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::UnrealSpline; - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - USplineComponent* NewUnrealSpline = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); - if (!NewUnrealSpline) - continue; - - FoundOutputObject->OutputComponent = NewUnrealSpline; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - } - // We current support Unreal Spline output only... - /* - else - { - // We want to output a Houdini Spline Component - // See if we can simply update the previous Houdini Spline Component - bool bCanUpdateHoudiniSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); - if (bCanUpdateHoudiniSpline) - { - // Update the existing houdini spline component - bReusedPreviousOutput = true; - HOUDINI_LOG_WARNING( - TEXT("Changing Houdini Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - UHoudiniSplineComponent* FoundHoudiniSpline = Cast(FoundOutputObject->OutputComponent); - if (!FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], FoundHoudiniSpline)) - continue; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - else - { - // Create a new Houdini spline component - bReusedPreviousOutput = false; - FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; - HOUDINI_LOG_WARNING( - TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - - UHoudiniSplineComponent* NewHoudiniSpline = CreateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuter); - if (!NewHoudiniSpline) - continue; - - FoundOutputObject->OutputComponent = NewHoudiniSpline; - - OutSplines.Add(CurveIdentifier, *FoundOutputObject); - } - } - */ - } - - // Cache commonly supported Houdini attributes on the OutputAttributes - TArray LevelPaths; - if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute(InHGPO.GeoId, InHGPO.PartId, LevelPaths)) - { - if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) - { - // cache the level path attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); - } - } - - TArray OutputNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutputNames)) - { - if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) - { - // cache the output name attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); - } - } - - TArray BakeOutputActorNames; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute(InHGPO.GeoId, InHGPO.PartId, BakeOutputActorNames)) - { - if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); - } - } - - TArray BakeFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute(InHGPO.GeoId, BakeFolders, InHGPO.PartId)) - { - if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) - { - // cache the unreal_bake_folder attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); - } - } - - TArray BakeOutlinerFolders; - if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute(InHGPO.GeoId, InHGPO.PartId, BakeOutlinerFolders)) - { - if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) - { - // cache the bake actor attribute on the output object - FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); - } - } - - // Update generic properties attributes on the spline component - TArray GenericAttributes; - if (FoundOutputObject && FHoudiniEngineUtils::GetGenericPropertiesAttributes( - InHGPO.GeoId, InHGPO.PartId, true, 0, 0, 0, GenericAttributes)) - { - FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(FoundOutputObject->OutputComponent, GenericAttributes); - } - - if (bReusedPreviousOutput) - { - // Remove the reused output unreal spline from the old map to avoid its deletion - InSplines.Remove(CurveIdentifier); - } - - HOUDINI_LOG_WARNING( - TEXT("Finished Generating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), - InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); - } - - return true; -} - - -bool -FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent) -{ - if (!InOutput || InOutput->IsPendingKill()) - return false; - - if (!InOuterComponent || InOuterComponent->IsPendingKill()) - return false; - - // ONLY DO THIS ON CURVES!!!! - if (InOutput->GetType() != EHoudiniOutputType::Curve) - return false; - - // UHoudiniAssetComponent* OuterHAC = Cast(InOuterComponent); - // - // if (!OuterHAC || OuterHAC->IsPendingKill()) - // return false; - - TMap NewOutputObjects; - TMap& OldOutputObjects = InOutput->GetOutputObjects(); - - // Iterate on all the output's HGPO - for (const FHoudiniGeoPartObject & CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - // not a curve, skip - if (CurHGPO.Type != EHoudiniPartType::Curve) - continue; - - // Check if we want to create a houdini output curve from corresponding attribute - HAPI_AttributeInfo CurveOutputAttriInfo; - FHoudiniApi::AttributeInfo_Init(&CurveOutputAttriInfo); - TArray IntData; - IntData.Empty(); - - if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE, CurveOutputAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - continue; - - if (IntData.Num() <= 0) - continue; - else - { - if (IntData[0] == 0) - continue; - } - - HAPI_AttributeInfo LinearAttriInfo; - FHoudiniApi::AttributeInfo_Init(&LinearAttriInfo); - IntData.Empty(); - - bool bIsLinear = false; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR, LinearAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - { - if (IntData.Num() > 0) - bIsLinear = IntData[0] == 1; - } - - HAPI_AttributeInfo ClosedAttriInfo; - FHoudiniApi::AttributeInfo_Init(&ClosedAttriInfo); - IntData.Empty(); - - bool bIsClosed = false; - if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( - CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED, ClosedAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM)) - { - if (IntData.Num() > 0) - bIsClosed = IntData[0] == 1; - } - - // We output curve to Unreal Spline only for now - // May support output to Houdini Spline later - CreateOutputSplinesFromHoudiniGeoPartObject(CurHGPO, InOuterComponent, OldOutputObjects, NewOutputObjects, false, EHoudiniCurveOutputType::UnrealSpline, bIsLinear, bIsClosed); - } - - // TODO: FIX ME!!! This literally nukes all the output objects, even if they are not curves! - - // The old map now only contains unused/stale output curves destroy them - for (auto& OldPair : OldOutputObjects) - { - USceneComponent* OldSplineSceneComponent = Cast(OldPair.Value.OutputComponent); - - if (!OldSplineSceneComponent || OldSplineSceneComponent->IsPendingKill()) - continue; - - // The output object is supposed to be a spline - if (!OldSplineSceneComponent->IsA() && !OldSplineSceneComponent->IsA()) - continue; - - OldSplineSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - OldSplineSceneComponent->UnregisterComponent(); - OldSplineSceneComponent->DestroyComponent(); - } - OldOutputObjects.Empty(); - - InOutput->SetOutputObjects(NewOutputObjects); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - - return true; -} - -void -FHoudiniSplineTranslator::ReselectSelectedActors() -{ - // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? - USelection* Selection = GEditor->GetSelectedActors(); - TArray SelectedActors; - SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); - Selection->GetSelectedObjects(SelectedActors); - - GEditor->SelectNone(false, false, false); - - for (AActor* NextSelected : SelectedActors) - { - GEditor->SelectActor(NextSelected, true, true, true, true); - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineTranslator.h" + +#include "HoudiniApi.h" +#include "HoudiniEngine.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniGeoPartObject.h" +#include "Components/SplineComponent.h" + +#include "EditorViewportClient.h" +#include "Engine/Selection.h" + +#include "HoudiniEnginePrivatePCH.h" + +void +FHoudiniSplineTranslator::ExtractStringPositions(const FString& Positions, TArray& OutPositions) +{ + TArray< FString > PointStrings; + static const TCHAR * PositionSeparators[] = + { + TEXT(" "), + TEXT(","), + }; + + int32 NumCoords = Positions.ParseIntoArray(PointStrings, PositionSeparators, 2); + OutPositions.SetNum(NumCoords / 3); + for (int32 OutIndex = 0; OutIndex < OutPositions.Num(); OutIndex++) + { + const int32& CoordIndex = OutIndex * 3; + OutPositions[OutIndex].X = FCString::Atof(*(PointStrings[CoordIndex + 0])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutPositions[OutIndex].Y = FCString::Atof(*(PointStrings[CoordIndex + 2])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutPositions[OutIndex].Z = FCString::Atof(*(PointStrings[CoordIndex + 1])) * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } +} + +void +FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData) +{ + OutVectorData.SetNum(InRawData.Num() / 3); + + for (int32 OutIndex = 0; OutIndex < OutVectorData.Num(); OutIndex++) + { + const int32& InIndex = OutIndex * 3; + OutVectorData[OutIndex].X = InRawData[InIndex + 0] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutVectorData[OutIndex].Y = InRawData[InIndex + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + OutVectorData[OutIndex].Z = InRawData[InIndex + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + } +} + +void +FHoudiniSplineTranslator::ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts) +{ + OutVectorData.SetNum(CurveCounts.Num()); + + int32 TotalNumPoints = 0; + for (const int32 & NextCount : CurveCounts) + TotalNumPoints += NextCount; + + // Do not fill the output array, if the total number of points does not match + if (InRawData.Num() < TotalNumPoints * 3) + return; + + + int32 Itr = 0; + + for (int32 n = 0; n < CurveCounts.Num(); ++n) + { + TArray & NextVectorDataArray = OutVectorData[n]; + NextVectorDataArray.SetNumZeroed(CurveCounts[n]); + + for (int32 PtIdx = 0; PtIdx < CurveCounts[n]; ++PtIdx) + { + if (Itr + 2 >= InRawData.Num()) + return; + + NextVectorDataArray[PtIdx].X = InRawData[Itr] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + NextVectorDataArray[PtIdx].Y = InRawData[Itr + 2] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + NextVectorDataArray[PtIdx].Z = InRawData[Itr + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; + + Itr += 3; + } + } +} +void +FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC) +{ + for (UHoudiniInput * NextInput : HAC->Inputs) + UpdateHoudiniInputCurves(NextInput); +} + +void +FHoudiniSplineTranslator::UpdateHoudiniInputCurves(UHoudiniInput* Input) +{ + if (!Input || Input->GetInputType() != EHoudiniInputType::Curve) + return; + + TArray *InputObjectArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!InputObjectArray) + return; + + for (UHoudiniInputObject * NextInputObject : *InputObjectArray) + { + UHoudiniInputHoudiniSplineComponent * HoudiniSplineInput = Cast(NextInputObject); + if (!HoudiniSplineInput) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInput->GetCurveComponent(); + FHoudiniSplineTranslator::UpdateHoudiniCurve(HoudiniSplineComponent); + } +} + +bool +FHoudiniSplineTranslator::UpdateHoudiniCurve(UHoudiniSplineComponent* HoudiniSplineComponent) +{ + if (!IsValid(HoudiniSplineComponent)) + return false; + + int32 CurveNode_id = HoudiniSplineComponent->GetNodeId(); + if (CurveNode_id < 0) + return false; + + FString CurvePointsString = FString(); + if (!FHoudiniEngineUtils::HapiGetParameterDataAsString( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_COORDS, TEXT(""), CurvePointsString)) + { + return false; + } + + int32 CurveTypeValue = (int32)EHoudiniCurveType::Bezier; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue)) + { + return false; + } + HoudiniSplineComponent->SetCurveType((EHoudiniCurveType)CurveTypeValue); + + int32 CurveMethodValue = (int32)EHoudiniCurveMethod::CVs; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue)) + { + return false; + } + HoudiniSplineComponent->SetCurveMethod((EHoudiniCurveMethod)CurveMethodValue); + + int32 CurveClosed = 0; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed)) + { + return false; + } + HoudiniSplineComponent->SetClosedCurve(CurveClosed == 1); + + int32 CurveReversed = 0; + if (!FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNode_id, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed)) + { + return false; + } + HoudiniSplineComponent->SetReversed(CurveReversed == 1); + + // We need to get the NodeInfo to get the parent id + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), CurveNode_id, &NodeInfo), false); + + TArray RefinedCurvePositions; + HAPI_AttributeInfo AttributeRefinedCurvePositions; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNode_id, 0, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions)) + { + return false; + } + + // Process coords string and extract positions. + TArray CurvePoints; + FHoudiniSplineTranslator::ExtractStringPositions(CurvePointsString, CurvePoints); + + TArray CurveDisplayPoints; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurveDisplayPoints); + + // Build curve points for editable curves. + if (HoudiniSplineComponent->CurvePoints.Num() != CurvePoints.Num()) + { + HoudiniSplineComponent->CurvePoints.SetNum(CurvePoints.Num()); + for(int32 Idx = 0; Idx < CurvePoints.Num(); Idx++) + { + FTransform Transform = FTransform::Identity; + Transform.SetLocation(CurvePoints[Idx]); + HoudiniSplineComponent->CurvePoints[Idx] = Transform; + } + } + + // Update the display point on the curve + HoudiniSplineComponent->Construct(CurveDisplayPoints); + + HoudiniSplineComponent->MarkChanged(false); + + return true; +} + + +bool +FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( + UHoudiniSplineComponent* HoudiniSplineComponent, + bool bInAddRotAndScaleAttributes) +{ + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return true; + + TArray PositionArray; + TArray RotationArray; + TArray Scales3dArray; + for (FTransform& CurrentTransform : HoudiniSplineComponent->CurvePoints) + { + PositionArray.Add(CurrentTransform.GetLocation()); + if (bInAddRotAndScaleAttributes) + { + RotationArray.Add(CurrentTransform.GetRotation()); + Scales3dArray.Add(CurrentTransform.GetScale3D()); + } + } + + HAPI_NodeId CurveNode_id = HoudiniSplineComponent->GetNodeId(); + FTransform ParentTransform = HoudiniSplineComponent->GetComponentTransform(); + + FString InputNodeNameString = HoudiniSplineComponent->GetName(); + UHoudiniInputHoudiniSplineComponent* InputObject = Cast(HoudiniSplineComponent->GetOuter()); + if (InputObject) + { + UHoudiniInput* Input = Cast(InputObject->GetOuter()); + if (Input) + { + InputNodeNameString = Input->GetNodeBaseName(); + } + } + InputNodeNameString += TEXT("_curve"); + + bool Success = FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( + CurveNode_id, + InputNodeNameString, + &PositionArray, + bInAddRotAndScaleAttributes ? &RotationArray : nullptr, + bInAddRotAndScaleAttributes ? &Scales3dArray : nullptr, + HoudiniSplineComponent->GetCurveType(), + HoudiniSplineComponent->GetCurveMethod(), + HoudiniSplineComponent->IsClosedCurve(), + HoudiniSplineComponent->IsReversed(), + false, + ParentTransform); + + HoudiniSplineComponent->SetNodeId(CurveNode_id); + Success &= UpdateHoudiniCurve(HoudiniSplineComponent); + + return Success; +} + +bool +FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData( + HAPI_NodeId& CurveNodeId, + const FString& InputNodeName, + TArray* Positions, + TArray* Rotations, + TArray* Scales3d, + EHoudiniCurveType InCurveType, + EHoudiniCurveMethod InCurveMethod, + const bool& InClosed, + const bool& InReversed, + const bool& InForceClose, + const FTransform& ParentTransform ) +{ +#if WITH_EDITOR + // Positions are required + if (!Positions) + return false; + + // We also need a valid host asset and 2 points to make a curve + int32 NumberOfCVs = Positions->Num(); + if (NumberOfCVs < 2) + return false; + + // Check if connected asset id is valid, if it is not, we need to create an input asset. + if (CurveNodeId < 0) + { + HAPI_NodeId NodeId = -1; + // Create the curve SOP Node + if (!FHoudiniSplineTranslator::HapiCreateCurveInputNode(NodeId, InputNodeName)) + return false; + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NodeId)) + return false; + + // We now have a valid id. + CurveNodeId = NodeId; + } + else + { + // We have to revert the Geo to its original state so we can use the Curve SOP: + // adding parameters to the Curve SOP locked it, preventing its parameters (type, method, isClosed) from working + FHoudiniApi::RevertGeo(FHoudiniEngine::Get().GetSession(), CurveNodeId); + } + + // + // In order to be able to add rotations and scale attributes to the curve SOP, we need to cook it twice: + // + // - First, we send the positions string to it, and cook it without refinement. + // this will allow us to get the proper curve CVs, part attributes and curve info to create the desired curve. + // + // - We then need to send back all the info extracted from the curve SOP to it, and add the rotation + // and scale attributes to it. This will lock the curve SOP, and prevent the curve type and method + // parameters from functioning properly (hence why we needed the first cook to set that up) + // + + // Set the curve type and curve method parameters for the curve node + int32 CurveTypeValue = (int32)InCurveType; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); + + int32 CurveMethodValue = (int32)InCurveMethod; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); + + int32 CurveClosed = InClosed ? 1 : 0; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); + + int32 CurveReversed = InReversed ? 1 : 0; + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + + // Reading the curve parameters + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_TYPE, 0, CurveTypeValue); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_METHOD, 0, CurveMethodValue); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, CurveClosed); + FHoudiniEngineUtils::HapiGetParameterDataAsInteger( + CurveNodeId, HAPI_UNREAL_PARAM_CURVE_REVERSED, 0, CurveReversed); + + if (InForceClose) + { + // We need to update the closed parameter + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); + + CurveClosed = 1; + } + + // For closed NURBS (CVs and Breakpoints), we have to close the curve manually, by duplicating its last point + // in order to be able to set the rotations and scales attributes properly. + bool bCloseCurveManually = false; + if (CurveClosed && (CurveTypeValue == HAPI_CURVETYPE_NURBS) && (CurveMethodValue != 2)) + { + // The curve is not closed anymore + if (HAPI_RESULT_SUCCESS == FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 0)) + { + bCloseCurveManually = true; + + // Duplicating the first point to the end of the curve + // This needs to be done before sending the position string + FVector pos = (*Positions)[0]; + Positions->Add(pos); + + CurveClosed = false; + } + } + + // Creating the position string + FString PositionString = TEXT(""); + FHoudiniSplineTranslator::CreatePositionsString(*Positions, PositionString); + + // Get param id for the PositionString and modify it + HAPI_ParmId ParmId = -1; + if (FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId) != HAPI_RESULT_SUCCESS) + { + return false; + } + + std::string ConvertedString = TCHAR_TO_UTF8(*PositionString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + ConvertedString.c_str(), ParmId, 0), false); + + // If we don't want to add rotations or scale attributes to the curve, + // we can just cook the node normally and stop here. + bool bAddRotations = (Rotations != nullptr); + bool bAddScales3d = (Scales3d != nullptr); + if (!bAddRotations && !bAddScales3d) + { + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), CurveNodeId, &CookOptions), false); + */ + + // Cook the node, no need to wait for completion + return FHoudiniEngineUtils::HapiCookNode(CurveNodeId, nullptr, false); + } + + // Setting up the first cook, without the curve refinement + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + CookOptions.maxVerticesPerPrimitive = -1; + CookOptions.refineCurveToLinear = false; + if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, true)) + return false; + + // We can now read back the Part infos from the cooked curve. + HAPI_PartInfo PartInfos; + FHoudiniApi::PartInfo_Init(&PartInfos); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), CurveNodeId, 0, &PartInfos), false); + + // + // Depending on the curve type and method, additionnal control points might have been created. + // We now have to interpolate the rotations and scale attributes for these. + // + + // Lambda function that interpolates rotation, scale and uniform scales values + // between two points using fCoeff as a weight, and insert the interpolated value at nInsertIndex + auto InterpolateRotScaleUScale = [&](const int32& nIndex1, const int32& nIndex2, const float& fCoeff, const int32& nInsertIndex) + { + if (Rotations && Rotations->IsValidIndex(nIndex1) && Rotations->IsValidIndex(nIndex2)) + { + FQuat interpolation = FQuat::Slerp((*Rotations)[nIndex1], (*Rotations)[nIndex2], fCoeff); + if (Rotations->IsValidIndex(nInsertIndex)) + Rotations->Insert(interpolation, nInsertIndex); + else + Rotations->Add(interpolation); + } + + if (Scales3d && Scales3d->IsValidIndex(nIndex1) && Scales3d->IsValidIndex(nIndex2)) + { + FVector interpolation = fCoeff * (*Scales3d)[nIndex1] + (1.0f - fCoeff) * (*Scales3d)[nIndex2]; + if (Scales3d->IsValidIndex(nInsertIndex)) + Scales3d->Insert(interpolation, nInsertIndex); + else + Scales3d->Add(interpolation); + } + }; + + // Lambda function that duplicates rotation and scale values + // at nIndex and insert/adds it at nInsertIndex + auto DuplicateRotScale = [&](const int32& nIndex, const int32& nInsertIndex) + { + if (Rotations && Rotations->IsValidIndex(nIndex)) + { + FQuat value = (*Rotations)[nIndex]; + if (Rotations->IsValidIndex(nInsertIndex)) + Rotations->Insert(value, nInsertIndex); + else + Rotations->Add(value); + } + + if (Scales3d && Scales3d->IsValidIndex(nIndex)) + { + FVector value = (*Scales3d)[nIndex]; + if (Scales3d->IsValidIndex(nInsertIndex)) + Scales3d->Insert(value, nInsertIndex); + else + Scales3d->Add(value); + } + }; + + // Do we want to close the curve by ourselves? + if (bCloseCurveManually) + { + // We need to duplicate the info of the first point to the last + DuplicateRotScale(0, NumberOfCVs++); + + // We need to update the closed parameter + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CurveNodeId, + HAPI_UNREAL_PARAM_CURVE_CLOSED, 0, 1); + } + + // INTERPOLATION + if (CurveTypeValue == HAPI_CURVETYPE_NURBS) + { + // Closed NURBS have additional points reproducing the first ones + if (InClosed) + { + // Only the first one if the method is freehand ... + DuplicateRotScale(0, NumberOfCVs++); + + if (CurveMethodValue != 2) + { + // ... but also the 2nd and 3rd if the method is CVs or Breakpoints. + DuplicateRotScale(1, NumberOfCVs++); + DuplicateRotScale(2, NumberOfCVs++); + } + } + else if (CurveMethodValue == 1) + { + // Open NURBS have 2 new points if the method is breakpoint: + // One between the 1st and 2nd ... + InterpolateRotScaleUScale(0, 1, 0.5f, 1); + + // ... and one before the last one. + InterpolateRotScaleUScale(NumberOfCVs, NumberOfCVs - 1, 0.5f, NumberOfCVs); + NumberOfCVs += 2; + } + } + else if (CurveTypeValue == HAPI_CURVETYPE_BEZIER) + { + // Bezier curves requires additional point if the method is Breakpoints + if (CurveMethodValue == 1) + { + // 2 interpolated control points are added per points (except the last one) + int32 nOffset = 0; + for (int32 n = 0; n < NumberOfCVs - 1; n++) + { + int nIndex1 = n + nOffset; + int nIndex2 = n + nOffset + 1; + + InterpolateRotScaleUScale(nIndex1, nIndex2, 0.33f, nIndex2); + nIndex2++; + InterpolateRotScaleUScale(nIndex1, nIndex2, 0.66f, nIndex2); + + nOffset += 2; + } + NumberOfCVs += nOffset; + + if (CurveClosed) + { + // If the curve is closed, we need to add 2 points after the last, + // interpolated between the last and the first one + int nIndex = NumberOfCVs - 1; + InterpolateRotScaleUScale(nIndex, 0, 0.33f, NumberOfCVs++); + InterpolateRotScaleUScale(nIndex, 0, 0.66f, NumberOfCVs++); + + // and finally, the last point is the first.. + DuplicateRotScale(0, NumberOfCVs++); + } + } + else if (CurveClosed) + { + // For the other methods, if the bezier curve is closed, the last point is the 1st + DuplicateRotScale(0, NumberOfCVs++); + } + } + + // Even after interpolation, additional points might still be missing: + // Bezier curves require a certain number of points regarding their order, + // if points are lacking then HAPI duplicates the last one. + if (NumberOfCVs < PartInfos.pointCount) + { + int nToAdd = PartInfos.pointCount - NumberOfCVs; + for (int n = 0; n < nToAdd; n++) + { + DuplicateRotScale(NumberOfCVs - 1, NumberOfCVs); + NumberOfCVs++; + } + } + + // To avoid crashes, attributes will only be added if we now have the correct number of them + bAddRotations = bAddRotations && (Rotations->Num() == PartInfos.pointCount); + bAddScales3d = bAddScales3d && (Scales3d->Num() == PartInfos.pointCount); + + // We need to increase the point attributes count for points in the Part Infos + HAPI_AttributeOwner NewAttributesOwner = HAPI_ATTROWNER_POINT; + HAPI_AttributeOwner OriginalAttributesOwner = HAPI_ATTROWNER_POINT; + + int OriginalPointParametersCount = PartInfos.attributeCounts[NewAttributesOwner]; + if (bAddRotations) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + if (bAddScales3d) + PartInfos.attributeCounts[NewAttributesOwner] += 1; + + // Sending the updated PartInfos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, &PartInfos), false); + + // We need now to reproduce ALL the curves attributes for ALL the Owners.. + for (int nOwner = 0; nOwner < HAPI_ATTROWNER_MAX; nOwner++) + { + int nOwnerAttributeCount = nOwner == NewAttributesOwner ? OriginalPointParametersCount : PartInfos.attributeCounts[nOwner]; + if (nOwnerAttributeCount == 0) + continue; + + TArray AttributeNamesSH; + AttributeNamesSH.SetNum(nOwnerAttributeCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeNames( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, (HAPI_AttributeOwner)nOwner, + AttributeNamesSH.GetData(), AttributeNamesSH.Num()), false); + + for (int nAttribute = 0; nAttribute < AttributeNamesSH.Num(); nAttribute++) + { + const HAPI_StringHandle sh = AttributeNamesSH[nAttribute]; + if (sh == 0) + continue; + + // Get the attribute name + std::string attr_name; + FHoudiniEngineString::ToStdString(sh, attr_name); + if (strcmp(attr_name.c_str(), "__topology") == 0) + continue; + + // and the attribute infos + HAPI_AttributeInfo attr_info; + FHoudiniApi::AttributeInfo_Init(&attr_info); + //FMemory::Memzero< HAPI_AttributeInfo >( attr_info ); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, attr_name.c_str(), + (HAPI_AttributeOwner)nOwner, &attr_info), false); + + switch (attr_info.storage) + { + case HAPI_STORAGETYPE_INT: + { + // Storing IntData + TArray< int > IntData; + IntData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + IntData.GetData(), 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info, IntData.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_INT64: + { + // Storing IntData + TArray Int64Data; + Int64Data.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + Int64Data.GetData(), 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeInt64Data( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info, Int64Data.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_FLOAT: + { + // Storing Float Data + TArray< float > FloatData; + FloatData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, -1, + FloatData.GetData(), + 0, attr_info.count), false); + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + FloatData.GetData(), + 0, attr_info.count), false); + } + break; + + case HAPI_STORAGETYPE_STRING: + { + // Storing String Data + TArray StringHandleData; + StringHandleData.SetNumUninitialized(attr_info.count * attr_info.tupleSize); + + // GET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + StringHandleData.GetData(), + 0, attr_info.count), false); + + // Convert the SH to const char * + TArray StringData; + StringData.SetNumUninitialized(attr_info.count); + for (int n = 0; n < StringHandleData.Num(); n++) + { + // Converting the string + std::string strSTD; + FHoudiniEngineString::ToStdString(sh, strSTD); + + StringData[n] = strSTD.c_str(); + } + + // ADD + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), + &attr_info), false); + + // SET + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + attr_name.c_str(), &attr_info, + StringData.GetData(), + 0, attr_info.count), false); + } + break; + + default: + { + // Unhandled attribute type - warn + HOUDINI_LOG_WARNING( + TEXT("HapiCreateCurveInputNodeForData() - Unhandled attribute type - skipping") + TEXT("- consider disabling additionnal rot/scale if having issues with the HDA")); + continue; + } + + } + } + } + + // Only GET/SET curve infos if the part is a curve... + // (Closed linear curves are actually not considered as curves...) + if (PartInfos.type == HAPI_PARTTYPE_CURVE) + { + // We need to read the curve infos ... + HAPI_CurveInfo CurveInfo; + FHoudiniApi::CurveInfo_Init(&CurveInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + &CurveInfo), false); + + // ... the curve counts + TArray< int > CurveCounts; + CurveCounts.SetNumUninitialized(CurveInfo.curveCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveCounts.GetData(), + 0, CurveInfo.curveCount), false); + + // .. the curve orders + TArray< int > CurveOrders; + CurveOrders.SetNumUninitialized(CurveInfo.curveCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetCurveOrders( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveOrders.GetData(), + 0, CurveInfo.curveCount), false); + + // .. And the Knots if they exist. + TArray< float > KnotsArray; + if (CurveInfo.hasKnots) + { + KnotsArray.SetNumUninitialized(CurveInfo.knotCount); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetCurveKnots( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + KnotsArray.GetData(), + 0, CurveInfo.knotCount), false); + } + + // To set them back in HAPI + // CurveInfo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveInfo( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + &CurveInfo), false); + + // CurveCounts + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveCounts.GetData(), + 0, CurveInfo.curveCount), false); + + // CurveOrders + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveOrders( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + CurveOrders.GetData(), + 0, CurveInfo.curveCount), false); + + // And Knots if they exist + if (CurveInfo.hasKnots) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetCurveKnots( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + KnotsArray.GetData(), + 0, CurveInfo.knotCount), false); + } + } + + if (PartInfos.faceCount > 0) + { + // getting the face counts + TArray< int > FaceCounts; + FaceCounts.SetNumUninitialized(PartInfos.faceCount); + + if (FHoudiniApi::GetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + FaceCounts.GetData(), 0, + PartInfos.faceCount) == HAPI_RESULT_SUCCESS) + { + // Set the face count + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + FaceCounts.GetData(), + 0, PartInfos.faceCount), false); + } + } + + if (PartInfos.vertexCount > 0) + { + // the vertex list + TArray< int > VertexList; + VertexList.SetNumUninitialized(PartInfos.vertexCount); + + if (FHoudiniApi::GetVertexList( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + VertexList.GetData(), + 0, PartInfos.vertexCount) == HAPI_RESULT_SUCCESS) + { + // setting the vertex list + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + VertexList.GetData(), + 0, PartInfos.vertexCount), false); + } + } + + // We can add attributes to the curve now that all the curves attributes + // and properties have been reset. + if (bAddRotations) + { + // Create ROTATION attribute info + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + AttributeInfoRotation.count = NumberOfCVs; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = NewAttributesOwner; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation), false); + + // Convert the rotation infos + TArray< float > CurveRotations; + CurveRotations.SetNumZeroed(NumberOfCVs * 4); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + // Get current quaternion + const FQuat& RotationQuaternion = (*Rotations)[Idx]; + + CurveRotations[Idx * 4 + 0] = RotationQuaternion.X; + CurveRotations[Idx * 4 + 1] = RotationQuaternion.Z; + CurveRotations[Idx * 4 + 2] = RotationQuaternion.Y; + CurveRotations[Idx * 4 + 3] = -RotationQuaternion.W; + } + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation, + CurveRotations.GetData(), + 0, AttributeInfoRotation.count), false); + } + + // Create SCALE attribute info. + if (bAddScales3d) + { + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = NumberOfCVs; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = NewAttributesOwner; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = OriginalAttributesOwner; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale), false); + + // Convert the scale + TArray< float > CurveScales; + CurveScales.SetNumZeroed(NumberOfCVs * 3); + for (int32 Idx = 0; Idx < NumberOfCVs; ++Idx) + { + // Get current scale + FVector ScaleVector = (*Scales3d)[Idx]; + CurveScales[Idx * 3 + 0] = ScaleVector.X; + CurveScales[Idx * 3 + 1] = ScaleVector.Z; + CurveScales[Idx * 3 + 2] = ScaleVector.Y; + } + + // We can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CurveNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale, + CurveScales.GetData(), + 0, AttributeInfoScale.count), false); + } + + // Finally, commit the geo ... + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), CurveNodeId), false); + + // And cook it with refinement enabled + CookOptions.refineCurveToLinear = true; + if(!FHoudiniEngineUtils::HapiCookNode(CurveNodeId, &CookOptions, false)) + return false; +#endif + + return true; +} + +void +FHoudiniSplineTranslator::CreatePositionsString(const TArray& InPositions, FString& OutPositionString) +{ + OutPositionString = TEXT(""); + for (int32 Idx = 0; Idx < InPositions.Num(); ++Idx) + { + FVector Position = InPositions[Idx]; + // Convert to meters + Position /= HAPI_UNREAL_SCALE_FACTOR_POSITION; + // Swap Y/Z + OutPositionString += FString::Printf(TEXT("%f, %f, %f "), Position.X, Position.Z, Position.Y); + } +} + +bool +FHoudiniSplineTranslator::HapiCreateCurveInputNode(HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName) +{ + // Create the curve SOP Node + HAPI_NodeId NewNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/curve"), InputNodeName, false, &NewNodeId), false); + + OutCurveNodeId = NewNodeId; + + // Submit default points to curve. + HAPI_ParmId ParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), NewNodeId, + HAPI_UNREAL_PARAM_CURVE_COORDS, &ParmId), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), NewNodeId, + HAPI_UNREAL_PARAM_INPUT_CURVE_COORDS_DEFAULT, ParmId, 0), false); + + // Cook the newly created node + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); + */ + + return FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true); +} + +UHoudiniSplineComponent* +FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent) +{ + if (GeoId < 0) + return nullptr; + + if (!OuterComponent || OuterComponent->IsPendingKill()) + return nullptr; + + USceneComponent* const SceneComponent = Cast(OuterComponent); + if (!IsValid(SceneComponent)) + return nullptr; + + // Create a HoudiniSplineComponent for the editable curve. + UHoudiniSplineComponent* HoudiniSplineComponent = NewObject( + OuterComponent, + UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Public | RF_Transactional); + + HoudiniSplineComponent->SetNodeId(GeoId); + HoudiniSplineComponent->SetGeoPartName(PartName); + + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(SceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + + // Delete the curve points so that UpdateHoudiniCurves initializes them from HAPI + HoudiniSplineComponent->CurvePoints.Empty(); + HoudiniSplineComponent->DisplayPoints.Empty(); + UpdateHoudiniCurve(HoudiniSplineComponent); + + ReselectSelectedActors(); + + return HoudiniSplineComponent; + +} + +UHoudiniSplineComponent* +FHoudiniSplineTranslator::CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC) +{ + if (!OuterHAC || OuterHAC->IsPendingKill()) + return nullptr; + + UObject* Outer = nullptr; + if (OuterHAC && !OuterHAC->IsPendingKill()) + Outer = OuterHAC->GetOwner() ? OuterHAC->GetOwner() : OuterHAC->GetOuter(); + + UHoudiniSplineComponent *NewHoudiniSplineComponent = NewObject(Outer, UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!NewHoudiniSplineComponent) + return nullptr; + + NewHoudiniSplineComponent->Construct(CurvePoints); + + bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); + bool bHasScales = CurveScales.Num() == CurvePoints.Num(); + + TArray Transforms; + for (int32 n = 0; n < CurvePoints.Num(); ++n) + { + FTransform NextTransform = FTransform::Identity; + NextTransform.SetLocation(CurvePoints[n]); + + if (bHasRotations) + NextTransform.SetRotation(CurveRotations[n].Rotation().Quaternion()); + + if (bHasScales) + NextTransform.SetScale3D(CurveScales[n]); + + Transforms.Add(NextTransform); + } + + NewHoudiniSplineComponent->CurveType = EHoudiniCurveType::Polygon; + NewHoudiniSplineComponent->bIsOutputCurve = true; + + NewHoudiniSplineComponent->AttachToComponent(OuterHAC, FAttachmentTransformRules::KeepRelativeTransform); + NewHoudiniSplineComponent->RegisterComponent(); + + ReselectSelectedActors(); + + return NewHoudiniSplineComponent; +} + +USplineComponent* +FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, + UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed) +{ + if (!OuterComponent || OuterComponent->IsPendingKill()) + return nullptr; + + USceneComponent* OuterSceneComponent = Cast(OuterComponent); + if (!IsValid(OuterSceneComponent)) + return nullptr; + + UObject* Outer = nullptr; + Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); + + USplineComponent* NewSplineComponent = NewObject(Outer, USplineComponent::StaticClass(), NAME_None, RF_Transactional); + + if (!NewSplineComponent) + return nullptr; + + // Clear default USplineComponent's points + NewSplineComponent->ClearSplinePoints(); + NewSplineComponent->bEditableWhenInherited = false; + + //bool bHasRotations = CurveRotations.Num() == CurvePoints.Num(); + //bool bHasScales = CurveScales.Num() == CurvePoints.Num(); + + for (int32 n = 0; n < CurvePoints.Num(); ++n) + { + NewSplineComponent->AddSplinePoint(CurvePoints[n], ESplineCoordinateSpace::Local); + + //FSplinePoint NewSplinePoint; + //NewSplinePoint.Position = CurvePoints[n]; + //if (bHasRotations) + // NewSplinePoint.Rotation = CurveRotations[n].Rotation(); + + //if (bHasScales) + // NewSplinePoint.Scale = CurveScales[n]; + //NewSplineComponent->AddPoint(NewSplinePoint, false); + } + + if (bIsLinear) + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + else + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); + } + + + NewSplineComponent->SetClosedLoop(bIsClosed); + + /* + NewSplineComponent->SetClosedLoop(bClosed); + + if (Type == int32(EHoudiniCurveType::Linear)) + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + else + { + for (int32 n = 0; n < CurvePoints.Num(); ++n) + NewSplineComponent->SetSplinePointType(n, ESplinePointType::Curve); + } + */ + + NewSplineComponent->AttachToComponent(OuterSceneComponent, FAttachmentTransformRules::KeepRelativeTransform); + NewSplineComponent->RegisterComponent(); + AActor *OwnerActor = Cast(Outer); + if (IsValid(OwnerActor)) + OwnerActor->AddInstanceComponent(NewSplineComponent); + + ReselectSelectedActors(); + + return NewSplineComponent; +} + +bool +FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed) +{ + if (!EditedSplineComponent || EditedSplineComponent->IsPendingKill()) + return false; + + if (CurvePoints.Num() < 2) + return false; + + int MinCount = FMath::Min(CurvePoints.Num(), EditedSplineComponent->GetNumberOfSplinePoints()); + + for (int32 Idx = EditedSplineComponent->GetNumberOfSplinePoints() - 1; Idx >= 0; --Idx) + { + EditedSplineComponent->RemoveSplinePoint(Idx, false); + } + + for (int32 Idx = 0; Idx < CurvePoints.Num(); ++Idx) + { + EditedSplineComponent->AddSplinePoint(CurvePoints[Idx], ESplineCoordinateSpace::Local, false); + + if (CurveType == EHoudiniCurveType::Polygon) + EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Linear, false); + else + EditedSplineComponent->SetSplinePointType(Idx, ESplinePointType::Curve, false); + } + + EditedSplineComponent->SetClosedLoop(bClosed, true); + + return true; +} + +bool +FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent) +{ + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + if (CurvePoints.Num() < 2) + return false; + + int MinCount = FMath::Min(CurvePoints.Num(), EditedHoudiniSplineComponent->CurvePoints.Num()); + + int Idx = 0; + // modify existing points + for (; Idx < MinCount; ++Idx) + { + FTransform CurTrans = EditedHoudiniSplineComponent->CurvePoints[Idx]; + if (CurTrans.GetLocation() == CurvePoints[Idx]) + continue; + + CurTrans.SetLocation(CurvePoints[Idx]); + } + + // remove extra points + if (Idx < EditedHoudiniSplineComponent->CurvePoints.Num()-1) + { + for (int32 n = EditedHoudiniSplineComponent->CurvePoints.Num() - 1; n >= Idx; --n) + { + EditedHoudiniSplineComponent->RemovePointAtIndex(n); + } + } + + + // append extra points + for (; Idx < CurvePoints.Num(); ++Idx) + { + FTransform NewPoint = FTransform::Identity; + NewPoint.SetLocation(CurvePoints[Idx]); + EditedHoudiniSplineComponent->CurvePoints.Add(NewPoint); + } + + return true; +} + + +bool +FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( + const FHoudiniGeoPartObject& InHGPO, + UObject* InOuterComponent, + TMap& InSplines, + TMap& OutSplines, + const bool& InForceRebuild, + const EHoudiniCurveOutputType& OutputCurveType, + const bool& bIsLinear, + const bool& bIsClosed) +{ + // If we're not forcing the rebuild + // No need to recreate something that hasn't changed + if (!InForceRebuild && (!InHGPO.bHasGeoChanged || !InHGPO.bHasPartChanged)) + { + // Simply reuse the existing meshes + OutSplines = InSplines; + return true; + } + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + int32 CurveNodeId = InHGPO.GeoId; + int32 CurvePartId = InHGPO.PartId; + if (CurveNodeId < 0 || CurvePartId < 0) + return false; + + // Extract all curve points from this HGPO + TArray RefinedCurvePositions; + HAPI_AttributeInfo AttributeRefinedCurvePositions; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurvePositions); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_POSITION, AttributeRefinedCurvePositions, RefinedCurvePositions); + + TArray RefinedCurveRotations; + HAPI_AttributeInfo AttributeRefinedCurveRotations; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveRotations); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_ROTATION, AttributeRefinedCurveRotations, RefinedCurveRotations); + + TArray RefinedCurveScales; + HAPI_AttributeInfo AttributeRefinedCurveScales; + FHoudiniApi::AttributeInfo_Init(&AttributeRefinedCurveScales); + FHoudiniEngineUtils::HapiGetAttributeDataAsFloat( + CurveNodeId, CurvePartId, HAPI_UNREAL_ATTRIB_SCALE, AttributeRefinedCurveScales, RefinedCurveScales); + + HAPI_CurveInfo CurveInfo; + FHoudiniApi::CurveInfo_Init(&CurveInfo); + FHoudiniApi::GetCurveInfo(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, &CurveInfo); + + int32 NumOfCurves = CurveInfo.curveCount; + TArray CurvePointsCounts; + CurvePointsCounts.SetNumZeroed(NumOfCurves); + FHoudiniApi::GetCurveCounts(FHoudiniEngine::Get().GetSession(), CurveNodeId, CurvePartId, CurvePointsCounts.GetData(), 0, NumOfCurves); + + TArray> CurvesDisplayPoints; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurvePositions, CurvesDisplayPoints, CurvePointsCounts); + + TArray> CurvesRotations; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveRotations, CurvesRotations, CurvePointsCounts); + + TArray> CurvesScales; + FHoudiniSplineTranslator::ConvertToVectorData(RefinedCurveScales, CurvesScales, CurvePointsCounts); + + // Extract all curve points from this HGPO + FString GeoName = InHGPO.PartName; + int32 CurveIdx = 1; + + // Iterate through all curves found in this HGPO + for (int32 n = 0; n < CurvesDisplayPoints.Num(); ++n) + { + FString CurveName = FString::Printf(TEXT("%s curve %d"), *GeoName, CurveIdx); + CurveIdx += 1; + + if (CurvePointsCounts[n] < 2) + { + // Invalid vertex count, skip this curve. + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d] invalid vertex count.") + TEXT("- skipping."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx); + continue; + } + + FHoudiniOutputObjectIdentifier CurveIdentifier(InHGPO.ObjectId, InHGPO.GeoId, InHGPO.PartId, CurveName); + FHoudiniOutputObject* FoundOutputObject = InSplines.Find(CurveIdentifier); + + bool bNeedToRebuildSpline = false; + if (!FoundOutputObject) + bNeedToRebuildSpline = true; + + USceneComponent* FoundComponent = Cast(FoundOutputObject ? FoundOutputObject->OutputComponent : nullptr); + if (FoundComponent && !FoundComponent->IsPendingKill()) + { + // Only support output to Unreal Spline for now... + //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::UnrealSpline) + // bNeedToRebuildSpline = true; + + //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + // bNeedToRebuildSpline = true; + + if (InHGPO.bHasGeoChanged || InHGPO.PartInfo.bHasChanged || InForceRebuild) + bNeedToRebuildSpline = true; + } + else + { + bNeedToRebuildSpline = true; + } + + // The curve has not changed, no need to go through the rest + if (!bNeedToRebuildSpline) + { + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + continue; + } + + bool bReusedPreviousOutput = false; + if (!FoundOutputObject) + { + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline (default): Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + // If not found (at initialize), create an Unreal spline + // We only support unreal spline for now.. + // May support Houdini spline too later + USplineComponent* CreatedSplineComponent = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); + if (!CreatedSplineComponent) + continue; + + // Create a new output object + FHoudiniOutputObject NewOutputObject; + NewOutputObject.OutputComponent = CreatedSplineComponent; + + NewOutputObject.CurveOutputProperty.CurveOutputType = OutputCurveType; + NewOutputObject.CurveOutputProperty.NumPoints = CurvePointsCounts[n]; + + // TODO: Need a way to access info of the output curve + NewOutputObject.CurveOutputProperty.CurveMethod = EHoudiniCurveMethod::Breakpoints; + NewOutputObject.CurveOutputProperty.CurveType = bIsLinear ? EHoudiniCurveType::Polygon : EHoudiniCurveType::Bezier; + NewOutputObject.CurveOutputProperty.bClosed = false; + // Fill in the rest of output curve properties + + OutSplines.Add(CurveIdentifier, NewOutputObject); + + // Update FOundOutputObject so we can cache attributes after + FoundOutputObject = OutSplines.Find(CurveIdentifier); + } + else + { + // + if (FoundOutputObject->CurveOutputProperty.CurveOutputType == EHoudiniCurveOutputType::UnrealSpline) + { + // See if we can simply update the previous Spline Component + bool bCanUpdateUnrealSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); + if (bCanUpdateUnrealSpline) + { + // Update the existing unreal spline component + bReusedPreviousOutput = true; + HOUDINI_LOG_WARNING( + TEXT("Updating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + USplineComponent* FoundUnrealSpline = Cast(FoundOutputObject->OutputComponent); + if (!FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(CurvesDisplayPoints[n], FoundUnrealSpline, FoundOutputObject->CurveOutputProperty.CurveType, FoundOutputObject->CurveOutputProperty.bClosed)) + continue; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + else + { + // Create a new Unreal spline component + // We support unreal spline only for now... + bReusedPreviousOutput = false; + FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::UnrealSpline; + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + USplineComponent* NewUnrealSpline = FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuterComponent, bIsLinear, bIsClosed); + if (!NewUnrealSpline) + continue; + + FoundOutputObject->OutputComponent = NewUnrealSpline; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + } + // We current support Unreal Spline output only... + /* + else + { + // We want to output a Houdini Spline Component + // See if we can simply update the previous Houdini Spline Component + bool bCanUpdateHoudiniSpline = (FoundOutputObject->OutputComponent && FoundOutputObject->OutputComponent->IsA()); + if (bCanUpdateHoudiniSpline) + { + // Update the existing houdini spline component + bReusedPreviousOutput = true; + HOUDINI_LOG_WARNING( + TEXT("Changing Houdini Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + UHoudiniSplineComponent* FoundHoudiniSpline = Cast(FoundOutputObject->OutputComponent); + if (!FHoudiniSplineTranslator::UpdateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], FoundHoudiniSpline)) + continue; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + else + { + // Create a new Houdini spline component + bReusedPreviousOutput = false; + FoundOutputObject->CurveOutputProperty.CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; + HOUDINI_LOG_WARNING( + TEXT("Creating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + + UHoudiniSplineComponent* NewHoudiniSpline = CreateOutputHoudiniSplineComponent(CurvesDisplayPoints[n], CurvesRotations[n], CurvesScales[n], InOuter); + if (!NewHoudiniSpline) + continue; + + FoundOutputObject->OutputComponent = NewHoudiniSpline; + + OutSplines.Add(CurveIdentifier, *FoundOutputObject); + } + } + */ + } + + // Cache commonly supported Houdini attributes on the OutputAttributes + TArray LevelPaths; + if (FoundOutputObject && FHoudiniEngineUtils::GetLevelPathAttribute( + InHGPO.GeoId, InHGPO.PartId, LevelPaths, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (LevelPaths.Num() > 0 && !LevelPaths[0].IsEmpty()) + { + // cache the level path attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_LEVEL_PATH, LevelPaths[0]); + } + } + + TArray OutputNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetOutputNameAttribute( + InHGPO.GeoId, InHGPO.PartId, OutputNames, 0, 1)) + { + if (OutputNames.Num() > 0 && !OutputNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2, OutputNames[0]); + } + } + + TArray BakeOutputActorNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute( + InHGPO.GeoId, InHGPO.PartId, BakeOutputActorNames, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (BakeOutputActorNames.Num() > 0 && !BakeOutputActorNames[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_ACTOR, BakeOutputActorNames[0]); + } + } + + TArray BakeFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeFolderAttribute( + InHGPO.GeoId, BakeFolders, InHGPO.PartId, 0, 1)) + { + if (BakeFolders.Num() > 0 && !BakeFolders[0].IsEmpty()) + { + // cache the unreal_bake_folder attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, BakeFolders[0]); + } + } + + TArray BakeOutlinerFolders; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeOutlinerFolderAttribute( + InHGPO.GeoId, InHGPO.PartId, BakeOutlinerFolders, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (BakeOutlinerFolders.Num() > 0 && !BakeOutlinerFolders[0].IsEmpty()) + { + // cache the bake actor attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER, BakeOutlinerFolders[0]); + } + } + + // Update generic properties attributes on the spline component + TArray GenericAttributes; + if (FoundOutputObject && FHoudiniEngineUtils::GetGenericPropertiesAttributes( + InHGPO.GeoId, InHGPO.PartId, true, 0, 0, 0, GenericAttributes)) + { + FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(FoundOutputObject->OutputComponent, GenericAttributes); + } + + if (bReusedPreviousOutput) + { + // Remove the reused output unreal spline from the old map to avoid its deletion + InSplines.Remove(CurveIdentifier); + } + + HOUDINI_LOG_WARNING( + TEXT("Finished Generating Unreal Spline: Object [%d %s], Geo [%d], Part [%d %s], Curve# [%d], number of points [%d]."), + InHGPO.ObjectId, *InHGPO.ObjectName, InHGPO.GeoId, InHGPO.PartId, *InHGPO.PartName, CurveIdx, CurvePointsCounts[n]); + } + + return true; +} + + +bool +FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent) +{ + if (!InOutput || InOutput->IsPendingKill()) + return false; + + if (!InOuterComponent || InOuterComponent->IsPendingKill()) + return false; + + // ONLY DO THIS ON CURVES!!!! + if (InOutput->GetType() != EHoudiniOutputType::Curve) + return false; + + // UHoudiniAssetComponent* OuterHAC = Cast(InOuterComponent); + // + // if (!OuterHAC || OuterHAC->IsPendingKill()) + // return false; + + TMap NewOutputObjects; + TMap& OldOutputObjects = InOutput->GetOutputObjects(); + + // Iterate on all the output's HGPO + for (const FHoudiniGeoPartObject & CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + // not a curve, skip + if (CurHGPO.Type != EHoudiniPartType::Curve) + continue; + + // Check if we want to create a houdini output curve from corresponding attribute + HAPI_AttributeInfo CurveOutputAttriInfo; + FHoudiniApi::AttributeInfo_Init(&CurveOutputAttriInfo); + TArray IntData; + IntData.Empty(); + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE, + CurveOutputAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) + continue; + + if (IntData.Num() <= 0) + continue; + + if (IntData[0] == 0) + continue; + + HAPI_AttributeInfo LinearAttriInfo; + FHoudiniApi::AttributeInfo_Init(&LinearAttriInfo); + IntData.Empty(); + + bool bIsLinear = false; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_LINEAR, + LinearAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) + { + if (IntData.Num() > 0) + bIsLinear = IntData[0] != 0; + } + + HAPI_AttributeInfo ClosedAttriInfo; + FHoudiniApi::AttributeInfo_Init(&ClosedAttriInfo); + IntData.Empty(); + + bool bIsClosed = false; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + CurHGPO.GeoId, CurHGPO.PartId, HAPI_UNREAL_ATTRIB_OUTPUT_UNREAL_CURVE_CLOSED, + ClosedAttriInfo, IntData, 1, HAPI_ATTROWNER_PRIM, 0, 1)) + { + if (IntData.Num() > 0) + bIsClosed = IntData[0] != 0; + } + + // We output curve to Unreal Spline only for now + // May support output to Houdini Spline later + CreateOutputSplinesFromHoudiniGeoPartObject(CurHGPO, InOuterComponent, OldOutputObjects, NewOutputObjects, false, EHoudiniCurveOutputType::UnrealSpline, bIsLinear, bIsClosed); + } + + // TODO: FIX ME!!! This literally nukes all the output objects, even if they are not curves! + + // The old map now only contains unused/stale output curves destroy them + for (auto& OldPair : OldOutputObjects) + { + USceneComponent* OldSplineSceneComponent = Cast(OldPair.Value.OutputComponent); + + if (!OldSplineSceneComponent || OldSplineSceneComponent->IsPendingKill()) + continue; + + // The output object is supposed to be a spline + if (!OldSplineSceneComponent->IsA() && !OldSplineSceneComponent->IsA()) + continue; + + OldSplineSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + OldSplineSceneComponent->UnregisterComponent(); + OldSplineSceneComponent->DestroyComponent(); + } + OldOutputObjects.Empty(); + + InOutput->SetOutputObjects(NewOutputObjects); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + + return true; +} + +void +FHoudiniSplineTranslator::ReselectSelectedActors() +{ + // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? + USelection* Selection = GEditor->GetSelectedActors(); + TArray SelectedActors; + SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); + Selection->GetSelectedObjects(SelectedActors); + + GEditor->SelectNone(false, false, false); + + for (AActor* NextSelected : SelectedActors) + { + GEditor->SelectActor(NextSelected, true, true, true, true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h index e7f566b75..2c90a3ffe 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.h @@ -1,112 +1,112 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" - -class UHoudiniInput; -class UHoudiniOutput; -class UHoudiniAssetComponent; -class UHoudiniSplineComponent; -class USceneComponent; -class USplineComponent; - -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniOutputObject; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniCurveOutputType : uint8; - -struct HOUDINIENGINE_API FHoudiniSplineTranslator -{ - // Get the cooked Houdini curve. - static bool UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent); - - // Get all cooked Houdini curves of an input. - static void UpdateHoudiniInputCurves(UHoudiniInput* Input); - - // Get all cooked Houdini curves of inputs in an HAC. - static void UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC); - - // Upload Houdini spline component data to the curve node, and then sync the Houdini Spline Component with the curve node. - static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent, bool bInSetRotAndScaleAttributes); - - // Update the curve node data, or create a new curve node if the CurveNodeId is valid. - static bool HapiCreateCurveInputNodeForData( - HAPI_NodeId& CurveNodeId, - const FString& InputNodeName, - TArray* Positions, - TArray* Rotations, - TArray* Scales3d, - EHoudiniCurveType InCurveType, - EHoudiniCurveMethod InCurveMethod, - const bool& InClosed, - const bool& InReversed, - const bool& InForceClose = false, - const FTransform& ParentTransform = FTransform::Identity); - - // Create a default curve node. - static bool HapiCreateCurveInputNode( - HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName); - - // Create a Houdini spline component from a given editable node. (Only called once when first build the editable node.) - static UHoudiniSplineComponent* CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent); - - // Helper functions. - static void ExtractStringPositions(const FString& Positions, TArray& OutPositions); - - static void ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData); - - static void ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts); - - static void CreatePositionsString(const TArray& InPositions, FString& OutPositionString); - - static bool CreateOutputSplinesFromHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO, UObject* InOuterComponent, - TMap& InSplines, - TMap& OutSplines, - const bool& InForceRebuild, - const EHoudiniCurveOutputType& OutputCurveType, - const bool& bIsLinear, - const bool& bIsclosed); - - static bool CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent); - - static USplineComponent* CreateOutputUnrealSplineComponent(const TArray& CurvePoints, - const TArray& CurveRotations, const TArray& CurveScales, UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed); - - static UHoudiniSplineComponent* CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC); - - static bool UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed); - - static bool UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent); - - static void ReselectSelectedActors(); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" + +class UHoudiniInput; +class UHoudiniOutput; +class UHoudiniAssetComponent; +class UHoudiniSplineComponent; +class USceneComponent; +class USplineComponent; + +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniOutputObject; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniCurveOutputType : uint8; + +struct HOUDINIENGINE_API FHoudiniSplineTranslator +{ + // Get the cooked Houdini curve. + static bool UpdateHoudiniCurve(UHoudiniSplineComponent * HoudiniSplineComponent); + + // Get all cooked Houdini curves of an input. + static void UpdateHoudiniInputCurves(UHoudiniInput* Input); + + // Get all cooked Houdini curves of inputs in an HAC. + static void UpdateHoudiniInputCurves(UHoudiniAssetComponent* HAC); + + // Upload Houdini spline component data to the curve node, and then sync the Houdini Spline Component with the curve node. + static bool HapiUpdateNodeForHoudiniSplineComponent(UHoudiniSplineComponent* HoudiniSplineComponent, bool bInSetRotAndScaleAttributes); + + // Update the curve node data, or create a new curve node if the CurveNodeId is valid. + static bool HapiCreateCurveInputNodeForData( + HAPI_NodeId& CurveNodeId, + const FString& InputNodeName, + TArray* Positions, + TArray* Rotations, + TArray* Scales3d, + EHoudiniCurveType InCurveType, + EHoudiniCurveMethod InCurveMethod, + const bool& InClosed, + const bool& InReversed, + const bool& InForceClose = false, + const FTransform& ParentTransform = FTransform::Identity); + + // Create a default curve node. + static bool HapiCreateCurveInputNode( + HAPI_NodeId& OutCurveNodeId, const FString& InputNodeName); + + // Create a Houdini spline component from a given editable node. (Only called once when first build the editable node.) + static UHoudiniSplineComponent* CreateHoudiniSplineComponentFromHoudiniEditableNode(const int32 & GeoId, const FString & PartName, UObject* OuterComponent); + + // Helper functions. + static void ExtractStringPositions(const FString& Positions, TArray& OutPositions); + + static void ConvertToVectorData(const TArray & InRawData, TArray& OutVectorData); + + static void ConvertToVectorData(const TArray & InRawData, TArray>& OutVectorData, const TArray& CurveCounts); + + static void CreatePositionsString(const TArray& InPositions, FString& OutPositionString); + + static bool CreateOutputSplinesFromHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO, UObject* InOuterComponent, + TMap& InSplines, + TMap& OutSplines, + const bool& InForceRebuild, + const EHoudiniCurveOutputType& OutputCurveType, + const bool& bIsLinear, + const bool& bIsclosed); + + static bool CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent); + + static USplineComponent* CreateOutputUnrealSplineComponent(const TArray& CurvePoints, + const TArray& CurveRotations, const TArray& CurveScales, UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed); + + static UHoudiniSplineComponent* CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC); + + static bool UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed); + + static bool UpdateOutputHoudiniSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent); + + static void ReselectSelectedActors(); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp index a3725b82f..90c2f02c7 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp @@ -1,220 +1,220 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStringResolver.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniEngineRuntime.h" - -#include "HoudiniEnginePrivatePCH.h" -#include "HAL/FileManager.h" - -void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutTokens) const -{ - for (auto& Elem : CachedTokens) - { - OutTokens.Add(Elem.Key, Elem.Value.StringValue); - } -} - -FString FHoudiniStringResolver::SanitizeTokenValue(const FString& InValue) -{ - // Replace {} characters with __ - FString OutString = InValue; - OutString.ReplaceInline(ANSI_TO_TCHAR("{"), ANSI_TO_TCHAR("__")); - OutString.ReplaceInline(ANSI_TO_TCHAR("}"), ANSI_TO_TCHAR("__")); - - return OutString; -} - -void FHoudiniStringResolver::SetToken(const FString& InName, const FString& InValue) -{ - CachedTokens.Add(InName, SanitizeTokenValue(InValue)); -} - -void FHoudiniStringResolver::SetTokensFromStringMap(const TMap& InTokens, bool bClearTokens) -{ - if (bClearTokens) - { - CachedTokens.Empty(); - } - - for (auto& Elem : InTokens) - { - CachedTokens.Add(Elem.Key, SanitizeTokenValue(Elem.Value)); - } -} - - - -FString FHoudiniStringResolver::ResolveString( - const FString& InString) const -{ - const FString Result = FString::Format(*InString, CachedTokens); - return Result; -} - -//void FHoudiniStringResolver::SetCurrentWorld(UWorld* InWorld) -//{ -// SetAttribute("world", InWorld->GetPathName()); -//} - -FString FHoudiniAttributeResolver::ResolveAttribute( - const FString& InAttrName, - const FString& InDefaultValue) const -{ - if (!CachedAttributes.Contains(InAttrName)) - { - return ResolveString(InDefaultValue); - } - FString AttrStr = CachedAttributes.FindChecked(InAttrName); - return ResolveString(AttrStr); -} - -//FString FHoudiniStringResolver::GetTempFolderArgument() const -//{ -// // The actual temp directory should have been supplied externally -// if (Tokens.Contains(TEXT("temp"))) -// return Tokens.FindChecked(TEXT("temp")); -// -// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'temp' argument. Using fallback value.")); -// return TEXT("/Game/Content/HoudiniEngine/Temp"); // Fallback value -//} -// -//FString FHoudiniStringResolver::GetBakeFolderArgument() const -//{ -// // The actual bake directory should have been supplied externally -// if (Tokens.Contains(TEXT("bake"))) -// return Tokens.FindChecked(TEXT("bake")); -// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'bake' argument. Using fallback value.")); -// return TEXT("/Game/Content/HoudiniEngine/Bake"); // Fallback value -//} -// -//FString FHoudiniStringResolver::GetOutputFolderForPackageMode(EPackageMode PackageMode) const -//{ -// switch (PackageMode) -// { -// case EPackageMode::Bake: -// return GetBakeFolderArgument(); -// case EPackageMode::CookToLevel: -// case EPackageMode::CookToTemp: -// return GetTempFolderArgument(); -// } -// return ""; -//} - - void FHoudiniAttributeResolver::SetCachedAttributes(const TMap& Attributes) - { - CachedAttributes = Attributes; - } - -void FHoudiniAttributeResolver::SetAttribute(const FString& InName, const FString& InValue) -{ - CachedAttributes.Add(InName, InValue); -} - -FString FHoudiniAttributeResolver::ResolveFullLevelPath() const -{ - FString OutputFolder = TEXT("/Game/Content/HoudiniEngine/Temp"); - - const FStringFormatArg* BaseDir = CachedTokens.Find(TEXT("out_basedir")); - if (BaseDir) - OutputFolder = BaseDir->StringValue; - - FString LevelPathAttr = ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, TEXT("{out}")); - if (LevelPathAttr.IsEmpty()) - return OutputFolder; - - return FHoudiniEngineRuntimeUtils::JoinPaths(OutputFolder, LevelPathAttr); -} - -FString FHoudiniAttributeResolver::ResolveOutputName() const -{ - FString OutputAttribName; - - if (CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2)) - OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2; - else - OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1; - - return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); -} - -FString FHoudiniAttributeResolver::ResolveBakeFolder() const -{ - const FString DefaultBakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); - - FString BakeFolder = ResolveAttribute(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, TEXT("{bake}")); - if (BakeFolder.IsEmpty()) - return DefaultBakeFolder; - - //if (BakeFolder.StartsWith("Game/")) - //{ - // BakeFolder = "/" + BakeFolder; - //} - - //FString AbsoluteOverridePath; - //if (BakeFolder.StartsWith("/Game/")) - //{ - // const FString RelativePath = FPaths::ProjectContentDir() + BakeFolder.Mid(6, BakeFolder.Len() - 6); - // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - //} - //else - //{ - // if (!BakeFolder.IsEmpty()) - // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolder); - //} - - //// Check Validity of the path - //if (AbsoluteOverridePath.IsEmpty()) - //{ - // return DefaultBakeFolder; - //} - - return BakeFolder; -} - -void FHoudiniAttributeResolver::LogCachedAttributesAndTokens() const -{ - TArray Lines; - Lines.Add(TEXT("==================")); - Lines.Add(TEXT("Cached Attributes:")); - Lines.Add(TEXT("==================")); - for (const auto& Entry : CachedAttributes) - { - Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value))); - } - - Lines.Add(TEXT("==============")); - Lines.Add(TEXT("Cached Tokens:")); - Lines.Add(TEXT("==============")); - for (const auto& Entry : CachedTokens) - { - Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value.StringValue))); - } - - HOUDINI_LOG_DISPLAY(TEXT("%s"), *FString::Join(Lines, TEXT("\n"))); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStringResolver.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntime.h" + +#include "HoudiniEnginePrivatePCH.h" +#include "HAL/FileManager.h" + +void FHoudiniStringResolver::GetTokensAsStringMap(TMap& OutTokens) const +{ + for (auto& Elem : CachedTokens) + { + OutTokens.Add(Elem.Key, Elem.Value.StringValue); + } +} + +FString FHoudiniStringResolver::SanitizeTokenValue(const FString& InValue) +{ + // Replace {} characters with __ + FString OutString = InValue; + OutString.ReplaceInline(ANSI_TO_TCHAR("{"), ANSI_TO_TCHAR("__")); + OutString.ReplaceInline(ANSI_TO_TCHAR("}"), ANSI_TO_TCHAR("__")); + + return OutString; +} + +void FHoudiniStringResolver::SetToken(const FString& InName, const FString& InValue) +{ + CachedTokens.Add(InName, SanitizeTokenValue(InValue)); +} + +void FHoudiniStringResolver::SetTokensFromStringMap(const TMap& InTokens, bool bClearTokens) +{ + if (bClearTokens) + { + CachedTokens.Empty(); + } + + for (auto& Elem : InTokens) + { + CachedTokens.Add(Elem.Key, SanitizeTokenValue(Elem.Value)); + } +} + + + +FString FHoudiniStringResolver::ResolveString( + const FString& InString) const +{ + const FString Result = FString::Format(*InString, CachedTokens); + return Result; +} + +//void FHoudiniStringResolver::SetCurrentWorld(UWorld* InWorld) +//{ +// SetAttribute("world", InWorld->GetPathName()); +//} + +FString FHoudiniAttributeResolver::ResolveAttribute( + const FString& InAttrName, + const FString& InDefaultValue) const +{ + if (!CachedAttributes.Contains(InAttrName)) + { + return ResolveString(InDefaultValue); + } + FString AttrStr = CachedAttributes.FindChecked(InAttrName); + return ResolveString(AttrStr); +} + +//FString FHoudiniStringResolver::GetTempFolderArgument() const +//{ +// // The actual temp directory should have been supplied externally +// if (Tokens.Contains(TEXT("temp"))) +// return Tokens.FindChecked(TEXT("temp")); +// +// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'temp' argument. Using fallback value.")); +// return TEXT("/Game/Content/HoudiniEngine/Temp"); // Fallback value +//} +// +//FString FHoudiniStringResolver::GetBakeFolderArgument() const +//{ +// // The actual bake directory should have been supplied externally +// if (Tokens.Contains(TEXT("bake"))) +// return Tokens.FindChecked(TEXT("bake")); +// UE_LOG(LogTemp, Warning, TEXT("[GetBakeFolderArgument] Could not find 'bake' argument. Using fallback value.")); +// return TEXT("/Game/Content/HoudiniEngine/Bake"); // Fallback value +//} +// +//FString FHoudiniStringResolver::GetOutputFolderForPackageMode(EPackageMode PackageMode) const +//{ +// switch (PackageMode) +// { +// case EPackageMode::Bake: +// return GetBakeFolderArgument(); +// case EPackageMode::CookToLevel: +// case EPackageMode::CookToTemp: +// return GetTempFolderArgument(); +// } +// return ""; +//} + + void FHoudiniAttributeResolver::SetCachedAttributes(const TMap& Attributes) + { + CachedAttributes = Attributes; + } + +void FHoudiniAttributeResolver::SetAttribute(const FString& InName, const FString& InValue) +{ + CachedAttributes.Add(InName, InValue); +} + +FString FHoudiniAttributeResolver::ResolveFullLevelPath() const +{ + FString OutputFolder = TEXT("/Game/Content/HoudiniEngine/Temp"); + + const FStringFormatArg* BaseDir = CachedTokens.Find(TEXT("out_basedir")); + if (BaseDir) + OutputFolder = BaseDir->StringValue; + + FString LevelPathAttr = ResolveAttribute(HAPI_UNREAL_ATTRIB_LEVEL_PATH, TEXT("{out}")); + if (LevelPathAttr.IsEmpty()) + return OutputFolder; + + return FHoudiniEngineRuntimeUtils::JoinPaths(OutputFolder, LevelPathAttr); +} + +FString FHoudiniAttributeResolver::ResolveOutputName() const +{ + FString OutputAttribName; + + if (CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2)) + OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2; + else + OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1; + + return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); +} + +FString FHoudiniAttributeResolver::ResolveBakeFolder() const +{ + const FString DefaultBakeFolder = FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + FString BakeFolder = ResolveAttribute(HAPI_UNREAL_ATTRIB_BAKE_FOLDER, TEXT("{bake}")); + if (BakeFolder.IsEmpty()) + return DefaultBakeFolder; + + //if (BakeFolder.StartsWith("Game/")) + //{ + // BakeFolder = "/" + BakeFolder; + //} + + //FString AbsoluteOverridePath; + //if (BakeFolder.StartsWith("/Game/")) + //{ + // const FString RelativePath = FPaths::ProjectContentDir() + BakeFolder.Mid(6, BakeFolder.Len() - 6); + // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + //} + //else + //{ + // if (!BakeFolder.IsEmpty()) + // AbsoluteOverridePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*BakeFolder); + //} + + //// Check Validity of the path + //if (AbsoluteOverridePath.IsEmpty()) + //{ + // return DefaultBakeFolder; + //} + + return BakeFolder; +} + +void FHoudiniAttributeResolver::LogCachedAttributesAndTokens() const +{ + TArray Lines; + Lines.Add(TEXT("==================")); + Lines.Add(TEXT("Cached Attributes:")); + Lines.Add(TEXT("==================")); + for (const auto& Entry : CachedAttributes) + { + Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value))); + } + + Lines.Add(TEXT("==============")); + Lines.Add(TEXT("Cached Tokens:")); + Lines.Add(TEXT("==============")); + for (const auto& Entry : CachedTokens) + { + Lines.Add(FString::Printf(TEXT("%s: %s"), *(Entry.Key), *(Entry.Value.StringValue))); + } + + HOUDINI_LOG_DISPLAY(TEXT("%s"), *FString::Join(Lines, TEXT("\n"))); +} + diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.h b/Source/HoudiniEngine/Private/HoudiniStringResolver.h index e43fcc0b9..bedf2768d 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.h +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.h @@ -1,112 +1,112 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "HoudiniStringResolver.generated.h" - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniStringResolver -{ - - GENERATED_USTRUCT_BODY(); - -protected: - - // Named arguments that will be substituted into attribute values upon retrieval. - TMap CachedTokens; - - -public: - // ---------------------------------- - // Named argument accessors - // ---------------------------------- - - TMap& GetCachedTokens() { return CachedTokens; } - - - // Set a named argument that will be used for argument replacement during GetAttribute calls. - void SetToken(const FString& InName, const FString& InValue); - - // Sanitize a token value. Currently only replaces { and } with __. - static FString SanitizeTokenValue(const FString& InValue); - - void GetTokensAsStringMap(TMap& OutTokens) const; - - void SetTokensFromStringMap(const TMap& InValue, bool bClearTokens=true); - - // Resolve a string by substituting `Tokens` as named arguments during string formatting. - FString ResolveString(const FString& InStr) const; - -}; - - -USTRUCT() -struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolver -{ - GENERATED_USTRUCT_BODY(); - -protected: - TMap CachedAttributes; - -public: - - void SetCachedAttributes(const TMap& Attributes); - - // Return a mutable reference to the cached attributes. - TMap& GetCachedAttributes() { return CachedAttributes; } - - // Return immutable reference to cached attributes. - const TMap& GetCachedAttributes() const { return CachedAttributes; } - - // Set an attribute with the given name and value in the attribute cache. - void SetAttribute(const FString& InName, const FString& InValue); - - // Try to resolve an attribute with the given name. If the attribute could not be - // found, use DefaultValue as a fallback. - FString ResolveAttribute(const FString& InAttrName, const FString& InDefaultValue) const; - - // ---------------------------------- - // Helpers - // ---------------------------------- - - // Helper for resolving the `unreal_level_path` attribute. - FString ResolveFullLevelPath() const; - - // Helper for resolver custom output name attributes. - FString ResolveOutputName() const; - - // Helper for resolving the unreal_bake_folder attribute. Converts to an absolute path. - FString ResolveBakeFolder() const; - - // ---------------------------------- - // Debug - // ---------------------------------- - // Logs the resolver's cached attributes and tokens - void LogCachedAttributesAndTokens() const; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniStringResolver.generated.h" + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniStringResolver +{ + + GENERATED_USTRUCT_BODY(); + +protected: + + // Named arguments that will be substituted into attribute values upon retrieval. + TMap CachedTokens; + + +public: + // ---------------------------------- + // Named argument accessors + // ---------------------------------- + + TMap& GetCachedTokens() { return CachedTokens; } + + + // Set a named argument that will be used for argument replacement during GetAttribute calls. + void SetToken(const FString& InName, const FString& InValue); + + // Sanitize a token value. Currently only replaces { and } with __. + static FString SanitizeTokenValue(const FString& InValue); + + void GetTokensAsStringMap(TMap& OutTokens) const; + + void SetTokensFromStringMap(const TMap& InValue, bool bClearTokens=true); + + // Resolve a string by substituting `Tokens` as named arguments during string formatting. + FString ResolveString(const FString& InStr) const; + +}; + + +USTRUCT() +struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolver +{ + GENERATED_USTRUCT_BODY(); + +protected: + TMap CachedAttributes; + +public: + + void SetCachedAttributes(const TMap& Attributes); + + // Return a mutable reference to the cached attributes. + TMap& GetCachedAttributes() { return CachedAttributes; } + + // Return immutable reference to cached attributes. + const TMap& GetCachedAttributes() const { return CachedAttributes; } + + // Set an attribute with the given name and value in the attribute cache. + void SetAttribute(const FString& InName, const FString& InValue); + + // Try to resolve an attribute with the given name. If the attribute could not be + // found, use DefaultValue as a fallback. + FString ResolveAttribute(const FString& InAttrName, const FString& InDefaultValue) const; + + // ---------------------------------- + // Helpers + // ---------------------------------- + + // Helper for resolving the `unreal_level_path` attribute. + FString ResolveFullLevelPath() const; + + // Helper for resolver custom output name attributes. + FString ResolveOutputName() const; + + // Helper for resolving the unreal_bake_folder attribute. Converts to an absolute path. + FString ResolveBakeFolder() const; + + // ---------------------------------- + // Debug + // ---------------------------------- + // Logs the resolver's cached attributes and tokens + void LogCachedAttributesAndTokens() const; }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp b/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp index 28eded86a..1fd5162eb 100644 --- a/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp +++ b/Source/HoudiniEngine/Private/SAssetSelectionWidget.cpp @@ -1,159 +1,159 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "SAssetSelectionWidget.h" - -#include "HoudiniEngineString.h" - -#if WITH_EDITOR - -#include "EditorStyleSet.h" -#include "Widgets/Layout/SBorder.h" -#include "Widgets/Input/SButton.h" - - -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 WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - -FReply -SAssetSelectionWidget::OnButtonOk() -{ - if (TSharedPtr WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - -FReply -SAssetSelectionWidget::OnButtonCancel() -{ - bIsCancelled = true; - - if (TSharedPtr WindowPtr = WidgetWindow.Pin()) - { - WindowPtr->HideWindow(); - WindowPtr->RequestDestroyWindow(); - } - - return FReply::Handled(); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "SAssetSelectionWidget.h" + +#include "HoudiniEngineString.h" + +#if WITH_EDITOR + +#include "EditorStyleSet.h" +#include "Widgets/Layout/SBorder.h" +#include "Widgets/Input/SButton.h" + + +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 WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply +SAssetSelectionWidget::OnButtonOk() +{ + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + +FReply +SAssetSelectionWidget::OnButtonCancel() +{ + bIsCancelled = true; + + if (TSharedPtr WindowPtr = WidgetWindow.Pin()) + { + WindowPtr->HideWindow(); + WindowPtr->RequestDestroyWindow(); + } + + return FReply::Handled(); +} + #endif \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/SAssetSelectionWidget.h b/Source/HoudiniEngine/Private/SAssetSelectionWidget.h index 2d6cd3e5d..66f2c39e6 100644 --- a/Source/HoudiniEngine/Private/SAssetSelectionWidget.h +++ b/Source/HoudiniEngine/Private/SAssetSelectionWidget.h @@ -1,100 +1,100 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#if WITH_EDITOR - -#include "CoreMinimal.h" -//#include "Misc/Attribute.h" - -#include "Widgets/SWidget.h" -#include "Widgets/SCompoundWidget.h" -#include "Widgets/SWindow.h" -#include "Widgets/DeclarativeSyntaxSupport.h" - -#include "HAPI/HAPI_Common.h" - -/** 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, 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; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#if WITH_EDITOR + +#include "CoreMinimal.h" +//#include "Misc/Attribute.h" + +#include "Widgets/SWidget.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWindow.h" +#include "Widgets/DeclarativeSyntaxSupport.h" + +#include "HAPI/HAPI_Common.h" + +/** 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, 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; +}; + #endif \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp index 0cfc6e9ce..75d4f1dc2 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp @@ -1,448 +1,448 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealBrushTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniInputObject.h" - -#include "HoudiniGeoPartObject.h" -#include "Model.h" -#include "Engine/Polys.h" - -#include "HoudiniEngineRuntimeUtils.h" - -// Includes for Brush building code. Remove when the code is in the correct place. -#include "HCsgUtils.h" -#include "ActorEditorUtils.h" -#include "Misc/ScopedSlowTask.h" - -#include "Engine/Level.h" - -// TODO: Fix this -// This is currently being included to get access to the CreateFaceMaterialArray / DeleteFaceMaterialArray methods. -#include "UnrealMeshTranslator.h" - -DEFINE_LOG_CATEGORY_STATIC(LogBrushTranslator, Log, All); - -bool FUnrealBrushTranslator::CreateInputNodeForBrush( - UHoudiniInputBrush* InputBrushObject, - ABrush* BrushActor, - const TArray* ExcludeActors, - HAPI_NodeId &CreatedNodeId, - const FString& NodeName -) -{ - if (!IsValid(BrushActor)) - return false; - - if (!IsValid(BrushActor->Brush)) - return false; - - if (InputBrushObject->ShouldIgnoreThisInput()) - return true; - - //-------------------------------------------------------------------------------------------------- - // Create an input node - //-------------------------------------------------------------------------------------------------- - - HAPI_NodeId ParentNodeId = -1; - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CreatedNodeId)) - { - HAPI_NodeId InputNodeId = -1; - //FString BrushNodeName = NodeName + TEXT("_") + BrushActor->GetName(); - FString BrushNodeName = BrushActor->GetName(); - // Create Brush SOP node - std::string NodeNameRawString; - FHoudiniEngineUtils::ConvertUnrealString(BrushNodeName, NodeNameRawString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - return false; - - // We now have a valid id. - CreatedNodeId = InputNodeId; - ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); - - // Add a clean node - HAPI_NodeId CleanNodeId; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - ParentNodeId, TEXT("clean"), TEXT("clean"), true, &CleanNodeId), false); - - // Connect input node to the clean node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CleanNodeId, 0, CreatedNodeId, 0), false); - - // Set display flag on the clean node - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetNodeDisplay( - FHoudiniEngine::Get().GetSession(), CleanNodeId, 1), false); - } - else - { - ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); - } - - - // Transform for positions - const FTransform ActorTransform = BrushActor->GetActorTransform(); - const FTransform ActorTransformInverse = ActorTransform.Inverse(); - // Precompute matrices for Normal transformations (see FPlane::TransformBy) - const FMatrix NmlInvXform = ActorTransformInverse.ToMatrixWithScale().TransposeAdjoint(); - float NScale = ActorTransformInverse.GetDeterminant() < 0 ? -1.f : 1.f; - - //-------------------------------------------------------------------------------------------------- - // Find actors that intersect with the given brush. - //-------------------------------------------------------------------------------------------------- - TArray BrushActors; - UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(InputBrushObject, BrushActors); - - UModel* BrushModel = UHCsgUtils::BuildModelFromBrushes(BrushActors); - InputBrushObject->UpdateCachedData(BrushModel, BrushActors); - - // DEBUG: Upload the level model (baked by UE) to Houdini - // ULevel* Level = BrushActor->GetTypedOuter(); - // BrushModel = Level->Model; - - - int NumPoints = BrushModel->Points.Num(); - if (NumPoints == 0) - { - // The content has changed and now we don't have geo to output. - // Be sure to clean up existing nodes in Houdini. - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), ParentNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *(BrushActor->GetName())); - } - CreatedNodeId = -1; - return true; - } - - //-------------------------------------------------------------------------------------------------- - // Construct the face count buffer. Also count the vertex indices in required to define the Part. - //-------------------------------------------------------------------------------------------------- - - int NumIndices = 0; - TArray FaceCountBuffer; - - { - // Calculate the size of the vertex buffer and the base vertex index of each node. - TArray& Nodes = BrushModel->Nodes; - //TArray& Surfs = BrushModel->Surfs; - TArray& Verts = BrushModel->Verts; - - int32 NumNodes = Nodes.Num(); - - FaceCountBuffer.SetNumUninitialized(NumNodes); - // Build the face counts buffer by iterating over the BSP nodes. - for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) - { - FBspNode& Node = Nodes[NodeIndex]; - FaceCountBuffer[NodeIndex] = Node.NumVertices; - NumIndices += Node.NumVertices; - } - } - - //-------------------------------------------------------------------------------------------------- - // Apply actor transform - //-------------------------------------------------------------------------------------------------- - - if (!ActorTransform.Equals(FTransform::Identity)) - { - - // convert to HAPI_Transform - HAPI_TransformEuler HapiTransform; - FHoudiniApi::TransformEuler_Init(&HapiTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(ActorTransform, HapiTransform); - - // Set the transform on the OBJ parent - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( - FHoudiniEngine::Get().GetSession(), ParentNodeId, &HapiTransform), false); - } - - //-------------------------------------------------------------------------------------------------- - // Start processing the geo and add it to the input node - //-------------------------------------------------------------------------------------------------- - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumIndices; - Part.faceCount = FaceCountBuffer.Num(); - Part.pointCount = NumPoints; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, &Part), false); - - // ----------------------------- - // Vector - Point Attribute Info - // ----------------------------- - HAPI_AttributeInfo AttributeInfoPointVector; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointVector); - AttributeInfoPointVector.count = NumPoints; - AttributeInfoPointVector.tupleSize = 3; - AttributeInfoPointVector.exists = true; - AttributeInfoPointVector.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointVector.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointVector.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector), false); - - // ----------------------------- - // Vector - Vertex Attribute Info - // ----------------------------- - HAPI_AttributeInfo AttributeInfoVertexVector; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertexVector); - AttributeInfoVertexVector.count = NumIndices; - AttributeInfoVertexVector.tupleSize = 3; - AttributeInfoVertexVector.exists = true; - AttributeInfoVertexVector.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertexVector.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertexVector.originalOwner = HAPI_ATTROWNER_INVALID; - - // ----------------------------- - // POSITION (P) - // ----------------------------- - - { - TArray< FVector > OutPosition; - FVector Scale = FVector(1.f, 1.f, 1.f); // TODO: Extract from actor transform. - OutPosition.SetNumUninitialized(NumPoints); - - for (int32 PosIndex = 0; PosIndex < NumPoints; ++PosIndex) - { - FVector Point = BrushModel->Points[PosIndex]; - Point = ActorTransformInverse.TransformPosition(Point); - FVector Pos(Point.X, Point.Z, Point.Y); - OutPosition[PosIndex] = Pos/HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - - // Upload point positions. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector, - (const float *)OutPosition.GetData(), 0, AttributeInfoPointVector.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES (VertexList), NORMALS, UVS - //--------------------------------------------------------------------------------------------------------------------- - // Vertex buffer processing logic based on UModel::BuildVertexBuffers(). - { - // Calculate the size of the vertex buffer and the base vertex index of each node. - TArray& Positions = BrushModel->Points; - TArray& Nodes = BrushModel->Nodes; - TArray& Surfs = BrushModel->Surfs; - TArray& Verts = BrushModel->Verts; - TArray& Vectors = BrushModel->Vectors; - - int32 NumNodes = Nodes.Num(); - TArray Indices; - TArray OutNormals; - TArray OutUV; - TArray MaterialIndices; - TMap MaterialMap; - - Indices.SetNumUninitialized(NumIndices); - OutNormals.SetNumUninitialized(NumIndices); - OutUV.SetNumUninitialized(NumIndices); - - MaterialIndices.SetNumUninitialized(NumNodes); - - // Populate the vertex index buffer. - int32 iVertex = 0; - for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) - { - FBspNode& Node = Nodes[NodeIndex]; - FBspSurf& Surf = Surfs[Node.iSurf]; - for (int32 NodeVertexIndex = 0; NodeVertexIndex < Node.NumVertices; ++NodeVertexIndex) - { - // Vertex Index - Indices[iVertex] = Verts[Node.iVertPool + NodeVertexIndex].pVertex; - // Normal - FVector N = Vectors[Surf.vNormal]; - N = NmlInvXform.TransformVector(N).GetSafeNormal(); - - OutNormals[iVertex] = FVector(N.X, N.Z, N.Y); - // UVs - FVector& vU = Vectors[Surf.vTextureU]; - FVector& vV = Vectors[Surf.vTextureV]; - FVector deltaVtx = (Positions[Indices[iVertex]] - Positions[Surf.pBase]); - float U = FVector::DotProduct(deltaVtx, vU) / UModel::GetGlobalBSPTexelScale(); - float V = -FVector::DotProduct(deltaVtx, vV) / UModel::GetGlobalBSPTexelScale(); - OutUV[iVertex] = FVector(U, V, 0.f); - ++iVertex; - } - // Face Material - // Construct a material index array for the faces - int32 MaterialIndex = MaterialMap.FindOrAdd(Surf.Material, MaterialMap.Num()); - MaterialIndices[NodeIndex] = MaterialIndex; - } - - // Set the vertex index buffer - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, Indices.GetData(), 0, NumIndices), false); - - // Set the face counts as per the BSP nodes. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, FaceCountBuffer.GetData(), 0, FaceCountBuffer.Num()), false); - - // ----------------------------- - // Normal attribute - // ----------------------------- - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertexVector), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertexVector, (const float *)OutNormals.GetData(), - 0, AttributeInfoVertexVector.count), false); - - // ----------------------------- - // UV attribute - // ----------------------------- - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, - HAPI_UNREAL_ATTRIB_UV, &AttributeInfoVertexVector), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_UV, - &AttributeInfoVertexVector, (const float *)OutUV.GetData(), - 0, AttributeInfoVertexVector.count), false); - - // ----------------------------- - // Material attribute - // ----------------------------- - - TArray Materials; - - if (MaterialMap.Num() > 0) - { - // Sort the output material in the correct order (by material index) - MaterialMap.ValueSort([](int32 A, int32 B) { return A < B; }); - - // Set the value in the correct order - // Do not reduce the array of materials, this could cause crahses in some weird cases.. - /*if (MaterialMap.Num() > MaterialInterfaces.Num()) - MaterialInterfaces.SetNumZeroed(MaterialMap.Num());*/ - Materials.SetNumUninitialized(MaterialMap.Num()); - - int32 MaterialIndex = 0; - for (auto Kvp : MaterialMap) - { - Materials[MaterialIndex++] = Kvp.Key; - } - } - - // List of materials, one for each face. - TArray OutMaterials; - - //Lists of material parameters - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - bool bAttributeSuccess = false; - bool bAddMaterialParametersAsAttributes = false; - - if (bAddMaterialParametersAsAttributes) - { - // Create attributes for the material and all its parameters - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - Materials, MaterialIndices, OutMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - CreatedNodeId, - 0, - NumNodes, - OutMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - else - { - // Create attributes only for the materials - // Only get the material attribute data - FUnrealMeshTranslator::CreateFaceMaterialArray( - Materials, MaterialIndices, OutMaterials); - - // Create attribute for materials - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - CreatedNodeId, - 0, - NumNodes, - OutMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(OutMaterials); - - // Delete texture material parameter names. - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedNodeId), false ); - - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealBrushTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniInputObject.h" + +#include "HoudiniGeoPartObject.h" +#include "Model.h" +#include "Engine/Polys.h" + +#include "HoudiniEngineRuntimeUtils.h" + +// Includes for Brush building code. Remove when the code is in the correct place. +#include "HCsgUtils.h" +#include "ActorEditorUtils.h" +#include "Misc/ScopedSlowTask.h" + +#include "Engine/Level.h" + +// TODO: Fix this +// This is currently being included to get access to the CreateFaceMaterialArray / DeleteFaceMaterialArray methods. +#include "UnrealMeshTranslator.h" + +DEFINE_LOG_CATEGORY_STATIC(LogBrushTranslator, Log, All); + +bool FUnrealBrushTranslator::CreateInputNodeForBrush( + UHoudiniInputBrush* InputBrushObject, + ABrush* BrushActor, + const TArray* ExcludeActors, + HAPI_NodeId &CreatedNodeId, + const FString& NodeName +) +{ + if (!IsValid(BrushActor)) + return false; + + if (!IsValid(BrushActor->Brush)) + return false; + + if (InputBrushObject->ShouldIgnoreThisInput()) + return true; + + //-------------------------------------------------------------------------------------------------- + // Create an input node + //-------------------------------------------------------------------------------------------------- + + HAPI_NodeId ParentNodeId = -1; + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(CreatedNodeId)) + { + HAPI_NodeId InputNodeId = -1; + //FString BrushNodeName = NodeName + TEXT("_") + BrushActor->GetName(); + FString BrushNodeName = BrushActor->GetName(); + // Create Brush SOP node + std::string NodeNameRawString; + FHoudiniEngineUtils::ConvertUnrealString(BrushNodeName, NodeNameRawString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + return false; + + // We now have a valid id. + CreatedNodeId = InputNodeId; + ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); + + // Add a clean node + HAPI_NodeId CleanNodeId; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + ParentNodeId, TEXT("clean"), TEXT("clean"), true, &CleanNodeId), false); + + // Connect input node to the clean node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CleanNodeId, 0, CreatedNodeId, 0), false); + + // Set display flag on the clean node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetNodeDisplay( + FHoudiniEngine::Get().GetSession(), CleanNodeId, 1), false); + } + else + { + ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CreatedNodeId); + } + + + // Transform for positions + const FTransform ActorTransform = BrushActor->GetActorTransform(); + const FTransform ActorTransformInverse = ActorTransform.Inverse(); + // Precompute matrices for Normal transformations (see FPlane::TransformBy) + const FMatrix NmlInvXform = ActorTransformInverse.ToMatrixWithScale().TransposeAdjoint(); + float NScale = ActorTransformInverse.GetDeterminant() < 0 ? -1.f : 1.f; + + //-------------------------------------------------------------------------------------------------- + // Find actors that intersect with the given brush. + //-------------------------------------------------------------------------------------------------- + TArray BrushActors; + UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(InputBrushObject, BrushActors); + + UModel* BrushModel = UHCsgUtils::BuildModelFromBrushes(BrushActors); + InputBrushObject->UpdateCachedData(BrushModel, BrushActors); + + // DEBUG: Upload the level model (baked by UE) to Houdini + // ULevel* Level = BrushActor->GetTypedOuter(); + // BrushModel = Level->Model; + + + int NumPoints = BrushModel->Points.Num(); + if (NumPoints == 0) + { + // The content has changed and now we don't have geo to output. + // Be sure to clean up existing nodes in Houdini. + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), ParentNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *(BrushActor->GetName())); + } + CreatedNodeId = -1; + return true; + } + + //-------------------------------------------------------------------------------------------------- + // Construct the face count buffer. Also count the vertex indices in required to define the Part. + //-------------------------------------------------------------------------------------------------- + + int NumIndices = 0; + TArray FaceCountBuffer; + + { + // Calculate the size of the vertex buffer and the base vertex index of each node. + TArray& Nodes = BrushModel->Nodes; + //TArray& Surfs = BrushModel->Surfs; + TArray& Verts = BrushModel->Verts; + + int32 NumNodes = Nodes.Num(); + + FaceCountBuffer.SetNumUninitialized(NumNodes); + // Build the face counts buffer by iterating over the BSP nodes. + for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) + { + FBspNode& Node = Nodes[NodeIndex]; + FaceCountBuffer[NodeIndex] = Node.NumVertices; + NumIndices += Node.NumVertices; + } + } + + //-------------------------------------------------------------------------------------------------- + // Apply actor transform + //-------------------------------------------------------------------------------------------------- + + if (!ActorTransform.Equals(FTransform::Identity)) + { + + // convert to HAPI_Transform + HAPI_TransformEuler HapiTransform; + FHoudiniApi::TransformEuler_Init(&HapiTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(ActorTransform, HapiTransform); + + // Set the transform on the OBJ parent + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetObjectTransform( + FHoudiniEngine::Get().GetSession(), ParentNodeId, &HapiTransform), false); + } + + //-------------------------------------------------------------------------------------------------- + // Start processing the geo and add it to the input node + //-------------------------------------------------------------------------------------------------- + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumIndices; + Part.faceCount = FaceCountBuffer.Num(); + Part.pointCount = NumPoints; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, &Part), false); + + // ----------------------------- + // Vector - Point Attribute Info + // ----------------------------- + HAPI_AttributeInfo AttributeInfoPointVector; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointVector); + AttributeInfoPointVector.count = NumPoints; + AttributeInfoPointVector.tupleSize = 3; + AttributeInfoPointVector.exists = true; + AttributeInfoPointVector.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointVector.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointVector.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector), false); + + // ----------------------------- + // Vector - Vertex Attribute Info + // ----------------------------- + HAPI_AttributeInfo AttributeInfoVertexVector; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertexVector); + AttributeInfoVertexVector.count = NumIndices; + AttributeInfoVertexVector.tupleSize = 3; + AttributeInfoVertexVector.exists = true; + AttributeInfoVertexVector.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertexVector.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertexVector.originalOwner = HAPI_ATTROWNER_INVALID; + + // ----------------------------- + // POSITION (P) + // ----------------------------- + + { + TArray< FVector > OutPosition; + FVector Scale = FVector(1.f, 1.f, 1.f); // TODO: Extract from actor transform. + OutPosition.SetNumUninitialized(NumPoints); + + for (int32 PosIndex = 0; PosIndex < NumPoints; ++PosIndex) + { + FVector Point = BrushModel->Points[PosIndex]; + Point = ActorTransformInverse.TransformPosition(Point); + FVector Pos(Point.X, Point.Z, Point.Y); + OutPosition[PosIndex] = Pos/HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + // Upload point positions. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointVector, + (const float *)OutPosition.GetData(), 0, AttributeInfoPointVector.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES (VertexList), NORMALS, UVS + //--------------------------------------------------------------------------------------------------------------------- + // Vertex buffer processing logic based on UModel::BuildVertexBuffers(). + { + // Calculate the size of the vertex buffer and the base vertex index of each node. + TArray& Positions = BrushModel->Points; + TArray& Nodes = BrushModel->Nodes; + TArray& Surfs = BrushModel->Surfs; + TArray& Verts = BrushModel->Verts; + TArray& Vectors = BrushModel->Vectors; + + int32 NumNodes = Nodes.Num(); + TArray Indices; + TArray OutNormals; + TArray OutUV; + TArray MaterialIndices; + TMap MaterialMap; + + Indices.SetNumUninitialized(NumIndices); + OutNormals.SetNumUninitialized(NumIndices); + OutUV.SetNumUninitialized(NumIndices); + + MaterialIndices.SetNumUninitialized(NumNodes); + + // Populate the vertex index buffer. + int32 iVertex = 0; + for(int32 NodeIndex = 0; NodeIndex < NumNodes; NodeIndex++) + { + FBspNode& Node = Nodes[NodeIndex]; + FBspSurf& Surf = Surfs[Node.iSurf]; + for (int32 NodeVertexIndex = 0; NodeVertexIndex < Node.NumVertices; ++NodeVertexIndex) + { + // Vertex Index + Indices[iVertex] = Verts[Node.iVertPool + NodeVertexIndex].pVertex; + // Normal + FVector N = Vectors[Surf.vNormal]; + N = NmlInvXform.TransformVector(N).GetSafeNormal(); + + OutNormals[iVertex] = FVector(N.X, N.Z, N.Y); + // UVs + FVector& vU = Vectors[Surf.vTextureU]; + FVector& vV = Vectors[Surf.vTextureV]; + FVector deltaVtx = (Positions[Indices[iVertex]] - Positions[Surf.pBase]); + float U = FVector::DotProduct(deltaVtx, vU) / UModel::GetGlobalBSPTexelScale(); + float V = -FVector::DotProduct(deltaVtx, vV) / UModel::GetGlobalBSPTexelScale(); + OutUV[iVertex] = FVector(U, V, 0.f); + ++iVertex; + } + // Face Material + // Construct a material index array for the faces + int32 MaterialIndex = MaterialMap.FindOrAdd(Surf.Material, MaterialMap.Num()); + MaterialIndices[NodeIndex] = MaterialIndex; + } + + // Set the vertex index buffer + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, Indices.GetData(), 0, NumIndices), false); + + // Set the face counts as per the BSP nodes. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, FaceCountBuffer.GetData(), 0, FaceCountBuffer.Num()), false); + + // ----------------------------- + // Normal attribute + // ----------------------------- + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertexVector), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertexVector, (const float *)OutNormals.GetData(), + 0, AttributeInfoVertexVector.count), false); + + // ----------------------------- + // UV attribute + // ----------------------------- + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, 0, + HAPI_UNREAL_ATTRIB_UV, &AttributeInfoVertexVector), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + CreatedNodeId, 0, HAPI_UNREAL_ATTRIB_UV, + &AttributeInfoVertexVector, (const float *)OutUV.GetData(), + 0, AttributeInfoVertexVector.count), false); + + // ----------------------------- + // Material attribute + // ----------------------------- + + TArray Materials; + + if (MaterialMap.Num() > 0) + { + // Sort the output material in the correct order (by material index) + MaterialMap.ValueSort([](int32 A, int32 B) { return A < B; }); + + // Set the value in the correct order + // Do not reduce the array of materials, this could cause crahses in some weird cases.. + /*if (MaterialMap.Num() > MaterialInterfaces.Num()) + MaterialInterfaces.SetNumZeroed(MaterialMap.Num());*/ + Materials.SetNumUninitialized(MaterialMap.Num()); + + int32 MaterialIndex = 0; + for (auto Kvp : MaterialMap) + { + Materials[MaterialIndex++] = Kvp.Key; + } + } + + // List of materials, one for each face. + TArray OutMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + Materials, MaterialIndices, OutMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + CreatedNodeId, + 0, + NumNodes, + OutMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + Materials, MaterialIndices, OutMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + CreatedNodeId, + 0, + NumNodes, + OutMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(OutMaterials); + + // Delete texture material parameter names. + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedNodeId), false ); + + + return true; +} + diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.h b/Source/HoudiniEngine/Private/UnrealBrushTranslator.h index b98197392..1d285570f 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.h @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "UObject/NameTypes.h" - -class ABrush; -class AActor; -class UModel; -class UHoudiniInputBrush; -class ABrush; -class AActor; - -struct HOUDINIENGINE_API FUnrealBrushTranslator -{ -public: - static bool CreateInputNodeForBrush( - UHoudiniInputBrush* InputBrushObject, - ABrush* BrushActor, - const TArray* ExcludeActors, - HAPI_NodeId &CreatedNodeId, - const FString& NodeName - ); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "UObject/NameTypes.h" + +class ABrush; +class AActor; +class UModel; +class UHoudiniInputBrush; +class ABrush; +class AActor; + +struct HOUDINIENGINE_API FUnrealBrushTranslator +{ +public: + static bool CreateInputNodeForBrush( + UHoudiniInputBrush* InputBrushObject, + ABrush* BrushActor, + const TArray* ExcludeActors, + HAPI_NodeId &CreatedNodeId, + const FString& NodeName + ); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp index b3c464e20..9b3ce7d63 100644 --- a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp @@ -1,289 +1,289 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealFoliageTypeTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "FoliageType_InstancedStaticMesh.h" -#include "HoudiniGenericAttribute.h" -#include "HoudiniInputTranslator.h" - -#include "Engine/StaticMesh.h" -#include "Components/StaticMeshComponent.h" - - -bool -FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - UFoliageType_InstancedStaticMesh* InFoliageType, - HAPI_NodeId& InputObjectNodeId, - const FString& InputNodeName, - const bool& ExportAllLODs, - const bool& ExportSockets, - const bool& ExportColliders) -{ - if (!IsValid(InFoliageType)) - return false; - - UStaticMesh* const InputSM = InFoliageType->GetStaticMesh(); - if (!IsValid(InputSM)) - return false; - - UStaticMeshComponent* const StaticMeshComponent = nullptr; - bool bSuccess = HapiCreateInputNodeForStaticMesh( - InputSM, - InputObjectNodeId, - InputNodeName, - StaticMeshComponent, - ExportAllLODs, - ExportSockets, - ExportColliders); - - if (bSuccess) - { - const int32 PartId = 0; - CreateHoudiniFoliageTypeAttributes(InFoliageType, InputObjectNodeId, PartId, HAPI_ATTROWNER_DETAIL); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InputObjectNodeId), false); - } - - return bSuccess; -} - -bool FUnrealFoliageTypeTranslator::CreateInputNodeForReference( - UFoliageType* InFoliageType, - HAPI_NodeId& InInputNodeId, - const FString& InRef, - const FString& InInputNodeName, - const FTransform& InTransform) -{ - bool bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InInputNodeId, InRef, InInputNodeName, InTransform); - if (!bSuccess) - return false; - - const int32 PartId = 0; - if (CreateHoudiniFoliageTypeAttributes(InFoliageType, InInputNodeId, PartId, HAPI_ATTROWNER_POINT)) - { - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InInputNodeId), false); - return true; - } - - return false; -} - -bool -FUnrealFoliageTypeTranslator::CreateHoudiniFoliageTypeAttributes(UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner) -{ - if (InNodeId < 0) - return false; - - bool bSuccess = true; - - // Create attribute for unreal_foliage - HAPI_AttributeInfo AttributeInfoUnrealFoliage; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoUnrealFoliage); - AttributeInfoUnrealFoliage.tupleSize = 1; - AttributeInfoUnrealFoliage.count = 1; - AttributeInfoUnrealFoliage.exists = true; - AttributeInfoUnrealFoliage.owner = InAttributeOwner; - AttributeInfoUnrealFoliage.storage = HAPI_STORAGETYPE_INT; - AttributeInfoUnrealFoliage.originalOwner = HAPI_ATTROWNER_INVALID; - - // Create the new attribute - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage)) - { - // The New attribute has been successfully created, set its value - int UnrealFoliage = 1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage, - &UnrealFoliage, 0, 1)) - { - bSuccess = false; - } - } - - if (!bSuccess) - return false; - - // Foliage type properties that should be sent to Houdini as unreal_uproperty_ attributes. - static TArray FoliageTypePropertyNames({ - // float - // FName("Density"), - // FName("DensityAdjustmentFactor"), - // FName("Radius"), - // FName("SingleInstanceModeRadius"), - FName("AlignMaxAngle"), - FName("RandomPitchAngle"), - FName("MinimumLayerWeight"), - FName("MinimumExclusionLayerWeight"), - FName("CollisionRadius"), - FName("ShadeRadius"), - FName("InitialSeedDensity"), - FName("AverageSpreadDistance"), - FName("SpreadVariance"), - FName("MaxInitialSeedOffset"), - FName("MaxInitialAge"), - FName("MaxAge"), - FName("OverlapPriority"), - - // bool - // FName("bSingleInstanceModeOverrideRadius"), - FName("bCanGrowInShade"), - FName("bSpawnsInShade"), - - // int32 - FName("OverriddenLightMapRes"), - FName("CustomDepthStencilValue"), - FName("TranslucencySortPriority"), - FName("NumSteps"), - FName("SeedsPerStep"), - FName("DistributionSeed"), - FName("ChangeCount"), - FName("VirtualTextureCullMips"), - - // uint32 - FName("AlignToNormal"), - FName("RandomYaw"), - FName("CollisionWithWorld"), - FName("CastShadow"), - FName("bAffectDynamicIndirectLighting"), - FName("bAffectDistanceFieldLighting"), - FName("bCastDynamicShadow"), - FName("bCastStaticShadow"), - FName("bCastShadowAsTwoSided"), - FName("bReceivesDecals"), - FName("bOverrideLightMapRes"), - FName("bUseAsOccluder"), - FName("bRenderCustomDepth"), - FName("ReapplyDensity"), - FName("ReapplyRadius"), - FName("ReapplyAlignToNormal"), - FName("ReapplyRandomYaw"), - FName("ReapplyScaling"), - FName("ReapplyScaleX"), - FName("ReapplyScaleY"), - FName("ReapplyScaleZ"), - FName("ReapplyRandomPitchAngle"), - FName("ReapplyGroundSlope"), - FName("ReapplyHeight"), - FName("ReapplyLandscapeLayers"), - FName("ReapplyZOffset"), - FName("ReapplyCollisionWithWorld"), - FName("ReapplyVertexColorMask"), - FName("bEnableDensityScaling"), - FName("bEnableDiscardOnLoad"), - - // enums - // FName("Scaling"), - FName("LightmapType"), - - // FFloatInterval - // FName("ScaleX"), - // FName("ScaleY"), - // FName("ScaleZ"), - FName("ZOffset"), - FName("GroundSlopeAngle"), - FName("Height"), - FName("ProceduralScale"), - - // FVector - FName("CollisionScale"), - FName("LowBoundOriginRadius")}); - - EAttribOwner AttribOwner; - switch (InAttributeOwner) - { - case HAPI_ATTROWNER_POINT: - AttribOwner = EAttribOwner::Point; - break; - case HAPI_ATTROWNER_VERTEX: - AttribOwner = EAttribOwner::Vertex; - break; - case HAPI_ATTROWNER_PRIM: - AttribOwner = EAttribOwner::Prim; - break; - case HAPI_ATTROWNER_DETAIL: - AttribOwner = EAttribOwner::Detail; - break; - case HAPI_ATTROWNER_INVALID: - case HAPI_ATTROWNER_MAX: - default: - HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InAttributeOwner); - return false; - } - FHoudiniGenericAttribute GenericAttribute; - GenericAttribute.AttributeCount = 1; - GenericAttribute.AttributeOwner = AttribOwner; - - // Reserve enough space in the arrays (we only have a single point (or all are detail attributes), so attribute - // count is 1, but the tuple size could be up to 10 for transforms - GenericAttribute.DoubleValues.Reserve(10); - GenericAttribute.IntValues.Reserve(10); - GenericAttribute.StringValues.Reserve(10); - - for (const FName& PropertyName : FoliageTypePropertyNames) - { - const FString PropertyNameStr = PropertyName.ToString(); - GenericAttribute.AttributeName = FString::Printf(TEXT("unreal_uproperty_%s"), *PropertyNameStr); - // Find the property on the foliage type instance - FProperty* FoundProperty = nullptr; - UObject* FoundPropertyObject = nullptr; - void* Container = nullptr; - if (!FHoudiniGenericAttribute::FindPropertyOnObject( - InFoliageType, - PropertyNameStr, - FoundProperty, - FoundPropertyObject, - Container)) - continue; - - if (!FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( - InFoliageType, - FoundProperty, - Container, - GenericAttribute.AttributeTupleSize, - GenericAttribute.AttributeType)) - continue; - - const int32 AtIndex = 0; - if (!FHoudiniGenericAttribute::GetPropertyValueFromObject( - InFoliageType, - FoundProperty, - Container, - GenericAttribute, - AtIndex)) - continue; - - FHoudiniEngineUtils::SetGenericPropertyAttribute(InNodeId, InPartId, GenericAttribute); - } - - return bSuccess; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealFoliageTypeTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "FoliageType_InstancedStaticMesh.h" +#include "HoudiniGenericAttribute.h" +#include "HoudiniInputTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" + + +bool +FUnrealFoliageTypeTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + UFoliageType_InstancedStaticMesh* InFoliageType, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + const bool& ExportAllLODs, + const bool& ExportSockets, + const bool& ExportColliders) +{ + if (!IsValid(InFoliageType)) + return false; + + UStaticMesh* const InputSM = InFoliageType->GetStaticMesh(); + if (!IsValid(InputSM)) + return false; + + UStaticMeshComponent* const StaticMeshComponent = nullptr; + bool bSuccess = HapiCreateInputNodeForStaticMesh( + InputSM, + InputObjectNodeId, + InputNodeName, + StaticMeshComponent, + ExportAllLODs, + ExportSockets, + ExportColliders); + + if (bSuccess) + { + const int32 PartId = 0; + CreateHoudiniFoliageTypeAttributes(InFoliageType, InputObjectNodeId, PartId, HAPI_ATTROWNER_DETAIL); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InputObjectNodeId), false); + } + + return bSuccess; +} + +bool FUnrealFoliageTypeTranslator::CreateInputNodeForReference( + UFoliageType* InFoliageType, + HAPI_NodeId& InInputNodeId, + const FString& InRef, + const FString& InInputNodeName, + const FTransform& InTransform) +{ + bool bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InInputNodeId, InRef, InInputNodeName, InTransform); + if (!bSuccess) + return false; + + const int32 PartId = 0; + if (CreateHoudiniFoliageTypeAttributes(InFoliageType, InInputNodeId, PartId, HAPI_ATTROWNER_POINT)) + { + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InInputNodeId), false); + return true; + } + + return false; +} + +bool +FUnrealFoliageTypeTranslator::CreateHoudiniFoliageTypeAttributes(UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner) +{ + if (InNodeId < 0) + return false; + + bool bSuccess = true; + + // Create attribute for unreal_foliage + HAPI_AttributeInfo AttributeInfoUnrealFoliage; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoUnrealFoliage); + AttributeInfoUnrealFoliage.tupleSize = 1; + AttributeInfoUnrealFoliage.count = 1; + AttributeInfoUnrealFoliage.exists = true; + AttributeInfoUnrealFoliage.owner = InAttributeOwner; + AttributeInfoUnrealFoliage.storage = HAPI_STORAGETYPE_INT; + AttributeInfoUnrealFoliage.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage)) + { + // The New attribute has been successfully created, set its value + int UnrealFoliage = 1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + InNodeId, InPartId, HAPI_UNREAL_ATTRIB_FOLIAGE_INSTANCER, &AttributeInfoUnrealFoliage, + &UnrealFoliage, 0, 1)) + { + bSuccess = false; + } + } + + if (!bSuccess) + return false; + + // Foliage type properties that should be sent to Houdini as unreal_uproperty_ attributes. + static TArray FoliageTypePropertyNames({ + // float + // FName("Density"), + // FName("DensityAdjustmentFactor"), + // FName("Radius"), + // FName("SingleInstanceModeRadius"), + FName("AlignMaxAngle"), + FName("RandomPitchAngle"), + FName("MinimumLayerWeight"), + FName("MinimumExclusionLayerWeight"), + FName("CollisionRadius"), + FName("ShadeRadius"), + FName("InitialSeedDensity"), + FName("AverageSpreadDistance"), + FName("SpreadVariance"), + FName("MaxInitialSeedOffset"), + FName("MaxInitialAge"), + FName("MaxAge"), + FName("OverlapPriority"), + + // bool + // FName("bSingleInstanceModeOverrideRadius"), + FName("bCanGrowInShade"), + FName("bSpawnsInShade"), + + // int32 + FName("OverriddenLightMapRes"), + FName("CustomDepthStencilValue"), + FName("TranslucencySortPriority"), + FName("NumSteps"), + FName("SeedsPerStep"), + FName("DistributionSeed"), + FName("ChangeCount"), + FName("VirtualTextureCullMips"), + + // uint32 + FName("AlignToNormal"), + FName("RandomYaw"), + FName("CollisionWithWorld"), + FName("CastShadow"), + FName("bAffectDynamicIndirectLighting"), + FName("bAffectDistanceFieldLighting"), + FName("bCastDynamicShadow"), + FName("bCastStaticShadow"), + FName("bCastShadowAsTwoSided"), + FName("bReceivesDecals"), + FName("bOverrideLightMapRes"), + FName("bUseAsOccluder"), + FName("bRenderCustomDepth"), + FName("ReapplyDensity"), + FName("ReapplyRadius"), + FName("ReapplyAlignToNormal"), + FName("ReapplyRandomYaw"), + FName("ReapplyScaling"), + FName("ReapplyScaleX"), + FName("ReapplyScaleY"), + FName("ReapplyScaleZ"), + FName("ReapplyRandomPitchAngle"), + FName("ReapplyGroundSlope"), + FName("ReapplyHeight"), + FName("ReapplyLandscapeLayers"), + FName("ReapplyZOffset"), + FName("ReapplyCollisionWithWorld"), + FName("ReapplyVertexColorMask"), + FName("bEnableDensityScaling"), + FName("bEnableDiscardOnLoad"), + + // enums + // FName("Scaling"), + FName("LightmapType"), + + // FFloatInterval + // FName("ScaleX"), + // FName("ScaleY"), + // FName("ScaleZ"), + FName("ZOffset"), + FName("GroundSlopeAngle"), + FName("Height"), + FName("ProceduralScale"), + + // FVector + FName("CollisionScale"), + FName("LowBoundOriginRadius")}); + + EAttribOwner AttribOwner; + switch (InAttributeOwner) + { + case HAPI_ATTROWNER_POINT: + AttribOwner = EAttribOwner::Point; + break; + case HAPI_ATTROWNER_VERTEX: + AttribOwner = EAttribOwner::Vertex; + break; + case HAPI_ATTROWNER_PRIM: + AttribOwner = EAttribOwner::Prim; + break; + case HAPI_ATTROWNER_DETAIL: + AttribOwner = EAttribOwner::Detail; + break; + case HAPI_ATTROWNER_INVALID: + case HAPI_ATTROWNER_MAX: + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported Attribute Owner: %d"), InAttributeOwner); + return false; + } + FHoudiniGenericAttribute GenericAttribute; + GenericAttribute.AttributeCount = 1; + GenericAttribute.AttributeOwner = AttribOwner; + + // Reserve enough space in the arrays (we only have a single point (or all are detail attributes), so attribute + // count is 1, but the tuple size could be up to 10 for transforms + GenericAttribute.DoubleValues.Reserve(10); + GenericAttribute.IntValues.Reserve(10); + GenericAttribute.StringValues.Reserve(10); + + for (const FName& PropertyName : FoliageTypePropertyNames) + { + const FString PropertyNameStr = PropertyName.ToString(); + GenericAttribute.AttributeName = FString::Printf(TEXT("unreal_uproperty_%s"), *PropertyNameStr); + // Find the property on the foliage type instance + FProperty* FoundProperty = nullptr; + UObject* FoundPropertyObject = nullptr; + void* Container = nullptr; + if (!FHoudiniGenericAttribute::FindPropertyOnObject( + InFoliageType, + PropertyNameStr, + FoundProperty, + FoundPropertyObject, + Container)) + continue; + + if (!FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( + InFoliageType, + FoundProperty, + Container, + GenericAttribute.AttributeTupleSize, + GenericAttribute.AttributeType)) + continue; + + const int32 AtIndex = 0; + if (!FHoudiniGenericAttribute::GetPropertyValueFromObject( + InFoliageType, + FoundProperty, + Container, + GenericAttribute, + AtIndex)) + continue; + + FHoudiniEngineUtils::SetGenericPropertyAttribute(InNodeId, InPartId, GenericAttribute); + } + + return bSuccess; +} diff --git a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h index a5a1ed6d3..2e0d14c81 100644 --- a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h @@ -1,66 +1,66 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "UnrealMeshTranslator.h" - -class UFoliageType; -class UFoliageType_InstancedStaticMesh; -class UStaticMeshComponent; - -struct HOUDINIENGINE_API FUnrealFoliageTypeTranslator : public FUnrealMeshTranslator -{ -public: - // HAPI : Marshaling, extract geometry and create input asset for it - return true on success - static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( - UFoliageType_InstancedStaticMesh* InFoliageType, - HAPI_NodeId& InputObjectNodeId, - const FString& InputNodeName, - const bool& ExportAllLODs = false, - const bool& ExportSockets = false, - const bool& ExportColliders = false); - - // Create an input node that references the asset via InRef (unreal_instance). - // Also calls CreateHoudiniFoliageTypeAttributes, to create the unreal_foliage attribute, as well as - // unreal_uproperty_ attributes for the foliage type settings. - static bool CreateInputNodeForReference( - UFoliageType* InFoliageType, - HAPI_NodeId& InInputNodeId, - const FString& InRef, - const FString& InInputNodeName, - const FTransform& InTransform); - -protected: - // Creates the unreal_foliage and unreal_uproperty_ attributes for the foliage type. - static bool CreateHoudiniFoliageTypeAttributes( - UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "UnrealMeshTranslator.h" + +class UFoliageType; +class UFoliageType_InstancedStaticMesh; +class UStaticMeshComponent; + +struct HOUDINIENGINE_API FUnrealFoliageTypeTranslator : public FUnrealMeshTranslator +{ +public: + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForFoliageType_InstancedStaticMesh( + UFoliageType_InstancedStaticMesh* InFoliageType, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false, + const bool& ExportColliders = false); + + // Create an input node that references the asset via InRef (unreal_instance). + // Also calls CreateHoudiniFoliageTypeAttributes, to create the unreal_foliage attribute, as well as + // unreal_uproperty_ attributes for the foliage type settings. + static bool CreateInputNodeForReference( + UFoliageType* InFoliageType, + HAPI_NodeId& InInputNodeId, + const FString& InRef, + const FString& InInputNodeName, + const FTransform& InTransform); + +protected: + // Creates the unreal_foliage and unreal_uproperty_ attributes for the foliage type. + static bool CreateHoudiniFoliageTypeAttributes( + UFoliageType* InFoliageType, const int32& InNodeId, const int32& InPartId, HAPI_AttributeOwner InAttributeOwner); +}; diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp index b3b969dc1..3857d0416 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp @@ -1,212 +1,212 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealInstanceTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "UnrealMeshTranslator.h" - -#include "Engine/StaticMesh.h" -#include "Components/InstancedStaticMeshComponent.h" - -bool -FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( - UInstancedStaticMeshComponent* ISMC, - const FString& InNodeName, - HAPI_NodeId& OutCreatedNodeId, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bExportAsAttributeInstancer) -{ - int32 InstanceCount = ISMC->GetInstanceCount(); - if (InstanceCount < 1) - return true; - - // Get the Static Mesh instanced by the component - UStaticMesh* SM = ISMC->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) - return true; - - // Marshall the Static Mesh to Houdini - int32 SMNodeId = -1; - FString ISMCName = InNodeName + TEXT("_") + ISMC->GetName(); - bool bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - SM, SMNodeId, InNodeName, ISMC, bExportLODs, bExportSockets, bExportColliders); - - if (!bSuccess) - return false; - - // To create the instance properly (via packed prim), we need to: - // - create a copytopoints (with pack and instance enable - // - an inputnode containing all of the instances transform as points - // - plug the input node and the static mesh node in the copytopoints - - // Create the copytopoints SOP. - int32 CopyNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/copytopoints"), InNodeName, true, &CopyNodeId), false); - - // set "Pack And Instance" (pack) to true - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), CopyNodeId, "pack", 0, 1), false); - - // Get the copytopoints parent OBJ NodeID - HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CopyNodeId); - - // Now create an input node for the instance transforms - int32 InstancesNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - ParentNodeId, TEXT("null"), "instances", false, &InstancesNodeId), false); - - // MARSHALL THE INSTANCE TRANSFORM - { - // Get the instance transform and convert them to Position/Rotation/Scale array - TArray Positions; - Positions.SetNumZeroed(InstanceCount * 3); - TArray Rotations; - Rotations.SetNumZeroed(InstanceCount * 4); - TArray Scales; - Scales.SetNumZeroed(InstanceCount * 3); - for (int32 InstanceIdx = 0; InstanceIdx < InstanceCount; InstanceIdx++) - { - FTransform CurTransform; - ISMC->GetInstanceTransform(InstanceIdx, CurTransform); - - // Convert Unreal Position to Houdini - FVector PositionVector = CurTransform.GetLocation(); - Positions[InstanceIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Positions[InstanceIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Positions[InstanceIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - - // Convert Unreal Rotation to Houdini - FQuat RotationQuaternion = CurTransform.GetRotation(); - Rotations[InstanceIdx * 4 + 0] = RotationQuaternion.X; - Rotations[InstanceIdx * 4 + 1] = RotationQuaternion.Z; - Rotations[InstanceIdx * 4 + 2] = RotationQuaternion.Y; - Rotations[InstanceIdx * 4 + 3] = -RotationQuaternion.W; - - // Convert Unreal Scale to Houdini - FVector ScaleVector = CurTransform.GetScale3D(); - Scales[InstanceIdx * 3 + 0] = ScaleVector.X; - Scales[InstanceIdx * 3 + 1] = ScaleVector.Z; - Scales[InstanceIdx * 3 + 2] = ScaleVector.Y; - } - - // Create a part for the instance points. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = InstanceCount; - Part.type = HAPI_PARTTYPE_MESH; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), InstancesNodeId, 0, &Part), false); - - // Create position (P) attribute - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = InstanceCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - Positions.GetData(), 0, AttributeInfoPoint.count), false); - - // Create Rotation (rot) attribute - HAPI_AttributeInfo AttributeInfoRotation; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); - AttributeInfoRotation.count = InstanceCount; - AttributeInfoRotation.tupleSize = 4; - AttributeInfoRotation.exists = true; - AttributeInfoRotation.owner = HAPI_ATTROWNER_POINT; - AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRotation.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation, - Rotations.GetData(), 0, AttributeInfoRotation.count), false); - - // Create scale attribute - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = InstanceCount; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, - Scales.GetData(), 0, AttributeInfoScale.count), false); - - // Commit the instance point geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), InstancesNodeId), false); - } - - // Connect the mesh to the copytopoints node's second input - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CopyNodeId, 0, SMNodeId, 0), false); - - // Connect the instances to the copytopoints node's second input - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), CopyNodeId, 1, InstancesNodeId, 0), false); - - // Update this input object's node IDs - OutCreatedNodeId = CopyNodeId; - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealInstanceTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "UnrealMeshTranslator.h" + +#include "Engine/StaticMesh.h" +#include "Components/InstancedStaticMeshComponent.h" + +bool +FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( + UInstancedStaticMeshComponent* ISMC, + const FString& InNodeName, + HAPI_NodeId& OutCreatedNodeId, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bExportAsAttributeInstancer) +{ + int32 InstanceCount = ISMC->GetInstanceCount(); + if (InstanceCount < 1) + return true; + + // Get the Static Mesh instanced by the component + UStaticMesh* SM = ISMC->GetStaticMesh(); + if (!SM || SM->IsPendingKill()) + return true; + + // Marshall the Static Mesh to Houdini + int32 SMNodeId = -1; + FString ISMCName = InNodeName + TEXT("_") + ISMC->GetName(); + bool bSuccess = FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + SM, SMNodeId, InNodeName, ISMC, bExportLODs, bExportSockets, bExportColliders); + + if (!bSuccess) + return false; + + // To create the instance properly (via packed prim), we need to: + // - create a copytopoints (with pack and instance enable + // - an inputnode containing all of the instances transform as points + // - plug the input node and the static mesh node in the copytopoints + + // Create the copytopoints SOP. + int32 CopyNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/copytopoints"), InNodeName, true, &CopyNodeId), false); + + // set "Pack And Instance" (pack) to true + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), CopyNodeId, "pack", 0, 1), false); + + // Get the copytopoints parent OBJ NodeID + HAPI_NodeId ParentNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(CopyNodeId); + + // Now create an input node for the instance transforms + int32 InstancesNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + ParentNodeId, TEXT("null"), "instances", false, &InstancesNodeId), false); + + // MARSHALL THE INSTANCE TRANSFORM + { + // Get the instance transform and convert them to Position/Rotation/Scale array + TArray Positions; + Positions.SetNumZeroed(InstanceCount * 3); + TArray Rotations; + Rotations.SetNumZeroed(InstanceCount * 4); + TArray Scales; + Scales.SetNumZeroed(InstanceCount * 3); + for (int32 InstanceIdx = 0; InstanceIdx < InstanceCount; InstanceIdx++) + { + FTransform CurTransform; + ISMC->GetInstanceTransform(InstanceIdx, CurTransform); + + // Convert Unreal Position to Houdini + FVector PositionVector = CurTransform.GetLocation(); + Positions[InstanceIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Positions[InstanceIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Positions[InstanceIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + + // Convert Unreal Rotation to Houdini + FQuat RotationQuaternion = CurTransform.GetRotation(); + Rotations[InstanceIdx * 4 + 0] = RotationQuaternion.X; + Rotations[InstanceIdx * 4 + 1] = RotationQuaternion.Z; + Rotations[InstanceIdx * 4 + 2] = RotationQuaternion.Y; + Rotations[InstanceIdx * 4 + 3] = -RotationQuaternion.W; + + // Convert Unreal Scale to Houdini + FVector ScaleVector = CurTransform.GetScale3D(); + Scales[InstanceIdx * 3 + 0] = ScaleVector.X; + Scales[InstanceIdx * 3 + 1] = ScaleVector.Z; + Scales[InstanceIdx * 3 + 2] = ScaleVector.Y; + } + + // Create a part for the instance points. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = InstanceCount; + Part.type = HAPI_PARTTYPE_MESH; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), InstancesNodeId, 0, &Part), false); + + // Create position (P) attribute + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = InstanceCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + Positions.GetData(), 0, AttributeInfoPoint.count), false); + + // Create Rotation (rot) attribute + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + AttributeInfoRotation.count = InstanceCount; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRotation, + Rotations.GetData(), 0, AttributeInfoRotation.count), false); + + // Create scale attribute + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = InstanceCount; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + InstancesNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, + Scales.GetData(), 0, AttributeInfoScale.count), false); + + // Commit the instance point geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), InstancesNodeId), false); + } + + // Connect the mesh to the copytopoints node's second input + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CopyNodeId, 0, SMNodeId, 0), false); + + // Connect the instances to the copytopoints node's second input + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), CopyNodeId, 1, InstancesNodeId, 0), false); + + // Update this input object's node IDs + OutCreatedNodeId = CopyNodeId; + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h index e025a23e7..c7a6a5ef9 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.h @@ -1,49 +1,49 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -class UInstancedStaticMeshComponent; - -struct HOUDINIENGINE_API FUnrealInstanceTranslator -{ - public: - - // HAPI : Marshaling, extract geometry and create input asset for it - return true on success - static bool HapiCreateInputNodeForInstancer( - UInstancedStaticMeshComponent* ISMC, - const FString& InNodeName, - HAPI_NodeId& OutCreatedNodeId, - const bool& bExportLODs, - const bool& bExportSockets, - const bool& bExportColliders, - const bool& bExportAsAttributeInstancer); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +class UInstancedStaticMeshComponent; + +struct HOUDINIENGINE_API FUnrealInstanceTranslator +{ + public: + + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForInstancer( + UInstancedStaticMeshComponent* ISMC, + const FString& InNodeName, + HAPI_NodeId& OutCreatedNodeId, + const bool& bExportLODs, + const bool& bExportSockets, + const bool& bExportColliders, + const bool& bExportAsAttributeInstancer); }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp index 6ec026186..116f3399d 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp @@ -1,2201 +1,2201 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniApi.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineString.h" - -#include "UnrealLandscapeTranslator.h" -#include "HoudiniGeoPartObject.h" - -#include "Landscape.h" -#include "LandscapeDataAccess.h" -#include "LandscapeEdit.h" -#include "LightMap.h" -#include "Engine/MapBuildDataRegistry.h" -#include "PhysicalMaterials/PhysicalMaterial.h" - - -bool -FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( - ALandscapeProxy* LandscapeProxy, - HAPI_NodeId& CreatedNodeId, - const FString& InputNodeNameString, - const bool& bExportGeometryAsMesh, - const bool& bExportTileUVs, - const bool bExportNormalizedUVs, - const bool bExportLighting, - const bool bExportMaterials ) -{ - //-------------------------------------------------------------------------------------------------- - // 1. Create an input node - //-------------------------------------------------------------------------------------------------- - HAPI_NodeId InputNodeId = -1; - // Create the curve SOP Node - std::string NodeNameRawString; - FHoudiniEngineUtils::ConvertUnrealString(InputNodeNameString, NodeNameRawString); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) - return false; - - // We now have a valid id. - CreatedNodeId = InputNodeId; - - if(!FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true)) - return false; - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); - */ - //-------------------------------------------------------------------------------------------------- - // 2. Set the part info - //-------------------------------------------------------------------------------------------------- - int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; - float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; - - //int32 NumComponents = bExportOnlySelected ? SelectedComponents.Num() : LandscapeProxy->LandscapeComponents.Num(); - int32 NumComponents = LandscapeProxy->LandscapeComponents.Num(); - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - int32 VertexCount = NumComponents * VertexCountPerComponent; - if (!VertexCount) - return false; - - int32 TriangleCount = NumComponents * FMath::Square(ComponentSizeQuads) * 2; - int32 QuadCount = NumComponents * FMath::Square(ComponentSizeQuads); - int32 IndexCount = QuadCount * 4; - - // Create part info - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - //FMemory::Memzero< HAPI_PartInfo >(Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.pointCount = VertexCount; - Part.type = HAPI_PARTTYPE_MESH; - - // If we are exporting to a mesh, we need vertices and faces - if (bExportGeometryAsMesh) - { - Part.vertexCount = IndexCount; - Part.faceCount = QuadCount; - } - - // Set the part infos - HAPI_GeoInfo DisplayGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CreatedNodeId, &DisplayGeoInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part), false); - - //-------------------------------------------------------------------------------------------------- - // 3. Extract the landscape data - //-------------------------------------------------------------------------------------------------- - // Array for the position data - TArray LandscapePositionArray; - // Array for the normals - TArray LandscapeNormalArray; - // Array for the UVs - TArray LandscapeUVArray; - // Array for the vertex index of each point in its component - TArray LandscapeComponentVertexIndicesArray; - // Array for the tile names per point - TArray LandscapeComponentNameArray; - // Array for the lightmap values - TArray LandscapeLightmapValues; - // Selected components set to all components in current landscape proxy - TSet SelectedComponents; - SelectedComponents.Append(LandscapeProxy->LandscapeComponents); - - // Extract all the data from the landscape to the arrays - if (!ExtractLandscapeData( - LandscapeProxy, SelectedComponents, - bExportLighting, bExportTileUVs, bExportNormalizedUVs, - LandscapePositionArray, LandscapeNormalArray, - LandscapeUVArray, LandscapeComponentVertexIndicesArray, - LandscapeComponentNameArray, LandscapeLightmapValues)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 3. Set the corresponding attributes in Houdini - //-------------------------------------------------------------------------------------------------- - - // Create point attribute info containing positions. - if (!AddLandscapePositionAttribute(DisplayGeoInfo.nodeId, LandscapePositionArray)) - return false; - - // Create point attribute info containing normals. - if (!AddLandscapeNormalAttribute(DisplayGeoInfo.nodeId, LandscapeNormalArray)) - return false; - - // Create point attribute info containing UVs. - if (!AddLandscapeUVAttribute(DisplayGeoInfo.nodeId, LandscapeUVArray)) - return false; - - // Create point attribute containing landscape component vertex indices (indices of vertices within the grid - x,y). - if (!AddLandscapeComponentVertexIndicesAttribute(DisplayGeoInfo.nodeId, LandscapeComponentVertexIndicesArray)) - return false; - - // Create point attribute containing landscape component name. - if (!AddLandscapeComponentNameAttribute(DisplayGeoInfo.nodeId, LandscapeComponentNameArray)) - return false; - - // Create point attribute info containing lightmap information. - if (bExportLighting) - { - if (!AddLandscapeLightmapColorAttribute(DisplayGeoInfo.nodeId, LandscapeLightmapValues)) - return false; - } - - // Set indices if we are exporting full geometry. - if (bExportGeometryAsMesh) - { - if (!AddLandscapeMeshIndicesAndMaterialsAttribute( - DisplayGeoInfo.nodeId, - bExportMaterials, - ComponentSizeQuads, - QuadCount, - LandscapeProxy, - SelectedComponents)) - return false; - } - - // If we are marshalling material information. - if (bExportMaterials) - { - if (!AddLandscapeGlobalMaterialAttribute(DisplayGeoInfo.nodeId, LandscapeProxy)) - return false; - } - - /* - // TODO: Move this to ExtractLandscapeData() - //-------------------------------------------------------------------------------------------------- - // 4. Extract and convert all the layers - //-------------------------------------------------------------------------------------------------- - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Get the landscape X/Y Size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) - return false; - - // Calc the X/Y size in points - int32 XSize = (MaxX - MinX + 1); - int32 YSize = (MaxY - MinY + 1); - if ((XSize < 2) || (YSize < 2)) - return false; - - bool MaskInitialized = false; - int32 NumLayers = LandscapeInfo->Layers.Num(); - for (int32 n = 0; n < NumLayers; n++) - { - // 1. Extract the uint8 values from the layer - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!GetLandscapeLayerData( - LandscapeInfo, n, - MinX, MinY, MaxX, MaxY, - CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - // 2. Convert unreal uint8 values to floats - // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float - HAPI_VolumeInfo CurrentLayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); - TArray CurrentLayerFloatData; - if (!ConvertLandscapeLayerDataToHeightfieldData( - CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, - CurrentLayerFloatData, CurrentLayerVolumeInfo)) - continue; - - if (!AddLandscapeLayerAttribute( - DisplayGeoInfo.nodeId, CurrentLayerFloatData, LayerName)) - continue; - } - */ - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId), false); - - // TODO: Remove me! - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); - */ - - return FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true); -} - -bool -FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( - ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId, const FString& InputNodeNameStr) -{ - if (!LandscapeProxy) - return false; - - // Export the whole landscape and its layer as a single heightfield. - FString NodeName = InputNodeNameStr + TEXT("_") + LandscapeProxy->GetName(); - - //-------------------------------------------------------------------------------------------------- - // 1. Extracting the height data - //-------------------------------------------------------------------------------------------------- - TArray HeightData; - int32 XSize, YSize; - FVector Min, Max; - if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 2. Convert the height uint16 data to float - //-------------------------------------------------------------------------------------------------- - TArray HeightfieldFloatValues; - HAPI_VolumeInfo HeightfieldVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); - //FTransform LandscapeTransform = LandscapeProxy->LandscapeActorToWorld();// LandscapeProxy->ActorToWorld(); - - // Get the actual transform of this proxy, not the landscape actor's transform! - FTransform LandscapeTM = LandscapeProxy->LandscapeActorToWorld(); - FTransform ProxyRelativeTM(FVector(LandscapeProxy->LandscapeSectionOffset)); - FTransform LandscapeTransform = ProxyRelativeTM * LandscapeTM; - - FVector CenterOffset = FVector::ZeroVector; - if (!ConvertLandscapeDataToHeightfieldData( - HeightData, XSize, YSize, Min, Max, LandscapeTransform, - HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 3. Create the Heightfield Input Node - //-------------------------------------------------------------------------------------------------- - HAPI_NodeId HeightFieldId = -1; - HAPI_NodeId HeightId = -1; - HAPI_NodeId MaskId = -1; - HAPI_NodeId MergeId = -1; - if (!CreateHeightfieldInputNode(NodeName, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 4. Set the HeightfieldData in Houdini - //-------------------------------------------------------------------------------------------------- - // Set the Height volume's data - HAPI_PartId PartId = 0; - if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) - return false; - - // Apply attributes to the heightfield - ApplyAttributesToHeightfieldNode(HeightId, PartId, LandscapeProxy); - - // Commit the height volume - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), HeightId), false); - - //-------------------------------------------------------------------------------------------------- - // 5. Extract and convert all the layers - //-------------------------------------------------------------------------------------------------- - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - bool MaskInitialized = false; - int32 MergeInputIndex = 2; - - auto MergeInputFn = [&MergeInputIndex] (const HAPI_NodeId MergeId, const HAPI_NodeId NodeId) -> HAPI_Result - { - // We had to create a new volume for this layer, so we need to connect it to the HF's merge node - HAPI_Result Result = FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - MergeId, MergeInputIndex, NodeId, 0); - - if (Result == HAPI_RESULT_SUCCESS) - { - MergeInputIndex++; - } - return Result; - }; - - int32 NumLayers = LandscapeInfo->Layers.Num(); - for (int32 n = 0; n < NumLayers; n++) - { - // 1. Extract the uint8 values from the layer - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!GetLandscapeLayerData(LandscapeProxy, LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - // 2. Convert unreal uint8 values to floats - // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float - HAPI_VolumeInfo CurrentLayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); - TArray CurrentLayerFloatData; - if (!ConvertLandscapeLayerDataToHeightfieldData( - CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, - CurrentLayerFloatData, CurrentLayerVolumeInfo)) - continue; - - // We reuse the height layer's transform - CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; - - // 3. See if we need to create an input volume, or can reuse the HF's default mask volume - bool IsMask = false; - if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) - IsMask = true; - - HAPI_NodeId LayerVolumeNodeId = -1; - if (!IsMask) - { - // Current layer is not mask, so we need to create a new input volume - std::string LayerNameStr; - FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); - - FHoudiniApi::CreateHeightfieldInputVolumeNode( - FHoudiniEngine::Get().GetSession(), - HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); - } - else - { - // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node - LayerVolumeNodeId = MaskId; - } - - // Check if we have a valid id for the input volume. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) - continue; - - // 4. Set the layer/mask heighfield data in Houdini - HAPI_PartId CurrentPartId = 0; - if (!SetHeightfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) - continue; - - // Get the physical material used by that layer - UPhysicalMaterial* LayerPhysicalMat = LandscapeProxy->DefaultPhysMaterial; - { - FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; - ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; - if (LayerInfo) - LayerPhysicalMat = LayerInfo->PhysMaterial; - } - - // Apply attributes to the heightfield input node - ApplyAttributesToHeightfieldNode(LayerVolumeNodeId, PartId, LandscapeProxy); - - // Commit the volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); - - if (!IsMask) - { - // We had to create a new volume for this layer, so we need to connect it to the HF's merge node - HOUDINI_CHECK_ERROR_RETURN(MergeInputFn(MergeId, LayerVolumeNodeId), false); - } - else - { - MaskInitialized = true; - } - } - - // We need to have a mask layer as it is required for proper heightfield functionalities - // Setting the volume info on the mask is needed for the HF to have proper transform in H! - // If we didn't create a mask volume before, send a default one now - if (!MaskInitialized) - { - MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); - - ApplyAttributesToHeightfieldNode(MaskId, PartId, LandscapeProxy); - - // Commit the mask volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), MaskId), false); - } - - // We need a valid landscape actor to get the edit layers - ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); - if(IsValid(Landscape)) - { - //-------------------------------------------------------------------------------------------------- - // Create heightfield input for each editable landscape layer - //-------------------------------------------------------------------------------------------------- - HAPI_VolumeInfo LayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); - - for(FLandscapeLayer& Layer : Landscape->LandscapeLayers) - { - const FString LayerVolumeName = FString::Format(TEXT("landscapelayer_{0}"), {Layer.Name.ToString()}); - - HAPI_NodeId LandscapeLayerNodeId = -1; - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape] Creating input node for editable landscape layer: %s"), *LayerVolumeName); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightfieldInputVolumeNode( - FHoudiniEngine::Get().GetSession(), - HeightFieldId, - &LandscapeLayerNodeId, - TCHAR_TO_UTF8(*LayerVolumeName), - XSize, YSize, - 1.f - ), false); - - // Create a volume visualization node - const FString VisualizationName = FString::Format(TEXT("visualization_{0}"), {Layer.Name.ToString()}); - HAPI_NodeId VisualizationNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( - FHoudiniEngine::Get().GetSession(), - HeightFieldId, - "volumevisualization", - TCHAR_TO_UTF8(*VisualizationName), - false, - &VisualizationNodeId - ), false); - - // Set Visualization Mode to Height Field - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), - VisualizationNodeId, - "vismode", - 0, 2 - ), false); - - // Set Density Field to '*'. - HAPI_ParmId DensityFieldParmId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( - FHoudiniEngine::Get().GetSession(), - VisualizationNodeId, - "densityfield", - &DensityFieldParmId - ), false); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), - VisualizationNodeId, - "*", - DensityFieldParmId, 0 - ), false); - - // Create a visibility node - const FString VisibilityName = FString::Format(TEXT("visibility_{0}"), {Layer.Name.ToString()}); - HAPI_NodeId VisibilityNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( - FHoudiniEngine::Get().GetSession(), - HeightFieldId, - "visibility", - TCHAR_TO_UTF8(*VisibilityName), - false, - &VisibilityNodeId - ), false); - - // Connect landscape layer to visualization - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - VisualizationNodeId, 0, LandscapeLayerNodeId, 0), false); - - // Connect visualization to visibility - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - VisibilityNodeId, 0, VisualizationNodeId, 0), false); - - // Connect the visibility node to the merge input - HOUDINI_CHECK_ERROR_RETURN(MergeInputFn(MergeId, VisibilityNodeId), false); - - FScopedSetLandscapeEditingLayer Scope(Landscape, Layer.Guid ); // Scope landscape access to the current layer - - TArray LayerHeightData; - TArray LayerHeightFloatData; - //-------------------------------------------------------------------------------------------------- - // Extracting height data - //-------------------------------------------------------------------------------------------------- - if (!GetLandscapeData(LandscapeProxy, LayerHeightData, XSize, YSize, Min, Max)) - return false; - - //-------------------------------------------------------------------------------------------------- - // Convert the height uint16 data to float - //-------------------------------------------------------------------------------------------------- - if (!ConvertLandscapeDataToHeightfieldData( - LayerHeightData, XSize, YSize, Min, Max, LandscapeTransform, - LayerHeightFloatData, LayerVolumeInfo, CenterOffset)) - return false; - - HAPI_PartId LayerPartId = 0; - SetHeightfieldData(LandscapeLayerNodeId, LayerPartId, LayerHeightFloatData, LayerVolumeInfo, LayerVolumeName); - - // Apply attributes to the heightfield input node - ApplyAttributesToHeightfieldNode(LandscapeLayerNodeId, 0, LandscapeProxy); - - // Commit the volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), LandscapeLayerNodeId), false); - } - } - - HAPI_TransformEuler HAPIObjectTransform; - FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); - //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); - LandscapeTransform.SetScale3D(FVector::OneVector); - FHoudiniEngineUtils::TranslateUnrealTransform(LandscapeTransform, HAPIObjectTransform); - HAPIObjectTransform.position[1] = 0.0f; - - HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(HeightFieldId); - FHoudiniApi::SetObjectTransform(FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform); - - // Since HF are centered but landscape aren't, we need to set the HF's center parameter - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); - FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); - - // Finally, cook the Heightfield node - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), HeightFieldId, &CookOptions), false); - */ - if(!FHoudiniEngineUtils::HapiCookNode(HeightFieldId, nullptr, true)) - return false; - - CreatedHeightfieldNodeId = HeightFieldId; - - return true; -} - -bool -FUnrealLandscapeTranslator::CreateInputNodeForLandscape( - ALandscapeProxy* LandscapeProxy, - const FString& InputNodeNameStr, - const FString& HeightFieldName, - const FTransform& LandscapeTransform, - FVector& CenterOffset, - HAPI_NodeId& HeightId, - HAPI_PartId& PartId, - HAPI_NodeId& HeightFieldId, - HAPI_NodeId& MaskId, - HAPI_NodeId& MergeId, - TArray& HeightData, - HAPI_VolumeInfo& HeightfieldVolumeInfo, - int32& XSize, int32& YSize - ) -{ - //-------------------------------------------------------------------------------------------------- - // 1. Extracting the height data - //-------------------------------------------------------------------------------------------------- - - FVector Min, Max; - - if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 2. Convert the height uint16 data to float - //-------------------------------------------------------------------------------------------------- - TArray HeightfieldFloatValues; - - if (!ConvertLandscapeDataToHeightfieldData( - HeightData, XSize, YSize, Min, Max, LandscapeTransform, - HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 3. Create the Heightfield Input Node - //-------------------------------------------------------------------------------------------------- - if (!CreateHeightfieldInputNode(InputNodeNameStr, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) - return false; - - //-------------------------------------------------------------------------------------------------- - // 4. Set the HeightfieldData in Houdini - //-------------------------------------------------------------------------------------------------- - // Set the Height volume's data - if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, HeightFieldName)) - return false; - - return true; -} - -// Converts Unreal uint16 values to Houdini Float -bool -FUnrealLandscapeTranslator::ConvertLandscapeLayerDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - const FLinearColor& LayerUsageDebugColor, - TArray& LayerFloatValues, - HAPI_VolumeInfo& LayerVolumeInfo) -{ - LayerFloatValues.Empty(); - - int32 HoudiniXSize = YSize; - int32 HoudiniYSize = XSize; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - if (IntHeightData.Num() != SizeInPoints) - return false; - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to float - //-------------------------------------------------------------------------------------------------- - - // By default, values are converted from unreal [0 255] uint8 to Houdini [0 1] float - // uint8 min/max - uint8 IntMin = 0; - uint8 IntMax = UINT8_MAX; - // The range in Digits - double DigitRange = (double)UINT8_MAX; - - // By default, the values will be converted to [0, 1] - float LayerMin = 0.0f; - float LayerMax = 1.0f; - float LayerSpacing = 1.0f / DigitRange; - - // If this layer came from Houdini, its alpha value should be PI - // This indicates that we can extract additional infos stored its debug usage color - // so we can reconstruct the original source values (float) more accurately - if (LayerUsageDebugColor.A == PI) - { - // We need the ZMin / ZMax uint8 values - IntMin = IntHeightData[0]; - IntMax = IntMin; - for (int n = 0; n < IntHeightData.Num(); n++) - { - if (IntHeightData[n] < IntMin) - IntMin = IntHeightData[n]; - if (IntHeightData[n] > IntMax) - IntMax = IntHeightData[n]; - } - - DigitRange = (double)IntMax - (double)IntMin; - - // Read the original min/max and spacing stored in the debug color - LayerMin = LayerUsageDebugColor.R; - LayerMax = LayerUsageDebugColor.G; - LayerSpacing = LayerUsageDebugColor.B; - } - - // Convert the Int data to Float - LayerFloatValues.SetNumUninitialized(SizeInPoints); - - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // We need to invert X/Y when reading the value from Unreal - int32 nHoudini = nX + nY * HoudiniXSize; - int32 nUnreal = nY + nX * XSize; - - // Convert the int values to meter - // Unreal's digit value have a zero value of 32768 - double DoubleValue = ((double)IntHeightData[nUnreal] - (double)IntMin) * LayerSpacing + LayerMin; - LayerFloatValues[nHoudini] = (float)DoubleValue; - } - } - - /* - // Verifying the converted ZMin / ZMax - float FloatMin = LayerFloatValues[0]; - float FloatMax = FloatMin; - for (int32 n = 0; n < LayerFloatValues.Num(); n++) - { - if (LayerFloatValues[n] < FloatMin) - FloatMin = LayerFloatValues[n]; - if (LayerFloatValues[n] > FloatMax) - FloatMax = LayerFloatValues[n]; - } - */ - - //-------------------------------------------------------------------------------------------------- - // 2. Fill the volume info - //-------------------------------------------------------------------------------------------------- - LayerVolumeInfo.xLength = HoudiniXSize; - LayerVolumeInfo.yLength = HoudiniYSize; - LayerVolumeInfo.zLength = 1; - - LayerVolumeInfo.minX = 0; - LayerVolumeInfo.minY = 0; - LayerVolumeInfo.minZ = 0; - - LayerVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; - LayerVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; - LayerVolumeInfo.tupleSize = 1; - LayerVolumeInfo.tileSize = 1; - - LayerVolumeInfo.hasTaper = false; - LayerVolumeInfo.xTaper = 0.0; - LayerVolumeInfo.yTaper = 0.0; - - // The layer transform will have to be copied from the main heightfield's transform - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeData( - ALandscapeProxy* LandscapeProxy, - TArray& HeightData, - int32& XSize, int32& YSize, - FVector& Min, FVector& Max) -{ - if (!LandscapeProxy) - return false; - - ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); - if (!LandscapeInfo) - return false; - - // Get the landscape extents to get its size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - - ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); - if (LandscapeProxy == Landscape) - { - // The proxy is a landscape actor, so we have to use the landscape extent (landscape components - // may have been moved to proxies and may not be present on this actor). - LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); - } - else - { - // We only want to get the data for this landscape proxy. - // To handle streaming proxies correctly, get the extents via all the components, - // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. - for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) - { - Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); - } - } - - if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize)) - return false; - - // Get the landscape Min/Max values - // Do not use Landscape->GetActorBounds() here as instanced geo - // (due to grass layers for example) can cause it to return incorrect bounds! - FVector Origin, Extent; - GetLandscapeProxyBounds(LandscapeProxy, Origin, Extent); - - // Get the landscape Min/Max values - Min = Origin - Extent; - Max = Origin + Extent; - - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeData( - ULandscapeInfo* LandscapeInfo, - const int32& MinX, const int32& MinY, - const int32& MaxX, const int32& MaxY, - TArray& HeightData, - int32& XSize, int32& YSize) -{ - if (!LandscapeInfo) - return false; - - // Get the X/Y size in points - XSize = (MaxX - MinX + 1); - YSize = (MaxY - MinY + 1); - - if ((XSize < 2) || (YSize < 2)) - return false; - - // Extracting the uint16 values from the landscape - FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); - HeightData.AddZeroed(XSize * YSize); - LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0); - - return true; -} - - -void -FUnrealLandscapeTranslator::GetLandscapeProxyBounds( - ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents) -{ - // Iterate only on the landscape components - FBox Bounds(ForceInit); - for (const UActorComponent* ActorComponent : LandscapeProxy->GetComponents()) - { - const ULandscapeComponent* LandscapeComp = Cast(ActorComponent); - if (LandscapeComp && LandscapeComp->IsRegistered()) - Bounds += LandscapeComp->Bounds.GetBox(); - } - - // Convert the bounds to origin/offset vectors - Bounds.GetCenterAndExtents(Origin, Extents); -} - -void -FUnrealLandscapeTranslator::ApplyAttributesToHeightfieldNode( - const HAPI_NodeId HeightId, - const HAPI_PartId PartId, - ALandscapeProxy* LandscapeProxy) -{ - UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); - UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); - UPhysicalMaterial* LandscapePhysMat = LandscapeProxy->DefaultPhysMaterial; - - AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); - - // Add the landscape's actor tags as prim attributes if we have any - FHoudiniEngineUtils::CreateAttributesFromTags(HeightId, PartId, LandscapeProxy->Tags); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(HeightId, PartId, LandscapeProxy, 1); - - // Add the unreal_level_path attribute - ULevel* Level = LandscapeProxy->GetLevel(); - if (Level) - { - FHoudiniEngineUtils::AddLevelPathAttribute(HeightId, PartId, Level, 1); - /* - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToVolume(HeightId, PartId, LevelPath); - */ - } -} - - -bool -FUnrealLandscapeTranslator::ConvertLandscapeDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - FVector Min, FVector Max, - const FTransform& LandscapeTransform, - TArray& HeightfieldFloatValues, - HAPI_VolumeInfo& HeightfieldVolumeInfo, - FVector& CenterOffset) -{ - HeightfieldFloatValues.Empty(); - - int32 HoudiniXSize = YSize; - int32 HoudiniYSize = XSize; - int32 SizeInPoints = HoudiniXSize * HoudiniYSize; - if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) - return false; - - if (IntHeightData.Num() != SizeInPoints) - return false; - - // Use default unreal scaling for marshalling landscapes - // A lot of precision will be lost in order to keep the same transform as the landscape input - bool bUseDefaultUE4Scaling = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) - bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; - - //-------------------------------------------------------------------------------------------------- - // 1. Convert values to float - //-------------------------------------------------------------------------------------------------- - - - // Convert the min/max values from cm to meters - Min /= 100.0; - Max /= 100.0; - - // Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0 - // To convert the uint16 values to float "metric" values, offset the int by 32768 to center it, - // then scale it - - // Spacing used to convert from uint16 to meters - double ZSpacing = 512.0 / ((double)UINT16_MAX); - ZSpacing *= ((double)LandscapeTransform.GetScale3D().Z / 100.0); - - // Center value in meters (Landscape ranges from [-255:257] meters at default scale - double ZCenterOffset = 32767; - double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f; - // Convert the Int data to Float - HeightfieldFloatValues.SetNumUninitialized(SizeInPoints); - - for (int32 nY = 0; nY < HoudiniYSize; nY++) - { - for (int32 nX = 0; nX < HoudiniXSize; nX++) - { - // We need to invert X/Y when reading the value from Unreal - int32 nHoudini = nX + nY * HoudiniXSize; - int32 nUnreal = nY + nX * XSize; - - // Convert the int values to meter - // Unreal's digit value have a zero value of 32768 - double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset; - HeightfieldFloatValues[nHoudini] = (float)DoubleValue; - } - } - - //-------------------------------------------------------------------------------------------------- - // 2. Convert the Unreal Transform to a HAPI_transform - //-------------------------------------------------------------------------------------------------- - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - //FMemory::Memzero< HAPI_Transform >( HapiTransform ); - { - FQuat Rotation = LandscapeTransform.GetRotation(); - if (Rotation != FQuat::Identity) - { - //Swap(ObjectRotation.Y, ObjectRotation.Z); - HapiTransform.rotationQuaternion[0] = Rotation.X; - HapiTransform.rotationQuaternion[1] = Rotation.Z; - HapiTransform.rotationQuaternion[2] = Rotation.Y; - HapiTransform.rotationQuaternion[3] = -Rotation.W; - } - else - { - HapiTransform.rotationQuaternion[0] = 0; - HapiTransform.rotationQuaternion[1] = 0; - HapiTransform.rotationQuaternion[2] = 0; - HapiTransform.rotationQuaternion[3] = 1; - } - - // Heightfield are centered, landscapes are not - CenterOffset = (Max - Min) * 0.5f; - - // Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform) - //FVector Position = LandscapeTransform.GetLocation() / 100.0f; - HapiTransform.position[1] = 0.0f;//Position.X + CenterOffset.X; - HapiTransform.position[0] = 0.0f;//Position.Y + CenterOffset.Y; - HapiTransform.position[2] = 0.0f; - - FVector Scale = LandscapeTransform.GetScale3D() / 100.0f; - HapiTransform.scale[0] = Scale.X * 0.5f * HoudiniXSize; - HapiTransform.scale[1] = Scale.Y * 0.5f * HoudiniYSize; - HapiTransform.scale[2] = 0.5f; - if (bUseDefaultUE4Scaling) - HapiTransform.scale[2] *= Scale.Z; - - HapiTransform.shear[0] = 0.0f; - HapiTransform.shear[1] = 0.0f; - HapiTransform.shear[2] = 0.0f; - } - - //-------------------------------------------------------------------------------------------------- - // 3. Fill the volume info - //-------------------------------------------------------------------------------------------------- - HeightfieldVolumeInfo.xLength = HoudiniXSize; - HeightfieldVolumeInfo.yLength = HoudiniYSize; - HeightfieldVolumeInfo.zLength = 1; - - HeightfieldVolumeInfo.minX = 0; - HeightfieldVolumeInfo.minY = 0; - HeightfieldVolumeInfo.minZ = 0; - - HeightfieldVolumeInfo.transform = HapiTransform; - - HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; - HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; - HeightfieldVolumeInfo.tupleSize = 1; - HeightfieldVolumeInfo.tileSize = 1; - - HeightfieldVolumeInfo.hasTaper = false; - HeightfieldVolumeInfo.xTaper = 0.0; - HeightfieldVolumeInfo.yTaper = 0.0; - - return true; -} - -bool -FUnrealLandscapeTranslator::CreateHeightfieldInputNode( - const FString& NodeName, - const int32& XSize, - const int32& YSize, - HAPI_NodeId& HeightfieldNodeId, - HAPI_NodeId& HeightNodeId, - HAPI_NodeId& MaskNodeId, - HAPI_NodeId& MergeNodeId) -{ - // Make sure the Heightfield node doesnt already exists - if (HeightfieldNodeId != -1) - return false; - - // Convert the node's name - std::string NameStr; - FHoudiniEngineUtils::ConvertUnrealString(NodeName, NameStr); - - // Create the heigthfield node via HAPI - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightFieldInput( - FHoudiniEngine::Get().GetSession(), - -1, NameStr.c_str(), XSize, YSize, 1.0f, HAPI_HeightFieldSampling::HAPI_HEIGHTFIELD_SAMPLING_CORNER, - &HeightfieldNodeId, &HeightNodeId, &MaskNodeId, &MergeNodeId), false); - - // Cook it - return FHoudiniEngineUtils::HapiCookNode(HeightfieldNodeId, nullptr, true); - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), HeightfieldNodeId, &CookOptions), false); - - return true; - */ -} - -bool -FUnrealLandscapeTranslator::SetHeightfieldData( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - TArray& FloatValues, - const HAPI_VolumeInfo& VolumeInfo, - const FString& HeightfieldName) -{ - // Cook the node to get proper infos on it - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, &CookOptions), false); - */ - if(!FHoudiniEngineUtils::HapiCookNode(VolumeNodeId, nullptr, true)) - return false; - - // Read the geo/part/volume info from the volume node - HAPI_GeoInfo GeoInfo; - FHoudiniApi::GeoInfo_Init(&GeoInfo); - //FMemory::Memset< HAPI_GeoInfo >(GeoInfo, 0); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, &GeoInfo), false); - - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - //FMemory::Memset< HAPI_PartInfo >(PartInfo, 0); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( - FHoudiniEngine::Get().GetSession(), - GeoInfo.nodeId, PartId, &PartInfo), false); - - // Update the volume infos - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVolumeInfo( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, PartInfo.id, &VolumeInfo), false); - - // Volume name - std::string NameStr; - FHoudiniEngineUtils::ConvertUnrealString(HeightfieldName, NameStr); - - // Set the Heighfield data on the volume - float * HeightData = FloatValues.GetData(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetHeightFieldData( - FHoudiniEngine::Get().GetSession(), - GeoInfo.nodeId, PartInfo.id, NameStr.c_str(), HeightData, 0, FloatValues.Num()), false); - - return true; -} - -bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - UMaterialInterface* InLandscapeMaterial, - UMaterialInterface* InLandscapeHoleMaterial, - UPhysicalMaterial* InPhysicalMaterial) -{ - if (VolumeNodeId == -1) - return false; - - // LANDSCAPE MATERIAL - if (InLandscapeMaterial && !InLandscapeMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InLandscapeMaterialString = InLandscapeMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - // HOLE MATERIAL - if (InLandscapeHoleMaterial && !InLandscapeHoleMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InLandscapeMaterialString = InLandscapeHoleMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (Result == HAPI_RESULT_SUCCESS) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_hole_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - // PHYSICAL MATERIAL - if (InPhysicalMaterial && !InPhysicalMaterial->IsPendingKill()) - { - // Extract the path name from the material interface - FString InPhysMatlString = InPhysicalMaterial->GetPathName(); - - // Get name of attribute used for marshalling materials. - std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL; - - // Marshall in material names. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - AttributeInfoMaterial.count = 1; - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); - - if (Result == HAPI_RESULT_SUCCESS) - { - // Convert the FString to cont char * - std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InPhysMatlString); - const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); - TArray LandscapeMatArr; - LandscapeMatArr.Add(LandscapeMatCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, - LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_physical_material attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - } - - return true; -} - -/* -bool -FUnrealLandscapeTranslator::AddLevelPathAttributeToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - const FString& LevelPath) -{ - if (VolumeNodeId == -1) - return false; - - // LANDSCAPE MATERIAL - if (LevelPath.IsEmpty()) - return false; - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = 1; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to cont char * - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray LevelPathArr; - LevelPathArr.Add(LevelPathCStrRaw); - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - VolumeNodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - LevelPathArr.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for landscape: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} -*/ - -bool -FUnrealLandscapeTranslator::GetLandscapeLayerData( - ALandscapeProxy* LandscapeProxy, - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName) -{ - if (!IsValid(LandscapeInfo) || !IsValid(LandscapeProxy)) - return false; - - // Get the landscape X/Y Size - int32 MinX = MAX_int32; - int32 MinY = MAX_int32; - int32 MaxX = -MAX_int32; - int32 MaxY = -MAX_int32; - - ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); - if (LandscapeProxy == Landscape) - { - // The proxy is a landscape actor, so we have to use the landscape extent (landscape components - // may have been moved to proxies and may not be present on this actor). - LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); - } - else - { - // We only want to get the data for this landscape proxy. - // To handle streaming proxies correctly, get the extents via all the components, - // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. - for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) - { - Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); - } - } - - if(MinX == MAX_int32 || MinY == MAX_int32 || MaxX == -MAX_int32 || MaxY == -MAX_int32) - return false; - - if (!GetLandscapeLayerData( - LandscapeInfo, LayerIndex, - MinX, MinY, MaxX, MaxY, - LayerData, LayerUsageDebugColor, LayerName)) - return false; - - return true; -} - -bool -FUnrealLandscapeTranslator::GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - const int32& MinX, const int32& MinY, - const int32& MaxX, const int32& MaxY, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName) -{ - if (!LandscapeInfo) - return false; - - if (!LandscapeInfo->Layers.IsValidIndex(LayerIndex)) - return false; - - FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[LayerIndex]; - ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; - if (!LayerInfo) - return false; - - // Calc the X/Y size in points - int32 XSize = (MaxX - MinX + 1); - int32 YSize = (MaxY - MinY + 1); - if ((XSize < 2) || (YSize < 2)) - return false; - - // extracting the uint8 values from the layer - FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); - LayerData.AddZeroed(XSize * YSize); - LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, LayerData.GetData(), 0); - - LayerUsageDebugColor = LayerInfo->LayerUsageDebugColor; - - LayerName = LayersSetting.GetLayerName().ToString(); - - return true; -} - -bool -FUnrealLandscapeTranslator::InitDefaultHeightfieldMask( - const HAPI_VolumeInfo& HeightVolumeInfo, - const HAPI_NodeId& MaskVolumeNodeId) -{ - // We need to have a mask layer as it is required for proper heightfield functionalities - - // Creating an array filled with 0.0 - TArray< float > MaskFloatData; - MaskFloatData.Init(0.0f, HeightVolumeInfo.xLength * HeightVolumeInfo.yLength); - - // Creating the volume infos - HAPI_VolumeInfo MaskVolumeInfo = HeightVolumeInfo; - - // Set the heighfield data in Houdini - FString MaskName = TEXT("mask"); - HAPI_PartId PartId = 0; - if (!SetHeightfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) - return false; - - return true; -} - -bool -FUnrealLandscapeTranslator::DestroyLandscapeAssetNode(HAPI_NodeId& ConnectedAssetId, TArray& CreatedInputAssetIds) -{ - HAPI_AssetInfo NodeAssetInfo; - FHoudiniApi::AssetInfo_Init(&NodeAssetInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( - FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeAssetInfo), false); - - FHoudiniEngineString AssetOpName(NodeAssetInfo.fullOpNameSH); - FString OpName; - if (!AssetOpName.ToFString(OpName)) - return false; - - if (!OpName.Contains(TEXT("xform"))) - { - // Not a transform node, so not a Heightfield - // We just need to destroy the landscape asset node - return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); - } - - // The landscape was marshalled as a heightfield, so we need to destroy and disconnect - // the volvis nodes, all the merge node's input (each merge input is a volume for one - // of the layer/mask of the landscape ) - - // Query the volvis node id - // The volvis node is the fist input of the xform node - HAPI_NodeId VolvisNodeId = -1; - FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - ConnectedAssetId, 0, &VolvisNodeId); - - // First, destroy the merge node and its inputs - // The merge node is in the first input of the volvis node - HAPI_NodeId MergeNodeId = -1; - FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - VolvisNodeId, 0, &MergeNodeId); - - if (MergeNodeId != -1) - { - // Get the merge node info - HAPI_NodeInfo NodeInfo; - FHoudiniApi::NodeInfo_Init(&NodeInfo); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( - FHoudiniEngine::Get().GetSession(), MergeNodeId, &NodeInfo), false); - - for (int32 n = 0; n < NodeInfo.inputCount; n++) - { - // Get the Input node ID from the host ID - HAPI_NodeId InputNodeId = -1; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( - FHoudiniEngine::Get().GetSession(), - MergeNodeId, n, &InputNodeId)) - break; - - if (InputNodeId == -1) - break; - - // Disconnect and Destroy that input - FHoudiniEngineUtils::HapiDisconnectAsset(MergeNodeId, n); - FHoudiniEngineUtils::DestroyHoudiniAsset(InputNodeId); - } - } - - // Second step, destroy all the volumes GEO assets - for (HAPI_NodeId AssetNodeId : CreatedInputAssetIds) - { - FHoudiniEngineUtils::DestroyHoudiniAsset(AssetNodeId); - } - CreatedInputAssetIds.Empty(); - - // Finally disconnect and destroy the xform, volvis and merge nodes, then destroy them - FHoudiniEngineUtils::HapiDisconnectAsset(ConnectedAssetId, 0); - FHoudiniEngineUtils::HapiDisconnectAsset(VolvisNodeId, 0); - FHoudiniEngineUtils::DestroyHoudiniAsset(MergeNodeId); - FHoudiniEngineUtils::DestroyHoudiniAsset(VolvisNodeId); - - return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); -} - - -bool -FUnrealLandscapeTranslator::ExtractLandscapeData( - ALandscapeProxy * LandscapeProxy, TSet& SelectedComponents, - const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, - TArray& LandscapePositionArray, - TArray& LandscapeNormalArray, - TArray& LandscapeUVArray, - TArray& LandscapeComponentVertexIndicesArray, - TArray& LandscapeComponentNameArray, - TArray& LandscapeLightmapValues) -{ - if (!LandscapeProxy) - return false; - - if (SelectedComponents.Num() < 1) - return false; - - // Get runtime settings. - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Calc all the needed sizes - int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; - float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; - - int32 NumComponents = SelectedComponents.Num(); - bool bExportOnlySelected = NumComponents != LandscapeProxy->LandscapeComponents.Num(); - - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - int32 VertexCount = NumComponents * VertexCountPerComponent; - if (!VertexCount) - return false; - - // Initialize the data arrays - LandscapePositionArray.SetNumUninitialized(VertexCount); - LandscapeNormalArray.SetNumUninitialized(VertexCount); - LandscapeUVArray.SetNumUninitialized(VertexCount); - LandscapeComponentNameArray.SetNumUninitialized(VertexCount); - LandscapeComponentVertexIndicesArray.SetNumUninitialized(VertexCount); - if (bExportLighting) - LandscapeLightmapValues.SetNumUninitialized(VertexCount); - - //----------------------------------------------------------------------------------------------------------------- - // EXTRACT THE LANDSCAPE DATA - //----------------------------------------------------------------------------------------------------------------- - FIntPoint IntPointMax = FIntPoint::ZeroValue; - - int32 AllPositionsIdx = 0; - for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) - { - ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; - if (bExportOnlySelected && !SelectedComponents.Contains(LandscapeComponent)) - continue; - - TArray64< uint8 > LightmapMipData; - int32 LightmapMipSizeX = 0; - int32 LightmapMipSizeY = 0; - - // See if we need to export lighting information. - if (bExportLighting) - { - const FMeshMapBuildData* MapBuildData = LandscapeComponent->GetMeshMapBuildData(); - FLightMap2D* LightMap2D = MapBuildData && MapBuildData->LightMap ? MapBuildData->LightMap->GetLightMap2D() : nullptr; - if (LightMap2D && LightMap2D->IsValid(0)) - { - UTexture2D * TextureLightmap = LightMap2D->GetTexture(0); - if (TextureLightmap) - { - if (TextureLightmap->Source.GetMipData(LightmapMipData, 0, 0, 0, nullptr)) - { - LightmapMipSizeX = TextureLightmap->Source.GetSizeX(); - LightmapMipSizeY = TextureLightmap->Source.GetSizeY(); - } - else - { - LightmapMipData.Empty(); - } - } - } - } - - // Construct landscape component data interface to access raw data. - FLandscapeComponentDataInterface CDI(LandscapeComponent, LandscapeProxy->ExportLOD); - - // Get name of this landscape component. - const char * LandscapeComponentNameStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->GetName()); - for (int32 VertexIdx = 0; VertexIdx < VertexCountPerComponent; VertexIdx++) - { - int32 VertX = 0; - int32 VertY = 0; - CDI.VertexIndexToXY(VertexIdx, VertX, VertY); - - // Get position. - FVector PositionVector = CDI.GetWorldVertex(VertX, VertY); - - // Get normal / tangent / binormal. - FVector Normal = FVector::ZeroVector; - FVector TangentX = FVector::ZeroVector; - FVector TangentY = FVector::ZeroVector; - CDI.GetLocalTangentVectors(VertX, VertY, TangentX, TangentY, Normal); - - // Export UVs. - FVector TextureUV = FVector::ZeroVector; - if (bExportTileUVs) - { - // We want to export uvs per tile. - TextureUV = FVector(VertX, VertY, 0.0f); - - // If we need to normalize UV space. - if (bExportNormalizedUVs) - TextureUV /= ComponentSizeQuads; - } - else - { - // We want to export global uvs (default). - FIntPoint IntPoint = LandscapeComponent->GetSectionBase(); - TextureUV = FVector(VertX * ScaleFactor + IntPoint.X, VertY * ScaleFactor + IntPoint.Y, 0.0f); - - // Keep track of max offset. - IntPointMax = IntPointMax.ComponentMax(IntPoint); - } - - if (bExportLighting) - { - FLinearColor VertexLightmapColor(0.0f, 0.0f, 0.0f, 1.0f); - if (LightmapMipData.Num() > 0) - { - FVector2D UVCoord(VertX, VertY); - UVCoord /= (ComponentSizeQuads + 1); - - FColor LightmapColorRaw = PickVertexColorFromTextureMip( - LightmapMipData.GetData(), UVCoord, LightmapMipSizeX, LightmapMipSizeY); - - VertexLightmapColor = LightmapColorRaw.ReinterpretAsLinear(); - } - - LandscapeLightmapValues[AllPositionsIdx] = VertexLightmapColor; - } - - // Retrieve component transform. - const FTransform & ComponentTransform = LandscapeComponent->GetComponentTransform(); - - // Retrieve component scale. - const FVector & ScaleVector = ComponentTransform.GetScale3D(); - - // Perform normalization. - Normal /= ScaleVector; - Normal.Normalize(); - - TangentX /= ScaleVector; - TangentX.Normalize(); - - TangentY /= ScaleVector; - TangentY.Normalize(); - - // Perform position scaling. - FVector PositionTransformed = PositionVector / HAPI_UNREAL_SCALE_FACTOR_POSITION; - LandscapePositionArray[AllPositionsIdx].X = PositionTransformed.X; - LandscapePositionArray[AllPositionsIdx].Y = PositionTransformed.Z; - LandscapePositionArray[AllPositionsIdx].Z = PositionTransformed.Y; - - Swap(Normal.Y, Normal.Z); - - // Store landscape component name for this point. - LandscapeComponentNameArray[AllPositionsIdx] = LandscapeComponentNameStr; - - // Store vertex index (x,y) for this point. - LandscapeComponentVertexIndicesArray[AllPositionsIdx].X = VertX; - LandscapeComponentVertexIndicesArray[AllPositionsIdx].Y = VertY; - - // Store point normal. - LandscapeNormalArray[AllPositionsIdx] = Normal; - - // Store uv. - LandscapeUVArray[AllPositionsIdx] = TextureUV; - - AllPositionsIdx++; - } - - // Free the memory allocated for LandscapeComponentNameStr - FHoudiniEngineUtils::FreeRawStringMemory(LandscapeComponentNameStr); - } - - // If we need to normalize UV space and we are doing global UVs. - if (!bExportTileUVs && bExportNormalizedUVs) - { - IntPointMax += FIntPoint(ComponentSizeQuads, ComponentSizeQuads); - IntPointMax = IntPointMax.ComponentMax(FIntPoint(1, 1)); - - for (int32 UVIdx = 0; UVIdx < VertexCount; ++UVIdx) - { - FVector & PositionUV = LandscapeUVArray[UVIdx]; - PositionUV.X /= IntPointMax.X; - PositionUV.Y /= IntPointMax.Y; - } - } - - return true; -} - -FColor -FUnrealLandscapeTranslator::PickVertexColorFromTextureMip( - const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight) -{ - check(MipBytes); - - FColor ResultColor(0, 0, 0, 255); - - if (UVCoord.X >= 0.0f && UVCoord.X < 1.0f && UVCoord.Y >= 0.0f && UVCoord.Y < 1.0f) - { - const int32 X = MipWidth * UVCoord.X; - const int32 Y = MipHeight * UVCoord.Y; - - const int32 Index = ((Y * MipWidth) + X) * 4; - - ResultColor.B = MipBytes[Index + 0]; - ResultColor.G = MipBytes[Index + 1]; - ResultColor.R = MipBytes[Index + 2]; - ResultColor.A = MipBytes[Index + 3]; - } - - return ResultColor; -} - -bool -FUnrealLandscapeTranslator::AddLandscapePositionAttribute(const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray) -{ - int32 VertexCount = LandscapePositionArray.Num(); - if (VertexCount < 3) - return false; - - // Create point attribute info containing positions. - HAPI_AttributeInfo AttributeInfoPointPosition; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointPosition); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointPosition ); - AttributeInfoPointPosition.count = VertexCount; - AttributeInfoPointPosition.tupleSize = 3; - AttributeInfoPointPosition.exists = true; - AttributeInfoPointPosition.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointPosition.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointPosition.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition), false); - - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition, - (const float *)LandscapePositionArray.GetData(), - 0, AttributeInfoPointPosition.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeNormalAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeNormalArray) -{ - int32 VertexCount = LandscapeNormalArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointNormal; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointNormal); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointNormal ); - AttributeInfoPointNormal.count = VertexCount; - AttributeInfoPointNormal.tupleSize = 3; - AttributeInfoPointNormal.exists = true; - AttributeInfoPointNormal.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointNormal.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointNormal.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal, - (const float *)LandscapeNormalArray.GetData(), 0, VertexCount), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeUVAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeUVArray) -{ - int32 VertexCount = LandscapeUVArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointUV; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointUV); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointUV ); - AttributeInfoPointUV.count = VertexCount; - AttributeInfoPointUV.tupleSize = 3; - AttributeInfoPointUV.exists = true; - AttributeInfoPointUV.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointUV.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointUV.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV, - (const float *)LandscapeUVArray.GetData(), 0, AttributeInfoPointUV.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeComponentVertexIndicesAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentVertexIndicesArray) -{ - int32 VertexCount = LandscapeComponentVertexIndicesArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoPointLandscapeComponentVertexIndices; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentVertexIndices); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentVertexIndices ); - AttributeInfoPointLandscapeComponentVertexIndices.count = VertexCount; - AttributeInfoPointLandscapeComponentVertexIndices.tupleSize = 2; - AttributeInfoPointLandscapeComponentVertexIndices.exists = true; - AttributeInfoPointLandscapeComponentVertexIndices.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLandscapeComponentVertexIndices.storage = HAPI_STORAGETYPE_INT; - AttributeInfoPointLandscapeComponentVertexIndices.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, - &AttributeInfoPointLandscapeComponentVertexIndices), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, - &AttributeInfoPointLandscapeComponentVertexIndices, - (const int *)LandscapeComponentVertexIndicesArray.GetData(), 0, - AttributeInfoPointLandscapeComponentVertexIndices.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeComponentNameAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray) -{ - int32 VertexCount = LandscapeComponentNameArray.Num(); - if (VertexCount < 3) - return false; - - // Create point attribute containing landscape component name. - HAPI_AttributeInfo AttributeInfoPointLandscapeComponentNames; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentNames); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentNames ); - AttributeInfoPointLandscapeComponentNames.count = VertexCount; - AttributeInfoPointLandscapeComponentNames.tupleSize = 1; - AttributeInfoPointLandscapeComponentNames.exists = true; - AttributeInfoPointLandscapeComponentNames.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLandscapeComponentNames.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPointLandscapeComponentNames.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, - &AttributeInfoPointLandscapeComponentNames), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, - &AttributeInfoPointLandscapeComponentNames, - (const char **)LandscapeComponentNameArray.GetData(), - 0, AttributeInfoPointLandscapeComponentNames.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeLightmapColorAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues) -{ - int32 VertexCount = LandscapeLightmapValues.Num(); - - HAPI_AttributeInfo AttributeInfoPointLightmapColor; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLightmapColor); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLightmapColor ); - AttributeInfoPointLightmapColor.count = VertexCount; - AttributeInfoPointLightmapColor.tupleSize = 4; - AttributeInfoPointLightmapColor.exists = true; - AttributeInfoPointLightmapColor.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPointLightmapColor.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPointLightmapColor.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, - 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor, - (const float *)LandscapeLightmapValues.GetData(), 0, - AttributeInfoPointLightmapColor.count), false); - - return true; -} - -bool -FUnrealLandscapeTranslator::AddLandscapeMeshIndicesAndMaterialsAttribute( - const HAPI_NodeId& NodeId, const bool& bExportMaterials, - const int32& ComponentSizeQuads, const int32& QuadCount, - ALandscapeProxy * LandscapeProxy, - const TSet< ULandscapeComponent * >& SelectedComponents) -{ - if (!LandscapeProxy) - return false; - - // Compute number of necessary indices. - int32 IndexCount = QuadCount * 4; - if (IndexCount < 0) - return false; - - int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); - - // Array holding indices data. - TArray LandscapeIndices; - LandscapeIndices.SetNumUninitialized(IndexCount); - - // Allocate space for face names. - // The LandscapeMaterial and HoleMaterial per point - TArray FaceMaterials; - TArray FaceHoleMaterials; - FaceMaterials.SetNumUninitialized(QuadCount); - FaceHoleMaterials.SetNumUninitialized(QuadCount); - - int32 VertIdx = 0; - int32 QuadIdx = 0; - - const char * MaterialRawStr = nullptr; - const char * MaterialHoleRawStr = nullptr; - - // Lambda for freeing the memory allocated by ExtractRawString and returning - auto FreeMemoryReturn = [&MaterialRawStr, &MaterialHoleRawStr](const bool& bReturn) - { - FHoudiniEngineUtils::FreeRawStringMemory(MaterialRawStr); - FHoudiniEngineUtils::FreeRawStringMemory(MaterialHoleRawStr); - - return bReturn; - }; - - const int32 QuadComponentCount = ComponentSizeQuads + 1; - for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) - { - ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; - if (!SelectedComponents.Contains(LandscapeComponent)) - continue; - - if (bExportMaterials) - { - // If component has an override material, we need to get the raw name (if exporting materials). - if (LandscapeComponent->OverrideMaterial) - { - MaterialRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideMaterial->GetName()); - } - - // If component has an override hole material, we need to get the raw name (if exporting materials). - if (LandscapeComponent->OverrideHoleMaterial) - { - MaterialHoleRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideHoleMaterial->GetName()); - } - } - - int32 BaseVertIndex = ComponentIdx * VertexCountPerComponent; - for (int32 YIdx = 0; YIdx < ComponentSizeQuads; YIdx++) - { - for (int32 XIdx = 0; XIdx < ComponentSizeQuads; XIdx++) - { - LandscapeIndices[VertIdx + 0] = BaseVertIndex + (XIdx + 0) + (YIdx + 0) * QuadComponentCount; - LandscapeIndices[VertIdx + 1] = BaseVertIndex + (XIdx + 1) + (YIdx + 0) * QuadComponentCount; - LandscapeIndices[VertIdx + 2] = BaseVertIndex + (XIdx + 1) + (YIdx + 1) * QuadComponentCount; - LandscapeIndices[VertIdx + 3] = BaseVertIndex + (XIdx + 0) + (YIdx + 1) * QuadComponentCount; - - // Store override materials (if exporting materials). - if (bExportMaterials) - { - FaceMaterials[QuadIdx] = MaterialRawStr; - FaceHoleMaterials[QuadIdx] = MaterialHoleRawStr; - } - - VertIdx += 4; - QuadIdx++; - } - } - } - - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, LandscapeIndices.GetData(), 0, LandscapeIndices.Num()), - FreeMemoryReturn(false)); - - // We need to generate array of face counts. - TArray LandscapeFaces; - LandscapeFaces.Init(4, QuadCount); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, LandscapeFaces.GetData(), 0, LandscapeFaces.Num()), - FreeMemoryReturn(false)); - - if (bExportMaterials) - { - if (!FaceMaterials.Contains(nullptr)) - { - // Marshall in override primitive material names. - HAPI_AttributeInfo AttributeInfoPrimitiveMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterial ); - AttributeInfoPrimitiveMaterial.count = FaceMaterials.Num(); - AttributeInfoPrimitiveMaterial.tupleSize = 1; - AttributeInfoPrimitiveMaterial.exists = true; - AttributeInfoPrimitiveMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoPrimitiveMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPrimitiveMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial, - (const char **)FaceMaterials.GetData(), 0, AttributeInfoPrimitiveMaterial.count), - FreeMemoryReturn(false)); - } - - if (!FaceHoleMaterials.Contains(nullptr)) - { - // Marshall in override primitive material hole names. - HAPI_AttributeInfo AttributeInfoPrimitiveMaterialHole; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterialHole); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterialHole ); - AttributeInfoPrimitiveMaterialHole.count = FaceHoleMaterials.Num(); - AttributeInfoPrimitiveMaterialHole.tupleSize = 1; - AttributeInfoPrimitiveMaterialHole.exists = true; - AttributeInfoPrimitiveMaterialHole.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoPrimitiveMaterialHole.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoPrimitiveMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoPrimitiveMaterialHole), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoPrimitiveMaterialHole, (const char **)FaceHoleMaterials.GetData(), 0, - AttributeInfoPrimitiveMaterialHole.count), - FreeMemoryReturn(false)); - } - } - - // Free the memory and return true - return FreeMemoryReturn(true); -} - -bool -FUnrealLandscapeTranslator::AddLandscapeGlobalMaterialAttribute( - const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy) -{ - if (!LandscapeProxy) - return false; - - // If there's a global landscape material, we marshall it as detail. - UMaterialInterface * MaterialInterface = LandscapeProxy->GetLandscapeMaterial(); - const char * MaterialNameStr = ""; - if (MaterialInterface) - { - FString FullMaterialName = MaterialInterface->GetPathName(); - MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); - } - - HAPI_AttributeInfo AttributeInfoDetailMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterial); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterial ); - AttributeInfoDetailMaterial.count = 1; - AttributeInfoDetailMaterial.tupleSize = 1; - AttributeInfoDetailMaterial.exists = true; - AttributeInfoDetailMaterial.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoDetailMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoDetailMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial, - (const char**)&MaterialNameStr, 0, AttributeInfoDetailMaterial.count), false); - - // If there's a global landscape hole material, we marshall it as detail. - UMaterialInterface * HoleMaterialInterface = LandscapeProxy->GetLandscapeHoleMaterial(); - const char * HoleMaterialNameStr = ""; - if (HoleMaterialInterface) - { - FString FullMaterialName = HoleMaterialInterface->GetPathName(); - MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); - } - - HAPI_AttributeInfo AttributeInfoDetailMaterialHole; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterialHole); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterialHole ); - AttributeInfoDetailMaterialHole.count = 1; - AttributeInfoDetailMaterialHole.tupleSize = 1; - AttributeInfoDetailMaterialHole.exists = true; - AttributeInfoDetailMaterialHole.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoDetailMaterialHole.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoDetailMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoDetailMaterialHole), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, - &AttributeInfoDetailMaterialHole, (const char **)&HoleMaterialNameStr, 0, - AttributeInfoDetailMaterialHole.count), false); - - return true; -} - - -bool -FUnrealLandscapeTranslator::AddLandscapeLayerAttribute( - const HAPI_NodeId& NodeId, const TArray& LandscapeLayerArray, const FString& LayerName) -{ - int32 VertexCount = LandscapeLayerArray.Num(); - if (VertexCount < 3) - return false; - - HAPI_AttributeInfo AttributeInfoLayer; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLayer); - AttributeInfoLayer.count = VertexCount; - AttributeInfoLayer.tupleSize = 1; - AttributeInfoLayer.exists = true; - AttributeInfoLayer.owner = HAPI_ATTROWNER_POINT; - AttributeInfoLayer.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLayer.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, - TCHAR_TO_ANSI(*LayerName), - &AttributeInfoLayer), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, - TCHAR_TO_ANSI(*LayerName), - &AttributeInfoLayer, - (const float *)LandscapeLayerArray.GetData(), - 0, AttributeInfoLayer.count), false); - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniApi.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineString.h" + +#include "UnrealLandscapeTranslator.h" +#include "HoudiniGeoPartObject.h" + +#include "Landscape.h" +#include "LandscapeDataAccess.h" +#include "LandscapeEdit.h" +#include "LightMap.h" +#include "Engine/MapBuildDataRegistry.h" +#include "PhysicalMaterials/PhysicalMaterial.h" + + +bool +FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( + ALandscapeProxy* LandscapeProxy, + HAPI_NodeId& CreatedNodeId, + const FString& InputNodeNameString, + const bool& bExportGeometryAsMesh, + const bool& bExportTileUVs, + const bool bExportNormalizedUVs, + const bool bExportLighting, + const bool bExportMaterials ) +{ + //-------------------------------------------------------------------------------------------------- + // 1. Create an input node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId InputNodeId = -1; + // Create the curve SOP Node + std::string NodeNameRawString; + FHoudiniEngineUtils::ConvertUnrealString(InputNodeNameString, NodeNameRawString); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &InputNodeId, NodeNameRawString.c_str()), false); + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InputNodeId)) + return false; + + // We now have a valid id. + CreatedNodeId = InputNodeId; + + if(!FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true)) + return false; + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); + */ + //-------------------------------------------------------------------------------------------------- + // 2. Set the part info + //-------------------------------------------------------------------------------------------------- + int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; + float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; + + //int32 NumComponents = bExportOnlySelected ? SelectedComponents.Num() : LandscapeProxy->LandscapeComponents.Num(); + int32 NumComponents = LandscapeProxy->LandscapeComponents.Num(); + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + int32 VertexCount = NumComponents * VertexCountPerComponent; + if (!VertexCount) + return false; + + int32 TriangleCount = NumComponents * FMath::Square(ComponentSizeQuads) * 2; + int32 QuadCount = NumComponents * FMath::Square(ComponentSizeQuads); + int32 IndexCount = QuadCount * 4; + + // Create part info + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + //FMemory::Memzero< HAPI_PartInfo >(Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.pointCount = VertexCount; + Part.type = HAPI_PARTTYPE_MESH; + + // If we are exporting to a mesh, we need vertices and faces + if (bExportGeometryAsMesh) + { + Part.vertexCount = IndexCount; + Part.faceCount = QuadCount; + } + + // Set the part infos + HAPI_GeoInfo DisplayGeoInfo; + FHoudiniApi::GeoInfo_Init(&DisplayGeoInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetDisplayGeoInfo( + FHoudiniEngine::Get().GetSession(), CreatedNodeId, &DisplayGeoInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId, 0, &Part), false); + + //-------------------------------------------------------------------------------------------------- + // 3. Extract the landscape data + //-------------------------------------------------------------------------------------------------- + // Array for the position data + TArray LandscapePositionArray; + // Array for the normals + TArray LandscapeNormalArray; + // Array for the UVs + TArray LandscapeUVArray; + // Array for the vertex index of each point in its component + TArray LandscapeComponentVertexIndicesArray; + // Array for the tile names per point + TArray LandscapeComponentNameArray; + // Array for the lightmap values + TArray LandscapeLightmapValues; + // Selected components set to all components in current landscape proxy + TSet SelectedComponents; + SelectedComponents.Append(LandscapeProxy->LandscapeComponents); + + // Extract all the data from the landscape to the arrays + if (!ExtractLandscapeData( + LandscapeProxy, SelectedComponents, + bExportLighting, bExportTileUVs, bExportNormalizedUVs, + LandscapePositionArray, LandscapeNormalArray, + LandscapeUVArray, LandscapeComponentVertexIndicesArray, + LandscapeComponentNameArray, LandscapeLightmapValues)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Set the corresponding attributes in Houdini + //-------------------------------------------------------------------------------------------------- + + // Create point attribute info containing positions. + if (!AddLandscapePositionAttribute(DisplayGeoInfo.nodeId, LandscapePositionArray)) + return false; + + // Create point attribute info containing normals. + if (!AddLandscapeNormalAttribute(DisplayGeoInfo.nodeId, LandscapeNormalArray)) + return false; + + // Create point attribute info containing UVs. + if (!AddLandscapeUVAttribute(DisplayGeoInfo.nodeId, LandscapeUVArray)) + return false; + + // Create point attribute containing landscape component vertex indices (indices of vertices within the grid - x,y). + if (!AddLandscapeComponentVertexIndicesAttribute(DisplayGeoInfo.nodeId, LandscapeComponentVertexIndicesArray)) + return false; + + // Create point attribute containing landscape component name. + if (!AddLandscapeComponentNameAttribute(DisplayGeoInfo.nodeId, LandscapeComponentNameArray)) + return false; + + // Create point attribute info containing lightmap information. + if (bExportLighting) + { + if (!AddLandscapeLightmapColorAttribute(DisplayGeoInfo.nodeId, LandscapeLightmapValues)) + return false; + } + + // Set indices if we are exporting full geometry. + if (bExportGeometryAsMesh) + { + if (!AddLandscapeMeshIndicesAndMaterialsAttribute( + DisplayGeoInfo.nodeId, + bExportMaterials, + ComponentSizeQuads, + QuadCount, + LandscapeProxy, + SelectedComponents)) + return false; + } + + // If we are marshalling material information. + if (bExportMaterials) + { + if (!AddLandscapeGlobalMaterialAttribute(DisplayGeoInfo.nodeId, LandscapeProxy)) + return false; + } + + /* + // TODO: Move this to ExtractLandscapeData() + //-------------------------------------------------------------------------------------------------- + // 4. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Get the landscape X/Y Size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + if (!LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY)) + return false; + + // Calc the X/Y size in points + int32 XSize = (MaxX - MinX + 1); + int32 YSize = (MaxY - MinY + 1); + if ((XSize < 2) || (YSize < 2)) + return false; + + bool MaskInitialized = false; + int32 NumLayers = LandscapeInfo->Layers.Num(); + for (int32 n = 0; n < NumLayers; n++) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!GetLandscapeLayerData( + LandscapeInfo, n, + MinX, MinY, MaxX, MaxY, + CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray CurrentLayerFloatData; + if (!ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo)) + continue; + + if (!AddLandscapeLayerAttribute( + DisplayGeoInfo.nodeId, CurrentLayerFloatData, LayerName)) + continue; + } + */ + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), DisplayGeoInfo.nodeId), false); + + // TODO: Remove me! + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), InputNodeId, &CookOptions), false); + */ + + return FHoudiniEngineUtils::HapiCookNode(InputNodeId, nullptr, true); +} + +bool +FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( + ALandscapeProxy* LandscapeProxy, HAPI_NodeId& CreatedHeightfieldNodeId, const FString& InputNodeNameStr) +{ + if (!LandscapeProxy) + return false; + + // Export the whole landscape and its layer as a single heightfield. + FString NodeName = InputNodeNameStr + TEXT("_") + LandscapeProxy->GetName(); + + //-------------------------------------------------------------------------------------------------- + // 1. Extracting the height data + //-------------------------------------------------------------------------------------------------- + TArray HeightData; + int32 XSize, YSize; + FVector Min, Max; + if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + HAPI_VolumeInfo HeightfieldVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + //FTransform LandscapeTransform = LandscapeProxy->LandscapeActorToWorld();// LandscapeProxy->ActorToWorld(); + + // Get the actual transform of this proxy, not the landscape actor's transform! + FTransform LandscapeTM = LandscapeProxy->LandscapeActorToWorld(); + FTransform ProxyRelativeTM(FVector(LandscapeProxy->LandscapeSectionOffset)); + FTransform LandscapeTransform = ProxyRelativeTM * LandscapeTM; + + FVector CenterOffset = FVector::ZeroVector; + if (!ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId HeightFieldId = -1; + HAPI_NodeId HeightId = -1; + HAPI_NodeId MaskId = -1; + HAPI_NodeId MergeId = -1; + if (!CreateHeightfieldInputNode(NodeName, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + HAPI_PartId PartId = 0; + if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) + return false; + + // Apply attributes to the heightfield + ApplyAttributesToHeightfieldNode(HeightId, PartId, LandscapeProxy); + + // Commit the height volume + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), HeightId), false); + + //-------------------------------------------------------------------------------------------------- + // 5. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + bool MaskInitialized = false; + int32 MergeInputIndex = 2; + + auto MergeInputFn = [&MergeInputIndex] (const HAPI_NodeId MergeId, const HAPI_NodeId NodeId) -> HAPI_Result + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HAPI_Result Result = FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex, NodeId, 0); + + if (Result == HAPI_RESULT_SUCCESS) + { + MergeInputIndex++; + } + return Result; + }; + + int32 NumLayers = LandscapeInfo->Layers.Num(); + for (int32 n = 0; n < NumLayers; n++) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!GetLandscapeLayerData(LandscapeProxy, LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray CurrentLayerFloatData; + if (!ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo)) + continue; + + // We reuse the height layer's transform + CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; + + // 3. See if we need to create an input volume, or can reuse the HF's default mask volume + bool IsMask = false; + if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) + IsMask = true; + + HAPI_NodeId LayerVolumeNodeId = -1; + if (!IsMask) + { + // Current layer is not mask, so we need to create a new input volume + std::string LayerNameStr; + FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); + } + else + { + // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node + LayerVolumeNodeId = MaskId; + } + + // Check if we have a valid id for the input volume. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) + continue; + + // 4. Set the layer/mask heighfield data in Houdini + HAPI_PartId CurrentPartId = 0; + if (!SetHeightfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) + continue; + + // Get the physical material used by that layer + UPhysicalMaterial* LayerPhysicalMat = LandscapeProxy->DefaultPhysMaterial; + { + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (LayerInfo) + LayerPhysicalMat = LayerInfo->PhysMaterial; + } + + // Apply attributes to the heightfield input node + ApplyAttributesToHeightfieldNode(LayerVolumeNodeId, PartId, LandscapeProxy); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); + + if (!IsMask) + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN(MergeInputFn(MergeId, LayerVolumeNodeId), false); + } + else + { + MaskInitialized = true; + } + } + + // We need to have a mask layer as it is required for proper heightfield functionalities + // Setting the volume info on the mask is needed for the HF to have proper transform in H! + // If we didn't create a mask volume before, send a default one now + if (!MaskInitialized) + { + MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); + + ApplyAttributesToHeightfieldNode(MaskId, PartId, LandscapeProxy); + + // Commit the mask volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), MaskId), false); + } + + // We need a valid landscape actor to get the edit layers + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if(IsValid(Landscape)) + { + //-------------------------------------------------------------------------------------------------- + // Create heightfield input for each editable landscape layer + //-------------------------------------------------------------------------------------------------- + HAPI_VolumeInfo LayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + + for(FLandscapeLayer& Layer : Landscape->LandscapeLayers) + { + const FString LayerVolumeName = FString::Format(TEXT("landscapelayer_{0}"), {Layer.Name.ToString()}); + + HAPI_NodeId LandscapeLayerNodeId = -1; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape] Creating input node for editable landscape layer: %s"), *LayerVolumeName); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, + &LandscapeLayerNodeId, + TCHAR_TO_UTF8(*LayerVolumeName), + XSize, YSize, + 1.f + ), false); + + // Create a volume visualization node + const FString VisualizationName = FString::Format(TEXT("visualization_{0}"), {Layer.Name.ToString()}); + HAPI_NodeId VisualizationNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, + "volumevisualization", + TCHAR_TO_UTF8(*VisualizationName), + false, + &VisualizationNodeId + ), false); + + // Set Visualization Mode to Height Field + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, + "vismode", + 0, 2 + ), false); + + // Set Density Field to '*'. + HAPI_ParmId DensityFieldParmId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetParmIdFromName( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, + "densityfield", + &DensityFieldParmId + ), false); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, + "*", + DensityFieldParmId, 0 + ), false); + + // Create a visibility node + const FString VisibilityName = FString::Format(TEXT("visibility_{0}"), {Layer.Name.ToString()}); + HAPI_NodeId VisibilityNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, + "visibility", + TCHAR_TO_UTF8(*VisibilityName), + false, + &VisibilityNodeId + ), false); + + // Connect landscape layer to visualization + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + VisualizationNodeId, 0, LandscapeLayerNodeId, 0), false); + + // Connect visualization to visibility + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + VisibilityNodeId, 0, VisualizationNodeId, 0), false); + + // Connect the visibility node to the merge input + HOUDINI_CHECK_ERROR_RETURN(MergeInputFn(MergeId, VisibilityNodeId), false); + + FScopedSetLandscapeEditingLayer Scope(Landscape, Layer.Guid ); // Scope landscape access to the current layer + + TArray LayerHeightData; + TArray LayerHeightFloatData; + //-------------------------------------------------------------------------------------------------- + // Extracting height data + //-------------------------------------------------------------------------------------------------- + if (!GetLandscapeData(LandscapeProxy, LayerHeightData, XSize, YSize, Min, Max)) + return false; + + //-------------------------------------------------------------------------------------------------- + // Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + if (!ConvertLandscapeDataToHeightfieldData( + LayerHeightData, XSize, YSize, Min, Max, LandscapeTransform, + LayerHeightFloatData, LayerVolumeInfo, CenterOffset)) + return false; + + HAPI_PartId LayerPartId = 0; + SetHeightfieldData(LandscapeLayerNodeId, LayerPartId, LayerHeightFloatData, LayerVolumeInfo, LayerVolumeName); + + // Apply attributes to the heightfield input node + ApplyAttributesToHeightfieldNode(LandscapeLayerNodeId, 0, LandscapeProxy); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LandscapeLayerNodeId), false); + } + } + + HAPI_TransformEuler HAPIObjectTransform; + FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); + LandscapeTransform.SetScale3D(FVector::OneVector); + FHoudiniEngineUtils::TranslateUnrealTransform(LandscapeTransform, HAPIObjectTransform); + HAPIObjectTransform.position[1] = 0.0f; + + HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(HeightFieldId); + FHoudiniApi::SetObjectTransform(FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform); + + // Since HF are centered but landscape aren't, we need to set the HF's center parameter + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); + + // Finally, cook the Heightfield node + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightFieldId, &CookOptions), false); + */ + if(!FHoudiniEngineUtils::HapiCookNode(HeightFieldId, nullptr, true)) + return false; + + CreatedHeightfieldNodeId = HeightFieldId; + + return true; +} + +bool +FUnrealLandscapeTranslator::CreateInputNodeForLandscape( + ALandscapeProxy* LandscapeProxy, + const FString& InputNodeNameStr, + const FString& HeightFieldName, + const FTransform& LandscapeTransform, + FVector& CenterOffset, + HAPI_NodeId& HeightId, + HAPI_PartId& PartId, + HAPI_NodeId& HeightFieldId, + HAPI_NodeId& MaskId, + HAPI_NodeId& MergeId, + TArray& HeightData, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + int32& XSize, int32& YSize + ) +{ + //-------------------------------------------------------------------------------------------------- + // 1. Extracting the height data + //-------------------------------------------------------------------------------------------------- + + FVector Min, Max; + + if (!GetLandscapeData(LandscapeProxy, HeightData, XSize, YSize, Min, Max)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + + if (!ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, CenterOffset)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + if (!CreateHeightfieldInputNode(InputNodeNameStr, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + return false; + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, HeightFieldName)) + return false; + + return true; +} + +// Converts Unreal uint16 values to Houdini Float +bool +FUnrealLandscapeTranslator::ConvertLandscapeLayerDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + const FLinearColor& LayerUsageDebugColor, + TArray& LayerFloatValues, + HAPI_VolumeInfo& LayerVolumeInfo) +{ + LayerFloatValues.Empty(); + + int32 HoudiniXSize = YSize; + int32 HoudiniYSize = XSize; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + if (IntHeightData.Num() != SizeInPoints) + return false; + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to float + //-------------------------------------------------------------------------------------------------- + + // By default, values are converted from unreal [0 255] uint8 to Houdini [0 1] float + // uint8 min/max + uint8 IntMin = 0; + uint8 IntMax = UINT8_MAX; + // The range in Digits + double DigitRange = (double)UINT8_MAX; + + // By default, the values will be converted to [0, 1] + float LayerMin = 0.0f; + float LayerMax = 1.0f; + float LayerSpacing = 1.0f / DigitRange; + + // If this layer came from Houdini, its alpha value should be PI + // This indicates that we can extract additional infos stored its debug usage color + // so we can reconstruct the original source values (float) more accurately + if (LayerUsageDebugColor.A == PI) + { + // We need the ZMin / ZMax uint8 values + IntMin = IntHeightData[0]; + IntMax = IntMin; + for (int n = 0; n < IntHeightData.Num(); n++) + { + if (IntHeightData[n] < IntMin) + IntMin = IntHeightData[n]; + if (IntHeightData[n] > IntMax) + IntMax = IntHeightData[n]; + } + + DigitRange = (double)IntMax - (double)IntMin; + + // Read the original min/max and spacing stored in the debug color + LayerMin = LayerUsageDebugColor.R; + LayerMax = LayerUsageDebugColor.G; + LayerSpacing = LayerUsageDebugColor.B; + } + + // Convert the Int data to Float + LayerFloatValues.SetNumUninitialized(SizeInPoints); + + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // We need to invert X/Y when reading the value from Unreal + int32 nHoudini = nX + nY * HoudiniXSize; + int32 nUnreal = nY + nX * XSize; + + // Convert the int values to meter + // Unreal's digit value have a zero value of 32768 + double DoubleValue = ((double)IntHeightData[nUnreal] - (double)IntMin) * LayerSpacing + LayerMin; + LayerFloatValues[nHoudini] = (float)DoubleValue; + } + } + + /* + // Verifying the converted ZMin / ZMax + float FloatMin = LayerFloatValues[0]; + float FloatMax = FloatMin; + for (int32 n = 0; n < LayerFloatValues.Num(); n++) + { + if (LayerFloatValues[n] < FloatMin) + FloatMin = LayerFloatValues[n]; + if (LayerFloatValues[n] > FloatMax) + FloatMax = LayerFloatValues[n]; + } + */ + + //-------------------------------------------------------------------------------------------------- + // 2. Fill the volume info + //-------------------------------------------------------------------------------------------------- + LayerVolumeInfo.xLength = HoudiniXSize; + LayerVolumeInfo.yLength = HoudiniYSize; + LayerVolumeInfo.zLength = 1; + + LayerVolumeInfo.minX = 0; + LayerVolumeInfo.minY = 0; + LayerVolumeInfo.minZ = 0; + + LayerVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; + LayerVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; + LayerVolumeInfo.tupleSize = 1; + LayerVolumeInfo.tileSize = 1; + + LayerVolumeInfo.hasTaper = false; + LayerVolumeInfo.xTaper = 0.0; + LayerVolumeInfo.yTaper = 0.0; + + // The layer transform will have to be copied from the main heightfield's transform + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeData( + ALandscapeProxy* LandscapeProxy, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max) +{ + if (!LandscapeProxy) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + // Get the landscape extents to get its size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (LandscapeProxy == Landscape) + { + // The proxy is a landscape actor, so we have to use the landscape extent (landscape components + // may have been moved to proxies and may not be present on this actor). + LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); + } + else + { + // We only want to get the data for this landscape proxy. + // To handle streaming proxies correctly, get the extents via all the components, + // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. + for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) + { + Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); + } + } + + if (!GetLandscapeData(LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize)) + return false; + + // Get the landscape Min/Max values + // Do not use Landscape->GetActorBounds() here as instanced geo + // (due to grass layers for example) can cause it to return incorrect bounds! + FVector Origin, Extent; + GetLandscapeProxyBounds(LandscapeProxy, Origin, Extent); + + // Get the landscape Min/Max values + Min = Origin - Extent; + Max = Origin + Extent; + + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeData( + ULandscapeInfo* LandscapeInfo, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& HeightData, + int32& XSize, int32& YSize) +{ + if (!LandscapeInfo) + return false; + + // Get the X/Y size in points + XSize = (MaxX - MinX + 1); + YSize = (MaxY - MinY + 1); + + if ((XSize < 2) || (YSize < 2)) + return false; + + // Extracting the uint16 values from the landscape + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + HeightData.AddZeroed(XSize * YSize); + LandscapeEdit.GetHeightDataFast(MinX, MinY, MaxX, MaxY, HeightData.GetData(), 0); + + return true; +} + + +void +FUnrealLandscapeTranslator::GetLandscapeProxyBounds( + ALandscapeProxy* LandscapeProxy, FVector& Origin, FVector& Extents) +{ + // Iterate only on the landscape components + FBox Bounds(ForceInit); + for (const UActorComponent* ActorComponent : LandscapeProxy->GetComponents()) + { + const ULandscapeComponent* LandscapeComp = Cast(ActorComponent); + if (LandscapeComp && LandscapeComp->IsRegistered()) + Bounds += LandscapeComp->Bounds.GetBox(); + } + + // Convert the bounds to origin/offset vectors + Bounds.GetCenterAndExtents(Origin, Extents); +} + +void +FUnrealLandscapeTranslator::ApplyAttributesToHeightfieldNode( + const HAPI_NodeId HeightId, + const HAPI_PartId PartId, + ALandscapeProxy* LandscapeProxy) +{ + UMaterialInterface* LandscapeMat = LandscapeProxy->GetLandscapeMaterial(); + UMaterialInterface* LandscapeHoleMat = LandscapeProxy->GetLandscapeHoleMaterial(); + UPhysicalMaterial* LandscapePhysMat = LandscapeProxy->DefaultPhysMaterial; + + AddLandscapeMaterialAttributesToVolume(HeightId, PartId, LandscapeMat, LandscapeHoleMat, LandscapePhysMat); + + // Add the landscape's actor tags as prim attributes if we have any + FHoudiniEngineUtils::CreateAttributesFromTags(HeightId, PartId, LandscapeProxy->Tags); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(HeightId, PartId, LandscapeProxy, 1); + + // Add the unreal_level_path attribute + ULevel* Level = LandscapeProxy->GetLevel(); + if (Level) + { + FHoudiniEngineUtils::AddLevelPathAttribute(HeightId, PartId, Level, 1); + /* + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToVolume(HeightId, PartId, LevelPath); + */ + } +} + + +bool +FUnrealLandscapeTranslator::ConvertLandscapeDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + FVector Min, FVector Max, + const FTransform& LandscapeTransform, + TArray& HeightfieldFloatValues, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + FVector& CenterOffset) +{ + HeightfieldFloatValues.Empty(); + + int32 HoudiniXSize = YSize; + int32 HoudiniYSize = XSize; + int32 SizeInPoints = HoudiniXSize * HoudiniYSize; + if ((HoudiniXSize < 2) || (HoudiniYSize < 2)) + return false; + + if (IntHeightData.Num() != SizeInPoints) + return false; + + // Use default unreal scaling for marshalling landscapes + // A lot of precision will be lost in order to keep the same transform as the landscape input + bool bUseDefaultUE4Scaling = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling) + bUseDefaultUE4Scaling = HoudiniRuntimeSettings->MarshallingLandscapesUseDefaultUnrealScaling; + + //-------------------------------------------------------------------------------------------------- + // 1. Convert values to float + //-------------------------------------------------------------------------------------------------- + + + // Convert the min/max values from cm to meters + Min /= 100.0; + Max /= 100.0; + + // Unreal's landscape uses 16bits precision and range from -256m to 256m with the default scale of 100.0 + // To convert the uint16 values to float "metric" values, offset the int by 32768 to center it, + // then scale it + + // Spacing used to convert from uint16 to meters + double ZSpacing = 512.0 / ((double)UINT16_MAX); + ZSpacing *= ((double)LandscapeTransform.GetScale3D().Z / 100.0); + + // Center value in meters (Landscape ranges from [-255:257] meters at default scale + double ZCenterOffset = 32767; + double ZPositionOffset = LandscapeTransform.GetLocation().Z / 100.0f; + // Convert the Int data to Float + HeightfieldFloatValues.SetNumUninitialized(SizeInPoints); + + for (int32 nY = 0; nY < HoudiniYSize; nY++) + { + for (int32 nX = 0; nX < HoudiniXSize; nX++) + { + // We need to invert X/Y when reading the value from Unreal + int32 nHoudini = nX + nY * HoudiniXSize; + int32 nUnreal = nY + nX * XSize; + + // Convert the int values to meter + // Unreal's digit value have a zero value of 32768 + double DoubleValue = ((double)IntHeightData[nUnreal] - ZCenterOffset) * ZSpacing + ZPositionOffset; + HeightfieldFloatValues[nHoudini] = (float)DoubleValue; + } + } + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the Unreal Transform to a HAPI_transform + //-------------------------------------------------------------------------------------------------- + HAPI_Transform HapiTransform; + FHoudiniApi::Transform_Init(&HapiTransform); + //FMemory::Memzero< HAPI_Transform >( HapiTransform ); + { + FQuat Rotation = LandscapeTransform.GetRotation(); + if (Rotation != FQuat::Identity) + { + //Swap(ObjectRotation.Y, ObjectRotation.Z); + HapiTransform.rotationQuaternion[0] = Rotation.X; + HapiTransform.rotationQuaternion[1] = Rotation.Z; + HapiTransform.rotationQuaternion[2] = Rotation.Y; + HapiTransform.rotationQuaternion[3] = -Rotation.W; + } + else + { + HapiTransform.rotationQuaternion[0] = 0; + HapiTransform.rotationQuaternion[1] = 0; + HapiTransform.rotationQuaternion[2] = 0; + HapiTransform.rotationQuaternion[3] = 1; + } + + // Heightfield are centered, landscapes are not + CenterOffset = (Max - Min) * 0.5f; + + // Unreal XYZ becomes Houdini YXZ (since heightfields are also rotated due the ZX transform) + //FVector Position = LandscapeTransform.GetLocation() / 100.0f; + HapiTransform.position[1] = 0.0f;//Position.X + CenterOffset.X; + HapiTransform.position[0] = 0.0f;//Position.Y + CenterOffset.Y; + HapiTransform.position[2] = 0.0f; + + FVector Scale = LandscapeTransform.GetScale3D() / 100.0f; + HapiTransform.scale[0] = Scale.X * 0.5f * HoudiniXSize; + HapiTransform.scale[1] = Scale.Y * 0.5f * HoudiniYSize; + HapiTransform.scale[2] = 0.5f; + if (bUseDefaultUE4Scaling) + HapiTransform.scale[2] *= Scale.Z; + + HapiTransform.shear[0] = 0.0f; + HapiTransform.shear[1] = 0.0f; + HapiTransform.shear[2] = 0.0f; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Fill the volume info + //-------------------------------------------------------------------------------------------------- + HeightfieldVolumeInfo.xLength = HoudiniXSize; + HeightfieldVolumeInfo.yLength = HoudiniYSize; + HeightfieldVolumeInfo.zLength = 1; + + HeightfieldVolumeInfo.minX = 0; + HeightfieldVolumeInfo.minY = 0; + HeightfieldVolumeInfo.minZ = 0; + + HeightfieldVolumeInfo.transform = HapiTransform; + + HeightfieldVolumeInfo.type = HAPI_VOLUMETYPE_HOUDINI; + HeightfieldVolumeInfo.storage = HAPI_STORAGETYPE_FLOAT; + HeightfieldVolumeInfo.tupleSize = 1; + HeightfieldVolumeInfo.tileSize = 1; + + HeightfieldVolumeInfo.hasTaper = false; + HeightfieldVolumeInfo.xTaper = 0.0; + HeightfieldVolumeInfo.yTaper = 0.0; + + return true; +} + +bool +FUnrealLandscapeTranslator::CreateHeightfieldInputNode( + const FString& NodeName, + const int32& XSize, + const int32& YSize, + HAPI_NodeId& HeightfieldNodeId, + HAPI_NodeId& HeightNodeId, + HAPI_NodeId& MaskNodeId, + HAPI_NodeId& MergeNodeId) +{ + // Make sure the Heightfield node doesnt already exists + if (HeightfieldNodeId != -1) + return false; + + // Convert the node's name + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString(NodeName, NameStr); + + // Create the heigthfield node via HAPI + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateHeightFieldInput( + FHoudiniEngine::Get().GetSession(), + -1, NameStr.c_str(), XSize, YSize, 1.0f, HAPI_HeightFieldSampling::HAPI_HEIGHTFIELD_SAMPLING_CORNER, + &HeightfieldNodeId, &HeightNodeId, &MaskNodeId, &MergeNodeId), false); + + // Cook it + return FHoudiniEngineUtils::HapiCookNode(HeightfieldNodeId, nullptr, true); + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightfieldNodeId, &CookOptions), false); + + return true; + */ +} + +bool +FUnrealLandscapeTranslator::SetHeightfieldData( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + TArray& FloatValues, + const HAPI_VolumeInfo& VolumeInfo, + const FString& HeightfieldName) +{ + // Cook the node to get proper infos on it + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, &CookOptions), false); + */ + if(!FHoudiniEngineUtils::HapiCookNode(VolumeNodeId, nullptr, true)) + return false; + + // Read the geo/part/volume info from the volume node + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + //FMemory::Memset< HAPI_GeoInfo >(GeoInfo, 0); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, &GeoInfo), false); + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + //FMemory::Memset< HAPI_PartInfo >(PartInfo, 0); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetPartInfo( + FHoudiniEngine::Get().GetSession(), + GeoInfo.nodeId, PartId, &PartInfo), false); + + // Update the volume infos + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVolumeInfo( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, PartInfo.id, &VolumeInfo), false); + + // Volume name + std::string NameStr; + FHoudiniEngineUtils::ConvertUnrealString(HeightfieldName, NameStr); + + // Set the Heighfield data on the volume + float * HeightData = FloatValues.GetData(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetHeightFieldData( + FHoudiniEngine::Get().GetSession(), + GeoInfo.nodeId, PartInfo.id, NameStr.c_str(), HeightData, 0, FloatValues.Num()), false); + + return true; +} + +bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + UMaterialInterface* InLandscapeMaterial, + UMaterialInterface* InLandscapeHoleMaterial, + UPhysicalMaterial* InPhysicalMaterial) +{ + if (VolumeNodeId == -1) + return false; + + // LANDSCAPE MATERIAL + if (InLandscapeMaterial && !InLandscapeMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InLandscapeMaterialString = InLandscapeMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + // HOLE MATERIAL + if (InLandscapeHoleMaterial && !InLandscapeHoleMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InLandscapeMaterialString = InLandscapeHoleMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_MATERIAL_HOLE; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >(AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (Result == HAPI_RESULT_SUCCESS) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InLandscapeMaterialString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_hole_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + // PHYSICAL MATERIAL + if (InPhysicalMaterial && !InPhysicalMaterial->IsPendingKill()) + { + // Extract the path name from the material interface + FString InPhysMatlString = InPhysicalMaterial->GetPathName(); + + // Get name of attribute used for marshalling materials. + std::string MarshallingAttributeMaterialName = HAPI_UNREAL_ATTRIB_PHYSICAL_MATERIAL; + + // Marshall in material names. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + AttributeInfoMaterial.count = 1; + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial); + + if (Result == HAPI_RESULT_SUCCESS) + { + // Convert the FString to cont char * + std::string LandscapeMatCStr = TCHAR_TO_ANSI(*InPhysMatlString); + const char* LandscapeMatCStrRaw = LandscapeMatCStr.c_str(); + TArray LandscapeMatArr; + LandscapeMatArr.Add(LandscapeMatCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeMaterialName.c_str(), &AttributeInfoMaterial, + LandscapeMatArr.GetData(), 0, AttributeInfoMaterial.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_physical_material attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + } + + return true; +} + +/* +bool +FUnrealLandscapeTranslator::AddLevelPathAttributeToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + const FString& LevelPath) +{ + if (VolumeNodeId == -1) + return false; + + // LANDSCAPE MATERIAL + if (LevelPath.IsEmpty()) + return false; + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = 1; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), VolumeNodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to cont char * + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray LevelPathArr; + LevelPathArr.Add(LevelPathCStrRaw); + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + VolumeNodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + LevelPathArr.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for landscape: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} +*/ + +bool +FUnrealLandscapeTranslator::GetLandscapeLayerData( + ALandscapeProxy* LandscapeProxy, + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName) +{ + if (!IsValid(LandscapeInfo) || !IsValid(LandscapeProxy)) + return false; + + // Get the landscape X/Y Size + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + + ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); + if (LandscapeProxy == Landscape) + { + // The proxy is a landscape actor, so we have to use the landscape extent (landscape components + // may have been moved to proxies and may not be present on this actor). + LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); + } + else + { + // We only want to get the data for this landscape proxy. + // To handle streaming proxies correctly, get the extents via all the components, + // not by calling GetLandscapeExtent or we'll end up sending ALL the streaming proxies. + for (const ULandscapeComponent* Comp : LandscapeProxy->LandscapeComponents) + { + Comp->GetComponentExtent(MinX, MinY, MaxX, MaxY); + } + } + + if(MinX == MAX_int32 || MinY == MAX_int32 || MaxX == -MAX_int32 || MaxY == -MAX_int32) + return false; + + if (!GetLandscapeLayerData( + LandscapeInfo, LayerIndex, + MinX, MinY, MaxX, MaxY, + LayerData, LayerUsageDebugColor, LayerName)) + return false; + + return true; +} + +bool +FUnrealLandscapeTranslator::GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + const int32& MinX, const int32& MinY, + const int32& MaxX, const int32& MaxY, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName) +{ + if (!LandscapeInfo) + return false; + + if (!LandscapeInfo->Layers.IsValidIndex(LayerIndex)) + return false; + + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[LayerIndex]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (!LayerInfo) + return false; + + // Calc the X/Y size in points + int32 XSize = (MaxX - MinX + 1); + int32 YSize = (MaxY - MinY + 1); + if ((XSize < 2) || (YSize < 2)) + return false; + + // extracting the uint8 values from the layer + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + LayerData.AddZeroed(XSize * YSize); + LandscapeEdit.GetWeightDataFast(LayerInfo, MinX, MinY, MaxX, MaxY, LayerData.GetData(), 0); + + LayerUsageDebugColor = LayerInfo->LayerUsageDebugColor; + + LayerName = LayersSetting.GetLayerName().ToString(); + + return true; +} + +bool +FUnrealLandscapeTranslator::InitDefaultHeightfieldMask( + const HAPI_VolumeInfo& HeightVolumeInfo, + const HAPI_NodeId& MaskVolumeNodeId) +{ + // We need to have a mask layer as it is required for proper heightfield functionalities + + // Creating an array filled with 0.0 + TArray< float > MaskFloatData; + MaskFloatData.Init(0.0f, HeightVolumeInfo.xLength * HeightVolumeInfo.yLength); + + // Creating the volume infos + HAPI_VolumeInfo MaskVolumeInfo = HeightVolumeInfo; + + // Set the heighfield data in Houdini + FString MaskName = TEXT("mask"); + HAPI_PartId PartId = 0; + if (!SetHeightfieldData(MaskVolumeNodeId, PartId, MaskFloatData, MaskVolumeInfo, MaskName)) + return false; + + return true; +} + +bool +FUnrealLandscapeTranslator::DestroyLandscapeAssetNode(HAPI_NodeId& ConnectedAssetId, TArray& CreatedInputAssetIds) +{ + HAPI_AssetInfo NodeAssetInfo; + FHoudiniApi::AssetInfo_Init(&NodeAssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), ConnectedAssetId, &NodeAssetInfo), false); + + FHoudiniEngineString AssetOpName(NodeAssetInfo.fullOpNameSH); + FString OpName; + if (!AssetOpName.ToFString(OpName)) + return false; + + if (!OpName.Contains(TEXT("xform"))) + { + // Not a transform node, so not a Heightfield + // We just need to destroy the landscape asset node + return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); + } + + // The landscape was marshalled as a heightfield, so we need to destroy and disconnect + // the volvis nodes, all the merge node's input (each merge input is a volume for one + // of the layer/mask of the landscape ) + + // Query the volvis node id + // The volvis node is the fist input of the xform node + HAPI_NodeId VolvisNodeId = -1; + FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + ConnectedAssetId, 0, &VolvisNodeId); + + // First, destroy the merge node and its inputs + // The merge node is in the first input of the volvis node + HAPI_NodeId MergeNodeId = -1; + FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + VolvisNodeId, 0, &MergeNodeId); + + if (MergeNodeId != -1) + { + // Get the merge node info + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), MergeNodeId, &NodeInfo), false); + + for (int32 n = 0; n < NodeInfo.inputCount; n++) + { + // Get the Input node ID from the host ID + HAPI_NodeId InputNodeId = -1; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::QueryNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeNodeId, n, &InputNodeId)) + break; + + if (InputNodeId == -1) + break; + + // Disconnect and Destroy that input + FHoudiniEngineUtils::HapiDisconnectAsset(MergeNodeId, n); + FHoudiniEngineUtils::DestroyHoudiniAsset(InputNodeId); + } + } + + // Second step, destroy all the volumes GEO assets + for (HAPI_NodeId AssetNodeId : CreatedInputAssetIds) + { + FHoudiniEngineUtils::DestroyHoudiniAsset(AssetNodeId); + } + CreatedInputAssetIds.Empty(); + + // Finally disconnect and destroy the xform, volvis and merge nodes, then destroy them + FHoudiniEngineUtils::HapiDisconnectAsset(ConnectedAssetId, 0); + FHoudiniEngineUtils::HapiDisconnectAsset(VolvisNodeId, 0); + FHoudiniEngineUtils::DestroyHoudiniAsset(MergeNodeId); + FHoudiniEngineUtils::DestroyHoudiniAsset(VolvisNodeId); + + return FHoudiniEngineUtils::DestroyHoudiniAsset(ConnectedAssetId); +} + + +bool +FUnrealLandscapeTranslator::ExtractLandscapeData( + ALandscapeProxy * LandscapeProxy, TSet& SelectedComponents, + const bool& bExportLighting, const bool& bExportTileUVs, const bool& bExportNormalizedUVs, + TArray& LandscapePositionArray, + TArray& LandscapeNormalArray, + TArray& LandscapeUVArray, + TArray& LandscapeComponentVertexIndicesArray, + TArray& LandscapeComponentNameArray, + TArray& LandscapeLightmapValues) +{ + if (!LandscapeProxy) + return false; + + if (SelectedComponents.Num() < 1) + return false; + + // Get runtime settings. + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Calc all the needed sizes + int32 ComponentSizeQuads = ((LandscapeProxy->ComponentSizeQuads + 1) >> LandscapeProxy->ExportLOD) - 1; + float ScaleFactor = (float)LandscapeProxy->ComponentSizeQuads / (float)ComponentSizeQuads; + + int32 NumComponents = SelectedComponents.Num(); + bool bExportOnlySelected = NumComponents != LandscapeProxy->LandscapeComponents.Num(); + + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + int32 VertexCount = NumComponents * VertexCountPerComponent; + if (!VertexCount) + return false; + + // Initialize the data arrays + LandscapePositionArray.SetNumUninitialized(VertexCount); + LandscapeNormalArray.SetNumUninitialized(VertexCount); + LandscapeUVArray.SetNumUninitialized(VertexCount); + LandscapeComponentNameArray.SetNumUninitialized(VertexCount); + LandscapeComponentVertexIndicesArray.SetNumUninitialized(VertexCount); + if (bExportLighting) + LandscapeLightmapValues.SetNumUninitialized(VertexCount); + + //----------------------------------------------------------------------------------------------------------------- + // EXTRACT THE LANDSCAPE DATA + //----------------------------------------------------------------------------------------------------------------- + FIntPoint IntPointMax = FIntPoint::ZeroValue; + + int32 AllPositionsIdx = 0; + for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; + if (bExportOnlySelected && !SelectedComponents.Contains(LandscapeComponent)) + continue; + + TArray64< uint8 > LightmapMipData; + int32 LightmapMipSizeX = 0; + int32 LightmapMipSizeY = 0; + + // See if we need to export lighting information. + if (bExportLighting) + { + const FMeshMapBuildData* MapBuildData = LandscapeComponent->GetMeshMapBuildData(); + FLightMap2D* LightMap2D = MapBuildData && MapBuildData->LightMap ? MapBuildData->LightMap->GetLightMap2D() : nullptr; + if (LightMap2D && LightMap2D->IsValid(0)) + { + UTexture2D * TextureLightmap = LightMap2D->GetTexture(0); + if (TextureLightmap) + { + if (TextureLightmap->Source.GetMipData(LightmapMipData, 0, 0, 0, nullptr)) + { + LightmapMipSizeX = TextureLightmap->Source.GetSizeX(); + LightmapMipSizeY = TextureLightmap->Source.GetSizeY(); + } + else + { + LightmapMipData.Empty(); + } + } + } + } + + // Construct landscape component data interface to access raw data. + FLandscapeComponentDataInterface CDI(LandscapeComponent, LandscapeProxy->ExportLOD); + + // Get name of this landscape component. + const char * LandscapeComponentNameStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->GetName()); + for (int32 VertexIdx = 0; VertexIdx < VertexCountPerComponent; VertexIdx++) + { + int32 VertX = 0; + int32 VertY = 0; + CDI.VertexIndexToXY(VertexIdx, VertX, VertY); + + // Get position. + FVector PositionVector = CDI.GetWorldVertex(VertX, VertY); + + // Get normal / tangent / binormal. + FVector Normal = FVector::ZeroVector; + FVector TangentX = FVector::ZeroVector; + FVector TangentY = FVector::ZeroVector; + CDI.GetLocalTangentVectors(VertX, VertY, TangentX, TangentY, Normal); + + // Export UVs. + FVector TextureUV = FVector::ZeroVector; + if (bExportTileUVs) + { + // We want to export uvs per tile. + TextureUV = FVector(VertX, VertY, 0.0f); + + // If we need to normalize UV space. + if (bExportNormalizedUVs) + TextureUV /= ComponentSizeQuads; + } + else + { + // We want to export global uvs (default). + FIntPoint IntPoint = LandscapeComponent->GetSectionBase(); + TextureUV = FVector(VertX * ScaleFactor + IntPoint.X, VertY * ScaleFactor + IntPoint.Y, 0.0f); + + // Keep track of max offset. + IntPointMax = IntPointMax.ComponentMax(IntPoint); + } + + if (bExportLighting) + { + FLinearColor VertexLightmapColor(0.0f, 0.0f, 0.0f, 1.0f); + if (LightmapMipData.Num() > 0) + { + FVector2D UVCoord(VertX, VertY); + UVCoord /= (ComponentSizeQuads + 1); + + FColor LightmapColorRaw = PickVertexColorFromTextureMip( + LightmapMipData.GetData(), UVCoord, LightmapMipSizeX, LightmapMipSizeY); + + VertexLightmapColor = LightmapColorRaw.ReinterpretAsLinear(); + } + + LandscapeLightmapValues[AllPositionsIdx] = VertexLightmapColor; + } + + // Retrieve component transform. + const FTransform & ComponentTransform = LandscapeComponent->GetComponentTransform(); + + // Retrieve component scale. + const FVector & ScaleVector = ComponentTransform.GetScale3D(); + + // Perform normalization. + Normal /= ScaleVector; + Normal.Normalize(); + + TangentX /= ScaleVector; + TangentX.Normalize(); + + TangentY /= ScaleVector; + TangentY.Normalize(); + + // Perform position scaling. + FVector PositionTransformed = PositionVector / HAPI_UNREAL_SCALE_FACTOR_POSITION; + LandscapePositionArray[AllPositionsIdx].X = PositionTransformed.X; + LandscapePositionArray[AllPositionsIdx].Y = PositionTransformed.Z; + LandscapePositionArray[AllPositionsIdx].Z = PositionTransformed.Y; + + Swap(Normal.Y, Normal.Z); + + // Store landscape component name for this point. + LandscapeComponentNameArray[AllPositionsIdx] = LandscapeComponentNameStr; + + // Store vertex index (x,y) for this point. + LandscapeComponentVertexIndicesArray[AllPositionsIdx].X = VertX; + LandscapeComponentVertexIndicesArray[AllPositionsIdx].Y = VertY; + + // Store point normal. + LandscapeNormalArray[AllPositionsIdx] = Normal; + + // Store uv. + LandscapeUVArray[AllPositionsIdx] = TextureUV; + + AllPositionsIdx++; + } + + // Free the memory allocated for LandscapeComponentNameStr + FHoudiniEngineUtils::FreeRawStringMemory(LandscapeComponentNameStr); + } + + // If we need to normalize UV space and we are doing global UVs. + if (!bExportTileUVs && bExportNormalizedUVs) + { + IntPointMax += FIntPoint(ComponentSizeQuads, ComponentSizeQuads); + IntPointMax = IntPointMax.ComponentMax(FIntPoint(1, 1)); + + for (int32 UVIdx = 0; UVIdx < VertexCount; ++UVIdx) + { + FVector & PositionUV = LandscapeUVArray[UVIdx]; + PositionUV.X /= IntPointMax.X; + PositionUV.Y /= IntPointMax.Y; + } + } + + return true; +} + +FColor +FUnrealLandscapeTranslator::PickVertexColorFromTextureMip( + const uint8 * MipBytes, FVector2D & UVCoord, int32 MipWidth, int32 MipHeight) +{ + check(MipBytes); + + FColor ResultColor(0, 0, 0, 255); + + if (UVCoord.X >= 0.0f && UVCoord.X < 1.0f && UVCoord.Y >= 0.0f && UVCoord.Y < 1.0f) + { + const int32 X = MipWidth * UVCoord.X; + const int32 Y = MipHeight * UVCoord.Y; + + const int32 Index = ((Y * MipWidth) + X) * 4; + + ResultColor.B = MipBytes[Index + 0]; + ResultColor.G = MipBytes[Index + 1]; + ResultColor.R = MipBytes[Index + 2]; + ResultColor.A = MipBytes[Index + 3]; + } + + return ResultColor; +} + +bool +FUnrealLandscapeTranslator::AddLandscapePositionAttribute(const HAPI_NodeId& NodeId, const TArray< FVector >& LandscapePositionArray) +{ + int32 VertexCount = LandscapePositionArray.Num(); + if (VertexCount < 3) + return false; + + // Create point attribute info containing positions. + HAPI_AttributeInfo AttributeInfoPointPosition; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointPosition); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointPosition ); + AttributeInfoPointPosition.count = VertexCount; + AttributeInfoPointPosition.tupleSize = 3; + AttributeInfoPointPosition.exists = true; + AttributeInfoPointPosition.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointPosition.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointPosition.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition), false); + + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPointPosition, + (const float *)LandscapePositionArray.GetData(), + 0, AttributeInfoPointPosition.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeNormalAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeNormalArray) +{ + int32 VertexCount = LandscapeNormalArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointNormal; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointNormal); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointNormal ); + AttributeInfoPointNormal.count = VertexCount; + AttributeInfoPointNormal.tupleSize = 3; + AttributeInfoPointNormal.exists = true; + AttributeInfoPointNormal.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointNormal.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointNormal.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoPointNormal, + (const float *)LandscapeNormalArray.GetData(), 0, VertexCount), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeUVAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeUVArray) +{ + int32 VertexCount = LandscapeUVArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointUV; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointUV); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointUV ); + AttributeInfoPointUV.count = VertexCount; + AttributeInfoPointUV.tupleSize = 3; + AttributeInfoPointUV.exists = true; + AttributeInfoPointUV.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointUV.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointUV.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_UV, &AttributeInfoPointUV, + (const float *)LandscapeUVArray.GetData(), 0, AttributeInfoPointUV.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeComponentVertexIndicesAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentVertexIndicesArray) +{ + int32 VertexCount = LandscapeComponentVertexIndicesArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoPointLandscapeComponentVertexIndices; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentVertexIndices); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentVertexIndices ); + AttributeInfoPointLandscapeComponentVertexIndices.count = VertexCount; + AttributeInfoPointLandscapeComponentVertexIndices.tupleSize = 2; + AttributeInfoPointLandscapeComponentVertexIndices.exists = true; + AttributeInfoPointLandscapeComponentVertexIndices.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLandscapeComponentVertexIndices.storage = HAPI_STORAGETYPE_INT; + AttributeInfoPointLandscapeComponentVertexIndices.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, + &AttributeInfoPointLandscapeComponentVertexIndices), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LANDSCAPE_VERTEX_INDEX, + &AttributeInfoPointLandscapeComponentVertexIndices, + (const int *)LandscapeComponentVertexIndicesArray.GetData(), 0, + AttributeInfoPointLandscapeComponentVertexIndices.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeComponentNameAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray) +{ + int32 VertexCount = LandscapeComponentNameArray.Num(); + if (VertexCount < 3) + return false; + + // Create point attribute containing landscape component name. + HAPI_AttributeInfo AttributeInfoPointLandscapeComponentNames; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLandscapeComponentNames); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLandscapeComponentNames ); + AttributeInfoPointLandscapeComponentNames.count = VertexCount; + AttributeInfoPointLandscapeComponentNames.tupleSize = 1; + AttributeInfoPointLandscapeComponentNames.exists = true; + AttributeInfoPointLandscapeComponentNames.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLandscapeComponentNames.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPointLandscapeComponentNames.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, + &AttributeInfoPointLandscapeComponentNames), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME, + &AttributeInfoPointLandscapeComponentNames, + (const char **)LandscapeComponentNameArray.GetData(), + 0, AttributeInfoPointLandscapeComponentNames.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeLightmapColorAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues) +{ + int32 VertexCount = LandscapeLightmapValues.Num(); + + HAPI_AttributeInfo AttributeInfoPointLightmapColor; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPointLightmapColor); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPointLightmapColor ); + AttributeInfoPointLightmapColor.count = VertexCount; + AttributeInfoPointLightmapColor.tupleSize = 4; + AttributeInfoPointLightmapColor.exists = true; + AttributeInfoPointLightmapColor.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPointLightmapColor.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPointLightmapColor.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_COLOR, &AttributeInfoPointLightmapColor, + (const float *)LandscapeLightmapValues.GetData(), 0, + AttributeInfoPointLightmapColor.count), false); + + return true; +} + +bool +FUnrealLandscapeTranslator::AddLandscapeMeshIndicesAndMaterialsAttribute( + const HAPI_NodeId& NodeId, const bool& bExportMaterials, + const int32& ComponentSizeQuads, const int32& QuadCount, + ALandscapeProxy * LandscapeProxy, + const TSet< ULandscapeComponent * >& SelectedComponents) +{ + if (!LandscapeProxy) + return false; + + // Compute number of necessary indices. + int32 IndexCount = QuadCount * 4; + if (IndexCount < 0) + return false; + + int32 VertexCountPerComponent = FMath::Square(ComponentSizeQuads + 1); + + // Array holding indices data. + TArray LandscapeIndices; + LandscapeIndices.SetNumUninitialized(IndexCount); + + // Allocate space for face names. + // The LandscapeMaterial and HoleMaterial per point + TArray FaceMaterials; + TArray FaceHoleMaterials; + FaceMaterials.SetNumUninitialized(QuadCount); + FaceHoleMaterials.SetNumUninitialized(QuadCount); + + int32 VertIdx = 0; + int32 QuadIdx = 0; + + const char * MaterialRawStr = nullptr; + const char * MaterialHoleRawStr = nullptr; + + // Lambda for freeing the memory allocated by ExtractRawString and returning + auto FreeMemoryReturn = [&MaterialRawStr, &MaterialHoleRawStr](const bool& bReturn) + { + FHoudiniEngineUtils::FreeRawStringMemory(MaterialRawStr); + FHoudiniEngineUtils::FreeRawStringMemory(MaterialHoleRawStr); + + return bReturn; + }; + + const int32 QuadComponentCount = ComponentSizeQuads + 1; + for (int32 ComponentIdx = 0; ComponentIdx < LandscapeProxy->LandscapeComponents.Num(); ComponentIdx++) + { + ULandscapeComponent * LandscapeComponent = LandscapeProxy->LandscapeComponents[ComponentIdx]; + if (!SelectedComponents.Contains(LandscapeComponent)) + continue; + + if (bExportMaterials) + { + // If component has an override material, we need to get the raw name (if exporting materials). + if (LandscapeComponent->OverrideMaterial) + { + MaterialRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideMaterial->GetName()); + } + + // If component has an override hole material, we need to get the raw name (if exporting materials). + if (LandscapeComponent->OverrideHoleMaterial) + { + MaterialHoleRawStr = FHoudiniEngineUtils::ExtractRawString(LandscapeComponent->OverrideHoleMaterial->GetName()); + } + } + + int32 BaseVertIndex = ComponentIdx * VertexCountPerComponent; + for (int32 YIdx = 0; YIdx < ComponentSizeQuads; YIdx++) + { + for (int32 XIdx = 0; XIdx < ComponentSizeQuads; XIdx++) + { + LandscapeIndices[VertIdx + 0] = BaseVertIndex + (XIdx + 0) + (YIdx + 0) * QuadComponentCount; + LandscapeIndices[VertIdx + 1] = BaseVertIndex + (XIdx + 1) + (YIdx + 0) * QuadComponentCount; + LandscapeIndices[VertIdx + 2] = BaseVertIndex + (XIdx + 1) + (YIdx + 1) * QuadComponentCount; + LandscapeIndices[VertIdx + 3] = BaseVertIndex + (XIdx + 0) + (YIdx + 1) * QuadComponentCount; + + // Store override materials (if exporting materials). + if (bExportMaterials) + { + FaceMaterials[QuadIdx] = MaterialRawStr; + FaceHoleMaterials[QuadIdx] = MaterialHoleRawStr; + } + + VertIdx += 4; + QuadIdx++; + } + } + } + + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, LandscapeIndices.GetData(), 0, LandscapeIndices.Num()), + FreeMemoryReturn(false)); + + // We need to generate array of face counts. + TArray LandscapeFaces; + LandscapeFaces.Init(4, QuadCount); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, LandscapeFaces.GetData(), 0, LandscapeFaces.Num()), + FreeMemoryReturn(false)); + + if (bExportMaterials) + { + if (!FaceMaterials.Contains(nullptr)) + { + // Marshall in override primitive material names. + HAPI_AttributeInfo AttributeInfoPrimitiveMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterial ); + AttributeInfoPrimitiveMaterial.count = FaceMaterials.Num(); + AttributeInfoPrimitiveMaterial.tupleSize = 1; + AttributeInfoPrimitiveMaterial.exists = true; + AttributeInfoPrimitiveMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoPrimitiveMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPrimitiveMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoPrimitiveMaterial, + (const char **)FaceMaterials.GetData(), 0, AttributeInfoPrimitiveMaterial.count), + FreeMemoryReturn(false)); + } + + if (!FaceHoleMaterials.Contains(nullptr)) + { + // Marshall in override primitive material hole names. + HAPI_AttributeInfo AttributeInfoPrimitiveMaterialHole; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPrimitiveMaterialHole); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPrimitiveMaterialHole ); + AttributeInfoPrimitiveMaterialHole.count = FaceHoleMaterials.Num(); + AttributeInfoPrimitiveMaterialHole.tupleSize = 1; + AttributeInfoPrimitiveMaterialHole.exists = true; + AttributeInfoPrimitiveMaterialHole.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoPrimitiveMaterialHole.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoPrimitiveMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoPrimitiveMaterialHole), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoPrimitiveMaterialHole, (const char **)FaceHoleMaterials.GetData(), 0, + AttributeInfoPrimitiveMaterialHole.count), + FreeMemoryReturn(false)); + } + } + + // Free the memory and return true + return FreeMemoryReturn(true); +} + +bool +FUnrealLandscapeTranslator::AddLandscapeGlobalMaterialAttribute( + const HAPI_NodeId& NodeId, ALandscapeProxy * LandscapeProxy) +{ + if (!LandscapeProxy) + return false; + + // If there's a global landscape material, we marshall it as detail. + UMaterialInterface * MaterialInterface = LandscapeProxy->GetLandscapeMaterial(); + const char * MaterialNameStr = ""; + if (MaterialInterface) + { + FString FullMaterialName = MaterialInterface->GetPathName(); + MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); + } + + HAPI_AttributeInfo AttributeInfoDetailMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterial); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterial ); + AttributeInfoDetailMaterial.count = 1; + AttributeInfoDetailMaterial.tupleSize = 1; + AttributeInfoDetailMaterial.exists = true; + AttributeInfoDetailMaterial.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoDetailMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoDetailMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoDetailMaterial, + (const char**)&MaterialNameStr, 0, AttributeInfoDetailMaterial.count), false); + + // If there's a global landscape hole material, we marshall it as detail. + UMaterialInterface * HoleMaterialInterface = LandscapeProxy->GetLandscapeHoleMaterial(); + const char * HoleMaterialNameStr = ""; + if (HoleMaterialInterface) + { + FString FullMaterialName = HoleMaterialInterface->GetPathName(); + MaterialNameStr = TCHAR_TO_UTF8(*FullMaterialName); + } + + HAPI_AttributeInfo AttributeInfoDetailMaterialHole; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoDetailMaterialHole); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoDetailMaterialHole ); + AttributeInfoDetailMaterialHole.count = 1; + AttributeInfoDetailMaterialHole.tupleSize = 1; + AttributeInfoDetailMaterialHole.exists = true; + AttributeInfoDetailMaterialHole.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoDetailMaterialHole.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoDetailMaterialHole.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoDetailMaterialHole), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_MATERIAL_HOLE, + &AttributeInfoDetailMaterialHole, (const char **)&HoleMaterialNameStr, 0, + AttributeInfoDetailMaterialHole.count), false); + + return true; +} + + +bool +FUnrealLandscapeTranslator::AddLandscapeLayerAttribute( + const HAPI_NodeId& NodeId, const TArray& LandscapeLayerArray, const FString& LayerName) +{ + int32 VertexCount = LandscapeLayerArray.Num(); + if (VertexCount < 3) + return false; + + HAPI_AttributeInfo AttributeInfoLayer; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLayer); + AttributeInfoLayer.count = VertexCount; + AttributeInfoLayer.tupleSize = 1; + AttributeInfoLayer.exists = true; + AttributeInfoLayer.owner = HAPI_ATTROWNER_POINT; + AttributeInfoLayer.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLayer.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, + TCHAR_TO_ANSI(*LayerName), + &AttributeInfoLayer), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, + TCHAR_TO_ANSI(*LayerName), + &AttributeInfoLayer, + (const float *)LandscapeLayerArray.GetData(), + 0, AttributeInfoLayer.count), false); + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h index 4b7542d16..ff49af19f 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h @@ -1,258 +1,258 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Landscape.h" -#include "HAPI/HAPI_Common.h" - -class ALandscapeProxy; -class UHoudiniInputLandscape; - -struct HOUDINIENGINE_API FUnrealLandscapeTranslator -{ - public: - - // ------------------------------------------------------------------------------------------ - // Unreal Landscape to Houdini Heightfield - // ------------------------------------------------------------------------------------------ - static bool CreateHeightfieldFromLandscape( - ALandscapeProxy* LandcapeProxy, - HAPI_NodeId& CreatedHeightfieldNodeId, - const FString &InputNodeNameStr); - - static bool CreateInputNodeForLandscape( - ALandscapeProxy* LandscapeProxy, - const FString& InputNodeNameStr, - const FString& HeightFieldName, - const FTransform& LandscapeTransform, - FVector& CenterOffset, - HAPI_NodeId& HeightId, - HAPI_PartId& PartId, - HAPI_NodeId& HeightFieldId, - HAPI_NodeId& MaskId, - HAPI_NodeId& MergeId, - TArray& HeightData, - HAPI_VolumeInfo& HeightfieldVolumeInfo, - int32& XSize, int32& YSize - - ); - - static void ApplyAttributesToHeightfieldNode( - const HAPI_NodeId HeightId, - const HAPI_PartId PartId, - ALandscapeProxy* LandscapeProxy - ); - - // Extracts the uint16 values of a given landscape - static bool GetLandscapeData( - ALandscapeProxy* LandscapeProxy, - TArray& HeightData, - int32& XSize, int32& YSize, - FVector& Min, FVector& Max); - - static bool GetLandscapeData( - ULandscapeInfo* LandscapeInfo, - const int32& MinX, - const int32& MinY, - const int32& MaxX, - const int32& MaxY, - TArray& HeightData, - int32& XSize, int32& YSize); - - static void GetLandscapeProxyBounds( - ALandscapeProxy* LandscapeProxy, - FVector& Origin, FVector& Extents); - - // Converts Unreal uint16 values to Houdini Float - static bool ConvertLandscapeDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, - const int32& YSize, - FVector Min, - FVector Max, - const FTransform& LandscapeTransform, - TArray& HeightfieldFloatValues, - HAPI_VolumeInfo& HeightfieldVolumeInfo, - FVector& CenterOffset); - - // Converts Unreal uint8 values to Houdini Float - static bool ConvertLandscapeLayerDataToHeightfieldData( - const TArray& IntHeightData, - const int32& XSize, const int32& YSize, - const FLinearColor& LayerUsageDebugColor, - TArray& LayerFloatValues, - HAPI_VolumeInfo& LayerVolumeInfo); - - // Creates an unlocked heightfield input node - static bool CreateHeightfieldInputNode( - const FString& NodeName, - const int32& XSize, - const int32& YSize, - HAPI_NodeId& HeightfieldNodeId, - HAPI_NodeId& HeightNodeId, - HAPI_NodeId& MaskNodeId, - HAPI_NodeId& MergeNodeId ); - - // Set the volume float value for a heightfield - static bool SetHeightfieldData( - const HAPI_NodeId& AssetId, - const HAPI_PartId& PartId, - TArray< float >& FloatValues, - const HAPI_VolumeInfo& VolumeInfo, - const FString& HeightfieldName); - - static bool AddLandscapeMaterialAttributesToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - UMaterialInterface* LandscapeMaterial, - UMaterialInterface* LandscapeHoleMaterial, - UPhysicalMaterial* InPhysicalMaterial); - - /* - static bool AddLevelPathAttributeToVolume( - const HAPI_NodeId& VolumeNodeId, - const HAPI_PartId& PartId, - const FString& LevelPath); - */ - - // Extracts the uint8 values of a given landscape - static bool GetLandscapeLayerData( - ALandscapeProxy* LandscapeProxy, - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName); - - static bool GetLandscapeLayerData( - ULandscapeInfo* LandscapeInfo, - const int32& LayerIndex, - const int32& MinX, - const int32& MinY, - const int32& MaxX, - const int32& MaxY, - TArray& LayerData, - FLinearColor& LayerUsageDebugColor, - FString& LayerName); - - // Initialise the Heightfield Mask with default values - static bool InitDefaultHeightfieldMask( - const HAPI_VolumeInfo& HeightVolumeInfo, - const HAPI_NodeId& MaskVolumeNodeId); - - // Landscape nodes clean up - static bool DestroyLandscapeAssetNode( - HAPI_NodeId& ConnectedAssetId, - TArray& CreatedInputAssetIds); - - - //-------------------------------------------------------------------------------------------------- - // Unreal to Houdini - MESH / POINTS - //-------------------------------------------------------------------------------------------------- - - static bool CreateMeshOrPointsFromLandscape( - ALandscapeProxy* InLandscape, - HAPI_NodeId& InputNodeId, - const FString& InInputNodeNameString, - const bool& bExportGeometryAsMesh, - const bool& bExportTileUVs, - const bool bExportNormalizedUVs, - const bool bExportLighting, - const bool bExportMaterials); - - // Extract data from the landscape - static bool ExtractLandscapeData( - ALandscapeProxy * LandscapeProxy, - TSet& SelectedComponents, - const bool& bExportLighting, - const bool& bExportTileUVs, - const bool& bExportNormalizedUVs, - TArray& LandscapePositionArray, - TArray& LandscapeNormalArray, - TArray& LandscapeUVArray, - TArray& LandscapeComponentVertexIndicesArray, - TArray& LandscapeComponentNameArray, - TArray& LandscapeLightmapValues); - - // Helper functions to extract color from a texture - static FColor PickVertexColorFromTextureMip( - const uint8 * MipBytes, - FVector2D & UVCoord, - int32 MipWidth, - int32 MipHeight); - - // Add the Position attribute extracted from a landscape - static bool AddLandscapePositionAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapePositionArray); - - // Add the Normal attribute extracted from a landscape - static bool AddLandscapeNormalAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapeNormalArray); - - // Add the UV attribute extracted from a landscape - static bool AddLandscapeUVAttribute( - const HAPI_NodeId& NodeId, - const TArray< FVector >& LandscapeUVArray); - - // Add the Component Vertex Index attribute extracted from a landscape - static bool AddLandscapeComponentVertexIndicesAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeComponentVertexIndicesArray); - - // Add the Component Name attribute extracted from a landscape - static bool AddLandscapeComponentNameAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeComponentNameArray); - - // Add the lightmap color attribute extracted from a landscape - static bool AddLandscapeLightmapColorAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeLightmapValues); - - // Creates and add the vertex indices and face materials attribute from a landscape - static bool AddLandscapeMeshIndicesAndMaterialsAttribute( - const HAPI_NodeId& NodeId, - const bool& bExportMaterials, - const int32& ComponentSizeQuads, - const int32& QuadCount, - ALandscapeProxy * LandscapeProxy, - const TSet& SelectedComponents); - - // Add the global (detail) material and hole material attribute from a landscape - static bool AddLandscapeGlobalMaterialAttribute( - const HAPI_NodeId& NodeId, - ALandscapeProxy * LandscapeProxy); - - // Add landscape layer values as point attributes - static bool AddLandscapeLayerAttribute( - const HAPI_NodeId& NodeId, - const TArray& LandscapeLayerArray, - const FString& LayerName); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Landscape.h" +#include "HAPI/HAPI_Common.h" + +class ALandscapeProxy; +class UHoudiniInputLandscape; + +struct HOUDINIENGINE_API FUnrealLandscapeTranslator +{ + public: + + // ------------------------------------------------------------------------------------------ + // Unreal Landscape to Houdini Heightfield + // ------------------------------------------------------------------------------------------ + static bool CreateHeightfieldFromLandscape( + ALandscapeProxy* LandcapeProxy, + HAPI_NodeId& CreatedHeightfieldNodeId, + const FString &InputNodeNameStr); + + static bool CreateInputNodeForLandscape( + ALandscapeProxy* LandscapeProxy, + const FString& InputNodeNameStr, + const FString& HeightFieldName, + const FTransform& LandscapeTransform, + FVector& CenterOffset, + HAPI_NodeId& HeightId, + HAPI_PartId& PartId, + HAPI_NodeId& HeightFieldId, + HAPI_NodeId& MaskId, + HAPI_NodeId& MergeId, + TArray& HeightData, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + int32& XSize, int32& YSize + + ); + + static void ApplyAttributesToHeightfieldNode( + const HAPI_NodeId HeightId, + const HAPI_PartId PartId, + ALandscapeProxy* LandscapeProxy + ); + + // Extracts the uint16 values of a given landscape + static bool GetLandscapeData( + ALandscapeProxy* LandscapeProxy, + TArray& HeightData, + int32& XSize, int32& YSize, + FVector& Min, FVector& Max); + + static bool GetLandscapeData( + ULandscapeInfo* LandscapeInfo, + const int32& MinX, + const int32& MinY, + const int32& MaxX, + const int32& MaxY, + TArray& HeightData, + int32& XSize, int32& YSize); + + static void GetLandscapeProxyBounds( + ALandscapeProxy* LandscapeProxy, + FVector& Origin, FVector& Extents); + + // Converts Unreal uint16 values to Houdini Float + static bool ConvertLandscapeDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, + const int32& YSize, + FVector Min, + FVector Max, + const FTransform& LandscapeTransform, + TArray& HeightfieldFloatValues, + HAPI_VolumeInfo& HeightfieldVolumeInfo, + FVector& CenterOffset); + + // Converts Unreal uint8 values to Houdini Float + static bool ConvertLandscapeLayerDataToHeightfieldData( + const TArray& IntHeightData, + const int32& XSize, const int32& YSize, + const FLinearColor& LayerUsageDebugColor, + TArray& LayerFloatValues, + HAPI_VolumeInfo& LayerVolumeInfo); + + // Creates an unlocked heightfield input node + static bool CreateHeightfieldInputNode( + const FString& NodeName, + const int32& XSize, + const int32& YSize, + HAPI_NodeId& HeightfieldNodeId, + HAPI_NodeId& HeightNodeId, + HAPI_NodeId& MaskNodeId, + HAPI_NodeId& MergeNodeId ); + + // Set the volume float value for a heightfield + static bool SetHeightfieldData( + const HAPI_NodeId& AssetId, + const HAPI_PartId& PartId, + TArray< float >& FloatValues, + const HAPI_VolumeInfo& VolumeInfo, + const FString& HeightfieldName); + + static bool AddLandscapeMaterialAttributesToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + UMaterialInterface* LandscapeMaterial, + UMaterialInterface* LandscapeHoleMaterial, + UPhysicalMaterial* InPhysicalMaterial); + + /* + static bool AddLevelPathAttributeToVolume( + const HAPI_NodeId& VolumeNodeId, + const HAPI_PartId& PartId, + const FString& LevelPath); + */ + + // Extracts the uint8 values of a given landscape + static bool GetLandscapeLayerData( + ALandscapeProxy* LandscapeProxy, + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName); + + static bool GetLandscapeLayerData( + ULandscapeInfo* LandscapeInfo, + const int32& LayerIndex, + const int32& MinX, + const int32& MinY, + const int32& MaxX, + const int32& MaxY, + TArray& LayerData, + FLinearColor& LayerUsageDebugColor, + FString& LayerName); + + // Initialise the Heightfield Mask with default values + static bool InitDefaultHeightfieldMask( + const HAPI_VolumeInfo& HeightVolumeInfo, + const HAPI_NodeId& MaskVolumeNodeId); + + // Landscape nodes clean up + static bool DestroyLandscapeAssetNode( + HAPI_NodeId& ConnectedAssetId, + TArray& CreatedInputAssetIds); + + + //-------------------------------------------------------------------------------------------------- + // Unreal to Houdini - MESH / POINTS + //-------------------------------------------------------------------------------------------------- + + static bool CreateMeshOrPointsFromLandscape( + ALandscapeProxy* InLandscape, + HAPI_NodeId& InputNodeId, + const FString& InInputNodeNameString, + const bool& bExportGeometryAsMesh, + const bool& bExportTileUVs, + const bool bExportNormalizedUVs, + const bool bExportLighting, + const bool bExportMaterials); + + // Extract data from the landscape + static bool ExtractLandscapeData( + ALandscapeProxy * LandscapeProxy, + TSet& SelectedComponents, + const bool& bExportLighting, + const bool& bExportTileUVs, + const bool& bExportNormalizedUVs, + TArray& LandscapePositionArray, + TArray& LandscapeNormalArray, + TArray& LandscapeUVArray, + TArray& LandscapeComponentVertexIndicesArray, + TArray& LandscapeComponentNameArray, + TArray& LandscapeLightmapValues); + + // Helper functions to extract color from a texture + static FColor PickVertexColorFromTextureMip( + const uint8 * MipBytes, + FVector2D & UVCoord, + int32 MipWidth, + int32 MipHeight); + + // Add the Position attribute extracted from a landscape + static bool AddLandscapePositionAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapePositionArray); + + // Add the Normal attribute extracted from a landscape + static bool AddLandscapeNormalAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapeNormalArray); + + // Add the UV attribute extracted from a landscape + static bool AddLandscapeUVAttribute( + const HAPI_NodeId& NodeId, + const TArray< FVector >& LandscapeUVArray); + + // Add the Component Vertex Index attribute extracted from a landscape + static bool AddLandscapeComponentVertexIndicesAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeComponentVertexIndicesArray); + + // Add the Component Name attribute extracted from a landscape + static bool AddLandscapeComponentNameAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeComponentNameArray); + + // Add the lightmap color attribute extracted from a landscape + static bool AddLandscapeLightmapColorAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeLightmapValues); + + // Creates and add the vertex indices and face materials attribute from a landscape + static bool AddLandscapeMeshIndicesAndMaterialsAttribute( + const HAPI_NodeId& NodeId, + const bool& bExportMaterials, + const int32& ComponentSizeQuads, + const int32& QuadCount, + ALandscapeProxy * LandscapeProxy, + const TSet& SelectedComponents); + + // Add the global (detail) material and hole material attribute from a landscape + static bool AddLandscapeGlobalMaterialAttribute( + const HAPI_NodeId& NodeId, + ALandscapeProxy * LandscapeProxy); + + // Add landscape layer values as point attributes + static bool AddLandscapeLayerAttribute( + const HAPI_NodeId& NodeId, + const TArray& LandscapeLayerArray, + const FString& LayerName); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp index aac5ab692..246a88cda 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp @@ -1,4259 +1,4266 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealMeshTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "RawMesh.h" -#include "MeshDescription.h" -#include "MeshDescriptionOperations.h" -#include "Engine/StaticMesh.h" -#include "PhysicsEngine/BodySetup.h" -#include "Engine/StaticMeshSocket.h" -#include "Components/StaticMeshComponent.h" -#include "Materials/Material.h" -#include "Materials/MaterialInterface.h" -#include "MeshAttributes.h" -#include "StaticMeshAttributes.h" - -#if WITH_EDITOR - #include "EditorFramework/AssetImportData.h" -#endif - -bool -FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( - UStaticMesh* StaticMesh, - HAPI_NodeId& InputNodeId, - const FString& InputNodeName, - UStaticMeshComponent* StaticMeshComponent /* = nullptr */, - const bool& ExportAllLODs /* = false */, - const bool& ExportSockets /* = false */, - const bool& ExportColliders /* = false */) -{ - // If we don't have a static mesh there's nothing to do. - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - // Node ID for the newly created node - HAPI_NodeId NewNodeId = -1; - - // Export sockets if there are some - bool DoExportSockets = ExportSockets && (StaticMesh->Sockets.Num() > 0); - - // Export LODs if there are some - bool DoExportLODs = ExportAllLODs && (StaticMesh->GetNumLODs() > 1); - - // Export colliders if there are some - bool DoExportColliders = ExportColliders && StaticMesh->BodySetup != nullptr; - if (DoExportColliders) - { - if (!StaticMesh->BodySetup) - { - DoExportColliders = false; - } - else - { - if (StaticMesh->BodySetup->AggGeom.GetElementCount() <= 0) - DoExportColliders = false; - } - } - - // We need to use a merge node if we export lods OR sockets - bool UseMergeNode = DoExportLODs || DoExportSockets || DoExportColliders; - if (UseMergeNode) - { - // TODO: - // What if OutInputNodeId already exists? - // Delete previous merge?/input? - - // Create a merge SOP asset. This will be our "InputNodeId" - // as all the different LOD meshes and sockets will be plugged into it - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - -1, TEXT("SOP/merge"), InputNodeName, true, &NewNodeId), false); - } - else - { - // No LODs/Sockets, we just need a single input node - // If InputNodeId is invalid, we need to create an input node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( - FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_ANSI(*InputNodeName)), false); - - if (!FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true)) - return false; - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( - FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); - */ - } - - // Check if we have a valid id for this new input asset. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) - return false; - - HAPI_NodeId PreviousInputNodeId = InputNodeId; - - // Update our input NodeId - InputNodeId = NewNodeId; - // Get our parent OBJ NodeID - HAPI_NodeId InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); - - // We have now created a valid new input node, delete the previous one - if (PreviousInputNodeId >= 0) - { - // Get the parent OBJ node ID before deleting! - HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); - } - - if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( - FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) - { - HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); - } - } - - // TODO: - // Setting for lightmap resolution? - //const uint8 ExportMethod = 0; // Raw mesh - //const uint8 ExportMethod = 1; // Mesh description - const uint8 ExportMethod = 2; // LODResources (render mesh) - //bool bExportViaRawMesh = false; - - int32 NumLODsToExport = DoExportLODs ? StaticMesh->GetNumLODs() : 1; - for (int32 LODIndex = 0; LODIndex < NumLODsToExport; LODIndex++) - { - // Grab the LOD level. - FStaticMeshSourceModel & SrcModel = StaticMesh->GetSourceModel(LODIndex); - - // If we're using a merge node, we need to create a new input null - HAPI_NodeId CurrentLODNodeId = -1; - if (UseMergeNode) - { - // Create a new input node for the current LOD - const char * LODName = ""; - { - FString LOD = TEXT("lod") + FString::FromInt(LODIndex); - LODName = TCHAR_TO_UTF8(*LOD); - } - - // Create the node in this input object's OBJ node - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InputObjectNodeId, TEXT("null"), LODName, false, &CurrentLODNodeId), false); - } - else - { - // No merge node, just use the input node we created before - CurrentLODNodeId = NewNodeId; - } - - // Either export the current LOD Mesh by using RawMEsh or MeshDescription (legacy) - FMeshDescription* MeshDesc = nullptr; - // if (!bExportViaRawMesh) - if (ExportMethod == 1) - { - // This will either fetch the mesh description that is cached on the SrcModel - // or load it from bulk data / DDC once - if (SrcModel.MeshDescription.IsValid()) - { - MeshDesc = SrcModel.MeshDescription.Get(); - } - else - { - const double StartTime = FPlatformTime::Seconds(); - MeshDesc = StaticMesh->GetMeshDescription(LODIndex); - HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->GetMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - } - - bool bMeshSuccess = false; - if (ExportMethod == 1 && MeshDesc) - { - // Convert the Mesh using FMeshDescription - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForMeshDescription( - CurrentLODNodeId, - *MeshDesc, - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - else if (ExportMethod == 2) - { - // Convert the LOD Mesh using FStaticMeshLODResources - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( - CurrentLODNodeId, - StaticMesh->GetLODForExport(LODIndex), - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - else - { - // Convert the LOD Mesh using FRawMesh - const double StartTime = FPlatformTime::Seconds(); - bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForRawMesh( - CurrentLODNodeId, - SrcModel, - LODIndex, - DoExportLODs, - StaticMesh, - StaticMeshComponent); - HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForRawMesh completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); - } - - if (!bMeshSuccess) - continue; - - if (UseMergeNode) - { - // Connect the LOD node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, LODIndex, CurrentLODNodeId, 0), false); - } - } - - // next Index for adding nodes to the merge - int32 NextMergeIndex = NumLODsToExport; - if (DoExportColliders) - { - FKAggregateGeom SimpleColliders = StaticMesh->BodySetup->AggGeom; - - // Export BOX colliders - for (auto& CurBox : SimpleColliders.BoxElems) - { - FVector BoxCenter = CurBox.Center; - FVector BoxExtent = FVector(CurBox.X, CurBox.Y, CurBox.Z); - FRotator BoxRotation = CurBox.Rotation; - - HAPI_NodeId BoxNodeId = -1; - if (!CreateInputNodeForBox( - BoxNodeId, InputObjectNodeId, NextMergeIndex, - BoxCenter, BoxExtent, BoxRotation)) - continue; - - if (BoxNodeId < 0) - continue; - - // Connect the Box node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, BoxNodeId, 0), false); - - NextMergeIndex++; - } - - // Export SPHERE colliders - for (auto& CurSphere : SimpleColliders.SphereElems) - { - HAPI_NodeId SphereNodeId = -1; - if (!CreateInputNodeForSphere( - SphereNodeId, InputObjectNodeId, NextMergeIndex, - CurSphere.Center, CurSphere.Radius)) - continue; - - if (SphereNodeId < 0) - continue; - - // Connect the Sphere node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, SphereNodeId, 0), false); - - NextMergeIndex++; - } - - // Export CAPSULE colliders - for (auto& CurSphyl : SimpleColliders.SphylElems) - { - HAPI_NodeId SphylNodeId = -1; - if (!CreateInputNodeForSphyl( - SphylNodeId, InputObjectNodeId, NextMergeIndex, - CurSphyl.Center, CurSphyl.Rotation, CurSphyl.Radius, CurSphyl.Length)) - continue; - - if (SphylNodeId < 0) - continue; - - // Connect the capsule node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, SphylNodeId, 0), false); - - NextMergeIndex++; - } - - // Export CONVEX colliders - for (auto& CurConvex : SimpleColliders.ConvexElems) - { - HAPI_NodeId ConvexNodeId = -1; - if (!CreateInputNodeForConvex( - ConvexNodeId, InputObjectNodeId, NextMergeIndex, CurConvex)) - continue; - - if (ConvexNodeId < 0) - continue; - - // Connect the capsule node to the merge node. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), - NewNodeId, NextMergeIndex, ConvexNodeId, 0), false); - - NextMergeIndex++; - } - } - - if (DoExportSockets && StaticMesh->Sockets.Num() > 0) - { - // Create an input node for the mesh sockets - HAPI_NodeId SocketsNodeId = -1; - if (CreateInputNodeForMeshSockets(StaticMesh->Sockets, InputObjectNodeId, SocketsNodeId)) - { - // We can connect the socket node to the merge node's last input. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), NewNodeId, NextMergeIndex, SocketsNodeId, 0), false); - - NextMergeIndex++; - } - else if (SocketsNodeId != -1) - { - // If we failed to properly export the sockets, clean up the created node - FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), SocketsNodeId); - } - } - - // - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForMeshSockets( - const TArray& InMeshSocket, const HAPI_NodeId& InParentNodeId, HAPI_NodeId& OutSocketsNodeId) -{ - int32 NumSockets = InMeshSocket.Num(); - if (NumSockets <= 0) - return false; - - // Create a new input node for the sockets - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeId, TEXT("null"), "sockets", false, &OutSocketsNodeId), false); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.pointCount = NumSockets; - Part.vertexCount = 0; - Part.faceCount = 0; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, &Part), false); - - // Create POS point attribute info. - HAPI_AttributeInfo AttributeInfoPos; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); - AttributeInfoPos.count = NumSockets; - AttributeInfoPos.tupleSize = 3; - AttributeInfoPos.exists = true; - AttributeInfoPos.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos), false); - - // Create Rot point attribute Info - HAPI_AttributeInfo AttributeInfoRot; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); - AttributeInfoRot.count = NumSockets; - AttributeInfoRot.tupleSize = 4; - AttributeInfoRot.exists = true; - AttributeInfoRot.owner = HAPI_ATTROWNER_POINT; - AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot), false); - - // Create scale point attribute Info - HAPI_AttributeInfo AttributeInfoScale; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = NumSockets; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); - - // Create the name attrib info - HAPI_AttributeInfo AttributeInfoName; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); - AttributeInfoName.count = NumSockets; - AttributeInfoName.tupleSize = 1; - AttributeInfoName.exists = true; - AttributeInfoName.owner = HAPI_ATTROWNER_POINT; - AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName), false); - - // Create the tag attrib info - HAPI_AttributeInfo AttributeInfoTag; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); - AttributeInfoTag.count = NumSockets; - AttributeInfoTag.tupleSize = 1; - AttributeInfoTag.exists = true; - AttributeInfoTag.owner = HAPI_ATTROWNER_POINT; - AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag), false); - - // Extract the sockets transform values - TArray SocketPos; - SocketPos.SetNumZeroed(NumSockets * 3); - TArray SocketRot; - SocketRot.SetNumZeroed(NumSockets * 4); - TArray SocketScale; - SocketScale.SetNumZeroed(NumSockets * 3); - - // raw string array for names and tag, will need to be free before returning - TArray SocketNames; - TArray SocketTags; - - // Lambda for freeing the const char array's memory and returning - auto FreeMemoryReturn = [&SocketNames, &SocketTags](const bool& bReturn) - { - // Frees the memory allocated by ExtractRawString for the names and tags - FHoudiniEngineUtils::FreeRawStringMemory(SocketNames); - FHoudiniEngineUtils::FreeRawStringMemory(SocketTags); - - return bReturn; - }; - - for (int32 Idx = 0; Idx < NumSockets; ++Idx) - { - UStaticMeshSocket* CurrentSocket = InMeshSocket[Idx]; - if (!CurrentSocket || CurrentSocket->IsPendingKill()) - continue; - - // Get the socket's transform and convert it to HapiTransform - FTransform SocketTransform(CurrentSocket->RelativeRotation, CurrentSocket->RelativeLocation, CurrentSocket->RelativeScale); - HAPI_Transform HapiSocketTransform; - FHoudiniApi::Transform_Init(&HapiSocketTransform); - FHoudiniEngineUtils::TranslateUnrealTransform(SocketTransform, HapiSocketTransform); - - // Fill the attribute values - SocketPos[3 * Idx + 0] = HapiSocketTransform.position[0]; - SocketPos[3 * Idx + 1] = HapiSocketTransform.position[1]; - SocketPos[3 * Idx + 2] = HapiSocketTransform.position[2]; - - SocketRot[4 * Idx + 0] = HapiSocketTransform.rotationQuaternion[0]; - SocketRot[4 * Idx + 1] = HapiSocketTransform.rotationQuaternion[1]; - SocketRot[4 * Idx + 2] = HapiSocketTransform.rotationQuaternion[2]; - SocketRot[4 * Idx + 3] = HapiSocketTransform.rotationQuaternion[3]; - - SocketScale[3 * Idx + 0] = HapiSocketTransform.scale[0]; - SocketScale[3 * Idx + 1] = HapiSocketTransform.scale[1]; - SocketScale[3 * Idx + 2] = HapiSocketTransform.scale[2]; - - FString CurrentSocketName; - if (!CurrentSocket->SocketName.IsNone()) - CurrentSocketName = CurrentSocket->SocketName.ToString(); - else - CurrentSocketName = TEXT("Socket") + FString::FromInt(Idx); - SocketNames.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocketName)); - - if (!CurrentSocket->Tag.IsEmpty()) - SocketTags.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocket->Tag)); - else - SocketTags.Add(""); - } - - //we can now upload them to our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos, - SocketPos.GetData(), 0, AttributeInfoPos.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot, - SocketRot.GetData(), 0, AttributeInfoRot.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, - SocketScale.GetData(), 0, AttributeInfoScale.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName, - SocketNames.GetData(), 0, AttributeInfoName.count), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag, - SocketTags.GetData(), 0, AttributeInfoTag.count), - FreeMemoryReturn(false)); - - // We will also create the socket_details attributes - for (int32 Idx = 0; Idx < NumSockets; ++Idx) - { - // Build the current socket's prefix - FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(Idx); - - // Create mesh_socketX_pos attribute info. - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); - AttributeInfoPos.count = 1; - AttributeInfoPos.tupleSize = 3; - AttributeInfoPos.exists = true; - AttributeInfoPos.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; - - FString PosAttr = SocketAttrPrefix + TEXT("_pos"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos, - &(SocketPos[3 * Idx]), 0, AttributeInfoPos.count), - FreeMemoryReturn(false)); - - // Create mesh_socketX_rot point attribute Info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); - AttributeInfoRot.count = 1; - AttributeInfoRot.tupleSize = 4; - AttributeInfoRot.exists = true; - AttributeInfoRot.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; - - FString RotAttr = SocketAttrPrefix + TEXT("_rot"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot, - &(SocketRot[4 * Idx]), 0, AttributeInfoRot.count), - FreeMemoryReturn(false)); - - // Create mesh_socketX_scale point attribute Info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); - AttributeInfoScale.count = 1; - AttributeInfoScale.tupleSize = 3; - AttributeInfoScale.exists = true; - AttributeInfoScale.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; - - FString ScaleAttr = SocketAttrPrefix + TEXT("_scale"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale, - &(SocketScale[3 * Idx]), 0, AttributeInfoScale.count), - FreeMemoryReturn(false)); - - // Create the mesh_socketX_name attrib info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); - AttributeInfoName.count = 1; - AttributeInfoName.tupleSize = 1; - AttributeInfoName.exists = true; - AttributeInfoName.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; - - FString NameAttr = SocketAttrPrefix + TEXT("_name"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName, - &(SocketNames[Idx]), 0, AttributeInfoName.count), - FreeMemoryReturn(false)); - - // Create the mesh_socketX_tag attrib info - FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); - AttributeInfoTag.count = 1; - AttributeInfoTag.tupleSize = 1; - AttributeInfoTag.exists = true; - AttributeInfoTag.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; - - FString TagAttr = SocketAttrPrefix + TEXT("_tag"); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag), - FreeMemoryReturn(false)); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag, - &(SocketTags[Idx]), 0, AttributeInfoTag.count), - FreeMemoryReturn(false)); - } - - // Now add the sockets group - const char * SocketGroupStr = "socket_imported"; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr), - FreeMemoryReturn(false)); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, NumSockets); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr, GroupArray.GetData(), 0, NumSockets), - FreeMemoryReturn(false)); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), OutSocketsNodeId), - FreeMemoryReturn(false)); - - return FreeMemoryReturn(true); -} - -bool -FUnrealMeshTranslator::CreateInputNodeForRawMesh( - const HAPI_NodeId& NodeId, - const FStaticMeshSourceModel& SourceModel, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent ) -{ - // Convert the Mesh using FRawMesh - FRawMesh RawMesh; - SourceModel.LoadRawMesh(RawMesh); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = RawMesh.WedgeIndices.Num(); - Part.faceCount = RawMesh.WedgeIndices.Num() / 3; - Part.pointCount = RawMesh.VertexPositions.Num(); - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = RawMesh.VertexPositions.Num(); - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Grab the build scale - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.VertexPositions.Num() > 3) - { - TArray StaticMeshVertices; - StaticMeshVertices.SetNumZeroed(RawMesh.VertexPositions.Num() * 3); - for (int32 VertexIdx = 0; VertexIdx < RawMesh.VertexPositions.Num(); ++VertexIdx) - { - // Convert Unreal to Houdini - const FVector & PositionVector = RawMesh.VertexPositions[VertexIdx]; - StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; - StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; - StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - for (int32 MeshTexCoordIdx = 0; MeshTexCoordIdx < MAX_STATIC_TEXCOORDS; MeshTexCoordIdx++) - { - int32 StaticMeshUVCount = RawMesh.WedgeTexCoords[MeshTexCoordIdx].Num(); - if (StaticMeshUVCount > 0) - { - const TArray & RawMeshUVs = RawMesh.WedgeTexCoords[MeshTexCoordIdx]; - TArray StaticMeshUVs; - StaticMeshUVs.Reserve(StaticMeshUVCount); - - // Transfer UV data. - for (int32 UVIdx = 0; UVIdx < StaticMeshUVCount; UVIdx++) - StaticMeshUVs.Emplace(RawMeshUVs[UVIdx].X, 1.0 - RawMeshUVs[UVIdx].Y, 0); - - // Convert Unreal to Houdini - // We need to re-index UVs for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - // We do not touch wedge 0 of this triangle, Swap 2 and 3 to reverse the winding order. - StaticMeshUVs.SwapMemory(WedgeIdx + 1, WedgeIdx + 2); - } - - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (MeshTexCoordIdx > 0) - UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = StaticMeshUVCount; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, (const float *)StaticMeshUVs.GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentZ.Num() > 0) - { - TArray ChangedNormals(RawMesh.WedgeTangentZ); - - // We need to re-index normals for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentZ1 = ChangedNormals[WedgeIdx + 1]; - FVector TangentZ2 = ChangedNormals[WedgeIdx + 2]; - - ChangedNormals[WedgeIdx + 1] = TangentZ2; - ChangedNormals[WedgeIdx + 2] = TangentZ1; - } - - // We also need to swap the vector's Y and Z components - for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedNormals[WedgeIdx].Y, ChangedNormals[WedgeIdx].Z); - - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedNormals.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, (const float *)ChangedNormals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentX.Num() > 0) - { - TArray ChangedTangentU(RawMesh.WedgeTangentX); - - // We need to re-index tangents for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentU1 = ChangedTangentU[WedgeIdx + 1]; - FVector TangentU2 = ChangedTangentU[WedgeIdx + 2]; - - ChangedTangentU[WedgeIdx + 1] = TangentU2; - ChangedTangentU[WedgeIdx + 2] = TangentU1; - } - - // We also need to swap the vector's Y and Z components - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedTangentU[WedgeIdx].Y, ChangedTangentU[WedgeIdx].Z); - - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedTangentU.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - (const float *)ChangedTangentU.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeTangentY.Num() > 0) - { - TArray ChangedTangentV(RawMesh.WedgeTangentY); - // We need to re-index normals for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - FVector TangentV1 = ChangedTangentV[WedgeIdx + 1]; - FVector TangentV2 = ChangedTangentV[WedgeIdx + 2]; - - ChangedTangentV[WedgeIdx + 1] = TangentV2; - ChangedTangentV[WedgeIdx + 2] = TangentV1; - } - - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) - Swap(ChangedTangentV[WedgeIdx].Y, ChangedTangentV[WedgeIdx].Z); - - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedTangentV.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - (const float *)ChangedTangentV.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - { - // If we have instance override vertex colors on the StaticMeshComponent, - // we first need to propagate them to our copy of the RawMesh Vert Colors - TArray ChangedColors; - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && - StaticMesh->RenderData && - StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; - FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) - { - // Use the wedge map if it is available as it is lossless. - int32 NumWedges = RawMesh.WedgeIndices.Num(); - if (RenderModel.WedgeMap.Num() == NumWedges) - { - int32 NumExistingColors = RawMesh.WedgeColors.Num(); - if (NumExistingColors < NumWedges) - { - RawMesh.WedgeColors.AddUninitialized(NumWedges - NumExistingColors); - } - - // Replace mesh colors with override colors - for (int32 i = 0; i < NumWedges; i++) - { - FColor WedgeColor = FColor::White; - int32 Index = RenderModel.WedgeMap[i]; - if (Index != INDEX_NONE) - { - WedgeColor = ColorVertexBuffer.VertexColor(Index); - } - RawMesh.WedgeColors[i] = WedgeColor; - } - } - } - } - - // See if we have colors to upload. - if (RawMesh.WedgeColors.Num() > 0) - { - ChangedColors.SetNumUninitialized(RawMesh.WedgeColors.Num()); - - // Convert Unreal to Houdini - // We need to re-index colors for wedges we swapped (due to winding differences). - for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) - { - ChangedColors[WedgeIdx + 0] = RawMesh.WedgeColors[WedgeIdx + 0].ReinterpretAsLinear(); - ChangedColors[WedgeIdx + 1] = RawMesh.WedgeColors[WedgeIdx + 2].ReinterpretAsLinear(); - ChangedColors[WedgeIdx + 2] = RawMesh.WedgeColors[WedgeIdx + 1].ReinterpretAsLinear(); - } - } - - if (ChangedColors.Num() > 0) - { - // Extract the RGB colors - TArray ColorValues; - ColorValues.SetNum(ChangedColors.Num() * 3); - for (int32 colorIndex = 0; colorIndex < ChangedColors.Num(); colorIndex++) - { - ColorValues[colorIndex * 3] = ChangedColors[colorIndex].R; - ColorValues[colorIndex * 3 + 1] = ChangedColors[colorIndex].G; - ColorValues[colorIndex * 3 + 2] = ChangedColors[colorIndex].B; - } - - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = ChangedColors.Num(); - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - ColorValues.GetData(), 0, AttributeInfoVertex.count), false); - - // Create the attribute for Alpha - TArray AlphaValues; - AlphaValues.SetNum(ChangedColors.Num()); - for (int32 alphaIndex = 0; alphaIndex < ChangedColors.Num(); alphaIndex++) - AlphaValues[alphaIndex] = ChangedColors[alphaIndex].A; - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.count = AlphaValues.Num(); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - AlphaValues.GetData(), 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // INDICES (VertexList) - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.WedgeIndices.Num() > 0) - { - TArray StaticMeshIndices; - StaticMeshIndices.SetNumUninitialized(RawMesh.WedgeIndices.Num()); - - // Convert Unreal to Houdini - for (int32 IndexIdx = 0; IndexIdx < RawMesh.WedgeIndices.Num(); IndexIdx += 3) - { - // Swap indices to fix winding order. - StaticMeshIndices[IndexIdx + 0] = RawMesh.WedgeIndices[IndexIdx + 0]; - StaticMeshIndices[IndexIdx + 1] = RawMesh.WedgeIndices[IndexIdx + 2]; - StaticMeshIndices[IndexIdx + 2] = RawMesh.WedgeIndices[IndexIdx + 1]; - } - - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, StaticMeshIndices.GetData(), 0, StaticMeshIndices.Num()), false); - - // We need to generate array of face counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, StaticMeshFaceCounts.GetData(), 0, StaticMeshFaceCounts.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE MATERIALS - //--------------------------------------------------------------------------------------------------------------------- - // Marshall face material indices. - if (RawMesh.FaceMaterialIndices.Num() > 0) - { - // Create an array of Material Interfaces - TArray MaterialInterfaces; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()) - { - // StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); - - int32 NumMeshBasedMaterials = StaticMeshComponent->GetNumMaterials(); - TArray MeshBasedMaterialInterfaces; - MeshBasedMaterialInterfaces.SetNumUninitialized(NumMeshBasedMaterials); - for (int32 i = 0; i < NumMeshBasedMaterials; i++) - MeshBasedMaterialInterfaces[i] = StaticMeshComponent->GetMaterial(i); - - int32 NumSections = StaticMesh->GetNumSections(InLODIndex); - MaterialInterfaces.SetNumUninitialized(NumSections); - for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) - { - FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex); - check(Info.MaterialIndex < NumMeshBasedMaterials); - MaterialInterfaces[SectionIndex] = MeshBasedMaterialInterfaces[Info.MaterialIndex]; - } - } - else - { - // Query the Static mesh's materials - for (int32 MatIdx = 0; MatIdx < StaticMesh->StaticMaterials.Num(); MatIdx++) - { - MaterialInterfaces.Add(StaticMesh->GetMaterial(MatIdx)); - } - - // Try to fix up inconsistencies between the RawMesh / StaticMesh material indexes - // by using the meshes sections... - // TODO: Fix me properly! - // Proper fix would be to export the meshes via the FStaticMeshLODResources obtained - // by GetLODForExport(), and then export the mesh by sections. - if (StaticMesh->RenderData && StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - TMap MapOfMaterials; - FStaticMeshLODResources& LODResources = StaticMesh->RenderData->LODResources[InLODIndex]; - for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) - { - // Get the material for each element at the current lod index - int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; - if (!MapOfMaterials.Contains(MaterialIndex)) - { - MapOfMaterials.Add(MaterialIndex, StaticMesh->GetMaterial(MaterialIndex)); - } - } - - if (MapOfMaterials.Num() > 0) - { - // Sort the output material in the correct order (by material index) - MapOfMaterials.KeySort([](int32 A, int32 B) { return A < B; }); - - // Set the value in the correct order - // Do not reduce the array of materials, this could cause crahses in some weird cases.. - if (MapOfMaterials.Num() > MaterialInterfaces.Num()) - MaterialInterfaces.SetNumZeroed(MapOfMaterials.Num()); - - int32 MaterialIndex = 0; - for (auto Kvp : MapOfMaterials) - { - MaterialInterfaces[MaterialIndex++] = Kvp.Value; - } - } - } - } - - // List of materials, one for each face. - TArray StaticMeshFaceMaterials; - - //Lists of material parameters - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - bool bAttributeSuccess = false; - bool bAddMaterialParametersAsAttributes = false; - - if (bAddMaterialParametersAsAttributes) - { - // Create attributes for the material and all its parameters - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - RawMesh.FaceMaterialIndices.Num(), - StaticMeshFaceMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - else - { - // Create attributes only for the materials - // Only get the material attribute data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials); - - // Create attribute for materials - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - RawMesh.FaceMaterialIndices.Num(), - StaticMeshFaceMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(StaticMeshFaceMaterials); - - // Delete texture material parameter names - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // FACE SMOOTHING MASKS - //--------------------------------------------------------------------------------------------------------------------- - if (RawMesh.FaceSmoothingMasks.Num() > 0) - { - HAPI_AttributeInfo AttributeInfoSmoothingMasks; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - AttributeInfoSmoothingMasks.count = RawMesh.FaceSmoothingMasks.Num(); - AttributeInfoSmoothingMasks.tupleSize = 1; - AttributeInfoSmoothingMasks.exists = true; - AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - (const int32 *)RawMesh.FaceSmoothingMasks.GetData(), 0, RawMesh.FaceSmoothingMasks.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - TArray AllTags; - for (auto& ComponentTag : StaticMeshComponent->ComponentTags) - AllTags.AddUnique(ComponentTag); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - for (auto& ActorTag : ParentActor->Tags) - AllTags.AddUnique(ActorTag); - } - - // Try to create groups for the tags - if (!FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, AllTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups for the Static Mesh Component and Actor tags!")); - - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - /* - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - -bool -FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( - const HAPI_NodeId& NodeId, - const FStaticMeshLODResources& LODResources, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent) -{ - // Convert the Mesh using FStaticMeshLODResources - - // Check that the mesh is not empty - if (LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0) - { - HOUDINI_LOG_ERROR(TEXT("No vertices in mesh!")); - return false; - } - - if (LODResources.Sections.Num() == 0) - { - HOUDINI_LOG_ERROR(TEXT("No triangles in mesh!")); - return false; - } - - // Vertex instance and triangle counts - const uint32 OrigNumVertexInstances = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); - const uint32 NumTriangles = LODResources.GetNumTriangles(); - const uint32 NumVertexInstances = NumTriangles * 3; - const uint32 NumSections = LODResources.Sections.Num(); - - // Grab the build scale - const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - // In FStaticMeshLODResources each vertex instances stores its position, even if the positions are not unique (in other - // words, in Houdini terminology, the number of points and vertices are the same. We'll do the same thing that Epic - // does in FBX export: we'll run through all vertex instances and use a hash to determine which instances share a - // position, so that we can a smaller number of points than vertices, and vertices share point positions - TArray UEVertexInstanceIdxToPointIdx; - UEVertexInstanceIdxToPointIdx.Reserve(OrigNumVertexInstances); - - TMap PositionToPointIndexMap; - PositionToPointIndexMap.Reserve(OrigNumVertexInstances); - - TArray StaticMeshVertices; - StaticMeshVertices.Reserve(OrigNumVertexInstances * 3); - for (uint32 VertexInstanceIndex = 0; VertexInstanceIndex < OrigNumVertexInstances; ++VertexInstanceIndex) - { - // Convert Unreal to Houdini - const FVector &PositionVector = LODResources.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexInstanceIndex); - const int32 *FoundPointIndexPtr = PositionToPointIndexMap.Find(PositionVector); - if (!FoundPointIndexPtr) - { - const int32 NewPointIndex = StaticMeshVertices.Add(PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X) / 3; - StaticMeshVertices.Add(PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z); - StaticMeshVertices.Add(PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y); - - PositionToPointIndexMap.Add(PositionVector, NewPointIndex); - UEVertexInstanceIdxToPointIdx.Add(NewPointIndex); - } - else - { - UEVertexInstanceIdxToPointIdx.Add(*FoundPointIndexPtr); - } - } - - StaticMeshVertices.Shrink(); - const uint32 NumVertices = StaticMeshVertices.Num() / 3; - - // Now that we know how many vertices (points), vertex instances (vertices) and triagnles we have, - // we can create the part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumVertexInstances; - Part.faceCount = NumTriangles; - Part.pointCount = NumVertices; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = Part.pointCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - - // Determine which attributes we have - const bool bIsVertexInstanceNormalsValid = true; - const bool bIsVertexInstanceTangentsValid = true; - const bool bIsVertexInstanceBinormalsValid = true; - const bool bIsVertexInstanceColorsValid = LODResources.bHasColorVertexData; - const uint32 NumUVLayers = FMath::Min(LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(), MAX_STATIC_TEXCOORDS); - const bool bIsVertexInstanceUVsValid = NumUVLayers > 0; - - bool bUseComponentOverrideColors = false; - // Determine if have override colors on the static mesh component, if so prefer to use those - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (ColorVertexBuffer.GetNumVertices() == LODResources.GetNumVertices()) - { - bUseComponentOverrideColors = true; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL INDEX -> MATERIAL INTERFACE - //--------------------------------------------------------------------------------------------------------------------- - TArray MaterialInterfaces; - TArray TriangleMaterialIndices; - - // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); - const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); - // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, - // then we will set UEDefaultMaterial at the invalid index - int32 UEDefaultMaterialIndex = INDEX_NONE; - UMaterialInterface *UEDefaultMaterial = nullptr; - if (NumStaticMaterials > 0) - { - MaterialInterfaces.Reserve(NumStaticMaterials); - for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) - { - const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; - UMaterialInterface *Material = nullptr; - if (bIsStaticMeshComponentValid) - { - Material = StaticMeshComponent->GetMaterial(MaterialIndex); - } - else - { - Material = MaterialInfo.MaterialInterface; - } - // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - UEDefaultMaterialIndex = MaterialIndex; - } - Material = UEDefaultMaterial; - HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); - } - // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); - MaterialInterfaces.Add(Material); - } - - TriangleMaterialIndices.Reserve(NumTriangles); - } - - // If we haven't created UEDefaultMaterial yet, check that all the sections' MaterialIndex - // is valid, if not, create UEDefaultMaterial and add to MaterialInterfaces to get UEDefaultMaterialIndex - if (!UEDefaultMaterial || UEDefaultMaterialIndex == INDEX_NONE) - { - for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) - { - // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial - // up to and including MaterialIndex and log a warning - const int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; - if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - // Add the UEDefaultMaterial to MaterialInterfaces - UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); - } - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); - } - } - } - - // Determine the final number of materials we have, with default for missing/invalid indices - const int32 NumMaterials = MaterialInterfaces.Num(); - - // Now we deal with vertex instance attributes. - if (NumTriangles > 0) - { - // UV layer array. Each layer has an array of floats, 3 floats per vertex instance - TArray> UVs; - // Normals: 3 floats per vertex instance - TArray Normals; - // Tangents: 3 floats per vertex instance - TArray Tangents; - // Binormals: 3 floats per vertex instance - TArray Binormals; - // RGBColors: 3 floats per vertex instance - TArray RGBColors; - // Alphas: 1 float per vertex instance - TArray Alphas; - - // Initialize the arrays for the attributes that are valid - if (bIsVertexInstanceUVsValid) - { - UVs.SetNum(NumUVLayers); - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); - } - } - - if (bIsVertexInstanceNormalsValid) - { - Normals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceTangentsValid) - { - Tangents.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceBinormalsValid) - { - Binormals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - RGBColors.SetNumUninitialized(NumVertexInstances * 3); - Alphas.SetNumUninitialized(NumVertexInstances); - } - - // Array of vertex (point position) indices per triangle - TArray MeshTriangleVertexIndices; - MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); - // Array of vertex counts per triangle/face - TArray MeshTriangleVertexCounts; - MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); - - int32 TriangleIdx = 0; - int32 HoudiniVertexIdx = 0; - FIndexArrayView TriangleVertexIndices = LODResources.IndexBuffer.GetArrayView(); - for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) - { - const FStaticMeshSection& Section = LODResources.Sections[SectionIndex]; - for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < Section.NumTriangles; ++SectionTriangleIndex) - { - MeshTriangleVertexCounts[TriangleIdx] = 3; - for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) - { - // Reverse the winding order for Houdini (but still start at 0) - const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; - const uint32 UEVertexIndex = TriangleVertexIndices[Section.FirstIndex + SectionTriangleIndex * 3 + WindingIdx]; - - // Calculate the index of the first component of a vertex instance's value in an inline float array - // representing vectors (3 float) per vertex instance - const int32 Float3Index = HoudiniVertexIdx * 3; - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - const FVector2D &UV = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(UEVertexIndex, UVLayerIndex); - UVs[UVLayerIndex][Float3Index + 0] = UV.X; - UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; - UVs[UVLayerIndex][Float3Index + 2] = 0; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - const FVector &Normal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(UEVertexIndex); - Normals[Float3Index + 0] = Normal.X; - Normals[Float3Index + 1] = Normal.Z; - Normals[Float3Index + 2] = Normal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - const FVector &Tangent = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(UEVertexIndex); - Tangents[Float3Index + 0] = Tangent.X; - Tangents[Float3Index + 1] = Tangent.Z; - Tangents[Float3Index + 2] = Tangent.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - // In order to calculate the binormal we also need the tangent and normal - if (bIsVertexInstanceBinormalsValid) - { - FVector Binormal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(UEVertexIndex); - Binormals[Float3Index + 0] = Binormal.X; - Binormals[Float3Index + 1] = Binormal.Z; - Binormals[Float3Index + 2] = Binormal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - FVector4 Color = FLinearColor::White; - if (bUseComponentOverrideColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - Color = ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); - } - else - { - Color = LODResources.VertexBuffers.ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); - } - RGBColors[Float3Index + 0] = Color[0]; - RGBColors[Float3Index + 1] = Color[1]; - RGBColors[Float3Index + 2] = Color[2]; - Alphas[HoudiniVertexIdx] = Color[3]; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - if (UEVertexInstanceIdxToPointIdx.IsValidIndex(UEVertexIndex)) - { - MeshTriangleVertexIndices[HoudiniVertexIdx] = UEVertexInstanceIdxToPointIdx[UEVertexIndex]; - } - - HoudiniVertexIdx++; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - if (MaterialInterfaces.IsValidIndex(Section.MaterialIndex)) - { - TriangleMaterialIndices.Add(Section.MaterialIndex); - } - else - { - TriangleMaterialIndices.Add(UEDefaultMaterialIndex); - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, Section.MaterialIndex, *(UEDefaultMaterial->GetPathName())); - } - - TriangleIdx++; - } - } - - // Now transfer valid vertex instance attributes to Houdini vertex attributes - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) - { - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (UVLayerIndex > 0) - UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = NumVertexInstances; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, Normals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - Tangents.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceBinormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - Binormals.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - RGBColors.GetData(), 0, AttributeInfoVertex.count), false); - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.count = Alphas.Num(); - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - Alphas.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); - - // Send the array of face vertex counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); - - // Send material assignments to Houdini - if (NumMaterials > 0) - { - // List of materials, one for each face. - TArray TriangleMaterials; - - //Lists of material parameters - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - bool bAttributeSuccess = false; - bool bAddMaterialParametersAsAttributes = false; - - if (bAddMaterialParametersAsAttributes) - { - // Create attributes for the material and all its parameters - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - else - { - // Create attributes only for the materials - // Only get the material attribute data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); - - // Create attribute for materials - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); - - // Delete texture material parameter names - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (!bAttributeSuccess) - { - check(0); - return false; - } - } - - // TODO: The render mesh (LODResources) does not have face smoothing information, and the raw mesh triangle order is - // potentially different (see also line 4152 TODO_FBX in Engine\Source\Editor\UnrealEd\Private\Fbx\FbxMainExport.cpp - ////--------------------------------------------------------------------------------------------------------------------- - //// TRIANGLE SMOOTHING MASKS - ////--------------------------------------------------------------------------------------------------------------------- - //TArray TriangleSmoothingMasks; - //TriangleSmoothingMasks.SetNumZeroed(NumTriangles); - //FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); - //if (TriangleSmoothingMasks.Num() > 0) - //{ - // HAPI_AttributeInfo AttributeInfoSmoothingMasks; - // FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - // AttributeInfoSmoothingMasks.tupleSize = 1; - // AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); - // AttributeInfoSmoothingMasks.exists = true; - // AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - // AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - // AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - // FHoudiniEngine::Get().GetSession(), - // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - // FHoudiniEngine::Get().GetSession(), - // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - // (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); - //} - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - // Try to create groups for the static mesh component's tags - if (StaticMeshComponent->ComponentTags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Try to create groups for the parent Actor's tags - if (ParentActor->Tags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - - /* - // Add the unreal_level_path attribute - FString LevelPath = ActorPath;// FString(); - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - -bool -FUnrealMeshTranslator::CreateInputNodeForMeshDescription( - const HAPI_NodeId& NodeId, - const FMeshDescription& MeshDescription, - const int32& InLODIndex, - const bool& bAddLODGroups, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent) -{ - // Convert the Mesh using FMeshDescription - // Get references to the attributes we are interested in - // before sending to Houdini we'll check if each attribute is valid - FStaticMeshConstAttributes MeshDescriptionAttributes(MeshDescription); - - TVertexAttributesConstRef VertexPositions = MeshDescriptionAttributes.GetVertexPositions(); - TVertexInstanceAttributesConstRef VertexInstanceNormals = MeshDescriptionAttributes.GetVertexInstanceNormals(); - TVertexInstanceAttributesConstRef VertexInstanceTangents = MeshDescriptionAttributes.GetVertexInstanceTangents(); - TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = MeshDescriptionAttributes.GetVertexInstanceBinormalSigns(); - TVertexInstanceAttributesConstRef VertexInstanceColors = MeshDescriptionAttributes.GetVertexInstanceColors(); - TVertexInstanceAttributesConstRef VertexInstanceUVs = MeshDescriptionAttributes.GetVertexInstanceUVs(); - //TPolygonGroupAttributesConstRef PolygonGroupMaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames(); - - // Get the vertex and triangle arrays - const FVertexArray &MDVertices = MeshDescription.Vertices(); - const FPolygonGroupArray &MDPolygonGroups = MeshDescription.PolygonGroups(); - const FPolygonArray &MDPolygons = MeshDescription.Polygons(); - const FTriangleArray &MDTriangles = MeshDescription.Triangles(); - - // Determine point, vertex and polygon counts - const uint32 NumVertices = MDVertices.Num(); - const uint32 NumTriangles = MDTriangles.Num(); - const uint32 NumVertexInstances = NumTriangles * 3; - - // Some checks: we expect triangulated meshes - if (MeshDescription.VertexInstances().Num() != NumTriangles * 3) - { - HOUDINI_LOG_ERROR(TEXT("Expected a triangulated mesh, but # VertexInstances (%d) != # Triangles * 3 (%d)"), MeshDescription.VertexInstances().Num(), NumTriangles * 3); - return false; - } - - // Determine which attributes we have - const bool bIsVertexPositionsValid = VertexPositions.IsValid(); - const bool bIsVertexInstanceNormalsValid = VertexInstanceNormals.IsValid(); - const bool bIsVertexInstanceTangentsValid = VertexInstanceTangents.IsValid(); - const bool bIsVertexInstanceBinormalSignsValid = VertexInstanceBinormalSigns.IsValid(); - const bool bIsVertexInstanceColorsValid = VertexInstanceColors.IsValid(); - const bool bIsVertexInstanceUVsValid = VertexInstanceUVs.IsValid(); - //const bool bIsPolygonGroupImportedMaterialSlotNamesValid = PolygonGroupMaterialSlotNames.IsValid(); - - // Create part. - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = NumVertexInstances; - Part.faceCount = NumTriangles; - Part.pointCount = NumVertices; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); - AttributeInfoPoint.count = Part.pointCount; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Grab the build scale - const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); - FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; - - //--------------------------------------------------------------------------------------------------------------------- - // POSITION (P) - //--------------------------------------------------------------------------------------------------------------------- - // The mesh element arrays are sparse: the max index/ID value can be larger than the number of elements - 1 - // so we have to maintain a lookup of VertexID (UE) to PointIndex (Houdini) - TArray VertexIDToHIndex; - if (bIsVertexPositionsValid && VertexPositions.GetNumElements() >= 3) - { - TArray StaticMeshVertices; - StaticMeshVertices.SetNumUninitialized(NumVertices * 3); - - int32 VertexIdx = 0; - VertexIDToHIndex.Init(INDEX_NONE, MDVertices.GetArraySize()); - - for (const FVertexID& VertexID : MDVertices.GetElementIDs()) - { - // Convert Unreal to Houdini - const FVector &PositionVector = VertexPositions.Get(VertexID); - StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; - StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; - StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; - - // Record the UE Vertex ID to Houdini Point Index lookup - VertexIDToHIndex[VertexID.GetValue()] = VertexIdx; - VertexIdx++; - } - - // Now that we have raw positions, we can upload them for our attribute. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); - } - - bool bUseComponentOverrideColors = false; - // Determine if have override colors on the static mesh component, if so prefer to use those - if (StaticMeshComponent && - StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && - StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && - StaticMesh->RenderData && - StaticMesh->RenderData->LODResources.IsValidIndex(InLODIndex)) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshRenderData& RenderData = *StaticMesh->RenderData; - FStaticMeshLODResources& RenderModel = RenderData.LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) - { - // Use the wedge map if it is available as it is lossless. - if (RenderModel.WedgeMap.Num() == NumVertexInstances) - { - bUseComponentOverrideColors = true; - } - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // MATERIAL SLOT -> MATERIAL INTERFACE - //--------------------------------------------------------------------------------------------------------------------- - // In theory the ImportedMaterialSlotName attribute on PolygonGroups should tell us which material slot is used by - // that group, and thus which MaterialIndex we should assign to triangles in that group. Unfortunately we have - // encountered cases where the ImportedMaterialSlotName name attribute does not match any of the MaterialSlotName or - // ImportedMaterialSlotNames in the StaticMesh->StaticMaterials array. Therefore we have no choice but to rely - // on the PolygonGroup order vs Section order to determine the MaterialIndex for a group. We do what Epic does - // when building a static mesh: Sections are created in the same order as iterating over PolygonGroups, but importantly, - // empty PolygonGroups are skipped - - // // Get material slot name to material index - // and the UMaterialInterface array - // TMap MaterialSlotToInterface; - TArray MaterialInterfaces; - TArray TriangleMaterialIndices; - - // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); - const int32 NumStaticMaterials = StaticMesh->StaticMaterials.Num(); - // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, - // then we will set UEDefaultMaterial at the invalid index - int32 UEDefaultMaterialIndex = INDEX_NONE; - UMaterialInterface *UEDefaultMaterial = nullptr; - if (NumStaticMaterials > 0) - { - MaterialInterfaces.Reserve(NumStaticMaterials); - for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) - { - const FStaticMaterial &MaterialInfo = StaticMesh->StaticMaterials[MaterialIndex]; - UMaterialInterface *Material = nullptr; - if (bIsStaticMeshComponentValid) - { - Material = StaticMeshComponent->GetMaterial(MaterialIndex); - } - else - { - Material = MaterialInfo.MaterialInterface; - } - // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - UEDefaultMaterialIndex = MaterialIndex; - } - Material = UEDefaultMaterial; - HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); - } - // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); - MaterialInterfaces.Add(Material); - } - - TriangleMaterialIndices.Reserve(NumTriangles); - } - // SectionIndex: Looking at Epic's StaticMesh build code, Sections are created in the same - // order as iterating over PolygonGroups, but skipping empty PolygonGroups - TMap PolygonGroupToMaterialIndex; - PolygonGroupToMaterialIndex.Reserve(MeshDescription.PolygonGroups().Num()); - int32 SectionIndex = 0; - for (const FPolygonGroupID &PolygonGroupID : MDPolygonGroups.GetElementIDs()) - { - // Skip empty polygon groups - if (MeshDescription.GetNumPolygonGroupPolygons(PolygonGroupID) == 0) - { - continue; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - // // Get the material index for the material slot for this polygon group - //int32 MaterialIndex = INDEX_NONE; - //if (bIsPolygonGroupImportedMaterialSlotNamesValid) - //{ - // const FName &MaterialSlotName = PolygonGroupMaterialSlotNames.Get(PolygonGroupID); - // const int32 *MaterialIndexPtr = MaterialSlotToInterface.Find(MaterialSlotName); - // if (MaterialIndexPtr != nullptr) - // { - // MaterialIndex = *MaterialIndexPtr; - // } - //} - - // Get the material for the LOD and section via the section info map - if (StaticMesh->GetNumSections(InLODIndex) <= SectionIndex) - { - HOUDINI_LOG_ERROR(TEXT("Found more non-empty polygon groups in the mesh description for LOD %d than sections in the static mesh..."), InLODIndex); - return false; - } - - // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial - // up to and including MaterialIndex and log a warning - int32 MaterialIndex = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex).MaterialIndex; - if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) - { - if (!UEDefaultMaterial) - { - UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); - // Add the UEDefaultMaterial to MaterialInterfaces - UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); - } - HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); - MaterialIndex = UEDefaultMaterialIndex; - } - - PolygonGroupToMaterialIndex.Add(PolygonGroupID, MaterialIndex); - SectionIndex++; - } - - // Determine the final number of materials we have, with defaults for missing/invalid indices - const int32 NumMaterials = MaterialInterfaces.Num(); - - // Now we deal with vertex instance attributes. - // // First we must also record a UE VertexInstanceID to Houdini Vertex Index lookup, - // // and then get and convert all valid and supported vertex instance attributes from UE - // TArray VertexInstanceIDToHIndex; - - if (NumTriangles > 0) - { - // UV layer array. Each layer has an array of floats, 3 floats per vertex instance - TArray> UVs; - const int32 NumUVLayers = bIsVertexInstanceUVsValid ? FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_STATIC_TEXCOORDS) : 0; - // Normals: 3 floats per vertex instance - TArray Normals; - // Tangents: 3 floats per vertex instance - TArray Tangents; - // Binormals: 3 floats per vertex instance - TArray Binormals; - // RGBColors: 3 floats per vertex instance - TArray RGBColors; - // Alphas: 1 float per vertex instance - TArray Alphas; - - // Initialize the arrays for the attributes that are valid - if (bIsVertexInstanceUVsValid) - { - UVs.SetNum(NumUVLayers); - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); - } - } - - if (bIsVertexInstanceNormalsValid) - { - Normals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceTangentsValid) - { - Tangents.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bIsVertexInstanceBinormalSignsValid) - { - Binormals.SetNumUninitialized(NumVertexInstances * 3); - } - - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - RGBColors.SetNumUninitialized(NumVertexInstances * 3); - Alphas.SetNumUninitialized(NumVertexInstances); - } - - // Array of material index per triangle/face - TArray MeshTriangleVertexIndices; - MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); - // Array of vertex counts per triangle/face - TArray MeshTriangleVertexCounts; - MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); - - int32 TriangleIdx = 0; - int32 VertexInstanceIdx = 0; - // VertexInstanceIDToHIndex.Init(-1, MDVertexInstances.GetArraySize()); - - for (const FPolygonID &PolygonID : MDPolygons.GetElementIDs()) - { - for (const FTriangleID &TriangleID : MeshDescription.GetPolygonTriangleIDs(PolygonID)) - { - MeshTriangleVertexCounts[TriangleIdx] = 3; - for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) - { - // Reverse the winding order for Houdini (but still start at 0) - const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; - const FVertexInstanceID &VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, WindingIdx); - - // // UE Vertex Instance ID to Houdini Vertex Index look up - // VertexInstanceIDToHIndex[VertexInstanceID.GetValue()] = VertexInstanceIdx; - - // Calculate the index of the first component of a vertex instance's value in an inline float array - // representing vectors (3 float) per vertex instance - const int32 Float3Index = VertexInstanceIdx * 3; - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) - { - const FVector2D &UV = VertexInstanceUVs.Get(VertexInstanceID, UVLayerIndex); - UVs[UVLayerIndex][Float3Index + 0] = UV.X; - UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; - UVs[UVLayerIndex][Float3Index + 2] = 0; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - const FVector &Normal = VertexInstanceNormals.Get(VertexInstanceID); - Normals[Float3Index + 0] = Normal.X; - Normals[Float3Index + 1] = Normal.Z; - Normals[Float3Index + 2] = Normal.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - const FVector &Tangent = VertexInstanceTangents.Get(VertexInstanceID); - Tangents[Float3Index + 0] = Tangent.X; - Tangents[Float3Index + 1] = Tangent.Z; - Tangents[Float3Index + 2] = Tangent.Y; - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - // In order to calculate the binormal we also need the tangent and normal - if (bIsVertexInstanceBinormalSignsValid && bIsVertexInstanceTangentsValid && bIsVertexInstanceNormalsValid) - { - const float &BinormalSign = VertexInstanceBinormalSigns.Get(VertexInstanceID); - FVector Binormal = FVector::CrossProduct( - FVector(Tangents[Float3Index + 0], Tangents[Float3Index + 1], Tangents[Float3Index + 2]), - FVector(Normals[Float3Index + 0], Normals[Float3Index + 1], Normals[Float3Index + 2]) - ) * BinormalSign; - Binormals[Float3Index + 0] = Binormal.X; - Binormals[Float3Index + 1] = Binormal.Y; - Binormals[Float3Index + 2] = Binormal.Z; - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - FVector4 Color = FLinearColor::White; - if (bUseComponentOverrideColors) - { - FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; - FStaticMeshLODResources& RenderModel = StaticMesh->RenderData->LODResources[InLODIndex]; - FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; - - int32 Index = RenderModel.WedgeMap[VertexInstanceIdx]; - if (Index != INDEX_NONE) - { - Color = ColorVertexBuffer.VertexColor(Index).ReinterpretAsLinear(); - } - } - else - { - Color = VertexInstanceColors.Get(VertexInstanceID); - } - RGBColors[Float3Index + 0] = Color[0]; - RGBColors[Float3Index + 1] = Color[1]; - RGBColors[Float3Index + 2] = Color[2]; - Alphas[VertexInstanceIdx] = Color[3]; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - const FVertexID& VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID); - const int32 UEVertexIdx = VertexID.GetValue(); - if (VertexIDToHIndex.IsValidIndex(UEVertexIdx)) - { - MeshTriangleVertexIndices[VertexInstanceIdx] = VertexIDToHIndex[UEVertexIdx]; - } - - VertexInstanceIdx++; - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE MATERIAL ASSIGNMENT - //--------------------------------------------------------------------------------------------------------------------- - const FPolygonGroupID &PolygonGroupID = MeshDescription.GetPolygonPolygonGroup(PolygonID); - const int32 MaterialIndex = PolygonGroupToMaterialIndex.FindChecked(PolygonGroupID); - TriangleMaterialIndices.Add(MaterialIndex); - - TriangleIdx++; - } - } - - // Now transfer valid vertex instance attributes to Houdini vertex attributes - - //--------------------------------------------------------------------------------------------------------------------- - // UVS (uvX) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceUVsValid) - { - for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) - { - // Construct the attribute name for this UV index. - FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; - if (UVLayerIndex > 0) - UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); - - // Create attribute for UVs - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.count = NumVertexInstances; - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), - &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), - 0, AttributeInfoVertex.count), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // NORMALS (N) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceNormalsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, - &AttributeInfoVertex, Normals.GetData(), - 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TANGENT (tangentu) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceTangentsValid) - { - // Create attribute for tangentu. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, - Tangents.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // BINORMAL (tangentv) - //--------------------------------------------------------------------------------------------------------------------- - if (bIsVertexInstanceBinormalSignsValid) - { - // Create attribute for normals. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, - Binormals.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // COLORS (Cd) - //--------------------------------------------------------------------------------------------------------------------- - if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) - { - // Create attribute for colors. - HAPI_AttributeInfo AttributeInfoVertex; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - - AttributeInfoVertex.tupleSize = 3; - AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, - RGBColors.GetData(), 0, AttributeInfoVertex.count), false); - - FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); - AttributeInfoVertex.tupleSize = 1; - AttributeInfoVertex.count = Alphas.Num(); - AttributeInfoVertex.exists = true; - AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; - AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, - Alphas.GetData(), 0, AttributeInfoVertex.count), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE/FACE VERTEX INDICES - //--------------------------------------------------------------------------------------------------------------------- - // We can now set vertex list. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); - - // Send the array of face vertex counts. - TArray< int32 > StaticMeshFaceCounts; - StaticMeshFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); - - // Send material assignments to Houdini - if (NumMaterials > 0) - { - // List of materials, one for each face. - TArray TriangleMaterials; - - //Lists of material parameters - TMap> ScalarMaterialParameters; - TMap> VectorMaterialParameters; - TMap> TextureMaterialParameters; - - bool bAttributeSuccess = false; - bool bAddMaterialParametersAsAttributes = false; - - if (bAddMaterialParametersAsAttributes) - { - // Create attributes for the material and all its parameters - // Get material attribute data, and all material parameters data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, - ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); - - // Create attribute for materials and all attributes for material parameters - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - else - { - // Create attributes only for the materials - // Only get the material attribute data - FUnrealMeshTranslator::CreateFaceMaterialArray( - MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); - - // Create attribute for materials - bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - NodeId, - 0, - TriangleMaterials.Num(), - TriangleMaterials, - ScalarMaterialParameters, - VectorMaterialParameters, - TextureMaterialParameters); - } - - // Delete material names. - FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); - - // Delete texture material parameter names - for (auto & Pair : TextureMaterialParameters) - { - FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); - } - - if (bAttributeSuccess) - { - check(0); - return false; - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // TRIANGLE SMOOTHING MASKS - //--------------------------------------------------------------------------------------------------------------------- - TArray TriangleSmoothingMasks; - TriangleSmoothingMasks.SetNumZeroed(NumTriangles); - FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); - if (TriangleSmoothingMasks.Num() > 0) - { - HAPI_AttributeInfo AttributeInfoSmoothingMasks; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); - - AttributeInfoSmoothingMasks.tupleSize = 1; - AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); - AttributeInfoSmoothingMasks.exists = true; - AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; - AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, - (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // LIGHTMAP RESOLUTION - //--------------------------------------------------------------------------------------------------------------------- - - // TODO: - // Fetch default lightmap res from settings... - int32 GeneratedLightMapResolution = 32; - if (StaticMesh->LightMapResolution != GeneratedLightMapResolution) - { - TArray< int32 > LightMapResolutions; - LightMapResolutions.Add(StaticMesh->LightMapResolution); - - HAPI_AttributeInfo AttributeInfoLightMapResolution; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); - AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); - AttributeInfoLightMapResolution.tupleSize = 1; - AttributeInfoLightMapResolution.exists = true; - AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; - AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, - (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT MESH NAME - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - const FString MeshAssetPath = StaticMesh->GetPathName(); - std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); - const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = MeshAssetPathRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - - //--------------------------------------------------------------------------------------------------------------------- - // INPUT SOURCE FILE - //--------------------------------------------------------------------------------------------------------------------- - { - // Create primitive attribute with mesh asset path - FString Filename; - if (UAssetImportData* ImportData = StaticMesh->AssetImportData) - { - for (const auto& SourceFile : ImportData->SourceData.SourceFiles) - { - Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); - break; - } - } - - if (!Filename.IsEmpty()) - { - std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); - const char* FilenameCStrRaw = FilenameCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Part.faceCount); - for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) - { - PrimitiveAttrs[Ix] = FilenameCStrRaw; - } - - HAPI_AttributeInfo AttributeInfo; - FHoudiniApi::AttributeInfo_Init(&AttributeInfo); - AttributeInfo.count = Part.faceCount; - AttributeInfo.tupleSize = 1; - AttributeInfo.exists = true; - AttributeInfo.owner = HAPI_ATTROWNER_PRIM; - AttributeInfo.storage = HAPI_STORAGETYPE_STRING; - AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, - PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); - } - } - - /* - // Check if we have vertex attribute data to add - if (StaticMeshComponent && StaticMeshComponent->GetOwner()) - { - if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) - { - bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); - if (!bSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); - } - } - } - */ - - //--------------------------------------------------------------------------------------------------------------------- - // LOD GROUP AND SCREENSIZE - //--------------------------------------------------------------------------------------------------------------------- - if (bAddLODGroups) - { - // LOD Group - const char * LODGroupStr = ""; - { - FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); - LODGroupStr = TCHAR_TO_UTF8(*LODGroup); - } - - // Add a LOD group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); - - // Set GroupMembership - TArray GroupArray; - GroupArray.Init(1, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, - GroupArray.GetData(), 0, Part.faceCount), false); - - if (!StaticMesh->bAutoComputeLODScreenSize) - { - // Add the lodX_screensize attribute - FString LODAttributeName = - TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); - - // Create lodX_screensize detail attribute info. - HAPI_AttributeInfo AttributeInfoLODScreenSize; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); - AttributeInfoLODScreenSize.count = 1; - AttributeInfoLODScreenSize.tupleSize = 1; - AttributeInfoLODScreenSize.exists = true; - AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; - AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); - - // TODO: FIX? - // Get the actual screensize instead of the src model default? - float lodscreensize = SourceModel.ScreenSize.Default; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), NodeId, 0, - TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, - &lodscreensize, 0, 1), false); - } - } - - //--------------------------------------------------------------------------------------------------------------------- - // COMPONENT AND ACTOR TAGS - //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) - { - // Try to create groups for the static mesh component's tags - if (StaticMeshComponent->ComponentTags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); - - AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - // Try to create groups for the parent Actor's tags - if (ParentActor->Tags.Num() > 0 - && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); - - // Add the unreal_actor_path attribute - FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); - - // Add the unreal_level_path attribute - FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); - - /* - FString LevelPath = FString(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - if (ULevel* Level = ParentActor->GetLevel()) - { - LevelPath = Level->GetPathName(); - - // We just want the path up to the first point - int32 DotIndex; - if (LevelPath.FindChar('.', DotIndex)) - LevelPath.LeftInline(DotIndex, false); - - AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); - } - } - */ - } - } - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), NodeId), false); - - return true; -} - - -void -FUnrealMeshTranslator::CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray& OutStaticMeshFaceMaterials) -{ - // We need to create list of unique materials. - TArray UniqueMaterialList; - - UMaterialInterface * MaterialInterface = nullptr; - char* UniqueName = nullptr; - - UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); - - if (Materials.Num()) - { - // We have materials. - for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) - { - UniqueName = nullptr; - MaterialInterface = Materials[MaterialIdx]; - if (!MaterialInterface) - { - // Null material interface found, add default instead. - UniqueMaterialList.Add(DefaultMaterialName); - - // No need to collect material parameters on the default material - continue; - } - - // We found a material, get its name and material parameters - FString FullMaterialName = MaterialInterface->GetPathName(); - UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); - UniqueMaterialList.Add(UniqueName); - } - } - else - { - // We do not have any materials, add default. - UniqueMaterialList.Add(DefaultMaterialName); - } - - // TODO: Needs to be improved! - // We shouldnt be testing for each face, but only for each unique facematerial value... - for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) - { - int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; - if (UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) - { - OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); - } - else - { - OutStaticMeshFaceMaterials.Add(DefaultMaterialName); - } - } -} - - -void -FUnrealMeshTranslator::CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray& OutStaticMeshFaceMaterials, - TMap> & OutScalarMaterialParameters, - TMap> & OutVectorMaterialParameters, - TMap> & OutTextureMaterialParameters) -{ - // We need to create list of unique materials. - TArray UniqueMaterialList; - - UMaterialInterface * MaterialInterface = nullptr; - char* UniqueName = nullptr; - - UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); - - // Initialize material parameter arrays - TMap> ScalarParams; - TMap> VectorParams; - TMap> TextureParams; - - if (Materials.Num()) - { - // We have materials. - for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) - { - UniqueName = nullptr; - MaterialInterface = Materials[MaterialIdx]; - if (!MaterialInterface) - { - // Null material interface found, add default instead. - UniqueMaterialList.Add(DefaultMaterialName); - - // No need to collect material parameters on the default material - continue; - } - - // We found a material, get its name and material parameters - FString FullMaterialName = MaterialInterface->GetPathName(); - UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); - UniqueMaterialList.Add(UniqueName); - - // Collect all scalar parameters in all materials - { - TArray MaterialScalarParamInfos; - TArray MaterialScalarParamGuids; - MaterialInterface->GetAllScalarParameterInfo(MaterialScalarParamInfos, MaterialScalarParamGuids); - - for (auto & CurScalarParam : MaterialScalarParamInfos) - { - FString CurScalarParamName = CurScalarParam.Name.ToString(); - float CurScalarVal; - MaterialInterface->GetScalarParameterValue(CurScalarParam, CurScalarVal); - if (!ScalarParams.Contains(CurScalarParamName)) - { - TArray CurArray; - CurArray.SetNumUninitialized(Materials.Num()); - // Initialize the array with the Min float value - for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) - CurArray[ArrIdx] = FLT_MIN; - - ScalarParams.Add(CurScalarParamName, CurArray); - OutScalarMaterialParameters.Add(CurScalarParamName); - } - - ScalarParams[CurScalarParamName][MaterialIdx] = CurScalarVal; - } - } - - // Collect all vector parameters in all materials - { - TArray MaterialVectorParamInfos; - TArray MaterialVectorParamGuids; - MaterialInterface->GetAllVectorParameterInfo(MaterialVectorParamInfos, MaterialVectorParamGuids); - - for (auto & CurVectorParam : MaterialVectorParamInfos) - { - FString CurVectorParamName = CurVectorParam.Name.ToString(); - FLinearColor CurVectorValue; - MaterialInterface->GetVectorParameterValue(CurVectorParam, CurVectorValue); - if (!VectorParams.Contains(CurVectorParamName)) - { - TArray CurArray; - CurArray.SetNumUninitialized(Materials.Num()); - FLinearColor MinColor(FLT_MIN, FLT_MIN, FLT_MIN, FLT_MIN); - for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) - CurArray[ArrIdx] = MinColor; - - VectorParams.Add(CurVectorParamName, CurArray); - OutVectorMaterialParameters.Add(CurVectorParamName); - } - - VectorParams[CurVectorParamName][MaterialIdx] = CurVectorValue; - } - } - - // Collect all texture parameters in all materials - { - TArray MaterialTextureParamInfos; - TArray MaterialTextureParamGuids; - MaterialInterface->GetAllTextureParameterInfo(MaterialTextureParamInfos, MaterialTextureParamGuids); - - for (auto & CurTextureParam : MaterialTextureParamInfos) - { - FString CurTextureParamName = CurTextureParam.Name.ToString(); - UTexture * CurTexture = nullptr; - MaterialInterface->GetTextureParameterValue(CurTextureParam, CurTexture); - - if (!CurTexture || CurTexture->IsPendingKill()) - continue; - - FString TexturePath = CurTexture->GetPathName(); - if (!TextureParams.Contains(CurTextureParamName)) - { - TArray CurArray; - CurArray.SetNumZeroed(Materials.Num()); - - TextureParams.Add(CurTextureParamName, CurArray); - OutTextureMaterialParameters.Add(CurTextureParamName); - } - - char * TexturePathRawStr = UniqueName = FHoudiniEngineUtils::ExtractRawString(TexturePath); - TextureParams[CurTextureParamName][MaterialIdx] = TexturePathRawStr; - } - } - - } - } - else - { - // We do not have any materials, add default. - UniqueMaterialList.Add(DefaultMaterialName); - } - - // TODO: Needs to be improved! - // We shouldnt be testing for each face, but only for each unique facematerial value... - for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) - { - int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; - if(UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) - { - OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); - - for (auto & Pair : ScalarParams) - { - OutScalarMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); - } - - for (auto & Pair : VectorParams) - { - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].R); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].G); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].B); - OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].A); - } - - for (auto & Pair : TextureParams) - { - OutTextureMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); - } - } - else - { - OutStaticMeshFaceMaterials.Add(DefaultMaterialName); - } - } -} - - -void -FUnrealMeshTranslator::DeleteFaceMaterialArray(TArray& OutStaticMeshFaceMaterials) -{ - // Clean up the memory allocated by CreateFaceMaterialArray() - TSet UniqueMaterials(OutStaticMeshFaceMaterials); - for (TSet::TIterator Iter = UniqueMaterials.CreateIterator(); Iter; ++Iter) - { - char* MaterialName = *Iter; - FMemory::Free(MaterialName); - } - - OutStaticMeshFaceMaterials.Empty(); -} - -bool -FUnrealMeshTranslator::CreateInputNodeForBox( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& BoxCenter, - const FVector& BoxExtent, - const FRotator& BoxRotation) -{ - // Create a new input node for the box collider - FString BoxName = TEXT("box") + FString::FromInt(ColliderIndex); - - // Create the node in this input object's OBJ node - HAPI_NodeId BoxNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("box"), BoxName, false, &BoxNodeId), false); - - // Set the box parameters - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 0, BoxExtent.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 1, BoxExtent.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 2, BoxExtent.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 0, BoxCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 1, BoxCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 2, BoxCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 0, BoxRotation.Roll); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 2, BoxRotation.Pitch); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 1, BoxRotation.Yaw); - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), BoxNodeId, &CookOptions); - */ - if (!FHoudiniEngineUtils::HapiCookNode(BoxNodeId, nullptr, true)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmInfo ParmInfo; - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_box") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, BoxNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForSphere( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphereCenter, - const float& SphereRadius) -{ - // Create a new input node for the sphere collider - const char * SphereName = ""; - { - FString SPH = TEXT("Sphere") + FString::FromInt(ColliderIndex); - SphereName = TCHAR_TO_UTF8(*SPH); - } - - // Create the node in this input object's OBJ node - HAPI_NodeId SphereNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "sphere", SphereName, false, &SphereNodeId), false); - - // Set the box parameters - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 1, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 2, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 0, SphereCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 1, SphereCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 2, SphereCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); - - FHoudiniApi::SetParmIntValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "type", 0, 1); - /* - FHoudiniApi::SetParmFloatValue( - FHoudiniEngine::Get().GetSession(), SphereNodeId, "scale", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); - */ - - /* - HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), SphereNodeId, &CookOptions); - */ - if (!FHoudiniEngineUtils::HapiCookNode(SphereNodeId, nullptr, true)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmInfo ParmInfo; - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_sphere") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphereNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForSphyl( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphylCenter, - const FRotator& SphylRotation, - const float& SphylRadius, - const float& SphereLength) -{ - // - // Get the Sphyl's vertices and indices - // (code drived from FKSphylElem::GetElemSolid) - // - - // TODO: - // Rotation? - - const int32 NumSides = 6; - const int32 NumRings = (NumSides / 2) + 1; - - // The first/last arc are on top of each other. - const int32 NumVerts = (NumSides + 1) * (NumRings + 1); - - // Calculate the vertices for one arc - TArray ArcVertices; - ArcVertices.SetNum(NumRings + 1); - for (int32 RingIdx = 0; RingIdx < NumRings + 1; RingIdx++) - { - float Angle; - float ZOffset; - if (RingIdx <= NumSides / 4) - { - Angle = ((float)RingIdx / (NumRings - 1)) * PI; - ZOffset = 0.5 * SphereLength; - } - else - { - Angle = ((float)(RingIdx - 1) / (NumRings - 1)) * PI; - ZOffset = -0.5 * SphereLength; - } - - // Note- unit sphere, so position always has mag of one. We can just use it for normal! - FVector SpherePos; - SpherePos.X = 0.0f; - SpherePos.Y = SphylRadius * FMath::Sin(Angle); - SpherePos.Z = SphylRadius * FMath::Cos(Angle); - - ArcVertices[RingIdx] = SpherePos + FVector(0, 0, ZOffset); - } - - // Get the transform matrix for the rotation - FRotationMatrix SphylRotMatrix(SphylRotation); - - // Get the Sphyl's vertices by rotating the arc NumSides+1 times. - TArray Vertices; - Vertices.SetNum(NumVerts * 3); - for (int32 SideIdx = 0; SideIdx < NumSides + 1; SideIdx++) - { - const FRotator ArcRotator(0, 360.f * ((float)SideIdx / NumSides), 0); - const FRotationMatrix ArcRot(ArcRotator); - const float XTexCoord = ((float)SideIdx / NumSides); - - for (int32 VertIdx = 0; VertIdx < NumRings + 1; VertIdx++) - { - int32 VIx = (NumRings + 1)*SideIdx + VertIdx; - - FVector CurPosition = SphylCenter + ArcRot.TransformPosition(ArcVertices[VertIdx]); - CurPosition = SphylRotMatrix.TransformPosition(CurPosition); - - // Convert the UE4 position to Houdini - Vertices[VIx * 3 + 0] = CurPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[VIx * 3 + 1] = CurPosition.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[VIx * 3 + 2] = CurPosition.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - } - } - - // Add all of the indices to the mesh. - int32 NumIndices = NumSides * NumRings * 6; - TArray Indices; - Indices.SetNum(NumIndices); - - int32 CurIndex = 0; - for (int32 SideIdx = 0; SideIdx < NumSides; SideIdx++) - { - const int32 a0start = (SideIdx + 0) * (NumRings + 1); - const int32 a1start = (SideIdx + 1) * (NumRings + 1); - for (int32 RingIdx = 0; RingIdx < NumRings; RingIdx++) - { - // First Tri (reverse winding) - Indices[CurIndex+0] = a0start + RingIdx + 0; - Indices[CurIndex+2] = a1start + RingIdx + 0; - Indices[CurIndex+1] = a0start + RingIdx + 1; - CurIndex += 3; - // Second Tri (reverse winding) - Indices[CurIndex+0] = a1start + RingIdx + 0; - Indices[CurIndex+2] = a1start + RingIdx + 1; - Indices[CurIndex+1] = a0start + RingIdx + 1; - CurIndex += 3; - } - } - - // - // Create the Sphyl Mesh in houdini - // - HAPI_NodeId SphylNodeId = -1; - FString SphylName = TEXT("Sphyl") + FString::FromInt(ColliderIndex); - if(!CreateInputNodeForCollider(SphylNodeId, InParentNodeID, ColliderIndex, SphylName, Vertices, Indices)) - return false; - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_box - HAPI_ParmInfo ParmInfo; - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_capsule") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Connect the box to the group - FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphylNodeId, 0); - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForConvex( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FKConvexElem& ConvexCollider) -{ - TArray Vertices; - TArray Indices; - -#if PHYSICS_INTERFACE_PHYSX - if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) -#elif WITH_CHAOS - //if (ConvexCollider.GetChaosConvexMesh().IsValid()) - if (ConvexCollider.IndexData.Num() > 0 && ConvexCollider.IndexData.Num() % 3 == 0) -#else - if(false) -#endif - { - // Get the convex colliders vertices and indices from the mesh - TArray VertexBuffer; - TArray IndexBuffer; - ConvexCollider.AddCachedSolidConvexGeom(VertexBuffer, IndexBuffer, FColor::White); - - Vertices.SetNum(VertexBuffer.Num() * 3); - int32 CurIndex = 0; - for (auto& CurVert : VertexBuffer) - { - Vertices[CurIndex * 3 + 0] = CurVert.Position.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 1] = CurVert.Position.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 2] = CurVert.Position.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - CurIndex++; - } - - Indices.SetNum(IndexBuffer.Num()); - for (int Idx = 0; (Idx + 2) < IndexBuffer.Num(); Idx += 3) - { - // Reverse winding - Indices[Idx + 0] = Indices[Idx + 0]; - Indices[Idx + 2] = Indices[Idx + 1]; - Indices[Idx + 1] = Indices[Idx + 2]; - } - } - else - { - int32 NumVert = ConvexCollider.VertexData.Num(); - Vertices.SetNum(NumVert * 3); - //Indices.SetNum(NumVert); - int32 CurIndex = 0; - for (auto& CurVert : ConvexCollider.VertexData) - { - Vertices[CurIndex * 3 + 0] = CurVert.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 1] = CurVert.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; - Vertices[CurIndex * 3 + 2] = CurVert.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; - - /* - // TODO: Get proper polygons... - Indices[CurIndex] = CurIndex; - */ - CurIndex++; - } - - // TODO: Get Proper polygons - for (int32 Idx = 0; Idx + 2 < NumVert; Idx++) - { - Indices.Add(Idx + 0); - Indices.Add(Idx + 1); - Indices.Add(Idx + 2); - } - - /* - for (int32 Idx = 0; Idx + 3 < NumVert; Idx+= 4) - { - Indices.Add(Idx + 0); - Indices.Add(Idx + 1); - Indices.Add(Idx + 2); - - Indices.Add(Idx + 2); - Indices.Add(Idx + 1); - Indices.Add(Idx + 3); - } - */ - } - - // - // Create the Convex Mesh in houdini - // - HAPI_NodeId ConvexNodeId = -1; - FString ConvexName = TEXT("Convex") + FString::FromInt(ColliderIndex); - if (!CreateInputNodeForCollider(ConvexNodeId, InParentNodeID, ColliderIndex, ConvexName, Vertices, Indices)) - return false; - - //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); - //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), ColliderNodeId, &CookOptions); - - // Create a group node - FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); - HAPI_NodeId GroupNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "groupcreate", GroupNodeName, false, &GroupNodeId), false); - - // Set its group name param to collision_geo_simple_ucx - HAPI_ParmInfo ParmInfo; - HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); - const char * GroupNameStr = ""; - { - FString LODGroup = TEXT("collision_geo_simple_ucx") + FString::FromInt(ColliderIndex); - GroupNameStr = TCHAR_TO_UTF8(*LODGroup); - } - FHoudiniApi::SetParmStringValue( - FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); - - // Create a convex hull (shrinkwrap::2.0) node to fix the lack of proper indices - HAPI_NodeId ConvexHullNodeId = -1; - FString ConvexHullName = TEXT("ConvexHull") + FString::FromInt(ColliderIndex); - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "shrinkwrap::2.0", ConvexHullName, false, &ConvexHullNodeId), false); - - if (ConvexHullNodeId > 0) - { - // Connect the collider to the convex hull - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), ConvexHullNodeId, 0, ConvexNodeId, 0), false); - - // Connect the convex hull to the group - HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexHullNodeId, 0), false); - } - else - { - // Connect the collider to the group - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( - FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexNodeId, 0), false); - - } - - OutNodeId = GroupNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateInputNodeForCollider( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FString& ColliderName, - const TArray& ColliderVertices, - const TArray& ColliderIndices) -{ - // Create a new input node for the collider - const char * ColliderNameStr = TCHAR_TO_UTF8(*ColliderName); - - // Create the node in this input object's OBJ node - HAPI_NodeId ColliderNodeId = -1; - HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( - InParentNodeID, "null", ColliderNameStr, false, &ColliderNodeId), false); - - // Create a part - HAPI_PartInfo Part; - FHoudiniApi::PartInfo_Init(&Part); - Part.id = 0; - Part.nameSH = 0; - Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; - Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; - Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; - Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; - Part.vertexCount = ColliderIndices.Num(); - Part.faceCount = ColliderIndices.Num() / 3; - Part.pointCount = ColliderVertices.Num() / 3; - Part.type = HAPI_PARTTYPE_MESH; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( - FHoudiniEngine::Get().GetSession(), ColliderNodeId, 0, &Part), false); - - // Create point attribute info. - HAPI_AttributeInfo AttributeInfoPoint; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); - AttributeInfoPoint.count = ColliderVertices.Num() / 3; - AttributeInfoPoint.tupleSize = 3; - AttributeInfoPoint.exists = true; - AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; - AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; - - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); - - // Upload the positions - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, - ColliderVertices.GetData(), 0, AttributeInfoPoint.count), false); - - // Upload the indices - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, ColliderIndices.GetData(), 0, ColliderIndices.Num()), false); - - // Generate the array of face counts. - TArray ColldierFaceCounts; - ColldierFaceCounts.Init(3, Part.faceCount); - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( - FHoudiniEngine::Get().GetSession(), - ColliderNodeId, 0, ColldierFaceCounts.GetData(), 0, ColldierFaceCounts.Num()), false); - - // Commit the geo. - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), ColliderNodeId), false); - - OutNodeId = ColliderNodeId; - - return true; -} - -bool -FUnrealMeshTranslator::CreateHoudiniMeshAttributes( - const int32 & NodeId, - const int32 & PartId, - const int32 & Count, - const TArray & TriangleMaterials, - const TMap> & ScalarMaterialParameters, - const TMap> & VectorMaterialParameters, - const TMap> & TextureMaterialParameters) -{ - if (NodeId < 0) - return false; - - bool bSuccess = true; - - // Create attribute for materials. - HAPI_AttributeInfo AttributeInfoMaterial; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); - AttributeInfoMaterial.tupleSize = 1; - AttributeInfoMaterial.count = Count; - AttributeInfoMaterial.exists = true; - AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; - - // Create the new attribute - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial, - (const char **)TriangleMaterials.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - - // Add scalar material parameter attributes - for (auto & Pair : ScalarMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 1; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - // Create the new attribute - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - Pair.Value.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - // Add vector material parameters - for (auto & Pair : VectorMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 4; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - Pair.Value.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - // Add texture material parameter attributes - for (auto & Pair : TextureMaterialParameters) - { - FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; - const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); - - // Create attribute for material parameter. - HAPI_AttributeInfo AttributeInfoMaterialParameter; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); - AttributeInfoMaterialParameter.tupleSize = 1; - AttributeInfoMaterialParameter.count = Count; - AttributeInfoMaterialParameter.exists = true; - AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; - - if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) - { - // Replace null strings by empty strings to prevent crashes when setting the attribute. - char* EmptyString = nullptr; - TArray StringData = Pair.Value; - for (auto& CurValue : StringData) - { - if (CurValue != nullptr) - continue; - - if (!EmptyString) - { - // Allocate the empty string the first time it is needed. This is free'd along with - // the other strings in the arrays in TextureMaterialParameters by calls to DeleteFaceMaterialArray - EmptyString = FHoudiniEngineUtils::ExtractRawString(FString(TEXT(""))); - } - CurValue = EmptyString; - } - - // The New attribute has been successfully created, set its value - if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, - (const char **)StringData.GetData(), PartId, TriangleMaterials.Num())) - { - bSuccess = false; - } - } - } - - return bSuccess; -} - -/* -bool -FUnrealMeshTranslator::AddLevelPathAttributeToMesh( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const FString& LevelPath, - const int32& Count) -{ - if (NodeId == -1 || LevelPath.IsEmpty() || Count <= 0) - return false; - - // Get name of attribute used for level path - std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; - - // Marshall in level path. - HAPI_AttributeInfo AttributeInfoLevelPath; - FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); - AttributeInfoLevelPath.count = Count; - AttributeInfoLevelPath.tupleSize = 1; - AttributeInfoLevelPath.exists = true; - AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; - AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; - AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; - - HAPI_Result Result = FHoudiniApi::AddAttribute( - FHoudiniEngine::Get().GetSession(), NodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); - - if (HAPI_RESULT_SUCCESS == Result) - { - // Convert the FString to a cont char * array - std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); - const char* LevelPathCStrRaw = LevelPathCStr.c_str(); - TArray PrimitiveAttrs; - PrimitiveAttrs.AddUninitialized(Count); - for (int32 Idx = 0; Idx < Count; ++Idx) - { - PrimitiveAttrs[Idx] = LevelPathCStrRaw; - } - - // Set the attribute's string data - Result = FHoudiniApi::SetAttributeStringData( - FHoudiniEngine::Get().GetSession(), - NodeId, PartId, - MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, - PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); - } - - if (Result != HAPI_RESULT_SUCCESS) - { - // Failed to create the attribute - HOUDINI_LOG_WARNING( - TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), - *FHoudiniEngineUtils::GetErrorDescription()); - } - - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealMeshTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "RawMesh.h" +#include "MeshDescription.h" +#include "MeshDescriptionOperations.h" +#include "Engine/StaticMesh.h" +#include "PhysicsEngine/BodySetup.h" +#include "Engine/StaticMeshSocket.h" +#include "Components/StaticMeshComponent.h" +#include "Materials/Material.h" +#include "Materials/MaterialInterface.h" +#include "MeshAttributes.h" +#include "StaticMeshAttributes.h" + +#if WITH_EDITOR + #include "EditorFramework/AssetImportData.h" +#endif + +bool +FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( + UStaticMesh* StaticMesh, + HAPI_NodeId& InputNodeId, + const FString& InputNodeName, + UStaticMeshComponent* StaticMeshComponent /* = nullptr */, + const bool& ExportAllLODs /* = false */, + const bool& ExportSockets /* = false */, + const bool& ExportColliders /* = false */) +{ + // If we don't have a static mesh there's nothing to do. + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Node ID for the newly created node + HAPI_NodeId NewNodeId = -1; + + // Export sockets if there are some + bool DoExportSockets = ExportSockets && (StaticMesh->Sockets.Num() > 0); + + // Export LODs if there are some + bool DoExportLODs = ExportAllLODs && (StaticMesh->GetNumLODs() > 1); + + // Export colliders if there are some + bool DoExportColliders = ExportColliders && StaticMesh->GetBodySetup() != nullptr; + if (DoExportColliders) + { + if (!StaticMesh->GetBodySetup()) + { + DoExportColliders = false; + } + else + { + if (StaticMesh->GetBodySetup()->AggGeom.GetElementCount() <= 0) + DoExportColliders = false; + } + } + + // We need to use a merge node if we export lods OR sockets + bool UseMergeNode = DoExportLODs || DoExportSockets || DoExportColliders; + if (UseMergeNode) + { + // TODO: + // What if OutInputNodeId already exists? + // Delete previous merge?/input? + + // Create a merge SOP asset. This will be our "InputNodeId" + // as all the different LOD meshes and sockets will be plugged into it + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + -1, TEXT("SOP/merge"), InputNodeName, true, &NewNodeId), false); + } + else + { + // No LODs/Sockets, we just need a single input node + // If InputNodeId is invalid, we need to create an input node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CreateInputNode( + FHoudiniEngine::Get().GetSession(), &NewNodeId, TCHAR_TO_ANSI(*InputNodeName)), false); + + if (!FHoudiniEngineUtils::HapiCookNode(NewNodeId, nullptr, true)) + return false; + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), NewNodeId, &CookOptions), false); + */ + } + + // Check if we have a valid id for this new input asset. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(NewNodeId)) + return false; + + HAPI_NodeId PreviousInputNodeId = InputNodeId; + + // Update our input NodeId + InputNodeId = NewNodeId; + // Get our parent OBJ NodeID + HAPI_NodeId InputObjectNodeId = FHoudiniEngineUtils::HapiGetParentNodeId(NewNodeId); + + // We have now created a valid new input node, delete the previous one + if (PreviousInputNodeId >= 0) + { + // Get the parent OBJ node ID before deleting! + HAPI_NodeId PreviousInputOBJNode = FHoudiniEngineUtils::HapiGetParentNodeId(PreviousInputNodeId); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputNodeId)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input node for %s."), *InputNodeName); + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::DeleteNode( + FHoudiniEngine::Get().GetSession(), PreviousInputOBJNode)) + { + HOUDINI_LOG_WARNING(TEXT("Failed to cleanup the previous input OBJ node for %s."), *InputNodeName); + } + } + + // TODO: + // Setting for lightmap resolution? + //const uint8 ExportMethod = 0; // Raw mesh + //const uint8 ExportMethod = 1; // Mesh description + const uint8 ExportMethod = 2; // LODResources (render mesh) + //bool bExportViaRawMesh = false; + + int32 NumLODsToExport = DoExportLODs ? StaticMesh->GetNumLODs() : 1; + for (int32 LODIndex = 0; LODIndex < NumLODsToExport; LODIndex++) + { + // Grab the LOD level. + FStaticMeshSourceModel & SrcModel = StaticMesh->GetSourceModel(LODIndex); + + // If we're using a merge node, we need to create a new input null + HAPI_NodeId CurrentLODNodeId = -1; + if (UseMergeNode) + { + // Create a new input node for the current LOD + const char * LODName = ""; + { + FString LOD = TEXT("lod") + FString::FromInt(LODIndex); + LODName = TCHAR_TO_UTF8(*LOD); + } + + // Create the node in this input object's OBJ node + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InputObjectNodeId, TEXT("null"), LODName, false, &CurrentLODNodeId), false); + } + else + { + // No merge node, just use the input node we created before + CurrentLODNodeId = NewNodeId; + } + + // Either export the current LOD Mesh by using RawMEsh or MeshDescription (legacy) + FMeshDescription* MeshDesc = nullptr; + // if (!bExportViaRawMesh) + if (ExportMethod == 1) + { + // This will either fetch the mesh description that is cached on the SrcModel + // or load it from bulk data / DDC once + if (SrcModel.MeshDescription.IsValid()) + { + MeshDesc = SrcModel.MeshDescription.Get(); + } + else + { + const double StartTime = FPlatformTime::Seconds(); + MeshDesc = StaticMesh->GetMeshDescription(LODIndex); + HOUDINI_LOG_MESSAGE(TEXT("StaticMesh->GetMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + } + + bool bMeshSuccess = false; + if (ExportMethod == 1 && MeshDesc) + { + // Convert the Mesh using FMeshDescription + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForMeshDescription( + CurrentLODNodeId, + *MeshDesc, + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForMeshDescription completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + else if (ExportMethod == 2) + { + // Convert the LOD Mesh using FStaticMeshLODResources + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( + CurrentLODNodeId, + StaticMesh->GetLODForExport(LODIndex), + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + else + { + // Convert the LOD Mesh using FRawMesh + const double StartTime = FPlatformTime::Seconds(); + bMeshSuccess = FUnrealMeshTranslator::CreateInputNodeForRawMesh( + CurrentLODNodeId, + SrcModel, + LODIndex, + DoExportLODs, + StaticMesh, + StaticMeshComponent); + HOUDINI_LOG_MESSAGE(TEXT("FUnrealMeshTranslator::CreateInputNodeForRawMesh completed in %.4f seconds"), FPlatformTime::Seconds() - StartTime); + } + + if (!bMeshSuccess) + continue; + + if (UseMergeNode) + { + // Connect the LOD node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, LODIndex, CurrentLODNodeId, 0), false); + } + } + + // next Index for adding nodes to the merge + int32 NextMergeIndex = NumLODsToExport; + if (DoExportColliders) + { + FKAggregateGeom SimpleColliders = StaticMesh->GetBodySetup()->AggGeom; + + // Export BOX colliders + for (auto& CurBox : SimpleColliders.BoxElems) + { + FVector BoxCenter = CurBox.Center; + FVector BoxExtent = FVector(CurBox.X, CurBox.Y, CurBox.Z); + FRotator BoxRotation = CurBox.Rotation; + + HAPI_NodeId BoxNodeId = -1; + if (!CreateInputNodeForBox( + BoxNodeId, InputObjectNodeId, NextMergeIndex, + BoxCenter, BoxExtent, BoxRotation)) + continue; + + if (BoxNodeId < 0) + continue; + + // Connect the Box node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, BoxNodeId, 0), false); + + NextMergeIndex++; + } + + // Export SPHERE colliders + for (auto& CurSphere : SimpleColliders.SphereElems) + { + HAPI_NodeId SphereNodeId = -1; + if (!CreateInputNodeForSphere( + SphereNodeId, InputObjectNodeId, NextMergeIndex, + CurSphere.Center, CurSphere.Radius)) + continue; + + if (SphereNodeId < 0) + continue; + + // Connect the Sphere node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, SphereNodeId, 0), false); + + NextMergeIndex++; + } + + // Export CAPSULE colliders + for (auto& CurSphyl : SimpleColliders.SphylElems) + { + HAPI_NodeId SphylNodeId = -1; + if (!CreateInputNodeForSphyl( + SphylNodeId, InputObjectNodeId, NextMergeIndex, + CurSphyl.Center, CurSphyl.Rotation, CurSphyl.Radius, CurSphyl.Length)) + continue; + + if (SphylNodeId < 0) + continue; + + // Connect the capsule node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, SphylNodeId, 0), false); + + NextMergeIndex++; + } + + // Export CONVEX colliders + for (auto& CurConvex : SimpleColliders.ConvexElems) + { + HAPI_NodeId ConvexNodeId = -1; + if (!CreateInputNodeForConvex( + ConvexNodeId, InputObjectNodeId, NextMergeIndex, CurConvex)) + continue; + + if (ConvexNodeId < 0) + continue; + + // Connect the capsule node to the merge node. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + NewNodeId, NextMergeIndex, ConvexNodeId, 0), false); + + NextMergeIndex++; + } + } + + if (DoExportSockets && StaticMesh->Sockets.Num() > 0) + { + // Create an input node for the mesh sockets + HAPI_NodeId SocketsNodeId = -1; + if (CreateInputNodeForMeshSockets(StaticMesh->Sockets, InputObjectNodeId, SocketsNodeId)) + { + // We can connect the socket node to the merge node's last input. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), NewNodeId, NextMergeIndex, SocketsNodeId, 0), false); + + NextMergeIndex++; + } + else if (SocketsNodeId != -1) + { + // If we failed to properly export the sockets, clean up the created node + FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), SocketsNodeId); + } + } + + // + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForMeshSockets( + const TArray& InMeshSocket, const HAPI_NodeId& InParentNodeId, HAPI_NodeId& OutSocketsNodeId) +{ + int32 NumSockets = InMeshSocket.Num(); + if (NumSockets <= 0) + return false; + + // Create a new input node for the sockets + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeId, TEXT("null"), "sockets", false, &OutSocketsNodeId), false); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.pointCount = NumSockets; + Part.vertexCount = 0; + Part.faceCount = 0; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, &Part), false); + + // Create POS point attribute info. + HAPI_AttributeInfo AttributeInfoPos; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); + AttributeInfoPos.count = NumSockets; + AttributeInfoPos.tupleSize = 3; + AttributeInfoPos.exists = true; + AttributeInfoPos.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos), false); + + // Create Rot point attribute Info + HAPI_AttributeInfo AttributeInfoRot; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); + AttributeInfoRot.count = NumSockets; + AttributeInfoRot.tupleSize = 4; + AttributeInfoRot.exists = true; + AttributeInfoRot.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot), false); + + // Create scale point attribute Info + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = NumSockets; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale), false); + + // Create the name attrib info + HAPI_AttributeInfo AttributeInfoName; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); + AttributeInfoName.count = NumSockets; + AttributeInfoName.tupleSize = 1; + AttributeInfoName.exists = true; + AttributeInfoName.owner = HAPI_ATTROWNER_POINT; + AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName), false); + + // Create the tag attrib info + HAPI_AttributeInfo AttributeInfoTag; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); + AttributeInfoTag.count = NumSockets; + AttributeInfoTag.tupleSize = 1; + AttributeInfoTag.exists = true; + AttributeInfoTag.owner = HAPI_ATTROWNER_POINT; + AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag), false); + + // Extract the sockets transform values + TArray SocketPos; + SocketPos.SetNumZeroed(NumSockets * 3); + TArray SocketRot; + SocketRot.SetNumZeroed(NumSockets * 4); + TArray SocketScale; + SocketScale.SetNumZeroed(NumSockets * 3); + + // raw string array for names and tag, will need to be free before returning + TArray SocketNames; + TArray SocketTags; + + // Lambda for freeing the const char array's memory and returning + auto FreeMemoryReturn = [&SocketNames, &SocketTags](const bool& bReturn) + { + // Frees the memory allocated by ExtractRawString for the names and tags + FHoudiniEngineUtils::FreeRawStringMemory(SocketNames); + FHoudiniEngineUtils::FreeRawStringMemory(SocketTags); + + return bReturn; + }; + + for (int32 Idx = 0; Idx < NumSockets; ++Idx) + { + UStaticMeshSocket* CurrentSocket = InMeshSocket[Idx]; + if (!CurrentSocket || CurrentSocket->IsPendingKill()) + continue; + + // Get the socket's transform and convert it to HapiTransform + FTransform SocketTransform(CurrentSocket->RelativeRotation, CurrentSocket->RelativeLocation, CurrentSocket->RelativeScale); + HAPI_Transform HapiSocketTransform; + FHoudiniApi::Transform_Init(&HapiSocketTransform); + FHoudiniEngineUtils::TranslateUnrealTransform(SocketTransform, HapiSocketTransform); + + // Fill the attribute values + SocketPos[3 * Idx + 0] = HapiSocketTransform.position[0]; + SocketPos[3 * Idx + 1] = HapiSocketTransform.position[1]; + SocketPos[3 * Idx + 2] = HapiSocketTransform.position[2]; + + SocketRot[4 * Idx + 0] = HapiSocketTransform.rotationQuaternion[0]; + SocketRot[4 * Idx + 1] = HapiSocketTransform.rotationQuaternion[1]; + SocketRot[4 * Idx + 2] = HapiSocketTransform.rotationQuaternion[2]; + SocketRot[4 * Idx + 3] = HapiSocketTransform.rotationQuaternion[3]; + + SocketScale[3 * Idx + 0] = HapiSocketTransform.scale[0]; + SocketScale[3 * Idx + 1] = HapiSocketTransform.scale[1]; + SocketScale[3 * Idx + 2] = HapiSocketTransform.scale[2]; + + FString CurrentSocketName; + if (!CurrentSocket->SocketName.IsNone()) + CurrentSocketName = CurrentSocket->SocketName.ToString(); + else + CurrentSocketName = TEXT("Socket") + FString::FromInt(Idx); + SocketNames.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocketName)); + + if (!CurrentSocket->Tag.IsEmpty()) + SocketTags.Add(FHoudiniEngineUtils::ExtractRawString(CurrentSocket->Tag)); + else + SocketTags.Add(""); + } + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPos, + SocketPos.GetData(), 0, AttributeInfoPos.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_ROTATION, &AttributeInfoRot, + SocketRot.GetData(), 0, AttributeInfoRot.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_SCALE, &AttributeInfoScale, + SocketScale.GetData(), 0, AttributeInfoScale.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_NAME, &AttributeInfoName, + SocketNames.GetData(), 0, AttributeInfoName.count), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_UNREAL_ATTRIB_MESH_SOCKET_TAG, &AttributeInfoTag, + SocketTags.GetData(), 0, AttributeInfoTag.count), + FreeMemoryReturn(false)); + + // We will also create the socket_details attributes + for (int32 Idx = 0; Idx < NumSockets; ++Idx) + { + // Build the current socket's prefix + FString SocketAttrPrefix = TEXT(HAPI_UNREAL_ATTRIB_MESH_SOCKET_PREFIX) + FString::FromInt(Idx); + + // Create mesh_socketX_pos attribute info. + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPos); + AttributeInfoPos.count = 1; + AttributeInfoPos.tupleSize = 3; + AttributeInfoPos.exists = true; + AttributeInfoPos.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoPos.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPos.originalOwner = HAPI_ATTROWNER_INVALID; + + FString PosAttr = SocketAttrPrefix + TEXT("_pos"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*PosAttr), &AttributeInfoPos, + &(SocketPos[3 * Idx]), 0, AttributeInfoPos.count), + FreeMemoryReturn(false)); + + // Create mesh_socketX_rot point attribute Info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRot); + AttributeInfoRot.count = 1; + AttributeInfoRot.tupleSize = 4; + AttributeInfoRot.exists = true; + AttributeInfoRot.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoRot.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRot.originalOwner = HAPI_ATTROWNER_INVALID; + + FString RotAttr = SocketAttrPrefix + TEXT("_rot"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*RotAttr), &AttributeInfoRot, + &(SocketRot[4 * Idx]), 0, AttributeInfoRot.count), + FreeMemoryReturn(false)); + + // Create mesh_socketX_scale point attribute Info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = 1; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + FString ScaleAttr = SocketAttrPrefix + TEXT("_scale"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*ScaleAttr), &AttributeInfoScale, + &(SocketScale[3 * Idx]), 0, AttributeInfoScale.count), + FreeMemoryReturn(false)); + + // Create the mesh_socketX_name attrib info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoName); + AttributeInfoName.count = 1; + AttributeInfoName.tupleSize = 1; + AttributeInfoName.exists = true; + AttributeInfoName.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoName.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoName.originalOwner = HAPI_ATTROWNER_INVALID; + + FString NameAttr = SocketAttrPrefix + TEXT("_name"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*NameAttr), &AttributeInfoName, + &(SocketNames[Idx]), 0, AttributeInfoName.count), + FreeMemoryReturn(false)); + + // Create the mesh_socketX_tag attrib info + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTag); + AttributeInfoTag.count = 1; + AttributeInfoTag.tupleSize = 1; + AttributeInfoTag.exists = true; + AttributeInfoTag.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoTag.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoTag.originalOwner = HAPI_ATTROWNER_INVALID; + + FString TagAttr = SocketAttrPrefix + TEXT("_tag"); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag), + FreeMemoryReturn(false)); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, TCHAR_TO_ANSI(*TagAttr), &AttributeInfoTag, + &(SocketTags[Idx]), 0, AttributeInfoTag.count), + FreeMemoryReturn(false)); + } + + // Now add the sockets group + const char * SocketGroupStr = "socket_imported"; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr), + FreeMemoryReturn(false)); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, NumSockets); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + OutSocketsNodeId, 0, HAPI_GROUPTYPE_POINT, SocketGroupStr, GroupArray.GetData(), 0, NumSockets), + FreeMemoryReturn(false)); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), OutSocketsNodeId), + FreeMemoryReturn(false)); + + return FreeMemoryReturn(true); +} + +bool +FUnrealMeshTranslator::CreateInputNodeForRawMesh( + const HAPI_NodeId& NodeId, + const FStaticMeshSourceModel& SourceModel, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent ) +{ + // Convert the Mesh using FRawMesh + FRawMesh RawMesh; + SourceModel.LoadRawMesh(RawMesh); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = RawMesh.WedgeIndices.Num(); + Part.faceCount = RawMesh.WedgeIndices.Num() / 3; + Part.pointCount = RawMesh.VertexPositions.Num(); + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = RawMesh.VertexPositions.Num(); + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Grab the build scale + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.VertexPositions.Num() > 3) + { + TArray StaticMeshVertices; + StaticMeshVertices.SetNumZeroed(RawMesh.VertexPositions.Num() * 3); + for (int32 VertexIdx = 0; VertexIdx < RawMesh.VertexPositions.Num(); ++VertexIdx) + { + // Convert Unreal to Houdini + const FVector & PositionVector = RawMesh.VertexPositions[VertexIdx]; + StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; + StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; + StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + for (int32 MeshTexCoordIdx = 0; MeshTexCoordIdx < MAX_STATIC_TEXCOORDS; MeshTexCoordIdx++) + { + int32 StaticMeshUVCount = RawMesh.WedgeTexCoords[MeshTexCoordIdx].Num(); + if (StaticMeshUVCount > 0) + { + const TArray & RawMeshUVs = RawMesh.WedgeTexCoords[MeshTexCoordIdx]; + TArray StaticMeshUVs; + StaticMeshUVs.Reserve(StaticMeshUVCount); + + // Transfer UV data. + for (int32 UVIdx = 0; UVIdx < StaticMeshUVCount; UVIdx++) + StaticMeshUVs.Emplace(RawMeshUVs[UVIdx].X, 1.0 - RawMeshUVs[UVIdx].Y, 0); + + // Convert Unreal to Houdini + // We need to re-index UVs for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + // We do not touch wedge 0 of this triangle, Swap 2 and 3 to reverse the winding order. + StaticMeshUVs.SwapMemory(WedgeIdx + 1, WedgeIdx + 2); + } + + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (MeshTexCoordIdx > 0) + UVAttributeName += FString::Printf(TEXT("%d"), MeshTexCoordIdx + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = StaticMeshUVCount; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, (const float *)StaticMeshUVs.GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentZ.Num() > 0) + { + TArray ChangedNormals(RawMesh.WedgeTangentZ); + + // We need to re-index normals for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentZ1 = ChangedNormals[WedgeIdx + 1]; + FVector TangentZ2 = ChangedNormals[WedgeIdx + 2]; + + ChangedNormals[WedgeIdx + 1] = TangentZ2; + ChangedNormals[WedgeIdx + 2] = TangentZ1; + } + + // We also need to swap the vector's Y and Z components + for (int32 WedgeIdx = 0; WedgeIdx + 2 < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedNormals[WedgeIdx].Y, ChangedNormals[WedgeIdx].Z); + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedNormals.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, (const float *)ChangedNormals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentX.Num() > 0) + { + TArray ChangedTangentU(RawMesh.WedgeTangentX); + + // We need to re-index tangents for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentU1 = ChangedTangentU[WedgeIdx + 1]; + FVector TangentU2 = ChangedTangentU[WedgeIdx + 2]; + + ChangedTangentU[WedgeIdx + 1] = TangentU2; + ChangedTangentU[WedgeIdx + 2] = TangentU1; + } + + // We also need to swap the vector's Y and Z components + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedTangentU[WedgeIdx].Y, ChangedTangentU[WedgeIdx].Z); + + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedTangentU.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + (const float *)ChangedTangentU.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeTangentY.Num() > 0) + { + TArray ChangedTangentV(RawMesh.WedgeTangentY); + // We need to re-index normals for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + FVector TangentV1 = ChangedTangentV[WedgeIdx + 1]; + FVector TangentV2 = ChangedTangentV[WedgeIdx + 2]; + + ChangedTangentV[WedgeIdx + 1] = TangentV2; + ChangedTangentV[WedgeIdx + 2] = TangentV1; + } + + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx++) + Swap(ChangedTangentV[WedgeIdx].Y, ChangedTangentV[WedgeIdx].Z); + + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedTangentV.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + (const float *)ChangedTangentV.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + { + // If we have instance override vertex colors on the StaticMeshComponent, + // we first need to propagate them to our copy of the RawMesh Vert Colors + TArray ChangedColors; + FStaticMeshRenderData* SMRenderData = StaticMesh->GetRenderData(); + + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && + SMRenderData && + SMRenderData->LODResources.IsValidIndex(InLODIndex)) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshLODResources& RenderModel = SMRenderData->LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) + { + // Use the wedge map if it is available as it is lossless. + int32 NumWedges = RawMesh.WedgeIndices.Num(); + if (RenderModel.WedgeMap.Num() == NumWedges) + { + int32 NumExistingColors = RawMesh.WedgeColors.Num(); + if (NumExistingColors < NumWedges) + { + RawMesh.WedgeColors.AddUninitialized(NumWedges - NumExistingColors); + } + + // Replace mesh colors with override colors + for (int32 i = 0; i < NumWedges; i++) + { + FColor WedgeColor = FColor::White; + int32 Index = RenderModel.WedgeMap[i]; + if (Index != INDEX_NONE) + { + WedgeColor = ColorVertexBuffer.VertexColor(Index); + } + RawMesh.WedgeColors[i] = WedgeColor; + } + } + } + } + + // See if we have colors to upload. + if (RawMesh.WedgeColors.Num() > 0) + { + ChangedColors.SetNumUninitialized(RawMesh.WedgeColors.Num()); + + // Convert Unreal to Houdini + // We need to re-index colors for wedges we swapped (due to winding differences). + for (int32 WedgeIdx = 0; WedgeIdx < RawMesh.WedgeIndices.Num(); WedgeIdx += 3) + { + ChangedColors[WedgeIdx + 0] = RawMesh.WedgeColors[WedgeIdx + 0].ReinterpretAsLinear(); + ChangedColors[WedgeIdx + 1] = RawMesh.WedgeColors[WedgeIdx + 2].ReinterpretAsLinear(); + ChangedColors[WedgeIdx + 2] = RawMesh.WedgeColors[WedgeIdx + 1].ReinterpretAsLinear(); + } + } + + if (ChangedColors.Num() > 0) + { + // Extract the RGB colors + TArray ColorValues; + ColorValues.SetNum(ChangedColors.Num() * 3); + for (int32 colorIndex = 0; colorIndex < ChangedColors.Num(); colorIndex++) + { + ColorValues[colorIndex * 3] = ChangedColors[colorIndex].R; + ColorValues[colorIndex * 3 + 1] = ChangedColors[colorIndex].G; + ColorValues[colorIndex * 3 + 2] = ChangedColors[colorIndex].B; + } + + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = ChangedColors.Num(); + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + ColorValues.GetData(), 0, AttributeInfoVertex.count), false); + + // Create the attribute for Alpha + TArray AlphaValues; + AlphaValues.SetNum(ChangedColors.Num()); + for (int32 alphaIndex = 0; alphaIndex < ChangedColors.Num(); alphaIndex++) + AlphaValues[alphaIndex] = ChangedColors[alphaIndex].A; + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.count = AlphaValues.Num(); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + AlphaValues.GetData(), 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // INDICES (VertexList) + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.WedgeIndices.Num() > 0) + { + TArray StaticMeshIndices; + StaticMeshIndices.SetNumUninitialized(RawMesh.WedgeIndices.Num()); + + // Convert Unreal to Houdini + for (int32 IndexIdx = 0; IndexIdx < RawMesh.WedgeIndices.Num(); IndexIdx += 3) + { + // Swap indices to fix winding order. + StaticMeshIndices[IndexIdx + 0] = RawMesh.WedgeIndices[IndexIdx + 0]; + StaticMeshIndices[IndexIdx + 1] = RawMesh.WedgeIndices[IndexIdx + 2]; + StaticMeshIndices[IndexIdx + 2] = RawMesh.WedgeIndices[IndexIdx + 1]; + } + + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, StaticMeshIndices.GetData(), 0, StaticMeshIndices.Num()), false); + + // We need to generate array of face counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, StaticMeshFaceCounts.GetData(), 0, StaticMeshFaceCounts.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE MATERIALS + //--------------------------------------------------------------------------------------------------------------------- + // Marshall face material indices. + if (RawMesh.FaceMaterialIndices.Num() > 0) + { + // Create an array of Material Interfaces + TArray MaterialInterfaces; + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()) + { + // StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); + + int32 NumMeshBasedMaterials = StaticMeshComponent->GetNumMaterials(); + TArray MeshBasedMaterialInterfaces; + MeshBasedMaterialInterfaces.SetNumUninitialized(NumMeshBasedMaterials); + for (int32 i = 0; i < NumMeshBasedMaterials; i++) + MeshBasedMaterialInterfaces[i] = StaticMeshComponent->GetMaterial(i); + + int32 NumSections = StaticMesh->GetNumSections(InLODIndex); + MaterialInterfaces.SetNumUninitialized(NumSections); + for (int32 SectionIndex = 0; SectionIndex < NumSections; SectionIndex++) + { + FMeshSectionInfo Info = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex); + check(Info.MaterialIndex < NumMeshBasedMaterials); + MaterialInterfaces[SectionIndex] = MeshBasedMaterialInterfaces[Info.MaterialIndex]; + } + } + else + { + // Query the Static mesh's materials + for (int32 MatIdx = 0; MatIdx < StaticMesh->GetStaticMaterials().Num(); MatIdx++) + { + MaterialInterfaces.Add(StaticMesh->GetMaterial(MatIdx)); + } + + // Try to fix up inconsistencies between the RawMesh / StaticMesh material indexes + // by using the meshes sections... + // TODO: Fix me properly! + // Proper fix would be to export the meshes via the FStaticMeshLODResources obtained + // by GetLODForExport(), and then export the mesh by sections. + FStaticMeshRenderData* SMRenderData = StaticMesh->GetRenderData(); + if (SMRenderData && SMRenderData->LODResources.IsValidIndex(InLODIndex)) + { + TMap MapOfMaterials; + FStaticMeshLODResources& LODResources = SMRenderData->LODResources[InLODIndex]; + for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++) + { + // Get the material for each element at the current lod index + int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; + if (!MapOfMaterials.Contains(MaterialIndex)) + { + MapOfMaterials.Add(MaterialIndex, StaticMesh->GetMaterial(MaterialIndex)); + } + } + + if (MapOfMaterials.Num() > 0) + { + // Sort the output material in the correct order (by material index) + MapOfMaterials.KeySort([](int32 A, int32 B) { return A < B; }); + + // Set the value in the correct order + // Do not reduce the array of materials, this could cause crahses in some weird cases.. + if (MapOfMaterials.Num() > MaterialInterfaces.Num()) + MaterialInterfaces.SetNumZeroed(MapOfMaterials.Num()); + + int32 MaterialIndex = 0; + for (auto Kvp : MapOfMaterials) + { + MaterialInterfaces[MaterialIndex++] = Kvp.Value; + } + } + } + } + + // List of materials, one for each face. + TArray StaticMeshFaceMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + RawMesh.FaceMaterialIndices.Num(), + StaticMeshFaceMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, RawMesh.FaceMaterialIndices, StaticMeshFaceMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + RawMesh.FaceMaterialIndices.Num(), + StaticMeshFaceMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(StaticMeshFaceMaterials); + + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // FACE SMOOTHING MASKS + //--------------------------------------------------------------------------------------------------------------------- + if (RawMesh.FaceSmoothingMasks.Num() > 0) + { + HAPI_AttributeInfo AttributeInfoSmoothingMasks; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + AttributeInfoSmoothingMasks.count = RawMesh.FaceSmoothingMasks.Num(); + AttributeInfoSmoothingMasks.tupleSize = 1; + AttributeInfoSmoothingMasks.exists = true; + AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + (const int32 *)RawMesh.FaceSmoothingMasks.GetData(), 0, RawMesh.FaceSmoothingMasks.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->GetLightMapResolution() != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->GetLightMapResolution()); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + TArray AllTags; + for (auto& ComponentTag : StaticMeshComponent->ComponentTags) + AllTags.AddUnique(ComponentTag); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + for (auto& ActorTag : ParentActor->Tags) + AllTags.AddUnique(ActorTag); + } + + // Try to create groups for the tags + if (!FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, AllTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups for the Static Mesh Component and Actor tags!")); + + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + /* + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +bool +FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( + const HAPI_NodeId& NodeId, + const FStaticMeshLODResources& LODResources, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent) +{ + // Convert the Mesh using FStaticMeshLODResources + + // Check that the mesh is not empty + if (LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices() == 0) + { + HOUDINI_LOG_ERROR(TEXT("No vertices in mesh!")); + return false; + } + + if (LODResources.Sections.Num() == 0) + { + HOUDINI_LOG_ERROR(TEXT("No triangles in mesh!")); + return false; + } + + // Vertex instance and triangle counts + const uint32 OrigNumVertexInstances = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumVertices(); + const uint32 NumTriangles = LODResources.GetNumTriangles(); + const uint32 NumVertexInstances = NumTriangles * 3; + const uint32 NumSections = LODResources.Sections.Num(); + + // Grab the build scale + const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + // In FStaticMeshLODResources each vertex instances stores its position, even if the positions are not unique (in other + // words, in Houdini terminology, the number of points and vertices are the same. We'll do the same thing that Epic + // does in FBX export: we'll run through all vertex instances and use a hash to determine which instances share a + // position, so that we can a smaller number of points than vertices, and vertices share point positions + TArray UEVertexInstanceIdxToPointIdx; + UEVertexInstanceIdxToPointIdx.Reserve(OrigNumVertexInstances); + + TMap PositionToPointIndexMap; + PositionToPointIndexMap.Reserve(OrigNumVertexInstances); + + TArray StaticMeshVertices; + StaticMeshVertices.Reserve(OrigNumVertexInstances * 3); + for (uint32 VertexInstanceIndex = 0; VertexInstanceIndex < OrigNumVertexInstances; ++VertexInstanceIndex) + { + // Convert Unreal to Houdini + const FVector &PositionVector = LODResources.VertexBuffers.PositionVertexBuffer.VertexPosition(VertexInstanceIndex); + const int32 *FoundPointIndexPtr = PositionToPointIndexMap.Find(PositionVector); + if (!FoundPointIndexPtr) + { + const int32 NewPointIndex = StaticMeshVertices.Add(PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X) / 3; + StaticMeshVertices.Add(PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z); + StaticMeshVertices.Add(PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y); + + PositionToPointIndexMap.Add(PositionVector, NewPointIndex); + UEVertexInstanceIdxToPointIdx.Add(NewPointIndex); + } + else + { + UEVertexInstanceIdxToPointIdx.Add(*FoundPointIndexPtr); + } + } + + StaticMeshVertices.Shrink(); + const uint32 NumVertices = StaticMeshVertices.Num() / 3; + + // Now that we know how many vertices (points), vertex instances (vertices) and triagnles we have, + // we can create the part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumVertexInstances; + Part.faceCount = NumTriangles; + Part.pointCount = NumVertices; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = Part.pointCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + + // Determine which attributes we have + const bool bIsVertexInstanceNormalsValid = true; + const bool bIsVertexInstanceTangentsValid = true; + const bool bIsVertexInstanceBinormalsValid = true; + const bool bIsVertexInstanceColorsValid = LODResources.bHasColorVertexData; + const uint32 NumUVLayers = FMath::Min(LODResources.VertexBuffers.StaticMeshVertexBuffer.GetNumTexCoords(), MAX_STATIC_TEXCOORDS); + const bool bIsVertexInstanceUVsValid = NumUVLayers > 0; + + bool bUseComponentOverrideColors = false; + // Determine if have override colors on the static mesh component, if so prefer to use those + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (ColorVertexBuffer.GetNumVertices() == LODResources.GetNumVertices()) + { + bUseComponentOverrideColors = true; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL INDEX -> MATERIAL INTERFACE + //--------------------------------------------------------------------------------------------------------------------- + TArray MaterialInterfaces; + TArray TriangleMaterialIndices; + + const TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); + + // If the static mesh component is valid, get the materials via the component to account for overrides + const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); + const int32 NumStaticMaterials = StaticMaterials.Num(); + // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, + // then we will set UEDefaultMaterial at the invalid index + int32 UEDefaultMaterialIndex = INDEX_NONE; + UMaterialInterface *UEDefaultMaterial = nullptr; + if (NumStaticMaterials > 0) + { + MaterialInterfaces.Reserve(NumStaticMaterials); + for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) + { + const FStaticMaterial &MaterialInfo = StaticMaterials[MaterialIndex]; + UMaterialInterface *Material = nullptr; + if (bIsStaticMeshComponentValid) + { + Material = StaticMeshComponent->GetMaterial(MaterialIndex); + } + else + { + Material = MaterialInfo.MaterialInterface; + } + // If the Material is NULL or invalid, fallback to the default material + if (!Material || Material->IsPendingKill()) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + UEDefaultMaterialIndex = MaterialIndex; + } + Material = UEDefaultMaterial; + HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); + } + // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); + MaterialInterfaces.Add(Material); + } + + TriangleMaterialIndices.Reserve(NumTriangles); + } + + // If we haven't created UEDefaultMaterial yet, check that all the sections' MaterialIndex + // is valid, if not, create UEDefaultMaterial and add to MaterialInterfaces to get UEDefaultMaterialIndex + if (!UEDefaultMaterial || UEDefaultMaterialIndex == INDEX_NONE) + { + for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + { + // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial + // up to and including MaterialIndex and log a warning + const int32 MaterialIndex = LODResources.Sections[SectionIndex].MaterialIndex; + if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + // Add the UEDefaultMaterial to MaterialInterfaces + UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); + } + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); + } + } + } + + // Determine the final number of materials we have, with default for missing/invalid indices + const int32 NumMaterials = MaterialInterfaces.Num(); + + // Now we deal with vertex instance attributes. + if (NumTriangles > 0) + { + // UV layer array. Each layer has an array of floats, 3 floats per vertex instance + TArray> UVs; + // Normals: 3 floats per vertex instance + TArray Normals; + // Tangents: 3 floats per vertex instance + TArray Tangents; + // Binormals: 3 floats per vertex instance + TArray Binormals; + // RGBColors: 3 floats per vertex instance + TArray RGBColors; + // Alphas: 1 float per vertex instance + TArray Alphas; + + // Initialize the arrays for the attributes that are valid + if (bIsVertexInstanceUVsValid) + { + UVs.SetNum(NumUVLayers); + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); + } + } + + if (bIsVertexInstanceNormalsValid) + { + Normals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceTangentsValid) + { + Tangents.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceBinormalsValid) + { + Binormals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + RGBColors.SetNumUninitialized(NumVertexInstances * 3); + Alphas.SetNumUninitialized(NumVertexInstances); + } + + // Array of vertex (point position) indices per triangle + TArray MeshTriangleVertexIndices; + MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); + // Array of vertex counts per triangle/face + TArray MeshTriangleVertexCounts; + MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); + + int32 TriangleIdx = 0; + int32 HoudiniVertexIdx = 0; + FIndexArrayView TriangleVertexIndices = LODResources.IndexBuffer.GetArrayView(); + for (uint32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) + { + const FStaticMeshSection& Section = LODResources.Sections[SectionIndex]; + for (uint32 SectionTriangleIndex = 0; SectionTriangleIndex < Section.NumTriangles; ++SectionTriangleIndex) + { + MeshTriangleVertexCounts[TriangleIdx] = 3; + for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) + { + // Reverse the winding order for Houdini (but still start at 0) + const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; + const uint32 UEVertexIndex = TriangleVertexIndices[Section.FirstIndex + SectionTriangleIndex * 3 + WindingIdx]; + + // Calculate the index of the first component of a vertex instance's value in an inline float array + // representing vectors (3 float) per vertex instance + const int32 Float3Index = HoudiniVertexIdx * 3; + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + const FVector2D &UV = LODResources.VertexBuffers.StaticMeshVertexBuffer.GetVertexUV(UEVertexIndex, UVLayerIndex); + UVs[UVLayerIndex][Float3Index + 0] = UV.X; + UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; + UVs[UVLayerIndex][Float3Index + 2] = 0; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + const FVector &Normal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentZ(UEVertexIndex); + Normals[Float3Index + 0] = Normal.X; + Normals[Float3Index + 1] = Normal.Z; + Normals[Float3Index + 2] = Normal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + const FVector &Tangent = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentX(UEVertexIndex); + Tangents[Float3Index + 0] = Tangent.X; + Tangents[Float3Index + 1] = Tangent.Z; + Tangents[Float3Index + 2] = Tangent.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + // In order to calculate the binormal we also need the tangent and normal + if (bIsVertexInstanceBinormalsValid) + { + FVector Binormal = LODResources.VertexBuffers.StaticMeshVertexBuffer.VertexTangentY(UEVertexIndex); + Binormals[Float3Index + 0] = Binormal.X; + Binormals[Float3Index + 1] = Binormal.Z; + Binormals[Float3Index + 2] = Binormal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + FVector4 Color = FLinearColor::White; + if (bUseComponentOverrideColors) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + Color = ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); + } + else + { + Color = LODResources.VertexBuffers.ColorVertexBuffer.VertexColor(UEVertexIndex).ReinterpretAsLinear(); + } + RGBColors[Float3Index + 0] = Color[0]; + RGBColors[Float3Index + 1] = Color[1]; + RGBColors[Float3Index + 2] = Color[2]; + Alphas[HoudiniVertexIdx] = Color[3]; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + if (UEVertexInstanceIdxToPointIdx.IsValidIndex(UEVertexIndex)) + { + MeshTriangleVertexIndices[HoudiniVertexIdx] = UEVertexInstanceIdxToPointIdx[UEVertexIndex]; + } + + HoudiniVertexIdx++; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + if (MaterialInterfaces.IsValidIndex(Section.MaterialIndex)) + { + TriangleMaterialIndices.Add(Section.MaterialIndex); + } + else + { + TriangleMaterialIndices.Add(UEDefaultMaterialIndex); + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, Section.MaterialIndex, *(UEDefaultMaterial->GetPathName())); + } + + TriangleIdx++; + } + } + + // Now transfer valid vertex instance attributes to Houdini vertex attributes + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (uint32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) + { + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (UVLayerIndex > 0) + UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = NumVertexInstances; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, Normals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + Tangents.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceBinormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + Binormals.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + RGBColors.GetData(), 0, AttributeInfoVertex.count), false); + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.count = Alphas.Num(); + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + Alphas.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); + + // Send the array of face vertex counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); + + // Send material assignments to Houdini + if (NumMaterials > 0) + { + // List of materials, one for each face. + TArray TriangleMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); + + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (!bAttributeSuccess) + { + check(0); + return false; + } + } + + // TODO: The render mesh (LODResources) does not have face smoothing information, and the raw mesh triangle order is + // potentially different (see also line 4152 TODO_FBX in Engine\Source\Editor\UnrealEd\Private\Fbx\FbxMainExport.cpp + ////--------------------------------------------------------------------------------------------------------------------- + //// TRIANGLE SMOOTHING MASKS + ////--------------------------------------------------------------------------------------------------------------------- + //TArray TriangleSmoothingMasks; + //TriangleSmoothingMasks.SetNumZeroed(NumTriangles); + //FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); + //if (TriangleSmoothingMasks.Num() > 0) + //{ + // HAPI_AttributeInfo AttributeInfoSmoothingMasks; + // FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + // AttributeInfoSmoothingMasks.tupleSize = 1; + // AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); + // AttributeInfoSmoothingMasks.exists = true; + // AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + // AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + // AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + // FHoudiniEngine::Get().GetSession(), + // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + // HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + // FHoudiniEngine::Get().GetSession(), + // NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + // (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); + //} + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->GetLightMapResolution() != GeneratedLightMapResolution) + { + TArray< int32 > LightMapResolutions; + LightMapResolutions.Add(StaticMesh->GetLightMapResolution()); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + // Try to create groups for the static mesh component's tags + if (StaticMeshComponent->ComponentTags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Try to create groups for the parent Actor's tags + if (ParentActor->Tags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + + /* + // Add the unreal_level_path attribute + FString LevelPath = ActorPath;// FString(); + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +bool +FUnrealMeshTranslator::CreateInputNodeForMeshDescription( + const HAPI_NodeId& NodeId, + const FMeshDescription& MeshDescription, + const int32& InLODIndex, + const bool& bAddLODGroups, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent) +{ + // Convert the Mesh using FMeshDescription + // Get references to the attributes we are interested in + // before sending to Houdini we'll check if each attribute is valid + FStaticMeshConstAttributes MeshDescriptionAttributes(MeshDescription); + + TVertexAttributesConstRef VertexPositions = MeshDescriptionAttributes.GetVertexPositions(); + TVertexInstanceAttributesConstRef VertexInstanceNormals = MeshDescriptionAttributes.GetVertexInstanceNormals(); + TVertexInstanceAttributesConstRef VertexInstanceTangents = MeshDescriptionAttributes.GetVertexInstanceTangents(); + TVertexInstanceAttributesConstRef VertexInstanceBinormalSigns = MeshDescriptionAttributes.GetVertexInstanceBinormalSigns(); + TVertexInstanceAttributesConstRef VertexInstanceColors = MeshDescriptionAttributes.GetVertexInstanceColors(); + TVertexInstanceAttributesConstRef VertexInstanceUVs = MeshDescriptionAttributes.GetVertexInstanceUVs(); + //TPolygonGroupAttributesConstRef PolygonGroupMaterialSlotNames = MeshDescriptionAttributes.GetPolygonGroupMaterialSlotNames(); + + // Get the vertex and triangle arrays + const FVertexArray &MDVertices = MeshDescription.Vertices(); + const FPolygonGroupArray &MDPolygonGroups = MeshDescription.PolygonGroups(); + const FPolygonArray &MDPolygons = MeshDescription.Polygons(); + const FTriangleArray &MDTriangles = MeshDescription.Triangles(); + + // Determine point, vertex and polygon counts + const uint32 NumVertices = MDVertices.Num(); + const uint32 NumTriangles = MDTriangles.Num(); + const uint32 NumVertexInstances = NumTriangles * 3; + + // Some checks: we expect triangulated meshes + if (MeshDescription.VertexInstances().Num() != NumTriangles * 3) + { + HOUDINI_LOG_ERROR(TEXT("Expected a triangulated mesh, but # VertexInstances (%d) != # Triangles * 3 (%d)"), MeshDescription.VertexInstances().Num(), NumTriangles * 3); + return false; + } + + // Determine which attributes we have + const bool bIsVertexPositionsValid = VertexPositions.IsValid(); + const bool bIsVertexInstanceNormalsValid = VertexInstanceNormals.IsValid(); + const bool bIsVertexInstanceTangentsValid = VertexInstanceTangents.IsValid(); + const bool bIsVertexInstanceBinormalSignsValid = VertexInstanceBinormalSigns.IsValid(); + const bool bIsVertexInstanceColorsValid = VertexInstanceColors.IsValid(); + const bool bIsVertexInstanceUVsValid = VertexInstanceUVs.IsValid(); + //const bool bIsPolygonGroupImportedMaterialSlotNamesValid = PolygonGroupMaterialSlotNames.IsValid(); + + // Create part. + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = NumVertexInstances; + Part.faceCount = NumTriangles; + Part.pointCount = NumVertices; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), NodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + //FMemory::Memzero< HAPI_AttributeInfo >( AttributeInfoPoint ); + AttributeInfoPoint.count = Part.pointCount; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Grab the build scale + const FStaticMeshSourceModel &SourceModel = StaticMesh->GetSourceModel(InLODIndex); + FVector BuildScaleVector = SourceModel.BuildSettings.BuildScale3D; + + //--------------------------------------------------------------------------------------------------------------------- + // POSITION (P) + //--------------------------------------------------------------------------------------------------------------------- + // The mesh element arrays are sparse: the max index/ID value can be larger than the number of elements - 1 + // so we have to maintain a lookup of VertexID (UE) to PointIndex (Houdini) + TArray VertexIDToHIndex; + if (bIsVertexPositionsValid && VertexPositions.GetNumElements() >= 3) + { + TArray StaticMeshVertices; + StaticMeshVertices.SetNumUninitialized(NumVertices * 3); + + int32 VertexIdx = 0; + VertexIDToHIndex.Init(INDEX_NONE, MDVertices.GetArraySize()); + + for (const FVertexID& VertexID : MDVertices.GetElementIDs()) + { + // Convert Unreal to Houdini + const FVector &PositionVector = VertexPositions.Get(VertexID); + StaticMeshVertices[VertexIdx * 3 + 0] = PositionVector.X / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.X; + StaticMeshVertices[VertexIdx * 3 + 1] = PositionVector.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Z; + StaticMeshVertices[VertexIdx * 3 + 2] = PositionVector.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION * BuildScaleVector.Y; + + // Record the UE Vertex ID to Houdini Point Index lookup + VertexIDToHIndex[VertexID.GetValue()] = VertexIdx; + VertexIdx++; + } + + // Now that we have raw positions, we can upload them for our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + StaticMeshVertices.GetData(), 0, AttributeInfoPoint.count), false); + } + + bool bUseComponentOverrideColors = false; + FStaticMeshRenderData* SMRenderData = StaticMesh->GetRenderData(); + + // Determine if have override colors on the static mesh component, if so prefer to use those + if (StaticMeshComponent && + StaticMeshComponent->LODData.IsValidIndex(InLODIndex) && + StaticMeshComponent->LODData[InLODIndex].OverrideVertexColors && + SMRenderData && + SMRenderData->LODResources.IsValidIndex(InLODIndex)) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshLODResources& RenderModel = SMRenderData->LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + if (RenderModel.WedgeMap.Num() > 0 && ColorVertexBuffer.GetNumVertices() == RenderModel.GetNumVertices()) + { + // Use the wedge map if it is available as it is lossless. + if (RenderModel.WedgeMap.Num() == NumVertexInstances) + { + bUseComponentOverrideColors = true; + } + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // MATERIAL SLOT -> MATERIAL INTERFACE + //--------------------------------------------------------------------------------------------------------------------- + // In theory the ImportedMaterialSlotName attribute on PolygonGroups should tell us which material slot is used by + // that group, and thus which MaterialIndex we should assign to triangles in that group. Unfortunately we have + // encountered cases where the ImportedMaterialSlotName name attribute does not match any of the MaterialSlotName or + // ImportedMaterialSlotNames in the StaticMesh->StaticMaterials array. Therefore we have no choice but to rely + // on the PolygonGroup order vs Section order to determine the MaterialIndex for a group. We do what Epic does + // when building a static mesh: Sections are created in the same order as iterating over PolygonGroups, but importantly, + // empty PolygonGroups are skipped + + // // Get material slot name to material index + // and the UMaterialInterface array + // TMap MaterialSlotToInterface; + TArray MaterialInterfaces; + TArray TriangleMaterialIndices; + + const TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); + + // If the static mesh component is valid, get the materials via the component to account for overrides + const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); + const int32 NumStaticMaterials = StaticMaterials.Num(); + // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, + // then we will set UEDefaultMaterial at the invalid index + int32 UEDefaultMaterialIndex = INDEX_NONE; + UMaterialInterface *UEDefaultMaterial = nullptr; + if (NumStaticMaterials > 0) + { + MaterialInterfaces.Reserve(NumStaticMaterials); + for (int32 MaterialIndex = 0; MaterialIndex < NumStaticMaterials; ++MaterialIndex) + { + const FStaticMaterial &MaterialInfo = StaticMaterials[MaterialIndex]; + UMaterialInterface *Material = nullptr; + if (bIsStaticMeshComponentValid) + { + Material = StaticMeshComponent->GetMaterial(MaterialIndex); + } + else + { + Material = MaterialInfo.MaterialInterface; + } + // If the Material is NULL or invalid, fallback to the default material + if (!Material || Material->IsPendingKill()) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + UEDefaultMaterialIndex = MaterialIndex; + } + Material = UEDefaultMaterial; + HOUDINI_LOG_WARNING(TEXT("Material Index %d (slot %s) has an invalid material, falling back to default: %s"), MaterialIndex, *(MaterialInfo.MaterialSlotName.ToString()), *(UEDefaultMaterial->GetPathName())); + } + // MaterialSlotToInterface.Add(MaterialInfo.ImportedMaterialSlotName, MaterialIndex); + MaterialInterfaces.Add(Material); + } + + TriangleMaterialIndices.Reserve(NumTriangles); + } + // SectionIndex: Looking at Epic's StaticMesh build code, Sections are created in the same + // order as iterating over PolygonGroups, but skipping empty PolygonGroups + TMap PolygonGroupToMaterialIndex; + PolygonGroupToMaterialIndex.Reserve(MeshDescription.PolygonGroups().Num()); + int32 SectionIndex = 0; + for (const FPolygonGroupID &PolygonGroupID : MDPolygonGroups.GetElementIDs()) + { + // Skip empty polygon groups + if (MeshDescription.GetNumPolygonGroupPolygons(PolygonGroupID) == 0) + { + continue; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + // // Get the material index for the material slot for this polygon group + //int32 MaterialIndex = INDEX_NONE; + //if (bIsPolygonGroupImportedMaterialSlotNamesValid) + //{ + // const FName &MaterialSlotName = PolygonGroupMaterialSlotNames.Get(PolygonGroupID); + // const int32 *MaterialIndexPtr = MaterialSlotToInterface.Find(MaterialSlotName); + // if (MaterialIndexPtr != nullptr) + // { + // MaterialIndex = *MaterialIndexPtr; + // } + //} + + // Get the material for the LOD and section via the section info map + if (StaticMesh->GetNumSections(InLODIndex) <= SectionIndex) + { + HOUDINI_LOG_ERROR(TEXT("Found more non-empty polygon groups in the mesh description for LOD %d than sections in the static mesh..."), InLODIndex); + return false; + } + + // If the MaterialIndex referenced by this Section is out of range, fill MaterialInterfaces with UEDefaultMaterial + // up to and including MaterialIndex and log a warning + int32 MaterialIndex = StaticMesh->GetSectionInfoMap().Get(InLODIndex, SectionIndex).MaterialIndex; + if (!MaterialInterfaces.IsValidIndex(MaterialIndex)) + { + if (!UEDefaultMaterial) + { + UEDefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + // Add the UEDefaultMaterial to MaterialInterfaces + UEDefaultMaterialIndex = MaterialInterfaces.Add(UEDefaultMaterial); + } + HOUDINI_LOG_WARNING(TEXT("Section Index %d references an invalid Material Index %d, falling back to default material: %s"), SectionIndex, MaterialIndex, *(UEDefaultMaterial->GetPathName())); + MaterialIndex = UEDefaultMaterialIndex; + } + + PolygonGroupToMaterialIndex.Add(PolygonGroupID, MaterialIndex); + SectionIndex++; + } + + // Determine the final number of materials we have, with defaults for missing/invalid indices + const int32 NumMaterials = MaterialInterfaces.Num(); + + // Now we deal with vertex instance attributes. + // // First we must also record a UE VertexInstanceID to Houdini Vertex Index lookup, + // // and then get and convert all valid and supported vertex instance attributes from UE + // TArray VertexInstanceIDToHIndex; + + if (NumTriangles > 0) + { + // UV layer array. Each layer has an array of floats, 3 floats per vertex instance + TArray> UVs; + const int32 NumUVLayers = bIsVertexInstanceUVsValid ? FMath::Min(VertexInstanceUVs.GetNumIndices(), (int32)MAX_STATIC_TEXCOORDS) : 0; + // Normals: 3 floats per vertex instance + TArray Normals; + // Tangents: 3 floats per vertex instance + TArray Tangents; + // Binormals: 3 floats per vertex instance + TArray Binormals; + // RGBColors: 3 floats per vertex instance + TArray RGBColors; + // Alphas: 1 float per vertex instance + TArray Alphas; + + // Initialize the arrays for the attributes that are valid + if (bIsVertexInstanceUVsValid) + { + UVs.SetNum(NumUVLayers); + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + UVs[UVLayerIndex].SetNumUninitialized(NumVertexInstances * 3); + } + } + + if (bIsVertexInstanceNormalsValid) + { + Normals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceTangentsValid) + { + Tangents.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bIsVertexInstanceBinormalSignsValid) + { + Binormals.SetNumUninitialized(NumVertexInstances * 3); + } + + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + RGBColors.SetNumUninitialized(NumVertexInstances * 3); + Alphas.SetNumUninitialized(NumVertexInstances); + } + + // Array of material index per triangle/face + TArray MeshTriangleVertexIndices; + MeshTriangleVertexIndices.SetNumUninitialized(NumVertexInstances); + // Array of vertex counts per triangle/face + TArray MeshTriangleVertexCounts; + MeshTriangleVertexCounts.SetNumUninitialized(NumTriangles); + + int32 TriangleIdx = 0; + int32 VertexInstanceIdx = 0; + // VertexInstanceIDToHIndex.Init(-1, MDVertexInstances.GetArraySize()); + + for (const FPolygonID &PolygonID : MDPolygons.GetElementIDs()) + { + for (const FTriangleID &TriangleID : MeshDescription.GetPolygonTriangleIDs(PolygonID)) + { + MeshTriangleVertexCounts[TriangleIdx] = 3; + for (int32 TriangleVertexIndex = 0; TriangleVertexIndex < 3; ++TriangleVertexIndex) + { + // Reverse the winding order for Houdini (but still start at 0) + const int32 WindingIdx = (3 - TriangleVertexIndex) % 3; + const FVertexInstanceID &VertexInstanceID = MeshDescription.GetTriangleVertexInstance(TriangleID, WindingIdx); + + // // UE Vertex Instance ID to Houdini Vertex Index look up + // VertexInstanceIDToHIndex[VertexInstanceID.GetValue()] = VertexInstanceIdx; + + // Calculate the index of the first component of a vertex instance's value in an inline float array + // representing vectors (3 float) per vertex instance + const int32 Float3Index = VertexInstanceIdx * 3; + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; ++UVLayerIndex) + { + const FVector2D &UV = VertexInstanceUVs.Get(VertexInstanceID, UVLayerIndex); + UVs[UVLayerIndex][Float3Index + 0] = UV.X; + UVs[UVLayerIndex][Float3Index + 1] = 1.0f - UV.Y; + UVs[UVLayerIndex][Float3Index + 2] = 0; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + const FVector &Normal = VertexInstanceNormals.Get(VertexInstanceID); + Normals[Float3Index + 0] = Normal.X; + Normals[Float3Index + 1] = Normal.Z; + Normals[Float3Index + 2] = Normal.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + const FVector &Tangent = VertexInstanceTangents.Get(VertexInstanceID); + Tangents[Float3Index + 0] = Tangent.X; + Tangents[Float3Index + 1] = Tangent.Z; + Tangents[Float3Index + 2] = Tangent.Y; + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + // In order to calculate the binormal we also need the tangent and normal + if (bIsVertexInstanceBinormalSignsValid && bIsVertexInstanceTangentsValid && bIsVertexInstanceNormalsValid) + { + const float &BinormalSign = VertexInstanceBinormalSigns.Get(VertexInstanceID); + FVector Binormal = FVector::CrossProduct( + FVector(Tangents[Float3Index + 0], Tangents[Float3Index + 1], Tangents[Float3Index + 2]), + FVector(Normals[Float3Index + 0], Normals[Float3Index + 1], Normals[Float3Index + 2]) + ) * BinormalSign; + Binormals[Float3Index + 0] = Binormal.X; + Binormals[Float3Index + 1] = Binormal.Y; + Binormals[Float3Index + 2] = Binormal.Z; + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + FVector4 Color = FLinearColor::White; + if (bUseComponentOverrideColors && SMRenderData) + { + FStaticMeshComponentLODInfo& ComponentLODInfo = StaticMeshComponent->LODData[InLODIndex]; + FStaticMeshLODResources& RenderModel = SMRenderData->LODResources[InLODIndex]; + FColorVertexBuffer& ColorVertexBuffer = *ComponentLODInfo.OverrideVertexColors; + + int32 Index = RenderModel.WedgeMap[VertexInstanceIdx]; + if (Index != INDEX_NONE) + { + Color = ColorVertexBuffer.VertexColor(Index).ReinterpretAsLinear(); + } + } + else + { + Color = VertexInstanceColors.Get(VertexInstanceID); + } + RGBColors[Float3Index + 0] = Color[0]; + RGBColors[Float3Index + 1] = Color[1]; + RGBColors[Float3Index + 2] = Color[2]; + Alphas[VertexInstanceIdx] = Color[3]; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + const FVertexID& VertexID = MeshDescription.GetVertexInstanceVertex(VertexInstanceID); + const int32 UEVertexIdx = VertexID.GetValue(); + if (VertexIDToHIndex.IsValidIndex(UEVertexIdx)) + { + MeshTriangleVertexIndices[VertexInstanceIdx] = VertexIDToHIndex[UEVertexIdx]; + } + + VertexInstanceIdx++; + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE MATERIAL ASSIGNMENT + //--------------------------------------------------------------------------------------------------------------------- + const FPolygonGroupID &PolygonGroupID = MeshDescription.GetPolygonPolygonGroup(PolygonID); + const int32 MaterialIndex = PolygonGroupToMaterialIndex.FindChecked(PolygonGroupID); + TriangleMaterialIndices.Add(MaterialIndex); + + TriangleIdx++; + } + } + + // Now transfer valid vertex instance attributes to Houdini vertex attributes + + //--------------------------------------------------------------------------------------------------------------------- + // UVS (uvX) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceUVsValid) + { + for (int32 UVLayerIndex = 0; UVLayerIndex < NumUVLayers; UVLayerIndex++) + { + // Construct the attribute name for this UV index. + FString UVAttributeName = HAPI_UNREAL_ATTRIB_UV; + if (UVLayerIndex > 0) + UVAttributeName += FString::Printf(TEXT("%d"), UVLayerIndex + 1); + + // Create attribute for UVs + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.count = NumVertexInstances; + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_ANSI(*UVAttributeName), + &AttributeInfoVertex, UVs[UVLayerIndex].GetData(), + 0, AttributeInfoVertex.count), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // NORMALS (N) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceNormalsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Normals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_NORMAL, + &AttributeInfoVertex, Normals.GetData(), + 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TANGENT (tangentu) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceTangentsValid) + { + // Create attribute for tangentu. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Tangents.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTU, &AttributeInfoVertex, + Tangents.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // BINORMAL (tangentv) + //--------------------------------------------------------------------------------------------------------------------- + if (bIsVertexInstanceBinormalSignsValid) + { + // Create attribute for normals. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = Binormals.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_TANGENTV, &AttributeInfoVertex, + Binormals.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // COLORS (Cd) + //--------------------------------------------------------------------------------------------------------------------- + if (bUseComponentOverrideColors || bIsVertexInstanceColorsValid) + { + // Create attribute for colors. + HAPI_AttributeInfo AttributeInfoVertex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + + AttributeInfoVertex.tupleSize = 3; + AttributeInfoVertex.count = RGBColors.Num() / AttributeInfoVertex.tupleSize; + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_COLOR, &AttributeInfoVertex, + RGBColors.GetData(), 0, AttributeInfoVertex.count), false); + + FHoudiniApi::AttributeInfo_Init(&AttributeInfoVertex); + AttributeInfoVertex.tupleSize = 1; + AttributeInfoVertex.count = Alphas.Num(); + AttributeInfoVertex.exists = true; + AttributeInfoVertex.owner = HAPI_ATTROWNER_VERTEX; + AttributeInfoVertex.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoVertex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_ALPHA, &AttributeInfoVertex, + Alphas.GetData(), 0, AttributeInfoVertex.count), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE/FACE VERTEX INDICES + //--------------------------------------------------------------------------------------------------------------------- + // We can now set vertex list. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexIndices.GetData(), 0, MeshTriangleVertexIndices.Num()), false); + + // Send the array of face vertex counts. + TArray< int32 > StaticMeshFaceCounts; + StaticMeshFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, MeshTriangleVertexCounts.GetData(), 0, MeshTriangleVertexCounts.Num()), false); + + // Send material assignments to Houdini + if (NumMaterials > 0) + { + // List of materials, one for each face. + TArray TriangleMaterials; + + //Lists of material parameters + TMap> ScalarMaterialParameters; + TMap> VectorMaterialParameters; + TMap> TextureMaterialParameters; + + bool bAttributeSuccess = false; + bool bAddMaterialParametersAsAttributes = false; + + if (bAddMaterialParametersAsAttributes) + { + // Create attributes for the material and all its parameters + // Get material attribute data, and all material parameters data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials, + ScalarMaterialParameters, VectorMaterialParameters, TextureMaterialParameters); + + // Create attribute for materials and all attributes for material parameters + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + else + { + // Create attributes only for the materials + // Only get the material attribute data + FUnrealMeshTranslator::CreateFaceMaterialArray( + MaterialInterfaces, TriangleMaterialIndices, TriangleMaterials); + + // Create attribute for materials + bAttributeSuccess = FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + NodeId, + 0, + TriangleMaterials.Num(), + TriangleMaterials, + ScalarMaterialParameters, + VectorMaterialParameters, + TextureMaterialParameters); + } + + // Delete material names. + FUnrealMeshTranslator::DeleteFaceMaterialArray(TriangleMaterials); + + // Delete texture material parameter names + for (auto & Pair : TextureMaterialParameters) + { + FUnrealMeshTranslator::DeleteFaceMaterialArray(Pair.Value); + } + + if (bAttributeSuccess) + { + check(0); + return false; + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // TRIANGLE SMOOTHING MASKS + //--------------------------------------------------------------------------------------------------------------------- + TArray TriangleSmoothingMasks; + TriangleSmoothingMasks.SetNumZeroed(NumTriangles); + FStaticMeshOperations::ConvertHardEdgesToSmoothGroup(MeshDescription, TriangleSmoothingMasks); + if (TriangleSmoothingMasks.Num() > 0) + { + HAPI_AttributeInfo AttributeInfoSmoothingMasks; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoSmoothingMasks); + + AttributeInfoSmoothingMasks.tupleSize = 1; + AttributeInfoSmoothingMasks.count = TriangleSmoothingMasks.Num(); + AttributeInfoSmoothingMasks.exists = true; + AttributeInfoSmoothingMasks.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoSmoothingMasks.storage = HAPI_STORAGETYPE_INT; + AttributeInfoSmoothingMasks.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_FACE_SMOOTHING_MASK, &AttributeInfoSmoothingMasks, + (const int32 *)TriangleSmoothingMasks.GetData(), 0, TriangleSmoothingMasks.Num()), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // LIGHTMAP RESOLUTION + //--------------------------------------------------------------------------------------------------------------------- + + // TODO: + // Fetch default lightmap res from settings... + int32 GeneratedLightMapResolution = 32; + if (StaticMesh->GetLightMapResolution() != GeneratedLightMapResolution) + { + TArray LightMapResolutions; + LightMapResolutions.Add(StaticMesh->GetLightMapResolution()); + + HAPI_AttributeInfo AttributeInfoLightMapResolution; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLightMapResolution); + AttributeInfoLightMapResolution.count = LightMapResolutions.Num(); + AttributeInfoLightMapResolution.tupleSize = 1; + AttributeInfoLightMapResolution.exists = true; + AttributeInfoLightMapResolution.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLightMapResolution.storage = HAPI_STORAGETYPE_INT; + AttributeInfoLightMapResolution.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_LIGHTMAP_RESOLUTION, &AttributeInfoLightMapResolution, + (const int32 *)LightMapResolutions.GetData(), 0, LightMapResolutions.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT MESH NAME + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + const FString MeshAssetPath = StaticMesh->GetPathName(); + std::string MeshAssetPathCStr = TCHAR_TO_ANSI(*MeshAssetPath); + const char* MeshAssetPathRaw = MeshAssetPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = MeshAssetPathRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_MESH_NAME, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + + //--------------------------------------------------------------------------------------------------------------------- + // INPUT SOURCE FILE + //--------------------------------------------------------------------------------------------------------------------- + { + // Create primitive attribute with mesh asset path + FString Filename; + if (UAssetImportData* ImportData = StaticMesh->AssetImportData) + { + for (const auto& SourceFile : ImportData->SourceData.SourceFiles) + { + Filename = UAssetImportData::ResolveImportFilename(SourceFile.RelativeFilename, ImportData->GetOutermost()); + break; + } + } + + if (!Filename.IsEmpty()) + { + std::string FilenameCStr = TCHAR_TO_ANSI(*Filename); + const char* FilenameCStrRaw = FilenameCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Part.faceCount); + for (int32 Ix = 0; Ix < Part.faceCount; ++Ix) + { + PrimitiveAttrs[Ix] = FilenameCStrRaw; + } + + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + AttributeInfo.count = Part.faceCount; + AttributeInfo.tupleSize = 1; + AttributeInfo.exists = true; + AttributeInfo.owner = HAPI_ATTROWNER_PRIM; + AttributeInfo.storage = HAPI_STORAGETYPE_STRING; + AttributeInfo.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo), false); + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_UNREAL_ATTRIB_INPUT_SOURCE_FILE, &AttributeInfo, + PrimitiveAttrs.GetData(), 0, PrimitiveAttrs.Num()), false); + } + } + + /* + // Check if we have vertex attribute data to add + if (StaticMeshComponent && StaticMeshComponent->GetOwner()) + { + if (UHoudiniAttributeDataComponent* DataComponent = StaticMeshComponent->GetOwner()->FindComponentByClass()) + { + bool bSuccess = DataComponent->Upload(NodeId, StaticMeshComponent); + if (!bSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Upload of attribute data for %s failed"), *StaticMeshComponent->GetOwner()->GetName()); + } + } + } + */ + + //--------------------------------------------------------------------------------------------------------------------- + // LOD GROUP AND SCREENSIZE + //--------------------------------------------------------------------------------------------------------------------- + if (bAddLODGroups) + { + // LOD Group + const char * LODGroupStr = ""; + { + FString LODGroup = TEXT("lod") + FString::FromInt(InLODIndex); + LODGroupStr = TCHAR_TO_UTF8(*LODGroup); + } + + // Add a LOD group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddGroup( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr), false); + + // Set GroupMembership + TArray GroupArray; + GroupArray.Init(1, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetGroupMembership( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, HAPI_GROUPTYPE_PRIM, LODGroupStr, + GroupArray.GetData(), 0, Part.faceCount), false); + + if (!StaticMesh->bAutoComputeLODScreenSize) + { + // Add the lodX_screensize attribute + FString LODAttributeName = + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_PREFIX) + FString::FromInt(InLODIndex) + TEXT(HAPI_UNREAL_ATTRIB_LOD_SCREENSIZE_POSTFIX); + + // Create lodX_screensize detail attribute info. + HAPI_AttributeInfo AttributeInfoLODScreenSize; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLODScreenSize); + AttributeInfoLODScreenSize.count = 1; + AttributeInfoLODScreenSize.tupleSize = 1; + AttributeInfoLODScreenSize.exists = true; + AttributeInfoLODScreenSize.owner = HAPI_ATTROWNER_DETAIL; + AttributeInfoLODScreenSize.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoLODScreenSize.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, 0, TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize), false); + + // TODO: FIX? + // Get the actual screensize instead of the src model default? + float lodscreensize = SourceModel.ScreenSize.Default; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), NodeId, 0, + TCHAR_TO_UTF8(*LODAttributeName), &AttributeInfoLODScreenSize, + &lodscreensize, 0, 1), false); + } + } + + //--------------------------------------------------------------------------------------------------------------------- + // COMPONENT AND ACTOR TAGS + //--------------------------------------------------------------------------------------------------------------------- + if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + { + // Try to create groups for the static mesh component's tags + if (StaticMeshComponent->ComponentTags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, StaticMeshComponent->ComponentTags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); + + AActor* ParentActor = StaticMeshComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + // Try to create groups for the parent Actor's tags + if (ParentActor->Tags.Num() > 0 + && !FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, ParentActor->Tags)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's parent actor tags!")); + + // Add the unreal_actor_path attribute + FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); + + // Add the unreal_level_path attribute + FHoudiniEngineUtils::AddLevelPathAttribute(NodeId, 0, ParentActor->GetLevel(), Part.faceCount); + + /* + FString LevelPath = FString(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + if (ULevel* Level = ParentActor->GetLevel()) + { + LevelPath = Level->GetPathName(); + + // We just want the path up to the first point + int32 DotIndex; + if (LevelPath.FindChar('.', DotIndex)) + LevelPath.LeftInline(DotIndex, false); + + AddLevelPathAttributeToMesh(NodeId, 0, LevelPath, Part.faceCount); + } + } + */ + } + } + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), NodeId), false); + + return true; +} + + +void +FUnrealMeshTranslator::CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials) +{ + // We need to create list of unique materials. + TArray UniqueMaterialList; + + UMaterialInterface * MaterialInterface = nullptr; + char* UniqueName = nullptr; + + UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); + + if (Materials.Num()) + { + // We have materials. + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) + { + UniqueName = nullptr; + MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface) + { + // Null material interface found, add default instead. + UniqueMaterialList.Add(DefaultMaterialName); + + // No need to collect material parameters on the default material + continue; + } + + // We found a material, get its name and material parameters + FString FullMaterialName = MaterialInterface->GetPathName(); + UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); + UniqueMaterialList.Add(UniqueName); + } + } + else + { + // We do not have any materials, add default. + UniqueMaterialList.Add(DefaultMaterialName); + } + + // TODO: Needs to be improved! + // We shouldnt be testing for each face, but only for each unique facematerial value... + for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) + { + int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; + if (UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) + { + OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); + } + else + { + OutStaticMeshFaceMaterials.Add(DefaultMaterialName); + } + } +} + + +void +FUnrealMeshTranslator::CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials, + TMap> & OutScalarMaterialParameters, + TMap> & OutVectorMaterialParameters, + TMap> & OutTextureMaterialParameters) +{ + // We need to create list of unique materials. + TArray UniqueMaterialList; + + UMaterialInterface * MaterialInterface = nullptr; + char* UniqueName = nullptr; + + UMaterialInterface * DefaultMaterialInterface = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + char* DefaultMaterialName = FHoudiniEngineUtils::ExtractRawString(DefaultMaterialInterface->GetPathName()); + + // Initialize material parameter arrays + TMap> ScalarParams; + TMap> VectorParams; + TMap> TextureParams; + + if (Materials.Num()) + { + // We have materials. + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); MaterialIdx++) + { + UniqueName = nullptr; + MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface) + { + // Null material interface found, add default instead. + UniqueMaterialList.Add(DefaultMaterialName); + + // No need to collect material parameters on the default material + continue; + } + + // We found a material, get its name and material parameters + FString FullMaterialName = MaterialInterface->GetPathName(); + UniqueName = FHoudiniEngineUtils::ExtractRawString(FullMaterialName); + UniqueMaterialList.Add(UniqueName); + + // Collect all scalar parameters in all materials + { + TArray MaterialScalarParamInfos; + TArray MaterialScalarParamGuids; + MaterialInterface->GetAllScalarParameterInfo(MaterialScalarParamInfos, MaterialScalarParamGuids); + + for (auto & CurScalarParam : MaterialScalarParamInfos) + { + FString CurScalarParamName = CurScalarParam.Name.ToString(); + float CurScalarVal; + MaterialInterface->GetScalarParameterValue(CurScalarParam, CurScalarVal); + if (!ScalarParams.Contains(CurScalarParamName)) + { + TArray CurArray; + CurArray.SetNumUninitialized(Materials.Num()); + // Initialize the array with the Min float value + for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) + CurArray[ArrIdx] = FLT_MIN; + + ScalarParams.Add(CurScalarParamName, CurArray); + OutScalarMaterialParameters.Add(CurScalarParamName); + } + + ScalarParams[CurScalarParamName][MaterialIdx] = CurScalarVal; + } + } + + // Collect all vector parameters in all materials + { + TArray MaterialVectorParamInfos; + TArray MaterialVectorParamGuids; + MaterialInterface->GetAllVectorParameterInfo(MaterialVectorParamInfos, MaterialVectorParamGuids); + + for (auto & CurVectorParam : MaterialVectorParamInfos) + { + FString CurVectorParamName = CurVectorParam.Name.ToString(); + FLinearColor CurVectorValue; + MaterialInterface->GetVectorParameterValue(CurVectorParam, CurVectorValue); + if (!VectorParams.Contains(CurVectorParamName)) + { + TArray CurArray; + CurArray.SetNumUninitialized(Materials.Num()); + FLinearColor MinColor(FLT_MIN, FLT_MIN, FLT_MIN, FLT_MIN); + for (int32 ArrIdx = 0; ArrIdx < CurArray.Num(); ++ArrIdx) + CurArray[ArrIdx] = MinColor; + + VectorParams.Add(CurVectorParamName, CurArray); + OutVectorMaterialParameters.Add(CurVectorParamName); + } + + VectorParams[CurVectorParamName][MaterialIdx] = CurVectorValue; + } + } + + // Collect all texture parameters in all materials + { + TArray MaterialTextureParamInfos; + TArray MaterialTextureParamGuids; + MaterialInterface->GetAllTextureParameterInfo(MaterialTextureParamInfos, MaterialTextureParamGuids); + + for (auto & CurTextureParam : MaterialTextureParamInfos) + { + FString CurTextureParamName = CurTextureParam.Name.ToString(); + UTexture * CurTexture = nullptr; + MaterialInterface->GetTextureParameterValue(CurTextureParam, CurTexture); + + if (!CurTexture || CurTexture->IsPendingKill()) + continue; + + FString TexturePath = CurTexture->GetPathName(); + if (!TextureParams.Contains(CurTextureParamName)) + { + TArray CurArray; + CurArray.SetNumZeroed(Materials.Num()); + + TextureParams.Add(CurTextureParamName, CurArray); + OutTextureMaterialParameters.Add(CurTextureParamName); + } + + char * TexturePathRawStr = UniqueName = FHoudiniEngineUtils::ExtractRawString(TexturePath); + TextureParams[CurTextureParamName][MaterialIdx] = TexturePathRawStr; + } + } + + } + } + else + { + // We do not have any materials, add default. + UniqueMaterialList.Add(DefaultMaterialName); + } + + // TODO: Needs to be improved! + // We shouldnt be testing for each face, but only for each unique facematerial value... + for (int32 FaceIdx = 0; FaceIdx < FaceMaterialIndices.Num(); ++FaceIdx) + { + int32 FaceMaterialIdx = FaceMaterialIndices[FaceIdx]; + if(UniqueMaterialList.IsValidIndex(FaceMaterialIdx)) + { + OutStaticMeshFaceMaterials.Add(UniqueMaterialList[FaceMaterialIdx]); + + for (auto & Pair : ScalarParams) + { + OutScalarMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); + } + + for (auto & Pair : VectorParams) + { + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].R); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].G); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].B); + OutVectorMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx].A); + } + + for (auto & Pair : TextureParams) + { + OutTextureMaterialParameters[Pair.Key].Add(Pair.Value[FaceMaterialIdx]); + } + } + else + { + OutStaticMeshFaceMaterials.Add(DefaultMaterialName); + } + } +} + + +void +FUnrealMeshTranslator::DeleteFaceMaterialArray(TArray& OutStaticMeshFaceMaterials) +{ + // Clean up the memory allocated by CreateFaceMaterialArray() + TSet UniqueMaterials(OutStaticMeshFaceMaterials); + for (TSet::TIterator Iter = UniqueMaterials.CreateIterator(); Iter; ++Iter) + { + char* MaterialName = *Iter; + FMemory::Free(MaterialName); + } + + OutStaticMeshFaceMaterials.Empty(); +} + +bool +FUnrealMeshTranslator::CreateInputNodeForBox( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& BoxCenter, + const FVector& BoxExtent, + const FRotator& BoxRotation) +{ + // Create a new input node for the box collider + FString BoxName = TEXT("box") + FString::FromInt(ColliderIndex); + + // Create the node in this input object's OBJ node + HAPI_NodeId BoxNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("box"), BoxName, false, &BoxNodeId), false); + + // Set the box parameters + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 0, BoxExtent.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 1, BoxExtent.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "size", 2, BoxExtent.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 0, BoxCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 1, BoxCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "t", 2, BoxCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 0, BoxRotation.Roll); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 2, BoxRotation.Pitch); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), BoxNodeId, "r", 1, BoxRotation.Yaw); + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), BoxNodeId, &CookOptions); + */ + if (!FHoudiniEngineUtils::HapiCookNode(BoxNodeId, nullptr, true)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_box") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, BoxNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForSphere( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphereCenter, + const float& SphereRadius) +{ + // Create a new input node for the sphere collider + const char * SphereName = ""; + { + FString SPH = TEXT("Sphere") + FString::FromInt(ColliderIndex); + SphereName = TCHAR_TO_UTF8(*SPH); + } + + // Create the node in this input object's OBJ node + HAPI_NodeId SphereNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "sphere", SphereName, false, &SphereNodeId), false); + + // Set the box parameters + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 1, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "rad", 2, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 0, SphereCenter.X / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 1, SphereCenter.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION); + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "t", 2, SphereCenter.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION); + + FHoudiniApi::SetParmIntValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "type", 0, 1); + /* + FHoudiniApi::SetParmFloatValue( + FHoudiniEngine::Get().GetSession(), SphereNodeId, "scale", 0, SphereRadius / HAPI_UNREAL_SCALE_FACTOR_POSITION); + */ + + /* + HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), SphereNodeId, &CookOptions); + */ + if (!FHoudiniEngineUtils::HapiCookNode(SphereNodeId, nullptr, true)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN(FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_sphere") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphereNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForSphyl( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphylCenter, + const FRotator& SphylRotation, + const float& SphylRadius, + const float& SphereLength) +{ + // + // Get the Sphyl's vertices and indices + // (code drived from FKSphylElem::GetElemSolid) + // + + // TODO: + // Rotation? + + const int32 NumSides = 6; + const int32 NumRings = (NumSides / 2) + 1; + + // The first/last arc are on top of each other. + const int32 NumVerts = (NumSides + 1) * (NumRings + 1); + + // Calculate the vertices for one arc + TArray ArcVertices; + ArcVertices.SetNum(NumRings + 1); + for (int32 RingIdx = 0; RingIdx < NumRings + 1; RingIdx++) + { + float Angle; + float ZOffset; + if (RingIdx <= NumSides / 4) + { + Angle = ((float)RingIdx / (NumRings - 1)) * PI; + ZOffset = 0.5 * SphereLength; + } + else + { + Angle = ((float)(RingIdx - 1) / (NumRings - 1)) * PI; + ZOffset = -0.5 * SphereLength; + } + + // Note- unit sphere, so position always has mag of one. We can just use it for normal! + FVector SpherePos; + SpherePos.X = 0.0f; + SpherePos.Y = SphylRadius * FMath::Sin(Angle); + SpherePos.Z = SphylRadius * FMath::Cos(Angle); + + ArcVertices[RingIdx] = SpherePos + FVector(0, 0, ZOffset); + } + + // Get the transform matrix for the rotation + FRotationMatrix SphylRotMatrix(SphylRotation); + + // Get the Sphyl's vertices by rotating the arc NumSides+1 times. + TArray Vertices; + Vertices.SetNum(NumVerts * 3); + for (int32 SideIdx = 0; SideIdx < NumSides + 1; SideIdx++) + { + const FRotator ArcRotator(0, 360.f * ((float)SideIdx / NumSides), 0); + const FRotationMatrix ArcRot(ArcRotator); + const float XTexCoord = ((float)SideIdx / NumSides); + + for (int32 VertIdx = 0; VertIdx < NumRings + 1; VertIdx++) + { + int32 VIx = (NumRings + 1)*SideIdx + VertIdx; + + FVector CurPosition = SphylCenter + ArcRot.TransformPosition(ArcVertices[VertIdx]); + CurPosition = SphylRotMatrix.TransformPosition(CurPosition); + + // Convert the UE4 position to Houdini + Vertices[VIx * 3 + 0] = CurPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[VIx * 3 + 1] = CurPosition.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[VIx * 3 + 2] = CurPosition.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + } + + // Add all of the indices to the mesh. + int32 NumIndices = NumSides * NumRings * 6; + TArray Indices; + Indices.SetNum(NumIndices); + + int32 CurIndex = 0; + for (int32 SideIdx = 0; SideIdx < NumSides; SideIdx++) + { + const int32 a0start = (SideIdx + 0) * (NumRings + 1); + const int32 a1start = (SideIdx + 1) * (NumRings + 1); + for (int32 RingIdx = 0; RingIdx < NumRings; RingIdx++) + { + // First Tri (reverse winding) + Indices[CurIndex+0] = a0start + RingIdx + 0; + Indices[CurIndex+2] = a1start + RingIdx + 0; + Indices[CurIndex+1] = a0start + RingIdx + 1; + CurIndex += 3; + // Second Tri (reverse winding) + Indices[CurIndex+0] = a1start + RingIdx + 0; + Indices[CurIndex+2] = a1start + RingIdx + 1; + Indices[CurIndex+1] = a0start + RingIdx + 1; + CurIndex += 3; + } + } + + // + // Create the Sphyl Mesh in houdini + // + HAPI_NodeId SphylNodeId = -1; + FString SphylName = TEXT("Sphyl") + FString::FromInt(ColliderIndex); + if(!CreateInputNodeForCollider(SphylNodeId, InParentNodeID, ColliderIndex, SphylName, Vertices, Indices)) + return false; + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, TEXT("groupcreate"), GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_box + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_capsule") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Connect the box to the group + FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, SphylNodeId, 0); + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForConvex( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FKConvexElem& ConvexCollider) +{ + TArray Vertices; + TArray Indices; + +#if PHYSICS_INTERFACE_PHYSX + if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) +#elif WITH_CHAOS + //if (ConvexCollider.GetChaosConvexMesh().IsValid()) + if (ConvexCollider.IndexData.Num() > 0 && ConvexCollider.IndexData.Num() % 3 == 0) +#else + if(false) +#endif + { + // Get the convex colliders vertices and indices from the mesh + TArray VertexBuffer; + TArray IndexBuffer; + ConvexCollider.AddCachedSolidConvexGeom(VertexBuffer, IndexBuffer, FColor::White); + + Vertices.SetNum(VertexBuffer.Num() * 3); + int32 CurIndex = 0; + for (auto& CurVert : VertexBuffer) + { + Vertices[CurIndex * 3 + 0] = CurVert.Position.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 1] = CurVert.Position.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 2] = CurVert.Position.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + CurIndex++; + } + + Indices.SetNum(IndexBuffer.Num()); + for (int Idx = 0; (Idx + 2) < IndexBuffer.Num(); Idx += 3) + { + // Reverse winding + Indices[Idx + 0] = Indices[Idx + 0]; + Indices[Idx + 2] = Indices[Idx + 1]; + Indices[Idx + 1] = Indices[Idx + 2]; + } + } + else + { + int32 NumVert = ConvexCollider.VertexData.Num(); + Vertices.SetNum(NumVert * 3); + //Indices.SetNum(NumVert); + int32 CurIndex = 0; + for (auto& CurVert : ConvexCollider.VertexData) + { + Vertices[CurIndex * 3 + 0] = CurVert.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 1] = CurVert.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; + Vertices[CurIndex * 3 + 2] = CurVert.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + + /* + // TODO: Get proper polygons... + Indices[CurIndex] = CurIndex; + */ + CurIndex++; + } + + // TODO: Get Proper polygons + for (int32 Idx = 0; Idx + 2 < NumVert; Idx++) + { + Indices.Add(Idx + 0); + Indices.Add(Idx + 1); + Indices.Add(Idx + 2); + } + + /* + for (int32 Idx = 0; Idx + 3 < NumVert; Idx+= 4) + { + Indices.Add(Idx + 0); + Indices.Add(Idx + 1); + Indices.Add(Idx + 2); + + Indices.Add(Idx + 2); + Indices.Add(Idx + 1); + Indices.Add(Idx + 3); + } + */ + } + + // + // Create the Convex Mesh in houdini + // + HAPI_NodeId ConvexNodeId = -1; + FString ConvexName = TEXT("Convex") + FString::FromInt(ColliderIndex); + if (!CreateInputNodeForCollider(ConvexNodeId, InParentNodeID, ColliderIndex, ConvexName, Vertices, Indices)) + return false; + + //HAPI_CookOptions CookOptions = FHoudiniEngine::GetDefaultCookOptions(); + //FHoudiniApi::CookNode(FHoudiniEngine::Get().GetSession(), ColliderNodeId, &CookOptions); + + // Create a group node + FString GroupNodeName = TEXT("group") + FString::FromInt(ColliderIndex); + HAPI_NodeId GroupNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "groupcreate", GroupNodeName, false, &GroupNodeId), false); + + // Set its group name param to collision_geo_simple_ucx + HAPI_ParmInfo ParmInfo; + HAPI_ParmId parmId = FHoudiniEngineUtils::HapiFindParameterByName(GroupNodeId, "groupname", ParmInfo); + const char * GroupNameStr = ""; + { + FString LODGroup = TEXT("collision_geo_simple_ucx") + FString::FromInt(ColliderIndex); + GroupNameStr = TCHAR_TO_UTF8(*LODGroup); + } + FHoudiniApi::SetParmStringValue( + FHoudiniEngine::Get().GetSession(), GroupNodeId, GroupNameStr, parmId, 0); + + // Create a convex hull (shrinkwrap::2.0) node to fix the lack of proper indices + HAPI_NodeId ConvexHullNodeId = -1; + FString ConvexHullName = TEXT("ConvexHull") + FString::FromInt(ColliderIndex); + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "shrinkwrap::2.0", ConvexHullName, false, &ConvexHullNodeId), false); + + if (ConvexHullNodeId > 0) + { + // Connect the collider to the convex hull + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), ConvexHullNodeId, 0, ConvexNodeId, 0), false); + + // Connect the convex hull to the group + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexHullNodeId, 0), false); + } + else + { + // Connect the collider to the group + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), GroupNodeId, 0, ConvexNodeId, 0), false); + + } + + OutNodeId = GroupNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateInputNodeForCollider( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FString& ColliderName, + const TArray& ColliderVertices, + const TArray& ColliderIndices) +{ + // Create a new input node for the collider + const char * ColliderNameStr = TCHAR_TO_UTF8(*ColliderName); + + // Create the node in this input object's OBJ node + HAPI_NodeId ColliderNodeId = -1; + HOUDINI_CHECK_ERROR_RETURN( FHoudiniEngineUtils::CreateNode( + InParentNodeID, "null", ColliderNameStr, false, &ColliderNodeId), false); + + // Create a part + HAPI_PartInfo Part; + FHoudiniApi::PartInfo_Init(&Part); + Part.id = 0; + Part.nameSH = 0; + Part.attributeCounts[HAPI_ATTROWNER_POINT] = 0; + Part.attributeCounts[HAPI_ATTROWNER_PRIM] = 0; + Part.attributeCounts[HAPI_ATTROWNER_VERTEX] = 0; + Part.attributeCounts[HAPI_ATTROWNER_DETAIL] = 0; + Part.vertexCount = ColliderIndices.Num(); + Part.faceCount = ColliderIndices.Num() / 3; + Part.pointCount = ColliderVertices.Num() / 3; + Part.type = HAPI_PARTTYPE_MESH; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetPartInfo( + FHoudiniEngine::Get().GetSession(), ColliderNodeId, 0, &Part), false); + + // Create point attribute info. + HAPI_AttributeInfo AttributeInfoPoint; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoPoint); + AttributeInfoPoint.count = ColliderVertices.Num() / 3; + AttributeInfoPoint.tupleSize = 3; + AttributeInfoPoint.exists = true; + AttributeInfoPoint.owner = HAPI_ATTROWNER_POINT; + AttributeInfoPoint.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoPoint.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint), false); + + // Upload the positions + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, HAPI_UNREAL_ATTRIB_POSITION, &AttributeInfoPoint, + ColliderVertices.GetData(), 0, AttributeInfoPoint.count), false); + + // Upload the indices + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetVertexList( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, ColliderIndices.GetData(), 0, ColliderIndices.Num()), false); + + // Generate the array of face counts. + TArray ColldierFaceCounts; + ColldierFaceCounts.Init(3, Part.faceCount); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetFaceCounts( + FHoudiniEngine::Get().GetSession(), + ColliderNodeId, 0, ColldierFaceCounts.GetData(), 0, ColldierFaceCounts.Num()), false); + + // Commit the geo. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), ColliderNodeId), false); + + OutNodeId = ColliderNodeId; + + return true; +} + +bool +FUnrealMeshTranslator::CreateHoudiniMeshAttributes( + const int32 & NodeId, + const int32 & PartId, + const int32 & Count, + const TArray & TriangleMaterials, + const TMap> & ScalarMaterialParameters, + const TMap> & VectorMaterialParameters, + const TMap> & TextureMaterialParameters) +{ + if (NodeId < 0) + return false; + + bool bSuccess = true; + + // Create attribute for materials. + HAPI_AttributeInfo AttributeInfoMaterial; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterial); + AttributeInfoMaterial.tupleSize = 1; + AttributeInfoMaterial.count = Count; + AttributeInfoMaterial.exists = true; + AttributeInfoMaterial.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterial.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterial.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_UNREAL_ATTRIB_MATERIAL, &AttributeInfoMaterial, + (const char **)TriangleMaterials.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + + // Add scalar material parameter attributes + for (auto & Pair : ScalarMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 1; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + // Create the new attribute + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + Pair.Value.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + // Add vector material parameters + for (auto & Pair : VectorMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 4; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + Pair.Value.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + // Add texture material parameter attributes + for (auto & Pair : TextureMaterialParameters) + { + FString CurMaterialParamAttriName = FString(HAPI_UNREAL_ATTRIB_MATERIAL) + "_parameter_" + Pair.Key; + const char * CurMaterialParamAttriNameRawStr = FHoudiniEngineUtils::ExtractRawString(CurMaterialParamAttriName); + + // Create attribute for material parameter. + HAPI_AttributeInfo AttributeInfoMaterialParameter; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoMaterialParameter); + AttributeInfoMaterialParameter.tupleSize = 1; + AttributeInfoMaterialParameter.count = Count; + AttributeInfoMaterialParameter.exists = true; + AttributeInfoMaterialParameter.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoMaterialParameter.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoMaterialParameter.originalOwner = HAPI_ATTROWNER_INVALID; + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::AddAttribute(FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter)) + { + // Replace null strings by empty strings to prevent crashes when setting the attribute. + char* EmptyString = nullptr; + TArray StringData = Pair.Value; + for (auto& CurValue : StringData) + { + if (CurValue != nullptr) + continue; + + if (!EmptyString) + { + // Allocate the empty string the first time it is needed. This is free'd along with + // the other strings in the arrays in TextureMaterialParameters by calls to DeleteFaceMaterialArray + EmptyString = FHoudiniEngineUtils::ExtractRawString(FString(TEXT(""))); + } + CurValue = EmptyString; + } + + // The New attribute has been successfully created, set its value + if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, CurMaterialParamAttriNameRawStr, &AttributeInfoMaterialParameter, + (const char **)StringData.GetData(), PartId, TriangleMaterials.Num())) + { + bSuccess = false; + } + } + } + + return bSuccess; +} + +/* +bool +FUnrealMeshTranslator::AddLevelPathAttributeToMesh( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const FString& LevelPath, + const int32& Count) +{ + if (NodeId == -1 || LevelPath.IsEmpty() || Count <= 0) + return false; + + // Get name of attribute used for level path + std::string MarshallingAttributeLevelPath = HAPI_UNREAL_ATTRIB_LEVEL_PATH; + + // Marshall in level path. + HAPI_AttributeInfo AttributeInfoLevelPath; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoLevelPath); + AttributeInfoLevelPath.count = Count; + AttributeInfoLevelPath.tupleSize = 1; + AttributeInfoLevelPath.exists = true; + AttributeInfoLevelPath.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoLevelPath.storage = HAPI_STORAGETYPE_STRING; + AttributeInfoLevelPath.originalOwner = HAPI_ATTROWNER_INVALID; + + HAPI_Result Result = FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath); + + if (HAPI_RESULT_SUCCESS == Result) + { + // Convert the FString to a cont char * array + std::string LevelPathCStr = TCHAR_TO_ANSI(*LevelPath); + const char* LevelPathCStrRaw = LevelPathCStr.c_str(); + TArray PrimitiveAttrs; + PrimitiveAttrs.AddUninitialized(Count); + for (int32 Idx = 0; Idx < Count; ++Idx) + { + PrimitiveAttrs[Idx] = LevelPathCStrRaw; + } + + // Set the attribute's string data + Result = FHoudiniApi::SetAttributeStringData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, + MarshallingAttributeLevelPath.c_str(), &AttributeInfoLevelPath, + PrimitiveAttrs.GetData(), 0, AttributeInfoLevelPath.count); + } + + if (Result != HAPI_RESULT_SUCCESS) + { + // Failed to create the attribute + HOUDINI_LOG_WARNING( + TEXT("Failed to upload unreal_level_path attribute for mesh: %s"), + *FHoudiniEngineUtils::GetErrorDescription()); + } + + return true; +} */ \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.h b/Source/HoudiniEngine/Private/UnrealMeshTranslator.h index 571b2e895..b524ea308 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.h @@ -1,176 +1,176 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -class UStaticMesh; -class UStaticMeshComponent; -class UMaterialInterface; -class UStaticMeshSocket; - -struct FStaticMeshSourceModel; -struct FStaticMeshLODResources; -struct FMeshDescription; -struct FKConvexElem; - -struct HOUDINIENGINE_API FUnrealMeshTranslator -{ - public: - - // HAPI : Marshaling, extract geometry and create input asset for it - return true on success - static bool HapiCreateInputNodeForStaticMesh( - UStaticMesh * Mesh, - HAPI_NodeId& InputObjectNodeId, - const FString& InputNodeName, - class UStaticMeshComponent* StaticMeshComponent = nullptr, - const bool& ExportAllLODs = false, - const bool& ExportSockets = false, - const bool& ExportColliders = false); - - // Convert the Mesh using FStaticMeshLODResources - static bool CreateInputNodeForStaticMeshLODResources( - const HAPI_NodeId& NodeId, - const FStaticMeshLODResources& LODResources, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - // Convert the Mesh using FMeshDescription - static bool CreateInputNodeForMeshDescription( - const HAPI_NodeId& NodeId, - const FMeshDescription& MeshDescription, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - // Convert the Mesh using FRawMesh - - static bool CreateInputNodeForRawMesh( - const HAPI_NodeId& NodeId, - const FStaticMeshSourceModel& SourceModel, - const int32& LODIndex, - const bool& DoExportLODs, - UStaticMesh* StaticMesh, - UStaticMeshComponent* StaticMeshComponent); - - static bool CreateInputNodeForBox( - HAPI_NodeId& OutBoxNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& BoxCenter, - const FVector& BoxExtent, - const FRotator& BoxRotation); - - static bool CreateInputNodeForSphere( - HAPI_NodeId& OutSphereNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphereCenter, - const float& SphereRadius); - - static bool CreateInputNodeForSphyl( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FVector& SphylCenter, - const FRotator& SphylRotation, - const float& SphylRadius, - const float& SphereLength); - - static bool CreateInputNodeForConvex( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FKConvexElem& ConvexCollider); - - static bool CreateInputNodeForCollider( - HAPI_NodeId& OutNodeId, - const HAPI_NodeId& InParentNodeID, - const int32& ColliderIndex, - const FString& ColliderName, - const TArray& ColliderVertices, - const TArray& ColliderIndices); - - static bool CreateInputNodeForMeshSockets( - const TArray& InMeshSocket, - const HAPI_NodeId& InParentNodeId, - HAPI_NodeId& OutSocketsNodeId); - - - // Helper function to extract the array of material names used by a given mesh - // This is used for marshalling static mesh's materials. - // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() - static void CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray& OutStaticMeshFaceMaterials); - - // Helper function to extract the array of material names used by a given mesh - // Also extracts all scalar/vector/texture parameter in the materials - // This is used for marshalling static mesh's materials. - // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() - // The texture parameter array also needs to be cleared. - static void CreateFaceMaterialArray( - const TArray& Materials, - const TArray& FaceMaterialIndices, - TArray & OutStaticMeshFaceMaterials, - TMap> & OutScalarMaterialParameters, - TMap> & OutVectorMaterialParameters, - TMap> & OutTextureMaterialParameters); - - // Delete helper array of material names. - // Clean up the memory allocated by CreateFaceMaterialArray() - static void DeleteFaceMaterialArray(TArray & OutStaticMeshFaceMaterials); - - // Create and set mesh material attribute and material (scalar, vector and texture) parameters attributes - static bool CreateHoudiniMeshAttributes( - const int32 & NodeId, - const int32 & PartId, - const int32 & Count, - const TArray & TriangleMaterials, - const TMap> & ScalarMaterialParameters, - const TMap> & VectorMaterialParameters, - const TMap> & TextureMaterialParameters); - - /* - // Creates the unreal_level_path attribute on the input mesh - static bool AddLevelPathAttributeToMesh( - const HAPI_NodeId& NodeId, - const HAPI_PartId& PartId, - const FString& LevelPath, - const int32& Count); - */ - - private: - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +class UStaticMesh; +class UStaticMeshComponent; +class UMaterialInterface; +class UStaticMeshSocket; + +struct FStaticMeshSourceModel; +struct FStaticMeshLODResources; +struct FMeshDescription; +struct FKConvexElem; + +struct HOUDINIENGINE_API FUnrealMeshTranslator +{ + public: + + // HAPI : Marshaling, extract geometry and create input asset for it - return true on success + static bool HapiCreateInputNodeForStaticMesh( + UStaticMesh * Mesh, + HAPI_NodeId& InputObjectNodeId, + const FString& InputNodeName, + class UStaticMeshComponent* StaticMeshComponent = nullptr, + const bool& ExportAllLODs = false, + const bool& ExportSockets = false, + const bool& ExportColliders = false); + + // Convert the Mesh using FStaticMeshLODResources + static bool CreateInputNodeForStaticMeshLODResources( + const HAPI_NodeId& NodeId, + const FStaticMeshLODResources& LODResources, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + // Convert the Mesh using FMeshDescription + static bool CreateInputNodeForMeshDescription( + const HAPI_NodeId& NodeId, + const FMeshDescription& MeshDescription, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + // Convert the Mesh using FRawMesh + + static bool CreateInputNodeForRawMesh( + const HAPI_NodeId& NodeId, + const FStaticMeshSourceModel& SourceModel, + const int32& LODIndex, + const bool& DoExportLODs, + UStaticMesh* StaticMesh, + UStaticMeshComponent* StaticMeshComponent); + + static bool CreateInputNodeForBox( + HAPI_NodeId& OutBoxNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& BoxCenter, + const FVector& BoxExtent, + const FRotator& BoxRotation); + + static bool CreateInputNodeForSphere( + HAPI_NodeId& OutSphereNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphereCenter, + const float& SphereRadius); + + static bool CreateInputNodeForSphyl( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FVector& SphylCenter, + const FRotator& SphylRotation, + const float& SphylRadius, + const float& SphereLength); + + static bool CreateInputNodeForConvex( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FKConvexElem& ConvexCollider); + + static bool CreateInputNodeForCollider( + HAPI_NodeId& OutNodeId, + const HAPI_NodeId& InParentNodeID, + const int32& ColliderIndex, + const FString& ColliderName, + const TArray& ColliderVertices, + const TArray& ColliderIndices); + + static bool CreateInputNodeForMeshSockets( + const TArray& InMeshSocket, + const HAPI_NodeId& InParentNodeId, + HAPI_NodeId& OutSocketsNodeId); + + + // Helper function to extract the array of material names used by a given mesh + // This is used for marshalling static mesh's materials. + // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() + static void CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray& OutStaticMeshFaceMaterials); + + // Helper function to extract the array of material names used by a given mesh + // Also extracts all scalar/vector/texture parameter in the materials + // This is used for marshalling static mesh's materials. + // Memory allocated by this function needs to be cleared by DeleteFaceMaterialArray() + // The texture parameter array also needs to be cleared. + static void CreateFaceMaterialArray( + const TArray& Materials, + const TArray& FaceMaterialIndices, + TArray & OutStaticMeshFaceMaterials, + TMap> & OutScalarMaterialParameters, + TMap> & OutVectorMaterialParameters, + TMap> & OutTextureMaterialParameters); + + // Delete helper array of material names. + // Clean up the memory allocated by CreateFaceMaterialArray() + static void DeleteFaceMaterialArray(TArray & OutStaticMeshFaceMaterials); + + // Create and set mesh material attribute and material (scalar, vector and texture) parameters attributes + static bool CreateHoudiniMeshAttributes( + const int32 & NodeId, + const int32 & PartId, + const int32 & Count, + const TArray & TriangleMaterials, + const TMap> & ScalarMaterialParameters, + const TMap> & VectorMaterialParameters, + const TMap> & TextureMaterialParameters); + + /* + // Creates the unreal_level_path attribute on the input mesh + static bool AddLevelPathAttributeToMesh( + const HAPI_NodeId& NodeId, + const HAPI_PartId& PartId, + const FString& LevelPath, + const int32& Count); + */ + + private: + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp index 844259113..268476eba 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp @@ -1,124 +1,124 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "UnrealSplineTranslator.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEnginePrivatePCH.h" - -#include "Components/SplineComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniSplineTranslator.h" - -bool -FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId& CreatedInputNodeId, const FString& NodeName) -{ - if (!SplineComponent || SplineComponent->IsPendingKill()) - return false; - - int32 NumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); - float SplineLength = SplineComponent->GetSplineLength(); - - // Calculate the number of refined point we want - int32 NumberOfRefinedSplinePoints = SplineResolution > 0.0f ? ceil(SplineLength / SplineResolution) + 1 : NumberOfControlPoints; - - TArray RefinedSplinePositions; - TArray RefinedSplineRotations; - TArray RefinedSplineScales; - - if (NumberOfRefinedSplinePoints <= NumberOfControlPoints) - { - // There's not enough refined points, so we'll use the control points instead - RefinedSplinePositions.SetNumZeroed(NumberOfControlPoints); - RefinedSplineRotations.SetNumZeroed(NumberOfControlPoints); - RefinedSplineScales.SetNumZeroed(NumberOfControlPoints); - - for (int32 n = 0; n < NumberOfControlPoints; ++n) - { - RefinedSplinePositions[n] = SplineComponent->GetLocationAtSplinePoint(n, ESplineCoordinateSpace::Local); - RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World); - RefinedSplineScales[n] = SplineComponent->GetScaleAtSplinePoint(n); - } - } - else - { - // Calculate the refined spline component - RefinedSplinePositions.SetNumZeroed(NumberOfRefinedSplinePoints); - RefinedSplineRotations.SetNumZeroed(NumberOfRefinedSplinePoints); - RefinedSplineScales.SetNumZeroed(NumberOfRefinedSplinePoints); - - float CurrentDistance = 0.0f; - for (int32 n = 0; n < NumberOfRefinedSplinePoints; ++n) - { - RefinedSplinePositions[n] = SplineComponent->GetLocationAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::Local); - RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::World); - RefinedSplineScales[n] = SplineComponent->GetScaleAtDistanceAlongSpline(CurrentDistance); - - CurrentDistance += SplineResolution; - } - } - - - if (!FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData(CreatedInputNodeId, NodeName, - &RefinedSplinePositions, &RefinedSplineRotations, &RefinedSplineScales, - EHoudiniCurveType::Polygon, EHoudiniCurveMethod::Breakpoints, false, SplineComponent->IsClosedLoop())) - return false; - - // Add spline component tags if it has any - bool NeedToCommit = FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, SplineComponent->ComponentTags); - - // Add the parent actor's tag if it has any - AActor* ParentActor = SplineComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) - { - if (FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, ParentActor->Tags)) - NeedToCommit = true; - - HAPI_PartInfo PartInfo; - FHoudiniApi::PartInfo_Init(&PartInfo); - FHoudiniApi::GetPartInfo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId, 0, &PartInfo); - - // Add the unreal_actor_path attribute - if(FHoudiniEngineUtils::AddActorPathAttribute(CreatedInputNodeId, 0, ParentActor, PartInfo.faceCount)) - NeedToCommit = true; - - // Add the unreal_level_path attribute - if(FHoudiniEngineUtils::AddLevelPathAttribute(CreatedInputNodeId, 0, ParentActor->GetLevel(), PartInfo.faceCount)) - NeedToCommit = true; - } - - if (NeedToCommit) - { - // We successfully added tags to the geo, so we need to commit the changes - if (HAPI_RESULT_SUCCESS != FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId)) - HOUDINI_LOG_WARNING(TEXT("Could not create groups for the spline input's tags!")); - } - - - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "UnrealSplineTranslator.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEnginePrivatePCH.h" + +#include "Components/SplineComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniSplineTranslator.h" + +bool +FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId& CreatedInputNodeId, const FString& NodeName) +{ + if (!SplineComponent || SplineComponent->IsPendingKill()) + return false; + + int32 NumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); + float SplineLength = SplineComponent->GetSplineLength(); + + // Calculate the number of refined point we want + int32 NumberOfRefinedSplinePoints = SplineResolution > 0.0f ? ceil(SplineLength / SplineResolution) + 1 : NumberOfControlPoints; + + TArray RefinedSplinePositions; + TArray RefinedSplineRotations; + TArray RefinedSplineScales; + + if (NumberOfRefinedSplinePoints <= NumberOfControlPoints) + { + // There's not enough refined points, so we'll use the control points instead + RefinedSplinePositions.SetNumZeroed(NumberOfControlPoints); + RefinedSplineRotations.SetNumZeroed(NumberOfControlPoints); + RefinedSplineScales.SetNumZeroed(NumberOfControlPoints); + + for (int32 n = 0; n < NumberOfControlPoints; ++n) + { + RefinedSplinePositions[n] = SplineComponent->GetLocationAtSplinePoint(n, ESplineCoordinateSpace::Local); + RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtSplinePoint(n, ESplineCoordinateSpace::World); + RefinedSplineScales[n] = SplineComponent->GetScaleAtSplinePoint(n); + } + } + else + { + // Calculate the refined spline component + RefinedSplinePositions.SetNumZeroed(NumberOfRefinedSplinePoints); + RefinedSplineRotations.SetNumZeroed(NumberOfRefinedSplinePoints); + RefinedSplineScales.SetNumZeroed(NumberOfRefinedSplinePoints); + + float CurrentDistance = 0.0f; + for (int32 n = 0; n < NumberOfRefinedSplinePoints; ++n) + { + RefinedSplinePositions[n] = SplineComponent->GetLocationAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::Local); + RefinedSplineRotations[n] = SplineComponent->GetQuaternionAtDistanceAlongSpline(CurrentDistance, ESplineCoordinateSpace::World); + RefinedSplineScales[n] = SplineComponent->GetScaleAtDistanceAlongSpline(CurrentDistance); + + CurrentDistance += SplineResolution; + } + } + + + if (!FHoudiniSplineTranslator::HapiCreateCurveInputNodeForData(CreatedInputNodeId, NodeName, + &RefinedSplinePositions, &RefinedSplineRotations, &RefinedSplineScales, + EHoudiniCurveType::Polygon, EHoudiniCurveMethod::Breakpoints, false, SplineComponent->IsClosedLoop())) + return false; + + // Add spline component tags if it has any + bool NeedToCommit = FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, SplineComponent->ComponentTags); + + // Add the parent actor's tag if it has any + AActor* ParentActor = SplineComponent->GetOwner(); + if (ParentActor && !ParentActor->IsPendingKill()) + { + if (FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, ParentActor->Tags)) + NeedToCommit = true; + + HAPI_PartInfo PartInfo; + FHoudiniApi::PartInfo_Init(&PartInfo); + FHoudiniApi::GetPartInfo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId, 0, &PartInfo); + + // Add the unreal_actor_path attribute + if(FHoudiniEngineUtils::AddActorPathAttribute(CreatedInputNodeId, 0, ParentActor, PartInfo.faceCount)) + NeedToCommit = true; + + // Add the unreal_level_path attribute + if(FHoudiniEngineUtils::AddLevelPathAttribute(CreatedInputNodeId, 0, ParentActor->GetLevel(), PartInfo.faceCount)) + NeedToCommit = true; + } + + if (NeedToCommit) + { + // We successfully added tags to the geo, so we need to commit the changes + if (HAPI_RESULT_SUCCESS != FHoudiniApi::CommitGeo(FHoudiniEngine::Get().GetSession(), CreatedInputNodeId)) + HOUDINI_LOG_WARNING(TEXT("Could not create groups for the spline input's tags!")); + } + + + return true; +} diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.h b/Source/HoudiniEngine/Private/UnrealSplineTranslator.h index f4d8adeb5..e6d302233 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.h @@ -1,39 +1,39 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HAPI/HAPI_Common.h" -#include "UObject/NameTypes.h" - -class USplineComponent; - -struct HOUDINIENGINE_API FUnrealSplineTranslator -{ -public: - static bool CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId &CreatedInputNodeId, const FString& NodeName); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HAPI/HAPI_Common.h" +#include "UObject/NameTypes.h" + +class USplineComponent; + +struct HOUDINIENGINE_API FUnrealSplineTranslator +{ +public: + static bool CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId &CreatedInputNodeId, const FString& NodeName); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h index 6d89a2be0..bafb29ed2 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Common.h @@ -1,4 +1,4 @@ -/* +/* * PROPRIETARY INFORMATION. This software is proprietary to * Side Effects Software Inc., and is not to be reproduced, * transmitted, or disclosed in any way without written permission. @@ -184,6 +184,7 @@ enum HAPI_License HAPI_LICENSE_HOUDINI_FX, HAPI_LICENSE_HOUDINI_ENGINE_INDIE, HAPI_LICENSE_HOUDINI_INDIE, + HAPI_LICENSE_HOUDINI_ENGINE_UNITY_UNREAL, HAPI_LICENSE_MAX }; HAPI_C_ENUM_TYPEDEF( HAPI_License ) @@ -1434,6 +1435,9 @@ struct HAPI_API HAPI_ParmInfo /// Provides the raw condition string which is used to evalute whether /// a parm is enabled or disabled HAPI_StringHandle disabledConditionSH; + + /// Whether or not the "Use Menu Item Token As Value" checkbox was checked in a integer menu item. + HAPI_Bool useMenuItemTokenAsValue; }; HAPI_C_STRUCT_TYPEDEF( HAPI_ParmInfo ) @@ -1825,17 +1829,27 @@ HAPI_C_STRUCT_TYPEDEF( HAPI_VolumeVisualInfo ) struct HAPI_API HAPI_CurveInfo { HAPI_CurveType curveType; - int curveCount; /// The number of curves contained in this curve mesh. - int vertexCount; /// The number of control vertices (CVs) for all curves. - int knotCount; /// The number of knots for all curves. + /// The number of curves contained in this curve mesh. + int curveCount; + + /// The number of control vertices (CVs) for all curves. + int vertexCount; + + /// The number of knots for all curves. + int knotCount; + + /// Whether the curves in this curve mesh are periodic. HAPI_Bool isPeriodic; - /// Whether the curves in this curve mesh are periodic. + + /// Whether the curves in this curve mesh are rational. HAPI_Bool isRational; - /// Whether the curves in this curve mesh are rational. - int order; /// Order of 1 is invalid. 0 means there is a varying order. - HAPI_Bool hasKnots; /// Whether the curve has knots. + /// Order of 1 is invalid. 0 means there is a varying order. + int order; + + /// Whether the curve has knots. + HAPI_Bool hasKnots; }; HAPI_C_STRUCT_TYPEDEF( HAPI_CurveInfo ) diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h index 70a18952f..5d7c6f307 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h @@ -27,12 +27,12 @@ // expecting to compile against. #define HAPI_VERSION_HOUDINI_MAJOR 18 #define HAPI_VERSION_HOUDINI_MINOR 5 -#define HAPI_VERSION_HOUDINI_BUILD 633 +#define HAPI_VERSION_HOUDINI_BUILD 696 #define HAPI_VERSION_HOUDINI_PATCH 0 // The two components of the Houdini Engine (marketed) version. #define HAPI_VERSION_HOUDINI_ENGINE_MAJOR 3 -#define HAPI_VERSION_HOUDINI_ENGINE_MINOR 6 +#define HAPI_VERSION_HOUDINI_ENGINE_MINOR 7 // This is a monotonously increasing API version number that can be used // to lock against a certain API for compatibility purposes. Basically, @@ -40,6 +40,6 @@ // might no longer compile. Semantic changes to the methods will also // cause this version to increase. This number will be reset to 0 // every time the Houdini Engine version is bumped. -#define HAPI_VERSION_HOUDINI_ENGINE_API 3 +#define HAPI_VERSION_HOUDINI_ENGINE_API 1 #endif // __HAPI_VERSION_h__ diff --git a/Source/HoudiniEngine/Public/HoudiniApi.h b/Source/HoudiniEngine/Public/HoudiniApi.h index 2358241ec..f2cc2a25a 100644 --- a/Source/HoudiniEngine/Public/HoudiniApi.h +++ b/Source/HoudiniEngine/Public/HoudiniApi.h @@ -1,1025 +1,1025 @@ -/* - * Copyright (c) <2021> 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. - * - * - * COMMENTS: - * This file is generated. Do not modify directly. - */ - -#pragma once -#include "HAPI/HAPI.h" -#include "HAL/PlatformProcess.h" - - -struct HOUDINIENGINE_API FHoudiniApi -{ -public: - - static void InitializeHAPI(void* LibraryHandle); - static void FinalizeHAPI(); - static bool IsHAPIInitialized(); - -public: - - typedef HAPI_Result (*AddAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*AddGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - typedef HAPI_AssetInfo (*AssetInfo_CreateFuncPtr)(); - typedef void (*AssetInfo_InitFuncPtr)(HAPI_AssetInfo * in); - typedef HAPI_AttributeInfo (*AttributeInfo_CreateFuncPtr)(); - typedef void (*AttributeInfo_InitFuncPtr)(HAPI_AttributeInfo * in); - typedef HAPI_Result (*BindCustomImplementationFuncPtr)(HAPI_SessionType session_type, const char * dll_path); - typedef HAPI_Result (*CancelPDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - typedef HAPI_Result (*CheckForSpecificErrorsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); - typedef HAPI_Result (*CleanupFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*ClearConnectionErrorFuncPtr)(); - typedef HAPI_Result (*CloseSessionFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*CommitGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*CommitWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*ComposeChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - typedef HAPI_Result (*ComposeNodeCookResultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); - typedef HAPI_Result (*ComposeObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); - typedef HAPI_CompositorOptions (*CompositorOptions_CreateFuncPtr)(); - typedef void (*CompositorOptions_InitFuncPtr)(HAPI_CompositorOptions * in); - typedef HAPI_Result (*ConnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); - typedef HAPI_Result (*ConvertMatrixToEulerFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - typedef HAPI_Result (*ConvertMatrixToQuatFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); - typedef HAPI_Result (*ConvertTransformFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - typedef HAPI_Result (*ConvertTransformEulerToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); - typedef HAPI_Result (*ConvertTransformQuatToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); - typedef HAPI_Result (*CookNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); - typedef HAPI_Bool (*CookOptions_AreEqualFuncPtr)(const HAPI_CookOptions * left, const HAPI_CookOptions * right); - typedef HAPI_CookOptions (*CookOptions_CreateFuncPtr)(); - typedef void (*CookOptions_InitFuncPtr)(HAPI_CookOptions * in); - typedef HAPI_Result (*CookPDGFuncPtr)(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); - typedef HAPI_Result (*CreateCustomSessionFuncPtr)(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); - typedef HAPI_Result (*CreateHeightFieldInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); - typedef HAPI_Result (*CreateHeightfieldInputVolumeNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); - typedef HAPI_Result (*CreateInProcessSessionFuncPtr)(HAPI_Session * session); - typedef HAPI_Result (*CreateInputNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); - typedef HAPI_Result (*CreateNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); - typedef HAPI_Result (*CreateThriftNamedPipeSessionFuncPtr)(HAPI_Session * session, const char * pipe_name); - typedef HAPI_Result (*CreateThriftSocketSessionFuncPtr)(HAPI_Session * session, const char * host_name, int port); - typedef HAPI_Result (*CreateWorkitemFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); - typedef HAPI_CurveInfo (*CurveInfo_CreateFuncPtr)(); - typedef void (*CurveInfo_InitFuncPtr)(HAPI_CurveInfo * in); - typedef HAPI_Result (*DeleteAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*DeleteGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - typedef HAPI_Result (*DeleteNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*DirtyPDGNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); - typedef HAPI_Result (*DisconnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); - typedef HAPI_Result (*DisconnectNodeOutputsAtFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); - typedef HAPI_Result (*ExtractImageToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); - typedef HAPI_Result (*ExtractImageToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); - typedef HAPI_GeoInfo (*GeoInfo_CreateFuncPtr)(); - typedef int (*GeoInfo_GetGroupCountByTypeFuncPtr)(HAPI_GeoInfo * in, HAPI_GroupType type); - typedef void (*GeoInfo_InitFuncPtr)(HAPI_GeoInfo * in); - typedef HAPI_Result (*GetActiveCacheCountFuncPtr)(const HAPI_Session * session, int * active_cache_count); - typedef HAPI_Result (*GetActiveCacheNamesFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); - typedef HAPI_Result (*GetAssetDefinitionParmCountsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); - typedef HAPI_Result (*GetAssetDefinitionParmInfosFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); - typedef HAPI_Result (*GetAssetDefinitionParmValuesFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); - typedef HAPI_Result (*GetAssetInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); - typedef HAPI_Result (*GetAttributeFloat64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeFloatArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); - typedef HAPI_Result (*GetAttributeInt16ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeInt64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeIntArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); - typedef HAPI_Result (*GetAttributeStringArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); - typedef HAPI_Result (*GetAttributeUInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - typedef HAPI_Result (*GetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); - typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); - typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); - typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); - typedef HAPI_Result (*GetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); - typedef HAPI_Result (*GetComposedChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); - typedef HAPI_Result (*GetComposedNodeCookResultFuncPtr)(const HAPI_Session * session, char * string_value, int length); - typedef HAPI_Result (*GetComposedObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); - typedef HAPI_Result (*GetComposedObjectTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); - typedef HAPI_Result (*GetCompositorOptionsFuncPtr)(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options); - typedef HAPI_Result (*GetConnectionErrorFuncPtr)(char * string_value, int length, HAPI_Bool clear); - typedef HAPI_Result (*GetConnectionErrorLengthFuncPtr)(int * buffer_length); - typedef HAPI_Result (*GetCookingCurrentCountFuncPtr)(const HAPI_Session * session, int * count); - typedef HAPI_Result (*GetCookingTotalCountFuncPtr)(const HAPI_Session * session, int * count); - typedef HAPI_Result (*GetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); - typedef HAPI_Result (*GetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); - typedef HAPI_Result (*GetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); - typedef HAPI_Result (*GetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); - typedef HAPI_Result (*GetDisplayGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); - typedef HAPI_Result (*GetEdgeCountOfEdgeGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count); - typedef HAPI_Result (*GetEnvIntFuncPtr)(HAPI_EnvIntType int_type, int * value); - typedef HAPI_Result (*GetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); - typedef HAPI_Result (*GetFirstVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - typedef HAPI_Result (*GetGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); - typedef HAPI_Result (*GetGeoSizeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); - typedef HAPI_Result (*GetGroupCountOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); - typedef HAPI_Result (*GetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - typedef HAPI_Result (*GetGroupMembershipOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - typedef HAPI_Result (*GetGroupNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - typedef HAPI_Result (*GetGroupNamesOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - typedef HAPI_Result (*GetHIPFileNodeCountFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, int * count); - typedef HAPI_Result (*GetHIPFileNodeIdsFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); - typedef HAPI_Result (*GetHandleBindingInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); - typedef HAPI_Result (*GetHandleInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); - typedef HAPI_Result (*GetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); - typedef HAPI_Result (*GetImageFilePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); - typedef HAPI_Result (*GetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); - typedef HAPI_Result (*GetImageMemoryBufferFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); - typedef HAPI_Result (*GetImagePlaneCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); - typedef HAPI_Result (*GetImagePlanesFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); - typedef HAPI_Result (*GetInstanceTransformsOnPartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - typedef HAPI_Result (*GetInstancedObjectIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); - typedef HAPI_Result (*GetInstancedPartIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); - typedef HAPI_Result (*GetInstancerPartTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - typedef HAPI_Result (*GetManagerNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); - typedef HAPI_Result (*GetMaterialInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); - typedef HAPI_Result (*GetMaterialNodeIdsOnFacesFuncPtr)(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); - typedef HAPI_Result (*GetNextVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - typedef HAPI_Result (*GetNodeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); - typedef HAPI_Result (*GetNodeInputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); - typedef HAPI_Result (*GetNodeOutputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); - typedef HAPI_Result (*GetNodePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); - typedef HAPI_Result (*GetNumWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * num); - typedef HAPI_Result (*GetObjectInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); - typedef HAPI_Result (*GetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); - typedef HAPI_Result (*GetOutputGeoCountFuncPtr)(const HAPI_Session* session, HAPI_NodeId node_id, int* count); - typedef HAPI_Result (*GetOutputGeoInfosFuncPtr)(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count); - typedef HAPI_Result (*GetOutputNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); - typedef HAPI_Result (*GetPDGEventsFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); - typedef HAPI_Result (*GetPDGGraphContextIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); - typedef HAPI_Result (*GetPDGGraphContextsFuncPtr)(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); - typedef HAPI_Result (*GetPDGStateFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); - typedef HAPI_Result (*GetParametersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); - typedef HAPI_Result (*GetParmChoiceListsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); - typedef HAPI_Result (*GetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); - typedef HAPI_Result (*GetParmFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); - typedef HAPI_Result (*GetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); - typedef HAPI_Result (*GetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); - typedef HAPI_Result (*GetParmIdFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); - typedef HAPI_Result (*GetParmInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); - typedef HAPI_Result (*GetParmInfoFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); - typedef HAPI_Result (*GetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); - typedef HAPI_Result (*GetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); - typedef HAPI_Result (*GetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); - typedef HAPI_Result (*GetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); - typedef HAPI_Result (*GetParmStringValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); - typedef HAPI_Result (*GetParmTagNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); - typedef HAPI_Result (*GetParmTagValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); - typedef HAPI_Result (*GetParmWithTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); - typedef HAPI_Result (*GetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); - typedef HAPI_Result (*GetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); - typedef HAPI_Result (*GetPresetBufLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); - typedef HAPI_Result (*GetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int * value); - typedef HAPI_Result (*GetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); - typedef HAPI_Result (*GetServerEnvVarCountFuncPtr)(const HAPI_Session * session, int * env_count); - typedef HAPI_Result (*GetServerEnvVarListFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); - typedef HAPI_Result (*GetSessionEnvIntFuncPtr)(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); - typedef HAPI_Result (*GetSessionSyncInfoFuncPtr)(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); - typedef HAPI_Result (*GetSphereInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); - typedef HAPI_Result (*GetStatusFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, int * status); - typedef HAPI_Result (*GetStatusStringFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); - typedef HAPI_Result (*GetStatusStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); - typedef HAPI_Result (*GetStringFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); - typedef HAPI_Result (*GetStringBatchFuncPtr)(const HAPI_Session * session, char * char_buffer, int char_array_length); - typedef HAPI_Result (*GetStringBatchSizeFuncPtr)(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); - typedef HAPI_Result (*GetStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); - typedef HAPI_Result (*GetSupportedImageFileFormatCountFuncPtr)(const HAPI_Session * session, int * file_format_count); - typedef HAPI_Result (*GetSupportedImageFileFormatsFuncPtr)(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); - typedef HAPI_Result (*GetTimeFuncPtr)(const HAPI_Session * session, float * time); - typedef HAPI_Result (*GetTimelineOptionsFuncPtr)(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); - typedef HAPI_Result (*GetTotalCookCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - typedef HAPI_Result (*GetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool * enabled); - typedef HAPI_Result (*GetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); - typedef HAPI_Result (*GetViewportFuncPtr)(const HAPI_Session * session, HAPI_Viewport * viewport); - typedef HAPI_Result (*GetVolumeBoundsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); - typedef HAPI_Result (*GetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); - typedef HAPI_Result (*GetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); - typedef HAPI_Result (*GetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); - typedef HAPI_Result (*GetVolumeVisualInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); - typedef HAPI_Result (*GetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); - typedef HAPI_Result (*GetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); - typedef HAPI_Result (*GetWorkitemDataLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); - typedef HAPI_Result (*GetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); - typedef HAPI_Result (*GetWorkitemInfoFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); - typedef HAPI_Result (*GetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); - typedef HAPI_Result (*GetWorkitemResultInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); - typedef HAPI_Result (*GetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); - typedef HAPI_Result (*GetWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); - typedef HAPI_HandleBindingInfo (*HandleBindingInfo_CreateFuncPtr)(); - typedef void (*HandleBindingInfo_InitFuncPtr)(HAPI_HandleBindingInfo * in); - typedef HAPI_HandleInfo (*HandleInfo_CreateFuncPtr)(); - typedef void (*HandleInfo_InitFuncPtr)(HAPI_HandleInfo * in); - typedef HAPI_ImageFileFormat (*ImageFileFormat_CreateFuncPtr)(); - typedef void (*ImageFileFormat_InitFuncPtr)(HAPI_ImageFileFormat *in); - typedef HAPI_ImageInfo (*ImageInfo_CreateFuncPtr)(); - typedef void (*ImageInfo_InitFuncPtr)(HAPI_ImageInfo * in); - typedef HAPI_Result (*InitializeFuncPtr)(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); - typedef HAPI_Result (*InsertMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - typedef HAPI_Result (*InterruptFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*IsInitializedFuncPtr)(const HAPI_Session * session); - typedef HAPI_Result (*IsNodeValidFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); - typedef HAPI_Result (*IsSessionValidFuncPtr)(const HAPI_Session * session); - typedef HAPI_Keyframe (*Keyframe_CreateFuncPtr)(); - typedef void (*Keyframe_InitFuncPtr)(HAPI_Keyframe * in); - typedef HAPI_Result (*LoadAssetLibraryFromFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - typedef HAPI_Result (*LoadAssetLibraryFromMemoryFuncPtr)(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - typedef HAPI_Result (*LoadGeoFromFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_Result (*LoadGeoFromMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); - typedef HAPI_Result (*LoadHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); - typedef HAPI_Result (*LoadNodeFromFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); - typedef HAPI_MaterialInfo (*MaterialInfo_CreateFuncPtr)(); - typedef void (*MaterialInfo_InitFuncPtr)(HAPI_MaterialInfo * in); - typedef HAPI_Result (*MergeHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); - typedef HAPI_NodeInfo (*NodeInfo_CreateFuncPtr)(); - typedef void (*NodeInfo_InitFuncPtr)(HAPI_NodeInfo * in); - typedef HAPI_ObjectInfo (*ObjectInfo_CreateFuncPtr)(); - typedef void (*ObjectInfo_InitFuncPtr)(HAPI_ObjectInfo * in); - typedef HAPI_ParmChoiceInfo (*ParmChoiceInfo_CreateFuncPtr)(); - typedef void (*ParmChoiceInfo_InitFuncPtr)(HAPI_ParmChoiceInfo * in); - typedef HAPI_Result (*ParmHasExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); - typedef HAPI_Result (*ParmHasTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); - typedef HAPI_ParmInfo (*ParmInfo_CreateFuncPtr)(); - typedef int (*ParmInfo_GetFloatValueCountFuncPtr)(const HAPI_ParmInfo * in); - typedef int (*ParmInfo_GetIntValueCountFuncPtr)(const HAPI_ParmInfo * in); - typedef int (*ParmInfo_GetStringValueCountFuncPtr)(const HAPI_ParmInfo* in); - typedef void (*ParmInfo_InitFuncPtr)(HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsFloatFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsIntFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsNodeFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsNonValueFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsPathFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_Bool (*ParmInfo_IsStringFuncPtr)(const HAPI_ParmInfo * in); - typedef HAPI_PartInfo (*PartInfo_CreateFuncPtr)(); - typedef int (*PartInfo_GetAttributeCountByOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - typedef int (*PartInfo_GetElementCountByAttributeOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - typedef int (*PartInfo_GetElementCountByGroupTypeFuncPtr)(HAPI_PartInfo * in, HAPI_GroupType type); - typedef void (*PartInfo_InitFuncPtr)(HAPI_PartInfo * in); - typedef HAPI_Result (*PausePDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - typedef HAPI_Result (*PythonThreadInterpreterLockFuncPtr)(const HAPI_Session * session, HAPI_Bool locked); - typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); - typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); - typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const HAPI_StringHandle string_handle); - typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); - typedef HAPI_Result (*RenderCOPToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId cop_node_id); - typedef HAPI_Result (*RenderTextureToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); - typedef HAPI_Result (*ResetSimulationFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*RevertGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); - typedef HAPI_Result (*RevertParmToDefaultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); - typedef HAPI_Result (*RevertParmToDefaultsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); - typedef HAPI_Result (*SaveGeoToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_Result (*SaveGeoToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); - typedef HAPI_Result (*SaveHIPFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); - typedef HAPI_Result (*SaveNodeToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - typedef HAPI_SessionSyncInfo (*SessionSyncInfo_CreateFuncPtr)(); - typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); - typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); - typedef HAPI_Result (*SetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); - typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); - typedef HAPI_Result (*SetCompositorOptionsFuncPtr)(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options); - typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); - typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); - typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); - typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); - typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); - typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); - typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); - typedef HAPI_Result (*SetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); - typedef HAPI_Result (*SetNodeDisplayFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); - typedef HAPI_Result (*SetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); - typedef HAPI_Result (*SetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*SetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); - typedef HAPI_Result (*SetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); - typedef HAPI_Result (*SetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); - typedef HAPI_Result (*SetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); - typedef HAPI_Result (*SetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); - typedef HAPI_Result (*SetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - typedef HAPI_Result (*SetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); - typedef HAPI_Result (*SetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); - typedef HAPI_Result (*SetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int value); - typedef HAPI_Result (*SetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, const char * value); - typedef HAPI_Result (*SetSessionSyncFuncPtr)(const HAPI_Session * session, HAPI_Bool enable); - typedef HAPI_Result (*SetSessionSyncInfoFuncPtr)(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); - typedef HAPI_Result (*SetTimeFuncPtr)(const HAPI_Session * session, float time); - typedef HAPI_Result (*SetTimelineOptionsFuncPtr)(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); - typedef HAPI_Result (*SetTransformAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - typedef HAPI_Result (*SetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool enabled); - typedef HAPI_Result (*SetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); - typedef HAPI_Result (*SetViewportFuncPtr)(const HAPI_Session * session, const HAPI_Viewport * viewport); - typedef HAPI_Result (*SetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); - typedef HAPI_Result (*SetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); - typedef HAPI_Result (*SetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); - typedef HAPI_Result (*SetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); - typedef HAPI_Result (*SetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); - typedef HAPI_Result (*SetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); - typedef HAPI_Result (*SetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); - typedef HAPI_Result (*SetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); - typedef HAPI_Result (*StartThriftNamedPipeServerFuncPtr)(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); - typedef HAPI_Result (*StartThriftSocketServerFuncPtr)(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); - typedef HAPI_ThriftServerOptions (*ThriftServerOptions_CreateFuncPtr)(); - typedef void (*ThriftServerOptions_InitFuncPtr)(HAPI_ThriftServerOptions * in); - typedef HAPI_TimelineOptions (*TimelineOptions_CreateFuncPtr)(); - typedef void (*TimelineOptions_InitFuncPtr)(HAPI_TimelineOptions * in); - typedef HAPI_TransformEuler (*TransformEuler_CreateFuncPtr)(); - typedef void (*TransformEuler_InitFuncPtr)(HAPI_TransformEuler * in); - typedef HAPI_Transform (*Transform_CreateFuncPtr)(); - typedef void (*Transform_InitFuncPtr)(HAPI_Transform * in); - typedef HAPI_Viewport (*Viewport_CreateFuncPtr)(); - typedef HAPI_VolumeInfo (*VolumeInfo_CreateFuncPtr)(); - typedef void (*VolumeInfo_InitFuncPtr)(HAPI_VolumeInfo * in); - typedef HAPI_VolumeTileInfo (*VolumeTileInfo_CreateFuncPtr)(); - typedef void (*VolumeTileInfo_InitFuncPtr)(HAPI_VolumeTileInfo * in); - -public: - - static AddAttributeFuncPtr AddAttribute; - static AddGroupFuncPtr AddGroup; - static AssetInfo_CreateFuncPtr AssetInfo_Create; - static AssetInfo_InitFuncPtr AssetInfo_Init; - static AttributeInfo_CreateFuncPtr AttributeInfo_Create; - static AttributeInfo_InitFuncPtr AttributeInfo_Init; - static BindCustomImplementationFuncPtr BindCustomImplementation; - static CancelPDGCookFuncPtr CancelPDGCook; - static CheckForSpecificErrorsFuncPtr CheckForSpecificErrors; - static CleanupFuncPtr Cleanup; - static ClearConnectionErrorFuncPtr ClearConnectionError; - static CloseSessionFuncPtr CloseSession; - static CommitGeoFuncPtr CommitGeo; - static CommitWorkitemsFuncPtr CommitWorkitems; - static ComposeChildNodeListFuncPtr ComposeChildNodeList; - static ComposeNodeCookResultFuncPtr ComposeNodeCookResult; - static ComposeObjectListFuncPtr ComposeObjectList; - static CompositorOptions_CreateFuncPtr CompositorOptions_Create; - static CompositorOptions_InitFuncPtr CompositorOptions_Init; - static ConnectNodeInputFuncPtr ConnectNodeInput; - static ConvertMatrixToEulerFuncPtr ConvertMatrixToEuler; - static ConvertMatrixToQuatFuncPtr ConvertMatrixToQuat; - static ConvertTransformFuncPtr ConvertTransform; - static ConvertTransformEulerToMatrixFuncPtr ConvertTransformEulerToMatrix; - static ConvertTransformQuatToMatrixFuncPtr ConvertTransformQuatToMatrix; - static CookNodeFuncPtr CookNode; - static CookOptions_AreEqualFuncPtr CookOptions_AreEqual; - static CookOptions_CreateFuncPtr CookOptions_Create; - static CookOptions_InitFuncPtr CookOptions_Init; - static CookPDGFuncPtr CookPDG; - static CreateCustomSessionFuncPtr CreateCustomSession; - static CreateHeightFieldInputFuncPtr CreateHeightFieldInput; - static CreateHeightfieldInputVolumeNodeFuncPtr CreateHeightfieldInputVolumeNode; - static CreateInProcessSessionFuncPtr CreateInProcessSession; - static CreateInputNodeFuncPtr CreateInputNode; - static CreateNodeFuncPtr CreateNode; - static CreateThriftNamedPipeSessionFuncPtr CreateThriftNamedPipeSession; - static CreateThriftSocketSessionFuncPtr CreateThriftSocketSession; - static CreateWorkitemFuncPtr CreateWorkitem; - static CurveInfo_CreateFuncPtr CurveInfo_Create; - static CurveInfo_InitFuncPtr CurveInfo_Init; - static DeleteAttributeFuncPtr DeleteAttribute; - static DeleteGroupFuncPtr DeleteGroup; - static DeleteNodeFuncPtr DeleteNode; - static DirtyPDGNodeFuncPtr DirtyPDGNode; - static DisconnectNodeInputFuncPtr DisconnectNodeInput; - static DisconnectNodeOutputsAtFuncPtr DisconnectNodeOutputsAt; - static ExtractImageToFileFuncPtr ExtractImageToFile; - static ExtractImageToMemoryFuncPtr ExtractImageToMemory; - static GeoInfo_CreateFuncPtr GeoInfo_Create; - static GeoInfo_GetGroupCountByTypeFuncPtr GeoInfo_GetGroupCountByType; - static GeoInfo_InitFuncPtr GeoInfo_Init; - static GetActiveCacheCountFuncPtr GetActiveCacheCount; - static GetActiveCacheNamesFuncPtr GetActiveCacheNames; - static GetAssetDefinitionParmCountsFuncPtr GetAssetDefinitionParmCounts; - static GetAssetDefinitionParmInfosFuncPtr GetAssetDefinitionParmInfos; - static GetAssetDefinitionParmValuesFuncPtr GetAssetDefinitionParmValues; - static GetAssetInfoFuncPtr GetAssetInfo; - static GetAttributeFloat64ArrayDataFuncPtr GetAttributeFloat64ArrayData; - static GetAttributeFloat64DataFuncPtr GetAttributeFloat64Data; - static GetAttributeFloatArrayDataFuncPtr GetAttributeFloatArrayData; - static GetAttributeFloatDataFuncPtr GetAttributeFloatData; - static GetAttributeInfoFuncPtr GetAttributeInfo; - static GetAttributeInt16ArrayDataFuncPtr GetAttributeInt16ArrayData; - static GetAttributeInt16DataFuncPtr GetAttributeInt16Data; - static GetAttributeInt64ArrayDataFuncPtr GetAttributeInt64ArrayData; - static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; - static GetAttributeInt8ArrayDataFuncPtr GetAttributeInt8ArrayData; - static GetAttributeInt8DataFuncPtr GetAttributeInt8Data; - static GetAttributeIntArrayDataFuncPtr GetAttributeIntArrayData; - static GetAttributeIntDataFuncPtr GetAttributeIntData; - static GetAttributeNamesFuncPtr GetAttributeNames; - static GetAttributeStringArrayDataFuncPtr GetAttributeStringArrayData; - static GetAttributeStringDataFuncPtr GetAttributeStringData; - static GetAttributeUInt8ArrayDataFuncPtr GetAttributeUInt8ArrayData; - static GetAttributeUInt8DataFuncPtr GetAttributeUInt8Data; - static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; - static GetAvailableAssetsFuncPtr GetAvailableAssets; - static GetBoxInfoFuncPtr GetBoxInfo; - static GetCachePropertyFuncPtr GetCacheProperty; - static GetComposedChildNodeListFuncPtr GetComposedChildNodeList; - static GetComposedNodeCookResultFuncPtr GetComposedNodeCookResult; - static GetComposedObjectListFuncPtr GetComposedObjectList; - static GetComposedObjectTransformsFuncPtr GetComposedObjectTransforms; - static GetCompositorOptionsFuncPtr GetCompositorOptions; - static GetConnectionErrorFuncPtr GetConnectionError; - static GetConnectionErrorLengthFuncPtr GetConnectionErrorLength; - static GetCookingCurrentCountFuncPtr GetCookingCurrentCount; - static GetCookingTotalCountFuncPtr GetCookingTotalCount; - static GetCurveCountsFuncPtr GetCurveCounts; - static GetCurveInfoFuncPtr GetCurveInfo; - static GetCurveKnotsFuncPtr GetCurveKnots; - static GetCurveOrdersFuncPtr GetCurveOrders; - static GetDisplayGeoInfoFuncPtr GetDisplayGeoInfo; - static GetEdgeCountOfEdgeGroupFuncPtr GetEdgeCountOfEdgeGroup; - static GetEnvIntFuncPtr GetEnvInt; - static GetFaceCountsFuncPtr GetFaceCounts; - static GetFirstVolumeTileFuncPtr GetFirstVolumeTile; - static GetGeoInfoFuncPtr GetGeoInfo; - static GetGeoSizeFuncPtr GetGeoSize; - static GetGroupCountOnPackedInstancePartFuncPtr GetGroupCountOnPackedInstancePart; - static GetGroupMembershipFuncPtr GetGroupMembership; - static GetGroupMembershipOnPackedInstancePartFuncPtr GetGroupMembershipOnPackedInstancePart; - static GetGroupNamesFuncPtr GetGroupNames; - static GetGroupNamesOnPackedInstancePartFuncPtr GetGroupNamesOnPackedInstancePart; - static GetHIPFileNodeCountFuncPtr GetHIPFileNodeCount; - static GetHIPFileNodeIdsFuncPtr GetHIPFileNodeIds; - static GetHandleBindingInfoFuncPtr GetHandleBindingInfo; - static GetHandleInfoFuncPtr GetHandleInfo; - static GetHeightFieldDataFuncPtr GetHeightFieldData; - static GetImageFilePathFuncPtr GetImageFilePath; - static GetImageInfoFuncPtr GetImageInfo; - static GetImageMemoryBufferFuncPtr GetImageMemoryBuffer; - static GetImagePlaneCountFuncPtr GetImagePlaneCount; - static GetImagePlanesFuncPtr GetImagePlanes; - static GetInstanceTransformsOnPartFuncPtr GetInstanceTransformsOnPart; - static GetInstancedObjectIdsFuncPtr GetInstancedObjectIds; - static GetInstancedPartIdsFuncPtr GetInstancedPartIds; - static GetInstancerPartTransformsFuncPtr GetInstancerPartTransforms; - static GetManagerNodeIdFuncPtr GetManagerNodeId; - static GetMaterialInfoFuncPtr GetMaterialInfo; - static GetMaterialNodeIdsOnFacesFuncPtr GetMaterialNodeIdsOnFaces; - static GetNextVolumeTileFuncPtr GetNextVolumeTile; - static GetNodeInfoFuncPtr GetNodeInfo; - static GetNodeInputNameFuncPtr GetNodeInputName; - static GetNodeOutputNameFuncPtr GetNodeOutputName; - static GetNodePathFuncPtr GetNodePath; - static GetNumWorkitemsFuncPtr GetNumWorkitems; - static GetObjectInfoFuncPtr GetObjectInfo; - static GetObjectTransformFuncPtr GetObjectTransform; - static GetOutputGeoCountFuncPtr GetOutputGeoCount; - static GetOutputGeoInfosFuncPtr GetOutputGeoInfos; - static GetOutputNodeIdFuncPtr GetOutputNodeId; - static GetPDGEventsFuncPtr GetPDGEvents; - static GetPDGGraphContextIdFuncPtr GetPDGGraphContextId; - static GetPDGGraphContextsFuncPtr GetPDGGraphContexts; - static GetPDGStateFuncPtr GetPDGState; - static GetParametersFuncPtr GetParameters; - static GetParmChoiceListsFuncPtr GetParmChoiceLists; - static GetParmExpressionFuncPtr GetParmExpression; - static GetParmFileFuncPtr GetParmFile; - static GetParmFloatValueFuncPtr GetParmFloatValue; - static GetParmFloatValuesFuncPtr GetParmFloatValues; - static GetParmIdFromNameFuncPtr GetParmIdFromName; - static GetParmInfoFuncPtr GetParmInfo; - static GetParmInfoFromNameFuncPtr GetParmInfoFromName; - static GetParmIntValueFuncPtr GetParmIntValue; - static GetParmIntValuesFuncPtr GetParmIntValues; - static GetParmNodeValueFuncPtr GetParmNodeValue; - static GetParmStringValueFuncPtr GetParmStringValue; - static GetParmStringValuesFuncPtr GetParmStringValues; - static GetParmTagNameFuncPtr GetParmTagName; - static GetParmTagValueFuncPtr GetParmTagValue; - static GetParmWithTagFuncPtr GetParmWithTag; - static GetPartInfoFuncPtr GetPartInfo; - static GetPresetFuncPtr GetPreset; - static GetPresetBufLengthFuncPtr GetPresetBufLength; - static GetServerEnvIntFuncPtr GetServerEnvInt; - static GetServerEnvStringFuncPtr GetServerEnvString; - static GetServerEnvVarCountFuncPtr GetServerEnvVarCount; - static GetServerEnvVarListFuncPtr GetServerEnvVarList; - static GetSessionEnvIntFuncPtr GetSessionEnvInt; - static GetSessionSyncInfoFuncPtr GetSessionSyncInfo; - static GetSphereInfoFuncPtr GetSphereInfo; - static GetStatusFuncPtr GetStatus; - static GetStatusStringFuncPtr GetStatusString; - static GetStatusStringBufLengthFuncPtr GetStatusStringBufLength; - static GetStringFuncPtr GetString; - static GetStringBatchFuncPtr GetStringBatch; - static GetStringBatchSizeFuncPtr GetStringBatchSize; - static GetStringBufLengthFuncPtr GetStringBufLength; - static GetSupportedImageFileFormatCountFuncPtr GetSupportedImageFileFormatCount; - static GetSupportedImageFileFormatsFuncPtr GetSupportedImageFileFormats; - static GetTimeFuncPtr GetTime; - static GetTimelineOptionsFuncPtr GetTimelineOptions; - static GetTotalCookCountFuncPtr GetTotalCookCount; - static GetUseHoudiniTimeFuncPtr GetUseHoudiniTime; - static GetVertexListFuncPtr GetVertexList; - static GetViewportFuncPtr GetViewport; - static GetVolumeBoundsFuncPtr GetVolumeBounds; - static GetVolumeInfoFuncPtr GetVolumeInfo; - static GetVolumeTileFloatDataFuncPtr GetVolumeTileFloatData; - static GetVolumeTileIntDataFuncPtr GetVolumeTileIntData; - static GetVolumeVisualInfoFuncPtr GetVolumeVisualInfo; - static GetVolumeVoxelFloatDataFuncPtr GetVolumeVoxelFloatData; - static GetVolumeVoxelIntDataFuncPtr GetVolumeVoxelIntData; - static GetWorkitemDataLengthFuncPtr GetWorkitemDataLength; - static GetWorkitemFloatDataFuncPtr GetWorkitemFloatData; - static GetWorkitemInfoFuncPtr GetWorkitemInfo; - static GetWorkitemIntDataFuncPtr GetWorkitemIntData; - static GetWorkitemResultInfoFuncPtr GetWorkitemResultInfo; - static GetWorkitemStringDataFuncPtr GetWorkitemStringData; - static GetWorkitemsFuncPtr GetWorkitems; - static HandleBindingInfo_CreateFuncPtr HandleBindingInfo_Create; - static HandleBindingInfo_InitFuncPtr HandleBindingInfo_Init; - static HandleInfo_CreateFuncPtr HandleInfo_Create; - static HandleInfo_InitFuncPtr HandleInfo_Init; - static ImageFileFormat_CreateFuncPtr ImageFileFormat_Create; - static ImageFileFormat_InitFuncPtr ImageFileFormat_Init; - static ImageInfo_CreateFuncPtr ImageInfo_Create; - static ImageInfo_InitFuncPtr ImageInfo_Init; - static InitializeFuncPtr Initialize; - static InsertMultiparmInstanceFuncPtr InsertMultiparmInstance; - static InterruptFuncPtr Interrupt; - static IsInitializedFuncPtr IsInitialized; - static IsNodeValidFuncPtr IsNodeValid; - static IsSessionValidFuncPtr IsSessionValid; - static Keyframe_CreateFuncPtr Keyframe_Create; - static Keyframe_InitFuncPtr Keyframe_Init; - static LoadAssetLibraryFromFileFuncPtr LoadAssetLibraryFromFile; - static LoadAssetLibraryFromMemoryFuncPtr LoadAssetLibraryFromMemory; - static LoadGeoFromFileFuncPtr LoadGeoFromFile; - static LoadGeoFromMemoryFuncPtr LoadGeoFromMemory; - static LoadHIPFileFuncPtr LoadHIPFile; - static LoadNodeFromFileFuncPtr LoadNodeFromFile; - static MaterialInfo_CreateFuncPtr MaterialInfo_Create; - static MaterialInfo_InitFuncPtr MaterialInfo_Init; - static MergeHIPFileFuncPtr MergeHIPFile; - static NodeInfo_CreateFuncPtr NodeInfo_Create; - static NodeInfo_InitFuncPtr NodeInfo_Init; - static ObjectInfo_CreateFuncPtr ObjectInfo_Create; - static ObjectInfo_InitFuncPtr ObjectInfo_Init; - static ParmChoiceInfo_CreateFuncPtr ParmChoiceInfo_Create; - static ParmChoiceInfo_InitFuncPtr ParmChoiceInfo_Init; - static ParmHasExpressionFuncPtr ParmHasExpression; - static ParmHasTagFuncPtr ParmHasTag; - static ParmInfo_CreateFuncPtr ParmInfo_Create; - static ParmInfo_GetFloatValueCountFuncPtr ParmInfo_GetFloatValueCount; - static ParmInfo_GetIntValueCountFuncPtr ParmInfo_GetIntValueCount; - static ParmInfo_GetStringValueCountFuncPtr ParmInfo_GetStringValueCount; - static ParmInfo_InitFuncPtr ParmInfo_Init; - static ParmInfo_IsFloatFuncPtr ParmInfo_IsFloat; - static ParmInfo_IsIntFuncPtr ParmInfo_IsInt; - static ParmInfo_IsNodeFuncPtr ParmInfo_IsNode; - static ParmInfo_IsNonValueFuncPtr ParmInfo_IsNonValue; - static ParmInfo_IsPathFuncPtr ParmInfo_IsPath; - static ParmInfo_IsStringFuncPtr ParmInfo_IsString; - static PartInfo_CreateFuncPtr PartInfo_Create; - static PartInfo_GetAttributeCountByOwnerFuncPtr PartInfo_GetAttributeCountByOwner; - static PartInfo_GetElementCountByAttributeOwnerFuncPtr PartInfo_GetElementCountByAttributeOwner; - static PartInfo_GetElementCountByGroupTypeFuncPtr PartInfo_GetElementCountByGroupType; - static PartInfo_InitFuncPtr PartInfo_Init; - static PausePDGCookFuncPtr PausePDGCook; - static PythonThreadInterpreterLockFuncPtr PythonThreadInterpreterLock; - static QueryNodeInputFuncPtr QueryNodeInput; - static QueryNodeOutputConnectedCountFuncPtr QueryNodeOutputConnectedCount; - static QueryNodeOutputConnectedNodesFuncPtr QueryNodeOutputConnectedNodes; - static RemoveCustomStringFuncPtr RemoveCustomString; - static RemoveMultiparmInstanceFuncPtr RemoveMultiparmInstance; - static RemoveParmExpressionFuncPtr RemoveParmExpression; - static RenameNodeFuncPtr RenameNode; - static RenderCOPToImageFuncPtr RenderCOPToImage; - static RenderTextureToImageFuncPtr RenderTextureToImage; - static ResetSimulationFuncPtr ResetSimulation; - static RevertGeoFuncPtr RevertGeo; - static RevertParmToDefaultFuncPtr RevertParmToDefault; - static RevertParmToDefaultsFuncPtr RevertParmToDefaults; - static SaveGeoToFileFuncPtr SaveGeoToFile; - static SaveGeoToMemoryFuncPtr SaveGeoToMemory; - static SaveHIPFileFuncPtr SaveHIPFile; - static SaveNodeToFileFuncPtr SaveNodeToFile; - static SessionSyncInfo_CreateFuncPtr SessionSyncInfo_Create; - static SetAnimCurveFuncPtr SetAnimCurve; - static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; - static SetAttributeFloatDataFuncPtr SetAttributeFloatData; - static SetAttributeInt16DataFuncPtr SetAttributeInt16Data; - static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; - static SetAttributeInt8DataFuncPtr SetAttributeInt8Data; - static SetAttributeIntDataFuncPtr SetAttributeIntData; - static SetAttributeStringDataFuncPtr SetAttributeStringData; - static SetAttributeUInt8DataFuncPtr SetAttributeUInt8Data; - static SetCachePropertyFuncPtr SetCacheProperty; - static SetCompositorOptionsFuncPtr SetCompositorOptions; - static SetCurveCountsFuncPtr SetCurveCounts; - static SetCurveInfoFuncPtr SetCurveInfo; - static SetCurveKnotsFuncPtr SetCurveKnots; - static SetCurveOrdersFuncPtr SetCurveOrders; - static SetCustomStringFuncPtr SetCustomString; - static SetFaceCountsFuncPtr SetFaceCounts; - static SetGroupMembershipFuncPtr SetGroupMembership; - static SetHeightFieldDataFuncPtr SetHeightFieldData; - static SetImageInfoFuncPtr SetImageInfo; - static SetNodeDisplayFuncPtr SetNodeDisplay; - static SetObjectTransformFuncPtr SetObjectTransform; - static SetParmExpressionFuncPtr SetParmExpression; - static SetParmFloatValueFuncPtr SetParmFloatValue; - static SetParmFloatValuesFuncPtr SetParmFloatValues; - static SetParmIntValueFuncPtr SetParmIntValue; - static SetParmIntValuesFuncPtr SetParmIntValues; - static SetParmNodeValueFuncPtr SetParmNodeValue; - static SetParmStringValueFuncPtr SetParmStringValue; - static SetPartInfoFuncPtr SetPartInfo; - static SetPresetFuncPtr SetPreset; - static SetServerEnvIntFuncPtr SetServerEnvInt; - static SetServerEnvStringFuncPtr SetServerEnvString; - static SetSessionSyncFuncPtr SetSessionSync; - static SetSessionSyncInfoFuncPtr SetSessionSyncInfo; - static SetTimeFuncPtr SetTime; - static SetTimelineOptionsFuncPtr SetTimelineOptions; - static SetTransformAnimCurveFuncPtr SetTransformAnimCurve; - static SetUseHoudiniTimeFuncPtr SetUseHoudiniTime; - static SetVertexListFuncPtr SetVertexList; - static SetViewportFuncPtr SetViewport; - static SetVolumeInfoFuncPtr SetVolumeInfo; - static SetVolumeTileFloatDataFuncPtr SetVolumeTileFloatData; - static SetVolumeTileIntDataFuncPtr SetVolumeTileIntData; - static SetVolumeVoxelFloatDataFuncPtr SetVolumeVoxelFloatData; - static SetVolumeVoxelIntDataFuncPtr SetVolumeVoxelIntData; - static SetWorkitemFloatDataFuncPtr SetWorkitemFloatData; - static SetWorkitemIntDataFuncPtr SetWorkitemIntData; - static SetWorkitemStringDataFuncPtr SetWorkitemStringData; - static StartThriftNamedPipeServerFuncPtr StartThriftNamedPipeServer; - static StartThriftSocketServerFuncPtr StartThriftSocketServer; - static ThriftServerOptions_CreateFuncPtr ThriftServerOptions_Create; - static ThriftServerOptions_InitFuncPtr ThriftServerOptions_Init; - static TimelineOptions_CreateFuncPtr TimelineOptions_Create; - static TimelineOptions_InitFuncPtr TimelineOptions_Init; - static TransformEuler_CreateFuncPtr TransformEuler_Create; - static TransformEuler_InitFuncPtr TransformEuler_Init; - static Transform_CreateFuncPtr Transform_Create; - static Transform_InitFuncPtr Transform_Init; - static Viewport_CreateFuncPtr Viewport_Create; - static VolumeInfo_CreateFuncPtr VolumeInfo_Create; - static VolumeInfo_InitFuncPtr VolumeInfo_Init; - static VolumeTileInfo_CreateFuncPtr VolumeTileInfo_Create; - static VolumeTileInfo_InitFuncPtr VolumeTileInfo_Init; - -public: - - static HAPI_Result AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - static HAPI_Result AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - static HAPI_AssetInfo AssetInfo_CreateEmptyStub(); - static void AssetInfo_InitEmptyStub(HAPI_AssetInfo * in); - static HAPI_AttributeInfo AttributeInfo_CreateEmptyStub(); - static void AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in); - static HAPI_Result BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path); - static HAPI_Result CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - static HAPI_Result CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); - static HAPI_Result CleanupEmptyStub(const HAPI_Session * session); - static HAPI_Result ClearConnectionErrorEmptyStub(); - static HAPI_Result CloseSessionEmptyStub(const HAPI_Session * session); - static HAPI_Result CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - static HAPI_Result ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); - static HAPI_Result ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); - static HAPI_CompositorOptions CompositorOptions_CreateEmptyStub(); - static void CompositorOptions_InitEmptyStub(HAPI_CompositorOptions * in); - static HAPI_Result ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); - static HAPI_Result ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - static HAPI_Result ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); - static HAPI_Result ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); - static HAPI_Result ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); - static HAPI_Result ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); - static HAPI_Result CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); - static HAPI_Bool CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right); - static HAPI_CookOptions CookOptions_CreateEmptyStub(); - static void CookOptions_InitEmptyStub(HAPI_CookOptions * in); - static HAPI_Result CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); - static HAPI_Result CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); - static HAPI_Result CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); - static HAPI_Result CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); - static HAPI_Result CreateInProcessSessionEmptyStub(HAPI_Session * session); - static HAPI_Result CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); - static HAPI_Result CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); - static HAPI_Result CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name); - static HAPI_Result CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port); - static HAPI_Result CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); - static HAPI_CurveInfo CurveInfo_CreateEmptyStub(); - static void CurveInfo_InitEmptyStub(HAPI_CurveInfo * in); - static HAPI_Result DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); - static HAPI_Result DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); - static HAPI_Result DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); - static HAPI_Result DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); - static HAPI_Result DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); - static HAPI_Result ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); - static HAPI_Result ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); - static HAPI_GeoInfo GeoInfo_CreateEmptyStub(); - static int GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type); - static void GeoInfo_InitEmptyStub(HAPI_GeoInfo * in); - static HAPI_Result GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count); - static HAPI_Result GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); - static HAPI_Result GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); - static HAPI_Result GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); - static HAPI_Result GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); - static HAPI_Result GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); - static HAPI_Result GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); - static HAPI_Result GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); - static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); - static HAPI_Result GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); - static HAPI_Result GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); - static HAPI_Result GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); - static HAPI_Result GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); - static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); - static HAPI_Result GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); - static HAPI_Result GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); - static HAPI_Result GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); - static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); - static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); - static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); - static HAPI_Result GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); - static HAPI_Result GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); - static HAPI_Result GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length); - static HAPI_Result GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); - static HAPI_Result GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); - static HAPI_Result GetCompositorOptionsEmptyStub(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options); - static HAPI_Result GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear); - static HAPI_Result GetConnectionErrorLengthEmptyStub(int * buffer_length); - static HAPI_Result GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count); - static HAPI_Result GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count); - static HAPI_Result GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); - static HAPI_Result GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); - static HAPI_Result GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); - static HAPI_Result GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); - static HAPI_Result GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); - static HAPI_Result GetEdgeCountOfEdgeGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count); - static HAPI_Result GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value); - static HAPI_Result GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); - static HAPI_Result GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - static HAPI_Result GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); - static HAPI_Result GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); - static HAPI_Result GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); - static HAPI_Result GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - static HAPI_Result GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); - static HAPI_Result GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - static HAPI_Result GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); - static HAPI_Result GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count); - static HAPI_Result GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); - static HAPI_Result GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); - static HAPI_Result GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); - static HAPI_Result GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); - static HAPI_Result GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); - static HAPI_Result GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); - static HAPI_Result GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); - static HAPI_Result GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); - static HAPI_Result GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); - static HAPI_Result GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - static HAPI_Result GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); - static HAPI_Result GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); - static HAPI_Result GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); - static HAPI_Result GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); - static HAPI_Result GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); - static HAPI_Result GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); - static HAPI_Result GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); - static HAPI_Result GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); - static HAPI_Result GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); - static HAPI_Result GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); - static HAPI_Result GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); - static HAPI_Result GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num); - static HAPI_Result GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); - static HAPI_Result GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); - static HAPI_Result GetOutputGeoCountEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, int* count); - static HAPI_Result GetOutputGeoInfosEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count); - static HAPI_Result GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); - static HAPI_Result GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); - static HAPI_Result GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); - static HAPI_Result GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); - static HAPI_Result GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); - static HAPI_Result GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); - static HAPI_Result GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); - static HAPI_Result GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); - static HAPI_Result GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); - static HAPI_Result GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); - static HAPI_Result GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); - static HAPI_Result GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); - static HAPI_Result GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); - static HAPI_Result GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); - static HAPI_Result GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); - static HAPI_Result GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); - static HAPI_Result GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); - static HAPI_Result GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); - static HAPI_Result GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); - static HAPI_Result GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); - static HAPI_Result GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); - static HAPI_Result GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); - static HAPI_Result GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); - static HAPI_Result GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); - static HAPI_Result GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); - static HAPI_Result GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value); - static HAPI_Result GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); - static HAPI_Result GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count); - static HAPI_Result GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); - static HAPI_Result GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); - static HAPI_Result GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); - static HAPI_Result GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); - static HAPI_Result GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status); - static HAPI_Result GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); - static HAPI_Result GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); - static HAPI_Result GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); - static HAPI_Result GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length); - static HAPI_Result GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); - static HAPI_Result GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); - static HAPI_Result GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count); - static HAPI_Result GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); - static HAPI_Result GetTimeEmptyStub(const HAPI_Session * session, float * time); - static HAPI_Result GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); - static HAPI_Result GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); - static HAPI_Result GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled); - static HAPI_Result GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); - static HAPI_Result GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport); - static HAPI_Result GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); - static HAPI_Result GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); - static HAPI_Result GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); - static HAPI_Result GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); - static HAPI_Result GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); - static HAPI_Result GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); - static HAPI_Result GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); - static HAPI_Result GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); - static HAPI_Result GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); - static HAPI_Result GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); - static HAPI_Result GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); - static HAPI_Result GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); - static HAPI_Result GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); - static HAPI_Result GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); - static HAPI_HandleBindingInfo HandleBindingInfo_CreateEmptyStub(); - static void HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in); - static HAPI_HandleInfo HandleInfo_CreateEmptyStub(); - static void HandleInfo_InitEmptyStub(HAPI_HandleInfo * in); - static HAPI_ImageFileFormat ImageFileFormat_CreateEmptyStub(); - static void ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in); - static HAPI_ImageInfo ImageInfo_CreateEmptyStub(); - static void ImageInfo_InitEmptyStub(HAPI_ImageInfo * in); - static HAPI_Result InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); - static HAPI_Result InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - static HAPI_Result InterruptEmptyStub(const HAPI_Session * session); - static HAPI_Result IsInitializedEmptyStub(const HAPI_Session * session); - static HAPI_Result IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); - static HAPI_Result IsSessionValidEmptyStub(const HAPI_Session * session); - static HAPI_Keyframe Keyframe_CreateEmptyStub(); - static void Keyframe_InitEmptyStub(HAPI_Keyframe * in); - static HAPI_Result LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - static HAPI_Result LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); - static HAPI_Result LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_Result LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); - static HAPI_Result LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); - static HAPI_Result LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); - static HAPI_MaterialInfo MaterialInfo_CreateEmptyStub(); - static void MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in); - static HAPI_Result MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); - static HAPI_NodeInfo NodeInfo_CreateEmptyStub(); - static void NodeInfo_InitEmptyStub(HAPI_NodeInfo * in); - static HAPI_ObjectInfo ObjectInfo_CreateEmptyStub(); - static void ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in); - static HAPI_ParmChoiceInfo ParmChoiceInfo_CreateEmptyStub(); - static void ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in); - static HAPI_Result ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); - static HAPI_Result ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); - static HAPI_ParmInfo ParmInfo_CreateEmptyStub(); - static int ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in); - static int ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in); - static int ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in); - static void ParmInfo_InitEmptyStub(HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in); - static HAPI_Bool ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in); - static HAPI_PartInfo PartInfo_CreateEmptyStub(); - static int PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - static int PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); - static int PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type); - static void PartInfo_InitEmptyStub(HAPI_PartInfo * in); - static HAPI_Result PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); - static HAPI_Result PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked); - static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); - static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); - static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); - static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle); - static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); - static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); - static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); - static HAPI_Result RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id); - static HAPI_Result RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); - static HAPI_Result ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); - static HAPI_Result RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); - static HAPI_Result RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); - static HAPI_Result SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_Result SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); - static HAPI_Result SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); - static HAPI_Result SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); - static HAPI_SessionSyncInfo SessionSyncInfo_CreateEmptyStub(); - static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); - static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); - static HAPI_Result SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); - static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); - static HAPI_Result SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); - static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); - static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); - static HAPI_Result SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); - static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); - static HAPI_Result SetCompositorOptionsEmptyStub(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options); - static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); - static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); - static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); - static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); - static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); - static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); - static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); - static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); - static HAPI_Result SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); - static HAPI_Result SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); - static HAPI_Result SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); - static HAPI_Result SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - static HAPI_Result SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); - static HAPI_Result SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); - static HAPI_Result SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); - static HAPI_Result SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); - static HAPI_Result SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); - static HAPI_Result SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); - static HAPI_Result SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); - static HAPI_Result SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); - static HAPI_Result SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value); - static HAPI_Result SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value); - static HAPI_Result SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable); - static HAPI_Result SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); - static HAPI_Result SetTimeEmptyStub(const HAPI_Session * session, float time); - static HAPI_Result SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); - static HAPI_Result SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); - static HAPI_Result SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled); - static HAPI_Result SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); - static HAPI_Result SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport); - static HAPI_Result SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); - static HAPI_Result SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); - static HAPI_Result SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); - static HAPI_Result SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); - static HAPI_Result SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); - static HAPI_Result SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); - static HAPI_Result SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); - static HAPI_Result SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); - static HAPI_Result StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); - static HAPI_Result StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); - static HAPI_ThriftServerOptions ThriftServerOptions_CreateEmptyStub(); - static void ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in); - static HAPI_TimelineOptions TimelineOptions_CreateEmptyStub(); - static void TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in); - static HAPI_TransformEuler TransformEuler_CreateEmptyStub(); - static void TransformEuler_InitEmptyStub(HAPI_TransformEuler * in); - static HAPI_Transform Transform_CreateEmptyStub(); - static void Transform_InitEmptyStub(HAPI_Transform * in); - static HAPI_Viewport Viewport_CreateEmptyStub(); - static HAPI_VolumeInfo VolumeInfo_CreateEmptyStub(); - static void VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in); - static HAPI_VolumeTileInfo VolumeTileInfo_CreateEmptyStub(); - static void VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in); -}; +/* + * Copyright (c) <2021> 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. + * + * + * COMMENTS: + * This file is generated. Do not modify directly. + */ + +#pragma once +#include "HAPI/HAPI.h" +#include "HAL/PlatformProcess.h" + + +struct HOUDINIENGINE_API FHoudiniApi +{ +public: + + static void InitializeHAPI(void* LibraryHandle); + static void FinalizeHAPI(); + static bool IsHAPIInitialized(); + +public: + + typedef HAPI_Result (*AddAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*AddGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + typedef HAPI_AssetInfo (*AssetInfo_CreateFuncPtr)(); + typedef void (*AssetInfo_InitFuncPtr)(HAPI_AssetInfo * in); + typedef HAPI_AttributeInfo (*AttributeInfo_CreateFuncPtr)(); + typedef void (*AttributeInfo_InitFuncPtr)(HAPI_AttributeInfo * in); + typedef HAPI_Result (*BindCustomImplementationFuncPtr)(HAPI_SessionType session_type, const char * dll_path); + typedef HAPI_Result (*CancelPDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + typedef HAPI_Result (*CheckForSpecificErrorsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); + typedef HAPI_Result (*CleanupFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*ClearConnectionErrorFuncPtr)(); + typedef HAPI_Result (*CloseSessionFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*CommitGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*CommitWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*ComposeChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + typedef HAPI_Result (*ComposeNodeCookResultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); + typedef HAPI_Result (*ComposeObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + typedef HAPI_CompositorOptions (*CompositorOptions_CreateFuncPtr)(); + typedef void (*CompositorOptions_InitFuncPtr)(HAPI_CompositorOptions * in); + typedef HAPI_Result (*ConnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); + typedef HAPI_Result (*ConvertMatrixToEulerFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + typedef HAPI_Result (*ConvertMatrixToQuatFuncPtr)(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); + typedef HAPI_Result (*ConvertTransformFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + typedef HAPI_Result (*ConvertTransformEulerToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); + typedef HAPI_Result (*ConvertTransformQuatToMatrixFuncPtr)(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); + typedef HAPI_Result (*CookNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); + typedef HAPI_Bool (*CookOptions_AreEqualFuncPtr)(const HAPI_CookOptions * left, const HAPI_CookOptions * right); + typedef HAPI_CookOptions (*CookOptions_CreateFuncPtr)(); + typedef void (*CookOptions_InitFuncPtr)(HAPI_CookOptions * in); + typedef HAPI_Result (*CookPDGFuncPtr)(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); + typedef HAPI_Result (*CreateCustomSessionFuncPtr)(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); + typedef HAPI_Result (*CreateHeightFieldInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); + typedef HAPI_Result (*CreateHeightfieldInputVolumeNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); + typedef HAPI_Result (*CreateInProcessSessionFuncPtr)(HAPI_Session * session); + typedef HAPI_Result (*CreateInputNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); + typedef HAPI_Result (*CreateNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); + typedef HAPI_Result (*CreateThriftNamedPipeSessionFuncPtr)(HAPI_Session * session, const char * pipe_name); + typedef HAPI_Result (*CreateThriftSocketSessionFuncPtr)(HAPI_Session * session, const char * host_name, int port); + typedef HAPI_Result (*CreateWorkitemFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); + typedef HAPI_CurveInfo (*CurveInfo_CreateFuncPtr)(); + typedef void (*CurveInfo_InitFuncPtr)(HAPI_CurveInfo * in); + typedef HAPI_Result (*DeleteAttributeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*DeleteGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + typedef HAPI_Result (*DeleteNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*DirtyPDGNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); + typedef HAPI_Result (*DisconnectNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); + typedef HAPI_Result (*DisconnectNodeOutputsAtFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); + typedef HAPI_Result (*ExtractImageToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); + typedef HAPI_Result (*ExtractImageToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); + typedef HAPI_GeoInfo (*GeoInfo_CreateFuncPtr)(); + typedef int (*GeoInfo_GetGroupCountByTypeFuncPtr)(HAPI_GeoInfo * in, HAPI_GroupType type); + typedef void (*GeoInfo_InitFuncPtr)(HAPI_GeoInfo * in); + typedef HAPI_Result (*GetActiveCacheCountFuncPtr)(const HAPI_Session * session, int * active_cache_count); + typedef HAPI_Result (*GetActiveCacheNamesFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); + typedef HAPI_Result (*GetAssetDefinitionParmCountsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); + typedef HAPI_Result (*GetAssetDefinitionParmInfosFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); + typedef HAPI_Result (*GetAssetDefinitionParmValuesFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); + typedef HAPI_Result (*GetAssetInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); + typedef HAPI_Result (*GetAttributeFloat64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeFloatArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + typedef HAPI_Result (*GetAttributeInt16ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInt64ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeIntArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); + typedef HAPI_Result (*GetAttributeStringArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + typedef HAPI_Result (*GetAttributeUInt8ArrayDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + typedef HAPI_Result (*GetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); + typedef HAPI_Result (*GetAvailableAssetCountFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); + typedef HAPI_Result (*GetAvailableAssetsFuncPtr)(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); + typedef HAPI_Result (*GetBoxInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); + typedef HAPI_Result (*GetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); + typedef HAPI_Result (*GetComposedChildNodeListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); + typedef HAPI_Result (*GetComposedNodeCookResultFuncPtr)(const HAPI_Session * session, char * string_value, int length); + typedef HAPI_Result (*GetComposedObjectListFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); + typedef HAPI_Result (*GetComposedObjectTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + typedef HAPI_Result (*GetCompositorOptionsFuncPtr)(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options); + typedef HAPI_Result (*GetConnectionErrorFuncPtr)(char * string_value, int length, HAPI_Bool clear); + typedef HAPI_Result (*GetConnectionErrorLengthFuncPtr)(int * buffer_length); + typedef HAPI_Result (*GetCookingCurrentCountFuncPtr)(const HAPI_Session * session, int * count); + typedef HAPI_Result (*GetCookingTotalCountFuncPtr)(const HAPI_Session * session, int * count); + typedef HAPI_Result (*GetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); + typedef HAPI_Result (*GetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); + typedef HAPI_Result (*GetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); + typedef HAPI_Result (*GetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); + typedef HAPI_Result (*GetDisplayGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetEdgeCountOfEdgeGroupFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count); + typedef HAPI_Result (*GetEnvIntFuncPtr)(HAPI_EnvIntType int_type, int * value); + typedef HAPI_Result (*GetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); + typedef HAPI_Result (*GetFirstVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + typedef HAPI_Result (*GetGeoInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); + typedef HAPI_Result (*GetGeoSizeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); + typedef HAPI_Result (*GetGroupCountOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); + typedef HAPI_Result (*GetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + typedef HAPI_Result (*GetGroupMembershipOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + typedef HAPI_Result (*GetGroupNamesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + typedef HAPI_Result (*GetGroupNamesOnPackedInstancePartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + typedef HAPI_Result (*GetHIPFileNodeCountFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, int * count); + typedef HAPI_Result (*GetHIPFileNodeIdsFuncPtr)(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); + typedef HAPI_Result (*GetHandleBindingInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); + typedef HAPI_Result (*GetHandleInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); + typedef HAPI_Result (*GetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); + typedef HAPI_Result (*GetImageFilePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); + typedef HAPI_Result (*GetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); + typedef HAPI_Result (*GetImageMemoryBufferFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); + typedef HAPI_Result (*GetImagePlaneCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); + typedef HAPI_Result (*GetImagePlanesFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); + typedef HAPI_Result (*GetInstanceTransformsOnPartFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + typedef HAPI_Result (*GetInstancedObjectIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); + typedef HAPI_Result (*GetInstancedPartIdsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); + typedef HAPI_Result (*GetInstancerPartTransformsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + typedef HAPI_Result (*GetManagerNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); + typedef HAPI_Result (*GetMaterialInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); + typedef HAPI_Result (*GetMaterialNodeIdsOnFacesFuncPtr)(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); + typedef HAPI_Result (*GetNextVolumeTileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + typedef HAPI_Result (*GetNodeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); + typedef HAPI_Result (*GetNodeInputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); + typedef HAPI_Result (*GetNodeOutputNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); + typedef HAPI_Result (*GetNodePathFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); + typedef HAPI_Result (*GetNumWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * num); + typedef HAPI_Result (*GetObjectInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); + typedef HAPI_Result (*GetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + typedef HAPI_Result (*GetOutputGeoCountFuncPtr)(const HAPI_Session* session, HAPI_NodeId node_id, int* count); + typedef HAPI_Result (*GetOutputGeoInfosFuncPtr)(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count); + typedef HAPI_Result (*GetOutputNodeIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); + typedef HAPI_Result (*GetPDGEventsFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); + typedef HAPI_Result (*GetPDGGraphContextIdFuncPtr)(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); + typedef HAPI_Result (*GetPDGGraphContextsFuncPtr)(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); + typedef HAPI_Result (*GetPDGStateFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); + typedef HAPI_Result (*GetParametersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); + typedef HAPI_Result (*GetParmChoiceListsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); + typedef HAPI_Result (*GetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); + typedef HAPI_Result (*GetParmFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); + typedef HAPI_Result (*GetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); + typedef HAPI_Result (*GetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); + typedef HAPI_Result (*GetParmIdFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); + typedef HAPI_Result (*GetParmInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); + typedef HAPI_Result (*GetParmInfoFromNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); + typedef HAPI_Result (*GetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); + typedef HAPI_Result (*GetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); + typedef HAPI_Result (*GetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); + typedef HAPI_Result (*GetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); + typedef HAPI_Result (*GetParmStringValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); + typedef HAPI_Result (*GetParmTagNameFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); + typedef HAPI_Result (*GetParmTagValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); + typedef HAPI_Result (*GetParmWithTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); + typedef HAPI_Result (*GetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); + typedef HAPI_Result (*GetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); + typedef HAPI_Result (*GetPresetBufLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); + typedef HAPI_Result (*GetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int * value); + typedef HAPI_Result (*GetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); + typedef HAPI_Result (*GetServerEnvVarCountFuncPtr)(const HAPI_Session * session, int * env_count); + typedef HAPI_Result (*GetServerEnvVarListFuncPtr)(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); + typedef HAPI_Result (*GetSessionEnvIntFuncPtr)(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); + typedef HAPI_Result (*GetSessionSyncInfoFuncPtr)(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); + typedef HAPI_Result (*GetSphereInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); + typedef HAPI_Result (*GetStatusFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, int * status); + typedef HAPI_Result (*GetStatusStringFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); + typedef HAPI_Result (*GetStatusStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); + typedef HAPI_Result (*GetStringFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); + typedef HAPI_Result (*GetStringBatchFuncPtr)(const HAPI_Session * session, char * char_buffer, int char_array_length); + typedef HAPI_Result (*GetStringBatchSizeFuncPtr)(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); + typedef HAPI_Result (*GetStringBufLengthFuncPtr)(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); + typedef HAPI_Result (*GetSupportedImageFileFormatCountFuncPtr)(const HAPI_Session * session, int * file_format_count); + typedef HAPI_Result (*GetSupportedImageFileFormatsFuncPtr)(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); + typedef HAPI_Result (*GetTimeFuncPtr)(const HAPI_Session * session, float * time); + typedef HAPI_Result (*GetTimelineOptionsFuncPtr)(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); + typedef HAPI_Result (*GetTotalCookCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + typedef HAPI_Result (*GetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool * enabled); + typedef HAPI_Result (*GetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); + typedef HAPI_Result (*GetViewportFuncPtr)(const HAPI_Session * session, HAPI_Viewport * viewport); + typedef HAPI_Result (*GetVolumeBoundsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); + typedef HAPI_Result (*GetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); + typedef HAPI_Result (*GetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); + typedef HAPI_Result (*GetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); + typedef HAPI_Result (*GetVolumeVisualInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); + typedef HAPI_Result (*GetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); + typedef HAPI_Result (*GetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); + typedef HAPI_Result (*GetWorkitemDataLengthFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); + typedef HAPI_Result (*GetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); + typedef HAPI_Result (*GetWorkitemInfoFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); + typedef HAPI_Result (*GetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); + typedef HAPI_Result (*GetWorkitemResultInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); + typedef HAPI_Result (*GetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); + typedef HAPI_Result (*GetWorkitemsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); + typedef HAPI_HandleBindingInfo (*HandleBindingInfo_CreateFuncPtr)(); + typedef void (*HandleBindingInfo_InitFuncPtr)(HAPI_HandleBindingInfo * in); + typedef HAPI_HandleInfo (*HandleInfo_CreateFuncPtr)(); + typedef void (*HandleInfo_InitFuncPtr)(HAPI_HandleInfo * in); + typedef HAPI_ImageFileFormat (*ImageFileFormat_CreateFuncPtr)(); + typedef void (*ImageFileFormat_InitFuncPtr)(HAPI_ImageFileFormat *in); + typedef HAPI_ImageInfo (*ImageInfo_CreateFuncPtr)(); + typedef void (*ImageInfo_InitFuncPtr)(HAPI_ImageInfo * in); + typedef HAPI_Result (*InitializeFuncPtr)(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); + typedef HAPI_Result (*InsertMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + typedef HAPI_Result (*InterruptFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*IsInitializedFuncPtr)(const HAPI_Session * session); + typedef HAPI_Result (*IsNodeValidFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); + typedef HAPI_Result (*IsSessionValidFuncPtr)(const HAPI_Session * session); + typedef HAPI_Keyframe (*Keyframe_CreateFuncPtr)(); + typedef void (*Keyframe_InitFuncPtr)(HAPI_Keyframe * in); + typedef HAPI_Result (*LoadAssetLibraryFromFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + typedef HAPI_Result (*LoadAssetLibraryFromMemoryFuncPtr)(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + typedef HAPI_Result (*LoadGeoFromFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_Result (*LoadGeoFromMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); + typedef HAPI_Result (*LoadHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); + typedef HAPI_Result (*LoadNodeFromFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); + typedef HAPI_MaterialInfo (*MaterialInfo_CreateFuncPtr)(); + typedef void (*MaterialInfo_InitFuncPtr)(HAPI_MaterialInfo * in); + typedef HAPI_Result (*MergeHIPFileFuncPtr)(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); + typedef HAPI_NodeInfo (*NodeInfo_CreateFuncPtr)(); + typedef void (*NodeInfo_InitFuncPtr)(HAPI_NodeInfo * in); + typedef HAPI_ObjectInfo (*ObjectInfo_CreateFuncPtr)(); + typedef void (*ObjectInfo_InitFuncPtr)(HAPI_ObjectInfo * in); + typedef HAPI_ParmChoiceInfo (*ParmChoiceInfo_CreateFuncPtr)(); + typedef void (*ParmChoiceInfo_InitFuncPtr)(HAPI_ParmChoiceInfo * in); + typedef HAPI_Result (*ParmHasExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); + typedef HAPI_Result (*ParmHasTagFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); + typedef HAPI_ParmInfo (*ParmInfo_CreateFuncPtr)(); + typedef int (*ParmInfo_GetFloatValueCountFuncPtr)(const HAPI_ParmInfo * in); + typedef int (*ParmInfo_GetIntValueCountFuncPtr)(const HAPI_ParmInfo * in); + typedef int (*ParmInfo_GetStringValueCountFuncPtr)(const HAPI_ParmInfo* in); + typedef void (*ParmInfo_InitFuncPtr)(HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsFloatFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsIntFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsNodeFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsNonValueFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsPathFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_Bool (*ParmInfo_IsStringFuncPtr)(const HAPI_ParmInfo * in); + typedef HAPI_PartInfo (*PartInfo_CreateFuncPtr)(); + typedef int (*PartInfo_GetAttributeCountByOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + typedef int (*PartInfo_GetElementCountByAttributeOwnerFuncPtr)(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + typedef int (*PartInfo_GetElementCountByGroupTypeFuncPtr)(HAPI_PartInfo * in, HAPI_GroupType type); + typedef void (*PartInfo_InitFuncPtr)(HAPI_PartInfo * in); + typedef HAPI_Result (*PausePDGCookFuncPtr)(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + typedef HAPI_Result (*PythonThreadInterpreterLockFuncPtr)(const HAPI_Session * session, HAPI_Bool locked); + typedef HAPI_Result (*QueryNodeInputFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); + typedef HAPI_Result (*QueryNodeOutputConnectedCountFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); + typedef HAPI_Result (*QueryNodeOutputConnectedNodesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); + typedef HAPI_Result (*RemoveCustomStringFuncPtr)(const HAPI_Session * session, const HAPI_StringHandle string_handle); + typedef HAPI_Result (*RemoveMultiparmInstanceFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + typedef HAPI_Result (*RemoveParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*RenameNodeFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); + typedef HAPI_Result (*RenderCOPToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId cop_node_id); + typedef HAPI_Result (*RenderTextureToImageFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); + typedef HAPI_Result (*ResetSimulationFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*RevertGeoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id); + typedef HAPI_Result (*RevertParmToDefaultFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); + typedef HAPI_Result (*RevertParmToDefaultsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); + typedef HAPI_Result (*SaveGeoToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_Result (*SaveGeoToMemoryFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); + typedef HAPI_Result (*SaveHIPFileFuncPtr)(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); + typedef HAPI_Result (*SaveNodeToFileFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + typedef HAPI_SessionSyncInfo (*SessionSyncInfo_CreateFuncPtr)(); + typedef HAPI_Result (*SetAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + typedef HAPI_Result (*SetAttributeFloat64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt16DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt64DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); + typedef HAPI_Result (*SetAttributeStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + typedef HAPI_Result (*SetAttributeUInt8DataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); + typedef HAPI_Result (*SetCachePropertyFuncPtr)(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + typedef HAPI_Result (*SetCompositorOptionsFuncPtr)(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options); + typedef HAPI_Result (*SetCurveCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); + typedef HAPI_Result (*SetCurveInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); + typedef HAPI_Result (*SetCurveKnotsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); + typedef HAPI_Result (*SetCurveOrdersFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); + typedef HAPI_Result (*SetCustomStringFuncPtr)(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); + typedef HAPI_Result (*SetFaceCountsFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); + typedef HAPI_Result (*SetGroupMembershipFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); + typedef HAPI_Result (*SetHeightFieldDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); + typedef HAPI_Result (*SetImageInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); + typedef HAPI_Result (*SetNodeDisplayFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); + typedef HAPI_Result (*SetObjectTransformFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); + typedef HAPI_Result (*SetParmExpressionFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*SetParmFloatValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); + typedef HAPI_Result (*SetParmFloatValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); + typedef HAPI_Result (*SetParmIntValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); + typedef HAPI_Result (*SetParmIntValuesFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); + typedef HAPI_Result (*SetParmNodeValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); + typedef HAPI_Result (*SetParmStringValueFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + typedef HAPI_Result (*SetPartInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); + typedef HAPI_Result (*SetPresetFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); + typedef HAPI_Result (*SetServerEnvIntFuncPtr)(const HAPI_Session * session, const char * variable_name, int value); + typedef HAPI_Result (*SetServerEnvStringFuncPtr)(const HAPI_Session * session, const char * variable_name, const char * value); + typedef HAPI_Result (*SetSessionSyncFuncPtr)(const HAPI_Session * session, HAPI_Bool enable); + typedef HAPI_Result (*SetSessionSyncInfoFuncPtr)(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); + typedef HAPI_Result (*SetTimeFuncPtr)(const HAPI_Session * session, float time); + typedef HAPI_Result (*SetTimelineOptionsFuncPtr)(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); + typedef HAPI_Result (*SetTransformAnimCurveFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + typedef HAPI_Result (*SetUseHoudiniTimeFuncPtr)(const HAPI_Session * session, HAPI_Bool enabled); + typedef HAPI_Result (*SetVertexListFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); + typedef HAPI_Result (*SetViewportFuncPtr)(const HAPI_Session * session, const HAPI_Viewport * viewport); + typedef HAPI_Result (*SetVolumeInfoFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); + typedef HAPI_Result (*SetVolumeTileFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); + typedef HAPI_Result (*SetVolumeTileIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); + typedef HAPI_Result (*SetVolumeVoxelFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); + typedef HAPI_Result (*SetVolumeVoxelIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); + typedef HAPI_Result (*SetWorkitemFloatDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); + typedef HAPI_Result (*SetWorkitemIntDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); + typedef HAPI_Result (*SetWorkitemStringDataFuncPtr)(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); + typedef HAPI_Result (*StartThriftNamedPipeServerFuncPtr)(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); + typedef HAPI_Result (*StartThriftSocketServerFuncPtr)(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); + typedef HAPI_ThriftServerOptions (*ThriftServerOptions_CreateFuncPtr)(); + typedef void (*ThriftServerOptions_InitFuncPtr)(HAPI_ThriftServerOptions * in); + typedef HAPI_TimelineOptions (*TimelineOptions_CreateFuncPtr)(); + typedef void (*TimelineOptions_InitFuncPtr)(HAPI_TimelineOptions * in); + typedef HAPI_TransformEuler (*TransformEuler_CreateFuncPtr)(); + typedef void (*TransformEuler_InitFuncPtr)(HAPI_TransformEuler * in); + typedef HAPI_Transform (*Transform_CreateFuncPtr)(); + typedef void (*Transform_InitFuncPtr)(HAPI_Transform * in); + typedef HAPI_Viewport (*Viewport_CreateFuncPtr)(); + typedef HAPI_VolumeInfo (*VolumeInfo_CreateFuncPtr)(); + typedef void (*VolumeInfo_InitFuncPtr)(HAPI_VolumeInfo * in); + typedef HAPI_VolumeTileInfo (*VolumeTileInfo_CreateFuncPtr)(); + typedef void (*VolumeTileInfo_InitFuncPtr)(HAPI_VolumeTileInfo * in); + +public: + + static AddAttributeFuncPtr AddAttribute; + static AddGroupFuncPtr AddGroup; + static AssetInfo_CreateFuncPtr AssetInfo_Create; + static AssetInfo_InitFuncPtr AssetInfo_Init; + static AttributeInfo_CreateFuncPtr AttributeInfo_Create; + static AttributeInfo_InitFuncPtr AttributeInfo_Init; + static BindCustomImplementationFuncPtr BindCustomImplementation; + static CancelPDGCookFuncPtr CancelPDGCook; + static CheckForSpecificErrorsFuncPtr CheckForSpecificErrors; + static CleanupFuncPtr Cleanup; + static ClearConnectionErrorFuncPtr ClearConnectionError; + static CloseSessionFuncPtr CloseSession; + static CommitGeoFuncPtr CommitGeo; + static CommitWorkitemsFuncPtr CommitWorkitems; + static ComposeChildNodeListFuncPtr ComposeChildNodeList; + static ComposeNodeCookResultFuncPtr ComposeNodeCookResult; + static ComposeObjectListFuncPtr ComposeObjectList; + static CompositorOptions_CreateFuncPtr CompositorOptions_Create; + static CompositorOptions_InitFuncPtr CompositorOptions_Init; + static ConnectNodeInputFuncPtr ConnectNodeInput; + static ConvertMatrixToEulerFuncPtr ConvertMatrixToEuler; + static ConvertMatrixToQuatFuncPtr ConvertMatrixToQuat; + static ConvertTransformFuncPtr ConvertTransform; + static ConvertTransformEulerToMatrixFuncPtr ConvertTransformEulerToMatrix; + static ConvertTransformQuatToMatrixFuncPtr ConvertTransformQuatToMatrix; + static CookNodeFuncPtr CookNode; + static CookOptions_AreEqualFuncPtr CookOptions_AreEqual; + static CookOptions_CreateFuncPtr CookOptions_Create; + static CookOptions_InitFuncPtr CookOptions_Init; + static CookPDGFuncPtr CookPDG; + static CreateCustomSessionFuncPtr CreateCustomSession; + static CreateHeightFieldInputFuncPtr CreateHeightFieldInput; + static CreateHeightfieldInputVolumeNodeFuncPtr CreateHeightfieldInputVolumeNode; + static CreateInProcessSessionFuncPtr CreateInProcessSession; + static CreateInputNodeFuncPtr CreateInputNode; + static CreateNodeFuncPtr CreateNode; + static CreateThriftNamedPipeSessionFuncPtr CreateThriftNamedPipeSession; + static CreateThriftSocketSessionFuncPtr CreateThriftSocketSession; + static CreateWorkitemFuncPtr CreateWorkitem; + static CurveInfo_CreateFuncPtr CurveInfo_Create; + static CurveInfo_InitFuncPtr CurveInfo_Init; + static DeleteAttributeFuncPtr DeleteAttribute; + static DeleteGroupFuncPtr DeleteGroup; + static DeleteNodeFuncPtr DeleteNode; + static DirtyPDGNodeFuncPtr DirtyPDGNode; + static DisconnectNodeInputFuncPtr DisconnectNodeInput; + static DisconnectNodeOutputsAtFuncPtr DisconnectNodeOutputsAt; + static ExtractImageToFileFuncPtr ExtractImageToFile; + static ExtractImageToMemoryFuncPtr ExtractImageToMemory; + static GeoInfo_CreateFuncPtr GeoInfo_Create; + static GeoInfo_GetGroupCountByTypeFuncPtr GeoInfo_GetGroupCountByType; + static GeoInfo_InitFuncPtr GeoInfo_Init; + static GetActiveCacheCountFuncPtr GetActiveCacheCount; + static GetActiveCacheNamesFuncPtr GetActiveCacheNames; + static GetAssetDefinitionParmCountsFuncPtr GetAssetDefinitionParmCounts; + static GetAssetDefinitionParmInfosFuncPtr GetAssetDefinitionParmInfos; + static GetAssetDefinitionParmValuesFuncPtr GetAssetDefinitionParmValues; + static GetAssetInfoFuncPtr GetAssetInfo; + static GetAttributeFloat64ArrayDataFuncPtr GetAttributeFloat64ArrayData; + static GetAttributeFloat64DataFuncPtr GetAttributeFloat64Data; + static GetAttributeFloatArrayDataFuncPtr GetAttributeFloatArrayData; + static GetAttributeFloatDataFuncPtr GetAttributeFloatData; + static GetAttributeInfoFuncPtr GetAttributeInfo; + static GetAttributeInt16ArrayDataFuncPtr GetAttributeInt16ArrayData; + static GetAttributeInt16DataFuncPtr GetAttributeInt16Data; + static GetAttributeInt64ArrayDataFuncPtr GetAttributeInt64ArrayData; + static GetAttributeInt64DataFuncPtr GetAttributeInt64Data; + static GetAttributeInt8ArrayDataFuncPtr GetAttributeInt8ArrayData; + static GetAttributeInt8DataFuncPtr GetAttributeInt8Data; + static GetAttributeIntArrayDataFuncPtr GetAttributeIntArrayData; + static GetAttributeIntDataFuncPtr GetAttributeIntData; + static GetAttributeNamesFuncPtr GetAttributeNames; + static GetAttributeStringArrayDataFuncPtr GetAttributeStringArrayData; + static GetAttributeStringDataFuncPtr GetAttributeStringData; + static GetAttributeUInt8ArrayDataFuncPtr GetAttributeUInt8ArrayData; + static GetAttributeUInt8DataFuncPtr GetAttributeUInt8Data; + static GetAvailableAssetCountFuncPtr GetAvailableAssetCount; + static GetAvailableAssetsFuncPtr GetAvailableAssets; + static GetBoxInfoFuncPtr GetBoxInfo; + static GetCachePropertyFuncPtr GetCacheProperty; + static GetComposedChildNodeListFuncPtr GetComposedChildNodeList; + static GetComposedNodeCookResultFuncPtr GetComposedNodeCookResult; + static GetComposedObjectListFuncPtr GetComposedObjectList; + static GetComposedObjectTransformsFuncPtr GetComposedObjectTransforms; + static GetCompositorOptionsFuncPtr GetCompositorOptions; + static GetConnectionErrorFuncPtr GetConnectionError; + static GetConnectionErrorLengthFuncPtr GetConnectionErrorLength; + static GetCookingCurrentCountFuncPtr GetCookingCurrentCount; + static GetCookingTotalCountFuncPtr GetCookingTotalCount; + static GetCurveCountsFuncPtr GetCurveCounts; + static GetCurveInfoFuncPtr GetCurveInfo; + static GetCurveKnotsFuncPtr GetCurveKnots; + static GetCurveOrdersFuncPtr GetCurveOrders; + static GetDisplayGeoInfoFuncPtr GetDisplayGeoInfo; + static GetEdgeCountOfEdgeGroupFuncPtr GetEdgeCountOfEdgeGroup; + static GetEnvIntFuncPtr GetEnvInt; + static GetFaceCountsFuncPtr GetFaceCounts; + static GetFirstVolumeTileFuncPtr GetFirstVolumeTile; + static GetGeoInfoFuncPtr GetGeoInfo; + static GetGeoSizeFuncPtr GetGeoSize; + static GetGroupCountOnPackedInstancePartFuncPtr GetGroupCountOnPackedInstancePart; + static GetGroupMembershipFuncPtr GetGroupMembership; + static GetGroupMembershipOnPackedInstancePartFuncPtr GetGroupMembershipOnPackedInstancePart; + static GetGroupNamesFuncPtr GetGroupNames; + static GetGroupNamesOnPackedInstancePartFuncPtr GetGroupNamesOnPackedInstancePart; + static GetHIPFileNodeCountFuncPtr GetHIPFileNodeCount; + static GetHIPFileNodeIdsFuncPtr GetHIPFileNodeIds; + static GetHandleBindingInfoFuncPtr GetHandleBindingInfo; + static GetHandleInfoFuncPtr GetHandleInfo; + static GetHeightFieldDataFuncPtr GetHeightFieldData; + static GetImageFilePathFuncPtr GetImageFilePath; + static GetImageInfoFuncPtr GetImageInfo; + static GetImageMemoryBufferFuncPtr GetImageMemoryBuffer; + static GetImagePlaneCountFuncPtr GetImagePlaneCount; + static GetImagePlanesFuncPtr GetImagePlanes; + static GetInstanceTransformsOnPartFuncPtr GetInstanceTransformsOnPart; + static GetInstancedObjectIdsFuncPtr GetInstancedObjectIds; + static GetInstancedPartIdsFuncPtr GetInstancedPartIds; + static GetInstancerPartTransformsFuncPtr GetInstancerPartTransforms; + static GetManagerNodeIdFuncPtr GetManagerNodeId; + static GetMaterialInfoFuncPtr GetMaterialInfo; + static GetMaterialNodeIdsOnFacesFuncPtr GetMaterialNodeIdsOnFaces; + static GetNextVolumeTileFuncPtr GetNextVolumeTile; + static GetNodeInfoFuncPtr GetNodeInfo; + static GetNodeInputNameFuncPtr GetNodeInputName; + static GetNodeOutputNameFuncPtr GetNodeOutputName; + static GetNodePathFuncPtr GetNodePath; + static GetNumWorkitemsFuncPtr GetNumWorkitems; + static GetObjectInfoFuncPtr GetObjectInfo; + static GetObjectTransformFuncPtr GetObjectTransform; + static GetOutputGeoCountFuncPtr GetOutputGeoCount; + static GetOutputGeoInfosFuncPtr GetOutputGeoInfos; + static GetOutputNodeIdFuncPtr GetOutputNodeId; + static GetPDGEventsFuncPtr GetPDGEvents; + static GetPDGGraphContextIdFuncPtr GetPDGGraphContextId; + static GetPDGGraphContextsFuncPtr GetPDGGraphContexts; + static GetPDGStateFuncPtr GetPDGState; + static GetParametersFuncPtr GetParameters; + static GetParmChoiceListsFuncPtr GetParmChoiceLists; + static GetParmExpressionFuncPtr GetParmExpression; + static GetParmFileFuncPtr GetParmFile; + static GetParmFloatValueFuncPtr GetParmFloatValue; + static GetParmFloatValuesFuncPtr GetParmFloatValues; + static GetParmIdFromNameFuncPtr GetParmIdFromName; + static GetParmInfoFuncPtr GetParmInfo; + static GetParmInfoFromNameFuncPtr GetParmInfoFromName; + static GetParmIntValueFuncPtr GetParmIntValue; + static GetParmIntValuesFuncPtr GetParmIntValues; + static GetParmNodeValueFuncPtr GetParmNodeValue; + static GetParmStringValueFuncPtr GetParmStringValue; + static GetParmStringValuesFuncPtr GetParmStringValues; + static GetParmTagNameFuncPtr GetParmTagName; + static GetParmTagValueFuncPtr GetParmTagValue; + static GetParmWithTagFuncPtr GetParmWithTag; + static GetPartInfoFuncPtr GetPartInfo; + static GetPresetFuncPtr GetPreset; + static GetPresetBufLengthFuncPtr GetPresetBufLength; + static GetServerEnvIntFuncPtr GetServerEnvInt; + static GetServerEnvStringFuncPtr GetServerEnvString; + static GetServerEnvVarCountFuncPtr GetServerEnvVarCount; + static GetServerEnvVarListFuncPtr GetServerEnvVarList; + static GetSessionEnvIntFuncPtr GetSessionEnvInt; + static GetSessionSyncInfoFuncPtr GetSessionSyncInfo; + static GetSphereInfoFuncPtr GetSphereInfo; + static GetStatusFuncPtr GetStatus; + static GetStatusStringFuncPtr GetStatusString; + static GetStatusStringBufLengthFuncPtr GetStatusStringBufLength; + static GetStringFuncPtr GetString; + static GetStringBatchFuncPtr GetStringBatch; + static GetStringBatchSizeFuncPtr GetStringBatchSize; + static GetStringBufLengthFuncPtr GetStringBufLength; + static GetSupportedImageFileFormatCountFuncPtr GetSupportedImageFileFormatCount; + static GetSupportedImageFileFormatsFuncPtr GetSupportedImageFileFormats; + static GetTimeFuncPtr GetTime; + static GetTimelineOptionsFuncPtr GetTimelineOptions; + static GetTotalCookCountFuncPtr GetTotalCookCount; + static GetUseHoudiniTimeFuncPtr GetUseHoudiniTime; + static GetVertexListFuncPtr GetVertexList; + static GetViewportFuncPtr GetViewport; + static GetVolumeBoundsFuncPtr GetVolumeBounds; + static GetVolumeInfoFuncPtr GetVolumeInfo; + static GetVolumeTileFloatDataFuncPtr GetVolumeTileFloatData; + static GetVolumeTileIntDataFuncPtr GetVolumeTileIntData; + static GetVolumeVisualInfoFuncPtr GetVolumeVisualInfo; + static GetVolumeVoxelFloatDataFuncPtr GetVolumeVoxelFloatData; + static GetVolumeVoxelIntDataFuncPtr GetVolumeVoxelIntData; + static GetWorkitemDataLengthFuncPtr GetWorkitemDataLength; + static GetWorkitemFloatDataFuncPtr GetWorkitemFloatData; + static GetWorkitemInfoFuncPtr GetWorkitemInfo; + static GetWorkitemIntDataFuncPtr GetWorkitemIntData; + static GetWorkitemResultInfoFuncPtr GetWorkitemResultInfo; + static GetWorkitemStringDataFuncPtr GetWorkitemStringData; + static GetWorkitemsFuncPtr GetWorkitems; + static HandleBindingInfo_CreateFuncPtr HandleBindingInfo_Create; + static HandleBindingInfo_InitFuncPtr HandleBindingInfo_Init; + static HandleInfo_CreateFuncPtr HandleInfo_Create; + static HandleInfo_InitFuncPtr HandleInfo_Init; + static ImageFileFormat_CreateFuncPtr ImageFileFormat_Create; + static ImageFileFormat_InitFuncPtr ImageFileFormat_Init; + static ImageInfo_CreateFuncPtr ImageInfo_Create; + static ImageInfo_InitFuncPtr ImageInfo_Init; + static InitializeFuncPtr Initialize; + static InsertMultiparmInstanceFuncPtr InsertMultiparmInstance; + static InterruptFuncPtr Interrupt; + static IsInitializedFuncPtr IsInitialized; + static IsNodeValidFuncPtr IsNodeValid; + static IsSessionValidFuncPtr IsSessionValid; + static Keyframe_CreateFuncPtr Keyframe_Create; + static Keyframe_InitFuncPtr Keyframe_Init; + static LoadAssetLibraryFromFileFuncPtr LoadAssetLibraryFromFile; + static LoadAssetLibraryFromMemoryFuncPtr LoadAssetLibraryFromMemory; + static LoadGeoFromFileFuncPtr LoadGeoFromFile; + static LoadGeoFromMemoryFuncPtr LoadGeoFromMemory; + static LoadHIPFileFuncPtr LoadHIPFile; + static LoadNodeFromFileFuncPtr LoadNodeFromFile; + static MaterialInfo_CreateFuncPtr MaterialInfo_Create; + static MaterialInfo_InitFuncPtr MaterialInfo_Init; + static MergeHIPFileFuncPtr MergeHIPFile; + static NodeInfo_CreateFuncPtr NodeInfo_Create; + static NodeInfo_InitFuncPtr NodeInfo_Init; + static ObjectInfo_CreateFuncPtr ObjectInfo_Create; + static ObjectInfo_InitFuncPtr ObjectInfo_Init; + static ParmChoiceInfo_CreateFuncPtr ParmChoiceInfo_Create; + static ParmChoiceInfo_InitFuncPtr ParmChoiceInfo_Init; + static ParmHasExpressionFuncPtr ParmHasExpression; + static ParmHasTagFuncPtr ParmHasTag; + static ParmInfo_CreateFuncPtr ParmInfo_Create; + static ParmInfo_GetFloatValueCountFuncPtr ParmInfo_GetFloatValueCount; + static ParmInfo_GetIntValueCountFuncPtr ParmInfo_GetIntValueCount; + static ParmInfo_GetStringValueCountFuncPtr ParmInfo_GetStringValueCount; + static ParmInfo_InitFuncPtr ParmInfo_Init; + static ParmInfo_IsFloatFuncPtr ParmInfo_IsFloat; + static ParmInfo_IsIntFuncPtr ParmInfo_IsInt; + static ParmInfo_IsNodeFuncPtr ParmInfo_IsNode; + static ParmInfo_IsNonValueFuncPtr ParmInfo_IsNonValue; + static ParmInfo_IsPathFuncPtr ParmInfo_IsPath; + static ParmInfo_IsStringFuncPtr ParmInfo_IsString; + static PartInfo_CreateFuncPtr PartInfo_Create; + static PartInfo_GetAttributeCountByOwnerFuncPtr PartInfo_GetAttributeCountByOwner; + static PartInfo_GetElementCountByAttributeOwnerFuncPtr PartInfo_GetElementCountByAttributeOwner; + static PartInfo_GetElementCountByGroupTypeFuncPtr PartInfo_GetElementCountByGroupType; + static PartInfo_InitFuncPtr PartInfo_Init; + static PausePDGCookFuncPtr PausePDGCook; + static PythonThreadInterpreterLockFuncPtr PythonThreadInterpreterLock; + static QueryNodeInputFuncPtr QueryNodeInput; + static QueryNodeOutputConnectedCountFuncPtr QueryNodeOutputConnectedCount; + static QueryNodeOutputConnectedNodesFuncPtr QueryNodeOutputConnectedNodes; + static RemoveCustomStringFuncPtr RemoveCustomString; + static RemoveMultiparmInstanceFuncPtr RemoveMultiparmInstance; + static RemoveParmExpressionFuncPtr RemoveParmExpression; + static RenameNodeFuncPtr RenameNode; + static RenderCOPToImageFuncPtr RenderCOPToImage; + static RenderTextureToImageFuncPtr RenderTextureToImage; + static ResetSimulationFuncPtr ResetSimulation; + static RevertGeoFuncPtr RevertGeo; + static RevertParmToDefaultFuncPtr RevertParmToDefault; + static RevertParmToDefaultsFuncPtr RevertParmToDefaults; + static SaveGeoToFileFuncPtr SaveGeoToFile; + static SaveGeoToMemoryFuncPtr SaveGeoToMemory; + static SaveHIPFileFuncPtr SaveHIPFile; + static SaveNodeToFileFuncPtr SaveNodeToFile; + static SessionSyncInfo_CreateFuncPtr SessionSyncInfo_Create; + static SetAnimCurveFuncPtr SetAnimCurve; + static SetAttributeFloat64DataFuncPtr SetAttributeFloat64Data; + static SetAttributeFloatDataFuncPtr SetAttributeFloatData; + static SetAttributeInt16DataFuncPtr SetAttributeInt16Data; + static SetAttributeInt64DataFuncPtr SetAttributeInt64Data; + static SetAttributeInt8DataFuncPtr SetAttributeInt8Data; + static SetAttributeIntDataFuncPtr SetAttributeIntData; + static SetAttributeStringDataFuncPtr SetAttributeStringData; + static SetAttributeUInt8DataFuncPtr SetAttributeUInt8Data; + static SetCachePropertyFuncPtr SetCacheProperty; + static SetCompositorOptionsFuncPtr SetCompositorOptions; + static SetCurveCountsFuncPtr SetCurveCounts; + static SetCurveInfoFuncPtr SetCurveInfo; + static SetCurveKnotsFuncPtr SetCurveKnots; + static SetCurveOrdersFuncPtr SetCurveOrders; + static SetCustomStringFuncPtr SetCustomString; + static SetFaceCountsFuncPtr SetFaceCounts; + static SetGroupMembershipFuncPtr SetGroupMembership; + static SetHeightFieldDataFuncPtr SetHeightFieldData; + static SetImageInfoFuncPtr SetImageInfo; + static SetNodeDisplayFuncPtr SetNodeDisplay; + static SetObjectTransformFuncPtr SetObjectTransform; + static SetParmExpressionFuncPtr SetParmExpression; + static SetParmFloatValueFuncPtr SetParmFloatValue; + static SetParmFloatValuesFuncPtr SetParmFloatValues; + static SetParmIntValueFuncPtr SetParmIntValue; + static SetParmIntValuesFuncPtr SetParmIntValues; + static SetParmNodeValueFuncPtr SetParmNodeValue; + static SetParmStringValueFuncPtr SetParmStringValue; + static SetPartInfoFuncPtr SetPartInfo; + static SetPresetFuncPtr SetPreset; + static SetServerEnvIntFuncPtr SetServerEnvInt; + static SetServerEnvStringFuncPtr SetServerEnvString; + static SetSessionSyncFuncPtr SetSessionSync; + static SetSessionSyncInfoFuncPtr SetSessionSyncInfo; + static SetTimeFuncPtr SetTime; + static SetTimelineOptionsFuncPtr SetTimelineOptions; + static SetTransformAnimCurveFuncPtr SetTransformAnimCurve; + static SetUseHoudiniTimeFuncPtr SetUseHoudiniTime; + static SetVertexListFuncPtr SetVertexList; + static SetViewportFuncPtr SetViewport; + static SetVolumeInfoFuncPtr SetVolumeInfo; + static SetVolumeTileFloatDataFuncPtr SetVolumeTileFloatData; + static SetVolumeTileIntDataFuncPtr SetVolumeTileIntData; + static SetVolumeVoxelFloatDataFuncPtr SetVolumeVoxelFloatData; + static SetVolumeVoxelIntDataFuncPtr SetVolumeVoxelIntData; + static SetWorkitemFloatDataFuncPtr SetWorkitemFloatData; + static SetWorkitemIntDataFuncPtr SetWorkitemIntData; + static SetWorkitemStringDataFuncPtr SetWorkitemStringData; + static StartThriftNamedPipeServerFuncPtr StartThriftNamedPipeServer; + static StartThriftSocketServerFuncPtr StartThriftSocketServer; + static ThriftServerOptions_CreateFuncPtr ThriftServerOptions_Create; + static ThriftServerOptions_InitFuncPtr ThriftServerOptions_Init; + static TimelineOptions_CreateFuncPtr TimelineOptions_Create; + static TimelineOptions_InitFuncPtr TimelineOptions_Init; + static TransformEuler_CreateFuncPtr TransformEuler_Create; + static TransformEuler_InitFuncPtr TransformEuler_Init; + static Transform_CreateFuncPtr Transform_Create; + static Transform_InitFuncPtr Transform_Init; + static Viewport_CreateFuncPtr Viewport_Create; + static VolumeInfo_CreateFuncPtr VolumeInfo_Create; + static VolumeInfo_InitFuncPtr VolumeInfo_Init; + static VolumeTileInfo_CreateFuncPtr VolumeTileInfo_Create; + static VolumeTileInfo_InitFuncPtr VolumeTileInfo_Init; + +public: + + static HAPI_Result AddAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + static HAPI_Result AddGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + static HAPI_AssetInfo AssetInfo_CreateEmptyStub(); + static void AssetInfo_InitEmptyStub(HAPI_AssetInfo * in); + static HAPI_AttributeInfo AttributeInfo_CreateEmptyStub(); + static void AttributeInfo_InitEmptyStub(HAPI_AttributeInfo * in); + static HAPI_Result BindCustomImplementationEmptyStub(HAPI_SessionType session_type, const char * dll_path); + static HAPI_Result CancelPDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + static HAPI_Result CheckForSpecificErrorsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ErrorCodeBits errors_to_look_for, HAPI_ErrorCodeBits * errors_found); + static HAPI_Result CleanupEmptyStub(const HAPI_Session * session); + static HAPI_Result ClearConnectionErrorEmptyStub(); + static HAPI_Result CloseSessionEmptyStub(const HAPI_Session * session); + static HAPI_Result CommitGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result CommitWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result ComposeChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + static HAPI_Result ComposeNodeCookResultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_StatusVerbosity verbosity, int * buffer_length); + static HAPI_Result ComposeObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * categories, int * object_count); + static HAPI_CompositorOptions CompositorOptions_CreateEmptyStub(); + static void CompositorOptions_InitEmptyStub(HAPI_CompositorOptions * in); + static HAPI_Result ConnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index, HAPI_NodeId node_id_to_connect, int output_index); + static HAPI_Result ConvertMatrixToEulerEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + static HAPI_Result ConvertMatrixToQuatEmptyStub(const HAPI_Session * session, const float * matrix, HAPI_RSTOrder rst_order, HAPI_Transform * transform_out); + static HAPI_Result ConvertTransformEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform_in, HAPI_RSTOrder rst_order, HAPI_XYZOrder rot_order, HAPI_TransformEuler * transform_out); + static HAPI_Result ConvertTransformEulerToMatrixEmptyStub(const HAPI_Session * session, const HAPI_TransformEuler * transform, float * matrix); + static HAPI_Result ConvertTransformQuatToMatrixEmptyStub(const HAPI_Session * session, const HAPI_Transform * transform, float * matrix); + static HAPI_Result CookNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_CookOptions * cook_options); + static HAPI_Bool CookOptions_AreEqualEmptyStub(const HAPI_CookOptions * left, const HAPI_CookOptions * right); + static HAPI_CookOptions CookOptions_CreateEmptyStub(); + static void CookOptions_InitEmptyStub(HAPI_CookOptions * in); + static HAPI_Result CookPDGEmptyStub(const HAPI_Session * session, HAPI_NodeId cook_node_id, int generate_only, int blocking); + static HAPI_Result CreateCustomSessionEmptyStub(HAPI_SessionType session_type, void * session_info, HAPI_Session * session); + static HAPI_Result CreateHeightFieldInputEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * name, int xsize, int ysize, float voxelsize, HAPI_HeightFieldSampling sampling, HAPI_NodeId * heightfield_node_id, HAPI_NodeId * height_node_id, HAPI_NodeId * mask_node_id, HAPI_NodeId * merge_node_id); + static HAPI_Result CreateHeightfieldInputVolumeNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * new_node_id, const char * name, int xsize, int ysize, float voxelsize); + static HAPI_Result CreateInProcessSessionEmptyStub(HAPI_Session * session); + static HAPI_Result CreateInputNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId * node_id, const char * name); + static HAPI_Result CreateNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, const char * operator_name, const char * node_label, HAPI_Bool cook_on_creation, HAPI_NodeId * new_node_id); + static HAPI_Result CreateThriftNamedPipeSessionEmptyStub(HAPI_Session * session, const char * pipe_name); + static HAPI_Result CreateThriftSocketSessionEmptyStub(HAPI_Session * session, const char * host_name, int port); + static HAPI_Result CreateWorkitemEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId * workitem_id, const char * name, int index); + static HAPI_CurveInfo CurveInfo_CreateEmptyStub(); + static void CurveInfo_InitEmptyStub(HAPI_CurveInfo * in); + static HAPI_Result DeleteAttributeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info); + static HAPI_Result DeleteGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name); + static HAPI_Result DeleteNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result DirtyPDGNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool clean_results); + static HAPI_Result DisconnectNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_index); + static HAPI_Result DisconnectNodeOutputsAtEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_index); + static HAPI_Result ExtractImageToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, int * destination_file_path); + static HAPI_Result ExtractImageToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, int * buffer_size); + static HAPI_GeoInfo GeoInfo_CreateEmptyStub(); + static int GeoInfo_GetGroupCountByTypeEmptyStub(HAPI_GeoInfo * in, HAPI_GroupType type); + static void GeoInfo_InitEmptyStub(HAPI_GeoInfo * in); + static HAPI_Result GetActiveCacheCountEmptyStub(const HAPI_Session * session, int * active_cache_count); + static HAPI_Result GetActiveCacheNamesEmptyStub(const HAPI_Session * session, HAPI_StringHandle * cache_names_array, int active_cache_count); + static HAPI_Result GetAssetDefinitionParmCountsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * parm_count, int * int_value_count, int * float_value_count, int * string_value_count, int * choice_value_count); + static HAPI_Result GetAssetDefinitionParmInfosEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, HAPI_ParmInfo * parm_infos_array, int start, int length); + static HAPI_Result GetAssetDefinitionParmValuesEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, const char * asset_name, int * int_values_array, int int_start, int int_length, float * float_values_array, int float_start, int float_length, HAPI_Bool string_evaluate, HAPI_StringHandle * string_values_array, int string_start, int string_length, HAPI_ParmChoiceInfo * choice_values_array, int choice_start, int choice_length); + static HAPI_Result GetAssetInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_AssetInfo * asset_info); + static HAPI_Result GetAttributeFloat64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, double * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, double * data_array, int start, int length); + static HAPI_Result GetAttributeFloatArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, float * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, float * data_array, int start, int length); + static HAPI_Result GetAttributeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeOwner owner, HAPI_AttributeInfo * attr_info); + static HAPI_Result GetAttributeInt16ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int16 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int16 * data_array, int start, int length); + static HAPI_Result GetAttributeInt64ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int64 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int64 * data_array, int start, int length); + static HAPI_Result GetAttributeInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_Int8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_Int8 * data_array, int start, int length); + static HAPI_Result GetAttributeIntArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, int * data_array, int start, int length); + static HAPI_Result GetAttributeNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_AttributeOwner owner, HAPI_StringHandle * attribute_names_array, int count); + static HAPI_Result GetAttributeStringArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_StringHandle * data_array, int start, int length); + static HAPI_Result GetAttributeUInt8ArrayDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, HAPI_UInt8 * data_fixed_array, int data_fixed_length, int * sizes_fixed_array, int start, int sizes_fixed_length); + static HAPI_Result GetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, HAPI_AttributeInfo * attr_info, int stride, HAPI_UInt8 * data_array, int start, int length); + static HAPI_Result GetAvailableAssetCountEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, int * asset_count); + static HAPI_Result GetAvailableAssetsEmptyStub(const HAPI_Session * session, HAPI_AssetLibraryId library_id, HAPI_StringHandle * asset_names_array, int asset_count); + static HAPI_Result GetBoxInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_BoxInfo * box_info); + static HAPI_Result GetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int * property_value); + static HAPI_Result GetComposedChildNodeListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_NodeId * child_node_ids_array, int count); + static HAPI_Result GetComposedNodeCookResultEmptyStub(const HAPI_Session * session, char * string_value, int length); + static HAPI_Result GetComposedObjectListEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_ObjectInfo * object_infos_array, int start, int length); + static HAPI_Result GetComposedObjectTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId parent_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform_array, int start, int length); + static HAPI_Result GetCompositorOptionsEmptyStub(const HAPI_Session * session, HAPI_CompositorOptions * compositor_options); + static HAPI_Result GetConnectionErrorEmptyStub(char * string_value, int length, HAPI_Bool clear); + static HAPI_Result GetConnectionErrorLengthEmptyStub(int * buffer_length); + static HAPI_Result GetCookingCurrentCountEmptyStub(const HAPI_Session * session, int * count); + static HAPI_Result GetCookingTotalCountEmptyStub(const HAPI_Session * session, int * count); + static HAPI_Result GetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * counts_array, int start, int length); + static HAPI_Result GetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_CurveInfo * info); + static HAPI_Result GetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * knots_array, int start, int length); + static HAPI_Result GetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * orders_array, int start, int length); + static HAPI_Result GetDisplayGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetEdgeCountOfEdgeGroupEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * group_name, int * edge_count); + static HAPI_Result GetEnvIntEmptyStub(HAPI_EnvIntType int_type, int * value); + static HAPI_Result GetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * face_counts_array, int start, int length); + static HAPI_Result GetFirstVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + static HAPI_Result GetGeoInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GeoInfo * geo_info); + static HAPI_Result GetGeoSizeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, int * size); + static HAPI_Result GetGroupCountOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * pointGroupCount, int * primitiveGroupCount); + static HAPI_Result GetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + static HAPI_Result GetGroupMembershipOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, HAPI_Bool * membership_array_all_equal, int * membership_array, int start, int length); + static HAPI_Result GetGroupNamesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + static HAPI_Result GetGroupNamesOnPackedInstancePartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, HAPI_StringHandle * group_names_array, int group_count); + static HAPI_Result GetHIPFileNodeCountEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, int * count); + static HAPI_Result GetHIPFileNodeIdsEmptyStub(const HAPI_Session *session, HAPI_HIPFileId id, HAPI_NodeId * node_ids, int length); + static HAPI_Result GetHandleBindingInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int handle_index, HAPI_HandleBindingInfo * handle_binding_infos_array, int start, int length); + static HAPI_Result GetHandleInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_HandleInfo * handle_infos_array, int start, int length); + static HAPI_Result GetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * values_array, int start, int length); + static HAPI_Result GetImageFilePathEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const char * image_file_format_name, const char * image_planes, const char * destination_folder_path, const char * destination_file_name, HAPI_ParmId texture_parm_id, int * destination_file_path); + static HAPI_Result GetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ImageInfo * image_info); + static HAPI_Result GetImageMemoryBufferEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, char * buffer, int length); + static HAPI_Result GetImagePlaneCountEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, int * image_plane_count); + static HAPI_Result GetImagePlanesEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_StringHandle * image_planes_array, int image_plane_count); + static HAPI_Result GetInstanceTransformsOnPartEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + static HAPI_Result GetInstancedObjectIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId object_node_id, HAPI_NodeId * instanced_node_id_array, int start, int length); + static HAPI_Result GetInstancedPartIdsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartId * instanced_parts_array, int start, int length); + static HAPI_Result GetInstancerPartTransformsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_RSTOrder rst_order, HAPI_Transform * transforms_array, int start, int length); + static HAPI_Result GetManagerNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeType node_type, HAPI_NodeId * node_id); + static HAPI_Result GetMaterialInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_MaterialInfo * material_info); + static HAPI_Result GetMaterialNodeIdsOnFacesEmptyStub(const HAPI_Session * session, HAPI_NodeId geometry_node_id, HAPI_PartId part_id, HAPI_Bool * are_all_the_same, HAPI_NodeId * material_ids_array, int start, int length); + static HAPI_Result GetNextVolumeTileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeTileInfo * tile); + static HAPI_Result GetNodeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeInfo * node_info); + static HAPI_Result GetNodeInputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int input_idx, HAPI_StringHandle * name); + static HAPI_Result GetNodeOutputNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_StringHandle * name); + static HAPI_Result GetNodePathEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_StringHandle * path); + static HAPI_Result GetNumWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * num); + static HAPI_Result GetObjectInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ObjectInfo * object_info); + static HAPI_Result GetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeId relative_to_node_id, HAPI_RSTOrder rst_order, HAPI_Transform * transform); + static HAPI_Result GetOutputGeoCountEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, int* count); + static HAPI_Result GetOutputGeoInfosEmptyStub(const HAPI_Session* session, HAPI_NodeId node_id, HAPI_GeoInfo* geo_infos_array, int count); + static HAPI_Result GetOutputNodeIdEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output, HAPI_NodeId * output_node_id); + static HAPI_Result GetPDGEventsEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_EventInfo * event_array, int length, int * event_count, int * remaining_events); + static HAPI_Result GetPDGGraphContextIdEmptyStub(const HAPI_Session * session, HAPI_NodeId top_node_id, HAPI_PDG_GraphContextId * context_id); + static HAPI_Result GetPDGGraphContextsEmptyStub(const HAPI_Session * session, int * num_contexts, HAPI_StringHandle * context_names_array, HAPI_PDG_GraphContextId * context_id_array, int count); + static HAPI_Result GetPDGStateEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, int * pdg_state); + static HAPI_Result GetParametersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmInfo * parm_infos_array, int start, int length); + static HAPI_Result GetParmChoiceListsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmChoiceInfo * parm_choices_array, int start, int length); + static HAPI_Result GetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_StringHandle * value); + static HAPI_Result GetParmFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, const char * destination_directory, const char * destination_file_name); + static HAPI_Result GetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float * value); + static HAPI_Result GetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, float * values_array, int start, int length); + static HAPI_Result GetParmIdFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmId * parm_id); + static HAPI_Result GetParmInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, HAPI_ParmInfo * parm_info); + static HAPI_Result GetParmInfoFromNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_ParmInfo * parm_info); + static HAPI_Result GetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int * value); + static HAPI_Result GetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * values_array, int start, int length); + static HAPI_Result GetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId * value); + static HAPI_Result GetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool evaluate, HAPI_StringHandle * value); + static HAPI_Result GetParmStringValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_Bool evaluate, HAPI_StringHandle * values_array, int start, int length); + static HAPI_Result GetParmTagNameEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int tag_index, HAPI_StringHandle * tag_name); + static HAPI_Result GetParmTagValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_StringHandle * tag_value); + static HAPI_Result GetParmWithTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * tag_name, HAPI_ParmId * parm_id); + static HAPI_Result GetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_PartInfo * part_info); + static HAPI_Result GetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int buffer_length); + static HAPI_Result GetPresetBufLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, int * buffer_length); + static HAPI_Result GetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int * value); + static HAPI_Result GetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, HAPI_StringHandle * value); + static HAPI_Result GetServerEnvVarCountEmptyStub(const HAPI_Session * session, int * env_count); + static HAPI_Result GetServerEnvVarListEmptyStub(const HAPI_Session * session, HAPI_StringHandle * values_array, int start, int length); + static HAPI_Result GetSessionEnvIntEmptyStub(const HAPI_Session * session, HAPI_SessionEnvIntType int_type, int * value); + static HAPI_Result GetSessionSyncInfoEmptyStub(const HAPI_Session * session, HAPI_SessionSyncInfo * session_sync_info); + static HAPI_Result GetSphereInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId geo_node_id, HAPI_PartId part_id, HAPI_SphereInfo * sphere_info); + static HAPI_Result GetStatusEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, int * status); + static HAPI_Result GetStatusStringEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, char * string_value, int length); + static HAPI_Result GetStatusStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity, int * buffer_length); + static HAPI_Result GetStringEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, char * string_value, int length); + static HAPI_Result GetStringBatchEmptyStub(const HAPI_Session * session, char * char_buffer, int char_array_length); + static HAPI_Result GetStringBatchSizeEmptyStub(const HAPI_Session * session, const int * string_handle_array, int string_handle_count, int * string_buffer_size); + static HAPI_Result GetStringBufLengthEmptyStub(const HAPI_Session * session, HAPI_StringHandle string_handle, int * buffer_length); + static HAPI_Result GetSupportedImageFileFormatCountEmptyStub(const HAPI_Session * session, int * file_format_count); + static HAPI_Result GetSupportedImageFileFormatsEmptyStub(const HAPI_Session * session, HAPI_ImageFileFormat * formats_array, int file_format_count); + static HAPI_Result GetTimeEmptyStub(const HAPI_Session * session, float * time); + static HAPI_Result GetTimelineOptionsEmptyStub(const HAPI_Session * session, HAPI_TimelineOptions * timeline_options); + static HAPI_Result GetTotalCookCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_NodeTypeBits node_type_filter, HAPI_NodeFlagsBits node_flags_filter, HAPI_Bool recursive, int * count); + static HAPI_Result GetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool * enabled); + static HAPI_Result GetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int * vertex_list_array, int start, int length); + static HAPI_Result GetViewportEmptyStub(const HAPI_Session * session, HAPI_Viewport * viewport); + static HAPI_Result GetVolumeBoundsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float * x_min, float * y_min, float * z_min, float * x_max, float * y_max, float * z_max, float * x_center, float * y_center, float * z_center); + static HAPI_Result GetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeInfo * volume_info); + static HAPI_Result GetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, float fill_value, const HAPI_VolumeTileInfo * tile, float * values_array, int length); + static HAPI_Result GetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int fill_value, const HAPI_VolumeTileInfo * tile, int * values_array, int length); + static HAPI_Result GetVolumeVisualInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_VolumeVisualInfo * visual_info); + static HAPI_Result GetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, float * values_array, int value_count); + static HAPI_Result GetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, int * values_array, int value_count); + static HAPI_Result GetWorkitemDataLengthEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * length); + static HAPI_Result GetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, float * data_array, int length); + static HAPI_Result GetWorkitemInfoEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemInfo * workitem_info); + static HAPI_Result GetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int * data_array, int length); + static HAPI_Result GetWorkitemResultInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, HAPI_PDG_WorkitemResultInfo * resultinfo_array, int resultinfo_count); + static HAPI_Result GetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, HAPI_StringHandle * data_array, int length); + static HAPI_Result GetWorkitemsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int * workitem_ids_array, int length); + static HAPI_HandleBindingInfo HandleBindingInfo_CreateEmptyStub(); + static void HandleBindingInfo_InitEmptyStub(HAPI_HandleBindingInfo * in); + static HAPI_HandleInfo HandleInfo_CreateEmptyStub(); + static void HandleInfo_InitEmptyStub(HAPI_HandleInfo * in); + static HAPI_ImageFileFormat ImageFileFormat_CreateEmptyStub(); + static void ImageFileFormat_InitEmptyStub(HAPI_ImageFileFormat *in); + static HAPI_ImageInfo ImageInfo_CreateEmptyStub(); + static void ImageInfo_InitEmptyStub(HAPI_ImageInfo * in); + static HAPI_Result InitializeEmptyStub(const HAPI_Session * session, const HAPI_CookOptions * cook_options, HAPI_Bool use_cooking_thread, int cooking_thread_stack_size, const char * houdini_environment_files, const char * otl_search_path, const char * dso_search_path, const char * image_dso_search_path, const char * audio_dso_search_path); + static HAPI_Result InsertMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + static HAPI_Result InterruptEmptyStub(const HAPI_Session * session); + static HAPI_Result IsInitializedEmptyStub(const HAPI_Session * session); + static HAPI_Result IsNodeValidEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int unique_node_id, HAPI_Bool * answer); + static HAPI_Result IsSessionValidEmptyStub(const HAPI_Session * session); + static HAPI_Keyframe Keyframe_CreateEmptyStub(); + static void Keyframe_InitEmptyStub(HAPI_Keyframe * in); + static HAPI_Result LoadAssetLibraryFromFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + static HAPI_Result LoadAssetLibraryFromMemoryEmptyStub(const HAPI_Session * session, const char * library_buffer, int library_buffer_length, HAPI_Bool allow_overwrite, HAPI_AssetLibraryId * library_id); + static HAPI_Result LoadGeoFromFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_Result LoadGeoFromMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * format, const char * buffer, int length); + static HAPI_Result LoadHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load); + static HAPI_Result LoadNodeFromFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_NodeId parent_node_id, const char * node_label, HAPI_Bool cook_on_load, HAPI_NodeId * new_node_id); + static HAPI_MaterialInfo MaterialInfo_CreateEmptyStub(); + static void MaterialInfo_InitEmptyStub(HAPI_MaterialInfo * in); + static HAPI_Result MergeHIPFileEmptyStub(const HAPI_Session * session, const char * file_name, HAPI_Bool cook_on_load, HAPI_HIPFileId * file_id); + static HAPI_NodeInfo NodeInfo_CreateEmptyStub(); + static void NodeInfo_InitEmptyStub(HAPI_NodeInfo * in); + static HAPI_ObjectInfo ObjectInfo_CreateEmptyStub(); + static void ObjectInfo_InitEmptyStub(HAPI_ObjectInfo * in); + static HAPI_ParmChoiceInfo ParmChoiceInfo_CreateEmptyStub(); + static void ParmChoiceInfo_InitEmptyStub(HAPI_ParmChoiceInfo * in); + static HAPI_Result ParmHasExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, HAPI_Bool * has_expression); + static HAPI_Result ParmHasTagEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, const char * tag_name, HAPI_Bool * has_tag); + static HAPI_ParmInfo ParmInfo_CreateEmptyStub(); + static int ParmInfo_GetFloatValueCountEmptyStub(const HAPI_ParmInfo * in); + static int ParmInfo_GetIntValueCountEmptyStub(const HAPI_ParmInfo * in); + static int ParmInfo_GetStringValueCountEmptyStub(const HAPI_ParmInfo* in); + static void ParmInfo_InitEmptyStub(HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsFloatEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsIntEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsNodeEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsNonValueEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsPathEmptyStub(const HAPI_ParmInfo * in); + static HAPI_Bool ParmInfo_IsStringEmptyStub(const HAPI_ParmInfo * in); + static HAPI_PartInfo PartInfo_CreateEmptyStub(); + static int PartInfo_GetAttributeCountByOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + static int PartInfo_GetElementCountByAttributeOwnerEmptyStub(HAPI_PartInfo * in, HAPI_AttributeOwner owner); + static int PartInfo_GetElementCountByGroupTypeEmptyStub(HAPI_PartInfo * in, HAPI_GroupType type); + static void PartInfo_InitEmptyStub(HAPI_PartInfo * in); + static HAPI_Result PausePDGCookEmptyStub(const HAPI_Session * session, HAPI_PDG_GraphContextId graph_context_id); + static HAPI_Result PythonThreadInterpreterLockEmptyStub(const HAPI_Session * session, HAPI_Bool locked); + static HAPI_Result QueryNodeInputEmptyStub(const HAPI_Session * session, HAPI_NodeId node_to_query, int input_index, HAPI_NodeId * connected_node_id); + static HAPI_Result QueryNodeOutputConnectedCountEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, int * connected_count); + static HAPI_Result QueryNodeOutputConnectedNodesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int output_idx, HAPI_Bool into_subnets, HAPI_Bool through_dots, HAPI_NodeId * connected_node_ids_array, int start, int length); + static HAPI_Result RemoveCustomStringEmptyStub(const HAPI_Session * session, const HAPI_StringHandle string_handle); + static HAPI_Result RemoveMultiparmInstanceEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int instance_position); + static HAPI_Result RemoveParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int index); + static HAPI_Result RenameNodeEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * new_name); + static HAPI_Result RenderCOPToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId cop_node_id); + static HAPI_Result RenderTextureToImageEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, HAPI_ParmId parm_id); + static HAPI_Result ResetSimulationEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result RevertGeoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id); + static HAPI_Result RevertParmToDefaultEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index); + static HAPI_Result RevertParmToDefaultsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name); + static HAPI_Result SaveGeoToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_Result SaveGeoToMemoryEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, char * buffer, int length); + static HAPI_Result SaveHIPFileEmptyStub(const HAPI_Session * session, const char * file_path, HAPI_Bool lock_nodes); + static HAPI_Result SaveNodeToFileEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * file_name); + static HAPI_SessionSyncInfo SessionSyncInfo_CreateEmptyStub(); + static HAPI_Result SetAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_ParmId parm_id, int parm_index, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + static HAPI_Result SetAttributeFloat64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const double * data_array, int start, int length); + static HAPI_Result SetAttributeFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const float * data_array, int start, int length); + static HAPI_Result SetAttributeInt16DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int16 * data_array, int start, int length); + static HAPI_Result SetAttributeInt64DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int64 * data_array, int start, int length); + static HAPI_Result SetAttributeInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_Int8 * data_array, int start, int length); + static HAPI_Result SetAttributeIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const int * data_array, int start, int length); + static HAPI_Result SetAttributeStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const char ** data_array, int start, int length); + static HAPI_Result SetAttributeUInt8DataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const HAPI_AttributeInfo * attr_info, const HAPI_UInt8 * data_array, int start, int length); + static HAPI_Result SetCachePropertyEmptyStub(const HAPI_Session * session, const char * cache_name, HAPI_CacheProperty cache_property, int property_value); + static HAPI_Result SetCompositorOptionsEmptyStub(const HAPI_Session * session, const HAPI_CompositorOptions * compositor_options); + static HAPI_Result SetCurveCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * counts_array, int start, int length); + static HAPI_Result SetCurveInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_CurveInfo * info); + static HAPI_Result SetCurveKnotsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const float * knots_array, int start, int length); + static HAPI_Result SetCurveOrdersEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * orders_array, int start, int length); + static HAPI_Result SetCustomStringEmptyStub(const HAPI_Session * session, const char * string_value, HAPI_StringHandle * handle_value); + static HAPI_Result SetFaceCountsEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * face_counts_array, int start, int length); + static HAPI_Result SetGroupMembershipEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, HAPI_GroupType group_type, const char * group_name, const int * membership_array, int start, int length); + static HAPI_Result SetHeightFieldDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const char * name, const float * values_array, int start, int length); + static HAPI_Result SetImageInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId material_node_id, const HAPI_ImageInfo * image_info); + static HAPI_Result SetNodeDisplayEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, int onOff); + static HAPI_Result SetObjectTransformEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const HAPI_TransformEuler * trans); + static HAPI_Result SetParmExpressionEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + static HAPI_Result SetParmFloatValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, float value); + static HAPI_Result SetParmFloatValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const float * values_array, int start, int length); + static HAPI_Result SetParmIntValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, int index, int value); + static HAPI_Result SetParmIntValuesEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const int * values_array, int start, int length); + static HAPI_Result SetParmNodeValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * parm_name, HAPI_NodeId value); + static HAPI_Result SetParmStringValueEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, const char * value, HAPI_ParmId parm_id, int index); + static HAPI_Result SetPartInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_PartInfo * part_info); + static HAPI_Result SetPresetEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PresetType preset_type, const char * preset_name, const char * buffer, int buffer_length); + static HAPI_Result SetServerEnvIntEmptyStub(const HAPI_Session * session, const char * variable_name, int value); + static HAPI_Result SetServerEnvStringEmptyStub(const HAPI_Session * session, const char * variable_name, const char * value); + static HAPI_Result SetSessionSyncEmptyStub(const HAPI_Session * session, HAPI_Bool enable); + static HAPI_Result SetSessionSyncInfoEmptyStub(const HAPI_Session * session, const HAPI_SessionSyncInfo * session_sync_info); + static HAPI_Result SetTimeEmptyStub(const HAPI_Session * session, float time); + static HAPI_Result SetTimelineOptionsEmptyStub(const HAPI_Session * session, const HAPI_TimelineOptions * timeline_options); + static HAPI_Result SetTransformAnimCurveEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_TransformComponent trans_comp, const HAPI_Keyframe * curve_keyframes_array, int keyframe_count); + static HAPI_Result SetUseHoudiniTimeEmptyStub(const HAPI_Session * session, HAPI_Bool enabled); + static HAPI_Result SetVertexListEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const int * vertex_list_array, int start, int length); + static HAPI_Result SetViewportEmptyStub(const HAPI_Session * session, const HAPI_Viewport * viewport); + static HAPI_Result SetVolumeInfoEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeInfo * volume_info); + static HAPI_Result SetVolumeTileFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const float * values_array, int length); + static HAPI_Result SetVolumeTileIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, const HAPI_VolumeTileInfo * tile, const int * values_array, int length); + static HAPI_Result SetVolumeVoxelFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const float * values_array, int value_count); + static HAPI_Result SetVolumeVoxelIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PartId part_id, int x_index, int y_index, int z_index, const int * values_array, int value_count); + static HAPI_Result SetWorkitemFloatDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const float * values_array, int length); + static HAPI_Result SetWorkitemIntDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, const int * values_array, int length); + static HAPI_Result SetWorkitemStringDataEmptyStub(const HAPI_Session * session, HAPI_NodeId node_id, HAPI_PDG_WorkitemId workitem_id, const char * data_name, int data_index, const char * value); + static HAPI_Result StartThriftNamedPipeServerEmptyStub(const HAPI_ThriftServerOptions * options, const char * pipe_name, HAPI_ProcessId * process_id); + static HAPI_Result StartThriftSocketServerEmptyStub(const HAPI_ThriftServerOptions * options, int port, HAPI_ProcessId * process_id); + static HAPI_ThriftServerOptions ThriftServerOptions_CreateEmptyStub(); + static void ThriftServerOptions_InitEmptyStub(HAPI_ThriftServerOptions * in); + static HAPI_TimelineOptions TimelineOptions_CreateEmptyStub(); + static void TimelineOptions_InitEmptyStub(HAPI_TimelineOptions * in); + static HAPI_TransformEuler TransformEuler_CreateEmptyStub(); + static void TransformEuler_InitEmptyStub(HAPI_TransformEuler * in); + static HAPI_Transform Transform_CreateEmptyStub(); + static void Transform_InitEmptyStub(HAPI_Transform * in); + static HAPI_Viewport Viewport_CreateEmptyStub(); + static HAPI_VolumeInfo VolumeInfo_CreateEmptyStub(); + static void VolumeInfo_InitEmptyStub(HAPI_VolumeInfo * in); + static HAPI_VolumeTileInfo VolumeTileInfo_CreateEmptyStub(); + static void VolumeTileInfo_InitEmptyStub(HAPI_VolumeTileInfo * in); +}; diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp index 200856334..36dd0f577 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp @@ -1,459 +1,459 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "AssetTypeActions_HoudiniAsset.h" -#include "HoudiniAsset.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniTool.h" -#include "HoudiniEngineEditorUtils.h" - -#include "EditorReimportHandler.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "HAL/FileManager.h" -#include "EditorFramework/AssetImportData.h" -#include "LevelEditor.h" -#include "Modules/ModuleManager.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FText -FAssetTypeActions_HoudiniAsset::GetName() const -{ - return LOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset"); -} - -FColor -FAssetTypeActions_HoudiniAsset::GetTypeColor() const -{ - return FColor(255, 165, 0); -} - -UClass * -FAssetTypeActions_HoudiniAsset::GetSupportedClass() const -{ - return UHoudiniAsset::StaticClass(); -} - -uint32 -FAssetTypeActions_HoudiniAsset::GetCategories() -{ - return EAssetTypeCategories::Misc; -} - -/* -UThumbnailInfo * -FAssetTypeActions_HoudiniAsset::GetThumbnailInfo(UObject * Asset) const -{ - if (!Asset || Asset->IsPendingKill()) - return nullptr; - - UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >(Asset); - UThumbnailInfo * ThumbnailInfo = HoudiniAsset->ThumbnailInfo; - if (!ThumbnailInfo) - { - // If we have no thumbnail information, construct it. - ThumbnailInfo = NewObject< USceneThumbnailInfo >(HoudiniAsset, USceneThumbnailInfo::StaticClass()); - HoudiniAsset->ThumbnailInfo = ThumbnailInfo; - } - - return ThumbnailInfo; -} -*/ - -bool -FAssetTypeActions_HoudiniAsset::HasActions(const TArray< UObject * > & InObjects) const -{ - return true; -} - -void -FAssetTypeActions_HoudiniAsset::GetActions(const TArray & InObjects, class FMenuBuilder & MenuBuilder) -{ - bool ValidObjects = false; - TArray> HoudiniAssets; - if (InObjects.Num() > 0) - { - HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); - ValidObjects = true; - } - - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Reimport", "Reimport"), - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ReimportTooltip", "Reimport selected Houdini Assets."), - FSlateIcon(StyleSetName, "HoudiniEngine._Reset"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteReimport, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAll", "Rebuild All Instances"), - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAllTooltip", "Reimports and rebuild all instances of the selected Houdini Assets."), - FSlateIcon(StyleSetName, "HoudiniEngine._RebuildAll"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorer", "Find Source"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorerTooltip", - "Opens explorer at the location of this asset."), - FSlateIcon(StyleSetName, "HoudiniEngine.Hou_OpenInHoudinidiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudini", "Open in Houdini"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudiniTooltip", - "Opens the selected asset in Houdini."), - FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuSeparator(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Instantiate", "Instantiate"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateTooltip", - "Instantiate the selected asset in the current world."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiate, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOrigin", "Instantiate at the origin"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOriginTooltip", - "Instantiate the selected asset in the current world. The Houdini Asset Actor will be created at the origin of the level (0,0,0)."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuSeparator(); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingle", "Apply to the current selection (single input)"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingleTooltip", - "Applies the selected asset to the current world selection. All the selected object will be assigned to the first input."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMulti", "Apply to the current selection (multiple inputs )"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMultiTooltip", - "Applies the selected asset to the current world selection. Each selected object will be assigned to its own input (one object per input)."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatch", "Batch Apply to the current selection"), - NSLOCTEXT( - "HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatchTooltip", - "Batch apply the selected asset to the current world selection. An instance of the selected Houdini asset will be created for each selected object."), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); -} - - -bool -FAssetTypeActions_HoudiniAsset::AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) -{ - if (ActivationType == EAssetTypeActivationMethod::DoubleClicked) - { - bool ValidObjects = false; - TArray> HoudiniAssets; - if (InObjects.Num() > 0) - { - HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); - ValidObjects = true; - } - - if (ValidObjects) - { - ExecuteInstantiate(HoudiniAssets); - return true; - } - } - - return false; -} - - -TSharedRef -FAssetTypeActions_HoudiniAsset::AddLevelEditorMenuExtenders(TArray> HoudiniAssets) -{ - FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); - TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); - - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - TSharedRef Extender = MakeShareable(new FExtender); - Extender->AddMenuExtension( - "ActorAsset", - EExtensionHook::After, - LevelEditorCommandBindings, - FMenuExtensionDelegate::CreateLambda([this, HoudiniAssets, StyleSetName](FMenuBuilder& MenuBuilder) - { - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorer", "Find Source HDA"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorerTooltip", "Opens an explorer at the location of this actor's source HDA file."), - FSlateIcon(StyleSetName, "HoudiniEngine.DigitalAsset"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudini", "Open HDA in Houdini"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudiniTooltip", "Opens the selected asset's source HDA file in Houdini."), - FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), - FUIAction( - FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) - ) - ); - }) - ); - - return Extender; -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteReimport(TArray> InHoudiniAssetPtrs) -{ - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (HoudiniAsset) - FReimportManager::Instance()->Reimport(HoudiniAsset, true); - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs) -{ - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (HoudiniAsset && HoudiniAsset->AssetImportData) - { - const FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); - if (SourceFilePath.Len() && IFileManager::Get().FileSize(*SourceFilePath) != INDEX_NONE) - return FPlatformProcess::ExploreFolder(*SourceFilePath); - } - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs) -{ - if (!FHoudiniEngine::IsInitialized()) - return; - - if (InHoudiniAssetPtrs.Num() != 1) - return; - - UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - return; - - FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); - if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) - return; - - if (!FPaths::FileExists(SourceFilePath)) - return; - - // We'll need to modify the file name for expanded .hda - FString FileExtension = FPaths::GetExtension(SourceFilePath); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) - { - // the .hda directory is what we're actually interested in loading - SourceFilePath = FPaths::GetPath(SourceFilePath); - } - - if (FPaths::IsRelative(SourceFilePath)) - FPaths::ConvertRelativePathToFull(SourceFilePath); - - // Then open the HDA file in Houdini - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniLocation = LibHAPILocation + "/hview"; - - FPlatformProcess::CreateProc( - HoudiniLocation.GetCharArray().GetData(), - SourceFilePath.GetCharArray().GetData(), - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); -} - - -void -FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs) -{ - // Reimports and then rebuild all instances of the asset - for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) - { - UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); - if (!HoudiniAsset) - continue; - - // Reimports the asset - FReimportManager::Instance()->Reimport(HoudiniAsset, true); - - // Rebuilds all instances of that asset in the scene - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * Component = *Itr; - if (Component && (Component->GetHoudiniAsset() == HoudiniAsset)) - { - Component->MarkAsNeedRebuild(); - } - } - } -} - - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE); -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI); -} -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch(TArray> InHoudiniAssetPtrs) -{ - return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH); -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType) -{ - if (InHoudiniAssetPtrs.Num() != 1) - return; - - UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - return; - - - FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(HoudiniAsset, InType, EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY); - /* - // Creating a temporary tool for the selected asset - TSoftObjectPtr HoudiniAssetPtr(HoudiniAsset); - FHoudiniTool HoudiniTool( - HoudiniAssetPtr, - FText::FromString(HoudiniAsset->GetName()), - Type, - EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY, - FText(), - NULL, - FString(), - false, - FFilePath(), - FHoudiniToolDirectory(), - FString()); - - SHoudiniToolPalette::InstantiateHoudiniTool(&HoudiniTool); - */ -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs) -{ - for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) - { - UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - continue; - - FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, FTransform::Identity); - } -} - -void -FAssetTypeActions_HoudiniAsset::ExecuteInstantiate(TArray> InHoudiniAssetPtrs) -{ - FTransform DefaultTransform = FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform(); - for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) - { - UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); - if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) - continue; - - FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, DefaultTransform); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "AssetTypeActions_HoudiniAsset.h" +#include "HoudiniAsset.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniTool.h" +#include "HoudiniEngineEditorUtils.h" + +#include "EditorReimportHandler.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "HAL/FileManager.h" +#include "EditorFramework/AssetImportData.h" +#include "LevelEditor.h" +#include "Modules/ModuleManager.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FText +FAssetTypeActions_HoudiniAsset::GetName() const +{ + return LOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset"); +} + +FColor +FAssetTypeActions_HoudiniAsset::GetTypeColor() const +{ + return FColor(255, 165, 0); +} + +UClass * +FAssetTypeActions_HoudiniAsset::GetSupportedClass() const +{ + return UHoudiniAsset::StaticClass(); +} + +uint32 +FAssetTypeActions_HoudiniAsset::GetCategories() +{ + return EAssetTypeCategories::Misc; +} + +/* +UThumbnailInfo * +FAssetTypeActions_HoudiniAsset::GetThumbnailInfo(UObject * Asset) const +{ + if (!Asset || Asset->IsPendingKill()) + return nullptr; + + UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >(Asset); + UThumbnailInfo * ThumbnailInfo = HoudiniAsset->ThumbnailInfo; + if (!ThumbnailInfo) + { + // If we have no thumbnail information, construct it. + ThumbnailInfo = NewObject< USceneThumbnailInfo >(HoudiniAsset, USceneThumbnailInfo::StaticClass()); + HoudiniAsset->ThumbnailInfo = ThumbnailInfo; + } + + return ThumbnailInfo; +} +*/ + +bool +FAssetTypeActions_HoudiniAsset::HasActions(const TArray< UObject * > & InObjects) const +{ + return true; +} + +void +FAssetTypeActions_HoudiniAsset::GetActions(const TArray & InObjects, class FMenuBuilder & MenuBuilder) +{ + bool ValidObjects = false; + TArray> HoudiniAssets; + if (InObjects.Num() > 0) + { + HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); + ValidObjects = true; + } + + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Reimport", "Reimport"), + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ReimportTooltip", "Reimport selected Houdini Assets."), + FSlateIcon(StyleSetName, "HoudiniEngine._Reset"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteReimport, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAll", "Rebuild All Instances"), + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_RebuildAllTooltip", "Reimports and rebuild all instances of the selected Houdini Assets."), + FSlateIcon(StyleSetName, "HoudiniEngine._RebuildAll"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorer", "Find Source"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_FindInExplorerTooltip", + "Opens explorer at the location of this asset."), + FSlateIcon(StyleSetName, "HoudiniEngine.Hou_OpenInHoudinidiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return ValidObjects; }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudini", "Open in Houdini"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_OpenInHoudiniTooltip", + "Opens the selected asset in Houdini."), + FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuSeparator(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_Instantiate", "Instantiate"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateTooltip", + "Instantiate the selected asset in the current world."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiate, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOrigin", "Instantiate at the origin"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_InstantiateOriginTooltip", + "Instantiate the selected asset in the current world. The Houdini Asset Actor will be created at the origin of the level (0,0,0)."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuSeparator(); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingle", "Apply to the current selection (single input)"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpSingleTooltip", + "Applies the selected asset to the current world selection. All the selected object will be assigned to the first input."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMulti", "Apply to the current selection (multiple inputs )"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyOpMultiTooltip", + "Applies the selected asset to the current world selection. Each selected object will be assigned to its own input (one object per input)."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatch", "Batch Apply to the current selection"), + NSLOCTEXT( + "HoudiniAssetTypeActions", "HoudiniAsset_ApplyBatchTooltip", + "Batch apply the selected asset to the current world selection. An instance of the selected Houdini asset will be created for each selected object."), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); +} + + +bool +FAssetTypeActions_HoudiniAsset::AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) +{ + if (ActivationType == EAssetTypeActivationMethod::DoubleClicked) + { + bool ValidObjects = false; + TArray> HoudiniAssets; + if (InObjects.Num() > 0) + { + HoudiniAssets = GetTypedWeakObjectPtrs(InObjects); + ValidObjects = true; + } + + if (ValidObjects) + { + ExecuteInstantiate(HoudiniAssets); + return true; + } + } + + return false; +} + + +TSharedRef +FAssetTypeActions_HoudiniAsset::AddLevelEditorMenuExtenders(TArray> HoudiniAssets) +{ + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); + + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + TSharedRef Extender = MakeShareable(new FExtender); + Extender->AddMenuExtension( + "ActorAsset", + EExtensionHook::After, + LevelEditorCommandBindings, + FMenuExtensionDelegate::CreateLambda([this, HoudiniAssets, StyleSetName](FMenuBuilder& MenuBuilder) + { + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorer", "Find Source HDA"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_FindInExplorerTooltip", "Opens an explorer at the location of this actor's source HDA file."), + FSlateIcon(StyleSetName, "HoudiniEngine.DigitalAsset"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudini", "Open HDA in Houdini"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_OpenInHoudiniTooltip", "Opens the selected asset's source HDA file in Houdini."), + FSlateIcon(StyleSetName, "HoudiniEngine._OpenInHoudini"), + FUIAction( + FExecuteAction::CreateSP(this, &FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini, HoudiniAssets), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssets.Num() == 1); }) + ) + ); + }) + ); + + return Extender; +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteReimport(TArray> InHoudiniAssetPtrs) +{ + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (HoudiniAsset) + FReimportManager::Instance()->Reimport(HoudiniAsset, true); + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs) +{ + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (HoudiniAsset && HoudiniAsset->AssetImportData) + { + const FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); + if (SourceFilePath.Len() && IFileManager::Get().FileSize(*SourceFilePath) != INDEX_NONE) + return FPlatformProcess::ExploreFolder(*SourceFilePath); + } + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs) +{ + if (!FHoudiniEngine::IsInitialized()) + return; + + if (InHoudiniAssetPtrs.Num() != 1) + return; + + UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + return; + + FString SourceFilePath = HoudiniAsset->AssetImportData->GetFirstFilename(); + if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) + return; + + if (!FPaths::FileExists(SourceFilePath)) + return; + + // We'll need to modify the file name for expanded .hda + FString FileExtension = FPaths::GetExtension(SourceFilePath); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) == 0) + { + // the .hda directory is what we're actually interested in loading + SourceFilePath = FPaths::GetPath(SourceFilePath); + } + + if (FPaths::IsRelative(SourceFilePath)) + FPaths::ConvertRelativePathToFull(SourceFilePath); + + // Then open the HDA file in Houdini + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniLocation = LibHAPILocation + "/hview"; + + FPlatformProcess::CreateProc( + HoudiniLocation.GetCharArray().GetData(), + SourceFilePath.GetCharArray().GetData(), + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); +} + + +void +FAssetTypeActions_HoudiniAsset::ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs) +{ + // Reimports and then rebuild all instances of the asset + for (auto ObjIt = InHoudiniAssetPtrs.CreateConstIterator(); ObjIt; ++ObjIt) + { + UHoudiniAsset * HoudiniAsset = (*ObjIt).Get(); + if (!HoudiniAsset) + continue; + + // Reimports the asset + FReimportManager::Instance()->Reimport(HoudiniAsset, true); + + // Rebuilds all instances of that asset in the scene + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * Component = *Itr; + if (Component && (Component->GetHoudiniAsset() == HoudiniAsset)) + { + Component->MarkAsNeedRebuild(); + } + } + } +} + + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE); +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI); +} +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyBatch(TArray> InHoudiniAssetPtrs) +{ + return ExecuteApplyAssetToSelection(InHoudiniAssetPtrs, EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH); +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType) +{ + if (InHoudiniAssetPtrs.Num() != 1) + return; + + UHoudiniAsset * HoudiniAsset = InHoudiniAssetPtrs[0].Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + return; + + + FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(HoudiniAsset, InType, EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY); + /* + // Creating a temporary tool for the selected asset + TSoftObjectPtr HoudiniAssetPtr(HoudiniAsset); + FHoudiniTool HoudiniTool( + HoudiniAssetPtr, + FText::FromString(HoudiniAsset->GetName()), + Type, + EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY, + FText(), + NULL, + FString(), + false, + FFilePath(), + FHoudiniToolDirectory(), + FString()); + + SHoudiniToolPalette::InstantiateHoudiniTool(&HoudiniTool); + */ +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs) +{ + for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) + { + UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + continue; + + FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, FTransform::Identity); + } +} + +void +FAssetTypeActions_HoudiniAsset::ExecuteInstantiate(TArray> InHoudiniAssetPtrs) +{ + FTransform DefaultTransform = FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform(); + for (auto HoudiniAssetPtr : InHoudiniAssetPtrs) + { + UHoudiniAsset * HoudiniAsset = HoudiniAssetPtr.Get(); + if (!HoudiniAsset || !(HoudiniAsset->AssetImportData)) + continue; + + FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(HoudiniAsset, DefaultTransform); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h index b0aeb658a..fe798135a 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.h @@ -1,84 +1,84 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "AssetTypeActions_Base.h" - -class UClass; -class UObject; -class UHoudiniAsset; - -enum class EHoudiniToolType : uint8; - -class FAssetTypeActions_HoudiniAsset : public FAssetTypeActions_Base -{ - public: - - // FAssetTypeActions_Base methods. - virtual FText GetName() const override; - virtual FColor GetTypeColor() const override; - virtual UClass* GetSupportedClass() const override; - virtual uint32 GetCategories() override; - //virtual UThumbnailInfo * GetThumbnailInfo(UObject * Asset) const override; - virtual bool HasActions(const TArray< UObject * > & InObjects) const override; - virtual void GetActions(const TArray< UObject * > & InObjects, class FMenuBuilder & MenuBuilder) override; - - virtual bool AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) override; - - TSharedRef AddLevelEditorMenuExtenders(TArray> InHoudiniAssetPtrs); - - protected: - - // Handler for reimport option. - void ExecuteReimport(TArray> InHoudiniAssetPtrs); - - // Handler for rebuild all option - void ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs); - - // Handler for find in explorer option - void ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs); - - // Handler for the open in Houdini option - void ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs); - - // Handler to apply the current hda to the current world selection (single input) - void ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs); - - // Handler to apply the current hda to the current world selection (multi input) - void ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs); - - // Handler to batch apply the current hda to the current world selection - void ExecuteApplyBatch(TArray> InHoudiniAssetPtrs ); - - // Handler to instantiate the HDA in the world - void ExecuteInstantiate(TArray> InHoudiniAssetPtrs); - - // Handler to instantiate the HDA in the world, actor is placed at the origin - void ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs); - - void ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "AssetTypeActions_Base.h" + +class UClass; +class UObject; +class UHoudiniAsset; + +enum class EHoudiniToolType : uint8; + +class FAssetTypeActions_HoudiniAsset : public FAssetTypeActions_Base +{ + public: + + // FAssetTypeActions_Base methods. + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + virtual UClass* GetSupportedClass() const override; + virtual uint32 GetCategories() override; + //virtual UThumbnailInfo * GetThumbnailInfo(UObject * Asset) const override; + virtual bool HasActions(const TArray< UObject * > & InObjects) const override; + virtual void GetActions(const TArray< UObject * > & InObjects, class FMenuBuilder & MenuBuilder) override; + + virtual bool AssetsActivatedOverride(const TArray& InObjects, EAssetTypeActivationMethod::Type ActivationType) override; + + TSharedRef AddLevelEditorMenuExtenders(TArray> InHoudiniAssetPtrs); + + protected: + + // Handler for reimport option. + void ExecuteReimport(TArray> InHoudiniAssetPtrs); + + // Handler for rebuild all option + void ExecuteRebuildAllInstances(TArray> InHoudiniAssetPtrs); + + // Handler for find in explorer option + void ExecuteFindInExplorer(TArray> InHoudiniAssetPtrs); + + // Handler for the open in Houdini option + void ExecuteOpenInHoudini(TArray> InHoudiniAssetPtrs); + + // Handler to apply the current hda to the current world selection (single input) + void ExecuteApplyOpSingle(TArray> InHoudiniAssetPtrs); + + // Handler to apply the current hda to the current world selection (multi input) + void ExecuteApplyOpMulti(TArray> InHoudiniAssetPtrs); + + // Handler to batch apply the current hda to the current world selection + void ExecuteApplyBatch(TArray> InHoudiniAssetPtrs ); + + // Handler to instantiate the HDA in the world + void ExecuteInstantiate(TArray> InHoudiniAssetPtrs); + + // Handler to instantiate the HDA in the world, actor is placed at the origin + void ExecuteInstantiateOrigin(TArray> InHoudiniAssetPtrs); + + void ExecuteApplyAssetToSelection(TArray> InHoudiniAssetPtrs, const EHoudiniToolType& InType); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp index 933ba8bf2..8f7e86ba7 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.cpp @@ -1,117 +1,117 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetActorFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineUtils.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniAssetActorFactory::UHoudiniAssetActorFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - DisplayName = LOCTEXT("HoudiniAssetDisplayName", "Houdini Engine Asset"); - NewActorClass = AHoudiniAssetActor::StaticClass(); -} - -bool -UHoudiniAssetActorFactory::CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) -{ - if (!AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UHoudiniAsset::StaticClass())) - { - OutErrorMsg = NSLOCTEXT("CanCreateActor", "NoHoudiniAsset", "A valid Houdini Engine asset must be specified."); - return false; - } - - return true; -} - -UObject * -UHoudiniAssetActorFactory::GetAssetFromActorInstance(AActor * Instance) -{ - check(Instance->IsA(NewActorClass)); - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(Instance); - - check(HoudiniAssetActor->GetHoudiniAssetComponent()); - return HoudiniAssetActor->GetHoudiniAssetComponent()->HoudiniAsset; -} - -void -UHoudiniAssetActorFactory::PostSpawnActor(UObject * Asset, AActor * NewActor) -{ - HOUDINI_LOG_MESSAGE(TEXT("PostSpawnActor %s, supplied Asset = 0x%0.8p"), *NewActor->GetName(), Asset); - - UHoudiniAsset * HoudiniAsset = CastChecked(Asset); - if (HoudiniAsset) - { - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(NewActor); - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - check(HoudiniAssetComponent); - - //HoudiniAssetComponent->UnregisterComponent(); - //HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - //HoudiniAssetComponent->RegisterComponent(); - - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); - - if (!HoudiniAssetActor->IsUsedForPreview()) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); - } - } -} - -void -UHoudiniAssetActorFactory::PostCreateBlueprint(UObject * Asset, AActor * CDO) -{ - HOUDINI_LOG_MESSAGE(TEXT("PostCreateBlueprint, supplied Asset = 0x%0.8p"), Asset); - - UHoudiniAsset * HoudiniAsset = CastChecked(Asset); - if (HoudiniAsset) - { - AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(CDO); - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - check(HoudiniAssetComponent); - - FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); - - if (!HoudiniAssetActor->IsUsedForPreview()) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); - } - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetActorFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineUtils.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetActorFactory::UHoudiniAssetActorFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + DisplayName = LOCTEXT("HoudiniAssetDisplayName", "Houdini Engine Asset"); + NewActorClass = AHoudiniAssetActor::StaticClass(); +} + +bool +UHoudiniAssetActorFactory::CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) +{ + if (!AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UHoudiniAsset::StaticClass())) + { + OutErrorMsg = NSLOCTEXT("CanCreateActor", "NoHoudiniAsset", "A valid Houdini Engine asset must be specified."); + return false; + } + + return true; +} + +UObject * +UHoudiniAssetActorFactory::GetAssetFromActorInstance(AActor * Instance) +{ + check(Instance->IsA(NewActorClass)); + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(Instance); + + check(HoudiniAssetActor->GetHoudiniAssetComponent()); + return HoudiniAssetActor->GetHoudiniAssetComponent()->HoudiniAsset; +} + +void +UHoudiniAssetActorFactory::PostSpawnActor(UObject * Asset, AActor * NewActor) +{ + HOUDINI_LOG_MESSAGE(TEXT("PostSpawnActor %s, supplied Asset = 0x%0.8p"), *NewActor->GetName(), Asset); + + UHoudiniAsset * HoudiniAsset = CastChecked(Asset); + if (HoudiniAsset) + { + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(NewActor); + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + check(HoudiniAssetComponent); + + //HoudiniAssetComponent->UnregisterComponent(); + //HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + //HoudiniAssetComponent->RegisterComponent(); + + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); + + if (!HoudiniAssetActor->IsUsedForPreview()) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); + } + } +} + +void +UHoudiniAssetActorFactory::PostCreateBlueprint(UObject * Asset, AActor * CDO) +{ + HOUDINI_LOG_MESSAGE(TEXT("PostCreateBlueprint, supplied Asset = 0x%0.8p"), Asset); + + UHoudiniAsset * HoudiniAsset = CastChecked(Asset); + if (HoudiniAsset) + { + AHoudiniAssetActor * HoudiniAssetActor = CastChecked< AHoudiniAssetActor >(CDO); + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + check(HoudiniAssetComponent); + + FHoudiniEngineUtils::AddHoudiniLogoToComponent(HoudiniAssetComponent); + + if (!HoudiniAssetActor->IsUsedForPreview()) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(HoudiniAssetComponent); + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h index 0ea4eb1be..286b7586a 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetActorFactory.h @@ -1,58 +1,58 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ActorFactories/ActorFactory.h" -#include "HoudiniAssetActorFactory.generated.h" - -class FText; -class AActor; -class UObject; -class UHoudiniAssetComponent; - -struct FAssetData; - -UCLASS(config = Editor) -class UHoudiniAssetActorFactory : public UActorFactory -{ - GENERATED_UCLASS_BODY() - -public: - // UActorFactory methods: - // Return true if Actor can be created from a given asset. - virtual bool CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) override; - // Given an instance of an actor pertaining to this factory, find the asset that should be used to create a new actor. - virtual UObject * GetAssetFromActorInstance(AActor * Instance) override; - // Modify the actor after it has been spawned. - virtual void PostSpawnActor(UObject * Asset, AActor * NewActor) override; - // Called after a blueprint is created by this factory to update the blueprint's CDO properties - // with state from the asset for this factory. - virtual void PostCreateBlueprint(UObject * Asset, AActor * CDO) override; - -protected: - bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ActorFactories/ActorFactory.h" +#include "HoudiniAssetActorFactory.generated.h" + +class FText; +class AActor; +class UObject; +class UHoudiniAssetComponent; + +struct FAssetData; + +UCLASS(config = Editor) +class UHoudiniAssetActorFactory : public UActorFactory +{ + GENERATED_UCLASS_BODY() + +public: + // UActorFactory methods: + // Return true if Actor can be created from a given asset. + virtual bool CanCreateActorFrom(const FAssetData & AssetData, FText & OutErrorMsg) override; + // Given an instance of an actor pertaining to this factory, find the asset that should be used to create a new actor. + virtual UObject * GetAssetFromActorInstance(AActor * Instance) override; + // Modify the actor after it has been spawned. + virtual void PostSpawnActor(UObject * Asset, AActor * NewActor) override; + // Called after a blueprint is created by this factory to update the blueprint's CDO properties + // with state from the asset for this factory. + virtual void PostCreateBlueprint(UObject * Asset, AActor * CDO) override; + +protected: + bool AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp index c1c515bdc..895b17c06 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.cpp @@ -1,72 +1,72 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetBroker.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAsset.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniAssetBroker::~FHoudiniAssetBroker() -{ - -} - -UClass * -FHoudiniAssetBroker::GetSupportedAssetClass() -{ - return UHoudiniAsset::StaticClass(); -} - -bool -FHoudiniAssetBroker::AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) -{ - if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) - { - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(InAsset); - if (HoudiniAsset || !InAsset) - { - HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); - return true; - } - } - - return false; -} - -UObject * -FHoudiniAssetBroker::GetAssetFromComponent(UActorComponent * InComponent) -{ - if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) - { - return HoudiniAssetComponent->GetHoudiniAsset(); - } - - return nullptr; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetBroker.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAsset.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniAssetBroker::~FHoudiniAssetBroker() +{ + +} + +UClass * +FHoudiniAssetBroker::GetSupportedAssetClass() +{ + return UHoudiniAsset::StaticClass(); +} + +bool +FHoudiniAssetBroker::AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) +{ + if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) + { + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(InAsset); + if (HoudiniAsset || !InAsset) + { + HoudiniAssetComponent->SetHoudiniAsset(HoudiniAsset); + return true; + } + } + + return false; +} + +UObject * +FHoudiniAssetBroker::GetAssetFromComponent(UActorComponent * InComponent) +{ + if (UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >(InComponent)) + { + return HoudiniAssetComponent->GetHoudiniAsset(); + } + + return nullptr; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h index 9c0183fcd..3357d841f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetBroker.h @@ -1,49 +1,49 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ComponentAssetBroker.h" - -class UObject; -class UActorComponent; - -class FHoudiniAssetBroker : public IComponentAssetBroker -{ -public: - - virtual ~FHoudiniAssetBroker(); - - // IComponentAssetBroker methods. - // Reports the asset class this broker knows how to handle. - UClass * GetSupportedAssetClass() override; - - // Assign the assigned asset to the supplied component. - bool AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) override; - - // Get the currently assigned asset from the component. - UObject * GetAssetFromComponent(UActorComponent * InComponent) override; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ComponentAssetBroker.h" + +class UObject; +class UActorComponent; + +class FHoudiniAssetBroker : public IComponentAssetBroker +{ +public: + + virtual ~FHoudiniAssetBroker(); + + // IComponentAssetBroker methods. + // Reports the asset class this broker knows how to handle. + UClass * GetSupportedAssetClass() override; + + // Assign the assigned asset to the supplied component. + bool AssignAssetToComponent(UActorComponent * InComponent, UObject * InAsset) override; + + // Get the currently assigned asset from the component. + UObject * GetAssetFromComponent(UActorComponent * InComponent) override; +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp index 24c6fb453..6499e571c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp @@ -1,593 +1,593 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetComponentDetails.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAsset.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniParameter.h" -#include "HoudiniHandleComponent.h" -#include "HoudiniParameterDetails.h" -#include "HoudiniInput.h" -#include "HoudiniInputDetails.h" -#include "HoudiniHandleDetails.h" -#include "HoudiniOutput.h" -#include "HoudiniOutputDetails.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Layout/SSeparator.h" -#include "Widgets/Layout/SBox.h" -#include "Widgets/Images/SImage.h" - -#include "PropertyCustomizationHelpers.h" -#include "DetailLayoutBuilder.h" -#include "DetailCategoryBuilder.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -TSharedRef< IDetailCustomization > -FHoudiniAssetComponentDetails::MakeInstance() -{ - return MakeShareable(new FHoudiniAssetComponentDetails); -} - -FHoudiniAssetComponentDetails::FHoudiniAssetComponentDetails() -{ - OutputDetails = MakeShared(); - ParameterDetails = MakeShared(); - PDGDetails = MakeShared(); - HoudiniEngineDetails = MakeShared(); -} - - -FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() -{ - // The ramp param's curves are added to root to avoid garbage collection - // We need to remove those curves from the root when the details classes are destroyed. - if (ParameterDetails.IsValid()) - { - FHoudiniParameterDetails* ParamDetailsPtr = ParameterDetails.Get(); - - for (auto& CurFloatRampCurveEditor : ParamDetailsPtr->CreatedFloatCurveEditors) - { - if (CurFloatRampCurveEditor.IsValid()) - { - CurFloatRampCurveEditor->HoudiniFloatRampCurve = nullptr; - CurFloatRampCurveEditor->SetCurveOwner(nullptr); - } - } - for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) - { - if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) - continue; - - CurFloatRampCurve->RemoveFromRoot(); - } - - for (auto& CurColorRampCurveEditor : ParamDetailsPtr->CreatedColorGradientEditors) - { - if (CurColorRampCurveEditor.IsValid()) - { - CurColorRampCurveEditor->HoudiniColorRampCurve = nullptr; - CurColorRampCurveEditor->SetCurveOwner(nullptr); - } - } - for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) - { - if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) - continue; - - CurColorRampCurve->RemoveFromRoot(); - } - - ParamDetailsPtr->CreatedFloatCurveEditors.Empty(); - ParamDetailsPtr->CreatedColorGradientEditors.Empty(); - ParamDetailsPtr->CreatedFloatRampCurves.Empty(); - ParamDetailsPtr->CreatedColorRampCurves.Empty(); - } -} - -void -FHoudiniAssetComponentDetails::AddIndieLicenseRow(IDetailCategoryBuilder& InCategory) -{ - FText IndieText = - FText::FromString(TEXT("Houdini Engine Indie - For Limited Commercial Use Only")); - - FSlateFontInfo LargeDetailsFont = IDetailLayoutBuilder::GetDetailFontBold(); - LargeDetailsFont.Size += 2; - - FSlateColor LabelColor = FLinearColor(1.0f, 1.0f, 0.0f, 1.0f); - - InCategory.AddCustomRow(FText::GetEmpty()) - [ - SNew(STextBlock) - .Text(IndieText) - .ToolTipText(IndieText) - .Font(LargeDetailsFont) - .Justification(ETextJustify::Center) - .ColorAndOpacity(LabelColor) - ]; - - InCategory.AddCustomRow(FText::GetEmpty()) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .Padding(0, 0, 5, 0) - [ - SNew(SSeparator) - .Thickness(2.0f) - ] - ]; -} - - -void -FHoudiniAssetComponentDetails::AddSessionStatusRow(IDetailCategoryBuilder& InCategory) -{ - FDetailWidgetRow& PDGStatusRow = InCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetSessionStatusAndColor(StatusString, StatusColor); - return FText::FromString(StatusString); - }) - .ColorAndOpacity_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetSessionStatusAndColor(StatusString, StatusColor); - return FSlateColor(StatusColor); - }) - ] - ]; -} - -bool -FHoudiniAssetComponentDetails::GetSessionStatusAndColor( - FString& OutStatusString, FLinearColor& OutStatusColor) -{ - OutStatusString = FString(); - OutStatusColor = FLinearColor::White; - - const EHoudiniSessionStatus& SessionStatus = FHoudiniEngine::Get().GetSessionStatus(); - - switch (SessionStatus) - { - case EHoudiniSessionStatus::NotStarted: - // Session not initialized yet - OutStatusString = TEXT("Houdini Engine Session - Not Started"); - OutStatusColor = FLinearColor::White; - break; - - case EHoudiniSessionStatus::Connected: - // Session successfully started - OutStatusString = TEXT("Houdini Engine Session READY"); - OutStatusColor = FLinearColor::Green; - break; - case EHoudiniSessionStatus::Stopped: - // Session stopped - OutStatusString = TEXT("Houdini Engine Session STOPPED"); - OutStatusColor = FLinearColor(1.0f, 0.5f, 0.0f); - break; - case EHoudiniSessionStatus::Failed: - // Session failed to be created/connected - OutStatusString = TEXT("Houdini Engine Session FAILED"); - OutStatusColor = FLinearColor::Red; - break; - case EHoudiniSessionStatus::Lost: - // Session Lost (HARS/Houdini Crash?) - OutStatusString = TEXT("Houdini Engine Session LOST"); - OutStatusColor = FLinearColor::Red; - break; - case EHoudiniSessionStatus::NoLicense: - // Failed to acquire a license - OutStatusString = TEXT("Houdini Engine Session FAILED - No License"); - OutStatusColor = FLinearColor::Red; - break; - case EHoudiniSessionStatus::None: - // Session type set to None - OutStatusString = TEXT("Houdini Engine Session DISABLED"); - OutStatusColor = FLinearColor::White; - break; - default: - case EHoudiniSessionStatus::Invalid: - OutStatusString = TEXT("Houdini Engine Session INVALID"); - OutStatusColor = FLinearColor::Red; - break; - } - - // Handle a few specific case for active session - if (SessionStatus == EHoudiniSessionStatus::Connected) - { - bool bPaused = !FHoudiniEngine::Get().IsCookingEnabled(); - bool bSSync = FHoudiniEngine::Get().IsSessionSyncEnabled(); - if (bPaused) - { - OutStatusString = TEXT("Houdini Engine Session PAUSED"); - OutStatusColor = FLinearColor::Yellow; - } - /* - else if (bSSync) - { - OutStatusString = TEXT("Houdini Engine Session Sync READY"); - OutStatusColor = FLinearColor::Blue; - } - */ - } - - return true; -} - -void -FHoudiniAssetComponentDetails::AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC) -{ - FString CategoryName = "Bake"; - InCategory.AddGroup(FName(*CategoryName), FText::FromString(CategoryName), false, false); - -} - -void -FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) -{ - // Get all components which are being customized. - TArray< TWeakObjectPtr< UObject > > ObjectsCustomized; - DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); - - // Extract the Houdini Asset Component to detail - for (int32 i = 0; i < ObjectsCustomized.Num(); ++i) - { - if (ObjectsCustomized[i].IsValid()) - { - UObject * Object = ObjectsCustomized[i].Get(); - if (Object) - { - UHoudiniAssetComponent * HAC = Cast< UHoudiniAssetComponent >(Object); - if (HAC && !HAC->IsPendingKill()) - HoudiniAssetComponents.Add(HAC); - } - } - } - - // Check if we'll need to add indie license labels - bool bIsIndieLicense = FHoudiniEngine::Get().IsLicenseIndie(); - - // To handle multiselection parameter edit, we try to group the selected components by their houdini assets - // TODO? ignore multiselection if all are not the same HDA? - // TODO do the same for inputs - TMap, TArray>> HoudiniAssetToHACs; - for (auto HAC : HoudiniAssetComponents) - { - TWeakObjectPtr HoudiniAsset = HAC->GetHoudiniAsset(); - if (!HoudiniAsset.IsValid()) - continue; - - TArray>& ValueRef = HoudiniAssetToHACs.FindOrAdd(HoudiniAsset); - ValueRef.Add(HAC); - } - - for (auto Iter : HoudiniAssetToHACs) - { - TArray> HACs = Iter.Value; - if (HACs.Num() < 1) - continue; - - TWeakObjectPtr MainComponent = HACs[0]; - if (!MainComponent.IsValid()) - continue; - - // If we have selected more than one component that have different HDAs, - // we'll want to separate the param/input/output category for each HDA - FString MultiSelectionIdentifier = FString(); - if (HoudiniAssetToHACs.Num() > 1) - { - MultiSelectionIdentifier = TEXT("("); - if (MainComponent->GetHoudiniAsset()) - MultiSelectionIdentifier += MainComponent->GetHoudiniAsset()->GetName(); - MultiSelectionIdentifier += TEXT(")"); - } - - /* - // Handled by the UPROPERTIES on the component in v2! - // Edit the Houdini details category - IDetailCategoryBuilder & HoudiniAssetCategory = - DetailBuilder.EditCategory("HoudiniAsset", FText::GetEmpty(), ECategoryPriority::Important); - */ - - // - // 0. HOUDINI ASSET DETAILS - // - - { - FString HoudiniEngineCategoryName = "Houdini Engine"; - HoudiniEngineCategoryName += MultiSelectionIdentifier; - - // Create Houdini Engine details category - IDetailCategoryBuilder & HouEngineCategory = - DetailBuilder.EditCategory(*HoudiniEngineCategoryName, FText::FromString("Houdini Engine"), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouEngineCategory); - - TArray MultiSelectedHACs; - for (auto& NextHACWeakPtr : HACs) - { - if (NextHACWeakPtr.IsValid()) - MultiSelectedHACs.Add(NextHACWeakPtr.Get()); - } - - HoudiniEngineDetails->CreateWidget(HouEngineCategory, MultiSelectedHACs); - } - - // - // 1. PDG ASSET LINK (if available) - // - if (MainComponent->GetPDGAssetLink()) - { - FString PDGCatName = "HoudiniPDGAssetLink"; - PDGCatName += MultiSelectionIdentifier; - - // Create the PDG Asset Link details category - IDetailCategoryBuilder & HouPDGCategory = - DetailBuilder.EditCategory(*PDGCatName, FText::FromString("Houdini - PDG Asset Link"), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouPDGCategory); - - // TODO: Handle multi selection of outputs like params/inputs? - - - PDGDetails->CreateWidget(HouPDGCategory, MainComponent->GetPDGAssetLink()/*, MainComponent*/); - } - - - // - // 2. PARAMETER DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString ParamCatName = "HoudiniParameters"; - ParamCatName += MultiSelectionIdentifier; - - // Create the Parameters details category - IDetailCategoryBuilder & HouParameterCategory = - DetailBuilder.EditCategory(*ParamCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if(bIsIndieLicense) - AddIndieLicenseRow(HouParameterCategory); - - // Iterate through the component's parameters - for (int32 ParamIdx = 0; ParamIdx < MainComponent->GetNumParameters(); ParamIdx++) - { - // We only want to create root parameters here, they will recursively create child parameters. - UHoudiniParameter* CurrentParam = MainComponent->GetParameterAt(ParamIdx); - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - // TODO: remove ? unneeded? - // ensure the parameter is actually owned by a HAC - /*const TWeakObjectPtr Owner = Cast(CurrentParam->GetOuter()); - if (!Owner.IsValid()) - continue;*/ - - // Build an array of edited parameter for multi edit - TArray EditedParams; - EditedParams.Add(CurrentParam); - - // Add the corresponding params in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniParameter* LinkedParam = HACs[LinkedIdx]->GetParameterAt(ParamIdx); - if (!LinkedParam || LinkedParam->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if ( !LinkedParam->Matches(*CurrentParam) ) - { - LinkedParam = MainComponent->FindMatchingParameter(CurrentParam); - if (!LinkedParam || LinkedParam->IsPendingKill() || LinkedParam->IsChildParameter()) - continue; - } - - EditedParams.Add(LinkedParam); - } - - ParameterDetails->CreateWidget(HouParameterCategory, EditedParams); - } - - /*** HOUDINI HANDLE DETAILS ***/ - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString HandleCatName = "HoudiniHandles"; - HandleCatName += MultiSelectionIdentifier; - - // Create the Parameters details category - IDetailCategoryBuilder & HouHandleCategory = - DetailBuilder.EditCategory(*HandleCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouHandleCategory); - - // Iterate through the component's Houdini handles - for (int32 HandleIdx = 0; HandleIdx < MainComponent->GetNumHandles(); ++HandleIdx) - { - UHoudiniHandleComponent* CurrentHandleComponent = MainComponent->GetHandleComponentAt(HandleIdx); - - if (!CurrentHandleComponent || CurrentHandleComponent->IsPendingKill()) - continue; - - TArray EditedHandles; - EditedHandles.Add(CurrentHandleComponent); - - // Add the corresponding params in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); ++LinkedIdx) - { - UHoudiniHandleComponent* LinkedHandle = HACs[LinkedIdx]->GetHandleComponentAt(HandleIdx); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) - continue; - - // Linked handles should match the main param, if not try to find one that matches - if (!LinkedHandle->Matches(*CurrentHandleComponent)) - { - LinkedHandle = MainComponent->FindMatchingHandle(CurrentHandleComponent); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) - continue; - } - - EditedHandles.Add(LinkedHandle); - } - - FHoudiniHandleDetails::CreateWidget(HouHandleCategory, EditedHandles); - } - - - // - // 3. INPUT DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString InputCatName = "HoudiniInputs"; - InputCatName += MultiSelectionIdentifier; - - // Create the input details category - IDetailCategoryBuilder & HouInputCategory = - DetailBuilder.EditCategory(*InputCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // If we are running Houdini Engine Indie license, we need to display a special label. - if (bIsIndieLicense) - AddIndieLicenseRow(HouInputCategory); - - // Iterate through the component's inputs - for (int32 InputIdx = 0; InputIdx < MainComponent->GetNumInputs(); InputIdx++) - { - UHoudiniInput* CurrentInput = MainComponent->GetInputAt(InputIdx); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (!MainComponent->IsInputTypeSupported(CurrentInput->GetInputType())) - continue; - - // Object path parameter inputs are displayed by the ParameterDetails - skip them - if (CurrentInput->IsObjectPathParameter()) - continue; - - // Build an array of edited inputs for multi edit - TArray EditedInputs; - EditedInputs.Add(CurrentInput); - - // Add the corresponding inputs in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniInput* LinkedInput = HACs[LinkedIdx]->GetInputAt(InputIdx); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if (!LinkedInput->Matches(*CurrentInput)) - { - LinkedInput = MainComponent->FindMatchingInput(CurrentInput); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - } - - EditedInputs.Add(LinkedInput); - } - - FHoudiniInputDetails::CreateWidget(HouInputCategory, EditedInputs); - } - - // - // 4. OUTPUT DETAILS - // - - // If we have selected more than one component that have different HDAs, - // we need to create multiple categories one for each different HDA - FString OutputCatName = "HoudiniOutputs"; - OutputCatName += MultiSelectionIdentifier; - - // Create the output details category - IDetailCategoryBuilder & HouOutputCategory = - DetailBuilder.EditCategory(*OutputCatName, FText::GetEmpty(), ECategoryPriority::Important); - - // Iterate through the component's outputs - for (int32 OutputIdx = 0; OutputIdx < MainComponent->GetNumOutputs(); OutputIdx++) - { - UHoudiniOutput* CurrentOutput = MainComponent->GetOutputAt(OutputIdx); - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - // Build an array of edited inpoutputs for multi edit - TArray EditedOutputs; - EditedOutputs.Add(CurrentOutput); - - // Add the corresponding outputs in the other HAC - for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) - { - UHoudiniOutput* LinkedOutput = HACs[LinkedIdx]->GetOutputAt(OutputIdx); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) - continue; - - /* - // Linked output should match the main output! If not try to find one that matches - if (!LinkedOutput->Matches(*CurrentOutput)) - { - LinkedOutput = MainComponent->FindMatchingInput(CurrentOutput); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) - continue; - } - */ - - EditedOutputs.Add(LinkedOutput); - } - - // TODO: Handle multi selection of outputs like params/inputs? - OutputDetails->CreateWidget(HouOutputCategory, EditedOutputs); - } - } -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetComponentDetails.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAsset.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniParameter.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniParameterDetails.h" +#include "HoudiniInput.h" +#include "HoudiniInputDetails.h" +#include "HoudiniHandleDetails.h" +#include "HoudiniOutput.h" +#include "HoudiniOutputDetails.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Images/SImage.h" + +#include "PropertyCustomizationHelpers.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +TSharedRef< IDetailCustomization > +FHoudiniAssetComponentDetails::MakeInstance() +{ + return MakeShareable(new FHoudiniAssetComponentDetails); +} + +FHoudiniAssetComponentDetails::FHoudiniAssetComponentDetails() +{ + OutputDetails = MakeShared(); + ParameterDetails = MakeShared(); + PDGDetails = MakeShared(); + HoudiniEngineDetails = MakeShared(); +} + + +FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() +{ + // The ramp param's curves are added to root to avoid garbage collection + // We need to remove those curves from the root when the details classes are destroyed. + if (ParameterDetails.IsValid()) + { + FHoudiniParameterDetails* ParamDetailsPtr = ParameterDetails.Get(); + + for (auto& CurFloatRampCurveEditor : ParamDetailsPtr->CreatedFloatCurveEditors) + { + if (CurFloatRampCurveEditor.IsValid()) + { + CurFloatRampCurveEditor->HoudiniFloatRampCurve = nullptr; + CurFloatRampCurveEditor->SetCurveOwner(nullptr); + } + } + for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) + { + if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) + continue; + + CurFloatRampCurve->RemoveFromRoot(); + } + + for (auto& CurColorRampCurveEditor : ParamDetailsPtr->CreatedColorGradientEditors) + { + if (CurColorRampCurveEditor.IsValid()) + { + CurColorRampCurveEditor->HoudiniColorRampCurve = nullptr; + CurColorRampCurveEditor->SetCurveOwner(nullptr); + } + } + for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) + { + if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) + continue; + + CurColorRampCurve->RemoveFromRoot(); + } + + ParamDetailsPtr->CreatedFloatCurveEditors.Empty(); + ParamDetailsPtr->CreatedColorGradientEditors.Empty(); + ParamDetailsPtr->CreatedFloatRampCurves.Empty(); + ParamDetailsPtr->CreatedColorRampCurves.Empty(); + } +} + +void +FHoudiniAssetComponentDetails::AddIndieLicenseRow(IDetailCategoryBuilder& InCategory) +{ + FText IndieText = + FText::FromString(TEXT("Houdini Engine Indie - For Limited Commercial Use Only")); + + FSlateFontInfo LargeDetailsFont = IDetailLayoutBuilder::GetDetailFontBold(); + LargeDetailsFont.Size += 2; + + FSlateColor LabelColor = FLinearColor(1.0f, 1.0f, 0.0f, 1.0f); + + InCategory.AddCustomRow(FText::GetEmpty()) + [ + SNew(STextBlock) + .Text(IndieText) + .ToolTipText(IndieText) + .Font(LargeDetailsFont) + .Justification(ETextJustify::Center) + .ColorAndOpacity(LabelColor) + ]; + + InCategory.AddCustomRow(FText::GetEmpty()) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(0, 0, 5, 0) + [ + SNew(SSeparator) + .Thickness(2.0f) + ] + ]; +} + + +void +FHoudiniAssetComponentDetails::AddSessionStatusRow(IDetailCategoryBuilder& InCategory) +{ + FDetailWidgetRow& PDGStatusRow = InCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetSessionStatusAndColor(StatusString, StatusColor); + return FText::FromString(StatusString); + }) + .ColorAndOpacity_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetSessionStatusAndColor(StatusString, StatusColor); + return FSlateColor(StatusColor); + }) + ] + ]; +} + +bool +FHoudiniAssetComponentDetails::GetSessionStatusAndColor( + FString& OutStatusString, FLinearColor& OutStatusColor) +{ + OutStatusString = FString(); + OutStatusColor = FLinearColor::White; + + const EHoudiniSessionStatus& SessionStatus = FHoudiniEngine::Get().GetSessionStatus(); + + switch (SessionStatus) + { + case EHoudiniSessionStatus::NotStarted: + // Session not initialized yet + OutStatusString = TEXT("Houdini Engine Session - Not Started"); + OutStatusColor = FLinearColor::White; + break; + + case EHoudiniSessionStatus::Connected: + // Session successfully started + OutStatusString = TEXT("Houdini Engine Session READY"); + OutStatusColor = FLinearColor::Green; + break; + case EHoudiniSessionStatus::Stopped: + // Session stopped + OutStatusString = TEXT("Houdini Engine Session STOPPED"); + OutStatusColor = FLinearColor(1.0f, 0.5f, 0.0f); + break; + case EHoudiniSessionStatus::Failed: + // Session failed to be created/connected + OutStatusString = TEXT("Houdini Engine Session FAILED"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::Lost: + // Session Lost (HARS/Houdini Crash?) + OutStatusString = TEXT("Houdini Engine Session LOST"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::NoLicense: + // Failed to acquire a license + OutStatusString = TEXT("Houdini Engine Session FAILED - No License"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniSessionStatus::None: + // Session type set to None + OutStatusString = TEXT("Houdini Engine Session DISABLED"); + OutStatusColor = FLinearColor::White; + break; + default: + case EHoudiniSessionStatus::Invalid: + OutStatusString = TEXT("Houdini Engine Session INVALID"); + OutStatusColor = FLinearColor::Red; + break; + } + + // Handle a few specific case for active session + if (SessionStatus == EHoudiniSessionStatus::Connected) + { + bool bPaused = !FHoudiniEngine::Get().IsCookingEnabled(); + bool bSSync = FHoudiniEngine::Get().IsSessionSyncEnabled(); + if (bPaused) + { + OutStatusString = TEXT("Houdini Engine Session PAUSED"); + OutStatusColor = FLinearColor::Yellow; + } + /* + else if (bSSync) + { + OutStatusString = TEXT("Houdini Engine Session Sync READY"); + OutStatusColor = FLinearColor::Blue; + } + */ + } + + return true; +} + +void +FHoudiniAssetComponentDetails::AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC) +{ + FString CategoryName = "Bake"; + InCategory.AddGroup(FName(*CategoryName), FText::FromString(CategoryName), false, false); + +} + +void +FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + // Get all components which are being customized. + TArray< TWeakObjectPtr< UObject > > ObjectsCustomized; + DetailBuilder.GetObjectsBeingCustomized(ObjectsCustomized); + + // Extract the Houdini Asset Component to detail + for (int32 i = 0; i < ObjectsCustomized.Num(); ++i) + { + if (ObjectsCustomized[i].IsValid()) + { + UObject * Object = ObjectsCustomized[i].Get(); + if (Object) + { + UHoudiniAssetComponent * HAC = Cast< UHoudiniAssetComponent >(Object); + if (HAC && !HAC->IsPendingKill()) + HoudiniAssetComponents.Add(HAC); + } + } + } + + // Check if we'll need to add indie license labels + bool bIsIndieLicense = FHoudiniEngine::Get().IsLicenseIndie(); + + // To handle multiselection parameter edit, we try to group the selected components by their houdini assets + // TODO? ignore multiselection if all are not the same HDA? + // TODO do the same for inputs + TMap, TArray>> HoudiniAssetToHACs; + for (auto HAC : HoudiniAssetComponents) + { + TWeakObjectPtr HoudiniAsset = HAC->GetHoudiniAsset(); + if (!HoudiniAsset.IsValid()) + continue; + + TArray>& ValueRef = HoudiniAssetToHACs.FindOrAdd(HoudiniAsset); + ValueRef.Add(HAC); + } + + for (auto Iter : HoudiniAssetToHACs) + { + TArray> HACs = Iter.Value; + if (HACs.Num() < 1) + continue; + + TWeakObjectPtr MainComponent = HACs[0]; + if (!MainComponent.IsValid()) + continue; + + // If we have selected more than one component that have different HDAs, + // we'll want to separate the param/input/output category for each HDA + FString MultiSelectionIdentifier = FString(); + if (HoudiniAssetToHACs.Num() > 1) + { + MultiSelectionIdentifier = TEXT("("); + if (MainComponent->GetHoudiniAsset()) + MultiSelectionIdentifier += MainComponent->GetHoudiniAsset()->GetName(); + MultiSelectionIdentifier += TEXT(")"); + } + + /* + // Handled by the UPROPERTIES on the component in v2! + // Edit the Houdini details category + IDetailCategoryBuilder & HoudiniAssetCategory = + DetailBuilder.EditCategory("HoudiniAsset", FText::GetEmpty(), ECategoryPriority::Important); + */ + + // + // 0. HOUDINI ASSET DETAILS + // + + { + FString HoudiniEngineCategoryName = "Houdini Engine"; + HoudiniEngineCategoryName += MultiSelectionIdentifier; + + // Create Houdini Engine details category + IDetailCategoryBuilder & HouEngineCategory = + DetailBuilder.EditCategory(*HoudiniEngineCategoryName, FText::FromString("Houdini Engine"), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouEngineCategory); + + TArray MultiSelectedHACs; + for (auto& NextHACWeakPtr : HACs) + { + if (NextHACWeakPtr.IsValid()) + MultiSelectedHACs.Add(NextHACWeakPtr.Get()); + } + + HoudiniEngineDetails->CreateWidget(HouEngineCategory, MultiSelectedHACs); + } + + // + // 1. PDG ASSET LINK (if available) + // + if (MainComponent->GetPDGAssetLink()) + { + FString PDGCatName = "HoudiniPDGAssetLink"; + PDGCatName += MultiSelectionIdentifier; + + // Create the PDG Asset Link details category + IDetailCategoryBuilder & HouPDGCategory = + DetailBuilder.EditCategory(*PDGCatName, FText::FromString("Houdini - PDG Asset Link"), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouPDGCategory); + + // TODO: Handle multi selection of outputs like params/inputs? + + + PDGDetails->CreateWidget(HouPDGCategory, MainComponent->GetPDGAssetLink()/*, MainComponent*/); + } + + + // + // 2. PARAMETER DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString ParamCatName = "HoudiniParameters"; + ParamCatName += MultiSelectionIdentifier; + + // Create the Parameters details category + IDetailCategoryBuilder & HouParameterCategory = + DetailBuilder.EditCategory(*ParamCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if(bIsIndieLicense) + AddIndieLicenseRow(HouParameterCategory); + + // Iterate through the component's parameters + for (int32 ParamIdx = 0; ParamIdx < MainComponent->GetNumParameters(); ParamIdx++) + { + // We only want to create root parameters here, they will recursively create child parameters. + UHoudiniParameter* CurrentParam = MainComponent->GetParameterAt(ParamIdx); + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + // TODO: remove ? unneeded? + // ensure the parameter is actually owned by a HAC + /*const TWeakObjectPtr Owner = Cast(CurrentParam->GetOuter()); + if (!Owner.IsValid()) + continue;*/ + + // Build an array of edited parameter for multi edit + TArray EditedParams; + EditedParams.Add(CurrentParam); + + // Add the corresponding params in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniParameter* LinkedParam = HACs[LinkedIdx]->GetParameterAt(ParamIdx); + if (!LinkedParam || LinkedParam->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if ( !LinkedParam->Matches(*CurrentParam) ) + { + LinkedParam = MainComponent->FindMatchingParameter(CurrentParam); + if (!LinkedParam || LinkedParam->IsPendingKill() || LinkedParam->IsChildParameter()) + continue; + } + + EditedParams.Add(LinkedParam); + } + + ParameterDetails->CreateWidget(HouParameterCategory, EditedParams); + } + + /*** HOUDINI HANDLE DETAILS ***/ + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString HandleCatName = "HoudiniHandles"; + HandleCatName += MultiSelectionIdentifier; + + // Create the Parameters details category + IDetailCategoryBuilder & HouHandleCategory = + DetailBuilder.EditCategory(*HandleCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouHandleCategory); + + // Iterate through the component's Houdini handles + for (int32 HandleIdx = 0; HandleIdx < MainComponent->GetNumHandles(); ++HandleIdx) + { + UHoudiniHandleComponent* CurrentHandleComponent = MainComponent->GetHandleComponentAt(HandleIdx); + + if (!CurrentHandleComponent || CurrentHandleComponent->IsPendingKill()) + continue; + + TArray EditedHandles; + EditedHandles.Add(CurrentHandleComponent); + + // Add the corresponding params in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); ++LinkedIdx) + { + UHoudiniHandleComponent* LinkedHandle = HACs[LinkedIdx]->GetHandleComponentAt(HandleIdx); + if (!LinkedHandle || LinkedHandle->IsPendingKill()) + continue; + + // Linked handles should match the main param, if not try to find one that matches + if (!LinkedHandle->Matches(*CurrentHandleComponent)) + { + LinkedHandle = MainComponent->FindMatchingHandle(CurrentHandleComponent); + if (!LinkedHandle || LinkedHandle->IsPendingKill()) + continue; + } + + EditedHandles.Add(LinkedHandle); + } + + FHoudiniHandleDetails::CreateWidget(HouHandleCategory, EditedHandles); + } + + + // + // 3. INPUT DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString InputCatName = "HoudiniInputs"; + InputCatName += MultiSelectionIdentifier; + + // Create the input details category + IDetailCategoryBuilder & HouInputCategory = + DetailBuilder.EditCategory(*InputCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // If we are running Houdini Engine Indie license, we need to display a special label. + if (bIsIndieLicense) + AddIndieLicenseRow(HouInputCategory); + + // Iterate through the component's inputs + for (int32 InputIdx = 0; InputIdx < MainComponent->GetNumInputs(); InputIdx++) + { + UHoudiniInput* CurrentInput = MainComponent->GetInputAt(InputIdx); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (!MainComponent->IsInputTypeSupported(CurrentInput->GetInputType())) + continue; + + // Object path parameter inputs are displayed by the ParameterDetails - skip them + if (CurrentInput->IsObjectPathParameter()) + continue; + + // Build an array of edited inputs for multi edit + TArray EditedInputs; + EditedInputs.Add(CurrentInput); + + // Add the corresponding inputs in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniInput* LinkedInput = HACs[LinkedIdx]->GetInputAt(InputIdx); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if (!LinkedInput->Matches(*CurrentInput)) + { + LinkedInput = MainComponent->FindMatchingInput(CurrentInput); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + } + + EditedInputs.Add(LinkedInput); + } + + FHoudiniInputDetails::CreateWidget(HouInputCategory, EditedInputs); + } + + // + // 4. OUTPUT DETAILS + // + + // If we have selected more than one component that have different HDAs, + // we need to create multiple categories one for each different HDA + FString OutputCatName = "HoudiniOutputs"; + OutputCatName += MultiSelectionIdentifier; + + // Create the output details category + IDetailCategoryBuilder & HouOutputCategory = + DetailBuilder.EditCategory(*OutputCatName, FText::GetEmpty(), ECategoryPriority::Important); + + // Iterate through the component's outputs + for (int32 OutputIdx = 0; OutputIdx < MainComponent->GetNumOutputs(); OutputIdx++) + { + UHoudiniOutput* CurrentOutput = MainComponent->GetOutputAt(OutputIdx); + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + // Build an array of edited inpoutputs for multi edit + TArray EditedOutputs; + EditedOutputs.Add(CurrentOutput); + + // Add the corresponding outputs in the other HAC + for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) + { + UHoudiniOutput* LinkedOutput = HACs[LinkedIdx]->GetOutputAt(OutputIdx); + if (!LinkedOutput || LinkedOutput->IsPendingKill()) + continue; + + /* + // Linked output should match the main output! If not try to find one that matches + if (!LinkedOutput->Matches(*CurrentOutput)) + { + LinkedOutput = MainComponent->FindMatchingInput(CurrentOutput); + if (!LinkedOutput || LinkedOutput->IsPendingKill()) + continue; + } + */ + + EditedOutputs.Add(LinkedOutput); + } + + // TODO: Handle multi selection of outputs like params/inputs? + OutputDetails->CreateWidget(HouOutputCategory, EditedOutputs); + } + } +} + + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h index cfcc9980c..f640fb21b 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.h @@ -1,91 +1,91 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "IDetailCustomization.h" -#include "HoudiniPDGDetails.h" -#include "HoudiniOutputDetails.h" -#include "HoudiniParameterDetails.h" -#include "HoudiniEngineDetails.h" - -class UHoudiniAssetComponent; -class UStaticMesh; - -class FHoudiniAssetComponentDetails : public IDetailCustomization -{ -public: - - // Constructor. - FHoudiniAssetComponentDetails(); - - // Destructor. - virtual ~FHoudiniAssetComponentDetails(); - - // IDetailCustomization methods. - virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; - - // Create an instance of this detail layout class. - static TSharedRef MakeInstance(); - - // Adds a text row that indicate the status of the Houdini Session - static void AddSessionStatusRow(IDetailCategoryBuilder& InCategory); - - static bool GetSessionStatusAndColor(FString& OutStatusString, FLinearColor& OutStatusColor); - -private: - - // Adds a text row indicate we're using a Houdini indie license - void AddIndieLicenseRow(IDetailCategoryBuilder& InCategory); - - // Adds a category for baking options - void AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC); - - // Handler for double clicking the static mesh thumbnail, opens the editor. - FReply OnThumbnailDoubleClick( - const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent, UObject* Object); - - -private: - - // Components which are being customized. - TArray> HoudiniAssetComponents; - - // Structure holding the output's details - TSharedPtr OutputDetails; - - // Structure holding the parameter's details - TSharedPtr ParameterDetails; - - // Structure holding the PDG Asset Link's details - TSharedPtr PDGDetails; - - // Structure holding the HoudiniAsset details - TSharedPtr HoudiniEngineDetails; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" +#include "HoudiniPDGDetails.h" +#include "HoudiniOutputDetails.h" +#include "HoudiniParameterDetails.h" +#include "HoudiniEngineDetails.h" + +class UHoudiniAssetComponent; +class UStaticMesh; + +class FHoudiniAssetComponentDetails : public IDetailCustomization +{ +public: + + // Constructor. + FHoudiniAssetComponentDetails(); + + // Destructor. + virtual ~FHoudiniAssetComponentDetails(); + + // IDetailCustomization methods. + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + + // Create an instance of this detail layout class. + static TSharedRef MakeInstance(); + + // Adds a text row that indicate the status of the Houdini Session + static void AddSessionStatusRow(IDetailCategoryBuilder& InCategory); + + static bool GetSessionStatusAndColor(FString& OutStatusString, FLinearColor& OutStatusColor); + +private: + + // Adds a text row indicate we're using a Houdini indie license + void AddIndieLicenseRow(IDetailCategoryBuilder& InCategory); + + // Adds a category for baking options + void AddBakeMenu(IDetailCategoryBuilder& InCategory, UHoudiniAssetComponent* HAC); + + // Handler for double clicking the static mesh thumbnail, opens the editor. + FReply OnThumbnailDoubleClick( + const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent, UObject* Object); + + +private: + + // Components which are being customized. + TArray> HoudiniAssetComponents; + + // Structure holding the output's details + TSharedPtr OutputDetails; + + // Structure holding the parameter's details + TSharedPtr ParameterDetails; + + // Structure holding the PDG Asset Link's details + TSharedPtr PDGDetails; + + // Structure holding the HoudiniAsset details + TSharedPtr HoudiniEngineDetails; + + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp index 6b0ad57a0..4b1961337 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.cpp @@ -1,209 +1,209 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniAsset.h" - -#include "EditorFramework/AssetImportData.h" -#include "Misc/FileHelper.h" -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniAssetFactory::UHoudiniAssetFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - // This factory is responsible for manufacturing HoudiniEngine assets. - SupportedClass = UHoudiniAsset::StaticClass(); - - // This factory does not manufacture new objects from scratch. - bCreateNew = false; - - // This factory will not open the editor for each new object. - bEditAfterNew = false; - - // This factory will import objects from files. - bEditorImport = true; - - // Factory does not import objects from text. - bText = false; - - // Add supported formats. - Formats.Add(TEXT("otl;Houdini Engine Asset")); - Formats.Add(TEXT("otllc;Houdini Engine Limited Commercial Asset")); - Formats.Add(TEXT("otlnc;Houdini Engine Non-Commercial Asset")); - Formats.Add(TEXT("hda;Houdini Engine Asset")); - Formats.Add(TEXT("hdalc;Houdini Engine Limited Commercial Asset")); - Formats.Add(TEXT("hdanc;Houdini Engine Non-Commercial Asset")); - Formats.Add(TEXT("hdalibrary;Houdini Engine Expanded Asset")); -} - -bool -UHoudiniAssetFactory::DoesSupportClass(UClass * Class) -{ - return Class == SupportedClass; -} - -FText -UHoudiniAssetFactory::GetDisplayName() const -{ - return LOCTEXT("HoudiniAssetFactoryDescription", "Houdini Engine Asset"); -} - -UObject * -UHoudiniAssetFactory::FactoryCreateBinary( - UClass * InClass, UObject* InParent, FName InName, EObjectFlags Flags, - UObject * Context, const TCHAR * Type, const uint8 *& Buffer, - const uint8 * BufferEnd, FFeedbackContext * Warn ) -{ - // Broadcast notification that a new asset is being imported. - GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type); - - // Create a new asset. - UHoudiniAsset * HoudiniAsset = NewObject< UHoudiniAsset >(InParent, InName, Flags); - HoudiniAsset->CreateAsset(Buffer, BufferEnd, UFactory::GetCurrentFilename()); - - // Create reimport information. - UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(HoudiniAsset, UAssetImportData::StaticClass()); - HoudiniAsset->AssetImportData = AssetImportData; - } - - AssetImportData->Update(UFactory::GetCurrentFilename()); - - // Broadcast notification that the new asset has been imported. - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, HoudiniAsset); - - return HoudiniAsset; -} - -UObject* -UHoudiniAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) -{ - // "houdini.hdalibrary" files (expanded hda / hda folder) need a special treatment, - // but ".hda" files can be loaded normally - FString FileExtension = FPaths::GetExtension(Filename); - if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) != 0) - return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); - - // Make sure the file name is sections.list - FString NameOfFile = FPaths::GetBaseFilename(Filename); - if (NameOfFile.Compare(TEXT("houdini"), ESearchCase::IgnoreCase) != 0) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); - return nullptr; - } - - // Make sure that the proper .list file is loaded - FString PathToFile = FPaths::GetPath(Filename); - if (PathToFile.Find(TEXT(".hda")) != (PathToFile.Len() - 4)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); - return nullptr; - } - - FString NewFilename = PathToFile; - FString NewFileNameNoHDA = FPaths::GetBaseFilename(PathToFile); - FName NewIname = FName(*NewFileNameNoHDA); - FString NewFileExtension = FPaths::GetExtension(NewFilename); - - // load as binary - TArray Data; - if (!FFileHelper::LoadFileToArray(Data, *Filename)) - { - HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s' to array"), *Filename); - return nullptr; - } - - Data.Add(0); - ParseParms(Parms); - const uint8* Ptr = &Data[0]; - - return FactoryCreateBinary(InClass, InParent, NewIname, Flags, nullptr, *NewFileExtension, Ptr, Ptr + Data.Num() - 1, Warn); -} - -bool -UHoudiniAssetFactory::CanReimport(UObject * Obj, TArray< FString > & OutFilenames) -{ - UHoudiniAsset * HoudiniAsset = Cast(Obj); - if (HoudiniAsset) - { - UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; - if (AssetImportData) - OutFilenames.Add(AssetImportData->GetFirstFilename()); - else - OutFilenames.Add(TEXT("")); - - return true; - } - - return false; -} - -void -UHoudiniAssetFactory::SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) -{ - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); - if (HoudiniAsset && (1 == NewReimportPaths.Num())) - HoudiniAsset->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); -} - -EReimportResult::Type -UHoudiniAssetFactory::Reimport(UObject * Obj) -{ - UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); - if (HoudiniAsset && HoudiniAsset->AssetImportData) - { - // Make sure file is valid and exists. - const FString & Filename = HoudiniAsset->AssetImportData->GetFirstFilename(); - - if (!Filename.Len() || IFileManager::Get().FileSize(*Filename) == INDEX_NONE) - return EReimportResult::Failed; - - if (UFactory::StaticImportObject( - HoudiniAsset->GetClass(), HoudiniAsset->GetOuter(), *HoudiniAsset->GetName(), - RF_Public | RF_Standalone, *Filename, NULL, this)) - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimported successfully.")); - - if (HoudiniAsset->GetOuter()) - HoudiniAsset->GetOuter()->MarkPackageDirty(); - else - HoudiniAsset->MarkPackageDirty(); - - return EReimportResult::Succeeded; - } - } - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); - return EReimportResult::Failed; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniAsset.h" + +#include "EditorFramework/AssetImportData.h" +#include "Misc/FileHelper.h" +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniAssetFactory::UHoudiniAssetFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + // This factory is responsible for manufacturing HoudiniEngine assets. + SupportedClass = UHoudiniAsset::StaticClass(); + + // This factory does not manufacture new objects from scratch. + bCreateNew = false; + + // This factory will not open the editor for each new object. + bEditAfterNew = false; + + // This factory will import objects from files. + bEditorImport = true; + + // Factory does not import objects from text. + bText = false; + + // Add supported formats. + Formats.Add(TEXT("otl;Houdini Engine Asset")); + Formats.Add(TEXT("otllc;Houdini Engine Limited Commercial Asset")); + Formats.Add(TEXT("otlnc;Houdini Engine Non-Commercial Asset")); + Formats.Add(TEXT("hda;Houdini Engine Asset")); + Formats.Add(TEXT("hdalc;Houdini Engine Limited Commercial Asset")); + Formats.Add(TEXT("hdanc;Houdini Engine Non-Commercial Asset")); + Formats.Add(TEXT("hdalibrary;Houdini Engine Expanded Asset")); +} + +bool +UHoudiniAssetFactory::DoesSupportClass(UClass * Class) +{ + return Class == SupportedClass; +} + +FText +UHoudiniAssetFactory::GetDisplayName() const +{ + return LOCTEXT("HoudiniAssetFactoryDescription", "Houdini Engine Asset"); +} + +UObject * +UHoudiniAssetFactory::FactoryCreateBinary( + UClass * InClass, UObject* InParent, FName InName, EObjectFlags Flags, + UObject * Context, const TCHAR * Type, const uint8 *& Buffer, + const uint8 * BufferEnd, FFeedbackContext * Warn ) +{ + // Broadcast notification that a new asset is being imported. + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, InName, Type); + + // Create a new asset. + UHoudiniAsset * HoudiniAsset = NewObject< UHoudiniAsset >(InParent, InName, Flags); + HoudiniAsset->CreateAsset(Buffer, BufferEnd, UFactory::GetCurrentFilename()); + + // Create reimport information. + UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(HoudiniAsset, UAssetImportData::StaticClass()); + HoudiniAsset->AssetImportData = AssetImportData; + } + + AssetImportData->Update(UFactory::GetCurrentFilename()); + + // Broadcast notification that the new asset has been imported. + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, HoudiniAsset); + + return HoudiniAsset; +} + +UObject* +UHoudiniAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) +{ + // "houdini.hdalibrary" files (expanded hda / hda folder) need a special treatment, + // but ".hda" files can be loaded normally + FString FileExtension = FPaths::GetExtension(Filename); + if (FileExtension.Compare(TEXT("hdalibrary"), ESearchCase::IgnoreCase) != 0) + return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); + + // Make sure the file name is sections.list + FString NameOfFile = FPaths::GetBaseFilename(Filename); + if (NameOfFile.Compare(TEXT("houdini"), ESearchCase::IgnoreCase) != 0) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); + return nullptr; + } + + // Make sure that the proper .list file is loaded + FString PathToFile = FPaths::GetPath(Filename); + if (PathToFile.Find(TEXT(".hda")) != (PathToFile.Len() - 4)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s'. File is not a valid extended HDA."), *Filename); + return nullptr; + } + + FString NewFilename = PathToFile; + FString NewFileNameNoHDA = FPaths::GetBaseFilename(PathToFile); + FName NewIname = FName(*NewFileNameNoHDA); + FString NewFileExtension = FPaths::GetExtension(NewFilename); + + // load as binary + TArray Data; + if (!FFileHelper::LoadFileToArray(Data, *Filename)) + { + HOUDINI_LOG_ERROR(TEXT("Failed to load file '%s' to array"), *Filename); + return nullptr; + } + + Data.Add(0); + ParseParms(Parms); + const uint8* Ptr = &Data[0]; + + return FactoryCreateBinary(InClass, InParent, NewIname, Flags, nullptr, *NewFileExtension, Ptr, Ptr + Data.Num() - 1, Warn); +} + +bool +UHoudiniAssetFactory::CanReimport(UObject * Obj, TArray< FString > & OutFilenames) +{ + UHoudiniAsset * HoudiniAsset = Cast(Obj); + if (HoudiniAsset) + { + UAssetImportData * AssetImportData = HoudiniAsset->AssetImportData; + if (AssetImportData) + OutFilenames.Add(AssetImportData->GetFirstFilename()); + else + OutFilenames.Add(TEXT("")); + + return true; + } + + return false; +} + +void +UHoudiniAssetFactory::SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); + if (HoudiniAsset && (1 == NewReimportPaths.Num())) + HoudiniAsset->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); +} + +EReimportResult::Type +UHoudiniAssetFactory::Reimport(UObject * Obj) +{ + UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >(Obj); + if (HoudiniAsset && HoudiniAsset->AssetImportData) + { + // Make sure file is valid and exists. + const FString & Filename = HoudiniAsset->AssetImportData->GetFirstFilename(); + + if (!Filename.Len() || IFileManager::Get().FileSize(*Filename) == INDEX_NONE) + return EReimportResult::Failed; + + if (UFactory::StaticImportObject( + HoudiniAsset->GetClass(), HoudiniAsset->GetOuter(), *HoudiniAsset->GetName(), + RF_Public | RF_Standalone, *Filename, NULL, this)) + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimported successfully.")); + + if (HoudiniAsset->GetOuter()) + HoudiniAsset->GetOuter()->MarkPackageDirty(); + else + HoudiniAsset->MarkPackageDirty(); + + return EReimportResult::Succeeded; + } + } + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); + return EReimportResult::Failed; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h index d07ff8061..c0c1cf333 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetFactory.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "EditorReimportHandler.h" -#include "Factories/Factory.h" -#include "HoudiniAssetFactory.generated.h" - -class UClass; -class UObject; -class FFeedbackContext; - -UCLASS(config = Editor) -class UHoudiniAssetFactory : public UFactory, public FReimportHandler -{ - GENERATED_UCLASS_BODY() - - public: - - // UFactory methods. - // return true if it supports this class - virtual bool DoesSupportClass(UClass * Class) override; - - // Returns the name of the factory for menus - virtual FText GetDisplayName() const override; - - // Create a new object by importing it from a binary buffer. - virtual UObject * FactoryCreateBinary( - UClass * InClass, UObject * InParent, FName InName, EObjectFlags Flags, - UObject * Context, const TCHAR * Type, const uint8 *& Buffer, const uint8 * BufferEnd, - FFeedbackContext * Warn) override; - - // Create a new object by importing it from a file name. - virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, - EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, - FFeedbackContext* Warn, bool& bOutOperationCanceled) override; - - // FReimportHandler methods. - // Check to see if we have a handler to manage the reimporting of the object - virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; - - // Sets the reimport path(s) for the specified object - virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; - - // Attempt to reimport the specified object from its source - virtual EReimportResult::Type Reimport(UObject * Obj) override; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "EditorReimportHandler.h" +#include "Factories/Factory.h" +#include "HoudiniAssetFactory.generated.h" + +class UClass; +class UObject; +class FFeedbackContext; + +UCLASS(config = Editor) +class UHoudiniAssetFactory : public UFactory, public FReimportHandler +{ + GENERATED_UCLASS_BODY() + + public: + + // UFactory methods. + // return true if it supports this class + virtual bool DoesSupportClass(UClass * Class) override; + + // Returns the name of the factory for menus + virtual FText GetDisplayName() const override; + + // Create a new object by importing it from a binary buffer. + virtual UObject * FactoryCreateBinary( + UClass * InClass, UObject * InParent, FName InName, EObjectFlags Flags, + UObject * Context, const TCHAR * Type, const uint8 *& Buffer, const uint8 * BufferEnd, + FFeedbackContext * Warn) override; + + // Create a new object by importing it from a file name. + virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, + EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, + FFeedbackContext* Warn, bool& bOutOperationCanceled) override; + + // FReimportHandler methods. + // Check to see if we have a handler to manage the reimporting of the object + virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; + + // Sets the reimport path(s) for the specified object + virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; + + // Attempt to reimport the specified object from its source + virtual EReimportResult::Type Reimport(UObject * Obj) override; +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp index fdc3ed96c..28e6fe17a 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp @@ -1,5838 +1,6017 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineBakeUtils.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngineUtils.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutput.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineUtils.h" -#include "UnrealLandscapeTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniStringResolver.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniEngineRuntimeUtils.h" - -#include "Engine/StaticMesh.h" -#include "Engine/World.h" -#include "RawMesh.h" -#include "UObject/Package.h" -#include "PackageTools.h" -#include "UObject/MetaData.h" -#include "AssetRegistryModule.h" -#include "Materials/Material.h" -#include "LandscapeProxy.h" -#include "LandscapeStreamingProxy.h" -#include "LandscapeInfo.h" -#include "Factories/WorldFactory.h" -#include "AssetToolsModule.h" -#include "InstancedFoliageActor.h" -#include "Components/SplineComponent.h" -#include "GameFramework/Actor.h" -#include "Engine/StaticMeshActor.h" -#include "Components/StaticMeshComponent.h" -#include "PhysicsEngine/BodySetup.h" -#include "ActorFactories/ActorFactoryStaticMesh.h" -#include "ActorFactories/ActorFactoryEmptyActor.h" -#include "BusyCursor.h" -#include "Editor.h" -#include "Kismet2/KismetEditorUtilities.h" -#include "FileHelpers.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngine.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniOutputTranslator.h" -#include "Editor/EditorEngine.h" -#include "Factories/BlueprintFactory.h" -#include "Engine/SimpleConstructionScript.h" -#include "Misc/Paths.h" -#include "HAL/FileManager.h" -#include "LandscapeEdit.h" -#include "Containers/UnrealString.h" -#include "Components/AudioComponent.h" -#include "Engine/WorldComposition.h" -#include "Kismet2/BlueprintEditorUtils.h" -#include "MaterialEditor/Public/MaterialEditingLibrary.h" -#include "MaterialGraph/MaterialGraph.h" -#include "Particles/ParticleSystemComponent.h" -#include "Sound/SoundBase.h" -#include "UObject/UnrealType.h" -#include "Math/Box.h" -#include "Misc/ScopedSlowTask.h" - -HOUDINI_BAKING_DEFINE_LOG_CATEGORY(); - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() - : Actor(nullptr) - , OutputIndex(INDEX_NONE) - , OutputObjectIdentifier() - , ActorBakeName(NAME_None) - , BakedObject(nullptr) - , SourceObject(nullptr) - , BakeFolderPath() - , bInstancerOutput(false) - , bPostBakeProcessPostponed(false) -{ -} - -FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( - AActor* InActor, - FName InActorBakeName, - FName InWorldOutlinerFolder, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - UObject* InBakedObject, - UObject* InSourceObject, - UObject* InBakedComponent, - const FString& InBakeFolderPath, - const FHoudiniPackageParams& InBakedObjectPackageParams) - : Actor(InActor) - , OutputIndex(InOutputIndex) - , OutputObjectIdentifier(InOutputObjectIdentifier) - , ActorBakeName(InActorBakeName) - , WorldOutlinerFolder(InWorldOutlinerFolder) - , BakedObject(InBakedObject) - , SourceObject(InSourceObject) - , BakedComponent(InBakedComponent) - , BakeFolderPath(InBakeFolderPath) - , BakedObjectPackageParams(InBakedObjectPackageParams) - , bInstancerOutput(false) - , bPostBakeProcessPostponed(false) -{ -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - UHoudiniAssetComponent* InHACToBake, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess, - bool bInRecenterBakedActors) -{ - if (!IsValid(InHACToBake)) - return false; - - // Handle proxies: if the output has any current proxies, first refine them - bool bHACNeedsToReCook; - if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors, bHACNeedsToReCook)) - { - // Either the component is invalid, or needs a recook to refine a proxy mesh - return false; - } - - bool bSuccess = false; - switch (InBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake, bInRecenterBakedActors); - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake, bInRecenterBakedActors); - } - break; - - case EHoudiniEngineBakeOption::ToFoliage: - { - TMap AlreadyBakedMaterialsMap; - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake, AlreadyBakedMaterialsMap); - } - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - { - //Todo - bSuccess = false; - } - break; - - } - - if (bSuccess && bInRemoveHACOutputOnSuccess) - { - TArray DeferredClearOutputs; - FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake, DeferredClearOutputs, true); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActors) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - TArray NewActors; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - - const bool bBakedWithErrors = !FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats); - if (bBakedWithErrors) - { - // TODO ? - HOUDINI_LOG_WARNING(TEXT("Errors when baking")); - } - - // Save the created packages - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Recenter and select the baked actors - if (GEditor && NewActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : NewActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (bInRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && NewActors.Num() > 0) - GEditor->NoteSelectionChange(); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - // Broadcast that the bake is complete - HoudiniAssetComponent->HandleOnPostBake(!bBakedWithErrors); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!IsValid(OwnerActor)) - return false; - - const FString HoudiniAssetName = OwnerActor->GetName(); - - // Get an array of the outputs - const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); - TArray Outputs; - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - Outputs.Add(HoudiniAssetComponent->GetOutputAt(OutputIdx)); - } - - // Get the previous bake objects and grow/shrink to match asset outputs - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - // Ensure we have an entry for each output - if (BakedOutputs.Num() != NumOutputs) - BakedOutputs.SetNum(NumOutputs); - - return BakeHoudiniOutputsToActors( - HoudiniAssetComponent, - Outputs, - BakedOutputs, - HoudiniAssetName, - HoudiniAssetComponent->GetComponentTransform(), - HoudiniAssetComponent->BakeFolder, - HoudiniAssetComponent->TemporaryCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutNewActors, - OutPackagesToSave, - OutBakeStats, - InOutputTypesToBake, - InInstancerComponentTypesToBake, - InFallbackActor, - InFallbackWorldOutlinerFolder); -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - const TArray& InOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FTransform& InParentTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - const int32 NumOutputs = InOutputs.Num(); - - const FString MsgTemplate = TEXT("Baking output: {0}/{1}."); - FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs }); - FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg)); - - TArray BakedActors; - - // Ensure that InBakedOutputs is the same size as InOutputs - if (InBakedOutputs.Num() != NumOutputs) - InBakedOutputs.SetNum(NumOutputs); - - // First bake everything except instancers, then bake instancers. Since instancers might use meshes in - // from the other outputs. - bool bHasAnyInstancers = false; - int32 NumProcessedOutputs = 0; - - TMap AlreadyBakedMaterialsMap; - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) - { - NumProcessedOutputs++; - continue; - } - - Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); - - const EHoudiniOutputType OutputType = Output->GetType(); - // Check if we should skip this output type - if (InOutputTypesToBake && InOutputTypesToBake->Find(OutputType) == INDEX_NONE) - { - NumProcessedOutputs++; - continue; - } - - switch (OutputType) - { - case EHoudiniOutputType::Mesh: - { - FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( - HoudiniAssetComponent, - OutputIdx, - InOutputs, - InBakedOutputs, - InHoudiniAssetName, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - OutPackagesToSave, - AlreadyBakedMaterialsMap, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - break; - - case EHoudiniOutputType::Instancer: - { - if (!bHasAnyInstancers) - bHasAnyInstancers = true; - NumProcessedOutputs--; - } - break; - - case EHoudiniOutputType::Landscape: - { - const bool bResult = BakeLandscape( - HoudiniAssetComponent, - OutputIdx, - InOutputs, - InBakedOutputs, - bInReplaceActors, - bInReplaceAssets, - InBakeFolder.Path, - InHoudiniAssetName, - OutBakeStats); - } - break; - - case EHoudiniOutputType::Skeletal: - break; - - case EHoudiniOutputType::Curve: - { - FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - HoudiniAssetComponent, - OutputIdx, - InOutputs, - InBakedOutputs, - InHoudiniAssetName, - InBakeFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - break; - - case EHoudiniOutputType::Invalid: - break; - } - - NumProcessedOutputs++; - } - - if (bHasAnyInstancers) - { - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) - { - NumProcessedOutputs++; - continue; - } - - Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); - - if (Output->GetType() == EHoudiniOutputType::Instancer) - { - FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( - HoudiniAssetComponent, - OutputIdx, - InOutputs, - InBakedOutputs, - InParentTransform, - InHoudiniAssetName, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedActors, - OutPackagesToSave, - AlreadyBakedMaterialsMap, - InInstancerComponentTypesToBake, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - - NumProcessedOutputs++; - } - } - - // Only do the post bake post-process once per Actor - TSet UniqueActors; - for (FHoudiniEngineBakedActor& BakedActor : BakedActors) - { - if (BakedActor.bPostBakeProcessPostponed && BakedActor.Actor) - { - BakedActor.bPostBakeProcessPostponed = false; - AActor* Actor = BakedActor.Actor; - bool bIsAlreadyInSet = false; - UniqueActors.Add(Actor, &bIsAlreadyInSet); - if (!bIsAlreadyInSet) - { - Actor->InvalidateLightingCache(); - Actor->PostEditMove(true); - Actor->MarkPackageDirty(); - } - } - } - - OutNewActors.Append(BakedActors); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap) -{ - UHoudiniOutput* Output = InAllOutputs[InOutputIndex]; - if (!Output || Output->IsPendingKill()) - return false; - - if (Output->GetType() != EHoudiniOutputType::Instancer) - return false; - - if (!IsValid(InOutputObject.OutputComponent)) - return false; - - UStaticMeshComponent* SMC = Cast(InOutputObject.OutputComponent); - if (!IsValid(SMC)) - { - HOUDINI_LOG_WARNING( - TEXT("Unsupported component for foliage: %s"),*(InOutputObject.OutputComponent->GetClass()->GetName())); - return false; - } - - UStaticMesh* InstancedStaticMesh = SMC->GetStaticMesh(); - if (!IsValid(InstancedStaticMesh)) - { - // No mesh, skip this instancer - return false; - } - - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets - ? EPackageReplaceMode::ReplaceExistingAssets - : EPackageReplaceMode::CreateNewAssets; - UWorld* DesiredWorld = Output ? Output->GetWorld() : GWorld; - - // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params - // for baking from it. - // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) - FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = nullptr; - int32 MeshOutputIndex = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - FHoudiniAttributeResolver MeshResolver; - FHoudiniPackageParams MeshPackageParams; - const bool bFoundMeshOutput = FindOutputObject(InstancedStaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); - if (bFoundMeshOutput) - { - // Found the mesh in the mesh outputs, is temporary - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - InHoudiniAssetName, MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - // Update with resolved object name - ObjectName = MeshPackageParams.ObjectName; - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); - } - else - { - BakedStaticMesh = InstancedStaticMesh; - } - - // Update the baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // const FString InstancerName = FString::Printf(TEXT("%s_foliage_%s"), *ObjectName, *(InOutputObjectIdentifier.SplitIdentifier)); - // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone - // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the - // package params. - FHoudiniPackageParams InstancerPackageParams; - FHoudiniAttributeResolver InstancerResolver; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - InHoudiniAssetName, InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - HOUDINI_LOG_ERROR(TEXT("Could not find or create a level: %s"), *LevelPackagePath); - return false; - } - - // If we have created a new level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // Get foliage actor for the level - const bool bCreateIfNone = true; - AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, bCreateIfNone); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Could not find or create an instanced foliage actor for level %s"), *(DesiredLevel->GetPathName())); - return false; - } - - // Get the previous bake data for this instancer - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // Foliage type is replaced in replacement mode if: - // the previous baked object is this foliage type - // and we haven't bake this foliage type during this bake (BakeResults) - // NOTE: foliage type is only recorded as the previous bake object if we created the foliage type - // TODO: replacement mode should probably only affect the instances themselves and not the foliage type - // since the foliage type is already linked to whatever mesh we are using (which will be replaced - // incremented already). To track instances it looks like we would have to use the locations of the - // baked instances (likely cannot use the indices, since the user might modify/add/remove instances - // after the bake). - - // See if we already have a FoliageType for that static mesh - UFoliageType* FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(BakedStaticMesh); - if (!FoliageType || FoliageType->IsPendingKill()) - { - // We need to create a new FoliageType for this Static Mesh - // TODO: Add foliage default settings - InstancedFoliageActor->AddMesh(BakedStaticMesh, &FoliageType); - // Update the previous bake results with the foliage type we created - InBakedOutputObject.BakedComponent = FSoftObjectPath(FoliageType).ToString(); - } - else - { - const FString FoliageTypePath = FSoftObjectPath(FoliageType).ToString(); - if (bInReplaceAssets && InBakedOutputObject.BakedComponent == FoliageTypePath && - !OutActors.FindByPredicate([FoliageType](const FHoudiniEngineBakedActor& Entry) { return Entry.BakedComponent == FoliageType; })) - { - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - // Update the previous bake results with the foliage type - InBakedOutputObject.BakedComponent = FoliageTypePath; - } - else - { - // If we didn't create the foliage type, don't set the baked component - InBakedOutputObject.BakedComponent.Empty(); - } - } - - // Record the foliage bake in the current results - FHoudiniEngineBakedActor& NewResult = OutActors.Add_GetRef(FHoudiniEngineBakedActor()); - NewResult.OutputIndex = InOutputIndex; - NewResult.OutputObjectIdentifier = InOutputObjectIdentifier; - NewResult.SourceObject = InstancedStaticMesh; - NewResult.BakedObject = BakedStaticMesh; - NewResult.BakedComponent = FoliageType; - - // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it - FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); - if (!FoliageInfo) - return false; - - int32 CurrentInstanceCount = 0; - if (SMC->IsA()) - { - UInstancedStaticMeshComponent* ISMC = Cast(SMC); - const int32 NumInstances = ISMC->GetInstanceCount(); - for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex) - { - FTransform InstanceTransform; - const bool bWorldSpace = true; - if (ISMC->GetInstanceTransform(InstanceIndex, InstanceTransform, bWorldSpace)) - { - FFoliageInstance FoliageInstance; - FoliageInstance.Location = InstanceTransform.GetLocation(); - FoliageInstance.Rotation = InstanceTransform.GetRotation().Rotator(); - FoliageInstance.DrawScale3D = InstanceTransform.GetScale3D(); - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - - CurrentInstanceCount++; - } - } - } - else - { - const FTransform ComponentToWorldTransform = SMC->GetComponentToWorld(); - FFoliageInstance FoliageInstance; - FoliageInstance.Location = ComponentToWorldTransform.GetLocation(); - FoliageInstance.Rotation = ComponentToWorldTransform.GetRotation().Rotator(); - FoliageInstance.DrawScale3D = ComponentToWorldTransform.GetScale3D(); - - FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); - - CurrentInstanceCount++; - } - - // TODO: This was due to a bug in UE4.22-20, check if still needed! - if (FoliageInfo->GetComponent()) - FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); - - // Notify the user that we succesfully bake the instances to foliage - FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + BakedStaticMesh->GetName() + TEXT(" to Foliage"); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - InstancedFoliageActor->RegisterAllComponents(); - - // Update / repopulate the foliage editor mode's mesh list - if (CurrentInstanceCount > 0) - FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); - - return true; -} - -bool -FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n) - { - UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n); - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Instancer) - continue; - - if (Output->GetInstancedOutputs().Num() > 0) - return true; - /* - // TODO: Is this needed? check we have components to bake? - for (auto& OutputObjectPair : Output->GetOutputObjects()) - { - if (OutputObjectPair.Value.OutputCompoent!= nullpt) - return true; - } - */ - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, TMap& InOutAlreadyBakedMaterialsMap) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor * OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - TArray PackagesToSave; - TArray BakedResults; - - FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); - const FString HoudiniAssetName = OwnerActor->GetName(); - - // Build an array of the outputs so that we can search for meshes/previous baked meshes - TArray Outputs; - HoudiniAssetComponent->GetOutputs(Outputs); - const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); - - // Get the previous bake outputs and match the output array size - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - if (BakedOutputs.Num() != NumOutputs) - BakedOutputs.SetNum(NumOutputs); - - bool bSuccess = true; - // Map storing original and baked Static Meshes - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) - { - UHoudiniOutput* Output = Outputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Instancer) - continue; - - TMap& OutputObjects = Output->GetOutputObjects(); - const TMap& OldBakedOutputObjects = BakedOutputs[OutputIdx].BakedOutputObjects; - TMap NewBakedOutputObjects; - - for (auto & Pair : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - FHoudiniOutputObject& OutputObject = Pair.Value; - - FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); - if (OldBakedOutputObjects.Contains(Identifier)) - BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); - - const bool bInReplaceActors = false; - bSuccess &= BakeInstancerOutputToFoliage( - HoudiniAssetComponent, - OutputIdx, - Outputs, - Identifier, - OutputObject, - BakedOutputObject, - HoudiniAssetName, - HoudiniAssetComponent->BakeFolder, - HoudiniAssetComponent->TemporaryCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedResults, - PackagesToSave, - InOutAlreadyBakedMaterialsMap); - } - - // Update the cached baked output data - BakedOutputs[OutputIdx].BakedOutputObjects = NewBakedOutputObjects; - } - - if (PackagesToSave.Num() > 0) - { - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - } - - // Broadcast that the bake is complete - HoudiniAssetComponent->HandleOnPostBake(bSuccess); - - return bSuccess; -} - - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FTransform& InTransform, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap, - TArray const* InInstancerComponentTypesToBake, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) - return false; - - // Ensure we have the same number of baked outputs and asset outputs - if (InBakedOutputs.Num() != InAllOutputs.Num()) - InBakedOutputs.SetNum(InAllOutputs.Num()); - - TMap& OutputObjects = InOutput->GetOutputObjects(); - const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; - TMap NewBakedOutputObjects; - - // Iterate on the output objects, baking their object/component as we go - for (auto& Pair : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - FHoudiniOutputObject& CurrentOutputObject = Pair.Value; - - FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); - if (OldBakedOutputObjects.Contains(Identifier)) - BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); - - if (CurrentOutputObject.bProxyIsCurrent) - { - // TODO: we need to refine the SM first! - // ?? - } - - if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill()) - continue; - - if (CurrentOutputObject.OutputComponent->IsA()) - { - // Bake foliage as foliage - if (!InInstancerComponentTypesToBake || - InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageInstancedStaticMeshComponent)) - { - BakeInstancerOutputToFoliage( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InHoudiniAssetName, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InOutAlreadyBakedMaterialsMap); - } - else if (!InInstancerComponentTypesToBake || - InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent)) - { - BakeInstancerOutputToActors_ISMC( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InOutAlreadyBakedMaterialsMap, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent))) - { - BakeInstancerOutputToActors_ISMC( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InOutAlreadyBakedMaterialsMap, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent))) - { - BakeInstancerOutputToActors_IAC( - HoudiniAssetComponent, - InOutputIndex, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InBakeFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) - { - BakeInstancerOutputToActors_MSIC( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InOutAlreadyBakedMaterialsMap, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else if (CurrentOutputObject.OutputComponent->IsA() - && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) - { - BakeInstancerOutputToActors_SMC( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InOutAlreadyBakedMaterialsMap, - InFallbackActor, - InFallbackWorldOutlinerFolder); - } - else - { - // Unsupported component! - } - - } - - // Update the cached baked output data - InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UInstancedStaticMeshComponent * InISMC = Cast(InOutputObject.OutputComponent); - if (!InISMC || InISMC->IsPendingKill()) - return false; - - AActor * OwnerActor = InISMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh * StaticMesh = InISMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - - // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params - // for baking from it. - // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) - FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = nullptr; - int32 MeshOutputIndex = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - FHoudiniAttributeResolver MeshResolver; - FHoudiniPackageParams MeshPackageParams; - const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); - if (bFoundMeshOutput) - { - // Found the mesh in the mesh outputs, is temporary - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - OwnerActor->GetName(), MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - // Update with resolved object name - ObjectName = MeshPackageParams.ObjectName; - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); - } - else - { - BakedStaticMesh = StaticMesh; - } - - // Update the baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone - // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the - // package params. - FHoudiniPackageParams InstancerPackageParams; - FHoudiniAttributeResolver InstancerResolver; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a new level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if(!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - /* - // TODO: Get the bake name! - // Bake override, the output name - // The bake name override has priority - FString InstancerName = InOutputObject.BakeName; - if (InstancerName.IsEmpty()) - { - // .. then use the output name - InstancerName = Resolver.ResolveOutputName(); - } - */ - - // Should we create one actor with an ISMC or multiple actors with one SMC? - bool bSpawnMultipleSMC = false; - if (bSpawnMultipleSMC) - { - // TODO: Double check, Has a crash here! - - // Get the StaticMesh ActorFactory - UActorFactory* SMFactory = nullptr; - - if (!FoundActor) - { - SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!SMFactory) - return false; - } - - // Split the instances to multiple StaticMeshActors - for (int32 InstanceIdx = 0; InstanceIdx < InISMC->GetInstanceCount(); InstanceIdx++) - { - FTransform InstanceTransform; - InISMC->GetInstanceTransform(InstanceIdx, InstanceTransform, true); - - if (!FoundActor) - { - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform); - if (!FoundActor || FoundActor->IsPendingKill()) - continue; - } - - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName.ToString(), FoundActor); - RenameAndRelabelActor(FoundActor, NewNameStr, false); - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - continue; - - // Copy properties from the existing component - CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC); - - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh, - SMActor->GetStaticMeshComponent(), - bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), - MeshPackageParams)); - OutputEntry.bInstancerOutput = true; - OutputEntry.InstancerPackageParams = InstancerPackageParams; - } - } - else - { - bool bSpawnedActor = false; - if (!FoundActor) - { - // Only create one actor - FActorSpawnParameters SpawnInfo; - SpawnInfo.OverrideLevel = DesiredLevel; - SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); - SpawnInfo.bDeferConstruction = true; - - // Spawn the new Actor - FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - bSpawnedActor = true; - - FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); - FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); - } - else - { - // If there is a previously baked component, and we are in replace mode, remove it - if (bInReplaceAssets) - { - USceneComponent* InPrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(InPrevComponent) && InPrevComponent->GetOwner() == FoundActor) - RemovePreviouslyBakedComponent(InPrevComponent); - } - - const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); - } - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Get/create the actor's root component - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - if (bSpawnedActor && IsValid(RootComponent)) - RootComponent->SetWorldTransform(InTransform); - - // Duplicate the instancer component, create a Hierarchical ISMC if needed - UInstancedStaticMeshComponent* NewISMC = nullptr; - UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast(InISMC); - if (InHISMC) - { - // Handle foliage: don't duplicate foliage component, create a new hierarchical one and copy what we can - // from the foliage component - if (InHISMC->IsA()) - { - NewISMC = NewObject( - FoundActor, - FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); - CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); - } - else - { - NewISMC = DuplicateObject( - InHISMC, - FoundActor, - FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); - } - } - else - { - NewISMC = DuplicateObject( - InISMC, - FoundActor, - FName(MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetName()))); - } - - if (!NewISMC) - { - //DesiredLevel->OwningWorld-> - return false; - } - - InBakedOutputObject.BakedComponent = FSoftObjectPath(NewISMC).ToString(); - - NewISMC->RegisterComponent(); - // NewISMC->SetupAttachment(nullptr); - NewISMC->SetStaticMesh(BakedStaticMesh); - FoundActor->AddInstanceComponent(NewISMC); - // NewActor->SetRootComponent(NewISMC); - if (IsValid(RootComponent)) - NewISMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - NewISMC->SetWorldTransform(InISMC->GetComponentTransform()); - - // TODO: do we need to copy properties here, we duplicated the component - // // Copy properties from the existing component - // CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); - - if (bSpawnedActor) - FoundActor->FinishSpawning(InTransform); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh, - NewISMC, - bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), - MeshPackageParams)); - OutputEntry.bInstancerOutput = true; - OutputEntry.InstancerPackageParams = InstancerPackageParams; - - // Postpone post-bake calls to do them once per actor - OutActors.Last().bPostBakeProcessPostponed = true; - } - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UStaticMeshComponent* InSMC = Cast(InOutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) - return false; - - AActor* OwnerActor = InSMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh* StaticMesh = InSMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - - // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params - // for baking from it. - // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) - FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = nullptr; - int32 MeshOutputIndex = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - FHoudiniAttributeResolver MeshResolver; - FHoudiniPackageParams MeshPackageParams; - const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); - if (bFoundMeshOutput) - { - // Found the mesh in the mesh outputs, is temporary - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - OwnerActor->GetName(), MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - // Update with resolved object name - ObjectName = MeshPackageParams.ObjectName; - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); - } - else - { - BakedStaticMesh = StaticMesh; - } - - // Update the previous baked object - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // BaseName holds the Actor / HDA name - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams InstancerPackageParams; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver InstancerResolver; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - UStaticMeshComponent* StaticMeshComponent = nullptr; - // Create an actor if we didn't find one - if (!FoundActor) - { - // Get the StaticMesh ActorFactory - UActorFactory* SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!SMFactory) - return false; - - FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform()); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - return false; - - StaticMeshComponent = SMActor->GetStaticMeshComponent(); - } - else - { - USceneComponent* RootComponent = GetActorRootComponent(FoundActor); - if (!IsValid(RootComponent)) - return false; - - if (bInReplaceAssets) - { - // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it - UStaticMeshComponent* PrevSMC = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) - { - StaticMeshComponent = PrevSMC; - } - } - - if (!IsValid(StaticMeshComponent)) - { - // Create a new static mesh component - StaticMeshComponent = NewObject(FoundActor, NAME_None, RF_Transactional); - - FoundActor->AddInstanceComponent(StaticMeshComponent); - StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - StaticMeshComponent->RegisterComponent(); - } - } - - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName.ToString(), FoundActor); - RenameAndRelabelActor(FoundActor, NewNameStr, false); - - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Update the previous baked component - InBakedOutputObject.BakedComponent = FSoftObjectPath(StaticMeshComponent).ToString(); - - if (!IsValid(StaticMeshComponent)) - return false; - - // Copy properties from the existing component - const bool bCopyWorldTransform = true; - CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC, bCopyWorldTransform); - StaticMeshComponent->SetStaticMesh(BakedStaticMesh); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh, - StaticMeshComponent, - MeshPackageParams.BakeFolder, - MeshPackageParams)); - OutputEntry.bInstancerOutput = true; - OutputEntry.InstancerPackageParams = InstancerPackageParams; - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave) -{ - UHoudiniInstancedActorComponent* InIAC = Cast(InOutputObject.OutputComponent); - if (!InIAC || InIAC->IsPendingKill()) - return false; - - AActor * OwnerActor = InIAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - // BaseName holds the Actor / HDA name - const FName BaseName = FName(OwnerActor->GetName()); - - // Get the object instanced by this IAC - UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) - return false; - - FHoudiniPackageParams PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver Resolver; - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, BaseName.ToString(), - OwnerActor->GetName(), PackageParams, Resolver, - InBakeFolder.Path, AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // If we are baking in actor replacement mode, remove any previously baked instanced actors for this output - if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0) - { - UWorld* LevelWorld = DesiredLevel->GetWorld(); - if (IsValid(LevelWorld)) - { - for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) - { - const FSoftObjectPath ActorPath(ActorPathStr); - - if (!ActorPath.IsValid()) - continue; - - AActor* Actor = Cast(ActorPath.TryLoad()); - // Destroy Actor if it is valid and part of DesiredLevel - if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel) - { -#if WITH_EDITOR - LevelWorld->EditorDestroyActor(Actor, true); -#else - LevelWorld->DestroyActor(Actor); -#endif - } - } - } - } - - // Empty and reserve enough space for new instanced actors - InBakedOutputObject.InstancedActors.Empty(InIAC->GetInstancedActors().Num()); - - // Iterates on all the instances of the IAC - for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors()) - { - if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) - continue; - - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName.ToString()); - - FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); - AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); - if (!NewActor || NewActor->IsPendingKill()) - continue; - - const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) - (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); - - EditorUtilities::CopyActorProperties(CurrentInstancedActor, NewActor); - - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); - - FHoudiniEngineRuntimeUtils::SetActorLabel(NewActor, NewNameStr); - - SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); - NewActor->SetActorTransform(CurrentTransform); - - InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); - - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( - NewActor, - BaseName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - nullptr, - InstancedObject, - nullptr, - PackageParams.BakeFolder, - PackageParams)); - OutputEntry.bInstancerOutput = true; - OutputEntry.InstancerPackageParams = PackageParams; - } - - // TODO: - // Move Actors to DesiredLevel if needed?? - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = true; - const bool bInDestroyBakedInstancedActors = false; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - UHoudiniMeshSplitInstancerComponent * InMSIC = Cast(InOutputObject.OutputComponent); - if (!InMSIC || InMSIC->IsPendingKill()) - return false; - - AActor * OwnerActor = InMSIC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return false; - - UStaticMesh * StaticMesh = InMSIC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return false; - - UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - - // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params - // for baking from it. - // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) - FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - UStaticMesh* BakedStaticMesh = nullptr; - int32 MeshOutputIndex = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - FHoudiniAttributeResolver MeshResolver; - FHoudiniPackageParams MeshPackageParams; - const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); - if (bFoundMeshOutput) - { - // Found the mesh in the mesh outputs, is temporary - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - OwnerActor->GetName(), MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - // Update with resolved object name - ObjectName = MeshPackageParams.ObjectName; - - // This will bake/duplicate the mesh if temporary, or return the input one if it is not - BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); - } - else - { - BakedStaticMesh = StaticMesh; - } - - // Update the baked output - InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - - // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); - const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - // See if the instanced static mesh is still a temporary Houdini created Static Mesh - // If it is, we need to bake the StaticMesh first - FHoudiniPackageParams InstancerPackageParams; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver InstancerResolver; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if (!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - bool bSpawnedActor = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - if (!FoundActor) - { - // This is a split mesh instancer component - we will create a generic AActor with a bunch of SMC - FActorSpawnParameters SpawnInfo; - SpawnInfo.OverrideLevel = DesiredLevel; - SpawnInfo.ObjectFlags = RF_Transactional; - SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); - SpawnInfo.bDeferConstruction = true; - - // Spawn the new Actor - FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) - return false; - bSpawnedActor = true; - - FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); - - FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); - } - else - { - // If we are baking in replacement mode, remove the previous components (if they belong to FoundActor) - for (const FString& PrevComponentPathStr : InBakedOutputObject.InstancedComponents) - { - const FSoftObjectPath PrevComponentPath(PrevComponentPathStr); - - if (!PrevComponentPath.IsValid()) - continue; - - UActorComponent* PrevComponent = Cast(PrevComponentPath.TryLoad()); - if (!IsValid(PrevComponent) || PrevComponent->GetOwner() != FoundActor) - continue; - - RemovePreviouslyBakedComponent(PrevComponent); - } - - const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); - RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); - } - // The folder is named after the original actor and contains all generated actors - SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); - - // Get/create the actor's root component - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - if (bSpawnedActor && IsValid(RootComponent)) - RootComponent->SetWorldTransform(InTransform); - - // Empty and reserve enough space in the baked components array for the new components - InBakedOutputObject.InstancedComponents.Empty(InMSIC->GetInstances().Num()); - - // Now add s SMC component for each of the SMC's instance - for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances()) - { - if (!CurrentSMC || CurrentSMC->IsPendingKill()) - continue; - - UStaticMeshComponent* NewSMC = DuplicateObject( - CurrentSMC, - FoundActor, - FName(MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetName()))); - if (!NewSMC || NewSMC->IsPendingKill()) - continue; - - InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString()); - - NewSMC->RegisterComponent(); - // NewSMC->SetupAttachment(nullptr); - NewSMC->SetStaticMesh(BakedStaticMesh); - FoundActor->AddInstanceComponent(NewSMC); - NewSMC->SetWorldTransform(CurrentSMC->GetComponentTransform()); - if (IsValid(RootComponent)) - NewSMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); - - // TODO: Do we need to copy properties here, we duplicated the component - // // Copy properties from the existing component - // CopyPropertyToNewActorAndComponent(FoundActor, NewSMC, CurrentSMC); - } - - if (bSpawnedActor) - FoundActor->FinishSpawning(InTransform); - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( - FoundActor, - BakeActorName, - WorldOutlinerFolderPath, - InOutputIndex, - InOutputObjectIdentifier, - BakedStaticMesh, - StaticMesh, - nullptr, - MeshPackageParams.BakeFolder, - MeshPackageParams)); - OutputEntry.bInstancerOutput = true; - OutputEntry.InstancerPackageParams = InstancerPackageParams; - - // Postpone these calls to do them once per actor - OutActors.Last().bPostBakeProcessPostponed = true; - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = true; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = false; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::FindHGPO( - const FHoudiniOutputObjectIdentifier& InIdentifier, - const TArray& InHGPOs, - FHoudiniGeoPartObject const*& OutHGPO) -{ - // Find the HGPO that matches this output identifier - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - for (auto & NextHGPO : InHGPOs) - { - // We use Matches() here as it handles the case where the HDA was loaded, - // which likely means that the the obj/geo/part ids dont match the output identifier - if(InIdentifier.Matches(NextHGPO)) - { - FoundHGPO = &NextHGPO; - break; - } - } - - OutHGPO = FoundHGPO; - return !OutHGPO; -} - -void -FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const FHoudiniOutputObject& InMeshOutputObject, - FString& OutBakeName) -{ - // The bake name override has priority - OutBakeName = InMeshOutputObject.BakeName; - if (OutBakeName.IsEmpty()) - { - FHoudiniAttributeResolver Resolver; - Resolver.SetCachedAttributes(InMeshOutputObject.CachedAttributes); - Resolver.SetTokensFromStringMap(InMeshOutputObject.CachedTokens); - const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InObject); - // The default output name (if not set via attributes) is {object_name}, which look for an object_name - // key-value token - if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) - Resolver.SetToken(TEXT("object_name"), DefaultObjectName); - OutBakeName = Resolver.ResolveOutputName(); - // const TArray& HGPOs = InAllOutputs[MeshOutputIdx]->GetHoudiniGeoPartObjects(); - // const FHoudiniGeoPartObject* FoundHGPO = nullptr; - // FindHGPO(MeshIdentifier, HGPOs, FoundHGPO); - // // ... finally the part name - // if (FoundHGPO && FoundHGPO->bHasCustomPartName) - // OutBakeName = FoundHGPO->PartName; - if (OutBakeName.IsEmpty()) - OutBakeName = DefaultObjectName; - } -} - -bool -FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( - const UObject* InObject, - EHoudiniOutputType InOutputType, - const TArray& InAllOutputs, - FString& OutBakeName) -{ - if (!IsValid(InObject)) - return false; - - OutBakeName.Empty(); - - int32 MeshOutputIdx = INDEX_NONE; - FHoudiniOutputObjectIdentifier MeshIdentifier; - if (FindOutputObject(InObject, InOutputType, InAllOutputs, MeshOutputIdx, MeshIdentifier)) - { - // Found the mesh, get its name - const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier); - GetTemporaryOutputObjectBakeName(InObject, MeshOutputObject, OutBakeName); - - return true; - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - // Check that index is not negative - if (InOutputIndex < 0) - return false; - - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) - return false; - - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; - if (!Factory) - return false; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); - - // Get the previous bake objects - if (!InBakedOutputs.IsValidIndex(InOutputIndex)) - InBakedOutputs.SetNum(InOutputIndex + 1); - - const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; - TMap NewBakedOutputObjects; - - for (auto& Pair : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - const FHoudiniOutputObject& OutputObject = Pair.Value; - - // Add a new baked output object entry and update it with the previous bake's data, if available - FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); - if (OldBakedOutputObjects.Contains(Identifier)) - BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); - - UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) - continue; - - UStaticMeshComponent* InSMC = Cast(OutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) - continue; - - // Find the HGPO that matches this output identifier - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - FindHGPO(Identifier, HGPOs, FoundHGPO); - - // We do not bake templated geos - if (FoundHGPO && FoundHGPO->bIsTemplated) - continue; - - const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); - - UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - FHoudiniPackageParams PackageParams; - - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); - - if (!ResolvePackageParams( - HoudiniAssetComponent, - InOutput, - Identifier, - OutputObject, - InHoudiniAssetName, - DefaultObjectName, - InBakeFolder, - bInReplaceAssets, - PackageParams, - OutPackagesToSave)) - { - continue; - } - - // Bake the static mesh if it is still temporary - UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, - Cast(BakedOutputObject.GetBakedObjectIfValid()), - PackageParams, - InAllOutputs, - OutActors, - InTempCookFolder.Path, - OutPackagesToSave, - InOutAlreadyBakedMaterialsMap); - - if (!BakedSM || BakedSM->IsPendingKill()) - continue; - - // Record the baked object - BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString(); - - // Make sure we have a level to spawn to - if (!DesiredLevel || DesiredLevel->IsPendingKill()) - continue; - - // Try to find the unreal_bake_actor, if specified - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - UStaticMeshComponent* SMC = nullptr; - if (!FoundActor) - { - // Spawn the new actor - FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform()); - if (!FoundActor || FoundActor->IsPendingKill()) - continue; - - // Copy properties to new actor - AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) - continue; - - SMC = SMActor->GetStaticMeshComponent(); - } - else - { - if (bInReplaceAssets) - { - // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it - UStaticMeshComponent* PrevSMC = Cast(BakedOutputObject.GetBakedComponentIfValid()); - if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) - { - SMC = PrevSMC; - } - } - - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); - - if (!IsValid(SMC)) - { - // Create a new static mesh component on the existing actor - SMC = NewObject(FoundActor, NAME_None, RF_Transactional); - - FoundActor->AddInstanceComponent(SMC); - if (IsValid(RootComponent)) - SMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - else - FoundActor->SetRootComponent(SMC); - SMC->RegisterComponent(); - } - } - - // We need to make a unique name for the actor, renaming an object on top of another is a fatal error - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName.ToString(), FoundActor); - RenameAndRelabelActor(FoundActor, NewNameStr, false); - SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath); - - if (IsValid(SMC)) - { - const bool bCopyWorldTransform = true; - CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC, bCopyWorldTransform); - SMC->SetStaticMesh(BakedSM); - BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString(); - } - - BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( - FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh, SMC, - PackageParams.BakeFolder, PackageParams)); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - BakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - } - - // Update the cached baked output data - InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; - - return true; -} - -bool FHoudiniEngineBakeUtils::ResolvePackageParams( - const UHoudiniAssetComponent* HoudiniAssetComponent, - UHoudiniOutput* InOutput, - const FHoudiniOutputObjectIdentifier& Identifier, - const FHoudiniOutputObject& InOutputObject, - const FString& InHoudiniAssetName, - const FString& DefaultObjectName, - const FDirectoryPath& InBakeFolder, - const bool bInReplaceAssets, - FHoudiniPackageParams& OutPackageParams, - TArray& OutPackagesToSave) -{ - FHoudiniAttributeResolver Resolver; - - UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, Identifier, InOutputObject, DefaultObjectName, - InHoudiniAssetName, OutPackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); - - - // See if this output object has an unreal_level_path attribute specified - // In which case, we need to create/find the desired level for baking instead of using the current one - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - // Get the package path from the unreal_level_path attribute - FString LevelPackagePath = Resolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a level, add it to the packages to save - // TODO: ? always add the level to the packages to save? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - // Check that index is not negative - if (InOutputIndex < 0) - return false; - - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; - if (!Output || Output->IsPendingKill()) - return false; - - TArray PackagesToSave; - - // Find the previous baked output data for this output index. If an entry - // does not exist, create entries up to and including this output index - if (!InBakedOutputs.IsValidIndex(InOutputIndex)) - InBakedOutputs.SetNum(InOutputIndex + 1); - - TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; - const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; - TMap NewBakedOutputObjects; - - const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); - - for (auto & Pair : OutputObjects) - { - FHoudiniOutputObject& OutputObject = Pair.Value; - USplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; - FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); - if (OldBakedOutputObjects.Contains(Identifier)) - BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); - - // TODO: FIX ME!! May not work 100% - const FHoudiniGeoPartObject* FoundHGPO = nullptr; - for (auto & NextHGPO : HGPOs) - { - if (Identifier.GeoId == NextHGPO.GeoId && - Identifier.ObjectId == NextHGPO.ObjectId && - Identifier.PartId == NextHGPO.PartId) - { - FoundHGPO = &NextHGPO; - break; - } - } - - if (!FoundHGPO) - continue; - - const FString DefaultObjectName = InHoudiniAssetName + "_" + SplineComponent->GetName(); - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver Resolver; - UWorld* const DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, - InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); - - BakeCurve( - OutputObject, BakedOutputObject, PackageParams, Resolver, bInReplaceActors, bInReplaceAssets, - OutActors, PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); - } - - // Update the cached bake output results - BakedOutput.BakedOutputObjects = NewBakedOutputObjects; - - SaveBakedPackages(PackagesToSave); - - return true; -} - -bool -FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint) -{ - if (!InActor || InActor->IsPendingKill()) - return false; - - if (!OutBlueprint || OutBlueprint->IsPendingKill()) - return false; - - if (InActor->GetInstanceComponents().Num() > 0) - FKismetEditorUtilities::AddComponentsToBlueprint( - OutBlueprint, - InActor->GetInstanceComponents()); - - if (OutBlueprint->GeneratedClass) - { - AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject()); - if (!CDO || CDO->IsPendingKill()) - return false; - - const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) - (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); - - EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions); - - USceneComponent * Scene = CDO->GetRootComponent(); - if (Scene && !Scene->IsPendingKill()) - { - Scene->SetRelativeLocation(FVector::ZeroVector); - Scene->SetRelativeRotation(FRotator::ZeroRotator); - - // Clear out the attachment info after having copied the properties from the source actor - Scene->SetupAttachment(nullptr); - while (true) - { - const int32 ChildCount = Scene->GetAttachChildren().Num(); - if (ChildCount < 1) - break; - - USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; - if (Component && !Component->IsPendingKill()) - Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - } - check(Scene->GetAttachChildren().Num() == 0); - - // Ensure the light mass information is cleaned up - Scene->InvalidateLightingCache(); - - // Copy relative scale from source to target. - if (USceneComponent* SrcSceneRoot = InActor->GetRootComponent()) - { - Scene->SetRelativeScale3D_Direct(SrcSceneRoot->GetRelativeScale3D()); - } - } - } - - // Compile our blueprint and notify asset system about blueprint. - //FKismetEditorUtilities::CompileBlueprint(OutBlueprint); - //FAssetRegistryModule::AssetCreated(OutBlueprint); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, bool bInRecenterBakedActors) -{ - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, bInRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); - if (!bSuccess) - { - // TODO: ? - HOUDINI_LOG_WARNING(TEXT("Errors while baking to blueprints.")); - } - - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - // Broadcast that the bake is complete - HoudiniAssetComponent->HandleOnPostBake(bSuccess); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprints( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceAssets, - bool bInRecenterBakedActors, - FHoudiniEngineOutputStats& InBakeStats, - TArray& OutBlueprints, - TArray& OutPackagesToSave) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); - const bool bIsOwnerActorValid = IsValid(OwnerActor); - - TArray Actors; - - // Don't process outputs that are not supported in blueprints - TArray OutputsToBake = { - EHoudiniOutputType::Mesh, - EHoudiniOutputType::Instancer, - EHoudiniOutputType::Curve - }; - TArray InstancerComponentTypesToBake = { - EHoudiniInstancerComponentType::StaticMeshComponent, - EHoudiniInstancerComponentType::InstancedStaticMeshComponent, - EHoudiniInstancerComponentType::MeshSplitInstancerComponent, - EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent - }; - // When baking blueprints we always create new actors since they are deleted from the world once copied into the - // blueprint - const bool bReplaceActors = false; - bool bBakeSuccess = BakeHoudiniActorToActors( - HoudiniAssetComponent, - bReplaceActors, - bInReplaceAssets, - Actors, - OutPackagesToSave, - InBakeStats, - &OutputsToBake, - &InstancerComponentTypesToBake); - if (!bBakeSuccess) - { - HOUDINI_LOG_ERROR(TEXT("Could not create output actors for baking to blueprint.")); - return false; - } - - // Get the previous baked outputs - TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); - - bBakeSuccess = BakeBlueprintsFromBakedActors( - Actors, - bInRecenterBakedActors, - bInReplaceAssets, - bIsOwnerActorValid ? OwnerActor->GetName() : FString(), - HoudiniAssetComponent->BakeFolder, - &BakedOutputs, - nullptr, - OutBlueprints, - OutPackagesToSave); - - return bBakeSuccess; -} - -UStaticMesh* -FHoudiniEngineBakeUtils::BakeStaticMesh( - UStaticMesh * StaticMesh, - const FHoudiniPackageParams& PackageParams, - const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder, - TMap& InOutAlreadyBakedMaterialsMap) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return nullptr; - - TArray PackagesToSave; - TArray Outputs; - const TArray BakedResults; - UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave, InOutAlreadyBakedMaterialsMap); - - if (BakedStaticMesh) - { - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor) - { - TArray Objects; - Objects.Add(BakedStaticMesh); - GEditor->SyncBrowserToObjects(Objects); - } - } - - return BakedStaticMesh; -} - -bool -FHoudiniEngineBakeUtils::BakeLandscape( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - bool bInReplaceActors, - bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, - FHoudiniEngineOutputStats& BakeStats - ) -{ - // Check that index is not negative - if (InOutputIndex < 0) - return false; - - if (!InAllOutputs.IsValidIndex(InOutputIndex)) - return false; - - UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; - if (!IsValid(Output)) - return false; - - // Find the previous baked output data for this output index. If an entry - // does not exist, create entries up to and including this output index - if (!InBakedOutputs.IsValidIndex(InOutputIndex)) - InBakedOutputs.SetNum(InOutputIndex + 1); - - TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; - const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; - TMap NewBakedOutputObjects; - TArray PackagesToSave; - TArray LandscapeWorldsToUpdate; - - FHoudiniPackageParams PackageParams; - - for (auto& Elem : OutputObjects) - { - const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key; - FHoudiniOutputObject& OutputObject = Elem.Value; - FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(ObjectIdentifier); - if (OldBakedOutputObjects.Contains(ObjectIdentifier)) - BakedOutputObject = OldBakedOutputObjects.FindChecked(ObjectIdentifier); - - // Populate the package params for baking this output object. - if (!IsValid(OutputObject.OutputObject)) - continue; - - if (!OutputObject.OutputObject->IsA()) - continue; - - UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); - ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); - if (!IsValid(Landscape)) - continue; - - FString ObjectName = Landscape->GetName(); - - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver Resolver; - UWorld* const DesiredWorld = Landscape ? Landscape->GetWorld() : GWorld; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, ObjectIdentifier, OutputObject, ObjectName, - HoudiniAssetName, PackageParams, Resolver, BakePath, AssetPackageReplaceMode); - - BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, - PackageParams, Resolver, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); - } - - // Update the cached baked output data - BakedOutput.BakedOutputObjects = NewBakedOutputObjects; - - if (PackagesToSave.Num() > 0) - { - FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); - } - - for(UWorld* LandscapeWorld : LandscapeWorldsToUpdate) - { - if (!LandscapeWorld) - continue; - FHoudiniEngineUtils::RescanWorldPath(LandscapeWorld); - ULandscapeInfo::RecreateLandscapeInfo(LandscapeWorld, true); - if (LandscapeWorld->WorldComposition) - { - UWorldComposition::WorldCompositionChangedEvent.Broadcast(LandscapeWorld); - } - } - - if (PackagesToSave.Num() > 0) - { - // These packages were either created during the Bake process or they weren't - // loaded in the first place so be sure to unload them again to preserve their "state". - - TArray PackagesToUnload; - for (UPackage* Package : PackagesToSave) - { - if (!Package->IsDirty()) - PackagesToUnload.Add(Package); - } - UPackageTools::UnloadPackages(PackagesToUnload); - } - -#if WITH_EDITOR - FEditorDelegates::RefreshLevelBrowser.Broadcast(); - FEditorDelegates::RefreshAllBrowsers.Broadcast(); -#endif - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeLandscapeObject( - FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInReplaceActors, - bool bInReplaceAssets, - FHoudiniPackageParams& PackageParams, - FHoudiniAttributeResolver& InResolver, - TArray& WorldsToUpdate, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& BakeStats) -{ - UHoudiniLandscapePtr* LandscapePointer = Cast(InOutputObject.OutputObject); - if (!LandscapePointer) - return false; - - ALandscapeProxy* TileActor = LandscapePointer->GetRawPtr(); - if (!TileActor) - return false; - - // Fetch the previous bake's pointer and proxy (if available) - ALandscapeProxy* PreviousTileActor = Cast(InBakedOutputObject.GetBakedObjectIfValid()); - - UWorld* TileWorld = TileActor->GetWorld(); - ULevel* TileLevel = TileActor->GetLevel(); - - ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true); - - // If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC - // and has the appropriate name. - ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor(); - check(SharedLandscapeActor); - - // Fetch the previous bake's shared landscape actor (if available) - ALandscape* PreviousSharedLandscapeActor = nullptr; - if (IsValid(PreviousTileActor)) - PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor(); - - const bool bHasSharedLandscape = SharedLandscapeActor != TileActor; - bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; - if (bHasPreviousSharedLandscape) - { - // Ignore the previous shared landscape if the world's are different - // Typically in baking we treat completely different asset/output names in a bake as detached from the "previous" bake - if (PreviousSharedLandscapeActor->GetWorld() != SharedLandscapeActor->GetWorld()) - bHasPreviousSharedLandscape = false; - } - - bool bLandscapeReplaced = false; - if (bHasSharedLandscape) - { - // If we are baking in replace mode and we have a previous shared landscape actor, use the name of that - // actor - FString SharedLandscapeName = InResolver.ResolveAttribute( - HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, - SharedLandscapeActor->GetName()); - - // If the shared landscape is still attached, or it's base name does not match the desired name, "bake" it - AActor* const AttachedParent = SharedLandscapeActor->GetAttachParentActor(); - if (AttachedParent || SharedLandscapeActor->GetFName().GetPlainNameString() != SharedLandscapeName) - { - if (bHasPreviousSharedLandscape && bInReplaceActors && - PreviousSharedLandscapeActor->GetFName().GetPlainNameString() == SharedLandscapeName) - { - SharedLandscapeName = PreviousSharedLandscapeActor->GetName(); - } - else if (!bInReplaceActors) - { - // If we are not baking in replacement mode, create a unique name if the name is already in use - SharedLandscapeName = MakeUniqueObjectNameIfNeeded( - SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *SharedLandscapeName, SharedLandscapeActor); - } - - if (SharedLandscapeActor->GetName() != SharedLandscapeName) - { - AActor* FoundActor = nullptr; - ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); - if (ExistingLandscape && bInReplaceActors) - { - // Even though we found an existing landscape with the desired type, we're just going to destroy/replace - // it for now. - FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); - ExistingLandscape->Destroy(); - bLandscapeReplaced = true; - } - - // Fix name of shared landscape - FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); - } - - SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); - } - } - - // Find the world where the landscape tile should be placed. - - TArray ValidLandscapes; - - FString ActorName = InResolver.ResolveOutputName(); - - // If the unreal_level_path was not specified, then fallback to the tile world's package - FString PackagePath = TileWorld->GetPackage() ? TileWorld->GetPackage()->GetPathName() : FString(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - PackagePath = InResolver.ResolveFullLevelPath(); - - // Get the previous baked actor (if available) name, but only if it is in the - // same target level, and it's plain name (no numeric suffix) matches ActorName - // In replacement mode we'll then replace the previous tile actor. - if (bInReplaceActors && IsValid(PreviousTileActor)) - { - UPackage* PreviousPackage = PreviousTileActor->GetPackage(); - if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath && - PreviousTileActor->GetFName().GetPlainNameString() == ActorName) - { - ActorName = PreviousTileActor->GetName(); - } - } - - bool bCreatedPackage = false; - UWorld* TargetWorld = nullptr; - ULevel* TargetLevel = nullptr; - ALandscapeProxy* TargetActor = FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( - TileActor->GetWorld(), - nullptr, //unused in bake mode - ValidLandscapes,//unused in bake mode - -1, //unused in bake mode - -1, //unused in bake mode - ActorName, - PackagePath, - TargetWorld, - TargetLevel, - bCreatedPackage - ); - - check(TargetLevel) - check(TargetWorld) - - if (TargetActor && TargetActor != TileActor) - { - if (bInReplaceActors && (!PreviousTileActor || PreviousTileActor == TargetActor)) - { - // We found an target matching the name that we want. For now, rename it and then nuke it, so that - // at the very least we can spawn a new actor with the desired name. At a later stage we'll implement - // a content update, if possible. - FHoudiniEngineUtils::RenameToUniqueActor(TargetActor, ActorName + TEXT("_0")); - TargetActor->Destroy(); - } - else - { - // incremental, keep existing actor and create a unique name for the new one - ActorName = MakeUniqueObjectNameIfNeeded(TargetActor->GetOuter(), TargetActor->GetClass(), ActorName, TileActor); - } - TargetActor = nullptr; - } - - if (TargetLevel != TileActor->GetLevel()) - { - bool bLevelInWorld = TileWorld->ContainsLevel(TargetLevel); - ALandscape* SharedLandscape = TileActor->GetLandscapeActor(); - ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); - - check(LandscapeInfo); - - // We can now move the current landscape to the new world / level - // if (TileActor->GetClass()->IsChildOf()) - { - // We can only move streaming proxies to sublevels for now. - TArray ActorsToMove = {TileActor}; - - ALandscapeProxy* NewLandscapeProxy = LandscapeInfo->MoveComponentsToLevel(TileActor->LandscapeComponents, TargetLevel); - // We have now moved the landscape components into the new level. We can (hopefully) safely delete the - // old tile actor. - TileActor->Destroy(); - - TargetLevel->MarkPackageDirty(); - - TileActor = NewLandscapeProxy; - } - } - else - { - // Ensure the landscape actor is detached. - TileActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - } - - // Ensure the tile actor has the desired name. - FHoudiniEngineUtils::SafeRenameActor(TileActor, ActorName); - - if (TileActor->GetClass()->IsChildOf(ALandscape::StaticClass())) - { - // This is not a shared landscape. Be sure to update this landscape's world when - // baking is done. - WorldsToUpdate.AddUnique(TileActor->GetWorld()); - } - - if (bCreatedPackage) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(TargetLevel->GetOutermost()); - } - - // Record the landscape in the baked output object via a new UHoudiniLandscapePtr - // UHoudiniLandscapePtr* BakedLandscapePtr = NewObject(LandscapePointer->GetOuter()); - // if (IsValid(BakedLandscapePtr)) - // { - // BakedLandscapePtr->SetSoftPtr(TileActor); - InBakedOutputObject.BakedObject = FSoftObjectPath(TileActor).ToString(); - // } - // else - // { - // InBakedOutputObject.BakedObject = nullptr; - // } - - // Bake the landscape layer uassets - ULandscapeInfo* const LandscapeInfo = TileActor->GetLandscapeInfo(); - if (IsValid(LandscapeInfo) && LandscapeInfo->Layers.Num() > 0) - { - TSet TempLayers; - const int32 NumLayers = LandscapeInfo->Layers.Num(); - TempLayers.Reserve(NumLayers); - for (int32 LayerIndex = 0; LayerIndex < NumLayers; ++LayerIndex) - { - const FLandscapeInfoLayerSettings& Layer = LandscapeInfo->Layers[LayerIndex]; - if (!IsValid(Layer.LayerInfoObj)) - continue; - - if (!IsObjectInTempFolder(Layer.LayerInfoObj, PackageParams.TempCookFolder)) - continue; - - if (!TempLayers.Contains(Layer.LayerInfoObj)) - TempLayers.Add(Layer.LayerInfoObj); - } - - // Setup package params to duplicate each layer - FHoudiniPackageParams LayerPackageParams = PackageParams; - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - LayerPackageParams.ReplaceMode = AssetPackageReplaceMode; - - // Determine the final bake name of the "owning" landscape (shared landscape in tiled mode, or just the - // landscape actor itself in non-tiled mode - FString OwningLandscapeActorBakeName; - if (bHasSharedLandscape && IsValid(SharedLandscapeActor)) - { - SharedLandscapeActor->GetName(OwningLandscapeActorBakeName); - } - else - { - TileActor->GetName(OwningLandscapeActorBakeName); - } - - // Keep track of the landscape layers we are baking this time around, and replace in the baked output object - // at the end. - TMap ThisBakedLandscapeLayers; - - // Bake/duplicate temp layers and replace temp layers via LandscapeInfo - for (ULandscapeLayerInfoObject* const LayerInfo : TempLayers) - { - const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerInfo->LayerName.ToString()); - LayerPackageParams.SplitStr = SanitizedLayerName; - LayerPackageParams.ObjectName = OwningLandscapeActorBakeName + TEXT("_layer_") + SanitizedLayerName; - - // Get the previously baked layer info for this layer, if any - ULandscapeLayerInfoObject* const PreviousBakedLayerInfo = InBakedOutputObject.GetLandscapeLayerInfoIfValid( - LayerInfo->LayerName); - - // If our name is the base name (no number) of the previous, then we can fetch the bake counter for - // replacement / incrementing from it - int32 BakeCounter = 0; - if (IsValid(PreviousBakedLayerInfo) && LayerPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakedLayerInfo)) - { - // Get the bake counter from the previous bake - FHoudiniPackageParams::GetBakeCounterFromBakedAsset(PreviousBakedLayerInfo, BakeCounter); - } - - FString LayerPackageName; - UPackage* const LayerPackage = LayerPackageParams.CreatePackageForObject(LayerPackageName, BakeCounter); - if (IsValid(LayerPackage)) - { - BakeStats.NotifyPackageCreated(1); - ULandscapeLayerInfoObject* BakedLayer = DuplicateObject( - LayerInfo, LayerPackage, *LayerPackageName); - if (IsValid(BakedLayer)) - { - OutPackagesToSave.Add(LayerPackage); - - // Trigger update of the Layer Info - BakedLayer->PreEditChange(nullptr); - BakedLayer->PostEditChange(); - BakedLayer->MarkPackageDirty(); - - // Mark the package dirty... - LayerPackage->MarkPackageDirty(); - - LandscapeInfo->ReplaceLayer(LayerInfo, BakedLayer); - - // Record as the new baked result for the LayerName - ThisBakedLandscapeLayers.Add(LayerInfo->LayerName, FSoftObjectPath(BakedLayer).ToString()); - } - } - } - - // Update the baked landscape layers in InBakedOutputObject - InBakedOutputObject.LandscapeLayers = ThisBakedLandscapeLayers; - } - - // Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks - InOutputObject.OutputObject = nullptr; - - DestroyPreviousBakeOutput(InBakedOutputObject, true, true, true); - - // ---------------------------------------------------- - // Collect baking stats - // ---------------------------------------------------- - if (bLandscapeReplaced) - BakeStats.NotifyObjectsReplaced(EHoudiniOutputType::Landscape, 1); - else - BakeStats.NotifyObjectsCreated(EHoudiniOutputType::Landscape, 1); - - if (bCreatedPackage) - BakeStats.NotifyPackageCreated(1); - else - if (TileLevel != TargetLevel) - BakeStats.NotifyPackageUpdated(1); - - return true; -} - -UStaticMesh * -FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - UStaticMesh * InStaticMesh, - UStaticMesh * InPreviousBakeStaticMesh, - const FHoudiniPackageParams &PackageParams, - const TArray& InParentOutputs, - const TArray& InCurrentBakedActors, - const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages, - TMap& InOutAlreadyBakedMaterialsMap) -{ - if (!InStaticMesh || InStaticMesh->IsPendingKill()) - return nullptr; - - const bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, EHoudiniOutputType::Mesh, InParentOutputs, InTemporaryCookFolder); - if (!bIsTemporaryStaticMesh) - { - // The Static Mesh is not a temporary one/already baked, we can simply reuse it - // instead of duplicating it - return InStaticMesh; - } - - // Look for InStaticMesh as the SourceObject in InCurrentBakedActors (it could have already been baked along with - // a previous output: instancers etc) - for (const FHoudiniEngineBakedActor& BakedActor : InCurrentBakedActors) - { - if (BakedActor.SourceObject == InStaticMesh && IsValid(BakedActor.BakedObject) - && BakedActor.BakedObject->IsA(InStaticMesh->GetClass())) - { - // We have found a bake result where InStaticMesh was the source object and we have a valid BakedObject - // of a compatible class - return Cast(BakedActor.BakedObject); - } - } - - // InStaticMesh is temporary and we didn't find a baked version of it in our current bake output, we need to bake it - - // If we have a previously baked static mesh, get the bake counter from it so that both replace and increment - // is consistent with the bake counter - int32 BakeCounter = 0; - bool bPreviousBakeStaticMeshValid = IsValid(InPreviousBakeStaticMesh); - TArray PreviousBakeMaterials; - if (bPreviousBakeStaticMeshValid) - { - bPreviousBakeStaticMeshValid = PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBakeStaticMesh); - if (bPreviousBakeStaticMeshValid) - { - PackageParams.GetBakeCounterFromBakedAsset(InPreviousBakeStaticMesh, BakeCounter); - PreviousBakeMaterials = InPreviousBakeStaticMesh->StaticMaterials;//GetStaticMaterials(); - } - } - FString CreatedPackageName; - UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter); - if (!MeshPackage || MeshPackage->IsPendingKill()) - return nullptr; - - OutCreatedPackages.Add(MeshPackage); - - // We need to be sure the package has been fully loaded before calling DuplicateObject - if (!MeshPackage->IsFullyLoaded()) - { - FlushAsyncLoading(); - if (!MeshPackage->GetOuter()) - { - MeshPackage->FullyLoad(); - } - else - { - MeshPackage->GetOutermost()->FullyLoad(); - } - } - - // If the a UStaticMesh with that name already exists then detach it from all of its components before replacing - // it so that its render resources can be safely replaced/updated, and then reattach it - UStaticMesh * DuplicatedStaticMesh = nullptr; - UStaticMesh* ExistingMesh = FindObject(MeshPackage, *CreatedPackageName); - bool bFoundExistingMesh = false; - if (IsValid(ExistingMesh)) - { - FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); - DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); - bFoundExistingMesh = true; - } - else - { - DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); - } - - if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MeshPackage, DuplicatedStaticMesh, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MeshPackage, DuplicatedStaticMesh, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedPackageName); - - // See if we need to duplicate materials and textures. - TArrayDuplicatedMaterials; - TArray& Materials = DuplicatedStaticMesh->StaticMaterials; - for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) - { - UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - continue; - - // Only duplicate the material if it is temporary - if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InParentOutputs, InTemporaryCookFolder)) - { - UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); - if (MaterialPackage && !MaterialPackage->IsPendingKill()) - { - FString MaterialName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( - MeshPackage, DuplicatedStaticMesh, MaterialName)) - { - MaterialName = MaterialName + "_Material" + FString::FromInt(MaterialIdx + 1); - - // We only deal with materials. - UMaterial * Material = Cast< UMaterial >(MaterialInterface); - if (Material && !Material->IsPendingKill()) - { - // Look for a previous bake material at this index - UMaterial* PreviousBakeMaterial = nullptr; - if (bPreviousBakeStaticMeshValid && PreviousBakeMaterials.IsValidIndex(MaterialIdx)) - { - PreviousBakeMaterial = Cast(PreviousBakeMaterials[MaterialIdx].MaterialInterface); - } - // Duplicate material resource. - UMaterial * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages, InOutAlreadyBakedMaterialsMap); - - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) - continue; - - // Store duplicated material. - FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx]; - DupeStaticMaterial.MaterialInterface = DuplicatedMaterial; - DuplicatedMaterials.Add(DupeStaticMaterial); - continue; - } - } - } - } - - // We can simply reuse the source material - DuplicatedMaterials.Add(Materials[MaterialIdx]); - } - - // Assign duplicated materials. - DuplicatedStaticMesh->StaticMaterials = DuplicatedMaterials; - - // Notify registry that we have created a new duplicate mesh. - if (!bFoundExistingMesh) - FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); - - // Dirty the static mesh package. - DuplicatedStaticMesh->MarkPackageDirty(); - - return DuplicatedStaticMesh; -} - -ALandscapeProxy* -FHoudiniEngineBakeUtils::BakeHeightfield( - ALandscapeProxy * InLandscapeProxy, - const FHoudiniPackageParams & PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType) -{ - if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill()) - return nullptr; - - const FString & BakeFolder = PackageParams.BakeFolder; - const FString & AssetName = PackageParams.HoudiniAssetName; - - switch (LandscapeOutputBakeType) - { - case EHoudiniLandscapeOutputBakeType::Detachment: - { - // Detach the landscape from the Houdini Asset Actor - InLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - } - break; - case EHoudiniLandscapeOutputBakeType::BakeToImage: - { - // Create heightmap image to the bake folder - ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) - return nullptr; - - // bake to image must use absoluate path, - // and the file name has a file extension (.png) - FString BakeFolderInFullPath = BakeFolder; - - // Figure absolute path, - if (!BakeFolderInFullPath.EndsWith("/")) - BakeFolderInFullPath += "/"; - - if (BakeFolderInFullPath.StartsWith("/Game")) - BakeFolderInFullPath = BakeFolderInFullPath.Mid(5, BakeFolderInFullPath.Len() - 5); - - if (BakeFolderInFullPath.StartsWith("/")) - BakeFolderInFullPath = BakeFolderInFullPath.Mid(1, BakeFolderInFullPath.Len() - 1); - - FString FullPath = FPaths::ProjectContentDir() + BakeFolderInFullPath + AssetName + "_" + InLandscapeProxy->GetName() + ".png"; - - InLandscapeInfo->ExportHeightmap(FullPath); - - // TODO: - // We should update this to have an asset/package.. - } - break; - case EHoudiniLandscapeOutputBakeType::BakeToWorld: - { - ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) - return nullptr; - - // 0. Get Landscape Data // - - // Extract landscape height data - TArray InLandscapeHeightData; - int32 XSize, YSize; - FVector Min, Max; - if (!FUnrealLandscapeTranslator::GetLandscapeData(InLandscapeProxy, InLandscapeHeightData, XSize, YSize, Min, Max)) - return nullptr; - - // Extract landscape Layers data - TArray InLandscapeImportLayerInfos; - for (int32 n = 0; n < InLandscapeInfo->Layers.Num(); ++n) - { - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeProxy, InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - FLandscapeImportLayerInfo CurrentLayerInfo; - CurrentLayerInfo.LayerName = FName(LayerName); - CurrentLayerInfo.LayerInfo = InLandscapeInfo->Layers[n].LayerInfoObj; - CurrentLayerInfo.LayerData = CurrentLayerIntData; - - CurrentLayerInfo.LayerInfo->LayerUsageDebugColor = LayerUsageDebugColor; - - InLandscapeImportLayerInfos.Add(CurrentLayerInfo); - } - - // 1. Create package // - - FString PackagePath = PackageParams.GetPackagePath(); - FString PackageName = PackageParams.GetPackageName(); - - UPackage *CreatedPackage = nullptr; - FString CreatedPackageName; - - CreatedPackage = PackageParams.CreatePackageForObject(CreatedPackageName); - - if (!CreatedPackage) - return nullptr; - - // 2. Create a new world asset with dialog // - UWorldFactory* Factory = NewObject(); - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - UObject* Asset = AssetToolsModule.Get().CreateAssetWithDialog( - PackageName, PackagePath, - UWorld::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - - - UWorld* NewWorld = Cast(Asset); - if (!NewWorld) - return nullptr; - - NewWorld->SetCurrentLevel(NewWorld->PersistentLevel); - - // 4. Spawn a landscape proxy actor in the created world - ALandscapeStreamingProxy * BakedLandscapeProxy = NewWorld->SpawnActor(); - if (!BakedLandscapeProxy) - return nullptr; - - // Create a new GUID - FGuid currentGUID = FGuid::NewGuid(); - BakedLandscapeProxy->SetLandscapeGuid(currentGUID); - - // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue - BakedLandscapeProxy->bCastStaticShadow = false; - - - // 5. Import data to the created landscape proxy - TMap> HeightmapDataPerLayers; - TMap> MaterialLayerDataPerLayer; - - HeightmapDataPerLayers.Add(FGuid(), InLandscapeHeightData); - MaterialLayerDataPerLayer.Add(FGuid(), InLandscapeImportLayerInfos); - - ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; - - BakedLandscapeProxy->Import( - currentGUID, - 0, 0, XSize-1, YSize-1, - InLandscapeInfo->ComponentNumSubsections, InLandscapeInfo->SubsectionSizeQuads, - HeightmapDataPerLayers, NULL, - MaterialLayerDataPerLayer, ImportLayerType); - - BakedLandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); - - - if (BakedLandscapeProxy->LandscapeMaterial) - BakedLandscapeProxy->LandscapeMaterial = InLandscapeProxy->LandscapeMaterial; - - if (BakedLandscapeProxy->LandscapeHoleMaterial) - BakedLandscapeProxy->LandscapeHoleMaterial = InLandscapeProxy->LandscapeHoleMaterial; - - // 6. Register all the landscape components, and set landscape actor transform - BakedLandscapeProxy->RegisterAllComponents(); - BakedLandscapeProxy->SetActorTransform(InLandscapeProxy->GetTransform()); - - // 7. Save Package - TArray PackagesToSave; - PackagesToSave.Add(CreatedPackage); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor) - { - TArray Objects; - Objects.Add(NewWorld); - GEditor->SyncBrowserToObjects(Objects); - } - } - break; - } - - return InLandscapeProxy; -} - -bool -FHoudiniEngineBakeUtils::BakeCurve( - USplineComponent* InSplineComponent, - ULevel* InLevel, - const FHoudiniPackageParams &PackageParams, - AActor*& OutActor, - USplineComponent*& OutSplineComponent, - FName InOverrideFolderPath, - AActor* InActor) -{ - if (!IsValid(InActor)) - { - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; - if (!Factory) - return false; - - OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform()); - } - else - { - OutActor = InActor; - } - - // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FName BaseActorName(PackageParams.ObjectName); - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName.ToString(), OutActor); - RenameAndRelabelActor(OutActor, NewNameStr, false); - OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); - - USplineComponent* DuplicatedSplineComponent = DuplicateObject( - InSplineComponent, - OutActor, - FName(MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), PackageParams.ObjectName))); - OutActor->AddInstanceComponent(DuplicatedSplineComponent); - const bool bCreateIfMissing = true; - USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); - DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); - - // We duplicated the InSplineComponent, so we don't have to copy all of its properties, but we must set the - // world transform - DuplicatedSplineComponent->SetWorldTransform(InSplineComponent->GetComponentTransform()); - - FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent); - DuplicatedSplineComponent->RegisterComponent(); - - OutSplineComponent = DuplicatedSplineComponent; - return true; -} - -bool -FHoudiniEngineBakeUtils::BakeCurve( - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - // const TArray& InAllBakedOutputs, - const FHoudiniPackageParams &PackageParams, - FHoudiniAttributeResolver& InResolver, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor, - const FString& InFallbackWorldOutlinerFolder) -{ - USplineComponent* SplineComponent = Cast(InOutputObject.OutputComponent); - if (!IsValid(SplineComponent)) - return false; - - // By default spawn in the current level unless specified via the unreal_level_path attribute - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); - if (bHasLevelPathAttribute) - { - UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; - - // Get the package path from the unreal_level_apth attribute - FString LevelPackagePath = InResolver.ResolveFullLevelPath(); - - bool bCreatedPackage = false; - if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - LevelPackagePath, - DesiredLevel, - DesiredWorld, - bCreatedPackage)) - { - // TODO: LOG ERROR IF NO LEVEL - return false; - } - - // If we have created a new level, add it to the packages to save - // TODO: ? always add? - if (bCreatedPackage && DesiredLevel) - { - // We can now save the package again, and unload it. - OutPackagesToSave.Add(DesiredLevel->GetOutermost()); - } - } - - if(!DesiredLevel) - return false; - - // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor - FName BakeActorName; - AActor* FoundActor = nullptr; - bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) - return false; - - // If we are baking in replace mode, remove the previous bake component - if (bInReplaceAssets && !InBakedOutputObject.BakedComponent.IsEmpty()) - { - UActorComponent* PrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (PrevComponent && PrevComponent->GetOwner() == FoundActor) - { - RemovePreviouslyBakedComponent(PrevComponent); - } - } - - FHoudiniPackageParams CurvePackageParams = PackageParams; - CurvePackageParams.ObjectName = BakeActorName.ToString(); - USplineComponent* NewSplineComponent = nullptr; - const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName)); - if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor)) - return false; - - InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - InBakedOutputObject.BakedComponent = FSoftObjectPath(NewSplineComponent).ToString(); - - // If we are baking in replace mode, remove previously baked components/instancers - if (bInReplaceActors && bInReplaceAssets) - { - const bool bInDestroyBakedComponent = false; - const bool bInDestroyBakedInstancedActors = true; - const bool bInDestroyBakedInstancedComponents = true; - DestroyPreviousBakeOutput( - InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); - } - - FHoudiniEngineBakedActor Result; - Result.Actor = FoundActor; - Result.ActorBakeName = BakeActorName; - Result.BakeFolderPath = PackageParams.BakeFolder; - Result.BakedObjectPackageParams = PackageParams; - OutActors.Add(Result); - - return true; -} - -AActor* -FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PackageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform) -{ - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) - return nullptr; - - TArray & DisplayPoints = InHoudiniSplineComponent->DisplayPoints; - if (DisplayPoints.Num() < 2) - return nullptr; - - ULevel* DesiredLevel = GWorld->GetCurrentLevel(); - - UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; - if (!Factory) - return nullptr; - - // Remove the actor if it exists - for (auto & Actor : DesiredLevel->Actors) - { - if (!Actor) - continue; - - if (Actor->GetName() == PackageParams.ObjectName) - { - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - Actor->RemoveFromRoot(); - Actor->ConditionalBeginDestroy(); - World->EditorDestroyActor(Actor, true); - - break; - } - } - - AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform()); - - USplineComponent* BakedUnrealSplineComponent = NewObject(NewActor); - if (!BakedUnrealSplineComponent) - return nullptr; - - // add display points to created unreal spline component - for (int32 n = 0; n < DisplayPoints.Num(); ++n) - { - FVector & NextPoint = DisplayPoints[n]; - BakedUnrealSplineComponent->AddSplinePoint(NextPoint, ESplineCoordinateSpace::Local); - // Set the curve point type to be linear, since we are using display points - BakedUnrealSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); - } - NewActor->AddInstanceComponent(BakedUnrealSplineComponent); - - BakedUnrealSplineComponent->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); - - FAssetRegistryModule::AssetCreated(NewActor); - FAssetRegistryModule::AssetCreated(BakedUnrealSplineComponent); - BakedUnrealSplineComponent->RegisterComponent(); - - // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); - RenameAndRelabelActor(NewActor, NewNameStr, false); - NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName)); - - return NewActor; -} - -UBlueprint* -FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PackageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform) -{ - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) - return nullptr; - - FGuid BakeGUID = FGuid::NewGuid(); - - if (!BakeGUID.IsValid()) - BakeGUID = FGuid::NewGuid(); - - // We only want half of generated guid string. - FString BakeGUIDString = BakeGUID.ToString().Left(FHoudiniEngineUtils::PackageGUIDItemNameLength); - - // Generate Blueprint name. - FString BlueprintName = PackageParams.ObjectName + "_BP"; - - // Generate unique package name. - FString PackageName = PackageParams.BakeFolder + "/" + BlueprintName; - PackageName = UPackageTools::SanitizePackageName(PackageName); - - // See if package exists, if it does, we need to regenerate the name. - UPackage * Package = FindPackage(nullptr, *PackageName); - - if (Package && !Package->IsPendingKill()) - { - // Package does exist, there's a collision, we need to generate a new name. - BakeGUID.Invalidate(); - } - else - { - // Create actual package. - Package = CreatePackage(*PackageName); - } - - AActor * CreatedHoudiniSplineActor = FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - InHoudiniSplineComponent, PackageParams, WorldToSpawn, SpawnTransform); - - TArray PackagesToSave; - - UBlueprint * Blueprint = nullptr; - if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill()) - { - - UObject* Asset = nullptr; - - Asset = StaticFindObjectFast(UObject::StaticClass(), Package, FName(*BlueprintName)); - if (!Asset) - { - UBlueprintFactory* Factory = NewObject(); - - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - Asset = AssetToolsModule.Get().CreateAsset( - BlueprintName, PackageParams.BakeFolder, - UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - } - - TArray Components; - for (auto & Next : CreatedHoudiniSplineActor->GetComponents()) - { - Components.Add(Next); - } - - Blueprint = Cast(Asset); - - // Clear old Blueprint Node tree - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - - int32 NodeSize = SCS->GetAllNodes().Num(); - for (int32 n = NodeSize - 1; n >= 0; --n) - SCS->RemoveNode(SCS->GetAllNodes()[n]); - - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components); - - CreatedHoudiniSplineActor->RemoveFromRoot(); - CreatedHoudiniSplineActor->ConditionalBeginDestroy(); - - GWorld->EditorDestroyActor(CreatedHoudiniSplineActor, true); - - Package->MarkPackageDirty(); - PackagesToSave.Add(Package); - } - - // Save the created BP package. - FHoudiniEngineBakeUtils::SaveBakedPackages - (PackagesToSave); - - return Blueprint; -} - - -void -FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const TCHAR * Key, - const TCHAR * Value) -{ - if (!Package || Package->IsPendingKill()) - return; - - UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) - MetaData->SetValue(Object, Key, Value); -} - - -bool -FHoudiniEngineBakeUtils:: -GetHoudiniGeneratedNameFromMetaInformation( - UPackage * Package, UObject * Object, FString & HoudiniName) -{ - if (!Package || Package->IsPendingKill()) - return false; - - UMetaData * MetaData = Package->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return false; - - if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - { - // Retrieve name used for package generation. - const FString NameFull = MetaData->GetValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME); - - //HoudiniName = NameFull.Left(FMath::Min(NameFull.Len(), FHoudiniEngineUtils::PackageGUIDItemNameLength)); - HoudiniName = NameFull; - return true; - } - - return false; -} - -UMaterial * -FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - UMaterial * Material, UMaterial* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutGeneratedPackages, - TMap& InOutAlreadyBakedMaterialsMap) -{ - if (InOutAlreadyBakedMaterialsMap.Contains(Material)) - { - return InOutAlreadyBakedMaterialsMap[Material]; - } - - UMaterial * DuplicatedMaterial = nullptr; - - FString CreatedMaterialName; - // Create material package. Use the same package params as static mesh, but with the material's name - FHoudiniPackageParams MaterialPackageParams = ObjectPackageParams; - MaterialPackageParams.ObjectName = MaterialName; - - // Check if there is a valid previous material. If so, get the bake counter for consistency in - // replace or iterative package naming - bool bIsPreviousBakeMaterialValid = IsValid(PreviousBakeMaterial); - int32 BakeCounter = 0; - TArray PreviousBakeMaterialExpressions; - if (bIsPreviousBakeMaterialValid) - { - bIsPreviousBakeMaterialValid = MaterialPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeMaterial); - if (bIsPreviousBakeMaterialValid) - { - MaterialPackageParams.GetBakeCounterFromBakedAsset(PreviousBakeMaterial, BakeCounter); - PreviousBakeMaterialExpressions = PreviousBakeMaterial->Expressions; - } - } - - UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter); - - if (!MaterialPackage || MaterialPackage->IsPendingKill()) - return nullptr; - - // Clone material. - DuplicatedMaterial = DuplicateObject< UMaterial >(Material, MaterialPackage, *CreatedMaterialName); - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, DuplicatedMaterial, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - MaterialPackage, DuplicatedMaterial, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedMaterialName); - - // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. - const int32 NumExpressions = DuplicatedMaterial->Expressions.Num(); - for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx) - { - UMaterialExpression* Expression = DuplicatedMaterial->Expressions[ExpressionIdx]; - UMaterialExpression* PreviousBakeExpression = nullptr; - if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx)) - { - PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; - } - FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( - Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages); - } - - // Notify registry that we have created a new duplicate material. - FAssetRegistryModule::AssetCreated(DuplicatedMaterial); - - // Dirty the material package. - DuplicatedMaterial->MarkPackageDirty(); - - // Recompile the baked material - // DuplicatedMaterial->ForceRecompileForRendering(); - // Use UMaterialEditingLibrary::RecompileMaterial since it correctly updates texture references in the material - // which ForceRecompileForRendering does not do - UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterial); - - OutGeneratedPackages.Add(MaterialPackage); - - InOutAlreadyBakedMaterialsMap.Add(Material, DuplicatedMaterial); - - return DuplicatedMaterial; -} - -void -FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( - UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, - const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages) -{ - UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression); - if (!TextureSample || TextureSample->IsPendingKill()) - return; - - UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture); - if (!Texture || Texture->IsPendingKill()) - return; - - UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) - return; - - // Try to get the previous bake's texture - UTexture2D* PreviousBakeTexture = nullptr; - if (IsValid(PreviousBakeMaterialExpression)) - { - UMaterialExpressionTextureSample* PreviousBakeTextureSample = Cast< UMaterialExpressionTextureSample >(PreviousBakeMaterialExpression); - if (IsValid(PreviousBakeTextureSample)) - PreviousBakeTexture = Cast< UTexture2D >(PreviousBakeTextureSample->Texture); - } - - FString GeneratedTextureName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( - TexturePackage, Texture, GeneratedTextureName)) - { - // Duplicate texture. - UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( - Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages); - - // Re-assign generated texture. - TextureSample->Texture = DuplicatedTexture; - } -} - -UTexture2D * -FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( - UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages) -{ - UTexture2D* DuplicatedTexture = nullptr; -#if WITH_EDITOR - // Retrieve original package of this texture. - UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) - return nullptr; - - FString GeneratedTextureName; - if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName)) - { - UMetaData * MetaData = TexturePackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return nullptr; - - // Retrieve texture type. - const FString & TextureType = - MetaData->GetValue(Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); - - FString CreatedTextureName; - - // Create texture package. Use the same package params as material's, but with object name appended by generated texture's name - FHoudiniPackageParams TexturePackageParams = PackageParams; - TexturePackageParams.ObjectName = TexturePackageParams.ObjectName + "_" + GeneratedTextureName; - - // Determine the bake counter of the previous bake's texture (if exists/valid) for naming consistency when - // replacing/iterating - bool bIsPreviousBakeTextureValid = IsValid(PreviousBakeTexture); - int32 BakeCounter = 0; - if (bIsPreviousBakeTextureValid) - { - bIsPreviousBakeTextureValid = TexturePackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeTexture); - if (bIsPreviousBakeTextureValid) - { - TexturePackageParams.GetBakeCounterFromBakedAsset(PreviousBakeTexture, BakeCounter); - } - } - - UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter); - - if (!NewTexturePackage || NewTexturePackage->IsPendingKill()) - return nullptr; - - // Clone texture. - DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName); - if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill()) - return nullptr; - - // Add meta information. - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedTextureName); - FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( - NewTexturePackage, DuplicatedTexture, - HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); - - // Notify registry that we have created a new duplicate texture. - FAssetRegistryModule::AssetCreated(DuplicatedTexture); - - // Dirty the texture package. - DuplicatedTexture->MarkPackageDirty(); - - OutCreatedPackages.Add(NewTexturePackage); - } -#endif - return DuplicatedTexture; -} - - -bool -FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); - - if (!ActorOwner || ActorOwner->IsPendingKill()) - return false; - - UWorld* World = ActorOwner->GetWorld(); - if (!World) - World = GWorld; - - World->EditorDestroyActor(ActorOwner, false); - - return true; -} - - -void -FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld) -{ - UWorld * CurrentWorld = nullptr; - if (bSaveCurrentWorld && GEditor) - CurrentWorld = GEditor->GetEditorWorldContext().World(); - - if (CurrentWorld) - { - // Save the current map - FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); - UPackage* CurrentWorldPackage = CreatePackage(*CurrentWorldPath); - - if (CurrentWorldPackage) - { - CurrentWorldPackage->MarkPackageDirty(); - PackagesToSave.Add(CurrentWorldPackage); - } - } - - FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); -} - -bool -FHoudiniEngineBakeUtils::FindOutputObject(const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) -{ - if (!InObjectToFind || InObjectToFind->IsPendingKill()) - return false; - - const int32 NumOutputs = InOutputs.Num(); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - const UHoudiniOutput* CurOutput = InOutputs[OutputIdx]; - if (!IsValid(CurOutput)) - continue; - - if (CurOutput->GetType() != InOutputType) - continue; - - for (auto& CurOutputObject : CurOutput->GetOutputObjects()) - { - if (CurOutputObject.Value.OutputObject == InObjectToFind - || CurOutputObject.Value.OutputComponent == InObjectToFind - || CurOutputObject.Value.ProxyObject == InObjectToFind - || CurOutputObject.Value.ProxyComponent == InObjectToFind) - { - OutOutputIndex = OutputIdx; - OutIdentifier = CurOutputObject.Key; - return true; - } - } - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - FString TempPath = FString(); - - // TODO: Get the HAC outputs in a better way? - TArray Outputs; - if (InHAC && !InHAC->IsPendingKill()) - { - const int32 NumOutputs = InHAC->GetNumOutputs(); - Outputs.Reserve(NumOutputs); - for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) - { - Outputs.Add(InHAC->GetOutputAt(OutputIdx)); - } - - TempPath = InHAC->TemporaryCookFolder.Path; - } - - return IsObjectTemporary(InObject, InOutputType, Outputs, TempPath); -} - -bool FHoudiniEngineBakeUtils::IsObjectInTempFolder(UObject* const InObject, const FString& InTemporaryCookFolder) -{ - if (!IsValid(InObject)) - return false; - - // Check the package path for this object - // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated - UPackage* ObjectPackage = InObject->GetOutermost(); - if (ObjectPackage && !ObjectPackage->IsPendingKill()) - { - const FString PathName = ObjectPackage->GetPathName(); - if (PathName.StartsWith(InTemporaryCookFolder)) - return true; - - // Also check the default temp folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder)) - return true; - } - - return false; -} - -bool FHoudiniEngineBakeUtils::IsObjectTemporary( - UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - int32 ParentOutputIndex = -1; - FHoudiniOutputObjectIdentifier Identifier; - if (FindOutputObject(InObject, InOutputType, InParentOutputs, ParentOutputIndex, Identifier)) - return true; - - // Check the package path for this object - // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated - if (IsObjectInTempFolder(InObject, InTemporaryCookFolder)) - return true; - - /* - UPackage* ObjectPackage = InObject->GetOutermost(); - if (ObjectPackage && !ObjectPackage->IsPendingKill()) - { - // TODO: this just indicates that the object was generated by H - // it could as well have been baked before... - // we should probably add a "temp" metadata - // Look in the meta info as well?? - UMetaData * MetaData = ObjectPackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) - return false; - - if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) - return true; - } - */ - - return false; -} - -void -FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent( - AActor* NewActor, - UStaticMeshComponent* NewSMC, - UStaticMeshComponent* InSMC, - bool bInCopyWorldTransform) -{ - if (!NewSMC || NewSMC->IsPendingKill()) - return; - - if (!InSMC || InSMC->IsPendingKill()) - return; - - // Copy properties to new actor - //UStaticMeshComponent* OtherSMC_NonConst = const_cast(StaticMeshComponent); - NewSMC->SetCollisionProfileName(InSMC->GetCollisionProfileName()); - NewSMC->SetCollisionEnabled(InSMC->GetCollisionEnabled()); - NewSMC->LightmassSettings = InSMC->LightmassSettings; - NewSMC->CastShadow = InSMC->CastShadow; - NewSMC->SetMobility(InSMC->Mobility); - - UBodySetup* InBodySetup = InSMC->GetBodySetup(); - UBodySetup* NewBodySetup = NewSMC->GetBodySetup(); - if (InBodySetup && NewBodySetup) - { - // Copy the BodySetup - NewBodySetup->CopyBodyPropertiesFrom(InBodySetup); - - // We need to recreate the physics mesh for the new body setup - NewBodySetup->InvalidatePhysicsData(); - NewBodySetup->CreatePhysicsMeshes(); - - // Only copy the physical material if it's different from the default one, - // As this was causing crashes on BakeToActors in some cases - if (GEngine != NULL && NewBodySetup->GetPhysMaterial() != GEngine->DefaultPhysMaterial) - NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial()); - } - - if (NewActor && !NewActor->IsPendingKill()) - NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame); - - NewSMC->SetVisibility(InSMC->IsVisible()); - - // TODO: - // // Reapply the uproperties modified by attributes on the new component - // FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO); - - // The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another - UClass* ComponentClass = nullptr; - if (InSMC->GetClass()->IsChildOf(NewSMC->GetClass())) - { - ComponentClass = NewSMC->GetClass(); - } - else if (NewSMC->GetClass()->IsChildOf(InSMC->GetClass())) - { - ComponentClass = InSMC->GetClass(); - } - else - { - HOUDINI_LOG_WARNING( - TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), - *(InSMC->GetName()), - *(NewSMC->GetClass()->GetName())); - - NewSMC->PostEditChange(); - return; - } - - TSet SourceUCSModifiedProperties; - InSMC->GetUCSModifiedProperties(SourceUCSModifiedProperties); - - AActor* SourceActor = InSMC->GetOwner(); - if (!IsValid(SourceActor)) - { - NewSMC->PostEditChange(); - return; - } - - TArray ModifiedObjects; - const EditorUtilities::FCopyOptions Options(EditorUtilities::ECopyOptions::CallPostEditChangeProperty); - // Copy component properties - for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) - { - const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); - const bool bIsIdentical = Property->Identical_InContainer( InSMC, NewSMC ); - const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); - const bool bIsTransform = - Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); - - // auto SourceComponentIsRoot = [&]() - // { - // USceneComponent* RootComponent = SourceActor->GetRootComponent(); - // if (InSMC == RootComponent) - // { - // return true; - // } - // return false; - // }; - - // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - // && ( !bIsTransform || !SourceComponentIsRoot() ) ) - if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - && !bIsTransform ) - { - // const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) - // && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); - const bool bIsSafeToCopy = true; - if( bIsSafeToCopy ) - { - if (!Options.CanCopyProperty(*Property, *SourceActor)) - { - continue; - } - - if( !ModifiedObjects.Contains(NewSMC) ) - { - NewSMC->SetFlags(RF_Transactional); - NewSMC->Modify(); - ModifiedObjects.Add(NewSMC); - } - - if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) - { - // @todo simulate: Should we be calling this on the component instead? - NewActor->PreEditChange( Property ); - } - - EditorUtilities::CopySingleProperty(InSMC, NewSMC, Property); - - if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) - { - FPropertyChangedEvent PropertyChangedEvent( Property ); - NewActor->PostEditChangeProperty( PropertyChangedEvent ); - } - } - } - } - - if (bInCopyWorldTransform) - { - NewSMC->SetWorldTransform(InSMC->GetComponentTransform()); - } - - NewSMC->PostEditChange(); -}; - -bool -FHoudiniEngineBakeUtils::RemovePreviouslyBakedActor( - AActor* InNewBakedActor, - ULevel* InLevel, - const FHoudiniPackageParams& InPackageParams) -{ - // Remove a previous bake actor if it exists - for (auto & Actor : InLevel->Actors) - { - if (!Actor) - continue; - - if (Actor != InNewBakedActor && Actor->GetName() == InPackageParams.ObjectName) - { - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - Actor->RemoveFromRoot(); - Actor->ConditionalBeginDestroy(); - World->EditorDestroyActor(Actor, true); - - return true; - } - } - - return false; -} - -bool -FHoudiniEngineBakeUtils::RemovePreviouslyBakedComponent(UActorComponent* InComponent) -{ - if (!IsValid(InComponent)) - return false; - - // Remove from its actor first - if (InComponent->GetOwner()) - InComponent->GetOwner()->RemoveOwnedComponent(InComponent); - - // Detach from its parent component if attached - USceneComponent* SceneComponent = Cast(InComponent); - if (IsValid(SceneComponent)) - SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - InComponent->UnregisterComponent(); - InComponent->DestroyComponent(); - - return true; -} - -FName -FHoudiniEngineBakeUtils::GetOutputFolderPath(UObject* InOutputOwner) -{ - // Get an output folder path for PDG outputs generated by InOutputOwner. - // The folder path is: / - FString FolderName; - FName FolderDirName; - AActor* OuterActor = Cast(InOutputOwner); - if (OuterActor) - { - FolderName = OuterActor->GetActorLabel(); - FolderDirName = OuterActor->GetFolderPath(); - } - else - { - FolderName = InOutputOwner->GetName(); - } - if (!FolderDirName.IsNone()) - return FName(FString::Printf(TEXT("%s/%s"), *FolderDirName.ToString(), *FolderName)); - else - return FName(FolderName); -} - -void -FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique) -{ - FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); - - const FSoftObjectPath OldPath = FSoftObjectPath(InAsset); - - FString NewName; - if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetPackage(), InAsset->GetClass(), InNewName, InAsset); - else - NewName = InNewName; - - FHoudiniEngineUtils::RenameObject(InAsset, *NewName); - - const FSoftObjectPath NewPath = FSoftObjectPath(InAsset); - if (OldPath != NewPath) - { - TArray RenameData; - RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); - AssetToolsModule.Get().RenameAssets(RenameData); - } -} - -void -FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique) -{ - if (!IsValid(InActor)) - return; - - FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); - - const FSoftObjectPath OldPath = FSoftObjectPath(InActor); - - FString NewName; - if (bMakeUniqueIfNotUnique) - NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), InNewName, InActor); - else - NewName = InNewName; - - FHoudiniEngineUtils::RenameObject(InActor, *NewName); - FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName); - - const FSoftObjectPath NewPath = FSoftObjectPath(InActor); - if (OldPath != NewPath) - { - TArray RenameData; - RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); - AssetToolsModule.Get().RenameAssets(RenameData); - } -} - -bool -FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor( - AActor* InActor, - const FString& InNewName, - const FName& InFolderPath) -{ - if (!IsValid(InActor)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: InActor is null.")); - return false; - } - - if (InNewName.TrimStartAndEnd().IsEmpty()) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: A valid actor name must be specified.")); - return false; - } - - // Detach from parent - InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - // Rename - const bool bMakeUniqueIfNotUnique = true; - RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique); - - InActor->SetFolderPath(InFolderPath); - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultArrayIndex, - int32 InWorkResultObjectArrayIndex, - bool bInReplaceActors, - bool bInReplaceAssets, - bool bInBakeToWorkResultActor, - bool bInIsAutoBake, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake, - TArray const* InInstancerComponentTypesToBake, - const FString& InFallbackWorldOutlinerFolder) -{ - if (!IsValid(InPDGAssetLink)) - return false; - - if (!IsValid(InNode)) - return false; - - if (!InNode->WorkResult.IsValidIndex(InWorkResultArrayIndex)) - return false; - - FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultArrayIndex]; - if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) - return false; - - FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectArrayIndex]; - TArray& Outputs = WorkResultObject.GetResultOutputs(); - if (Outputs.Num() == 0) - return true; - - if (WorkResultObject.State != EPDGWorkResultState::Loaded) - { - if (bInIsAutoBake && WorkResultObject.AutoBakedSinceLastLoad()) - { - HOUDINI_LOG_MESSAGE(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded but was auto-baked since its last load."), *WorkResultObject.Name); - return true; - } - - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded, cannot bake it."), *WorkResultObject.Name); - return false; - } - - AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor(); - if (!IsValid(WorkResultObjectActor)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObjectActor (%s) is null (unexpected since # Outputs > 0)"), *WorkResultObject.Name); - return false; - } - - // OutBakedActors contains each actor that contains baked PDG results. Actors may - // appear in the array more than once if they have more than one baked result/component associated with - // them - // TArray BakedActorsForWorkResultObject; - const int32 NumBakedPre = OutBakedActors.Num(); - const FString HoudiniAssetName(WorkResultObject.Name); - - // Find the previous bake output for this work result object - FString Key; - InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key); - FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key); - - const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InPDGAssetLink); - check(IsValid(HoudiniAssetComponent)); - - BakeHoudiniOutputsToActors( - HoudiniAssetComponent, - Outputs, - BakedOutputContainer.BakedOutputs, - HoudiniAssetName, - WorkResultObjectActor->GetActorTransform(), - InPDGAssetLink->BakeFolder, - InPDGAssetLink->GetTemporaryCookFolder(), - bInReplaceActors, - bInReplaceAssets, - OutBakedActors, - OutPackagesToSave, - OutBakeStats, - InOutputTypesToBake, - InInstancerComponentTypesToBake, - bInBakeToWorkResultActor ? WorkResultObjectActor : nullptr, - InFallbackWorldOutlinerFolder); - - // Set the PDG indices on the output baked actor entries - FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); - AActor* const WROActor = OutputActorOwner.GetOutputActor(); - FHoudiniEngineBakedActor const * BakedWROActorEntry = nullptr; - const int32 NumBakedPost = OutBakedActors.Num(); - if (NumBakedPost > NumBakedPre) - { - for (int32 Index = FMath::Max(0, NumBakedPre); Index < NumBakedPost; ++Index) - { - FHoudiniEngineBakedActor& BakedActorEntry = OutBakedActors[Index]; - BakedActorEntry.PDGWorkResultArrayIndex = InWorkResultArrayIndex; - BakedActorEntry.PDGWorkItemIndex = WorkResult.WorkItemIndex; - BakedActorEntry.PDGWorkResultObjectArrayIndex = InWorkResultObjectArrayIndex; - - if (WROActor && BakedActorEntry.Actor == WROActor) - { - BakedWROActorEntry = &BakedActorEntry; - } - } - } - - // If anything was baked to WorkResultObjectActor, detach it from its parent - if (bInBakeToWorkResultActor) - { - // if we re-used the temp actor as a bake actor, then remove its temp outputs - WorkResultObject.DestroyResultOutputs(); - if (WROActor) - { - if (BakedWROActorEntry) - { - OutputActorOwner.SetOutputActor(nullptr); - const FString OldActorPath = FSoftObjectPath(WROActor).ToString(); - DetachAndRenameBakedPDGOutputActor( - WROActor, BakedWROActorEntry->ActorBakeName.ToString(), BakedWROActorEntry->WorldOutlinerFolder); - const FString NewActorPath = FSoftObjectPath(WROActor).ToString(); - if (OldActorPath != NewActorPath) - { - // Fix cached string reference in baked outputs to WROActor - for (FHoudiniBakedOutput& BakedOutput : BakedOutputContainer.BakedOutputs) - { - for (auto& Entry : BakedOutput.BakedOutputObjects) - { - if (Entry.Value.Actor == OldActorPath) - Entry.Value.Actor = NewActorPath; - } - } - } - } - else - { - OutputActorOwner.DestroyOutputActor(); - } - } - } - - if (bInIsAutoBake) - WorkResultObject.SetAutoBakedSinceLastLoad(true); - // OutBakedActors.Append(BakedActorsForWorkResultObject); - return true; -} - -void -FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkItemHAPIIndex, - int32 InWorkItemResultInfoIndex) -{ - if (!IsValid(InPDGAssetLink)) - return; - - if (!InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded) - return; - - if (!IsValid(InNode)) - return; - - // Check if the node is ready for baking: all work items must be complete - bool bDoNotBake = false; - bool bPendingBakeItems = false; - if (!InNode->AreAllWorkItemsComplete() || InNode->AnyWorkItemsFailed()) - bDoNotBake = true; - - // Check if the node is ready for baking: all work items must be loaded - if (!bDoNotBake) - { - for (const FTOPWorkResult& WorkResult : InNode->WorkResult) - { - for (const FTOPWorkResultObject& WRO : WorkResult.ResultObjects) - { - if (WRO.State != EPDGWorkResultState::Loaded && !WRO.AutoBakedSinceLastLoad()) - { - bDoNotBake = true; - break; - } - } - if (bDoNotBake) - break; - } - } - - if (!bDoNotBake) - { - // Check which outputs are selected for baking: selected node, selected network or all - // And only bake if the node falls within the criteria - UTOPNetwork const * const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - UTOPNode const * const SelectedTOPNode = InPDGAssetLink->GetSelectedTOPNode(); - switch (InPDGAssetLink->PDGBakeSelectionOption) - { - case EPDGBakeSelectionOption::SelectedNetwork: - if (!IsValid(SelectedTOPNetwork) || !InNode->IsParentTOPNetwork(SelectedTOPNetwork)) - { - HOUDINI_LOG_WARNING( - TEXT("Not baking Node %s (Net %s): not in selected network"), - InNode ? *InNode->GetName() : TEXT(""), - SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); - bDoNotBake = true; - } - break; - case EPDGBakeSelectionOption::SelectedNode: - if (InNode != SelectedTOPNode) - { - HOUDINI_LOG_WARNING( - TEXT("Not baking Node %s (Net %s): not the selected node"), - InNode ? *InNode->GetName() : TEXT(""), - SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); - bDoNotBake = true; - } - break; - case EPDGBakeSelectionOption::All: - default: - break; - } - } - - // If there are no nodes left to auto-bake, broadcast the onpostbake delegate - if (bDoNotBake && !InPDGAssetLink->AnyRemainingAutoBakeNodes()) - InPDGAssetLink->HandleOnPostBake(true); - - if (bDoNotBake) - return; - - bool bSuccess = false; - const bool bIsAutoBake = true; - switch (InPDGAssetLink->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - bSuccess = FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InNode, bIsAutoBake, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - bSuccess = FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(InPDGAssetLink, InNode, bIsAutoBake, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); - break; - - default: - HOUDINI_LOG_WARNING(TEXT("Unsupported HoudiniEngineBakeOption %i"), InPDGAssetLink->HoudiniEngineBakeOption); - } - - if (!InPDGAssetLink->AnyRemainingAutoBakeNodes()) - InPDGAssetLink->HandleOnPostBake(bSuccess); -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInBakeForBlueprint, - bool bInIsAutoBake, - const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNode)) - return false; - - // Determine the output world outliner folder path via the PDG asset link's - // owner's folder path and name - UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); - if (!PDGOwner) - PDGOwner = InPDGAssetLink->GetOuter(); - const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); - - // Determine the actor/package replacement settings - const bool bReplaceActors = !bInBakeForBlueprint && InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - const bool bReplaceAssets = InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Determine the output types to bake: don't bake landscapes in blueprint baking mode - TArray OutputTypesToBake; - TArray InstancerComponentTypesToBake; - if (bInBakeForBlueprint) - { - OutputTypesToBake.Add(EHoudiniOutputType::Mesh); - OutputTypesToBake.Add(EHoudiniOutputType::Instancer); - OutputTypesToBake.Add(EHoudiniOutputType::Curve); - - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); - InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent); - } - - const int32 NumWorkResults = InNode->WorkResult.Num(); - FScopedSlowTask Progress(NumWorkResults, FText::FromString(FString::Printf(TEXT("Baking PDG Node Output %s ..."), *InNode->GetName()))); - Progress.MakeDialog(); - for (int32 WorkResultArrayIdx = 0; WorkResultArrayIdx < NumWorkResults; ++WorkResultArrayIdx) - { - FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultArrayIdx]; - const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); - for (int32 WorkResultObjectArrayIdx = 0; WorkResultObjectArrayIdx < NumWorkResultObjects; ++WorkResultObjectArrayIdx) - { - Progress.EnterProgressFrame(1.0f); - - BakePDGWorkResultObject( - InPDGAssetLink, - InNode, - WorkResultArrayIdx, - WorkResultObjectArrayIdx, - bReplaceActors, - bReplaceAssets, - !bInBakeForBlueprint, - bInIsAutoBake, - OutBakedActors, - OutPackagesToSave, - OutBakeStats, - OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, - InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, - FallbackWorldOutlinerFolderPath.ToString() - ); - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) -{ - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - TArray BakedActors; - - const bool bBakeBlueprints = false; - - bool bSuccess = BakePDGTOPNodeOutputsKeepActors( - InPDGAssetLink, InTOPNode, bBakeBlueprints, bInIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); - - SaveBakedPackages(PackagesToSave); - - // Recenter and select the baked actors - if (GEditor && BakedActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : BakedActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (bInRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && BakedActors.Num() > 0) - GEditor->NoteSelectionChange(); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - bool bInBakeForBlueprint, - bool bInIsAutoBake, - const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, - TArray& BakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNetwork)) - return false; - - bool bSuccess = true; - for (UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, bInIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, OutPackagesToSave, OutBakeStats); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - TArray BakedActors; - - const bool bBakeBlueprints = false; - const bool bIsAutoBake = false; - - bool bSuccess = true; - switch(InBakeSelectionOption) - { - case EPDGBakeSelectionOption::All: - for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) - { - if (!IsValid(Network)) - continue; - - for (UTOPNode* Node : Network->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); - } - } - break; - case EPDGBakeSelectionOption::SelectedNetwork: - bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); - case EPDGBakeSelectionOption::SelectedNode: - bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); - } - - SaveBakedPackages(PackagesToSave); - - // Recenter and select the baked actors - if (GEditor && BakedActors.Num() > 0) - GEditor->SelectNone(false, true); - - for (const FHoudiniEngineBakedActor& Entry : BakedActors) - { - if (!IsValid(Entry.Actor)) - continue; - - if (bInRecenterBakedActors) - CenterActorToBoundingBoxCenter(Entry.Actor); - - if (GEditor) - GEditor->SelectActor(Entry.Actor, true, false); - } - - if (GEditor && BakedActors.Num() > 0) - GEditor->NoteSelectionChange(); - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - // Broadcast that the bake is complete - InPDGAssetLink->HandleOnPostBake(bSuccess); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( - const TArray& InBakedActors, - bool bInRecenterBakedActors, - bool bInReplaceAssets, - const FString& InAssetName, - const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOutputs, - TMap* const InPDGBakedOutputs, - TArray& OutBlueprints, - TArray& OutPackagesToSave) -{ - // // Clear selection - // if (GEditor) - // { - // GEditor->SelectNone(false, true); - // GEditor->NoteSelectionChange(); - // } - - // Iterate over the baked actors. An actor might appear multiple times if multiple OutputComponents were - // baked to the same actor, so keep track of actors we have already processed in BakedActorSet - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem); - TArray AssetsToReOpenEditors; - TSet BakedActorSet; - - for (const FHoudiniEngineBakedActor& Entry : InBakedActors) - { - AActor *Actor = Entry.Actor; - - if (!Actor || Actor->IsPendingKill()) - continue; - - if (BakedActorSet.Contains(Actor)) - continue; - - BakedActorSet.Add(Actor); - - UObject* Asset = nullptr; - - // Recenter the actor to its bounding box center - if (bInRecenterBakedActors) - CenterActorToBoundingBoxCenter(Actor); - - // Create package for out Blueprint - FString BlueprintName; - - // For instancers we determine the bake folder from the instancer, - // for everything else we use the baked object's bake folder - // If all of that is blank, we fall back to InBakeFolder. - FString BakeFolderPath; - if (Entry.bInstancerOutput) - BakeFolderPath = Entry.InstancerPackageParams.BakeFolder; - else - BakeFolderPath = Entry.BakeFolderPath; - if (BakeFolderPath.IsEmpty()) - BakeFolderPath = InBakeFolder.Path; - - FHoudiniPackageParams PackageParams; - // Set the replace mode based on if we are doing a replacement or incremental asset bake - const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? - EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( - PackageParams, - FHoudiniOutputObjectIdentifier(), - BakeFolderPath, - Entry.ActorBakeName.ToString() + "_BP", - InAssetName, - AssetPackageReplaceMode); - - // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment - // is consistent with the bake counter - int32 BakeCounter = 0; - UBlueprint* InPreviousBlueprint = nullptr; - FHoudiniBakedOutputObject* BakedOutputObject = nullptr; - FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; - // Get the baked output object - if (Entry.PDGWorkResultArrayIndex >= 0 && Entry.PDGWorkItemIndex >= 0 && Entry.PDGWorkResultObjectArrayIndex >= 0 && InPDGBakedOutputs) - { - const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkItemIndex, Entry.PDGWorkResultObjectArrayIndex); - WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); - if (WorkResultObjectBakedOutput) - { - if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } - } - else if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs) - { - if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs->IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = (*InNonPDGBakedOutputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } - if (BakedOutputObject) - { - InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); - if (IsValid(InPreviousBlueprint)) - { - if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint)) - { - PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter); - } - } - } - - UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter); - - if (!Package || Package->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName())); - continue; - } - - if (!Package->IsFullyLoaded()) - Package->FullyLoad(); - - //Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false); - // Find existing asset first (only relevant if we are in replacement mode). If the existing asset has a - // different base class than the incoming actor, we reparent the blueprint to the new base class before - // clearing the SCS graph and repopulating it from the temp actor. - Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName)); - if (IsValid(Asset)) - { - UBlueprint* Blueprint = Cast(Asset); - if (IsValid(Blueprint)) - { - if (Blueprint->GeneratedClass && Blueprint->GeneratedClass != Actor->GetClass()) - { - // Close editors opened on existing asset if applicable - if (Asset && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Asset, false) != nullptr) - { - AssetEditorSubsystem->CloseAllEditorsForAsset(Asset); - AssetsToReOpenEditors.Add(Asset); - } - - Blueprint->ParentClass = Actor->GetClass(); - - FBlueprintEditorUtils::RefreshAllNodes(Blueprint); - FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); - FKismetEditorUtilities::CompileBlueprint(Blueprint); - } - } - } - else if (Asset && Asset->IsPendingKill()) - { - // Rename to pending kill so that we can use the desired name - const FString AssetPendingKillName(BlueprintName + "_PENDING_KILL"); - // Asset->Rename(*MakeUniqueObjectNameIfNeeded(Package, UBlueprint::StaticClass(), AssetPendingKillName).ToString()); - RenameAsset(Asset, AssetPendingKillName, true); - Asset = nullptr; - } - - if (!Asset) - { - UBlueprintFactory* Factory = NewObject(); - Factory->ParentClass = Actor->GetClass(); - - FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); - - Asset = AssetToolsModule.Get().CreateAsset( - BlueprintName, PackageParams.GetPackagePath(), - UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); - } - - UBlueprint* Blueprint = Cast(Asset); - - if (!Blueprint || Blueprint->IsPendingKill()) - { - HOUDINI_LOG_WARNING( - TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."), - *(InBakeFolder.Path), *BlueprintName); - - continue; - } - - // Close editors opened on existing asset if applicable - if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr) - { - AssetEditorSubsystem->CloseAllEditorsForAsset(Blueprint); - AssetsToReOpenEditors.Add(Blueprint); - } - - // Record the blueprint as the previous bake blueprint - if (BakedOutputObject) - BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString(); - - OutBlueprints.Add(Blueprint); - - // Clear old Blueprint Node tree - { - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - - int32 NodeSize = SCS->GetAllNodes().Num(); - for (int32 n = NodeSize - 1; n >= 0; --n) - SCS->RemoveNode(SCS->GetAllNodes()[n]); - } - - FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint); - - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - World->EditorDestroyActor(Actor, true); - - // Save the created BP package. - Package->MarkPackageDirty(); - OutPackagesToSave.Add(Package); - } - - // Re-open asset editors for updated blueprints that were open in editors - if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0) - { - for (UObject* Asset : AssetsToReOpenEditors) - { - if (IsValid(Asset)) - { - AssetEditorSubsystem->OpenEditorForAsset(Asset); - } - } - } - - return true; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInIsAutoBake, - const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, - bool bInRecenterBakedActors, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - TArray BPActors; - - if (!IsValid(InPDGAssetLink)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InPDGAssetLink is null")); - return false; - } - - if (!IsValid(InNode)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InNode is null")); - return false; - } - - const bool bReplaceAssets = InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - // Bake PDG output to new actors - // bInBakeForBlueprint == true will skip landscapes and instanced actor components - const bool bInBakeForBlueprint = true; - TArray BakedActors; - bool bSuccess = BakePDGTOPNodeOutputsKeepActors( - InPDGAssetLink, - InNode, - bInBakeForBlueprint, - bInIsAutoBake, - InPDGBakePackageReplaceMode, - BakedActors, - OutPackagesToSave, - OutBakeStats - ); - - if (bSuccess) - { - bSuccess = BakeBlueprintsFromBakedActors( - BakedActors, - bInRecenterBakedActors, - bReplaceAssets, - InPDGAssetLink->AssetName, - InPDGAssetLink->BakeFolder, - nullptr, - &InNode->GetBakedWorkResultObjectsOutputs(), - OutBlueprints, - OutPackagesToSave); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) -{ - TArray Blueprints; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - const bool bSuccess = BakePDGTOPNodeBlueprints( - InPDGAssetLink, - InTOPNode, - bInIsAutoBake, - InPDGBakePackageReplaceMode, - bInRecenterBakedActors, - Blueprints, - PackagesToSave, - BakeStats); - - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, - bool bInRecenterBakedActors, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - if (!IsValid(InNetwork)) - return false; - - const bool bIsAutoBake = false; - bool bSuccess = true; - for (UTOPNode* Node : InNetwork->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, InPDGBakePackageReplaceMode, bInRecenterBakedActors, OutBlueprints, OutPackagesToSave, OutBakeStats); - } - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) -{ - TArray Blueprints; - TArray PackagesToSave; - FHoudiniEngineOutputStats BakeStats; - - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return false; - - const bool bIsAutoBake = false; - bool bSuccess = true; - switch(InBakeSelectionOption) - { - case EPDGBakeSelectionOption::All: - for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) - { - if (!IsValid(Network)) - continue; - - for (UTOPNode* Node : Network->AllTOPNodes) - { - if (!IsValid(Node)) - continue; - - bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, InPDGBakePackageReplaceMode, bInRecenterBakedActors, Blueprints, PackagesToSave, BakeStats); - } - } - break; - case EPDGBakeSelectionOption::SelectedNetwork: - bSuccess &= BakePDGTOPNetworkBlueprints( - InPDGAssetLink, - InPDGAssetLink->GetSelectedTOPNetwork(), - InPDGBakePackageReplaceMode, - bInRecenterBakedActors, - Blueprints, - PackagesToSave, - BakeStats); - case EPDGBakeSelectionOption::SelectedNode: - bSuccess &= BakePDGTOPNodeBlueprints( - InPDGAssetLink, - InPDGAssetLink->GetSelectedTOPNode(), - bIsAutoBake, - InPDGBakePackageReplaceMode, - bInRecenterBakedActors, - Blueprints, - PackagesToSave, - BakeStats); - } - - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - // Sync the CB to the baked objects - if(GEditor && Blueprints.Num() > 0) - { - TArray Assets; - Assets.Reserve(Blueprints.Num()); - for (UBlueprint* Blueprint : Blueprints) - { - Assets.Add(Blueprint); - } - GEditor->SyncBrowserToObjects(Assets); - } - - { - const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); - FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); - FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); - } - - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - // Broadcast that the bake is complete - InPDGAssetLink->HandleOnPostBake(bSuccess); - - return bSuccess; -} - -bool -FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( - const FString& InLevelPath, - ULevel*& OutDesiredLevel, - UWorld*& OutDesiredWorld, - bool& OutCreatedPackage) -{ - OutDesiredLevel = nullptr; - OutDesiredWorld = nullptr; - if (InLevelPath.IsEmpty()) - { - OutDesiredWorld = GWorld; - OutDesiredLevel = GWorld->GetCurrentLevel(); - } - else - { - OutCreatedPackage = false; - - UWorld* FoundWorld = nullptr; - ULevel* FoundLevel = nullptr; - bool bActorInWorld = false; - if (FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - GWorld, - InLevelPath, - true, - FoundWorld, - FoundLevel, - OutCreatedPackage, - bActorInWorld)) - { - OutDesiredLevel = FoundLevel; - OutDesiredWorld = FoundWorld; - } - } - - return ((OutDesiredWorld != nullptr) && (OutDesiredLevel != nullptr)); -} - - -bool -FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( - const FString& InBakeActorName, - ULevel* InLevel, - AActor*& OutActor, - bool bInNoPendingKillActors, - bool bRenamePendingKillActor) -{ - OutActor = nullptr; - - if (!IsValid(InLevel)) - return false; - - UWorld* const World = InLevel->GetWorld(); - if (!IsValid(World)) - return false; - - // Look for an actor with the given name in the world - const FName BakeActorFName(InBakeActorName); - AActor* FoundActor = Cast(StaticFindObjectFast(AActor::StaticClass(), InLevel, BakeActorFName)); - // for (TActorIterator Iter(World, AActor::StaticClass(), EActorIteratorFlags::AllActors); Iter; ++Iter) - // { - // AActor* const Actor = *Iter; - // if (Actor->GetFName() == BakeActorFName && Actor->GetLevel() == InLevel) - // { - // // Found the actor - // FoundActor = Actor; - // break; - // } - // } - - // If we found an actor and it is pending kill, rename it and don't use it - if (FoundActor) - { - if (FoundActor->IsPendingKill()) - { - if (bRenamePendingKillActor) - { - // FoundActor->Rename( - // *MakeUniqueObjectNameIfNeeded( - // FoundActor->GetOuter(), - // FoundActor->GetClass(), - // FName(FoundActor->GetName() + "_Pending_Kill")).ToString()); - RenameAndRelabelActor( - FoundActor, - *MakeUniqueObjectNameIfNeeded( - FoundActor->GetOuter(), - FoundActor->GetClass(), - FoundActor->GetName() + "_Pending_Kill", - FoundActor), - false); - } - if (bInNoPendingKillActors) - FoundActor = nullptr; - else - OutActor = FoundActor; - } - else - { - OutActor = FoundActor; - } - } - - return true; -} - -bool FHoudiniEngineBakeUtils::FindUnrealBakeActor( - const FHoudiniOutputObject& InOutputObject, - const FHoudiniBakedOutputObject& InBakedOutputObject, - const TArray& InAllBakedActors, - ULevel* InLevel, - FName InDefaultActorName, - bool bInReplaceActorBakeMode, - AActor* InFallbackActor, - AActor*& OutFoundActor, - bool& bOutHasBakeActorName, - FName& OutBakeActorName) -{ - // Determine desired actor name via unreal_output_actor, fallback to InDefaultActorName - OutBakeActorName = NAME_None; - OutFoundActor = nullptr; - bOutHasBakeActorName = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_ACTOR); - if (bOutHasBakeActorName) - { - const FString& BakeActorNameStr = InOutputObject.CachedAttributes[HAPI_UNREAL_ATTRIB_BAKE_ACTOR]; - if (BakeActorNameStr.IsEmpty()) - { - OutBakeActorName = NAME_None; - bOutHasBakeActorName = false; - } - else - { - OutBakeActorName = FName(BakeActorNameStr, NAME_NO_NUMBER_INTERNAL); - // We have a bake actor name, look for the actor - AActor* BakeNameActor = nullptr; - if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor)) - { - // Found an actor with that name, check that we "own" it (we created in during baking previously) - AActor* IncrementedBakedActor = nullptr; - for (const FHoudiniEngineBakedActor& BakedActor : InAllBakedActors) - { - if (!IsValid(BakedActor.Actor)) - continue; - if (BakedActor.Actor == BakeNameActor) - { - OutFoundActor = BakeNameActor; - break; - } - else if (!IncrementedBakedActor && BakedActor.ActorBakeName == OutBakeActorName) - { - // Found an actor we have baked named OutBakeActorName_# (incremental version of our desired name) - IncrementedBakedActor = BakedActor.Actor; - } - } - if (!OutFoundActor && IncrementedBakedActor) - OutFoundActor = IncrementedBakedActor; - } - } - } - - // If unreal_actor_name is not set, or is blank, fallback to InDefaultActorName - if (!bOutHasBakeActorName || (OutBakeActorName.IsNone() || OutBakeActorName.ToString().TrimStartAndEnd().IsEmpty())) - OutBakeActorName = InDefaultActorName; - - if (!OutFoundActor) - { - // If in replace mode, use previous bake actor if valid and in InLevel - if (bInReplaceActorBakeMode) - { - const FSoftObjectPath PrevActorPath(InBakedOutputObject.Actor); - const FString ActorPath = PrevActorPath.IsSubobject() - ? PrevActorPath.GetAssetPathString() + ":" + PrevActorPath.GetSubPathString() - : PrevActorPath.GetAssetPathString(); - const FString LevelPath = IsValid(InLevel) ? InLevel->GetPathName() : ""; - if (PrevActorPath.IsValid() && (LevelPath.IsEmpty() || ActorPath.StartsWith(LevelPath))) - OutFoundActor = InBakedOutputObject.GetActorIfValid(); - } - - // Fallback to InFallbackActor if valid and in InLevel - if (!OutFoundActor && IsValid(InFallbackActor) && (!InLevel || InFallbackActor->GetLevel() == InLevel)) - OutFoundActor = InFallbackActor; - } - - return true; -} - -AActor* -FHoudiniEngineBakeUtils::FindExistingActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage) -{ - bCreatedPackage = false; - - // Try to Locate a previous actor - AActor* FoundActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); - if (FoundActor) - FoundActor->Destroy(); // nuke it! - - if (FoundActor) - { - // TODO: make sure that the found is actor is actually assigned to the level defined by package path. - // If the found actor is not from that level, it should be moved there. - - OutWorld = FoundActor->GetWorld(); - OutLevel = FoundActor->GetLevel(); - } - else - { - // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. - bool bActorInWorld = false; - const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( - InWorld, - InPackagePath, - true, - OutWorld, - OutLevel, - bCreatedPackage, - bActorInWorld); - - if (!bResult) - { - return nullptr; - } - - if (!bActorInWorld) - { - // The OutLevel is not present in the current world which means we might - // still find the tile actor in OutWorld. - FoundActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); - } - } - - return FoundActor; -} - -bool -FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh( - UHoudiniAssetComponent* InHoudiniAssetComponent, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess, - bool bInRecenterBakedActors, - bool& bOutNeedsReCook) -{ - if (!IsValid(InHoudiniAssetComponent)) - { - return false; - } - - // Handle proxies: if the output has any current proxies, first refine them - bOutNeedsReCook = false; - if (InHoudiniAssetComponent->HasAnyCurrentProxyOutput()) - { - bool bNeedsRebuildOrDelete; - bool bInvalidState; - const bool bCookedDataAvailable = InHoudiniAssetComponent->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bInvalidState); - - if (bCookedDataAvailable) - { - // Cook data is available, refine the mesh - AHoudiniAssetActor* HoudiniActor = Cast(InHoudiniAssetComponent->GetOwner()); - if (IsValid(HoudiniActor)) - { - FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ HoudiniActor }); - } - } - else if (!bNeedsRebuildOrDelete && !bInvalidState) - { - // A cook is needed: request the cook, but with no proxy and with a bake after cook - InHoudiniAssetComponent->SetNoProxyMeshNextCookRequested(true); - // Only - if (!InHoudiniAssetComponent->IsBakeAfterNextCookEnabled() || !InHoudiniAssetComponent->GetOnPostCookBakeDelegate().IsBound()) - { - InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors](UHoudiniAssetComponent* InHAC) { - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors); - }); - } - InHoudiniAssetComponent->MarkAsNeedCook(); - - bOutNeedsReCook = true; - - // The cook has to complete first (asynchronously) before the bake can happen - // The SetBakeAfterNextCookEnabled flag will result in a bake after cook - return false; - } - else - { - // The HAC is in an unsupported state - const EHoudiniAssetState AssetState = InHoudiniAssetComponent->GetAssetState(); - HOUDINI_LOG_ERROR(TEXT("Could not refine (in order to bake) %s, the asset is in an unsupported state: %s"), *(InHoudiniAssetComponent->GetPathName()), *(UEnum::GetValueAsString(AssetState))); - return false; - } - } - - return true; -} - -void -FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor) -{ - if (!IsValid(InActor)) - return; - - USceneComponent * const RootComponent = InActor->GetRootComponent(); - if (!IsValid(RootComponent)) - return; - - // If the root component does not have any child components, then there is nothing to recenter - if (RootComponent->GetNumChildrenComponents() <= 0) - return; - - const bool bOnlyCollidingComponents = false; - const bool bIncludeFromChildActors = true; - FVector Origin; - FVector BoxExtent; - // InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); - FBox Box(ForceInit); - - InActor->ForEachComponent(bIncludeFromChildActors, [&](const UPrimitiveComponent* InPrimComp) - { - // Only use non-editor-only components for the bounds calculation (to exclude things like editor only sprite/billboard components) - if (InPrimComp->IsRegistered() && !InPrimComp->IsEditorOnly() && - (!bOnlyCollidingComponents || InPrimComp->IsCollisionEnabled())) - { - Box += InPrimComp->Bounds.GetBox(); - } - }); - Box.GetCenterAndExtents(Origin, BoxExtent); - - const FVector Delta = Origin - RootComponent->GetComponentLocation(); - // Actor->SetActorLocation(Origin); - RootComponent->SetWorldLocation(Origin); - - for (USceneComponent* SceneComponent : RootComponent->GetAttachChildren()) - { - if (!IsValid(SceneComponent)) - continue; - - SceneComponent->SetWorldLocation(SceneComponent->GetComponentLocation() - Delta); - } -} - -void -FHoudiniEngineBakeUtils::CenterActorsToBoundingBoxCenter(const TArray& InActors) -{ - for (AActor* Actor : InActors) - { - if (!IsValid(Actor)) - continue; - - CenterActorToBoundingBoxCenter(Actor); - } -} - -USceneComponent* -FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMissing, EComponentMobility::Type InMobilityIfCreated) -{ - USceneComponent* RootComponent = InActor->GetRootComponent(); - if (!IsValid(RootComponent)) - { - RootComponent = NewObject(InActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); - - // Change the creation method so the component is listed in the details panels - InActor->SetRootComponent(RootComponent); - InActor->AddInstanceComponent(RootComponent); - RootComponent->RegisterComponent(); - RootComponent->SetMobility(InMobilityIfCreated); - } - - return RootComponent; -} - -FString -FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed) -{ - if (IsValid(InObjectThatWouldBeRenamed)) - { - const FName CurrentName = InObjectThatWouldBeRenamed->GetFName(); - if (CurrentName.ToString() == InName) - return InName; - - // Check if the prefix matches (without counter suffix) the new name - // In other words, if InName is 'my_actor' and the object is already an increment of it, 'my_actor_5' then - // don't we can just keep the current name - if (CurrentName.GetPlainNameString() == InName) - return CurrentName.ToString(); - } - - UObject* ExistingObject = nullptr; - FName CandidateName(InName); - bool bAppendedNumber = false; - // Do our own loop for generate suffixes as sequentially as possible. If this turns out to be expensive we can - // revert to MakeUniqueObjectName. - // return MakeUniqueObjectName(InOuter, InClass, CandidateName).ToString(); - do - { - if (InOuter == ANY_PACKAGE) - { - ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *(CandidateName.ToString())); - } - else - { - ExistingObject = StaticFindObjectFast(nullptr, InOuter, CandidateName); - } - - if (ExistingObject) - { - if (!bAppendedNumber) - { - const bool bSplitName = false; - CandidateName = FName(*InName, NAME_EXTERNAL_TO_INTERNAL(1), FNAME_Add, bSplitName); - bAppendedNumber = true; - } - else - { - CandidateName.SetNumber(CandidateName.GetNumber() + 1); - } - // CandidateName = FString::Printf(TEXT("%s_%d"), *InName, ++Counter); - } - } while (ExistingObject); - - return CandidateName.ToString(); -} - -FName -FHoudiniEngineBakeUtils::GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) -{ - const FString* FolderPathPtr = InOutputObject.CachedAttributes.Find(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER); - if (FolderPathPtr && !FolderPathPtr->IsEmpty()) - return FName(*FolderPathPtr); - else - return InDefaultFolder; -} - -bool -FHoudiniEngineBakeUtils::SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) -{ - if (!IsValid(InActor)) - return false; - - InActor->SetFolderPath(GetOutlinerFolderPath(InOutputObject, InDefaultFolder)); - return true; -} - -uint32 -FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInDestroyBakedComponent, - bool bInDestroyBakedInstancedActors, - bool bInDestroyBakedInstancedComponents) -{ - uint32 NumDeleted = 0; - - if (bInDestroyBakedComponent) - { - UActorComponent* Component = Cast(InBakedOutputObject.GetBakedComponentIfValid()); - if (Component) - { - if (RemovePreviouslyBakedComponent(Component)) - { - InBakedOutputObject.BakedComponent = nullptr; - NumDeleted++; - } - } - } - - if (bInDestroyBakedInstancedActors) - { - for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) - { - const FSoftObjectPath ActorPath(ActorPathStr); - - if (!ActorPath.IsValid()) - continue; - - AActor* Actor = Cast(ActorPath.TryLoad()); - if (IsValid(Actor)) - { - UWorld* World = Actor->GetWorld(); - if (IsValid(World)) - { -#if WITH_EDITOR - World->EditorDestroyActor(Actor, true); -#else - World->DestroyActor(Actor); -#endif - NumDeleted++; - } - } - } - InBakedOutputObject.InstancedActors.Empty(); - } - - if (bInDestroyBakedInstancedComponents) - { - for (const FString& ComponentPathStr : InBakedOutputObject.InstancedComponents) - { - const FSoftObjectPath ComponentPath(ComponentPathStr); - - if (!ComponentPath.IsValid()) - continue; - - UActorComponent* Component = Cast(ComponentPath.TryLoad()); - if (IsValid(Component)) - { - if (RemovePreviouslyBakedComponent(Component)) - NumDeleted++; - } - } - InBakedOutputObject.InstancedComponents.Empty(); - } - - return NumDeleted; -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineBakeUtils.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngineUtils.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutput.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineUtils.h" +#include "UnrealLandscapeTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniStringResolver.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniEngineRuntimeUtils.h" + +#include "Engine/StaticMesh.h" +#include "Engine/World.h" +#include "RawMesh.h" +#include "UObject/Package.h" +#include "PackageTools.h" +#include "UObject/MetaData.h" +#include "AssetRegistryModule.h" +#include "Materials/Material.h" +#include "LandscapeProxy.h" +#include "LandscapeStreamingProxy.h" +#include "LandscapeInfo.h" +#include "Factories/WorldFactory.h" +#include "AssetToolsModule.h" +#include "InstancedFoliageActor.h" +#include "Components/SplineComponent.h" +#include "GameFramework/Actor.h" +#include "Engine/StaticMeshActor.h" +#include "Components/StaticMeshComponent.h" +#include "PhysicsEngine/BodySetup.h" +#include "ActorFactories/ActorFactoryStaticMesh.h" +#include "ActorFactories/ActorFactoryEmptyActor.h" +#include "BusyCursor.h" +#include "Editor.h" +#include "Kismet2/KismetEditorUtilities.h" +#include "FileHelpers.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngine.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniOutputTranslator.h" +#include "Editor/EditorEngine.h" +#include "Factories/BlueprintFactory.h" +#include "Engine/SimpleConstructionScript.h" +#include "Misc/Paths.h" +#include "HAL/FileManager.h" +#include "LandscapeEdit.h" +#include "Containers/UnrealString.h" +#include "Components/AudioComponent.h" +#include "Engine/WorldComposition.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "MaterialEditor/Public/MaterialEditingLibrary.h" +#include "MaterialGraph/MaterialGraph.h" +#include "Materials/MaterialInstance.h" +#include "Particles/ParticleSystemComponent.h" +#include "Sound/SoundBase.h" +#include "UObject/UnrealType.h" +#include "Math/Box.h" +#include "Misc/ScopedSlowTask.h" + +HOUDINI_BAKING_DEFINE_LOG_CATEGORY(); + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FHoudiniEngineBakedActor::FHoudiniEngineBakedActor() + : Actor(nullptr) + , OutputIndex(INDEX_NONE) + , OutputObjectIdentifier() + , ActorBakeName(NAME_None) + , BakedObject(nullptr) + , SourceObject(nullptr) + , BakeFolderPath() + , bInstancerOutput(false) + , bPostBakeProcessPostponed(false) +{ +} + +FHoudiniEngineBakedActor::FHoudiniEngineBakedActor( + AActor* InActor, + FName InActorBakeName, + FName InWorldOutlinerFolder, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + UObject* InBakedObject, + UObject* InSourceObject, + UObject* InBakedComponent, + const FString& InBakeFolderPath, + const FHoudiniPackageParams& InBakedObjectPackageParams) + : Actor(InActor) + , OutputIndex(InOutputIndex) + , OutputObjectIdentifier(InOutputObjectIdentifier) + , ActorBakeName(InActorBakeName) + , WorldOutlinerFolder(InWorldOutlinerFolder) + , BakedObject(InBakedObject) + , SourceObject(InSourceObject) + , BakedComponent(InBakedComponent) + , BakeFolderPath(InBakeFolderPath) + , BakedObjectPackageParams(InBakedObjectPackageParams) + , bInstancerOutput(false) + , bPostBakeProcessPostponed(false) +{ +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + UHoudiniAssetComponent* InHACToBake, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors) +{ + if (!IsValid(InHACToBake)) + return false; + + // Handle proxies: if the output has any current proxies, first refine them + bool bHACNeedsToReCook; + if (!CheckForAndRefineHoudiniProxyMesh(InHACToBake, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors, bHACNeedsToReCook)) + { + // Either the component is invalid, or needs a recook to refine a proxy mesh + return false; + } + + bool bSuccess = false; + switch (InBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(InHACToBake, bInReplacePreviousBake, bInReplacePreviousBake, bInRecenterBakedActors); + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(InHACToBake, bInReplacePreviousBake, bInRecenterBakedActors); + } + break; + + case EHoudiniEngineBakeOption::ToFoliage: + { + TMap AlreadyBakedMaterialsMap; + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake, AlreadyBakedMaterialsMap); + } + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + { + //Todo + bSuccess = false; + } + break; + + } + + if (bSuccess && bInRemoveHACOutputOnSuccess) + { + TArray DeferredClearOutputs; + FHoudiniOutputTranslator::ClearAndRemoveOutputs(InHACToBake, DeferredClearOutputs, true); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActors) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + TArray NewActors; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + const bool bBakedWithErrors = !FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + HoudiniAssetComponent, bInReplaceActors, bInReplaceAssets, NewActors, PackagesToSave, BakeStats); + if (bBakedWithErrors) + { + // TODO ? + HOUDINI_LOG_WARNING(TEXT("Errors when baking")); + } + + // Save the created packages + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && NewActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : NewActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && NewActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + // Broadcast that the bake is complete + HoudiniAssetComponent->HandleOnPostBake(!bBakedWithErrors); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); + if (!IsValid(OwnerActor)) + return false; + + const FString HoudiniAssetName = OwnerActor->GetName(); + + // Get an array of the outputs + const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); + TArray Outputs; + Outputs.Reserve(NumOutputs); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + Outputs.Add(HoudiniAssetComponent->GetOutputAt(OutputIdx)); + } + + // Get the previous bake objects and grow/shrink to match asset outputs + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + // Ensure we have an entry for each output + if (BakedOutputs.Num() != NumOutputs) + BakedOutputs.SetNum(NumOutputs); + + return BakeHoudiniOutputsToActors( + HoudiniAssetComponent, + Outputs, + BakedOutputs, + HoudiniAssetName, + HoudiniAssetComponent->GetComponentTransform(), + HoudiniAssetComponent->BakeFolder, + HoudiniAssetComponent->TemporaryCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutNewActors, + OutPackagesToSave, + OutBakeStats, + InOutputTypesToBake, + InInstancerComponentTypesToBake, + InFallbackActor, + InFallbackWorldOutlinerFolder); +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + const TArray& InOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FTransform& InParentTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + const int32 NumOutputs = InOutputs.Num(); + + const FString MsgTemplate = TEXT("Baking output: {0}/{1}."); + FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs }); + FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg)); + + TArray BakedActors; + + // Ensure that InBakedOutputs is the same size as InOutputs + if (InBakedOutputs.Num() != NumOutputs) + InBakedOutputs.SetNum(NumOutputs); + + // First bake everything except instancers, then bake instancers. Since instancers might use meshes in + // from the other outputs. + bool bHasAnyInstancers = false; + int32 NumProcessedOutputs = 0; + + TMap AlreadyBakedMaterialsMap; + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + UHoudiniOutput* Output = InOutputs[OutputIdx]; + if (!Output || Output->IsPendingKill()) + { + NumProcessedOutputs++; + continue; + } + + Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); + + const EHoudiniOutputType OutputType = Output->GetType(); + // Check if we should skip this output type + if (InOutputTypesToBake && InOutputTypesToBake->Find(OutputType) == INDEX_NONE) + { + NumProcessedOutputs++; + continue; + } + + switch (OutputType) + { + case EHoudiniOutputType::Mesh: + { + FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + HoudiniAssetComponent, + OutputIdx, + InOutputs, + InBakedOutputs, + InHoudiniAssetName, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + OutPackagesToSave, + AlreadyBakedMaterialsMap, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + break; + + case EHoudiniOutputType::Instancer: + { + if (!bHasAnyInstancers) + bHasAnyInstancers = true; + NumProcessedOutputs--; + } + break; + + case EHoudiniOutputType::Landscape: + { + const bool bResult = BakeLandscape( + HoudiniAssetComponent, + OutputIdx, + InOutputs, + InBakedOutputs, + bInReplaceActors, + bInReplaceAssets, + InBakeFolder.Path, + InHoudiniAssetName, + OutBakeStats); + } + break; + + case EHoudiniOutputType::Skeletal: + break; + + case EHoudiniOutputType::Curve: + { + FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( + HoudiniAssetComponent, + OutputIdx, + InOutputs, + InBakedOutputs, + InHoudiniAssetName, + InBakeFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + break; + + case EHoudiniOutputType::Invalid: + break; + } + + NumProcessedOutputs++; + } + + if (bHasAnyInstancers) + { + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + UHoudiniOutput* Output = InOutputs[OutputIdx]; + if (!Output || Output->IsPendingKill()) + { + NumProcessedOutputs++; + continue; + } + + Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); + + if (Output->GetType() == EHoudiniOutputType::Instancer) + { + FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + HoudiniAssetComponent, + OutputIdx, + InOutputs, + InBakedOutputs, + InParentTransform, + InHoudiniAssetName, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedActors, + OutPackagesToSave, + AlreadyBakedMaterialsMap, + InInstancerComponentTypesToBake, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + + NumProcessedOutputs++; + } + } + + // Only do the post bake post-process once per Actor + TSet UniqueActors; + for (FHoudiniEngineBakedActor& BakedActor : BakedActors) + { + if (BakedActor.bPostBakeProcessPostponed && BakedActor.Actor) + { + BakedActor.bPostBakeProcessPostponed = false; + AActor* Actor = BakedActor.Actor; + bool bIsAlreadyInSet = false; + UniqueActors.Add(Actor, &bIsAlreadyInSet); + if (!bIsAlreadyInSet) + { + Actor->InvalidateLightingCache(); + Actor->PostEditMove(true); + Actor->MarkPackageDirty(); + } + } + } + + OutNewActors.Append(BakedActors); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap) +{ + UHoudiniOutput* Output = InAllOutputs[InOutputIndex]; + if (!Output || Output->IsPendingKill()) + return false; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + return false; + + if (!IsValid(InOutputObject.OutputComponent)) + return false; + + UStaticMeshComponent* SMC = Cast(InOutputObject.OutputComponent); + if (!IsValid(SMC)) + { + HOUDINI_LOG_WARNING( + TEXT("Unsupported component for foliage: %s"),*(InOutputObject.OutputComponent->GetClass()->GetName())); + return false; + } + + UStaticMesh* InstancedStaticMesh = SMC->GetStaticMesh(); + if (!IsValid(InstancedStaticMesh)) + { + // No mesh, skip this instancer + return false; + } + + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets + ? EPackageReplaceMode::ReplaceExistingAssets + : EPackageReplaceMode::CreateNewAssets; + UWorld* DesiredWorld = Output ? Output->GetWorld() : GWorld; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InstancedStaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + const bool bFoundMeshOutput = FindOutputObject(InstancedStaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + InHoudiniAssetName, MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + } + else + { + BakedStaticMesh = InstancedStaticMesh; + } + + // Update the baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // const FString InstancerName = FString::Printf(TEXT("%s_foliage_%s"), *ObjectName, *(InOutputObjectIdentifier.SplitIdentifier)); + // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone + // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the + // package params. + FHoudiniPackageParams InstancerPackageParams; + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + InHoudiniAssetName, InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + HOUDINI_LOG_ERROR(TEXT("Could not find or create a level: %s"), *LevelPackagePath); + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Get foliage actor for the level + const bool bCreateIfNone = true; + AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, bCreateIfNone); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Could not find or create an instanced foliage actor for level %s"), *(DesiredLevel->GetPathName())); + return false; + } + + // Get the previous bake data for this instancer + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // Foliage type is replaced in replacement mode if: + // the previous baked object is this foliage type + // and we haven't bake this foliage type during this bake (BakeResults) + // NOTE: foliage type is only recorded as the previous bake object if we created the foliage type + // TODO: replacement mode should probably only affect the instances themselves and not the foliage type + // since the foliage type is already linked to whatever mesh we are using (which will be replaced + // incremented already). To track instances it looks like we would have to use the locations of the + // baked instances (likely cannot use the indices, since the user might modify/add/remove instances + // after the bake). + + // See if we already have a FoliageType for that static mesh + UFoliageType* FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(BakedStaticMesh); + if (!FoliageType || FoliageType->IsPendingKill()) + { + // We need to create a new FoliageType for this Static Mesh + // TODO: Add foliage default settings + InstancedFoliageActor->AddMesh(BakedStaticMesh, &FoliageType); + // Update the previous bake results with the foliage type we created + InBakedOutputObject.BakedComponent = FSoftObjectPath(FoliageType).ToString(); + } + else + { + const FString FoliageTypePath = FSoftObjectPath(FoliageType).ToString(); + if (bInReplaceAssets && InBakedOutputObject.BakedComponent == FoliageTypePath && + !OutActors.FindByPredicate([FoliageType](const FHoudiniEngineBakedActor& Entry) { return Entry.BakedComponent == FoliageType; })) + { + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + // Update the previous bake results with the foliage type + InBakedOutputObject.BakedComponent = FoliageTypePath; + } + else + { + // If we didn't create the foliage type, don't set the baked component + InBakedOutputObject.BakedComponent.Empty(); + } + } + + // Record the foliage bake in the current results + FHoudiniEngineBakedActor& NewResult = OutActors.Add_GetRef(FHoudiniEngineBakedActor()); + NewResult.OutputIndex = InOutputIndex; + NewResult.OutputObjectIdentifier = InOutputObjectIdentifier; + NewResult.SourceObject = InstancedStaticMesh; + NewResult.BakedObject = BakedStaticMesh; + NewResult.BakedComponent = FoliageType; + + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it + FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); + if (!FoliageInfo) + return false; + + int32 CurrentInstanceCount = 0; + if (SMC->IsA()) + { + UInstancedStaticMeshComponent* ISMC = Cast(SMC); + const int32 NumInstances = ISMC->GetInstanceCount(); + for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex) + { + FTransform InstanceTransform; + const bool bWorldSpace = true; + if (ISMC->GetInstanceTransform(InstanceIndex, InstanceTransform, bWorldSpace)) + { + FFoliageInstance FoliageInstance; + FoliageInstance.Location = InstanceTransform.GetLocation(); + FoliageInstance.Rotation = InstanceTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = InstanceTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + + CurrentInstanceCount++; + } + } + } + else + { + const FTransform ComponentToWorldTransform = SMC->GetComponentToWorld(); + FFoliageInstance FoliageInstance; + FoliageInstance.Location = ComponentToWorldTransform.GetLocation(); + FoliageInstance.Rotation = ComponentToWorldTransform.GetRotation().Rotator(); + FoliageInstance.DrawScale3D = ComponentToWorldTransform.GetScale3D(); + + FoliageInfo->AddInstance(InstancedFoliageActor, FoliageType, FoliageInstance); + + CurrentInstanceCount++; + } + + // TODO: This was due to a bug in UE4.22-20, check if still needed! + if (FoliageInfo->GetComponent()) + FoliageInfo->GetComponent()->BuildTreeIfOutdated(true, true); + + // Notify the user that we succesfully bake the instances to foliage + FString Notification = TEXT("Successfully baked ") + FString::FromInt(CurrentInstanceCount) + TEXT(" instances of ") + BakedStaticMesh->GetName() + TEXT(" to Foliage"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + InstancedFoliageActor->RegisterAllComponents(); + + // Update / repopulate the foliage editor mode's mesh list + if (CurrentInstanceCount > 0) + FHoudiniEngineUtils::RepopulateFoliageTypeListInUI(); + + return true; +} + +bool +FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n) + { + UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n); + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + continue; + + if (Output->GetInstancedOutputs().Num() > 0) + return true; + /* + // TODO: Is this needed? check we have components to bake? + for (auto& OutputObjectPair : Output->GetOutputObjects()) + { + if (OutputObjectPair.Value.OutputCompoent!= nullpt) + return true; + } + */ + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, TMap& InOutAlreadyBakedMaterialsMap) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor * OwnerActor = HoudiniAssetComponent->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + TArray PackagesToSave; + TArray BakedResults; + + FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); + const FString HoudiniAssetName = OwnerActor->GetName(); + + // Build an array of the outputs so that we can search for meshes/previous baked meshes + TArray Outputs; + HoudiniAssetComponent->GetOutputs(Outputs); + const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); + + // Get the previous bake outputs and match the output array size + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + if (BakedOutputs.Num() != NumOutputs) + BakedOutputs.SetNum(NumOutputs); + + bool bSuccess = true; + // Map storing original and baked Static Meshes + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) + { + UHoudiniOutput* Output = Outputs[OutputIdx]; + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Instancer) + continue; + + TMap& OutputObjects = Output->GetOutputObjects(); + const TMap& OldBakedOutputObjects = BakedOutputs[OutputIdx].BakedOutputObjects; + TMap NewBakedOutputObjects; + + for (auto & Pair : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniOutputObject& OutputObject = Pair.Value; + + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); + + const bool bInReplaceActors = false; + bSuccess &= BakeInstancerOutputToFoliage( + HoudiniAssetComponent, + OutputIdx, + Outputs, + Identifier, + OutputObject, + BakedOutputObject, + HoudiniAssetName, + HoudiniAssetComponent->BakeFolder, + HoudiniAssetComponent->TemporaryCookFolder, + bInReplaceActors, + bInReplaceAssets, + BakedResults, + PackagesToSave, + InOutAlreadyBakedMaterialsMap); + } + + // Update the cached baked output data + BakedOutputs[OutputIdx].BakedOutputObjects = NewBakedOutputObjects; + } + + if (PackagesToSave.Num() > 0) + { + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + } + + // Broadcast that the bake is complete + HoudiniAssetComponent->HandleOnPostBake(bSuccess); + + return bSuccess; +} + + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FTransform& InTransform, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + TArray const* InInstancerComponentTypesToBake, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; + if (!InOutput || InOutput->IsPendingKill()) + return false; + + // Ensure we have the same number of baked outputs and asset outputs + if (InBakedOutputs.Num() != InAllOutputs.Num()) + InBakedOutputs.SetNum(InAllOutputs.Num()); + + TMap& OutputObjects = InOutput->GetOutputObjects(); + const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + TMap NewBakedOutputObjects; + + // Iterate on the output objects, baking their object/component as we go + for (auto& Pair : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniOutputObject& CurrentOutputObject = Pair.Value; + + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); + + if (CurrentOutputObject.bProxyIsCurrent) + { + // TODO: we need to refine the SM first! + // ?? + } + + if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill()) + continue; + + if (CurrentOutputObject.OutputComponent->IsA()) + { + // Bake foliage as foliage + if (!InInstancerComponentTypesToBake || + InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageInstancedStaticMeshComponent)) + { + BakeInstancerOutputToFoliage( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InHoudiniAssetName, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap); + } + else if (!InInstancerComponentTypesToBake || + InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent)) + { + BakeInstancerOutputToActors_ISMC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedStaticMeshComponent))) + { + BakeInstancerOutputToActors_ISMC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::InstancedActorComponent))) + { + BakeInstancerOutputToActors_IAC( + HoudiniAssetComponent, + InOutputIndex, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) + { + BakeInstancerOutputToActors_MSIC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else if (CurrentOutputObject.OutputComponent->IsA() + && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) + { + BakeInstancerOutputToActors_SMC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + OutActors, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + InFallbackActor, + InFallbackWorldOutlinerFolder); + } + else + { + // Unsupported component! + } + + } + + // Update the cached baked output data + InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UInstancedStaticMeshComponent * InISMC = Cast(InOutputObject.OutputComponent); + if (!InISMC || InISMC->IsPendingKill()) + return false; + + AActor * OwnerActor = InISMC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh * StaticMesh = InISMC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Certain SMC materials may need to be duplicated if we didn't generate the mesh object. + TArray DuplicatedISMCOverrideMaterials; + + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + + // Construct PackageParams for the instancer itself. When baking to actor we technically won't create a stand-alone + // disk package for the instancer, but certain attributes (such as level path) use tokens populated from the + // package params. + FHoudiniPackageParams InstancerPackageParams; + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + OwnerActor->GetName(), MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + } + else + { + BakedStaticMesh = StaticMesh; + + + // We still need to duplicate materials, if they are temporary. + TArray Materials = InISMC->GetMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + continue; + + // Only duplicate the material if it is temporary + if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path)) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + DuplicatedISMCOverrideMaterials.Add(DuplicatedMaterial); + } + } + } + + // Update the baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if(!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + /* + // TODO: Get the bake name! + // Bake override, the output name + // The bake name override has priority + FString InstancerName = InOutputObject.BakeName; + if (InstancerName.IsEmpty()) + { + // .. then use the output name + InstancerName = Resolver.ResolveOutputName(); + } + */ + + // Should we create one actor with an ISMC or multiple actors with one SMC? + bool bSpawnMultipleSMC = false; + if (bSpawnMultipleSMC) + { + // TODO: Double check, Has a crash here! + + // Get the StaticMesh ActorFactory + UActorFactory* SMFactory = nullptr; + + if (!FoundActor) + { + SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!SMFactory) + return false; + } + + // Split the instances to multiple StaticMeshActors + for (int32 InstanceIdx = 0; InstanceIdx < InISMC->GetInstanceCount(); InstanceIdx++) + { + FTransform InstanceTransform; + InISMC->GetInstanceTransform(InstanceIdx, InstanceTransform, true); + + if (!FoundActor) + { + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform); + if (!FoundActor || FoundActor->IsPendingKill()) + continue; + } + + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, SMFactory->NewActorClass, BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, NewNameStr, false); + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + continue; + + // Copy properties from the existing component + CopyPropertyToNewActorAndComponent(FoundActor, SMActor->GetStaticMeshComponent(), InISMC); + + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh, + SMActor->GetStaticMeshComponent(), + bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + } + } + else + { + bool bSpawnedActor = false; + if (!FoundActor) + { + // Only create one actor + FActorSpawnParameters SpawnInfo; + SpawnInfo.OverrideLevel = DesiredLevel; + SpawnInfo.ObjectFlags = RF_Transactional; + SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); + SpawnInfo.bDeferConstruction = true; + + // Spawn the new Actor + FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + bSpawnedActor = true; + + FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); + FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); + } + else + { + // If there is a previously baked component, and we are in replace mode, remove it + if (bInReplaceAssets) + { + USceneComponent* InPrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(InPrevComponent) && InPrevComponent->GetOwner() == FoundActor) + RemovePreviouslyBakedComponent(InPrevComponent); + } + + const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); + } + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Get/create the actor's root component + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + if (bSpawnedActor && IsValid(RootComponent)) + RootComponent->SetWorldTransform(InTransform); + + // Duplicate the instancer component, create a Hierarchical ISMC if needed + UInstancedStaticMeshComponent* NewISMC = nullptr; + UHierarchicalInstancedStaticMeshComponent* InHISMC = Cast(InISMC); + if (InHISMC) + { + // Handle foliage: don't duplicate foliage component, create a new hierarchical one and copy what we can + // from the foliage component + if (InHISMC->IsA()) + { + NewISMC = NewObject( + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); + CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); + } + else + { + NewISMC = DuplicateObject( + InHISMC, + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); + } + } + else + { + NewISMC = DuplicateObject( + InISMC, + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetName()))); + } + + if (!NewISMC) + { + //DesiredLevel->OwningWorld-> + return false; + } + + InBakedOutputObject.BakedComponent = FSoftObjectPath(NewISMC).ToString(); + + NewISMC->RegisterComponent(); + // NewISMC->SetupAttachment(nullptr); + NewISMC->SetStaticMesh(BakedStaticMesh); + FoundActor->AddInstanceComponent(NewISMC); + + if (DuplicatedISMCOverrideMaterials.Num() > 0) + { + UMaterialInterface * InstancerMaterial = DuplicatedISMCOverrideMaterials[0]; + if (InstancerMaterial) + { + NewISMC->OverrideMaterials.Empty(); + int32 MeshMaterialCount = BakedStaticMesh->GetStaticMaterials().Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + NewISMC->SetMaterial(Idx, InstancerMaterial); + } + } + + // NewActor->SetRootComponent(NewISMC); + if (IsValid(RootComponent)) + NewISMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + NewISMC->SetWorldTransform(InISMC->GetComponentTransform()); + + // TODO: do we need to copy properties here, we duplicated the component + // // Copy properties from the existing component + // CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); + + if (bSpawnedActor) + FoundActor->FinishSpawning(InTransform); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh, + NewISMC, + bFoundMeshOutput ? MeshPackageParams.BakeFolder : FString(), + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + + // Postpone post-bake calls to do them once per actor + OutActors.Last().bPostBakeProcessPostponed = true; + } + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UStaticMeshComponent* InSMC = Cast(InOutputObject.OutputComponent); + if (!InSMC || InSMC->IsPendingKill()) + return false; + + AActor* OwnerActor = InSMC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh* StaticMesh = InSMC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Certain SMC materials may need to be duplicated if we didn't generate the mesh object. + TArray DuplicatedSMCOverrideMaterials; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + + // Package params for the instancer + // See if the instanced static mesh is still a temporary Houdini created Static Mesh + // If it is, we need to bake the StaticMesh first + FHoudiniPackageParams InstancerPackageParams; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + OwnerActor->GetName(), MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + } + else + { + BakedStaticMesh = StaticMesh; + + // We still need to duplicate materials, if they are temporary. + TArray Materials = InSMC->GetMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + continue; + + // Only duplicate the material if it is temporary + if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path)) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + DuplicatedSMCOverrideMaterials.Add(DuplicatedMaterial); + } + } + } + + // Update the previous baked object + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // BaseName holds the Actor / HDA name + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + UStaticMeshComponent* StaticMeshComponent = nullptr; + // Create an actor if we didn't find one + if (!FoundActor) + { + // Get the StaticMesh ActorFactory + UActorFactory* SMFactory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!SMFactory) + return false; + + FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform()); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + return false; + + StaticMeshComponent = SMActor->GetStaticMeshComponent(); + } + else + { + USceneComponent* RootComponent = GetActorRootComponent(FoundActor); + if (!IsValid(RootComponent)) + return false; + + if (bInReplaceAssets) + { + // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it + UStaticMeshComponent* PrevSMC = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) + { + StaticMeshComponent = PrevSMC; + } + } + + if (!IsValid(StaticMeshComponent)) + { + // Create a new static mesh component + StaticMeshComponent = NewObject(FoundActor, NAME_None, RF_Transactional); + + FoundActor->AddInstanceComponent(StaticMeshComponent); + StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + StaticMeshComponent->RegisterComponent(); + } + } + + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, FoundActor->GetClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, NewNameStr, false); + + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Update the previous baked component + InBakedOutputObject.BakedComponent = FSoftObjectPath(StaticMeshComponent).ToString(); + + if (!IsValid(StaticMeshComponent)) + return false; + + // Copy properties from the existing component + const bool bCopyWorldTransform = true; + CopyPropertyToNewActorAndComponent(FoundActor, StaticMeshComponent, InSMC, bCopyWorldTransform); + StaticMeshComponent->SetStaticMesh(BakedStaticMesh); + + if (DuplicatedSMCOverrideMaterials.Num() > 0) + { + UMaterialInterface * InstancerMaterial = DuplicatedSMCOverrideMaterials[0]; + if (InstancerMaterial) + { + StaticMeshComponent->OverrideMaterials.Empty(); + int32 MeshMaterialCount = BakedStaticMesh->GetStaticMaterials().Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + StaticMeshComponent->SetMaterial(Idx, InstancerMaterial); + } + } + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh, + StaticMeshComponent, + MeshPackageParams.BakeFolder, + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave) +{ + UHoudiniInstancedActorComponent* InIAC = Cast(InOutputObject.OutputComponent); + if (!InIAC || InIAC->IsPendingKill()) + return false; + + AActor * OwnerActor = InIAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + // BaseName holds the Actor / HDA name + const FName BaseName = FName(OwnerActor->GetName()); + + // Get the object instanced by this IAC + UObject* InstancedObject = InIAC->GetInstancedObject(); + if (!InstancedObject || InstancedObject->IsPendingKill()) + return false; + + FHoudiniPackageParams PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, BaseName.ToString(), + OwnerActor->GetName(), PackageParams, Resolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // If we are baking in actor replacement mode, remove any previously baked instanced actors for this output + if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0) + { + UWorld* LevelWorld = DesiredLevel->GetWorld(); + if (IsValid(LevelWorld)) + { + for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) + { + const FSoftObjectPath ActorPath(ActorPathStr); + + if (!ActorPath.IsValid()) + continue; + + AActor* Actor = Cast(ActorPath.TryLoad()); + // Destroy Actor if it is valid and part of DesiredLevel + if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel) + { +#if WITH_EDITOR + LevelWorld->EditorDestroyActor(Actor, true); +#else + LevelWorld->DestroyActor(Actor); +#endif + } + } + } + } + + // Empty and reserve enough space for new instanced actors + InBakedOutputObject.InstancedActors.Empty(InIAC->GetInstancedActors().Num()); + + // Iterates on all the instances of the IAC + for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors()) + { + if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) + continue; + + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName.ToString()); + + FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); + AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); + if (!NewActor || NewActor->IsPendingKill()) + continue; + + const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) + (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); + + EditorUtilities::CopyActorProperties(CurrentInstancedActor, NewActor); + + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); + + FHoudiniEngineRuntimeUtils::SetActorLabel(NewActor, NewNameStr); + + SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); + NewActor->SetActorTransform(CurrentTransform); + + InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); + + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + NewActor, + BaseName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + nullptr, + InstancedObject, + nullptr, + PackageParams.BakeFolder, + PackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = PackageParams; + } + + // TODO: + // Move Actors to DesiredLevel if needed?? + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = true; + const bool bInDestroyBakedInstancedActors = false; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + UHoudiniMeshSplitInstancerComponent * InMSIC = Cast(InOutputObject.OutputComponent); + if (!InMSIC || InMSIC->IsPendingKill()) + return false; + + AActor * OwnerActor = InMSIC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return false; + + UStaticMesh * StaticMesh = InMSIC->GetStaticMesh(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return false; + + // Certain SMC materials may need to be duplicated if we didn't generate the mesh object. + TArray DuplicatedMSICOverrideMaterials; + + + UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Determine if the incoming mesh is temporary by looking for it in the mesh outputs. Populate mesh package params + // for baking from it. + // If not temporary set the ObjectName from the its package. (Also use this as a fallback default) + FString ObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + UStaticMesh* PreviousStaticMesh = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + UStaticMesh* BakedStaticMesh = nullptr; + int32 MeshOutputIndex = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + FHoudiniAttributeResolver MeshResolver; + FHoudiniPackageParams MeshPackageParams; + + // See if the instanced static mesh is still a temporary Houdini created Static Mesh + // If it is, we need to bake the StaticMesh first + FHoudiniPackageParams InstancerPackageParams; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver InstancerResolver; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + + const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); + if (bFoundMeshOutput) + { + // Found the mesh in the mesh outputs, is temporary + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, + OwnerActor->GetName(), MeshPackageParams, MeshResolver, + InBakeFolder.Path, AssetPackageReplaceMode); + // Update with resolved object name + ObjectName = MeshPackageParams.ObjectName; + + // This will bake/duplicate the mesh if temporary, or return the input one if it is not + BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + } + else + { + BakedStaticMesh = StaticMesh; + + + // We still need to duplicate materials, if they are temporary. + TArray Materials = InMSIC->GetOverrideMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx]; + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + continue; + + // Only duplicate the material if it is temporary + if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path)) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + DuplicatedMSICOverrideMaterials.Add(DuplicatedMaterial); + } + } + } + + // Update the baked output + InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); + + // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) + const FString BaseName = OwnerActor->GetName(); + const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + + + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = InstancerResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if (!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + bool bSpawnedActor = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + if (!FoundActor) + { + // This is a split mesh instancer component - we will create a generic AActor with a bunch of SMC + FActorSpawnParameters SpawnInfo; + SpawnInfo.OverrideLevel = DesiredLevel; + SpawnInfo.ObjectFlags = RF_Transactional; + SpawnInfo.Name = FName(MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString())); + SpawnInfo.bDeferConstruction = true; + + // Spawn the new Actor + FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); + if (!FoundActor || FoundActor->IsPendingKill()) + return false; + bSpawnedActor = true; + + FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); + + FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); + } + else + { + // If we are baking in replacement mode, remove the previous components (if they belong to FoundActor) + for (const FString& PrevComponentPathStr : InBakedOutputObject.InstancedComponents) + { + const FSoftObjectPath PrevComponentPath(PrevComponentPathStr); + + if (!PrevComponentPath.IsValid()) + continue; + + UActorComponent* PrevComponent = Cast(PrevComponentPath.TryLoad()); + if (!IsValid(PrevComponent) || PrevComponent->GetOwner() != FoundActor) + continue; + + RemovePreviouslyBakedComponent(PrevComponent); + } + + const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); + } + // The folder is named after the original actor and contains all generated actors + SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); + + // Get/create the actor's root component + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + if (bSpawnedActor && IsValid(RootComponent)) + RootComponent->SetWorldTransform(InTransform); + + // Empty and reserve enough space in the baked components array for the new components + InBakedOutputObject.InstancedComponents.Empty(InMSIC->GetInstances().Num()); + + // Now add s SMC component for each of the SMC's instance + for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances()) + { + if (!CurrentSMC || CurrentSMC->IsPendingKill()) + continue; + + UStaticMeshComponent* NewSMC = DuplicateObject( + CurrentSMC, + FoundActor, + FName(MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetName()))); + if (!NewSMC || NewSMC->IsPendingKill()) + continue; + + InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString()); + + NewSMC->RegisterComponent(); + // NewSMC->SetupAttachment(nullptr); + NewSMC->SetStaticMesh(BakedStaticMesh); + FoundActor->AddInstanceComponent(NewSMC); + NewSMC->SetWorldTransform(CurrentSMC->GetComponentTransform()); + + if (DuplicatedMSICOverrideMaterials.Num() > 0) + { + UMaterialInterface * InstancerMaterial = DuplicatedMSICOverrideMaterials[0]; + if (InstancerMaterial) + { + NewSMC->OverrideMaterials.Empty(); + int32 MeshMaterialCount = BakedStaticMesh->GetStaticMaterials().Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + NewSMC->SetMaterial(Idx, InstancerMaterial); + } + } + + if (IsValid(RootComponent)) + NewSMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform); + + // TODO: Do we need to copy properties here, we duplicated the component + // // Copy properties from the existing component + // CopyPropertyToNewActorAndComponent(FoundActor, NewSMC, CurrentSMC); + } + + if (bSpawnedActor) + FoundActor->FinishSpawning(InTransform); + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + FoundActor, + BakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + BakedStaticMesh, + StaticMesh, + nullptr, + MeshPackageParams.BakeFolder, + MeshPackageParams)); + OutputEntry.bInstancerOutput = true; + OutputEntry.InstancerPackageParams = InstancerPackageParams; + + // Postpone these calls to do them once per actor + OutActors.Last().bPostBakeProcessPostponed = true; + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = true; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = false; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::FindHGPO( + const FHoudiniOutputObjectIdentifier& InIdentifier, + const TArray& InHGPOs, + FHoudiniGeoPartObject const*& OutHGPO) +{ + // Find the HGPO that matches this output identifier + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + for (auto & NextHGPO : InHGPOs) + { + // We use Matches() here as it handles the case where the HDA was loaded, + // which likely means that the the obj/geo/part ids dont match the output identifier + if(InIdentifier.Matches(NextHGPO)) + { + FoundHGPO = &NextHGPO; + break; + } + } + + OutHGPO = FoundHGPO; + return !OutHGPO; +} + +void +FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const FHoudiniOutputObject& InMeshOutputObject, + FString& OutBakeName) +{ + // The bake name override has priority + OutBakeName = InMeshOutputObject.BakeName; + if (OutBakeName.IsEmpty()) + { + FHoudiniAttributeResolver Resolver; + Resolver.SetCachedAttributes(InMeshOutputObject.CachedAttributes); + Resolver.SetTokensFromStringMap(InMeshOutputObject.CachedTokens); + const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(InObject); + // The default output name (if not set via attributes) is {object_name}, which look for an object_name + // key-value token + if (!Resolver.GetCachedTokens().Contains(TEXT("object_name"))) + Resolver.SetToken(TEXT("object_name"), DefaultObjectName); + OutBakeName = Resolver.ResolveOutputName(); + // const TArray& HGPOs = InAllOutputs[MeshOutputIdx]->GetHoudiniGeoPartObjects(); + // const FHoudiniGeoPartObject* FoundHGPO = nullptr; + // FindHGPO(MeshIdentifier, HGPOs, FoundHGPO); + // // ... finally the part name + // if (FoundHGPO && FoundHGPO->bHasCustomPartName) + // OutBakeName = FoundHGPO->PartName; + if (OutBakeName.IsEmpty()) + OutBakeName = DefaultObjectName; + } +} + +bool +FHoudiniEngineBakeUtils::GetTemporaryOutputObjectBakeName( + const UObject* InObject, + EHoudiniOutputType InOutputType, + const TArray& InAllOutputs, + FString& OutBakeName) +{ + if (!IsValid(InObject)) + return false; + + OutBakeName.Empty(); + + int32 MeshOutputIdx = INDEX_NONE; + FHoudiniOutputObjectIdentifier MeshIdentifier; + if (FindOutputObject(InObject, InOutputType, InAllOutputs, MeshOutputIdx, MeshIdentifier)) + { + // Found the mesh, get its name + const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIdx]->GetOutputObjects().FindChecked(MeshIdentifier); + GetTemporaryOutputObjectBakeName(InObject, MeshOutputObject, OutBakeName); + + return true; + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + // Check that index is not negative + if (InOutputIndex < 0) + return false; + + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; + if (!InOutput || InOutput->IsPendingKill()) + return false; + + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; + if (!Factory) + return false; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); + + // Get the previous bake objects + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + + const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; + TMap NewBakedOutputObjects; + + for (auto& Pair : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + const FHoudiniOutputObject& OutputObject = Pair.Value; + + // Add a new baked output object entry and update it with the previous bake's data, if available + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); + + UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); + if (!StaticMesh || StaticMesh->IsPendingKill()) + continue; + + UStaticMeshComponent* InSMC = Cast(OutputObject.OutputComponent); + if (!InSMC || InSMC->IsPendingKill()) + continue; + + // Find the HGPO that matches this output identifier + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + FindHGPO(Identifier, HGPOs, FoundHGPO); + + // We do not bake templated geos + if (FoundHGPO && FoundHGPO->bIsTemplated) + continue; + + const FString DefaultObjectName = FHoudiniPackageParams::GetPackageNameExcludingGUID(StaticMesh); + + UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + FHoudiniPackageParams PackageParams; + + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); + + if (!ResolvePackageParams( + HoudiniAssetComponent, + InOutput, + Identifier, + OutputObject, + InHoudiniAssetName, + DefaultObjectName, + InBakeFolder, + bInReplaceAssets, + PackageParams, + OutPackagesToSave)) + { + continue; + } + + // Bake the static mesh if it is still temporary + UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, + Cast(BakedOutputObject.GetBakedObjectIfValid()), + PackageParams, + InAllOutputs, + OutActors, + InTempCookFolder.Path, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap); + + if (!BakedSM || BakedSM->IsPendingKill()) + continue; + + // Record the baked object + BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString(); + + // Make sure we have a level to spawn to + if (!DesiredLevel || DesiredLevel->IsPendingKill()) + continue; + + // Try to find the unreal_bake_actor, if specified + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + UStaticMeshComponent* SMC = nullptr; + if (!FoundActor) + { + // Spawn the new actor + FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform()); + if (!FoundActor || FoundActor->IsPendingKill()) + continue; + + // Copy properties to new actor + AStaticMeshActor* SMActor = Cast(FoundActor); + if (!SMActor || SMActor->IsPendingKill()) + continue; + + SMC = SMActor->GetStaticMeshComponent(); + } + else + { + if (bInReplaceAssets) + { + // Check if we have a previous bake component and that it belongs to FoundActor, if so, reuse it + UStaticMeshComponent* PrevSMC = Cast(BakedOutputObject.GetBakedComponentIfValid()); + if (IsValid(PrevSMC) && (PrevSMC->GetOwner() == FoundActor)) + { + SMC = PrevSMC; + } + } + + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(FoundActor, bCreateIfMissing); + + if (!IsValid(SMC)) + { + // Create a new static mesh component on the existing actor + SMC = NewObject(FoundActor, NAME_None, RF_Transactional); + + FoundActor->AddInstanceComponent(SMC); + if (IsValid(RootComponent)) + SMC->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + else + FoundActor->SetRootComponent(SMC); + SMC->RegisterComponent(); + } + } + + // We need to make a unique name for the actor, renaming an object on top of another is a fatal error + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, BakeActorName.ToString(), FoundActor); + RenameAndRelabelActor(FoundActor, NewNameStr, false); + SetOutlinerFolderPath(FoundActor, OutputObject, WorldOutlinerFolderPath); + + if (IsValid(SMC)) + { + const bool bCopyWorldTransform = true; + CopyPropertyToNewActorAndComponent(FoundActor, SMC, InSMC, bCopyWorldTransform); + SMC->SetStaticMesh(BakedSM); + BakedOutputObject.BakedComponent = FSoftObjectPath(SMC).ToString(); + } + + BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + OutActors.Add(FHoudiniEngineBakedActor( + FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh, SMC, + PackageParams.BakeFolder, PackageParams)); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + BakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + } + + // Update the cached baked output data + InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; + + return true; +} + +bool FHoudiniEngineBakeUtils::ResolvePackageParams( + const UHoudiniAssetComponent* HoudiniAssetComponent, + UHoudiniOutput* InOutput, + const FHoudiniOutputObjectIdentifier& Identifier, + const FHoudiniOutputObject& InOutputObject, + const FString& InHoudiniAssetName, + const FString& DefaultObjectName, + const FDirectoryPath& InBakeFolder, + const bool bInReplaceAssets, + FHoudiniPackageParams& OutPackageParams, + TArray& OutPackagesToSave) +{ + FHoudiniAttributeResolver Resolver; + + UWorld* DesiredWorld = InOutput ? InOutput->GetWorld() : GWorld; + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, Identifier, InOutputObject, DefaultObjectName, + InHoudiniAssetName, OutPackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); + + + // See if this output object has an unreal_level_path attribute specified + // In which case, we need to create/find the desired level for baking instead of using the current one + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + // Get the package path from the unreal_level_path attribute + FString LevelPackagePath = Resolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a level, add it to the packages to save + // TODO: ? always add the level to the packages to save? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + // Check that index is not negative + if (InOutputIndex < 0) + return false; + + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; + if (!Output || Output->IsPendingKill()) + return false; + + TArray PackagesToSave; + + // Find the previous baked output data for this output index. If an entry + // does not exist, create entries up to and including this output index + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; + const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; + TMap NewBakedOutputObjects; + + const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); + + for (auto & Pair : OutputObjects) + { + FHoudiniOutputObject& OutputObject = Pair.Value; + USplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(Identifier); + if (OldBakedOutputObjects.Contains(Identifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); + + // TODO: FIX ME!! May not work 100% + const FHoudiniGeoPartObject* FoundHGPO = nullptr; + for (auto & NextHGPO : HGPOs) + { + if (Identifier.GeoId == NextHGPO.GeoId && + Identifier.ObjectId == NextHGPO.ObjectId && + Identifier.PartId == NextHGPO.PartId) + { + FoundHGPO = &NextHGPO; + break; + } + } + + if (!FoundHGPO) + continue; + + const FString DefaultObjectName = InHoudiniAssetName + "_" + SplineComponent->GetName(); + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* const DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, + InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); + + BakeCurve( + OutputObject, BakedOutputObject, PackageParams, Resolver, bInReplaceActors, bInReplaceAssets, + OutActors, PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); + } + + // Update the cached bake output results + BakedOutput.BakedOutputObjects = NewBakedOutputObjects; + + SaveBakedPackages(PackagesToSave); + + return true; +} + +bool +FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint) +{ + if (!InActor || InActor->IsPendingKill()) + return false; + + if (!OutBlueprint || OutBlueprint->IsPendingKill()) + return false; + + if (InActor->GetInstanceComponents().Num() > 0) + FKismetEditorUtilities::AddComponentsToBlueprint( + OutBlueprint, + InActor->GetInstanceComponents()); + + if (OutBlueprint->GeneratedClass) + { + AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject()); + if (!CDO || CDO->IsPendingKill()) + return false; + + const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) + (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); + + EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions); + + USceneComponent * Scene = CDO->GetRootComponent(); + if (Scene && !Scene->IsPendingKill()) + { + Scene->SetRelativeLocation(FVector::ZeroVector); + Scene->SetRelativeRotation(FRotator::ZeroRotator); + + // Clear out the attachment info after having copied the properties from the source actor + Scene->SetupAttachment(nullptr); + while (true) + { + const int32 ChildCount = Scene->GetAttachChildren().Num(); + if (ChildCount < 1) + break; + + USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; + if (Component && !Component->IsPendingKill()) + Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + } + check(Scene->GetAttachChildren().Num() == 0); + + // Ensure the light mass information is cleaned up + Scene->InvalidateLightingCache(); + + // Copy relative scale from source to target. + if (USceneComponent* SrcSceneRoot = InActor->GetRootComponent()) + { + Scene->SetRelativeScale3D_Direct(SrcSceneRoot->GetRelativeScale3D()); + } + } + } + + // Compile our blueprint and notify asset system about blueprint. + //FKismetEditorUtilities::CompileBlueprint(OutBlueprint); + //FAssetRegistryModule::AssetCreated(OutBlueprint); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, bool bInRecenterBakedActors) +{ + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + const bool bSuccess = BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, bInRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); + if (!bSuccess) + { + // TODO: ? + HOUDINI_LOG_WARNING(TEXT("Errors while baking to blueprints.")); + } + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Broadcast that the bake is complete + HoudiniAssetComponent->HandleOnPostBake(bSuccess); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprints( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceAssets, + bool bInRecenterBakedActors, + FHoudiniEngineOutputStats& InBakeStats, + TArray& OutBlueprints, + TArray& OutPackagesToSave) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); + const bool bIsOwnerActorValid = IsValid(OwnerActor); + + TArray Actors; + + // Don't process outputs that are not supported in blueprints + TArray OutputsToBake = { + EHoudiniOutputType::Mesh, + EHoudiniOutputType::Instancer, + EHoudiniOutputType::Curve + }; + TArray InstancerComponentTypesToBake = { + EHoudiniInstancerComponentType::StaticMeshComponent, + EHoudiniInstancerComponentType::InstancedStaticMeshComponent, + EHoudiniInstancerComponentType::MeshSplitInstancerComponent, + EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent + }; + // When baking blueprints we always create new actors since they are deleted from the world once copied into the + // blueprint + const bool bReplaceActors = false; + bool bBakeSuccess = BakeHoudiniActorToActors( + HoudiniAssetComponent, + bReplaceActors, + bInReplaceAssets, + Actors, + OutPackagesToSave, + InBakeStats, + &OutputsToBake, + &InstancerComponentTypesToBake); + if (!bBakeSuccess) + { + HOUDINI_LOG_ERROR(TEXT("Could not create output actors for baking to blueprint.")); + return false; + } + + // Get the previous baked outputs + TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); + + bBakeSuccess = BakeBlueprintsFromBakedActors( + Actors, + bInRecenterBakedActors, + bInReplaceAssets, + bIsOwnerActorValid ? OwnerActor->GetName() : FString(), + HoudiniAssetComponent->BakeFolder, + &BakedOutputs, + nullptr, + OutBlueprints, + OutPackagesToSave); + + return bBakeSuccess; +} + +UStaticMesh* +FHoudiniEngineBakeUtils::BakeStaticMesh( + UStaticMesh * StaticMesh, + const FHoudiniPackageParams& PackageParams, + const TArray& InAllOutputs, + const FDirectoryPath& InTempCookFolder, + TMap& InOutAlreadyBakedMaterialsMap) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return nullptr; + + TArray PackagesToSave; + TArray Outputs; + const TArray BakedResults; + UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave, InOutAlreadyBakedMaterialsMap); + + if (BakedStaticMesh) + { + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor) + { + TArray Objects; + Objects.Add(BakedStaticMesh); + GEditor->SyncBrowserToObjects(Objects); + } + } + + return BakedStaticMesh; +} + +bool +FHoudiniEngineBakeUtils::BakeLandscape( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + bool bInReplaceActors, + bool bInReplaceAssets, + FString BakePath, + FString HoudiniAssetName, + FHoudiniEngineOutputStats& BakeStats + ) +{ + // Check that index is not negative + if (InOutputIndex < 0) + return false; + + if (!InAllOutputs.IsValidIndex(InOutputIndex)) + return false; + + UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; + if (!IsValid(Output)) + return false; + + // Find the previous baked output data for this output index. If an entry + // does not exist, create entries up to and including this output index + if (!InBakedOutputs.IsValidIndex(InOutputIndex)) + InBakedOutputs.SetNum(InOutputIndex + 1); + + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniBakedOutput& BakedOutput = InBakedOutputs[InOutputIndex]; + const TMap& OldBakedOutputObjects = BakedOutput.BakedOutputObjects; + TMap NewBakedOutputObjects; + TArray PackagesToSave; + TArray LandscapeWorldsToUpdate; + + FHoudiniPackageParams PackageParams; + + for (auto& Elem : OutputObjects) + { + const FHoudiniOutputObjectIdentifier& ObjectIdentifier = Elem.Key; + FHoudiniOutputObject& OutputObject = Elem.Value; + FHoudiniBakedOutputObject& BakedOutputObject = NewBakedOutputObjects.Add(ObjectIdentifier); + if (OldBakedOutputObjects.Contains(ObjectIdentifier)) + BakedOutputObject = OldBakedOutputObjects.FindChecked(ObjectIdentifier); + + // Populate the package params for baking this output object. + if (!IsValid(OutputObject.OutputObject)) + continue; + + if (!OutputObject.OutputObject->IsA()) + continue; + + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject.OutputObject); + ALandscapeProxy* Landscape = LandscapePtr->GetRawPtr(); + if (!IsValid(Landscape)) + continue; + + FString ObjectName = Landscape->GetName(); + + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + UWorld* const DesiredWorld = Landscape ? Landscape->GetWorld() : GWorld; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + DesiredWorld, HoudiniAssetComponent, ObjectIdentifier, OutputObject, ObjectName, + HoudiniAssetName, PackageParams, Resolver, BakePath, AssetPackageReplaceMode); + + BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, + PackageParams, Resolver, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); + } + + // Update the cached baked output data + BakedOutput.BakedOutputObjects = NewBakedOutputObjects; + + if (PackagesToSave.Num() > 0) + { + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); + } + + for(UWorld* LandscapeWorld : LandscapeWorldsToUpdate) + { + if (!LandscapeWorld) + continue; + FHoudiniEngineUtils::RescanWorldPath(LandscapeWorld); + ULandscapeInfo::RecreateLandscapeInfo(LandscapeWorld, true); + if (LandscapeWorld->WorldComposition) + { + UWorldComposition::WorldCompositionChangedEvent.Broadcast(LandscapeWorld); + } + } + + if (PackagesToSave.Num() > 0) + { + // These packages were either created during the Bake process or they weren't + // loaded in the first place so be sure to unload them again to preserve their "state". + + TArray PackagesToUnload; + for (UPackage* Package : PackagesToSave) + { + if (!Package->IsDirty()) + PackagesToUnload.Add(Package); + } + UPackageTools::UnloadPackages(PackagesToUnload); + } + +#if WITH_EDITOR + FEditorDelegates::RefreshLevelBrowser.Broadcast(); + FEditorDelegates::RefreshAllBrowsers.Broadcast(); +#endif + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeLandscapeObject( + FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInReplaceActors, + bool bInReplaceAssets, + FHoudiniPackageParams& PackageParams, + FHoudiniAttributeResolver& InResolver, + TArray& WorldsToUpdate, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& BakeStats) +{ + UHoudiniLandscapePtr* LandscapePointer = Cast(InOutputObject.OutputObject); + if (!LandscapePointer) + return false; + + ALandscapeProxy* TileActor = LandscapePointer->GetRawPtr(); + if (!TileActor) + return false; + + // Fetch the previous bake's pointer and proxy (if available) + ALandscapeProxy* PreviousTileActor = Cast(InBakedOutputObject.GetBakedObjectIfValid()); + + UWorld* TileWorld = TileActor->GetWorld(); + ULevel* TileLevel = TileActor->GetLevel(); + + ULandscapeInfo::RecreateLandscapeInfo(TileWorld, true); + + // If this actor has a shared landscape, ensure the shared landscape gets detached from the HAC + // and has the appropriate name. + ALandscape* SharedLandscapeActor = TileActor->GetLandscapeActor(); + check(SharedLandscapeActor); + + // Fetch the previous bake's shared landscape actor (if available) + ALandscape* PreviousSharedLandscapeActor = nullptr; + if (IsValid(PreviousTileActor)) + PreviousSharedLandscapeActor = PreviousTileActor->GetLandscapeActor(); + + const bool bHasSharedLandscape = SharedLandscapeActor != TileActor; + bool bHasPreviousSharedLandscape = PreviousSharedLandscapeActor && PreviousSharedLandscapeActor != PreviousTileActor; + if (bHasPreviousSharedLandscape) + { + // Ignore the previous shared landscape if the world's are different + // Typically in baking we treat completely different asset/output names in a bake as detached from the "previous" bake + if (PreviousSharedLandscapeActor->GetWorld() != SharedLandscapeActor->GetWorld()) + bHasPreviousSharedLandscape = false; + } + + bool bLandscapeReplaced = false; + if (bHasSharedLandscape) + { + // If we are baking in replace mode and we have a previous shared landscape actor, use the name of that + // actor + FString SharedLandscapeName = InResolver.ResolveAttribute( + HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + SharedLandscapeActor->GetName()); + + // If the shared landscape is still attached, or it's base name does not match the desired name, "bake" it + AActor* const AttachedParent = SharedLandscapeActor->GetAttachParentActor(); + if (AttachedParent || SharedLandscapeActor->GetFName().GetPlainNameString() != SharedLandscapeName) + { + if (bHasPreviousSharedLandscape && bInReplaceActors && + PreviousSharedLandscapeActor->GetFName().GetPlainNameString() == SharedLandscapeName) + { + SharedLandscapeName = PreviousSharedLandscapeActor->GetName(); + } + else if (!bInReplaceActors) + { + // If we are not baking in replacement mode, create a unique name if the name is already in use + SharedLandscapeName = MakeUniqueObjectNameIfNeeded( + SharedLandscapeActor->GetOuter(), SharedLandscapeActor->GetClass(), *SharedLandscapeName, SharedLandscapeActor); + } + + if (SharedLandscapeActor->GetName() != SharedLandscapeName) + { + AActor* FoundActor = nullptr; + ALandscape* ExistingLandscape = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeName, FoundActor); + if (ExistingLandscape && bInReplaceActors) + { + // Even though we found an existing landscape with the desired type, we're just going to destroy/replace + // it for now. + FHoudiniEngineUtils::RenameToUniqueActor(ExistingLandscape, SharedLandscapeName+"_0"); + ExistingLandscape->Destroy(); + bLandscapeReplaced = true; + } + + // Fix name of shared landscape + FHoudiniEngineUtils::SafeRenameActor(SharedLandscapeActor, *SharedLandscapeName); + } + + SharedLandscapeActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + WorldsToUpdate.AddUnique(SharedLandscapeActor->GetWorld()); + } + } + + // Find the world where the landscape tile should be placed. + + TArray ValidLandscapes; + + FString ActorName = InResolver.ResolveOutputName(); + + // If the unreal_level_path was not specified, then fallback to the tile world's package + FString PackagePath = TileWorld->GetPackage() ? TileWorld->GetPackage()->GetPathName() : FString(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + PackagePath = InResolver.ResolveFullLevelPath(); + + // Get the previous baked actor (if available) name, but only if it is in the + // same target level, and it's plain name (no numeric suffix) matches ActorName + // In replacement mode we'll then replace the previous tile actor. + if (bInReplaceActors && IsValid(PreviousTileActor)) + { + UPackage* PreviousPackage = PreviousTileActor->GetPackage(); + if (IsValid(PreviousPackage) && PreviousPackage->GetPathName() == PackagePath && + PreviousTileActor->GetFName().GetPlainNameString() == ActorName) + { + ActorName = PreviousTileActor->GetName(); + } + } + + bool bCreatedPackage = false; + UWorld* TargetWorld = nullptr; + ULevel* TargetLevel = nullptr; + ALandscapeProxy* TargetActor = FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Bake( + TileActor->GetWorld(), + nullptr, //unused in bake mode + ValidLandscapes,//unused in bake mode + -1, //unused in bake mode + -1, //unused in bake mode + ActorName, + PackagePath, + TargetWorld, + TargetLevel, + bCreatedPackage + ); + + check(TargetLevel) + check(TargetWorld) + + if (TargetActor && TargetActor != TileActor) + { + if (bInReplaceActors && (!PreviousTileActor || PreviousTileActor == TargetActor)) + { + // We found an target matching the name that we want. For now, rename it and then nuke it, so that + // at the very least we can spawn a new actor with the desired name. At a later stage we'll implement + // a content update, if possible. + FHoudiniEngineUtils::RenameToUniqueActor(TargetActor, ActorName + TEXT("_0")); + TargetActor->Destroy(); + } + else + { + // incremental, keep existing actor and create a unique name for the new one + ActorName = MakeUniqueObjectNameIfNeeded(TargetActor->GetOuter(), TargetActor->GetClass(), ActorName, TileActor); + } + TargetActor = nullptr; + } + + if (TargetLevel != TileActor->GetLevel()) + { + bool bLevelInWorld = TileWorld->ContainsLevel(TargetLevel); + ALandscape* SharedLandscape = TileActor->GetLandscapeActor(); + ULandscapeInfo* LandscapeInfo = TileActor->GetLandscapeInfo(); + + check(LandscapeInfo); + + // We can now move the current landscape to the new world / level + // if (TileActor->GetClass()->IsChildOf()) + { + // We can only move streaming proxies to sublevels for now. + TArray ActorsToMove = {TileActor}; + + ALandscapeProxy* NewLandscapeProxy = LandscapeInfo->MoveComponentsToLevel(TileActor->LandscapeComponents, TargetLevel); + // We have now moved the landscape components into the new level. We can (hopefully) safely delete the + // old tile actor. + TileActor->Destroy(); + + TargetLevel->MarkPackageDirty(); + + TileActor = NewLandscapeProxy; + } + } + else + { + // Ensure the landscape actor is detached. + TileActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + } + + UPackage * CreatedPackage = TargetLevel->GetOutermost(); + TMap AlreadyBakedMaterialsMap; + + // Replace materials + if (TileActor->LandscapeMaterial) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(TileActor->LandscapeMaterial, PackageParams, OutPackagesToSave, AlreadyBakedMaterialsMap); + TileActor->LandscapeMaterial = DuplicatedMaterial; + } + + if (TileActor->LandscapeHoleMaterial) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(TileActor->LandscapeHoleMaterial, PackageParams, OutPackagesToSave, AlreadyBakedMaterialsMap); + TileActor->LandscapeHoleMaterial = DuplicatedMaterial; + } + + // Ensure the tile actor has the desired name. + FHoudiniEngineUtils::SafeRenameActor(TileActor, ActorName); + + if (TileActor->GetClass()->IsChildOf(ALandscape::StaticClass())) + { + // This is not a shared landscape. Be sure to update this landscape's world when + // baking is done. + WorldsToUpdate.AddUnique(TileActor->GetWorld()); + } + + if (bCreatedPackage) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(TargetLevel->GetOutermost()); + } + + // Record the landscape in the baked output object via a new UHoudiniLandscapePtr + // UHoudiniLandscapePtr* BakedLandscapePtr = NewObject(LandscapePointer->GetOuter()); + // if (IsValid(BakedLandscapePtr)) + // { + // BakedLandscapePtr->SetSoftPtr(TileActor); + InBakedOutputObject.BakedObject = FSoftObjectPath(TileActor).ToString(); + // } + // else + // { + // InBakedOutputObject.BakedObject = nullptr; + // } + + // Bake the landscape layer uassets + ULandscapeInfo* const LandscapeInfo = TileActor->GetLandscapeInfo(); + if (IsValid(LandscapeInfo) && LandscapeInfo->Layers.Num() > 0) + { + TSet TempLayers; + const int32 NumLayers = LandscapeInfo->Layers.Num(); + TempLayers.Reserve(NumLayers); + for (int32 LayerIndex = 0; LayerIndex < NumLayers; ++LayerIndex) + { + const FLandscapeInfoLayerSettings& Layer = LandscapeInfo->Layers[LayerIndex]; + if (!IsValid(Layer.LayerInfoObj)) + continue; + + if (!IsObjectInTempFolder(Layer.LayerInfoObj, PackageParams.TempCookFolder)) + continue; + + if (!TempLayers.Contains(Layer.LayerInfoObj)) + TempLayers.Add(Layer.LayerInfoObj); + } + + // Setup package params to duplicate each layer + FHoudiniPackageParams LayerPackageParams = PackageParams; + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + LayerPackageParams.ReplaceMode = AssetPackageReplaceMode; + + // Determine the final bake name of the "owning" landscape (shared landscape in tiled mode, or just the + // landscape actor itself in non-tiled mode + FString OwningLandscapeActorBakeName; + if (bHasSharedLandscape && IsValid(SharedLandscapeActor)) + { + SharedLandscapeActor->GetName(OwningLandscapeActorBakeName); + } + else + { + TileActor->GetName(OwningLandscapeActorBakeName); + } + + // Keep track of the landscape layers we are baking this time around, and replace in the baked output object + // at the end. + TMap ThisBakedLandscapeLayers; + + // Bake/duplicate temp layers and replace temp layers via LandscapeInfo + for (ULandscapeLayerInfoObject* const LayerInfo : TempLayers) + { + const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerInfo->LayerName.ToString()); + LayerPackageParams.SplitStr = SanitizedLayerName; + LayerPackageParams.ObjectName = OwningLandscapeActorBakeName + TEXT("_layer_") + SanitizedLayerName; + + // Get the previously baked layer info for this layer, if any + ULandscapeLayerInfoObject* const PreviousBakedLayerInfo = InBakedOutputObject.GetLandscapeLayerInfoIfValid( + LayerInfo->LayerName); + + // If our name is the base name (no number) of the previous, then we can fetch the bake counter for + // replacement / incrementing from it + int32 BakeCounter = 0; + if (IsValid(PreviousBakedLayerInfo) && LayerPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakedLayerInfo)) + { + // Get the bake counter from the previous bake + FHoudiniPackageParams::GetBakeCounterFromBakedAsset(PreviousBakedLayerInfo, BakeCounter); + } + + FString LayerPackageName; + UPackage* const LayerPackage = LayerPackageParams.CreatePackageForObject(LayerPackageName, BakeCounter); + if (IsValid(LayerPackage)) + { + BakeStats.NotifyPackageCreated(1); + ULandscapeLayerInfoObject* BakedLayer = DuplicateObject( + LayerInfo, LayerPackage, *LayerPackageName); + if (IsValid(BakedLayer)) + { + OutPackagesToSave.Add(LayerPackage); + + // Trigger update of the Layer Info + BakedLayer->PreEditChange(nullptr); + BakedLayer->PostEditChange(); + BakedLayer->MarkPackageDirty(); + + // Mark the package dirty... + LayerPackage->MarkPackageDirty(); + + LandscapeInfo->ReplaceLayer(LayerInfo, BakedLayer); + + // Record as the new baked result for the LayerName + ThisBakedLandscapeLayers.Add(LayerInfo->LayerName, FSoftObjectPath(BakedLayer).ToString()); + } + } + } + + // Update the baked landscape layers in InBakedOutputObject + InBakedOutputObject.LandscapeLayers = ThisBakedLandscapeLayers; + } + + // Remove the landscape from the InOutputObject since it should no longer be used/reused/updated by temp cooks + InOutputObject.OutputObject = nullptr; + + DestroyPreviousBakeOutput(InBakedOutputObject, true, true, true); + + // ---------------------------------------------------- + // Collect baking stats + // ---------------------------------------------------- + if (bLandscapeReplaced) + BakeStats.NotifyObjectsReplaced(EHoudiniOutputType::Landscape, 1); + else + BakeStats.NotifyObjectsCreated(EHoudiniOutputType::Landscape, 1); + + if (bCreatedPackage) + BakeStats.NotifyPackageCreated(1); + else + if (TileLevel != TargetLevel) + BakeStats.NotifyPackageUpdated(1); + + return true; +} + +UStaticMesh * +FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( + UStaticMesh * InStaticMesh, + UStaticMesh * InPreviousBakeStaticMesh, + const FHoudiniPackageParams &PackageParams, + const TArray& InParentOutputs, + const TArray& InCurrentBakedActors, + const FString& InTemporaryCookFolder, + TArray & OutCreatedPackages, + TMap& InOutAlreadyBakedMaterialsMap) +{ + if (!InStaticMesh || InStaticMesh->IsPendingKill()) + return nullptr; + + const bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, EHoudiniOutputType::Mesh, InParentOutputs, InTemporaryCookFolder); + if (!bIsTemporaryStaticMesh) + { + // The Static Mesh is not a temporary one/already baked, we can simply reuse it + // instead of duplicating it + return InStaticMesh; + } + + // Look for InStaticMesh as the SourceObject in InCurrentBakedActors (it could have already been baked along with + // a previous output: instancers etc) + for (const FHoudiniEngineBakedActor& BakedActor : InCurrentBakedActors) + { + if (BakedActor.SourceObject == InStaticMesh && IsValid(BakedActor.BakedObject) + && BakedActor.BakedObject->IsA(InStaticMesh->GetClass())) + { + // We have found a bake result where InStaticMesh was the source object and we have a valid BakedObject + // of a compatible class + return Cast(BakedActor.BakedObject); + } + } + + // InStaticMesh is temporary and we didn't find a baked version of it in our current bake output, we need to bake it + + // If we have a previously baked static mesh, get the bake counter from it so that both replace and increment + // is consistent with the bake counter + int32 BakeCounter = 0; + bool bPreviousBakeStaticMeshValid = IsValid(InPreviousBakeStaticMesh); + TArray PreviousBakeMaterials; + if (bPreviousBakeStaticMeshValid) + { + bPreviousBakeStaticMeshValid = PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBakeStaticMesh); + if (bPreviousBakeStaticMeshValid) + { + PackageParams.GetBakeCounterFromBakedAsset(InPreviousBakeStaticMesh, BakeCounter); + PreviousBakeMaterials = InPreviousBakeStaticMesh->GetStaticMaterials(); + } + } + FString CreatedPackageName; + UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter); + if (!MeshPackage || MeshPackage->IsPendingKill()) + return nullptr; + + OutCreatedPackages.Add(MeshPackage); + + // We need to be sure the package has been fully loaded before calling DuplicateObject + if (!MeshPackage->IsFullyLoaded()) + { + FlushAsyncLoading(); + if (!MeshPackage->GetOuter()) + { + MeshPackage->FullyLoad(); + } + else + { + MeshPackage->GetOutermost()->FullyLoad(); + } + } + + // If the a UStaticMesh with that name already exists then detach it from all of its components before replacing + // it so that its render resources can be safely replaced/updated, and then reattach it + UStaticMesh * DuplicatedStaticMesh = nullptr; + UStaticMesh* ExistingMesh = FindObject(MeshPackage, *CreatedPackageName); + bool bFoundExistingMesh = false; + if (IsValid(ExistingMesh)) + { + FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); + DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + bFoundExistingMesh = true; + } + else + { + DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + } + + if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, DuplicatedStaticMesh, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MeshPackage, DuplicatedStaticMesh, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedPackageName); + + // See if we need to duplicate materials and textures. + TArrayDuplicatedMaterials; + TArray& Materials = DuplicatedStaticMesh->GetStaticMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) + { + UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + continue; + + // Only duplicate the material if it is temporary + if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InParentOutputs, InTemporaryCookFolder)) + { + UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); + if (MaterialPackage && !MaterialPackage->IsPendingKill()) + { + FString MaterialName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + MeshPackage, DuplicatedStaticMesh, MaterialName)) + { + MaterialName = MaterialName + "_Material" + FString::FromInt(MaterialIdx + 1); + + // We only deal with materials. + if (!MaterialInterface->IsA(UMaterial::StaticClass()) && !MaterialInterface->IsA(UMaterialInstance::StaticClass())) + { + continue; + } + + UMaterialInterface * Material = MaterialInterface; + + if (Material && !Material->IsPendingKill()) + { + // Look for a previous bake material at this index + UMaterialInterface* PreviousBakeMaterial = nullptr; + if (bPreviousBakeStaticMeshValid && PreviousBakeMaterials.IsValidIndex(MaterialIdx)) + { + PreviousBakeMaterial = Cast(PreviousBakeMaterials[MaterialIdx].MaterialInterface); + } + // Duplicate material resource. + UMaterialInterface * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages, InOutAlreadyBakedMaterialsMap); + + if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + continue; + + // Store duplicated material. + FStaticMaterial DupeStaticMaterial = Materials[MaterialIdx]; + DupeStaticMaterial.MaterialInterface = DuplicatedMaterial; + DuplicatedMaterials.Add(DupeStaticMaterial); + continue; + } + } + } + } + + // We can simply reuse the source material + DuplicatedMaterials.Add(Materials[MaterialIdx]); + } + + // Assign duplicated materials. + DuplicatedStaticMesh->SetStaticMaterials(DuplicatedMaterials); + + // Notify registry that we have created a new duplicate mesh. + if (!bFoundExistingMesh) + FAssetRegistryModule::AssetCreated(DuplicatedStaticMesh); + + // Dirty the static mesh package. + DuplicatedStaticMesh->MarkPackageDirty(); + + return DuplicatedStaticMesh; +} + +ALandscapeProxy* +FHoudiniEngineBakeUtils::BakeHeightfield( + ALandscapeProxy * InLandscapeProxy, + const FHoudiniPackageParams & PackageParams, + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType) +{ + if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill()) + return nullptr; + + const FString & BakeFolder = PackageParams.BakeFolder; + const FString & AssetName = PackageParams.HoudiniAssetName; + TArray PackagesToSave; + + switch (LandscapeOutputBakeType) + { + case EHoudiniLandscapeOutputBakeType::Detachment: + { + // Detach the landscape from the Houdini Asset Actor + InLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + } + break; + case EHoudiniLandscapeOutputBakeType::BakeToImage: + { + // Create heightmap image to the bake folder + ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); + if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + return nullptr; + + // bake to image must use absoluate path, + // and the file name has a file extension (.png) + FString BakeFolderInFullPath = BakeFolder; + + // Figure absolute path, + if (!BakeFolderInFullPath.EndsWith("/")) + BakeFolderInFullPath += "/"; + + if (BakeFolderInFullPath.StartsWith("/Game")) + BakeFolderInFullPath = BakeFolderInFullPath.Mid(5, BakeFolderInFullPath.Len() - 5); + + if (BakeFolderInFullPath.StartsWith("/")) + BakeFolderInFullPath = BakeFolderInFullPath.Mid(1, BakeFolderInFullPath.Len() - 1); + + FString FullPath = FPaths::ProjectContentDir() + BakeFolderInFullPath + AssetName + "_" + InLandscapeProxy->GetName() + ".png"; + + InLandscapeInfo->ExportHeightmap(FullPath); + + // TODO: + // We should update this to have an asset/package.. + } + break; + case EHoudiniLandscapeOutputBakeType::BakeToWorld: + { + ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); + if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + return nullptr; + + // 0. Get Landscape Data // + + // Extract landscape height data + TArray InLandscapeHeightData; + int32 XSize, YSize; + FVector Min, Max; + if (!FUnrealLandscapeTranslator::GetLandscapeData(InLandscapeProxy, InLandscapeHeightData, XSize, YSize, Min, Max)) + return nullptr; + + // Extract landscape Layers data + TArray InLandscapeImportLayerInfos; + for (int32 n = 0; n < InLandscapeInfo->Layers.Num(); ++n) + { + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!FUnrealLandscapeTranslator::GetLandscapeLayerData(InLandscapeProxy, InLandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + FLandscapeImportLayerInfo CurrentLayerInfo; + CurrentLayerInfo.LayerName = FName(LayerName); + CurrentLayerInfo.LayerInfo = InLandscapeInfo->Layers[n].LayerInfoObj; + CurrentLayerInfo.LayerData = CurrentLayerIntData; + + CurrentLayerInfo.LayerInfo->LayerUsageDebugColor = LayerUsageDebugColor; + + InLandscapeImportLayerInfos.Add(CurrentLayerInfo); + } + + // 1. Create package // + + FString PackagePath = PackageParams.GetPackagePath(); + FString PackageName = PackageParams.GetPackageName(); + + UPackage *CreatedPackage = nullptr; + FString CreatedPackageName; + + CreatedPackage = PackageParams.CreatePackageForObject(CreatedPackageName); + + if (!CreatedPackage) + return nullptr; + + // 2. Create a new world asset with dialog // + UWorldFactory* Factory = NewObject(); + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + UObject* Asset = AssetToolsModule.Get().CreateAssetWithDialog( + PackageName, PackagePath, + UWorld::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + + + UWorld* NewWorld = Cast(Asset); + if (!NewWorld) + return nullptr; + + NewWorld->SetCurrentLevel(NewWorld->PersistentLevel); + + // 4. Spawn a landscape proxy actor in the created world + ALandscapeStreamingProxy * BakedLandscapeProxy = NewWorld->SpawnActor(); + if (!BakedLandscapeProxy) + return nullptr; + + // Create a new GUID + FGuid currentGUID = FGuid::NewGuid(); + BakedLandscapeProxy->SetLandscapeGuid(currentGUID); + + // Deactivate CastStaticShadow on the landscape to avoid "grid shadow" issue + BakedLandscapeProxy->bCastStaticShadow = false; + + + // 5. Import data to the created landscape proxy + TMap> HeightmapDataPerLayers; + TMap> MaterialLayerDataPerLayer; + + HeightmapDataPerLayers.Add(FGuid(), InLandscapeHeightData); + MaterialLayerDataPerLayer.Add(FGuid(), InLandscapeImportLayerInfos); + + ELandscapeImportAlphamapType ImportLayerType = ELandscapeImportAlphamapType::Additive; + + BakedLandscapeProxy->Import( + currentGUID, + 0, 0, XSize-1, YSize-1, + InLandscapeInfo->ComponentNumSubsections, InLandscapeInfo->SubsectionSizeQuads, + HeightmapDataPerLayers, NULL, + MaterialLayerDataPerLayer, ImportLayerType); + + BakedLandscapeProxy->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((XSize * YSize) / (2048 * 2048) + 1), (uint32)2); + + TMap AlreadyBakedMaterialsMap; + + + if (BakedLandscapeProxy->LandscapeMaterial) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(BakedLandscapeProxy->LandscapeMaterial, PackageParams, PackagesToSave, AlreadyBakedMaterialsMap); + BakedLandscapeProxy->LandscapeMaterial = DuplicatedMaterial; + } + + if (BakedLandscapeProxy->LandscapeHoleMaterial) + { + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(BakedLandscapeProxy->LandscapeHoleMaterial, PackageParams, PackagesToSave, AlreadyBakedMaterialsMap); + BakedLandscapeProxy->LandscapeHoleMaterial = DuplicatedMaterial; + } + + // 6. Register all the landscape components, and set landscape actor transform + BakedLandscapeProxy->RegisterAllComponents(); + BakedLandscapeProxy->SetActorTransform(InLandscapeProxy->GetTransform()); + + // 7. Save Package + PackagesToSave.Add(CreatedPackage); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor) + { + TArray Objects; + Objects.Add(NewWorld); + GEditor->SyncBrowserToObjects(Objects); + } + } + break; + } + + return InLandscapeProxy; +} + +bool +FHoudiniEngineBakeUtils::BakeCurve( + USplineComponent* InSplineComponent, + ULevel* InLevel, + const FHoudiniPackageParams &PackageParams, + AActor*& OutActor, + USplineComponent*& OutSplineComponent, + FName InOverrideFolderPath, + AActor* InActor) +{ + if (!IsValid(InActor)) + { + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; + if (!Factory) + return false; + + OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform()); + } + else + { + OutActor = InActor; + } + + // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset + const FName BaseActorName(PackageParams.ObjectName); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName.ToString(), OutActor); + RenameAndRelabelActor(OutActor, NewNameStr, false); + OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); + + USplineComponent* DuplicatedSplineComponent = DuplicateObject( + InSplineComponent, + OutActor, + FName(MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), PackageParams.ObjectName))); + OutActor->AddInstanceComponent(DuplicatedSplineComponent); + const bool bCreateIfMissing = true; + USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); + DuplicatedSplineComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); + + // We duplicated the InSplineComponent, so we don't have to copy all of its properties, but we must set the + // world transform + DuplicatedSplineComponent->SetWorldTransform(InSplineComponent->GetComponentTransform()); + + FAssetRegistryModule::AssetCreated(DuplicatedSplineComponent); + DuplicatedSplineComponent->RegisterComponent(); + + OutSplineComponent = DuplicatedSplineComponent; + return true; +} + +bool +FHoudiniEngineBakeUtils::BakeCurve( + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + // const TArray& InAllBakedOutputs, + const FHoudiniPackageParams &PackageParams, + FHoudiniAttributeResolver& InResolver, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor, + const FString& InFallbackWorldOutlinerFolder) +{ + USplineComponent* SplineComponent = Cast(InOutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + return false; + + // By default spawn in the current level unless specified via the unreal_level_path attribute + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + bool bHasLevelPathAttribute = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_LEVEL_PATH); + if (bHasLevelPathAttribute) + { + UWorld* DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; + + // Get the package path from the unreal_level_apth attribute + FString LevelPackagePath = InResolver.ResolveFullLevelPath(); + + bool bCreatedPackage = false; + if (!FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + LevelPackagePath, + DesiredLevel, + DesiredWorld, + bCreatedPackage)) + { + // TODO: LOG ERROR IF NO LEVEL + return false; + } + + // If we have created a new level, add it to the packages to save + // TODO: ? always add? + if (bCreatedPackage && DesiredLevel) + { + // We can now save the package again, and unload it. + OutPackagesToSave.Add(DesiredLevel->GetOutermost()); + } + } + + if(!DesiredLevel) + return false; + + // Try to find the unreal_bake_actor, if specified, or fallback to the default named actor + FName BakeActorName; + AActor* FoundActor = nullptr; + bool bHasBakeActorName = false; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + return false; + + // If we are baking in replace mode, remove the previous bake component + if (bInReplaceAssets && !InBakedOutputObject.BakedComponent.IsEmpty()) + { + UActorComponent* PrevComponent = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (PrevComponent && PrevComponent->GetOwner() == FoundActor) + { + RemovePreviouslyBakedComponent(PrevComponent); + } + } + + FHoudiniPackageParams CurvePackageParams = PackageParams; + CurvePackageParams.ObjectName = BakeActorName.ToString(); + USplineComponent* NewSplineComponent = nullptr; + const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName)); + if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor)) + return false; + + InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); + InBakedOutputObject.BakedComponent = FSoftObjectPath(NewSplineComponent).ToString(); + + // If we are baking in replace mode, remove previously baked components/instancers + if (bInReplaceActors && bInReplaceAssets) + { + const bool bInDestroyBakedComponent = false; + const bool bInDestroyBakedInstancedActors = true; + const bool bInDestroyBakedInstancedComponents = true; + DestroyPreviousBakeOutput( + InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); + } + + FHoudiniEngineBakedActor Result; + Result.Actor = FoundActor; + Result.ActorBakeName = BakeActorName; + Result.BakeFolderPath = PackageParams.BakeFolder; + Result.BakedObjectPackageParams = PackageParams; + OutActors.Add(Result); + + return true; +} + +AActor* +FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PackageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform) +{ + if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + return nullptr; + + TArray & DisplayPoints = InHoudiniSplineComponent->DisplayPoints; + if (DisplayPoints.Num() < 2) + return nullptr; + + ULevel* DesiredLevel = GWorld->GetCurrentLevel(); + + UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()) : nullptr; + if (!Factory) + return nullptr; + + // Remove the actor if it exists + for (auto & Actor : DesiredLevel->Actors) + { + if (!Actor) + continue; + + if (Actor->GetName() == PackageParams.ObjectName) + { + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + Actor->RemoveFromRoot(); + Actor->ConditionalBeginDestroy(); + World->EditorDestroyActor(Actor, true); + + break; + } + } + + AActor* NewActor = Factory->CreateActor(nullptr, DesiredLevel, InHoudiniSplineComponent->GetComponentTransform()); + + USplineComponent* BakedUnrealSplineComponent = NewObject(NewActor); + if (!BakedUnrealSplineComponent) + return nullptr; + + // add display points to created unreal spline component + for (int32 n = 0; n < DisplayPoints.Num(); ++n) + { + FVector & NextPoint = DisplayPoints[n]; + BakedUnrealSplineComponent->AddSplinePoint(NextPoint, ESplineCoordinateSpace::Local); + // Set the curve point type to be linear, since we are using display points + BakedUnrealSplineComponent->SetSplinePointType(n, ESplinePointType::Linear); + } + NewActor->AddInstanceComponent(BakedUnrealSplineComponent); + + BakedUnrealSplineComponent->AttachToComponent(NewActor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); + + FAssetRegistryModule::AssetCreated(NewActor); + FAssetRegistryModule::AssetCreated(BakedUnrealSplineComponent); + BakedUnrealSplineComponent->RegisterComponent(); + + // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, Factory->NewActorClass, *(PackageParams.ObjectName), NewActor); + RenameAndRelabelActor(NewActor, NewNameStr, false); + NewActor->SetFolderPath(FName(PackageParams.HoudiniAssetName)); + + return NewActor; +} + +UBlueprint* +FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PackageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform) +{ + if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + return nullptr; + + FGuid BakeGUID = FGuid::NewGuid(); + + if (!BakeGUID.IsValid()) + BakeGUID = FGuid::NewGuid(); + + // We only want half of generated guid string. + FString BakeGUIDString = BakeGUID.ToString().Left(FHoudiniEngineUtils::PackageGUIDItemNameLength); + + // Generate Blueprint name. + FString BlueprintName = PackageParams.ObjectName + "_BP"; + + // Generate unique package name. + FString PackageName = PackageParams.BakeFolder + "/" + BlueprintName; + PackageName = UPackageTools::SanitizePackageName(PackageName); + + // See if package exists, if it does, we need to regenerate the name. + UPackage * Package = FindPackage(nullptr, *PackageName); + + if (Package && !Package->IsPendingKill()) + { + // Package does exist, there's a collision, we need to generate a new name. + BakeGUID.Invalidate(); + } + else + { + // Create actual package. + Package = CreatePackage(*PackageName); + } + + AActor * CreatedHoudiniSplineActor = FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + InHoudiniSplineComponent, PackageParams, WorldToSpawn, SpawnTransform); + + TArray PackagesToSave; + + UBlueprint * Blueprint = nullptr; + if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill()) + { + + UObject* Asset = nullptr; + + Asset = StaticFindObjectFast(UObject::StaticClass(), Package, FName(*BlueprintName)); + if (!Asset) + { + UBlueprintFactory* Factory = NewObject(); + + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + Asset = AssetToolsModule.Get().CreateAsset( + BlueprintName, PackageParams.BakeFolder, + UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + } + + TArray Components; + for (auto & Next : CreatedHoudiniSplineActor->GetComponents()) + { + Components.Add(Next); + } + + Blueprint = Cast(Asset); + + // Clear old Blueprint Node tree + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + + int32 NodeSize = SCS->GetAllNodes().Num(); + for (int32 n = NodeSize - 1; n >= 0; --n) + SCS->RemoveNode(SCS->GetAllNodes()[n]); + + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components); + + CreatedHoudiniSplineActor->RemoveFromRoot(); + CreatedHoudiniSplineActor->ConditionalBeginDestroy(); + + GWorld->EditorDestroyActor(CreatedHoudiniSplineActor, true); + + Package->MarkPackageDirty(); + PackagesToSave.Add(Package); + } + + // Save the created BP package. + FHoudiniEngineBakeUtils::SaveBakedPackages + (PackagesToSave); + + return Blueprint; +} + + +void +FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const TCHAR * Key, + const TCHAR * Value) +{ + if (!Package || Package->IsPendingKill()) + return; + + UMetaData * MetaData = Package->GetMetaData(); + if (MetaData && !MetaData->IsPendingKill()) + MetaData->SetValue(Object, Key, Value); +} + + +bool +FHoudiniEngineBakeUtils:: +GetHoudiniGeneratedNameFromMetaInformation( + UPackage * Package, UObject * Object, FString & HoudiniName) +{ + if (!Package || Package->IsPendingKill()) + return false; + + UMetaData * MetaData = Package->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return false; + + if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + { + // Retrieve name used for package generation. + const FString NameFull = MetaData->GetValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME); + + //HoudiniName = NameFull.Left(FMath::Min(NameFull.Len(), FHoudiniEngineUtils::PackageGUIDItemNameLength)); + HoudiniName = NameFull; + return true; + } + + return false; +} + +UMaterialInterface * +FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + UMaterialInterface * Material, UMaterialInterface* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, + TArray & OutGeneratedPackages, + TMap& InOutAlreadyBakedMaterialsMap) +{ + if (InOutAlreadyBakedMaterialsMap.Contains(Material)) + { + return InOutAlreadyBakedMaterialsMap[Material]; + } + + UMaterialInterface * DuplicatedMaterial = nullptr; + + FString CreatedMaterialName; + // Create material package. Use the same package params as static mesh, but with the material's name + FHoudiniPackageParams MaterialPackageParams = ObjectPackageParams; + MaterialPackageParams.ObjectName = MaterialName; + + // Check if there is a valid previous material. If so, get the bake counter for consistency in + // replace or iterative package naming + bool bIsPreviousBakeMaterialValid = IsValid(PreviousBakeMaterial); + int32 BakeCounter = 0; + TArray PreviousBakeMaterialExpressions; + + + if (bIsPreviousBakeMaterialValid && PreviousBakeMaterial->IsA(UMaterial::StaticClass())) + { + UMaterial * PreviousMaterialCast = Cast(PreviousBakeMaterial); + bIsPreviousBakeMaterialValid = MaterialPackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeMaterial); + + if (bIsPreviousBakeMaterialValid && PreviousMaterialCast) + { + MaterialPackageParams.GetBakeCounterFromBakedAsset(PreviousBakeMaterial, BakeCounter); + + PreviousBakeMaterialExpressions = PreviousMaterialCast->Expressions; + } + } + + UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter); + + if (!MaterialPackage || MaterialPackage->IsPendingKill()) + return nullptr; + + // Clone material. + DuplicatedMaterial = DuplicateObject< UMaterialInterface >(Material, MaterialPackage, *CreatedMaterialName); + if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, DuplicatedMaterial, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + MaterialPackage, DuplicatedMaterial, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedMaterialName); + + // Retrieve and check various sampling expressions. If they contain textures, duplicate (and bake) them. + UMaterial * DuplicatedMaterialCast = Cast(DuplicatedMaterial); + if (DuplicatedMaterialCast) + { + const int32 NumExpressions = DuplicatedMaterialCast->Expressions.Num(); + for (int32 ExpressionIdx = 0; ExpressionIdx < NumExpressions; ++ExpressionIdx) + { + UMaterialExpression* Expression = DuplicatedMaterialCast->Expressions[ExpressionIdx]; + UMaterialExpression* PreviousBakeExpression = nullptr; + if (bIsPreviousBakeMaterialValid && PreviousBakeMaterialExpressions.IsValidIndex(ExpressionIdx)) + { + PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; + } + FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages); + } + } + + // Notify registry that we have created a new duplicate material. + FAssetRegistryModule::AssetCreated(DuplicatedMaterial); + + // Dirty the material package. + DuplicatedMaterial->MarkPackageDirty(); + + // Recompile the baked material + // DuplicatedMaterial->ForceRecompileForRendering(); + // Use UMaterialEditingLibrary::RecompileMaterial since it correctly updates texture references in the material + // which ForceRecompileForRendering does not do + if (DuplicatedMaterialCast) + { + UMaterialEditingLibrary::RecompileMaterial(DuplicatedMaterialCast); + } + + OutGeneratedPackages.Add(MaterialPackage); + + InOutAlreadyBakedMaterialsMap.Add(Material, DuplicatedMaterial); + + return DuplicatedMaterial; +} + +void +FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( + UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, + const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages) +{ + UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression); + if (!TextureSample || TextureSample->IsPendingKill()) + return; + + UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture); + if (!Texture || Texture->IsPendingKill()) + return; + + UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); + if (!TexturePackage || TexturePackage->IsPendingKill()) + return; + + // Try to get the previous bake's texture + UTexture2D* PreviousBakeTexture = nullptr; + if (IsValid(PreviousBakeMaterialExpression)) + { + UMaterialExpressionTextureSample* PreviousBakeTextureSample = Cast< UMaterialExpressionTextureSample >(PreviousBakeMaterialExpression); + if (IsValid(PreviousBakeTextureSample)) + PreviousBakeTexture = Cast< UTexture2D >(PreviousBakeTextureSample->Texture); + } + + FString GeneratedTextureName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( + TexturePackage, Texture, GeneratedTextureName)) + { + // Duplicate texture. + UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( + Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages); + + // Re-assign generated texture. + TextureSample->Texture = DuplicatedTexture; + } +} + +UTexture2D * +FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( + UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages) +{ + UTexture2D* DuplicatedTexture = nullptr; +#if WITH_EDITOR + // Retrieve original package of this texture. + UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); + if (!TexturePackage || TexturePackage->IsPendingKill()) + return nullptr; + + FString GeneratedTextureName; + if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName)) + { + UMetaData * MetaData = TexturePackage->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return nullptr; + + // Retrieve texture type. + const FString & TextureType = + MetaData->GetValue(Texture, HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE); + + FString CreatedTextureName; + + // Create texture package. Use the same package params as material's, but with object name appended by generated texture's name + FHoudiniPackageParams TexturePackageParams = PackageParams; + TexturePackageParams.ObjectName = TexturePackageParams.ObjectName + "_" + GeneratedTextureName; + + // Determine the bake counter of the previous bake's texture (if exists/valid) for naming consistency when + // replacing/iterating + bool bIsPreviousBakeTextureValid = IsValid(PreviousBakeTexture); + int32 BakeCounter = 0; + if (bIsPreviousBakeTextureValid) + { + bIsPreviousBakeTextureValid = TexturePackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBakeTexture); + if (bIsPreviousBakeTextureValid) + { + TexturePackageParams.GetBakeCounterFromBakedAsset(PreviousBakeTexture, BakeCounter); + } + } + + UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter); + + if (!NewTexturePackage || NewTexturePackage->IsPendingKill()) + return nullptr; + + // Clone texture. + DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName); + if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill()) + return nullptr; + + // Add meta information. + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *CreatedTextureName); + FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( + NewTexturePackage, DuplicatedTexture, + HAPI_UNREAL_PACKAGE_META_GENERATED_TEXTURE_TYPE, *TextureType); + + // Notify registry that we have created a new duplicate texture. + FAssetRegistryModule::AssetCreated(DuplicatedTexture); + + // Dirty the texture package. + DuplicatedTexture->MarkPackageDirty(); + + OutCreatedPackages.Add(NewTexturePackage); + } +#endif + return DuplicatedTexture; +} + + +bool +FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return false; + + AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); + + if (!ActorOwner || ActorOwner->IsPendingKill()) + return false; + + UWorld* World = ActorOwner->GetWorld(); + if (!World) + World = GWorld; + + World->EditorDestroyActor(ActorOwner, false); + + return true; +} + + +void +FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld) +{ + UWorld * CurrentWorld = nullptr; + if (bSaveCurrentWorld && GEditor) + CurrentWorld = GEditor->GetEditorWorldContext().World(); + + if (CurrentWorld) + { + // Save the current map + FString CurrentWorldPath = FPaths::GetBaseFilename(CurrentWorld->GetPathName(), false); + UPackage* CurrentWorldPackage = CreatePackage(*CurrentWorldPath); + + if (CurrentWorldPackage) + { + CurrentWorldPackage->MarkPackageDirty(); + PackagesToSave.Add(CurrentWorldPackage); + } + } + + FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, true, false); +} + +bool +FHoudiniEngineBakeUtils::FindOutputObject(const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) +{ + if (!InObjectToFind || InObjectToFind->IsPendingKill()) + return false; + + const int32 NumOutputs = InOutputs.Num(); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + const UHoudiniOutput* CurOutput = InOutputs[OutputIdx]; + if (!IsValid(CurOutput)) + continue; + + if (CurOutput->GetType() != InOutputType) + continue; + + for (auto& CurOutputObject : CurOutput->GetOutputObjects()) + { + if (CurOutputObject.Value.OutputObject == InObjectToFind + || CurOutputObject.Value.OutputComponent == InObjectToFind + || CurOutputObject.Value.ProxyObject == InObjectToFind + || CurOutputObject.Value.ProxyComponent == InObjectToFind) + { + OutOutputIndex = OutputIdx; + OutIdentifier = CurOutputObject.Key; + return true; + } + } + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + FString TempPath = FString(); + + // TODO: Get the HAC outputs in a better way? + TArray Outputs; + if (InHAC && !InHAC->IsPendingKill()) + { + const int32 NumOutputs = InHAC->GetNumOutputs(); + Outputs.Reserve(NumOutputs); + for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) + { + Outputs.Add(InHAC->GetOutputAt(OutputIdx)); + } + + TempPath = InHAC->TemporaryCookFolder.Path; + } + + return IsObjectTemporary(InObject, InOutputType, Outputs, TempPath); +} + +bool FHoudiniEngineBakeUtils::IsObjectInTempFolder(UObject* const InObject, const FString& InTemporaryCookFolder) +{ + if (!IsValid(InObject)) + return false; + + // Check the package path for this object + // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated + UPackage* ObjectPackage = InObject->GetOutermost(); + if (ObjectPackage && !ObjectPackage->IsPendingKill()) + { + const FString PathName = ObjectPackage->GetPathName(); + if (PathName.StartsWith(InTemporaryCookFolder)) + return true; + + // Also check the default temp folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (PathName.StartsWith(HoudiniRuntimeSettings->DefaultTemporaryCookFolder)) + return true; + } + + return false; +} + +bool FHoudiniEngineBakeUtils::IsObjectTemporary( + UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + int32 ParentOutputIndex = -1; + FHoudiniOutputObjectIdentifier Identifier; + if (FindOutputObject(InObject, InOutputType, InParentOutputs, ParentOutputIndex, Identifier)) + return true; + + // Check the package path for this object + // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated + if (IsObjectInTempFolder(InObject, InTemporaryCookFolder)) + return true; + + /* + UPackage* ObjectPackage = InObject->GetOutermost(); + if (ObjectPackage && !ObjectPackage->IsPendingKill()) + { + // TODO: this just indicates that the object was generated by H + // it could as well have been baked before... + // we should probably add a "temp" metadata + // Look in the meta info as well?? + UMetaData * MetaData = ObjectPackage->GetMetaData(); + if (!MetaData || MetaData->IsPendingKill()) + return false; + + if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) + return true; + } + */ + + return false; +} + +void +FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent( + AActor* NewActor, + UStaticMeshComponent* NewSMC, + UStaticMeshComponent* InSMC, + bool bInCopyWorldTransform) +{ + if (!NewSMC || NewSMC->IsPendingKill()) + return; + + if (!InSMC || InSMC->IsPendingKill()) + return; + + // Copy properties to new actor + //UStaticMeshComponent* OtherSMC_NonConst = const_cast(StaticMeshComponent); + NewSMC->SetCollisionProfileName(InSMC->GetCollisionProfileName()); + NewSMC->SetCollisionEnabled(InSMC->GetCollisionEnabled()); + NewSMC->LightmassSettings = InSMC->LightmassSettings; + NewSMC->CastShadow = InSMC->CastShadow; + NewSMC->SetMobility(InSMC->Mobility); + + UBodySetup* InBodySetup = InSMC->GetBodySetup(); + UBodySetup* NewBodySetup = NewSMC->GetBodySetup(); + if (InBodySetup && NewBodySetup) + { + // Copy the BodySetup + NewBodySetup->CopyBodyPropertiesFrom(InBodySetup); + + // We need to recreate the physics mesh for the new body setup + NewBodySetup->InvalidatePhysicsData(); + NewBodySetup->CreatePhysicsMeshes(); + + // Only copy the physical material if it's different from the default one, + // As this was causing crashes on BakeToActors in some cases + if (GEngine != NULL && NewBodySetup->GetPhysMaterial() != GEngine->DefaultPhysMaterial) + NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial()); + } + + if (NewActor && !NewActor->IsPendingKill()) + NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame); + + NewSMC->SetVisibility(InSMC->IsVisible()); + + // TODO: + // // Reapply the uproperties modified by attributes on the new component + // FHoudiniEngineUtils::UpdateAllPropertyAttributesOnObject(InSMC, InHGPO); + + // The below code is from EditorUtilities::CopyActorProperties and modified to only copy from one component to another + UClass* ComponentClass = nullptr; + if (InSMC->GetClass()->IsChildOf(NewSMC->GetClass())) + { + ComponentClass = NewSMC->GetClass(); + } + else if (NewSMC->GetClass()->IsChildOf(InSMC->GetClass())) + { + ComponentClass = InSMC->GetClass(); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Incompatible component classes in CopyPropertyToNewActorAndComponent: %s vs %s"), + *(InSMC->GetName()), + *(NewSMC->GetClass()->GetName())); + + NewSMC->PostEditChange(); + return; + } + + TSet SourceUCSModifiedProperties; + InSMC->GetUCSModifiedProperties(SourceUCSModifiedProperties); + + AActor* SourceActor = InSMC->GetOwner(); + if (!IsValid(SourceActor)) + { + NewSMC->PostEditChange(); + return; + } + + TArray ModifiedObjects; + const EditorUtilities::FCopyOptions Options(EditorUtilities::ECopyOptions::CallPostEditChangeProperty); + // Copy component properties + for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) + { + const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); + const bool bIsIdentical = Property->Identical_InContainer( InSMC, NewSMC ); + const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); + const bool bIsTransform = + Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); + + // auto SourceComponentIsRoot = [&]() + // { + // USceneComponent* RootComponent = SourceActor->GetRootComponent(); + // if (InSMC == RootComponent) + // { + // return true; + // } + // return false; + // }; + + // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + // && ( !bIsTransform || !SourceComponentIsRoot() ) ) + if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + && !bIsTransform ) + { + // const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) + // && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); + const bool bIsSafeToCopy = true; + if( bIsSafeToCopy ) + { + if (!Options.CanCopyProperty(*Property, *SourceActor)) + { + continue; + } + + if( !ModifiedObjects.Contains(NewSMC) ) + { + NewSMC->SetFlags(RF_Transactional); + NewSMC->Modify(); + ModifiedObjects.Add(NewSMC); + } + + if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) + { + // @todo simulate: Should we be calling this on the component instead? + NewActor->PreEditChange( Property ); + } + + EditorUtilities::CopySingleProperty(InSMC, NewSMC, Property); + + if (Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty) + { + FPropertyChangedEvent PropertyChangedEvent( Property ); + NewActor->PostEditChangeProperty( PropertyChangedEvent ); + } + } + } + } + + if (bInCopyWorldTransform) + { + NewSMC->SetWorldTransform(InSMC->GetComponentTransform()); + } + + NewSMC->PostEditChange(); +}; + +bool +FHoudiniEngineBakeUtils::RemovePreviouslyBakedActor( + AActor* InNewBakedActor, + ULevel* InLevel, + const FHoudiniPackageParams& InPackageParams) +{ + // Remove a previous bake actor if it exists + for (auto & Actor : InLevel->Actors) + { + if (!Actor) + continue; + + if (Actor != InNewBakedActor && Actor->GetName() == InPackageParams.ObjectName) + { + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + Actor->RemoveFromRoot(); + Actor->ConditionalBeginDestroy(); + World->EditorDestroyActor(Actor, true); + + return true; + } + } + + return false; +} + +bool +FHoudiniEngineBakeUtils::RemovePreviouslyBakedComponent(UActorComponent* InComponent) +{ + if (!IsValid(InComponent)) + return false; + + // Remove from its actor first + if (InComponent->GetOwner()) + InComponent->GetOwner()->RemoveOwnedComponent(InComponent); + + // Detach from its parent component if attached + USceneComponent* SceneComponent = Cast(InComponent); + if (IsValid(SceneComponent)) + SceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + InComponent->UnregisterComponent(); + InComponent->DestroyComponent(); + + return true; +} + +FName +FHoudiniEngineBakeUtils::GetOutputFolderPath(UObject* InOutputOwner) +{ + // Get an output folder path for PDG outputs generated by InOutputOwner. + // The folder path is: / + FString FolderName; + FName FolderDirName; + AActor* OuterActor = Cast(InOutputOwner); + if (OuterActor) + { + FolderName = OuterActor->GetActorLabel(); + FolderDirName = OuterActor->GetFolderPath(); + } + else + { + FolderName = InOutputOwner->GetName(); + } + if (!FolderDirName.IsNone()) + return FName(FString::Printf(TEXT("%s/%s"), *FolderDirName.ToString(), *FolderName)); + else + return FName(FolderName); +} + +void +FHoudiniEngineBakeUtils::RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique) +{ + FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); + + const FSoftObjectPath OldPath = FSoftObjectPath(InAsset); + + FString NewName; + if (bMakeUniqueIfNotUnique) + NewName = MakeUniqueObjectNameIfNeeded(InAsset->GetPackage(), InAsset->GetClass(), InNewName, InAsset); + else + NewName = InNewName; + + FHoudiniEngineUtils::RenameObject(InAsset, *NewName); + + const FSoftObjectPath NewPath = FSoftObjectPath(InAsset); + if (OldPath != NewPath) + { + TArray RenameData; + RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); + AssetToolsModule.Get().RenameAssets(RenameData); + } +} + +void +FHoudiniEngineBakeUtils::RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique) +{ + if (!IsValid(InActor)) + return; + + FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked(TEXT("AssetTools")); + + const FSoftObjectPath OldPath = FSoftObjectPath(InActor); + + FString NewName; + if (bMakeUniqueIfNotUnique) + NewName = MakeUniqueObjectNameIfNeeded(InActor->GetOuter(), InActor->GetClass(), InNewName, InActor); + else + NewName = InNewName; + + FHoudiniEngineUtils::RenameObject(InActor, *NewName); + FHoudiniEngineRuntimeUtils::SetActorLabel(InActor, NewName); + + const FSoftObjectPath NewPath = FSoftObjectPath(InActor); + if (OldPath != NewPath) + { + TArray RenameData; + RenameData.Add(FAssetRenameData(OldPath, NewPath, true)); + AssetToolsModule.Get().RenameAssets(RenameData); + } +} + +bool +FHoudiniEngineBakeUtils::DetachAndRenameBakedPDGOutputActor( + AActor* InActor, + const FString& InNewName, + const FName& InFolderPath) +{ + if (!IsValid(InActor)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: InActor is null.")); + return false; + } + + if (InNewName.TrimStartAndEnd().IsEmpty()) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineUtils::DetachAndRenameBakedPDGOutputActor]: A valid actor name must be specified.")); + return false; + } + + // Detach from parent + InActor->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + // Rename + const bool bMakeUniqueIfNotUnique = true; + RenameAndRelabelActor(InActor, InNewName, bMakeUniqueIfNotUnique); + + InActor->SetFolderPath(InFolderPath); + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultArrayIndex, + int32 InWorkResultObjectArrayIndex, + bool bInReplaceActors, + bool bInReplaceAssets, + bool bInBakeToWorkResultActor, + bool bInIsAutoBake, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake, + TArray const* InInstancerComponentTypesToBake, + const FString& InFallbackWorldOutlinerFolder) +{ + if (!IsValid(InPDGAssetLink)) + return false; + + if (!IsValid(InNode)) + return false; + + if (!InNode->WorkResult.IsValidIndex(InWorkResultArrayIndex)) + return false; + + FTOPWorkResult& WorkResult = InNode->WorkResult[InWorkResultArrayIndex]; + if (!WorkResult.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) + return false; + + FTOPWorkResultObject& WorkResultObject = WorkResult.ResultObjects[InWorkResultObjectArrayIndex]; + TArray& Outputs = WorkResultObject.GetResultOutputs(); + if (Outputs.Num() == 0) + return true; + + if (WorkResultObject.State != EPDGWorkResultState::Loaded) + { + if (bInIsAutoBake && WorkResultObject.AutoBakedSinceLastLoad()) + { + HOUDINI_LOG_MESSAGE(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded but was auto-baked since its last load."), *WorkResultObject.Name); + return true; + } + + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObject (%s) is not loaded, cannot bake it."), *WorkResultObject.Name); + return false; + } + + AActor* WorkResultObjectActor = WorkResultObject.GetOutputActorOwner().GetOutputActor(); + if (!IsValid(WorkResultObjectActor)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors]: WorkResultObjectActor (%s) is null (unexpected since # Outputs > 0)"), *WorkResultObject.Name); + return false; + } + + // OutBakedActors contains each actor that contains baked PDG results. Actors may + // appear in the array more than once if they have more than one baked result/component associated with + // them + // TArray BakedActorsForWorkResultObject; + const int32 NumBakedPre = OutBakedActors.Num(); + const FString HoudiniAssetName(WorkResultObject.Name); + + // Find the previous bake output for this work result object + FString Key; + InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key); + FHoudiniPDGWorkResultObjectBakedOutput& BakedOutputContainer = InNode->GetBakedWorkResultObjectsOutputs().FindOrAdd(Key); + + const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InPDGAssetLink); + check(IsValid(HoudiniAssetComponent)); + + BakeHoudiniOutputsToActors( + HoudiniAssetComponent, + Outputs, + BakedOutputContainer.BakedOutputs, + HoudiniAssetName, + WorkResultObjectActor->GetActorTransform(), + InPDGAssetLink->BakeFolder, + InPDGAssetLink->GetTemporaryCookFolder(), + bInReplaceActors, + bInReplaceAssets, + OutBakedActors, + OutPackagesToSave, + OutBakeStats, + InOutputTypesToBake, + InInstancerComponentTypesToBake, + bInBakeToWorkResultActor ? WorkResultObjectActor : nullptr, + InFallbackWorldOutlinerFolder); + + // Set the PDG indices on the output baked actor entries + FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); + AActor* const WROActor = OutputActorOwner.GetOutputActor(); + FHoudiniEngineBakedActor const * BakedWROActorEntry = nullptr; + const int32 NumBakedPost = OutBakedActors.Num(); + if (NumBakedPost > NumBakedPre) + { + for (int32 Index = FMath::Max(0, NumBakedPre); Index < NumBakedPost; ++Index) + { + FHoudiniEngineBakedActor& BakedActorEntry = OutBakedActors[Index]; + BakedActorEntry.PDGWorkResultArrayIndex = InWorkResultArrayIndex; + BakedActorEntry.PDGWorkItemIndex = WorkResult.WorkItemIndex; + BakedActorEntry.PDGWorkResultObjectArrayIndex = InWorkResultObjectArrayIndex; + + if (WROActor && BakedActorEntry.Actor == WROActor) + { + BakedWROActorEntry = &BakedActorEntry; + } + } + } + + // If anything was baked to WorkResultObjectActor, detach it from its parent + if (bInBakeToWorkResultActor) + { + // if we re-used the temp actor as a bake actor, then remove its temp outputs + WorkResultObject.DestroyResultOutputs(); + if (WROActor) + { + if (BakedWROActorEntry) + { + OutputActorOwner.SetOutputActor(nullptr); + const FString OldActorPath = FSoftObjectPath(WROActor).ToString(); + DetachAndRenameBakedPDGOutputActor( + WROActor, BakedWROActorEntry->ActorBakeName.ToString(), BakedWROActorEntry->WorldOutlinerFolder); + const FString NewActorPath = FSoftObjectPath(WROActor).ToString(); + if (OldActorPath != NewActorPath) + { + // Fix cached string reference in baked outputs to WROActor + for (FHoudiniBakedOutput& BakedOutput : BakedOutputContainer.BakedOutputs) + { + for (auto& Entry : BakedOutput.BakedOutputObjects) + { + if (Entry.Value.Actor == OldActorPath) + Entry.Value.Actor = NewActorPath; + } + } + } + } + else + { + OutputActorOwner.DestroyOutputActor(); + } + } + } + + if (bInIsAutoBake) + WorkResultObject.SetAutoBakedSinceLastLoad(true); + // OutBakedActors.Append(BakedActorsForWorkResultObject); + return true; +} + +void +FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkItemHAPIIndex, + int32 InWorkItemResultInfoIndex) +{ + if (!IsValid(InPDGAssetLink)) + return; + + if (!InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded) + return; + + if (!IsValid(InNode)) + return; + + // Check if the node is ready for baking: all work items must be complete + bool bDoNotBake = false; + bool bPendingBakeItems = false; + if (!InNode->AreAllWorkItemsComplete() || InNode->AnyWorkItemsFailed()) + bDoNotBake = true; + + // Check if the node is ready for baking: all work items must be loaded + if (!bDoNotBake) + { + for (const FTOPWorkResult& WorkResult : InNode->WorkResult) + { + for (const FTOPWorkResultObject& WRO : WorkResult.ResultObjects) + { + if (WRO.State != EPDGWorkResultState::Loaded && !WRO.AutoBakedSinceLastLoad()) + { + bDoNotBake = true; + break; + } + } + if (bDoNotBake) + break; + } + } + + if (!bDoNotBake) + { + // Check which outputs are selected for baking: selected node, selected network or all + // And only bake if the node falls within the criteria + UTOPNetwork const * const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + UTOPNode const * const SelectedTOPNode = InPDGAssetLink->GetSelectedTOPNode(); + switch (InPDGAssetLink->PDGBakeSelectionOption) + { + case EPDGBakeSelectionOption::SelectedNetwork: + if (!IsValid(SelectedTOPNetwork) || !InNode->IsParentTOPNetwork(SelectedTOPNetwork)) + { + HOUDINI_LOG_WARNING( + TEXT("Not baking Node %s (Net %s): not in selected network"), + InNode ? *InNode->GetName() : TEXT(""), + SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); + bDoNotBake = true; + } + break; + case EPDGBakeSelectionOption::SelectedNode: + if (InNode != SelectedTOPNode) + { + HOUDINI_LOG_WARNING( + TEXT("Not baking Node %s (Net %s): not the selected node"), + InNode ? *InNode->GetName() : TEXT(""), + SelectedTOPNetwork ? *SelectedTOPNetwork->GetName() : TEXT("")); + bDoNotBake = true; + } + break; + case EPDGBakeSelectionOption::All: + default: + break; + } + } + + // If there are no nodes left to auto-bake, broadcast the onpostbake delegate + if (bDoNotBake && !InPDGAssetLink->AnyRemainingAutoBakeNodes()) + InPDGAssetLink->HandleOnPostBake(true); + + if (bDoNotBake) + return; + + bool bSuccess = false; + const bool bIsAutoBake = true; + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + bSuccess = FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InNode, bIsAutoBake, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + bSuccess = FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(InPDGAssetLink, InNode, bIsAutoBake, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); + break; + + default: + HOUDINI_LOG_WARNING(TEXT("Unsupported HoudiniEngineBakeOption %i"), InPDGAssetLink->HoudiniEngineBakeOption); + } + + if (!InPDGAssetLink->AnyRemainingAutoBakeNodes()) + InPDGAssetLink->HandleOnPostBake(bSuccess); +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNode)) + return false; + + // Determine the output world outliner folder path via the PDG asset link's + // owner's folder path and name + UObject* PDGOwner = InPDGAssetLink->GetOwnerActor(); + if (!PDGOwner) + PDGOwner = InPDGAssetLink->GetOuter(); + const FName& FallbackWorldOutlinerFolderPath = GetOutputFolderPath(PDGOwner); + + // Determine the actor/package replacement settings + const bool bReplaceActors = !bInBakeForBlueprint && InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + const bool bReplaceAssets = InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + // Determine the output types to bake: don't bake landscapes in blueprint baking mode + TArray OutputTypesToBake; + TArray InstancerComponentTypesToBake; + if (bInBakeForBlueprint) + { + OutputTypesToBake.Add(EHoudiniOutputType::Mesh); + OutputTypesToBake.Add(EHoudiniOutputType::Instancer); + OutputTypesToBake.Add(EHoudiniOutputType::Curve); + + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::StaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::InstancedStaticMeshComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::MeshSplitInstancerComponent); + InstancerComponentTypesToBake.Add(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent); + } + + const int32 NumWorkResults = InNode->WorkResult.Num(); + FScopedSlowTask Progress(NumWorkResults, FText::FromString(FString::Printf(TEXT("Baking PDG Node Output %s ..."), *InNode->GetName()))); + Progress.MakeDialog(); + for (int32 WorkResultArrayIdx = 0; WorkResultArrayIdx < NumWorkResults; ++WorkResultArrayIdx) + { + FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultArrayIdx]; + const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); + for (int32 WorkResultObjectArrayIdx = 0; WorkResultObjectArrayIdx < NumWorkResultObjects; ++WorkResultObjectArrayIdx) + { + Progress.EnterProgressFrame(1.0f); + + BakePDGWorkResultObject( + InPDGAssetLink, + InNode, + WorkResultArrayIdx, + WorkResultObjectArrayIdx, + bReplaceActors, + bReplaceAssets, + !bInBakeForBlueprint, + bInIsAutoBake, + OutBakedActors, + OutPackagesToSave, + OutBakeStats, + OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, + InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, + FallbackWorldOutlinerFolderPath.ToString() + ); + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) +{ + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + TArray BakedActors; + + const bool bBakeBlueprints = false; + + bool bSuccess = BakePDGTOPNodeOutputsKeepActors( + InPDGAssetLink, InTOPNode, bBakeBlueprints, bInIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); + + SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && BakedActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : BakedActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && BakedActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + TArray& BakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNetwork)) + return false; + + bool bSuccess = true; + for (UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bInBakeForBlueprint, bInIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, OutPackagesToSave, OutBakeStats); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + TArray BakedActors; + + const bool bBakeBlueprints = false; + const bool bIsAutoBake = false; + + bool bSuccess = true; + switch(InBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) + { + if (!IsValid(Network)) + continue; + + for (UTOPNode* Node : Network->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, Node, bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); + } + } + break; + case EPDGBakeSelectionOption::SelectedNetwork: + bSuccess = BakePDGTOPNetworkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNetwork(), bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); + case EPDGBakeSelectionOption::SelectedNode: + bSuccess = BakePDGTOPNodeOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->GetSelectedTOPNode(), bBakeBlueprints, bIsAutoBake, InPDGBakePackageReplaceMode, BakedActors, PackagesToSave, BakeStats); + } + + SaveBakedPackages(PackagesToSave); + + // Recenter and select the baked actors + if (GEditor && BakedActors.Num() > 0) + GEditor->SelectNone(false, true); + + for (const FHoudiniEngineBakedActor& Entry : BakedActors) + { + if (!IsValid(Entry.Actor)) + continue; + + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Entry.Actor); + + if (GEditor) + GEditor->SelectActor(Entry.Actor, true, false); + } + + if (GEditor && BakedActors.Num() > 0) + GEditor->NoteSelectionChange(); + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + // Broadcast that the bake is complete + InPDGAssetLink->HandleOnPostBake(bSuccess); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( + const TArray& InBakedActors, + bool bInRecenterBakedActors, + bool bInReplaceAssets, + const FString& InAssetName, + const FDirectoryPath& InBakeFolder, + TArray* const InNonPDGBakedOutputs, + TMap* const InPDGBakedOutputs, + TArray& OutBlueprints, + TArray& OutPackagesToSave) +{ + // // Clear selection + // if (GEditor) + // { + // GEditor->SelectNone(false, true); + // GEditor->NoteSelectionChange(); + // } + + // Iterate over the baked actors. An actor might appear multiple times if multiple OutputComponents were + // baked to the same actor, so keep track of actors we have already processed in BakedActorSet + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem); + TArray AssetsToReOpenEditors; + TSet BakedActorSet; + + for (const FHoudiniEngineBakedActor& Entry : InBakedActors) + { + AActor *Actor = Entry.Actor; + + if (!Actor || Actor->IsPendingKill()) + continue; + + if (BakedActorSet.Contains(Actor)) + continue; + + BakedActorSet.Add(Actor); + + UObject* Asset = nullptr; + + // Recenter the actor to its bounding box center + if (bInRecenterBakedActors) + CenterActorToBoundingBoxCenter(Actor); + + // Create package for out Blueprint + FString BlueprintName; + + // For instancers we determine the bake folder from the instancer, + // for everything else we use the baked object's bake folder + // If all of that is blank, we fall back to InBakeFolder. + FString BakeFolderPath; + if (Entry.bInstancerOutput) + BakeFolderPath = Entry.InstancerPackageParams.BakeFolder; + else + BakeFolderPath = Entry.BakeFolderPath; + if (BakeFolderPath.IsEmpty()) + BakeFolderPath = InBakeFolder.Path; + + FHoudiniPackageParams PackageParams; + // Set the replace mode based on if we are doing a replacement or incremental asset bake + const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? + EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( + PackageParams, + FHoudiniOutputObjectIdentifier(), + BakeFolderPath, + Entry.ActorBakeName.ToString() + "_BP", + InAssetName, + AssetPackageReplaceMode); + + // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment + // is consistent with the bake counter + int32 BakeCounter = 0; + UBlueprint* InPreviousBlueprint = nullptr; + FHoudiniBakedOutputObject* BakedOutputObject = nullptr; + FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; + // Get the baked output object + if (Entry.PDGWorkResultArrayIndex >= 0 && Entry.PDGWorkItemIndex >= 0 && Entry.PDGWorkResultObjectArrayIndex >= 0 && InPDGBakedOutputs) + { + const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkItemIndex, Entry.PDGWorkResultObjectArrayIndex); + WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); + if (WorkResultObjectBakedOutput) + { + if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + } + else if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs) + { + if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs->IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = (*InNonPDGBakedOutputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + if (BakedOutputObject) + { + InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); + if (IsValid(InPreviousBlueprint)) + { + if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint)) + { + PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter); + } + } + } + + UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter); + + if (!Package || Package->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName())); + continue; + } + + if (!Package->IsFullyLoaded()) + Package->FullyLoad(); + + //Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(*BlueprintName, Package, Actor, false); + // Find existing asset first (only relevant if we are in replacement mode). If the existing asset has a + // different base class than the incoming actor, we reparent the blueprint to the new base class before + // clearing the SCS graph and repopulating it from the temp actor. + Asset = StaticFindObjectFast(UBlueprint::StaticClass(), Package, FName(*BlueprintName)); + if (IsValid(Asset)) + { + UBlueprint* Blueprint = Cast(Asset); + if (IsValid(Blueprint)) + { + if (Blueprint->GeneratedClass && Blueprint->GeneratedClass != Actor->GetClass()) + { + // Close editors opened on existing asset if applicable + if (Asset && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Asset, false) != nullptr) + { + AssetEditorSubsystem->CloseAllEditorsForAsset(Asset); + AssetsToReOpenEditors.Add(Asset); + } + + Blueprint->ParentClass = Actor->GetClass(); + + FBlueprintEditorUtils::RefreshAllNodes(Blueprint); + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + FKismetEditorUtilities::CompileBlueprint(Blueprint); + } + } + } + else if (Asset && Asset->IsPendingKill()) + { + // Rename to pending kill so that we can use the desired name + const FString AssetPendingKillName(BlueprintName + "_PENDING_KILL"); + // Asset->Rename(*MakeUniqueObjectNameIfNeeded(Package, UBlueprint::StaticClass(), AssetPendingKillName).ToString()); + RenameAsset(Asset, AssetPendingKillName, true); + Asset = nullptr; + } + + if (!Asset) + { + UBlueprintFactory* Factory = NewObject(); + Factory->ParentClass = Actor->GetClass(); + + FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); + + Asset = AssetToolsModule.Get().CreateAsset( + BlueprintName, PackageParams.GetPackagePath(), + UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + } + + UBlueprint* Blueprint = Cast(Asset); + + if (!Blueprint || Blueprint->IsPendingKill()) + { + HOUDINI_LOG_WARNING( + TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."), + *(InBakeFolder.Path), *BlueprintName); + + continue; + } + + // Close editors opened on existing asset if applicable + if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr) + { + AssetEditorSubsystem->CloseAllEditorsForAsset(Blueprint); + AssetsToReOpenEditors.Add(Blueprint); + } + + // Record the blueprint as the previous bake blueprint + if (BakedOutputObject) + BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString(); + + OutBlueprints.Add(Blueprint); + + // Clear old Blueprint Node tree + { + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + + int32 NodeSize = SCS->GetAllNodes().Num(); + for (int32 n = NodeSize - 1; n >= 0; --n) + SCS->RemoveNode(SCS->GetAllNodes()[n]); + } + + FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint); + + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + World->EditorDestroyActor(Actor, true); + + // Save the created BP package. + Package->MarkPackageDirty(); + OutPackagesToSave.Add(Package); + } + + // Re-open asset editors for updated blueprints that were open in editors + if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0) + { + for (UObject* Asset : AssetsToReOpenEditors) + { + if (IsValid(Asset)) + { + AssetEditorSubsystem->OpenEditorForAsset(Asset); + } + } + } + + return true; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + TArray BPActors; + + if (!IsValid(InPDGAssetLink)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InPDGAssetLink is null")); + return false; + } + + if (!IsValid(InNode)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineBakeUtils::BakePDGBlueprint]: InNode is null")); + return false; + } + + const bool bReplaceAssets = InPDGBakePackageReplaceMode == EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + // Bake PDG output to new actors + // bInBakeForBlueprint == true will skip landscapes and instanced actor components + const bool bInBakeForBlueprint = true; + TArray BakedActors; + bool bSuccess = BakePDGTOPNodeOutputsKeepActors( + InPDGAssetLink, + InNode, + bInBakeForBlueprint, + bInIsAutoBake, + InPDGBakePackageReplaceMode, + BakedActors, + OutPackagesToSave, + OutBakeStats + ); + + if (bSuccess) + { + bSuccess = BakeBlueprintsFromBakedActors( + BakedActors, + bInRecenterBakedActors, + bReplaceAssets, + InPDGAssetLink->AssetName, + InPDGAssetLink->BakeFolder, + nullptr, + &InNode->GetBakedWorkResultObjectsOutputs(), + OutBlueprints, + OutPackagesToSave); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) +{ + TArray Blueprints; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + const bool bSuccess = BakePDGTOPNodeBlueprints( + InPDGAssetLink, + InTOPNode, + bInIsAutoBake, + InPDGBakePackageReplaceMode, + bInRecenterBakedActors, + Blueprints, + PackagesToSave, + BakeStats); + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + if (!IsValid(InNetwork)) + return false; + + const bool bIsAutoBake = false; + bool bSuccess = true; + for (UTOPNode* Node : InNetwork->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, InPDGBakePackageReplaceMode, bInRecenterBakedActors, OutBlueprints, OutPackagesToSave, OutBakeStats); + } + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) +{ + TArray Blueprints; + TArray PackagesToSave; + FHoudiniEngineOutputStats BakeStats; + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return false; + + const bool bIsAutoBake = false; + bool bSuccess = true; + switch(InBakeSelectionOption) + { + case EPDGBakeSelectionOption::All: + for (UTOPNetwork* Network : InPDGAssetLink->AllTOPNetworks) + { + if (!IsValid(Network)) + continue; + + for (UTOPNode* Node : Network->AllTOPNodes) + { + if (!IsValid(Node)) + continue; + + bSuccess &= BakePDGTOPNodeBlueprints(InPDGAssetLink, Node, bIsAutoBake, InPDGBakePackageReplaceMode, bInRecenterBakedActors, Blueprints, PackagesToSave, BakeStats); + } + } + break; + case EPDGBakeSelectionOption::SelectedNetwork: + bSuccess &= BakePDGTOPNetworkBlueprints( + InPDGAssetLink, + InPDGAssetLink->GetSelectedTOPNetwork(), + InPDGBakePackageReplaceMode, + bInRecenterBakedActors, + Blueprints, + PackagesToSave, + BakeStats); + case EPDGBakeSelectionOption::SelectedNode: + bSuccess &= BakePDGTOPNodeBlueprints( + InPDGAssetLink, + InPDGAssetLink->GetSelectedTOPNode(), + bIsAutoBake, + InPDGBakePackageReplaceMode, + bInRecenterBakedActors, + Blueprints, + PackagesToSave, + BakeStats); + } + + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + // Sync the CB to the baked objects + if(GEditor && Blueprints.Num() > 0) + { + TArray Assets; + Assets.Reserve(Blueprints.Num()); + for (UBlueprint* Blueprint : Blueprints) + { + Assets.Add(Blueprint); + } + GEditor->SyncBrowserToObjects(Assets); + } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + // Broadcast that the bake is complete + InPDGAssetLink->HandleOnPostBake(bSuccess); + + return bSuccess; +} + +bool +FHoudiniEngineBakeUtils::FindOrCreateDesiredLevelFromLevelPath( + const FString& InLevelPath, + ULevel*& OutDesiredLevel, + UWorld*& OutDesiredWorld, + bool& OutCreatedPackage) +{ + OutDesiredLevel = nullptr; + OutDesiredWorld = nullptr; + if (InLevelPath.IsEmpty()) + { + OutDesiredWorld = GWorld; + OutDesiredLevel = GWorld->GetCurrentLevel(); + } + else + { + OutCreatedPackage = false; + + UWorld* FoundWorld = nullptr; + ULevel* FoundLevel = nullptr; + bool bActorInWorld = false; + if (FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + GWorld, + InLevelPath, + true, + FoundWorld, + FoundLevel, + OutCreatedPackage, + bActorInWorld)) + { + OutDesiredLevel = FoundLevel; + OutDesiredWorld = FoundWorld; + } + } + + return ((OutDesiredWorld != nullptr) && (OutDesiredLevel != nullptr)); +} + + +bool +FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( + const FString& InBakeActorName, + ULevel* InLevel, + AActor*& OutActor, + bool bInNoPendingKillActors, + bool bRenamePendingKillActor) +{ + OutActor = nullptr; + + if (!IsValid(InLevel)) + return false; + + UWorld* const World = InLevel->GetWorld(); + if (!IsValid(World)) + return false; + + // Look for an actor with the given name in the world + const FName BakeActorFName(InBakeActorName); + AActor* FoundActor = Cast(StaticFindObjectFast(AActor::StaticClass(), InLevel, BakeActorFName)); + // for (TActorIterator Iter(World, AActor::StaticClass(), EActorIteratorFlags::AllActors); Iter; ++Iter) + // { + // AActor* const Actor = *Iter; + // if (Actor->GetFName() == BakeActorFName && Actor->GetLevel() == InLevel) + // { + // // Found the actor + // FoundActor = Actor; + // break; + // } + // } + + // If we found an actor and it is pending kill, rename it and don't use it + if (FoundActor) + { + if (FoundActor->IsPendingKill()) + { + if (bRenamePendingKillActor) + { + // FoundActor->Rename( + // *MakeUniqueObjectNameIfNeeded( + // FoundActor->GetOuter(), + // FoundActor->GetClass(), + // FName(FoundActor->GetName() + "_Pending_Kill")).ToString()); + RenameAndRelabelActor( + FoundActor, + *MakeUniqueObjectNameIfNeeded( + FoundActor->GetOuter(), + FoundActor->GetClass(), + FoundActor->GetName() + "_Pending_Kill", + FoundActor), + false); + } + if (bInNoPendingKillActors) + FoundActor = nullptr; + else + OutActor = FoundActor; + } + else + { + OutActor = FoundActor; + } + } + + return true; +} + +bool FHoudiniEngineBakeUtils::FindUnrealBakeActor( + const FHoudiniOutputObject& InOutputObject, + const FHoudiniBakedOutputObject& InBakedOutputObject, + const TArray& InAllBakedActors, + ULevel* InLevel, + FName InDefaultActorName, + bool bInReplaceActorBakeMode, + AActor* InFallbackActor, + AActor*& OutFoundActor, + bool& bOutHasBakeActorName, + FName& OutBakeActorName) +{ + // Determine desired actor name via unreal_output_actor, fallback to InDefaultActorName + OutBakeActorName = NAME_None; + OutFoundActor = nullptr; + bOutHasBakeActorName = InOutputObject.CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_ACTOR); + if (bOutHasBakeActorName) + { + const FString& BakeActorNameStr = InOutputObject.CachedAttributes[HAPI_UNREAL_ATTRIB_BAKE_ACTOR]; + if (BakeActorNameStr.IsEmpty()) + { + OutBakeActorName = NAME_None; + bOutHasBakeActorName = false; + } + else + { + OutBakeActorName = FName(BakeActorNameStr, NAME_NO_NUMBER_INTERNAL); + // We have a bake actor name, look for the actor + AActor* BakeNameActor = nullptr; + if (FindDesiredBakeActorFromBakeActorName(BakeActorNameStr, InLevel, BakeNameActor)) + { + // Found an actor with that name, check that we "own" it (we created in during baking previously) + AActor* IncrementedBakedActor = nullptr; + for (const FHoudiniEngineBakedActor& BakedActor : InAllBakedActors) + { + if (!IsValid(BakedActor.Actor)) + continue; + if (BakedActor.Actor == BakeNameActor) + { + OutFoundActor = BakeNameActor; + break; + } + else if (!IncrementedBakedActor && BakedActor.ActorBakeName == OutBakeActorName) + { + // Found an actor we have baked named OutBakeActorName_# (incremental version of our desired name) + IncrementedBakedActor = BakedActor.Actor; + } + } + if (!OutFoundActor && IncrementedBakedActor) + OutFoundActor = IncrementedBakedActor; + } + } + } + + // If unreal_actor_name is not set, or is blank, fallback to InDefaultActorName + if (!bOutHasBakeActorName || (OutBakeActorName.IsNone() || OutBakeActorName.ToString().TrimStartAndEnd().IsEmpty())) + OutBakeActorName = InDefaultActorName; + + if (!OutFoundActor) + { + // If in replace mode, use previous bake actor if valid and in InLevel + if (bInReplaceActorBakeMode) + { + const FSoftObjectPath PrevActorPath(InBakedOutputObject.Actor); + const FString ActorPath = PrevActorPath.IsSubobject() + ? PrevActorPath.GetAssetPathString() + ":" + PrevActorPath.GetSubPathString() + : PrevActorPath.GetAssetPathString(); + const FString LevelPath = IsValid(InLevel) ? InLevel->GetPathName() : ""; + if (PrevActorPath.IsValid() && (LevelPath.IsEmpty() || ActorPath.StartsWith(LevelPath))) + OutFoundActor = InBakedOutputObject.GetActorIfValid(); + } + + // Fallback to InFallbackActor if valid and in InLevel + if (!OutFoundActor && IsValid(InFallbackActor) && (!InLevel || InFallbackActor->GetLevel() == InLevel)) + OutFoundActor = InFallbackActor; + } + + return true; +} + +AActor* +FHoudiniEngineBakeUtils::FindExistingActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage) +{ + bCreatedPackage = false; + + // Try to Locate a previous actor + AActor* FoundActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, InActorName, FoundActor); + if (FoundActor) + FoundActor->Destroy(); // nuke it! + + if (FoundActor) + { + // TODO: make sure that the found is actor is actually assigned to the level defined by package path. + // If the found actor is not from that level, it should be moved there. + + OutWorld = FoundActor->GetWorld(); + OutLevel = FoundActor->GetLevel(); + } + else + { + // Actor is not present, BUT target package might be loaded. Find the appropriate World and Level for spawning. + bool bActorInWorld = false; + const bool bResult = FHoudiniEngineUtils::FindWorldAndLevelForSpawning( + InWorld, + InPackagePath, + true, + OutWorld, + OutLevel, + bCreatedPackage, + bActorInWorld); + + if (!bResult) + { + return nullptr; + } + + if (!bActorInWorld) + { + // The OutLevel is not present in the current world which means we might + // still find the tile actor in OutWorld. + FoundActor = FHoudiniEngineUtils::FindActorInWorld(OutWorld, FName(InActorName)); + } + } + + return FoundActor; +} + +bool +FHoudiniEngineBakeUtils::CheckForAndRefineHoudiniProxyMesh( + UHoudiniAssetComponent* InHoudiniAssetComponent, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors, + bool& bOutNeedsReCook) +{ + if (!IsValid(InHoudiniAssetComponent)) + { + return false; + } + + // Handle proxies: if the output has any current proxies, first refine them + bOutNeedsReCook = false; + if (InHoudiniAssetComponent->HasAnyCurrentProxyOutput()) + { + bool bNeedsRebuildOrDelete; + bool bInvalidState; + const bool bCookedDataAvailable = InHoudiniAssetComponent->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bInvalidState); + + if (bCookedDataAvailable) + { + // Cook data is available, refine the mesh + AHoudiniAssetActor* HoudiniActor = Cast(InHoudiniAssetComponent->GetOwner()); + if (IsValid(HoudiniActor)) + { + FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ HoudiniActor }); + } + } + else if (!bNeedsRebuildOrDelete && !bInvalidState) + { + // A cook is needed: request the cook, but with no proxy and with a bake after cook + InHoudiniAssetComponent->SetNoProxyMeshNextCookRequested(true); + // Only + if (!InHoudiniAssetComponent->IsBakeAfterNextCookEnabled() || !InHoudiniAssetComponent->GetOnPostCookBakeDelegate().IsBound()) + { + InHoudiniAssetComponent->GetOnPostCookBakeDelegate().BindLambda([bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors](UHoudiniAssetComponent* InHAC) { + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(InHAC, bInReplacePreviousBake, InBakeOption, bInRemoveHACOutputOnSuccess, bInRecenterBakedActors); + }); + } + InHoudiniAssetComponent->MarkAsNeedCook(); + + bOutNeedsReCook = true; + + // The cook has to complete first (asynchronously) before the bake can happen + // The SetBakeAfterNextCookEnabled flag will result in a bake after cook + return false; + } + else + { + // The HAC is in an unsupported state + const EHoudiniAssetState AssetState = InHoudiniAssetComponent->GetAssetState(); + HOUDINI_LOG_ERROR(TEXT("Could not refine (in order to bake) %s, the asset is in an unsupported state: %s"), *(InHoudiniAssetComponent->GetPathName()), *(UEnum::GetValueAsString(AssetState))); + return false; + } + } + + return true; +} + +void +FHoudiniEngineBakeUtils::CenterActorToBoundingBoxCenter(AActor* InActor) +{ + if (!IsValid(InActor)) + return; + + USceneComponent * const RootComponent = InActor->GetRootComponent(); + if (!IsValid(RootComponent)) + return; + + // If the root component does not have any child components, then there is nothing to recenter + if (RootComponent->GetNumChildrenComponents() <= 0) + return; + + const bool bOnlyCollidingComponents = false; + const bool bIncludeFromChildActors = true; + FVector Origin; + FVector BoxExtent; + // InActor->GetActorBounds(bOnlyCollidingComponents, Origin, BoxExtent, bIncludeFromChildActors); + FBox Box(ForceInit); + + InActor->ForEachComponent(bIncludeFromChildActors, [&](const UPrimitiveComponent* InPrimComp) + { + // Only use non-editor-only components for the bounds calculation (to exclude things like editor only sprite/billboard components) + if (InPrimComp->IsRegistered() && !InPrimComp->IsEditorOnly() && + (!bOnlyCollidingComponents || InPrimComp->IsCollisionEnabled())) + { + Box += InPrimComp->Bounds.GetBox(); + } + }); + Box.GetCenterAndExtents(Origin, BoxExtent); + + const FVector Delta = Origin - RootComponent->GetComponentLocation(); + // Actor->SetActorLocation(Origin); + RootComponent->SetWorldLocation(Origin); + + for (USceneComponent* SceneComponent : RootComponent->GetAttachChildren()) + { + if (!IsValid(SceneComponent)) + continue; + + SceneComponent->SetWorldLocation(SceneComponent->GetComponentLocation() - Delta); + } +} + +void +FHoudiniEngineBakeUtils::CenterActorsToBoundingBoxCenter(const TArray& InActors) +{ + for (AActor* Actor : InActors) + { + if (!IsValid(Actor)) + continue; + + CenterActorToBoundingBoxCenter(Actor); + } +} + +USceneComponent* +FHoudiniEngineBakeUtils::GetActorRootComponent(AActor* InActor, bool bCreateIfMissing, EComponentMobility::Type InMobilityIfCreated) +{ + USceneComponent* RootComponent = InActor->GetRootComponent(); + if (!IsValid(RootComponent)) + { + RootComponent = NewObject(InActor, USceneComponent::GetDefaultSceneRootVariableName(), RF_Transactional); + + // Change the creation method so the component is listed in the details panels + InActor->SetRootComponent(RootComponent); + InActor->AddInstanceComponent(RootComponent); + RootComponent->RegisterComponent(); + RootComponent->SetMobility(InMobilityIfCreated); + } + + return RootComponent; +} + +FString +FHoudiniEngineBakeUtils::MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed) +{ + if (IsValid(InObjectThatWouldBeRenamed)) + { + const FName CurrentName = InObjectThatWouldBeRenamed->GetFName(); + if (CurrentName.ToString() == InName) + return InName; + + // Check if the prefix matches (without counter suffix) the new name + // In other words, if InName is 'my_actor' and the object is already an increment of it, 'my_actor_5' then + // don't we can just keep the current name + if (CurrentName.GetPlainNameString() == InName) + return CurrentName.ToString(); + } + + UObject* ExistingObject = nullptr; + FName CandidateName(InName); + bool bAppendedNumber = false; + // Do our own loop for generate suffixes as sequentially as possible. If this turns out to be expensive we can + // revert to MakeUniqueObjectName. + // return MakeUniqueObjectName(InOuter, InClass, CandidateName).ToString(); + do + { + if (InOuter == ANY_PACKAGE) + { + ExistingObject = StaticFindObject(nullptr, ANY_PACKAGE, *(CandidateName.ToString())); + } + else + { + ExistingObject = StaticFindObjectFast(nullptr, InOuter, CandidateName); + } + + if (ExistingObject) + { + if (!bAppendedNumber) + { + const bool bSplitName = false; + CandidateName = FName(*InName, NAME_EXTERNAL_TO_INTERNAL(1), FNAME_Add, bSplitName); + bAppendedNumber = true; + } + else + { + CandidateName.SetNumber(CandidateName.GetNumber() + 1); + } + // CandidateName = FString::Printf(TEXT("%s_%d"), *InName, ++Counter); + } + } while (ExistingObject); + + return CandidateName.ToString(); +} + +FName +FHoudiniEngineBakeUtils::GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) +{ + const FString* FolderPathPtr = InOutputObject.CachedAttributes.Find(HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER); + if (FolderPathPtr && !FolderPathPtr->IsEmpty()) + return FName(*FolderPathPtr); + else + return InDefaultFolder; +} + +bool +FHoudiniEngineBakeUtils::SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder) +{ + if (!IsValid(InActor)) + return false; + + InActor->SetFolderPath(GetOutlinerFolderPath(InOutputObject, InDefaultFolder)); + return true; +} + +uint32 +FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInDestroyBakedComponent, + bool bInDestroyBakedInstancedActors, + bool bInDestroyBakedInstancedComponents) +{ + uint32 NumDeleted = 0; + + if (bInDestroyBakedComponent) + { + UActorComponent* Component = Cast(InBakedOutputObject.GetBakedComponentIfValid()); + if (Component) + { + if (RemovePreviouslyBakedComponent(Component)) + { + InBakedOutputObject.BakedComponent = nullptr; + NumDeleted++; + } + } + } + + if (bInDestroyBakedInstancedActors) + { + for (const FString& ActorPathStr : InBakedOutputObject.InstancedActors) + { + const FSoftObjectPath ActorPath(ActorPathStr); + + if (!ActorPath.IsValid()) + continue; + + AActor* Actor = Cast(ActorPath.TryLoad()); + if (IsValid(Actor)) + { + UWorld* World = Actor->GetWorld(); + if (IsValid(World)) + { +#if WITH_EDITOR + World->EditorDestroyActor(Actor, true); +#else + World->DestroyActor(Actor); +#endif + NumDeleted++; + } + } + } + InBakedOutputObject.InstancedActors.Empty(); + } + + if (bInDestroyBakedInstancedComponents) + { + for (const FString& ComponentPathStr : InBakedOutputObject.InstancedComponents) + { + const FSoftObjectPath ComponentPath(ComponentPathStr); + + if (!ComponentPath.IsValid()) + continue; + + UActorComponent* Component = Cast(ComponentPath.TryLoad()); + if (IsValid(Component)) + { + if (RemovePreviouslyBakedComponent(Component)) + NumDeleted++; + } + } + InBakedOutputObject.InstancedComponents.Empty(); + } + + return NumDeleted; +} + +UMaterialInterface* FHoudiniEngineBakeUtils::BakeSingleMaterialToPackage(UMaterialInterface* InOriginalMaterial, + const FHoudiniPackageParams & InPackageParams, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap) +{ + if (!InOriginalMaterial || InOriginalMaterial->IsPendingKill()) + { + return nullptr; + } + + // We only deal with materials. + if (!InOriginalMaterial->IsA(UMaterial::StaticClass()) && !InOriginalMaterial->IsA(UMaterialInstance::StaticClass())) + { + return nullptr; + } + + FString MaterialName = InOriginalMaterial->GetName(); + + // Duplicate material resource. + UMaterialInterface * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( + InOriginalMaterial, nullptr, MaterialName, InPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + + if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + return nullptr; + + return DuplicatedMaterial; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h index 6a4017832..011b07368 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h @@ -1,733 +1,735 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniPDGAssetLink.h" -#include "HoudiniOutput.h" -#include "HoudiniPackageParams.h" - -class UHoudiniAssetComponent; -class UHoudiniOutput; -class ALandscapeProxy; -class UStaticMesh; -class USplineComponent; -class UPackage; -class UWorld; -class AActor; -class UHoudiniSplineComponent; -class UStaticMeshComponent; -class UHoudiniPDGAssetLink; -class UTOPNetwork; -class UTOPNode; - -struct FHoudiniPackageParams; -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniEngineOutputStats; -struct FHoudiniBakedOutputObject; -struct FHoudiniAttributeResolver; - -enum class EHoudiniLandscapeOutputBakeType : uint8; - -// An enum of the different types for instancer component/bake types -UENUM() -enum class EHoudiniInstancerComponentType : uint8 -{ - // Single static mesh component - StaticMeshComponent, - // (Hierarichal)InstancedStaticMeshComponent - InstancedStaticMeshComponent, - MeshSplitInstancerComponent, - InstancedActorComponent, - // For baking foliage as foliage - FoliageInstancedStaticMeshComponent, - // Baking foliage as HISMC - FoliageAsHierarchicalInstancedStaticMeshComponent -}; - -// Helper struct to track actors created/used when baking, with -// the intended bake name (before making it unique), and their -// output index and output object identifier. -struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor -{ - FHoudiniEngineBakedActor(); - - FHoudiniEngineBakedActor( - AActor* InActor, - FName InActorBakeName, - FName InWorldOutlinerFolder, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - UObject* InBakedObject, - UObject* InSourceObject, - UObject* InBakedComponent, - const FString& InBakeFolderPath, - const FHoudiniPackageParams& InBakedObjectPackageParams); - - // The actor that the baked output was associated with - AActor* Actor = nullptr; - - // The output index on the HAC for the baked object - int32 OutputIndex = INDEX_NONE; - - // The output object identifier for the baked object - FHoudiniOutputObjectIdentifier OutputObjectIdentifier; - - // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. - FName ActorBakeName = NAME_None; - - // The world outliner folder the actor is placed in - FName WorldOutlinerFolder = NAME_None; - - // The array index of the work result when baking PDG - int32 PDGWorkResultArrayIndex = INDEX_NONE; - - // The work item index (as returned by HAPI) for the work item/work result, used when baking PDG - int32 PDGWorkItemIndex = INDEX_NONE; - - // The array index of the work result object of the work result when baking PDG - int32 PDGWorkResultObjectArrayIndex = INDEX_NONE; - - // The baked primary asset (such as static mesh) - UObject* BakedObject = nullptr; - - // The temp asset that was baked to BakedObject - UObject* SourceObject = nullptr; - - // The baked component or foliage type in the case of foliage - UObject* BakedComponent = nullptr; - - // The bake folder path to where BakedObject was baked - FString BakeFolderPath; - - // The package params for the BakedObject - FHoudiniPackageParams BakedObjectPackageParams; - - // True if this entry was created by an instancer output. - bool bInstancerOutput; - - // The package params built for the instancer part of the output, if this was an instancer. - // This would mostly be useful in situations for we later need the resolver and/or cached attributes and - // tokens, such as for blueprint baking. - FHoudiniPackageParams InstancerPackageParams; - - // Used to delay all post bake calls so they are done only once per baked actor - bool bPostBakeProcessPostponed = false; - -}; - -struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils -{ -public: - - /** Bake static mesh. **/ - - /*static UStaticMesh * BakeStaticMesh( - UHoudiniAssetComponent * HoudiniAssetComponent, - UStaticMesh * InStaticMesh, - const FHoudiniPackageParams &PackageParams);*/ - - static ALandscapeProxy* BakeHeightfield( - ALandscapeProxy * InLandscapeProxy, - const FHoudiniPackageParams &PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType); - - static bool BakeCurve( - USplineComponent* InSplineComponent, - ULevel* InLevel, - const FHoudiniPackageParams &PackageParams, - AActor*& OutActor, - USplineComponent*& OutSplineComponent, - FName InOverrideFolderPath=NAME_None, - AActor* InActor=nullptr); - - static bool BakeCurve( - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - // const TArray& InAllBakedOutputs, - const FHoudiniPackageParams &PackageParams, - FHoudiniAttributeResolver& InResolver, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static AActor* BakeInputHoudiniCurveToActor( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PakcageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform); - - static UBlueprint* BakeInputHoudiniCurveToBlueprint( - UHoudiniSplineComponent * InHoudiniSplineComponent, - const FHoudiniPackageParams & PakcageParams, - UWorld* WorldToSpawn, - const FTransform & SpawnTransform); - - static UStaticMesh* BakeStaticMesh( - UStaticMesh * StaticMesh, - const FHoudiniPackageParams & PackageParams, - const TArray& InAllOutputs, - const FDirectoryPath& InTempCookFolder, - TMap& InOutAlreadyBakedMaterialsMap); - - static bool BakeLandscape( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - bool bInReplaceActors, - bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, - FHoudiniEngineOutputStats& BakeStats); - - static bool BakeLandscapeObject( - FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInReplaceActors, - bool bInReplaceAssets, - FHoudiniPackageParams& PackageParams, - FHoudiniAttributeResolver& InResolver, - TArray& WorldsToUpdate, - TArray& OutPackagesToUnload, - FHoudiniEngineOutputStats& BakeStats); - - static bool BakeInstancerOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetcomponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FTransform& InTransform, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_ISMC( - const UHoudiniAssetComponent* HoudiniAssetcomponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_IAC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave); - - static bool BakeInstancerOutputToActors_MSIC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FTransform& InTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToActors_SMC( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static UStaticMesh * DuplicateStaticMeshAndCreatePackageIfNeeded( - UStaticMesh * InStaticMesh, - UStaticMesh * InPreviousBakeStaticMesh, - const FHoudiniPackageParams &PackageParams, - const TArray& InParentOutputs, - const TArray& InCurrentBakedActors, - const FString& InTemporaryCookFolder, - TArray & OutCreatedPackages, - TMap& InOutAlreadyBakedMaterialsMap); - - static UMaterial * DuplicateMaterialAndCreatePackage( - UMaterial * Material, - UMaterial* PreviousBakeMaterial, - const FString & SubMaterialName, - const FHoudiniPackageParams& ObjectPackageParams, - TArray & OutCreatedPackages, - TMap& InOutAlreadyBakedMaterialsMap); - - static void ReplaceDuplicatedMaterialTextureSample( - UMaterialExpression * MaterialExpression, - UMaterialExpression* PreviousBakeMaterialExpression, - const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); - - static UTexture2D * DuplicateTextureAndCreatePackage( - UTexture2D * Texture, - UTexture2D* PreviousBakeTexture, - const FString & SubTextureName, - const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); - - // Bake a Houdini asset component (InHACToBake) based on the bInReplace and BakeOption arguments. - // Returns true if the underlying bake function (for example, BakeHoudiniActorToActors, returns true (or a valid UObject*)) - static bool BakeHoudiniAssetComponent( - UHoudiniAssetComponent* InHACToBake, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption InBakeOption, - bool bInRemoveHACOutputOnSuccess, - bool bInRecenterBakedActors); - - static bool BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActors); - - static bool BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeHoudiniOutputsToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - const TArray& InOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FTransform& InParentTransform, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutNewActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeInstancerOutputToFoliage( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - // const TArray& InAllBakedOutputs, - const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, - const FHoudiniOutputObject& InOutputObject, - FHoudiniBakedOutputObject& InBakedOutputObject, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap); - - static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); - - static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, TMap& InOutAlreadyBakedMaterialsMap); - - static bool BakeStaticMeshOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - const FDirectoryPath& InTempCookFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool ResolvePackageParams( - const UHoudiniAssetComponent* HoudiniAssetComponent, - UHoudiniOutput* InOutput, - const FHoudiniOutputObjectIdentifier& Identifier, - const FHoudiniOutputObject& InOutputObject, - const FString& InHoudiniAssetName, - const FString& DefaultObjectName, - const FDirectoryPath& InBakeFolder, - const bool bInReplaceAssets, - FHoudiniPackageParams& OutPackageParams, - TArray& OutPackagesToSave); - - static bool BakeHoudiniCurveOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetComponent, - int32 InOutputIndex, - const TArray& InAllOutputs, - TArray& InBakedOutputs, - const FString& InHoudiniAssetName, - const FDirectoryPath& InBakeFolder, - bool bInReplaceActors, - bool bInReplaceAssets, - TArray& OutActors, - AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - static bool BakeBlueprintsFromBakedActors( - const TArray& InBakedActors, - bool bInRecenterBakedActors, - bool bInReplaceAssets, - const FString& InAssetName, - const FDirectoryPath& InBakeFolder, - TArray* const InNonPDGBakedOutputs, - TMap* const InPDGBakedOutputs, - TArray& OutBlueprints, - TArray& OutPackagesToSave); - - static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, bool bInRecenterBakedActors); - - static bool BakeBlueprints( - UHoudiniAssetComponent* HoudiniAssetComponent, - bool bInReplaceAssets, - bool bInRecenterBakedActors, - FHoudiniEngineOutputStats& InBakeStats, - TArray& OutBlueprints, - TArray& OutPackagesToSave); - - static bool CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint); - - static void AddHoudiniMetaInformationToPackage( - UPackage * Package, UObject * Object, const TCHAR * Key, - const TCHAR * Value); - - static bool GetHoudiniGeneratedNameFromMetaInformation( - UPackage * Package, UObject * Object, FString & HoudiniName); - - static bool DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent); - - static void SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld = false); - - // Look for InObjectToFind among InOutputs. Return true if found and set OutOutputIndex and OutIdentifier. - static bool FindOutputObject( - const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); - - static bool IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC); - - // Returns true if InObject is in InTemporaryCookFolder, or in the default Temporary cook folder from the runtime - // settings. - static bool IsObjectInTempFolder(UObject* const InObject, const FString& InTemporaryCookFolder); - - static bool IsObjectTemporary( - UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); - - // Function used to copy properties from the source Static Mesh Component to the new (baked) one - static void CopyPropertyToNewActorAndComponent( - AActor* NewActor, - UStaticMeshComponent* NewSMC, - UStaticMeshComponent* InSMC, - bool bInCopyWorldTransform=false); - - // Finds the world/level indicated by the package path. - // If the level doesn't exists, it will be created. - // If InLevelPath is empty, outputs the editor world and current level - // Returns true if the world/level were found, false otherwise - static bool FindOrCreateDesiredLevelFromLevelPath( - const FString& InLevelPath, - ULevel*& OutDesiredLevel, - UWorld*& OutDesiredWorld, - bool& OutCreatedPackage); - - // Finds the actor indicated by InBakeActorName in InLevel. - // Returns false if any input was invalid (InLevel is invalid for example), true otherwise - // If an actor was found OutActor is set - // If bInNoPendingKillActors is true, then if an actor called InBakeActorName is found but is pending kill, then - // it is not set in OutActor - // If bRenamePendingKillActor is true, then if a pending kill actor call InBakeActorName is found it is renamed - // (uniquely) with a _Pending_Kill suffix (regardless of bInNoPendingKillActors). - static bool FindDesiredBakeActorFromBakeActorName( - const FString& InBakeActorName, - ULevel* InLevel, - AActor*& OutActor, - bool bInNoPendingKillActors=true, - bool bRenamePendingKillActor=true); - - // Helper that determines the desired bake actor name with unreal_bake_actor attribute, falling - // back to InDefaultActorName if the attribute is not set. - // If unreal_bake_actor is set, we look for such in InLevel, and use it *if* it is present in InAlLBakedOutputs. - // Otherwise if we are baking in replace mode, and the previous bake actor is available and in InLevel, return it - // as OutFoundActor. Otherwise return InFallbackActor as OutFoundActor. - // bOutHasBakeActorName indicates if the output has the unreal_bake_actor attribute set. - // OutFoundActor is the actor that was found (if one was found) - static bool FindUnrealBakeActor( - const FHoudiniOutputObject& InOutputObject, - const FHoudiniBakedOutputObject& InBakedOutputObject, - const TArray& InAllBakedActors, - ULevel* InLevel, - FName InDefaultActorName, - bool bInReplaceActorBakeMode, - AActor* InFallbackActor, - AActor*& OutFoundActor, - bool& bOutHasBakeActorName, - FName& OutBakeActorName); - - // Try to find an actor that we can use for baking. - // If the requested actor could not be found, then `OutWorld` and `OutLevel` - // should be used to spawn the new bake actor. - // @returns AActor* if found. Otherwise, returns nullptr. - static AActor* FindExistingActor_Bake( - UWorld* InWorld, - UHoudiniOutput* InOutput, - const FString& InActorName, - const FString& InPackagePath, - UWorld*& OutWorld, - ULevel*& OutLevel, - bool& bCreatedPackage); - - // Remove a previously baked actor - static bool RemovePreviouslyBakedActor( - AActor* InNewBakedActor, - ULevel* InLevel, - const FHoudiniPackageParams& InPackageParams); - - static bool RemovePreviouslyBakedComponent(UActorComponent* InComponent); - - // Get the world outliner folder path for output generated by InOutputOwner - static FName GetOutputFolderPath(UObject* InOutputOwner); - - static void RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); - - // Helper function for renaming and relabelling an actor - static void RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); - - // Start: PDG Baking - - // Detach InActor from its parent, and rename to InNewName (attaches a numeric suffix to make it unique via - // MakeUniqueObjectName). Place it in the world outliner folder InFolderPath. - static bool DetachAndRenameBakedPDGOutputActor(AActor* InActor, const FString& InNewName, const FName& InFolderPath); - - static bool BakePDGWorkResultObject( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkResultArrayIndex, - int32 InWorkResultObjectArrayIndex, - bool bInReplaceActors, - bool bInReplaceAssets, - bool bInBakeToWorkResultActor, - bool bInIsAutoBake, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats, - TArray const* InOutputTypesToBake=nullptr, - TArray const* InInstancerComponentTypesToBake=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); - - // Checks if auto-bake is enabled on InPDGAssetLink, and if it is, calls BakePDGWorkResultObject. - static void CheckPDGAutoBakeAfterResultObjectLoaded( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - int32 InWorkItemHAPIIndex, - int32 InWorkItemResultInfoIndex); - - // Bake PDG output. This bakes all assets from all work items in the specified InNode (FTOPNode). - // It uses the existing output actors in the level, but breaks any links from these actors to the PDG link and - // moves the actors out of the parent Folder/ detaches from the parent PDG output actor. - static bool BakePDGTOPNodeOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInBakeForBlueprint, - bool bInIsAutoBake, - const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Helper function to bake only a specific PDG TOP node's outputs to actors. - static bool BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); - - // Bake PDG output. This bakes all assets from all work items in the specified TOP network. - // It uses the existing output actors in the level, but breaks any links - // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent - // PDG output actor. - static bool BakePDGTOPNetworkOutputsKeepActors( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - bool bInBakeForBlueprint, - bool bInIsAutoBake, - const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, - TArray& OutBakedActors, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes assets from TOP networks and nodes according to - // InPDGAssetLink->PDGBakeSelectionOption. It uses the existing output actors in the level, but breaks any links - // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent - // PDG output actor. - static bool BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); - - // Bake PDG output. This bakes all supported assets from all work items in the specified InNode (FTOPNode). - // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from - // PDG output actors. - static bool BakePDGTOPNodeBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNode* InNode, - bool bInIsAutoBake, - const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, - bool bInRecenterBakedActors, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Helper to bake only a specific PDG TOP node's outputs to blueprint(s). - static bool BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); - - // Bake PDG output. This bakes all supported assets from all work items in the specified TOP network. - // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from - // PDG output actors. - static bool BakePDGTOPNetworkBlueprints( - UHoudiniPDGAssetLink* InPDGAssetLink, - UTOPNetwork* InNetwork, - const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, - bool bInRecenterBakedActors, - TArray& OutBlueprints, - TArray& OutPackagesToSave, - FHoudiniEngineOutputStats& OutBakeStats); - - // Bake PDG output. This bakes assets from TOP networks and nodes according to - // InPDGAssetLink->PDGBakeSelectionOption. It duplicates the output actors and bakes them to blueprints. Assets - // that were baked are removed from PDG output actors. - static bool BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); - - // End: PDG Baking - -protected: - - // Find the HGPO with matching identifier. Returns true if the HGPO was found. - static bool FindHGPO( - const FHoudiniOutputObjectIdentifier& InIdentifier, - const TArray& InHGPOs, - FHoudiniGeoPartObject const*& OutHGPO); - - // Set OutBakeName to the resolved output name of InMeshOutputObject / InObject. OutBakeName is set to the object's - // BakeName (the BakeName on the InMeshOutputObject, or if that is not set, the custom part name or finally the - // package name. - static void GetTemporaryOutputObjectBakeName( - const UObject* InObject, - const FHoudiniOutputObject& InMeshOutputObject, - FString& OutBakeName); - - // Look for InObject in InAllOutputs. If found the function returns true and OutBakeName is set to the object's - // BakeName (the BakeName on the OutputObject, or if that is not set, the custom part name or finally the package - // name. - static bool GetTemporaryOutputObjectBakeName( - const UObject* InObject, - EHoudiniOutputType InOutputType, - const TArray& InAllOutputs, - FString& OutBakeName); - - // Checks if InHoudiniAssetComponent has any current proxy mesh. Refines if it possible. Returns true - // if baking can continue, false otherwise. If the component has a proxy, but no cook data, then false is - // returned, the component is set to recook without a proxy and with bake after cook, and bOutNeedsReCook is set - // to true. - // bInReplace and BakeOption represents the baking settings to use if a delayed bake (post-cook) needs to be triggered. - static bool CheckForAndRefineHoudiniProxyMesh( - UHoudiniAssetComponent* InHoudiniAssetComponent, - bool bInReplacePreviousBake, - EHoudiniEngineBakeOption BakeOption, - bool bInRemoveHACOutputOnSuccess, - bool bInRecenterBakedActors, - bool& bOutNeedsReCook); - - // Position InActor at its bounding box center (keep components' world location) - static void CenterActorToBoundingBoxCenter(AActor* InActor); - - // Position each of the actors in InActors at its bounding box center (keep components' world location) - static void CenterActorsToBoundingBoxCenter(const TArray& InActors); - - // Helper to get or optionally create a RootComponent for an actor - static USceneComponent* GetActorRootComponent( - AActor* InActor, bool bCreateIfMissing=true, EComponentMobility::Type InMobilityIfCreated=EComponentMobility::Static); - - // Helper function to return a unique object name if the given is already in use - static FString MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed=nullptr); - - // Helper for getting the actor folder path for the world outliner, based unreal_bake_outliner_folder - static FName GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); - - // Helper for setting the actor folder path in the world outliner - static bool SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); - - // Helper for destroying previous bake components/actors - static uint32 DestroyPreviousBakeOutput( - FHoudiniBakedOutputObject& InBakedOutputObject, - bool bInDestroyBakedComponent, - bool bInDestroyBakedInstancedActors, - bool bInDestroyBakedInstancedComponents); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniPDGAssetLink.h" +#include "HoudiniOutput.h" +#include "HoudiniPackageParams.h" + +class UHoudiniAssetComponent; +class UHoudiniOutput; +class ALandscapeProxy; +class UStaticMesh; +class USplineComponent; +class UPackage; +class UWorld; +class AActor; +class UHoudiniSplineComponent; +class UStaticMeshComponent; +class UHoudiniPDGAssetLink; +class UTOPNetwork; +class UTOPNode; + +struct FHoudiniPackageParams; +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniEngineOutputStats; +struct FHoudiniBakedOutputObject; +struct FHoudiniAttributeResolver; + +enum class EHoudiniLandscapeOutputBakeType : uint8; + +// An enum of the different types for instancer component/bake types +UENUM() +enum class EHoudiniInstancerComponentType : uint8 +{ + // Single static mesh component + StaticMeshComponent, + // (Hierarichal)InstancedStaticMeshComponent + InstancedStaticMeshComponent, + MeshSplitInstancerComponent, + InstancedActorComponent, + // For baking foliage as foliage + FoliageInstancedStaticMeshComponent, + // Baking foliage as HISMC + FoliageAsHierarchicalInstancedStaticMeshComponent +}; + +// Helper struct to track actors created/used when baking, with +// the intended bake name (before making it unique), and their +// output index and output object identifier. +struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakedActor +{ + FHoudiniEngineBakedActor(); + + FHoudiniEngineBakedActor( + AActor* InActor, + FName InActorBakeName, + FName InWorldOutlinerFolder, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + UObject* InBakedObject, + UObject* InSourceObject, + UObject* InBakedComponent, + const FString& InBakeFolderPath, + const FHoudiniPackageParams& InBakedObjectPackageParams); + + // The actor that the baked output was associated with + AActor* Actor = nullptr; + + // The output index on the HAC for the baked object + int32 OutputIndex = INDEX_NONE; + + // The output object identifier for the baked object + FHoudiniOutputObjectIdentifier OutputObjectIdentifier; + + // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. + FName ActorBakeName = NAME_None; + + // The world outliner folder the actor is placed in + FName WorldOutlinerFolder = NAME_None; + + // The array index of the work result when baking PDG + int32 PDGWorkResultArrayIndex = INDEX_NONE; + + // The work item index (as returned by HAPI) for the work item/work result, used when baking PDG + int32 PDGWorkItemIndex = INDEX_NONE; + + // The array index of the work result object of the work result when baking PDG + int32 PDGWorkResultObjectArrayIndex = INDEX_NONE; + + // The baked primary asset (such as static mesh) + UObject* BakedObject = nullptr; + + // The temp asset that was baked to BakedObject + UObject* SourceObject = nullptr; + + // The baked component or foliage type in the case of foliage + UObject* BakedComponent = nullptr; + + // The bake folder path to where BakedObject was baked + FString BakeFolderPath; + + // The package params for the BakedObject + FHoudiniPackageParams BakedObjectPackageParams; + + // True if this entry was created by an instancer output. + bool bInstancerOutput; + + // The package params built for the instancer part of the output, if this was an instancer. + // This would mostly be useful in situations for we later need the resolver and/or cached attributes and + // tokens, such as for blueprint baking. + FHoudiniPackageParams InstancerPackageParams; + + // Used to delay all post bake calls so they are done only once per baked actor + bool bPostBakeProcessPostponed = false; + +}; + +struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils +{ +public: + + /** Bake static mesh. **/ + + /*static UStaticMesh * BakeStaticMesh( + UHoudiniAssetComponent * HoudiniAssetComponent, + UStaticMesh * InStaticMesh, + const FHoudiniPackageParams &PackageParams);*/ + + static ALandscapeProxy* BakeHeightfield( + ALandscapeProxy * InLandscapeProxy, + const FHoudiniPackageParams &PackageParams, + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType); + + static bool BakeCurve( + USplineComponent* InSplineComponent, + ULevel* InLevel, + const FHoudiniPackageParams &PackageParams, + AActor*& OutActor, + USplineComponent*& OutSplineComponent, + FName InOverrideFolderPath=NAME_None, + AActor* InActor=nullptr); + + static bool BakeCurve( + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + // const TArray& InAllBakedOutputs, + const FHoudiniPackageParams &PackageParams, + FHoudiniAttributeResolver& InResolver, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static AActor* BakeInputHoudiniCurveToActor( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PakcageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform); + + static UBlueprint* BakeInputHoudiniCurveToBlueprint( + UHoudiniSplineComponent * InHoudiniSplineComponent, + const FHoudiniPackageParams & PakcageParams, + UWorld* WorldToSpawn, + const FTransform & SpawnTransform); + + static UStaticMesh* BakeStaticMesh( + UStaticMesh * StaticMesh, + const FHoudiniPackageParams & PackageParams, + const TArray& InAllOutputs, + const FDirectoryPath& InTempCookFolder, + TMap& InOutAlreadyBakedMaterialsMap); + + static bool BakeLandscape( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + bool bInReplaceActors, + bool bInReplaceAssets, + FString BakePath, + FString HoudiniAssetName, + FHoudiniEngineOutputStats& BakeStats); + + static bool BakeLandscapeObject( + FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInReplaceActors, + bool bInReplaceAssets, + FHoudiniPackageParams& PackageParams, + FHoudiniAttributeResolver& InResolver, + TArray& WorldsToUpdate, + TArray& OutPackagesToUnload, + FHoudiniEngineOutputStats& BakeStats); + + static bool BakeInstancerOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetcomponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FTransform& InTransform, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_ISMC( + const UHoudiniAssetComponent* HoudiniAssetcomponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_IAC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave); + + static bool BakeInstancerOutputToActors_MSIC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FTransform& InTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToActors_SMC( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static UStaticMesh * DuplicateStaticMeshAndCreatePackageIfNeeded( + UStaticMesh * InStaticMesh, + UStaticMesh * InPreviousBakeStaticMesh, + const FHoudiniPackageParams &PackageParams, + const TArray& InParentOutputs, + const TArray& InCurrentBakedActors, + const FString& InTemporaryCookFolder, + TArray & OutCreatedPackages, + TMap& InOutAlreadyBakedMaterialsMap); + + static UMaterialInterface * DuplicateMaterialAndCreatePackage( + UMaterialInterface * Material, + UMaterialInterface * PreviousBakeMaterial, + const FString & SubMaterialName, + const FHoudiniPackageParams& ObjectPackageParams, + TArray & OutCreatedPackages, + TMap& InOutAlreadyBakedMaterialsMap); + + static void ReplaceDuplicatedMaterialTextureSample( + UMaterialExpression * MaterialExpression, + UMaterialExpression* PreviousBakeMaterialExpression, + const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages); + + static UTexture2D * DuplicateTextureAndCreatePackage( + UTexture2D * Texture, + UTexture2D* PreviousBakeTexture, + const FString & SubTextureName, + const FHoudiniPackageParams& PackageParams, + TArray & OutCreatedPackages); + + // Bake a Houdini asset component (InHACToBake) based on the bInReplace and BakeOption arguments. + // Returns true if the underlying bake function (for example, BakeHoudiniActorToActors, returns true (or a valid UObject*)) + static bool BakeHoudiniAssetComponent( + UHoudiniAssetComponent* InHACToBake, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption InBakeOption, + bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors); + + static bool BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActors); + + static bool BakeHoudiniActorToActors( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeHoudiniOutputsToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + const TArray& InOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FTransform& InParentTransform, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutNewActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeInstancerOutputToFoliage( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + // const TArray& InAllBakedOutputs, + const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, + const FHoudiniOutputObject& InOutputObject, + FHoudiniBakedOutputObject& InBakedOutputObject, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap); + + static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); + + static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, TMap& InOutAlreadyBakedMaterialsMap); + + static bool BakeStaticMeshOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + const FDirectoryPath& InTempCookFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + TArray& OutPackagesToSave, + TMap& InOutAlreadyBakedMaterialsMap, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool ResolvePackageParams( + const UHoudiniAssetComponent* HoudiniAssetComponent, + UHoudiniOutput* InOutput, + const FHoudiniOutputObjectIdentifier& Identifier, + const FHoudiniOutputObject& InOutputObject, + const FString& InHoudiniAssetName, + const FString& DefaultObjectName, + const FDirectoryPath& InBakeFolder, + const bool bInReplaceAssets, + FHoudiniPackageParams& OutPackageParams, + TArray& OutPackagesToSave); + + static bool BakeHoudiniCurveOutputToActors( + const UHoudiniAssetComponent* HoudiniAssetComponent, + int32 InOutputIndex, + const TArray& InAllOutputs, + TArray& InBakedOutputs, + const FString& InHoudiniAssetName, + const FDirectoryPath& InBakeFolder, + bool bInReplaceActors, + bool bInReplaceAssets, + TArray& OutActors, + AActor* InFallbackActor=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + static bool BakeBlueprintsFromBakedActors( + const TArray& InBakedActors, + bool bInRecenterBakedActors, + bool bInReplaceAssets, + const FString& InAssetName, + const FDirectoryPath& InBakeFolder, + TArray* const InNonPDGBakedOutputs, + TMap* const InPDGBakedOutputs, + TArray& OutBlueprints, + TArray& OutPackagesToSave); + + static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, bool bInRecenterBakedActors); + + static bool BakeBlueprints( + UHoudiniAssetComponent* HoudiniAssetComponent, + bool bInReplaceAssets, + bool bInRecenterBakedActors, + FHoudiniEngineOutputStats& InBakeStats, + TArray& OutBlueprints, + TArray& OutPackagesToSave); + + static bool CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint); + + static void AddHoudiniMetaInformationToPackage( + UPackage * Package, UObject * Object, const TCHAR * Key, + const TCHAR * Value); + + static bool GetHoudiniGeneratedNameFromMetaInformation( + UPackage * Package, UObject * Object, FString & HoudiniName); + + static bool DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent); + + static void SaveBakedPackages(TArray & PackagesToSave, bool bSaveCurrentWorld = false); + + // Look for InObjectToFind among InOutputs. Return true if found and set OutOutputIndex and OutIdentifier. + static bool FindOutputObject( + const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier); + + static bool IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC); + + // Returns true if InObject is in InTemporaryCookFolder, or in the default Temporary cook folder from the runtime + // settings. + static bool IsObjectInTempFolder(UObject* const InObject, const FString& InTemporaryCookFolder); + + static bool IsObjectTemporary( + UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder); + + // Function used to copy properties from the source Static Mesh Component to the new (baked) one + static void CopyPropertyToNewActorAndComponent( + AActor* NewActor, + UStaticMeshComponent* NewSMC, + UStaticMeshComponent* InSMC, + bool bInCopyWorldTransform=false); + + // Finds the world/level indicated by the package path. + // If the level doesn't exists, it will be created. + // If InLevelPath is empty, outputs the editor world and current level + // Returns true if the world/level were found, false otherwise + static bool FindOrCreateDesiredLevelFromLevelPath( + const FString& InLevelPath, + ULevel*& OutDesiredLevel, + UWorld*& OutDesiredWorld, + bool& OutCreatedPackage); + + // Finds the actor indicated by InBakeActorName in InLevel. + // Returns false if any input was invalid (InLevel is invalid for example), true otherwise + // If an actor was found OutActor is set + // If bInNoPendingKillActors is true, then if an actor called InBakeActorName is found but is pending kill, then + // it is not set in OutActor + // If bRenamePendingKillActor is true, then if a pending kill actor call InBakeActorName is found it is renamed + // (uniquely) with a _Pending_Kill suffix (regardless of bInNoPendingKillActors). + static bool FindDesiredBakeActorFromBakeActorName( + const FString& InBakeActorName, + ULevel* InLevel, + AActor*& OutActor, + bool bInNoPendingKillActors=true, + bool bRenamePendingKillActor=true); + + // Helper that determines the desired bake actor name with unreal_bake_actor attribute, falling + // back to InDefaultActorName if the attribute is not set. + // If unreal_bake_actor is set, we look for such in InLevel, and use it *if* it is present in InAlLBakedOutputs. + // Otherwise if we are baking in replace mode, and the previous bake actor is available and in InLevel, return it + // as OutFoundActor. Otherwise return InFallbackActor as OutFoundActor. + // bOutHasBakeActorName indicates if the output has the unreal_bake_actor attribute set. + // OutFoundActor is the actor that was found (if one was found) + static bool FindUnrealBakeActor( + const FHoudiniOutputObject& InOutputObject, + const FHoudiniBakedOutputObject& InBakedOutputObject, + const TArray& InAllBakedActors, + ULevel* InLevel, + FName InDefaultActorName, + bool bInReplaceActorBakeMode, + AActor* InFallbackActor, + AActor*& OutFoundActor, + bool& bOutHasBakeActorName, + FName& OutBakeActorName); + + // Try to find an actor that we can use for baking. + // If the requested actor could not be found, then `OutWorld` and `OutLevel` + // should be used to spawn the new bake actor. + // @returns AActor* if found. Otherwise, returns nullptr. + static AActor* FindExistingActor_Bake( + UWorld* InWorld, + UHoudiniOutput* InOutput, + const FString& InActorName, + const FString& InPackagePath, + UWorld*& OutWorld, + ULevel*& OutLevel, + bool& bCreatedPackage); + + // Remove a previously baked actor + static bool RemovePreviouslyBakedActor( + AActor* InNewBakedActor, + ULevel* InLevel, + const FHoudiniPackageParams& InPackageParams); + + static bool RemovePreviouslyBakedComponent(UActorComponent* InComponent); + + // Get the world outliner folder path for output generated by InOutputOwner + static FName GetOutputFolderPath(UObject* InOutputOwner); + + static void RenameAsset(UObject* InAsset, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); + + // Helper function for renaming and relabelling an actor + static void RenameAndRelabelActor(AActor* InActor, const FString& InNewName, bool bMakeUniqueIfNotUnique=true); + + // Start: PDG Baking + + // Detach InActor from its parent, and rename to InNewName (attaches a numeric suffix to make it unique via + // MakeUniqueObjectName). Place it in the world outliner folder InFolderPath. + static bool DetachAndRenameBakedPDGOutputActor(AActor* InActor, const FString& InNewName, const FName& InFolderPath); + + static bool BakePDGWorkResultObject( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkResultArrayIndex, + int32 InWorkResultObjectArrayIndex, + bool bInReplaceActors, + bool bInReplaceAssets, + bool bInBakeToWorkResultActor, + bool bInIsAutoBake, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, + TArray const* InOutputTypesToBake=nullptr, + TArray const* InInstancerComponentTypesToBake=nullptr, + const FString& InFallbackWorldOutlinerFolder=""); + + // Checks if auto-bake is enabled on InPDGAssetLink, and if it is, calls BakePDGWorkResultObject. + static void CheckPDGAutoBakeAfterResultObjectLoaded( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + int32 InWorkItemHAPIIndex, + int32 InWorkItemResultInfoIndex); + + // Bake PDG output. This bakes all assets from all work items in the specified InNode (FTOPNode). + // It uses the existing output actors in the level, but breaks any links from these actors to the PDG link and + // moves the actors out of the parent Folder/ detaches from the parent PDG output actor. + static bool BakePDGTOPNodeOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Helper function to bake only a specific PDG TOP node's outputs to actors. + static bool BakePDGTOPNodeOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); + + // Bake PDG output. This bakes all assets from all work items in the specified TOP network. + // It uses the existing output actors in the level, but breaks any links + // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent + // PDG output actor. + static bool BakePDGTOPNetworkOutputsKeepActors( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + bool bInBakeForBlueprint, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + TArray& OutBakedActors, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes assets from TOP networks and nodes according to + // InPDGAssetLink->PDGBakeSelectionOption. It uses the existing output actors in the level, but breaks any links + // from these actors to the PDG link and moves the actors out of the parent Folder/ detaches from the parent + // PDG output actor. + static bool BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); + + // Bake PDG output. This bakes all supported assets from all work items in the specified InNode (FTOPNode). + // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from + // PDG output actors. + static bool BakePDGTOPNodeBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNode* InNode, + bool bInIsAutoBake, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Helper to bake only a specific PDG TOP node's outputs to blueprint(s). + static bool BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNode* InTOPNode, bool bInIsAutoBake, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); + + // Bake PDG output. This bakes all supported assets from all work items in the specified TOP network. + // It duplicates the output actors and bakes them to blueprints. Assets that were baked are removed from + // PDG output actors. + static bool BakePDGTOPNetworkBlueprints( + UHoudiniPDGAssetLink* InPDGAssetLink, + UTOPNetwork* InNetwork, + const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, + bool bInRecenterBakedActors, + TArray& OutBlueprints, + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); + + // Bake PDG output. This bakes assets from TOP networks and nodes according to + // InPDGAssetLink->PDGBakeSelectionOption. It duplicates the output actors and bakes them to blueprints. Assets + // that were baked are removed from PDG output actors. + static bool BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors); + + // End: PDG Baking + +protected: + + // Find the HGPO with matching identifier. Returns true if the HGPO was found. + static bool FindHGPO( + const FHoudiniOutputObjectIdentifier& InIdentifier, + const TArray& InHGPOs, + FHoudiniGeoPartObject const*& OutHGPO); + + // Set OutBakeName to the resolved output name of InMeshOutputObject / InObject. OutBakeName is set to the object's + // BakeName (the BakeName on the InMeshOutputObject, or if that is not set, the custom part name or finally the + // package name. + static void GetTemporaryOutputObjectBakeName( + const UObject* InObject, + const FHoudiniOutputObject& InMeshOutputObject, + FString& OutBakeName); + + // Look for InObject in InAllOutputs. If found the function returns true and OutBakeName is set to the object's + // BakeName (the BakeName on the OutputObject, or if that is not set, the custom part name or finally the package + // name. + static bool GetTemporaryOutputObjectBakeName( + const UObject* InObject, + EHoudiniOutputType InOutputType, + const TArray& InAllOutputs, + FString& OutBakeName); + + // Checks if InHoudiniAssetComponent has any current proxy mesh. Refines if it possible. Returns true + // if baking can continue, false otherwise. If the component has a proxy, but no cook data, then false is + // returned, the component is set to recook without a proxy and with bake after cook, and bOutNeedsReCook is set + // to true. + // bInReplace and BakeOption represents the baking settings to use if a delayed bake (post-cook) needs to be triggered. + static bool CheckForAndRefineHoudiniProxyMesh( + UHoudiniAssetComponent* InHoudiniAssetComponent, + bool bInReplacePreviousBake, + EHoudiniEngineBakeOption BakeOption, + bool bInRemoveHACOutputOnSuccess, + bool bInRecenterBakedActors, + bool& bOutNeedsReCook); + + // Position InActor at its bounding box center (keep components' world location) + static void CenterActorToBoundingBoxCenter(AActor* InActor); + + // Position each of the actors in InActors at its bounding box center (keep components' world location) + static void CenterActorsToBoundingBoxCenter(const TArray& InActors); + + // Helper to get or optionally create a RootComponent for an actor + static USceneComponent* GetActorRootComponent( + AActor* InActor, bool bCreateIfMissing=true, EComponentMobility::Type InMobilityIfCreated=EComponentMobility::Static); + + // Helper function to return a unique object name if the given is already in use + static FString MakeUniqueObjectNameIfNeeded(UObject* InOuter, const UClass* InClass, const FString& InName, UObject* InObjectThatWouldBeRenamed=nullptr); + + // Helper for getting the actor folder path for the world outliner, based unreal_bake_outliner_folder + static FName GetOutlinerFolderPath(const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); + + // Helper for setting the actor folder path in the world outliner + static bool SetOutlinerFolderPath(AActor* InActor, const FHoudiniOutputObject& InOutputObject, FName InDefaultFolder); + + // Helper for destroying previous bake components/actors + static uint32 DestroyPreviousBakeOutput( + FHoudiniBakedOutputObject& InBakedOutputObject, + bool bInDestroyBakedComponent, + bool bInDestroyBakedInstancedActors, + bool bInDestroyBakedInstancedComponents); + + static UMaterialInterface * BakeSingleMaterialToPackage(UMaterialInterface * InOriginalMaterial, const FHoudiniPackageParams & PackageParams, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp index 6b159a8a9..2ffae90c1 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp @@ -1,1824 +1,1831 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineCommands.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutputTranslator.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniOutput.h" - -#include "DesktopPlatformModule.h" -#include "Interfaces/IMainFrameModule.h" -#include "EditorDirectories.h" -#include "Misc/ScopedSlowTask.h" -#include "Async/Async.h" -#include "FileHelpers.h" -#include "AssetRegistryModule.h" -#include "Engine/ObjectLibrary.h" -#include "ObjectTools.h" -#include "CoreGlobals.h" -#include "HoudiniEngineOutputStats.h" -#include "Misc/FeedbackContext.h" -#include "HAL/FileManager.h" -#include "Modules/ModuleManager.h" -#include "ISettingsModule.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -FDelegateHandle FHoudiniEngineCommands::OnPostSaveWorldRefineProxyMeshesHandle = FDelegateHandle(); -FHoudiniEngineCommands::FOnHoudiniProxyMeshesRefinedDelegate FHoudiniEngineCommands::OnHoudiniProxyMeshesRefinedDelegate = FHoudiniEngineCommands::FOnHoudiniProxyMeshesRefinedDelegate(); - -void -FHoudiniEngineCommands::RegisterCommands() -{ - UI_COMMAND(_CreateSession, "Create Session", "Creates a new Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_ConnectSession, "Connect Session", "Connects to an existing Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_StopSession, "Stop Session", "Stops the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RestartSession, "Restart Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_OpenSessionSync, "Open Houdini Session Sync", "Opens Houdini with Session Sync and connect to it.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_CloseSessionSync, "Close Houdini Session Sync", "Close the Session Sync Houdini.", EUserInterfaceActionType::Button, FInputChord()); - - // Viewport Sync - UI_COMMAND(_ViewportSyncNone, "Disabled", "Do not sync viewports.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncUnreal, "Sync Unreal to Houdini.", "Sync the Unreal viewport to Houdini's.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncHoudini, "Sync Houdini to Unreal", "Sync the Houdini viewport to Unreal's.", EUserInterfaceActionType::Check, FInputChord()); - UI_COMMAND(_ViewportSyncBoth, "Both", "Sync both Unreal and Houdini's viewport.", EUserInterfaceActionType::Check, FInputChord()); - - // PDG Import Commandlet - UI_COMMAND(_StartPDGCommandlet, "Start Async Importer", "Start the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_StopPDGCommandlet, "Stop Async Importer", "Stops the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_IsPDGCommandletEnabled, "Enable Async Importer", "Enables the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Check, FInputChord()); - - UI_COMMAND(_InstallInfo, "Installation Info", "Display information on the current Houdini Engine installation", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_PluginSettings, "PluginSettings", "Displays the Houdini Engine plugin settings", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_OpenInHoudini, "Open scene in Houdini", "Opens the current Houdini scene in Houdini.", EUserInterfaceActionType::Button, FInputChord(EKeys::O, EModifierKey::Control | EModifierKey::Alt)); - UI_COMMAND(_SaveHIPFile, "Save Houdini scene (HIP)", "Saves a .hip file of the current Houdini scene.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_OnlineDoc, "Online Documentation", "Go to the plugin's online documentation.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_OnlineForum, "Online Forum", "Go to the plugin's online forum.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_ReportBug, "Report a bug", "Report a bug for Houdini Engine for Unreal plugin.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_CookAll, "Recook All", "Recooks all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_CookSelected, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::C, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_RebuildAll, "Rebuild All", "Rebuilds all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RebuildSelected, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::R, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_BakeAll, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_BakeSelected, "Bake And Replace Selection", "Bakes and replaces with blueprints selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::B, EModifierKey::Control | EModifierKey::Alt)); - - UI_COMMAND(_RefineAll, "Refine all Houdini Proxy Meshes To Static Meshes", "Builds and replaces all Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_RefineSelected, "Refine selected Houdini Proxy Meshes To Static Meshes", "Builds and replaces selected Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(_CleanUpTempFolder, "Clean Houdini Engine Temp Folder", "Deletes the unused temporary files in the Temporary Cook Folder.", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(_PauseAssetCooking, "Pause Houdini Engine Cooking", "When activated, prevents Houdini Engine from cooking assets until unpaused.", EUserInterfaceActionType::Check, FInputChord(EKeys::P, EModifierKey::Control | EModifierKey::Alt)); -} - -void -FHoudiniEngineCommands::SaveHIPFile() -{ - if (!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) - { - HOUDINI_LOG_ERROR(TEXT("Cannot save the Houdini scene, the Houdini Engine session hasn't been started.")); - return; - } - - IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get(); - if (!DesktopPlatform || !FHoudiniEngineUtils::IsInitialized()) - return; - - TArray< FString > SaveFilenames; - bool bSaved = false; - void * ParentWindowWindowHandle = NULL; - - IMainFrameModule & MainFrameModule = FModuleManager::LoadModuleChecked< IMainFrameModule >(TEXT("MainFrame")); - const TSharedPtr< SWindow > & MainFrameParentWindow = MainFrameModule.GetParentWindow(); - if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) - ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); - - bSaved = DesktopPlatform->SaveFileDialog( - ParentWindowWindowHandle, - NSLOCTEXT("SaveHIPFile", "SaveHIPFile", "Saves a .hip file of the current Houdini scene.").ToString(), - *(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT)), - TEXT(""), - TEXT("Houdini HIP file|*.hip"), - EFileDialogFlags::None, - SaveFilenames); - - if (bSaved && SaveFilenames.Num()) - { - // Add a slate notification - FString Notification = TEXT("Saving internal Houdini scene..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Saved Houdini scene to %s"), *SaveFilenames[0]); - - // Get first path. - std::string HIPPathConverted(TCHAR_TO_UTF8(*SaveFilenames[0])); - - // Save HIP file through Engine. - FHoudiniApi::SaveHIPFile(FHoudiniEngine::Get().GetSession(), HIPPathConverted.c_str(), false); - } -} - -void -FHoudiniEngineCommands::OpenInHoudini() -{ - if(!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) - { - HOUDINI_LOG_ERROR(TEXT("Cannot open the scene in Houdini, the Houdini Engine session hasn't been started.")); - return; - } - - // First, saves the current scene as a hip file - // Creates a proper temporary file name - FString UserTempPath = FPaths::CreateTempFilename( - FPlatformProcess::UserTempDir(), - TEXT("HoudiniEngine"), TEXT(".hip")); - - // Save HIP file through Engine. - std::string TempPathConverted(TCHAR_TO_UTF8(*UserTempPath)); - FHoudiniApi::SaveHIPFile( - FHoudiniEngine::Get().GetSession(), - TempPathConverted.c_str(), false); - - if (!FPaths::FileExists(UserTempPath)) - return; - - // Add a slate notification - FString Notification = TEXT("Opening scene in Houdini..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Add quotes to the path to avoid issues with spaces - UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); - - // Then open the hip file in Houdini - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - FString HoudiniExecutable = FHoudiniEngine::Get().GetHoudiniExecutable(); - FString HoudiniLocation = LibHAPILocation + TEXT("//") + HoudiniExecutable; - - FProcHandle ProcHandle = FPlatformProcess::CreateProc( - *HoudiniLocation, - *UserTempPath, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - if (!ProcHandle.IsValid()) - { - // Try with the steam version executable instead - HoudiniLocation = LibHAPILocation + TEXT("//hindie.steam"); - - ProcHandle = FPlatformProcess::CreateProc( - *HoudiniLocation, - *UserTempPath, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - if (!ProcHandle.IsValid()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to open scene in Houdini.")); - } - } - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini.")); -} - -void -FHoudiniEngineCommands::ReportBug() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::ShowInstallInfo() -{ - // TODO -} - -void -FHoudiniEngineCommands::ShowPluginSettings() -{ - FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Plugins"), FName("HoudiniEngine")); -} - -void -FHoudiniEngineCommands::OnlineDocumentation() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_DOC_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::OnlineForum() -{ - FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_FORUM_URL, nullptr, nullptr); -} - -void -FHoudiniEngineCommands::CleanUpTempFolder() -{ - // TODO: Improve me! slow now that we also have SM saved in the temp directory - // Due to the ref, we probably iterate a little too much, and should maybe do passes following the order of refs: - // mesh first, then materials, then textures. - // have a look at UWrangleContentCommandlet as well - - // Add a slate notification - FString Notification = TEXT("Cleaning up Houdini Engine temporary folder..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - GWarn->BeginSlowTask(LOCTEXT("CleanUpTemp", "Cleaning up the Houdini Engine Temp Folder"), false, false); - - // Get the default temp cook folder - FString TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - - TArray TempCookFolders; - TempCookFolders.Add(FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder()); - for (TObjectIterator It; It; ++It) - { - FString CookFolder = It->TemporaryCookFolder.Path; - if (CookFolder.IsEmpty()) - continue; - - TempCookFolders.AddUnique(CookFolder); - } - - // The Asset registry will help us finding if the content of the asset is referenced - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - int32 DeletedCount = 0; - bool bDidDeleteAsset = true; - while (bDidDeleteAsset) - { - // To correctly clean the temp folder, we need to iterate multiple times, because some of the temp assets - // might be referenced by other temp assets.. (ie Textures are referenced by Materials) - // We'll stop looking for assets to delete when no deletion occured. - bDidDeleteAsset = false; - - TArray AssetDataList; - for (auto& TempFolder : TempCookFolders) - { - // The Object library will list all UObjects found in the TempFolder - auto ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, true); - ObjectLibrary->LoadAssetDataFromPath(TempFolder); - - // Get all the assets found in the TEMP folder - TArray CurrentAssetDataList; - ObjectLibrary->GetAssetDataList(CurrentAssetDataList); - - AssetDataList.Append(CurrentAssetDataList); - } - - // All the assets we're going to delete - TArray AssetDataToDelete; - for (FAssetData Data : AssetDataList) - { - UPackage* CurrentPackage = Data.GetPackage(); - if (!CurrentPackage || CurrentPackage->IsPendingKill()) - continue; - - // Do not try to delete the package if it's referenced anywhere - TArray ReferenceNames; - AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, UE::AssetRegistry::EDependencyCategory::All); - if (ReferenceNames.Num() > 0) - continue; - - bool bAssetDataSafeToDelete = true; - TArray AssetsInPackage; - AssetRegistryModule.Get().GetAssetsByPackageName(CurrentPackage->GetFName(), AssetsInPackage); - for (const auto& AssetInfo : AssetsInPackage) - { - // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) - UObject* AssetInPackage = AssetInfo.GetAsset(); - if (!AssetInPackage || AssetInPackage->IsPendingKill()) - continue; - - FReferencerInformationList ReferencesIncludingUndo; - bool bReferencedInMemoryOrUndoStack = IsReferenced(AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo); - if (!bReferencedInMemoryOrUndoStack) - continue; - - // We do have external references, check if the external references are in our ObjectToDelete list - // If they are, we can delete the asset because its references are going to be deleted as well. - for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences) - { - UObject* Outer = ExtRef.Referencer->GetOuter(); - if (!Outer || Outer->IsPendingKill()) - continue; - - bool bOuterFound = false; - for (auto DataToDelete : AssetDataToDelete) - { - if (DataToDelete.GetPackage() == Outer) - { - bOuterFound = true; - break; - } - else if (DataToDelete.GetAsset() == Outer) - { - bOuterFound = true; - break; - } - } - - // We have at least one reference that's not going to be deleted, we have to keep the asset - if (!bOuterFound) - { - bAssetDataSafeToDelete = false; - break; - } - } - } - - if (bAssetDataSafeToDelete) - AssetDataToDelete.Add(Data); - } - - // Nothing to delete - if (AssetDataToDelete.Num() <= 0) - break; - - int32 CurrentDeleted = ObjectTools::DeleteAssets(AssetDataToDelete, false); - - if (CurrentDeleted > 0) - { - DeletedCount += CurrentDeleted; - bDidDeleteAsset = true; - } - } - - - // Now, go through all the directories in the temp directories and delete all the empty ones - IFileManager& FM = IFileManager::Get(); - // Lambda that parses a directory recursively and returns true if it is empty - auto IsEmptyFolder = [&FM](FString PathToDeleteOnDisk) - { - struct FEmptyFolderVisitor : public IPlatformFile::FDirectoryVisitor - { - bool bIsEmpty; - FEmptyFolderVisitor() - : bIsEmpty(true) - { - } - - virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override - { - if (!bIsDirectory) - { - bIsEmpty = false; - return false; // abort searching - } - - return true; // continue searching - } - }; - - // Look for files on disk in case the folder contains things not tracked by the asset registry - FEmptyFolderVisitor EmptyFolderVisitor; - IFileManager::Get().IterateDirectoryRecursively(*PathToDeleteOnDisk, EmptyFolderVisitor); - return EmptyFolderVisitor.bIsEmpty; - }; - - // Iterates on all the temporary cook directories recursively, - // And keep not of all the empty directories - FString TempCookPathOnDisk; - TArray FoldersToDelete; - if (FPackageName::TryConvertLongPackageNameToFilename(TempCookFolder, TempCookPathOnDisk)) - { - FM.IterateDirectoryRecursively(*TempCookPathOnDisk, [&FM, &FoldersToDelete, &IsEmptyFolder](const TCHAR* InFilenameOrDirectory, const bool InIsDirectory) -> bool - { - // Skip Files - if (!InIsDirectory) - return true; - - FString CurrentDirectoryPath = FString(InFilenameOrDirectory); - if (IsEmptyFolder(CurrentDirectoryPath)) - FoldersToDelete.Add(CurrentDirectoryPath); - - // keep iterating - return true; - }); - } - - int32 DeletedDirectories = 0; - for (auto& FolderPath : FoldersToDelete) - { - FString PathToDelete; - if (!FPackageName::TryConvertFilenameToLongPackageName(FolderPath, PathToDelete)) - continue; - - if (IFileManager::Get().DeleteDirectory(*FolderPath, false, true)) - { - AssetRegistryModule.Get().RemovePath(PathToDelete); - DeletedDirectories++; - } - } - - GWarn->EndSlowTask(); - - // Add a slate notification - Notification = TEXT("Deleted ") + FString::FromInt(DeletedCount) + TEXT(" temporary files and ") + FString::FromInt(DeletedDirectories) + TEXT(" directories."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Deleted %d temporary files and %d directories."), DeletedCount, DeletedDirectories); -} - -void -FHoudiniEngineCommands::BakeAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Baking all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 BakedCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component")); - continue; - } - - if (!HoudiniAssetComponent->IsComponentValid()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - if (AssetName != "Default__HoudiniAssetActor") - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is invalid"), *AssetName); - continue; - } - - // If component is not cooking or instancing, we can bake blueprint. - if (HoudiniAssetComponent->IsInstantiatingOrCooking()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is actively instantiating or cooking"), *AssetName); - continue; - } - - bool bSuccess = false; - bool BakeToBlueprints = true; - if (BakeToBlueprints) - { - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // bSuccess = true; - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - const bool bInReplaceAssets = true; - bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - if (bSuccess) - { - // Instantiate blueprints in component's level, then remove houdini asset actor - bSuccess = false; - ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); - if (IsValid(Level)) - { - UWorld* World = Level->GetWorld(); - if (IsValid(World)) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = Level; - FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); - for (UBlueprint* Blueprint : Blueprints) - { - if (!IsValid(Blueprint)) - continue; - World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); - } - - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - bSuccess = true; - } - } - } - } - else - { - // TODO: this used to have a way to not select in v1 - // if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent)) - // bSuccess = true; - const bool bReplaceActors = true; - const bool bReplaceAssets = true; - if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, bReplaceActors, bReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors)) - { - bSuccess = true; - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - } - } - - if (bSuccess) - BakedCount++; - } - - // Add a slate notification - Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); -} - -void -FHoudiniEngineCommands::PauseAssetCooking() -{ - // Revert the global flag - bool bCurrentCookingEnabled = !FHoudiniEngine::Get().IsCookingEnabled(); - FHoudiniEngine::Get().SetCookingEnabled(bCurrentCookingEnabled); - - // We need to refresh UI when pause cooking. Set refresh UI counter to be the number of current registered HACs. - if (!bCurrentCookingEnabled) - FHoudiniEngine::Get().SetUIRefreshCountWhenPauseCooking( FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount() ); - - // Add a slate notification - FString Notification = TEXT("Houdini Engine cooking paused"); - if (bCurrentCookingEnabled) - Notification = TEXT("Houdini Engine cooking resumed"); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - if (bCurrentCookingEnabled) - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking resumed.")); - else - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking paused.")); - - if (!bCurrentCookingEnabled) - return; - - /* - // If we are unpausing, tick each asset component to "update" them - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!")); - continue; - } - - HoudiniAssetComponent->StartHoudiniTicking(); - } - */ -} - -bool -FHoudiniEngineCommands::IsAssetCookingPaused() -{ - return !FHoudiniEngine::Get().IsCookingEnabled(); -} - -void -FHoudiniEngineCommands::RecookSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Cooking selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and cook the assets if they're in a valid state - int32 CookedCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedCook(); - CookedCount++; - } - - // Add a slate notification - Notification = TEXT("Re-cooking ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-cooking %d selected Houdini assets."), CookedCount); -} - -void -FHoudiniEngineCommands::RecookAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Cooking all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 CookedCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedCook(); - CookedCount++; - } - - // Add a slate notification - Notification = TEXT("Re-cooked ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-cooked %d Houdini assets in the current level."), CookedCount); -} - -void -FHoudiniEngineCommands::RebuildAllAssets() -{ - // Add a slate notification - FString Notification = TEXT("Re-building all assets in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Bakes and replaces with blueprints all Houdini Assets in the current level - int32 RebuiltCount = 0; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedRebuild(); - RebuiltCount++; - } - - // Add a slate notification - Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d Houdini assets in the current level."), RebuiltCount); -} - -void -FHoudiniEngineCommands::RebuildSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Rebuilding selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and rebuilds the assets if they're in a valid state - int32 RebuiltCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid()) - continue; - - HoudiniAssetComponent->MarkAsNeedRebuild(); - RebuiltCount++; - } - - // Add a slate notification - Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d selected Houdini assets."), RebuiltCount); -} - -void -FHoudiniEngineCommands::BakeSelection() -{ - // Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Baking selected Houdini Asset Actors in the current level..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and rebuilds the assets if they're in a valid state - int32 BakedCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); - continue; - } - - if (!HoudiniAssetComponent->IsComponentValid()) - { - FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); - HOUDINI_LOG_ERROR(TEXT("Failed to export Houdini Asset: %s in the scene!"), *AssetName); - continue; - } - - // If component is not cooking or instancing, we can bake blueprint. - if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) - { - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // BakedCount++; - // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) - // bSuccess = true; - FHoudiniEngineOutputStats BakeStats; - TArray PackagesToSave; - TArray Blueprints; - const bool bReplaceAssets = true; - const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, bReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); - FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); - - if (bSuccess) - { - // Instantiate blueprints in component's level, then remove houdini asset actor - ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); - if (IsValid(Level)) - { - UWorld* World = Level->GetWorld(); - if (IsValid(World)) - { - FActorSpawnParameters SpawnParams; - SpawnParams.OverrideLevel = Level; - FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); - for (UBlueprint* Blueprint : Blueprints) - { - if (!IsValid(Blueprint)) - continue; - World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); - } - - FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); - BakedCount++; - } - } - } - } - } - - // Add a slate notification - Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); -} - -// Recentre HoudiniAsset actors' pivots to their input / cooked static-mesh average centre. -void FHoudiniEngineCommands::RecentreSelection() -{ - /* -#if WITH_EDITOR - //Get current world selection - TArray WorldSelection; - int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (SelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Recentering selected Houdini Assets..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // Iterates over the selection and cook the assets if they're in a valid state - int32 RecentreCount = 0; - for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || !HoudiniAssetComponent->IsComponentValid()) - continue; - - // Get the average centre of all the created Static Meshes - FVector AverageBoundsCentre = FVector::ZeroVector; - int32 NumBounds = 0; - const FVector CurrentLocation = HoudiniAssetComponent->GetComponentLocation(); - { - //Check Static Meshes - TArray StaticMeshes; - StaticMeshes.Reserve(16); - HoudiniAssetComponent->GetAllUsedStaticMeshes(StaticMeshes); - - //Get average centre of all the static meshes. - for (const UStaticMesh* pMesh : StaticMeshes) - { - if (!pMesh) - continue; - - //to world space - AverageBoundsCentre += (pMesh->GetBounds().Origin + CurrentLocation); - NumBounds++; - } - } - - //Check Inputs - if (0 == NumBounds) - { - const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs; - for (const UHoudiniInput* pInput : AssetInputs) - { - if (!pInput || pInput->IsPendingKill()) - continue; - - // to world space - FBox Bounds = pInput->GetInputBounds(CurrentLocation); - if (Bounds.IsValid) - { - AverageBoundsCentre += Bounds.GetCenter(); - NumBounds++; - } - } - } - - //if we have more than one, get the average centre - if (NumBounds > 1) - { - AverageBoundsCentre /= (float)NumBounds; - } - - //if we need to move... - float fDist = FVector::DistSquared(CurrentLocation, AverageBoundsCentre); - if (NumBounds && fDist > 1.0f) - { - // Move actor to average centre and recook - // This will refresh the static mesh under the HoudiniAssestComponent ( undoing the translation ). - HoudiniAssetActor->SetActorLocation(AverageBoundsCentre, false, nullptr, ETeleportType::TeleportPhysics); - - // Recook now the houdini-static-mesh has a new origin - HoudiniAssetComponent->StartTaskAssetCookingManual(); - RecentreCount++; - } - } - - if (RecentreCount) - { - // UE4 Editor doesn't refresh the translation-handles until they are re-selected, confusing the user, deselect the objects. - GEditor->SelectNone(true, false); - } - - // Add a slate notification - Notification = TEXT("Re-centred ") + FString::FromInt(RecentreCount) + TEXT(" Houdini assets."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Re-centred %d selected Houdini assets."), RecentreCount); - -#endif //WITH_EDITOR - */ -} - -void -FHoudiniEngineCommands::OpenSessionSync() -{ - //if (!FHoudiniEngine::IsInitialized()) - // return; - - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - HAPI Not initialized")); - return; - } - - // Get the runtime settings to get the session/type and settings - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - - EHoudiniRuntimeSettingsSessionType SessionType = HoudiniRuntimeSettings->SessionType; - FString ServerPipeName = HoudiniRuntimeSettings->ServerPipeName; - int32 ServerPort = HoudiniRuntimeSettings->ServerPort; - - FString SessionSyncArgs = TEXT("-hess="); - if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe) - { - // Add the -hess=pipe:hapi argument - SessionSyncArgs += TEXT("pipe:") + ServerPipeName; - } - else if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_Socket) - { - // Add the -hess=port:9090 argument - SessionSyncArgs += TEXT("port:") + FString::FromInt(ServerPort); - } - else - { - // Invalid session type - HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - Invalid session type")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Opening Houdini Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Opening Houdini Session Sync.")); - - // Only launch Houdini in Session sync if we havent started it already! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - if (!FPlatformProcess::IsProcRunning(PreviousHESS)) - { - // Start houdini with the -hess commandline args - const FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); -# if PLATFORM_MAC - const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../Resources/bin"); -# elif PLATFORM_LINUX - const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../bin"); -# elif PLATFORM_WINDOWS - const FString HoudiniExeLocationRelativeToLibHAPI; -# else - // Treat an unknown platform the same as Windows for now - const FString HoudiniExeLocationRelativeToLibHAPI; -# endif - - FString HoudiniExecutable = FHoudiniEngine::Get().GetHoudiniExecutable(); - FString HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/") + HoudiniExecutable; - HOUDINI_LOG_MESSAGE(TEXT("Path to houdini executable: %s"), *HoudiniLocation); - FProcHandle HESSHandle = FPlatformProcess::CreateProc( - *HoudiniLocation, - *SessionSyncArgs, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - if (!HESSHandle.IsValid()) - { - // Try with the steam version executable instead - HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/hindie.steam"); - HOUDINI_LOG_MESSAGE(TEXT("Path to hindie.steam executable: %s"), *HoudiniLocation); - - HESSHandle = FPlatformProcess::CreateProc( - *HoudiniLocation, - *SessionSyncArgs, - true, false, false, - nullptr, 0, - FPlatformProcess::UserTempDir(), - nullptr, nullptr); - - if (!HESSHandle.IsValid()) - { - HOUDINI_LOG_ERROR(TEXT("Failed to launch Houdini in Session Sync mode.")); - return; - } - } - - // Keep track of the SessionSync ProcHandle - FHoudiniEngine::Get().SetHESSProcHandle(HESSHandle); - } - - // Start an Async task to connect to Session Sync - Async(EAsyncExecution::TaskGraphMainThread, [SessionType, ServerPipeName, ServerPort]() - { - // Use a timeout to avoid waiting indefinitely for H to start in session sync mode - const double Timeout = 180.0; // 3min - const double StartTimestamp = FPlatformTime::Seconds(); - - FString ServerHost = TEXT("localhost"); - while (!FHoudiniEngine::Get().SessionSyncConnect(SessionType, ServerPipeName, ServerHost, ServerPort)) - { - // Houdini might not be done loading, sleep for one second - FPlatformProcess::Sleep(1); - - // Check for the timeout - if (FPlatformTime::Seconds() - StartTimestamp > Timeout) - { - // ... and a log message - HOUDINI_LOG_ERROR(TEXT("Failed to start SessionSync - Timeout...")); - return false; - } - } - - // Initialize HAPI with this session - if (!FHoudiniEngine::Get().InitializeHAPISession()) - { - FHoudiniEngine::Get().StopTicking(); - return false; - } - - // Notify all HACs that they need to instantiate in the new session - MarkAllHACsAsNeedInstantiation(); - - // Start ticking - FHoudiniEngine::Get().StartTicking(); - - // Add a slate notification - FString Notification = TEXT("Succesfully connected to Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Succesfully connected to Session Sync...")); - - return true; - }); -} - -void -FHoudiniEngineCommands::CloseSessionSync() -{ - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to stop Session Sync - HAPI Not initialized")); - return; - } - - // Add a slate notification - FString Notification = TEXT("Stopping Houdini Session Sync..."); - FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // ... and a log message - HOUDINI_LOG_MESSAGE(TEXT("Stopping Houdini Session Sync.")); - - // Stop Houdini Session sync if it is still running! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - if (FPlatformProcess::IsProcRunning(PreviousHESS)) - { - FPlatformProcess::TerminateProc(PreviousHESS, true); - } -} - -void -FHoudiniEngineCommands::SetViewportSync(const int32& ViewportSync) -{ - if (ViewportSync == 1) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); - } - else if (ViewportSync == 2) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); - } - else if (ViewportSync == 3) - { - FHoudiniEngine::Get().SetSyncViewportEnabled(true); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); - } - else - { - FHoudiniEngine::Get().SetSyncViewportEnabled(false); - FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); - FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); - } -} - -int32 -FHoudiniEngineCommands::GetViewportSync() -{ - if(!FHoudiniEngine::Get().IsSyncViewportEnabled()) - return 0; - - bool bSyncH = FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled(); - bool bSyncU = FHoudiniEngine::Get().IsSyncUnrealViewportEnabled(); - if (bSyncH && !bSyncU) - return 1; - else if (!bSyncH && bSyncU) - return 2; - else if (bSyncH && bSyncU) - return 3; - else - return 0; -} - -void -FHoudiniEngineCommands::RestartSession() -{ - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().RestartSession()) - return; - - // We've successfully restarted the Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::CreateSession() -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().CreateSession(HoudiniRuntimeSettings->SessionType)) - return; - - // We've successfully created the Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::ConnectSession() -{ - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().ConnectSession(HoudiniRuntimeSettings->SessionType)) - return; - - // We've successfully connected to a Houdini Engine session, - // We now need to notify all the HoudiniAssetComponent that they need to re instantiate - // themselves in the new Houdini engine session. - MarkAllHACsAsNeedInstantiation(); -} - -void -FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation() -{ - // Notify all the HoudiniAssetComponents that they need to re instantiate themselves in the new Houdini engine session. - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssetComponent->MarkAsNeedInstantiation(); - } -} - -bool -FHoudiniEngineCommands::IsSessionValid() -{ - return FHoudiniEngine::IsInitialized(); -} - -bool -FHoudiniEngineCommands::IsSessionSyncProcessValid() -{ - // Only launch Houdini in Session sync if we havent started it already! - FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); - return FPlatformProcess::IsProcRunning(PreviousHESS); -} - -void -FHoudiniEngineCommands::StopSession() -{ - // Restart the current Houdini Engine Session - if (!FHoudiniEngine::Get().StopSession()) - { - // StopSession returns false only if Houdini is not initialized - HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); - } - else - { - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine session stopped.")); - } -} - -EHoudiniProxyRefineRequestResult -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE) -{ - // Get current world selection - TArray WorldSelection; - int32 NumSelectedHoudiniAssets = 0; - if (bOnlySelectedActors) - { - NumSelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); - if (NumSelectedHoudiniAssets <= 0) - { - HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); - return EHoudiniProxyRefineRequestResult::Invalid; - } - } - - // Add a slate notification - FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - - // First find the components that have meshes that we must refine - TArray ComponentsToRefine; - TArray ComponentsToCook; - // Components that would be candidates for refinement/cooking, but have errors - TArray SkippedComponents; - if (bOnlySelectedActors) - { - for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Index]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - } - else - { - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - } - - return RefineTriagedHoudiniProxyMesehesToStaticMeshes( - ComponentsToRefine, - ComponentsToCook, - SkippedComponents, - bSilent, - bRefineAll, - bOnPreSaveWorld, - OnPreSaveWorld, - bOnPreBeginPIE - ); -} - -EHoudiniProxyRefineRequestResult -FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent) -{ - const bool bRefineAll = true; - const bool bOnPreSaveWorld = false; - UWorld* OnPreSaveWorld = nullptr; - const bool bOnPreBeginPIE = false; - - // First find the components that have meshes that we must refine - TArray ComponentsToRefine; - TArray ComponentsToCook; - // Components that would be candidates for refinement/cooking, but have errors - TArray SkippedComponents; - for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine) - { - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and - // flags passed to the function. - TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); - } - - return RefineTriagedHoudiniProxyMesehesToStaticMeshes( - ComponentsToRefine, - ComponentsToCook, - SkippedComponents, - bSilent, - bRefineAll, - bOnPreSaveWorld, - OnPreSaveWorld, - bOnPreBeginPIE - ); -} - -void -FHoudiniEngineCommands::StartPDGCommandlet() -{ - FHoudiniEngine::Get().StartPDGCommandlet(); -} - -void -FHoudiniEngineCommands::StopPDGCommandlet() -{ - FHoudiniEngine::Get().StopPDGCommandlet(); -} - -bool -FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected() -{ - return FHoudiniEngine::Get().IsPDGCommandletRunningOrConnected(); -} - -bool -FHoudiniEngineCommands::IsPDGCommandletEnabled() -{ - const UHoudiniRuntimeSettings* const Settings = GetDefault(); - if (IsValid(Settings)) - { - return Settings->bPDGAsyncCommandletImportEnabled; - } - - return false; -} - -bool -FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled) -{ - UHoudiniRuntimeSettings* const Settings = GetMutableDefault(); - if (IsValid(Settings)) - { - Settings->bPDGAsyncCommandletImportEnabled = InEnabled; - return true; - } - - return false; -} - -void -FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped) -{ - if (!InHAC || InHAC->IsPendingKill()) - return; - - // Make sure that the component's World and Owner are valid - AActor *Owner = InHAC->GetOwner(); - if (!Owner || Owner->IsPendingKill()) - return; - - UWorld *World = InHAC->GetWorld(); - if (!World || World->IsPendingKill()) - return; - - if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World) - return; - - // Check if we should consider this component for proxy mesh refinement based on its settings and - // flags passed to the function - if (bRefineAll || - (bOnPreSaveWorld && InHAC->IsProxyStaticMeshRefinementOnPreSaveWorldEnabled()) || - (bOnPreBeginPIE && InHAC->IsProxyStaticMeshRefinementOnPreBeginPIEEnabled())) - { - TArray ProxyMeshPackagesToSave; - TArray ComponentsWithProxiesToSave; - - if (InHAC->HasAnyCurrentProxyOutput()) - { - // Get the state of the asset and check if it is cooked - // If it is not cook, request a cook. We can only build the UStaticMesh - // if the data from the cook is available - // If the state is not pre-cook, or None (cooked), then the state is invalid, - // log an error and skip the component - bool bNeedsRebuildOrDelete = false; - bool bUnsupportedState = false; - const bool bCookedDataAvailable = InHAC->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bUnsupportedState); - if (bCookedDataAvailable) - { - OutToRefine.Add(InHAC); - ComponentsWithProxiesToSave.Add(InHAC); - } - else if (!bUnsupportedState && !bNeedsRebuildOrDelete) - { - InHAC->MarkAsNeedCook(); - // Force the output of the cook to be directly created as a UStaticMesh and not a proxy - InHAC->SetNoProxyMeshNextCookRequested(true); - OutToCook.Add(InHAC); - ComponentsWithProxiesToSave.Add(InHAC); - } - else - { - OutSkipped.Add(InHAC); - const EHoudiniAssetState AssetState = InHAC->GetAssetState(); - HOUDINI_LOG_ERROR(TEXT("Could not refine %s, the asset is in an unsupported state: %s"), *(InHAC->GetPathName()), *(UEnum::GetValueAsString(AssetState))); - } - } - else if (InHAC->HasAnyProxyOutput()) - { - // If the HAC has non-current proxies, destroy them - // TODO: Make this its own command? - const uint32 NumOutputs = InHAC->GetNumOutputs(); - for (uint32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = InHAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - TMap& OutputObjects = Output->GetOutputObjects(); - for (auto& CurrentPair : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; - if (!CurrentOutputObject.bProxyIsCurrent) - { - // The proxy is not current, delete it and its component - USceneComponent* FoundProxyComponent = Cast(CurrentOutputObject.ProxyComponent); - if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill()) - { - // Remove from the HoudiniAssetActor - if (FoundProxyComponent->GetOwner()) - FoundProxyComponent->GetOwner()->RemoveOwnedComponent(FoundProxyComponent); - - FoundProxyComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - FoundProxyComponent->UnregisterComponent(); - FoundProxyComponent->DestroyComponent(); - } - - UObject* ProxyObject = CurrentOutputObject.ProxyObject; - if (!ProxyObject || ProxyObject->IsPendingKill()) - continue; - - ProxyObject->MarkPendingKill(); - ProxyObject->MarkPackageDirty(); - UPackage* const Package = ProxyObject->GetPackage(); - if (IsValid(Package)) - ProxyMeshPackagesToSave.Add(Package); - } - } - } - } - - for (UHoudiniAssetComponent* const HAC : ComponentsWithProxiesToSave) - { - const uint32 NumOutputs = HAC->GetNumOutputs(); - for (uint32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - TMap& OutputObjects = Output->GetOutputObjects(); - for (auto& CurrentPair : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; - if (CurrentOutputObject.bProxyIsCurrent && CurrentOutputObject.ProxyObject) - { - UPackage* const Package = CurrentOutputObject.ProxyObject->GetPackage(); - if (IsValid(Package) && Package->IsDirty()) - ProxyMeshPackagesToSave.Add(Package); - } - } - } - } - - if (ProxyMeshPackagesToSave.Num() > 0) - { - TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - FEditorFileUtils::PromptForCheckoutAndSave(ProxyMeshPackagesToSave, true, false); - } - } -} - -EHoudiniProxyRefineRequestResult -FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( - const TArray& InComponentsToRefine, - const TArray& InComponentsToCook, - const TArray& InSkippedComponents, - bool bInSilent, - bool bInRefineAll, - bool bInOnPreSaveWorld, - UWorld* InOnPreSaveWorld, - bool bInOnPrePIEBeginPlay) -{ - // Slate notification text - FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); - - const uint32 NumComponentsToCook = InComponentsToCook.Num(); - const uint32 NumComponentsToRefine = InComponentsToRefine.Num(); - const uint32 NumComponentsToProcess = NumComponentsToCook + NumComponentsToRefine; - - TArray SuccessfulComponents; - TArray FailedComponents; - TArray SkippedComponents(InSkippedComponents); - - if (NumComponentsToProcess > 0) - { - // The task progress pointer is potentially going to be shared with a background thread and tasks - // on the main thread, so make it thread safe - TSharedPtr TaskProgress = MakeShareable(new FSlowTask((float)NumComponentsToProcess, FText::FromString(Notification))); - TaskProgress->Initialize(); - if (!bInSilent) - TaskProgress->MakeDialog(/*bShowCancelButton=*/true); - - // Iterate over the components for which we can build UStaticMesh, and build the meshes - bool bCancelled = false; - for (uint32 ComponentIndex = 0; ComponentIndex < NumComponentsToRefine; ++ComponentIndex) - { - UHoudiniAssetComponent* HoudiniAssetComponent = InComponentsToRefine[ComponentIndex]; - TaskProgress->EnterProgressFrame(1.0f); - const bool bDestroyProxies = true; - FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HoudiniAssetComponent, bDestroyProxies); - - SuccessfulComponents.Add(HoudiniAssetComponent); - - bCancelled = TaskProgress->ShouldCancel(); - if (bCancelled) - { - for (uint32 SkippedIndex = ComponentIndex + 1; SkippedIndex < NumComponentsToRefine; ++SkippedIndex) - { - SkippedComponents.Add(InComponentsToRefine[ComponentIndex]); - } - break; - } - } - - if (bCancelled && NumComponentsToCook > 0) - { - for (UHoudiniAssetComponent* const HAC : InComponentsToCook) - { - SkippedComponents.Add(HAC); - } - } - - if (NumComponentsToCook > 0 && !bCancelled) - { - // Now use an async task to check on the progress of the cooking components - Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents]() { - RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread( - InComponentsToCook, TaskProgress, NumComponentsToProcess, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); - }); - - // We have to wait for cook(s) before completing refinement - return EHoudiniProxyRefineRequestResult::PendingCooks; - } - else - { - RefineHoudiniProxyMeshesToStaticMeshesNotifyDone( - NumComponentsToProcess, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); - - // We didn't have to cook anything, so refinement is complete. - return EHoudiniProxyRefineRequestResult::Refined; - } - } - - // Nothing to refine - return EHoudiniProxyRefineRequestResult::None; -} - - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray& InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumComponentsToProcess, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents) -{ - // Copy to a double linked list so that we can loop through - // to check progress of each component and remove it easily - // if it has completed/failed - TDoubleLinkedList CookList; - for (UHoudiniAssetComponent *HAC : InComponentsToCook) - { - CookList.AddTail(HAC); - } - - // Add the successfully cooked components to the incoming successful components (previously refined) - TArray SuccessfulComponents(InSuccessfulComponents); - TArray FailedComponents(InFailedComponents); - TArray SkippedComponents(InSkippedComponents); - - bool bCancelled = false; - uint32 NumFailedToCook = 0; - while (CookList.Num() > 0 && !bCancelled) - { - TDoubleLinkedList::TDoubleLinkedListNode *Node = CookList.GetHead(); - while (Node && !bCancelled) - { - TDoubleLinkedList::TDoubleLinkedListNode *Next = Node->GetNextNode(); - UHoudiniAssetComponent* HAC = Node->GetValue(); - - if (HAC && !HAC->IsPendingKill()) - { - const EHoudiniAssetState State = HAC->GetAssetState(); - const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult(); - bool bUpdateProgress = false; - if (State == EHoudiniAssetState::None) - { - // Cooked, count as success, remove node - CookList.RemoveNode(Node); - SuccessfulComponents.Add(HAC); - bUpdateProgress = true; - } - else if (ResultState != EHoudiniAssetStateResult::None && ResultState != EHoudiniAssetStateResult::Working) - { - // Failed, remove node - HOUDINI_LOG_ERROR(TEXT("Failed to cook %s to obtain static mesh."), *(HAC->GetPathName())); - CookList.RemoveNode(Node); - FailedComponents.Add(HAC); - bUpdateProgress = true; - NumFailedToCook++; - } - - if (bUpdateProgress && InTaskProgress.IsValid()) - { - // Update progress only on the main thread, and check for cancellation request - bCancelled = Async(EAsyncExecution::TaskGraphMainThread, [InTaskProgress]() { - InTaskProgress->EnterProgressFrame(1.0f); - return InTaskProgress->ShouldCancel(); - }).Get(); - } - } - else - { - SkippedComponents.Add(HAC); - CookList.RemoveNode(Node); - } - - Node = Next; - } - FPlatformProcess::Sleep(0.01f); - } - - if (bCancelled) - { - HOUDINI_LOG_WARNING(TEXT("Mesh refinement cancelled while waiting for %d components to cook."), CookList.Num()); - // Mark any remaining HACs in the cook list as skipped - TDoubleLinkedList::TDoubleLinkedListNode* Node = CookList.GetHead(); - while (Node) - { - TDoubleLinkedList::TDoubleLinkedListNode* const Next = Node->GetNextNode(); - UHoudiniAssetComponent* HAC = Node->GetValue(); - if (HAC) - SkippedComponents.Add(HAC); - CookList.RemoveNode(Node); - Node = Next; - } - } - - // Cooking is done, or failed, display the notifications on the main thread - Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents]() { - RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); - }); -} - -void -FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(const uint32 InNumTotalComponents, FSlowTask* const InTaskProgress, const bool bCancelled, const bool bOnPreSaveWorld, UWorld* const InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents) -{ - FString Notification; - const uint32 NumSkippedComponents = InSkippedComponents.Num(); - const uint32 NumFailedToCook = InFailedComponents.Num(); - if (NumSkippedComponents + NumFailedToCook > 0) - { - if (bCancelled) - { - Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), NumSkippedComponents + NumFailedToCook, InNumTotalComponents); - } - else - { - Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), NumSkippedComponents + NumFailedToCook, InNumTotalComponents); - } - FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_ERROR(TEXT("%s"), *Notification); - } - else if (InNumTotalComponents > 0) - { - Notification = TEXT("Done: Refining Houdini proxy meshes to static meshes."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - HOUDINI_LOG_MESSAGE(TEXT("%s"), *Notification); - } - if (InTaskProgress) - { - InTaskProgress->Destroy(); - } - if (bOnPreSaveWorld && InSuccessfulComponents.Num() > 0) - { - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - - // Save the dirty static meshes in InSuccessfulComponents OnPostSaveWorld - // TODO: Remove? This may not be necessary now as we save all dirty temporary cook data in PostSaveWorld() already (Static Meshes, Materials...) - OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([InSuccessfulComponents, bOnPreSaveWorld, InOnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) { - if (bOnPreSaveWorld && InOnPreSaveWorld && InOnPreSaveWorld != InWorld) - return; - - RefineProxyMeshesHandleOnPostSaveWorld(InSuccessfulComponents, InSaveFlags, InWorld, bInSuccess); - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - }); - } - - // Broadcast refinement result per HAC - for (UHoudiniAssetComponent* const HAC : InSuccessfulComponents) - { - if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) - OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Success); - } - for (UHoudiniAssetComponent* const HAC : InFailedComponents) - { - if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) - OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Failed); - } - for (UHoudiniAssetComponent* const HAC : InSkippedComponents) - { - if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) - OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Skipped); - } -} - -void -FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) -{ - TArray PackagesToSave; - - for (UHoudiniAssetComponent* HAC : InSuccessfulComponents) - { - if (!HAC || HAC->IsPendingKill()) - continue; - - const int32 NumOutputs = HAC->GetNumOutputs(); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - if (Output->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (auto &OutputObjectPair : Output->GetOutputObjects()) - { - UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) - continue; - - UStaticMesh *SM = Cast(Obj); - if (!SM) - continue; - - UPackage *Package = SM->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - } - } - - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineCommands.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutputTranslator.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniOutput.h" +#include "HoudiniEngineStyle.h" + +#include "DesktopPlatformModule.h" +#include "Interfaces/IMainFrameModule.h" +#include "EditorDirectories.h" +#include "Misc/ScopedSlowTask.h" +#include "Async/Async.h" +#include "FileHelpers.h" +#include "AssetRegistryModule.h" +#include "Engine/ObjectLibrary.h" +#include "ObjectTools.h" +#include "CoreGlobals.h" +#include "HoudiniEngineOutputStats.h" +#include "Misc/FeedbackContext.h" +#include "HAL/FileManager.h" +#include "Modules/ModuleManager.h" +#include "ISettingsModule.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +FDelegateHandle FHoudiniEngineCommands::OnPostSaveWorldRefineProxyMeshesHandle = FDelegateHandle(); +FHoudiniEngineCommands::FOnHoudiniProxyMeshesRefinedDelegate FHoudiniEngineCommands::OnHoudiniProxyMeshesRefinedDelegate = FHoudiniEngineCommands::FOnHoudiniProxyMeshesRefinedDelegate(); + +FHoudiniEngineCommands::FHoudiniEngineCommands() + : TCommands (TEXT("HoudiniEngine"), NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), NAME_None, FHoudiniEngineStyle::GetStyleSetName()) +{ +} + +void +FHoudiniEngineCommands::RegisterCommands() +{ + UI_COMMAND(_CreateSession, "Create Session", "Creates a new Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_ConnectSession, "Connect Session", "Connects to an existing Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_StopSession, "Stop Session", "Stops the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RestartSession, "Restart Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_OpenSessionSync, "Open Houdini Session Sync", "Opens Houdini with Session Sync and connect to it.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_CloseSessionSync, "Close Houdini Session Sync", "Close the Session Sync Houdini.", EUserInterfaceActionType::Button, FInputChord()); + + // Viewport Sync + UI_COMMAND(_ViewportSyncNone, "Disabled", "Do not sync viewports.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncUnreal, "Sync Unreal to Houdini.", "Sync the Unreal viewport to Houdini's.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncHoudini, "Sync Houdini to Unreal", "Sync the Houdini viewport to Unreal's.", EUserInterfaceActionType::Check, FInputChord()); + UI_COMMAND(_ViewportSyncBoth, "Both", "Sync both Unreal and Houdini's viewport.", EUserInterfaceActionType::Check, FInputChord()); + + // PDG Import Commandlet + UI_COMMAND(_StartPDGCommandlet, "Start Async Importer", "Start the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_StopPDGCommandlet, "Stop Async Importer", "Stops the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_IsPDGCommandletEnabled, "Enable Async Importer", "Enables the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Check, FInputChord()); + + UI_COMMAND(_InstallInfo, "Installation Info", "Display information on the current Houdini Engine installation", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_PluginSettings, "PluginSettings", "Displays the Houdini Engine plugin settings", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_OpenInHoudini, "Open scene in Houdini", "Opens the current Houdini scene in Houdini.", EUserInterfaceActionType::Button, FInputChord(EKeys::O, EModifierKey::Control | EModifierKey::Alt)); + UI_COMMAND(_SaveHIPFile, "Save Houdini scene (HIP)", "Saves a .hip file of the current Houdini scene.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_OnlineDoc, "Online Documentation", "Go to the plugin's online documentation.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_OnlineForum, "Online Forum", "Go to the plugin's online forum.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_ReportBug, "Report a bug", "Report a bug for Houdini Engine for Unreal plugin.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_CookAll, "Recook All", "Recooks all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_CookSelected, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::C, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_RebuildAll, "Rebuild All", "Rebuilds all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RebuildSelected, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::R, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_BakeAll, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_BakeSelected, "Bake And Replace Selection", "Bakes and replaces with blueprints selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::B, EModifierKey::Control | EModifierKey::Alt)); + + UI_COMMAND(_RefineAll, "Refine all Houdini Proxy Meshes To Static Meshes", "Builds and replaces all Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_RefineSelected, "Refine selected Houdini Proxy Meshes To Static Meshes", "Builds and replaces selected Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(_CleanUpTempFolder, "Clean Houdini Engine Temp Folder", "Deletes the unused temporary files in the Temporary Cook Folder.", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(_PauseAssetCooking, "Pause Houdini Engine Cooking", "When activated, prevents Houdini Engine from cooking assets until unpaused.", EUserInterfaceActionType::Check, FInputChord(EKeys::P, EModifierKey::Control | EModifierKey::Alt)); +} + +void +FHoudiniEngineCommands::SaveHIPFile() +{ + if (!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) + { + HOUDINI_LOG_ERROR(TEXT("Cannot save the Houdini scene, the Houdini Engine session hasn't been started.")); + return; + } + + IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get(); + if (!DesktopPlatform || !FHoudiniEngineUtils::IsInitialized()) + return; + + TArray< FString > SaveFilenames; + bool bSaved = false; + void * ParentWindowWindowHandle = NULL; + + IMainFrameModule & MainFrameModule = FModuleManager::LoadModuleChecked< IMainFrameModule >(TEXT("MainFrame")); + const TSharedPtr< SWindow > & MainFrameParentWindow = MainFrameModule.GetParentWindow(); + if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) + ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); + + bSaved = DesktopPlatform->SaveFileDialog( + ParentWindowWindowHandle, + NSLOCTEXT("SaveHIPFile", "SaveHIPFile", "Saves a .hip file of the current Houdini scene.").ToString(), + *(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT)), + TEXT(""), + TEXT("Houdini HIP file|*.hip"), + EFileDialogFlags::None, + SaveFilenames); + + if (bSaved && SaveFilenames.Num()) + { + // Add a slate notification + FString Notification = TEXT("Saving internal Houdini scene..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Saved Houdini scene to %s"), *SaveFilenames[0]); + + // Get first path. + std::string HIPPathConverted(TCHAR_TO_UTF8(*SaveFilenames[0])); + + // Save HIP file through Engine. + FHoudiniApi::SaveHIPFile(FHoudiniEngine::Get().GetSession(), HIPPathConverted.c_str(), false); + } +} + +void +FHoudiniEngineCommands::OpenInHoudini() +{ + if(!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr) + { + HOUDINI_LOG_ERROR(TEXT("Cannot open the scene in Houdini, the Houdini Engine session hasn't been started.")); + return; + } + + // First, saves the current scene as a hip file + // Creates a proper temporary file name + FString UserTempPath = FPaths::CreateTempFilename( + FPlatformProcess::UserTempDir(), + TEXT("HoudiniEngine"), TEXT(".hip")); + + // Save HIP file through Engine. + std::string TempPathConverted(TCHAR_TO_UTF8(*UserTempPath)); + FHoudiniApi::SaveHIPFile( + FHoudiniEngine::Get().GetSession(), + TempPathConverted.c_str(), false); + + if (!FPaths::FileExists(UserTempPath)) + return; + + // Add a slate notification + FString Notification = TEXT("Opening scene in Houdini..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Add quotes to the path to avoid issues with spaces + UserTempPath = TEXT("\"") + UserTempPath + TEXT("\""); + + // Then open the hip file in Houdini + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + FString HoudiniExecutable = FHoudiniEngine::Get().GetHoudiniExecutable(); + FString HoudiniLocation = LibHAPILocation + TEXT("//") + HoudiniExecutable; + + FProcHandle ProcHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *UserTempPath, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!ProcHandle.IsValid()) + { + // Try with the steam version executable instead + HoudiniLocation = LibHAPILocation + TEXT("//hindie.steam"); + + ProcHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *UserTempPath, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!ProcHandle.IsValid()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to open scene in Houdini.")); + } + } + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini.")); +} + +void +FHoudiniEngineCommands::ReportBug() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::ShowInstallInfo() +{ + // TODO +} + +void +FHoudiniEngineCommands::ShowPluginSettings() +{ + FModuleManager::LoadModuleChecked("Settings").ShowViewer(FName("Project"), FName("Plugins"), FName("HoudiniEngine")); +} + +void +FHoudiniEngineCommands::OnlineDocumentation() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_DOC_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::OnlineForum() +{ + FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_FORUM_URL, nullptr, nullptr); +} + +void +FHoudiniEngineCommands::CleanUpTempFolder() +{ + // TODO: Improve me! slow now that we also have SM saved in the temp directory + // Due to the ref, we probably iterate a little too much, and should maybe do passes following the order of refs: + // mesh first, then materials, then textures. + // have a look at UWrangleContentCommandlet as well + + // Add a slate notification + FString Notification = TEXT("Cleaning up Houdini Engine temporary folder..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + GWarn->BeginSlowTask(LOCTEXT("CleanUpTemp", "Cleaning up the Houdini Engine Temp Folder"), false, false); + + // Get the default temp cook folder + FString TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + + TArray TempCookFolders; + TempCookFolders.Add(FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder()); + for (TObjectIterator It; It; ++It) + { + FString CookFolder = It->TemporaryCookFolder.Path; + if (CookFolder.IsEmpty()) + continue; + + TempCookFolders.AddUnique(CookFolder); + } + + // The Asset registry will help us finding if the content of the asset is referenced + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + int32 DeletedCount = 0; + bool bDidDeleteAsset = true; + while (bDidDeleteAsset) + { + // To correctly clean the temp folder, we need to iterate multiple times, because some of the temp assets + // might be referenced by other temp assets.. (ie Textures are referenced by Materials) + // We'll stop looking for assets to delete when no deletion occured. + bDidDeleteAsset = false; + + TArray AssetDataList; + for (auto& TempFolder : TempCookFolders) + { + // The Object library will list all UObjects found in the TempFolder + auto ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, true); + ObjectLibrary->LoadAssetDataFromPath(TempFolder); + + // Get all the assets found in the TEMP folder + TArray CurrentAssetDataList; + ObjectLibrary->GetAssetDataList(CurrentAssetDataList); + + AssetDataList.Append(CurrentAssetDataList); + } + + // All the assets we're going to delete + TArray AssetDataToDelete; + for (FAssetData Data : AssetDataList) + { + UPackage* CurrentPackage = Data.GetPackage(); + if (!CurrentPackage || CurrentPackage->IsPendingKill()) + continue; + + // Do not try to delete the package if it's referenced anywhere + TArray ReferenceNames; + //AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, EAssetRegistryDependencyType::All); + AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, UE::AssetRegistry::EDependencyCategory::All); + if (ReferenceNames.Num() > 0) + continue; + + bool bAssetDataSafeToDelete = true; + TArray AssetsInPackage; + AssetRegistryModule.Get().GetAssetsByPackageName(CurrentPackage->GetFName(), AssetsInPackage); + for (const auto& AssetInfo : AssetsInPackage) + { + // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) + UObject* AssetInPackage = AssetInfo.GetAsset(); + if (!AssetInPackage || AssetInPackage->IsPendingKill()) + continue; + + FReferencerInformationList ReferencesIncludingUndo; + bool bReferencedInMemoryOrUndoStack = IsReferenced(AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo); + if (!bReferencedInMemoryOrUndoStack) + continue; + + // We do have external references, check if the external references are in our ObjectToDelete list + // If they are, we can delete the asset because its references are going to be deleted as well. + for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences) + { + UObject* Outer = ExtRef.Referencer->GetOuter(); + if (!Outer || Outer->IsPendingKill()) + continue; + + bool bOuterFound = false; + for (auto DataToDelete : AssetDataToDelete) + { + if (DataToDelete.GetPackage() == Outer) + { + bOuterFound = true; + break; + } + else if (DataToDelete.GetAsset() == Outer) + { + bOuterFound = true; + break; + } + } + + // We have at least one reference that's not going to be deleted, we have to keep the asset + if (!bOuterFound) + { + bAssetDataSafeToDelete = false; + break; + } + } + } + + if (bAssetDataSafeToDelete) + AssetDataToDelete.Add(Data); + } + + // Nothing to delete + if (AssetDataToDelete.Num() <= 0) + break; + + int32 CurrentDeleted = ObjectTools::DeleteAssets(AssetDataToDelete, false); + + if (CurrentDeleted > 0) + { + DeletedCount += CurrentDeleted; + bDidDeleteAsset = true; + } + } + + + // Now, go through all the directories in the temp directories and delete all the empty ones + IFileManager& FM = IFileManager::Get(); + // Lambda that parses a directory recursively and returns true if it is empty + auto IsEmptyFolder = [&FM](FString PathToDeleteOnDisk) + { + struct FEmptyFolderVisitor : public IPlatformFile::FDirectoryVisitor + { + bool bIsEmpty; + FEmptyFolderVisitor() + : bIsEmpty(true) + { + } + + virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override + { + if (!bIsDirectory) + { + bIsEmpty = false; + return false; // abort searching + } + + return true; // continue searching + } + }; + + // Look for files on disk in case the folder contains things not tracked by the asset registry + FEmptyFolderVisitor EmptyFolderVisitor; + IFileManager::Get().IterateDirectoryRecursively(*PathToDeleteOnDisk, EmptyFolderVisitor); + return EmptyFolderVisitor.bIsEmpty; + }; + + // Iterates on all the temporary cook directories recursively, + // And keep not of all the empty directories + FString TempCookPathOnDisk; + TArray FoldersToDelete; + if (FPackageName::TryConvertLongPackageNameToFilename(TempCookFolder, TempCookPathOnDisk)) + { + FM.IterateDirectoryRecursively(*TempCookPathOnDisk, [&FM, &FoldersToDelete, &IsEmptyFolder](const TCHAR* InFilenameOrDirectory, const bool InIsDirectory) -> bool + { + // Skip Files + if (!InIsDirectory) + return true; + + FString CurrentDirectoryPath = FString(InFilenameOrDirectory); + if (IsEmptyFolder(CurrentDirectoryPath)) + FoldersToDelete.Add(CurrentDirectoryPath); + + // keep iterating + return true; + }); + } + + int32 DeletedDirectories = 0; + for (auto& FolderPath : FoldersToDelete) + { + FString PathToDelete; + if (!FPackageName::TryConvertFilenameToLongPackageName(FolderPath, PathToDelete)) + continue; + + if (IFileManager::Get().DeleteDirectory(*FolderPath, false, true)) + { + AssetRegistryModule.Get().RemovePath(PathToDelete); + DeletedDirectories++; + } + } + + GWarn->EndSlowTask(); + + // Add a slate notification + Notification = TEXT("Deleted ") + FString::FromInt(DeletedCount) + TEXT(" temporary files and ") + FString::FromInt(DeletedDirectories) + TEXT(" directories."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Deleted %d temporary files and %d directories."), DeletedCount, DeletedDirectories); +} + +void +FHoudiniEngineCommands::BakeAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Baking all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 BakedCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component")); + continue; + } + + if (!HoudiniAssetComponent->IsComponentValid()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + if (AssetName != "Default__HoudiniAssetActor") + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is invalid"), *AssetName); + continue; + } + + // If component is not cooking or instancing, we can bake blueprint. + if (HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is actively instantiating or cooking"), *AssetName); + continue; + } + + bool bSuccess = false; + bool BakeToBlueprints = true; + if (BakeToBlueprints) + { + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // bSuccess = true; + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + const bool bInReplaceAssets = true; + bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + if (bSuccess) + { + // Instantiate blueprints in component's level, then remove houdini asset actor + bSuccess = false; + ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); + if (IsValid(Level)) + { + UWorld* World = Level->GetWorld(); + if (IsValid(World)) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = Level; + FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); + for (UBlueprint* Blueprint : Blueprints) + { + if (!IsValid(Blueprint)) + continue; + World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); + } + + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + bSuccess = true; + } + } + } + } + else + { + // TODO: this used to have a way to not select in v1 + // if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent)) + // bSuccess = true; + const bool bReplaceActors = true; + const bool bReplaceAssets = true; + if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, bReplaceActors, bReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors)) + { + bSuccess = true; + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + } + } + + if (bSuccess) + BakedCount++; + } + + // Add a slate notification + Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); +} + +void +FHoudiniEngineCommands::PauseAssetCooking() +{ + // Revert the global flag + bool bCurrentCookingEnabled = !FHoudiniEngine::Get().IsCookingEnabled(); + FHoudiniEngine::Get().SetCookingEnabled(bCurrentCookingEnabled); + + // We need to refresh UI when pause cooking. Set refresh UI counter to be the number of current registered HACs. + if (!bCurrentCookingEnabled) + FHoudiniEngine::Get().SetUIRefreshCountWhenPauseCooking( FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount() ); + + // Add a slate notification + FString Notification = TEXT("Houdini Engine cooking paused"); + if (bCurrentCookingEnabled) + Notification = TEXT("Houdini Engine cooking resumed"); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + if (bCurrentCookingEnabled) + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking resumed.")); + else + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking paused.")); + + if (!bCurrentCookingEnabled) + return; + + /* + // If we are unpausing, tick each asset component to "update" them + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!")); + continue; + } + + HoudiniAssetComponent->StartHoudiniTicking(); + } + */ +} + +bool +FHoudiniEngineCommands::IsAssetCookingPaused() +{ + return !FHoudiniEngine::Get().IsCookingEnabled(); +} + +void +FHoudiniEngineCommands::RecookSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Cooking selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and cook the assets if they're in a valid state + int32 CookedCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedCook(); + CookedCount++; + } + + // Add a slate notification + Notification = TEXT("Re-cooking ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-cooking %d selected Houdini assets."), CookedCount); +} + +void +FHoudiniEngineCommands::RecookAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Cooking all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 CookedCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedCook(); + CookedCount++; + } + + // Add a slate notification + Notification = TEXT("Re-cooked ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-cooked %d Houdini assets in the current level."), CookedCount); +} + +void +FHoudiniEngineCommands::RebuildAllAssets() +{ + // Add a slate notification + FString Notification = TEXT("Re-building all assets in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Bakes and replaces with blueprints all Houdini Assets in the current level + int32 RebuiltCount = 0; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedRebuild(); + RebuiltCount++; + } + + // Add a slate notification + Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d Houdini assets in the current level."), RebuiltCount); +} + +void +FHoudiniEngineCommands::RebuildSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Rebuilding selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and rebuilds the assets if they're in a valid state + int32 RebuiltCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid()) + continue; + + HoudiniAssetComponent->MarkAsNeedRebuild(); + RebuiltCount++; + } + + // Add a slate notification + Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d selected Houdini assets."), RebuiltCount); +} + +void +FHoudiniEngineCommands::BakeSelection() +{ + // Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Baking selected Houdini Asset Actors in the current level..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and rebuilds the assets if they're in a valid state + int32 BakedCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); + continue; + } + + if (!HoudiniAssetComponent->IsComponentValid()) + { + FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName(); + HOUDINI_LOG_ERROR(TEXT("Failed to export Houdini Asset: %s in the scene!"), *AssetName); + continue; + } + + // If component is not cooking or instancing, we can bake blueprint. + if (!HoudiniAssetComponent->IsInstantiatingOrCooking()) + { + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // BakedCount++; + // if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr) + // bSuccess = true; + FHoudiniEngineOutputStats BakeStats; + TArray PackagesToSave; + TArray Blueprints; + const bool bReplaceAssets = true; + const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, bReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors, BakeStats, Blueprints, PackagesToSave); + FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); + + if (bSuccess) + { + // Instantiate blueprints in component's level, then remove houdini asset actor + ULevel* Level = HoudiniAssetComponent->GetComponentLevel(); + if (IsValid(Level)) + { + UWorld* World = Level->GetWorld(); + if (IsValid(World)) + { + FActorSpawnParameters SpawnParams; + SpawnParams.OverrideLevel = Level; + FTransform Transform = HoudiniAssetComponent->GetComponentTransform(); + for (UBlueprint* Blueprint : Blueprints) + { + if (!IsValid(Blueprint)) + continue; + World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams); + } + + FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent); + BakedCount++; + } + } + } + } + } + + // Add a slate notification + Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount); +} + +// Recentre HoudiniAsset actors' pivots to their input / cooked static-mesh average centre. +void FHoudiniEngineCommands::RecentreSelection() +{ + /* +#if WITH_EDITOR + //Get current world selection + TArray WorldSelection; + int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (SelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Recentering selected Houdini Assets..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // Iterates over the selection and cook the assets if they're in a valid state + int32 RecentreCount = 0; + for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || !HoudiniAssetComponent->IsComponentValid()) + continue; + + // Get the average centre of all the created Static Meshes + FVector AverageBoundsCentre = FVector::ZeroVector; + int32 NumBounds = 0; + const FVector CurrentLocation = HoudiniAssetComponent->GetComponentLocation(); + { + //Check Static Meshes + TArray StaticMeshes; + StaticMeshes.Reserve(16); + HoudiniAssetComponent->GetAllUsedStaticMeshes(StaticMeshes); + + //Get average centre of all the static meshes. + for (const UStaticMesh* pMesh : StaticMeshes) + { + if (!pMesh) + continue; + + //to world space + AverageBoundsCentre += (pMesh->GetBounds().Origin + CurrentLocation); + NumBounds++; + } + } + + //Check Inputs + if (0 == NumBounds) + { + const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs; + for (const UHoudiniInput* pInput : AssetInputs) + { + if (!pInput || pInput->IsPendingKill()) + continue; + + // to world space + FBox Bounds = pInput->GetInputBounds(CurrentLocation); + if (Bounds.IsValid) + { + AverageBoundsCentre += Bounds.GetCenter(); + NumBounds++; + } + } + } + + //if we have more than one, get the average centre + if (NumBounds > 1) + { + AverageBoundsCentre /= (float)NumBounds; + } + + //if we need to move... + float fDist = FVector::DistSquared(CurrentLocation, AverageBoundsCentre); + if (NumBounds && fDist > 1.0f) + { + // Move actor to average centre and recook + // This will refresh the static mesh under the HoudiniAssestComponent ( undoing the translation ). + HoudiniAssetActor->SetActorLocation(AverageBoundsCentre, false, nullptr, ETeleportType::TeleportPhysics); + + // Recook now the houdini-static-mesh has a new origin + HoudiniAssetComponent->StartTaskAssetCookingManual(); + RecentreCount++; + } + } + + if (RecentreCount) + { + // UE4 Editor doesn't refresh the translation-handles until they are re-selected, confusing the user, deselect the objects. + GEditor->SelectNone(true, false); + } + + // Add a slate notification + Notification = TEXT("Re-centred ") + FString::FromInt(RecentreCount) + TEXT(" Houdini assets."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Re-centred %d selected Houdini assets."), RecentreCount); + +#endif //WITH_EDITOR + */ +} + +void +FHoudiniEngineCommands::OpenSessionSync() +{ + //if (!FHoudiniEngine::IsInitialized()) + // return; + + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - HAPI Not initialized")); + return; + } + + // Get the runtime settings to get the session/type and settings + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + + EHoudiniRuntimeSettingsSessionType SessionType = HoudiniRuntimeSettings->SessionType; + FString ServerPipeName = HoudiniRuntimeSettings->ServerPipeName; + int32 ServerPort = HoudiniRuntimeSettings->ServerPort; + + FString SessionSyncArgs = TEXT("-hess="); + if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe) + { + // Add the -hess=pipe:hapi argument + SessionSyncArgs += TEXT("pipe:") + ServerPipeName; + } + else if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_Socket) + { + // Add the -hess=port:9090 argument + SessionSyncArgs += TEXT("port:") + FString::FromInt(ServerPort); + } + else + { + // Invalid session type + HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - Invalid session type")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Opening Houdini Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Opening Houdini Session Sync.")); + + // Only launch Houdini in Session sync if we havent started it already! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + if (!FPlatformProcess::IsProcRunning(PreviousHESS)) + { + // Start houdini with the -hess commandline args + const FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); +# if PLATFORM_MAC + const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../Resources/bin"); +# elif PLATFORM_LINUX + const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../bin"); +# elif PLATFORM_WINDOWS + const FString HoudiniExeLocationRelativeToLibHAPI; +# else + // Treat an unknown platform the same as Windows for now + const FString HoudiniExeLocationRelativeToLibHAPI; +# endif + + FString HoudiniExecutable = FHoudiniEngine::Get().GetHoudiniExecutable(); + FString HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/") + HoudiniExecutable; + HOUDINI_LOG_MESSAGE(TEXT("Path to houdini executable: %s"), *HoudiniLocation); + FProcHandle HESSHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *SessionSyncArgs, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!HESSHandle.IsValid()) + { + // Try with the steam version executable instead + HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/hindie.steam"); + HOUDINI_LOG_MESSAGE(TEXT("Path to hindie.steam executable: %s"), *HoudiniLocation); + + HESSHandle = FPlatformProcess::CreateProc( + *HoudiniLocation, + *SessionSyncArgs, + true, false, false, + nullptr, 0, + FPlatformProcess::UserTempDir(), + nullptr, nullptr); + + if (!HESSHandle.IsValid()) + { + HOUDINI_LOG_ERROR(TEXT("Failed to launch Houdini in Session Sync mode.")); + return; + } + } + + // Keep track of the SessionSync ProcHandle + FHoudiniEngine::Get().SetHESSProcHandle(HESSHandle); + } + + // Start an Async task to connect to Session Sync + Async(EAsyncExecution::TaskGraphMainThread, [SessionType, ServerPipeName, ServerPort]() + { + // Use a timeout to avoid waiting indefinitely for H to start in session sync mode + const double Timeout = 180.0; // 3min + const double StartTimestamp = FPlatformTime::Seconds(); + + FString ServerHost = TEXT("localhost"); + while (!FHoudiniEngine::Get().SessionSyncConnect(SessionType, ServerPipeName, ServerHost, ServerPort)) + { + // Houdini might not be done loading, sleep for one second + FPlatformProcess::Sleep(1); + + // Check for the timeout + if (FPlatformTime::Seconds() - StartTimestamp > Timeout) + { + // ... and a log message + HOUDINI_LOG_ERROR(TEXT("Failed to start SessionSync - Timeout...")); + return false; + } + } + + // Initialize HAPI with this session + if (!FHoudiniEngine::Get().InitializeHAPISession()) + { + FHoudiniEngine::Get().StopTicking(); + return false; + } + + // Notify all HACs that they need to instantiate in the new session + MarkAllHACsAsNeedInstantiation(); + + // Start ticking + FHoudiniEngine::Get().StartTicking(); + + // Add a slate notification + FString Notification = TEXT("Succesfully connected to Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Succesfully connected to Session Sync...")); + + return true; + }); +} + +void +FHoudiniEngineCommands::CloseSessionSync() +{ + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to stop Session Sync - HAPI Not initialized")); + return; + } + + // Add a slate notification + FString Notification = TEXT("Stopping Houdini Session Sync..."); + FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // ... and a log message + HOUDINI_LOG_MESSAGE(TEXT("Stopping Houdini Session Sync.")); + + // Stop Houdini Session sync if it is still running! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + if (FPlatformProcess::IsProcRunning(PreviousHESS)) + { + FPlatformProcess::TerminateProc(PreviousHESS, true); + } +} + +void +FHoudiniEngineCommands::SetViewportSync(const int32& ViewportSync) +{ + if (ViewportSync == 1) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); + } + else if (ViewportSync == 2) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); + } + else if (ViewportSync == 3) + { + FHoudiniEngine::Get().SetSyncViewportEnabled(true); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true); + } + else + { + FHoudiniEngine::Get().SetSyncViewportEnabled(false); + FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false); + FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false); + } +} + +int32 +FHoudiniEngineCommands::GetViewportSync() +{ + if(!FHoudiniEngine::Get().IsSyncViewportEnabled()) + return 0; + + bool bSyncH = FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled(); + bool bSyncU = FHoudiniEngine::Get().IsSyncUnrealViewportEnabled(); + if (bSyncH && !bSyncU) + return 1; + else if (!bSyncH && bSyncU) + return 2; + else if (bSyncH && bSyncU) + return 3; + else + return 0; +} + +void +FHoudiniEngineCommands::RestartSession() +{ + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().RestartSession()) + return; + + // We've successfully restarted the Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::CreateSession() +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().CreateSession(HoudiniRuntimeSettings->SessionType)) + return; + + // We've successfully created the Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::ConnectSession() +{ + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().ConnectSession(HoudiniRuntimeSettings->SessionType)) + return; + + // We've successfully connected to a Houdini Engine session, + // We now need to notify all the HoudiniAssetComponent that they need to re instantiate + // themselves in the new Houdini engine session. + MarkAllHACsAsNeedInstantiation(); +} + +void +FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation() +{ + // Notify all the HoudiniAssetComponents that they need to re instantiate themselves in the new Houdini engine session. + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssetComponent->MarkAsNeedInstantiation(); + } +} + +bool +FHoudiniEngineCommands::IsSessionValid() +{ + return FHoudiniEngine::IsInitialized(); +} + +bool +FHoudiniEngineCommands::IsSessionSyncProcessValid() +{ + // Only launch Houdini in Session sync if we havent started it already! + FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle(); + return FPlatformProcess::IsProcRunning(PreviousHESS); +} + +void +FHoudiniEngineCommands::StopSession() +{ + // Restart the current Houdini Engine Session + if (!FHoudiniEngine::Get().StopSession()) + { + // StopSession returns false only if Houdini is not initialized + HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized")); + } + else + { + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine session stopped.")); + } +} + +EHoudiniProxyRefineRequestResult +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE) +{ + // Get current world selection + TArray WorldSelection; + int32 NumSelectedHoudiniAssets = 0; + if (bOnlySelectedActors) + { + NumSelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true); + if (NumSelectedHoudiniAssets <= 0) + { + HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner")); + return EHoudiniProxyRefineRequestResult::Invalid; + } + } + + // Add a slate notification + FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + + // First find the components that have meshes that we must refine + TArray ComponentsToRefine; + TArray ComponentsToCook; + // Components that would be candidates for refinement/cooking, but have errors + TArray SkippedComponents; + if (bOnlySelectedActors) + { + for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Index]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + } + else + { + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + } + + return RefineTriagedHoudiniProxyMesehesToStaticMeshes( + ComponentsToRefine, + ComponentsToCook, + SkippedComponents, + bSilent, + bRefineAll, + bOnPreSaveWorld, + OnPreSaveWorld, + bOnPreBeginPIE + ); +} + +EHoudiniProxyRefineRequestResult +FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent) +{ + const bool bRefineAll = true; + const bool bOnPreSaveWorld = false; + UWorld* OnPreSaveWorld = nullptr; + const bool bOnPreBeginPIE = false; + + // First find the components that have meshes that we must refine + TArray ComponentsToRefine; + TArray ComponentsToCook; + // Components that would be candidates for refinement/cooking, but have errors + TArray SkippedComponents; + for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine) + { + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and + // flags passed to the function. + TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents); + } + + return RefineTriagedHoudiniProxyMesehesToStaticMeshes( + ComponentsToRefine, + ComponentsToCook, + SkippedComponents, + bSilent, + bRefineAll, + bOnPreSaveWorld, + OnPreSaveWorld, + bOnPreBeginPIE + ); +} + +void +FHoudiniEngineCommands::StartPDGCommandlet() +{ + FHoudiniEngine::Get().StartPDGCommandlet(); +} + +void +FHoudiniEngineCommands::StopPDGCommandlet() +{ + FHoudiniEngine::Get().StopPDGCommandlet(); +} + +bool +FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected() +{ + return FHoudiniEngine::Get().IsPDGCommandletRunningOrConnected(); +} + +bool +FHoudiniEngineCommands::IsPDGCommandletEnabled() +{ + const UHoudiniRuntimeSettings* const Settings = GetDefault(); + if (IsValid(Settings)) + { + return Settings->bPDGAsyncCommandletImportEnabled; + } + + return false; +} + +bool +FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled) +{ + UHoudiniRuntimeSettings* const Settings = GetMutableDefault(); + if (IsValid(Settings)) + { + Settings->bPDGAsyncCommandletImportEnabled = InEnabled; + return true; + } + + return false; +} + +void +FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped) +{ + if (!InHAC || InHAC->IsPendingKill()) + return; + + // Make sure that the component's World and Owner are valid + AActor *Owner = InHAC->GetOwner(); + if (!Owner || Owner->IsPendingKill()) + return; + + UWorld *World = InHAC->GetWorld(); + if (!World || World->IsPendingKill()) + return; + + if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World) + return; + + // Check if we should consider this component for proxy mesh refinement based on its settings and + // flags passed to the function + if (bRefineAll || + (bOnPreSaveWorld && InHAC->IsProxyStaticMeshRefinementOnPreSaveWorldEnabled()) || + (bOnPreBeginPIE && InHAC->IsProxyStaticMeshRefinementOnPreBeginPIEEnabled())) + { + TArray ProxyMeshPackagesToSave; + TArray ComponentsWithProxiesToSave; + + if (InHAC->HasAnyCurrentProxyOutput()) + { + // Get the state of the asset and check if it is cooked + // If it is not cook, request a cook. We can only build the UStaticMesh + // if the data from the cook is available + // If the state is not pre-cook, or None (cooked), then the state is invalid, + // log an error and skip the component + bool bNeedsRebuildOrDelete = false; + bool bUnsupportedState = false; + const bool bCookedDataAvailable = InHAC->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bUnsupportedState); + if (bCookedDataAvailable) + { + OutToRefine.Add(InHAC); + ComponentsWithProxiesToSave.Add(InHAC); + } + else if (!bUnsupportedState && !bNeedsRebuildOrDelete) + { + InHAC->MarkAsNeedCook(); + // Force the output of the cook to be directly created as a UStaticMesh and not a proxy + InHAC->SetNoProxyMeshNextCookRequested(true); + OutToCook.Add(InHAC); + ComponentsWithProxiesToSave.Add(InHAC); + } + else + { + OutSkipped.Add(InHAC); + const EHoudiniAssetState AssetState = InHAC->GetAssetState(); + HOUDINI_LOG_ERROR(TEXT("Could not refine %s, the asset is in an unsupported state: %s"), *(InHAC->GetPathName()), *(UEnum::GetValueAsString(AssetState))); + } + } + else if (InHAC->HasAnyProxyOutput()) + { + // If the HAC has non-current proxies, destroy them + // TODO: Make this its own command? + const uint32 NumOutputs = InHAC->GetNumOutputs(); + for (uint32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = InHAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + TMap& OutputObjects = Output->GetOutputObjects(); + for (auto& CurrentPair : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; + if (!CurrentOutputObject.bProxyIsCurrent) + { + // The proxy is not current, delete it and its component + USceneComponent* FoundProxyComponent = Cast(CurrentOutputObject.ProxyComponent); + if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill()) + { + // Remove from the HoudiniAssetActor + if (FoundProxyComponent->GetOwner()) + FoundProxyComponent->GetOwner()->RemoveOwnedComponent(FoundProxyComponent); + + FoundProxyComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + FoundProxyComponent->UnregisterComponent(); + FoundProxyComponent->DestroyComponent(); + } + + UObject* ProxyObject = CurrentOutputObject.ProxyObject; + if (!ProxyObject || ProxyObject->IsPendingKill()) + continue; + + ProxyObject->MarkPendingKill(); + ProxyObject->MarkPackageDirty(); + UPackage* const Package = ProxyObject->GetPackage(); + if (IsValid(Package)) + ProxyMeshPackagesToSave.Add(Package); + } + } + } + } + + for (UHoudiniAssetComponent* const HAC : ComponentsWithProxiesToSave) + { + const uint32 NumOutputs = HAC->GetNumOutputs(); + for (uint32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + TMap& OutputObjects = Output->GetOutputObjects(); + for (auto& CurrentPair : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value; + if (CurrentOutputObject.bProxyIsCurrent && CurrentOutputObject.ProxyObject) + { + UPackage* const Package = CurrentOutputObject.ProxyObject->GetPackage(); + if (IsValid(Package) && Package->IsDirty()) + ProxyMeshPackagesToSave.Add(Package); + } + } + } + } + + if (ProxyMeshPackagesToSave.Num() > 0) + { + TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + FEditorFileUtils::PromptForCheckoutAndSave(ProxyMeshPackagesToSave, true, false); + } + } +} + +EHoudiniProxyRefineRequestResult +FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( + const TArray& InComponentsToRefine, + const TArray& InComponentsToCook, + const TArray& InSkippedComponents, + bool bInSilent, + bool bInRefineAll, + bool bInOnPreSaveWorld, + UWorld* InOnPreSaveWorld, + bool bInOnPrePIEBeginPlay) +{ + // Slate notification text + FString Notification = TEXT("Refining Houdini proxy meshes to static meshes..."); + + const uint32 NumComponentsToCook = InComponentsToCook.Num(); + const uint32 NumComponentsToRefine = InComponentsToRefine.Num(); + const uint32 NumComponentsToProcess = NumComponentsToCook + NumComponentsToRefine; + + TArray SuccessfulComponents; + TArray FailedComponents; + TArray SkippedComponents(InSkippedComponents); + + if (NumComponentsToProcess > 0) + { + // The task progress pointer is potentially going to be shared with a background thread and tasks + // on the main thread, so make it thread safe + TSharedPtr TaskProgress = MakeShareable(new FSlowTask((float)NumComponentsToProcess, FText::FromString(Notification))); + TaskProgress->Initialize(); + if (!bInSilent) + TaskProgress->MakeDialog(/*bShowCancelButton=*/true); + + // Iterate over the components for which we can build UStaticMesh, and build the meshes + bool bCancelled = false; + for (uint32 ComponentIndex = 0; ComponentIndex < NumComponentsToRefine; ++ComponentIndex) + { + UHoudiniAssetComponent* HoudiniAssetComponent = InComponentsToRefine[ComponentIndex]; + TaskProgress->EnterProgressFrame(1.0f); + const bool bDestroyProxies = true; + FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HoudiniAssetComponent, bDestroyProxies); + + SuccessfulComponents.Add(HoudiniAssetComponent); + + bCancelled = TaskProgress->ShouldCancel(); + if (bCancelled) + { + for (uint32 SkippedIndex = ComponentIndex + 1; SkippedIndex < NumComponentsToRefine; ++SkippedIndex) + { + SkippedComponents.Add(InComponentsToRefine[ComponentIndex]); + } + break; + } + } + + if (bCancelled && NumComponentsToCook > 0) + { + for (UHoudiniAssetComponent* const HAC : InComponentsToCook) + { + SkippedComponents.Add(HAC); + } + } + + if (NumComponentsToCook > 0 && !bCancelled) + { + // Now use an async task to check on the progress of the cooking components + Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents]() { + RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread( + InComponentsToCook, TaskProgress, NumComponentsToProcess, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); + }); + + // We have to wait for cook(s) before completing refinement + return EHoudiniProxyRefineRequestResult::PendingCooks; + } + else + { + RefineHoudiniProxyMeshesToStaticMeshesNotifyDone( + NumComponentsToProcess, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); + + // We didn't have to cook anything, so refinement is complete. + return EHoudiniProxyRefineRequestResult::Refined; + } + } + + // Nothing to refine + return EHoudiniProxyRefineRequestResult::None; +} + + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray& InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumComponentsToProcess, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents) +{ + // Copy to a double linked list so that we can loop through + // to check progress of each component and remove it easily + // if it has completed/failed + TDoubleLinkedList CookList; + for (UHoudiniAssetComponent *HAC : InComponentsToCook) + { + CookList.AddTail(HAC); + } + + // Add the successfully cooked components to the incoming successful components (previously refined) + TArray SuccessfulComponents(InSuccessfulComponents); + TArray FailedComponents(InFailedComponents); + TArray SkippedComponents(InSkippedComponents); + + bool bCancelled = false; + uint32 NumFailedToCook = 0; + while (CookList.Num() > 0 && !bCancelled) + { + TDoubleLinkedList::TDoubleLinkedListNode *Node = CookList.GetHead(); + while (Node && !bCancelled) + { + TDoubleLinkedList::TDoubleLinkedListNode *Next = Node->GetNextNode(); + UHoudiniAssetComponent* HAC = Node->GetValue(); + + if (HAC && !HAC->IsPendingKill()) + { + const EHoudiniAssetState State = HAC->GetAssetState(); + const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult(); + bool bUpdateProgress = false; + if (State == EHoudiniAssetState::None) + { + // Cooked, count as success, remove node + CookList.RemoveNode(Node); + SuccessfulComponents.Add(HAC); + bUpdateProgress = true; + } + else if (ResultState != EHoudiniAssetStateResult::None && ResultState != EHoudiniAssetStateResult::Working) + { + // Failed, remove node + HOUDINI_LOG_ERROR(TEXT("Failed to cook %s to obtain static mesh."), *(HAC->GetPathName())); + CookList.RemoveNode(Node); + FailedComponents.Add(HAC); + bUpdateProgress = true; + NumFailedToCook++; + } + + if (bUpdateProgress && InTaskProgress.IsValid()) + { + // Update progress only on the main thread, and check for cancellation request + bCancelled = Async(EAsyncExecution::TaskGraphMainThread, [InTaskProgress]() { + InTaskProgress->EnterProgressFrame(1.0f); + return InTaskProgress->ShouldCancel(); + }).Get(); + } + } + else + { + SkippedComponents.Add(HAC); + CookList.RemoveNode(Node); + } + + Node = Next; + } + FPlatformProcess::Sleep(0.01f); + } + + if (bCancelled) + { + HOUDINI_LOG_WARNING(TEXT("Mesh refinement cancelled while waiting for %d components to cook."), CookList.Num()); + // Mark any remaining HACs in the cook list as skipped + TDoubleLinkedList::TDoubleLinkedListNode* Node = CookList.GetHead(); + while (Node) + { + TDoubleLinkedList::TDoubleLinkedListNode* const Next = Node->GetNextNode(); + UHoudiniAssetComponent* HAC = Node->GetValue(); + if (HAC) + SkippedComponents.Add(HAC); + CookList.RemoveNode(Node); + Node = Next; + } + } + + // Cooking is done, or failed, display the notifications on the main thread + Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents]() { + RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); + }); +} + +void +FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(const uint32 InNumTotalComponents, FSlowTask* const InTaskProgress, const bool bCancelled, const bool bOnPreSaveWorld, UWorld* const InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents) +{ + FString Notification; + const uint32 NumSkippedComponents = InSkippedComponents.Num(); + const uint32 NumFailedToCook = InFailedComponents.Num(); + if (NumSkippedComponents + NumFailedToCook > 0) + { + if (bCancelled) + { + Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), NumSkippedComponents + NumFailedToCook, InNumTotalComponents); + } + else + { + Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), NumSkippedComponents + NumFailedToCook, InNumTotalComponents); + } + FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_ERROR(TEXT("%s"), *Notification); + } + else if (InNumTotalComponents > 0) + { + Notification = TEXT("Done: Refining Houdini proxy meshes to static meshes."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + HOUDINI_LOG_MESSAGE(TEXT("%s"), *Notification); + } + if (InTaskProgress) + { + InTaskProgress->Destroy(); + } + if (bOnPreSaveWorld && InSuccessfulComponents.Num() > 0) + { + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + + // Save the dirty static meshes in InSuccessfulComponents OnPostSaveWorld + // TODO: Remove? This may not be necessary now as we save all dirty temporary cook data in PostSaveWorld() already (Static Meshes, Materials...) + OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([InSuccessfulComponents, bOnPreSaveWorld, InOnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) { + if (bOnPreSaveWorld && InOnPreSaveWorld && InOnPreSaveWorld != InWorld) + return; + + RefineProxyMeshesHandleOnPostSaveWorld(InSuccessfulComponents, InSaveFlags, InWorld, bInSuccess); + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + }); + } + + // Broadcast refinement result per HAC + for (UHoudiniAssetComponent* const HAC : InSuccessfulComponents) + { + if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) + OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Success); + } + for (UHoudiniAssetComponent* const HAC : InFailedComponents) + { + if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) + OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Failed); + } + for (UHoudiniAssetComponent* const HAC : InSkippedComponents) + { + if (OnHoudiniProxyMeshesRefinedDelegate.IsBound()) + OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Skipped); + } +} + +void +FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) +{ + TArray PackagesToSave; + + for (UHoudiniAssetComponent* HAC : InSuccessfulComponents) + { + if (!HAC || HAC->IsPendingKill()) + continue; + + const int32 NumOutputs = HAC->GetNumOutputs(); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + if (Output->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (auto &OutputObjectPair : Output->GetOutputObjects()) + { + UObject *Obj = OutputObjectPair.Value.OutputObject; + if (!Obj || Obj->IsPendingKill()) + continue; + + UStaticMesh *SM = Cast(Obj); + if (!SM) + continue; + + UPackage *Package = SM->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + } + } + + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h index b8b1516a2..1945e6da4 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h @@ -1,305 +1,260 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniEngineStyle.h" - -#include "Framework/Commands/Commands.h" -#include "Misc/SlowTask.h" -#include "Delegates/IDelegateInstance.h" - -class UHoudiniAssetComponent; -class AHoudiniAssetActor; -struct FSlowTask; - -// When attempting to refine proxy mesh outputs it is a possible that a cook is needed. The enum -// defines the possible return values on a request to refine proxies. -UENUM() -enum class EHoudiniProxyRefineRequestResult : uint8 -{ - Invalid, - - // No refinement is needed - None, - // A cook is needed, refinement will commence automatically after the cook - PendingCooks, - // Successfully refined - Refined, - - Max, -}; - -// When attempting to refine proxy mesh outputs it is a possible that a cook is needed. The enum -// defines the possible return values on a request to refine proxies. -UENUM() -enum class EHoudiniProxyRefineResult : uint8 -{ - Invalid, - - // Refinement (or cook if needed) failed - Failed, - // Refinement completed successfully - Success, - // Refinement was skipped, either it was not necessary or the operation was cancelled by the user - Skipped, - - Max, -}; - - -// Class containing commands for Houdini Engine actions -class FHoudiniEngineCommands : public TCommands -{ -public: - // Multi-cast delegate type for broadcasting when proxy mesh refinement of a HAC is complete. - DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHoudiniProxyMeshesRefinedDelegate, UHoudiniAssetComponent* const, const EHoudiniProxyRefineResult); - - FHoudiniEngineCommands() - : TCommands - ( - TEXT("HoudiniEngine"), // Context name for fast lookup - NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), // Localized context name for displaying - NAME_None, // Parent context name. - FHoudiniEngineStyle::GetStyleSetName() // Icon Style Set - ) - { - } - - // TCommand<> interface - virtual void RegisterCommands() override; - -public: - - // Menu action called to save a HIP file. - static void SaveHIPFile(); - - // Menu action called to report a bug. - static void ReportBug(); - - // Menu action called to open the current scene in Houdini. - static void OpenInHoudini(); - - // Menu action called to clean up all unused files in the cook temp folder - static void CleanUpTempFolder(); - - // Menu action to bake/replace all current Houdini Assets with blueprints - static void BakeAllAssets(); - - // Helper function for baking/replacing the current select Houdini Assets with blueprints - static void BakeSelection(); - - // Helper function for restarting the current Houdini Engine session. - static void RestartSession(); - - // Menu action to pause cooking for all Houdini Assets - static void PauseAssetCooking(); - - // Helper delegate used to get the current state of PauseAssetCooking. - static bool IsAssetCookingPaused(); - - // Helper function for recooking all assets in the current level - static void RecookAllAssets(); - - // Helper function for rebuilding all assets in the current level - static void RebuildAllAssets(); - - // Helper function for recooking selected assets - static void RecookSelection(); - - // Helper function for rebuilding selected assets - static void RebuildSelection(); - - // Helper function for rebuilding selected assets - static void RecentreSelection(); - - // Helper function for starting Houdini in Sesion Sync mode - static void OpenSessionSync(); - - // Helper function for closing the current Houdini Sesion Sync - static void CloseSessionSync(); - - // returns true if the current HE session is valid - static bool IsSessionValid(); - - // Returns true if the current Session Sync process is still running - static bool IsSessionSyncProcessValid(); - - static int32 GetViewportSync(); - - static void SetViewportSync(const int32& ViewportSync); - - static void CreateSession(); - - static void ConnectSession(); - - static void StopSession(); - - static void ShowInstallInfo(); - - static void ShowPluginSettings(); - - static void OnlineDocumentation(); - - static void OnlineForum(); - - // Helper function for building static meshes for all assets using HoudiniStaticMesh - // If bSilent is false, show a progress dialog. - // If bRefineAll is true, then all components with HoudiniStaticMesh meshes will be - // refined to UStaticMesh. Otherwise, bOnPreSaveWorld and bOnPrePIEBeginPlay is checked - // against the settings of the component to determine if refinement should take place. - // If bOnPreSaveWorld is true, then OnPreSaveWorld should be the World that is being saved. In - // that case, only proxy meshes attached to components from that world will be refined. - static EHoudiniProxyRefineRequestResult RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent=false, bool bRefineAll=true, bool bOnPreSaveWorld=false, UWorld *PreSaveWorld=nullptr, bool bOnPrePIEBeginPlay=false); - - // Refine all proxy meshes on UHoudiniAssetCompoments of InActorsToRefine. - static EHoudiniProxyRefineRequestResult RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent=false); - - static void StartPDGCommandlet(); - - static void StopPDGCommandlet(); - - static bool IsPDGCommandletRunningOrConnected(); - - // Returns true if the commandlet is enabled in the settings - static bool IsPDGCommandletEnabled(); - - // Set the bPDGAsyncCommandletImportEnabled in the settings - static bool SetPDGCommandletEnabled(bool InEnabled); - - static FDelegateHandle& GetOnPostSaveWorldRefineProxyMeshesHandle() { return OnPostSaveWorldRefineProxyMeshesHandle; } - - static FOnHoudiniProxyMeshesRefinedDelegate& GetOnHoudiniProxyMeshesRefinedDelegate() { return OnHoudiniProxyMeshesRefinedDelegate; } - -public: - - // UI Action to create a Houdini Engine Session - TSharedPtr _CreateSession; - // UI Action to connect to a Houdini Engine Session - TSharedPtr _ConnectSession; - // UI Action to stop the current Houdini Engine Session - TSharedPtr _StopSession; - // UI Action to restart the current Houdini Engine Session - TSharedPtr _RestartSession; - // UI Action to open Houdini Session Sync - TSharedPtr _OpenSessionSync; - // UI Action to open Houdini Session Sync - TSharedPtr _CloseSessionSync; - - // UI Action to disable viewport sync - TSharedPtr _ViewportSyncNone; - // UI Action to enable unreal viewport sync - TSharedPtr _ViewportSyncUnreal; - // UI Action to enable houdini viewport sync - TSharedPtr _ViewportSyncHoudini; - // UI Action to enable bidirectionnal viewport sync - TSharedPtr _ViewportSyncBoth; - - // - TSharedPtr _InstallInfo; - // - TSharedPtr _PluginSettings; - - // Menu action called to open the current scene in Houdini. - TSharedPtr _OpenInHoudini; - // Menu action called to save a HIP file. - TSharedPtr _SaveHIPFile; - // Menu action called to clean up all unused files in the cook temp folder - TSharedPtr _CleanUpTempFolder; - - // - TSharedPtr _OnlineDoc; - // - TSharedPtr _OnlineForum; - // Menu action called to report a bug. - TSharedPtr _ReportBug; - - // UI Action to recook all HDA - TSharedPtr _CookAll; - // UI Action to recook the current world selection - TSharedPtr _CookSelected; - // Menu action to bake/replace all current Houdini Assets with blueprints - TSharedPtr _BakeAll; - // UI Action to bake and replace the current world selection - TSharedPtr _BakeSelected; - // UI Action to rebuild all HDA - TSharedPtr _RebuildAll; - // UI Action to rebuild the current world selection - TSharedPtr _RebuildSelected; - // UI Action for building static meshes for all assets using HoudiniStaticMesh - TSharedPtr _RefineAll; - // UI Action for building static meshes for selected assets using HoudiniStaticMesh - TSharedPtr _RefineSelected; - // Menu action to pause cooking for all Houdini Assets - TSharedPtr _PauseAssetCooking; - - // UI Action to recentre the current selection - TSharedPtr _RecentreSelected; - - // Start PDG/BGEO commandlet - TSharedPtr _StartPDGCommandlet; - // Stop PDG/BGEO commandlet - TSharedPtr _StopPDGCommandlet; - // Is PDG/BGEO commandlet enabled - TSharedPtr _IsPDGCommandletEnabled; - -protected: - - // Triage a HoudiniAssetComponent with UHoudiniStaticMesh as needing cooking or if a UStaticMesh can be immediately built - static void TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped); - - static EHoudiniProxyRefineRequestResult RefineTriagedHoudiniProxyMesehesToStaticMeshes( - const TArray& InComponentsToRefine, - const TArray& InComponentsToCook, - const TArray& InSkippedComponents, - bool bInSilent=false, - bool bInRefineAll=true, - bool bInOnPreSaveWorld=false, - UWorld* InOnPreSaveWorld=nullptr, - bool bInOnPrePIEBeginPlay=false); - - // Called in a background thread by RefineHoudiniProxyMeshesToStaticMeshes when some components need to be cooked to generate UStaticMeshes. Checks and waits for - // cooking of each component to complete, and then calls RefineHoudiniProxyMeshesToStaticMeshesNotifyDone on the main thread. - static void RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray &InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents); - - // Display a notification / end/close progress dialog, when refining mesh proxies to static meshes is complete - static void RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(const uint32 InNumTotalComponents, FSlowTask* const InTaskProgress, const bool bCancelled, const bool bOnPreSaveWorld, UWorld* const InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents); - - // Handle OnPostSaveWorld for refining proxy meshes: this saves all the dirty UPackages of the UStaticMeshes that were created during RefineHoudiniProxyMeshesToStaticMeshes - // if it was called as a result of a PreSaveWorld. - static void RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess); - - // Helper function used to indicate to all HAC that they need to be instantiated in the new HE session - // Needs to be call after starting/restarting/connecting/session syncing a HE session.. - static void MarkAllHACsAsNeedInstantiation(); - - // Delegate that is set up to refined proxy meshes post save world (it removes itself afterwards) - static FDelegateHandle OnPostSaveWorldRefineProxyMeshesHandle; - - // Delegate for broadcasting when proxy mesh refinement of a HAC's output is complete. - static FOnHoudiniProxyMeshesRefinedDelegate OnHoudiniProxyMeshesRefinedDelegate; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Framework/Commands/Commands.h" +#include "Misc/SlowTask.h" +#include "Delegates/IDelegateInstance.h" +#include "HoudiniEngineRuntimeCommon.h" + +class UHoudiniAssetComponent; +class AHoudiniAssetActor; +struct FSlowTask; + +// Class containing commands for Houdini Engine actions +class FHoudiniEngineCommands : public TCommands +{ +public: + // Multi-cast delegate type for broadcasting when proxy mesh refinement of a HAC is complete. + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnHoudiniProxyMeshesRefinedDelegate, UHoudiniAssetComponent* const, const EHoudiniProxyRefineResult); + + FHoudiniEngineCommands(); + + // TCommand<> interface + virtual void RegisterCommands() override; + +public: + + // Menu action called to save a HIP file. + static void SaveHIPFile(); + + // Menu action called to report a bug. + static void ReportBug(); + + // Menu action called to open the current scene in Houdini. + static void OpenInHoudini(); + + // Menu action called to clean up all unused files in the cook temp folder + static void CleanUpTempFolder(); + + // Menu action to bake/replace all current Houdini Assets with blueprints + static void BakeAllAssets(); + + // Helper function for baking/replacing the current select Houdini Assets with blueprints + static void BakeSelection(); + + // Helper function for restarting the current Houdini Engine session. + static void RestartSession(); + + // Menu action to pause cooking for all Houdini Assets + static void PauseAssetCooking(); + + // Helper delegate used to get the current state of PauseAssetCooking. + static bool IsAssetCookingPaused(); + + // Helper function for recooking all assets in the current level + static void RecookAllAssets(); + + // Helper function for rebuilding all assets in the current level + static void RebuildAllAssets(); + + // Helper function for recooking selected assets + static void RecookSelection(); + + // Helper function for rebuilding selected assets + static void RebuildSelection(); + + // Helper function for rebuilding selected assets + static void RecentreSelection(); + + // Helper function for starting Houdini in Sesion Sync mode + static void OpenSessionSync(); + + // Helper function for closing the current Houdini Sesion Sync + static void CloseSessionSync(); + + // returns true if the current HE session is valid + static bool IsSessionValid(); + + // Returns true if the current Session Sync process is still running + static bool IsSessionSyncProcessValid(); + + static int32 GetViewportSync(); + + static void SetViewportSync(const int32& ViewportSync); + + static void CreateSession(); + + static void ConnectSession(); + + static void StopSession(); + + static void ShowInstallInfo(); + + static void ShowPluginSettings(); + + static void OnlineDocumentation(); + + static void OnlineForum(); + + // Helper function for building static meshes for all assets using HoudiniStaticMesh + // If bSilent is false, show a progress dialog. + // If bRefineAll is true, then all components with HoudiniStaticMesh meshes will be + // refined to UStaticMesh. Otherwise, bOnPreSaveWorld and bOnPrePIEBeginPlay is checked + // against the settings of the component to determine if refinement should take place. + // If bOnPreSaveWorld is true, then OnPreSaveWorld should be the World that is being saved. In + // that case, only proxy meshes attached to components from that world will be refined. + static EHoudiniProxyRefineRequestResult RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent=false, bool bRefineAll=true, bool bOnPreSaveWorld=false, UWorld *PreSaveWorld=nullptr, bool bOnPrePIEBeginPlay=false); + + // Refine all proxy meshes on UHoudiniAssetCompoments of InActorsToRefine. + static EHoudiniProxyRefineRequestResult RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray& InActorsToRefine, bool bSilent=false); + + static void StartPDGCommandlet(); + + static void StopPDGCommandlet(); + + static bool IsPDGCommandletRunningOrConnected(); + + // Returns true if the commandlet is enabled in the settings + static bool IsPDGCommandletEnabled(); + + // Set the bPDGAsyncCommandletImportEnabled in the settings + static bool SetPDGCommandletEnabled(bool InEnabled); + + static FDelegateHandle& GetOnPostSaveWorldRefineProxyMeshesHandle() { return OnPostSaveWorldRefineProxyMeshesHandle; } + + static FOnHoudiniProxyMeshesRefinedDelegate& GetOnHoudiniProxyMeshesRefinedDelegate() { return OnHoudiniProxyMeshesRefinedDelegate; } + +public: + + // UI Action to create a Houdini Engine Session + TSharedPtr _CreateSession; + // UI Action to connect to a Houdini Engine Session + TSharedPtr _ConnectSession; + // UI Action to stop the current Houdini Engine Session + TSharedPtr _StopSession; + // UI Action to restart the current Houdini Engine Session + TSharedPtr _RestartSession; + // UI Action to open Houdini Session Sync + TSharedPtr _OpenSessionSync; + // UI Action to open Houdini Session Sync + TSharedPtr _CloseSessionSync; + + // UI Action to disable viewport sync + TSharedPtr _ViewportSyncNone; + // UI Action to enable unreal viewport sync + TSharedPtr _ViewportSyncUnreal; + // UI Action to enable houdini viewport sync + TSharedPtr _ViewportSyncHoudini; + // UI Action to enable bidirectionnal viewport sync + TSharedPtr _ViewportSyncBoth; + + // + TSharedPtr _InstallInfo; + // + TSharedPtr _PluginSettings; + + // Menu action called to open the current scene in Houdini. + TSharedPtr _OpenInHoudini; + // Menu action called to save a HIP file. + TSharedPtr _SaveHIPFile; + // Menu action called to clean up all unused files in the cook temp folder + TSharedPtr _CleanUpTempFolder; + + // + TSharedPtr _OnlineDoc; + // + TSharedPtr _OnlineForum; + // Menu action called to report a bug. + TSharedPtr _ReportBug; + + // UI Action to recook all HDA + TSharedPtr _CookAll; + // UI Action to recook the current world selection + TSharedPtr _CookSelected; + // Menu action to bake/replace all current Houdini Assets with blueprints + TSharedPtr _BakeAll; + // UI Action to bake and replace the current world selection + TSharedPtr _BakeSelected; + // UI Action to rebuild all HDA + TSharedPtr _RebuildAll; + // UI Action to rebuild the current world selection + TSharedPtr _RebuildSelected; + // UI Action for building static meshes for all assets using HoudiniStaticMesh + TSharedPtr _RefineAll; + // UI Action for building static meshes for selected assets using HoudiniStaticMesh + TSharedPtr _RefineSelected; + // Menu action to pause cooking for all Houdini Assets + TSharedPtr _PauseAssetCooking; + + // UI Action to recentre the current selection + TSharedPtr _RecentreSelected; + + // Start PDG/BGEO commandlet + TSharedPtr _StartPDGCommandlet; + // Stop PDG/BGEO commandlet + TSharedPtr _StopPDGCommandlet; + // Is PDG/BGEO commandlet enabled + TSharedPtr _IsPDGCommandletEnabled; + +protected: + + // Triage a HoudiniAssetComponent with UHoudiniStaticMesh as needing cooking or if a UStaticMesh can be immediately built + static void TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped); + + static EHoudiniProxyRefineRequestResult RefineTriagedHoudiniProxyMesehesToStaticMeshes( + const TArray& InComponentsToRefine, + const TArray& InComponentsToCook, + const TArray& InSkippedComponents, + bool bInSilent=false, + bool bInRefineAll=true, + bool bInOnPreSaveWorld=false, + UWorld* InOnPreSaveWorld=nullptr, + bool bInOnPrePIEBeginPlay=false); + + // Called in a background thread by RefineHoudiniProxyMeshesToStaticMeshes when some components need to be cooked to generate UStaticMeshes. Checks and waits for + // cooking of each component to complete, and then calls RefineHoudiniProxyMeshesToStaticMeshesNotifyDone on the main thread. + static void RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray &InComponentsToCook, TSharedPtr InTaskProgress, const uint32 InNumSkippedComponents, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents); + + // Display a notification / end/close progress dialog, when refining mesh proxies to static meshes is complete + static void RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(const uint32 InNumTotalComponents, FSlowTask* const InTaskProgress, const bool bCancelled, const bool bOnPreSaveWorld, UWorld* const InOnPreSaveWorld, const TArray &InSuccessfulComponents, const TArray &InFailedComponents, const TArray &InSkippedComponents); + + // Handle OnPostSaveWorld for refining proxy meshes: this saves all the dirty UPackages of the UStaticMeshes that were created during RefineHoudiniProxyMeshesToStaticMeshes + // if it was called as a result of a PreSaveWorld. + static void RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess); + + // Helper function used to indicate to all HAC that they need to be instantiated in the new HE session + // Needs to be call after starting/restarting/connecting/session syncing a HE session.. + static void MarkAllHACsAsNeedInstantiation(); + + // Delegate that is set up to refined proxy meshes post save world (it removes itself afterwards) + static FDelegateHandle OnPostSaveWorldRefineProxyMeshesHandle; + + // Delegate for broadcasting when proxy mesh refinement of a HAC's output is complete. + static FOnHoudiniProxyMeshesRefinedDelegate OnHoudiniProxyMeshesRefinedDelegate; +}; + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp index 87d733797..be5d19638 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp @@ -1,1942 +1,2042 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetComponentDetails.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" - -#include "CoreMinimal.h" -#include "DetailCategoryBuilder.h" -#include "IDetailGroup.h" -#include "DetailWidgetRow.h" -#include "Widgets/SBoxPanel.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Layout/SScrollBox.h" -#include "Widgets/Input/SMultiLineEditableTextBox.h" -#include "Widgets/Layout/SUniformGridPanel.h" -#include "Brushes/SlateImageBrush.h" -#include "Widgets/Input/SComboBox.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "ActorPickerMode.h" -#include "SceneOutlinerModule.h" -#include "Modules/ModuleManager.h" -#include "Interfaces/IMainFrameModule.h" -#include "AssetThumbnail.h" -#include "DetailLayoutBuilder.h" -#include "SAssetDropTarget.h" -#include "PropertyCustomizationHelpers.h" -#include "ScopedTransaction.h" -#include "SEnumCombobox.h" -#include "HAL/FileManager.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define HOUDINI_ENGINE_UI_SECTION_GENERATE 1 -#define HOUDINI_ENGINE_UI_SECTION_BAKE 2 -#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS 3 -#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG 4 - -#define HOUDINI_ENGINE_UI_BUTTON_WIDTH 135.0f - -#define HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT "Generate" -#define HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT "Bake" -#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT "Asset Options" -#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT "Help and Debug" - - -void -SHoudiniAssetLogWidget::Construct(const FArguments & InArgs) -{ - this->ChildSlot - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background"))) - .Content() - [ - SNew(SScrollBox) - + SScrollBox::Slot() - [ - SNew(SMultiLineEditableTextBox) - .Text(FText::FromString(InArgs._LogText)) - .AutoWrapText(true) - .IsReadOnly(true) - ] - ] - ]; -} - - -void -FHoudiniEngineDetails::CreateWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // 0. Houdini Engine Icon - FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs); - - // 1. Houdini Engine Session Status - FHoudiniAssetComponentDetails::AddSessionStatusRow(HoudiniEngineCategoryBuilder); - - // 2. Create Generate Category - FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 3. Create Bake Category - FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 4. Create Asset Options Category - FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs); - - // 5. Create Help and Debug Category - FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs); - -} - -void -FHoudiniEngineDetails::CreateHoudiniEngineIconWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Skip drawing the icon if the icon image is not loaded correctly. - TSharedPtr HoudiniEngineUIIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIIconBrush(); - if (!HoudiniEngineUIIconBrush.IsValid()) - return; - - FDetailWidgetRow & Row = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef Box = SNew(SHorizontalBox); - TSharedPtr Image; - - Box->AddSlot() - .AutoWidth() - .Padding(0.0f, 5.0f, 5.0f, 10.0f) - .HAlign(HAlign_Left) - [ - SNew(SBox) - .HeightOverride(30) - .WidthOverride(208) - [ - SAssignNew(Image, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - Image->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIIconBrush]() { - return HoudiniEngineUIIconBrush.Get(); - }))); - - Row.WholeRowWidget.Widget = Box; - Row.IsEnabled(false); -} - -void -FHoudiniEngineDetails::CreateGenerateWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent* MainHAC = InHACs[0]; - - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - auto OnReBuildClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->MarkAsNeedRebuild(); - } - - return FReply::Handled(); - }; - - auto OnRecookClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->MarkAsNeedCook(); - } - - return FReply::Handled(); - }; - - auto ShouldEnableResetParametersButtonLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - // Reset parameters to default values? - for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) - { - UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); - - if (NextParm && !NextParm->IsDefault()) - return true; - } - } - - return false; - }; - - auto OnResetParametersClickedLambda = [InHACs]() - { - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - // Reset parameters to default values? - for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) - { - UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); - - if (NextParm && !NextParm->IsDefault()) - { - NextParm->RevertToDefault(); - } - } - } - - return FReply::Handled(); - }; - - auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) - { - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FString NewPathStr = Val.ToString(); - - if (NewPathStr.IsEmpty()) - return; - - if (NewPathStr.StartsWith("Game/")) - { - NewPathStr = "/" + NewPathStr; - } - - FString AbsolutePath; - if (NewPathStr.StartsWith("/Game/")) - { - FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); - } - else - { - AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); - } - - if (!FPaths::DirectoryExists(AbsolutePath)) - { - HOUDINI_LOG_WARNING(TEXT("Invalid path")); - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - return; - } - - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr)) - continue; - - NextHAC->TemporaryCookFolder.Path = NewPathStr; - } - }; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_GENERATE); - - // Button Row (draw only if expanded) - if (!MainHAC->bGenerateMenuExpanded) - return; - - TSharedPtr HoudiniEngineUIRebuildIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRebuildIconBrush(); - TSharedPtr HoudiniEngineUIRecookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - TSharedPtr HoudiniEngineUIResetParametersIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIResetParametersIconBrush(); - - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef ButtonHorizontalBox = SNew(SHorizontalBox); - - // Recook button - TSharedPtr RecookButton; - TSharedPtr RecookButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(RecookButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) - //.Text(FText::FromString("Recook")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnRecookClickedLambda) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) - ] - ] - ] - ]; - - if (HoudiniEngineUIRecookIconBrush.IsValid()) - { - TSharedPtr RecookImage; - RecookButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RecookImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - RecookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { - return HoudiniEngineUIRecookIconBrush.Get(); - }))); - } - - RecookButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Recook")) - ]; - - // Rebuild button - TSharedPtr RebuildButton; - TSharedPtr RebuildButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(15.0f, 0.0f, 0.0f, 2.0f) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(RebuildButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) - //.Text(FText::FromString("Rebuild")) - .Visibility(EVisibility::Visible) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) - ] - ] - .OnClicked_Lambda(OnReBuildClickedLambda) - ] - ]; - - if (HoudiniEngineUIRebuildIconBrush.IsValid()) - { - TSharedPtr RebuildImage; - RebuildButtonHorizontalBox->AddSlot() - //.Padding(25.0f, 0.0f, 3.0f, 0.0f) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RebuildImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - RebuildImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { - return HoudiniEngineUIRebuildIconBrush.Get(); - }))); - } - - RebuildButtonHorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .AutoWidth() - .Padding(5.0, 0.0, 0.0, 0.0) - [ - SNew(STextBlock) - .Text(FText::FromString("Rebuild")) - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; - ButtonRow.IsEnabled(false); - - // Reset Parameters button - TSharedPtr ResetParametersButton; - TSharedPtr ResetParametersButtonHorizontalBox; - ButtonHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(2.0f, 0.0f, 0.0f, 2.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(ResetParametersButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsResetParametersAssetButton", "Reset the selected Houdini Asset's parameters to their default values.")) - //.Text(FText::FromString("Reset Parameters")) - .IsEnabled_Lambda(ShouldEnableResetParametersButtonLambda) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnResetParametersClickedLambda) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .HAlign(HAlign_Center) - [ - SAssignNew(ResetParametersButtonHorizontalBox, SHorizontalBox) - ] - ] - ] - ]; - - if (HoudiniEngineUIResetParametersIconBrush.IsValid()) - { - TSharedPtr ResetParametersImage; - ResetParametersButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(0.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(ResetParametersImage, SImage) - //.ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - ResetParametersImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([HoudiniEngineUIResetParametersIconBrush]() { - return HoudiniEngineUIResetParametersIconBrush.Get(); - }))); - } - - ResetParametersButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - //.FillWidth(4.2f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - //.MinDesiredWidth(160.f) - .Text(FText::FromString("Reset Parameters")) - ]; - - - // Temp Cook Folder Row - FDetailWidgetRow & TempCookFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef TempCookFolderRowHorizontalBox = SNew(SHorizontalBox); - - TempCookFolderRowHorizontalBox->AddSlot() - //.Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineTemporaryCookFolderLabel", "Temporary Cook Folder")) - .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, MAterials, Textures..) that are generated by Houdini Assets when they cook.")) - ] - ]; - - TempCookFolderRowHorizontalBox->AddSlot() - .MaxWidth(235.0f) - [ - SNew(SBox) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, Materials, Textures..) that are generated by Houdini Assets when they cook.")) - .HintText(LOCTEXT("HoudiniEngineTempCookFolderHintText", "Input to set temporary cook folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainHAC->TemporaryCookFolder.Path)) - .OnTextCommitted_Lambda(OnCookFolderTextCommittedLambda) - ] - ]; - - TempCookFolderRow.WholeRowWidget.Widget = TempCookFolderRowHorizontalBox; -} - -void -FHoudiniEngineDetails::OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC) -{ - if (!IsValid(InHAC)) - return; - - if (!bInState) - { - if (InHAC->GetOnPostCookBakeDelegate().IsBound()) - InHAC->GetOnPostCookBakeDelegate().Unbind(); - } - else - { - InHAC->GetOnPostCookBakeDelegate().BindLambda([](UHoudiniAssetComponent* HAC) - { - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - HAC, - HAC->bReplacePreviousBake, - HAC->HoudiniEngineBakeOption, - HAC->bRemoveOutputAfterBake, - HAC->bRecenterBakedActors); - }); - } -} - -void -FHoudiniEngineDetails::CreateBakeWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE); - - if (!MainHAC->bBakeMenuExpanded) - return; - - auto OnBakeButtonClickedLambda = [InHACs, MainHAC]() - { - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - NextHAC, - MainHAC->bReplacePreviousBake, - MainHAC->HoudiniEngineBakeOption, - MainHAC->bRemoveOutputAfterBake, - MainHAC->bRecenterBakedActors); - } - - return FReply::Handled(); - }; - - auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) - { - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - FString NewPathStr = Val.ToString(); - - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - if (NextHAC->BakeFolder.Path.Equals(NewPathStr)) - continue; - - NextHAC->BakeFolder.Path = NewPathStr; - } - }; - - // Button Row - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - - // Bake Button - TSharedPtr BakeButton; - TSharedPtr BakeButtonHorizontalBox; - - ButtonRowHorizontalBox->AddSlot() - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - //.Padding(15.f, 0.0f, 0.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(BakeButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(LOCTEXT("HoudiniAssetDetailsBakeButton", "Bake the Houdini Asset Component(s).")) - //.Text(FText::FromString("Recook")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnBakeButtonClickedLambda) - .Content() - [ - SAssignNew(BakeButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); - if (BakeIconBrush.IsValid()) - { - TSharedPtr BakeImage; - BakeButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(BakeImage, SImage) - ] - ]; - - BakeImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([BakeIconBrush]() { - return BakeIconBrush.Get(); - }))); - } - - BakeButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Bake")) - ]; - - // Bake Type ComboBox - TSharedPtr>> TypeComboBox; - - TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEngineBakeTypeOptionsLabels(); - TSharedPtr IntialSelec; - if (OptionSource) - { - IntialSelec = (*OptionSource)[(int)MainHAC->HoudiniEngineBakeOption]; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - //.MaxWidth(103.f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - //.WidthOverride(103.f) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [MainHAC, InHACs](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EHoudiniEngineBakeOption NewOption = - FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - MainHAC->HoudiniEngineBakeOption = NewOption; - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - [ - SNew(STextBlock) - .Text_Lambda([MainHAC]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(MainHAC->HoudiniEngineBakeOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; - - // Clear Output After Baking Row - FDetailWidgetRow & ClearOutputAfterBakingRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); - - // Remove Output Checkbox - TSharedPtr CheckBoxRemoveOutput; - TSharedPtr CheckBoxAutoBake; - TSharedPtr CheckBoxRecenterBakedActors; - TSharedPtr CheckBoxReplacePreviousBake; - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRemoveOutput, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBox", "Remove HDA Output After Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBoxToolTip", "After baking the existing output of this Houdini Asset Actor will be removed.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bRemoveOutputAfterBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bRemoveOutputAfterBake = bNewState; - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bRecenterBakedActors = bNewState; - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - // TODO: find a better way to manage the initial binding/unbinding of the post cook bake delegate - // We do this here to ensure the delegate is bound/unbound correctly when the UI is initially drawn - // Currently we have the problem that the HoudiniEngineRuntime and HoudiniEngine modules cannot access - // the FHoudiniEngineBakeUtils code that is in HoudiniEngineEditor (this is the primary reason for the delegate and - // managing the delegate in this way). - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - const bool bState = NextHAC->IsBakeAfterNextCookEnabled(); - NextHAC->SetBakeAfterNextCookEnabled(bState); - OnBakeAfterCookChangedHelper(bState, NextHAC); - } - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxAutoBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake the next cook.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->IsBakeAfterNextCookEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->SetBakeAfterNextCookEnabled(bNewState); - OnBakeAfterCookChangedHelper(bNewState, NextHAC); - } - - // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - // Replace Checkbox - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxReplacePreviousBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBox", "Replace Previous Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBoxToolTip", "When baking replace the previous bake's output instead of creating additional output actors/components/objects.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainHAC]() - { - return MainHAC->bReplacePreviousBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - MainHAC->bReplacePreviousBake = bNewState; - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); - }) - ] - ]; - - ClearOutputAfterBakingRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; - - // Bake Folder Row - FDetailWidgetRow & BakeFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT( - "HoudiniEngineBakeFolderTooltip", - "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " - "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " - "plugin settings is used.")) - ] - ]; - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .MaxWidth(235.0) - [ - SNew(SBox) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT( - "HoudiniEngineBakeFolderTooltip", - "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " - "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " - "plugin settings is used.")) - .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text_Lambda([MainHAC]() - { - if (!IsValid(MainHAC)) - return FText(); - return FText::FromString(MainHAC->BakeFolder.Path); - }) - .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) - ] - ]; - - BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; - - switch (MainHAC->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToActorToolTip", - "Bake this Houdini Asset Actor and its components to native unreal actors and components, replacing the previous baked result.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToActorToolTip", - "Bake this Houdini Asset Actor and its components to native unreal actors and components.")); - } - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToBlueprintToolTip", - "Bake this Houdini Asset Actor to a blueprint.")); - } - break; - - case EHoudiniEngineBakeOption::ToFoliage: - { - if (!FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(MainHAC)) - { - // If the HAC does not have instanced output, disable Bake to Foliage - BakeButton->SetEnabled(false); - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonNoInstancedOutputToolTip", - "The Houdini Asset must be outputing at least one instancer in order to be able to bake to Foliage.")); - } - else - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToFoliageToolTip", - "Add this Houdini Asset Actor's instancers to the current level's Foliage, replacing the previously baked foliage instancers from this actor.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToFoliageToolTip", - "Add this Houdini Asset Actor's instancers to the current level's Foliage.")); - } - } - } - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - { - if (MainHAC->bReplacePreviousBake) - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToWorldOutlinerToolTip", - "Not implemented.")); - } - else - { - BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToWorldOutlinerToolTip", - "Not implemented.")); - } - } - break; - } - - // Todo: remove me! - if (MainHAC->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToWorldOutliner) - BakeButton->SetEnabled(false); - -} - -void -FHoudiniEngineDetails::CreateAssetOptionsWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Header Row - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS); - - if (!MainHAC->bAssetOptionMenuExpanded) - return; - - auto IsCheckedParameterChangedLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateParameterChangedLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnParameterChange = bChecked; - } - }; - - auto IsCheckedTransformChangeLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedTransformChangeLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnTransformChange = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedAssetInputCookLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedAssetInputCookLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bCookOnAssetInputCook = bChecked; - } - }; - - auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedPushTransformToHoudiniLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bUploadTransformsToHoudiniEngine = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedDoNotGenerateOutputsLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bOutputless = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]() - { - if (!MainHAC || MainHAC->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto OnCheckStateChangedOutputTemplatedGeosLambda = [InHACs](ECheckBoxState NewState) - { - bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; - - NextHAC->bOutputTemplateGeos = bChecked; - - NextHAC->MarkAsNeedCook(); - } - }; - - // Checkboxes row - FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef CheckBoxesHorizontalBox = SNew(SHorizontalBox); - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - CheckBoxesHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - CheckBoxesHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineCookTriggersLabel", "Cook Triggers")) - ] - ]; - - // Parameter change check box - FText TooltipText = LOCTEXT("HoudiniEngineParameterChangeTooltip", "If enabled, modifying a parameter or input on this Houdini Asset will automatically trigger a cook of the HDA in Houdini."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineParameterChangeCheckBoxLabel", "On Parameter/Input Change")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateParameterChangedLambda) - .IsChecked_Lambda(IsCheckedParameterChangedLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Transform change check box - TooltipText = LOCTEXT("HoudiniEngineTransformChangeTooltip", "If enabled, changing the Houdini Asset Actor's transform in Unreal will also update its HDA's node transform in Houdini, and trigger a recook of the HDA with the updated transform."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineTransformChangeCheckBoxLabel", "On Transform Change")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedTransformChangeLambda) - .IsChecked_Lambda(IsCheckedTransformChangeLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Triggers Downstream cook checkbox - TooltipText = LOCTEXT("HoudiniEngineAssetInputCookTooltip", "When enabled, this asset will automatically re-cook after one its asset input has finished cooking."); - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineAssetInputCheckBoxLabel", "On Asset Input Cook")) - .ToolTipText(TooltipText) - ] - - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedAssetInputCookLambda) - .IsChecked_Lambda(IsCheckedAssetInputCookLambda) - .ToolTipText(TooltipText) - ] - ]; - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous")) - ]; - - // Push Transform to Houdini check box - TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini")) - .ToolTipText(TooltipText) - ] - - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda) - .IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Do not generate output check box - TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda) - .IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda) - .ToolTipText(TooltipText) - ] - ]; - - // Output templated geos check box - TooltipText = LOCTEXT("HoudiniEnginOutputTemplatesTooltip", "If enabled, Geometry nodes in the asset that have the template flag will be outputed."); - RightColumnVerticalBox->AddSlot() - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(4.0f) - [ - SNew(STextBlock) - .MinDesiredWidth(160.f) - .Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Output Templated Geos")) - .ToolTipText(TooltipText) - ] - + SHorizontalBox::Slot() - [ - SNew(SCheckBox) - .OnCheckStateChanged_Lambda(OnCheckStateChangedOutputTemplatedGeosLambda) - .IsChecked_Lambda(IsCheckedOutputTemplatedGeosLambda) - .ToolTipText(TooltipText) - ] - ]; - - CheckBoxesRow.WholeRowWidget.Widget = CheckBoxesHorizontalBox; -} - -void -FHoudiniEngineDetails::CreateHelpAndDebugWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs) -{ - if (InHACs.Num() <= 0) - return; - - UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) - return; - - // Header Row - FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG); - - if (!MainHAC->bHelpAndDebugMenuExpanded) - return; - - auto OnFetchCookLogButtonClickedLambda = [InHACs]() - { - return ShowCookLog(InHACs); - }; - - auto OnHelpButtonClickedLambda = [MainHAC]() - { - return ShowAssetHelp(MainHAC); - }; - - // Button Row - FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - TSharedPtr CookLogButtonHorizontalBox = SNew(SHorizontalBox); - - // Fetch Cook Log button - ButtonRowHorizontalBox->AddSlot() - //.Padding(15.0f, 0.0f, 0.0f, 0.0f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(FText::FromString("Fetch and display all cook logs available for this Houdini Asset Actor.")) - //.Text(FText::FromString("Fetch Cook Log")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnFetchCookLogButtonClickedLambda) - .Content() - [ - SAssignNew(CookLogButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr CookLogIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUICookLogIconBrush(); - if (CookLogIconBrush.IsValid()) - { - TSharedPtr CookImage; - CookLogButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookImage, SImage) - ] - ]; - - CookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookLogIconBrush]() { - return CookLogIconBrush.Get(); - }))); - } - - CookLogButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Show Cook Logs")) - ]; - - // Asset Help Button - TSharedPtr AssetHelpButtonHorizontalBox; - ButtonRowHorizontalBox->AddSlot() - //.Padding(4.0, 0.0f, 0.0f, 0.0f) - .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SBox) - .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .ToolTipText(FText::FromString("Display this Houdini Asset Actor's HDA help.")) - //.Text(FText::FromString("Asset Help")) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda(OnHelpButtonClickedLambda) - .Content() - [ - SAssignNew(AssetHelpButtonHorizontalBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr AssetHelpIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIAssetHelpIconBrush(); - if (AssetHelpIconBrush.IsValid()) - { - TSharedPtr AssetHelpImage; - AssetHelpButtonHorizontalBox->AddSlot() - .MaxWidth(16.0f) - //.Padding(23.0f, 0.0f, 3.0f, 0.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(AssetHelpImage, SImage) - ] - ]; - - AssetHelpImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([AssetHelpIconBrush]() { - return AssetHelpIconBrush.Get(); - }))); - } - - AssetHelpButtonHorizontalBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Asset Help")) - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; -} - -FMenuBuilder -FHoudiniEngineDetails::Helper_CreateHoudiniAssetPicker() -{ - auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - return true; - }; - - auto OnActorSelected = [](AActor* Actor) - { - UE_LOG(LogTemp, Warning, TEXT("Actor Selected")); - - return; - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterHoudiniAssetLambda); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("HoudiniEngineDetailsAssetPicker", "Asset")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -const FSlateBrush * -FHoudiniEngineDetails::GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const -{ - if (HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - -/* -TSharedRef< SWidget > -FHoudiniEngineDetails::OnGetHoudiniAssetMenuContent(TArray InHACs) -{ - TArray< const UClass * > AllowedClasses; - AllowedClasses.Add(UHoudiniAsset::StaticClass()); - - TArray< UFactory * > NewAssetFactories; - - UHoudiniAsset * HoudiniAsset = nullptr; - if (InHACs.Num() > 0) - { - UHoudiniAssetComponent * HoudiniAssetComponent = InHACs[0]; - HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; - } - - auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - return true; - }; - - // Delegate for filtering Houdini assets. - FOnShouldFilterAsset OnShouldFilterHoudiniAsset = FOnShouldFilterAsset::CreateLambda(OnShouldFilterHoudiniAssetLambda); - - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(HoudiniAsset), true, - AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset, - FOnAssetSelected::CreateLambda([](const FAssetData & AssetData) {}), - FSimpleDelegate::CreateLambda([]() { }) - ); -} -*/ - -FReply -FHoudiniEngineDetails::ShowCookLog(TArray InHACS) -{ - TSharedPtr< SWindow > ParentWindow; - FString CookLog = FHoudiniEngineUtils::GetCookLog(InHACS); - - // 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("MainFrame"); - ParentWindow = MainFrame.GetParentWindow(); - } - - if (ParentWindow.IsValid()) - { - TSharedPtr HoudiniAssetCookLog; - - TSharedRef Window = - SNew(SWindow) - .Title(LOCTEXT("WindowTitle", "Houdini Cook Log")) - .ClientSize(FVector2D(640, 480)); - - Window->SetContent( - SAssignNew(HoudiniAssetCookLog, SHoudiniAssetLogWidget) - .LogText(CookLog)); - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - } - - return FReply::Handled(); -} - -FReply -FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC) -{ - if (!InHAC) - return FReply::Handled(); - - FString AssetHelp = FHoudiniEngineUtils::GetAssetHelp(InHAC); - - 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 HoudiniAssetHelpLog; - - TSharedRef Window = - SNew(SWindow) - .Title(LOCTEXT("WindowTitle", "Houdini Asset Help")) - .ClientSize(FVector2D(640, 480)); - - Window->SetContent( - SAssignNew(HoudiniAssetHelpLog, SHoudiniAssetLogWidget) - .LogText(AssetHelp)); - - if (FSlateApplication::IsInitialized()) - FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); - } - return FReply::Handled(); -} - -void -FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection) -{ - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return; - - FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - HoudiniAssetComponent->bGenerateMenuExpanded = !HoudiniAssetComponent->bGenerateMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - HoudiniAssetComponent->bBakeMenuExpanded = !HoudiniAssetComponent->bBakeMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - HoudiniAssetComponent->bAssetOptionMenuExpanded = !HoudiniAssetComponent->bAssetOptionMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - HoudiniAssetComponent->bHelpAndDebugMenuExpanded = !HoudiniAssetComponent->bHelpAndDebugMenuExpanded; - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniAssetComponent, true); - - return FReply::Handled(); - }); - - TFunction GetText = [MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT); - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT); - break; - } - return FText::FromString(""); - }; - - TFunction GetExpanderBrush = [HoudiniAssetComponent, MenuSection](SButton* InExpanderArrow) - { - FName ResourceName; - bool bMenuExpanded = false; - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_GENERATE: - bMenuExpanded = HoudiniAssetComponent->bGenerateMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_BAKE: - bMenuExpanded = HoudiniAssetComponent->bBakeMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: - bMenuExpanded = HoudiniAssetComponent->bAssetOptionMenuExpanded; - break; - - case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: - bMenuExpanded = HoudiniAssetComponent->bHelpAndDebugMenuExpanded; - } - - if (bMenuExpanded) - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - }; - - return AddHeaderRow(HoudiniEngineCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); -} - -void -FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]() - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - InPDGAssetLink->Modify(); - InPDGAssetLink->bBakeMenuExpanded = !InPDGAssetLink->bBakeMenuExpanded; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded), InPDGAssetLink); - break; - } - - //FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, true); - - return FReply::Handled(); - }); - - TFunction GetText = [MenuSection]() - { - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); - break; - } - return FText::FromString(""); - }; - - TFunction GetExpanderBrush = [InPDGAssetLink, MenuSection](SButton* InExpanderArrow) - { - FName ResourceName; - bool bMenuExpanded = false; - - switch (MenuSection) - { - case HOUDINI_ENGINE_UI_SECTION_BAKE: - bMenuExpanded = InPDGAssetLink->bBakeMenuExpanded; - break; - } - - if (bMenuExpanded) - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - }; - - return AddHeaderRow(PDGCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); -} - -void -FHoudiniEngineDetails::AddHeaderRow( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - FOnClicked& InOnExpanderClick, - TFunction& InGetText, - TFunction& InGetExpanderBrush) -{ - // Header Row - FDetailWidgetRow & HeaderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); - TSharedPtr HeaderHorizontalBox; - HeaderRow.WholeRowWidget.Widget = SAssignNew(HeaderHorizontalBox, SHorizontalBox); - - TSharedPtr ExpanderImage; - TSharedPtr ExpanderArrow; - HeaderHorizontalBox->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth() - [ - SAssignNew(ExpanderArrow, SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - .OnClicked(InOnExpanderClick) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - HeaderHorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SNew(STextBlock) - .Text_Lambda([InGetText](){return InGetText(); }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - ExpanderImage->SetImage( - TAttribute::Create( - [ExpanderArrow, InGetExpanderBrush]() - { - return InGetExpanderBrush(ExpanderArrow.Get()); - })); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" + +#include "CoreMinimal.h" +#include "DetailCategoryBuilder.h" +#include "IDetailGroup.h" +#include "DetailWidgetRow.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Brushes/SlateImageBrush.h" +#include "Widgets/Input/SComboBox.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "ActorPickerMode.h" +#include "SceneOutlinerModule.h" +#include "Modules/ModuleManager.h" +#include "Interfaces/IMainFrameModule.h" +#include "AssetThumbnail.h" +#include "DetailLayoutBuilder.h" +#include "SAssetDropTarget.h" +#include "PropertyCustomizationHelpers.h" +#include "ScopedTransaction.h" +#include "SEnumCombobox.h" +#include "HAL/FileManager.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define HOUDINI_ENGINE_UI_SECTION_GENERATE 1 +#define HOUDINI_ENGINE_UI_SECTION_BAKE 2 +#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS 3 +#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG 4 + +#define HOUDINI_ENGINE_UI_BUTTON_WIDTH 135.0f + +#define HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT "Generate" +#define HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT "Bake" +#define HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT "Asset Options" +#define HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT "Help and Debug" + + +void +SHoudiniAssetLogWidget::Construct(const FArguments & InArgs) +{ + this->ChildSlot + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush(TEXT("Menu.Background"))) + .Content() + [ + SNew(SScrollBox) + + SScrollBox::Slot() + [ + SNew(SMultiLineEditableTextBox) + .Text(FText::FromString(InArgs._LogText)) + .AutoWrapText(true) + .IsReadOnly(true) + ] + ] + ]; +} + + +void +FHoudiniEngineDetails::CreateWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // 0. Houdini Engine Icon + FHoudiniEngineDetails::CreateHoudiniEngineIconWidget(HoudiniEngineCategoryBuilder, InHACs); + + // 1. Houdini Engine Session Status + FHoudiniAssetComponentDetails::AddSessionStatusRow(HoudiniEngineCategoryBuilder); + + // 2. Create Generate Category + FHoudiniEngineDetails::CreateGenerateWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 3. Create Bake Category + FHoudiniEngineDetails::CreateBakeWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 4. Create Asset Options Category + FHoudiniEngineDetails::CreateAssetOptionsWidgets(HoudiniEngineCategoryBuilder, InHACs); + + // 5. Create Help and Debug Category + FHoudiniEngineDetails::CreateHelpAndDebugWidgets(HoudiniEngineCategoryBuilder, InHACs); + +} + +void +FHoudiniEngineDetails::CreateHoudiniEngineIconWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Skip drawing the icon if the icon image is not loaded correctly. + TSharedPtr HoudiniEngineUIIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIIconBrush(); + if (!HoudiniEngineUIIconBrush.IsValid()) + return; + + FDetailWidgetRow & Row = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef Box = SNew(SHorizontalBox); + TSharedPtr Image; + + Box->AddSlot() + .AutoWidth() + .Padding(0.0f, 5.0f, 5.0f, 10.0f) + .HAlign(HAlign_Left) + [ + SNew(SBox) + .HeightOverride(30) + .WidthOverride(208) + [ + SAssignNew(Image, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + Image->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIIconBrush]() { + return HoudiniEngineUIIconBrush.Get(); + }))); + + Row.WholeRowWidget.Widget = Box; + Row.IsEnabled(false); +} + +void +FHoudiniEngineDetails::CreateGenerateWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent* MainHAC = InHACs[0]; + + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + auto OnReBuildClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->MarkAsNeedRebuild(); + } + + return FReply::Handled(); + }; + + auto OnRecookClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->MarkAsNeedCook(); + } + + return FReply::Handled(); + }; + + auto ShouldEnableResetParametersButtonLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + // Reset parameters to default values? + for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) + { + UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); + + if (NextParm && !NextParm->IsDefault()) + return true; + } + } + + return false; + }; + + auto OnResetParametersClickedLambda = [InHACs]() + { + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + // Reset parameters to default values? + for (int32 n = 0; n < NextHAC->GetNumParameters(); ++n) + { + UHoudiniParameter* NextParm = NextHAC->GetParameterAt(n); + + if (NextParm && !NextParm->IsDefault()) + { + NextParm->RevertToDefault(); + } + } + } + + return FReply::Handled(); + }; + + auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) + { + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FString NewPathStr = Val.ToString(); + + if (NewPathStr.IsEmpty()) + return; + + if (NewPathStr.StartsWith("Game/")) + { + NewPathStr = "/" + NewPathStr; + } + + FString AbsolutePath; + if (NewPathStr.StartsWith("/Game/")) + { + FString RelativePath = FPaths::ProjectContentDir() + NewPathStr.Mid(6, NewPathStr.Len() - 6); + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativePath); + } + else + { + AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*NewPathStr); + } + + if (!FPaths::DirectoryExists(AbsolutePath)) + { + HOUDINI_LOG_WARNING(TEXT("Invalid path")); + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + return; + } + + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr)) + continue; + + NextHAC->TemporaryCookFolder.Path = NewPathStr; + } + }; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_GENERATE); + + // Button Row (draw only if expanded) + if (!MainHAC->bGenerateMenuExpanded) + return; + + TSharedPtr HoudiniEngineUIRebuildIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRebuildIconBrush(); + TSharedPtr HoudiniEngineUIRecookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + TSharedPtr HoudiniEngineUIResetParametersIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIResetParametersIconBrush(); + + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef ButtonHorizontalBox = SNew(SHorizontalBox); + + // Recook button + TSharedPtr RecookButton; + TSharedPtr RecookButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(RecookButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRecookAssetButton", "Recook the selected Houdini Asset: all parameters and inputs are re-upload to Houdini and the asset is then forced to recook.")) + //.Text(FText::FromString("Recook")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnRecookClickedLambda) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(RecookButtonHorizontalBox, SHorizontalBox) + ] + ] + ] + ]; + + if (HoudiniEngineUIRecookIconBrush.IsValid()) + { + TSharedPtr RecookImage; + RecookButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RecookImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + RecookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRecookIconBrush]() { + return HoudiniEngineUIRecookIconBrush.Get(); + }))); + } + + RecookButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + //.MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Recook")) + ]; + + // Rebuild button + TSharedPtr RebuildButton; + TSharedPtr RebuildButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(15.0f, 0.0f, 0.0f, 2.0f) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(RebuildButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsRebuildAssetButton", "Rebuild the selected Houdini Asset: its source .HDA file is reimported and updated, the asset's nodes in Houdini are destroyed and recreated, and the asset is then forced to recook.")) + //.Text(FText::FromString("Rebuild")) + .Visibility(EVisibility::Visible) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(RebuildButtonHorizontalBox, SHorizontalBox) + ] + ] + .OnClicked_Lambda(OnReBuildClickedLambda) + ] + ]; + + if (HoudiniEngineUIRebuildIconBrush.IsValid()) + { + TSharedPtr RebuildImage; + RebuildButtonHorizontalBox->AddSlot() + //.Padding(25.0f, 0.0f, 3.0f, 0.0f) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RebuildImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + RebuildImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIRebuildIconBrush]() { + return HoudiniEngineUIRebuildIconBrush.Get(); + }))); + } + + RebuildButtonHorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .AutoWidth() + .Padding(5.0, 0.0, 0.0, 0.0) + [ + SNew(STextBlock) + .Text(FText::FromString("Rebuild")) + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonHorizontalBox; + ButtonRow.IsEnabled(false); + + // Reset Parameters button + TSharedPtr ResetParametersButton; + TSharedPtr ResetParametersButtonHorizontalBox; + ButtonHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(2.0f, 0.0f, 0.0f, 2.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(ResetParametersButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsResetParametersAssetButton", "Reset the selected Houdini Asset's parameters to their default values.")) + //.Text(FText::FromString("Reset Parameters")) + .IsEnabled_Lambda(ShouldEnableResetParametersButtonLambda) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnResetParametersClickedLambda) + .Content() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .HAlign(HAlign_Center) + [ + SAssignNew(ResetParametersButtonHorizontalBox, SHorizontalBox) + ] + ] + ] + ]; + + if (HoudiniEngineUIResetParametersIconBrush.IsValid()) + { + TSharedPtr ResetParametersImage; + ResetParametersButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(0.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(ResetParametersImage, SImage) + //.ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + ResetParametersImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([HoudiniEngineUIResetParametersIconBrush]() { + return HoudiniEngineUIResetParametersIconBrush.Get(); + }))); + } + + ResetParametersButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + //.FillWidth(4.2f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + //.MinDesiredWidth(160.f) + .Text(FText::FromString("Reset Parameters")) + ]; + + + // Temp Cook Folder Row + FDetailWidgetRow & TempCookFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef TempCookFolderRowHorizontalBox = SNew(SHorizontalBox); + + TempCookFolderRowHorizontalBox->AddSlot() + //.Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineTemporaryCookFolderLabel", "Temporary Cook Folder")) + .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, MAterials, Textures..) that are generated by Houdini Assets when they cook.")) + ] + ]; + + TempCookFolderRowHorizontalBox->AddSlot() + .MaxWidth(235.0f) + [ + SNew(SBox) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT("HoudiniEngineTemporaryCookFolderTooltip", "Default folder used to store the temporary files (Static Meshes, Materials, Textures..) that are generated by Houdini Assets when they cook.")) + .HintText(LOCTEXT("HoudiniEngineTempCookFolderHintText", "Input to set temporary cook folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainHAC->TemporaryCookFolder.Path)) + .OnTextCommitted_Lambda(OnCookFolderTextCommittedLambda) + ] + ]; + + TempCookFolderRow.WholeRowWidget.Widget = TempCookFolderRowHorizontalBox; +} + +void +FHoudiniEngineDetails::OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC) +{ + if (!IsValid(InHAC)) + return; + + if (!bInState) + { + if (InHAC->GetOnPostCookBakeDelegate().IsBound()) + InHAC->GetOnPostCookBakeDelegate().Unbind(); + } + else + { + InHAC->GetOnPostCookBakeDelegate().BindLambda([](UHoudiniAssetComponent* HAC) + { + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + HAC, + HAC->bReplacePreviousBake, + HAC->HoudiniEngineBakeOption, + HAC->bRemoveOutputAfterBake, + HAC->bRecenterBakedActors); + }); + } +} + +void +FHoudiniEngineDetails::CreateBakeWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE); + + if (!MainHAC->bBakeMenuExpanded) + return; + + auto OnBakeButtonClickedLambda = [InHACs, MainHAC]() + { + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + NextHAC, + MainHAC->bReplacePreviousBake, + MainHAC->HoudiniEngineBakeOption, + MainHAC->bRemoveOutputAfterBake, + MainHAC->bRecenterBakedActors); + } + + return FReply::Handled(); + }; + + auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) + { + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + FString NewPathStr = Val.ToString(); + + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + if (NextHAC->BakeFolder.Path.Equals(NewPathStr)) + continue; + + NextHAC->BakeFolder.Path = NewPathStr; + } + }; + + // Button Row + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + + // Bake Button + TSharedPtr BakeButton; + TSharedPtr BakeButtonHorizontalBox; + + ButtonRowHorizontalBox->AddSlot() + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + //.Padding(15.f, 0.0f, 0.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(BakeButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(LOCTEXT("HoudiniAssetDetailsBakeButton", "Bake the Houdini Asset Component(s).")) + //.Text(FText::FromString("Recook")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnBakeButtonClickedLambda) + .Content() + [ + SAssignNew(BakeButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); + if (BakeIconBrush.IsValid()) + { + TSharedPtr BakeImage; + BakeButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(BakeImage, SImage) + ] + ]; + + BakeImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([BakeIconBrush]() { + return BakeIconBrush.Get(); + }))); + } + + BakeButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Bake")) + ]; + + // Bake Type ComboBox + TSharedPtr>> TypeComboBox; + + TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEngineBakeTypeOptionsLabels(); + TSharedPtr IntialSelec; + if (OptionSource) + { + IntialSelec = (*OptionSource)[(int)MainHAC->HoudiniEngineBakeOption]; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + //.MaxWidth(103.f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + //.WidthOverride(103.f) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [MainHAC, InHACs](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EHoudiniEngineBakeOption NewOption = + FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + MainHAC->HoudiniEngineBakeOption = NewOption; + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + [ + SNew(STextBlock) + .Text_Lambda([MainHAC]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(MainHAC->HoudiniEngineBakeOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; + + // Clear Output After Baking Row + FDetailWidgetRow & ClearOutputAfterBakingRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); + + // Remove Output Checkbox + TSharedPtr CheckBoxRemoveOutput; + TSharedPtr CheckBoxAutoBake; + TSharedPtr CheckBoxRecenterBakedActors; + TSharedPtr CheckBoxReplacePreviousBake; + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRemoveOutput, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBox", "Remove HDA Output After Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRemoveOutputCheckBoxToolTip", "After baking the existing output of this Houdini Asset Actor will be removed.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bRemoveOutputAfterBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bRemoveOutputAfterBake = bNewState; + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bRecenterBakedActors = bNewState; + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + // TODO: find a better way to manage the initial binding/unbinding of the post cook bake delegate + // We do this here to ensure the delegate is bound/unbound correctly when the UI is initially drawn + // Currently we have the problem that the HoudiniEngineRuntime and HoudiniEngine modules cannot access + // the FHoudiniEngineBakeUtils code that is in HoudiniEngineEditor (this is the primary reason for the delegate and + // managing the delegate in this way). + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + const bool bState = NextHAC->IsBakeAfterNextCookEnabled(); + NextHAC->SetBakeAfterNextCookEnabled(bState); + OnBakeAfterCookChangedHelper(bState, NextHAC); + } + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxAutoBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake the next cook.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->IsBakeAfterNextCookEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->SetBakeAfterNextCookEnabled(bNewState); + OnBakeAfterCookChangedHelper(bNewState, NextHAC); + } + + // FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + // Replace Checkbox + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxReplacePreviousBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBox", "Replace Previous Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIBakeReplaceWithPreviousCheckBoxToolTip", "When baking replace the previous bake's output instead of creating additional output actors/components/objects.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainHAC]() + { + return MainHAC->bReplacePreviousBake ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([MainHAC, InHACs](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + MainHAC->bReplacePreviousBake = bNewState; + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainHAC, true); + }) + ] + ]; + + ClearOutputAfterBakingRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; + + // Bake Folder Row + FDetailWidgetRow & BakeFolderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) + .ToolTipText(LOCTEXT( + "HoudiniEngineBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) + ] + ]; + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .MaxWidth(235.0) + [ + SNew(SBox) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT( + "HoudiniEngineBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) + .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text_Lambda([MainHAC]() + { + if (!IsValid(MainHAC)) + return FText(); + return FText::FromString(MainHAC->BakeFolder.Path); + }) + .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) + ] + ]; + + BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; + + switch (MainHAC->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToActorToolTip", + "Bake this Houdini Asset Actor and its components to native unreal actors and components, replacing the previous baked result.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToActorToolTip", + "Bake this Houdini Asset Actor and its components to native unreal actors and components.")); + } + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToBlueprintToolTip", + "Bake this Houdini Asset Actor to a blueprint.")); + } + break; + + case EHoudiniEngineBakeOption::ToFoliage: + { + if (!FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(MainHAC)) + { + // If the HAC does not have instanced output, disable Bake to Foliage + BakeButton->SetEnabled(false); + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonNoInstancedOutputToolTip", + "The Houdini Asset must be outputing at least one instancer in order to be able to bake to Foliage.")); + } + else + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToFoliageToolTip", + "Add this Houdini Asset Actor's instancers to the current level's Foliage, replacing the previously baked foliage instancers from this actor.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToFoliageToolTip", + "Add this Houdini Asset Actor's instancers to the current level's Foliage.")); + } + } + } + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + { + if (MainHAC->bReplacePreviousBake) + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeWithReplaceToWorldOutlinerToolTip", + "Not implemented.")); + } + else + { + BakeButton->SetToolTipText(LOCTEXT("HoudiniEngineBakeButtonBakeToWorldOutlinerToolTip", + "Not implemented.")); + } + } + break; + } + + // Todo: remove me! + if (MainHAC->HoudiniEngineBakeOption == EHoudiniEngineBakeOption::ToWorldOutliner) + BakeButton->SetEnabled(false); + +} + +void +FHoudiniEngineDetails::CreateAssetOptionsWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Header Row + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS); + + if (!MainHAC->bAssetOptionMenuExpanded) + return; + + auto IsCheckedParameterChangedLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateParameterChangedLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnParameterChange = bChecked; + } + }; + + auto IsCheckedTransformChangeLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedTransformChangeLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnTransformChange = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedAssetInputCookLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedAssetInputCookLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bCookOnAssetInputCook = bChecked; + } + }; + + auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedPushTransformToHoudiniLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bUploadTransformsToHoudiniEngine = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedDoNotGenerateOutputsLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bOutputless = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedOutputTemplatedGeosLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bOutputTemplateGeos = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + auto IsCheckedUseOutputNodesLambda = [MainHAC]() + { + if (!MainHAC || MainHAC->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainHAC->bUseOutputNodes ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedUseOutputNodesLambda = [InHACs](ECheckBoxState NewState) + { + bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& NextHAC : InHACs) + { + if (!NextHAC || NextHAC->IsPendingKill()) + continue; + + NextHAC->bUseOutputNodes = bChecked; + + NextHAC->MarkAsNeedCook(); + } + }; + + // Checkboxes row + FDetailWidgetRow & CheckBoxesRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedPtr FirstLeftColumnVerticalBox; + TSharedPtr FirstRightColumnVerticalBox; + TSharedPtr SecondLeftColumnVerticalBox; + TSharedPtr SecondRightColumnVerticalBox; + TSharedRef WidgetBox = SNew(SHorizontalBox); + WidgetBox->AddSlot() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + [ + //First Line + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + // First Left + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(FirstLeftColumnVerticalBox, SVerticalBox) + ] + ] + + SHorizontalBox::Slot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + // First Right + SNew(SBox) + [ + SAssignNew(FirstRightColumnVerticalBox, SVerticalBox) + ] + ] + ] + + SVerticalBox::Slot() + .AutoHeight() + [ + //Second Line + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + // Second Left + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(SecondLeftColumnVerticalBox, SVerticalBox) + ] + ] + + SHorizontalBox::Slot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + // Second Right + SNew(SBox) + [ + SAssignNew(SecondRightColumnVerticalBox, SVerticalBox) + ] + ] + ] + ]; + + // + // First line - left + // + FirstLeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineCookTriggersLabel", "Cook Triggers")) + ] + ]; + + // Parameter change check box + FText TooltipText = LOCTEXT("HoudiniEngineParameterChangeTooltip", "If enabled, modifying a parameter or input on this Houdini Asset will automatically trigger a cook of the HDA in Houdini."); + FirstLeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineParameterChangeCheckBoxLabel", "On Parameter/Input Change")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateParameterChangedLambda) + .IsChecked_Lambda(IsCheckedParameterChangedLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Transform change check box + TooltipText = LOCTEXT("HoudiniEngineTransformChangeTooltip", "If enabled, changing the Houdini Asset Actor's transform in Unreal will also update its HDA's node transform in Houdini, and trigger a recook of the HDA with the updated transform."); + FirstLeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineTransformChangeCheckBoxLabel", "On Transform Change")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedTransformChangeLambda) + .IsChecked_Lambda(IsCheckedTransformChangeLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Triggers Downstream cook checkbox + TooltipText = LOCTEXT("HoudiniEngineAssetInputCookTooltip", "When enabled, this asset will automatically re-cook after one its asset input has finished cooking."); + FirstLeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineAssetInputCheckBoxLabel", "On Asset Input Cook")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedAssetInputCookLambda) + .IsChecked_Lambda(IsCheckedAssetInputCookLambda) + .ToolTipText(TooltipText) + ] + ]; + + // + // First line - right + // + FirstRightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineOutputLabel", "Outputs")) + ]; + + // Do not generate output check box + TooltipText = LOCTEXT("HoudiniEnginOutputlessTooltip", "If enabled, this Houdini Asset will cook normally but will not generate any output in Unreal. This is especially usefull when chaining multiple assets together via Asset Inputs."); + FirstRightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEngineDoNotGenerateOutputsCheckBoxLabel", "Do Not Generate Outputs")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedDoNotGenerateOutputsLambda) + .IsChecked_Lambda(IsCheckedDoNotGenerateOutputsLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Use Output Nodes geos check box + TooltipText = LOCTEXT("HoudiniEnginUseOutputNodesTooltip", "If enabled, Output nodes found in this Houdini asset will be used alongside the Display node to create outputs."); + FirstRightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEnginUseOutputNodesCheckBoxLabel", "Use Output Nodes")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedUseOutputNodesLambda) + .IsChecked_Lambda(IsCheckedUseOutputNodesLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Output templated geos check box + TooltipText = LOCTEXT("HoudiniEnginOutputTemplatesTooltip", "If enabled, Geometry nodes in the asset that have the template flag will be outputed."); + FirstRightColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEnginOutputTemplatesCheckBoxLabel", "Use Templated Geos")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedOutputTemplatedGeosLambda) + .IsChecked_Lambda(IsCheckedOutputTemplatedGeosLambda) + .ToolTipText(TooltipText) + ] + ]; + + + // + // Second line + // + SecondLeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineMiscLabel", "Miscellaneous")) + ]; + + // Push Transform to Houdini check box + TooltipText = LOCTEXT("HoudiniEnginePushTransformTooltip", "If enabled, modifying this Houdini Asset Actor's transform will automatically update the HDA's node transform in Houdini."); + SecondLeftColumnVerticalBox->AddSlot() + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(4.0f) + [ + SNew(STextBlock) + .MinDesiredWidth(160.f) + .Text(LOCTEXT("HoudiniEnginePushTransformToHoudiniCheckBoxLabel", "Push Transform to Houdini")) + .ToolTipText(TooltipText) + ] + + SHorizontalBox::Slot() + [ + SNew(SCheckBox) + .OnCheckStateChanged_Lambda(OnCheckStateChangedPushTransformToHoudiniLambda) + .IsChecked_Lambda(IsCheckedPushTransformToHoudiniLambda) + .ToolTipText(TooltipText) + ] + ]; + + // Use whole widget + CheckBoxesRow.WholeRowWidget.Widget = WidgetBox; +} + +void +FHoudiniEngineDetails::CreateHelpAndDebugWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs) +{ + if (InHACs.Num() <= 0) + return; + + UHoudiniAssetComponent * MainHAC = InHACs[0]; + if (!MainHAC || MainHAC->IsPendingKill()) + return; + + // Header Row + FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG); + + if (!MainHAC->bHelpAndDebugMenuExpanded) + return; + + auto OnFetchCookLogButtonClickedLambda = [InHACs]() + { + return ShowCookLog(InHACs); + }; + + auto OnHelpButtonClickedLambda = [MainHAC]() + { + return ShowAssetHelp(MainHAC); + }; + + // Button Row + FDetailWidgetRow & ButtonRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + TSharedPtr CookLogButtonHorizontalBox = SNew(SHorizontalBox); + + // Fetch Cook Log button + ButtonRowHorizontalBox->AddSlot() + //.Padding(15.0f, 0.0f, 0.0f, 0.0f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(FText::FromString("Fetch and display all cook logs available for this Houdini Asset Actor.")) + //.Text(FText::FromString("Fetch Cook Log")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnFetchCookLogButtonClickedLambda) + .Content() + [ + SAssignNew(CookLogButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr CookLogIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUICookLogIconBrush(); + if (CookLogIconBrush.IsValid()) + { + TSharedPtr CookImage; + CookLogButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookImage, SImage) + ] + ]; + + CookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookLogIconBrush]() { + return CookLogIconBrush.Get(); + }))); + } + + CookLogButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Show Cook Logs")) + ]; + + // Asset Help Button + TSharedPtr AssetHelpButtonHorizontalBox; + ButtonRowHorizontalBox->AddSlot() + //.Padding(4.0, 0.0f, 0.0f, 0.0f) + .MaxWidth(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SBox) + .WidthOverride(HOUDINI_ENGINE_UI_BUTTON_WIDTH) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .ToolTipText(FText::FromString("Display this Houdini Asset Actor's HDA help.")) + //.Text(FText::FromString("Asset Help")) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda(OnHelpButtonClickedLambda) + .Content() + [ + SAssignNew(AssetHelpButtonHorizontalBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr AssetHelpIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIAssetHelpIconBrush(); + if (AssetHelpIconBrush.IsValid()) + { + TSharedPtr AssetHelpImage; + AssetHelpButtonHorizontalBox->AddSlot() + .MaxWidth(16.0f) + //.Padding(23.0f, 0.0f, 3.0f, 0.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(AssetHelpImage, SImage) + ] + ]; + + AssetHelpImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([AssetHelpIconBrush]() { + return AssetHelpIconBrush.Get(); + }))); + } + + AssetHelpButtonHorizontalBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Asset Help")) + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; +} + +FMenuBuilder +FHoudiniEngineDetails::Helper_CreateHoudiniAssetPicker() +{ + auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + return true; + }; + + auto OnActorSelected = [](AActor* Actor) + { + UE_LOG(LogTemp, Warning, TEXT("Actor Selected")); + + return; + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterHoudiniAssetLambda); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("HoudiniEngineDetailsAssetPicker", "Asset")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +const FSlateBrush * +FHoudiniEngineDetails::GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const +{ + if (HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + +/* +TSharedRef< SWidget > +FHoudiniEngineDetails::OnGetHoudiniAssetMenuContent(TArray InHACs) +{ + TArray< const UClass * > AllowedClasses; + AllowedClasses.Add(UHoudiniAsset::StaticClass()); + + TArray< UFactory * > NewAssetFactories; + + UHoudiniAsset * HoudiniAsset = nullptr; + if (InHACs.Num() > 0) + { + UHoudiniAssetComponent * HoudiniAssetComponent = InHACs[0]; + HoudiniAsset = HoudiniAssetComponent->HoudiniAsset; + } + + auto OnShouldFilterHoudiniAssetLambda = [](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + return true; + }; + + // Delegate for filtering Houdini assets. + FOnShouldFilterAsset OnShouldFilterHoudiniAsset = FOnShouldFilterAsset::CreateLambda(OnShouldFilterHoudiniAssetLambda); + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(HoudiniAsset), true, + AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset, + FOnAssetSelected::CreateLambda([](const FAssetData & AssetData) {}), + FSimpleDelegate::CreateLambda([]() { }) + ); +} +*/ + +FReply +FHoudiniEngineDetails::ShowCookLog(TArray InHACS) +{ + TSharedPtr< SWindow > ParentWindow; + FString CookLog = FHoudiniEngineUtils::GetCookLog(InHACS); + + // 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("MainFrame"); + ParentWindow = MainFrame.GetParentWindow(); + } + + if (ParentWindow.IsValid()) + { + TSharedPtr HoudiniAssetCookLog; + + TSharedRef Window = + SNew(SWindow) + .Title(LOCTEXT("WindowTitle", "Houdini Cook Log")) + .ClientSize(FVector2D(640, 480)); + + Window->SetContent( + SAssignNew(HoudiniAssetCookLog, SHoudiniAssetLogWidget) + .LogText(CookLog)); + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + } + + return FReply::Handled(); +} + +FReply +FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC) +{ + if (!InHAC) + return FReply::Handled(); + + FString AssetHelp = FHoudiniEngineUtils::GetAssetHelp(InHAC); + + 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 HoudiniAssetHelpLog; + + TSharedRef Window = + SNew(SWindow) + .Title(LOCTEXT("WindowTitle", "Houdini Asset Help")) + .ClientSize(FVector2D(640, 480)); + + Window->SetContent( + SAssignNew(HoudiniAssetHelpLog, SHoudiniAssetLogWidget) + .LogText(AssetHelp)); + + if (FSlateApplication::IsInitialized()) + FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); + } + return FReply::Handled(); +} + +void +FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection) +{ + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return; + + FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + HoudiniAssetComponent->bGenerateMenuExpanded = !HoudiniAssetComponent->bGenerateMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + HoudiniAssetComponent->bBakeMenuExpanded = !HoudiniAssetComponent->bBakeMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + HoudiniAssetComponent->bAssetOptionMenuExpanded = !HoudiniAssetComponent->bAssetOptionMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + HoudiniAssetComponent->bHelpAndDebugMenuExpanded = !HoudiniAssetComponent->bHelpAndDebugMenuExpanded; + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniAssetComponent, true); + + return FReply::Handled(); + }); + + TFunction GetText = [MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_GENERATE_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS_HEADER_TEXT); + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG_HEADER_TEXT); + break; + } + return FText::FromString(""); + }; + + TFunction GetExpanderBrush = [HoudiniAssetComponent, MenuSection](SButton* InExpanderArrow) + { + FName ResourceName; + bool bMenuExpanded = false; + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_GENERATE: + bMenuExpanded = HoudiniAssetComponent->bGenerateMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_BAKE: + bMenuExpanded = HoudiniAssetComponent->bBakeMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_ASSET_OPTIONS: + bMenuExpanded = HoudiniAssetComponent->bAssetOptionMenuExpanded; + break; + + case HOUDINI_ENGINE_UI_SECTION_HELP_AND_DEBUG: + bMenuExpanded = HoudiniAssetComponent->bHelpAndDebugMenuExpanded; + } + + if (bMenuExpanded) + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + }; + + return AddHeaderRow(HoudiniEngineCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); +} + +void +FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]() + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + InPDGAssetLink->Modify(); + InPDGAssetLink->bBakeMenuExpanded = !InPDGAssetLink->bBakeMenuExpanded; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeMenuExpanded), InPDGAssetLink); + break; + } + + //FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, true); + + return FReply::Handled(); + }); + + TFunction GetText = [MenuSection]() + { + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + return FText::FromString(HOUDINI_ENGINE_UI_SECTION_BAKE_HEADER_TEXT); + break; + } + return FText::FromString(""); + }; + + TFunction GetExpanderBrush = [InPDGAssetLink, MenuSection](SButton* InExpanderArrow) + { + FName ResourceName; + bool bMenuExpanded = false; + + switch (MenuSection) + { + case HOUDINI_ENGINE_UI_SECTION_BAKE: + bMenuExpanded = InPDGAssetLink->bBakeMenuExpanded; + break; + } + + if (bMenuExpanded) + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = InExpanderArrow->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + }; + + return AddHeaderRow(PDGCategoryBuilder, OnExpanderClick, GetText, GetExpanderBrush); +} + +void +FHoudiniEngineDetails::AddHeaderRow( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + FOnClicked& InOnExpanderClick, + TFunction& InGetText, + TFunction& InGetExpanderBrush) +{ + // Header Row + FDetailWidgetRow & HeaderRow = HoudiniEngineCategoryBuilder.AddCustomRow(FText::GetEmpty()); + TSharedPtr HeaderHorizontalBox; + HeaderRow.WholeRowWidget.Widget = SAssignNew(HeaderHorizontalBox, SHorizontalBox); + + TSharedPtr ExpanderImage; + TSharedPtr ExpanderArrow; + HeaderHorizontalBox->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth() + [ + SAssignNew(ExpanderArrow, SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + .OnClicked(InOnExpanderClick) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + HeaderHorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SNew(STextBlock) + .Text_Lambda([InGetText](){return InGetText(); }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + ExpanderImage->SetImage( + TAttribute::Create( + [ExpanderArrow, InGetExpanderBrush]() + { + return InGetExpanderBrush(ExpanderArrow.Get()); + })); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h index 29da133e7..bd6b0b568 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.h @@ -1,122 +1,122 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SCompoundWidget.h" -#include "Framework/SlateDelegates.h" -#include "Styling/SlateBrush.h" -#include "Widgets/Layout/SBorder.h" -#include "Framework/SlateDelegates.h" -#include "Widgets/Input/SButton.h" - -class IDetailCategoryBuilder; -class UHoudiniAssetComponent; -class UHoudiniPDGAssetLink; -class FMenuBuilder; -class SBorder; -class SButton; - -class SHoudiniAssetLogWidget : public SCompoundWidget -{ -public: - - SLATE_BEGIN_ARGS(SHoudiniAssetLogWidget) - : _LogText(TEXT("")) - {} - - SLATE_ARGUMENT(FString, LogText) - SLATE_END_ARGS() - - /** Widget construct. **/ - void Construct(const FArguments & InArgs); -}; - -class FHoudiniEngineDetails : public TSharedFromThis -{ -public: - static void CreateWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateHoudiniEngineIconWidget( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateGenerateWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateBakeWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreatePDGBakeWidgets( - IDetailCategoryBuilder& InPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink); - - static void CreateAssetOptionsWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static void CreateHelpAndDebugWidgets( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - TArray& InHACs); - - static FReply ShowCookLog(TArray InHACS); - - static FReply ShowAssetHelp(UHoudiniAssetComponent * InHAC); - - static FMenuBuilder Helper_CreateHoudiniAssetPicker(); - - const FSlateBrush * GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const; - - /** Construct drop down menu content for Houdini asset. **/ - //static TSharedRef< SWidget > OnGetHoudiniAssetMenuContent(TArray InHACs); - - static void AddHeaderRowForHoudiniAssetComponent( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - UHoudiniAssetComponent* HoudiniAssetComponent, - int32 MenuSection); - - static void AddHeaderRowForHoudiniPDGAssetLink( - IDetailCategoryBuilder& PDGCategoryBuilder, - UHoudiniPDGAssetLink* InPDGAssetLink, - int32 MenuSection); - - static void AddHeaderRow( - IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, - FOnClicked& InOnExpanderClick, - TFunction& InGetText, - TFunction& InGetExpanderBrush); - - // Helper for binding/unbinding the post cook bake delegate - static void OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC); -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Framework/SlateDelegates.h" +#include "Styling/SlateBrush.h" +#include "Widgets/Layout/SBorder.h" +#include "Framework/SlateDelegates.h" +#include "Widgets/Input/SButton.h" + +class IDetailCategoryBuilder; +class UHoudiniAssetComponent; +class UHoudiniPDGAssetLink; +class FMenuBuilder; +class SBorder; +class SButton; + +class SHoudiniAssetLogWidget : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SHoudiniAssetLogWidget) + : _LogText(TEXT("")) + {} + + SLATE_ARGUMENT(FString, LogText) + SLATE_END_ARGS() + + /** Widget construct. **/ + void Construct(const FArguments & InArgs); +}; + +class FHoudiniEngineDetails : public TSharedFromThis +{ +public: + static void CreateWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateHoudiniEngineIconWidget( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateGenerateWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateBakeWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreatePDGBakeWidgets( + IDetailCategoryBuilder& InPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink); + + static void CreateAssetOptionsWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static void CreateHelpAndDebugWidgets( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + TArray& InHACs); + + static FReply ShowCookLog(TArray InHACS); + + static FReply ShowAssetHelp(UHoudiniAssetComponent * InHAC); + + static FMenuBuilder Helper_CreateHoudiniAssetPicker(); + + const FSlateBrush * GetHoudiniAssetThumbnailBorder(TSharedPtr< SBorder > HoudiniAssetThumbnailBorder) const; + + /** Construct drop down menu content for Houdini asset. **/ + //static TSharedRef< SWidget > OnGetHoudiniAssetMenuContent(TArray InHACs); + + static void AddHeaderRowForHoudiniAssetComponent( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + UHoudiniAssetComponent* HoudiniAssetComponent, + int32 MenuSection); + + static void AddHeaderRowForHoudiniPDGAssetLink( + IDetailCategoryBuilder& PDGCategoryBuilder, + UHoudiniPDGAssetLink* InPDGAssetLink, + int32 MenuSection); + + static void AddHeaderRow( + IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, + FOnClicked& InOnExpanderClick, + TFunction& InGetText, + TFunction& InGetExpanderBrush); + + // Helper for binding/unbinding the post cook bake delegate + static void OnBakeAfterCookChangedHelper(bool bInState, UHoudiniAssetComponent* InHAC); +}; + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp index ddcb6c849..fdca2cb1a 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp @@ -1,1543 +1,1543 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineEditor.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngine.h" -#include "HoudiniAsset.h" -#include "HoudiniAssetBroker.h" -#include "HoudiniAssetActorFactory.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetComponentDetails.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniParameter.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniRuntimeSettingsDetails.h" -#include "HoudiniSplineComponentVisualizer.h" -#include "HoudiniHandleComponentVisualizer.h" -#include "AssetTypeActions_HoudiniAsset.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPackageParams.h" - -#include "Modules/ModuleManager.h" -#include "Interfaces/IPluginManager.h" -#include "HAL/PlatformFilemanager.h" -#include "Misc/MessageDialog.h" -#include "Misc/Paths.h" -#include "AssetRegistryModule.h" -#include "PropertyEditorModule.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "LevelEditor.h" -#include "Templates/SharedPointer.h" -#include "Framework/Application/SlateApplication.h" -#include "HAL/ConsoleManager.h" -#include "Editor/UnrealEdEngine.h" -#include "Editor.h" -#include "UnrealEdGlobals.h" -#include "Engine/Selection.h" -#include "Widgets/Input/SCheckBox.h" -#include "Logging/LogMacros.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FHoudiniEngineEditor, HoudiniEngineEditor); -DEFINE_LOG_CATEGORY(LogHoudiniEngineEditor); - -FHoudiniEngineEditor * -FHoudiniEngineEditor::HoudiniEngineEditorInstance = nullptr; - -FHoudiniEngineEditor & -FHoudiniEngineEditor::Get() -{ - return *HoudiniEngineEditorInstance; -} - -bool -FHoudiniEngineEditor::IsInitialized() -{ - return FHoudiniEngineEditor::HoudiniEngineEditorInstance != nullptr; -} - -FHoudiniEngineEditor::FHoudiniEngineEditor() -{ -} - -void FHoudiniEngineEditor::StartupModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine Editor module.")); - - // Create style set. - FHoudiniEngineStyle::Initialize(); - - // Initilizes various resources used by our editor UI widgets - InitializeWidgetResource(); - - // Register asset type actions. - RegisterAssetTypeActions(); - - // Register asset brokers. - RegisterAssetBrokers(); - - // Register component visualizers. - RegisterComponentVisualizers(); - - // Register detail presenters. - RegisterDetails(); - - // Register actor factories. - RegisterActorFactories(); - - // Extends the file menu. - ExtendMenu(); - - // Extend the World Outliner Menu - AddLevelViewportMenuExtender(); - - // Adds the custom console commands - RegisterConsoleCommands(); - - // Register global undo / redo callbacks. - //RegisterForUndo(); - - //RegisterPlacementModeExtensions(); - - // Register for any FEditorDelegates that we are interested in, such as - // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds - RegisterEditorDelegates(); - - // Store the instance. - FHoudiniEngineEditor::HoudiniEngineEditorInstance = this; - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module startup complete.")); -} - -void FHoudiniEngineEditor::ShutdownModule() -{ - HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine Editor module.")); - - // Deregister editor delegates - UnregisterEditorDelegates(); - - // Deregister console commands - UnregisterConsoleCommands(); - - // Remove the level viewport Menu extender - RemoveLevelViewportMenuExtender(); - - // Unregister asset type actions. - UnregisterAssetTypeActions(); - - // Unregister asset brokers. - //UnregisterAssetBrokers(); - - // Unregister detail presenters. - UnregisterDetails(); - - // Unregister our component visualizers. - //UnregisterComponentVisualizers(); - - // Unregister global undo / redo callbacks. - //UnregisterForUndo(); - - //UnregisterPlacementModeExtensions(); - - // Unregister the styleset - FHoudiniEngineStyle::Shutdown(); - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module shutdown complete.")); -} - -FString -FHoudiniEngineEditor::GetHoudiniEnginePluginDir() -{ - FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(EnginePluginDir)) - return EnginePluginDir; - - FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); - if (FPaths::DirectoryExists(ProjectPluginDir)) - return ProjectPluginDir; - - TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); - FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; - if (FPaths::DirectoryExists(PluginBaseDir)) - return PluginBaseDir; - - HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); - - return EnginePluginDir; -} - -void -FHoudiniEngineEditor::RegisterDetails() -{ - FPropertyEditorModule & PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Register details presenter for our component type and runtime settings. - PropertyModule.RegisterCustomClassLayout( - TEXT("HoudiniAssetComponent"), - FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniAssetComponentDetails::MakeInstance)); - - PropertyModule.RegisterCustomClassLayout( - TEXT("HoudiniRuntimeSettings"), - FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniRuntimeSettingsDetails::MakeInstance)); -} - -void -FHoudiniEngineEditor::UnregisterDetails() -{ - if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) - { - FPropertyEditorModule & PropertyModule = - FModuleManager::LoadModuleChecked("PropertyEditor"); - - PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniAssetComponent")); - PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniRuntimeSettings")); - } -} - -void -FHoudiniEngineEditor::RegisterComponentVisualizers() -{ - if (GUnrealEd) - { - // Register Houdini spline visualizer - SplineComponentVisualizer = MakeShareable(new FHoudiniSplineComponentVisualizer); - if (SplineComponentVisualizer.IsValid()) - { - GUnrealEd->RegisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName(), SplineComponentVisualizer); - SplineComponentVisualizer->OnRegister(); - } - - // Register Houdini handle visualizer - HandleComponentVisualizer = MakeShareable(new FHoudiniHandleComponentVisualizer); - if (HandleComponentVisualizer.IsValid()) - { - GUnrealEd->RegisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName(), HandleComponentVisualizer); - HandleComponentVisualizer->OnRegister(); - } - } -} - -void -FHoudiniEngineEditor::UnregisterComponentVisualizers() -{ - if (GUnrealEd) - { - // Unregister Houdini spline visualizer - if(SplineComponentVisualizer.IsValid()) - GUnrealEd->UnregisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); - - // Unregister Houdini handle visualizer - if (HandleComponentVisualizer.IsValid()) - GUnrealEd->UnregisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - } -} - -void -FHoudiniEngineEditor::RegisterAssetTypeAction(IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action) -{ - AssetTools.RegisterAssetTypeActions(Action); - AssetTypeActions.Add(Action); -} - -void -FHoudiniEngineEditor::RegisterAssetTypeActions() -{ - // Create and register asset type actions for Houdini asset. - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools").Get(); - RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_HoudiniAsset())); -} - -void -FHoudiniEngineEditor::UnregisterAssetTypeActions() -{ - // Unregister asset type actions we have previously registered. - if (FModuleManager::Get().IsModuleLoaded("AssetTools")) - { - IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >("AssetTools").Get(); - - for (int32 Index = 0; Index < AssetTypeActions.Num(); ++Index) - AssetTools.UnregisterAssetTypeActions(AssetTypeActions[Index].ToSharedRef()); - - AssetTypeActions.Empty(); - } -} - -void -FHoudiniEngineEditor::RegisterAssetBrokers() -{ - // Create and register broker for Houdini asset. - HoudiniAssetBroker = MakeShareable(new FHoudiniAssetBroker()); - FComponentAssetBrokerage::RegisterBroker( HoudiniAssetBroker, UHoudiniAssetComponent::StaticClass(), true, true ); -} - -void -FHoudiniEngineEditor::UnregisterAssetBrokers() -{ - if (UObjectInitialized()) - { - // Unregister broker. - FComponentAssetBrokerage::UnregisterBroker( HoudiniAssetBroker ); - } -} - -void -FHoudiniEngineEditor::RegisterActorFactories() -{ - if (GEditor) - { - UHoudiniAssetActorFactory * HoudiniAssetActorFactory = - NewObject< UHoudiniAssetActorFactory >(GetTransientPackage(), UHoudiniAssetActorFactory::StaticClass()); - - GEditor->ActorFactories.Add(HoudiniAssetActorFactory); - } -} - -void -FHoudiniEngineEditor::BindMenuCommands() -{ - HEngineCommands = MakeShareable(new FUICommandList); - - FHoudiniEngineCommands::Register(); - const FHoudiniEngineCommands& Commands = FHoudiniEngineCommands::Get(); - - // Session - - HEngineCommands->MapAction( - Commands._CreateSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CreateSession(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._ConnectSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ConnectSession(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._StopSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopSession(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RestartSession, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RestartSession(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OpenSessionSync, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OpenSessionSync(); }), - FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); - - HEngineCommands->MapAction( - Commands._CloseSessionSync, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CloseSessionSync(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); - - HEngineCommands->MapAction( - Commands._ViewportSyncNone, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(0); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 0); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncHoudini, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(1); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 1); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncUnreal, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(2); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 2); }) - ); - - HEngineCommands->MapAction( - Commands._ViewportSyncBoth, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(3); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 3); }) - ); - - // PDG commandlet - HEngineCommands->MapAction( - Commands._IsPDGCommandletEnabled, - FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::SetPDGCommandletEnabled(!FHoudiniEngineCommands::IsPDGCommandletEnabled()); }), - FCanExecuteAction::CreateLambda([]() { return true; }), - FIsActionChecked::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletEnabled(); }) - ); - - HEngineCommands->MapAction( - Commands._StartPDGCommandlet, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StartPDGCommandlet(); }), - FCanExecuteAction::CreateLambda([]() - { - return FHoudiniEngineCommands::IsPDGCommandletEnabled() && !FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); - }) - ); - - HEngineCommands->MapAction( - Commands._StopPDGCommandlet, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopPDGCommandlet(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); })); - - // Plugin - - HEngineCommands->MapAction( - Commands._InstallInfo, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowInstallInfo(); }), - FCanExecuteAction::CreateLambda([]() { return false; })); - - HEngineCommands->MapAction( - Commands._PluginSettings, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowPluginSettings(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Files - - HEngineCommands->MapAction( - Commands._OpenInHoudini, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::OpenInHoudini(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._SaveHIPFile, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::SaveHIPFile(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._CleanUpTempFolder, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CleanUpTempFolder(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Help and support - - HEngineCommands->MapAction( - Commands._ReportBug, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::ReportBug(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OnlineDoc, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineDocumentation(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._OnlineForum, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineForum(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - // Actions - - HEngineCommands->MapAction( - Commands._CookAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookAllAssets(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._CookSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RebuildAll, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildAllAssets(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._RebuildSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), - FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); - - HEngineCommands->MapAction( - Commands._BakeAll, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::BakeAllAssets(); }), - FCanExecuteAction::CreateLambda([](){ return true; })); - - HEngineCommands->MapAction( - Commands._BakeSelected, - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::BakeSelection(); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._RefineAll, - FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._RefineSelected, - FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), - FCanExecuteAction::CreateLambda([]() { return true; })); - - HEngineCommands->MapAction( - Commands._PauseAssetCooking, - FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::PauseAssetCooking(); }), - FCanExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::IsSessionValid(); }), - FIsActionChecked::CreateLambda([](){ return FHoudiniEngineCommands::IsAssetCookingPaused(); })); - - // Non menu command (used for shortcuts only) - - // Append the command to the editor module - FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >("LevelEditor"); - LevelEditorModule.GetGlobalLevelEditorActions()->Append(HEngineCommands.ToSharedRef()); -} - -void -FHoudiniEngineEditor::ExtendMenu() -{ - if (IsRunningCommandlet()) - return; - - // We need to add/bind the UI Commands to their functions first - BindMenuCommands(); - - MainMenuExtender = MakeShareable(new FExtender); - - // Extend File menu, we will add Houdini section. - MainMenuExtender->AddMenuExtension( - "FileLoadAndSave", - EExtensionHook::After, - HEngineCommands, - FMenuExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniFileMenuExtension)); - - MainMenuExtender->AddMenuBarExtension( - "Edit", - EExtensionHook::After, - HEngineCommands, - FMenuBarExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniEditorMenu)); - - // Add our menu extender - FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); - LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); -} - -void -FHoudiniEngineEditor::AddHoudiniFileMenuExtension(FMenuBuilder & MenuBuilder) -{ - MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); - - // Icons used by the commands are defined in the HoudiniEngineStyle - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - - MenuBuilder.EndSection(); -} - -void -FHoudiniEngineEditor::AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder) -{ - // View - MenuBarBuilder.AddPullDownMenu( - LOCTEXT("HoudiniLabel", "Houdini Engine"), - LOCTEXT("HoudiniMenu_ToolTip", "Open the Houdini Engine menu"), - FNewMenuDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniMainMenuExtension), - "View"); -} - -void -FHoudiniEngineEditor::AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder) -{ - /* - MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); - // Icons used by the commands are defined in the HoudiniEngineStyle - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - MenuBuilder.EndSection(); - */ - - MenuBuilder.BeginSection("Session", LOCTEXT("SessionLabel", "Session")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CreateSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ConnectSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenSessionSync); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CloseSessionSync); - - // Viewport sync menu - struct FLocalMenuBuilder - { - static void FillViewportSyncMenu(FMenuBuilder& InSubMenuBuilder) - { - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncNone); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncHoudini); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncUnreal); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncBoth); - } - }; - - MenuBuilder.AddSubMenu( - LOCTEXT("SyncViewport", "Sync Viewport"), - LOCTEXT("SyncViewport_ToolTip", "Sync Viewport"), - FNewMenuDelegate::CreateStatic(&FLocalMenuBuilder::FillViewportSyncMenu), - false, - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._SyncViewport")); - - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("PDG", LOCTEXT("PDGLabel", "PDG")); - struct FLocalPDGMenuBuilder - { - static void FillPDGMenu(FMenuBuilder& InSubMenuBuilder) - { - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._IsPDGCommandletEnabled); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StartPDGCommandlet); - InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopPDGCommandlet); - } - }; - MenuBuilder.AddSubMenu( - LOCTEXT("PDGSubMenu", "Work Item Import Settings"), - LOCTEXT("PDGSubmenu_ToolTip", "PDG Work Item Import Settings"), - FNewMenuDelegate::CreateStatic(&FLocalPDGMenuBuilder::FillPDGMenu), - false, - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.PDGLink")); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Plugin", LOCTEXT("PluginLabel", "Plugin")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._InstallInfo); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PluginSettings); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("File", LOCTEXT("FileLabel", "File")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Help", LOCTEXT("HelpLabel", "Help And Support")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineDoc); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineForum); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection("Actions", LOCTEXT("ActionsLabel", "Actions")); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineSelected); - MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); - - - MenuBuilder.EndSection(); -} - -void -FHoudiniEngineEditor::RegisterForUndo() -{ - /* - if (GUnrealEd) - GUnrealEd->RegisterForUndo(this); - */ -} - -void -FHoudiniEngineEditor::UnregisterForUndo() -{ - /* - if (GUnrealEd) - GUnrealEd->UnregisterForUndo(this); - */ -} - -void -FHoudiniEngineEditor::RegisterPlacementModeExtensions() -{ - // Load custom houdini tools - /* - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - check(HoudiniRuntimeSettings); - - if (HoudiniRuntimeSettings->bHidePlacementModeHoudiniTools) - return; - - FPlacementCategoryInfo Info( - LOCTEXT("HoudiniCategoryName", "Houdini Engine"), - "HoudiniEngine", - TEXT("PMHoudiniEngine"), - 25 - ); - Info.CustomGenerator = []() -> TSharedRef { return SNew(SHoudiniToolPalette); }; - - IPlacementModeModule::Get().RegisterPlacementCategory(Info); - */ -} - -void -FHoudiniEngineEditor::UnregisterPlacementModeExtensions() -{ - /* - if (IPlacementModeModule::IsAvailable()) - { - IPlacementModeModule::Get().UnregisterPlacementCategory("HoudiniEngine"); - } - - HoudiniTools.Empty(); - */ -} - -void -FHoudiniEngineEditor::InitializeWidgetResource() -{ - // Choice labels for all the input types - //TArray> InputTypeChoiceLabels; - InputTypeChoiceLabels.Reset(); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Asset)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Landscape)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::World)))); - InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Skeletal)))); - - BlueprintInputTypeChoiceLabels.Reset(); - BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); - BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); - - // Choice labels for all Houdini curve types - HoudiniCurveTypeChoiceLabels.Reset(); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Polygon)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Nurbs)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Bezier)))); - HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Points)))); - - // Choice labels for all Houdini curve methods - HoudiniCurveMethodChoiceLabels.Reset(); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::CVs)))); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Breakpoints)))); - HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Freehand)))); - - // Choice labels for all Houdini ramp parameter interpolation methods - HoudiniParameterRampInterpolationLabels.Reset(); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CONSTANT)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::LINEAR)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CATMULL_ROM)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::MONOTONE_CUBIC)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BEZIER)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BSPLINE)))); - HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::HERMITE)))); - - // Choice labels for all Houdini curve output export types - HoudiniCurveOutputExportTypeLabels.Reset(); - HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Unreal Spline")))); - HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Houdini Spline")))); - - // Choice labels for all Unreal curve output curve types - //(for temporary, we need to figure out a way to access the output curve's info later) - UnrealCurveOutputCurveTypeLabels.Reset(); - UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Linear")))); - UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Curve")))); - - // Option labels for all landscape outputs bake options - HoudiniLandscapeOutputBakeOptionLabels.Reset(); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Current Level")))); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Image")))); - HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To New World")))); - - // Option labels for Houdini Engine PDG bake options - HoudiniEnginePDGBakeTypeOptionLabels.Reset(); - HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); - HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); - - // Option labels for Houdini Engine bake options - HoudiniEngineBakeTypeOptionLabels.Reset(); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToFoliage)))); - HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToWorldOutliner)))); - - // Option labels for Houdini Engine PDG bake options - HoudiniEnginePDGBakeSelectionOptionLabels.Reset(); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::All)))); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNetwork)))); - HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNode)))); - - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Reset(); - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::ReplaceExistingAssets)))); - HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::CreateNewAssets)))); - - - static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); - - // Houdini Logo Brush - FString Icon128FilePath = IconsDir + TEXT("icon_houdini_logo_128"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) - { - const FName BrushName(*Icon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Logo Brush - FString HEIcon128FilePath = IconsDir + TEXT("icon_hengine_logo_128"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) - { - const FName BrushName(*HEIcon128FilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Houdini Engine Banner - FString HoudiniEngineUIIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_banner_d.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Rebuild Icon Brush - FString HoudiniEngineUIRebuildIconFilePath = IconsDir + TEXT("rebuild_all16x16.png"); - //FString HoudiniEngineUIRebuildIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_reload_icon.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRebuildIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIRebuildIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIRebuildIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Recook Icon Brush - //FString HoudiniEngineUIRecookIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_recook_icon.png"); - FString HoudiniEngineUIRecookIconFilePath = IconsDir + TEXT("cook_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRecookIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIRecookIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIRecookIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Reset Parameters Icon Brush - //FString HoudiniEngineUIResetParametersIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_resetparameters_icon.png"); - FString HoudiniEngineUIResetParametersIconFilePath = IconsDir + TEXT("reset_parameters16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIResetParametersIconFilePath)) - { - const FName BrushName(*HoudiniEngineUIResetParametersIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIResetParametersIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // Bake - FString BakeIconFilePath = IconsDir + TEXT("bake_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*BakeIconFilePath)) - { - const FName BrushName(*BakeIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIBakeIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // CookLog - FString CookLogIconFilePath = IconsDir + TEXT("cook_log16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*CookLogIconFilePath)) - { - const FName BrushName(*CookLogIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUICookLogIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // AssetHelp - FString AssetHelpIconFilePath = IconsDir + TEXT("asset_help16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*AssetHelpIconFilePath)) - { - const FName BrushName(*AssetHelpIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIAssetHelpIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - - // PDG Asset Link - // PDG - FString PDGIconFilePath = IconsDir + TEXT("pdg_link16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGIconFilePath)) - { - const FName BrushName(*PDGIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Cancel - // PDGCancel - FString PDGCancelIconFilePath = IconsDir + TEXT("pdg_cancel16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGCancelIconFilePath)) - { - const FName BrushName(*PDGCancelIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGCancelIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Dirty All - // PDGDirtyAll - FString PDGDirtyAllIconFilePath = IconsDir + TEXT("pdg_dirty_all16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyAllIconFilePath)) - { - const FName BrushName(*PDGDirtyAllIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGDirtyAllIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Dirty Node - // PDGDirtyNode - FString PDGDirtyNodeIconFilePath = IconsDir + TEXT("pdg_dirty_node16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyNodeIconFilePath)) - { - const FName BrushName(*PDGDirtyNodeIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGDirtyNodeIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Pause - // PDGReset - FString PDGPauseIconFilePath = IconsDir + TEXT("pdg_pause16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGPauseIconFilePath)) - { - const FName BrushName(*PDGPauseIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGPauseIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Reset - // PDGReset - FString PDGResetIconFilePath = IconsDir + TEXT("pdg_reset16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGResetIconFilePath)) - { - const FName BrushName(*PDGResetIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGResetIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } - - // PDG Refresh - // PDGRefresh - FString PDGRefreshIconFilePath = IconsDir + TEXT("pdg_refresh16x16.png"); - if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGRefreshIconFilePath)) - { - const FName BrushName(*PDGRefreshIconFilePath); - const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); - if (Size.X > 0 && Size.Y > 0) - { - static const int32 ProgressIconSize = 32; - HoudiniEngineUIPDGRefreshIconBrush = MakeShareable(new FSlateDynamicImageBrush( - BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); - } - } -} - -void -FHoudiniEngineEditor::AddLevelViewportMenuExtender() -{ - FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked("LevelEditor"); - auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); - - MenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender)); - LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle(); -} - -void -FHoudiniEngineEditor::RemoveLevelViewportMenuExtender() -{ - if (LevelViewportExtenderHandle.IsValid()) - { - FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr("LevelEditor"); - if (LevelEditorModule) - { - typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType; - LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll( - [=](const DelegateType& In) { return In.GetHandle() == LevelViewportExtenderHandle; }); - } - } -} - -TSharedRef -FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef CommandList, const TArray InActors) -{ - TSharedRef Extender = MakeShareable(new FExtender); - - // Build an array of the HoudiniAssets corresponding to the selected actors - TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets; - TArray< TWeakObjectPtr< AHoudiniAssetActor > > HoudiniAssetActors; - for (auto CurrentActor : InActors) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(CurrentActor); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - continue; - - HoudiniAssetActors.Add(HoudiniAssetActor); - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - continue; - - HoudiniAssets.AddUnique(HoudiniAssetComponent->GetHoudiniAsset()); - } - - if (HoudiniAssets.Num() > 0) - { - // Add the Asset menu extension - if (AssetTypeActions.Num() > 0) - { - // Add the menu extensions via our HoudiniAssetTypeActions - FAssetTypeActions_HoudiniAsset * HATA = static_cast(AssetTypeActions[0].Get()); - if (HATA) - Extender = HATA->AddLevelEditorMenuExtenders(HoudiniAssets); - } - } - - if (HoudiniAssetActors.Num() > 0) - { - // Add some actor menu extensions - FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); - TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); - Extender->AddMenuExtension( - "ActorControl", - EExtensionHook::After, - LevelEditorCommandBindings, - FMenuExtensionDelegate::CreateLambda([this, HoudiniAssetActors](FMenuBuilder& MenuBuilder) - { - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recentre", "Recentre selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecentreTooltip", "Recentres the selected Houdini Asset Actors pivots to their input/cooked static mesh average centre."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecentreSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recook", "Recook selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecookTooltip", "Forces a recook on the selected Houdini Asset Actors."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._CookSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Rebuild", "Rebuild selected"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RebuildTooltip", "Rebuilds selected Houdini Asset Actors in the current level."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RebuildSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - - MenuBuilder.AddMenuEntry( - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshes", "Refine Houdini Proxy Meshes"), - NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshesTooltip", "Build and replace Houdini Proxy Meshes with Static Meshes."), - FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RefineSelected"), - FUIAction( - FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), - FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) - ) - ); - }) - ); - } - - return Extender; -} - -void -FHoudiniEngineEditor::RegisterConsoleCommands() -{ - // Register corresponding console commands - static FAutoConsoleCommand CCmdOpen = FAutoConsoleCommand( - TEXT("Houdini.Open"), - TEXT("Open the scene in Houdini."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::OpenInHoudini)); - - static FAutoConsoleCommand CCmdSave = FAutoConsoleCommand( - TEXT("Houdini.Save"), - TEXT("Save the current Houdini scene to a hip file."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::SaveHIPFile)); - - static FAutoConsoleCommand CCmdBake = FAutoConsoleCommand( - TEXT("Houdini.BakeAll"), - TEXT("Bakes and replaces with blueprints all Houdini Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::BakeAllAssets)); - - static FAutoConsoleCommand CCmdClean = FAutoConsoleCommand( - TEXT("Houdini.Clean"), - TEXT("Cleans up unused/unreferenced Houdini Engine temporary files."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::CleanUpTempFolder)); - - static FAutoConsoleCommand CCmdPause = FAutoConsoleCommand( - TEXT("Houdini.Pause"), - TEXT("Pauses Houdini Engine Asset cooking."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::PauseAssetCooking)); - - // Additional console only commands - static FAutoConsoleCommand CCmdCookAll = FAutoConsoleCommand( - TEXT("Houdini.CookAll"), - TEXT("Re-cooks all Houdini Engine Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RecookAllAssets)); - - static FAutoConsoleCommand CCmdRebuildAll = FAutoConsoleCommand( - TEXT("Houdini.RebuildAll"), - TEXT("Rebuilds all Houdini Engine Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RebuildAllAssets)); - - static FAutoConsoleCommand CCmdCookSelec = FAutoConsoleCommand( - TEXT("Houdini.Cook"), - TEXT("Re-cooks selected Houdini Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RecookSelection)); - - static FAutoConsoleCommand CCmdRebuildSelec = FAutoConsoleCommand( - TEXT("Houdini.Rebuild"), - TEXT("Rebuilds selected Houdini Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RebuildSelection)); - - static FAutoConsoleCommand CCmdBakeSelec = FAutoConsoleCommand( - TEXT("Houdini.Bake"), - TEXT("Bakes and replaces with blueprints selected Houdini Asset Actors in the current level."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::BakeSelection)); - - static FAutoConsoleCommand CCmdRestartSession = FAutoConsoleCommand( - TEXT("Houdini.RestartSession"), - TEXT("Restart the current Houdini Session."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RestartSession)); - - /* - IConsoleManager &ConsoleManager = IConsoleManager::Get(); - const TCHAR *CommandName = TEXT("HoudiniEngine.RefineHoudiniProxyMeshesToStaticMeshes"); - IConsoleCommand *Command = ConsoleManager.RegisterConsoleCommand( - CommandName, - TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), - FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); - if (Command) - { - ConsoleCommands.Add(Command); - } - else - { - HOUDINI_LOG_ERROR(TEXT("Failed to register the '%s' console command."), CommandName); - } - */ - - static FAutoConsoleCommand CCmdRefine = FAutoConsoleCommand( - TEXT("Houdini.RefineAll"), - TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), - FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); - - static FAutoConsoleCommand CCmdOpenSessionSync = FAutoConsoleCommand( - TEXT("Houdini.OpenSessionSync"), - TEXT("Stops the current session, opens Houdini and automnatically start and connect a Session Sync."), - FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::OpenSessionSync)); -} - -void -FHoudiniEngineEditor::UnregisterConsoleCommands() -{ - IConsoleManager &ConsoleManager = IConsoleManager::Get(); - for (IConsoleCommand *Command : ConsoleCommands) - { - if (Command) - { - ConsoleManager.UnregisterConsoleObject(Command); - } - } - ConsoleCommands.Empty(); -} - -void -FHoudiniEngineEditor::RegisterEditorDelegates() -{ - PreSaveWorldEditorDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([](uint32 SaveFlags, UWorld* World) - { - // Skip if this is a game world or an autosave, only refine meshes when the user manually saves - if (!World->IsGameWorld() && (SaveFlags & ESaveFlags::SAVE_FromAutosave) == 0) - { - const bool bSelectedOnly = false; - const bool bSilent = false; - const bool bRefineAll = false; - const bool bOnPreSaveWorld = true; - UWorld * const OnPreSaveWorld = World; - const bool bOnPreBeginPIE = false; - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); - } - - if (!World->IsGameWorld()) - { - UWorld * const OnPreSaveWorld = World; - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - - // Save all dirty temporary cook package OnPostSaveWorld - OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([OnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) - { - if (OnPreSaveWorld && OnPreSaveWorld != InWorld) - return; - - FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(InWorld); - - FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); - if (OnPostSaveWorldHandle.IsValid()) - { - if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) - OnPostSaveWorldHandle.Reset(); - } - }); - } - }); - - PreBeginPIEEditorDelegateHandle = FEditorDelegates::PreBeginPIE.AddLambda([](const bool bIsSimulating) - { - const bool bSelectedOnly = false; - const bool bSilent = false; - const bool bRefineAll = false; - const bool bOnPreSaveWorld = false; - UWorld * const OnPreSaveWorld = nullptr; - const bool bOnPreBeginPIE = true; - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); - }); - - OnDeleteActorsBegin = FEditorDelegates::OnDeleteActorsBegin.AddLambda([this](){ this->HandleOnDeleteActorsBegin(); }); - OnDeleteActorsEnd = FEditorDelegates::OnDeleteActorsEnd.AddLambda([this](){ this-> HandleOnDeleteActorsEnd(); }); -} - -void -FHoudiniEngineEditor::UnregisterEditorDelegates() -{ - if (PreSaveWorldEditorDelegateHandle.IsValid()) - FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldEditorDelegateHandle); - - if (PreBeginPIEEditorDelegateHandle.IsValid()) - FEditorDelegates::PreBeginPIE.Remove(PreBeginPIEEditorDelegateHandle); - - if (OnDeleteActorsBegin.IsValid()) - FEditorDelegates::OnDeleteActorsBegin.Remove(OnDeleteActorsBegin); - - if (OnDeleteActorsEnd.IsValid()) - FEditorDelegates::OnDeleteActorsEnd.Remove(OnDeleteActorsEnd); -} - -FString -FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption) -{ - FString Str; - switch (BakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - Str = "Actor"; - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - Str = "Blueprint"; - break; - - case EHoudiniEngineBakeOption::ToFoliage: - Str = "Foliage"; - break; - - case EHoudiniEngineBakeOption::ToWorldOutliner: - Str = "World Outliner"; - break; - } - - return Str; -} - -FString -FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption) -{ - FString Str; - switch (BakeOption) - { - case EPDGBakeSelectionOption::All: - Str = "All Outputs"; - break; - - case EPDGBakeSelectionOption::SelectedNetwork: - Str = "Selected Network (All Outputs)"; - break; - - case EPDGBakeSelectionOption::SelectedNode: - Str = "Selected Node (All Outputs)"; - break; - } - - return Str; -} - -FString -FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption) -{ - FString Str; - switch (InOption) - { - case EPDGBakePackageReplaceModeOption::CreateNewAssets: - Str = "Create New Assets"; - break; - case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: - Str = "Replace Existing Assets"; - break; - } - - return Str; -} - -const EHoudiniEngineBakeOption -FHoudiniEngineEditor::StringToHoudiniEngineBakeOption(const FString & InString) -{ - if (InString == "Actor") - return EHoudiniEngineBakeOption::ToActor; - - if (InString == "Blueprint") - return EHoudiniEngineBakeOption::ToBlueprint; - - if (InString == "Foliage") - return EHoudiniEngineBakeOption::ToFoliage; - - if (InString == "World Outliner") - return EHoudiniEngineBakeOption::ToWorldOutliner; - - return EHoudiniEngineBakeOption::ToActor; -} - -const EPDGBakeSelectionOption -FHoudiniEngineEditor::StringToPDGBakeSelectionOption(const FString& InString) -{ - if (InString == "All Outputs") - return EPDGBakeSelectionOption::All; - - if (InString == "Selected Network (All Outputs)") - return EPDGBakeSelectionOption::SelectedNetwork; - - if (InString == "Selected Node (All Outputs)") - return EPDGBakeSelectionOption::SelectedNode; - - return EPDGBakeSelectionOption::All; -} - -const EPDGBakePackageReplaceModeOption -FHoudiniEngineEditor::StringToPDGBakePackageReplaceModeOption(const FString & InString) -{ - if (InString == "Create New Assets") - return EPDGBakePackageReplaceModeOption::CreateNewAssets; - - if (InString == "Replace Existing Assets") - return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; - - return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; -} - -const EPackageReplaceMode -FHoudiniEngineEditor::PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode) -{ - EPackageReplaceMode Mode; - switch (InReplaceMode) - { - case EPDGBakePackageReplaceModeOption::CreateNewAssets: - Mode = EPackageReplaceMode::CreateNewAssets; - break; - case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: - Mode = EPackageReplaceMode::ReplaceExistingAssets; - break; - default: - { - Mode = FHoudiniPackageParams::GetDefaultReplaceMode(); - HOUDINI_LOG_WARNING(TEXT("Unsupported value for EPDGBakePackageReplaceModeOption %d, using " - "FHoudiniPackageParams::GetDefaultReplaceMode() for resulting EPackageReplaceMode %d"), - InReplaceMode, Mode); - } - } - - return Mode; -} - -void -FHoudiniEngineEditor::HandleOnDeleteActorsBegin() -{ - if (!GEditor) - return; - - TArray AssetActorsWithTempPDGOutput; - // Iterate over all selected actors - for(FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) - { - AActor* SelectedActor = Cast(*It); - if (IsValid(SelectedActor)) - { - // If the class is a AHoudiniAssetActor check if it has temporary PDG outputs - AHoudiniAssetActor* AssetActor = Cast(SelectedActor); - if (IsValid(AssetActor)) - { - UHoudiniPDGAssetLink* AssetLink = AssetActor->GetPDGAssetLink(); - if (IsValid(AssetLink) && AssetLink->HasTemporaryOutputs()) - { - AssetActorsWithTempPDGOutput.Add(AssetActor); - } - } - } - } - - if (AssetActorsWithTempPDGOutput.Num() > 0) - { - const FText DialogTitle = LOCTEXT( - "PDGAssetLink_DeleteWithTemporaryOutputs_Title", - "Warning: PDG Asset Link(s) With Temporary Outputs"); - const EAppReturnType::Type Choice = FMessageDialog::Open( - EAppMsgType::YesNo, - EAppReturnType::No, - LOCTEXT( - "PDGAssetLink_DeleteWithTemporaryOutputs", - "One or more PDG Asset Links in the selection still have temporary outputs. Are you sure you want to " - "delete these PDG Asset Links and their actors?"), - &DialogTitle); - - const bool bKeepAssetLinkActors = (Choice == EAppReturnType::No); - for (AHoudiniAssetActor* AssetActor : AssetActorsWithTempPDGOutput) - { - if (bKeepAssetLinkActors) - { - GEditor->SelectActor(AssetActor, false, false); - ActorsToReselectOnDeleteActorsEnd.Add(AssetActor); - } - } - } -} - -void -FHoudiniEngineEditor::HandleOnDeleteActorsEnd() -{ - if (!GEditor) - return; - - for (AActor* Actor : ActorsToReselectOnDeleteActorsEnd) - { - if (IsValid(Actor)) - GEditor->SelectActor(Actor, true, false); - } - GEditor->NoteSelectionChange(); - ActorsToReselectOnDeleteActorsEnd.Empty(); -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineEditor.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngine.h" +#include "HoudiniAsset.h" +#include "HoudiniAssetBroker.h" +#include "HoudiniAssetActorFactory.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniParameter.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniRuntimeSettingsDetails.h" +#include "HoudiniSplineComponentVisualizer.h" +#include "HoudiniHandleComponentVisualizer.h" +#include "AssetTypeActions_HoudiniAsset.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPackageParams.h" + +#include "Modules/ModuleManager.h" +#include "Interfaces/IPluginManager.h" +#include "HAL/PlatformFilemanager.h" +#include "Misc/MessageDialog.h" +#include "Misc/Paths.h" +#include "AssetRegistryModule.h" +#include "PropertyEditorModule.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "LevelEditor.h" +#include "Templates/SharedPointer.h" +#include "Framework/Application/SlateApplication.h" +#include "HAL/ConsoleManager.h" +#include "Editor/UnrealEdEngine.h" +#include "Editor.h" +#include "UnrealEdGlobals.h" +#include "Engine/Selection.h" +#include "Widgets/Input/SCheckBox.h" +#include "Logging/LogMacros.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHoudiniEngineEditor, HoudiniEngineEditor); +DEFINE_LOG_CATEGORY(LogHoudiniEngineEditor); + +FHoudiniEngineEditor * +FHoudiniEngineEditor::HoudiniEngineEditorInstance = nullptr; + +FHoudiniEngineEditor & +FHoudiniEngineEditor::Get() +{ + return *HoudiniEngineEditorInstance; +} + +bool +FHoudiniEngineEditor::IsInitialized() +{ + return FHoudiniEngineEditor::HoudiniEngineEditorInstance != nullptr; +} + +FHoudiniEngineEditor::FHoudiniEngineEditor() +{ +} + +void FHoudiniEngineEditor::StartupModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Starting the Houdini Engine Editor module.")); + + // Create style set. + FHoudiniEngineStyle::Initialize(); + + // Initilizes various resources used by our editor UI widgets + InitializeWidgetResource(); + + // Register asset type actions. + RegisterAssetTypeActions(); + + // Register asset brokers. + RegisterAssetBrokers(); + + // Register component visualizers. + RegisterComponentVisualizers(); + + // Register detail presenters. + RegisterDetails(); + + // Register actor factories. + RegisterActorFactories(); + + // Extends the file menu. + ExtendMenu(); + + // Extend the World Outliner Menu + AddLevelViewportMenuExtender(); + + // Adds the custom console commands + RegisterConsoleCommands(); + + // Register global undo / redo callbacks. + //RegisterForUndo(); + + //RegisterPlacementModeExtensions(); + + // Register for any FEditorDelegates that we are interested in, such as + // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds + RegisterEditorDelegates(); + + // Store the instance. + FHoudiniEngineEditor::HoudiniEngineEditorInstance = this; + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module startup complete.")); +} + +void FHoudiniEngineEditor::ShutdownModule() +{ + HOUDINI_LOG_MESSAGE(TEXT("Shutting down the Houdini Engine Editor module.")); + + // Deregister editor delegates + UnregisterEditorDelegates(); + + // Deregister console commands + UnregisterConsoleCommands(); + + // Remove the level viewport Menu extender + RemoveLevelViewportMenuExtender(); + + // Unregister asset type actions. + UnregisterAssetTypeActions(); + + // Unregister asset brokers. + //UnregisterAssetBrokers(); + + // Unregister detail presenters. + UnregisterDetails(); + + // Unregister our component visualizers. + //UnregisterComponentVisualizers(); + + // Unregister global undo / redo callbacks. + //UnregisterForUndo(); + + //UnregisterPlacementModeExtensions(); + + // Unregister the styleset + FHoudiniEngineStyle::Shutdown(); + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine Editor module shutdown complete.")); +} + +FString +FHoudiniEngineEditor::GetHoudiniEnginePluginDir() +{ + FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(EnginePluginDir)) + return EnginePluginDir; + + FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine"); + if (FPaths::DirectoryExists(ProjectPluginDir)) + return ProjectPluginDir; + + TSharedPtr HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine")); + FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir; + if (FPaths::DirectoryExists(PluginBaseDir)) + return PluginBaseDir; + + HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory")); + + return EnginePluginDir; +} + +void +FHoudiniEngineEditor::RegisterDetails() +{ + FPropertyEditorModule & PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Register details presenter for our component type and runtime settings. + PropertyModule.RegisterCustomClassLayout( + TEXT("HoudiniAssetComponent"), + FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniAssetComponentDetails::MakeInstance)); + + PropertyModule.RegisterCustomClassLayout( + TEXT("HoudiniRuntimeSettings"), + FOnGetDetailCustomizationInstance::CreateStatic(&FHoudiniRuntimeSettingsDetails::MakeInstance)); +} + +void +FHoudiniEngineEditor::UnregisterDetails() +{ + if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) + { + FPropertyEditorModule & PropertyModule = + FModuleManager::LoadModuleChecked("PropertyEditor"); + + PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniAssetComponent")); + PropertyModule.UnregisterCustomClassLayout(TEXT("HoudiniRuntimeSettings")); + } +} + +void +FHoudiniEngineEditor::RegisterComponentVisualizers() +{ + if (GUnrealEd) + { + // Register Houdini spline visualizer + SplineComponentVisualizer = MakeShareable(new FHoudiniSplineComponentVisualizer); + if (SplineComponentVisualizer.IsValid()) + { + GUnrealEd->RegisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName(), SplineComponentVisualizer); + SplineComponentVisualizer->OnRegister(); + } + + // Register Houdini handle visualizer + HandleComponentVisualizer = MakeShareable(new FHoudiniHandleComponentVisualizer); + if (HandleComponentVisualizer.IsValid()) + { + GUnrealEd->RegisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName(), HandleComponentVisualizer); + HandleComponentVisualizer->OnRegister(); + } + } +} + +void +FHoudiniEngineEditor::UnregisterComponentVisualizers() +{ + if (GUnrealEd) + { + // Unregister Houdini spline visualizer + if(SplineComponentVisualizer.IsValid()) + GUnrealEd->UnregisterComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); + + // Unregister Houdini handle visualizer + if (HandleComponentVisualizer.IsValid()) + GUnrealEd->UnregisterComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + } +} + +void +FHoudiniEngineEditor::RegisterAssetTypeAction(IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action) +{ + AssetTools.RegisterAssetTypeActions(Action); + AssetTypeActions.Add(Action); +} + +void +FHoudiniEngineEditor::RegisterAssetTypeActions() +{ + // Create and register asset type actions for Houdini asset. + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools").Get(); + RegisterAssetTypeAction(AssetTools, MakeShareable(new FAssetTypeActions_HoudiniAsset())); +} + +void +FHoudiniEngineEditor::UnregisterAssetTypeActions() +{ + // Unregister asset type actions we have previously registered. + if (FModuleManager::Get().IsModuleLoaded("AssetTools")) + { + IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >("AssetTools").Get(); + + for (int32 Index = 0; Index < AssetTypeActions.Num(); ++Index) + AssetTools.UnregisterAssetTypeActions(AssetTypeActions[Index].ToSharedRef()); + + AssetTypeActions.Empty(); + } +} + +void +FHoudiniEngineEditor::RegisterAssetBrokers() +{ + // Create and register broker for Houdini asset. + HoudiniAssetBroker = MakeShareable(new FHoudiniAssetBroker()); + FComponentAssetBrokerage::RegisterBroker( HoudiniAssetBroker, UHoudiniAssetComponent::StaticClass(), true, true ); +} + +void +FHoudiniEngineEditor::UnregisterAssetBrokers() +{ + if (UObjectInitialized()) + { + // Unregister broker. + FComponentAssetBrokerage::UnregisterBroker( HoudiniAssetBroker ); + } +} + +void +FHoudiniEngineEditor::RegisterActorFactories() +{ + if (GEditor) + { + UHoudiniAssetActorFactory * HoudiniAssetActorFactory = + NewObject< UHoudiniAssetActorFactory >(GetTransientPackage(), UHoudiniAssetActorFactory::StaticClass()); + + GEditor->ActorFactories.Add(HoudiniAssetActorFactory); + } +} + +void +FHoudiniEngineEditor::BindMenuCommands() +{ + HEngineCommands = MakeShareable(new FUICommandList); + + FHoudiniEngineCommands::Register(); + const FHoudiniEngineCommands& Commands = FHoudiniEngineCommands::Get(); + + // Session + + HEngineCommands->MapAction( + Commands._CreateSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CreateSession(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._ConnectSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ConnectSession(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._StopSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopSession(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RestartSession, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RestartSession(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OpenSessionSync, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OpenSessionSync(); }), + FCanExecuteAction::CreateLambda([]() { return !FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); + + HEngineCommands->MapAction( + Commands._CloseSessionSync, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CloseSessionSync(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionSyncProcessValid(); })); + + HEngineCommands->MapAction( + Commands._ViewportSyncNone, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(0); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 0); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncHoudini, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(1); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 1); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncUnreal, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(2); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 2); }) + ); + + HEngineCommands->MapAction( + Commands._ViewportSyncBoth, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::SetViewportSync(3); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return (FHoudiniEngineCommands::GetViewportSync() == 3); }) + ); + + // PDG commandlet + HEngineCommands->MapAction( + Commands._IsPDGCommandletEnabled, + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::SetPDGCommandletEnabled(!FHoudiniEngineCommands::IsPDGCommandletEnabled()); }), + FCanExecuteAction::CreateLambda([]() { return true; }), + FIsActionChecked::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletEnabled(); }) + ); + + HEngineCommands->MapAction( + Commands._StartPDGCommandlet, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StartPDGCommandlet(); }), + FCanExecuteAction::CreateLambda([]() + { + return FHoudiniEngineCommands::IsPDGCommandletEnabled() && !FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); + }) + ); + + HEngineCommands->MapAction( + Commands._StopPDGCommandlet, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::StopPDGCommandlet(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected(); })); + + // Plugin + + HEngineCommands->MapAction( + Commands._InstallInfo, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowInstallInfo(); }), + FCanExecuteAction::CreateLambda([]() { return false; })); + + HEngineCommands->MapAction( + Commands._PluginSettings, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::ShowPluginSettings(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Files + + HEngineCommands->MapAction( + Commands._OpenInHoudini, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::OpenInHoudini(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._SaveHIPFile, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::SaveHIPFile(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._CleanUpTempFolder, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::CleanUpTempFolder(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Help and support + + HEngineCommands->MapAction( + Commands._ReportBug, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::ReportBug(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OnlineDoc, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineDocumentation(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._OnlineForum, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::OnlineForum(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + // Actions + + HEngineCommands->MapAction( + Commands._CookAll, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookAllAssets(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._CookSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RebuildAll, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildAllAssets(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._RebuildSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), + FCanExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::IsSessionValid(); })); + + HEngineCommands->MapAction( + Commands._BakeAll, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::BakeAllAssets(); }), + FCanExecuteAction::CreateLambda([](){ return true; })); + + HEngineCommands->MapAction( + Commands._BakeSelected, + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::BakeSelection(); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._RefineAll, + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._RefineSelected, + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), + FCanExecuteAction::CreateLambda([]() { return true; })); + + HEngineCommands->MapAction( + Commands._PauseAssetCooking, + FExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::PauseAssetCooking(); }), + FCanExecuteAction::CreateLambda([](){ return FHoudiniEngineCommands::IsSessionValid(); }), + FIsActionChecked::CreateLambda([](){ return FHoudiniEngineCommands::IsAssetCookingPaused(); })); + + // Non menu command (used for shortcuts only) + + // Append the command to the editor module + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >("LevelEditor"); + LevelEditorModule.GetGlobalLevelEditorActions()->Append(HEngineCommands.ToSharedRef()); +} + +void +FHoudiniEngineEditor::ExtendMenu() +{ + if (IsRunningCommandlet()) + return; + + // We need to add/bind the UI Commands to their functions first + BindMenuCommands(); + + MainMenuExtender = MakeShareable(new FExtender); + + // Extend File menu, we will add Houdini section. + MainMenuExtender->AddMenuExtension( + "FileLoadAndSave", + EExtensionHook::After, + HEngineCommands, + FMenuExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniFileMenuExtension)); + + MainMenuExtender->AddMenuBarExtension( + "Edit", + EExtensionHook::After, + HEngineCommands, + FMenuBarExtensionDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniEditorMenu)); + + // Add our menu extender + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); +} + +void +FHoudiniEngineEditor::AddHoudiniFileMenuExtension(FMenuBuilder & MenuBuilder) +{ + MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); + + // Icons used by the commands are defined in the HoudiniEngineStyle + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + //MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + + MenuBuilder.EndSection(); +} + +void +FHoudiniEngineEditor::AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder) +{ + // View + MenuBarBuilder.AddPullDownMenu( + LOCTEXT("HoudiniLabel", "Houdini Engine"), + LOCTEXT("HoudiniMenu_ToolTip", "Open the Houdini Engine menu"), + FNewMenuDelegate::CreateRaw(this, &FHoudiniEngineEditor::AddHoudiniMainMenuExtension), + "View"); +} + +void +FHoudiniEngineEditor::AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder) +{ + /* + MenuBuilder.BeginSection("Houdini", LOCTEXT("HoudiniLabel", "Houdini Engine")); + // Icons used by the commands are defined in the HoudiniEngineStyle + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + MenuBuilder.EndSection(); + */ + + MenuBuilder.BeginSection("Session", LOCTEXT("SessionLabel", "Session")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CreateSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ConnectSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RestartSession); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenSessionSync); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CloseSessionSync); + + // Viewport sync menu + struct FLocalMenuBuilder + { + static void FillViewportSyncMenu(FMenuBuilder& InSubMenuBuilder) + { + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncNone); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncHoudini); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncUnreal); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ViewportSyncBoth); + } + }; + + MenuBuilder.AddSubMenu( + LOCTEXT("SyncViewport", "Sync Viewport"), + LOCTEXT("SyncViewport_ToolTip", "Sync Viewport"), + FNewMenuDelegate::CreateStatic(&FLocalMenuBuilder::FillViewportSyncMenu), + false, + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._SyncViewport")); + + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("PDG", LOCTEXT("PDGLabel", "PDG")); + struct FLocalPDGMenuBuilder + { + static void FillPDGMenu(FMenuBuilder& InSubMenuBuilder) + { + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._IsPDGCommandletEnabled); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StartPDGCommandlet); + InSubMenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._StopPDGCommandlet); + } + }; + MenuBuilder.AddSubMenu( + LOCTEXT("PDGSubMenu", "Work Item Import Settings"), + LOCTEXT("PDGSubmenu_ToolTip", "PDG Work Item Import Settings"), + FNewMenuDelegate::CreateStatic(&FLocalPDGMenuBuilder::FillPDGMenu), + false, + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.PDGLink")); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Plugin", LOCTEXT("PluginLabel", "Plugin")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._InstallInfo); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PluginSettings); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("File", LOCTEXT("FileLabel", "File")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OpenInHoudini); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._SaveHIPFile); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CleanUpTempFolder); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Help", LOCTEXT("HelpLabel", "Help And Support")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineDoc); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._OnlineForum); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._ReportBug); + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection("Actions", LOCTEXT("ActionsLabel", "Actions")); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._CookSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RebuildSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._BakeSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineAll); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._RefineSelected); + MenuBuilder.AddMenuEntry(FHoudiniEngineCommands::Get()._PauseAssetCooking); + + + MenuBuilder.EndSection(); +} + +void +FHoudiniEngineEditor::RegisterForUndo() +{ + /* + if (GUnrealEd) + GUnrealEd->RegisterForUndo(this); + */ +} + +void +FHoudiniEngineEditor::UnregisterForUndo() +{ + /* + if (GUnrealEd) + GUnrealEd->UnregisterForUndo(this); + */ +} + +void +FHoudiniEngineEditor::RegisterPlacementModeExtensions() +{ + // Load custom houdini tools + /* + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + check(HoudiniRuntimeSettings); + + if (HoudiniRuntimeSettings->bHidePlacementModeHoudiniTools) + return; + + FPlacementCategoryInfo Info( + LOCTEXT("HoudiniCategoryName", "Houdini Engine"), + "HoudiniEngine", + TEXT("PMHoudiniEngine"), + 25 + ); + Info.CustomGenerator = []() -> TSharedRef { return SNew(SHoudiniToolPalette); }; + + IPlacementModeModule::Get().RegisterPlacementCategory(Info); + */ +} + +void +FHoudiniEngineEditor::UnregisterPlacementModeExtensions() +{ + /* + if (IPlacementModeModule::IsAvailable()) + { + IPlacementModeModule::Get().UnregisterPlacementCategory("HoudiniEngine"); + } + + HoudiniTools.Empty(); + */ +} + +void +FHoudiniEngineEditor::InitializeWidgetResource() +{ + // Choice labels for all the input types + //TArray> InputTypeChoiceLabels; + InputTypeChoiceLabels.Reset(); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Asset)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Landscape)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::World)))); + InputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Skeletal)))); + + BlueprintInputTypeChoiceLabels.Reset(); + BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Geometry)))); + BlueprintInputTypeChoiceLabels.Add(MakeShareable(new FString(UHoudiniInput::InputTypeToString(EHoudiniInputType::Curve)))); + + // Choice labels for all Houdini curve types + HoudiniCurveTypeChoiceLabels.Reset(); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Polygon)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Nurbs)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Bezier)))); + HoudiniCurveTypeChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(EHoudiniCurveType::Points)))); + + // Choice labels for all Houdini curve methods + HoudiniCurveMethodChoiceLabels.Reset(); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::CVs)))); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Breakpoints)))); + HoudiniCurveMethodChoiceLabels.Add(MakeShareable(new FString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(EHoudiniCurveMethod::Freehand)))); + + // Choice labels for all Houdini ramp parameter interpolation methods + HoudiniParameterRampInterpolationLabels.Reset(); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CONSTANT)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::LINEAR)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::CATMULL_ROM)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::MONOTONE_CUBIC)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BEZIER)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::BSPLINE)))); + HoudiniParameterRampInterpolationLabels.Add(MakeShareable(new FString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType::HERMITE)))); + + // Choice labels for all Houdini curve output export types + HoudiniCurveOutputExportTypeLabels.Reset(); + HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Unreal Spline")))); + HoudiniCurveOutputExportTypeLabels.Add(MakeShareable(new FString(TEXT("Houdini Spline")))); + + // Choice labels for all Unreal curve output curve types + //(for temporary, we need to figure out a way to access the output curve's info later) + UnrealCurveOutputCurveTypeLabels.Reset(); + UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Linear")))); + UnrealCurveOutputCurveTypeLabels.Add(MakeShareable(new FString(TEXT("Curve")))); + + // Option labels for all landscape outputs bake options + HoudiniLandscapeOutputBakeOptionLabels.Reset(); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Current Level")))); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To Image")))); + HoudiniLandscapeOutputBakeOptionLabels.Add(MakeShareable(new FString(TEXT("To New World")))); + + // Option labels for Houdini Engine PDG bake options + HoudiniEnginePDGBakeTypeOptionLabels.Reset(); + HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); + HoudiniEnginePDGBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); + + // Option labels for Houdini Engine bake options + HoudiniEngineBakeTypeOptionLabels.Reset(); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToActor)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToBlueprint)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToFoliage)))); + HoudiniEngineBakeTypeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(EHoudiniEngineBakeOption::ToWorldOutliner)))); + + // Option labels for Houdini Engine PDG bake options + HoudiniEnginePDGBakeSelectionOptionLabels.Reset(); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::All)))); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNetwork)))); + HoudiniEnginePDGBakeSelectionOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(EPDGBakeSelectionOption::SelectedNode)))); + + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Reset(); + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::ReplaceExistingAssets)))); + HoudiniEnginePDGBakePackageReplaceModeOptionLabels.Add(MakeShareable(new FString(FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(EPDGBakePackageReplaceModeOption::CreateNewAssets)))); + + + static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); + + // Houdini Logo Brush + FString Icon128FilePath = IconsDir + TEXT("icon_houdini_logo_128"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) + { + const FName BrushName(*Icon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Logo Brush + FString HEIcon128FilePath = IconsDir + TEXT("icon_hengine_logo_128"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HEIcon128FilePath)) + { + const FName BrushName(*HEIcon128FilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineLogoBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Houdini Engine Banner + FString HoudiniEngineUIIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_banner_d.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Rebuild Icon Brush + FString HoudiniEngineUIRebuildIconFilePath = IconsDir + TEXT("rebuild_all16x16.png"); + //FString HoudiniEngineUIRebuildIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_reload_icon.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRebuildIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIRebuildIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIRebuildIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Recook Icon Brush + //FString HoudiniEngineUIRecookIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_recook_icon.png"); + FString HoudiniEngineUIRecookIconFilePath = IconsDir + TEXT("cook_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIRecookIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIRecookIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIRecookIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Reset Parameters Icon Brush + //FString HoudiniEngineUIResetParametersIconFilePath = GetHoudiniEnginePluginDir() / TEXT("Resources/hengine_resetparameters_icon.png"); + FString HoudiniEngineUIResetParametersIconFilePath = IconsDir + TEXT("reset_parameters16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*HoudiniEngineUIResetParametersIconFilePath)) + { + const FName BrushName(*HoudiniEngineUIResetParametersIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIResetParametersIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // Bake + FString BakeIconFilePath = IconsDir + TEXT("bake_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*BakeIconFilePath)) + { + const FName BrushName(*BakeIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIBakeIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // CookLog + FString CookLogIconFilePath = IconsDir + TEXT("cook_log16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*CookLogIconFilePath)) + { + const FName BrushName(*CookLogIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUICookLogIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // AssetHelp + FString AssetHelpIconFilePath = IconsDir + TEXT("asset_help16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*AssetHelpIconFilePath)) + { + const FName BrushName(*AssetHelpIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIAssetHelpIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + + // PDG Asset Link + // PDG + FString PDGIconFilePath = IconsDir + TEXT("pdg_link16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGIconFilePath)) + { + const FName BrushName(*PDGIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Cancel + // PDGCancel + FString PDGCancelIconFilePath = IconsDir + TEXT("pdg_cancel16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGCancelIconFilePath)) + { + const FName BrushName(*PDGCancelIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGCancelIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Dirty All + // PDGDirtyAll + FString PDGDirtyAllIconFilePath = IconsDir + TEXT("pdg_dirty_all16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyAllIconFilePath)) + { + const FName BrushName(*PDGDirtyAllIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGDirtyAllIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Dirty Node + // PDGDirtyNode + FString PDGDirtyNodeIconFilePath = IconsDir + TEXT("pdg_dirty_node16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGDirtyNodeIconFilePath)) + { + const FName BrushName(*PDGDirtyNodeIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGDirtyNodeIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Pause + // PDGReset + FString PDGPauseIconFilePath = IconsDir + TEXT("pdg_pause16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGPauseIconFilePath)) + { + const FName BrushName(*PDGPauseIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGPauseIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Reset + // PDGReset + FString PDGResetIconFilePath = IconsDir + TEXT("pdg_reset16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGResetIconFilePath)) + { + const FName BrushName(*PDGResetIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGResetIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } + + // PDG Refresh + // PDGRefresh + FString PDGRefreshIconFilePath = IconsDir + TEXT("pdg_refresh16x16.png"); + if (FSlateApplication::IsInitialized() && FPlatformFileManager::Get().GetPlatformFile().FileExists(*PDGRefreshIconFilePath)) + { + const FName BrushName(*PDGRefreshIconFilePath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if (Size.X > 0 && Size.Y > 0) + { + static const int32 ProgressIconSize = 32; + HoudiniEngineUIPDGRefreshIconBrush = MakeShareable(new FSlateDynamicImageBrush( + BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); + } + } +} + +void +FHoudiniEngineEditor::AddLevelViewportMenuExtender() +{ + FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked("LevelEditor"); + auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders(); + + MenuExtenders.Add(FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw(this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender)); + LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle(); +} + +void +FHoudiniEngineEditor::RemoveLevelViewportMenuExtender() +{ + if (LevelViewportExtenderHandle.IsValid()) + { + FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr("LevelEditor"); + if (LevelEditorModule) + { + typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType; + LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll( + [=](const DelegateType& In) { return In.GetHandle() == LevelViewportExtenderHandle; }); + } + } +} + +TSharedRef +FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef CommandList, const TArray InActors) +{ + TSharedRef Extender = MakeShareable(new FExtender); + + // Build an array of the HoudiniAssets corresponding to the selected actors + TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets; + TArray< TWeakObjectPtr< AHoudiniAssetActor > > HoudiniAssetActors; + for (auto CurrentActor : InActors) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(CurrentActor); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + continue; + + HoudiniAssetActors.Add(HoudiniAssetActor); + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + continue; + + HoudiniAssets.AddUnique(HoudiniAssetComponent->GetHoudiniAsset()); + } + + if (HoudiniAssets.Num() > 0) + { + // Add the Asset menu extension + if (AssetTypeActions.Num() > 0) + { + // Add the menu extensions via our HoudiniAssetTypeActions + FAssetTypeActions_HoudiniAsset * HATA = static_cast(AssetTypeActions[0].Get()); + if (HATA) + Extender = HATA->AddLevelEditorMenuExtenders(HoudiniAssets); + } + } + + if (HoudiniAssetActors.Num() > 0) + { + // Add some actor menu extensions + FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked(TEXT("LevelEditor")); + TSharedRef LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions(); + Extender->AddMenuExtension( + "ActorControl", + EExtensionHook::After, + LevelEditorCommandBindings, + FMenuExtensionDelegate::CreateLambda([this, HoudiniAssetActors](FMenuBuilder& MenuBuilder) + { + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recentre", "Recentre selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecentreTooltip", "Recentres the selected Houdini Asset Actors pivots to their input/cooked static mesh average centre."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecentreSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recook", "Recook selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecookTooltip", "Forces a recook on the selected Houdini Asset Actors."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._CookSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RecookSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Rebuild", "Rebuild selected"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RebuildTooltip", "Rebuilds selected Houdini Asset Actors in the current level."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RebuildSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { return FHoudiniEngineCommands::RebuildSelection(); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + + MenuBuilder.AddMenuEntry( + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshes", "Refine Houdini Proxy Meshes"), + NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Refine_ProxyMeshesTooltip", "Build and replace Houdini Proxy Meshes with Static Meshes."), + FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine._RefineSelected"), + FUIAction( + FExecuteAction::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true); }), + FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); }) + ) + ); + }) + ); + } + + return Extender; +} + +void +FHoudiniEngineEditor::RegisterConsoleCommands() +{ + // Register corresponding console commands + static FAutoConsoleCommand CCmdOpen = FAutoConsoleCommand( + TEXT("Houdini.Open"), + TEXT("Open the scene in Houdini."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::OpenInHoudini)); + + static FAutoConsoleCommand CCmdSave = FAutoConsoleCommand( + TEXT("Houdini.Save"), + TEXT("Save the current Houdini scene to a hip file."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::SaveHIPFile)); + + static FAutoConsoleCommand CCmdBake = FAutoConsoleCommand( + TEXT("Houdini.BakeAll"), + TEXT("Bakes and replaces with blueprints all Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::BakeAllAssets)); + + static FAutoConsoleCommand CCmdClean = FAutoConsoleCommand( + TEXT("Houdini.Clean"), + TEXT("Cleans up unused/unreferenced Houdini Engine temporary files."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::CleanUpTempFolder)); + + static FAutoConsoleCommand CCmdPause = FAutoConsoleCommand( + TEXT("Houdini.Pause"), + TEXT("Pauses Houdini Engine Asset cooking."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::PauseAssetCooking)); + + // Additional console only commands + static FAutoConsoleCommand CCmdCookAll = FAutoConsoleCommand( + TEXT("Houdini.CookAll"), + TEXT("Re-cooks all Houdini Engine Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RecookAllAssets)); + + static FAutoConsoleCommand CCmdRebuildAll = FAutoConsoleCommand( + TEXT("Houdini.RebuildAll"), + TEXT("Rebuilds all Houdini Engine Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RebuildAllAssets)); + + static FAutoConsoleCommand CCmdCookSelec = FAutoConsoleCommand( + TEXT("Houdini.Cook"), + TEXT("Re-cooks selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RecookSelection)); + + static FAutoConsoleCommand CCmdRebuildSelec = FAutoConsoleCommand( + TEXT("Houdini.Rebuild"), + TEXT("Rebuilds selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RebuildSelection)); + + static FAutoConsoleCommand CCmdBakeSelec = FAutoConsoleCommand( + TEXT("Houdini.Bake"), + TEXT("Bakes and replaces with blueprints selected Houdini Asset Actors in the current level."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::BakeSelection)); + + static FAutoConsoleCommand CCmdRestartSession = FAutoConsoleCommand( + TEXT("Houdini.RestartSession"), + TEXT("Restart the current Houdini Session."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::RestartSession)); + + /* + IConsoleManager &ConsoleManager = IConsoleManager::Get(); + const TCHAR *CommandName = TEXT("HoudiniEngine.RefineHoudiniProxyMeshesToStaticMeshes"); + IConsoleCommand *Command = ConsoleManager.RegisterConsoleCommand( + CommandName, + TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), + FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); + if (Command) + { + ConsoleCommands.Add(Command); + } + else + { + HOUDINI_LOG_ERROR(TEXT("Failed to register the '%s' console command."), CommandName); + } + */ + + static FAutoConsoleCommand CCmdRefine = FAutoConsoleCommand( + TEXT("Houdini.RefineAll"), + TEXT("Builds and replaces all Houdini proxy meshes with UStaticMeshes."), + FConsoleCommandDelegate::CreateLambda([]() { FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(false); })); + + static FAutoConsoleCommand CCmdOpenSessionSync = FAutoConsoleCommand( + TEXT("Houdini.OpenSessionSync"), + TEXT("Stops the current session, opens Houdini and automnatically start and connect a Session Sync."), + FConsoleCommandDelegate::CreateStatic(&FHoudiniEngineCommands::OpenSessionSync)); +} + +void +FHoudiniEngineEditor::UnregisterConsoleCommands() +{ + IConsoleManager &ConsoleManager = IConsoleManager::Get(); + for (IConsoleCommand *Command : ConsoleCommands) + { + if (Command) + { + ConsoleManager.UnregisterConsoleObject(Command); + } + } + ConsoleCommands.Empty(); +} + +void +FHoudiniEngineEditor::RegisterEditorDelegates() +{ + PreSaveWorldEditorDelegateHandle = FEditorDelegates::PreSaveWorld.AddLambda([](uint32 SaveFlags, UWorld* World) + { + // Skip if this is a game world or an autosave, only refine meshes when the user manually saves + if (!World->IsGameWorld() && (SaveFlags & ESaveFlags::SAVE_FromAutosave) == 0) + { + const bool bSelectedOnly = false; + const bool bSilent = false; + const bool bRefineAll = false; + const bool bOnPreSaveWorld = true; + UWorld * const OnPreSaveWorld = World; + const bool bOnPreBeginPIE = false; + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); + } + + if (!World->IsGameWorld()) + { + UWorld * const OnPreSaveWorld = World; + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + + // Save all dirty temporary cook package OnPostSaveWorld + OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([OnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) + { + if (OnPreSaveWorld && OnPreSaveWorld != InWorld) + return; + + FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(InWorld); + + FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineEditor::Get().GetOnPostSaveWorldOnceHandle(); + if (OnPostSaveWorldHandle.IsValid()) + { + if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle)) + OnPostSaveWorldHandle.Reset(); + } + }); + } + }); + + PreBeginPIEEditorDelegateHandle = FEditorDelegates::PreBeginPIE.AddLambda([](const bool bIsSimulating) + { + const bool bSelectedOnly = false; + const bool bSilent = false; + const bool bRefineAll = false; + const bool bOnPreSaveWorld = false; + UWorld * const OnPreSaveWorld = nullptr; + const bool bOnPreBeginPIE = true; + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); + }); + + OnDeleteActorsBegin = FEditorDelegates::OnDeleteActorsBegin.AddLambda([this](){ this->HandleOnDeleteActorsBegin(); }); + OnDeleteActorsEnd = FEditorDelegates::OnDeleteActorsEnd.AddLambda([this](){ this-> HandleOnDeleteActorsEnd(); }); +} + +void +FHoudiniEngineEditor::UnregisterEditorDelegates() +{ + if (PreSaveWorldEditorDelegateHandle.IsValid()) + FEditorDelegates::PreSaveWorld.Remove(PreSaveWorldEditorDelegateHandle); + + if (PreBeginPIEEditorDelegateHandle.IsValid()) + FEditorDelegates::PreBeginPIE.Remove(PreBeginPIEEditorDelegateHandle); + + if (OnDeleteActorsBegin.IsValid()) + FEditorDelegates::OnDeleteActorsBegin.Remove(OnDeleteActorsBegin); + + if (OnDeleteActorsEnd.IsValid()) + FEditorDelegates::OnDeleteActorsEnd.Remove(OnDeleteActorsEnd); +} + +FString +FHoudiniEngineEditor::GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption) +{ + FString Str; + switch (BakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + Str = "Actor"; + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + Str = "Blueprint"; + break; + + case EHoudiniEngineBakeOption::ToFoliage: + Str = "Foliage"; + break; + + case EHoudiniEngineBakeOption::ToWorldOutliner: + Str = "World Outliner"; + break; + } + + return Str; +} + +FString +FHoudiniEngineEditor::GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption) +{ + FString Str; + switch (BakeOption) + { + case EPDGBakeSelectionOption::All: + Str = "All Outputs"; + break; + + case EPDGBakeSelectionOption::SelectedNetwork: + Str = "Selected Network (All Outputs)"; + break; + + case EPDGBakeSelectionOption::SelectedNode: + Str = "Selected Node (All Outputs)"; + break; + } + + return Str; +} + +FString +FHoudiniEngineEditor::GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption) +{ + FString Str; + switch (InOption) + { + case EPDGBakePackageReplaceModeOption::CreateNewAssets: + Str = "Create New Assets"; + break; + case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: + Str = "Replace Existing Assets"; + break; + } + + return Str; +} + +const EHoudiniEngineBakeOption +FHoudiniEngineEditor::StringToHoudiniEngineBakeOption(const FString & InString) +{ + if (InString == "Actor") + return EHoudiniEngineBakeOption::ToActor; + + if (InString == "Blueprint") + return EHoudiniEngineBakeOption::ToBlueprint; + + if (InString == "Foliage") + return EHoudiniEngineBakeOption::ToFoliage; + + if (InString == "World Outliner") + return EHoudiniEngineBakeOption::ToWorldOutliner; + + return EHoudiniEngineBakeOption::ToActor; +} + +const EPDGBakeSelectionOption +FHoudiniEngineEditor::StringToPDGBakeSelectionOption(const FString& InString) +{ + if (InString == "All Outputs") + return EPDGBakeSelectionOption::All; + + if (InString == "Selected Network (All Outputs)") + return EPDGBakeSelectionOption::SelectedNetwork; + + if (InString == "Selected Node (All Outputs)") + return EPDGBakeSelectionOption::SelectedNode; + + return EPDGBakeSelectionOption::All; +} + +const EPDGBakePackageReplaceModeOption +FHoudiniEngineEditor::StringToPDGBakePackageReplaceModeOption(const FString & InString) +{ + if (InString == "Create New Assets") + return EPDGBakePackageReplaceModeOption::CreateNewAssets; + + if (InString == "Replace Existing Assets") + return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; + + return EPDGBakePackageReplaceModeOption::ReplaceExistingAssets; +} + +const EPackageReplaceMode +FHoudiniEngineEditor::PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode) +{ + EPackageReplaceMode Mode; + switch (InReplaceMode) + { + case EPDGBakePackageReplaceModeOption::CreateNewAssets: + Mode = EPackageReplaceMode::CreateNewAssets; + break; + case EPDGBakePackageReplaceModeOption::ReplaceExistingAssets: + Mode = EPackageReplaceMode::ReplaceExistingAssets; + break; + default: + { + Mode = FHoudiniPackageParams::GetDefaultReplaceMode(); + HOUDINI_LOG_WARNING(TEXT("Unsupported value for EPDGBakePackageReplaceModeOption %d, using " + "FHoudiniPackageParams::GetDefaultReplaceMode() for resulting EPackageReplaceMode %d"), + InReplaceMode, Mode); + } + } + + return Mode; +} + +void +FHoudiniEngineEditor::HandleOnDeleteActorsBegin() +{ + if (!GEditor) + return; + + TArray AssetActorsWithTempPDGOutput; + // Iterate over all selected actors + for(FSelectionIterator It(GEditor->GetSelectedActorIterator()); It; ++It) + { + AActor* SelectedActor = Cast(*It); + if (IsValid(SelectedActor)) + { + // If the class is a AHoudiniAssetActor check if it has temporary PDG outputs + AHoudiniAssetActor* AssetActor = Cast(SelectedActor); + if (IsValid(AssetActor)) + { + UHoudiniPDGAssetLink* AssetLink = AssetActor->GetPDGAssetLink(); + if (IsValid(AssetLink) && AssetLink->HasTemporaryOutputs()) + { + AssetActorsWithTempPDGOutput.Add(AssetActor); + } + } + } + } + + if (AssetActorsWithTempPDGOutput.Num() > 0) + { + const FText DialogTitle = LOCTEXT( + "PDGAssetLink_DeleteWithTemporaryOutputs_Title", + "Warning: PDG Asset Link(s) With Temporary Outputs"); + const EAppReturnType::Type Choice = FMessageDialog::Open( + EAppMsgType::YesNo, + EAppReturnType::No, + LOCTEXT( + "PDGAssetLink_DeleteWithTemporaryOutputs", + "One or more PDG Asset Links in the selection still have temporary outputs. Are you sure you want to " + "delete these PDG Asset Links and their actors?"), + &DialogTitle); + + const bool bKeepAssetLinkActors = (Choice == EAppReturnType::No); + for (AHoudiniAssetActor* AssetActor : AssetActorsWithTempPDGOutput) + { + if (bKeepAssetLinkActors) + { + GEditor->SelectActor(AssetActor, false, false); + ActorsToReselectOnDeleteActorsEnd.Add(AssetActor); + } + } + } +} + +void +FHoudiniEngineEditor::HandleOnDeleteActorsEnd() +{ + if (!GEditor) + return; + + for (AActor* Actor : ActorsToReselectOnDeleteActorsEnd) + { + if (IsValid(Actor)) + GEditor->SelectActor(Actor, true, false); + } + GEditor->NoteSelectionChange(); + ActorsToReselectOnDeleteActorsEnd.Empty(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h index 71e3cc81e..451d781dd 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h @@ -1,350 +1,350 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "IHoudiniEngineEditor.h" -#include "HoudiniInputTypes.h" - -#include "CoreTypes.h" -#include "Templates/SharedPointer.h" -#include "Framework/Commands/UICommandList.h" -#include "Brushes/SlateDynamicImageBrush.h" - - -class FExtender; -class IAssetTools; -class IAssetTypeActions; -class IComponentAssetBroker; -class FComponentVisualizer; -class FMenuBuilder; -class FMenuBarBuilder; -class FUICommandList; -class AActor; - -struct IConsoleCommand; -struct FSlateDynamicImageBrush; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod: int8; -enum class EHoudiniLandscapeOutputBakeType: uint8; -enum class EHoudiniEngineBakeOption : uint8; -enum class EPDGBakeSelectionOption : uint8; -enum class EPDGBakePackageReplaceModeOption : uint8; -enum class EPackageReplaceMode : int8; - -class HOUDINIENGINEEDITOR_API FHoudiniEngineEditor : public IHoudiniEngineEditor -{ - public: - FHoudiniEngineEditor(); - - // IModuleInterface methods. - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // IHoudiniEngineEditor methods - virtual void RegisterComponentVisualizers() override; - virtual void UnregisterComponentVisualizers() override; - virtual void RegisterDetails() override; - virtual void UnregisterDetails() override; - virtual void RegisterAssetTypeActions() override; - virtual void UnregisterAssetTypeActions() override; - virtual void RegisterAssetBrokers() override; - virtual void UnregisterAssetBrokers() override; - virtual void RegisterActorFactories() override; - virtual void ExtendMenu() override; - virtual void RegisterForUndo() override; - virtual void UnregisterForUndo() override; - virtual void RegisterPlacementModeExtensions() override; - virtual void UnregisterPlacementModeExtensions() override; - - // Return singleton instance of Houdini Engine Editor, used internally. - static FHoudiniEngineEditor & Get(); - - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // Returns the plugin's directory - static FString GetHoudiniEnginePluginDir(); - - // Initializes Widget resources - void InitializeWidgetResource(); - - // Menu action to pause cooking for all Houdini Assets - void PauseAssetCooking(); - - // Helper delegate used to determine if PauseAssetCooking can be executed. - bool CanPauseAssetCooking(); - - // Helper delegate used to get the current state of PauseAssetCooking. - bool IsAssetCookingPaused(); - - // Returns a pointer to the input choice types - TArray>* GetInputTypeChoiceLabels() { return &InputTypeChoiceLabels; }; - TArray>* GetBlueprintInputTypeChoiceLabels() { return &BlueprintInputTypeChoiceLabels; }; - - // Returns a pointer to the Houdini curve types - TArray>* GetHoudiniCurveTypeChoiceLabels() { return &HoudiniCurveTypeChoiceLabels; }; - - // Returns a pointer to the Houdini curve methods - TArray>* GetHoudiniCurveMethodChoiceLabels() { return &HoudiniCurveMethodChoiceLabels; }; - - // Returns a pointer to the Houdini ramp parameter interpolation methods - TArray>* GetHoudiniParameterRampInterpolationMethodLabels() {return &HoudiniParameterRampInterpolationLabels;} - - // Returns a pointer to the Houdini curve output export types - TArray>* GetHoudiniCurveOutputExportTypeLabels() { return &HoudiniCurveOutputExportTypeLabels; }; - - TArray>* GetHoudiniLandscapeOutputBakeOptionsLabels() { return &HoudiniLandscapeOutputBakeOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Type labels - TArray>* GetHoudiniEnginePDGBakeTypeOptionsLabels() { return &HoudiniEnginePDGBakeTypeOptionLabels; }; - - // Returns a pointer to the Houdini Engine Bake Type labels - TArray>* GetHoudiniEngineBakeTypeOptionsLabels() { return &HoudiniEngineBakeTypeOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Target labels - TArray>* GetHoudiniEnginePDGBakeSelectionOptionsLabels() { return &HoudiniEnginePDGBakeSelectionOptionLabels; }; - - // Returns a pointer to the Houdini Engine PDG Bake Package Replace Mode labels - TArray>* GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels() { return &HoudiniEnginePDGBakePackageReplaceModeOptionLabels; }; - - // Returns a shared Ptr to the Houdini logo - TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; - TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; - - // Functions Return a shared Ptr to the Houdini Engine UI Icon - TSharedPtr GetHoudiniEngineUIIconBrush() const { return HoudiniEngineUIIconBrush; } - TSharedPtr GetHoudiniEngineUIRebuildIconBrush() const { return HoudiniEngineUIRebuildIconBrush; } - TSharedPtr GetHoudiniEngineUIRecookIconBrush() const { return HoudiniEngineUIRecookIconBrush; } - TSharedPtr GetHoudiniEngineUIResetParametersIconBrush() const { return HoudiniEngineUIResetParametersIconBrush; } - - TSharedPtr GetHoudiniEngineUIBakeIconBrush() const { return HoudiniEngineUIBakeIconBrush; } - TSharedPtr GetHoudiniEngineUICookLogIconBrush() const { return HoudiniEngineUICookLogIconBrush; } - TSharedPtr GetHoudiniEngineUIAssetHelpIconBrush() const { return HoudiniEngineUIAssetHelpIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGIconBrush() const { return HoudiniEngineUIPDGIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGCancelIconBrush() const { return HoudiniEngineUIPDGCancelIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGDirtyAllIconBrush() const { return HoudiniEngineUIPDGDirtyAllIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGDirtyNodeIconBrush() const { return HoudiniEngineUIPDGDirtyNodeIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGPauseIconBrush() const { return HoudiniEngineUIPDGPauseIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGResetIconBrush() const { return HoudiniEngineUIPDGResetIconBrush; } - TSharedPtr GetHoudiniEngineUIPDGRefreshIconBrush() const { return HoudiniEngineUIPDGRefreshIconBrush; } - - // Returns a pointer to Unreal output curve types (for temporary) - TArray>* GetUnrealOutputCurveTypeLabels() { return &UnrealCurveOutputCurveTypeLabels; }; - - // returns string from Houdini Engine Bake Option - FString GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption); - - // returns string from Houdini Engine PDG Bake Target Option - FString GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption); - - // returns string from PDG package replace mode option - FString GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption); - - // Return HoudiniEngineBakeOption from FString - const EHoudiniEngineBakeOption StringToHoudiniEngineBakeOption(const FString & InString); - - // Return EPDGBakeSelectionOption from FString - const EPDGBakeSelectionOption StringToPDGBakeSelectionOption(const FString& InString); - - // Return EPDGBakePackageReplaceModeOption from FString - const EPDGBakePackageReplaceModeOption StringToPDGBakePackageReplaceModeOption(const FString & InString); - - // Convert EPDGBakePackageReplaceModeOption to EPackageReplaceMode - // TODO: perhaps EPackageReplaceMode can be moved to HoudiniEngineRuntime to avoid having both - // TODO: EPDGBakePackageReplaceModeOption and EPackageReplaceMode? - const EPackageReplaceMode PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode); - - // Get the reference of the radio button folder circle point arrays reference - TArray & GetHoudiniParameterRadioButtonPointsOuter() { return HoudiniParameterRadioButtonPointsOuter; }; - TArray & GetHoudiniParameterRadioButtonPointsInner() { return HoudiniParameterRadioButtonPointsInner; }; - - // Gets the PostSaveWorldOnceHandle - FDelegateHandle& GetOnPostSaveWorldOnceHandle() { return PostSaveWorldOnceHandle; } - - protected: - - // Binds the commands used by the menus - void BindMenuCommands(); - - // Register AssetType action. - void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef< IAssetTypeActions > Action); - - // Add menu extension for our module. - void AddHoudiniFileMenuExtension(FMenuBuilder& MenuBuilder); - - // Add the Houdini Engine editor menu - void AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder); - - // Add menu extension for our module. - void AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder); - - // Adds the custom Houdini Engine commands to the world outliner context menu - void AddLevelViewportMenuExtender(); - - // Removes the custom Houdini Engine commands to the world outliner context menu - void RemoveLevelViewportMenuExtender(); - - // Returns all the custom Houdini Engine commands for the world outliner context menu - TSharedRef GetLevelViewportContextMenuExtender( - const TSharedRef CommandList, const TArray InActors); - - // Register all console commands provided by this module - void RegisterConsoleCommands(); - - // Unregister all registered console commands provided by this module - void UnregisterConsoleCommands(); - - // Register for any FEditorDelegates that we are interested in, such as - // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds - void RegisterEditorDelegates(); - - // Deregister editor delegates - void UnregisterEditorDelegates(); - - // Process the OnDeleteActorsBegin call received from FEditorDelegates. - // Check if any AHoudiniAssetActors with PDG links are selected for deletion. If so, - // check if these still have temporary outputs and give the user to option to skip - // deleting the ones with temporary output. - void HandleOnDeleteActorsBegin(); - - // Re-select AHoudiniAssetActors that were deselected (to avoid deletion) by HandleOnDeleteActorsBegin - void HandleOnDeleteActorsEnd(); - - private: - - // Singleton instance of Houdini Engine Editor. - static FHoudiniEngineEditor * HoudiniEngineEditorInstance; - - // AssetType actions associated with Houdini asset. - TArray> AssetTypeActions; - - // Broker associated with Houdini asset. - TSharedPtr HoudiniAssetBroker; - - // Widget resources: Input Type combo box labels - TArray> InputTypeChoiceLabels; - TArray> BlueprintInputTypeChoiceLabels; - - // Widget resources: Houdini Curve Type combo box labels - TArray> HoudiniCurveTypeChoiceLabels; - - // Widget resources: Houdini Curve Method combo box labels - TArray> HoudiniCurveMethodChoiceLabels; - - // Widget resources: Houdini Ramp Interpolation method combo box labels - TArray> HoudiniParameterRampInterpolationLabels; - - // Widget resources: Houdini Curve Output type labels - TArray> HoudiniCurveOutputExportTypeLabels; - - // Widget resources: Unreal Curve type labels (for temporary, we need to figure out a way to access the output curve's info later) - TArray> UnrealCurveOutputCurveTypeLabels; - - // Widget resources: Landscape output Bake type labels - TArray> HoudiniLandscapeOutputBakeOptionLabels; - - // Widget resources: PDG Bake type labels - TArray> HoudiniEnginePDGBakeTypeOptionLabels; - - // Widget resources: Bake type labels - TArray> HoudiniEngineBakeTypeOptionLabels; - - // Widget resources: PDG Bake target labels - TArray> HoudiniEnginePDGBakeSelectionOptionLabels; - - // Widget resources: PDG Bake package replace mode labels - TArray> HoudiniEnginePDGBakePackageReplaceModeOptionLabels; - - // List of UI commands used by the various menus - TSharedPtr HEngineCommands; - - // Houdini logo brush. - TSharedPtr HoudiniLogoBrush; - // Houdini Engine logo brush - TSharedPtr HoudiniEngineLogoBrush; - - // houdini Engine UI Brushes - TSharedPtr HoudiniEngineUIIconBrush; - TSharedPtr HoudiniEngineUIRebuildIconBrush; - TSharedPtr HoudiniEngineUIRecookIconBrush; - TSharedPtr HoudiniEngineUIResetParametersIconBrush; - - TSharedPtr HoudiniEngineUIBakeIconBrush; - TSharedPtr HoudiniEngineUICookLogIconBrush; - TSharedPtr HoudiniEngineUIAssetHelpIconBrush; - TSharedPtr HoudiniEngineUIPDGIconBrush; - TSharedPtr HoudiniEngineUIPDGCancelIconBrush; - TSharedPtr HoudiniEngineUIPDGDirtyAllIconBrush; - TSharedPtr HoudiniEngineUIPDGDirtyNodeIconBrush; - TSharedPtr HoudiniEngineUIPDGPauseIconBrush; - TSharedPtr HoudiniEngineUIPDGResetIconBrush; - TSharedPtr HoudiniEngineUIPDGRefreshIconBrush; - - // The extender to pass to the level editor to extend it's File menu. - TSharedPtr MainMenuExtender; - - // The extender to pass to the level editor to extend it's Main menu. - //TSharedPtr FileMenuExtender; - - // DelegateHandle for the viewport's context menu extender - FDelegateHandle LevelViewportExtenderHandle; - - // SplineComponentVisualizer - TSharedPtr SplineComponentVisualizer; - - TSharedPtr HandleComponentVisualizer; - - // Array of HoudiniEngine console commands - TArray ConsoleCommands; - - // Delegate handle for the PreSaveWorld editor delegate - FDelegateHandle PreSaveWorldEditorDelegateHandle; - - // Delegate handle for the PostSaveWorld editor delegate: this - // is bound on PreSaveWorld with specific captures and then unbound - // by itself - FDelegateHandle PostSaveWorldOnceHandle; - - // Delegate handle for the PreBeginPIE editor delegate - FDelegateHandle PreBeginPIEEditorDelegateHandle; - - // Delegate handle for OnDeleteActorsBegin - FDelegateHandle OnDeleteActorsBegin; - - // Delegate handle for OnDeleteActorsEnd - FDelegateHandle OnDeleteActorsEnd; - - // List of actors that HandleOnDeleteActorsBegin marked to _not_ be deleted. This - // is used to re-select these actors in HandleOnDeleteActorsEnd. - TArray ActorsToReselectOnDeleteActorsEnd; - - // Cache the points of radio button folder circle points to avoid huge amount of repeat computation. - // (Computing points are time consuming since it uses trigonometric functions) - TArray HoudiniParameterRadioButtonPointsOuter; - TArray HoudiniParameterRadioButtonPointsInner; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "IHoudiniEngineEditor.h" +#include "HoudiniInputTypes.h" + +#include "CoreTypes.h" +#include "Templates/SharedPointer.h" +#include "Framework/Commands/UICommandList.h" +#include "Brushes/SlateDynamicImageBrush.h" + + +class FExtender; +class IAssetTools; +class IAssetTypeActions; +class IComponentAssetBroker; +class FComponentVisualizer; +class FMenuBuilder; +class FMenuBarBuilder; +class FUICommandList; +class AActor; + +struct IConsoleCommand; +struct FSlateDynamicImageBrush; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod: int8; +enum class EHoudiniLandscapeOutputBakeType: uint8; +enum class EHoudiniEngineBakeOption : uint8; +enum class EPDGBakeSelectionOption : uint8; +enum class EPDGBakePackageReplaceModeOption : uint8; +enum class EPackageReplaceMode : int8; + +class HOUDINIENGINEEDITOR_API FHoudiniEngineEditor : public IHoudiniEngineEditor +{ + public: + FHoudiniEngineEditor(); + + // IModuleInterface methods. + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // IHoudiniEngineEditor methods + virtual void RegisterComponentVisualizers() override; + virtual void UnregisterComponentVisualizers() override; + virtual void RegisterDetails() override; + virtual void UnregisterDetails() override; + virtual void RegisterAssetTypeActions() override; + virtual void UnregisterAssetTypeActions() override; + virtual void RegisterAssetBrokers() override; + virtual void UnregisterAssetBrokers() override; + virtual void RegisterActorFactories() override; + virtual void ExtendMenu() override; + virtual void RegisterForUndo() override; + virtual void UnregisterForUndo() override; + virtual void RegisterPlacementModeExtensions() override; + virtual void UnregisterPlacementModeExtensions() override; + + // Return singleton instance of Houdini Engine Editor, used internally. + static FHoudiniEngineEditor & Get(); + + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // Returns the plugin's directory + static FString GetHoudiniEnginePluginDir(); + + // Initializes Widget resources + void InitializeWidgetResource(); + + // Menu action to pause cooking for all Houdini Assets + void PauseAssetCooking(); + + // Helper delegate used to determine if PauseAssetCooking can be executed. + bool CanPauseAssetCooking(); + + // Helper delegate used to get the current state of PauseAssetCooking. + bool IsAssetCookingPaused(); + + // Returns a pointer to the input choice types + TArray>* GetInputTypeChoiceLabels() { return &InputTypeChoiceLabels; }; + TArray>* GetBlueprintInputTypeChoiceLabels() { return &BlueprintInputTypeChoiceLabels; }; + + // Returns a pointer to the Houdini curve types + TArray>* GetHoudiniCurveTypeChoiceLabels() { return &HoudiniCurveTypeChoiceLabels; }; + + // Returns a pointer to the Houdini curve methods + TArray>* GetHoudiniCurveMethodChoiceLabels() { return &HoudiniCurveMethodChoiceLabels; }; + + // Returns a pointer to the Houdini ramp parameter interpolation methods + TArray>* GetHoudiniParameterRampInterpolationMethodLabels() {return &HoudiniParameterRampInterpolationLabels;} + + // Returns a pointer to the Houdini curve output export types + TArray>* GetHoudiniCurveOutputExportTypeLabels() { return &HoudiniCurveOutputExportTypeLabels; }; + + TArray>* GetHoudiniLandscapeOutputBakeOptionsLabels() { return &HoudiniLandscapeOutputBakeOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Type labels + TArray>* GetHoudiniEnginePDGBakeTypeOptionsLabels() { return &HoudiniEnginePDGBakeTypeOptionLabels; }; + + // Returns a pointer to the Houdini Engine Bake Type labels + TArray>* GetHoudiniEngineBakeTypeOptionsLabels() { return &HoudiniEngineBakeTypeOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Target labels + TArray>* GetHoudiniEnginePDGBakeSelectionOptionsLabels() { return &HoudiniEnginePDGBakeSelectionOptionLabels; }; + + // Returns a pointer to the Houdini Engine PDG Bake Package Replace Mode labels + TArray>* GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels() { return &HoudiniEnginePDGBakePackageReplaceModeOptionLabels; }; + + // Returns a shared Ptr to the Houdini logo + TSharedPtr GetHoudiniLogoBrush() const { return HoudiniLogoBrush; }; + TSharedPtr GetHoudiniEngineLogoBrush() const { return HoudiniEngineLogoBrush; }; + + // Functions Return a shared Ptr to the Houdini Engine UI Icon + TSharedPtr GetHoudiniEngineUIIconBrush() const { return HoudiniEngineUIIconBrush; } + TSharedPtr GetHoudiniEngineUIRebuildIconBrush() const { return HoudiniEngineUIRebuildIconBrush; } + TSharedPtr GetHoudiniEngineUIRecookIconBrush() const { return HoudiniEngineUIRecookIconBrush; } + TSharedPtr GetHoudiniEngineUIResetParametersIconBrush() const { return HoudiniEngineUIResetParametersIconBrush; } + + TSharedPtr GetHoudiniEngineUIBakeIconBrush() const { return HoudiniEngineUIBakeIconBrush; } + TSharedPtr GetHoudiniEngineUICookLogIconBrush() const { return HoudiniEngineUICookLogIconBrush; } + TSharedPtr GetHoudiniEngineUIAssetHelpIconBrush() const { return HoudiniEngineUIAssetHelpIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGIconBrush() const { return HoudiniEngineUIPDGIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGCancelIconBrush() const { return HoudiniEngineUIPDGCancelIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGDirtyAllIconBrush() const { return HoudiniEngineUIPDGDirtyAllIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGDirtyNodeIconBrush() const { return HoudiniEngineUIPDGDirtyNodeIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGPauseIconBrush() const { return HoudiniEngineUIPDGPauseIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGResetIconBrush() const { return HoudiniEngineUIPDGResetIconBrush; } + TSharedPtr GetHoudiniEngineUIPDGRefreshIconBrush() const { return HoudiniEngineUIPDGRefreshIconBrush; } + + // Returns a pointer to Unreal output curve types (for temporary) + TArray>* GetUnrealOutputCurveTypeLabels() { return &UnrealCurveOutputCurveTypeLabels; }; + + // returns string from Houdini Engine Bake Option + FString GetStringFromHoudiniEngineBakeOption(const EHoudiniEngineBakeOption & BakeOption); + + // returns string from Houdini Engine PDG Bake Target Option + FString GetStringFromPDGBakeTargetOption(const EPDGBakeSelectionOption& BakeOption); + + // returns string from PDG package replace mode option + FString GetStringFromPDGBakePackageReplaceModeOption(const EPDGBakePackageReplaceModeOption & InOption); + + // Return HoudiniEngineBakeOption from FString + const EHoudiniEngineBakeOption StringToHoudiniEngineBakeOption(const FString & InString); + + // Return EPDGBakeSelectionOption from FString + const EPDGBakeSelectionOption StringToPDGBakeSelectionOption(const FString& InString); + + // Return EPDGBakePackageReplaceModeOption from FString + const EPDGBakePackageReplaceModeOption StringToPDGBakePackageReplaceModeOption(const FString & InString); + + // Convert EPDGBakePackageReplaceModeOption to EPackageReplaceMode + // TODO: perhaps EPackageReplaceMode can be moved to HoudiniEngineRuntime to avoid having both + // TODO: EPDGBakePackageReplaceModeOption and EPackageReplaceMode? + const EPackageReplaceMode PDGBakePackageReplaceModeToPackageReplaceMode(const EPDGBakePackageReplaceModeOption& InReplaceMode); + + // Get the reference of the radio button folder circle point arrays reference + TArray & GetHoudiniParameterRadioButtonPointsOuter() { return HoudiniParameterRadioButtonPointsOuter; }; + TArray & GetHoudiniParameterRadioButtonPointsInner() { return HoudiniParameterRadioButtonPointsInner; }; + + // Gets the PostSaveWorldOnceHandle + FDelegateHandle& GetOnPostSaveWorldOnceHandle() { return PostSaveWorldOnceHandle; } + + protected: + + // Binds the commands used by the menus + void BindMenuCommands(); + + // Register AssetType action. + void RegisterAssetTypeAction(IAssetTools& AssetTools, TSharedRef< IAssetTypeActions > Action); + + // Add menu extension for our module. + void AddHoudiniFileMenuExtension(FMenuBuilder& MenuBuilder); + + // Add the Houdini Engine editor menu + void AddHoudiniEditorMenu(FMenuBarBuilder& MenuBarBuilder); + + // Add menu extension for our module. + void AddHoudiniMainMenuExtension(FMenuBuilder & MenuBuilder); + + // Adds the custom Houdini Engine commands to the world outliner context menu + void AddLevelViewportMenuExtender(); + + // Removes the custom Houdini Engine commands to the world outliner context menu + void RemoveLevelViewportMenuExtender(); + + // Returns all the custom Houdini Engine commands for the world outliner context menu + TSharedRef GetLevelViewportContextMenuExtender( + const TSharedRef CommandList, const TArray InActors); + + // Register all console commands provided by this module + void RegisterConsoleCommands(); + + // Unregister all registered console commands provided by this module + void UnregisterConsoleCommands(); + + // Register for any FEditorDelegates that we are interested in, such as + // PreSaveWorld and PreBeginPIE, for HoudiniStaticMesh -> UStaticMesh builds + void RegisterEditorDelegates(); + + // Deregister editor delegates + void UnregisterEditorDelegates(); + + // Process the OnDeleteActorsBegin call received from FEditorDelegates. + // Check if any AHoudiniAssetActors with PDG links are selected for deletion. If so, + // check if these still have temporary outputs and give the user to option to skip + // deleting the ones with temporary output. + void HandleOnDeleteActorsBegin(); + + // Re-select AHoudiniAssetActors that were deselected (to avoid deletion) by HandleOnDeleteActorsBegin + void HandleOnDeleteActorsEnd(); + + private: + + // Singleton instance of Houdini Engine Editor. + static FHoudiniEngineEditor * HoudiniEngineEditorInstance; + + // AssetType actions associated with Houdini asset. + TArray> AssetTypeActions; + + // Broker associated with Houdini asset. + TSharedPtr HoudiniAssetBroker; + + // Widget resources: Input Type combo box labels + TArray> InputTypeChoiceLabels; + TArray> BlueprintInputTypeChoiceLabels; + + // Widget resources: Houdini Curve Type combo box labels + TArray> HoudiniCurveTypeChoiceLabels; + + // Widget resources: Houdini Curve Method combo box labels + TArray> HoudiniCurveMethodChoiceLabels; + + // Widget resources: Houdini Ramp Interpolation method combo box labels + TArray> HoudiniParameterRampInterpolationLabels; + + // Widget resources: Houdini Curve Output type labels + TArray> HoudiniCurveOutputExportTypeLabels; + + // Widget resources: Unreal Curve type labels (for temporary, we need to figure out a way to access the output curve's info later) + TArray> UnrealCurveOutputCurveTypeLabels; + + // Widget resources: Landscape output Bake type labels + TArray> HoudiniLandscapeOutputBakeOptionLabels; + + // Widget resources: PDG Bake type labels + TArray> HoudiniEnginePDGBakeTypeOptionLabels; + + // Widget resources: Bake type labels + TArray> HoudiniEngineBakeTypeOptionLabels; + + // Widget resources: PDG Bake target labels + TArray> HoudiniEnginePDGBakeSelectionOptionLabels; + + // Widget resources: PDG Bake package replace mode labels + TArray> HoudiniEnginePDGBakePackageReplaceModeOptionLabels; + + // List of UI commands used by the various menus + TSharedPtr HEngineCommands; + + // Houdini logo brush. + TSharedPtr HoudiniLogoBrush; + // Houdini Engine logo brush + TSharedPtr HoudiniEngineLogoBrush; + + // houdini Engine UI Brushes + TSharedPtr HoudiniEngineUIIconBrush; + TSharedPtr HoudiniEngineUIRebuildIconBrush; + TSharedPtr HoudiniEngineUIRecookIconBrush; + TSharedPtr HoudiniEngineUIResetParametersIconBrush; + + TSharedPtr HoudiniEngineUIBakeIconBrush; + TSharedPtr HoudiniEngineUICookLogIconBrush; + TSharedPtr HoudiniEngineUIAssetHelpIconBrush; + TSharedPtr HoudiniEngineUIPDGIconBrush; + TSharedPtr HoudiniEngineUIPDGCancelIconBrush; + TSharedPtr HoudiniEngineUIPDGDirtyAllIconBrush; + TSharedPtr HoudiniEngineUIPDGDirtyNodeIconBrush; + TSharedPtr HoudiniEngineUIPDGPauseIconBrush; + TSharedPtr HoudiniEngineUIPDGResetIconBrush; + TSharedPtr HoudiniEngineUIPDGRefreshIconBrush; + + // The extender to pass to the level editor to extend it's File menu. + TSharedPtr MainMenuExtender; + + // The extender to pass to the level editor to extend it's Main menu. + //TSharedPtr FileMenuExtender; + + // DelegateHandle for the viewport's context menu extender + FDelegateHandle LevelViewportExtenderHandle; + + // SplineComponentVisualizer + TSharedPtr SplineComponentVisualizer; + + TSharedPtr HandleComponentVisualizer; + + // Array of HoudiniEngine console commands + TArray ConsoleCommands; + + // Delegate handle for the PreSaveWorld editor delegate + FDelegateHandle PreSaveWorldEditorDelegateHandle; + + // Delegate handle for the PostSaveWorld editor delegate: this + // is bound on PreSaveWorld with specific captures and then unbound + // by itself + FDelegateHandle PostSaveWorldOnceHandle; + + // Delegate handle for the PreBeginPIE editor delegate + FDelegateHandle PreBeginPIEEditorDelegateHandle; + + // Delegate handle for OnDeleteActorsBegin + FDelegateHandle OnDeleteActorsBegin; + + // Delegate handle for OnDeleteActorsEnd + FDelegateHandle OnDeleteActorsEnd; + + // List of actors that HandleOnDeleteActorsBegin marked to _not_ be deleted. This + // is used to re-select these actors in HandleOnDeleteActorsEnd. + TArray ActorsToReselectOnDeleteActorsEnd; + + // Cache the points of radio button folder circle points to avoid huge amount of repeat computation. + // (Computing points are time consuming since it uses trigonometric functions) + TArray HoudiniParameterRadioButtonPointsOuter; + TArray HoudiniParameterRadioButtonPointsInner; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h index a92998038..b6765739d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorPrivatePCH.h @@ -1,149 +1,149 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#define HOUDINI_ENGINE_EDITOR - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "Editor.h" - -// Details panel desired sizes. -#define HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH 270 -#define HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH 310 -#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH 350 -#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_FULL_WIDGET_WIDTH 400 - - // URL used for bug reporting. -#define HAPI_UNREAL_BUG_REPORT_URL TEXT("https://www.sidefx.com/bugs/submit/") -#define HAPI_UNREAL_ONLINE_DOC_URL TEXT("https://www.sidefx.com/docs/unreal/") -#define HAPI_UNREAL_ONLINE_FORUM_URL TEXT("https://www.sidefx.com/forum/51/") - - -// -// Parameter UI constants -// - -// Constants for parameter UI indentation - -// Change this constant to change the overall indentation width -#define INDENTATION_UNIT_WIDTH 20.0f -// Do not change this width unless the folder triangle arrow is customized. -#define NON_FOLDER_OFFSET_WIDTH 22.0f - - -// Houdini parameter UI row margin heights -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON 8.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP 2.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR 5.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP 57.20f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE 6.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3 7.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP 51.70f -#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER 2.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST 2.05f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY 62.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE 49.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM 36.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE 41.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE 68.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET 177.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE 235.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH 275.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD 219.35f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL 18.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3 7.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE 7.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL 4.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM 6.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR 1.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING 6.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF 6.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE 7.80f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE 10.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE 5.60f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID 0.0f - - - -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER 4.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER 2.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER 2.15f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_MULTIPARMHEADER 57.20f -#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE_MULTIPARMHEADER 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER 2.68f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER 2.60f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER 3.75f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_MULTIPARMHEADER 51.70f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE_MULTIPARMHEADER 12.90f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER 1.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER 58.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER 49.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER 40.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER 37.45f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER 68.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER 173.55f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER 231.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER 266.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER 215.05f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER 14.00f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER 2.57f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER 4.12f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER 10.40f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER 4.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER 0.75f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER 0.0f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER 1.95f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER 2.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER 2.50f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER 4.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER 10.30f -#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER 2.30f - -// Radio button UI constants -#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER 18 -#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER 8 -#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER 4.5f -#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER 1.0f -#define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X 7.0f +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#define HOUDINI_ENGINE_EDITOR + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Editor.h" + +// Details panel desired sizes. +#define HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH 270 +#define HAPI_UNREAL_DESIRED_ROW_FULL_WIDGET_WIDTH 310 +#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH 350 +#define HAPI_UNREAL_DESIRED_SETTINGS_ROW_FULL_WIDGET_WIDTH 400 + + // URL used for bug reporting. +#define HAPI_UNREAL_BUG_REPORT_URL TEXT("https://www.sidefx.com/bugs/submit/") +#define HAPI_UNREAL_ONLINE_DOC_URL TEXT("https://www.sidefx.com/docs/unreal/") +#define HAPI_UNREAL_ONLINE_FORUM_URL TEXT("https://www.sidefx.com/forum/51/") + + +// +// Parameter UI constants +// + +// Constants for parameter UI indentation + +// Change this constant to change the overall indentation width +#define INDENTATION_UNIT_WIDTH 20.0f +// Do not change this width unless the folder triangle arrow is customized. +#define NON_FOLDER_OFFSET_WIDTH 22.0f + + +// Houdini parameter UI row margin heights +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON 8.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP 2.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR 5.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP 57.20f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE 6.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3 7.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP 51.70f +#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER 2.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST 2.05f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY 62.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE 49.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM 36.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE 41.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE 68.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET 177.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE 235.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH 275.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD 219.35f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL 18.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3 7.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE 7.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL 4.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM 6.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR 1.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING 6.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF 6.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE 7.80f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE 10.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE 5.60f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID 0.0f + + + +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER 4.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER 2.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER 2.15f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_MULTIPARMHEADER 57.20f +#define Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE_MULTIPARMHEADER 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER 2.68f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER 2.60f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER 3.75f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_MULTIPARMHEADER 51.70f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE_MULTIPARMHEADER 12.90f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER 1.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER 58.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER 49.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER 40.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER 37.45f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER 68.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER 173.55f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER 231.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER 266.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER 215.05f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER 14.00f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER 2.57f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER 4.12f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER 10.40f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER 4.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER 0.75f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER 0.0f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER 1.95f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER 2.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER 2.50f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER 4.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER 10.30f +#define HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER 2.30f + +// Radio button UI constants +#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER 18 +#define HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER 8 +#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER 4.5f +#define HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER 1.0f +#define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X 7.0f #define HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_Y 13.2f \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp index b1f3e3212..0716324da 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp @@ -1,666 +1,666 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineEditorUtils.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniAssetActor.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniAsset.h" -#include "HoudiniOutput.h" -#include "HoudiniTool.h" - -#include "ContentBrowserModule.h" -#include "IContentBrowserSingleton.h" -#include "Engine/Selection.h" -#include "AssetRegistryModule.h" -#include "EditorViewportClient.h" -#include "ActorFactories/ActorFactory.h" -#include "FileHelpers.h" -#include "PropertyPathHelpers.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -int32 -FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection) -{ - ContentBrowserSelection.Empty(); - - // Get the current Content browser selection - FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >("ContentBrowser"); - TArray SelectedAssets; - ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets); - - for (int32 n = 0; n < SelectedAssets.Num(); n++) - { - // Get the current object - UObject * Object = SelectedAssets[n].GetAsset(); - if (!Object || Object->IsPendingKill()) - continue; - - // Only static meshes are supported - if (Object->GetClass() != UStaticMesh::StaticClass()) - continue; - - ContentBrowserSelection.Add(Object); - } - - return ContentBrowserSelection.Num(); -} - -int32 -FHoudiniEngineEditorUtils::GetWorldSelection(TArray& WorldSelection, bool bHoudiniAssetActorsOnly) -{ - WorldSelection.Empty(); - - // Get the current editor selection - if (GEditor) - { - USelection* SelectedActors = GEditor->GetSelectedActors(); - if (SelectedActors && !SelectedActors->IsPendingKill()) - { - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor * Actor = Cast(*It); - if (!IsValid(Actor)) - continue; - - // Ignore the SkySphere? - FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); - if (ClassName == TEXT("BP_Sky_Sphere_C")) - continue; - - // We're normally only selecting actors with StaticMeshComponents and SplineComponents - // Heightfields? Filter here or later? also allow HoudiniAssets? - WorldSelection.Add(Actor); - } - } - } - - // If we only want Houdini Actors... - if (bHoudiniAssetActorsOnly) - { - // ... remove all but them - for (int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx--) - { - AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) - WorldSelection.RemoveAt(Idx); - } - } - - return WorldSelection.Num(); -} - - -FString -FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType) -{ - FString HoudiniCurveTypeStr; - switch (HoudiniCurveType) - { - case EHoudiniCurveType::Invalid: - { - HoudiniCurveTypeStr = TEXT("Invalid"); - } - break; - - case EHoudiniCurveType::Polygon: - { - HoudiniCurveTypeStr = TEXT("Polygon"); - } - break; - - case EHoudiniCurveType::Nurbs: - { - HoudiniCurveTypeStr = TEXT("Nurbs"); - } - break; - - case EHoudiniCurveType::Bezier: - { - HoudiniCurveTypeStr = TEXT("Bezier"); - } - break; - - case EHoudiniCurveType::Points: - { - HoudiniCurveTypeStr = TEXT("Points"); - } - break; - } - - return HoudiniCurveTypeStr; -} - -FString -FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(const EHoudiniCurveMethod& CurveMethod) -{ - FString HoudiniCurveMethodStr; - switch (CurveMethod) - { - case EHoudiniCurveMethod::Invalid: - { - HoudiniCurveMethodStr = TEXT("Invalid"); - } - break; - case EHoudiniCurveMethod::CVs: - { - HoudiniCurveMethodStr = TEXT("CVs"); - } - break; - case EHoudiniCurveMethod::Breakpoints: - { - HoudiniCurveMethodStr = TEXT("Breakpoints"); - } - break; - case EHoudiniCurveMethod::Freehand: - { - HoudiniCurveMethodStr = TEXT("Freehand"); - } - break; - } - - return HoudiniCurveMethodStr; -} - - -FString -FHoudiniEngineEditorUtils::HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType) -{ - // Temporary, we need to figure out a way to access the output curve's info later - FString UnrealCurveType; - switch (HoudiniCurveType) - { - case EHoudiniCurveType::Polygon: - case EHoudiniCurveType::Points: - { - UnrealCurveType = TEXT("Linear"); - } - break; - - case EHoudiniCurveType::Nurbs: - case EHoudiniCurveType::Bezier: - { - UnrealCurveType = TEXT("Curve"); - } - break; - } - - return UnrealCurveType; -} - -FString -FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType) -{ - FString LandscapeBakeTypeString; - switch (LandscapeBakeType) - { - case EHoudiniLandscapeOutputBakeType::Detachment: - LandscapeBakeTypeString = "To Current Level"; - break; - - case EHoudiniLandscapeOutputBakeType::BakeToImage: - LandscapeBakeTypeString = "To Image"; - break; - - case EHoudiniLandscapeOutputBakeType::BakeToWorld: - LandscapeBakeTypeString = "To New World"; - break; - - } - - return LandscapeBakeTypeString; -} - - -FTransform -FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform() -{ - FTransform SpawnTransform = FTransform::Identity; - - // Get the editor viewport LookAt position to spawn the new objects - if (GEditor && GEditor->GetActiveViewport()) - { - FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); - if (ViewportClient) - { - // We need to toggle the orbit camera on to get the proper LookAtLocation to spawn our asset - ViewportClient->ToggleOrbitCamera(true); - SpawnTransform.SetLocation(ViewportClient->GetLookAtLocation()); - ViewportClient->ToggleOrbitCamera(false); - } - } - - return SpawnTransform; -} - -FTransform -FHoudiniEngineEditorUtils::GetMeanWorldSelectionTransform() -{ - FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); - - if (GEditor && (GEditor->GetSelectedActorCount() > 0)) - { - // Get the current Level Editor Selection - USelection* SelectedActors = GEditor->GetSelectedActors(); - - int NumAppliedTransform = 0; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor * Actor = Cast< AActor >(*It); - if (!Actor) - continue; - - // Just Ignore the SkySphere... - FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); - if (ClassName == TEXT("BP_Sky_Sphere_C")) - continue; - - FTransform CurrentTransform = Actor->GetTransform(); - - ALandscapeProxy* Landscape = Cast< ALandscapeProxy >(Actor); - if (Landscape) - { - // We need to offset Landscape's transform in X/Y to center them properly - FVector Origin, Extent; - Actor->GetActorBounds(false, Origin, Extent); - - // Use the origin's XY Position - FVector Location = CurrentTransform.GetLocation(); - Location.X = Origin.X; - Location.Y = Origin.Y; - CurrentTransform.SetLocation(Location); - } - - // Accumulate all the actor transforms... - if (NumAppliedTransform == 0) - SpawnTransform = CurrentTransform; - else - SpawnTransform.Accumulate(CurrentTransform); - - NumAppliedTransform++; - } - - if (NumAppliedTransform > 0) - { - // "Mean" all the accumulated Transform - SpawnTransform.SetScale3D(FVector::OneVector); - SpawnTransform.NormalizeRotation(); - - if (NumAppliedTransform > 1) - SpawnTransform.SetLocation(SpawnTransform.GetLocation() / (float)NumAppliedTransform); - } - } - - return SpawnTransform; -} - -void -FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType) -{ - if (!InHoudiniAsset) - return; - - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - // Load the asset - UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); - if (!AssetObj) - return; - - // Get the asset Factory - UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); - if (!Factory) - return; - - // Get the current Level Editor Selection - TArray WorldSelection; - int32 WorldSelectionCount = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection); - - // Get the current Content browser selection - TArray ContentBrowserSelection; - int32 ContentBrowserSelectionCount = FHoudiniEngineEditorUtils::GetContentBrowserSelection(ContentBrowserSelection); - - // By default, Content browser selection has a priority over the world selection - bool UseCBSelection = ContentBrowserSelectionCount > 0; - if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY) - UseCBSelection = true; - else if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY) - UseCBSelection = false; - - // Modify the created actor's position from the current editor world selection - FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); - if (WorldSelectionCount > 0) - { - // Get the "mean" transform of all the selected actors - SpawnTransform = GetMeanWorldSelectionTransform(); - } - - // If the current tool is a batch one, we'll need to create multiple instances of the HDA - if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH) - { - // Unselect the current selection to select the created actor after - if (GEditor) - GEditor->SelectNone(true, true, false); - - // An instance of the asset will be created for each selected object - for (int32 SelecIndex = 0; SelecIndex < (UseCBSelection ? ContentBrowserSelectionCount : WorldSelectionCount); SelecIndex++) - { - // Get the current object - UObject* CurrentSelectedObject = nullptr; - if (UseCBSelection && ContentBrowserSelection.IsValidIndex(SelecIndex)) - CurrentSelectedObject = ContentBrowserSelection[SelecIndex]; - - if (!UseCBSelection && WorldSelection.IsValidIndex(SelecIndex)) - CurrentSelectedObject = WorldSelection[SelecIndex]; - - if (!CurrentSelectedObject) - continue; - - // If it's an actor, use its Transform to spawn the HDA - AActor* CurrentSelectedActor = Cast(CurrentSelectedObject); - if (CurrentSelectedActor) - SpawnTransform = CurrentSelectedActor->GetTransform(); - else - SpawnTransform = GetDefaulAssetSpawnTransform(); - - // Create the actor for the HDA - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); - if (!CreatedActor) - continue; - - // Get the HoudiniAssetActor / HoudiniAssetComponent we just created - AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; - if (!HoudiniAssetActor) - continue; - - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent) - continue; - - // Create and set the input preset for this HDA and selected Object - TMap InputPreset; - InputPreset.Add(CurrentSelectedObject, 0); - HoudiniAssetComponent->SetInputPresets(InputPreset); - - // Select the Actor we just created - if (GEditor && GEditor->CanSelectActor(CreatedActor, true, false)) - GEditor->SelectActor(CreatedActor, true, true, true); - } - } - else - { - // We only need to create a single instance of the asset, regarding of the selection - AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); - if (!CreatedActor) - return; - - // Generator tools don't need to preset their input - if (InType != EHoudiniToolType::HTOOLTYPE_GENERATOR) - { - TMap InputPresets; - AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; - UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor ? HoudiniAssetActor->GetHoudiniAssetComponent() : nullptr; - if (HoudiniAssetComponent) - { - // Build the preset map - int InputIndex = 0; - for (auto CurrentObject : (UseCBSelection ? ContentBrowserSelection : WorldSelection)) - { - if (!CurrentObject) - continue; - - if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI) - { - // The selection will be applied individually to multiple inputs - // (first object to first input, second object to second input etc...) - InputPresets.Add(CurrentObject, InputIndex++); - } - else - { - // All the selection will be applied to the asset's first input - InputPresets.Add(CurrentObject, 0); - } - } - - // Set the input preset on the HoudiniAssetComponent - if (InputPresets.Num() > 0) - HoudiniAssetComponent->SetInputPresets(InputPresets); - } - } - - // Select the Actor we just created - if (GEditor->CanSelectActor(CreatedActor, true, true)) - { - GEditor->SelectNone(true, true, false); - GEditor->SelectActor(CreatedActor, true, true, true); - } - } -} - -AActor* -FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform, UWorld* InSpawnInWorld, ULevel* InSpawnInLevelOverride) -{ - if (!InHoudiniAsset) - return nullptr; - - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - - // Load the asset - UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); - if (!AssetObj) - return nullptr; - - // Get the asset Factory - UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); - if (!Factory) - return nullptr; - - // Determine the level to spawn in from the supplied parameters - // InSpawnInLevelOverride if valid, else if InSpawnInWorld is valid its current level - // lastly, get the editor world's current level - ULevel* LevelToSpawnIn = InSpawnInLevelOverride; - if (!IsValid(LevelToSpawnIn)) - { - if (IsValid(InSpawnInWorld)) - LevelToSpawnIn = InSpawnInWorld->GetCurrentLevel(); - else - LevelToSpawnIn = GEditor->GetEditorWorldContext().World()->GetCurrentLevel(); - } - - // We only need to create a single instance of the asset, regarding of the selection - AActor* CreatedActor = Factory->CreateActor(AssetObj, LevelToSpawnIn, InTransform); - if (!CreatedActor) - return nullptr; - - // Select the Actor we just created - if (GEditor->CanSelectActor(CreatedActor, true, true)) - { - GEditor->SelectNone(true, true, false); - GEditor->SelectActor(CreatedActor, true, true, true); - } - - return CreatedActor; -} - - -void -FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) -{ - // Add a slate notification - FString Notification = TEXT("Saving all Houdini temporary cook data..."); - // FHoudiniEngineUtils::CreateSlateNotification(Notification); - - TArray PackagesToSave; - for (TObjectIterator Itr; Itr; ++Itr) - { - UHoudiniAssetComponent * HAC = *Itr; - if (!HAC || HAC->IsPendingKill()) - continue; - - if (InSaveWorld && InSaveWorld != HAC->GetWorld()) - continue; - - const int32 NumOutputs = HAC->GetNumOutputs(); - for (int32 Index = 0; Index < NumOutputs; ++Index) - { - UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) - continue; - - // TODO: Also save landscape layer info objects? - if (Output->GetType() != EHoudiniOutputType::Mesh) - continue; - - for (auto &OutputObjectPair : Output->GetOutputObjects()) - { - UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) - continue; - - UStaticMesh *SM = Cast(Obj); - if (!SM) - continue; - - UPackage *Package = SM->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - - for (auto& MaterialAssignementPair : Output->GetAssignementMaterials()) - { - UMaterialInterface* MatInterface = MaterialAssignementPair.Value; - if (!MatInterface || MatInterface->IsPendingKill()) - continue; - - UPackage *Package = MatInterface->GetOutermost(); - if (!Package || Package->IsPendingKill()) - continue; - - if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) - { - PackagesToSave.Add(Package); - } - } - } - } - - UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); -} - -void -FHoudiniEngineEditorUtils::ReselectSelectedActors() -{ - // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? - USelection* Selection = GEditor->GetSelectedActors(); - TArray SelectedActors; - SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); - Selection->GetSelectedObjects(SelectedActors); - - GEditor->SelectNone(false, false, false); - - for (AActor* NextSelected : SelectedActors) - { - GEditor->SelectActor(NextSelected, true, true, true, true); - } -} - -FString -FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding, const TCHAR PathSep) -{ - int32 Depth = 0; - for (const TCHAR Char : InNodePath) - { - if (Char == PathSep) - Depth++; - } - FString Trimmed = InNodeName; - Trimmed.TrimStartInline(); - return Trimmed.LeftPad(Trimmed.Len() + (Depth * Padding)); -} - -void -FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject) -{ - if (!IsValid(InRootObject)) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty]: InRootObject is null.")); - return; - } - - const FCachedPropertyPath CachedPath(InPropertyPath.ToString()); - if (CachedPath.Resolve(InRootObject)) - { - // Notify that we have changed the property - // FPropertyChangedEvent Evt = CachedPath.ToPropertyChangedEvent(EPropertyChangeType::Unspecified); - // Construct FPropertyChangedEvent from the cached property path - const int32 NumSegments = CachedPath.GetNumSegments(); - FPropertyChangedEvent Evt( - CastFieldChecked(CachedPath.GetLastSegment().GetField().ToField()), - EPropertyChangeType::Unspecified, - { InRootObject }); - - if(NumSegments > 1) - { - Evt.SetActiveMemberProperty(CastFieldChecked(CachedPath.GetSegment(NumSegments - 2).GetField().ToField())); - } - - // Set the array of indices to the changed property - TArray> ArrayIndicesPerObject; - ArrayIndicesPerObject.AddDefaulted(1); - for (int32 SegmentIdx = 0; SegmentIdx < NumSegments; ++SegmentIdx) - { - const FPropertyPathSegment& Segment = CachedPath.GetSegment(SegmentIdx); - const int32 ArrayIndex = Segment.GetArrayIndex(); - if (ArrayIndex != INDEX_NONE) - { - ArrayIndicesPerObject[0].Add(Segment.GetName().ToString(), ArrayIndex); - } - } - Evt.SetArrayIndexPerObject(ArrayIndicesPerObject); - - FEditPropertyChain Chain; - CachedPath.ToEditPropertyChain(Chain); - FPropertyChangedChainEvent ChainEvent(Chain, Evt); - ChainEvent.ObjectIteratorIndex = 0; - InRootObject->PostEditChangeChainProperty(ChainEvent); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could not resolve property path '%s' on %s."), *InPropertyPath.ToString(), *(InRootObject->GetFullName())); - } -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineEditorUtils.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniAssetActor.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniAsset.h" +#include "HoudiniOutput.h" +#include "HoudiniTool.h" + +#include "ContentBrowserModule.h" +#include "IContentBrowserSingleton.h" +#include "Engine/Selection.h" +#include "AssetRegistryModule.h" +#include "EditorViewportClient.h" +#include "ActorFactories/ActorFactory.h" +#include "FileHelpers.h" +#include "PropertyPathHelpers.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +int32 +FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection) +{ + ContentBrowserSelection.Empty(); + + // Get the current Content browser selection + FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >("ContentBrowser"); + TArray SelectedAssets; + ContentBrowserModule.Get().GetSelectedAssets(SelectedAssets); + + for (int32 n = 0; n < SelectedAssets.Num(); n++) + { + // Get the current object + UObject * Object = SelectedAssets[n].GetAsset(); + if (!Object || Object->IsPendingKill()) + continue; + + // Only static meshes are supported + if (Object->GetClass() != UStaticMesh::StaticClass()) + continue; + + ContentBrowserSelection.Add(Object); + } + + return ContentBrowserSelection.Num(); +} + +int32 +FHoudiniEngineEditorUtils::GetWorldSelection(TArray& WorldSelection, bool bHoudiniAssetActorsOnly) +{ + WorldSelection.Empty(); + + // Get the current editor selection + if (GEditor) + { + USelection* SelectedActors = GEditor->GetSelectedActors(); + if (SelectedActors && !SelectedActors->IsPendingKill()) + { + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor * Actor = Cast(*It); + if (!IsValid(Actor)) + continue; + + // Ignore the SkySphere? + FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); + if (ClassName == TEXT("BP_Sky_Sphere_C")) + continue; + + // We're normally only selecting actors with StaticMeshComponents and SplineComponents + // Heightfields? Filter here or later? also allow HoudiniAssets? + WorldSelection.Add(Actor); + } + } + } + + // If we only want Houdini Actors... + if (bHoudiniAssetActorsOnly) + { + // ... remove all but them + for (int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx--) + { + AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); + if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + WorldSelection.RemoveAt(Idx); + } + } + + return WorldSelection.Num(); +} + + +FString +FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType) +{ + FString HoudiniCurveTypeStr; + switch (HoudiniCurveType) + { + case EHoudiniCurveType::Invalid: + { + HoudiniCurveTypeStr = TEXT("Invalid"); + } + break; + + case EHoudiniCurveType::Polygon: + { + HoudiniCurveTypeStr = TEXT("Polygon"); + } + break; + + case EHoudiniCurveType::Nurbs: + { + HoudiniCurveTypeStr = TEXT("Nurbs"); + } + break; + + case EHoudiniCurveType::Bezier: + { + HoudiniCurveTypeStr = TEXT("Bezier"); + } + break; + + case EHoudiniCurveType::Points: + { + HoudiniCurveTypeStr = TEXT("Points"); + } + break; + } + + return HoudiniCurveTypeStr; +} + +FString +FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(const EHoudiniCurveMethod& CurveMethod) +{ + FString HoudiniCurveMethodStr; + switch (CurveMethod) + { + case EHoudiniCurveMethod::Invalid: + { + HoudiniCurveMethodStr = TEXT("Invalid"); + } + break; + case EHoudiniCurveMethod::CVs: + { + HoudiniCurveMethodStr = TEXT("CVs"); + } + break; + case EHoudiniCurveMethod::Breakpoints: + { + HoudiniCurveMethodStr = TEXT("Breakpoints"); + } + break; + case EHoudiniCurveMethod::Freehand: + { + HoudiniCurveMethodStr = TEXT("Freehand"); + } + break; + } + + return HoudiniCurveMethodStr; +} + + +FString +FHoudiniEngineEditorUtils::HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType) +{ + // Temporary, we need to figure out a way to access the output curve's info later + FString UnrealCurveType; + switch (HoudiniCurveType) + { + case EHoudiniCurveType::Polygon: + case EHoudiniCurveType::Points: + { + UnrealCurveType = TEXT("Linear"); + } + break; + + case EHoudiniCurveType::Nurbs: + case EHoudiniCurveType::Bezier: + { + UnrealCurveType = TEXT("Curve"); + } + break; + } + + return UnrealCurveType; +} + +FString +FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType) +{ + FString LandscapeBakeTypeString; + switch (LandscapeBakeType) + { + case EHoudiniLandscapeOutputBakeType::Detachment: + LandscapeBakeTypeString = "To Current Level"; + break; + + case EHoudiniLandscapeOutputBakeType::BakeToImage: + LandscapeBakeTypeString = "To Image"; + break; + + case EHoudiniLandscapeOutputBakeType::BakeToWorld: + LandscapeBakeTypeString = "To New World"; + break; + + } + + return LandscapeBakeTypeString; +} + + +FTransform +FHoudiniEngineEditorUtils::GetDefaulAssetSpawnTransform() +{ + FTransform SpawnTransform = FTransform::Identity; + + // Get the editor viewport LookAt position to spawn the new objects + if (GEditor && GEditor->GetActiveViewport()) + { + FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); + if (ViewportClient) + { + // We need to toggle the orbit camera on to get the proper LookAtLocation to spawn our asset + ViewportClient->ToggleOrbitCamera(true); + SpawnTransform.SetLocation(ViewportClient->GetLookAtLocation()); + ViewportClient->ToggleOrbitCamera(false); + } + } + + return SpawnTransform; +} + +FTransform +FHoudiniEngineEditorUtils::GetMeanWorldSelectionTransform() +{ + FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); + + if (GEditor && (GEditor->GetSelectedActorCount() > 0)) + { + // Get the current Level Editor Selection + USelection* SelectedActors = GEditor->GetSelectedActors(); + + int NumAppliedTransform = 0; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor * Actor = Cast< AActor >(*It); + if (!Actor) + continue; + + // Just Ignore the SkySphere... + FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); + if (ClassName == TEXT("BP_Sky_Sphere_C")) + continue; + + FTransform CurrentTransform = Actor->GetTransform(); + + ALandscapeProxy* Landscape = Cast< ALandscapeProxy >(Actor); + if (Landscape) + { + // We need to offset Landscape's transform in X/Y to center them properly + FVector Origin, Extent; + Actor->GetActorBounds(false, Origin, Extent); + + // Use the origin's XY Position + FVector Location = CurrentTransform.GetLocation(); + Location.X = Origin.X; + Location.Y = Origin.Y; + CurrentTransform.SetLocation(Location); + } + + // Accumulate all the actor transforms... + if (NumAppliedTransform == 0) + SpawnTransform = CurrentTransform; + else + SpawnTransform.Accumulate(CurrentTransform); + + NumAppliedTransform++; + } + + if (NumAppliedTransform > 0) + { + // "Mean" all the accumulated Transform + SpawnTransform.SetScale3D(FVector::OneVector); + SpawnTransform.NormalizeRotation(); + + if (NumAppliedTransform > 1) + SpawnTransform.SetLocation(SpawnTransform.GetLocation() / (float)NumAppliedTransform); + } + } + + return SpawnTransform; +} + +void +FHoudiniEngineEditorUtils::InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType) +{ + if (!InHoudiniAsset) + return; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + // Load the asset + UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); + if (!AssetObj) + return; + + // Get the asset Factory + UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); + if (!Factory) + return; + + // Get the current Level Editor Selection + TArray WorldSelection; + int32 WorldSelectionCount = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection); + + // Get the current Content browser selection + TArray ContentBrowserSelection; + int32 ContentBrowserSelectionCount = FHoudiniEngineEditorUtils::GetContentBrowserSelection(ContentBrowserSelection); + + // By default, Content browser selection has a priority over the world selection + bool UseCBSelection = ContentBrowserSelectionCount > 0; + if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY) + UseCBSelection = true; + else if (InSelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY) + UseCBSelection = false; + + // Modify the created actor's position from the current editor world selection + FTransform SpawnTransform = GetDefaulAssetSpawnTransform(); + if (WorldSelectionCount > 0) + { + // Get the "mean" transform of all the selected actors + SpawnTransform = GetMeanWorldSelectionTransform(); + } + + // If the current tool is a batch one, we'll need to create multiple instances of the HDA + if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH) + { + // Unselect the current selection to select the created actor after + if (GEditor) + GEditor->SelectNone(true, true, false); + + // An instance of the asset will be created for each selected object + for (int32 SelecIndex = 0; SelecIndex < (UseCBSelection ? ContentBrowserSelectionCount : WorldSelectionCount); SelecIndex++) + { + // Get the current object + UObject* CurrentSelectedObject = nullptr; + if (UseCBSelection && ContentBrowserSelection.IsValidIndex(SelecIndex)) + CurrentSelectedObject = ContentBrowserSelection[SelecIndex]; + + if (!UseCBSelection && WorldSelection.IsValidIndex(SelecIndex)) + CurrentSelectedObject = WorldSelection[SelecIndex]; + + if (!CurrentSelectedObject) + continue; + + // If it's an actor, use its Transform to spawn the HDA + AActor* CurrentSelectedActor = Cast(CurrentSelectedObject); + if (CurrentSelectedActor) + SpawnTransform = CurrentSelectedActor->GetTransform(); + else + SpawnTransform = GetDefaulAssetSpawnTransform(); + + // Create the actor for the HDA + AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); + if (!CreatedActor) + continue; + + // Get the HoudiniAssetActor / HoudiniAssetComponent we just created + AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; + if (!HoudiniAssetActor) + continue; + + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); + if (!HoudiniAssetComponent) + continue; + + // Create and set the input preset for this HDA and selected Object + TMap InputPreset; + InputPreset.Add(CurrentSelectedObject, 0); + HoudiniAssetComponent->SetInputPresets(InputPreset); + + // Select the Actor we just created + if (GEditor && GEditor->CanSelectActor(CreatedActor, true, false)) + GEditor->SelectActor(CreatedActor, true, true, true); + } + } + else + { + // We only need to create a single instance of the asset, regarding of the selection + AActor* CreatedActor = Factory->CreateActor(AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform); + if (!CreatedActor) + return; + + // Generator tools don't need to preset their input + if (InType != EHoudiniToolType::HTOOLTYPE_GENERATOR) + { + TMap InputPresets; + AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; + UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor ? HoudiniAssetActor->GetHoudiniAssetComponent() : nullptr; + if (HoudiniAssetComponent) + { + // Build the preset map + int InputIndex = 0; + for (auto CurrentObject : (UseCBSelection ? ContentBrowserSelection : WorldSelection)) + { + if (!CurrentObject) + continue; + + if (InType == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI) + { + // The selection will be applied individually to multiple inputs + // (first object to first input, second object to second input etc...) + InputPresets.Add(CurrentObject, InputIndex++); + } + else + { + // All the selection will be applied to the asset's first input + InputPresets.Add(CurrentObject, 0); + } + } + + // Set the input preset on the HoudiniAssetComponent + if (InputPresets.Num() > 0) + HoudiniAssetComponent->SetInputPresets(InputPresets); + } + } + + // Select the Actor we just created + if (GEditor->CanSelectActor(CreatedActor, true, true)) + { + GEditor->SelectNone(true, true, false); + GEditor->SelectActor(CreatedActor, true, true, true); + } + } +} + +AActor* +FHoudiniEngineEditorUtils::InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform, UWorld* InSpawnInWorld, ULevel* InSpawnInLevelOverride) +{ + if (!InHoudiniAsset) + return nullptr; + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + + // Load the asset + UObject* AssetObj = Cast(InHoudiniAsset);//InHoudiniAsset->LoadSynchronous(); + if (!AssetObj) + return nullptr; + + // Get the asset Factory + UActorFactory* Factory = GEditor->FindActorFactoryForActorClass(AHoudiniAssetActor::StaticClass()); + if (!Factory) + return nullptr; + + // Determine the level to spawn in from the supplied parameters + // InSpawnInLevelOverride if valid, else if InSpawnInWorld is valid its current level + // lastly, get the editor world's current level + ULevel* LevelToSpawnIn = InSpawnInLevelOverride; + if (!IsValid(LevelToSpawnIn)) + { + if (IsValid(InSpawnInWorld)) + LevelToSpawnIn = InSpawnInWorld->GetCurrentLevel(); + else + LevelToSpawnIn = GEditor->GetEditorWorldContext().World()->GetCurrentLevel(); + } + + // We only need to create a single instance of the asset, regarding of the selection + AActor* CreatedActor = Factory->CreateActor(AssetObj, LevelToSpawnIn, InTransform); + if (!CreatedActor) + return nullptr; + + // Select the Actor we just created + if (GEditor->CanSelectActor(CreatedActor, true, true)) + { + GEditor->SelectNone(true, true, false); + GEditor->SelectActor(CreatedActor, true, true, true); + } + + return CreatedActor; +} + + +void +FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) +{ + // Add a slate notification + FString Notification = TEXT("Saving all Houdini temporary cook data..."); + // FHoudiniEngineUtils::CreateSlateNotification(Notification); + + TArray PackagesToSave; + for (TObjectIterator Itr; Itr; ++Itr) + { + UHoudiniAssetComponent * HAC = *Itr; + if (!HAC || HAC->IsPendingKill()) + continue; + + if (InSaveWorld && InSaveWorld != HAC->GetWorld()) + continue; + + const int32 NumOutputs = HAC->GetNumOutputs(); + for (int32 Index = 0; Index < NumOutputs; ++Index) + { + UHoudiniOutput *Output = HAC->GetOutputAt(Index); + if (!Output || Output->IsPendingKill()) + continue; + + // TODO: Also save landscape layer info objects? + if (Output->GetType() != EHoudiniOutputType::Mesh) + continue; + + for (auto &OutputObjectPair : Output->GetOutputObjects()) + { + UObject *Obj = OutputObjectPair.Value.OutputObject; + if (!Obj || Obj->IsPendingKill()) + continue; + + UStaticMesh *SM = Cast(Obj); + if (!SM) + continue; + + UPackage *Package = SM->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + + for (auto& MaterialAssignementPair : Output->GetAssignementMaterials()) + { + UMaterialInterface* MatInterface = MaterialAssignementPair.Value; + if (!MatInterface || MatInterface->IsPendingKill()) + continue; + + UPackage *Package = MatInterface->GetOutermost(); + if (!Package || Package->IsPendingKill()) + continue; + + if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) + { + PackagesToSave.Add(Package); + } + } + } + } + + UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true); +} + +void +FHoudiniEngineEditorUtils::ReselectSelectedActors() +{ + // TODO: Duplicate with FHoudiniEngineUtils::UpdateEditorProperties ?? + USelection* Selection = GEditor->GetSelectedActors(); + TArray SelectedActors; + SelectedActors.SetNumUninitialized(GEditor->GetSelectedActorCount()); + Selection->GetSelectedObjects(SelectedActors); + + GEditor->SelectNone(false, false, false); + + for (AActor* NextSelected : SelectedActors) + { + GEditor->SelectActor(NextSelected, true, true, true, true); + } +} + +FString +FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding, const TCHAR PathSep) +{ + int32 Depth = 0; + for (const TCHAR Char : InNodePath) + { + if (Char == PathSep) + Depth++; + } + FString Trimmed = InNodeName; + Trimmed.TrimStartInline(); + return Trimmed.LeftPad(Trimmed.Len() + (Depth * Padding)); +} + +void +FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject) +{ + if (!IsValid(InRootObject)) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty]: InRootObject is null.")); + return; + } + + const FCachedPropertyPath CachedPath(InPropertyPath.ToString()); + if (CachedPath.Resolve(InRootObject)) + { + // Notify that we have changed the property + // FPropertyChangedEvent Evt = CachedPath.ToPropertyChangedEvent(EPropertyChangeType::Unspecified); + // Construct FPropertyChangedEvent from the cached property path + const int32 NumSegments = CachedPath.GetNumSegments(); + FPropertyChangedEvent Evt( + CastFieldChecked(CachedPath.GetLastSegment().GetField().ToField()), + EPropertyChangeType::Unspecified, + { InRootObject }); + + if(NumSegments > 1) + { + Evt.SetActiveMemberProperty(CastFieldChecked(CachedPath.GetSegment(NumSegments - 2).GetField().ToField())); + } + + // Set the array of indices to the changed property + TArray> ArrayIndicesPerObject; + ArrayIndicesPerObject.AddDefaulted(1); + for (int32 SegmentIdx = 0; SegmentIdx < NumSegments; ++SegmentIdx) + { + const FPropertyPathSegment& Segment = CachedPath.GetSegment(SegmentIdx); + const int32 ArrayIndex = Segment.GetArrayIndex(); + if (ArrayIndex != INDEX_NONE) + { + ArrayIndicesPerObject[0].Add(Segment.GetName().ToString(), ArrayIndex); + } + } + Evt.SetArrayIndexPerObject(ArrayIndicesPerObject); + + FEditPropertyChain Chain; + CachedPath.ToEditPropertyChain(Chain); + FPropertyChangedChainEvent ChainEvent(Chain, Evt); + ChainEvent.ObjectIteratorIndex = 0; + InRootObject->PostEditChangeChainProperty(ChainEvent); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could not resolve property path '%s' on %s."), *InPropertyPath.ToString(), *(InRootObject->GetFullName())); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h index f1a03b69b..e8059bb12 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h @@ -1,88 +1,90 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -class FString; -class UObject; -class UHoudiniAsset; - -enum class EHoudiniCurveType : int8; -enum class EHoudiniCurveMethod : int8; -enum class EHoudiniLandscapeOutputBakeType : uint8; -enum class EHoudiniToolType : uint8; -enum class EHoudiniToolSelectionType : uint8; - -struct FHoudiniEngineEditorUtils -{ -public: - - // Triggers an update the details panel - //static void UpdateEditorProperties(UObject* ObjectToUpdate); - - // Helper function for accessing the current CB selection - static int32 GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection); - - // Helper function for accessing the current world selection - static int32 GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly = false); - - static FString HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType); - - static FString HoudiniCurveMethodToString(const EHoudiniCurveMethod& HoudinCurveMethod); - - static FString HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType); - - // (for temporary, we need to figure out a way to access the output curve's info later) - static FString HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType); - - static FTransform GetDefaulAssetSpawnTransform(); - - static FTransform GetMeanWorldSelectionTransform(); - - // Instantiate a HoudiniAsset at a given position. If InSpawnInLevelOverride is non-null, spawns in that level. - // Otherwise if InSpawnInWorld, spawns in the current level of InSpawnInWorld. Lastly, if both are null, spawns - // in the current level of the editor context world. - static AActor* InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform, UWorld* InSpawnInWorld=nullptr, ULevel* InSpawnInLevelOverride=nullptr); - - // Instantiate the given HDA, and handles the current CB/World selection - static void InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType); - - // Helper function used to save all temporary packages when the level is saved - static void SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld); - - // Deselect and reselect all selected actors to get rid of component not showing bug after create. - static void ReselectSelectedActors(); - - // Gets the node name indent from the left with the specified number of spaces based on the path depth. - static FString GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding=4, const TCHAR PathSep='/'); - - // Property change notifications - // Call PostEditChangeChainProperty on InRootObject for the property at InPropertyPath relative to - // InRootObject. - static void NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +class FString; +class UObject; +class UHoudiniAsset; +class AActor; +class ULevel; + +enum class EHoudiniCurveType : int8; +enum class EHoudiniCurveMethod : int8; +enum class EHoudiniLandscapeOutputBakeType : uint8; +enum class EHoudiniToolType : uint8; +enum class EHoudiniToolSelectionType : uint8; + +struct FHoudiniEngineEditorUtils +{ +public: + + // Triggers an update the details panel + //static void UpdateEditorProperties(UObject* ObjectToUpdate); + + // Helper function for accessing the current CB selection + static int32 GetContentBrowserSelection(TArray< UObject* >& ContentBrowserSelection); + + // Helper function for accessing the current world selection + static int32 GetWorldSelection(TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly = false); + + static FString HoudiniCurveTypeToString(const EHoudiniCurveType& HoudiniCurveType); + + static FString HoudiniCurveMethodToString(const EHoudiniCurveMethod& HoudinCurveMethod); + + static FString HoudiniLandscapeOutputBakeTypeToString(const EHoudiniLandscapeOutputBakeType& LandscapeBakeType); + + // (for temporary, we need to figure out a way to access the output curve's info later) + static FString HoudiniCurveMethodToUnrealCurveTypeString(const EHoudiniCurveType& HoudiniCurveType); + + static FTransform GetDefaulAssetSpawnTransform(); + + static FTransform GetMeanWorldSelectionTransform(); + + // Instantiate a HoudiniAsset at a given position. If InSpawnInLevelOverride is non-null, spawns in that level. + // Otherwise if InSpawnInWorld, spawns in the current level of InSpawnInWorld. Lastly, if both are null, spawns + // in the current level of the editor context world. + static AActor* InstantiateHoudiniAssetAt(UHoudiniAsset* InHoudiniAsset, const FTransform& InTransform, UWorld* InSpawnInWorld=nullptr, ULevel* InSpawnInLevelOverride=nullptr); + + // Instantiate the given HDA, and handles the current CB/World selection + static void InstantiateHoudiniAsset(UHoudiniAsset* InHoudiniAsset, const EHoudiniToolType& InType, const EHoudiniToolSelectionType& InSelectionType); + + // Helper function used to save all temporary packages when the level is saved + static void SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld); + + // Deselect and reselect all selected actors to get rid of component not showing bug after create. + static void ReselectSelectedActors(); + + // Gets the node name indent from the left with the specified number of spaces based on the path depth. + static FString GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding=4, const TCHAR PathSep='/'); + + // Property change notifications + // Call PostEditChangeChainProperty on InRootObject for the property at InPropertyPath relative to + // InRootObject. + static void NotifyPostEditChangeProperty(FName InPropertyPath, UObject* InRootObject); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp index 1ebf3ed4c..a8c45c221 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp @@ -1,311 +1,311 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineStyle.h" - -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineUtils.h" - -#include "EditorStyleSet.h" -#include "Styling/SlateStyleRegistry.h" -#include "Styling/SlateTypes.h" -#include "SlateOptMacros.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) -#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) -#define TTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) -#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) -#define OTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) - -TSharedPtr FHoudiniEngineStyle::StyleSet = nullptr; - -TSharedPtr -FHoudiniEngineStyle::Get() -{ - return StyleSet; -} - -FName -FHoudiniEngineStyle::GetStyleSetName() -{ - static FName HoudiniStyleName(TEXT("HoudiniEngineStyle")); - return HoudiniStyleName; -} - -BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION - -void -FHoudiniEngineStyle::Initialize() -{ - // Only register the StyleSet once - if (StyleSet.IsValid()) - return; - - StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName())); - StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); - StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); - - // Note, these sizes are in Slate Units. - // Slate Units do NOT have to map to pixels. - const FVector2D Icon5x16(5.0f, 16.0f); - const FVector2D Icon8x4(8.0f, 4.0f); - const FVector2D Icon8x8(8.0f, 8.0f); - const FVector2D Icon10x10(10.0f, 10.0f); - const FVector2D Icon12x12(12.0f, 12.0f); - const FVector2D Icon12x16(12.0f, 16.0f); - const FVector2D Icon14x14(14.0f, 14.0f); - const FVector2D Icon16x16(16.0f, 16.0f); - const FVector2D Icon20x20(20.0f, 20.0f); - const FVector2D Icon22x22(22.0f, 22.0f); - const FVector2D Icon24x24(24.0f, 24.0f); - const FVector2D Icon25x25(25.0f, 25.0f); - const FVector2D Icon32x32(32.0f, 32.0f); - const FVector2D Icon40x40(40.0f, 40.0f); - const FVector2D Icon64x64(64.0f, 64.0f); - const FVector2D Icon36x24(36.0f, 24.0f); - const FVector2D Icon128x128(128.0f, 128.0f); - - static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); - StyleSet->Set( - "HoudiniEngine.HoudiniEngineLogo", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set( - "ClassIcon.HoudiniAssetActor", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set( - "HoudiniEngine.HoudiniEngineLogo40", - new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); - - StyleSet->Set( - "ClassIcon.HoudiniAsset", - new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset.png"), Icon16x16)); - - StyleSet->Set( - "ClassThumbnail.HoudiniAsset", - new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset_128.png"), Icon64x64)); - - static FString ResourcesDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/"); - - FString AssetHelpIcon = IconsDir + TEXT("asset_help16x16.png"); - FString BakeAllIcon = IconsDir + TEXT("bake_all16x16.png"); - FString BakeSelIcon = IconsDir + TEXT("bake_selected16x16.png"); - FString CleanTempIcon = IconsDir + TEXT("clean_temp16x16.png"); - FString CookAllIcon = IconsDir + TEXT("cook_all16x16.png"); - FString CookLogIcon = IconsDir + TEXT("cook_log16x16.png"); - FString CookSelIcon = IconsDir + TEXT("cook_selected16x16.png"); - FString DigitalAssetIcon = IconsDir + TEXT("digital_asset16x16.png"); - FString OnlineForumIcon = IconsDir + TEXT("online_forum16x16.png"); - FString OnlineHelpIcon = IconsDir + TEXT("online_help16x16.png"); - FString OpenInHIcon = IconsDir + TEXT("open_in_houdini16x16.png"); - FString PauseIcon = IconsDir + TEXT("pause16x16.png"); - FString PDGCancelIcon = IconsDir + TEXT("pdg_cancel16x16.png"); - FString PDGDirtyAllIcon = IconsDir + TEXT("pdg_dirty_all16x16.png"); - FString PDGDirtyNodeIcon = IconsDir + TEXT("pdg_dirty_node16x16.png"); - FString PDGLinkIcon = IconsDir + TEXT("pdg_link16x16.png"); - FString PDGPauseIcon = IconsDir + TEXT("pdg_pause16x16.png"); - FString PDGRefreshIcon = IconsDir + TEXT("pdg_refresh16x16.png"); - FString PDGResetIcon = IconsDir + TEXT("pdg_reset16x16.png"); - FString RebuildAllIcon = IconsDir + TEXT("rebuild_all16x16.png"); - FString RebuildSelIcon = IconsDir + TEXT("rebuild_selected16x16.png"); - FString RefineAllIcon = IconsDir + TEXT("refine_all16x16.png"); - FString RefineSelIcon = IconsDir + TEXT("refine_selected16x16.png"); - FString ReportBugIcon = IconsDir + TEXT("report_bug16x16.png"); - FString ResetIcon = IconsDir + TEXT("reset16x16.png"); - FString ResetParamIcon = IconsDir + TEXT("reset_parameters16x16.png"); - FString SaveToHipIcon = IconsDir + TEXT("save_to_hip16x16.png"); - FString SessionConnectIcon = IconsDir + TEXT("session_connect16x16.png"); - FString SessionCreateIcon = IconsDir + TEXT("session_create16x16.png"); - FString SessionRestartIcon = IconsDir + TEXT("session_restart16x16.png"); - FString SessionStopIcon = IconsDir + TEXT("session_stop16x16.png"); - FString SessionSyncIcon = IconsDir + TEXT("session_sync16x16.png"); - FString SessionSyncStartIcon = IconsDir + TEXT("session_sync_start16x16.png"); - FString SessionSyncStopIcon = IconsDir + TEXT("session_sync_stop16x16.png"); - FString ViewportSyncIcon = IconsDir + TEXT("viewport_sync16x16.png"); - FString ViewportSyncBothIcon = IconsDir + TEXT("viewport_sync_both16x16.png"); - FString ViewportSyncHoudiniIcon = IconsDir + TEXT("viewport_sync_houdini16x16.png"); - FString ViewportSyncOffIcon = IconsDir + TEXT("viewport_sync_off16x16.png"); - FString ViewportSyncUnrealIcon = IconsDir + TEXT("viewport_sync_unreal16x16.png"); - - FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); - FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); - - StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(SessionCreateIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(SessionConnectIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(SessionStopIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(SessionRestartIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._SessionSync", new FSlateImageBrush(SessionSyncIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(SessionSyncStartIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(SessionSyncStopIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._SyncViewport", new FSlateImageBrush(ViewportSyncIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncNone", new FSlateImageBrush(ViewportSyncOffIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncBoth", new FSlateImageBrush(ViewportSyncBothIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncUnreal", new FSlateImageBrush(ViewportSyncUnrealIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ViewportSyncHoudini", new FSlateImageBrush(ViewportSyncHoudiniIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(OpenInHIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(SaveToHipIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(CleanTempIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(OnlineHelpIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(OnlineForumIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(ReportBugIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(CookAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(CookSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(BakeSelIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(BakeAllIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(RebuildAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(RebuildSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(RefineAllIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(RefineSelIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(PauseIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._Reset", new FSlateImageBrush(ResetIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine.DigitalAsset", new FSlateImageBrush(DigitalAssetIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine.PDGLink", new FSlateImageBrush(PDGLinkIcon, Icon16x16)); - - /* - FString StopIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Clear")->GetResourceName().ToString(); - FString RestartIcon = FEditorStyle::GetBrush("Tutorials.Browser.RestartButton")->GetResourceName().ToString(); - FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); - FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); - FString ClearIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Delete")->GetResourceName().ToString(); - FString HelpIcon = FEditorStyle::GetBrush("Icons.Help")->GetResourceName().ToString(); - FString WarningIcon = FEditorStyle::GetBrush("Icons.Warning")->GetResourceName().ToString(); - FString BPIcon = FEditorStyle::GetBrush("PropertyWindow.Button_CreateNewBlueprint")->GetResourceName().ToString(); - FString PauseIcon = FEditorStyle::GetBrush("Profiler.Pause")->GetResourceName().ToString(); - - StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(StopIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(RestartIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(StopIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); - - - StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(HelpIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(InfoIcon, Icon16x16)); - StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(WarningIcon, Icon16x16)); - - StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - - StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); - */ - - // We need some colors from Editor Style & this is the only way to do this at the moment - const FSlateColor DefaultForeground = FEditorStyle::GetSlateColor("DefaultForeground"); - const FSlateColor InvertedForeground = FEditorStyle::GetSlateColor("InvertedForeground"); - const FSlateColor SelectorColor = FEditorStyle::GetSlateColor("SelectorColor"); - const FSlateColor SelectionColor = FEditorStyle::GetSlateColor("SelectionColor"); - const FSlateColor SelectionColor_Inactive = FEditorStyle::GetSlateColor("SelectionColor_Inactive"); - - const FTableRowStyle &NormalTableRowStyle = FEditorStyle::Get().GetWidgetStyle("TableView.Row"); - StyleSet->Set( - "HoudiniEngine.TableRow", FTableRowStyle(NormalTableRowStyle) - .SetEvenRowBackgroundBrush(FSlateNoResource()) - .SetEvenRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) - .SetOddRowBackgroundBrush(FSlateNoResource()) - .SetOddRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) - .SetSelectorFocusedBrush(BORDER_BRUSH("Common/Selector", FMargin(4.f / 16.f), SelectorColor)) - .SetActiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) - .SetActiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) - .SetInactiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) - .SetInactiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) - .SetTextColor(DefaultForeground) - .SetSelectedTextColor(InvertedForeground) - ); - - // Normal Text - const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle("NormalText"); - StyleSet->Set( - "HoudiniEngine.ThumbnailText", FTextBlockStyle(NormalText) - .SetFont(TTF_CORE_FONT("Fonts/Roboto-Regular", 9)) - .SetColorAndOpacity(FSlateColor::UseForeground()) - .SetShadowOffset(FVector2D::ZeroVector) - .SetShadowColorAndOpacity(FLinearColor::Black) - .SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f)) - .SetHighlightShape(BOX_BRUSH("Common/TextBlockHighlightShape", FMargin(3.f / 8.f))) - ); - - StyleSet->Set("HoudiniEngine.ThumbnailShadow", new BOX_BRUSH("ContentBrowser/ThumbnailShadow", FMargin(4.0f / 64.0f))); - StyleSet->Set("HoudiniEngine.ThumbnailBackground", new IMAGE_BRUSH("Common/ClassBackground_64x", FVector2D(64.f, 64.f), FLinearColor(0.75f, 0.75f, 0.75f, 1.0f))); - - // Register Slate style. - FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); -}; - -END_SLATE_FUNCTION_BUILD_OPTIMIZATION - -#undef IMAGE_BRUSH -#undef BOX_BRUSH -#undef BORDER_BRUSH -#undef TTF_FONT -#undef TTF_CORE_FONT -#undef OTF_FONT -#undef OTF_CORE_FONT - -void -FHoudiniEngineStyle::Shutdown() -{ - if (StyleSet.IsValid()) - { - FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); - ensure(StyleSet.IsUnique()); - StyleSet.Reset(); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineStyle.h" + +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineUtils.h" + +#include "EditorStyleSet.h" +#include "Styling/SlateStyleRegistry.h" +#include "Styling/SlateTypes.h" +#include "SlateOptMacros.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define TTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) +#define OTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +TSharedPtr FHoudiniEngineStyle::StyleSet = nullptr; + +TSharedPtr +FHoudiniEngineStyle::Get() +{ + return StyleSet; +} + +FName +FHoudiniEngineStyle::GetStyleSetName() +{ + static FName HoudiniStyleName(TEXT("HoudiniEngineStyle")); + return HoudiniStyleName; +} + +BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + +void +FHoudiniEngineStyle::Initialize() +{ + // Only register the StyleSet once + if (StyleSet.IsValid()) + return; + + StyleSet = MakeShareable(new FSlateStyleSet(GetStyleSetName())); + StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); + StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); + + // Note, these sizes are in Slate Units. + // Slate Units do NOT have to map to pixels. + const FVector2D Icon5x16(5.0f, 16.0f); + const FVector2D Icon8x4(8.0f, 4.0f); + const FVector2D Icon8x8(8.0f, 8.0f); + const FVector2D Icon10x10(10.0f, 10.0f); + const FVector2D Icon12x12(12.0f, 12.0f); + const FVector2D Icon12x16(12.0f, 16.0f); + const FVector2D Icon14x14(14.0f, 14.0f); + const FVector2D Icon16x16(16.0f, 16.0f); + const FVector2D Icon20x20(20.0f, 20.0f); + const FVector2D Icon22x22(22.0f, 22.0f); + const FVector2D Icon24x24(24.0f, 24.0f); + const FVector2D Icon25x25(25.0f, 25.0f); + const FVector2D Icon32x32(32.0f, 32.0f); + const FVector2D Icon40x40(40.0f, 40.0f); + const FVector2D Icon64x64(64.0f, 64.0f); + const FVector2D Icon36x24(36.0f, 24.0f); + const FVector2D Icon128x128(128.0f, 128.0f); + + static FString IconsDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/Icons/"); + StyleSet->Set( + "HoudiniEngine.HoudiniEngineLogo", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( + "ClassIcon.HoudiniAssetActor", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( + "HoudiniEngine.HoudiniEngineLogo40", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); + + StyleSet->Set( + "ClassIcon.HoudiniAsset", + new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset.png"), Icon16x16)); + + StyleSet->Set( + "ClassThumbnail.HoudiniAsset", + new FSlateImageBrush(IconsDir + TEXT("houdini_digital_asset_128.png"), Icon64x64)); + + static FString ResourcesDir = FHoudiniEngineUtils::GetHoudiniEnginePluginDir() / TEXT("Resources/"); + + FString AssetHelpIcon = IconsDir + TEXT("asset_help16x16.png"); + FString BakeAllIcon = IconsDir + TEXT("bake_all16x16.png"); + FString BakeSelIcon = IconsDir + TEXT("bake_selected16x16.png"); + FString CleanTempIcon = IconsDir + TEXT("clean_temp16x16.png"); + FString CookAllIcon = IconsDir + TEXT("cook_all16x16.png"); + FString CookLogIcon = IconsDir + TEXT("cook_log16x16.png"); + FString CookSelIcon = IconsDir + TEXT("cook_selected16x16.png"); + FString DigitalAssetIcon = IconsDir + TEXT("digital_asset16x16.png"); + FString OnlineForumIcon = IconsDir + TEXT("online_forum16x16.png"); + FString OnlineHelpIcon = IconsDir + TEXT("online_help16x16.png"); + FString OpenInHIcon = IconsDir + TEXT("open_in_houdini16x16.png"); + FString PauseIcon = IconsDir + TEXT("pause16x16.png"); + FString PDGCancelIcon = IconsDir + TEXT("pdg_cancel16x16.png"); + FString PDGDirtyAllIcon = IconsDir + TEXT("pdg_dirty_all16x16.png"); + FString PDGDirtyNodeIcon = IconsDir + TEXT("pdg_dirty_node16x16.png"); + FString PDGLinkIcon = IconsDir + TEXT("pdg_link16x16.png"); + FString PDGPauseIcon = IconsDir + TEXT("pdg_pause16x16.png"); + FString PDGRefreshIcon = IconsDir + TEXT("pdg_refresh16x16.png"); + FString PDGResetIcon = IconsDir + TEXT("pdg_reset16x16.png"); + FString RebuildAllIcon = IconsDir + TEXT("rebuild_all16x16.png"); + FString RebuildSelIcon = IconsDir + TEXT("rebuild_selected16x16.png"); + FString RefineAllIcon = IconsDir + TEXT("refine_all16x16.png"); + FString RefineSelIcon = IconsDir + TEXT("refine_selected16x16.png"); + FString ReportBugIcon = IconsDir + TEXT("report_bug16x16.png"); + FString ResetIcon = IconsDir + TEXT("reset16x16.png"); + FString ResetParamIcon = IconsDir + TEXT("reset_parameters16x16.png"); + FString SaveToHipIcon = IconsDir + TEXT("save_to_hip16x16.png"); + FString SessionConnectIcon = IconsDir + TEXT("session_connect16x16.png"); + FString SessionCreateIcon = IconsDir + TEXT("session_create16x16.png"); + FString SessionRestartIcon = IconsDir + TEXT("session_restart16x16.png"); + FString SessionStopIcon = IconsDir + TEXT("session_stop16x16.png"); + FString SessionSyncIcon = IconsDir + TEXT("session_sync16x16.png"); + FString SessionSyncStartIcon = IconsDir + TEXT("session_sync_start16x16.png"); + FString SessionSyncStopIcon = IconsDir + TEXT("session_sync_stop16x16.png"); + FString ViewportSyncIcon = IconsDir + TEXT("viewport_sync16x16.png"); + FString ViewportSyncBothIcon = IconsDir + TEXT("viewport_sync_both16x16.png"); + FString ViewportSyncHoudiniIcon = IconsDir + TEXT("viewport_sync_houdini16x16.png"); + FString ViewportSyncOffIcon = IconsDir + TEXT("viewport_sync_off16x16.png"); + FString ViewportSyncUnrealIcon = IconsDir + TEXT("viewport_sync_unreal16x16.png"); + + FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); + FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); + + StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(SessionCreateIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(SessionConnectIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(SessionStopIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(SessionRestartIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._SessionSync", new FSlateImageBrush(SessionSyncIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(SessionSyncStartIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(SessionSyncStopIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._SyncViewport", new FSlateImageBrush(ViewportSyncIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncNone", new FSlateImageBrush(ViewportSyncOffIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncBoth", new FSlateImageBrush(ViewportSyncBothIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncUnreal", new FSlateImageBrush(ViewportSyncUnrealIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ViewportSyncHoudini", new FSlateImageBrush(ViewportSyncHoudiniIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(OpenInHIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(SaveToHipIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(CleanTempIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(OnlineHelpIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(OnlineForumIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(ReportBugIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(CookAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(CookSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(BakeSelIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(BakeAllIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(RebuildAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(RebuildSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(RefineAllIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(RefineSelIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(PauseIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._Reset", new FSlateImageBrush(ResetIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine.DigitalAsset", new FSlateImageBrush(DigitalAssetIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine.PDGLink", new FSlateImageBrush(PDGLinkIcon, Icon16x16)); + + /* + FString StopIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Clear")->GetResourceName().ToString(); + FString RestartIcon = FEditorStyle::GetBrush("Tutorials.Browser.RestartButton")->GetResourceName().ToString(); + FString InfoIcon = FEditorStyle::GetBrush("Icons.Info")->GetResourceName().ToString(); + FString SettingsIcon = FEditorStyle::GetBrush("Launcher.EditSettings")->GetResourceName().ToString(); + FString ClearIcon = FEditorStyle::GetBrush("PropertyWindow.Button_Delete")->GetResourceName().ToString(); + FString HelpIcon = FEditorStyle::GetBrush("Icons.Help")->GetResourceName().ToString(); + FString WarningIcon = FEditorStyle::GetBrush("Icons.Warning")->GetResourceName().ToString(); + FString BPIcon = FEditorStyle::GetBrush("PropertyWindow.Button_CreateNewBlueprint")->GetResourceName().ToString(); + FString PauseIcon = FEditorStyle::GetBrush("Profiler.Pause")->GetResourceName().ToString(); + + StyleSet->Set("HoudiniEngine._CreateSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._ConnectSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._StopSession", new FSlateImageBrush(StopIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._RestartSession", new FSlateImageBrush(RestartIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OpenSessionSync", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CloseSessionSync", new FSlateImageBrush(StopIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._InstallInfo", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._PluginSettings", new FSlateImageBrush(SettingsIcon, Icon16x16)); + + + StyleSet->Set("HoudiniEngine._OpenInHoudini", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._SaveHIPFile", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CleanUpTempFolder", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._OnlineDoc", new FSlateImageBrush(HelpIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._OnlineForum", new FSlateImageBrush(InfoIcon, Icon16x16)); + StyleSet->Set("HoudiniEngine._ReportBug", new FSlateImageBrush(WarningIcon, Icon16x16)); + + StyleSet->Set("HoudiniEngine._CookAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._CookSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_recook_icon.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._BakeSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._BakeAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._RebuildAll", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._RebuildSelected", new FSlateImageBrush(ResourcesDir + TEXT("hengine_reload_icon.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._RefineAll", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set("HoudiniEngine._RefineSelected", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set("HoudiniEngine._PauseAssetCooking", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + */ + + // We need some colors from Editor Style & this is the only way to do this at the moment + const FSlateColor DefaultForeground = FEditorStyle::GetSlateColor("DefaultForeground"); + const FSlateColor InvertedForeground = FEditorStyle::GetSlateColor("InvertedForeground"); + const FSlateColor SelectorColor = FEditorStyle::GetSlateColor("SelectorColor"); + const FSlateColor SelectionColor = FEditorStyle::GetSlateColor("SelectionColor"); + const FSlateColor SelectionColor_Inactive = FEditorStyle::GetSlateColor("SelectionColor_Inactive"); + + const FTableRowStyle &NormalTableRowStyle = FEditorStyle::Get().GetWidgetStyle("TableView.Row"); + StyleSet->Set( + "HoudiniEngine.TableRow", FTableRowStyle(NormalTableRowStyle) + .SetEvenRowBackgroundBrush(FSlateNoResource()) + .SetEvenRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) + .SetOddRowBackgroundBrush(FSlateNoResource()) + .SetOddRowBackgroundHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, FLinearColor(1.0f, 1.0f, 1.0f, 0.1f))) + .SetSelectorFocusedBrush(BORDER_BRUSH("Common/Selector", FMargin(4.f / 16.f), SelectorColor)) + .SetActiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) + .SetActiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor)) + .SetInactiveBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) + .SetInactiveHoveredBrush(IMAGE_BRUSH("Common/Selection", Icon8x8, SelectionColor_Inactive)) + .SetTextColor(DefaultForeground) + .SetSelectedTextColor(InvertedForeground) + ); + + // Normal Text + const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle("NormalText"); + StyleSet->Set( + "HoudiniEngine.ThumbnailText", FTextBlockStyle(NormalText) + .SetFont(TTF_CORE_FONT("Fonts/Roboto-Regular", 9)) + .SetColorAndOpacity(FSlateColor::UseForeground()) + .SetShadowOffset(FVector2D::ZeroVector) + .SetShadowColorAndOpacity(FLinearColor::Black) + .SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f)) + .SetHighlightShape(BOX_BRUSH("Common/TextBlockHighlightShape", FMargin(3.f / 8.f))) + ); + + StyleSet->Set("HoudiniEngine.ThumbnailShadow", new BOX_BRUSH("ContentBrowser/ThumbnailShadow", FMargin(4.0f / 64.0f))); + StyleSet->Set("HoudiniEngine.ThumbnailBackground", new IMAGE_BRUSH("Common/ClassBackground_64x", FVector2D(64.f, 64.f), FLinearColor(0.75f, 0.75f, 0.75f, 1.0f))); + + // Register Slate style. + FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); +}; + +END_SLATE_FUNCTION_BUILD_OPTIMIZATION + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef TTF_CORE_FONT +#undef OTF_FONT +#undef OTF_CORE_FONT + +void +FHoudiniEngineStyle::Shutdown() +{ + if (StyleSet.IsValid()) + { + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); + ensure(StyleSet.IsUnique()); + StyleSet.Reset(); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h index de25f3b67..ac64f0faa 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.h @@ -1,42 +1,42 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Styling/SlateStyle.h" - -class FHoudiniEngineStyle -{ - public: - static void Initialize(); - static void Shutdown(); - static TSharedPtr Get(); - static FName GetStyleSetName(); - - private: - //static FString InContent(const FString &RelativePath, const ANSICHAR *Extension); - static TSharedPtr StyleSet; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Styling/SlateStyle.h" + +class FHoudiniEngineStyle +{ + public: + static void Initialize(); + static void Shutdown(); + static TSharedPtr Get(); + static FName GetStyleSetName(); + + private: + //static FString InContent(const FString &RelativePath, const ANSICHAR *Extension); + static TSharedPtr StyleSet; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp index 6799aaff6..5541b05bf 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp @@ -1,382 +1,382 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoFactory.h" - -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniGeoImporter.h" -#include "HoudiniPackageParams.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniOutput.h" -#include "HoudiniEngine.h" - -#include "Engine/StaticMesh.h" -//#include "Engine/SkeletalMesh.h" - -#include "EditorFramework/AssetImportData.h" -#include "Misc/FileHelper.h" -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniGeoFactory::UHoudiniGeoFactory(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - // This factory is responsible for manufacturing HoudiniEngine assets. - SupportedClass = nullptr;// UHoudiniAsset::StaticClass(); - - // This factory does not manufacture new objects from scratch. - bCreateNew = false; - - // This factory will not open the editor for each new object. - bEditAfterNew = false; - - // This factory will import objects from files. - bEditorImport = true; - - // Factory does not import objects from text. - bText = false; - - // Add supported formats. - Formats.Add(TEXT("bgeo;Houdini Geometry")); - Formats.Add(TEXT("bgeo.sc;Houdini Geometry (compressed)")); - Formats.Add(TEXT("sc;Houdini Geometry (compressed)")); -} - -bool -UHoudiniGeoFactory::FactoryCanImport(const FString& Filename) -{ - const FString Extension = FPaths::GetExtension(Filename); - if(FPaths::GetExtension(Filename) == TEXT("bgeo")) - return true; - if (FPaths::GetExtension(Filename) == TEXT("bgeo.sc")) - return true; - if (FPaths::GetExtension(Filename) == TEXT("sc")) - return true; - - return false; -} - -bool -UHoudiniGeoFactory::DoesSupportClass(UClass * Class) -{ - return Class == UStaticMesh::StaticClass(); //|| Class == USkeletalMesh::StaticClass()); -} - -UClass* -UHoudiniGeoFactory::ResolveSupportedClass() -{ - return UStaticMesh::StaticClass(); -} - -FText -UHoudiniGeoFactory::GetDisplayName() const -{ - return LOCTEXT("HoudiniGeoFactoryDescription", "Houdini Engine Geo"); -} - -UObject* -UHoudiniGeoFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) -{ - // Make sure we're loading bgeo / bgeo.sc files - FString FileExtension = FPaths::GetExtension(Filename); - if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) - return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); - - // - // TODO: - // Handle import settings here? - // - UObject* Success = Import(InClass, Cast(InParent), InName.ToString(), Filename, Flags, false); - if (!Success) - { - FString Notification = TEXT("BGEO Importer: Failed to load the BGEO file."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - return nullptr; - } - - // Notifiy we're done loading the bgeo - FString Notification = TEXT("BGEO Importer: BGEO file imported succesfully."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - return Success; -} - -bool -UHoudiniGeoFactory::CanReimport(UObject * Obj, TArray& OutFilenames) -{ - UAssetImportData* ImportData = nullptr; - if (Obj->GetClass() == UStaticMesh::StaticClass()) - { - UStaticMesh* Mesh = Cast(Obj); - ImportData = Mesh->AssetImportData; - } - /* - else if (Obj->GetClass() == USkeletalMesh::StaticClass()) - { - USkeletalMesh* Cache = Cast(Obj); - ImportData = Cache->AssetImportData; - } - */ - - if (ImportData) - { - if (FPaths::GetExtension(ImportData->GetFirstFilename()).Contains(TEXT("bgeo"))) - { - ImportData->ExtractFilenames(OutFilenames); - return true; - } - } - return false; -} - -void -UHoudiniGeoFactory::SetReimportPaths(UObject * Obj, const TArray& NewReimportPaths) -{ - UStaticMesh* Mesh = Cast(Obj); - if (Mesh && Mesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) - { - Mesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); - } - - /* - USkeletalMesh* SkeletalMesh = Cast(Obj); - if (SkeletalMesh && SkeletalMesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) - { - SkeletalMesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); - } - */ -} - - -UObject* -UHoudiniGeoFactory::Import(UClass* InClass, UPackage* InParent, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport) -{ - // Broadcast PreImport - GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, FName(FileName), TEXT("Houdini GEO")); - - // Create a new Geo importer - TArray DummyOldOutputs; - TArray NewOutputs; - UHoudiniGeoImporter* BGEOImporter = NewObject(this); - BGEOImporter->AddToRoot(); - - // Clean up lambda - auto CleanUp = [&NewOutputs, &BGEOImporter]() - { - // Remove the importer and output objects from the root set - BGEOImporter->RemoveFromRoot(); - for (auto Out : NewOutputs) - Out->RemoveFromRoot(); - }; - - // Failure lambda - auto FailImportAndReturnNull = [this, &CleanUp, &NewOutputs, &BGEOImporter]() - { - CleanUp(); - - // Failed to read the file info, fail the import - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, nullptr); - - return nullptr; - }; - - // 1. Houdini Engine Session - // See if we should/can start the default "first" HE session - if (!BGEOImporter->AutoStartHoudiniEngineSessionIfNeeded()) - return FailImportAndReturnNull(); - - // 2. Update the file paths - if (!BGEOImporter->SetFilePath(AbsoluteFilePath)) - return FailImportAndReturnNull(); - - // 3. Load the BGEO file in HAPI - HAPI_NodeId NodeId; - if (!BGEOImporter->LoadBGEOFileInHAPI(NodeId)) - return FailImportAndReturnNull(); - - // Prepare the package used for creating the mesh, landscape and instancer pacakges - FHoudiniPackageParams PackageParams; - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); - PackageParams.HoudiniAssetName = FString(); - PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); - PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); - - if (bReimport) - { - PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; - } - else - { - PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; - } - - // 4. Get the output from the file node - if (!BGEOImporter->BuildOutputsForNode(NodeId, DummyOldOutputs, NewOutputs)) - return FailImportAndReturnNull(); - - // 5. Create the static meshes in the outputs - if (!BGEOImporter->CreateStaticMeshes(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 6. Create the curves in the outputs - if (!BGEOImporter->CreateCurves(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 7. Create the landscape in the outputs - if (!BGEOImporter->CreateLandscapes(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 8. Create the instancers in the outputs - if (!BGEOImporter->CreateInstancers(NewOutputs, InParent, PackageParams)) - return FailImportAndReturnNull(); - - // 9. Delete the created node in Houdini - if (!BGEOImporter->DeleteCreatedNode(NodeId)) - { - // Not good, but not fatal.. - //return false; - } - - // Get our result object and "finalize" them - TArray Results = BGEOImporter->GetOutputObjects(); - for (UObject* Object : Results) - { - if (!Object || Object->IsPendingKill()) - continue; - - Object->SetFlags(Flags); - - UAssetImportData * AssetImportData = nullptr; - if (Object->IsA()) - { - UStaticMesh* SM = Cast(Object); - AssetImportData = SM->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); - SM->AssetImportData = AssetImportData; - } - } - /* - else if (Object->IsA()) - { - USkeletalMesh * SkeletalMesh = Cast(Object); - AssetImportData = SkeletalMesh->AssetImportData; - // Create reimport information. - if (!AssetImportData) - { - AssetImportData = NewObject(SkeletalMesh, USkeletalMesh::StaticClass()); - SkeletalMesh->AssetImportData = AssetImportData; - } - } - */ - - if (AssetImportData) - AssetImportData->Update(AbsoluteFilePath); - - GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, Object); - Object->MarkPackageDirty(); - Object->PostEditChange(); - } - - CleanUp(); - - // Determine out parent according to the generated assets outer - UObject* OutParent = (Results.Num() > 0 && InParent != Results[0]->GetOutermost()) ? Results[0]->GetOutermost() : InParent; - return (Results.Num() > 0) ? OutParent : nullptr; -} - -EReimportResult::Type -UHoudiniGeoFactory::Reimport(UObject * Obj) -{ - auto FailReimport = []() - { - // Notifiy we failed to load the bgeo - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); - return EReimportResult::Failed; - }; - - if (!Obj || Obj->IsPendingKill()) - return FailReimport(); - - UPackage* Package = Cast(Obj->GetOuter()); - if (!Package || Package->IsPendingKill()) - return FailReimport(); - - UAssetImportData* ImportData = nullptr; - if (Obj->GetClass() == UStaticMesh::StaticClass()) - { - UStaticMesh* StaticMesh = Cast(Obj); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return FailReimport(); - - ImportData = StaticMesh->AssetImportData; - } - /* - else if(Obj->GetClass() == USkeletalMesh::StaticClass()) - { - USkeletalMesh* SkeletalMesh = Cast(Obj); - if (!SkeletalMesh || SkeletalMesh->IsPendingKill()) - return FailReimport(); - - ImportData = SkeletalMesh->AssetImportData; - } - */ - if (!ImportData || ImportData->IsPendingKill()) - return FailReimport(); - - if (ImportData->GetSourceFileCount() <= 0) - return FailReimport(); - - const FString RelativeFileName = ImportData->SourceData.SourceFiles[0].RelativeFilename; - const FString FileExtension = FPaths::GetExtension(RelativeFileName); - FString FileName = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativeFileName); - if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) - return FailReimport(); - - if (!Import(Obj->GetClass(), Package, Obj->GetName(), FileName, Obj->GetFlags(), true)) - return FailReimport(); - - HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has Succeed.")); - - // Notifiy we're done loading the bgeo - FString Notification = TEXT("BGEO Importer: BGEO file re-imported succesfully."); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); - - return EReimportResult::Succeeded; -} - -int32 -UHoudiniGeoFactory::GetPriority() const -{ - return ImportPriority; -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoFactory.h" + +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniGeoImporter.h" +#include "HoudiniPackageParams.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniOutput.h" +#include "HoudiniEngine.h" + +#include "Engine/StaticMesh.h" +//#include "Engine/SkeletalMesh.h" + +#include "EditorFramework/AssetImportData.h" +#include "Misc/FileHelper.h" +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniGeoFactory::UHoudiniGeoFactory(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + // This factory is responsible for manufacturing HoudiniEngine assets. + SupportedClass = nullptr;// UHoudiniAsset::StaticClass(); + + // This factory does not manufacture new objects from scratch. + bCreateNew = false; + + // This factory will not open the editor for each new object. + bEditAfterNew = false; + + // This factory will import objects from files. + bEditorImport = true; + + // Factory does not import objects from text. + bText = false; + + // Add supported formats. + Formats.Add(TEXT("bgeo;Houdini Geometry")); + Formats.Add(TEXT("bgeo.sc;Houdini Geometry (compressed)")); + Formats.Add(TEXT("sc;Houdini Geometry (compressed)")); +} + +bool +UHoudiniGeoFactory::FactoryCanImport(const FString& Filename) +{ + const FString Extension = FPaths::GetExtension(Filename); + if(FPaths::GetExtension(Filename) == TEXT("bgeo")) + return true; + if (FPaths::GetExtension(Filename) == TEXT("bgeo.sc")) + return true; + if (FPaths::GetExtension(Filename) == TEXT("sc")) + return true; + + return false; +} + +bool +UHoudiniGeoFactory::DoesSupportClass(UClass * Class) +{ + return Class == UStaticMesh::StaticClass(); //|| Class == USkeletalMesh::StaticClass()); +} + +UClass* +UHoudiniGeoFactory::ResolveSupportedClass() +{ + return UStaticMesh::StaticClass(); +} + +FText +UHoudiniGeoFactory::GetDisplayName() const +{ + return LOCTEXT("HoudiniGeoFactoryDescription", "Houdini Engine Geo"); +} + +UObject* +UHoudiniGeoFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) +{ + // Make sure we're loading bgeo / bgeo.sc files + FString FileExtension = FPaths::GetExtension(Filename); + if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) + return Super::FactoryCreateFile(InClass, InParent, InName, Flags, Filename, Parms, Warn, bOutOperationCanceled); + + // + // TODO: + // Handle import settings here? + // + UObject* Success = Import(InClass, Cast(InParent), InName.ToString(), Filename, Flags, false); + if (!Success) + { + FString Notification = TEXT("BGEO Importer: Failed to load the BGEO file."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + return nullptr; + } + + // Notifiy we're done loading the bgeo + FString Notification = TEXT("BGEO Importer: BGEO file imported succesfully."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + return Success; +} + +bool +UHoudiniGeoFactory::CanReimport(UObject * Obj, TArray& OutFilenames) +{ + UAssetImportData* ImportData = nullptr; + if (Obj->GetClass() == UStaticMesh::StaticClass()) + { + UStaticMesh* Mesh = Cast(Obj); + ImportData = Mesh->AssetImportData; + } + /* + else if (Obj->GetClass() == USkeletalMesh::StaticClass()) + { + USkeletalMesh* Cache = Cast(Obj); + ImportData = Cache->AssetImportData; + } + */ + + if (ImportData) + { + if (FPaths::GetExtension(ImportData->GetFirstFilename()).Contains(TEXT("bgeo"))) + { + ImportData->ExtractFilenames(OutFilenames); + return true; + } + } + return false; +} + +void +UHoudiniGeoFactory::SetReimportPaths(UObject * Obj, const TArray& NewReimportPaths) +{ + UStaticMesh* Mesh = Cast(Obj); + if (Mesh && Mesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) + { + Mesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); + } + + /* + USkeletalMesh* SkeletalMesh = Cast(Obj); + if (SkeletalMesh && SkeletalMesh->AssetImportData && ensure(NewReimportPaths.Num() == 1)) + { + SkeletalMesh->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); + } + */ +} + + +UObject* +UHoudiniGeoFactory::Import(UClass* InClass, UPackage* InParent, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport) +{ + // Broadcast PreImport + GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, FName(FileName), TEXT("Houdini GEO")); + + // Create a new Geo importer + TArray DummyOldOutputs; + TArray NewOutputs; + UHoudiniGeoImporter* BGEOImporter = NewObject(this); + BGEOImporter->AddToRoot(); + + // Clean up lambda + auto CleanUp = [&NewOutputs, &BGEOImporter]() + { + // Remove the importer and output objects from the root set + BGEOImporter->RemoveFromRoot(); + for (auto Out : NewOutputs) + Out->RemoveFromRoot(); + }; + + // Failure lambda + auto FailImportAndReturnNull = [this, &CleanUp, &NewOutputs, &BGEOImporter]() + { + CleanUp(); + + // Failed to read the file info, fail the import + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, nullptr); + + return nullptr; + }; + + // 1. Houdini Engine Session + // See if we should/can start the default "first" HE session + if (!BGEOImporter->AutoStartHoudiniEngineSessionIfNeeded()) + return FailImportAndReturnNull(); + + // 2. Update the file paths + if (!BGEOImporter->SetFilePath(AbsoluteFilePath)) + return FailImportAndReturnNull(); + + // 3. Load the BGEO file in HAPI + HAPI_NodeId NodeId; + if (!BGEOImporter->LoadBGEOFileInHAPI(NodeId)) + return FailImportAndReturnNull(); + + // Prepare the package used for creating the mesh, landscape and instancer pacakges + FHoudiniPackageParams PackageParams; + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder(); + PackageParams.HoudiniAssetName = FString(); + PackageParams.BakeFolder = FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()); + PackageParams.ObjectName = FPaths::GetBaseFilename(InParent->GetName()); + + if (bReimport) + { + PackageParams.ReplaceMode = EPackageReplaceMode::ReplaceExistingAssets; + } + else + { + PackageParams.ReplaceMode = EPackageReplaceMode::CreateNewAssets; + } + + // 4. Get the output from the file node + if (!BGEOImporter->BuildOutputsForNode(NodeId, DummyOldOutputs, NewOutputs)) + return FailImportAndReturnNull(); + + // 5. Create the static meshes in the outputs + if (!BGEOImporter->CreateStaticMeshes(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 6. Create the curves in the outputs + if (!BGEOImporter->CreateCurves(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 7. Create the landscape in the outputs + if (!BGEOImporter->CreateLandscapes(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 8. Create the instancers in the outputs + if (!BGEOImporter->CreateInstancers(NewOutputs, InParent, PackageParams)) + return FailImportAndReturnNull(); + + // 9. Delete the created node in Houdini + if (!BGEOImporter->DeleteCreatedNode(NodeId)) + { + // Not good, but not fatal.. + //return false; + } + + // Get our result object and "finalize" them + TArray Results = BGEOImporter->GetOutputObjects(); + for (UObject* Object : Results) + { + if (!Object || Object->IsPendingKill()) + continue; + + Object->SetFlags(Flags); + + UAssetImportData * AssetImportData = nullptr; + if (Object->IsA()) + { + UStaticMesh* SM = Cast(Object); + AssetImportData = SM->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject< UAssetImportData >(SM, UAssetImportData::StaticClass()); + SM->AssetImportData = AssetImportData; + } + } + /* + else if (Object->IsA()) + { + USkeletalMesh * SkeletalMesh = Cast(Object); + AssetImportData = SkeletalMesh->AssetImportData; + // Create reimport information. + if (!AssetImportData) + { + AssetImportData = NewObject(SkeletalMesh, USkeletalMesh::StaticClass()); + SkeletalMesh->AssetImportData = AssetImportData; + } + } + */ + + if (AssetImportData) + AssetImportData->Update(AbsoluteFilePath); + + GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, Object); + Object->MarkPackageDirty(); + Object->PostEditChange(); + } + + CleanUp(); + + // Determine out parent according to the generated assets outer + UObject* OutParent = (Results.Num() > 0 && InParent != Results[0]->GetOutermost()) ? Results[0]->GetOutermost() : InParent; + return (Results.Num() > 0) ? OutParent : nullptr; +} + +EReimportResult::Type +UHoudiniGeoFactory::Reimport(UObject * Obj) +{ + auto FailReimport = []() + { + // Notifiy we failed to load the bgeo + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has failed.")); + return EReimportResult::Failed; + }; + + if (!Obj || Obj->IsPendingKill()) + return FailReimport(); + + UPackage* Package = Cast(Obj->GetOuter()); + if (!Package || Package->IsPendingKill()) + return FailReimport(); + + UAssetImportData* ImportData = nullptr; + if (Obj->GetClass() == UStaticMesh::StaticClass()) + { + UStaticMesh* StaticMesh = Cast(Obj); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return FailReimport(); + + ImportData = StaticMesh->AssetImportData; + } + /* + else if(Obj->GetClass() == USkeletalMesh::StaticClass()) + { + USkeletalMesh* SkeletalMesh = Cast(Obj); + if (!SkeletalMesh || SkeletalMesh->IsPendingKill()) + return FailReimport(); + + ImportData = SkeletalMesh->AssetImportData; + } + */ + if (!ImportData || ImportData->IsPendingKill()) + return FailReimport(); + + if (ImportData->GetSourceFileCount() <= 0) + return FailReimport(); + + const FString RelativeFileName = ImportData->SourceData.SourceFiles[0].RelativeFilename; + const FString FileExtension = FPaths::GetExtension(RelativeFileName); + FString FileName = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*RelativeFileName); + if (!FileExtension.Contains(TEXT("bgeo"), ESearchCase::IgnoreCase) && !FileExtension.Contains(TEXT("sc"), ESearchCase::IgnoreCase)) + return FailReimport(); + + if (!Import(Obj->GetClass(), Package, Obj->GetName(), FileName, Obj->GetFlags(), true)) + return FailReimport(); + + HOUDINI_LOG_MESSAGE(TEXT("Houdini Asset reimport has Succeed.")); + + // Notifiy we're done loading the bgeo + FString Notification = TEXT("BGEO Importer: BGEO file re-imported succesfully."); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Notification)); + + return EReimportResult::Succeeded; +} + +int32 +UHoudiniGeoFactory::GetPriority() const +{ + return ImportPriority; +} + + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h index c8a03b737..8dd0a42cb 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.h @@ -1,83 +1,83 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "EditorReimportHandler.h" -#include "Factories/Factory.h" -#include "HoudiniGeoFactory.generated.h" - -class UClass; -class UObject; -class FFeedbackContext; - -UCLASS(config = Editor) -class UHoudiniGeoFactory : public UFactory, public FReimportHandler -{ - GENERATED_UCLASS_BODY() - - public: - - // - // UFactory Interface - // - // Returns the name of the factory for menus - virtual FText GetDisplayName() const override; - // return true if it supports this class - virtual bool DoesSupportClass(UClass * Class) override; - // - virtual UClass* ResolveSupportedClass() override; - // - //virtual UClass* ResolveSupportedClass() override; - // Return true if we can import the file - virtual bool FactoryCanImport(const FString& Filename) override; - - // Create a new object by importing it from a file name. - virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, - EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, - FFeedbackContext* Warn, bool& bOutOperationCanceled) override; - - // - // FReimportHandler Interface - // - UObject* Import(UClass* InClass, UPackage* OuterObject, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport); - - // Check to see if we have a handler to manage the reimporting of the object - virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; - - // Sets the reimport path(s) for the specified object - virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; - // Attempt to reimport the specified object from its source - virtual EReimportResult::Type Reimport(UObject * Obj) override; - - //void ShowImportOptionsWindow(TSharedPtr& Options, FString FilePath, const UHoudiniGeoImporter& Importer); - - virtual int32 GetPriority() const override; - - // - // - // -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "EditorReimportHandler.h" +#include "Factories/Factory.h" +#include "HoudiniGeoFactory.generated.h" + +class UClass; +class UObject; +class FFeedbackContext; + +UCLASS(config = Editor) +class UHoudiniGeoFactory : public UFactory, public FReimportHandler +{ + GENERATED_UCLASS_BODY() + + public: + + // + // UFactory Interface + // + // Returns the name of the factory for menus + virtual FText GetDisplayName() const override; + // return true if it supports this class + virtual bool DoesSupportClass(UClass * Class) override; + // + virtual UClass* ResolveSupportedClass() override; + // + //virtual UClass* ResolveSupportedClass() override; + // Return true if we can import the file + virtual bool FactoryCanImport(const FString& Filename) override; + + // Create a new object by importing it from a file name. + virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, + EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, + FFeedbackContext* Warn, bool& bOutOperationCanceled) override; + + // + // FReimportHandler Interface + // + UObject* Import(UClass* InClass, UPackage* OuterObject, const FString & FileName, const FString & AbsoluteFilePath, EObjectFlags Flags, const bool& bReimport); + + // Check to see if we have a handler to manage the reimporting of the object + virtual bool CanReimport(UObject * Obj, TArray< FString > & OutFilenames) override; + + // Sets the reimport path(s) for the specified object + virtual void SetReimportPaths(UObject * Obj, const TArray< FString > & NewReimportPaths) override; + // Attempt to reimport the specified object from its source + virtual EReimportResult::Type Reimport(UObject * Obj) override; + + //void ShowImportOptionsWindow(TSharedPtr& Options, FString FilePath, const UHoudiniGeoImporter& Importer); + + virtual int32 GetPriority() const override; + + // + // + // +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp index 12681e4ca..13b4057bd 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.cpp @@ -1,260 +1,260 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleComponentVisualizer.h" - -#include "EditorViewportClient.h" - -#include "HoudiniHandleTranslator.h" -#include "HoudiniAssetComponent.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_HIT_PROXY(HHoudiniHandleVisProxy, HComponentVisProxy); - -HHoudiniHandleVisProxy::HHoudiniHandleVisProxy(const UActorComponent * InComponent) - : HComponentVisProxy(InComponent, HPP_Wireframe) -{} - - -FHoudiniHandleComponentVisualizerCommands::FHoudiniHandleComponentVisualizerCommands() - : TCommands< FHoudiniHandleComponentVisualizerCommands >( - "HoudiniHandleComponentVisualizer", - FText::FromString("Houdini handle Component Visualizer"), - NAME_None, - FEditorStyle::GetStyleSetName()) -{} - -void -FHoudiniHandleComponentVisualizerCommands::RegisterCommands() -{} - - -FHoudiniHandleComponentVisualizer::FHoudiniHandleComponentVisualizer() - : FComponentVisualizer() - , EditedComponent(nullptr) - , bEditing(false) -{ - FHoudiniHandleComponentVisualizerCommands::Register(); - VisualizerActions = MakeShareable(new FUICommandList); -} - -FHoudiniHandleComponentVisualizer::~FHoudiniHandleComponentVisualizer() -{ - FHoudiniHandleComponentVisualizerCommands::Unregister(); -} - -void -FHoudiniHandleComponentVisualizer::DrawVisualization(const UActorComponent * Component, - const FSceneView * View, FPrimitiveDrawInterface * PDI) -{ - const UHoudiniHandleComponent* HandleComponent = Cast(Component); - - if (!HandleComponent) - return; - - UHoudiniAssetComponent* HAC = Cast(HandleComponent->GetOuter()); - - if (!HAC) - return; - - - static TMap ColorMapActive; - static TMap ColorMapInactive; - - - int32 AssetId = HAC->GetAssetId(); - - if (!ColorMapActive.Contains(AssetId) || !ColorMapInactive.Contains(AssetId)) - { - FLinearColor NewActiveColor = FLinearColor::MakeRandomColor(); - FLinearColor NewInactiveColor = NewActiveColor.CopyWithNewOpacity(0.1)/2.5f; - - ColorMapActive.Add(AssetId, NewActiveColor); - ColorMapInactive.Add(AssetId, NewInactiveColor); - } - - - const FLinearColor& ActiveColor = ColorMapActive[AssetId]; - const FLinearColor& InactiveColor = ColorMapInactive[AssetId]; - - bool IsActive = EditedComponent != nullptr; - - if (IsActive) - { - UHoudiniAssetComponent* EditedComponentParent = Cast(EditedComponent->GetOuter()); - IsActive &= EditedComponentParent && EditedComponentParent->GetAssetId() == HAC->GetAssetId(); - } - - // Draw point and set hit box for it. - PDI->SetHitProxy(new HHoudiniHandleVisProxy(HandleComponent)); - { - static const float GrabHandleSizeActive = 24.0f; - static const float GrabHandleSizeInactive = 18.0f; - - PDI->DrawPoint(HandleComponent->GetComponentTransform().GetLocation(), IsActive ? ActiveColor : InactiveColor, IsActive ? GrabHandleSizeActive : GrabHandleSizeInactive, SDPG_Foreground); - } - - if (HandleComponent->HandleType == EHoudiniHandleType::Bounder) - { - // draw the scale box - FTransform BoxTransform = HandleComponent->GetComponentTransform(); - const float BoxRad = 50.f; - const FBox Box(FVector(-BoxRad, -BoxRad, -BoxRad), FVector(BoxRad, BoxRad, BoxRad)); - DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), Box, IsActive ? ActiveColor : InactiveColor, SDPG_Foreground); - } - - PDI->SetHitProxy(nullptr); -} - -bool -FHoudiniHandleComponentVisualizer::VisProxyHandleClick( - FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) -{ - bEditing = false; - - bAllowTranslate = false; - bAllowRotation = false; - bAllowScale = false; - - - if (VisProxy && VisProxy->Component.IsValid()) - { - const UHoudiniHandleComponent * Component = - CastChecked< const UHoudiniHandleComponent >(VisProxy->Component.Get()); - - const TArray &XformParms = Component->XformParms; - - if (!Component->CheckHandleValid()) - return bEditing; - - EditedComponent = const_cast(Component); - - if (Component) - { - if (VisProxy->IsA(HHoudiniHandleVisProxy::StaticGetType())) - bEditing = true; - - bAllowTranslate = - XformParms[int32(EXformParameter::TX)]->AssetParameter || - XformParms[int32(EXformParameter::TY)]->AssetParameter || - XformParms[int32(EXformParameter::TZ)]->AssetParameter; - - bAllowRotation = - XformParms[int32(EXformParameter::RX)]->AssetParameter || - XformParms[int32(EXformParameter::RY)]->AssetParameter || - XformParms[int32(EXformParameter::RZ)]->AssetParameter; - - bAllowScale = - XformParms[int32(EXformParameter::SX)]->AssetParameter || - XformParms[int32(EXformParameter::SY)]->AssetParameter || - XformParms[int32(EXformParameter::SZ)]->AssetParameter; - } - } - - return bEditing; -} - -void -FHoudiniHandleComponentVisualizer::EndEditing() -{ - EditedComponent = nullptr; -} - -bool -FHoudiniHandleComponentVisualizer::GetWidgetLocation( - const FEditorViewportClient * ViewportClient, - FVector & OutLocation) const -{ - if (EditedComponent) - { - OutLocation = EditedComponent->GetComponentTransform().GetLocation(); - return true; - } - - return false; -} - -bool -FHoudiniHandleComponentVisualizer::GetCustomInputCoordinateSystem( - const FEditorViewportClient * ViewportClient, - FMatrix & OutMatrix) const -{ - if (EditedComponent && ViewportClient->GetWidgetMode() == FWidget::WM_Scale) - { - OutMatrix = FRotationMatrix::Make(EditedComponent->GetComponentTransform().GetRotation()); - return true; - } - else - { - return false; - } -} - -bool -FHoudiniHandleComponentVisualizer::HandleInputDelta( - FEditorViewportClient * ViewportClient, FViewport * Viewport, - FVector& DeltaTranslate, FRotator & DeltaRotate, FVector & DeltaScale) -{ - if (!EditedComponent) - return false; - - if (!DeltaTranslate.IsZero() && bAllowTranslate) - { - EditedComponent->SetRelativeLocation(EditedComponent->GetRelativeTransform().GetLocation() + DeltaTranslate); - } - - if (!DeltaRotate.IsZero() && bAllowRotation) - { - EditedComponent->SetRelativeRotation(DeltaRotate.Quaternion() * EditedComponent->GetRelativeTransform().GetRotation()); - } - - if (!DeltaScale.IsZero() && bAllowScale) - { - EditedComponent->SetRelativeScale3D(EditedComponent->GetRelativeTransform().GetScale3D() + DeltaScale); - } - - return true; -} - -bool -FHoudiniHandleComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) -{ - if (EditedComponent) - { - if (Key == EKeys::LeftMouseButton && Event == IE_Released) - { - if (GEditor) - GEditor->RedrawLevelEditingViewports(true); - - FHoudiniHandleTranslator::UpdateTransformParameters(EditedComponent); - } - - } - return false; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleComponentVisualizer.h" + +#include "EditorViewportClient.h" + +#include "HoudiniHandleTranslator.h" +#include "HoudiniAssetComponent.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_HIT_PROXY(HHoudiniHandleVisProxy, HComponentVisProxy); + +HHoudiniHandleVisProxy::HHoudiniHandleVisProxy(const UActorComponent * InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) +{} + + +FHoudiniHandleComponentVisualizerCommands::FHoudiniHandleComponentVisualizerCommands() + : TCommands< FHoudiniHandleComponentVisualizerCommands >( + "HoudiniHandleComponentVisualizer", + FText::FromString("Houdini handle Component Visualizer"), + NAME_None, + FEditorStyle::GetStyleSetName()) +{} + +void +FHoudiniHandleComponentVisualizerCommands::RegisterCommands() +{} + + +FHoudiniHandleComponentVisualizer::FHoudiniHandleComponentVisualizer() + : FComponentVisualizer() + , EditedComponent(nullptr) + , bEditing(false) +{ + FHoudiniHandleComponentVisualizerCommands::Register(); + VisualizerActions = MakeShareable(new FUICommandList); +} + +FHoudiniHandleComponentVisualizer::~FHoudiniHandleComponentVisualizer() +{ + FHoudiniHandleComponentVisualizerCommands::Unregister(); +} + +void +FHoudiniHandleComponentVisualizer::DrawVisualization(const UActorComponent * Component, + const FSceneView * View, FPrimitiveDrawInterface * PDI) +{ + const UHoudiniHandleComponent* HandleComponent = Cast(Component); + + if (!HandleComponent) + return; + + UHoudiniAssetComponent* HAC = Cast(HandleComponent->GetOuter()); + + if (!HAC) + return; + + + static TMap ColorMapActive; + static TMap ColorMapInactive; + + + int32 AssetId = HAC->GetAssetId(); + + if (!ColorMapActive.Contains(AssetId) || !ColorMapInactive.Contains(AssetId)) + { + FLinearColor NewActiveColor = FLinearColor::MakeRandomColor(); + FLinearColor NewInactiveColor = NewActiveColor.CopyWithNewOpacity(0.1)/2.5f; + + ColorMapActive.Add(AssetId, NewActiveColor); + ColorMapInactive.Add(AssetId, NewInactiveColor); + } + + + const FLinearColor& ActiveColor = ColorMapActive[AssetId]; + const FLinearColor& InactiveColor = ColorMapInactive[AssetId]; + + bool IsActive = EditedComponent != nullptr; + + if (IsActive) + { + UHoudiniAssetComponent* EditedComponentParent = Cast(EditedComponent->GetOuter()); + IsActive &= EditedComponentParent && EditedComponentParent->GetAssetId() == HAC->GetAssetId(); + } + + // Draw point and set hit box for it. + PDI->SetHitProxy(new HHoudiniHandleVisProxy(HandleComponent)); + { + static const float GrabHandleSizeActive = 24.0f; + static const float GrabHandleSizeInactive = 18.0f; + + PDI->DrawPoint(HandleComponent->GetComponentTransform().GetLocation(), IsActive ? ActiveColor : InactiveColor, IsActive ? GrabHandleSizeActive : GrabHandleSizeInactive, SDPG_Foreground); + } + + if (HandleComponent->HandleType == EHoudiniHandleType::Bounder) + { + // draw the scale box + FTransform BoxTransform = HandleComponent->GetComponentTransform(); + const float BoxRad = 50.f; + const FBox Box(FVector(-BoxRad, -BoxRad, -BoxRad), FVector(BoxRad, BoxRad, BoxRad)); + DrawWireBox(PDI, BoxTransform.ToMatrixWithScale(), Box, IsActive ? ActiveColor : InactiveColor, SDPG_Foreground); + } + + PDI->SetHitProxy(nullptr); +} + +bool +FHoudiniHandleComponentVisualizer::VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) +{ + bEditing = false; + + bAllowTranslate = false; + bAllowRotation = false; + bAllowScale = false; + + + if (VisProxy && VisProxy->Component.IsValid()) + { + const UHoudiniHandleComponent * Component = + CastChecked< const UHoudiniHandleComponent >(VisProxy->Component.Get()); + + const TArray &XformParms = Component->XformParms; + + if (!Component->CheckHandleValid()) + return bEditing; + + EditedComponent = const_cast(Component); + + if (Component) + { + if (VisProxy->IsA(HHoudiniHandleVisProxy::StaticGetType())) + bEditing = true; + + bAllowTranslate = + XformParms[int32(EXformParameter::TX)]->AssetParameter || + XformParms[int32(EXformParameter::TY)]->AssetParameter || + XformParms[int32(EXformParameter::TZ)]->AssetParameter; + + bAllowRotation = + XformParms[int32(EXformParameter::RX)]->AssetParameter || + XformParms[int32(EXformParameter::RY)]->AssetParameter || + XformParms[int32(EXformParameter::RZ)]->AssetParameter; + + bAllowScale = + XformParms[int32(EXformParameter::SX)]->AssetParameter || + XformParms[int32(EXformParameter::SY)]->AssetParameter || + XformParms[int32(EXformParameter::SZ)]->AssetParameter; + } + } + + return bEditing; +} + +void +FHoudiniHandleComponentVisualizer::EndEditing() +{ + EditedComponent = nullptr; +} + +bool +FHoudiniHandleComponentVisualizer::GetWidgetLocation( + const FEditorViewportClient * ViewportClient, + FVector & OutLocation) const +{ + if (EditedComponent) + { + OutLocation = EditedComponent->GetComponentTransform().GetLocation(); + return true; + } + + return false; +} + +bool +FHoudiniHandleComponentVisualizer::GetCustomInputCoordinateSystem( + const FEditorViewportClient * ViewportClient, + FMatrix & OutMatrix) const +{ + if (EditedComponent && ViewportClient->GetWidgetMode() == FWidget::WM_Scale) + { + OutMatrix = FRotationMatrix::Make(EditedComponent->GetComponentTransform().GetRotation()); + return true; + } + else + { + return false; + } +} + +bool +FHoudiniHandleComponentVisualizer::HandleInputDelta( + FEditorViewportClient * ViewportClient, FViewport * Viewport, + FVector& DeltaTranslate, FRotator & DeltaRotate, FVector & DeltaScale) +{ + if (!EditedComponent) + return false; + + if (!DeltaTranslate.IsZero() && bAllowTranslate) + { + EditedComponent->SetRelativeLocation(EditedComponent->GetRelativeTransform().GetLocation() + DeltaTranslate); + } + + if (!DeltaRotate.IsZero() && bAllowRotation) + { + EditedComponent->SetRelativeRotation(DeltaRotate.Quaternion() * EditedComponent->GetRelativeTransform().GetRotation()); + } + + if (!DeltaScale.IsZero() && bAllowScale) + { + EditedComponent->SetRelativeScale3D(EditedComponent->GetRelativeTransform().GetScale3D() + DeltaScale); + } + + return true; +} + +bool +FHoudiniHandleComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) +{ + if (EditedComponent) + { + if (Key == EKeys::LeftMouseButton && Event == IE_Released) + { + if (GEditor) + GEditor->RedrawLevelEditingViewports(true); + + FHoudiniHandleTranslator::UpdateTransformParameters(EditedComponent); + } + + } + return false; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h index 55fe29a57..5eed6461b 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleComponentVisualizer.h @@ -1,114 +1,114 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "ComponentVisualizer.h" -#include "Framework/Commands/Commands.h" -#include "Framework/Commands/UICommandList.h" - -#include "Components/ActorComponent.h" -#include "HoudiniHandleComponent.h" - -/** Base class for clickable editing proxies. **/ -struct HHoudiniHandleVisProxy : public HComponentVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniHandleVisProxy(const UActorComponent * InComponent); -}; - -/** Define commands for our component visualizer */ -class FHoudiniHandleComponentVisualizerCommands : public TCommands< FHoudiniHandleComponentVisualizerCommands > -{ -public: - - /** Constructor. **/ - FHoudiniHandleComponentVisualizerCommands(); - - /** Register commands. **/ - virtual void RegisterCommands() override; - -public: - - /** Command for adding a control point. **/ - TSharedPtr< FUICommandInfo > CommandAddControlPoint; - - /** Command for deleting a control point. **/ - TSharedPtr< FUICommandInfo > CommandDeleteControlPoint; -}; - - -/** Our handle visualizer. **/ -class FHoudiniHandleComponentVisualizer : public FComponentVisualizer -{ -public: - FHoudiniHandleComponentVisualizer(); - - virtual ~FHoudiniHandleComponentVisualizer(); - - /** FComponentVisualizer methods. **/ - - /** Draw visualization for the given component. **/ - virtual void DrawVisualization( - const UActorComponent * Component, const FSceneView * View, - FPrimitiveDrawInterface * PDI) override; - - /** Handle a click on a registered hit box. **/ - virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; - - virtual void EndEditing(); - - /** Returns location of a gizmo widget. **/ - virtual bool GetWidgetLocation( - const FEditorViewportClient *, FVector & OutLocation) const override; - - virtual bool GetCustomInputCoordinateSystem( - const FEditorViewportClient * ViewportClient, FMatrix & OutMatrix) const override; - - /** Handle input change. **/ - virtual bool HandleInputDelta( - FEditorViewportClient *, FViewport *, FVector & DeltaTranslate, - FRotator & DeltaRotate, FVector & DeltaScale) override; - - virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; - - void SetEditedComponent(UHoudiniHandleComponent* InComponent) { EditedComponent = InComponent; }; - void ClearEditedComponent() { EditedComponent = nullptr; }; - - -protected: - /** Visualizer actions. **/ - TSharedPtr< FUICommandList > VisualizerActions; - - /** Houdini component which is being edited. **/ - UHoudiniHandleComponent* EditedComponent; - - /** Is set to true if we are editing. **/ - uint32 bEditing : 1; - uint32 bAllowTranslate : 1; - uint32 bAllowRotation : 1; - uint32 bAllowScale : 1; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "ComponentVisualizer.h" +#include "Framework/Commands/Commands.h" +#include "Framework/Commands/UICommandList.h" + +#include "Components/ActorComponent.h" +#include "HoudiniHandleComponent.h" + +/** Base class for clickable editing proxies. **/ +struct HHoudiniHandleVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniHandleVisProxy(const UActorComponent * InComponent); +}; + +/** Define commands for our component visualizer */ +class FHoudiniHandleComponentVisualizerCommands : public TCommands< FHoudiniHandleComponentVisualizerCommands > +{ +public: + + /** Constructor. **/ + FHoudiniHandleComponentVisualizerCommands(); + + /** Register commands. **/ + virtual void RegisterCommands() override; + +public: + + /** Command for adding a control point. **/ + TSharedPtr< FUICommandInfo > CommandAddControlPoint; + + /** Command for deleting a control point. **/ + TSharedPtr< FUICommandInfo > CommandDeleteControlPoint; +}; + + +/** Our handle visualizer. **/ +class FHoudiniHandleComponentVisualizer : public FComponentVisualizer +{ +public: + FHoudiniHandleComponentVisualizer(); + + virtual ~FHoudiniHandleComponentVisualizer(); + + /** FComponentVisualizer methods. **/ + + /** Draw visualization for the given component. **/ + virtual void DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI) override; + + /** Handle a click on a registered hit box. **/ + virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override; + + virtual void EndEditing(); + + /** Returns location of a gizmo widget. **/ + virtual bool GetWidgetLocation( + const FEditorViewportClient *, FVector & OutLocation) const override; + + virtual bool GetCustomInputCoordinateSystem( + const FEditorViewportClient * ViewportClient, FMatrix & OutMatrix) const override; + + /** Handle input change. **/ + virtual bool HandleInputDelta( + FEditorViewportClient *, FViewport *, FVector & DeltaTranslate, + FRotator & DeltaRotate, FVector & DeltaScale) override; + + virtual bool HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) override; + + void SetEditedComponent(UHoudiniHandleComponent* InComponent) { EditedComponent = InComponent; }; + void ClearEditedComponent() { EditedComponent = nullptr; }; + + +protected: + /** Visualizer actions. **/ + TSharedPtr< FUICommandList > VisualizerActions; + + /** Houdini component which is being edited. **/ + UHoudiniHandleComponent* EditedComponent; + + /** Is set to true if we are editing. **/ + uint32 bEditing : 1; + uint32 bAllowTranslate : 1; + uint32 bAllowRotation : 1; + uint32 bAllowScale : 1; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp index 656eeb082..613c1f094 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp @@ -1,397 +1,397 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleDetails.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniHandleComponent.h" -#include "HoudiniHandleTranslator.h" -#include "HoudiniHandleComponentVisualizer.h" - -#include "DetailCategoryBuilder.h" -#include "DetailWidgetRow.h" -#include "IDetailGroup.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Images/SImage.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -void -FHoudiniHandleDetails::CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles) -{ - - if (InHandles.Num() <= 0) - return; - - UHoudiniHandleComponent* MainHandle = InHandles[0]; - - if (!MainHandle || MainHandle->IsPendingKill()) - return; - - - FString HandleNameStr = MainHandle->GetHandleName() + TEXT(" (") + GetHandleTypeString(MainHandle->GetHandleType()) + TEXT(" )"); - FName HandleName = FName(*HandleNameStr); - IDetailGroup& Group = HouHandleCategory.AddGroup(HandleName, FText::FromString(HandleNameStr), false, false); - - // Create a widget row for this handle - FDetailWidgetRow& Row = Group.AddWidgetRow(); - - CreateNameWidget(Row); - - // Create value widget - - TSharedRef ValueWidgetVerticalBox = SNew(SVerticalBox); - - // Translate - auto OnLocationChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FVector Location = MainHandle->GetRelativeTransform().GetLocation(); - - if (Axis == 0) - Location.X = Val; - else if (Axis == 1) - Location.Y = Val; - else - Location.Z = Val; - - MainHandle->SetRelativeLocation(Location); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertLocationToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - FVector DefaultLocation = FVector(0.f, 0.f, 0.f); - MainHandle->SetRelativeLocation(DefaultLocation); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Z; }) - .OnXCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 0); - }) - .OnYCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 1); - }) - .OnZCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnLocationChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeLocation() == FVector::ZeroVector) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertLocationToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - // Rotation - auto OnRotationChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FQuat Rotation = MainHandle->GetRelativeTransform().GetRotation(); - - if (Axis == 0) - Rotation.X = Val; - else if (Axis == 1) - Rotation.Y = Val; - else - Rotation.Z = Val; - - MainHandle->SetRelativeRotation(Rotation); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertRotationToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - MainHandle->SetRelativeRotation(FQuat::Identity); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Z; }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 0); - }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 1); - }) - .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnRotationChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeTransform().GetRotation() == FQuat::Identity) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertRotationToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - // Scale - auto OnScaleChangedLambda = [MainHandle](float Val, int32 Axis) - { - if (!MainHandle) - return; - - FVector Scale = MainHandle->GetRelativeTransform().GetScale3D(); - - if (Axis == 0) - Scale.X = Val; - else if (Axis == 1) - Scale.Y = Val; - else - Scale.Z = Val; - - MainHandle->SetRelativeScale3D(Scale); - FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); - }; - - auto RevertScaleToDefault = [MainHandle]() - { - if (!MainHandle) - return FReply::Handled(); - - MainHandle->SetRelativeScale3D(FVector::OneVector); - - return FReply::Handled(); - }; - - ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().X; }) - .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Y; }) - .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Z; }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 0); - }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 1); - }) - .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) - { - OnScaleChangedLambda(Val, 2); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([MainHandle]() - { - if (!MainHandle) - return EVisibility::Hidden; - - if (MainHandle->GetRelativeTransform().GetScale3D() == FVector::OneVector) - return EVisibility::Hidden; - - return EVisibility::Visible; - }) - .OnClicked_Lambda(RevertScaleToDefault) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - - Row.ValueWidget.Widget = ValueWidgetVerticalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - - auto OnMouseEnterLambda = [MainHandle](const FGeometry& Geometry, const FPointerEvent& Event) - { - if (!MainHandle) - return; - - TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); - - if (HandleVisualizer.IsValid()) - { - HandleVisualizer->SetEditedComponent(MainHandle); - } - }; - - auto OnMouseLeaveLambda = [MainHandle](const FPointerEvent& Event) - { - if (!MainHandle) - return; - - TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); - TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); - - if (HandleVisualizer.IsValid()) - { - HandleVisualizer->SetEditedComponent(nullptr); - } - }; - - // Set on mouse leave UI widget callback functions - Row.NameWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); - Row.ValueWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); - - Row.NameWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); - Row.ValueWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); -} - -void -FHoudiniHandleDetails::CreateNameWidget(FDetailWidgetRow& Row) -{ - TSharedRef VerticalBox = SNew(SVerticalBox); - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Translate")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Rotation")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString("Scale")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - - Row.NameWidget.Widget = VerticalBox; -} - -FString -FHoudiniHandleDetails::GetHandleTypeString(const EHoudiniHandleType& HandleType) -{ - switch (HandleType) - { - case EHoudiniHandleType::Bounder: - return FString("Bounder"); - case EHoudiniHandleType::Xform: - return FString("Xform"); - case EHoudiniHandleType::Unsupported: - return FString("Unsupported"); - - default: - break; - } - - return FString(""); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleDetails.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniHandleTranslator.h" +#include "HoudiniHandleComponentVisualizer.h" + +#include "DetailCategoryBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailGroup.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Images/SImage.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +void +FHoudiniHandleDetails::CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles) +{ + + if (InHandles.Num() <= 0) + return; + + UHoudiniHandleComponent* MainHandle = InHandles[0]; + + if (!MainHandle || MainHandle->IsPendingKill()) + return; + + + FString HandleNameStr = MainHandle->GetHandleName() + TEXT(" (") + GetHandleTypeString(MainHandle->GetHandleType()) + TEXT(" )"); + FName HandleName = FName(*HandleNameStr); + IDetailGroup& Group = HouHandleCategory.AddGroup(HandleName, FText::FromString(HandleNameStr), false, false); + + // Create a widget row for this handle + FDetailWidgetRow& Row = Group.AddWidgetRow(); + + CreateNameWidget(Row); + + // Create value widget + + TSharedRef ValueWidgetVerticalBox = SNew(SVerticalBox); + + // Translate + auto OnLocationChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FVector Location = MainHandle->GetRelativeTransform().GetLocation(); + + if (Axis == 0) + Location.X = Val; + else if (Axis == 1) + Location.Y = Val; + else + Location.Z = Val; + + MainHandle->SetRelativeLocation(Location); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertLocationToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + FVector DefaultLocation = FVector(0.f, 0.f, 0.f); + MainHandle->SetRelativeLocation(DefaultLocation); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetLocation().Z; }) + .OnXCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 0); + }) + .OnYCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 1); + }) + .OnZCommitted_Lambda([OnLocationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnLocationChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeLocation() == FVector::ZeroVector) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertLocationToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + // Rotation + auto OnRotationChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FQuat Rotation = MainHandle->GetRelativeTransform().GetRotation(); + + if (Axis == 0) + Rotation.X = Val; + else if (Axis == 1) + Rotation.Y = Val; + else + Rotation.Z = Val; + + MainHandle->SetRelativeRotation(Rotation); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertRotationToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + MainHandle->SetRelativeRotation(FQuat::Identity); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetRotation().Z; }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 0); + }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 1); + }) + .OnXCommitted_Lambda([OnRotationChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnRotationChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeTransform().GetRotation() == FQuat::Identity) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertRotationToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + // Scale + auto OnScaleChangedLambda = [MainHandle](float Val, int32 Axis) + { + if (!MainHandle) + return; + + FVector Scale = MainHandle->GetRelativeTransform().GetScale3D(); + + if (Axis == 0) + Scale.X = Val; + else if (Axis == 1) + Scale.Y = Val; + else + Scale.Z = Val; + + MainHandle->SetRelativeScale3D(Scale); + FHoudiniHandleTranslator::UpdateTransformParameters(MainHandle); + }; + + auto RevertScaleToDefault = [MainHandle]() + { + if (!MainHandle) + return FReply::Handled(); + + MainHandle->SetRelativeScale3D(FVector::OneVector); + + return FReply::Handled(); + }; + + ValueWidgetVerticalBox->AddSlot().Padding(2.0f, 2.0f, 5.0f, 2.0f).VAlign(VAlign_Top) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().X; }) + .Y_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Y; }) + .Z_Lambda([MainHandle]() {return MainHandle->GetRelativeTransform().GetScale3D().Z; }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 0); + }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 1); + }) + .OnXCommitted_Lambda([OnScaleChangedLambda](float Val, ETextCommit::Type TextCommitType) + { + OnScaleChangedLambda(Val, 2); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([MainHandle]() + { + if (!MainHandle) + return EVisibility::Hidden; + + if (MainHandle->GetRelativeTransform().GetScale3D() == FVector::OneVector) + return EVisibility::Hidden; + + return EVisibility::Visible; + }) + .OnClicked_Lambda(RevertScaleToDefault) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + + Row.ValueWidget.Widget = ValueWidgetVerticalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + + auto OnMouseEnterLambda = [MainHandle](const FGeometry& Geometry, const FPointerEvent& Event) + { + if (!MainHandle) + return; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); + + if (HandleVisualizer.IsValid()) + { + HandleVisualizer->SetEditedComponent(MainHandle); + } + }; + + auto OnMouseLeaveLambda = [MainHandle](const FPointerEvent& Event) + { + if (!MainHandle) + return; + + TSharedPtr Visualizer = GUnrealEd->FindComponentVisualizer(UHoudiniHandleComponent::StaticClass()->GetFName()); + TSharedPtr HandleVisualizer = StaticCastSharedPtr(Visualizer); + + if (HandleVisualizer.IsValid()) + { + HandleVisualizer->SetEditedComponent(nullptr); + } + }; + + // Set on mouse leave UI widget callback functions + Row.NameWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); + Row.ValueWidget.Widget->SetOnMouseEnter(FNoReplyPointerEventHandler::CreateLambda(OnMouseEnterLambda)); + + Row.NameWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); + Row.ValueWidget.Widget->SetOnMouseLeave(FSimpleNoReplyPointerEventHandler::CreateLambda(OnMouseLeaveLambda)); +} + +void +FHoudiniHandleDetails::CreateNameWidget(FDetailWidgetRow& Row) +{ + TSharedRef VerticalBox = SNew(SVerticalBox); + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Translate")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Rotation")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().AutoHeight().Padding(2.0f, 5.0f, 5.0f, 5.0f).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString("Scale")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + + Row.NameWidget.Widget = VerticalBox; +} + +FString +FHoudiniHandleDetails::GetHandleTypeString(const EHoudiniHandleType& HandleType) +{ + switch (HandleType) + { + case EHoudiniHandleType::Bounder: + return FString("Bounder"); + case EHoudiniHandleType::Xform: + return FString("Xform"); + case EHoudiniHandleType::Unsupported: + return FString("Unsupported"); + + default: + break; + } + + return FString(""); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h index b56257c67..14dfd7c8c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "DetailWidgetRow.h" - -class UHoudiniHandleComponent; -class IDetailCategoryBuilder; -enum class EHoudiniHandleType : uint8; - -class FHoudiniHandleDetails : public TSharedFromThis -{ -public: - static void CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles); - - static void CreateNameWidget(FDetailWidgetRow& Row); - - static FString GetHandleTypeString(const EHoudiniHandleType& HandleType); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "DetailWidgetRow.h" + +class UHoudiniHandleComponent; +class IDetailCategoryBuilder; +enum class EHoudiniHandleType : uint8; + +class FHoudiniHandleDetails : public TSharedFromThis +{ +public: + static void CreateWidget(IDetailCategoryBuilder & HouHandleCategory, TArray &InHandles); + + static void CreateNameWidget(FDetailWidgetRow& Row); + + static FString GetHandleTypeString(const EHoudiniHandleType& HandleType); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp index 1efc1ec3e..c0f7ab6c4 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp @@ -1,5024 +1,5024 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInputDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniInput.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetBlueprintComponent.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniLandscapeTranslator.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniPackageParams.h" - -#include "Editor.h" -#include "DetailLayoutBuilder.h" -#include "DetailCategoryBuilder.h" -#include "IDetailCustomization.h" -#include "DetailWidgetRow.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SRotatorInputBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SNumericEntryBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SEditableText.h" -#include "Widgets/Layout/SUniformGridPanel.h" -#include "Editor/UnrealEd/Public/AssetThumbnail.h" -#include "Editor/PropertyEditor/Private/SDetailsViewBase.h" -#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" -#include "SAssetDropTarget.h" -#include "ScopedTransaction.h" -#include "Engine/SkeletalMesh.h" -#include "Engine/Selection.h" -#include "EngineUtils.h" -#include "AssetData.h" -#include "Framework/SlateDelegates.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "Modules/ModuleManager.h" -#include "SceneOutlinerModule.h" - -#include "Editor/UnrealEdEngine.h" -#include "HoudiniSplineComponentVisualizer.h" -#include "UnrealEdGlobals.h" -#include "Widgets/SWidget.h" - -#include "HoudiniEngineRuntimeUtils.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -// Customized TextBlock to show 'editing...' text if this Houdini Spline Component is being edited -class SCurveEditingTextBlock : public STextBlock -{ -public: - UHoudiniSplineComponent* HoudiniSplineComponent; - TSharedPtr HoudiniSplineComponentVisualizer; -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override - { - if (!HoudiniSplineComponentVisualizer.IsValid() || !HoudiniSplineComponent) - return LayerId; - - if (HoudiniSplineComponentVisualizer->GetEditedHoudiniSplineComponent() != HoudiniSplineComponent) - return LayerId; - - return STextBlock::OnPaint(Args, AllottedGeometry, MyClippingRect, - OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - } -}; - -void -FHoudiniInputDetails::CreateWidget( - IDetailCategoryBuilder& HouInputCategory, - TArray InInputs, - FDetailWidgetRow* InputRow) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouInputCategory.GetParentLayout().GetThumbnailPool(); - - EHoudiniInputType MainInputType = MainInput->GetInputType(); - UHoudiniAssetComponent* HAC = MainInput->GetTypedOuter(); - - // Create a widget row, or get the given row. - FDetailWidgetRow* Row = InputRow; - Row = InputRow == nullptr ? &(HouInputCategory.AddCustomRow(FText::GetEmpty())) : InputRow; - if (!Row) - return; - - // Create the standard input name widget if this is not a operator path parameter. - // Operator path parameter's name widget is handled by HoudiniParameterDetails. - if (!InputRow) - CreateNameWidget(MainInput, *Row, true, InInputs.Num()); - - // Create a vertical Box for storing the UI - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - // ComboBox : Input Type - const IDetailsView* DetailsView = HouInputCategory.GetParentLayout().GetDetailsView(); - AddInputTypeComboBox(HouInputCategory, VerticalBox, InInputs, DetailsView); - - // Checkbox : Keep World Transform - AddKeepWorldTransformCheckBox(VerticalBox, InInputs); - - - // Checkbox : CurveInput trigger cook on curve changed - AddCurveInputCookOnChangeCheckBox(VerticalBox, InInputs); - - - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) - { - // Checkbox : Pack before merging - AddPackBeforeMergeCheckbox(VerticalBox, InInputs); - } - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World || MainInputType == EHoudiniInputType::Asset) - { - AddImportAsReferenceCheckbox(VerticalBox, InInputs); - } - - if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) - { - // Checkboxes : Export LODs / Sockets / Collisions - AddExportCheckboxes(VerticalBox, InInputs); - } - - switch (MainInput->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - AddGeometryInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); - } - break; - - case EHoudiniInputType::Asset: - { - AddAssetInputUI(VerticalBox, InInputs); - } - break; - - case EHoudiniInputType::Curve: - { - AddCurveInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); - } - break; - - case EHoudiniInputType::Landscape: - { - AddLandscapeInputUI(VerticalBox, InInputs); - } - break; - - case EHoudiniInputType::World: - { - AddWorldInputUI(HouInputCategory, VerticalBox, InInputs, DetailsView); - } - break; - - case EHoudiniInputType::Skeletal: - { - AddSkeletalInputUI(VerticalBox, InInputs, AssetThumbnailPool); - } - break; - } - - - Row->ValueWidget.Widget = VerticalBox; - - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - //Row.ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniInputDetails::CreateNameWidget( - UHoudiniInput* InInput, FDetailWidgetRow & Row, bool bLabel, int32 InInputCount) -{ - if (!InInput || InInput->IsPendingKill()) - return; - - FString InputLabelStr = InInput->GetLabel(); - if (InInputCount > 1) - { - InputLabelStr += TEXT(" (") + FString::FromInt(InInputCount) + TEXT(")"); - } - - const FText & FinalInputLabelText = bLabel ? FText::FromString(InputLabelStr) : FText::GetEmpty(); - FText InputTooltip = GetInputTooltip(InInput); - { - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(FinalInputLabelText) - .ToolTipText(InputTooltip) - .Font(FEditorStyle::GetFontStyle(!InInput->HasChanged() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))); - } -} - -FText -FHoudiniInputDetails::GetInputTooltip(UHoudiniInput* InParam) -{ - // TODO - return FText(); -} - -void -FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuilder, TSharedRef VerticalBox, TArray& InInputs, const IDetailsView* DetailsView) -{ - // Get the details view name and locked status - bool bDetailsLocked = false; - FName DetailsPanelName = "LevelEditorSelectionDetails"; - if (DetailsView) - { - DetailsPanelName = DetailsView->GetIdentifier(); - if (DetailsView->IsLocked()) - bDetailsLocked = true; - } - - // Lambda return a FText correpsonding to an input's current type - auto GetInputText = [](UHoudiniInput* InInput) - { - return FText::FromString(InInput->GetInputTypeAsString()); - }; - - // Lambda for changing inputs type - auto OnSelChanged = [DetailsPanelName, &CategoryBuilder](TArray InInputsToUpdate, TSharedPtr InNewChoice) - { - if (!InNewChoice.IsValid()) - return; - - EHoudiniInputType NewInputType = UHoudiniInput::StringToInputType(*InNewChoice.Get()); - if (NewInputType != EHoudiniInputType::World) - { - Helper_CancelWorldSelection(InInputsToUpdate, DetailsPanelName); - } - - if (InInputsToUpdate.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputsToUpdate[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetBlueprintComponent* HAB = MainInput->GetTypedOuter(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Input Type"), - MainInput->GetOuter()); - - bool bBlueprintStructureModified = false; - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetInputType() == NewInputType) - continue; - - /* This causes multiple issues. It does not set reset the previous type variable to Invalid sometimes - and it causes re-cook infinitely after few undo changing type. - { - CurInput->SetInputType(NewInputType); - CurInput->Modify(); - } - */ - - { - // Cache the current input type for undo type changing (since new type becomes previous type after undo) - EHoudiniInputType PrevType = CurInput->GetPreviousInputType(); - CurInput->SetPreviousInputType(NewInputType); - - CurInput->Modify(); - CurInput->SetPreviousInputType(PrevType); - CurInput->SetInputType(NewInputType, bBlueprintStructureModified); // pass in false for 2nd parameter in order to avoid creating default curve if empty - } - CurInput->MarkChanged(true); - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - - } - - if (HAB) - { - if (bBlueprintStructureModified) - HAB->MarkAsBlueprintStructureModified(); - } - - }; - - UHoudiniInput* MainInput = InInputs[0]; - TArray>* SupportedChoices = nullptr; - UHoudiniAssetBlueprintComponent* HAC = MainInput->GetTypedOuter(); - if (HAC) - { - SupportedChoices = FHoudiniEngineEditor::Get().GetBlueprintInputTypeChoiceLabels(); - } - else - { - SupportedChoices = FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels(); - } - - // ComboBox : Input Type - TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBoxInputType; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(ComboBoxInputType, SComboBox>) - .OptionsSource(SupportedChoices) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels())[((int32)MainInput->GetInputType() - 1)]) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([=](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnSelChanged(InInputs, NewChoice); - }) - [ - SNew( STextBlock ) - .Text_Lambda([=]() - { - return GetInputText(MainInput); - }) - .Font( FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; -} - -void -FHoudiniInputDetails:: AddCurveInputCookOnChangeCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - - if (!MainInput || MainInput->GetInputType() != EHoudiniInputType::Curve) - return; - - auto IsCheckedCookOnChange = [MainInput]() - { - if (!MainInput) - return ECheckBoxState::Checked; - - return MainInput->GetCookOnCurveChange() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - auto CheckStateChangedCookOnChange = [InInputs](ECheckBoxState NewState) - { - bool bChecked = NewState == ECheckBoxState::Checked; - for (auto & NextInput : InInputs) - { - if (!NextInput) - continue; - - NextInput->SetCookOnCurveChange(bChecked); - } - }; - - // Checkbox : Trigger cook on input curve changed - TSharedPtr< SCheckBox > CheckBoxCookOnCurveChanged; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxCookOnCurveChanged, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookOnCurveChangedCheckbox", "Auto-update")) - .ToolTipText(LOCTEXT("CookOnCurveChangeCheckboxTip", "When checked, cook is triggered automatically when the curve is modified.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedCookOnChange, MainInput]() - { - return IsCheckedCookOnChange(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedCookOnChange](ECheckBoxState NewState) - { - return CheckStateChangedCookOnChange( NewState); - }) - ]; - -} - -void -FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current KeepWorldTransform state - auto IsCheckedKeepWorldTransform = [&](UHoudiniInput* InInput) - { - return InInput->GetKeepWorldTransform() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing KeepWorldTransform state - auto CheckStateChangedKeepWorldTransform = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (MainInput->GetKeepWorldTransform() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Keep World Transform"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetKeepWorldTransform() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetKeepWorldTransform(bNewState); - CurInput->MarkChanged(true); - } - }; - - - // Checkbox : Keep World Transform - TSharedPtr< SCheckBox > CheckBoxTranformType; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxTranformType, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("KeepWorldTransformCheckbox", "Keep World Transform")) - .ToolTipText(LOCTEXT("KeepWorldTransformCheckboxTip", "Set this Input's object_merge Transform Type to INTO_THIS_OBJECT. If unchecked, it will be set to NONE.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedKeepWorldTransform(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedKeepWorldTransform(InInputs, NewState); - }) - ]; - - // the checkbox is read only for geo inputs - if (MainInput->GetInputType() == EHoudiniInputType::Geometry) - CheckBoxTranformType->SetEnabled(false); - - // Checkbox is read only if the input is an object-path parameter - //if (MainInput->IsObjectPathParameter() ) - // CheckBoxTranformType->SetEnabled(false); -} - -void -FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current PackBeforeMerge state - auto IsCheckedPackBeforeMerge = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetPackBeforeMerge() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing PackBeforeMerge state - auto CheckStateChangedPackBeforeMerge = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetPackBeforeMerge() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetPackBeforeMerge() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetPackBeforeMerge(bNewState); - CurInput->MarkChanged(true); - } - }; - - TSharedPtr< SCheckBox > CheckBoxPackBeforeMerge; - VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() - [ - SAssignNew( CheckBoxPackBeforeMerge, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "PackBeforeMergeCheckbox", "Pack Geometry before merging" ) ) - .ToolTipText( LOCTEXT( "PackBeforeMergeCheckboxTip", "Pack each separate piece of geometry before merging them into the input." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedPackBeforeMerge(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedPackBeforeMerge(InInputs, NewState); - }) - ]; -} - -void -FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current PackBeforeMerge state - auto IsCheckedImportAsReference= [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetImportAsReference() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing PackBeforeMerge state - auto CheckStateChangedImportAsReference= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetImportAsReference() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetImportAsReference() == bNewState) - continue; - - TArray * InputObjs = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (InputObjs) - { - // Mark all its input objects as changed to trigger recook. - for (auto CurInputObj : *InputObjs) - { - if (!CurInputObj || CurInputObj->IsPendingKill()) - continue; - - if (CurInputObj->GetImportAsReference() != bNewState) - { - CurInputObj->SetImportAsReference(bNewState); - CurInputObj->MarkChanged(true); - } - } - } - - CurInput->Modify(); - CurInput->SetImportAsReference(bNewState); - } - }; - - TSharedPtr< SCheckBox > CheckBoxImportAsReference; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxImportAsReference, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("ImportInputAsRefCheckbox", "Import input as references")) - .ToolTipText(LOCTEXT("ImportInputAsRefCheckboxTip", "Import input objects as references. (Geometry, World and Asset input types only)")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedImportAsReference(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedImportAsReference(InInputs, NewState); - }) - ]; -} -void -FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Lambda returning a CheckState from the input's current ExportLODs state - auto IsCheckedExportLODs = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportLODs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda returning a CheckState from the input's current ExportSockets state - auto IsCheckedExportSockets = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportSockets() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda returning a CheckState from the input's current ExportColliders state - auto IsCheckedExportColliders = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetExportColliders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing ExportLODs state - auto CheckStateChangedExportLODs = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportLODs() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export LODs"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportLODs() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportLODs(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - // Lambda for changing ExportSockets state - auto CheckStateChangedExportSockets = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportSockets() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Sockets"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportSockets() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportSockets(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - // Lambda for changing ExportColliders state - auto CheckStateChangedExportColliders = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - if (MainInput->GetExportColliders() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Colliders"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetExportColliders() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetExportColliders(bNewState); - CurInput->MarkChanged(true); - CurInput->MarkAllInputObjectsChanged(true); - } - }; - - TSharedPtr< SCheckBox > CheckBoxExportLODs; - TSharedPtr< SCheckBox > CheckBoxExportSockets; - TSharedPtr< SCheckBox > CheckBoxExportColliders; - VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew(CheckBoxExportLODs, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportAllLOD", "Export LODs" ) ) - .ToolTipText( LOCTEXT( "ExportAllLODCheckboxTip", "If enabled, all LOD Meshes in this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportLODs(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportLODs(InInputs, NewState); - }) - ] - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( CheckBoxExportSockets, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportSockets", "Export Sockets" ) ) - .ToolTipText( LOCTEXT( "ExportSocketsTip", "If enabled, all Mesh Sockets in this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportSockets(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportSockets(InInputs, NewState); - }) - ] - + SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( CheckBoxExportColliders, SCheckBox ) - .Content() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "ExportColliders", "Export Colliders" ) ) - .ToolTipText( LOCTEXT( "ExportCollidersTip", "If enabled, collision geometry for this static mesh will be sent to Houdini." ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - .IsChecked_Lambda([=]() - { - return IsCheckedExportColliders(MainInput); - }) - .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) - { - return CheckStateChangedExportColliders(InInputs, NewState); - }) - ] - ]; -} - -void -FHoudiniInputDetails::AddGeometryInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool ) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - - // Lambda for changing ExportColliders state - auto SetGeometryInputObjectsCount = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& NewInputCount) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing the number of Geometry Input Objects"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry) == NewInputCount) - continue; - - CurInput->Modify(); - - CurInput->SetInputObjectsNumber(EHoudiniInputType::Geometry, NewInputCount); - CurInput->MarkChanged(true); - - // - if (GEditor) - GEditor->RedrawAllViewports(); - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - }; - - InVerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs, NumInputObjects]() - { - return SetGeometryInputObjectsCount(InInputs, NumInputObjects + 1); - }), - LOCTEXT("AddInput", "Adds a Geometry Input"), true) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs]() - { - return SetGeometryInputObjectsCount(InInputs, 0); - }), - LOCTEXT("EmptyInputs", "Removes All Inputs"), true) - ] - ]; - - for (int32 GeometryObjectIdx = 0; GeometryObjectIdx < NumInputObjects; GeometryObjectIdx++) - { - //UObject* InputObject = InParam.GetInputObject(Idx); - Helper_CreateGeometryWidget(CategoryBuilder, InInputs, GeometryObjectIdx, AssetThumbnailPool, InVerticalBox); - } -} - - - -// Create a single geometry widget for the given input object -void -FHoudiniInputDetails::Helper_CreateGeometryWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InGeometryObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox ) -{ - UHoudiniInput* MainInput = InInputs[0]; - - // Access the object used in the corresponding geometry input - UHoudiniInputObject* HoudiniInputObject = MainInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - UObject* InputObject = HoudiniInputObject ? HoudiniInputObject->GetObject() : nullptr; - - // Create thumbnail for this static mesh. - TSharedPtr StaticMeshThumbnail = MakeShareable( - new FAssetThumbnail(InputObject, 64, 64, AssetThumbnailPool)); - - // Lambda for adding new geometry input objects - auto UpdateGeometryObjectAt = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& AtIndex, UObject* InObject) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - if (!InObject || InObject->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changing a Geometry Input Object"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - UObject* InputObject = CurInput->GetInputObjectAt(EHoudiniInputType::Geometry, AtIndex); - if (InObject == InputObject) - continue; - - UHoudiniInputObject* CurrentInputObjectWrapper = CurInput->GetHoudiniInputObjectAt(AtIndex); - if (CurrentInputObjectWrapper) - CurrentInputObjectWrapper->Modify(); - - CurInput->Modify(); - - CurInput->SetInputObjectAt(EHoudiniInputType::Geometry, AtIndex, InObject); - CurInput->MarkChanged(true); - - // TODO: Not needed? - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - }; - - // Drop Target: Static/Skeletal Mesh - TSharedPtr HorizontalBox = NULL; - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop_Lambda([]( const UObject* InObject) - { - return UHoudiniInput::IsObjectAcceptable(EHoudiniInputType::Geometry, InObject); - }) - .OnAssetDropped_Lambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt](UObject* InObject) - { - return UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, InObject); - }) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - // Thumbnail : Static Mesh - FText ParameterLabelText = FText::FromString(MainInput->GetLabel()); - - TSharedPtr< SBorder > StaticMeshThumbnailBorder; - HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(StaticMeshThumbnailBorder, SBorder) - .Padding(5.0f) - .OnMouseDoubleClick_Lambda([MainInput, InGeometryObjectIdx](const FGeometry&, const FPointerEvent&) - { - UObject* InputObject = MainInput->GetInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - if (GEditor && InputObject) - GEditor->EditObject(InputObject); - - return FReply::Handled(); - }) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(ParameterLabelText) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - TWeakPtr WeakStaticMeshThumbnailBorder(StaticMeshThumbnailBorder); - StaticMeshThumbnailBorder->SetBorderImage(TAttribute::Create( - TAttribute::FGetter::CreateLambda([WeakStaticMeshThumbnailBorder]() - { - TSharedPtr ThumbnailBorder = WeakStaticMeshThumbnailBorder.Pin(); - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ))); - - FText MeshNameText = FText::GetEmpty(); - if (InputObject) - MeshNameText = FText::FromString(InputObject->GetName()); - - - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add Combo box : Static Mesh - TSharedPtr StaticMeshComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SAssignNew(StaticMeshComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(MeshNameText) - ] - ] - ]; - - - TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); - StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( - [MainInput, InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt]() - { - TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); - UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); - - TArray< UFactory * > NewAssetFactories; - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(DefaultObj), - true, - AllowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda( - [InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) - { - TSharedPtr ComboButton = WeakStaticMeshComboButton.Pin(); - if (ComboButton.IsValid()) - { - ComboButton->SetIsOpen(false); - UObject * Object = AssetData.GetAsset(); - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); - } - } - ), - FSimpleDelegate::CreateLambda([]() {}) - ); - } - )); - - - // Add buttons - TSharedPtr ButtonHorizontalBox; - ComboAndButtonBox->AddSlot() - .FillHeight(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ButtonHorizontalBox, SHorizontalBox) - ]; - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add( TEXT( "Asset" ), MeshNameText ); - FText StaticMeshTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", - "Browse to '{Asset}' in Content Browser" ), Args ); - - // Button : Use selected in content browser - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton(FSimpleDelegate::CreateLambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt]() - { - if (GEditor) - { - TArray CBSelections; - GEditor->GetContentBrowserSelections(CBSelections); - - // Get the first selected static mesh object - UObject* Object = nullptr; - for (auto & CurAssetData : CBSelections) - { - if (CurAssetData.AssetClass != UStaticMesh::StaticClass()->GetFName()) - continue; - - Object = CurAssetData.GetAsset(); - break; - } - - if (Object && !Object->IsPendingKill()) - { - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); - } - } - }), TAttribute< FText >(LOCTEXT("GeometryInputUseSelectedAssetFromCB", "Use Selected Asset from Content Browser"))) - ]; - - // Button : Browse Static Mesh - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateLambda([MainInput, InGeometryObjectIdx]() - { - UObject* InputObject = MainInput->GetInputObjectAt(InGeometryObjectIdx); - if (GEditor && InputObject) - { - TArray Objects; - Objects.Add(InputObject); - GEditor->SyncBrowserToObjects(Objects); - } - }), - TAttribute< FText >( StaticMeshTooltip ) - ) - ]; - - // ButtonBox : Reset - ButtonHorizontalBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default static mesh" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked_Lambda( [UpdateGeometryObjectAt, InInputs, InGeometryObjectIdx]() - { - UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, nullptr); - return FReply::Handled(); - }) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ]; - - // Insert/Delete/Duplicate - ButtonHorizontalBox->AddSlot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( - FExecuteAction::CreateLambda( [ InInputs, InGeometryObjectIdx, MainInput ]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: insert a Geometry Input Object"), - MainInput->GetOuter()); - // Insert - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->InsertInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - } - } ), - FExecuteAction::CreateLambda([MainInput, InInputs, InGeometryObjectIdx]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: delete a Geometry Input Object"), - MainInput->GetOuter()); - - // Delete - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->DeleteInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - - if (GEditor) - GEditor->RedrawAllViewports(); - } - } ), - FExecuteAction::CreateLambda([InInputs, InGeometryObjectIdx, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), - MainInput->GetOuter()); - - // Duplicate - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->DuplicateInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - } - } ) ) - ]; - - // TRANSFORM OFFSET EXPANDER - { - TSharedPtr ExpanderArrow; - TSharedPtr ExpanderImage; - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SAssignNew( ExpanderArrow, SButton ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ClickMethod( EButtonClickMethod::MouseDown ) - .Visibility( EVisibility::Visible ) - .OnClicked(FOnClicked::CreateLambda([InInputs, InGeometryObjectIdx, MainInput, &CategoryBuilder]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled();; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), - MainInput->GetOuter()); - - // Expand transform - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - CurInput->OnTransformUIExpand(InGeometryObjectIdx); - } - - // TODO: Not needed? - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - })) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ] - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( STextBlock ) - .Text( LOCTEXT("GeoInputTransform", "Transform Offset") ) - .ToolTipText( LOCTEXT( "GeoInputTransformTooltip", "Transform offset used for correction before sending the asset to Houdini" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - ]; - - TWeakPtr WeakExpanderArrow(ExpanderArrow); - // Set delegate for image - ExpanderImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([InGeometryObjectIdx, MainInput, WeakExpanderArrow]() - { - FName ResourceName; - TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); - if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) - { - ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - return FEditorStyle::GetBrush(ResourceName); - } - ))); - } - - // Lambda for changing the transform values - auto ChangeTransformOffsetAt = [&](const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex, const bool& DoChange, TArray InInputs) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputTransformChange", "Houdini Input: Changing Transform offset"), - InInputs[0]->GetOuter()); - - bool bChanged = true; - for (int Idx = 0; Idx < InInputs.Num(); Idx++) - { - if (!InInputs[Idx] || InInputs[Idx]->IsPendingKill()) - continue; - - UHoudiniInputObject* InputObject = InInputs[Idx]->GetHoudiniInputObjectAt(AtIndex); - if (InputObject) - InputObject->Modify(); - - bChanged &= InInputs[Idx]->SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); - } - - if (bChanged && DoChange) - { - // Mark the values as changed to trigger an update - for (int Idx = 0; Idx < InInputs.Num(); Idx++) - { - InInputs[Idx]->MarkChanged(true); - } - } - else - { - // Cancel the transaction - Transaction.Cancel(); - } - }; - - // Get Visibility of reset buttons - bool bResetButtonVisiblePosition = false; - bool bResetButtonVisibleRotation = false; - bool bResetButtonVisibleScale = false; - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - FTransform* CurTransform = CurInput->GetTransformOffset(InGeometryObjectIdx); - if (!CurTransform) - continue; - - if (CurTransform->GetLocation() != FVector::ZeroVector) - bResetButtonVisiblePosition = true; - - FRotator Rotator = CurTransform->Rotator(); - if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) - bResetButtonVisibleRotation = true; - - if (CurTransform->GetScale3D() != FVector::OneVector) - bResetButtonVisibleScale = true; - } - - auto ChangeTransformOffsetUniformlyAt = [InGeometryObjectIdx, InInputs, ChangeTransformOffsetAt](const float & Val, const int32& PosRotScaleIndex) - { - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 0, true, InInputs); - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 1, true, InInputs); - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 2, true, InInputs); - }; - - // TRANSFORM OFFSET - if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) - { - // Position - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew(STextBlock) - .Text( LOCTEXT("GeoInputTranslate", "T") ) - .ToolTipText( LOCTEXT( "GeoInputTranslateTooltip", "Translate" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SVectorInputBox ) - .bColorAxisLabels( true ) - .AllowSpin(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetX, InGeometryObjectIdx))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetY, InGeometryObjectIdx))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetPositionOffsetZ, InGeometryObjectIdx))) - .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 0, true, InInputs); }) - .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 1, true, InInputs); }) - .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 2, true, InInputs); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([MainInput, ChangeTransformOffsetUniformlyAt, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 0); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - - // Rotation - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text( LOCTEXT("GeoInputRotate", "R") ) - .ToolTipText( LOCTEXT( "GeoInputRotateTooltip", "Rotate" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SRotatorInputBox ) - .AllowSpin( true ) - .bColorAxisLabels( true ) - .Roll(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetRoll, InGeometryObjectIdx))) - .Pitch(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetPitch, InGeometryObjectIdx))) - .Yaw(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetRotationOffsetYaw, InGeometryObjectIdx))) - .OnRollCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 0, true, InInputs); }) - .OnPitchCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 1, true, InInputs); }) - .OnYawCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 2, true, InInputs); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (Not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 1); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - - // Scale - bool bLocked = false; - if (HoudiniInputObject) - bLocked = HoudiniInputObject->IsUniformScaleLocked(); - - VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 1.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( STextBlock ) - .Text( LOCTEXT( "GeoInputScale", "S" ) ) - .ToolTipText( LOCTEXT( "GeoInputScaleTooltip", "Scale" ) ) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew( SVectorInputBox ) - .bColorAxisLabels( true ) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetX, InGeometryObjectIdx))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetY, InGeometryObjectIdx))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateUObject( - MainInput, &UHoudiniInput::GetScaleOffsetZ, InGeometryObjectIdx))) - .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 0, true, InInputs); - }) - .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 1, true, InInputs); - }) - .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) - { - if (bLocked) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 2, true, InInputs); - }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - SNew(SHorizontalBox) - // Lock Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ToolTipText(HoudiniInputObject ? - LOCTEXT("GeoInputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the input object maintains its shape in each direction when scaled") : - LOCTEXT("GeoInputLockButtonToolTipNoObject", "No input object selected")) - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(bLocked ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([InInputs, MainInput, InGeometryObjectIdx, HoudiniInputObject, &CategoryBuilder]() - { - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - UHoudiniInputObject*CurInputObject = CurInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - CurInputObject->SwitchUniformScaleLock(); - } - - if (HoudiniInputObject) - { - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); - }) - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() - { - ChangeTransformOffsetUniformlyAt(1.0f, 2); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - return FReply::Handled(); - }) - ] - ] - ]; - } -} - -void -FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Houdini Asset Picker Widget - { - FMenuBuilder MenuBuilder = Helper_CreateHoudiniAssetPickerWidget(InInputs); - - VerticalBox->AddSlot() - .Padding(2.0f, 2.0f, 5.0f, 2.0f) - .AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - // Button : Clear Selection - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - auto IsClearButtonEnabled = [MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return false; - - TArray* AssetInputObjectsArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - - if (!AssetInputObjectsArray) - return false; - - if (AssetInputObjectsArray->Num() <= 0) - return false; - - return true; - }; - - FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChangeClear", "Houdini Input: Clearing asset input selection"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - TArray* AssetInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - if (!AssetInputObjectsArray) - continue; - - CurrentInput->Modify(); - - AssetInputObjectsArray->Empty(); - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }); - - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - // Button : Clear Selection - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("ClearSelection", "Clear Selection")) - .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) - .IsEnabled_Lambda(IsClearButtonEnabled) - .OnClicked(OnClearSelect) - ] - ]; - - // Do not enable select all/clear select when selection has been started and details are locked - //HorizontalBox->SetEnabled(!bDetailsLocked); - } - - -} - -void -FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs, TSharedPtr AssetThumbnailPool) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Curve); - - // lambda for inserting an input Houdini curve. - auto InsertAnInputCurve = [MainInput, &CategoryBuilder](const int32& NewInputCount) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetComponent* OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - // Do not insert input object when the HAC does not finish cooking - EHoudiniAssetState CurrentHACState = OuterHAC->GetAssetState(); - if (CurrentHACState >= EHoudiniAssetState::PreCook && CurrentHACState<= EHoudiniAssetState::Processing) - return; - - // Clear the to be inserted object array, which records the pointers of the input objects to be inserted. - MainInput->LastInsertedInputs.Empty(); - // Record the pointer of the object to be inserted before transaction for undo the insert action. - bool bBlueprintStructureModified = false; - UHoudiniInputHoudiniSplineComponent* NewInput = MainInput->CreateHoudiniSplineInput(nullptr, true, false, bBlueprintStructureModified); - MainInput->LastInsertedInputs.Add(NewInput); - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction(FText::FromString("Modifying Houdini input: Adding curve input.")); - MainInput->Modify(); - - // Modify the MainInput. - MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType())->Add(NewInput); - - MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, NewInputCount); - - if (bBlueprintStructureModified) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - // Add Rot/Scale attribute checkbox - FText TooltipText = LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesTooltip", "If enabled, rot and scale attributes will be added per to the input curve on each control points."); - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesLabel", "Add rot & scale Attributes")) - .ToolTipText(TooltipText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - //.MinDesiredWidth(160.f) - ] - .OnCheckStateChanged_Lambda([InInputs](ECheckBoxState NewState) - { - const bool bChecked = (NewState == ECheckBoxState::Checked); - for (auto& CurrentInput : InInputs) - { - if (!IsValid(CurrentInput)) - continue; - - CurrentInput->SetAddRotAndScaleAttributes(bChecked); - } - }) - .IsChecked_Lambda([MainInput]() - { - if (!IsValid(MainInput)) - return ECheckBoxState::Unchecked; - - return MainInput->IsAddRotAndScaleAttributesEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .ToolTipText(TooltipText) - ]; - - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([InsertAnInputCurve, NumInputObjects]() - { - return InsertAnInputCurve(NumInputObjects+1); - //return SetCurveInputObjectsCount(NumInputObjects+1); - }), - - LOCTEXT("AddInputCurve", "Adds a Curve Input"), true) - ] - + SHorizontalBox::Slot() - .Padding(1.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateLambda([InInputs, MainInput, &CategoryBuilder]() - { - TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - - // Detach all curves before deleting. - for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) - { - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - } - - // Clear the insert objects buffer before transaction. - MainInput->LastInsertedInputs.Empty(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction(FText::FromString("Modifying Houdini Input: Delete curve inputs.")); - MainInput->Modify(); - - bool bBlueprintStructureModified = false; - - // actual delete. - for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) - { - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - MainInput->RemoveSplineFromInputObject(HoudiniInput, bBlueprintStructureModified); - - MainInput->DeleteInputObjectAt(EHoudiniInputType::Curve, n); - } - - MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, 0); - - if (bBlueprintStructureModified) - { - UActorComponent* OuterComponent = Cast(MainInput->GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterComponent); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - - }), - LOCTEXT("EmptyInputsCurve", "Removes All Curve Inputs"), true) - ] - + SHorizontalBox::Slot().FillWidth(80.f).MaxWidth(80.f) - [ - SNew(SButton) - .Text(LOCTEXT("ResetCurveOffsetStr", "Reset Offset")) - .OnClicked_Lambda([MainInput]()->FReply - { - MainInput->ResetDefaultCurveOffset(); - return FReply::Handled(); - }) - ] - ]; - - //UHoudiniSplineComponent* SplineCompBeingEdited = nullptr; - TSharedPtr HouSplineComponentVisualizer; - if (GUnrealEd) - { - TSharedPtr Visualizer = - GUnrealEd->FindComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); - - HouSplineComponentVisualizer = StaticCastSharedPtr(Visualizer); - } - - - for (int n = 0; n < NumInputObjects; n++) - { - Helper_CreateCurveWidget(CategoryBuilder, InInputs, n, AssetThumbnailPool ,VerticalBox, HouSplineComponentVisualizer); - } -} - -void -FHoudiniInputDetails::Helper_CreateCurveWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InCurveObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox, - TSharedPtr HouSplineComponentVisualizer) -{ - UHoudiniInput* MainInput = InInputs[0]; - - if (!MainInput || MainInput->IsPendingKill()) - return; - - UHoudiniAssetComponent * OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - auto GetHoudiniSplineComponentAtIndex = [](UHoudiniInput * Input, int32 Index) - { - UHoudiniSplineComponent* FoundHoudiniSplineComponent = nullptr; - if (!Input || Input->IsPendingKill()) - return FoundHoudiniSplineComponent; - - // Get the TArray ptr to the curve objects in this input - TArray * CurveInputComponentArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - return FoundHoudiniSplineComponent; - - if (!CurveInputComponentArray->IsValidIndex(Index)) - return FoundHoudiniSplineComponent; - - // Access the object used in the corresponding Houdini curve input - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - FoundHoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - - return FoundHoudiniSplineComponent; - }; - - - // Get the TArray ptr to the curve objects in this input - TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - return; - - if (!CurveInputComponentArray->IsValidIndex(InCurveObjectIdx)) - return; - - // Access the object used in the corresponding Houdini curve input - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[InCurveObjectIdx]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent) - return; - - FString HoudiniSplineName = HoudiniSplineComponent->GetHoudiniSplineName(); - - // Editable label for the current Houdini curve - TSharedPtr LabelHorizontalBox; - VerticalBox->AddSlot() - .Padding(0, 2) - .AutoHeight() - [ - SAssignNew(LabelHorizontalBox, SHorizontalBox) - ]; - - - TSharedPtr LabelBlock; - LabelHorizontalBox->AddSlot() - .Padding(0, 15, 0, 2) - .MaxWidth(150.f) - .FillWidth(150.f) - .VAlign(VAlign_Bottom) - .HAlign(HAlign_Left) - [ - SNew(SBox).HeightOverride(20.f).WidthOverride(200.f).VAlign(VAlign_Center) - [ - SAssignNew(LabelBlock, SEditableText).Text(FText::FromString(HoudiniSplineName)) - .OnTextCommitted_Lambda([HoudiniSplineComponent](FText NewText, ETextCommit::Type CommitType) - { - if (CommitType == ETextCommit::Type::OnEnter) - { - HoudiniSplineComponent->SetHoudiniSplineName(NewText.ToString()); - } - }) - ] - ]; - - // 'Editing...' TextBlock showing if this component is being edited - TSharedPtr EditingTextBlock; - LabelHorizontalBox->AddSlot() - .Padding(0, 15, 0, 2) - .MaxWidth(55.f) - .FillWidth(55.f) - .VAlign(VAlign_Bottom) - .HAlign(HAlign_Left) - [ - SNew(SBox).HeightOverride(20.f).WidthOverride(75.f).VAlign(VAlign_Center) - [ - SAssignNew(EditingTextBlock, SCurveEditingTextBlock).Text(LOCTEXT("HoudiniCurveInputEditingLabel", "(editing...)")) - ] - ]; - - EditingTextBlock->HoudiniSplineComponent = HoudiniSplineComponent; - EditingTextBlock->HoudiniSplineComponentVisualizer = HouSplineComponentVisualizer; - - // Lambda for deleting the current curve input - auto DeleteHoudiniCurveAtIndex = [InInputs, InCurveObjectIdx, OuterHAC, CurveInputComponentArray, &CategoryBuilder]() - { - if (!OuterHAC|| OuterHAC->IsPendingKill()) - return; - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeDeleteACurve", "Houdini Input: Deleting a curve input"), - OuterHAC); - - int MainInputCurveArraySize = CurveInputComponentArray->Num(); - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - Input->Modify(); - - TArray* InputObjectArr = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!InputObjectArr) - continue; - - if (!InputObjectArr->IsValidIndex(InCurveObjectIdx)) - continue; - - if (MainInputCurveArraySize != InputObjectArr->Num()) - continue; - - UHoudiniInputHoudiniSplineComponent* HoudiniInput = - Cast((*InputObjectArr)[InCurveObjectIdx]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) - return; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent) - return; - - // Detach the spline component before delete. - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - - // This input is marked changed when an input component is deleted. - Input->DeleteInputObjectAt(EHoudiniInputType::Curve, InCurveObjectIdx); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - // Add delete button UI - LabelHorizontalBox->AddSlot().Padding(0, 2, 0, 2).HAlign(HAlign_Right).VAlign(VAlign_Bottom).AutoWidth() - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([DeleteHoudiniCurveAtIndex]() - { - return DeleteHoudiniCurveAtIndex(); - })) - ]; - - - TSharedPtr HorizontalBox = NULL; - VerticalBox->AddSlot().Padding(0, 2).AutoHeight()[SAssignNew(HorizontalBox, SHorizontalBox)]; - - // Closed check box - // Lambda returning a closed state - auto IsCheckedClosedCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsClosedCurve() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing Closed state - auto CheckStateChangedClosedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeClosed", "Houdini Input: Changing Curve Closed"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->IsClosedCurve() == bNewState) - continue; - - HoudiniSplineComponent->Modify(); - - HoudiniSplineComponent->SetClosedCurve(bNewState); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Add Closed check box UI - TSharedPtr CheckBoxClosed = NULL; - HorizontalBox->AddSlot().Padding(0, 2).AutoWidth() - [ - SAssignNew(CheckBoxClosed, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("ClosedCurveCheckBox", "Closed")) - .ToolTipText(LOCTEXT("ClosedCurveCheckboxTip", "Close this input curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedClosedCurve]() - { - return IsCheckedClosedCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedClosedCurve](ECheckBoxState NewState) - { - return CheckStateChangedClosedCurve(NewState); - }) - ]; - - // Reversed check box - // Lambda returning a reversed state - auto IsCheckedReversedCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsReversed() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing reversed state - auto CheckStateChangedReversedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeReversed", "Houdini Input: Changing Curve Reversed"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->IsReversed() == bNewState) - continue; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetReversed(bNewState); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Add reversed check box UI - TSharedPtr CheckBoxReversed = NULL; - HorizontalBox->AddSlot() - .Padding(2, 2) - .AutoWidth() - [ - SAssignNew(CheckBoxReversed, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("ReversedCurveCheckBox", "Reversed")) - .ToolTipText(LOCTEXT("ReversedCurveCheckboxTip", "Reverse this input curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedReversedCurve]() - { - return IsCheckedReversedCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedReversedCurve](ECheckBoxState NewState) - { - return CheckStateChangedReversedCurve(NewState); - }) - ]; - - // Visible check box - // Lambda returning a visible state - auto IsCheckedVisibleCurve = [HoudiniSplineComponent]() - { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return HoudiniSplineComponent->IsHoudiniSplineVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing visible state - auto CheckStateChangedVisibleCurve = [GetHoudiniSplineComponentAtIndex, InInputs, OuterHAC, InCurveObjectIdx](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent) - continue; - - if (HoudiniSplineComponent->IsHoudiniSplineVisible() == bNewState) - return; - - HoudiniSplineComponent->SetHoudiniSplineVisible(bNewState); - } - - if (GEditor) - GEditor->RedrawAllViewports(); - - }; - - // Add visible check box UI - TSharedPtr CheckBoxVisible = NULL; - HorizontalBox->AddSlot().Padding(2, 2).AutoWidth() - [ - SAssignNew(CheckBoxVisible, SCheckBox).Content() - [ - SNew(STextBlock).Text(LOCTEXT("VisibleCurveCheckBox", "Visible")) - .ToolTipText(LOCTEXT("VisibleCurveCheckboxTip", "Set the visibility of this curve.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedVisibleCurve]() - { - return IsCheckedVisibleCurve(); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedVisibleCurve](ECheckBoxState NewState) - { - return CheckStateChangedVisibleCurve(NewState); - }) - ]; - - // Curve type comboBox - // Lambda for changing Houdini curve type - auto OnCurveTypeChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - if (!InNewChoice.IsValid()) - return; - - EHoudiniCurveType NewInputType = UHoudiniInput::StringToHoudiniCurveType(*InNewChoice.Get()); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeType", "Houdini Input: Changing Curve Type"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->GetCurveType() == NewInputType) - continue; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetCurveType(NewInputType); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Lambda for getting Houdini curve type - auto GetCurveTypeText = [HoudiniSplineComponent]() - { - return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(HoudiniSplineComponent->GetCurveType())); - }; - - // Add curve type combo box UI - TSharedPtr CurveTypeHorizontalBox; - VerticalBox->AddSlot() - .Padding(0, 2, 2, 0) - .AutoHeight() - [ - SAssignNew(CurveTypeHorizontalBox, SHorizontalBox) - ]; - - // Add curve type label UI - CurveTypeHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() - [ - SNew(STextBlock).Text(LOCTEXT("CurveTypeText", "Curve Type ")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveType; - CurveTypeHorizontalBox->AddSlot() - .Padding(2, 2, 5, 2) - .FillWidth(150.f) - .MaxWidth(150.f) - [ - SAssignNew(ComboBoxCurveType, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels())[(int)HoudiniSplineComponent->GetCurveType()]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnCurveTypeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnCurveTypeChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([GetCurveTypeText]() - { - return GetCurveTypeText(); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - // Houdini curve method combo box - // Lambda for changing Houdini curve method - auto OnCurveMethodChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) - { - if (!OuterHAC || OuterHAC->IsPendingKill()) - return; - - if (!InNewChoice.IsValid()) - return; - - EHoudiniCurveMethod NewInputMethod = UHoudiniInput::StringToHoudiniCurveMethod(*InNewChoice.Get()); - - // Record a transaction for undo/redo. - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniCurveInputChangeMethod", "Houdini Input: Changing Curve Method"), - OuterHAC); - - for (auto & Input : InInputs) - { - if (!Input || Input->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - if (HoudiniSplineComponent->GetCurveMethod() == NewInputMethod) - return; - - HoudiniSplineComponent->Modify(); - HoudiniSplineComponent->SetCurveMethod(NewInputMethod); - HoudiniSplineComponent->MarkChanged(true); - } - }; - - // Lambda for getting Houdini curve method - auto GetCurveMethodText = [HoudiniSplineComponent]() - { - return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(HoudiniSplineComponent->GetCurveMethod())); - }; - - // Add curve method combo box UI - TSharedPtr< SHorizontalBox > CurveMethodHorizontalBox; - VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(CurveMethodHorizontalBox, SHorizontalBox)]; - - // Add curve method label UI - CurveMethodHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() - [ - SNew(STextBlock).Text(LOCTEXT("CurveMethodText", "Curve Method ")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveMethod; - CurveMethodHorizontalBox->AddSlot().Padding(2, 2, 5, 2).FillWidth(150.f).MaxWidth(150.f) - [ - SAssignNew(ComboBoxCurveMethod, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels())[(int)HoudiniSplineComponent->GetCurveMethod()]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnCurveMethodChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnCurveMethodChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([GetCurveMethodText]() - { - return GetCurveMethodText(); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - auto BakeInputCurveLambda = [](TArray Inputs, int32 Index, bool bBakeToBlueprint) - { - for (auto & NextInput : Inputs) - { - if (!NextInput || NextInput->IsPendingKill()) - continue; - - UHoudiniAssetComponent * OuterHAC = Cast(NextInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) - continue; - - AActor * OwnerActor = OuterHAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - continue; - - TArray * CurveInputComponentArray = NextInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); - if (!CurveInputComponentArray) - continue; - - if (!CurveInputComponentArray->IsValidIndex(Index)) - continue; - - UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = - Cast(HoudiniInputObject); - - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - FHoudiniPackageParams PackageParams; - PackageParams.BakeFolder = OuterHAC->BakeFolder.Path; - PackageParams.HoudiniAssetName = OuterHAC->GetName(); - PackageParams.GeoId = NextInput->GetAssetNodeId(); - PackageParams.PackageMode = EPackageMode::Bake; - PackageParams.ObjectId = Index; - PackageParams.ObjectName = OwnerActor->GetName() + "InputHoudiniSpline" + FString::FromInt(Index); - - if (bBakeToBlueprint) - { - FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( - HoudiniSplineComponent, - PackageParams, - OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); - } - else - { - FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( - HoudiniSplineComponent, - PackageParams, - OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); - } - } - - return FReply::Handled(); - }; - - // Add input curve bake button - TSharedPtr< SHorizontalBox > InputCurveBakeHorizontalBox; - VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(InputCurveBakeHorizontalBox, SHorizontalBox)]; - VerticalBox->AddSlot() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(110.f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("HoudiniInputCurveBakeToActorButton", "Bake to Actor")) - .IsEnabled(true) - .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() - { - return BakeInputCurveLambda(InInputs, InCurveObjectIdx, false); - }) - .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToActorButtonToolTip", "Bake this input curve to Actor")) - ] - - + SHorizontalBox::Slot().MaxWidth(110.f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("HoudiniInputCurveBakeToBPButton", "Bake to Blueprint")) - .IsEnabled(true) - .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() - { - return BakeInputCurveLambda(InInputs, InCurveObjectIdx, true); - }) - .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToBPButtonToolTip", "Bake this input curve to Blueprint")) - ] - ]; - - // Do we actually need to set enable the UI components? - if (MainInput->GetInputType() == EHoudiniInputType::Curve) - { - LabelBlock->SetEnabled(true); - CheckBoxClosed->SetEnabled(true); - CheckBoxReversed->SetEnabled(true); - CheckBoxVisible->SetEnabled(true); - ComboBoxCurveType->SetEnabled(true); - ComboBoxCurveMethod->SetEnabled(true); - } - else - { - LabelBlock->SetEnabled(false); - CheckBoxClosed->SetEnabled(false); - CheckBoxReversed->SetEnabled(false); - CheckBoxVisible->SetEnabled(false); - ComboBoxCurveType->SetEnabled(false); - ComboBoxCurveMethod->SetEnabled(false); - } -} - -void -FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, TArray& InInputs) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - // Lambda returning a CheckState from the input's current KeepWorldTransform state - auto IsCheckedUpdateInputLandscape = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetUpdateInputLandscape() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing KeepWorldTransform state - auto CheckStateChangedUpdateInputLandscape = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - bool bNewState = (NewState == ECheckBoxState::Checked); - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangedUpdate", "Houdini Input: Changing Keep World Transform"), - MainInput->GetOuter()); - - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (bNewState == CurInput->GetUpdateInputLandscape()) - continue; - - CurInput->Modify(); - - UHoudiniAssetComponent* HAC = Cast(CurInput->GetOuter()); - if (!HAC) - continue; - - TArray* LandscapeInputObjects = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (!LandscapeInputObjects) - continue; - - for (UHoudiniInputObject* NextInputObj : *LandscapeInputObjects) - { - UHoudiniInputLandscape* CurrentInputLandscape = Cast(NextInputObj); - if (!CurrentInputLandscape) - continue; - - ALandscapeProxy* CurrentInputLandscapeProxy = CurrentInputLandscape->GetLandscapeProxy(); - if (!CurrentInputLandscapeProxy) - continue; - - if (bNewState) - { - // We want to update this landscape data directly, start by backing it up to image files in the temp folder - FString BackupBaseName = HAC->TemporaryCookFolder.Path - + TEXT("/") - + CurrentInputLandscapeProxy->GetName() - + TEXT("_") - + HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); - - // We need to cache the input landscape to a file - FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(BackupBaseName, CurrentInputLandscapeProxy); - - // Cache its transform on the input - CurrentInputLandscape->CachedInputLandscapeTraqnsform = CurrentInputLandscapeProxy->ActorToWorld(); - - HAC->SetMobility(EComponentMobility::Static); - CurrentInputLandscapeProxy->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); - } - else - { - // We are not updating this input landscape anymore, detach it and restore its backed-up values - CurrentInputLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - - // Restore the input landscape's backup data - FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(CurrentInputLandscapeProxy); - - // Reapply the source Landscape's transform - CurrentInputLandscapeProxy->SetActorTransform(CurrentInputLandscape->CachedInputLandscapeTraqnsform); - - // TODO: - // Clear the input obj map? - } - } - - CurInput->bUpdateInputLandscape = (NewState == ECheckBoxState::Checked); - CurInput->MarkChanged(true); - } - }; - - // CheckBox : Update Input Landscape Data - TSharedPtr< SCheckBox > CheckBoxUpdateInput; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew( CheckBoxUpdateInput, SCheckBox).Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) - .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedUpdateInputLandscape, MainInput]() - { - return IsCheckedUpdateInputLandscape(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedUpdateInputLandscape, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedUpdateInputLandscape(InInputs, NewState); - }) - ]; - - // Actor picker: Landscape. - FMenuBuilder MenuBuilder = Helper_CreateLandscapePickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - - // Checkboxes : Export landscape as Heightfield/Mesh/Points - { - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAs", "Export Landscape As")) - .ToolTipText(LOCTEXT("LandscapeExportAsToolTip", "Choose the type of data you want the ladscape to be exported to:\n * Heightfield\n * Mesh\n * Points")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TSharedPtr ButtonOptionsPanel; - VerticalBox->AddSlot().Padding(5, 2, 5, 2).AutoHeight() - [ - SAssignNew(ButtonOptionsPanel, SUniformGridPanel) - ]; - - auto IsCheckedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) - { - if (!Input || Input->IsPendingKill()) - return ECheckBoxState::Unchecked; - - if (Input->GetLandscapeExportType() == LandscapeExportType) - return ECheckBoxState::Checked; - else - return ECheckBoxState::Unchecked; - }; - - auto CheckStateChangedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) - { - if (!Input || Input->IsPendingKill()) - return false; - - if (Input->GetLandscapeExportType() == LandscapeExportType) - return false; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniInputChange", "Houdini Input: Changed Landscape export type."), - Input->GetOuter()); - Input->Modify(); - - Input->SetLandscapeExportType(LandscapeExportType); - Input->SetHasLandscapeExportTypeChanged(true); - Input->MarkChanged(true); - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return true; - - for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) - { - if (!NextInputObj) - continue; - NextInputObj->MarkChanged(true); - } - - return true; - }; - - // Heightfield - FText HeightfieldTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a heightfield."); - ButtonOptionsPanel->AddSlot(0, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Start") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Heightfield); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for(auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Heightfield); - }) - .ToolTipText(HeightfieldTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("ClassIcon.LandscapeComponent")) - ] - + SHorizontalBox::Slot() - .FillWidth(1.f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsHeightfieldCheckbox", "Heightfield")) - .ToolTipText(HeightfieldTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - - // Mesh - FText MeshTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a mesh."); - ButtonOptionsPanel->AddSlot(1, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Mesh); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for (auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Mesh); - }) - .ToolTipText(MeshTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("ClassIcon.StaticMeshComponent")) - ] - - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsMeshCheckbox", "Mesh")) - .ToolTipText(MeshTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - - // Points - FText PointsTooltip = LOCTEXT("LandscapeExportAsPointsTooltip", "If enabled, the landscape will be exported to Houdini as points."); - ButtonOptionsPanel->AddSlot(2, 0) - [ - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.End") - .IsChecked_Lambda([IsCheckedExportAs, MainInput]() - { - return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Points); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) - { - for (auto CurrentInput : InInputs) - CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Points); - }) - .ToolTipText(PointsTooltip) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(2, 2) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("Mobility.Static")) - ] - - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(2, 2) - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeExportAsPointsCheckbox", "Points")) - .ToolTipText(PointsTooltip) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - ] - ]; - } - - // CheckBox : Export selected components only - { - TSharedPtr< SCheckBox > CheckBoxExportSelected; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportSelected, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeSelectedCheckbox", "Export Selected Landscape Components Only")) - .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, only the selected Landscape Components will be exported.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportSelectionOnly ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportSelectionOnly", "Houdini Input: Changing Landscape export only selected component."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportSelectionOnly) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportSelectionOnly = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - } - - // Checkbox: auto select components - { - TSharedPtr< SCheckBox > CheckBoxAutoSelectComponents; - VerticalBox->AddSlot().Padding(10, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxAutoSelectComponents, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("AutoSelectComponentCheckbox", "Auto-select component in asset bounds")) - .ToolTipText(LOCTEXT("AutoSelectComponentCheckboxTooltip", "If enabled, when no Landscape components are curremtly selected, the one within the asset's bounding box will be exported.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeAutoSelectComponent ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeAutoSelectComponent", "Houdini Input: Changing Landscape input auto-selects components."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeAutoSelectComponent) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeAutoSelectComponent = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - // Enable only when exporting selection or when exporting heighfield (for now) - bool bEnable = false; - for (auto CurrentInput : InInputs) - { - if (!MainInput->bLandscapeExportSelectionOnly) - continue; - - bEnable = true; - break; - } - CheckBoxAutoSelectComponents->SetEnabled(bEnable); - } - - - // The following checkbox are only added when not in heightfield mode - if (MainInput->LandscapeExportType != EHoudiniLandscapeExportType::Heightfield) - { - // Checkbox : Export materials - { - TSharedPtr< SCheckBox > CheckBoxExportMaterials; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportMaterials, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeMaterialsCheckbox", "Export Landscape Materials")) - .ToolTipText(LOCTEXT("LandscapeMaterialsTooltip", "If enabled, the landscape materials will be exported with it.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportMaterials", "Houdini Input: Changing Landscape input export materials."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportMaterials) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportMaterials = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportMaterials->SetEnabled(false); - */ - } - - // Checkbox : Export Tile UVs - { - TSharedPtr< SCheckBox > CheckBoxExportTileUVs; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportTileUVs, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeTileUVsCheckbox", "Export Landscape Tile UVs")) - .ToolTipText(LOCTEXT("LandscapeTileUVsTooltip", "If enabled, UVs will be exported separately for each Landscape tile.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportTileUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportTileUVs", "Houdini Input: Changing Landscape export tile UVs."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportTileUVs) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportTileUVs = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportTileUVs->SetEnabled(false); - */ - } - - // Checkbox : Export normalized UVs - { - TSharedPtr< SCheckBox > CheckBoxExportNormalizedUVs; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportNormalizedUVs, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeNormalizedUVsCheckbox", "Export Landscape Normalized UVs")) - .ToolTipText(LOCTEXT("LandscapeNormalizedUVsTooltip", "If enabled, landscape UVs will be exported in [0, 1].")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportNormalizedUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Changing Landscape export normalized UVs."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportNormalizedUVs) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportNormalizedUVs = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportNormalizedUVs->SetEnabled(false); - */ - } - - // Checkbox : Export lighting - { - TSharedPtr< SCheckBox > CheckBoxExportLighting; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxExportLighting, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeLightingCheckbox", "Export Landscape Lighting")) - .ToolTipText(LOCTEXT("LandscapeLightingTooltip", "If enabled, lightmap information will be exported with the landscape.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainInput->bLandscapeExportLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportLighting", "Houdini Input: Changing Landscape export lighting."), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - bool bNewState = (NewState == ECheckBoxState::Checked); - if (bNewState == CurrentInput->bLandscapeExportLighting) - continue; - - CurrentInput->Modify(); - - CurrentInput->bLandscapeExportLighting = bNewState; - CurrentInput->MarkChanged(true); - } - }) - ]; - - /* - // Disable when exporting heightfields - if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - CheckBoxExportLighting->SetEnabled(false); - */ - } - - } - - // Button : Recommit - { - auto OnButtonRecommitClicked = [InInputs]() - { - for (auto CurrentInput : InInputs) - { - TArray* LandscapeInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); - if (!LandscapeInputObjectsArray) - continue; - - for (UHoudiniInputObject* NextLandscapeInput : *LandscapeInputObjectsArray) - { - if (!NextLandscapeInput) - continue; - - NextLandscapeInput->MarkChanged(true); - } - - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(1, 2, 4, 2) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("LandscapeInputRecommit", "Recommit Landscape")) - .ToolTipText(LOCTEXT("LandscapeInputRecommitTooltip", "Recommits the Landscape to Houdini.")) - .OnClicked_Lambda(OnButtonRecommitClicked) - ] - ]; - } - - - // Button : Clear Selection - { - auto IsClearButtonEnabled = [MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return false; - - if (MainInput->GetInputType() != EHoudiniInputType::Landscape) - return false; - - TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); - if (!MainInputObjectsArray) - return false; - - if (MainInputObjectsArray->Num() <= 0) - return false; - - return true; - }; - - auto OnButtonClearClicked = [InInputs]() - { - if (InInputs.Num() <= 0) - return FReply::Handled(); - - UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - if (MainInput->GetInputType() != EHoudiniInputType::Landscape) - return FReply::Handled(); - - TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); - if (!MainInputObjectsArray) - return FReply::Handled(); - - if (MainInputObjectsArray->Num() <= 0) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Clearing landscape input."), - MainInput->GetOuter()); - - for (auto & CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - TArray* LandscapeInputObjectsArray = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); - if (!LandscapeInputObjectsArray) - continue; - - if (LandscapeInputObjectsArray->Num() <= 0) - continue; - - CurInput->MarkChanged(true); - CurInput->Modify(); - - LandscapeInputObjectsArray->Empty(); - } - - return FReply::Handled(); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().Padding(1, 2, 4, 2) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("ClearSelection", "Clear Selection")) - .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) - .IsEnabled_Lambda(IsClearButtonEnabled) - .OnClicked_Lambda(OnButtonClearClicked) - ] - ]; - } -} - -/* -FMenuBuilder -FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArray& InInputs, const TAttribute& HeadingText, const bool& bShowCurrentSelectionSection) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - - // Filters are only based on the MainInput - auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - if (!Actor->IsA()) - return false; - - ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); - if (!LandscapeProxy) - return false; - - // Get the landscape's actor - AActor* OwnerActor = LandscapeProxy->GetOwner(); - - // Get our Actor - UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); - AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; - - // TODO: FIX ME! - // IF the landscape is owned by ourself, skip it! - if (OwnerActor == MyOwner) - return false; - - return true; - }; - - auto OnShouldFilterWorld = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - const TArray* InputObjects = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurInputObject : *InputObjects) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - auto OnShouldFilterHoudiniAsset = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - // But not our own Asset Actor - if (const USceneComponent* RootComp = Cast(InInput->GetOuter())) - { - if (RootComp && Cast(RootComp->GetOwner()) != Actor) - return true; - } - - return false; - }; - - auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape, OnShouldFilterWorld, OnShouldFilterHoudiniAsset](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - switch (MainInput->GetInputType()) - { - case EHoudiniInputType::Landscape: - return OnShouldFilterLandscape(Actor, MainInput); - case EHoudiniInputType::World: - return OnShouldFilterWorld(Actor, MainInput); - case EHoudiniInputType::Asset: - return OnShouldFilterHoudiniAsset(Actor, MainInput); - default: - return true; - } - - return false; - }; - - - // Selection uses the input arrays - auto OnLandscapeSelected = [](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - ALandscapeProxy* LandscapeProxy = Cast(Actor); - if (!LandscapeProxy) - return; - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return; - - LandscapeInputObjectsArray->Empty(); - - FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); - - // Create a Houdini Input Object. - UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( - LandscapeProxy, Input, LandscapeName.ToString()); - - UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); - LandscapeInput->MarkChanged(true); - - LandscapeInputObjectsArray->Add(LandscapeInput); - Input->MarkChanged(true); - }; - - auto OnHoudiniAssetActorSelected = [](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); - if (!HoudiniAssetActor) - return; - - TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!AssetInputObjectsArray) - return; - - AssetInputObjectsArray->Empty(); - - FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); - - // Create a Houdini Asset Input Object - UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); - - UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); - AssetInput->MarkChanged(true); - - AssetInputObjectsArray->Add(AssetInput); - Input->MarkChanged(true); - }; - - auto OnWorldSelected = [](AActor* Actor, UHoudiniInput* Input) - { - // Do Nothing - }; - - auto OnActorSelected = [OnLandscapeSelected, OnWorldSelected, OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) - { - for (auto& CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - return; - - switch (CurInput->GetInputType()) - { - case EHoudiniInputType::Landscape: - return OnLandscapeSelected(Actor, CurInput); - case EHoudiniInputType::World: - return OnWorldSelected(Actor, CurInput); - case EHoudiniInputType::Asset: - return OnHoudiniAssetActorSelected(Actor, CurInput); - default: - return; - } - } - - return; - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - if (bShowCurrentSelectionSection) - { - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - } - - - MenuBuilder.BeginSection(NAME_None, HeadingText); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} -*/ - - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterHoudiniAsset = [InInputs](const AActor* const Actor) - { - if (!Actor) - return false; - - // Only return HoudiniAssetActors, but not our HAA - if (!Actor->IsA()) - return false; - - // But not our selected Asset Actor - for (auto & NextSelectedInput : InInputs) - { - if (!NextSelectedInput) - continue; - - const USceneComponent* RootComp = Cast(NextSelectedInput->GetOuter()); - if (RootComp && Cast(RootComp->GetOwner()) == Actor) - return false; - - } - - return true; - }; - - // Filters are only based on the MainInput - auto OnShouldFilterActor = [MainInput, OnShouldFilterHoudiniAsset](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - return OnShouldFilterHoudiniAsset(Actor); - }; - - auto OnHoudiniAssetActorSelected = [OnShouldFilterHoudiniAsset](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || Actor->IsPendingKill() || !Input || Input->IsPendingKill()) - return; - - AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); - if (!HoudiniAssetActor) - return; - - // Make sure that the actor is valid for this input - if (!OnShouldFilterHoudiniAsset(Actor)) - return; - - TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); - if (!AssetInputObjectsArray) - return; - - FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); - - // Create a Houdini Asset Input Object - UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); - - UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); - AssetInput->MarkChanged(true); - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniAssetInputChange", "Houdini Input: Selecting an asset input"), - Input->GetOuter()); - - Input->Modify(); - - AssetInputObjectsArray->Empty(); - AssetInputObjectsArray->Add(AssetInput); - Input->MarkChanged(true); - }; - - auto OnActorSelected = [OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) - { - for (auto& CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - return; - - OnHoudiniAssetActorSelected(Actor, CurInput); - } - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - // Show current selection - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("AssetInputSelectableActors", "Houdini Assets")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - if (!Actor->IsA()) - return false; - - ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); - if (!LandscapeProxy) - return false; - - // Get the landscape's parent actor - // We need to get the AttachParent on the root componet, GteOwner will not return the parent actor! - AActor* OwnerActor = nullptr; - USceneComponent* RootComponent = LandscapeProxy->GetRootComponent(); - if (RootComponent && !RootComponent->IsPendingKill()) - OwnerActor = RootComponent->GetAttachParent() ? RootComponent->GetAttachParent()->GetOwner() : LandscapeProxy->GetOwner(); - - // Get our Actor - UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); - AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; - - // IF the landscape is owned by ourself, skip it! - if (OwnerActor && OwnerActor == MyOwner) - { - // ... buuuut we dont want to filter input landscapes that have the "Update Input Landscape Data" option enabled - // (and are, therefore, outputs as well) - for (int32 Idx = 0; Idx < MyHAC->GetNumInputs(); Idx++) - { - UHoudiniInput* CurrentInput = MyHAC->GetInputAt(Idx); - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) - continue; - - if (!CurrentInput->GetUpdateInputLandscape()) - continue; - - // Don't filter our input landscapes - ALandscapeProxy* UpdatedInputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); - if (LandscapeProxy == UpdatedInputLandscape) - return true; - } - - return false; - } - - return true; - }; - - // Filters are only based on the MainInput - auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - return OnShouldFilterLandscape(Actor, MainInput); - }; - - // Selection uses the input arrays - auto OnLandscapeSelected = [OnShouldFilterLandscape](AActor* Actor, UHoudiniInput* Input) - { - if (!Actor || !Input) - return; - - // Make sure that the actor is valid for this input - if (!OnShouldFilterLandscape(Actor, Input)) - return; - - ALandscapeProxy* LandscapeProxy = Cast(Actor); - if (!LandscapeProxy) - return; - - TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); - if (!LandscapeInputObjectsArray) - return; - - LandscapeInputObjectsArray->Empty(); - - FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); - - // Create a Houdini Input Object. - UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( - LandscapeProxy, Input, LandscapeName.ToString()); - - UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); - LandscapeInput->MarkChanged(true); - - LandscapeInputObjectsArray->Add(LandscapeInput); - Input->MarkChanged(true); - }; - - auto OnActorSelected = [OnLandscapeSelected](AActor* Actor, TArray InInputs) - { - if (InInputs.Num() <= 0) - return; - - UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniLandscapeInputChangeSelections", "Houdini Input: Selecting input landscape."), - MainInput->GetOuter()); - - for (auto CurInput : InInputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - CurInput->Modify(); - OnLandscapeSelected(Actor, CurInput); - } - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); - - // Show current selection - MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); - { - MenuBuilder.AddMenuEntry( - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), - FSlateIcon(), - FUIAction(), - NAME_None, - EUserInterfaceActionType::Button, - NAME_None); - } - MenuBuilder.EndSection(); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("LandscapeInputSelectableActors", "Landscapes")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilterWorld = [MainInput](const AActor* const Actor) - { - if (!MainInput || MainInput->IsPendingKill()) - return true; - - const TArray* InputObjects = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); - if (!InputObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurInputObject : *InputObjects) - { - if (!CurInputObject || CurInputObject->IsPendingKill()) - continue; - - AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) - { - // See if the input object is a HAC, if it is, get its parent actor - UHoudiniAssetComponent* CurHAC = Cast(CurInputObject->GetObject()); - if (CurHAC && !CurHAC->IsPendingKill()) - CurActor = CurHAC->GetOwner(); - } - - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - auto OnWorldSelected = [](AActor* Actor) - { - // Do Nothing - }; - - FMenuBuilder MenuBuilder(true, nullptr); - FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterWorld); - - MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputSelectedActors", "Currently Selected Actors")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(ActorFilter); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnWorldSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -FMenuBuilder -FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArray& InInputs) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - auto OnShouldFilter = [MainInput](const AActor* const Actor) - { - if (!Actor || Actor->IsPendingKill()) - return false; - - const TArray* BoundObjects = MainInput->GetBoundSelectorObjectArray(); - if (!BoundObjects) - return false; - - // Only return actors that are currently selected by our input - for (const auto& CurActor : *BoundObjects) - { - if (!CurActor || CurActor->IsPendingKill()) - continue; - - if (CurActor == Actor) - return true; - } - - return false; - }; - - - auto OnSelected = [](AActor* Actor) - { - // Do Nothing - }; - - FMenuBuilder MenuBuilder(true, nullptr); - MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputBoundSelectors", "Bound Selectors")); - { - FSceneOutlinerModule & SceneOutlinerModule = - FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); - SceneOutliner::FInitializationOptions InitOptions; - { - InitOptions.Mode = ESceneOutlinerMode::ActorPicker; - InitOptions.Filters->AddFilterPredicate(FOnShouldFilterActor::CreateLambda(OnShouldFilter)); - InitOptions.bFocusSearchBoxWhenOpened = true; - InitOptions.bShowCreateNewFolder = false; - - // Add the gutter so we can change the selection's visibility - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); - InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); - } - - static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); - TSharedRef< SWidget > MenuWidget = - SNew(SBox) - .WidthOverride(SceneOutlinerWindowSize.X) - .HeightOverride(SceneOutlinerWindowSize.Y) - [ - SNew(SBorder) - .BorderImage(FEditorStyle::GetBrush("Menu.Background")) - [ - SceneOutlinerModule.CreateSceneOutliner( - InitOptions, - FOnActorPicked::CreateLambda(OnSelected)) - ] - ]; - - MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); - } - MenuBuilder.EndSection(); - - return MenuBuilder; -} - -void -FHoudiniInputDetails::AddWorldInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - const IDetailsView* DetailsView) -{ - if (InInputs.Num() <= 0) - return; - - UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput) - return; - - const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); - - // Get the details view name and locked status - bool bDetailsLocked = false; - FName DetailsPanelName = "LevelEditorSelectionDetails"; - if (DetailsView) - { - DetailsPanelName = DetailsView->GetIdentifier(); - if (DetailsView->IsLocked()) - bDetailsLocked = true; - } - - // Check of we're in bound selector mode - bool bIsBoundSelector = MainInput->IsWorldInputBoundSelector(); - - // Button : Start Selection / Use current selection + refresh - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - //auto ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); - //auto ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); - FText ButtonLabel; - FText ButtonTooltip; - if (!bIsBoundSelector) - { - // Button : Start Selection / Use current selection - if (bDetailsLocked) - { - ButtonLabel = LOCTEXT("WorldInputUseCurrentSelection", "Use Current Selection (Unlocks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputUseCurrentSelectionTooltip", "Fill the asset's input with the current selection and unlocks the Details Panel."); - } - else - { - ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); - } - /* - FOnClicked OnSelectActors = FOnClicked::CreateStatic( - &FHoudiniInputDetails::Helper_OnButtonClickSelectActors, InInputs, DetailsPanelName); - */ - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ButtonLabel) - .ToolTipText(ButtonTooltip) - //.OnClicked(OnSelectActors) - .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() - { - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName); - }) - - ] - ]; - } - else - { - // Button : Start Selection / Use current selection as Bound selector - if (bDetailsLocked) - { - ButtonLabel = LOCTEXT("WorldInputUseSelectionAsBoundSelector", "Use Selection as Bound Selector (Unlocks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputUseSelectionAsBoundSelectorTooltip", "Fill the asset's input with all the actors contains in the current's selection bound, and unlocks the Details Panel."); - } - else - { - ButtonLabel = LOCTEXT("WorldInputStartBoundSelection", "Start Bound Selection (Locks Details Panel)"); - ButtonTooltip = LOCTEXT("WorldInputStartBoundSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that will be used as bounds."); - } - - /* - FOnClicked OnSelectBounds = FOnClicked::CreateStatic( - &FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector, InInputs, DetailsPanelName); - */ - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ButtonLabel) - .ToolTipText(ButtonTooltip) - //.OnClicked(OnSelectBounds) - .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() - { - return Helper_OnButtonClickUseSelectionAsBoundSelector(CategoryBuilder, InInputs, DetailsPanelName); - }) - ] - ]; - } - } - - // Button : Select All + Clear Selection - { - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FOnClicked OnSelectAll = FOnClicked::CreateLambda([InInputs, MainInput]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputSelectedAll", "Houdini Input: Selecting all actor in the current world"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - // Get the parent component/actor/world of the current input - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - UWorld* MyWorld = CurrentInput->GetWorld(); - - TArray NewSelectedActors; - for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) - { - AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - NewSelectedActors.Add(CurrentActor); - } - - CurrentInput->Modify(); - - bool bHasChanged = CurrentInput->UpdateWorldSelection(NewSelectedActors); - } - - return FReply::Handled(); - }); - - FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput, &CategoryBuilder]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - const bool bMainInputBoundSelection = MainInput->IsWorldInputBoundSelector(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputClear", "Houdini Input: Clearing world input selection"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - // Do nothing if the current input has different selector settings from the main input - if (CurrentInput->IsWorldInputBoundSelector() != bMainInputBoundSelection) - continue; - - CurrentInput->Modify(); - - if (CurrentInput->IsWorldInputBoundSelector()) - { - CurrentInput->SetBoundSelectorObjectsNumber(0); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - else - { - TArray EmptySelection; - bool bHasChanged = CurrentInput->UpdateWorldSelection(EmptySelection); - } - } - - return FReply::Handled(); - }); - - FText ClearSelectionLabel; - FText ClearSelectionTooltip; - if (bIsBoundSelector) - { - ClearSelectionLabel = LOCTEXT("ClearBoundSelection", "Clear Bound Selection"); - ClearSelectionTooltip = LOCTEXT("ClearBoundSelectionTooltip", "Clear the input's current bound selection."); - } - else - { - ClearSelectionLabel = LOCTEXT("ClearSelection", "Clear Selection"); - ClearSelectionTooltip = LOCTEXT("ClearSelectionTooltip", "Clear the input's current selection."); - } - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(HorizontalBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - // Button : SelectAll - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("WorldInputSelectAll", "Select All")) - .ToolTipText(LOCTEXT("WorldInputSelectAll", "Fill the asset's input with all actors.")) - .OnClicked(OnSelectAll) - .IsEnabled(!bIsBoundSelector) - ] - + SHorizontalBox::Slot() - [ - // Button : Clear Selection - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ClearSelectionLabel) - .ToolTipText(ClearSelectionTooltip) - .OnClicked(OnClearSelect) - ] - ]; - - // Do not enable select all/clear select when selection has been started and details are locked - HorizontalBox->SetEnabled(!bDetailsLocked); - } - - // Checkbox: Bound Selector - { - // Lambda returning a CheckState from the input's current bound selector state - auto IsCheckedBoundSelector = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->IsWorldInputBoundSelector() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing bound selector state - auto CheckStateChangedIsBoundSelector = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeBoungSelector", "Houdini Input: Changing world input to bound selector"), - MainInput->GetOuter()); - - bool bNewState = (NewState == ECheckBoxState::Checked); - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->IsWorldInputBoundSelector() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetWorldInputBoundSelector(bNewState); - } - - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - }; - - // Checkbox : Is Bound Selector - TSharedPtr< SCheckBox > CheckBoxBoundSelector; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxBoundSelector, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("BoundSelector", "Bound Selector")) - .ToolTipText(LOCTEXT("BoundSelectorTip", "When enabled, this world input works as a bound selector, sending all the objects contained in the bound selector bounding boxes.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedBoundSelector, MainInput]() - { - return IsCheckedBoundSelector(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedIsBoundSelector, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedIsBoundSelector(InInputs, NewState); - }) - ]; - } - - // Checkbox: Bound Selector Auto update - { - // Lambda returning a CheckState from the input's current auto update state - auto IsCheckedAutoUpdate = [](UHoudiniInput* InInput) - { - if (!InInput || InInput->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return InInput->GetWorldInputBoundSelectorAutoUpdates() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - // Lambda for changing the auto update state - auto CheckStateChangedBoundAutoUpdates = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeAutoUpdate", "Houdini Input: Changing bound selector auto-update state."), - MainInput->GetOuter()); - - bool bNewState = (NewState == ECheckBoxState::Checked); - for (auto CurInput : InInputsToUpdate) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - if (CurInput->GetWorldInputBoundSelectorAutoUpdates() == bNewState) - continue; - - CurInput->Modify(); - - CurInput->SetWorldInputBoundSelectorAutoUpdates(bNewState); - CurInput->MarkChanged(true); - } - }; - - // Checkbox : Is Bound Selector - TSharedPtr< SCheckBox > CheckBoxBoundAutoUpdate; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SAssignNew(CheckBoxBoundAutoUpdate, SCheckBox) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("BoundAutoUpdate", "Update bound selection automatically")) - .ToolTipText(LOCTEXT("BoundAutoUpdateTip", "If enabled and if this world input is set as a bound selector, the objects selected by the bounds will update automatically.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([IsCheckedAutoUpdate, MainInput]() - { - return IsCheckedAutoUpdate(MainInput); - }) - .OnCheckStateChanged_Lambda([CheckStateChangedBoundAutoUpdates, InInputs](ECheckBoxState NewState) - { - return CheckStateChangedBoundAutoUpdates(InInputs, NewState); - }) - ]; - - CheckBoxBoundAutoUpdate->SetEnabled(MainInput->IsWorldInputBoundSelector()); - } - - // ActorPicker : Bound Selector - if(bIsBoundSelector) - { - FMenuBuilder MenuBuilder = Helper_CreateBoundSelectorPickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - // ActorPicker : World Outliner - { - FMenuBuilder MenuBuilder = Helper_CreateWorldActorPickerWidget(InInputs); - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - MenuBuilder.MakeWidget() - ]; - } - - { - // Spline Resolution - TSharedPtr> NumericEntryBox; - int32 Idx = 0; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(LOCTEXT("SplineRes", "Unreal Spline Resolution")) - .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm between control points)\nSet this to 0 to only export the control points.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SNumericEntryBox) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .MinValue(-1.0f) - .MaxValue(1000.0f) - .MinSliderValue(0.0f) - .MaxSliderValue(1000.0f) - .Value(MainInput->GetUnrealSplineResolution()) - .OnValueChanged_Lambda([MainInput, InInputs](float Val) - { - if (!MainInput || MainInput->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputChangeSplineResolution", "Houdini Input: Changing world input spline resolution"), - MainInput->GetOuter()); - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetUnrealSplineResolution() == Val) - continue; - - CurrentInput->Modify(); - - CurrentInput->SetUnrealSplineResolution(Val); - CurrentInput->MarkChanged(true); - } - }) - /* - .Value(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject( - &InParam, &UHoudiniAssetInput::GetSplineResolutionValue))) - .OnValueChanged(SNumericEntryBox< float >::FOnValueChanged::CreateUObject( - &InParam, &UHoudiniAssetInput::SetSplineResolutionValue)) - .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateUObject( - &InParam, &UHoudiniAssetInput::IsSplineResolutionEnabled))) - */ - .SliderExponent(1.0f) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("SplineResToDefault", "Reset to default value.")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - // TODO: FINISH ME! - //.OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) - .OnClicked_Lambda([MainInput, InInputs]() - { - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniWorldInputRevertSplineResolution", "Houdini Input: Reverting world input spline resolution to default"), - MainInput->GetOuter()); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - float DefaultSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; - - for (auto CurrentInput : InInputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetUnrealSplineResolution() == DefaultSplineResolution) - continue; - - CurrentInput->Modify(); - - CurrentInput->SetUnrealSplineResolution(DefaultSplineResolution); - CurrentInput->MarkChanged(true); - } - - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - } -} - -void -FHoudiniInputDetails::AddSkeletalInputUI( - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool ) -{ -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) -{ - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, false); -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) -{ - return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, true); -} - -FReply -FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName, const bool& bUseWorldInAsWorldSelector) -{ - UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - if (!MainInput || MainInput->IsPendingKill()) - return FReply::Handled(); - - // There's no undo operation for button. - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Locate the details panel. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - return FReply::Handled(); - - class SLocalDetailsView : public SDetailsViewBase - { - public: - void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } - void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } - }; - auto * LocalDetailsView = static_cast(DetailsView.Get()); - - if (!DetailsView->IsLocked()) - { - // - // START SELECTION - // Locks the details view and select our currently selected actors - // - LocalDetailsView->LockDetailsView(); - check(DetailsView->IsLocked()); - - // Force refresh of details view. - TArray InputOuters; - for (auto CurIn : InInputs) - InputOuters.Add(CurIn->GetOuter()); - if (CategoryBuilder.IsParentLayoutValid()) - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - //ReselectSelectedActors(); - - if (bUseWorldInAsWorldSelector) - { - // Bound Selection - // Select back the previously chosen bound selectors - GEditor->SelectNone(false, true); - int32 NumBoundSelectors = MainInput->GetNumberOfBoundSelectorObjects(); - for (int32 Idx = 0; Idx < NumBoundSelectors; Idx++) - { - AActor* Actor = MainInput->GetBoundSelectorObjectAt(Idx); - if (!Actor || Actor->IsPendingKill()) - continue; - - GEditor->SelectActor(Actor, true, true); - } - } - else - { - // Regular selection - // Select the already chosen input Actors from the World Outliner. - GEditor->SelectNone(false, true); - int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); - for (int32 Idx = 0; Idx < NumInputObjects; Idx++) - { - UHoudiniInputObject* CurInputObject = MainInput->GetHoudiniInputObjectAt(Idx); - if (!CurInputObject) - continue; - - AActor* Actor = nullptr; - UHoudiniInputActor* InputActor = Cast(CurInputObject); - if (InputActor && !InputActor->IsPendingKill()) - { - // Get the input actor - Actor = InputActor->GetActor(); - } - else - { - // See if the input object is a HAC - UHoudiniInputHoudiniAsset* InputHAC = Cast(CurInputObject); - if (InputHAC && !InputHAC->IsPendingKill()) - { - Actor = InputHAC->GetHoudiniAssetComponent() ? InputHAC->GetHoudiniAssetComponent()->GetOwner() : nullptr; - } - } - - if (!Actor || Actor->IsPendingKill()) - continue; - - GEditor->SelectActor(Actor, true, true); - } - } - - return FReply::Handled(); - } - else - { - // - // UPDATE SELECTION - // Unlocks the input's selection and select the HDA back. - // - - if (!GEditor || !GEditor->GetSelectedObjects()) - return FReply::Handled(); - - USelection * SelectedActors = GEditor->GetSelectedActors(); - if (!SelectedActors) - return FReply::Handled(); - - // Create a transaction - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniWorldInputSelectionChanged", "Changing Houdini world outliner input objects"), - MainInput->GetOuter()); - - - TArray AllActors; - for (auto CurrentInput : InInputs) - { - CurrentInput->Modify(); - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - AllActors.Add(ParentActor); - - bool bHasChanged = true; - if (bUseWorldInAsWorldSelector) - { - // - // Update bound selectors - - // Clean up the selected actors - TArray ValidBoundSelectedActors; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor* CurrentBoundActor = Cast(*It); - if (!CurrentBoundActor) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentBoundActor == ParentActor)) - continue; - - ValidBoundSelectedActors.Add(CurrentBoundActor); - } - - // See if the bound selector have changed - int32 PreviousBoundSelectorCount = CurrentInput->GetNumberOfBoundSelectorObjects(); - if (PreviousBoundSelectorCount == ValidBoundSelectedActors.Num()) - { - // Same number of BoundSelectors, see if they have changed - bHasChanged = false; - for (int32 BoundIdx = 0; BoundIdx < PreviousBoundSelectorCount; BoundIdx++) - { - AActor* PreviousBound = CurrentInput->GetBoundSelectorObjectAt(BoundIdx); - if (!PreviousBound) - continue; - - if (!ValidBoundSelectedActors.Contains(PreviousBound)) - { - bHasChanged = true; - break; - } - } - } - - if (bHasChanged) - { - // Only update the bound selector objects on the input if they have changed - CurrentInput->SetBoundSelectorObjectsNumber(ValidBoundSelectedActors.Num()); - int32 InputObjectIdx = 0; - for (auto CurActor : ValidBoundSelectedActors) - { - CurrentInput->SetBoundSelectorObjectAt(InputObjectIdx++, CurActor); - } - - // Update the current selection from the BoundSelectors - CurrentInput->UpdateWorldSelectionFromBoundSelectors(); - } - } - else - { - // - // Update our selection directly with the currently selected actors - // - - TArray ValidSelectedActors; - for (FSelectionIterator It(*SelectedActors); It; ++It) - { - AActor* CurrentActor = Cast(*It); - if (!CurrentActor) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - ValidSelectedActors.Add(CurrentActor); - } - - // Update the input objects from the valid selected actors array - // Only new/remove input objects will be marked as changed - bHasChanged = CurrentInput->UpdateWorldSelection(ValidSelectedActors); - } - - // If we didnt change the selection, cancel the transaction - if (!bHasChanged) - Transaction.Cancel(); - } - - // We can now unlock the details view... - LocalDetailsView->UnlockDetailsView(); - check(!DetailsView->IsLocked()); - - // .. reset the selected actors, force refresh and override the lock. - DetailsView->SetObjects(AllActors, true, true); - - // We now need to reselect all our Asset Actors. - // If we don't do this, our Asset parameters will stop refreshing and the user will be very confused. - // It is also resetting the state of the selection before the input actor selection process was started. - GEditor->SelectNone(false, true); - for (auto CurrentActor : AllActors) - { - AActor* ParentActor = Cast(CurrentActor); - if (!ParentActor) - continue; - - GEditor->SelectActor(ParentActor, true, true); - } - - // Update the input details layout. - // if (CategoryBuilder.IsParentLayoutValid()) - // CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); -} - - -bool -FHoudiniInputDetails::Helper_CancelWorldSelection(TArray& InInputs, const FName& DetailsPanelName) -{ - if (InInputs.Num() <= 0) - return false; - - // Get the property module to access the details view - FPropertyEditorModule & PropertyModule = - FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); - - // Locate the details panel. - TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); - if (!DetailsView.IsValid()) - return false; - - if (!DetailsView->IsLocked()) - return false; - - class SLocalDetailsView : public SDetailsViewBase - { - public: - void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } - void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } - }; - auto * LocalDetailsView = static_cast(DetailsView.Get()); - - // Get all our parent components / actors - TArray AllComponents; - TArray AllActors; - for (auto CurrentInput : InInputs) - { - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); - if (!ParentComponent) - continue; - - AllComponents.Add(ParentComponent); - - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - if (!ParentActor) - continue; - - AllActors.Add(ParentActor); - } - - // Unlock the detail view and re-select our parent actors - { - LocalDetailsView->UnlockDetailsView(); - check(!DetailsView->IsLocked()); - - // Reset selected actor to itself, force refresh and override the lock. - DetailsView->SetObjects(AllActors, true, true); - } - - // Reselect the Asset Actor. If we don't do this, our Asset parameters will stop - // refreshing and the user will be very confused. It is also resetting the state - // of the selection before the input actor selection process was started. - GEditor->SelectNone(false, true); - for (auto ParentActorObj : AllActors) - { - AActor* ParentActor = Cast(ParentActorObj); - if (!ParentActor) - continue; - - GEditor->SelectActor(ParentActor, true, true); - } - - return true; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniInput.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetBlueprintComponent.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniLandscapeTranslator.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniPackageParams.h" + +#include "Editor.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SRotatorInputBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SEditableText.h" +#include "Widgets/Layout/SUniformGridPanel.h" +#include "Editor/UnrealEd/Public/AssetThumbnail.h" +#include "Editor/PropertyEditor/Private/SDetailsViewBase.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" +#include "SAssetDropTarget.h" +#include "ScopedTransaction.h" +#include "Engine/SkeletalMesh.h" +#include "Engine/Selection.h" +#include "EngineUtils.h" +#include "AssetData.h" +#include "Framework/SlateDelegates.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Modules/ModuleManager.h" +#include "SceneOutlinerModule.h" + +#include "Editor/UnrealEdEngine.h" +#include "HoudiniSplineComponentVisualizer.h" +#include "UnrealEdGlobals.h" +#include "Widgets/SWidget.h" + +#include "HoudiniEngineRuntimeUtils.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +// Customized TextBlock to show 'editing...' text if this Houdini Spline Component is being edited +class SCurveEditingTextBlock : public STextBlock +{ +public: + UHoudiniSplineComponent* HoudiniSplineComponent; + TSharedPtr HoudiniSplineComponentVisualizer; +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override + { + if (!HoudiniSplineComponentVisualizer.IsValid() || !HoudiniSplineComponent) + return LayerId; + + if (HoudiniSplineComponentVisualizer->GetEditedHoudiniSplineComponent() != HoudiniSplineComponent) + return LayerId; + + return STextBlock::OnPaint(Args, AllottedGeometry, MyClippingRect, + OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + } +}; + +void +FHoudiniInputDetails::CreateWidget( + IDetailCategoryBuilder& HouInputCategory, + TArray InInputs, + FDetailWidgetRow* InputRow) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouInputCategory.GetParentLayout().GetThumbnailPool(); + + EHoudiniInputType MainInputType = MainInput->GetInputType(); + UHoudiniAssetComponent* HAC = MainInput->GetTypedOuter(); + + // Create a widget row, or get the given row. + FDetailWidgetRow* Row = InputRow; + Row = InputRow == nullptr ? &(HouInputCategory.AddCustomRow(FText::GetEmpty())) : InputRow; + if (!Row) + return; + + // Create the standard input name widget if this is not a operator path parameter. + // Operator path parameter's name widget is handled by HoudiniParameterDetails. + if (!InputRow) + CreateNameWidget(MainInput, *Row, true, InInputs.Num()); + + // Create a vertical Box for storing the UI + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + // ComboBox : Input Type + const IDetailsView* DetailsView = HouInputCategory.GetParentLayout().GetDetailsView(); + AddInputTypeComboBox(HouInputCategory, VerticalBox, InInputs, DetailsView); + + // Checkbox : Keep World Transform + AddKeepWorldTransformCheckBox(VerticalBox, InInputs); + + + // Checkbox : CurveInput trigger cook on curve changed + AddCurveInputCookOnChangeCheckBox(VerticalBox, InInputs); + + + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) + { + // Checkbox : Pack before merging + AddPackBeforeMergeCheckbox(VerticalBox, InInputs); + } + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World || MainInputType == EHoudiniInputType::Asset) + { + AddImportAsReferenceCheckbox(VerticalBox, InInputs); + } + + if (MainInputType == EHoudiniInputType::Geometry || MainInputType == EHoudiniInputType::World) + { + // Checkboxes : Export LODs / Sockets / Collisions + AddExportCheckboxes(VerticalBox, InInputs); + } + + switch (MainInput->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + AddGeometryInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); + } + break; + + case EHoudiniInputType::Asset: + { + AddAssetInputUI(VerticalBox, InInputs); + } + break; + + case EHoudiniInputType::Curve: + { + AddCurveInputUI(HouInputCategory, VerticalBox, InInputs, AssetThumbnailPool); + } + break; + + case EHoudiniInputType::Landscape: + { + AddLandscapeInputUI(VerticalBox, InInputs); + } + break; + + case EHoudiniInputType::World: + { + AddWorldInputUI(HouInputCategory, VerticalBox, InInputs, DetailsView); + } + break; + + case EHoudiniInputType::Skeletal: + { + AddSkeletalInputUI(VerticalBox, InInputs, AssetThumbnailPool); + } + break; + } + + + Row->ValueWidget.Widget = VerticalBox; + + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + //Row.ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniInputDetails::CreateNameWidget( + UHoudiniInput* InInput, FDetailWidgetRow & Row, bool bLabel, int32 InInputCount) +{ + if (!InInput || InInput->IsPendingKill()) + return; + + FString InputLabelStr = InInput->GetLabel(); + if (InInputCount > 1) + { + InputLabelStr += TEXT(" (") + FString::FromInt(InInputCount) + TEXT(")"); + } + + const FText & FinalInputLabelText = bLabel ? FText::FromString(InputLabelStr) : FText::GetEmpty(); + FText InputTooltip = GetInputTooltip(InInput); + { + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(FinalInputLabelText) + .ToolTipText(InputTooltip) + .Font(FEditorStyle::GetFontStyle(!InInput->HasChanged() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))); + } +} + +FText +FHoudiniInputDetails::GetInputTooltip(UHoudiniInput* InParam) +{ + // TODO + return FText(); +} + +void +FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuilder, TSharedRef VerticalBox, TArray& InInputs, const IDetailsView* DetailsView) +{ + // Get the details view name and locked status + bool bDetailsLocked = false; + FName DetailsPanelName = "LevelEditorSelectionDetails"; + if (DetailsView) + { + DetailsPanelName = DetailsView->GetIdentifier(); + if (DetailsView->IsLocked()) + bDetailsLocked = true; + } + + // Lambda return a FText correpsonding to an input's current type + auto GetInputText = [](UHoudiniInput* InInput) + { + return FText::FromString(InInput->GetInputTypeAsString()); + }; + + // Lambda for changing inputs type + auto OnSelChanged = [DetailsPanelName, &CategoryBuilder](TArray InInputsToUpdate, TSharedPtr InNewChoice) + { + if (!InNewChoice.IsValid()) + return; + + EHoudiniInputType NewInputType = UHoudiniInput::StringToInputType(*InNewChoice.Get()); + if (NewInputType != EHoudiniInputType::World) + { + Helper_CancelWorldSelection(InInputsToUpdate, DetailsPanelName); + } + + if (InInputsToUpdate.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputsToUpdate[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetBlueprintComponent* HAB = MainInput->GetTypedOuter(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Input Type"), + MainInput->GetOuter()); + + bool bBlueprintStructureModified = false; + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetInputType() == NewInputType) + continue; + + /* This causes multiple issues. It does not set reset the previous type variable to Invalid sometimes + and it causes re-cook infinitely after few undo changing type. + { + CurInput->SetInputType(NewInputType); + CurInput->Modify(); + } + */ + + { + // Cache the current input type for undo type changing (since new type becomes previous type after undo) + EHoudiniInputType PrevType = CurInput->GetPreviousInputType(); + CurInput->SetPreviousInputType(NewInputType); + + CurInput->Modify(); + CurInput->SetPreviousInputType(PrevType); + CurInput->SetInputType(NewInputType, bBlueprintStructureModified); // pass in false for 2nd parameter in order to avoid creating default curve if empty + } + CurInput->MarkChanged(true); + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + + } + + if (HAB) + { + if (bBlueprintStructureModified) + HAB->MarkAsBlueprintStructureModified(); + } + + }; + + UHoudiniInput* MainInput = InInputs[0]; + TArray>* SupportedChoices = nullptr; + UHoudiniAssetBlueprintComponent* HAC = MainInput->GetTypedOuter(); + if (HAC) + { + SupportedChoices = FHoudiniEngineEditor::Get().GetBlueprintInputTypeChoiceLabels(); + } + else + { + SupportedChoices = FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels(); + } + + // ComboBox : Input Type + TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBoxInputType; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(ComboBoxInputType, SComboBox>) + .OptionsSource(SupportedChoices) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetInputTypeChoiceLabels())[((int32)MainInput->GetInputType() - 1)]) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([=](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnSelChanged(InInputs, NewChoice); + }) + [ + SNew( STextBlock ) + .Text_Lambda([=]() + { + return GetInputText(MainInput); + }) + .Font( FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; +} + +void +FHoudiniInputDetails:: AddCurveInputCookOnChangeCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + + if (!MainInput || MainInput->GetInputType() != EHoudiniInputType::Curve) + return; + + auto IsCheckedCookOnChange = [MainInput]() + { + if (!MainInput) + return ECheckBoxState::Checked; + + return MainInput->GetCookOnCurveChange() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + auto CheckStateChangedCookOnChange = [InInputs](ECheckBoxState NewState) + { + bool bChecked = NewState == ECheckBoxState::Checked; + for (auto & NextInput : InInputs) + { + if (!NextInput) + continue; + + NextInput->SetCookOnCurveChange(bChecked); + } + }; + + // Checkbox : Trigger cook on input curve changed + TSharedPtr< SCheckBox > CheckBoxCookOnCurveChanged; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxCookOnCurveChanged, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookOnCurveChangedCheckbox", "Auto-update")) + .ToolTipText(LOCTEXT("CookOnCurveChangeCheckboxTip", "When checked, cook is triggered automatically when the curve is modified.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedCookOnChange, MainInput]() + { + return IsCheckedCookOnChange(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedCookOnChange](ECheckBoxState NewState) + { + return CheckStateChangedCookOnChange( NewState); + }) + ]; + +} + +void +FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current KeepWorldTransform state + auto IsCheckedKeepWorldTransform = [&](UHoudiniInput* InInput) + { + return InInput->GetKeepWorldTransform() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing KeepWorldTransform state + auto CheckStateChangedKeepWorldTransform = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (MainInput->GetKeepWorldTransform() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Keep World Transform"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetKeepWorldTransform() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetKeepWorldTransform(bNewState); + CurInput->MarkChanged(true); + } + }; + + + // Checkbox : Keep World Transform + TSharedPtr< SCheckBox > CheckBoxTranformType; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxTranformType, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("KeepWorldTransformCheckbox", "Keep World Transform")) + .ToolTipText(LOCTEXT("KeepWorldTransformCheckboxTip", "Set this Input's object_merge Transform Type to INTO_THIS_OBJECT. If unchecked, it will be set to NONE.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedKeepWorldTransform(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedKeepWorldTransform(InInputs, NewState); + }) + ]; + + // the checkbox is read only for geo inputs + if (MainInput->GetInputType() == EHoudiniInputType::Geometry) + CheckBoxTranformType->SetEnabled(false); + + // Checkbox is read only if the input is an object-path parameter + //if (MainInput->IsObjectPathParameter() ) + // CheckBoxTranformType->SetEnabled(false); +} + +void +FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current PackBeforeMerge state + auto IsCheckedPackBeforeMerge = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetPackBeforeMerge() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing PackBeforeMerge state + auto CheckStateChangedPackBeforeMerge = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetPackBeforeMerge() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetPackBeforeMerge() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetPackBeforeMerge(bNewState); + CurInput->MarkChanged(true); + } + }; + + TSharedPtr< SCheckBox > CheckBoxPackBeforeMerge; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SAssignNew( CheckBoxPackBeforeMerge, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "PackBeforeMergeCheckbox", "Pack Geometry before merging" ) ) + .ToolTipText( LOCTEXT( "PackBeforeMergeCheckboxTip", "Pack each separate piece of geometry before merging them into the input." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedPackBeforeMerge(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedPackBeforeMerge(InInputs, NewState); + }) + ]; +} + +void +FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current PackBeforeMerge state + auto IsCheckedImportAsReference= [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetImportAsReference() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing PackBeforeMerge state + auto CheckStateChangedImportAsReference= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetImportAsReference() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Pack before merge"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetImportAsReference() == bNewState) + continue; + + TArray * InputObjs = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (InputObjs) + { + // Mark all its input objects as changed to trigger recook. + for (auto CurInputObj : *InputObjs) + { + if (!CurInputObj || CurInputObj->IsPendingKill()) + continue; + + if (CurInputObj->GetImportAsReference() != bNewState) + { + CurInputObj->SetImportAsReference(bNewState); + CurInputObj->MarkChanged(true); + } + } + } + + CurInput->Modify(); + CurInput->SetImportAsReference(bNewState); + } + }; + + TSharedPtr< SCheckBox > CheckBoxImportAsReference; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxImportAsReference, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ImportInputAsRefCheckbox", "Import input as references")) + .ToolTipText(LOCTEXT("ImportInputAsRefCheckboxTip", "Import input objects as references. (Geometry, World and Asset input types only)")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedImportAsReference(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedImportAsReference(InInputs, NewState); + }) + ]; +} +void +FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Lambda returning a CheckState from the input's current ExportLODs state + auto IsCheckedExportLODs = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportLODs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda returning a CheckState from the input's current ExportSockets state + auto IsCheckedExportSockets = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportSockets() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda returning a CheckState from the input's current ExportColliders state + auto IsCheckedExportColliders = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetExportColliders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing ExportLODs state + auto CheckStateChangedExportLODs = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportLODs() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export LODs"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportLODs() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportLODs(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + // Lambda for changing ExportSockets state + auto CheckStateChangedExportSockets = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportSockets() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Sockets"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportSockets() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportSockets(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + // Lambda for changing ExportColliders state + auto CheckStateChangedExportColliders = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetExportColliders() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing Export Colliders"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetExportColliders() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetExportColliders(bNewState); + CurInput->MarkChanged(true); + CurInput->MarkAllInputObjectsChanged(true); + } + }; + + TSharedPtr< SCheckBox > CheckBoxExportLODs; + TSharedPtr< SCheckBox > CheckBoxExportSockets; + TSharedPtr< SCheckBox > CheckBoxExportColliders; + VerticalBox->AddSlot().Padding( 2, 2, 5, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew(CheckBoxExportLODs, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportAllLOD", "Export LODs" ) ) + .ToolTipText( LOCTEXT( "ExportAllLODCheckboxTip", "If enabled, all LOD Meshes in this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportLODs(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportLODs(InInputs, NewState); + }) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( CheckBoxExportSockets, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportSockets", "Export Sockets" ) ) + .ToolTipText( LOCTEXT( "ExportSocketsTip", "If enabled, all Mesh Sockets in this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportSockets(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportSockets(InInputs, NewState); + }) + ] + + SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( CheckBoxExportColliders, SCheckBox ) + .Content() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "ExportColliders", "Export Colliders" ) ) + .ToolTipText( LOCTEXT( "ExportCollidersTip", "If enabled, collision geometry for this static mesh will be sent to Houdini." ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedExportColliders(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedExportColliders(InInputs, NewState); + }) + ] + ]; +} + +void +FHoudiniInputDetails::AddGeometryInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool ) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + + // Lambda for changing ExportColliders state + auto SetGeometryInputObjectsCount = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& NewInputCount) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing the number of Geometry Input Objects"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry) == NewInputCount) + continue; + + CurInput->Modify(); + + CurInput->SetInputObjectsNumber(EHoudiniInputType::Geometry, NewInputCount); + CurInput->MarkChanged(true); + + // + if (GEditor) + GEditor->RedrawAllViewports(); + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + }; + + InVerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs, NumInputObjects]() + { + return SetGeometryInputObjectsCount(InInputs, NumInputObjects + 1); + }), + LOCTEXT("AddInput", "Adds a Geometry Input"), true) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([SetGeometryInputObjectsCount, InInputs]() + { + return SetGeometryInputObjectsCount(InInputs, 0); + }), + LOCTEXT("EmptyInputs", "Removes All Inputs"), true) + ] + ]; + + for (int32 GeometryObjectIdx = 0; GeometryObjectIdx < NumInputObjects; GeometryObjectIdx++) + { + //UObject* InputObject = InParam.GetInputObject(Idx); + Helper_CreateGeometryWidget(CategoryBuilder, InInputs, GeometryObjectIdx, AssetThumbnailPool, InVerticalBox); + } +} + + + +// Create a single geometry widget for the given input object +void +FHoudiniInputDetails::Helper_CreateGeometryWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InGeometryObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox ) +{ + UHoudiniInput* MainInput = InInputs[0]; + + // Access the object used in the corresponding geometry input + UHoudiniInputObject* HoudiniInputObject = MainInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + UObject* InputObject = HoudiniInputObject ? HoudiniInputObject->GetObject() : nullptr; + + // Create thumbnail for this static mesh. + TSharedPtr StaticMeshThumbnail = MakeShareable( + new FAssetThumbnail(InputObject, 64, 64, AssetThumbnailPool)); + + // Lambda for adding new geometry input objects + auto UpdateGeometryObjectAt = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& AtIndex, UObject* InObject) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + if (!InObject || InObject->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changing a Geometry Input Object"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + UObject* InputObject = CurInput->GetInputObjectAt(EHoudiniInputType::Geometry, AtIndex); + if (InObject == InputObject) + continue; + + UHoudiniInputObject* CurrentInputObjectWrapper = CurInput->GetHoudiniInputObjectAt(AtIndex); + if (CurrentInputObjectWrapper) + CurrentInputObjectWrapper->Modify(); + + CurInput->Modify(); + + CurInput->SetInputObjectAt(EHoudiniInputType::Geometry, AtIndex, InObject); + CurInput->MarkChanged(true); + + // TODO: Not needed? + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + }; + + // Drop Target: Static/Skeletal Mesh + TSharedPtr HorizontalBox = NULL; + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop_Lambda([]( const UObject* InObject) + { + return UHoudiniInput::IsObjectAcceptable(EHoudiniInputType::Geometry, InObject); + }) + .OnAssetDropped_Lambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt](UObject* InObject) + { + return UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, InObject); + }) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + // Thumbnail : Static Mesh + FText ParameterLabelText = FText::FromString(MainInput->GetLabel()); + + TSharedPtr< SBorder > StaticMeshThumbnailBorder; + HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(StaticMeshThumbnailBorder, SBorder) + .Padding(5.0f) + .OnMouseDoubleClick_Lambda([MainInput, InGeometryObjectIdx](const FGeometry&, const FPointerEvent&) + { + UObject* InputObject = MainInput->GetInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + if (GEditor && InputObject) + GEditor->EditObject(InputObject); + + return FReply::Handled(); + }) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(ParameterLabelText) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + TWeakPtr WeakStaticMeshThumbnailBorder(StaticMeshThumbnailBorder); + StaticMeshThumbnailBorder->SetBorderImage(TAttribute::Create( + TAttribute::FGetter::CreateLambda([WeakStaticMeshThumbnailBorder]() + { + TSharedPtr ThumbnailBorder = WeakStaticMeshThumbnailBorder.Pin(); + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ))); + + FText MeshNameText = FText::GetEmpty(); + if (InputObject) + MeshNameText = FText::FromString(InputObject->GetName()); + + + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add Combo box : Static Mesh + TSharedPtr StaticMeshComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SAssignNew(StaticMeshComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(MeshNameText) + ] + ] + ]; + + + TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); + StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( + [MainInput, InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt]() + { + TArray< const UClass * > AllowedClasses = UHoudiniInput::GetAllowedClasses(EHoudiniInputType::Geometry); + UObject* DefaultObj = MainInput->GetInputObjectAt(InGeometryObjectIdx); + + TArray< UFactory * > NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(DefaultObj), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( + [InInputs, InGeometryObjectIdx, WeakStaticMeshComboButton, UpdateGeometryObjectAt](const FAssetData & AssetData) + { + TSharedPtr ComboButton = WeakStaticMeshComboButton.Pin(); + if (ComboButton.IsValid()) + { + ComboButton->SetIsOpen(false); + UObject * Object = AssetData.GetAsset(); + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); + } + } + ), + FSimpleDelegate::CreateLambda([]() {}) + ); + } + )); + + + // Add buttons + TSharedPtr ButtonHorizontalBox; + ComboAndButtonBox->AddSlot() + .FillHeight(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ButtonHorizontalBox, SHorizontalBox) + ]; + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add( TEXT( "Asset" ), MeshNameText ); + FText StaticMeshTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", + "Browse to '{Asset}' in Content Browser" ), Args ); + + // Button : Use selected in content browser + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton(FSimpleDelegate::CreateLambda([InInputs, InGeometryObjectIdx, UpdateGeometryObjectAt]() + { + if (GEditor) + { + TArray CBSelections; + GEditor->GetContentBrowserSelections(CBSelections); + + // Get the first selected static mesh object + UObject* Object = nullptr; + for (auto & CurAssetData : CBSelections) + { + if (CurAssetData.AssetClass != UStaticMesh::StaticClass()->GetFName()) + continue; + + Object = CurAssetData.GetAsset(); + break; + } + + if (Object && !Object->IsPendingKill()) + { + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); + } + } + }), TAttribute< FText >(LOCTEXT("GeometryInputUseSelectedAssetFromCB", "Use Selected Asset from Content Browser"))) + ]; + + // Button : Browse Static Mesh + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateLambda([MainInput, InGeometryObjectIdx]() + { + UObject* InputObject = MainInput->GetInputObjectAt(InGeometryObjectIdx); + if (GEditor && InputObject) + { + TArray Objects; + Objects.Add(InputObject); + GEditor->SyncBrowserToObjects(Objects); + } + }), + TAttribute< FText >( StaticMeshTooltip ) + ) + ]; + + // ButtonBox : Reset + ButtonHorizontalBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBase", "Reset to default static mesh" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked_Lambda( [UpdateGeometryObjectAt, InInputs, InGeometryObjectIdx]() + { + UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, nullptr); + return FReply::Handled(); + }) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + // Insert/Delete/Duplicate + ButtonHorizontalBox->AddSlot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( + FExecuteAction::CreateLambda( [ InInputs, InGeometryObjectIdx, MainInput ]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: insert a Geometry Input Object"), + MainInput->GetOuter()); + // Insert + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->InsertInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + } + } ), + FExecuteAction::CreateLambda([MainInput, InInputs, InGeometryObjectIdx]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: delete a Geometry Input Object"), + MainInput->GetOuter()); + + // Delete + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->DeleteInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + + if (GEditor) + GEditor->RedrawAllViewports(); + } + } ), + FExecuteAction::CreateLambda([InInputs, InGeometryObjectIdx, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), + MainInput->GetOuter()); + + // Duplicate + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->DuplicateInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + } + } ) ) + ]; + + // TRANSFORM OFFSET EXPANDER + { + TSharedPtr ExpanderArrow; + TSharedPtr ExpanderImage; + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SAssignNew( ExpanderArrow, SButton ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ClickMethod( EButtonClickMethod::MouseDown ) + .Visibility( EVisibility::Visible ) + .OnClicked(FOnClicked::CreateLambda([InInputs, InGeometryObjectIdx, MainInput, &CategoryBuilder]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled();; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: duplicate a Geometry Input Object"), + MainInput->GetOuter()); + + // Expand transform + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + CurInput->OnTransformUIExpand(InGeometryObjectIdx); + } + + // TODO: Not needed? + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + })) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ] + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT("GeoInputTransform", "Transform Offset") ) + .ToolTipText( LOCTEXT( "GeoInputTransformTooltip", "Transform offset used for correction before sending the asset to Houdini" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + TWeakPtr WeakExpanderArrow(ExpanderArrow); + // Set delegate for image + ExpanderImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([InGeometryObjectIdx, MainInput, WeakExpanderArrow]() + { + FName ResourceName; + TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); + if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + return FEditorStyle::GetBrush(ResourceName); + } + ))); + } + + // Lambda for changing the transform values + auto ChangeTransformOffsetAt = [&](const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex, const bool& DoChange, TArray InInputs) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputTransformChange", "Houdini Input: Changing Transform offset"), + InInputs[0]->GetOuter()); + + bool bChanged = true; + for (int Idx = 0; Idx < InInputs.Num(); Idx++) + { + if (!InInputs[Idx] || InInputs[Idx]->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObject = InInputs[Idx]->GetHoudiniInputObjectAt(AtIndex); + if (InputObject) + InputObject->Modify(); + + bChanged &= InInputs[Idx]->SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); + } + + if (bChanged && DoChange) + { + // Mark the values as changed to trigger an update + for (int Idx = 0; Idx < InInputs.Num(); Idx++) + { + InInputs[Idx]->MarkChanged(true); + } + } + else + { + // Cancel the transaction + Transaction.Cancel(); + } + }; + + // Get Visibility of reset buttons + bool bResetButtonVisiblePosition = false; + bool bResetButtonVisibleRotation = false; + bool bResetButtonVisibleScale = false; + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + FTransform* CurTransform = CurInput->GetTransformOffset(InGeometryObjectIdx); + if (!CurTransform) + continue; + + if (CurTransform->GetLocation() != FVector::ZeroVector) + bResetButtonVisiblePosition = true; + + FRotator Rotator = CurTransform->Rotator(); + if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) + bResetButtonVisibleRotation = true; + + if (CurTransform->GetScale3D() != FVector::OneVector) + bResetButtonVisibleScale = true; + } + + auto ChangeTransformOffsetUniformlyAt = [InGeometryObjectIdx, InInputs, ChangeTransformOffsetAt](const float & Val, const int32& PosRotScaleIndex) + { + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 0, true, InInputs); + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 1, true, InInputs); + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, PosRotScaleIndex, 2, true, InInputs); + }; + + // TRANSFORM OFFSET + if (MainInput->IsTransformUIExpanded(InGeometryObjectIdx)) + { + // Position + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew(STextBlock) + .Text( LOCTEXT("GeoInputTranslate", "T") ) + .ToolTipText( LOCTEXT( "GeoInputTranslateTooltip", "Translate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .AllowSpin(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetX, InGeometryObjectIdx))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetY, InGeometryObjectIdx))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetPositionOffsetZ, InGeometryObjectIdx))) + .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 0, true, InInputs); }) + .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 1, true, InInputs); }) + .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 0, 2, true, InInputs); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([MainInput, ChangeTransformOffsetUniformlyAt, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 0); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + + // Rotation + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text( LOCTEXT("GeoInputRotate", "R") ) + .ToolTipText( LOCTEXT( "GeoInputRotateTooltip", "Rotate" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SRotatorInputBox ) + .AllowSpin( true ) + .bColorAxisLabels( true ) + .Roll(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetRoll, InGeometryObjectIdx))) + .Pitch(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetPitch, InGeometryObjectIdx))) + .Yaw(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetRotationOffsetYaw, InGeometryObjectIdx))) + .OnRollCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 0, true, InInputs); }) + .OnPitchCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 1, true, InInputs); }) + .OnYawCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 1, 2, true, InInputs); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (Not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 1); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + + // Scale + bool bLocked = false; + if (HoudiniInputObject) + bLocked = HoudiniInputObject->IsUniformScaleLocked(); + + VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 1.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( STextBlock ) + .Text( LOCTEXT( "GeoInputScale", "S" ) ) + .ToolTipText( LOCTEXT( "GeoInputScaleTooltip", "Scale" ) ) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew( SVectorInputBox ) + .bColorAxisLabels( true ) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetX, InGeometryObjectIdx))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetY, InGeometryObjectIdx))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateUObject( + MainInput, &UHoudiniInput::GetScaleOffsetZ, InGeometryObjectIdx))) + .OnXCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 0, true, InInputs); + }) + .OnYCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 1, true, InInputs); + }) + .OnZCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) + { + if (bLocked) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(Val, InGeometryObjectIdx, 2, 2, true, InInputs); + }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + SNew(SHorizontalBox) + // Lock Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ToolTipText(HoudiniInputObject ? + LOCTEXT("GeoInputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the input object maintains its shape in each direction when scaled") : + LOCTEXT("GeoInputLockButtonToolTipNoObject", "No input object selected")) + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(bLocked ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([InInputs, MainInput, InGeometryObjectIdx, HoudiniInputObject, &CategoryBuilder]() + { + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + UHoudiniInputObject*CurInputObject = CurInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); + + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + CurInputObject->SwitchUniformScaleLock(); + } + + if (HoudiniInputObject) + { + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); + }) + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, MainInput, &CategoryBuilder]() + { + ChangeTransformOffsetUniformlyAt(1.0f, 2); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + return FReply::Handled(); + }) + ] + ] + ]; + } +} + +void +FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Houdini Asset Picker Widget + { + FMenuBuilder MenuBuilder = Helper_CreateHoudiniAssetPickerWidget(InInputs); + + VerticalBox->AddSlot() + .Padding(2.0f, 2.0f, 5.0f, 2.0f) + .AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + // Button : Clear Selection + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + auto IsClearButtonEnabled = [MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return false; + + TArray* AssetInputObjectsArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + + if (!AssetInputObjectsArray) + return false; + + if (AssetInputObjectsArray->Num() <= 0) + return false; + + return true; + }; + + FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChangeClear", "Houdini Input: Clearing asset input selection"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + TArray* AssetInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + if (!AssetInputObjectsArray) + continue; + + CurrentInput->Modify(); + + AssetInputObjectsArray->Empty(); + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }); + + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + // Button : Clear Selection + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("ClearSelection", "Clear Selection")) + .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) + .IsEnabled_Lambda(IsClearButtonEnabled) + .OnClicked(OnClearSelect) + ] + ]; + + // Do not enable select all/clear select when selection has been started and details are locked + //HorizontalBox->SetEnabled(!bDetailsLocked); + } + + +} + +void +FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs, TSharedPtr AssetThumbnailPool) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::Curve); + + // lambda for inserting an input Houdini curve. + auto InsertAnInputCurve = [MainInput, &CategoryBuilder](const int32& NewInputCount) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetComponent* OuterHAC = Cast(MainInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + // Do not insert input object when the HAC does not finish cooking + EHoudiniAssetState CurrentHACState = OuterHAC->GetAssetState(); + if (CurrentHACState >= EHoudiniAssetState::PreCook && CurrentHACState<= EHoudiniAssetState::Processing) + return; + + // Clear the to be inserted object array, which records the pointers of the input objects to be inserted. + MainInput->LastInsertedInputs.Empty(); + // Record the pointer of the object to be inserted before transaction for undo the insert action. + bool bBlueprintStructureModified = false; + UHoudiniInputHoudiniSplineComponent* NewInput = MainInput->CreateHoudiniSplineInput(nullptr, true, false, bBlueprintStructureModified); + MainInput->LastInsertedInputs.Add(NewInput); + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction(FText::FromString("Modifying Houdini input: Adding curve input.")); + MainInput->Modify(); + + // Modify the MainInput. + MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType())->Add(NewInput); + + MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, NewInputCount); + + if (bBlueprintStructureModified) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + // Add Rot/Scale attribute checkbox + FText TooltipText = LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesTooltip", "If enabled, rot and scale attributes will be added per to the input curve on each control points."); + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineCurveAddRotScaleAttributesLabel", "Add rot & scale Attributes")) + .ToolTipText(TooltipText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + //.MinDesiredWidth(160.f) + ] + .OnCheckStateChanged_Lambda([InInputs](ECheckBoxState NewState) + { + const bool bChecked = (NewState == ECheckBoxState::Checked); + for (auto& CurrentInput : InInputs) + { + if (!IsValid(CurrentInput)) + continue; + + CurrentInput->SetAddRotAndScaleAttributes(bChecked); + } + }) + .IsChecked_Lambda([MainInput]() + { + if (!IsValid(MainInput)) + return ECheckBoxState::Unchecked; + + return MainInput->IsAddRotAndScaleAttributesEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .ToolTipText(TooltipText) + ]; + + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::Format(LOCTEXT("NumArrayItemsFmt", "{0} elements"), FText::AsNumber(NumInputObjects))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([InsertAnInputCurve, NumInputObjects]() + { + return InsertAnInputCurve(NumInputObjects+1); + //return SetCurveInputObjectsCount(NumInputObjects+1); + }), + + LOCTEXT("AddInputCurve", "Adds a Curve Input"), true) + ] + + SHorizontalBox::Slot() + .Padding(1.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton( FSimpleDelegate::CreateLambda([InInputs, MainInput, &CategoryBuilder]() + { + TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + + // Detach all curves before deleting. + for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) + { + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast ((*CurveInputComponentArray)[n]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + } + + // Clear the insert objects buffer before transaction. + MainInput->LastInsertedInputs.Empty(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction(FText::FromString("Modifying Houdini Input: Delete curve inputs.")); + MainInput->Modify(); + + bool bBlueprintStructureModified = false; + + // actual delete. + for (int n = CurveInputComponentArray->Num() - 1; n >= 0; n--) + { + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast ((*CurveInputComponentArray)[n]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + MainInput->RemoveSplineFromInputObject(HoudiniInput, bBlueprintStructureModified); + + MainInput->DeleteInputObjectAt(EHoudiniInputType::Curve, n); + } + + MainInput->SetInputObjectsNumber(EHoudiniInputType::Curve, 0); + + if (bBlueprintStructureModified) + { + UActorComponent* OuterComponent = Cast(MainInput->GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterComponent); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + + }), + LOCTEXT("EmptyInputsCurve", "Removes All Curve Inputs"), true) + ] + + SHorizontalBox::Slot().FillWidth(80.f).MaxWidth(80.f) + [ + SNew(SButton) + .Text(LOCTEXT("ResetCurveOffsetStr", "Reset Offset")) + .OnClicked_Lambda([MainInput]()->FReply + { + MainInput->ResetDefaultCurveOffset(); + return FReply::Handled(); + }) + ] + ]; + + //UHoudiniSplineComponent* SplineCompBeingEdited = nullptr; + TSharedPtr HouSplineComponentVisualizer; + if (GUnrealEd) + { + TSharedPtr Visualizer = + GUnrealEd->FindComponentVisualizer(UHoudiniSplineComponent::StaticClass()->GetFName()); + + HouSplineComponentVisualizer = StaticCastSharedPtr(Visualizer); + } + + + for (int n = 0; n < NumInputObjects; n++) + { + Helper_CreateCurveWidget(CategoryBuilder, InInputs, n, AssetThumbnailPool ,VerticalBox, HouSplineComponentVisualizer); + } +} + +void +FHoudiniInputDetails::Helper_CreateCurveWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InCurveObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox, + TSharedPtr HouSplineComponentVisualizer) +{ + UHoudiniInput* MainInput = InInputs[0]; + + if (!MainInput || MainInput->IsPendingKill()) + return; + + UHoudiniAssetComponent * OuterHAC = Cast(MainInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + auto GetHoudiniSplineComponentAtIndex = [](UHoudiniInput * Input, int32 Index) + { + UHoudiniSplineComponent* FoundHoudiniSplineComponent = nullptr; + if (!Input || Input->IsPendingKill()) + return FoundHoudiniSplineComponent; + + // Get the TArray ptr to the curve objects in this input + TArray * CurveInputComponentArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + return FoundHoudiniSplineComponent; + + if (!CurveInputComponentArray->IsValidIndex(Index)) + return FoundHoudiniSplineComponent; + + // Access the object used in the corresponding Houdini curve input + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + FoundHoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + + return FoundHoudiniSplineComponent; + }; + + + // Get the TArray ptr to the curve objects in this input + TArray * CurveInputComponentArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + return; + + if (!CurveInputComponentArray->IsValidIndex(InCurveObjectIdx)) + return; + + // Access the object used in the corresponding Houdini curve input + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[InCurveObjectIdx]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + UHoudiniSplineComponent * HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent) + return; + + FString HoudiniSplineName = HoudiniSplineComponent->GetHoudiniSplineName(); + + // Editable label for the current Houdini curve + TSharedPtr LabelHorizontalBox; + VerticalBox->AddSlot() + .Padding(0, 2) + .AutoHeight() + [ + SAssignNew(LabelHorizontalBox, SHorizontalBox) + ]; + + + TSharedPtr LabelBlock; + LabelHorizontalBox->AddSlot() + .Padding(0, 15, 0, 2) + .MaxWidth(150.f) + .FillWidth(150.f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Left) + [ + SNew(SBox).HeightOverride(20.f).WidthOverride(200.f).VAlign(VAlign_Center) + [ + SAssignNew(LabelBlock, SEditableText).Text(FText::FromString(HoudiniSplineName)) + .OnTextCommitted_Lambda([HoudiniSplineComponent](FText NewText, ETextCommit::Type CommitType) + { + if (CommitType == ETextCommit::Type::OnEnter) + { + HoudiniSplineComponent->SetHoudiniSplineName(NewText.ToString()); + } + }) + ] + ]; + + // 'Editing...' TextBlock showing if this component is being edited + TSharedPtr EditingTextBlock; + LabelHorizontalBox->AddSlot() + .Padding(0, 15, 0, 2) + .MaxWidth(55.f) + .FillWidth(55.f) + .VAlign(VAlign_Bottom) + .HAlign(HAlign_Left) + [ + SNew(SBox).HeightOverride(20.f).WidthOverride(75.f).VAlign(VAlign_Center) + [ + SAssignNew(EditingTextBlock, SCurveEditingTextBlock).Text(LOCTEXT("HoudiniCurveInputEditingLabel", "(editing...)")) + ] + ]; + + EditingTextBlock->HoudiniSplineComponent = HoudiniSplineComponent; + EditingTextBlock->HoudiniSplineComponentVisualizer = HouSplineComponentVisualizer; + + // Lambda for deleting the current curve input + auto DeleteHoudiniCurveAtIndex = [InInputs, InCurveObjectIdx, OuterHAC, CurveInputComponentArray, &CategoryBuilder]() + { + if (!OuterHAC|| OuterHAC->IsPendingKill()) + return; + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeDeleteACurve", "Houdini Input: Deleting a curve input"), + OuterHAC); + + int MainInputCurveArraySize = CurveInputComponentArray->Num(); + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + Input->Modify(); + + TArray* InputObjectArr = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!InputObjectArr) + continue; + + if (!InputObjectArr->IsValidIndex(InCurveObjectIdx)) + continue; + + if (MainInputCurveArraySize != InputObjectArr->Num()) + continue; + + UHoudiniInputHoudiniSplineComponent* HoudiniInput = + Cast((*InputObjectArr)[InCurveObjectIdx]); + if (!HoudiniInput || HoudiniInput->IsPendingKill()) + return; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); + if (!HoudiniSplineComponent) + return; + + // Detach the spline component before delete. + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + + // This input is marked changed when an input component is deleted. + Input->DeleteInputObjectAt(EHoudiniInputType::Curve, InCurveObjectIdx); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + // Add delete button UI + LabelHorizontalBox->AddSlot().Padding(0, 2, 0, 2).HAlign(HAlign_Right).VAlign(VAlign_Bottom).AutoWidth() + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([DeleteHoudiniCurveAtIndex]() + { + return DeleteHoudiniCurveAtIndex(); + })) + ]; + + + TSharedPtr HorizontalBox = NULL; + VerticalBox->AddSlot().Padding(0, 2).AutoHeight()[SAssignNew(HorizontalBox, SHorizontalBox)]; + + // Closed check box + // Lambda returning a closed state + auto IsCheckedClosedCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsClosedCurve() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing Closed state + auto CheckStateChangedClosedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeClosed", "Houdini Input: Changing Curve Closed"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->IsClosedCurve() == bNewState) + continue; + + HoudiniSplineComponent->Modify(); + + HoudiniSplineComponent->SetClosedCurve(bNewState); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Add Closed check box UI + TSharedPtr CheckBoxClosed = NULL; + HorizontalBox->AddSlot().Padding(0, 2).AutoWidth() + [ + SAssignNew(CheckBoxClosed, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("ClosedCurveCheckBox", "Closed")) + .ToolTipText(LOCTEXT("ClosedCurveCheckboxTip", "Close this input curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedClosedCurve]() + { + return IsCheckedClosedCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedClosedCurve](ECheckBoxState NewState) + { + return CheckStateChangedClosedCurve(NewState); + }) + ]; + + // Reversed check box + // Lambda returning a reversed state + auto IsCheckedReversedCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsReversed() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing reversed state + auto CheckStateChangedReversedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeReversed", "Houdini Input: Changing Curve Reversed"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->IsReversed() == bNewState) + continue; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetReversed(bNewState); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Add reversed check box UI + TSharedPtr CheckBoxReversed = NULL; + HorizontalBox->AddSlot() + .Padding(2, 2) + .AutoWidth() + [ + SAssignNew(CheckBoxReversed, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("ReversedCurveCheckBox", "Reversed")) + .ToolTipText(LOCTEXT("ReversedCurveCheckboxTip", "Reverse this input curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedReversedCurve]() + { + return IsCheckedReversedCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedReversedCurve](ECheckBoxState NewState) + { + return CheckStateChangedReversedCurve(NewState); + }) + ]; + + // Visible check box + // Lambda returning a visible state + auto IsCheckedVisibleCurve = [HoudiniSplineComponent]() + { + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return HoudiniSplineComponent->IsHoudiniSplineVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing visible state + auto CheckStateChangedVisibleCurve = [GetHoudiniSplineComponentAtIndex, InInputs, OuterHAC, InCurveObjectIdx](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent) + continue; + + if (HoudiniSplineComponent->IsHoudiniSplineVisible() == bNewState) + return; + + HoudiniSplineComponent->SetHoudiniSplineVisible(bNewState); + } + + if (GEditor) + GEditor->RedrawAllViewports(); + + }; + + // Add visible check box UI + TSharedPtr CheckBoxVisible = NULL; + HorizontalBox->AddSlot().Padding(2, 2).AutoWidth() + [ + SAssignNew(CheckBoxVisible, SCheckBox).Content() + [ + SNew(STextBlock).Text(LOCTEXT("VisibleCurveCheckBox", "Visible")) + .ToolTipText(LOCTEXT("VisibleCurveCheckboxTip", "Set the visibility of this curve.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedVisibleCurve]() + { + return IsCheckedVisibleCurve(); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedVisibleCurve](ECheckBoxState NewState) + { + return CheckStateChangedVisibleCurve(NewState); + }) + ]; + + // Curve type comboBox + // Lambda for changing Houdini curve type + auto OnCurveTypeChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + if (!InNewChoice.IsValid()) + return; + + EHoudiniCurveType NewInputType = UHoudiniInput::StringToHoudiniCurveType(*InNewChoice.Get()); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeType", "Houdini Input: Changing Curve Type"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->GetCurveType() == NewInputType) + continue; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetCurveType(NewInputType); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Lambda for getting Houdini curve type + auto GetCurveTypeText = [HoudiniSplineComponent]() + { + return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(HoudiniSplineComponent->GetCurveType())); + }; + + // Add curve type combo box UI + TSharedPtr CurveTypeHorizontalBox; + VerticalBox->AddSlot() + .Padding(0, 2, 2, 0) + .AutoHeight() + [ + SAssignNew(CurveTypeHorizontalBox, SHorizontalBox) + ]; + + // Add curve type label UI + CurveTypeHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() + [ + SNew(STextBlock).Text(LOCTEXT("CurveTypeText", "Curve Type ")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveType; + CurveTypeHorizontalBox->AddSlot() + .Padding(2, 2, 5, 2) + .FillWidth(150.f) + .MaxWidth(150.f) + [ + SAssignNew(ComboBoxCurveType, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveTypeChoiceLabels())[(int)HoudiniSplineComponent->GetCurveType()]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnCurveTypeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnCurveTypeChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([GetCurveTypeText]() + { + return GetCurveTypeText(); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + // Houdini curve method combo box + // Lambda for changing Houdini curve method + auto OnCurveMethodChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) + { + if (!OuterHAC || OuterHAC->IsPendingKill()) + return; + + if (!InNewChoice.IsValid()) + return; + + EHoudiniCurveMethod NewInputMethod = UHoudiniInput::StringToHoudiniCurveMethod(*InNewChoice.Get()); + + // Record a transaction for undo/redo. + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniCurveInputChangeMethod", "Houdini Input: Changing Curve Method"), + OuterHAC); + + for (auto & Input : InInputs) + { + if (!Input || Input->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + if (HoudiniSplineComponent->GetCurveMethod() == NewInputMethod) + return; + + HoudiniSplineComponent->Modify(); + HoudiniSplineComponent->SetCurveMethod(NewInputMethod); + HoudiniSplineComponent->MarkChanged(true); + } + }; + + // Lambda for getting Houdini curve method + auto GetCurveMethodText = [HoudiniSplineComponent]() + { + return FText::FromString(FHoudiniEngineEditorUtils::HoudiniCurveMethodToString(HoudiniSplineComponent->GetCurveMethod())); + }; + + // Add curve method combo box UI + TSharedPtr< SHorizontalBox > CurveMethodHorizontalBox; + VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(CurveMethodHorizontalBox, SHorizontalBox)]; + + // Add curve method label UI + CurveMethodHorizontalBox->AddSlot().Padding(0, 10, 0, 2).AutoWidth() + [ + SNew(STextBlock).Text(LOCTEXT("CurveMethodText", "Curve Method ")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr < SComboBox < TSharedPtr < FString > > > ComboBoxCurveMethod; + CurveMethodHorizontalBox->AddSlot().Padding(2, 2, 5, 2).FillWidth(150.f).MaxWidth(150.f) + [ + SAssignNew(ComboBoxCurveMethod, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniCurveMethodChoiceLabels())[(int)HoudiniSplineComponent->GetCurveMethod()]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnCurveMethodChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnCurveMethodChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([GetCurveMethodText]() + { + return GetCurveMethodText(); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + auto BakeInputCurveLambda = [](TArray Inputs, int32 Index, bool bBakeToBlueprint) + { + for (auto & NextInput : Inputs) + { + if (!NextInput || NextInput->IsPendingKill()) + continue; + + UHoudiniAssetComponent * OuterHAC = Cast(NextInput->GetOuter()); + if (!OuterHAC || OuterHAC->IsPendingKill()) + continue; + + AActor * OwnerActor = OuterHAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + continue; + + TArray * CurveInputComponentArray = NextInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); + if (!CurveInputComponentArray) + continue; + + if (!CurveInputComponentArray->IsValidIndex(Index)) + continue; + + UHoudiniInputObject* HoudiniInputObject = (*CurveInputComponentArray)[Index]; + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = + Cast(HoudiniInputObject); + + if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + FHoudiniPackageParams PackageParams; + PackageParams.BakeFolder = OuterHAC->BakeFolder.Path; + PackageParams.HoudiniAssetName = OuterHAC->GetName(); + PackageParams.GeoId = NextInput->GetAssetNodeId(); + PackageParams.PackageMode = EPackageMode::Bake; + PackageParams.ObjectId = Index; + PackageParams.ObjectName = OwnerActor->GetName() + "InputHoudiniSpline" + FString::FromInt(Index); + + if (bBakeToBlueprint) + { + FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( + HoudiniSplineComponent, + PackageParams, + OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); + } + else + { + FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( + HoudiniSplineComponent, + PackageParams, + OwnerActor->GetWorld(), OwnerActor->GetActorTransform()); + } + } + + return FReply::Handled(); + }; + + // Add input curve bake button + TSharedPtr< SHorizontalBox > InputCurveBakeHorizontalBox; + VerticalBox->AddSlot().Padding(0, 2, 2, 0).AutoHeight()[SAssignNew(InputCurveBakeHorizontalBox, SHorizontalBox)]; + VerticalBox->AddSlot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(110.f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("HoudiniInputCurveBakeToActorButton", "Bake to Actor")) + .IsEnabled(true) + .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() + { + return BakeInputCurveLambda(InInputs, InCurveObjectIdx, false); + }) + .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToActorButtonToolTip", "Bake this input curve to Actor")) + ] + + + SHorizontalBox::Slot().MaxWidth(110.f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("HoudiniInputCurveBakeToBPButton", "Bake to Blueprint")) + .IsEnabled(true) + .OnClicked_Lambda([InInputs, InCurveObjectIdx, BakeInputCurveLambda]() + { + return BakeInputCurveLambda(InInputs, InCurveObjectIdx, true); + }) + .ToolTipText(LOCTEXT("HoudiniInputCurveBakeToBPButtonToolTip", "Bake this input curve to Blueprint")) + ] + ]; + + // Do we actually need to set enable the UI components? + if (MainInput->GetInputType() == EHoudiniInputType::Curve) + { + LabelBlock->SetEnabled(true); + CheckBoxClosed->SetEnabled(true); + CheckBoxReversed->SetEnabled(true); + CheckBoxVisible->SetEnabled(true); + ComboBoxCurveType->SetEnabled(true); + ComboBoxCurveMethod->SetEnabled(true); + } + else + { + LabelBlock->SetEnabled(false); + CheckBoxClosed->SetEnabled(false); + CheckBoxReversed->SetEnabled(false); + CheckBoxVisible->SetEnabled(false); + ComboBoxCurveType->SetEnabled(false); + ComboBoxCurveMethod->SetEnabled(false); + } +} + +void +FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, TArray& InInputs) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + // Lambda returning a CheckState from the input's current KeepWorldTransform state + auto IsCheckedUpdateInputLandscape = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetUpdateInputLandscape() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing KeepWorldTransform state + auto CheckStateChangedUpdateInputLandscape = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangedUpdate", "Houdini Input: Changing Keep World Transform"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (bNewState == CurInput->GetUpdateInputLandscape()) + continue; + + CurInput->Modify(); + + UHoudiniAssetComponent* HAC = Cast(CurInput->GetOuter()); + if (!HAC) + continue; + + TArray* LandscapeInputObjects = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (!LandscapeInputObjects) + continue; + + for (UHoudiniInputObject* NextInputObj : *LandscapeInputObjects) + { + UHoudiniInputLandscape* CurrentInputLandscape = Cast(NextInputObj); + if (!CurrentInputLandscape) + continue; + + ALandscapeProxy* CurrentInputLandscapeProxy = CurrentInputLandscape->GetLandscapeProxy(); + if (!CurrentInputLandscapeProxy) + continue; + + if (bNewState) + { + // We want to update this landscape data directly, start by backing it up to image files in the temp folder + FString BackupBaseName = HAC->TemporaryCookFolder.Path + + TEXT("/") + + CurrentInputLandscapeProxy->GetName() + + TEXT("_") + + HAC->GetComponentGUID().ToString().Left(FHoudiniEngineUtils::PackageGUIDComponentNameLength); + + // We need to cache the input landscape to a file + FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(BackupBaseName, CurrentInputLandscapeProxy); + + // Cache its transform on the input + CurrentInputLandscape->CachedInputLandscapeTraqnsform = CurrentInputLandscapeProxy->ActorToWorld(); + + HAC->SetMobility(EComponentMobility::Static); + CurrentInputLandscapeProxy->AttachToComponent(HAC, FAttachmentTransformRules::KeepWorldTransform); + } + else + { + // We are not updating this input landscape anymore, detach it and restore its backed-up values + CurrentInputLandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + + // Restore the input landscape's backup data + FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(CurrentInputLandscapeProxy); + + // Reapply the source Landscape's transform + CurrentInputLandscapeProxy->SetActorTransform(CurrentInputLandscape->CachedInputLandscapeTraqnsform); + + // TODO: + // Clear the input obj map? + } + } + + CurInput->bUpdateInputLandscape = (NewState == ECheckBoxState::Checked); + CurInput->MarkChanged(true); + } + }; + + // CheckBox : Update Input Landscape Data + TSharedPtr< SCheckBox > CheckBoxUpdateInput; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew( CheckBoxUpdateInput, SCheckBox).Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) + .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedUpdateInputLandscape, MainInput]() + { + return IsCheckedUpdateInputLandscape(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedUpdateInputLandscape, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedUpdateInputLandscape(InInputs, NewState); + }) + ]; + + // Actor picker: Landscape. + FMenuBuilder MenuBuilder = Helper_CreateLandscapePickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + + // Checkboxes : Export landscape as Heightfield/Mesh/Points + { + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAs", "Export Landscape As")) + .ToolTipText(LOCTEXT("LandscapeExportAsToolTip", "Choose the type of data you want the ladscape to be exported to:\n * Heightfield\n * Mesh\n * Points")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TSharedPtr ButtonOptionsPanel; + VerticalBox->AddSlot().Padding(5, 2, 5, 2).AutoHeight() + [ + SAssignNew(ButtonOptionsPanel, SUniformGridPanel) + ]; + + auto IsCheckedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) + { + if (!Input || Input->IsPendingKill()) + return ECheckBoxState::Unchecked; + + if (Input->GetLandscapeExportType() == LandscapeExportType) + return ECheckBoxState::Checked; + else + return ECheckBoxState::Unchecked; + }; + + auto CheckStateChangedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) + { + if (!Input || Input->IsPendingKill()) + return false; + + if (Input->GetLandscapeExportType() == LandscapeExportType) + return false; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputChange", "Houdini Input: Changed Landscape export type."), + Input->GetOuter()); + Input->Modify(); + + Input->SetLandscapeExportType(LandscapeExportType); + Input->SetHasLandscapeExportTypeChanged(true); + Input->MarkChanged(true); + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return true; + + for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) + { + if (!NextInputObj) + continue; + NextInputObj->MarkChanged(true); + } + + return true; + }; + + // Heightfield + FText HeightfieldTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a heightfield."); + ButtonOptionsPanel->AddSlot(0, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Start") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Heightfield); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for(auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Heightfield); + }) + .ToolTipText(HeightfieldTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("ClassIcon.LandscapeComponent")) + ] + + SHorizontalBox::Slot() + .FillWidth(1.f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsHeightfieldCheckbox", "Heightfield")) + .ToolTipText(HeightfieldTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + + // Mesh + FText MeshTooltip = LOCTEXT("LandscapeExportAsHeightfieldTooltip", "If enabled, the landscape will be exported to Houdini as a mesh."); + ButtonOptionsPanel->AddSlot(1, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Mesh); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for (auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Mesh); + }) + .ToolTipText(MeshTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("ClassIcon.StaticMeshComponent")) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsMeshCheckbox", "Mesh")) + .ToolTipText(MeshTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + + // Points + FText PointsTooltip = LOCTEXT("LandscapeExportAsPointsTooltip", "If enabled, the landscape will be exported to Houdini as points."); + ButtonOptionsPanel->AddSlot(2, 0) + [ + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.End") + .IsChecked_Lambda([IsCheckedExportAs, MainInput]() + { + return IsCheckedExportAs(MainInput, EHoudiniLandscapeExportType::Points); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedExportAs, InInputs](ECheckBoxState NewState) + { + for (auto CurrentInput : InInputs) + CheckStateChangedExportAs(CurrentInput, EHoudiniLandscapeExportType::Points); + }) + .ToolTipText(PointsTooltip) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(2, 2) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("Mobility.Static")) + ] + + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(2, 2) + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeExportAsPointsCheckbox", "Points")) + .ToolTipText(PointsTooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + ] + ]; + } + + // CheckBox : Export selected components only + { + TSharedPtr< SCheckBox > CheckBoxExportSelected; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportSelected, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeSelectedCheckbox", "Export Selected Landscape Components Only")) + .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, only the selected Landscape Components will be exported.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportSelectionOnly ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportSelectionOnly", "Houdini Input: Changing Landscape export only selected component."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportSelectionOnly) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportSelectionOnly = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + } + + // Checkbox: auto select components + { + TSharedPtr< SCheckBox > CheckBoxAutoSelectComponents; + VerticalBox->AddSlot().Padding(10, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxAutoSelectComponents, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AutoSelectComponentCheckbox", "Auto-select component in asset bounds")) + .ToolTipText(LOCTEXT("AutoSelectComponentCheckboxTooltip", "If enabled, when no Landscape components are curremtly selected, the one within the asset's bounding box will be exported.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeAutoSelectComponent ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeAutoSelectComponent", "Houdini Input: Changing Landscape input auto-selects components."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeAutoSelectComponent) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeAutoSelectComponent = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + // Enable only when exporting selection or when exporting heighfield (for now) + bool bEnable = false; + for (auto CurrentInput : InInputs) + { + if (!MainInput->bLandscapeExportSelectionOnly) + continue; + + bEnable = true; + break; + } + CheckBoxAutoSelectComponents->SetEnabled(bEnable); + } + + + // The following checkbox are only added when not in heightfield mode + if (MainInput->LandscapeExportType != EHoudiniLandscapeExportType::Heightfield) + { + // Checkbox : Export materials + { + TSharedPtr< SCheckBox > CheckBoxExportMaterials; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportMaterials, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeMaterialsCheckbox", "Export Landscape Materials")) + .ToolTipText(LOCTEXT("LandscapeMaterialsTooltip", "If enabled, the landscape materials will be exported with it.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportMaterials", "Houdini Input: Changing Landscape input export materials."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportMaterials) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportMaterials = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportMaterials->SetEnabled(false); + */ + } + + // Checkbox : Export Tile UVs + { + TSharedPtr< SCheckBox > CheckBoxExportTileUVs; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportTileUVs, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeTileUVsCheckbox", "Export Landscape Tile UVs")) + .ToolTipText(LOCTEXT("LandscapeTileUVsTooltip", "If enabled, UVs will be exported separately for each Landscape tile.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportTileUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportTileUVs", "Houdini Input: Changing Landscape export tile UVs."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportTileUVs) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportTileUVs = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportTileUVs->SetEnabled(false); + */ + } + + // Checkbox : Export normalized UVs + { + TSharedPtr< SCheckBox > CheckBoxExportNormalizedUVs; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportNormalizedUVs, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeNormalizedUVsCheckbox", "Export Landscape Normalized UVs")) + .ToolTipText(LOCTEXT("LandscapeNormalizedUVsTooltip", "If enabled, landscape UVs will be exported in [0, 1].")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportNormalizedUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Changing Landscape export normalized UVs."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportNormalizedUVs) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportNormalizedUVs = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportNormalizedUVs->SetEnabled(false); + */ + } + + // Checkbox : Export lighting + { + TSharedPtr< SCheckBox > CheckBoxExportLighting; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxExportLighting, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeLightingCheckbox", "Export Landscape Lighting")) + .ToolTipText(LOCTEXT("LandscapeLightingTooltip", "If enabled, lightmap information will be exported with the landscape.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainInput->bLandscapeExportLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportLighting", "Houdini Input: Changing Landscape export lighting."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + bool bNewState = (NewState == ECheckBoxState::Checked); + if (bNewState == CurrentInput->bLandscapeExportLighting) + continue; + + CurrentInput->Modify(); + + CurrentInput->bLandscapeExportLighting = bNewState; + CurrentInput->MarkChanged(true); + } + }) + ]; + + /* + // Disable when exporting heightfields + if (MainInput->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + CheckBoxExportLighting->SetEnabled(false); + */ + } + + } + + // Button : Recommit + { + auto OnButtonRecommitClicked = [InInputs]() + { + for (auto CurrentInput : InInputs) + { + TArray* LandscapeInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); + if (!LandscapeInputObjectsArray) + continue; + + for (UHoudiniInputObject* NextLandscapeInput : *LandscapeInputObjectsArray) + { + if (!NextLandscapeInput) + continue; + + NextLandscapeInput->MarkChanged(true); + } + + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1, 2, 4, 2) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("LandscapeInputRecommit", "Recommit Landscape")) + .ToolTipText(LOCTEXT("LandscapeInputRecommitTooltip", "Recommits the Landscape to Houdini.")) + .OnClicked_Lambda(OnButtonRecommitClicked) + ] + ]; + } + + + // Button : Clear Selection + { + auto IsClearButtonEnabled = [MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return false; + + if (MainInput->GetInputType() != EHoudiniInputType::Landscape) + return false; + + TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); + if (!MainInputObjectsArray) + return false; + + if (MainInputObjectsArray->Num() <= 0) + return false; + + return true; + }; + + auto OnButtonClearClicked = [InInputs]() + { + if (InInputs.Num() <= 0) + return FReply::Handled(); + + UHoudiniInput * MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + if (MainInput->GetInputType() != EHoudiniInputType::Landscape) + return FReply::Handled(); + + TArray* MainInputObjectsArray = MainInput->GetHoudiniInputObjectArray(MainInput->GetInputType()); + if (!MainInputObjectsArray) + return FReply::Handled(); + + if (MainInputObjectsArray->Num() <= 0) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeExportNormalizedUVs", "Houdini Input: Clearing landscape input."), + MainInput->GetOuter()); + + for (auto & CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + TArray* LandscapeInputObjectsArray = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (!LandscapeInputObjectsArray) + continue; + + if (LandscapeInputObjectsArray->Num() <= 0) + continue; + + CurInput->MarkChanged(true); + CurInput->Modify(); + + LandscapeInputObjectsArray->Empty(); + } + + return FReply::Handled(); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1, 2, 4, 2) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("ClearSelection", "Clear Selection")) + .ToolTipText(LOCTEXT("ClearSelectionTooltip", "Clear input selection")) + .IsEnabled_Lambda(IsClearButtonEnabled) + .OnClicked_Lambda(OnButtonClearClicked) + ] + ]; + } +} + +/* +FMenuBuilder +FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArray& InInputs, const TAttribute& HeadingText, const bool& bShowCurrentSelectionSection) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + + // Filters are only based on the MainInput + auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + if (!Actor->IsA()) + return false; + + ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); + if (!LandscapeProxy) + return false; + + // Get the landscape's actor + AActor* OwnerActor = LandscapeProxy->GetOwner(); + + // Get our Actor + UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); + AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; + + // TODO: FIX ME! + // IF the landscape is owned by ourself, skip it! + if (OwnerActor == MyOwner) + return false; + + return true; + }; + + auto OnShouldFilterWorld = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + const TArray* InputObjects = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurInputObject : *InputObjects) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + AActor* CurActor = Cast(CurInputObject->GetObject()); + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + auto OnShouldFilterHoudiniAsset = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + // But not our own Asset Actor + if (const USceneComponent* RootComp = Cast(InInput->GetOuter())) + { + if (RootComp && Cast(RootComp->GetOwner()) != Actor) + return true; + } + + return false; + }; + + auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape, OnShouldFilterWorld, OnShouldFilterHoudiniAsset](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + switch (MainInput->GetInputType()) + { + case EHoudiniInputType::Landscape: + return OnShouldFilterLandscape(Actor, MainInput); + case EHoudiniInputType::World: + return OnShouldFilterWorld(Actor, MainInput); + case EHoudiniInputType::Asset: + return OnShouldFilterHoudiniAsset(Actor, MainInput); + default: + return true; + } + + return false; + }; + + + // Selection uses the input arrays + auto OnLandscapeSelected = [](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + ALandscapeProxy* LandscapeProxy = Cast(Actor); + if (!LandscapeProxy) + return; + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return; + + LandscapeInputObjectsArray->Empty(); + + FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); + + // Create a Houdini Input Object. + UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( + LandscapeProxy, Input, LandscapeName.ToString()); + + UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); + LandscapeInput->MarkChanged(true); + + LandscapeInputObjectsArray->Add(LandscapeInput); + Input->MarkChanged(true); + }; + + auto OnHoudiniAssetActorSelected = [](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); + if (!HoudiniAssetActor) + return; + + TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!AssetInputObjectsArray) + return; + + AssetInputObjectsArray->Empty(); + + FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); + + // Create a Houdini Asset Input Object + UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); + + UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); + AssetInput->MarkChanged(true); + + AssetInputObjectsArray->Add(AssetInput); + Input->MarkChanged(true); + }; + + auto OnWorldSelected = [](AActor* Actor, UHoudiniInput* Input) + { + // Do Nothing + }; + + auto OnActorSelected = [OnLandscapeSelected, OnWorldSelected, OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) + { + for (auto& CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + return; + + switch (CurInput->GetInputType()) + { + case EHoudiniInputType::Landscape: + return OnLandscapeSelected(Actor, CurInput); + case EHoudiniInputType::World: + return OnWorldSelected(Actor, CurInput); + case EHoudiniInputType::Asset: + return OnHoudiniAssetActorSelected(Actor, CurInput); + default: + return; + } + } + + return; + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + if (bShowCurrentSelectionSection) + { + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + } + + + MenuBuilder.BeginSection(NAME_None, HeadingText); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} +*/ + + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterHoudiniAsset = [InInputs](const AActor* const Actor) + { + if (!Actor) + return false; + + // Only return HoudiniAssetActors, but not our HAA + if (!Actor->IsA()) + return false; + + // But not our selected Asset Actor + for (auto & NextSelectedInput : InInputs) + { + if (!NextSelectedInput) + continue; + + const USceneComponent* RootComp = Cast(NextSelectedInput->GetOuter()); + if (RootComp && Cast(RootComp->GetOwner()) == Actor) + return false; + + } + + return true; + }; + + // Filters are only based on the MainInput + auto OnShouldFilterActor = [MainInput, OnShouldFilterHoudiniAsset](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + return OnShouldFilterHoudiniAsset(Actor); + }; + + auto OnHoudiniAssetActorSelected = [OnShouldFilterHoudiniAsset](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || Actor->IsPendingKill() || !Input || Input->IsPendingKill()) + return; + + AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); + if (!HoudiniAssetActor) + return; + + // Make sure that the actor is valid for this input + if (!OnShouldFilterHoudiniAsset(Actor)) + return; + + TArray* AssetInputObjectsArray = Input->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); + if (!AssetInputObjectsArray) + return; + + FName HoudiniAssetActorName = MakeUniqueObjectName(Input->GetOuter(), AHoudiniAssetActor::StaticClass(), TEXT("HoudiniAsset")); + + // Create a Houdini Asset Input Object + UHoudiniInputObject* NewInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniAssetActor->GetHoudiniAssetComponent(), Input, HoudiniAssetActorName.ToString()); + + UHoudiniInputHoudiniAsset* AssetInput = Cast(NewInputObject); + AssetInput->MarkChanged(true); + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniAssetInputChange", "Houdini Input: Selecting an asset input"), + Input->GetOuter()); + + Input->Modify(); + + AssetInputObjectsArray->Empty(); + AssetInputObjectsArray->Add(AssetInput); + Input->MarkChanged(true); + }; + + auto OnActorSelected = [OnHoudiniAssetActorSelected](AActor* Actor, TArray InInputs) + { + for (auto& CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + return; + + OnHoudiniAssetActorSelected(Actor, CurInput); + } + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + // Show current selection + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("AssetInputSelectableActors", "Houdini Assets")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + if (!Actor->IsA()) + return false; + + ALandscapeProxy* LandscapeProxy = const_cast(Cast(Actor)); + if (!LandscapeProxy) + return false; + + // Get the landscape's parent actor + // We need to get the AttachParent on the root componet, GteOwner will not return the parent actor! + AActor* OwnerActor = nullptr; + USceneComponent* RootComponent = LandscapeProxy->GetRootComponent(); + if (RootComponent && !RootComponent->IsPendingKill()) + OwnerActor = RootComponent->GetAttachParent() ? RootComponent->GetAttachParent()->GetOwner() : LandscapeProxy->GetOwner(); + + // Get our Actor + UHoudiniAssetComponent* MyHAC = Cast(InInput->GetOuter()); + AActor* MyOwner = MyHAC ? MyHAC->GetOwner() : nullptr; + + // IF the landscape is owned by ourself, skip it! + if (OwnerActor && OwnerActor == MyOwner) + { + // ... buuuut we dont want to filter input landscapes that have the "Update Input Landscape Data" option enabled + // (and are, therefore, outputs as well) + for (int32 Idx = 0; Idx < MyHAC->GetNumInputs(); Idx++) + { + UHoudiniInput* CurrentInput = MyHAC->GetInputAt(Idx); + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) + continue; + + if (!CurrentInput->GetUpdateInputLandscape()) + continue; + + // Don't filter our input landscapes + ALandscapeProxy* UpdatedInputLandscape = Cast(CurrentInput->GetInputObjectAt(0)); + if (LandscapeProxy == UpdatedInputLandscape) + return true; + } + + return false; + } + + return true; + }; + + // Filters are only based on the MainInput + auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + return OnShouldFilterLandscape(Actor, MainInput); + }; + + // Selection uses the input arrays + auto OnLandscapeSelected = [OnShouldFilterLandscape](AActor* Actor, UHoudiniInput* Input) + { + if (!Actor || !Input) + return; + + // Make sure that the actor is valid for this input + if (!OnShouldFilterLandscape(Actor, Input)) + return; + + ALandscapeProxy* LandscapeProxy = Cast(Actor); + if (!LandscapeProxy) + return; + + TArray* LandscapeInputObjectsArray = Input->GetHoudiniInputObjectArray(Input->GetInputType()); + if (!LandscapeInputObjectsArray) + return; + + LandscapeInputObjectsArray->Empty(); + + FName LandscapeName = MakeUniqueObjectName(Input->GetOuter(), ALandscapeProxy::StaticClass(), TEXT("Landscape")); + + // Create a Houdini Input Object. + UHoudiniInputObject* NewInputObject = UHoudiniInputLandscape::Create( + LandscapeProxy, Input, LandscapeName.ToString()); + + UHoudiniInputLandscape* LandscapeInput = Cast(NewInputObject); + LandscapeInput->MarkChanged(true); + + LandscapeInputObjectsArray->Add(LandscapeInput); + Input->MarkChanged(true); + }; + + auto OnActorSelected = [OnLandscapeSelected](AActor* Actor, TArray InInputs) + { + if (InInputs.Num() <= 0) + return; + + UHoudiniInput * MainInput = InInputs[0]; + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniLandscapeInputChangeSelections", "Houdini Input: Selecting input landscape."), + MainInput->GetOuter()); + + for (auto CurInput : InInputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + CurInput->Modify(); + OnLandscapeSelected(Actor, CurInput); + } + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterActor); + + // Show current selection + MenuBuilder.BeginSection(NAME_None, LOCTEXT("CurrentActorOperationHeader", "Current Selection")); + { + MenuBuilder.AddMenuEntry( + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + TAttribute::Create(TAttribute::FGetter::CreateUObject(MainInput, &UHoudiniInput::GetCurrentSelectionText)), + FSlateIcon(), + FUIAction(), + NAME_None, + EUserInterfaceActionType::Button, + NAME_None); + } + MenuBuilder.EndSection(); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("LandscapeInputSelectableActors", "Landscapes")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnActorSelected, InInputs)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilterWorld = [MainInput](const AActor* const Actor) + { + if (!MainInput || MainInput->IsPendingKill()) + return true; + + const TArray* InputObjects = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); + if (!InputObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurInputObject : *InputObjects) + { + if (!CurInputObject || CurInputObject->IsPendingKill()) + continue; + + AActor* CurActor = Cast(CurInputObject->GetObject()); + if (!CurActor || CurActor->IsPendingKill()) + { + // See if the input object is a HAC, if it is, get its parent actor + UHoudiniAssetComponent* CurHAC = Cast(CurInputObject->GetObject()); + if (CurHAC && !CurHAC->IsPendingKill()) + CurActor = CurHAC->GetOwner(); + } + + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + auto OnWorldSelected = [](AActor* Actor) + { + // Do Nothing + }; + + FMenuBuilder MenuBuilder(true, nullptr); + FOnShouldFilterActor ActorFilter = FOnShouldFilterActor::CreateLambda(OnShouldFilterWorld); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputSelectedActors", "Currently Selected Actors")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(ActorFilter); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnWorldSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +FMenuBuilder +FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArray& InInputs) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + auto OnShouldFilter = [MainInput](const AActor* const Actor) + { + if (!Actor || Actor->IsPendingKill()) + return false; + + const TArray* BoundObjects = MainInput->GetBoundSelectorObjectArray(); + if (!BoundObjects) + return false; + + // Only return actors that are currently selected by our input + for (const auto& CurActor : *BoundObjects) + { + if (!CurActor || CurActor->IsPendingKill()) + continue; + + if (CurActor == Actor) + return true; + } + + return false; + }; + + + auto OnSelected = [](AActor* Actor) + { + // Do Nothing + }; + + FMenuBuilder MenuBuilder(true, nullptr); + MenuBuilder.BeginSection(NAME_None, LOCTEXT("WorldInputBoundSelectors", "Bound Selectors")); + { + FSceneOutlinerModule & SceneOutlinerModule = + FModuleManager::Get().LoadModuleChecked< FSceneOutlinerModule >(TEXT("SceneOutliner")); + SceneOutliner::FInitializationOptions InitOptions; + { + InitOptions.Mode = ESceneOutlinerMode::ActorPicker; + InitOptions.Filters->AddFilterPredicate(FOnShouldFilterActor::CreateLambda(OnShouldFilter)); + InitOptions.bFocusSearchBoxWhenOpened = true; + InitOptions.bShowCreateNewFolder = false; + + // Add the gutter so we can change the selection's visibility + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Gutter(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 0)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::Label(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 10)); + InitOptions.ColumnMap.Add(SceneOutliner::FBuiltInColumnTypes::ActorInfo(), SceneOutliner::FColumnInfo(SceneOutliner::EColumnVisibility::Visible, 20)); + } + + static const FVector2D SceneOutlinerWindowSize(350.0f, 200.0f); + TSharedRef< SWidget > MenuWidget = + SNew(SBox) + .WidthOverride(SceneOutlinerWindowSize.X) + .HeightOverride(SceneOutlinerWindowSize.Y) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + [ + SceneOutlinerModule.CreateSceneOutliner( + InitOptions, + FOnActorPicked::CreateLambda(OnSelected)) + ] + ]; + + MenuBuilder.AddWidget(MenuWidget, FText::GetEmpty(), true); + } + MenuBuilder.EndSection(); + + return MenuBuilder; +} + +void +FHoudiniInputDetails::AddWorldInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + const IDetailsView* DetailsView) +{ + if (InInputs.Num() <= 0) + return; + + UHoudiniInput* MainInput = InInputs[0]; + if (!MainInput) + return; + + const int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); + + // Get the details view name and locked status + bool bDetailsLocked = false; + FName DetailsPanelName = "LevelEditorSelectionDetails"; + if (DetailsView) + { + DetailsPanelName = DetailsView->GetIdentifier(); + if (DetailsView->IsLocked()) + bDetailsLocked = true; + } + + // Check of we're in bound selector mode + bool bIsBoundSelector = MainInput->IsWorldInputBoundSelector(); + + // Button : Start Selection / Use current selection + refresh + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + //auto ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); + //auto ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); + FText ButtonLabel; + FText ButtonTooltip; + if (!bIsBoundSelector) + { + // Button : Start Selection / Use current selection + if (bDetailsLocked) + { + ButtonLabel = LOCTEXT("WorldInputUseCurrentSelection", "Use Current Selection (Unlocks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputUseCurrentSelectionTooltip", "Fill the asset's input with the current selection and unlocks the Details Panel."); + } + else + { + ButtonLabel = LOCTEXT("WorldInputStartSelection", "Start Selection (Locks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputStartSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that you can commit to the input afterwards."); + } + /* + FOnClicked OnSelectActors = FOnClicked::CreateStatic( + &FHoudiniInputDetails::Helper_OnButtonClickSelectActors, InInputs, DetailsPanelName); + */ + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ButtonLabel) + .ToolTipText(ButtonTooltip) + //.OnClicked(OnSelectActors) + .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() + { + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName); + }) + + ] + ]; + } + else + { + // Button : Start Selection / Use current selection as Bound selector + if (bDetailsLocked) + { + ButtonLabel = LOCTEXT("WorldInputUseSelectionAsBoundSelector", "Use Selection as Bound Selector (Unlocks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputUseSelectionAsBoundSelectorTooltip", "Fill the asset's input with all the actors contains in the current's selection bound, and unlocks the Details Panel."); + } + else + { + ButtonLabel = LOCTEXT("WorldInputStartBoundSelection", "Start Bound Selection (Locks Details Panel)"); + ButtonTooltip = LOCTEXT("WorldInputStartBoundSelectionTooltip", "Locks the Details Panel, and allow you to select object in the world that will be used as bounds."); + } + + /* + FOnClicked OnSelectBounds = FOnClicked::CreateStatic( + &FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector, InInputs, DetailsPanelName); + */ + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ButtonLabel) + .ToolTipText(ButtonTooltip) + //.OnClicked(OnSelectBounds) + .OnClicked_Lambda([InInputs, DetailsPanelName, &CategoryBuilder]() + { + return Helper_OnButtonClickUseSelectionAsBoundSelector(CategoryBuilder, InInputs, DetailsPanelName); + }) + ] + ]; + } + } + + // Button : Select All + Clear Selection + { + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FOnClicked OnSelectAll = FOnClicked::CreateLambda([InInputs, MainInput]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputSelectedAll", "Houdini Input: Selecting all actor in the current world"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // Get the parent component/actor/world of the current input + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + UWorld* MyWorld = CurrentInput->GetWorld(); + + TArray NewSelectedActors; + for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) + { + AActor *CurrentActor = *ActorItr; + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + NewSelectedActors.Add(CurrentActor); + } + + CurrentInput->Modify(); + + bool bHasChanged = CurrentInput->UpdateWorldSelection(NewSelectedActors); + } + + return FReply::Handled(); + }); + + FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput, &CategoryBuilder]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + const bool bMainInputBoundSelection = MainInput->IsWorldInputBoundSelector(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputClear", "Houdini Input: Clearing world input selection"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + // Do nothing if the current input has different selector settings from the main input + if (CurrentInput->IsWorldInputBoundSelector() != bMainInputBoundSelection) + continue; + + CurrentInput->Modify(); + + if (CurrentInput->IsWorldInputBoundSelector()) + { + CurrentInput->SetBoundSelectorObjectsNumber(0); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + else + { + TArray EmptySelection; + bool bHasChanged = CurrentInput->UpdateWorldSelection(EmptySelection); + } + } + + return FReply::Handled(); + }); + + FText ClearSelectionLabel; + FText ClearSelectionTooltip; + if (bIsBoundSelector) + { + ClearSelectionLabel = LOCTEXT("ClearBoundSelection", "Clear Bound Selection"); + ClearSelectionTooltip = LOCTEXT("ClearBoundSelectionTooltip", "Clear the input's current bound selection."); + } + else + { + ClearSelectionLabel = LOCTEXT("ClearSelection", "Clear Selection"); + ClearSelectionTooltip = LOCTEXT("ClearSelectionTooltip", "Clear the input's current selection."); + } + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(HorizontalBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + // Button : SelectAll + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("WorldInputSelectAll", "Select All")) + .ToolTipText(LOCTEXT("WorldInputSelectAll", "Fill the asset's input with all actors.")) + .OnClicked(OnSelectAll) + .IsEnabled(!bIsBoundSelector) + ] + + SHorizontalBox::Slot() + [ + // Button : Clear Selection + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ClearSelectionLabel) + .ToolTipText(ClearSelectionTooltip) + .OnClicked(OnClearSelect) + ] + ]; + + // Do not enable select all/clear select when selection has been started and details are locked + HorizontalBox->SetEnabled(!bDetailsLocked); + } + + // Checkbox: Bound Selector + { + // Lambda returning a CheckState from the input's current bound selector state + auto IsCheckedBoundSelector = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->IsWorldInputBoundSelector() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing bound selector state + auto CheckStateChangedIsBoundSelector = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeBoungSelector", "Houdini Input: Changing world input to bound selector"), + MainInput->GetOuter()); + + bool bNewState = (NewState == ECheckBoxState::Checked); + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->IsWorldInputBoundSelector() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetWorldInputBoundSelector(bNewState); + } + + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + }; + + // Checkbox : Is Bound Selector + TSharedPtr< SCheckBox > CheckBoxBoundSelector; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxBoundSelector, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("BoundSelector", "Bound Selector")) + .ToolTipText(LOCTEXT("BoundSelectorTip", "When enabled, this world input works as a bound selector, sending all the objects contained in the bound selector bounding boxes.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedBoundSelector, MainInput]() + { + return IsCheckedBoundSelector(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedIsBoundSelector, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedIsBoundSelector(InInputs, NewState); + }) + ]; + } + + // Checkbox: Bound Selector Auto update + { + // Lambda returning a CheckState from the input's current auto update state + auto IsCheckedAutoUpdate = [](UHoudiniInput* InInput) + { + if (!InInput || InInput->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return InInput->GetWorldInputBoundSelectorAutoUpdates() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing the auto update state + auto CheckStateChangedBoundAutoUpdates = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeAutoUpdate", "Houdini Input: Changing bound selector auto-update state."), + MainInput->GetOuter()); + + bool bNewState = (NewState == ECheckBoxState::Checked); + for (auto CurInput : InInputsToUpdate) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + if (CurInput->GetWorldInputBoundSelectorAutoUpdates() == bNewState) + continue; + + CurInput->Modify(); + + CurInput->SetWorldInputBoundSelectorAutoUpdates(bNewState); + CurInput->MarkChanged(true); + } + }; + + // Checkbox : Is Bound Selector + TSharedPtr< SCheckBox > CheckBoxBoundAutoUpdate; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxBoundAutoUpdate, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("BoundAutoUpdate", "Update bound selection automatically")) + .ToolTipText(LOCTEXT("BoundAutoUpdateTip", "If enabled and if this world input is set as a bound selector, the objects selected by the bounds will update automatically.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([IsCheckedAutoUpdate, MainInput]() + { + return IsCheckedAutoUpdate(MainInput); + }) + .OnCheckStateChanged_Lambda([CheckStateChangedBoundAutoUpdates, InInputs](ECheckBoxState NewState) + { + return CheckStateChangedBoundAutoUpdates(InInputs, NewState); + }) + ]; + + CheckBoxBoundAutoUpdate->SetEnabled(MainInput->IsWorldInputBoundSelector()); + } + + // ActorPicker : Bound Selector + if(bIsBoundSelector) + { + FMenuBuilder MenuBuilder = Helper_CreateBoundSelectorPickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + // ActorPicker : World Outliner + { + FMenuBuilder MenuBuilder = Helper_CreateWorldActorPickerWidget(InInputs); + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + MenuBuilder.MakeWidget() + ]; + } + + { + // Spline Resolution + TSharedPtr> NumericEntryBox; + int32 Idx = 0; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(LOCTEXT("SplineRes", "Unreal Spline Resolution")) + .ToolTipText(LOCTEXT("SplineResTooltip", "Resolution used when marshalling the Unreal Splines to HoudiniEngine.\n(step in cm between control points)\nSet this to 0 to only export the control points.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .MinValue(-1.0f) + .MaxValue(1000.0f) + .MinSliderValue(0.0f) + .MaxSliderValue(1000.0f) + .Value(MainInput->GetUnrealSplineResolution()) + .OnValueChanged_Lambda([MainInput, InInputs](float Val) + { + if (!MainInput || MainInput->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputChangeSplineResolution", "Houdini Input: Changing world input spline resolution"), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetUnrealSplineResolution() == Val) + continue; + + CurrentInput->Modify(); + + CurrentInput->SetUnrealSplineResolution(Val); + CurrentInput->MarkChanged(true); + } + }) + /* + .Value(TAttribute< TOptional< float > >::Create(TAttribute< TOptional< float > >::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::GetSplineResolutionValue))) + .OnValueChanged(SNumericEntryBox< float >::FOnValueChanged::CreateUObject( + &InParam, &UHoudiniAssetInput::SetSplineResolutionValue)) + .IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateUObject( + &InParam, &UHoudiniAssetInput::IsSplineResolutionEnabled))) + */ + .SliderExponent(1.0f) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("SplineResToDefault", "Reset to default value.")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + // TODO: FINISH ME! + //.OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) + .OnClicked_Lambda([MainInput, InInputs]() + { + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniWorldInputRevertSplineResolution", "Houdini Input: Reverting world input spline resolution to default"), + MainInput->GetOuter()); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + float DefaultSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; + + for (auto CurrentInput : InInputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetUnrealSplineResolution() == DefaultSplineResolution) + continue; + + CurrentInput->Modify(); + + CurrentInput->SetUnrealSplineResolution(DefaultSplineResolution); + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + } +} + +void +FHoudiniInputDetails::AddSkeletalInputUI( + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool ) +{ +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) +{ + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, false); +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName) +{ + return Helper_OnButtonClickSelectActors(CategoryBuilder, InInputs, DetailsPanelName, true); +} + +FReply +FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName, const bool& bUseWorldInAsWorldSelector) +{ + UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; + if (!MainInput || MainInput->IsPendingKill()) + return FReply::Handled(); + + // There's no undo operation for button. + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Locate the details panel. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + return FReply::Handled(); + + class SLocalDetailsView : public SDetailsViewBase + { + public: + void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } + void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } + }; + auto * LocalDetailsView = static_cast(DetailsView.Get()); + + if (!DetailsView->IsLocked()) + { + // + // START SELECTION + // Locks the details view and select our currently selected actors + // + LocalDetailsView->LockDetailsView(); + check(DetailsView->IsLocked()); + + // Force refresh of details view. + TArray InputOuters; + for (auto CurIn : InInputs) + InputOuters.Add(CurIn->GetOuter()); + if (CategoryBuilder.IsParentLayoutValid()) + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + //ReselectSelectedActors(); + + if (bUseWorldInAsWorldSelector) + { + // Bound Selection + // Select back the previously chosen bound selectors + GEditor->SelectNone(false, true); + int32 NumBoundSelectors = MainInput->GetNumberOfBoundSelectorObjects(); + for (int32 Idx = 0; Idx < NumBoundSelectors; Idx++) + { + AActor* Actor = MainInput->GetBoundSelectorObjectAt(Idx); + if (!Actor || Actor->IsPendingKill()) + continue; + + GEditor->SelectActor(Actor, true, true); + } + } + else + { + // Regular selection + // Select the already chosen input Actors from the World Outliner. + GEditor->SelectNone(false, true); + int32 NumInputObjects = MainInput->GetNumberOfInputObjects(EHoudiniInputType::World); + for (int32 Idx = 0; Idx < NumInputObjects; Idx++) + { + UHoudiniInputObject* CurInputObject = MainInput->GetHoudiniInputObjectAt(Idx); + if (!CurInputObject) + continue; + + AActor* Actor = nullptr; + UHoudiniInputActor* InputActor = Cast(CurInputObject); + if (InputActor && !InputActor->IsPendingKill()) + { + // Get the input actor + Actor = InputActor->GetActor(); + } + else + { + // See if the input object is a HAC + UHoudiniInputHoudiniAsset* InputHAC = Cast(CurInputObject); + if (InputHAC && !InputHAC->IsPendingKill()) + { + Actor = InputHAC->GetHoudiniAssetComponent() ? InputHAC->GetHoudiniAssetComponent()->GetOwner() : nullptr; + } + } + + if (!Actor || Actor->IsPendingKill()) + continue; + + GEditor->SelectActor(Actor, true, true); + } + } + + return FReply::Handled(); + } + else + { + // + // UPDATE SELECTION + // Unlocks the input's selection and select the HDA back. + // + + if (!GEditor || !GEditor->GetSelectedObjects()) + return FReply::Handled(); + + USelection * SelectedActors = GEditor->GetSelectedActors(); + if (!SelectedActors) + return FReply::Handled(); + + // Create a transaction + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniWorldInputSelectionChanged", "Changing Houdini world outliner input objects"), + MainInput->GetOuter()); + + + TArray AllActors; + for (auto CurrentInput : InInputs) + { + CurrentInput->Modify(); + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + AllActors.Add(ParentActor); + + bool bHasChanged = true; + if (bUseWorldInAsWorldSelector) + { + // + // Update bound selectors + + // Clean up the selected actors + TArray ValidBoundSelectedActors; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor* CurrentBoundActor = Cast(*It); + if (!CurrentBoundActor) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentBoundActor == ParentActor)) + continue; + + ValidBoundSelectedActors.Add(CurrentBoundActor); + } + + // See if the bound selector have changed + int32 PreviousBoundSelectorCount = CurrentInput->GetNumberOfBoundSelectorObjects(); + if (PreviousBoundSelectorCount == ValidBoundSelectedActors.Num()) + { + // Same number of BoundSelectors, see if they have changed + bHasChanged = false; + for (int32 BoundIdx = 0; BoundIdx < PreviousBoundSelectorCount; BoundIdx++) + { + AActor* PreviousBound = CurrentInput->GetBoundSelectorObjectAt(BoundIdx); + if (!PreviousBound) + continue; + + if (!ValidBoundSelectedActors.Contains(PreviousBound)) + { + bHasChanged = true; + break; + } + } + } + + if (bHasChanged) + { + // Only update the bound selector objects on the input if they have changed + CurrentInput->SetBoundSelectorObjectsNumber(ValidBoundSelectedActors.Num()); + int32 InputObjectIdx = 0; + for (auto CurActor : ValidBoundSelectedActors) + { + CurrentInput->SetBoundSelectorObjectAt(InputObjectIdx++, CurActor); + } + + // Update the current selection from the BoundSelectors + CurrentInput->UpdateWorldSelectionFromBoundSelectors(); + } + } + else + { + // + // Update our selection directly with the currently selected actors + // + + TArray ValidSelectedActors; + for (FSelectionIterator It(*SelectedActors); It; ++It) + { + AActor* CurrentActor = Cast(*It); + if (!CurrentActor) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + ValidSelectedActors.Add(CurrentActor); + } + + // Update the input objects from the valid selected actors array + // Only new/remove input objects will be marked as changed + bHasChanged = CurrentInput->UpdateWorldSelection(ValidSelectedActors); + } + + // If we didnt change the selection, cancel the transaction + if (!bHasChanged) + Transaction.Cancel(); + } + + // We can now unlock the details view... + LocalDetailsView->UnlockDetailsView(); + check(!DetailsView->IsLocked()); + + // .. reset the selected actors, force refresh and override the lock. + DetailsView->SetObjects(AllActors, true, true); + + // We now need to reselect all our Asset Actors. + // If we don't do this, our Asset parameters will stop refreshing and the user will be very confused. + // It is also resetting the state of the selection before the input actor selection process was started. + GEditor->SelectNone(false, true); + for (auto CurrentActor : AllActors) + { + AActor* ParentActor = Cast(CurrentActor); + if (!ParentActor) + continue; + + GEditor->SelectActor(ParentActor, true, true); + } + + // Update the input details layout. + // if (CategoryBuilder.IsParentLayoutValid()) + // CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); +} + + +bool +FHoudiniInputDetails::Helper_CancelWorldSelection(TArray& InInputs, const FName& DetailsPanelName) +{ + if (InInputs.Num() <= 0) + return false; + + // Get the property module to access the details view + FPropertyEditorModule & PropertyModule = + FModuleManager::Get().GetModuleChecked< FPropertyEditorModule >("PropertyEditor"); + + // Locate the details panel. + TSharedPtr DetailsView = PropertyModule.FindDetailView(DetailsPanelName); + if (!DetailsView.IsValid()) + return false; + + if (!DetailsView->IsLocked()) + return false; + + class SLocalDetailsView : public SDetailsViewBase + { + public: + void LockDetailsView() { SDetailsViewBase::bIsLocked = true; } + void UnlockDetailsView() { SDetailsViewBase::bIsLocked = false; } + }; + auto * LocalDetailsView = static_cast(DetailsView.Get()); + + // Get all our parent components / actors + TArray AllComponents; + TArray AllActors; + for (auto CurrentInput : InInputs) + { + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(CurrentInput->GetOuter()); + if (!ParentComponent) + continue; + + AllComponents.Add(ParentComponent); + + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + if (!ParentActor) + continue; + + AllActors.Add(ParentActor); + } + + // Unlock the detail view and re-select our parent actors + { + LocalDetailsView->UnlockDetailsView(); + check(!DetailsView->IsLocked()); + + // Reset selected actor to itself, force refresh and override the lock. + DetailsView->SetObjects(AllActors, true, true); + } + + // Reselect the Asset Actor. If we don't do this, our Asset parameters will stop + // refreshing and the user will be very confused. It is also resetting the state + // of the selection before the input actor selection process was started. + GEditor->SelectNone(false, true); + for (auto ParentActorObj : AllActors) + { + AActor* ParentActor = Cast(ParentActorObj); + if (!ParentActor) + continue; + + GEditor->SelectActor(ParentActor, true, true); + } + + return true; +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h index 3153a1417..b6be86b23 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.h @@ -1,168 +1,168 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Widgets/SBoxPanel.h" -#include "IDetailsView.h" - -class UHoudiniInput; -class UHoudiniSplineComponent; - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class FMenuBuilder; -class SVerticalBox; -class IDetailsView; -class FReply; -class FAssetThumbnailPool; - -class FHoudiniInputDetails : public TSharedFromThis -{ - public: - static void CreateWidget( - IDetailCategoryBuilder& HouInputCategoryBuilder, - TArray InInputs, FDetailWidgetRow* InputRow = nullptr); - - static void CreateNameWidget( - UHoudiniInput* InParam, - FDetailWidgetRow & Row, - bool bLabel, - int32 InInputCount); - - static FText GetInputTooltip( UHoudiniInput* InInput ); - - // ComboBox : Input Type - static void AddInputTypeComboBox( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - const IDetailsView* InDetailsView); - - // Checkbox : Keep World Transform - static void AddKeepWorldTransformCheckBox( - TSharedRef InVerticalBox, - TArray& InInputs); - - static void AddCurveInputCookOnChangeCheckBox( - TSharedRef< SVerticalBox > VerticalBox, - TArray& InInputs); - - // Checkbox : Pack before merging - static void AddPackBeforeMergeCheckbox( - TSharedRef InVerticalBox, - TArray& InInputs); - - static void AddImportAsReferenceCheckbox( - TSharedRef< SVerticalBox > VerticalBox, - TArray& InInputs); - - // Checkboxes : Export LODs / Sockets / Collisions - static void AddExportCheckboxes( - TSharedRef InVerticalBox, - TArray& InInputs); - - // Add Geometry Inputs UI Widgets - static void AddGeometryInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef InVerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - - // Create a single geometry widget for the given input object - static void Helper_CreateGeometryWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const FPlatformTypes::int32& InGeometryObjectIdx, - TSharedPtr AssetThumbnailPool, TSharedRef VerticalBox); - - static void Helper_CreateCurveWidget( - IDetailCategoryBuilder& CategoryBuilder, - TArray& InInputs, - const int32& InCurveObjectIdx, - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, - TSharedRef< SVerticalBox > VerticalBox, - TSharedPtr HouSplineComponentVisualizer); - - // Add Asset Inputs UI Widgets - static void AddAssetInputUI( - TSharedRef VerticalBox, - TArray& InInputs); - - // Add Curve Inputs UI Widgets - static void AddCurveInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - - // Add Landscape Inputs UI Widgets - static void AddLandscapeInputUI( - TSharedRef VerticalBox, - TArray& InInputs); - - // Add World Inputs UI Widgets - static void AddWorldInputUI( - IDetailCategoryBuilder& CategoryBuilder, - TSharedRef VerticalBox, - TArray& InInputs, - const IDetailsView* InDetailsView); - - // Add Skeletal Inputs UI Widgets - static void AddSkeletalInputUI( - TSharedRef VerticalBox, - TArray& InInputs, - TSharedPtr AssetThumbnailPool); - /* - static FMenuBuilder Helper_CreateCustomActorPickerWidget( - UHoudiniInput* InParam, - const TAttribute& HeadingText, - const bool& bShowCurrentSelectionSection) - */ - - static FMenuBuilder Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateLandscapePickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateWorldActorPickerWidget(TArray& InInputs); - - static FMenuBuilder Helper_CreateBoundSelectorPickerWidget(TArray& InInputs); - - static FReply Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); - - static FReply Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); - - static FReply Helper_OnButtonClickSelectActors( - IDetailCategoryBuilder& CategoryBuilder, - TArray InInputs, - const FName& InDetailsPanelName, - const bool& bUseWorldInAsWorldSelector); - - static bool Helper_CancelWorldSelection( - TArray& InInputs, const FName& DetailsPanelName); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Widgets/SBoxPanel.h" +#include "IDetailsView.h" + +class UHoudiniInput; +class UHoudiniSplineComponent; + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class FMenuBuilder; +class SVerticalBox; +class IDetailsView; +class FReply; +class FAssetThumbnailPool; + +class FHoudiniInputDetails : public TSharedFromThis +{ + public: + static void CreateWidget( + IDetailCategoryBuilder& HouInputCategoryBuilder, + TArray InInputs, FDetailWidgetRow* InputRow = nullptr); + + static void CreateNameWidget( + UHoudiniInput* InParam, + FDetailWidgetRow & Row, + bool bLabel, + int32 InInputCount); + + static FText GetInputTooltip( UHoudiniInput* InInput ); + + // ComboBox : Input Type + static void AddInputTypeComboBox( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + const IDetailsView* InDetailsView); + + // Checkbox : Keep World Transform + static void AddKeepWorldTransformCheckBox( + TSharedRef InVerticalBox, + TArray& InInputs); + + static void AddCurveInputCookOnChangeCheckBox( + TSharedRef< SVerticalBox > VerticalBox, + TArray& InInputs); + + // Checkbox : Pack before merging + static void AddPackBeforeMergeCheckbox( + TSharedRef InVerticalBox, + TArray& InInputs); + + static void AddImportAsReferenceCheckbox( + TSharedRef< SVerticalBox > VerticalBox, + TArray& InInputs); + + // Checkboxes : Export LODs / Sockets / Collisions + static void AddExportCheckboxes( + TSharedRef InVerticalBox, + TArray& InInputs); + + // Add Geometry Inputs UI Widgets + static void AddGeometryInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef InVerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + + // Create a single geometry widget for the given input object + static void Helper_CreateGeometryWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const FPlatformTypes::int32& InGeometryObjectIdx, + TSharedPtr AssetThumbnailPool, TSharedRef VerticalBox); + + static void Helper_CreateCurveWidget( + IDetailCategoryBuilder& CategoryBuilder, + TArray& InInputs, + const int32& InCurveObjectIdx, + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool, + TSharedRef< SVerticalBox > VerticalBox, + TSharedPtr HouSplineComponentVisualizer); + + // Add Asset Inputs UI Widgets + static void AddAssetInputUI( + TSharedRef VerticalBox, + TArray& InInputs); + + // Add Curve Inputs UI Widgets + static void AddCurveInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + + // Add Landscape Inputs UI Widgets + static void AddLandscapeInputUI( + TSharedRef VerticalBox, + TArray& InInputs); + + // Add World Inputs UI Widgets + static void AddWorldInputUI( + IDetailCategoryBuilder& CategoryBuilder, + TSharedRef VerticalBox, + TArray& InInputs, + const IDetailsView* InDetailsView); + + // Add Skeletal Inputs UI Widgets + static void AddSkeletalInputUI( + TSharedRef VerticalBox, + TArray& InInputs, + TSharedPtr AssetThumbnailPool); + /* + static FMenuBuilder Helper_CreateCustomActorPickerWidget( + UHoudiniInput* InParam, + const TAttribute& HeadingText, + const bool& bShowCurrentSelectionSection) + */ + + static FMenuBuilder Helper_CreateHoudiniAssetPickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateLandscapePickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateWorldActorPickerWidget(TArray& InInputs); + + static FMenuBuilder Helper_CreateBoundSelectorPickerWidget(TArray& InInputs); + + static FReply Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); + + static FReply Helper_OnButtonClickUseSelectionAsBoundSelector(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName); + + static FReply Helper_OnButtonClickSelectActors( + IDetailCategoryBuilder& CategoryBuilder, + TArray InInputs, + const FName& InDetailsPanelName, + const bool& bUseWorldInAsWorldSelector); + + static bool Helper_CancelWorldSelection( + TArray& InInputs, const FName& DetailsPanelName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp index dd64d788d..5fb7ee3da 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp @@ -1,3656 +1,3659 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniOutputDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniAssetComponentDetails.h" -#include "HoudiniMeshTranslator.h" -#include "HoudiniInstanceTranslator.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAsset.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniStaticMesh.h" -#include "HoudiniEngineCommands.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "DetailWidgetRow.h" -#include "IDetailGroup.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SRotatorInputBox.h" -#include "Widgets/Layout/SBox.h" -#include "Widgets/Text/STextBlock.h" -#include "Editor/UnrealEd/Public/AssetThumbnail.h" -#include "SAssetDropTarget.h" -#include "Engine/StaticMesh.h" -#include "Components/SplineComponent.h" -#include "Materials/Material.h" -#include "Materials/MaterialInstance.h" -#include "Materials/MaterialInstanceConstant.h" -#include "Sound/SoundBase.h" -#include "Engine/SkeletalMesh.h" -#include "Particles/ParticleSystem.h" -//#include "Landscape.h" -#include "LandscapeProxy.h" -#include "ScopedTransaction.h" -#include "PhysicsEngine/BodySetup.h" -#include "UnrealEdGlobals.h" -#include "Editor/UnrealEdEngine.h" -#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -void -FHoudiniOutputDetails::CreateWidget( - IDetailCategoryBuilder& HouOutputCategory, - TArray InOutputs) -{ - if (InOutputs.Num() <= 0) - return; - - UHoudiniOutput* MainOutput = InOutputs[0]; - if (!IsValid(MainOutput)) - return; - - // Don't create UI for editable curve. - if (MainOutput->IsEditableNode() && MainOutput->GetType() == EHoudiniOutputType::Curve) - return; - - // Get thumbnail pool for this builder. - TSharedPtr AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); - - switch (MainOutput->GetType()) - { - case EHoudiniOutputType::Mesh: - { - FHoudiniOutputDetails::CreateMeshOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Landscape: - { - FHoudiniOutputDetails::CreateLandscapeOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Instancer: - { - FHoudiniOutputDetails::CreateInstancerOutputWidget(HouOutputCategory, MainOutput); - break; - } - - case EHoudiniOutputType::Curve: - { - FHoudiniOutputDetails::CreateCurveOutputWidget(HouOutputCategory, MainOutput); - break; - } - case EHoudiniOutputType::Skeletal: - default: - { - FHoudiniOutputDetails::CreateDefaultOutputWidget(HouOutputCategory, MainOutput); - break; - } - } -} - - -void -FHoudiniOutputDetails::CreateLandscapeOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // Go through this output's objects - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& CurrentOutputObj : OutputObjects) - { - FHoudiniOutputObjectIdentifier& Identifier = CurrentOutputObj.Key; - const FHoudiniGeoPartObject *HGPO = nullptr; - for (const auto& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!Identifier.Matches(CurHGPO)) - continue; - - HGPO = &CurHGPO; - break; - } - - if (!HGPO) - continue; - - - if (UHoudiniLandscapePtr* LandscapePointer = Cast(CurrentOutputObj.Value.OutputObject)) - { - CreateLandscapeOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapePointer, Identifier); - } - else if (UHoudiniLandscapeEditLayer* LandscapeLayer = Cast(CurrentOutputObj.Value.OutputObject)) - { - // TODO: Create widget for landscape editlayer output - CreateLandscapeEditLayerOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapeLayer, Identifier); - } - - - - } -} - -void -FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - const FHoudiniGeoPartObject& HGPO, - UHoudiniLandscapePtr* LandscapePointer, - const FHoudiniOutputObjectIdentifier & OutputIdentifier) -{ - if (!LandscapePointer || LandscapePointer->IsPendingKill() || !LandscapePointer->LandscapeSoftPtr.IsValid()) - return; - - if (!InOutput || InOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return; - - ALandscapeProxy * Landscape = LandscapePointer->LandscapeSoftPtr.Get(); - if (!Landscape || Landscape->IsPendingKill()) - return; - - // TODO: Get bake base name - FString Label = Landscape->GetName(); - - EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType = LandscapePointer->BakeType; - - // Get thumbnail pool for this builder - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - TArray>* BakeOptionString = FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels(); - - // Create bake mesh name textfield. - IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - LandscapeGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(Label)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - // Create the thumbnail for the landscape output object. - TSharedPtr< FAssetThumbnail > LandscapeThumbnail = - MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); - - TSharedPtr< SBorder > LandscapeThumbnailBorder; - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - LandscapeGrp.AddWidgetRow() - .NameContent() - [ - SNew(SSpacer) - .Size(FVector2D(250, 64)) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot().Padding(0, 2).AutoHeight() - [ - SNew(SBox).WidthOverride(175) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(LandscapeThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(Landscape->GetPathName())) - [ - LandscapeThumbnail->MakeThumbnailWidget() - ] - ] - ] - - + SHorizontalBox::Slot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SBox).WidthOverride(40.0f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("Bake", "Bake")) - .IsEnabled(true) - .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() - { - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - if (FoundOutputObject) - { - TArray AllOutputs; - AllOutputs.Reserve(HAC->GetNumOutputs()); - HAC->GetOutputs(AllOutputs); - FHoudiniOutputDetails::OnBakeOutputObject( - FoundOutputObject->BakeName, - Landscape, - OutputIdentifier, - *FoundOutputObject, - HGPO, - HAC, - OwnerActor->GetName(), - HAC->BakeFolder.Path, - HAC->TemporaryCookFolder.Path, - InOutput->GetType(), - LandscapeOutputBakeType, - AllOutputs); - } - - // TODO: Remove the output landscape if the landscape bake type is Detachment? - return FReply::Handled(); - }) - .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) - ] - ] - + SHorizontalBox::Slot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SBox).WidthOverride(120.f) - [ - SNew(SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - if (SelectType != ESelectInfo::Type::OnMouseClick) - return; - - FString *NewChoiceStr = NewChoice.Get(); - if (!NewChoiceStr) - return; - - if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); - } - else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); - } - else - { - LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); - } - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - [ - SNew(STextBlock) - .Text_Lambda([LandscapePointer]() - { - FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); - return FText::FromString(BakeTypeString); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ] - ] - ]; - - // Store thumbnail for this landscape. - OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); - - // We need to add material box for each the landscape and landscape hole materials - for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) - { - UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); - TSharedPtr MaterialThumbnailBorder; - TSharedPtr HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if (MaterialInterface) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - - // Create thumbnail for this material. - TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = - MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); - - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(STextBlock) - .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - VerticalBox->AddSlot().Padding(0, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) - .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(MaterialThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(MaterialPathName)) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this landscape and material index. - { - TPairInitializer Pair(Landscape, MaterialIdx); - MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); - } - - // Combox Box and Button Box - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Combo row - TSharedPtr< SComboButton > AssetComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) - [ - SAssignNew(AssetComboButton, SComboButton) - //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - // Buttons row - TSharedPtr ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Add use Content Browser selection arrow - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)Landscape, InOutput, MaterialIdx), - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) - ]; - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), - TAttribute< FText >(MaterialTooltip)) - ]; - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - - // Store combo button for this mesh and index. - { - TPairInitializer Pair(Landscape, MaterialIdx); - MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); - } - } -} - -void FHoudiniOutputDetails::CreateLandscapeEditLayerOutputWidget_Helper(IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& HGPO, UHoudiniLandscapeEditLayer* LandscapeEditLayer, - const FHoudiniOutputObjectIdentifier& OutputIdentifier) -{ - if (!LandscapeEditLayer || LandscapeEditLayer->IsPendingKill() || !LandscapeEditLayer->LandscapeSoftPtr.IsValid()) - return; - - if (!InOutput || InOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return; - - ALandscapeProxy * Landscape = LandscapeEditLayer->LandscapeSoftPtr.Get(); - if (!Landscape || Landscape->IsPendingKill()) - return; - - const FString Label = Landscape->GetName(); - const FString LayerName = LandscapeEditLayer->LayerName; - - // Get thumbnail pool for this builder - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // Create labels to display the edit layer name. - IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - LandscapeGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("LandscapeEditLayerName", "Edit Layer Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(STextBlock) - .Text(FText::AsCultureInvariant(LayerName)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - - // SNew(SHorizontalBox) - // + SHorizontalBox::Slot() - // .Padding(2.0f, 0.0f) - // .VAlign(VAlign_Center) - // .FillWidth(1) - // [ - // SNew(SEditableTextBox) - // .Text(FText::FromString(Label)) - // .Font(IDetailLayoutBuilder::GetDetailFont()) - // .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - // .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - // .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) - // { - // FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - // FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - // }) - // ] - // + SHorizontalBox::Slot() - // .Padding(2.0f, 0.0f) - // .VAlign(VAlign_Center) - // .AutoWidth() - // [ - // SNew(SButton) - // .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - // .ButtonStyle(FEditorStyle::Get(), "NoBorder") - // .ContentPadding(0) - // .Visibility(EVisibility::Visible) - // .OnClicked_Lambda([InOutput, OutputIdentifier]() - // { - // FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - // return FReply::Handled(); - // }) - // [ - // SNew(SImage) - // .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - // ] - // ] - ]; - - // // Create the thumbnail for the landscape output object. - // TSharedPtr< FAssetThumbnail > LandscapeThumbnail = - // MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); - // - // TSharedPtr< SBorder > LandscapeThumbnailBorder; - // TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - // - // LandscapeGrp.AddWidgetRow() - // .NameContent() - // [ - // SNew(SSpacer) - // .Size(FVector2D(250, 64)) - // ] - // .ValueContent() - // .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - // [ - // VerticalBox - // ]; - // - // VerticalBox->AddSlot().Padding(0, 2).AutoHeight() - // [ - // SNew(SBox).WidthOverride(175) - // [ - // SNew(SHorizontalBox) - // + SHorizontalBox::Slot() - // .Padding(0.0f, 0.0f, 2.0f, 0.0f) - // .AutoWidth() - // [ - // SAssignNew(LandscapeThumbnailBorder, SBorder) - // .Padding(5.0f) - // .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) - // .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) - // [ - // SNew(SBox) - // .WidthOverride(64) - // .HeightOverride(64) - // .ToolTipText(FText::FromString(Landscape->GetPathName())) - // [ - // LandscapeThumbnail->MakeThumbnailWidget() - // ] - // ] - // ] - // - // + SHorizontalBox::Slot() - // .Padding(0.0f, 4.0f, 4.0f, 4.0f) - // .VAlign(VAlign_Center) - // [ - // SNew(SBox).WidthOverride(40.0f) - // [ - // SNew(SButton) - // .VAlign(VAlign_Center) - // .HAlign(HAlign_Center) - // .Text(LOCTEXT("Bake", "Bake")) - // .IsEnabled(true) - // .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() - // { - // FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - // if (FoundOutputObject) - // { - // TArray AllOutputs; - // AllOutputs.Reserve(HAC->GetNumOutputs()); - // HAC->GetOutputs(AllOutputs); - // FHoudiniOutputDetails::OnBakeOutputObject( - // FoundOutputObject->BakeName, - // Landscape, - // OutputIdentifier, - // *FoundOutputObject, - // HGPO, - // HAC, - // OwnerActor->GetName(), - // HAC->BakeFolder.Path, - // HAC->TemporaryCookFolder.Path, - // InOutput->GetType(), - // LandscapeOutputBakeType, - // AllOutputs); - // } - // - // // TODO: Remove the output landscape if the landscape bake type is Detachment? - // return FReply::Handled(); - // }) - // .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) - // ] - // ] - // + SHorizontalBox::Slot() - // .Padding(0.0f, 4.0f, 4.0f, 4.0f) - // .VAlign(VAlign_Center) - // [ - // SNew(SBox).WidthOverride(120.f) - // [ - // SNew(SComboBox>) - // .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) - // .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) - // .OnGenerateWidget_Lambda( - // [](TSharedPtr< FString > InItem) - // { - // return SNew(STextBlock).Text(FText::FromString(*InItem)); - // }) - // .OnSelectionChanged_Lambda( - // [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - // { - // if (SelectType != ESelectInfo::Type::OnMouseClick) - // return; - // - // FString *NewChoiceStr = NewChoice.Get(); - // if (!NewChoiceStr) - // return; - // - // if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) - // { - // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); - // } - // else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) - // { - // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); - // } - // else - // { - // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); - // } - // - // FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - // }) - // [ - // SNew(STextBlock) - // .Text_Lambda([LandscapePointer]() - // { - // FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); - // return FText::FromString(BakeTypeString); - // }) - // .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - // ] - // ] - // ] - // ] - // ]; - // - // // Store thumbnail for this landscape. - // OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); - // - // // We need to add material box for each the landscape and landscape hole materials - // for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) - // { - // UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); - // TSharedPtr MaterialThumbnailBorder; - // TSharedPtr HorizontalBox = NULL; - // - // FString MaterialName, MaterialPathName; - // if (MaterialInterface) - // { - // MaterialName = MaterialInterface->GetName(); - // MaterialPathName = MaterialInterface->GetPathName(); - // } - // - // // Create thumbnail for this material. - // TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = - // MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); - // - // VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - // [ - // SNew(STextBlock) - // .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) - // .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - // ]; - // - // VerticalBox->AddSlot().Padding(0, 2) - // [ - // SNew(SAssetDropTarget) - // .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) - // .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) - // [ - // SAssignNew(HorizontalBox, SHorizontalBox) - // ] - // ]; - // - // HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - // [ - // SAssignNew(MaterialThumbnailBorder, SBorder) - // .Padding(5.0f) - // .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) - // .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) - // [ - // SNew(SBox) - // .WidthOverride(64) - // .HeightOverride(64) - // .ToolTipText(FText::FromString(MaterialPathName)) - // [ - // MaterialInterfaceThumbnail->MakeThumbnailWidget() - // ] - // ] - // ]; - // - // // Store thumbnail for this landscape and material index. - // { - // TPairInitializer Pair(Landscape, MaterialIdx); - // MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); - // } - // - // // Combox Box and Button Box - // TSharedPtr ComboAndButtonBox; - // HorizontalBox->AddSlot() - // .FillWidth(1.0f) - // .Padding(0.0f, 4.0f, 4.0f, 4.0f) - // .VAlign(VAlign_Center) - // [ - // SAssignNew(ComboAndButtonBox, SVerticalBox) - // ]; - // - // // Combo row - // TSharedPtr< SComboButton > AssetComboButton; - // ComboAndButtonBox->AddSlot().FillHeight(1.0f) - // [ - // SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) - // [ - // SAssignNew(AssetComboButton, SComboButton) - // //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) - // .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - // .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - // .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) - // .ContentPadding(2.0f) - // .ButtonContent() - // [ - // SNew(STextBlock) - // .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - // .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - // .Text(FText::FromString(MaterialName)) - // ] - // ] - // ]; - // - // // Buttons row - // TSharedPtr ButtonBox; - // ComboAndButtonBox->AddSlot().FillHeight(1.0f) - // [ - // SAssignNew(ButtonBox, SHorizontalBox) - // ]; - // - // // Add use Content Browser selection arrow - // ButtonBox->AddSlot() - // .AutoWidth() - // .Padding(2.0f, 0.0f) - // .VAlign(VAlign_Center) - // [ - // PropertyCustomizationHelpers::MakeUseSelectedButton( - // FSimpleDelegate::CreateSP( - // this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - // (UObject*)Landscape, InOutput, MaterialIdx), - // TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) - // ]; - // - // // Create tooltip. - // FFormatNamedArguments Args; - // Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - // FText MaterialTooltip = FText::Format( - // LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - // - // ButtonBox->AddSlot() - // .AutoWidth() - // .Padding(2.0f, 0.0f) - // .VAlign(VAlign_Center) - // [ - // PropertyCustomizationHelpers::MakeBrowseButton( - // FSimpleDelegate::CreateSP( - // this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), - // TAttribute< FText >(MaterialTooltip)) - // ]; - // - // ButtonBox->AddSlot() - // .AutoWidth() - // .Padding(2.0f, 0.0f) - // .VAlign(VAlign_Center) - // [ - // SNew(SButton) - // .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) - // .ButtonStyle(FEditorStyle::Get(), "NoBorder") - // .ContentPadding(0) - // .Visibility(EVisibility::Visible) - // .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) - // [ - // SNew(SImage) - // .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - // ] - // ]; - // - // // Store combo button for this mesh and index. - // { - // TPairInitializer Pair(Landscape, MaterialIdx); - // MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); - // } - // } -} - -void -FHoudiniOutputDetails::CreateMeshOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent* HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - FString HoudiniAssetName; - if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) - { - HoudiniAssetName = HAC->GetOwner()->GetName(); - } - else if (HAC->GetHoudiniAsset()) - { - HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); - } - else - { - HoudiniAssetName = HAC->GetName(); - } - - // Go through this output's object - int32 OutputObjIdx = 0; - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& IterObject : OutputObjects) - { - UStaticMesh* StaticMesh = Cast(IterObject.Value.OutputObject); - UHoudiniStaticMesh* ProxyMesh = Cast(IterObject.Value.ProxyObject); - - if ((!StaticMesh || StaticMesh->IsPendingKill()) - && (!ProxyMesh || ProxyMesh->IsPendingKill())) - continue; - - FHoudiniOutputObjectIdentifier & OutputIdentifier = IterObject.Key; - - // Find the corresponding HGPO in the output - FHoudiniGeoPartObject HoudiniGeoPartObject; - for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!OutputIdentifier.Matches(curHGPO)) - continue; - - HoudiniGeoPartObject = curHGPO; - break; - } - - if (StaticMesh && !StaticMesh->IsPendingKill()) - { - bool bIsProxyMeshCurrent = IterObject.Value.bProxyIsCurrent; - - // If we have a static mesh, alway display its widget even if the proxy is more recent - CreateStaticMeshAndMaterialWidgets( - HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); - } - else - { - // If we only have a proxy mesh, then show the proxy widget - CreateProxyMeshAndMaterialWidgets( - HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject); - } - } -} - -void -FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - int32 OutputObjIdx = 0; - TMap& OutputObjects = InOutput->GetOutputObjects(); - for (auto& IterObject : OutputObjects) - { - FHoudiniOutputObject& CurrentOutputObject = IterObject.Value; - USceneComponent* SplineComponent = Cast(IterObject.Value.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - FHoudiniOutputObjectIdentifier& OutputIdentifier = IterObject.Key; - FHoudiniGeoPartObject HoudiniGeoPartObject; - for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - if (!OutputIdentifier.Matches(curHGPO)) - continue; - - HoudiniGeoPartObject = curHGPO; - break; - } - - CreateCurveWidgets(HouOutputCategory, InOutput, SplineComponent, CurrentOutputObject, OutputIdentifier, HoudiniGeoPartObject); - } -} - -void -FHoudiniOutputDetails::CreateCurveWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - USceneComponent* SplineComponent, - FHoudiniOutputObject& OutputObject, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - FHoudiniGeoPartObject& HoudiniGeoPartObject) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // We support Unreal Spline out only for now - USplineComponent* SplineOutput = Cast(SplineComponent); - if (!SplineOutput || SplineOutput->IsPendingKill()) - return; - - UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) - return; - - AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) - return; - - FHoudiniCurveOutputProperties* OutputProperty = &(OutputObject.CurveOutputProperty); - EHoudiniCurveType OutputCurveType = OutputObject.CurveOutputProperty.CurveType; - - FString Label = SplineComponent->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - //Label += FString("_") + OutputIdentifier.SplitIdentifier; - - FString OutputCurveName = OutputObject.BakeName; - if(OutputCurveName.IsEmpty()) - OutputCurveName = OwnerActor->GetName() + "_" + Label; - - const FText& LabelText = FText::FromString("Unreal Spline"); - - IDetailGroup& CurveOutputGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label), false, false); - - // Bake name row UI - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(OutputObject.BakeName)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - ] - - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("OutputCurveSplineType", "Spline Type")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText_Lambda([SplineOutput, Label, OutputCurveType]() - { - FString ToolTipStr = FString::Printf(TEXT(" curve: %s\n Export type: Unreal Spline\n num points: %d\n curve type: %s\n closed: %s"), - *Label, - SplineOutput->GetNumberOfSplinePoints(), - *FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(OutputCurveType), - SplineOutput->IsClosedLoop() ? *(FString("yes")) : *(FString("no"))); - - return FText::FromString(ToolTipStr); - }) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(STextBlock) - // We support Unreal Spline output only for now... - .Text(LOCTEXT("OutputCurveSplineTypeUnreal", "Unreal Spline")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ]; - - //if (bIsUnrealSpline) - //{ - USplineComponent* UnrealSpline = Cast(SplineComponent); - - // Curve type combo box UI - auto InitialSelectionLambda = [OutputProperty]() - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - { - return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[0]; - } - else - { - return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[1]; - } - }; - - TSharedPtr>> UnrealCurveTypeComboBox; - - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Text(LOCTEXT("OutputCurveUnrealSplinePointType", "Spline Point Type")) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(UnrealCurveTypeComboBox, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels()) - .InitiallySelectedItem(InitialSelectionLambda()) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [OutputProperty, InOutput, SplineComponent](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - // Set the curve point type locally - USplineComponent* Spline = Cast(SplineComponent); - if (!Spline || Spline->IsPendingKill()) - return; - - FString *NewChoiceStr = NewChoice.Get(); - if (!NewChoiceStr) - return; - - if (*NewChoiceStr == "Linear") - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - return; - - OutputProperty->CurveType = EHoudiniCurveType::Polygon; - - for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) - { - Spline->SetSplinePointType(PtIdx, ESplinePointType::Linear); - } - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - } - else if (*NewChoiceStr == "Curve") - { - if (OutputProperty->CurveType != EHoudiniCurveType::Polygon) - return; - - OutputProperty->CurveType = EHoudiniCurveType::Bezier; - - for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) - { - Spline->SetSplinePointType(PtIdx, ESplinePointType::Curve); - } - - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([OutputProperty]() - { - if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) - return FText::FromString(TEXT("Linear")); - else - return FText::FromString(TEXT("Curve")); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - // Add closed curve checkbox UI - TSharedPtr ClosedCheckBox; - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Text(LOCTEXT("OutputCurveUnrealSplineClosed", "Closed")) - ] - .ValueContent() - [ - SAssignNew(ClosedCheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([UnrealSpline, InOutput](ECheckBoxState NewState) - { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) - return; - - UnrealSpline->SetClosedLoop(NewState == ECheckBoxState::Checked); - FHoudiniEngineEditorUtils::ReselectSelectedActors(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }) - .IsChecked_Lambda([UnrealSpline]() - { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return UnrealSpline->IsClosedLoop() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - ]; - //} - - // Add Bake Button UI - TSharedPtr BakeButton; - CurveOutputGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - ] - .ValueContent() - [ - SAssignNew(BakeButton, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) - .IsEnabled(true) - .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) - .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName, OutputObject]() - { - TArray AllOutputs; - AllOutputs.Reserve(HAC->GetNumOutputs()); - HAC->GetOutputs(AllOutputs); - FHoudiniOutputDetails::OnBakeOutputObject( - OutputCurveName, - SplineComponent, - OutputIdentifier, - OutputObject, - HoudiniGeoPartObject, - HAC, - OwnerActor->GetName(), - HAC->BakeFolder.Path, - HAC->TemporaryCookFolder.Path, - InOutput->GetType(), - EHoudiniLandscapeOutputBakeType::InValid, - AllOutputs); - - return FReply::Handled(); - }) - ]; -} - -void -FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UStaticMesh * StaticMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject, - const bool& bIsProxyMeshCurrent) -{ - if (!StaticMesh || StaticMesh->IsPendingKill()) - return; - - UHoudiniAssetComponent* OwningHAC = Cast(InOutput->GetOuter()); - - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); - - // Get thumbnail pool for this builder. - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // TODO: GetBakingBaseName! - FString Label = StaticMesh->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - // Create thumbnail for this mesh. - TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = - MakeShareable(new FAssetThumbnail(StaticMesh, 64, 64, AssetThumbnailPool)); - TSharedPtr StaticMeshThumbnailBorder; - - TSharedRef VerticalBox = SNew(SVerticalBox); - - IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(BakeName)) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - }) - .ToolTipText( LOCTEXT( "BakeNameTip", "The base name of the baked asset") ) - ] - - +SHorizontalBox::Slot() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - .AutoWidth() - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "RevertNameOverride", "Revert bake name override" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ] - ]; - - // Add details on the SM colliders - EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); - FString MeshLabel = TEXT( "Static Mesh" ); - - // If the Proxy mesh is more recent, indicate it in the details - if (bIsProxyMeshCurrent) - { - MeshLabel += TEXT("\n(unrefined)"); - } - - // Indicate that this mesh is instanced - if (HoudiniGeoPartObject.bIsInstanced) - { - MeshLabel += TEXT("\n(instanced)"); - } - - if (HoudiniGeoPartObject.bIsTemplated) - { - MeshLabel += TEXT("\n(templated)"); - } - - int32 NumSimpleColliders = 0; - if (StaticMesh->BodySetup && !StaticMesh->BodySetup->IsPendingKill()) - NumSimpleColliders = StaticMesh->BodySetup->AggGeom.GetElementCount(); - - if(NumSimpleColliders > 0) - { - MeshLabel += TEXT( "\n(") + FString::FromInt(NumSimpleColliders) + TEXT(" Simple Collider" ); - if (NumSimpleColliders > 1 ) - MeshLabel += TEXT("s"); - MeshLabel += TEXT(")"); - } - else if (SplitType == EHoudiniSplitType::RenderedComplexCollider) - { - MeshLabel += TEXT( "\n(Rendered Complex Collider)" ); - } - else if(SplitType == EHoudiniSplitType::InvisibleComplexCollider ) - { - MeshLabel += TEXT( "\n(Invisible Complex Collider)" ); - } - - if ( StaticMesh->GetNumLODs() > 1 ) - MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)"); - - if (HoudiniGeoPartObject.AllMeshSockets.Num() > 0) - { - if (bIsProxyMeshCurrent) - { - // Proxy is current, show the number of sockets on the HGPO - MeshLabel += TEXT("\n(") + FString::FromInt(HoudiniGeoPartObject.AllMeshSockets.Num()) + TEXT(" sockets)"); - } - else - { - // Show the number of sockets on the SM - MeshLabel += TEXT("\n(") + FString::FromInt(StaticMesh->Sockets.Num()) + TEXT(" sockets)"); - } - } - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew( STextBlock ) - .Text( FText::FromString(MeshLabel) ) - .Font( IDetailLayoutBuilder::GetDetailFont() ) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot() - .Padding( 0, 2 ) - .AutoHeight() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) - .AutoWidth() - [ - SAssignNew( StaticMeshThumbnailBorder, SBorder ) - .Padding( 5.0f ) - .BorderImage( this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)StaticMesh ) - .OnMouseDoubleClick( this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *) StaticMesh ) - [ - SNew( SBox ) - .WidthOverride( 64 ) - .HeightOverride( 64 ) - .ToolTipText( FText::FromString( StaticMesh->GetPathName() ) ) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ] - - +SHorizontalBox::Slot() - .FillWidth( 1.0f ) - .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SVerticalBox ) - +SVerticalBox::Slot() - [ - SNew( SHorizontalBox ) - +SHorizontalBox::Slot() - .MaxWidth( 80.0f ) - [ - SNew( SButton ) - .VAlign( VAlign_Center ) - .HAlign( HAlign_Center ) - .Text( LOCTEXT( "Bake", "Bake" ) ) - .IsEnabled(true) - .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC, FoundOutputObject]() - { - if (FoundOutputObject) - { - TArray AllOutputs; - FString TempCookFolder; - if (IsValid(OwningHAC)) - { - AllOutputs.Reserve(OwningHAC->GetNumOutputs()); - OwningHAC->GetOutputs(AllOutputs); - - TempCookFolder = OwningHAC->TemporaryCookFolder.Path; - } - FHoudiniOutputDetails::OnBakeOutputObject( - BakeName, - StaticMesh, - OutputIdentifier, - *FoundOutputObject, - HoudiniGeoPartObject, - OwningHAC, - HoudiniAssetName, - BakeFolder, - TempCookFolder, - InOutput->GetType(), - EHoudiniLandscapeOutputBakeType::InValid, - AllOutputs); - } - - return FReply::Handled(); - }) - .ToolTipText( LOCTEXT( "HoudiniStaticMeshBakeButton", "Bake this generated static mesh" ) ) - ] - +SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)StaticMesh), - TAttribute(LOCTEXT("HoudiniStaticMeshBrowseButton", "Browse to this generated static mesh in the content browser"))) - ] - ] - ] - ]; - - // Store thumbnail for this mesh. - OutputObjectThumbnailBorders.Add((UObject*)StaticMesh, StaticMeshThumbnailBorder); - - // We need to add material box for each material present in this static mesh. - auto & StaticMeshMaterials = StaticMesh->StaticMaterials; - for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) - { - UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface; - TSharedPtr< SBorder > MaterialThumbnailBorder; - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if ( MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - else - { - MaterialInterface = nullptr; - MaterialName = TEXT("Material (invalid)") + FString::FromInt( MaterialIdx ) ; - MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - } - - // Create thumbnail for this material. - TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = - MakeShareable( new FAssetThumbnail( MaterialInterface, 64, 64, AssetThumbnailPool ) ); - - VerticalBox->AddSlot().Padding( 0, 2 ) - [ - SNew( SAssetDropTarget ) - .OnIsAssetAcceptableForDrop( this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver ) - .OnAssetDropped( - this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx ) - [ - SAssignNew( HorizontalBox, SHorizontalBox ) - ] - ]; - - HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() - [ - SAssignNew( MaterialThumbnailBorder, SBorder ) - .Padding( 5.0f ) - .BorderImage( - this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject *)StaticMesh, MaterialIdx ) - .OnMouseDoubleClick( - this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface ) - [ - SNew( SBox ) - .WidthOverride( 64 ) - .HeightOverride( 64 ) - .ToolTipText( FText::FromString( MaterialPathName ) ) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this mesh and material index. - { - TPairInitializer Pair( StaticMesh, MaterialIdx ); - MaterialInterfaceThumbnailBorders.Add( Pair, MaterialThumbnailBorder ); - } - - // ComboBox and buttons - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add Combo box - TSharedPtr< SComboButton > AssetComboButton; - ComboAndButtonBox->AddSlot().VAlign(VAlign_Center).FillHeight(1.0f) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().VAlign(VAlign_Center).FillHeight(1.0f) - [ - SAssignNew(AssetComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, - MaterialInterface, (UObject*)StaticMesh, InOutput, MaterialIdx) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - - // Add buttons - TSharedPtr< SHorizontalBox > ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Use CB selection arrow button - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)StaticMesh, InOutput, MaterialIdx), - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) - ]; - - // Browse CB button - ButtonBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface ), TAttribute< FText >( MaterialTooltip ) ) - ]; - - // Reset button - ButtonBox->AddSlot() - .AutoWidth() - .Padding( 2.0f, 0.0f ) - .VAlign( VAlign_Center ) - [ - SNew( SButton ) - .ToolTipText( LOCTEXT( "ResetToBaseMaterial", "Reset to base material" ) ) - .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) - .ContentPadding( 0 ) - .Visibility( EVisibility::Visible ) - .OnClicked( - this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) - [ - SNew( SImage ) - .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) - ] - ]; - - // Store combo button for this mesh and index. - { - TPairInitializer Pair( StaticMesh, MaterialIdx ); - MaterialInterfaceComboButtons.Add( Pair, AssetComboButton ); - } - } -} - -void -FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UHoudiniStaticMesh * ProxyMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject) -{ - if (!ProxyMesh || ProxyMesh->IsPendingKill()) - return; - - FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); - FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); - - // Get thumbnail pool for this builder. - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // TODO: GetBakingBaseName! - FString Label = ProxyMesh->GetName(); - if (HoudiniGeoPartObject.bHasCustomPartName) - Label = HoudiniGeoPartObject.PartName; - - // Create thumbnail for this mesh. - TSharedPtr MeshThumbnail = MakeShareable(new FAssetThumbnail(ProxyMesh, 64, 64, AssetThumbnailPool)); - TSharedPtr MeshThumbnailBorder; - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); - - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LOCTEXT("BakeBaseName", "Bake Name")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .FillWidth(1) - [ - SNew(SEditableTextBox) - .Text(FText::FromString(BakeName)) - .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) - { - FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - }) - .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - ] - + SHorizontalBox::Slot() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([InOutput, OutputIdentifier]() - { - FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - - // Add details on the Proxy Mesh - EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); - FString MeshLabel = TEXT("Proxy Mesh"); - - // Indicate that this mesh is instanced - if (HoudiniGeoPartObject.bIsInstanced) - { - MeshLabel += TEXT("\n(instanced)"); - } - - if (HoudiniGeoPartObject.bIsTemplated) - { - MeshLabel += TEXT("\n(templated)"); - } - - if (HoudiniGeoPartObject.AllMeshSockets.Num() > 0) - { - MeshLabel += TEXT("\n(") + FString::FromInt(HoudiniGeoPartObject.AllMeshSockets.Num()) + TEXT(" sockets)"); - } - - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); - StaticMeshGrp.AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(FText::FromString(MeshLabel)) - .Font(IDetailLayoutBuilder::GetDetailFont()) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - VerticalBox - ]; - - VerticalBox->AddSlot() - .Padding(0, 2) - .AutoHeight() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(MeshThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)ProxyMesh) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)ProxyMesh) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(ProxyMesh->GetPathName())) - [ - MeshThumbnail->MakeThumbnailWidget() - ] - ] - ] - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .MaxWidth(80.0f) - [ - SNew(SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(LOCTEXT("Refine", "Refine")) - .IsEnabled(true) - .OnClicked(this, &FHoudiniOutputDetails::OnRefineClicked, (UObject *)ProxyMesh, InOutput) - .ToolTipText(LOCTEXT("RefineTooltip", "Refine this Proxy Mesh to a Static Mesh")) - ] - ] - ] - ]; - - // Store thumbnail for this mesh. - OutputObjectThumbnailBorders.Add(ProxyMesh, MeshThumbnailBorder); - - // We need to add material box for each material present in this static mesh. - auto & ProxyMeshMaterials = ProxyMesh->GetStaticMaterials(); - for (int32 MaterialIdx = 0; MaterialIdx < ProxyMeshMaterials.Num(); ++MaterialIdx) - { - UMaterialInterface * MaterialInterface = ProxyMeshMaterials[MaterialIdx].MaterialInterface; - TSharedPtr< SBorder > MaterialThumbnailBorder; - TSharedPtr< SHorizontalBox > HorizontalBox = NULL; - - FString MaterialName, MaterialPathName; - if (MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill()) - { - MaterialName = MaterialInterface->GetName(); - MaterialPathName = MaterialInterface->GetPathName(); - } - else - { - MaterialInterface = nullptr; - MaterialName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); - } - - // Create thumbnail for this material. - TSharedPtr MaterialInterfaceThumbnail = - MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); - - // No drop target - VerticalBox->AddSlot() - .Padding(0, 2) - [ - SNew(SAssetDropTarget) - //.OnIsAssetAcceptableForDrop(false) - //.OnAssetDropped( - // this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - HorizontalBox->AddSlot() - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .AutoWidth() - [ - SAssignNew(MaterialThumbnailBorder, SBorder) - .Padding(5.0f) - .BorderImage( - this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)ProxyMesh, MaterialIdx) - .OnMouseDoubleClick( - this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(MaterialPathName)) - [ - MaterialInterfaceThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - // Store thumbnail for this mesh and material index. - { - TPairInitializer Pair((UObject*)ProxyMesh, MaterialIdx); - MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); - } - - // Combo box and buttons - TSharedPtr ComboAndButtonBox; - HorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SAssignNew(ComboAndButtonBox, SVerticalBox) - ]; - - // Add combo box - TSharedPtr AssetComboButton; - ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) - [ - SAssignNew(AssetComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - /*.OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, - MaterialInterface, StaticMesh, InOutput, MaterialIdx)*/ - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(MaterialName)) - ] - ] - ]; - - - TSharedPtr ButtonBox; - ComboAndButtonBox->AddSlot().FillHeight(1.0f) - [ - SAssignNew(ButtonBox, SHorizontalBox) - ]; - - // Disable the combobutton for proxies - AssetComboButton->SetEnabled(false); - - // Add use selection form content browser array - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeUseSelectedButton( - /*FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, - (UObject*)ProxyMesh, InOutput, MaterialIdx),*/ - FSimpleDelegate::CreateLambda([]() {}), // Do nothing for proxies - TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser")), false) - // Disable the use CB selection button for proxies - ]; - - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); - FText MaterialTooltip = FText::Format( - LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateSP(this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), TAttribute(MaterialTooltip)) - ]; - - /* - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked( - this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - */ - - // Store combo button for this mesh and index. - { - TPairInitializer Pair(ProxyMesh, MaterialIdx); - MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); - } - } -} - -FText -FHoudiniOutputDetails::GetOutputDebugName(UHoudiniOutput* InOutput) -{ - // Get the name and type - FString OutputNameStr = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); - - // Then add the number of parts - OutputNameStr += TEXT(" (") + FString::FromInt(InOutput->GetHoudiniGeoPartObjects().Num()) + TEXT(" Part(s))\n"); - - return FText::FromString(OutputNameStr); -} -FText -FHoudiniOutputDetails::GetOutputDebugDescription(UHoudiniOutput* InOutput) -{ - const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); - - FString OutputValStr; - OutputValStr += TEXT("HGPOs:\n"); - for (auto& HGPO : HGPOs) - { - OutputValStr += TEXT(" - ") + HGPO.PartName + TEXT(" (") + FHoudiniGeoPartObject::HoudiniPartTypeToString(HGPO.Type) + TEXT(")"); - - if (HGPO.SplitGroups.Num() > 0) - { - OutputValStr += TEXT("( ") + FString::FromInt(HGPO.SplitGroups.Num()) + TEXT(" splits:"); - for (auto& split : HGPO.SplitGroups) - { - OutputValStr += TEXT(" ") + split; - } - OutputValStr += TEXT(")"); - } - - if (!HGPO.VolumeName.IsEmpty()) - { - OutputValStr += TEXT("( ") + HGPO.VolumeName; - if (HGPO.VolumeTileIndex >= 0) - OutputValStr += TEXT(" tile ") + FString::FromInt(HGPO.VolumeTileIndex); - OutputValStr += TEXT(" )"); - } - - OutputValStr += TEXT("\n"); - } - - // Add output objects if any - TMap AllOutputObj = InOutput->GetOutputObjects(); - if (AllOutputObj.Num() > 0) - { - bool TitleAdded = false; - for (const auto& Iter : AllOutputObj) - { - UObject* OutObject = Iter.Value.OutputObject; - if (OutObject) - { - OutputValStr += OutObject->GetFullName() + TEXT(" (obj)\n"); - } - - UObject* OutComp = Iter.Value.OutputComponent; - if (OutComp) - { - OutputValStr += OutComp->GetFullName() + TEXT(" (comp)\n"); - } - } - } - - return FText::FromString(OutputValStr); -} - -FText -FHoudiniOutputDetails::GetOutputTooltip(UHoudiniOutput* InOutput) -{ - // TODO - return FText(); -} - - -const FSlateBrush * -FHoudiniOutputDetails::GetThumbnailBorder(UObject* Mesh) const -{ - TSharedPtr ThumbnailBorder = OutputObjectThumbnailBorders[Mesh]; - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - - -const FSlateBrush * -FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(UObject* OutputObject, int32 MaterialIdx) const -{ - if (!OutputObject) - return nullptr; - - TPairInitializer Pair(OutputObject, MaterialIdx); - TSharedPtr ThumbnailBorder = MaterialInterfaceThumbnailBorders[Pair]; - - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} - -/* -const FSlateBrush * -FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx) const -{ - if (!Landscape) - return nullptr; - - TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); - TSharedPtr< SBorder > ThumbnailBorder = LandscapeMaterialInterfaceThumbnailBorders[Pair]; - - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); -} -*/ - -FReply -FHoudiniOutputDetails::OnThumbnailDoubleClick( - const FGeometry & InMyGeometry, - const FPointerEvent & InMouseEvent, UObject * Object) -{ - if (Object && GEditor) - GEditor->EditObject(Object); - - return FReply::Handled(); -} - -/* -FReply -FHoudiniOutputDetails::OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject) -{ - if (HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill()) - { - FHoudiniPackageParams PackageParms; - - - FHoudiniEngineBakeUtils::BakeStaticMesh(HoudiniAssetComponent, GeoPartObject, StaticMesh, PackageParms); - // TODO: Bake the SM - - - // We need to locate corresponding geo part object in component. - const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(StaticMesh); - - // (void)FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( - // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); - - } - - return FReply::Handled(); -} -*/ - -bool -FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver(const UObject * InObject) const -{ - return (InObject && InObject->IsA(UMaterialInterface::StaticClass())); -} - - -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - UStaticMesh * StaticMesh, - UHoudiniOutput * HoudiniOutput, - int32 MaterialIdx) -{ - FReply RetValue = FReply::Handled(); - if (!StaticMesh || StaticMesh->IsPendingKill()) - return RetValue; - - if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) - return RetValue; - - // Retrieve material interface which is being replaced. - UMaterialInterface * MaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; - if (!MaterialInterface) - return RetValue; - - // Find the string corresponding to the material that is being replaced - const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); - if (!FoundString ) - { - // This material was not replaced, no need to reset it - return RetValue; - } - - // This material has been replaced previously. - FString MaterialString = *FoundString; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), HoudiniOutput); - - // Remove the replacement - HoudiniOutput->Modify(); - HoudiniOutput->GetReplacementMaterials().Remove(MaterialString); - - bool bViewportNeedsUpdate = true; - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - UMaterialInterface * const * FoundMat = HoudiniOutput->GetAssignementMaterials().Find(MaterialString); - if (FoundMat && (*FoundMat)) - AssignMaterial = *FoundMat; - - // Replace material on static mesh. - StaticMesh->Modify(); - StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = AssignMaterial; - - // Replace the material on any component (SMC/ISMC) that uses the above SM - // TODO: ?? Replace for all? - for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) - { - // Only look at MeshComponents - UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); - if (!SMC) - continue; - - if (SMC->GetStaticMesh() != StaticMesh) - continue; - - SMC->Modify(); - SMC->SetMaterial(MaterialIdx, AssignMaterial); - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - return RetValue; -} - -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - ALandscapeProxy* InLandscape, - UHoudiniOutput * InHoudiniOutput, - int32 InMaterialIdx) -{ - FReply RetValue = FReply::Handled(); - if (!InLandscape || InLandscape->IsPendingKill()) - return RetValue; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * MaterialInterface = InMaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); - UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - - // Find the string corresponding to the material that is being replaced - const FString* FoundString = InHoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); - if (!FoundString) - { - // This material was not replaced, no need to reset it - return RetValue; - } - - // This material has been replaced previously. - FString MaterialString = *FoundString; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), InHoudiniOutput); - - // Remove the replacement - InHoudiniOutput->Modify(); - InHoudiniOutput->GetReplacementMaterials().Remove(MaterialString); - - bool bViewportNeedsUpdate = true; - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - UMaterialInterface * const * FoundMat = InHoudiniOutput->GetAssignementMaterials().Find(MaterialString); - if (FoundMat && (*FoundMat)) - AssignMaterial = *FoundMat; - - // Replace material on Landscape - InLandscape->Modify(); - if (InMaterialIdx == 0) - InLandscape->LandscapeMaterial = AssignMaterial; - else - InLandscape->LandscapeHoleMaterial = AssignMaterial; - - InLandscape->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); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - */ - - FHoudiniEngineUtils::UpdateEditorProperties(InHoudiniOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - return RetValue; -} -/* -FReply -FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( - ALandscapeProxy * Landscape, UHoudiniOutput * InOutput, int32 MaterialIdx) -{ - bool bViewportNeedsUpdate = false; - - // TODO: Handle me! - for (TArray< UHoudiniAssetComponent * >::TIterator - IterComponents(HoudiniAssetComponents); IterComponents; ++IterComponents) - { - UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; - if (!HoudiniAssetComponent) - continue; - - TWeakObjectPtr* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find(*HoudiniGeoPartObject); - if (!FoundLandscapePtr) - continue; - - ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get(); - if (!FoundLandscape || !FoundLandscape->IsValidLowLevel()) - continue; - - if (FoundLandscape != Landscape) - continue; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); - UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); - - bool bMaterialRestored = false; - FString MaterialShopName; - if (!HoudiniAssetComponent->GetReplacementMaterialShopName(*HoudiniGeoPartObject, MaterialInterface, MaterialShopName)) - { - // This material was not replaced so there's no need to reset it - continue; - } - - // Remove the replacement - HoudiniAssetComponent->RemoveReplacementMaterial(*HoudiniGeoPartObject, MaterialShopName); - - // Try to find the original assignment, if not, we'll use the default material - UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial(MaterialShopName); - if (AssignedMaterial) - MaterialInterfaceReplacement = AssignedMaterial; - - // Replace material on the landscape - Landscape->Modify(); - - if (MaterialIdx == 0) - Landscape->LandscapeMaterial = MaterialInterfaceReplacement; - else - Landscape->LandscapeHoleMaterial = MaterialInterfaceReplacement; - - //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); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - - HoudiniAssetComponent->UpdateEditorProperties(false); - bViewportNeedsUpdate = true; - } - - if (GEditor && bViewportNeedsUpdate) - { - GEditor->RedrawAllViewports(); - } - - return FReply::Handled(); -} -*/ - -void -FHoudiniOutputDetails::OnBrowseTo(UObject* InObject) -{ - if (GEditor) - { - TArray Objects; - Objects.Add(InObject); - GEditor->SyncBrowserToObjects(Objects); - } -} - -TSharedRef -FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent( - UMaterialInterface* MaterialInterface, - UObject* OutputObject, - UHoudiniOutput* InOutput, - int32 MaterialIdx) -{ - TArray AllowedClasses; - AllowedClasses.Add(UMaterialInterface::StaticClass()); - - TArray NewAssetFactories; - - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(MaterialInterface), - true, - AllowedClasses, - NewAssetFactories, - OnShouldFilterMaterialInterface, - FOnAssetSelected::CreateSP( - this, &FHoudiniOutputDetails::OnMaterialInterfaceSelected, OutputObject, InOutput, MaterialIdx), - FSimpleDelegate::CreateSP( - this, &FHoudiniOutputDetails::CloseMaterialInterfaceComboButton)); -} - - -void -FHoudiniOutputDetails::CloseMaterialInterfaceComboButton() -{ - -} - -void -FHoudiniOutputDetails::OnMaterialInterfaceDropped( - UObject * InObject, - UStaticMesh * StaticMesh, - UHoudiniOutput * HoudiniOutput, - int32 MaterialIdx) -{ - UMaterialInterface * MaterialInterface = Cast(InObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - return; - - if (!StaticMesh || StaticMesh->IsPendingKill()) - return; - - if (!StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx)) - return; - - bool bViewportNeedsUpdate = false; - - // Retrieve material interface which is being replaced. - UMaterialInterface * OldMaterialInterface = StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface; - if (OldMaterialInterface == MaterialInterface) - return; - - // Find the string corresponding to the material that is being replaced - FString MaterialString = FString(); - const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been replaced previously. - MaterialString = *FoundString; - } - else - { - // We have no previous replacement for this material, - // see if we can find it the material assignment list. - FoundString = HoudiniOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been assigned previously. - MaterialString = *FoundString; - } - else - { - UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - if (OldMaterialInterface == DefaultMaterial) - { - // This is replacement for default material. - MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - } - else - { - // External Material? - MaterialString = OldMaterialInterface->GetName(); - } - } - } - - if (MaterialString.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), HoudiniOutput); - - // Add a new material replacement entry. - HoudiniOutput->Modify(); - HoudiniOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); - - // Replace material on static mesh. - StaticMesh->Modify(); - StaticMesh->StaticMaterials[MaterialIdx].MaterialInterface = MaterialInterface; - - // Replace the material on any component (SMC/ISMC) that uses the above SM - for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) - { - // Only look at MeshComponents - UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); - if (SMC && !SMC->IsPendingKill()) - { - if (SMC->GetStaticMesh() == StaticMesh) - { - SMC->Modify(); - SMC->SetMaterial(MaterialIdx, MaterialInterface); - } - } - else - { - UStaticMesh* SM = Cast(OutputObject.Value.OutputObject); - if (SM && !SM->IsPendingKill()) - { - SM->Modify(); - SM->SetMaterial(MaterialIdx, MaterialInterface); - } - } - - - - } - - FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); - - /* - if(GUnrealEd) - GUnrealEd->UpdateFloatingPropertyWindows(); -*/ - if (GEditor) - GEditor->RedrawAllViewports(); -} - -// Delegate used when a valid material has been drag and dropped on a landscape. -void -FHoudiniOutputDetails::OnMaterialInterfaceDropped( - UObject* InDroppedObject, - ALandscapeProxy* InLandscape, - UHoudiniOutput* InOutput, - int32 MaterialIdx) -{ - UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >(InDroppedObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) - return; - - if (!InLandscape || InLandscape->IsPendingKill()) - return; - - bool bViewportNeedsUpdate = false; - - // Retrieve the material interface which is being replaced. - UMaterialInterface * OldMaterialInterface = MaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); - if (OldMaterialInterface == MaterialInterface) - return; - - // Find the string corresponding to the material that is being replaced - FString MaterialString = FString(); - const FString* FoundString = InOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been replaced previously. - MaterialString = *FoundString; - } - else - { - // We have no previous replacement for this material, - // see if we can find it the material assignment list. - FoundString = InOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); - if (FoundString) - { - // This material has been assigned previously. - MaterialString = *FoundString; - } - else - { - UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); - if (OldMaterialInterface == DefaultMaterial) - { - // This is replacement for default material. - MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; - } - else - { - // External Material? - if (OldMaterialInterface && !OldMaterialInterface->IsPendingKill()) - MaterialString = OldMaterialInterface->GetName(); - } - } - } - - if (MaterialString.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_EDITOR), - LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), InOutput); - - // Add a new material replacement entry. - InOutput->Modify(); - InOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); - - // Replace material on the landscape - InLandscape->Modify(); - - if (MaterialIdx == 0) - InLandscape->LandscapeMaterial = MaterialInterface; - else - InLandscape->LandscapeHoleMaterial = MaterialInterface; - - // Update the landscape components Material instances - InLandscape->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 >(InLandscape->GetClass(), (MaterialIdx == 0) ? TEXT("LandscapeMaterial") : TEXT("LandscapeHoleMaterial")); - if (FoundProperty) - { - FPropertyChangedEvent PropChanged(FoundProperty, EPropertyChangeType::ValueSet); - InLandscape->PostEditChangeProperty(PropChanged); - } - else - { - // The only way to update the material for now is to recook/recreate the landscape... - HoudiniAssetComponent->StartTaskAssetCookingManual(); - } - */ - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - - if (GEditor) - GEditor->RedrawAllViewports(); -} - -void -FHoudiniOutputDetails::OnMaterialInterfaceSelected( - const FAssetData & AssetData, - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - TPairInitializer Pair(OutputObject, MaterialIdx); - TSharedPtr AssetComboButton = MaterialInterfaceComboButtons[Pair]; - if (AssetComboButton.IsValid()) - { - AssetComboButton->SetIsOpen(false); - - UObject * Object = AssetData.GetAsset(); - - UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); - } - - ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } - } -} - -void -FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - if (!OutputObject || OutputObject->IsPendingKill()) - return; - - if (!InOutput || InOutput->IsPendingKill()) - return; - - if (GEditor) - { - TArray CBSelections; - GEditor->GetContentBrowserSelections(CBSelections); - - // Get the first selected material object - UObject* Object = nullptr; - for (auto & CurAssetData : CBSelections) - { - if (CurAssetData.AssetClass != UMaterial::StaticClass()->GetFName() && - CurAssetData.AssetClass != UMaterialInstance::StaticClass()->GetFName() && - CurAssetData.AssetClass != UMaterialInstanceConstant::StaticClass()->GetFName()) - continue; - - Object = CurAssetData.GetAsset(); - break; - } - - if (Object && !Object->IsPendingKill()) - { - UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); - } - - ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) - { - return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } - } - } -} - -void -FHoudiniOutputDetails::CreateInstancerOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput || InOutput->IsPendingKill()) - return; - - // Do not display instancer UI for one-instance instancers - bool OnlyOneInstanceInstancers = true; - for (auto& Iter : InOutput->GetInstancedOutputs()) - { - FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); - if (CurInstanceOutput.OriginalTransforms.Num() <= 1) - continue; - - OnlyOneInstanceInstancers = false; - break; - } - - // This output only has one-instance instancers (SMC), no need to display the instancer UI. - if (OnlyOneInstanceInstancers) - return; - - // Classes allowed for instance variations. - const TArray AllowedClasses = - { - UStaticMesh::StaticClass(), USkeletalMesh::StaticClass(), - AActor::StaticClass(), UBlueprint::StaticClass(), - UFXSystemAsset::StaticClass(), USoundBase::StaticClass() - }; - - // Classes not allowed for instances variations (useless?) - TArray DisallowedClasses = - { - UClass::StaticClass(), ULevel::StaticClass(), - UMaterial::StaticClass(), UTexture::StaticClass() - }; - - IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); - TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); - - // Lambda for adding new variation objects - auto AddObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) - { - // TODO: undo/redo? - InOutputToUpdate.VariationObjects.Insert(InObject, AtIndex); - InOutputToUpdate.VariationTransformOffsets.Insert(FTransform::Identity, AtIndex); - FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for adding new geometry input objects - auto RemoveObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex) - { - // Also keep one instance object - if (AtIndex < 0 || AtIndex >= InOutputToUpdate.VariationObjects.Num()) - return; - - if (InOutputToUpdate.VariationObjects.Num() == 1) - return; - - // TODO: undo/redo? - InOutputToUpdate.VariationObjects.RemoveAt(AtIndex); - InOutputToUpdate.VariationTransformOffsets.RemoveAt( AtIndex); - FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for updating a variation - auto SetObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) - { - if (!InOutputToUpdate.VariationObjects.IsValidIndex(AtIndex)) - return; - - InOutputToUpdate.VariationObjects[AtIndex] = InObject; - - InOutputToUpdate.MarkChanged(true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Lambda for changing the transform offset values - auto ChangeTransformOffsetAt = [InOutput]( - FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, - const float& Value, const int32& PosRotScaleIndex, const int32& XYZIndex) - { - bool bChanged = InOutputToUpdate.SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); - if (!bChanged) - return; - - InOutputToUpdate.MarkChanged(true); - - if (GEditor) - GEditor->RedrawAllViewports(); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - }; - - // Get this output's OutputObject - const TMap& OutputObjects = InOutput->GetOutputObjects(); - - // Iterate on all of the output's HGPO - for (const FHoudiniGeoPartObject& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) - { - // Not an instancer, skip - if (CurHGPO.Type != EHoudiniPartType::Instancer) - continue; - - // Get the label for that instancer - FString InstancerLabel = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); - if (CurHGPO.bHasCustomPartName) - InstancerLabel = CurHGPO.PartName; - - TSharedRef InstancerVerticalBox = SNew(SVerticalBox); - TSharedPtr InstancerHorizontalBox = nullptr; - - // Create a new Group for that instancer - IDetailGroup& InstancerGroup = HouOutputCategory.AddGroup(FName(*InstancerLabel), FText::FromString(InstancerLabel)); - - // Now iterate and display the instance outputs that matches this HGPO - for (auto& Iter : InOutput->GetInstancedOutputs()) - { - FHoudiniOutputObjectIdentifier& CurOutputObjectIdentifier = Iter.Key; - if (!CurOutputObjectIdentifier.Matches(CurHGPO)) - continue; - - FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); - - // Dont display instancer UI for one-instance instancers (SMC) - if (CurInstanceOutput.OriginalTransforms.Num() <= 1) - continue; - - for( int32 VariationIdx = 0; VariationIdx < CurInstanceOutput.VariationObjects.Num(); VariationIdx++ ) - { - UObject * InstancedObject = CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous(); - if ( !InstancedObject || InstancedObject->IsPendingKill() ) - { - HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); - continue; - } - - // Create thumbnail for this object. - TSharedPtr VariationThumbnail = - MakeShareable(new FAssetThumbnail(InstancedObject, 64, 64, AssetThumbnailPool)); - TSharedRef PickerVerticalBox = SNew(SVerticalBox); - TSharedPtr PickerHorizontalBox = nullptr; - TSharedPtr VariationThumbnailBorder; - - // For the variation name, reuse the instancer label and append the variation index if we have more than one variation - FString InstanceOutputLabel = InstancerLabel; - if(CurInstanceOutput.VariationObjects.Num() > 1) - InstanceOutputLabel += TEXT(" [") + FString::FromInt(VariationIdx) + TEXT("]"); - - IDetailGroup* DetailGroup = &InstancerGroup; - if (CurInstanceOutput.VariationObjects.Num() > 1) - { - // If we have more than one variation, add a new group for each variation - DetailGroup = &InstancerGroup.AddGroup(FName(*InstanceOutputLabel), FText::FromString(InstanceOutputLabel), true); - } - - // See if we can find the corresponding component to get its type - FString InstancerType = TEXT("(Instancer)"); - FHoudiniOutputObjectIdentifier CurVariationIdentifier = CurOutputObjectIdentifier; - CurVariationIdentifier.SplitIdentifier += TEXT("_") + FString::FromInt(VariationIdx); - const FHoudiniOutputObject* VariationOutputObject = OutputObjects.Find(CurVariationIdentifier); - if(VariationOutputObject) - InstancerType = FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(VariationOutputObject->OutputComponent); - - DetailGroup->AddWidgetRow() - .NameContent() - [ - //SNew(SSpacer) - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(InstancerType)) - //.Size(FVector2D(250, 64)) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - PickerVerticalBox - ]; - - // Add an asset drop target - PickerVerticalBox->AddSlot() - .Padding(0, 2) - .AutoHeight() - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop(SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( - [DisallowedClasses](const UObject* Obj) - { - for (auto Klass : DisallowedClasses) - { - if (Obj && Obj->IsA(Klass)) - return false; - } - return true; - }) - ) - .OnAssetDropped_Lambda([&CurInstanceOutput, VariationIdx, SetObjectAt](UObject* InObject) - { - return SetObjectAt(CurInstanceOutput, VariationIdx, InObject); - }) - [ - SAssignNew(PickerHorizontalBox, SHorizontalBox) - ] - ]; - - PickerHorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() - [ - SAssignNew(VariationThumbnailBorder, SBorder) - .Padding( 5.0f ) - .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, InstancedObject) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - .ToolTipText(FText::FromString(InstancedObject->GetPathName())) - [ - VariationThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - TWeakPtr WeakVariationThumbnailBorder(VariationThumbnailBorder); - VariationThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush *>::Create( - TAttribute::FGetter::CreateLambda([WeakVariationThumbnailBorder]() - { - TSharedPtr ThumbnailBorder = WeakVariationThumbnailBorder.Pin(); - if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ))); - - PickerHorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 28.0f, 0.0f, 28.0f) - [ - PropertyCustomizationHelpers::MakeAddButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, AddObjectAt]() - { - UObject* ObjToAdd = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? - CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() - : nullptr; - - return AddObjectAt(CurInstanceOutput, VariationIdx, ObjToAdd); - }), - LOCTEXT("AddAnotherInstanceToolTip", "Add Another Instance")) - ]; - - PickerHorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 28.0f, 4.0f, 28.0f ) - [ - PropertyCustomizationHelpers::MakeRemoveButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, RemoveObjectAt]() - { - return RemoveObjectAt(CurInstanceOutput, VariationIdx); - }), - LOCTEXT("RemoveLastInstanceToolTip", "Remove Last Instance")) - ]; - - TSharedPtr AssetComboButton; - TSharedPtr ButtonBox; - PickerHorizontalBox->AddSlot() - .FillWidth(1.0f) - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - .HAlign(HAlign_Fill) - [ - SAssignNew(ButtonBox, SHorizontalBox) - +SHorizontalBox::Slot() - [ - SAssignNew(AssetComboButton, SComboButton) - //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) - /* TODO: Update UI - .OnMenuOpenChanged( FOnIsOpenChanged::CreateUObject( - &InParam, &UHoudiniAssetInstanceInput::ChangedStaticMeshComboButton, - CurInstanceOutput, InstOutIdx, VariationIdx ) ) - */ - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromString(InstancedObject->GetName())) - ] - ] - ] - ]; - - // Create asset picker for this combo button. - { - TWeakPtr WeakAssetComboButton(AssetComboButton); - TArray NewAssetFactories; - TSharedRef PropertyMenuAssetPicker = PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(InstancedObject), - true, - AllowedClasses, - DisallowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda( - [&CurInstanceOutput, VariationIdx, SetObjectAt, WeakAssetComboButton](const FAssetData& AssetData) - { - TSharedPtr AssetComboButtonPtr = WeakAssetComboButton.Pin(); - if (AssetComboButtonPtr.IsValid()) - { - AssetComboButtonPtr->SetIsOpen(false); - UObject * Object = AssetData.GetAsset(); - SetObjectAt(CurInstanceOutput, VariationIdx, Object); - } - } - ), - // Nothing to do on close - FSimpleDelegate::CreateLambda([](){}) - ); - - AssetComboButton->SetMenuContent(PropertyMenuAssetPicker); - } - - // Create tooltip. - FFormatNamedArguments Args; - Args.Add(TEXT("Asset"), FText::FromString(InstancedObject->GetName())); - FText StaticMeshTooltip = - FText::Format(LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args); - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - PropertyCustomizationHelpers::MakeBrowseButton( - FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx]() - { - UObject* InputObject = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? - CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() - : nullptr; - - if (GEditor && InputObject) - { - TArray Objects; - Objects.Add(InputObject); - GEditor->SyncBrowserToObjects(Objects); - } - }), - TAttribute< FText >( StaticMeshTooltip ) ) - ]; - - ButtonBox->AddSlot() - .AutoWidth() - .Padding(2.0f, 0.0f ) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT( "ResetToBase", "Reset to default static mesh")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([SetObjectAt, &CurInstanceOutput, VariationIdx]() - { - SetObjectAt(CurInstanceOutput, VariationIdx, CurInstanceOutput.OriginalObject.LoadSynchronous()); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - - - // Get Visibility of reset buttons - bool bResetButtonVisiblePosition = false; - bool bResetButtonVisibleRotation = false; - bool bResetButtonVisibleScale = false; - - FTransform CurTransform = CurInstanceOutput.VariationTransformOffsets[VariationIdx]; - - if (CurTransform.GetLocation() != FVector::ZeroVector) - bResetButtonVisiblePosition = true; - - FRotator Rotator = CurTransform.Rotator(); - if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) - bResetButtonVisibleRotation = true; - - if (CurTransform.GetScale3D() != FVector::OneVector) - bResetButtonVisibleScale = true; - - auto ChangeTransformOffsetUniformlyAt = [ChangeTransformOffsetAt, VariationIdx, &CurInstanceOutput](const float& Val, const int32& PosRotScaleIndex) - { - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 0); - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 1); - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 2); - }; - - TSharedRef OffsetVerticalBox = SNew(SVerticalBox); - FText LabelPositionText = LOCTEXT("HoudiniPositionOffset", "Position Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelPositionText) - .ToolTipText(LabelPositionText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .AllowSpin(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 0); } - ))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 1); } - ))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 2); } - ))) - .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 0); }) - .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 1); }) - .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 2); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("InstancerOutputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, CurInstanceOutput, InOutput]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 0); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - ]; - - FText LabelRotationText = LOCTEXT("HoudiniRotationOffset", "Rotation Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelRotationText) - .ToolTipText(LabelRotationText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SRotatorInputBox) - .AllowSpin(true) - .bColorAxisLabels(true) - .Roll(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 0); } - ))) - .Pitch(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 1); } - ))) - .Yaw(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 2); } - ))) - .OnRollCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 0); }) - .OnPitchCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 1); }) - .OnYawCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) - { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 2); }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button (not visible) - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("GenericLock")) - ] - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() - { - ChangeTransformOffsetUniformlyAt(0.0f, 1); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - ]; - - FText LabelScaleText = LOCTEXT("HoudiniScaleOffset", "Scale Offset"); - DetailGroup->AddWidgetRow() - .NameContent() - [ - SNew(STextBlock) - .Text(LabelScaleText) - .ToolTipText(LabelScaleText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .ValueContent() - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .X(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 0); } - ))) - .Y(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 1); } - ))) - .Z(TAttribute>::Create( - TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() - { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 2); } - ))) - .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 0); - }) - .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 1); - }) - .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) - { - if (CurInstanceOutput.IsUnformScaleLocked()) - ChangeTransformOffsetUniformlyAt(Val, 2); - else - ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 2); - }) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Right) - [ - // Lock Button - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("InstancerOutputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the output object maintains its shape in each direction when scaled")) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(CurInstanceOutput.IsUnformScaleLocked() ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([&CurInstanceOutput, InOutput]() - { - CurInstanceOutput.SwitchUniformScaleLock(); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - // Reset Button - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) - .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() - { - ChangeTransformOffsetUniformlyAt(1.0f, 2); - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); - }) - ] - ] - /* - // TODO: Add support for this back - + SHorizontalBox::Slot().AutoWidth() - [ - // Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered - SNew(SCheckBox) - .Style(FEditorStyle::Get(), "TransparentCheckBox") - .ToolTipText(LOCTEXT("PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled")) - *//* - .OnCheckStateChanged(FOnCheckStateChanged::CreateLambda([=](ECheckBoxState NewState) - { - if ( MyParam.IsValid() && InputFieldPtr.IsValid() ) - MyParam->CheckStateChanged( NewState == ECheckBoxState::Checked, InputFieldPtr.Get(), VariationIdx ); - })) - .IsChecked( TAttribute< ECheckBoxState >::Create( - TAttribute::FGetter::CreateLambda( [=]() - { - if (InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly(VariationIdx)) - return ECheckBoxState::Checked; - return ECheckBoxState::Unchecked; - } - ))) - *//* - [ - SNew(SImage) - *//*.Image(TAttribute::Create( - TAttribute::FGetter::CreateLambda( [=]() - { - if ( InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly( VariationIdx ) ) - { - return FEditorStyle::GetBrush( TEXT( "GenericLock" ) ); - } - return FEditorStyle::GetBrush( TEXT( "GenericUnlock" ) ); - } - ))) - *//* - .ColorAndOpacity( FSlateColor::UseForeground() ) - ] - ] - */ - ]; - } - } - } -} - -/* -void -FHoudiniOutputDetails::OnMaterialInterfaceSelected( - const FAssetData & AssetData, - ALandscapeProxy* Landscape, - UHoudiniOutput * InOutput, - int32 MaterialIdx) -{ - TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); - TSharedPtr< SComboButton > AssetComboButton = LandscapeMaterialInterfaceComboButtons[Pair]; - if (AssetComboButton.IsValid()) - { - AssetComboButton->SetIsOpen(false); - - UObject * Object = AssetData.GetAsset(); - OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); - } -} -*/ - -void -FHoudiniOutputDetails::CreateDefaultOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput) -{ - if (!InOutput) - return; - - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); - - // TODO - // This is just a temporary placeholder displaying name/output type - { - FString OutputNameStr = InOutput->GetName(); - FText OutputTooltip = GetOutputTooltip(InOutput); - - // Create a new detail row - // Name - FText OutputNameTxt = GetOutputDebugName(InOutput); - FDetailWidgetRow & Row = HouOutputCategory.AddCustomRow(FText::GetEmpty()); - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(OutputNameTxt) - .ToolTipText(OutputTooltip) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - - // Value - FText OutputTypeTxt = GetOutputDebugDescription(InOutput); - Row.ValueWidget.Widget = - SNew(STextBlock) - .Text(OutputTypeTxt) - .ToolTipText(OutputTooltip) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - } -} - -void -FHoudiniOutputDetails::OnBakeOutputObject( - const FString& InBakeName, - UObject * BakedOutputObject, - const FHoudiniOutputObjectIdentifier & OutputIdentifier, - const FHoudiniOutputObject& InOutputObject, - const FHoudiniGeoPartObject & HGPO, - const UObject* OutputOwner, - const FString & HoudiniAssetName, - const FString & BakeFolder, - const FString & TempCookFolder, - const EHoudiniOutputType & Type, - const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, - const TArray& InAllOutputs) -{ - if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) - return; - - // Fill in the package params - FHoudiniPackageParams PackageParams; - // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. - // The resolver is then also configured with the package params for subsequent resolving (level_path etc) - FHoudiniAttributeResolver Resolver; - // Determine the relevant WorldContext based on the output owner - UWorld* WorldContext = OutputOwner ? OutputOwner->GetWorld() : GWorld; - const UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(OutputOwner); - check(IsValid(HAC)); - const bool bAutomaticallySetAttemptToLoadMissingPackages = true; - const bool bSkipObjectNameResolutionAndUseDefault = !InBakeName.IsEmpty(); // If InBakeName is set use it as is for the object name - const bool bSkipBakeFolderResolutionAndUseDefault = false; - FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - WorldContext, HAC, OutputIdentifier, InOutputObject, BakedOutputObject->GetName(), - HoudiniAssetName, PackageParams, Resolver, - BakeFolder, EPackageReplaceMode::ReplaceExistingAssets, - bAutomaticallySetAttemptToLoadMissingPackages, bSkipObjectNameResolutionAndUseDefault, - bSkipBakeFolderResolutionAndUseDefault); - - switch (Type) - { - case EHoudiniOutputType::Mesh: - { - UStaticMesh* StaticMesh = Cast(BakedOutputObject); - if (StaticMesh) - { - FDirectoryPath TempCookFolderPath; - TempCookFolderPath.Path = TempCookFolder; - TMap AlreadyBakedMaterialsMap; - UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( - StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath, AlreadyBakedMaterialsMap); - } - } - break; - case EHoudiniOutputType::Curve: - { - USplineComponent* SplineComponent = Cast(BakedOutputObject); - if (SplineComponent) - { - AActor* BakedActor; - USplineComponent* BakedSplineComponent; - FHoudiniEngineBakeUtils::BakeCurve(SplineComponent, GWorld->GetCurrentLevel(), PackageParams, BakedActor, BakedSplineComponent); - } - } - break; - case EHoudiniOutputType::Landscape: - { - ALandscapeProxy* Landscape = Cast(BakedOutputObject); - if (Landscape) - { - FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType); - } - } - break; - } -} - -FReply -FHoudiniOutputDetails::OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput) -{ - // TODO: Actually refine only the selected ProxyMesh - // For now, refine all the selection - FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true, true); - - FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); - return FReply::Handled(); -} - -void -FHoudiniOutputDetails::OnBakeNameCommitted( - const FText& Val, ETextCommit::Type TextCommitType, - UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - if (!InOutput) - return; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - - if (!FoundOutputObject) - return; - - FoundOutputObject->BakeName = Val.ToString(); -} - -void -FHoudiniOutputDetails::OnRevertBakeNameToDefault(UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier & InIdentifier) -{ - if (!InOutput) - return; - - TMap& OutputObjects = InOutput->GetOutputObjects(); - FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - - if (!FoundOutputObject) - return; - - FoundOutputObject->BakeName = FString(); -} -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniOutputDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniAssetComponentDetails.h" +#include "HoudiniMeshTranslator.h" +#include "HoudiniInstanceTranslator.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAsset.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniStaticMesh.h" +#include "HoudiniEngineCommands.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailGroup.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SRotatorInputBox.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Text/STextBlock.h" +#include "Editor/UnrealEd/Public/AssetThumbnail.h" +#include "SAssetDropTarget.h" +#include "Engine/StaticMesh.h" +#include "Components/SplineComponent.h" +#include "Materials/Material.h" +#include "Materials/MaterialInstance.h" +#include "Materials/MaterialInstanceConstant.h" +#include "Sound/SoundBase.h" +#include "Engine/SkeletalMesh.h" +#include "Particles/ParticleSystem.h" +//#include "Landscape.h" +#include "LandscapeProxy.h" +#include "ScopedTransaction.h" +#include "PhysicsEngine/BodySetup.h" +#include "UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +void +FHoudiniOutputDetails::CreateWidget( + IDetailCategoryBuilder& HouOutputCategory, + TArray InOutputs) +{ + if (InOutputs.Num() <= 0) + return; + + UHoudiniOutput* MainOutput = InOutputs[0]; + if (!IsValid(MainOutput)) + return; + + // Don't create UI for editable curve. + if (MainOutput->IsEditableNode() && MainOutput->GetType() == EHoudiniOutputType::Curve) + return; + + // Get thumbnail pool for this builder. + TSharedPtr AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); + + switch (MainOutput->GetType()) + { + case EHoudiniOutputType::Mesh: + { + FHoudiniOutputDetails::CreateMeshOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Landscape: + { + FHoudiniOutputDetails::CreateLandscapeOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Instancer: + { + FHoudiniOutputDetails::CreateInstancerOutputWidget(HouOutputCategory, MainOutput); + break; + } + + case EHoudiniOutputType::Curve: + { + FHoudiniOutputDetails::CreateCurveOutputWidget(HouOutputCategory, MainOutput); + break; + } + case EHoudiniOutputType::Skeletal: + default: + { + FHoudiniOutputDetails::CreateDefaultOutputWidget(HouOutputCategory, MainOutput); + break; + } + } +} + + +void +FHoudiniOutputDetails::CreateLandscapeOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // Go through this output's objects + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& CurrentOutputObj : OutputObjects) + { + FHoudiniOutputObjectIdentifier& Identifier = CurrentOutputObj.Key; + const FHoudiniGeoPartObject *HGPO = nullptr; + for (const auto& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!Identifier.Matches(CurHGPO)) + continue; + + HGPO = &CurHGPO; + break; + } + + if (!HGPO) + continue; + + + if (UHoudiniLandscapePtr* LandscapePointer = Cast(CurrentOutputObj.Value.OutputObject)) + { + CreateLandscapeOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapePointer, Identifier); + } + else if (UHoudiniLandscapeEditLayer* LandscapeLayer = Cast(CurrentOutputObj.Value.OutputObject)) + { + // TODO: Create widget for landscape editlayer output + CreateLandscapeEditLayerOutputWidget_Helper(HouOutputCategory, InOutput, *HGPO, LandscapeLayer, Identifier); + } + + + + } +} + +void +FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + const FHoudiniGeoPartObject& HGPO, + UHoudiniLandscapePtr* LandscapePointer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier) +{ + if (!LandscapePointer || LandscapePointer->IsPendingKill() || !LandscapePointer->LandscapeSoftPtr.IsValid()) + return; + + if (!InOutput || InOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return; + + ALandscapeProxy * Landscape = LandscapePointer->LandscapeSoftPtr.Get(); + if (!Landscape || Landscape->IsPendingKill()) + return; + + // TODO: Get bake base name + FString Label = Landscape->GetName(); + + EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType = LandscapePointer->BakeType; + + // Get thumbnail pool for this builder + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + TArray>* BakeOptionString = FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels(); + + // Create bake mesh name textfield. + IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(Label)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + // Create the thumbnail for the landscape output object. + TSharedPtr< FAssetThumbnail > LandscapeThumbnail = + MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); + + TSharedPtr< SBorder > LandscapeThumbnailBorder; + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(SSpacer) + .Size(FVector2D(250, 64)) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot().Padding(0, 2).AutoHeight() + [ + SNew(SBox).WidthOverride(175) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(LandscapeThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(Landscape->GetPathName())) + [ + LandscapeThumbnail->MakeThumbnailWidget() + ] + ] + ] + + + SHorizontalBox::Slot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SBox).WidthOverride(40.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("Bake", "Bake")) + .IsEnabled(true) + .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() + { + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + if (FoundOutputObject) + { + TArray AllOutputs; + AllOutputs.Reserve(HAC->GetNumOutputs()); + HAC->GetOutputs(AllOutputs); + FHoudiniOutputDetails::OnBakeOutputObject( + FoundOutputObject->BakeName, + Landscape, + OutputIdentifier, + *FoundOutputObject, + HGPO, + HAC, + OwnerActor->GetName(), + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + InOutput->GetType(), + LandscapeOutputBakeType, + AllOutputs); + } + + // TODO: Remove the output landscape if the landscape bake type is Detachment? + return FReply::Handled(); + }) + .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) + ] + ] + + SHorizontalBox::Slot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SBox).WidthOverride(120.f) + [ + SNew(SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + if (SelectType != ESelectInfo::Type::OnMouseClick) + return; + + FString *NewChoiceStr = NewChoice.Get(); + if (!NewChoiceStr) + return; + + if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); + } + else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); + } + else + { + LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); + } + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + [ + SNew(STextBlock) + .Text_Lambda([LandscapePointer]() + { + FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); + return FText::FromString(BakeTypeString); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ] + ] + ]; + + // Store thumbnail for this landscape. + OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); + + // We need to add material box for each the landscape and landscape hole materials + for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) + { + UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + TSharedPtr MaterialThumbnailBorder; + TSharedPtr HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if (MaterialInterface) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + + // Create thumbnail for this material. + TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(STextBlock) + .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + VerticalBox->AddSlot().Padding(0, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) + .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(MaterialThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(MaterialPathName)) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this landscape and material index. + { + TPairInitializer Pair(Landscape, MaterialIdx); + MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + } + + // Combox Box and Button Box + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Combo row + TSharedPtr< SComboButton > AssetComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) + [ + SAssignNew(AssetComboButton, SComboButton) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + // Buttons row + TSharedPtr ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Add use Content Browser selection arrow + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)Landscape, InOutput, MaterialIdx), + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + ]; + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), + TAttribute< FText >(MaterialTooltip)) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + + // Store combo button for this mesh and index. + { + TPairInitializer Pair(Landscape, MaterialIdx); + MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + } + } +} + +void FHoudiniOutputDetails::CreateLandscapeEditLayerOutputWidget_Helper(IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& HGPO, UHoudiniLandscapeEditLayer* LandscapeEditLayer, + const FHoudiniOutputObjectIdentifier& OutputIdentifier) +{ + if (!LandscapeEditLayer || LandscapeEditLayer->IsPendingKill() || !LandscapeEditLayer->LandscapeSoftPtr.IsValid()) + return; + + if (!InOutput || InOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return; + + ALandscapeProxy * Landscape = LandscapeEditLayer->LandscapeSoftPtr.Get(); + if (!Landscape || Landscape->IsPendingKill()) + return; + + const FString Label = Landscape->GetName(); + const FString LayerName = LandscapeEditLayer->LayerName; + + // Get thumbnail pool for this builder + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // Create labels to display the edit layer name. + IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeEditLayerName", "Edit Layer Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(STextBlock) + .Text(FText::AsCultureInvariant(LayerName)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + + // SNew(SHorizontalBox) + // + SHorizontalBox::Slot() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // .FillWidth(1) + // [ + // SNew(SEditableTextBox) + // .Text(FText::FromString(Label)) + // .Font(IDetailLayoutBuilder::GetDetailFont()) + // .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + // .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + // .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) + // { + // FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + // FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + // }) + // ] + // + SHorizontalBox::Slot() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // .AutoWidth() + // [ + // SNew(SButton) + // .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + // .ButtonStyle(FEditorStyle::Get(), "NoBorder") + // .ContentPadding(0) + // .Visibility(EVisibility::Visible) + // .OnClicked_Lambda([InOutput, OutputIdentifier]() + // { + // FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + // return FReply::Handled(); + // }) + // [ + // SNew(SImage) + // .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + // ] + // ] + ]; + + // // Create the thumbnail for the landscape output object. + // TSharedPtr< FAssetThumbnail > LandscapeThumbnail = + // MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool)); + // + // TSharedPtr< SBorder > LandscapeThumbnailBorder; + // TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + // + // LandscapeGrp.AddWidgetRow() + // .NameContent() + // [ + // SNew(SSpacer) + // .Size(FVector2D(250, 64)) + // ] + // .ValueContent() + // .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + // [ + // VerticalBox + // ]; + // + // VerticalBox->AddSlot().Padding(0, 2).AutoHeight() + // [ + // SNew(SBox).WidthOverride(175) + // [ + // SNew(SHorizontalBox) + // + SHorizontalBox::Slot() + // .Padding(0.0f, 0.0f, 2.0f, 0.0f) + // .AutoWidth() + // [ + // SAssignNew(LandscapeThumbnailBorder, SBorder) + // .Padding(5.0f) + // .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)Landscape) + // .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)Landscape) + // [ + // SNew(SBox) + // .WidthOverride(64) + // .HeightOverride(64) + // .ToolTipText(FText::FromString(Landscape->GetPathName())) + // [ + // LandscapeThumbnail->MakeThumbnailWidget() + // ] + // ] + // ] + // + // + SHorizontalBox::Slot() + // .Padding(0.0f, 4.0f, 4.0f, 4.0f) + // .VAlign(VAlign_Center) + // [ + // SNew(SBox).WidthOverride(40.0f) + // [ + // SNew(SButton) + // .VAlign(VAlign_Center) + // .HAlign(HAlign_Center) + // .Text(LOCTEXT("Bake", "Bake")) + // .IsEnabled(true) + // .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() + // { + // FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + // if (FoundOutputObject) + // { + // TArray AllOutputs; + // AllOutputs.Reserve(HAC->GetNumOutputs()); + // HAC->GetOutputs(AllOutputs); + // FHoudiniOutputDetails::OnBakeOutputObject( + // FoundOutputObject->BakeName, + // Landscape, + // OutputIdentifier, + // *FoundOutputObject, + // HGPO, + // HAC, + // OwnerActor->GetName(), + // HAC->BakeFolder.Path, + // HAC->TemporaryCookFolder.Path, + // InOutput->GetType(), + // LandscapeOutputBakeType, + // AllOutputs); + // } + // + // // TODO: Remove the output landscape if the landscape bake type is Detachment? + // return FReply::Handled(); + // }) + // .ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape")) + // ] + // ] + // + SHorizontalBox::Slot() + // .Padding(0.0f, 4.0f, 4.0f, 4.0f) + // .VAlign(VAlign_Center) + // [ + // SNew(SBox).WidthOverride(120.f) + // [ + // SNew(SComboBox>) + // .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels()) + // .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels())[(uint8)LandscapeOutputBakeType]) + // .OnGenerateWidget_Lambda( + // [](TSharedPtr< FString > InItem) + // { + // return SNew(STextBlock).Text(FText::FromString(*InItem)); + // }) + // .OnSelectionChanged_Lambda( + // [LandscapePointer, InOutput](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + // { + // if (SelectType != ESelectInfo::Type::OnMouseClick) + // return; + // + // FString *NewChoiceStr = NewChoice.Get(); + // if (!NewChoiceStr) + // return; + // + // if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::Detachment)) + // { + // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::Detachment); + // } + // else if (*NewChoiceStr == FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(EHoudiniLandscapeOutputBakeType::BakeToImage)) + // { + // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToImage); + // } + // else + // { + // LandscapePointer->SetLandscapeOutputBakeType(EHoudiniLandscapeOutputBakeType::BakeToWorld); + // } + // + // FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + // }) + // [ + // SNew(STextBlock) + // .Text_Lambda([LandscapePointer]() + // { + // FString BakeTypeString = FHoudiniEngineEditorUtils::HoudiniLandscapeOutputBakeTypeToString(LandscapePointer->GetLandscapeOutputBakeType()); + // return FText::FromString(BakeTypeString); + // }) + // .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + // ] + // ] + // ] + // ] + // ]; + // + // // Store thumbnail for this landscape. + // OutputObjectThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder); + // + // // We need to add material box for each the landscape and landscape hole materials + // for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx) + // { + // UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + // TSharedPtr MaterialThumbnailBorder; + // TSharedPtr HorizontalBox = NULL; + // + // FString MaterialName, MaterialPathName; + // if (MaterialInterface) + // { + // MaterialName = MaterialInterface->GetName(); + // MaterialPathName = MaterialInterface->GetPathName(); + // } + // + // // Create thumbnail for this material. + // TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + // MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + // + // VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + // [ + // SNew(STextBlock) + // .Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material")) + // .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + // ]; + // + // VerticalBox->AddSlot().Padding(0, 2) + // [ + // SNew(SAssetDropTarget) + // .OnIsAssetAcceptableForDrop(this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver) + // .OnAssetDropped(this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, Landscape, InOutput, MaterialIdx) + // [ + // SAssignNew(HorizontalBox, SHorizontalBox) + // ] + // ]; + // + // HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + // [ + // SAssignNew(MaterialThumbnailBorder, SBorder) + // .Padding(5.0f) + // .BorderImage(this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)Landscape, MaterialIdx) + // .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + // [ + // SNew(SBox) + // .WidthOverride(64) + // .HeightOverride(64) + // .ToolTipText(FText::FromString(MaterialPathName)) + // [ + // MaterialInterfaceThumbnail->MakeThumbnailWidget() + // ] + // ] + // ]; + // + // // Store thumbnail for this landscape and material index. + // { + // TPairInitializer Pair(Landscape, MaterialIdx); + // MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + // } + // + // // Combox Box and Button Box + // TSharedPtr ComboAndButtonBox; + // HorizontalBox->AddSlot() + // .FillWidth(1.0f) + // .Padding(0.0f, 4.0f, 4.0f, 4.0f) + // .VAlign(VAlign_Center) + // [ + // SAssignNew(ComboAndButtonBox, SVerticalBox) + // ]; + // + // // Combo row + // TSharedPtr< SComboButton > AssetComboButton; + // ComboAndButtonBox->AddSlot().FillHeight(1.0f) + // [ + // SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f) + // [ + // SAssignNew(AssetComboButton, SComboButton) + // //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + // .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + // .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + // .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, MaterialInterface, (UObject*)Landscape, InOutput, MaterialIdx) + // .ContentPadding(2.0f) + // .ButtonContent() + // [ + // SNew(STextBlock) + // .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + // .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + // .Text(FText::FromString(MaterialName)) + // ] + // ] + // ]; + // + // // Buttons row + // TSharedPtr ButtonBox; + // ComboAndButtonBox->AddSlot().FillHeight(1.0f) + // [ + // SAssignNew(ButtonBox, SHorizontalBox) + // ]; + // + // // Add use Content Browser selection arrow + // ButtonBox->AddSlot() + // .AutoWidth() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // [ + // PropertyCustomizationHelpers::MakeUseSelectedButton( + // FSimpleDelegate::CreateSP( + // this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + // (UObject*)Landscape, InOutput, MaterialIdx), + // TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + // ]; + // + // // Create tooltip. + // FFormatNamedArguments Args; + // Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + // FText MaterialTooltip = FText::Format( + // LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + // + // ButtonBox->AddSlot() + // .AutoWidth() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // [ + // PropertyCustomizationHelpers::MakeBrowseButton( + // FSimpleDelegate::CreateSP( + // this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), + // TAttribute< FText >(MaterialTooltip)) + // ]; + // + // ButtonBox->AddSlot() + // .AutoWidth() + // .Padding(2.0f, 0.0f) + // .VAlign(VAlign_Center) + // [ + // SNew(SButton) + // .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + // .ButtonStyle(FEditorStyle::Get(), "NoBorder") + // .ContentPadding(0) + // .Visibility(EVisibility::Visible) + // .OnClicked( this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, Landscape, InOutput, MaterialIdx) + // [ + // SNew(SImage) + // .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + // ] + // ]; + // + // // Store combo button for this mesh and index. + // { + // TPairInitializer Pair(Landscape, MaterialIdx); + // MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + // } + // } +} + +void +FHoudiniOutputDetails::CreateMeshOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent* HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + FString HoudiniAssetName; + if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) + { + HoudiniAssetName = HAC->GetOwner()->GetName(); + } + else if (HAC->GetHoudiniAsset()) + { + HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); + } + else + { + HoudiniAssetName = HAC->GetName(); + } + + // Go through this output's object + int32 OutputObjIdx = 0; + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& IterObject : OutputObjects) + { + UStaticMesh* StaticMesh = Cast(IterObject.Value.OutputObject); + UHoudiniStaticMesh* ProxyMesh = Cast(IterObject.Value.ProxyObject); + + if ((!StaticMesh || StaticMesh->IsPendingKill()) + && (!ProxyMesh || ProxyMesh->IsPendingKill())) + continue; + + FHoudiniOutputObjectIdentifier & OutputIdentifier = IterObject.Key; + + // Find the corresponding HGPO in the output + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!OutputIdentifier.Matches(curHGPO)) + continue; + + HoudiniGeoPartObject = curHGPO; + break; + } + + if (StaticMesh && !StaticMesh->IsPendingKill()) + { + bool bIsProxyMeshCurrent = IterObject.Value.bProxyIsCurrent; + + // If we have a static mesh, alway display its widget even if the proxy is more recent + CreateStaticMeshAndMaterialWidgets( + HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); + } + else + { + // If we only have a proxy mesh, then show the proxy widget + CreateProxyMeshAndMaterialWidgets( + HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject); + } + } +} + +void +FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + int32 OutputObjIdx = 0; + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& IterObject : OutputObjects) + { + FHoudiniOutputObject& CurrentOutputObject = IterObject.Value; + USceneComponent* SplineComponent = Cast(IterObject.Value.OutputComponent); + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + FHoudiniOutputObjectIdentifier& OutputIdentifier = IterObject.Key; + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& curHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + if (!OutputIdentifier.Matches(curHGPO)) + continue; + + HoudiniGeoPartObject = curHGPO; + break; + } + + CreateCurveWidgets(HouOutputCategory, InOutput, SplineComponent, CurrentOutputObject, OutputIdentifier, HoudiniGeoPartObject); + } +} + +void +FHoudiniOutputDetails::CreateCurveWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + USceneComponent* SplineComponent, + FHoudiniOutputObject& OutputObject, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + FHoudiniGeoPartObject& HoudiniGeoPartObject) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // We support Unreal Spline out only for now + USplineComponent* SplineOutput = Cast(SplineComponent); + if (!SplineOutput || SplineOutput->IsPendingKill()) + return; + + UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); + if (!HAC || HAC->IsPendingKill()) + return; + + AActor * OwnerActor = HAC->GetOwner(); + if (!OwnerActor || OwnerActor->IsPendingKill()) + return; + + FHoudiniCurveOutputProperties* OutputProperty = &(OutputObject.CurveOutputProperty); + EHoudiniCurveType OutputCurveType = OutputObject.CurveOutputProperty.CurveType; + + FString Label = SplineComponent->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + //Label += FString("_") + OutputIdentifier.SplitIdentifier; + + FString OutputCurveName = OutputObject.BakeName; + if(OutputCurveName.IsEmpty()) + OutputCurveName = OwnerActor->GetName() + "_" + Label; + + const FText& LabelText = FText::FromString("Unreal Spline"); + + IDetailGroup& CurveOutputGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label), false, false); + + // Bake name row UI + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(OutputObject.BakeName)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + ] + + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("OutputCurveSplineType", "Spline Type")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText_Lambda([SplineOutput, Label, OutputCurveType]() + { + FString ToolTipStr = FString::Printf(TEXT(" curve: %s\n Export type: Unreal Spline\n num points: %d\n curve type: %s\n closed: %s"), + *Label, + SplineOutput->GetNumberOfSplinePoints(), + *FHoudiniEngineEditorUtils::HoudiniCurveTypeToString(OutputCurveType), + SplineOutput->IsClosedLoop() ? *(FString("yes")) : *(FString("no"))); + + return FText::FromString(ToolTipStr); + }) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(STextBlock) + // We support Unreal Spline output only for now... + .Text(LOCTEXT("OutputCurveSplineTypeUnreal", "Unreal Spline")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + + //if (bIsUnrealSpline) + //{ + USplineComponent* UnrealSpline = Cast(SplineComponent); + + // Curve type combo box UI + auto InitialSelectionLambda = [OutputProperty]() + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + { + return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[0]; + } + else + { + return (*FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels())[1]; + } + }; + + TSharedPtr>> UnrealCurveTypeComboBox; + + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(LOCTEXT("OutputCurveUnrealSplinePointType", "Spline Point Type")) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(UnrealCurveTypeComboBox, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetUnrealOutputCurveTypeLabels()) + .InitiallySelectedItem(InitialSelectionLambda()) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [OutputProperty, InOutput, SplineComponent](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + // Set the curve point type locally + USplineComponent* Spline = Cast(SplineComponent); + if (!Spline || Spline->IsPendingKill()) + return; + + FString *NewChoiceStr = NewChoice.Get(); + if (!NewChoiceStr) + return; + + if (*NewChoiceStr == "Linear") + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + return; + + OutputProperty->CurveType = EHoudiniCurveType::Polygon; + + for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) + { + Spline->SetSplinePointType(PtIdx, ESplinePointType::Linear); + } + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + } + else if (*NewChoiceStr == "Curve") + { + if (OutputProperty->CurveType != EHoudiniCurveType::Polygon) + return; + + OutputProperty->CurveType = EHoudiniCurveType::Bezier; + + for (int32 PtIdx = 0; PtIdx < Spline->GetNumberOfSplinePoints(); ++PtIdx) + { + Spline->SetSplinePointType(PtIdx, ESplinePointType::Curve); + } + + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([OutputProperty]() + { + if (OutputProperty->CurveType == EHoudiniCurveType::Polygon) + return FText::FromString(TEXT("Linear")); + else + return FText::FromString(TEXT("Curve")); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + // Add closed curve checkbox UI + TSharedPtr ClosedCheckBox; + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Text(LOCTEXT("OutputCurveUnrealSplineClosed", "Closed")) + ] + .ValueContent() + [ + SAssignNew(ClosedCheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([UnrealSpline, InOutput](ECheckBoxState NewState) + { + if (!UnrealSpline || UnrealSpline->IsPendingKill()) + return; + + UnrealSpline->SetClosedLoop(NewState == ECheckBoxState::Checked); + FHoudiniEngineEditorUtils::ReselectSelectedActors(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }) + .IsChecked_Lambda([UnrealSpline]() + { + if (!UnrealSpline || UnrealSpline->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return UnrealSpline->IsClosedLoop() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + ]; + //} + + // Add Bake Button UI + TSharedPtr BakeButton; + CurveOutputGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + ] + .ValueContent() + [ + SAssignNew(BakeButton, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) + .IsEnabled(true) + .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) + .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName, OutputObject]() + { + TArray AllOutputs; + AllOutputs.Reserve(HAC->GetNumOutputs()); + HAC->GetOutputs(AllOutputs); + FHoudiniOutputDetails::OnBakeOutputObject( + OutputCurveName, + SplineComponent, + OutputIdentifier, + OutputObject, + HoudiniGeoPartObject, + HAC, + OwnerActor->GetName(), + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + InOutput->GetType(), + EHoudiniLandscapeOutputBakeType::InValid, + AllOutputs); + + return FReply::Handled(); + }) + ]; +} + +void +FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UStaticMesh * StaticMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + const bool& bIsProxyMeshCurrent) +{ + if (!StaticMesh || StaticMesh->IsPendingKill()) + return; + + UHoudiniAssetComponent* OwningHAC = Cast(InOutput->GetOuter()); + + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // TODO: GetBakingBaseName! + FString Label = StaticMesh->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + // Create thumbnail for this mesh. + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = + MakeShareable(new FAssetThumbnail(StaticMesh, 64, 64, AssetThumbnailPool)); + TSharedPtr StaticMeshThumbnailBorder; + + TSharedRef VerticalBox = SNew(SVerticalBox); + + IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(BakeName)) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + }) + .ToolTipText( LOCTEXT( "BakeNameTip", "The base name of the baked asset") ) + ] + + +SHorizontalBox::Slot() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + .AutoWidth() + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "RevertNameOverride", "Revert bake name override" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ] + ]; + + // Add details on the SM colliders + EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); + FString MeshLabel = TEXT( "Static Mesh" ); + + // If the Proxy mesh is more recent, indicate it in the details + if (bIsProxyMeshCurrent) + { + MeshLabel += TEXT("\n(unrefined)"); + } + + // Indicate that this mesh is instanced + if (HoudiniGeoPartObject.bIsInstanced) + { + MeshLabel += TEXT("\n(instanced)"); + } + + if (HoudiniGeoPartObject.bIsTemplated) + { + MeshLabel += TEXT("\n(templated)"); + } + + int32 NumSimpleColliders = 0; + if (StaticMesh->GetBodySetup() && !StaticMesh->GetBodySetup()->IsPendingKill()) + NumSimpleColliders = StaticMesh->GetBodySetup()->AggGeom.GetElementCount(); + + if(NumSimpleColliders > 0) + { + MeshLabel += TEXT( "\n(") + FString::FromInt(NumSimpleColliders) + TEXT(" Simple Collider" ); + if (NumSimpleColliders > 1 ) + MeshLabel += TEXT("s"); + MeshLabel += TEXT(")"); + } + else if (SplitType == EHoudiniSplitType::RenderedComplexCollider) + { + MeshLabel += TEXT( "\n(Rendered Complex Collider)" ); + } + else if(SplitType == EHoudiniSplitType::InvisibleComplexCollider ) + { + MeshLabel += TEXT( "\n(Invisible Complex Collider)" ); + } + + if ( StaticMesh->GetNumLODs() > 1 ) + MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)"); + + if (HoudiniGeoPartObject.AllMeshSockets.Num() > 0) + { + if (bIsProxyMeshCurrent) + { + // Proxy is current, show the number of sockets on the HGPO + MeshLabel += TEXT("\n(") + FString::FromInt(HoudiniGeoPartObject.AllMeshSockets.Num()) + TEXT(" sockets)"); + } + else + { + // Show the number of sockets on the SM + MeshLabel += TEXT("\n(") + FString::FromInt(StaticMesh->Sockets.Num()) + TEXT(" sockets)"); + } + } + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew( STextBlock ) + .Text( FText::FromString(MeshLabel) ) + .Font( IDetailLayoutBuilder::GetDetailFont() ) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot() + .Padding( 0, 2 ) + .AutoHeight() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .Padding( 0.0f, 0.0f, 2.0f, 0.0f ) + .AutoWidth() + [ + SAssignNew( StaticMeshThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)StaticMesh ) + .OnMouseDoubleClick( this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *) StaticMesh ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( StaticMesh->GetPathName() ) ) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ] + + +SHorizontalBox::Slot() + .FillWidth( 1.0f ) + .Padding( 0.0f, 4.0f, 4.0f, 4.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SVerticalBox ) + +SVerticalBox::Slot() + [ + SNew( SHorizontalBox ) + +SHorizontalBox::Slot() + .MaxWidth( 80.0f ) + [ + SNew( SButton ) + .VAlign( VAlign_Center ) + .HAlign( HAlign_Center ) + .Text( LOCTEXT( "Bake", "Bake" ) ) + .IsEnabled(true) + .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC, FoundOutputObject]() + { + if (FoundOutputObject) + { + TArray AllOutputs; + FString TempCookFolder; + if (IsValid(OwningHAC)) + { + AllOutputs.Reserve(OwningHAC->GetNumOutputs()); + OwningHAC->GetOutputs(AllOutputs); + + TempCookFolder = OwningHAC->TemporaryCookFolder.Path; + } + FHoudiniOutputDetails::OnBakeOutputObject( + BakeName, + StaticMesh, + OutputIdentifier, + *FoundOutputObject, + HoudiniGeoPartObject, + OwningHAC, + HoudiniAssetName, + BakeFolder, + TempCookFolder, + InOutput->GetType(), + EHoudiniLandscapeOutputBakeType::InValid, + AllOutputs); + } + + return FReply::Handled(); + }) + .ToolTipText( LOCTEXT( "HoudiniStaticMeshBakeButton", "Bake this generated static mesh" ) ) + ] + +SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)StaticMesh), + TAttribute(LOCTEXT("HoudiniStaticMeshBrowseButton", "Browse to this generated static mesh in the content browser"))) + ] + ] + ] + ]; + + // Store thumbnail for this mesh. + OutputObjectThumbnailBorders.Add((UObject*)StaticMesh, StaticMeshThumbnailBorder); + + // We need to add material box for each material present in this static mesh. + auto & StaticMeshMaterials = StaticMesh->GetStaticMaterials(); + for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx ) + { + UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface; + TSharedPtr< SBorder > MaterialThumbnailBorder; + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if ( MaterialInterface && !MaterialInterface->IsPendingKill() + && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + else + { + MaterialInterface = nullptr; + MaterialName = TEXT("Material (invalid)") + FString::FromInt( MaterialIdx ) ; + MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + } + + // Create thumbnail for this material. + TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail = + MakeShareable( new FAssetThumbnail( MaterialInterface, 64, 64, AssetThumbnailPool ) ); + + VerticalBox->AddSlot().Padding( 0, 2 ) + [ + SNew( SAssetDropTarget ) + .OnIsAssetAcceptableForDrop( this, &FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver ) + .OnAssetDropped( + this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx ) + [ + SAssignNew( HorizontalBox, SHorizontalBox ) + ] + ]; + + HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth() + [ + SAssignNew( MaterialThumbnailBorder, SBorder ) + .Padding( 5.0f ) + .BorderImage( + this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject *)StaticMesh, MaterialIdx ) + .OnMouseDoubleClick( + this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface ) + [ + SNew( SBox ) + .WidthOverride( 64 ) + .HeightOverride( 64 ) + .ToolTipText( FText::FromString( MaterialPathName ) ) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this mesh and material index. + { + TPairInitializer Pair( StaticMesh, MaterialIdx ); + MaterialInterfaceThumbnailBorders.Add( Pair, MaterialThumbnailBorder ); + } + + // ComboBox and buttons + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add Combo box + TSharedPtr< SComboButton > AssetComboButton; + ComboAndButtonBox->AddSlot().VAlign(VAlign_Center).FillHeight(1.0f) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().VAlign(VAlign_Center).FillHeight(1.0f) + [ + SAssignNew(AssetComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, + MaterialInterface, (UObject*)StaticMesh, InOutput, MaterialIdx) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + + // Add buttons + TSharedPtr< SHorizontalBox > ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Use CB selection arrow button + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)StaticMesh, InOutput, MaterialIdx), + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser"))) + ]; + + // Browse CB button + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface ), TAttribute< FText >( MaterialTooltip ) ) + ]; + + // Reset button + ButtonBox->AddSlot() + .AutoWidth() + .Padding( 2.0f, 0.0f ) + .VAlign( VAlign_Center ) + [ + SNew( SButton ) + .ToolTipText( LOCTEXT( "ResetToBaseMaterial", "Reset to base material" ) ) + .ButtonStyle( FEditorStyle::Get(), "NoBorder" ) + .ContentPadding( 0 ) + .Visibility( EVisibility::Visible ) + .OnClicked( + this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) + [ + SNew( SImage ) + .Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) ) + ] + ]; + + // Store combo button for this mesh and index. + { + TPairInitializer Pair( StaticMesh, MaterialIdx ); + MaterialInterfaceComboButtons.Add( Pair, AssetComboButton ); + } + } +} + +void +FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UHoudiniStaticMesh * ProxyMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject) +{ + if (!ProxyMesh || ProxyMesh->IsPendingKill()) + return; + + FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); + FString BakeName = FoundOutputObject ? FoundOutputObject->BakeName : FString(); + + // Get thumbnail pool for this builder. + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // TODO: GetBakingBaseName! + FString Label = ProxyMesh->GetName(); + if (HoudiniGeoPartObject.bHasCustomPartName) + Label = HoudiniGeoPartObject.PartName; + + // Create thumbnail for this mesh. + TSharedPtr MeshThumbnail = MakeShareable(new FAssetThumbnail(ProxyMesh, 64, 64, AssetThumbnailPool)); + TSharedPtr MeshThumbnailBorder; + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + IDetailGroup& StaticMeshGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("BakeBaseName", "Bake Name")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(BakeName)) + .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .OnTextCommitted_Lambda([OutputIdentifier, InOutput](const FText& Val, ETextCommit::Type TextCommitType) + { + FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + }) + .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) + ] + + SHorizontalBox::Slot() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([InOutput, OutputIdentifier]() + { + FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + + // Add details on the Proxy Mesh + EHoudiniSplitType SplitType = FHoudiniMeshTranslator::GetSplitTypeFromSplitName(OutputIdentifier.SplitIdentifier); + FString MeshLabel = TEXT("Proxy Mesh"); + + // Indicate that this mesh is instanced + if (HoudiniGeoPartObject.bIsInstanced) + { + MeshLabel += TEXT("\n(instanced)"); + } + + if (HoudiniGeoPartObject.bIsTemplated) + { + MeshLabel += TEXT("\n(templated)"); + } + + if (HoudiniGeoPartObject.AllMeshSockets.Num() > 0) + { + MeshLabel += TEXT("\n(") + FString::FromInt(HoudiniGeoPartObject.AllMeshSockets.Num()) + TEXT(" sockets)"); + } + + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InOutput->GetOuter()); + StaticMeshGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(FText::FromString(MeshLabel)) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + VerticalBox + ]; + + VerticalBox->AddSlot() + .Padding(0, 2) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(MeshThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage(this, &FHoudiniOutputDetails::GetThumbnailBorder, (UObject*)ProxyMesh) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)ProxyMesh) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(ProxyMesh->GetPathName())) + [ + MeshThumbnail->MakeThumbnailWidget() + ] + ] + ] + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .MaxWidth(80.0f) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("Refine", "Refine")) + .IsEnabled(true) + .OnClicked(this, &FHoudiniOutputDetails::OnRefineClicked, (UObject *)ProxyMesh, InOutput) + .ToolTipText(LOCTEXT("RefineTooltip", "Refine this Proxy Mesh to a Static Mesh")) + ] + ] + ] + ]; + + // Store thumbnail for this mesh. + OutputObjectThumbnailBorders.Add(ProxyMesh, MeshThumbnailBorder); + + // We need to add material box for each material present in this static mesh. + auto & ProxyMeshMaterials = ProxyMesh->GetStaticMaterials(); + for (int32 MaterialIdx = 0; MaterialIdx < ProxyMeshMaterials.Num(); ++MaterialIdx) + { + UMaterialInterface * MaterialInterface = ProxyMeshMaterials[MaterialIdx].MaterialInterface; + TSharedPtr< SBorder > MaterialThumbnailBorder; + TSharedPtr< SHorizontalBox > HorizontalBox = NULL; + + FString MaterialName, MaterialPathName; + if (MaterialInterface && !MaterialInterface->IsPendingKill() + && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill()) + { + MaterialName = MaterialInterface->GetName(); + MaterialPathName = MaterialInterface->GetPathName(); + } + else + { + MaterialInterface = nullptr; + MaterialName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx); + } + + // Create thumbnail for this material. + TSharedPtr MaterialInterfaceThumbnail = + MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool)); + + // No drop target + VerticalBox->AddSlot() + .Padding(0, 2) + [ + SNew(SAssetDropTarget) + //.OnIsAssetAcceptableForDrop(false) + //.OnAssetDropped( + // this, &FHoudiniOutputDetails::OnMaterialInterfaceDropped, StaticMesh, InOutput, MaterialIdx) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + HorizontalBox->AddSlot() + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .AutoWidth() + [ + SAssignNew(MaterialThumbnailBorder, SBorder) + .Padding(5.0f) + .BorderImage( + this, &FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder, (UObject*)ProxyMesh, MaterialIdx) + .OnMouseDoubleClick( + this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(MaterialPathName)) + [ + MaterialInterfaceThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + // Store thumbnail for this mesh and material index. + { + TPairInitializer Pair((UObject*)ProxyMesh, MaterialIdx); + MaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder); + } + + // Combo box and buttons + TSharedPtr ComboAndButtonBox; + HorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SAssignNew(ComboAndButtonBox, SVerticalBox) + ]; + + // Add combo box + TSharedPtr AssetComboButton; + ComboAndButtonBox->AddSlot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(1.0f).VAlign(VAlign_Center) + [ + SAssignNew(AssetComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + /*.OnGetMenuContent(this, &FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent, + MaterialInterface, StaticMesh, InOutput, MaterialIdx)*/ + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(MaterialName)) + ] + ] + ]; + + + TSharedPtr ButtonBox; + ComboAndButtonBox->AddSlot().FillHeight(1.0f) + [ + SAssignNew(ButtonBox, SHorizontalBox) + ]; + + // Disable the combobutton for proxies + AssetComboButton->SetEnabled(false); + + // Add use selection form content browser array + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeUseSelectedButton( + /*FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface, + (UObject*)ProxyMesh, InOutput, MaterialIdx),*/ + FSimpleDelegate::CreateLambda([]() {}), // Do nothing for proxies + TAttribute< FText >(LOCTEXT("UseSelectedAssetFromContentBrowser", "Use Selected Asset from Content Browser")), false) + // Disable the use CB selection button for proxies + ]; + + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(MaterialName)); + FText MaterialTooltip = FText::Format( + LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateSP(this, &FHoudiniOutputDetails::OnBrowseTo, (UObject*)MaterialInterface), TAttribute(MaterialTooltip)) + ]; + + /* + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked( + this, &FHoudiniOutputDetails::OnResetMaterialInterfaceClicked, StaticMesh, InOutput, MaterialIdx) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + */ + + // Store combo button for this mesh and index. + { + TPairInitializer Pair(ProxyMesh, MaterialIdx); + MaterialInterfaceComboButtons.Add(Pair, AssetComboButton); + } + } +} + +FText +FHoudiniOutputDetails::GetOutputDebugName(UHoudiniOutput* InOutput) +{ + // Get the name and type + FString OutputNameStr = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); + + // Then add the number of parts + OutputNameStr += TEXT(" (") + FString::FromInt(InOutput->GetHoudiniGeoPartObjects().Num()) + TEXT(" Part(s))\n"); + + return FText::FromString(OutputNameStr); +} +FText +FHoudiniOutputDetails::GetOutputDebugDescription(UHoudiniOutput* InOutput) +{ + const TArray& HGPOs = InOutput->GetHoudiniGeoPartObjects(); + + FString OutputValStr; + OutputValStr += TEXT("HGPOs:\n"); + for (auto& HGPO : HGPOs) + { + OutputValStr += TEXT(" - ") + HGPO.PartName + TEXT(" (") + FHoudiniGeoPartObject::HoudiniPartTypeToString(HGPO.Type) + TEXT(")"); + + if (HGPO.SplitGroups.Num() > 0) + { + OutputValStr += TEXT("( ") + FString::FromInt(HGPO.SplitGroups.Num()) + TEXT(" splits:"); + for (auto& split : HGPO.SplitGroups) + { + OutputValStr += TEXT(" ") + split; + } + OutputValStr += TEXT(")"); + } + + if (!HGPO.VolumeName.IsEmpty()) + { + OutputValStr += TEXT("( ") + HGPO.VolumeName; + if (HGPO.VolumeTileIndex >= 0) + OutputValStr += TEXT(" tile ") + FString::FromInt(HGPO.VolumeTileIndex); + OutputValStr += TEXT(" )"); + } + + OutputValStr += TEXT("\n"); + } + + // Add output objects if any + TMap AllOutputObj = InOutput->GetOutputObjects(); + if (AllOutputObj.Num() > 0) + { + bool TitleAdded = false; + for (const auto& Iter : AllOutputObj) + { + UObject* OutObject = Iter.Value.OutputObject; + if (OutObject) + { + OutputValStr += OutObject->GetFullName() + TEXT(" (obj)\n"); + } + + UObject* OutComp = Iter.Value.OutputComponent; + if (OutComp) + { + OutputValStr += OutComp->GetFullName() + TEXT(" (comp)\n"); + } + } + } + + return FText::FromString(OutputValStr); +} + +FText +FHoudiniOutputDetails::GetOutputTooltip(UHoudiniOutput* InOutput) +{ + // TODO + return FText(); +} + + +const FSlateBrush * +FHoudiniOutputDetails::GetThumbnailBorder(UObject* Mesh) const +{ + TSharedPtr ThumbnailBorder = OutputObjectThumbnailBorders[Mesh]; + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + + +const FSlateBrush * +FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(UObject* OutputObject, int32 MaterialIdx) const +{ + if (!OutputObject) + return nullptr; + + TPairInitializer Pair(OutputObject, MaterialIdx); + TSharedPtr ThumbnailBorder = MaterialInterfaceThumbnailBorders[Pair]; + + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} + +/* +const FSlateBrush * +FHoudiniOutputDetails::GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx) const +{ + if (!Landscape) + return nullptr; + + TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); + TSharedPtr< SBorder > ThumbnailBorder = LandscapeMaterialInterfaceThumbnailBorders[Pair]; + + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); +} +*/ + +FReply +FHoudiniOutputDetails::OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, + const FPointerEvent & InMouseEvent, UObject * Object) +{ + if (Object && GEditor) + GEditor->EditObject(Object); + + return FReply::Handled(); +} + +/* +FReply +FHoudiniOutputDetails::OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject) +{ + if (HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill()) + { + FHoudiniPackageParams PackageParms; + + + FHoudiniEngineBakeUtils::BakeStaticMesh(HoudiniAssetComponent, GeoPartObject, StaticMesh, PackageParms); + // TODO: Bake the SM + + + // We need to locate corresponding geo part object in component. + const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(StaticMesh); + + // (void)FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( + // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); + + } + + return FReply::Handled(); +} +*/ + +bool +FHoudiniOutputDetails::OnMaterialInterfaceDraggedOver(const UObject * InObject) const +{ + return (InObject && InObject->IsA(UMaterialInterface::StaticClass())); +} + + +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + UStaticMesh * StaticMesh, + UHoudiniOutput * HoudiniOutput, + int32 MaterialIdx) +{ + FReply RetValue = FReply::Handled(); + if (!StaticMesh || StaticMesh->IsPendingKill()) + return RetValue; + + TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); + + if (!StaticMaterials.IsValidIndex(MaterialIdx)) + return RetValue; + + // Retrieve material interface which is being replaced. + UMaterialInterface * MaterialInterface = StaticMaterials[MaterialIdx].MaterialInterface; + if (!MaterialInterface) + return RetValue; + + // Find the string corresponding to the material that is being replaced + const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); + if (!FoundString ) + { + // This material was not replaced, no need to reset it + return RetValue; + } + + // This material has been replaced previously. + FString MaterialString = *FoundString; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), HoudiniOutput); + + // Remove the replacement + HoudiniOutput->Modify(); + HoudiniOutput->GetReplacementMaterials().Remove(MaterialString); + + bool bViewportNeedsUpdate = true; + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + UMaterialInterface * const * FoundMat = HoudiniOutput->GetAssignementMaterials().Find(MaterialString); + if (FoundMat && (*FoundMat)) + AssignMaterial = *FoundMat; + + // Replace material on static mesh. + StaticMesh->Modify(); + StaticMaterials[MaterialIdx].MaterialInterface = AssignMaterial; + + // Replace the material on any component (SMC/ISMC) that uses the above SM + // TODO: ?? Replace for all? + for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) + { + // Only look at MeshComponents + UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); + if (!SMC) + continue; + + if (SMC->GetStaticMesh() != StaticMesh) + continue; + + SMC->Modify(); + SMC->SetMaterial(MaterialIdx, AssignMaterial); + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + return RetValue; +} + +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + ALandscapeProxy* InLandscape, + UHoudiniOutput * InHoudiniOutput, + int32 InMaterialIdx) +{ + FReply RetValue = FReply::Handled(); + if (!InLandscape || InLandscape->IsPendingKill()) + return RetValue; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * MaterialInterface = InMaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); + UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + // Find the string corresponding to the material that is being replaced + const FString* FoundString = InHoudiniOutput->GetReplacementMaterials().FindKey(MaterialInterface); + if (!FoundString) + { + // This material was not replaced, no need to reset it + return RetValue; + } + + // This material has been replaced previously. + FString MaterialString = *FoundString; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Reset"), InHoudiniOutput); + + // Remove the replacement + InHoudiniOutput->Modify(); + InHoudiniOutput->GetReplacementMaterials().Remove(MaterialString); + + bool bViewportNeedsUpdate = true; + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + UMaterialInterface * const * FoundMat = InHoudiniOutput->GetAssignementMaterials().Find(MaterialString); + if (FoundMat && (*FoundMat)) + AssignMaterial = *FoundMat; + + // Replace material on Landscape + InLandscape->Modify(); + if (InMaterialIdx == 0) + InLandscape->LandscapeMaterial = AssignMaterial; + else + InLandscape->LandscapeHoleMaterial = AssignMaterial; + + InLandscape->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); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + */ + + FHoudiniEngineUtils::UpdateEditorProperties(InHoudiniOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + return RetValue; +} +/* +FReply +FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( + ALandscapeProxy * Landscape, UHoudiniOutput * InOutput, int32 MaterialIdx) +{ + bool bViewportNeedsUpdate = false; + + // TODO: Handle me! + for (TArray< UHoudiniAssetComponent * >::TIterator + IterComponents(HoudiniAssetComponents); IterComponents; ++IterComponents) + { + UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents; + if (!HoudiniAssetComponent) + continue; + + TWeakObjectPtr* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find(*HoudiniGeoPartObject); + if (!FoundLandscapePtr) + continue; + + ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get(); + if (!FoundLandscape || !FoundLandscape->IsValidLowLevel()) + continue; + + if (FoundLandscape != Landscape) + continue; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial(); + UMaterialInterface * MaterialInterfaceReplacement = Cast(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get()); + + bool bMaterialRestored = false; + FString MaterialShopName; + if (!HoudiniAssetComponent->GetReplacementMaterialShopName(*HoudiniGeoPartObject, MaterialInterface, MaterialShopName)) + { + // This material was not replaced so there's no need to reset it + continue; + } + + // Remove the replacement + HoudiniAssetComponent->RemoveReplacementMaterial(*HoudiniGeoPartObject, MaterialShopName); + + // Try to find the original assignment, if not, we'll use the default material + UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial(MaterialShopName); + if (AssignedMaterial) + MaterialInterfaceReplacement = AssignedMaterial; + + // Replace material on the landscape + Landscape->Modify(); + + if (MaterialIdx == 0) + Landscape->LandscapeMaterial = MaterialInterfaceReplacement; + else + Landscape->LandscapeHoleMaterial = MaterialInterfaceReplacement; + + //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); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + + HoudiniAssetComponent->UpdateEditorProperties(false); + bViewportNeedsUpdate = true; + } + + if (GEditor && bViewportNeedsUpdate) + { + GEditor->RedrawAllViewports(); + } + + return FReply::Handled(); +} +*/ + +void +FHoudiniOutputDetails::OnBrowseTo(UObject* InObject) +{ + if (GEditor) + { + TArray Objects; + Objects.Add(InObject); + GEditor->SyncBrowserToObjects(Objects); + } +} + +TSharedRef +FHoudiniOutputDetails::OnGetMaterialInterfaceMenuContent( + UMaterialInterface* MaterialInterface, + UObject* OutputObject, + UHoudiniOutput* InOutput, + int32 MaterialIdx) +{ + TArray AllowedClasses; + AllowedClasses.Add(UMaterialInterface::StaticClass()); + + TArray NewAssetFactories; + + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(MaterialInterface), + true, + AllowedClasses, + NewAssetFactories, + OnShouldFilterMaterialInterface, + FOnAssetSelected::CreateSP( + this, &FHoudiniOutputDetails::OnMaterialInterfaceSelected, OutputObject, InOutput, MaterialIdx), + FSimpleDelegate::CreateSP( + this, &FHoudiniOutputDetails::CloseMaterialInterfaceComboButton)); +} + + +void +FHoudiniOutputDetails::CloseMaterialInterfaceComboButton() +{ + +} + +void +FHoudiniOutputDetails::OnMaterialInterfaceDropped( + UObject * InObject, + UStaticMesh * StaticMesh, + UHoudiniOutput * HoudiniOutput, + int32 MaterialIdx) +{ + UMaterialInterface * MaterialInterface = Cast(InObject); + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + return; + + if (!StaticMesh || StaticMesh->IsPendingKill()) + return; + + TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); + if (!StaticMaterials.IsValidIndex(MaterialIdx)) + return; + + bool bViewportNeedsUpdate = false; + + // Retrieve material interface which is being replaced. + UMaterialInterface * OldMaterialInterface = StaticMaterials[MaterialIdx].MaterialInterface; + if (OldMaterialInterface == MaterialInterface) + return; + + // Find the string corresponding to the material that is being replaced + FString MaterialString = FString(); + const FString* FoundString = HoudiniOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been replaced previously. + MaterialString = *FoundString; + } + else + { + // We have no previous replacement for this material, + // see if we can find it the material assignment list. + FoundString = HoudiniOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been assigned previously. + MaterialString = *FoundString; + } + else + { + UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + if (OldMaterialInterface == DefaultMaterial) + { + // This is replacement for default material. + MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + } + else + { + // External Material? + MaterialString = OldMaterialInterface->GetName(); + } + } + } + + if (MaterialString.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), HoudiniOutput); + + // Add a new material replacement entry. + HoudiniOutput->Modify(); + HoudiniOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); + + // Replace material on static mesh. + StaticMesh->Modify(); + StaticMaterials[MaterialIdx].MaterialInterface = MaterialInterface; + + // Replace the material on any component (SMC/ISMC) that uses the above SM + for (auto& OutputObject : HoudiniOutput->GetOutputObjects()) + { + // Only look at MeshComponents + UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); + if (SMC && !SMC->IsPendingKill()) + { + if (SMC->GetStaticMesh() == StaticMesh) + { + SMC->Modify(); + SMC->SetMaterial(MaterialIdx, MaterialInterface); + } + } + else + { + UStaticMesh* SM = Cast(OutputObject.Value.OutputObject); + if (SM && !SM->IsPendingKill()) + { + SM->Modify(); + SM->SetMaterial(MaterialIdx, MaterialInterface); + } + } + + + + } + + FHoudiniEngineUtils::UpdateEditorProperties(HoudiniOutput->GetOuter(), true); + + /* + if(GUnrealEd) + GUnrealEd->UpdateFloatingPropertyWindows(); +*/ + if (GEditor) + GEditor->RedrawAllViewports(); +} + +// Delegate used when a valid material has been drag and dropped on a landscape. +void +FHoudiniOutputDetails::OnMaterialInterfaceDropped( + UObject* InDroppedObject, + ALandscapeProxy* InLandscape, + UHoudiniOutput* InOutput, + int32 MaterialIdx) +{ + UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >(InDroppedObject); + if (!MaterialInterface || MaterialInterface->IsPendingKill()) + return; + + if (!InLandscape || InLandscape->IsPendingKill()) + return; + + bool bViewportNeedsUpdate = false; + + // Retrieve the material interface which is being replaced. + UMaterialInterface * OldMaterialInterface = MaterialIdx == 0 ? InLandscape->GetLandscapeMaterial() : InLandscape->GetLandscapeHoleMaterial(); + if (OldMaterialInterface == MaterialInterface) + return; + + // Find the string corresponding to the material that is being replaced + FString MaterialString = FString(); + const FString* FoundString = InOutput->GetReplacementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been replaced previously. + MaterialString = *FoundString; + } + else + { + // We have no previous replacement for this material, + // see if we can find it the material assignment list. + FoundString = InOutput->GetAssignementMaterials().FindKey(OldMaterialInterface); + if (FoundString) + { + // This material has been assigned previously. + MaterialString = *FoundString; + } + else + { + UMaterialInterface * DefaultMaterial = FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get(); + if (OldMaterialInterface == DefaultMaterial) + { + // This is replacement for default material. + MaterialString = HAPI_UNREAL_DEFAULT_MATERIAL_NAME; + } + else + { + // External Material? + if (OldMaterialInterface && !OldMaterialInterface->IsPendingKill()) + MaterialString = OldMaterialInterface->GetName(); + } + } + } + + if (MaterialString.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniMaterialReplacement", "Houdini Material Replacement"), InOutput); + + // Add a new material replacement entry. + InOutput->Modify(); + InOutput->GetReplacementMaterials().Add(MaterialString, MaterialInterface); + + // Replace material on the landscape + InLandscape->Modify(); + + if (MaterialIdx == 0) + InLandscape->LandscapeMaterial = MaterialInterface; + else + InLandscape->LandscapeHoleMaterial = MaterialInterface; + + // Update the landscape components Material instances + InLandscape->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 >(InLandscape->GetClass(), (MaterialIdx == 0) ? TEXT("LandscapeMaterial") : TEXT("LandscapeHoleMaterial")); + if (FoundProperty) + { + FPropertyChangedEvent PropChanged(FoundProperty, EPropertyChangeType::ValueSet); + InLandscape->PostEditChangeProperty(PropChanged); + } + else + { + // The only way to update the material for now is to recook/recreate the landscape... + HoudiniAssetComponent->StartTaskAssetCookingManual(); + } + */ + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + + if (GEditor) + GEditor->RedrawAllViewports(); +} + +void +FHoudiniOutputDetails::OnMaterialInterfaceSelected( + const FAssetData & AssetData, + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + TPairInitializer Pair(OutputObject, MaterialIdx); + TSharedPtr AssetComboButton = MaterialInterfaceComboButtons[Pair]; + if (AssetComboButton.IsValid()) + { + AssetComboButton->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + + UStaticMesh* SM = Cast(OutputObject); + if (SM && !SM->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); + } + + ALandscapeProxy* Landscape = Cast(OutputObject); + if (Landscape && !Landscape->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } + } +} + +void +FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + if (!OutputObject || OutputObject->IsPendingKill()) + return; + + if (!InOutput || InOutput->IsPendingKill()) + return; + + if (GEditor) + { + TArray CBSelections; + GEditor->GetContentBrowserSelections(CBSelections); + + // Get the first selected material object + UObject* Object = nullptr; + for (auto & CurAssetData : CBSelections) + { + if (CurAssetData.AssetClass != UMaterial::StaticClass()->GetFName() && + CurAssetData.AssetClass != UMaterialInstance::StaticClass()->GetFName() && + CurAssetData.AssetClass != UMaterialInstanceConstant::StaticClass()->GetFName()) + continue; + + Object = CurAssetData.GetAsset(); + break; + } + + if (Object && !Object->IsPendingKill()) + { + UStaticMesh* SM = Cast(OutputObject); + if (SM && !SM->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); + } + + ALandscapeProxy* Landscape = Cast(OutputObject); + if (Landscape && !Landscape->IsPendingKill()) + { + return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } + } + } +} + +void +FHoudiniOutputDetails::CreateInstancerOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput || InOutput->IsPendingKill()) + return; + + // Do not display instancer UI for one-instance instancers + bool OnlyOneInstanceInstancers = true; + for (auto& Iter : InOutput->GetInstancedOutputs()) + { + FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); + if (CurInstanceOutput.OriginalTransforms.Num() <= 1) + continue; + + OnlyOneInstanceInstancers = false; + break; + } + + // This output only has one-instance instancers (SMC), no need to display the instancer UI. + if (OnlyOneInstanceInstancers) + return; + + // Classes allowed for instance variations. + const TArray AllowedClasses = + { + UStaticMesh::StaticClass(), USkeletalMesh::StaticClass(), + AActor::StaticClass(), UBlueprint::StaticClass(), + UFXSystemAsset::StaticClass(), USoundBase::StaticClass() + }; + + // Classes not allowed for instances variations (useless?) + TArray DisallowedClasses = + { + UClass::StaticClass(), ULevel::StaticClass(), + UMaterial::StaticClass(), UTexture::StaticClass() + }; + + IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); + TSharedPtr AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); + + // Lambda for adding new variation objects + auto AddObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) + { + // TODO: undo/redo? + InOutputToUpdate.VariationObjects.Insert(InObject, AtIndex); + InOutputToUpdate.VariationTransformOffsets.Insert(FTransform::Identity, AtIndex); + FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for adding new geometry input objects + auto RemoveObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex) + { + // Also keep one instance object + if (AtIndex < 0 || AtIndex >= InOutputToUpdate.VariationObjects.Num()) + return; + + if (InOutputToUpdate.VariationObjects.Num() == 1) + return; + + // TODO: undo/redo? + InOutputToUpdate.VariationObjects.RemoveAt(AtIndex); + InOutputToUpdate.VariationTransformOffsets.RemoveAt( AtIndex); + FHoudiniInstanceTranslator::UpdateVariationAssignements(InOutputToUpdate); + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for updating a variation + auto SetObjectAt = [InOutput](FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, UObject* InObject) + { + if (!InOutputToUpdate.VariationObjects.IsValidIndex(AtIndex)) + return; + + InOutputToUpdate.VariationObjects[AtIndex] = InObject; + + InOutputToUpdate.MarkChanged(true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Lambda for changing the transform offset values + auto ChangeTransformOffsetAt = [InOutput]( + FHoudiniInstancedOutput& InOutputToUpdate, const int32& AtIndex, + const float& Value, const int32& PosRotScaleIndex, const int32& XYZIndex) + { + bool bChanged = InOutputToUpdate.SetTransformOffsetAt(Value, AtIndex, PosRotScaleIndex, XYZIndex); + if (!bChanged) + return; + + InOutputToUpdate.MarkChanged(true); + + if (GEditor) + GEditor->RedrawAllViewports(); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); + }; + + // Get this output's OutputObject + const TMap& OutputObjects = InOutput->GetOutputObjects(); + + // Iterate on all of the output's HGPO + for (const FHoudiniGeoPartObject& CurHGPO : InOutput->GetHoudiniGeoPartObjects()) + { + // Not an instancer, skip + if (CurHGPO.Type != EHoudiniPartType::Instancer) + continue; + + // Get the label for that instancer + FString InstancerLabel = InOutput->GetName() + TEXT(" ") + UHoudiniOutput::OutputTypeToString(InOutput->GetType()); + if (CurHGPO.bHasCustomPartName) + InstancerLabel = CurHGPO.PartName; + + TSharedRef InstancerVerticalBox = SNew(SVerticalBox); + TSharedPtr InstancerHorizontalBox = nullptr; + + // Create a new Group for that instancer + IDetailGroup& InstancerGroup = HouOutputCategory.AddGroup(FName(*InstancerLabel), FText::FromString(InstancerLabel)); + + // Now iterate and display the instance outputs that matches this HGPO + for (auto& Iter : InOutput->GetInstancedOutputs()) + { + FHoudiniOutputObjectIdentifier& CurOutputObjectIdentifier = Iter.Key; + if (!CurOutputObjectIdentifier.Matches(CurHGPO)) + continue; + + FHoudiniInstancedOutput& CurInstanceOutput = (Iter.Value); + + // Dont display instancer UI for one-instance instancers (SMC) + if (CurInstanceOutput.OriginalTransforms.Num() <= 1) + continue; + + for( int32 VariationIdx = 0; VariationIdx < CurInstanceOutput.VariationObjects.Num(); VariationIdx++ ) + { + UObject * InstancedObject = CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous(); + if ( !InstancedObject || InstancedObject->IsPendingKill() ) + { + HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); + continue; + } + + // Create thumbnail for this object. + TSharedPtr VariationThumbnail = + MakeShareable(new FAssetThumbnail(InstancedObject, 64, 64, AssetThumbnailPool)); + TSharedRef PickerVerticalBox = SNew(SVerticalBox); + TSharedPtr PickerHorizontalBox = nullptr; + TSharedPtr VariationThumbnailBorder; + + // For the variation name, reuse the instancer label and append the variation index if we have more than one variation + FString InstanceOutputLabel = InstancerLabel; + if(CurInstanceOutput.VariationObjects.Num() > 1) + InstanceOutputLabel += TEXT(" [") + FString::FromInt(VariationIdx) + TEXT("]"); + + IDetailGroup* DetailGroup = &InstancerGroup; + if (CurInstanceOutput.VariationObjects.Num() > 1) + { + // If we have more than one variation, add a new group for each variation + DetailGroup = &InstancerGroup.AddGroup(FName(*InstanceOutputLabel), FText::FromString(InstanceOutputLabel), true); + } + + // See if we can find the corresponding component to get its type + FString InstancerType = TEXT("(Instancer)"); + FHoudiniOutputObjectIdentifier CurVariationIdentifier = CurOutputObjectIdentifier; + CurVariationIdentifier.SplitIdentifier += TEXT("_") + FString::FromInt(VariationIdx); + const FHoudiniOutputObject* VariationOutputObject = OutputObjects.Find(CurVariationIdentifier); + if(VariationOutputObject) + InstancerType = FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(VariationOutputObject->OutputComponent); + + DetailGroup->AddWidgetRow() + .NameContent() + [ + //SNew(SSpacer) + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(InstancerType)) + //.Size(FVector2D(250, 64)) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + PickerVerticalBox + ]; + + // Add an asset drop target + PickerVerticalBox->AddSlot() + .Padding(0, 2) + .AutoHeight() + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop(SAssetDropTarget::FIsAssetAcceptableForDrop::CreateLambda( + [DisallowedClasses](const UObject* Obj) + { + for (auto Klass : DisallowedClasses) + { + if (Obj && Obj->IsA(Klass)) + return false; + } + return true; + }) + ) + .OnAssetDropped_Lambda([&CurInstanceOutput, VariationIdx, SetObjectAt](UObject* InObject) + { + return SetObjectAt(CurInstanceOutput, VariationIdx, InObject); + }) + [ + SAssignNew(PickerHorizontalBox, SHorizontalBox) + ] + ]; + + PickerHorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth() + [ + SAssignNew(VariationThumbnailBorder, SBorder) + .Padding( 5.0f ) + .OnMouseDoubleClick(this, &FHoudiniOutputDetails::OnThumbnailDoubleClick, InstancedObject) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + .ToolTipText(FText::FromString(InstancedObject->GetPathName())) + [ + VariationThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + TWeakPtr WeakVariationThumbnailBorder(VariationThumbnailBorder); + VariationThumbnailBorder->SetBorderImage(TAttribute< const FSlateBrush *>::Create( + TAttribute::FGetter::CreateLambda([WeakVariationThumbnailBorder]() + { + TSharedPtr ThumbnailBorder = WeakVariationThumbnailBorder.Pin(); + if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ))); + + PickerHorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 28.0f, 0.0f, 28.0f) + [ + PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, AddObjectAt]() + { + UObject* ObjToAdd = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? + CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() + : nullptr; + + return AddObjectAt(CurInstanceOutput, VariationIdx, ObjToAdd); + }), + LOCTEXT("AddAnotherInstanceToolTip", "Add Another Instance")) + ]; + + PickerHorizontalBox->AddSlot().AutoWidth().Padding( 2.0f, 28.0f, 4.0f, 28.0f ) + [ + PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx, RemoveObjectAt]() + { + return RemoveObjectAt(CurInstanceOutput, VariationIdx); + }), + LOCTEXT("RemoveLastInstanceToolTip", "Remove Last Instance")) + ]; + + TSharedPtr AssetComboButton; + TSharedPtr ButtonBox; + PickerHorizontalBox->AddSlot() + .FillWidth(1.0f) + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .HAlign(HAlign_Fill) + [ + SAssignNew(ButtonBox, SHorizontalBox) + +SHorizontalBox::Slot() + [ + SAssignNew(AssetComboButton, SComboButton) + //.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip ) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) ) + /* TODO: Update UI + .OnMenuOpenChanged( FOnIsOpenChanged::CreateUObject( + &InParam, &UHoudiniAssetInstanceInput::ChangedStaticMeshComboButton, + CurInstanceOutput, InstOutIdx, VariationIdx ) ) + */ + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromString(InstancedObject->GetName())) + ] + ] + ] + ]; + + // Create asset picker for this combo button. + { + TWeakPtr WeakAssetComboButton(AssetComboButton); + TArray NewAssetFactories; + TSharedRef PropertyMenuAssetPicker = PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(InstancedObject), + true, + AllowedClasses, + DisallowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( + [&CurInstanceOutput, VariationIdx, SetObjectAt, WeakAssetComboButton](const FAssetData& AssetData) + { + TSharedPtr AssetComboButtonPtr = WeakAssetComboButton.Pin(); + if (AssetComboButtonPtr.IsValid()) + { + AssetComboButtonPtr->SetIsOpen(false); + UObject * Object = AssetData.GetAsset(); + SetObjectAt(CurInstanceOutput, VariationIdx, Object); + } + } + ), + // Nothing to do on close + FSimpleDelegate::CreateLambda([](){}) + ); + + AssetComboButton->SetMenuContent(PropertyMenuAssetPicker); + } + + // Create tooltip. + FFormatNamedArguments Args; + Args.Add(TEXT("Asset"), FText::FromString(InstancedObject->GetName())); + FText StaticMeshTooltip = + FText::Format(LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args); + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + PropertyCustomizationHelpers::MakeBrowseButton( + FSimpleDelegate::CreateLambda([&CurInstanceOutput, VariationIdx]() + { + UObject* InputObject = CurInstanceOutput.VariationObjects.IsValidIndex(VariationIdx) ? + CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous() + : nullptr; + + if (GEditor && InputObject) + { + TArray Objects; + Objects.Add(InputObject); + GEditor->SyncBrowserToObjects(Objects); + } + }), + TAttribute< FText >( StaticMeshTooltip ) ) + ]; + + ButtonBox->AddSlot() + .AutoWidth() + .Padding(2.0f, 0.0f ) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT( "ResetToBase", "Reset to default static mesh")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([SetObjectAt, &CurInstanceOutput, VariationIdx]() + { + SetObjectAt(CurInstanceOutput, VariationIdx, CurInstanceOutput.OriginalObject.LoadSynchronous()); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + + + // Get Visibility of reset buttons + bool bResetButtonVisiblePosition = false; + bool bResetButtonVisibleRotation = false; + bool bResetButtonVisibleScale = false; + + FTransform CurTransform = CurInstanceOutput.VariationTransformOffsets[VariationIdx]; + + if (CurTransform.GetLocation() != FVector::ZeroVector) + bResetButtonVisiblePosition = true; + + FRotator Rotator = CurTransform.Rotator(); + if (Rotator.Roll != 0 || Rotator.Pitch != 0 || Rotator.Yaw != 0) + bResetButtonVisibleRotation = true; + + if (CurTransform.GetScale3D() != FVector::OneVector) + bResetButtonVisibleScale = true; + + auto ChangeTransformOffsetUniformlyAt = [ChangeTransformOffsetAt, VariationIdx, &CurInstanceOutput](const float& Val, const int32& PosRotScaleIndex) + { + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 0); + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 1); + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, PosRotScaleIndex, 2); + }; + + TSharedRef OffsetVerticalBox = SNew(SVerticalBox); + FText LabelPositionText = LOCTEXT("HoudiniPositionOffset", "Position Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelPositionText) + .ToolTipText(LabelPositionText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .AllowSpin(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 0); } + ))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 1); } + ))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 0, 2); } + ))) + .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 0); }) + .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 1); }) + .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 0, 2); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("InstancerOutputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisiblePosition ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, CurInstanceOutput, InOutput]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 0); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + ]; + + FText LabelRotationText = LOCTEXT("HoudiniRotationOffset", "Rotation Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelRotationText) + .ToolTipText(LabelRotationText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SRotatorInputBox) + .AllowSpin(true) + .bColorAxisLabels(true) + .Roll(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 0); } + ))) + .Pitch(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 1); } + ))) + .Yaw(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 1, 2); } + ))) + .OnRollCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 0); }) + .OnPitchCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 1); }) + .OnYawCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt](float Val, ETextCommit::Type TextCommitType) + { ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 1, 2); }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button (not visible) + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("GenericLock")) + ] + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleRotation ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() + { + ChangeTransformOffsetUniformlyAt(0.0f, 1); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + ]; + + FText LabelScaleText = LOCTEXT("HoudiniScaleOffset", "Scale Offset"); + DetailGroup->AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LabelScaleText) + .ToolTipText(LabelScaleText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .X(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 0); } + ))) + .Y(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 1); } + ))) + .Z(TAttribute>::Create( + TAttribute>::FGetter::CreateLambda([&CurInstanceOutput, VariationIdx]() + { return CurInstanceOutput.GetTransformOffsetAt(VariationIdx, 2, 2); } + ))) + .OnXCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 0); + }) + .OnYCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 1); + }) + .OnZCommitted_Lambda([&CurInstanceOutput, VariationIdx, ChangeTransformOffsetAt, ChangeTransformOffsetUniformlyAt](float Val, ETextCommit::Type TextCommitType) + { + if (CurInstanceOutput.IsUnformScaleLocked()) + ChangeTransformOffsetUniformlyAt(Val, 2); + else + ChangeTransformOffsetAt(CurInstanceOutput, VariationIdx, Val, 2, 2); + }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + [ + // Lock Button + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("InstancerOutputLockButtonToolTip", "When locked, scales uniformly based on the current xyz scale values so the output object maintains its shape in each direction when scaled")) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(CurInstanceOutput.IsUnformScaleLocked() ? FEditorStyle::GetBrush("GenericLock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([&CurInstanceOutput, InOutput]() + { + CurInstanceOutput.SwitchUniformScaleLock(); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + // Reset Button + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center).Padding(0.0f) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("GeoInputResetButtonToolTip", "Reset To Default")) + .Visibility(bResetButtonVisibleScale ? EVisibility::Visible : EVisibility::Hidden) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + .OnClicked_Lambda([ChangeTransformOffsetUniformlyAt, InOutput]() + { + ChangeTransformOffsetUniformlyAt(1.0f, 2); + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); + }) + ] + ] + /* + // TODO: Add support for this back + + SHorizontalBox::Slot().AutoWidth() + [ + // Add a checkbox to toggle between preserving the ratio of x,y,z components of scale when a value is entered + SNew(SCheckBox) + .Style(FEditorStyle::Get(), "TransparentCheckBox") + .ToolTipText(LOCTEXT("PreserveScaleToolTip", "When locked, scales uniformly based on the current xyz scale values so the object maintains its shape in each direction when scaled")) + *//* + .OnCheckStateChanged(FOnCheckStateChanged::CreateLambda([=](ECheckBoxState NewState) + { + if ( MyParam.IsValid() && InputFieldPtr.IsValid() ) + MyParam->CheckStateChanged( NewState == ECheckBoxState::Checked, InputFieldPtr.Get(), VariationIdx ); + })) + .IsChecked( TAttribute< ECheckBoxState >::Create( + TAttribute::FGetter::CreateLambda( [=]() + { + if (InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly(VariationIdx)) + return ECheckBoxState::Checked; + return ECheckBoxState::Unchecked; + } + ))) + *//* + [ + SNew(SImage) + *//*.Image(TAttribute::Create( + TAttribute::FGetter::CreateLambda( [=]() + { + if ( InputFieldPtr.IsValid() && InputFieldPtr->AreOffsetsScaledLinearly( VariationIdx ) ) + { + return FEditorStyle::GetBrush( TEXT( "GenericLock" ) ); + } + return FEditorStyle::GetBrush( TEXT( "GenericUnlock" ) ); + } + ))) + *//* + .ColorAndOpacity( FSlateColor::UseForeground() ) + ] + ] + */ + ]; + } + } + } +} + +/* +void +FHoudiniOutputDetails::OnMaterialInterfaceSelected( + const FAssetData & AssetData, + ALandscapeProxy* Landscape, + UHoudiniOutput * InOutput, + int32 MaterialIdx) +{ + TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx); + TSharedPtr< SComboButton > AssetComboButton = LandscapeMaterialInterfaceComboButtons[Pair]; + if (AssetComboButton.IsValid()) + { + AssetComboButton->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); + } +} +*/ + +void +FHoudiniOutputDetails::CreateDefaultOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput) +{ + if (!InOutput) + return; + + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouOutputCategory.GetParentLayout().GetThumbnailPool(); + + // TODO + // This is just a temporary placeholder displaying name/output type + { + FString OutputNameStr = InOutput->GetName(); + FText OutputTooltip = GetOutputTooltip(InOutput); + + // Create a new detail row + // Name + FText OutputNameTxt = GetOutputDebugName(InOutput); + FDetailWidgetRow & Row = HouOutputCategory.AddCustomRow(FText::GetEmpty()); + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(OutputNameTxt) + .ToolTipText(OutputTooltip) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + + // Value + FText OutputTypeTxt = GetOutputDebugDescription(InOutput); + Row.ValueWidget.Widget = + SNew(STextBlock) + .Text(OutputTypeTxt) + .ToolTipText(OutputTooltip) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + } +} + +void +FHoudiniOutputDetails::OnBakeOutputObject( + const FString& InBakeName, + UObject * BakedOutputObject, + const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FHoudiniGeoPartObject & HGPO, + const UObject* OutputOwner, + const FString & HoudiniAssetName, + const FString & BakeFolder, + const FString & TempCookFolder, + const EHoudiniOutputType & Type, + const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, + const TArray& InAllOutputs) +{ + if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) + return; + + // Fill in the package params + FHoudiniPackageParams PackageParams; + // Configure FHoudiniAttributeResolver and fill the package params with resolved object name and bake folder. + // The resolver is then also configured with the package params for subsequent resolving (level_path etc) + FHoudiniAttributeResolver Resolver; + // Determine the relevant WorldContext based on the output owner + UWorld* WorldContext = OutputOwner ? OutputOwner->GetWorld() : GWorld; + const UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(OutputOwner); + check(IsValid(HAC)); + const bool bAutomaticallySetAttemptToLoadMissingPackages = true; + const bool bSkipObjectNameResolutionAndUseDefault = !InBakeName.IsEmpty(); // If InBakeName is set use it as is for the object name + const bool bSkipBakeFolderResolutionAndUseDefault = false; + FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( + WorldContext, HAC, OutputIdentifier, InOutputObject, BakedOutputObject->GetName(), + HoudiniAssetName, PackageParams, Resolver, + BakeFolder, EPackageReplaceMode::ReplaceExistingAssets, + bAutomaticallySetAttemptToLoadMissingPackages, bSkipObjectNameResolutionAndUseDefault, + bSkipBakeFolderResolutionAndUseDefault); + + switch (Type) + { + case EHoudiniOutputType::Mesh: + { + UStaticMesh* StaticMesh = Cast(BakedOutputObject); + if (StaticMesh) + { + FDirectoryPath TempCookFolderPath; + TempCookFolderPath.Path = TempCookFolder; + TMap AlreadyBakedMaterialsMap; + UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( + StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath, AlreadyBakedMaterialsMap); + } + } + break; + case EHoudiniOutputType::Curve: + { + USplineComponent* SplineComponent = Cast(BakedOutputObject); + if (SplineComponent) + { + AActor* BakedActor; + USplineComponent* BakedSplineComponent; + FHoudiniEngineBakeUtils::BakeCurve(SplineComponent, GWorld->GetCurrentLevel(), PackageParams, BakedActor, BakedSplineComponent); + } + } + break; + case EHoudiniOutputType::Landscape: + { + ALandscapeProxy* Landscape = Cast(BakedOutputObject); + if (Landscape) + { + FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType); + } + } + break; + } +} + +FReply +FHoudiniOutputDetails::OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput) +{ + // TODO: Actually refine only the selected ProxyMesh + // For now, refine all the selection + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(true, true); + + FHoudiniEngineUtils::UpdateEditorProperties(InOutput->GetOuter(), true); + return FReply::Handled(); +} + +void +FHoudiniOutputDetails::OnBakeNameCommitted( + const FText& Val, ETextCommit::Type TextCommitType, + UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + if (!InOutput) + return; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + + if (!FoundOutputObject) + return; + + FoundOutputObject->BakeName = Val.ToString(); +} + +void +FHoudiniOutputDetails::OnRevertBakeNameToDefault(UHoudiniOutput * InOutput, const FHoudiniOutputObjectIdentifier & InIdentifier) +{ + if (!InOutput) + return; + + TMap& OutputObjects = InOutput->GetOutputObjects(); + FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + + if (!FoundOutputObject) + return; + + FoundOutputObject->BakeName = FString(); +} +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h index 0c121a712..be8862f71 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h @@ -1,226 +1,226 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "ContentBrowserDelegates.h" -#include "Materials/MaterialInterface.h" -#include "Components/Border.h" -#include "Components/ComboBox.h" - - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class UHoudiniOutput; -class UHoudiniAssetComponent; -class FAssetThumbnailPool; -class ALandscapeProxy; -class USplineComponent; -class UHoudiniLandscapePtr; -class UHoudiniLandscapeEditLayer; -class UHoudiniStaticMesh; -class UMaterialInterface; -class SBorder; -class SComboButton; - -struct FHoudiniGeoPartObject; -struct FHoudiniOutputObjectIdentifier; -struct FHoudiniOutputObject; - -enum class EHoudiniOutputType : uint8; -enum class EHoudiniLandscapeOutputBakeType : uint8; - -class FHoudiniOutputDetails : public TSharedFromThis -{ -public: - void CreateWidget( - IDetailCategoryBuilder& HouInputCategoryBuilder, - TArray InOutputs); - - void CreateMeshOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateCurveOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateStaticMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UStaticMesh * StaticMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject, - const bool& bIsProxyMeshCurrent); - - void CreateProxyMeshAndMaterialWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - UHoudiniStaticMesh * ProxyMesh, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, - const FString BakeFolder, - FHoudiniGeoPartObject& HoudiniGeoPartObject); - - void CreateCurveWidgets( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput, - USceneComponent* SplineComponent, - FHoudiniOutputObject& OutputObject, - FHoudiniOutputObjectIdentifier& OutputIdentifier, - FHoudiniGeoPartObject& HoudiniGeoPartObject); - - void CreateLandscapeOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - void CreateLandscapeOutputWidget_Helper( - IDetailCategoryBuilder & HouOutputCategory, - UHoudiniOutput * InOutput, - const FHoudiniGeoPartObject & HGPO, - UHoudiniLandscapePtr * LandscapePointer, - const FHoudiniOutputObjectIdentifier & OutputIdentifier); - - void CreateLandscapeEditLayerOutputWidget_Helper( - IDetailCategoryBuilder & HouOutputCategory, - UHoudiniOutput * InOutput, - const FHoudiniGeoPartObject & HGPO, - UHoudiniLandscapeEditLayer * LandscapeEditLayer, - const FHoudiniOutputObjectIdentifier & OutputIdentifier); - - void CreateInstancerOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput * InOutput); - - void CreateDefaultOutputWidget( - IDetailCategoryBuilder& HouOutputCategory, - UHoudiniOutput* InOutput); - - static FText GetOutputTooltip(UHoudiniOutput* MainOutput); - static FText GetOutputDebugName(UHoudiniOutput* InOutput); - static FText GetOutputDebugDescription(UHoudiniOutput* InOutput); - - static void OnBakeNameCommitted( - const FText& Val, ETextCommit::Type TextCommitType, - UHoudiniOutput * InOutput, - const FHoudiniOutputObjectIdentifier & InIdentifier); - - static void OnRevertBakeNameToDefault( - UHoudiniOutput * InOutput, - const FHoudiniOutputObjectIdentifier & InIdentifier); - - static void OnBakeOutputObject( - const FString& InBakeName, - UObject * BakedOutputObject, - const FHoudiniOutputObjectIdentifier & OutputIdentifier, - const FHoudiniOutputObject& InOutputObject, - const FHoudiniGeoPartObject & HGPO, - const UObject* OutputOwner, - const FString & HoudiniAssetName, - const FString & BakeFolder, - const FString & TempCookFolder, - const EHoudiniOutputType & Type, - const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, - const TArray& InAllOutputs); - - FReply OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput); - - // Gets the border brush to show around thumbnails, changes when the user hovers on it. - const FSlateBrush * GetThumbnailBorder(UObject* Mesh) const; - const FSlateBrush * GetMaterialInterfaceThumbnailBorder(UObject* Mesh, int32 MaterialIdx) const; - - // Delegate used to detect if valid object has been dragged and dropped. - bool OnMaterialInterfaceDraggedOver(const UObject * InObject) const; - - // Delegate used when a valid material has been drag and dropped on a mesh. - void OnMaterialInterfaceDropped( - UObject* InDroppedObject, - UStaticMesh* InStaticMesh, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Delegate used when a valid material has been drag and dropped on a landscape. - void OnMaterialInterfaceDropped( - UObject* InDroppedObject, - ALandscapeProxy* InLandscape, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Construct drop down menu content for material. - TSharedRef OnGetMaterialInterfaceMenuContent( - UMaterialInterface* MaterialInterface, - UObject* OutputObject, - UHoudiniOutput* InOutput, - int32 MaterialIdx); - - // Delegate for handling selection in content browser. - void OnMaterialInterfaceSelected( - const FAssetData & AssetData, - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx); - - // Delegate for handling Use CB selection arrow button clicked. - void OnUseContentBrowserSelectedMaterialInterface( - UObject* OutputObject, - UHoudiniOutput * InOutput, - int32 MaterialIdx); - - // Closes the combo button. - void CloseMaterialInterfaceComboButton(); - - // Browse to material interface. - void OnBrowseTo(UObject* InObject); - - // Handler for reset material interface button. - FReply OnResetMaterialInterfaceClicked( - UStaticMesh * StaticMesh, UHoudiniOutput * InOutput, int32 MaterialIdx); - - FReply OnResetMaterialInterfaceClicked( - ALandscapeProxy* InLandscape, UHoudiniOutput * InHoudiniOutput, int32 InMaterialIdx); - - // Handler for when static mesh thumbnail is double clicked. We open editor in this case. - FReply OnThumbnailDoubleClick( - const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object); - - // Handler for bake individual static mesh action. - // static FReply OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject); - -private: - - // Map of meshes and corresponding thumbnail borders. - TMap> OutputObjectThumbnailBorders; - // Map of meshes / material indices to thumbnail borders. - TMap, TSharedPtr> MaterialInterfaceThumbnailBorders; - // Map of meshes / material indices to combo elements. - TMap, TSharedPtr> MaterialInterfaceComboButtons; - - /** Delegate for filtering material interfaces. **/ - FOnShouldFilterAsset OnShouldFilterMaterialInterface; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "ContentBrowserDelegates.h" +#include "Materials/MaterialInterface.h" +#include "Components/Border.h" +#include "Components/ComboBox.h" + + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class UHoudiniOutput; +class UHoudiniAssetComponent; +class FAssetThumbnailPool; +class ALandscapeProxy; +class USplineComponent; +class UHoudiniLandscapePtr; +class UHoudiniLandscapeEditLayer; +class UHoudiniStaticMesh; +class UMaterialInterface; +class SBorder; +class SComboButton; + +struct FHoudiniGeoPartObject; +struct FHoudiniOutputObjectIdentifier; +struct FHoudiniOutputObject; + +enum class EHoudiniOutputType : uint8; +enum class EHoudiniLandscapeOutputBakeType : uint8; + +class FHoudiniOutputDetails : public TSharedFromThis +{ +public: + void CreateWidget( + IDetailCategoryBuilder& HouInputCategoryBuilder, + TArray InOutputs); + + void CreateMeshOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateCurveOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateStaticMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UStaticMesh * StaticMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject, + const bool& bIsProxyMeshCurrent); + + void CreateProxyMeshAndMaterialWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + UHoudiniStaticMesh * ProxyMesh, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + const FString HoudiniAssetName, + const FString BakeFolder, + FHoudiniGeoPartObject& HoudiniGeoPartObject); + + void CreateCurveWidgets( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput, + USceneComponent* SplineComponent, + FHoudiniOutputObject& OutputObject, + FHoudiniOutputObjectIdentifier& OutputIdentifier, + FHoudiniGeoPartObject& HoudiniGeoPartObject); + + void CreateLandscapeOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + void CreateLandscapeOutputWidget_Helper( + IDetailCategoryBuilder & HouOutputCategory, + UHoudiniOutput * InOutput, + const FHoudiniGeoPartObject & HGPO, + UHoudiniLandscapePtr * LandscapePointer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier); + + void CreateLandscapeEditLayerOutputWidget_Helper( + IDetailCategoryBuilder & HouOutputCategory, + UHoudiniOutput * InOutput, + const FHoudiniGeoPartObject & HGPO, + UHoudiniLandscapeEditLayer * LandscapeEditLayer, + const FHoudiniOutputObjectIdentifier & OutputIdentifier); + + void CreateInstancerOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput * InOutput); + + void CreateDefaultOutputWidget( + IDetailCategoryBuilder& HouOutputCategory, + UHoudiniOutput* InOutput); + + static FText GetOutputTooltip(UHoudiniOutput* MainOutput); + static FText GetOutputDebugName(UHoudiniOutput* InOutput); + static FText GetOutputDebugDescription(UHoudiniOutput* InOutput); + + static void OnBakeNameCommitted( + const FText& Val, ETextCommit::Type TextCommitType, + UHoudiniOutput * InOutput, + const FHoudiniOutputObjectIdentifier & InIdentifier); + + static void OnRevertBakeNameToDefault( + UHoudiniOutput * InOutput, + const FHoudiniOutputObjectIdentifier & InIdentifier); + + static void OnBakeOutputObject( + const FString& InBakeName, + UObject * BakedOutputObject, + const FHoudiniOutputObjectIdentifier & OutputIdentifier, + const FHoudiniOutputObject& InOutputObject, + const FHoudiniGeoPartObject & HGPO, + const UObject* OutputOwner, + const FString & HoudiniAssetName, + const FString & BakeFolder, + const FString & TempCookFolder, + const EHoudiniOutputType & Type, + const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, + const TArray& InAllOutputs); + + FReply OnRefineClicked(UObject* ObjectToRefine, UHoudiniOutput* InOutput); + + // Gets the border brush to show around thumbnails, changes when the user hovers on it. + const FSlateBrush * GetThumbnailBorder(UObject* Mesh) const; + const FSlateBrush * GetMaterialInterfaceThumbnailBorder(UObject* Mesh, int32 MaterialIdx) const; + + // Delegate used to detect if valid object has been dragged and dropped. + bool OnMaterialInterfaceDraggedOver(const UObject * InObject) const; + + // Delegate used when a valid material has been drag and dropped on a mesh. + void OnMaterialInterfaceDropped( + UObject* InDroppedObject, + UStaticMesh* InStaticMesh, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Delegate used when a valid material has been drag and dropped on a landscape. + void OnMaterialInterfaceDropped( + UObject* InDroppedObject, + ALandscapeProxy* InLandscape, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Construct drop down menu content for material. + TSharedRef OnGetMaterialInterfaceMenuContent( + UMaterialInterface* MaterialInterface, + UObject* OutputObject, + UHoudiniOutput* InOutput, + int32 MaterialIdx); + + // Delegate for handling selection in content browser. + void OnMaterialInterfaceSelected( + const FAssetData & AssetData, + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx); + + // Delegate for handling Use CB selection arrow button clicked. + void OnUseContentBrowserSelectedMaterialInterface( + UObject* OutputObject, + UHoudiniOutput * InOutput, + int32 MaterialIdx); + + // Closes the combo button. + void CloseMaterialInterfaceComboButton(); + + // Browse to material interface. + void OnBrowseTo(UObject* InObject); + + // Handler for reset material interface button. + FReply OnResetMaterialInterfaceClicked( + UStaticMesh * StaticMesh, UHoudiniOutput * InOutput, int32 MaterialIdx); + + FReply OnResetMaterialInterfaceClicked( + ALandscapeProxy* InLandscape, UHoudiniOutput * InHoudiniOutput, int32 InMaterialIdx); + + // Handler for when static mesh thumbnail is double clicked. We open editor in this case. + FReply OnThumbnailDoubleClick( + const FGeometry & InMyGeometry, const FPointerEvent & InMouseEvent, UObject * Object); + + // Handler for bake individual static mesh action. + // static FReply OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject); + +private: + + // Map of meshes and corresponding thumbnail borders. + TMap> OutputObjectThumbnailBorders; + // Map of meshes / material indices to thumbnail borders. + TMap, TSharedPtr> MaterialInterfaceThumbnailBorders; + // Map of meshes / material indices to combo elements. + TMap, TSharedPtr> MaterialInterfaceComboButtons; + + /** Delegate for filtering material interfaces. **/ + FOnShouldFilterAsset OnShouldFilterMaterialInterface; }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp index 411857626..a20219861 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.cpp @@ -1,2636 +1,2636 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPDGDetails.h" - -#include "HoudiniEngineEditorPrivatePCH.h" - -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPDGManager.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAssetActor.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniEngineDetails.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorUtils.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "IDetailGroup.h" -#include "IDetailCustomization.h" -#include "PropertyCustomizationHelpers.h" -#include "DetailWidgetRow.h" -#include "ScopedTransaction.h" -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/SBoxPanel.h" -#include "Widgets/Layout/SSpacer.h" -#include "Framework/SlateDelegates.h" -#include "Templates/SharedPointer.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -#define HOUDINI_ENGINE_UI_SECTION_PDG_BAKE 2 - -void -FHoudiniPDGDetails::CreateWidget( - IDetailCategoryBuilder& HouPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // PDG ASSET - FHoudiniPDGDetails::AddPDGAssetWidget(HouPDGCategory, InPDGAssetLink); - - // TOP NETWORKS - FHoudiniPDGDetails::AddTOPNetworkWidget(HouPDGCategory, InPDGAssetLink); - - // PDG EVENT MESSAGES -} - - -void -FHoudiniPDGDetails::AddPDGAssetWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // PDG STATUS ROW - AddPDGAssetStatus(InPDGCategory, InPDGAssetLink); - - // Commandlet Status row - AddPDGCommandletStatus(InPDGCategory, FHoudiniEngine::Get().GetPDGCommandletStatus()); - - // REFRESH / RESET Buttons - { - TSharedRef RefreshHBox = SNew(SHorizontalBox); - TSharedPtr ResetHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGRefreshResetRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Refresh", "Refresh")) - .ToolTipText(LOCTEXT("RefreshTooltip", "Refreshes infos displayed by the the PDG Asset Link")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .OnClicked_Lambda([InPDGAssetLink]() - { - FHoudiniPDGDetails::RefreshPDGAssetLink(InPDGAssetLink); - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(RefreshHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Reset", "Reset")) - .ToolTipText(LOCTEXT("ResetTooltip", "Resets the PDG Asset Link")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .OnClicked_Lambda([InPDGAssetLink]() - { - // TODO: RESET USELESS? - FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(ResetHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr RefreshIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGRefreshIconBrush(); - if (RefreshIconBrush.IsValid()) - { - TSharedPtr RefreshImage; - RefreshHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(RefreshImage, SImage) - ] - ]; - - RefreshImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([RefreshIconBrush]() { return RefreshIconBrush.Get(); }))); - } - - RefreshHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Refresh", "Refresh")) - ]; - - TSharedPtr ResetIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGResetIconBrush(); - if (ResetIconBrush.IsValid()) - { - TSharedPtr ResetImage; - ResetHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(ResetImage, SImage) - ] - ]; - - ResetImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([ResetIconBrush]() { return ResetIconBrush.Get(); }))); - } - - ResetHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Reset", "Reset")) - ]; - } - - // TOP NODE FILTER - { - FText Tooltip = FText::FromString(TEXT("When enabled, the TOP Node Filter will only display the TOP Nodes found in the current network that start with the filter prefix. Disabling the Filter will display all of the TOP Network's TOP Nodes.")); - // Lambda for changing the filter value - auto ChangeTOPNodeFilter = [InPDGAssetLink](const FString& NewValue) - { - if (InPDGAssetLink->TOPNodeFilter.Equals(NewValue)) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->TOPNodeFilter = NewValue; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter), InPDGAssetLink); - }; - - FDetailWidgetRow& PDGFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGFilterRow, InPDGAssetLink); - PDGFilterRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox enable filter - SNew(SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bUseTOPNodeFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (InPDGAssetLink->bUseTOPNodeFilter == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bUseTOPNodeFilter = bNewState; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node Filter"))) - .ToolTipText(Tooltip) - ]; - - PDGFilterRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .ToolTipText(Tooltip) - .Text_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return FText(); - return FText::FromString(InPDGAssetLink->TOPNodeFilter); - }) - .OnTextCommitted_Lambda([ChangeTOPNodeFilter](const FText& Val, ETextCommit::Type TextCommitType) - { - ChangeTOPNodeFilter(Val.ToString()); - }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([=]() - { - FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER); - ChangeTOPNodeFilter(DefaultFilter); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - } - - // TOP OUTPUT FILTER - { - // Lambda for changing the filter value - FText Tooltip = FText::FromString(TEXT("When enabled, the Work Item Output Files created for the TOP Nodes found in the current network that start with the filter prefix will be automatically loaded int the world after being cooked.")); - auto ChangeTOPOutputFilter = [InPDGAssetLink](const FString& NewValue) - { - if (InPDGAssetLink->TOPOutputFilter.Equals(NewValue)) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->TOPOutputFilter = NewValue; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter), InPDGAssetLink); - }; - - FDetailWidgetRow& PDGOutputFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGOutputFilterRow, InPDGAssetLink); - - PDGOutputFilterRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox enable filter - SNew(SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bUseTOPOutputFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (InPDGAssetLink->bUseTOPOutputFilter == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bUseTOPOutputFilter = bNewState; - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Output Filter"))) - .ToolTipText(Tooltip) - ]; - - PDGOutputFilterRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SNew(SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return FText(); - return FText::FromString(InPDGAssetLink->TOPOutputFilter); - }) - .OnTextCommitted_Lambda([ChangeTOPOutputFilter](const FText& Val, ETextCommit::Type TextCommitType) - { - ChangeTOPOutputFilter(Val.ToString()); - }) - .ToolTipText(Tooltip) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([ChangeTOPOutputFilter]() - { - FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER); - ChangeTOPOutputFilter(DefaultFilter); - return FReply::Handled(); - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ]; - } - - // Checkbox: Autocook - { - FText Tooltip = FText::FromString(TEXT("When enabled, the selected TOP Network's output will automatically cook after succesfully cooking the PDG Asset Link HDA.")); - FDetailWidgetRow& PDGAutocookRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGAutocookRow, InPDGAssetLink); - PDGAutocookRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Auto-cook"))) - .ToolTipText(Tooltip) - ]; - - TSharedPtr AutoCookCheckBox; - PDGAutocookRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(AutoCookCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bAutoCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - if (!InPDGAssetLink || InPDGAssetLink->bAutoCook == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bAutoCook = bNewState; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bAutoCook), InPDGAssetLink); - }) - .ToolTipText(Tooltip) - ]; - } - // Output parent actor selector - { - IDetailPropertyRow* PDGOutputParentActorRow = InPDGCategory.AddExternalObjectProperty({ InPDGAssetLink }, "OutputParentActor"); - if (PDGOutputParentActorRow) - { - TAttribute PDGOutputParentActorRowEnabled; - BindDisableIfPDGNotLinked(PDGOutputParentActorRowEnabled, InPDGAssetLink); - PDGOutputParentActorRow->IsEnabled(PDGOutputParentActorRowEnabled); - TSharedPtr NameWidget; - TSharedPtr ValueWidget; - PDGOutputParentActorRow->GetDefaultWidgets(NameWidget, ValueWidget); - PDGOutputParentActorRow->DisplayName(FText::FromString(TEXT("Output Parent Actor"))); - PDGOutputParentActorRow->ToolTip(FText::FromString( - TEXT("The PDG Output Actors will be created under this parent actor. If not set, then the PDG Output Actors will be created under a new folder."))); - } - } - - // Add bake widgets for PDG output - CreatePDGBakeWidgets(InPDGCategory, InPDGAssetLink); - - // TODO: move this to a better place: the baking code is in HoudiniEngineEditor, the PDG manager (that knows about - // when work object results are loaded is in HoudiniEngine and the PDGAssetLink is in HoudiniEngineRuntime). So - // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? - if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) - InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); - InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded); - - // WORK ITEM STATUS - { - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - // Disable if PDG is not linked - DisableIfPDGNotLinked(PDGStatusRow, InPDGAssetLink); - FHoudiniPDGDetails::AddWorkItemStatusWidget( - PDGStatusRow, TEXT("Asset Work Item Status"), InPDGAssetLink, false); - } -} - -bool -FHoudiniPDGDetails::GetPDGStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor) -{ - OutPDGStatusString = FString(); - OutPDGStatusColor = FLinearColor::White; - - if (!IsValid(InPDGAssetLink)) - return false; - - switch (InPDGAssetLink->LinkState) - { - case EPDGLinkState::Linked: - OutPDGStatusString = TEXT("PDG is READY"); - OutPDGStatusColor = FLinearColor::Green; - break; - case EPDGLinkState::Linking: - OutPDGStatusString = TEXT("PDG is Linking"); - OutPDGStatusColor = FLinearColor::Yellow; - break; - case EPDGLinkState::Error_Not_Linked: - OutPDGStatusString = TEXT("PDG is ERRORED"); - OutPDGStatusColor = FLinearColor::Red; - break; - case EPDGLinkState::Inactive: - OutPDGStatusString = TEXT("PDG is INACTIVE"); - OutPDGStatusColor = FLinearColor::White; - break; - default: - return false; - } - - return true; -} - -void -FHoudiniPDGDetails::AddPDGAssetStatus( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink) -{ - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - FString PDGStatusString; - FLinearColor PDGStatusColor; - GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); - return FText::FromString(PDGStatusString); - }) - .ColorAndOpacity_Lambda([InPDGAssetLink]() - { - FString PDGStatusString; - FLinearColor PDGStatusColor; - GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); - return FSlateColor(PDGStatusColor); - }) - ] - ]; -} - -void -FHoudiniPDGDetails::GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor) -{ - OutStatusString = FString(); - OutStatusColor = FLinearColor::White; - switch (FHoudiniEngine::Get().GetPDGCommandletStatus()) - { - case EHoudiniBGEOCommandletStatus::Connected: - OutStatusString = TEXT("Async importer is CONNECTED"); - OutStatusColor = FLinearColor::Green; - break; - case EHoudiniBGEOCommandletStatus::Running: - OutStatusString = TEXT("Async importer is Running"); - OutStatusColor = FLinearColor::Yellow; - break; - case EHoudiniBGEOCommandletStatus::Crashed: - OutStatusString = TEXT("Async importer has CRASHED"); - OutStatusColor = FLinearColor::Red; - break; - case EHoudiniBGEOCommandletStatus::NotStarted: - OutStatusString = TEXT("Async importer is NOT STARTED"); - OutStatusColor = FLinearColor::White; - break; - } -} - -void -FHoudiniPDGDetails::AddPDGCommandletStatus( - IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus) -{ - FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Visibility_Lambda([]() - { - const UHoudiniRuntimeSettings* Settings = GetDefault(); - if (IsValid(Settings)) - { - return FHoudiniEngineCommands::IsPDGCommandletEnabled() ? EVisibility::Visible : EVisibility::Collapsed; - } - - return EVisibility::Visible; - }) - .Text_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetPDGCommandletStatus(StatusString, StatusColor); - return FText::FromString(StatusString); - }) - .ColorAndOpacity_Lambda([]() - { - FString StatusString; - FLinearColor StatusColor; - GetPDGCommandletStatus(StatusString, StatusColor); - return FSlateColor(StatusColor); - }) - ] - ]; -} - -bool -FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( - UHoudiniPDGAssetLink* InAssetLink, - bool bInForSelectedNode, - const FString& InTallyItemString, - int32& OutValue, - FLinearColor& OutColor) -{ - OutValue = 0; - OutColor = FLinearColor::White; - - if (!IsValid(InAssetLink)) - return false; - - bool bFound = false; - const FWorkItemTallyBase* TallyPtr = nullptr; - if (bInForSelectedNode) - { - UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); - if (TOPNode && !TOPNode->bHidden) - TallyPtr = &(TOPNode->GetWorkItemTally()); - } - else - TallyPtr = &(InAssetLink->WorkItemTally); - - if (TallyPtr) - { - if (InTallyItemString == TEXT("WAITING")) - { - // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI - OutValue = TallyPtr->NumWaitingWorkItems() + TallyPtr->NumScheduledWorkItems(); - OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("COOKING")) - { - OutValue = TallyPtr->NumCookingWorkItems(); - OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("COOKED")) - { - OutValue = TallyPtr->NumCookedWorkItems(); - OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; - bFound = true; - } - else if (InTallyItemString == TEXT("FAILED")) - { - OutValue = TallyPtr->NumErroredWorkItems(); - OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; - bFound = true; - } - } - - return bFound; -} - -void -FHoudiniPDGDetails::AddWorkItemStatusWidget( - FDetailWidgetRow& InRow, const FString& InTitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode) -{ - auto AddGridBox = [InAssetLink, bInForSelectedNode](const FString& Title) -> SHorizontalBox::FSlot& - { - return SHorizontalBox::Slot() - .MaxWidth(500.0f) - .Padding(0.0f, 0.0f, 2.0f, 0.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .AutoHeight() - .Padding(FMargin(1.0f, 2.0f)) - [ - SNew(SBorder) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) - .BorderBackgroundColor(FSlateColor(FLinearColor(0.6, 0.6, 0.6))) - .Padding(FMargin(1.0f, 5.0f)) - [ - SNew(SBox) - .WidthOverride(95.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text(FText::FromString(Title)) - .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FSlateColor(Color); - }) - ] - ] - ] - + SVerticalBox::Slot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .AutoHeight() - .Padding(FMargin(1.0f, 2.0f)) - [ - SNew(SBorder) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) - .BorderBackgroundColor(FSlateColor(FLinearColor(0.8, 0.8, 0.8))) - .Padding(FMargin(1.0f, 5.0f)) - [ - SNew(SBox) - .WidthOverride(95.0f) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(STextBlock) - .Text_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FText::AsNumber(Value); - }) - .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() - { - int32 Value; - FLinearColor Color; - GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); - return FSlateColor(Color); - }) - ] - ] - ] - ]; - }; - - InRow.WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(0.0f, 0.0f) - .AutoWidth() - [ - SNew(SVerticalBox) - +SVerticalBox::Slot() - [ - SNew(SSpacer) - ] - + SVerticalBox::Slot() - .AutoHeight() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(FMargin(0.0f, 2.0f)) - [ - SNew(STextBlock) - .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) - .Text(FText::FromString(InTitleString)) - - ] - + SVerticalBox::Slot() - .AutoHeight() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Padding(FMargin(0.0f, 2.0f)) - [ - SNew(SHorizontalBox) - + AddGridBox(TEXT("WAITING")) - + AddGridBox(TEXT("COOKING")) - + AddGridBox(TEXT("COOKED")) - + AddGridBox(TEXT("FAILED")) - ] - + SVerticalBox::Slot() - [ - SNew(SSpacer) - ] - ] - ]; -} - - -void -FHoudiniPDGDetails::AddTOPNetworkWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink ) -{ - if (!InPDGAssetLink->GetSelectedTOPNetwork()) - return; - - if (InPDGAssetLink->AllTOPNetworks.Num() <= 0) - return; - - TOPNetworksPtr.Reset(); - - FString GroupLabel = TEXT("TOP Networks"); - IDetailGroup& TOPNetWorkGrp = InPDGCategory.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), false, true); - - // Combobox: TOP Network - { - FDetailWidgetRow& PDGTOPNetRow = TOPNetWorkGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGTOPNetRow, InPDGAssetLink); - PDGTOPNetRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Network"))) - ]; - - // Fill the TOP Networks SharedString array - TOPNetworksPtr.SetNum(InPDGAssetLink->AllTOPNetworks.Num()); - for(int32 Idx = 0; Idx < InPDGAssetLink->AllTOPNetworks.Num(); Idx++) - { - const UTOPNetwork* Network = InPDGAssetLink->AllTOPNetworks[Idx]; - if (!IsValid(Network)) - { - TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( - Idx, - TEXT("Invalid"), - TEXT("Invalid") - )); - } - else - { - TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( - Idx, - FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Network->NodeName, Network->NodePath), - Network->NodePath - )); - } - } - - if(TOPNetworksPtr.Num() <= 0) - TOPNetworksPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, "----"))); - - // Lambda for selecting another TOPNet - auto OnTOPNetChanged = [InPDGAssetLink](TSharedPtr InNewChoice) - { - if (!InNewChoice.IsValid()) - return; - - const int32 NewChoice = InNewChoice->Value; - int32 NewSelectedIndex = -1; - if (InPDGAssetLink->AllTOPNetworks.IsValidIndex(NewChoice)) - NewSelectedIndex = NewChoice; - - if (InPDGAssetLink->SelectedTOPNetworkIndex == NewSelectedIndex) - return; - - if (NewSelectedIndex < 0) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->SelectedTOPNetworkIndex = NewSelectedIndex; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex), InPDGAssetLink); - }; - - TSharedPtr HorizontalBoxTOPNet; - TSharedPtr>> ComboBoxTOPNet; - int32 SelectedIndex = TOPNetworksPtr.IndexOfByPredicate([InPDGAssetLink](const TSharedPtr& InEntry) - { - return InEntry.IsValid() && InEntry->Value == InPDGAssetLink->SelectedTOPNetworkIndex; - }); - if (SelectedIndex < 0) - SelectedIndex = 0; - - PDGTOPNetRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .FillWidth(300.f) - .MaxWidth(300.f) - [ - SAssignNew(ComboBoxTOPNet, SComboBox>) - .OptionsSource(&TOPNetworksPtr) - .InitiallySelectedItem(TOPNetworksPtr[SelectedIndex]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); - const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryToolTip) - .Margin(2.0f) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnTOPNetChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnTOPNetChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString(InPDGAssetLink->GetSelectedTOPNetworkName()); - }) - .ToolTipText_Lambda([InPDGAssetLink]() - { - UTOPNetwork const * const Network = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(Network)) - { - if (!Network->NodePath.IsEmpty()) - return FText::FromString(Network->NodePath); - else - return FText::FromString(Network->NodeName); - } - else - { - return FText(); - } - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - } - - // Buttons: DIRTY ALL / COOK OUTPUT - { - TSharedRef DirtyAllHBox = SNew(SHorizontalBox); - TSharedPtr CookOutHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("DirtyAll", "Dirty All")) - .ToolTipText(LOCTEXT("DirtyAllTooltip", "Dirty all TOP nodes in the selected TOP network and clears all of its work item results.")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork()); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(TOPNetwork)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - FHoudiniPDGManager::DirtyAll(TOPNetwork); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - else - { - UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(TOPNetwork); - } - } - } - - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(DirtyAllHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("CookOut", "Cook Output")) - .ToolTipText(LOCTEXT("CookOutTooltip", "Cooks the output nodes of the selected TOP network")) - .ContentPadding(FMargin(5.0f, 5.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!IsValid(SelectedTOPNet)) - return false; - - // Disable if there any nodes in the network that are already cooking - return !SelectedTOPNet->AnyWorkItemsPending(); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::CookOutput(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CookOutHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr DirtyAllIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyAllIconBrush(); - if (DirtyAllIconBrush.IsValid()) - { - TSharedPtr DirtyAllImage; - DirtyAllHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(DirtyAllImage, SImage) - ] - ]; - - DirtyAllImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([DirtyAllIconBrush]() { return DirtyAllIconBrush.Get(); }))); - } - - DirtyAllHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("DirtyAll", "Dirty All")) - ]; - - TSharedPtr CookOutIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - if (CookOutIconBrush.IsValid()) - { - TSharedPtr CookOutImage; - CookOutHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookOutImage, SImage) - ] - ]; - - CookOutImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookOutIconBrush]() { return CookOutIconBrush.Get(); }))); - } - - CookOutHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookOut", "Cook Output")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: PAUSE COOK / CANCEL COOK - { - TSharedRef PauseHBox = SNew(SHorizontalBox); - TSharedPtr CancelHBox = SNew(SHorizontalBox); - - FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Pause", "Pause Cook")) - .ToolTipText(LOCTEXT("PauseTooltip", "Pauses cooking for the selected TOP Network")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::PauseCook(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(PauseHBox, SHorizontalBox) - ] - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .WidthOverride(200.0f) - [ - SNew(SButton) - //.Text(LOCTEXT("Cancel", "Cancel Cook")) - .ToolTipText(LOCTEXT("CancelTooltip", "Cancels cooking the selected TOP network")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) - { - //InPDGAssetLink->WorkItemTally.ZeroAll(); - FHoudiniPDGManager::CancelCook(InPDGAssetLink->GetSelectedTOPNetwork()); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CancelHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr PauseIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGPauseIconBrush(); - if (PauseIconBrush.IsValid()) - { - TSharedPtr PauseImage; - PauseHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(PauseImage, SImage) - ] - ]; - - PauseImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([PauseIconBrush]() { return PauseIconBrush.Get(); }))); - } - - PauseHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Pause", "Pause Cook")) - ]; - - TSharedPtr CancelIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGCancelIconBrush(); - if (CancelIconBrush.IsValid()) - { - TSharedPtr CancelImage; - CancelHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CancelImage, SImage) - ] - ]; - - CancelImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CancelIconBrush]() { return CancelIconBrush.Get(); }))); - } - - CancelHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("Cancel", "Cancel Cook")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: Unload Work Item Objects - { - FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNetWorkGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork(); }) - .WidthOverride(200.0f) - [ - SNew(SButton) - .Text(LOCTEXT("UnloadWorkItemsForNetwork", "Unload All Work Item Objects")) - .ToolTipText(LOCTEXT("UnloadWorkItemsForNetworkTooltip", "Unloads / removes loaded work item results from level for all nodes in this network. Not undoable: use the \"Load Work Item Objects\" button on the individual TOP nodes to reload work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNetwork* const SelectedNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!IsValid(SelectedNet) || - INDEX_NONE == SelectedNet->AllTOPNodes.IndexOfByPredicate([](const UTOPNode* InNode) { return IsValid(InNode) && InNode->bCachedHaveLoadedWorkResults; })) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNetwork* const TOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(TOPNet)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - // Set the state to ToDelete, PDGManager will delete it when processing work items - TOPNet->SetLoadedWorkResultsToDelete(); - } - else - { - // Delete and unload the result objects and actors now - TOPNet->DeleteWorkResultOutputObjects(); - } - } - } - - return FReply::Handled(); - }) - ] - ] - ]; - } - - // TOP NODE WIDGETS - FHoudiniPDGDetails::AddTOPNodeWidget(TOPNetWorkGrp, InPDGAssetLink); -} - -bool -FHoudiniPDGDetails::GetSelectedTOPNodeStatusAndColor(UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor) -{ - OutTOPNodeStatus = FString(); - OutTOPNodeStatusColor = FLinearColor::White; - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode) && !TOPNode->bHidden) - { - OutTOPNodeStatus = UHoudiniPDGAssetLink::GetTOPNodeStatus(TOPNode); - OutTOPNodeStatusColor = UHoudiniPDGAssetLink::GetTOPNodeStatusColor(TOPNode); - - return true; - } - } - - return false; -} - -void -FHoudiniPDGDetails::AddTOPNodeWidget( - IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink ) -{ - if (!InPDGAssetLink->GetSelectedTOPNetwork()) - return; - - FString GroupLabel = TEXT("TOP Nodes"); - IDetailGroup& TOPNodesGrp = InGroup.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), true); - - // Combobox: TOP Node - { - FDetailWidgetRow& PDGTOPNodeRow = TOPNodesGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGTOPNodeRow, InPDGAssetLink); - PDGTOPNodeRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node"))) - ]; - - // Update the TOP Node SharedString - TOPNodesPtr.Reset(); - TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, LOCTEXT("ComboBoxEntryNoSelectedTOPNode", "- Select -").ToString()))); - const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNet)) - { - const int32 NumTOPNodes = SelectedTOPNet->AllTOPNodes.Num(); - for (int32 Idx = 0; Idx < NumTOPNodes; Idx++) - { - const UTOPNode* const Node = SelectedTOPNet->AllTOPNodes[Idx]; - if (!IsValid(Node) || Node->bHidden) - continue; - - TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip( - Idx, - FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Node->NodeName, Node->NodePath), - Node->NodePath - ))); - } - } - - FString NodeErrorText = FString(); - FString NodeErrorTooltip = FString(); - FLinearColor NodeErrorColor = FLinearColor::White; - if (!IsValid(SelectedTOPNet) || SelectedTOPNet->AllTOPNodes.Num() <= 0) - { - NodeErrorText = TEXT("No valid TOP Node found!"); - NodeErrorTooltip = TEXT("There is no valid TOP Node found in the selected TOP Network!"); - NodeErrorColor = FLinearColor::Red; - } - else if(TOPNodesPtr.Num() <= 0) - { - NodeErrorText = TEXT("No visible TOP Node found!"); - NodeErrorTooltip = TEXT("No visible TOP Node found, all nodes in this network are hidden. Please update your TOP Node Filter."); - NodeErrorColor = FLinearColor::Yellow; - } - - // Lambda for selecting a TOPNode - auto OnTOPNodeChanged = [InPDGAssetLink](TSharedPtr InNewChoice) - { - UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (!InNewChoice.IsValid() || !IsValid(TOPNetwork)) - return; - - const int32 NewChoice = InNewChoice->Value; - int32 NewSelectedIndex = INDEX_NONE; - if (TOPNetwork->AllTOPNodes.IsValidIndex(NewChoice)) - NewSelectedIndex = NewChoice; - - if (TOPNetwork->SelectedTOPIndex != NewSelectedIndex) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNetwork); - - TOPNetwork->Modify(); - TOPNetwork->SelectedTOPIndex = NewSelectedIndex; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UTOPNetwork, SelectedTOPIndex), TOPNetwork); - } - }; - - TSharedPtr HorizontalBoxTOPNode; - TSharedPtr>> ComboBoxTOPNode; - int32 SelectedIndex = 0; - UTOPNetwork* const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); - if (IsValid(SelectedTOPNetwork) && SelectedTOPNetwork->SelectedTOPIndex >= 0) - { - //SelectedIndex = InPDGAssetLink->GetSelectedTOPNetwork()->SelectedTOPIndex; - - // We need to match the selection by the index in the AllTopNodes array - // Because of the nodefilter, it is possible that the selected index does not match the index in TOPNodesPtr - const int32 SelectedTOPNodeIndex = SelectedTOPNetwork->SelectedTOPIndex; - // Find the matching UI index - for (int32 UIIndex = 0; UIIndex < TOPNodesPtr.Num(); UIIndex++) - { - if (TOPNodesPtr[UIIndex] && TOPNodesPtr[UIIndex]->Value == SelectedTOPNodeIndex) - { - // We found the UI Index that matches the current TOP Node! - SelectedIndex = UIIndex; - break; - } - } - } - - TSharedPtr ErrorText; - - PDGTOPNodeRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .FillWidth(300.f) - .MaxWidth(300.f) - [ - SAssignNew(ComboBoxTOPNode, SComboBox>) - .OptionsSource(&TOPNodesPtr) - .InitiallySelectedItem(TOPNodesPtr[SelectedIndex]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); - const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryToolTip) - .Margin(2.0f) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda([OnTOPNodeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) - { - return OnTOPNodeChanged(NewChoice); - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink, ComboBoxTOPNode, Options = TOPNodesPtr]() - { - if (IsValid(InPDGAssetLink)) - return FText::FromString(InPDGAssetLink->GetSelectedTOPNodeName()); - else - return FText(); - }) - .ToolTipText_Lambda([InPDGAssetLink]() - { - UTOPNode const * const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (!TOPNode->NodePath.IsEmpty()) - return FText::FromString(TOPNode->NodePath); - else - return FText::FromString(TOPNode->NodeName); - } - else - { - return FText(); - } - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - + SHorizontalBox::Slot() - .Padding(2, 2, 5, 2) - .AutoWidth() - [ - SAssignNew(ErrorText, STextBlock) - .Text(FText::FromString(NodeErrorText)) - .ToolTipText(FText::FromString(NodeErrorText)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .ColorAndOpacity(FLinearColor::Red) - //.ShadowColorAndOpacity(FLinearColor::Black) - ]; - - // Update the error text if needed - ErrorText->SetText(FText::FromString(NodeErrorText)); - ErrorText->SetToolTipText(FText::FromString(NodeErrorTooltip)); - ErrorText->SetColorAndOpacity(NodeErrorColor); - - // Hide the combobox if we have an error - ComboBoxTOPNode->SetVisibility(NodeErrorText.IsEmpty() ? EVisibility::Visible : EVisibility::Hidden); - } - - // TOP Node State - { - FDetailWidgetRow& PDGNodeStateResultRow = TOPNodesGrp.AddWidgetRow(); - DisableIfPDGNotLinked(PDGNodeStateResultRow, InPDGAssetLink); - PDGNodeStateResultRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("TOP Node State"))) - ]; - - PDGNodeStateResultRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - FString TOPNodeStatus = FString(); - FLinearColor TOPNodeStatusColor = FLinearColor::White; - GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); - return FText::FromString(TOPNodeStatus); - }) - .ColorAndOpacity_Lambda([InPDGAssetLink]() - { - FString TOPNodeStatus = FString(); - FLinearColor TOPNodeStatusColor = FLinearColor::White; - GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); - return FSlateColor(TOPNodeStatusColor); - }) - ]; - } - - // Checkbox: Load Work Item Output Files - { - auto ToolTipLambda = [InPDGAssetLink]() - { - bool bDisabled = false; - if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) - { - bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; - } - - return bDisabled - ? FText::FromString(TEXT("This node has child nodes, the auto-load setting must be set on the child nodes individually.")) - : FText::FromString(TEXT("When enabled, Output files produced by this TOP Node's Work Items will automatically be loaded when cooked.")); - }; - FDetailWidgetRow& PDGNodeAutoLoadRow = TOPNodesGrp.AddWidgetRow(); - - DisableIfPDGNotLinked(PDGNodeAutoLoadRow, InPDGAssetLink); - PDGNodeAutoLoadRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) - return true; - return false; - })); - - PDGNodeAutoLoadRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Auto-Load Work Item Output Files"))) - .ToolTipText_Lambda(ToolTipLambda) - ]; - - TSharedPtr AutoLoadCheckBox; - - PDGNodeAutoLoadRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(AutoLoadCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->GetSelectedTOPNode() - ? (InPDGAssetLink->GetSelectedTOPNode()->bAutoLoad ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - UTOPNode* TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(TOPNode) || TOPNode->bAutoLoad == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNode); - - TOPNode->Modify(); - TOPNode->bAutoLoad = bNewState; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UTOPNode, bAutoLoad), TOPNode); - - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - }) - .ToolTipText_Lambda(ToolTipLambda) - ]; - } - - // Checkbox: Work Item Output Files Visible - { - auto ToolTipLambda = [InPDGAssetLink]() - { - bool bDisabled = false; - if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) - { - bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; - } - - return bDisabled - ? FText::FromString(TEXT("This node has child nodes, visibility of work item outputs must be set on the child nodes individually.")) - : FText::FromString(TEXT("Toggles the visibility of the actors created from this TOP Node's Work Item File Outputs.")); - }; - - FDetailWidgetRow& PDGNodeShowResultRow = TOPNodesGrp.AddWidgetRow(); - // DisableIfPDGNotLinked(PDGNodeShowResultRow, InPDGAssetLink); - PDGNodeShowResultRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) - return true; - - return false; - })); - PDGNodeShowResultRow.NameWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Work Item Output Files Visible"))) - .ToolTipText_Lambda(ToolTipLambda) - ]; - - TSharedPtr ShowResCheckBox; - PDGNodeShowResultRow.ValueWidget.Widget = - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - [ - // Checkbox - SAssignNew(ShowResCheckBox, SCheckBox) - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->GetSelectedTOPNode() - ? (InPDGAssetLink->GetSelectedTOPNode()->IsVisibleInLevel() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(TOPNode) || TOPNode->IsVisibleInLevel() == bNewState) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - TOPNode); - - TOPNode->Modify(); - TOPNode->SetVisibleInLevel(bNewState); - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(TEXT("bShow"), TOPNode); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - }) - .ToolTipText_Lambda(ToolTipLambda) - ]; - } - - // Buttons: DIRTY NODE / COOK NODE - { - TSharedRef DirtyHBox = SNew(SHorizontalBox); - TSharedPtr CookHBox = SNew(SHorizontalBox); - - TSharedPtr DirtyButton; - TSharedPtr CookButton; - - FDetailWidgetRow& PDGDirtyCookRow = TOPNodesGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); - }) - .WidthOverride(200.0f) - [ - SAssignNew(DirtyButton, SButton) - //.Text(LOCTEXT("DirtyNode", "Dirty Node")) - .ToolTipText(LOCTEXT("DirtyNodeTooltip", "Dirties the selected TOP node and clears its work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - FHoudiniPDGManager::DirtyTOPNode(TOPNode); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - else - { - UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(TOPNode); - } - } - } - - return FReply::Handled(); - }) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink->GetSelectedTOPNode()) && !InPDGAssetLink->GetSelectedTOPNode()->bHidden) - return true; - return false; - }) - .Content() - [ - SAssignNew(DirtyHBox, SHorizontalBox) - ] - ] - ] - // + SHorizontalBox::Slot() - // .AutoWidth() - // [ - // SNew(SBox) - // .WidthOverride(200.0f) - // [ - // SAssignNew(DirtyButton, SButton) - // .Text(LOCTEXT("DirtyAllTasks", "Dirty All Tasks")) - // .ToolTipText(LOCTEXT("DirtyAllTasksTooltip", "Dirties all tasks/work items on the selected TOP node.")) - // .ContentPadding(FMargin(5.0f, 2.0f)) - // .VAlign(VAlign_Center) - // .HAlign(HAlign_Center) - // .OnClicked_Lambda([InPDGAssetLink]() - // { - // if(InPDGAssetLink->GetSelectedTOPNode()) - // { - // FHoudiniPDGManager::DirtyAllTasksOfTOPNode(*(InPDGAssetLink->GetSelectedTOPNode())); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - // } - // - // return FReply::Handled(); - // }) - // ] - // ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(200.0f) - [ - SAssignNew(CookButton, SButton) - //.Text(LOCTEXT("CookNode", "Cook Node")) - .ToolTipText(LOCTEXT("CookNodeTooltip", "Cooks the selected TOP Node.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsPDGLinked(InPDGAssetLink)) - return false; - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode)) - return false; - // Disable Cook Node button if the node is already cooking - return !SelectedNode->bHidden && SelectedNode->NodeState != EPDGNodeState::Cooking && !SelectedNode->AnyWorkItemsPending(); - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(Node)) - { - FHoudiniPDGManager::CookTOPNode(Node); - // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); - } - return FReply::Handled(); - }) - .Content() - [ - SAssignNew(CookHBox, SHorizontalBox) - ] - ] - ] - ]; - - TSharedPtr DirtyIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyNodeIconBrush(); - if (DirtyIconBrush.IsValid()) - { - TSharedPtr DirtyImage; - DirtyHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(DirtyImage, SImage) - ] - ]; - - DirtyImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([DirtyIconBrush]() { return DirtyIconBrush.Get(); }))); - } - - DirtyHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("DirtyNode", "Dirty Node")) - ]; - - TSharedPtr CookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); - if (CookIconBrush.IsValid()) - { - TSharedPtr CookImage; - CookHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(CookImage, SImage) - ] - ]; - - CookImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([CookIconBrush]() { return CookIconBrush.Get(); }))); - } - - CookHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(LOCTEXT("CookNode", "Cook Node")) - ]; - - DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); - } - - // Buttons: Load Work Item Objects / Unload Work Item Objects - { - TSharedPtr UnloadWorkItemsButton; - TSharedPtr LoadWorkItemsButton; - - FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNodesGrp.AddWidgetRow() - .WholeRowContent() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() - { - return IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode()); - }) - .WidthOverride(200.0f) - [ - SAssignNew(UnloadWorkItemsButton, SButton) - .Text(LOCTEXT("UnloadWorkItemsForNode", "Unload Work Item Objects")) - .ToolTipText(LOCTEXT("UnloadWorkItemsForNodeTooltip", "Unloads / removes loaded work item results from level. Not undoable: use the \"Load Work Item Objects\" button to reload the results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveLoadedWorkResults) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(TOPNode)) - { - if (IsPDGLinked(InPDGAssetLink)) - { - // Set the state to ToDelete, PDGManager will delete it when processing work items - TOPNode->SetLoadedWorkResultsToDelete(); - } - else - { - // Delete and unload the result objects and actors now - TOPNode->DeleteWorkResultOutputObjects(); - } - } - } - - return FReply::Handled(); - }) - ] - ] - + SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(200.0f) - [ - SAssignNew(LoadWorkItemsButton, SButton) - .Text(LOCTEXT("LoadWorkItems", "Load Work Item Objects")) - .ToolTipText(LOCTEXT("LoadWorkItemsForNodeTooltip", "Loads any available but not loaded work items objects (this could include items from a previous cook). Creates output actors. Not undoable: use the \"Unload Work Item Objects\" button to unload/remove loaded work item results.")) - .ContentPadding(FMargin(5.0f, 2.0f)) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .IsEnabled_Lambda([InPDGAssetLink]() - { - if (!IsValid(InPDGAssetLink)) - return false; - - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveNotLoadedWorkResults) - return false; - - return true; - }) - .OnClicked_Lambda([InPDGAssetLink]() - { - if (IsValid(InPDGAssetLink)) - { - UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); - if (IsValid(SelectedNode)) - { - SelectedNode->SetNotLoadedWorkResultsToLoad(true); - } - } - return FReply::Handled(); - }) - ] - ] - ]; - } - - // TOP Node WorkItem Status - { - if (InPDGAssetLink->GetSelectedTOPNode()) - { - FDetailWidgetRow& PDGNodeWorkItemStatsRow = TOPNodesGrp.AddWidgetRow(); - DisableIfPDGNotLinked(PDGNodeWorkItemStatsRow, InPDGAssetLink); - FHoudiniPDGDetails::AddWorkItemStatusWidget( - PDGNodeWorkItemStatsRow, TEXT("TOP Node Work Item Status"), InPDGAssetLink, true); - } - } -} - -void -FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // Repopulate the network and nodes for the assetlink - if (!FHoudiniPDGManager::UpdatePDGAssetLink(InPDGAssetLink)) - return; - - FHoudiniPDGDetails::RefreshUI(InPDGAssetLink, true); -} - -void -FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // Update the workitem stats - InPDGAssetLink->UpdateWorkItemTally(); - - // Update the editor properties - FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, InFullUpdate); -} - -void -FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) -{ - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); - - if (!InPDGAssetLink->bBakeMenuExpanded) - return; - - auto OnBakeButtonClickedLambda = [InPDGAssetLink]() - { - switch (InPDGAssetLink->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(InPDGAssetLink); - // else - FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->PDGBakeSelectionOption, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); - } - break; - - case EHoudiniEngineBakeOption::ToBlueprint: - { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceWithBlueprint(InPDGAssetLink); - // else - FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink, InPDGAssetLink->PDGBakeSelectionOption, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); - } - break; - // - // case EHoudiniEngineBakeOption::ToFoliage: - // { - // if (InPDGAssetLink->bIsReplace) - // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(InPDGAssetLink); - // else - // FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InPDGAssetLink); - // } - // break; - // - // case EHoudiniEngineBakeOption::ToWorldOutliner: - // { - // if (InPDGAssetLink->bIsReplace) - // { - // // Todo - // } - // else - // { - // //Todo - // } - // } - // break; - } - - return FReply::Handled(); - }; - - auto OnBakeFolderTextCommittedLambda = [InPDGAssetLink](const FText& Val, ETextCommit::Type TextCommitType) - { - FString NewPathStr = Val.ToString(); - if (NewPathStr.IsEmpty()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - //Todo? Check if the new Bake folder path is valid - InPDGAssetLink->Modify(); - InPDGAssetLink->BakeFolder.Path = NewPathStr; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, BakeFolder), InPDGAssetLink); - }; - - // Button Row - FDetailWidgetRow & ButtonRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(ButtonRow, InPDGAssetLink); - - TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); - - // Bake Button - TSharedRef BakeHBox = SNew(SHorizontalBox); - TSharedPtr BakeButton; - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(15.f, 0.0f, 0.0f, 0.0f) - .MaxWidth(75.0f) - [ - SNew(SBox) - .WidthOverride(75.0f) - [ - SAssignNew(BakeButton, SButton) - //.Text(FText::FromString("Bake")) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - //.ToolTipText(LOCTEXT("HoudiniPDGDetailsBakeButton", "Bake the Houdini PDG TOP Node(s)")) - .ToolTipText_Lambda([InPDGAssetLink]() - { - switch (InPDGAssetLink->HoudiniEngineBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - { - return LOCTEXT( - "HoudiniEnginePDGBakeButtonBakeToActorToolTip", - "Bake this Houdini PDG Asset's output assets and seperate the output actors from the PDG asset link."); - } - break; - case EHoudiniEngineBakeOption::ToBlueprint: - { - return LOCTEXT( - "HoudiniEnginePDGBakeButtonBakeToBlueprintToolTip", - "Bake this Houdini PDG Asset's output assets to blueprints and remove temporary output actors that no " - "longer has output components from the PDG asset link."); - } - break; - default: - { - return FText(); - } - } - }) - .Visibility(EVisibility::Visible) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .OnClicked_Lambda(OnBakeButtonClickedLambda) - .Content() - [ - SAssignNew(BakeHBox, SHorizontalBox) - ] - ] - ]; - - TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); - if (BakeIconBrush.IsValid()) - { - TSharedPtr BakeImage; - BakeHBox->AddSlot() - .MaxWidth(16.0f) - [ - SNew(SBox) - .WidthOverride(16.0f) - .HeightOverride(16.0f) - [ - SAssignNew(BakeImage, SImage) - ] - ]; - - BakeImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([BakeIconBrush]() { return BakeIconBrush.Get(); }))); - } - - BakeHBox->AddSlot() - .Padding(5.0, 0.0, 0.0, 0.0) - .VAlign(VAlign_Center) - .AutoWidth() - [ - SNew(STextBlock) - .Text(FText::FromString("Bake")) - ]; - - // bake Type ComboBox - TSharedPtr>> TypeComboBox; - - TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeTypeOptionsLabels(); - TSharedPtr IntialSelec; - if (OptionSource) - { - // IntialSelec = (*OptionSource)[(int)InPDGAssetLink->HoudiniEngineBakeOption]; - const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption); - const TSharedPtr* DefaultOption = OptionSource->FindByPredicate( - [DefaultStr](TSharedPtr InStringPtr) - { - return InStringPtr.IsValid() && *InStringPtr == DefaultStr; - } - ); - if (DefaultOption) - IntialSelec = *DefaultOption; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(93.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(93.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid() || SelectType == ESelectInfo::Type::Direct) - return; - - const EHoudiniEngineBakeOption NewOption = - FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->HoudiniEngineBakeOption) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->HoudiniEngineBakeOption = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink, TypeComboBox, OptionSource]() - { - return FText::FromString(FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - // bake selection ComboBox - TSharedPtr>> BakeSelectionComboBox; - - TArray>* PDGBakeSelectionOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeSelectionOptionsLabels(); - TSharedPtr PDGBakeSelectionIntialSelec; - if (PDGBakeSelectionOptionSource) - { - PDGBakeSelectionIntialSelec = (*PDGBakeSelectionOptionSource)[(int)InPDGAssetLink->PDGBakeSelectionOption]; - } - - ButtonRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(163.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(163.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(PDGBakeSelectionOptionSource) - .InitiallySelectedItem(PDGBakeSelectionIntialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EPDGBakeSelectionOption NewOption = - FHoudiniEngineEditor::Get().StringToPDGBakeSelectionOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->PDGBakeSelectionOption) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->PDGBakeSelectionOption = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromPDGBakeTargetOption(InPDGAssetLink->PDGBakeSelectionOption)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; - - // Bake package replacement mode row - FDetailWidgetRow & BakePackageReplaceRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(BakePackageReplaceRow, InPDGAssetLink); - - TSharedRef BakePackageReplaceRowHorizontalBox = SNew(SHorizontalBox); - - BakePackageReplaceRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeLabel", "Replace Mode")) - .ToolTipText( - LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeTooltip", "Replacement mode " - "during baking. Create new assets, using numerical suffixes in package names when necessary, or " - "replace existing assets with matching names. Also replaces the previous bake's output actors in " - "replacement mode vs creating incremental ones.")) - ] - ]; - - // bake package replace mode ComboBox - TSharedPtr>> BakePackageReplaceModeComboBox; - - TArray>* PDGBakePackageReplaceModeOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels(); - TSharedPtr PDGBakePackageReplaceModeInitialSelec; - if (PDGBakePackageReplaceModeOptionSource) - { - const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode); - const TSharedPtr* DefaultOption = PDGBakePackageReplaceModeOptionSource->FindByPredicate( - [DefaultStr](TSharedPtr InStringPtr) - { - return InStringPtr.IsValid() && *InStringPtr == DefaultStr; - } - ); - if (DefaultOption) - PDGBakePackageReplaceModeInitialSelec = *DefaultOption; - } - - BakePackageReplaceRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(3.0, 0.0, 4.0f, 0.0f) - .MaxWidth(163.f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(163.f) - [ - SAssignNew(TypeComboBox, SComboBox>) - .OptionsSource(PDGBakePackageReplaceModeOptionSource) - .InitiallySelectedItem(PDGBakePackageReplaceModeInitialSelec) - .OnGenerateWidget_Lambda( - [](TSharedPtr< FString > InItem) - { - const FText ChoiceEntryText = FText::FromString(*InItem); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - const EPDGBakePackageReplaceModeOption NewOption = - FHoudiniEngineEditor::Get().StringToPDGBakePackageReplaceModeOption(*NewChoice.Get()); - - if (NewOption != InPDGAssetLink->PDGBakePackageReplaceMode) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->PDGBakePackageReplaceMode = NewOption; - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode), InPDGAssetLink); - } - }) - [ - SNew(STextBlock) - .Text_Lambda([InPDGAssetLink]() - { - return FText::FromString( - FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - BakePackageReplaceRow.WholeRowWidget.Widget = BakePackageReplaceRowHorizontalBox; - - // Bake Folder Row - FDetailWidgetRow & BakeFolderRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - DisableIfPDGNotLinked(BakeFolderRow, InPDGAssetLink); - - TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .Padding(30.0f, 0.0f, 6.0f, 0.0f) - .MaxWidth(155.0f) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(155.0f) - [ - SNew(STextBlock) - .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) - .ToolTipText(LOCTEXT( - "HoudiniEnginePDGBakeFolderTooltip", - "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " - "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " - "plugin settings is used.")) - ] - ]; - - BakeFolderRowHorizontalBox->AddSlot() - /*.AutoWidth()*/ - .MaxWidth(235.0) - [ - SNew(SBox) - .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) - .WidthOverride(235.0f) - [ - SNew(SEditableTextBox) - .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - .ToolTipText(LOCTEXT( - "HoudiniEnginePDGBakeFolderTooltip", - "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " - "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " - "plugin settings is used.")) - .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) - .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) - ] - ]; - - BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; - - // Add additional bake options - FDetailWidgetRow & AdditionalBakeSettingsRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); - TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); - - TSharedPtr CheckBoxAutoBake; - TSharedPtr CheckBoxRecenterBakedActors; - - TSharedPtr LeftColumnVerticalBox; - TSharedPtr RightColumnVerticalBox; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(30.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - .WidthOverride(200.f) - [ - SAssignNew(LeftColumnVerticalBox, SVerticalBox) - ] - ]; - - AdditionalBakeSettingsRowHorizontalBox->AddSlot() - .Padding(20.0f, 5.0f, 0.0f, 0.0f) - .MaxWidth(200.f) - [ - SNew(SBox) - [ - SAssignNew(RightColumnVerticalBox, SVerticalBox) - ] - ]; - - LeftColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) - .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bRecenterBakedActors = bNewState; - - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bRecenterBakedActors), InPDGAssetLink); - }) - ] - ]; - - RightColumnVerticalBox->AddSlot() - .AutoHeight() - .Padding(0.0f, 0.0f, 0.0f, 3.5f) - [ - SNew(SBox) - .WidthOverride(160.f) - [ - SAssignNew(CheckBoxAutoBake, SCheckBox) - .Content() - [ - SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) - .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake work result object as they are loaded.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - .IsChecked_Lambda([InPDGAssetLink]() - { - return InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }) - .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) - { - const bool bNewState = (NewState == ECheckBoxState::Checked); - - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), - InPDGAssetLink); - - InPDGAssetLink->Modify(); - InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded = bNewState; - - // Notify that we have changed the property - FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( - GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterAllWorkResultObjectsLoaded), InPDGAssetLink); - }) - ] - ]; - - AdditionalBakeSettingsRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText) - : Text(InText) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText, const FString &InToolTip) - : Text(InText) - , ToolTip(InToolTip) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText) - : Text(InText) - , Value(InValue) -{ -} - -FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText, FString&& InToolTip) - : Text(InText) - , ToolTip(InToolTip) - , Value(InValue) -{ -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPDGDetails.h" + +#include "HoudiniEngineEditorPrivatePCH.h" + +#include "HoudiniPDGAssetLink.h" +#include "HoudiniPDGManager.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetActor.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniEngineDetails.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorUtils.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "IDetailGroup.h" +#include "IDetailCustomization.h" +#include "PropertyCustomizationHelpers.h" +#include "DetailWidgetRow.h" +#include "ScopedTransaction.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/SBoxPanel.h" +#include "Widgets/Layout/SSpacer.h" +#include "Framework/SlateDelegates.h" +#include "Templates/SharedPointer.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +#define HOUDINI_ENGINE_UI_SECTION_PDG_BAKE 2 + +void +FHoudiniPDGDetails::CreateWidget( + IDetailCategoryBuilder& HouPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // PDG ASSET + FHoudiniPDGDetails::AddPDGAssetWidget(HouPDGCategory, InPDGAssetLink); + + // TOP NETWORKS + FHoudiniPDGDetails::AddTOPNetworkWidget(HouPDGCategory, InPDGAssetLink); + + // PDG EVENT MESSAGES +} + + +void +FHoudiniPDGDetails::AddPDGAssetWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // PDG STATUS ROW + AddPDGAssetStatus(InPDGCategory, InPDGAssetLink); + + // Commandlet Status row + AddPDGCommandletStatus(InPDGCategory, FHoudiniEngine::Get().GetPDGCommandletStatus()); + + // REFRESH / RESET Buttons + { + TSharedRef RefreshHBox = SNew(SHorizontalBox); + TSharedPtr ResetHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGRefreshResetRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Refresh", "Refresh")) + .ToolTipText(LOCTEXT("RefreshTooltip", "Refreshes infos displayed by the the PDG Asset Link")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked_Lambda([InPDGAssetLink]() + { + FHoudiniPDGDetails::RefreshPDGAssetLink(InPDGAssetLink); + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(RefreshHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Reset", "Reset")) + .ToolTipText(LOCTEXT("ResetTooltip", "Resets the PDG Asset Link")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .OnClicked_Lambda([InPDGAssetLink]() + { + // TODO: RESET USELESS? + FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(ResetHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr RefreshIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGRefreshIconBrush(); + if (RefreshIconBrush.IsValid()) + { + TSharedPtr RefreshImage; + RefreshHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(RefreshImage, SImage) + ] + ]; + + RefreshImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([RefreshIconBrush]() { return RefreshIconBrush.Get(); }))); + } + + RefreshHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Refresh", "Refresh")) + ]; + + TSharedPtr ResetIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGResetIconBrush(); + if (ResetIconBrush.IsValid()) + { + TSharedPtr ResetImage; + ResetHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(ResetImage, SImage) + ] + ]; + + ResetImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([ResetIconBrush]() { return ResetIconBrush.Get(); }))); + } + + ResetHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Reset", "Reset")) + ]; + } + + // TOP NODE FILTER + { + FText Tooltip = FText::FromString(TEXT("When enabled, the TOP Node Filter will only display the TOP Nodes found in the current network that start with the filter prefix. Disabling the Filter will display all of the TOP Network's TOP Nodes.")); + // Lambda for changing the filter value + auto ChangeTOPNodeFilter = [InPDGAssetLink](const FString& NewValue) + { + if (InPDGAssetLink->TOPNodeFilter.Equals(NewValue)) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->TOPNodeFilter = NewValue; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter), InPDGAssetLink); + }; + + FDetailWidgetRow& PDGFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGFilterRow, InPDGAssetLink); + PDGFilterRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox enable filter + SNew(SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bUseTOPNodeFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (InPDGAssetLink->bUseTOPNodeFilter == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bUseTOPNodeFilter = bNewState; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node Filter"))) + .ToolTipText(Tooltip) + ]; + + PDGFilterRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .ToolTipText(Tooltip) + .Text_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return FText(); + return FText::FromString(InPDGAssetLink->TOPNodeFilter); + }) + .OnTextCommitted_Lambda([ChangeTOPNodeFilter](const FText& Val, ETextCommit::Type TextCommitType) + { + ChangeTOPNodeFilter(Val.ToString()); + }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([=]() + { + FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER); + ChangeTOPNodeFilter(DefaultFilter); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + } + + // TOP OUTPUT FILTER + { + // Lambda for changing the filter value + FText Tooltip = FText::FromString(TEXT("When enabled, the Work Item Output Files created for the TOP Nodes found in the current network that start with the filter prefix will be automatically loaded int the world after being cooked.")); + auto ChangeTOPOutputFilter = [InPDGAssetLink](const FString& NewValue) + { + if (InPDGAssetLink->TOPOutputFilter.Equals(NewValue)) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->TOPOutputFilter = NewValue; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter), InPDGAssetLink); + }; + + FDetailWidgetRow& PDGOutputFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGOutputFilterRow, InPDGAssetLink); + + PDGOutputFilterRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox enable filter + SNew(SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bUseTOPOutputFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (InPDGAssetLink->bUseTOPOutputFilter == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bUseTOPOutputFilter = bNewState; + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Output Filter"))) + .ToolTipText(Tooltip) + ]; + + PDGOutputFilterRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return FText(); + return FText::FromString(InPDGAssetLink->TOPOutputFilter); + }) + .OnTextCommitted_Lambda([ChangeTOPOutputFilter](const FText& Val, ETextCommit::Type TextCommitType) + { + ChangeTOPOutputFilter(Val.ToString()); + }) + .ToolTipText(Tooltip) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([ChangeTOPOutputFilter]() + { + FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER); + ChangeTOPOutputFilter(DefaultFilter); + return FReply::Handled(); + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ]; + } + + // Checkbox: Autocook + { + FText Tooltip = FText::FromString(TEXT("When enabled, the selected TOP Network's output will automatically cook after succesfully cooking the PDG Asset Link HDA.")); + FDetailWidgetRow& PDGAutocookRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGAutocookRow, InPDGAssetLink); + PDGAutocookRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Auto-cook"))) + .ToolTipText(Tooltip) + ]; + + TSharedPtr AutoCookCheckBox; + PDGAutocookRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(AutoCookCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bAutoCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + if (!InPDGAssetLink || InPDGAssetLink->bAutoCook == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bAutoCook = bNewState; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bAutoCook), InPDGAssetLink); + }) + .ToolTipText(Tooltip) + ]; + } + // Output parent actor selector + { + IDetailPropertyRow* PDGOutputParentActorRow = InPDGCategory.AddExternalObjectProperty({ InPDGAssetLink }, "OutputParentActor"); + if (PDGOutputParentActorRow) + { + TAttribute PDGOutputParentActorRowEnabled; + BindDisableIfPDGNotLinked(PDGOutputParentActorRowEnabled, InPDGAssetLink); + PDGOutputParentActorRow->IsEnabled(PDGOutputParentActorRowEnabled); + TSharedPtr NameWidget; + TSharedPtr ValueWidget; + PDGOutputParentActorRow->GetDefaultWidgets(NameWidget, ValueWidget); + PDGOutputParentActorRow->DisplayName(FText::FromString(TEXT("Output Parent Actor"))); + PDGOutputParentActorRow->ToolTip(FText::FromString( + TEXT("The PDG Output Actors will be created under this parent actor. If not set, then the PDG Output Actors will be created under a new folder."))); + } + } + + // Add bake widgets for PDG output + CreatePDGBakeWidgets(InPDGCategory, InPDGAssetLink); + + // TODO: move this to a better place: the baking code is in HoudiniEngineEditor, the PDG manager (that knows about + // when work object results are loaded is in HoudiniEngine and the PDGAssetLink is in HoudiniEngineRuntime). So + // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? + if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) + InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); + InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded); + + // WORK ITEM STATUS + { + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + // Disable if PDG is not linked + DisableIfPDGNotLinked(PDGStatusRow, InPDGAssetLink); + FHoudiniPDGDetails::AddWorkItemStatusWidget( + PDGStatusRow, TEXT("Asset Work Item Status"), InPDGAssetLink, false); + } +} + +bool +FHoudiniPDGDetails::GetPDGStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor) +{ + OutPDGStatusString = FString(); + OutPDGStatusColor = FLinearColor::White; + + if (!IsValid(InPDGAssetLink)) + return false; + + switch (InPDGAssetLink->LinkState) + { + case EPDGLinkState::Linked: + OutPDGStatusString = TEXT("PDG is READY"); + OutPDGStatusColor = FLinearColor::Green; + break; + case EPDGLinkState::Linking: + OutPDGStatusString = TEXT("PDG is Linking"); + OutPDGStatusColor = FLinearColor::Yellow; + break; + case EPDGLinkState::Error_Not_Linked: + OutPDGStatusString = TEXT("PDG is ERRORED"); + OutPDGStatusColor = FLinearColor::Red; + break; + case EPDGLinkState::Inactive: + OutPDGStatusString = TEXT("PDG is INACTIVE"); + OutPDGStatusColor = FLinearColor::White; + break; + default: + return false; + } + + return true; +} + +void +FHoudiniPDGDetails::AddPDGAssetStatus( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink) +{ + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + FString PDGStatusString; + FLinearColor PDGStatusColor; + GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); + return FText::FromString(PDGStatusString); + }) + .ColorAndOpacity_Lambda([InPDGAssetLink]() + { + FString PDGStatusString; + FLinearColor PDGStatusColor; + GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); + return FSlateColor(PDGStatusColor); + }) + ] + ]; +} + +void +FHoudiniPDGDetails::GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor) +{ + OutStatusString = FString(); + OutStatusColor = FLinearColor::White; + switch (FHoudiniEngine::Get().GetPDGCommandletStatus()) + { + case EHoudiniBGEOCommandletStatus::Connected: + OutStatusString = TEXT("Async importer is CONNECTED"); + OutStatusColor = FLinearColor::Green; + break; + case EHoudiniBGEOCommandletStatus::Running: + OutStatusString = TEXT("Async importer is Running"); + OutStatusColor = FLinearColor::Yellow; + break; + case EHoudiniBGEOCommandletStatus::Crashed: + OutStatusString = TEXT("Async importer has CRASHED"); + OutStatusColor = FLinearColor::Red; + break; + case EHoudiniBGEOCommandletStatus::NotStarted: + OutStatusString = TEXT("Async importer is NOT STARTED"); + OutStatusColor = FLinearColor::White; + break; + } +} + +void +FHoudiniPDGDetails::AddPDGCommandletStatus( + IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus) +{ + FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Visibility_Lambda([]() + { + const UHoudiniRuntimeSettings* Settings = GetDefault(); + if (IsValid(Settings)) + { + return FHoudiniEngineCommands::IsPDGCommandletEnabled() ? EVisibility::Visible : EVisibility::Collapsed; + } + + return EVisibility::Visible; + }) + .Text_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetPDGCommandletStatus(StatusString, StatusColor); + return FText::FromString(StatusString); + }) + .ColorAndOpacity_Lambda([]() + { + FString StatusString; + FLinearColor StatusColor; + GetPDGCommandletStatus(StatusString, StatusColor); + return FSlateColor(StatusColor); + }) + ] + ]; +} + +bool +FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( + UHoudiniPDGAssetLink* InAssetLink, + bool bInForSelectedNode, + const FString& InTallyItemString, + int32& OutValue, + FLinearColor& OutColor) +{ + OutValue = 0; + OutColor = FLinearColor::White; + + if (!IsValid(InAssetLink)) + return false; + + bool bFound = false; + const FWorkItemTallyBase* TallyPtr = nullptr; + if (bInForSelectedNode) + { + UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); + if (TOPNode && !TOPNode->bHidden) + TallyPtr = &(TOPNode->GetWorkItemTally()); + } + else + TallyPtr = &(InAssetLink->WorkItemTally); + + if (TallyPtr) + { + if (InTallyItemString == TEXT("WAITING")) + { + // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI + OutValue = TallyPtr->NumWaitingWorkItems() + TallyPtr->NumScheduledWorkItems(); + OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("COOKING")) + { + OutValue = TallyPtr->NumCookingWorkItems(); + OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("COOKED")) + { + OutValue = TallyPtr->NumCookedWorkItems(); + OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; + bFound = true; + } + else if (InTallyItemString == TEXT("FAILED")) + { + OutValue = TallyPtr->NumErroredWorkItems(); + OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; + bFound = true; + } + } + + return bFound; +} + +void +FHoudiniPDGDetails::AddWorkItemStatusWidget( + FDetailWidgetRow& InRow, const FString& InTitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode) +{ + auto AddGridBox = [InAssetLink, bInForSelectedNode](const FString& Title) -> SHorizontalBox::FSlot& + { + return SHorizontalBox::Slot() + .MaxWidth(500.0f) + .Padding(0.0f, 0.0f, 2.0f, 0.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(FMargin(1.0f, 2.0f)) + [ + SNew(SBorder) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .BorderBackgroundColor(FSlateColor(FLinearColor(0.6, 0.6, 0.6))) + .Padding(FMargin(1.0f, 5.0f)) + [ + SNew(SBox) + .WidthOverride(95.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(Title)) + .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FSlateColor(Color); + }) + ] + ] + ] + + SVerticalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .AutoHeight() + .Padding(FMargin(1.0f, 2.0f)) + [ + SNew(SBorder) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + .BorderBackgroundColor(FSlateColor(FLinearColor(0.8, 0.8, 0.8))) + .Padding(FMargin(1.0f, 5.0f)) + [ + SNew(SBox) + .WidthOverride(95.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(STextBlock) + .Text_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FText::AsNumber(Value); + }) + .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() + { + int32 Value; + FLinearColor Color; + GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); + return FSlateColor(Color); + }) + ] + ] + ] + ]; + }; + + InRow.WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.0f, 0.0f) + .AutoWidth() + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + [ + SNew(SSpacer) + ] + + SVerticalBox::Slot() + .AutoHeight() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(FMargin(0.0f, 2.0f)) + [ + SNew(STextBlock) + .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) + .Text(FText::FromString(InTitleString)) + + ] + + SVerticalBox::Slot() + .AutoHeight() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Padding(FMargin(0.0f, 2.0f)) + [ + SNew(SHorizontalBox) + + AddGridBox(TEXT("WAITING")) + + AddGridBox(TEXT("COOKING")) + + AddGridBox(TEXT("COOKED")) + + AddGridBox(TEXT("FAILED")) + ] + + SVerticalBox::Slot() + [ + SNew(SSpacer) + ] + ] + ]; +} + + +void +FHoudiniPDGDetails::AddTOPNetworkWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink ) +{ + if (!InPDGAssetLink->GetSelectedTOPNetwork()) + return; + + if (InPDGAssetLink->AllTOPNetworks.Num() <= 0) + return; + + TOPNetworksPtr.Reset(); + + FString GroupLabel = TEXT("TOP Networks"); + IDetailGroup& TOPNetWorkGrp = InPDGCategory.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), false, true); + + // Combobox: TOP Network + { + FDetailWidgetRow& PDGTOPNetRow = TOPNetWorkGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGTOPNetRow, InPDGAssetLink); + PDGTOPNetRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Network"))) + ]; + + // Fill the TOP Networks SharedString array + TOPNetworksPtr.SetNum(InPDGAssetLink->AllTOPNetworks.Num()); + for(int32 Idx = 0; Idx < InPDGAssetLink->AllTOPNetworks.Num(); Idx++) + { + const UTOPNetwork* Network = InPDGAssetLink->AllTOPNetworks[Idx]; + if (!IsValid(Network)) + { + TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( + Idx, + TEXT("Invalid"), + TEXT("Invalid") + )); + } + else + { + TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( + Idx, + FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Network->NodeName, Network->NodePath), + Network->NodePath + )); + } + } + + if(TOPNetworksPtr.Num() <= 0) + TOPNetworksPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, "----"))); + + // Lambda for selecting another TOPNet + auto OnTOPNetChanged = [InPDGAssetLink](TSharedPtr InNewChoice) + { + if (!InNewChoice.IsValid()) + return; + + const int32 NewChoice = InNewChoice->Value; + int32 NewSelectedIndex = -1; + if (InPDGAssetLink->AllTOPNetworks.IsValidIndex(NewChoice)) + NewSelectedIndex = NewChoice; + + if (InPDGAssetLink->SelectedTOPNetworkIndex == NewSelectedIndex) + return; + + if (NewSelectedIndex < 0) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->SelectedTOPNetworkIndex = NewSelectedIndex; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex), InPDGAssetLink); + }; + + TSharedPtr HorizontalBoxTOPNet; + TSharedPtr>> ComboBoxTOPNet; + int32 SelectedIndex = TOPNetworksPtr.IndexOfByPredicate([InPDGAssetLink](const TSharedPtr& InEntry) + { + return InEntry.IsValid() && InEntry->Value == InPDGAssetLink->SelectedTOPNetworkIndex; + }); + if (SelectedIndex < 0) + SelectedIndex = 0; + + PDGTOPNetRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .FillWidth(300.f) + .MaxWidth(300.f) + [ + SAssignNew(ComboBoxTOPNet, SComboBox>) + .OptionsSource(&TOPNetworksPtr) + .InitiallySelectedItem(TOPNetworksPtr[SelectedIndex]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); + const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryToolTip) + .Margin(2.0f) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnTOPNetChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnTOPNetChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString(InPDGAssetLink->GetSelectedTOPNetworkName()); + }) + .ToolTipText_Lambda([InPDGAssetLink]() + { + UTOPNetwork const * const Network = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(Network)) + { + if (!Network->NodePath.IsEmpty()) + return FText::FromString(Network->NodePath); + else + return FText::FromString(Network->NodeName); + } + else + { + return FText(); + } + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + // Buttons: DIRTY ALL / COOK OUTPUT + { + TSharedRef DirtyAllHBox = SNew(SHorizontalBox); + TSharedPtr CookOutHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("DirtyAll", "Dirty All")) + .ToolTipText(LOCTEXT("DirtyAllTooltip", "Dirty all TOP nodes in the selected TOP network and clears all of its work item results.")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork()); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(TOPNetwork)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + FHoudiniPDGManager::DirtyAll(TOPNetwork); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + else + { + UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(TOPNetwork); + } + } + } + + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(DirtyAllHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("CookOut", "Cook Output")) + .ToolTipText(LOCTEXT("CookOutTooltip", "Cooks the output nodes of the selected TOP network")) + .ContentPadding(FMargin(5.0f, 5.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!IsValid(SelectedTOPNet)) + return false; + + // Disable if there any nodes in the network that are already cooking + return !SelectedTOPNet->AnyWorkItemsPending(); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::CookOutput(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CookOutHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr DirtyAllIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyAllIconBrush(); + if (DirtyAllIconBrush.IsValid()) + { + TSharedPtr DirtyAllImage; + DirtyAllHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(DirtyAllImage, SImage) + ] + ]; + + DirtyAllImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([DirtyAllIconBrush]() { return DirtyAllIconBrush.Get(); }))); + } + + DirtyAllHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("DirtyAll", "Dirty All")) + ]; + + TSharedPtr CookOutIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + if (CookOutIconBrush.IsValid()) + { + TSharedPtr CookOutImage; + CookOutHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookOutImage, SImage) + ] + ]; + + CookOutImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookOutIconBrush]() { return CookOutIconBrush.Get(); }))); + } + + CookOutHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookOut", "Cook Output")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: PAUSE COOK / CANCEL COOK + { + TSharedRef PauseHBox = SNew(SHorizontalBox); + TSharedPtr CancelHBox = SNew(SHorizontalBox); + + FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Pause", "Pause Cook")) + .ToolTipText(LOCTEXT("PauseTooltip", "Pauses cooking for the selected TOP Network")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::PauseCook(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(PauseHBox, SHorizontalBox) + ] + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .WidthOverride(200.0f) + [ + SNew(SButton) + //.Text(LOCTEXT("Cancel", "Cancel Cook")) + .ToolTipText(LOCTEXT("CancelTooltip", "Cancels cooking the selected TOP network")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) + { + //InPDGAssetLink->WorkItemTally.ZeroAll(); + FHoudiniPDGManager::CancelCook(InPDGAssetLink->GetSelectedTOPNetwork()); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CancelHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr PauseIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGPauseIconBrush(); + if (PauseIconBrush.IsValid()) + { + TSharedPtr PauseImage; + PauseHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(PauseImage, SImage) + ] + ]; + + PauseImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([PauseIconBrush]() { return PauseIconBrush.Get(); }))); + } + + PauseHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Pause", "Pause Cook")) + ]; + + TSharedPtr CancelIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGCancelIconBrush(); + if (CancelIconBrush.IsValid()) + { + TSharedPtr CancelImage; + CancelHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CancelImage, SImage) + ] + ]; + + CancelImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CancelIconBrush]() { return CancelIconBrush.Get(); }))); + } + + CancelHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("Cancel", "Cancel Cook")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: Unload Work Item Objects + { + FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNetWorkGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork(); }) + .WidthOverride(200.0f) + [ + SNew(SButton) + .Text(LOCTEXT("UnloadWorkItemsForNetwork", "Unload All Work Item Objects")) + .ToolTipText(LOCTEXT("UnloadWorkItemsForNetworkTooltip", "Unloads / removes loaded work item results from level for all nodes in this network. Not undoable: use the \"Load Work Item Objects\" button on the individual TOP nodes to reload work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNetwork* const SelectedNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!IsValid(SelectedNet) || + INDEX_NONE == SelectedNet->AllTOPNodes.IndexOfByPredicate([](const UTOPNode* InNode) { return IsValid(InNode) && InNode->bCachedHaveLoadedWorkResults; })) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNetwork* const TOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(TOPNet)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + // Set the state to ToDelete, PDGManager will delete it when processing work items + TOPNet->SetLoadedWorkResultsToDelete(); + } + else + { + // Delete and unload the result objects and actors now + TOPNet->DeleteWorkResultOutputObjects(); + } + } + } + + return FReply::Handled(); + }) + ] + ] + ]; + } + + // TOP NODE WIDGETS + FHoudiniPDGDetails::AddTOPNodeWidget(TOPNetWorkGrp, InPDGAssetLink); +} + +bool +FHoudiniPDGDetails::GetSelectedTOPNodeStatusAndColor(UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor) +{ + OutTOPNodeStatus = FString(); + OutTOPNodeStatusColor = FLinearColor::White; + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode) && !TOPNode->bHidden) + { + OutTOPNodeStatus = UHoudiniPDGAssetLink::GetTOPNodeStatus(TOPNode); + OutTOPNodeStatusColor = UHoudiniPDGAssetLink::GetTOPNodeStatusColor(TOPNode); + + return true; + } + } + + return false; +} + +void +FHoudiniPDGDetails::AddTOPNodeWidget( + IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink ) +{ + if (!InPDGAssetLink->GetSelectedTOPNetwork()) + return; + + FString GroupLabel = TEXT("TOP Nodes"); + IDetailGroup& TOPNodesGrp = InGroup.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), true); + + // Combobox: TOP Node + { + FDetailWidgetRow& PDGTOPNodeRow = TOPNodesGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGTOPNodeRow, InPDGAssetLink); + PDGTOPNodeRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node"))) + ]; + + // Update the TOP Node SharedString + TOPNodesPtr.Reset(); + TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, LOCTEXT("ComboBoxEntryNoSelectedTOPNode", "- Select -").ToString()))); + const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNet)) + { + const int32 NumTOPNodes = SelectedTOPNet->AllTOPNodes.Num(); + for (int32 Idx = 0; Idx < NumTOPNodes; Idx++) + { + const UTOPNode* const Node = SelectedTOPNet->AllTOPNodes[Idx]; + if (!IsValid(Node) || Node->bHidden) + continue; + + TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip( + Idx, + FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Node->NodeName, Node->NodePath), + Node->NodePath + ))); + } + } + + FString NodeErrorText = FString(); + FString NodeErrorTooltip = FString(); + FLinearColor NodeErrorColor = FLinearColor::White; + if (!IsValid(SelectedTOPNet) || SelectedTOPNet->AllTOPNodes.Num() <= 0) + { + NodeErrorText = TEXT("No valid TOP Node found!"); + NodeErrorTooltip = TEXT("There is no valid TOP Node found in the selected TOP Network!"); + NodeErrorColor = FLinearColor::Red; + } + else if(TOPNodesPtr.Num() <= 0) + { + NodeErrorText = TEXT("No visible TOP Node found!"); + NodeErrorTooltip = TEXT("No visible TOP Node found, all nodes in this network are hidden. Please update your TOP Node Filter."); + NodeErrorColor = FLinearColor::Yellow; + } + + // Lambda for selecting a TOPNode + auto OnTOPNodeChanged = [InPDGAssetLink](TSharedPtr InNewChoice) + { + UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (!InNewChoice.IsValid() || !IsValid(TOPNetwork)) + return; + + const int32 NewChoice = InNewChoice->Value; + int32 NewSelectedIndex = INDEX_NONE; + if (TOPNetwork->AllTOPNodes.IsValidIndex(NewChoice)) + NewSelectedIndex = NewChoice; + + if (TOPNetwork->SelectedTOPIndex != NewSelectedIndex) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNetwork); + + TOPNetwork->Modify(); + TOPNetwork->SelectedTOPIndex = NewSelectedIndex; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UTOPNetwork, SelectedTOPIndex), TOPNetwork); + } + }; + + TSharedPtr HorizontalBoxTOPNode; + TSharedPtr>> ComboBoxTOPNode; + int32 SelectedIndex = 0; + UTOPNetwork* const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); + if (IsValid(SelectedTOPNetwork) && SelectedTOPNetwork->SelectedTOPIndex >= 0) + { + //SelectedIndex = InPDGAssetLink->GetSelectedTOPNetwork()->SelectedTOPIndex; + + // We need to match the selection by the index in the AllTopNodes array + // Because of the nodefilter, it is possible that the selected index does not match the index in TOPNodesPtr + const int32 SelectedTOPNodeIndex = SelectedTOPNetwork->SelectedTOPIndex; + // Find the matching UI index + for (int32 UIIndex = 0; UIIndex < TOPNodesPtr.Num(); UIIndex++) + { + if (TOPNodesPtr[UIIndex] && TOPNodesPtr[UIIndex]->Value == SelectedTOPNodeIndex) + { + // We found the UI Index that matches the current TOP Node! + SelectedIndex = UIIndex; + break; + } + } + } + + TSharedPtr ErrorText; + + PDGTOPNodeRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .FillWidth(300.f) + .MaxWidth(300.f) + [ + SAssignNew(ComboBoxTOPNode, SComboBox>) + .OptionsSource(&TOPNodesPtr) + .InitiallySelectedItem(TOPNodesPtr[SelectedIndex]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); + const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryToolTip) + .Margin(2.0f) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda([OnTOPNodeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) + { + return OnTOPNodeChanged(NewChoice); + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink, ComboBoxTOPNode, Options = TOPNodesPtr]() + { + if (IsValid(InPDGAssetLink)) + return FText::FromString(InPDGAssetLink->GetSelectedTOPNodeName()); + else + return FText(); + }) + .ToolTipText_Lambda([InPDGAssetLink]() + { + UTOPNode const * const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (!TOPNode->NodePath.IsEmpty()) + return FText::FromString(TOPNode->NodePath); + else + return FText::FromString(TOPNode->NodeName); + } + else + { + return FText(); + } + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + + SHorizontalBox::Slot() + .Padding(2, 2, 5, 2) + .AutoWidth() + [ + SAssignNew(ErrorText, STextBlock) + .Text(FText::FromString(NodeErrorText)) + .ToolTipText(FText::FromString(NodeErrorText)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .ColorAndOpacity(FLinearColor::Red) + //.ShadowColorAndOpacity(FLinearColor::Black) + ]; + + // Update the error text if needed + ErrorText->SetText(FText::FromString(NodeErrorText)); + ErrorText->SetToolTipText(FText::FromString(NodeErrorTooltip)); + ErrorText->SetColorAndOpacity(NodeErrorColor); + + // Hide the combobox if we have an error + ComboBoxTOPNode->SetVisibility(NodeErrorText.IsEmpty() ? EVisibility::Visible : EVisibility::Hidden); + } + + // TOP Node State + { + FDetailWidgetRow& PDGNodeStateResultRow = TOPNodesGrp.AddWidgetRow(); + DisableIfPDGNotLinked(PDGNodeStateResultRow, InPDGAssetLink); + PDGNodeStateResultRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("TOP Node State"))) + ]; + + PDGNodeStateResultRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + FString TOPNodeStatus = FString(); + FLinearColor TOPNodeStatusColor = FLinearColor::White; + GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); + return FText::FromString(TOPNodeStatus); + }) + .ColorAndOpacity_Lambda([InPDGAssetLink]() + { + FString TOPNodeStatus = FString(); + FLinearColor TOPNodeStatusColor = FLinearColor::White; + GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); + return FSlateColor(TOPNodeStatusColor); + }) + ]; + } + + // Checkbox: Load Work Item Output Files + { + auto ToolTipLambda = [InPDGAssetLink]() + { + bool bDisabled = false; + if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) + { + bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; + } + + return bDisabled + ? FText::FromString(TEXT("This node has child nodes, the auto-load setting must be set on the child nodes individually.")) + : FText::FromString(TEXT("When enabled, Output files produced by this TOP Node's Work Items will automatically be loaded when cooked.")); + }; + FDetailWidgetRow& PDGNodeAutoLoadRow = TOPNodesGrp.AddWidgetRow(); + + DisableIfPDGNotLinked(PDGNodeAutoLoadRow, InPDGAssetLink); + PDGNodeAutoLoadRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) + return true; + return false; + })); + + PDGNodeAutoLoadRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Auto-Load Work Item Output Files"))) + .ToolTipText_Lambda(ToolTipLambda) + ]; + + TSharedPtr AutoLoadCheckBox; + + PDGNodeAutoLoadRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(AutoLoadCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->GetSelectedTOPNode() + ? (InPDGAssetLink->GetSelectedTOPNode()->bAutoLoad ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + UTOPNode* TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(TOPNode) || TOPNode->bAutoLoad == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNode); + + TOPNode->Modify(); + TOPNode->bAutoLoad = bNewState; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UTOPNode, bAutoLoad), TOPNode); + + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + }) + .ToolTipText_Lambda(ToolTipLambda) + ]; + } + + // Checkbox: Work Item Output Files Visible + { + auto ToolTipLambda = [InPDGAssetLink]() + { + bool bDisabled = false; + if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) + { + bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; + } + + return bDisabled + ? FText::FromString(TEXT("This node has child nodes, visibility of work item outputs must be set on the child nodes individually.")) + : FText::FromString(TEXT("Toggles the visibility of the actors created from this TOP Node's Work Item File Outputs.")); + }; + + FDetailWidgetRow& PDGNodeShowResultRow = TOPNodesGrp.AddWidgetRow(); + // DisableIfPDGNotLinked(PDGNodeShowResultRow, InPDGAssetLink); + PDGNodeShowResultRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) + return true; + + return false; + })); + PDGNodeShowResultRow.NameWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Work Item Output Files Visible"))) + .ToolTipText_Lambda(ToolTipLambda) + ]; + + TSharedPtr ShowResCheckBox; + PDGNodeShowResultRow.ValueWidget.Widget = + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + [ + // Checkbox + SAssignNew(ShowResCheckBox, SCheckBox) + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->GetSelectedTOPNode() + ? (InPDGAssetLink->GetSelectedTOPNode()->IsVisibleInLevel() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(TOPNode) || TOPNode->IsVisibleInLevel() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + TOPNode); + + TOPNode->Modify(); + TOPNode->SetVisibleInLevel(bNewState); + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(TEXT("bShow"), TOPNode); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + }) + .ToolTipText_Lambda(ToolTipLambda) + ]; + } + + // Buttons: DIRTY NODE / COOK NODE + { + TSharedRef DirtyHBox = SNew(SHorizontalBox); + TSharedPtr CookHBox = SNew(SHorizontalBox); + + TSharedPtr DirtyButton; + TSharedPtr CookButton; + + FDetailWidgetRow& PDGDirtyCookRow = TOPNodesGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); + }) + .WidthOverride(200.0f) + [ + SAssignNew(DirtyButton, SButton) + //.Text(LOCTEXT("DirtyNode", "Dirty Node")) + .ToolTipText(LOCTEXT("DirtyNodeTooltip", "Dirties the selected TOP node and clears its work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + FHoudiniPDGManager::DirtyTOPNode(TOPNode); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + else + { + UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(TOPNode); + } + } + } + + return FReply::Handled(); + }) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink->GetSelectedTOPNode()) && !InPDGAssetLink->GetSelectedTOPNode()->bHidden) + return true; + return false; + }) + .Content() + [ + SAssignNew(DirtyHBox, SHorizontalBox) + ] + ] + ] + // + SHorizontalBox::Slot() + // .AutoWidth() + // [ + // SNew(SBox) + // .WidthOverride(200.0f) + // [ + // SAssignNew(DirtyButton, SButton) + // .Text(LOCTEXT("DirtyAllTasks", "Dirty All Tasks")) + // .ToolTipText(LOCTEXT("DirtyAllTasksTooltip", "Dirties all tasks/work items on the selected TOP node.")) + // .ContentPadding(FMargin(5.0f, 2.0f)) + // .VAlign(VAlign_Center) + // .HAlign(HAlign_Center) + // .OnClicked_Lambda([InPDGAssetLink]() + // { + // if(InPDGAssetLink->GetSelectedTOPNode()) + // { + // FHoudiniPDGManager::DirtyAllTasksOfTOPNode(*(InPDGAssetLink->GetSelectedTOPNode())); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + // } + // + // return FReply::Handled(); + // }) + // ] + // ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(200.0f) + [ + SAssignNew(CookButton, SButton) + //.Text(LOCTEXT("CookNode", "Cook Node")) + .ToolTipText(LOCTEXT("CookNodeTooltip", "Cooks the selected TOP Node.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsPDGLinked(InPDGAssetLink)) + return false; + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode)) + return false; + // Disable Cook Node button if the node is already cooking + return !SelectedNode->bHidden && SelectedNode->NodeState != EPDGNodeState::Cooking && !SelectedNode->AnyWorkItemsPending(); + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(Node)) + { + FHoudiniPDGManager::CookTOPNode(Node); + // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); + } + return FReply::Handled(); + }) + .Content() + [ + SAssignNew(CookHBox, SHorizontalBox) + ] + ] + ] + ]; + + TSharedPtr DirtyIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyNodeIconBrush(); + if (DirtyIconBrush.IsValid()) + { + TSharedPtr DirtyImage; + DirtyHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(DirtyImage, SImage) + ] + ]; + + DirtyImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([DirtyIconBrush]() { return DirtyIconBrush.Get(); }))); + } + + DirtyHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("DirtyNode", "Dirty Node")) + ]; + + TSharedPtr CookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); + if (CookIconBrush.IsValid()) + { + TSharedPtr CookImage; + CookHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(CookImage, SImage) + ] + ]; + + CookImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([CookIconBrush]() { return CookIconBrush.Get(); }))); + } + + CookHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("CookNode", "Cook Node")) + ]; + + DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); + } + + // Buttons: Load Work Item Objects / Unload Work Item Objects + { + TSharedPtr UnloadWorkItemsButton; + TSharedPtr LoadWorkItemsButton; + + FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNodesGrp.AddWidgetRow() + .WholeRowContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() + { + return IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode()); + }) + .WidthOverride(200.0f) + [ + SAssignNew(UnloadWorkItemsButton, SButton) + .Text(LOCTEXT("UnloadWorkItemsForNode", "Unload Work Item Objects")) + .ToolTipText(LOCTEXT("UnloadWorkItemsForNodeTooltip", "Unloads / removes loaded work item results from level. Not undoable: use the \"Load Work Item Objects\" button to reload the results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveLoadedWorkResults) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(TOPNode)) + { + if (IsPDGLinked(InPDGAssetLink)) + { + // Set the state to ToDelete, PDGManager will delete it when processing work items + TOPNode->SetLoadedWorkResultsToDelete(); + } + else + { + // Delete and unload the result objects and actors now + TOPNode->DeleteWorkResultOutputObjects(); + } + } + } + + return FReply::Handled(); + }) + ] + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(200.0f) + [ + SAssignNew(LoadWorkItemsButton, SButton) + .Text(LOCTEXT("LoadWorkItems", "Load Work Item Objects")) + .ToolTipText(LOCTEXT("LoadWorkItemsForNodeTooltip", "Loads any available but not loaded work items objects (this could include items from a previous cook). Creates output actors. Not undoable: use the \"Unload Work Item Objects\" button to unload/remove loaded work item results.")) + .ContentPadding(FMargin(5.0f, 2.0f)) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .IsEnabled_Lambda([InPDGAssetLink]() + { + if (!IsValid(InPDGAssetLink)) + return false; + + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveNotLoadedWorkResults) + return false; + + return true; + }) + .OnClicked_Lambda([InPDGAssetLink]() + { + if (IsValid(InPDGAssetLink)) + { + UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); + if (IsValid(SelectedNode)) + { + SelectedNode->SetNotLoadedWorkResultsToLoad(true); + } + } + return FReply::Handled(); + }) + ] + ] + ]; + } + + // TOP Node WorkItem Status + { + if (InPDGAssetLink->GetSelectedTOPNode()) + { + FDetailWidgetRow& PDGNodeWorkItemStatsRow = TOPNodesGrp.AddWidgetRow(); + DisableIfPDGNotLinked(PDGNodeWorkItemStatsRow, InPDGAssetLink); + FHoudiniPDGDetails::AddWorkItemStatusWidget( + PDGNodeWorkItemStatsRow, TEXT("TOP Node Work Item Status"), InPDGAssetLink, true); + } + } +} + +void +FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // Repopulate the network and nodes for the assetlink + if (!FHoudiniPDGManager::UpdatePDGAssetLink(InPDGAssetLink)) + return; + + FHoudiniPDGDetails::RefreshUI(InPDGAssetLink, true); +} + +void +FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // Update the workitem stats + InPDGAssetLink->UpdateWorkItemTally(); + + // Update the editor properties + FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, InFullUpdate); +} + +void +FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) +{ + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); + + if (!InPDGAssetLink->bBakeMenuExpanded) + return; + + auto OnBakeButtonClickedLambda = [InPDGAssetLink]() + { + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(InPDGAssetLink); + // else + FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->PDGBakeSelectionOption, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); + } + break; + + case EHoudiniEngineBakeOption::ToBlueprint: + { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceWithBlueprint(InPDGAssetLink); + // else + FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink, InPDGAssetLink->PDGBakeSelectionOption, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); + } + break; + // + // case EHoudiniEngineBakeOption::ToFoliage: + // { + // if (InPDGAssetLink->bIsReplace) + // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(InPDGAssetLink); + // else + // FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InPDGAssetLink); + // } + // break; + // + // case EHoudiniEngineBakeOption::ToWorldOutliner: + // { + // if (InPDGAssetLink->bIsReplace) + // { + // // Todo + // } + // else + // { + // //Todo + // } + // } + // break; + } + + return FReply::Handled(); + }; + + auto OnBakeFolderTextCommittedLambda = [InPDGAssetLink](const FText& Val, ETextCommit::Type TextCommitType) + { + FString NewPathStr = Val.ToString(); + if (NewPathStr.IsEmpty()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + //Todo? Check if the new Bake folder path is valid + InPDGAssetLink->Modify(); + InPDGAssetLink->BakeFolder.Path = NewPathStr; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, BakeFolder), InPDGAssetLink); + }; + + // Button Row + FDetailWidgetRow & ButtonRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(ButtonRow, InPDGAssetLink); + + TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); + + // Bake Button + TSharedRef BakeHBox = SNew(SHorizontalBox); + TSharedPtr BakeButton; + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(15.f, 0.0f, 0.0f, 0.0f) + .MaxWidth(75.0f) + [ + SNew(SBox) + .WidthOverride(75.0f) + [ + SAssignNew(BakeButton, SButton) + //.Text(FText::FromString("Bake")) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + //.ToolTipText(LOCTEXT("HoudiniPDGDetailsBakeButton", "Bake the Houdini PDG TOP Node(s)")) + .ToolTipText_Lambda([InPDGAssetLink]() + { + switch (InPDGAssetLink->HoudiniEngineBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + { + return LOCTEXT( + "HoudiniEnginePDGBakeButtonBakeToActorToolTip", + "Bake this Houdini PDG Asset's output assets and seperate the output actors from the PDG asset link."); + } + break; + case EHoudiniEngineBakeOption::ToBlueprint: + { + return LOCTEXT( + "HoudiniEnginePDGBakeButtonBakeToBlueprintToolTip", + "Bake this Houdini PDG Asset's output assets to blueprints and remove temporary output actors that no " + "longer has output components from the PDG asset link."); + } + break; + default: + { + return FText(); + } + } + }) + .Visibility(EVisibility::Visible) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .OnClicked_Lambda(OnBakeButtonClickedLambda) + .Content() + [ + SAssignNew(BakeHBox, SHorizontalBox) + ] + ] + ]; + + TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); + if (BakeIconBrush.IsValid()) + { + TSharedPtr BakeImage; + BakeHBox->AddSlot() + .MaxWidth(16.0f) + [ + SNew(SBox) + .WidthOverride(16.0f) + .HeightOverride(16.0f) + [ + SAssignNew(BakeImage, SImage) + ] + ]; + + BakeImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([BakeIconBrush]() { return BakeIconBrush.Get(); }))); + } + + BakeHBox->AddSlot() + .Padding(5.0, 0.0, 0.0, 0.0) + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(STextBlock) + .Text(FText::FromString("Bake")) + ]; + + // bake Type ComboBox + TSharedPtr>> TypeComboBox; + + TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeTypeOptionsLabels(); + TSharedPtr IntialSelec; + if (OptionSource) + { + // IntialSelec = (*OptionSource)[(int)InPDGAssetLink->HoudiniEngineBakeOption]; + const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption); + const TSharedPtr* DefaultOption = OptionSource->FindByPredicate( + [DefaultStr](TSharedPtr InStringPtr) + { + return InStringPtr.IsValid() && *InStringPtr == DefaultStr; + } + ); + if (DefaultOption) + IntialSelec = *DefaultOption; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(93.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(93.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid() || SelectType == ESelectInfo::Type::Direct) + return; + + const EHoudiniEngineBakeOption NewOption = + FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->HoudiniEngineBakeOption) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->HoudiniEngineBakeOption = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink, TypeComboBox, OptionSource]() + { + return FText::FromString(FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + // bake selection ComboBox + TSharedPtr>> BakeSelectionComboBox; + + TArray>* PDGBakeSelectionOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeSelectionOptionsLabels(); + TSharedPtr PDGBakeSelectionIntialSelec; + if (PDGBakeSelectionOptionSource) + { + PDGBakeSelectionIntialSelec = (*PDGBakeSelectionOptionSource)[(int)InPDGAssetLink->PDGBakeSelectionOption]; + } + + ButtonRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(163.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(163.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(PDGBakeSelectionOptionSource) + .InitiallySelectedItem(PDGBakeSelectionIntialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EPDGBakeSelectionOption NewOption = + FHoudiniEngineEditor::Get().StringToPDGBakeSelectionOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->PDGBakeSelectionOption) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->PDGBakeSelectionOption = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromPDGBakeTargetOption(InPDGAssetLink->PDGBakeSelectionOption)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; + + // Bake package replacement mode row + FDetailWidgetRow & BakePackageReplaceRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(BakePackageReplaceRow, InPDGAssetLink); + + TSharedRef BakePackageReplaceRowHorizontalBox = SNew(SHorizontalBox); + + BakePackageReplaceRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeLabel", "Replace Mode")) + .ToolTipText( + LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeTooltip", "Replacement mode " + "during baking. Create new assets, using numerical suffixes in package names when necessary, or " + "replace existing assets with matching names. Also replaces the previous bake's output actors in " + "replacement mode vs creating incremental ones.")) + ] + ]; + + // bake package replace mode ComboBox + TSharedPtr>> BakePackageReplaceModeComboBox; + + TArray>* PDGBakePackageReplaceModeOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels(); + TSharedPtr PDGBakePackageReplaceModeInitialSelec; + if (PDGBakePackageReplaceModeOptionSource) + { + const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode); + const TSharedPtr* DefaultOption = PDGBakePackageReplaceModeOptionSource->FindByPredicate( + [DefaultStr](TSharedPtr InStringPtr) + { + return InStringPtr.IsValid() && *InStringPtr == DefaultStr; + } + ); + if (DefaultOption) + PDGBakePackageReplaceModeInitialSelec = *DefaultOption; + } + + BakePackageReplaceRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(3.0, 0.0, 4.0f, 0.0f) + .MaxWidth(163.f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(163.f) + [ + SAssignNew(TypeComboBox, SComboBox>) + .OptionsSource(PDGBakePackageReplaceModeOptionSource) + .InitiallySelectedItem(PDGBakePackageReplaceModeInitialSelec) + .OnGenerateWidget_Lambda( + [](TSharedPtr< FString > InItem) + { + const FText ChoiceEntryText = FText::FromString(*InItem); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + const EPDGBakePackageReplaceModeOption NewOption = + FHoudiniEngineEditor::Get().StringToPDGBakePackageReplaceModeOption(*NewChoice.Get()); + + if (NewOption != InPDGAssetLink->PDGBakePackageReplaceMode) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->PDGBakePackageReplaceMode = NewOption; + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode), InPDGAssetLink); + } + }) + [ + SNew(STextBlock) + .Text_Lambda([InPDGAssetLink]() + { + return FText::FromString( + FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + BakePackageReplaceRow.WholeRowWidget.Widget = BakePackageReplaceRowHorizontalBox; + + // Bake Folder Row + FDetailWidgetRow & BakeFolderRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + DisableIfPDGNotLinked(BakeFolderRow, InPDGAssetLink); + + TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .Padding(30.0f, 0.0f, 6.0f, 0.0f) + .MaxWidth(155.0f) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(155.0f) + [ + SNew(STextBlock) + .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) + .ToolTipText(LOCTEXT( + "HoudiniEnginePDGBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) + ] + ]; + + BakeFolderRowHorizontalBox->AddSlot() + /*.AutoWidth()*/ + .MaxWidth(235.0) + [ + SNew(SBox) + .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) + .WidthOverride(235.0f) + [ + SNew(SEditableTextBox) + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + .ToolTipText(LOCTEXT( + "HoudiniEnginePDGBakeFolderTooltip", + "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " + "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " + "plugin settings is used.")) + .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) + .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) + ] + ]; + + BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; + + // Add additional bake options + FDetailWidgetRow & AdditionalBakeSettingsRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); + TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); + + TSharedPtr CheckBoxAutoBake; + TSharedPtr CheckBoxRecenterBakedActors; + + TSharedPtr LeftColumnVerticalBox; + TSharedPtr RightColumnVerticalBox; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(30.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + .WidthOverride(200.f) + [ + SAssignNew(LeftColumnVerticalBox, SVerticalBox) + ] + ]; + + AdditionalBakeSettingsRowHorizontalBox->AddSlot() + .Padding(20.0f, 5.0f, 0.0f, 0.0f) + .MaxWidth(200.f) + [ + SNew(SBox) + [ + SAssignNew(RightColumnVerticalBox, SVerticalBox) + ] + ]; + + LeftColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) + .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bRecenterBakedActors = bNewState; + + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bRecenterBakedActors), InPDGAssetLink); + }) + ] + ]; + + RightColumnVerticalBox->AddSlot() + .AutoHeight() + .Padding(0.0f, 0.0f, 0.0f, 3.5f) + [ + SNew(SBox) + .WidthOverride(160.f) + [ + SAssignNew(CheckBoxAutoBake, SCheckBox) + .Content() + [ + SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) + .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake work result object as they are loaded.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([InPDGAssetLink]() + { + return InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }) + .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) + { + const bool bNewState = (NewState == ECheckBoxState::Checked); + + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), + InPDGAssetLink); + + InPDGAssetLink->Modify(); + InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded = bNewState; + + // Notify that we have changed the property + FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( + GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterAllWorkResultObjectsLoaded), InPDGAssetLink); + }) + ] + ]; + + AdditionalBakeSettingsRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText) + : Text(InText) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText, const FString &InToolTip) + : Text(InText) + , ToolTip(InToolTip) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText) + : Text(InText) + , Value(InValue) +{ +} + +FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText, FString&& InToolTip) + : Text(InText) + , ToolTip(InToolTip) + , Value(InValue) +{ +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h index de9d43d6a..00a8b3629 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniPDGDetails.h @@ -1,140 +1,140 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Templates/SharedPointer.h" -#include "DetailWidgetRow.h" - -#include "HoudiniPDGAssetLink.h" - -class IDetailGroup; -class IDetailCategoryBuilder; - -struct FWorkItemTally; -enum class EPDGLinkState : uint8; -enum class EHoudiniBGEOCommandletStatus : uint8; - -// Convenience struct to hold a label and tooltip for widgets. -struct FTextAndTooltip -{ -public: - FTextAndTooltip(int32 InValue, const FString& InText); - FTextAndTooltip(int32 InValue, const FString& InText, const FString &InTooltip); - FTextAndTooltip(int32 InValue, FString&& InText); - FTextAndTooltip(int32 InValue, FString&& InText, FString&& InTooltip); - - FString Text; - - FString ToolTip; - - int32 Value; -}; - -class FHoudiniPDGDetails : public TSharedFromThis -{ - public: - - void CreateWidget( - IDetailCategoryBuilder & HouPDGCategory, - UHoudiniPDGAssetLink* InPDGAssetLink); - //UHoudiniAssetComponent* InHAC); - - void AddPDGAssetWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - - void AddWorkItemStatusWidget( - FDetailWidgetRow& InRow, const FString& TitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode); - - void AddPDGAssetStatus( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink); - - void AddPDGCommandletStatus( - IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus); - - void AddTOPNetworkWidget( - IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - - void AddTOPNodeWidget( - IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink); - - static void RefreshPDGAssetLink( - UHoudiniPDGAssetLink* InPDGAssetLink); - - static void RefreshUI( - UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate = true); - - static void - CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); - protected: - // Helper function for getting the work item tally and color - static bool GetWorkItemTallyValueAndColor( - UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode, const FString& InTallyItemString, - int32& OutValue, FLinearColor& OutColor); - - // Helper to get the status text for the selected TOP node, and the color with which to display it on the UI. - // Returns false if the InPDGAssetLink is invalid, or there is no selected TOP node. - static bool GetSelectedTOPNodeStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor); - - // Helper to get asset link status and status color for UI - static bool GetPDGStatusAndColor( - UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor); - - // Helper for getting the commandlet status text and color for the UI - static void GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor); - - // Helper to check if the asset link state is Linked - static FORCEINLINE bool IsPDGLinked(UHoudiniPDGAssetLink* InPDGAssetLink) - { - return IsValid(InPDGAssetLink) && InPDGAssetLink->LinkState == EPDGLinkState::Linked; - } - - // Helper for binding IsPDGLinked to a TAttribute - static FORCEINLINE void BindDisableIfPDGNotLinked(TAttribute &InAttrToBind, UHoudiniPDGAssetLink* InPDGAssetLink) - { - InAttrToBind.Bind( - TAttribute::FGetter::CreateLambda([InPDGAssetLink]() - { - return IsPDGLinked(InPDGAssetLink); - }) - ); - } - - // Helper to disable a UI row if InPDGAssetLink is not linked - static FORCEINLINE void DisableIfPDGNotLinked(FDetailWidgetRow& InRow, UHoudiniPDGAssetLink* InPDGAssetLink) - { - BindDisableIfPDGNotLinked(InRow.IsEnabledAttr, InPDGAssetLink); - } - - private: - - TArray> TOPNetworksPtr; - - TArray> TOPNodesPtr; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Templates/SharedPointer.h" +#include "DetailWidgetRow.h" + +#include "HoudiniPDGAssetLink.h" + +class IDetailGroup; +class IDetailCategoryBuilder; + +struct FWorkItemTally; +enum class EPDGLinkState : uint8; +enum class EHoudiniBGEOCommandletStatus : uint8; + +// Convenience struct to hold a label and tooltip for widgets. +struct FTextAndTooltip +{ +public: + FTextAndTooltip(int32 InValue, const FString& InText); + FTextAndTooltip(int32 InValue, const FString& InText, const FString &InTooltip); + FTextAndTooltip(int32 InValue, FString&& InText); + FTextAndTooltip(int32 InValue, FString&& InText, FString&& InTooltip); + + FString Text; + + FString ToolTip; + + int32 Value; +}; + +class FHoudiniPDGDetails : public TSharedFromThis +{ + public: + + void CreateWidget( + IDetailCategoryBuilder & HouPDGCategory, + UHoudiniPDGAssetLink* InPDGAssetLink); + //UHoudiniAssetComponent* InHAC); + + void AddPDGAssetWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + + void AddWorkItemStatusWidget( + FDetailWidgetRow& InRow, const FString& TitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode); + + void AddPDGAssetStatus( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink); + + void AddPDGCommandletStatus( + IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus); + + void AddTOPNetworkWidget( + IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + + void AddTOPNodeWidget( + IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink); + + static void RefreshPDGAssetLink( + UHoudiniPDGAssetLink* InPDGAssetLink); + + static void RefreshUI( + UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate = true); + + static void + CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink); + protected: + // Helper function for getting the work item tally and color + static bool GetWorkItemTallyValueAndColor( + UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode, const FString& InTallyItemString, + int32& OutValue, FLinearColor& OutColor); + + // Helper to get the status text for the selected TOP node, and the color with which to display it on the UI. + // Returns false if the InPDGAssetLink is invalid, or there is no selected TOP node. + static bool GetSelectedTOPNodeStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor); + + // Helper to get asset link status and status color for UI + static bool GetPDGStatusAndColor( + UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor); + + // Helper for getting the commandlet status text and color for the UI + static void GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor); + + // Helper to check if the asset link state is Linked + static FORCEINLINE bool IsPDGLinked(UHoudiniPDGAssetLink* InPDGAssetLink) + { + return IsValid(InPDGAssetLink) && InPDGAssetLink->LinkState == EPDGLinkState::Linked; + } + + // Helper for binding IsPDGLinked to a TAttribute + static FORCEINLINE void BindDisableIfPDGNotLinked(TAttribute &InAttrToBind, UHoudiniPDGAssetLink* InPDGAssetLink) + { + InAttrToBind.Bind( + TAttribute::FGetter::CreateLambda([InPDGAssetLink]() + { + return IsPDGLinked(InPDGAssetLink); + }) + ); + } + + // Helper to disable a UI row if InPDGAssetLink is not linked + static FORCEINLINE void DisableIfPDGNotLinked(FDetailWidgetRow& InRow, UHoudiniPDGAssetLink* InPDGAssetLink) + { + BindDisableIfPDGNotLinked(InRow.IsEnabledAttr, InPDGAssetLink); + } + + private: + + TArray> TOPNetworksPtr; + + TArray> TOPNodesPtr; + +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp index 35c98adca..a5b843df7 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp @@ -1,7411 +1,7403 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterDetails.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniInput.h" -#include "HoudiniAsset.h" - -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEnginePrivatePCH.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "SNewFilePathPicker.h" - -#include "DetailCategoryBuilder.h" -#include "DetailLayoutBuilder.h" -#include "IDetailGroup.h" -#include "IDetailCustomization.h" -#include "PropertyCustomizationHelpers.h" -#include "DetailWidgetRow.h" -#include "Math/UnitConversion.h" -#include "ScopedTransaction.h" -#include "EditorDirectories.h" - -#include "Widgets/Input/SButton.h" -#include "Widgets/Input/SCheckBox.h" -#include "Widgets/Input/SComboBox.h" -#include "Widgets/Input/SVectorInputBox.h" -#include "Widgets/Input/SNumericEntryBox.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SMultiLineEditableTextBox.h" -#include "Widgets/Colors/SColorPicker.h" -#include "Widgets/Views/SExpanderArrow.h" -#include "Widgets/Layout/SExpandableArea.h" -#include "Widgets/Views/STableRow.h" -#include "Widgets/Input/NumericUnitTypeInterface.inl" -#include "Widgets/Images/SImage.h" -#include "Widgets/Layout/SSeparator.h" -#include "Widgets/Layout/SSplitter.h" -#include "SCurveEditorView.h" -#include "SAssetDropTarget.h" -#include "AssetThumbnail.h" - -#include "Sound/SoundBase.h" -#include "Engine/SkeletalMesh.h" -#include "Particles/ParticleSystem.h" -#include "FoliageType.h" - -#include "HoudiniInputDetails.h" - -#include "Framework/SlateDelegates.h" -#include "Templates/SharedPointer.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -int32 -SCustomizedButton::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const -{ - TSharedPtr Content = GetContent(); - - // 0. Initialize Line Buffer. - TArray Line; - Line.SetNumUninitialized(2); - - // Initialize Color buffer. - FLinearColor Color = FLinearColor::White; - - // 1. Draw the radio button. - if (bIsRadioButton) - { - // Construct the radio button circles exactly once, - // All radio buttons share the same circles then - if (FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER || - FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER) - { - ConstructRadioButtonCircles(); - } - - DrawRadioButton(AllottedGeometry, OutDrawElements, LayerId, bChosen); - } - - // 2. Draw background color (if selected) - if (bChosen) - { - Line[0].X = AllottedGeometry.Size.X - AllottedGeometry.Size.Y / 2.0f + 2.5f; - Line[0].Y = Content->GetDesiredSize().Y / 2.0f; - Line[1].X = AllottedGeometry.Size.Y / 2.0f - 2.5f; - Line[1].Y = Content->GetDesiredSize().Y / 2.0f; - - Color = FLinearColor::White; - Color.A = bIsRadioButton ? 0.05 : 0.1; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, AllottedGeometry.Size.Y); - } - - // 3. Drawing square around the text - { - // Switch the point order for each line to save few value assignment cycles - Line[0].X = 0.0f; - Line[0].Y = 0.0f; - Line[1].X = 0.0f; - Line[1].Y = Content->GetDesiredSize().Y; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); - - //Line[0].X = 0.0f; - //Line[0].Y = Content->GetDesiredSize().Y; - Line[0].X = AllottedGeometry.Size.X; - Line[0].Y = Content->GetDesiredSize().Y; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, bChosen ? FLinearColor::Gray : FLinearColor::Black, true, 1.0f); - - //Line[0].X = AllottedGeometry.Size.X; - //Line[0].Y = Content->GetDesiredSize().Y; - Line[1].X = AllottedGeometry.Size.X; - Line[1].Y = 0.0f; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); /* draw gray bottom line if this tab is selected, black otherwise*/ - - //Line[0].X = AllottedGeometry.Size.X; - //Line[0].Y = 0.0f; - Line[0].X = 0.0f; - Line[0].Y = 0.0f; - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); - } - - // 4. Draw child widget - Content->Paint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - - return LayerId; -}; - -void -SCustomizedButton::ConstructRadioButtonCircles() const -{ - TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); - TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); - OuterPoints.Empty(); - InnerPoints.Empty(); - - OuterPoints.SetNumZeroed(HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER); - InnerPoints.SetNumZeroed(8); - - // Construct outer circle - int32 CurDegree = 0; - int32 DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; - - for (int32 Idx = 0; Idx < HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; ++Idx) - { - OuterPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); - OuterPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); - - CurDegree += DegStep; - } - - // Construct inner circle - CurDegree = 0; - DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER; - for (int32 Idx = 0; Idx < 8; ++Idx) - { - InnerPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); - InnerPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + - HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); - - CurDegree += DegStep; - } -} - -void -SCustomizedButton::DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const -{ - TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); - TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); - if (OuterPoints.Num() <= 1 || InnerPoints.Num() <= 1) - return; - - FLinearColor ColorNonSelected = FLinearColor::White; - FLinearColor ColorSelected = FLinearColor::Yellow; - - // initialize line buffer - TArray Line; - Line.SetNumZeroed(2); - bool alternator = false; - - // Draw outer circle - Line[0] = OuterPoints.Last(); - for (int32 Idx = 0; Idx < OuterPoints.Num(); ++Idx) - { - // alternate the points order each time to some some assignment cycles - if (alternator) - { - Line[0].X = OuterPoints[Idx].X; - Line[0].Y = OuterPoints[Idx].Y; - } - else - { - Line[1].X = OuterPoints[Idx].X; - Line[1].Y = OuterPoints[Idx].Y; - } - - alternator = !alternator; - - // Draw a line segment - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, ColorNonSelected, true, 1.0f); - } - - // Draw inner circle - alternator = false; - Line[0] = InnerPoints.Last(); - for (int32 Idx = 0; Idx < InnerPoints.Num(); ++Idx) - { - // alternate the points order each time to some some assignment cycles - if (alternator) - { - Line[0].X = InnerPoints[Idx].X; - Line[0].Y = InnerPoints[Idx].Y; - } - else - { - Line[1].X = InnerPoints[Idx].X; - Line[1].Y = InnerPoints[Idx].Y; - } - - alternator = !alternator; - - // Draw a line segment - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, bSelected ? ColorSelected : ColorNonSelected, true, 3.0f); - } -} - -void -SCustomizedBox::SetHoudiniParameter(TArray& InParams) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - - bool bIsMultiparmInstanceHeader = MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 0; - - switch (MainParam->GetParameterType()) - { - case EHoudiniParameterType::Button: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON; - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP; - } - break; - - case EHoudiniParameterType::Color: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR; - } - break; - - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor * ColorRampParameter = Cast(MainParam); - if (!ColorRampParameter || ColorRampParameter->IsPendingKill()) - return; - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP; - if (ColorRampParameter->CachedPoints.Num() > 0) - MarginHeight = MarginHeight + HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE * (float)(ColorRampParameter->CachedPoints.Num() - 1); - } - break; - - case EHoudiniParameterType::File: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE; - } - break; - - case EHoudiniParameterType::FileDir: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR; - } - break; - - case EHoudiniParameterType::FileGeo: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO; - } - break; - - case EHoudiniParameterType::FileImage: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE; - } - break; - - case EHoudiniParameterType::Float: - { - if (MainParam->GetTupleSize() == 3) - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3; - } - else - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT - + (MainParam->GetTupleSize() - 1)* HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE; - } - } - } - break; - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat * FloatRampParameter = Cast(MainParam); - if (!FloatRampParameter || FloatRampParameter->IsPendingKill()) - return; - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP; - - if (FloatRampParameter->CachedPoints.Num() > 0) - MarginHeight = MarginHeight + Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE * (float)(FloatRampParameter->CachedPoints.Num() - 1); - } - break; - - case EHoudiniParameterType::Folder: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER; - } - break; - - case EHoudiniParameterType::FolderList: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST; - } - break; - - case EHoudiniParameterType::Input: - { - UHoudiniParameterOperatorPath* InputParam = Cast(MainParam); - - if (!InputParam || InputParam->IsPendingKill() || !InputParam->HoudiniInput.IsValid()) - break; - - UHoudiniInput* Input = InputParam->HoudiniInput.Get(); - - if (!Input || Input->IsPendingKill()) - break; - - - if (bIsMultiparmInstanceHeader) - { - switch (Input->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - int32 ExpandedTransformUIs = 0; - for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) - { - if (Input->IsTransformUIExpanded(Idx)) - ExpandedTransformUIs += 1; - } - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER - + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Curve: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Asset: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Landscape: - { - if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::World: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER; - } - break; - case EHoudiniInputType::Skeletal: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER; - } - break; - default: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER; - break; - } - } - else - { - switch (Input->GetInputType()) - { - case EHoudiniInputType::Geometry: - { - int32 ExpandedTransformUIs = 0; - for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) - { - if (Input->IsTransformUIExpanded(Idx)) - ExpandedTransformUIs += 1; - } - - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE - + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM; - } - break; - case EHoudiniInputType::Curve: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE - + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE; - } - break; - case EHoudiniInputType::Asset: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET; - } - break; - case EHoudiniInputType::Landscape: - { - if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH; - } - break; - case EHoudiniInputType::World: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD; - } - break; - case EHoudiniInputType::Skeletal: - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL; - } - break; - default: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT; - break; - - } - } - } - break; - - case EHoudiniParameterType::Int: - { - if (MainParam->GetTupleSize() == 3) - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3; - } - else - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER + - (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE; - } - } - } - break; - - case EHoudiniParameterType::IntChoice: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE; - } - break; - - case EHoudiniParameterType::Label: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL; - } - break; - - case EHoudiniParameterType::MultiParm: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM; - } - break; - - case EHoudiniParameterType::Separator: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR; - bIsSeparator = true; - } - break; - - case EHoudiniParameterType::String: - { - if (bIsMultiparmInstanceHeader) - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER; - } - else - { - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING - + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE; - } - } - break; - - case EHoudiniParameterType::StringAssetRef: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF; - } - break; - - case EHoudiniParameterType::StringChoice: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE; - } - break; - - case EHoudiniParameterType::Toggle: - { - if (bIsMultiparmInstanceHeader) - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER; - else - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE; - } - break; - - case EHoudiniParameterType::Invalid: - MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID; - break; - - default: - MarginHeight = 0.0f; - break; - } -} - -float -SCustomizedBox::AddIndentation(UHoudiniParameter* InParam, - TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists) -{ - if (!InParam || InParam->IsPendingKill()) - return 0.0f; - - bool bIsMainParmSimpleFolder = false; - // Get if this Parameter is a simple / collapsible folder - if (InParam->GetParameterType() == EHoudiniParameterType::Folder) - { - UHoudiniParameterFolder* FolderParm = Cast(InParam); - if (FolderParm) - bIsMainParmSimpleFolder = !FolderParm->IsTab(); - } - - int32 ParentId = InParam->GetParentParmId(); - UHoudiniParameter* CurParm = InParam; - float Indentation = 0.0f; - - while (ParentId >= 0) - { - UHoudiniParameter* ParentFolder = nullptr; - UHoudiniParameterMultiParm* ParentMultiParm = nullptr; - - if (InAllFoldersAndFolderLists.Contains(ParentId)) - ParentFolder = InAllFoldersAndFolderLists[ParentId]; - - if (InAllMultiParms.Contains(ParentId)) - ParentMultiParm = InAllMultiParms[ParentId]; - - // The parent is a folder, add one unit of indentation - if (ParentFolder) - { - // Update the parent parm id - ParentId = ParentFolder->GetParentParmId(); - - if (ParentFolder->GetParameterType() == EHoudiniParameterType::FolderList) - continue; - - UHoudiniParameterFolder* Folder = Cast(ParentFolder); - - if (!Folder) - continue; - - // update the current parm, find the parent of new cur param in the next round - CurParm = Folder; - Indentation += 1.0f; - } - // The parent is a multiparm - else if (ParentMultiParm) - { - // Update the parent parm id - ParentId = ParentMultiParm->GetParentParmId(); - - if (CurParm->GetChildIndex() == 0) - { - Indentation += 0.0f; - } - else - { - Indentation += 2.0f; - } - - // update the current parm, find the parent of new cur param in the next round - CurParm = ParentMultiParm; - } - else - { - // no folder/multiparm parent, end the loop - ParentId = -1; - } - } - - - float IndentationWidth = INDENTATION_UNIT_WIDTH * Indentation; - - // Add a base indentation to non simple/collapsible param - // Since it needs more space to offset the arrow width - if (!bIsMainParmSimpleFolder) - IndentationWidth += NON_FOLDER_OFFSET_WIDTH; - - this->AddSlot().AutoWidth() - [ - SNew(SBox).WidthOverride(IndentationWidth) - ]; - - - return IndentationWidth; -}; - -int32 -SCustomizedBox::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const -{ - - SHorizontalBox::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); - - // Initialize line buffer - TArray Line; - Line.SetNumZeroed(2); - // Initialize color buffer - FLinearColor Color = FLinearColor::White; - Color.A = 0.3; - - // draw the bottom line if this row is the tab folder list - if (bIsTabFolderListRow) - { - // Get the start position of the tabs bottom line (right bottom pt of the right most child widget) - float VerticalLineStartPosX = 0.0f; - float VerticalLineStartPosY = 0.0f; - float BottomLineStartPosX = 0.0f; - float BottomLineStartPosY = -1.0f; - - for (int32 Idx = 0; Idx < Children.Num(); ++Idx) - { - TSharedPtr CurChild = Children.GetChildAt(Idx); - if (!CurChild.IsValid()) - continue; - - if (Idx == 0) - { - VerticalLineStartPosX = CurChild->GetDesiredSize().X; - VerticalLineStartPosY = CurChild->GetDesiredSize().Y; - } - - BottomLineStartPosX += CurChild->GetDesiredSize().X; - - if (BottomLineStartPosY < 0.0f) - BottomLineStartPosY= CurChild->GetDesiredSize().Y; - } - - // Draw bottom line - Line[0].X = BottomLineStartPosX; - Line[0].Y = BottomLineStartPosY; - Line[1].X = AllottedGeometry.Size.X; - Line[1].Y = BottomLineStartPosY; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - - // Draw divider lines - { - Line[0].Y = -MarginHeight; - Line[1].Y = AllottedGeometry.Size.Y + MarginHeight; - - int32 NumOfLinesToDraw = bIsTabFolderListRow ? DividerLinePositions.Num() - 1 : DividerLinePositions.Num(); - for (int32 Idx = 0; Idx < NumOfLinesToDraw; ++Idx) - { - const float& CurDivider = DividerLinePositions[Idx]; - Line[0].X = CurDivider; - Line[1].X = CurDivider; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - - // Draw the last inner most divider line differently when this the tabs' row. - if (bIsTabFolderListRow && DividerLinePositions.Num() > 0) - { - const float& TabDivider = DividerLinePositions.Last(); - Line[0].X = TabDivider; - Line[1].X = TabDivider; - Line[0].Y = 0.f; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - } - } - - // Draw tab ending lines - { - float YPos = 0.0f; - - for (const float & CurEndingDivider : EndingDividerLinePositions) - { - // Draw cur ending line (vertical) - - Line[0].X = CurEndingDivider; - Line[0].Y = -2.3f; - Line[1].X = CurEndingDivider; - Line[1].Y = YPos; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - - // Draw cur ending line (horizontal) - - // Line[0].X = CurEndingDivider; - Line[0].Y = YPos; - Line[1].X = AllottedGeometry.Size.X; - // Line[1].Y = YPos; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.0f); - - YPos += 2.0f; - } - } - - // Draw the separator line if this is the row of a separator parameter - { - if (bIsSeparator) - { - Line[0].X = 25.f; - if (DividerLinePositions.Num() > 0) - Line[0].X += DividerLinePositions.Last(); - - Line[0].Y = AllottedGeometry.Size.Y / 2.f; - Line[1].X = AllottedGeometry.Size.X - 20.f; - Line[1].Y = Line[0].Y; - - Color.A = 0.7; - - FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, - ESlateDrawEffect::None, Color, true, 1.5f); - } - } - - return LayerId; -}; - -void -SHoudiniFloatRampCurveEditor::Construct(const FArguments & InArgs) -{ - SCurveEditor::Construct(SCurveEditor::FArguments() - .ViewMinInput(InArgs._ViewMinInput) - .ViewMaxInput(InArgs._ViewMaxInput) - .ViewMinOutput(InArgs._ViewMinOutput) - .ViewMaxOutput(InArgs._ViewMaxOutput) - .XAxisName(InArgs._XAxisName) - .YAxisName(InArgs._YAxisName) - .HideUI(InArgs._HideUI) - .DrawCurve(InArgs._DrawCurve) - .TimelineLength(InArgs._TimelineLength) - .AllowZoomOutput(InArgs._AllowZoomOutput) - .ShowInputGridNumbers(InArgs._ShowInputGridNumbers) - .ShowOutputGridNumbers(InArgs._ShowOutputGridNumbers) - .ShowZoomButtons(InArgs._ShowZoomButtons) - .ZoomToFitHorizontal(InArgs._ZoomToFitHorizontal) - .ZoomToFitVertical(InArgs._ZoomToFitVertical) - ); - - - UCurveEditorSettings * CurveEditorSettings = GetSettings(); - if (CurveEditorSettings) - { - CurveEditorSettings->SetTangentVisibility(ECurveEditorTangentVisibility::NoTangents); - } -} - -void -SHoudiniColorRampCurveEditor::Construct(const FArguments & InArgs) -{ - SColorGradientEditor::Construct(SColorGradientEditor::FArguments() - .ViewMinInput(InArgs._ViewMinInput) - .ViewMaxInput(InArgs._ViewMaxInput) - ); -} - - -FReply -SHoudiniFloatRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - FReply Reply = SCurveEditor::OnMouseButtonUp(MyGeometry, MouseEvent); - - if (!HoudiniFloatRampCurve.IsValid()) - return Reply; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - FRichCurve& FloatCurve = HoudiniFloatRampCurve.Get()->FloatCurve; - - TArray>& FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; - - if (FloatRampParameters.Num() < 1) - return Reply; - - if (!FloatRampParameters[0].IsValid()) - return Reply; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - // Do not allow modification when the parent HDA of the main param is being cooked. - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points of the main float ramp param to other parameters - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - // Modification is based on the main parameter, use synced points if the main param is on auto update mode, use cached points otherwise. - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - // On mouse button up handler handles point modification only - if (FloatCurve.GetNumKeys() != NumMainPoints) - return Reply; - - bool bNeedToRefreshEditor= false; - - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float& CurvePosition = FloatCurve.Keys[Idx].Time; - float& CurveValue = FloatCurve.Keys[Idx].Value; - - // This point is modified - if (MainPoint->GetPosition() != CurvePosition || MainPoint->GetValue() != CurveValue) - { - - // The editor needs refresh only if the main parameter is on manual mode, and has been modified - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedToRefreshEditor = true; - - // Iterate through the float ramp parameter of all selected HDAs. - for (auto & NextRampFloat : FloatRampParameters) - { - if (!NextRampFloat.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedRampFloat = NextRampFloat.Get(); - - if (!SelectedRampFloat) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedRampFloat)) - continue; - - if (SelectedRampFloat->IsAutoUpdate() && bCookingEnabled) - { - // The selected float ramp parameter is on auto update mode, use its synced points. - TArray &SelectedRampPoints = SelectedRampFloat->Points; - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // Synced points in the selected ramp is more than or the same number as that in the main parameter, - // modify the position and value of the synced point and mark them as changed. - - UHoudiniParameterRampFloatPoint*& ModifiedPoint = SelectedRampPoints[Idx]; - - if (!ModifiedPoint) - continue; - - if (ModifiedPoint->GetPosition() != CurvePosition && ModifiedPoint->PositionParentParm) - { - ModifiedPoint->SetPosition(CurvePosition); - ModifiedPoint->PositionParentParm->MarkChanged(true); - } - - if (ModifiedPoint->GetValue() != CurveValue && ModifiedPoint->ValueParentParm) - { - ModifiedPoint->SetValue(CurveValue); - ModifiedPoint->ValueParentParm->MarkChanged(true); - } - } - else - { - // Synced points in the selected ramp is less than that in the main parameter - // Since we have pushed the points of the main param to all of the selected ramps, - // We need to modify the insert event. - - int32 IndexInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedRampFloat->ModificationEvents.IsValidIndex(Idx)) - { - UHoudiniParameterRampModificationEvent*& ModEvent = SelectedRampFloat->ModificationEvents[Idx]; - if (!ModEvent) - continue; - - if (ModEvent->InsertPosition != CurvePosition) - ModEvent->InsertPosition = CurvePosition; - - if (ModEvent->InsertFloat != CurveValue) - ModEvent->InsertFloat = CurveValue; - } - - } - } - else - { - // The selected float ramp is on manual update mode, use the cached points. - TArray &FloatRampCachedPoints = SelectedRampFloat->CachedPoints; - - // Since we have pushed the points in main param to all the selected float ramp, - // we need to modify the corresponding cached point in the selected float ramp. - - if (FloatRampCachedPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampFloatPoint*& ModifiedCachedPoint = FloatRampCachedPoints[Idx]; - - if (!ModifiedCachedPoint) - continue; - - if (ModifiedCachedPoint->Position != CurvePosition) - { - ModifiedCachedPoint->Position = CurvePosition; - SelectedRampFloat->bCaching = true; - if (!bCookingEnabled) - { - //SelectedRampFloat->MarkChanged(true); - if (ModifiedCachedPoint->PositionParentParm) - ModifiedCachedPoint->PositionParentParm->MarkChanged(true); - } - } - - if (ModifiedCachedPoint->Value != CurveValue) - { - ModifiedCachedPoint->Value = CurveValue; - SelectedRampFloat->bCaching = true; - if (!bCookingEnabled) - { - //SelectedRampFloat->MarkChanged(true); - if (ModifiedCachedPoint->ValueParentParm) - ModifiedCachedPoint->ValueParentParm->MarkChanged(true); - } - } - } - } - } - } - } - - - if (bNeedToRefreshEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } - - return Reply; -} - -FReply -SHoudiniFloatRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) -{ - FReply Reply = SCurveEditor::OnKeyDown(MyGeometry, InKeyEvent); - - if (InKeyEvent.GetKey().ToString() != FString("Enter")) - return Reply; - - if (!HoudiniFloatRampCurve.IsValid() || !HoudiniFloatRampCurve.Get()) - return Reply; - - TArray> FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; - - if (FloatRampParameters.Num() < 1) - return Reply; - - if (!FloatRampParameters[0].IsValid()) - return Reply; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Do nothing if the main param is on auto update mode - if (MainParam->IsAutoUpdate() && bCookingEnabled) - return Reply; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points in the main float ramp to the float ramp parameters in all selected HDAs. - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - for (auto& NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - if (!SelectedFloatRamp) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not sync the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - // Sync the cached points if the selected float ramp parameter is on manual update mode - //FHoudiniParameterDetails::SyncCachedFloatRampPoints(SelectedFloatRamp); - SelectedFloatRamp->SyncCachedPoints(); - } - - return Reply; -} - -void -UHoudiniFloatRampCurve::OnCurveChanged(const TArray& ChangedCurveEditInfos) -{ - Super::OnCurveChanged(ChangedCurveEditInfos); - - if (FloatRampParameters.Num() < 1) - return; - - if (!FloatRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return; - - // Do not allow modification when the parent HDA of the main param is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - // Push all the points of the Main parameter to other parameters - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); - - // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode, otherwise use its cached points. - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - bool bNeedUpdateEditor = false; - - // OnCurveChanged handler handles point delete and insertion only - - // A point is deleted. - if (FloatCurve.GetNumKeys() < NumMainPoints) - { - // Find the index of the deleted point - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float CurPointPosition = MainPoint->GetPosition(); - float CurCurvePosition = -1.0f; - - if (FloatCurve.Keys.IsValidIndex(Idx)) - CurCurvePosition = FloatCurve.Keys[Idx].Time; - - // Delete the point at Idx - if (CurCurvePosition != CurPointPosition) - { - // Iterate through all the float ramp parameter in all the selected HDAs - for (auto & NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - if (!SelectedFloatRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - TArray & SelectedRampPoints = SelectedFloatRamp->Points; - - // The selected float ramp is on auto update mode: - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // If the number of synced points of the selected float ramp is greater or equal to the number of points of that in the main param, - // Create a Houdini engine manager event to delete the point at Idx of the selected float ramp; - - UHoudiniParameterRampFloatPoint* PointToDelete = SelectedRampPoints[Idx]; - - if (!PointToDelete) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(SelectedFloatRamp, PointToDelete->InstanceIndex); - SelectedFloatRamp->MarkChanged(true); - } - else - { - // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, - // delete the corresponding inserting event. - - int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - SelectedFloatRamp->ModificationEvents.RemoveAt(IdxInEventsArray); - } - } - else - { - // The selected float ramp is on manual update mode: - // Since we have pushed all the points in main param to the cached points of the selected float ramp, - // remove the corresponding points from the cached points array. - - if (SelectedFloatRamp->CachedPoints.IsValidIndex(Idx)) - { - SelectedFloatRamp->CachedPoints.RemoveAt(Idx); - SelectedFloatRamp->bCaching = true; - } - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point is inserted - else if (FloatCurve.GetNumKeys() > NumMainPoints) - { - // Find the index of the inserted point - for (int32 Idx = 0; Idx < FloatCurve.GetNumKeys(); ++Idx) - { - - float CurPointPosition = -1.0f; - float CurCurvePosition = FloatCurve.Keys[Idx].Time; - - if (MainPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - CurPointPosition = MainPoint->GetPosition(); - } - - // Insert instance at Idx - if (CurPointPosition != CurCurvePosition) - { - // Iterate through the float ramp parameter of all selected HDAs. - for (auto & NextFloatRamp : FloatRampParameters) - { - if (!NextFloatRamp.IsValid()) - continue; - - UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) - continue; - - if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - // If the selected float ramp is on auto update mode: - // Since we have pushed all the points of main parameter to the selected, - // create a Houdini engine manager event to insert a point. - - FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent( - SelectedFloatRamp, CurCurvePosition, FloatCurve.Keys[Idx].Value, EHoudiniRampInterpolationType::LINEAR); - - SelectedFloatRamp->MarkChanged(true); - } - else - { - // If the selected float ramp is on manual update mode: - // push a new point to the cached points array - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(SelectedFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = CurCurvePosition; - NewCachedPoint->Value = FloatCurve.Keys[Idx].Value; - NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; - - if (Idx >= SelectedFloatRamp->CachedPoints.Num()) - SelectedFloatRamp->CachedPoints.Add(NewCachedPoint); - else - SelectedFloatRamp->CachedPoints.Insert(NewCachedPoint, Idx); - - SelectedFloatRamp->bCaching = true; - - if (!bCookingEnabled) - SelectedFloatRamp->MarkChanged(true); - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - if (bNeedUpdateEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } - -} - - -FReply -SHoudiniColorRampCurveEditor::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - FReply Reply = SColorGradientEditor::OnMouseButtonDown(MyGeometry, MouseEvent); - - if (HoudiniColorRampCurve.IsValid()) - { - UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); - if (Curve) - Curve->bEditing = true; - } - - return Reply; -} - -FReply -SHoudiniColorRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) -{ - - FReply Reply = SColorGradientEditor::OnMouseButtonUp(MyGeometry, MouseEvent); - - if (HoudiniColorRampCurve.IsValid()) - { - UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); - - if (Curve) - { - Curve->bEditing = false; - Curve->OnColorRampCurveChanged(true); - } - } - - return Reply; - -} - -FReply -SHoudiniColorRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) -{ - FReply Reply = SColorGradientEditor::OnKeyDown(MyGeometry, InKeyEvent); - - if (InKeyEvent.GetKey().ToString() != FString("Enter")) - return Reply; - - if (!HoudiniColorRampCurve.IsValid() || !HoudiniColorRampCurve.Get()) - return Reply; - - TArray> &ColorRampParameters = HoudiniColorRampCurve.Get()->ColorRampParameters; - - if (ColorRampParameters.Num() < 1) - return Reply; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return Reply; - - // Do nothing if the main param is on auto update mode - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - if (MainParam->IsAutoUpdate() && bCookingEnabled) - return Reply; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return Reply; - - // Push the points in the main color ramp to the color ramp parameters in all selected HDAs. - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); - - for (auto& NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not sync the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - // Sync the cached points if the selected color ramp is on manual update mode - FHoudiniParameterDetails::SyncCachedColorRampPoints(SelectedColorRamp); - } - - return Reply; -} - -void -UHoudiniColorRampCurve::OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) -{ - Super::OnCurveChanged(ChangedCurveEditInfos); - - OnColorRampCurveChanged(); -} - -void -UHoudiniColorRampCurve::OnColorRampCurveChanged(bool bModificationOnly) -{ - // Array is always true in this case - // if (!FloatCurves) - // return; - - if (ColorRampParameters.Num() < 1) - return; - - if (!ColorRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return; - - // Do not allow modification when the parent HDA of the main param is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - // Push all the points of the main parameter to other parameters - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); - - // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode,otherwise use its cached points. - bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - int32 NumMainPoints = MainPoints.Num(); - - bool bNeedUpdateEditor = false; - - // OnCurveChanged handler of color ramp curve editor handles point delete, insert and color change - - // A point is deleted - if (FloatCurves->GetNumKeys() < NumMainPoints) - { - if (bModificationOnly) - return; - - // Find the index of the deleted point - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - float CurPointPosition = MainPoint->GetPosition(); - float CurCurvePosition = -1.0f; - - if (FloatCurves[0].Keys.IsValidIndex(Idx)) - CurCurvePosition = FloatCurves[0].Keys[Idx].Time; - - // Delete the point at Idx - if (CurCurvePosition != CurPointPosition) - { - // Iterate through all the color ramp parameter in all the selected HDAs - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - TArray & SelectedRampPoints = SelectedColorRamp->Points; - - // The selected color ramp is on auto update mode: - - if (SelectedRampPoints.IsValidIndex(Idx)) - { - // If the number of synced points of the selected color ramp is greater or equal to the number of points of that in the main param, - // create a Houdini engine manager event to delete the point at Idx of the selected float ramp; - - UHoudiniParameterRampColorPoint* PointToDelete = SelectedRampPoints[Idx]; - - if (!PointToDelete) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(SelectedColorRamp, PointToDelete->InstanceIndex); - SelectedColorRamp->MarkChanged(true); - } - else - { - // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, - // delete the corresponding inserting event. - - int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); - if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - SelectedColorRamp->ModificationEvents.RemoveAt(IdxInEventsArray); - } - } - else - { - // The selected color ramp is on manual update mode: - // Since we have pushed all the points in main param to the cached points of the selected float ramp, - // remove the corresponding points from the cached points array - if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) - { - SelectedColorRamp->CachedPoints.RemoveAt(Idx); - SelectedColorRamp->bCaching = true; - } - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point is inserted - else if (FloatCurves[0].GetNumKeys() > NumMainPoints) - { - - if (bModificationOnly) - return; - - // Find the index of the inserted point - for (int32 Idx = 0; Idx < FloatCurves[0].GetNumKeys(); ++Idx) - { - - float CurPointPosition = -1.0f; - float CurCurvePosition = FloatCurves[0].Keys[Idx].Time; - - if (MainPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - CurPointPosition = MainPoint->GetPosition(); - } - - // Insert a point at Idx - if (CurPointPosition != CurCurvePosition) - { - // Get the interpolation value of inserted color point - - FLinearColor ColorPrev = FLinearColor::Black; - FLinearColor ColorNext = FLinearColor::White; - float PositionPrev = 0.0f; - float PositionNext = 1.0f; - - if (MainParam->IsAutoUpdate() && bCookingEnabled) - { - // Try to get its previous point's color - if (MainParam->Points.IsValidIndex(Idx - 1)) - { - ColorPrev = MainParam->Points[Idx - 1]->GetValue(); - PositionPrev = MainParam->Points[Idx - 1]->GetPosition(); - } - - // Try to get its next point's color - if (MainParam->Points.IsValidIndex(Idx)) - { - ColorNext = MainParam->Points[Idx]->GetValue(); - PositionNext = MainParam->Points[Idx]->GetPosition(); - } - } - else - { - // Try to get its previous point's color - if (MainParam->CachedPoints.IsValidIndex(Idx - 1)) - { - ColorPrev = MainParam->CachedPoints[Idx - 1]->GetValue(); - PositionPrev = MainParam->CachedPoints[Idx - 1]->GetPosition(); - } - - // Try to get its next point's color - if (MainParam->CachedPoints.IsValidIndex(Idx)) - { - ColorNext = MainParam->CachedPoints[Idx]->GetValue(); - PositionNext = MainParam->CachedPoints[Idx]->GetPosition(); - } - } - - float TotalWeight = FMath::Abs(PositionNext - PositionPrev); - float PrevWeight = FMath::Abs(CurCurvePosition - PositionPrev); - float NextWeight = FMath::Abs(PositionNext - CurCurvePosition); - - FLinearColor InsertedColor = ColorPrev * (PrevWeight / TotalWeight) + ColorNext * (NextWeight / TotalWeight); - - // Iterate through the color ramp parameter of all selected HDAs. - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - // If the selected color ramp is on auto update mode: - // Since we have pushed all the points of main parameter to the selected, - // create a Houdini engine manager event to insert a point. - - FHoudiniParameterDetails::CreateColorRampParameterInsertEvent( - SelectedColorRamp, CurCurvePosition, InsertedColor, EHoudiniRampInterpolationType::LINEAR); - - SelectedColorRamp->MarkChanged(true); - } - else - { - // If the selected color ramp is on manual update mode: - // Push a new point to the cached points array - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(SelectedColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = CurCurvePosition; - NewCachedPoint->Value = InsertedColor; - NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; - - if (Idx >= SelectedColorRamp->CachedPoints.Num()) - SelectedColorRamp->CachedPoints.Add(NewCachedPoint); - else - SelectedColorRamp->CachedPoints.Insert(NewCachedPoint, Idx); - - SelectedColorRamp->bCaching = true; - } - } - - // Refresh the editor only when the main parameter is on manual update mode and has been modified. - if (!MainParam->IsAutoUpdate() && bCookingEnabled) - bNeedUpdateEditor = true; - - break; - } - } - } - - // A point's color is changed - else - { - if (bEditing) - return; - - for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) - { - UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; - - if (!MainPoint) - continue; - - // Only handle color change - { - float CurvePosition = FloatCurves[0].Keys[Idx].Time; - float PointPosition = MainPoint->GetPosition(); - - FLinearColor CurveColor = FLinearColor::Black; - FLinearColor PointColor = MainPoint->GetValue(); - - CurveColor.R = FloatCurves[0].Keys[Idx].Value; - CurveColor.G = FloatCurves[1].Keys[Idx].Value; - CurveColor.B = FloatCurves[2].Keys[Idx].Value; - - // Color is changed at Idx - if (CurveColor != PointColor || CurvePosition != PointPosition) - { - // Iterate through the all selected color ramp parameters - for (auto & NextColorRamp : ColorRampParameters) - { - if (!NextColorRamp.IsValid()) - continue; - - UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); - - if (!SelectedColorRamp) - continue; - - // Do not modify the selected parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) - continue; - - if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) - { - // The selected color ramp parameter is on auto update mode - - if (SelectedColorRamp->Points.IsValidIndex(Idx)) - { - // If the number of synced points in the selected color ramp is more or equal to that in the main parameter: - // Modify the corresponding synced point of the selected color ramp, and marked it as changed. - - UHoudiniParameterRampColorPoint* Point = SelectedColorRamp->Points[Idx]; - - if (!Point) - continue; - - if (Point->GetValue() != CurveColor && Point->ValueParentParm) - { - Point->SetValue(CurveColor); - Point->ValueParentParm->MarkChanged(true); - } - - if (Point->GetPosition() != CurvePosition && Point->PositionParentParm) - { - Point->SetPosition(CurvePosition); - Point->PositionParentParm->MarkChanged(true); - } - } - else - { - // If the number of synced points in the selected color ramp is less than that in the main parameter: - // Since we have push the points in the main parameter to all selected parameters, - // we need to modify the corresponding insert event. - - int32 IdxInEventsArray = Idx - SelectedColorRamp->Points.Num(); - - if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = SelectedColorRamp->ModificationEvents[IdxInEventsArray]; - - if (!Event) - continue; - - if (Event->InsertColor != CurveColor) - Event->InsertColor = CurveColor; - - if (Event->InsertPosition != CurvePosition) - Event->InsertPosition = CurvePosition; - } - } - } - else - { - // The selected color ramp is on manual update mode - // Since we have push the points in the main parameter to all selected parameters, - // modify the corresponding point in the cached points array of the selected color ramp. - if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) - { - UHoudiniParameterRampColorPoint* CachedPoint = SelectedColorRamp->CachedPoints[Idx]; - - if (!CachedPoint) - continue; - - if (CachedPoint->Value != CurveColor) - { - CachedPoint->Value = CurveColor; - bNeedUpdateEditor = true; - } - - if (CachedPoint->Position != CurvePosition) - { - CachedPoint->Position = CurvePosition; - SelectedColorRamp->bCaching = true; - bNeedUpdateEditor = true; - } - } - } - } - } - } - } - } - - - if (bNeedUpdateEditor) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - } -} - -template< class T > -bool FHoudiniParameterDetails::CastParameters( - TArray InParams, TArray& OutCastedParams ) -{ - for (auto CurrentParam : InParams) - { - T* CastedParam = Cast(CurrentParam); - if (CastedParam && !CastedParam->IsPendingKill()) - OutCastedParams.Add(CastedParam); - } - - return (OutCastedParams.Num() == InParams.Num()); -} - - -void -FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* InParam = InParams[0]; - if (!InParam || InParam->IsPendingKill()) - return; - - // The directory won't parse if parameter ids are -1 - // simply return - if (InParam->GetParmId() < 0) - return; - - if (CurrentRampFloat) - { - // CreateWidgetFloatRamp(HouParameterCategory, InParams); - // If this parameter is a part of the last float ramp, skip it - if (InParam->GetIsChildOfMultiParm() && InParam->GetParentParmId() == CurrentRampFloat->GetParmId()) - return; - - // This parameter is not part of the last float ramp (we've passed all of its points/instances), reset - // CurrentRampFloat in order to continue normal processing of parameters - CurrentRampFloat = nullptr; - } - if (CurrentRampColor) - { - // CreateWidgetColorRamp(HouParameterCategory, InParams); - // if this parameter is a part of the last color ramp, skip it - if (InParam->GetIsChildOfMultiParm() && InParam->GetParentParmId() == CurrentRampColor->GetParmId()) - return; - - // This parameter is not part of the last color ramp (we've passed all of its points/instances), reset - // CurrentRampColor in order to continue normal processing of parameters - CurrentRampColor = nullptr; - } - - switch (InParam->GetParameterType()) - { - case EHoudiniParameterType::Float: - { - CreateWidgetFloat(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Int: - { - CreateWidgetInt(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::String: - { - CreateWidgetString(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::IntChoice: - case EHoudiniParameterType::StringChoice: - { - CreateWidgetChoice(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Separator: - { - TArray SepParams; - if (CastParameters(InParams, SepParams)) - { - bool bEnabled = InParams.IsValidIndex(0) ? !SepParams[0]->IsDisabled() : true; - CreateWidgetSeparator(HouParameterCategory, InParams, bEnabled); - } - } - break; - - case EHoudiniParameterType::Color: - { - CreateWidgetColor(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Button: - { - CreateWidgetButton(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::ButtonStrip: - { - CreateWidgetButtonStrip(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Label: - { - CreateWidgetLabel(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Toggle: - { - CreateWidgetToggle(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - CreateWidgetFile(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::FolderList: - { - CreateWidgetFolderList(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Folder: - { - CreateWidgetFolder(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::MultiParm: - { - CreateWidgetMultiParm(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::FloatRamp: - { - CreateWidgetFloatRamp(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::ColorRamp: - { - CreateWidgetColorRamp(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Input: - { - CreateWidgetOperatorPath(HouParameterCategory, InParams); - } - break; - - case EHoudiniParameterType::Invalid: - { - HandleUnsupportedParmType(HouParameterCategory, InParams); - } - break; - - default: - { - HandleUnsupportedParmType(HouParameterCategory, InParams); - } - break; - } - - // Remove a divider lines recurrsively if current parameter hits the end of a tabs - RemoveTabDividers(HouParameterCategory, InParam); - -} - -void -FHoudiniParameterDetails::CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory) -{ - FDetailWidgetRow & Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()); - TSharedPtr TabEndingRow = SNew(SCustomizedBox); - - TabEndingRow->DividerLinePositions = DividerLinePositions; - - if (TabEndingRow.IsValid()) - CurrentTabEndingRow = TabEndingRow.Get(); - - Row.WholeRowWidget.Widget = TabEndingRow.ToSharedRef(); - Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniParameterDetails::CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam|| MainParam->IsPendingKill()) - return; - - if (!Row) - return; - - TSharedRef< SCustomizedBox > HorizontalBox = SNew(SCustomizedBox); - - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - - - if (MainParam->IsDirectChildOfMultiParm()) - { - FString ParameterLabelStr = MainParam->GetParameterLabel(); - - // If it is head of an multiparm instance - if (MainParam->GetChildIndex() == 0) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - } - ParameterLabelStr += TEXT(" (") + FString("") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - HorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - else - { - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(MainParam->GetParameterLabel()) : FText::GetEmpty(); - HorizontalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - - Row->NameWidget.Widget = HorizontalBox; -} - -void -FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) -{ - if (!Row) - return; - - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - FString ParameterLabelStr = MainParam->GetParameterLabel(); - TSharedRef HorizontalBox = SNew(SCustomizedBox); - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - - TSharedPtr VerticalBox; - HorizontalBox->AddSlot() - [ - SAssignNew(VerticalBox, SVerticalBox) - ]; - - if (MainParam->IsDirectChildOfMultiParm()) - { - // If it is head of an multiparm instance - if (MainParam->GetChildIndex() == 0) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - } - - ParameterLabelStr += TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor* RampParameter = Cast(MainParam); - if (RampParameter) - { - if (RampParameter->bCaching) - ParameterLabelStr += "*"; - } - } - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - else - { - // TODO: Refactor me...extend 'auto/manual update' to all parameters? (It only applies to color and float ramps for now.) - bool bParamNeedUpdate = false; - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor* RampParameter = Cast(MainParam); - if (RampParameter) - bParamNeedUpdate = RampParameter->bCaching; - } - else if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat* RampParameter = Cast(MainParam); - if (RampParameter) - bParamNeedUpdate = RampParameter->bCaching; - } - - if (bParamNeedUpdate) - ParameterLabelStr += "*"; - - const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FinalParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) - ]; - } - - auto IsAutoUpdateChecked = [MainParam]() - { - if (!MainParam || MainParam->IsPendingKill()) - return ECheckBoxState::Unchecked; - - return MainParam->IsAutoUpdate() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; - }; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - auto OnAutoUpdateCheckBoxStateChanged = [MainParam, InParams, bCookingEnabled](ECheckBoxState NewState) - { - if (NewState == ECheckBoxState::Checked) - { - for (auto & NextSelectedParam : InParams) - { - if (!NextSelectedParam) - continue; - - if (NextSelectedParam->IsAutoUpdate() && bCookingEnabled) - continue; - - // Do not allow mode change when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) - continue; - - switch (MainParam->GetParameterType()) - { - case EHoudiniParameterType::ColorRamp: - { - UHoudiniParameterRampColor* ColorRampParameter = Cast(NextSelectedParam); - - if (!ColorRampParameter) - continue; - - // Do not sync the selected color ramp parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) - continue; - - // Sync the Cached curve points at update mode switch. - FHoudiniParameterDetails::SyncCachedColorRampPoints(ColorRampParameter); - } - break; - - case EHoudiniParameterType::FloatRamp: - { - UHoudiniParameterRampFloat* FloatRampParameter = Cast(NextSelectedParam); - - if (!FloatRampParameter) - continue; - - // Do not sync the selected float ramp parameter if its parent HDA is being cooked - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) - continue; - - // Sync the Cached curve points at update mode switch. - //FHoudiniParameterDetails::SyncCachedFloatRampPoints(FloatRampParameter); - FloatRampParameter->SyncCachedPoints(); - } - break; - - default: - break; - } - - NextSelectedParam->SetAutoUpdate(true); - } - } - else - { - for (auto & NextSelectedParam : InParams) - { - if (!NextSelectedParam) - continue; - - if (!(NextSelectedParam->IsAutoUpdate() && bCookingEnabled)) - continue; - - // Do not allow mode change when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) - continue; - - NextSelectedParam->SetAutoUpdate(false); - } - } - }; - - // Auto update check box - TSharedPtr CheckBox; - - VerticalBox->AddSlot() - .VAlign(VAlign_Center) - .HAlign(HAlign_Left) - [ - SNew(SHorizontalBox) - - + SHorizontalBox::Slot() - [ - SAssignNew(CheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([OnAutoUpdateCheckBoxStateChanged](ECheckBoxState NewState) - { - OnAutoUpdateCheckBoxStateChanged(NewState); - }) - .IsChecked_Lambda([IsAutoUpdateChecked]() - { - return IsAutoUpdateChecked(); - }) - .Content() - [ - SNew(STextBlock) - .Text(LOCTEXT("AutoUpdate", "Auto-update")) - .ToolTipText(LOCTEXT("AutoUpdateTip", "When enabled, this parameter will automatically update its value while editing. Turning this off will allow you to more easily update it, and the update can be pushed by checking the toggle again.")) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ] - ]; - - if ((MainParam->GetParameterType() != EHoudiniParameterType::FloatRamp) && (MainParam->GetParameterType() != EHoudiniParameterType::ColorRamp)) - CheckBox->SetVisibility(EVisibility::Hidden); - - Row->NameWidget.Widget = HorizontalBox; -} - -FDetailWidgetRow* -FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount) -{ - if (InParams.Num() <= 0) - return nullptr; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return nullptr; - - // Created row for the current parameter (if there is not a row created, do not show the parameter). - FDetailWidgetRow* Row = nullptr; - - // Current parameter is in a multiparm instance (directly) - if (MainParam->IsDirectChildOfMultiParm()) - { - int32 ParentMultiParmId = MainParam->GetParentParmId(); - - // If this is a folder param, its folder list parent parm is the multiparm - if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) - { - if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) // This should not happen - return nullptr; - - UHoudiniParameterFolderList* ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); - if (!ParentFolderList || ParentFolderList->IsPendingKill()) - return nullptr; // This should not happen - - ParentMultiParmId = ParentFolderList->GetParentParmId(); - } - - if (!AllMultiParms.Contains(ParentMultiParmId)) // This should not happen normally - return nullptr; - - // Get the parent multiparm - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentMultiParmId]; - - // The parent multiparm is visible. - if (ParentMultiParm && ParentMultiParm->IsShown() && MainParam->ShouldDisplay()) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - - } - // This item is not a direct child of a multiparm. - else - { - bool bIsFolder = MainParam->GetParameterType() == EHoudiniParameterType::Folder; - - // If this parameter is a folder, its parent folder should be the second top of the stack - int32 NestedMinStackDepth = bIsFolder ? 1 : 0; - - // Current parameter is inside a folder. - if (FolderStack.Num() > NestedMinStackDepth) - { - // If the current parameter is a folder, we take the top second queue on the stack, since the top one represents itself. - // Otherwise take the top queue on the stack. - TArray & CurrentLayerFolderQueue = bIsFolder ? - FolderStack[FolderStack.Num() - 2] : FolderStack.Last(); - - if (CurrentLayerFolderQueue.Num() <= 0) // Error state - return nullptr; - - bool bParentFolderVisible = CurrentLayerFolderQueue[0]->IsContentShown(); - - bool bIsSelectedTabVisible = false; - - // If its parent folder is visible, display current parameter, - // Otherwise, just prune the stacks. - if (bParentFolderVisible) - { - int32 ParentFolderId = MainParam->GetParentParmId(); - - // If the current parameter is a folder, its parent is a folderlist. - // So we need to continue to get the parent of the folderlist. - if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) - { - if (AllFoldersAndFolderLists.Contains(ParentFolderId)) - ParentFolderId = AllFoldersAndFolderLists[ParentFolderId]->GetParentParmId(); - else - return nullptr; // error state - } - - UHoudiniParameterFolder* ParentFolder = nullptr; - - if (AllFoldersAndFolderLists.Contains(ParentFolderId)) - ParentFolder = Cast(AllFoldersAndFolderLists[ParentFolderId]); - - bool bShouldDisplayRow = MainParam->ShouldDisplay(); - - // This row should be shown if its parent folder is shown. - if (ParentFolder) - bShouldDisplayRow &= (ParentFolder->IsTab() && ParentFolder->IsChosen()) || (!ParentFolder->IsTab() && ParentFolder->IsExpanded()); - - if (bShouldDisplayRow) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - } - - // prune the stack finally - if (bDecreaseChildCount) - { - CurrentLayerFolderQueue[0]->GetChildCounter() -= 1; - PruneStack(); - } - } - // If this parameter is in the root dir, just create a row. - else - { - if (MainParam->ShouldDisplay()) - { - if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) - Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); - } - } - } - - if (!MainParam->IsVisible()) - return nullptr; - - - if (Row) - CurrentTabEndingRow = nullptr; - - return Row; -} - -void -FHoudiniParameterDetails::HandleUnsupportedParmType(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - CreateNestedRow(HouParameterCategory, (TArray)InParams); -} - -void -FHoudiniParameterDetails::CreateWidgetFloat( - IDetailCategoryBuilder & HouParameterCategory, - TArray& InParams ) -{ - TArray FloatParams; - if (!CastParameters(InParams, FloatParams)) - return; - - if (FloatParams.Num() <= 0) - return; - - UHoudiniParameterFloat* MainParam = FloatParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - // Helper function to find a unit from a string (name or abbreviation) - auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); - EUnit Unit = EUnit::Unspecified; - if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) - Unit = ParmUnit.GetValue(); - - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - // Lambdas for slider begin - auto SliderBegin = [&](TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), - FloatParams[0]->GetOuter()); - - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - FloatParams[Idx]->Modify(); - } - }; - - // Lambdas for slider end - auto SliderEnd = [&](TArray FloatParams) - { - // Mark the value as changed to trigger an update - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - FloatParams[Idx]->MarkChanged(true); - } - }; - - // Lambdas for changing the parameter value - auto ChangeFloatValueAt = [&](const float& Value, const int32& Index, const bool& DoChange, TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), - FloatParams[0]->GetOuter() ); - - bool bChanged = false; - for (int Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - FloatParams[Idx]->Modify(); - if (FloatParams[Idx]->SetValueAt(Value, Index)) - { - // Only mark the param has changed if DoChange is true!!! - if(DoChange) - FloatParams[Idx]->MarkChanged(true); - bChanged = true; - } - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if no parameter's value has actually been changed - Transaction.Cancel(); - } - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray FloatParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Revert to default value"), - FloatParams[0]->GetOuter()); - - if (TupleIndex < 0) - { - for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - if (FloatParams[Idx]->IsDefault()) - continue; - - FloatParams[Idx]->RevertToDefault(-1); - } - } - else - { - for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) - { - if (!FloatParams[Idx]) - continue; - - if (FloatParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - FloatParams[Idx]->RevertToDefault(TupleIndex); - } - } - return FReply::Handled(); - }; - - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - //TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - if (MainParam->GetTupleSize() == 3) - { - // Should we swap Y and Z fields (only relevant for Vector3) - // Ignore the swapping if that parameter has the noswap tag - bool SwapVector3 = !MainParam->GetNoSwap(); - - auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float& Val, const bool& bDoChange) - { - ChangeFloatValueAt(Val, 0, bDoChange, FloatParams); - ChangeFloatValueAt(Val, 1, bDoChange, FloatParams); - ChangeFloatValueAt(Val, 2, bDoChange, FloatParams); - }; - - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SNew(SVectorInputBox) - .bColorAxisLabels(true) - .AllowSpin(true) - .X(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, 0))) - .Y(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 2 : 1))) - .Z(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 1 : 2))) - .OnXCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, true); - else - ChangeFloatValueAt( Val, 0, true, FloatParams); - }) - .OnYCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, true); - else - ChangeFloatValueAt( Val, SwapVector3 ? 2 : 1, true, FloatParams); - }) - .OnZCommitted_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, true); - else - ChangeFloatValueAt( Val, SwapVector3 ? 1 : 2, true, FloatParams); - }) - .OnXChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, false); - else - ChangeFloatValueAt(Val, 0, false, FloatParams); - }) - .OnYChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, false); - else - ChangeFloatValueAt(Val, SwapVector3 ? 2 : 1, false, FloatParams); - }) - .OnZChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) - { - if (MainParam->IsUniformLocked()) - ChangeFloatValueUniformly(Val, false); - else - ChangeFloatValueAt(Val, SwapVector3 ? 1 : 2, false, FloatParams); - }) - .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) - .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center) - [ - SNew(SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .ToolTipText(LOCTEXT("FloatParameterLockButtonToolTip", "When locked, change the vector value uniformly.")) - .Visibility(EVisibility::Visible) - [ - SNew(SImage) - .Image(MainParam->IsUniformLocked() ? FEditorStyle::GetBrush("Genericlock") : FEditorStyle::GetBrush("GenericUnlock")) - ] - .OnClicked_Lambda([FloatParams, MainParam]() - { - if (!MainParam || MainParam->IsPendingKill()) - return FReply::Handled(); - - for (auto & CurParam : FloatParams) - { - if (!CurParam || CurParam->IsPendingKill()) - continue; - - CurParam->SwitchUniformLock(); - } - - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - - return FReply::Handled(); - }) - ] - - + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([FloatParams]() - { - for (auto & SelectedParam : FloatParams) - { - if (!SelectedParam) - continue; - - if (!SelectedParam->IsDefault()) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([FloatParams, RevertToDefault]() { return RevertToDefault(-1, FloatParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - else - { - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - TSharedPtr> NumericEntryBox; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< float >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - - .MinValue(MainParam->GetMin()) - .MaxValue(MainParam->GetMax()) - - .MinSliderValue(MainParam->GetUIMin()) - .MaxSliderValue(MainParam->GetUIMax()) - - .Value(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, Idx))) - .OnValueChanged_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) - .OnValueCommitted_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) - .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) - .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) - .SliderExponent(MainParam->IsLogarithmic() ?8.0f : 1.0f) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .OnClicked_Lambda([Idx, FloatParams, RevertToDefault]() { return RevertToDefault(Idx, FloatParams); }) - .Visibility_Lambda([Idx, FloatParams]() - { - for (auto & SelectedParam :FloatParams) - { - if (!SelectedParam) - continue; - - if (!SelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - } - } - - Row->ValueWidget.Widget =VerticalBox; - - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetInt(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray IntParams; - if (!CastParameters(InParams, IntParams)) - - if (IntParams.Num() <= 0) - return; - - UHoudiniParameterInt* MainParam = IntParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - // Helper function to find a unit from a string (name or abbreviation) - auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); - EUnit Unit = EUnit::Unspecified; - if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) - Unit = ParmUnit.GetValue(); - - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - // Lambda for slider begin - auto SliderBegin = [&](TArray IntParams) - { - // Record a transaction for undo/redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), - IntParams[0]->GetOuter()); - - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - IntParams[Idx]->Modify(); - } - }; - - // Lambda for slider end - auto SliderEnd = [&](TArray IntParams) - { - // Mark the value as changed to trigger an update - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - IntParams[Idx]->MarkChanged(true); - } - }; - - // Lambda for changing the parameter value - auto ChangeIntValueAt = [&](const int32& Value, const int32& Index, const bool& DoChange, TArray IntParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), - IntParams[0]->GetOuter()); - - bool bChanged = false; - for (int Idx = 0; Idx < IntParams.Num(); Idx++) - { - if (!IntParams[Idx]) - continue; - - IntParams[Idx]->Modify(); - if (IntParams[Idx]->SetValueAt(Value, Index)) - { - // Only mark the param has changed if DoChange is true!!! - if (DoChange) - IntParams[Idx]->MarkChanged(true); - bChanged = true; - } - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if there is no param has actually been changed - Transaction.Cancel(); - } - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray IntParams) - { - for (int32 Idx = 0; Idx < IntParams.Num(); Idx++) - { - if (!IntParams[Idx]) - continue; - - if (IntParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - IntParams[Idx]->RevertToDefault(TupleIndex); - } - - return FReply::Handled(); - }; - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - - .MinValue(MainParam->GetMin()) - .MaxValue(MainParam->GetMax()) - - .MinSliderValue(MainParam->GetUIMin()) - .MaxSliderValue(MainParam->GetUIMax()) - - .Value( TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterInt::GetValue, Idx))) - .OnValueChanged_Lambda( [=](int32 Val) { ChangeIntValueAt(Val, Idx, false, IntParams); } ) - .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeIntValueAt(Val, Idx, true, IntParams); }) - .OnBeginSliderMovement_Lambda( [=]() { SliderBegin(IntParams); }) - .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(IntParams); }) - .SliderExponent(MainParam->IsLogarithmic() ? 8.0f : 1.0f) - .TypeInterface(paramTypeInterface) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, IntParams]() - { - for (auto & NextSelectedParam : IntParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, IntParams, RevertToDefault]() { return RevertToDefault(Idx, IntParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ]; - /* - if (NumericEntryBox.IsValid()) - NumericEntryBox->SetEnabled(!MainParam->IsDisabled()); - */ - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray StringParams; - if (!CastParameters(InParams, StringParams)) - return; - - if (StringParams.Num() <= 0) - return; - - UHoudiniParameterString* MainParam = StringParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - bool bIsMultiLine = false; - bool bIsUnrealRef = false; - UClass* UnrealRefClass = UObject::StaticClass(); - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); - - TMap& Tags = MainParam->GetTags(); - if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_TAG) && FCString::Atoi(*Tags[HOUDINI_PARAMETER_STRING_REF_TAG]) == 1) - { - bIsUnrealRef = true; - - if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_CLASS_TAG)) - { - UClass * FoundClass = FindObject(ANY_PACKAGE, *Tags[HOUDINI_PARAMETER_STRING_REF_CLASS_TAG]); - if (FoundClass != nullptr) - { - UnrealRefClass = FoundClass; - } - } - } - - if (Tags.Contains(HOUDINI_PARAMETER_STRING_MULTILINE_TAG)) - { - bIsMultiLine = true; - } - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - // Lambda for changing the parameter value - auto ChangeStringValueAt = [&](const FString& Value, UObject* ChosenObj, const int32& Index, const bool& DoChange, TArray StringParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterSrtingChange", "Houdini Parameter String: Changing a value"), - StringParams[0]->GetOuter()); - - bool bChanged = false; - for (int Idx = 0; Idx < StringParams.Num(); Idx++) - { - if (!StringParams[Idx]) - continue; - - StringParams[Idx]->Modify(); - if (StringParams[Idx]->SetValueAt(Value, Index)) - { - StringParams[Idx]->MarkChanged(true); - bChanged = true; - } - - StringParams[Idx]->SetAssetAt(ChosenObj, Index); - } - - if (!bChanged || !DoChange) - { - // Cancel the transaction if there is no param actually has been changed - Transaction.Cancel(); - } - - FHoudiniEngineUtils::UpdateEditorProperties(StringParams[0], false); - }; - - auto RevertToDefault = [&](const int32& TupleIndex, TArray StringParams) - { - for (int32 Idx = 0; Idx < StringParams.Num(); Idx++) - { - if (!StringParams[Idx]) - continue; - - if (StringParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) - continue; - - StringParams[Idx]->RevertToDefault(TupleIndex); - } - - return FReply::Handled(); - }; - - if (bIsUnrealRef) - { - TSharedPtr EditableTextBox; - TSharedPtr HorizontalBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([UnrealRefClass](const UObject* InObject) - { - return InObject->IsA(UnrealRefClass); - }) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - ChangeStringValueAt(ReferenceStr, InObject, Idx, true, StringParams); - }) - [ - SAssignNew(HorizontalBox, SHorizontalBox) - ] - ]; - - // Thumbnail - // Get thumbnail pool for this builder. - TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouParameterCategory.GetParentLayout().GetThumbnailPool(); - - // Create a thumbnail for the selected object / class - UObject* EditObject = nullptr; - const FString AssetPath = MainParam->GetValueAt(Idx); - EditObject = StaticFindObject(nullptr, nullptr, *AssetPath, true); - - FAssetData AssetData; - if (IsValid(EditObject)) - { - AssetData = FAssetData(EditObject); - } - else - { - AssetData.AssetClass = UnrealRefClass->GetFName(); - } - - TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = MakeShareable( - new FAssetThumbnail(AssetData, 64, 64, AssetThumbnailPool)); - - TSharedPtr ThumbnailBorder; - HorizontalBox->AddSlot().Padding(0.f, 0.f, 2.f, 0.f).AutoWidth() - [ - SAssignNew(ThumbnailBorder, SBorder) - .OnMouseDoubleClick_Lambda([EditObject, Idx](const FGeometry&, const FPointerEvent&) - { - if (EditObject && GEditor) - GEditor->EditObject(EditObject); - - return FReply::Handled(); - }) - .Padding(5.f) - [ - SNew(SBox) - .WidthOverride(64) - .HeightOverride(64) - [ - StaticMeshThumbnail->MakeThumbnailWidget() - ] - ] - ]; - - TWeakPtr WeakThumbnailBorder(ThumbnailBorder); - ThumbnailBorder->SetBorderImage(TAttribute::Create( - TAttribute::FGetter::CreateLambda( - [WeakThumbnailBorder]() - { - TSharedPtr ThumbnailBorderPtr = WeakThumbnailBorder.Pin(); - if (ThumbnailBorderPtr.IsValid() && ThumbnailBorderPtr->IsHovered()) - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); - else - return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); - } - ) - )); - - FText MeshNameText = FText::GetEmpty(); - //if (InputObject) - // MeshNameText = FText::FromString(InputObject->GetName()); - - TSharedPtr StaticMeshComboButton; - - TSharedPtr ButtonBox; - HorizontalBox->AddSlot() - .Padding(0.0f, 4.0f, 4.0f, 4.0f) - .VAlign(VAlign_Center) - [ - SNew(SVerticalBox) - + SVerticalBox::Slot() - .HAlign(HAlign_Fill) - [ - SAssignNew(ButtonBox, SHorizontalBox) - + SHorizontalBox::Slot() - [ - SAssignNew(StaticMeshComboButton, SComboButton) - .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") - .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) - .ContentPadding(2.0f) - .ButtonContent() - [ - SNew(STextBlock) - .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") - .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) - .Text(FText::FromName(AssetData.AssetName)) - .ToolTipText(FText::FromString(MainParam->GetValueAt(Idx))) - ] - ] - ] - ]; - - TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); - StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( - [UnrealRefClass, WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() - { - TArray AllowedClasses; - if (UnrealRefClass != UObject::StaticClass()) - { - // Use the class specified by the user - AllowedClasses.Add(UnrealRefClass); - } - else - { - // Using UObject would list way too many assets, and take a long time to open the menu, - // so we need to reestrict the classes a bit - AllowedClasses.Add(UStaticMesh::StaticClass()); - AllowedClasses.Add(UHoudiniAsset::StaticClass()); - AllowedClasses.Add(USkeletalMesh::StaticClass()); - AllowedClasses.Add(UBlueprint::StaticClass()); - AllowedClasses.Add(UMaterialInterface::StaticClass()); - AllowedClasses.Add(UTexture::StaticClass()); - AllowedClasses.Add(ULevel::StaticClass()); - AllowedClasses.Add(UStreamableRenderAsset::StaticClass()); - AllowedClasses.Add(USoundBase::StaticClass()); - AllowedClasses.Add(UParticleSystem::StaticClass()); - AllowedClasses.Add(UFoliageType::StaticClass()); - } - - TArray NewAssetFactories; - return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( - FAssetData(nullptr), - true, - AllowedClasses, - NewAssetFactories, - FOnShouldFilterAsset(), - FOnAssetSelected::CreateLambda( - [WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) - { - TSharedPtr StaticMeshComboButtonPtr = WeakStaticMeshComboButton.Pin(); - if (StaticMeshComboButtonPtr.IsValid()) - { - StaticMeshComboButtonPtr->SetIsOpen(false); - - UObject * Object = AssetData.GetAsset(); - // Get the asset reference string for this object - // !! Accept null objects to allow clearing the asset picker !! - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); - - ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); - } - } - ), - FSimpleDelegate::CreateLambda([]() {})); - }) - ); - } - else if (bIsMultiLine) - { - TSharedPtr< SMultiLineEditableTextBox > MultiLineEditableTextBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) - {return true;}) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - FString NewString = ReferenceStr; - if (StringParams[0]->GetValueAt(Idx).Len() > 0) - NewString = StringParams[0]->GetValueAt(Idx) + "\n" + NewString; - - ChangeStringValueAt(NewString, nullptr, Idx, true, StringParams); - }) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Top).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(MultiLineEditableTextBox, SMultiLineEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainParam->GetValueAt(Idx))) - .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, StringParams]() - { - for (auto & NextSelectedParam : StringParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() { return RevertToDefault(Idx, StringParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - else - { - TSharedPtr< SEditableTextBox > EditableTextBox; - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SAssetDropTarget) - .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) - {return true;}) - .OnAssetDropped_Lambda([=](UObject* InObject) - { - // Get the asset reference string for this object - FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); - - ChangeStringValueAt(ReferenceStr, nullptr, Idx, true, StringParams); - }) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SAssignNew(EditableTextBox, SEditableTextBox) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Text(FText::FromString(MainParam->GetValueAt(Idx))) - .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) - { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(2.0f, 0.0f) - .VAlign(VAlign_Center) - [ - SNew(SButton) - .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ContentPadding(0) - .Visibility_Lambda([Idx, StringParams]() - { - for (auto & NextSelectedParam : StringParams) - { - if (!NextSelectedParam) - continue; - - if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) - return EVisibility::Visible; - } - - return EVisibility::Hidden; - }) - .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() - { return RevertToDefault(Idx, StringParams); }) - [ - SNew(SImage) - .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - ] - ] - ] - ]; - } - - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ColorParams; - if (!CastParameters(InParams, ColorParams)) - return; - - if (ColorParams.Num() <= 0) - return; - - UHoudiniParameterColor* MainParam = ColorParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - bool bHasAlpha = (MainParam->GetTupleSize() == 4); - - // Add color picker UI. - TSharedPtr ColorBlock; - TSharedRef VerticalBox = SNew(SVerticalBox); - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(ColorBlock, SColorBlock) - .Color(MainParam->GetColorValue()) - .ShowBackgroundForAlpha(bHasAlpha) - .OnMouseButtonDown_Lambda([this, ColorParams, MainParam, bHasAlpha](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) - { - FColorPickerArgs PickerArgs; - PickerArgs.ParentWidget = FSlateApplication::Get().GetActiveTopLevelWindow(); - PickerArgs.bUseAlpha = bHasAlpha; - PickerArgs.DisplayGamma = TAttribute< float >::Create( - TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), - MainParam->GetOuter(), true); - - bool bChanged = false; - for (auto & Param : ColorParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetColorValue(InColor)) - { - Param->MarkChanged(true); - bChanged = true; - } - } - - // cancel the transaction if there is actually no value changed - if (!bChanged) - { - Transaction.Cancel(); - } - }); - PickerArgs.InitialColorOverride = MainParam->GetColorValue(); - PickerArgs.bOnlyRefreshOnOk = true; - OpenColorPicker(PickerArgs); - return FReply::Handled(); - }) - ]; - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetButton(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ButtonParams; - if (!CastParameters(InParams, ButtonParams)) - return; - - if (ButtonParams.Num() <= 0) - return; - - UHoudiniParameterButton* MainParam = ButtonParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - FText ParameterTooltip = GetParameterTooltip(MainParam); - - TSharedRef HorizontalBox = SNew(SHorizontalBox); - TSharedPtr Button; - - // Add button UI. - HorizontalBox->AddSlot().Padding(1, 2, 4, 2) - [ - SAssignNew(Button, SButton) - .VAlign(VAlign_Center) - .HAlign(HAlign_Center) - .Text(ParameterLabelText) - .ToolTipText(ParameterTooltip) - .OnClicked(FOnClicked::CreateLambda( [MainParam, ButtonParams]() - { - for (auto & Param : ButtonParams) - { - if (!Param) - continue; - - // There is no undo redo operation for button - Param->MarkChanged(true); - } - - return FReply::Handled(); - })) - ]; - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ButtonStripParams; - if (!CastParameters(InParams, ButtonStripParams)) - return; - - if (ButtonStripParams.Num() <= 0) - return; - - UHoudiniParameterButtonStrip* MainParam = ButtonStripParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - if (!Row) - return; - - auto OnButtonStateChanged = [MainParam, ButtonStripParams](ECheckBoxState NewState, int32 Idx) - { - - /* - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterButtonStripChange", "Houdini Parameter Button Strip: Changing value"), - MainParam->GetOuter(), true); - */ - int32 StateInt = NewState == ECheckBoxState::Checked ? 1 : 0; - bool bChanged = false; - - for (auto & NextParam : ButtonStripParams) - { - if (!NextParam || NextParam->IsPendingKill()) - continue; - - if (!NextParam->Values.IsValidIndex(Idx)) - continue; - - //NextParam->Modify(); - if (NextParam->SetValueAt(Idx, StateInt)) - { - NextParam->MarkChanged(true); - bChanged = true; - } - } - - //if (!bChanged) - // Transaction.Cancel(); - - }; - - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - FText ParameterTooltip = GetParameterTooltip(MainParam); - - TSharedRef HorizontalBox = SNew(SHorizontalBox); - FLinearColor BgColor(0.53f, 0.81f, 0.82f, 1.0f); // Sky Blue Backgroud color - - for (int32 Idx = 0; Idx < MainParam->Count; ++Idx) - { - if (!MainParam->Values.IsValidIndex(Idx) || !MainParam->Labels.IsValidIndex(Idx)) - continue; - - bool bPressed = MainParam->Values[Idx] > 0; - FText LabelText = FText::FromString(MainParam->Labels[Idx]); - - TSharedPtr Button; - - HorizontalBox->AddSlot().Padding(0).FillWidth(1.0f) - [ - SAssignNew(Button, SCheckBox) - .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") - .IsChecked(bPressed ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) - .OnCheckStateChanged_Lambda([OnButtonStateChanged, Idx](ECheckBoxState NewState) - { - OnButtonStateChanged(NewState, Idx); - }) - .Content() - [ - SNew(STextBlock) - .Text(LabelText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - Button->SetColorAndOpacity(BgColor); - } - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetLabel(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray LabelParams; - if (!CastParameters(InParams, LabelParams)) - return; - - if (LabelParams.Num() <= 0) - return; - - UHoudiniParameterLabel* MainParam = LabelParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - - for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) - { - FString NextLabelString = MainParam->GetStringAtIndex(Index); - FText ParameterLabelText = FText::FromString(NextLabelString); - - TSharedPtr TextBlock; - - // Add Label UI. - VerticalBox->AddSlot().Padding(1, 2, 4, 2) - [ - SAssignNew(TextBlock, STextBlock).Text(ParameterLabelText) - ]; - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetToggle(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray ToggleParams; - if (!CastParameters(InParams, ToggleParams)) - return; - - if (ToggleParams.Num() <= 0) - return; - - UHoudiniParameterToggle* MainParam = ToggleParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); - - TSharedRef VerticalBox = SNew(SVerticalBox); - auto IsToggleCheckedLambda = [MainParam](int32 Index) - { - if (Index >= MainParam->GetNumValues()) - return ECheckBoxState::Unchecked; - - if (MainParam->GetValueAt(Index)) - return ECheckBoxState::Checked; - - return ECheckBoxState::Unchecked; - }; - - auto OnToggleCheckStateChanged = [MainParam, ToggleParams](ECheckBoxState NewState, int32 Index) - { - if (Index >= MainParam->GetNumValues()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterToggleChange", "Houdini Parameter Toggle: Changing value"), - MainParam->GetOuter(), true); - - bool bState = (NewState == ECheckBoxState::Checked); - - bool bChanged = false; - for (auto & Param : ToggleParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetValueAt(bState, Index)) - { - bChanged = true; - Param->MarkChanged(true); - } - } - - // Cancel the transaction if no parameter has actually been changed - if (!bChanged) - { - Transaction.Cancel(); - } - }; - - for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) - { - TSharedPtr< SCheckBox > CheckBox; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - [ - SAssignNew(CheckBox, SCheckBox) - .OnCheckStateChanged_Lambda([OnToggleCheckStateChanged, Index](ECheckBoxState NewState) { - OnToggleCheckStateChanged(NewState, Index); - - }) - .IsChecked_Lambda([IsToggleCheckedLambda, Index]() { - return IsToggleCheckedLambda(Index); - }) - .Content() - [ - SNew(STextBlock) - .Text(ParameterLabelText) - .ToolTipText(GetParameterTooltip(MainParam)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FileParams; - if (!CastParameters(InParams, FileParams)) - return; - - if (FileParams.Num() <= 0) - return; - - UHoudiniParameterFile* MainParam = FileParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - - FString FileTypeWidgetFilter = TEXT("All files (*.*)|*.*"); - if (!MainParam->GetFileFilters().IsEmpty()) - FileTypeWidgetFilter = FString::Printf(TEXT("%s files (%s)|%s"), *MainParam->GetFileFilters(), *MainParam->GetFileFilters(), *MainParam->GetFileFilters()); - - FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); - - auto UpdateCheckRelativePath = [MainParam](const FString & PickedPath) - { - UHoudiniAssetComponent* HoudiniAssetComponent = Cast(MainParam->GetOuter()); - if (MainParam->GetOuter() && !PickedPath.IsEmpty() && FPaths::IsRelative(PickedPath)) - { - // Check if the path is relative to the UE4 project - FString AbsolutePath = FPaths::ConvertRelativePathToFull(PickedPath); - if (FPaths::FileExists(AbsolutePath)) - { - return AbsolutePath; - } - - // Check if the path is relative to the asset - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) - { - if (HoudiniAssetComponent->HoudiniAsset && !HoudiniAssetComponent->HoudiniAsset->IsPendingKill()) - { - FString AssetFilePath = FPaths::GetPath(HoudiniAssetComponent->HoudiniAsset->AssetFileName); - if (FPaths::FileExists(AssetFilePath)) - { - FString UpdatedFileWidgetPath = FPaths::Combine(*AssetFilePath, *PickedPath); - if (FPaths::FileExists(UpdatedFileWidgetPath)) - { - return UpdatedFileWidgetPath; - } - } - } - } - } - - return PickedPath; - }; - - for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) - { - FString FileWidgetPath = MainParam->GetValueAt(Idx); - FString FileWidgetBrowsePath = BrowseWidgetDirectory; - - if (!FileWidgetPath.IsEmpty()) - { - FString FileWidgetDirPath = FPaths::GetPath(FileWidgetPath); - if (!FileWidgetDirPath.IsEmpty()) - FileWidgetBrowsePath = FileWidgetDirPath; - } - - bool IsDirectoryPicker = MainParam->GetParameterType() == EHoudiniParameterType::FileDir; - bool bIsNewFile = !MainParam->IsReadOnly(); - - FText BrowseTooltip = LOCTEXT("FileButtonToolTipText", "Choose a file from this computer"); - if (IsDirectoryPicker) - BrowseTooltip = LOCTEXT("DirButtonToolTipText", "Choose a directory from this computer"); - - VerticalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) - [ - SNew(SNewFilePathPicker) - .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) - .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") - .BrowseButtonToolTip(BrowseTooltip) - .BrowseDirectory(FileWidgetBrowsePath) - .BrowseTitle(LOCTEXT("PropertyEditorTitle", "File picker...")) - .FilePath(FileWidgetPath) - .FileTypeFilter(FileTypeWidgetFilter) - .IsNewFile(bIsNewFile) - .IsDirectoryPicker(IsDirectoryPicker) - .ToolTipText_Lambda([MainParam]() - { - // return the current param value as a tooltip - FString FileValue = MainParam ? MainParam->GetValueAt(0) : FString(); - return FText::FromString(FileValue); - }) - .OnPathPicked(FOnPathPicked::CreateLambda([MainParam, FileParams, UpdateCheckRelativePath, Idx](const FString & PickedPath) - { - if (MainParam->GetNumValues() <= Idx) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterFileChange", "Houdini Parameter File: Changing a file path"), - MainParam->GetOuter(), true); - - bool bChanged = false; - - for (auto & Param : FileParams) - { - if (!Param) - continue; - - Param->Modify(); - if (Param->SetValueAt(UpdateCheckRelativePath(PickedPath), Idx)) - { - bChanged = true; - Param->MarkChanged(true); - } - } - - // Cancel the transaction if no value has actually been changed - if (!bChanged) - { - Transaction.Cancel(); - } - })) - ] - ]; - - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - - -void -FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) -{ - TArray ChoiceParams; - if (!CastParameters(InParams, ChoiceParams)) - return; - - if (ChoiceParams.Num() <= 0) - return; - - UHoudiniParameterChoice* MainParam = ChoiceParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - // Lambda for changing the parameter value - auto ChangeSelectionLambda = [ChoiceParams](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - if (!NewChoice.IsValid()) - return; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterChoiceChange", "Houdini Parameter Choice: Changing selection"), - ChoiceParams[0]->GetOuter()); - - const int32 NewIntValue = ChoiceParams[0]->GetIntValueFromLabel(*NewChoice); - - bool bChanged = false; - for (int Idx = 0; Idx < ChoiceParams.Num(); Idx++) - { - if (!ChoiceParams[Idx]) - continue; - - ChoiceParams[Idx]->Modify(); - if (ChoiceParams[Idx]->SetIntValue(NewIntValue)) - { - bChanged = true; - ChoiceParams[Idx]->MarkChanged(true); - ChoiceParams[Idx]->UpdateStringValueFromInt(); - } - } - - if (!bChanged) - { - // Cancel the transaction if no parameter was changed - Transaction.Cancel(); - } - }; - - // - MainParam->UpdateChoiceLabelsPtr(); - TArray>* OptionSource = MainParam->GetChoiceLabelsPtr(); - TSharedPtr IntialSelec; - if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValueIndex())) - { - IntialSelec = (*OptionSource)[MainParam->GetIntValueIndex()]; - } - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBox; - HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) - [ - SAssignNew( ComboBox, SComboBox< TSharedPtr< FString > > ) - .OptionsSource(OptionSource) - .InitiallySelectedItem(IntialSelec) - .OnGenerateWidget_Lambda( - []( TSharedPtr< FString > InItem ) - { - return SNew(STextBlock).Text(FText::FromString(*InItem)); - }) - .OnSelectionChanged_Lambda( - [ChangeSelectionLambda](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) - { - ChangeSelectionLambda(NewChoice, SelectType); - }) - [ - SNew(STextBlock) - .Text_Lambda([MainParam]() { return FText::FromString(MainParam->GetLabel()); }) - .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) - ] - ]; - - if ( ComboBox.IsValid() ) - ComboBox->SetEnabled( !MainParam->IsDisabled() ); - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - -} - -void -FHoudiniParameterDetails::CreateWidgetSeparator(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled) -{ - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam) - return; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - return; - - TSharedRef HorizontalBox = SNew(SCustomizedBox); - - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - - Row->WholeRowWidget.Widget = HorizontalBox; -} - -void -FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray OperatorPathParams; - if (!CastParameters(InParams, OperatorPathParams)) - return; - - if (OperatorPathParams.Num() <= 0) - return; - - UHoudiniParameterOperatorPath* MainParam = OperatorPathParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - UHoudiniInput* MainInput = MainParam->HoudiniInput.Get(); - if (!MainInput) - return; - - // Build an array of edited inputs for multi edition - TArray EditedInputs; - EditedInputs.Add(MainInput); - - // Add the corresponding inputs found in the other HAC - for (int LinkedIdx = 1; LinkedIdx < OperatorPathParams.Num(); LinkedIdx++) - { - UHoudiniInput* LinkedInput = OperatorPathParams[LinkedIdx]->HoudiniInput.Get(); - if (!LinkedInput || LinkedInput->IsPendingKill()) - continue; - - // Linked params should match the main param! If not try to find one that matches - if (!LinkedInput->Matches(*MainInput)) - continue; - - EditedInputs.Add(LinkedInput); - } - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); - if (!Row) - return; - - // Create the standard parameter name widget. - CreateNameWidget(Row, InParams, true); - - FHoudiniInputDetails::CreateWidget(HouParameterCategory, EditedInputs, Row); - - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetFloatRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // TODO: remove this once we have verified that updating the Points and CachedPoints arrays in - // TODO: HoudiniParameterTranslator::BuildAllParameters() (via RampParam->UpdatePointsArray()) is sufficient. - // // Parsing a float ramp: 1->(2->3->4)*->5 // - // switch (MainParam->GetParameterType()) - // { - // //*****State 1: Float Ramp*****// - // case EHoudiniParameterType::FloatRamp: - // { - // UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); - // if (FloatRampParameter) - // { - // CurrentRampFloat = FloatRampParameter; - // CurrentRampFloatPointsArray.Empty(); - // - // CurrentRampParameterList = InParams; - // - // FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - // CurrentRampRow = Row; - // } - // break; - // } - // - // case EHoudiniParameterType::Float: - // { - // UHoudiniParameterFloat* FloatParameter = Cast(MainParam); - // if (FloatParameter) - // { - // bool bCreateNewPoint = true; - // if (CurrentRampFloatPointsArray.Num() > 0) - // { - // UHoudiniParameterRampFloatPoint* LastPtInArr = CurrentRampFloatPointsArray.Last(); - // if (LastPtInArr && !LastPtInArr->ValueParentParm) - // bCreateNewPoint = false; - // } - // - // //*****State 2: Float Parameter (position)*****// - // if (bCreateNewPoint) - // { - // UHoudiniParameterRampFloatPoint* NewRampFloatPoint = nullptr; - // - // int32 PointIndex = CurrentRampFloatPointsArray.Num(); - // if (CurrentRampFloat->Points.IsValidIndex(PointIndex)) - // { - // - // // TODO: We should reuse existing point objects, if they exist. Currently - // // this causes results in unexpected behaviour in other parts of this detail code. - // // Give this code a bit of an overhaul at some point. - // // NewRampFloatPoint = CurrentRampFloat->Points[PointIndex]; - // } - // - // if (!NewRampFloatPoint) - // { - // // Create a new float ramp point, and add its pointer to the current float points buffer array. - // NewRampFloatPoint = NewObject< UHoudiniParameterRampFloatPoint >(CurrentRampFloat, FName(), CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); - // - // } - // - // CurrentRampFloatPointsArray.Add(NewRampFloatPoint); - // - // if (FloatParameter->GetNumberOfValues() <= 0) - // return; - // // Set the float ramp point's position parent parm, and value - // NewRampFloatPoint->PositionParentParm = FloatParameter; - // NewRampFloatPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); - // } - // //*****State 3: Float Parameter (value)*****// - // else - // { - // if (FloatParameter->GetNumberOfValues() <= 0) - // return; - // // Get the last point in the buffer array - // if (CurrentRampFloatPointsArray.Num() > 0) - // { - // // Set the last inserted float ramp point's float parent parm, and value - // UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); - // LastAddedFloatRampPoint->ValueParentParm = FloatParameter; - // LastAddedFloatRampPoint->SetValue(FloatParameter->GetValuesPtr()[0]); - // } - // } - // } - // - // break; - // } - // //*****State 4: Choice parameter*****// - // case EHoudiniParameterType::IntChoice: - // { - // UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); - // if (ChoiceParameter && CurrentRampFloatPointsArray.Num() > 0) - // { - // // Set the last inserted float ramp point's interpolation parent parm, and value - // UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); - // - // LastAddedFloatRampPoint->InterpolationParentParm = ChoiceParameter; - // LastAddedFloatRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); - // - // // Set the index of this point in the multi parm. - // LastAddedFloatRampPoint->InstanceIndex = CurrentRampFloatPointsArray.Num() - 1; - // } - // - // - // //*****State 5: All ramp points have been parsed, finish!*****// - // if (CurrentRampFloatPointsArray.Num() >= (int32)CurrentRampFloat->MultiParmInstanceCount) - // { - // CurrentRampFloatPointsArray.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { - // return P1.Position < P2.Position; - // }); - // - // CurrentRampFloat->Points = CurrentRampFloatPointsArray; - // - // // Not caching, points are synced, update cached points - // if (!CurrentRampFloat->bCaching) - // { - // const int32 NumPoints = CurrentRampFloat->Points.Num(); - // CurrentRampFloat->CachedPoints.SetNum(NumPoints); - // for (int32 i = 0; i < NumPoints; ++i) - // { - // UHoudiniParameterRampFloatPoint* FromPoint = CurrentRampFloat->Points[i]; - // UHoudiniParameterRampFloatPoint* ToPoint = CurrentRampFloat->CachedPoints[i]; - // ToPoint = nullptr; - // check(FromPoint) - // if (!ToPoint) - // { - // ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampFloat, RF_NoFlags, CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); - // } - // else - // { - // ToPoint->CopyStateFrom(FromPoint, true); - // } - // CurrentRampFloat->CachedPoints[i] = ToPoint; - // } - // } - // - // CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampFloat, CurrentRampParameterList); - // - // CurrentRampFloat->SetDefaultValues(); - // - // CurrentRampFloat = nullptr; - // CurrentRampRow = nullptr; - // } - // - // break; - // } - // - // default: - // break; - // } - - //*****Float Ramp*****// - if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); - if (FloatRampParameter) - { - CurrentRampFloat = FloatRampParameter; - FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - CreateWidgetRampPoints(HouParameterCategory, Row, FloatRampParameter, InParams); - //FloatRampParameter->SetDefaultValues(); - } - } -} - -void -FHoudiniParameterDetails::CreateWidgetColorRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // TODO: remove this once we have verified that updating the Points and CachedPoints arrays in - // TODO: HoudiniParameterTranslator::BuildAllParameters() (via RampParam->UpdatePointsArray()) is sufficient. - // // Parsing a color ramp: 1->(2->3->4)*->5 // - // switch (MainParam->GetParameterType()) - // { - // //*****State 1: Color Ramp*****// - // case EHoudiniParameterType::ColorRamp: - // { - // UHoudiniParameterRampColor* RampColor = Cast(MainParam); - // if (RampColor) - // { - // CurrentRampColor = RampColor; - // CurrentRampColorPointsArray.Empty(); - // - // CurrentRampParameterList = InParams; - // - // FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - // CurrentRampRow = Row; - // } - // - // break; - // } - // //*****State 2: Float parameter*****// - // case EHoudiniParameterType::Float: - // { - // UHoudiniParameterFloat* FloatParameter = Cast(MainParam); - // if (FloatParameter) - // { - // // Create a new color ramp point, and add its pointer to the current color points buffer array. - // UHoudiniParameterRampColorPoint* NewRampColorPoint = nullptr; - // int32 PointIndex = CurrentRampColorPointsArray.Num(); - // - // if (CurrentRampColor->Points.IsValidIndex(PointIndex)) - // { - // NewRampColorPoint = CurrentRampColor->Points[PointIndex]; - // } - // - // if (!NewRampColorPoint) - // { - // NewRampColorPoint = NewObject< UHoudiniParameterRampColorPoint >(CurrentRampColor, FName(), CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); - // } - // - // CurrentRampColorPointsArray.Add(NewRampColorPoint); - // - // if (FloatParameter->GetNumberOfValues() <= 0) - // return; - // // Set the color ramp point's position parent parm, and value - // NewRampColorPoint->PositionParentParm = FloatParameter; - // NewRampColorPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); - // } - // - // break; - // } - // //*****State 3: Color parameter*****// - // case EHoudiniParameterType::Color: - // { - // UHoudiniParameterColor* ColorParameter = Cast(MainParam); - // if (ColorParameter && CurrentRampColorPointsArray.Num() > 0) - // { - // // Set the last inserted color ramp point's color parent parm, and value - // UHoudiniParameterRampColorPoint* LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); - // LastAddedColorRampPoint->ValueParentParm = ColorParameter; - // LastAddedColorRampPoint->SetValue(ColorParameter->GetColorValue()); - // } - // - // break; - // } - // //*****State 4: Choice Parameter*****// - // case EHoudiniParameterType::IntChoice: - // { - // UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); - // if (ChoiceParameter) - // { - // // Set the last inserted color ramp point's interpolation parent parm, and value - // UHoudiniParameterRampColorPoint*& LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); - // - // LastAddedColorRampPoint->InterpolationParentParm = ChoiceParameter; - // LastAddedColorRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); - // - // // Set the index of this point in the multi parm. - // LastAddedColorRampPoint->InstanceIndex = CurrentRampColorPointsArray.Num() - 1; - // } - // - // - // //*****State 5: All ramp points have been parsed, finish!*****// - // if (CurrentRampColorPointsArray.Num() >= (int32)CurrentRampColor->MultiParmInstanceCount) - // { - // CurrentRampColorPointsArray.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) - // { - // return P1.Position < P2.Position; - // }); - // - // CurrentRampColor->Points = CurrentRampColorPointsArray; - // - // // Not caching, points are synced, update cached points - // - // if (!CurrentRampColor->bCaching) - // { - // const int32 NumPoints = CurrentRampColor->Points.Num(); - // CurrentRampColor->CachedPoints.SetNum(NumPoints); - // - // for (int32 i = 0; i < NumPoints; ++i) - // { - // UHoudiniParameterRampColorPoint* FromPoint = CurrentRampColor->Points[i]; - // UHoudiniParameterRampColorPoint* ToPoint = CurrentRampColor->CachedPoints[i]; - // - // if (!ToPoint) - // { - // ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampColor, RF_NoFlags, CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); - // } - // else - // { - // ToPoint->CopyStateFrom(FromPoint, true); - // } - // CurrentRampColor->CachedPoints[i] = ToPoint; - // } - // } - // - // - // CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampColor, CurrentRampParameterList); - // - // CurrentRampColor->SetDefaultValues(); - // - // CurrentRampColor = nullptr; - // CurrentRampRow = nullptr; - // } - // - // break; - // } - // - // default: - // break; - // } - - //*****Color Ramp*****// - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor* RampColor = Cast(MainParam); - if (RampColor) - { - CurrentRampColor = RampColor; - FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); - CreateWidgetRampPoints(HouParameterCategory, Row, RampColor, InParams); - //RampColor->SetDefaultValues(); - } - } - -} - - -FDetailWidgetRow* -FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - if (InParams.Num() <= 0) - return nullptr; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam) - return nullptr; - - // Create a new detail row - FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, (TArray)InParams); - if (!Row) - return nullptr; - - Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); - - // Create the standard parameter name widget with an added autoupdate checkbox. - CreateNameWidgetWithAutoUpdate(Row, InParams, true); - - TSharedRef VerticalBox = SNew(SVerticalBox); - if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - UHoudiniParameterRampColor *RampColorParam = Cast(MainParam); - if (!RampColorParam) - return nullptr; - - TSharedPtr ColorGradientEditor; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - SAssignNew(ColorGradientEditor, SHoudiniColorRampCurveEditor) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - ] - ]; - - if (!ColorGradientEditor.IsValid()) - return nullptr; - - // Avoid showing tooltips inside of the curve editor - ColorGradientEditor->EnableToolTipForceField(true); - - CurrentRampParameterColorCurve = NewObject( - GetTransientPackage(), UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); - - if (!CurrentRampParameterColorCurve) - return nullptr; - - CreatedColorRampCurves.Add(CurrentRampParameterColorCurve); - - // Add the ramp curve to root to avoid garabage collected. - CurrentRampParameterColorCurve->AddToRoot(); - - TArray ColorRampParameters; - CastParameters(InParams, ColorRampParameters); - - for (auto NextColorRamp : ColorRampParameters) - { - CurrentRampParameterColorCurve->ColorRampParameters.Add(NextColorRamp); - } - ColorGradientEditor->HoudiniColorRampCurve = CurrentRampParameterColorCurve; - - // Clear default curve points - for (int Idx = 0; Idx < 4; ++Idx) - { - FRichCurve& RichCurve = (CurrentRampParameterColorCurve->FloatCurves)[Idx]; - if (RichCurve.GetNumKeys() > 0) - RichCurve.Keys.Empty(); - } - ColorGradientEditor->SetCurveOwner(CurrentRampParameterColorCurve); - CreatedColorGradientEditors.Add(ColorGradientEditor); - } - else if(MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - UHoudiniParameterRampFloat *RampFloatParam = Cast(MainParam); - if (!RampFloatParam) - return nullptr; - - TSharedPtr FloatCurveEditor; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SNew(SBorder) - .VAlign(VAlign_Fill) - [ - SAssignNew(FloatCurveEditor, SHoudiniFloatRampCurveEditor) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - .HideUI(true) - .DrawCurve(true) - .ViewMinInput(0.0f) - .ViewMaxInput(1.0f) - .ViewMinOutput(0.0f) - .ViewMaxOutput(1.0f) - .TimelineLength(1.0f) - .AllowZoomOutput(false) - .ShowInputGridNumbers(false) - .ShowOutputGridNumbers(false) - .ShowZoomButtons(false) - .ZoomToFitHorizontal(false) - .ZoomToFitVertical(false) - .XAxisName(FString("X")) - .YAxisName(FString("Y")) - .ShowCurveSelector(false) - - ] - ]; - - if (!FloatCurveEditor.IsValid()) - return nullptr; - - // Avoid showing tooltips inside of the curve editor - FloatCurveEditor->EnableToolTipForceField(true); - - CurrentRampParameterFloatCurve = NewObject( - GetTransientPackage(), UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); - - if (!CurrentRampParameterFloatCurve) - return nullptr; - - CreatedFloatRampCurves.Add(CurrentRampParameterFloatCurve); - - // Add the ramp curve to root to avoid garbage collected - CurrentRampParameterFloatCurve->AddToRoot(); - - TArray FloatRampParameters; - CastParameters(InParams, FloatRampParameters); - for (auto NextFloatRamp : FloatRampParameters) - { - CurrentRampParameterFloatCurve->FloatRampParameters.Add(NextFloatRamp); - } - FloatCurveEditor->HoudiniFloatRampCurve = CurrentRampParameterFloatCurve; - - FloatCurveEditor->SetCurveOwner(CurrentRampParameterFloatCurve, true); - CreatedFloatCurveEditors.Add(FloatCurveEditor); - } - - Row->ValueWidget.Widget = VerticalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - return Row; -} - - -void -FHoudiniParameterDetails::CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray& InParams) -{ - if (!Row || !InParameter) - return; - - if (InParams.Num() < 1) - return; - - UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - UHoudiniParameterRampFloat * MainFloatRampParameter = nullptr; - UHoudiniParameterRampColor * MainColorRampParameter = nullptr; - - TArray FloatRampParameterList; - TArray ColorRampParameterList; - if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) - { - MainFloatRampParameter = Cast(MainParam); - - if (!MainFloatRampParameter) - return; - - if (!CastParameters(InParams, FloatRampParameterList)) - return; - } - else if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) - { - MainColorRampParameter = Cast(MainParam); - - if (!MainColorRampParameter) - return; - - if (!CastParameters(InParams, ColorRampParameterList)) - return; - } - else - { - return; - } - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Lambda for computing the float point to be inserted - auto GetInsertFloatPointLambda = [MainFloatRampParameter]( - const int32& InsertAtIndex, - float& OutPosition, - float& OutValue, - EHoudiniRampInterpolationType& OutInterpType) mutable - { - if (!MainFloatRampParameter) - return; - - float PrevPosition = 0.0f; - float NextPosition = 1.0f; - - TArray &CurrentPoints = MainFloatRampParameter->Points; - TArray &CachedPoints = MainFloatRampParameter->CachedPoints; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - int32 NumPoints = 0; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - { - NumPoints = CurrentPoints.Num(); - } - else - { - MainFloatRampParameter->SetCaching(true); - NumPoints = CachedPoints.Num(); - } - - if (InsertAtIndex >= NumPoints) - { - // Insert at the end - if (NumPoints > 0) - { - UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - PrevPoint = CurrentPoints.Last(); - else - PrevPoint = CachedPoints.Last(); - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - } - } - else if (InsertAtIndex <= 0) - { - // Insert at the beginning - if (NumPoints > 0) - { - UHoudiniParameterRampFloatPoint* NextPoint = nullptr; - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - NextPoint = CurrentPoints[0]; - else - NextPoint = CachedPoints[0]; - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - OutInterpType = NextPoint->GetInterpolation(); - } - } - } - else - { - // Insert in the middle - if (NumPoints > 1) - { - UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; - UHoudiniParameterRampFloatPoint* NextPoint = nullptr; - - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - { - PrevPoint = CurrentPoints[InsertAtIndex - 1]; - NextPoint = CurrentPoints[InsertAtIndex]; - } - else - { - PrevPoint = CachedPoints[InsertAtIndex - 1]; - NextPoint = CachedPoints[InsertAtIndex]; - } - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - } - - if (PrevPoint && NextPoint) - { - OutValue = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; - } - } - } - - OutPosition = (PrevPosition + NextPosition) / 2.0f; - }; - - - // Lambda for computing the color point to be inserted - auto GetInsertColorPointLambda = [MainColorRampParameter]( - const int32& InsertAtIndex, - float& OutPosition, - FLinearColor& OutColor, - EHoudiniRampInterpolationType& OutInterpType) mutable - { - if (!MainColorRampParameter) - return; - - float PrevPosition = 0.0f; - float NextPosition = 1.0f; - - TArray &CurrentPoints = MainColorRampParameter->Points; - TArray &CachedPoints = MainColorRampParameter->CachedPoints; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - int32 NumPoints = 0; - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NumPoints = CurrentPoints.Num(); - else - NumPoints = CachedPoints.Num(); - - if (InsertAtIndex >= NumPoints) - { - // Insert at the end - if (NumPoints > 0) - { - UHoudiniParameterRampColorPoint* PrevPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - PrevPoint = CurrentPoints.Last(); - else - PrevPoint = CachedPoints.Last(); - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - } - } - else if (InsertAtIndex <= 0) - { - // Insert at the beginning - if (NumPoints > 0) - { - UHoudiniParameterRampColorPoint* NextPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NextPoint = CurrentPoints[0]; - else - NextPoint = CachedPoints[0]; - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - OutInterpType = NextPoint->GetInterpolation(); - } - } - } - else - { - // Insert in the middle - if (NumPoints > 1) - { - UHoudiniParameterRampColorPoint* PrevPoint = nullptr; - UHoudiniParameterRampColorPoint* NextPoint = nullptr; - - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - { - PrevPoint = CurrentPoints[InsertAtIndex - 1]; - NextPoint = CurrentPoints[InsertAtIndex]; - } - else - { - PrevPoint = CachedPoints[InsertAtIndex - 1]; - NextPoint = CachedPoints[InsertAtIndex]; - } - - if (PrevPoint) - { - PrevPosition = PrevPoint->GetPosition(); - OutInterpType = PrevPoint->GetInterpolation(); - } - - if (NextPoint) - { - NextPosition = NextPoint->GetPosition(); - } - - if (PrevPoint && NextPoint) - { - OutColor = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; - } - } - } - - OutPosition = (PrevPosition + NextPosition) / 2.0f; - }; - - int32 RowIndex = 0; - auto InsertRampPoint_Lambda = [GetInsertColorPointLambda, GetInsertFloatPointLambda, &CategoryBuilder, bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, - UHoudiniParameterRampColor* MainRampColor, - TArray &RampFloatList, - TArray &RampColorList, - const int32& Index) mutable - { - if (MainRampFloat) - { - float InsertPosition = 0.0f; - float InsertValue = 1.0f; - EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; - - GetInsertFloatPointLambda(Index, InsertPosition, InsertValue, InsertInterp); - - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); - - for (auto & NextFloatRamp : RampFloatList) - { - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - CreateFloatRampParameterInsertEvent( - NextFloatRamp, InsertPosition, InsertValue, InsertInterp); - - NextFloatRamp->MarkChanged(true); - } - else - { - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject - (NextFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); - NewCachedPoint->Position = InsertPosition; - NewCachedPoint->Value = InsertValue; - NewCachedPoint->Interpolation = InsertInterp; - - NextFloatRamp->CachedPoints.Add(NewCachedPoint); - NextFloatRamp->bCaching = true; - if (!bCookingEnabled) - { - // If cooking is not enabled, be sure to mark this parameter as changed - // so that it triggers an update once cooking is enabled again. - NextFloatRamp->MarkChanged(true); - } - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - { - CategoryBuilder.GetParentLayout().ForceRefreshDetails(); - } - - } - else if (MainRampColor) - { - float InsertPosition = 0.0f; - FLinearColor InsertColor = FLinearColor::Black; - EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; - - GetInsertColorPointLambda(Index, InsertPosition, InsertColor, InsertInterp); - - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); - for (auto& NextColorRamp : RampColorList) - { - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - CreateColorRampParameterInsertEvent( - NextColorRamp, InsertPosition, InsertColor, InsertInterp); - - NextColorRamp->MarkChanged(true); - } - else - { - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject - (NextColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); - NewCachedPoint->Position = InsertPosition; - NewCachedPoint->Value = InsertColor; - NewCachedPoint->Interpolation = InsertInterp; - - NextColorRamp->CachedPoints.Add(NewCachedPoint); - NextColorRamp->bCaching = true; - if (!bCookingEnabled) - NextColorRamp->MarkChanged(true); - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - auto DeleteRampPoint_Lambda = [bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, - UHoudiniParameterRampColor* MainRampColor, - TArray &FloatRampList, - TArray &ColorRampList, - const int32& Index) mutable - { - if (MainRampFloat) - { - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampList); - - for (auto& NextFloatRamp : FloatRampList) - { - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextFloatRamp->Points.Num() == 0) - return; - - UHoudiniParameterRampFloatPoint* PointToDelete = nullptr; - - if (Index == -1) - PointToDelete = NextFloatRamp->Points.Last(); - else if (NextFloatRamp->Points.IsValidIndex(Index)) - PointToDelete = NextFloatRamp->Points[Index]; - - if (!PointToDelete) - return; - - const int32 & InstanceIndexToDelete = PointToDelete->InstanceIndex; - - CreateFloatRampParameterDeleteEvent(NextFloatRamp, InstanceIndexToDelete); - NextFloatRamp->MarkChanged(true); - } - else - { - if (NextFloatRamp->CachedPoints.Num() == 0) - return; - - if (Index == -1) - NextFloatRamp->CachedPoints.Pop(); - else if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) - NextFloatRamp->CachedPoints.RemoveAt(Index); - else - return; - - NextFloatRamp->bCaching = true; - if (!bCookingEnabled) - NextFloatRamp->MarkChanged(true); - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); - } - } - else - { - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampList); - - for (auto& NextColorRamp : ColorRampList) - { - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextColorRamp->Points.Num() == 0) - return; - - UHoudiniParameterRampColorPoint* PointToRemove = nullptr; - - if (Index == -1) - PointToRemove = NextColorRamp->Points.Last(); - else if (NextColorRamp->Points.IsValidIndex(Index)) - PointToRemove = NextColorRamp->Points[Index]; - - if (!PointToRemove) - return; - - const int32 & InstanceIndexToDelete = PointToRemove->InstanceIndex; - - CreateColorRampParameterDeleteEvent(NextColorRamp, InstanceIndexToDelete); - - NextColorRamp->MarkChanged(true); - } - else - { - if (NextColorRamp->CachedPoints.Num() == 0) - return; - - if (Index == -1) - NextColorRamp->CachedPoints.Pop(); - else if (NextColorRamp->CachedPoints.IsValidIndex(Index)) - NextColorRamp->CachedPoints.RemoveAt(Index); - else - return; - - NextColorRamp->bCaching = true; - if (!bCookingEnabled) - NextColorRamp->MarkChanged(true); - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - { - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - - TSharedRef VerticalBox = StaticCastSharedRef(Row->ValueWidget.Widget); - - TSharedPtr GridPanel; - VerticalBox->AddSlot() - .Padding(2, 2, 5, 2) - .AutoHeight() - [ - SAssignNew(GridPanel, SUniformGridPanel) - ]; - - //AllUniformGridPanels.Add(GridPanel.Get()); - - GridPanel->SetSlotPadding(FMargin(2.f, 2.f, 5.f, 3.f)); - GridPanel->AddSlot(0, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Position"))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - FString ValueString = TEXT("Value"); - if (!MainFloatRampParameter) - ValueString = TEXT("Color"); - - GridPanel->AddSlot(1, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(ValueString)) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - GridPanel->AddSlot(2, RowIndex) - [ - SNew(STextBlock) - .Text(FText::FromString(TEXT("Interp."))) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - - GridPanel->AddSlot(3, RowIndex) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton( - FSimpleDelegate::CreateLambda([InsertRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, bCookingEnabled]() mutable - { - int32 InsertAtIndex = -1; - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - InsertAtIndex = MainFloatRampParameter->Points.Num(); - else - InsertAtIndex = MainFloatRampParameter->CachedPoints.Num(); - } - else if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - InsertAtIndex = MainColorRampParameter->Points.Num(); - else - InsertAtIndex = MainColorRampParameter->CachedPoints.Num(); - } - - InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, InsertAtIndex); - }), - LOCTEXT("AddRampPoint", "Add a ramp point to the end"), true) - ] - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeRemoveButton( - FSimpleDelegate::CreateLambda([DeleteRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList]() mutable - { - DeleteRampPoint_Lambda( - MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, -1); - }), - LOCTEXT("DeleteRampPoint", "Delete the last ramp point"), true) - ] - - ]; - - EUnit Unit = EUnit::Unspecified; - TSharedPtr> paramTypeInterface; - paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); - - int32 PointCount = 0; - // Use Synced points on auto update mode - // Use Cached points on manual update mode - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - PointCount = MainFloatRampParameter->Points.Num(); - else - PointCount = MainFloatRampParameter->CachedPoints.Num(); - } - - if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate()) - PointCount = MainColorRampParameter->Points.Num(); - else - PointCount = MainColorRampParameter->CachedPoints.Num(); - } - - // Lambda function for changing a ramp point - auto OnPointChangeCommit = [bCookingEnabled]( - UHoudiniParameterRampFloat* MainRampFloat, UHoudiniParameterRampColor* MainRampColor, - UHoudiniParameterRampFloatPoint* MainRampFloatPoint, UHoudiniParameterRampColorPoint* MainRampColorPoint, - TArray &RampFloatList, TArray &RampColorList, - const int32& Index, const FString& ChangedDataName, - const float& NewPosition, const float& NewFloat, - const FLinearColor& NewColor, - const EHoudiniRampInterpolationType& NewInterpType) mutable - { - if (MainRampFloat && MainRampFloatPoint) - { - if (ChangedDataName == FString("position") && MainRampFloatPoint->GetPosition() == NewPosition) - return; - if (ChangedDataName == FString("value") && MainRampFloatPoint->GetValue() == NewFloat) - return; - if (ChangedDataName == FString("interp") && MainRampFloatPoint->GetInterpolation() == NewInterpType) - return; - - FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); - for (auto NextFloatRamp : RampFloatList) - { - if (!NextFloatRamp) - continue; - - if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextFloatRamp->Points.IsValidIndex(Index)) - { - UHoudiniParameterRampFloatPoint* CurrentFloatRampPoint = NextFloatRamp->Points[Index]; - if (!CurrentFloatRampPoint) - continue; - - if (ChangedDataName == FString("position")) - { - if (!CurrentFloatRampPoint->PositionParentParm) - continue; - - CurrentFloatRampPoint->SetPosition(NewPosition); - CurrentFloatRampPoint->PositionParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("value")) - { - if (!CurrentFloatRampPoint->PositionParentParm) - continue; - - CurrentFloatRampPoint->SetValue(NewFloat); - CurrentFloatRampPoint->ValueParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("interp")) - { - if (!CurrentFloatRampPoint->InterpolationParentParm) - continue; - - CurrentFloatRampPoint->SetInterpolation(NewInterpType); - CurrentFloatRampPoint->InterpolationParentParm->MarkChanged(true); - } - } - else - { - int32 IdxInEventsArray = Index - NextFloatRamp->Points.Num(); - if (NextFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = NextFloatRamp->ModificationEvents[IdxInEventsArray]; - if (!Event) - continue; - - if (ChangedDataName == FString("position")) - { - Event->InsertPosition = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - Event->InsertFloat = NewFloat; - } - else if (ChangedDataName == FString("interp")) - { - Event->InsertInterpolation = NewInterpType; - } - } - } - } - else - { - if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) - { - UHoudiniParameterRampFloatPoint* CachedPoint = NextFloatRamp->CachedPoints[Index]; - - if (!CachedPoint) - continue; - - if (ChangedDataName == FString("position")) - { - CachedPoint->Position = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - CachedPoint->Value = NewFloat; - } - else if (ChangedDataName == FString("interp")) - { - CachedPoint->Interpolation = NewInterpType; - } - - NextFloatRamp->bCaching = true; - } - } - - if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) - FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); - } - } - else if (MainRampColor && MainRampColorPoint) - { - if (ChangedDataName == FString("position") && MainRampColorPoint->GetPosition() == NewPosition) - return; - - if (ChangedDataName == FString("value") && MainRampColorPoint->GetValue() == NewColor) - return; - - if (ChangedDataName == FString("interp") && MainRampColorPoint->GetInterpolation() == NewInterpType) - return; - - FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); - for (auto NextColorRamp : RampColorList) - { - if (!NextColorRamp) - continue; - - if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) - { - if (NextColorRamp->Points.IsValidIndex(Index)) - { - UHoudiniParameterRampColorPoint* CurrentColorRampPoint = NextColorRamp->Points[Index]; - if (!CurrentColorRampPoint) - continue; - - if (ChangedDataName == FString("position")) - { - if (!CurrentColorRampPoint->PositionParentParm) - continue; - - CurrentColorRampPoint->SetPosition(NewPosition); - CurrentColorRampPoint->PositionParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("value")) - { - if (!CurrentColorRampPoint->PositionParentParm) - continue; - - CurrentColorRampPoint->SetValue(NewColor); - CurrentColorRampPoint->ValueParentParm->MarkChanged(true); - } - else if (ChangedDataName == FString("interp")) - { - if (!CurrentColorRampPoint->InterpolationParentParm) - continue; - - CurrentColorRampPoint->SetInterpolation(NewInterpType); - CurrentColorRampPoint->InterpolationParentParm->MarkChanged(true); - } - } - else - { - int32 IdxInEventsArray = Index - NextColorRamp->Points.Num(); - if (NextColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) - { - UHoudiniParameterRampModificationEvent* Event = NextColorRamp->ModificationEvents[IdxInEventsArray]; - if (!Event) - continue; - - if (ChangedDataName == FString("position")) - { - Event->InsertPosition = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - Event->InsertColor = NewColor; - } - else if (ChangedDataName == FString("interp")) - { - Event->InsertInterpolation = NewInterpType; - } - - } - } - } - else - { - if (NextColorRamp->CachedPoints.IsValidIndex(Index)) - { - UHoudiniParameterRampColorPoint* CachedPoint = NextColorRamp->CachedPoints[Index]; - - if (!CachedPoint) - continue; - - if (ChangedDataName == FString("position")) - { - CachedPoint->Position = NewPosition; - } - else if (ChangedDataName == FString("value")) - { - CachedPoint->Value = NewColor; - } - else if (ChangedDataName == FString("interp")) - { - CachedPoint->Interpolation = NewInterpType; - } - - NextColorRamp->bCaching = true; - } - } - - if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) - FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); - } - } - }; - - for (int32 Index = 0; Index < PointCount; ++Index) - { - UHoudiniParameterRampFloatPoint* NextFloatRampPoint = nullptr; - UHoudiniParameterRampColorPoint* NextColorRampPoint = nullptr; - - if (MainFloatRampParameter) - { - if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) - NextFloatRampPoint = MainFloatRampParameter->Points[Index]; - else - NextFloatRampPoint = MainFloatRampParameter->CachedPoints[Index]; - } - if (MainColorRampParameter) - { - if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) - NextColorRampPoint = MainColorRampParameter->Points[Index]; - else - NextColorRampPoint = MainColorRampParameter->CachedPoints[Index]; - } - - if (!NextFloatRampPoint && !NextColorRampPoint) - continue; - - RowIndex += 1; - - float CurPos = 0.f; - if (NextFloatRampPoint) - CurPos = NextFloatRampPoint->Position; - else - CurPos = NextColorRampPoint->Position; - - - GridPanel->AddSlot(0, RowIndex) - [ - SNew(SNumericEntryBox) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Value(CurPos) - .OnValueChanged_Lambda([](float Val) {}) - .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("position"), - Val, float(-1.0), - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .OnBeginSliderMovement_Lambda([]() {}) - .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("position"), - Val, float(-1.0), - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .SliderExponent(1.0f) - .TypeInterface(paramTypeInterface) - ]; - - if (NextFloatRampPoint) - { - GridPanel->AddSlot(1, RowIndex) - [ - SNew(SNumericEntryBox< float >) - .AllowSpin(true) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .Value(NextFloatRampPoint->Value) - .OnValueChanged_Lambda([](float Val){}) - .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), Val, - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .OnBeginSliderMovement_Lambda([]() {}) - .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), Val, - FLinearColor(), - EHoudiniRampInterpolationType::LINEAR); - }) - .SliderExponent(1.0f) - .TypeInterface(paramTypeInterface) - ]; - } - else if (NextColorRampPoint) - { - auto OnColorChangeLambda = [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](FLinearColor InColor) mutable - { - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("value"), - float(-1.0), float(-1.0), - InColor, - EHoudiniRampInterpolationType::LINEAR); - }; - - // Add color picker UI. - //TSharedPtr ColorBlock; - GridPanel->AddSlot(1, RowIndex) - [ - SNew(SColorBlock) - .Color(NextColorRampPoint->Value) - .OnMouseButtonDown( FPointerEventHandler::CreateLambda( - [NextColorRampPoint, OnColorChangeLambda](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) - { - if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) - return FReply::Unhandled(); - - FColorPickerArgs PickerArgs; - PickerArgs.bUseAlpha = true; - PickerArgs.DisplayGamma = TAttribute< float >::Create( - TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); - PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda(OnColorChangeLambda); - FLinearColor InitColor = NextColorRampPoint->Value; - PickerArgs.InitialColorOverride = InitColor; - PickerArgs.bOnlyRefreshOnOk = true; - OpenColorPicker(PickerArgs); - return FReply::Handled(); - })) - ]; - } - - int32 CurChoice = 0; - if (NextFloatRampPoint) - CurChoice = (int)NextFloatRampPoint->Interpolation; - else - CurChoice = (int)NextColorRampPoint->Interpolation; - - TSharedPtr >> ComboBoxCurveMethod; - GridPanel->AddSlot(2, RowIndex) - [ - SAssignNew(ComboBoxCurveMethod, SComboBox>) - .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels()) - .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels())[CurChoice]) - .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) - { - FText ChoiceEntryText = FText::FromString(*ChoiceEntry); - return SNew(STextBlock) - .Text(ChoiceEntryText) - .ToolTipText(ChoiceEntryText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); - }) - .OnSelectionChanged_Lambda( - [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, - ColorRampParameterList, Index](TSharedPtr NewChoice, ESelectInfo::Type SelectType) mutable - { - EHoudiniRampInterpolationType NewInterpType = UHoudiniParameter::GetHoudiniInterpMethodFromString(*NewChoice.Get()); - - OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, - NextFloatRampPoint, NextColorRampPoint, - FloatRampParameterList, ColorRampParameterList, - Index, FString("interp"), - float(-1.0), float(-1.0), - FLinearColor(), - NewInterpType); - }) - [ - SNew(STextBlock) - .Text_Lambda([NextFloatRampPoint, NextColorRampPoint]() - { - EHoudiniRampInterpolationType CurInterpType = EHoudiniRampInterpolationType::InValid; - if (NextFloatRampPoint) - CurInterpType = NextFloatRampPoint->GetInterpolation(); - else - CurInterpType = NextColorRampPoint->GetInterpolation(); - - return FText::FromString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(CurInterpType)); - }) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ] - ]; - - GridPanel->AddSlot(3, RowIndex) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateLambda( - [InsertRampPoint_Lambda, MainFloatRampParameter, - MainColorRampParameter, FloatRampParameterList, - ColorRampParameterList, Index]() mutable - { - InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); - }), - LOCTEXT("AddRampPoint", "Add a ramp point before this point"), true) - ] - + SHorizontalBox::Slot() - .Padding(3.f, 0.f) - .MaxWidth(35.f) - .AutoWidth() - [ - PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateLambda( - [DeleteRampPoint_Lambda, MainFloatRampParameter, - MainColorRampParameter, FloatRampParameterList, - ColorRampParameterList, Index]() mutable - { - DeleteRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); - }), - LOCTEXT("DeleteFloatRampPoint", "Delete this ramp point"), true) - ] - ]; - - if (MainFloatRampParameter && CurrentRampParameterFloatCurve) - { - ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextFloatRampPoint->GetInterpolation()); - FRichCurve & RichCurve = CurrentRampParameterFloatCurve->FloatCurve; - FKeyHandle const KeyHandle = RichCurve.AddKey(NextFloatRampPoint->GetPosition(), NextFloatRampPoint->GetValue()); - RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); - } - - if (MainColorRampParameter && CurrentRampParameterColorCurve) - { - ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextColorRampPoint->GetInterpolation()); - for (int32 CurveIdx = 0; CurveIdx < 4; ++CurveIdx) - { - FRichCurve & RichCurve = CurrentRampParameterColorCurve->FloatCurves[CurveIdx]; - - FKeyHandle const KeyHandle = RichCurve.AddKey(NextColorRampPoint->GetPosition(), NextColorRampPoint->GetValue().Component(CurveIdx)); - RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); - } - } - } - - if (MainFloatRampParameter) - GridPanel->SetEnabled(!MainFloatRampParameter->IsDisabled()); - - if (MainColorRampParameter) - GridPanel->SetEnabled(!MainColorRampParameter->IsDisabled()); -} - -void -FHoudiniParameterDetails::CreateWidgetFolderList(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FolderListParams; - if (!CastParameters(InParams, FolderListParams)) - return; - - if (FolderListParams.Num() <= 0) - return; - - UHoudiniParameterFolderList* MainParam = FolderListParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Add this folder list to the folder map - AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); - - MainParam->GetTabs().Empty(); - - // A folder list will be followed by all its child folders, - // so set the CurrentFolderListSize to the tuple size, we'll process such many folder parameters right after - CurrentFolderListSize = MainParam->GetTupleSize(); - - if (MainParam->IsDirectChildOfMultiParm()) - MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); - - if (CurrentFolderListSize <= 0) // There should not be empty folder list, this will not happen normally - return; - - // The following folders belong to current folder list - CurrentFolderList = MainParam; - - // If the tab is either a tabs or radio button and the parameter is visible - if (MainParam->IsTabMenu() && MainParam->ShouldDisplay()) - { - // Set the current tabs to be not shown by default now. CreateWidgetTab will decide if the tabs is shown. - CurrentFolderList->SetTabsShown(false); - - // Create a row to hold tab buttons if the folder list is a tabs or radio button - - // CreateNestedRow does not actually create a row for tabs, it is responsible to prune the folder stack. - // ( CreateWidgetTab will be responsible to create a row according to the visibility of its outer level folders ) - FDetailWidgetRow* TabRow = CreateNestedRow(HouParameterCategory, InParams, false); - - } - - // When see a folder list, go depth first search at this step. - // Push an empty queue to the stack. - FolderStack.Add(TArray()); -} - - -void -FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray FolderParams; - if (!CastParameters(InParams, FolderParams)) - return; - - if (FolderParams.Num() <= 0) - return; - - UHoudiniParameterFolder* MainParam = FolderParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen - return; - - // If a folder is invisible, its children won't be listed by HAPI. - // So just reduce FolderListSize by 1, reduce the child counter of its parent folder by 1 if necessary, - // and prune the stack in such case. - if (!MainParam->IsVisible()) - { - CurrentFolderListSize -= 1; - - if (CurrentFolderListSize == 0) - { - if (FolderStack.Num() > 1) - { - TArray &ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) - ParentFolderQueue[0]->GetChildCounter() -= 1; - } - - PruneStack(); - } - - return; - } - - // We expect 'TupleSize' children param of this folder after finish processing all the child folders of cur folderlist - MainParam->ResetChildCounter(); - - // Add this folder to the folder map - AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); - - // Set the parent param to current folderList. - // it was parent multiparm's id if this folder is a child of a multiparms. - // This will cause problem if the folder is inside of a multiparm - MainParam->SetParentParmId(CurrentFolderList->GetParmId()); - - - // Case 1: The folder is a direct child of a multiparm. - if (MainParam->IsDirectChildOfMultiParm()) - { - if (FolderStack.Num() <= 0) // This should not happen - return; - - // Get its parent multiparm first - UHoudiniParameterMultiParm* ParentMultiParm = nullptr; - { - UHoudiniParameterFolderList * ParentFolderList = nullptr; - if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) - return; - - ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); - - if (!ParentFolderList) - return; - - if (AllMultiParms.Contains(ParentFolderList->GetParentParmId())) - ParentMultiParm = AllMultiParms[ParentFolderList->GetParentParmId()]; - - if (!ParentMultiParm) // This should not happen - return; - } - - bool bShown = ParentMultiParm->IsShown(); - - // Case 1-1: The folder is NOT tabs - if (!MainParam->IsTab()) - { - bShown = MainParam->IsExpanded() && bShown; - - // If the parent multiparm is shown. - if (ParentMultiParm->IsShown()) - { - FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderHeaderRow, InParams); - } - } - // Case 1-2: The folder IS tabs. - else - { - CreateWidgetTab(HouParameterCategory, MainParam, ParentMultiParm->IsShown()); - } - - // Push the folder to the queue if it is not a tab folder - // This step is handled by CreateWidgetTab() if it is tabs - if ((!MainParam->IsTab() || !ParentMultiParm->IsShown()) && MainParam->GetTupleSize() > 0) - { - TArray & MyQueue = FolderStack.Last(); - MainParam->SetIsContentShown(bShown); - MyQueue.Add(MainParam); - } - } - - // Case 2: The folder is NOT a direct child of a multiparm. - else - { - // Case 2-1: The folder is in another folder. - if (FolderStack.Num() > 1 && CurrentFolderListSize > 0) - { - TArray & MyFolderQueue = FolderStack.Last(); - TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - - if (ParentFolderQueue.Num() <= 0) //This should happen - return; - - // Peek the folder queue of the last layer to get its parent folder parm. - bool ParentFolderVisible = ParentFolderQueue[0]->IsContentShown(); - - // If this folder is expanded (selected if is tabs) - bool bExpanded = ParentFolderVisible; - - // Case 2-1-1: The folder is NOT in a tab menu. - if (!MainParam->IsTab()) - { - bExpanded &= MainParam->IsExpanded(); - - // The parent folder is visible. - if (ParentFolderVisible) - { - // Add the folder header UI. - FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderHeaderRow, InParams); - } - - MainParam->SetIsContentShown(bExpanded); - MyFolderQueue.Add(MainParam); - } - // Case 2-1-2: The folder IS in a tab menu. - else - { - bExpanded &= MainParam->IsChosen(); - - CreateWidgetTab(HouParameterCategory, MainParam, ParentFolderVisible); - } - } - // Case 2-2: The folder is in the root. - else - { - bool bExpanded = true; - - // Case 2-2-1: The folder is NOT under a tab menu. - if (!MainParam->IsTab()) - { - if (FolderStack.Num() <= 0) // This will not happen - return; - - // Create Folder header under root. - FDetailWidgetRow* FolderRow = CreateNestedRow(HouParameterCategory, InParams, false); - CreateFolderHeaderUI(FolderRow, InParams); - - if (FolderStack.Num() == 0) // This should not happen - return; - - TArray& MyFolderQueue = FolderStack[0]; - bExpanded &= MainParam->IsExpanded(); - MainParam->SetIsContentShown(bExpanded); - MyFolderQueue.Add(MainParam); - } - // Case 2-2-2: The folder IS under a tab menu. - else - { - // Tabs in root is always visible - CreateWidgetTab(HouParameterCategory, MainParam, true); - } - } - } - - - CurrentFolderListSize -= 1; - - // Prune the stack if finished parsing current folderlist - if (CurrentFolderListSize == 0) - { - if (FolderStack.Num() > 1 && !MainParam->IsDirectChildOfMultiParm()) - { - TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) - ParentFolderQueue[0]->GetChildCounter() -= 1; - } - - PruneStack(); - - CurrentFolderList = nullptr; - } -} - -void -FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray &InParams) -{ - if (!HeaderRow) // The folder is invisible. - return; - - TArray FolderParams; - if (!CastParameters(InParams, FolderParams)) - return; - - if (FolderParams.Num() <= 0) - return; - - UHoudiniParameterFolder* MainParam = FolderParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return; - - TSharedPtr VerticalBox; - - FString LabelStr = MainParam->GetParameterLabel(); - - TSharedPtr HorizontalBox; - TSharedPtr ExpanderArrow; - TSharedPtr ExpanderImage; - - HeaderRow->NameWidget.Widget = SAssignNew(HorizontalBox, SCustomizedBox); - - HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); - HorizontalBox->DividerLinePositions = DividerLinePositions; - HorizontalBox->SetHoudiniParameter(InParams); - - if (MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 1) - { - int32 CurrentMultiParmInstanceIndex = 0; - if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - { - MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; - CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - LabelStr = LabelStr + TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex) + TEXT(")"); - } - - CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); - } - - HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SAssignNew(ExpanderArrow, SButton) - .ButtonStyle(FEditorStyle::Get(), "NoBorder") - .ClickMethod(EButtonClickMethod::MouseDown) - .Visibility(EVisibility::Visible) - .OnClicked_Lambda([=]() - { - MainParam->ExpandButtonClicked(); - - FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); - - return FReply::Handled(); - }) - [ - SAssignNew(ExpanderImage, SImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ]; - - - FText LabelText = FText::FromString(LabelStr); - - HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() - [ - SNew(STextBlock) - .Text(LabelText) - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - ]; - - TWeakPtr WeakExpanderArrow(ExpanderArrow); - ExpanderImage->SetImage( - TAttribute::Create( - TAttribute::FGetter::CreateLambda([MainParam, WeakExpanderArrow]() - { - FName ResourceName; - TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); - if (MainParam->IsExpanded()) - { - ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; - } - else - { - ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; - } - - return FEditorStyle::GetBrush(ResourceName); - } - ) - )); - - if(MainParam->GetFolderType() == EHoudiniFolderParameterType::Simple) - ExpanderArrow->SetEnabled(false); - -} - -void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InFolder, const bool& bIsShown) -{ - if (!InFolder || InFolder->IsPendingKill() || !CurrentFolderList) - return; - - if (FolderStack.Num() <= 0) // error state - return; - - TArray & FolderQueue = FolderStack.Last(); - - // Cache all tabs of current tab folder list. - CurrentFolderList->AddTabFolder(InFolder); - - // If the tabs is not shown, just push the folder param into the queue. - if (!bIsShown) - { - InFolder->SetIsContentShown(bIsShown); - FolderQueue.Add(InFolder); - return; - } - - // tabs currently being processed - CurrentTabs.Add(InFolder); - - if (CurrentFolderListSize > 1) - return; - - // The tabs belong to current folder list - UHoudiniParameterFolderList* CurrentTabMenuFolderList = CurrentFolderList; - - // Create a row (UI) for current tabs - TSharedPtr HorizontalBox; - FDetailWidgetRow &Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()) - [ - SAssignNew(HorizontalBox, SCustomizedBox) - ]; - - // Put current tab folder list param into an array - TArray CurrentTabMenuFolderListArr; - CurrentTabMenuFolderListArr.Add(CurrentTabMenuFolderList); - - HorizontalBox->SetHoudiniParameter(CurrentTabMenuFolderListArr); - DividerLinePositions.Add(HorizontalBox->AddIndentation(InFolder, AllMultiParms, AllFoldersAndFolderLists)); - HorizontalBox->DividerLinePositions = DividerLinePositions; - - float DesiredHeight = 0.0f; - float DesiredWidth = 0.0f; - - // Process all tabs of current folder list at once when done. - - for (auto & CurTab : CurrentTabs) - { - if (!CurTab || CurTab->IsPendingKill()) - continue; - - CurTab->SetIsContentShown(CurTab->IsChosen()); - FolderQueue.Add(CurTab); - - auto OnTabClickedLambda = [CurrentTabMenuFolderList, CurTab, &HouParameterCategory]() - { - if (CurrentTabMenuFolderList) - { - if (!CurrentTabMenuFolderList->bIsTabMenu || CurrentTabMenuFolderList->TabFolders.Num() <= 0) - return FReply::Handled(); - - if (CurTab->IsChosen()) - return FReply::Handled(); - - CurTab->SetChosen(true); - - for (UHoudiniParameterFolder* NextFolder : CurrentTabMenuFolderList->TabFolders) - { - if (CurTab->GetParmId() != NextFolder->GetParmId() && NextFolder->IsChosen()) - NextFolder->SetChosen(false); - } - - HouParameterCategory.GetParentLayout().ForceRefreshDetails(); - } - - return FReply::Handled(); - }; - - FString FolderLabelString = TEXT(" ") + CurTab->GetParameterLabel(); - if (CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio) - FolderLabelString = TEXT(" ") + FolderLabelString; - - bool bChosen = CurTab->IsTab() && CurTab->IsChosen(); - - TSharedPtr CurCustomizedButton; - - HorizontalBox->AddSlot().VAlign(VAlign_Bottom) - .AutoWidth() - .Padding(0.f) - .HAlign(HAlign_Left) - [ - SAssignNew(CurCustomizedButton, SCustomizedButton) - .OnClicked_Lambda(OnTabClickedLambda) - .Content() - [ - SNew(STextBlock) - .Text(FText::FromString(FolderLabelString)) - ] - ]; - - CurCustomizedButton->bChosen = bChosen; - CurCustomizedButton->bIsRadioButton = CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio; - - DesiredHeight = CurCustomizedButton->GetDesiredSize().Y; - DesiredWidth += CurCustomizedButton->GetDesiredSize().X; - } - - HorizontalBox->bIsTabFolderListRow = true; - - Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); - - // Set the current tabs to be shown, since slate widgets have been created - CurrentTabMenuFolderList->SetTabsShown(true); - - // Clear the temporary tabs - CurrentTabs.Empty(); -} - -void -FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) -{ - TArray MultiParmParams; - if (!CastParameters(InParams, MultiParmParams)) - return; - - if (MultiParmParams.Num() <= 0) - return; - - UHoudiniParameterMultiParm* MainParam = MultiParmParams[0]; - if (!MainParam || MainParam->IsPendingKill()) - return; - - // Add current multiparm parameter to AllmultiParms map - AllMultiParms.Add(MainParam->GetParmId(), MainParam); - - // Create a new detail row - FDetailWidgetRow * Row = CreateNestedRow(HouParameterCategory, InParams); - - if (!Row) - { - MainParam->SetIsShown(false); - return; - } - - MainParam->SetIsShown(true); - - MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); - - CreateNameWidget(Row, InParams, true); - - auto OnInstanceValueChangedLambda = [MainParam](int32 InValue) - { - if (InValue < 0) - return; - - int32 ChangesCount = FMath::Abs(MainParam->MultiParmInstanceLastModifyArray.Num() - InValue); - - if (MainParam->MultiParmInstanceLastModifyArray.Num() > InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->RemoveElement(-1); - - MainParam->MarkChanged(true); - } - else if (MainParam->MultiParmInstanceLastModifyArray.Num() < InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->InsertElement(); - - MainParam->MarkChanged(true); - } - }; - - // Add multiparm UI. - TSharedRef HorizontalBox = SNew(SHorizontalBox); - TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; - int32 NumericalCount = MainParam->MultiParmInstanceCount; - HorizontalBox->AddSlot().Padding(2, 2, 5, 2) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(true) - - .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) - .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateLambda([OnInstanceValueChangedLambda](int32 InValue) { - OnInstanceValueChangedLambda(InValue); - })) - .Value(NumericalCount) - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamAddInstance", "Houdini Parameter Multi Parameter: Adding an instance"), - MainParam->GetOuter(), true); - - for (auto& Param : MultiParmParams) - { - if (!Param) - continue; - - // Add a reverse step for redo/undo - Param->MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Removed); - - Param->MarkChanged(true); - Param->Modify(); - - if (Param->MultiParmInstanceLastModifyArray.Num() > 0) - Param->MultiParmInstanceLastModifyArray.RemoveAt(Param->MultiParmInstanceLastModifyArray.Num() - 1); - - Param->InsertElement(); - - } - }), - LOCTEXT("AddMultiparmInstanceToolTipAddLastInstance", "Add an Instance"), true) - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - // Remove the last multiparm instance - PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamDeleteInstance", "Houdini Parameter Multi Parameter: Deleting an instance"), - MainParam->GetOuter(), true); - - for (auto & Param : MultiParmParams) - { - TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; - int32 RemovedIndex = LastModifiedArray.Num() - 1; - while (LastModifiedArray.IsValidIndex(RemovedIndex) && LastModifiedArray[RemovedIndex] == EHoudiniMultiParmModificationType::Removed) - RemovedIndex -= 1; - - // Add a reverse step for redo/undo - EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; - if (LastModifiedArray.IsValidIndex(RemovedIndex)) - { - PreviousModType = LastModifiedArray[RemovedIndex]; - LastModifiedArray[RemovedIndex] = EHoudiniMultiParmModificationType::Inserted; - } - - Param->MarkChanged(true); - - Param->Modify(); - - if (LastModifiedArray.IsValidIndex(RemovedIndex)) - { - LastModifiedArray[RemovedIndex] = PreviousModType; - } - - Param->RemoveElement(RemovedIndex); - } - - }), - LOCTEXT("RemoveLastMultiParamLastToolTipRemoveLastInstance", "Remove the last instance"), true) - - ]; - - HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) - [ - PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() - { - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParamDeleteAllInstances", "Houdini Parameter Multi Parameter: Deleting all instances"), - MainParam->GetOuter(), true); - - for (auto & Param : MultiParmParams) - { - TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; - TArray IndicesToReverse; - - for (int32 Index = 0; Index < LastModifiedArray.Num(); ++Index) - { - if (LastModifiedArray[Index] == EHoudiniMultiParmModificationType::None) - { - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; - IndicesToReverse.Add(Index); - } - } - - Param->MarkChanged(true); - - Param->Modify(); - - for (int32 & Index : IndicesToReverse) - { - if (LastModifiedArray.IsValidIndex(Index)) - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::None; - } - - - Param->EmptyElements(); - } - - }), - LOCTEXT("HoudiniParameterRemoveAllMultiparmInstancesToolTip", "Remove all instances"), true) - ]; - - Row->ValueWidget.Widget = HorizontalBox; - Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniParameterDetails::CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams) -{ - - if (InParams.Num() <= 0) - return; - - UHoudiniParameter* MainParam = InParams[0]; - - if (!MainParam || MainParam->IsPendingKill()) - return; - - if (!HorizontalBox || !AllMultiParms.Contains(MainParam->GetParentParmId()) || !MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) - return; - - UHoudiniParameterMultiParm* MainParentMultiParm = AllMultiParms[MainParam->GetParentParmId()]; - - if (!MainParentMultiParm) - return; - - if (!MainParentMultiParm->IsShown()) - return; - - // push all parent multiparm of the InParams to the array - TArray ParentMultiParams; - for (auto & InParam : InParams) - { - if (!InParam) - continue; - - if (!MultiParmInstanceIndices.Contains(InParam->GetParentParmId())) - continue; - - if (InParam->GetChildIndex() == 0) - { - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[InParam->GetParentParmId()]; - - if (ParentMultiParm) - ParentMultiParams.Add(ParentMultiParm); - } - } - - - int32 InstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; - - TSharedRef AddButton = PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() - { - for (auto & ParentParam : ParentMultiParams) - { - // Add button call back - if (!ParentParam) - continue; - - TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; - - if (!LastModifiedArray.IsValidIndex(InstanceIndex)) - continue; - - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParmAddBeforeCurInstance", "Houdini Parameter Multi Parm: Adding an instance"), - ParentParam->GetOuter(), true); - - - int32 Index = InstanceIndex; - - // Add a reverse step for undo/redo - if (Index >= LastModifiedArray.Num()) - LastModifiedArray.Add(EHoudiniMultiParmModificationType::Removed); - else - LastModifiedArray.Insert(EHoudiniMultiParmModificationType::Removed, Index); - - ParentParam->MarkChanged(true); - ParentParam->Modify(); - - if (Index >= LastModifiedArray.Num() - 1 && LastModifiedArray.Num()) - LastModifiedArray.RemoveAt(LastModifiedArray.Num() - 1); - else - LastModifiedArray.RemoveAt(Index); - - ParentParam->InsertElementAt(InstanceIndex); - - } - }), - LOCTEXT("HoudiniParameterMultiParamAddBeforeCurrentInstanceToolTip", "Insert an instance before this instance")); - - - TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() - { - for (auto & ParentParam : ParentMultiParams) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniParameterMultiParmDeleteCurInstance", "Houdini Parameter Multi Parm: Deleting an instance"), - ParentParam->GetOuter(), true); - - - TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; - - int32 Index = InstanceIndex; - EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; - while (LastModifiedArray.IsValidIndex(Index) && LastModifiedArray[Index] == EHoudiniMultiParmModificationType::Removed) - { - Index -= 1; - } - - if (LastModifiedArray.IsValidIndex(Index)) - { - PreviousModType = LastModifiedArray[Index]; - LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; - } - - ParentParam->MarkChanged(true); - - ParentParam->Modify(); - - if (LastModifiedArray.IsValidIndex(Index)) - { - LastModifiedArray[Index] = PreviousModType; - } - - ParentParam->RemoveElement(InstanceIndex); - } - - }), - LOCTEXT("HoudiniParameterMultiParamDeleteCurrentInstanceToolTip", "Remove an instance"), true); - - - HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[AddButton]; - HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[RemoveButton]; - - int32 StartIdx = MainParam->GetParameterType() == EHoudiniParameterType::Folder ? 1 : 0; - if (MainParam->GetChildIndex() != StartIdx) - { - AddButton.Get().SetVisibility(EVisibility::Hidden); - RemoveButton.Get().SetVisibility(EVisibility::Hidden); - } - -} - -void -FHoudiniParameterDetails::PruneStack() -{ - for (int32 StackItr = FolderStack.Num() - 1; StackItr >= 0; --StackItr) - { - TArray &CurrentQueue = FolderStack[StackItr]; - - for (int32 QueueItr = CurrentQueue.Num() - 1; QueueItr >= 0; --QueueItr) - { - UHoudiniParameterFolder * CurrentFolder = CurrentQueue[QueueItr]; - if (!CurrentFolder || CurrentFolder->IsPendingKill()) - continue; - - if (CurrentFolder->GetChildCounter() == 0) - { - CurrentQueue.RemoveAt(QueueItr); - } - } - - if (CurrentQueue.Num() == 0) - { - FolderStack.RemoveAt(StackItr); - } - } -} - -FText -FHoudiniParameterDetails::GetParameterTooltip(UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return FText(); - - // Tooltip starts with Label (name) - FString Tooltip = InParam->GetParameterLabel() + TEXT(" (") + InParam->GetParameterName() + TEXT(")"); - - // Append the parameter type - FString ParmTypeStr = GetParameterTypeString(InParam->GetParameterType(), InParam->GetTupleSize()); - if (!ParmTypeStr.IsEmpty()) - Tooltip += TEXT("\n") + ParmTypeStr; - - // If the parameter has some help, append it - FString Help = InParam->GetParameterHelp(); - if (!Help.IsEmpty()) - Tooltip += TEXT("\n") + Help; - - // If the parameter has an expression, append it - if (InParam->HasExpression()) - { - FString Expr = InParam->GetExpression(); - if (!Expr.IsEmpty()) - Tooltip += TEXT("\nExpression: ") + Expr; - } - - return FText::FromString(Tooltip); -} - -FString -FHoudiniParameterDetails::GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize) -{ - FString ParamStr; - - switch (InType) - { - case EHoudiniParameterType::Button: - ParamStr = TEXT("Button"); - break; - - case EHoudiniParameterType::ButtonStrip: - ParamStr = TEXT("Button Strip"); - break; - - case EHoudiniParameterType::Color: - { - if (InTupleSize == 4) - ParamStr = TEXT("Color with Alpha"); - else - ParamStr = TEXT("Color"); - } - break; - - case EHoudiniParameterType::ColorRamp: - ParamStr = TEXT("Color Ramp"); - break; - - case EHoudiniParameterType::File: - ParamStr = TEXT("File (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileDir: - ParamStr = TEXT("File Dir (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileGeo: - ParamStr = TEXT("File Geo (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::FileImage: - ParamStr = TEXT("File Image (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::Float: - ParamStr = TEXT("Float (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); - break; - - case EHoudiniParameterType::FloatRamp: - ParamStr = TEXT("Float Ramp"); - break; - - case EHoudiniParameterType::Folder: - case EHoudiniParameterType::FolderList: - break; - - case EHoudiniParameterType::Input: - ParamStr = TEXT("Opearator Path"); - break; - - case EHoudiniParameterType::Int: - ParamStr = TEXT("Integer (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); - break; - - case EHoudiniParameterType::IntChoice: - ParamStr = TEXT("Int Choice"); - break; - - case EHoudiniParameterType::Label: - ParamStr = TEXT("Label (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::MultiParm: - ParamStr = TEXT("MultiParm"); - break; - - case EHoudiniParameterType::Separator: - break; - - case EHoudiniParameterType::String: - ParamStr = TEXT("String (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::StringAssetRef: - ParamStr = TEXT("String Asset Ref (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - case EHoudiniParameterType::StringChoice: - ParamStr = TEXT("String Choice"); - break; - - case EHoudiniParameterType::Toggle: - ParamStr = TEXT("Toggle (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); - break; - - default: - ParamStr = TEXT("invalid parameter type"); - break; - } - - - return ParamStr; -} - -void -FHoudiniParameterDetails::SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter) -{ - if (!ColorRampParameter) - return; - - // Do not sync when the Houdini asset component is cooking - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) - return; - - TArray &CachedPoints = ColorRampParameter->CachedPoints; - TArray &CurrentPoints = ColorRampParameter->Points; - - bool bCurveNeedsUpdate = false; - bool bRampParmNeedsUpdate = false; - - int32 Idx = 0; - - while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) - { - UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[Idx]; - UHoudiniParameterRampColorPoint* CurrentPoint = CurrentPoints[Idx]; - - if (!CachedPoint || !CurrentPoint) - continue; - - if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) - { - if (CurrentPoint->PositionParentParm) - { - CurrentPoint->SetPosition(CachedPoint->GetPosition()); - CurrentPoint->PositionParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - if (CachedPoint->GetValue() != CurrentPoint->GetValue()) - { - if (CurrentPoint->ValueParentParm) - { - CurrentPoint->SetValue(CachedPoint->GetValue()); - CurrentPoint->ValueParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) - { - if (CurrentPoint->InterpolationParentParm) - { - CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); - CurrentPoint->InterpolationParentParm->MarkChanged(true); - bCurveNeedsUpdate = true; - } - } - - Idx += 1; - } - - // Insert points - for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) - { - UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[IdxCachedPointLeft]; - - CreateColorRampParameterInsertEvent( - ColorRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); - - bCurveNeedsUpdate = true; - bRampParmNeedsUpdate = true; - - } - - // Delete points - for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) - { - ColorRampParameter->RemoveElement(IdxCurrentPointLeft); - - UHoudiniParameterRampColorPoint* Point = CurrentPoints[IdxCurrentPointLeft]; - - if (!Point) - continue; - - CreateColorRampParameterDeleteEvent(ColorRampParameter, Point->InstanceIndex); - - bCurveNeedsUpdate = true; - bRampParmNeedsUpdate = true; - } - - - ColorRampParameter->MarkChanged(bRampParmNeedsUpdate); -} - -//void -//FHoudiniParameterDetails::SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter) -//{ -// if (!FloatRampParameter) -// return; -// -// // Do not sync when the Houdini asset component is cooking -// if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) -// return; -// -// TArray &CachedPoints = FloatRampParameter->CachedPoints; -// TArray &CurrentPoints = FloatRampParameter->Points; -// -// int32 Idx = 0; -// -// while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) -// { -// UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; -// UHoudiniParameterRampFloatPoint* &CurrentPoint = CurrentPoints[Idx]; -// -// if (!CachedPoint || !CurrentPoint) -// continue; -// -// if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) -// { -// if (CurrentPoint->PositionParentParm) -// { -// CurrentPoint->SetPosition(CachedPoint->GetPosition()); -// CurrentPoint->PositionParentParm->MarkChanged(true); -// } -// } -// -// if (CachedPoint->GetValue() != CurrentPoint->GetValue()) -// { -// if (CurrentPoint->ValueParentParm) -// { -// CurrentPoint->SetValue(CachedPoint->GetValue()); -// CurrentPoint->ValueParentParm->MarkChanged(true); -// } -// } -// -// if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) -// { -// if (CurrentPoint->InterpolationParentParm) -// { -// CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); -// CurrentPoint->InterpolationParentParm->MarkChanged(true); -// } -// } -// -// Idx += 1; -// } -// -// // Insert points -// for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) -// { -// UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; -// if (!CachedPoint) -// continue; -// -// CreateFloatRampParameterInsertEvent( -// FloatRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); -// -// FloatRampParameter->MarkChanged(true); -// } -// -// // Remove points -// for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) -// { -// FloatRampParameter->RemoveElement(IdxCurrentPointLeft); -// -// UHoudiniParameterRampFloatPoint* Point = CurrentPoints[IdxCurrentPointLeft]; -// -// if (!Point) -// continue; -// -// CreateFloatRampParameterDeleteEvent(FloatRampParameter, Point->InstanceIndex); -// -// FloatRampParameter->MarkChanged(true); -// } -//} - -void -FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetFloatRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - InParam->ModificationEvents.Add(DeleteEvent); -} - -void -FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetColorRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - InParam->ModificationEvents.Add(DeleteEvent); -} - -void -FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, - const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetFloatRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertFloat = InValue; - InsertEvent->InsertInterpolation = InInterp; - - InParam->ModificationEvents.Add(InsertEvent); -} - -void -FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, - const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - InParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetColorRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertColor = InColor; - InsertEvent->InsertInterpolation = InInterp; - - InParam->ModificationEvents.Add(InsertEvent); -} - -void -FHoudiniParameterDetails:: ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters) -{ - if (FloatRampParameters.Num() < 1) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0]; - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) - { - UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx]; - - if (!NextFloatRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); - } -} - -void -FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters) -{ - if (FloatRampParameters.Num() < 1) - return; - - if (!FloatRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - - for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) - { - if (!FloatRampParameters[Idx].IsValid()) - continue; - - UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx].Get(); - - if (!NextFloatRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); - } - -} - -void -FHoudiniParameterDetails:: ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam) -{ - if (!Param || !MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) - return; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Use Synced points if the MainParam is on auto update mode - // Use Cached points if the Mainparam is on manual update mode - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - if (Param->IsAutoUpdate() && bCookingEnabled) - { - TArray & Points = Param->Points; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (MainPoint->GetPosition() != Point->GetPosition()) - { - if (Point->PositionParentParm) - { - Point->SetPosition(MainPoint->GetPosition()); - Point->PositionParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetValue() != Point->GetValue()) - { - if (Point->ValueParentParm) - { - Point->SetValue(MainPoint->GetValue()); - Point->ValueParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetInterpolation() != Point->GetInterpolation()) - { - if (Point->InterpolationParentParm) - { - Point->SetInterpolation(MainPoint->GetInterpolation()); - Point->InterpolationParentParm->MarkChanged(true); - } - } - - PointIdx += 1; - } - - int32 PointInsertIdx = PointIdx; - int32 PointDeleteIdx = PointIdx; - - // skip the pending modification events - for (auto & Event : Param->ModificationEvents) - { - if (!Event) - continue; - - if (Event->IsInsertEvent()) - PointInsertIdx += 1; - - if (Event->IsDeleteEvent()) - PointDeleteIdx += 1; - } - - // There are more points in MainPoints array - for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) - { - UHoudiniParameterRampFloatPoint*& NextMainPoint = MainPoints[PointInsertIdx]; - - if (!NextMainPoint) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(Param, - NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); - - Param->MarkChanged(true); - } - - // There are more points in Points array - for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) - { - UHoudiniParameterRampFloatPoint*& NextPoint = Points[PointDeleteIdx]; - - if (!NextPoint) - continue; - - FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); - - Param->MarkChanged(true); - } - - } - else - { - TArray &Points = Param->CachedPoints; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (Point->Position != MainPoint->Position) - { - Point->Position = MainPoint->Position; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->PositionParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Value != MainPoint->Value) - { - Point->Value = MainPoint->Value; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->ValueParentParm) - Point->ValueParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Interpolation != MainPoint->Interpolation) - { - Point->Interpolation = MainPoint->Interpolation; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->InterpolationParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - PointIdx += 1; - } - - // There are more points in MainPoints array - for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) - { - UHoudiniParameterRampFloatPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; - - if (!NextMainPoint) - continue; - - UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampFloatPoint::StaticClass()); - - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = NextMainPoint->GetPosition(); - NewCachedPoint->Value = NextMainPoint->GetValue(); - NewCachedPoint->Interpolation = NextMainPoint->GetInterpolation(); - - Points.Add(NewCachedPoint); - - Param->bCaching = true; - } - - // there are more points in Points array - for (int32 PointsLeftIdx = PointIdx; PointIdx < Points.Num(); ++PointIdx) - { - Points.Pop(); - Param->bCaching = true; - } - } - -} - - -void -FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters) -{ - if (ColorRampParameters.Num() < 1) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0]; - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) - { - UHoudiniParameterRampColor* NextColorRampParam = ColorRampParameters[Idx]; - - if (!NextColorRampParam) - continue; - - FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParam); - } -} - -void -FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters) -{ - if (ColorRampParameters.Num() < 1) - return; - - if (!ColorRampParameters[0].IsValid()) - return; - - UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); - - if (!MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) - return; - - for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) - { - if (!ColorRampParameters[Idx].IsValid()) - continue; - - UHoudiniParameterRampColor* NextColorRampParameter = ColorRampParameters[Idx].Get(); - - if (!NextColorRampParameter) - continue; - - FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParameter); - - } - -} - -void -FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParam) -{ - if (!Param || !MainParam) - return; - - if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) - return; - - const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); - - // Use Synced points if the MainParam is on auto update mode - // Use Cached points if the Mainparam is on manual update mode - - TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; - - if (Param->IsAutoUpdate() && bCookingEnabled) - { - TArray & Points = Param->Points; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (MainPoint->GetPosition() != Point->GetPosition()) - { - if (Point->PositionParentParm) - { - Point->SetPosition(MainPoint->GetPosition()); - Point->PositionParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetValue() != Point->GetValue()) - { - if (Point->ValueParentParm) - { - Point->SetValue(MainPoint->GetValue()); - Point->ValueParentParm->MarkChanged(true); - } - } - - if (MainPoint->GetInterpolation() != Point->GetInterpolation()) - { - if (Point->InterpolationParentParm) - { - Point->SetInterpolation(MainPoint->GetInterpolation()); - Point->InterpolationParentParm->MarkChanged(true); - } - } - - PointIdx += 1; - - } - - int32 PointInsertIdx = PointIdx; - int32 PointDeleteIdx = PointIdx; - - // skip the pending modification events - for (auto & Event : Param->ModificationEvents) - { - if (!Event) - continue; - - if (Event->IsInsertEvent()) - PointInsertIdx += 1; - - if (Event->IsDeleteEvent()) - PointDeleteIdx += 1; - } - - // There are more points in MainPoints array - for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) - { - UHoudiniParameterRampColorPoint*& NextMainPoint = MainPoints[PointInsertIdx]; - - if (!NextMainPoint) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(Param, - NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); - - Param->MarkChanged(true); - } - - // There are more points in Points array - for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) - { - UHoudiniParameterRampColorPoint*& NextPoint = Points[PointDeleteIdx]; - - if (!NextPoint) - continue; - - FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); - - Param->MarkChanged(true); - } - } - else - { - TArray &Points = Param->CachedPoints; - - int32 PointIdx = 0; - while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) - { - UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; - UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; - - if (!MainPoint || !Point) - continue; - - if (Point->Position != MainPoint->Position) - { - Point->Position = MainPoint->Position; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->PositionParentParm) - Point->PositionParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Value != MainPoint->Value) - { - Point->Value = MainPoint->Value; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->ValueParentParm) - Point->ValueParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - if (Point->Interpolation != MainPoint->Interpolation) - { - Point->Interpolation = MainPoint->Interpolation; - Param->bCaching = true; - if (!bCookingEnabled) - { - if (Point->InterpolationParentParm) - Point->InterpolationParentParm->MarkChanged(true); - Param->MarkChanged(true); - } - } - - PointIdx += 1; - } - - // There are more points in Main Points array. - for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) - { - UHoudiniParameterRampColorPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; - - if (!NextMainPoint) - continue; - - UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampColorPoint::StaticClass()); - - if (!NewCachedPoint) - continue; - - NewCachedPoint->Position = NextMainPoint->Position; - NewCachedPoint->Value = NextMainPoint->Value; - NewCachedPoint->Interpolation = NextMainPoint->Interpolation; - - Points.Add(NewCachedPoint); - - Param->bCaching = true; - } - - // There are more points in Points array - for (int32 PointsleftIdx = PointIdx; PointIdx < MainPoints.Num(); ++PointsleftIdx) - { - Points.Pop(); - - Param->bCaching = true; - } - } -} - -// Check recussively if a parameter hits the end of a visible tabs -void -FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam) -{ - if (!InParam || InParam->IsPendingKill()) - return; - - // When the paramId is invalid, the directory won't parse. - // So simply return the function - if (InParam->GetParmId() < 0) - return; - - // Do not end the tab if this param is a non empty parent type, leave it to its children - EHoudiniParameterType ParmType = InParam->GetParameterType(); - if ((ParmType == EHoudiniParameterType::FolderList || - ParmType == EHoudiniParameterType::Folder) && InParam->GetTupleSize() > 0) - return; - - if (ParmType == EHoudiniParameterType::MultiParm) - { - UHoudiniParameterMultiParm * InMultiParm = Cast(InParam); - if (!InMultiParm) - return; - - if (InMultiParm->MultiParmInstanceCount * InMultiParm->MultiParmInstanceLength > 0) - return; - } - - int32 ParentParamId = InParam->GetParentParmId(); - UHoudiniParameter* CurParam = InParam; - - while (AllFoldersAndFolderLists.Contains(ParentParamId) || AllMultiParms.Contains(ParentParamId)) - { - // The parent is a multiparm - if (AllMultiParms.Contains(ParentParamId)) - { - UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentParamId]; - if (!ParentMultiParm || ParentMultiParm->IsPendingKill()) - return; - - if (ParentMultiParm->MultiParmInstanceCount * ParentMultiParm->MultiParmInstanceLength - 1 == CurParam->GetChildIndex()) - { - ParentParamId = ParentMultiParm->GetParentParmId(); - CurParam = ParentMultiParm; - - continue; - } - else - { - // return directly if the parameter is not the last child param of the multiparm - return; - } - } - // The parent is a folder or folderlist - else - { - UHoudiniParameter* ParentFolderParam = AllFoldersAndFolderLists[ParentParamId]; - CurParam = ParentFolderParam; - - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) - return; - - // The parent is a folder - if (ParentFolderParam->GetParameterType() == EHoudiniParameterType::Folder) - { - ParentParamId = ParentFolderParam->GetParentParmId(); - - continue; - } - // The parent is a folderlist - else - { - UHoudiniParameterFolderList* ParentFolderList = Cast(ParentFolderParam); - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) - return; - - if (ParentFolderList->IsTabMenu() && ParentFolderList->IsTabsShown() && ParentFolderList->IsTabParseFinished() && DividerLinePositions.Num() > 0) - { - if (!CurrentTabEndingRow) - CreateTabEndingRow(HouParameterCategory); - - if (CurrentTabEndingRow) - { - CurrentTabEndingRow->EndingDividerLinePositions.Add(DividerLinePositions.Top()); - CurrentTabEndingRow->DividerLinePositions.Pop(); - } - - DividerLinePositions.Pop(); - - ParentParamId = ParentFolderList->GetParentParmId(); - } - else - { - return; - } - - } - - } - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterDetails.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniInput.h" +#include "HoudiniAsset.h" + +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEnginePrivatePCH.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "SNewFilePathPicker.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "IDetailGroup.h" +#include "IDetailCustomization.h" +#include "PropertyCustomizationHelpers.h" +#include "DetailWidgetRow.h" +#include "Math/UnitConversion.h" +#include "ScopedTransaction.h" +#include "EditorDirectories.h" + +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/SVectorInputBox.h" +#include "Widgets/Input/SNumericEntryBox.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "Widgets/Colors/SColorPicker.h" +#include "Widgets/Views/SExpanderArrow.h" +#include "Widgets/Layout/SExpandableArea.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Input/NumericUnitTypeInterface.inl" +#include "Widgets/Images/SImage.h" +#include "Widgets/Layout/SSeparator.h" +#include "Widgets/Layout/SSplitter.h" +#include "SCurveEditorView.h" +#include "SAssetDropTarget.h" +#include "AssetThumbnail.h" + +#include "Sound/SoundBase.h" +#include "Engine/SkeletalMesh.h" +#include "Particles/ParticleSystem.h" +#include "FoliageType.h" + +#include "HoudiniInputDetails.h" + +#include "Framework/SlateDelegates.h" +#include "Templates/SharedPointer.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +int32 +SCustomizedButton::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + TSharedPtr Content = GetContent(); + + // 0. Initialize Line Buffer. + TArray Line; + Line.SetNumUninitialized(2); + + // Initialize Color buffer. + FLinearColor Color = FLinearColor::White; + + // 1. Draw the radio button. + if (bIsRadioButton) + { + // Construct the radio button circles exactly once, + // All radio buttons share the same circles then + if (FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER || + FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner().Num() != HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER) + { + ConstructRadioButtonCircles(); + } + + DrawRadioButton(AllottedGeometry, OutDrawElements, LayerId, bChosen); + } + + // 2. Draw background color (if selected) + if (bChosen) + { + Line[0].X = AllottedGeometry.Size.X - AllottedGeometry.Size.Y / 2.0f + 2.5f; + Line[0].Y = Content->GetDesiredSize().Y / 2.0f; + Line[1].X = AllottedGeometry.Size.Y / 2.0f - 2.5f; + Line[1].Y = Content->GetDesiredSize().Y / 2.0f; + + Color = FLinearColor::White; + Color.A = bIsRadioButton ? 0.05 : 0.1; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, AllottedGeometry.Size.Y); + } + + // 3. Drawing square around the text + { + // Switch the point order for each line to save few value assignment cycles + Line[0].X = 0.0f; + Line[0].Y = 0.0f; + Line[1].X = 0.0f; + Line[1].Y = Content->GetDesiredSize().Y; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); + + //Line[0].X = 0.0f; + //Line[0].Y = Content->GetDesiredSize().Y; + Line[0].X = AllottedGeometry.Size.X; + Line[0].Y = Content->GetDesiredSize().Y; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, bChosen ? FLinearColor::Gray : FLinearColor::Black, true, 1.0f); + + //Line[0].X = AllottedGeometry.Size.X; + //Line[0].Y = Content->GetDesiredSize().Y; + Line[1].X = AllottedGeometry.Size.X; + Line[1].Y = 0.0f; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); /* draw gray bottom line if this tab is selected, black otherwise*/ + + //Line[0].X = AllottedGeometry.Size.X; + //Line[0].Y = 0.0f; + Line[0].X = 0.0f; + Line[0].Y = 0.0f; + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, FLinearColor::Black, true, 1.0f); + } + + // 4. Draw child widget + Content->Paint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + return LayerId; +}; + +void +SCustomizedButton::ConstructRadioButtonCircles() const +{ + TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); + TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); + OuterPoints.Empty(); + InnerPoints.Empty(); + + OuterPoints.SetNumZeroed(HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER); + InnerPoints.SetNumZeroed(8); + + // Construct outer circle + int32 CurDegree = 0; + int32 DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; + + for (int32 Idx = 0; Idx < HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_OUTER; ++Idx) + { + OuterPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); + OuterPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_OUTER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); + + CurDegree += DegStep; + } + + // Construct inner circle + CurDegree = 0; + DegStep = 360 / HOUDINI_RADIO_BUTTON_CIRCLE_SAMPLES_NUM_INNER; + for (int32 Idx = 0; Idx < 8; ++Idx) + { + InnerPoints[Idx].X = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Sin(FMath::DegreesToRadians(CurDegree)); + InnerPoints[Idx].Y = HOUDINI_RADIO_BUTTON_CIRCLE_CENTER_X + + HOUDINI_RADIO_BUTTON_CIRCLE_RADIUS_INNER * FMath::Cos(FMath::DegreesToRadians(CurDegree)); + + CurDegree += DegStep; + } +} + +void +SCustomizedButton::DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const +{ + TArray& OuterPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsOuter(); + TArray& InnerPoints = FHoudiniEngineEditor::Get().GetHoudiniParameterRadioButtonPointsInner(); + if (OuterPoints.Num() <= 1 || InnerPoints.Num() <= 1) + return; + + FLinearColor ColorNonSelected = FLinearColor::White; + FLinearColor ColorSelected = FLinearColor::Yellow; + + // initialize line buffer + TArray Line; + Line.SetNumZeroed(2); + bool alternator = false; + + // Draw outer circle + Line[0] = OuterPoints.Last(); + for (int32 Idx = 0; Idx < OuterPoints.Num(); ++Idx) + { + // alternate the points order each time to some some assignment cycles + if (alternator) + { + Line[0].X = OuterPoints[Idx].X; + Line[0].Y = OuterPoints[Idx].Y; + } + else + { + Line[1].X = OuterPoints[Idx].X; + Line[1].Y = OuterPoints[Idx].Y; + } + + alternator = !alternator; + + // Draw a line segment + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, ColorNonSelected, true, 1.0f); + } + + // Draw inner circle + alternator = false; + Line[0] = InnerPoints.Last(); + for (int32 Idx = 0; Idx < InnerPoints.Num(); ++Idx) + { + // alternate the points order each time to some some assignment cycles + if (alternator) + { + Line[0].X = InnerPoints[Idx].X; + Line[0].Y = InnerPoints[Idx].Y; + } + else + { + Line[1].X = InnerPoints[Idx].X; + Line[1].Y = InnerPoints[Idx].Y; + } + + alternator = !alternator; + + // Draw a line segment + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, bSelected ? ColorSelected : ColorNonSelected, true, 3.0f); + } +} + +void +SCustomizedBox::SetHoudiniParameter(TArray& InParams) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + + bool bIsMultiparmInstanceHeader = MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 0; + + switch (MainParam->GetParameterType()) + { + case EHoudiniParameterType::Button: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTON; + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_BUTTONSTRIP; + } + break; + + case EHoudiniParameterType::Color: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLOR; + } + break; + + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor * ColorRampParameter = Cast(MainParam); + if (!ColorRampParameter || ColorRampParameter->IsPendingKill()) + return; + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP; + if (ColorRampParameter->CachedPoints.Num() > 0) + MarginHeight = MarginHeight + HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP_INSTANCE * (float)(ColorRampParameter->CachedPoints.Num() - 1); + } + break; + + case EHoudiniParameterType::File: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILE; + } + break; + + case EHoudiniParameterType::FileDir: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEDIR; + } + break; + + case EHoudiniParameterType::FileGeo: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEGEO; + } + break; + + case EHoudiniParameterType::FileImage: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FILEIMAGE; + } + break; + + case EHoudiniParameterType::Float: + { + if (MainParam->GetTupleSize() == 3) + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_VEC3; + } + else + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT + + (MainParam->GetTupleSize() - 1)* HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOAT_INSTANCE; + } + } + } + break; + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat * FloatRampParameter = Cast(MainParam); + if (!FloatRampParameter || FloatRampParameter->IsPendingKill()) + return; + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP; + + if (FloatRampParameter->CachedPoints.Num() > 0) + MarginHeight = MarginHeight + Houdini_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP_INSTANCE * (float)(FloatRampParameter->CachedPoints.Num() - 1); + } + break; + + case EHoudiniParameterType::Folder: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDER; + } + break; + + case EHoudiniParameterType::FolderList: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FOLDERLIST; + } + break; + + case EHoudiniParameterType::Input: + { + UHoudiniParameterOperatorPath* InputParam = Cast(MainParam); + + if (!InputParam || InputParam->IsPendingKill() || !InputParam->HoudiniInput.IsValid()) + break; + + UHoudiniInput* Input = InputParam->HoudiniInput.Get(); + + if (!Input || Input->IsPendingKill()) + break; + + + if (bIsMultiparmInstanceHeader) + { + switch (Input->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + int32 ExpandedTransformUIs = 0; + for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) + { + if (Input->IsTransformUIExpanded(Idx)) + ExpandedTransformUIs += 1; + } + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_MULTIPARMHEADER + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_MULTIPARMHEADER + + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Curve: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_MULTIPARMHEADER + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Asset: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Landscape: + { + if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::World: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD_MULTIPARMHEADER; + } + break; + case EHoudiniInputType::Skeletal: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL_MULTIPARMHEADER; + } + break; + default: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_MULTIPARMHEADER; + break; + } + } + else + { + switch (Input->GetInputType()) + { + case EHoudiniInputType::Geometry: + { + int32 ExpandedTransformUIs = 0; + for (int32 Idx = 0; Idx < Input->GetNumberOfInputObjects(); ++Idx) + { + if (Input->IsTransformUIExpanded(Idx)) + ExpandedTransformUIs += 1; + } + + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE + + ExpandedTransformUIs * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_GEOMETRY_INSTANCE_TRANSFORM; + } + break; + case EHoudiniInputType::Curve: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE + + Input->GetNumberOfInputObjects() * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_CURVE_INSTANCE; + } + break; + case EHoudiniInputType::Asset: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_ASSET; + } + break; + case EHoudiniInputType::Landscape: + { + if (Input->LandscapeExportType == EHoudiniLandscapeExportType::Heightfield) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_LANDSCAPE_MESH; + } + break; + case EHoudiniInputType::World: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_WORLD; + } + break; + case EHoudiniInputType::Skeletal: + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT_SKELETAL; + } + break; + default: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INPUT; + break; + + } + } + } + break; + + case EHoudiniParameterType::Int: + { + if (MainParam->GetTupleSize() == 3) + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_VEC3; + } + else + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INT_INSTANCE; + } + } + } + break; + + case EHoudiniParameterType::IntChoice: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INTCHOICE; + } + break; + + case EHoudiniParameterType::Label: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_LABEL; + } + break; + + case EHoudiniParameterType::MultiParm: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_MULTIPARM; + } + break; + + case EHoudiniParameterType::Separator: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_SEPARATOR; + bIsSeparator = true; + } + break; + + case EHoudiniParameterType::String: + { + if (bIsMultiparmInstanceHeader) + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_MULTIPARMHEADER + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE_MULTIPARMHEADER; + } + else + { + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING + + (MainParam->GetTupleSize() - 1) * HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRING_INSTANCE; + } + } + break; + + case EHoudiniParameterType::StringAssetRef: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGASSETREF; + } + break; + + case EHoudiniParameterType::StringChoice: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_STRINGCHOICE; + } + break; + + case EHoudiniParameterType::Toggle: + { + if (bIsMultiparmInstanceHeader) + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE_MULTIPARMHEADER; + else + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_TOGGLE; + } + break; + + case EHoudiniParameterType::Invalid: + MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_INVALID; + break; + + default: + MarginHeight = 0.0f; + break; + } +} + +float +SCustomizedBox::AddIndentation(UHoudiniParameter* InParam, + TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists) +{ + if (!InParam || InParam->IsPendingKill()) + return 0.0f; + + bool bIsMainParmSimpleFolder = false; + // Get if this Parameter is a simple / collapsible folder + if (InParam->GetParameterType() == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParm = Cast(InParam); + if (FolderParm) + bIsMainParmSimpleFolder = !FolderParm->IsTab(); + } + + int32 ParentId = InParam->GetParentParmId(); + UHoudiniParameter* CurParm = InParam; + float Indentation = 0.0f; + + while (ParentId >= 0) + { + UHoudiniParameter* ParentFolder = nullptr; + UHoudiniParameterMultiParm* ParentMultiParm = nullptr; + + if (InAllFoldersAndFolderLists.Contains(ParentId)) + ParentFolder = InAllFoldersAndFolderLists[ParentId]; + + if (InAllMultiParms.Contains(ParentId)) + ParentMultiParm = InAllMultiParms[ParentId]; + + // The parent is a folder, add one unit of indentation + if (ParentFolder) + { + // Update the parent parm id + ParentId = ParentFolder->GetParentParmId(); + + if (ParentFolder->GetParameterType() == EHoudiniParameterType::FolderList) + continue; + + UHoudiniParameterFolder* Folder = Cast(ParentFolder); + + if (!Folder) + continue; + + // update the current parm, find the parent of new cur param in the next round + CurParm = Folder; + Indentation += 1.0f; + } + // The parent is a multiparm + else if (ParentMultiParm) + { + // Update the parent parm id + ParentId = ParentMultiParm->GetParentParmId(); + + if (CurParm->GetChildIndex() == 0) + { + Indentation += 0.0f; + } + else + { + Indentation += 2.0f; + } + + // update the current parm, find the parent of new cur param in the next round + CurParm = ParentMultiParm; + } + else + { + // no folder/multiparm parent, end the loop + ParentId = -1; + } + } + + + float IndentationWidth = INDENTATION_UNIT_WIDTH * Indentation; + + // Add a base indentation to non simple/collapsible param + // Since it needs more space to offset the arrow width + if (!bIsMainParmSimpleFolder) + IndentationWidth += NON_FOLDER_OFFSET_WIDTH; + + this->AddSlot().AutoWidth() + [ + SNew(SBox).WidthOverride(IndentationWidth) + ]; + + + return IndentationWidth; +}; + +int32 +SCustomizedBox::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + + SHorizontalBox::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + // Initialize line buffer + TArray Line; + Line.SetNumZeroed(2); + // Initialize color buffer + FLinearColor Color = FLinearColor::White; + Color.A = 0.3; + + // draw the bottom line if this row is the tab folder list + if (bIsTabFolderListRow) + { + // Get the start position of the tabs bottom line (right bottom pt of the right most child widget) + float VerticalLineStartPosX = 0.0f; + float VerticalLineStartPosY = 0.0f; + float BottomLineStartPosX = 0.0f; + float BottomLineStartPosY = -1.0f; + + for (int32 Idx = 0; Idx < Children.Num(); ++Idx) + { + TSharedPtr CurChild = Children.GetChildAt(Idx); + if (!CurChild.IsValid()) + continue; + + if (Idx == 0) + { + VerticalLineStartPosX = CurChild->GetDesiredSize().X; + VerticalLineStartPosY = CurChild->GetDesiredSize().Y; + } + + BottomLineStartPosX += CurChild->GetDesiredSize().X; + + if (BottomLineStartPosY < 0.0f) + BottomLineStartPosY= CurChild->GetDesiredSize().Y; + } + + // Draw bottom line + Line[0].X = BottomLineStartPosX; + Line[0].Y = BottomLineStartPosY; + Line[1].X = AllottedGeometry.Size.X; + Line[1].Y = BottomLineStartPosY; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + + // Draw divider lines + { + Line[0].Y = -MarginHeight; + Line[1].Y = AllottedGeometry.Size.Y + MarginHeight; + + int32 NumOfLinesToDraw = bIsTabFolderListRow ? DividerLinePositions.Num() - 1 : DividerLinePositions.Num(); + for (int32 Idx = 0; Idx < NumOfLinesToDraw; ++Idx) + { + const float& CurDivider = DividerLinePositions[Idx]; + Line[0].X = CurDivider; + Line[1].X = CurDivider; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + + // Draw the last inner most divider line differently when this the tabs' row. + if (bIsTabFolderListRow && DividerLinePositions.Num() > 0) + { + const float& TabDivider = DividerLinePositions.Last(); + Line[0].X = TabDivider; + Line[1].X = TabDivider; + Line[0].Y = 0.f; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + } + } + + // Draw tab ending lines + { + float YPos = 0.0f; + + for (const float & CurEndingDivider : EndingDividerLinePositions) + { + // Draw cur ending line (vertical) + + Line[0].X = CurEndingDivider; + Line[0].Y = -2.3f; + Line[1].X = CurEndingDivider; + Line[1].Y = YPos; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + + // Draw cur ending line (horizontal) + + // Line[0].X = CurEndingDivider; + Line[0].Y = YPos; + Line[1].X = AllottedGeometry.Size.X; + // Line[1].Y = YPos; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.0f); + + YPos += 2.0f; + } + } + + // Draw the separator line if this is the row of a separator parameter + { + if (bIsSeparator) + { + Line[0].X = 25.f; + if (DividerLinePositions.Num() > 0) + Line[0].X += DividerLinePositions.Last(); + + Line[0].Y = AllottedGeometry.Size.Y / 2.f; + Line[1].X = AllottedGeometry.Size.X - 20.f; + Line[1].Y = Line[0].Y; + + Color.A = 0.7; + + FSlateDrawElement::MakeLines(OutDrawElements, LayerId, AllottedGeometry.ToPaintGeometry(), Line, + ESlateDrawEffect::None, Color, true, 1.5f); + } + } + + return LayerId; +}; + +void +SHoudiniFloatRampCurveEditor::Construct(const FArguments & InArgs) +{ + SCurveEditor::Construct(SCurveEditor::FArguments() + .ViewMinInput(InArgs._ViewMinInput) + .ViewMaxInput(InArgs._ViewMaxInput) + .ViewMinOutput(InArgs._ViewMinOutput) + .ViewMaxOutput(InArgs._ViewMaxOutput) + .XAxisName(InArgs._XAxisName) + .YAxisName(InArgs._YAxisName) + .HideUI(InArgs._HideUI) + .DrawCurve(InArgs._DrawCurve) + .TimelineLength(InArgs._TimelineLength) + .AllowZoomOutput(InArgs._AllowZoomOutput) + .ShowInputGridNumbers(InArgs._ShowInputGridNumbers) + .ShowOutputGridNumbers(InArgs._ShowOutputGridNumbers) + .ShowZoomButtons(InArgs._ShowZoomButtons) + .ZoomToFitHorizontal(InArgs._ZoomToFitHorizontal) + .ZoomToFitVertical(InArgs._ZoomToFitVertical) + ); + + + UCurveEditorSettings * CurveEditorSettings = GetSettings(); + if (CurveEditorSettings) + { + CurveEditorSettings->SetTangentVisibility(ECurveEditorTangentVisibility::NoTangents); + } +} + +void +SHoudiniColorRampCurveEditor::Construct(const FArguments & InArgs) +{ + SColorGradientEditor::Construct(SColorGradientEditor::FArguments() + .ViewMinInput(InArgs._ViewMinInput) + .ViewMaxInput(InArgs._ViewMaxInput) + ); +} + + +FReply +SHoudiniFloatRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = SCurveEditor::OnMouseButtonUp(MyGeometry, MouseEvent); + + if (!HoudiniFloatRampCurve.IsValid()) + return Reply; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + FRichCurve& FloatCurve = HoudiniFloatRampCurve.Get()->FloatCurve; + + TArray>& FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; + + if (FloatRampParameters.Num() < 1) + return Reply; + + if (!FloatRampParameters[0].IsValid()) + return Reply; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + // Do not allow modification when the parent HDA of the main param is being cooked. + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points of the main float ramp param to other parameters + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + // Modification is based on the main parameter, use synced points if the main param is on auto update mode, use cached points otherwise. + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + // On mouse button up handler handles point modification only + if (FloatCurve.GetNumKeys() != NumMainPoints) + return Reply; + + bool bNeedToRefreshEditor= false; + + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float& CurvePosition = FloatCurve.Keys[Idx].Time; + float& CurveValue = FloatCurve.Keys[Idx].Value; + + // This point is modified + if (MainPoint->GetPosition() != CurvePosition || MainPoint->GetValue() != CurveValue) + { + + // The editor needs refresh only if the main parameter is on manual mode, and has been modified + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedToRefreshEditor = true; + + // Iterate through the float ramp parameter of all selected HDAs. + for (auto & NextRampFloat : FloatRampParameters) + { + if (!NextRampFloat.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedRampFloat = NextRampFloat.Get(); + + if (!SelectedRampFloat) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedRampFloat)) + continue; + + if (SelectedRampFloat->IsAutoUpdate() && bCookingEnabled) + { + // The selected float ramp parameter is on auto update mode, use its synced points. + TArray &SelectedRampPoints = SelectedRampFloat->Points; + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // Synced points in the selected ramp is more than or the same number as that in the main parameter, + // modify the position and value of the synced point and mark them as changed. + + UHoudiniParameterRampFloatPoint*& ModifiedPoint = SelectedRampPoints[Idx]; + + if (!ModifiedPoint) + continue; + + if (ModifiedPoint->GetPosition() != CurvePosition && ModifiedPoint->PositionParentParm) + { + ModifiedPoint->SetPosition(CurvePosition); + ModifiedPoint->PositionParentParm->MarkChanged(true); + } + + if (ModifiedPoint->GetValue() != CurveValue && ModifiedPoint->ValueParentParm) + { + ModifiedPoint->SetValue(CurveValue); + ModifiedPoint->ValueParentParm->MarkChanged(true); + } + } + else + { + // Synced points in the selected ramp is less than that in the main parameter + // Since we have pushed the points of the main param to all of the selected ramps, + // We need to modify the insert event. + + int32 IndexInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedRampFloat->ModificationEvents.IsValidIndex(Idx)) + { + UHoudiniParameterRampModificationEvent*& ModEvent = SelectedRampFloat->ModificationEvents[Idx]; + if (!ModEvent) + continue; + + if (ModEvent->InsertPosition != CurvePosition) + ModEvent->InsertPosition = CurvePosition; + + if (ModEvent->InsertFloat != CurveValue) + ModEvent->InsertFloat = CurveValue; + } + + } + } + else + { + // The selected float ramp is on manual update mode, use the cached points. + TArray &FloatRampCachedPoints = SelectedRampFloat->CachedPoints; + + // Since we have pushed the points in main param to all the selected float ramp, + // we need to modify the corresponding cached point in the selected float ramp. + + if (FloatRampCachedPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampFloatPoint*& ModifiedCachedPoint = FloatRampCachedPoints[Idx]; + + if (!ModifiedCachedPoint) + continue; + + if (ModifiedCachedPoint->Position != CurvePosition) + { + ModifiedCachedPoint->Position = CurvePosition; + SelectedRampFloat->bCaching = true; + if (!bCookingEnabled) + { + //SelectedRampFloat->MarkChanged(true); + if (ModifiedCachedPoint->PositionParentParm) + ModifiedCachedPoint->PositionParentParm->MarkChanged(true); + } + } + + if (ModifiedCachedPoint->Value != CurveValue) + { + ModifiedCachedPoint->Value = CurveValue; + SelectedRampFloat->bCaching = true; + if (!bCookingEnabled) + { + //SelectedRampFloat->MarkChanged(true); + if (ModifiedCachedPoint->ValueParentParm) + ModifiedCachedPoint->ValueParentParm->MarkChanged(true); + } + } + } + } + } + } + } + + + if (bNeedToRefreshEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } + + return Reply; +} + +FReply +SHoudiniFloatRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + FReply Reply = SCurveEditor::OnKeyDown(MyGeometry, InKeyEvent); + + if (InKeyEvent.GetKey().ToString() != FString("Enter")) + return Reply; + + if (!HoudiniFloatRampCurve.IsValid() || !HoudiniFloatRampCurve.Get()) + return Reply; + + TArray> FloatRampParameters = HoudiniFloatRampCurve.Get()->FloatRampParameters; + + if (FloatRampParameters.Num() < 1) + return Reply; + + if (!FloatRampParameters[0].IsValid()) + return Reply; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Do nothing if the main param is on auto update mode + if (MainParam->IsAutoUpdate() && bCookingEnabled) + return Reply; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points in the main float ramp to the float ramp parameters in all selected HDAs. + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + for (auto& NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + if (!SelectedFloatRamp) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not sync the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + // Sync the cached points if the selected float ramp parameter is on manual update mode + //FHoudiniParameterDetails::SyncCachedFloatRampPoints(SelectedFloatRamp); + SelectedFloatRamp->SyncCachedPoints(); + } + + return Reply; +} + +void +UHoudiniFloatRampCurve::OnCurveChanged(const TArray& ChangedCurveEditInfos) +{ + Super::OnCurveChanged(ChangedCurveEditInfos); + + if (FloatRampParameters.Num() < 1) + return; + + if (!FloatRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return; + + // Do not allow modification when the parent HDA of the main param is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + // Push all the points of the Main parameter to other parameters + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampParameters); + + // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode, otherwise use its cached points. + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + bool bNeedUpdateEditor = false; + + // OnCurveChanged handler handles point delete and insertion only + + // A point is deleted. + if (FloatCurve.GetNumKeys() < NumMainPoints) + { + // Find the index of the deleted point + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampFloatPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float CurPointPosition = MainPoint->GetPosition(); + float CurCurvePosition = -1.0f; + + if (FloatCurve.Keys.IsValidIndex(Idx)) + CurCurvePosition = FloatCurve.Keys[Idx].Time; + + // Delete the point at Idx + if (CurCurvePosition != CurPointPosition) + { + // Iterate through all the float ramp parameter in all the selected HDAs + for (auto & NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + if (!SelectedFloatRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + TArray & SelectedRampPoints = SelectedFloatRamp->Points; + + // The selected float ramp is on auto update mode: + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // If the number of synced points of the selected float ramp is greater or equal to the number of points of that in the main param, + // Create a Houdini engine manager event to delete the point at Idx of the selected float ramp; + + UHoudiniParameterRampFloatPoint* PointToDelete = SelectedRampPoints[Idx]; + + if (!PointToDelete) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(SelectedFloatRamp, PointToDelete->InstanceIndex); + SelectedFloatRamp->MarkChanged(true); + } + else + { + // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, + // delete the corresponding inserting event. + + int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + SelectedFloatRamp->ModificationEvents.RemoveAt(IdxInEventsArray); + } + } + else + { + // The selected float ramp is on manual update mode: + // Since we have pushed all the points in main param to the cached points of the selected float ramp, + // remove the corresponding points from the cached points array. + + if (SelectedFloatRamp->CachedPoints.IsValidIndex(Idx)) + { + SelectedFloatRamp->CachedPoints.RemoveAt(Idx); + SelectedFloatRamp->bCaching = true; + } + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point is inserted + else if (FloatCurve.GetNumKeys() > NumMainPoints) + { + // Find the index of the inserted point + for (int32 Idx = 0; Idx < FloatCurve.GetNumKeys(); ++Idx) + { + + float CurPointPosition = -1.0f; + float CurCurvePosition = FloatCurve.Keys[Idx].Time; + + if (MainPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + CurPointPosition = MainPoint->GetPosition(); + } + + // Insert instance at Idx + if (CurPointPosition != CurCurvePosition) + { + // Iterate through the float ramp parameter of all selected HDAs. + for (auto & NextFloatRamp : FloatRampParameters) + { + if (!NextFloatRamp.IsValid()) + continue; + + UHoudiniParameterRampFloat* SelectedFloatRamp = NextFloatRamp.Get(); + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedFloatRamp)) + continue; + + if (SelectedFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + // If the selected float ramp is on auto update mode: + // Since we have pushed all the points of main parameter to the selected, + // create a Houdini engine manager event to insert a point. + + FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent( + SelectedFloatRamp, CurCurvePosition, FloatCurve.Keys[Idx].Value, EHoudiniRampInterpolationType::LINEAR); + + SelectedFloatRamp->MarkChanged(true); + } + else + { + // If the selected float ramp is on manual update mode: + // push a new point to the cached points array + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(SelectedFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = CurCurvePosition; + NewCachedPoint->Value = FloatCurve.Keys[Idx].Value; + NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; + + if (Idx >= SelectedFloatRamp->CachedPoints.Num()) + SelectedFloatRamp->CachedPoints.Add(NewCachedPoint); + else + SelectedFloatRamp->CachedPoints.Insert(NewCachedPoint, Idx); + + SelectedFloatRamp->bCaching = true; + + if (!bCookingEnabled) + SelectedFloatRamp->MarkChanged(true); + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + if (bNeedUpdateEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } + +} + + +FReply +SHoudiniColorRampCurveEditor::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + FReply Reply = SColorGradientEditor::OnMouseButtonDown(MyGeometry, MouseEvent); + + if (HoudiniColorRampCurve.IsValid()) + { + UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); + if (Curve) + Curve->bEditing = true; + } + + return Reply; +} + +FReply +SHoudiniColorRampCurveEditor::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + + FReply Reply = SColorGradientEditor::OnMouseButtonUp(MyGeometry, MouseEvent); + + if (HoudiniColorRampCurve.IsValid()) + { + UHoudiniColorRampCurve* Curve = HoudiniColorRampCurve.Get(); + + if (Curve) + { + Curve->bEditing = false; + Curve->OnColorRampCurveChanged(true); + } + } + + return Reply; + +} + +FReply +SHoudiniColorRampCurveEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + FReply Reply = SColorGradientEditor::OnKeyDown(MyGeometry, InKeyEvent); + + if (InKeyEvent.GetKey().ToString() != FString("Enter")) + return Reply; + + if (!HoudiniColorRampCurve.IsValid() || !HoudiniColorRampCurve.Get()) + return Reply; + + TArray> &ColorRampParameters = HoudiniColorRampCurve.Get()->ColorRampParameters; + + if (ColorRampParameters.Num() < 1) + return Reply; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return Reply; + + // Do nothing if the main param is on auto update mode + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + if (MainParam->IsAutoUpdate() && bCookingEnabled) + return Reply; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return Reply; + + // Push the points in the main color ramp to the color ramp parameters in all selected HDAs. + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); + + for (auto& NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not sync the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + // Sync the cached points if the selected color ramp is on manual update mode + FHoudiniParameterDetails::SyncCachedColorRampPoints(SelectedColorRamp); + } + + return Reply; +} + +void +UHoudiniColorRampCurve::OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) +{ + Super::OnCurveChanged(ChangedCurveEditInfos); + + OnColorRampCurveChanged(); +} + +void +UHoudiniColorRampCurve::OnColorRampCurveChanged(bool bModificationOnly) +{ + // Array is always true in this case + // if (!FloatCurves) + // return; + + if (ColorRampParameters.Num() < 1) + return; + + if (!ColorRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return; + + // Do not allow modification when the parent HDA of the main param is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + // Push all the points of the main parameter to other parameters + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampParameters); + + // Modification is based on the Main Param, use synced points if the Main Param is on auto update mode,otherwise use its cached points. + bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + int32 NumMainPoints = MainPoints.Num(); + + bool bNeedUpdateEditor = false; + + // OnCurveChanged handler of color ramp curve editor handles point delete, insert and color change + + // A point is deleted + if (FloatCurves->GetNumKeys() < NumMainPoints) + { + if (bModificationOnly) + return; + + // Find the index of the deleted point + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + float CurPointPosition = MainPoint->GetPosition(); + float CurCurvePosition = -1.0f; + + if (FloatCurves[0].Keys.IsValidIndex(Idx)) + CurCurvePosition = FloatCurves[0].Keys[Idx].Time; + + // Delete the point at Idx + if (CurCurvePosition != CurPointPosition) + { + // Iterate through all the color ramp parameter in all the selected HDAs + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + TArray & SelectedRampPoints = SelectedColorRamp->Points; + + // The selected color ramp is on auto update mode: + + if (SelectedRampPoints.IsValidIndex(Idx)) + { + // If the number of synced points of the selected color ramp is greater or equal to the number of points of that in the main param, + // create a Houdini engine manager event to delete the point at Idx of the selected float ramp; + + UHoudiniParameterRampColorPoint* PointToDelete = SelectedRampPoints[Idx]; + + if (!PointToDelete) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(SelectedColorRamp, PointToDelete->InstanceIndex); + SelectedColorRamp->MarkChanged(true); + } + else + { + // If the number is smaller than that in the main param, since we have pushed all the points in the main param to the selected parameters, + // delete the corresponding inserting event. + + int32 IdxInEventsArray = Idx - SelectedRampPoints.Num(); + if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + SelectedColorRamp->ModificationEvents.RemoveAt(IdxInEventsArray); + } + } + else + { + // The selected color ramp is on manual update mode: + // Since we have pushed all the points in main param to the cached points of the selected float ramp, + // remove the corresponding points from the cached points array + if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) + { + SelectedColorRamp->CachedPoints.RemoveAt(Idx); + SelectedColorRamp->bCaching = true; + } + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!(MainParam->IsAutoUpdate() && bCookingEnabled)) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point is inserted + else if (FloatCurves[0].GetNumKeys() > NumMainPoints) + { + + if (bModificationOnly) + return; + + // Find the index of the inserted point + for (int32 Idx = 0; Idx < FloatCurves[0].GetNumKeys(); ++Idx) + { + + float CurPointPosition = -1.0f; + float CurCurvePosition = FloatCurves[0].Keys[Idx].Time; + + if (MainPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + CurPointPosition = MainPoint->GetPosition(); + } + + // Insert a point at Idx + if (CurPointPosition != CurCurvePosition) + { + // Get the interpolation value of inserted color point + + FLinearColor ColorPrev = FLinearColor::Black; + FLinearColor ColorNext = FLinearColor::White; + float PositionPrev = 0.0f; + float PositionNext = 1.0f; + + if (MainParam->IsAutoUpdate() && bCookingEnabled) + { + // Try to get its previous point's color + if (MainParam->Points.IsValidIndex(Idx - 1)) + { + ColorPrev = MainParam->Points[Idx - 1]->GetValue(); + PositionPrev = MainParam->Points[Idx - 1]->GetPosition(); + } + + // Try to get its next point's color + if (MainParam->Points.IsValidIndex(Idx)) + { + ColorNext = MainParam->Points[Idx]->GetValue(); + PositionNext = MainParam->Points[Idx]->GetPosition(); + } + } + else + { + // Try to get its previous point's color + if (MainParam->CachedPoints.IsValidIndex(Idx - 1)) + { + ColorPrev = MainParam->CachedPoints[Idx - 1]->GetValue(); + PositionPrev = MainParam->CachedPoints[Idx - 1]->GetPosition(); + } + + // Try to get its next point's color + if (MainParam->CachedPoints.IsValidIndex(Idx)) + { + ColorNext = MainParam->CachedPoints[Idx]->GetValue(); + PositionNext = MainParam->CachedPoints[Idx]->GetPosition(); + } + } + + float TotalWeight = FMath::Abs(PositionNext - PositionPrev); + float PrevWeight = FMath::Abs(CurCurvePosition - PositionPrev); + float NextWeight = FMath::Abs(PositionNext - CurCurvePosition); + + FLinearColor InsertedColor = ColorPrev * (PrevWeight / TotalWeight) + ColorNext * (NextWeight / TotalWeight); + + // Iterate through the color ramp parameter of all selected HDAs. + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + // If the selected color ramp is on auto update mode: + // Since we have pushed all the points of main parameter to the selected, + // create a Houdini engine manager event to insert a point. + + FHoudiniParameterDetails::CreateColorRampParameterInsertEvent( + SelectedColorRamp, CurCurvePosition, InsertedColor, EHoudiniRampInterpolationType::LINEAR); + + SelectedColorRamp->MarkChanged(true); + } + else + { + // If the selected color ramp is on manual update mode: + // Push a new point to the cached points array + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(SelectedColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = CurCurvePosition; + NewCachedPoint->Value = InsertedColor; + NewCachedPoint->Interpolation = EHoudiniRampInterpolationType::LINEAR; + + if (Idx >= SelectedColorRamp->CachedPoints.Num()) + SelectedColorRamp->CachedPoints.Add(NewCachedPoint); + else + SelectedColorRamp->CachedPoints.Insert(NewCachedPoint, Idx); + + SelectedColorRamp->bCaching = true; + } + } + + // Refresh the editor only when the main parameter is on manual update mode and has been modified. + if (!MainParam->IsAutoUpdate() && bCookingEnabled) + bNeedUpdateEditor = true; + + break; + } + } + } + + // A point's color is changed + else + { + if (bEditing) + return; + + for (int32 Idx = 0; Idx < NumMainPoints; ++Idx) + { + UHoudiniParameterRampColorPoint* MainPoint = MainPoints[Idx]; + + if (!MainPoint) + continue; + + // Only handle color change + { + float CurvePosition = FloatCurves[0].Keys[Idx].Time; + float PointPosition = MainPoint->GetPosition(); + + FLinearColor CurveColor = FLinearColor::Black; + FLinearColor PointColor = MainPoint->GetValue(); + + CurveColor.R = FloatCurves[0].Keys[Idx].Value; + CurveColor.G = FloatCurves[1].Keys[Idx].Value; + CurveColor.B = FloatCurves[2].Keys[Idx].Value; + + // Color is changed at Idx + if (CurveColor != PointColor || CurvePosition != PointPosition) + { + // Iterate through the all selected color ramp parameters + for (auto & NextColorRamp : ColorRampParameters) + { + if (!NextColorRamp.IsValid()) + continue; + + UHoudiniParameterRampColor* SelectedColorRamp = NextColorRamp.Get(); + + if (!SelectedColorRamp) + continue; + + // Do not modify the selected parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(SelectedColorRamp)) + continue; + + if (SelectedColorRamp->IsAutoUpdate() && bCookingEnabled) + { + // The selected color ramp parameter is on auto update mode + + if (SelectedColorRamp->Points.IsValidIndex(Idx)) + { + // If the number of synced points in the selected color ramp is more or equal to that in the main parameter: + // Modify the corresponding synced point of the selected color ramp, and marked it as changed. + + UHoudiniParameterRampColorPoint* Point = SelectedColorRamp->Points[Idx]; + + if (!Point) + continue; + + if (Point->GetValue() != CurveColor && Point->ValueParentParm) + { + Point->SetValue(CurveColor); + Point->ValueParentParm->MarkChanged(true); + } + + if (Point->GetPosition() != CurvePosition && Point->PositionParentParm) + { + Point->SetPosition(CurvePosition); + Point->PositionParentParm->MarkChanged(true); + } + } + else + { + // If the number of synced points in the selected color ramp is less than that in the main parameter: + // Since we have push the points in the main parameter to all selected parameters, + // we need to modify the corresponding insert event. + + int32 IdxInEventsArray = Idx - SelectedColorRamp->Points.Num(); + + if (SelectedColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = SelectedColorRamp->ModificationEvents[IdxInEventsArray]; + + if (!Event) + continue; + + if (Event->InsertColor != CurveColor) + Event->InsertColor = CurveColor; + + if (Event->InsertPosition != CurvePosition) + Event->InsertPosition = CurvePosition; + } + } + } + else + { + // The selected color ramp is on manual update mode + // Since we have push the points in the main parameter to all selected parameters, + // modify the corresponding point in the cached points array of the selected color ramp. + if (SelectedColorRamp->CachedPoints.IsValidIndex(Idx)) + { + UHoudiniParameterRampColorPoint* CachedPoint = SelectedColorRamp->CachedPoints[Idx]; + + if (!CachedPoint) + continue; + + if (CachedPoint->Value != CurveColor) + { + CachedPoint->Value = CurveColor; + bNeedUpdateEditor = true; + } + + if (CachedPoint->Position != CurvePosition) + { + CachedPoint->Position = CurvePosition; + SelectedColorRamp->bCaching = true; + bNeedUpdateEditor = true; + } + } + } + } + } + } + } + } + + + if (bNeedUpdateEditor) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + } +} + +template< class T > +bool FHoudiniParameterDetails::CastParameters( + TArray InParams, TArray& OutCastedParams ) +{ + for (auto CurrentParam : InParams) + { + T* CastedParam = Cast(CurrentParam); + if (CastedParam && !CastedParam->IsPendingKill()) + OutCastedParams.Add(CastedParam); + } + + return (OutCastedParams.Num() == InParams.Num()); +} + + +void +FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* InParam = InParams[0]; + if (!InParam || InParam->IsPendingKill()) + return; + + // The directory won't parse if parameter ids are -1 + // simply return + if (InParam->GetParmId() < 0) + return; + + if (CurrentRampFloat) + { + // CreateWidgetFloatRamp(HouParameterCategory, InParams); + // If this parameter is a part of the last float ramp, skip it + if (InParam->GetIsChildOfMultiParm() && InParam->GetParentParmId() == CurrentRampFloat->GetParmId()) + return; + + // This parameter is not part of the last float ramp (we've passed all of its points/instances), reset + // CurrentRampFloat in order to continue normal processing of parameters + CurrentRampFloat = nullptr; + } + if (CurrentRampColor) + { + // CreateWidgetColorRamp(HouParameterCategory, InParams); + // if this parameter is a part of the last color ramp, skip it + if (InParam->GetIsChildOfMultiParm() && InParam->GetParentParmId() == CurrentRampColor->GetParmId()) + return; + + // This parameter is not part of the last color ramp (we've passed all of its points/instances), reset + // CurrentRampColor in order to continue normal processing of parameters + CurrentRampColor = nullptr; + } + + switch (InParam->GetParameterType()) + { + case EHoudiniParameterType::Float: + { + CreateWidgetFloat(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Int: + { + CreateWidgetInt(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::String: + { + CreateWidgetString(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::StringChoice: + { + CreateWidgetChoice(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Separator: + { + TArray SepParams; + if (CastParameters(InParams, SepParams)) + { + bool bEnabled = InParams.IsValidIndex(0) ? !SepParams[0]->IsDisabled() : true; + CreateWidgetSeparator(HouParameterCategory, InParams, bEnabled); + } + } + break; + + case EHoudiniParameterType::Color: + { + CreateWidgetColor(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Button: + { + CreateWidgetButton(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::ButtonStrip: + { + CreateWidgetButtonStrip(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Label: + { + CreateWidgetLabel(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Toggle: + { + CreateWidgetToggle(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + CreateWidgetFile(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::FolderList: + { + CreateWidgetFolderList(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Folder: + { + CreateWidgetFolder(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::MultiParm: + { + CreateWidgetMultiParm(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::FloatRamp: + { + CreateWidgetFloatRamp(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::ColorRamp: + { + CreateWidgetColorRamp(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Input: + { + CreateWidgetOperatorPath(HouParameterCategory, InParams); + } + break; + + case EHoudiniParameterType::Invalid: + { + HandleUnsupportedParmType(HouParameterCategory, InParams); + } + break; + + default: + { + HandleUnsupportedParmType(HouParameterCategory, InParams); + } + break; + } + + // Remove a divider lines recurrsively if current parameter hits the end of a tabs + RemoveTabDividers(HouParameterCategory, InParam); + +} + +void +FHoudiniParameterDetails::CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory) +{ + FDetailWidgetRow & Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()); + TSharedPtr TabEndingRow = SNew(SCustomizedBox); + + TabEndingRow->DividerLinePositions = DividerLinePositions; + + if (TabEndingRow.IsValid()) + CurrentTabEndingRow = TabEndingRow.Get(); + + Row.WholeRowWidget.Widget = TabEndingRow.ToSharedRef(); + Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniParameterDetails::CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam|| MainParam->IsPendingKill()) + return; + + if (!Row) + return; + + TSharedRef< SCustomizedBox > HorizontalBox = SNew(SCustomizedBox); + + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + + + if (MainParam->IsDirectChildOfMultiParm()) + { + FString ParameterLabelStr = MainParam->GetParameterLabel(); + + // If it is head of an multiparm instance + if (MainParam->GetChildIndex() == 0) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + } + ParameterLabelStr += TEXT(" (") + FString("") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + HorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + else + { + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(MainParam->GetParameterLabel()) : FText::GetEmpty(); + HorizontalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + + Row->NameWidget.Widget = HorizontalBox; +} + +void +FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel) +{ + if (!Row) + return; + + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + FString ParameterLabelStr = MainParam->GetParameterLabel(); + TSharedRef HorizontalBox = SNew(SCustomizedBox); + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + + TSharedPtr VerticalBox; + HorizontalBox->AddSlot() + [ + SAssignNew(VerticalBox, SVerticalBox) + ]; + + if (MainParam->IsDirectChildOfMultiParm()) + { + // If it is head of an multiparm instance + if (MainParam->GetChildIndex() == 0) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + } + + ParameterLabelStr += TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex + 1) + TEXT(")"); + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* RampParameter = Cast(MainParam); + if (RampParameter) + { + if (RampParameter->bCaching) + ParameterLabelStr += "*"; + } + } + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + else + { + // TODO: Refactor me...extend 'auto/manual update' to all parameters? (It only applies to color and float ramps for now.) + bool bParamNeedUpdate = false; + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* RampParameter = Cast(MainParam); + if (RampParameter) + bParamNeedUpdate = RampParameter->bCaching; + } + else if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat* RampParameter = Cast(MainParam); + if (RampParameter) + bParamNeedUpdate = RampParameter->bCaching; + } + + if (bParamNeedUpdate) + ParameterLabelStr += "*"; + + const FText & FinalParameterLabelText = WithLabel ? FText::FromString(ParameterLabelStr) : FText::GetEmpty(); + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(STextBlock) + .Text(FinalParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(MainParam->IsDefault() ? TEXT("PropertyWindow.NormalFont") : TEXT("PropertyWindow.BoldFont"))) + ]; + } + + auto IsAutoUpdateChecked = [MainParam]() + { + if (!MainParam || MainParam->IsPendingKill()) + return ECheckBoxState::Unchecked; + + return MainParam->IsAutoUpdate() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + auto OnAutoUpdateCheckBoxStateChanged = [MainParam, InParams, bCookingEnabled](ECheckBoxState NewState) + { + if (NewState == ECheckBoxState::Checked) + { + for (auto & NextSelectedParam : InParams) + { + if (!NextSelectedParam) + continue; + + if (NextSelectedParam->IsAutoUpdate() && bCookingEnabled) + continue; + + // Do not allow mode change when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) + continue; + + switch (MainParam->GetParameterType()) + { + case EHoudiniParameterType::ColorRamp: + { + UHoudiniParameterRampColor* ColorRampParameter = Cast(NextSelectedParam); + + if (!ColorRampParameter) + continue; + + // Do not sync the selected color ramp parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) + continue; + + // Sync the Cached curve points at update mode switch. + FHoudiniParameterDetails::SyncCachedColorRampPoints(ColorRampParameter); + } + break; + + case EHoudiniParameterType::FloatRamp: + { + UHoudiniParameterRampFloat* FloatRampParameter = Cast(NextSelectedParam); + + if (!FloatRampParameter) + continue; + + // Do not sync the selected float ramp parameter if its parent HDA is being cooked + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) + continue; + + // Sync the Cached curve points at update mode switch. + //FHoudiniParameterDetails::SyncCachedFloatRampPoints(FloatRampParameter); + FloatRampParameter->SyncCachedPoints(); + } + break; + + default: + break; + } + + NextSelectedParam->SetAutoUpdate(true); + } + } + else + { + for (auto & NextSelectedParam : InParams) + { + if (!NextSelectedParam) + continue; + + if (!(NextSelectedParam->IsAutoUpdate() && bCookingEnabled)) + continue; + + // Do not allow mode change when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(NextSelectedParam)) + continue; + + NextSelectedParam->SetAutoUpdate(false); + } + } + }; + + // Auto update check box + TSharedPtr CheckBox; + + VerticalBox->AddSlot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Left) + [ + SNew(SHorizontalBox) + + + SHorizontalBox::Slot() + [ + SAssignNew(CheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([OnAutoUpdateCheckBoxStateChanged](ECheckBoxState NewState) + { + OnAutoUpdateCheckBoxStateChanged(NewState); + }) + .IsChecked_Lambda([IsAutoUpdateChecked]() + { + return IsAutoUpdateChecked(); + }) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("AutoUpdate", "Auto-update")) + .ToolTipText(LOCTEXT("AutoUpdateTip", "When enabled, this parameter will automatically update its value while editing. Turning this off will allow you to more easily update it, and the update can be pushed by checking the toggle again.")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ] + ]; + + if ((MainParam->GetParameterType() != EHoudiniParameterType::FloatRamp) && (MainParam->GetParameterType() != EHoudiniParameterType::ColorRamp)) + CheckBox->SetVisibility(EVisibility::Hidden); + + Row->NameWidget.Widget = HorizontalBox; +} + +FDetailWidgetRow* +FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount) +{ + if (InParams.Num() <= 0) + return nullptr; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return nullptr; + + // Created row for the current parameter (if there is not a row created, do not show the parameter). + FDetailWidgetRow* Row = nullptr; + + // Current parameter is in a multiparm instance (directly) + if (MainParam->IsDirectChildOfMultiParm()) + { + int32 ParentMultiParmId = MainParam->GetParentParmId(); + + // If this is a folder param, its folder list parent parm is the multiparm + if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) + { + if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) // This should not happen + return nullptr; + + UHoudiniParameterFolderList* ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); + if (!ParentFolderList || ParentFolderList->IsPendingKill()) + return nullptr; // This should not happen + + ParentMultiParmId = ParentFolderList->GetParentParmId(); + } + + if (!AllMultiParms.Contains(ParentMultiParmId)) // This should not happen normally + return nullptr; + + // Get the parent multiparm + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentMultiParmId]; + + // The parent multiparm is visible. + if (ParentMultiParm && ParentMultiParm->IsShown() && MainParam->ShouldDisplay()) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + + } + // This item is not a direct child of a multiparm. + else + { + bool bIsFolder = MainParam->GetParameterType() == EHoudiniParameterType::Folder; + + // If this parameter is a folder, its parent folder should be the second top of the stack + int32 NestedMinStackDepth = bIsFolder ? 1 : 0; + + // Current parameter is inside a folder. + if (FolderStack.Num() > NestedMinStackDepth) + { + // If the current parameter is a folder, we take the top second queue on the stack, since the top one represents itself. + // Otherwise take the top queue on the stack. + TArray & CurrentLayerFolderQueue = bIsFolder ? + FolderStack[FolderStack.Num() - 2] : FolderStack.Last(); + + if (CurrentLayerFolderQueue.Num() <= 0) // Error state + return nullptr; + + bool bParentFolderVisible = CurrentLayerFolderQueue[0]->IsContentShown(); + + bool bIsSelectedTabVisible = false; + + // If its parent folder is visible, display current parameter, + // Otherwise, just prune the stacks. + if (bParentFolderVisible) + { + int32 ParentFolderId = MainParam->GetParentParmId(); + + // If the current parameter is a folder, its parent is a folderlist. + // So we need to continue to get the parent of the folderlist. + if (MainParam->GetParameterType() == EHoudiniParameterType::Folder) + { + if (AllFoldersAndFolderLists.Contains(ParentFolderId)) + ParentFolderId = AllFoldersAndFolderLists[ParentFolderId]->GetParentParmId(); + else + return nullptr; // error state + } + + UHoudiniParameterFolder* ParentFolder = nullptr; + + if (AllFoldersAndFolderLists.Contains(ParentFolderId)) + ParentFolder = Cast(AllFoldersAndFolderLists[ParentFolderId]); + + bool bShouldDisplayRow = MainParam->ShouldDisplay(); + + // This row should be shown if its parent folder is shown. + if (ParentFolder) + bShouldDisplayRow &= (ParentFolder->IsTab() && ParentFolder->IsChosen()) || (!ParentFolder->IsTab() && ParentFolder->IsExpanded()); + + if (bShouldDisplayRow) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + } + + // prune the stack finally + if (bDecreaseChildCount) + { + CurrentLayerFolderQueue[0]->GetChildCounter() -= 1; + PruneStack(); + } + } + // If this parameter is in the root dir, just create a row. + else + { + if (MainParam->ShouldDisplay()) + { + if (MainParam->GetParameterType() != EHoudiniParameterType::FolderList) + Row = &(HouParameterCategory.AddCustomRow(FText::GetEmpty())); + } + } + } + + if (!MainParam->IsVisible()) + return nullptr; + + + if (Row) + CurrentTabEndingRow = nullptr; + + return Row; +} + +void +FHoudiniParameterDetails::HandleUnsupportedParmType(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + CreateNestedRow(HouParameterCategory, (TArray)InParams); +} + +void +FHoudiniParameterDetails::CreateWidgetFloat( + IDetailCategoryBuilder & HouParameterCategory, + TArray& InParams ) +{ + TArray FloatParams; + if (!CastParameters(InParams, FloatParams)) + return; + + if (FloatParams.Num() <= 0) + return; + + UHoudiniParameterFloat* MainParam = FloatParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + // Helper function to find a unit from a string (name or abbreviation) + auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); + EUnit Unit = EUnit::Unspecified; + if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) + Unit = ParmUnit.GetValue(); + + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + // Lambdas for slider begin + auto SliderBegin = [&](TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), + FloatParams[0]->GetOuter()); + + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + FloatParams[Idx]->Modify(); + } + }; + + // Lambdas for slider end + auto SliderEnd = [&](TArray FloatParams) + { + // Mark the value as changed to trigger an update + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + FloatParams[Idx]->MarkChanged(true); + } + }; + + // Lambdas for changing the parameter value + auto ChangeFloatValueAt = [&](const float& Value, const int32& Index, const bool& DoChange, TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Changing a value"), + FloatParams[0]->GetOuter() ); + + bool bChanged = false; + for (int Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + FloatParams[Idx]->Modify(); + if (FloatParams[Idx]->SetValueAt(Value, Index)) + { + // Only mark the param has changed if DoChange is true!!! + if(DoChange) + FloatParams[Idx]->MarkChanged(true); + bChanged = true; + } + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if no parameter's value has actually been changed + Transaction.Cancel(); + } + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray FloatParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFloatChange", "Houdini Parameter Float: Revert to default value"), + FloatParams[0]->GetOuter()); + + if (TupleIndex < 0) + { + for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + if (FloatParams[Idx]->IsDefault()) + continue; + + FloatParams[Idx]->RevertToDefault(-1); + } + } + else + { + for (int32 Idx = 0; Idx < FloatParams.Num(); Idx++) + { + if (!FloatParams[Idx]) + continue; + + if (FloatParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + FloatParams[Idx]->RevertToDefault(TupleIndex); + } + } + return FReply::Handled(); + }; + + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + //TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + if (MainParam->GetTupleSize() == 3) + { + // Should we swap Y and Z fields (only relevant for Vector3) + // Ignore the swapping if that parameter has the noswap tag + bool SwapVector3 = !MainParam->GetNoSwap(); + + auto ChangeFloatValueUniformly = [FloatParams, ChangeFloatValueAt](const float& Val, const bool& bDoChange) + { + ChangeFloatValueAt(Val, 0, bDoChange, FloatParams); + ChangeFloatValueAt(Val, 1, bDoChange, FloatParams); + ChangeFloatValueAt(Val, 2, bDoChange, FloatParams); + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SNew(SVectorInputBox) + .bColorAxisLabels(true) + .AllowSpin(true) + .X(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, 0))) + .Y(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 2 : 1))) + .Z(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, SwapVector3 ? 1 : 2))) + .OnXCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, true); + else + ChangeFloatValueAt( Val, 0, true, FloatParams); + }) + .OnYCommitted_Lambda( [ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, true); + else + ChangeFloatValueAt( Val, SwapVector3 ? 2 : 1, true, FloatParams); + }) + .OnZCommitted_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val, ETextCommit::Type TextCommitType) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, true); + else + ChangeFloatValueAt( Val, SwapVector3 ? 1 : 2, true, FloatParams); + }) + .OnXChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, 0, false, FloatParams); + }) + .OnYChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, SwapVector3 ? 2 : 1, false, FloatParams); + }) + .OnZChanged_Lambda([ChangeFloatValueAt, ChangeFloatValueUniformly, FloatParams, MainParam, SwapVector3](float Val) + { + if (MainParam->IsUniformLocked()) + ChangeFloatValueUniformly(Val, false); + else + ChangeFloatValueAt(Val, SwapVector3 ? 1 : 2, false, FloatParams); + }) + .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) + .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Right).VAlign(VAlign_Center) + [ + SNew(SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .ToolTipText(LOCTEXT("FloatParameterLockButtonToolTip", "When locked, change the vector value uniformly.")) + .Visibility(EVisibility::Visible) + [ + SNew(SImage) + .Image(MainParam->IsUniformLocked() ? FEditorStyle::GetBrush("Genericlock") : FEditorStyle::GetBrush("GenericUnlock")) + ] + .OnClicked_Lambda([FloatParams, MainParam]() + { + if (!MainParam || MainParam->IsPendingKill()) + return FReply::Handled(); + + for (auto & CurParam : FloatParams) + { + if (!CurParam || CurParam->IsPendingKill()) + continue; + + CurParam->SwitchUniformLock(); + } + + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + + return FReply::Handled(); + }) + ] + + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left).VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([FloatParams]() + { + for (auto & SelectedParam : FloatParams) + { + if (!SelectedParam) + continue; + + if (!SelectedParam->IsDefault()) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([FloatParams, RevertToDefault]() { return RevertToDefault(-1, FloatParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + else + { + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + TSharedPtr> NumericEntryBox; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< float >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + + .MinValue(MainParam->GetMin()) + .MaxValue(MainParam->GetMax()) + + .MinSliderValue(MainParam->GetUIMin()) + .MaxSliderValue(MainParam->GetUIMax()) + + .Value(TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterFloat::GetValue, Idx))) + .OnValueChanged_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val) { ChangeFloatValueAt(Val, Idx, false, FloatParams); }) + .OnValueCommitted_Lambda([ChangeFloatValueAt, Idx, FloatParams](float Val, ETextCommit::Type TextCommitType) { ChangeFloatValueAt(Val, Idx, true, FloatParams); }) + .OnBeginSliderMovement_Lambda([SliderBegin, FloatParams]() { SliderBegin(FloatParams); }) + .OnEndSliderMovement_Lambda([SliderEnd, FloatParams](const float NewValue) { SliderEnd(FloatParams); }) + .SliderExponent(MainParam->IsLogarithmic() ?8.0f : 1.0f) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .OnClicked_Lambda([Idx, FloatParams, RevertToDefault]() { return RevertToDefault(Idx, FloatParams); }) + .Visibility_Lambda([Idx, FloatParams]() + { + for (auto & SelectedParam :FloatParams) + { + if (!SelectedParam) + continue; + + if (!SelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + } + } + + Row->ValueWidget.Widget =VerticalBox; + + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetInt(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray IntParams; + if (!CastParameters(InParams, IntParams)) + + if (IntParams.Num() <= 0) + return; + + UHoudiniParameterInt* MainParam = IntParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + // Helper function to find a unit from a string (name or abbreviation) + auto ParmUnit = FUnitConversion::UnitFromString(*(MainParam->GetUnit())); + EUnit Unit = EUnit::Unspecified; + if (FUnitConversion::Settings().ShouldDisplayUnits() && ParmUnit.IsSet()) + Unit = ParmUnit.GetValue(); + + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + // Lambda for slider begin + auto SliderBegin = [&](TArray IntParams) + { + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), + IntParams[0]->GetOuter()); + + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + IntParams[Idx]->Modify(); + } + }; + + // Lambda for slider end + auto SliderEnd = [&](TArray IntParams) + { + // Mark the value as changed to trigger an update + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + IntParams[Idx]->MarkChanged(true); + } + }; + + // Lambda for changing the parameter value + auto ChangeIntValueAt = [&](const int32& Value, const int32& Index, const bool& DoChange, TArray IntParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterIntChange", "Houdini Parameter Int: Changing a value"), + IntParams[0]->GetOuter()); + + bool bChanged = false; + for (int Idx = 0; Idx < IntParams.Num(); Idx++) + { + if (!IntParams[Idx]) + continue; + + IntParams[Idx]->Modify(); + if (IntParams[Idx]->SetValueAt(Value, Index)) + { + // Only mark the param has changed if DoChange is true!!! + if (DoChange) + IntParams[Idx]->MarkChanged(true); + bChanged = true; + } + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if there is no param has actually been changed + Transaction.Cancel(); + } + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray IntParams) + { + for (int32 Idx = 0; Idx < IntParams.Num(); Idx++) + { + if (!IntParams[Idx]) + continue; + + if (IntParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + IntParams[Idx]->RevertToDefault(TupleIndex); + } + + return FReply::Handled(); + }; + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + + .MinValue(MainParam->GetMin()) + .MaxValue(MainParam->GetMax()) + + .MinSliderValue(MainParam->GetUIMin()) + .MaxSliderValue(MainParam->GetUIMax()) + + .Value( TAttribute>::Create(TAttribute>::FGetter::CreateUObject(MainParam, &UHoudiniParameterInt::GetValue, Idx))) + .OnValueChanged_Lambda( [=](int32 Val) { ChangeIntValueAt(Val, Idx, false, IntParams); } ) + .OnValueCommitted_Lambda([=](float Val, ETextCommit::Type TextCommitType) { ChangeIntValueAt(Val, Idx, true, IntParams); }) + .OnBeginSliderMovement_Lambda( [=]() { SliderBegin(IntParams); }) + .OnEndSliderMovement_Lambda([=](const float NewValue) { SliderEnd(IntParams); }) + .SliderExponent(MainParam->IsLogarithmic() ? 8.0f : 1.0f) + .TypeInterface(paramTypeInterface) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, IntParams]() + { + for (auto & NextSelectedParam : IntParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, IntParams, RevertToDefault]() { return RevertToDefault(Idx, IntParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ]; + /* + if (NumericEntryBox.IsValid()) + NumericEntryBox->SetEnabled(!MainParam->IsDisabled()); + */ + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray StringParams; + if (!CastParameters(InParams, StringParams)) + return; + + if (StringParams.Num() <= 0) + return; + + UHoudiniParameterString* MainParam = StringParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + bool bIsMultiLine = false; + bool bIsUnrealRef = false; + UClass* UnrealRefClass = UObject::StaticClass(); + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox); + + TMap& Tags = MainParam->GetTags(); + if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_TAG) && FCString::Atoi(*Tags[HOUDINI_PARAMETER_STRING_REF_TAG]) == 1) + { + bIsUnrealRef = true; + + if (Tags.Contains(HOUDINI_PARAMETER_STRING_REF_CLASS_TAG)) + { + UClass * FoundClass = FindObject(ANY_PACKAGE, *Tags[HOUDINI_PARAMETER_STRING_REF_CLASS_TAG]); + if (FoundClass != nullptr) + { + UnrealRefClass = FoundClass; + } + } + } + + if (Tags.Contains(HOUDINI_PARAMETER_STRING_MULTILINE_TAG)) + { + bIsMultiLine = true; + } + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + // Lambda for changing the parameter value + auto ChangeStringValueAt = [&](const FString& Value, UObject* ChosenObj, const int32& Index, const bool& DoChange, TArray StringParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterSrtingChange", "Houdini Parameter String: Changing a value"), + StringParams[0]->GetOuter()); + + bool bChanged = false; + for (int Idx = 0; Idx < StringParams.Num(); Idx++) + { + if (!StringParams[Idx]) + continue; + + StringParams[Idx]->Modify(); + if (StringParams[Idx]->SetValueAt(Value, Index)) + { + StringParams[Idx]->MarkChanged(true); + bChanged = true; + } + + StringParams[Idx]->SetAssetAt(ChosenObj, Index); + } + + if (!bChanged || !DoChange) + { + // Cancel the transaction if there is no param actually has been changed + Transaction.Cancel(); + } + + FHoudiniEngineUtils::UpdateEditorProperties(StringParams[0], false); + }; + + auto RevertToDefault = [&](const int32& TupleIndex, TArray StringParams) + { + for (int32 Idx = 0; Idx < StringParams.Num(); Idx++) + { + if (!StringParams[Idx]) + continue; + + if (StringParams[Idx]->IsDefaultValueAtIndex(TupleIndex)) + continue; + + StringParams[Idx]->RevertToDefault(TupleIndex); + } + + return FReply::Handled(); + }; + + if (bIsUnrealRef) + { + TSharedPtr EditableTextBox; + TSharedPtr HorizontalBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([UnrealRefClass](const UObject* InObject) + { + return InObject->IsA(UnrealRefClass); + }) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + ChangeStringValueAt(ReferenceStr, InObject, Idx, true, StringParams); + }) + [ + SAssignNew(HorizontalBox, SHorizontalBox) + ] + ]; + + // Thumbnail + // Get thumbnail pool for this builder. + TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = HouParameterCategory.GetParentLayout().GetThumbnailPool(); + + // Create a thumbnail for the selected object / class + UObject* EditObject = nullptr; + const FString AssetPath = MainParam->GetValueAt(Idx); + EditObject = StaticFindObject(nullptr, nullptr, *AssetPath, true); + + FAssetData AssetData; + if (IsValid(EditObject)) + { + AssetData = FAssetData(EditObject); + } + else + { + AssetData.AssetClass = UnrealRefClass->GetFName(); + } + + TSharedPtr< FAssetThumbnail > StaticMeshThumbnail = MakeShareable( + new FAssetThumbnail(AssetData, 64, 64, AssetThumbnailPool)); + + TSharedPtr ThumbnailBorder; + HorizontalBox->AddSlot().Padding(0.f, 0.f, 2.f, 0.f).AutoWidth() + [ + SAssignNew(ThumbnailBorder, SBorder) + .OnMouseDoubleClick_Lambda([EditObject, Idx](const FGeometry&, const FPointerEvent&) + { + if (EditObject && GEditor) + GEditor->EditObject(EditObject); + + return FReply::Handled(); + }) + .Padding(5.f) + [ + SNew(SBox) + .WidthOverride(64) + .HeightOverride(64) + [ + StaticMeshThumbnail->MakeThumbnailWidget() + ] + ] + ]; + + TWeakPtr WeakThumbnailBorder(ThumbnailBorder); + ThumbnailBorder->SetBorderImage(TAttribute::Create( + TAttribute::FGetter::CreateLambda( + [WeakThumbnailBorder]() + { + TSharedPtr ThumbnailBorderPtr = WeakThumbnailBorder.Pin(); + if (ThumbnailBorderPtr.IsValid() && ThumbnailBorderPtr->IsHovered()) + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight"); + else + return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow"); + } + ) + )); + + FText MeshNameText = FText::GetEmpty(); + //if (InputObject) + // MeshNameText = FText::FromString(InputObject->GetName()); + + TSharedPtr StaticMeshComboButton; + + TSharedPtr ButtonBox; + HorizontalBox->AddSlot() + .Padding(0.0f, 4.0f, 4.0f, 4.0f) + .VAlign(VAlign_Center) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .HAlign(HAlign_Fill) + [ + SAssignNew(ButtonBox, SHorizontalBox) + + SHorizontalBox::Slot() + [ + SAssignNew(StaticMeshComboButton, SComboButton) + .ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle") + .ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity")) + .ContentPadding(2.0f) + .ButtonContent() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass") + .Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont")))) + .Text(FText::FromName(AssetData.AssetName)) + .ToolTipText(FText::FromString(MainParam->GetValueAt(Idx))) + ] + ] + ] + ]; + + TWeakPtr WeakStaticMeshComboButton(StaticMeshComboButton); + StaticMeshComboButton->SetOnGetMenuContent(FOnGetContent::CreateLambda( + [UnrealRefClass, WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams]() + { + TArray AllowedClasses; + if (UnrealRefClass != UObject::StaticClass()) + { + // Use the class specified by the user + AllowedClasses.Add(UnrealRefClass); + } + else + { + // Using UObject would list way too many assets, and take a long time to open the menu, + // so we need to reestrict the classes a bit + AllowedClasses.Add(UStaticMesh::StaticClass()); + AllowedClasses.Add(UHoudiniAsset::StaticClass()); + AllowedClasses.Add(USkeletalMesh::StaticClass()); + AllowedClasses.Add(UBlueprint::StaticClass()); + AllowedClasses.Add(UMaterialInterface::StaticClass()); + AllowedClasses.Add(UTexture::StaticClass()); + AllowedClasses.Add(ULevel::StaticClass()); + AllowedClasses.Add(UStreamableRenderAsset::StaticClass()); + AllowedClasses.Add(USoundBase::StaticClass()); + AllowedClasses.Add(UParticleSystem::StaticClass()); + AllowedClasses.Add(UFoliageType::StaticClass()); + } + + TArray NewAssetFactories; + return PropertyCustomizationHelpers::MakeAssetPickerWithMenu( + FAssetData(nullptr), + true, + AllowedClasses, + NewAssetFactories, + FOnShouldFilterAsset(), + FOnAssetSelected::CreateLambda( + [WeakStaticMeshComboButton, ChangeStringValueAt, Idx, StringParams](const FAssetData & AssetData) + { + TSharedPtr StaticMeshComboButtonPtr = WeakStaticMeshComboButton.Pin(); + if (StaticMeshComboButtonPtr.IsValid()) + { + StaticMeshComboButtonPtr->SetIsOpen(false); + + UObject * Object = AssetData.GetAsset(); + // Get the asset reference string for this object + // !! Accept null objects to allow clearing the asset picker !! + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(Object); + + ChangeStringValueAt(ReferenceStr, Object, Idx, true, StringParams); + } + } + ), + FSimpleDelegate::CreateLambda([]() {})); + }) + ); + } + else if (bIsMultiLine) + { + TSharedPtr< SMultiLineEditableTextBox > MultiLineEditableTextBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) + {return true;}) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + FString NewString = ReferenceStr; + if (StringParams[0]->GetValueAt(Idx).Len() > 0) + NewString = StringParams[0]->GetValueAt(Idx) + "\n" + NewString; + + ChangeStringValueAt(NewString, nullptr, Idx, true, StringParams); + }) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Top).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(MultiLineEditableTextBox, SMultiLineEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainParam->GetValueAt(Idx))) + .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, StringParams]() + { + for (auto & NextSelectedParam : StringParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() { return RevertToDefault(Idx, StringParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + else + { + TSharedPtr< SEditableTextBox > EditableTextBox; + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SAssetDropTarget) + .OnIsAssetAcceptableForDrop_Lambda([](const UObject* InObject) + {return true;}) + .OnAssetDropped_Lambda([=](UObject* InObject) + { + // Get the asset reference string for this object + FString ReferenceStr = UHoudiniParameterString::GetAssetReference(InObject); + + ChangeStringValueAt(ReferenceStr, nullptr, Idx, true, StringParams); + }) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SAssignNew(EditableTextBox, SEditableTextBox) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Text(FText::FromString(MainParam->GetValueAt(Idx))) + .OnTextCommitted_Lambda([=](const FText& Val, ETextCommit::Type TextCommitType) + { ChangeStringValueAt(Val.ToString(), nullptr, Idx, true, StringParams); }) + ] + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(2.0f, 0.0f) + .VAlign(VAlign_Center) + [ + SNew(SButton) + .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ContentPadding(0) + .Visibility_Lambda([Idx, StringParams]() + { + for (auto & NextSelectedParam : StringParams) + { + if (!NextSelectedParam) + continue; + + if (!NextSelectedParam->IsDefaultValueAtIndex(Idx)) + return EVisibility::Visible; + } + + return EVisibility::Hidden; + }) + .OnClicked_Lambda([Idx, StringParams, RevertToDefault]() + { return RevertToDefault(Idx, StringParams); }) + [ + SNew(SImage) + .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) + ] + ] + ] + ]; + } + + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ColorParams; + if (!CastParameters(InParams, ColorParams)) + return; + + if (ColorParams.Num() <= 0) + return; + + UHoudiniParameterColor* MainParam = ColorParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + bool bHasAlpha = (MainParam->GetTupleSize() == 4); + + // Add color picker UI. + TSharedPtr ColorBlock; + TSharedRef VerticalBox = SNew(SVerticalBox); + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(ColorBlock, SColorBlock) + .Color(MainParam->GetColorValue()) + .ShowBackgroundForAlpha(bHasAlpha) + .OnMouseButtonDown_Lambda([this, ColorParams, MainParam, bHasAlpha](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) + { + FColorPickerArgs PickerArgs; + PickerArgs.ParentWidget = FSlateApplication::Get().GetActiveTopLevelWindow(); + PickerArgs.bUseAlpha = bHasAlpha; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda([&](FLinearColor InColor) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterColorChange", "Houdini Parameter Color: Changing value"), + MainParam->GetOuter(), true); + + bool bChanged = false; + for (auto & Param : ColorParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetColorValue(InColor)) + { + Param->MarkChanged(true); + bChanged = true; + } + } + + // cancel the transaction if there is actually no value changed + if (!bChanged) + { + Transaction.Cancel(); + } + }); + PickerArgs.InitialColorOverride = MainParam->GetColorValue(); + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker(PickerArgs); + return FReply::Handled(); + }) + ]; + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetButton(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ButtonParams; + if (!CastParameters(InParams, ButtonParams)) + return; + + if (ButtonParams.Num() <= 0) + return; + + UHoudiniParameterButton* MainParam = ButtonParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + FText ParameterTooltip = GetParameterTooltip(MainParam); + + TSharedRef HorizontalBox = SNew(SHorizontalBox); + TSharedPtr Button; + + // Add button UI. + HorizontalBox->AddSlot().Padding(1, 2, 4, 2) + [ + SAssignNew(Button, SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(ParameterLabelText) + .ToolTipText(ParameterTooltip) + .OnClicked(FOnClicked::CreateLambda( [MainParam, ButtonParams]() + { + for (auto & Param : ButtonParams) + { + if (!Param) + continue; + + // There is no undo redo operation for button + Param->MarkChanged(true); + } + + return FReply::Handled(); + })) + ]; + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ButtonStripParams; + if (!CastParameters(InParams, ButtonStripParams)) + return; + + if (ButtonStripParams.Num() <= 0) + return; + + UHoudiniParameterButtonStrip* MainParam = ButtonStripParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + if (!Row) + return; + + auto OnButtonStateChanged = [MainParam, ButtonStripParams](ECheckBoxState NewState, int32 Idx) + { + + /* + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterButtonStripChange", "Houdini Parameter Button Strip: Changing value"), + MainParam->GetOuter(), true); + */ + int32 StateInt = NewState == ECheckBoxState::Checked ? 1 : 0; + bool bChanged = false; + + for (auto & NextParam : ButtonStripParams) + { + if (!NextParam || NextParam->IsPendingKill()) + continue; + + if (!NextParam->Values.IsValidIndex(Idx)) + continue; + + //NextParam->Modify(); + if (NextParam->SetValueAt(Idx, StateInt)) + { + NextParam->MarkChanged(true); + bChanged = true; + } + } + + //if (!bChanged) + // Transaction.Cancel(); + + }; + + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + FText ParameterTooltip = GetParameterTooltip(MainParam); + + TSharedRef HorizontalBox = SNew(SHorizontalBox); + FLinearColor BgColor(0.53f, 0.81f, 0.82f, 1.0f); // Sky Blue Backgroud color + + for (int32 Idx = 0; Idx < MainParam->Count; ++Idx) + { + if (!MainParam->Values.IsValidIndex(Idx) || !MainParam->Labels.IsValidIndex(Idx)) + continue; + + bool bPressed = MainParam->Values[Idx] > 0; + FText LabelText = FText::FromString(MainParam->Labels[Idx]); + + TSharedPtr Button; + + HorizontalBox->AddSlot().Padding(0).FillWidth(1.0f) + [ + SAssignNew(Button, SCheckBox) + .Style(FEditorStyle::Get(), "Property.ToggleButton.Middle") + .IsChecked(bPressed ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + .OnCheckStateChanged_Lambda([OnButtonStateChanged, Idx](ECheckBoxState NewState) + { + OnButtonStateChanged(NewState, Idx); + }) + .Content() + [ + SNew(STextBlock) + .Text(LabelText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + Button->SetColorAndOpacity(BgColor); + } + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetLabel(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray LabelParams; + if (!CastParameters(InParams, LabelParams)) + return; + + if (LabelParams.Num() <= 0) + return; + + UHoudiniParameterLabel* MainParam = LabelParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + + for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) + { + FString NextLabelString = MainParam->GetStringAtIndex(Index); + FText ParameterLabelText = FText::FromString(NextLabelString); + + TSharedPtr TextBlock; + + // Add Label UI. + VerticalBox->AddSlot().Padding(1, 2, 4, 2) + [ + SAssignNew(TextBlock, STextBlock).Text(ParameterLabelText) + ]; + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetToggle(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray ToggleParams; + if (!CastParameters(InParams, ToggleParams)) + return; + + if (ToggleParams.Num() <= 0) + return; + + UHoudiniParameterToggle* MainParam = ToggleParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FText ParameterLabelText = FText::FromString(MainParam->GetParameterLabel()); + + TSharedRef VerticalBox = SNew(SVerticalBox); + auto IsToggleCheckedLambda = [MainParam](int32 Index) + { + if (Index >= MainParam->GetNumValues()) + return ECheckBoxState::Unchecked; + + if (MainParam->GetValueAt(Index)) + return ECheckBoxState::Checked; + + return ECheckBoxState::Unchecked; + }; + + auto OnToggleCheckStateChanged = [MainParam, ToggleParams](ECheckBoxState NewState, int32 Index) + { + if (Index >= MainParam->GetNumValues()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterToggleChange", "Houdini Parameter Toggle: Changing value"), + MainParam->GetOuter(), true); + + bool bState = (NewState == ECheckBoxState::Checked); + + bool bChanged = false; + for (auto & Param : ToggleParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetValueAt(bState, Index)) + { + bChanged = true; + Param->MarkChanged(true); + } + } + + // Cancel the transaction if no parameter has actually been changed + if (!bChanged) + { + Transaction.Cancel(); + } + }; + + for (int32 Index = 0; Index < MainParam->GetTupleSize(); ++Index) + { + TSharedPtr< SCheckBox > CheckBox; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + [ + SAssignNew(CheckBox, SCheckBox) + .OnCheckStateChanged_Lambda([OnToggleCheckStateChanged, Index](ECheckBoxState NewState) { + OnToggleCheckStateChanged(NewState, Index); + + }) + .IsChecked_Lambda([IsToggleCheckedLambda, Index]() { + return IsToggleCheckedLambda(Index); + }) + .Content() + [ + SNew(STextBlock) + .Text(ParameterLabelText) + .ToolTipText(GetParameterTooltip(MainParam)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FileParams; + if (!CastParameters(InParams, FileParams)) + return; + + if (FileParams.Num() <= 0) + return; + + UHoudiniParameterFile* MainParam = FileParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + + FString FileTypeWidgetFilter = TEXT("All files (*.*)|*.*"); + if (!MainParam->GetFileFilters().IsEmpty()) + FileTypeWidgetFilter = FString::Printf(TEXT("%s files (%s)|%s"), *MainParam->GetFileFilters(), *MainParam->GetFileFilters(), *MainParam->GetFileFilters()); + + FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); + + auto UpdateCheckRelativePath = [MainParam](const FString & PickedPath) + { + UHoudiniAssetComponent* HoudiniAssetComponent = Cast(MainParam->GetOuter()); + if (MainParam->GetOuter() && !PickedPath.IsEmpty() && FPaths::IsRelative(PickedPath)) + { + // Check if the path is relative to the UE4 project + FString AbsolutePath = FPaths::ConvertRelativePathToFull(PickedPath); + if (FPaths::FileExists(AbsolutePath)) + { + return AbsolutePath; + } + + // Check if the path is relative to the asset + if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + { + if (HoudiniAssetComponent->HoudiniAsset && !HoudiniAssetComponent->HoudiniAsset->IsPendingKill()) + { + FString AssetFilePath = FPaths::GetPath(HoudiniAssetComponent->HoudiniAsset->AssetFileName); + if (FPaths::FileExists(AssetFilePath)) + { + FString UpdatedFileWidgetPath = FPaths::Combine(*AssetFilePath, *PickedPath); + if (FPaths::FileExists(UpdatedFileWidgetPath)) + { + return UpdatedFileWidgetPath; + } + } + } + } + } + + return PickedPath; + }; + + for (int32 Idx = 0; Idx < MainParam->GetTupleSize(); ++Idx) + { + FString FileWidgetPath = MainParam->GetValueAt(Idx); + FString FileWidgetBrowsePath = BrowseWidgetDirectory; + + if (!FileWidgetPath.IsEmpty()) + { + FString FileWidgetDirPath = FPaths::GetPath(FileWidgetPath); + if (!FileWidgetDirPath.IsEmpty()) + FileWidgetBrowsePath = FileWidgetDirPath; + } + + bool IsDirectoryPicker = MainParam->GetParameterType() == EHoudiniParameterType::FileDir; + bool bIsNewFile = !MainParam->IsReadOnly(); + + FText BrowseTooltip = LOCTEXT("FileButtonToolTipText", "Choose a file from this computer"); + if (IsDirectoryPicker) + BrowseTooltip = LOCTEXT("DirButtonToolTipText", "Choose a directory from this computer"); + + VerticalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.0f).MaxWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(SNewFilePathPicker) + .BrowseButtonImage(FEditorStyle::GetBrush("PropertyWindow.Button_Ellipsis")) + .BrowseButtonStyle(FEditorStyle::Get(), "HoverHintOnly") + .BrowseButtonToolTip(BrowseTooltip) + .BrowseDirectory(FileWidgetBrowsePath) + .BrowseTitle(LOCTEXT("PropertyEditorTitle", "File picker...")) + .FilePath(FileWidgetPath) + .FileTypeFilter(FileTypeWidgetFilter) + .IsNewFile(bIsNewFile) + .IsDirectoryPicker(IsDirectoryPicker) + .ToolTipText_Lambda([MainParam]() + { + // return the current param value as a tooltip + FString FileValue = MainParam ? MainParam->GetValueAt(0) : FString(); + return FText::FromString(FileValue); + }) + .OnPathPicked(FOnPathPicked::CreateLambda([MainParam, FileParams, UpdateCheckRelativePath, Idx](const FString & PickedPath) + { + if (MainParam->GetNumValues() <= Idx) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterFileChange", "Houdini Parameter File: Changing a file path"), + MainParam->GetOuter(), true); + + bool bChanged = false; + + for (auto & Param : FileParams) + { + if (!Param) + continue; + + Param->Modify(); + if (Param->SetValueAt(UpdateCheckRelativePath(PickedPath), Idx)) + { + bChanged = true; + Param->MarkChanged(true); + } + } + + // Cancel the transaction if no value has actually been changed + if (!bChanged) + { + Transaction.Cancel(); + } + })) + ] + ]; + + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + + +void +FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams) +{ + TArray ChoiceParams; + if (!CastParameters(InParams, ChoiceParams)) + return; + + if (ChoiceParams.Num() <= 0) + return; + + UHoudiniParameterChoice* MainParam = ChoiceParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + // Lambda for changing the parameter value + auto ChangeSelectionLambda = [ChoiceParams](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + if (!NewChoice.IsValid()) + return; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterChoiceChange", "Houdini Parameter Choice: Changing selection"), + ChoiceParams[0]->GetOuter()); + + const int32 NewIntValue = ChoiceParams[0]->GetIntValueFromLabel(*NewChoice); + + bool bChanged = false; + for (int Idx = 0; Idx < ChoiceParams.Num(); Idx++) + { + if (!ChoiceParams[Idx]) + continue; + + ChoiceParams[Idx]->Modify(); + if (ChoiceParams[Idx]->SetIntValue(NewIntValue)) + { + bChanged = true; + ChoiceParams[Idx]->MarkChanged(true); + ChoiceParams[Idx]->UpdateStringValueFromInt(); + } + } + + if (!bChanged) + { + // Cancel the transaction if no parameter was changed + Transaction.Cancel(); + } + }; + + // + MainParam->UpdateChoiceLabelsPtr(); + TArray>* OptionSource = MainParam->GetChoiceLabelsPtr(); + TSharedPtr IntialSelec; + if (OptionSource && OptionSource->IsValidIndex(MainParam->GetIntValueIndex())) + { + IntialSelec = (*OptionSource)[MainParam->GetIntValueIndex()]; + } + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + TSharedPtr< SComboBox< TSharedPtr< FString > > > ComboBox; + HorizontalBox->AddSlot().Padding( 2, 2, 5, 2 ) + [ + SAssignNew( ComboBox, SComboBox< TSharedPtr< FString > > ) + .OptionsSource(OptionSource) + .InitiallySelectedItem(IntialSelec) + .OnGenerateWidget_Lambda( + []( TSharedPtr< FString > InItem ) + { + return SNew(STextBlock).Text(FText::FromString(*InItem)); + }) + .OnSelectionChanged_Lambda( + [ChangeSelectionLambda](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) + { + ChangeSelectionLambda(NewChoice, SelectType); + }) + [ + SNew(STextBlock) + .Text_Lambda([MainParam]() { return FText::FromString(MainParam->GetLabel()); }) + .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ) + ] + ]; + + if ( ComboBox.IsValid() ) + ComboBox->SetEnabled( !MainParam->IsDisabled() ); + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH ); + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + +} + +void +FHoudiniParameterDetails::CreateWidgetSeparator(IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled) +{ + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam) + return; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + return; + + TSharedRef HorizontalBox = SNew(SCustomizedBox); + + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + + Row->WholeRowWidget.Widget = HorizontalBox; +} + +void +FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray OperatorPathParams; + if (!CastParameters(InParams, OperatorPathParams)) + return; + + if (OperatorPathParams.Num() <= 0) + return; + + UHoudiniParameterOperatorPath* MainParam = OperatorPathParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + UHoudiniInput* MainInput = MainParam->HoudiniInput.Get(); + if (!MainInput) + return; + + // Build an array of edited inputs for multi edition + TArray EditedInputs; + EditedInputs.Add(MainInput); + + // Add the corresponding inputs found in the other HAC + for (int LinkedIdx = 1; LinkedIdx < OperatorPathParams.Num(); LinkedIdx++) + { + UHoudiniInput* LinkedInput = OperatorPathParams[LinkedIdx]->HoudiniInput.Get(); + if (!LinkedInput || LinkedInput->IsPendingKill()) + continue; + + // Linked params should match the main param! If not try to find one that matches + if (!LinkedInput->Matches(*MainInput)) + continue; + + EditedInputs.Add(LinkedInput); + } + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); + if (!Row) + return; + + // Create the standard parameter name widget. + CreateNameWidget(Row, InParams, true); + + FHoudiniInputDetails::CreateWidget(HouParameterCategory, EditedInputs, Row); + + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetFloatRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // TODO: remove this once we have verified that updating the Points and CachedPoints arrays in + // TODO: HoudiniParameterTranslator::BuildAllParameters() (via RampParam->UpdatePointsArray()) is sufficient. + // // Parsing a float ramp: 1->(2->3->4)*->5 // + // switch (MainParam->GetParameterType()) + // { + // //*****State 1: Float Ramp*****// + // case EHoudiniParameterType::FloatRamp: + // { + // UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); + // if (FloatRampParameter) + // { + // CurrentRampFloat = FloatRampParameter; + // CurrentRampFloatPointsArray.Empty(); + // + // CurrentRampParameterList = InParams; + // + // FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + // CurrentRampRow = Row; + // } + // break; + // } + // + // case EHoudiniParameterType::Float: + // { + // UHoudiniParameterFloat* FloatParameter = Cast(MainParam); + // if (FloatParameter) + // { + // bool bCreateNewPoint = true; + // if (CurrentRampFloatPointsArray.Num() > 0) + // { + // UHoudiniParameterRampFloatPoint* LastPtInArr = CurrentRampFloatPointsArray.Last(); + // if (LastPtInArr && !LastPtInArr->ValueParentParm) + // bCreateNewPoint = false; + // } + // + // //*****State 2: Float Parameter (position)*****// + // if (bCreateNewPoint) + // { + // UHoudiniParameterRampFloatPoint* NewRampFloatPoint = nullptr; + // + // int32 PointIndex = CurrentRampFloatPointsArray.Num(); + // if (CurrentRampFloat->Points.IsValidIndex(PointIndex)) + // { + // + // // TODO: We should reuse existing point objects, if they exist. Currently + // // this causes results in unexpected behaviour in other parts of this detail code. + // // Give this code a bit of an overhaul at some point. + // // NewRampFloatPoint = CurrentRampFloat->Points[PointIndex]; + // } + // + // if (!NewRampFloatPoint) + // { + // // Create a new float ramp point, and add its pointer to the current float points buffer array. + // NewRampFloatPoint = NewObject< UHoudiniParameterRampFloatPoint >(CurrentRampFloat, FName(), CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); + // + // } + // + // CurrentRampFloatPointsArray.Add(NewRampFloatPoint); + // + // if (FloatParameter->GetNumberOfValues() <= 0) + // return; + // // Set the float ramp point's position parent parm, and value + // NewRampFloatPoint->PositionParentParm = FloatParameter; + // NewRampFloatPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); + // } + // //*****State 3: Float Parameter (value)*****// + // else + // { + // if (FloatParameter->GetNumberOfValues() <= 0) + // return; + // // Get the last point in the buffer array + // if (CurrentRampFloatPointsArray.Num() > 0) + // { + // // Set the last inserted float ramp point's float parent parm, and value + // UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); + // LastAddedFloatRampPoint->ValueParentParm = FloatParameter; + // LastAddedFloatRampPoint->SetValue(FloatParameter->GetValuesPtr()[0]); + // } + // } + // } + // + // break; + // } + // //*****State 4: Choice parameter*****// + // case EHoudiniParameterType::IntChoice: + // { + // UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); + // if (ChoiceParameter && CurrentRampFloatPointsArray.Num() > 0) + // { + // // Set the last inserted float ramp point's interpolation parent parm, and value + // UHoudiniParameterRampFloatPoint* LastAddedFloatRampPoint = CurrentRampFloatPointsArray.Last(); + // + // LastAddedFloatRampPoint->InterpolationParentParm = ChoiceParameter; + // LastAddedFloatRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + // + // // Set the index of this point in the multi parm. + // LastAddedFloatRampPoint->InstanceIndex = CurrentRampFloatPointsArray.Num() - 1; + // } + // + // + // //*****State 5: All ramp points have been parsed, finish!*****// + // if (CurrentRampFloatPointsArray.Num() >= (int32)CurrentRampFloat->MultiParmInstanceCount) + // { + // CurrentRampFloatPointsArray.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { + // return P1.Position < P2.Position; + // }); + // + // CurrentRampFloat->Points = CurrentRampFloatPointsArray; + // + // // Not caching, points are synced, update cached points + // if (!CurrentRampFloat->bCaching) + // { + // const int32 NumPoints = CurrentRampFloat->Points.Num(); + // CurrentRampFloat->CachedPoints.SetNum(NumPoints); + // for (int32 i = 0; i < NumPoints; ++i) + // { + // UHoudiniParameterRampFloatPoint* FromPoint = CurrentRampFloat->Points[i]; + // UHoudiniParameterRampFloatPoint* ToPoint = CurrentRampFloat->CachedPoints[i]; + // ToPoint = nullptr; + // check(FromPoint) + // if (!ToPoint) + // { + // ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampFloat, RF_NoFlags, CurrentRampFloat->GetMaskedFlags(RF_PropagateToSubObjects)); + // } + // else + // { + // ToPoint->CopyStateFrom(FromPoint, true); + // } + // CurrentRampFloat->CachedPoints[i] = ToPoint; + // } + // } + // + // CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampFloat, CurrentRampParameterList); + // + // CurrentRampFloat->SetDefaultValues(); + // + // CurrentRampFloat = nullptr; + // CurrentRampRow = nullptr; + // } + // + // break; + // } + // + // default: + // break; + // } + + //*****Float Ramp*****// + if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat* FloatRampParameter = Cast(MainParam); + if (FloatRampParameter) + { + CurrentRampFloat = FloatRampParameter; + FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + CreateWidgetRampPoints(HouParameterCategory, Row, FloatRampParameter, InParams); + //FloatRampParameter->SetDefaultValues(); + } + } +} + +void +FHoudiniParameterDetails::CreateWidgetColorRamp(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // TODO: remove this once we have verified that updating the Points and CachedPoints arrays in + // TODO: HoudiniParameterTranslator::BuildAllParameters() (via RampParam->UpdatePointsArray()) is sufficient. + // // Parsing a color ramp: 1->(2->3->4)*->5 // + // switch (MainParam->GetParameterType()) + // { + // //*****State 1: Color Ramp*****// + // case EHoudiniParameterType::ColorRamp: + // { + // UHoudiniParameterRampColor* RampColor = Cast(MainParam); + // if (RampColor) + // { + // CurrentRampColor = RampColor; + // CurrentRampColorPointsArray.Empty(); + // + // CurrentRampParameterList = InParams; + // + // FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + // CurrentRampRow = Row; + // } + // + // break; + // } + // //*****State 2: Float parameter*****// + // case EHoudiniParameterType::Float: + // { + // UHoudiniParameterFloat* FloatParameter = Cast(MainParam); + // if (FloatParameter) + // { + // // Create a new color ramp point, and add its pointer to the current color points buffer array. + // UHoudiniParameterRampColorPoint* NewRampColorPoint = nullptr; + // int32 PointIndex = CurrentRampColorPointsArray.Num(); + // + // if (CurrentRampColor->Points.IsValidIndex(PointIndex)) + // { + // NewRampColorPoint = CurrentRampColor->Points[PointIndex]; + // } + // + // if (!NewRampColorPoint) + // { + // NewRampColorPoint = NewObject< UHoudiniParameterRampColorPoint >(CurrentRampColor, FName(), CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); + // } + // + // CurrentRampColorPointsArray.Add(NewRampColorPoint); + // + // if (FloatParameter->GetNumberOfValues() <= 0) + // return; + // // Set the color ramp point's position parent parm, and value + // NewRampColorPoint->PositionParentParm = FloatParameter; + // NewRampColorPoint->SetPosition(FloatParameter->GetValuesPtr()[0]); + // } + // + // break; + // } + // //*****State 3: Color parameter*****// + // case EHoudiniParameterType::Color: + // { + // UHoudiniParameterColor* ColorParameter = Cast(MainParam); + // if (ColorParameter && CurrentRampColorPointsArray.Num() > 0) + // { + // // Set the last inserted color ramp point's color parent parm, and value + // UHoudiniParameterRampColorPoint* LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); + // LastAddedColorRampPoint->ValueParentParm = ColorParameter; + // LastAddedColorRampPoint->SetValue(ColorParameter->GetColorValue()); + // } + // + // break; + // } + // //*****State 4: Choice Parameter*****// + // case EHoudiniParameterType::IntChoice: + // { + // UHoudiniParameterChoice* ChoiceParameter = Cast(MainParam); + // if (ChoiceParameter) + // { + // // Set the last inserted color ramp point's interpolation parent parm, and value + // UHoudiniParameterRampColorPoint*& LastAddedColorRampPoint = CurrentRampColorPointsArray.Last(); + // + // LastAddedColorRampPoint->InterpolationParentParm = ChoiceParameter; + // LastAddedColorRampPoint->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValue())); + // + // // Set the index of this point in the multi parm. + // LastAddedColorRampPoint->InstanceIndex = CurrentRampColorPointsArray.Num() - 1; + // } + // + // + // //*****State 5: All ramp points have been parsed, finish!*****// + // if (CurrentRampColorPointsArray.Num() >= (int32)CurrentRampColor->MultiParmInstanceCount) + // { + // CurrentRampColorPointsArray.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) + // { + // return P1.Position < P2.Position; + // }); + // + // CurrentRampColor->Points = CurrentRampColorPointsArray; + // + // // Not caching, points are synced, update cached points + // + // if (!CurrentRampColor->bCaching) + // { + // const int32 NumPoints = CurrentRampColor->Points.Num(); + // CurrentRampColor->CachedPoints.SetNum(NumPoints); + // + // for (int32 i = 0; i < NumPoints; ++i) + // { + // UHoudiniParameterRampColorPoint* FromPoint = CurrentRampColor->Points[i]; + // UHoudiniParameterRampColorPoint* ToPoint = CurrentRampColor->CachedPoints[i]; + // + // if (!ToPoint) + // { + // ToPoint = FromPoint->DuplicateAndCopyState(CurrentRampColor, RF_NoFlags, CurrentRampColor->GetMaskedFlags(RF_PropagateToSubObjects)); + // } + // else + // { + // ToPoint->CopyStateFrom(FromPoint, true); + // } + // CurrentRampColor->CachedPoints[i] = ToPoint; + // } + // } + // + // + // CreateWidgetRampPoints(HouParameterCategory, CurrentRampRow, CurrentRampColor, CurrentRampParameterList); + // + // CurrentRampColor->SetDefaultValues(); + // + // CurrentRampColor = nullptr; + // CurrentRampRow = nullptr; + // } + // + // break; + // } + // + // default: + // break; + // } + + //*****Color Ramp*****// + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor* RampColor = Cast(MainParam); + if (RampColor) + { + CurrentRampColor = RampColor; + FDetailWidgetRow *Row = CreateWidgetRampCurveEditor(HouParameterCategory, InParams); + CreateWidgetRampPoints(HouParameterCategory, Row, RampColor, InParams); + //RampColor->SetDefaultValues(); + } + } + +} + + +FDetailWidgetRow* +FHoudiniParameterDetails::CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + if (InParams.Num() <= 0) + return nullptr; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam) + return nullptr; + + // Create a new detail row + FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, (TArray)InParams); + if (!Row) + return nullptr; + + Row->ValueWidget.Widget->SetEnabled(!MainParam->IsDisabled()); + + // Create the standard parameter name widget with an added autoupdate checkbox. + CreateNameWidgetWithAutoUpdate(Row, InParams, true); + + TSharedRef VerticalBox = SNew(SVerticalBox); + if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + UHoudiniParameterRampColor *RampColorParam = Cast(MainParam); + if (!RampColorParam) + return nullptr; + + TSharedPtr ColorGradientEditor; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SBorder) + .VAlign(VAlign_Fill) + [ + SAssignNew(ColorGradientEditor, SHoudiniColorRampCurveEditor) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + ] + ]; + + if (!ColorGradientEditor.IsValid()) + return nullptr; + + // Avoid showing tooltips inside of the curve editor + ColorGradientEditor->EnableToolTipForceField(true); + + CurrentRampParameterColorCurve = NewObject( + GetTransientPackage(), UHoudiniColorRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + + if (!CurrentRampParameterColorCurve) + return nullptr; + + CreatedColorRampCurves.Add(CurrentRampParameterColorCurve); + + // Add the ramp curve to root to avoid garabage collected. + CurrentRampParameterColorCurve->AddToRoot(); + + TArray ColorRampParameters; + CastParameters(InParams, ColorRampParameters); + + for (auto NextColorRamp : ColorRampParameters) + { + CurrentRampParameterColorCurve->ColorRampParameters.Add(NextColorRamp); + } + ColorGradientEditor->HoudiniColorRampCurve = CurrentRampParameterColorCurve; + + // Clear default curve points + for (int Idx = 0; Idx < 4; ++Idx) + { + FRichCurve& RichCurve = (CurrentRampParameterColorCurve->FloatCurves)[Idx]; + if (RichCurve.GetNumKeys() > 0) + RichCurve.Keys.Empty(); + } + ColorGradientEditor->SetCurveOwner(CurrentRampParameterColorCurve); + CreatedColorGradientEditors.Add(ColorGradientEditor); + } + else if(MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + UHoudiniParameterRampFloat *RampFloatParam = Cast(MainParam); + if (!RampFloatParam) + return nullptr; + + TSharedPtr FloatCurveEditor; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SNew(SBorder) + .VAlign(VAlign_Fill) + [ + SAssignNew(FloatCurveEditor, SHoudiniFloatRampCurveEditor) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + .HideUI(true) + .DrawCurve(true) + .ViewMinInput(0.0f) + .ViewMaxInput(1.0f) + .ViewMinOutput(0.0f) + .ViewMaxOutput(1.0f) + .TimelineLength(1.0f) + .AllowZoomOutput(false) + .ShowInputGridNumbers(false) + .ShowOutputGridNumbers(false) + .ShowZoomButtons(false) + .ZoomToFitHorizontal(false) + .ZoomToFitVertical(false) + .XAxisName(FString("X")) + .YAxisName(FString("Y")) + .ShowCurveSelector(false) + + ] + ]; + + if (!FloatCurveEditor.IsValid()) + return nullptr; + + // Avoid showing tooltips inside of the curve editor + FloatCurveEditor->EnableToolTipForceField(true); + + CurrentRampParameterFloatCurve = NewObject( + GetTransientPackage(), UHoudiniFloatRampCurve::StaticClass(), NAME_None, RF_Transactional | RF_Public); + + if (!CurrentRampParameterFloatCurve) + return nullptr; + + CreatedFloatRampCurves.Add(CurrentRampParameterFloatCurve); + + // Add the ramp curve to root to avoid garbage collected + CurrentRampParameterFloatCurve->AddToRoot(); + + TArray FloatRampParameters; + CastParameters(InParams, FloatRampParameters); + for (auto NextFloatRamp : FloatRampParameters) + { + CurrentRampParameterFloatCurve->FloatRampParameters.Add(NextFloatRamp); + } + FloatCurveEditor->HoudiniFloatRampCurve = CurrentRampParameterFloatCurve; + + FloatCurveEditor->SetCurveOwner(CurrentRampParameterFloatCurve, true); + CreatedFloatCurveEditors.Add(FloatCurveEditor); + } + + Row->ValueWidget.Widget = VerticalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + Row->ValueWidget.MaxDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + return Row; +} + + +void +FHoudiniParameterDetails::CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray& InParams) +{ + if (!Row || !InParameter) + return; + + if (InParams.Num() < 1) + return; + + UHoudiniParameter* MainParam = InParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + UHoudiniParameterRampFloat * MainFloatRampParameter = nullptr; + UHoudiniParameterRampColor * MainColorRampParameter = nullptr; + + TArray FloatRampParameterList; + TArray ColorRampParameterList; + if (MainParam->GetParameterType() == EHoudiniParameterType::FloatRamp) + { + MainFloatRampParameter = Cast(MainParam); + + if (!MainFloatRampParameter) + return; + + if (!CastParameters(InParams, FloatRampParameterList)) + return; + } + else if (MainParam->GetParameterType() == EHoudiniParameterType::ColorRamp) + { + MainColorRampParameter = Cast(MainParam); + + if (!MainColorRampParameter) + return; + + if (!CastParameters(InParams, ColorRampParameterList)) + return; + } + else + { + return; + } + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Lambda for computing the float point to be inserted + auto GetInsertFloatPointLambda = [MainFloatRampParameter]( + const int32& InsertAtIndex, + float& OutPosition, + float& OutValue, + EHoudiniRampInterpolationType& OutInterpType) mutable + { + if (!MainFloatRampParameter) + return; + + float PrevPosition = 0.0f; + float NextPosition = 1.0f; + + TArray &CurrentPoints = MainFloatRampParameter->Points; + TArray &CachedPoints = MainFloatRampParameter->CachedPoints; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + int32 NumPoints = 0; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + { + NumPoints = CurrentPoints.Num(); + } + else + { + MainFloatRampParameter->SetCaching(true); + NumPoints = CachedPoints.Num(); + } + + if (InsertAtIndex >= NumPoints) + { + // Insert at the end + if (NumPoints > 0) + { + UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + PrevPoint = CurrentPoints.Last(); + else + PrevPoint = CachedPoints.Last(); + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + } + } + else if (InsertAtIndex <= 0) + { + // Insert at the beginning + if (NumPoints > 0) + { + UHoudiniParameterRampFloatPoint* NextPoint = nullptr; + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + NextPoint = CurrentPoints[0]; + else + NextPoint = CachedPoints[0]; + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + OutInterpType = NextPoint->GetInterpolation(); + } + } + } + else + { + // Insert in the middle + if (NumPoints > 1) + { + UHoudiniParameterRampFloatPoint* PrevPoint = nullptr; + UHoudiniParameterRampFloatPoint* NextPoint = nullptr; + + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + { + PrevPoint = CurrentPoints[InsertAtIndex - 1]; + NextPoint = CurrentPoints[InsertAtIndex]; + } + else + { + PrevPoint = CachedPoints[InsertAtIndex - 1]; + NextPoint = CachedPoints[InsertAtIndex]; + } + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + } + + if (PrevPoint && NextPoint) + { + OutValue = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; + } + } + } + + OutPosition = (PrevPosition + NextPosition) / 2.0f; + }; + + + // Lambda for computing the color point to be inserted + auto GetInsertColorPointLambda = [MainColorRampParameter]( + const int32& InsertAtIndex, + float& OutPosition, + FLinearColor& OutColor, + EHoudiniRampInterpolationType& OutInterpType) mutable + { + if (!MainColorRampParameter) + return; + + float PrevPosition = 0.0f; + float NextPosition = 1.0f; + + TArray &CurrentPoints = MainColorRampParameter->Points; + TArray &CachedPoints = MainColorRampParameter->CachedPoints; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + int32 NumPoints = 0; + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NumPoints = CurrentPoints.Num(); + else + NumPoints = CachedPoints.Num(); + + if (InsertAtIndex >= NumPoints) + { + // Insert at the end + if (NumPoints > 0) + { + UHoudiniParameterRampColorPoint* PrevPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + PrevPoint = CurrentPoints.Last(); + else + PrevPoint = CachedPoints.Last(); + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + } + } + else if (InsertAtIndex <= 0) + { + // Insert at the beginning + if (NumPoints > 0) + { + UHoudiniParameterRampColorPoint* NextPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NextPoint = CurrentPoints[0]; + else + NextPoint = CachedPoints[0]; + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + OutInterpType = NextPoint->GetInterpolation(); + } + } + } + else + { + // Insert in the middle + if (NumPoints > 1) + { + UHoudiniParameterRampColorPoint* PrevPoint = nullptr; + UHoudiniParameterRampColorPoint* NextPoint = nullptr; + + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + { + PrevPoint = CurrentPoints[InsertAtIndex - 1]; + NextPoint = CurrentPoints[InsertAtIndex]; + } + else + { + PrevPoint = CachedPoints[InsertAtIndex - 1]; + NextPoint = CachedPoints[InsertAtIndex]; + } + + if (PrevPoint) + { + PrevPosition = PrevPoint->GetPosition(); + OutInterpType = PrevPoint->GetInterpolation(); + } + + if (NextPoint) + { + NextPosition = NextPoint->GetPosition(); + } + + if (PrevPoint && NextPoint) + { + OutColor = (PrevPoint->GetValue() + NextPoint->GetValue()) / 2.0; + } + } + } + + OutPosition = (PrevPosition + NextPosition) / 2.0f; + }; + + int32 RowIndex = 0; + auto InsertRampPoint_Lambda = [GetInsertColorPointLambda, GetInsertFloatPointLambda, &CategoryBuilder, bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, + UHoudiniParameterRampColor* MainRampColor, + TArray &RampFloatList, + TArray &RampColorList, + const int32& Index) mutable + { + if (MainRampFloat) + { + float InsertPosition = 0.0f; + float InsertValue = 1.0f; + EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; + + GetInsertFloatPointLambda(Index, InsertPosition, InsertValue, InsertInterp); + + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); + + for (auto & NextFloatRamp : RampFloatList) + { + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + CreateFloatRampParameterInsertEvent( + NextFloatRamp, InsertPosition, InsertValue, InsertInterp); + + NextFloatRamp->MarkChanged(true); + } + else + { + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject + (NextFloatRamp, UHoudiniParameterRampFloatPoint::StaticClass()); + NewCachedPoint->Position = InsertPosition; + NewCachedPoint->Value = InsertValue; + NewCachedPoint->Interpolation = InsertInterp; + + NextFloatRamp->CachedPoints.Add(NewCachedPoint); + NextFloatRamp->bCaching = true; + if (!bCookingEnabled) + { + // If cooking is not enabled, be sure to mark this parameter as changed + // so that it triggers an update once cooking is enabled again. + NextFloatRamp->MarkChanged(true); + } + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + { + CategoryBuilder.GetParentLayout().ForceRefreshDetails(); + } + + } + else if (MainRampColor) + { + float InsertPosition = 0.0f; + FLinearColor InsertColor = FLinearColor::Black; + EHoudiniRampInterpolationType InsertInterp = EHoudiniRampInterpolationType::LINEAR; + + GetInsertColorPointLambda(Index, InsertPosition, InsertColor, InsertInterp); + + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); + for (auto& NextColorRamp : RampColorList) + { + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + CreateColorRampParameterInsertEvent( + NextColorRamp, InsertPosition, InsertColor, InsertInterp); + + NextColorRamp->MarkChanged(true); + } + else + { + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject + (NextColorRamp, UHoudiniParameterRampColorPoint::StaticClass()); + NewCachedPoint->Position = InsertPosition; + NewCachedPoint->Value = InsertColor; + NewCachedPoint->Interpolation = InsertInterp; + + NextColorRamp->CachedPoints.Add(NewCachedPoint); + NextColorRamp->bCaching = true; + if (!bCookingEnabled) + NextColorRamp->MarkChanged(true); + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + auto DeleteRampPoint_Lambda = [bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, + UHoudiniParameterRampColor* MainRampColor, + TArray &FloatRampList, + TArray &ColorRampList, + const int32& Index) mutable + { + if (MainRampFloat) + { + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(FloatRampList); + + for (auto& NextFloatRamp : FloatRampList) + { + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextFloatRamp->Points.Num() == 0) + return; + + UHoudiniParameterRampFloatPoint* PointToDelete = nullptr; + + if (Index == -1) + PointToDelete = NextFloatRamp->Points.Last(); + else if (NextFloatRamp->Points.IsValidIndex(Index)) + PointToDelete = NextFloatRamp->Points[Index]; + + if (!PointToDelete) + return; + + const int32 & InstanceIndexToDelete = PointToDelete->InstanceIndex; + + CreateFloatRampParameterDeleteEvent(NextFloatRamp, InstanceIndexToDelete); + NextFloatRamp->MarkChanged(true); + } + else + { + if (NextFloatRamp->CachedPoints.Num() == 0) + return; + + if (Index == -1) + NextFloatRamp->CachedPoints.Pop(); + else if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) + NextFloatRamp->CachedPoints.RemoveAt(Index); + else + return; + + NextFloatRamp->bCaching = true; + if (!bCookingEnabled) + NextFloatRamp->MarkChanged(true); + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); + } + } + else + { + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(ColorRampList); + + for (auto& NextColorRamp : ColorRampList) + { + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextColorRamp->Points.Num() == 0) + return; + + UHoudiniParameterRampColorPoint* PointToRemove = nullptr; + + if (Index == -1) + PointToRemove = NextColorRamp->Points.Last(); + else if (NextColorRamp->Points.IsValidIndex(Index)) + PointToRemove = NextColorRamp->Points[Index]; + + if (!PointToRemove) + return; + + const int32 & InstanceIndexToDelete = PointToRemove->InstanceIndex; + + CreateColorRampParameterDeleteEvent(NextColorRamp, InstanceIndexToDelete); + + NextColorRamp->MarkChanged(true); + } + else + { + if (NextColorRamp->CachedPoints.Num() == 0) + return; + + if (Index == -1) + NextColorRamp->CachedPoints.Pop(); + else if (NextColorRamp->CachedPoints.IsValidIndex(Index)) + NextColorRamp->CachedPoints.RemoveAt(Index); + else + return; + + NextColorRamp->bCaching = true; + if (!bCookingEnabled) + NextColorRamp->MarkChanged(true); + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + { + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + + TSharedRef VerticalBox = StaticCastSharedRef(Row->ValueWidget.Widget); + + TSharedPtr GridPanel; + VerticalBox->AddSlot() + .Padding(2, 2, 5, 2) + .AutoHeight() + [ + SAssignNew(GridPanel, SUniformGridPanel) + ]; + + //AllUniformGridPanels.Add(GridPanel.Get()); + + GridPanel->SetSlotPadding(FMargin(2.f, 2.f, 5.f, 3.f)); + GridPanel->AddSlot(0, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Position"))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + FString ValueString = TEXT("Value"); + if (!MainFloatRampParameter) + ValueString = TEXT("Color"); + + GridPanel->AddSlot(1, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(ValueString)) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + GridPanel->AddSlot(2, RowIndex) + [ + SNew(STextBlock) + .Text(FText::FromString(TEXT("Interp."))) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + + GridPanel->AddSlot(3, RowIndex) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton( + FSimpleDelegate::CreateLambda([InsertRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, bCookingEnabled]() mutable + { + int32 InsertAtIndex = -1; + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + InsertAtIndex = MainFloatRampParameter->Points.Num(); + else + InsertAtIndex = MainFloatRampParameter->CachedPoints.Num(); + } + else if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + InsertAtIndex = MainColorRampParameter->Points.Num(); + else + InsertAtIndex = MainColorRampParameter->CachedPoints.Num(); + } + + InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, InsertAtIndex); + }), + LOCTEXT("AddRampPoint", "Add a ramp point to the end"), true) + ] + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeRemoveButton( + FSimpleDelegate::CreateLambda([DeleteRampPoint_Lambda, MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList]() mutable + { + DeleteRampPoint_Lambda( + MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, -1); + }), + LOCTEXT("DeleteRampPoint", "Delete the last ramp point"), true) + ] + + ]; + + EUnit Unit = EUnit::Unspecified; + TSharedPtr> paramTypeInterface; + paramTypeInterface = MakeShareable(new TNumericUnitTypeInterface(Unit)); + + int32 PointCount = 0; + // Use Synced points on auto update mode + // Use Cached points on manual update mode + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + PointCount = MainFloatRampParameter->Points.Num(); + else + PointCount = MainFloatRampParameter->CachedPoints.Num(); + } + + if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate()) + PointCount = MainColorRampParameter->Points.Num(); + else + PointCount = MainColorRampParameter->CachedPoints.Num(); + } + + // Lambda function for changing a ramp point + auto OnPointChangeCommit = [bCookingEnabled]( + UHoudiniParameterRampFloat* MainRampFloat, UHoudiniParameterRampColor* MainRampColor, + UHoudiniParameterRampFloatPoint* MainRampFloatPoint, UHoudiniParameterRampColorPoint* MainRampColorPoint, + TArray &RampFloatList, TArray &RampColorList, + const int32& Index, const FString& ChangedDataName, + const float& NewPosition, const float& NewFloat, + const FLinearColor& NewColor, + const EHoudiniRampInterpolationType& NewInterpType) mutable + { + if (MainRampFloat && MainRampFloatPoint) + { + if (ChangedDataName == FString("position") && MainRampFloatPoint->GetPosition() == NewPosition) + return; + if (ChangedDataName == FString("value") && MainRampFloatPoint->GetValue() == NewFloat) + return; + if (ChangedDataName == FString("interp") && MainRampFloatPoint->GetInterpolation() == NewInterpType) + return; + + FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(RampFloatList); + for (auto NextFloatRamp : RampFloatList) + { + if (!NextFloatRamp) + continue; + + if (NextFloatRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextFloatRamp->Points.IsValidIndex(Index)) + { + UHoudiniParameterRampFloatPoint* CurrentFloatRampPoint = NextFloatRamp->Points[Index]; + if (!CurrentFloatRampPoint) + continue; + + if (ChangedDataName == FString("position")) + { + if (!CurrentFloatRampPoint->PositionParentParm) + continue; + + CurrentFloatRampPoint->SetPosition(NewPosition); + CurrentFloatRampPoint->PositionParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("value")) + { + if (!CurrentFloatRampPoint->PositionParentParm) + continue; + + CurrentFloatRampPoint->SetValue(NewFloat); + CurrentFloatRampPoint->ValueParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("interp")) + { + if (!CurrentFloatRampPoint->InterpolationParentParm) + continue; + + CurrentFloatRampPoint->SetInterpolation(NewInterpType); + CurrentFloatRampPoint->InterpolationParentParm->MarkChanged(true); + } + } + else + { + int32 IdxInEventsArray = Index - NextFloatRamp->Points.Num(); + if (NextFloatRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = NextFloatRamp->ModificationEvents[IdxInEventsArray]; + if (!Event) + continue; + + if (ChangedDataName == FString("position")) + { + Event->InsertPosition = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + Event->InsertFloat = NewFloat; + } + else if (ChangedDataName == FString("interp")) + { + Event->InsertInterpolation = NewInterpType; + } + } + } + } + else + { + if (NextFloatRamp->CachedPoints.IsValidIndex(Index)) + { + UHoudiniParameterRampFloatPoint* CachedPoint = NextFloatRamp->CachedPoints[Index]; + + if (!CachedPoint) + continue; + + if (ChangedDataName == FString("position")) + { + CachedPoint->Position = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + CachedPoint->Value = NewFloat; + } + else if (ChangedDataName == FString("interp")) + { + CachedPoint->Interpolation = NewInterpType; + } + + NextFloatRamp->bCaching = true; + } + } + + if (!(MainRampFloat->IsAutoUpdate() && bCookingEnabled)) + FHoudiniEngineUtils::UpdateEditorProperties(MainRampFloat, true); + } + } + else if (MainRampColor && MainRampColorPoint) + { + if (ChangedDataName == FString("position") && MainRampColorPoint->GetPosition() == NewPosition) + return; + + if (ChangedDataName == FString("value") && MainRampColorPoint->GetValue() == NewColor) + return; + + if (ChangedDataName == FString("interp") && MainRampColorPoint->GetInterpolation() == NewInterpType) + return; + + FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(RampColorList); + for (auto NextColorRamp : RampColorList) + { + if (!NextColorRamp) + continue; + + if (NextColorRamp->IsAutoUpdate() && bCookingEnabled) + { + if (NextColorRamp->Points.IsValidIndex(Index)) + { + UHoudiniParameterRampColorPoint* CurrentColorRampPoint = NextColorRamp->Points[Index]; + if (!CurrentColorRampPoint) + continue; + + if (ChangedDataName == FString("position")) + { + if (!CurrentColorRampPoint->PositionParentParm) + continue; + + CurrentColorRampPoint->SetPosition(NewPosition); + CurrentColorRampPoint->PositionParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("value")) + { + if (!CurrentColorRampPoint->PositionParentParm) + continue; + + CurrentColorRampPoint->SetValue(NewColor); + CurrentColorRampPoint->ValueParentParm->MarkChanged(true); + } + else if (ChangedDataName == FString("interp")) + { + if (!CurrentColorRampPoint->InterpolationParentParm) + continue; + + CurrentColorRampPoint->SetInterpolation(NewInterpType); + CurrentColorRampPoint->InterpolationParentParm->MarkChanged(true); + } + } + else + { + int32 IdxInEventsArray = Index - NextColorRamp->Points.Num(); + if (NextColorRamp->ModificationEvents.IsValidIndex(IdxInEventsArray)) + { + UHoudiniParameterRampModificationEvent* Event = NextColorRamp->ModificationEvents[IdxInEventsArray]; + if (!Event) + continue; + + if (ChangedDataName == FString("position")) + { + Event->InsertPosition = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + Event->InsertColor = NewColor; + } + else if (ChangedDataName == FString("interp")) + { + Event->InsertInterpolation = NewInterpType; + } + + } + } + } + else + { + if (NextColorRamp->CachedPoints.IsValidIndex(Index)) + { + UHoudiniParameterRampColorPoint* CachedPoint = NextColorRamp->CachedPoints[Index]; + + if (!CachedPoint) + continue; + + if (ChangedDataName == FString("position")) + { + CachedPoint->Position = NewPosition; + } + else if (ChangedDataName == FString("value")) + { + CachedPoint->Value = NewColor; + } + else if (ChangedDataName == FString("interp")) + { + CachedPoint->Interpolation = NewInterpType; + } + + NextColorRamp->bCaching = true; + } + } + + if (!(MainRampColor->IsAutoUpdate() && bCookingEnabled)) + FHoudiniEngineUtils::UpdateEditorProperties(MainRampColor, true); + } + } + }; + + for (int32 Index = 0; Index < PointCount; ++Index) + { + UHoudiniParameterRampFloatPoint* NextFloatRampPoint = nullptr; + UHoudiniParameterRampColorPoint* NextColorRampPoint = nullptr; + + if (MainFloatRampParameter) + { + if (MainFloatRampParameter->IsAutoUpdate() && bCookingEnabled) + NextFloatRampPoint = MainFloatRampParameter->Points[Index]; + else + NextFloatRampPoint = MainFloatRampParameter->CachedPoints[Index]; + } + if (MainColorRampParameter) + { + if (MainColorRampParameter->IsAutoUpdate() && bCookingEnabled) + NextColorRampPoint = MainColorRampParameter->Points[Index]; + else + NextColorRampPoint = MainColorRampParameter->CachedPoints[Index]; + } + + if (!NextFloatRampPoint && !NextColorRampPoint) + continue; + + RowIndex += 1; + + float CurPos = 0.f; + if (NextFloatRampPoint) + CurPos = NextFloatRampPoint->Position; + else + CurPos = NextColorRampPoint->Position; + + + GridPanel->AddSlot(0, RowIndex) + [ + SNew(SNumericEntryBox) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Value(CurPos) + .OnValueChanged_Lambda([](float Val) {}) + .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("position"), + Val, float(-1.0), + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .OnBeginSliderMovement_Lambda([]() {}) + .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("position"), + Val, float(-1.0), + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .SliderExponent(1.0f) + .TypeInterface(paramTypeInterface) + ]; + + if (NextFloatRampPoint) + { + GridPanel->AddSlot(1, RowIndex) + [ + SNew(SNumericEntryBox< float >) + .AllowSpin(true) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .Value(NextFloatRampPoint->Value) + .OnValueChanged_Lambda([](float Val){}) + .OnValueCommitted_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](float Val, ETextCommit::Type TextCommitType) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), Val, + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .OnBeginSliderMovement_Lambda([]() {}) + .OnEndSliderMovement_Lambda([OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](const float Val) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), Val, + FLinearColor(), + EHoudiniRampInterpolationType::LINEAR); + }) + .SliderExponent(1.0f) + .TypeInterface(paramTypeInterface) + ]; + } + else if (NextColorRampPoint) + { + auto OnColorChangeLambda = [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, ColorRampParameterList, Index](FLinearColor InColor) mutable + { + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("value"), + float(-1.0), float(-1.0), + InColor, + EHoudiniRampInterpolationType::LINEAR); + }; + + // Add color picker UI. + //TSharedPtr ColorBlock; + GridPanel->AddSlot(1, RowIndex) + [ + SNew(SColorBlock) + .Color(NextColorRampPoint->Value) + .OnMouseButtonDown( FPointerEventHandler::CreateLambda( + [NextColorRampPoint, OnColorChangeLambda](const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) + { + if (MouseEvent.GetEffectingButton() != EKeys::LeftMouseButton) + return FReply::Unhandled(); + + FColorPickerArgs PickerArgs; + PickerArgs.bUseAlpha = true; + PickerArgs.DisplayGamma = TAttribute< float >::Create( + TAttribute< float >::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma)); + PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateLambda(OnColorChangeLambda); + FLinearColor InitColor = NextColorRampPoint->Value; + PickerArgs.InitialColorOverride = InitColor; + PickerArgs.bOnlyRefreshOnOk = true; + OpenColorPicker(PickerArgs); + return FReply::Handled(); + })) + ]; + } + + int32 CurChoice = 0; + if (NextFloatRampPoint) + CurChoice = (int)NextFloatRampPoint->Interpolation; + else + CurChoice = (int)NextColorRampPoint->Interpolation; + + TSharedPtr >> ComboBoxCurveMethod; + GridPanel->AddSlot(2, RowIndex) + [ + SAssignNew(ComboBoxCurveMethod, SComboBox>) + .OptionsSource(FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels()) + .InitiallySelectedItem((*FHoudiniEngineEditor::Get().GetHoudiniParameterRampInterpolationMethodLabels())[CurChoice]) + .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) + { + FText ChoiceEntryText = FText::FromString(*ChoiceEntry); + return SNew(STextBlock) + .Text(ChoiceEntryText) + .ToolTipText(ChoiceEntryText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); + }) + .OnSelectionChanged_Lambda( + [OnPointChangeCommit, MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, FloatRampParameterList, + ColorRampParameterList, Index](TSharedPtr NewChoice, ESelectInfo::Type SelectType) mutable + { + EHoudiniRampInterpolationType NewInterpType = UHoudiniParameter::GetHoudiniInterpMethodFromString(*NewChoice.Get()); + + OnPointChangeCommit(MainFloatRampParameter, MainColorRampParameter, + NextFloatRampPoint, NextColorRampPoint, + FloatRampParameterList, ColorRampParameterList, + Index, FString("interp"), + float(-1.0), float(-1.0), + FLinearColor(), + NewInterpType); + }) + [ + SNew(STextBlock) + .Text_Lambda([NextFloatRampPoint, NextColorRampPoint]() + { + EHoudiniRampInterpolationType CurInterpType = EHoudiniRampInterpolationType::InValid; + if (NextFloatRampPoint) + CurInterpType = NextFloatRampPoint->GetInterpolation(); + else + CurInterpType = NextColorRampPoint->GetInterpolation(); + + return FText::FromString(UHoudiniParameter::GetStringFromHoudiniInterpMethod(CurInterpType)); + }) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + ]; + + GridPanel->AddSlot(3, RowIndex) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeAddButton( FSimpleDelegate::CreateLambda( + [InsertRampPoint_Lambda, MainFloatRampParameter, + MainColorRampParameter, FloatRampParameterList, + ColorRampParameterList, Index]() mutable + { + InsertRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); + }), + LOCTEXT("AddRampPoint", "Add a ramp point before this point"), true) + ] + + SHorizontalBox::Slot() + .Padding(3.f, 0.f) + .MaxWidth(35.f) + .AutoWidth() + [ + PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateLambda( + [DeleteRampPoint_Lambda, MainFloatRampParameter, + MainColorRampParameter, FloatRampParameterList, + ColorRampParameterList, Index]() mutable + { + DeleteRampPoint_Lambda(MainFloatRampParameter, MainColorRampParameter, FloatRampParameterList, ColorRampParameterList, Index); + }), + LOCTEXT("DeleteFloatRampPoint", "Delete this ramp point"), true) + ] + ]; + + if (MainFloatRampParameter && CurrentRampParameterFloatCurve) + { + ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextFloatRampPoint->GetInterpolation()); + FRichCurve & RichCurve = CurrentRampParameterFloatCurve->FloatCurve; + FKeyHandle const KeyHandle = RichCurve.AddKey(NextFloatRampPoint->GetPosition(), NextFloatRampPoint->GetValue()); + RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); + } + + if (MainColorRampParameter && CurrentRampParameterColorCurve) + { + ERichCurveInterpMode RichCurveInterpMode = UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(NextColorRampPoint->GetInterpolation()); + for (int32 CurveIdx = 0; CurveIdx < 4; ++CurveIdx) + { + FRichCurve & RichCurve = CurrentRampParameterColorCurve->FloatCurves[CurveIdx]; + + FKeyHandle const KeyHandle = RichCurve.AddKey(NextColorRampPoint->GetPosition(), NextColorRampPoint->GetValue().Component(CurveIdx)); + RichCurve.SetKeyInterpMode(KeyHandle, RichCurveInterpMode); + } + } + } + + if (MainFloatRampParameter) + GridPanel->SetEnabled(!MainFloatRampParameter->IsDisabled()); + + if (MainColorRampParameter) + GridPanel->SetEnabled(!MainColorRampParameter->IsDisabled()); +} + +void +FHoudiniParameterDetails::CreateWidgetFolderList(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FolderListParams; + if (!CastParameters(InParams, FolderListParams)) + return; + + if (FolderListParams.Num() <= 0) + return; + + UHoudiniParameterFolderList* MainParam = FolderListParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Add this folder list to the folder map + AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); + + MainParam->GetTabs().Empty(); + + // A folder list will be followed by all its child folders, + // so set the CurrentFolderListSize to the tuple size, we'll process such many folder parameters right after + CurrentFolderListSize = MainParam->GetTupleSize(); + + if (MainParam->IsDirectChildOfMultiParm()) + MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); + + if (CurrentFolderListSize <= 0) // There should not be empty folder list, this will not happen normally + return; + + // The following folders belong to current folder list + CurrentFolderList = MainParam; + + // If the tab is either a tabs or radio button and the parameter is visible + if (MainParam->IsTabMenu() && MainParam->ShouldDisplay()) + { + // Set the current tabs to be not shown by default now. CreateWidgetTab will decide if the tabs is shown. + CurrentFolderList->SetTabsShown(false); + + // Create a row to hold tab buttons if the folder list is a tabs or radio button + + // CreateNestedRow does not actually create a row for tabs, it is responsible to prune the folder stack. + // ( CreateWidgetTab will be responsible to create a row according to the visibility of its outer level folders ) + FDetailWidgetRow* TabRow = CreateNestedRow(HouParameterCategory, InParams, false); + + } + + // When see a folder list, go depth first search at this step. + // Push an empty queue to the stack. + FolderStack.Add(TArray()); +} + + +void +FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray FolderParams; + if (!CastParameters(InParams, FolderParams)) + return; + + if (FolderParams.Num() <= 0) + return; + + UHoudiniParameterFolder* MainParam = FolderParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen + return; + + // If a folder is invisible, its children won't be listed by HAPI. + // So just reduce FolderListSize by 1, reduce the child counter of its parent folder by 1 if necessary, + // and prune the stack in such case. + if (!MainParam->IsVisible()) + { + CurrentFolderListSize -= 1; + + if (CurrentFolderListSize == 0) + { + if (FolderStack.Num() > 1) + { + TArray &ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + ParentFolderQueue[0]->GetChildCounter() -= 1; + } + + PruneStack(); + } + + return; + } + + // We expect 'TupleSize' children param of this folder after finish processing all the child folders of cur folderlist + MainParam->ResetChildCounter(); + + // Add this folder to the folder map + AllFoldersAndFolderLists.Add(MainParam->GetParmId(), MainParam); + + // Set the parent param to current folderList. + // it was parent multiparm's id if this folder is a child of a multiparms. + // This will cause problem if the folder is inside of a multiparm + MainParam->SetParentParmId(CurrentFolderList->GetParmId()); + + + // Case 1: The folder is a direct child of a multiparm. + if (MainParam->IsDirectChildOfMultiParm()) + { + if (FolderStack.Num() <= 0) // This should not happen + return; + + // Get its parent multiparm first + UHoudiniParameterMultiParm* ParentMultiParm = nullptr; + { + UHoudiniParameterFolderList * ParentFolderList = nullptr; + if (!AllFoldersAndFolderLists.Contains(MainParam->GetParentParmId())) + return; + + ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); + + if (!ParentFolderList) + return; + + if (AllMultiParms.Contains(ParentFolderList->GetParentParmId())) + ParentMultiParm = AllMultiParms[ParentFolderList->GetParentParmId()]; + + if (!ParentMultiParm) // This should not happen + return; + } + + bool bShown = ParentMultiParm->IsShown(); + + // Case 1-1: The folder is NOT tabs + if (!MainParam->IsTab()) + { + bShown = MainParam->IsExpanded() && bShown; + + // If the parent multiparm is shown. + if (ParentMultiParm->IsShown()) + { + FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderHeaderRow, InParams); + } + } + // Case 1-2: The folder IS tabs. + else + { + CreateWidgetTab(HouParameterCategory, MainParam, ParentMultiParm->IsShown()); + } + + // Push the folder to the queue if it is not a tab folder + // This step is handled by CreateWidgetTab() if it is tabs + if ((!MainParam->IsTab() || !ParentMultiParm->IsShown()) && MainParam->GetTupleSize() > 0) + { + TArray & MyQueue = FolderStack.Last(); + MainParam->SetIsContentShown(bShown); + MyQueue.Add(MainParam); + } + } + + // Case 2: The folder is NOT a direct child of a multiparm. + else + { + // Case 2-1: The folder is in another folder. + if (FolderStack.Num() > 1 && CurrentFolderListSize > 0) + { + TArray & MyFolderQueue = FolderStack.Last(); + TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + + if (ParentFolderQueue.Num() <= 0) //This should happen + return; + + // Peek the folder queue of the last layer to get its parent folder parm. + bool ParentFolderVisible = ParentFolderQueue[0]->IsContentShown(); + + // If this folder is expanded (selected if is tabs) + bool bExpanded = ParentFolderVisible; + + // Case 2-1-1: The folder is NOT in a tab menu. + if (!MainParam->IsTab()) + { + bExpanded &= MainParam->IsExpanded(); + + // The parent folder is visible. + if (ParentFolderVisible) + { + // Add the folder header UI. + FDetailWidgetRow* FolderHeaderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderHeaderRow, InParams); + } + + MainParam->SetIsContentShown(bExpanded); + MyFolderQueue.Add(MainParam); + } + // Case 2-1-2: The folder IS in a tab menu. + else + { + bExpanded &= MainParam->IsChosen(); + + CreateWidgetTab(HouParameterCategory, MainParam, ParentFolderVisible); + } + } + // Case 2-2: The folder is in the root. + else + { + bool bExpanded = true; + + // Case 2-2-1: The folder is NOT under a tab menu. + if (!MainParam->IsTab()) + { + if (FolderStack.Num() <= 0) // This will not happen + return; + + // Create Folder header under root. + FDetailWidgetRow* FolderRow = CreateNestedRow(HouParameterCategory, InParams, false); + CreateFolderHeaderUI(FolderRow, InParams); + + if (FolderStack.Num() == 0) // This should not happen + return; + + TArray& MyFolderQueue = FolderStack[0]; + bExpanded &= MainParam->IsExpanded(); + MainParam->SetIsContentShown(bExpanded); + MyFolderQueue.Add(MainParam); + } + // Case 2-2-2: The folder IS under a tab menu. + else + { + // Tabs in root is always visible + CreateWidgetTab(HouParameterCategory, MainParam, true); + } + } + } + + + CurrentFolderListSize -= 1; + + // Prune the stack if finished parsing current folderlist + if (CurrentFolderListSize == 0) + { + if (FolderStack.Num() > 1 && !MainParam->IsDirectChildOfMultiParm()) + { + TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; + if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + ParentFolderQueue[0]->GetChildCounter() -= 1; + } + + PruneStack(); + + CurrentFolderList = nullptr; + } +} + +void +FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray &InParams) +{ + if (!HeaderRow) // The folder is invisible. + return; + + TArray FolderParams; + if (!CastParameters(InParams, FolderParams)) + return; + + if (FolderParams.Num() <= 0) + return; + + UHoudiniParameterFolder* MainParam = FolderParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return; + + TSharedPtr VerticalBox; + + FString LabelStr = MainParam->GetParameterLabel(); + + TSharedPtr HorizontalBox; + TSharedPtr ExpanderArrow; + TSharedPtr ExpanderImage; + + HeaderRow->NameWidget.Widget = SAssignNew(HorizontalBox, SCustomizedBox); + + HorizontalBox->AddIndentation(MainParam, AllMultiParms, AllFoldersAndFolderLists); + HorizontalBox->DividerLinePositions = DividerLinePositions; + HorizontalBox->SetHoudiniParameter(InParams); + + if (MainParam->IsDirectChildOfMultiParm() && MainParam->GetChildIndex() == 1) + { + int32 CurrentMultiParmInstanceIndex = 0; + if (MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + { + MultiParmInstanceIndices[MainParam->GetParentParmId()] += 1; + CurrentMultiParmInstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + LabelStr = LabelStr + TEXT(" (") + FString::FromInt(CurrentMultiParmInstanceIndex) + TEXT(")"); + } + + CreateWidgetMultiParmObjectButtons(HorizontalBox, InParams); + } + + HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SAssignNew(ExpanderArrow, SButton) + .ButtonStyle(FEditorStyle::Get(), "NoBorder") + .ClickMethod(EButtonClickMethod::MouseDown) + .Visibility(EVisibility::Visible) + .OnClicked_Lambda([=]() + { + MainParam->ExpandButtonClicked(); + + FHoudiniEngineUtils::UpdateEditorProperties(MainParam, true); + + return FReply::Handled(); + }) + [ + SAssignNew(ExpanderImage, SImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ]; + + + FText LabelText = FText::FromString(LabelStr); + + HorizontalBox->AddSlot().Padding(1.0f).VAlign(VAlign_Center).AutoWidth() + [ + SNew(STextBlock) + .Text(LabelText) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ]; + + TWeakPtr WeakExpanderArrow(ExpanderArrow); + ExpanderImage->SetImage( + TAttribute::Create( + TAttribute::FGetter::CreateLambda([MainParam, WeakExpanderArrow]() + { + FName ResourceName; + TSharedPtr ExpanderArrowPtr = WeakExpanderArrow.Pin(); + if (MainParam->IsExpanded()) + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Expanded_Hovered" : "TreeArrow_Expanded"; + } + else + { + ResourceName = ExpanderArrowPtr.IsValid() && ExpanderArrowPtr->IsHovered() ? "TreeArrow_Collapsed_Hovered" : "TreeArrow_Collapsed"; + } + + return FEditorStyle::GetBrush(ResourceName); + } + ) + )); + + if(MainParam->GetFolderType() == EHoudiniFolderParameterType::Simple) + ExpanderArrow->SetEnabled(false); + +} + +void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InFolder, const bool& bIsShown) +{ + if (!InFolder || InFolder->IsPendingKill() || !CurrentFolderList) + return; + + if (FolderStack.Num() <= 0) // error state + return; + + TArray & FolderQueue = FolderStack.Last(); + + // Cache all tabs of current tab folder list. + CurrentFolderList->AddTabFolder(InFolder); + + // If the tabs is not shown, just push the folder param into the queue. + if (!bIsShown) + { + InFolder->SetIsContentShown(bIsShown); + FolderQueue.Add(InFolder); + return; + } + + // tabs currently being processed + CurrentTabs.Add(InFolder); + + if (CurrentFolderListSize > 1) + return; + + // The tabs belong to current folder list + UHoudiniParameterFolderList* CurrentTabMenuFolderList = CurrentFolderList; + + // Create a row (UI) for current tabs + TSharedPtr HorizontalBox; + FDetailWidgetRow &Row = HouParameterCategory.AddCustomRow(FText::GetEmpty()) + [ + SAssignNew(HorizontalBox, SCustomizedBox) + ]; + + // Put current tab folder list param into an array + TArray CurrentTabMenuFolderListArr; + CurrentTabMenuFolderListArr.Add(CurrentTabMenuFolderList); + + HorizontalBox->SetHoudiniParameter(CurrentTabMenuFolderListArr); + DividerLinePositions.Add(HorizontalBox->AddIndentation(InFolder, AllMultiParms, AllFoldersAndFolderLists)); + HorizontalBox->DividerLinePositions = DividerLinePositions; + + float DesiredHeight = 0.0f; + float DesiredWidth = 0.0f; + + // Process all tabs of current folder list at once when done. + + for (auto & CurTab : CurrentTabs) + { + if (!CurTab || CurTab->IsPendingKill()) + continue; + + CurTab->SetIsContentShown(CurTab->IsChosen()); + FolderQueue.Add(CurTab); + + auto OnTabClickedLambda = [CurrentTabMenuFolderList, CurTab, &HouParameterCategory]() + { + if (CurrentTabMenuFolderList) + { + if (!CurrentTabMenuFolderList->bIsTabMenu || CurrentTabMenuFolderList->TabFolders.Num() <= 0) + return FReply::Handled(); + + if (CurTab->IsChosen()) + return FReply::Handled(); + + CurTab->SetChosen(true); + + for (UHoudiniParameterFolder* NextFolder : CurrentTabMenuFolderList->TabFolders) + { + if (CurTab->GetParmId() != NextFolder->GetParmId() && NextFolder->IsChosen()) + NextFolder->SetChosen(false); + } + + HouParameterCategory.GetParentLayout().ForceRefreshDetails(); + } + + return FReply::Handled(); + }; + + FString FolderLabelString = TEXT(" ") + CurTab->GetParameterLabel(); + if (CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio) + FolderLabelString = TEXT(" ") + FolderLabelString; + + bool bChosen = CurTab->IsTab() && CurTab->IsChosen(); + + TSharedPtr CurCustomizedButton; + + HorizontalBox->AddSlot().VAlign(VAlign_Bottom) + .AutoWidth() + .Padding(0.f) + .HAlign(HAlign_Left) + [ + SAssignNew(CurCustomizedButton, SCustomizedButton) + .OnClicked_Lambda(OnTabClickedLambda) + .Content() + [ + SNew(STextBlock) + .Text(FText::FromString(FolderLabelString)) + ] + ]; + + CurCustomizedButton->bChosen = bChosen; + CurCustomizedButton->bIsRadioButton = CurTab->GetFolderType() == EHoudiniFolderParameterType::Radio; + + DesiredHeight = CurCustomizedButton->GetDesiredSize().Y; + DesiredWidth += CurCustomizedButton->GetDesiredSize().X; + } + + HorizontalBox->bIsTabFolderListRow = true; + + Row.WholeRowWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); + + // Set the current tabs to be shown, since slate widgets have been created + CurrentTabMenuFolderList->SetTabsShown(true); + + // Clear the temporary tabs + CurrentTabs.Empty(); +} + +void +FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams) +{ + TArray MultiParmParams; + if (!CastParameters(InParams, MultiParmParams)) + return; + + if (MultiParmParams.Num() <= 0) + return; + + UHoudiniParameterMultiParm* MainParam = MultiParmParams[0]; + if (!MainParam || MainParam->IsPendingKill()) + return; + + // Add current multiparm parameter to AllmultiParms map + AllMultiParms.Add(MainParam->GetParmId(), MainParam); + + // Create a new detail row + FDetailWidgetRow * Row = CreateNestedRow(HouParameterCategory, InParams); + + if (!Row) + { + MainParam->SetIsShown(false); + return; + } + + MainParam->SetIsShown(true); + + MultiParmInstanceIndices.Add(MainParam->GetParmId(), -1); + + CreateNameWidget(Row, InParams, true); + + auto OnInstanceValueChangedLambda = [MainParam](int32 InValue) + { + if (InValue < 0) + return; + + int32 ChangesCount = FMath::Abs(MainParam->MultiParmInstanceLastModifyArray.Num() - InValue); + + if (MainParam->MultiParmInstanceLastModifyArray.Num() > InValue) + { + for (int32 Idx = 0; Idx < ChangesCount; ++Idx) + MainParam->RemoveElement(-1); + + MainParam->MarkChanged(true); + } + else if (MainParam->MultiParmInstanceLastModifyArray.Num() < InValue) + { + for (int32 Idx = 0; Idx < ChangesCount; ++Idx) + MainParam->InsertElement(); + + MainParam->MarkChanged(true); + } + }; + + // Add multiparm UI. + TSharedRef HorizontalBox = SNew(SHorizontalBox); + TSharedPtr< SNumericEntryBox< int32 > > NumericEntryBox; + int32 NumericalCount = MainParam->MultiParmInstanceCount; + HorizontalBox->AddSlot().Padding(2, 2, 5, 2) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(true) + + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + .OnValueChanged(SNumericEntryBox::FOnValueChanged::CreateLambda([OnInstanceValueChangedLambda](int32 InValue) { + OnInstanceValueChangedLambda(InValue); + })) + .Value(NumericalCount) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamAddInstance", "Houdini Parameter Multi Parameter: Adding an instance"), + MainParam->GetOuter(), true); + + for (auto& Param : MultiParmParams) + { + if (!Param) + continue; + + // Add a reverse step for redo/undo + Param->MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Removed); + + Param->MarkChanged(true); + Param->Modify(); + + if (Param->MultiParmInstanceLastModifyArray.Num() > 0) + Param->MultiParmInstanceLastModifyArray.RemoveAt(Param->MultiParmInstanceLastModifyArray.Num() - 1); + + Param->InsertElement(); + + } + }), + LOCTEXT("AddMultiparmInstanceToolTipAddLastInstance", "Add an Instance"), true) + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + // Remove the last multiparm instance + PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamDeleteInstance", "Houdini Parameter Multi Parameter: Deleting an instance"), + MainParam->GetOuter(), true); + + for (auto & Param : MultiParmParams) + { + TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; + int32 RemovedIndex = LastModifiedArray.Num() - 1; + while (LastModifiedArray.IsValidIndex(RemovedIndex) && LastModifiedArray[RemovedIndex] == EHoudiniMultiParmModificationType::Removed) + RemovedIndex -= 1; + + // Add a reverse step for redo/undo + EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; + if (LastModifiedArray.IsValidIndex(RemovedIndex)) + { + PreviousModType = LastModifiedArray[RemovedIndex]; + LastModifiedArray[RemovedIndex] = EHoudiniMultiParmModificationType::Inserted; + } + + Param->MarkChanged(true); + + Param->Modify(); + + if (LastModifiedArray.IsValidIndex(RemovedIndex)) + { + LastModifiedArray[RemovedIndex] = PreviousModType; + } + + Param->RemoveElement(RemovedIndex); + } + + }), + LOCTEXT("RemoveLastMultiParamLastToolTipRemoveLastInstance", "Remove the last instance"), true) + + ]; + + HorizontalBox->AddSlot().AutoWidth().Padding(2.0f, 0.0f) + [ + PropertyCustomizationHelpers::MakeEmptyButton(FSimpleDelegate::CreateLambda([MainParam, MultiParmParams]() + { + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParamDeleteAllInstances", "Houdini Parameter Multi Parameter: Deleting all instances"), + MainParam->GetOuter(), true); + + for (auto & Param : MultiParmParams) + { + TArray& LastModifiedArray = Param->MultiParmInstanceLastModifyArray; + TArray IndicesToReverse; + + for (int32 Index = 0; Index < LastModifiedArray.Num(); ++Index) + { + if (LastModifiedArray[Index] == EHoudiniMultiParmModificationType::None) + { + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; + IndicesToReverse.Add(Index); + } + } + + Param->MarkChanged(true); + + Param->Modify(); + + for (int32 & Index : IndicesToReverse) + { + if (LastModifiedArray.IsValidIndex(Index)) + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::None; + } + + + Param->EmptyElements(); + } + + }), + LOCTEXT("HoudiniParameterRemoveAllMultiparmInstancesToolTip", "Remove all instances"), true) + ]; + + Row->ValueWidget.Widget = HorizontalBox; + Row->ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniParameterDetails::CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams) +{ + + if (InParams.Num() <= 0) + return; + + UHoudiniParameter* MainParam = InParams[0]; + + if (!MainParam || MainParam->IsPendingKill()) + return; + + if (!HorizontalBox || !AllMultiParms.Contains(MainParam->GetParentParmId()) || !MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) + return; + + UHoudiniParameterMultiParm* MainParentMultiParm = AllMultiParms[MainParam->GetParentParmId()]; + + if (!MainParentMultiParm) + return; + + if (!MainParentMultiParm->IsShown()) + return; + + // push all parent multiparm of the InParams to the array + TArray ParentMultiParams; + for (auto & InParam : InParams) + { + if (!InParam) + continue; + + if (!MultiParmInstanceIndices.Contains(InParam->GetParentParmId())) + continue; + + if (InParam->GetChildIndex() == 0) + { + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[InParam->GetParentParmId()]; + + if (ParentMultiParm) + ParentMultiParams.Add(ParentMultiParm); + } + } + + + int32 InstanceIndex = MultiParmInstanceIndices[MainParam->GetParentParmId()]; + + TSharedRef AddButton = PropertyCustomizationHelpers::MakeAddButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() + { + for (auto & ParentParam : ParentMultiParams) + { + // Add button call back + if (!ParentParam) + continue; + + TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; + + if (!LastModifiedArray.IsValidIndex(InstanceIndex)) + continue; + + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParmAddBeforeCurInstance", "Houdini Parameter Multi Parm: Adding an instance"), + ParentParam->GetOuter(), true); + + + int32 Index = InstanceIndex; + + // Add a reverse step for undo/redo + if (Index >= LastModifiedArray.Num()) + LastModifiedArray.Add(EHoudiniMultiParmModificationType::Removed); + else + LastModifiedArray.Insert(EHoudiniMultiParmModificationType::Removed, Index); + + ParentParam->MarkChanged(true); + ParentParam->Modify(); + + if (Index >= LastModifiedArray.Num() - 1 && LastModifiedArray.Num()) + LastModifiedArray.RemoveAt(LastModifiedArray.Num() - 1); + else + LastModifiedArray.RemoveAt(Index); + + ParentParam->InsertElementAt(InstanceIndex); + + } + }), + LOCTEXT("HoudiniParameterMultiParamAddBeforeCurrentInstanceToolTip", "Insert an instance before this instance")); + + + TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeRemoveButton(FSimpleDelegate::CreateLambda([ParentMultiParams, InstanceIndex]() + { + for (auto & ParentParam : ParentMultiParams) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniParameterMultiParmDeleteCurInstance", "Houdini Parameter Multi Parm: Deleting an instance"), + ParentParam->GetOuter(), true); + + + TArray& LastModifiedArray = ParentParam->MultiParmInstanceLastModifyArray; + + int32 Index = InstanceIndex; + EHoudiniMultiParmModificationType PreviousModType = EHoudiniMultiParmModificationType::None; + while (LastModifiedArray.IsValidIndex(Index) && LastModifiedArray[Index] == EHoudiniMultiParmModificationType::Removed) + { + Index -= 1; + } + + if (LastModifiedArray.IsValidIndex(Index)) + { + PreviousModType = LastModifiedArray[Index]; + LastModifiedArray[Index] = EHoudiniMultiParmModificationType::Inserted; + } + + ParentParam->MarkChanged(true); + + ParentParam->Modify(); + + if (LastModifiedArray.IsValidIndex(Index)) + { + LastModifiedArray[Index] = PreviousModType; + } + + ParentParam->RemoveElement(InstanceIndex); + } + + }), + LOCTEXT("HoudiniParameterMultiParamDeleteCurrentInstanceToolTip", "Remove an instance"), true); + + + HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[AddButton]; + HorizontalBox->AddSlot().AutoWidth().Padding(0.0f, 0.0f)[RemoveButton]; + + int32 StartIdx = MainParam->GetParameterType() == EHoudiniParameterType::Folder ? 1 : 0; + if (MainParam->GetChildIndex() != StartIdx) + { + AddButton.Get().SetVisibility(EVisibility::Hidden); + RemoveButton.Get().SetVisibility(EVisibility::Hidden); + } + +} + +void +FHoudiniParameterDetails::PruneStack() +{ + for (int32 StackItr = FolderStack.Num() - 1; StackItr >= 0; --StackItr) + { + TArray &CurrentQueue = FolderStack[StackItr]; + + for (int32 QueueItr = CurrentQueue.Num() - 1; QueueItr >= 0; --QueueItr) + { + UHoudiniParameterFolder * CurrentFolder = CurrentQueue[QueueItr]; + if (!CurrentFolder || CurrentFolder->IsPendingKill()) + continue; + + if (CurrentFolder->GetChildCounter() == 0) + { + CurrentQueue.RemoveAt(QueueItr); + } + } + + if (CurrentQueue.Num() == 0) + { + FolderStack.RemoveAt(StackItr); + } + } +} + +FText +FHoudiniParameterDetails::GetParameterTooltip(UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return FText(); + + // Tooltip starts with Label (name) + FString Tooltip = InParam->GetParameterLabel() + TEXT(" (") + InParam->GetParameterName() + TEXT(")"); + + // Append the parameter type + FString ParmTypeStr = GetParameterTypeString(InParam->GetParameterType(), InParam->GetTupleSize()); + if (!ParmTypeStr.IsEmpty()) + Tooltip += TEXT("\n") + ParmTypeStr; + + // If the parameter has some help, append it + FString Help = InParam->GetParameterHelp(); + if (!Help.IsEmpty()) + Tooltip += TEXT("\n") + Help; + + // If the parameter has an expression, append it + if (InParam->HasExpression()) + { + FString Expr = InParam->GetExpression(); + if (!Expr.IsEmpty()) + Tooltip += TEXT("\nExpression: ") + Expr; + } + + return FText::FromString(Tooltip); +} + +FString +FHoudiniParameterDetails::GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize) +{ + FString ParamStr; + + switch (InType) + { + case EHoudiniParameterType::Button: + ParamStr = TEXT("Button"); + break; + + case EHoudiniParameterType::ButtonStrip: + ParamStr = TEXT("Button Strip"); + break; + + case EHoudiniParameterType::Color: + { + if (InTupleSize == 4) + ParamStr = TEXT("Color with Alpha"); + else + ParamStr = TEXT("Color"); + } + break; + + case EHoudiniParameterType::ColorRamp: + ParamStr = TEXT("Color Ramp"); + break; + + case EHoudiniParameterType::File: + ParamStr = TEXT("File (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileDir: + ParamStr = TEXT("File Dir (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileGeo: + ParamStr = TEXT("File Geo (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::FileImage: + ParamStr = TEXT("File Image (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::Float: + ParamStr = TEXT("Float (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); + break; + + case EHoudiniParameterType::FloatRamp: + ParamStr = TEXT("Float Ramp"); + break; + + case EHoudiniParameterType::Folder: + case EHoudiniParameterType::FolderList: + break; + + case EHoudiniParameterType::Input: + ParamStr = TEXT("Opearator Path"); + break; + + case EHoudiniParameterType::Int: + ParamStr = TEXT("Integer (VEC") + FString::FromInt(InTupleSize) + TEXT(")"); + break; + + case EHoudiniParameterType::IntChoice: + ParamStr = TEXT("Int Choice"); + break; + + case EHoudiniParameterType::Label: + ParamStr = TEXT("Label (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::MultiParm: + ParamStr = TEXT("MultiParm"); + break; + + case EHoudiniParameterType::Separator: + break; + + case EHoudiniParameterType::String: + ParamStr = TEXT("String (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::StringAssetRef: + ParamStr = TEXT("String Asset Ref (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + case EHoudiniParameterType::StringChoice: + ParamStr = TEXT("String Choice"); + break; + + case EHoudiniParameterType::Toggle: + ParamStr = TEXT("Toggle (") + FString::FromInt(InTupleSize) + TEXT(" tuple)"); + break; + + default: + ParamStr = TEXT("invalid parameter type"); + break; + } + + + return ParamStr; +} + +void +FHoudiniParameterDetails::SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter) +{ + if (!ColorRampParameter) + return; + + // Do not sync when the Houdini asset component is cooking + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(ColorRampParameter)) + return; + + TArray &CachedPoints = ColorRampParameter->CachedPoints; + TArray &CurrentPoints = ColorRampParameter->Points; + + bool bCurveNeedsUpdate = false; + bool bRampParmNeedsUpdate = false; + + int32 Idx = 0; + + while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) + { + UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[Idx]; + UHoudiniParameterRampColorPoint* CurrentPoint = CurrentPoints[Idx]; + + if (!CachedPoint || !CurrentPoint) + continue; + + if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) + { + if (CurrentPoint->PositionParentParm) + { + CurrentPoint->SetPosition(CachedPoint->GetPosition()); + CurrentPoint->PositionParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + if (CachedPoint->GetValue() != CurrentPoint->GetValue()) + { + if (CurrentPoint->ValueParentParm) + { + CurrentPoint->SetValue(CachedPoint->GetValue()); + CurrentPoint->ValueParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) + { + if (CurrentPoint->InterpolationParentParm) + { + CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); + CurrentPoint->InterpolationParentParm->MarkChanged(true); + bCurveNeedsUpdate = true; + } + } + + Idx += 1; + } + + // Insert points + for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) + { + UHoudiniParameterRampColorPoint* CachedPoint = CachedPoints[IdxCachedPointLeft]; + + CreateColorRampParameterInsertEvent( + ColorRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); + + bCurveNeedsUpdate = true; + bRampParmNeedsUpdate = true; + + } + + // Delete points + for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) + { + ColorRampParameter->RemoveElement(IdxCurrentPointLeft); + + UHoudiniParameterRampColorPoint* Point = CurrentPoints[IdxCurrentPointLeft]; + + if (!Point) + continue; + + CreateColorRampParameterDeleteEvent(ColorRampParameter, Point->InstanceIndex); + + bCurveNeedsUpdate = true; + bRampParmNeedsUpdate = true; + } + + + ColorRampParameter->MarkChanged(bRampParmNeedsUpdate); +} + +//void +//FHoudiniParameterDetails::SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter) +//{ +// if (!FloatRampParameter) +// return; +// +// // Do not sync when the Houdini asset component is cooking +// if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(FloatRampParameter)) +// return; +// +// TArray &CachedPoints = FloatRampParameter->CachedPoints; +// TArray &CurrentPoints = FloatRampParameter->Points; +// +// int32 Idx = 0; +// +// while (Idx < CachedPoints.Num() && Idx < CurrentPoints.Num()) +// { +// UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; +// UHoudiniParameterRampFloatPoint* &CurrentPoint = CurrentPoints[Idx]; +// +// if (!CachedPoint || !CurrentPoint) +// continue; +// +// if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) +// { +// if (CurrentPoint->PositionParentParm) +// { +// CurrentPoint->SetPosition(CachedPoint->GetPosition()); +// CurrentPoint->PositionParentParm->MarkChanged(true); +// } +// } +// +// if (CachedPoint->GetValue() != CurrentPoint->GetValue()) +// { +// if (CurrentPoint->ValueParentParm) +// { +// CurrentPoint->SetValue(CachedPoint->GetValue()); +// CurrentPoint->ValueParentParm->MarkChanged(true); +// } +// } +// +// if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) +// { +// if (CurrentPoint->InterpolationParentParm) +// { +// CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); +// CurrentPoint->InterpolationParentParm->MarkChanged(true); +// } +// } +// +// Idx += 1; +// } +// +// // Insert points +// for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) +// { +// UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; +// if (!CachedPoint) +// continue; +// +// CreateFloatRampParameterInsertEvent( +// FloatRampParameter, CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); +// +// FloatRampParameter->MarkChanged(true); +// } +// +// // Remove points +// for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < CurrentPoints.Num(); ++IdxCurrentPointLeft) +// { +// FloatRampParameter->RemoveElement(IdxCurrentPointLeft); +// +// UHoudiniParameterRampFloatPoint* Point = CurrentPoints[IdxCurrentPointLeft]; +// +// if (!Point) +// continue; +// +// CreateFloatRampParameterDeleteEvent(FloatRampParameter, Point->InstanceIndex); +// +// FloatRampParameter->MarkChanged(true); +// } +//} + +void +FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetFloatRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + InParam->ModificationEvents.Add(DeleteEvent); +} + +void +FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetColorRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + InParam->ModificationEvents.Add(DeleteEvent); +} + +void +FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, + const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetFloatRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertFloat = InValue; + InsertEvent->InsertInterpolation = InInterp; + + InParam->ModificationEvents.Add(InsertEvent); +} + +void +FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, + const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + InParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetColorRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertColor = InColor; + InsertEvent->InsertInterpolation = InInterp; + + InParam->ModificationEvents.Add(InsertEvent); +} + +void +FHoudiniParameterDetails:: ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters) +{ + if (FloatRampParameters.Num() < 1) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0]; + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) + { + UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx]; + + if (!NextFloatRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); + } +} + +void +FHoudiniParameterDetails::ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters) +{ + if (FloatRampParameters.Num() < 1) + return; + + if (!FloatRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampFloat* MainParam = FloatRampParameters[0].Get(); + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + + for (int32 Idx = 1; Idx < FloatRampParameters.Num(); ++Idx) + { + if (!FloatRampParameters[Idx].IsValid()) + continue; + + UHoudiniParameterRampFloat* NextFloatRampParameter = FloatRampParameters[Idx].Get(); + + if (!NextFloatRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceFloatRampParameterPointsWithMainParameter(MainParam, NextFloatRampParameter); + } + +} + +void +FHoudiniParameterDetails:: ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam) +{ + if (!Param || !MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) + return; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Use Synced points if the MainParam is on auto update mode + // Use Cached points if the Mainparam is on manual update mode + + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + + if (Param->IsAutoUpdate() && bCookingEnabled) + { + TArray & Points = Param->Points; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (MainPoint->GetPosition() != Point->GetPosition()) + { + if (Point->PositionParentParm) + { + Point->SetPosition(MainPoint->GetPosition()); + Point->PositionParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetValue() != Point->GetValue()) + { + if (Point->ValueParentParm) + { + Point->SetValue(MainPoint->GetValue()); + Point->ValueParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetInterpolation() != Point->GetInterpolation()) + { + if (Point->InterpolationParentParm) + { + Point->SetInterpolation(MainPoint->GetInterpolation()); + Point->InterpolationParentParm->MarkChanged(true); + } + } + + PointIdx += 1; + } + + int32 PointInsertIdx = PointIdx; + int32 PointDeleteIdx = PointIdx; + + // skip the pending modification events + for (auto & Event : Param->ModificationEvents) + { + if (!Event) + continue; + + if (Event->IsInsertEvent()) + PointInsertIdx += 1; + + if (Event->IsDeleteEvent()) + PointDeleteIdx += 1; + } + + // There are more points in MainPoints array + for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) + { + UHoudiniParameterRampFloatPoint*& NextMainPoint = MainPoints[PointInsertIdx]; + + if (!NextMainPoint) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(Param, + NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); + + Param->MarkChanged(true); + } + + // There are more points in Points array + for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) + { + UHoudiniParameterRampFloatPoint*& NextPoint = Points[PointDeleteIdx]; + + if (!NextPoint) + continue; + + FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); + + Param->MarkChanged(true); + } + + } + else + { + TArray &Points = Param->CachedPoints; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampFloatPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampFloatPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (Point->Position != MainPoint->Position) + { + Point->Position = MainPoint->Position; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->PositionParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Value != MainPoint->Value) + { + Point->Value = MainPoint->Value; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->ValueParentParm) + Point->ValueParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Interpolation != MainPoint->Interpolation) + { + Point->Interpolation = MainPoint->Interpolation; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->InterpolationParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + PointIdx += 1; + } + + // There are more points in MainPoints array + for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) + { + UHoudiniParameterRampFloatPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; + + if (!NextMainPoint) + continue; + + UHoudiniParameterRampFloatPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampFloatPoint::StaticClass()); + + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = NextMainPoint->GetPosition(); + NewCachedPoint->Value = NextMainPoint->GetValue(); + NewCachedPoint->Interpolation = NextMainPoint->GetInterpolation(); + + Points.Add(NewCachedPoint); + + Param->bCaching = true; + } + + // there are more points in Points array + for (int32 PointsLeftIdx = PointIdx; PointIdx < Points.Num(); ++PointIdx) + { + Points.Pop(); + Param->bCaching = true; + } + } + +} + + +void +FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters) +{ + if (ColorRampParameters.Num() < 1) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0]; + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) + { + UHoudiniParameterRampColor* NextColorRampParam = ColorRampParameters[Idx]; + + if (!NextColorRampParam) + continue; + + FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParam); + } +} + +void +FHoudiniParameterDetails::ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters) +{ + if (ColorRampParameters.Num() < 1) + return; + + if (!ColorRampParameters[0].IsValid()) + return; + + UHoudiniParameterRampColor* MainParam = ColorRampParameters[0].Get(); + + if (!MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(MainParam)) + return; + + for (int32 Idx = 1; Idx < ColorRampParameters.Num(); ++Idx) + { + if (!ColorRampParameters[Idx].IsValid()) + continue; + + UHoudiniParameterRampColor* NextColorRampParameter = ColorRampParameters[Idx].Get(); + + if (!NextColorRampParameter) + continue; + + FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(MainParam, NextColorRampParameter); + + } + +} + +void +FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParam) +{ + if (!Param || !MainParam) + return; + + if (FHoudiniEngineUtils::IsHoudiniAssetComponentCooking(Param)) + return; + + const bool bCookingEnabled = FHoudiniEngine::Get().IsCookingEnabled(); + + // Use Synced points if the MainParam is on auto update mode + // Use Cached points if the Mainparam is on manual update mode + TArray & MainPoints = (MainParam->IsAutoUpdate() && bCookingEnabled) ? MainParam->Points : MainParam->CachedPoints; + if (Param->IsAutoUpdate() && bCookingEnabled) + { + TArray & Points = Param->Points; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (MainPoint->GetPosition() != Point->GetPosition()) + { + if (Point->PositionParentParm) + { + Point->SetPosition(MainPoint->GetPosition()); + Point->PositionParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetValue() != Point->GetValue()) + { + if (Point->ValueParentParm) + { + Point->SetValue(MainPoint->GetValue()); + Point->ValueParentParm->MarkChanged(true); + } + } + + if (MainPoint->GetInterpolation() != Point->GetInterpolation()) + { + if (Point->InterpolationParentParm) + { + Point->SetInterpolation(MainPoint->GetInterpolation()); + Point->InterpolationParentParm->MarkChanged(true); + } + } + + PointIdx += 1; + + } + + int32 PointInsertIdx = PointIdx; + int32 PointDeleteIdx = PointIdx; + + // skip the pending modification events + for (auto & Event : Param->ModificationEvents) + { + if (!Event) + continue; + + if (Event->IsInsertEvent()) + PointInsertIdx += 1; + + if (Event->IsDeleteEvent()) + PointDeleteIdx += 1; + } + + // There are more points in MainPoints array + for (; PointInsertIdx < MainPoints.Num(); ++PointInsertIdx) + { + UHoudiniParameterRampColorPoint*& NextMainPoint = MainPoints[PointInsertIdx]; + if (!NextMainPoint) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(Param, + NextMainPoint->GetPosition(), NextMainPoint->GetValue(), NextMainPoint->GetInterpolation()); + + Param->MarkChanged(true); + } + + // There are more points in Points array + for (; PointDeleteIdx < Points.Num(); ++PointDeleteIdx) + { + UHoudiniParameterRampColorPoint*& NextPoint = Points[PointDeleteIdx]; + if (!NextPoint) + continue; + + FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(Param, NextPoint->InstanceIndex); + + Param->MarkChanged(true); + } + } + else + { + TArray &Points = Param->CachedPoints; + + int32 PointIdx = 0; + while (MainPoints.IsValidIndex(PointIdx) && Points.IsValidIndex(PointIdx)) + { + UHoudiniParameterRampColorPoint*& MainPoint = MainPoints[PointIdx]; + UHoudiniParameterRampColorPoint*& Point = Points[PointIdx]; + + if (!MainPoint || !Point) + continue; + + if (Point->Position != MainPoint->Position) + { + Point->Position = MainPoint->Position; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->PositionParentParm) + Point->PositionParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Value != MainPoint->Value) + { + Point->Value = MainPoint->Value; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->ValueParentParm) + Point->ValueParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + if (Point->Interpolation != MainPoint->Interpolation) + { + Point->Interpolation = MainPoint->Interpolation; + Param->bCaching = true; + if (!bCookingEnabled) + { + if (Point->InterpolationParentParm) + Point->InterpolationParentParm->MarkChanged(true); + Param->MarkChanged(true); + } + } + + PointIdx += 1; + } + + // There are more points in Main Points array. + for (int32 MainPointsLeftIdx = PointIdx; MainPointsLeftIdx < MainPoints.Num(); ++MainPointsLeftIdx) + { + UHoudiniParameterRampColorPoint* NextMainPoint = MainPoints[MainPointsLeftIdx]; + + if (!NextMainPoint) + continue; + + UHoudiniParameterRampColorPoint* NewCachedPoint = NewObject(Param, UHoudiniParameterRampColorPoint::StaticClass()); + if (!NewCachedPoint) + continue; + + NewCachedPoint->Position = NextMainPoint->Position; + NewCachedPoint->Value = NextMainPoint->Value; + NewCachedPoint->Interpolation = NextMainPoint->Interpolation; + + Points.Add(NewCachedPoint); + + Param->bCaching = true; + } + + // There are more points in Points array + for (int32 PointsleftIdx = PointIdx; PointIdx < MainPoints.Num(); ++PointsleftIdx) + { + Points.Pop(); + + Param->bCaching = true; + } + } +} + +// Check recursively if a parameter hits the end of a visible tabs +void +FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam) +{ + if (!InParam || InParam->IsPendingKill()) + return; + + // When the paramId is invalid, the directory won't parse. + // So simply return the function + if (InParam->GetParmId() < 0) + return; + + // Do not end the tab if this param is a non empty parent type, leave it to its children + EHoudiniParameterType ParmType = InParam->GetParameterType(); + if ((ParmType == EHoudiniParameterType::FolderList || + ParmType == EHoudiniParameterType::Folder) && InParam->GetTupleSize() > 0) + return; + + if (ParmType == EHoudiniParameterType::MultiParm) + { + UHoudiniParameterMultiParm* InMultiParm = Cast(InParam); + if (!InMultiParm) + return; + + if (InMultiParm->MultiParmInstanceCount * InMultiParm->MultiParmInstanceLength > 0) + return; + } + + int32 ParentParamId = InParam->GetParentParmId(); + UHoudiniParameter* CurParam = InParam; + + while (AllFoldersAndFolderLists.Contains(ParentParamId) || AllMultiParms.Contains(ParentParamId)) + { + if (AllMultiParms.Contains(ParentParamId)) + { + // The parent is a multiparm + UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentParamId]; + if (!ParentMultiParm || ParentMultiParm->IsPendingKill()) + return; + + if (ParentMultiParm->MultiParmInstanceCount * ParentMultiParm->MultiParmInstanceLength - 1 == CurParam->GetChildIndex()) + { + ParentParamId = ParentMultiParm->GetParentParmId(); + CurParam = ParentMultiParm; + + continue; + } + else + { + // return directly if the parameter is not the last child param of the multiparm + return; + } + } + else + { + // The parent is a folder or folderlist + UHoudiniParameter* ParentFolderParam = AllFoldersAndFolderLists[ParentParamId]; + CurParam = ParentFolderParam; + + if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) + return; + + if (ParentFolderParam->GetParameterType() == EHoudiniParameterType::Folder) + { + // The parent is a folder + ParentParamId = ParentFolderParam->GetParentParmId(); + continue; + } + else + { + // The parent is a folderlist + UHoudiniParameterFolderList* ParentFolderList = Cast(ParentFolderParam); + if (!ParentFolderList || ParentFolderList->IsPendingKill()) + return; + + if (ParentFolderList->IsTabMenu() && ParentFolderList->IsTabsShown() && ParentFolderList->IsTabParseFinished() && DividerLinePositions.Num() > 0) + { + if (!CurrentTabEndingRow) + CreateTabEndingRow(HouParameterCategory); + + if (CurrentTabEndingRow) + { + CurrentTabEndingRow->EndingDividerLinePositions.Add(DividerLinePositions.Top()); + CurrentTabEndingRow->DividerLinePositions.Pop(); + } + + DividerLinePositions.Pop(); + + ParentParamId = ParentFolderList->GetParentParmId(); + } + else + { + return; + } + } + } + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h index 10848fb76..73cf64d3f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.h @@ -1,491 +1,491 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" - -#include "CoreMinimal.h" - -#include "Widgets/Layout/SUniformGridPanel.h" -#include "SCurveEditor.h" -#include "Editor/CurveEditor/Public/CurveEditorSettings.h" -#include "HoudiniParameterTranslator.h" -#include "Curves/CurveFloat.h" -#include "SColorGradientEditor.h" -#include "Curves/CurveLinearColor.h" - -#include "Widgets/SBoxPanel.h" -#include "Widgets/Input/SButton.h" - -#include "HoudiniParameterDetails.generated.h" - -class UHoudiniAssetComponent; -class UHoudiniParameter; -class UHoudiniParameterFloat; -class UHoudiniParameterInt; -class UHoudiniParameterString; -class UHoudiniParameterColor; -class UHoudiniParameterButton; -class UHoudiniParameterButtonStrip; -class UHoudiniParameterLabel; -class UHoudiniParameterToggle; -class UHoudiniParameterFile; -class UHoudiniParameterChoice; -class UHoudiniParameterFolder; -class UHoudiniParameterFolderList; -class UHoudiniParameterMultiParm; -class UHoudiniParameterRampFloat; -class UHoudiniParameterRampColor; -class UHoudiniParameterOperatorPath; - -class UHoudiniParameterRampColorPoint; -class UHoudiniParameterRampFloatPoint; - -class UHoudiniColorRampCurve; -class UHoudiniFloatRampCurve; - -class IDetailCategoryBuilder; -class FDetailWidgetRow; -class SHorizontalBox; -class SHoudiniAssetParameterRampCurveEditor; - -enum class EHoudiniRampInterpolationType : int8; - -class SCustomizedButton : public SButton -{ -public: - bool bChosen; - - bool bIsRadioButton; - -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; - - // Construct the circles for all radio buttons. Initialize at first use - void ConstructRadioButtonCircles() const; - - void DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const; -}; - -class SCustomizedBox : public SHorizontalBox -{ -public: - bool bIsTabFolderListRow; - - bool bIsSeparator; - - TArray DividerLinePositions; - - TArray EndingDividerLinePositions; - - float MarginHeight; - -public: - virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, - FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; - - // Add indentation to current row, computed by tracing the directory hierarchy, - // return the indentation width of this parameter row. - float AddIndentation(UHoudiniParameter* InParam, TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists); - - void SetHoudiniParameter(TArray& InParams); -}; - -class SHoudiniFloatRampCurveEditor : public SCurveEditor -{ -public: - SLATE_BEGIN_ARGS(SHoudiniFloatRampCurveEditor) - : _ViewMinInput(0.0f) - , _ViewMaxInput(10.0f) - , _ViewMinOutput(0.0f) - , _ViewMaxOutput(1.0f) - , _InputSnap(0.1f) - , _OutputSnap(0.05f) - , _InputSnappingEnabled(false) - , _OutputSnappingEnabled(false) - , _ShowTimeInFrames(false) - , _TimelineLength(5.0f) - , _DesiredSize(FVector2D::ZeroVector) - , _DrawCurve(true) - , _HideUI(true) - , _AllowZoomOutput(true) - , _AlwaysDisplayColorCurves(false) - , _ZoomToFitVertical(true) - , _ZoomToFitHorizontal(true) - , _ShowZoomButtons(true) - , _XAxisName() - , _YAxisName() - , _ShowInputGridNumbers(true) - , _ShowOutputGridNumbers(true) - , _ShowCurveSelector(true) - , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) - { - _Clipping = EWidgetClipping::ClipToBounds; - } - - SLATE_ATTRIBUTE(float, ViewMinInput) - SLATE_ATTRIBUTE(float, ViewMaxInput) - SLATE_ATTRIBUTE(TOptional, DataMinInput) - SLATE_ATTRIBUTE(TOptional, DataMaxInput) - SLATE_ATTRIBUTE(float, ViewMinOutput) - SLATE_ATTRIBUTE(float, ViewMaxOutput) - SLATE_ATTRIBUTE(float, InputSnap) - SLATE_ATTRIBUTE(float, OutputSnap) - SLATE_ATTRIBUTE(bool, InputSnappingEnabled) - SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) - SLATE_ATTRIBUTE(bool, ShowTimeInFrames) - SLATE_ATTRIBUTE(float, TimelineLength) - SLATE_ATTRIBUTE(FVector2D, DesiredSize) - SLATE_ATTRIBUTE(bool, AreCurvesVisible) - SLATE_ARGUMENT(bool, DrawCurve) - SLATE_ARGUMENT(bool, HideUI) - SLATE_ARGUMENT(bool, AllowZoomOutput) - SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) - SLATE_ARGUMENT(bool, ZoomToFitVertical) - SLATE_ARGUMENT(bool, ZoomToFitHorizontal) - SLATE_ARGUMENT(bool, ShowZoomButtons) - SLATE_ARGUMENT(TOptional, XAxisName) - SLATE_ARGUMENT(TOptional, YAxisName) - SLATE_ARGUMENT(bool, ShowInputGridNumbers) - SLATE_ARGUMENT(bool, ShowOutputGridNumbers) - SLATE_ARGUMENT(bool, ShowCurveSelector) - SLATE_ARGUMENT(FLinearColor, GridColor) - SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) - SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) - SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) - SLATE_EVENT(FSimpleDelegate, OnCreateAsset) - SLATE_END_ARGS() - - public: - TWeakObjectPtr HoudiniFloatRampCurve; - - /** Widget construction. **/ - void Construct(const FArguments & InArgs); - - virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; - -}; - - -class SHoudiniColorRampCurveEditor : public SColorGradientEditor -{ - -public: - SLATE_BEGIN_ARGS(SHoudiniColorRampCurveEditor) - : _ViewMinInput(0.0f) - , _ViewMaxInput(10.0f) - , _InputSnap(0.1f) - , _OutputSnap(0.05f) - , _InputSnappingEnabled(false) - , _OutputSnappingEnabled(false) - , _ShowTimeInFrames(false) - , _TimelineLength(5.0f) - , _DesiredSize(FVector2D::ZeroVector) - , _DrawCurve(true) - , _HideUI(true) - , _AllowZoomOutput(true) - , _AlwaysDisplayColorCurves(false) - , _ZoomToFitVertical(true) - , _ZoomToFitHorizontal(true) - , _ShowZoomButtons(true) - , _XAxisName() - , _YAxisName() - , _ShowInputGridNumbers(true) - , _ShowOutputGridNumbers(true) - , _ShowCurveSelector(true) - , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) - { - _Clipping = EWidgetClipping::ClipToBounds; - } - - SLATE_ATTRIBUTE(float, ViewMinInput) - SLATE_ATTRIBUTE(float, ViewMaxInput) - SLATE_ATTRIBUTE(TOptional, DataMinInput) - SLATE_ATTRIBUTE(TOptional, DataMaxInput) - SLATE_ATTRIBUTE(float, InputSnap) - SLATE_ATTRIBUTE(float, OutputSnap) - SLATE_ATTRIBUTE(bool, InputSnappingEnabled) - SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) - SLATE_ATTRIBUTE(bool, ShowTimeInFrames) - SLATE_ATTRIBUTE(float, TimelineLength) - SLATE_ATTRIBUTE(FVector2D, DesiredSize) - SLATE_ATTRIBUTE(bool, AreCurvesVisible) - SLATE_ARGUMENT(bool, DrawCurve) - SLATE_ARGUMENT(bool, HideUI) - SLATE_ARGUMENT(bool, AllowZoomOutput) - SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) - SLATE_ARGUMENT(bool, ZoomToFitVertical) - SLATE_ARGUMENT(bool, ZoomToFitHorizontal) - SLATE_ARGUMENT(bool, ShowZoomButtons) - SLATE_ARGUMENT(TOptional, XAxisName) - SLATE_ARGUMENT(TOptional, YAxisName) - SLATE_ARGUMENT(bool, ShowInputGridNumbers) - SLATE_ARGUMENT(bool, ShowOutputGridNumbers) - SLATE_ARGUMENT(bool, ShowCurveSelector) - SLATE_ARGUMENT(FLinearColor, GridColor) - SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) - SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) - SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) - SLATE_EVENT(FSimpleDelegate, OnCreateAsset) - SLATE_END_ARGS() - - public: - /** Widget construction. **/ - void Construct(const FArguments & InArgs); - - TWeakObjectPtr HoudiniColorRampCurve; - - virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; - - virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; -}; - -UCLASS() -class UHoudiniFloatRampCurve : public UCurveFloat -{ - GENERATED_BODY() - - public: - - TArray> FloatRampParameters; - - virtual void OnCurveChanged(const TArray& ChangedCurveEditInfos) override; -}; - - -UCLASS() -class UHoudiniColorRampCurve : public UCurveLinearColor -{ - GENERATED_BODY() - - public: - bool bEditing = false; - - TArray> ColorRampParameters; - - virtual void OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) override; - - void OnColorRampCurveChanged(bool bModificationOnly = false); - -}; - - -//class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface -class FHoudiniParameterDetails : public TSharedFromThis -{ - public: - void CreateWidget( - IDetailCategoryBuilder & HouParameterCategory, - TArray &InParams); - - void CreateWidgetInt( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetFloat( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetString( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetColor( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetButton( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetButtonStrip( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetLabel( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetToggle( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFile( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetChoice( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); - void CreateWidgetSeparator( - IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled); - void CreateWidgetFolderList( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFolder( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetMultiParm( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetOperatorPath( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetFloatRamp( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - void CreateWidgetColorRamp( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); - - void CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory); - - - void HandleUnsupportedParmType( - IDetailCategoryBuilder & HouParameterCategory, TArray &InParams - ); - - - static FText GetParameterTooltip(UHoudiniParameter* InParam); - - static FString GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize); - - static void SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter); - - //static void SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter); - - // replace the children parameter values of all (multi-selected) float ramp parameters with the main parameter (weak object pointer version) - static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters); - // raw pointer version - static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters); - // helper - static void ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam); - - - // replace the children parameter values of all (multi-selected) color ramp parameters with the main parameter (weak object pointer version) - static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters); - // raw pointer version - static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters); - // helper - static void ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParame); - - - - // Create an insert event for a float ramp parameter - static void CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, - const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); - - // Create an insert event for a color ramp parameter - static void CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, - const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp); - - // Create a delete event for a float ramp parameter - static void CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex); - - // Create a delete event for a color ramp parameter - static void CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex); - - - private: - - template< class T > - static bool CastParameters( - TArray InParams, TArray& OutCastedParams); - - // - // Private helper functions for widget creation - // - - // Creates the default name widget, the parameter will then fill the value after - void CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); - - // Creates the default name widget, with an extra checkbox for disabling the the parameter update - void CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); - - FDetailWidgetRow* CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount = true); // - - void CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray& InParams); // - - void CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InParam, const bool& bIsShown); // - - void CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams); // - - // Create the UI for ramp's curve editor. - FDetailWidgetRow* CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); // - - // Create the UI for ramp's stop points. - void CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray< - UHoudiniParameter*>& InParams); // - - void PruneStack(); - - void RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam); - - public: - // Stores the created ramp curves - // In order to avoid being grabage collected, curves are added to root, thus need to handle GC manually. - // These points are for releasing the memory when the detail class are destroyed - TArray CreatedFloatRampCurves; - TArray CreatedColorRampCurves; - // The curve editors reference the UHoudini*Curves as "CurveOwners" as raw (non UObject) pointers, so we have - // to set their owners to null here before we destroy the Created*RampCuvers - TArray> CreatedColorGradientEditors; - TArray> CreatedFloatCurveEditors; - - private: - // The parameter directory is flattened with BFS inside of DFS. - // When a folderlist is encountered, it goes 'one step' of DFS, otherwise BFS. - // So that use a Stack structure to reconstruct the tree. - TArray> FolderStack; - - // Float Ramp currently being processed - UHoudiniParameterRampFloat* CurrentRampFloat; - - // Color Ramp currently being processed - UHoudiniParameterRampColor* CurrentRampColor; - - TArray CurrentRampParameterList; - - // Cached curve points of float ramp which being processed - TArray CurrentRampFloatPointsArray; - - // Cached curve points of color ramp which being processed - TArray CurrentRampColorPointsArray; - - // Cached color ramp curve which being processed - UHoudiniColorRampCurve* CurrentRampParameterColorCurve; - - // Cached float ramp curve which being processed - UHoudiniFloatRampCurve* CurrentRampParameterFloatCurve; - - FDetailWidgetRow * CurrentRampRow; - - - /* Variables for keeping expansion state after adding multiparm instance*/ - TMap AllMultiParms; - - // Cached the map of parameter id and folders/folder lists - TMap AllFoldersAndFolderLists; - - /* Variables for keeping expansion state after adding multiparm instance*/ - - TMap MultiParmInstanceIndices; - - // Number of remaining folders for current folder list - int32 CurrentFolderListSize = 0; - - // The folder list currently being processed - UHoudiniParameterFolderList* CurrentFolderList; - - // Cached child folders of current tabs - TArray CurrentTabs; - - TArray DividerLinePositions; - - SCustomizedBox* CurrentTabEndingRow; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" + +#include "CoreMinimal.h" + +#include "Widgets/Layout/SUniformGridPanel.h" +#include "SCurveEditor.h" +#include "Editor/CurveEditor/Public/CurveEditorSettings.h" +#include "HoudiniParameterTranslator.h" +#include "Curves/CurveFloat.h" +#include "SColorGradientEditor.h" +#include "Curves/CurveLinearColor.h" + +#include "Widgets/SBoxPanel.h" +#include "Widgets/Input/SButton.h" + +#include "HoudiniParameterDetails.generated.h" + +class UHoudiniAssetComponent; +class UHoudiniParameter; +class UHoudiniParameterFloat; +class UHoudiniParameterInt; +class UHoudiniParameterString; +class UHoudiniParameterColor; +class UHoudiniParameterButton; +class UHoudiniParameterButtonStrip; +class UHoudiniParameterLabel; +class UHoudiniParameterToggle; +class UHoudiniParameterFile; +class UHoudiniParameterChoice; +class UHoudiniParameterFolder; +class UHoudiniParameterFolderList; +class UHoudiniParameterMultiParm; +class UHoudiniParameterRampFloat; +class UHoudiniParameterRampColor; +class UHoudiniParameterOperatorPath; + +class UHoudiniParameterRampColorPoint; +class UHoudiniParameterRampFloatPoint; + +class UHoudiniColorRampCurve; +class UHoudiniFloatRampCurve; + +class IDetailCategoryBuilder; +class FDetailWidgetRow; +class SHorizontalBox; +class SHoudiniAssetParameterRampCurveEditor; + +enum class EHoudiniRampInterpolationType : int8; + +class SCustomizedButton : public SButton +{ +public: + bool bChosen; + + bool bIsRadioButton; + +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + // Construct the circles for all radio buttons. Initialize at first use + void ConstructRadioButtonCircles() const; + + void DrawRadioButton(const FGeometry& AllottedGeometry, FSlateWindowElementList& OutDrawElements, int32 LayerId, const bool& bSelected) const; +}; + +class SCustomizedBox : public SHorizontalBox +{ +public: + bool bIsTabFolderListRow; + + bool bIsSeparator; + + TArray DividerLinePositions; + + TArray EndingDividerLinePositions; + + float MarginHeight; + +public: + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, + FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + // Add indentation to current row, computed by tracing the directory hierarchy, + // return the indentation width of this parameter row. + float AddIndentation(UHoudiniParameter* InParam, TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists); + + void SetHoudiniParameter(TArray& InParams); +}; + +class SHoudiniFloatRampCurveEditor : public SCurveEditor +{ +public: + SLATE_BEGIN_ARGS(SHoudiniFloatRampCurveEditor) + : _ViewMinInput(0.0f) + , _ViewMaxInput(10.0f) + , _ViewMinOutput(0.0f) + , _ViewMaxOutput(1.0f) + , _InputSnap(0.1f) + , _OutputSnap(0.05f) + , _InputSnappingEnabled(false) + , _OutputSnappingEnabled(false) + , _ShowTimeInFrames(false) + , _TimelineLength(5.0f) + , _DesiredSize(FVector2D::ZeroVector) + , _DrawCurve(true) + , _HideUI(true) + , _AllowZoomOutput(true) + , _AlwaysDisplayColorCurves(false) + , _ZoomToFitVertical(true) + , _ZoomToFitHorizontal(true) + , _ShowZoomButtons(true) + , _XAxisName() + , _YAxisName() + , _ShowInputGridNumbers(true) + , _ShowOutputGridNumbers(true) + , _ShowCurveSelector(true) + , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + + SLATE_ATTRIBUTE(float, ViewMinInput) + SLATE_ATTRIBUTE(float, ViewMaxInput) + SLATE_ATTRIBUTE(TOptional, DataMinInput) + SLATE_ATTRIBUTE(TOptional, DataMaxInput) + SLATE_ATTRIBUTE(float, ViewMinOutput) + SLATE_ATTRIBUTE(float, ViewMaxOutput) + SLATE_ATTRIBUTE(float, InputSnap) + SLATE_ATTRIBUTE(float, OutputSnap) + SLATE_ATTRIBUTE(bool, InputSnappingEnabled) + SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) + SLATE_ATTRIBUTE(bool, ShowTimeInFrames) + SLATE_ATTRIBUTE(float, TimelineLength) + SLATE_ATTRIBUTE(FVector2D, DesiredSize) + SLATE_ATTRIBUTE(bool, AreCurvesVisible) + SLATE_ARGUMENT(bool, DrawCurve) + SLATE_ARGUMENT(bool, HideUI) + SLATE_ARGUMENT(bool, AllowZoomOutput) + SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) + SLATE_ARGUMENT(bool, ZoomToFitVertical) + SLATE_ARGUMENT(bool, ZoomToFitHorizontal) + SLATE_ARGUMENT(bool, ShowZoomButtons) + SLATE_ARGUMENT(TOptional, XAxisName) + SLATE_ARGUMENT(TOptional, YAxisName) + SLATE_ARGUMENT(bool, ShowInputGridNumbers) + SLATE_ARGUMENT(bool, ShowOutputGridNumbers) + SLATE_ARGUMENT(bool, ShowCurveSelector) + SLATE_ARGUMENT(FLinearColor, GridColor) + SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) + SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) + SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) + SLATE_EVENT(FSimpleDelegate, OnCreateAsset) + SLATE_END_ARGS() + + public: + TWeakObjectPtr HoudiniFloatRampCurve; + + /** Widget construction. **/ + void Construct(const FArguments & InArgs); + + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; + +}; + + +class SHoudiniColorRampCurveEditor : public SColorGradientEditor +{ + +public: + SLATE_BEGIN_ARGS(SHoudiniColorRampCurveEditor) + : _ViewMinInput(0.0f) + , _ViewMaxInput(10.0f) + , _InputSnap(0.1f) + , _OutputSnap(0.05f) + , _InputSnappingEnabled(false) + , _OutputSnappingEnabled(false) + , _ShowTimeInFrames(false) + , _TimelineLength(5.0f) + , _DesiredSize(FVector2D::ZeroVector) + , _DrawCurve(true) + , _HideUI(true) + , _AllowZoomOutput(true) + , _AlwaysDisplayColorCurves(false) + , _ZoomToFitVertical(true) + , _ZoomToFitHorizontal(true) + , _ShowZoomButtons(true) + , _XAxisName() + , _YAxisName() + , _ShowInputGridNumbers(true) + , _ShowOutputGridNumbers(true) + , _ShowCurveSelector(true) + , _GridColor(FLinearColor(0.0f, 0.0f, 0.0f, 0.3f)) + { + _Clipping = EWidgetClipping::ClipToBounds; + } + + SLATE_ATTRIBUTE(float, ViewMinInput) + SLATE_ATTRIBUTE(float, ViewMaxInput) + SLATE_ATTRIBUTE(TOptional, DataMinInput) + SLATE_ATTRIBUTE(TOptional, DataMaxInput) + SLATE_ATTRIBUTE(float, InputSnap) + SLATE_ATTRIBUTE(float, OutputSnap) + SLATE_ATTRIBUTE(bool, InputSnappingEnabled) + SLATE_ATTRIBUTE(bool, OutputSnappingEnabled) + SLATE_ATTRIBUTE(bool, ShowTimeInFrames) + SLATE_ATTRIBUTE(float, TimelineLength) + SLATE_ATTRIBUTE(FVector2D, DesiredSize) + SLATE_ATTRIBUTE(bool, AreCurvesVisible) + SLATE_ARGUMENT(bool, DrawCurve) + SLATE_ARGUMENT(bool, HideUI) + SLATE_ARGUMENT(bool, AllowZoomOutput) + SLATE_ARGUMENT(bool, AlwaysDisplayColorCurves) + SLATE_ARGUMENT(bool, ZoomToFitVertical) + SLATE_ARGUMENT(bool, ZoomToFitHorizontal) + SLATE_ARGUMENT(bool, ShowZoomButtons) + SLATE_ARGUMENT(TOptional, XAxisName) + SLATE_ARGUMENT(TOptional, YAxisName) + SLATE_ARGUMENT(bool, ShowInputGridNumbers) + SLATE_ARGUMENT(bool, ShowOutputGridNumbers) + SLATE_ARGUMENT(bool, ShowCurveSelector) + SLATE_ARGUMENT(FLinearColor, GridColor) + SLATE_EVENT(FOnSetInputViewRange, OnSetInputViewRange) + SLATE_EVENT(FOnSetOutputViewRange, OnSetOutputViewRange) + SLATE_EVENT(FOnSetAreCurvesVisible, OnSetAreCurvesVisible) + SLATE_EVENT(FSimpleDelegate, OnCreateAsset) + SLATE_END_ARGS() + + public: + /** Widget construction. **/ + void Construct(const FArguments & InArgs); + + TWeakObjectPtr HoudiniColorRampCurve; + + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override; + + virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override; +}; + +UCLASS() +class UHoudiniFloatRampCurve : public UCurveFloat +{ + GENERATED_BODY() + + public: + + TArray> FloatRampParameters; + + virtual void OnCurveChanged(const TArray& ChangedCurveEditInfos) override; +}; + + +UCLASS() +class UHoudiniColorRampCurve : public UCurveLinearColor +{ + GENERATED_BODY() + + public: + bool bEditing = false; + + TArray> ColorRampParameters; + + virtual void OnCurveChanged(const TArray< FRichCurveEditInfo > & ChangedCurveEditInfos) override; + + void OnColorRampCurveChanged(bool bModificationOnly = false); + +}; + + +//class FHoudiniParameterDetails : public TSharedFromThis, public TNumericUnitTypeInterface, public TNumericUnitTypeInterface +class FHoudiniParameterDetails : public TSharedFromThis +{ + public: + void CreateWidget( + IDetailCategoryBuilder & HouParameterCategory, + TArray &InParams); + + void CreateWidgetInt( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetFloat( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetString( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetColor( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetButton( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetButtonStrip( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetLabel( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetToggle( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFile( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetChoice( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams); + void CreateWidgetSeparator( + IDetailCategoryBuilder & HouParameterCategory, TArray& InParams, const bool& InIsEnabled); + void CreateWidgetFolderList( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFolder( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetMultiParm( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetOperatorPath( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetFloatRamp( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + void CreateWidgetColorRamp( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); + + void CreateTabEndingRow(IDetailCategoryBuilder & HouParameterCategory); + + + void HandleUnsupportedParmType( + IDetailCategoryBuilder & HouParameterCategory, TArray &InParams + ); + + + static FText GetParameterTooltip(UHoudiniParameter* InParam); + + static FString GetParameterTypeString(const EHoudiniParameterType& InType, const int32& InTupleSize); + + static void SyncCachedColorRampPoints(UHoudiniParameterRampColor* ColorRampParameter); + + //static void SyncCachedFloatRampPoints(UHoudiniParameterRampFloat* FloatRampParameter); + + // replace the children parameter values of all (multi-selected) float ramp parameters with the main parameter (weak object pointer version) + static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray>& FloatRampParameters); + // raw pointer version + static void ReplaceAllFloatRampParameterPointsWithMainParameter(TArray& FloatRampParameters); + // helper + static void ReplaceFloatRampParameterPointsWithMainParameter(UHoudiniParameterRampFloat* Param, UHoudiniParameterRampFloat* MainParam); + + + // replace the children parameter values of all (multi-selected) color ramp parameters with the main parameter (weak object pointer version) + static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray>& ColorRampParameters); + // raw pointer version + static void ReplaceAllColorRampParameterPointsWithMainParameter(TArray& ColorRampParameters); + // helper + static void ReplaceColorRampParameterPointsWithMainParameter(UHoudiniParameterRampColor* Param, UHoudiniParameterRampColor* MainParame); + + + + // Create an insert event for a float ramp parameter + static void CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, + const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); + + // Create an insert event for a color ramp parameter + static void CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, + const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp); + + // Create a delete event for a float ramp parameter + static void CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex); + + // Create a delete event for a color ramp parameter + static void CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex); + + + private: + + template< class T > + static bool CastParameters( + TArray InParams, TArray& OutCastedParams); + + // + // Private helper functions for widget creation + // + + // Creates the default name widget, the parameter will then fill the value after + void CreateNameWidget(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); + + // Creates the default name widget, with an extra checkbox for disabling the the parameter update + void CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, TArray &InParams, bool WithLabel); + + FDetailWidgetRow* CreateNestedRow(IDetailCategoryBuilder & HouParameterCategory, TArray InParams, bool bDecreaseChildCount = true); // + + void CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArray& InParams); // + + void CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InParam, const bool& bIsShown); // + + void CreateWidgetMultiParmObjectButtons(TSharedPtr HorizontalBox, TArray InParams); // + + // Create the UI for ramp's curve editor. + FDetailWidgetRow* CreateWidgetRampCurveEditor(IDetailCategoryBuilder & HouParameterCategory, TArray &InParams); // + + // Create the UI for ramp's stop points. + void CreateWidgetRampPoints(IDetailCategoryBuilder& CategoryBuilder, FDetailWidgetRow* Row, UHoudiniParameter* InParameter, TArray< + UHoudiniParameter*>& InParams); // + + void PruneStack(); + + void RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam); + + public: + // Stores the created ramp curves + // In order to avoid being grabage collected, curves are added to root, thus need to handle GC manually. + // These points are for releasing the memory when the detail class are destroyed + TArray CreatedFloatRampCurves; + TArray CreatedColorRampCurves; + // The curve editors reference the UHoudini*Curves as "CurveOwners" as raw (non UObject) pointers, so we have + // to set their owners to null here before we destroy the Created*RampCuvers + TArray> CreatedColorGradientEditors; + TArray> CreatedFloatCurveEditors; + + private: + // The parameter directory is flattened with BFS inside of DFS. + // When a folderlist is encountered, it goes 'one step' of DFS, otherwise BFS. + // So that use a Stack structure to reconstruct the tree. + TArray> FolderStack; + + // Float Ramp currently being processed + UHoudiniParameterRampFloat* CurrentRampFloat; + + // Color Ramp currently being processed + UHoudiniParameterRampColor* CurrentRampColor; + + TArray CurrentRampParameterList; + + // Cached curve points of float ramp which being processed + TArray CurrentRampFloatPointsArray; + + // Cached curve points of color ramp which being processed + TArray CurrentRampColorPointsArray; + + // Cached color ramp curve which being processed + UHoudiniColorRampCurve* CurrentRampParameterColorCurve; + + // Cached float ramp curve which being processed + UHoudiniFloatRampCurve* CurrentRampParameterFloatCurve; + + FDetailWidgetRow * CurrentRampRow; + + + /* Variables for keeping expansion state after adding multiparm instance*/ + TMap AllMultiParms; + + // Cached the map of parameter id and folders/folder lists + TMap AllFoldersAndFolderLists; + + /* Variables for keeping expansion state after adding multiparm instance*/ + + TMap MultiParmInstanceIndices; + + // Number of remaining folders for current folder list + int32 CurrentFolderListSize = 0; + + // The folder list currently being processed + UHoudiniParameterFolderList* CurrentFolderList; + + // Cached child folders of current tabs + TArray CurrentTabs; + + TArray DividerLinePositions; + + SCustomizedBox* CurrentTabEndingRow; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp index 135426a94..49169caa9 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPI.cpp @@ -31,11 +31,18 @@ #include "HoudiniEngineEditorUtils.h" #include "HoudiniPublicAPIAssetWrapper.h" #include "HoudiniPublicAPIInputTypes.h" +#include "HoudiniEngineCommands.h" UHoudiniPublicAPI::UHoudiniPublicAPI() { } +bool +UHoudiniPublicAPI::IsSessionValid_Implementation() const +{ + return FHoudiniEngineCommands::IsSessionValid(); +} + void UHoudiniPublicAPI::CreateSession_Implementation() { @@ -172,6 +179,12 @@ UHoudiniPublicAPI::InstantiateAssetWithExistingWrapper_Implementation( return true; } +bool +UHoudiniPublicAPI::IsAssetCookingPaused_Implementation() const +{ + return FHoudiniEngineCommands::IsAssetCookingPaused(); +} + void UHoudiniPublicAPI::PauseAssetCooking_Implementation() { diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp index 43347d130..a5d2793df 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp @@ -1,4189 +1,4202 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniPublicAPIAssetWrapper.h" - -#include "HoudiniAssetActor.h" -#include "HoudiniEngineBakeUtils.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniOutputDetails.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniPDGManager.h" -#include "HoudiniPublicAPI.h" -#include "HoudiniPublicAPIBlueprintLib.h" -#include "HoudiniPublicAPIInputTypes.h" - - -FHoudiniPublicAPIRampPoint::FHoudiniPublicAPIRampPoint() - : Position(0) - , Interpolation(EHoudiniPublicAPIRampInterpolationType::LINEAR) -{ -} - -FHoudiniPublicAPIRampPoint::FHoudiniPublicAPIRampPoint( - const float InPosition, - const EHoudiniPublicAPIRampInterpolationType InInterpolation) - : Position(InPosition) - , Interpolation(InInterpolation) -{ - -} - -FHoudiniPublicAPIFloatRampPoint::FHoudiniPublicAPIFloatRampPoint() - : Value(0) -{ -} - -FHoudiniPublicAPIFloatRampPoint::FHoudiniPublicAPIFloatRampPoint( - const float InPosition, - const float InValue, - const EHoudiniPublicAPIRampInterpolationType InInterpolation) - : FHoudiniPublicAPIRampPoint(InPosition, InInterpolation) - , Value(InValue) -{ - -} - -FHoudiniPublicAPIColorRampPoint::FHoudiniPublicAPIColorRampPoint() - : Value(FLinearColor::Black) -{ -} - -FHoudiniPublicAPIColorRampPoint::FHoudiniPublicAPIColorRampPoint( - const float InPosition, - const FLinearColor& InValue, - const EHoudiniPublicAPIRampInterpolationType InInterpolation) - : FHoudiniPublicAPIRampPoint(InPosition, InInterpolation) - , Value(InValue) -{ - -} - -FHoudiniParameterTuple::FHoudiniParameterTuple() - : BoolValues() - , Int32Values() - , FloatValues() - , StringValues() -{ -} - -FHoudiniParameterTuple::FHoudiniParameterTuple(const bool& InValue) - : FHoudiniParameterTuple() -{ - BoolValues.Add(InValue); -} - -FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) - : BoolValues(InValues) -{ -} - -FHoudiniParameterTuple::FHoudiniParameterTuple(const int32& InValue) - : FHoudiniParameterTuple() -{ - Int32Values.Add(InValue); -} - -FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) - : Int32Values(InValues) -{ -} - -FHoudiniParameterTuple::FHoudiniParameterTuple(const float& InValue) - : FHoudiniParameterTuple() -{ - FloatValues.Add(InValue); -} - -FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) - : FloatValues(InValues) -{ -} - -FHoudiniParameterTuple::FHoudiniParameterTuple(const FString& InValue) - : FHoudiniParameterTuple() -{ - StringValues.Add(InValue); -} - -FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) - : StringValues(InValues) -{ -} - -FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InRampPoints) - : FloatRampPoints(InRampPoints) -{ -} - -FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InRampPoints) - : ColorRampPoints(InRampPoints) -{ -} - -// -// UHoudiniPublicAPIAssetWrapper -// - - -UHoudiniPublicAPIAssetWrapper::UHoudiniPublicAPIAssetWrapper() - : HoudiniAssetObject(nullptr) - , bAssetLinkSetupAttemptComplete(false) -{ - -} - -UHoudiniPublicAPIAssetWrapper* -UHoudiniPublicAPIAssetWrapper::CreateWrapper(UObject* InOuter, UObject* InHoudiniAssetActorOrComponent) -{ - if (!IsValid(InHoudiniAssetActorOrComponent)) - return nullptr; - - // Check if InHoudiniAssetActorOrComponent is supported - if (!CanWrapHoudiniObject(InHoudiniAssetActorOrComponent)) - return nullptr; - - UHoudiniPublicAPIAssetWrapper* NewWrapper = CreateEmptyWrapper(InOuter); - if (!IsValid(NewWrapper)) - return nullptr; - - // If we cannot wrap the specified actor, return nullptr. - if (!NewWrapper->WrapHoudiniAssetObject(InHoudiniAssetActorOrComponent)) - { - NewWrapper->MarkPendingKill(); - return nullptr; - } - - return NewWrapper; -} - -UHoudiniPublicAPIAssetWrapper* -UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(UObject* InOuter) -{ - UObject* const Outer = InOuter ? InOuter : GetTransientPackage(); - UClass* const Class = StaticClass(); - UHoudiniPublicAPIAssetWrapper* NewWrapper = NewObject( - Outer, Class, - MakeUniqueObjectName(Outer, Class)); - if (!IsValid(NewWrapper)) - return nullptr; - - return NewWrapper; -} - -bool -UHoudiniPublicAPIAssetWrapper::CanWrapHoudiniObject(UObject* InObject) -{ - if (!IsValid(InObject)) - return false; - - return InObject->IsA() || InObject->IsA(); -} - -bool -UHoudiniPublicAPIAssetWrapper::GetTemporaryCookFolder_Implementation(FDirectoryPath& OutDirectoryPath) const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - OutDirectoryPath = HAC->TemporaryCookFolder; - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetTemporaryCookFolder_Implementation(const FDirectoryPath& InDirectoryPath) const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - if (HAC->TemporaryCookFolder.Path != InDirectoryPath.Path) - HAC->TemporaryCookFolder = InDirectoryPath; - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetBakeFolder_Implementation(FDirectoryPath& OutDirectoryPath) const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - OutDirectoryPath = HAC->BakeFolder; - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetBakeFolder_Implementation(const FDirectoryPath& InDirectoryPath) const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - if (HAC->BakeFolder.Path != InDirectoryPath.Path) - HAC->BakeFolder = InDirectoryPath; - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::BakeAllOutputs_Implementation() -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( - HAC, - HAC->bReplacePreviousBake, - HAC->HoudiniEngineBakeOption, - HAC->bRemoveOutputAfterBake, - HAC->bRecenterBakedActors); -} - -bool -UHoudiniPublicAPIAssetWrapper::BakeAllOutputsWithSettings_Implementation( - EHoudiniEngineBakeOption InBakeOption, - bool bInReplacePreviousBake, - bool bInRemoveTempOutputsOnSuccess, - bool bInRecenterBakedActors) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(HAC, bInReplacePreviousBake, InBakeOption, bInRemoveTempOutputsOnSuccess, bInRecenterBakedActors); -} - -bool -UHoudiniPublicAPIAssetWrapper::SetAutoBakeEnabled_Implementation(const bool bInAutoBakeEnabled) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - HAC->SetBakeAfterNextCookEnabled(bInAutoBakeEnabled); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::IsAutoBakeEnabled_Implementation() const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - return HAC->IsBakeAfterNextCookEnabled(); -} - -bool -UHoudiniPublicAPIAssetWrapper::SetBakeMethod_Implementation(const EHoudiniEngineBakeOption InBakeMethod) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - HAC->HoudiniEngineBakeOption = InBakeMethod; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetBakeMethod_Implementation(EHoudiniEngineBakeOption& OutBakeMethod) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - OutBakeMethod = HAC->HoudiniEngineBakeOption; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetRemoveOutputAfterBake_Implementation(const bool bInRemoveOutputAfterBake) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - HAC->bRemoveOutputAfterBake = bInRemoveOutputAfterBake; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetRemoveOutputAfterBake_Implementation() const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - return HAC->bRemoveOutputAfterBake; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetRecenterBakedActors_Implementation(const bool bInRecenterBakedActors) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - HAC->bRecenterBakedActors = bInRecenterBakedActors; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetRecenterBakedActors_Implementation() const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - return HAC->bRecenterBakedActors; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetReplacePreviousBake_Implementation(const bool bInReplacePreviousBake) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - HAC->bReplacePreviousBake = bInReplacePreviousBake; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetReplacePreviousBake_Implementation() const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - return HAC->bReplacePreviousBake; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetValidHoudiniAssetActorWithError(AHoudiniAssetActor*& OutActor) const -{ - AHoudiniAssetActor* const Actor = GetHoudiniAssetActor(); - if (!IsValid(Actor)) - { - SetErrorMessage( - TEXT("Could not find a valid AHoudiniAssetActor for the wrapped asset, or no asset has been wrapped.")); - return false; - } - - OutActor = Actor; - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetValidHoudiniAssetComponentWithError(UHoudiniAssetComponent*& OutHAC) const -{ - UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); - if (!IsValid(HAC)) - { - SetErrorMessage( - TEXT("Could not find a valid HoudiniAssetComponent for the wrapped asset, or no asset has been wrapped.")); - return false; - } - - OutHAC = HAC; - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetValidOutputAtWithError(const int32 InOutputIndex, UHoudiniOutput*& OutOutput) const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - // Check if InOutputIndex is a valid/in-range index - const int32 NumOutputs = HAC->GetNumOutputs(); - if (InOutputIndex < 0 || InOutputIndex >= NumOutputs) - { - SetErrorMessage(FString::Printf( - TEXT("Output index %d is out of range [0, %d]"), InOutputIndex, NumOutputs)); - return false; - } - - UHoudiniOutput* const Output= HAC->GetOutputAt(InOutputIndex); - if (!IsValid(Output)) - { - SetErrorMessage(FString::Printf(TEXT("Output at index %d is invalid."), InOutputIndex)); - return false; - } - - OutOutput = Output; - return true; -} - - -UHoudiniPDGAssetLink* -UHoudiniPublicAPIAssetWrapper::GetHoudiniPDGAssetLink() const -{ - UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); - if (!IsValid(HAC)) - return nullptr; - - return HAC->GetPDGAssetLink(); -} - -bool -UHoudiniPublicAPIAssetWrapper::GetValidHoudiniPDGAssetLinkWithError(UHoudiniPDGAssetLink*& OutAssetLink) const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - UHoudiniPDGAssetLink* const AssetLink = HAC->GetPDGAssetLink(); - if (!IsValid(AssetLink)) - { - SetErrorMessage( - TEXT("Could not find a valid HoudiniPDGAssetLink for the wrapped asset. Does it contain a TOP network?")); - return false; - } - - OutAssetLink = AssetLink; - return true; -} - -void -UHoudiniPublicAPIAssetWrapper::ClearHoudiniAssetObject_Implementation() -{ - UHoudiniPDGAssetLink* const AssetLink = GetHoudiniPDGAssetLink(); - if (IsValid(AssetLink)) - { - if (OnPDGPostTOPNetworkCookDelegateHandle.IsValid()) - AssetLink->GetOnPostTOPNetworkCookDelegate().Remove(OnPDGPostTOPNetworkCookDelegateHandle); - if (OnPDGPostBakeDelegateHandle.IsValid()) - AssetLink->GetOnPostBakeDelegate().Remove(OnPDGPostBakeDelegateHandle); - } - - bAssetLinkSetupAttemptComplete = false; - - FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefinedDelegate().Remove(OnHoudiniProxyMeshesRefinedDelegateHandle); - - UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); - if (IsValid(HAC)) - { - if (OnAssetStateChangeDelegateHandle.IsValid()) - HAC->GetOnAssetStateChangeDelegate().Remove(OnAssetStateChangeDelegateHandle); - if (OnPostCookDelegateHandle.IsValid()) - HAC->GetOnPostCookDelegate().Remove(OnPostCookDelegateHandle); - if (OnPostBakeDelegateHandle.IsValid()) - HAC->GetOnPostBakeDelegate().Remove(OnPostBakeDelegateHandle); - } - - OnPDGPostTOPNetworkCookDelegateHandle.Reset(); - OnPDGPostBakeDelegateHandle.Reset(); - OnAssetStateChangeDelegateHandle.Reset(); - OnPostCookDelegateHandle.Reset(); - OnPostBakeDelegateHandle.Reset(); - OnHoudiniProxyMeshesRefinedDelegateHandle.Reset(); - - HoudiniAssetObject = nullptr; - CachedHoudiniAssetActor = nullptr; - CachedHoudiniAssetComponent = nullptr; -} - -bool -UHoudiniPublicAPIAssetWrapper::WrapHoudiniAssetObject_Implementation(UObject* InHoudiniAssetObjectToWrap) -{ - // If InHoudiniAssetObjectToWrap is null, just unwrap any currently wrapped asset - if (!InHoudiniAssetObjectToWrap) - { - ClearHoudiniAssetObject(); - return true; - } - - // Check if InHoudiniAssetObjectToWrap is supported - if (!CanWrapHoudiniObject(InHoudiniAssetObjectToWrap)) - { - UClass* const ObjectClass = IsValid(InHoudiniAssetObjectToWrap) ? InHoudiniAssetObjectToWrap->GetClass() : nullptr; - SetErrorMessage(FString::Printf( - TEXT("Cannot wrap objects of class '%s'."), ObjectClass ? *(ObjectClass->GetName()) : TEXT("Unknown"))); - return false; - } - - // First unwrap/unbind if we are currently wrapping an instantiated asset - ClearHoudiniAssetObject(); - - HoudiniAssetObject = InHoudiniAssetObjectToWrap; - - // Cache the HoudiniAssetActor and HoudiniAssetComponent - if (HoudiniAssetObject->IsA()) - { - CachedHoudiniAssetActor = Cast(InHoudiniAssetObjectToWrap); - CachedHoudiniAssetComponent = CachedHoudiniAssetActor->HoudiniAssetComponent; - } - else if (HoudiniAssetObject->IsA()) - { - CachedHoudiniAssetComponent = Cast(InHoudiniAssetObjectToWrap); - CachedHoudiniAssetActor = Cast(CachedHoudiniAssetComponent->GetOwner()); - } - - UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); - if (IsValid(HAC)) - { - // Bind to HandleOnHoudiniAssetStateChange from the HAC: we also implement IHoudiniAssetStateEvents, and - // in the default implementation HandleOnHoudiniAssetStateChange will call the appropriate Handle functions - // for PostInstantiate, PostCook etc - OnAssetStateChangeDelegateHandle = HAC->GetOnAssetStateChangeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentStateChange")); - OnPostCookDelegateHandle = HAC->GetOnPostCookDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentPostCook")); - OnPostBakeDelegateHandle = HAC->GetOnPostBakeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentPostBake")); - } - - OnHoudiniProxyMeshesRefinedDelegateHandle = FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefinedDelegate().AddUFunction(this, TEXT("HandleOnHoudiniProxyMeshesRefinedGlobal")); - - // PDG asset link bindings: We attempt to bind to PDG here, but it likely is not available yet. - // We have to wait until post instantiation in order to know if there is a PDG asset link - // for this HDA. This is checked again in HandleOnHoudiniAssetComponentStateChange and sets - // bAssetLinkSetupAttemptComplete. - BindToPDGAssetLink(); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::DeleteInstantiatedAsset_Implementation() -{ - AHoudiniAssetActor* AssetActor = nullptr; - if (!GetValidHoudiniAssetActorWithError(AssetActor)) - return false; - - // Unbind / unwrap the HDA actor - ClearHoudiniAssetObject(); - AssetActor->Destroy(); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::Rebuild_Implementation() -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - HAC->MarkAsNeedRebuild(); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::Recook_Implementation() -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - HAC->MarkAsNeedCook(); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetAutoCookingEnabled_Implementation(const bool bInSetEnabled) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - if (HAC->IsCookingEnabled() == bInSetEnabled) - return false; - - HAC->SetCookingEnabled(bInSetEnabled); - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::IsAutoCookingEnabled_Implementation() const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - return HAC->IsCookingEnabled(); -} - -bool -UHoudiniPublicAPIAssetWrapper::SetFloatParameterValue_Implementation(FName InParameterTupleName, float InValue, int32 InAtIndex, bool bInMarkChanged) -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is a float - const EHoudiniParameterType ParamType = Param->GetParameterType(); - bool bDidChangeValue = false; - if (ParamType == EHoudiniParameterType::Float) - { - UHoudiniParameterFloat* FloatParam = Cast(Param); - if (!IsValid(FloatParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - if (InAtIndex >= FloatParam->GetNumberOfValues()) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FloatParam->GetNumberOfValues())); - return false; - } - - bDidChangeValue = FloatParam->SetValueAt(InValue, InAtIndex); - } - else if (ParamType == EHoudiniParameterType::Color) - { - UHoudiniParameterColor* ColorParam = Cast(Param); - if (!IsValid(ColorParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - static const int32 NumColorChannels = 4; - if (InAtIndex >= NumColorChannels) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, NumColorChannels)); - return false; - } - - FLinearColor CurrentColorValue = ColorParam->GetColorValue(); - if (CurrentColorValue.Component(InAtIndex) != InValue) - { - CurrentColorValue.Component(InAtIndex) = InValue; - ColorParam->SetColorValue(CurrentColorValue); - bDidChangeValue = true; - } - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple '%s' is of a type that cannot be set via SetFloatParamterValue."), *InParameterTupleName.ToString())); - return false; - } - - if (bDidChangeValue && bInMarkChanged) - Param->MarkChanged(true); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetFloatParameterValue_Implementation(FName InParameterTupleName, float& OutValue, int32 InAtIndex) const -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is a float - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::Float) - { - UHoudiniParameterFloat* FloatParam = Cast(Param); - if (!IsValid(FloatParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - if (InAtIndex >= FloatParam->GetNumberOfValues()) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FloatParam->GetNumberOfValues())); - return false; - } - - return FloatParam->GetValueAt(InAtIndex, OutValue); - } - else if (ParamType == EHoudiniParameterType::Color) - { - UHoudiniParameterColor* ColorParam = Cast(Param); - if (!IsValid(ColorParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - static const int32 NumColorChannels = 4; - if (InAtIndex >= NumColorChannels) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, NumColorChannels)); - return false; - } - - OutValue = ColorParam->GetColorValue().Component(InAtIndex); - return true; - } - - return false; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetColorParameterValue_Implementation(FName InParameterTupleName, const FLinearColor& InValue, bool bInMarkChanged) -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is a float - const EHoudiniParameterType ParamType = Param->GetParameterType(); - bool bDidChangeValue = false; - if (ParamType == EHoudiniParameterType::Color) - { - UHoudiniParameterColor* ColorParam = Cast(Param); - if (!IsValid(ColorParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - bDidChangeValue = ColorParam->SetColorValue(InValue); - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple '%s' is of a type that cannot be set via SetColorParamterValue."), *InParameterTupleName.ToString())); - return false; - } - - if (bDidChangeValue && bInMarkChanged) - Param->MarkChanged(true); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetColorParameterValue_Implementation(FName InParameterTupleName, FLinearColor& OutValue) const -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is a float - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::Color) - { - UHoudiniParameterColor* ColorParam = Cast(Param); - if (!IsValid(ColorParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - OutValue = ColorParam->GetColorValue(); - return true; - } - - return false; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetIntParameterValue_Implementation(FName InParameterTupleName, int32 InValue, int32 InAtIndex, bool bInMarkChanged) -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - bool bDidChangeValue = false; - if (ParamType == EHoudiniParameterType::Int) - { - UHoudiniParameterInt* IntParam = Cast(Param); - if (!IsValid(IntParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - if (InAtIndex >= IntParam->GetNumberOfValues()) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, IntParam->GetNumberOfValues())); - return false; - } - - bDidChangeValue = IntParam->SetValueAt(InValue, InAtIndex); - } - else if (ParamType == EHoudiniParameterType::IntChoice) - { - UHoudiniParameterChoice* ChoiceParam = Cast(Param); - if (!IsValid(ChoiceParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - bDidChangeValue = ChoiceParam->SetIntValue(InValue); - } - else if (ParamType == EHoudiniParameterType::MultiParm) - { - UHoudiniParameterMultiParm* MultiParam = Cast(Param); - if (!IsValid(MultiParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - bDidChangeValue = MultiParam->SetValue(InValue); - } - else if (ParamType == EHoudiniParameterType::Toggle) - { - UHoudiniParameterToggle* ToggleParam = Cast(Param); - if (!IsValid(ToggleParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - bDidChangeValue = ToggleParam->SetValueAt(InValue != 0, InAtIndex); - } - else if (ParamType == EHoudiniParameterType::Folder) - { - UHoudiniParameterFolder* FolderParam = Cast(Param); - if (!IsValid(FolderParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - const bool NewValue = InValue != 0; - if (FolderParam->IsChosen() != NewValue) - { - FolderParam->SetChosen(NewValue); - bDidChangeValue = true; - } - } - else if (ParamType == EHoudiniParameterType::FloatRamp || ParamType == EHoudiniParameterType::ColorRamp) - { - // For ramps we have to use the appropriate function so that delete/insert operations are managed correctly - bDidChangeValue = SetRampParameterNumPoints(InParameterTupleName, InValue); - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple '%s' is of a type that cannot be set via SetIntParameterValue."), *InParameterTupleName.ToString())); - return false; - } - - if (bDidChangeValue && bInMarkChanged) - Param->MarkChanged(true); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetIntParameterValue_Implementation(FName InParameterTupleName, int32& OutValue, int32 InAtIndex) const -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::Int) - { - UHoudiniParameterInt* IntParam = Cast(Param); - if (!IsValid(IntParam)) - return false; - - if (InAtIndex >= IntParam->GetNumberOfValues()) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, IntParam->GetNumberOfValues())); - return false; - } - - return IntParam->GetValueAt(InAtIndex, OutValue); - } - else if (ParamType == EHoudiniParameterType::IntChoice) - { - UHoudiniParameterChoice* ChoiceParam = Cast(Param); - if (!IsValid(ChoiceParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - OutValue = ChoiceParam->GetIntValue(ChoiceParam->GetIntValueIndex()); - return true; - } - else if (ParamType == EHoudiniParameterType::MultiParm) - { - UHoudiniParameterMultiParm* MultiParam = Cast(Param); - if (!IsValid(MultiParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - OutValue = MultiParam->GetValue(); - return true; - } - else if (ParamType == EHoudiniParameterType::Toggle) - { - UHoudiniParameterToggle* ToggleParam = Cast(Param); - if (!IsValid(ToggleParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - OutValue = ToggleParam->GetValueAt(InAtIndex); - return true; - } - else if (ParamType == EHoudiniParameterType::Folder) - { - UHoudiniParameterFolder* FolderParam = Cast(Param); - if (!IsValid(FolderParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - OutValue = FolderParam->IsChosen(); - return true; - } - - return false; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetBoolParameterValue_Implementation(FName InParameterTupleName, bool InValue, int32 InAtIndex, bool bInMarkChanged) -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - bool bDidChangeValue = false; - if (ParamType == EHoudiniParameterType::Toggle) - { - UHoudiniParameterToggle* ToggleParam = Cast(Param); - if (!IsValid(ToggleParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - bDidChangeValue = ToggleParam->SetValueAt(InValue, InAtIndex); - } - else if (ParamType == EHoudiniParameterType::Folder) - { - UHoudiniParameterFolder* FolderParam = Cast(Param); - if (!IsValid(FolderParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - if (FolderParam->IsChosen() != InValue) - { - FolderParam->SetChosen(InValue); - bDidChangeValue = true; - } - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple '%s' is of a type that cannot be set via SetBoolParameterValue."), *InParameterTupleName.ToString())); - return false; - } - - if (bDidChangeValue && bInMarkChanged) - Param->MarkChanged(true); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetBoolParameterValue_Implementation(FName InParameterTupleName, bool& OutValue, int32 InAtIndex) const -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::Toggle) - { - UHoudiniParameterToggle* ToggleParam = Cast(Param); - if (!IsValid(ToggleParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - OutValue = ToggleParam->GetValueAt(InAtIndex); - return true; - } - else if (ParamType == EHoudiniParameterType::Folder) - { - UHoudiniParameterFolder* FolderParam = Cast(Param); - if (!IsValid(FolderParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - OutValue = FolderParam->IsChosen(); - return true; - } - - return false; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetStringParameterValue_Implementation(FName InParameterTupleName, const FString& InValue, int32 InAtIndex, bool bInMarkChanged) -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - bool bDidChangeValue = false; - if (ParamType == EHoudiniParameterType::String || ParamType == EHoudiniParameterType::StringAssetRef) - { - UHoudiniParameterString* StringParam = Cast(Param); - if (!IsValid(StringParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - if (InAtIndex >= StringParam->GetNumberOfValues()) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); - return false; - } - - // We have to handle asset references differently - if (ParamType == EHoudiniParameterType::StringAssetRef) - { - // Find/load the asset, make sure it is a valid reference/object - const FSoftObjectPath AssetRef(InValue); - UObject* const Asset = AssetRef.TryLoad(); - if (IsValid(Asset)) - { - UObject* const CurrentAsset = StringParam->GetAssetAt(InAtIndex); - if (CurrentAsset != Asset) - { - StringParam->SetAssetAt(Asset, InAtIndex); - bDidChangeValue = true; - } - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Asset reference '%s' is invalid. Not setting parameter value."), *InValue)); - return false; - } - } - else - { - bDidChangeValue = StringParam->SetValueAt(InValue, InAtIndex); - } - } - else if (ParamType == EHoudiniParameterType::StringChoice) - { - UHoudiniParameterChoice* ChoiceParam = Cast(Param); - if (!IsValid(ChoiceParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - bDidChangeValue = ChoiceParam->SetStringValue(InValue); - } - else if (ParamType == EHoudiniParameterType::File || ParamType == EHoudiniParameterType::FileDir || - ParamType == EHoudiniParameterType::FileGeo || ParamType == EHoudiniParameterType::FileImage) - { - UHoudiniParameterFile* FileParam = Cast(Param); - if (!IsValid(FileParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - if (InAtIndex >= FileParam->GetNumValues()) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FileParam->GetNumValues())); - return false; - } - - bDidChangeValue = FileParam->SetValueAt(InValue, InAtIndex); - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple '%s' is of a type that cannot be set via SetStringParameterValue."), *InParameterTupleName.ToString())); - return false; - } - - if (bDidChangeValue && bInMarkChanged) - Param->MarkChanged(true); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetStringParameterValue_Implementation(FName InParameterTupleName, FString& OutValue, int32 InAtIndex) const -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::String || ParamType == EHoudiniParameterType::StringAssetRef) - { - UHoudiniParameterString* StringParam = Cast(Param); - if (!IsValid(StringParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - if (InAtIndex >= StringParam->GetNumberOfValues()) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); - return false; - } - - // For asset references: get the asset, and then get the string reference from it and return that. - // If the asset is null, return the empty string. - if (ParamType == EHoudiniParameterType::StringAssetRef) - { - UObject* const Asset = StringParam->GetAssetAt(InAtIndex); - if (IsValid(Asset)) - { - OutValue = UHoudiniParameterString::GetAssetReference(Asset); - } - else - { - OutValue.Empty(); - } - } - else - { - OutValue = StringParam->GetValueAt(InAtIndex); - } - return true; - } - else if (ParamType == EHoudiniParameterType::StringChoice) - { - UHoudiniParameterChoice* ChoiceParam = Cast(Param); - if (!IsValid(ChoiceParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - OutValue = ChoiceParam->GetStringValue(); - return true; - } - else if (ParamType == EHoudiniParameterType::File || ParamType == EHoudiniParameterType::FileDir || - ParamType == EHoudiniParameterType::FileGeo || ParamType == EHoudiniParameterType::FileImage) - { - UHoudiniParameterFile* FileParam = Cast(Param); - if (!IsValid(FileParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - if (InAtIndex >= FileParam->GetNumValues()) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FileParam->GetNumValues())); - return false; - } - - OutValue = FileParam->GetValueAt(InAtIndex); - return true; - } - - return false; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetAssetRefParameterValue_Implementation(FName InParameterTupleName, UObject* InValue, int32 InAtIndex, bool bInMarkChanged) -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - bool bDidChangeValue = false; - if (ParamType == EHoudiniParameterType::StringAssetRef) - { - UHoudiniParameterString* StringParam = Cast(Param); - if (!IsValid(StringParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - if (InAtIndex >= StringParam->GetNumberOfValues()) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); - return false; - } - - // Find/load the asset, make sure it is a valid reference/object - UObject* const CurrentAsset = StringParam->GetAssetAt(InAtIndex); - if (CurrentAsset != InValue) - { - StringParam->SetAssetAt(InValue, InAtIndex); - bDidChangeValue = true; - } - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple '%s' is of a type that cannot be set via SetAssetRefParamter."), *InParameterTupleName.ToString())); - return false; - } - - if (bDidChangeValue && bInMarkChanged) - Param->MarkChanged(true); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetAssetRefParameterValue_Implementation(FName InParameterTupleName, UObject*& OutValue, int32 InAtIndex) const -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::StringAssetRef) - { - UHoudiniParameterString* StringParam = Cast(Param); - if (!IsValid(StringParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - if (InAtIndex >= StringParam->GetNumberOfValues()) - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); - return false; - } - - OutValue = StringParam->GetAssetAt(InAtIndex); - return true; - } - - return false; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetRampParameterNumPoints_Implementation(FName InParameterTupleName, const int32 InNumPoints) const -{ - if (InNumPoints < 1) - { - SetErrorMessage(TEXT("InNumPoints must be >= 1.")); - return false; - } - - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and - // not the main points. - // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); - // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); - const bool bUseCachedPoints = !Param->IsAutoUpdate(); - - UHoudiniParameterRampFloat* FloatRampParam = nullptr; - UHoudiniParameterRampColor* ColorRampParam = nullptr; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::FloatRamp) - { - FloatRampParam = Cast(Param); - if (!IsValid(FloatRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else if (ParamType == EHoudiniParameterType::ColorRamp) - { - ColorRampParam = Cast(Param); - if (!IsValid(ColorRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParameterTupleName.ToString()))); - return false; - } - - if (bUseCachedPoints) - { - // When using the cached points we only have to resize the array - if (FloatRampParam) - { - const int32 CurrentNumPoints = FloatRampParam->CachedPoints.Num(); - if (CurrentNumPoints != InNumPoints) - { - FloatRampParam->CachedPoints.SetNum(InNumPoints); - // FloatRampParam->MarkChanged(true); - } - } - else - { - const int32 CurrentNumPoints = ColorRampParam->CachedPoints.Num(); - if (CurrentNumPoints != InNumPoints) - { - ColorRampParam->CachedPoints.SetNum(InNumPoints); - // ColorRampParam->MarkChanged(true); - } - } - - // Update the ramp's widget if it is currently visible/selected - const bool bForceFullUpdate = true; - FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); - } - else - { - int32 NumPendingInsertOperations = 0; - int32 NumPendingDeleteOperations = 0; - TSet InstanceIndexesPendingDelete; - TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; - for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) - { - if (!IsValid(Event)) - continue; - - if (Event->IsInsertEvent()) - NumPendingInsertOperations++; - else if (Event->IsDeleteEvent()) - { - InstanceIndexesPendingDelete.Add(Event->DeleteInstanceIndex); - NumPendingDeleteOperations++; - } - } - - const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); - int32 CurrentNumPoints = PointsArraySize + NumPendingInsertOperations - NumPendingDeleteOperations; - - if (InNumPoints < CurrentNumPoints) - { - // When deleting points, first remove pending insert operations from the end - if (NumPendingInsertOperations > 0) - { - const int32 NumEvents = ModificationEvents.Num(); - TArray TempModificationArray; - TempModificationArray.Reserve(NumEvents); - - for (int32 Index = NumEvents - 1; Index >= 0; --Index) - { - UHoudiniParameterRampModificationEvent* const Event = ModificationEvents[Index]; - if (InNumPoints < CurrentNumPoints && IsValid(Event) && Event->IsInsertEvent()) - { - CurrentNumPoints--; - NumPendingInsertOperations--; - continue; - } - - TempModificationArray.Add(Event); - } - - Algo::Reverse(TempModificationArray); - ModificationEvents = MoveTemp(TempModificationArray); - } - - // If we still have points to delete... - if (InNumPoints < CurrentNumPoints) - { - // Deleting points, add delete operations, deleting from the end of Points (points that are not yet - // pending delete) - for (int32 Index = PointsArraySize - 1; (InNumPoints < CurrentNumPoints && Index >= 0); --Index) - { - int32 InstanceIndex = INDEX_NONE; - - if (FloatRampParam) - { - UHoudiniParameterRampFloatPoint const* const PointData = FloatRampParam->Points[Index]; - if (!IsValid(PointData)) - continue; - - InstanceIndex = PointData->InstanceIndex; - } - else - { - UHoudiniParameterRampColorPoint const* const PointData = ColorRampParam->Points[Index]; - if (!IsValid(PointData)) - continue; - - InstanceIndex = PointData->InstanceIndex; - } - - if (!InstanceIndexesPendingDelete.Contains(InstanceIndex)) - { - InstanceIndexesPendingDelete.Add(InstanceIndex); - CurrentNumPoints--; - - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - FloatRampParam, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (DeleteEvent) - { - if (FloatRampParam) - { - DeleteEvent->SetFloatRampEvent(); - } - else - { - DeleteEvent->SetColorRampEvent(); - } - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InstanceIndex; - - ModificationEvents.Add(DeleteEvent); - } - } - } - } - - Param->MarkChanged(true); - } - else if (InNumPoints > CurrentNumPoints) - { - // Adding points, add insert operations - while (InNumPoints > CurrentNumPoints) - { - CurrentNumPoints++; - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - Param, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (InsertEvent) - { - if (FloatRampParam) - { - InsertEvent->SetFloatRampEvent(); - } - else - { - InsertEvent->SetColorRampEvent(); - } - InsertEvent->SetInsertEvent(); - // Leave point position, value and interpolation at default - - ModificationEvents.Add(InsertEvent); - } - } - - Param->MarkChanged(true); - } - - // If at this point InNumPoints != CurrentNumPoints then something went wrong, we couldn't delete all the - // desired points - if (InNumPoints != CurrentNumPoints) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected error: could not delete the required number of ramp points " - "(target # points = %d; have # points %d)."), InNumPoints, CurrentNumPoints)); - return false; - } - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetRampParameterNumPoints_Implementation(FName InParameterTupleName, int32& OutNumPoints) const -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and - // not the main points. - // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); - // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); - const bool bUseCachedPoints = !Param->IsAutoUpdate(); - - UHoudiniParameterRampFloat* FloatRampParam = nullptr; - UHoudiniParameterRampColor* ColorRampParam = nullptr; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::FloatRamp) - { - FloatRampParam = Cast(Param); - if (!IsValid(FloatRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else if (ParamType == EHoudiniParameterType::ColorRamp) - { - ColorRampParam = Cast(Param); - if (!IsValid(ColorRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParameterTupleName.ToString()))); - return false; - } - - if (bUseCachedPoints) - { - // When using the cached points we only have to resize the array - if (FloatRampParam) - { - OutNumPoints = FloatRampParam->CachedPoints.Num(); - } - else - { - OutNumPoints = ColorRampParam->CachedPoints.Num(); - } - } - else - { - int32 NumPendingInsertOperations = 0; - int32 NumPendingDeleteOperations = 0; - TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; - for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) - { - if (!IsValid(Event)) - continue; - - if (Event->IsInsertEvent()) - NumPendingInsertOperations++; - else if (Event->IsDeleteEvent()) - NumPendingDeleteOperations++; - } - - const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); - OutNumPoints = PointsArraySize + NumPendingInsertOperations - NumPendingDeleteOperations; - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetFloatRampParameterPointValue_Implementation( - FName InParameterTupleName, - const int32 InPointIndex, - const float InPointPosition, - const float InPointValue, - const EHoudiniPublicAPIRampInterpolationType InInterpolationType, - const bool bInMarkChanged) -{ - return SetRampParameterPointValue( - InParameterTupleName, InPointIndex, InPointPosition, InPointValue, FLinearColor::Black, InInterpolationType, bInMarkChanged); -} - -bool -UHoudiniPublicAPIAssetWrapper::GetFloatRampParameterPointValue_Implementation( - FName InParameterTupleName, - const int32 InPointIndex, - float& OutPointPosition, - float& OutPointValue, - EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const -{ - FLinearColor ColorValue; - return GetRampParameterPointValue( - InParameterTupleName, InPointIndex, OutPointPosition, OutPointValue, ColorValue, OutInterpolationType); -} - -bool -UHoudiniPublicAPIAssetWrapper::SetFloatRampParameterPoints_Implementation( - FName InParameterTupleName, - const TArray& InRampPoints, - const bool bInMarkChanged) -{ - const int32 TargetNumPoints = InRampPoints.Num(); - if (TargetNumPoints == 0) - { - SetErrorMessage(TEXT("InRampPoints must have at least one entry.")); - return false; - } - - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - UHoudiniParameterRampFloat* FloatRampParam = nullptr; - - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::FloatRamp) - { - FloatRampParam = Cast(Param); - if (!IsValid(FloatRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else - { - SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); - return false; - } - - // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and - // not the main points. - // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); - // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); - const bool bUseCachedPoints = !Param->IsAutoUpdate(); - - // Set the ramp point count to match the size of InRampPoints - if (!SetRampParameterNumPoints(InParameterTupleName, TargetNumPoints)) - return false; - - // Get all ramp points (INDEX_NONE == get all points) - TArray> RampPointData; - if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) - return false; - - // Check that we fetched the correct number of point data objects - if (RampPointData.Num() != TargetNumPoints) - { - SetErrorMessage(FString::Printf(TEXT("Failed to set the number of ramp points to %d."), TargetNumPoints)); - return false; - } - - for (int32 Index = 0; Index < TargetNumPoints; ++Index) - { - TPair const& Entry = RampPointData[Index]; - UObject* const PointData = Entry.Key; - const bool bIsPointData = Entry.Value; - if (!IsValid(PointData)) - return false; - - const FHoudiniPublicAPIFloatRampPoint& NewRampPoint = InRampPoints[Index]; - const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( - NewRampPoint.Interpolation); - - if (bIsPointData) - { - UHoudiniParameterRampFloatPoint* FloatPointData = Cast(PointData); - if (!IsValid(FloatPointData)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - - if (bUseCachedPoints) - { - // When setting the cached points, we set the values directly instead of using the setters, but we set - // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed - if (FloatPointData->Position != NewRampPoint.Position) - { - FloatPointData->Position = NewRampPoint.Position; - FloatRampParam->bCaching = true; - } - - if (FloatPointData->Value != NewRampPoint.Value) - { - FloatPointData->Value = NewRampPoint.Value; - FloatRampParam->bCaching = true; - } - - if (FloatPointData->Interpolation != NewInterpolation) - { - FloatPointData->Interpolation = NewInterpolation; - FloatRampParam->bCaching = true; - } - } - else - { - // When setting the main points, we set the values using the setters on the point data but still manually - // mark the position/value/interpolation parent parameters as changed - if (FloatPointData->Position != NewRampPoint.Position && FloatPointData->PositionParentParm) - { - FloatPointData->SetPosition(NewRampPoint.Position); - if (bInMarkChanged) - FloatPointData->PositionParentParm->MarkChanged(bInMarkChanged); - } - - if (FloatPointData->Value != NewRampPoint.Value && FloatPointData->ValueParentParm) - { - FloatPointData->SetValue(NewRampPoint.Value); - if (bInMarkChanged) - FloatPointData->ValueParentParm->MarkChanged(bInMarkChanged); - } - - if (FloatPointData->Interpolation != NewInterpolation && FloatPointData->InterpolationParentParm) - { - FloatPointData->SetInterpolation(NewInterpolation); - if (bInMarkChanged) - FloatPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); - } - } - } - else - { - UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); - if (!IsValid(Event)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - - Event->InsertPosition = NewRampPoint.Position; - Event->InsertFloat = NewRampPoint.Value; - Event->InsertInterpolation = NewInterpolation; - } - } - - if (bUseCachedPoints) - { - // Update the ramp's widget if it is currently visible/selected - const bool bForceFullUpdate = true; - FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetFloatRampParameterPoints_Implementation( - FName InParameterTupleName, - TArray& OutRampPoints) const -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - UHoudiniParameterRampFloat* FloatRampParam = nullptr; - - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::FloatRamp) - { - FloatRampParam = Cast(Param); - if (!IsValid(FloatRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else - { - SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp parameter."), *(Param->GetName()))); - return false; - } - - // Get all ramp points (INDEX_NONE == get all points) - TArray> RampPointData; - if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) - return false; - - OutRampPoints.Reserve(RampPointData.Num()); - const bool bAllowShrinking = false; - OutRampPoints.SetNum(0, bAllowShrinking); - for (TPair const& Entry : RampPointData) - { - UObject* const PointData = Entry.Key; - const bool bIsPointData = Entry.Value; - if (!IsValid(PointData)) - return false; - - FHoudiniPublicAPIFloatRampPoint TempPointData; - - if (bIsPointData) - { - UHoudiniParameterRampFloatPoint* const FloatPointData = Cast(PointData); - if (!IsValid(FloatPointData)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - - TempPointData.Position = FloatPointData->Position; - TempPointData.Value = FloatPointData->Value; - TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( - FloatPointData->Interpolation); - } - else - { - UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); - if (!IsValid(Event)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - - TempPointData.Position = Event->InsertPosition; - TempPointData.Value = Event->InsertFloat; - TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); - } - - OutRampPoints.Add(TempPointData); - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetColorRampParameterPointValue_Implementation( - FName InParameterTupleName, - const int32 InPointIndex, - const float InPointPosition, - const FLinearColor& InPointValue, - const EHoudiniPublicAPIRampInterpolationType InInterpolationType, - const bool bInMarkChanged) -{ - const float FloatValue = 0; - return SetRampParameterPointValue( - InParameterTupleName, InPointIndex, InPointPosition, FloatValue, InPointValue, InInterpolationType, bInMarkChanged); -} - -bool -UHoudiniPublicAPIAssetWrapper::GetColorRampParameterPointValue_Implementation( - FName InParameterTupleName, - const int32 InPointIndex, - float& OutPointPosition, - FLinearColor& OutPointValue, - EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const -{ - float FloatValue = 0; - return GetRampParameterPointValue( - InParameterTupleName, InPointIndex, OutPointPosition, FloatValue, OutPointValue, OutInterpolationType); -} - -bool -UHoudiniPublicAPIAssetWrapper::SetColorRampParameterPoints_Implementation( - FName InParameterTupleName, - const TArray& InRampPoints, - const bool bInMarkChanged) -{ - const int32 TargetNumPoints = InRampPoints.Num(); - if (TargetNumPoints == 0) - { - SetErrorMessage(TEXT("InRampPoints must have at least one entry.")); - return false; - } - - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - UHoudiniParameterRampColor* ColorRampParam = nullptr; - - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::ColorRamp) - { - ColorRampParam = Cast(Param); - if (!IsValid(ColorRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else - { - SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a color ramp parameter."), *(Param->GetName()))); - return false; - } - - // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and - // not the main points. - // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); - // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); - const bool bUseCachedPoints = !Param->IsAutoUpdate(); - - // Set the ramp point count to match the size of InRampPoints - if (!SetRampParameterNumPoints(InParameterTupleName, TargetNumPoints)) - return false; - - // Get all ramp points (INDEX_NONE == get all points) - TArray> RampPointData; - if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) - return false; - - // Check that we fetched the correct number of point data objects - if (RampPointData.Num() != TargetNumPoints) - { - SetErrorMessage(FString::Printf(TEXT("Failed to set the number of ramp points to %d."), TargetNumPoints)); - return false; - } - - for (int32 Index = 0; Index < TargetNumPoints; ++Index) - { - TPair const& Entry = RampPointData[Index]; - UObject* const PointData = Entry.Key; - const bool bIsPointData = Entry.Value; - if (!IsValid(PointData)) - return false; - - const FHoudiniPublicAPIColorRampPoint& NewRampPoint = InRampPoints[Index]; - const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( - NewRampPoint.Interpolation); - - if (bIsPointData) - { - UHoudiniParameterRampColorPoint* ColorPointData = Cast(PointData); - if (!IsValid(ColorPointData)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - - if (bUseCachedPoints) - { - // When setting the cached points, we set the values directly instead of using the setters, but we set - // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed - if (ColorPointData->Position != NewRampPoint.Position) - { - ColorPointData->Position = NewRampPoint.Position; - ColorRampParam->bCaching = true; - } - - if (ColorPointData->Value != NewRampPoint.Value) - { - ColorPointData->Value = NewRampPoint.Value; - ColorRampParam->bCaching = true; - } - - if (ColorPointData->Interpolation != NewInterpolation) - { - ColorPointData->Interpolation = NewInterpolation; - ColorRampParam->bCaching = true; - } - } - else - { - // When setting the main points, we set the values using the setters on the point data but still manually - // mark the position/value/interpolation parent parameters as changed - if (ColorPointData->Position != NewRampPoint.Position && ColorPointData->PositionParentParm) - { - ColorPointData->SetPosition(NewRampPoint.Position); - if (bInMarkChanged) - ColorPointData->PositionParentParm->MarkChanged(bInMarkChanged); - } - - if (ColorPointData->Value != NewRampPoint.Value && ColorPointData->ValueParentParm) - { - ColorPointData->SetValue(NewRampPoint.Value); - if (bInMarkChanged) - ColorPointData->ValueParentParm->MarkChanged(bInMarkChanged); - } - - if (ColorPointData->Interpolation != NewInterpolation && ColorPointData->InterpolationParentParm) - { - ColorPointData->SetInterpolation(NewInterpolation); - if (bInMarkChanged) - ColorPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); - } - } - } - else - { - UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); - if (!IsValid(Event)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - - Event->InsertPosition = NewRampPoint.Position; - Event->InsertColor = NewRampPoint.Value; - Event->InsertInterpolation = NewInterpolation; - } - } - - if (bUseCachedPoints) - { - // Update the ramp's widget if it is currently visible/selected - const bool bForceFullUpdate = true; - FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetColorRampParameterPoints_Implementation( - FName InParameterTupleName, - TArray& OutRampPoints) const -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - UHoudiniParameterRampColor* ColorRampParam = nullptr; - - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::ColorRamp) - { - ColorRampParam = Cast(Param); - if (!IsValid(ColorRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else - { - SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp parameter."), *(Param->GetName()))); - return false; - } - - // Get all ramp points (INDEX_NONE == get all points) - TArray> RampPointData; - if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) - return false; - - OutRampPoints.Reserve(RampPointData.Num()); - const bool bAllowShrinking = false; - OutRampPoints.SetNum(0, bAllowShrinking); - for (TPair const& Entry : RampPointData) - { - UObject* const PointData = Entry.Key; - const bool bIsPointData = Entry.Value; - if (!IsValid(PointData)) - return false; - - FHoudiniPublicAPIColorRampPoint TempPointData; - - if (bIsPointData) - { - UHoudiniParameterRampColorPoint* const ColorPointData = Cast(PointData); - if (!IsValid(ColorPointData)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - - TempPointData.Position = ColorPointData->Position; - TempPointData.Value = ColorPointData->Value; - TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( - ColorPointData->Interpolation); - } - else - { - UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); - if (!IsValid(Event)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - - TempPointData.Position = Event->InsertPosition; - TempPointData.Value = Event->InsertColor; - TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); - } - - OutRampPoints.Add(TempPointData); - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::TriggerButtonParameter_Implementation(FName InButtonParameterName) -{ - UHoudiniParameter* Param = FindValidParameterByName(InButtonParameterName); - if (!Param) - return false; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = Param->GetParameterType(); - // bool bDidTrigger = false; - if (ParamType == EHoudiniParameterType::Button) - { - UHoudiniParameterButton* ButtonParam = Cast(Param); - if (!IsValid(ButtonParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - - // Marking the button as changed will result in it being triggered/clicked via HAPI - if (!ButtonParam->HasChanged() || !ButtonParam->NeedsToTriggerUpdate()) - { - ButtonParam->MarkChanged(true); - // bDidTrigger = true; - } - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Parameter tuple '%s' is not a button."), *InButtonParameterName.ToString())); - return false; - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetParameterTuples_Implementation(TMap& OutParameterTuples) const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - const int32 NumParameters = HAC->GetNumParameters(); - OutParameterTuples.Empty(NumParameters); - OutParameterTuples.Reserve(NumParameters); - for (int32 Index = 0; Index < NumParameters; ++Index) - { - const UHoudiniParameter* const Param = HAC->GetParameterAt(Index); - const EHoudiniParameterType ParameterType = Param->GetParameterType(); - const int32 TupleSize = Param->GetTupleSize(); - const FName PTName(Param->GetParameterName()); - - FHoudiniParameterTuple ParameterTuple; - - bool bSkipped = false; - switch (ParameterType) - { - case EHoudiniParameterType::Color: - case EHoudiniParameterType::Float: - { - // Output as float - ParameterTuple.FloatValues.SetNumZeroed(TupleSize); - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - GetFloatParameterValue(PTName, ParameterTuple.FloatValues[TupleIndex], TupleIndex); - } - break; - } - - case EHoudiniParameterType::Int: - case EHoudiniParameterType::IntChoice: - case EHoudiniParameterType::MultiParm: - { - // Output as int - ParameterTuple.Int32Values.SetNumZeroed(TupleSize); - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - GetIntParameterValue(PTName, ParameterTuple.Int32Values[TupleIndex], TupleIndex); - } - break; - } - - case EHoudiniParameterType::String: - case EHoudiniParameterType::StringChoice: - case EHoudiniParameterType::StringAssetRef: - case EHoudiniParameterType::File: - case EHoudiniParameterType::FileDir: - case EHoudiniParameterType::FileGeo: - case EHoudiniParameterType::FileImage: - { - // Output as string - ParameterTuple.StringValues.SetNumZeroed(TupleSize); - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - GetStringParameterValue(PTName, ParameterTuple.StringValues[TupleIndex], TupleIndex); - } - break; - } - - case EHoudiniParameterType::Toggle: - { - // Output as bool - ParameterTuple.BoolValues.SetNumZeroed(TupleSize); - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - GetBoolParameterValue(PTName, ParameterTuple.BoolValues[TupleIndex], TupleIndex); - } - break; - } - - case EHoudiniParameterType::ColorRamp: - { - GetColorRampParameterPoints(PTName, ParameterTuple.ColorRampPoints); - break; - } - case EHoudiniParameterType::FloatRamp: - { - GetFloatRampParameterPoints(PTName, ParameterTuple.FloatRampPoints); - break; - } - - case EHoudiniParameterType::Button: - case EHoudiniParameterType::ButtonStrip: - case EHoudiniParameterType::Input: - case EHoudiniParameterType::Invalid: - case EHoudiniParameterType::Folder: - case EHoudiniParameterType::FolderList: - case EHoudiniParameterType::Label: - case EHoudiniParameterType::Separator: - default: - // Skipped - bSkipped = true; - break; - } - - if (!bSkipped) - OutParameterTuples.Add(PTName, ParameterTuple); - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetParameterTuples_Implementation(const TMap& InParameterTuples) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - bool bSuccess = true; - for (const TPair& Entry : InParameterTuples) - { - const FName& ParameterTupleName = Entry.Key; - const FHoudiniParameterTuple& ParameterTuple = Entry.Value; - if (ParameterTuple.BoolValues.Num() > 0) - { - // Set as bool - const int32 TupleSize = ParameterTuple.BoolValues.Num(); - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - if (!SetBoolParameterValue(ParameterTupleName, ParameterTuple.BoolValues[TupleIndex], TupleIndex)) - { - SetErrorMessage(FString::Printf( - TEXT("SetParameterTuples: Failed to set %s as a bool at tuple index %d."), *ParameterTupleName.ToString(), TupleIndex)); - bSuccess = false; - break; - } - } - } - else if (ParameterTuple.FloatValues.Num() > 0) - { - // Set as float - const int32 TupleSize = ParameterTuple.FloatValues.Num(); - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - if (!SetFloatParameterValue(ParameterTupleName, ParameterTuple.FloatValues[TupleIndex], TupleIndex)) - { - SetErrorMessage(FString::Printf( - TEXT("SetParameterTuples: Failed to set %s as a float at tuple index %d."), - *ParameterTupleName.ToString(), TupleIndex)); - bSuccess = false; - break; - } - } - } - else if (ParameterTuple.Int32Values.Num() > 0) - { - // Set as int - const int32 TupleSize = ParameterTuple.Int32Values.Num(); - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - if (!SetIntParameterValue(ParameterTupleName, ParameterTuple.Int32Values[TupleIndex], TupleIndex)) - { - SetErrorMessage(FString::Printf( - TEXT("SetParameterTuples: Failed to set %s as a int at tuple index %d."), - *ParameterTupleName.ToString(), TupleIndex)); - bSuccess = false; - break; - } - } - } - else if (ParameterTuple.StringValues.Num() > 0) - { - // Set as string - const int32 TupleSize = ParameterTuple.StringValues.Num(); - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - if (!SetStringParameterValue(ParameterTupleName, ParameterTuple.StringValues[TupleIndex], TupleIndex)) - { - SetErrorMessage(FString::Printf( - TEXT("SetParameterTuples: Failed to set %s as a string at tuple index %d."), - *ParameterTupleName.ToString(), TupleIndex)); - bSuccess = false; - break; - } - } - } - else if (ParameterTuple.FloatRampPoints.Num() > 0) - { - // Set as a float ramp - if (!SetFloatRampParameterPoints(ParameterTupleName, ParameterTuple.FloatRampPoints)) - bSuccess = false; - } - else if (ParameterTuple.ColorRampPoints.Num() > 0) - { - // Set as a color ramp - if (!SetColorRampParameterPoints(ParameterTupleName, ParameterTuple.ColorRampPoints)) - bSuccess = false; - } - } - - return bSuccess; -} - -UHoudiniPublicAPIInput* -UHoudiniPublicAPIAssetWrapper::CreateEmptyInput_Implementation(TSubclassOf InInputClass) -{ - UHoudiniPublicAPI* API = UHoudiniPublicAPIBlueprintLib::GetAPI(); - if (!IsValid(API)) - return nullptr; - - UHoudiniPublicAPIInput* const NewInput = API->CreateEmptyInput(InInputClass, this); - if (!IsValid(NewInput)) - { - SetErrorMessage(FString::Printf( - TEXT("Failed to create a new input of class '%s'."), - *(IsValid(InInputClass.Get()) ? InInputClass->GetName() : FString()))); - - return nullptr; - } - - return NewInput; -} - -int32 -UHoudiniPublicAPIAssetWrapper::GetNumNodeInputs_Implementation() const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return -1; - - int32 NumNodeInputs = 0; - const int32 NumInputs = HAC->GetNumInputs(); - for (int32 Index = 0; Index < NumInputs; ++Index) - { - UHoudiniInput const* const Input = HAC->GetInputAt(Index); - if (!IsValid(Input)) - continue; - - if (!Input->IsObjectPathParameter()) - NumNodeInputs++; - } - - return NumNodeInputs; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetInputAtIndex_Implementation(const int32 InNodeInputIndex, const UHoudiniPublicAPIInput* InInput) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - UHoudiniInput* HoudiniInput = GetHoudiniNodeInputByIndex(InNodeInputIndex); - if (!IsValid(HoudiniInput)) - { - SetErrorMessage(FString::Printf( - TEXT("SetInputAtIndex: Could not find a HoudiniInput for InNodeInputIndex %d. Has the HDA been instantiated?"), - InNodeInputIndex)); - return false; - } - - return PopulateHoudiniInput(InInput, HoudiniInput); -} - -bool -UHoudiniPublicAPIAssetWrapper::GetInputAtIndex_Implementation(const int32 InNodeInputIndex, UHoudiniPublicAPIInput*& OutInput) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - const UHoudiniInput* HoudiniInput = GetHoudiniNodeInputByIndex(InNodeInputIndex); - if (!IsValid(HoudiniInput)) - { - SetErrorMessage(FString::Printf( - TEXT("GetInputAtIndex: Could not find a HoudiniInput for InNodeInputIndex %d. Has the HDA been instantiated?"), - InNodeInputIndex)); - return false; - } - - UHoudiniPublicAPIInput* APIInput = nullptr; - const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); - if (!IsValid(APIInput)) - return false; - - OutInput = APIInput; - return bSuccessfullyCopied; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetInputsAtIndices_Implementation(const TMap& InInputs) -{ - bool bAnyFailures = false; - for (const TPair& Entry : InInputs) - { - if (!SetInputAtIndex(Entry.Key, Entry.Value)) - { - SetErrorMessage(FString::Printf( - TEXT("SetInputsAtIndices: Failed to set node input at index %d"), Entry.Key)); - bAnyFailures = true; - } - } - - return !bAnyFailures; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetInputsAtIndices_Implementation(TMap& OutInputs) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - bool bAnyFailures = false; - const int32 NumInputs = HAC->GetNumInputs(); - for (int32 Index = 0; Index < NumInputs; ++Index) - { - UHoudiniInput const* const HoudiniInput = HAC->GetInputAt(Index); - if (!IsValid(HoudiniInput) || HoudiniInput->IsObjectPathParameter()) - continue; - - UHoudiniPublicAPIInput* APIInput = nullptr; - const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); - if (!IsValid(APIInput)) - { - bAnyFailures = true; - continue; - } - if (!bSuccessfullyCopied) - bAnyFailures = true; - - OutInputs.Add(HoudiniInput->GetInputIndex(), APIInput); - } - - return !bAnyFailures; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetInputParameter_Implementation(const FName& InParameterName, const UHoudiniPublicAPIInput* InInput) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - UHoudiniInput* HoudiniInput = FindValidHoudiniNodeInputParameter(InParameterName); - if (!IsValid(HoudiniInput)) - { - SetErrorMessage(FString::Printf( - TEXT("SetInputParameter: Could not find a parameter-based HoudiniInput with name %s. Has the HDA been instantiated?"), - *(InParameterName.ToString()))); - return false; - } - - return PopulateHoudiniInput(InInput, HoudiniInput); -} - -bool -UHoudiniPublicAPIAssetWrapper::GetInputParameter_Implementation(const FName& InParameterName, UHoudiniPublicAPIInput*& OutInput) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - const UHoudiniInput* HoudiniInput = FindValidHoudiniNodeInputParameter(InParameterName); - if (!IsValid(HoudiniInput)) - { - SetErrorMessage(FString::Printf( - TEXT("GetInputParameter: Could not find a parameter-based HoudiniInput with name %s. Has the HDA been instantiated?"), - *(InParameterName.ToString()))); - return false; - } - - UHoudiniPublicAPIInput* APIInput = nullptr; - const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); - if (!IsValid(APIInput)) - return false; - - OutInput = APIInput; - return bSuccessfullyCopied; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetInputParameters_Implementation(const TMap& InInputs) -{ - bool bAnyFailures = false; - for (const TPair& Entry : InInputs) - { - if (!SetInputParameter(Entry.Key, Entry.Value)) - { - SetErrorMessage(FString::Printf( - TEXT("SetInputParameters: Failed to set input parameter %s"), *(Entry.Key.ToString()))); - bAnyFailures = true; - } - } - - return !bAnyFailures; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetInputParameters_Implementation(TMap& OutInputs) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - bool bAnyFailures = false; - const int32 NumInputs = HAC->GetNumInputs(); - for (int32 Index = 0; Index < NumInputs; ++Index) - { - UHoudiniInput const* const HoudiniInput = HAC->GetInputAt(Index); - if (!IsValid(HoudiniInput) || !HoudiniInput->IsObjectPathParameter()) - continue; - - UHoudiniPublicAPIInput* APIInput = nullptr; - const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); - if (!IsValid(APIInput)) - { - bAnyFailures = true; - continue; - } - if (!bSuccessfullyCopied) - bAnyFailures = true; - - OutInputs.Add(FName(HoudiniInput->GetName()), APIInput); - } - - return !bAnyFailures; -} - -int32 -UHoudiniPublicAPIAssetWrapper::GetNumOutputs_Implementation() const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return -1; - - return HAC->GetNumOutputs(); -} - -EHoudiniOutputType -UHoudiniPublicAPIAssetWrapper::GetOutputTypeAt_Implementation(const int32 InIndex) const -{ - UHoudiniOutput* Output = nullptr; - if (!GetValidOutputAtWithError(InIndex, Output)) - return EHoudiniOutputType::Invalid; - - return Output->GetType(); -} - -bool -UHoudiniPublicAPIAssetWrapper::GetOutputIdentifiersAt_Implementation(const int32 InIndex, TArray& OutIdentifiers) const -{ - UHoudiniOutput* Output = nullptr; - if (!GetValidOutputAtWithError(InIndex, Output)) - return false; - - const TMap& OutputObjects = Output->GetOutputObjects(); - OutIdentifiers.Empty(); - OutIdentifiers.Reserve(OutputObjects.Num()); - for (const TPair& Entry : OutputObjects) - { - OutIdentifiers.Add(FHoudiniPublicAPIOutputObjectIdentifier(Entry.Key)); - } - - return true; -} - -UObject* -UHoudiniPublicAPIAssetWrapper::GetOutputObjectAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const -{ - UHoudiniOutput* Output = nullptr; - if (!GetValidOutputAtWithError(InIndex, Output)) - return nullptr; - - const TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniOutputObject const* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); - if (!OutputObject) - return nullptr; - - return OutputObject->bProxyIsCurrent ? OutputObject->ProxyObject : OutputObject->OutputObject; -} - -UObject* -UHoudiniPublicAPIAssetWrapper::GetOutputComponentAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const -{ - UHoudiniOutput* Output = nullptr; - if (!GetValidOutputAtWithError(InIndex, Output)) - return nullptr; - - const TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniOutputObject const* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); - if (!OutputObject) - return nullptr; - - return OutputObject->bProxyIsCurrent ? OutputObject->ProxyComponent : OutputObject->OutputComponent; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetOutputBakeNameFallbackAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, FString& OutBakeName) const -{ - UHoudiniOutput* Output = nullptr; - if (!GetValidOutputAtWithError(InIndex, Output)) - return false; - - const TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniOutputObject const* const OutputObject =OutputObjects.Find(InIdentifier.GetIdentifier()); - if (!OutputObject) - return false; - - OutBakeName = OutputObject->BakeName; - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetOutputBakeNameFallbackAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FString& InBakeName) -{ - UHoudiniOutput* Output = nullptr; - if (!GetValidOutputAtWithError(InIndex, Output)) - return false; - - TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniOutputObject* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); - if (!OutputObject) - return false; - - OutputObject->BakeName = InBakeName; - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::BakeOutputObjectAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FName InBakeName, const EHoudiniLandscapeOutputBakeType InLandscapeBakeType) -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - UHoudiniOutput* Output = nullptr; - if (!GetValidOutputAtWithError(InIndex, Output)) - return false; - - TMap& OutputObjects = Output->GetOutputObjects(); - const FHoudiniOutputObjectIdentifier& Identifier = InIdentifier.GetIdentifier(); - FHoudiniOutputObject* const OutputObject = OutputObjects.Find(Identifier); - if (!OutputObject) - { - SetErrorMessage(FString::Printf( - TEXT("BakeOutputObjectAt: Could not find an output object using the specified identifier."))); - return false; - } - - // Determine the object to bake (this is different depending on landscape, curve or mesh - const EHoudiniOutputType OutputType = Output->GetType(); - - if (OutputType == EHoudiniOutputType::Mesh && OutputObject->bProxyIsCurrent) - { - // Output is currently a proxy, this cannot be baked without cooking first. - SetErrorMessage(FString::Printf( - TEXT("BakeOutputObjectAt: Object is a proxy mesh, please refine it before baking to CB."))); - - return false; - } - - UObject* ObjectToBake = nullptr; - switch (OutputType) - { - case EHoudiniOutputType::Landscape: - { - UHoudiniLandscapePtr* const LandscapePtr = Cast(OutputObject->OutputObject); - if (IsValid(LandscapePtr)) - { - ObjectToBake = LandscapePtr->LandscapeSoftPtr.IsValid() ? LandscapePtr->LandscapeSoftPtr.Get() : nullptr; - } - break; - } - case EHoudiniOutputType::Curve: - ObjectToBake = OutputObject->OutputComponent; - break; - case EHoudiniOutputType::Mesh: - ObjectToBake = OutputObject->OutputObject; - break; - case EHoudiniOutputType::Instancer: - case EHoudiniOutputType::Skeletal: - case EHoudiniOutputType::Invalid: - default: - SetErrorMessage(FString::Printf( - TEXT("BakeOutputObjectAt: unsupported output type (%d) for baking to CB."), OutputType)); - return false; - } - - if (!IsValid(ObjectToBake)) - { - SetErrorMessage(FString::Printf(TEXT("BakeOutputObjectAt: Could not find a valid object to bake to CB."))); - return false; - } - - // Find the corresponding HGPO in the output - FHoudiniGeoPartObject HoudiniGeoPartObject; - for (const auto& HGPO : Output->GetHoudiniGeoPartObjects()) - { - if (!Identifier.Matches(HGPO)) - continue; - - HoudiniGeoPartObject = HGPO; - break; - } - - if (!HoudiniGeoPartObject.IsValid()) - { - SetErrorMessage(TEXT("BakeOutputObjectAt: Could not find a valid HGPO for the output object. Please recook.")); - return false; - } - - // Determine the HoudiniAssetName - FString HoudiniAssetName; - if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) - { - // If the HAC has a valid owner, use the owner's name - // TODO: Should this be more specific, such as checking for a HoudiniAssetActor? - HoudiniAssetName = HAC->GetOwner()->GetName(); - } - else if (HAC->GetHoudiniAsset()) - { - // Otherwise, if the HAC has a valid HoudiniAsset, use its name - HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); - } - else - { - // Fallback to the HAC's name - HoudiniAssetName = HAC->GetName(); - } - - TArray AllOutputs; - HAC->GetOutputs(AllOutputs); - - FHoudiniOutputDetails::OnBakeOutputObject( - InBakeName.IsNone() ? OutputObject->BakeName : InBakeName.ToString(), - ObjectToBake, - Identifier, - *OutputObject, - HoudiniGeoPartObject, - HAC, - HoudiniAssetName, - HAC->BakeFolder.Path, - HAC->TemporaryCookFolder.Path, - OutputType, - InLandscapeBakeType, - AllOutputs); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::HasAnyCurrentProxyOutput_Implementation() const -{ - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return false; - - return HAC->HasAnyCurrentProxyOutput(); -} - -bool -UHoudiniPublicAPIAssetWrapper::HasAnyCurrentProxyOutputAt_Implementation(const int32 InIndex) const -{ - UHoudiniOutput* Output = nullptr; - if (!GetValidOutputAtWithError(InIndex, Output)) - return false; - - return Output->HasAnyCurrentProxy(); -} - -bool -UHoudiniPublicAPIAssetWrapper::IsOutputCurrentProxyAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const -{ - UHoudiniOutput* Output = nullptr; - if (!GetValidOutputAtWithError(InIndex, Output)) - return false; - - return Output->IsProxyCurrent(InIdentifier.GetIdentifier()); -} - -EHoudiniProxyRefineRequestResult -UHoudiniPublicAPIAssetWrapper::RefineAllCurrentProxyOutputs_Implementation(const bool bInSilent) -{ - AHoudiniAssetActor* AssetActor = nullptr; - if (!GetValidHoudiniAssetActorWithError(AssetActor)) - return EHoudiniProxyRefineRequestResult::Invalid; - - return FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ AssetActor }, bInSilent); -} - -bool -UHoudiniPublicAPIAssetWrapper::HasPDGAssetLink_Implementation() const -{ - return IsValid(GetHoudiniPDGAssetLink()); -} - -bool -UHoudiniPublicAPIAssetWrapper::GetPDGTOPNetworkPaths_Implementation(TArray& OutTOPNetworkPaths) const -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - const uint32 NumNetworks = AssetLink->AllTOPNetworks.Num(); - OutTOPNetworkPaths.Empty(NumNetworks); - for (UTOPNetwork const* const TOPNet : AssetLink->AllTOPNetworks) - { - OutTOPNetworkPaths.Add(TOPNet->NodePath); - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetPDGTOPNodePaths_Implementation(const FString& InNetworkRelativePath, TArray& OutTOPNodePaths) const -{ - int32 NetworkIndex = INDEX_NONE; - UTOPNetwork* TOPNet = nullptr; - if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) - return false; - - const uint32 NumNodes = TOPNet->AllTOPNodes.Num(); - OutTOPNodePaths.Empty(NumNodes); - for (UTOPNode const* const TOPNode : TOPNet->AllTOPNodes) - { - OutTOPNodePaths.Add(TOPNode->NodePath); - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::PDGDirtyAllNetworks_Implementation() -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - for (UTOPNetwork* const TOPNetwork : AssetLink->AllTOPNetworks) - { - if (!IsValid(TOPNetwork)) - continue; - - FHoudiniPDGManager::DirtyAll(TOPNetwork); - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::PDGDirtyNetwork_Implementation(const FString& InNetworkRelativePath) -{ - int32 NetworkIndex = INDEX_NONE; - UTOPNetwork* TOPNet = nullptr; - if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) - return false; - - FHoudiniPDGManager::DirtyAll(TOPNet); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::PDGDirtyNode_Implementation(const FString& InNetworkRelativePath, const FString& InNodeRelativePath) -{ - int32 NetworkIndex = INDEX_NONE; - int32 NodeIndex = INDEX_NONE; - UTOPNode* TOPNode = nullptr; - if (!GetValidTOPNodeByPathWithError(InNetworkRelativePath, InNodeRelativePath, NetworkIndex, NodeIndex, TOPNode)) - return false; - - FHoudiniPDGManager::DirtyTOPNode(TOPNode); - - return true; -} - -// bool -// UHoudiniPublicAPIAssetWrapper::PDGCookOutputsForAllNetworks_Implementation() -// { -// UHoudiniPDGAssetLink* const AssetLink = GetHoudiniPDGAssetLink(); -// if (!IsValid(AssetLink)) -// return false; -// -// for (UTOPNetwork* const TOPNetwork : AssetLink->AllTOPNetworks) -// { -// if (!IsValid(TOPNetwork)) -// continue; -// -// FHoudiniPDGManager::CookOutput(TOPNetwork); -// } -// -// return true; -// } - -bool -UHoudiniPublicAPIAssetWrapper::PDGCookOutputsForNetwork_Implementation(const FString& InNetworkRelativePath) -{ - int32 NetworkIndex = INDEX_NONE; - UTOPNetwork* TOPNet = nullptr; - if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) - return false; - - FHoudiniPDGManager::CookOutput(TOPNet); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::PDGCookNode_Implementation(const FString& InNetworkRelativePath, const FString& InNodeRelativePath) -{ - int32 NetworkIndex = INDEX_NONE; - int32 NodeIndex = INDEX_NONE; - UTOPNode* TOPNode = nullptr; - if (!GetValidTOPNodeByPathWithError(InNetworkRelativePath, InNodeRelativePath, NetworkIndex, NodeIndex, TOPNode)) - return false; - - FHoudiniPDGManager::CookTOPNode(TOPNode); - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::PDGBakeAllOutputs_Implementation() -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - return PDGBakeAllOutputsWithSettings( - AssetLink->HoudiniEngineBakeOption, - AssetLink->PDGBakeSelectionOption, - AssetLink->PDGBakePackageReplaceMode, - AssetLink->bRecenterBakedActors); -} - -bool -UHoudiniPublicAPIAssetWrapper::PDGBakeAllOutputsWithSettings_Implementation( - const EHoudiniEngineBakeOption InBakeOption, - const EPDGBakeSelectionOption InBakeSelection, - const EPDGBakePackageReplaceModeOption InBakeReplacementMode, - const bool bInRecenterBakedActors) -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - bool bBakeSuccess = false; - switch (InBakeOption) - { - case EHoudiniEngineBakeOption::ToActor: - bBakeSuccess = FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors( - AssetLink, - InBakeSelection, - InBakeReplacementMode, - bInRecenterBakedActors); - break; - case EHoudiniEngineBakeOption::ToBlueprint: - bBakeSuccess = FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints( - AssetLink, - InBakeSelection, - InBakeReplacementMode, - bInRecenterBakedActors); - break; - default: - bBakeSuccess = false; - break; - } - - return bBakeSuccess; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetPDGAutoBakeEnabled_Implementation(const bool bInAutoBakeEnabled) -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - AssetLink->bBakeAfterAllWorkResultObjectsLoaded = bInAutoBakeEnabled; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::IsPDGAutoBakeEnabled_Implementation() const -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - return AssetLink->bBakeAfterAllWorkResultObjectsLoaded; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetPDGBakeMethod_Implementation(const EHoudiniEngineBakeOption InBakeMethod) -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - AssetLink->HoudiniEngineBakeOption = InBakeMethod; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetPDGBakeMethod_Implementation(EHoudiniEngineBakeOption& OutBakeMethod) -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - OutBakeMethod = AssetLink->HoudiniEngineBakeOption; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetPDGBakeSelection_Implementation(const EPDGBakeSelectionOption InBakeSelection) -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - AssetLink->PDGBakeSelectionOption = InBakeSelection; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetPDGBakeSelection_Implementation(EPDGBakeSelectionOption& OutBakeSelection) -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - OutBakeSelection = AssetLink->PDGBakeSelectionOption; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetPDGRecenterBakedActors_Implementation(const bool bInRecenterBakedActors) -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - AssetLink->bRecenterBakedActors = bInRecenterBakedActors; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetPDGRecenterBakedActors_Implementation() const -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - return AssetLink->bRecenterBakedActors; -} - -bool -UHoudiniPublicAPIAssetWrapper::SetPDGBakingReplacementMode_Implementation(const EPDGBakePackageReplaceModeOption InBakingReplacementMode) -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - AssetLink->PDGBakePackageReplaceMode = InBakingReplacementMode; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetPDGBakingReplacementMode_Implementation(EPDGBakePackageReplaceModeOption& OutBakingReplacementMode) const -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - OutBakingReplacementMode = AssetLink->PDGBakePackageReplaceMode; - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::BindToPDGAssetLink() -{ - if (bAssetLinkSetupAttemptComplete) - return true; - - UHoudiniPDGAssetLink* const PDGAssetLink = GetHoudiniPDGAssetLink(); - if (IsValid(PDGAssetLink)) - { - OnPDGPostTOPNetworkCookDelegateHandle = PDGAssetLink->GetOnPostTOPNetworkCookDelegate().AddUFunction(this, TEXT("HandleOnHoudiniPDGAssetLinkTOPNetPostCook")); - OnPDGPostBakeDelegateHandle = PDGAssetLink->GetOnPostBakeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniPDGAssetLinkPostBake")); - bAssetLinkSetupAttemptComplete = true; - - return true; - } - - return false; -} - -void -UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentStateChange(UHoudiniAssetComponent* InHAC, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) -{ - if (!IsValid(InHAC)) - return; - - if (InHAC != GetHoudiniAssetComponent()) - { - SetErrorMessage(FString::Printf( - TEXT("HandleOnHoudiniAssetComponentStateChange: unexpected InHAC: %s, expected the wrapper's HAC."), - IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); - return; - } - - if (InToState == EHoudiniAssetState::PreInstantiation) - { - if (OnPreInstantiationDelegate.IsBound()) - OnPreInstantiationDelegate.Broadcast(this); - } - - if (InFromState == EHoudiniAssetState::Instantiating && InToState == EHoudiniAssetState::PreCook) - { - // PDG link setup / bindings: we have to wait until post instantiation to check if we have an asset link and - // configure bindings - if (!bAssetLinkSetupAttemptComplete) - { - BindToPDGAssetLink(); - bAssetLinkSetupAttemptComplete = true; - } - - if (OnPostInstantiationDelegate.IsBound()) - OnPostInstantiationDelegate.Broadcast(this); - } - - if (InFromState == EHoudiniAssetState::PreProcess) - { - if (OnPreProcessStateExitedDelegate.IsBound()) - OnPreProcessStateExitedDelegate.Broadcast(this); - } - - if (InFromState == EHoudiniAssetState::Processing && InToState == EHoudiniAssetState::None) - { - if (OnPostProcessingDelegate.IsBound()) - OnPostProcessingDelegate.Broadcast(this); - } -} - -void -UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentPostCook(UHoudiniAssetComponent* InHAC, const bool bInCookSuccess) -{ - if (!IsValid(InHAC)) - return; - - if (InHAC != GetHoudiniAssetComponent()) - { - SetErrorMessage(FString::Printf( - TEXT("HandleOnHoudiniAssetComponentPostCook: unexpected InHAC: %s, expected the wrapper's HAC."), - IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); - return; - } - - if (OnPostCookDelegate.IsBound()) - OnPostCookDelegate.Broadcast(this, bInCookSuccess); -} - -void -UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentPostBake(UHoudiniAssetComponent* InHAC, const bool bInBakeSuccess) -{ - if (!IsValid(InHAC)) - return; - - if (InHAC != GetHoudiniAssetComponent()) - { - SetErrorMessage(FString::Printf( - TEXT("HandleOnHoudiniAssetComponentPostBake: unexpected InHAC: %s, expected the wrapper's HAC."), - IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); - return; - } - - if (OnPostBakeDelegate.IsBound()) - OnPostBakeDelegate.Broadcast(this, bInBakeSuccess); -} - -void -UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniPDGAssetLinkTOPNetPostCook(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InTOPNet, const bool bInAnyWorkItemsFailed) -{ - if (!IsValid(InPDGAssetLink)) - return; - - if (InPDGAssetLink != GetHoudiniPDGAssetLink()) - { - SetErrorMessage(FString::Printf( - TEXT("HandleOnHoudiniPDGAssetLinkTOPNetPostCook: unexpected InPDGAssetLink: %s, expected the wrapper's PDGAssetLink."), - IsValid(InPDGAssetLink) ? *InPDGAssetLink->GetName() : TEXT(""))); - return; - } - - if (OnPostPDGTOPNetworkCookDelegate.IsBound()) - OnPostPDGTOPNetworkCookDelegate.Broadcast(this, !bInAnyWorkItemsFailed); -} - -void -UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniPDGAssetLinkPostBake(UHoudiniPDGAssetLink* InPDGAssetLink, const bool bInBakeSuccess) -{ - if (!IsValid(InPDGAssetLink)) - return; - - if (InPDGAssetLink != GetHoudiniPDGAssetLink()) - { - SetErrorMessage(FString::Printf( - TEXT("HandleOnHoudiniPDGAssetLinkPostBake: unexpected InPDGAssetLink: %s, expected the wrapper's PDGAssetLink."), - IsValid(InPDGAssetLink) ? *InPDGAssetLink->GetName() : TEXT(""))); - return; - } - - if (OnPostPDGBakeDelegate.IsBound()) - OnPostPDGBakeDelegate.Broadcast(this, bInBakeSuccess); -} - -void -UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniProxyMeshesRefinedGlobal(UHoudiniAssetComponent* InHAC, const EHoudiniProxyRefineResult InResult) -{ - if (!IsValid(InHAC)) - return; - - if (InHAC != GetHoudiniAssetComponent()) - return; - - if (OnProxyMeshesRefinedDelegate.IsBound()) - OnProxyMeshesRefinedDelegate.Broadcast(this, InResult); -} - -UHoudiniParameter* -UHoudiniPublicAPIAssetWrapper::FindValidParameterByName(const FName& InParameterTupleName) const -{ - AActor* const Actor = GetHoudiniAssetActor(); - const FString ActorName = IsValid(Actor) ? Actor->GetName() : FString(); - - UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); - if (!IsValid(HAC)) - { - SetErrorMessage(FString::Printf(TEXT("Could not find HAC on Actor '%s'"), *ActorName)); - return nullptr; - } - - UHoudiniParameter* const Param = HAC->FindParameterByName(InParameterTupleName.ToString()); - if (!IsValid(Param)) - { - SetErrorMessage(FString::Printf( - TEXT("Could not find valid parameter tuple '%s' on '%s'."), - *InParameterTupleName.ToString(), *ActorName)); - return nullptr; - } - - return Param; -} - -bool -UHoudiniPublicAPIAssetWrapper::FindRampPointData( - UHoudiniParameter* const InParam, - const int32 InIndex, - TArray>& OutPointData) const -{ - if (!IsValid(InParam)) - return false; - - // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and - // not the main points. - // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); - // const bool bUseCachedPoints = (!InParam->IsAutoUpdate() || !bCookingEnabled); - const bool bUseCachedPoints = !InParam->IsAutoUpdate(); - - const bool bFetchAllPoints = (InIndex == INDEX_NONE); - - UHoudiniParameterRampFloat* FloatRampParam = nullptr; - UHoudiniParameterRampColor* ColorRampParam = nullptr; - - // Handle all the cases where the underlying parameter value is an int or bool - const EHoudiniParameterType ParamType = InParam->GetParameterType(); - if (ParamType == EHoudiniParameterType::FloatRamp) - { - FloatRampParam = Cast(InParam); - if (!IsValid(FloatRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *InParam->GetClass()->GetName(), ParamType)); - return false; - } - } - else if (ParamType == EHoudiniParameterType::ColorRamp) - { - ColorRampParam = Cast(InParam); - if (!IsValid(ColorRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *InParam->GetClass()->GetName(), ParamType)); - return false; - } - } - else - { - SetErrorMessage(FString::Printf( - TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParam->GetName()))); - return false; - } - - if (bUseCachedPoints) - { - // When using the cached points we only have to resize the array - if (FloatRampParam) - { - if (!bFetchAllPoints && !FloatRampParam->CachedPoints.IsValidIndex(InIndex)) - { - SetErrorMessage(FString::Printf( - TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, FloatRampParam->CachedPoints.Num() - 1)); - return false; - } - - if (bFetchAllPoints) - { - // Get all points - OutPointData.Reserve(FloatRampParam->CachedPoints.Num()); - const bool bAllowShrinking = false; - OutPointData.SetNum(0, bAllowShrinking); - for (UHoudiniParameterRampFloatPoint* const RampPoint : FloatRampParam->CachedPoints) - { - const bool bIsPointData = true; - OutPointData.Add(TPair(RampPoint, bIsPointData)); - } - } - else - { - OutPointData.Reserve(1); - const bool bAllowShrinking = false; - OutPointData.SetNum(0, bAllowShrinking); - - const bool bIsPointData = true; - OutPointData.Add(TPair(FloatRampParam->CachedPoints[InIndex], bIsPointData)); - } - - return true; - } - else - { - if (!bFetchAllPoints && !ColorRampParam->CachedPoints.IsValidIndex(InIndex)) - { - SetErrorMessage(FString::Printf( - TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, ColorRampParam->CachedPoints.Num() - 1)); - return false; - } - - if (bFetchAllPoints) - { - // Get all points - OutPointData.Reserve(ColorRampParam->CachedPoints.Num()); - const bool bAllowShrinking = false; - OutPointData.SetNum(0, bAllowShrinking); - for (UHoudiniParameterRampColorPoint* const RampPoint : ColorRampParam->CachedPoints) - { - const bool bIsPointData = true; - OutPointData.Add(TPair(RampPoint, bIsPointData)); - } - } - else - { - OutPointData.Reserve(1); - const bool bAllowShrinking = false; - OutPointData.SetNum(0, bAllowShrinking); - - const bool bIsPointData = true; - OutPointData.Add(TPair(ColorRampParam->CachedPoints[InIndex], bIsPointData)); - } - - return true; - } - } - else - { - TSet InstanceIndexesPendingDelete; - int32 NumInsertOps = 0; - TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; - for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) - { - if (!IsValid(Event)) - continue; - - if (Event->IsInsertEvent()) - NumInsertOps++; - else if (Event->IsDeleteEvent()) - InstanceIndexesPendingDelete.Add(Event->DeleteInstanceIndex); - } - - const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); - const int32 NumActivePointsInArray = PointsArraySize - InstanceIndexesPendingDelete.Num(); - const int32 TotalNumPoints = NumActivePointsInArray + NumInsertOps; - - // Reserve the expected amount of space needed in the array and reset / destruct existing items so we can - // add from index 0 - if (bFetchAllPoints) - OutPointData.Reserve(TotalNumPoints); - else - OutPointData.Reserve(1); - const bool bAllowShrinking = false; - OutPointData.SetNum(0, bAllowShrinking); - - if (bFetchAllPoints || InIndex < NumActivePointsInArray) - { - // Getting all points or point is in the points array - if (FloatRampParam) - { - const int32 ArraySize = FloatRampParam->Points.Num(); - int32 PointIndex = 0; - for (int32 Index = 0; Index < ArraySize && (bFetchAllPoints || PointIndex <= InIndex); ++Index) - { - UHoudiniParameterRampFloatPoint* const PointData = FloatRampParam->Points[Index]; - const bool bIsDeletedPoint = (PointData && InstanceIndexesPendingDelete.Contains(PointData->InstanceIndex)); - if (!bIsDeletedPoint) - { - if (PointIndex == InIndex || bFetchAllPoints) - { - const bool bIsPointData = true; - OutPointData.Add(TPair(PointData, bIsPointData)); - } - - // If we are fetching only the point at InIndex, then we are done here - if (PointIndex == InIndex) - return true; - - PointIndex++; - } - } - } - else - { - const int32 ArraySize = ColorRampParam->Points.Num(); - int32 PointIndex = 0; - for (int32 Index = 0; Index < ArraySize && (bFetchAllPoints || PointIndex <= InIndex); ++Index) - { - UHoudiniParameterRampColorPoint* const PointData = ColorRampParam->Points[Index]; - const bool bIsDeletedPoint = (PointData && InstanceIndexesPendingDelete.Contains(PointData->InstanceIndex)); - if (!bIsDeletedPoint) - { - if (PointIndex == InIndex || bFetchAllPoints) - { - const bool bIsPointData = true; - OutPointData.Add(TPair(PointData, bIsPointData)); - } - - // If we are fetching only the point at InIndex, then we are done here - if (PointIndex == InIndex) - return true; - - PointIndex++; - } - } - } - } - - if (bFetchAllPoints || InIndex < TotalNumPoints) - { - // Point is an insert operation - const int32 NumEvents = ModificationEvents.Num(); - int32 PointIndex = NumActivePointsInArray; - for (int32 Index = 0; Index < NumEvents && (bFetchAllPoints || PointIndex <= InIndex); ++Index) - { - UHoudiniParameterRampModificationEvent* const Event = ModificationEvents[Index]; - if (!IsValid(Event)) - continue; - - if (!Event->IsInsertEvent()) - continue; - - if (PointIndex == InIndex || bFetchAllPoints) - { - const bool bIsPointData = false; - OutPointData.Add(TPair(Event, bIsPointData)); - } - - if (PointIndex == InIndex) - return true; - - PointIndex++; - } - } - else - { - // Point is out of range - SetErrorMessage(FString::Printf( - TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, TotalNumPoints)); - return false; - } - - if (bFetchAllPoints) - { - if (TotalNumPoints != OutPointData.Num()) - { - SetErrorMessage(FString::Printf( - TEXT("Failed to fetch all ramp points. Got %d, expected %d."), OutPointData.Num(), TotalNumPoints)); - return false; - } - - return true; - } - } - - // If we reach this point we didn't find the point - SetErrorMessage(FString::Printf(TEXT("Could not find valid ramp point at index %d."), InIndex)); - return false; -} - -bool UHoudiniPublicAPIAssetWrapper::SetRampParameterPointValue( - FName InParameterTupleName, - const int32 InPointIndex, - const float InPosition, - const float InFloatValue, - const FLinearColor& InColorValue, - const EHoudiniPublicAPIRampInterpolationType InInterpolation, - const bool bInMarkChanged) -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - UHoudiniParameterRampFloat* FloatRampParam = nullptr; - UHoudiniParameterRampColor* ColorRampParam = nullptr; - - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::FloatRamp) - { - FloatRampParam = Cast(Param); - if (!IsValid(FloatRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else if (ParamType == EHoudiniParameterType::ColorRamp) - { - ColorRampParam = Cast(Param); - if (!IsValid(ColorRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else - { - SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); - return false; - } - - const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( - InInterpolation); - - // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and - // not the main points. - // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); - // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); - const bool bUseCachedPoints = !Param->IsAutoUpdate(); - - // Get the point at InPointIndex's data - TArray> RampPointData; - if (!FindRampPointData(Param, InPointIndex, RampPointData) || RampPointData.Num() < 1) - return false; - - UObject* const PointData = RampPointData[0].Key; - const bool bIsPointData = RampPointData[0].Value; - if (!IsValid(PointData)) - return false; - - if (bIsPointData) - { - UHoudiniParameterRampFloatPoint* FloatPointData = nullptr; - UHoudiniParameterRampColorPoint* ColorPointData = nullptr; - - if (FloatRampParam) - { - FloatPointData = Cast(PointData); - if (!IsValid(FloatPointData)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - } - else - { - ColorPointData = Cast(PointData); - if (!IsValid(ColorPointData)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - } - - if (bUseCachedPoints) - { - // When setting the cached points, we set the values directly instead of using the setters, but we set - // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed - if (FloatPointData) - { - if (FloatPointData->Position != InPosition) - { - FloatPointData->Position = InPosition; - FloatRampParam->bCaching = true; - } - - if (FloatPointData->Value != InFloatValue) - { - FloatPointData->Value = InFloatValue; - FloatRampParam->bCaching = true; - } - - if (FloatPointData->Interpolation != NewInterpolation) - { - FloatPointData->Interpolation = NewInterpolation; - FloatRampParam->bCaching = true; - } - } - else if (ColorPointData) - { - if (ColorPointData->Position != InPosition) - { - ColorPointData->Position = InPosition; - ColorRampParam->bCaching = true; - } - - if (ColorPointData->Value != InColorValue) - { - ColorPointData->Value = InColorValue; - ColorRampParam->bCaching = true; - } - - if (ColorPointData->Interpolation != NewInterpolation) - { - ColorPointData->Interpolation = NewInterpolation; - ColorRampParam->bCaching = true; - } - } - - // Update the ramp's widget if it is currently visible/selected - const bool bForceFullUpdate = true; - FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); - } - else - { - // When setting the main points, we set the values using the setters on the point data but still manually - // mark the position/value/interpolation parent parameters as changed - if (FloatPointData) - { - if (FloatPointData->Position != InPosition && FloatPointData->PositionParentParm) - { - FloatPointData->SetPosition(InPosition); - if (bInMarkChanged) - FloatPointData->PositionParentParm->MarkChanged(bInMarkChanged); - } - - if (FloatPointData->Value != InFloatValue && FloatPointData->ValueParentParm) - { - FloatPointData->SetValue(InFloatValue); - if (bInMarkChanged) - FloatPointData->ValueParentParm->MarkChanged(bInMarkChanged); - } - - if (FloatPointData->Interpolation != NewInterpolation && FloatPointData->InterpolationParentParm) - { - FloatPointData->SetInterpolation(NewInterpolation); - if (bInMarkChanged) - FloatPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); - } - } - else if (ColorPointData) - { - if (ColorPointData->Position != InPosition && ColorPointData->PositionParentParm) - { - ColorPointData->SetPosition(InPosition); - if (bInMarkChanged) - ColorPointData->PositionParentParm->MarkChanged(bInMarkChanged); - } - - if (ColorPointData->Value != InColorValue && ColorPointData->ValueParentParm) - { - ColorPointData->SetValue(InColorValue); - if (bInMarkChanged) - ColorPointData->ValueParentParm->MarkChanged(bInMarkChanged); - } - - if (ColorPointData->Interpolation != NewInterpolation && ColorPointData->InterpolationParentParm) - { - ColorPointData->SetInterpolation(NewInterpolation); - if (bInMarkChanged) - ColorPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); - } - } - } - } - else - { - UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); - if (!IsValid(Event)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - - Event->InsertPosition = InPosition; - if (FloatRampParam) - { - Event->InsertFloat = InFloatValue; - } - else if (ColorRampParam) - { - Event->InsertColor = InColorValue; - } - Event->InsertInterpolation = NewInterpolation; - } - - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetRampParameterPointValue( - FName InParameterTupleName, - const int32 InPointIndex, - float& OutPosition, - float& OutFloatValue, - FLinearColor& OutColorValue, - EHoudiniPublicAPIRampInterpolationType& OutInterpolation) const -{ - UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); - if (!Param) - return false; - - UHoudiniParameterRampFloat* FloatRampParam = nullptr; - UHoudiniParameterRampColor* ColorRampParam = nullptr; - - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType == EHoudiniParameterType::FloatRamp) - { - FloatRampParam = Cast(Param); - if (!IsValid(FloatRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else if (ParamType == EHoudiniParameterType::ColorRamp) - { - ColorRampParam = Cast(Param); - if (!IsValid(ColorRampParam)) - { - SetErrorMessage(FString::Printf( - TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); - return false; - } - } - else - { - SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); - return false; - } - - TArray> RampPointData; - if (!FindRampPointData(Param, InPointIndex, RampPointData) || RampPointData.Num() < 1) - return false; - - UObject* const PointData = RampPointData[0].Key; - const bool bIsPointData = RampPointData[0].Value; - if (!IsValid(PointData)) - return false; - - if (bIsPointData) - { - UHoudiniParameterRampFloatPoint* FloatPointData = nullptr; - UHoudiniParameterRampColorPoint* ColorPointData = nullptr; - - if (FloatRampParam) - { - FloatPointData = Cast(PointData); - if (!IsValid(FloatPointData)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - } - else - { - ColorPointData = Cast(PointData); - if (!IsValid(ColorPointData)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - } - - // When setting the cached points, we set the values directly instead of using the setters, but we set - // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed - if (FloatPointData) - { - OutPosition = FloatPointData->Position; - OutFloatValue = FloatPointData->Value; - OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( - FloatPointData->Interpolation); - } - else if (ColorPointData) - { - OutPosition = ColorPointData->Position; - OutColorValue = ColorPointData->Value; - OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( - ColorPointData->Interpolation); - } - } - else - { - UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); - if (!IsValid(Event)) - { - SetErrorMessage(FString::Printf( - TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), - *(PointData->GetClass()->GetName()))); - return false; - } - - OutPosition = Event->InsertPosition; - if (FloatRampParam) - { - OutFloatValue = Event->InsertFloat; - } - else if (ColorRampParam) - { - OutColorValue = Event->InsertColor; - } - OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); - } - - return true; -} - -UHoudiniInput* -UHoudiniPublicAPIAssetWrapper::GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) -{ - if (InNodeInputIndex < 0) - return nullptr; - - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return nullptr; - - const int32 NumInputs = HAC->GetNumInputs(); - for (int32 Index = 0; Index < NumInputs; ++Index) - { - UHoudiniInput* const Input = HAC->GetInputAt(Index); - if (!IsValid(Input)) - continue; - if (Input->GetInputIndex() == InNodeInputIndex) - return Input; - } - - return nullptr; -} - -const UHoudiniInput* -UHoudiniPublicAPIAssetWrapper::GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) const -{ - if (InNodeInputIndex < 0) - return nullptr; - - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return nullptr; - - const int32 NumInputs = HAC->GetNumInputs(); - for (int32 Index = 0; Index < NumInputs; ++Index) - { - UHoudiniInput const* const Input = HAC->GetInputAt(Index); - if (!IsValid(Input)) - continue; - if (Input->GetInputIndex() == InNodeInputIndex) - return Input; - } - - return nullptr; -} - -UHoudiniInput* -UHoudiniPublicAPIAssetWrapper::FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) -{ - if (InInputParameterName == NAME_None) - return nullptr; - - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return nullptr; - - const FString InputParameterName = InInputParameterName.ToString(); - const int32 NumInputs = HAC->GetNumInputs(); - for (int32 Index = 0; Index < NumInputs; ++Index) - { - UHoudiniInput* const Input = HAC->GetInputAt(Index); - if (!IsValid(Input)) - continue; - if (Input->IsObjectPathParameter() && Input->GetName() == InputParameterName) - return Input; - } - - return nullptr; -} - -const UHoudiniInput* -UHoudiniPublicAPIAssetWrapper::FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) const -{ - if (InInputParameterName == NAME_None) - return nullptr; - - UHoudiniAssetComponent* HAC = nullptr; - if (!GetValidHoudiniAssetComponentWithError(HAC)) - return nullptr; - - const FString InputParameterName = InInputParameterName.ToString(); - const int32 NumInputs = HAC->GetNumInputs(); - for (int32 Index = 0; Index < NumInputs; ++Index) - { - UHoudiniInput const* const Input = HAC->GetInputAt(Index); - if (!IsValid(Input)) - continue; - if (Input->IsObjectPathParameter() && Input->GetName() == InputParameterName) - return Input; - } - - return nullptr; -} - -bool -UHoudiniPublicAPIAssetWrapper::CreateAndPopulateAPIInput(const UHoudiniInput* InHoudiniInput, UHoudiniPublicAPIInput*& OutAPIInput) -{ - if (!IsValid(InHoudiniInput)) - return false; - - TSubclassOf APIInputClass; - const EHoudiniInputType InputType = InHoudiniInput->GetInputType(); - switch (InputType) - { - case EHoudiniInputType::Geometry: - APIInputClass = UHoudiniPublicAPIGeoInput::StaticClass(); - break; - case EHoudiniInputType::Curve: - APIInputClass = UHoudiniPublicAPICurveInput::StaticClass(); - break; - case EHoudiniInputType::Asset: - APIInputClass = UHoudiniPublicAPIAssetInput::StaticClass(); - break; - case EHoudiniInputType::World: - APIInputClass = UHoudiniPublicAPIWorldInput::StaticClass(); - break; - case EHoudiniInputType::Landscape: - APIInputClass = UHoudiniPublicAPILandscapeInput::StaticClass(); - break; - case EHoudiniInputType::Skeletal: - // Not yet implemented - SetErrorMessage(FString::Printf(TEXT("GetInputAtIndex: Input type not yet implemented %d"), InputType)); - return false; - case EHoudiniInputType::Invalid: - SetErrorMessage(FString::Printf(TEXT("GetInputAtIndex: Invalid input type %d"), InputType)); - return false; - } - - UHoudiniPublicAPIInput* APIInput = CreateEmptyInput(APIInputClass); - if (!IsValid(APIInput)) - { - return false; - } - - const bool bSuccessfullyCopied = APIInput->PopulateFromHoudiniInput(InHoudiniInput); - OutAPIInput = APIInput; - return bSuccessfullyCopied; -} - -bool -UHoudiniPublicAPIAssetWrapper::PopulateHoudiniInput(const UHoudiniPublicAPIInput* InAPIInput, UHoudiniInput* InHoudiniInput) const -{ - if (!IsValid(InHoudiniInput)) - return false; - - return InAPIInput->UpdateHoudiniInput(InHoudiniInput); -} - -bool -UHoudiniPublicAPIAssetWrapper::GetValidTOPNetworkByPathWithError(const FString& InNetworkRelativePath, int32& OutNetworkIndex, UTOPNetwork*& OutNetwork) const -{ - UHoudiniPDGAssetLink* AssetLink = nullptr; - if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) - return false; - - int32 NetworkIndex = INDEX_NONE; - UTOPNetwork* const Network = UHoudiniPDGAssetLink::GetTOPNetworkByNodePath( - InNetworkRelativePath, AssetLink->AllTOPNetworks, NetworkIndex); - if (!IsValid(Network)) - { - SetErrorMessage(FString::Printf( - TEXT("Could not find valid TOP network at relative path '%s'."), *InNetworkRelativePath)); - return false; - } - - OutNetworkIndex = NetworkIndex; - OutNetwork = Network; - return true; -} - -bool -UHoudiniPublicAPIAssetWrapper::GetValidTOPNodeByPathWithError( - const FString& InNetworkRelativePath, - const FString& InNodeRelativePath, - int32& OutNetworkIndex, - int32& OutNodeIndex, - UTOPNode*& OutNode) const -{ - int32 NetworkIndex = INDEX_NONE; - UTOPNetwork* Network = nullptr; - if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, Network)) - return false; - - int32 NodeIndex = INDEX_NONE; - UTOPNode* const Node = UHoudiniPDGAssetLink::GetTOPNodeByNodePath( - InNodeRelativePath, Network->AllTOPNodes, NodeIndex); - if (!IsValid(Node)) - { - SetErrorMessage(FString::Printf( - TEXT("Could not find valid TOP node at relative path '%s'."), *InNodeRelativePath)); - return false; - } - - OutNetworkIndex = NetworkIndex; - OutNodeIndex = NodeIndex; - OutNode = Node; - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniPublicAPIAssetWrapper.h" + +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineBakeUtils.h" +#include "HoudiniEngineCommands.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniOutputDetails.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniPDGManager.h" +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPIInputTypes.h" + +FHoudiniPublicAPIRampPoint::FHoudiniPublicAPIRampPoint() + : Position(0) + , Interpolation(EHoudiniPublicAPIRampInterpolationType::LINEAR) +{ +} + +FHoudiniPublicAPIRampPoint::FHoudiniPublicAPIRampPoint( + const float InPosition, + const EHoudiniPublicAPIRampInterpolationType InInterpolation) + : Position(InPosition) + , Interpolation(InInterpolation) +{ + +} + +FHoudiniPublicAPIFloatRampPoint::FHoudiniPublicAPIFloatRampPoint() + : Value(0) +{ +} + +FHoudiniPublicAPIFloatRampPoint::FHoudiniPublicAPIFloatRampPoint( + const float InPosition, + const float InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation) + : FHoudiniPublicAPIRampPoint(InPosition, InInterpolation) + , Value(InValue) +{ + +} + +FHoudiniPublicAPIColorRampPoint::FHoudiniPublicAPIColorRampPoint() + : Value(FLinearColor::Black) +{ +} + +FHoudiniPublicAPIColorRampPoint::FHoudiniPublicAPIColorRampPoint( + const float InPosition, + const FLinearColor& InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation) + : FHoudiniPublicAPIRampPoint(InPosition, InInterpolation) + , Value(InValue) +{ + +} + +FHoudiniParameterTuple::FHoudiniParameterTuple() + : BoolValues() + , Int32Values() + , FloatValues() + , StringValues() +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const bool& InValue) + : FHoudiniParameterTuple() +{ + BoolValues.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : BoolValues(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const int32& InValue) + : FHoudiniParameterTuple() +{ + Int32Values.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : Int32Values(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const float& InValue) + : FHoudiniParameterTuple() +{ + FloatValues.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : FloatValues(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const FString& InValue) + : FHoudiniParameterTuple() +{ + StringValues.Add(InValue); +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InValues) + : StringValues(InValues) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InRampPoints) + : FloatRampPoints(InRampPoints) +{ +} + +FHoudiniParameterTuple::FHoudiniParameterTuple(const TArray& InRampPoints) + : ColorRampPoints(InRampPoints) +{ +} + +// +// UHoudiniPublicAPIAssetWrapper +// + + +UHoudiniPublicAPIAssetWrapper::UHoudiniPublicAPIAssetWrapper() + : HoudiniAssetObject(nullptr) + , bAssetLinkSetupAttemptComplete(false) +{ + +} + +UHoudiniPublicAPIAssetWrapper* +UHoudiniPublicAPIAssetWrapper::CreateWrapper(UObject* InOuter, UObject* InHoudiniAssetActorOrComponent) +{ + if (!IsValid(InHoudiniAssetActorOrComponent)) + return nullptr; + + // Check if InHoudiniAssetActorOrComponent is supported + if (!CanWrapHoudiniObject(InHoudiniAssetActorOrComponent)) + return nullptr; + + UHoudiniPublicAPIAssetWrapper* NewWrapper = CreateEmptyWrapper(InOuter); + if (!IsValid(NewWrapper)) + return nullptr; + + // If we cannot wrap the specified actor, return nullptr. + if (!NewWrapper->WrapHoudiniAssetObject(InHoudiniAssetActorOrComponent)) + { + NewWrapper->MarkPendingKill(); + return nullptr; + } + + return NewWrapper; +} + +UHoudiniPublicAPIAssetWrapper* +UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(UObject* InOuter) +{ + UObject* const Outer = InOuter ? InOuter : GetTransientPackage(); + UClass* const Class = StaticClass(); + UHoudiniPublicAPIAssetWrapper* NewWrapper = NewObject( + Outer, Class, + MakeUniqueObjectName(Outer, Class)); + if (!IsValid(NewWrapper)) + return nullptr; + + return NewWrapper; +} + +bool +UHoudiniPublicAPIAssetWrapper::CanWrapHoudiniObject(UObject* InObject) +{ + if (!IsValid(InObject)) + return false; + + return InObject->IsA() || InObject->IsA(); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetTemporaryCookFolder_Implementation(FDirectoryPath& OutDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + OutDirectoryPath = HAC->TemporaryCookFolder; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetTemporaryCookFolder_Implementation(const FDirectoryPath& InDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + if (HAC->TemporaryCookFolder.Path != InDirectoryPath.Path) + HAC->TemporaryCookFolder = InDirectoryPath; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetBakeFolder_Implementation(FDirectoryPath& OutDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + OutDirectoryPath = HAC->BakeFolder; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetBakeFolder_Implementation(const FDirectoryPath& InDirectoryPath) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + if (HAC->BakeFolder.Path != InDirectoryPath.Path) + HAC->BakeFolder = InDirectoryPath; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::BakeAllOutputs_Implementation() +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( + HAC, + HAC->bReplacePreviousBake, + HAC->HoudiniEngineBakeOption, + HAC->bRemoveOutputAfterBake, + HAC->bRecenterBakedActors); +} + +bool +UHoudiniPublicAPIAssetWrapper::BakeAllOutputsWithSettings_Implementation( + EHoudiniEngineBakeOption InBakeOption, + bool bInReplacePreviousBake, + bool bInRemoveTempOutputsOnSuccess, + bool bInRecenterBakedActors) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent(HAC, bInReplacePreviousBake, InBakeOption, bInRemoveTempOutputsOnSuccess, bInRecenterBakedActors); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetAutoBakeEnabled_Implementation(const bool bInAutoBakeEnabled) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->SetBakeAfterNextCookEnabled(bInAutoBakeEnabled); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::IsAutoBakeEnabled_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->IsBakeAfterNextCookEnabled(); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetBakeMethod_Implementation(const EHoudiniEngineBakeOption InBakeMethod) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->HoudiniEngineBakeOption = InBakeMethod; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetBakeMethod_Implementation(EHoudiniEngineBakeOption& OutBakeMethod) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + OutBakeMethod = HAC->HoudiniEngineBakeOption; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetRemoveOutputAfterBake_Implementation(const bool bInRemoveOutputAfterBake) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->bRemoveOutputAfterBake = bInRemoveOutputAfterBake; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRemoveOutputAfterBake_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->bRemoveOutputAfterBake; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetRecenterBakedActors_Implementation(const bool bInRecenterBakedActors) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->bRecenterBakedActors = bInRecenterBakedActors; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRecenterBakedActors_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->bRecenterBakedActors; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetReplacePreviousBake_Implementation(const bool bInReplacePreviousBake) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->bReplacePreviousBake = bInReplacePreviousBake; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetReplacePreviousBake_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->bReplacePreviousBake; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidHoudiniAssetActorWithError(AHoudiniAssetActor*& OutActor) const +{ + AHoudiniAssetActor* const Actor = GetHoudiniAssetActor(); + if (!IsValid(Actor)) + { + SetErrorMessage( + TEXT("Could not find a valid AHoudiniAssetActor for the wrapped asset, or no asset has been wrapped.")); + return false; + } + + OutActor = Actor; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidHoudiniAssetComponentWithError(UHoudiniAssetComponent*& OutHAC) const +{ + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (!IsValid(HAC)) + { + SetErrorMessage( + TEXT("Could not find a valid HoudiniAssetComponent for the wrapped asset, or no asset has been wrapped.")); + return false; + } + + OutHAC = HAC; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidOutputAtWithError(const int32 InOutputIndex, UHoudiniOutput*& OutOutput) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + // Check if InOutputIndex is a valid/in-range index + const int32 NumOutputs = HAC->GetNumOutputs(); + if (InOutputIndex < 0 || InOutputIndex >= NumOutputs) + { + SetErrorMessage(FString::Printf( + TEXT("Output index %d is out of range [0, %d]"), InOutputIndex, NumOutputs)); + return false; + } + + UHoudiniOutput* const Output= HAC->GetOutputAt(InOutputIndex); + if (!IsValid(Output)) + { + SetErrorMessage(FString::Printf(TEXT("Output at index %d is invalid."), InOutputIndex)); + return false; + } + + OutOutput = Output; + return true; +} + + +UHoudiniPDGAssetLink* +UHoudiniPublicAPIAssetWrapper::GetHoudiniPDGAssetLink() const +{ + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (!IsValid(HAC)) + return nullptr; + + return HAC->GetPDGAssetLink(); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidHoudiniPDGAssetLinkWithError(UHoudiniPDGAssetLink*& OutAssetLink) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniPDGAssetLink* const AssetLink = HAC->GetPDGAssetLink(); + if (!IsValid(AssetLink)) + { + SetErrorMessage( + TEXT("Could not find a valid HoudiniPDGAssetLink for the wrapped asset. Does it contain a TOP network?")); + return false; + } + + OutAssetLink = AssetLink; + return true; +} + +void +UHoudiniPublicAPIAssetWrapper::ClearHoudiniAssetObject_Implementation() +{ + UHoudiniPDGAssetLink* const AssetLink = GetHoudiniPDGAssetLink(); + if (IsValid(AssetLink)) + { + if (OnPDGPostTOPNetworkCookDelegateHandle.IsValid()) + AssetLink->GetOnPostTOPNetworkCookDelegate().Remove(OnPDGPostTOPNetworkCookDelegateHandle); + if (OnPDGPostBakeDelegateHandle.IsValid()) + AssetLink->GetOnPostBakeDelegate().Remove(OnPDGPostBakeDelegateHandle); + } + + bAssetLinkSetupAttemptComplete = false; + + FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefinedDelegate().Remove(OnHoudiniProxyMeshesRefinedDelegateHandle); + + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (IsValid(HAC)) + { + if (OnAssetStateChangeDelegateHandle.IsValid()) + HAC->GetOnAssetStateChangeDelegate().Remove(OnAssetStateChangeDelegateHandle); + if (OnPostCookDelegateHandle.IsValid()) + HAC->GetOnPostCookDelegate().Remove(OnPostCookDelegateHandle); + if (OnPostBakeDelegateHandle.IsValid()) + HAC->GetOnPostBakeDelegate().Remove(OnPostBakeDelegateHandle); + } + + OnPDGPostTOPNetworkCookDelegateHandle.Reset(); + OnPDGPostBakeDelegateHandle.Reset(); + OnAssetStateChangeDelegateHandle.Reset(); + OnPostCookDelegateHandle.Reset(); + OnPostBakeDelegateHandle.Reset(); + OnHoudiniProxyMeshesRefinedDelegateHandle.Reset(); + + HoudiniAssetObject = nullptr; + CachedHoudiniAssetActor = nullptr; + CachedHoudiniAssetComponent = nullptr; +} + +bool +UHoudiniPublicAPIAssetWrapper::WrapHoudiniAssetObject_Implementation(UObject* InHoudiniAssetObjectToWrap) +{ + // If InHoudiniAssetObjectToWrap is null, just unwrap any currently wrapped asset + if (!InHoudiniAssetObjectToWrap) + { + ClearHoudiniAssetObject(); + return true; + } + + // Check if InHoudiniAssetObjectToWrap is supported + if (!CanWrapHoudiniObject(InHoudiniAssetObjectToWrap)) + { + UClass* const ObjectClass = IsValid(InHoudiniAssetObjectToWrap) ? InHoudiniAssetObjectToWrap->GetClass() : nullptr; + SetErrorMessage(FString::Printf( + TEXT("Cannot wrap objects of class '%s'."), ObjectClass ? *(ObjectClass->GetName()) : TEXT("Unknown"))); + return false; + } + + // First unwrap/unbind if we are currently wrapping an instantiated asset + ClearHoudiniAssetObject(); + + HoudiniAssetObject = InHoudiniAssetObjectToWrap; + + // Cache the HoudiniAssetActor and HoudiniAssetComponent + if (HoudiniAssetObject->IsA()) + { + CachedHoudiniAssetActor = Cast(InHoudiniAssetObjectToWrap); + CachedHoudiniAssetComponent = CachedHoudiniAssetActor->HoudiniAssetComponent; + } + else if (HoudiniAssetObject->IsA()) + { + CachedHoudiniAssetComponent = Cast(InHoudiniAssetObjectToWrap); + CachedHoudiniAssetActor = Cast(CachedHoudiniAssetComponent->GetOwner()); + } + + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (IsValid(HAC)) + { + // Bind to HandleOnHoudiniAssetStateChange from the HAC: we also implement IHoudiniAssetStateEvents, and + // in the default implementation HandleOnHoudiniAssetStateChange will call the appropriate Handle functions + // for PostInstantiate, PostCook etc + OnAssetStateChangeDelegateHandle = HAC->GetOnAssetStateChangeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentStateChange")); + OnPostCookDelegateHandle = HAC->GetOnPostCookDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentPostCook")); + OnPostBakeDelegateHandle = HAC->GetOnPostBakeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniAssetComponentPostBake")); + } + + OnHoudiniProxyMeshesRefinedDelegateHandle = FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefinedDelegate().AddUFunction(this, TEXT("HandleOnHoudiniProxyMeshesRefinedGlobal")); + + // PDG asset link bindings: We attempt to bind to PDG here, but it likely is not available yet. + // We have to wait until post instantiation in order to know if there is a PDG asset link + // for this HDA. This is checked again in HandleOnHoudiniAssetComponentStateChange and sets + // bAssetLinkSetupAttemptComplete. + BindToPDGAssetLink(); + + return true; +} + +AHoudiniAssetActor* +UHoudiniPublicAPIAssetWrapper::GetHoudiniAssetActor_Implementation() const +{ + return CachedHoudiniAssetActor.Get(); +} + +UHoudiniAssetComponent* +UHoudiniPublicAPIAssetWrapper::GetHoudiniAssetComponent_Implementation() const +{ + return CachedHoudiniAssetComponent.Get(); +} + +bool +UHoudiniPublicAPIAssetWrapper::DeleteInstantiatedAsset_Implementation() +{ + AHoudiniAssetActor* AssetActor = nullptr; + if (!GetValidHoudiniAssetActorWithError(AssetActor)) + return false; + + // Unbind / unwrap the HDA actor + ClearHoudiniAssetObject(); + AssetActor->Destroy(); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::Rebuild_Implementation() +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->MarkAsNeedRebuild(); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::Recook_Implementation() +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + HAC->MarkAsNeedCook(); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetAutoCookingEnabled_Implementation(const bool bInSetEnabled) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + if (HAC->IsCookingEnabled() == bInSetEnabled) + return false; + + HAC->SetCookingEnabled(bInSetEnabled); + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::IsAutoCookingEnabled_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->IsCookingEnabled(); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetFloatParameterValue_Implementation(FName InParameterTupleName, float InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParam = Cast(Param); + if (!IsValid(FloatParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FloatParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FloatParam->GetNumberOfValues())); + return false; + } + + bDidChangeValue = FloatParam->SetValueAt(InValue, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + static const int32 NumColorChannels = 4; + if (InAtIndex >= NumColorChannels) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, NumColorChannels)); + return false; + } + + FLinearColor CurrentColorValue = ColorParam->GetColorValue(); + if (CurrentColorValue.Component(InAtIndex) != InValue) + { + CurrentColorValue.Component(InAtIndex) = InValue; + ColorParam->SetColorValue(CurrentColorValue); + bDidChangeValue = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetFloatParamterValue."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetFloatParameterValue_Implementation(FName InParameterTupleName, float& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParam = Cast(Param); + if (!IsValid(FloatParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FloatParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FloatParam->GetNumberOfValues())); + return false; + } + + return FloatParam->GetValueAt(InAtIndex, OutValue); + } + else if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + static const int32 NumColorChannels = 4; + if (InAtIndex >= NumColorChannels) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, NumColorChannels)); + return false; + } + + OutValue = ColorParam->GetColorValue().Component(InAtIndex); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetColorParameterValue_Implementation(FName InParameterTupleName, const FLinearColor& InValue, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ColorParam->SetColorValue(InValue); + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetColorParamterValue."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetColorParameterValue_Implementation(FName InParameterTupleName, FLinearColor& OutValue) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is a float + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParam = Cast(Param); + if (!IsValid(ColorParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ColorParam->GetColorValue(); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetIntParameterValue_Implementation(FName InParameterTupleName, int32 InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Int) + { + UHoudiniParameterInt* IntParam = Cast(Param); + if (!IsValid(IntParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= IntParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, IntParam->GetNumberOfValues())); + return false; + } + + bDidChangeValue = IntParam->SetValueAt(InValue, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ChoiceParam->SetIntValue(InValue); + } + else if (ParamType == EHoudiniParameterType::MultiParm) + { + UHoudiniParameterMultiParm* MultiParam = Cast(Param); + if (!IsValid(MultiParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = MultiParam->SetValue(InValue); + } + else if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ToggleParam->SetValueAt(InValue != 0, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + const bool NewValue = InValue != 0; + if (FolderParam->IsChosen() != NewValue) + { + FolderParam->SetChosen(NewValue); + bDidChangeValue = true; + } + } + else if (ParamType == EHoudiniParameterType::FloatRamp || ParamType == EHoudiniParameterType::ColorRamp) + { + // For ramps we have to use the appropriate function so that delete/insert operations are managed correctly + bDidChangeValue = SetRampParameterNumPoints(InParameterTupleName, InValue); + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetIntParameterValue."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetIntParameterValue_Implementation(FName InParameterTupleName, int32& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Int) + { + UHoudiniParameterInt* IntParam = Cast(Param); + if (!IsValid(IntParam)) + return false; + + if (InAtIndex >= IntParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, IntParam->GetNumberOfValues())); + return false; + } + + return IntParam->GetValueAt(InAtIndex, OutValue); + } + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ChoiceParam->GetIntValue(ChoiceParam->GetIntValueIndex()); + return true; + } + else if (ParamType == EHoudiniParameterType::MultiParm) + { + UHoudiniParameterMultiParm* MultiParam = Cast(Param); + if (!IsValid(MultiParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = MultiParam->GetValue(); + return true; + } + else if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ToggleParam->GetValueAt(InAtIndex); + return true; + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = FolderParam->IsChosen(); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetBoolParameterValue_Implementation(FName InParameterTupleName, bool InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ToggleParam->SetValueAt(InValue, InAtIndex); + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (FolderParam->IsChosen() != InValue) + { + FolderParam->SetChosen(InValue); + bDidChangeValue = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetBoolParameterValue."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetBoolParameterValue_Implementation(FName InParameterTupleName, bool& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::Toggle) + { + UHoudiniParameterToggle* ToggleParam = Cast(Param); + if (!IsValid(ToggleParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ToggleParam->GetValueAt(InAtIndex); + return true; + } + else if (ParamType == EHoudiniParameterType::Folder) + { + UHoudiniParameterFolder* FolderParam = Cast(Param); + if (!IsValid(FolderParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = FolderParam->IsChosen(); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetStringParameterValue_Implementation(FName InParameterTupleName, const FString& InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::String || ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + // We have to handle asset references differently + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + // Find/load the asset, make sure it is a valid reference/object + const FSoftObjectPath AssetRef(InValue); + UObject* const Asset = AssetRef.TryLoad(); + if (IsValid(Asset)) + { + UObject* const CurrentAsset = StringParam->GetAssetAt(InAtIndex); + if (CurrentAsset != Asset) + { + StringParam->SetAssetAt(Asset, InAtIndex); + bDidChangeValue = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Asset reference '%s' is invalid. Not setting parameter value."), *InValue)); + return false; + } + } + else + { + bDidChangeValue = StringParam->SetValueAt(InValue, InAtIndex); + } + } + else if (ParamType == EHoudiniParameterType::StringChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + bDidChangeValue = ChoiceParam->SetStringValue(InValue); + } + else if (ParamType == EHoudiniParameterType::File || ParamType == EHoudiniParameterType::FileDir || + ParamType == EHoudiniParameterType::FileGeo || ParamType == EHoudiniParameterType::FileImage) + { + UHoudiniParameterFile* FileParam = Cast(Param); + if (!IsValid(FileParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FileParam->GetNumValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FileParam->GetNumValues())); + return false; + } + + bDidChangeValue = FileParam->SetValueAt(InValue, InAtIndex); + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetStringParameterValue."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetStringParameterValue_Implementation(FName InParameterTupleName, FString& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::String || ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + // For asset references: get the asset, and then get the string reference from it and return that. + // If the asset is null, return the empty string. + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + UObject* const Asset = StringParam->GetAssetAt(InAtIndex); + if (IsValid(Asset)) + { + OutValue = UHoudiniParameterString::GetAssetReference(Asset); + } + else + { + OutValue.Empty(); + } + } + else + { + OutValue = StringParam->GetValueAt(InAtIndex); + } + return true; + } + else if (ParamType == EHoudiniParameterType::StringChoice) + { + UHoudiniParameterChoice* ChoiceParam = Cast(Param); + if (!IsValid(ChoiceParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + OutValue = ChoiceParam->GetStringValue(); + return true; + } + else if (ParamType == EHoudiniParameterType::File || ParamType == EHoudiniParameterType::FileDir || + ParamType == EHoudiniParameterType::FileGeo || ParamType == EHoudiniParameterType::FileImage) + { + UHoudiniParameterFile* FileParam = Cast(Param); + if (!IsValid(FileParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= FileParam->GetNumValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, FileParam->GetNumValues())); + return false; + } + + OutValue = FileParam->GetValueAt(InAtIndex); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetAssetRefParameterValue_Implementation(FName InParameterTupleName, UObject* InValue, int32 InAtIndex, bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + bool bDidChangeValue = false; + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + // Find/load the asset, make sure it is a valid reference/object + UObject* const CurrentAsset = StringParam->GetAssetAt(InAtIndex); + if (CurrentAsset != InValue) + { + StringParam->SetAssetAt(InValue, InAtIndex); + bDidChangeValue = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is of a type that cannot be set via SetAssetRefParamter."), *InParameterTupleName.ToString())); + return false; + } + + if (bDidChangeValue && bInMarkChanged) + Param->MarkChanged(true); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetAssetRefParameterValue_Implementation(FName InParameterTupleName, UObject*& OutValue, int32 InAtIndex) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::StringAssetRef) + { + UHoudiniParameterString* StringParam = Cast(Param); + if (!IsValid(StringParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + if (InAtIndex >= StringParam->GetNumberOfValues()) + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple index %d is out of range (tuple size == %d)."), InAtIndex, StringParam->GetNumberOfValues())); + return false; + } + + OutValue = StringParam->GetAssetAt(InAtIndex); + return true; + } + + return false; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetRampParameterNumPoints_Implementation(FName InParameterTupleName, const int32 InNumPoints) const +{ + if (InNumPoints < 1) + { + SetErrorMessage(TEXT("InNumPoints must be >= 1.")); + return false; + } + + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParameterTupleName.ToString()))); + return false; + } + + if (bUseCachedPoints) + { + // When using the cached points we only have to resize the array + if (FloatRampParam) + { + const int32 CurrentNumPoints = FloatRampParam->CachedPoints.Num(); + if (CurrentNumPoints != InNumPoints) + { + FloatRampParam->CachedPoints.SetNum(InNumPoints); + // FloatRampParam->MarkChanged(true); + } + } + else + { + const int32 CurrentNumPoints = ColorRampParam->CachedPoints.Num(); + if (CurrentNumPoints != InNumPoints) + { + ColorRampParam->CachedPoints.SetNum(InNumPoints); + // ColorRampParam->MarkChanged(true); + } + } + + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + else + { + int32 NumPendingInsertOperations = 0; + int32 NumPendingDeleteOperations = 0; + TSet InstanceIndexesPendingDelete; + TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; + for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) + { + if (!IsValid(Event)) + continue; + + if (Event->IsInsertEvent()) + NumPendingInsertOperations++; + else if (Event->IsDeleteEvent()) + { + InstanceIndexesPendingDelete.Add(Event->DeleteInstanceIndex); + NumPendingDeleteOperations++; + } + } + + const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); + int32 CurrentNumPoints = PointsArraySize + NumPendingInsertOperations - NumPendingDeleteOperations; + + if (InNumPoints < CurrentNumPoints) + { + // When deleting points, first remove pending insert operations from the end + if (NumPendingInsertOperations > 0) + { + const int32 NumEvents = ModificationEvents.Num(); + TArray TempModificationArray; + TempModificationArray.Reserve(NumEvents); + + for (int32 Index = NumEvents - 1; Index >= 0; --Index) + { + UHoudiniParameterRampModificationEvent* const Event = ModificationEvents[Index]; + if (InNumPoints < CurrentNumPoints && IsValid(Event) && Event->IsInsertEvent()) + { + CurrentNumPoints--; + NumPendingInsertOperations--; + continue; + } + + TempModificationArray.Add(Event); + } + + Algo::Reverse(TempModificationArray); + ModificationEvents = MoveTemp(TempModificationArray); + } + + // If we still have points to delete... + if (InNumPoints < CurrentNumPoints) + { + // Deleting points, add delete operations, deleting from the end of Points (points that are not yet + // pending delete) + for (int32 Index = PointsArraySize - 1; (InNumPoints < CurrentNumPoints && Index >= 0); --Index) + { + int32 InstanceIndex = INDEX_NONE; + + if (FloatRampParam) + { + UHoudiniParameterRampFloatPoint const* const PointData = FloatRampParam->Points[Index]; + if (!IsValid(PointData)) + continue; + + InstanceIndex = PointData->InstanceIndex; + } + else + { + UHoudiniParameterRampColorPoint const* const PointData = ColorRampParam->Points[Index]; + if (!IsValid(PointData)) + continue; + + InstanceIndex = PointData->InstanceIndex; + } + + if (!InstanceIndexesPendingDelete.Contains(InstanceIndex)) + { + InstanceIndexesPendingDelete.Add(InstanceIndex); + CurrentNumPoints--; + + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + FloatRampParam, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (DeleteEvent) + { + if (FloatRampParam) + { + DeleteEvent->SetFloatRampEvent(); + } + else + { + DeleteEvent->SetColorRampEvent(); + } + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InstanceIndex; + + ModificationEvents.Add(DeleteEvent); + } + } + } + } + + Param->MarkChanged(true); + } + else if (InNumPoints > CurrentNumPoints) + { + // Adding points, add insert operations + while (InNumPoints > CurrentNumPoints) + { + CurrentNumPoints++; + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + Param, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (InsertEvent) + { + if (FloatRampParam) + { + InsertEvent->SetFloatRampEvent(); + } + else + { + InsertEvent->SetColorRampEvent(); + } + InsertEvent->SetInsertEvent(); + // Leave point position, value and interpolation at default + + ModificationEvents.Add(InsertEvent); + } + } + + Param->MarkChanged(true); + } + + // If at this point InNumPoints != CurrentNumPoints then something went wrong, we couldn't delete all the + // desired points + if (InNumPoints != CurrentNumPoints) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected error: could not delete the required number of ramp points " + "(target # points = %d; have # points %d)."), InNumPoints, CurrentNumPoints)); + return false; + } + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRampParameterNumPoints_Implementation(FName InParameterTupleName, int32& OutNumPoints) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParameterTupleName.ToString()))); + return false; + } + + if (bUseCachedPoints) + { + // When using the cached points we only have to resize the array + if (FloatRampParam) + { + OutNumPoints = FloatRampParam->CachedPoints.Num(); + } + else + { + OutNumPoints = ColorRampParam->CachedPoints.Num(); + } + } + else + { + int32 NumPendingInsertOperations = 0; + int32 NumPendingDeleteOperations = 0; + TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; + for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) + { + if (!IsValid(Event)) + continue; + + if (Event->IsInsertEvent()) + NumPendingInsertOperations++; + else if (Event->IsDeleteEvent()) + NumPendingDeleteOperations++; + } + + const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); + OutNumPoints = PointsArraySize + NumPendingInsertOperations - NumPendingDeleteOperations; + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetFloatRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const float InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType, + const bool bInMarkChanged) +{ + return SetRampParameterPointValue( + InParameterTupleName, InPointIndex, InPointPosition, InPointValue, FLinearColor::Black, InInterpolationType, bInMarkChanged); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetFloatRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + float& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const +{ + FLinearColor ColorValue; + return GetRampParameterPointValue( + InParameterTupleName, InPointIndex, OutPointPosition, OutPointValue, ColorValue, OutInterpolationType); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetFloatRampParameterPoints_Implementation( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged) +{ + const int32 TargetNumPoints = InRampPoints.Num(); + if (TargetNumPoints == 0) + { + SetErrorMessage(TEXT("InRampPoints must have at least one entry.")); + return false; + } + + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + return false; + } + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + // Set the ramp point count to match the size of InRampPoints + if (!SetRampParameterNumPoints(InParameterTupleName, TargetNumPoints)) + return false; + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + // Check that we fetched the correct number of point data objects + if (RampPointData.Num() != TargetNumPoints) + { + SetErrorMessage(FString::Printf(TEXT("Failed to set the number of ramp points to %d."), TargetNumPoints)); + return false; + } + + for (int32 Index = 0; Index < TargetNumPoints; ++Index) + { + TPair const& Entry = RampPointData[Index]; + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + const FHoudiniPublicAPIFloatRampPoint& NewRampPoint = InRampPoints[Index]; + const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( + NewRampPoint.Interpolation); + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + if (bUseCachedPoints) + { + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (FloatPointData->Position != NewRampPoint.Position) + { + FloatPointData->Position = NewRampPoint.Position; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Value != NewRampPoint.Value) + { + FloatPointData->Value = NewRampPoint.Value; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Interpolation != NewInterpolation) + { + FloatPointData->Interpolation = NewInterpolation; + FloatRampParam->bCaching = true; + } + } + else + { + // When setting the main points, we set the values using the setters on the point data but still manually + // mark the position/value/interpolation parent parameters as changed + if (FloatPointData->Position != NewRampPoint.Position && FloatPointData->PositionParentParm) + { + FloatPointData->SetPosition(NewRampPoint.Position); + if (bInMarkChanged) + FloatPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Value != NewRampPoint.Value && FloatPointData->ValueParentParm) + { + FloatPointData->SetValue(NewRampPoint.Value); + if (bInMarkChanged) + FloatPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Interpolation != NewInterpolation && FloatPointData->InterpolationParentParm) + { + FloatPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + FloatPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + Event->InsertPosition = NewRampPoint.Position; + Event->InsertFloat = NewRampPoint.Value; + Event->InsertInterpolation = NewInterpolation; + } + } + + if (bUseCachedPoints) + { + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetFloatRampParameterPoints_Implementation( + FName InParameterTupleName, + TArray& OutRampPoints) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp parameter."), *(Param->GetName()))); + return false; + } + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + OutRampPoints.Reserve(RampPointData.Num()); + const bool bAllowShrinking = false; + OutRampPoints.SetNum(0, bAllowShrinking); + for (TPair const& Entry : RampPointData) + { + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + FHoudiniPublicAPIFloatRampPoint TempPointData; + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* const FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = FloatPointData->Position; + TempPointData.Value = FloatPointData->Value; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + FloatPointData->Interpolation); + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = Event->InsertPosition; + TempPointData.Value = Event->InsertFloat; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); + } + + OutRampPoints.Add(TempPointData); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetColorRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const FLinearColor& InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType, + const bool bInMarkChanged) +{ + const float FloatValue = 0; + return SetRampParameterPointValue( + InParameterTupleName, InPointIndex, InPointPosition, FloatValue, InPointValue, InInterpolationType, bInMarkChanged); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetColorRampParameterPointValue_Implementation( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + FLinearColor& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const +{ + float FloatValue = 0; + return GetRampParameterPointValue( + InParameterTupleName, InPointIndex, OutPointPosition, FloatValue, OutPointValue, OutInterpolationType); +} + +bool +UHoudiniPublicAPIAssetWrapper::SetColorRampParameterPoints_Implementation( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged) +{ + const int32 TargetNumPoints = InRampPoints.Num(); + if (TargetNumPoints == 0) + { + SetErrorMessage(TEXT("InRampPoints must have at least one entry.")); + return false; + } + + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a color ramp parameter."), *(Param->GetName()))); + return false; + } + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + // Set the ramp point count to match the size of InRampPoints + if (!SetRampParameterNumPoints(InParameterTupleName, TargetNumPoints)) + return false; + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + // Check that we fetched the correct number of point data objects + if (RampPointData.Num() != TargetNumPoints) + { + SetErrorMessage(FString::Printf(TEXT("Failed to set the number of ramp points to %d."), TargetNumPoints)); + return false; + } + + for (int32 Index = 0; Index < TargetNumPoints; ++Index) + { + TPair const& Entry = RampPointData[Index]; + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + const FHoudiniPublicAPIColorRampPoint& NewRampPoint = InRampPoints[Index]; + const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( + NewRampPoint.Interpolation); + + if (bIsPointData) + { + UHoudiniParameterRampColorPoint* ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + if (bUseCachedPoints) + { + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (ColorPointData->Position != NewRampPoint.Position) + { + ColorPointData->Position = NewRampPoint.Position; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Value != NewRampPoint.Value) + { + ColorPointData->Value = NewRampPoint.Value; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Interpolation != NewInterpolation) + { + ColorPointData->Interpolation = NewInterpolation; + ColorRampParam->bCaching = true; + } + } + else + { + // When setting the main points, we set the values using the setters on the point data but still manually + // mark the position/value/interpolation parent parameters as changed + if (ColorPointData->Position != NewRampPoint.Position && ColorPointData->PositionParentParm) + { + ColorPointData->SetPosition(NewRampPoint.Position); + if (bInMarkChanged) + ColorPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Value != NewRampPoint.Value && ColorPointData->ValueParentParm) + { + ColorPointData->SetValue(NewRampPoint.Value); + if (bInMarkChanged) + ColorPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Interpolation != NewInterpolation && ColorPointData->InterpolationParentParm) + { + ColorPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + ColorPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + Event->InsertPosition = NewRampPoint.Position; + Event->InsertColor = NewRampPoint.Value; + Event->InsertInterpolation = NewInterpolation; + } + } + + if (bUseCachedPoints) + { + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetColorRampParameterPoints_Implementation( + FName InParameterTupleName, + TArray& OutRampPoints) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp parameter."), *(Param->GetName()))); + return false; + } + + // Get all ramp points (INDEX_NONE == get all points) + TArray> RampPointData; + if (!FindRampPointData(Param, INDEX_NONE, RampPointData) || RampPointData.Num() <= 0) + return false; + + OutRampPoints.Reserve(RampPointData.Num()); + const bool bAllowShrinking = false; + OutRampPoints.SetNum(0, bAllowShrinking); + for (TPair const& Entry : RampPointData) + { + UObject* const PointData = Entry.Key; + const bool bIsPointData = Entry.Value; + if (!IsValid(PointData)) + return false; + + FHoudiniPublicAPIColorRampPoint TempPointData; + + if (bIsPointData) + { + UHoudiniParameterRampColorPoint* const ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = ColorPointData->Position; + TempPointData.Value = ColorPointData->Value; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + ColorPointData->Interpolation); + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + TempPointData.Position = Event->InsertPosition; + TempPointData.Value = Event->InsertColor; + TempPointData.Interpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); + } + + OutRampPoints.Add(TempPointData); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::TriggerButtonParameter_Implementation(FName InButtonParameterName) +{ + UHoudiniParameter* Param = FindValidParameterByName(InButtonParameterName); + if (!Param) + return false; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = Param->GetParameterType(); + // bool bDidTrigger = false; + if (ParamType == EHoudiniParameterType::Button) + { + UHoudiniParameterButton* ButtonParam = Cast(Param); + if (!IsValid(ButtonParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + + // Marking the button as changed will result in it being triggered/clicked via HAPI + if (!ButtonParam->HasChanged() || !ButtonParam->NeedsToTriggerUpdate()) + { + ButtonParam->MarkChanged(true); + // bDidTrigger = true; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter tuple '%s' is not a button."), *InButtonParameterName.ToString())); + return false; + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetParameterTuples_Implementation(TMap& OutParameterTuples) const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + const int32 NumParameters = HAC->GetNumParameters(); + OutParameterTuples.Empty(NumParameters); + OutParameterTuples.Reserve(NumParameters); + for (int32 Index = 0; Index < NumParameters; ++Index) + { + const UHoudiniParameter* const Param = HAC->GetParameterAt(Index); + const EHoudiniParameterType ParameterType = Param->GetParameterType(); + const int32 TupleSize = Param->GetTupleSize(); + const FName PTName(Param->GetParameterName()); + + FHoudiniParameterTuple ParameterTuple; + + bool bSkipped = false; + switch (ParameterType) + { + case EHoudiniParameterType::Color: + case EHoudiniParameterType::Float: + { + // Output as float + ParameterTuple.FloatValues.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetFloatParameterValue(PTName, ParameterTuple.FloatValues[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::Int: + case EHoudiniParameterType::IntChoice: + case EHoudiniParameterType::MultiParm: + { + // Output as int + ParameterTuple.Int32Values.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetIntParameterValue(PTName, ParameterTuple.Int32Values[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::String: + case EHoudiniParameterType::StringChoice: + case EHoudiniParameterType::StringAssetRef: + case EHoudiniParameterType::File: + case EHoudiniParameterType::FileDir: + case EHoudiniParameterType::FileGeo: + case EHoudiniParameterType::FileImage: + { + // Output as string + ParameterTuple.StringValues.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetStringParameterValue(PTName, ParameterTuple.StringValues[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::Toggle: + { + // Output as bool + ParameterTuple.BoolValues.SetNumZeroed(TupleSize); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + GetBoolParameterValue(PTName, ParameterTuple.BoolValues[TupleIndex], TupleIndex); + } + break; + } + + case EHoudiniParameterType::ColorRamp: + { + GetColorRampParameterPoints(PTName, ParameterTuple.ColorRampPoints); + break; + } + case EHoudiniParameterType::FloatRamp: + { + GetFloatRampParameterPoints(PTName, ParameterTuple.FloatRampPoints); + break; + } + + case EHoudiniParameterType::Button: + case EHoudiniParameterType::ButtonStrip: + case EHoudiniParameterType::Input: + case EHoudiniParameterType::Invalid: + case EHoudiniParameterType::Folder: + case EHoudiniParameterType::FolderList: + case EHoudiniParameterType::Label: + case EHoudiniParameterType::Separator: + default: + // Skipped + bSkipped = true; + break; + } + + if (!bSkipped) + OutParameterTuples.Add(PTName, ParameterTuple); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetParameterTuples_Implementation(const TMap& InParameterTuples) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + bool bSuccess = true; + for (const TPair& Entry : InParameterTuples) + { + const FName& ParameterTupleName = Entry.Key; + const FHoudiniParameterTuple& ParameterTuple = Entry.Value; + if (ParameterTuple.BoolValues.Num() > 0) + { + // Set as bool + const int32 TupleSize = ParameterTuple.BoolValues.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetBoolParameterValue(ParameterTupleName, ParameterTuple.BoolValues[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a bool at tuple index %d."), *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.FloatValues.Num() > 0) + { + // Set as float + const int32 TupleSize = ParameterTuple.FloatValues.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetFloatParameterValue(ParameterTupleName, ParameterTuple.FloatValues[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a float at tuple index %d."), + *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.Int32Values.Num() > 0) + { + // Set as int + const int32 TupleSize = ParameterTuple.Int32Values.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetIntParameterValue(ParameterTupleName, ParameterTuple.Int32Values[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a int at tuple index %d."), + *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.StringValues.Num() > 0) + { + // Set as string + const int32 TupleSize = ParameterTuple.StringValues.Num(); + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + if (!SetStringParameterValue(ParameterTupleName, ParameterTuple.StringValues[TupleIndex], TupleIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("SetParameterTuples: Failed to set %s as a string at tuple index %d."), + *ParameterTupleName.ToString(), TupleIndex)); + bSuccess = false; + break; + } + } + } + else if (ParameterTuple.FloatRampPoints.Num() > 0) + { + // Set as a float ramp + if (!SetFloatRampParameterPoints(ParameterTupleName, ParameterTuple.FloatRampPoints)) + bSuccess = false; + } + else if (ParameterTuple.ColorRampPoints.Num() > 0) + { + // Set as a color ramp + if (!SetColorRampParameterPoints(ParameterTupleName, ParameterTuple.ColorRampPoints)) + bSuccess = false; + } + } + + return bSuccess; +} + +UHoudiniPublicAPIInput* +UHoudiniPublicAPIAssetWrapper::CreateEmptyInput_Implementation(TSubclassOf InInputClass) +{ + UHoudiniPublicAPI* API = UHoudiniPublicAPIBlueprintLib::GetAPI(); + if (!IsValid(API)) + return nullptr; + + UHoudiniPublicAPIInput* const NewInput = API->CreateEmptyInput(InInputClass, this); + if (!IsValid(NewInput)) + { + SetErrorMessage(FString::Printf( + TEXT("Failed to create a new input of class '%s'."), + *(IsValid(InInputClass.Get()) ? InInputClass->GetName() : FString()))); + + return nullptr; + } + + return NewInput; +} + +int32 +UHoudiniPublicAPIAssetWrapper::GetNumNodeInputs_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return -1; + + int32 NumNodeInputs = 0; + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + + if (!Input->IsObjectPathParameter()) + NumNodeInputs++; + } + + return NumNodeInputs; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputAtIndex_Implementation(const int32 InNodeInputIndex, const UHoudiniPublicAPIInput* InInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniInput* HoudiniInput = GetHoudiniNodeInputByIndex(InNodeInputIndex); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputAtIndex: Could not find a HoudiniInput for InNodeInputIndex %d. Has the HDA been instantiated?"), + InNodeInputIndex)); + return false; + } + + return PopulateHoudiniInput(InInput, HoudiniInput); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputAtIndex_Implementation(const int32 InNodeInputIndex, UHoudiniPublicAPIInput*& OutInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + const UHoudiniInput* HoudiniInput = GetHoudiniNodeInputByIndex(InNodeInputIndex); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("GetInputAtIndex: Could not find a HoudiniInput for InNodeInputIndex %d. Has the HDA been instantiated?"), + InNodeInputIndex)); + return false; + } + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + return false; + + OutInput = APIInput; + return bSuccessfullyCopied; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputsAtIndices_Implementation(const TMap& InInputs) +{ + bool bAnyFailures = false; + for (const TPair& Entry : InInputs) + { + if (!SetInputAtIndex(Entry.Key, Entry.Value)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputsAtIndices: Failed to set node input at index %d"), Entry.Key)); + bAnyFailures = true; + } + } + + return !bAnyFailures; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputsAtIndices_Implementation(TMap& OutInputs) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + bool bAnyFailures = false; + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const HoudiniInput = HAC->GetInputAt(Index); + if (!IsValid(HoudiniInput) || HoudiniInput->IsObjectPathParameter()) + continue; + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + { + bAnyFailures = true; + continue; + } + if (!bSuccessfullyCopied) + bAnyFailures = true; + + OutInputs.Add(HoudiniInput->GetInputIndex(), APIInput); + } + + return !bAnyFailures; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputParameter_Implementation(const FName& InParameterName, const UHoudiniPublicAPIInput* InInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniInput* HoudiniInput = FindValidHoudiniNodeInputParameter(InParameterName); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputParameter: Could not find a parameter-based HoudiniInput with name %s. Has the HDA been instantiated?"), + *(InParameterName.ToString()))); + return false; + } + + return PopulateHoudiniInput(InInput, HoudiniInput); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputParameter_Implementation(const FName& InParameterName, UHoudiniPublicAPIInput*& OutInput) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + const UHoudiniInput* HoudiniInput = FindValidHoudiniNodeInputParameter(InParameterName); + if (!IsValid(HoudiniInput)) + { + SetErrorMessage(FString::Printf( + TEXT("GetInputParameter: Could not find a parameter-based HoudiniInput with name %s. Has the HDA been instantiated?"), + *(InParameterName.ToString()))); + return false; + } + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + return false; + + OutInput = APIInput; + return bSuccessfullyCopied; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetInputParameters_Implementation(const TMap& InInputs) +{ + bool bAnyFailures = false; + for (const TPair& Entry : InInputs) + { + if (!SetInputParameter(Entry.Key, Entry.Value)) + { + SetErrorMessage(FString::Printf( + TEXT("SetInputParameters: Failed to set input parameter %s"), *(Entry.Key.ToString()))); + bAnyFailures = true; + } + } + + return !bAnyFailures; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetInputParameters_Implementation(TMap& OutInputs) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + bool bAnyFailures = false; + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const HoudiniInput = HAC->GetInputAt(Index); + if (!IsValid(HoudiniInput) || !HoudiniInput->IsObjectPathParameter()) + continue; + + UHoudiniPublicAPIInput* APIInput = nullptr; + const bool bSuccessfullyCopied = CreateAndPopulateAPIInput(HoudiniInput, APIInput); + if (!IsValid(APIInput)) + { + bAnyFailures = true; + continue; + } + if (!bSuccessfullyCopied) + bAnyFailures = true; + + OutInputs.Add(FName(HoudiniInput->GetName()), APIInput); + } + + return !bAnyFailures; +} + +int32 +UHoudiniPublicAPIAssetWrapper::GetNumOutputs_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return -1; + + return HAC->GetNumOutputs(); +} + +EHoudiniOutputType +UHoudiniPublicAPIAssetWrapper::GetOutputTypeAt_Implementation(const int32 InIndex) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return EHoudiniOutputType::Invalid; + + return Output->GetType(); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetOutputIdentifiersAt_Implementation(const int32 InIndex, TArray& OutIdentifiers) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + const TMap& OutputObjects = Output->GetOutputObjects(); + OutIdentifiers.Empty(); + OutIdentifiers.Reserve(OutputObjects.Num()); + for (const TPair& Entry : OutputObjects) + { + OutIdentifiers.Add(FHoudiniPublicAPIOutputObjectIdentifier(Entry.Key)); + } + + return true; +} + +UObject* +UHoudiniPublicAPIAssetWrapper::GetOutputObjectAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return nullptr; + + const TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject const* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return nullptr; + + return OutputObject->bProxyIsCurrent ? OutputObject->ProxyObject : OutputObject->OutputObject; +} + +UObject* +UHoudiniPublicAPIAssetWrapper::GetOutputComponentAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return nullptr; + + const TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject const* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return nullptr; + + return OutputObject->bProxyIsCurrent ? OutputObject->ProxyComponent : OutputObject->OutputComponent; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetOutputBakeNameFallbackAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, FString& OutBakeName) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + const TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject const* const OutputObject =OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return false; + + OutBakeName = OutputObject->BakeName; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetOutputBakeNameFallbackAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FString& InBakeName) +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject* const OutputObject = OutputObjects.Find(InIdentifier.GetIdentifier()); + if (!OutputObject) + return false; + + OutputObject->BakeName = InBakeName; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::BakeOutputObjectAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FName InBakeName, const EHoudiniLandscapeOutputBakeType InLandscapeBakeType) +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + TMap& OutputObjects = Output->GetOutputObjects(); + const FHoudiniOutputObjectIdentifier& Identifier = InIdentifier.GetIdentifier(); + FHoudiniOutputObject* const OutputObject = OutputObjects.Find(Identifier); + if (!OutputObject) + { + SetErrorMessage(FString::Printf( + TEXT("BakeOutputObjectAt: Could not find an output object using the specified identifier."))); + return false; + } + + // Determine the object to bake (this is different depending on landscape, curve or mesh + const EHoudiniOutputType OutputType = Output->GetType(); + + if (OutputType == EHoudiniOutputType::Mesh && OutputObject->bProxyIsCurrent) + { + // Output is currently a proxy, this cannot be baked without cooking first. + SetErrorMessage(FString::Printf( + TEXT("BakeOutputObjectAt: Object is a proxy mesh, please refine it before baking to CB."))); + + return false; + } + + UObject* ObjectToBake = nullptr; + switch (OutputType) + { + case EHoudiniOutputType::Landscape: + { + UHoudiniLandscapePtr* const LandscapePtr = Cast(OutputObject->OutputObject); + if (IsValid(LandscapePtr)) + { + ObjectToBake = LandscapePtr->LandscapeSoftPtr.IsValid() ? LandscapePtr->LandscapeSoftPtr.Get() : nullptr; + } + break; + } + case EHoudiniOutputType::Curve: + ObjectToBake = OutputObject->OutputComponent; + break; + case EHoudiniOutputType::Mesh: + ObjectToBake = OutputObject->OutputObject; + break; + case EHoudiniOutputType::Instancer: + case EHoudiniOutputType::Skeletal: + case EHoudiniOutputType::Invalid: + default: + SetErrorMessage(FString::Printf( + TEXT("BakeOutputObjectAt: unsupported output type (%d) for baking to CB."), OutputType)); + return false; + } + + if (!IsValid(ObjectToBake)) + { + SetErrorMessage(FString::Printf(TEXT("BakeOutputObjectAt: Could not find a valid object to bake to CB."))); + return false; + } + + // Find the corresponding HGPO in the output + FHoudiniGeoPartObject HoudiniGeoPartObject; + for (const auto& HGPO : Output->GetHoudiniGeoPartObjects()) + { + if (!Identifier.Matches(HGPO)) + continue; + + HoudiniGeoPartObject = HGPO; + break; + } + + if (!HoudiniGeoPartObject.IsValid()) + { + SetErrorMessage(TEXT("BakeOutputObjectAt: Could not find a valid HGPO for the output object. Please recook.")); + return false; + } + + // Determine the HoudiniAssetName + FString HoudiniAssetName; + if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) + { + // If the HAC has a valid owner, use the owner's name + // TODO: Should this be more specific, such as checking for a HoudiniAssetActor? + HoudiniAssetName = HAC->GetOwner()->GetName(); + } + else if (HAC->GetHoudiniAsset()) + { + // Otherwise, if the HAC has a valid HoudiniAsset, use its name + HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); + } + else + { + // Fallback to the HAC's name + HoudiniAssetName = HAC->GetName(); + } + + TArray AllOutputs; + HAC->GetOutputs(AllOutputs); + + FHoudiniOutputDetails::OnBakeOutputObject( + InBakeName.IsNone() ? OutputObject->BakeName : InBakeName.ToString(), + ObjectToBake, + Identifier, + *OutputObject, + HoudiniGeoPartObject, + HAC, + HoudiniAssetName, + HAC->BakeFolder.Path, + HAC->TemporaryCookFolder.Path, + OutputType, + InLandscapeBakeType, + AllOutputs); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::HasAnyCurrentProxyOutput_Implementation() const +{ + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return false; + + return HAC->HasAnyCurrentProxyOutput(); +} + +bool +UHoudiniPublicAPIAssetWrapper::HasAnyCurrentProxyOutputAt_Implementation(const int32 InIndex) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + return Output->HasAnyCurrentProxy(); +} + +bool +UHoudiniPublicAPIAssetWrapper::IsOutputCurrentProxyAt_Implementation(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const +{ + UHoudiniOutput* Output = nullptr; + if (!GetValidOutputAtWithError(InIndex, Output)) + return false; + + return Output->IsProxyCurrent(InIdentifier.GetIdentifier()); +} + +EHoudiniProxyRefineRequestResult +UHoudiniPublicAPIAssetWrapper::RefineAllCurrentProxyOutputs_Implementation(const bool bInSilent) +{ + AHoudiniAssetActor* AssetActor = nullptr; + if (!GetValidHoudiniAssetActorWithError(AssetActor)) + return EHoudiniProxyRefineRequestResult::Invalid; + + return FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes({ AssetActor }, bInSilent); +} + +bool +UHoudiniPublicAPIAssetWrapper::HasPDGAssetLink_Implementation() const +{ + return IsValid(GetHoudiniPDGAssetLink()); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGTOPNetworkPaths_Implementation(TArray& OutTOPNetworkPaths) const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + const uint32 NumNetworks = AssetLink->AllTOPNetworks.Num(); + OutTOPNetworkPaths.Empty(NumNetworks); + for (UTOPNetwork const* const TOPNet : AssetLink->AllTOPNetworks) + { + OutTOPNetworkPaths.Add(TOPNet->NodePath); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGTOPNodePaths_Implementation(const FString& InNetworkRelativePath, TArray& OutTOPNodePaths) const +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* TOPNet = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) + return false; + + const uint32 NumNodes = TOPNet->AllTOPNodes.Num(); + OutTOPNodePaths.Empty(NumNodes); + for (UTOPNode const* const TOPNode : TOPNet->AllTOPNodes) + { + OutTOPNodePaths.Add(TOPNode->NodePath); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGDirtyAllNetworks_Implementation() +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + for (UTOPNetwork* const TOPNetwork : AssetLink->AllTOPNetworks) + { + if (!IsValid(TOPNetwork)) + continue; + + FHoudiniPDGManager::DirtyAll(TOPNetwork); + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGDirtyNetwork_Implementation(const FString& InNetworkRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* TOPNet = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) + return false; + + FHoudiniPDGManager::DirtyAll(TOPNet); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGDirtyNode_Implementation(const FString& InNetworkRelativePath, const FString& InNodeRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + int32 NodeIndex = INDEX_NONE; + UTOPNode* TOPNode = nullptr; + if (!GetValidTOPNodeByPathWithError(InNetworkRelativePath, InNodeRelativePath, NetworkIndex, NodeIndex, TOPNode)) + return false; + + FHoudiniPDGManager::DirtyTOPNode(TOPNode); + + return true; +} + +// bool +// UHoudiniPublicAPIAssetWrapper::PDGCookOutputsForAllNetworks_Implementation() +// { +// UHoudiniPDGAssetLink* const AssetLink = GetHoudiniPDGAssetLink(); +// if (!IsValid(AssetLink)) +// return false; +// +// for (UTOPNetwork* const TOPNetwork : AssetLink->AllTOPNetworks) +// { +// if (!IsValid(TOPNetwork)) +// continue; +// +// FHoudiniPDGManager::CookOutput(TOPNetwork); +// } +// +// return true; +// } + +bool +UHoudiniPublicAPIAssetWrapper::PDGCookOutputsForNetwork_Implementation(const FString& InNetworkRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* TOPNet = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, TOPNet)) + return false; + + FHoudiniPDGManager::CookOutput(TOPNet); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGCookNode_Implementation(const FString& InNetworkRelativePath, const FString& InNodeRelativePath) +{ + int32 NetworkIndex = INDEX_NONE; + int32 NodeIndex = INDEX_NONE; + UTOPNode* TOPNode = nullptr; + if (!GetValidTOPNodeByPathWithError(InNetworkRelativePath, InNodeRelativePath, NetworkIndex, NodeIndex, TOPNode)) + return false; + + FHoudiniPDGManager::CookTOPNode(TOPNode); + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGBakeAllOutputs_Implementation() +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + return PDGBakeAllOutputsWithSettings( + AssetLink->HoudiniEngineBakeOption, + AssetLink->PDGBakeSelectionOption, + AssetLink->PDGBakePackageReplaceMode, + AssetLink->bRecenterBakedActors); +} + +bool +UHoudiniPublicAPIAssetWrapper::PDGBakeAllOutputsWithSettings_Implementation( + const EHoudiniEngineBakeOption InBakeOption, + const EPDGBakeSelectionOption InBakeSelection, + const EPDGBakePackageReplaceModeOption InBakeReplacementMode, + const bool bInRecenterBakedActors) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + bool bBakeSuccess = false; + switch (InBakeOption) + { + case EHoudiniEngineBakeOption::ToActor: + bBakeSuccess = FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors( + AssetLink, + InBakeSelection, + InBakeReplacementMode, + bInRecenterBakedActors); + break; + case EHoudiniEngineBakeOption::ToBlueprint: + bBakeSuccess = FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints( + AssetLink, + InBakeSelection, + InBakeReplacementMode, + bInRecenterBakedActors); + break; + default: + bBakeSuccess = false; + break; + } + + return bBakeSuccess; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGAutoBakeEnabled_Implementation(const bool bInAutoBakeEnabled) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->bBakeAfterAllWorkResultObjectsLoaded = bInAutoBakeEnabled; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::IsPDGAutoBakeEnabled_Implementation() const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + return AssetLink->bBakeAfterAllWorkResultObjectsLoaded; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGBakeMethod_Implementation(const EHoudiniEngineBakeOption InBakeMethod) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->HoudiniEngineBakeOption = InBakeMethod; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGBakeMethod_Implementation(EHoudiniEngineBakeOption& OutBakeMethod) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + OutBakeMethod = AssetLink->HoudiniEngineBakeOption; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGBakeSelection_Implementation(const EPDGBakeSelectionOption InBakeSelection) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->PDGBakeSelectionOption = InBakeSelection; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGBakeSelection_Implementation(EPDGBakeSelectionOption& OutBakeSelection) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + OutBakeSelection = AssetLink->PDGBakeSelectionOption; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGRecenterBakedActors_Implementation(const bool bInRecenterBakedActors) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->bRecenterBakedActors = bInRecenterBakedActors; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGRecenterBakedActors_Implementation() const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + return AssetLink->bRecenterBakedActors; +} + +bool +UHoudiniPublicAPIAssetWrapper::SetPDGBakingReplacementMode_Implementation(const EPDGBakePackageReplaceModeOption InBakingReplacementMode) +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + AssetLink->PDGBakePackageReplaceMode = InBakingReplacementMode; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetPDGBakingReplacementMode_Implementation(EPDGBakePackageReplaceModeOption& OutBakingReplacementMode) const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + OutBakingReplacementMode = AssetLink->PDGBakePackageReplaceMode; + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::BindToPDGAssetLink() +{ + if (bAssetLinkSetupAttemptComplete) + return true; + + UHoudiniPDGAssetLink* const PDGAssetLink = GetHoudiniPDGAssetLink(); + if (IsValid(PDGAssetLink)) + { + OnPDGPostTOPNetworkCookDelegateHandle = PDGAssetLink->GetOnPostTOPNetworkCookDelegate().AddUFunction(this, TEXT("HandleOnHoudiniPDGAssetLinkTOPNetPostCook")); + OnPDGPostBakeDelegateHandle = PDGAssetLink->GetOnPostBakeDelegate().AddUFunction(this, TEXT("HandleOnHoudiniPDGAssetLinkPostBake")); + bAssetLinkSetupAttemptComplete = true; + + return true; + } + + return false; +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentStateChange(UHoudiniAssetComponent* InHAC, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniAssetComponentStateChange: unexpected InHAC: %s, expected the wrapper's HAC."), + IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); + return; + } + + if (InToState == EHoudiniAssetState::PreInstantiation) + { + if (OnPreInstantiationDelegate.IsBound()) + OnPreInstantiationDelegate.Broadcast(this); + } + + if (InFromState == EHoudiniAssetState::Instantiating && InToState == EHoudiniAssetState::PreCook) + { + // PDG link setup / bindings: we have to wait until post instantiation to check if we have an asset link and + // configure bindings + if (!bAssetLinkSetupAttemptComplete) + { + BindToPDGAssetLink(); + bAssetLinkSetupAttemptComplete = true; + } + + if (OnPostInstantiationDelegate.IsBound()) + OnPostInstantiationDelegate.Broadcast(this); + } + + if (InFromState == EHoudiniAssetState::PreProcess) + { + if (OnPreProcessStateExitedDelegate.IsBound()) + OnPreProcessStateExitedDelegate.Broadcast(this); + } + + if (InFromState == EHoudiniAssetState::Processing && InToState == EHoudiniAssetState::None) + { + if (OnPostProcessingDelegate.IsBound()) + OnPostProcessingDelegate.Broadcast(this); + } +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentPostCook(UHoudiniAssetComponent* InHAC, const bool bInCookSuccess) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniAssetComponentPostCook: unexpected InHAC: %s, expected the wrapper's HAC."), + IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); + return; + } + + if (OnPostCookDelegate.IsBound()) + OnPostCookDelegate.Broadcast(this, bInCookSuccess); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniAssetComponentPostBake(UHoudiniAssetComponent* InHAC, const bool bInBakeSuccess) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniAssetComponentPostBake: unexpected InHAC: %s, expected the wrapper's HAC."), + IsValid(InHAC) ? *InHAC->GetName() : TEXT(""))); + return; + } + + if (OnPostBakeDelegate.IsBound()) + OnPostBakeDelegate.Broadcast(this, bInBakeSuccess); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniPDGAssetLinkTOPNetPostCook(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InTOPNet, const bool bInAnyWorkItemsFailed) +{ + if (!IsValid(InPDGAssetLink)) + return; + + if (InPDGAssetLink != GetHoudiniPDGAssetLink()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniPDGAssetLinkTOPNetPostCook: unexpected InPDGAssetLink: %s, expected the wrapper's PDGAssetLink."), + IsValid(InPDGAssetLink) ? *InPDGAssetLink->GetName() : TEXT(""))); + return; + } + + if (OnPostPDGTOPNetworkCookDelegate.IsBound()) + OnPostPDGTOPNetworkCookDelegate.Broadcast(this, !bInAnyWorkItemsFailed); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniPDGAssetLinkPostBake(UHoudiniPDGAssetLink* InPDGAssetLink, const bool bInBakeSuccess) +{ + if (!IsValid(InPDGAssetLink)) + return; + + if (InPDGAssetLink != GetHoudiniPDGAssetLink()) + { + SetErrorMessage(FString::Printf( + TEXT("HandleOnHoudiniPDGAssetLinkPostBake: unexpected InPDGAssetLink: %s, expected the wrapper's PDGAssetLink."), + IsValid(InPDGAssetLink) ? *InPDGAssetLink->GetName() : TEXT(""))); + return; + } + + if (OnPostPDGBakeDelegate.IsBound()) + OnPostPDGBakeDelegate.Broadcast(this, bInBakeSuccess); +} + +void +UHoudiniPublicAPIAssetWrapper::HandleOnHoudiniProxyMeshesRefinedGlobal(UHoudiniAssetComponent* InHAC, const EHoudiniProxyRefineResult InResult) +{ + if (!IsValid(InHAC)) + return; + + if (InHAC != GetHoudiniAssetComponent()) + return; + + if (OnProxyMeshesRefinedDelegate.IsBound()) + OnProxyMeshesRefinedDelegate.Broadcast(this, InResult); +} + +UHoudiniParameter* +UHoudiniPublicAPIAssetWrapper::FindValidParameterByName(const FName& InParameterTupleName) const +{ + AActor* const Actor = GetHoudiniAssetActor(); + const FString ActorName = IsValid(Actor) ? Actor->GetName() : FString(); + + UHoudiniAssetComponent* const HAC = GetHoudiniAssetComponent(); + if (!IsValid(HAC)) + { + SetErrorMessage(FString::Printf(TEXT("Could not find HAC on Actor '%s'"), *ActorName)); + return nullptr; + } + + UHoudiniParameter* const Param = HAC->FindParameterByName(InParameterTupleName.ToString()); + if (!IsValid(Param)) + { + SetErrorMessage(FString::Printf( + TEXT("Could not find valid parameter tuple '%s' on '%s'."), + *InParameterTupleName.ToString(), *ActorName)); + return nullptr; + } + + return Param; +} + +bool +UHoudiniPublicAPIAssetWrapper::FindRampPointData( + UHoudiniParameter* const InParam, + const int32 InIndex, + TArray>& OutPointData) const +{ + if (!IsValid(InParam)) + return false; + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!InParam->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !InParam->IsAutoUpdate(); + + const bool bFetchAllPoints = (InIndex == INDEX_NONE); + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + // Handle all the cases where the underlying parameter value is an int or bool + const EHoudiniParameterType ParamType = InParam->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(InParam); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *InParam->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(InParam); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *InParam->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf( + TEXT("Parameter '%s' is not a float or color ramp parameter."), *(InParam->GetName()))); + return false; + } + + if (bUseCachedPoints) + { + // When using the cached points we only have to resize the array + if (FloatRampParam) + { + if (!bFetchAllPoints && !FloatRampParam->CachedPoints.IsValidIndex(InIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, FloatRampParam->CachedPoints.Num() - 1)); + return false; + } + + if (bFetchAllPoints) + { + // Get all points + OutPointData.Reserve(FloatRampParam->CachedPoints.Num()); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + for (UHoudiniParameterRampFloatPoint* const RampPoint : FloatRampParam->CachedPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(RampPoint, bIsPointData)); + } + } + else + { + OutPointData.Reserve(1); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + + const bool bIsPointData = true; + OutPointData.Add(TPair(FloatRampParam->CachedPoints[InIndex], bIsPointData)); + } + + return true; + } + else + { + if (!bFetchAllPoints && !ColorRampParam->CachedPoints.IsValidIndex(InIndex)) + { + SetErrorMessage(FString::Printf( + TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, ColorRampParam->CachedPoints.Num() - 1)); + return false; + } + + if (bFetchAllPoints) + { + // Get all points + OutPointData.Reserve(ColorRampParam->CachedPoints.Num()); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + for (UHoudiniParameterRampColorPoint* const RampPoint : ColorRampParam->CachedPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(RampPoint, bIsPointData)); + } + } + else + { + OutPointData.Reserve(1); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + + const bool bIsPointData = true; + OutPointData.Add(TPair(ColorRampParam->CachedPoints[InIndex], bIsPointData)); + } + + return true; + } + } + else + { + TSet InstanceIndexesPendingDelete; + int32 NumInsertOps = 0; + TArray& ModificationEvents = FloatRampParam ? FloatRampParam->ModificationEvents : ColorRampParam->ModificationEvents; + for (UHoudiniParameterRampModificationEvent const* const Event : ModificationEvents) + { + if (!IsValid(Event)) + continue; + + if (Event->IsInsertEvent()) + NumInsertOps++; + else if (Event->IsDeleteEvent()) + InstanceIndexesPendingDelete.Add(Event->DeleteInstanceIndex); + } + + const int32 PointsArraySize = FloatRampParam ? FloatRampParam->Points.Num() : ColorRampParam->Points.Num(); + const int32 NumActivePointsInArray = PointsArraySize - InstanceIndexesPendingDelete.Num(); + const int32 TotalNumPoints = NumActivePointsInArray + NumInsertOps; + + // Reserve the expected amount of space needed in the array and reset / destruct existing items so we can + // add from index 0 + if (bFetchAllPoints) + OutPointData.Reserve(TotalNumPoints); + else + OutPointData.Reserve(1); + const bool bAllowShrinking = false; + OutPointData.SetNum(0, bAllowShrinking); + + if (bFetchAllPoints || InIndex < NumActivePointsInArray) + { + // Getting all points or point is in the points array + if (FloatRampParam) + { + const int32 ArraySize = FloatRampParam->Points.Num(); + int32 PointIndex = 0; + for (int32 Index = 0; Index < ArraySize && (bFetchAllPoints || PointIndex <= InIndex); ++Index) + { + UHoudiniParameterRampFloatPoint* const PointData = FloatRampParam->Points[Index]; + const bool bIsDeletedPoint = (PointData && InstanceIndexesPendingDelete.Contains(PointData->InstanceIndex)); + if (!bIsDeletedPoint) + { + if (PointIndex == InIndex || bFetchAllPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(PointData, bIsPointData)); + } + + // If we are fetching only the point at InIndex, then we are done here + if (PointIndex == InIndex) + return true; + + PointIndex++; + } + } + } + else + { + const int32 ArraySize = ColorRampParam->Points.Num(); + int32 PointIndex = 0; + for (int32 Index = 0; Index < ArraySize && (bFetchAllPoints || PointIndex <= InIndex); ++Index) + { + UHoudiniParameterRampColorPoint* const PointData = ColorRampParam->Points[Index]; + const bool bIsDeletedPoint = (PointData && InstanceIndexesPendingDelete.Contains(PointData->InstanceIndex)); + if (!bIsDeletedPoint) + { + if (PointIndex == InIndex || bFetchAllPoints) + { + const bool bIsPointData = true; + OutPointData.Add(TPair(PointData, bIsPointData)); + } + + // If we are fetching only the point at InIndex, then we are done here + if (PointIndex == InIndex) + return true; + + PointIndex++; + } + } + } + } + + if (bFetchAllPoints || InIndex < TotalNumPoints) + { + // Point is an insert operation + const int32 NumEvents = ModificationEvents.Num(); + int32 PointIndex = NumActivePointsInArray; + for (int32 Index = 0; Index < NumEvents && (bFetchAllPoints || PointIndex <= InIndex); ++Index) + { + UHoudiniParameterRampModificationEvent* const Event = ModificationEvents[Index]; + if (!IsValid(Event)) + continue; + + if (!Event->IsInsertEvent()) + continue; + + if (PointIndex == InIndex || bFetchAllPoints) + { + const bool bIsPointData = false; + OutPointData.Add(TPair(Event, bIsPointData)); + } + + if (PointIndex == InIndex) + return true; + + PointIndex++; + } + } + else + { + // Point is out of range + SetErrorMessage(FString::Printf( + TEXT("Ramp point index %d is out of range [0, %d]."), InIndex, TotalNumPoints)); + return false; + } + + if (bFetchAllPoints) + { + if (TotalNumPoints != OutPointData.Num()) + { + SetErrorMessage(FString::Printf( + TEXT("Failed to fetch all ramp points. Got %d, expected %d."), OutPointData.Num(), TotalNumPoints)); + return false; + } + + return true; + } + } + + // If we reach this point we didn't find the point + SetErrorMessage(FString::Printf(TEXT("Could not find valid ramp point at index %d."), InIndex)); + return false; +} + +bool UHoudiniPublicAPIAssetWrapper::SetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPosition, + const float InFloatValue, + const FLinearColor& InColorValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation, + const bool bInMarkChanged) +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + return false; + } + + const EHoudiniRampInterpolationType NewInterpolation = UHoudiniPublicAPI::ToHoudiniRampInterpolationType( + InInterpolation); + + // If the parameter is not set to auto update, or if cooking is paused, we have to set the cached points and + // not the main points. + // const bool bCookingEnabled = !FHoudiniEngineCommands::IsAssetCookingPaused(); + // const bool bUseCachedPoints = (!Param->IsAutoUpdate() || !bCookingEnabled); + const bool bUseCachedPoints = !Param->IsAutoUpdate(); + + // Get the point at InPointIndex's data + TArray> RampPointData; + if (!FindRampPointData(Param, InPointIndex, RampPointData) || RampPointData.Num() < 1) + return false; + + UObject* const PointData = RampPointData[0].Key; + const bool bIsPointData = RampPointData[0].Value; + if (!IsValid(PointData)) + return false; + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* FloatPointData = nullptr; + UHoudiniParameterRampColorPoint* ColorPointData = nullptr; + + if (FloatRampParam) + { + FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + else + { + ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + + if (bUseCachedPoints) + { + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (FloatPointData) + { + if (FloatPointData->Position != InPosition) + { + FloatPointData->Position = InPosition; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Value != InFloatValue) + { + FloatPointData->Value = InFloatValue; + FloatRampParam->bCaching = true; + } + + if (FloatPointData->Interpolation != NewInterpolation) + { + FloatPointData->Interpolation = NewInterpolation; + FloatRampParam->bCaching = true; + } + } + else if (ColorPointData) + { + if (ColorPointData->Position != InPosition) + { + ColorPointData->Position = InPosition; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Value != InColorValue) + { + ColorPointData->Value = InColorValue; + ColorRampParam->bCaching = true; + } + + if (ColorPointData->Interpolation != NewInterpolation) + { + ColorPointData->Interpolation = NewInterpolation; + ColorRampParam->bCaching = true; + } + } + + // Update the ramp's widget if it is currently visible/selected + const bool bForceFullUpdate = true; + FHoudiniEngineUtils::UpdateEditorProperties(Param, bForceFullUpdate); + } + else + { + // When setting the main points, we set the values using the setters on the point data but still manually + // mark the position/value/interpolation parent parameters as changed + if (FloatPointData) + { + if (FloatPointData->Position != InPosition && FloatPointData->PositionParentParm) + { + FloatPointData->SetPosition(InPosition); + if (bInMarkChanged) + FloatPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Value != InFloatValue && FloatPointData->ValueParentParm) + { + FloatPointData->SetValue(InFloatValue); + if (bInMarkChanged) + FloatPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (FloatPointData->Interpolation != NewInterpolation && FloatPointData->InterpolationParentParm) + { + FloatPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + FloatPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + else if (ColorPointData) + { + if (ColorPointData->Position != InPosition && ColorPointData->PositionParentParm) + { + ColorPointData->SetPosition(InPosition); + if (bInMarkChanged) + ColorPointData->PositionParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Value != InColorValue && ColorPointData->ValueParentParm) + { + ColorPointData->SetValue(InColorValue); + if (bInMarkChanged) + ColorPointData->ValueParentParm->MarkChanged(bInMarkChanged); + } + + if (ColorPointData->Interpolation != NewInterpolation && ColorPointData->InterpolationParentParm) + { + ColorPointData->SetInterpolation(NewInterpolation); + if (bInMarkChanged) + ColorPointData->InterpolationParentParm->MarkChanged(bInMarkChanged); + } + } + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + Event->InsertPosition = InPosition; + if (FloatRampParam) + { + Event->InsertFloat = InFloatValue; + } + else if (ColorRampParam) + { + Event->InsertColor = InColorValue; + } + Event->InsertInterpolation = NewInterpolation; + } + + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPosition, + float& OutFloatValue, + FLinearColor& OutColorValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolation) const +{ + UHoudiniParameter* Param = FindValidParameterByName(InParameterTupleName); + if (!Param) + return false; + + UHoudiniParameterRampFloat* FloatRampParam = nullptr; + UHoudiniParameterRampColor* ColorRampParam = nullptr; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType == EHoudiniParameterType::FloatRamp) + { + FloatRampParam = Cast(Param); + if (!IsValid(FloatRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else if (ParamType == EHoudiniParameterType::ColorRamp) + { + ColorRampParam = Cast(Param); + if (!IsValid(ColorRampParam)) + { + SetErrorMessage(FString::Printf( + TEXT("Unexpected parameter class (%s) vs type (%d)"), *Param->GetClass()->GetName(), ParamType)); + return false; + } + } + else + { + SetErrorMessage(FString::Printf(TEXT("Parameter '%s' is not a float ramp or color ramp parameter."), *(Param->GetName()))); + return false; + } + + TArray> RampPointData; + if (!FindRampPointData(Param, InPointIndex, RampPointData) || RampPointData.Num() < 1) + return false; + + UObject* const PointData = RampPointData[0].Key; + const bool bIsPointData = RampPointData[0].Value; + if (!IsValid(PointData)) + return false; + + if (bIsPointData) + { + UHoudiniParameterRampFloatPoint* FloatPointData = nullptr; + UHoudiniParameterRampColorPoint* ColorPointData = nullptr; + + if (FloatRampParam) + { + FloatPointData = Cast(PointData); + if (!IsValid(FloatPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampFloatPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + else + { + ColorPointData = Cast(PointData); + if (!IsValid(ColorPointData)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampColorPoint instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + } + + // When setting the cached points, we set the values directly instead of using the setters, but we set + // the bCaching flag on the parameter and mark the position/value/interpolation parent parameters as changed + if (FloatPointData) + { + OutPosition = FloatPointData->Position; + OutFloatValue = FloatPointData->Value; + OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + FloatPointData->Interpolation); + } + else if (ColorPointData) + { + OutPosition = ColorPointData->Position; + OutColorValue = ColorPointData->Value; + OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType( + ColorPointData->Interpolation); + } + } + else + { + UHoudiniParameterRampModificationEvent* const Event = Cast(PointData); + if (!IsValid(Event)) + { + SetErrorMessage(FString::Printf( + TEXT("Expected UHoudiniParameterRampModificationEvent instance, but received incompatible class '%s'."), + *(PointData->GetClass()->GetName()))); + return false; + } + + OutPosition = Event->InsertPosition; + if (FloatRampParam) + { + OutFloatValue = Event->InsertFloat; + } + else if (ColorRampParam) + { + OutColorValue = Event->InsertColor; + } + OutInterpolation = UHoudiniPublicAPI::ToHoudiniPublicAPIRampInterpolationType(Event->InsertInterpolation); + } + + return true; +} + +UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) +{ + if (InNodeInputIndex < 0) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->GetInputIndex() == InNodeInputIndex) + return Input; + } + + return nullptr; +} + +const UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) const +{ + if (InNodeInputIndex < 0) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->GetInputIndex() == InNodeInputIndex) + return Input; + } + + return nullptr; +} + +UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) +{ + if (InInputParameterName == NAME_None) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const FString InputParameterName = InInputParameterName.ToString(); + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->IsObjectPathParameter() && Input->GetName() == InputParameterName) + return Input; + } + + return nullptr; +} + +const UHoudiniInput* +UHoudiniPublicAPIAssetWrapper::FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) const +{ + if (InInputParameterName == NAME_None) + return nullptr; + + UHoudiniAssetComponent* HAC = nullptr; + if (!GetValidHoudiniAssetComponentWithError(HAC)) + return nullptr; + + const FString InputParameterName = InInputParameterName.ToString(); + const int32 NumInputs = HAC->GetNumInputs(); + for (int32 Index = 0; Index < NumInputs; ++Index) + { + UHoudiniInput const* const Input = HAC->GetInputAt(Index); + if (!IsValid(Input)) + continue; + if (Input->IsObjectPathParameter() && Input->GetName() == InputParameterName) + return Input; + } + + return nullptr; +} + +bool +UHoudiniPublicAPIAssetWrapper::CreateAndPopulateAPIInput(const UHoudiniInput* InHoudiniInput, UHoudiniPublicAPIInput*& OutAPIInput) +{ + if (!IsValid(InHoudiniInput)) + return false; + + TSubclassOf APIInputClass; + const EHoudiniInputType InputType = InHoudiniInput->GetInputType(); + switch (InputType) + { + case EHoudiniInputType::Geometry: + APIInputClass = UHoudiniPublicAPIGeoInput::StaticClass(); + break; + case EHoudiniInputType::Curve: + APIInputClass = UHoudiniPublicAPICurveInput::StaticClass(); + break; + case EHoudiniInputType::Asset: + APIInputClass = UHoudiniPublicAPIAssetInput::StaticClass(); + break; + case EHoudiniInputType::World: + APIInputClass = UHoudiniPublicAPIWorldInput::StaticClass(); + break; + case EHoudiniInputType::Landscape: + APIInputClass = UHoudiniPublicAPILandscapeInput::StaticClass(); + break; + case EHoudiniInputType::Skeletal: + // Not yet implemented + SetErrorMessage(FString::Printf(TEXT("GetInputAtIndex: Input type not yet implemented %d"), InputType)); + return false; + case EHoudiniInputType::Invalid: + SetErrorMessage(FString::Printf(TEXT("GetInputAtIndex: Invalid input type %d"), InputType)); + return false; + } + + UHoudiniPublicAPIInput* APIInput = CreateEmptyInput(APIInputClass); + if (!IsValid(APIInput)) + { + return false; + } + + const bool bSuccessfullyCopied = APIInput->PopulateFromHoudiniInput(InHoudiniInput); + OutAPIInput = APIInput; + return bSuccessfullyCopied; +} + +bool +UHoudiniPublicAPIAssetWrapper::PopulateHoudiniInput(const UHoudiniPublicAPIInput* InAPIInput, UHoudiniInput* InHoudiniInput) const +{ + if (!IsValid(InHoudiniInput)) + return false; + + return InAPIInput->UpdateHoudiniInput(InHoudiniInput); +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidTOPNetworkByPathWithError(const FString& InNetworkRelativePath, int32& OutNetworkIndex, UTOPNetwork*& OutNetwork) const +{ + UHoudiniPDGAssetLink* AssetLink = nullptr; + if (!GetValidHoudiniPDGAssetLinkWithError(AssetLink)) + return false; + + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* const Network = UHoudiniPDGAssetLink::GetTOPNetworkByNodePath( + InNetworkRelativePath, AssetLink->AllTOPNetworks, NetworkIndex); + if (!IsValid(Network)) + { + SetErrorMessage(FString::Printf( + TEXT("Could not find valid TOP network at relative path '%s'."), *InNetworkRelativePath)); + return false; + } + + OutNetworkIndex = NetworkIndex; + OutNetwork = Network; + return true; +} + +bool +UHoudiniPublicAPIAssetWrapper::GetValidTOPNodeByPathWithError( + const FString& InNetworkRelativePath, + const FString& InNodeRelativePath, + int32& OutNetworkIndex, + int32& OutNodeIndex, + UTOPNode*& OutNode) const +{ + int32 NetworkIndex = INDEX_NONE; + UTOPNetwork* Network = nullptr; + if (!GetValidTOPNetworkByPathWithError(InNetworkRelativePath, NetworkIndex, Network)) + return false; + + int32 NodeIndex = INDEX_NONE; + UTOPNode* const Node = UHoudiniPDGAssetLink::GetTOPNodeByNodePath( + InNodeRelativePath, Network->AllTOPNodes, NodeIndex); + if (!IsValid(Node)) + { + SetErrorMessage(FString::Printf( + TEXT("Could not find valid TOP node at relative path '%s'."), *InNodeRelativePath)); + return false; + } + + OutNetworkIndex = NetworkIndex; + OutNodeIndex = NodeIndex; + OutNode = Node; + return true; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp index 97b05f935..1f3802e71 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIBlueprintLib.cpp @@ -1,40 +1,40 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPublicAPIBlueprintLib.h" -#include "HoudiniPublicAPI.h" - - -UHoudiniPublicAPIBlueprintLib::UHoudiniPublicAPIBlueprintLib(class FObjectInitializer const & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniPublicAPI* UHoudiniPublicAPIBlueprintLib::GetAPI() -{ - static UHoudiniPublicAPI* Obj = NewObject(GetTransientPackage(), NAME_None, RF_MarkAsRootSet); - return Obj; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPI.h" + + +UHoudiniPublicAPIBlueprintLib::UHoudiniPublicAPIBlueprintLib(class FObjectInitializer const & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniPublicAPI* UHoudiniPublicAPIBlueprintLib::GetAPI() +{ + static UHoudiniPublicAPI* Obj = NewObject(GetTransientPackage(), NAME_None, RF_MarkAsRootSet); + return Obj; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp index aca5bafd9..f76de81a5 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp @@ -1,856 +1,865 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPublicAPIInputTypes.h" - -#include "HoudiniPublicAPIAssetWrapper.h" - - -UHoudiniPublicAPIInput::UHoudiniPublicAPIInput() -{ - bKeepWorldTransform = false; - bImportAsReference = false; -} - -bool -UHoudiniPublicAPIInput::SetInputObjects_Implementation(const TArray& InObjects) -{ - bool bHasFailures = false; - InputObjects.Empty(InObjects.Num()); - for (UObject* const Object : InObjects) - { - if (!IsValid(Object)) - { - SetErrorMessage(FString::Printf(TEXT("An input object is null or invalid."))); - bHasFailures = true; - continue; - } - else if (!IsAcceptableObjectForInput(Object)) - { - SetErrorMessage(FString::Printf( - TEXT("Object '%s' is not of an acceptable type for inputs of class %s."), - *(Object->GetName()), *(GetClass()->GetName()))); - bHasFailures = true; - continue; - } - - InputObjects.Add(Object); - } - - return !bHasFailures; -} - -bool -UHoudiniPublicAPIInput::GetInputObjects_Implementation(TArray& OutObjects) -{ - OutObjects = InputObjects; - - return true; -} - -bool -UHoudiniPublicAPIInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) -{ - const EHoudiniInputType InputType = GetInputType(); - if (!IsValid(InInput)) - { - SetErrorMessage(TEXT("InInput is invalid.")); - return false; - } - - if (InInput->GetInputType() != InputType) - { - SetErrorMessage(FString::Printf( - TEXT("Incompatible input types %d vs %d"), InInput->GetInputType(), InputType)); - return false; - } - - bKeepWorldTransform = InInput->GetKeepWorldTransform(); - bImportAsReference = InInput->GetImportAsReference(); - - const TArray* SrcInputObjectsPtr = InInput->GetHoudiniInputObjectArray(InputType); - if (SrcInputObjectsPtr && SrcInputObjectsPtr->Num() > 0) - { - InputObjects.Empty(SrcInputObjectsPtr->Num()); - for (UHoudiniInputObject const* const SrcInputObject : *SrcInputObjectsPtr) - { - if (!IsValid(SrcInputObject)) - continue; - - UObject* NewInputObject = ConvertInternalInputObject(SrcInputObject->GetObject()); - if (NewInputObject && NewInputObject->IsPendingKill()) - { - SetErrorMessage(FString::Printf( - TEXT("One of the input objects is non-null but pending kill/invalid."))); - return false; - } - - InputObjects.Add(NewInputObject); - - CopyHoudiniInputObjectProperties(SrcInputObject, NewInputObject); - } - } - - return true; -} - -bool -UHoudiniPublicAPIInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const -{ - if (!IsValid(InInput)) - { - SetErrorMessage(TEXT("InInput is invalid.")); - return false; - } - - // Set / change the input type - const EHoudiniInputType InputType = GetInputType(); - bool bBlueprintStructureModified = false; - InInput->SetInputType(InputType, bBlueprintStructureModified); - - // Set any general settings - bool bAnyChanges = false; - if (InInput->GetKeepWorldTransform() != bKeepWorldTransform) - { - InInput->SetKeepWorldTransform(bKeepWorldTransform); - bAnyChanges = true; - } - if (InInput->GetImportAsReference() != bImportAsReference) - { - InInput->SetImportAsReference(bImportAsReference); - bAnyChanges = true; - } - - // Copy / set the input objects on the Houdini Input - const int32 NumInputObjects = InputObjects.Num(); - InInput->SetInputObjectsNumber(InputType, NumInputObjects); - for (int32 Index = 0; Index < NumInputObjects; ++Index) - { - UObject* const InputObject = InputObjects[Index]; - - if (!IsValid(InputObject)) - { - InInput->SetInputObjectAt(Index, nullptr); - } - else - { - ConvertAPIInputObjectAndAssignToInput(InputObject, InInput, Index); - UHoudiniInputObject *DstInputObject = InInput->GetHoudiniInputObjectAt(Index); - if (DstInputObject) - CopyPropertiesToHoudiniInputObject(InputObject, DstInputObject); - } - } - - if (bAnyChanges) - { - InInput->MarkChanged(true); - } - - return true; -} - -bool -UHoudiniPublicAPIInput::CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) -{ - if (!IsValid(InInputObject) || !IsValid(InObject)) - return false; - - return true; -} - -bool -UHoudiniPublicAPIInput::CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const -{ - if (!IsValid(InObject) || !IsValid(InInputObject)) - return false; - - // const EHoudiniInputObjectType InputObjectType = InInputObject->Type; - - if (InInputObject->GetImportAsReference() != bImportAsReference) - { - InInputObject->SetImportAsReference(bImportAsReference); - InInputObject->MarkChanged(true); - } - - // switch (InputObjectType) - // { - // case EHoudiniInputObjectType::StaticMesh: - // case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: - // break; - // case EHoudiniInputObjectType::Object: - // case EHoudiniInputObjectType::SkeletalMesh: - // case EHoudiniInputObjectType::HoudiniSplineComponent: - // case EHoudiniInputObjectType::SceneComponent: - // case EHoudiniInputObjectType::StaticMeshComponent: - // case EHoudiniInputObjectType::InstancedStaticMeshComponent: - // case EHoudiniInputObjectType::SplineComponent: - // case EHoudiniInputObjectType::HoudiniAssetComponent: - // case EHoudiniInputObjectType::Actor: - // case EHoudiniInputObjectType::Landscape: - // case EHoudiniInputObjectType::Brush: - // case EHoudiniInputObjectType::CameraComponent: - // case EHoudiniInputObjectType::DataTable: - // case EHoudiniInputObjectType::HoudiniAssetActor: - // break; - // - // case EHoudiniInputObjectType::Invalid: - // return false; - // } - - return true; -} - -UObject* -UHoudiniPublicAPIInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const -{ - if (!IsValid(InHoudiniInput)) - return nullptr; - - UObject* const ObjectToSet = (InAPIInputObject && !InAPIInputObject->IsPendingKill()) ? InAPIInputObject : nullptr; - InHoudiniInput->SetInputObjectAt(InInputIndex, ObjectToSet); - - return ObjectToSet; -} - -UHoudiniPublicAPIGeoInput::UHoudiniPublicAPIGeoInput() -{ - bKeepWorldTransform = false; - bPackBeforeMerge = false; - bExportLODs = false; - bExportSockets = false; - bExportColliders = false; -} - -bool -UHoudiniPublicAPIGeoInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) -{ - if (!Super::PopulateFromHoudiniInput(InInput)) - return false; - - bPackBeforeMerge = InInput->GetPackBeforeMerge(); - bExportLODs = InInput->GetExportLODs(); - bExportSockets = InInput->GetExportSockets(); - bExportColliders = InInput->GetExportColliders(); - - return true; -} - -bool -UHoudiniPublicAPIGeoInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const -{ - if (!Super::UpdateHoudiniInput(InInput)) - return false; - - bool bAnyChanges = false; - if (InInput->GetPackBeforeMerge() != bPackBeforeMerge) - { - InInput->SetPackBeforeMerge(bPackBeforeMerge); - bAnyChanges = true; - } - if (InInput->GetExportLODs() != bExportLODs) - { - InInput->SetExportLODs(bExportLODs); - bAnyChanges = true; - } - if (InInput->GetExportSockets() != bExportSockets) - { - InInput->SetExportSockets(bExportSockets); - bAnyChanges = true; - } - if (InInput->GetExportColliders() != bExportColliders) - { - InInput->SetExportColliders(bExportColliders); - bAnyChanges = true; - } - - if (bAnyChanges) - { - InInput->MarkChanged(true); - } - - return true; -} - -bool -UHoudiniPublicAPIGeoInput::CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) -{ - if (!Super::CopyHoudiniInputObjectProperties(InInputObject, InObject)) - return false; - - if (!IsValid(InInputObject) || !IsValid(InObject)) - return false; - - // Copy the transform offset - SetObjectTransformOffset(InObject, InInputObject->Transform); - - return true; -} - - -bool -UHoudiniPublicAPIGeoInput::CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const -{ - if (!Super::CopyPropertiesToHoudiniInputObject(InObject, InInputObject)) - return false; - - if (!IsValid(InObject) || !IsValid(InInputObject)) - return false; - - // Copy the transform offset - FTransform Transform; - if (GetObjectTransformOffset(InObject, Transform)) - { - if (!InInputObject->Transform.Equals(Transform)) - { - InInputObject->Transform = Transform; - InInputObject->MarkChanged(true); - } - } - - return true; -} - -bool -UHoudiniPublicAPIGeoInput::SetObjectTransformOffset_Implementation(UObject* InObject, const FTransform& InTransform) -{ - // Ensure that InObject is valid and has already been added as input object - if (!IsValid(InObject)) - { - SetErrorMessage(TEXT("InObject is invalid.")); - return false; - } - - if (INDEX_NONE == InputObjects.Find(InObject)) - { - SetErrorMessage(FString::Printf( - TEXT("InObject '%s' is not currently set as input object on this input."), *(InObject->GetName()))); - return false; - } - - InputObjectTransformOffsets.Add(InObject, InTransform); - - return true; -} - -bool -UHoudiniPublicAPIGeoInput::GetObjectTransformOffset_Implementation(UObject* InObject, FTransform& OutTransform) const -{ - // Ensure that InObject is valid and has already been added as input object - if (!IsValid(InObject)) - { - SetErrorMessage(TEXT("InObject is invalid.")); - return false; - } - - if (INDEX_NONE == InputObjects.Find(InObject)) - { - SetErrorMessage(FString::Printf( - TEXT("InObject '%s' is not currently set as input object on this input."), *(InObject->GetName()))); - return false; - } - - FTransform const* const TransformPtr = InputObjectTransformOffsets.Find(InObject); - if (!TransformPtr) - { - SetErrorMessage(FString::Printf( - TEXT("InObject '%s' does not have a transform offset set."), *(InObject->GetName()))); - return false; - } - - OutTransform = *TransformPtr; - return true; -} - - -UHoudiniPublicAPICurveInputObject::UHoudiniPublicAPICurveInputObject() - : bClosed(false) - , bReversed(false) - , CurveType(EHoudiniPublicAPICurveType::Polygon) - , CurveMethod(EHoudiniPublicAPICurveMethod::CVs) -{ - -} - - -void -UHoudiniPublicAPICurveInputObject::PopulateFromHoudiniSplineComponent(UHoudiniSplineComponent const* const InSpline) -{ - if (!IsValid(InSpline)) - return; - - bClosed = InSpline->IsClosedCurve(); - bReversed = InSpline->IsReversed(); - CurveType = ToHoudiniPublicAPICurveType(InSpline->GetCurveType()); - CurveMethod = ToHoudiniPublicAPICurveMethod(InSpline->GetCurveMethod()); - CurvePoints = InSpline->CurvePoints; -} - -void -UHoudiniPublicAPICurveInputObject::CopyToHoudiniSplineComponent(UHoudiniSplineComponent* const InSpline) const -{ - if (!IsValid(InSpline)) - return; - - InSpline->SetClosedCurve(bClosed); - InSpline->SetReversed(bReversed); - InSpline->SetCurveType(ToHoudiniCurveType(CurveType)); - InSpline->SetCurveMethod(ToHoudiniCurveMethod(CurveMethod)); - InSpline->ResetCurvePoints(); - InSpline->ResetDisplayPoints(); - InSpline->CurvePoints = CurvePoints; -} - -EHoudiniCurveType -UHoudiniPublicAPICurveInputObject::ToHoudiniCurveType(const EHoudiniPublicAPICurveType InCurveType) -{ - switch (InCurveType) - { - case EHoudiniPublicAPICurveType::Invalid: - return EHoudiniCurveType::Invalid; - case EHoudiniPublicAPICurveType::Polygon: - return EHoudiniCurveType::Polygon; - case EHoudiniPublicAPICurveType::Nurbs: - return EHoudiniCurveType::Nurbs; - case EHoudiniPublicAPICurveType::Bezier: - return EHoudiniCurveType::Bezier; - case EHoudiniPublicAPICurveType::Points: - return EHoudiniCurveType::Points; - } - - return EHoudiniCurveType::Invalid; -} - -EHoudiniCurveMethod -UHoudiniPublicAPICurveInputObject::ToHoudiniCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod) -{ - switch (InCurveMethod) - { - case EHoudiniPublicAPICurveMethod::Invalid: - return EHoudiniCurveMethod::Invalid; - case EHoudiniPublicAPICurveMethod::CVs: - return EHoudiniCurveMethod::CVs; - case EHoudiniPublicAPICurveMethod::Breakpoints: - return EHoudiniCurveMethod::Breakpoints; - case EHoudiniPublicAPICurveMethod::Freehand: - return EHoudiniCurveMethod::Freehand; - } - - return EHoudiniCurveMethod::Invalid; -} - -EHoudiniPublicAPICurveType -UHoudiniPublicAPICurveInputObject::ToHoudiniPublicAPICurveType(const EHoudiniCurveType InCurveType) -{ - switch (InCurveType) - { - case EHoudiniCurveType::Invalid: - return EHoudiniPublicAPICurveType::Invalid; - case EHoudiniCurveType::Polygon: - return EHoudiniPublicAPICurveType::Polygon; - case EHoudiniCurveType::Nurbs: - return EHoudiniPublicAPICurveType::Nurbs; - case EHoudiniCurveType::Bezier: - return EHoudiniPublicAPICurveType::Bezier; - case EHoudiniCurveType::Points: - return EHoudiniPublicAPICurveType::Points; - } - - return EHoudiniPublicAPICurveType::Invalid; -} - -EHoudiniPublicAPICurveMethod -UHoudiniPublicAPICurveInputObject::ToHoudiniPublicAPICurveMethod(const EHoudiniCurveMethod InCurveMethod) -{ - switch (InCurveMethod) - { - case EHoudiniCurveMethod::Invalid: - return EHoudiniPublicAPICurveMethod::Invalid; - case EHoudiniCurveMethod::CVs: - return EHoudiniPublicAPICurveMethod::CVs; - case EHoudiniCurveMethod::Breakpoints: - return EHoudiniPublicAPICurveMethod::Breakpoints; - case EHoudiniCurveMethod::Freehand: - return EHoudiniPublicAPICurveMethod::Freehand; - } - - return EHoudiniPublicAPICurveMethod::Invalid; -} - -UHoudiniPublicAPICurveInput::UHoudiniPublicAPICurveInput() -{ - bKeepWorldTransform = false; - bCookOnCurveChanged = true; - bAddRotAndScaleAttributesOnCurves = false; -} - -bool -UHoudiniPublicAPICurveInput::IsAcceptableObjectForInput_Implementation(UObject* InObject) const -{ - if (!IsValid(InObject)) - return false; - - if (InObject->IsA()) - return true; - - return Super::IsAcceptableObjectForInput_Implementation(InObject); -} - -bool -UHoudiniPublicAPICurveInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) -{ - if (!Super::PopulateFromHoudiniInput(InInput)) - return false; - - bCookOnCurveChanged = InInput->GetCookOnCurveChange(); - bAddRotAndScaleAttributesOnCurves = InInput->IsAddRotAndScaleAttributesEnabled(); - - return true; -} - -bool -UHoudiniPublicAPICurveInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const -{ - if (!Super::UpdateHoudiniInput(InInput)) - return false; - - bool bAnyChanges = false; - if (InInput->GetCookOnCurveChange() != bCookOnCurveChanged) - { - InInput->SetCookOnCurveChange(bCookOnCurveChanged); - bAnyChanges = true; - } - if (InInput->IsAddRotAndScaleAttributesEnabled() != bAddRotAndScaleAttributesOnCurves) - { - InInput->SetAddRotAndScaleAttributes(bAddRotAndScaleAttributesOnCurves); - bAnyChanges = true; - } - - if (bAnyChanges) - { - InInput->MarkChanged(true); - } - - return true; -} - -UObject* -UHoudiniPublicAPICurveInput::ConvertInternalInputObject(UObject* InInternalInputObject) -{ - UObject* Object = Super::ConvertInternalInputObject(InInternalInputObject); - - // If the input object is a houdini spline component, convert it to an API curve wrapper - if (IsValid(Object) && Object->IsA()) - { - UHoudiniPublicAPICurveInputObject* const Curve = NewObject( - this, UHoudiniPublicAPICurveInputObject::StaticClass()); - if (IsValid(Curve)) - { - Curve->PopulateFromHoudiniSplineComponent(Cast(Object)); - return Curve; - } - } - - return Object; -} - -UObject* -UHoudiniPublicAPICurveInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const -{ - UObject* Object = nullptr; - - // If the input is an API curve wrapper, convert it to a UHoudiniSplineComponent - if (IsValid(InAPIInputObject) && InAPIInputObject->IsA() && IsValid(InHoudiniInput)) - { - UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputComponent = nullptr; - const bool bAttachToParent = true; - const bool bAppendToInputArray = false; - bool bBlueprintStructureModified; - UHoudiniInputHoudiniSplineComponent* const NewHoudiniInputObject = InHoudiniInput->CreateHoudiniSplineInput(FromHoudiniSplineInputComponent, bAttachToParent, bAppendToInputArray, bBlueprintStructureModified); - if (IsValid(NewHoudiniInputObject)) - { - UHoudiniSplineComponent* HoudiniSplineComponent = NewHoudiniInputObject->GetCurveComponent(); - if (IsValid(HoudiniSplineComponent)) - { - // Populate the HoudiniSplineComponent from the curve wrapper - Cast(InAPIInputObject)->CopyToHoudiniSplineComponent(HoudiniSplineComponent); - Object = HoudiniSplineComponent; - } - } - - TArray* HoudiniInputObjectArray = InHoudiniInput->GetHoudiniInputObjectArray(InHoudiniInput->GetInputType()); - if (HoudiniInputObjectArray && HoudiniInputObjectArray->IsValidIndex(InInputIndex)) - (*HoudiniInputObjectArray)[InInputIndex] = IsValid(NewHoudiniInputObject) ? NewHoudiniInputObject : nullptr; - } - else - { - Object = Super::ConvertAPIInputObjectAndAssignToInput(InAPIInputObject, InHoudiniInput, InInputIndex); - } - - return Object; -} - - -UHoudiniPublicAPIAssetInput::UHoudiniPublicAPIAssetInput() -{ - bKeepWorldTransform = true; -} - -bool -UHoudiniPublicAPIAssetInput::IsAcceptableObjectForInput_Implementation(UObject* InObject) const -{ - if (IsValid(InObject) && InObject->IsA()) - { - UHoudiniPublicAPIAssetWrapper* const Wrapper = Cast(InObject); - AHoudiniAssetActor* const AssetActor = Cast(Wrapper->GetHoudiniAssetActor()); - if (IsValid(AssetActor) && IsValid(AssetActor->HoudiniAssetComponent)) - return true; - } - - return Super::IsAcceptableObjectForInput_Implementation(InObject); -} - -bool -UHoudiniPublicAPIAssetInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) -{ - if (!Super::PopulateFromHoudiniInput(InInput)) - return false; - - return true; -} - -bool -UHoudiniPublicAPIAssetInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const -{ - if (!Super::UpdateHoudiniInput(InInput)) - return false; - - return true; -} - -UObject* -UHoudiniPublicAPIAssetInput::ConvertInternalInputObject(UObject* InInternalInputObject) -{ - // If InInternalInputObject is a Houdini Asset Component or Houdini Asset Actor, wrap it with the API and return - // wrapper. - if (IsValid(InInternalInputObject)) - { - if ((InInternalInputObject->IsA() || InInternalInputObject->IsA()) && - UHoudiniPublicAPIAssetWrapper::CanWrapHoudiniObject(InInternalInputObject)) - { - return UHoudiniPublicAPIAssetWrapper::CreateWrapper(this, InInternalInputObject); - } - } - - return Super::ConvertInternalInputObject(InInternalInputObject); -} - -UObject* -UHoudiniPublicAPIAssetInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const -{ - // If InAPIInputObject is an asset wrapper, extract the underlying HoudiniAssetComponent. - if (IsValid(InAPIInputObject) && InAPIInputObject->IsA()) - { - UHoudiniPublicAPIAssetWrapper* const Wrapper = Cast(InAPIInputObject); - if (Wrapper) - { - UHoudiniAssetComponent* const HAC = Wrapper->GetHoudiniAssetComponent(); - if (IsValid(HAC)) - { - return Super::ConvertAPIInputObjectAndAssignToInput(HAC, InHoudiniInput, InInputIndex); - } - } - } - - return Super::ConvertAPIInputObjectAndAssignToInput(InAPIInputObject, InHoudiniInput, InInputIndex); -} - - -UHoudiniPublicAPIWorldInput::UHoudiniPublicAPIWorldInput() -{ - bKeepWorldTransform = true; - bIsWorldInputBoundSelector = false; - bWorldInputBoundSelectorAutoUpdate = false; - UnrealSplineResolution = 50.0f; -} - -bool -UHoudiniPublicAPIWorldInput::SetInputObjects_Implementation(const TArray& InObjects) -{ - if (bIsWorldInputBoundSelector) - { - SetErrorMessage( - TEXT("This world input is not currently configured as a bound selector (bIsWorldInputBoundSelector == false)")); - return false; - } - - return Super::SetInputObjects_Implementation(InObjects); -} - -bool -UHoudiniPublicAPIWorldInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) -{ - if (!Super::PopulateFromHoudiniInput(InInput)) - return false; - - TArray const* const BoundSelectorObjectArray = InInput->GetBoundSelectorObjectArray(); - if (BoundSelectorObjectArray) - WorldInputBoundSelectorObjects = *BoundSelectorObjectArray; - else - WorldInputBoundSelectorObjects.Empty(); - bIsWorldInputBoundSelector = InInput->IsWorldInputBoundSelector(); - bWorldInputBoundSelectorAutoUpdate = InInput->GetWorldInputBoundSelectorAutoUpdates(); - UnrealSplineResolution = InInput->GetUnrealSplineResolution(); - - return true; -} - -bool -UHoudiniPublicAPIWorldInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const -{ - if (!Super::UpdateHoudiniInput(InInput)) - return false; - - InInput->SetBoundSelectorObjectsNumber(WorldInputBoundSelectorObjects.Num()); - TArray* const BoundSelectorObjectArray = InInput->GetBoundSelectorObjectArray(); - if (BoundSelectorObjectArray) - *BoundSelectorObjectArray = WorldInputBoundSelectorObjects; - InInput->SetWorldInputBoundSelector(bIsWorldInputBoundSelector); - InInput->SetWorldInputBoundSelectorAutoUpdates(bWorldInputBoundSelectorAutoUpdate); - InInput->SetUnrealSplineResolution(UnrealSplineResolution); - InInput->MarkChanged(true); - - return true; -} - - -UHoudiniPublicAPILandscapeInput::UHoudiniPublicAPILandscapeInput() - : bUpdateInputLandscape(false) - , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) - , bLandscapeExportSelectionOnly(false) - , bLandscapeAutoSelectComponent(false) - , bLandscapeExportMaterials(false) - , bLandscapeExportLighting(false) - , bLandscapeExportNormalizedUVs(false) - , bLandscapeExportTileUVs(false) -{ - -} - -bool -UHoudiniPublicAPILandscapeInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) -{ - if (!Super::PopulateFromHoudiniInput(InInput)) - return false; - - bUpdateInputLandscape = InInput->bUpdateInputLandscape; - LandscapeExportType = InInput->GetLandscapeExportType(); - bLandscapeExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; - bLandscapeAutoSelectComponent = InInput->bLandscapeAutoSelectComponent; - bLandscapeExportMaterials = InInput->bLandscapeExportMaterials; - bLandscapeExportLighting = InInput->bLandscapeExportLighting; - bLandscapeExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; - bLandscapeExportTileUVs = InInput->bLandscapeExportTileUVs; - - return true; -} - -bool -UHoudiniPublicAPILandscapeInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const -{ - if (!Super::UpdateHoudiniInput(InInput)) - return false; - - bool bAnyChanges = false; - if (InInput->bUpdateInputLandscape != bUpdateInputLandscape) - { - InInput->bUpdateInputLandscape = bUpdateInputLandscape; - bAnyChanges = true; - } - - if (InInput->GetLandscapeExportType() != LandscapeExportType) - { - InInput->SetLandscapeExportType(LandscapeExportType); - InInput->SetHasLandscapeExportTypeChanged(true); - - // Mark each input object as changed as well - TArray* LandscapeInputObjectsArray = InInput->GetHoudiniInputObjectArray(GetInputType()); - if (LandscapeInputObjectsArray) - { - for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) - { - if (!NextInputObj) - continue; - NextInputObj->MarkChanged(true); - } - } - - bAnyChanges = true; - } - - if (InInput->bLandscapeExportSelectionOnly != bLandscapeExportSelectionOnly) - { - InInput->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; - bAnyChanges = true; - } - - if (InInput->bLandscapeAutoSelectComponent != bLandscapeAutoSelectComponent) - { - InInput->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; - bAnyChanges = true; - } - - if (InInput->bLandscapeExportMaterials != bLandscapeExportMaterials) - { - InInput->bLandscapeExportMaterials = bLandscapeExportMaterials; - bAnyChanges = true; - } - - if (InInput->bLandscapeExportLighting != bLandscapeExportLighting) - { - InInput->bLandscapeExportLighting = bLandscapeExportLighting; - bAnyChanges = true; - } - - if (InInput->bLandscapeExportNormalizedUVs != bLandscapeExportNormalizedUVs) - { - InInput->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; - bAnyChanges = true; - } - - if (InInput->bLandscapeExportTileUVs != bLandscapeExportTileUVs) - { - InInput->bLandscapeExportTileUVs = bLandscapeExportTileUVs; - bAnyChanges = true; - } - - if (bAnyChanges) - { - InInput->MarkChanged(true); - } - - - return true; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIInputTypes.h" + +#include "HoudiniPublicAPIAssetWrapper.h" + +#include "HoudiniInput.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" + +UHoudiniPublicAPIInput::UHoudiniPublicAPIInput() +{ + bKeepWorldTransform = false; + bImportAsReference = false; +} + +bool +UHoudiniPublicAPIInput::IsAcceptableObjectForInput_Implementation(UObject* InObject) const +{ + return UHoudiniInput::IsObjectAcceptable(GetInputType(), InObject); +} + +bool +UHoudiniPublicAPIInput::SetInputObjects_Implementation(const TArray& InObjects) +{ + bool bHasFailures = false; + InputObjects.Empty(InObjects.Num()); + for (UObject* const Object : InObjects) + { + if (!IsValid(Object)) + { + SetErrorMessage(FString::Printf(TEXT("An input object is null or invalid."))); + bHasFailures = true; + continue; + } + else if (!IsAcceptableObjectForInput(Object)) + { + SetErrorMessage(FString::Printf( + TEXT("Object '%s' is not of an acceptable type for inputs of class %s."), + *(Object->GetName()), *(GetClass()->GetName()))); + bHasFailures = true; + continue; + } + + InputObjects.Add(Object); + } + + return !bHasFailures; +} + +bool +UHoudiniPublicAPIInput::GetInputObjects_Implementation(TArray& OutObjects) +{ + OutObjects = InputObjects; + + return true; +} + +bool +UHoudiniPublicAPIInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + const EHoudiniInputType InputType = GetInputType(); + if (!IsValid(InInput)) + { + SetErrorMessage(TEXT("InInput is invalid.")); + return false; + } + + if (InInput->GetInputType() != InputType) + { + SetErrorMessage(FString::Printf( + TEXT("Incompatible input types %d vs %d"), InInput->GetInputType(), InputType)); + return false; + } + + bKeepWorldTransform = InInput->GetKeepWorldTransform(); + bImportAsReference = InInput->GetImportAsReference(); + + const TArray* SrcInputObjectsPtr = InInput->GetHoudiniInputObjectArray(InputType); + if (SrcInputObjectsPtr && SrcInputObjectsPtr->Num() > 0) + { + InputObjects.Empty(SrcInputObjectsPtr->Num()); + for (UHoudiniInputObject const* const SrcInputObject : *SrcInputObjectsPtr) + { + if (!IsValid(SrcInputObject)) + continue; + + UObject* NewInputObject = ConvertInternalInputObject(SrcInputObject->GetObject()); + if (NewInputObject && NewInputObject->IsPendingKill()) + { + SetErrorMessage(FString::Printf( + TEXT("One of the input objects is non-null but pending kill/invalid."))); + return false; + } + + InputObjects.Add(NewInputObject); + + CopyHoudiniInputObjectProperties(SrcInputObject, NewInputObject); + } + } + + return true; +} + +bool +UHoudiniPublicAPIInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!IsValid(InInput)) + { + SetErrorMessage(TEXT("InInput is invalid.")); + return false; + } + + // Set / change the input type + const EHoudiniInputType InputType = GetInputType(); + bool bBlueprintStructureModified = false; + InInput->SetInputType(InputType, bBlueprintStructureModified); + + // Set any general settings + bool bAnyChanges = false; + if (InInput->GetKeepWorldTransform() != bKeepWorldTransform) + { + InInput->SetKeepWorldTransform(bKeepWorldTransform); + bAnyChanges = true; + } + if (InInput->GetImportAsReference() != bImportAsReference) + { + InInput->SetImportAsReference(bImportAsReference); + bAnyChanges = true; + } + + // Copy / set the input objects on the Houdini Input + const int32 NumInputObjects = InputObjects.Num(); + InInput->SetInputObjectsNumber(InputType, NumInputObjects); + for (int32 Index = 0; Index < NumInputObjects; ++Index) + { + UObject* const InputObject = InputObjects[Index]; + + if (!IsValid(InputObject)) + { + InInput->SetInputObjectAt(Index, nullptr); + } + else + { + ConvertAPIInputObjectAndAssignToInput(InputObject, InInput, Index); + UHoudiniInputObject *DstInputObject = InInput->GetHoudiniInputObjectAt(Index); + if (DstInputObject) + CopyPropertiesToHoudiniInputObject(InputObject, DstInputObject); + } + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + return true; +} + +bool +UHoudiniPublicAPIInput::CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) +{ + if (!IsValid(InInputObject) || !IsValid(InObject)) + return false; + + return true; +} + +bool +UHoudiniPublicAPIInput::CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const +{ + if (!IsValid(InObject) || !IsValid(InInputObject)) + return false; + + // const EHoudiniInputObjectType InputObjectType = InInputObject->Type; + + if (InInputObject->GetImportAsReference() != bImportAsReference) + { + InInputObject->SetImportAsReference(bImportAsReference); + InInputObject->MarkChanged(true); + } + + // switch (InputObjectType) + // { + // case EHoudiniInputObjectType::StaticMesh: + // case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + // break; + // case EHoudiniInputObjectType::Object: + // case EHoudiniInputObjectType::SkeletalMesh: + // case EHoudiniInputObjectType::HoudiniSplineComponent: + // case EHoudiniInputObjectType::SceneComponent: + // case EHoudiniInputObjectType::StaticMeshComponent: + // case EHoudiniInputObjectType::InstancedStaticMeshComponent: + // case EHoudiniInputObjectType::SplineComponent: + // case EHoudiniInputObjectType::HoudiniAssetComponent: + // case EHoudiniInputObjectType::Actor: + // case EHoudiniInputObjectType::Landscape: + // case EHoudiniInputObjectType::Brush: + // case EHoudiniInputObjectType::CameraComponent: + // case EHoudiniInputObjectType::DataTable: + // case EHoudiniInputObjectType::HoudiniAssetActor: + // break; + // + // case EHoudiniInputObjectType::Invalid: + // return false; + // } + + return true; +} + +UObject* +UHoudiniPublicAPIInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const +{ + if (!IsValid(InHoudiniInput)) + return nullptr; + + UObject* const ObjectToSet = (InAPIInputObject && !InAPIInputObject->IsPendingKill()) ? InAPIInputObject : nullptr; + InHoudiniInput->SetInputObjectAt(InInputIndex, ObjectToSet); + + return ObjectToSet; +} + +UHoudiniPublicAPIGeoInput::UHoudiniPublicAPIGeoInput() +{ + bKeepWorldTransform = false; + bPackBeforeMerge = false; + bExportLODs = false; + bExportSockets = false; + bExportColliders = false; +} + +bool +UHoudiniPublicAPIGeoInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + bPackBeforeMerge = InInput->GetPackBeforeMerge(); + bExportLODs = InInput->GetExportLODs(); + bExportSockets = InInput->GetExportSockets(); + bExportColliders = InInput->GetExportColliders(); + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + bool bAnyChanges = false; + if (InInput->GetPackBeforeMerge() != bPackBeforeMerge) + { + InInput->SetPackBeforeMerge(bPackBeforeMerge); + bAnyChanges = true; + } + if (InInput->GetExportLODs() != bExportLODs) + { + InInput->SetExportLODs(bExportLODs); + bAnyChanges = true; + } + if (InInput->GetExportSockets() != bExportSockets) + { + InInput->SetExportSockets(bExportSockets); + bAnyChanges = true; + } + if (InInput->GetExportColliders() != bExportColliders) + { + InInput->SetExportColliders(bExportColliders); + bAnyChanges = true; + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) +{ + if (!Super::CopyHoudiniInputObjectProperties(InInputObject, InObject)) + return false; + + if (!IsValid(InInputObject) || !IsValid(InObject)) + return false; + + // Copy the transform offset + SetObjectTransformOffset(InObject, InInputObject->Transform); + + return true; +} + + +bool +UHoudiniPublicAPIGeoInput::CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const +{ + if (!Super::CopyPropertiesToHoudiniInputObject(InObject, InInputObject)) + return false; + + if (!IsValid(InObject) || !IsValid(InInputObject)) + return false; + + // Copy the transform offset + FTransform Transform; + if (GetObjectTransformOffset(InObject, Transform)) + { + if (!InInputObject->Transform.Equals(Transform)) + { + InInputObject->Transform = Transform; + InInputObject->MarkChanged(true); + } + } + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::SetObjectTransformOffset_Implementation(UObject* InObject, const FTransform& InTransform) +{ + // Ensure that InObject is valid and has already been added as input object + if (!IsValid(InObject)) + { + SetErrorMessage(TEXT("InObject is invalid.")); + return false; + } + + if (INDEX_NONE == InputObjects.Find(InObject)) + { + SetErrorMessage(FString::Printf( + TEXT("InObject '%s' is not currently set as input object on this input."), *(InObject->GetName()))); + return false; + } + + InputObjectTransformOffsets.Add(InObject, InTransform); + + return true; +} + +bool +UHoudiniPublicAPIGeoInput::GetObjectTransformOffset_Implementation(UObject* InObject, FTransform& OutTransform) const +{ + // Ensure that InObject is valid and has already been added as input object + if (!IsValid(InObject)) + { + SetErrorMessage(TEXT("InObject is invalid.")); + return false; + } + + if (INDEX_NONE == InputObjects.Find(InObject)) + { + SetErrorMessage(FString::Printf( + TEXT("InObject '%s' is not currently set as input object on this input."), *(InObject->GetName()))); + return false; + } + + FTransform const* const TransformPtr = InputObjectTransformOffsets.Find(InObject); + if (!TransformPtr) + { + SetErrorMessage(FString::Printf( + TEXT("InObject '%s' does not have a transform offset set."), *(InObject->GetName()))); + return false; + } + + OutTransform = *TransformPtr; + return true; +} + + +UHoudiniPublicAPICurveInputObject::UHoudiniPublicAPICurveInputObject() + : bClosed(false) + , bReversed(false) + , CurveType(EHoudiniPublicAPICurveType::Polygon) + , CurveMethod(EHoudiniPublicAPICurveMethod::CVs) +{ + +} + + +void +UHoudiniPublicAPICurveInputObject::PopulateFromHoudiniSplineComponent(UHoudiniSplineComponent const* const InSpline) +{ + if (!IsValid(InSpline)) + return; + + bClosed = InSpline->IsClosedCurve(); + bReversed = InSpline->IsReversed(); + CurveType = ToHoudiniPublicAPICurveType(InSpline->GetCurveType()); + CurveMethod = ToHoudiniPublicAPICurveMethod(InSpline->GetCurveMethod()); + CurvePoints = InSpline->CurvePoints; +} + +void +UHoudiniPublicAPICurveInputObject::CopyToHoudiniSplineComponent(UHoudiniSplineComponent* const InSpline) const +{ + if (!IsValid(InSpline)) + return; + + InSpline->SetClosedCurve(bClosed); + InSpline->SetReversed(bReversed); + InSpline->SetCurveType(ToHoudiniCurveType(CurveType)); + InSpline->SetCurveMethod(ToHoudiniCurveMethod(CurveMethod)); + InSpline->ResetCurvePoints(); + InSpline->ResetDisplayPoints(); + InSpline->CurvePoints = CurvePoints; +} + +EHoudiniCurveType +UHoudiniPublicAPICurveInputObject::ToHoudiniCurveType(const EHoudiniPublicAPICurveType InCurveType) +{ + switch (InCurveType) + { + case EHoudiniPublicAPICurveType::Invalid: + return EHoudiniCurveType::Invalid; + case EHoudiniPublicAPICurveType::Polygon: + return EHoudiniCurveType::Polygon; + case EHoudiniPublicAPICurveType::Nurbs: + return EHoudiniCurveType::Nurbs; + case EHoudiniPublicAPICurveType::Bezier: + return EHoudiniCurveType::Bezier; + case EHoudiniPublicAPICurveType::Points: + return EHoudiniCurveType::Points; + } + + return EHoudiniCurveType::Invalid; +} + +EHoudiniCurveMethod +UHoudiniPublicAPICurveInputObject::ToHoudiniCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod) +{ + switch (InCurveMethod) + { + case EHoudiniPublicAPICurveMethod::Invalid: + return EHoudiniCurveMethod::Invalid; + case EHoudiniPublicAPICurveMethod::CVs: + return EHoudiniCurveMethod::CVs; + case EHoudiniPublicAPICurveMethod::Breakpoints: + return EHoudiniCurveMethod::Breakpoints; + case EHoudiniPublicAPICurveMethod::Freehand: + return EHoudiniCurveMethod::Freehand; + } + + return EHoudiniCurveMethod::Invalid; +} + +EHoudiniPublicAPICurveType +UHoudiniPublicAPICurveInputObject::ToHoudiniPublicAPICurveType(const EHoudiniCurveType InCurveType) +{ + switch (InCurveType) + { + case EHoudiniCurveType::Invalid: + return EHoudiniPublicAPICurveType::Invalid; + case EHoudiniCurveType::Polygon: + return EHoudiniPublicAPICurveType::Polygon; + case EHoudiniCurveType::Nurbs: + return EHoudiniPublicAPICurveType::Nurbs; + case EHoudiniCurveType::Bezier: + return EHoudiniPublicAPICurveType::Bezier; + case EHoudiniCurveType::Points: + return EHoudiniPublicAPICurveType::Points; + } + + return EHoudiniPublicAPICurveType::Invalid; +} + +EHoudiniPublicAPICurveMethod +UHoudiniPublicAPICurveInputObject::ToHoudiniPublicAPICurveMethod(const EHoudiniCurveMethod InCurveMethod) +{ + switch (InCurveMethod) + { + case EHoudiniCurveMethod::Invalid: + return EHoudiniPublicAPICurveMethod::Invalid; + case EHoudiniCurveMethod::CVs: + return EHoudiniPublicAPICurveMethod::CVs; + case EHoudiniCurveMethod::Breakpoints: + return EHoudiniPublicAPICurveMethod::Breakpoints; + case EHoudiniCurveMethod::Freehand: + return EHoudiniPublicAPICurveMethod::Freehand; + } + + return EHoudiniPublicAPICurveMethod::Invalid; +} + +UHoudiniPublicAPICurveInput::UHoudiniPublicAPICurveInput() +{ + bKeepWorldTransform = false; + bCookOnCurveChanged = true; + bAddRotAndScaleAttributesOnCurves = false; +} + +bool +UHoudiniPublicAPICurveInput::IsAcceptableObjectForInput_Implementation(UObject* InObject) const +{ + if (!IsValid(InObject)) + return false; + + if (InObject->IsA()) + return true; + + return Super::IsAcceptableObjectForInput_Implementation(InObject); +} + +bool +UHoudiniPublicAPICurveInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + bCookOnCurveChanged = InInput->GetCookOnCurveChange(); + bAddRotAndScaleAttributesOnCurves = InInput->IsAddRotAndScaleAttributesEnabled(); + + return true; +} + +bool +UHoudiniPublicAPICurveInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + bool bAnyChanges = false; + if (InInput->GetCookOnCurveChange() != bCookOnCurveChanged) + { + InInput->SetCookOnCurveChange(bCookOnCurveChanged); + bAnyChanges = true; + } + if (InInput->IsAddRotAndScaleAttributesEnabled() != bAddRotAndScaleAttributesOnCurves) + { + InInput->SetAddRotAndScaleAttributes(bAddRotAndScaleAttributesOnCurves); + bAnyChanges = true; + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + return true; +} + +UObject* +UHoudiniPublicAPICurveInput::ConvertInternalInputObject(UObject* InInternalInputObject) +{ + UObject* Object = Super::ConvertInternalInputObject(InInternalInputObject); + + // If the input object is a houdini spline component, convert it to an API curve wrapper + if (IsValid(Object) && Object->IsA()) + { + UHoudiniPublicAPICurveInputObject* const Curve = NewObject( + this, UHoudiniPublicAPICurveInputObject::StaticClass()); + if (IsValid(Curve)) + { + Curve->PopulateFromHoudiniSplineComponent(Cast(Object)); + return Curve; + } + } + + return Object; +} + +UObject* +UHoudiniPublicAPICurveInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const +{ + UObject* Object = nullptr; + + // If the input is an API curve wrapper, convert it to a UHoudiniSplineComponent + if (IsValid(InAPIInputObject) && InAPIInputObject->IsA() && IsValid(InHoudiniInput)) + { + UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputComponent = nullptr; + const bool bAttachToParent = true; + const bool bAppendToInputArray = false; + bool bBlueprintStructureModified; + UHoudiniInputHoudiniSplineComponent* const NewHoudiniInputObject = InHoudiniInput->CreateHoudiniSplineInput(FromHoudiniSplineInputComponent, bAttachToParent, bAppendToInputArray, bBlueprintStructureModified); + if (IsValid(NewHoudiniInputObject)) + { + UHoudiniSplineComponent* HoudiniSplineComponent = NewHoudiniInputObject->GetCurveComponent(); + if (IsValid(HoudiniSplineComponent)) + { + // Populate the HoudiniSplineComponent from the curve wrapper + Cast(InAPIInputObject)->CopyToHoudiniSplineComponent(HoudiniSplineComponent); + Object = HoudiniSplineComponent; + } + } + + TArray* HoudiniInputObjectArray = InHoudiniInput->GetHoudiniInputObjectArray(InHoudiniInput->GetInputType()); + if (HoudiniInputObjectArray && HoudiniInputObjectArray->IsValidIndex(InInputIndex)) + (*HoudiniInputObjectArray)[InInputIndex] = IsValid(NewHoudiniInputObject) ? NewHoudiniInputObject : nullptr; + } + else + { + Object = Super::ConvertAPIInputObjectAndAssignToInput(InAPIInputObject, InHoudiniInput, InInputIndex); + } + + return Object; +} + + +UHoudiniPublicAPIAssetInput::UHoudiniPublicAPIAssetInput() +{ + bKeepWorldTransform = true; +} + +bool +UHoudiniPublicAPIAssetInput::IsAcceptableObjectForInput_Implementation(UObject* InObject) const +{ + if (IsValid(InObject) && InObject->IsA()) + { + UHoudiniPublicAPIAssetWrapper* const Wrapper = Cast(InObject); + AHoudiniAssetActor* const AssetActor = Cast(Wrapper->GetHoudiniAssetActor()); + if (IsValid(AssetActor) && IsValid(AssetActor->HoudiniAssetComponent)) + return true; + } + + return Super::IsAcceptableObjectForInput_Implementation(InObject); +} + +bool +UHoudiniPublicAPIAssetInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + return true; +} + +bool +UHoudiniPublicAPIAssetInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + return true; +} + +UObject* +UHoudiniPublicAPIAssetInput::ConvertInternalInputObject(UObject* InInternalInputObject) +{ + // If InInternalInputObject is a Houdini Asset Component or Houdini Asset Actor, wrap it with the API and return + // wrapper. + if (IsValid(InInternalInputObject)) + { + if ((InInternalInputObject->IsA() || InInternalInputObject->IsA()) && + UHoudiniPublicAPIAssetWrapper::CanWrapHoudiniObject(InInternalInputObject)) + { + return UHoudiniPublicAPIAssetWrapper::CreateWrapper(this, InInternalInputObject); + } + } + + return Super::ConvertInternalInputObject(InInternalInputObject); +} + +UObject* +UHoudiniPublicAPIAssetInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const +{ + // If InAPIInputObject is an asset wrapper, extract the underlying HoudiniAssetComponent. + if (IsValid(InAPIInputObject) && InAPIInputObject->IsA()) + { + UHoudiniPublicAPIAssetWrapper* const Wrapper = Cast(InAPIInputObject); + if (Wrapper) + { + UHoudiniAssetComponent* const HAC = Wrapper->GetHoudiniAssetComponent(); + if (IsValid(HAC)) + { + return Super::ConvertAPIInputObjectAndAssignToInput(HAC, InHoudiniInput, InInputIndex); + } + } + } + + return Super::ConvertAPIInputObjectAndAssignToInput(InAPIInputObject, InHoudiniInput, InInputIndex); +} + + +UHoudiniPublicAPIWorldInput::UHoudiniPublicAPIWorldInput() +{ + bKeepWorldTransform = true; + bIsWorldInputBoundSelector = false; + bWorldInputBoundSelectorAutoUpdate = false; + UnrealSplineResolution = 50.0f; +} + +bool +UHoudiniPublicAPIWorldInput::SetInputObjects_Implementation(const TArray& InObjects) +{ + if (bIsWorldInputBoundSelector) + { + SetErrorMessage( + TEXT("This world input is not currently configured as a bound selector (bIsWorldInputBoundSelector == false)")); + return false; + } + + return Super::SetInputObjects_Implementation(InObjects); +} + +bool +UHoudiniPublicAPIWorldInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + TArray const* const BoundSelectorObjectArray = InInput->GetBoundSelectorObjectArray(); + if (BoundSelectorObjectArray) + WorldInputBoundSelectorObjects = *BoundSelectorObjectArray; + else + WorldInputBoundSelectorObjects.Empty(); + bIsWorldInputBoundSelector = InInput->IsWorldInputBoundSelector(); + bWorldInputBoundSelectorAutoUpdate = InInput->GetWorldInputBoundSelectorAutoUpdates(); + UnrealSplineResolution = InInput->GetUnrealSplineResolution(); + + return true; +} + +bool +UHoudiniPublicAPIWorldInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + InInput->SetBoundSelectorObjectsNumber(WorldInputBoundSelectorObjects.Num()); + TArray* const BoundSelectorObjectArray = InInput->GetBoundSelectorObjectArray(); + if (BoundSelectorObjectArray) + *BoundSelectorObjectArray = WorldInputBoundSelectorObjects; + InInput->SetWorldInputBoundSelector(bIsWorldInputBoundSelector); + InInput->SetWorldInputBoundSelectorAutoUpdates(bWorldInputBoundSelectorAutoUpdate); + InInput->SetUnrealSplineResolution(UnrealSplineResolution); + InInput->MarkChanged(true); + + return true; +} + + +UHoudiniPublicAPILandscapeInput::UHoudiniPublicAPILandscapeInput() + : bUpdateInputLandscape(false) + , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) + , bLandscapeExportSelectionOnly(false) + , bLandscapeAutoSelectComponent(false) + , bLandscapeExportMaterials(false) + , bLandscapeExportLighting(false) + , bLandscapeExportNormalizedUVs(false) + , bLandscapeExportTileUVs(false) +{ + +} + +bool +UHoudiniPublicAPILandscapeInput::PopulateFromHoudiniInput(UHoudiniInput const* const InInput) +{ + if (!Super::PopulateFromHoudiniInput(InInput)) + return false; + + bUpdateInputLandscape = InInput->bUpdateInputLandscape; + LandscapeExportType = InInput->GetLandscapeExportType(); + bLandscapeExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; + bLandscapeAutoSelectComponent = InInput->bLandscapeAutoSelectComponent; + bLandscapeExportMaterials = InInput->bLandscapeExportMaterials; + bLandscapeExportLighting = InInput->bLandscapeExportLighting; + bLandscapeExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; + bLandscapeExportTileUVs = InInput->bLandscapeExportTileUVs; + + return true; +} + +bool +UHoudiniPublicAPILandscapeInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const +{ + if (!Super::UpdateHoudiniInput(InInput)) + return false; + + bool bAnyChanges = false; + if (InInput->bUpdateInputLandscape != bUpdateInputLandscape) + { + InInput->bUpdateInputLandscape = bUpdateInputLandscape; + bAnyChanges = true; + } + + if (InInput->GetLandscapeExportType() != LandscapeExportType) + { + InInput->SetLandscapeExportType(LandscapeExportType); + InInput->SetHasLandscapeExportTypeChanged(true); + + // Mark each input object as changed as well + TArray* LandscapeInputObjectsArray = InInput->GetHoudiniInputObjectArray(GetInputType()); + if (LandscapeInputObjectsArray) + { + for (UHoudiniInputObject *NextInputObj : *LandscapeInputObjectsArray) + { + if (!NextInputObj) + continue; + NextInputObj->MarkChanged(true); + } + } + + bAnyChanges = true; + } + + if (InInput->bLandscapeExportSelectionOnly != bLandscapeExportSelectionOnly) + { + InInput->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; + bAnyChanges = true; + } + + if (InInput->bLandscapeAutoSelectComponent != bLandscapeAutoSelectComponent) + { + InInput->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportMaterials != bLandscapeExportMaterials) + { + InInput->bLandscapeExportMaterials = bLandscapeExportMaterials; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportLighting != bLandscapeExportLighting) + { + InInput->bLandscapeExportLighting = bLandscapeExportLighting; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportNormalizedUVs != bLandscapeExportNormalizedUVs) + { + InInput->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; + bAnyChanges = true; + } + + if (InInput->bLandscapeExportTileUVs != bLandscapeExportTileUVs) + { + InInput->bLandscapeExportTileUVs = bLandscapeExportTileUVs; + bAnyChanges = true; + } + + if (bAnyChanges) + { + InInput->MarkChanged(true); + } + + + return true; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp index caacedd09..50cad9825 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp @@ -1,79 +1,78 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniPublicAPIObjectBase.h" - -UHoudiniPublicAPIObjectBase::UHoudiniPublicAPIObjectBase() - : LastErrorMessage() - , bHasError(false) - , bIsLoggingErrors(true) -{ - -} - -bool -UHoudiniPublicAPIObjectBase::GetLastErrorMessage_Implementation(FString& OutLastErrorMessage) const -{ - if (!bHasError) - { - OutLastErrorMessage = FString(); - return false; - } - - OutLastErrorMessage = LastErrorMessage; - return true; -} - -void -UHoudiniPublicAPIObjectBase::ClearErrorMessages_Implementation() -{ - LastErrorMessage = FString(); - bHasError = false; -} - -void -UHoudiniPublicAPIObjectBase::SetErrorMessage_Implementation( - const FString& InErrorMessage, - const EHoudiniPublicAPIErrorLogOption InLoggingOption) const -{ - LastErrorMessage = InErrorMessage; - bHasError = true; - switch (InLoggingOption) - { - case EHoudiniPublicAPIErrorLogOption::Invalid: - case EHoudiniPublicAPIErrorLogOption::Auto: - case EHoudiniPublicAPIErrorLogOption::Log: - { - static const FString Prefix = TEXT("[HoudiniEngine:PublicAPI]"); - HOUDINI_LOG_WARNING(TEXT("%s %s"), *Prefix, *InErrorMessage); - break; - } - case EHoudiniPublicAPIErrorLogOption::NoLog: - // Don't log - break; - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIObjectBase.h" + +UHoudiniPublicAPIObjectBase::UHoudiniPublicAPIObjectBase() + : LastErrorMessage() + , bHasError(false) + , bIsLoggingErrors(true) +{ + +} + +bool +UHoudiniPublicAPIObjectBase::GetLastErrorMessage_Implementation(FString& OutLastErrorMessage) const +{ + if (!bHasError) + { + OutLastErrorMessage = FString(); + return false; + } + + OutLastErrorMessage = LastErrorMessage; + return true; +} + +void +UHoudiniPublicAPIObjectBase::ClearErrorMessages_Implementation() +{ + LastErrorMessage = FString(); + bHasError = false; +} + +void +UHoudiniPublicAPIObjectBase::SetErrorMessage_Implementation( + const FString& InErrorMessage, + const EHoudiniPublicAPIErrorLogOption InLoggingOption) const +{ + LastErrorMessage = InErrorMessage; + bHasError = true; + switch (InLoggingOption) + { + case EHoudiniPublicAPIErrorLogOption::Invalid: + case EHoudiniPublicAPIErrorLogOption::Auto: + case EHoudiniPublicAPIErrorLogOption::Log: + { + static const FString Prefix = TEXT("[HoudiniEngine:PublicAPI]"); + HOUDINI_LOG_WARNING(TEXT("%s %s"), *Prefix, *InErrorMessage); + break; + } + case EHoudiniPublicAPIErrorLogOption::NoLog: + // Don't log + break; + } +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp index faf41bbce..5dcd99ecb 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIOutputTypes.cpp @@ -1,49 +1,79 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPublicAPIOutputTypes.h" - -FHoudiniPublicAPIOutputObjectIdentifier::FHoudiniPublicAPIOutputObjectIdentifier() - : SplitIdentifier() - , PartName() - , Identifier() -{ -} - -FHoudiniPublicAPIOutputObjectIdentifier::FHoudiniPublicAPIOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) - : SplitIdentifier() - , PartName() -{ - SetIdentifier(InIdentifier); -} - -void -FHoudiniPublicAPIOutputObjectIdentifier::SetIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - Identifier = InIdentifier; - SplitIdentifier = Identifier.SplitIdentifier; - PartName = Identifier.PartName; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIOutputTypes.h" + +#include "HoudiniOutput.h" + +FHoudiniPublicAPIOutputObjectIdentifier::FHoudiniPublicAPIOutputObjectIdentifier() + : SplitIdentifier() + , PartName() + , ObjectId(-1) + , GeoId(-1) + , PartId(-1) + + , PrimitiveIndex(-1) + , PointIndex(-1) + , bLoaded(false) +{ +} + +FHoudiniPublicAPIOutputObjectIdentifier::FHoudiniPublicAPIOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) + : SplitIdentifier() + , PartName() +{ + SetIdentifier(InIdentifier); +} + +void +FHoudiniPublicAPIOutputObjectIdentifier::SetIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + ObjectId = InIdentifier.ObjectId; + GeoId = InIdentifier.GeoId; + PartId = InIdentifier.PartId; + SplitIdentifier = InIdentifier.SplitIdentifier; + PartName = InIdentifier.PartName; + PrimitiveIndex = InIdentifier.PrimitiveIndex; + PointIndex = InIdentifier.PointIndex; + bLoaded = InIdentifier.bLoaded; +} + +/** Returns the internal output object identifier wrapped by this class. */ +FHoudiniOutputObjectIdentifier +FHoudiniPublicAPIOutputObjectIdentifier::GetIdentifier() const +{ + FHoudiniOutputObjectIdentifier Identifier; + Identifier.ObjectId = ObjectId; + Identifier.GeoId = GeoId; + Identifier.PartId = PartId; + Identifier.SplitIdentifier = SplitIdentifier; + Identifier.PartName = PartName; + Identifier.PrimitiveIndex = PrimitiveIndex; + Identifier.PointIndex = PointIndex; + Identifier.bLoaded = bLoaded; + + return Identifier; +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp index 22f1f42c2..029b67388 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIProcessHDANode.cpp @@ -1,314 +1,316 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPublicAPIProcessHDANode.h" - -#include "HoudiniPublicAPI.h" -#include "HoudiniPublicAPIBlueprintLib.h" -#include "HoudiniPublicAPIAssetWrapper.h" -#include "HoudiniPublicAPIInputTypes.h" - - -UHoudiniPublicAPIProcessHDANode::UHoudiniPublicAPIProcessHDANode(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - if ( HasAnyFlags(RF_ClassDefaultObject) == false ) - { - AddToRoot(); - } - - AssetWrapper = nullptr; - bCookSuccess = false; - bBakeSuccess = false; - - HoudiniAsset = nullptr; - InstantiateAt = FTransform::Identity; - WorldContextObject = nullptr; - SpawnInLevelOverride = nullptr; - bEnableAutoCook = true; - bEnableAutoBake = false; - BakeDirectoryPath = FString(); - BakeMethod = EHoudiniEngineBakeOption::ToActor; - bRemoveOutputAfterBake = false; - bRecenterBakedActors = false; - bReplacePreviousBake = false; - bDeleteInstantiatedAssetOnCompletionOrFailure = false; -} - -UHoudiniPublicAPIProcessHDANode* -UHoudiniPublicAPIProcessHDANode::ProcessHDA( - UHoudiniAsset* InHoudiniAsset, - const FTransform& InInstantiateAt, - const TMap& InParameters, - const TMap& InNodeInputs, - const TMap& InParameterInputs, - UObject* InWorldContextObject, - ULevel* InSpawnInLevelOverride, - const bool bInEnableAutoCook, - const bool bInEnableAutoBake, - const FString& InBakeDirectoryPath, - const EHoudiniEngineBakeOption InBakeMethod, - const bool bInRemoveOutputAfterBake, - const bool bInRecenterBakedActors, - const bool bInReplacePreviousBake, - const bool bInDeleteInstantiatedAssetOnCompletionOrFailure) -{ - UHoudiniPublicAPIProcessHDANode* Node = NewObject(); - - Node->HoudiniAsset = InHoudiniAsset; - Node->InstantiateAt = InInstantiateAt; - Node->Parameters = InParameters; - Node->NodeInputs = InNodeInputs; - Node->ParameterInputs = InParameterInputs; - Node->WorldContextObject = InWorldContextObject; - Node->SpawnInLevelOverride = InSpawnInLevelOverride; - Node->bEnableAutoCook = bInEnableAutoCook; - Node->bEnableAutoBake = bInEnableAutoBake; - Node->BakeDirectoryPath = InBakeDirectoryPath; - Node->BakeMethod = InBakeMethod; - Node->bRemoveOutputAfterBake = bInRemoveOutputAfterBake; - Node->bRecenterBakedActors = bInRecenterBakedActors; - Node->bReplacePreviousBake = bInReplacePreviousBake; - Node->bDeleteInstantiatedAssetOnCompletionOrFailure = bInDeleteInstantiatedAssetOnCompletionOrFailure; - - return Node; -} - - -void -UHoudiniPublicAPIProcessHDANode::Activate() -{ - UHoudiniPublicAPI* API = UHoudiniPublicAPIBlueprintLib::GetAPI(); - if (!IsValid(API)) - { - HandleFailure(); - return; - } - - AssetWrapper = UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(API); - if (!IsValid(AssetWrapper)) - { - HandleFailure(); - return; - } - - AssetWrapper->GetOnPreInstantiationDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation); - AssetWrapper->GetOnPostInstantiationDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation); - AssetWrapper->GetOnPostCookDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook); - AssetWrapper->GetOnPreProcessStateExitedDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreProcess); - AssetWrapper->GetOnPostProcessingDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostProcessing); - AssetWrapper->GetOnPostBakeDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake); - - if (!API->InstantiateAssetWithExistingWrapper( - AssetWrapper, - HoudiniAsset, - InstantiateAt, - WorldContextObject, - SpawnInLevelOverride, - bEnableAutoCook, - bEnableAutoBake, - BakeDirectoryPath, - BakeMethod, - bRemoveOutputAfterBake, - bRecenterBakedActors, - bReplacePreviousBake)) - { - HandleFailure(); - return; - } -} - -void -UHoudiniPublicAPIProcessHDANode::UnbindDelegates() -{ - AssetWrapper->GetOnPreInstantiationDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation); - AssetWrapper->GetOnPostInstantiationDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation); - AssetWrapper->GetOnPostCookDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook); - AssetWrapper->GetOnPreProcessStateExitedDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreProcess); - AssetWrapper->GetOnPostProcessingDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostProcessing); - AssetWrapper->GetOnPostBakeDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake); -} - -void -UHoudiniPublicAPIProcessHDANode::HandleFailure() -{ - if (Failed.IsBound()) - Failed.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); - - UnbindDelegates(); - - RemoveFromRoot(); - - if (bDeleteInstantiatedAssetOnCompletionOrFailure && IsValid(AssetWrapper)) - AssetWrapper->DeleteInstantiatedAsset(); -} - -void -UHoudiniPublicAPIProcessHDANode::HandleComplete() -{ - if (Completed.IsBound()) - Completed.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); - - UnbindDelegates(); - - RemoveFromRoot(); - - if (bDeleteInstantiatedAssetOnCompletionOrFailure && IsValid(AssetWrapper)) - AssetWrapper->DeleteInstantiatedAsset(); -} - -void -UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) -{ - if (InAssetWrapper != AssetWrapper) - { - HOUDINI_LOG_WARNING( - TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), - IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), - IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); - return; - } - - // Set any parameters specified when the node was created - if (Parameters.Num() > 0 && IsValid(AssetWrapper)) - { - AssetWrapper->SetParameterTuples(Parameters); - } - - if (PreInstantiation.IsBound()) - PreInstantiation.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); -} - -void -UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) -{ - if (InAssetWrapper != AssetWrapper) - { - HOUDINI_LOG_WARNING( - TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), - IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), - IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); - return; - } - - // Set any inputs specified when the node was created - if (IsValid(AssetWrapper)) - { - if (NodeInputs.Num() > 0) - { - AssetWrapper->SetInputsAtIndices(NodeInputs); - } - if (ParameterInputs.Num() > 0) - { - AssetWrapper->SetInputParameters(ParameterInputs); - } - - // // Set any parameters specified when the node was created - // if (Parameters.Num() > 0) - // { - // AssetWrapper->SetParameterTuples(Parameters); - // } - } - - if (PostInstantiation.IsBound()) - PostInstantiation.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); - - if (!bEnableAutoCook) - HandleComplete(); -} - -void -UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInCookSuccess) -{ - if (InAssetWrapper != AssetWrapper) - { - HOUDINI_LOG_WARNING( - TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), - IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), - IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); - return; - } - - bCookSuccess = bInCookSuccess; - - if (PostAutoCook.IsBound()) - PostAutoCook.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); -} - -void -UHoudiniPublicAPIProcessHDANode::HandlePreProcess(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) -{ - if (InAssetWrapper != AssetWrapper) - { - HOUDINI_LOG_WARNING( - TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), - IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), - IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); - return; - } - - if (PreProcess.IsBound()) - PreProcess.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); -} - -void -UHoudiniPublicAPIProcessHDANode::HandlePostProcessing(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) -{ - if (InAssetWrapper != AssetWrapper) - { - HOUDINI_LOG_WARNING( - TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), - IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), - IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); - return; - } - - if (PostProcessing.IsBound()) - PostProcessing.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); - - if (!bEnableAutoBake) - HandleComplete(); -} - -void -UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInBakeSuccess) -{ - if (InAssetWrapper != AssetWrapper) - { - HOUDINI_LOG_WARNING( - TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), - IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), - IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); - return; - } - - bBakeSuccess = bInBakeSuccess; - - if (PostAutoBake.IsBound()) - PostAutoBake.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); - - HandleComplete(); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPublicAPIProcessHDANode.h" + +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPIAssetWrapper.h" +#include "HoudiniPublicAPIInputTypes.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + + +UHoudiniPublicAPIProcessHDANode::UHoudiniPublicAPIProcessHDANode(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + if ( HasAnyFlags(RF_ClassDefaultObject) == false ) + { + AddToRoot(); + } + + AssetWrapper = nullptr; + bCookSuccess = false; + bBakeSuccess = false; + + HoudiniAsset = nullptr; + InstantiateAt = FTransform::Identity; + WorldContextObject = nullptr; + SpawnInLevelOverride = nullptr; + bEnableAutoCook = true; + bEnableAutoBake = false; + BakeDirectoryPath = FString(); + BakeMethod = EHoudiniEngineBakeOption::ToActor; + bRemoveOutputAfterBake = false; + bRecenterBakedActors = false; + bReplacePreviousBake = false; + bDeleteInstantiatedAssetOnCompletionOrFailure = false; +} + +UHoudiniPublicAPIProcessHDANode* +UHoudiniPublicAPIProcessHDANode::ProcessHDA( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + const TMap& InParameters, + const TMap& InNodeInputs, + const TMap& InParameterInputs, + UObject* InWorldContextObject, + ULevel* InSpawnInLevelOverride, + const bool bInEnableAutoCook, + const bool bInEnableAutoBake, + const FString& InBakeDirectoryPath, + const EHoudiniEngineBakeOption InBakeMethod, + const bool bInRemoveOutputAfterBake, + const bool bInRecenterBakedActors, + const bool bInReplacePreviousBake, + const bool bInDeleteInstantiatedAssetOnCompletionOrFailure) +{ + UHoudiniPublicAPIProcessHDANode* Node = NewObject(); + + Node->HoudiniAsset = InHoudiniAsset; + Node->InstantiateAt = InInstantiateAt; + Node->Parameters = InParameters; + Node->NodeInputs = InNodeInputs; + Node->ParameterInputs = InParameterInputs; + Node->WorldContextObject = InWorldContextObject; + Node->SpawnInLevelOverride = InSpawnInLevelOverride; + Node->bEnableAutoCook = bInEnableAutoCook; + Node->bEnableAutoBake = bInEnableAutoBake; + Node->BakeDirectoryPath = InBakeDirectoryPath; + Node->BakeMethod = InBakeMethod; + Node->bRemoveOutputAfterBake = bInRemoveOutputAfterBake; + Node->bRecenterBakedActors = bInRecenterBakedActors; + Node->bReplacePreviousBake = bInReplacePreviousBake; + Node->bDeleteInstantiatedAssetOnCompletionOrFailure = bInDeleteInstantiatedAssetOnCompletionOrFailure; + + return Node; +} + + +void +UHoudiniPublicAPIProcessHDANode::Activate() +{ + UHoudiniPublicAPI* API = UHoudiniPublicAPIBlueprintLib::GetAPI(); + if (!IsValid(API)) + { + HandleFailure(); + return; + } + + AssetWrapper = UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper(API); + if (!IsValid(AssetWrapper)) + { + HandleFailure(); + return; + } + + AssetWrapper->GetOnPreInstantiationDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation); + AssetWrapper->GetOnPostInstantiationDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation); + AssetWrapper->GetOnPostCookDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook); + AssetWrapper->GetOnPreProcessStateExitedDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreProcess); + AssetWrapper->GetOnPostProcessingDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostProcessing); + AssetWrapper->GetOnPostBakeDelegate().AddDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake); + + if (!API->InstantiateAssetWithExistingWrapper( + AssetWrapper, + HoudiniAsset, + InstantiateAt, + WorldContextObject, + SpawnInLevelOverride, + bEnableAutoCook, + bEnableAutoBake, + BakeDirectoryPath, + BakeMethod, + bRemoveOutputAfterBake, + bRecenterBakedActors, + bReplacePreviousBake)) + { + HandleFailure(); + return; + } +} + +void +UHoudiniPublicAPIProcessHDANode::UnbindDelegates() +{ + AssetWrapper->GetOnPreInstantiationDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation); + AssetWrapper->GetOnPostInstantiationDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation); + AssetWrapper->GetOnPostCookDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook); + AssetWrapper->GetOnPreProcessStateExitedDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePreProcess); + AssetWrapper->GetOnPostProcessingDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostProcessing); + AssetWrapper->GetOnPostBakeDelegate().RemoveDynamic(this, &UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake); +} + +void +UHoudiniPublicAPIProcessHDANode::HandleFailure() +{ + if (Failed.IsBound()) + Failed.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + UnbindDelegates(); + + RemoveFromRoot(); + + if (bDeleteInstantiatedAssetOnCompletionOrFailure && IsValid(AssetWrapper)) + AssetWrapper->DeleteInstantiatedAsset(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandleComplete() +{ + if (Completed.IsBound()) + Completed.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + UnbindDelegates(); + + RemoveFromRoot(); + + if (bDeleteInstantiatedAssetOnCompletionOrFailure && IsValid(AssetWrapper)) + AssetWrapper->DeleteInstantiatedAsset(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePreInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + // Set any parameters specified when the node was created + if (Parameters.Num() > 0 && IsValid(AssetWrapper)) + { + AssetWrapper->SetParameterTuples(Parameters); + } + + if (PreInstantiation.IsBound()) + PreInstantiation.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + // Set any inputs specified when the node was created + if (IsValid(AssetWrapper)) + { + if (NodeInputs.Num() > 0) + { + AssetWrapper->SetInputsAtIndices(NodeInputs); + } + if (ParameterInputs.Num() > 0) + { + AssetWrapper->SetInputParameters(ParameterInputs); + } + + // // Set any parameters specified when the node was created + // if (Parameters.Num() > 0) + // { + // AssetWrapper->SetParameterTuples(Parameters); + // } + } + + if (PostInstantiation.IsBound()) + PostInstantiation.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + if (!bEnableAutoCook) + HandleComplete(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostAutoCook(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInCookSuccess) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + bCookSuccess = bInCookSuccess; + + if (PostAutoCook.IsBound()) + PostAutoCook.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePreProcess(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + if (PreProcess.IsBound()) + PreProcess.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostProcessing(UHoudiniPublicAPIAssetWrapper* InAssetWrapper) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + if (PostProcessing.IsBound()) + PostProcessing.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + if (!bEnableAutoBake) + HandleComplete(); +} + +void +UHoudiniPublicAPIProcessHDANode::HandlePostAutoBake(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInBakeSuccess) +{ + if (InAssetWrapper != AssetWrapper) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniPublicAPIProcessHDANode] Received delegate event from unexpected asset wrapper (%s vs %s)!"), + IsValid(AssetWrapper) ? *(AssetWrapper->GetName()) : TEXT(""), + IsValid(InAssetWrapper) ? *(InAssetWrapper->GetName()) : TEXT("")); + return; + } + + bBakeSuccess = bInBakeSuccess; + + if (PostAutoBake.IsBound()) + PostAutoBake.Broadcast(AssetWrapper, bCookSuccess, bBakeSuccess); + + HandleComplete(); +} diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp index eb20ab799..2520b6b90 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.cpp @@ -1,324 +1,324 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include "HoudiniRuntimeSettingsDetails.h" - -#include "HoudiniApi.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniEngine.h" -#include "HoudiniEngineUtils.h" -#include "HoudiniEngineRuntimeUtils.h" - -#include "HAPI/HAPI_Version.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "Internationalization/Internationalization.h" -#include "DetailLayoutBuilder.h" -#include "DetailWidgetRow.h" -#include "Widgets/Input/SNumericEntryBox.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -TSharedRef< IDetailCustomization > -FHoudiniRuntimeSettingsDetails::MakeInstance() -{ - return MakeShareable(new FHoudiniRuntimeSettingsDetails); -} - -FHoudiniRuntimeSettingsDetails::FHoudiniRuntimeSettingsDetails() -{} - -FHoudiniRuntimeSettingsDetails::~FHoudiniRuntimeSettingsDetails() -{} - -void -FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) -{ - // Create basic categories. - DetailBuilder.EditCategory("Session", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Instantiating", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Cooking", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Parameters", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Static Mesh", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("PDG Settings", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("Legacy", FText::GetEmpty(), ECategoryPriority::Important); - DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); - - // Create Plugin Information category. - { - static const FName InformationCategory = TEXT("Plugin Information"); - IDetailCategoryBuilder & InformationCategoryBuilder = DetailBuilder.EditCategory(InformationCategory); - - // Add built Houdini version. - CreateHoudiniEntry( - LOCTEXT("HInformationBuilt", "Built against Houdini"), - InformationCategoryBuilder, HAPI_VERSION_HOUDINI_MAJOR, HAPI_VERSION_HOUDINI_MINOR, - HAPI_VERSION_HOUDINI_BUILD, HAPI_VERSION_HOUDINI_PATCH); - - // Add built against Houdini Engine version. - CreateHoudiniEngineEntry( - LOCTEXT("HEngineInformationBuilt", "Built against Houdini Engine"), - InformationCategoryBuilder, HAPI_VERSION_HOUDINI_ENGINE_MAJOR, - HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API); - - // Add running against Houdini version. - { - int32 RunningMajor = 0; - int32 RunningMinor = 0; - int32 RunningBuild = 0; - int32 RunningPatch = 0; - - if (FHoudiniApi::IsHAPIInitialized()) - { - const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); - // Retrieve version numbers for running Houdini. - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MAJOR, &RunningMajor); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MINOR, &RunningMinor); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_BUILD, &RunningBuild); - FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_PATCH, &RunningPatch); - } - - CreateHoudiniEntry( - LOCTEXT("HInformationRunning", "Running against Houdini"), - InformationCategoryBuilder, RunningMajor, RunningMinor, RunningBuild, RunningPatch); - } - - // Add running against Houdini Engine version. - { - int32 RunningEngineMajor = 0; - int32 RunningEngineMinor = 0; - int32 RunningEngineApi = 0; - - if (FHoudiniApi::IsHAPIInitialized()) - { - 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); - } - - CreateHoudiniEngineEntry( - LOCTEXT("HEngineInformationRunning", "Running against Houdini Engine"), - InformationCategoryBuilder, RunningEngineMajor, RunningEngineMinor, RunningEngineApi); - } - - // Add path of libHAPI. - { - FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); - if (LibHAPILocation.IsEmpty()) - LibHAPILocation = TEXT("Not Found"); - - CreateHAPILibraryPathEntry(LibHAPILocation, InformationCategoryBuilder); - } - - // Add licensing info. - { - FString HAPILicenseType = TEXT(""); - if (!FHoudiniEngineUtils::GetLicenseType(HAPILicenseType)) - HAPILicenseType = TEXT("Unknown"); - - CreateHAPILicenseEntry(HAPILicenseType, InformationCategoryBuilder); - } - } -} - -void -FHoudiniRuntimeSettingsDetails::CreateHoudiniEntry( - const FText & EntryName, - IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, - int32 VersionPatch) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(EntryName) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMajor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMinor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionBuild) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionPatch) - ]; - NumericEntryBox->SetEnabled(false); - } - - Row.ValueWidget.Widget = HorizontalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHoudiniEngineEntry( - const FText & EntryName, - IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionApi) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(EntryName) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMajor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionMinor) - ]; - NumericEntryBox->SetEnabled(false); - } - - { - TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); - HorizontalBox->AddSlot().Padding(0, 0, 5, 0) - [ - SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) - .AllowSpin(false) - .Font(IDetailLayoutBuilder::GetDetailFont()) - .Value(VersionApi) - ]; - NumericEntryBox->SetEnabled(false); - } - - Row.ValueWidget.Widget = HorizontalBox; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHAPILibraryPathEntry( - const FString & LibHAPIPath, - IDetailCategoryBuilder & DetailCategoryBuilder) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - FString LibHAPIName = FString::Printf(TEXT("Location of %s"), *FHoudiniEngineRuntimeUtils::GetLibHAPIName()); - - Row.NameWidget.Widget = - SNew(STextBlock) - .Text(FText::FromString(LibHAPIName)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef TextBlock = - SNew(STextBlock) - .Text(FText::FromString(LibHAPIPath)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TextBlock->SetEnabled(false); - Row.ValueWidget.Widget = TextBlock; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - -void -FHoudiniRuntimeSettingsDetails::CreateHAPILicenseEntry( - const FString & LibHAPILicense, - IDetailCategoryBuilder & DetailCategoryBuilder) -{ - FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); - - FString LibHAPILicenseTypeText = TEXT("Acquired License Type"); - - Row.NameWidget.Widget = SNew(STextBlock) - .Text(FText::FromString(LibHAPILicenseTypeText)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TSharedRef TextBlock = SNew(STextBlock) - .Text(FText::FromString(LibHAPILicense)) - .Font(IDetailLayoutBuilder::GetDetailFont()); - - TextBlock->SetEnabled(false); - Row.ValueWidget.Widget = TextBlock; - Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "HoudiniRuntimeSettingsDetails.h" + +#include "HoudiniApi.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniEngine.h" +#include "HoudiniEngineUtils.h" +#include "HoudiniEngineRuntimeUtils.h" + +#include "HAPI/HAPI_Version.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "Internationalization/Internationalization.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "Widgets/Input/SNumericEntryBox.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +TSharedRef< IDetailCustomization > +FHoudiniRuntimeSettingsDetails::MakeInstance() +{ + return MakeShareable(new FHoudiniRuntimeSettingsDetails); +} + +FHoudiniRuntimeSettingsDetails::FHoudiniRuntimeSettingsDetails() +{} + +FHoudiniRuntimeSettingsDetails::~FHoudiniRuntimeSettingsDetails() +{} + +void +FHoudiniRuntimeSettingsDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) +{ + // Create basic categories. + DetailBuilder.EditCategory("Session", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Instantiating", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Cooking", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Parameters", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("CollisionGeneration", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeometryMarshalling", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeometryScalingAndImport", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Static Mesh", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("GeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("StaticMeshBuildSettings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("PDG Settings", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("Legacy", FText::GetEmpty(), ECategoryPriority::Important); + DetailBuilder.EditCategory("HoudiniLocation", FText::GetEmpty(), ECategoryPriority::Important); + + // Create Plugin Information category. + { + static const FName InformationCategory = TEXT("Plugin Information"); + IDetailCategoryBuilder & InformationCategoryBuilder = DetailBuilder.EditCategory(InformationCategory); + + // Add built Houdini version. + CreateHoudiniEntry( + LOCTEXT("HInformationBuilt", "Built against Houdini"), + InformationCategoryBuilder, HAPI_VERSION_HOUDINI_MAJOR, HAPI_VERSION_HOUDINI_MINOR, + HAPI_VERSION_HOUDINI_BUILD, HAPI_VERSION_HOUDINI_PATCH); + + // Add built against Houdini Engine version. + CreateHoudiniEngineEntry( + LOCTEXT("HEngineInformationBuilt", "Built against Houdini Engine"), + InformationCategoryBuilder, HAPI_VERSION_HOUDINI_ENGINE_MAJOR, + HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API); + + // Add running against Houdini version. + { + int32 RunningMajor = 0; + int32 RunningMinor = 0; + int32 RunningBuild = 0; + int32 RunningPatch = 0; + + if (FHoudiniApi::IsHAPIInitialized()) + { + const HAPI_Session * Session = FHoudiniEngine::Get().GetSession(); + // Retrieve version numbers for running Houdini. + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MAJOR, &RunningMajor); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_MINOR, &RunningMinor); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_BUILD, &RunningBuild); + FHoudiniApi::GetEnvInt(HAPI_ENVINT_VERSION_HOUDINI_PATCH, &RunningPatch); + } + + CreateHoudiniEntry( + LOCTEXT("HInformationRunning", "Running against Houdini"), + InformationCategoryBuilder, RunningMajor, RunningMinor, RunningBuild, RunningPatch); + } + + // Add running against Houdini Engine version. + { + int32 RunningEngineMajor = 0; + int32 RunningEngineMinor = 0; + int32 RunningEngineApi = 0; + + if (FHoudiniApi::IsHAPIInitialized()) + { + 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); + } + + CreateHoudiniEngineEntry( + LOCTEXT("HEngineInformationRunning", "Running against Houdini Engine"), + InformationCategoryBuilder, RunningEngineMajor, RunningEngineMinor, RunningEngineApi); + } + + // Add path of libHAPI. + { + FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation(); + if (LibHAPILocation.IsEmpty()) + LibHAPILocation = TEXT("Not Found"); + + CreateHAPILibraryPathEntry(LibHAPILocation, InformationCategoryBuilder); + } + + // Add licensing info. + { + FString HAPILicenseType = TEXT(""); + if (!FHoudiniEngineUtils::GetLicenseType(HAPILicenseType)) + HAPILicenseType = TEXT("Unknown"); + + CreateHAPILicenseEntry(HAPILicenseType, InformationCategoryBuilder); + } + } +} + +void +FHoudiniRuntimeSettingsDetails::CreateHoudiniEntry( + const FText & EntryName, + IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, + int32 VersionPatch) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(EntryName) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMajor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMinor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionBuild) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionPatch) + ]; + NumericEntryBox->SetEnabled(false); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHoudiniEngineEntry( + const FText & EntryName, + IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionApi) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(EntryName) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef< SHorizontalBox > HorizontalBox = SNew(SHorizontalBox); + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMajor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionMinor) + ]; + NumericEntryBox->SetEnabled(false); + } + + { + TSharedRef< SNumericEntryBox< int32 > > NumericEntryBox = SNew(SNumericEntryBox< int32 >); + HorizontalBox->AddSlot().Padding(0, 0, 5, 0) + [ + SAssignNew(NumericEntryBox, SNumericEntryBox< int32 >) + .AllowSpin(false) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .Value(VersionApi) + ]; + NumericEntryBox->SetEnabled(false); + } + + Row.ValueWidget.Widget = HorizontalBox; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHAPILibraryPathEntry( + const FString & LibHAPIPath, + IDetailCategoryBuilder & DetailCategoryBuilder) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + FString LibHAPIName = FString::Printf(TEXT("Location of %s"), *FHoudiniEngineRuntimeUtils::GetLibHAPIName()); + + Row.NameWidget.Widget = + SNew(STextBlock) + .Text(FText::FromString(LibHAPIName)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef TextBlock = + SNew(STextBlock) + .Text(FText::FromString(LibHAPIPath)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TextBlock->SetEnabled(false); + Row.ValueWidget.Widget = TextBlock; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + +void +FHoudiniRuntimeSettingsDetails::CreateHAPILicenseEntry( + const FString & LibHAPILicense, + IDetailCategoryBuilder & DetailCategoryBuilder) +{ + FDetailWidgetRow & Row = DetailCategoryBuilder.AddCustomRow(FText::GetEmpty()); + + FString LibHAPILicenseTypeText = TEXT("Acquired License Type"); + + Row.NameWidget.Widget = SNew(STextBlock) + .Text(FText::FromString(LibHAPILicenseTypeText)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TSharedRef TextBlock = SNew(STextBlock) + .Text(FText::FromString(LibHAPILicense)) + .Font(IDetailLayoutBuilder::GetDetailFont()); + + TextBlock->SetEnabled(false); + Row.ValueWidget.Widget = TextBlock; + Row.ValueWidget.MinDesiredWidth(HAPI_UNREAL_DESIRED_SETTINGS_ROW_VALUE_WIDGET_WIDTH); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h index b3aab58cc..1123bbdcf 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniRuntimeSettingsDetails.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "DetailCategoryBuilder.h" -#include "IDetailCustomization.h" - -class FHoudiniRuntimeSettingsDetails : public IDetailCustomization -{ -public: - - /** Constructor. **/ - FHoudiniRuntimeSettingsDetails(); - - /** Destructor. **/ - virtual ~FHoudiniRuntimeSettingsDetails(); - - /** IDetailCustomization methods. **/ -public: - - virtual void CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) override; - -public: - - /** Create an instance of this detail layout class. **/ - static TSharedRef< IDetailCustomization > MakeInstance(); - -protected: - - /** Used to create Houdini version entry. **/ - void CreateHoudiniEntry( - const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, int32 VersionPatch); - - /** Used to create Houdini Engine version entry. **/ - void CreateHoudiniEngineEntry( - const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, - int32 VersionMajor, int32 VersionMinor, int32 VersionApi); - - /** Used to create libHAPI dynamic library path entry. **/ - void CreateHAPILibraryPathEntry( - const FString & LibHAPIPath, IDetailCategoryBuilder & DetailCategoryBuilder); - - /** Used to create libHAPI license information entry. **/ - void CreateHAPILicenseEntry( - const FString & LibHAPILicense, IDetailCategoryBuilder & DetailCategoryBuilder); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "DetailCategoryBuilder.h" +#include "IDetailCustomization.h" + +class FHoudiniRuntimeSettingsDetails : public IDetailCustomization +{ +public: + + /** Constructor. **/ + FHoudiniRuntimeSettingsDetails(); + + /** Destructor. **/ + virtual ~FHoudiniRuntimeSettingsDetails(); + + /** IDetailCustomization methods. **/ +public: + + virtual void CustomizeDetails(IDetailLayoutBuilder & DetailBuilder) override; + +public: + + /** Create an instance of this detail layout class. **/ + static TSharedRef< IDetailCustomization > MakeInstance(); + +protected: + + /** Used to create Houdini version entry. **/ + void CreateHoudiniEntry( + const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionBuild, int32 VersionPatch); + + /** Used to create Houdini Engine version entry. **/ + void CreateHoudiniEngineEntry( + const FText & EntryName, IDetailCategoryBuilder & DetailCategoryBuilder, + int32 VersionMajor, int32 VersionMinor, int32 VersionApi); + + /** Used to create libHAPI dynamic library path entry. **/ + void CreateHAPILibraryPathEntry( + const FString & LibHAPIPath, IDetailCategoryBuilder & DetailCategoryBuilder); + + /** Used to create libHAPI license information entry. **/ + void CreateHAPILicenseEntry( + const FString & LibHAPILicense, IDetailCategoryBuilder & DetailCategoryBuilder); +}; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp index 5a0ddd942..e159cb270 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp @@ -1,1022 +1,1022 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineComponentVisualizer.h" - -#include "ActorEditorUtils.h" -#include "HoudiniEngineEditor.h" -#include "HoudiniEngineEditorPrivatePCH.h" -#include "HoudiniApi.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInputObject.h" -#include "HoudiniInput.h" -#include "HoudiniEngineStyle.h" -#include "HoudiniEngineUtils.h" - -#include "Editor/UnrealEdEngine.h" -#include "UnrealEdGlobals.h" -#include "ComponentVisualizerManager.h" - -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "ScopedTransaction.h" -#include "EditorViewportClient.h" -#include "Engine/Selection.h" -#include "HModel.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_HIT_PROXY(HHoudiniSplineVisProxy, HComponentVisProxy); -IMPLEMENT_HIT_PROXY(HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy); -IMPLEMENT_HIT_PROXY(HHoudiniSplineCurveSegmentVisProxy, HHoudiniSplineVisProxy); - -FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() - : TCommands< FHoudiniSplineComponentVisualizerCommands >( - "HoudiniSplineComponentVisualizer", - LOCTEXT("HoudiniSplineComponentVisualizer", "Houdini Spline Component Visualizer"), - NAME_None, - FEditorStyle::GetStyleSetName()) -{} - -void -FHoudiniSplineComponentVisualizerCommands::RegisterCommands() -{ - UI_COMMAND( - CommandAddControlPoint, "Add Control Point", "Add control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND( - CommandDuplicateControlPoint, "Duplicate Control Point", "Duplicate control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND( - CommandDeleteControlPoint, "Delete Control Point", "delete control points.", - EUserInterfaceActionType::Button, FInputChord(EKeys::Delete)); - - UI_COMMAND(CommandDeselectAllControlPoints, "Deselect All", "Deselect all control points.", - EUserInterfaceActionType::Button, FInputChord()); - - UI_COMMAND(CommandInsertControlPoint, "Insert Control Point", "Insert a control point on curve.", - EUserInterfaceActionType::Button, FInputChord()); -} - - -FHoudiniSplineComponentVisualizer::FHoudiniSplineComponentVisualizer() - :FComponentVisualizer() - ,bAllowDuplication(false) - ,EditedCurveSegmentIndex(-1) - ,CachedRotation(FQuat::Identity) - ,CachedScale3D(FVector::OneVector) - ,bMovingPoints(false) - ,bInsertingOnCurveControlPoints(false) - ,bRecordingMovingPoints(false) -{ - FHoudiniSplineComponentVisualizerCommands::Register(); - VisualizerActions = MakeShareable(new FUICommandList); -} - -void -FHoudiniSplineComponentVisualizer::OnRegister() -{ - HOUDINI_LOG_MESSAGE(TEXT("Houdini Spline Component Visualizer Registered!")); - const auto & Commands = FHoudiniSplineComponentVisualizerCommands::Get(); - - VisualizerActions->MapAction( - Commands.CommandAddControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnAddControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsAddControlPointValid)); - - VisualizerActions->MapAction( - Commands.CommandDuplicateControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid)); - - VisualizerActions->MapAction( - Commands.CommandDeleteControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeleteControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid)); - - VisualizerActions->MapAction(Commands.CommandDeselectAllControlPoints, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid)); - - VisualizerActions->MapAction(Commands.CommandInsertControlPoint, - FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnInsertControlPoint), - FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsInsertControlPointValid)); -} - - -void -FHoudiniSplineComponentVisualizer::DrawVisualization( - const UActorComponent * Component, - const FSceneView * View, - FPrimitiveDrawInterface * PDI) -{ - const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >(Component); - - if (!IsValid(HoudiniSplineComponent) - || !PDI - || !HoudiniSplineComponent->IsVisible() - || !HoudiniSplineComponent->IsHoudiniSplineVisible()) - return; - - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - - // Note: Undo a transaction clears the active visualizer in ComponnetVisMangaer, which is private to Visualizer manager. - // HandleProxyForComponentVis() sets the active visualizer. So the selection will be lost after undo. - - // A Way to bypass this annoying UE4 implementation: - // If the drawing spline is the one being edited and an undo just happened, - // force to trigger a 'bubble' hit proxy to re-activate the visualizer. - if (HoudiniSplineComponent == EditedHoudiniSplineComponent && EditedHoudiniSplineComponent->bPostUndo) - { - EditedHoudiniSplineComponent->bPostUndo = false; - - FEditorViewportClient * FoundViewportClient = FindViewportClient(EditedHoudiniSplineComponent, View); - HComponentVisProxy * BubbleComponentHitProxy = new HComponentVisProxy(EditedHoudiniSplineComponent); - - if (FoundViewportClient && BubbleComponentHitProxy) - { - FViewportClick BubbleClick(View, FoundViewportClient, FKey(), EInputEvent::IE_Axis, 0, 0); - GUnrealEd->ComponentVisManager.HandleProxyForComponentVis(FoundViewportClient, BubbleComponentHitProxy, BubbleClick); - } - } - - static const FColor ColorNormal = FColor(255.f, 255.f, 255.f); - static const FColor ColorNormalHandleFirst(172.f, 255.f, 172.f); - static const FColor ColorNormalHandleSecond(254.f, 216.f, 177.f); - - static const FColor ColorSelectedHandle(255.f, 0.f, 0.f); - static const FColor ColorSelectedHandleFirst(0.f, 192.f, 0.f); - static const FColor ColorSelectedHandleSecond(255.f, 159.f, 0.f); - - static const float SizeGrabHandleSelected = 15.f; - static const float SizeGrabHandleNormalLarge = 18.f; - static const float SizeGrabHandleNormalSmall = 12.f; - - FVector PreviousPosition; - - if (HoudiniSplineComponent) - { - const FTransform & HoudiniSplineComponentTransform = HoudiniSplineComponent->GetComponentTransform(); - - const TArray< FVector > & DisplayPoints = HoudiniSplineComponent->DisplayPoints; // not used yet - const TArray< FTransform > & CurvePoints = HoudiniSplineComponent->CurvePoints; - - // Draw display points (simply linearly connect the control points for temporary) - for (int32 Index = 0; Index < DisplayPoints.Num(); ++Index) - { - const FVector & CurrentPoint = DisplayPoints[Index]; - // Fix incorrect scale when actor has been scaled - //FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); - FVector CurrentPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint); - if (Index > 0) - { - // Add a hitproxy for the line segment - PDI->SetHitProxy(new HHoudiniSplineCurveSegmentVisProxy(HoudiniSplineComponent, Index)); - // Draw a line connecting the previous point and the current point - PDI->DrawLine(PreviousPosition, CurrentPosition, ColorNormal, SDPG_Foreground); - PDI->SetHitProxy(nullptr); - } - - PreviousPosition = CurrentPosition; - } - - // Draw control points (do not draw control points if the curve is an output) - if (!HoudiniSplineComponent->bIsOutputCurve) - { - for (int32 Index = 0; Index < CurvePoints.Num(); ++Index) - { - const FVector & ControlPoint = HoudiniSplineComponentTransform.TransformPosition(CurvePoints[Index].GetLocation()); - - HHoudiniSplineControlPointVisProxy * HitProxy = new HHoudiniSplineControlPointVisProxy(HoudiniSplineComponent, Index); - PDI->SetHitProxy(HitProxy); - - FColor DrawColor = ColorNormal; - float DrawSize = SizeGrabHandleNormalSmall; - - if (Index == 0) - { - DrawColor = ColorNormalHandleFirst; - DrawSize = SizeGrabHandleNormalLarge; - } - - if (Index == 1) - DrawColor = ColorNormalHandleSecond; - - // If this is an point that being editted - if (EditedHoudiniSplineComponent == HoudiniSplineComponent && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Contains(Index)) - { - if (Index == 0) - { - DrawColor = ColorSelectedHandleFirst; - } - - else if (Index == 1) - { - DrawColor = ColorSelectedHandleSecond; - DrawSize = SizeGrabHandleSelected; - } - - else - { - DrawColor = ColorSelectedHandle; - DrawSize = SizeGrabHandleSelected; - - } - } - - PDI->DrawPoint(ControlPoint, DrawColor, DrawSize, SDPG_Foreground); - PDI->SetHitProxy(nullptr); - } - - } - } -} - - -bool -FHoudiniSplineComponentVisualizer::VisProxyHandleClick( - FEditorViewportClient* InViewportClient, - HComponentVisProxy* VisProxy, - const FViewportClick& Click) -{ - if (!InViewportClient || !VisProxy || !VisProxy->Component.IsValid()) - return false; - - const UHoudiniSplineComponent * HoudiniSplineComponent = CastChecked< const UHoudiniSplineComponent >(VisProxy->Component.Get()); - - AActor* OldSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); - SplinePropertyPath = FComponentPropertyPath(HoudiniSplineComponent); - AActor* NewSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); - - if (!SplinePropertyPath.IsValid()) - { - SplinePropertyPath.Reset(); - return false; - } - - if (OldSplineOwningActor != NewSplineOwningActor) - { - // Reset selection state if we are selecting a different actor to the one previously selected - EditedCurveSegmentIndex = INDEX_NONE; - } - - // Note: This is for re-activating the component visualizer an undo. - // Return true if the hit proxy is a bubble (Neither HHoudiniSplineControlPointVisProxy nor HHoudiniSplineCurveSegmentVisProxy ) - // - if (!VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType()) && !VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) - return true; - - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - - EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); - - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - bool editingCurve = false; - - // If VisProxy is a HHoudiniSplineControlPointVisProxy - if (VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType())) - { - HHoudiniSplineControlPointVisProxy * ControlPointProxy = (HHoudiniSplineControlPointVisProxy*)VisProxy; - - if (!ControlPointProxy) - return editingCurve; - - editingCurve = true; - - // Clear the edited curve segment if a control point is clicked. - EditedCurveSegmentIndex = -1; - - if (Click.GetKey() != EKeys::LeftMouseButton) - return editingCurve; - - - if (InViewportClient->IsCtrlPressed()) - { - if (EditedControlPointsIndexes.Contains(ControlPointProxy->ControlPointIndex)) - { - EditedControlPointsIndexes.Remove(ControlPointProxy->ControlPointIndex); - } - else - { - EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); - } - } - else - { - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); - } - } - // VisProxy is a HHoudiniSplineCurveSegmentProxy - else if (VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) - { - //HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = Cast(VisProxy); - - HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = (HHoudiniSplineCurveSegmentVisProxy*)(VisProxy); - - if (!CurveSegmentProxy) - return false; - - editingCurve = true; - - if (Click.GetKey() == EKeys::LeftMouseButton && InViewportClient->IsAltPressed() && EditedHoudiniSplineComponent) - { - // Continuesly (Alt) inserting on-curve control points is only valid with Breakpoints mode, otherwise it has to be on linear curve type. - if (EditedHoudiniSplineComponent->CurveType != EHoudiniCurveType::Polygon && EditedHoudiniSplineComponent->CurveMethod != EHoudiniCurveMethod::Breakpoints) - return editingCurve; - - bInsertingOnCurveControlPoints = true; - - editingCurve = true; - EditedControlPointsIndexes.Empty(); - - EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; - int32 InsertedIndex = OnInsertControlPointWithoutUpdate(); - - if (InsertedIndex < 0) return false; - EditedControlPointsIndexes.Add(InsertedIndex); - - EditedCurveSegmentIndex = -1; - bInsertingOnCurveControlPoints = true; - - RefreshViewport(); - } - // Insert one on-curve control point. - else - { - EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; - return editingCurve; - } - } - - return editingCurve; -} - -bool -FHoudiniSplineComponentVisualizer::HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - if (Key == EKeys::Enter) - { - EditedHoudiniSplineComponent->MarkChanged(true); - - return true; - } - - bool bHandled = false; - - if (Key == EKeys::LeftMouseButton) - { - if (Event == IE_Pressed) - { - bMovingPoints = true; // Started moving points when the left mouse button is pressed - bAllowDuplication = true; - bRecordingMovingPoints = false; - } - - if (Event == IE_Released) - { - bMovingPoints = false; // Stopped moving points when the left mouse button is released - bAllowDuplication = false; - - if (bRecordingMovingPoints) - { - // Only mark the component as changed if a point was actually moved otherwise it will - // cook even if a point was selected. - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - } - - bRecordingMovingPoints = false; // allow recording pt moving again - } - } - - - if (Key == EKeys::Delete) - { - if (Event == IE_Pressed) return true; - - if (IsDeleteControlPointValid()) - { - OnDeleteControlPoint(); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - return true; - } - } - - - if (Event == IE_Pressed && VisualizerActions) - { - if (FSlateApplication::IsInitialized()) - bHandled = VisualizerActions->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), false); - } - - RefreshViewport(); - - return bHandled; -} - -void -FHoudiniSplineComponentVisualizer::EndEditing() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - // Clear edited spline if the EndEditing() function is not called from postUndo - if (!EditedHoudiniSplineComponent->bPostUndo) - { - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); - - EditedHoudiniSplineComponent = nullptr; - EditedCurveSegmentIndex = -1; - } - - //RefreshViewport(); -} - -bool -FHoudiniSplineComponentVisualizer::GetWidgetLocation( - const FEditorViewportClient* ViewportClient, - FVector& OutLocation) const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - const TArray& CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - // Set the widget location to the center of mass of the selected control points - int32 Sum = 0; - FVector CenterLocation = FVector::ZeroVector; - for (int32 EditedIdx = 0; EditedIdx < EditedControlPointsIndexes.Num(); EditedIdx++) - { - if (!CurvePoints.IsValidIndex(EditedIdx)) - continue; - - CenterLocation += CurvePoints[EditedControlPointsIndexes[EditedIdx]].GetLocation(); - Sum++; - } - - if(Sum > 0) - CenterLocation /= Sum; - - OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(CenterLocation); - - return true; -} - -bool -FHoudiniSplineComponentVisualizer::IsVisualizingArchetype() const -{ - UHoudiniSplineComponent* SplineComp = GetEditedHoudiniSplineComponent(); - return (SplineComp && SplineComp->GetOwner() && FActorEditorUtils::IsAPreviewOrInactiveActor(SplineComp->GetOwner())); -} - -bool -FHoudiniSplineComponentVisualizer::HandleInputDelta( - FEditorViewportClient* ViewportClient, - FViewport* Viewport, - FVector& DeltaTranslate, - FRotator& DeltaRotate, - FVector& DeltaScale) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!ViewportClient || !EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - if (ViewportClient->IsAltPressed() && bAllowDuplication) - { - OnDuplicateControlPoint(); - bAllowDuplication = false; - } - else - { - if (!bRecordingMovingPoints) - { - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentMovingPointsTransaction", "Houdini Spline Component: Moving curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - - EditedHoudiniSplineComponent->Modify(); - - bRecordingMovingPoints = true; - } - } - - - TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - const FTransform & HoudiniSplineComponentTransform = EditedHoudiniSplineComponent->GetComponentTransform(); - - for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i ) - { - - FTransform CurrentPoint = EditedHoudiniSplineComponent->CurvePoints[EditedControlPointsIndexes[i]]; - - if (!DeltaTranslate.IsZero()) - { - FVector OldWorldPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint.GetLocation()); - FVector NewWorldPosition = OldWorldPosition + DeltaTranslate; - FVector NewLocalPosition = HoudiniSplineComponentTransform.InverseTransformPosition(NewWorldPosition); - CurrentPoint.SetLocation( NewLocalPosition ); - } - - if (!DeltaRotate.IsZero()) - { - FQuat OldWorldRotation = HoudiniSplineComponentTransform.GetRotation() * CurrentPoint.GetRotation(); - FQuat NewWorldRotation = DeltaRotate.Quaternion() * OldWorldRotation; - FQuat NewLocalRotation = HoudiniSplineComponentTransform.GetRotation().Inverse() * NewWorldRotation; - CurrentPoint.SetRotation(NewLocalRotation); - } - - if (!DeltaScale.IsZero()) - { - FVector NewScale = CurrentPoint.GetScale3D() * (FVector(1.f, 1.f, 1.f) + DeltaScale); - CurrentPoint.SetScale3D(NewScale); - } - - - EditedHoudiniSplineComponent->EditPointAtindex(CurrentPoint, EditedControlPointsIndexes[i]); - } - - RefreshViewport(); - - return true; -} - -TSharedPtr -FHoudiniSplineComponentVisualizer::GenerateContextMenu() const -{ - FHoudiniEngineEditor& HoudiniEngineEditor = FHoudiniEngineEditor::Get(); - FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); - - FMenuBuilder MenuBuilder(true, VisualizerActions); - MenuBuilder.BeginSection("Houdini Spline actions"); - - // Create the context menu section - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - { - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, - NAME_None, TAttribute(), TAttribute(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDuplicateControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDeleteControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandDeselectAllControlPoints, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - - MenuBuilder.AddMenuEntry( - FHoudiniSplineComponentVisualizerCommands::Get().CommandInsertControlPoint, - NAME_None, TAttribute< FText >(), TAttribute< FText >(), - FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); - } - - MenuBuilder.EndSection(); - TSharedPtr MenuWidget = MenuBuilder.MakeWidget(); - return MenuWidget; -} - -// Used by alt-pressed on-curve control port insertion. -// We don't want it to be cooked before finishing editing. -// * Need to call WaitForHoudiniInputUpdate() after done. -int32 -FHoudiniSplineComponentVisualizer::OnInsertControlPointWithoutUpdate() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return -1; - - TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - TArray & DisplayPoints = EditedHoudiniSplineComponent->DisplayPoints; - - if (EditedCurveSegmentIndex >= DisplayPoints.Num()) - return -1; - - // ... // - int InsertAfterIndex = 0; - - TArray & DisplayPointIndexDivider = EditedHoudiniSplineComponent->DisplayPointIndexDivider; - for (int itr = 0; itr < DisplayPointIndexDivider.Num(); ++itr) - { - if (DisplayPointIndexDivider[itr] >= EditedCurveSegmentIndex) - { - InsertAfterIndex = itr; - break; - } - } - // ... // - - if (InsertAfterIndex >= CurvePoints.Num()) return -1; - - FTransform NewPoint = CurvePoints[InsertAfterIndex]; - NewPoint.SetLocation(DisplayPoints[EditedCurveSegmentIndex]); - // To Do: Should interpolate the rotation and scale as well here. - // ... - - // Insert new control point on curve, and add it to selected CP. - int32 NewPointIndex = AddControlPointAfter(NewPoint, InsertAfterIndex); - - // Don't have to reconstruct the index divider each time. - //EditedHoudiniSplineComponent->Construct(EditedHoudiniSplineComponent->DisplayPoints); - EditedHoudiniSplineComponent->DisplayPointIndexDivider.Insert(EditedCurveSegmentIndex, InsertAfterIndex); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - return NewPointIndex; -} - -void -FHoudiniSplineComponentVisualizer::OnInsertControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - int32 NewPointIndex = OnInsertControlPointWithoutUpdate(); - - if (NewPointIndex < 0) return; - - - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Add(NewPointIndex); - - RefreshViewport(); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); -} - -bool -FHoudiniSplineComponentVisualizer::IsInsertControlPointValid() const -{ - return EditedCurveSegmentIndex >= 0; -} - -void -FHoudiniSplineComponentVisualizer::OnAddControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentInsertingPointsTransaction", "Houdini Spline Component: Inserting curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - - EditedHoudiniSplineComponent->Modify(); - - EditedControlPointsIndexes.Sort(); - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - TArray tNewSelectedPoints; - - if (EditedControlPointsIndexes.Num() == 1) - { - FTransform Point = CurvePoints[EditedControlPointsIndexes[0]]; - FTransform NewTransform = FTransform::Identity; - FVector Location = Point.GetLocation(); - //FQuat Rotation = Point.GetRotation(); - //FVector Scale = Point.GetScale3D(); - - NewTransform.SetLocation(Location + 1.f); - //NewTransform.SetRotation(Rotation); - //NewTransform.SetScale3D(Scale); - - - - int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[0]); - tNewSelectedPoints.Add(NewPointIndex); - } - else - { - int IndexIncrement = 0; - int CurrentPointIndex, LastPointIndex; - FTransform CurrentPoint, LastPoint; - - for (int32 n = 0; n < EditedControlPointsIndexes.Num(); ++n) - { - // Insert a new point between each adjacent pair of points - if (n > 0) - { - CurrentPointIndex = EditedControlPointsIndexes[n]; - LastPointIndex = EditedControlPointsIndexes[n - 1]; - CurrentPoint = CurvePoints[CurrentPointIndex + IndexIncrement]; - LastPoint = CurvePoints[LastPointIndex + IndexIncrement]; - - // Insert a point in the middle of LastPoint and CurrentPoint - FVector NewPointLocation = LastPoint.GetLocation() + (CurrentPoint.GetLocation() - LastPoint.GetLocation()) / 2.f; - FVector NewPointScale = LastPoint.GetScale3D() + (CurrentPoint.GetScale3D() - LastPoint.GetScale3D()) / 2.f; - FQuat NewPointRotation = FQuat::Slerp(LastPoint.GetRotation(), CurrentPoint.GetRotation(), .5f); - - FTransform NewTransform = FTransform::Identity; - NewTransform.SetLocation(NewPointLocation); - NewTransform.SetScale3D(NewPointScale); - NewTransform.SetRotation(NewPointRotation); - - int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[n - 1] + IndexIncrement); - tNewSelectedPoints.Add(NewPointIndex); - - - IndexIncrement += 1; - } - } - } - - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes = tNewSelectedPoints; - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - RefreshViewport(); -} - - -bool -FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - return EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill() && - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; -} - -void -FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentDeletingPointsTransaction", "Houdini Spline Component: Deleting curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - EditedHoudiniSplineComponent->Modify(); - - EditedControlPointsIndexes.Sort(); - - int32 SelectedIndexAfterDelete = EditedControlPointsIndexes[0] - 1; - SelectedIndexAfterDelete = FMath::Max(SelectedIndexAfterDelete, 0); - - for (int32 n = EditedControlPointsIndexes.Num() - 1; n >= 0; --n) - { - int32 RemoveIndex = EditedControlPointsIndexes[n]; - EditedHoudiniSplineComponent->RemovePointAtIndex(RemoveIndex); - - } - - EditedControlPointsIndexes.Empty(); - OnDeselectAllControlPoints(); - EditedControlPointsIndexes.Add(SelectedIndexAfterDelete); - - if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) - EditedHoudiniSplineComponent->MarkChanged(true); - - // Force refresh the viewport after deleting points to ensure the consistency of HitProxy - RefreshViewport(); - -} - -bool -FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return false; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return false; - - // We only allow the number of Control Points is at least 2 after delete - if (EditedHoudiniSplineComponent->GetCurvePointCount() - EditedControlPointsIndexes.Num() < 2) - return false; - - return true; -} - -void -FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return; - - TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; - - if (EditedControlPointsIndexes.Num() <= 0) - return; - - // Transaction for Undo/Redo - FScopedTransaction Transaction( - TEXT(HOUDINI_MODULE_RUNTIME), - LOCTEXT("HoudiniSplineComponentDuplicatingPointsTransaction", "Houdini Spline Component: Duplicating curve points."), - EditedHoudiniSplineComponent->GetOuter(), true); - EditedHoudiniSplineComponent->Modify(); - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - EditedControlPointsIndexes.Sort(); - - TArray tNewSelectedPoints; - int IncrementIndex = 0; - for (int n = 0; n < EditedControlPointsIndexes.Num(); ++n) - { - int32 IndexAfter = EditedControlPointsIndexes[n] + IncrementIndex; - FTransform CurrentPoint = CurvePoints[IndexAfter]; - if (IndexAfter == 0) - IndexAfter = -1; - int32 NewPointIndex = AddControlPointAfter(CurrentPoint, IndexAfter); - tNewSelectedPoints.Add(NewPointIndex); - IncrementIndex ++; - } - - EditedControlPointsIndexes.Empty(); - EditedControlPointsIndexes = tNewSelectedPoints; - - EditedHoudiniSplineComponent->MarkModified(true); - - RefreshViewport(); -} - -bool -FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if(!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill() - || EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() == 0) - return false; - - return true; -} - -void -FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints() -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); -} - -bool -FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid() const -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) - return EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; - - return false; -} - -int32 -FHoudiniSplineComponentVisualizer::AddControlPointAfter( - const FTransform & NewPoint, - const int32 & nIndex) -{ - UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) - return nIndex; - - const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; - - int32 NewControlPointIndex = nIndex + 1; - - if (NewControlPointIndex == CurvePoints.Num()) - EditedHoudiniSplineComponent->AppendPoint(NewPoint); - else - EditedHoudiniSplineComponent->InsertPointAtIndex(NewPoint, NewControlPointIndex); - - // Return the index of the inserted control point - return NewControlPointIndex; -} - -void -FHoudiniSplineComponentVisualizer::RefreshViewport() -{ - if (GEditor) - GEditor->RedrawLevelEditingViewports(true); -} - -// Find the EditorViewportClient of the viewport where the Houdini Spline Component lives in -FEditorViewportClient * -FHoudiniSplineComponentVisualizer::FindViewportClient( - const UHoudiniSplineComponent * InHoudiniSplineComponent, - const FSceneView * View) -{ - if (!View || !InHoudiniSplineComponent) - return nullptr; - - UWorld * World = InHoudiniSplineComponent->GetWorld(); - uint32 ViewKey = View->GetViewKey(); - - const TArray & AllViewportClients = GUnrealEd->GetAllViewportClients(); - - for (auto & NextViewportClient : AllViewportClients) - { - if (!NextViewportClient) - continue; - - if (NextViewportClient->GetWorld() != World) - continue; - - // Found the viewport client which matches the unique key of the current scene view - if (NextViewportClient->ViewState.GetReference()->GetViewKey() == ViewKey) - return NextViewportClient; - } - - return nullptr; -} - -bool -FHoudiniSplineComponentVisualizer::IsCookOnCurveChanged(UHoudiniSplineComponent * InHoudiniSplineComponent) -{ - if (!InHoudiniSplineComponent) - return true; - - return InHoudiniSplineComponent->bCookOnCurveChanged; - - // UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - // UHoudiniInputObject * InputObject = Cast(EditedHoudiniSplineComponent->GetOuter()); - // if (!InputObject) - // return true; - // - // UHoudiniInput * Input = Cast(InputObject->GetOuter()); - // - // if (!Input) - // return true; - // - // return Input->GetCookOnCurveChange(); -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineComponentVisualizer.h" + +#include "ActorEditorUtils.h" +#include "HoudiniEngineEditor.h" +#include "HoudiniEngineEditorPrivatePCH.h" +#include "HoudiniApi.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInputObject.h" +#include "HoudiniInput.h" +#include "HoudiniEngineStyle.h" +#include "HoudiniEngineUtils.h" + +#include "Editor/UnrealEdEngine.h" +#include "UnrealEdGlobals.h" +#include "ComponentVisualizerManager.h" + +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "ScopedTransaction.h" +#include "EditorViewportClient.h" +#include "Engine/Selection.h" +#include "HModel.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_HIT_PROXY(HHoudiniSplineVisProxy, HComponentVisProxy); +IMPLEMENT_HIT_PROXY(HHoudiniSplineControlPointVisProxy, HHoudiniSplineVisProxy); +IMPLEMENT_HIT_PROXY(HHoudiniSplineCurveSegmentVisProxy, HHoudiniSplineVisProxy); + +FHoudiniSplineComponentVisualizerCommands::FHoudiniSplineComponentVisualizerCommands() + : TCommands< FHoudiniSplineComponentVisualizerCommands >( + "HoudiniSplineComponentVisualizer", + LOCTEXT("HoudiniSplineComponentVisualizer", "Houdini Spline Component Visualizer"), + NAME_None, + FEditorStyle::GetStyleSetName()) +{} + +void +FHoudiniSplineComponentVisualizerCommands::RegisterCommands() +{ + UI_COMMAND( + CommandAddControlPoint, "Add Control Point", "Add control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND( + CommandDuplicateControlPoint, "Duplicate Control Point", "Duplicate control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND( + CommandDeleteControlPoint, "Delete Control Point", "delete control points.", + EUserInterfaceActionType::Button, FInputChord(EKeys::Delete)); + + UI_COMMAND(CommandDeselectAllControlPoints, "Deselect All", "Deselect all control points.", + EUserInterfaceActionType::Button, FInputChord()); + + UI_COMMAND(CommandInsertControlPoint, "Insert Control Point", "Insert a control point on curve.", + EUserInterfaceActionType::Button, FInputChord()); +} + + +FHoudiniSplineComponentVisualizer::FHoudiniSplineComponentVisualizer() + :FComponentVisualizer() + ,bAllowDuplication(false) + ,EditedCurveSegmentIndex(-1) + ,CachedRotation(FQuat::Identity) + ,CachedScale3D(FVector::OneVector) + ,bMovingPoints(false) + ,bInsertingOnCurveControlPoints(false) + ,bRecordingMovingPoints(false) +{ + FHoudiniSplineComponentVisualizerCommands::Register(); + VisualizerActions = MakeShareable(new FUICommandList); +} + +void +FHoudiniSplineComponentVisualizer::OnRegister() +{ + HOUDINI_LOG_MESSAGE(TEXT("Houdini Spline Component Visualizer Registered!")); + const auto & Commands = FHoudiniSplineComponentVisualizerCommands::Get(); + + VisualizerActions->MapAction( + Commands.CommandAddControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnAddControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsAddControlPointValid)); + + VisualizerActions->MapAction( + Commands.CommandDuplicateControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid)); + + VisualizerActions->MapAction( + Commands.CommandDeleteControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeleteControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid)); + + VisualizerActions->MapAction(Commands.CommandDeselectAllControlPoints, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid)); + + VisualizerActions->MapAction(Commands.CommandInsertControlPoint, + FExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::OnInsertControlPoint), + FCanExecuteAction::CreateSP(this, &FHoudiniSplineComponentVisualizer::IsInsertControlPointValid)); +} + + +void +FHoudiniSplineComponentVisualizer::DrawVisualization( + const UActorComponent * Component, + const FSceneView * View, + FPrimitiveDrawInterface * PDI) +{ + const UHoudiniSplineComponent * HoudiniSplineComponent = Cast< const UHoudiniSplineComponent >(Component); + + if (!IsValid(HoudiniSplineComponent) + || !PDI + || !HoudiniSplineComponent->IsVisible() + || !HoudiniSplineComponent->IsHoudiniSplineVisible()) + return; + + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + + // Note: Undo a transaction clears the active visualizer in ComponnetVisMangaer, which is private to Visualizer manager. + // HandleProxyForComponentVis() sets the active visualizer. So the selection will be lost after undo. + + // A Way to bypass this annoying UE4 implementation: + // If the drawing spline is the one being edited and an undo just happened, + // force to trigger a 'bubble' hit proxy to re-activate the visualizer. + if (HoudiniSplineComponent == EditedHoudiniSplineComponent && EditedHoudiniSplineComponent->bPostUndo) + { + EditedHoudiniSplineComponent->bPostUndo = false; + + FEditorViewportClient * FoundViewportClient = FindViewportClient(EditedHoudiniSplineComponent, View); + HComponentVisProxy * BubbleComponentHitProxy = new HComponentVisProxy(EditedHoudiniSplineComponent); + + if (FoundViewportClient && BubbleComponentHitProxy) + { + FViewportClick BubbleClick(View, FoundViewportClient, FKey(), EInputEvent::IE_Axis, 0, 0); + GUnrealEd->ComponentVisManager.HandleProxyForComponentVis(FoundViewportClient, BubbleComponentHitProxy, BubbleClick); + } + } + + static const FColor ColorNormal = FColor(255.f, 255.f, 255.f); + static const FColor ColorNormalHandleFirst(172.f, 255.f, 172.f); + static const FColor ColorNormalHandleSecond(254.f, 216.f, 177.f); + + static const FColor ColorSelectedHandle(255.f, 0.f, 0.f); + static const FColor ColorSelectedHandleFirst(0.f, 192.f, 0.f); + static const FColor ColorSelectedHandleSecond(255.f, 159.f, 0.f); + + static const float SizeGrabHandleSelected = 15.f; + static const float SizeGrabHandleNormalLarge = 18.f; + static const float SizeGrabHandleNormalSmall = 12.f; + + FVector PreviousPosition; + + if (HoudiniSplineComponent) + { + const FTransform & HoudiniSplineComponentTransform = HoudiniSplineComponent->GetComponentTransform(); + + const TArray< FVector > & DisplayPoints = HoudiniSplineComponent->DisplayPoints; // not used yet + const TArray< FTransform > & CurvePoints = HoudiniSplineComponent->CurvePoints; + + // Draw display points (simply linearly connect the control points for temporary) + for (int32 Index = 0; Index < DisplayPoints.Num(); ++Index) + { + const FVector & CurrentPoint = DisplayPoints[Index]; + // Fix incorrect scale when actor has been scaled + //FVector CurrentPosition = CurrentPoint + HoudiniSplineComponentTransform.GetLocation(); + FVector CurrentPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint); + if (Index > 0) + { + // Add a hitproxy for the line segment + PDI->SetHitProxy(new HHoudiniSplineCurveSegmentVisProxy(HoudiniSplineComponent, Index)); + // Draw a line connecting the previous point and the current point + PDI->DrawLine(PreviousPosition, CurrentPosition, ColorNormal, SDPG_Foreground); + PDI->SetHitProxy(nullptr); + } + + PreviousPosition = CurrentPosition; + } + + // Draw control points (do not draw control points if the curve is an output) + if (!HoudiniSplineComponent->bIsOutputCurve) + { + for (int32 Index = 0; Index < CurvePoints.Num(); ++Index) + { + const FVector & ControlPoint = HoudiniSplineComponentTransform.TransformPosition(CurvePoints[Index].GetLocation()); + + HHoudiniSplineControlPointVisProxy * HitProxy = new HHoudiniSplineControlPointVisProxy(HoudiniSplineComponent, Index); + PDI->SetHitProxy(HitProxy); + + FColor DrawColor = ColorNormal; + float DrawSize = SizeGrabHandleNormalSmall; + + if (Index == 0) + { + DrawColor = ColorNormalHandleFirst; + DrawSize = SizeGrabHandleNormalLarge; + } + + if (Index == 1) + DrawColor = ColorNormalHandleSecond; + + // If this is an point that being editted + if (EditedHoudiniSplineComponent == HoudiniSplineComponent && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Contains(Index)) + { + if (Index == 0) + { + DrawColor = ColorSelectedHandleFirst; + } + + else if (Index == 1) + { + DrawColor = ColorSelectedHandleSecond; + DrawSize = SizeGrabHandleSelected; + } + + else + { + DrawColor = ColorSelectedHandle; + DrawSize = SizeGrabHandleSelected; + + } + } + + PDI->DrawPoint(ControlPoint, DrawColor, DrawSize, SDPG_Foreground); + PDI->SetHitProxy(nullptr); + } + + } + } +} + + +bool +FHoudiniSplineComponentVisualizer::VisProxyHandleClick( + FEditorViewportClient* InViewportClient, + HComponentVisProxy* VisProxy, + const FViewportClick& Click) +{ + if (!InViewportClient || !VisProxy || !VisProxy->Component.IsValid()) + return false; + + const UHoudiniSplineComponent * HoudiniSplineComponent = CastChecked< const UHoudiniSplineComponent >(VisProxy->Component.Get()); + + AActor* OldSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); + SplinePropertyPath = FComponentPropertyPath(HoudiniSplineComponent); + AActor* NewSplineOwningActor = SplinePropertyPath.GetParentOwningActor(); + + if (!SplinePropertyPath.IsValid()) + { + SplinePropertyPath.Reset(); + return false; + } + + if (OldSplineOwningActor != NewSplineOwningActor) + { + // Reset selection state if we are selecting a different actor to the one previously selected + EditedCurveSegmentIndex = INDEX_NONE; + } + + // Note: This is for re-activating the component visualizer an undo. + // Return true if the hit proxy is a bubble (Neither HHoudiniSplineControlPointVisProxy nor HHoudiniSplineCurveSegmentVisProxy ) + // + if (!VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType()) && !VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) + return true; + + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + + EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); + + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + bool editingCurve = false; + + // If VisProxy is a HHoudiniSplineControlPointVisProxy + if (VisProxy->IsA(HHoudiniSplineControlPointVisProxy::StaticGetType())) + { + HHoudiniSplineControlPointVisProxy * ControlPointProxy = (HHoudiniSplineControlPointVisProxy*)VisProxy; + + if (!ControlPointProxy) + return editingCurve; + + editingCurve = true; + + // Clear the edited curve segment if a control point is clicked. + EditedCurveSegmentIndex = -1; + + if (Click.GetKey() != EKeys::LeftMouseButton) + return editingCurve; + + + if (InViewportClient->IsCtrlPressed()) + { + if (EditedControlPointsIndexes.Contains(ControlPointProxy->ControlPointIndex)) + { + EditedControlPointsIndexes.Remove(ControlPointProxy->ControlPointIndex); + } + else + { + EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); + } + } + else + { + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes.Add(ControlPointProxy->ControlPointIndex); + } + } + // VisProxy is a HHoudiniSplineCurveSegmentProxy + else if (VisProxy->IsA(HHoudiniSplineCurveSegmentVisProxy::StaticGetType())) + { + //HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = Cast(VisProxy); + + HHoudiniSplineCurveSegmentVisProxy * CurveSegmentProxy = (HHoudiniSplineCurveSegmentVisProxy*)(VisProxy); + + if (!CurveSegmentProxy) + return false; + + editingCurve = true; + + if (Click.GetKey() == EKeys::LeftMouseButton && InViewportClient->IsAltPressed() && EditedHoudiniSplineComponent) + { + // Continuesly (Alt) inserting on-curve control points is only valid with Breakpoints mode, otherwise it has to be on linear curve type. + if (EditedHoudiniSplineComponent->CurveType != EHoudiniCurveType::Polygon && EditedHoudiniSplineComponent->CurveMethod != EHoudiniCurveMethod::Breakpoints) + return editingCurve; + + bInsertingOnCurveControlPoints = true; + + editingCurve = true; + EditedControlPointsIndexes.Empty(); + + EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; + int32 InsertedIndex = OnInsertControlPointWithoutUpdate(); + + if (InsertedIndex < 0) return false; + EditedControlPointsIndexes.Add(InsertedIndex); + + EditedCurveSegmentIndex = -1; + bInsertingOnCurveControlPoints = true; + + RefreshViewport(); + } + // Insert one on-curve control point. + else + { + EditedCurveSegmentIndex = CurveSegmentProxy->DisplayPointIndex; + return editingCurve; + } + } + + return editingCurve; +} + +bool +FHoudiniSplineComponentVisualizer::HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + if (Key == EKeys::Enter) + { + EditedHoudiniSplineComponent->MarkChanged(true); + + return true; + } + + bool bHandled = false; + + if (Key == EKeys::LeftMouseButton) + { + if (Event == IE_Pressed) + { + bMovingPoints = true; // Started moving points when the left mouse button is pressed + bAllowDuplication = true; + bRecordingMovingPoints = false; + } + + if (Event == IE_Released) + { + bMovingPoints = false; // Stopped moving points when the left mouse button is released + bAllowDuplication = false; + + if (bRecordingMovingPoints) + { + // Only mark the component as changed if a point was actually moved otherwise it will + // cook even if a point was selected. + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + } + + bRecordingMovingPoints = false; // allow recording pt moving again + } + } + + + if (Key == EKeys::Delete) + { + if (Event == IE_Pressed) return true; + + if (IsDeleteControlPointValid()) + { + OnDeleteControlPoint(); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + return true; + } + } + + + if (Event == IE_Pressed && VisualizerActions) + { + if (FSlateApplication::IsInitialized()) + bHandled = VisualizerActions->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), false); + } + + RefreshViewport(); + + return bHandled; +} + +void +FHoudiniSplineComponentVisualizer::EndEditing() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + // Clear edited spline if the EndEditing() function is not called from postUndo + if (!EditedHoudiniSplineComponent->bPostUndo) + { + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); + + EditedHoudiniSplineComponent = nullptr; + EditedCurveSegmentIndex = -1; + } + + //RefreshViewport(); +} + +bool +FHoudiniSplineComponentVisualizer::GetWidgetLocation( + const FEditorViewportClient* ViewportClient, + FVector& OutLocation) const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + const TArray& CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + // Set the widget location to the center of mass of the selected control points + int32 Sum = 0; + FVector CenterLocation = FVector::ZeroVector; + for (int32 EditedIdx = 0; EditedIdx < EditedControlPointsIndexes.Num(); EditedIdx++) + { + if (!CurvePoints.IsValidIndex(EditedIdx)) + continue; + + CenterLocation += CurvePoints[EditedControlPointsIndexes[EditedIdx]].GetLocation(); + Sum++; + } + + if(Sum > 0) + CenterLocation /= Sum; + + OutLocation = EditedHoudiniSplineComponent->GetComponentTransform().TransformPosition(CenterLocation); + + return true; +} + +bool +FHoudiniSplineComponentVisualizer::IsVisualizingArchetype() const +{ + UHoudiniSplineComponent* SplineComp = GetEditedHoudiniSplineComponent(); + return (SplineComp && SplineComp->GetOwner() && FActorEditorUtils::IsAPreviewOrInactiveActor(SplineComp->GetOwner())); +} + +bool +FHoudiniSplineComponentVisualizer::HandleInputDelta( + FEditorViewportClient* ViewportClient, + FViewport* Viewport, + FVector& DeltaTranslate, + FRotator& DeltaRotate, + FVector& DeltaScale) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!ViewportClient || !EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + if (ViewportClient->IsAltPressed() && bAllowDuplication) + { + OnDuplicateControlPoint(); + bAllowDuplication = false; + } + else + { + if (!bRecordingMovingPoints) + { + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentMovingPointsTransaction", "Houdini Spline Component: Moving curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + + EditedHoudiniSplineComponent->Modify(); + + bRecordingMovingPoints = true; + } + } + + + TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + const FTransform & HoudiniSplineComponentTransform = EditedHoudiniSplineComponent->GetComponentTransform(); + + for (int i = 0; i < EditedControlPointsIndexes.Num(); ++i ) + { + + FTransform CurrentPoint = EditedHoudiniSplineComponent->CurvePoints[EditedControlPointsIndexes[i]]; + + if (!DeltaTranslate.IsZero()) + { + FVector OldWorldPosition = HoudiniSplineComponentTransform.TransformPosition(CurrentPoint.GetLocation()); + FVector NewWorldPosition = OldWorldPosition + DeltaTranslate; + FVector NewLocalPosition = HoudiniSplineComponentTransform.InverseTransformPosition(NewWorldPosition); + CurrentPoint.SetLocation( NewLocalPosition ); + } + + if (!DeltaRotate.IsZero()) + { + FQuat OldWorldRotation = HoudiniSplineComponentTransform.GetRotation() * CurrentPoint.GetRotation(); + FQuat NewWorldRotation = DeltaRotate.Quaternion() * OldWorldRotation; + FQuat NewLocalRotation = HoudiniSplineComponentTransform.GetRotation().Inverse() * NewWorldRotation; + CurrentPoint.SetRotation(NewLocalRotation); + } + + if (!DeltaScale.IsZero()) + { + FVector NewScale = CurrentPoint.GetScale3D() * (FVector(1.f, 1.f, 1.f) + DeltaScale); + CurrentPoint.SetScale3D(NewScale); + } + + + EditedHoudiniSplineComponent->EditPointAtindex(CurrentPoint, EditedControlPointsIndexes[i]); + } + + RefreshViewport(); + + return true; +} + +TSharedPtr +FHoudiniSplineComponentVisualizer::GenerateContextMenu() const +{ + FHoudiniEngineEditor& HoudiniEngineEditor = FHoudiniEngineEditor::Get(); + FName StyleSetName = FHoudiniEngineStyle::GetStyleSetName(); + + FMenuBuilder MenuBuilder(true, VisualizerActions); + MenuBuilder.BeginSection("Houdini Spline actions"); + + // Create the context menu section + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + { + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, + NAME_None, TAttribute(), TAttribute(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDuplicateControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDeleteControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandDeselectAllControlPoints, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + + MenuBuilder.AddMenuEntry( + FHoudiniSplineComponentVisualizerCommands::Get().CommandInsertControlPoint, + NAME_None, TAttribute< FText >(), TAttribute< FText >(), + FSlateIcon(StyleSetName, "HoudiniEngine.HoudiniEngineLogo")); + } + + MenuBuilder.EndSection(); + TSharedPtr MenuWidget = MenuBuilder.MakeWidget(); + return MenuWidget; +} + +// Used by alt-pressed on-curve control port insertion. +// We don't want it to be cooked before finishing editing. +// * Need to call WaitForHoudiniInputUpdate() after done. +int32 +FHoudiniSplineComponentVisualizer::OnInsertControlPointWithoutUpdate() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return -1; + + TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + TArray & DisplayPoints = EditedHoudiniSplineComponent->DisplayPoints; + + if (EditedCurveSegmentIndex >= DisplayPoints.Num()) + return -1; + + // ... // + int InsertAfterIndex = 0; + + TArray & DisplayPointIndexDivider = EditedHoudiniSplineComponent->DisplayPointIndexDivider; + for (int itr = 0; itr < DisplayPointIndexDivider.Num(); ++itr) + { + if (DisplayPointIndexDivider[itr] >= EditedCurveSegmentIndex) + { + InsertAfterIndex = itr; + break; + } + } + // ... // + + if (InsertAfterIndex >= CurvePoints.Num()) return -1; + + FTransform NewPoint = CurvePoints[InsertAfterIndex]; + NewPoint.SetLocation(DisplayPoints[EditedCurveSegmentIndex]); + // To Do: Should interpolate the rotation and scale as well here. + // ... + + // Insert new control point on curve, and add it to selected CP. + int32 NewPointIndex = AddControlPointAfter(NewPoint, InsertAfterIndex); + + // Don't have to reconstruct the index divider each time. + //EditedHoudiniSplineComponent->Construct(EditedHoudiniSplineComponent->DisplayPoints); + EditedHoudiniSplineComponent->DisplayPointIndexDivider.Insert(EditedCurveSegmentIndex, InsertAfterIndex); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + return NewPointIndex; +} + +void +FHoudiniSplineComponentVisualizer::OnInsertControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + int32 NewPointIndex = OnInsertControlPointWithoutUpdate(); + + if (NewPointIndex < 0) return; + + + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Add(NewPointIndex); + + RefreshViewport(); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); +} + +bool +FHoudiniSplineComponentVisualizer::IsInsertControlPointValid() const +{ + return EditedCurveSegmentIndex >= 0; +} + +void +FHoudiniSplineComponentVisualizer::OnAddControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentInsertingPointsTransaction", "Houdini Spline Component: Inserting curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + + EditedHoudiniSplineComponent->Modify(); + + EditedControlPointsIndexes.Sort(); + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + TArray tNewSelectedPoints; + + if (EditedControlPointsIndexes.Num() == 1) + { + FTransform Point = CurvePoints[EditedControlPointsIndexes[0]]; + FTransform NewTransform = FTransform::Identity; + FVector Location = Point.GetLocation(); + //FQuat Rotation = Point.GetRotation(); + //FVector Scale = Point.GetScale3D(); + + NewTransform.SetLocation(Location + 1.f); + //NewTransform.SetRotation(Rotation); + //NewTransform.SetScale3D(Scale); + + + + int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[0]); + tNewSelectedPoints.Add(NewPointIndex); + } + else + { + int IndexIncrement = 0; + int CurrentPointIndex, LastPointIndex; + FTransform CurrentPoint, LastPoint; + + for (int32 n = 0; n < EditedControlPointsIndexes.Num(); ++n) + { + // Insert a new point between each adjacent pair of points + if (n > 0) + { + CurrentPointIndex = EditedControlPointsIndexes[n]; + LastPointIndex = EditedControlPointsIndexes[n - 1]; + CurrentPoint = CurvePoints[CurrentPointIndex + IndexIncrement]; + LastPoint = CurvePoints[LastPointIndex + IndexIncrement]; + + // Insert a point in the middle of LastPoint and CurrentPoint + FVector NewPointLocation = LastPoint.GetLocation() + (CurrentPoint.GetLocation() - LastPoint.GetLocation()) / 2.f; + FVector NewPointScale = LastPoint.GetScale3D() + (CurrentPoint.GetScale3D() - LastPoint.GetScale3D()) / 2.f; + FQuat NewPointRotation = FQuat::Slerp(LastPoint.GetRotation(), CurrentPoint.GetRotation(), .5f); + + FTransform NewTransform = FTransform::Identity; + NewTransform.SetLocation(NewPointLocation); + NewTransform.SetScale3D(NewPointScale); + NewTransform.SetRotation(NewPointRotation); + + int32 NewPointIndex = AddControlPointAfter(NewTransform, EditedControlPointsIndexes[n - 1] + IndexIncrement); + tNewSelectedPoints.Add(NewPointIndex); + + + IndexIncrement += 1; + } + } + } + + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes = tNewSelectedPoints; + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + RefreshViewport(); +} + + +bool +FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + return EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill() && + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; +} + +void +FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentDeletingPointsTransaction", "Houdini Spline Component: Deleting curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + EditedHoudiniSplineComponent->Modify(); + + EditedControlPointsIndexes.Sort(); + + int32 SelectedIndexAfterDelete = EditedControlPointsIndexes[0] - 1; + SelectedIndexAfterDelete = FMath::Max(SelectedIndexAfterDelete, 0); + + for (int32 n = EditedControlPointsIndexes.Num() - 1; n >= 0; --n) + { + int32 RemoveIndex = EditedControlPointsIndexes[n]; + EditedHoudiniSplineComponent->RemovePointAtIndex(RemoveIndex); + + } + + EditedControlPointsIndexes.Empty(); + OnDeselectAllControlPoints(); + EditedControlPointsIndexes.Add(SelectedIndexAfterDelete); + + if (IsCookOnCurveChanged(EditedHoudiniSplineComponent)) + EditedHoudiniSplineComponent->MarkChanged(true); + + // Force refresh the viewport after deleting points to ensure the consistency of HitProxy + RefreshViewport(); + +} + +bool +FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return false; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return false; + + // We only allow the number of Control Points is at least 2 after delete + if (EditedHoudiniSplineComponent->GetCurvePointCount() - EditedControlPointsIndexes.Num() < 2) + return false; + + return true; +} + +void +FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return; + + TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; + + if (EditedControlPointsIndexes.Num() <= 0) + return; + + // Transaction for Undo/Redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_RUNTIME), + LOCTEXT("HoudiniSplineComponentDuplicatingPointsTransaction", "Houdini Spline Component: Duplicating curve points."), + EditedHoudiniSplineComponent->GetOuter(), true); + EditedHoudiniSplineComponent->Modify(); + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + EditedControlPointsIndexes.Sort(); + + TArray tNewSelectedPoints; + int IncrementIndex = 0; + for (int n = 0; n < EditedControlPointsIndexes.Num(); ++n) + { + int32 IndexAfter = EditedControlPointsIndexes[n] + IncrementIndex; + FTransform CurrentPoint = CurvePoints[IndexAfter]; + if (IndexAfter == 0) + IndexAfter = -1; + int32 NewPointIndex = AddControlPointAfter(CurrentPoint, IndexAfter); + tNewSelectedPoints.Add(NewPointIndex); + IncrementIndex ++; + } + + EditedControlPointsIndexes.Empty(); + EditedControlPointsIndexes = tNewSelectedPoints; + + EditedHoudiniSplineComponent->MarkModified(true); + + RefreshViewport(); +} + +bool +FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if(!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill() + || EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() == 0) + return false; + + return true; +} + +void +FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints() +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); +} + +bool +FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid() const +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + return EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; + + return false; +} + +int32 +FHoudiniSplineComponentVisualizer::AddControlPointAfter( + const FTransform & NewPoint, + const int32 & nIndex) +{ + UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + return nIndex; + + const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; + + int32 NewControlPointIndex = nIndex + 1; + + if (NewControlPointIndex == CurvePoints.Num()) + EditedHoudiniSplineComponent->AppendPoint(NewPoint); + else + EditedHoudiniSplineComponent->InsertPointAtIndex(NewPoint, NewControlPointIndex); + + // Return the index of the inserted control point + return NewControlPointIndex; +} + +void +FHoudiniSplineComponentVisualizer::RefreshViewport() +{ + if (GEditor) + GEditor->RedrawLevelEditingViewports(true); +} + +// Find the EditorViewportClient of the viewport where the Houdini Spline Component lives in +FEditorViewportClient * +FHoudiniSplineComponentVisualizer::FindViewportClient( + const UHoudiniSplineComponent * InHoudiniSplineComponent, + const FSceneView * View) +{ + if (!View || !InHoudiniSplineComponent) + return nullptr; + + UWorld * World = InHoudiniSplineComponent->GetWorld(); + uint32 ViewKey = View->GetViewKey(); + + const TArray & AllViewportClients = GUnrealEd->GetAllViewportClients(); + + for (auto & NextViewportClient : AllViewportClients) + { + if (!NextViewportClient) + continue; + + if (NextViewportClient->GetWorld() != World) + continue; + + // Found the viewport client which matches the unique key of the current scene view + if (NextViewportClient->ViewState.GetReference()->GetViewKey() == ViewKey) + return NextViewportClient; + } + + return nullptr; +} + +bool +FHoudiniSplineComponentVisualizer::IsCookOnCurveChanged(UHoudiniSplineComponent * InHoudiniSplineComponent) +{ + if (!InHoudiniSplineComponent) + return true; + + return InHoudiniSplineComponent->bCookOnCurveChanged; + + // UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); + // UHoudiniInputObject * InputObject = Cast(EditedHoudiniSplineComponent->GetOuter()); + // if (!InputObject) + // return true; + // + // UHoudiniInput * Input = Cast(InputObject->GetOuter()); + // + // if (!Input) + // return true; + // + // return Input->GetCookOnCurveChange(); +}; + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h index fedba9821..d98c976d0 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.h @@ -1,185 +1,185 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniSplineComponent.h" - -#include "HitProxies.h" -#include "ComponentVisualizer.h" -#include "Framework/Commands/UICommandList.h" -#include "Framework/Commands/Commands.h" - -class FEditorViewportClient; - -/** Base class for clickable spline editing proxies. **/ -struct HHoudiniSplineVisProxy : public HComponentVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineVisProxy(const UActorComponent * InComponent) - : HComponentVisProxy(InComponent, HPP_Wireframe) - {} -}; - -/** Proxy for a spline control point. **/ -struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , ControlPointIndex(InControlPointIndex) - {} - - int32 ControlPointIndex; -}; - -/** Proxy for a spline display point. **/ -struct HHoudiniSplineCurveSegmentVisProxy : public HHoudiniSplineVisProxy -{ - DECLARE_HIT_PROXY(); - HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 InDisplayPointIndex) - : HHoudiniSplineVisProxy(InComponent) - , DisplayPointIndex(InDisplayPointIndex) - {} - - int32 DisplayPointIndex; -}; - -class FHoudiniSplineComponentVisualizerCommands : public TCommands< FHoudiniSplineComponentVisualizerCommands > -{ - public: - FHoudiniSplineComponentVisualizerCommands(); - - /** Register commands. **/ - virtual void RegisterCommands() override; - - public: - TSharedPtr CommandAddControlPoint; - - TSharedPtr CommandDuplicateControlPoint; - - TSharedPtr CommandDeleteControlPoint; - - TSharedPtr CommandDeselectAllControlPoints; - - TSharedPtr CommandInsertControlPoint; -}; - - -/** **/ -class FHoudiniSplineComponentVisualizer : public FComponentVisualizer -{ - public: - FHoudiniSplineComponentVisualizer(); - - private: - void RefreshViewport(); - - public: - virtual void OnRegister() override; - - virtual void DrawVisualization( - const UActorComponent * Component, const FSceneView * View, - FPrimitiveDrawInterface * PDI) override; - - virtual bool VisProxyHandleClick( - FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, - const FViewportClick& Click) override; - - virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; - virtual bool IsVisualizingArchetype() const override; - - virtual void EndEditing() override; - - virtual bool HandleInputDelta( - FEditorViewportClient* ViewportClient, FViewport* Viewport, - FVector& DeltaTranslate, FRotator& DeltaRotate, - FVector& DeltaScale) override; - - virtual bool HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) override; - - virtual TSharedPtr GenerateContextMenu() const override; - - protected: - - /** Callbacks for add control point action**/ - void OnAddControlPoint(); - bool IsAddControlPointValid() const; - - /** Callbacks for delete control point action. **/ - void OnDeleteControlPoint(); - bool IsDeleteControlPointValid() const; - - /** Callbacks for duplicate control point action. **/ - void OnDuplicateControlPoint(); - bool IsDuplicateControlPointValid() const; - - /** Callbacks for deselect all control points action. **/ - void OnDeselectAllControlPoints(); - bool IsDeselectAllControlPointsValid() const; - - /** Callbacks for inserting a control point action.**/ - void OnInsertControlPoint(); - bool IsInsertControlPointValid() const; - // For alt-pressed inserting control point on curve. - int32 OnInsertControlPointWithoutUpdate(); - - int32 AddControlPointAfter(const FTransform & NewPoint, const int32 & nIndex); - - public: - /** Property path from the parent actor to the component */ - // NOTE: We need to use SplinePropertyPath on the visualizer as opposed to a direct pointer since the - // direct pointer breaks during Blueprint reconstructions properly - // (see SplineComponent / SplineMeshComponent visualizers). - FComponentPropertyPath SplinePropertyPath; - UHoudiniSplineComponent* GetEditedHoudiniSplineComponent() const { return Cast(SplinePropertyPath.GetComponent()); } - - protected: - - bool bAllowDuplication; - - int32 EditedCurveSegmentIndex; - - TSharedPtr VisualizerActions; - - /** Rotation used for the gizmo widgets **/ - FQuat CachedRotation; - - FVector CachedScale3D; - - /** Indicates wether or not a transaction should be recorded when moving a point **/ - bool bMovingPoints; - - bool bInsertingOnCurveControlPoints; - - bool bRecordingMovingPoints; - - private: - FEditorViewportClient * FindViewportClient(const UHoudiniSplineComponent * InHoudiniSplineComponent, const FSceneView * View); - - bool IsCookOnCurveChanged(UHoudiniSplineComponent* InHoudiniSplineComponent); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniSplineComponent.h" + +#include "HitProxies.h" +#include "ComponentVisualizer.h" +#include "Framework/Commands/UICommandList.h" +#include "Framework/Commands/Commands.h" + +class FEditorViewportClient; + +/** Base class for clickable spline editing proxies. **/ +struct HHoudiniSplineVisProxy : public HComponentVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineVisProxy(const UActorComponent * InComponent) + : HComponentVisProxy(InComponent, HPP_Wireframe) + {} +}; + +/** Proxy for a spline control point. **/ +struct HHoudiniSplineControlPointVisProxy : public HHoudiniSplineVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineControlPointVisProxy(const UActorComponent * InComponent, int32 InControlPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , ControlPointIndex(InControlPointIndex) + {} + + int32 ControlPointIndex; +}; + +/** Proxy for a spline display point. **/ +struct HHoudiniSplineCurveSegmentVisProxy : public HHoudiniSplineVisProxy +{ + DECLARE_HIT_PROXY(); + HHoudiniSplineCurveSegmentVisProxy(const UActorComponent * InComponent, int32 InDisplayPointIndex) + : HHoudiniSplineVisProxy(InComponent) + , DisplayPointIndex(InDisplayPointIndex) + {} + + int32 DisplayPointIndex; +}; + +class FHoudiniSplineComponentVisualizerCommands : public TCommands< FHoudiniSplineComponentVisualizerCommands > +{ + public: + FHoudiniSplineComponentVisualizerCommands(); + + /** Register commands. **/ + virtual void RegisterCommands() override; + + public: + TSharedPtr CommandAddControlPoint; + + TSharedPtr CommandDuplicateControlPoint; + + TSharedPtr CommandDeleteControlPoint; + + TSharedPtr CommandDeselectAllControlPoints; + + TSharedPtr CommandInsertControlPoint; +}; + + +/** **/ +class FHoudiniSplineComponentVisualizer : public FComponentVisualizer +{ + public: + FHoudiniSplineComponentVisualizer(); + + private: + void RefreshViewport(); + + public: + virtual void OnRegister() override; + + virtual void DrawVisualization( + const UActorComponent * Component, const FSceneView * View, + FPrimitiveDrawInterface * PDI) override; + + virtual bool VisProxyHandleClick( + FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, + const FViewportClick& Click) override; + + virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override; + virtual bool IsVisualizingArchetype() const override; + + virtual void EndEditing() override; + + virtual bool HandleInputDelta( + FEditorViewportClient* ViewportClient, FViewport* Viewport, + FVector& DeltaTranslate, FRotator& DeltaRotate, + FVector& DeltaScale) override; + + virtual bool HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) override; + + virtual TSharedPtr GenerateContextMenu() const override; + + protected: + + /** Callbacks for add control point action**/ + void OnAddControlPoint(); + bool IsAddControlPointValid() const; + + /** Callbacks for delete control point action. **/ + void OnDeleteControlPoint(); + bool IsDeleteControlPointValid() const; + + /** Callbacks for duplicate control point action. **/ + void OnDuplicateControlPoint(); + bool IsDuplicateControlPointValid() const; + + /** Callbacks for deselect all control points action. **/ + void OnDeselectAllControlPoints(); + bool IsDeselectAllControlPointsValid() const; + + /** Callbacks for inserting a control point action.**/ + void OnInsertControlPoint(); + bool IsInsertControlPointValid() const; + // For alt-pressed inserting control point on curve. + int32 OnInsertControlPointWithoutUpdate(); + + int32 AddControlPointAfter(const FTransform & NewPoint, const int32 & nIndex); + + public: + /** Property path from the parent actor to the component */ + // NOTE: We need to use SplinePropertyPath on the visualizer as opposed to a direct pointer since the + // direct pointer breaks during Blueprint reconstructions properly + // (see SplineComponent / SplineMeshComponent visualizers). + FComponentPropertyPath SplinePropertyPath; + UHoudiniSplineComponent* GetEditedHoudiniSplineComponent() const { return Cast(SplinePropertyPath.GetComponent()); } + + protected: + + bool bAllowDuplication; + + int32 EditedCurveSegmentIndex; + + TSharedPtr VisualizerActions; + + /** Rotation used for the gizmo widgets **/ + FQuat CachedRotation; + + FVector CachedScale3D; + + /** Indicates wether or not a transaction should be recorded when moving a point **/ + bool bMovingPoints; + + bool bInsertingOnCurveControlPoints; + + bool bRecordingMovingPoints; + + private: + FEditorViewportClient * FindViewportClient(const UHoudiniSplineComponent * InHoudiniSplineComponent, const FSceneView * View); + + bool IsCookOnCurveChanged(UHoudiniSplineComponent* InHoudiniSplineComponent); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp b/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp index a34697eed..7f8046a09 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniTool.cpp @@ -1,26 +1,26 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + diff --git a/Source/HoudiniEngineEditor/Private/HoudiniTool.h b/Source/HoudiniEngineEditor/Private/HoudiniTool.h index 92e14c4bf..87d50681e 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniTool.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniTool.h @@ -1,56 +1,56 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -UENUM() -enum class EHoudiniToolType : uint8 -{ - // For tools that generates geometry, and do not need input - HTOOLTYPE_GENERATOR UMETA(DisplayName = "Generator"), - - // For tools that have a single input, the selection will be merged in that single input - HTOOLTYPE_OPERATOR_SINGLE UMETA(DisplayName = "Operator (single)"), - - // For Tools that have multiple input, a single selected asset will be applied to each input - HTOOLTYPE_OPERATOR_MULTI UMETA(DisplayName = "Operator (multiple)"), - - // For tools that needs to be applied each time for each single selected - HTOOLTYPE_OPERATOR_BATCH UMETA(DisplayName = "Batch Operator") -}; - -UENUM() -enum class EHoudiniToolSelectionType : uint8 -{ - // For tools that can be applied both to Content Browser and World selection - HTOOL_SELECTION_ALL UMETA(DisplayName = "Content Browser AND World"), - - // For tools that can be applied only to World selection - HTOOL_SELECTION_WORLD_ONLY UMETA(DisplayName = "World selection only"), - - // For tools that can be applied only to Content Browser selection - HTOOL_SELECTION_CB_ONLY UMETA(DisplayName = "Content browser selection only") +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +UENUM() +enum class EHoudiniToolType : uint8 +{ + // For tools that generates geometry, and do not need input + HTOOLTYPE_GENERATOR UMETA(DisplayName = "Generator"), + + // For tools that have a single input, the selection will be merged in that single input + HTOOLTYPE_OPERATOR_SINGLE UMETA(DisplayName = "Operator (single)"), + + // For Tools that have multiple input, a single selected asset will be applied to each input + HTOOLTYPE_OPERATOR_MULTI UMETA(DisplayName = "Operator (multiple)"), + + // For tools that needs to be applied each time for each single selected + HTOOLTYPE_OPERATOR_BATCH UMETA(DisplayName = "Batch Operator") +}; + +UENUM() +enum class EHoudiniToolSelectionType : uint8 +{ + // For tools that can be applied both to Content Browser and World selection + HTOOL_SELECTION_ALL UMETA(DisplayName = "Content Browser AND World"), + + // For tools that can be applied only to World selection + HTOOL_SELECTION_WORLD_ONLY UMETA(DisplayName = "World selection only"), + + // For tools that can be applied only to Content Browser selection + HTOOL_SELECTION_CB_ONLY UMETA(DisplayName = "Content browser selection only") }; \ No newline at end of file diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp index 1e8a403cc..85fca9b51 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.cpp @@ -1,353 +1,353 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "SNewFilePathPicker.h" - -#include "HoudiniApi.h" -#include "DesktopPlatformModule.h" -#include "Widgets/SBoxPanel.h" -#include "Framework/Application/SlateApplication.h" -#include "Widgets/Images/SImage.h" -#include "Widgets/Input/SEditableTextBox.h" -#include "Widgets/Input/SButton.h" - -#define LOCTEXT_NAMESPACE "SNewFilePathPicker" - -/* SNewFilePathPicker interface - *****************************************************************************/ - -void SNewFilePathPicker::Construct( const FArguments& InArgs ) -{ - BrowseDirectory = InArgs._BrowseDirectory; - BrowseTitle = InArgs._BrowseTitle; - FilePath = InArgs._FilePath; - FileTypeFilter = InArgs._FileTypeFilter; - OnPathPicked = InArgs._OnPathPicked; - IsNewFile = InArgs._IsNewFile; - IsDirectoryPicker = InArgs._IsDirectoryPicker; - - ChildSlot - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .FillWidth(1.0f) - .VAlign(VAlign_Center) - [ - // file path text box - SAssignNew(TextBox, SEditableTextBox) - .Text(this, &SNewFilePathPicker::HandleTextBoxText) - .Font(InArgs._Font) - .SelectAllTextWhenFocused(true) - .ClearKeyboardFocusOnCommit(false) - .OnTextCommitted(this, &SNewFilePathPicker::HandleTextBoxTextCommitted) - .SelectAllTextOnCommit(false) - .IsReadOnly(InArgs._IsReadOnly) - ] - - + SHorizontalBox::Slot() - .AutoWidth() - .Padding(4.0f, 0.0f, 0.0f, 0.0f) - .VAlign(VAlign_Center) - [ - // browse button - SNew(SButton) - .ButtonStyle(InArgs._BrowseButtonStyle) - .ToolTipText(InArgs._BrowseButtonToolTip) - .OnClicked(this, &SNewFilePathPicker::HandleBrowseButtonClicked) - .ContentPadding(2.0f) - .ForegroundColor(FSlateColor::UseForeground()) - .IsFocusable(false) - [ - SNew(SImage) - .Image(InArgs._BrowseButtonImage) - .ColorAndOpacity(FSlateColor::UseForeground()) - ] - ] - ]; -} - - -/* SNewFilePathPicker callbacks - *****************************************************************************/ -#if PLATFORM_WINDOWS - -#include "Windows/WindowsHWrapper.h" -#include "Windows/COMPointer.h" -//#include "Misc/Paths.h" -//#include "Misc/Guid.h" -#include "HAL/FileManager.h" -#include "Windows/AllowWindowsPlatformTypes.h" -#include -//#include -#include -//#include -//#include -//#include -//#include -#include "Windows/HideWindowsPlatformTypes.h" -//#pragma comment( lib, "version.lib" ) - -bool FileDialogShared( bool bSave, const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames, int32& OutFilterIndex ) -{ - FScopedSystemModalMode SystemModalScope; - - bool bSuccess = false; - TComPtr FileDialog; - if ( SUCCEEDED( ::CoCreateInstance( bSave ? CLSID_FileSaveDialog : CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, bSave ? IID_IFileSaveDialog : IID_IFileOpenDialog, IID_PPV_ARGS_Helper( &FileDialog ) ) ) ) - { - if ( bSave ) - { - // Set the default "filename" - if ( !DefaultFile.IsEmpty() ) - { - FileDialog->SetFileName( *FPaths::GetCleanFilename( DefaultFile ) ); - } - DWORD dwFlags = 0; - FileDialog->GetOptions( &dwFlags ); - FileDialog->SetOptions( dwFlags & ~FOS_OVERWRITEPROMPT ); - } - else - { - // Set this up as a multi-select picker - if ( Flags & EFileDialogFlags::Multiple ) - { - DWORD dwFlags = 0; - FileDialog->GetOptions( &dwFlags ); - FileDialog->SetOptions( dwFlags | FOS_ALLOWMULTISELECT ); - } - } - - // Set up common settings - FileDialog->SetTitle( *DialogTitle ); - if ( !DefaultPath.IsEmpty() ) - { - // SHCreateItemFromParsingName requires the given path be absolute and use \ rather than / as our normalized paths do - FString DefaultWindowsPath = FPaths::ConvertRelativePathToFull( DefaultPath ); - DefaultWindowsPath.ReplaceInline( TEXT( "/" ), TEXT( "\\" ), ESearchCase::CaseSensitive ); - - TComPtr DefaultPathItem; - if ( SUCCEEDED( ::SHCreateItemFromParsingName( *DefaultWindowsPath, nullptr, IID_PPV_ARGS( &DefaultPathItem ) ) ) ) - { - FileDialog->SetFolder( DefaultPathItem ); - } - } - - // Set-up the file type filters - TArray UnformattedExtensions; - TArray FileDialogFilters; - { - // Split the given filter string (formatted as "Pair1String1|Pair1String2|Pair2String1|Pair2String2") into the Windows specific filter struct - FileTypes.ParseIntoArray( UnformattedExtensions, TEXT( "|" ), true ); - - if ( UnformattedExtensions.Num() % 2 == 0 ) - { - FileDialogFilters.Reserve( UnformattedExtensions.Num() / 2 ); - for ( int32 ExtensionIndex = 0; ExtensionIndex < UnformattedExtensions.Num();) - { - COMDLG_FILTERSPEC& NewFilterSpec = FileDialogFilters[FileDialogFilters.AddDefaulted()]; - NewFilterSpec.pszName = *UnformattedExtensions[ExtensionIndex++]; - NewFilterSpec.pszSpec = *UnformattedExtensions[ExtensionIndex++]; - } - } - } - FileDialog->SetFileTypes( FileDialogFilters.Num(), FileDialogFilters.GetData() ); - - // Show the picker - if ( SUCCEEDED( FileDialog->Show( (HWND)ParentWindowHandle ) ) ) - { - OutFilterIndex = 0; - if ( SUCCEEDED( FileDialog->GetFileTypeIndex( (UINT*)&OutFilterIndex ) ) ) - { - OutFilterIndex -= 1; // GetFileTypeIndex returns a 1-based index - } - - auto AddOutFilename = [&OutFilenames]( const FString& InFilename ) - { - FString& OutFilename = OutFilenames[OutFilenames.Add( InFilename )]; - OutFilename = IFileManager::Get().ConvertToRelativePath( *OutFilename ); - FPaths::NormalizeFilename( OutFilename ); - }; - - if ( bSave ) - { - TComPtr Result; - if ( SUCCEEDED( FileDialog->GetResult( &Result ) ) ) - { - PWSTR pFilePath = nullptr; - if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) - { - bSuccess = true; - - // Apply the selected extension if the given filename doesn't already have one - FString SaveFilePath = pFilePath; - if ( FileDialogFilters.IsValidIndex( OutFilterIndex ) ) - { - // Build a "clean" version of the selected extension (without the wildcard) - FString CleanExtension = FileDialogFilters[OutFilterIndex].pszSpec; - if ( CleanExtension == TEXT( "*.*" ) ) - { - CleanExtension.Reset(); - } - else - { - const int32 WildCardIndex = CleanExtension.Find( TEXT( "*" ) ); - if ( WildCardIndex != INDEX_NONE ) - { - CleanExtension = CleanExtension.RightChop( WildCardIndex + 1 ); - } - } - - // We need to split these before testing the extension to avoid anything within the path being treated as a file extension - FString SaveFileName = FPaths::GetCleanFilename( SaveFilePath ); - SaveFilePath = FPaths::GetPath( SaveFilePath ); - - // Apply the extension if the file name doesn't already have one - if ( FPaths::GetExtension( SaveFileName ).IsEmpty() && !CleanExtension.IsEmpty() ) - { - SaveFileName = FPaths::SetExtension( SaveFileName, CleanExtension ); - } - - SaveFilePath /= SaveFileName; - } - AddOutFilename( SaveFilePath ); - - ::CoTaskMemFree( pFilePath ); - } - } - } - else - { - IFileOpenDialog* FileOpenDialog = static_cast( FileDialog.Get() ); - - TComPtr Results; - if ( SUCCEEDED( FileOpenDialog->GetResults( &Results ) ) ) - { - DWORD NumResults = 0; - Results->GetCount( &NumResults ); - for ( DWORD ResultIndex = 0; ResultIndex < NumResults; ++ResultIndex ) - { - TComPtr Result; - if ( SUCCEEDED( Results->GetItemAt( ResultIndex, &Result ) ) ) - { - PWSTR pFilePath = nullptr; - if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) - { - bSuccess = true; - AddOutFilename( pFilePath ); - ::CoTaskMemFree( pFilePath ); - } - } - } - } - } - } - } - - return bSuccess; -} - -bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) -{ - int32 DummyFilterIndex = 0; - return FileDialogShared( true, ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames, DummyFilterIndex ); -} - -#else - -bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) -{ - IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); - return DesktopPlatform->SaveFileDialog( ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames ); -} -#endif - -FReply SNewFilePathPicker::HandleBrowseButtonClicked() -{ - IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); - - if (DesktopPlatform == nullptr) - { - return FReply::Handled(); - } - - const FString DefaultPath = BrowseDirectory.IsSet() - ? BrowseDirectory.Get() - : FPaths::GetPath(FilePath.Get()); - - // show the file browse dialog - if (!FSlateApplication::IsInitialized()) - return FReply::Handled(); - - TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); - void* ParentWindowHandle = (ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid()) - ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() - : nullptr; - - if(!IsDirectoryPicker.Get()) - { - TArray OutFiles; - // CG: Use SaveFileDialog instead of OpenFileDialog - if ( IsNewFile.Get() ) - { - if ( SaveFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) - { - OnPathPicked.ExecuteIfBound( OutFiles[0] ); - } - } - else - { - if ( DesktopPlatform->OpenFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) - { - OnPathPicked.ExecuteIfBound( OutFiles[0] ); - } - } - } - else - { - FString OutDir; - if (DesktopPlatform->OpenDirectoryDialog(ParentWindowHandle, LOCTEXT("FolderDialogTitle", "Choose a directory").ToString(), DefaultPath, OutDir)) - { - OnPathPicked.ExecuteIfBound(OutDir); - } - } - - return FReply::Handled(); -} - - -FText SNewFilePathPicker::HandleTextBoxText() const -{ - return FText::FromString(FilePath.Get()); -} - - -void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type CommitInfo ) -{ - OnPathPicked.ExecuteIfBound(NewText.ToString()); -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "SNewFilePathPicker.h" + +#include "HoudiniApi.h" +#include "DesktopPlatformModule.h" +#include "Widgets/SBoxPanel.h" +#include "Framework/Application/SlateApplication.h" +#include "Widgets/Images/SImage.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SButton.h" + +#define LOCTEXT_NAMESPACE "SNewFilePathPicker" + +/* SNewFilePathPicker interface + *****************************************************************************/ + +void SNewFilePathPicker::Construct( const FArguments& InArgs ) +{ + BrowseDirectory = InArgs._BrowseDirectory; + BrowseTitle = InArgs._BrowseTitle; + FilePath = InArgs._FilePath; + FileTypeFilter = InArgs._FileTypeFilter; + OnPathPicked = InArgs._OnPathPicked; + IsNewFile = InArgs._IsNewFile; + IsDirectoryPicker = InArgs._IsDirectoryPicker; + + ChildSlot + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + [ + // file path text box + SAssignNew(TextBox, SEditableTextBox) + .Text(this, &SNewFilePathPicker::HandleTextBoxText) + .Font(InArgs._Font) + .SelectAllTextWhenFocused(true) + .ClearKeyboardFocusOnCommit(false) + .OnTextCommitted(this, &SNewFilePathPicker::HandleTextBoxTextCommitted) + .SelectAllTextOnCommit(false) + .IsReadOnly(InArgs._IsReadOnly) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(4.0f, 0.0f, 0.0f, 0.0f) + .VAlign(VAlign_Center) + [ + // browse button + SNew(SButton) + .ButtonStyle(InArgs._BrowseButtonStyle) + .ToolTipText(InArgs._BrowseButtonToolTip) + .OnClicked(this, &SNewFilePathPicker::HandleBrowseButtonClicked) + .ContentPadding(2.0f) + .ForegroundColor(FSlateColor::UseForeground()) + .IsFocusable(false) + [ + SNew(SImage) + .Image(InArgs._BrowseButtonImage) + .ColorAndOpacity(FSlateColor::UseForeground()) + ] + ] + ]; +} + + +/* SNewFilePathPicker callbacks + *****************************************************************************/ +#if PLATFORM_WINDOWS + +#include "Windows/WindowsHWrapper.h" +#include "Windows/COMPointer.h" +//#include "Misc/Paths.h" +//#include "Misc/Guid.h" +#include "HAL/FileManager.h" +#include "Windows/AllowWindowsPlatformTypes.h" +#include +//#include +#include +//#include +//#include +//#include +//#include +#include "Windows/HideWindowsPlatformTypes.h" +//#pragma comment( lib, "version.lib" ) + +bool FileDialogShared( bool bSave, const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames, int32& OutFilterIndex ) +{ + FScopedSystemModalMode SystemModalScope; + + bool bSuccess = false; + TComPtr FileDialog; + if ( SUCCEEDED( ::CoCreateInstance( bSave ? CLSID_FileSaveDialog : CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, bSave ? IID_IFileSaveDialog : IID_IFileOpenDialog, IID_PPV_ARGS_Helper( &FileDialog ) ) ) ) + { + if ( bSave ) + { + // Set the default "filename" + if ( !DefaultFile.IsEmpty() ) + { + FileDialog->SetFileName( *FPaths::GetCleanFilename( DefaultFile ) ); + } + DWORD dwFlags = 0; + FileDialog->GetOptions( &dwFlags ); + FileDialog->SetOptions( dwFlags & ~FOS_OVERWRITEPROMPT ); + } + else + { + // Set this up as a multi-select picker + if ( Flags & EFileDialogFlags::Multiple ) + { + DWORD dwFlags = 0; + FileDialog->GetOptions( &dwFlags ); + FileDialog->SetOptions( dwFlags | FOS_ALLOWMULTISELECT ); + } + } + + // Set up common settings + FileDialog->SetTitle( *DialogTitle ); + if ( !DefaultPath.IsEmpty() ) + { + // SHCreateItemFromParsingName requires the given path be absolute and use \ rather than / as our normalized paths do + FString DefaultWindowsPath = FPaths::ConvertRelativePathToFull( DefaultPath ); + DefaultWindowsPath.ReplaceInline( TEXT( "/" ), TEXT( "\\" ), ESearchCase::CaseSensitive ); + + TComPtr DefaultPathItem; + if ( SUCCEEDED( ::SHCreateItemFromParsingName( *DefaultWindowsPath, nullptr, IID_PPV_ARGS( &DefaultPathItem ) ) ) ) + { + FileDialog->SetFolder( DefaultPathItem ); + } + } + + // Set-up the file type filters + TArray UnformattedExtensions; + TArray FileDialogFilters; + { + // Split the given filter string (formatted as "Pair1String1|Pair1String2|Pair2String1|Pair2String2") into the Windows specific filter struct + FileTypes.ParseIntoArray( UnformattedExtensions, TEXT( "|" ), true ); + + if ( UnformattedExtensions.Num() % 2 == 0 ) + { + FileDialogFilters.Reserve( UnformattedExtensions.Num() / 2 ); + for ( int32 ExtensionIndex = 0; ExtensionIndex < UnformattedExtensions.Num();) + { + COMDLG_FILTERSPEC& NewFilterSpec = FileDialogFilters[FileDialogFilters.AddDefaulted()]; + NewFilterSpec.pszName = *UnformattedExtensions[ExtensionIndex++]; + NewFilterSpec.pszSpec = *UnformattedExtensions[ExtensionIndex++]; + } + } + } + FileDialog->SetFileTypes( FileDialogFilters.Num(), FileDialogFilters.GetData() ); + + // Show the picker + if ( SUCCEEDED( FileDialog->Show( (HWND)ParentWindowHandle ) ) ) + { + OutFilterIndex = 0; + if ( SUCCEEDED( FileDialog->GetFileTypeIndex( (UINT*)&OutFilterIndex ) ) ) + { + OutFilterIndex -= 1; // GetFileTypeIndex returns a 1-based index + } + + auto AddOutFilename = [&OutFilenames]( const FString& InFilename ) + { + FString& OutFilename = OutFilenames[OutFilenames.Add( InFilename )]; + OutFilename = IFileManager::Get().ConvertToRelativePath( *OutFilename ); + FPaths::NormalizeFilename( OutFilename ); + }; + + if ( bSave ) + { + TComPtr Result; + if ( SUCCEEDED( FileDialog->GetResult( &Result ) ) ) + { + PWSTR pFilePath = nullptr; + if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) + { + bSuccess = true; + + // Apply the selected extension if the given filename doesn't already have one + FString SaveFilePath = pFilePath; + if ( FileDialogFilters.IsValidIndex( OutFilterIndex ) ) + { + // Build a "clean" version of the selected extension (without the wildcard) + FString CleanExtension = FileDialogFilters[OutFilterIndex].pszSpec; + if ( CleanExtension == TEXT( "*.*" ) ) + { + CleanExtension.Reset(); + } + else + { + const int32 WildCardIndex = CleanExtension.Find( TEXT( "*" ) ); + if ( WildCardIndex != INDEX_NONE ) + { + CleanExtension = CleanExtension.RightChop( WildCardIndex + 1 ); + } + } + + // We need to split these before testing the extension to avoid anything within the path being treated as a file extension + FString SaveFileName = FPaths::GetCleanFilename( SaveFilePath ); + SaveFilePath = FPaths::GetPath( SaveFilePath ); + + // Apply the extension if the file name doesn't already have one + if ( FPaths::GetExtension( SaveFileName ).IsEmpty() && !CleanExtension.IsEmpty() ) + { + SaveFileName = FPaths::SetExtension( SaveFileName, CleanExtension ); + } + + SaveFilePath /= SaveFileName; + } + AddOutFilename( SaveFilePath ); + + ::CoTaskMemFree( pFilePath ); + } + } + } + else + { + IFileOpenDialog* FileOpenDialog = static_cast( FileDialog.Get() ); + + TComPtr Results; + if ( SUCCEEDED( FileOpenDialog->GetResults( &Results ) ) ) + { + DWORD NumResults = 0; + Results->GetCount( &NumResults ); + for ( DWORD ResultIndex = 0; ResultIndex < NumResults; ++ResultIndex ) + { + TComPtr Result; + if ( SUCCEEDED( Results->GetItemAt( ResultIndex, &Result ) ) ) + { + PWSTR pFilePath = nullptr; + if ( SUCCEEDED( Result->GetDisplayName( SIGDN_FILESYSPATH, &pFilePath ) ) ) + { + bSuccess = true; + AddOutFilename( pFilePath ); + ::CoTaskMemFree( pFilePath ); + } + } + } + } + } + } + } + + return bSuccess; +} + +bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) +{ + int32 DummyFilterIndex = 0; + return FileDialogShared( true, ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames, DummyFilterIndex ); +} + +#else + +bool SaveFileDialog( const void* ParentWindowHandle, const FString& DialogTitle, const FString& DefaultPath, const FString& DefaultFile, const FString& FileTypes, uint32 Flags, TArray& OutFilenames ) +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + return DesktopPlatform->SaveFileDialog( ParentWindowHandle, DialogTitle, DefaultPath, DefaultFile, FileTypes, Flags, OutFilenames ); +} +#endif + +FReply SNewFilePathPicker::HandleBrowseButtonClicked() +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + + if (DesktopPlatform == nullptr) + { + return FReply::Handled(); + } + + const FString DefaultPath = BrowseDirectory.IsSet() + ? BrowseDirectory.Get() + : FPaths::GetPath(FilePath.Get()); + + // show the file browse dialog + if (!FSlateApplication::IsInitialized()) + return FReply::Handled(); + + TSharedPtr ParentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + void* ParentWindowHandle = (ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid()) + ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() + : nullptr; + + if(!IsDirectoryPicker.Get()) + { + TArray OutFiles; + // CG: Use SaveFileDialog instead of OpenFileDialog + if ( IsNewFile.Get() ) + { + if ( SaveFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) + { + OnPathPicked.ExecuteIfBound( OutFiles[0] ); + } + } + else + { + if ( DesktopPlatform->OpenFileDialog( ParentWindowHandle, BrowseTitle.Get().ToString(), DefaultPath, TEXT( "" ), FileTypeFilter.Get(), EFileDialogFlags::None, OutFiles ) ) + { + OnPathPicked.ExecuteIfBound( OutFiles[0] ); + } + } + } + else + { + FString OutDir; + if (DesktopPlatform->OpenDirectoryDialog(ParentWindowHandle, LOCTEXT("FolderDialogTitle", "Choose a directory").ToString(), DefaultPath, OutDir)) + { + OnPathPicked.ExecuteIfBound(OutDir); + } + } + + return FReply::Handled(); +} + + +FText SNewFilePathPicker::HandleTextBoxText() const +{ + return FText::FromString(FilePath.Get()); +} + + +void SNewFilePathPicker::HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type CommitInfo ) +{ + OnPathPicked.ExecuteIfBound(NewText.ToString()); +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h index 4b6bd22fe..bfd0bf407 100644 --- a/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h +++ b/Source/HoudiniEngineEditor/Private/SNewFilePathPicker.h @@ -1,151 +1,151 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// This is the same as SFilePathPicker but it uses SaveFileDialog instead of OpenFileDialog -// to allow browsing to a new path - -#pragma once - -#include "CoreMinimal.h" -#include "Misc/Attribute.h" -#include "Fonts/SlateFontInfo.h" -#include "Input/Reply.h" -#include "Styling/SlateWidgetStyleAsset.h" -#include "Styling/ISlateStyle.h" -#include "Widgets/DeclarativeSyntaxSupport.h" -#include "Widgets/SCompoundWidget.h" -#include "Styling/SlateTypes.h" - -class SEditableTextBox; - -/** - * Declares a delegate that is executed when a file was picked in the SFilePathPicker widget. - * - * The first parameter will contain the path to the picked file. - */ -DECLARE_DELEGATE_OneParam(FOnPathPicked, const FString& /*PickedPath*/); - - -/** - * Implements an editable text box with a browse button. - */ -class SNewFilePathPicker - : public SCompoundWidget -{ -public: - - SLATE_BEGIN_ARGS(SNewFilePathPicker) - : _BrowseButtonToolTip(NSLOCTEXT("SNewFilePathPicker", "BrowseButtonToolTip", "Choose a file from this computer")) - , _FileTypeFilter(TEXT("All files (*.*)|*.*")) - , _Font() - , _IsReadOnly(false) - , _IsNewFile(true) - , _IsDirectoryPicker(false) - { } - - /** Browse button image resource. */ - SLATE_ATTRIBUTE(const FSlateBrush*, BrowseButtonImage) - - /** Browse button visual style. */ - SLATE_STYLE_ARGUMENT(FButtonStyle, BrowseButtonStyle) - - /** Browse button tool tip text. */ - SLATE_ATTRIBUTE(FText, BrowseButtonToolTip) - - /** The directory to browse by default */ - SLATE_ATTRIBUTE(FString, BrowseDirectory) - - /** Title for the browse dialog window. */ - SLATE_ATTRIBUTE(FText, BrowseTitle) - - /** The currently selected file path. */ - SLATE_ATTRIBUTE(FString, FilePath) - - /** File type filter string. */ - SLATE_ATTRIBUTE(FString, FileTypeFilter) - - /** Font color and opacity of the path text box. */ - SLATE_ATTRIBUTE(FSlateFontInfo, Font) - - /** Whether the path text box can be modified by the user. */ - SLATE_ATTRIBUTE(bool, IsReadOnly) - - /** Whether to use the new-file dialog instead of open-file */ - SLATE_ATTRIBUTE(bool, IsNewFile) - - /** Whether to use the a directory picker dialog */ - SLATE_ATTRIBUTE(bool, IsDirectoryPicker) - - /** Called when a file path has been picked. */ - SLATE_EVENT(FOnPathPicked, OnPathPicked) - - SLATE_END_ARGS() - - /** - * Constructs a new widget. - * - * @param InArgs The construction arguments. - */ - void Construct( const FArguments& InArgs ); - -private: - - /** Callback for clicking the browse button. */ - FReply HandleBrowseButtonClicked( ); - - /** Callback for getting the text in the path text box. */ - FText HandleTextBoxText( ) const; - - /** Callback for committing the text in the path text box. */ - void HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type /*CommitInfo*/ ); - -private: - - /** Holds the directory path to browse by default. */ - TAttribute BrowseDirectory; - - /** Holds the title for the browse dialog window. */ - TAttribute BrowseTitle; - - /** Holds the currently selected file path. */ - TAttribute FilePath; - - /** Holds the file type filter string. */ - TAttribute FileTypeFilter; - - /** Holds the editable text box. */ - TSharedPtr TextBox; - - TAttribute IsNewFile; - - TAttribute IsDirectoryPicker; - -private: - - /** Holds a delegate that is executed when a file was picked. */ - FOnPathPicked OnPathPicked; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// This is the same as SFilePathPicker but it uses SaveFileDialog instead of OpenFileDialog +// to allow browsing to a new path + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Attribute.h" +#include "Fonts/SlateFontInfo.h" +#include "Input/Reply.h" +#include "Styling/SlateWidgetStyleAsset.h" +#include "Styling/ISlateStyle.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Styling/SlateTypes.h" + +class SEditableTextBox; + +/** + * Declares a delegate that is executed when a file was picked in the SFilePathPicker widget. + * + * The first parameter will contain the path to the picked file. + */ +DECLARE_DELEGATE_OneParam(FOnPathPicked, const FString& /*PickedPath*/); + + +/** + * Implements an editable text box with a browse button. + */ +class SNewFilePathPicker + : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SNewFilePathPicker) + : _BrowseButtonToolTip(NSLOCTEXT("SNewFilePathPicker", "BrowseButtonToolTip", "Choose a file from this computer")) + , _FileTypeFilter(TEXT("All files (*.*)|*.*")) + , _Font() + , _IsReadOnly(false) + , _IsNewFile(true) + , _IsDirectoryPicker(false) + { } + + /** Browse button image resource. */ + SLATE_ATTRIBUTE(const FSlateBrush*, BrowseButtonImage) + + /** Browse button visual style. */ + SLATE_STYLE_ARGUMENT(FButtonStyle, BrowseButtonStyle) + + /** Browse button tool tip text. */ + SLATE_ATTRIBUTE(FText, BrowseButtonToolTip) + + /** The directory to browse by default */ + SLATE_ATTRIBUTE(FString, BrowseDirectory) + + /** Title for the browse dialog window. */ + SLATE_ATTRIBUTE(FText, BrowseTitle) + + /** The currently selected file path. */ + SLATE_ATTRIBUTE(FString, FilePath) + + /** File type filter string. */ + SLATE_ATTRIBUTE(FString, FileTypeFilter) + + /** Font color and opacity of the path text box. */ + SLATE_ATTRIBUTE(FSlateFontInfo, Font) + + /** Whether the path text box can be modified by the user. */ + SLATE_ATTRIBUTE(bool, IsReadOnly) + + /** Whether to use the new-file dialog instead of open-file */ + SLATE_ATTRIBUTE(bool, IsNewFile) + + /** Whether to use the a directory picker dialog */ + SLATE_ATTRIBUTE(bool, IsDirectoryPicker) + + /** Called when a file path has been picked. */ + SLATE_EVENT(FOnPathPicked, OnPathPicked) + + SLATE_END_ARGS() + + /** + * Constructs a new widget. + * + * @param InArgs The construction arguments. + */ + void Construct( const FArguments& InArgs ); + +private: + + /** Callback for clicking the browse button. */ + FReply HandleBrowseButtonClicked( ); + + /** Callback for getting the text in the path text box. */ + FText HandleTextBoxText( ) const; + + /** Callback for committing the text in the path text box. */ + void HandleTextBoxTextCommitted( const FText& NewText, ETextCommit::Type /*CommitInfo*/ ); + +private: + + /** Holds the directory path to browse by default. */ + TAttribute BrowseDirectory; + + /** Holds the title for the browse dialog window. */ + TAttribute BrowseTitle; + + /** Holds the currently selected file path. */ + TAttribute FilePath; + + /** Holds the file type filter string. */ + TAttribute FileTypeFilter; + + /** Holds the editable text box. */ + TSharedPtr TextBox; + + TAttribute IsNewFile; + + TAttribute IsDirectoryPicker; + +private: + + /** Holds a delegate that is executed when a file was picked. */ + FOnPathPicked OnPathPicked; +}; + diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp index 34845b12f..20695a5e5 100644 --- a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.cpp @@ -1,12 +1,11 @@ - - -#if WITH_DEV_AUTOMATION_TESTS +#if WITH_DEV_AUTOMATION_TESTS #include "HoudiniEditorTestUtils.h" #include "IAssetViewport.h" #include "Slate/SceneViewport.h" #include "Widgets/SViewport.h" #include "FileHelpers.h" #include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" #include "HoudiniAssetComponent.h" #include "HoudiniEngineEditor.h" #include "HoudiniEngineEditorUtils.h" diff --git a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h index 9be7e92d3..0d2e678de 100644 --- a/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h +++ b/Source/HoudiniEngineEditor/Private/Tests/HoudiniEditorTestUtils.h @@ -5,6 +5,8 @@ #include "CoreMinimal.h" class UHoudiniAssetComponent; +class ULevel; +class SWindow; class FHoudiniEditorTestUtils { diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h index d9014811c..00f5b1bff 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPI.h @@ -29,10 +29,9 @@ #include "CoreMinimal.h" #include "UObject/NoExportTypes.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniParameter.h" +#include "HoudiniPublicAPIInputTypes.h" #include "HoudiniPublicAPIObjectBase.h" +#include "HoudiniEngineRuntimeCommon.h" #include "HoudiniPublicAPI.generated.h" @@ -80,8 +79,6 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPI : public UHoudiniPublicAPIObject /** Returns true if there is a valid Houdini Engine session running/connected */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool IsSessionValid() const; - FORCEINLINE - virtual bool IsSessionValid_Implementation() const { return FHoudiniEngineCommands::IsSessionValid(); } /** Start a new Houdini Engine Session if there is no current session */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") @@ -173,8 +170,6 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPI : public UHoudiniPublicAPIObject /** Returns true if asset cooking is paused. */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool IsAssetCookingPaused() const; - FORCEINLINE - virtual bool IsAssetCookingPaused_Implementation() const { return FHoudiniEngineCommands::IsAssetCookingPaused(); } /** Pause asset cooking (if not already paused) */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h index 7f9df43b2..54c6ae8aa 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h @@ -1,1556 +1,1554 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" - -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineCommands.h" -#include "HoudiniParameter.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniPublicAPIObjectBase.h" -#include "HoudiniPublicAPI.h" -#include "HoudiniPublicAPIOutputTypes.h" -#include "IHoudiniAssetStateEvents.h" - -#include "HoudiniPublicAPIAssetWrapper.generated.h" - - -class UHoudiniPublicAPIInput; - -/** - * The base class of a struct for Houdini Ramp points. - */ -USTRUCT(BlueprintType, Category="Houdini Engine | Public API") -struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIRampPoint -{ - GENERATED_BODY(); - -public: - FHoudiniPublicAPIRampPoint(); - - FHoudiniPublicAPIRampPoint( - const float InPosition, - const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); - - /** The position of the point on the Ramp's x-axis [0,1]. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") - float Position; - - /** The interpolation type of the point. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") - EHoudiniPublicAPIRampInterpolationType Interpolation; -}; - -/** - * A struct for Houdini float ramp points. - */ -USTRUCT(BlueprintType, Category="Houdini Engine | Public API") -struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIFloatRampPoint : public FHoudiniPublicAPIRampPoint -{ - GENERATED_BODY(); - -public: - - FHoudiniPublicAPIFloatRampPoint(); - - FHoudiniPublicAPIFloatRampPoint( - const float InPosition, - const float InValue, - const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); - - /** The value of the point. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") - float Value; - -}; - -/** - * A struct for Houdini color ramp points. - */ -USTRUCT(BlueprintType, Category="Houdini Engine | Public API") -struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIColorRampPoint : public FHoudiniPublicAPIRampPoint -{ - GENERATED_BODY(); - -public: - - FHoudiniPublicAPIColorRampPoint(); - - FHoudiniPublicAPIColorRampPoint( - const float InPosition, - const FLinearColor& InValue, - const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); - - /** The value of the point. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") - FLinearColor Value; - -}; - -/** - * A struct for storing the values of a Houdini parameter tuple. - * Currently supports bool, int32, float and FString storage. - */ -USTRUCT(BlueprintType, Category="Houdini Engine | Public API") -struct HOUDINIENGINEEDITOR_API FHoudiniParameterTuple -{ - GENERATED_BODY(); - -public: - FHoudiniParameterTuple(); - - /** - * Wrap a single bool value. - * @param InValue The value to wrap. - */ - FHoudiniParameterTuple(const bool& InValue); - /** - * Wrap a bool tuple. - * @param InValues The tuple to wrap. - */ - FHoudiniParameterTuple(const TArray& InValues); - - /** - * Wrap a single int32 value. - * @param InValue The value to wrap. - */ - FHoudiniParameterTuple(const int32& InValue); - /** - * Wrap a int32 tuple. - * @param InValues The tuple to wrap. - */ - FHoudiniParameterTuple(const TArray& InValues); - - /** - * Wrap a single float value. - * @param InValue The value to wrap. - */ - FHoudiniParameterTuple(const float& InValue); - /** - * Wrap a float tuple. - * @param InValues The tuple to wrap. - */ - FHoudiniParameterTuple(const TArray& InValues); - - /** - * Wrap a single FString value. - * @param InValue The value to wrap. - */ - FHoudiniParameterTuple(const FString& InValue); - /** - * Wrap a FString tuple. - * @param InValues The tuple to wrap. - */ - FHoudiniParameterTuple(const TArray& InValues); - - /** - * Wrap float ramp points - * @param InRampPoints The float ramp points. - */ - FHoudiniParameterTuple(const TArray& InRampPoints); - - /** - * Wrap color ramp points - * @param InRampPoints The color ramp points. - */ - FHoudiniParameterTuple(const TArray& InRampPoints); - - // Parameter tuple storage - - /** For bool compatible parameters, the bool parameter tuple values. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") - TArray BoolValues; - - /** For int32 compatible parameters, the int32 parameter tuple values. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") - TArray Int32Values; - - /** For float compatible parameters, the float parameter tuple values. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") - TArray FloatValues; - - /** For string compatible parameters, the string parameter tuple values. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") - TArray StringValues; - - /** For float ramp parameters, the ramp points. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") - TArray FloatRampPoints; - - /** For color ramp parameters, the ramp points. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") - TArray ColorRampPoints; -}; - -/** - * A wrapper for spawned/instantiating HDAs. - * - * The wrapper/HDA should be instantiated via UHoudiniPublicAPI::InstantiateAsset(). Alternatively an empty - * wrapper can be created via UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper() and an HDA later instantiated and - * assigned to the wrapper via UHoudiniPublicAPI::InstantiateAssetWithExistingWrapper(). - * - * The wrapper provides functionality for interacting/manipulating a - * AHoudiniAssetActor / UHoudiniAssetComponent: - * - Get/Set Inputs - * - Get/Set Parameters - * - Manually initiate a cook/recook - * - Subscribe to delegates: - * - #OnPreInstantiationDelegate (good place to set parameter values before the first cook) - * - #OnPostInstantiationDelegate (good place to set/configure inputs before the first cook) - * - #OnPostCookDelegate - * - #OnPreProcessStateExitedDelegate - * - #OnPostProcessingDelegate (output objects are available if the cook was successful) - * - #OnPostBakeDelegate - * - #OnPostPDGTOPNetworkCookDelegate - * - #OnPostPDGBakeDelegate - * - #OnProxyMeshesRefinedDelegate - * - Iterate over outputs and find the output assets - * - Bake outputs - * - PDG: Dirty all, cook outputs - */ -UCLASS(BlueprintType, Blueprintable, Category="Houdini Engine|Public API") -class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPublicAPIObjectBase -{ - GENERATED_BODY() - -public: - UHoudiniPublicAPIAssetWrapper(); - - // Delegate types - - DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHoudiniAssetStateChange, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper); - DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetPostCook, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper, const bool, bInCookSuccess); - DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetPostBake, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper, const bool, bInBakeSuccess); - DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetProxyMeshesRefinedDelegate, UHoudiniPublicAPIAssetWrapper* const, InAssetWrapper, const EHoudiniProxyRefineResult, InResult); - - /** - * Factory function for creating new wrapper instances around instantiated assets. - * @param InOuter The outer for the new wrapper instance. - * @param InHoudiniAssetActorOrComponent The AHoudiniAssetActor or UHoudiniAssetComponent to wrap. - * @return The newly instantiated wrapper that wraps InHoudiniAssetActor, or nullptr if the wrapper could not - * be created, or if InHoudiniAssetActorOrComponent is invalid or not of a supported type. - */ - UFUNCTION(BlueprintCallable, Category="Houdini|Public API") - static UHoudiniPublicAPIAssetWrapper* CreateWrapper(UObject* InOuter, UObject* InHoudiniAssetActorOrComponent); - - /** - * Factory function for creating a new empty wrapper instance. - * An instantiated actor can be wrapped using SetHoudiniAssetActor. - * @param InOuter the outer for the new wrapper instance. - * @return The newly instantiated wrapper. - */ - UFUNCTION(BlueprintCallable, Category="Houdini|Public API") - static UHoudiniPublicAPIAssetWrapper* CreateEmptyWrapper(UObject* InOuter); - - /** - * Checks if InObject can be wrapped by instances of UHoudiniPublicAPIAssetWrapper. - * @param InObject The object to check for compatiblity. - * @return true if InObject can be wrapped by instances of UHoudiniPublicAPIAssetWrapper. - */ - UFUNCTION(BlueprintCallable, Category="Houdini|Public API") - static bool CanWrapHoudiniObject(UObject* InObject); - - /** - * Wrap the specified instantiated houdini asset object. Supported objects are: AHoudiniAssetActor, - * UHoudiniAssetComponent. This will first unwrap/unbind the currently wrapped instantiated - * asset if InHoudiniAssetObjectToWrap is valid and of a supported class. - * - * If InHoudiniAssetObjectToWrap is nullptr, then this wrapper will unwrap/unbind the currently wrapped - * instantiated asset and return true. - * - * @param InHoudiniAssetObjectToWrap The object to wrap, or nullptr to unwrap/unbind if currently wrapping an - * asset. - * @return true if InHoudiniAssetObjectToWrap is valid, of a supported class and was successfully wrapped, or true - * if InHoudiniAssetObjectToWrap is nullptr. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool WrapHoudiniAssetObject(UObject* InHoudiniAssetObjectToWrap); - - // Accessors and mutators - - /** - * Get the wrapped instantiated houdini asset object. - * @return The wrapped instantiated houdini asset object. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - UObject* GetHoudiniAssetObject() const; - FORCEINLINE - virtual UObject* GetHoudiniAssetObject_Implementation() const { return HoudiniAssetObject.Get(); } - - /** - * Helper function for getting the instantiated HDA asset actor, if HoudiniAssetObject is an AHoudiniAssetActor or - * a UHoudiniAssetComponent owned by a AHoudiniAssetActor. - * @return The instantiated AHoudiniAssetActor, if HoudiniAssetObject is an AHoudiniAssetActor or - * a UHoudiniAssetComponent owned by a AHoudiniAssetActor, otherwise nullptr. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - AHoudiniAssetActor* GetHoudiniAssetActor() const; - FORCEINLINE - virtual AHoudiniAssetActor* GetHoudiniAssetActor_Implementation() const { return CachedHoudiniAssetActor.Get(); } - - /** - * Helper function for getting the UHoudiniAssetComponent of the HDA, if HoudiniAssetObject is a - * UHoudiniAssetComponent or an AHoudiniAssetActor. - * @return The instantiated AHoudiniAssetActor, if HoudiniAssetObject is a - * UHoudiniAssetComponent or an AHoudiniAssetActor, otherwise nullptr. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - UHoudiniAssetComponent* GetHoudiniAssetComponent() const; - FORCEINLINE - virtual UHoudiniAssetComponent* GetHoudiniAssetComponent_Implementation() const { return CachedHoudiniAssetComponent.Get(); } - - /** - * Get the Temp Folder fallback as configured on asset details panel - * @param OutDirectoryPath The currently configured fallback temporary cook folder. - * @return true if the wrapper is valid and the value was fetched. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetTemporaryCookFolder(FDirectoryPath& OutDirectoryPath) const; - - /** - * Set the Temp Folder fallback as configured on asset details panel. Returns true if the value was changed. - * @param InDirectoryPath The new temp folder fallback to set. - * @return true if the wrapper is valid and the value was set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetTemporaryCookFolder(const FDirectoryPath& InDirectoryPath) const; - - /** - * Get the Bake Folder fallback as configured on asset details panel. - * @param OutDirectoryPath The current bake folder fallback. - * @return true if the wrapper is valid and the value was fetched. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetBakeFolder(FDirectoryPath& OutDirectoryPath) const; - - /** - * Set the Bake Folder fallback as configured on asset details panel. Returns true if the value was changed. - * @param InDirectoryPath The new bake folder fallback. - * @return true if the wrapper is valid and the value was set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetBakeFolder(const FDirectoryPath& InDirectoryPath) const; - - // Houdini Engine Actions - - /** Delete the instantiated asset from its level and mark the wrapper for destruction. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool DeleteInstantiatedAsset(); - - /** Rebuild the HDA node in Houdini. Returns true if the asset was successfully marked as needing to be rebuilt. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool Rebuild(); - - // Cooking - - /** Recook the asset. Returns true if the asset was successfully marked as needing to be cooked. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool Recook(); - - /** - * Enable or disable auto cooking of the asset (on parameter changes, input updates and transform changes, for - * example) - * @param bInSetEnabled Whether or not enable auto cooking. - * @return true if the value was changed. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetAutoCookingEnabled(const bool bInSetEnabled); - - /** Returns true if auto cooking is enabled for this instantiated asset. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool IsAutoCookingEnabled() const; - - // Baking - - /** - * Bake all outputs of the instantiated asset using the settings configured on the asset. - * @return true if the wrapper is valid and the baking process was started. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool BakeAllOutputs(); - - /** - * Bake all outputs of the instantiated asset using the specified settings. - * @param InBakeOption The bake method/target, (to actor vs blueprint, for example). - * @param bInReplacePreviousBake If true, replace the previous bake output (assets + actor) with the - * new results. - * @param bInRemoveTempOutputsOnSuccess If true, the temporary outputs of the wrapper asset are removed - * after a successful bake. - * @param bInRecenterBakedActors If true, recenter the baked actors to their bounding box center after the bake. - * @return true if the wrapper is valid and the baking process was started. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool BakeAllOutputsWithSettings( - EHoudiniEngineBakeOption InBakeOption, - bool bInReplacePreviousBake=false, - bool bInRemoveTempOutputsOnSuccess=false, - bool bInRecenterBakedActors=false); - - /** - * Set whether to automatically bake all outputs after a successful cook. - * @return false if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetAutoBakeEnabled(const bool bInAutoBakeEnabled); - - /** Returns true if auto bake is enabled. See SetAutoBakeEnabled. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool IsAutoBakeEnabled() const; - - /** - * Sets the bake method to use (to actor, blueprint, foliage). - * @param InBakeMethod The new bake method to set. - * @return false if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetBakeMethod(const EHoudiniEngineBakeOption InBakeMethod); - - /** - * Gets the currently set bake method to use (to actor, blueprint, foliage). - * @param OutBakeMethod The current bake method. - * @return false if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetBakeMethod(EHoudiniEngineBakeOption& OutBakeMethod); - - /** - * Set the bRemoveOutputAfterBake property, that controls if temporary outputs are removed after a successful bake. - * @param bInRemoveOutputAfterBake If true, then after a successful bake, the HACs outputs will be cleared and - * removed. - * @return false if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetRemoveOutputAfterBake(const bool bInRemoveOutputAfterBake); - - /** - * Get the bRemoveOutputAfterBake property, that controls if temporary outputs are removed after a successful bake. - * @return true if bRemoveOutputAfterBake is true. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetRemoveOutputAfterBake() const; - - /** - * Set the bRecenterBakedActors property that controls if baked actors are recentered around their bounding box center. - * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center after bake. - * @return false if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetRecenterBakedActors(const bool bInRecenterBakedActors); - - /** Gets the bRecenterBakedActors property. If true, recenter baked actors to their bounding box center after bake. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetRecenterBakedActors() const; - - /** - * Set the bReplacePreviousBake property, if true, replace the previously baked output (if any) instead of creating - * new objects. - * @param bInReplacePreviousBake If true, replace the previously baked output (if any) instead of creating new - * objects. - * @return false if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetReplacePreviousBake(const bool bInReplacePreviousBake); - - /** Get the bReplacePreviousBake property. - * @return The value of bReplacePreviousBake. If true, previous bake output (if any) will be replaced by subsequent - * bakes. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetReplacePreviousBake() const; - - // Parameters - - /** - * Set the value of a float-based parameter. - * Supported parameter types: - * - Float - * - Color - * @param InParameterTupleName The name of the parameter tuple. - * @param InValue The value to set. - * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. Defaults to 0. - * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set or the parameter already had the given value. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetFloatParameterValue(FName InParameterTupleName, float InValue, int32 InAtIndex=0, bool bInMarkChanged=true); - - /** - * Get the value of a float parameter. Returns true if the parameter and index was found and the value set in OutValue. - * Supported parameter types: - * - Float - * - Color - * @param InParameterTupleName The name of the parameter tuple. - * @param OutValue The value of the parameter that was fetched. - * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. - * @return true if the wrapper is valid and the parameter was found. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetFloatParameterValue(FName InParameterTupleName, float& OutValue, int32 InAtIndex=0) const; - - /** - * Set the value of a color parameter. - * Supported parameter types: - * - Color - * @param InParameterTupleName The name of the parameter tuple. - * @param InValue The value to set. - * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set or the parameter already had the given value. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetColorParameterValue(FName InParameterTupleName, const FLinearColor& InValue, bool bInMarkChanged=true); - - /** - * Get the value of a color parameter. Returns true if the parameter was found and the value set in OutValue. - * Supported parameter types: - * - Color - * @param InParameterTupleName The name of the parameter tuple. - * @param OutValue The value of the parameter that was fetched. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetColorParameterValue(FName InParameterTupleName, FLinearColor& OutValue) const; - - /** - * Set the value of a int32 parameter. - * Supported parameter types: - * - Int - * - IntChoice - * - MultiParm - * - Toggle - * - Folder (set the folder as currently shown) - * @param InParameterTupleName The name of the parameter tuple. - * @param InValue The value to set. - * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. Defaults to 0. - * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set or the parameter already had the given value. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetIntParameterValue(FName InParameterTupleName, int32 InValue, int32 InAtIndex=0, bool bInMarkChanged=true); - - /** - * Get the value of a int32 parameter. - * Supported parameter types: - * - Int - * - IntChoice - * - MultiParm - * - Toggle - * - Folder (get if the folder is currently shown) - * @param InParameterTupleName The name of the parameter tuple. - * @param OutValue The value of the parameter that was fetched. - * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. - * @return true if the parameter and index was found and the value set in OutValue. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetIntParameterValue(FName InParameterTupleName, int32& OutValue, int32 InAtIndex=0) const; - - /** - * Set the value of a bool parameter. - * Supported parameter types: - * - Toggle - * - Folder (set the folder as currently shown) - * @param InParameterTupleName The name of the parameter tuple. - * @param InValue The value to set. - * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. - * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set or the parameter already had the given value. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetBoolParameterValue(FName InParameterTupleName, bool InValue, int32 InAtIndex=0, bool bInMarkChanged=true); - - /** - * Get the value of a bool parameter. - * Supported parameter types: - * - Toggle - * - Folder (get if the folder is currently shown) - * @param InParameterTupleName The name of the parameter tuple. - * @param OutValue The value of the parameter that was fetched. - * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. - * @return true if the parameter and index was found and the value set in OutValue. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetBoolParameterValue(FName InParameterTupleName, bool& OutValue, int32 InAtIndex=0) const; - - /** - * Set the value of a String parameter. - * Supported parameter types: - * - String - * - StringChoice - * - StringAssetRef - * - File - * - FileDir - * - FileGeo - * - FileImage - * @param InParameterTupleName The name of the parameter tuple. - * @param InValue The value to set. - * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. - * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set or the parameter already had the given value. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetStringParameterValue(FName InParameterTupleName, const FString& InValue, int32 InAtIndex=0, bool bInMarkChanged=true); - - /** - * Get the value of a String parameter. - * Supported parameter types: - * - String - * - StringChoice - * - StringAssetRef - * - File - * - FileDir - * - FileGeo - * - FileImage - * @param InParameterTupleName The name of the parameter tuple. - * @param OutValue The value of the parameter that was fetched. - * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. - * @return true if the parameter was found and the value set in OutValue. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetStringParameterValue(FName InParameterTupleName, FString& OutValue, int32 InAtIndex=0) const; - - /** - * Set the value of an AssetRef parameter. - * Supported parameter types: - * - StringAssetRef - * @param InParameterTupleName The name of the parameter tuple. - * @param InValue The value to set. - * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. - * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set or the parameter already had the given value. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetAssetRefParameterValue(FName InParameterTupleName, UObject* InValue, int32 InAtIndex=0, bool bInMarkChanged=true); - - /** - * Get the value of an AssetRef parameter. - * Supported parameter types: - * - StringAssetRef - * @param InParameterTupleName The name of the parameter tuple. - * @param OutValue The value of the parameter that was fetched. - * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. - * @return True if the parameter was found and the value set in OutValue. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetAssetRefParameterValue(FName InParameterTupleName, UObject*& OutValue, int32 InAtIndex=0) const; - - /** - * Sets the number of points of the specified ramp parameter. This will insert or remove points from the end - * as necessary. - * @param InParameterTupleName The name of the parameter tuple. - * @param InNumPoints The new number of points to set. Must be >= 1. - * @return true if the parameter was found and the number of points set, or if the number of points was already InNumPoints. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetRampParameterNumPoints(FName InParameterTupleName, const int32 InNumPoints) const; - - /** - * Gets the number of points of the specified ramp parameter. - * @param InParameterTupleName The name of the parameter tuple. - * @param OutNumPoints The number of points the ramp has. - * @return true if the parameter was found and the number of points fetched. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetRampParameterNumPoints(FName InParameterTupleName, int32& OutNumPoints) const; - - /** - * Set the position, value and interpolation of a point of a FloatRamp parameter. - * Supported parameter types: - * - FloatRamp - * @param InParameterTupleName The name of the parameter tuple. - * @param InPointIndex The index of the ramp point to set. - * @param InPointPosition The point position to set [0, 1]. - * @param InPointValue The value to set for the point. - * @param InInterpolationType The interpolation to set at the point. Defaults to EHoudiniPublicAPIRampInterpolationType.Linear. - * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetFloatRampParameterPointValue( - FName InParameterTupleName, - const int32 InPointIndex, - const float InPointPosition, - const float InPointValue, - const EHoudiniPublicAPIRampInterpolationType InInterpolationType=EHoudiniPublicAPIRampInterpolationType::LINEAR, - const bool bInMarkChanged=true); - - /** - * Get the position, value and interpolation of a point of a FloatRamp parameter. - * Supported parameter types: - * - FloatRamp - * @param InParameterTupleName The name of the parameter tuple. - * @param InPointIndex The index of the ramp point to get. - * @param OutPointPosition The point position [0, 1]. - * @param OutPointValue The value at the point. - * @param OutInterpolationType The interpolation of the point. - * @return True if the parameter was found and output values set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetFloatRampParameterPointValue( - FName InParameterTupleName, - const int32 InPointIndex, - float& OutPointPosition, - float& OutPointValue, - EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const; - - /** - * Set all of the points (position, value and interpolation) of float ramp. - * Supported parameter types: - * - FloatRamp - * @param InParameterTupleName The name of the parameter tuple. - * @param InRampPoints An array of structs to set as the ramp's points. - * @param bInMarkChanged If true, parameters are marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the values were set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetFloatRampParameterPoints( - FName InParameterTupleName, - const TArray& InRampPoints, - const bool bInMarkChanged=true); - - /** - * Get the all of the points (position, value and interpolation) of a FloatRamp parameter. - * Supported parameter types: - * - FloatRamp - * @param InParameterTupleName The name of the parameter tuple. - * @param OutRampPoints The array to populate with the ramp's points. - * @return True if the parameter was found and output values set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetFloatRampParameterPoints( - FName InParameterTupleName, - TArray& OutRampPoints) const; - - /** - * Set the position, value and interpolation of a point of a ColorRamp parameter. - * Supported parameter types: - * - ColorRamp - * @param InParameterTupleName The name of the parameter tuple. - * @param InPointIndex The index of the ramp point to set. - * @param InPointPosition The point position to set [0, 1]. - * @param InPointValue The value to set for the point. - * @param InInterpolationType The interpolation to set at the point. Defaults to EHoudiniPublicAPIRampInterpolationType.Linear. - * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetColorRampParameterPointValue( - FName InParameterTupleName, - const int32 InPointIndex, - const float InPointPosition, - const FLinearColor& InPointValue, - const EHoudiniPublicAPIRampInterpolationType InInterpolationType=EHoudiniPublicAPIRampInterpolationType::LINEAR, - const bool bInMarkChanged=true); - - /** - * Get the position, value and interpolation of a point of a ColorRamp parameter. - * Supported parameter types: - * - ColorRamp - * @param InParameterTupleName The name of the parameter tuple. - * @param InPointIndex The index of the ramp point to get. - * @param OutPointPosition The point position [0, 1]. - * @param OutPointValue The value at the point. - * @param OutInterpolationType The interpolation of the point. - * @return True if the parameter was found and output values set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetColorRampParameterPointValue( - FName InParameterTupleName, - const int32 InPointIndex, - float& OutPointPosition, - FLinearColor& OutPointValue, - EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const; - - /** - * Set all of the points (position, value and interpolation) of color ramp. - * Supported parameter types: - * - ColorRamp - * @param InParameterTupleName The name of the parameter tuple. - * @param InRampPoints An array of structs to set as the ramp's points. - * @param bInMarkChanged If true, parameters are marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the values were set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetColorRampParameterPoints( - FName InParameterTupleName, - const TArray& InRampPoints, - const bool bInMarkChanged=true); - - /** - * Get the all of the points (position, value and interpolation) of a ColorRamp parameter. - * Supported parameter types: - * - ColorRamp - * @param InParameterTupleName The name of the parameter tuple. - * @param OutRampPoints The array to populate with the ramp's points. - * @return True if the parameter was found and output values set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetColorRampParameterPoints( - FName InParameterTupleName, - TArray& OutRampPoints) const; - - /** - * Trigger / click the specified button parameter. - * @return True if the button was found and triggered/clicked, or was already marked to be triggered. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool TriggerButtonParameter(FName InButtonParameterName); - - /** - * Gets all parameter tuples (with their values) from this asset and outputs it to OutParameterTuples. - * @param OutParameterTuples Populated with all parameter tuples and their values. - * @return false if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetParameterTuples(TMap& OutParameterTuples) const; - - /** - * Sets all parameter tuple values (matched by name and compatible type) from InParameterTuples on this - * instantiated asset. - * @param InParameterTuples The parameter tuples to set. - * @return false if any entry in InParameterTuples could not be found on the asset or had an incompatible type/size. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetParameterTuples(const TMap& InParameterTuples); - - // Inputs - - /** - * Creates an empty input wrapper. - * @param InInputClass the class of the input to create. See the UHoudiniPublicAPIInput class hierarchy. - * @return The newly created input wrapper, or null if the input wrapper could not be created. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(DeterminesOutputType="InInputClass")) - UHoudiniPublicAPIInput* CreateEmptyInput(TSubclassOf InInputClass); - - /** - * Get the number of node inputs supported by the asset. - * @return The number of node inputs (inputs on the HDA node, excluding parameter-based inputs). Returns -1 if the - * asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - UPARAM(DisplayName="OutNumNodeInputs") int32 GetNumNodeInputs() const; - - /** - * Set a node input at the specific index. - * @param InNodeInputIndex The index of the node input, starts at 0. - * @param InInput The input wrapper to use to set the input. - * @return false if the the input could not be set, for example, if the wrapper is invalid, or if the input index - * is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetInputAtIndex(const int32 InNodeInputIndex, const UHoudiniPublicAPIInput* InInput); - - /** - * Get the node input at the specific index and sets OutInput. This is a copy of the input structure. Changes - * properties in OutInput won't affect the instantiated HDA until a subsequent call to SetInputAtIndex. - * @param InNodeInputIndex The index of the node input to get. - * @param OutInput Copy of the input configuration and data for node input index InNodeInputIndex. - * @return false if the input could be fetched, for example if the wrapper is invalid or the input index is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetInputAtIndex(const int32 InNodeInputIndex, UHoudiniPublicAPIInput*& OutInput); - - /** - * Set node inputs at the specified indices via a map. - * @param InInputs A map of node input index to input wrapper to use to set inputs on the instantiated asset. - * @return true if all inputs from InInputs were set successfully. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetInputsAtIndices(const TMap& InInputs); - - /** - * Get all node inputs. - * @param OutInputs All node inputs as a map, with the node input index as key. The input configuration is copied - * from instantiated asset, and changing an input property from the entry in this map will not affect the - * instantiated asset until a subsequent SetInputsAtIndices() call or SetInputAtIndex() call. - * @return false if the wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetInputsAtIndices(TMap& OutInputs); - - /** - * Set a parameter-based input via parameter name. - * @param InParameterName The name of the input parameter. - * @param InInput The input wrapper to use to set/configure the input. - * @return false if the wrapper is invalid, InParameterName is not a valid input parameter, or if InInput could - * not be used to successfully configure/set the input. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) - bool SetInputParameter(const FName& InParameterName, const UHoudiniPublicAPIInput* InInput); - - /** - * Get a parameter-based input via parameter name. - * @param InParameterName The name of the input parameter. - * @param OutInput A copy of the input configuration for InParameterName. - * @return false if the wrapper is invalid, InParameterName is not a valid input parameter, or the current input - * configuration could not be successfully copied to a new UHoudiniPublicAPIInput wrapper. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) - bool GetInputParameter(const FName& InParameterName, UHoudiniPublicAPIInput*& OutInput); - - /** - * Set a parameter-based inputs via a map, - * @param InInputs A map of input parameter names to input wrapper to use to set inputs on the instantiated asset. - * @return true if all inputs from InInputs were set successfully. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) - bool SetInputParameters(const TMap& InInputs); - - /** - * Get a parameter-based inputs as a map - * @param OutInputs All parameter inputs as a map, with the input parameter name as key. The input configuration is copied - * from instantiated asset, and changing an input property from the entry in this map will not affect the - * instantiated asset until a subsequent SetInputParameters() call or SetInputParameter() call. - * @return false if the wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) - bool GetInputParameters(TMap& OutInputs); - - // Outputs - - /** - * Gets the number of outputs of the instantiated asset. - * @return the number of outputs of the instantiated asset. -1 if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - int32 GetNumOutputs() const; - - /** - * Gets the output type of the output at index InIndex. - * @param InIndex The output index to get the type for. - * @return the output type of the output at index InIndex. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - EHoudiniOutputType GetOutputTypeAt(const int32 InIndex) const; - - /** - * Populates OutIdentifiers with the output object identifiers of all the output objects of the output at InIndex. - * @param InIndex The output index to get output identifiers for. - * @param OutIdentifiers The output identifiers of the output objects at output index InIndex. - * @return false if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetOutputIdentifiersAt(const int32 InIndex, TArray& OutIdentifiers) const; - - /** - * Gets the output object at InIndex identified by InIdentifier. - * @param InIndex The output index to get output object from. - * @param InIdentifier The output identifier of the output object to get from output index InIndex. - * @return nullptr if the index/identifier is invalid or if the asset/wrapper is invalid, otherwise the output - * object. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - UObject* GetOutputObjectAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; - - /** - * Gets the output component at InIndex identified by InIdentifier. - * @param InIndex The output index to get output component from. - * @param InIdentifier The output identifier of the output component to get from output index InIndex. - * @return nullptr if the index/identifier is invalid or if the asset/wrapper is invalid, otherwise the output - * component. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - UObject* GetOutputComponentAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; - - /** - * Gets the output's fallback BakeName (as configured on the output details panel) for the output at InIndex - * identified by InIdentifier. - * @param InIndex The output index of the output object to get fallback BakeName for. - * @param InIdentifier The output identifier of the output object to get fallback BakeName for. - * @param OutBakeName The fallback BakeName configured for the output object identified by InIndex and InIdentifier. - * @return false if the index/identifier is invalid or if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetOutputBakeNameFallbackAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, FString& OutBakeName) const; - - /** - * Sets the output's fallback BakeName (as configured on the output details panel) for the output at InIndex - * identified by InIdentifier. - * @param InIndex The output index of the output object to set fallback BakeName for. - * @param InIdentifier The output identifier of the output object to set fallback BakeName for. - * @param InBakeName The fallback BakeName to set for the output object identified by InIndex and InIdentifier. - * @return false if the index/identifier is invalid or if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetOutputBakeNameFallbackAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FString& InBakeName); - - /** - * Bake the specified output object to the content browser. - * @param InIndex The output index of the output object to bake. - * @param InIdentifier The output identifier of the output object to bake. - * @param InBakeName The bake name to bake with. - * @param InLandscapeBakeType For landscape assets, the output bake type. - * @return true if the output was baked successfully, false if the wrapper/asset is invalid, or the output index - * and output identifier combination is invalid, or if baking failed. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool BakeOutputObjectAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FName InBakeName=NAME_None, const EHoudiniLandscapeOutputBakeType InLandscapeBakeType=EHoudiniLandscapeOutputBakeType::InValid); - - /** - * Returns true if the wrapped asset has any proxy output on any outputs. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool HasAnyCurrentProxyOutput() const; - - /** - * Returns true if the wrapped asset has any proxy output at output InIndex. - * @param InIndex The output index to check for proxies. - * @return true if the wrapped asset has any proxy output at output InIndex. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool HasAnyCurrentProxyOutputAt(const int32 InIndex) const; - - /** - * Returns true if the output object at output InIndex with identifier InIdentifier is a proxy. - * @param InIndex The output index of the output object to check. - * @param InIdentifier The output identifier of the output object at output index InIndex to check. - * @return true if the output object at output InIndex with identifier InIdentifier is a proxy. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool IsOutputCurrentProxyAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; - - // Proxy mesh - - /** - * Refines all current proxy mesh outputs (if any) to static mesh. This could trigger a cook if the asset is loaded - * and has no cook data. - * @param bInSilent If true, then slate notifications about the refinement process are not shown. - * @return Whether the refinement process is needed, requires a pending asynchronous cook, or was completed - * synchronously. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - EHoudiniProxyRefineRequestResult RefineAllCurrentProxyOutputs(const bool bInSilent); - - // PDG - - /** - * Returns true if the wrapped asset is valid and has a PDG asset link. - * @return true if the wrapped asset is valid and has a PDG asset link. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool HasPDGAssetLink() const; - - /** - * Gets the paths (relative to the instantiated asset) of all TOP networks in the HDA. - * @param OutTOPNetworkPaths The relative paths of the TOP networks in the HDA. - * @return false if the asset/wrapper is invalid, or does not contain any TOP networks. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetPDGTOPNetworkPaths(TArray& OutTOPNetworkPaths) const; - - /** - * Gets the paths (relative to the specified TOP network) of all TOP nodes in the network. - * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by - * GetPDGTOPNetworkPaths(), to fetch TOP node paths for. - * @return false if the asset/wrapper is invalid, or does not contain the specified TOP network. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetPDGTOPNodePaths(const FString& InNetworkRelativePath, TArray& OutTOPNodePaths) const; - - /** - * Dirty all TOP networks in this asset. - * @return true if TOP networks were dirtied. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool PDGDirtyAllNetworks(); - - /** - * Dirty the specified TOP network. - * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by - * GetPDGTOPNetworkPaths(). - * @return true if the TOP network was dirtied. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool PDGDirtyNetwork(const FString& InNetworkRelativePath); - - /** - * Dirty the specified TOP node. - * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by - * GetPDGTOPNetworkPaths(). - * @param InNodeRelativePath The relative path of the TOP node inside the specified TOP network. - * @return true if TOP nodes were dirtied. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool PDGDirtyNode(const FString& InNetworkRelativePath, const FString& InNodeRelativePath); - - // // Cook all outputs for all TOP networks in this asset. - // // Returns true if TOP networks were set to cook. - // UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - // bool PDGCookOutputsForAllNetworks(); - - /** - * Cook all outputs for the specified TOP network. - * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by - * GetPDGTOPNetworkPaths(). - * @return true if the TOP network was set to cook. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool PDGCookOutputsForNetwork(const FString& InNetworkRelativePath); - - /** - * Cook the specified TOP node. - * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by - * GetPDGTOPNetworkPaths(). - * @param InNodeRelativePath The relative path of the TOP node inside the specified TOP network. - * @return true if the TOP node was set to cook. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool PDGCookNode(const FString& InNetworkRelativePath, const FString& InNodeRelativePath); - - /** - * Bake all outputs of the instantiated asset's PDG contexts using the settings configured on the asset. - * @return true if the bake was successful. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool PDGBakeAllOutputs(); - - /** - * Bake all outputs of the instantiated asset's PDG contexts using the specified settings. - * @param InBakeOption The bake option (to actors, blueprints or foliage). - * @param InBakeSelection Whether to bake outputs from all networks, the selected network or the selected node. - * @param InBakeReplacementMode Whether to replace previous bake results/existing assets with the same name - * when baking. - * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center. Defaults to false. - * @return true if the bake was successful. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool PDGBakeAllOutputsWithSettings( - const EHoudiniEngineBakeOption InBakeOption, - const EPDGBakeSelectionOption InBakeSelection, - const EPDGBakePackageReplaceModeOption InBakeReplacementMode, - const bool bInRecenterBakedActors=false); - - /** - * Set whether to automatically bake PDG work items after a successfully loading them. - * @param bInAutoBakeEnabled If true, automatically bake work items after successfully loading them. - * @return false if the asset/wrapper is invalid, or does not contain a TOP network. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetPDGAutoBakeEnabled(const bool bInAutoBakeEnabled); - - /** Returns true if PDG auto bake is enabled. See SetPDGAutoBakeEnabled(). */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool IsPDGAutoBakeEnabled() const; - - /** - * Sets the bake method to use for PDG baking (to actor, blueprint, foliage). - * @param InBakeMethod The new bake method to set. - * @return false if the asset/wrapper is invalid. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetPDGBakeMethod(const EHoudiniEngineBakeOption InBakeMethod); - - /** - * Gets the currently set bake method to use for PDG baking (to actor, blueprint, foliage). - * @param OutBakeMethod The current bake method. - * @return false if the asset/wrapper is invalid or does not contain a TOP network. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetPDGBakeMethod(EHoudiniEngineBakeOption& OutBakeMethod); - - /** - * Set which outputs to bake for PDG, for example, all, selected network, selected node - * @param InBakeSelection The new bake selection. - * @return false if the asset/wrapper is invalid or does not contain a TOP network. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetPDGBakeSelection(const EPDGBakeSelectionOption InBakeSelection); - - /** - * Get which outputs to bake for PDG, for example, all, selected network, selected node - * @param OutBakeSelection The current bake selection setting. - * @return false if the asset/wrapper is invalid or does not contain a TOP network. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetPDGBakeSelection(EPDGBakeSelectionOption& OutBakeSelection); - - /** - * Setter for the bRecenterBakedActors property, that determines if baked actors are recentered to their bounding - * box center after a PDG bake, on the PDG asset link. - * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center after bake (PDG) - * @return false if the asset/wrapper is invalid or does not contain a TOP network. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetPDGRecenterBakedActors(const bool bInRecenterBakedActors); - - /** - * Getter for the bRecenterBakedActors property, that determines if baked actors are recentered to their bounding - * box center after a PDG bake, on the PDG asset link. - * @return true if baked actors should be recentered to their bounding box center after bake (PDG) - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetPDGRecenterBakedActors() const; - - /** - * Set the replacement mode to use for PDG baking (replace previous bake output vs increment) - * @param InBakingReplacementMode The new replacement mode to set. - * @return false if the asset/wrapper is invalid or does not contain a TOP network. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool SetPDGBakingReplacementMode(const EPDGBakePackageReplaceModeOption InBakingReplacementMode); - - /** - * Get the replacement mode to use for PDG baking (replace previous bake output vs increment) - * @param OutBakingReplacementMode The current replacement mode. - * @return false if the asset/wrapper is invalid or does not contain a TOP network. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - bool GetPDGBakingReplacementMode(EPDGBakePackageReplaceModeOption& OutBakingReplacementMode) const; - - /** - * Getter for the OnPreInstantiationDelegate, broadcast before the HDA is instantiated. The HDA's default parameter - * definitions are available, but the node has not yet been instantiated in HAPI/Houdini Engine. Parameter values - * can be set at this point. - */ - FOnHoudiniAssetStateChange& GetOnPreInstantiationDelegate() { return OnPreInstantiationDelegate; } - - /** - * Getter for the OnPostInstantiationDelegate, broadcast after the HDA is instantiated. This is a good place to - * set/configure inputs before the first cook. - */ - FOnHoudiniAssetStateChange& GetOnPostInstantiationDelegate() { return OnPostInstantiationDelegate; } - - /** - * Getter for the OnPostCookDelegate, broadcast after the HDA has cooked. - */ - FOnHoudiniAssetPostCook& GetOnPostCookDelegate() { return OnPostCookDelegate; } - - /** - * Getter for the OnPreProcessStateExitedDelegate, broadcast after the output pre-processing phase of the HDA, - * but before it enters the post processing phase. When this delegate is broadcast output objects/assets have not - * yet been created. - */ - FOnHoudiniAssetStateChange& GetOnPreProcessStateExitedDelegate() { return OnPreProcessStateExitedDelegate; } - - /** - * Getter for the OnPostProcessingDelegate, broadcast after the HDA has processed its outputs and created output - * objects/assets. - */ - FOnHoudiniAssetStateChange& GetOnPostProcessingDelegate() { return OnPostProcessingDelegate; } - - /** - * Getter for the OnPostBakeDelegate, broadcast after the HDA has finished baking outputs (not called for - * individual outputs that are baked to the content browser). - */ - FOnHoudiniAssetPostBake& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } - - /** - * Getter for the OnPostPDGTOPNetworkCookDelegate, broadcast after the HDA/PDG has cooked a TOP Network. Work item - * results have not necessarily yet been loaded. - */ - FOnHoudiniAssetPostCook& GetOnPostPDGTOPNetworkCookDelegate() { return OnPostPDGTOPNetworkCookDelegate; } - - /** - * Getter for the OnPostPDGBakeDelegate, broadcast after the HDA/PDG has finished baking outputs (not called for - * individual outputs that are baked to the content browser). - */ - FOnHoudiniAssetPostBake& GetOnPostPDGBakeDelegate() { return OnPostPDGBakeDelegate; } - - /** - * Getter for the OnProxyMeshesRefinedDelegate, broadcast for each proxy mesh of the HDA's outputs that has been - * refined to a UStaticMesh. - */ - FOnHoudiniAssetProxyMeshesRefinedDelegate& GetOnProxyMeshesRefinedDelegate() { return OnProxyMeshesRefinedDelegate; } - -protected: - - /** This will unwrap/unbind the currently wrapped instantiated asset. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") - void ClearHoudiniAssetObject(); - - /** - * Attempt to bind to the asset's PDG asset link, if it has one, and if the wrapper is not already bound to its - * events. - */ - UFUNCTION() - bool BindToPDGAssetLink(); - - /** Handler that is bound to the wrapped HAC's state change delegate. */ - UFUNCTION() - void HandleOnHoudiniAssetComponentStateChange(UHoudiniAssetComponent* InHAC, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState); - - /** Handler that is bound to the wrapped HAC's PostCook delegate. */ - UFUNCTION() - void HandleOnHoudiniAssetComponentPostCook(UHoudiniAssetComponent* InHAC, const bool bInCookSuccess); - - /** Handler that is bound to the wrapped HAC's PostBake delegate. */ - UFUNCTION() - void HandleOnHoudiniAssetComponentPostBake(UHoudiniAssetComponent* InHAC, const bool bInBakeSuccess); - - /** Handler that is bound to the wrapped PDG asset link's OnPostTOPNetworkCookDelegate delegate. */ - UFUNCTION() - void HandleOnHoudiniPDGAssetLinkTOPNetPostCook(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InTOPNet, const bool bInAnyWorkItemsFailed); - - /** Handler that is bound to the wrapped PDG asset link's OnPostBake delegate. */ - UFUNCTION() - void HandleOnHoudiniPDGAssetLinkPostBake(UHoudiniPDGAssetLink* InPDGAssetLink, const bool bInBakeSuccess); - - /** - * Handler that is bound to FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefined(). It is called for any HAC - * that has its proxy meshes refined. If relevant for this wrapped asset, then - * #OnProxyMeshesRefinedDelegate is broadcast. - */ - UFUNCTION() - void HandleOnHoudiniProxyMeshesRefinedGlobal(UHoudiniAssetComponent* InHAC, const EHoudiniProxyRefineResult InResult); - - /** - * Helper function for getting the instantiated asset's AHoudiniAssetActor. If there is no valid - * AHoudiniAssetActor an error is set with SetErrorMessage() and false is returned. - * @param OutActor Set to the AHoudiniAssetActor of the wrapped asset, if found. - * @return true if the wrapped asset has/is a valid AHoudiniAssetActor, false otherwise. - */ - bool GetValidHoudiniAssetActorWithError(AHoudiniAssetActor*& OutActor) const; - - /** - * Helper function for getting the instantiated asset's UHoudiniAssetComponent. If there is no valid - * HoudiniAssetComponent an error is set with SetErrorMessage() and false is returned. - * @param OutHAC Set to the HoudiniAssetComponent of the wrapped asset, if found. - * @return true if the wrapped asset has a valid HoudiniAssetComponent, false otherwise. - */ - bool GetValidHoudiniAssetComponentWithError(UHoudiniAssetComponent*& OutHAC) const; - - /** - * Helper function for getting a valid output at the specified index. If there is no valid - * UHoudiniOutput at that index (either the index is out of range or the output is null/invalid) an error is set - * with SetErrorMessage() and false is returned. - * @param InOutputIndex The output index. - * @param OutOutput Set to the valid UHoudiniOutput at InOutputIndex, if found. - * @return true if there is a valid UHoudiniOutput at index InOutputIndex. - */ - bool GetValidOutputAtWithError(const int32 InOutputIndex, UHoudiniOutput*& OutOutput) const; - - /** Helper function for getting the instantiated asset's PDG asset link. */ - UHoudiniPDGAssetLink* GetHoudiniPDGAssetLink() const; - - /** - * Helper function for getting the instantiated asset's UHoudiniPDGAssetLink. If there is no valid - * UHoudiniPDGAssetLink an error is set with SetErrorMessage() and false is returned. - * @param OutAssetLink Set to the UHoudiniPDGAssetLink of the wrapped asset, if found. - * @return true if the wrapped asset has a valid UHoudiniPDGAssetLink, false otherwise. - */ - bool GetValidHoudiniPDGAssetLinkWithError(UHoudiniPDGAssetLink*& OutAssetLink) const; - - /** Helper function for getting a valid parameter by name */ - UHoudiniParameter* FindValidParameterByName(const FName& InParameterTupleName) const; - - /** - * Helper function to find the appropriate array index for a ramp point. - * @param InParam The parameter. - * @param InIndex The index of the ramp point to get. If INDEX_NONE, all points are fetched. - * @param OutPointData Array populated with all fetched points. The bool in the pair is true if the - * object is UHoudiniParameterRampFloatPoint or UHoudiniParameterRampColorPoint and false if it is - * UHoudiniParameterRampModificationEvent. - * @return true if the point was, or all points were, fetched successfully. - */ - bool FindRampPointData( - UHoudiniParameter* const InParam, const int32 InIndex, TArray>& OutPointData) const; - - /** - * Set the position, value and interpolation of a point of a ramp parameter. - * Supported parameter types: - * - FloatRamp - * - ColorRamp - * @param InParameterTupleName The name of the parameter tuple. - * @param InPointIndex The index of the ramp point to set. - * @param InPosition The point position [0, 1]. - * @param InFloatValue The float value at the point (if this is a float ramp). - * @param InColorValue The color value at the point (if this is a color ramp). - * @param InInterpolation The interpolation of the point. - * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the - * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. - * @return true if the value was set. - */ - bool SetRampParameterPointValue( - FName InParameterTupleName, - const int32 InPointIndex, - const float InPosition, - const float InFloatValue, - const FLinearColor& InColorValue, - const EHoudiniPublicAPIRampInterpolationType InInterpolation, - const bool bInMarkChanged=true); - - /** - * Get the position, value and interpolation of a point of a ramp parameter. - * Supported parameter types: - * - FloatRamp - * - ColorRamp - * @param InParameterTupleName The name of the parameter tuple. - * @param InPointIndex The index of the ramp point to get. - * @param OutPosition The point position [0, 1]. - * @param OutFloatValue The float value at the point (if this is a float ramp). - * @param OutColorValue The color value at the point (if this is a color ramp). - * @param OutInterpolation The interpolation of the point. - * @return True if the parameter was found and output values set. - */ - bool GetRampParameterPointValue( - FName InParameterTupleName, - const int32 InPointIndex, - float& OutPosition, - float& OutFloatValue, - FLinearColor& OutColorValue, - EHoudiniPublicAPIRampInterpolationType& OutInterpolation) const; - - /** Helper function for getting an input by node index */ - UHoudiniInput* GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex); - const UHoudiniInput* GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) const; - - /** Helper function for getting an input by parameter name */ - UHoudiniInput* FindValidHoudiniNodeInputParameter(const FName& InInputParameterName); - const UHoudiniInput* FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) const; - - /** Helper function for populating a UHoudiniPublicAPIInput from a UHoudiniInput */ - bool CreateAndPopulateAPIInput(const UHoudiniInput* InHoudiniInput, UHoudiniPublicAPIInput*& OutAPIInput); - /** Helper function for populating a UHoudiniInput from a UHoudiniPublicAPIInput */ - bool PopulateHoudiniInput(const UHoudiniPublicAPIInput* InAPIInput, UHoudiniInput* InHoudiniInput) const; - - /** - * Helper functions for getting a TOP network by path. If the TOP network could not be found, an error is set - * with SetErrorMessage, and false is returned. - * @param InNetworkRelativePath The relative path to the network inside the asset. - * @param OutNetworkIndex The index to the network in the asset link's AllNetworks array. - * @param OutNetwork The network that was found at InNetworkRelativePath. - * @return true if the network was found and is valid, false otherwise. - */ - bool GetValidTOPNetworkByPathWithError(const FString& InNetworkRelativePath, int32& OutNetworkIndex, UTOPNetwork*& OutNetwork) const; - - /** - * Helper functions for getting a TOP node by path. If the TOP node could not be found, an error is set - * with SetErrorMessage, and false is returned. - * @param InNetworkRelativePath The relative path to the network inside the asset. - * @param InNodeRelativePath The relative path to the node inside the network. - * @param OutNetworkIndex The index to the network in the asset link's AllTOPNetworks array. - * @param OutNodeIndex The index to the TOP node in the network's AllTOPNodes array. - * @param OutNode The node that was found. - * @return true if the node was found and is valid, false otherwise. - */ - bool GetValidTOPNodeByPathWithError( - const FString& InNetworkRelativePath, - const FString& InNodeRelativePath, - int32& OutNetworkIndex, - int32& OutNodeIndex, - UTOPNode*& OutNode) const; - - /** The wrapped Houdini Asset object (not the uasset, an AHoudiniAssetActor or UHoudiniAssetComponent). */ - UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") - TSoftObjectPtr HoudiniAssetObject; - - /** The wrapped AHoudiniAssetActor (derived from HoudiniAssetObject when calling WrapHoudiniAssetObject()). */ - UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") - TSoftObjectPtr CachedHoudiniAssetActor; - - /** The wrapped UHoudiniAssetComponent (derived from HoudiniAssetObject when calling WrapHoudiniAssetObject()). */ - UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") - TSoftObjectPtr CachedHoudiniAssetComponent; - - /** - * Delegate that is broadcast when entering the PreInstantiation state: the HDA's default parameter definitions are - * available, but the node has not yet been instantiated in HAPI/Houdini Engine. Parameters can be set at this point. - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnHoudiniAssetStateChange OnPreInstantiationDelegate; - - /** - * Delegate that is broadcast after the asset was successfully instantiated. This is a good place to set/configure - * inputs before the first cook. - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnHoudiniAssetStateChange OnPostInstantiationDelegate; - - /** Delegate that is broadcast after a cook completes. Output objects/assets have not yet been created/updated. */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnHoudiniAssetPostCook OnPostCookDelegate; - - /** Delegate that is broadcast when PreProcess is exited. Output objects/assets have not yet been created/updated. */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnHoudiniAssetStateChange OnPreProcessStateExitedDelegate; - - /** - * Delegate that is broadcast when the Processing state is exited and the None state is entered. Output objects - * assets have been created/updated. - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnHoudiniAssetStateChange OnPostProcessingDelegate; - - /** - * Delegate that is broadcast after baking the asset (not called for individual outputs that are baked to the - * content browser). - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnHoudiniAssetPostBake OnPostBakeDelegate; - - /** - * Delegate that is broadcast after a cook of a TOP network completes. Work item results have not necessarily yet - * been loaded. - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnHoudiniAssetPostCook OnPostPDGTOPNetworkCookDelegate; - - /** - * Delegate that is broadcast after baking PDG outputs (not called for individual outputs that are baked to the - * content browser). - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnHoudiniAssetPostBake OnPostPDGBakeDelegate; - - /** Delegate that is broadcast after refining all proxy meshes for this wrapped asset. */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnHoudiniAssetProxyMeshesRefinedDelegate OnProxyMeshesRefinedDelegate; - - /** - * This starts as false and is set to true in HandleOnHoudiniAssetComponentStateChange during post instantiation, - * once we have checked for a PDG asset link and configured the bindings if there is one. - */ - UPROPERTY() - bool bAssetLinkSetupAttemptComplete; - - // Delegate handles - - /** Handle for the binding to the HAC's asset state change delegate */ - FDelegateHandle OnAssetStateChangeDelegateHandle; - /** Handle for the binding to the HAC's post cook delegate */ - FDelegateHandle OnPostCookDelegateHandle; - /** Handle for the binding to the HAC's post bake delegate */ - FDelegateHandle OnPostBakeDelegateHandle; - /** Handle for the binding to the global proxy mesh refined delegate */ - FDelegateHandle OnHoudiniProxyMeshesRefinedDelegateHandle; - /** Handle for the binding to the HAC's PDG asset link's post TOP Network cook delegate */ - FDelegateHandle OnPDGPostTOPNetworkCookDelegateHandle; - /** Handle for the binding to the HAC's PDG asset link's post bake delegate */ - FDelegateHandle OnPDGPostBakeDelegateHandle; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "GameFramework/Actor.h" + +#include "HoudiniPublicAPIObjectBase.h" +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIOutputTypes.h" +#include "HoudiniEngineRuntimeCommon.h" + +#include "HoudiniPublicAPIAssetWrapper.generated.h" + + +class UHoudiniPublicAPIInput; +class UHoudiniOutput; +class UHoudiniParameter; +class UHoudiniInput; +class UTOPNode; +class UHoudiniAssetComponent; +class AHoudiniAssetActor; + +/** + * The base class of a struct for Houdini Ramp points. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIRampPoint +{ + GENERATED_BODY(); + +public: + FHoudiniPublicAPIRampPoint(); + + FHoudiniPublicAPIRampPoint( + const float InPosition, + const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); + + /** The position of the point on the Ramp's x-axis [0,1]. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + float Position; + + /** The interpolation type of the point. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + EHoudiniPublicAPIRampInterpolationType Interpolation; +}; + +/** + * A struct for Houdini float ramp points. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIFloatRampPoint : public FHoudiniPublicAPIRampPoint +{ + GENERATED_BODY(); + +public: + + FHoudiniPublicAPIFloatRampPoint(); + + FHoudiniPublicAPIFloatRampPoint( + const float InPosition, + const float InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); + + /** The value of the point. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + float Value; + +}; + +/** + * A struct for Houdini color ramp points. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniPublicAPIColorRampPoint : public FHoudiniPublicAPIRampPoint +{ + GENERATED_BODY(); + +public: + + FHoudiniPublicAPIColorRampPoint(); + + FHoudiniPublicAPIColorRampPoint( + const float InPosition, + const FLinearColor& InValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation=EHoudiniPublicAPIRampInterpolationType::LINEAR); + + /** The value of the point. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + FLinearColor Value; + +}; + +/** + * A struct for storing the values of a Houdini parameter tuple. + * Currently supports bool, int32, float and FString storage. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API") +struct HOUDINIENGINEEDITOR_API FHoudiniParameterTuple +{ + GENERATED_BODY(); + +public: + FHoudiniParameterTuple(); + + /** + * Wrap a single bool value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const bool& InValue); + /** + * Wrap a bool tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap a single int32 value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const int32& InValue); + /** + * Wrap a int32 tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap a single float value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const float& InValue); + /** + * Wrap a float tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap a single FString value. + * @param InValue The value to wrap. + */ + FHoudiniParameterTuple(const FString& InValue); + /** + * Wrap a FString tuple. + * @param InValues The tuple to wrap. + */ + FHoudiniParameterTuple(const TArray& InValues); + + /** + * Wrap float ramp points + * @param InRampPoints The float ramp points. + */ + FHoudiniParameterTuple(const TArray& InRampPoints); + + /** + * Wrap color ramp points + * @param InRampPoints The color ramp points. + */ + FHoudiniParameterTuple(const TArray& InRampPoints); + + // Parameter tuple storage + + /** For bool compatible parameters, the bool parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray BoolValues; + + /** For int32 compatible parameters, the int32 parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray Int32Values; + + /** For float compatible parameters, the float parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray FloatValues; + + /** For string compatible parameters, the string parameter tuple values. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray StringValues; + + /** For float ramp parameters, the ramp points. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray FloatRampPoints; + + /** For color ramp parameters, the ramp points. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API") + TArray ColorRampPoints; +}; + +/** + * A wrapper for spawned/instantiating HDAs. + * + * The wrapper/HDA should be instantiated via UHoudiniPublicAPI::InstantiateAsset(). Alternatively an empty + * wrapper can be created via UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper() and an HDA later instantiated and + * assigned to the wrapper via UHoudiniPublicAPI::InstantiateAssetWithExistingWrapper(). + * + * The wrapper provides functionality for interacting/manipulating a + * AHoudiniAssetActor / UHoudiniAssetComponent: + * - Get/Set Inputs + * - Get/Set Parameters + * - Manually initiate a cook/recook + * - Subscribe to delegates: + * - #OnPreInstantiationDelegate (good place to set parameter values before the first cook) + * - #OnPostInstantiationDelegate (good place to set/configure inputs before the first cook) + * - #OnPostCookDelegate + * - #OnPreProcessStateExitedDelegate + * - #OnPostProcessingDelegate (output objects are available if the cook was successful) + * - #OnPostBakeDelegate + * - #OnPostPDGTOPNetworkCookDelegate + * - #OnPostPDGBakeDelegate + * - #OnProxyMeshesRefinedDelegate + * - Iterate over outputs and find the output assets + * - Bake outputs + * - PDG: Dirty all, cook outputs + */ +UCLASS(BlueprintType, Blueprintable, Category="Houdini Engine|Public API") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIAssetWrapper(); + + // Delegate types + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHoudiniAssetStateChange, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetPostCook, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper, const bool, bInCookSuccess); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetPostBake, UHoudiniPublicAPIAssetWrapper*, InAssetWrapper, const bool, bInBakeSuccess); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoudiniAssetProxyMeshesRefinedDelegate, UHoudiniPublicAPIAssetWrapper* const, InAssetWrapper, const EHoudiniProxyRefineResult, InResult); + + /** + * Factory function for creating new wrapper instances around instantiated assets. + * @param InOuter The outer for the new wrapper instance. + * @param InHoudiniAssetActorOrComponent The AHoudiniAssetActor or UHoudiniAssetComponent to wrap. + * @return The newly instantiated wrapper that wraps InHoudiniAssetActor, or nullptr if the wrapper could not + * be created, or if InHoudiniAssetActorOrComponent is invalid or not of a supported type. + */ + UFUNCTION(BlueprintCallable, Category="Houdini|Public API") + static UHoudiniPublicAPIAssetWrapper* CreateWrapper(UObject* InOuter, UObject* InHoudiniAssetActorOrComponent); + + /** + * Factory function for creating a new empty wrapper instance. + * An instantiated actor can be wrapped using SetHoudiniAssetActor. + * @param InOuter the outer for the new wrapper instance. + * @return The newly instantiated wrapper. + */ + UFUNCTION(BlueprintCallable, Category="Houdini|Public API") + static UHoudiniPublicAPIAssetWrapper* CreateEmptyWrapper(UObject* InOuter); + + /** + * Checks if InObject can be wrapped by instances of UHoudiniPublicAPIAssetWrapper. + * @param InObject The object to check for compatiblity. + * @return true if InObject can be wrapped by instances of UHoudiniPublicAPIAssetWrapper. + */ + UFUNCTION(BlueprintCallable, Category="Houdini|Public API") + static bool CanWrapHoudiniObject(UObject* InObject); + + /** + * Wrap the specified instantiated houdini asset object. Supported objects are: AHoudiniAssetActor, + * UHoudiniAssetComponent. This will first unwrap/unbind the currently wrapped instantiated + * asset if InHoudiniAssetObjectToWrap is valid and of a supported class. + * + * If InHoudiniAssetObjectToWrap is nullptr, then this wrapper will unwrap/unbind the currently wrapped + * instantiated asset and return true. + * + * @param InHoudiniAssetObjectToWrap The object to wrap, or nullptr to unwrap/unbind if currently wrapping an + * asset. + * @return true if InHoudiniAssetObjectToWrap is valid, of a supported class and was successfully wrapped, or true + * if InHoudiniAssetObjectToWrap is nullptr. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool WrapHoudiniAssetObject(UObject* InHoudiniAssetObjectToWrap); + + // Accessors and mutators + + /** + * Get the wrapped instantiated houdini asset object. + * @return The wrapped instantiated houdini asset object. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UObject* GetHoudiniAssetObject() const; + FORCEINLINE + virtual UObject* GetHoudiniAssetObject_Implementation() const { return HoudiniAssetObject.Get(); } + + /** + * Helper function for getting the instantiated HDA asset actor, if HoudiniAssetObject is an AHoudiniAssetActor or + * a UHoudiniAssetComponent owned by a AHoudiniAssetActor. + * @return The instantiated AHoudiniAssetActor, if HoudiniAssetObject is an AHoudiniAssetActor or + * a UHoudiniAssetComponent owned by a AHoudiniAssetActor, otherwise nullptr. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + AHoudiniAssetActor* GetHoudiniAssetActor() const; + + /** + * Helper function for getting the UHoudiniAssetComponent of the HDA, if HoudiniAssetObject is a + * UHoudiniAssetComponent or an AHoudiniAssetActor. + * @return The instantiated AHoudiniAssetActor, if HoudiniAssetObject is a + * UHoudiniAssetComponent or an AHoudiniAssetActor, otherwise nullptr. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UHoudiniAssetComponent* GetHoudiniAssetComponent() const; + + /** + * Get the Temp Folder fallback as configured on asset details panel + * @param OutDirectoryPath The currently configured fallback temporary cook folder. + * @return true if the wrapper is valid and the value was fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetTemporaryCookFolder(FDirectoryPath& OutDirectoryPath) const; + + /** + * Set the Temp Folder fallback as configured on asset details panel. Returns true if the value was changed. + * @param InDirectoryPath The new temp folder fallback to set. + * @return true if the wrapper is valid and the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetTemporaryCookFolder(const FDirectoryPath& InDirectoryPath) const; + + /** + * Get the Bake Folder fallback as configured on asset details panel. + * @param OutDirectoryPath The current bake folder fallback. + * @return true if the wrapper is valid and the value was fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetBakeFolder(FDirectoryPath& OutDirectoryPath) const; + + /** + * Set the Bake Folder fallback as configured on asset details panel. Returns true if the value was changed. + * @param InDirectoryPath The new bake folder fallback. + * @return true if the wrapper is valid and the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetBakeFolder(const FDirectoryPath& InDirectoryPath) const; + + // Houdini Engine Actions + + /** Delete the instantiated asset from its level and mark the wrapper for destruction. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool DeleteInstantiatedAsset(); + + /** Rebuild the HDA node in Houdini. Returns true if the asset was successfully marked as needing to be rebuilt. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool Rebuild(); + + // Cooking + + /** Recook the asset. Returns true if the asset was successfully marked as needing to be cooked. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool Recook(); + + /** + * Enable or disable auto cooking of the asset (on parameter changes, input updates and transform changes, for + * example) + * @param bInSetEnabled Whether or not enable auto cooking. + * @return true if the value was changed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetAutoCookingEnabled(const bool bInSetEnabled); + + /** Returns true if auto cooking is enabled for this instantiated asset. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsAutoCookingEnabled() const; + + // Baking + + /** + * Bake all outputs of the instantiated asset using the settings configured on the asset. + * @return true if the wrapper is valid and the baking process was started. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool BakeAllOutputs(); + + /** + * Bake all outputs of the instantiated asset using the specified settings. + * @param InBakeOption The bake method/target, (to actor vs blueprint, for example). + * @param bInReplacePreviousBake If true, replace the previous bake output (assets + actor) with the + * new results. + * @param bInRemoveTempOutputsOnSuccess If true, the temporary outputs of the wrapper asset are removed + * after a successful bake. + * @param bInRecenterBakedActors If true, recenter the baked actors to their bounding box center after the bake. + * @return true if the wrapper is valid and the baking process was started. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool BakeAllOutputsWithSettings( + EHoudiniEngineBakeOption InBakeOption, + bool bInReplacePreviousBake=false, + bool bInRemoveTempOutputsOnSuccess=false, + bool bInRecenterBakedActors=false); + + /** + * Set whether to automatically bake all outputs after a successful cook. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetAutoBakeEnabled(const bool bInAutoBakeEnabled); + + /** Returns true if auto bake is enabled. See SetAutoBakeEnabled. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsAutoBakeEnabled() const; + + /** + * Sets the bake method to use (to actor, blueprint, foliage). + * @param InBakeMethod The new bake method to set. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetBakeMethod(const EHoudiniEngineBakeOption InBakeMethod); + + /** + * Gets the currently set bake method to use (to actor, blueprint, foliage). + * @param OutBakeMethod The current bake method. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetBakeMethod(EHoudiniEngineBakeOption& OutBakeMethod); + + /** + * Set the bRemoveOutputAfterBake property, that controls if temporary outputs are removed after a successful bake. + * @param bInRemoveOutputAfterBake If true, then after a successful bake, the HACs outputs will be cleared and + * removed. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetRemoveOutputAfterBake(const bool bInRemoveOutputAfterBake); + + /** + * Get the bRemoveOutputAfterBake property, that controls if temporary outputs are removed after a successful bake. + * @return true if bRemoveOutputAfterBake is true. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetRemoveOutputAfterBake() const; + + /** + * Set the bRecenterBakedActors property that controls if baked actors are recentered around their bounding box center. + * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center after bake. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetRecenterBakedActors(const bool bInRecenterBakedActors); + + /** Gets the bRecenterBakedActors property. If true, recenter baked actors to their bounding box center after bake. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetRecenterBakedActors() const; + + /** + * Set the bReplacePreviousBake property, if true, replace the previously baked output (if any) instead of creating + * new objects. + * @param bInReplacePreviousBake If true, replace the previously baked output (if any) instead of creating new + * objects. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetReplacePreviousBake(const bool bInReplacePreviousBake); + + /** Get the bReplacePreviousBake property. + * @return The value of bReplacePreviousBake. If true, previous bake output (if any) will be replaced by subsequent + * bakes. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetReplacePreviousBake() const; + + // Parameters + + /** + * Set the value of a float-based parameter. + * Supported parameter types: + * - Float + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. Defaults to 0. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetFloatParameterValue(FName InParameterTupleName, float InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a float parameter. Returns true if the parameter and index was found and the value set in OutValue. + * Supported parameter types: + * - Float + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the wrapper is valid and the parameter was found. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetFloatParameterValue(FName InParameterTupleName, float& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of a color parameter. + * Supported parameter types: + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetColorParameterValue(FName InParameterTupleName, const FLinearColor& InValue, bool bInMarkChanged=true); + + /** + * Get the value of a color parameter. Returns true if the parameter was found and the value set in OutValue. + * Supported parameter types: + * - Color + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetColorParameterValue(FName InParameterTupleName, FLinearColor& OutValue) const; + + /** + * Set the value of a int32 parameter. + * Supported parameter types: + * - Int + * - IntChoice + * - MultiParm + * - Toggle + * - Folder (set the folder as currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. Defaults to 0. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetIntParameterValue(FName InParameterTupleName, int32 InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a int32 parameter. + * Supported parameter types: + * - Int + * - IntChoice + * - MultiParm + * - Toggle + * - Folder (get if the folder is currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the parameter and index was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetIntParameterValue(FName InParameterTupleName, int32& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of a bool parameter. + * Supported parameter types: + * - Toggle + * - Folder (set the folder as currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetBoolParameterValue(FName InParameterTupleName, bool InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a bool parameter. + * Supported parameter types: + * - Toggle + * - Folder (get if the folder is currently shown) + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the parameter and index was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetBoolParameterValue(FName InParameterTupleName, bool& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of a String parameter. + * Supported parameter types: + * - String + * - StringChoice + * - StringAssetRef + * - File + * - FileDir + * - FileGeo + * - FileImage + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetStringParameterValue(FName InParameterTupleName, const FString& InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of a String parameter. + * Supported parameter types: + * - String + * - StringChoice + * - StringAssetRef + * - File + * - FileDir + * - FileGeo + * - FileImage + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return true if the parameter was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetStringParameterValue(FName InParameterTupleName, FString& OutValue, int32 InAtIndex=0) const; + + /** + * Set the value of an AssetRef parameter. + * Supported parameter types: + * - StringAssetRef + * @param InParameterTupleName The name of the parameter tuple. + * @param InValue The value to set. + * @param InAtIndex The index of the parameter in the parameter tuple to set the value at. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set or the parameter already had the given value. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetAssetRefParameterValue(FName InParameterTupleName, UObject* InValue, int32 InAtIndex=0, bool bInMarkChanged=true); + + /** + * Get the value of an AssetRef parameter. + * Supported parameter types: + * - StringAssetRef + * @param InParameterTupleName The name of the parameter tuple. + * @param OutValue The value of the parameter that was fetched. + * @param InAtIndex The index of the parameter in the parameter tuple to get. Defaults to 0. + * @return True if the parameter was found and the value set in OutValue. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetAssetRefParameterValue(FName InParameterTupleName, UObject*& OutValue, int32 InAtIndex=0) const; + + /** + * Sets the number of points of the specified ramp parameter. This will insert or remove points from the end + * as necessary. + * @param InParameterTupleName The name of the parameter tuple. + * @param InNumPoints The new number of points to set. Must be >= 1. + * @return true if the parameter was found and the number of points set, or if the number of points was already InNumPoints. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetRampParameterNumPoints(FName InParameterTupleName, const int32 InNumPoints) const; + + /** + * Gets the number of points of the specified ramp parameter. + * @param InParameterTupleName The name of the parameter tuple. + * @param OutNumPoints The number of points the ramp has. + * @return true if the parameter was found and the number of points fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetRampParameterNumPoints(FName InParameterTupleName, int32& OutNumPoints) const; + + /** + * Set the position, value and interpolation of a point of a FloatRamp parameter. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to set. + * @param InPointPosition The point position to set [0, 1]. + * @param InPointValue The value to set for the point. + * @param InInterpolationType The interpolation to set at the point. Defaults to EHoudiniPublicAPIRampInterpolationType.Linear. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetFloatRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const float InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType=EHoudiniPublicAPIRampInterpolationType::LINEAR, + const bool bInMarkChanged=true); + + /** + * Get the position, value and interpolation of a point of a FloatRamp parameter. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to get. + * @param OutPointPosition The point position [0, 1]. + * @param OutPointValue The value at the point. + * @param OutInterpolationType The interpolation of the point. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetFloatRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + float& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const; + + /** + * Set all of the points (position, value and interpolation) of float ramp. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InRampPoints An array of structs to set as the ramp's points. + * @param bInMarkChanged If true, parameters are marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the values were set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetFloatRampParameterPoints( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged=true); + + /** + * Get the all of the points (position, value and interpolation) of a FloatRamp parameter. + * Supported parameter types: + * - FloatRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param OutRampPoints The array to populate with the ramp's points. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetFloatRampParameterPoints( + FName InParameterTupleName, + TArray& OutRampPoints) const; + + /** + * Set the position, value and interpolation of a point of a ColorRamp parameter. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to set. + * @param InPointPosition The point position to set [0, 1]. + * @param InPointValue The value to set for the point. + * @param InInterpolationType The interpolation to set at the point. Defaults to EHoudiniPublicAPIRampInterpolationType.Linear. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetColorRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPointPosition, + const FLinearColor& InPointValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolationType=EHoudiniPublicAPIRampInterpolationType::LINEAR, + const bool bInMarkChanged=true); + + /** + * Get the position, value and interpolation of a point of a ColorRamp parameter. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to get. + * @param OutPointPosition The point position [0, 1]. + * @param OutPointValue The value at the point. + * @param OutInterpolationType The interpolation of the point. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetColorRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPointPosition, + FLinearColor& OutPointValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolationType) const; + + /** + * Set all of the points (position, value and interpolation) of color ramp. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InRampPoints An array of structs to set as the ramp's points. + * @param bInMarkChanged If true, parameters are marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the values were set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetColorRampParameterPoints( + FName InParameterTupleName, + const TArray& InRampPoints, + const bool bInMarkChanged=true); + + /** + * Get the all of the points (position, value and interpolation) of a ColorRamp parameter. + * Supported parameter types: + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param OutRampPoints The array to populate with the ramp's points. + * @return True if the parameter was found and output values set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetColorRampParameterPoints( + FName InParameterTupleName, + TArray& OutRampPoints) const; + + /** + * Trigger / click the specified button parameter. + * @return True if the button was found and triggered/clicked, or was already marked to be triggered. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool TriggerButtonParameter(FName InButtonParameterName); + + /** + * Gets all parameter tuples (with their values) from this asset and outputs it to OutParameterTuples. + * @param OutParameterTuples Populated with all parameter tuples and their values. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetParameterTuples(TMap& OutParameterTuples) const; + + /** + * Sets all parameter tuple values (matched by name and compatible type) from InParameterTuples on this + * instantiated asset. + * @param InParameterTuples The parameter tuples to set. + * @return false if any entry in InParameterTuples could not be found on the asset or had an incompatible type/size. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetParameterTuples(const TMap& InParameterTuples); + + // Inputs + + /** + * Creates an empty input wrapper. + * @param InInputClass the class of the input to create. See the UHoudiniPublicAPIInput class hierarchy. + * @return The newly created input wrapper, or null if the input wrapper could not be created. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(DeterminesOutputType="InInputClass")) + UHoudiniPublicAPIInput* CreateEmptyInput(TSubclassOf InInputClass); + + /** + * Get the number of node inputs supported by the asset. + * @return The number of node inputs (inputs on the HDA node, excluding parameter-based inputs). Returns -1 if the + * asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UPARAM(DisplayName="OutNumNodeInputs") int32 GetNumNodeInputs() const; + + /** + * Set a node input at the specific index. + * @param InNodeInputIndex The index of the node input, starts at 0. + * @param InInput The input wrapper to use to set the input. + * @return false if the the input could not be set, for example, if the wrapper is invalid, or if the input index + * is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetInputAtIndex(const int32 InNodeInputIndex, const UHoudiniPublicAPIInput* InInput); + + /** + * Get the node input at the specific index and sets OutInput. This is a copy of the input structure. Changes + * properties in OutInput won't affect the instantiated HDA until a subsequent call to SetInputAtIndex. + * @param InNodeInputIndex The index of the node input to get. + * @param OutInput Copy of the input configuration and data for node input index InNodeInputIndex. + * @return false if the input could be fetched, for example if the wrapper is invalid or the input index is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetInputAtIndex(const int32 InNodeInputIndex, UHoudiniPublicAPIInput*& OutInput); + + /** + * Set node inputs at the specified indices via a map. + * @param InInputs A map of node input index to input wrapper to use to set inputs on the instantiated asset. + * @return true if all inputs from InInputs were set successfully. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetInputsAtIndices(const TMap& InInputs); + + /** + * Get all node inputs. + * @param OutInputs All node inputs as a map, with the node input index as key. The input configuration is copied + * from instantiated asset, and changing an input property from the entry in this map will not affect the + * instantiated asset until a subsequent SetInputsAtIndices() call or SetInputAtIndex() call. + * @return false if the wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetInputsAtIndices(TMap& OutInputs); + + /** + * Set a parameter-based input via parameter name. + * @param InParameterName The name of the input parameter. + * @param InInput The input wrapper to use to set/configure the input. + * @return false if the wrapper is invalid, InParameterName is not a valid input parameter, or if InInput could + * not be used to successfully configure/set the input. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool SetInputParameter(const FName& InParameterName, const UHoudiniPublicAPIInput* InInput); + + /** + * Get a parameter-based input via parameter name. + * @param InParameterName The name of the input parameter. + * @param OutInput A copy of the input configuration for InParameterName. + * @return false if the wrapper is invalid, InParameterName is not a valid input parameter, or the current input + * configuration could not be successfully copied to a new UHoudiniPublicAPIInput wrapper. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool GetInputParameter(const FName& InParameterName, UHoudiniPublicAPIInput*& OutInput); + + /** + * Set a parameter-based inputs via a map, + * @param InInputs A map of input parameter names to input wrapper to use to set inputs on the instantiated asset. + * @return true if all inputs from InInputs were set successfully. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool SetInputParameters(const TMap& InInputs); + + /** + * Get a parameter-based inputs as a map + * @param OutInputs All parameter inputs as a map, with the input parameter name as key. The input configuration is copied + * from instantiated asset, and changing an input property from the entry in this map will not affect the + * instantiated asset until a subsequent SetInputParameters() call or SetInputParameter() call. + * @return false if the wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API", Meta=(AutoCreateRefTerm="InParameterName")) + bool GetInputParameters(TMap& OutInputs); + + // Outputs + + /** + * Gets the number of outputs of the instantiated asset. + * @return the number of outputs of the instantiated asset. -1 if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + int32 GetNumOutputs() const; + + /** + * Gets the output type of the output at index InIndex. + * @param InIndex The output index to get the type for. + * @return the output type of the output at index InIndex. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + EHoudiniOutputType GetOutputTypeAt(const int32 InIndex) const; + + /** + * Populates OutIdentifiers with the output object identifiers of all the output objects of the output at InIndex. + * @param InIndex The output index to get output identifiers for. + * @param OutIdentifiers The output identifiers of the output objects at output index InIndex. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetOutputIdentifiersAt(const int32 InIndex, TArray& OutIdentifiers) const; + + /** + * Gets the output object at InIndex identified by InIdentifier. + * @param InIndex The output index to get output object from. + * @param InIdentifier The output identifier of the output object to get from output index InIndex. + * @return nullptr if the index/identifier is invalid or if the asset/wrapper is invalid, otherwise the output + * object. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UObject* GetOutputObjectAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; + + /** + * Gets the output component at InIndex identified by InIdentifier. + * @param InIndex The output index to get output component from. + * @param InIdentifier The output identifier of the output component to get from output index InIndex. + * @return nullptr if the index/identifier is invalid or if the asset/wrapper is invalid, otherwise the output + * component. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + UObject* GetOutputComponentAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; + + /** + * Gets the output's fallback BakeName (as configured on the output details panel) for the output at InIndex + * identified by InIdentifier. + * @param InIndex The output index of the output object to get fallback BakeName for. + * @param InIdentifier The output identifier of the output object to get fallback BakeName for. + * @param OutBakeName The fallback BakeName configured for the output object identified by InIndex and InIdentifier. + * @return false if the index/identifier is invalid or if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetOutputBakeNameFallbackAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, FString& OutBakeName) const; + + /** + * Sets the output's fallback BakeName (as configured on the output details panel) for the output at InIndex + * identified by InIdentifier. + * @param InIndex The output index of the output object to set fallback BakeName for. + * @param InIdentifier The output identifier of the output object to set fallback BakeName for. + * @param InBakeName The fallback BakeName to set for the output object identified by InIndex and InIdentifier. + * @return false if the index/identifier is invalid or if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetOutputBakeNameFallbackAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FString& InBakeName); + + /** + * Bake the specified output object to the content browser. + * @param InIndex The output index of the output object to bake. + * @param InIdentifier The output identifier of the output object to bake. + * @param InBakeName The bake name to bake with. + * @param InLandscapeBakeType For landscape assets, the output bake type. + * @return true if the output was baked successfully, false if the wrapper/asset is invalid, or the output index + * and output identifier combination is invalid, or if baking failed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool BakeOutputObjectAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier, const FName InBakeName=NAME_None, const EHoudiniLandscapeOutputBakeType InLandscapeBakeType=EHoudiniLandscapeOutputBakeType::InValid); + + /** + * Returns true if the wrapped asset has any proxy output on any outputs. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool HasAnyCurrentProxyOutput() const; + + /** + * Returns true if the wrapped asset has any proxy output at output InIndex. + * @param InIndex The output index to check for proxies. + * @return true if the wrapped asset has any proxy output at output InIndex. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool HasAnyCurrentProxyOutputAt(const int32 InIndex) const; + + /** + * Returns true if the output object at output InIndex with identifier InIdentifier is a proxy. + * @param InIndex The output index of the output object to check. + * @param InIdentifier The output identifier of the output object at output index InIndex to check. + * @return true if the output object at output InIndex with identifier InIdentifier is a proxy. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsOutputCurrentProxyAt(const int32 InIndex, const FHoudiniPublicAPIOutputObjectIdentifier& InIdentifier) const; + + // Proxy mesh + + /** + * Refines all current proxy mesh outputs (if any) to static mesh. This could trigger a cook if the asset is loaded + * and has no cook data. + * @param bInSilent If true, then slate notifications about the refinement process are not shown. + * @return Whether the refinement process is needed, requires a pending asynchronous cook, or was completed + * synchronously. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + EHoudiniProxyRefineRequestResult RefineAllCurrentProxyOutputs(const bool bInSilent); + + // PDG + + /** + * Returns true if the wrapped asset is valid and has a PDG asset link. + * @return true if the wrapped asset is valid and has a PDG asset link. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool HasPDGAssetLink() const; + + /** + * Gets the paths (relative to the instantiated asset) of all TOP networks in the HDA. + * @param OutTOPNetworkPaths The relative paths of the TOP networks in the HDA. + * @return false if the asset/wrapper is invalid, or does not contain any TOP networks. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGTOPNetworkPaths(TArray& OutTOPNetworkPaths) const; + + /** + * Gets the paths (relative to the specified TOP network) of all TOP nodes in the network. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(), to fetch TOP node paths for. + * @return false if the asset/wrapper is invalid, or does not contain the specified TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGTOPNodePaths(const FString& InNetworkRelativePath, TArray& OutTOPNodePaths) const; + + /** + * Dirty all TOP networks in this asset. + * @return true if TOP networks were dirtied. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGDirtyAllNetworks(); + + /** + * Dirty the specified TOP network. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @return true if the TOP network was dirtied. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGDirtyNetwork(const FString& InNetworkRelativePath); + + /** + * Dirty the specified TOP node. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @param InNodeRelativePath The relative path of the TOP node inside the specified TOP network. + * @return true if TOP nodes were dirtied. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGDirtyNode(const FString& InNetworkRelativePath, const FString& InNodeRelativePath); + + // // Cook all outputs for all TOP networks in this asset. + // // Returns true if TOP networks were set to cook. + // UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + // bool PDGCookOutputsForAllNetworks(); + + /** + * Cook all outputs for the specified TOP network. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @return true if the TOP network was set to cook. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGCookOutputsForNetwork(const FString& InNetworkRelativePath); + + /** + * Cook the specified TOP node. + * @param InNetworkRelativePath The relative path of the network inside the instantiated asset, as returned by + * GetPDGTOPNetworkPaths(). + * @param InNodeRelativePath The relative path of the TOP node inside the specified TOP network. + * @return true if the TOP node was set to cook. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGCookNode(const FString& InNetworkRelativePath, const FString& InNodeRelativePath); + + /** + * Bake all outputs of the instantiated asset's PDG contexts using the settings configured on the asset. + * @return true if the bake was successful. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGBakeAllOutputs(); + + /** + * Bake all outputs of the instantiated asset's PDG contexts using the specified settings. + * @param InBakeOption The bake option (to actors, blueprints or foliage). + * @param InBakeSelection Whether to bake outputs from all networks, the selected network or the selected node. + * @param InBakeReplacementMode Whether to replace previous bake results/existing assets with the same name + * when baking. + * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center. Defaults to false. + * @return true if the bake was successful. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool PDGBakeAllOutputsWithSettings( + const EHoudiniEngineBakeOption InBakeOption, + const EPDGBakeSelectionOption InBakeSelection, + const EPDGBakePackageReplaceModeOption InBakeReplacementMode, + const bool bInRecenterBakedActors=false); + + /** + * Set whether to automatically bake PDG work items after a successfully loading them. + * @param bInAutoBakeEnabled If true, automatically bake work items after successfully loading them. + * @return false if the asset/wrapper is invalid, or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGAutoBakeEnabled(const bool bInAutoBakeEnabled); + + /** Returns true if PDG auto bake is enabled. See SetPDGAutoBakeEnabled(). */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool IsPDGAutoBakeEnabled() const; + + /** + * Sets the bake method to use for PDG baking (to actor, blueprint, foliage). + * @param InBakeMethod The new bake method to set. + * @return false if the asset/wrapper is invalid. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGBakeMethod(const EHoudiniEngineBakeOption InBakeMethod); + + /** + * Gets the currently set bake method to use for PDG baking (to actor, blueprint, foliage). + * @param OutBakeMethod The current bake method. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGBakeMethod(EHoudiniEngineBakeOption& OutBakeMethod); + + /** + * Set which outputs to bake for PDG, for example, all, selected network, selected node + * @param InBakeSelection The new bake selection. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGBakeSelection(const EPDGBakeSelectionOption InBakeSelection); + + /** + * Get which outputs to bake for PDG, for example, all, selected network, selected node + * @param OutBakeSelection The current bake selection setting. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGBakeSelection(EPDGBakeSelectionOption& OutBakeSelection); + + /** + * Setter for the bRecenterBakedActors property, that determines if baked actors are recentered to their bounding + * box center after a PDG bake, on the PDG asset link. + * @param bInRecenterBakedActors If true, recenter baked actors to their bounding box center after bake (PDG) + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGRecenterBakedActors(const bool bInRecenterBakedActors); + + /** + * Getter for the bRecenterBakedActors property, that determines if baked actors are recentered to their bounding + * box center after a PDG bake, on the PDG asset link. + * @return true if baked actors should be recentered to their bounding box center after bake (PDG) + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGRecenterBakedActors() const; + + /** + * Set the replacement mode to use for PDG baking (replace previous bake output vs increment) + * @param InBakingReplacementMode The new replacement mode to set. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool SetPDGBakingReplacementMode(const EPDGBakePackageReplaceModeOption InBakingReplacementMode); + + /** + * Get the replacement mode to use for PDG baking (replace previous bake output vs increment) + * @param OutBakingReplacementMode The current replacement mode. + * @return false if the asset/wrapper is invalid or does not contain a TOP network. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + bool GetPDGBakingReplacementMode(EPDGBakePackageReplaceModeOption& OutBakingReplacementMode) const; + + /** + * Getter for the OnPreInstantiationDelegate, broadcast before the HDA is instantiated. The HDA's default parameter + * definitions are available, but the node has not yet been instantiated in HAPI/Houdini Engine. Parameter values + * can be set at this point. + */ + FOnHoudiniAssetStateChange& GetOnPreInstantiationDelegate() { return OnPreInstantiationDelegate; } + + /** + * Getter for the OnPostInstantiationDelegate, broadcast after the HDA is instantiated. This is a good place to + * set/configure inputs before the first cook. + */ + FOnHoudiniAssetStateChange& GetOnPostInstantiationDelegate() { return OnPostInstantiationDelegate; } + + /** + * Getter for the OnPostCookDelegate, broadcast after the HDA has cooked. + */ + FOnHoudiniAssetPostCook& GetOnPostCookDelegate() { return OnPostCookDelegate; } + + /** + * Getter for the OnPreProcessStateExitedDelegate, broadcast after the output pre-processing phase of the HDA, + * but before it enters the post processing phase. When this delegate is broadcast output objects/assets have not + * yet been created. + */ + FOnHoudiniAssetStateChange& GetOnPreProcessStateExitedDelegate() { return OnPreProcessStateExitedDelegate; } + + /** + * Getter for the OnPostProcessingDelegate, broadcast after the HDA has processed its outputs and created output + * objects/assets. + */ + FOnHoudiniAssetStateChange& GetOnPostProcessingDelegate() { return OnPostProcessingDelegate; } + + /** + * Getter for the OnPostBakeDelegate, broadcast after the HDA has finished baking outputs (not called for + * individual outputs that are baked to the content browser). + */ + FOnHoudiniAssetPostBake& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } + + /** + * Getter for the OnPostPDGTOPNetworkCookDelegate, broadcast after the HDA/PDG has cooked a TOP Network. Work item + * results have not necessarily yet been loaded. + */ + FOnHoudiniAssetPostCook& GetOnPostPDGTOPNetworkCookDelegate() { return OnPostPDGTOPNetworkCookDelegate; } + + /** + * Getter for the OnPostPDGBakeDelegate, broadcast after the HDA/PDG has finished baking outputs (not called for + * individual outputs that are baked to the content browser). + */ + FOnHoudiniAssetPostBake& GetOnPostPDGBakeDelegate() { return OnPostPDGBakeDelegate; } + + /** + * Getter for the OnProxyMeshesRefinedDelegate, broadcast for each proxy mesh of the HDA's outputs that has been + * refined to a UStaticMesh. + */ + FOnHoudiniAssetProxyMeshesRefinedDelegate& GetOnProxyMeshesRefinedDelegate() { return OnProxyMeshesRefinedDelegate; } + +protected: + + /** This will unwrap/unbind the currently wrapped instantiated asset. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") + void ClearHoudiniAssetObject(); + + /** + * Attempt to bind to the asset's PDG asset link, if it has one, and if the wrapper is not already bound to its + * events. + */ + UFUNCTION() + bool BindToPDGAssetLink(); + + /** Handler that is bound to the wrapped HAC's state change delegate. */ + UFUNCTION() + void HandleOnHoudiniAssetComponentStateChange(UHoudiniAssetComponent* InHAC, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState); + + /** Handler that is bound to the wrapped HAC's PostCook delegate. */ + UFUNCTION() + void HandleOnHoudiniAssetComponentPostCook(UHoudiniAssetComponent* InHAC, const bool bInCookSuccess); + + /** Handler that is bound to the wrapped HAC's PostBake delegate. */ + UFUNCTION() + void HandleOnHoudiniAssetComponentPostBake(UHoudiniAssetComponent* InHAC, const bool bInBakeSuccess); + + /** Handler that is bound to the wrapped PDG asset link's OnPostTOPNetworkCookDelegate delegate. */ + UFUNCTION() + void HandleOnHoudiniPDGAssetLinkTOPNetPostCook(UHoudiniPDGAssetLink* InPDGAssetLink, UTOPNetwork* InTOPNet, const bool bInAnyWorkItemsFailed); + + /** Handler that is bound to the wrapped PDG asset link's OnPostBake delegate. */ + UFUNCTION() + void HandleOnHoudiniPDGAssetLinkPostBake(UHoudiniPDGAssetLink* InPDGAssetLink, const bool bInBakeSuccess); + + /** + * Handler that is bound to FHoudiniEngineCommands::GetOnHoudiniProxyMeshesRefined(). It is called for any HAC + * that has its proxy meshes refined. If relevant for this wrapped asset, then + * #OnProxyMeshesRefinedDelegate is broadcast. + */ + UFUNCTION() + void HandleOnHoudiniProxyMeshesRefinedGlobal(UHoudiniAssetComponent* InHAC, const EHoudiniProxyRefineResult InResult); + + /** + * Helper function for getting the instantiated asset's AHoudiniAssetActor. If there is no valid + * AHoudiniAssetActor an error is set with SetErrorMessage() and false is returned. + * @param OutActor Set to the AHoudiniAssetActor of the wrapped asset, if found. + * @return true if the wrapped asset has/is a valid AHoudiniAssetActor, false otherwise. + */ + bool GetValidHoudiniAssetActorWithError(AHoudiniAssetActor*& OutActor) const; + + /** + * Helper function for getting the instantiated asset's UHoudiniAssetComponent. If there is no valid + * HoudiniAssetComponent an error is set with SetErrorMessage() and false is returned. + * @param OutHAC Set to the HoudiniAssetComponent of the wrapped asset, if found. + * @return true if the wrapped asset has a valid HoudiniAssetComponent, false otherwise. + */ + bool GetValidHoudiniAssetComponentWithError(UHoudiniAssetComponent*& OutHAC) const; + + /** + * Helper function for getting a valid output at the specified index. If there is no valid + * UHoudiniOutput at that index (either the index is out of range or the output is null/invalid) an error is set + * with SetErrorMessage() and false is returned. + * @param InOutputIndex The output index. + * @param OutOutput Set to the valid UHoudiniOutput at InOutputIndex, if found. + * @return true if there is a valid UHoudiniOutput at index InOutputIndex. + */ + bool GetValidOutputAtWithError(const int32 InOutputIndex, UHoudiniOutput*& OutOutput) const; + + /** Helper function for getting the instantiated asset's PDG asset link. */ + UHoudiniPDGAssetLink* GetHoudiniPDGAssetLink() const; + + /** + * Helper function for getting the instantiated asset's UHoudiniPDGAssetLink. If there is no valid + * UHoudiniPDGAssetLink an error is set with SetErrorMessage() and false is returned. + * @param OutAssetLink Set to the UHoudiniPDGAssetLink of the wrapped asset, if found. + * @return true if the wrapped asset has a valid UHoudiniPDGAssetLink, false otherwise. + */ + bool GetValidHoudiniPDGAssetLinkWithError(UHoudiniPDGAssetLink*& OutAssetLink) const; + + /** Helper function for getting a valid parameter by name */ + UHoudiniParameter* FindValidParameterByName(const FName& InParameterTupleName) const; + + /** + * Helper function to find the appropriate array index for a ramp point. + * @param InParam The parameter. + * @param InIndex The index of the ramp point to get. If INDEX_NONE, all points are fetched. + * @param OutPointData Array populated with all fetched points. The bool in the pair is true if the + * object is UHoudiniParameterRampFloatPoint or UHoudiniParameterRampColorPoint and false if it is + * UHoudiniParameterRampModificationEvent. + * @return true if the point was, or all points were, fetched successfully. + */ + bool FindRampPointData( + UHoudiniParameter* const InParam, const int32 InIndex, TArray>& OutPointData) const; + + /** + * Set the position, value and interpolation of a point of a ramp parameter. + * Supported parameter types: + * - FloatRamp + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to set. + * @param InPosition The point position [0, 1]. + * @param InFloatValue The float value at the point (if this is a float ramp). + * @param InColorValue The color value at the point (if this is a color ramp). + * @param InInterpolation The interpolation of the point. + * @param bInMarkChanged If true, the parameter is marked as changed and will be uploaded to Houdini before the + * next cook. If auto-cook triggers are enabled, this will also trigger a auto-cook. Defaults to true. + * @return true if the value was set. + */ + bool SetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + const float InPosition, + const float InFloatValue, + const FLinearColor& InColorValue, + const EHoudiniPublicAPIRampInterpolationType InInterpolation, + const bool bInMarkChanged=true); + + /** + * Get the position, value and interpolation of a point of a ramp parameter. + * Supported parameter types: + * - FloatRamp + * - ColorRamp + * @param InParameterTupleName The name of the parameter tuple. + * @param InPointIndex The index of the ramp point to get. + * @param OutPosition The point position [0, 1]. + * @param OutFloatValue The float value at the point (if this is a float ramp). + * @param OutColorValue The color value at the point (if this is a color ramp). + * @param OutInterpolation The interpolation of the point. + * @return True if the parameter was found and output values set. + */ + bool GetRampParameterPointValue( + FName InParameterTupleName, + const int32 InPointIndex, + float& OutPosition, + float& OutFloatValue, + FLinearColor& OutColorValue, + EHoudiniPublicAPIRampInterpolationType& OutInterpolation) const; + + /** Helper function for getting an input by node index */ + UHoudiniInput* GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex); + const UHoudiniInput* GetHoudiniNodeInputByIndex(const int32 InNodeInputIndex) const; + + /** Helper function for getting an input by parameter name */ + UHoudiniInput* FindValidHoudiniNodeInputParameter(const FName& InInputParameterName); + const UHoudiniInput* FindValidHoudiniNodeInputParameter(const FName& InInputParameterName) const; + + /** Helper function for populating a UHoudiniPublicAPIInput from a UHoudiniInput */ + bool CreateAndPopulateAPIInput(const UHoudiniInput* InHoudiniInput, UHoudiniPublicAPIInput*& OutAPIInput); + /** Helper function for populating a UHoudiniInput from a UHoudiniPublicAPIInput */ + bool PopulateHoudiniInput(const UHoudiniPublicAPIInput* InAPIInput, UHoudiniInput* InHoudiniInput) const; + + /** + * Helper functions for getting a TOP network by path. If the TOP network could not be found, an error is set + * with SetErrorMessage, and false is returned. + * @param InNetworkRelativePath The relative path to the network inside the asset. + * @param OutNetworkIndex The index to the network in the asset link's AllNetworks array. + * @param OutNetwork The network that was found at InNetworkRelativePath. + * @return true if the network was found and is valid, false otherwise. + */ + bool GetValidTOPNetworkByPathWithError(const FString& InNetworkRelativePath, int32& OutNetworkIndex, UTOPNetwork*& OutNetwork) const; + + /** + * Helper functions for getting a TOP node by path. If the TOP node could not be found, an error is set + * with SetErrorMessage, and false is returned. + * @param InNetworkRelativePath The relative path to the network inside the asset. + * @param InNodeRelativePath The relative path to the node inside the network. + * @param OutNetworkIndex The index to the network in the asset link's AllTOPNetworks array. + * @param OutNodeIndex The index to the TOP node in the network's AllTOPNodes array. + * @param OutNode The node that was found. + * @return true if the node was found and is valid, false otherwise. + */ + bool GetValidTOPNodeByPathWithError( + const FString& InNetworkRelativePath, + const FString& InNodeRelativePath, + int32& OutNetworkIndex, + int32& OutNodeIndex, + UTOPNode*& OutNode) const; + + /** The wrapped Houdini Asset object (not the uasset, an AHoudiniAssetActor or UHoudiniAssetComponent). */ + UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") + TSoftObjectPtr HoudiniAssetObject; + + /** The wrapped AHoudiniAssetActor (derived from HoudiniAssetObject when calling WrapHoudiniAssetObject()). */ + UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") + TSoftObjectPtr CachedHoudiniAssetActor; + + /** The wrapped UHoudiniAssetComponent (derived from HoudiniAssetObject when calling WrapHoudiniAssetObject()). */ + UPROPERTY(BlueprintReadOnly, Category="Houdini|Public API") + TSoftObjectPtr CachedHoudiniAssetComponent; + + /** + * Delegate that is broadcast when entering the PreInstantiation state: the HDA's default parameter definitions are + * available, but the node has not yet been instantiated in HAPI/Houdini Engine. Parameters can be set at this point. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPreInstantiationDelegate; + + /** + * Delegate that is broadcast after the asset was successfully instantiated. This is a good place to set/configure + * inputs before the first cook. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPostInstantiationDelegate; + + /** Delegate that is broadcast after a cook completes. Output objects/assets have not yet been created/updated. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostCook OnPostCookDelegate; + + /** Delegate that is broadcast when PreProcess is exited. Output objects/assets have not yet been created/updated. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPreProcessStateExitedDelegate; + + /** + * Delegate that is broadcast when the Processing state is exited and the None state is entered. Output objects + * assets have been created/updated. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetStateChange OnPostProcessingDelegate; + + /** + * Delegate that is broadcast after baking the asset (not called for individual outputs that are baked to the + * content browser). + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostBake OnPostBakeDelegate; + + /** + * Delegate that is broadcast after a cook of a TOP network completes. Work item results have not necessarily yet + * been loaded. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostCook OnPostPDGTOPNetworkCookDelegate; + + /** + * Delegate that is broadcast after baking PDG outputs (not called for individual outputs that are baked to the + * content browser). + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetPostBake OnPostPDGBakeDelegate; + + /** Delegate that is broadcast after refining all proxy meshes for this wrapped asset. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnHoudiniAssetProxyMeshesRefinedDelegate OnProxyMeshesRefinedDelegate; + + /** + * This starts as false and is set to true in HandleOnHoudiniAssetComponentStateChange during post instantiation, + * once we have checked for a PDG asset link and configured the bindings if there is one. + */ + UPROPERTY() + bool bAssetLinkSetupAttemptComplete; + + // Delegate handles + + /** Handle for the binding to the HAC's asset state change delegate */ + FDelegateHandle OnAssetStateChangeDelegateHandle; + /** Handle for the binding to the HAC's post cook delegate */ + FDelegateHandle OnPostCookDelegateHandle; + /** Handle for the binding to the HAC's post bake delegate */ + FDelegateHandle OnPostBakeDelegateHandle; + /** Handle for the binding to the global proxy mesh refined delegate */ + FDelegateHandle OnHoudiniProxyMeshesRefinedDelegateHandle; + /** Handle for the binding to the HAC's PDG asset link's post TOP Network cook delegate */ + FDelegateHandle OnPDGPostTOPNetworkCookDelegateHandle; + /** Handle for the binding to the HAC's PDG asset link's post bake delegate */ + FDelegateHandle OnPDGPostBakeDelegateHandle; + +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h index 173a83f99..d0b5ec1aa 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIBlueprintLib.h @@ -1,48 +1,48 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Kismet/BlueprintFunctionLibrary.h" - -#include "HoudiniPublicAPIBlueprintLib.generated.h" - -class UHoudiniPublicAPI; - -/** - * Houdini Public API Blueprint function library - */ -UCLASS(Category="Houdini Engine|Public API") -class UHoudiniPublicAPIBlueprintLib : public UBlueprintFunctionLibrary -{ - GENERATED_UCLASS_BODY() - -public: - /** Returns the Houdini Public API instance. */ - UFUNCTION(BlueprintPure, meta = (DisplayName = "GetHoudiniEnginePublicAPI"), Category = "Houdini Engine") - static UHoudiniPublicAPI* GetAPI(); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" + +#include "HoudiniPublicAPIBlueprintLib.generated.h" + +class UHoudiniPublicAPI; + +/** + * Houdini Public API Blueprint function library + */ +UCLASS(Category="Houdini Engine|Public API") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIBlueprintLib : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + +public: + /** Returns the Houdini Public API instance. */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "GetHoudiniEnginePublicAPI"), Category = "Houdini Engine") + static UHoudiniPublicAPI* GetAPI(); +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h index 7d5763409..6952b959e 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h @@ -1,552 +1,555 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "HoudiniInput.h" -#include "HoudiniPublicAPIObjectBase.h" - -#include "HoudiniPublicAPIInputTypes.generated.h" - - -/** - * This class is the base class of a hierarchy that represents an input to an HDA in the public API. - * - * Each type of input has a derived class: - * - Geometry: UHoudiniPublicAPIGeoInput - * - Asset: UHoudiniPublicAPIAssetInput - * - Curve: UHoudiniPublicAPICurveInput - * - World: UHoudiniPublicAPIWorldInput - * - Landscape: UHoudiniPublicAPILandscapeInput - * - * Each instance of one of these classes represents the configuration of an input and wraps the actor/object/asset - * used as the input. These instances are always treated as copies of the actual state of the HDA's input: changing - * a property of one of these instances does not immediately affect the instantiated HDA: one has to pass the input - * instances as arguments to UHoudiniPublicAPIAssetWrapper::SetInputAtIndex() or - * UHoudiniPublicAPIAssetWrapper::SetInputParameter() functions to actually change the inputs on the instantiated asset. - * A copy of the existing input state of an instantiated HDA can be fetched via - * UHoudiniPublicAPIAssetWrapper::GetInputAtIndex() and UHoudiniPublicAPIAssetWrapper::GetInputParameter(). - */ -UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPIInput : public UHoudiniPublicAPIObjectBase -{ - GENERATED_BODY() - -public: - UHoudiniPublicAPIInput(); - - /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value .*/ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bKeepWorldTransform; - - /** - * Indicates that all the input objects are imported to Houdini as references instead of actual geo - * (for Geo/World/Asset input types only) - */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bImportAsReference; - - /** Returns true if InObject is acceptable for this input type. - * @param InObject The object to check for acceptance as an input object on this input. - * @return true if InObject is acceptable for this input type. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") - bool IsAcceptableObjectForInput(UObject* InObject) const; - virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const { return UHoudiniInput::IsObjectAcceptable(GetInputType(), InObject); } - - /** - * Sets the specified objects as the input objects. - * @param InObjects The objects to set as input objects for this input. - * @return false if any object was incompatible (all compatible objects are added). - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") - bool SetInputObjects(const TArray& InObjects); - - /** - * Gets the currently assigned input objects. - * @param OutObjects The current input objects of this input. - * @return true if input objects were successfully added to OutObjects (even if there are no input objects). - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") - bool GetInputObjects(TArray& OutObjects); - - /** - * Populate this input instance from the internal UHoudiniInput instance InInput. - * This copies the configuration and UHoudiniInputObject(s). - * @param InInput The internal UHoudiniInput to copy to this instance. - * @return false if there were any errors while copying the input data. - */ - virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput); - - /** - * Update an internal UHoudiniInput instance InInput from this API input instance. - * This copies the configuration and UHoudiniInputObject(s) from this API instance instance to InInput. - * @param InInput The internal UHoudiniInput to update from to this API instance. - * @return false if there were any errors while copying the input data. - */ - virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const; - -protected: - /** - * Copy any properties we need from the UHoudiniInputObject InSrc for the the input object it wraps. - * @param InInputObject The UHoudiniInputObject to copy from. - * @param InObject The input object to copy per-object properties for. - * @return false if the copy failed, for example if InSrc is invalid or of incompatible type. - */ - virtual bool CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject); - - /** - * Copy any properties for InObject from this input wrapper to InInputObject. - * @param InObject The input object to copy per-object properties for. - * @param InInputObject The Houdini input object to copy the properties to. - * @return false if the copy failed, for example if InSrc is invalid or of incompatible type. - */ - virtual bool CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const; - - /** - * Convert an object used as an input in an internal UHoudiniInput to be API compatible. - * @param InInternalInputObject An object as an input by UHoudiniInput - * @return An API compatible input object created from InInternalInputObject. By default this is just - * InInternalInputObject. - */ - virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) { return InInternalInputObject; } - - /** - * Convert an object used as an input in the API to be UHoudiniInput to be compatible and assign it to the - * InHoudiniInput at index InIndex. - * @param InAPIInputObject An input object in the API. - * @param InHoudiniInput The UHoudiniInput to assign the input to. - * @param InInputIndex The object index in InHoudiniInput to assign the converted input object to. - * @return A UHoudiniInput compatible input object created from InAPIInputObject. By default this is just - * InAPIInputObject. - */ - virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const; - - /** - * Returns the type of the input. Subclasses should override this and return the appropriate input type. - * The base class / default implementation returns EHoudiniInputType::Invalid - */ - virtual EHoudiniInputType GetInputType() const { return EHoudiniInputType::Invalid; } - - /** The input objects for this input. */ - UPROPERTY() - TArray InputObjects; - -}; - - -/** - * API wrapper input class for geometry inputs. Derived from UHoudiniPublicAPIInput. - */ -UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPIGeoInput : public UHoudiniPublicAPIInput -{ - GENERATED_BODY() - -public: - UHoudiniPublicAPIGeoInput(); - - /** Indicates that the geometry must be packed before merging it into the input */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bPackBeforeMerge; - - /** Indicates that all LODs in the input should be marshalled to Houdini */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bExportLODs; - - /** Indicates that all sockets in the input should be marshalled to Houdini */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bExportSockets; - - /** Indicates that all colliders in the input should be marshalled to Houdini */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bExportColliders; - - /** - * Set the transform offset of the specified input object InObject (must already have been set via SetInputObjects()). - * @param InObject The input object to set a transform offset for. - * @param InTransform The transform offset to set. - * @return true if the object was found and the transform offset set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs", Meta=(AutoCreateRefTerm="InTransform")) - bool SetObjectTransformOffset(UObject* InObject, const FTransform& InTransform); - - /** - * Get the transform offset of the specified input object InObject (must already have been set via SetInputObjects()). - * @param InObject The input object to get a transform offset for. - * @param OutTransform The transform offset that was fetched. - * @return true if the object was found and the transform offset fetched. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs", Meta=(AutoCreateRefTerm="InTransform")) - bool GetObjectTransformOffset(UObject* InObject, FTransform& OutTransform) const; - - virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; - - virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; - -protected: - virtual bool CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) override; - - virtual bool CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const override; - - virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Geometry; } - - /** Per-Input-Object data: the transform offset per input object. */ - UPROPERTY() - TMap InputObjectTransformOffsets; -}; - -UENUM(BlueprintType) -enum class EHoudiniPublicAPICurveType : uint8 -{ - Invalid = 0, - - Polygon = 1, - Nurbs = 2, - Bezier = 3, - Points = 4 -}; - -UENUM(BlueprintType) -enum class EHoudiniPublicAPICurveMethod : uint8 -{ - Invalid = 0, - - CVs = 1, - Breakpoints = 2, - Freehand = 3 -}; - -/** - * API wrapper input class for curve inputs. Derived from UHoudiniPublicAPIInput. - */ -UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs | Input Objects") -class UHoudiniPublicAPICurveInputObject : public UHoudiniPublicAPIObjectBase -{ - GENERATED_BODY() - -public: - UHoudiniPublicAPICurveInputObject(); - - /** - * Set the points of the curve (replacing any previously set points with InCurvePoints). - * @param InCurvePoints The new points to set / replace the curve's points with. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - void SetCurvePoints(const TArray& InCurvePoints); - FORCEINLINE - virtual void SetCurvePoints_Implementation(const TArray& InCurvePoints) { CurvePoints = InCurvePoints; } - - /** - * Append a point to the end of this curve. - * @param InCurvePoint The point to append. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Meta=(AutoCreateRefTerm="InCurvePoint"), Category="Houdini Engine | Public API | Inputs | Input Objects") - void AppendCurvePoint(const FTransform& InCurvePoint); - FORCEINLINE - virtual void AppendCurvePoint_Implementation(const FTransform& InCurvePoint) { CurvePoints.Add(InCurvePoint); } - - /** Remove all points from the curve. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - void ClearCurvePoints(); - FORCEINLINE - virtual void ClearCurvePoints_Implementation() { CurvePoints.Empty(); } - - /** - * Get all points of the curve. - * @param OutCurvePoints Set to a copy of all of the points of the curve. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - void GetCurvePoints(TArray& OutCurvePoints) const; - FORCEINLINE - virtual void GetCurvePoints_Implementation(TArray& OutCurvePoints) const { OutCurvePoints = CurvePoints; } - - /** Returns true if this is a closed curve. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - bool IsClosed() const; - FORCEINLINE - virtual bool IsClosed_Implementation() const { return bClosed; } - - /** - * Set whether the curve is closed or not. - * @param bInClosed The new closed setting for the curve: set to true if the curve is closed. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - void SetClosed(const bool bInClosed); - FORCEINLINE - virtual void SetClosed_Implementation(const bool bInClosed) { bClosed = bInClosed; } - - /** Returns true if the curve is reversed. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - bool IsReversed() const; - FORCEINLINE - virtual bool IsReversed_Implementation() const { return bReversed; } - - /** - * Set whether the curve is reversed or not. - * @param bInReversed The new reversed setting for the curve: set to true if the curve is reversed. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - void SetReversed(const bool bInReversed); - FORCEINLINE - virtual void SetReversed_Implementation(const bool bInReversed) { bReversed = bInReversed; } - - /** Returns the curve type (for example: polygon, nurbs, bezier) */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - EHoudiniPublicAPICurveType GetCurveType() const; - FORCEINLINE - virtual EHoudiniPublicAPICurveType GetCurveType_Implementation() const { return CurveType; } - - /** - * Set the curve type (for example: polygon, nurbs, bezier). - * @param InCurveType The new curve type. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - void SetCurveType(const EHoudiniPublicAPICurveType InCurveType); - FORCEINLINE - virtual void SetCurveType_Implementation(const EHoudiniPublicAPICurveType InCurveType) { CurveType = InCurveType; } - - /** Get the curve method, for example CVs, or freehand. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - EHoudiniPublicAPICurveMethod GetCurveMethod() const; - FORCEINLINE - virtual EHoudiniPublicAPICurveMethod GetCurveMethod_Implementation() const { return CurveMethod; } - - /** - * Set the curve method, for example CVs, or freehand. - * @param InCurveMethod The new curve method. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") - void SetCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod); - FORCEINLINE - virtual void SetCurveMethod_Implementation(const EHoudiniPublicAPICurveMethod InCurveMethod) { CurveMethod = InCurveMethod; } - - /** - * Populate this wrapper from a UHoudiniSplineComponent. - * @param InSpline The spline to populate this wrapper from. - */ - void PopulateFromHoudiniSplineComponent(UHoudiniSplineComponent const* const InSpline); - - /** - * Copies the curve data to a UHoudiniSplineComponent. - * @param InSpline The spline to copy to. - */ - void CopyToHoudiniSplineComponent(UHoudiniSplineComponent* const InSpline) const; - -protected: - /** The control points of the curve. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Houdini Engine | Public API | Inputs | Input Objects") - TArray CurvePoints; - - /** Whether the curve is closed (true) or not. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") - bool bClosed; - - /** Whether the curve is reversed (true) or not. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") - bool bReversed; - - /** The curve type (for example: polygon, nurbs, bezier). */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") - EHoudiniPublicAPICurveType CurveType; - - /** The curve method, for example CVs, or freehand. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") - EHoudiniPublicAPICurveMethod CurveMethod; - - /** Helper function for converting EHoudiniPublicAPICurveType to EHoudiniCurveType */ - static EHoudiniCurveType ToHoudiniCurveType(const EHoudiniPublicAPICurveType InCurveType); - - /** Helper function for converting EHoudiniPublicAPICurveMethod to EHoudiniCurveMethod */ - static EHoudiniCurveMethod ToHoudiniCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod); - - /** Helper function for converting EHoudiniCurveType to EHoudiniPublicAPICurveType */ - static EHoudiniPublicAPICurveType ToHoudiniPublicAPICurveType(const EHoudiniCurveType InCurveType); - - /** Helper function for converting EHoudiniCurveMethod to EHoudiniPublicAPICurveMethod */ - static EHoudiniPublicAPICurveMethod ToHoudiniPublicAPICurveMethod(const EHoudiniCurveMethod InCurveMethod); -}; - -/** - * API wrapper input class for curve inputs. Derived from UHoudiniPublicAPIInput. - */ -UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPICurveInput : public UHoudiniPublicAPIInput -{ - GENERATED_BODY() - -public: - UHoudiniPublicAPICurveInput(); - - /** Indicates that if trigger cook automatically on curve Input spline modified */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bCookOnCurveChanged; - - /** Set this to true to add rot and scale attributes on curve inputs. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bAddRotAndScaleAttributesOnCurves; - - virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const override; - - virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; - - virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; - -protected: - virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Curve; } - - virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) override; - - virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const override; -}; - - -/** - * API wrapper input class for asset inputs. Derived from UHoudiniPublicAPIInput. - */ -UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPIAssetInput : public UHoudiniPublicAPIInput -{ - GENERATED_BODY() - -public: - UHoudiniPublicAPIAssetInput(); - - virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const override; - - virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; - - virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; - -protected: - virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Asset; } - - virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) override; - - virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const override; -}; - - -/** - * API wrapper input class for world inputs. Derived from UHoudiniPublicAPIGeoInput. - */ -UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPIWorldInput : public UHoudiniPublicAPIGeoInput -{ - GENERATED_BODY() - -public: - UHoudiniPublicAPIWorldInput(); - - /** Objects used for automatic bound selection */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - TArray WorldInputBoundSelectorObjects; - - /** Indicates that this world input is in "BoundSelector" mode */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bIsWorldInputBoundSelector; - - /** Indicates that selected actors by the bound selectors should update automatically */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bWorldInputBoundSelectorAutoUpdate; - - /** Resolution used when converting unreal splines to houdini curves */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - float UnrealSplineResolution; - - /** - * Setter for world input object array. If this is a bounds selector (#bIsWorldInputBoundSelector is true), then - * this function always returns false (and sets nothing), in that case only #WorldInputBoundSelectorObjects can be - * modified. - * @param InObjects The world input objects. - * @return true if all objects in InObjects could be set as world input objects, false otherwise. Always false - * if #bIsWorldInputBoundSelector is true. - */ - virtual bool SetInputObjects_Implementation(const TArray& InObjects) override; - - virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; - - virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; - -protected: - virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::World; } - -}; - - -/** - * API wrapper input class for landscape inputs. Derived from UHoudiniPublicAPIInput. - */ -UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPILandscapeInput : public UHoudiniPublicAPIInput -{ - GENERATED_BODY() - -public: - UHoudiniPublicAPILandscapeInput(); - - /** Indicates that the landscape input's source landscape should be updated instead of creating a new component */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bUpdateInputLandscape; - - /** Indicates if the landscape should be exported as heightfield, mesh or points */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - EHoudiniLandscapeExportType LandscapeExportType; - - /** Is set to true when landscape input is set to selection only. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bLandscapeExportSelectionOnly; - - /** Is set to true when the automatic selection of landscape component is active */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bLandscapeAutoSelectComponent; - - /** Is set to true when materials are to be exported. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bLandscapeExportMaterials; - - /** Is set to true when lightmap information export is desired. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bLandscapeExportLighting; - - /** Is set to true when uvs should be exported in [0,1] space. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bLandscapeExportNormalizedUVs; - - /** Is set to true when uvs should be exported for each tile separately. */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") - bool bLandscapeExportTileUVs; - - virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; - - virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; - -protected: - virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Landscape; } - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniEngineRuntimeCommon.h" +#include "HoudiniPublicAPIObjectBase.h" + +#include "HoudiniPublicAPIInputTypes.generated.h" + +class UHoudiniInput; +class UHoudiniInputObject; +class UHoudiniSplineComponent; + +/** + * This class is the base class of a hierarchy that represents an input to an HDA in the public API. + * + * Each type of input has a derived class: + * - Geometry: UHoudiniPublicAPIGeoInput + * - Asset: UHoudiniPublicAPIAssetInput + * - Curve: UHoudiniPublicAPICurveInput + * - World: UHoudiniPublicAPIWorldInput + * - Landscape: UHoudiniPublicAPILandscapeInput + * + * Each instance of one of these classes represents the configuration of an input and wraps the actor/object/asset + * used as the input. These instances are always treated as copies of the actual state of the HDA's input: changing + * a property of one of these instances does not immediately affect the instantiated HDA: one has to pass the input + * instances as arguments to UHoudiniPublicAPIAssetWrapper::SetInputAtIndex() or + * UHoudiniPublicAPIAssetWrapper::SetInputParameter() functions to actually change the inputs on the instantiated asset. + * A copy of the existing input state of an instantiated HDA can be fetched via + * UHoudiniPublicAPIAssetWrapper::GetInputAtIndex() and UHoudiniPublicAPIAssetWrapper::GetInputParameter(). + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIInput : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIInput(); + + /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value .*/ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bKeepWorldTransform; + + /** + * Indicates that all the input objects are imported to Houdini as references instead of actual geo + * (for Geo/World/Asset input types only) + */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bImportAsReference; + + /** Returns true if InObject is acceptable for this input type. + * @param InObject The object to check for acceptance as an input object on this input. + * @return true if InObject is acceptable for this input type. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") + bool IsAcceptableObjectForInput(UObject* InObject) const; + virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const; + + /** + * Sets the specified objects as the input objects. + * @param InObjects The objects to set as input objects for this input. + * @return false if any object was incompatible (all compatible objects are added). + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") + bool SetInputObjects(const TArray& InObjects); + + /** + * Gets the currently assigned input objects. + * @param OutObjects The current input objects of this input. + * @return true if input objects were successfully added to OutObjects (even if there are no input objects). + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs") + bool GetInputObjects(TArray& OutObjects); + + /** + * Populate this input instance from the internal UHoudiniInput instance InInput. + * This copies the configuration and UHoudiniInputObject(s). + * @param InInput The internal UHoudiniInput to copy to this instance. + * @return false if there were any errors while copying the input data. + */ + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput); + + /** + * Update an internal UHoudiniInput instance InInput from this API input instance. + * This copies the configuration and UHoudiniInputObject(s) from this API instance instance to InInput. + * @param InInput The internal UHoudiniInput to update from to this API instance. + * @return false if there were any errors while copying the input data. + */ + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const; + +protected: + /** + * Copy any properties we need from the UHoudiniInputObject InSrc for the the input object it wraps. + * @param InInputObject The UHoudiniInputObject to copy from. + * @param InObject The input object to copy per-object properties for. + * @return false if the copy failed, for example if InSrc is invalid or of incompatible type. + */ + virtual bool CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject); + + /** + * Copy any properties for InObject from this input wrapper to InInputObject. + * @param InObject The input object to copy per-object properties for. + * @param InInputObject The Houdini input object to copy the properties to. + * @return false if the copy failed, for example if InSrc is invalid or of incompatible type. + */ + virtual bool CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const; + + /** + * Convert an object used as an input in an internal UHoudiniInput to be API compatible. + * @param InInternalInputObject An object as an input by UHoudiniInput + * @return An API compatible input object created from InInternalInputObject. By default this is just + * InInternalInputObject. + */ + virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) { return InInternalInputObject; } + + /** + * Convert an object used as an input in the API to be UHoudiniInput to be compatible and assign it to the + * InHoudiniInput at index InIndex. + * @param InAPIInputObject An input object in the API. + * @param InHoudiniInput The UHoudiniInput to assign the input to. + * @param InInputIndex The object index in InHoudiniInput to assign the converted input object to. + * @return A UHoudiniInput compatible input object created from InAPIInputObject. By default this is just + * InAPIInputObject. + */ + virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const; + + /** + * Returns the type of the input. Subclasses should override this and return the appropriate input type. + * The base class / default implementation returns EHoudiniInputType::Invalid + */ + virtual EHoudiniInputType GetInputType() const { return EHoudiniInputType::Invalid; } + + /** The input objects for this input. */ + UPROPERTY() + TArray InputObjects; + +}; + + +/** + * API wrapper input class for geometry inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPIGeoInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIGeoInput(); + + /** Indicates that the geometry must be packed before merging it into the input */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bPackBeforeMerge; + + /** Indicates that all LODs in the input should be marshalled to Houdini */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bExportLODs; + + /** Indicates that all sockets in the input should be marshalled to Houdini */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bExportSockets; + + /** Indicates that all colliders in the input should be marshalled to Houdini */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bExportColliders; + + /** + * Set the transform offset of the specified input object InObject (must already have been set via SetInputObjects()). + * @param InObject The input object to set a transform offset for. + * @param InTransform The transform offset to set. + * @return true if the object was found and the transform offset set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs", Meta=(AutoCreateRefTerm="InTransform")) + bool SetObjectTransformOffset(UObject* InObject, const FTransform& InTransform); + + /** + * Get the transform offset of the specified input object InObject (must already have been set via SetInputObjects()). + * @param InObject The input object to get a transform offset for. + * @param OutTransform The transform offset that was fetched. + * @return true if the object was found and the transform offset fetched. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs", Meta=(AutoCreateRefTerm="InTransform")) + bool GetObjectTransformOffset(UObject* InObject, FTransform& OutTransform) const; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual bool CopyHoudiniInputObjectProperties(UHoudiniInputObject const* const InInputObject, UObject* const InObject) override; + + virtual bool CopyPropertiesToHoudiniInputObject(UObject* const InObject, UHoudiniInputObject* const InInputObject) const override; + + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Geometry; } + + /** Per-Input-Object data: the transform offset per input object. */ + UPROPERTY() + TMap InputObjectTransformOffsets; +}; + +UENUM(BlueprintType) +enum class EHoudiniPublicAPICurveType : uint8 +{ + Invalid = 0, + + Polygon = 1, + Nurbs = 2, + Bezier = 3, + Points = 4 +}; + +UENUM(BlueprintType) +enum class EHoudiniPublicAPICurveMethod : uint8 +{ + Invalid = 0, + + CVs = 1, + Breakpoints = 2, + Freehand = 3 +}; + +/** + * API wrapper input class for curve inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs | Input Objects") +class UHoudiniPublicAPICurveInputObject : public UHoudiniPublicAPIObjectBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPICurveInputObject(); + + /** + * Set the points of the curve (replacing any previously set points with InCurvePoints). + * @param InCurvePoints The new points to set / replace the curve's points with. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetCurvePoints(const TArray& InCurvePoints); + FORCEINLINE + virtual void SetCurvePoints_Implementation(const TArray& InCurvePoints) { CurvePoints = InCurvePoints; } + + /** + * Append a point to the end of this curve. + * @param InCurvePoint The point to append. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Meta=(AutoCreateRefTerm="InCurvePoint"), Category="Houdini Engine | Public API | Inputs | Input Objects") + void AppendCurvePoint(const FTransform& InCurvePoint); + FORCEINLINE + virtual void AppendCurvePoint_Implementation(const FTransform& InCurvePoint) { CurvePoints.Add(InCurvePoint); } + + /** Remove all points from the curve. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void ClearCurvePoints(); + FORCEINLINE + virtual void ClearCurvePoints_Implementation() { CurvePoints.Empty(); } + + /** + * Get all points of the curve. + * @param OutCurvePoints Set to a copy of all of the points of the curve. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void GetCurvePoints(TArray& OutCurvePoints) const; + FORCEINLINE + virtual void GetCurvePoints_Implementation(TArray& OutCurvePoints) const { OutCurvePoints = CurvePoints; } + + /** Returns true if this is a closed curve. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool IsClosed() const; + FORCEINLINE + virtual bool IsClosed_Implementation() const { return bClosed; } + + /** + * Set whether the curve is closed or not. + * @param bInClosed The new closed setting for the curve: set to true if the curve is closed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetClosed(const bool bInClosed); + FORCEINLINE + virtual void SetClosed_Implementation(const bool bInClosed) { bClosed = bInClosed; } + + /** Returns true if the curve is reversed. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool IsReversed() const; + FORCEINLINE + virtual bool IsReversed_Implementation() const { return bReversed; } + + /** + * Set whether the curve is reversed or not. + * @param bInReversed The new reversed setting for the curve: set to true if the curve is reversed. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetReversed(const bool bInReversed); + FORCEINLINE + virtual void SetReversed_Implementation(const bool bInReversed) { bReversed = bInReversed; } + + /** Returns the curve type (for example: polygon, nurbs, bezier) */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveType GetCurveType() const; + FORCEINLINE + virtual EHoudiniPublicAPICurveType GetCurveType_Implementation() const { return CurveType; } + + /** + * Set the curve type (for example: polygon, nurbs, bezier). + * @param InCurveType The new curve type. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetCurveType(const EHoudiniPublicAPICurveType InCurveType); + FORCEINLINE + virtual void SetCurveType_Implementation(const EHoudiniPublicAPICurveType InCurveType) { CurveType = InCurveType; } + + /** Get the curve method, for example CVs, or freehand. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveMethod GetCurveMethod() const; + FORCEINLINE + virtual EHoudiniPublicAPICurveMethod GetCurveMethod_Implementation() const { return CurveMethod; } + + /** + * Set the curve method, for example CVs, or freehand. + * @param InCurveMethod The new curve method. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Inputs | Input Objects") + void SetCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod); + FORCEINLINE + virtual void SetCurveMethod_Implementation(const EHoudiniPublicAPICurveMethod InCurveMethod) { CurveMethod = InCurveMethod; } + + /** + * Populate this wrapper from a UHoudiniSplineComponent. + * @param InSpline The spline to populate this wrapper from. + */ + void PopulateFromHoudiniSplineComponent(UHoudiniSplineComponent const* const InSpline); + + /** + * Copies the curve data to a UHoudiniSplineComponent. + * @param InSpline The spline to copy to. + */ + void CopyToHoudiniSplineComponent(UHoudiniSplineComponent* const InSpline) const; + +protected: + /** The control points of the curve. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Houdini Engine | Public API | Inputs | Input Objects") + TArray CurvePoints; + + /** Whether the curve is closed (true) or not. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool bClosed; + + /** Whether the curve is reversed (true) or not. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + bool bReversed; + + /** The curve type (for example: polygon, nurbs, bezier). */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveType CurveType; + + /** The curve method, for example CVs, or freehand. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Houdini Engine | Public API | Inputs | Input Objects") + EHoudiniPublicAPICurveMethod CurveMethod; + + /** Helper function for converting EHoudiniPublicAPICurveType to EHoudiniCurveType */ + static EHoudiniCurveType ToHoudiniCurveType(const EHoudiniPublicAPICurveType InCurveType); + + /** Helper function for converting EHoudiniPublicAPICurveMethod to EHoudiniCurveMethod */ + static EHoudiniCurveMethod ToHoudiniCurveMethod(const EHoudiniPublicAPICurveMethod InCurveMethod); + + /** Helper function for converting EHoudiniCurveType to EHoudiniPublicAPICurveType */ + static EHoudiniPublicAPICurveType ToHoudiniPublicAPICurveType(const EHoudiniCurveType InCurveType); + + /** Helper function for converting EHoudiniCurveMethod to EHoudiniPublicAPICurveMethod */ + static EHoudiniPublicAPICurveMethod ToHoudiniPublicAPICurveMethod(const EHoudiniCurveMethod InCurveMethod); +}; + +/** + * API wrapper input class for curve inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPICurveInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPICurveInput(); + + /** Indicates that if trigger cook automatically on curve Input spline modified */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bCookOnCurveChanged; + + /** Set this to true to add rot and scale attributes on curve inputs. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bAddRotAndScaleAttributesOnCurves; + + virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const override; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Curve; } + + virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) override; + + virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const override; +}; + + +/** + * API wrapper input class for asset inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPIAssetInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIAssetInput(); + + virtual bool IsAcceptableObjectForInput_Implementation(UObject* InObject) const override; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Asset; } + + virtual UObject* ConvertInternalInputObject(UObject* InInternalInputObject) override; + + virtual UObject* ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInputObject, UHoudiniInput* InHoudiniInput, const int32 InInputIndex) const override; +}; + + +/** + * API wrapper input class for world inputs. Derived from UHoudiniPublicAPIGeoInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPIWorldInput : public UHoudiniPublicAPIGeoInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIWorldInput(); + + /** Objects used for automatic bound selection */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + TArray WorldInputBoundSelectorObjects; + + /** Indicates that this world input is in "BoundSelector" mode */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bIsWorldInputBoundSelector; + + /** Indicates that selected actors by the bound selectors should update automatically */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bWorldInputBoundSelectorAutoUpdate; + + /** Resolution used when converting unreal splines to houdini curves */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + float UnrealSplineResolution; + + /** + * Setter for world input object array. If this is a bounds selector (#bIsWorldInputBoundSelector is true), then + * this function always returns false (and sets nothing), in that case only #WorldInputBoundSelectorObjects can be + * modified. + * @param InObjects The world input objects. + * @return true if all objects in InObjects could be set as world input objects, false otherwise. Always false + * if #bIsWorldInputBoundSelector is true. + */ + virtual bool SetInputObjects_Implementation(const TArray& InObjects) override; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::World; } + +}; + + +/** + * API wrapper input class for landscape inputs. Derived from UHoudiniPublicAPIInput. + */ +UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") +class UHoudiniPublicAPILandscapeInput : public UHoudiniPublicAPIInput +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPILandscapeInput(); + + /** Indicates that the landscape input's source landscape should be updated instead of creating a new component */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bUpdateInputLandscape; + + /** Indicates if the landscape should be exported as heightfield, mesh or points */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + EHoudiniLandscapeExportType LandscapeExportType; + + /** Is set to true when landscape input is set to selection only. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportSelectionOnly; + + /** Is set to true when the automatic selection of landscape component is active */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeAutoSelectComponent; + + /** Is set to true when materials are to be exported. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportMaterials; + + /** Is set to true when lightmap information export is desired. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportLighting; + + /** Is set to true when uvs should be exported in [0,1] space. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportNormalizedUVs; + + /** Is set to true when uvs should be exported for each tile separately. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Houdini Engine | Public API | Inputs") + bool bLandscapeExportTileUVs; + + virtual bool PopulateFromHoudiniInput(UHoudiniInput const* const InInput) override; + + virtual bool UpdateHoudiniInput(UHoudiniInput* const InInput) const override; + +protected: + virtual EHoudiniInputType GetInputType() const override { return EHoudiniInputType::Landscape; } + +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h index 1c7d8154e..94c839b40 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIObjectBase.h @@ -1,110 +1,110 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Object.h" -#include "HoudiniPublicAPIObjectBase.generated.h" - -/** An enum with values that determine if API errors are logged. */ -UENUM(BlueprintType) -enum class EHoudiniPublicAPIErrorLogOption : uint8 -{ - Invalid = 0, - Auto = 1, - Log = 2, - NoLog = 3, -}; - -/** - * Base class for API UObjects. Implements error logging: record and get a error messages for Houdini Public API objects. - */ -UCLASS() -class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIObjectBase : public UObject -{ - GENERATED_BODY() - -public: - UHoudiniPublicAPIObjectBase(); - - /** - * Gets the last error message recorded. - * @param OutLastErrorMessage Set to the last error message recorded, or the empty string if there are no errors - * messages. - * @return true if there was an error message to set. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") - bool GetLastErrorMessage(FString& OutLastErrorMessage) const; - - /** Clear any error messages that have been set. */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") - void ClearErrorMessages(); - - /** - * Returns whether or not API errors are written to the log. - * @return true if API errors are logged as warnings, false if API errors are not logged. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") - bool IsLoggingErrors() const; - FORCEINLINE - virtual bool IsLoggingErrors_Implementation() const { return bIsLoggingErrors; } - - /** - * Sets whether or not API errors are written to the log. - * @param bInEnabled True if API errors should be logged as warnings, false if API errors should not logged. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") - void SetLoggingErrorsEnabled(const bool bInEnabled); - FORCEINLINE - virtual void SetLoggingErrorsEnabled_Implementation(const bool bInEnabled) { bIsLoggingErrors = bInEnabled; } - -protected: - /** - * Set an error message. This is recorded as the current/last error message. - * @param InErrorMessage The error message to set. - * @param InLoggingOption Determines the behavior around logging the error message. If - * EHoudiniPublicAPIErrorLogOption.Invalid or EHoudiniPublicAPIErrorLogOption.Auto then IsLoggingErrors() is used to - * determine if the error message should be logged. If EHoudiniPublicAPIErrorLogOption.Log, then the error message - * is logged as a warning. If EHoudiniPublicAPIErrorLogOption.NoLog then the error message is not logged. - */ - UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") - void SetErrorMessage( - const FString& InErrorMessage, - const EHoudiniPublicAPIErrorLogOption InLoggingOption=EHoudiniPublicAPIErrorLogOption::Auto) const; - - /** The last error message that was set. */ - UPROPERTY() - mutable FString LastErrorMessage; - - /** True if an errors have been set and not yet cleared. */ - UPROPERTY() - mutable bool bHasError; - - /** If true, API errors logged with SetErrorMessage are written to the log as warnings by default. */ - UPROPERTY() - bool bIsLoggingErrors; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "HoudiniPublicAPIObjectBase.generated.h" + +/** An enum with values that determine if API errors are logged. */ +UENUM(BlueprintType) +enum class EHoudiniPublicAPIErrorLogOption : uint8 +{ + Invalid = 0, + Auto = 1, + Log = 2, + NoLog = 3, +}; + +/** + * Base class for API UObjects. Implements error logging: record and get a error messages for Houdini Public API objects. + */ +UCLASS() +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIObjectBase : public UObject +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIObjectBase(); + + /** + * Gets the last error message recorded. + * @param OutLastErrorMessage Set to the last error message recorded, or the empty string if there are no errors + * messages. + * @return true if there was an error message to set. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + bool GetLastErrorMessage(FString& OutLastErrorMessage) const; + + /** Clear any error messages that have been set. */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + void ClearErrorMessages(); + + /** + * Returns whether or not API errors are written to the log. + * @return true if API errors are logged as warnings, false if API errors are not logged. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + bool IsLoggingErrors() const; + FORCEINLINE + virtual bool IsLoggingErrors_Implementation() const { return bIsLoggingErrors; } + + /** + * Sets whether or not API errors are written to the log. + * @param bInEnabled True if API errors should be logged as warnings, false if API errors should not logged. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + void SetLoggingErrorsEnabled(const bool bInEnabled); + FORCEINLINE + virtual void SetLoggingErrorsEnabled_Implementation(const bool bInEnabled) { bIsLoggingErrors = bInEnabled; } + +protected: + /** + * Set an error message. This is recorded as the current/last error message. + * @param InErrorMessage The error message to set. + * @param InLoggingOption Determines the behavior around logging the error message. If + * EHoudiniPublicAPIErrorLogOption.Invalid or EHoudiniPublicAPIErrorLogOption.Auto then IsLoggingErrors() is used to + * determine if the error message should be logged. If EHoudiniPublicAPIErrorLogOption.Log, then the error message + * is logged as a warning. If EHoudiniPublicAPIErrorLogOption.NoLog then the error message is not logged. + */ + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini Engine | Public API | Errors") + void SetErrorMessage( + const FString& InErrorMessage, + const EHoudiniPublicAPIErrorLogOption InLoggingOption=EHoudiniPublicAPIErrorLogOption::Auto) const; + + /** The last error message that was set. */ + UPROPERTY() + mutable FString LastErrorMessage; + + /** True if an errors have been set and not yet cleared. */ + UPROPERTY() + mutable bool bHasError; + + /** If true, API errors logged with SetErrorMessage are written to the log as warnings by default. */ + UPROPERTY() + bool bIsLoggingErrors; +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h index f7b714d34..cd2cd707d 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIOutputTypes.h @@ -1,71 +1,94 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "HoudiniOutput.h" - -#include "HoudiniPublicAPIOutputTypes.generated.h" - -/** - * This class represents an output object identifier for an output object of a wrapped Houdini asset in the - * public API. - */ -USTRUCT(BlueprintType, Category="Houdini Engine | Public API | Outputs") -struct FHoudiniPublicAPIOutputObjectIdentifier -{ - GENERATED_BODY() - -public: - FHoudiniPublicAPIOutputObjectIdentifier(); - - FHoudiniPublicAPIOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); - - /** Returns the internal output object identifier wrapped by this class. */ - const FHoudiniOutputObjectIdentifier& GetIdentifier() const { return Identifier; } - - /** - * Sets the internal output object identifier wrapped by this class. - * @param InIdentifier The internal output object identifier. - */ - void SetIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); - - /** String identifier for the split that created the output object identified by this identifier. */ - UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Houdini Engine | Public API | Outputs") - FString SplitIdentifier; - - /** Name of the part used to generate the output object identified by this identifier. */ - UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Houdini Engine | Public API | Outputs") - FString PartName; - -protected: - - /** The internal output object identifier. */ - UPROPERTY() - FHoudiniOutputObjectIdentifier Identifier; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniEngineRuntimeCommon.h" + +#include "HoudiniPublicAPIOutputTypes.generated.h" + + +struct FHoudiniOutputObjectIdentifier; + +/** + * This class represents an output object identifier for an output object of a wrapped Houdini asset in the + * public API. + */ +USTRUCT(BlueprintType, Category="Houdini Engine | Public API | Outputs") +struct FHoudiniPublicAPIOutputObjectIdentifier +{ + GENERATED_BODY() + +public: + FHoudiniPublicAPIOutputObjectIdentifier(); + + FHoudiniPublicAPIOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); + + /** Returns the internal output object identifier wrapped by this class. */ + FHoudiniOutputObjectIdentifier GetIdentifier() const; + + /** + * Sets the internal output object identifier wrapped by this class. + * @param InIdentifier The internal output object identifier. + */ + void SetIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); + + /** String identifier for the split that created the output object identified by this identifier. */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Houdini Engine | Public API | Outputs") + FString SplitIdentifier; + + /** Name of the part used to generate the output object identified by this identifier. */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="Houdini Engine | Public API | Outputs") + FString PartName; + +protected: + + // NodeId of corresponding Houdini Object. + UPROPERTY() + int32 ObjectId = -1; + + // NodeId of corresponding Houdini Geo. + UPROPERTY() + int32 GeoId = -1; + + // PartId + UPROPERTY() + int32 PartId = -1; + + // First valid primitive index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PrimitiveIndex = -1; + + // First valid point index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PointIndex = -1; + + bool bLoaded = false; +}; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h index eeb658076..93183b5c2 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIProcessHDANode.h @@ -1,286 +1,285 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Kismet/BlueprintAsyncActionBase.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniPublicAPIAssetWrapper.h" - -#include "HoudiniPublicAPIProcessHDANode.generated.h" - - -class UHoudiniPublicAPIInput; -class UHoudiniAsset; - -// Delegate type for output pins on the node. -DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnProcessHDANodeOutputPinDelegate, UHoudiniPublicAPIAssetWrapper*, AssetWrapper, const bool, bCookSuccess, const bool, bBakeSuccess); - -/** - * A Blueprint async node for instantiating and cooking/processing an HDA, with delegate output pins for the - * various phases/state changes of the HDA. - */ -UCLASS() -class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIProcessHDANode : public UBlueprintAsyncActionBase -{ - GENERATED_BODY() - -public: - UHoudiniPublicAPIProcessHDANode(const FObjectInitializer& ObjectInitializer); - - /** - * Instantiates an HDA in the specified world/level. Sets parameters and inputs supplied in InParameters, - * InNodeInputs and InParameterInputs. If bInEnableAutoCook is true, cooks the HDA. If bInEnableAutoBake is - * true, bakes the cooked outputs according to the supplied baking parameters. - * This all happens asynchronously, with the various output pins firing at the various points in the process: - * - PreInstantiation: before the HDA is instantiated, a good place to set parameter values before the first cook. - * - PostInstantiation: after the HDA is instantiated, a good place to set/configure inputs before the first cook. - * - PostAutoCook: right after a cook - * - PreProcess: after a cook but before output objects have been created/processed - * - PostProcessing: after output objects have been created - * - PostAutoBake: after outputs have been baked - * - Completed: upon successful completion (could be PostInstantiation if auto cook is disabled, PostProcessing - * if auto bake is disabled, or after PostAutoBake if auto bake is enabled. - * - Failed: If the process failed at any point. - * @param InHoudiniAsset The HDA to instantiate. - * @param InInstantiateAt The Transform to instantiate the HDA with. - * @param InParameters The parameter values to set before cooking the instantiated HDA. - * @param InNodeInputs The node inputs to set before cooking the instantiated HDA. - * @param InParameterInputs The parameter-based inputs to set before cooking the instantiated HDA. - * @param InWorldContextObject A world context object for identifying the world to spawn in, if - * InSpawnInLevelOverride is null. - * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both - * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor - * context world's current level. - * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after - * parameter, transform and input changes. - * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. - * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. - * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. - * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. - * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. - * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with - * the new bake's output. Defaults to false. - * @param bInDeleteInstantiatedAssetOnCompletionOrFailure If true, deletes the instantiated asset actor on - * completion or failure. Defaults to false. - * @return The blueprint async node. - */ - UFUNCTION(BlueprintCallable, meta=(AdvancedDisplay=5,AutoCreateRefTerm="InInstantiateAt,InParameters,InNodeInputs,InParameterInputs",BlueprintInternalUseOnly="true", WorldContext="WorldContextObject"), Category="Houdini|Public API") - static UHoudiniPublicAPIProcessHDANode* ProcessHDA( - UHoudiniAsset* InHoudiniAsset, - const FTransform& InInstantiateAt, - const TMap& InParameters, - const TMap& InNodeInputs, - const TMap& InParameterInputs, - UObject* InWorldContextObject=nullptr, - ULevel* InSpawnInLevelOverride=nullptr, - const bool bInEnableAutoCook=true, - const bool bInEnableAutoBake=false, - const FString& InBakeDirectoryPath="", - const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, - const bool bInRemoveOutputAfterBake=false, - const bool bInRecenterBakedActors=false, - const bool bInReplacePreviousBake=false, - const bool bInDeleteInstantiatedAssetOnCompletionOrFailure=false); - - virtual void Activate() override; - - /** - * Delegate that is broadcast when entering the PreInstantiation state: the HDA's default parameter definitions are - * available, but the node has not yet been instantiated in HAPI/Houdini Engine - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnProcessHDANodeOutputPinDelegate PreInstantiation; - - /** Delegate that is broadcast after the asset was successfully instantiated */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnProcessHDANodeOutputPinDelegate PostInstantiation; - - /** - * Delegate that is broadcast after a cook completes, but before outputs have been created. This will not be - * broadcast from this node if bInAutoCookEnabled is false. - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnProcessHDANodeOutputPinDelegate PostAutoCook; - - /** - * Delegate that is broadcast just after PostCook (after parameters have been updated) but before creating - * output objects. This will not be broadcast from this node if bInAutoCookEnabled is false. - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnProcessHDANodeOutputPinDelegate PreProcess; - - /** - * Delegate that is broadcast after processing HDA outputs after a cook, the output objects have been created. - * This will not be broadcast from this node if bInAutoCookEnabled is false. - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnProcessHDANodeOutputPinDelegate PostProcessing; - - /** - * Delegate that is broadcast after auto-baking the asset (not called for individual outputs that are baked to the - * content browser). This will not be broadcast from this node if bInAutoBake or bInAutoCookEnabled is false. - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnProcessHDANodeOutputPinDelegate PostAutoBake; - - /** - * Deletate that is broadcast on completion of async processing of the instantiated asset by this node. - * After this broadcast, the instantiated asset will be deleted if bInDeleteInstantiatedAssetOnCompletion=true - * was set on creation. - */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnProcessHDANodeOutputPinDelegate Completed; - - /** Deletate that is broadcast if we fail during activation of the node. */ - UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") - FOnProcessHDANodeOutputPinDelegate Failed; - -protected: - - // Output pin data - - /** The asset wrapper for the instantiated HDA processed by this node. */ - UPROPERTY() - UHoudiniPublicAPIAssetWrapper* AssetWrapper; - - /** True if the last cook was successful. */ - UPROPERTY() - bool bCookSuccess; - - /** True if the last bake was successful. */ - UPROPERTY() - bool bBakeSuccess; - - // End: Output pin data - - /** The HDA to instantiate. */ - UPROPERTY() - UHoudiniAsset* HoudiniAsset; - - /** The transform the instantiate the asset with. */ - UPROPERTY() - FTransform InstantiateAt; - - /** The parameters to set on #PreInstantiation */ - UPROPERTY() - TMap Parameters; - - /** The node inputs to set on #PostInstantiation */ - UPROPERTY() - TMap NodeInputs; - - /** The object path parameter inputs to set on #PostInstantiation */ - UPROPERTY() - TMap ParameterInputs; - - /** The world context object: spawn in this world if #SpawnInLevelOverride is not set. */ - UPROPERTY() - UObject* WorldContextObject; - - /** The level to spawn in. If both this and #WorldContextObject is not set, spawn in the editor context's level. */ - UPROPERTY() - ULevel* SpawnInLevelOverride; - - /** Whether to set the instantiated asset to auto cook. */ - UPROPERTY() - bool bEnableAutoCook; - - /** Whether to set the instantiated asset to auto bake after a cook. */ - UPROPERTY() - bool bEnableAutoBake; - - /** Set the fallback bake directory, for if output attributes do not specify it. */ - UPROPERTY() - FString BakeDirectoryPath; - - /** The bake method/target: for example, to actors vs to blueprints. */ - UPROPERTY() - EHoudiniEngineBakeOption BakeMethod; - - /** Remove temporary HDA output after a bake. */ - UPROPERTY() - bool bRemoveOutputAfterBake; - - /** Recenter the baked actors at their bounding box center. */ - UPROPERTY() - bool bRecenterBakedActors; - - /** - * Replace previous bake output on each bake. For the purposes of this node, this would mostly apply to .uassets and - * not actors. - */ - UPROPERTY() - bool bReplacePreviousBake; - - /** Whether or not to delete the instantiated asset after Complete is called. */ - UPROPERTY() - bool bDeleteInstantiatedAssetOnCompletionOrFailure; - - /** Unbind all delegates */ - void UnbindDelegates(); - - /** Broadcast Failure and removes the node from the root set. */ - UFUNCTION() - virtual void HandleFailure(); - - /** Broadcast Complete and removes the node from the root set. */ - UFUNCTION() - virtual void HandleComplete(); - - /** - * Bound to the asset wrapper's pre-instantiation delegate. Sets the HDAs parameters from #Parameters and - * broadcasts #PreInstantiation. - */ - UFUNCTION() - virtual void HandlePreInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); - - /** - * Bound to the asset wrapper's post-instantiation delegate. Sets the HDAs inputs from #NodeInputs and - * #ParameterInputs and broadcasts #PostInstantiation. - */ - UFUNCTION() - virtual void HandlePostInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); - - /** Bound to the asset wrapper's post-cook delegate. Broadcasts #PostAutoCook. */ - UFUNCTION() - virtual void HandlePostAutoCook(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInCookSuccess); - - /** Bound to the asset wrapper's pre-processing delegate. Broadcasts #PreProcess. */ - UFUNCTION() - virtual void HandlePreProcess(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); - - /** Bound to the asset wrapper's post-processing delegate. Broadcasts #PostProcessing. */ - UFUNCTION() - virtual void HandlePostProcessing(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); - - /** Bound to the asset wrapper's post-bake delegate. Broadcasts #PostAutoBake. */ - UFUNCTION() - virtual void HandlePostAutoBake(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInBakeSuccess); -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Kismet/BlueprintAsyncActionBase.h" + +#include "HoudiniPublicAPIAssetWrapper.h" + +#include "HoudiniPublicAPIProcessHDANode.generated.h" + + +class UHoudiniPublicAPIInput; +class UHoudiniAsset; + +// Delegate type for output pins on the node. +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnProcessHDANodeOutputPinDelegate, UHoudiniPublicAPIAssetWrapper*, AssetWrapper, const bool, bCookSuccess, const bool, bBakeSuccess); + +/** + * A Blueprint async node for instantiating and cooking/processing an HDA, with delegate output pins for the + * various phases/state changes of the HDA. + */ +UCLASS() +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIProcessHDANode : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() + +public: + UHoudiniPublicAPIProcessHDANode(const FObjectInitializer& ObjectInitializer); + + /** + * Instantiates an HDA in the specified world/level. Sets parameters and inputs supplied in InParameters, + * InNodeInputs and InParameterInputs. If bInEnableAutoCook is true, cooks the HDA. If bInEnableAutoBake is + * true, bakes the cooked outputs according to the supplied baking parameters. + * This all happens asynchronously, with the various output pins firing at the various points in the process: + * - PreInstantiation: before the HDA is instantiated, a good place to set parameter values before the first cook. + * - PostInstantiation: after the HDA is instantiated, a good place to set/configure inputs before the first cook. + * - PostAutoCook: right after a cook + * - PreProcess: after a cook but before output objects have been created/processed + * - PostProcessing: after output objects have been created + * - PostAutoBake: after outputs have been baked + * - Completed: upon successful completion (could be PostInstantiation if auto cook is disabled, PostProcessing + * if auto bake is disabled, or after PostAutoBake if auto bake is enabled. + * - Failed: If the process failed at any point. + * @param InHoudiniAsset The HDA to instantiate. + * @param InInstantiateAt The Transform to instantiate the HDA with. + * @param InParameters The parameter values to set before cooking the instantiated HDA. + * @param InNodeInputs The node inputs to set before cooking the instantiated HDA. + * @param InParameterInputs The parameter-based inputs to set before cooking the instantiated HDA. + * @param InWorldContextObject A world context object for identifying the world to spawn in, if + * InSpawnInLevelOverride is null. + * @param InSpawnInLevelOverride If not nullptr, then the AHoudiniAssetActor is spawned in that level. If both + * InSpawnInLevelOverride and InWorldContextObject are null, then the actor is spawned in the current editor + * context world's current level. + * @param bInEnableAutoCook If true (the default) the HDA will cook automatically after instantiation and after + * parameter, transform and input changes. + * @param bInEnableAutoBake If true, the HDA output is automatically baked after a cook. Defaults to false. + * @param InBakeDirectoryPath The directory to bake to if the bake path is not set via attributes on the HDA output. + * @param InBakeMethod The bake target (to actor vs blueprint). @see EHoudiniEngineBakeOption. + * @param bInRemoveOutputAfterBake If true, HDA temporary outputs are removed after a bake. Defaults to false. + * @param bInRecenterBakedActors Recenter the baked actors to their bounding box center. Defaults to false. + * @param bInReplacePreviousBake If true, on every bake replace the previous bake's output (assets + actors) with + * the new bake's output. Defaults to false. + * @param bInDeleteInstantiatedAssetOnCompletionOrFailure If true, deletes the instantiated asset actor on + * completion or failure. Defaults to false. + * @return The blueprint async node. + */ + UFUNCTION(BlueprintCallable, meta=(AdvancedDisplay=5,AutoCreateRefTerm="InInstantiateAt,InParameters,InNodeInputs,InParameterInputs",BlueprintInternalUseOnly="true", WorldContext="WorldContextObject"), Category="Houdini|Public API") + static UHoudiniPublicAPIProcessHDANode* ProcessHDA( + UHoudiniAsset* InHoudiniAsset, + const FTransform& InInstantiateAt, + const TMap& InParameters, + const TMap& InNodeInputs, + const TMap& InParameterInputs, + UObject* InWorldContextObject=nullptr, + ULevel* InSpawnInLevelOverride=nullptr, + const bool bInEnableAutoCook=true, + const bool bInEnableAutoBake=false, + const FString& InBakeDirectoryPath="", + const EHoudiniEngineBakeOption InBakeMethod=EHoudiniEngineBakeOption::ToActor, + const bool bInRemoveOutputAfterBake=false, + const bool bInRecenterBakedActors=false, + const bool bInReplacePreviousBake=false, + const bool bInDeleteInstantiatedAssetOnCompletionOrFailure=false); + + virtual void Activate() override; + + /** + * Delegate that is broadcast when entering the PreInstantiation state: the HDA's default parameter definitions are + * available, but the node has not yet been instantiated in HAPI/Houdini Engine + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PreInstantiation; + + /** Delegate that is broadcast after the asset was successfully instantiated */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostInstantiation; + + /** + * Delegate that is broadcast after a cook completes, but before outputs have been created. This will not be + * broadcast from this node if bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostAutoCook; + + /** + * Delegate that is broadcast just after PostCook (after parameters have been updated) but before creating + * output objects. This will not be broadcast from this node if bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PreProcess; + + /** + * Delegate that is broadcast after processing HDA outputs after a cook, the output objects have been created. + * This will not be broadcast from this node if bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostProcessing; + + /** + * Delegate that is broadcast after auto-baking the asset (not called for individual outputs that are baked to the + * content browser). This will not be broadcast from this node if bInAutoBake or bInAutoCookEnabled is false. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate PostAutoBake; + + /** + * Deletate that is broadcast on completion of async processing of the instantiated asset by this node. + * After this broadcast, the instantiated asset will be deleted if bInDeleteInstantiatedAssetOnCompletion=true + * was set on creation. + */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate Completed; + + /** Deletate that is broadcast if we fail during activation of the node. */ + UPROPERTY(BlueprintAssignable, Category="Houdini|Public API") + FOnProcessHDANodeOutputPinDelegate Failed; + +protected: + + // Output pin data + + /** The asset wrapper for the instantiated HDA processed by this node. */ + UPROPERTY() + UHoudiniPublicAPIAssetWrapper* AssetWrapper; + + /** True if the last cook was successful. */ + UPROPERTY() + bool bCookSuccess; + + /** True if the last bake was successful. */ + UPROPERTY() + bool bBakeSuccess; + + // End: Output pin data + + /** The HDA to instantiate. */ + UPROPERTY() + UHoudiniAsset* HoudiniAsset; + + /** The transform the instantiate the asset with. */ + UPROPERTY() + FTransform InstantiateAt; + + /** The parameters to set on #PreInstantiation */ + UPROPERTY() + TMap Parameters; + + /** The node inputs to set on #PostInstantiation */ + UPROPERTY() + TMap NodeInputs; + + /** The object path parameter inputs to set on #PostInstantiation */ + UPROPERTY() + TMap ParameterInputs; + + /** The world context object: spawn in this world if #SpawnInLevelOverride is not set. */ + UPROPERTY() + UObject* WorldContextObject; + + /** The level to spawn in. If both this and #WorldContextObject is not set, spawn in the editor context's level. */ + UPROPERTY() + ULevel* SpawnInLevelOverride; + + /** Whether to set the instantiated asset to auto cook. */ + UPROPERTY() + bool bEnableAutoCook; + + /** Whether to set the instantiated asset to auto bake after a cook. */ + UPROPERTY() + bool bEnableAutoBake; + + /** Set the fallback bake directory, for if output attributes do not specify it. */ + UPROPERTY() + FString BakeDirectoryPath; + + /** The bake method/target: for example, to actors vs to blueprints. */ + UPROPERTY() + EHoudiniEngineBakeOption BakeMethod; + + /** Remove temporary HDA output after a bake. */ + UPROPERTY() + bool bRemoveOutputAfterBake; + + /** Recenter the baked actors at their bounding box center. */ + UPROPERTY() + bool bRecenterBakedActors; + + /** + * Replace previous bake output on each bake. For the purposes of this node, this would mostly apply to .uassets and + * not actors. + */ + UPROPERTY() + bool bReplacePreviousBake; + + /** Whether or not to delete the instantiated asset after Complete is called. */ + UPROPERTY() + bool bDeleteInstantiatedAssetOnCompletionOrFailure; + + /** Unbind all delegates */ + void UnbindDelegates(); + + /** Broadcast Failure and removes the node from the root set. */ + UFUNCTION() + virtual void HandleFailure(); + + /** Broadcast Complete and removes the node from the root set. */ + UFUNCTION() + virtual void HandleComplete(); + + /** + * Bound to the asset wrapper's pre-instantiation delegate. Sets the HDAs parameters from #Parameters and + * broadcasts #PreInstantiation. + */ + UFUNCTION() + virtual void HandlePreInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** + * Bound to the asset wrapper's post-instantiation delegate. Sets the HDAs inputs from #NodeInputs and + * #ParameterInputs and broadcasts #PostInstantiation. + */ + UFUNCTION() + virtual void HandlePostInstantiation(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** Bound to the asset wrapper's post-cook delegate. Broadcasts #PostAutoCook. */ + UFUNCTION() + virtual void HandlePostAutoCook(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInCookSuccess); + + /** Bound to the asset wrapper's pre-processing delegate. Broadcasts #PreProcess. */ + UFUNCTION() + virtual void HandlePreProcess(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** Bound to the asset wrapper's post-processing delegate. Broadcasts #PostProcessing. */ + UFUNCTION() + virtual void HandlePostProcessing(UHoudiniPublicAPIAssetWrapper* InAssetWrapper); + + /** Bound to the asset wrapper's post-bake delegate. Broadcasts #PostAutoBake. */ + UFUNCTION() + virtual void HandlePostAutoBake(UHoudiniPublicAPIAssetWrapper* InAssetWrapper, const bool bInBakeSuccess); +}; diff --git a/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h b/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h index 940f0ab60..3a722a67b 100644 --- a/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h +++ b/Source/HoudiniEngineEditor/Public/IHoudiniEngineEditor.h @@ -1,71 +1,71 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Modules/ModuleInterface.h" - -class IHoudiniEngineEditor : public IModuleInterface -{ - public: - /** Register and unregister component visualizers used by this module. **/ - virtual void RegisterComponentVisualizers() {} - virtual void UnregisterComponentVisualizers() {} - - /** Register and unregister detail presenters used by this module. **/ - virtual void RegisterDetails() {} - virtual void UnregisterDetails() {} - - /** Register and unregister asset type actions. **/ - virtual void RegisterAssetTypeActions() {} - virtual void UnregisterAssetTypeActions() {} - - /** Create and register / unregister asset brokers. **/ - virtual void RegisterAssetBrokers() {} - virtual void UnregisterAssetBrokers() {} - - /** Create and register actor factories. **/ - virtual void RegisterActorFactories() {} - - /** Extend menu. **/ - virtual void ExtendMenu() {} - - /** Register and unregister thumbnails. **/ - virtual void RegisterThumbnails() {} - virtual void UnregisterThumbnails() {} - - /** Register and unregister for undo/redo notifications. **/ - virtual void RegisterForUndo() {} - virtual void UnregisterForUndo() {} - - /** Create custom modes **/ - virtual void RegisterModes() {} - virtual void UnregisterModes() {} - - /** Create custom placement extensions */ - virtual void RegisterPlacementModeExtensions() {} - virtual void UnregisterPlacementModeExtensions() {} -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Modules/ModuleInterface.h" + +class IHoudiniEngineEditor : public IModuleInterface +{ + public: + /** Register and unregister component visualizers used by this module. **/ + virtual void RegisterComponentVisualizers() {} + virtual void UnregisterComponentVisualizers() {} + + /** Register and unregister detail presenters used by this module. **/ + virtual void RegisterDetails() {} + virtual void UnregisterDetails() {} + + /** Register and unregister asset type actions. **/ + virtual void RegisterAssetTypeActions() {} + virtual void UnregisterAssetTypeActions() {} + + /** Create and register / unregister asset brokers. **/ + virtual void RegisterAssetBrokers() {} + virtual void UnregisterAssetBrokers() {} + + /** Create and register actor factories. **/ + virtual void RegisterActorFactories() {} + + /** Extend menu. **/ + virtual void ExtendMenu() {} + + /** Register and unregister thumbnails. **/ + virtual void RegisterThumbnails() {} + virtual void UnregisterThumbnails() {} + + /** Register and unregister for undo/redo notifications. **/ + virtual void RegisterForUndo() {} + virtual void UnregisterForUndo() {} + + /** Create custom modes **/ + virtual void RegisterModes() {} + virtual void UnregisterModes() {} + + /** Create custom placement extensions */ + virtual void RegisterPlacementModeExtensions() {} + virtual void UnregisterPlacementModeExtensions() {} +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp index 4eb06991c..a05783499 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.cpp @@ -1,200 +1,200 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAsset.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Misc/Paths.h" -#include "HAL/UnrealMemory.h" - -UHoudiniAsset::UHoudiniAsset(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , AssetFileName(TEXT("")) - , AssetBytesCount(0) - , bAssetLimitedCommercial(false) - , bAssetNonCommercial(false) - , bAssetExpanded(false) -{} - -void -UHoudiniAsset::CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName) -{ - AssetFileName = InFileName; - - // Calculate buffer size. - AssetBytesCount = BufferEnd - BufferStart; - - if (AssetBytesCount) - { - // Allocate buffer to store the raw data. - AssetBytes.SetNumUninitialized(AssetBytesCount); - // Copy data into the newly allocated buffer. - FMemory::Memcpy(AssetBytes.GetData(), BufferStart, AssetBytesCount); - } - - FString FileExtension = FPaths::GetExtension(InFileName); - - // Expanded HDAs are imported via a "houdini.hdalibrary" file inside the .hda directory - // Identify them first, then update the file path to point to the .hda dir - if (FileExtension.Equals(TEXT("hdalibrary"), ESearchCase::IgnoreCase)) - { - bAssetExpanded = true; - - // Use the parent ".hda" directory as the filename - AssetFileName = FPaths::GetPath(AssetFileName); - FileExtension = FPaths::GetExtension(AssetFileName); - } - - if (FileExtension.Equals(TEXT("hdalc"), ESearchCase::IgnoreCase) - || FileExtension.Equals(TEXT("otlc"), ESearchCase::IgnoreCase)) - { - // Check if the HDA is limited (Indie) ... - bAssetLimitedCommercial = true; - } - else if (FileExtension.Equals(TEXT("hdanc"), ESearchCase::IgnoreCase) - || FileExtension.Equals(TEXT("otlnc"), ESearchCase::IgnoreCase)) - { - // ... or non commercial (Apprentice) - bAssetNonCommercial = true; - } -} - -void -UHoudiniAsset::FinishDestroy() -{ - // Release buffer which was used to store raw OTL data. - AssetBytes.Empty(); - Super::FinishDestroy(); -} - -const uint8 * -UHoudiniAsset::GetAssetBytes() const -{ - return AssetBytes.GetData(); -} - -const FString & -UHoudiniAsset::GetAssetFileName() const -{ - return AssetFileName; -} - -uint32 -UHoudiniAsset::GetAssetBytesCount() const -{ - return AssetBytesCount; -} - -void -UHoudiniAsset::Serialize(FArchive & Ar) -{ - // Serializes our UProperties - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Get the version - uint32 HoudiniAssetVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - - // Only version 1 assets needs manual serialization - if ( HoudiniAssetVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE - || HoudiniAssetVersion > VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION ) - return SerializeLegacy(Ar); -} - -void -UHoudiniAsset::SerializeLegacy(FArchive & Ar) -{ - uint32 FileFormatVersion; - Ar << FileFormatVersion; - - Ar << AssetBytesCount; - if (Ar.IsLoading()) - { - // Allocate sufficient space to read stored raw OTL data. - AssetBytes.SetNumUninitialized(AssetBytesCount); - } - - if (AssetBytesCount) - Ar.Serialize(AssetBytes.GetData(), AssetBytesCount); - - // Serialized flags. - union - { - struct - { - uint32 bLegacyPreviewHoudiniLogo : 1; - uint32 bLegacyAssetLimitedCommercial : 1; - uint32 bLegacyAssetNonCommercial : 1; - }; - uint32 HoudiniAssetFlagsPacked; - }; - Ar << HoudiniAssetFlagsPacked; - - bAssetNonCommercial = bLegacyAssetNonCommercial; - bAssetLimitedCommercial = bLegacyAssetLimitedCommercial; - - // Serialize asset file path. - Ar << AssetFileName; -} - -void -UHoudiniAsset::GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const -{ - // Filename - OutTags.Add(FAssetRegistryTag("FileName", AssetFileName, FAssetRegistryTag::TT_Alphabetical)); - - // Bytes - OutTags.Add(FAssetRegistryTag("Bytes", FString::FromInt(AssetBytesCount), FAssetRegistryTag::TT_Numerical)); - - // Indicate if the Asset is Full / Indie / NC - FString AssetType = TEXT("Full"); - if (bAssetLimitedCommercial) - AssetType = TEXT("Limited Commercial (LC)"); - else if (bAssetNonCommercial) - AssetType = TEXT("Non Commercial (NC)"); - - OutTags.Add(FAssetRegistryTag("Asset Type", AssetType, FAssetRegistryTag::TT_Alphabetical)); - - Super::GetAssetRegistryTags(OutTags); -} - -bool -UHoudiniAsset::IsAssetLimitedCommercial() const -{ - return bAssetLimitedCommercial; -} - -bool -UHoudiniAsset::IsAssetNonCommercial() const -{ - return bAssetNonCommercial; -} - -bool -UHoudiniAsset::IsExpandedHDA() const -{ - return bAssetExpanded; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAsset.h" +#include "HoudiniPluginSerializationVersion.h" + +#include "Misc/Paths.h" +#include "HAL/UnrealMemory.h" + +UHoudiniAsset::UHoudiniAsset(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , AssetFileName(TEXT("")) + , AssetBytesCount(0) + , bAssetLimitedCommercial(false) + , bAssetNonCommercial(false) + , bAssetExpanded(false) +{} + +void +UHoudiniAsset::CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName) +{ + AssetFileName = InFileName; + + // Calculate buffer size. + AssetBytesCount = BufferEnd - BufferStart; + + if (AssetBytesCount) + { + // Allocate buffer to store the raw data. + AssetBytes.SetNumUninitialized(AssetBytesCount); + // Copy data into the newly allocated buffer. + FMemory::Memcpy(AssetBytes.GetData(), BufferStart, AssetBytesCount); + } + + FString FileExtension = FPaths::GetExtension(InFileName); + + // Expanded HDAs are imported via a "houdini.hdalibrary" file inside the .hda directory + // Identify them first, then update the file path to point to the .hda dir + if (FileExtension.Equals(TEXT("hdalibrary"), ESearchCase::IgnoreCase)) + { + bAssetExpanded = true; + + // Use the parent ".hda" directory as the filename + AssetFileName = FPaths::GetPath(AssetFileName); + FileExtension = FPaths::GetExtension(AssetFileName); + } + + if (FileExtension.Equals(TEXT("hdalc"), ESearchCase::IgnoreCase) + || FileExtension.Equals(TEXT("otlc"), ESearchCase::IgnoreCase)) + { + // Check if the HDA is limited (Indie) ... + bAssetLimitedCommercial = true; + } + else if (FileExtension.Equals(TEXT("hdanc"), ESearchCase::IgnoreCase) + || FileExtension.Equals(TEXT("otlnc"), ESearchCase::IgnoreCase)) + { + // ... or non commercial (Apprentice) + bAssetNonCommercial = true; + } +} + +void +UHoudiniAsset::FinishDestroy() +{ + // Release buffer which was used to store raw OTL data. + AssetBytes.Empty(); + Super::FinishDestroy(); +} + +const uint8 * +UHoudiniAsset::GetAssetBytes() const +{ + return AssetBytes.GetData(); +} + +const FString & +UHoudiniAsset::GetAssetFileName() const +{ + return AssetFileName; +} + +uint32 +UHoudiniAsset::GetAssetBytesCount() const +{ + return AssetBytesCount; +} + +void +UHoudiniAsset::Serialize(FArchive & Ar) +{ + // Serializes our UProperties + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Get the version + uint32 HoudiniAssetVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + + // Only version 1 assets needs manual serialization + if ( HoudiniAssetVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE + || HoudiniAssetVersion > VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION ) + return SerializeLegacy(Ar); +} + +void +UHoudiniAsset::SerializeLegacy(FArchive & Ar) +{ + uint32 FileFormatVersion; + Ar << FileFormatVersion; + + Ar << AssetBytesCount; + if (Ar.IsLoading()) + { + // Allocate sufficient space to read stored raw OTL data. + AssetBytes.SetNumUninitialized(AssetBytesCount); + } + + if (AssetBytesCount) + Ar.Serialize(AssetBytes.GetData(), AssetBytesCount); + + // Serialized flags. + union + { + struct + { + uint32 bLegacyPreviewHoudiniLogo : 1; + uint32 bLegacyAssetLimitedCommercial : 1; + uint32 bLegacyAssetNonCommercial : 1; + }; + uint32 HoudiniAssetFlagsPacked; + }; + Ar << HoudiniAssetFlagsPacked; + + bAssetNonCommercial = bLegacyAssetNonCommercial; + bAssetLimitedCommercial = bLegacyAssetLimitedCommercial; + + // Serialize asset file path. + Ar << AssetFileName; +} + +void +UHoudiniAsset::GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const +{ + // Filename + OutTags.Add(FAssetRegistryTag("FileName", AssetFileName, FAssetRegistryTag::TT_Alphabetical)); + + // Bytes + OutTags.Add(FAssetRegistryTag("Bytes", FString::FromInt(AssetBytesCount), FAssetRegistryTag::TT_Numerical)); + + // Indicate if the Asset is Full / Indie / NC + FString AssetType = TEXT("Full"); + if (bAssetLimitedCommercial) + AssetType = TEXT("Limited Commercial (LC)"); + else if (bAssetNonCommercial) + AssetType = TEXT("Non Commercial (NC)"); + + OutTags.Add(FAssetRegistryTag("Asset Type", AssetType, FAssetRegistryTag::TT_Alphabetical)); + + Super::GetAssetRegistryTags(OutTags); +} + +bool +UHoudiniAsset::IsAssetLimitedCommercial() const +{ + return bAssetLimitedCommercial; +} + +bool +UHoudiniAsset::IsAssetNonCommercial() const +{ + return bAssetNonCommercial; +} + +bool +UHoudiniAsset::IsExpandedHDA() const +{ + return bAssetExpanded; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h b/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h deleted file mode 100644 index d651062fb..000000000 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAsset.h +++ /dev/null @@ -1,104 +0,0 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/Object.h" -#include "HoudiniAsset.generated.h" - -class UAssetImportData; - -UCLASS(BlueprintType, EditInlineNew, config = Engine) -class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject -{ - GENERATED_UCLASS_BODY() - - public: - - // UOBject functions - virtual void FinishDestroy() override; - virtual void Serialize(FArchive & Ar) override; - virtual void GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const override; - - // Creates and initialize this asset from a given buffer / file. - void CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName); - - // Return buffer containing the raw Houdini OTL data. - const uint8* GetAssetBytes() const; - - // Return path of the corresponding OTL/HDA file. - const FString& GetAssetFileName() const; - - // Return the size in bytes of raw Houdini OTL data. - uint32 GetAssetBytesCount() const; - - // Return true if this asset is a limited commercial asset. - bool IsAssetLimitedCommercial() const; - - // Return true if this asset is a non commercial asset. - bool IsAssetNonCommercial() const; - - // Return true if this asset is an expanded HDA (HDA dir) - bool IsExpandedHDA() const; - - private: - // Used to load old (version1) versions of HoudiniAssets - void SerializeLegacy(FArchive & Ar); - - public: - - // Source filename of the OTL. - UPROPERTY() - FString AssetFileName; - -#if WITH_EDITORONLY_DATA - // Importing data and options used for this Houdini asset. - UPROPERTY(Category = ImportSettings, VisibleAnywhere, Instanced) - UAssetImportData * AssetImportData; -#endif - - private: - - // Buffer containing the raw HDA OTL data. - UPROPERTY() - TArray AssetBytes; - - // Size in bytes of the raw HDA data. - UPROPERTY() - uint32 AssetBytesCount; - - // Indicates if this is a limited commercial asset. - UPROPERTY() - bool bAssetLimitedCommercial; - - // Indicates if this is a non-commercial license asset. - UPROPERTY() - bool bAssetNonCommercial; - - // Indicates if this is an expanded HDA file - UPROPERTY() - bool bAssetExpanded; -}; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp index 84c914fed..cf7257fee 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp @@ -1,146 +1,146 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetActor.h" -#include "HoudiniAsset.h" -#include "HoudiniPDGAssetLink.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -AHoudiniAssetActor::AHoudiniAssetActor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - SetCanBeDamaged(false); - //PrimaryActorTick.bCanEverTick = true; - //PrimaryActorTick.bStartWithTickEnabled = true; - - // Create Houdini component and attach it to a root component. - HoudiniAssetComponent = - ObjectInitializer.CreateDefaultSubobject< UHoudiniAssetComponent >(this, TEXT("HoudiniAssetComponent")); - - //HoudiniAssetComponent->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); - - RootComponent = HoudiniAssetComponent; -} - -UHoudiniAssetComponent * -AHoudiniAssetActor::GetHoudiniAssetComponent() const -{ - return HoudiniAssetComponent; -} - -/* -#if WITH_EDITOR -bool -AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) -{ - if (!ActorPropString) - return false; - - // Locate actor which is being copied in clipboard string. - AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor(this, *ActorPropString); - - // We no longer need clipboard string and can empty it. This seems to avoid occasional crash bug in UE4 which - // happens on copy / paste. - ActorPropString->Empty(); - - if (!CopiedActor || CopiedActor->IsPendingKill()) - { - HOUDINI_LOG_WARNING(TEXT("Failed to import from copy: Duplicated actor not found")); - return false; - } - - // Get Houdini component of an actor which is being copied. - UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; - if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill()) - return false; - - HoudiniAssetComponent->OnComponentClipboardCopy(CopiedActorHoudiniAssetComponent); - - // If actor is copied through moving, we need to copy main transform. - const FTransform & ComponentWorldTransform = CopiedActorHoudiniAssetComponent->GetComponentTransform(); - HoudiniAssetComponent->SetWorldLocationAndRotation( - ComponentWorldTransform.GetLocation(), - ComponentWorldTransform.GetRotation()); - - // We also need to copy actor label. - const FString & CopiedActorLabel = CopiedActor->GetActorLabel(); - FActorLabelUtilities::SetActorLabelUnique(this, CopiedActorLabel); - - return true; -} -#endif -*/ - -#if WITH_EDITOR -bool -AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) const -{ - Super::GetReferencedContentObjects(Objects); - - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) - { - UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); - if (HoudiniAsset && !HoudiniAsset->IsPendingKill()) - Objects.AddUnique(HoudiniAsset); - } - - return true; -} -#endif - -#if WITH_EDITOR -void -AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - // Some property changes need to be forwarded to the component (ie Transform) - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return; - - FProperty* Property = PropertyChangedEvent.MemberProperty; - if (!Property) - return; - - FName PropertyName = Property->GetFName(); - if (PropertyName == HoudiniAssetComponent->GetRelativeLocationPropertyName() - || PropertyName == HoudiniAssetComponent->GetRelativeRotationPropertyName() - || PropertyName == HoudiniAssetComponent->GetRelativeScale3DPropertyName()) - { - HoudiniAssetComponent->SetHasComponentTransformChanged(true); - } -} -#endif - - -bool -AHoudiniAssetActor::IsUsedForPreview() const -{ - return HasAnyFlags(RF_Transient); -} - -#undef LOCTEXT_NAMESPACE +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetActor.h" +#include "HoudiniAsset.h" +#include "HoudiniPDGAssetLink.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +AHoudiniAssetActor::AHoudiniAssetActor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + SetCanBeDamaged(false); + //PrimaryActorTick.bCanEverTick = true; + //PrimaryActorTick.bStartWithTickEnabled = true; + + // Create Houdini component and attach it to a root component. + HoudiniAssetComponent = + ObjectInitializer.CreateDefaultSubobject< UHoudiniAssetComponent >(this, TEXT("HoudiniAssetComponent")); + + //HoudiniAssetComponent->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); + + RootComponent = HoudiniAssetComponent; +} + +UHoudiniAssetComponent * +AHoudiniAssetActor::GetHoudiniAssetComponent() const +{ + return HoudiniAssetComponent; +} + +/* +#if WITH_EDITOR +bool +AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) +{ + if (!ActorPropString) + return false; + + // Locate actor which is being copied in clipboard string. + AHoudiniAssetActor * CopiedActor = FHoudiniEngineUtils::LocateClipboardActor(this, *ActorPropString); + + // We no longer need clipboard string and can empty it. This seems to avoid occasional crash bug in UE4 which + // happens on copy / paste. + ActorPropString->Empty(); + + if (!CopiedActor || CopiedActor->IsPendingKill()) + { + HOUDINI_LOG_WARNING(TEXT("Failed to import from copy: Duplicated actor not found")); + return false; + } + + // Get Houdini component of an actor which is being copied. + UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; + if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill()) + return false; + + HoudiniAssetComponent->OnComponentClipboardCopy(CopiedActorHoudiniAssetComponent); + + // If actor is copied through moving, we need to copy main transform. + const FTransform & ComponentWorldTransform = CopiedActorHoudiniAssetComponent->GetComponentTransform(); + HoudiniAssetComponent->SetWorldLocationAndRotation( + ComponentWorldTransform.GetLocation(), + ComponentWorldTransform.GetRotation()); + + // We also need to copy actor label. + const FString & CopiedActorLabel = CopiedActor->GetActorLabel(); + FActorLabelUtilities::SetActorLabelUnique(this, CopiedActorLabel); + + return true; +} +#endif +*/ + +#if WITH_EDITOR +bool +AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) const +{ + Super::GetReferencedContentObjects(Objects); + + if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + { + UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); + if (HoudiniAsset && !HoudiniAsset->IsPendingKill()) + Objects.AddUnique(HoudiniAsset); + } + + return true; +} +#endif + +#if WITH_EDITOR +void +AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + // Some property changes need to be forwarded to the component (ie Transform) + if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + return; + + FProperty* Property = PropertyChangedEvent.MemberProperty; + if (!Property) + return; + + FName PropertyName = Property->GetFName(); + if (PropertyName == HoudiniAssetComponent->GetRelativeLocationPropertyName() + || PropertyName == HoudiniAssetComponent->GetRelativeRotationPropertyName() + || PropertyName == HoudiniAssetComponent->GetRelativeScale3DPropertyName()) + { + HoudiniAssetComponent->SetHasComponentTransformChanged(true); + } +} +#endif + + +bool +AHoudiniAssetActor::IsUsedForPreview() const +{ + return HasAnyFlags(RF_Transient); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h index c3a316e75..f081efc07 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h @@ -1,77 +1,77 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" - -#include "UObject/ObjectMacros.h" -#include "Components/ActorComponent.h" -#include "GameFramework/Actor.h" - -#include "HoudiniAssetActor.generated.h" - -class UHoudiniPDGAssetLink; - -UCLASS(hidecategories = (Input), ConversionRoot, meta = (ChildCanTick), Blueprintable) -class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor -{ - GENERATED_UCLASS_BODY() - - // Pointer to the root HoudiniAssetComponent - UPROPERTY(Category = HoudiniAssetActor, VisibleAnywhere, BlueprintReadOnly, meta = (ExposeFunctionCategories = "Mesh,Rendering,Physics,Components|HoudiniEngine")/*, AllowPrivateAccess = "true"*/) - UHoudiniAssetComponent * HoudiniAssetComponent; - -public: - - // Returns the actor's houdini component. - UHoudiniAssetComponent* GetHoudiniAssetComponent() const; - - bool IsUsedForPreview() const; - - // Gets the Houdini PDG asset link associated with this actor, if it has one. - UHoudiniPDGAssetLink* GetPDGAssetLink() const { return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; } - -#if WITH_EDITOR - - // Called after a property has been changed - // Used to forward property changes to the HAC - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; - - // Used by the "Sync to Content Browser" right-click menu option in the editor. - virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; - -/* -public: - - // Called before editor paste, true allow import - virtual bool ShouldImport(FString * ActorPropString, bool IsMovingLevel) override; - - // Used by the "Sync to Content Browser" right-click menu option in the editor. - virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; -*/ -#endif -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" + +#include "UObject/ObjectMacros.h" +#include "Components/ActorComponent.h" +#include "GameFramework/Actor.h" + +#include "HoudiniAssetActor.generated.h" + +class UHoudiniPDGAssetLink; + +UCLASS(hidecategories = (Input), ConversionRoot, meta = (ChildCanTick), Blueprintable) +class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor +{ + GENERATED_UCLASS_BODY() + + // Pointer to the root HoudiniAssetComponent + UPROPERTY(Category = HoudiniAssetActor, VisibleAnywhere, BlueprintReadOnly, meta = (ExposeFunctionCategories = "Mesh,Rendering,Physics,Components|HoudiniEngine")/*, AllowPrivateAccess = "true"*/) + UHoudiniAssetComponent * HoudiniAssetComponent; + +public: + + // Returns the actor's houdini component. + UHoudiniAssetComponent* GetHoudiniAssetComponent() const; + + bool IsUsedForPreview() const; + + // Gets the Houdini PDG asset link associated with this actor, if it has one. + UHoudiniPDGAssetLink* GetPDGAssetLink() const { return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; } + +#if WITH_EDITOR + + // Called after a property has been changed + // Used to forward property changes to the HAC + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; + + // Used by the "Sync to Content Browser" right-click menu option in the editor. + virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; + +/* +public: + + // Called before editor paste, true allow import + virtual bool ShouldImport(FString * ActorPropString, bool IsMovingLevel) override; + + // Used by the "Sync to Content Browser" right-click menu option in the editor. + virtual bool GetReferencedContentObjects(TArray< UObject * >& Objects) const; +*/ +#endif +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp index a7bad2417..dc0abba90 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp @@ -1,2374 +1,2374 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetBlueprintComponent.h" - -#include "HoudiniEngineCopyPropertiesInterface.h" -#include "HoudiniOutput.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Engine/SCS_Node.h" -#include "Engine/SimpleConstructionScript.h" -#include "UObject/Object.h" -#include "Logging/LogMacros.h" - -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniInput.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Kismet2/BlueprintEditorUtils.h" - #include "Kismet2/KismetEditorUtilities.h" - #include "Toolkits/AssetEditorManager.h" - #include "Kismet2/ComponentEditorUtils.h" - #include "ComponentAssetBroker.h" -#endif - -HOUDINI_BP_DEFINE_LOG_CATEGORY(); - -UHoudiniAssetBlueprintComponent::UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - -#if WITH_EDITOR - if (IsTemplate()) - { - // CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); - //GEditor->GetEditorSubsystem()->OnAssetEditorRequestClose().AddUObject( this, &UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent ); - } -#endif - - bForceNeedUpdate = false; - bHoudiniAssetChanged = false; - bIsInBlueprintEditor = false; - bCanDeleteHoudiniNodes = false; - - // AssetState will be updated by changes to the HoudiniAsset - // or parameter changes on the Component template. - AssetState = EHoudiniAssetState::None; - bHasRegisteredComponentTemplate = false; - bHasBeenLoaded = false; - bUpdatedFromTemplate = false; - - // Disable proxy mesh by default (unsupported for now) - bOverrideGlobalProxyStaticMeshSettings = true; - bEnableProxyStaticMeshOverride = false; - bEnableProxyStaticMeshRefinementByTimerOverride = false; - bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = false; - bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = false; - StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; - - // Set default mobility to Movable - Mobility = EComponentMobility::Movable; -} - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() -{ - // We need to propagate changes made here back to the corresponding component in - // the Blueprint Generated Class ("_GEN_VARIABLE"). The reason being that - // the Blueprint editor works directly with the GEN_VARIABLE component (all - // PostEditChange() calls, Details Customizations, etc will receive the GEN_VARIABLE instance) BUT - // when the Editor runs the construction script it uses a different component instance, so all changes - // made to that instance won't write back to the Blueprint definition. - // To Summarize: - // Be sure to sync the Parameters array (and any other relevant properties) back - // to the corresponding component on the Blueprint Generated class otherwise these wont be - // accessible in the Details Customization callbacks. - - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] To Component: %s"), *(CachedTemplateComponent->GetPathName())); - - // This should never be called by component templates. - check(!IsTemplate()); - - if (!CachedTemplateComponent.IsValid()) - return; - - USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; - check(SCS); - - /* - USCS_Node* SCSNodeForInstance = FindSCSNodeForInstanceComponent(SCS, this); - if (SCSNodeForInstance) - { - - } - else - { - - } - */ - - //// If we don't have an SCS node for this preview instance, we need to create one, regardless - //// of whether output updates are required. - //if (!CachedTemplateComponent->bOutputsRequireUpdate && SCSNodeForInstance != nullptr) - // return; - - // TODO: If the blueprint editor is NOT open, then we shouldn't attempting - // to copy state back to the BPGC at all! - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - check(BlueprintEditor); - - USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - // check(SCSHACNode); - - // This is the actor instance that is being used for component editing. - AActor* PreviewActor = GetPreviewActor(); - check(PreviewActor); - - // NOTE: Inputs are only from component templates to instances, not the other way around ... I think. - - // ----------------------------------------------------- - // Copy outputs to component template - // ----------------------------------------------------- - - // Populate / update the outputs for the template from the preview / instance. - // TODO: Wrap the Blueprint manipulation in a transaction - TArray& TemplateOutputs = CachedTemplateComponent->Outputs; - TSet StaleTemplateOutputs(TemplateOutputs); - - TemplateOutputs.SetNum(Outputs.Num()); - CachedOutputNodes.Empty(); - - for (int i = 0; i < Outputs.Num(); i++) - { - // Find a output on the template that corresponds to this output from the instance. - UHoudiniOutput* TemplateOutput = nullptr; - UHoudiniOutput* InstanceOutput = nullptr; - InstanceOutput = Outputs[i]; - - //check(InstanceOutput) - if (!InstanceOutput || InstanceOutput->IsPendingKill()) - continue; - - // Ensure that instance outputs won't delete houdini content. - // Houdini content should only be allowed to be deleted from - // the component template. - InstanceOutput->SetCanDeleteHoudiniNodes(false); - - TemplateOutput = TemplateOutputs[i]; - - if (TemplateOutput) - { - check(TemplateOutput->GetOuter() == CachedTemplateComponent.Get()); - StaleTemplateOutputs.Remove(TemplateOutput); - } - - - if (TemplateOutput) - { - // Copy properties from the current instance component while preserving output objects - // and instanced outputs. - TemplateOutput->CopyPropertiesFrom(InstanceOutput, true); - } - else - { - // NOTE: If the template output is NULL it means that the HDA spawned a new component / output in the transient world - // and the new output object needs to be copied back to the BPGC. - - // Corresponding template output could not be found. Create one by duplicating the instance output. - TemplateOutput = InstanceOutput->DuplicateAndCopyProperties(CachedTemplateComponent.Get(), FName(InstanceOutput->GetName())); - // Treat these the same one would components created by CreateDefaultSubObject. - // NOTE: CreateDefaultSubobject performs lots of checks, and unfortunately we can't use it directly (it is - // only allowed to be used in a constructor). Not sure whether we need to either. For now, we just set the - // object flags to be similar to components created by CreateDefaultSubobject. - TemplateOutput->SetFlags(RF_Public|RF_ArchetypeObject|RF_DefaultSubObject); - TemplateOutputs[i] = TemplateOutput; - } - - check(TemplateOutput); - TemplateOutput->SetCanDeleteHoudiniNodes(false); - - // Keep track of potential stale output objects on the template component, for this output. - TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); - TArray StaleTemplateObjects; - TemplateOutputObjects.GetKeys(StaleTemplateObjects); - - for (auto& Entry : InstanceOutput->GetOutputObjects()) - { - - // Prepare the FHoudiniOutputObject for the template component - const FHoudiniOutputObject& InstanceObj = Entry.Value; - FHoudiniOutputObject TemplateObj; - - // Any output present in the Instance Outputs should be - // transferred to the template. - // Remove this output object from stale outputs list. - StaleTemplateObjects.Remove(Entry.Key); - - if (TemplateOutputObjects.Contains(Entry.Key)) - { - // Reuse the existing template object - TemplateObj = TemplateOutputObjects.FindChecked(Entry.Key); - } - else - { - // Create a new template output object object by duplicating the instance object. - // Keep the output object, but clear the output component since we have to - // create a new component template. - TemplateObj = InstanceObj; - TemplateObj.ProxyComponent = nullptr; - TemplateObj.OutputComponent = nullptr; - TemplateObj.ProxyObject = nullptr; - } - - USceneComponent* ComponentInstance = Cast(InstanceObj.OutputComponent); - USceneComponent* ComponentTemplate = Cast(TemplateObj.OutputComponent); - UObject* OutputObject = InstanceObj.OutputObject; - - if (ComponentInstance) - { - // The translation process has either constructed new components, or it is - // reusing existing components, or changed an output (or all or none of the aforementioned). - // Carefully inspect the SCS graph to determine whether there is a corresponding - // (and compatible) node for this output. If not, create a new node and remove unusable node, if any. - - USCS_Node* ComponentNode = nullptr; - { - // Check whether the current OutputComponent being referenced by the template is still valid. - // Even if it was removed in the editor, it doesn't have any associated destroyed / pendingkill state. - // Instead we're going to check for validity by finding an SCS node with a matching template component. - bool bValidComponentTemplate = (ComponentTemplate != nullptr); - if (ComponentTemplate) - { - - ComponentNode = FindSCSNodeForTemplateComponentInClassHierarchy(ComponentTemplate); - bValidComponentTemplate = bValidComponentTemplate && (ComponentNode != nullptr); - } - - if (!bValidComponentTemplate) - { - // Either this component was removed from the editor or it doesn't exist yet. - // Ensure the references are cleared - TemplateObj.OutputComponent = nullptr; - ComponentTemplate = nullptr; - } - } - - // NOTE: we can't use the component instance name directly due to the Blueprint compiler performing an internal checking - // using FComponentEditorUtils::IsValidVariableNameString(), which will return false if the name looks like an autogenerated name... - //FString ComponentName = ComponentInstance->GetName(); - FString ComponentName = FBlueprintEditorUtils::GetClassNameWithoutSuffix(ComponentInstance->GetClass()); - FName ComponentFName = FName(ComponentName); - - - const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )( - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | - EditorUtilities::ECopyOptions::CallPostEditChangeProperty | - EditorUtilities::ECopyOptions::CallPostEditMove); - - if (IsValid(ComponentNode)) - { - // Check if we have an existing and compatible SCS node containing a USceneComponent as a template component. - bool bComponentNodeIsValid = true; - - ComponentTemplate = Cast(ComponentNode->ComponentTemplate); - - bComponentNodeIsValid = bComponentNodeIsValid && ComponentInstance->GetClass() == ComponentNode->ComponentClass; - bComponentNodeIsValid = bComponentNodeIsValid && ComponentTemplate != nullptr; - // TODO: Do we need to perform any other compatibility checks? - - if (!bComponentNodeIsValid) - { - // Component template is not compatible. We can't reuse it. - - SCSHACNode->RemoveChildNode(ComponentNode); - SCS->RemoveNode(ComponentNode); - ComponentNode = nullptr; - ComponentTemplate = nullptr; - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - } - - if (ComponentNode) - { - // We found a reusable SCS node. Just copy the component instance - // properties over to the existing template. - check(ComponentNode->ComponentTemplate); - - // UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - // //Params.bReplaceObjectClassReferences = false; - // Params.bDoDelta = false; // Perform a deep copy - // Params.bClearReferences = false; - // UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - - FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - - ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; - } - else - { - // We couldn't find a reusable SCS node. - // Duplicate the instance component and create a new corresponding SCS node - ComponentNode = SCS->CreateNode(ComponentInstance->GetClass(), ComponentFName); - - UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // We need a deep copy of parameters here so the CDO values get copied as well - UEditorEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - // FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); - - // { - // UInstancedStaticMeshComponent* Component = Cast(ComponentNode->ComponentTemplate); - // if (Component) - // { - // } - // } - - // NOTE: The EComponentCreationMethod here is currently set to be the same as a component that was - // created manually in the editor. - ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; - - // Add this node to the SCS root set. - - // Attach the new node the HAC SCS node - // NOTE: This will add the node to the SCS->AllNodes list too but it won't update - // the nodename map. We can't forcibly update the Node/Name map either since the - // relevant functions have not been exported. - SCSHACNode->AddChildNode(ComponentNode); - - // Set the output component. - TemplateObj.OutputComponent = ComponentNode->ComponentTemplate; - - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - - // Cache the mapping between the output and the SCS node. - check(ComponentNode); - CachedOutputNodes.Add(Entry.Key, ComponentNode->VariableGuid); - } // if (ComponentInstance) - /* - else if (InstanceObj.OutputObject) - { - - } - */ - - // Add the updated output object to the template output - TemplateOutputObjects.Add(Entry.Key, TemplateObj); - } - - // Cleanup stale objects for this template output. - for (const auto& StaleId : StaleTemplateObjects) - { - FHoudiniOutputObject& OutputObj = TemplateOutputObjects.FindChecked(StaleId); - - // Ensure the component template is no longer referencing this output. - TemplateOutputObjects.Remove(StaleId); - - USceneComponent* TemplateComponent = Cast(OutputObj.OutputComponent); - - if (TemplateComponent) - { - USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(TemplateComponent); - if (StaleNode) - { - - SCS->RemoveNode(StaleNode, false); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - /* - else - { - - } - */ - } - /* - else - { - - } - */ - } - } //for (int i = 0; i < Outputs.Num(); i++) - - // Clean up stale outputs on the component template. - for (UHoudiniOutput* StaleOutput : StaleTemplateOutputs) - { - if (!StaleOutput) - continue; - - // Remove any components contained in this output from the SCS graph - for (auto& Entry : StaleOutput->GetOutputObjects()) - { - FHoudiniOutputObject& StaleObject = Entry.Value; - USceneComponent* OutputComponent = Cast(StaleObject.OutputComponent); - - if (OutputComponent) - { - - USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(OutputComponent); - if (StaleNode) - { - - SCS->RemoveNode(StaleNode, false); - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - } - } - } - - TemplateOutputs.Remove(StaleOutput); - //StaleOutput->ConditionalBeginDestroy(); - } - - SCS->ValidateSceneRootNodes(); - - // Copy parameters from this component to the template component. - // NOTE: We need to do this since the preview component will be cooking the HDA and get populated with - // all the parameters. This data needs to be sent back to the component template. - UClass* ComponentClass = CachedTemplateComponent->GetClass(); - UHoudiniAssetBlueprintComponent* DefaultObj = Cast(ComponentClass->GetDefaultObject()); - bool bBPStructureModified = false; - CachedTemplateComponent->CopyDetailsFromComponent( - this, - true, - true, - true, - false, - true, - bBPStructureModified, - /* SetFlags */ CachedTemplateComponent->GetMaskedFlags(RF_PropagateToSubObjects)); - - if (bBPStructureModified) - CachedTemplateComponent->MarkAsBlueprintStructureModified(); - - // Copy the cached output nodes back to the template so that - // reconstructed actors can correctly update output objects - // with newly constructed components during ApplyComponentInstanceData() calls. - CachedTemplateComponent->CachedOutputNodes = CachedOutputNodes; - - CachedTemplateComponent->MarkPackageDirty(); - PostEditChange(); - - CachedTemplateComponent->AssetId = AssetId; - CachedTemplateComponent->HapiGUID = HapiGUID; - CachedTemplateComponent->AssetCookCount = AssetCookCount; - CachedTemplateComponent->AssetStateResult = AssetStateResult; - CachedTemplateComponent->bLastCookSuccess = bLastCookSuccess; - -#if WITH_EDITOR - // TODO: Do we need to handle this right now or can we wait for the next Houdini Engine manager tick to process it? - if (CachedTemplateComponent->NeedBlueprintStructureUpdate()) - { - // We are about to recompile the blueprint. This will reconstruct the preview actor so we need to ensure - // that the old actor won't release the houdini nodes. - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); - SetCanDeleteHoudiniNodes(false); - } - /*else if (CachedTemplateComponent->NeedBlueprintUpdate()) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(CachedTemplateComponent.Get()); - }*/ -#endif -} -#endif - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties) -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] From Component: %s"), *(FromComponent->GetPathName())); - - // This should never be called by component templates. - check(!IsTemplate()); - - // Make sure all TransientDuplicate properties from the Template Component needed by this transient component - // gets copied. - - ComponentGUID = FromComponent->ComponentGUID; - - /* - { - const TArray Children = GetAttachChildren(); - for (USceneComponent* Child : Children) - { - if (!Child) - continue; - } - } - */ - - // AssetState = FromComponent->PreviewAssetState; - - // This state should not be shared between template / instance components. - //bFullyLoaded = FromComponent->bFullyLoaded; - - bNoProxyMeshNextCookRequested = FromComponent->bNoProxyMeshNextCookRequested; - - // Reconstruct outputs and update them to point to component instances as opposed to templates. - UObject* TemplateOuter = CachedTemplateComponent->GetOuter(); - - USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; - check(SCS); - - // NOTE: We can find the SCS node for the HoudiniAssetComponent from either the template component or the instance (editor preview) component. - USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - check(SCSHACNode); - - // ----------------------------------------------------- - // Copy outputs to component template - // ----------------------------------------------------- - - TArray& TemplateOutputs = CachedTemplateComponent->Outputs; - - TSet StaleInstanceOutputs(Outputs); - - Outputs.SetNum(TemplateOutputs.Num()); - - for (int i = 0; i < TemplateOutputs.Num(); i++) - { - UHoudiniOutput* TemplateOutput = TemplateOutputs[i]; - if (!IsValid(TemplateOutput)) - continue; - - UHoudiniOutput* InstanceOutput = Outputs[i]; - if (!(InstanceOutput->GetOuter() == this)) - InstanceOutput = nullptr; - - if (InstanceOutput) - { - StaleInstanceOutputs.Remove(InstanceOutput); - } - - if (InstanceOutput) - { - // Copy properties from the current instance component while preserving output objects - // and instanced outputs. - InstanceOutput->CopyPropertiesFrom(TemplateOutput, true); - } - else - { - InstanceOutput = TemplateOutput->DuplicateAndCopyProperties(this, FName(TemplateOutput->GetName())); - if (IsValid(InstanceOutput)) - InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); - } - - Outputs[i] = InstanceOutput; - - if (!IsValid(InstanceOutput)) - continue; - - InstanceOutput->SetCanDeleteHoudiniNodes(false); - - TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); - TMap& InstanceOutputObjects = InstanceOutput->GetOutputObjects(); - TArray StaleOutputObjects; - InstanceOutputObjects.GetKeys(StaleOutputObjects); - - for (auto& Entry : TemplateOutputObjects) - { - const FHoudiniOutputObject& TemplateObj = Entry.Value; - FHoudiniOutputObject InstanceObj = TemplateObj; - - if (!InstanceOutputObjects.Contains(Entry.Key)) - continue; - - StaleOutputObjects.Remove(Entry.Key); - InstanceObj = InstanceOutputObjects.FindChecked(Entry.Key); - - } // for (auto& Entry : TemplateOutputObjects) - - // Cleanup stale output objects for this output. - for (const auto& StaleId : StaleOutputObjects) - { - //TemplateOutput - //check(TemplateOutputs); - - FHoudiniOutputObject& OutputObj = InstanceOutputObjects.FindChecked(StaleId); - - InstanceOutputObjects.Remove(StaleId); - if (OutputObj.OutputComponent) - { - //OutputObj.OutputComponent->ConditionalBeginDestroy(); - OutputObj.OutputComponent = nullptr; - } - } - } // for (int i = 0; i < TemplateOutputs.Num(); i++) - - // Cleanup any stale outputs found on the component instance. - for (UHoudiniOutput* StaleOutput : StaleInstanceOutputs) - { - if (!StaleOutput) - continue; - - if (!(StaleOutput->GetOuter() == this)) - continue; - - // We don't want to clear stale outputs on components instances. Only on template components. - StaleOutput->SetCanDeleteHoudiniNodes(false); - } - - // Copy parameters from the component template to the instance. - bool bBlueprintStructureChanged = false; - CopyDetailsFromComponent(FromComponent, - false, - bClearFromInputs, - bClearToInputs, - false, - true, - bBlueprintStructureChanged, - /*SetFlags*/ RF_Public, - /*ClearFlags*/ RF_DefaultSubObject|RF_ArchetypeObject); -} -#endif - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent( - UHoudiniAssetBlueprintComponent* FromComponent, - const bool bCreateSCSNodes, - const bool bClearChangedToInputs, - const bool bClearChangedFromInputs, - const bool bInCanDeleteHoudiniNodes, - const bool bCopyInputObjectComponentProperties, - bool &bOutBlueprintStructureChanged, - EObjectFlags SetFlags, - EObjectFlags ClearFlags) -{ - check(FromComponent); - - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] Component: %s"), *(GetPathName())); - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] FromComponent: %s"), *(FromComponent->GetPathName())); - - /* - if (!FromComponent->HoudiniAsset) - { - return; - } - */ - - // TODO: Try to reuse objects here when we're able. - //// Copy UHoudiniOutput state from instance to template - //UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - ////Params.bReplaceObjectClassReferences = false; - ////Params.bClearReferences = false; - //Params.bDoDelta = true; - //UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); - - // Record input remapping that will need to take place when duplicating parameters. - TMap InputMapping; - - // ----------------------------------------------------- - // Copy inputs - // ----------------------------------------------------- - - // TODO: Add support for input components - { - TArray& FromInputs = FromComponent->Inputs; - TSet StaleInputs(Inputs); - USimpleConstructionScript* SCS = GetSCS(); - USCS_Node* SCSHACNode = nullptr; - - if (bCreateSCSNodes) - { - SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); - } - - Inputs.SetNum(FromInputs.Num()); - for (int i = 0; i < FromInputs.Num(); i++) - { - UHoudiniInput* FromInput = nullptr; - UHoudiniInput* ToInput = nullptr; - FromInput = FromInputs[i]; - - check(FromInput); - - ToInput = Inputs[i]; - - if (ToInput) - { - // Check whether the instance and template input objects are compatible. - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - - if (!bIsValid) - { - ToInput = nullptr; - } - } - - // TODO: Process stale input objects - - // NOTE: The CopyStateFrom() / DuplicateAndCopyState() will copy/duplicate/cleanup internal inputs to - // ensure that there aren't any shared instances between the ToInput/FromInput. - if (ToInput) - { - // We have a compatible input that we can reuse. - StaleInputs.Remove(ToInput); - ToInput->CopyStateFrom(FromInput, true, bInCanDeleteHoudiniNodes); - } - else - { - - // We don't have an existing / compatible input. Create a new one. - ToInput = FromInput->DuplicateAndCopyState(this, bInCanDeleteHoudiniNodes); - if (SetFlags != RF_NoFlags) - ToInput->SetFlags(SetFlags); - if (ClearFlags != RF_NoFlags) - ToInput->ClearFlags( ClearFlags ); - } - - check(ToInput); - - - UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, bCopyInputObjectComponentProperties, bCreateSCSNodes, SCSHACNode, &bOutBlueprintStructureChanged); - - Inputs[i] = ToInput; - InputMapping.Add(FromInput, ToInput); - - if (bClearChangedToInputs) - { - // Clear the changed flags on the FromInput so that it doesn't trigger - // another update. The ToInput will now be carrying to changed/update flags. - ToInput->MarkChanged(false); - ToInput->MarkAllInputObjectsChanged(false); - } - - if (bClearChangedFromInputs) - { - // Clear the changed flags on the FromInput so that it doesn't trigger - // another update. The ToInput will now be carrying to changed/update flags. - FromInput->MarkChanged(false); - FromInput->MarkAllInputObjectsChanged(false); - } - } - - // Cleanup any stale inputs from this component. - // NOTE: We would typically only have stale inputs when copying state from - // the component instance to the component template. Garbage collection - // eventually picks up the input objects and removes the content - // but until such time we are stuck with those nodes as inputs in the Houdini session - // so we get rid of those nodes immediately here to avoid some user confusion. - for (UHoudiniInput* StaleInput : StaleInputs) - { - if (!IsValid(StaleInput)) - continue; - - check(StaleInput->GetOuter() == this); - - if (StaleInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - StaleInput->ConditionalBeginDestroy(); - } - } - - - // ----------------------------------------------------- - // Copy parameters (and optionally remap inputs). - // ----------------------------------------------------- - TMap ParameterMapping; - - TArray& FromParameters = FromComponent->Parameters; - Parameters.SetNum(FromParameters.Num()); - - for (int i = 0; i < FromParameters.Num(); i++) - { - UHoudiniParameter* FromParameter = nullptr; - UHoudiniParameter* ToParameter = nullptr; - - FromParameter = FromParameters[i]; - - check(FromParameter); - - if (Parameters.IsValidIndex(i)) - { - ToParameter = Parameters[i]; - } - - if (ToParameter) - { - bool bIsValid = true; - // Check whether To/From parameters are compatible - bIsValid = bIsValid && ToParameter->Matches(*FromParameter); - bIsValid = bIsValid && ToParameter->GetOuter() == this; - - if (!bIsValid) - ToParameter = nullptr; - } - - if (ToParameter) - { - // Parameter already exists. Simply sync the state. - ToParameter->CopyStateFrom(FromParameter, true, ClearFlags, SetFlags); - } - else - { - // TODO: Check whether parameters are the same to avoid recreating them. - ToParameter = FromParameter->DuplicateAndCopyState(this, ClearFlags, SetFlags); - Parameters[i] = ToParameter; - } - - check(ToParameter); - ParameterMapping.Add(FromParameter, ToParameter); - - if (bClearChangedFromInputs) - { - // We clear the Changed flag on the FromParameter (most likely on the component template) - // since the template parameter state has now been transfered to the preview component and - // will resume processing from there. - FromParameter->MarkChanged(false); - } - } - - // Apply remappings on the new parameters - for (UHoudiniParameter* ToParameter : Parameters) - { - ToParameter->RemapParameters(ParameterMapping); - ToParameter->RemapInputs(InputMapping); - } - - FProperty* ParametersProperty = GetClass()->FindPropertyByName(TEXT("Parameters")); - FPropertyChangedEvent Evt(ParametersProperty); - PostEditChangeProperty(Evt); - - bEnableCooking = FromComponent->bEnableCooking; - bRecookRequested = FromComponent->bRecookRequested; - bRebuildRequested = FromComponent->bRebuildRequested; -} - -void -UHoudiniAssetBlueprintComponent::UpdateInputObjectComponentReferences( - USimpleConstructionScript* SCS, - UHoudiniInput* FromInput, - UHoudiniInput* ToInput, - const bool bCopyInputObjectProperties, - const bool bCreateMissingSCSNodes, - USCS_Node* SCSHACParent, - bool* bOutSCSNodeCreated) -{ - TArray ToInputObjects; - TArray FromInputObjects; - TArray StaleInputObjects; - - ToInput->GetAllHoudiniInputSplineComponents(ToInputObjects); - FromInput->GetAllHoudiniInputSplineComponents(FromInputObjects); - - StaleInputObjects = ToInputObjects; - - const int32 NumInputObjects = FromInputObjects.Num(); - ToInputObjects.SetNum(NumInputObjects); - - const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - //Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; - - for(int32 InputObjectIndex = 0; InputObjectIndex < NumInputObjects; ++InputObjectIndex) - { - UHoudiniInputHoudiniSplineComponent* FromInputObject = FromInputObjects[InputObjectIndex]; - UHoudiniInputHoudiniSplineComponent* ToInputObject = ToInputObjects[InputObjectIndex]; - if (!FromInputObject) - continue; - if (!ToInputObject) - continue; - - USCS_Node* SCSNode = nullptr; - if (CachedInputNodes.Contains(ToInputObject->Guid)) - { - // Reuse / update the existing SCS node. - SCSNode = SCS->FindSCSNodeByGuid( CachedInputNodes.FindChecked(ToInputObject->Guid) ); - } - - if (!SCSNode) - { - if (!bCreateMissingSCSNodes) - continue; // This input object should be removed. - } - - USceneComponent* ToComponent = nullptr; - USceneComponent* FromComponent = Cast(FromInputObject->GetObject()); - - StaleInputObjects.Remove(ToInputObject); - - if (FromComponent) - { - if (!SCSNode) - { - if (bCreateMissingSCSNodes) - { - // Create a new SCS node - SCSNode = SCS->CreateNode(FromComponent->GetClass()); - SCSHACParent->AddChildNode(SCSNode); - if (bOutSCSNodeCreated) - { - *bOutSCSNodeCreated = true; - } - AddInputObjectMapping(ToInputObject->Guid, SCSNode->VariableGuid); - } - } - - if (SCSNode) - { - if (bCreateMissingSCSNodes) - { - // If we have been instructed to create missing SCS nodes, assume we are copying - // the the component template. - ToComponent = Cast(SCSNode->ComponentTemplate); - } - else - { - // We are not copying to the component template, so we're assuming this is a - // component instance. Find the component on the owning actor that matches the SCS node. - AActor* ToOwningActor = ToInput->GetTypedOuter(); - check(ToOwningActor); - - ToComponent = Cast(FindComponentInstanceInActor(ToOwningActor, SCSNode)); - } - - if (bCopyInputObjectProperties && ToComponent) - { - USceneComponent* ToAttachParent = ToComponent->GetAttachParent(); - // Copy specific properties from the component template to the instance, if supported by the component. - // We typically resort to this in order to transfer Transient and TransientDuplicate properties from the - // component template over to the instance (typically HasChanged / NeedsToTriggerUpdate flags) in order for - // the instance to cook properly. - IHoudiniEngineCopyPropertiesInterface* ToCopyableComponent = Cast(ToComponent); - if (ToCopyableComponent) - { - // Let the component manage its own data copying. - ToCopyableComponent->CopyPropertiesFrom(FromComponent); - } - else - { - // The component doesn't implement the property copy interface. Simply do a general property copy. - //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, ToComponent, Params); - FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, ToComponent, ComponentCopyOptions); - } - ToComponent->PostEditChange(); - } - } - } - - ToInputObject->Update(ToComponent); - ToInputObjects[InputObjectIndex] = ToInputObject; - } - - for (UHoudiniInputObject* StaleInputObject : StaleInputObjects) - { - if (!StaleInputObject) - continue; - StaleInputObject->InvalidateData(); - ToInput->RemoveHoudiniInputObject(StaleInputObject); - ToInput->MarkChanged(true); - // TODO: Find the corresponding SCS node and remove it - } -} - -#endif - -#if WITH_EDITOR -bool -UHoudiniAssetBlueprintComponent::HasOpenEditor() const -{ - if (IsTemplate()) - { - IAssetEditorInstance* EditorInstance = FindEditorInstance(); - - return EditorInstance != nullptr; - } - - return false; -} -#endif - -#if WITH_EDITOR -IAssetEditorInstance* -UHoudiniAssetBlueprintComponent::FindEditorInstance() const -{ - UClass* BPGC = Cast(GetOuter()); - if (!IsValid(BPGC)) - return nullptr; - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!IsValid(Blueprint)) - return nullptr; - if (!CachedAssetEditorSubsystem.IsValid()) - return nullptr; - - IAssetEditorInstance* EditorInstance = CachedAssetEditorSubsystem->FindEditorForAsset(Blueprint, false); - - return EditorInstance; -} -#endif - -#if WITH_EDITOR -AActor* -UHoudiniAssetBlueprintComponent::GetPreviewActor() const -{ - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - if (BlueprintEditor) - { - return BlueprintEditor->GetPreviewActor(); - } - return nullptr; -} -#endif - -UHoudiniAssetComponent* -UHoudiniAssetBlueprintComponent::GetCachedTemplate() const -{ - return CachedTemplateComponent.Get(); -} - -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateParameters() const -//{ -// return IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateInputs() const -//{ -// return !IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanUpdateOutputs() const -//{ -// return !IsTemplate(); -//} -// -//bool -//UHoudiniAssetBlueprintComponent::CanProcessOutputs() const -//{ -// return !IsTemplate(); -//} - -//bool -//UHoudiniAssetBlueprintComponent::CanInstantiateAsset() const -//{ -// // If this is a preview component, it should not trigger an asset instantiation. It should wait -// // for the BPGC template component to finish the cook, get the synced data and then translate. -// -// if (IsPreview()) -// return false; -// -// return true; -//} -// -//// Check whether the HAC can translate Houdini outputs at all -//bool -//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini() const -//{ -// // Template components can't translate Houdini output since they typically do not exist in a world. -// if (IsTemplate()) -// return false; -// // Preview components and normally instanced actors can translate Houdini outputs. -// return true; -//} -// -//// Check whether the HAC can translate a specific output type. -//bool -//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini(EHoudiniOutputType OutputType) const -//{ -// // Blueprint components have limited translation support, for now. -// if (OutputType == EHoudiniOutputType::Mesh) -// return true; -// -// return false; -//} -// -bool -UHoudiniAssetBlueprintComponent::CanDeleteHoudiniNodes() const -{ - return bCanDeleteHoudiniNodes; -} - -void -UHoudiniAssetBlueprintComponent::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; - - for (UHoudiniInput* Input : Inputs) - { - Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for (UHoudiniOutput* Output : Outputs) - { - Output->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -bool -UHoudiniAssetBlueprintComponent::IsValidComponent() const -{ - if (!Super::IsValidComponent()) - return false; - - if (IsTemplate()) - { - UObject* Outer = this->GetOuter(); - if (!IsValid(Outer)) - return false; - UBlueprintGeneratedClass* BPGC = Cast(Outer); - if (!BPGC) - return false; - // Ensure this component is still in the SCS - USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; - if (!SCS) - return false; - USCS_Node* SCSNode = FindSCSNodeForTemplateComponentInClassHierarchy(this); - if (!SCSNode) - return false; - /*UClass* OwnerClass = Outer->GetClass(); - if (!IsValid(OwnerClass)) - return false;*/ - /*UBlueprint* Blueprint = Cast(Outhe); - if (Blueprint) - { - - }*/ - } - -#if WITH_EDITOR - if (!IsTemplate()) - { - if (!GetOwner()) - { - // If it's not a template, it needs an owner! - return false; - } - - USimpleConstructionScript* SCS = GetSCS(); - if (SCS) - { - // We're dealing with a Blueprint related component. - AActor* PreviewActor = GetPreviewActor(); - AActor* OwningActor = GetOwner(); - if (!OwningActor) - return false; - if (OwningActor != PreviewActor) - { - return false; - } - } - - } - - if (IsPreview() && false) - { - USimpleConstructionScript* SCS = GetSCS(); - if (!SCS) - return false; // Preview components should have an SCS. - - // We want to specifically detect whether an editor component is still being previewed. We do this - // by checking whether the owning actor is still the active editor actor in the SCS. - AActor* PreviewActor = GetPreviewActor(); - if (!PreviewActor) - { - return false; - } - - // Ensure this component still belongs the to the current preview actor. - if (PreviewActor != GetOwner()) - { - return false; - } - - /* - AActor* EditorActor = SCS->GetComponentEditorActorInstance(); - if (GetOwner() != EditorActor) - { - return false; - } - */ - } -#endif - - return true; -} - -bool -UHoudiniAssetBlueprintComponent::IsInputTypeSupported(EHoudiniInputType InType) const -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - case EHoudiniInputType::Curve: - return true; - break; - default: - break; - } - return false; -} - -bool -UHoudiniAssetBlueprintComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const -{ - switch (InType) - { - case EHoudiniOutputType::Mesh: - case EHoudiniOutputType::Instancer: - return true; - break; - default: - break; - } - return false; -} - -bool -UHoudiniAssetBlueprintComponent::IsProxyStaticMeshEnabled() const -{ - // TODO: Investigate adding support for proxy meshes in BP - // Disabled for now - return false; -} - -//void -//UHoudiniAssetBlueprintComponent::BroadcastPreAssetCook() -//{ -// // ------------------------------------------------ -// // NOTE: This code will run on TEMPLATE components -// // ------------------------------------------------ -// -// // The HoudiniAsset is about to be recooked. This flag will indicate to -// // the transient components that output processing needs to be baked -// // back to the BP definition. -// bOutputsRequireUpdate = true; -// -// Super::BroadcastPreAssetCook(); -//} - -void -UHoudiniAssetBlueprintComponent::OnPrePreCook() -{ - check(IsPreview()); - - Super::OnPrePreCook(); - - // We need to allow deleting houdini nodes - SetCanDeleteHoudiniNodes(true); -} - -void -UHoudiniAssetBlueprintComponent::OnPostPreCook() -{ - check(IsPreview()); - - Super::OnPostPreCook(); - - // Ensure the houdini nodes can be deleted during the translation process. - SetCanDeleteHoudiniNodes(false); -} - -void -UHoudiniAssetBlueprintComponent::OnPreOutputProcessing() -{ - check(IsPreview()); - - Super::OnPreOutputProcessing(); - - // Ensure the houdini nodes can be deleted during the translation process. - SetCanDeleteHoudiniNodes(true); -} - -void -UHoudiniAssetBlueprintComponent::OnPostOutputProcessing() -{ - Super::OnPostOutputProcessing(); - - // ------------------------------------------------ - // NOTE: - // In Blueprint editor mode, this code will run on PREVIEW components - // In Map editor mode, this code will run on component instances. - // ------------------------------------------------ - if (IsPreview()) - { - // Ensure all the inputs / outputs belonging to the - // preview actor won't be deleted by PreviewActor destruction. - SetCanDeleteHoudiniNodes(false); - -#if WITH_EDITOR - CopyStateToTemplateComponent(); -#endif - - } - bUpdatedFromTemplate = false; -} - -void UHoudiniAssetBlueprintComponent::OnPrePreInstantiation() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnPrePreInstantiation] Component: %s"), *(GetPathName())); - - check(IsPreview()); - - if (bUpdatedFromTemplate) - return; - - check(CachedTemplateComponent.IsValid()); - - // This HDA is about to be cooked but not through template parameter changes. It is likely that an input changed directly in the preview world. - // We need to flag our inputs and parameters appropriately in order to preserve their values. - - // We need to mark all our parameters as changed/not triggering update - for (auto CurrentParam : Parameters) - { - if (CurrentParam) - { - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(false); - } - } - - // We need to mark all our inputs as changed/not triggering update - for (auto CurrentInput : Inputs) - { - if (CurrentInput) - { - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(false); - CurrentInput->MarkDataUploadNeeded(true); - } - } -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniRegisterCompleted() -{ - if (IsTemplate()) - { - // TODO: Do we need to set any status flags or clear stuff to ensure - // the BP HAC will cook properly when the BP is opened again... - - // If the template is being registered, we need to invalidate the AssetId here since it likely - // contains a stale asset id from its last cook. - AssetId = -1; - // Template component's have very limited update requirements / capabilities. - // Mostly just cache parameters and cook state. - SetAssetState(EHoudiniAssetState::ProcessTemplate); - } - - Super::NotifyHoudiniRegisterCompleted(); -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniPreUnregister() -{ - if (IsTemplate()) - { - // Templates can delete Houdini nodes when they get deregistered. - SetCanDeleteHoudiniNodes(true); - } - Super::NotifyHoudiniPreUnregister(); -} - -void -UHoudiniAssetBlueprintComponent::NotifyHoudiniPostUnregister() -{ - InvalidateData(); - - Super::NotifyHoudiniPostUnregister(); - - if (IsTemplate()) - { - SetCanDeleteHoudiniNodes(false); - } -} - -#if WITH_EDITOR -void -UHoudiniAssetBlueprintComponent::OnComponentCreated() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnComponentCreated] Component: %s"), *(GetPathName())); - - Super::OnComponentCreated(); - bUpdatedFromTemplate = false; - - CachePreviewState(); - - if (IsPreview()) - { - // Don't set an initial AssetState here. Preview components should only cook when template's - // Houdini Asset or HDA parameters have changed. - - // Clear these to ensure that we're not sharing references with the component template (otherwise - // the shared objects will get deleted when the component instance gets destroyed). - // These objects will be properly duplicated when copying state from the component template. - Inputs.Empty(); - Parameters.Empty(); - } - - // Wait until InitializeComponent() for blueprint construction to complete before we start caching blueprint data. - -} -#endif - - -void -UHoudiniAssetBlueprintComponent::OnRegister() -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnRegister] Component: %s"), *(GetPathName())); - - Super::OnRegister(); - - // We run our Blueprint caching functions here since this the last hook that we have before - // entering HoudiniEngineTick(); - - CacheBlueprintData(); - CachePreviewState(); - - if (IsPreview()) - { - check(CachedTemplateComponent.Get()); - // Ensure that the component template has been registered since it needs to be processed for parameter updates by the HE manager. - if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(CachedTemplateComponent.Get())) - { - // The template component has not been registered yet, which means that we're probably busy opening a Blueprint editor and this - // preview component will need to be updated. - - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(CachedTemplateComponent.Get(), true); - CachedTemplateComponent->SetCanDeleteHoudiniNodes(false); - // Since we're likely opening a fresh blueprint editor, we'll need to instantiate the HDA. - bHasRegisteredComponentTemplate = true; - } - } - - if (IsTemplate()) - { - // We're initializing the asset id for HAC template here since it doesn't get unloaded - // from memory, for example, between Blueprint Editor open/close so we need to make sure - // that the AssetId has indeed been reset between registrations. - AssetId = -1; - } - - //TickInitialization(); -} - -void -UHoudiniAssetBlueprintComponent::BeginDestroy() -{ - Super::BeginDestroy(); -} - -void -UHoudiniAssetBlueprintComponent::DestroyComponent(bool bPromoteChildren) -{ - //FDebug::DumpStackTraceToLog(); - if (CachedTemplateComponent.IsValid() && TemplatePropertiesChangedHandle.IsValid()) - { - CachedTemplateComponent->Modify(); - CachedTemplateComponent->OnParametersChangedEvent.Remove(TemplatePropertiesChangedHandle); - } - Super::DestroyComponent(bPromoteChildren); -} - -void -UHoudiniAssetBlueprintComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - Super::OnComponentDestroyed(bDestroyingHierarchy); -} - -TStructOnScope -UHoudiniAssetBlueprintComponent::GetComponentInstanceData() const -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::GetComponentInstanceData] Component: %s"), *(GetPathName())); - - TStructOnScope ComponentInstanceData = MakeStructOnScope(this); - FHoudiniAssetBlueprintInstanceData* InstanceData = ComponentInstanceData.Cast(); - - InstanceData->AssetId = AssetId; - InstanceData->AssetState = AssetState; - InstanceData->SubAssetIndex = SubAssetIndex; - InstanceData->ComponentGUID = ComponentGUID; - InstanceData->HapiGUID = HapiGUID; - InstanceData->HoudiniAsset = HoudiniAsset; - InstanceData->SourceName = GetPathName(); - InstanceData->AssetCookCount = AssetCookCount; - InstanceData->bHasBeenLoaded = bHasBeenLoaded; - InstanceData->bHasBeenDuplicated = bHasBeenDuplicated; - InstanceData->bPendingDelete = bPendingDelete; - InstanceData->bRecookRequested = bRecookRequested; - InstanceData->bEnableCooking = bEnableCooking; - InstanceData->bForceNeedUpdate = bForceNeedUpdate; - InstanceData->bLastCookSuccess = bLastCookSuccess; - InstanceData->bRegisteredComponentTemplate = bHasRegisteredComponentTemplate; - - InstanceData->Inputs.Empty(); - - for (UHoudiniInput* Input : Inputs) - { - if (!Input) - continue; - UHoudiniInput* TransientInput = Input->DuplicateAndCopyState(GetTransientPackage(), false); - InstanceData->Inputs.Add(TransientInput); - } - - // Cache the current outputs - InstanceData->Outputs.Empty(); - int OutputIndex = 0; - for(UHoudiniOutput* Output : Outputs) - { - if (!Output) - continue; - - TMap OutputObjects = Output->GetOutputObjects(); - for (auto& Entry : OutputObjects) - { - FHoudiniAssetBlueprintOutput OutputObjectData; - OutputObjectData.OutputIndex = OutputIndex; - OutputObjectData.OutputObject = Entry.Value; - InstanceData->Outputs.Add(Entry.Key, OutputObjectData); - } - - ++OutputIndex; - } - - return ComponentInstanceData; - -} - -void -UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData(FHoudiniAssetBlueprintInstanceData* InstanceData, const bool bPostUCS) -{ - HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData] Component: %s"), *(GetPathName())); - check(InstanceData); - - if (!bPostUCS) - { - // Initialize the component before the User Construction Script runs - USimpleConstructionScript* SCS = GetSCS(); - check(SCS); - - TArray StaleInputs(Inputs); - - // We need to update references contain in inputs / outputs to point to new reconstructed components. - const int32 NumInputs = InstanceData->Inputs.Num(); - Inputs.SetNum(NumInputs); - for (int i = 0; i < NumInputs; ++i) - { - UHoudiniInput* FromInput = InstanceData->Inputs[i]; - UHoudiniInput* ToInput = Inputs[i]; - - if (ToInput) - { - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - if (!bIsValid) - { - ToInput = nullptr; - } - } - - if (ToInput) - { - // Reuse input - StaleInputs.Remove(ToInput); - ToInput->CopyStateFrom(FromInput, true, false); - } - else - { - // Create new input - ToInput = FromInput->DuplicateAndCopyState(this, false); - } - -#if WITH_EDITOR - // We can't create missing SCS nodes here since we're likely already in the middle of a - // Blueprint reconstruction. We'll have to recreate missing SCS nodes next time the - // component state if copied to the template. - UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, true, false); -#endif - - Inputs[i] = ToInput; - } - - // We need to update FHoudiniOutputObject SceneComponent references to - // the newly created components. Since we cached a map of Output Object IDs to - // SCSNodes (during CopyStateToTemplateComponent), we can the SCSNode that corresponds to this output objects and find - // the SceneComponent that matches the SCSNode's variable name. - // It is important to note that it is safe to do it this way since we're in the pre-UCS - // phase so that current components should match the SCS graph exactly (no user construction script - // interference here yet). - - for (auto& Entry : InstanceData->Outputs) - { - FHoudiniOutputObjectIdentifier& ObjectId = Entry.Key; - FHoudiniAssetBlueprintOutput& OutputData = Entry.Value; - - // NOTE: Output objects are going to be empty here since they dissapear during actor reconstruction. - // We'll need to repopulate from the instance data. - - check(Outputs.IsValidIndex(OutputData.OutputIndex)); - UHoudiniOutput* Output = Outputs[OutputData.OutputIndex]; - check(Output); - TMap& OutputObjects = Output->GetOutputObjects(); - FHoudiniOutputObject NewObject = OutputData.OutputObject; - - if (OutputData.OutputObject.OutputComponent) - { - // Update the output component reference. - check(CachedOutputNodes.Contains(ObjectId)) - const FGuid VariableGuid = CachedOutputNodes.FindChecked(ObjectId); - USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(VariableGuid); - - if (SCSNode) - { - // Find the component that corresponds to the SCS node. - USceneComponent* SceneComponent = FindActorComponentByName(GetOwner(), SCSNode->GetVariableName()); - NewObject.OutputComponent = SceneComponent; - } - else - { - NewObject.OutputComponent = nullptr; - } - } - - OutputObjects.Add(ObjectId, NewObject); - } - - if (CachedTemplateComponent.IsValid()) - { -#if WITH_EDITOR - CopyStateFromTemplateComponent( CachedTemplateComponent.Get(), false, false, true); -#endif - } - - AssetId = InstanceData->AssetId; - SubAssetIndex = InstanceData->SubAssetIndex; - ComponentGUID = InstanceData->ComponentGUID; - HapiGUID = InstanceData->HapiGUID; - - // Apply the previous HoudiniAsset to the component - // so that we can compare it against the template during CopyStateFromTemplate() calls to see whether it changed. - HoudiniAsset = InstanceData->HoudiniAsset; - - AssetCookCount = InstanceData->AssetCookCount; - bHasBeenLoaded = InstanceData->bHasBeenLoaded; - bHasBeenDuplicated = InstanceData->bHasBeenDuplicated; - bPendingDelete = InstanceData->bPendingDelete; - bRecookRequested = InstanceData->bRecookRequested; - bEnableCooking = InstanceData->bEnableCooking; - bForceNeedUpdate = InstanceData->bForceNeedUpdate; - bLastCookSuccess = InstanceData->bLastCookSuccess; - bHasRegisteredComponentTemplate = InstanceData->bRegisteredComponentTemplate; - - AssetState = InstanceData->AssetState; - - SetCanDeleteHoudiniNodes(false); - - } // if (!bPostUCS) - /* - else - { - // PostUCS - - } - */ -} - - -void -UHoudiniAssetBlueprintComponent::HoudiniEngineTick() -{ - if (!IsFullyLoaded()) - { - USimpleConstructionScript* SCS = GetSCS(); - if (SCS == nullptr) - { - OnFullyLoaded(); - } - else if (IsPreview()) - { - AActor* OwningActor = GetOwner(); - check(OwningActor); - - // If this is a *preview component*, it is important to wait for the template component to be fully loaded - // since it needs to be initialized so that the component instance can copy initial values from the template. - check(CachedTemplateComponent.Get()); - - if (CachedTemplateComponent->IsFullyLoaded()) - { -#if WITH_EDITOR - if(SCS->IsConstructingEditorComponents()) - { - // We're stuck in an editor blueprint construction / preview actor update. Wait some more. - } - else - { - OnFullyLoaded(); - } -#else - OnFullyLoaded(); -#endif - } - } - else - { - // Anything else can go onto being fully loaded at this point. - OnFullyLoaded(); - } - } -} - -void -UHoudiniAssetBlueprintComponent::OnFullyLoaded() -{ - Super::OnFullyLoaded(); - - // Check whether this component is inside a Blueprint editor. If this object lives outside the blueprint editor (in , then we need to ensure that this - // component won't be influencing the Blueprint asset. - - if (!IsTemplate()) - { -#if WITH_EDITOR - AActor* PreviewActor = GetPreviewActor(); -#else - AActor* PreviewActor = nullptr; -#endif - AActor* OwningActor = GetOwner(); - - if (!PreviewActor) - { - bIsInBlueprintEditor = false; - AssetState = EHoudiniAssetState::None; - return; - } - - if (OwningActor && PreviewActor != OwningActor) - { - bIsInBlueprintEditor = false; - AssetState = EHoudiniAssetState::None; - return; - } - } - - bIsInBlueprintEditor = true; - - CachePreviewState(); - CacheBlueprintData(); - - /* - for (UHoudiniOutput* Output : Outputs) - { - if (!Output) - continue; - } - */ - - if (IsTemplate()) - { - AssetId = -1; - AssetState = EHoudiniAssetState::ProcessTemplate; - } - - if (IsPreview()) - { - check(CachedTemplateComponent.Get()); - - // If this is a preview actor, sync initial settings from the component template -#if WITH_EDITOR - CopyStateFromTemplateComponent(CachedTemplateComponent.Get(), false, false, true); -#endif - - TemplatePropertiesChangedHandle = CachedTemplateComponent->OnParametersChangedEvent.AddUObject(this, &UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler); - if (bHoudiniAssetChanged) - { - - // The HoudiniAsset has changed, so we need to force the PreviewInstance to re-instantiate - AssetState = EHoudiniAssetState::NeedInstantiation; - bForceNeedUpdate = true; - bHoudiniAssetChanged = false; - // TODO: Make this better? - CachedTemplateComponent->bHoudiniAssetChanged = false; - } - - if (bHasRegisteredComponentTemplate) - { - // We have a newly registered component template. One of two things happened to cause this: - // 1. A new HoudiniAssetBlueprintComponent was created and registered. - // 2. The Blueprint Editor was closed / opened. - // The problem that arises in the #2 is that the template component was never fully unloaded - // from memory (it was deregistered but not destroyed). After deregistration we had the - // opportunity to invalidate asset/node ids but now that it has reregistered (without going - // through the normal initialization process) we will have to force a call to MarkAsNeedInstantiation - // during the next OnTemplateParametersChangedHandler() invocation. - bHasBeenLoaded = true; - } - } -} - -void -UHoudiniAssetBlueprintComponent::OnTemplateParametersChanged() -{ - OnParametersChangedEvent.Broadcast(this); -} - -void UHoudiniAssetBlueprintComponent::OnBlueprintStructureModified() -{ - check(IsTemplate()); - bBlueprintStructureModified = false; - -#if WITH_EDITOR - if (IsTemplate()) - { - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(this); - } - else - { - check(CachedTemplateComponent.IsValid()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); - } -#endif -} - -void UHoudiniAssetBlueprintComponent::OnBlueprintModified() -{ - check(IsTemplate()); - bBlueprintModified = false; -#if WITH_EDITOR - FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(this); -#endif -} - -void -UHoudiniAssetBlueprintComponent::OnHoudiniAssetChanged() -{ - if (IsTemplate()) - { - // Invalidate data associated with this component since we're about to change and reinstantiate the Houdini Asset. - SetCanDeleteHoudiniNodes(true); - InvalidateData(); - SetCanDeleteHoudiniNodes(false); - Parameters.Empty(); - Inputs.Empty(); - } - - Super::OnHoudiniAssetChanged(); - - // Set on template components, then copied to preview components, and - // then used (and reset) during OnFullyLoaded. - bHoudiniAssetChanged = true; -} - -void -UHoudiniAssetBlueprintComponent::RegisterHoudiniComponent(UHoudiniAssetComponent *InComponent) -{ - // We only want to register this component if it is the preview actor for the Blueprint editor. -#if WITH_EDITOR - AActor* PreviewActor = GetPreviewActor(); -#else - AActor* PreviewActor = nullptr; -#endif - AActor* OwningActor = GetOwner(); - if (!OwningActor) - return; - - if (PreviewActor != OwningActor) - return; - - Super::RegisterHoudiniComponent(InComponent); -} - - - - -//bool UHoudiniAssetBlueprintComponent::TickInitialization() -//{ -// return true; -// -// if (IsFullyLoaded()) -// return true; -// -// bool bHasFinishedLoading = Super::TickInitialization(); -// -// if (!bHasFinishedLoading) -// return false; -// -// if (!IsTemplate()) -// { -// -// if (CachedTemplateComponent.Get()) -// { -// // Now that that SCS has finished constructing editor components, we can continue. -// // Copy the current state from the template component, in case there is something that can be processed. -// CopyStateFromTemplateComponent(CachedTemplateComponent.Get()); -// } -// -// AssetStateResult = EHoudiniAssetStateResult::None; -// AssetState = EHoudiniAssetState::None; -// -// bForceNeedUpdate = true; -// AssetState = EHoudiniAssetState::PostCook; -// } -// -// return true; -//} - -template -inline void -UHoudiniAssetBlueprintComponent::SetTypedValueAt(const FString& Name, ValueT& Value, int Index) -{ - ParamT* Parameter = Cast(FindParameterByName(Name)); - if (!Parameter) - return; - - Parameter->SetValueAt(Value, Index); -} - -bool -UHoudiniAssetBlueprintComponent::HasParameter(FString Name) -{ - return FindParameterByName(Name) != nullptr; -} - -void -UHoudiniAssetBlueprintComponent::SetFloatParameter(FString Name, float Value, int Index) -{ - SetTypedValueAt(Name, Value, Index); -} - -void -UHoudiniAssetBlueprintComponent::SetToggleValueAt(FString Name, bool Value, int Index) -{ - UHoudiniParameterToggle* Parameter = Cast(FindParameterByName(Name)); - if (!Parameter) - return; - - Parameter->SetValueAt(Value, Index); -} - -//void UHoudiniAssetBlueprintComponent::OnPostCookHandler(UHoudiniAssetComponent* InComponent) -//{ -// -// // Before this component handles any translation, we need to make sure that it still belongs to the editor actor. -// // When a blueprint gets recompiled, the editor actor gets replaced with a new one but the old actor has not yet -// // been ftroyed / garbage collected so its components still receive cook events from the template. -// -// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponent); -// if (!IsValid(ComponentTemplate)) -// return; -// -// CopyStateFromTemplateComponent(ComponentTemplate); -// bForceNeedUpdate = true; -//} - -void -UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler(UHoudiniAssetComponent* InComponentTemplate) -{ - if (!(AssetState == EHoudiniAssetState::None || AssetState == EHoudiniAssetState::NeedInstantiation || AssetState == EHoudiniAssetState::NeedRebuild)) - // Don't process parameter changes since we're already cooking -- it is going to break things badly if we do. - return; - - if (!IsValidComponent()) - return; - - UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); - if (!ComponentTemplate) - return; - - // The component instance needs to copy values from the template. - bool bBlueprintStructureChanged = false; -#if WITH_EDITOR - CopyDetailsFromComponent(ComponentTemplate, - false, - false, - true, - false, - true, - bBlueprintStructureChanged, - RF_Public, - RF_ClassDefaultObject|RF_ArchetypeObject); -#endif - - SetCanDeleteHoudiniNodes(false); - - if (bHasRegisteredComponentTemplate) - { - // NOTE: It is very important to call this *after* CopyDetailsFromComponent(), since CopyDetailsFromComponent - // will clobber the inputs and parameter states on this component. - - // If we already have a valid asset id, keep it. - if (AssetId >= 0) - { - MarkAsNeedCook(); - } - else - { - MarkAsNeedInstantiation(); - } - - bHasRegisteredComponentTemplate = false; - bFullyLoaded = true; // MarkAsNeedInstantiation sets this to false. Force to true. - // While MarkAsNeedInstantiation() sets ParametersChanged to true, it does not - // set the 'NeedToTriggerUpdate' flag (both of which needs to be true in order - // to trigger an HDA update) so we are going to force NeedUpdate() to return true - // in order to get an initial cook. - bForceNeedUpdate = true; - } - - bUpdatedFromTemplate = true; -} - -void -UHoudiniAssetBlueprintComponent::InvalidateData() -{ - if (IsTemplate()) - { - // Ensure transient properties are invalidated/released for parameters, inputs and outputs as if the - // the object was undergoing destruction since the template component will likely be reregistered - // without being destroyed. - for(UHoudiniParameter* Param : Parameters) - { - Param->InvalidateData(); - } - - for(UHoudiniInput* Input : Inputs) - { - Input->InvalidateData(); - } - - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - AssetId = -1; - } -} - -//void UHoudiniAssetBlueprintComponent::OnTemplateHoudiniAssetChangedHandler(UHoudiniAssetComponent* InComponentTemplate) -//{ -// -// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); -// if (!ComponentTemplate) -// return; -// check(IsPreview()); -// -// // The Houdini Asset was changed on the template. We need to recook. -// AssetState = EHoudiniAssetState::NeedInstantiation; -// -//} - -USceneComponent* -UHoudiniAssetBlueprintComponent::FindOwnerComponentByName(FName ComponentName) const -{ - AActor* Owner = GetOwner(); - if (!Owner) - return nullptr; - - return FindActorComponentByName(Owner, ComponentName); -} - -USceneComponent* -UHoudiniAssetBlueprintComponent::FindActorComponentByName(AActor* InActor, FName ComponentName) const -{ - const TSet& Components = InActor->GetComponents(); - - for (UActorComponent* Component : Components) - { - USceneComponent* SceneComponent = Cast(Component); - if (!IsValid(SceneComponent)) - continue; - if (FName(SceneComponent->GetName()) == ComponentName) - return SceneComponent; - } - - return nullptr; -} - -bool UHoudiniAssetBlueprintComponent::GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid) -{ - FGuid* SCSGuid = CachedInputNodes.Find(InputGuid); - if (!SCSGuid) - return false; - OutSCSGuid = *SCSGuid; - return true; -} - -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const -{ - const TArray& AllNodes = SCS->GetAllNodes(); - - for (USCS_Node* Node : AllNodes) - { - if (!Node) - continue; - - if (Node->ComponentTemplate == InComponent) - return Node; - } - - return nullptr; -} - -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponentInClassHierarchy( - const UActorComponent* InComponent) const -{ - UObject* Outer = this->GetOuter(); - if (!IsValid(Outer)) - return nullptr; - UBlueprintGeneratedClass* MainBPGC; - if (IsTemplate()) - { - MainBPGC = Cast(Outer); - } - else - { - AActor* OwningActor = GetOwner(); - MainBPGC = Cast(OwningActor->GetClass()); - } - - check(MainBPGC); - TArray BPGCStack; - UBlueprintGeneratedClass::GetGeneratedClassesHierarchy(MainBPGC, BPGCStack); - for(const UBlueprintGeneratedClass* BPGC : BPGCStack) - { - USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; - if (!SCS) - return nullptr; - USCS_Node* SCSNode = FindSCSNodeForTemplateComponent(SCS, InComponent); - SCSNode = SCS->FindSCSNode(InComponent->GetFName()); - if (SCSNode) - return SCSNode; - } - - return nullptr; -} - -#if WITH_EDITOR -USCS_Node* -UHoudiniAssetBlueprintComponent::FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const -{ - const TArray& AllNodes = SCS->GetAllNodes(); - - if (!InComponent) - return nullptr; - - for (USCS_Node* Node : AllNodes) - { - if (!Node) - continue; - if (Node->EditorComponentInstance.Get() == InComponent) - return Node; - } - - return nullptr; -} -#endif - -UActorComponent* -UHoudiniAssetBlueprintComponent::FindComponentInstanceInActor(const AActor* InActor, - USCS_Node* SCSNode) const -{ - UActorComponent* ComponentTemplate = SCSNode->ComponentTemplate; - - UActorComponent* ComponentInstance = NULL; - if (InActor != NULL) - { - if (SCSNode != NULL) - { - FName VariableName = SCSNode->GetVariableName(); - if (VariableName != NAME_None) - { - UWorld* World = InActor->GetWorld(); - FObjectPropertyBase* Property = FindFProperty(InActor->GetClass(), VariableName); - if (Property != NULL) - { - // Return the component instance that's stored in the property with the given variable name - ComponentInstance = Cast(Property->GetObjectPropertyValue_InContainer(InActor)); - } - else if (World != nullptr && World->WorldType == EWorldType::EditorPreview) - { - // If this is the preview actor, return the cached component instance that's being used for the pmnaview actor prior to recompiling the Blueprint -#if WITH_EDITOR - ComponentInstance = SCSNode->EditorComponentInstance.Get(); -#endif - } - } - } - else if (ComponentTemplate != NULL) - { -#if WITH_EDITOR - TInlineComponentArray Components; - InActor->GetComponents(Components); - ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components); -#endif - } - } - - return ComponentInstance; -} - - -//void UHoudiniAssetBlueprintComponent::OnOutputProcessingCompletedHandler(UHoudiniAssetComponent* InComponent) -//{ -// -// UHoudiniAssetBlueprintComponent* TemplateComponent = Cast(InComponent); -// if (!IsValid(TemplateComponent)) -// return; -// -// CopyStateFromComponent(TemplateComponent); -// bForceNeedUpdate = true; -//} - -//#if WITH_EDITOR -//void UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason) -//{ -// -// if (CachedBlueprint.Get()) -// { -// } -// -// if (Asset) -// { -// -// } -// -//} -//#endif - -void -UHoudiniAssetBlueprintComponent::CachePreviewState() -{ - bCachedIsPreview = false; - -#if WITH_EDITOR - AActor* ComponentOwner = GetOwner(); - if (!IsValid(ComponentOwner)) - return; - - USimpleConstructionScript* SCS = GetSCS(); - if (SCS == nullptr) - return; - - // Get the preview actor directly from the BlueprintEditor. - FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); - if (BlueprintEditor) - { - AActor* PreviewActor = BlueprintEditor->GetPreviewActor(); - if (PreviewActor == ComponentOwner) - { - bCachedIsPreview = true; - return; - } - } -#endif -} - -void -UHoudiniAssetBlueprintComponent::CacheBlueprintData() -{ - CachedBlueprint = nullptr; - CachedActorCDO = nullptr; - CachedTemplateComponent = IsTemplate() ? this : nullptr; - -#if WITH_EDITOR - CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); -#endif - - UBlueprintGeneratedClass* BPGC = Cast(GetOuter()); - if (BPGC) - { - // Dealing with a component template - CachedBlueprint = Cast(BPGC->ClassGeneratedBy); - } - else - { - // Dealing with a component instance. - CachedBlueprint = Cast(GetOuter()->GetClass()->ClassGeneratedBy); - } - - if (CreationMethod != EComponentCreationMethod::SimpleConstructionScript) - return; - - AActor* ComponentOwner = this->GetOwner(); - if (!IsValid(ComponentOwner)) - return; - UClass* OwnerClass = ComponentOwner->GetClass(); - if (!IsValid(OwnerClass)) - return; - - if (!IsTemplate()) - { - // NOTE: The following code allows us to find the component template from an instance. - CachedActorCDO = Cast< AActor >(CachedBlueprint->GeneratedClass->GetDefaultObject()); - if (!CachedActorCDO.IsValid() || (CachedActorCDO.Get() == ComponentOwner)) - return; -#if WITH_EDITOR - UActorComponent* TargetComponent = EditorUtilities::FindMatchingComponentInstance(this, CachedActorCDO.Get()); - CachedTemplateComponent = Cast(TargetComponent); -#endif - } - -} - -USimpleConstructionScript* -UHoudiniAssetBlueprintComponent::GetSCS() const -{ - if (!CachedBlueprint.Get()) - return nullptr; - - return CachedBlueprint->SimpleConstructionScript; -} - -//------------------------------------------------------------------------------------------------ -// FHoudiniAssetBlueprintInstanceData -//------------------------------------------------------------------------------------------------ - -FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData() - : HoudiniAsset(nullptr) - , AssetId(-1) - , AssetState(EHoudiniAssetState::None) - , SubAssetIndex(-1) - , AssetCookCount(0) - , bHasBeenLoaded(false) - , bHasBeenDuplicated(false) - , bPendingDelete(false) - , bRecookRequested(false) - , bRebuildRequested(false) - , bEnableCooking(true) - , bForceNeedUpdate(false) - , bLastCookSuccess(false) - , ComponentGUID(FGuid()) - , HapiGUID(FGuid()) - , bRegisteredComponentTemplate(false) - , SourceName() -{ - -} - -FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent) - : FActorComponentInstanceData(SourceComponent) -{ - -} - -void -FHoudiniAssetBlueprintInstanceData::AddReferencedObjects(FReferenceCollector & Collector) -{ - Super::AddReferencedObjects(Collector); - // TODO: Do we need to add references to output objects here? - // Any other references? - // What are the implications? -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetBlueprintComponent.h" + +#include "HoudiniEngineCopyPropertiesInterface.h" +#include "HoudiniOutput.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Engine/SCS_Node.h" +#include "Engine/SimpleConstructionScript.h" +#include "UObject/Object.h" +#include "Logging/LogMacros.h" + +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniInput.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Kismet2/BlueprintEditorUtils.h" + #include "Kismet2/KismetEditorUtilities.h" + #include "Toolkits/AssetEditorManager.h" + #include "Kismet2/ComponentEditorUtils.h" + #include "ComponentAssetBroker.h" +#endif + +HOUDINI_BP_DEFINE_LOG_CATEGORY(); + +UHoudiniAssetBlueprintComponent::UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + +#if WITH_EDITOR + if (IsTemplate()) + { + // CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); + //GEditor->GetEditorSubsystem()->OnAssetEditorRequestClose().AddUObject( this, &UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent ); + } +#endif + + bForceNeedUpdate = false; + bHoudiniAssetChanged = false; + bIsInBlueprintEditor = false; + bCanDeleteHoudiniNodes = false; + + // AssetState will be updated by changes to the HoudiniAsset + // or parameter changes on the Component template. + AssetState = EHoudiniAssetState::None; + bHasRegisteredComponentTemplate = false; + bHasBeenLoaded = false; + bUpdatedFromTemplate = false; + + // Disable proxy mesh by default (unsupported for now) + bOverrideGlobalProxyStaticMeshSettings = true; + bEnableProxyStaticMeshOverride = false; + bEnableProxyStaticMeshRefinementByTimerOverride = false; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = false; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = false; + StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; + + // Set default mobility to Movable + Mobility = EComponentMobility::Movable; +} + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() +{ + // We need to propagate changes made here back to the corresponding component in + // the Blueprint Generated Class ("_GEN_VARIABLE"). The reason being that + // the Blueprint editor works directly with the GEN_VARIABLE component (all + // PostEditChange() calls, Details Customizations, etc will receive the GEN_VARIABLE instance) BUT + // when the Editor runs the construction script it uses a different component instance, so all changes + // made to that instance won't write back to the Blueprint definition. + // To Summarize: + // Be sure to sync the Parameters array (and any other relevant properties) back + // to the corresponding component on the Blueprint Generated class otherwise these wont be + // accessible in the Details Customization callbacks. + + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] To Component: %s"), *(CachedTemplateComponent->GetPathName())); + + // This should never be called by component templates. + check(!IsTemplate()); + + if (!CachedTemplateComponent.IsValid()) + return; + + USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; + check(SCS); + + /* + USCS_Node* SCSNodeForInstance = FindSCSNodeForInstanceComponent(SCS, this); + if (SCSNodeForInstance) + { + + } + else + { + + } + */ + + //// If we don't have an SCS node for this preview instance, we need to create one, regardless + //// of whether output updates are required. + //if (!CachedTemplateComponent->bOutputsRequireUpdate && SCSNodeForInstance != nullptr) + // return; + + // TODO: If the blueprint editor is NOT open, then we shouldn't attempting + // to copy state back to the BPGC at all! + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + check(BlueprintEditor); + + USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + // check(SCSHACNode); + + // This is the actor instance that is being used for component editing. + AActor* PreviewActor = GetPreviewActor(); + check(PreviewActor); + + // NOTE: Inputs are only from component templates to instances, not the other way around ... I think. + + // ----------------------------------------------------- + // Copy outputs to component template + // ----------------------------------------------------- + + // Populate / update the outputs for the template from the preview / instance. + // TODO: Wrap the Blueprint manipulation in a transaction + TArray& TemplateOutputs = CachedTemplateComponent->Outputs; + TSet StaleTemplateOutputs(TemplateOutputs); + + TemplateOutputs.SetNum(Outputs.Num()); + CachedOutputNodes.Empty(); + + for (int i = 0; i < Outputs.Num(); i++) + { + // Find a output on the template that corresponds to this output from the instance. + UHoudiniOutput* TemplateOutput = nullptr; + UHoudiniOutput* InstanceOutput = nullptr; + InstanceOutput = Outputs[i]; + + //check(InstanceOutput) + if (!InstanceOutput || InstanceOutput->IsPendingKill()) + continue; + + // Ensure that instance outputs won't delete houdini content. + // Houdini content should only be allowed to be deleted from + // the component template. + InstanceOutput->SetCanDeleteHoudiniNodes(false); + + TemplateOutput = TemplateOutputs[i]; + + if (TemplateOutput) + { + check(TemplateOutput->GetOuter() == CachedTemplateComponent.Get()); + StaleTemplateOutputs.Remove(TemplateOutput); + } + + + if (TemplateOutput) + { + // Copy properties from the current instance component while preserving output objects + // and instanced outputs. + TemplateOutput->CopyPropertiesFrom(InstanceOutput, true); + } + else + { + // NOTE: If the template output is NULL it means that the HDA spawned a new component / output in the transient world + // and the new output object needs to be copied back to the BPGC. + + // Corresponding template output could not be found. Create one by duplicating the instance output. + TemplateOutput = InstanceOutput->DuplicateAndCopyProperties(CachedTemplateComponent.Get(), FName(InstanceOutput->GetName())); + // Treat these the same one would components created by CreateDefaultSubObject. + // NOTE: CreateDefaultSubobject performs lots of checks, and unfortunately we can't use it directly (it is + // only allowed to be used in a constructor). Not sure whether we need to either. For now, we just set the + // object flags to be similar to components created by CreateDefaultSubobject. + TemplateOutput->SetFlags(RF_Public|RF_ArchetypeObject|RF_DefaultSubObject); + TemplateOutputs[i] = TemplateOutput; + } + + check(TemplateOutput); + TemplateOutput->SetCanDeleteHoudiniNodes(false); + + // Keep track of potential stale output objects on the template component, for this output. + TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); + TArray StaleTemplateObjects; + TemplateOutputObjects.GetKeys(StaleTemplateObjects); + + for (auto& Entry : InstanceOutput->GetOutputObjects()) + { + + // Prepare the FHoudiniOutputObject for the template component + const FHoudiniOutputObject& InstanceObj = Entry.Value; + FHoudiniOutputObject TemplateObj; + + // Any output present in the Instance Outputs should be + // transferred to the template. + // Remove this output object from stale outputs list. + StaleTemplateObjects.Remove(Entry.Key); + + if (TemplateOutputObjects.Contains(Entry.Key)) + { + // Reuse the existing template object + TemplateObj = TemplateOutputObjects.FindChecked(Entry.Key); + } + else + { + // Create a new template output object object by duplicating the instance object. + // Keep the output object, but clear the output component since we have to + // create a new component template. + TemplateObj = InstanceObj; + TemplateObj.ProxyComponent = nullptr; + TemplateObj.OutputComponent = nullptr; + TemplateObj.ProxyObject = nullptr; + } + + USceneComponent* ComponentInstance = Cast(InstanceObj.OutputComponent); + USceneComponent* ComponentTemplate = Cast(TemplateObj.OutputComponent); + UObject* OutputObject = InstanceObj.OutputObject; + + if (ComponentInstance) + { + // The translation process has either constructed new components, or it is + // reusing existing components, or changed an output (or all or none of the aforementioned). + // Carefully inspect the SCS graph to determine whether there is a corresponding + // (and compatible) node for this output. If not, create a new node and remove unusable node, if any. + + USCS_Node* ComponentNode = nullptr; + { + // Check whether the current OutputComponent being referenced by the template is still valid. + // Even if it was removed in the editor, it doesn't have any associated destroyed / pendingkill state. + // Instead we're going to check for validity by finding an SCS node with a matching template component. + bool bValidComponentTemplate = (ComponentTemplate != nullptr); + if (ComponentTemplate) + { + + ComponentNode = FindSCSNodeForTemplateComponentInClassHierarchy(ComponentTemplate); + bValidComponentTemplate = bValidComponentTemplate && (ComponentNode != nullptr); + } + + if (!bValidComponentTemplate) + { + // Either this component was removed from the editor or it doesn't exist yet. + // Ensure the references are cleared + TemplateObj.OutputComponent = nullptr; + ComponentTemplate = nullptr; + } + } + + // NOTE: we can't use the component instance name directly due to the Blueprint compiler performing an internal checking + // using FComponentEditorUtils::IsValidVariableNameString(), which will return false if the name looks like an autogenerated name... + //FString ComponentName = ComponentInstance->GetName(); + FString ComponentName = FBlueprintEditorUtils::GetClassNameWithoutSuffix(ComponentInstance->GetClass()); + FName ComponentFName = FName(ComponentName); + + + const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )( + EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances | + EditorUtilities::ECopyOptions::CallPostEditChangeProperty | + EditorUtilities::ECopyOptions::CallPostEditMove); + + if (IsValid(ComponentNode)) + { + // Check if we have an existing and compatible SCS node containing a USceneComponent as a template component. + bool bComponentNodeIsValid = true; + + ComponentTemplate = Cast(ComponentNode->ComponentTemplate); + + bComponentNodeIsValid = bComponentNodeIsValid && ComponentInstance->GetClass() == ComponentNode->ComponentClass; + bComponentNodeIsValid = bComponentNodeIsValid && ComponentTemplate != nullptr; + // TODO: Do we need to perform any other compatibility checks? + + if (!bComponentNodeIsValid) + { + // Component template is not compatible. We can't reuse it. + + SCSHACNode->RemoveChildNode(ComponentNode); + SCS->RemoveNode(ComponentNode); + ComponentNode = nullptr; + ComponentTemplate = nullptr; + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + } + + if (ComponentNode) + { + // We found a reusable SCS node. Just copy the component instance + // properties over to the existing template. + check(ComponentNode->ComponentTemplate); + + // UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + // //Params.bReplaceObjectClassReferences = false; + // Params.bDoDelta = false; // Perform a deep copy + // Params.bClearReferences = false; + // UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + + FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + + ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; + } + else + { + // We couldn't find a reusable SCS node. + // Duplicate the instance component and create a new corresponding SCS node + ComponentNode = SCS->CreateNode(ComponentInstance->GetClass(), ComponentFName); + + UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // We need a deep copy of parameters here so the CDO values get copied as well + UEditorEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + // FHoudiniEngineRuntimeUtils::CopyComponentProperties(ComponentInstance, ComponentNode->ComponentTemplate, ComponentCopyOptions); + + // { + // UInstancedStaticMeshComponent* Component = Cast(ComponentNode->ComponentTemplate); + // if (Component) + // { + // } + // } + + // NOTE: The EComponentCreationMethod here is currently set to be the same as a component that was + // created manually in the editor. + ComponentNode->ComponentTemplate->CreationMethod = EComponentCreationMethod::Native; + + // Add this node to the SCS root set. + + // Attach the new node the HAC SCS node + // NOTE: This will add the node to the SCS->AllNodes list too but it won't update + // the nodename map. We can't forcibly update the Node/Name map either since the + // relevant functions have not been exported. + SCSHACNode->AddChildNode(ComponentNode); + + // Set the output component. + TemplateObj.OutputComponent = ComponentNode->ComponentTemplate; + + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + + // Cache the mapping between the output and the SCS node. + check(ComponentNode); + CachedOutputNodes.Add(Entry.Key, ComponentNode->VariableGuid); + } // if (ComponentInstance) + /* + else if (InstanceObj.OutputObject) + { + + } + */ + + // Add the updated output object to the template output + TemplateOutputObjects.Add(Entry.Key, TemplateObj); + } + + // Cleanup stale objects for this template output. + for (const auto& StaleId : StaleTemplateObjects) + { + FHoudiniOutputObject& OutputObj = TemplateOutputObjects.FindChecked(StaleId); + + // Ensure the component template is no longer referencing this output. + TemplateOutputObjects.Remove(StaleId); + + USceneComponent* TemplateComponent = Cast(OutputObj.OutputComponent); + + if (TemplateComponent) + { + USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(TemplateComponent); + if (StaleNode) + { + + SCS->RemoveNode(StaleNode, false); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + /* + else + { + + } + */ + } + /* + else + { + + } + */ + } + } //for (int i = 0; i < Outputs.Num(); i++) + + // Clean up stale outputs on the component template. + for (UHoudiniOutput* StaleOutput : StaleTemplateOutputs) + { + if (!StaleOutput) + continue; + + // Remove any components contained in this output from the SCS graph + for (auto& Entry : StaleOutput->GetOutputObjects()) + { + FHoudiniOutputObject& StaleObject = Entry.Value; + USceneComponent* OutputComponent = Cast(StaleObject.OutputComponent); + + if (OutputComponent) + { + + USCS_Node* StaleNode = FindSCSNodeForTemplateComponentInClassHierarchy(OutputComponent); + if (StaleNode) + { + + SCS->RemoveNode(StaleNode, false); + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + } + } + } + + TemplateOutputs.Remove(StaleOutput); + //StaleOutput->ConditionalBeginDestroy(); + } + + SCS->ValidateSceneRootNodes(); + + // Copy parameters from this component to the template component. + // NOTE: We need to do this since the preview component will be cooking the HDA and get populated with + // all the parameters. This data needs to be sent back to the component template. + UClass* ComponentClass = CachedTemplateComponent->GetClass(); + UHoudiniAssetBlueprintComponent* DefaultObj = Cast(ComponentClass->GetDefaultObject()); + bool bBPStructureModified = false; + CachedTemplateComponent->CopyDetailsFromComponent( + this, + true, + true, + true, + false, + true, + bBPStructureModified, + /* SetFlags */ CachedTemplateComponent->GetMaskedFlags(RF_PropagateToSubObjects)); + + if (bBPStructureModified) + CachedTemplateComponent->MarkAsBlueprintStructureModified(); + + // Copy the cached output nodes back to the template so that + // reconstructed actors can correctly update output objects + // with newly constructed components during ApplyComponentInstanceData() calls. + CachedTemplateComponent->CachedOutputNodes = CachedOutputNodes; + + CachedTemplateComponent->MarkPackageDirty(); + PostEditChange(); + + CachedTemplateComponent->AssetId = AssetId; + CachedTemplateComponent->HapiGUID = HapiGUID; + CachedTemplateComponent->AssetCookCount = AssetCookCount; + CachedTemplateComponent->AssetStateResult = AssetStateResult; + CachedTemplateComponent->bLastCookSuccess = bLastCookSuccess; + +#if WITH_EDITOR + // TODO: Do we need to handle this right now or can we wait for the next Houdini Engine manager tick to process it? + if (CachedTemplateComponent->NeedBlueprintStructureUpdate()) + { + // We are about to recompile the blueprint. This will reconstruct the preview actor so we need to ensure + // that the old actor won't release the houdini nodes. + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); + SetCanDeleteHoudiniNodes(false); + } + /*else if (CachedTemplateComponent->NeedBlueprintUpdate()) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(CachedTemplateComponent.Get()); + }*/ +#endif +} +#endif + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties) +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyStateFromTemplateComponent] From Component: %s"), *(FromComponent->GetPathName())); + + // This should never be called by component templates. + check(!IsTemplate()); + + // Make sure all TransientDuplicate properties from the Template Component needed by this transient component + // gets copied. + + ComponentGUID = FromComponent->ComponentGUID; + + /* + { + const TArray Children = GetAttachChildren(); + for (USceneComponent* Child : Children) + { + if (!Child) + continue; + } + } + */ + + // AssetState = FromComponent->PreviewAssetState; + + // This state should not be shared between template / instance components. + //bFullyLoaded = FromComponent->bFullyLoaded; + + bNoProxyMeshNextCookRequested = FromComponent->bNoProxyMeshNextCookRequested; + + // Reconstruct outputs and update them to point to component instances as opposed to templates. + UObject* TemplateOuter = CachedTemplateComponent->GetOuter(); + + USimpleConstructionScript* SCS = CachedBlueprint->SimpleConstructionScript; + check(SCS); + + // NOTE: We can find the SCS node for the HoudiniAssetComponent from either the template component or the instance (editor preview) component. + USCS_Node* SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + check(SCSHACNode); + + // ----------------------------------------------------- + // Copy outputs to component template + // ----------------------------------------------------- + + TArray& TemplateOutputs = CachedTemplateComponent->Outputs; + + TSet StaleInstanceOutputs(Outputs); + + Outputs.SetNum(TemplateOutputs.Num()); + + for (int i = 0; i < TemplateOutputs.Num(); i++) + { + UHoudiniOutput* TemplateOutput = TemplateOutputs[i]; + if (!IsValid(TemplateOutput)) + continue; + + UHoudiniOutput* InstanceOutput = Outputs[i]; + if (!(InstanceOutput->GetOuter() == this)) + InstanceOutput = nullptr; + + if (InstanceOutput) + { + StaleInstanceOutputs.Remove(InstanceOutput); + } + + if (InstanceOutput) + { + // Copy properties from the current instance component while preserving output objects + // and instanced outputs. + InstanceOutput->CopyPropertiesFrom(TemplateOutput, true); + } + else + { + InstanceOutput = TemplateOutput->DuplicateAndCopyProperties(this, FName(TemplateOutput->GetName())); + if (IsValid(InstanceOutput)) + InstanceOutput->ClearFlags(RF_ArchetypeObject|RF_DefaultSubObject); + } + + Outputs[i] = InstanceOutput; + + if (!IsValid(InstanceOutput)) + continue; + + InstanceOutput->SetCanDeleteHoudiniNodes(false); + + TMap& TemplateOutputObjects = TemplateOutput->GetOutputObjects(); + TMap& InstanceOutputObjects = InstanceOutput->GetOutputObjects(); + TArray StaleOutputObjects; + InstanceOutputObjects.GetKeys(StaleOutputObjects); + + for (auto& Entry : TemplateOutputObjects) + { + const FHoudiniOutputObject& TemplateObj = Entry.Value; + FHoudiniOutputObject InstanceObj = TemplateObj; + + if (!InstanceOutputObjects.Contains(Entry.Key)) + continue; + + StaleOutputObjects.Remove(Entry.Key); + InstanceObj = InstanceOutputObjects.FindChecked(Entry.Key); + + } // for (auto& Entry : TemplateOutputObjects) + + // Cleanup stale output objects for this output. + for (const auto& StaleId : StaleOutputObjects) + { + //TemplateOutput + //check(TemplateOutputs); + + FHoudiniOutputObject& OutputObj = InstanceOutputObjects.FindChecked(StaleId); + + InstanceOutputObjects.Remove(StaleId); + if (OutputObj.OutputComponent) + { + //OutputObj.OutputComponent->ConditionalBeginDestroy(); + OutputObj.OutputComponent = nullptr; + } + } + } // for (int i = 0; i < TemplateOutputs.Num(); i++) + + // Cleanup any stale outputs found on the component instance. + for (UHoudiniOutput* StaleOutput : StaleInstanceOutputs) + { + if (!StaleOutput) + continue; + + if (!(StaleOutput->GetOuter() == this)) + continue; + + // We don't want to clear stale outputs on components instances. Only on template components. + StaleOutput->SetCanDeleteHoudiniNodes(false); + } + + // Copy parameters from the component template to the instance. + bool bBlueprintStructureChanged = false; + CopyDetailsFromComponent(FromComponent, + false, + bClearFromInputs, + bClearToInputs, + false, + true, + bBlueprintStructureChanged, + /*SetFlags*/ RF_Public, + /*ClearFlags*/ RF_DefaultSubObject|RF_ArchetypeObject); +} +#endif + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent( + UHoudiniAssetBlueprintComponent* FromComponent, + const bool bCreateSCSNodes, + const bool bClearChangedToInputs, + const bool bClearChangedFromInputs, + const bool bInCanDeleteHoudiniNodes, + const bool bCopyInputObjectComponentProperties, + bool &bOutBlueprintStructureChanged, + EObjectFlags SetFlags, + EObjectFlags ClearFlags) +{ + check(FromComponent); + + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] Component: %s"), *(GetPathName())); + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::CopyDetailsFromComponent] FromComponent: %s"), *(FromComponent->GetPathName())); + + /* + if (!FromComponent->HoudiniAsset) + { + return; + } + */ + + // TODO: Try to reuse objects here when we're able. + //// Copy UHoudiniOutput state from instance to template + //UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + ////Params.bReplaceObjectClassReferences = false; + ////Params.bClearReferences = false; + //Params.bDoDelta = true; + //UEngine::CopyPropertiesForUnrelatedObjects(ComponentInstance, ComponentNode->ComponentTemplate, Params); + + // Record input remapping that will need to take place when duplicating parameters. + TMap InputMapping; + + // ----------------------------------------------------- + // Copy inputs + // ----------------------------------------------------- + + // TODO: Add support for input components + { + TArray& FromInputs = FromComponent->Inputs; + TSet StaleInputs(Inputs); + USimpleConstructionScript* SCS = GetSCS(); + USCS_Node* SCSHACNode = nullptr; + + if (bCreateSCSNodes) + { + SCSHACNode = FindSCSNodeForTemplateComponentInClassHierarchy(CachedTemplateComponent.Get()); + } + + Inputs.SetNum(FromInputs.Num()); + for (int i = 0; i < FromInputs.Num(); i++) + { + UHoudiniInput* FromInput = nullptr; + UHoudiniInput* ToInput = nullptr; + FromInput = FromInputs[i]; + + check(FromInput); + + ToInput = Inputs[i]; + + if (ToInput) + { + // Check whether the instance and template input objects are compatible. + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + + if (!bIsValid) + { + ToInput = nullptr; + } + } + + // TODO: Process stale input objects + + // NOTE: The CopyStateFrom() / DuplicateAndCopyState() will copy/duplicate/cleanup internal inputs to + // ensure that there aren't any shared instances between the ToInput/FromInput. + if (ToInput) + { + // We have a compatible input that we can reuse. + StaleInputs.Remove(ToInput); + ToInput->CopyStateFrom(FromInput, true, bInCanDeleteHoudiniNodes); + } + else + { + + // We don't have an existing / compatible input. Create a new one. + ToInput = FromInput->DuplicateAndCopyState(this, bInCanDeleteHoudiniNodes); + if (SetFlags != RF_NoFlags) + ToInput->SetFlags(SetFlags); + if (ClearFlags != RF_NoFlags) + ToInput->ClearFlags( ClearFlags ); + } + + check(ToInput); + + + UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, bCopyInputObjectComponentProperties, bCreateSCSNodes, SCSHACNode, &bOutBlueprintStructureChanged); + + Inputs[i] = ToInput; + InputMapping.Add(FromInput, ToInput); + + if (bClearChangedToInputs) + { + // Clear the changed flags on the FromInput so that it doesn't trigger + // another update. The ToInput will now be carrying to changed/update flags. + ToInput->MarkChanged(false); + ToInput->MarkAllInputObjectsChanged(false); + } + + if (bClearChangedFromInputs) + { + // Clear the changed flags on the FromInput so that it doesn't trigger + // another update. The ToInput will now be carrying to changed/update flags. + FromInput->MarkChanged(false); + FromInput->MarkAllInputObjectsChanged(false); + } + } + + // Cleanup any stale inputs from this component. + // NOTE: We would typically only have stale inputs when copying state from + // the component instance to the component template. Garbage collection + // eventually picks up the input objects and removes the content + // but until such time we are stuck with those nodes as inputs in the Houdini session + // so we get rid of those nodes immediately here to avoid some user confusion. + for (UHoudiniInput* StaleInput : StaleInputs) + { + if (!IsValid(StaleInput)) + continue; + + check(StaleInput->GetOuter() == this); + + if (StaleInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + StaleInput->ConditionalBeginDestroy(); + } + } + + + // ----------------------------------------------------- + // Copy parameters (and optionally remap inputs). + // ----------------------------------------------------- + TMap ParameterMapping; + + TArray& FromParameters = FromComponent->Parameters; + Parameters.SetNum(FromParameters.Num()); + + for (int i = 0; i < FromParameters.Num(); i++) + { + UHoudiniParameter* FromParameter = nullptr; + UHoudiniParameter* ToParameter = nullptr; + + FromParameter = FromParameters[i]; + + check(FromParameter); + + if (Parameters.IsValidIndex(i)) + { + ToParameter = Parameters[i]; + } + + if (ToParameter) + { + bool bIsValid = true; + // Check whether To/From parameters are compatible + bIsValid = bIsValid && ToParameter->Matches(*FromParameter); + bIsValid = bIsValid && ToParameter->GetOuter() == this; + + if (!bIsValid) + ToParameter = nullptr; + } + + if (ToParameter) + { + // Parameter already exists. Simply sync the state. + ToParameter->CopyStateFrom(FromParameter, true, ClearFlags, SetFlags); + } + else + { + // TODO: Check whether parameters are the same to avoid recreating them. + ToParameter = FromParameter->DuplicateAndCopyState(this, ClearFlags, SetFlags); + Parameters[i] = ToParameter; + } + + check(ToParameter); + ParameterMapping.Add(FromParameter, ToParameter); + + if (bClearChangedFromInputs) + { + // We clear the Changed flag on the FromParameter (most likely on the component template) + // since the template parameter state has now been transfered to the preview component and + // will resume processing from there. + FromParameter->MarkChanged(false); + } + } + + // Apply remappings on the new parameters + for (UHoudiniParameter* ToParameter : Parameters) + { + ToParameter->RemapParameters(ParameterMapping); + ToParameter->RemapInputs(InputMapping); + } + + FProperty* ParametersProperty = GetClass()->FindPropertyByName(TEXT("Parameters")); + FPropertyChangedEvent Evt(ParametersProperty); + PostEditChangeProperty(Evt); + + bEnableCooking = FromComponent->bEnableCooking; + bRecookRequested = FromComponent->bRecookRequested; + bRebuildRequested = FromComponent->bRebuildRequested; +} + +void +UHoudiniAssetBlueprintComponent::UpdateInputObjectComponentReferences( + USimpleConstructionScript* SCS, + UHoudiniInput* FromInput, + UHoudiniInput* ToInput, + const bool bCopyInputObjectProperties, + const bool bCreateMissingSCSNodes, + USCS_Node* SCSHACParent, + bool* bOutSCSNodeCreated) +{ + TArray ToInputObjects; + TArray FromInputObjects; + TArray StaleInputObjects; + + ToInput->GetAllHoudiniInputSplineComponents(ToInputObjects); + FromInput->GetAllHoudiniInputSplineComponents(FromInputObjects); + + StaleInputObjects = ToInputObjects; + + const int32 NumInputObjects = FromInputObjects.Num(); + ToInputObjects.SetNum(NumInputObjects); + + const auto ComponentCopyOptions = ( EditorUtilities::ECopyOptions::Type )(EditorUtilities::ECopyOptions::Default); + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + //Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; + + for(int32 InputObjectIndex = 0; InputObjectIndex < NumInputObjects; ++InputObjectIndex) + { + UHoudiniInputHoudiniSplineComponent* FromInputObject = FromInputObjects[InputObjectIndex]; + UHoudiniInputHoudiniSplineComponent* ToInputObject = ToInputObjects[InputObjectIndex]; + if (!FromInputObject) + continue; + if (!ToInputObject) + continue; + + USCS_Node* SCSNode = nullptr; + if (CachedInputNodes.Contains(ToInputObject->Guid)) + { + // Reuse / update the existing SCS node. + SCSNode = SCS->FindSCSNodeByGuid( CachedInputNodes.FindChecked(ToInputObject->Guid) ); + } + + if (!SCSNode) + { + if (!bCreateMissingSCSNodes) + continue; // This input object should be removed. + } + + USceneComponent* ToComponent = nullptr; + USceneComponent* FromComponent = Cast(FromInputObject->GetObject()); + + StaleInputObjects.Remove(ToInputObject); + + if (FromComponent) + { + if (!SCSNode) + { + if (bCreateMissingSCSNodes) + { + // Create a new SCS node + SCSNode = SCS->CreateNode(FromComponent->GetClass()); + SCSHACParent->AddChildNode(SCSNode); + if (bOutSCSNodeCreated) + { + *bOutSCSNodeCreated = true; + } + AddInputObjectMapping(ToInputObject->Guid, SCSNode->VariableGuid); + } + } + + if (SCSNode) + { + if (bCreateMissingSCSNodes) + { + // If we have been instructed to create missing SCS nodes, assume we are copying + // the the component template. + ToComponent = Cast(SCSNode->ComponentTemplate); + } + else + { + // We are not copying to the component template, so we're assuming this is a + // component instance. Find the component on the owning actor that matches the SCS node. + AActor* ToOwningActor = ToInput->GetTypedOuter(); + check(ToOwningActor); + + ToComponent = Cast(FindComponentInstanceInActor(ToOwningActor, SCSNode)); + } + + if (bCopyInputObjectProperties && ToComponent) + { + USceneComponent* ToAttachParent = ToComponent->GetAttachParent(); + // Copy specific properties from the component template to the instance, if supported by the component. + // We typically resort to this in order to transfer Transient and TransientDuplicate properties from the + // component template over to the instance (typically HasChanged / NeedsToTriggerUpdate flags) in order for + // the instance to cook properly. + IHoudiniEngineCopyPropertiesInterface* ToCopyableComponent = Cast(ToComponent); + if (ToCopyableComponent) + { + // Let the component manage its own data copying. + ToCopyableComponent->CopyPropertiesFrom(FromComponent); + } + else + { + // The component doesn't implement the property copy interface. Simply do a general property copy. + //UEngine::CopyPropertiesForUnrelatedObjects(FromComponent, ToComponent, Params); + FHoudiniEngineRuntimeUtils::CopyComponentProperties(FromComponent, ToComponent, ComponentCopyOptions); + } + ToComponent->PostEditChange(); + } + } + } + + ToInputObject->Update(ToComponent); + ToInputObjects[InputObjectIndex] = ToInputObject; + } + + for (UHoudiniInputObject* StaleInputObject : StaleInputObjects) + { + if (!StaleInputObject) + continue; + StaleInputObject->InvalidateData(); + ToInput->RemoveHoudiniInputObject(StaleInputObject); + ToInput->MarkChanged(true); + // TODO: Find the corresponding SCS node and remove it + } +} + +#endif + +#if WITH_EDITOR +bool +UHoudiniAssetBlueprintComponent::HasOpenEditor() const +{ + if (IsTemplate()) + { + IAssetEditorInstance* EditorInstance = FindEditorInstance(); + + return EditorInstance != nullptr; + } + + return false; +} +#endif + +#if WITH_EDITOR +IAssetEditorInstance* +UHoudiniAssetBlueprintComponent::FindEditorInstance() const +{ + UClass* BPGC = Cast(GetOuter()); + if (!IsValid(BPGC)) + return nullptr; + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!IsValid(Blueprint)) + return nullptr; + if (!CachedAssetEditorSubsystem.IsValid()) + return nullptr; + + IAssetEditorInstance* EditorInstance = CachedAssetEditorSubsystem->FindEditorForAsset(Blueprint, false); + + return EditorInstance; +} +#endif + +#if WITH_EDITOR +AActor* +UHoudiniAssetBlueprintComponent::GetPreviewActor() const +{ + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + if (BlueprintEditor) + { + return BlueprintEditor->GetPreviewActor(); + } + return nullptr; +} +#endif + +UHoudiniAssetComponent* +UHoudiniAssetBlueprintComponent::GetCachedTemplate() const +{ + return CachedTemplateComponent.Get(); +} + +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateParameters() const +//{ +// return IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateInputs() const +//{ +// return !IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanUpdateOutputs() const +//{ +// return !IsTemplate(); +//} +// +//bool +//UHoudiniAssetBlueprintComponent::CanProcessOutputs() const +//{ +// return !IsTemplate(); +//} + +//bool +//UHoudiniAssetBlueprintComponent::CanInstantiateAsset() const +//{ +// // If this is a preview component, it should not trigger an asset instantiation. It should wait +// // for the BPGC template component to finish the cook, get the synced data and then translate. +// +// if (IsPreview()) +// return false; +// +// return true; +//} +// +//// Check whether the HAC can translate Houdini outputs at all +//bool +//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini() const +//{ +// // Template components can't translate Houdini output since they typically do not exist in a world. +// if (IsTemplate()) +// return false; +// // Preview components and normally instanced actors can translate Houdini outputs. +// return true; +//} +// +//// Check whether the HAC can translate a specific output type. +//bool +//UHoudiniAssetBlueprintComponent::CanTranslateFromHoudini(EHoudiniOutputType OutputType) const +//{ +// // Blueprint components have limited translation support, for now. +// if (OutputType == EHoudiniOutputType::Mesh) +// return true; +// +// return false; +//} +// +bool +UHoudiniAssetBlueprintComponent::CanDeleteHoudiniNodes() const +{ + return bCanDeleteHoudiniNodes; +} + +void +UHoudiniAssetBlueprintComponent::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; + + for (UHoudiniInput* Input : Inputs) + { + Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for (UHoudiniOutput* Output : Outputs) + { + Output->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +bool +UHoudiniAssetBlueprintComponent::IsValidComponent() const +{ + if (!Super::IsValidComponent()) + return false; + + if (IsTemplate()) + { + UObject* Outer = this->GetOuter(); + if (!IsValid(Outer)) + return false; + UBlueprintGeneratedClass* BPGC = Cast(Outer); + if (!BPGC) + return false; + // Ensure this component is still in the SCS + USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; + if (!SCS) + return false; + USCS_Node* SCSNode = FindSCSNodeForTemplateComponentInClassHierarchy(this); + if (!SCSNode) + return false; + /*UClass* OwnerClass = Outer->GetClass(); + if (!IsValid(OwnerClass)) + return false;*/ + /*UBlueprint* Blueprint = Cast(Outhe); + if (Blueprint) + { + + }*/ + } + +#if WITH_EDITOR + if (!IsTemplate()) + { + if (!GetOwner()) + { + // If it's not a template, it needs an owner! + return false; + } + + USimpleConstructionScript* SCS = GetSCS(); + if (SCS) + { + // We're dealing with a Blueprint related component. + AActor* PreviewActor = GetPreviewActor(); + AActor* OwningActor = GetOwner(); + if (!OwningActor) + return false; + if (OwningActor != PreviewActor) + { + return false; + } + } + + } + + if (IsPreview() && false) + { + USimpleConstructionScript* SCS = GetSCS(); + if (!SCS) + return false; // Preview components should have an SCS. + + // We want to specifically detect whether an editor component is still being previewed. We do this + // by checking whether the owning actor is still the active editor actor in the SCS. + AActor* PreviewActor = GetPreviewActor(); + if (!PreviewActor) + { + return false; + } + + // Ensure this component still belongs the to the current preview actor. + if (PreviewActor != GetOwner()) + { + return false; + } + + /* + AActor* EditorActor = SCS->GetComponentEditorActorInstance(); + if (GetOwner() != EditorActor) + { + return false; + } + */ + } +#endif + + return true; +} + +bool +UHoudiniAssetBlueprintComponent::IsInputTypeSupported(EHoudiniInputType InType) const +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + case EHoudiniInputType::Curve: + return true; + break; + default: + break; + } + return false; +} + +bool +UHoudiniAssetBlueprintComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const +{ + switch (InType) + { + case EHoudiniOutputType::Mesh: + case EHoudiniOutputType::Instancer: + return true; + break; + default: + break; + } + return false; +} + +bool +UHoudiniAssetBlueprintComponent::IsProxyStaticMeshEnabled() const +{ + // TODO: Investigate adding support for proxy meshes in BP + // Disabled for now + return false; +} + +//void +//UHoudiniAssetBlueprintComponent::BroadcastPreAssetCook() +//{ +// // ------------------------------------------------ +// // NOTE: This code will run on TEMPLATE components +// // ------------------------------------------------ +// +// // The HoudiniAsset is about to be recooked. This flag will indicate to +// // the transient components that output processing needs to be baked +// // back to the BP definition. +// bOutputsRequireUpdate = true; +// +// Super::BroadcastPreAssetCook(); +//} + +void +UHoudiniAssetBlueprintComponent::OnPrePreCook() +{ + check(IsPreview()); + + Super::OnPrePreCook(); + + // We need to allow deleting houdini nodes + SetCanDeleteHoudiniNodes(true); +} + +void +UHoudiniAssetBlueprintComponent::OnPostPreCook() +{ + check(IsPreview()); + + Super::OnPostPreCook(); + + // Ensure the houdini nodes can be deleted during the translation process. + SetCanDeleteHoudiniNodes(false); +} + +void +UHoudiniAssetBlueprintComponent::OnPreOutputProcessing() +{ + check(IsPreview()); + + Super::OnPreOutputProcessing(); + + // Ensure the houdini nodes can be deleted during the translation process. + SetCanDeleteHoudiniNodes(true); +} + +void +UHoudiniAssetBlueprintComponent::OnPostOutputProcessing() +{ + Super::OnPostOutputProcessing(); + + // ------------------------------------------------ + // NOTE: + // In Blueprint editor mode, this code will run on PREVIEW components + // In Map editor mode, this code will run on component instances. + // ------------------------------------------------ + if (IsPreview()) + { + // Ensure all the inputs / outputs belonging to the + // preview actor won't be deleted by PreviewActor destruction. + SetCanDeleteHoudiniNodes(false); + +#if WITH_EDITOR + CopyStateToTemplateComponent(); +#endif + + } + bUpdatedFromTemplate = false; +} + +void UHoudiniAssetBlueprintComponent::OnPrePreInstantiation() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnPrePreInstantiation] Component: %s"), *(GetPathName())); + + check(IsPreview()); + + if (bUpdatedFromTemplate) + return; + + check(CachedTemplateComponent.IsValid()); + + // This HDA is about to be cooked but not through template parameter changes. It is likely that an input changed directly in the preview world. + // We need to flag our inputs and parameters appropriately in order to preserve their values. + + // We need to mark all our parameters as changed/not triggering update + for (auto CurrentParam : Parameters) + { + if (CurrentParam) + { + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(false); + } + } + + // We need to mark all our inputs as changed/not triggering update + for (auto CurrentInput : Inputs) + { + if (CurrentInput) + { + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(false); + CurrentInput->MarkDataUploadNeeded(true); + } + } +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniRegisterCompleted() +{ + if (IsTemplate()) + { + // TODO: Do we need to set any status flags or clear stuff to ensure + // the BP HAC will cook properly when the BP is opened again... + + // If the template is being registered, we need to invalidate the AssetId here since it likely + // contains a stale asset id from its last cook. + AssetId = -1; + // Template component's have very limited update requirements / capabilities. + // Mostly just cache parameters and cook state. + SetAssetState(EHoudiniAssetState::ProcessTemplate); + } + + Super::NotifyHoudiniRegisterCompleted(); +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniPreUnregister() +{ + if (IsTemplate()) + { + // Templates can delete Houdini nodes when they get deregistered. + SetCanDeleteHoudiniNodes(true); + } + Super::NotifyHoudiniPreUnregister(); +} + +void +UHoudiniAssetBlueprintComponent::NotifyHoudiniPostUnregister() +{ + InvalidateData(); + + Super::NotifyHoudiniPostUnregister(); + + if (IsTemplate()) + { + SetCanDeleteHoudiniNodes(false); + } +} + +#if WITH_EDITOR +void +UHoudiniAssetBlueprintComponent::OnComponentCreated() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnComponentCreated] Component: %s"), *(GetPathName())); + + Super::OnComponentCreated(); + bUpdatedFromTemplate = false; + + CachePreviewState(); + + if (IsPreview()) + { + // Don't set an initial AssetState here. Preview components should only cook when template's + // Houdini Asset or HDA parameters have changed. + + // Clear these to ensure that we're not sharing references with the component template (otherwise + // the shared objects will get deleted when the component instance gets destroyed). + // These objects will be properly duplicated when copying state from the component template. + Inputs.Empty(); + Parameters.Empty(); + } + + // Wait until InitializeComponent() for blueprint construction to complete before we start caching blueprint data. + +} +#endif + + +void +UHoudiniAssetBlueprintComponent::OnRegister() +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::OnRegister] Component: %s"), *(GetPathName())); + + Super::OnRegister(); + + // We run our Blueprint caching functions here since this the last hook that we have before + // entering HoudiniEngineTick(); + + CacheBlueprintData(); + CachePreviewState(); + + if (IsPreview()) + { + check(CachedTemplateComponent.Get()); + // Ensure that the component template has been registered since it needs to be processed for parameter updates by the HE manager. + if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(CachedTemplateComponent.Get())) + { + // The template component has not been registered yet, which means that we're probably busy opening a Blueprint editor and this + // preview component will need to be updated. + + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(CachedTemplateComponent.Get(), true); + CachedTemplateComponent->SetCanDeleteHoudiniNodes(false); + // Since we're likely opening a fresh blueprint editor, we'll need to instantiate the HDA. + bHasRegisteredComponentTemplate = true; + } + } + + if (IsTemplate()) + { + // We're initializing the asset id for HAC template here since it doesn't get unloaded + // from memory, for example, between Blueprint Editor open/close so we need to make sure + // that the AssetId has indeed been reset between registrations. + AssetId = -1; + } + + //TickInitialization(); +} + +void +UHoudiniAssetBlueprintComponent::BeginDestroy() +{ + Super::BeginDestroy(); +} + +void +UHoudiniAssetBlueprintComponent::DestroyComponent(bool bPromoteChildren) +{ + //FDebug::DumpStackTraceToLog(); + if (CachedTemplateComponent.IsValid() && TemplatePropertiesChangedHandle.IsValid()) + { + CachedTemplateComponent->Modify(); + CachedTemplateComponent->OnParametersChangedEvent.Remove(TemplatePropertiesChangedHandle); + } + Super::DestroyComponent(bPromoteChildren); +} + +void +UHoudiniAssetBlueprintComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); +} + +TStructOnScope +UHoudiniAssetBlueprintComponent::GetComponentInstanceData() const +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::GetComponentInstanceData] Component: %s"), *(GetPathName())); + + TStructOnScope ComponentInstanceData = MakeStructOnScope(this); + FHoudiniAssetBlueprintInstanceData* InstanceData = ComponentInstanceData.Cast(); + + InstanceData->AssetId = AssetId; + InstanceData->AssetState = AssetState; + InstanceData->SubAssetIndex = SubAssetIndex; + InstanceData->ComponentGUID = ComponentGUID; + InstanceData->HapiGUID = HapiGUID; + InstanceData->HoudiniAsset = HoudiniAsset; + InstanceData->SourceName = GetPathName(); + InstanceData->AssetCookCount = AssetCookCount; + InstanceData->bHasBeenLoaded = bHasBeenLoaded; + InstanceData->bHasBeenDuplicated = bHasBeenDuplicated; + InstanceData->bPendingDelete = bPendingDelete; + InstanceData->bRecookRequested = bRecookRequested; + InstanceData->bEnableCooking = bEnableCooking; + InstanceData->bForceNeedUpdate = bForceNeedUpdate; + InstanceData->bLastCookSuccess = bLastCookSuccess; + InstanceData->bRegisteredComponentTemplate = bHasRegisteredComponentTemplate; + + InstanceData->Inputs.Empty(); + + for (UHoudiniInput* Input : Inputs) + { + if (!Input) + continue; + UHoudiniInput* TransientInput = Input->DuplicateAndCopyState(GetTransientPackage(), false); + InstanceData->Inputs.Add(TransientInput); + } + + // Cache the current outputs + InstanceData->Outputs.Empty(); + int OutputIndex = 0; + for(UHoudiniOutput* Output : Outputs) + { + if (!Output) + continue; + + TMap OutputObjects = Output->GetOutputObjects(); + for (auto& Entry : OutputObjects) + { + FHoudiniAssetBlueprintOutput OutputObjectData; + OutputObjectData.OutputIndex = OutputIndex; + OutputObjectData.OutputObject = Entry.Value; + InstanceData->Outputs.Add(Entry.Key, OutputObjectData); + } + + ++OutputIndex; + } + + return ComponentInstanceData; + +} + +void +UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData(FHoudiniAssetBlueprintInstanceData* InstanceData, const bool bPostUCS) +{ + HOUDINI_BP_MESSAGE(TEXT("[UHoudiniAssetBlueprintComponent::ApplyComponentInstanceData] Component: %s"), *(GetPathName())); + check(InstanceData); + + if (!bPostUCS) + { + // Initialize the component before the User Construction Script runs + USimpleConstructionScript* SCS = GetSCS(); + check(SCS); + + TArray StaleInputs(Inputs); + + // We need to update references contain in inputs / outputs to point to new reconstructed components. + const int32 NumInputs = InstanceData->Inputs.Num(); + Inputs.SetNum(NumInputs); + for (int i = 0; i < NumInputs; ++i) + { + UHoudiniInput* FromInput = InstanceData->Inputs[i]; + UHoudiniInput* ToInput = Inputs[i]; + + if (ToInput) + { + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + if (!bIsValid) + { + ToInput = nullptr; + } + } + + if (ToInput) + { + // Reuse input + StaleInputs.Remove(ToInput); + ToInput->CopyStateFrom(FromInput, true, false); + } + else + { + // Create new input + ToInput = FromInput->DuplicateAndCopyState(this, false); + } + +#if WITH_EDITOR + // We can't create missing SCS nodes here since we're likely already in the middle of a + // Blueprint reconstruction. We'll have to recreate missing SCS nodes next time the + // component state if copied to the template. + UpdateInputObjectComponentReferences(SCS, FromInput, ToInput, true, false); +#endif + + Inputs[i] = ToInput; + } + + // We need to update FHoudiniOutputObject SceneComponent references to + // the newly created components. Since we cached a map of Output Object IDs to + // SCSNodes (during CopyStateToTemplateComponent), we can the SCSNode that corresponds to this output objects and find + // the SceneComponent that matches the SCSNode's variable name. + // It is important to note that it is safe to do it this way since we're in the pre-UCS + // phase so that current components should match the SCS graph exactly (no user construction script + // interference here yet). + + for (auto& Entry : InstanceData->Outputs) + { + FHoudiniOutputObjectIdentifier& ObjectId = Entry.Key; + FHoudiniAssetBlueprintOutput& OutputData = Entry.Value; + + // NOTE: Output objects are going to be empty here since they dissapear during actor reconstruction. + // We'll need to repopulate from the instance data. + + check(Outputs.IsValidIndex(OutputData.OutputIndex)); + UHoudiniOutput* Output = Outputs[OutputData.OutputIndex]; + check(Output); + TMap& OutputObjects = Output->GetOutputObjects(); + FHoudiniOutputObject NewObject = OutputData.OutputObject; + + if (OutputData.OutputObject.OutputComponent) + { + // Update the output component reference. + check(CachedOutputNodes.Contains(ObjectId)) + const FGuid VariableGuid = CachedOutputNodes.FindChecked(ObjectId); + USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(VariableGuid); + + if (SCSNode) + { + // Find the component that corresponds to the SCS node. + USceneComponent* SceneComponent = FindActorComponentByName(GetOwner(), SCSNode->GetVariableName()); + NewObject.OutputComponent = SceneComponent; + } + else + { + NewObject.OutputComponent = nullptr; + } + } + + OutputObjects.Add(ObjectId, NewObject); + } + + if (CachedTemplateComponent.IsValid()) + { +#if WITH_EDITOR + CopyStateFromTemplateComponent( CachedTemplateComponent.Get(), false, false, true); +#endif + } + + AssetId = InstanceData->AssetId; + SubAssetIndex = InstanceData->SubAssetIndex; + ComponentGUID = InstanceData->ComponentGUID; + HapiGUID = InstanceData->HapiGUID; + + // Apply the previous HoudiniAsset to the component + // so that we can compare it against the template during CopyStateFromTemplate() calls to see whether it changed. + HoudiniAsset = InstanceData->HoudiniAsset; + + AssetCookCount = InstanceData->AssetCookCount; + bHasBeenLoaded = InstanceData->bHasBeenLoaded; + bHasBeenDuplicated = InstanceData->bHasBeenDuplicated; + bPendingDelete = InstanceData->bPendingDelete; + bRecookRequested = InstanceData->bRecookRequested; + bEnableCooking = InstanceData->bEnableCooking; + bForceNeedUpdate = InstanceData->bForceNeedUpdate; + bLastCookSuccess = InstanceData->bLastCookSuccess; + bHasRegisteredComponentTemplate = InstanceData->bRegisteredComponentTemplate; + + AssetState = InstanceData->AssetState; + + SetCanDeleteHoudiniNodes(false); + + } // if (!bPostUCS) + /* + else + { + // PostUCS + + } + */ +} + + +void +UHoudiniAssetBlueprintComponent::HoudiniEngineTick() +{ + if (!IsFullyLoaded()) + { + USimpleConstructionScript* SCS = GetSCS(); + if (SCS == nullptr) + { + OnFullyLoaded(); + } + else if (IsPreview()) + { + AActor* OwningActor = GetOwner(); + check(OwningActor); + + // If this is a *preview component*, it is important to wait for the template component to be fully loaded + // since it needs to be initialized so that the component instance can copy initial values from the template. + check(CachedTemplateComponent.Get()); + + if (CachedTemplateComponent->IsFullyLoaded()) + { +#if WITH_EDITOR + if(SCS->IsConstructingEditorComponents()) + { + // We're stuck in an editor blueprint construction / preview actor update. Wait some more. + } + else + { + OnFullyLoaded(); + } +#else + OnFullyLoaded(); +#endif + } + } + else + { + // Anything else can go onto being fully loaded at this point. + OnFullyLoaded(); + } + } +} + +void +UHoudiniAssetBlueprintComponent::OnFullyLoaded() +{ + Super::OnFullyLoaded(); + + // Check whether this component is inside a Blueprint editor. If this object lives outside the blueprint editor (in , then we need to ensure that this + // component won't be influencing the Blueprint asset. + + if (!IsTemplate()) + { +#if WITH_EDITOR + AActor* PreviewActor = GetPreviewActor(); +#else + AActor* PreviewActor = nullptr; +#endif + AActor* OwningActor = GetOwner(); + + if (!PreviewActor) + { + bIsInBlueprintEditor = false; + AssetState = EHoudiniAssetState::None; + return; + } + + if (OwningActor && PreviewActor != OwningActor) + { + bIsInBlueprintEditor = false; + AssetState = EHoudiniAssetState::None; + return; + } + } + + bIsInBlueprintEditor = true; + + CachePreviewState(); + CacheBlueprintData(); + + /* + for (UHoudiniOutput* Output : Outputs) + { + if (!Output) + continue; + } + */ + + if (IsTemplate()) + { + AssetId = -1; + AssetState = EHoudiniAssetState::ProcessTemplate; + } + + if (IsPreview()) + { + check(CachedTemplateComponent.Get()); + + // If this is a preview actor, sync initial settings from the component template +#if WITH_EDITOR + CopyStateFromTemplateComponent(CachedTemplateComponent.Get(), false, false, true); +#endif + + TemplatePropertiesChangedHandle = CachedTemplateComponent->OnParametersChangedEvent.AddUObject(this, &UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler); + if (bHoudiniAssetChanged) + { + + // The HoudiniAsset has changed, so we need to force the PreviewInstance to re-instantiate + AssetState = EHoudiniAssetState::NeedInstantiation; + bForceNeedUpdate = true; + bHoudiniAssetChanged = false; + // TODO: Make this better? + CachedTemplateComponent->bHoudiniAssetChanged = false; + } + + if (bHasRegisteredComponentTemplate) + { + // We have a newly registered component template. One of two things happened to cause this: + // 1. A new HoudiniAssetBlueprintComponent was created and registered. + // 2. The Blueprint Editor was closed / opened. + // The problem that arises in the #2 is that the template component was never fully unloaded + // from memory (it was deregistered but not destroyed). After deregistration we had the + // opportunity to invalidate asset/node ids but now that it has reregistered (without going + // through the normal initialization process) we will have to force a call to MarkAsNeedInstantiation + // during the next OnTemplateParametersChangedHandler() invocation. + bHasBeenLoaded = true; + } + } +} + +void +UHoudiniAssetBlueprintComponent::OnTemplateParametersChanged() +{ + OnParametersChangedEvent.Broadcast(this); +} + +void UHoudiniAssetBlueprintComponent::OnBlueprintStructureModified() +{ + check(IsTemplate()); + bBlueprintStructureModified = false; + +#if WITH_EDITOR + if (IsTemplate()) + { + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(this); + } + else + { + check(CachedTemplateComponent.IsValid()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(CachedTemplateComponent.Get()); + } +#endif +} + +void UHoudiniAssetBlueprintComponent::OnBlueprintModified() +{ + check(IsTemplate()); + bBlueprintModified = false; +#if WITH_EDITOR + FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(this); +#endif +} + +void +UHoudiniAssetBlueprintComponent::OnHoudiniAssetChanged() +{ + if (IsTemplate()) + { + // Invalidate data associated with this component since we're about to change and reinstantiate the Houdini Asset. + SetCanDeleteHoudiniNodes(true); + InvalidateData(); + SetCanDeleteHoudiniNodes(false); + Parameters.Empty(); + Inputs.Empty(); + } + + Super::OnHoudiniAssetChanged(); + + // Set on template components, then copied to preview components, and + // then used (and reset) during OnFullyLoaded. + bHoudiniAssetChanged = true; +} + +void +UHoudiniAssetBlueprintComponent::RegisterHoudiniComponent(UHoudiniAssetComponent *InComponent) +{ + // We only want to register this component if it is the preview actor for the Blueprint editor. +#if WITH_EDITOR + AActor* PreviewActor = GetPreviewActor(); +#else + AActor* PreviewActor = nullptr; +#endif + AActor* OwningActor = GetOwner(); + if (!OwningActor) + return; + + if (PreviewActor != OwningActor) + return; + + Super::RegisterHoudiniComponent(InComponent); +} + + + + +//bool UHoudiniAssetBlueprintComponent::TickInitialization() +//{ +// return true; +// +// if (IsFullyLoaded()) +// return true; +// +// bool bHasFinishedLoading = Super::TickInitialization(); +// +// if (!bHasFinishedLoading) +// return false; +// +// if (!IsTemplate()) +// { +// +// if (CachedTemplateComponent.Get()) +// { +// // Now that that SCS has finished constructing editor components, we can continue. +// // Copy the current state from the template component, in case there is something that can be processed. +// CopyStateFromTemplateComponent(CachedTemplateComponent.Get()); +// } +// +// AssetStateResult = EHoudiniAssetStateResult::None; +// AssetState = EHoudiniAssetState::None; +// +// bForceNeedUpdate = true; +// AssetState = EHoudiniAssetState::PostCook; +// } +// +// return true; +//} + +template +inline void +UHoudiniAssetBlueprintComponent::SetTypedValueAt(const FString& Name, ValueT& Value, int Index) +{ + ParamT* Parameter = Cast(FindParameterByName(Name)); + if (!Parameter) + return; + + Parameter->SetValueAt(Value, Index); +} + +bool +UHoudiniAssetBlueprintComponent::HasParameter(FString Name) +{ + return FindParameterByName(Name) != nullptr; +} + +void +UHoudiniAssetBlueprintComponent::SetFloatParameter(FString Name, float Value, int Index) +{ + SetTypedValueAt(Name, Value, Index); +} + +void +UHoudiniAssetBlueprintComponent::SetToggleValueAt(FString Name, bool Value, int Index) +{ + UHoudiniParameterToggle* Parameter = Cast(FindParameterByName(Name)); + if (!Parameter) + return; + + Parameter->SetValueAt(Value, Index); +} + +//void UHoudiniAssetBlueprintComponent::OnPostCookHandler(UHoudiniAssetComponent* InComponent) +//{ +// +// // Before this component handles any translation, we need to make sure that it still belongs to the editor actor. +// // When a blueprint gets recompiled, the editor actor gets replaced with a new one but the old actor has not yet +// // been ftroyed / garbage collected so its components still receive cook events from the template. +// +// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponent); +// if (!IsValid(ComponentTemplate)) +// return; +// +// CopyStateFromTemplateComponent(ComponentTemplate); +// bForceNeedUpdate = true; +//} + +void +UHoudiniAssetBlueprintComponent::OnTemplateParametersChangedHandler(UHoudiniAssetComponent* InComponentTemplate) +{ + if (!(AssetState == EHoudiniAssetState::None || AssetState == EHoudiniAssetState::NeedInstantiation || AssetState == EHoudiniAssetState::NeedRebuild)) + // Don't process parameter changes since we're already cooking -- it is going to break things badly if we do. + return; + + if (!IsValidComponent()) + return; + + UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); + if (!ComponentTemplate) + return; + + // The component instance needs to copy values from the template. + bool bBlueprintStructureChanged = false; +#if WITH_EDITOR + CopyDetailsFromComponent(ComponentTemplate, + false, + false, + true, + false, + true, + bBlueprintStructureChanged, + RF_Public, + RF_ClassDefaultObject|RF_ArchetypeObject); +#endif + + SetCanDeleteHoudiniNodes(false); + + if (bHasRegisteredComponentTemplate) + { + // NOTE: It is very important to call this *after* CopyDetailsFromComponent(), since CopyDetailsFromComponent + // will clobber the inputs and parameter states on this component. + + // If we already have a valid asset id, keep it. + if (AssetId >= 0) + { + MarkAsNeedCook(); + } + else + { + MarkAsNeedInstantiation(); + } + + bHasRegisteredComponentTemplate = false; + bFullyLoaded = true; // MarkAsNeedInstantiation sets this to false. Force to true. + // While MarkAsNeedInstantiation() sets ParametersChanged to true, it does not + // set the 'NeedToTriggerUpdate' flag (both of which needs to be true in order + // to trigger an HDA update) so we are going to force NeedUpdate() to return true + // in order to get an initial cook. + bForceNeedUpdate = true; + } + + bUpdatedFromTemplate = true; +} + +void +UHoudiniAssetBlueprintComponent::InvalidateData() +{ + if (IsTemplate()) + { + // Ensure transient properties are invalidated/released for parameters, inputs and outputs as if the + // the object was undergoing destruction since the template component will likely be reregistered + // without being destroyed. + for(UHoudiniParameter* Param : Parameters) + { + Param->InvalidateData(); + } + + for(UHoudiniInput* Input : Inputs) + { + Input->InvalidateData(); + } + + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + AssetId = -1; + } +} + +//void UHoudiniAssetBlueprintComponent::OnTemplateHoudiniAssetChangedHandler(UHoudiniAssetComponent* InComponentTemplate) +//{ +// +// UHoudiniAssetBlueprintComponent* ComponentTemplate = Cast(InComponentTemplate); +// if (!ComponentTemplate) +// return; +// check(IsPreview()); +// +// // The Houdini Asset was changed on the template. We need to recook. +// AssetState = EHoudiniAssetState::NeedInstantiation; +// +//} + +USceneComponent* +UHoudiniAssetBlueprintComponent::FindOwnerComponentByName(FName ComponentName) const +{ + AActor* Owner = GetOwner(); + if (!Owner) + return nullptr; + + return FindActorComponentByName(Owner, ComponentName); +} + +USceneComponent* +UHoudiniAssetBlueprintComponent::FindActorComponentByName(AActor* InActor, FName ComponentName) const +{ + const TSet& Components = InActor->GetComponents(); + + for (UActorComponent* Component : Components) + { + USceneComponent* SceneComponent = Cast(Component); + if (!IsValid(SceneComponent)) + continue; + if (FName(SceneComponent->GetName()) == ComponentName) + return SceneComponent; + } + + return nullptr; +} + +bool UHoudiniAssetBlueprintComponent::GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid) +{ + FGuid* SCSGuid = CachedInputNodes.Find(InputGuid); + if (!SCSGuid) + return false; + OutSCSGuid = *SCSGuid; + return true; +} + +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const +{ + const TArray& AllNodes = SCS->GetAllNodes(); + + for (USCS_Node* Node : AllNodes) + { + if (!Node) + continue; + + if (Node->ComponentTemplate == InComponent) + return Node; + } + + return nullptr; +} + +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForTemplateComponentInClassHierarchy( + const UActorComponent* InComponent) const +{ + UObject* Outer = this->GetOuter(); + if (!IsValid(Outer)) + return nullptr; + UBlueprintGeneratedClass* MainBPGC; + if (IsTemplate()) + { + MainBPGC = Cast(Outer); + } + else + { + AActor* OwningActor = GetOwner(); + MainBPGC = Cast(OwningActor->GetClass()); + } + + check(MainBPGC); + TArray BPGCStack; + UBlueprintGeneratedClass::GetGeneratedClassesHierarchy(MainBPGC, BPGCStack); + for(const UBlueprintGeneratedClass* BPGC : BPGCStack) + { + USimpleConstructionScript* SCS = BPGC->SimpleConstructionScript; + if (!SCS) + return nullptr; + USCS_Node* SCSNode = FindSCSNodeForTemplateComponent(SCS, InComponent); + SCSNode = SCS->FindSCSNode(InComponent->GetFName()); + if (SCSNode) + return SCSNode; + } + + return nullptr; +} + +#if WITH_EDITOR +USCS_Node* +UHoudiniAssetBlueprintComponent::FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const +{ + const TArray& AllNodes = SCS->GetAllNodes(); + + if (!InComponent) + return nullptr; + + for (USCS_Node* Node : AllNodes) + { + if (!Node) + continue; + if (Node->EditorComponentInstance.Get() == InComponent) + return Node; + } + + return nullptr; +} +#endif + +UActorComponent* +UHoudiniAssetBlueprintComponent::FindComponentInstanceInActor(const AActor* InActor, + USCS_Node* SCSNode) const +{ + UActorComponent* ComponentTemplate = SCSNode->ComponentTemplate; + + UActorComponent* ComponentInstance = NULL; + if (InActor != NULL) + { + if (SCSNode != NULL) + { + FName VariableName = SCSNode->GetVariableName(); + if (VariableName != NAME_None) + { + UWorld* World = InActor->GetWorld(); + FObjectPropertyBase* Property = FindFProperty(InActor->GetClass(), VariableName); + if (Property != NULL) + { + // Return the component instance that's stored in the property with the given variable name + ComponentInstance = Cast(Property->GetObjectPropertyValue_InContainer(InActor)); + } + else if (World != nullptr && World->WorldType == EWorldType::EditorPreview) + { + // If this is the preview actor, return the cached component instance that's being used for the pmnaview actor prior to recompiling the Blueprint +#if WITH_EDITOR + ComponentInstance = SCSNode->EditorComponentInstance.Get(); +#endif + } + } + } + else if (ComponentTemplate != NULL) + { +#if WITH_EDITOR + TInlineComponentArray Components; + InActor->GetComponents(Components); + ComponentInstance = FComponentEditorUtils::FindMatchingComponent(ComponentTemplate, Components); +#endif + } + } + + return ComponentInstance; +} + + +//void UHoudiniAssetBlueprintComponent::OnOutputProcessingCompletedHandler(UHoudiniAssetComponent* InComponent) +//{ +// +// UHoudiniAssetBlueprintComponent* TemplateComponent = Cast(InComponent); +// if (!IsValid(TemplateComponent)) +// return; +// +// CopyStateFromComponent(TemplateComponent); +// bForceNeedUpdate = true; +//} + +//#if WITH_EDITOR +//void UHoudiniAssetBlueprintComponent::ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason) +//{ +// +// if (CachedBlueprint.Get()) +// { +// } +// +// if (Asset) +// { +// +// } +// +//} +//#endif + +void +UHoudiniAssetBlueprintComponent::CachePreviewState() +{ + bCachedIsPreview = false; + +#if WITH_EDITOR + AActor* ComponentOwner = GetOwner(); + if (!IsValid(ComponentOwner)) + return; + + USimpleConstructionScript* SCS = GetSCS(); + if (SCS == nullptr) + return; + + // Get the preview actor directly from the BlueprintEditor. + FBlueprintEditor* BlueprintEditor = FHoudiniEngineRuntimeUtils::GetBlueprintEditor(this); + if (BlueprintEditor) + { + AActor* PreviewActor = BlueprintEditor->GetPreviewActor(); + if (PreviewActor == ComponentOwner) + { + bCachedIsPreview = true; + return; + } + } +#endif +} + +void +UHoudiniAssetBlueprintComponent::CacheBlueprintData() +{ + CachedBlueprint = nullptr; + CachedActorCDO = nullptr; + CachedTemplateComponent = IsTemplate() ? this : nullptr; + +#if WITH_EDITOR + CachedAssetEditorSubsystem = GEditor->GetEditorSubsystem(); +#endif + + UBlueprintGeneratedClass* BPGC = Cast(GetOuter()); + if (BPGC) + { + // Dealing with a component template + CachedBlueprint = Cast(BPGC->ClassGeneratedBy); + } + else + { + // Dealing with a component instance. + CachedBlueprint = Cast(GetOuter()->GetClass()->ClassGeneratedBy); + } + + if (CreationMethod != EComponentCreationMethod::SimpleConstructionScript) + return; + + AActor* ComponentOwner = this->GetOwner(); + if (!IsValid(ComponentOwner)) + return; + UClass* OwnerClass = ComponentOwner->GetClass(); + if (!IsValid(OwnerClass)) + return; + + if (!IsTemplate()) + { + // NOTE: The following code allows us to find the component template from an instance. + CachedActorCDO = Cast< AActor >(CachedBlueprint->GeneratedClass->GetDefaultObject()); + if (!CachedActorCDO.IsValid() || (CachedActorCDO.Get() == ComponentOwner)) + return; +#if WITH_EDITOR + UActorComponent* TargetComponent = EditorUtilities::FindMatchingComponentInstance(this, CachedActorCDO.Get()); + CachedTemplateComponent = Cast(TargetComponent); +#endif + } + +} + +USimpleConstructionScript* +UHoudiniAssetBlueprintComponent::GetSCS() const +{ + if (!CachedBlueprint.Get()) + return nullptr; + + return CachedBlueprint->SimpleConstructionScript; +} + +//------------------------------------------------------------------------------------------------ +// FHoudiniAssetBlueprintInstanceData +//------------------------------------------------------------------------------------------------ + +FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData() + : HoudiniAsset(nullptr) + , AssetId(-1) + , AssetState(EHoudiniAssetState::None) + , SubAssetIndex(-1) + , AssetCookCount(0) + , bHasBeenLoaded(false) + , bHasBeenDuplicated(false) + , bPendingDelete(false) + , bRecookRequested(false) + , bRebuildRequested(false) + , bEnableCooking(true) + , bForceNeedUpdate(false) + , bLastCookSuccess(false) + , ComponentGUID(FGuid()) + , HapiGUID(FGuid()) + , bRegisteredComponentTemplate(false) + , SourceName() +{ + +} + +FHoudiniAssetBlueprintInstanceData::FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent) + : FActorComponentInstanceData(SourceComponent) +{ + +} + +void +FHoudiniAssetBlueprintInstanceData::AddReferencedObjects(FReferenceCollector & Collector) +{ + Super::AddReferencedObjects(Collector); + // TODO: Do we need to add references to output objects here? + // Any other references? + // What are the implications? +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h index 3c23886e5..d02640bd5 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.h @@ -1,351 +1,351 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Delegates/IDelegateInstance.h" -#include "Engine/Blueprint.h" -#include "HoudiniAssetComponent.h" - -#if WITH_EDITOR - #include "Subsystems/AssetEditorSubsystem.h" -#endif - -#include "HoudiniAssetBlueprintComponent.generated.h" - -class USCS_Node; - -UCLASS(NotBlueprintType, Experimental, meta=(BlueprintSpawnableComponent, DisplayName="Houdini Asset")) -class HOUDINIENGINERUNTIME_API UHoudiniAssetBlueprintComponent : public UHoudiniAssetComponent -{ - GENERATED_BODY() - -public: - UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer); - -#if WITH_EDITOR - // Sync certain variables of this HoudiniAssetComponent to the blueprint generated class. - // This is typically used when the Blueprint definition is being edited and the HAC cook - // took place in a transient HoudiniAssetComponent. Certain properties needs to be copied - // from the transient component back to the Blueprint generated class in order to be retained - // as part of the Client MeetingBlueprint definition. - - void CopyStateToTemplateComponent(); - - void CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties); - - void CopyDetailsFromComponent( - UHoudiniAssetBlueprintComponent* FromComponent, - const bool bCreateSCSNodes, - const bool bClearChangedToInputs, - const bool bClearChangedFromInputs, - const bool bInCanDeleteHoudiniNodes, - const bool bCopyInputObjectComponentProperties, - bool &bOutBlueprintStructureChanged, - EObjectFlags SetFlags=RF_NoFlags, - EObjectFlags ClearFlags=RF_NoFlags); - - // Update references on ToInput by looking up component references on FromInput in the SCS graph, on locating the correct component for ToInput. - void UpdateInputObjectComponentReferences( - USimpleConstructionScript* SCS, - UHoudiniInput* FromInput, - UHoudiniInput* ToInput, - const bool bCopyInputObjectProperties, - const bool bCreateMissingSCSNodes=false, - USCS_Node* ParentSCSNode=nullptr, - bool* bOutSCSNodeCreated=nullptr); - - virtual bool HasOpenEditor() const override; - IAssetEditorInstance* FindEditorInstance() const; - AActor* GetPreviewActor() const; -#endif - - virtual UHoudiniAssetComponent* GetCachedTemplate() const override; - - //------------------------------------------------------------------------------------------------ - // Supported Features - //------------------------------------------------------------------------------------------------ - - // Some features may be unavaible depending on the context in which the Houdini Asset Component - // has been instantiated. - - virtual bool CanDeleteHoudiniNodes() const override; - - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - - virtual bool IsValidComponent() const override; - - virtual bool IsInputTypeSupported(EHoudiniInputType InType) const override; - virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const override; - - virtual bool IsProxyStaticMeshEnabled() const override; - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - //virtual void BroadcastPreAssetCook() override; - virtual void OnPrePreCook() override; - virtual void OnPostPreCook() override; - virtual void OnPreOutputProcessing() override; - virtual void OnPostOutputProcessing() override; - virtual void OnPrePreInstantiation() override; - virtual void NotifyHoudiniRegisterCompleted() override; - virtual void NotifyHoudiniPreUnregister() override; - virtual void NotifyHoudiniPostUnregister() override; - - //------------------------------------------------------------------------------------------------ - // UActorComponent overrides - //------------------------------------------------------------------------------------------------ -#if WITH_EDITOR - virtual void OnComponentCreated() override; -#endif - - virtual void OnRegister() override; - - virtual void BeginDestroy() override; - virtual void DestroyComponent(bool bPromoteChildren = false) override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - // Refer USplineComponent for a decent reference on how to use component instance data. - virtual TStructOnScope GetComponentInstanceData() const override; - void ApplyComponentInstanceData(struct FHoudiniAssetBlueprintInstanceData* ComponentInstanceData, const bool bPostUCS); - - //------------------------------------------------------------------------------------------------ - // UHoudiniAssetComponent overrides - //------------------------------------------------------------------------------------------------ - - FHoudiniAssetComponentEvent OnParametersChangedEvent; - FHoudiniAssetComponentEvent OnHoudiniAssetChangedEvent; - - virtual void HoudiniEngineTick() override; - virtual void OnFullyLoaded() override; - virtual void OnTemplateParametersChanged() override; - virtual void OnHoudiniAssetChanged() override; - virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) override; - - virtual void OnBlueprintStructureModified() override; - virtual void OnBlueprintModified() override; - - - //------------------------------------------------------------------------------------------------ - // Blueprint functions - //------------------------------------------------------------------------------------------------ - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - bool HasParameter(FString Name); - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - void SetFloatParameter(FString Name, float Value, int Index=0); - - UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") - void SetToggleValueAt(FString Name, bool Value, int Index=0); - - void AddInputObjectMapping(const FGuid& InputGuid, const FGuid& SCSVariableGuid) { CachedInputNodes.Add(InputGuid, SCSVariableGuid); } - bool GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid); - void RemoveInputObjectSCSVariableGuid(const FGuid& InputGuid) { CachedInputNodes.Remove(InputGuid); }; - - USCS_Node* FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; - USCS_Node* FindSCSNodeForTemplateComponentInClassHierarchy(const UActorComponent* InComponent) const; -#if WITH_EDITOR - USCS_Node* FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; -#endif // WITH_EDITOR - UActorComponent* FindComponentInstanceInActor(const AActor* InActor, USCS_Node* SCSNode) const; - -protected: - - template - void SetTypedValueAt(const FString& Name, ValueT& Value, int Index=0); - - void OnTemplateParametersChangedHandler(UHoudiniAssetComponent* ComponentTemplate); - void InvalidateData(); - - USceneComponent* FindOwnerComponentByName(FName ComponentName) const; - USceneComponent* FindActorComponentByName(AActor * InActor, FName ComponentName) const; - - void CachePreviewState(); - void CacheBlueprintData(); - - USimpleConstructionScript* GetSCS() const; - - //// The output translation has finished. - //void OnOutputProcessingCompletedHandler(UHoudiniAssetComponent * InComponent); - -#if WITH_EDITOR - //void ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason); - TWeakObjectPtr CachedAssetEditorSubsystem; -#endif - - TWeakObjectPtr CachedBlueprint; - TWeakObjectPtr CachedActorCDO; - TWeakObjectPtr CachedTemplateComponent; - - /*UPROPERTY(DuplicateTransient) - bool bOutputsRequireUpdate;*/ - UPROPERTY() - bool FauxBPProperty; - - UPROPERTY() - bool bHoudiniAssetChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bUpdatedFromTemplate; - - UPROPERTY() - bool bIsInBlueprintEditor; - - UPROPERTY(Transient, DuplicateTransient) - bool bCanDeleteHoudiniNodes; - - UPROPERTY(Transient, DuplicateTransient) - bool bHasRegisteredComponentTemplate; - - FDelegateHandle TemplatePropertiesChangedHandle; - - // This is used to keep track of which SCS variable names correspond to which - // output objects. - // This seems like it will cause issues in the map. - UPROPERTY() - TMap CachedOutputNodes; - - // This is used to keep track of which (SCS) variable guids correspond to which - // input objects. - UPROPERTY() - TMap CachedInputNodes; -}; - - -///** Used to keep track of output data and mappings during reconstruction */ -USTRUCT() -struct FHoudiniAssetBlueprintOutput -{ - GENERATED_BODY() - - UPROPERTY() - int32 OutputIndex; - - UPROPERTY() - FHoudiniOutputObject OutputObject; - - FHoudiniAssetBlueprintOutput() - : OutputIndex(INDEX_NONE) - { - - } -}; - - -/** Used to store HoudiniAssetComponent data during BP reconstruction */ -USTRUCT() -struct FHoudiniAssetBlueprintInstanceData : public FActorComponentInstanceData -{ - GENERATED_BODY() -public: - FHoudiniAssetBlueprintInstanceData(); - FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent); - - virtual ~FHoudiniAssetBlueprintInstanceData() = default; - - /*virtual bool ContainsData() const override - { - return (HAC != nullptr) || Super::ContainsData(); - }*/ - - virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override - { - Super::ApplyToComponent(Component, CacheApplyPhase); - CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); - } - - virtual void AddReferencedObjects(FReferenceCollector& Collector) override; - - // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. - UPROPERTY() - UHoudiniAsset* HoudiniAsset; - - UPROPERTY() - int32 AssetId; - - UPROPERTY() - EHoudiniAssetState AssetState; - - // Subasset index - UPROPERTY() - uint32 SubAssetIndex; - - UPROPERTY() - uint32 AssetCookCount; - - UPROPERTY() - bool bHasBeenLoaded; - - UPROPERTY() - bool bHasBeenDuplicated; - - UPROPERTY() - bool bPendingDelete; - - UPROPERTY() - bool bRecookRequested; - - UPROPERTY() - bool bRebuildRequested; - - UPROPERTY() - bool bEnableCooking; - - UPROPERTY() - bool bForceNeedUpdate; - - UPROPERTY() - bool bLastCookSuccess; - - /*UPROPERTY(DuplicateTransient) - TSet DownstreamHoudiniAssets;*/ - - UPROPERTY() - FGuid ComponentGUID; - - UPROPERTY() - FGuid HapiGUID; - - UPROPERTY() - bool bRegisteredComponentTemplate; - - // Name of the component from which this - // data was copied. Used for debugging purposes. - UPROPERTY() - FString SourceName; - - UPROPERTY() - TMap Outputs; - - UPROPERTY() - TArray Inputs; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Delegates/IDelegateInstance.h" +#include "Engine/Blueprint.h" +#include "HoudiniAssetComponent.h" + +#if WITH_EDITOR + #include "Subsystems/AssetEditorSubsystem.h" +#endif + +#include "HoudiniAssetBlueprintComponent.generated.h" + +class USCS_Node; + +UCLASS(NotBlueprintType, Experimental, meta=(BlueprintSpawnableComponent, DisplayName="Houdini Asset")) +class HOUDINIENGINERUNTIME_API UHoudiniAssetBlueprintComponent : public UHoudiniAssetComponent +{ + GENERATED_BODY() + +public: + UHoudiniAssetBlueprintComponent(const FObjectInitializer & ObjectInitializer); + +#if WITH_EDITOR + // Sync certain variables of this HoudiniAssetComponent to the blueprint generated class. + // This is typically used when the Blueprint definition is being edited and the HAC cook + // took place in a transient HoudiniAssetComponent. Certain properties needs to be copied + // from the transient component back to the Blueprint generated class in order to be retained + // as part of the Client MeetingBlueprint definition. + + void CopyStateToTemplateComponent(); + + void CopyStateFromTemplateComponent(UHoudiniAssetBlueprintComponent* FromComponent, const bool bClearFromInputs, const bool bClearToInputs, const bool bCopyInputObjectComponentProperties); + + void CopyDetailsFromComponent( + UHoudiniAssetBlueprintComponent* FromComponent, + const bool bCreateSCSNodes, + const bool bClearChangedToInputs, + const bool bClearChangedFromInputs, + const bool bInCanDeleteHoudiniNodes, + const bool bCopyInputObjectComponentProperties, + bool &bOutBlueprintStructureChanged, + EObjectFlags SetFlags=RF_NoFlags, + EObjectFlags ClearFlags=RF_NoFlags); + + // Update references on ToInput by looking up component references on FromInput in the SCS graph, on locating the correct component for ToInput. + void UpdateInputObjectComponentReferences( + USimpleConstructionScript* SCS, + UHoudiniInput* FromInput, + UHoudiniInput* ToInput, + const bool bCopyInputObjectProperties, + const bool bCreateMissingSCSNodes=false, + USCS_Node* ParentSCSNode=nullptr, + bool* bOutSCSNodeCreated=nullptr); + + virtual bool HasOpenEditor() const override; + IAssetEditorInstance* FindEditorInstance() const; + AActor* GetPreviewActor() const; +#endif + + virtual UHoudiniAssetComponent* GetCachedTemplate() const override; + + //------------------------------------------------------------------------------------------------ + // Supported Features + //------------------------------------------------------------------------------------------------ + + // Some features may be unavaible depending on the context in which the Houdini Asset Component + // has been instantiated. + + virtual bool CanDeleteHoudiniNodes() const override; + + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + + virtual bool IsValidComponent() const override; + + virtual bool IsInputTypeSupported(EHoudiniInputType InType) const override; + virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const override; + + virtual bool IsProxyStaticMeshEnabled() const override; + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + //virtual void BroadcastPreAssetCook() override; + virtual void OnPrePreCook() override; + virtual void OnPostPreCook() override; + virtual void OnPreOutputProcessing() override; + virtual void OnPostOutputProcessing() override; + virtual void OnPrePreInstantiation() override; + virtual void NotifyHoudiniRegisterCompleted() override; + virtual void NotifyHoudiniPreUnregister() override; + virtual void NotifyHoudiniPostUnregister() override; + + //------------------------------------------------------------------------------------------------ + // UActorComponent overrides + //------------------------------------------------------------------------------------------------ +#if WITH_EDITOR + virtual void OnComponentCreated() override; +#endif + + virtual void OnRegister() override; + + virtual void BeginDestroy() override; + virtual void DestroyComponent(bool bPromoteChildren = false) override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + // Refer USplineComponent for a decent reference on how to use component instance data. + virtual TStructOnScope GetComponentInstanceData() const override; + void ApplyComponentInstanceData(struct FHoudiniAssetBlueprintInstanceData* ComponentInstanceData, const bool bPostUCS); + + //------------------------------------------------------------------------------------------------ + // UHoudiniAssetComponent overrides + //------------------------------------------------------------------------------------------------ + + FHoudiniAssetComponentEvent OnParametersChangedEvent; + FHoudiniAssetComponentEvent OnHoudiniAssetChangedEvent; + + virtual void HoudiniEngineTick() override; + virtual void OnFullyLoaded() override; + virtual void OnTemplateParametersChanged() override; + virtual void OnHoudiniAssetChanged() override; + virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) override; + + virtual void OnBlueprintStructureModified() override; + virtual void OnBlueprintModified() override; + + + //------------------------------------------------------------------------------------------------ + // Blueprint functions + //------------------------------------------------------------------------------------------------ + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + bool HasParameter(FString Name); + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + void SetFloatParameter(FString Name, float Value, int Index=0); + + UFUNCTION(BlueprintCallable, Category="Houdini Asset Component") + void SetToggleValueAt(FString Name, bool Value, int Index=0); + + void AddInputObjectMapping(const FGuid& InputGuid, const FGuid& SCSVariableGuid) { CachedInputNodes.Add(InputGuid, SCSVariableGuid); } + bool GetInputObjectSCSVariableGuid(const FGuid& InputGuid, FGuid& OutSCSGuid); + void RemoveInputObjectSCSVariableGuid(const FGuid& InputGuid) { CachedInputNodes.Remove(InputGuid); }; + + USCS_Node* FindSCSNodeForTemplateComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; + USCS_Node* FindSCSNodeForTemplateComponentInClassHierarchy(const UActorComponent* InComponent) const; +#if WITH_EDITOR + USCS_Node* FindSCSNodeForInstanceComponent(USimpleConstructionScript* SCS, const UActorComponent* InComponent) const; +#endif // WITH_EDITOR + UActorComponent* FindComponentInstanceInActor(const AActor* InActor, USCS_Node* SCSNode) const; + +protected: + + template + void SetTypedValueAt(const FString& Name, ValueT& Value, int Index=0); + + void OnTemplateParametersChangedHandler(UHoudiniAssetComponent* ComponentTemplate); + void InvalidateData(); + + USceneComponent* FindOwnerComponentByName(FName ComponentName) const; + USceneComponent* FindActorComponentByName(AActor * InActor, FName ComponentName) const; + + void CachePreviewState(); + void CacheBlueprintData(); + + USimpleConstructionScript* GetSCS() const; + + //// The output translation has finished. + //void OnOutputProcessingCompletedHandler(UHoudiniAssetComponent * InComponent); + +#if WITH_EDITOR + //void ReceivedAssetEditorRequestCloseEvent(UObject* Asset, EAssetEditorCloseReason CloseReason); + TWeakObjectPtr CachedAssetEditorSubsystem; +#endif + + TWeakObjectPtr CachedBlueprint; + TWeakObjectPtr CachedActorCDO; + TWeakObjectPtr CachedTemplateComponent; + + /*UPROPERTY(DuplicateTransient) + bool bOutputsRequireUpdate;*/ + UPROPERTY() + bool FauxBPProperty; + + UPROPERTY() + bool bHoudiniAssetChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bUpdatedFromTemplate; + + UPROPERTY() + bool bIsInBlueprintEditor; + + UPROPERTY(Transient, DuplicateTransient) + bool bCanDeleteHoudiniNodes; + + UPROPERTY(Transient, DuplicateTransient) + bool bHasRegisteredComponentTemplate; + + FDelegateHandle TemplatePropertiesChangedHandle; + + // This is used to keep track of which SCS variable names correspond to which + // output objects. + // This seems like it will cause issues in the map. + UPROPERTY() + TMap CachedOutputNodes; + + // This is used to keep track of which (SCS) variable guids correspond to which + // input objects. + UPROPERTY() + TMap CachedInputNodes; +}; + + +///** Used to keep track of output data and mappings during reconstruction */ +USTRUCT() +struct FHoudiniAssetBlueprintOutput +{ + GENERATED_BODY() + + UPROPERTY() + int32 OutputIndex; + + UPROPERTY() + FHoudiniOutputObject OutputObject; + + FHoudiniAssetBlueprintOutput() + : OutputIndex(INDEX_NONE) + { + + } +}; + + +/** Used to store HoudiniAssetComponent data during BP reconstruction */ +USTRUCT() +struct FHoudiniAssetBlueprintInstanceData : public FActorComponentInstanceData +{ + GENERATED_BODY() +public: + FHoudiniAssetBlueprintInstanceData(); + FHoudiniAssetBlueprintInstanceData(const UHoudiniAssetBlueprintComponent* SourceComponent); + + virtual ~FHoudiniAssetBlueprintInstanceData() = default; + + /*virtual bool ContainsData() const override + { + return (HAC != nullptr) || Super::ContainsData(); + }*/ + + virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override + { + Super::ApplyToComponent(Component, CacheApplyPhase); + CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); + } + + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + + // Persist all the required properties for being able to recook the HoudiniAsset from its existing state. + UPROPERTY() + UHoudiniAsset* HoudiniAsset; + + UPROPERTY() + int32 AssetId; + + UPROPERTY() + EHoudiniAssetState AssetState; + + // Subasset index + UPROPERTY() + uint32 SubAssetIndex; + + UPROPERTY() + uint32 AssetCookCount; + + UPROPERTY() + bool bHasBeenLoaded; + + UPROPERTY() + bool bHasBeenDuplicated; + + UPROPERTY() + bool bPendingDelete; + + UPROPERTY() + bool bRecookRequested; + + UPROPERTY() + bool bRebuildRequested; + + UPROPERTY() + bool bEnableCooking; + + UPROPERTY() + bool bForceNeedUpdate; + + UPROPERTY() + bool bLastCookSuccess; + + /*UPROPERTY(DuplicateTransient) + TSet DownstreamHoudiniAssets;*/ + + UPROPERTY() + FGuid ComponentGUID; + + UPROPERTY() + FGuid HapiGUID; + + UPROPERTY() + bool bRegisteredComponentTemplate; + + // Name of the component from which this + // data was copied. Used for debugging purposes. + UPROPERTY() + FString SourceName; + + UPROPERTY() + TMap Outputs; + + UPROPERTY() + TArray Inputs; +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp index 68403e6a3..bebbcd8c8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp @@ -1,2890 +1,2954 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniAssetComponent.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetActor.h" -#include "HoudiniInput.h" -#include "HoudiniOutput.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterOperatorPath.h" -#include "HoudiniHandleComponent.h" -#include "HoudiniPDGAssetLink.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "Engine/StaticMesh.h" -#include "Components/StaticMeshComponent.h" -#include "TimerManager.h" -#include "Landscape.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" -#include "InstancedFoliageActor.h" -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" -#include "PhysicsEngine/BodySetup.h" -#include "UObject/UObjectGlobals.h" - -#if WITH_EDITOR - #include "Editor/UnrealEd/Private/GeomFitUtils.h" -#endif - -#include "ComponentReregisterContext.h" - -// Macro to update given properties on all children components of the HAC. -#define HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( COMPONENT_CLASS, PROPERTY ) \ - do \ - { \ - TArray ReregisterComponents; \ - TArray LocalAttachChildren;\ - GetChildrenComponents(true, LocalAttachChildren); \ - for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) \ - { \ - COMPONENT_CLASS * Component = Cast(*Iter); \ - if (Component) \ - { \ - Component->PROPERTY = PROPERTY; \ - ReregisterComponents.Add(Component); \ - } \ - } \ - \ - if (ReregisterComponents.Num() > 0) \ - { \ - FMultiComponentReregisterContext MultiComponentReregisterContext(ReregisterComponents); \ - } \ - } \ - while(0) - - -void -UHoudiniAssetComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - - // Legacy serialization - // Either try to convert or skip depending the setting value - if (bEnableBackwardCompatibility) - { - // Attemp to convert the v1 object to v2 - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - // Deserialize the legacy data, we'll do the actual conversion in PostLoad() - // After everything has been deserialized - Version1CompatibilityHAC = NewObject(this); - Version1CompatibilityHAC->Serialize(Ar); - } - else - { - // Skip the v1 object - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -bool -UHoudiniAssetComponent::ConvertLegacyData() -{ - if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill()) - return false; - - // Set the Houdini Asset - if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill()) - return false; - - HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset; - - // Convert all parameters - for (auto& LegacyParmPair : Version1CompatibilityHAC->Parameters) - { - if (!LegacyParmPair.Value) - continue; - - UHoudiniParameter* Parm = LegacyParmPair.Value->ConvertLegacyData(this); - LegacyParmPair.Value->CopyLegacyParameterData(Parm); - Parameters.Add(Parm); - } - - // Convert all inputs - for (auto& LegacyInput : Version1CompatibilityHAC->Inputs) - { - // Convert v1 input to v2 - UHoudiniInput* Input = LegacyInput->ConvertLegacyInput(this); - - Inputs.Add(Input); - } - - // Lambdas for finding/creating outputs from an HGPO - auto FindOrCreateOutput = [&](FHoudiniGeoPartObject& InNewHGPO, bool& bNew) - { - UHoudiniOutput* NewOutput = nullptr; - - // See if we can add to an existing output - UHoudiniOutput** FoundOutput = nullptr; - FoundOutput = Outputs.FindByPredicate( - [InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; }); - - if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill()) - { - // FoundOutput is valid, add to it - NewOutput = *FoundOutput; - bNew = false; - } - else - { - // Create a new output object - NewOutput = NewObject( - this, UHoudiniOutput::StaticClass(), NAME_None, RF_NoFlags); - bNew = true; - } - - return NewOutput; - }; - - - // Convert all outputs - // Start by handling the Static Meshes - for (auto& LegacySM : Version1CompatibilityHAC->StaticMeshes) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacySM.Key.ConvertLegacyData(); - - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(NewHGPO); - // Mark if the HoudiniOutput is editable - NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); - } - - // Build a new output object identifier - FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); - Identifier.bLoaded = true; - Identifier.PartName = NewHGPO.PartName; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); - OutputObj.OutputObject = LegacySM.Value; - OutputObj.OutputComponent = nullptr; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - // Handle the SMC for this SM / HGPO - if (LegacySM.Value && !LegacySM.Value->IsPendingKill()) - { - UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value); - if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill()) - OutputObj.OutputComponent = *FoundSMC; - } - - // Add to the outputs - Outputs.AddUnique(NewOutput); - - //NewOutput->StaleCount; - //NewOutput->bLandscapeWorldComposition; - //NewOutput->HoudiniCreatedSocketActors; - //NewOutput->HoudiniAttachedSocketActors; - //NewOutput->bHasEditableNodeBuilt - false; - } - - // ... then Landscapes - for (auto& LegacyLandscape : Version1CompatibilityHAC->LandscapeComponents) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacyLandscape.Key.ConvertLegacyData(); - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(NewHGPO); - // Mark if the HoudiniOutput is editable - NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); - } - - // Build a new output object identifier - FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); - Identifier.bLoaded = true; - Identifier.PartName = NewHGPO.PartName; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); - - // We need to create a LandscapePtr wrapper for the landscaope - UHoudiniLandscapePtr* LandscapePtr = NewObject(NewOutput); - LandscapePtr->SetSoftPtr(LegacyLandscape.Value.IsValid() ? LegacyLandscape.Value.Get() : nullptr); - - OutputObj.OutputObject = LandscapePtr; - OutputObj.OutputComponent = nullptr; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - // Add to the outputs - Outputs.AddUnique(NewOutput); - } - - // ... instancers - for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs) - { - if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill()) - continue; - - FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData(); - - // Prepare this output object's output identifier - FHoudiniOutputObjectIdentifier OutputIdentifier; - OutputIdentifier.ObjectId = InstancerHGPO.ObjectId; - OutputIdentifier.GeoId = InstancerHGPO.GeoId; - OutputIdentifier.PartId = InstancerHGPO.PartId; - OutputIdentifier.PartName = InstancerHGPO.PartName; - - EHoudiniInstancerType InstancerType = EHoudiniInstancerType::ObjectInstancer; - if (LegacyInstanceIn->Flags.bIsPackedPrimitiveInstancer) - InstancerType = EHoudiniInstancerType::PackedPrimitive; - else if (LegacyInstanceIn->Flags.bAttributeInstancerOverride) - InstancerType = EHoudiniInstancerType::AttributeInstancer; - else if (LegacyInstanceIn->Flags.bIsAttributeInstancer) - InstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; - else if (LegacyInstanceIn->ObjectToInstanceId >= 0) - InstancerType = EHoudiniInstancerType::ObjectInstancer; - - InstancerHGPO.InstancerType = InstancerType; - - //bool bIsMSIC = LegacyInstanceIn->Flags.bIsSplitMeshInstancer; - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(InstancerHGPO); - } - - // Get the output's instanced outputs - TMap& InstancedOutputs = NewOutput->GetInstancedOutputs(); - - int32 InstFieldIdx = 0; - for (auto& LegacyInstanceInputField : LegacyInstanceIn->InstanceInputFields) - { - FHoudiniGeoPartObject InstInputFieldHGPO = LegacyInstanceInputField->HoudiniGeoPartObject.ConvertLegacyData(); - - // Create an instanced output for this object - FHoudiniInstancedOutput NewInstOut; - NewInstOut.OriginalObject = LegacyInstanceInputField->OriginalObject; - NewInstOut.OriginalObjectIndex = -1; - NewInstOut.OriginalTransforms = LegacyInstanceInputField->InstancedTransforms; - - for (auto& InstObj : LegacyInstanceInputField->InstancedObjects) - NewInstOut.VariationObjects.Add(InstObj); - - int32 NumVar = LegacyInstanceInputField->RotationOffsets.Num(); - for (int32 Idx = 0; Idx < NumVar; Idx++) - { - FTransform TransOffset; - TransOffset.SetLocation(FVector::ZeroVector); - if (LegacyInstanceInputField->RotationOffsets.IsValidIndex(Idx)) - TransOffset.SetRotation(LegacyInstanceInputField->RotationOffsets[Idx].Quaternion()); - - if (LegacyInstanceInputField->ScaleOffsets.IsValidIndex(Idx)) - TransOffset.SetScale3D(LegacyInstanceInputField->ScaleOffsets[Idx]); - - NewInstOut.VariationTransformOffsets.Add(TransOffset); - } - - // Build an identifier for the instance output - FHoudiniOutputObjectIdentifier Identifier; - Identifier.ObjectId = InstInputFieldHGPO.ObjectId; - Identifier.GeoId = InstInputFieldHGPO.GeoId; - Identifier.PartId = InstInputFieldHGPO.PartId; - Identifier.PartName = InstInputFieldHGPO.PartName; - Identifier.SplitIdentifier = FString::FromInt(InstFieldIdx); - Identifier.bLoaded = true; - - // Add the instance output to the outputs - InstancedOutputs.Add(Identifier, NewInstOut); - - // Now create an Output object for each variation - int32 VarIdx = 0; - for (auto& LegacyComp : LegacyInstanceInputField->InstancerComponents) - { - // Build a new output object identifier for this variation - FHoudiniOutputObjectIdentifier VarIdentifier; - VarIdentifier.ObjectId = InstInputFieldHGPO.ObjectId; - VarIdentifier.GeoId = InstInputFieldHGPO.GeoId; - VarIdentifier.PartId = InstInputFieldHGPO.PartId; - VarIdentifier.PartName = InstInputFieldHGPO.PartName; - // Update the split identifier for this object - // We use both the original object index and the variation index: ORIG_VAR - VarIdentifier.SplitIdentifier = - FString::FromInt(InstFieldIdx) + TEXT("_") + FString::FromInt(VarIdx); - VarIdentifier.bLoaded = true; - - // Build/Update the output object - FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(VarIdentifier); - - OutputObj.OutputObject = nullptr; - OutputObj.OutputComponent = LegacyComp; - OutputObj.ProxyObject = nullptr; - OutputObj.ProxyComponent = nullptr; - OutputObj.bProxyIsCurrent = false; - - VarIdx++; - } - - // ??? - //LegacyInstanceInputField->VariationTransformsArray; - //LegacyInstanceInputField->InstanceColorOverride; - //LegacyInstanceInputField->VariationInstanceColorOverrideArray; - - // Index of the variation used for each transform - //NewInstOut.TransformVariationIndices; - //NewInstOut.bUniformScaleLocked = false; - - InstFieldIdx++; - } - - // Add to the outputs - Outputs.AddUnique(NewOutput); - } - - // ... then Spline Components (for Curve IN) - for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents) - { - UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value; - if (!CurSplineComp || CurSplineComp->IsPendingKill()) - continue; - - // TODO: Needed? - // Attach the spline to the HAC - CurSplineComp->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - - // Editable curve? / Should create an output for it! - if (CurSplineComp->IsEditableOutputCurve()) - { - FHoudiniGeoPartObject CurHGPO = LegacyCurve.Key.ConvertLegacyData(); - - // Look for an output for that HGPO - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - // Add the HGPO if we've just created it - if (bCreatedNew) - { - NewOutput->AddNewHGPO(CurHGPO); - } - - // Build an output object id for the editable curve output - FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; - EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; - EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; - EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; - EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; - - TMap& OutputObjects = NewOutput->GetOutputObjects(); - FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); - FoundOutputObject.OutputComponent = CurSplineComp; - - //CurSplineComp->SetHasEditableNodeBuilt(true); - CurSplineComp->SetIsInputCurve(false); - } - else - { - // Input! - // Conversion of the inputs should have done the job already - CurSplineComp->SetIsInputCurve(true); - } - } - - // ... Handles - for (auto& LegacyHandle : Version1CompatibilityHAC->HandleComponents) - { - // TODO: Handles!! - UHoudiniHandleComponent* NewHandle = nullptr; - HandleComponents.Add(NewHandle); - } - - // ... Materials - UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials; - if(LegacyMaterials && !LegacyMaterials->IsPendingKill()) - { - // Assignements: Apply to all outputs since they're not tied to an HGPO... - for (auto& CurOutput : Outputs) - { - TMap& CurrAssign = CurOutput->GetAssignementMaterials(); - for (auto& LegacyMaterial : LegacyMaterials->Assignments) - { - CurrAssign.Add(LegacyMaterial.Key, LegacyMaterial.Value); - } - } - - // Replacements - // Try to find the output matching the HGPO - for (auto& LegacyMaterial : LegacyMaterials->Replacements) - { - // Convert the legacy HGPO to a v2 HGPO - FHoudiniGeoPartObject NewHGPO = LegacyMaterial.Key.ConvertLegacyData(); - - TMap& LegacyReplacement = LegacyMaterial.Value; - - bool bCreatedNew = false; - UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) - continue; - - if (bCreatedNew) - continue; - - TMap& CurReplacement = NewOutput->GetReplacementMaterials(); - for (auto& CurLegacyReplacement : LegacyReplacement) - { - CurReplacement.Add(CurLegacyReplacement.Key, CurLegacyReplacement.Value); - } - } - } - - - // ... Bake Name overrides - for (auto& LegacyBakeNameOverride : Version1CompatibilityHAC->BakeNameOverrides) - { - // In Outputs? - } - - // ... then Downstream asset connections (due to Asset inputs) - for (auto& LegacyDownstreamHAC : Version1CompatibilityHAC->DownstreamAssetConnections) - { - //TSet DownstreamHoudiniAssets; - } - - // Then convert all remaing flags and properties - StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = Version1CompatibilityHAC->bGeneratedDoubleSidedGeometry; - StaticMeshGenerationProperties.GeneratedPhysMaterial = Version1CompatibilityHAC->GeneratedPhysMaterial; - StaticMeshGenerationProperties.DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; - StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; - StaticMeshGenerationProperties.GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; - StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; - StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; - StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; - StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = Version1CompatibilityHAC->GeneratedStreamingDistanceMultiplier; - //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; - StaticMeshGenerationProperties.GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; - - StaticMeshBuildSettings.DistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; - - BakeFolder.Path = Version1CompatibilityHAC->BakeFolder.ToString(); - TemporaryCookFolder.Path = Version1CompatibilityHAC->TempCookFolder.ToString(); - - ComponentGUID = Version1CompatibilityHAC->ComponentGUID; - - bEnableCooking = Version1CompatibilityHAC->bEnableCooking; - bUploadTransformsToHoudiniEngine = Version1CompatibilityHAC->bUploadTransformsToHoudiniEngine; - bCookOnTransformChange = Version1CompatibilityHAC->bTransformChangeTriggersCooks; - bCookOnParameterChange = true; - //Version1CompatibilityHAC->bCookingTriggersDownstreamCooks; - bCookOnAssetInputCook = true; - bOutputless = false; - bOutputTemplateGeos = false; - bFullyLoaded = Version1CompatibilityHAC->bFullyLoaded; - - //bContainsHoudiniLogoGeometry = Version1CompatibilityHAC->bContainsHoudiniLogoGeometry; - //bIsNativeComponent = Version1CompatibilityHAC->bIsNativeComponent; - //bIsPreviewComponent = Version1CompatibilityHAC->bIsPreviewComponent; - //bLoadedComponent = Version1CompatibilityHAC->bLoadedComponent; - //bIsPlayModeActive_Unused = Version1CompatibilityHAC->bIsPlayModeActive_Unused; - //Version1CompatibilityHAC->bTimeCookInPlaymode_Unused; - //Version1CompatibilityHAC->bUseHoudiniMaterials; - - //Version1CompatibilityHAC->GeneratedGeometryScaleFactor; - //Version1CompatibilityHAC->TransformScaleFactor; - //Version1CompatibilityHAC->PresetBuffer; - //Version1CompatibilityHAC->DefaultPresetBuffer; - //Version1CompatibilityHAC->ParameterByName; - - // Now that we're done, update all the output's types - for (auto& CurOutput : Outputs) - { - CurOutput->UpdateOutputType(); - } - - // - // Clean up the legacy HAC - // - - Version1CompatibilityHAC->Parameters.Empty(); - Version1CompatibilityHAC->Inputs.Empty(); - Version1CompatibilityHAC->StaticMeshes.Empty(); - Version1CompatibilityHAC->LandscapeComponents.Empty(); - Version1CompatibilityHAC->InstanceInputs.Empty(); - Version1CompatibilityHAC->SplineComponents.Empty(); - Version1CompatibilityHAC->HandleComponents.Empty(); - //Version1CompatibilityHAC->HoudiniAssetComponentMaterials.Empty(); - Version1CompatibilityHAC->BakeNameOverrides.Empty(); - Version1CompatibilityHAC->DownstreamAssetConnections.Empty(); - Version1CompatibilityHAC->MarkPendingKill(); - Version1CompatibilityHAC = nullptr; - - return true; -} - - -UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - HoudiniAsset = nullptr; - bCookOnParameterChange = true; - bUploadTransformsToHoudiniEngine = true; - bCookOnTransformChange = false; - //bUseNativeHoudiniMaterials = true; - bCookOnAssetInputCook = true; - - AssetId = -1; - AssetState = EHoudiniAssetState::NewHDA; - AssetStateResult = EHoudiniAssetStateResult::None; - AssetCookCount = 0; - - SubAssetIndex = -1; - - // Make an invalid GUID, since we do not have any cooking requests. - HapiGUID.Invalidate(); - - HapiAssetName = FString(); - - // Create unique component GUID. - ComponentGUID = FGuid::NewGuid(); - - bUploadTransformsToHoudiniEngine = true; - - bHasBeenLoaded = false; - bHasBeenDuplicated = false; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = false; - bEnableCooking = true; - bForceNeedUpdate = false; - bLastCookSuccess = false; - bBlueprintStructureModified = false; - bBlueprintModified = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // Folder used for cooking, the value is initialized by Output Translator - // TemporaryCookFolder.Path = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - - // Folder used for baking this asset's outputs, the value is initialized by Output Translator - // BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - bHasComponentTransformChanged = false; - - bFullyLoaded = false; - - bOutputless = false; - - bOutputTemplateGeos = false; - - PDGAssetLink = nullptr; - - StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; - - bOverrideGlobalProxyStaticMeshSettings = false; - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - bEnableProxyStaticMeshOverride = HoudiniRuntimeSettings->bEnableProxyStaticMesh; - bEnableProxyStaticMeshRefinementByTimerOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; - ProxyMeshAutoRefineTimeoutSecondsOverride = HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; - bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; - bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; - } - else - { - bEnableProxyStaticMeshOverride = false; - bEnableProxyStaticMeshRefinementByTimerOverride = true; - ProxyMeshAutoRefineTimeoutSecondsOverride = 10.0f; - bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = true; - bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = true; - } - - bNoProxyMeshNextCookRequested = false; - bBakeAfterNextCook = false; - -#if WITH_EDITORONLY_DATA - bGenerateMenuExpanded = true; - bBakeMenuExpanded = true; - bAssetOptionMenuExpanded = true; - bHelpAndDebugMenuExpanded = true; - - HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; - - bRemoveOutputAfterBake = false; - bRecenterBakedActors = false; - bReplacePreviousBake = false; -#endif - - // - // Set component properties. - // - - Mobility = EComponentMobility::Static; - - SetGenerateOverlapEvents(false); - - // Similar to UMeshComponent. - CastShadow = true; - bUseAsOccluder = true; - bCanEverAffectNavigation = true; - - // This component requires render update. - bNeverNeedsRenderUpdate = false; - - Bounds = FBox(ForceInitToZero); - - LastTickTime = 0.0; - - // Initialize the default SM Build settings with the plugin's settings default values - StaticMeshBuildSettings = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); -} - -UHoudiniAssetComponent::~UHoudiniAssetComponent() -{ - // Unregister ourself so our houdini node can be delete. - - // This gets called in UnRegisterHoudiniComponent, with appropriate checks. Don't call it here. - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); -} - -void UHoudiniAssetComponent::PostInitProperties() -{ - Super::PostInitProperties(); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - { - // Copy default static mesh generation parameters from settings. - StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; - StaticMeshGenerationProperties.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; - StaticMeshGenerationProperties.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; - StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; - StaticMeshGenerationProperties.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; - StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; - StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; - StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; - StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; - StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; - StaticMeshGenerationProperties.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; - } - - // Register ourself to the HER singleton - RegisterHoudiniComponent(this); -} - -UHoudiniAsset * -UHoudiniAssetComponent::GetHoudiniAsset() const -{ - return HoudiniAsset; -} - -FString -UHoudiniAssetComponent::GetDisplayName() const -{ - return GetOwner() ? GetOwner()->GetName() : GetName(); -} - -void -UHoudiniAssetComponent::GetOutputs(TArray& OutOutputs) const -{ - for (UHoudiniOutput* Output : Outputs) - { - OutOutputs.Add(Output); - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh; - } - else - { - return false; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementByTimerEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; - } - else - { - return false; - } - } -} - -float -UHoudiniAssetComponent::GetProxyMeshAutoRefineTimeoutSeconds() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return ProxyMeshAutoRefineTimeoutSecondsOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; - } - else - { - return 5.0f; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; - } - else - { - return false; - } - } -} - -bool -UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const -{ - if (bOverrideGlobalProxyStaticMeshSettings) - { - return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; - } - else - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); - if (HoudiniRuntimeSettings) - { - return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; - } - else - { - return false; - } - } -} - - -void -UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) -{ - // Check the asset validity - if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill()) - return; - - // If it is the same asset, do nothing. - if ( InHoudiniAsset == HoudiniAsset ) - return; - - HoudiniAsset = InHoudiniAsset; -} - - -void -UHoudiniAssetComponent::OnHoudiniAssetChanged() -{ - // TODO: clear input/params/outputs? - Parameters.Empty(); - - // The asset has been changed, mark us as needing to be reinstantiated - MarkAsNeedInstantiation(); - - // Force an update on the next tick - bForceNeedUpdate = true; -} - -bool -UHoudiniAssetComponent::NeedUpdateParameters() const -{ - // This is being split into a separate function to that it can - // be called separately for component templates. - - if (!bCookOnParameterChange) - return false; - - // Go through all our parameters, return true if they have been updated - for (auto CurrentParm : Parameters) - { - if (!CurrentParm || CurrentParm->IsPendingKill()) - continue; - - if (!CurrentParm->HasChanged()) - continue; - - // See if the parameter doesn't require an update - // (because it has failed to upload previously or has been loaded) - if (!CurrentParm->NeedsToTriggerUpdate()) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateParameters()] Parameters need update for component: %s"), *(GetPathName())); - return true; - } - - return false; -} - -bool -UHoudiniAssetComponent::NeedUpdateInputs() const -{ - // Go through all our inputs, return true if they have been updated - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (!CurrentInput->HasChanged()) - continue; - - // See if the input doesn't require an update - // (because it has failed to upload previously or has been loaded) - if (!CurrentInput->NeedsToTriggerUpdate()) - continue; - - HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateInputs()] Inputs need update for component: %s"), *(GetPathName())); - return true; - } - - return false; -} - -bool -UHoudiniAssetComponent::HasPreviousBakeOutput() const -{ - // Look for any bake output objects in the output array - for (const UHoudiniOutput* Output : Outputs) - { - if (!IsValid(Output)) - continue; - - if (BakedOutputs.Num() == 0) - return false; - - for (const FHoudiniBakedOutput& BakedOutput : BakedOutputs) - { - if (BakedOutput.BakedOutputObjects.Num() > 0) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::NeedUpdate() const -{ - if (AssetState != DebugLastAssetState) - { - DebugLastAssetState = AssetState; - } - - // It is important to check this when dealing with Blueprints since the - // preview components start receiving events from the template component - // before the preview component have finished initialization. - if (!IsFullyLoaded()) - return false; - - // We must have a valid asset - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return false; - - if (bForceNeedUpdate) - return true; - - // If we don't want to cook on parameter/input change dont bother looking for updates - if (!bCookOnParameterChange && !bRecookRequested && !bRebuildRequested) - return false; - - // Check if the HAC's transform has changed and transform triggers cook is enabled - if (bCookOnTransformChange && bHasComponentTransformChanged) - return true; - - if (NeedUpdateParameters()) - return true; - - if (NeedUpdateInputs()) - return true; - - // Go through all outputs, filter the editable nodes. Return true if they have been updated. - for (auto CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - // We only care about editable outputs - if (!CurrentOutput->IsEditableNode()) - continue; - - // Trigger an update if the output object is marked as modified by user. - TMap& OutputObjects = CurrentOutput->GetOutputObjects(); - for (auto& NextPair : OutputObjects) - { - // For now, only editable curves can trigger update - UHoudiniSplineComponent* HoudiniSplineComponent = Cast(NextPair.Value.OutputComponent); - if (!HoudiniSplineComponent) - continue; - - // Output curves cant trigger an update! - if (HoudiniSplineComponent->bIsOutputCurve) - continue; - - if (HoudiniSplineComponent->NeedsToTriggerUpdate()) - return true; - } - } - - return false; -} - -// Indicates if any of the HAC's output components needs to be updated (no recook needed) -bool -UHoudiniAssetComponent::NeedOutputUpdate() const -{ - // Go through all outputs - for (auto CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs()) - { - if (InstOutput.Value.bChanged) - return true; - } - } - - return false; -} - -bool UHoudiniAssetComponent::NeedBlueprintStructureUpdate() const -{ - // TODO: Add similar flags to inputs, parametsr - return bBlueprintStructureModified; -} - -bool UHoudiniAssetComponent::NeedBlueprintUpdate() const -{ - // TODO: Add similar flags to inputs, parametsr - return bBlueprintModified; -} - -bool -UHoudiniAssetComponent::NotifyCookedToDownstreamAssets() -{ - // Before notifying, clean up our downstream assets - // - check that they are still valid - // - check that we are still connected to one of its asset input - // - check that the asset as the CookOnAssetInputCook trigger enabled - TArray DownstreamToDelete; - for(auto& CurrentDownstreamHAC : DownstreamHoudiniAssets) - { - // Remove the downstream connection by default, - // unless we actually were properly connected to one of this HDa's input. - bool bRemoveDownstream = true; - if (CurrentDownstreamHAC && !CurrentDownstreamHAC->IsPendingKill()) - { - // Go through the HAC's input - for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs) - { - if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill()) - continue; - - EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType(); - if (CurrentDownstreamInputType != EHoudiniInputType::Asset - && CurrentDownstreamInputType != EHoudiniInputType::World) - continue; - - if (!CurrentDownstreamInput->ContainsInputObject(this, CurrentDownstreamInputType)) - continue; - - if (CurrentDownstreamHAC->bCookOnAssetInputCook) - { - // Mark that HAC's input has changed - CurrentDownstreamInput->MarkChanged(true); - } - bRemoveDownstream = false; - } - } - - if (bRemoveDownstream) - { - DownstreamToDelete.Add(CurrentDownstreamHAC); - } - } - - for (auto ToDelete : DownstreamToDelete) - { - DownstreamHoudiniAssets.Remove(ToDelete); - } - - return true; -} - -bool -UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() -{ - for (auto& CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); - - if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World) - continue; - - TArray* ObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInputType); - if (!ObjectArray) - continue; - - for (auto& CurrentInputObject : (*ObjectArray)) - { - // Get the input HDA - UHoudiniAssetComponent* InputHAC = CurrentInputObject - ? Cast(CurrentInputObject->GetObject()) - : nullptr; - - if (!InputHAC) - continue; - - // If the input HDA needs to be instantiated, force him to instantiate - // if the input HDA is in any other state than None, we need to wait for him - // to finish whatever it's doing - if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) - { - // Tell the input HAC to instantiate - InputHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); - - // We need to wait - return true; - } - else if (InputHAC->GetAssetState() != EHoudiniAssetState::None) - { - // We need to wait - return true; - } - } - } - - return false; -} - -void -UHoudiniAssetComponent::BeginDestroy() -{ - if (CanDeleteHoudiniNodes()) - { - } - - // Gets called through UnRegisterHoudiniComponent(). - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - - // Unregister ourself so our houdini node can be deleted - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - Super::BeginDestroy(); -} - -void -UHoudiniAssetComponent::MarkAsNeedCook() -{ - // Force the asset state to NeedCook - //AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = true; - bRebuildRequested = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/trigger update - for (auto CurrentParam : Parameters) - { - if (!IsValid(CurrentParam)) - continue; - - // Do not trigger parameter update for Button/Button strip when recooking - // As we don't want to trigger the buttons - if (CurrentParam->IsA() || CurrentParam->IsA()) - continue; - - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(true); - } - - // We need to mark all of our editable curves as changed - for (auto Output : Outputs) - { - if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) - continue; - - for (auto& OutputObjectEntry : Output->GetOutputObjects()) - { - FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; - if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) - continue; - - UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); - if (!IsValid(SplineComponent)) - continue; - - // This sets bHasChanged and bNeedsToTriggerUpdate - SplineComponent->MarkChanged(true); - } - } - - // We need to mark all our inputs as changed/trigger update - for (auto CurrentInput : Inputs) - { - if (!IsValid(CurrentInput)) - continue; - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(true); - CurrentInput->MarkDataUploadNeeded(true); - - // In addition to marking the input as changed/need update, we also need to make sure that any changes on the - // Unreal side have been recorded for the input before sending to Houdini. For that we also mark each input - // object as changed/need update and explicitly call the Update function on each input object. For example, for - // input actors this would recreate the Houdini input actor components from the actor's components, picking up - // any new components since the last call to Update. - TArray* InputObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); - if (InputObjectArray && InputObjectArray->Num() > 0) - { - for (auto CurrentInputObject : *InputObjectArray) - { - if (!IsValid(CurrentInputObject)) - continue; - - UObject* const Object = CurrentInputObject->GetObject(); - if (IsValid(Object)) - CurrentInputObject->Update(Object); - - CurrentInputObject->MarkChanged(true); - CurrentInputObject->SetNeedsToTriggerUpdate(true); - CurrentInputObject->MarkTransformChanged(true); - } - } - } - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -void -UHoudiniAssetComponent::MarkAsNeedRebuild() -{ - // Invalidate the asset ID - //AssetId = -1; - - // Force the asset state to NeedRebuild - SetAssetState(EHoudiniAssetState::NeedRebuild); - AssetStateResult = EHoudiniAssetStateResult::None; - - // Reset some of the asset's flag - //AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = true; - bFullyLoaded = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/trigger update - for (auto CurrentParam : Parameters) - { - if (!IsValid(CurrentParam)) - continue; - - // Do not trigger parameter update for Button/Button strip when rebuilding - // As we don't want to trigger the buttons - if (CurrentParam->IsA() || CurrentParam->IsA()) - continue; - - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(true); - } - - // We need to mark all of our editable curves as changed - for (auto Output : Outputs) - { - if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) - continue; - - for (auto& OutputObjectEntry : Output->GetOutputObjects()) - { - FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; - if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) - continue; - - UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); - if (!IsValid(SplineComponent)) - continue; - - // This sets bHasChanged and bNeedsToTriggerUpdate - SplineComponent->MarkChanged(true); - } - } - - // We need to mark all our inputs as changed/trigger update - for (auto CurrentInput : Inputs) - { - if (!IsValid(CurrentInput)) - continue; - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(true); - CurrentInput->MarkDataUploadNeeded(true); - } - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -// Marks the asset as needing to be instantiated -void -UHoudiniAssetComponent::MarkAsNeedInstantiation() -{ - // Invalidate the asset ID - AssetId = -1; - - if (Parameters.Num() <= 0 && Inputs.Num() <= 0 && Outputs.Num() <= 0) - { - // The asset has no parameters or inputs. - // This likely indicates it has never cooked/been instantiated. - // Set its state to NewHDA to force its instantiation - // so that we can have its parameters/input interface - SetAssetState(EHoudiniAssetState::NewHDA); - } - else - { - // The asset has cooked before since we have a parameter/input interface - // Set its state to need instantiation so that the asset is instantiated - // after being modified - SetAssetState(EHoudiniAssetState::NeedInstantiation); - } - - AssetStateResult = EHoudiniAssetStateResult::None; - - // Reset some of the asset's flag - AssetCookCount = 0; - bHasBeenLoaded = true; - bPendingDelete = false; - bRecookRequested = false; - bRebuildRequested = false; - bFullyLoaded = false; - - //bEditorPropertiesNeedFullUpdate = true; - - // We need to mark all our parameters as changed/not triggering update - for (auto CurrentParam : Parameters) - { - if (CurrentParam) - { - CurrentParam->MarkChanged(true); - CurrentParam->SetNeedsToTriggerUpdate(false); - } - } - - // We need to mark all our inputs as changed/not triggering update - for (auto CurrentInput : Inputs) - { - if (CurrentInput) - { - CurrentInput->MarkChanged(true); - CurrentInput->SetNeedsToTriggerUpdate(false); - CurrentInput->MarkDataUploadNeeded(true); - } - } - - /*if (!CanInstantiateAsset()) - { - AssetState = EHoudiniAssetState::None; - AssetStateResult = EHoudiniAssetStateResult::None; - }*/ - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); -} - -void UHoudiniAssetComponent::MarkAsBlueprintStructureModified() -{ - bBlueprintStructureModified = true; -} - -void UHoudiniAssetComponent::MarkAsBlueprintModified() -{ - bBlueprintModified = true; -} - -void -UHoudiniAssetComponent::PostLoad() -{ - Super::PostLoad(); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - bool bAutomaticLegacyHDARebuild = HoudiniRuntimeSettings->bAutomaticLegacyHDARebuild; - - // Legacy serialization: either try to convert or skip depending the setting value - if (bEnableBackwardCompatibility && Version1CompatibilityHAC != nullptr) - { - // If we have deserialized legacy v1 data, attempt to convert it now - ConvertLegacyData(); - - if (bAutomaticLegacyHDARebuild) - MarkAsNeedRebuild(); - else - MarkAsNeedInstantiation(); - } - else - { - // Normal v2 objet, mark as need instantiation - MarkAsNeedInstantiation(); - } - - // Component has been loaded, not duplicated - bHasBeenDuplicated = false; - - // We need to register ourself - RegisterHoudiniComponent(this); - - // Register our PDG Asset link if we have any - - // !!! Do not update rendering while loading, do it when setting up the render state - // UpdateRenderingInformation(); -} - -void -UHoudiniAssetComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context) -{ - UpdateRenderingInformation(); - Super::CreateRenderState_Concurrent(Context); -} - -void -UHoudiniAssetComponent::PostEditImport() -{ - Super::PostEditImport(); - - MarkAsNeedInstantiation(); - - // Component has been duplicated, not loaded - // We do need the loaded flag to reapply parameters, inputs - // and properly update some of the output objects - bHasBeenDuplicated = true; - - //RemoveAllAttachedComponents(); - - AssetState = EHoudiniAssetState::PreInstantiation; - AssetStateResult = EHoudiniAssetStateResult::None; - - // TODO? - // REGISTER? -} - -void -UHoudiniAssetComponent::UpdatePostDuplicate() -{ - // TODO: - // - Keep the output objects/components (remove duplicatetransient on the output object uproperties) - // - Duplicate created objects (ie SM) and materials - // - Update the output components to use these instead - // This should remove the need for a cook on duplicate - - // For now, we simply clean some of the HAC's component manually - const TArray Children = GetAttachChildren(); - - for (auto & NextChild : Children) - { - if (!NextChild || NextChild->IsPendingKill()) - continue; - - USceneComponent * ComponentToRemove = nullptr; - if (NextChild->IsA()) - { - ComponentToRemove = NextChild; - } - else if (NextChild->IsA()) - { - ComponentToRemove = NextChild; - } - /* do not destroy attached duplicated editable curves, they are needed to restore editable curves - else if (NextChild->IsA()) - { - // Remove duplicated editable curve output's Houdini Spline Component, since they will be re-built at duplication. - UHoudiniSplineComponent * HoudiniSplineComponent = Cast(NextChild); - if (HoudiniSplineComponent && HoudiniSplineComponent->IsEditableOutputCurve()) - ComponentToRemove = NextChild; - } - */ - if (ComponentToRemove) - { - ComponentToRemove->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - ComponentToRemove->UnregisterComponent(); - ComponentToRemove->DestroyComponent(); - } - } - - // if there is an associated PDG asset link, call its UpdatePostDuplicate to cleanup references to - // to the original instance's PDG output actors - if (IsValid(PDGAssetLink)) - { - PDGAssetLink->UpdatePostDuplicate(); - } - - SetHasBeenDuplicated(false); -} - -bool UHoudiniAssetComponent::IsInputTypeSupported(EHoudiniInputType InType) const -{ - return true; -} - -bool UHoudiniAssetComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const -{ - return true; -} - -bool -UHoudiniAssetComponent::IsPreview() const -{ - return bCachedIsPreview; -} - -bool UHoudiniAssetComponent::IsValidComponent() const -{ - return true; -} - -void UHoudiniAssetComponent::OnFullyLoaded() -{ - bFullyLoaded = true; -} - - -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); - } - */ -} - -void -UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - - if (CanDeleteHoudiniNodes()) - { - } - - // Unregister ourself so our houdini node can be deleted - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - HoudiniAsset = nullptr; - - // Clear Parameters - for (UHoudiniParameter*& CurrentParm : Parameters) - { - if (CurrentParm && !CurrentParm->IsPendingKill()) - { - CurrentParm->ConditionalBeginDestroy(); - } - else if (GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE) - { - // TODO unneeded log? - // Avoid spamming that error when leaving PIE mode - HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); - } - - CurrentParm = nullptr; - } - - Parameters.Empty(); - - // Clear Inputs - for (UHoudiniInput*& CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - // Destroy connected Houdini asset. - CurrentInput->ConditionalBeginDestroy(); - CurrentInput = nullptr; - } - - Inputs.Empty(); - - // Clear Output - for (UHoudiniOutput*& CurrentOutput : Outputs) - { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) - continue; - - if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) - continue; - - // Destroy all Houdini created socket actors. - TArray & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors(); - for (auto & CurCreatedActor : CurCreatedSocketActors) - { - if (!CurCreatedActor || CurCreatedActor->IsPendingKill()) - continue; - - CurCreatedActor->Destroy(); - } - CurCreatedSocketActors.Empty(); - - // Detach all Houdini attached socket actors - TArray & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors(); - for (auto & CurAttachedSocketActor : CurAttachedSocketActors) - { - if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill()) - continue; - - CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); - } - CurAttachedSocketActors.Empty(); - -#if WITH_EDITOR - // Clean up foliages instances - for (auto& CurrentOutputObject : CurrentOutput->GetOutputObjects()) - { - // Foliage instancers store a HISMC in the components - UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentOutputObject.Value.OutputComponent); - if (!FoliageHISMC) - continue; - - UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) - continue; - - // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, - // if it is not, then we are just a "regular" HISMC - AInstancedFoliageActor* InstancedFoliageActor = Cast(FoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) - continue; - - UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) - continue; - - if (IsInGameThread() && IsGarbageCollecting()) - { - // TODO: ?? - // Calling DeleteInstancesForComponent during GC will cause unreal to crash... - HOUDINI_LOG_WARNING(TEXT("%s: Unable to clear foliage instances because of GC"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); - } - else - { - // Clean up the instances generated for that component - InstancedFoliageActor->DeleteInstancesForComponent(this, FoliageType); - } - - if (FoliageHISMC->GetInstanceCount() > 0) - { - // If the component still has instances left after the cleanup, - // make sure that we dont delete it, as the leftover instances are likely hand-placed - CurrentOutputObject.Value.OutputComponent = nullptr; - } - else - { - // Remove the foliage type if it doesn't have any more instances - InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); - } - } -#endif - - CurrentOutput->Clear(); - // Destroy connected Houdini asset. - CurrentOutput->ConditionalBeginDestroy(); - CurrentOutput = nullptr; - } - - Outputs.Empty(); - - //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); - // Unregister ourself so our houdini node can be delete. - FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); - - // Clear the static mesh bake timer - ClearRefineMeshesTimer(); - - - // Clear all TOP data and temporary geo/objects from the PDG asset link (if valid) - if (IsValid(PDGAssetLink)) - { -#if WITH_EDITOR - const UWorld* const World = GetWorld(); - if (IsValid(World)) - { - // Only do this for editor worlds, only interactively (not during engine shutdown or garbage collection) - if (World->WorldType == EWorldType::Editor && GIsRunning && !GIsGarbageCollecting) - { - // In case we are recording a transaction (undo, for example) notify that the object will be - // modified. - PDGAssetLink->Modify(); - PDGAssetLink->ClearAllTOPData(); - } - } -#endif - } - - Super::OnComponentDestroyed(bDestroyingHierarchy); -} - -void UHoudiniAssetComponent::RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) -{ - // Registration of this component is wrapped in this virtual function to allow - // derived classed to override this behaviour. - FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(InComponent); -} - -void -UHoudiniAssetComponent::OnRegister() -{ - Super::OnRegister(); - - // NOTE: Wait until HoudiniEngineTick() before deciding to mark this object as fully loaded - // since preview components need to wait for component templates to finish their initialization - // before being able to perform state transfers. - - /* - // 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(); - } - } - */ - - // Let TickInitialization() take care of manipulating the bFullyLoaded state. - //bFullyLoaded = true; - - //// If we're constructing editable components in the SCS editor, set the component instance corresponding to this node for editing purposes - // - //USimpleConstructionScript* SCS = GetSCS(); - //if (SCS == nullptr) - //{ - // bFullyLoaded = true; - //} - //else - //{ - // if(SCS->IsConstructingEditorComponents()) - // { - // // We're not fully loaded yet. We're expecting - // } - // else - // { - // bFullyLoaded = true; - // } - //} - -} - -UHoudiniParameter* -UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) -{ - if (!InOtherParam || InOtherParam->IsPendingKill()) - return nullptr; - - for (auto CurrentParam : Parameters) - { - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - if (CurrentParam->Matches(*InOtherParam)) - return CurrentParam; - } - - return nullptr; -} - -UHoudiniInput* -UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) -{ - if (!InOtherInput || InOtherInput->IsPendingKill()) - return nullptr; - - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->Matches(*InOtherInput)) - return CurrentInput; - } - - return nullptr; -} - -UHoudiniHandleComponent* -UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle) -{ - if (!InOtherHandle || InOtherHandle->IsPendingKill()) - return nullptr; - - for (auto CurrentHandle : HandleComponents) - { - if (!CurrentHandle || CurrentHandle->IsPendingKill()) - continue; - - if (CurrentHandle->Matches(*InOtherHandle)) - return CurrentHandle; - } - - return nullptr; -} - -UHoudiniParameter* -UHoudiniAssetComponent::FindParameterByName(const FString& InParamName) -{ - for (auto CurrentParam : Parameters) - { - if (!CurrentParam || CurrentParam->IsPendingKill()) - continue; - - if (CurrentParam->GetParameterName().Equals(InParamName)) - return CurrentParam; - } - - return nullptr; -} - - -void -UHoudiniAssetComponent::OnChildAttached(USceneComponent* ChildComponent) -{ - Super::OnChildAttached(ChildComponent); - - // ... Do corresponding things for other houdini component types. - // ... -} - - -void -UHoudiniAssetComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) -{ - Super::OnUpdateTransform(UpdateTransformFlags, Teleport); - - SetHasComponentTransformChanged(true); -} - -void UHoudiniAssetComponent::HoudiniEngineTick() -{ - if (!IsFullyLoaded()) - { - OnFullyLoaded(); - } -} - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - FProperty * Property = PropertyChangedEvent.MemberProperty; - if (!Property) - return; - - FName PropertyName = Property->GetFName(); - - // Changing the Houdini Asset? - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, HoudiniAsset)) - { - OnHoudiniAssetChanged(); - } - else if (PropertyName == GetRelativeLocationPropertyName() - || PropertyName == GetRelativeRotationPropertyName() - || PropertyName == GetRelativeScale3DPropertyName()) - { - SetHasComponentTransformChanged(true); - } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bOverrideGlobalProxyStaticMeshSettings) - || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bEnableProxyStaticMeshRefinementByTimerOverride) - || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, ProxyMeshAutoRefineTimeoutSecondsOverride)) - { - ClearRefineMeshesTimer(); - // Reset the timer - // SetRefineMeshesTimer will check the relevant settings and only set the timer if enabled via settings - SetRefineMeshesTimer(); - } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, 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); - } - } - else if (PropertyName == TEXT("bVisible")) - { - // Visibility has changed, propagate it to children. - SetVisibility(IsVisible(), true); - } - else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bHiddenInGame)) - { - // Visibility has changed, propagate it to children. - SetHiddenInGame(bHiddenInGame, true); - } - else - { - // TODO: - // Propagate properties (mobility/visibility etc.. to children components) - // Look in v1 for: if (Property->HasMetaData(TEXT("Category"))) {} and HOUDINI_UPDATE_ALL_CHILD_COMPONENTS - } - - 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 (UHoudiniOutput* CurOutput : Outputs) - { - if (!CurOutput) - continue; - - for (auto& Pair : CurOutput->GetOutputObjects()) - { - UStaticMesh* StaticMesh = Cast(Pair.Value.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) - continue; - - SetStaticMeshGenerationProperties(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 ReregisterComponents; - { - TArray 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("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); - } - } - } -} -#endif - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::PostEditUndo() -{ - Super::PostEditUndo(); - - if (!IsPendingKill()) - { - // Make sure we are registered with the HER singleton - // We could be undoing a HoudiniActor delete - if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(this)) - { - MarkAsNeedInstantiation(); - - // Component has been loaded, not duplicated - bHasBeenDuplicated = false; - - RegisterHoudiniComponent(this); - } - } -} - -#endif - - -#if WITH_EDITOR -void -UHoudiniAssetComponent::OnActorMoved(AActor* Actor) -{ - if (GetOwner() != Actor) - return; - - SetHasComponentTransformChanged(true); -} -#endif - -void -UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged) -{ - // Only update the value if we're fully loaded - // This avoid triggering a recook when loading a level - if(bFullyLoaded) - bHasComponentTransformChanged = InHasChanged; -} - -void -UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) -{ - // Check the object validity - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) - return; - - // If it is the same object, do nothing. - if (InPDGAssetLink == PDGAssetLink) - return; - - PDGAssetLink = InPDGAssetLink; -} - - -FBoxSphereBounds -UHoudiniAssetComponent::CalcBounds(const FTransform & LocalToWorld) const -{ - FBoxSphereBounds LocalBounds; - FBox BoundingBox = GetAssetBounds(nullptr, false); - 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; -} - - -FBox -UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const -{ - FBox BoxBounds(ForceInitToZero); - - // Query the bounds for all output objects - for (auto & CurOutput : Outputs) - { - if (!CurOutput || CurOutput->IsPendingKill()) - continue; - - BoxBounds += CurOutput->GetBounds(); - } - - // Query the bounds for all our inputs - for (auto & CurInput : Inputs) - { - if (!CurInput || CurInput->IsPendingKill()) - continue; - - BoxBounds += CurInput->GetBounds(); - } - - // Query the bounds for all input parameters - for (auto & CurParam : Parameters) - { - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (CurParam->GetParameterType() != EHoudiniParameterType::Input) - continue; - - UHoudiniParameterOperatorPath* InputParam = Cast(CurParam); - if (!CurParam || CurParam->IsPendingKill()) - continue; - - if (!InputParam->HoudiniInput.IsValid()) - continue; - - BoxBounds += InputParam->HoudiniInput.Get()->GetBounds(); - } - - // Query the bounds for all our Houdini handles - for (auto & CurHandleComp : HandleComponents) - { - if (!CurHandleComp || CurHandleComp->IsPendingKill()) - continue; - - BoxBounds += CurHandleComp->GetBounds(); - } - - // Also scan all our decendants for SMC bounds not just top-level children - // ( split mesh instances' mesh bounds were not gathered proiperly ) - TArray 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(pChild)) - { - if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) - continue; - - FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); - if (StaticMeshBounds.IsValid) - BoxBounds += StaticMeshBounds; - } - } - - // If nothing was found, init with the asset's location - if (BoxBounds.GetVolume() == 0.0f) - BoxBounds += GetComponentLocation(); - - return BoxBounds; -} - -void -UHoudiniAssetComponent::ClearRefineMeshesTimer() -{ - UWorld *World = GetWorld(); - if (!World) - { - //HOUDINI_LOG_ERROR(TEXT("Cannot ClearRefineMeshesTimer, World is nullptr!")); - return; - } - - World->GetTimerManager().ClearTimer(RefineMeshesTimer); -} - -void -UHoudiniAssetComponent::SetRefineMeshesTimer() -{ - UWorld *World = GetWorld(); - if (!World) - { - HOUDINI_LOG_ERROR(TEXT("Cannot SetRefineMeshesTimer, World is nullptr!")); - return; - } - - // Check if timer-based proxy mesh refinement is enable for this component - const bool bEnableTimer = IsProxyStaticMeshRefinementByTimerEnabled(); - const float TimeSeconds = GetProxyMeshAutoRefineTimeoutSeconds(); - if (bEnableTimer) - { - World->GetTimerManager().SetTimer(RefineMeshesTimer, this, &UHoudiniAssetComponent::OnRefineMeshesTimerFired, 1.0f, false, TimeSeconds); - } - else - { - World->GetTimerManager().ClearTimer(RefineMeshesTimer); - } -} - -void -UHoudiniAssetComponent::OnRefineMeshesTimerFired() -{ - HOUDINI_LOG_MESSAGE(TEXT("UHoudiniAssetComponent::OnRefineMeshesTimerFired()")); - if (OnRefineMeshesTimerDelegate.IsBound()) - { - OnRefineMeshesTimerDelegate.Broadcast(this); - } -} - -bool -UHoudiniAssetComponent::HasAnyCurrentProxyOutput() const -{ - for (const UHoudiniOutput *Output : Outputs) - { - if (Output->HasAnyCurrentProxy()) - { - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasAnyProxyOutput() const -{ - for (const UHoudiniOutput *Output : Outputs) - { - if (Output->HasAnyProxy()) - { - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasAnyOutputComponent() const -{ - for (UHoudiniOutput *Output : Outputs) - { - for(auto& CurrentOutputObject : Output->GetOutputObjects()) - { - if(CurrentOutputObject.Value.OutputComponent) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::HasOutputObject(UObject* InOutputObjectToFind) const -{ - for (const auto& CurOutput : Outputs) - { - for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) - { - if (CurOutputObject.Value.OutputObject == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.OutputComponent == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.ProxyObject == InOutputObjectToFind) - return true; - else if (CurOutputObject.Value.ProxyComponent == InOutputObjectToFind) - return true; - } - } - - return false; -} - -bool -UHoudiniAssetComponent::IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const -{ - // Get the state of the asset and check if it is pre-cook, cooked, pending delete/rebuild or invalid - bOutNeedsRebuildOrDelete = false; - bOutInvalidState = false; - switch (AssetState) - { - case EHoudiniAssetState::NewHDA: - case EHoudiniAssetState::NeedInstantiation: - case EHoudiniAssetState::PreInstantiation: - case EHoudiniAssetState::Instantiating: - case EHoudiniAssetState::PreCook: - case EHoudiniAssetState::Cooking: - case EHoudiniAssetState::PostCook: - case EHoudiniAssetState::PreProcess: - case EHoudiniAssetState::Processing: - return false; - break; - case EHoudiniAssetState::None: - return true; - break; - case EHoudiniAssetState::NeedRebuild: - case EHoudiniAssetState::NeedDelete: - case EHoudiniAssetState::Deleting: - bOutNeedsRebuildOrDelete = true; - break; - default: - bOutInvalidState = true; - break; - } - - return false; -} - -void -UHoudiniAssetComponent::SetInputPresets(const TMap& InPresets) -{ - // Set the input preset for this HAC -#if WITH_EDITOR - InputPresets = InPresets; -#endif -} - - -void -UHoudiniAssetComponent::ApplyInputPresets() -{ - if (InputPresets.Num() <= 0) - return; - -#if WITH_EDITOR - // Ignore inputs that have been preset to curve - TArray InputArray; - for (auto CurrentInput : Inputs) - { - if (!CurrentInput || CurrentInput->IsPendingKill()) - continue; - - if (CurrentInput->GetInputType() != EHoudiniInputType::Curve) - InputArray.Add(CurrentInput); - } - - // Try to apply the supplied Object to the Input - for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset) - { - UObject * Object = IterToolPreset.Key(); - if (!Object || Object->IsPendingKill()) - continue; - - int32 InputNumber = IterToolPreset.Value(); - if (!InputArray.IsValidIndex(InputNumber)) - continue; - - // If the object is a landscape, add a new landscape input - if (Object->IsA()) - { - // selecting a landscape - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Landscape); - if (InsertNum == 0) - { - // Landscape inputs only support one object! - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Landscape, InsertNum, Object); - } - } - - // If the object is an actor, add a new world input - if (Object->IsA()) - { - // selecting an actor - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::World); - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::World, InsertNum, Object); - } - - // If the object is a static mesh, add a new geometry input (TODO: or BP ? ) - if (Object->IsA()) - { - // selecting a Staticn Mesh - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Geometry, InsertNum, Object); - } - - if (Object->IsA()) - { - // selecting a Houdini Asset - int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Asset); - if (InsertNum == 0) - { - // Assert inputs only support one object! - InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Asset, InsertNum, Object); - } - } - } - - // The input objects have been set, now change the input type - bool bBPStructureModified = false; - for (auto CurrentInput : Inputs) - { - int32 NumGeo = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); - int32 NumAsset = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Asset); - int32 NumWorld = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::World); - int32 NumLandscape = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Landscape); - - EHoudiniInputType NewInputType = EHoudiniInputType::Invalid; - if (NumLandscape > 0 && NumLandscape >= NumGeo && NumLandscape >= NumAsset && NumLandscape >= NumWorld) - NewInputType = EHoudiniInputType::Landscape; - else if (NumWorld > 0 && NumWorld >= NumGeo && NumWorld >= NumAsset && NumWorld >= NumLandscape) - NewInputType = EHoudiniInputType::World; - else if (NumAsset > 0 && NumAsset >= NumGeo && NumAsset >= NumWorld && NumAsset >= NumLandscape) - NewInputType = EHoudiniInputType::Asset; - else if (NumGeo > 0 && NumGeo >= NumAsset && NumGeo >= NumWorld && NumGeo >= NumLandscape) - NewInputType = EHoudiniInputType::Geometry; - - if (NewInputType == EHoudiniInputType::Invalid) - continue; - - // Change the input type, unless if it was preset to a different type and we have object for the preset type - if (CurrentInput->GetInputType() == EHoudiniInputType::Geometry && NewInputType != EHoudiniInputType::Geometry) - { - CurrentInput->SetInputType(NewInputType, bBPStructureModified); - } - else - { - // Input type was preset, only change if that type is empty - if(CurrentInput->GetNumberOfInputObjects() <= 0) - CurrentInput->SetInputType(NewInputType, bBPStructureModified); - } - } - if (bBPStructureModified) - { - MarkAsBlueprintStructureModified(); - } -#endif - - // Discard the tool presets after their first setup - InputPresets.Empty(); -} - - -bool -UHoudiniAssetComponent::IsComponentValid() const -{ - if (!IsValidLowLevel()) - return false; - - if (IsTemplate()) - return false; - - if (IsPendingKillOrUnreachable()) - return false; - - if (!GetOuter()) //|| !GetOuter()->GetLevel() ) - return false; - - return true; -} - -bool -UHoudiniAssetComponent::IsInstantiatingOrCooking() const -{ - return HapiGUID.IsValid(); -} - - -void -UHoudiniAssetComponent::SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const -{ -#if WITH_EDITOR - if (!InStaticMesh) - return; - - // Make sure static mesh has a new lighting guid. - InStaticMesh->LightingGuid = FGuid::NewGuid(); - InStaticMesh->LODGroup = NAME_None; - - // Set resolution of lightmap. - InStaticMesh->LightMapResolution = StaticMeshGenerationProperties.GeneratedLightMapResolution; - - // Set the global light map coordinate index if it looks valid - if (InStaticMesh->RenderData.IsValid() && InStaticMesh->RenderData->LODResources.Num() > 0) - { - int32 NumUVs = InStaticMesh->RenderData->LODResources[0].GetNumTexCoords(); - if (NumUVs > StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex) - { - InStaticMesh->LightMapCoordinateIndex = StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex; - } - } - - // TODO - // Set method for LOD texture factor computation. - //InStaticMesh->bUseMaximumStreamingTexelRatio = StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; - // Set distance where textures using UV 0 are streamed in/out. - GOES ON COMPONENT - // InStaticMesh->StreamingDistanceMultiplier = StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; - - // Add user data. - for (int32 AssetUserDataIdx = 0; AssetUserDataIdx < StaticMeshGenerationProperties.GeneratedAssetUserData.Num(); AssetUserDataIdx++) - InStaticMesh->AddAssetUserData(StaticMeshGenerationProperties.GeneratedAssetUserData[AssetUserDataIdx]); - - // - if (!InStaticMesh->BodySetup) - InStaticMesh->CreateBodySetup(); - - UBodySetup* BodySetup = InStaticMesh->BodySetup; - if (!InStaticMesh->BodySetup) - return; - - // Set flag whether physics triangle mesh will use double sided faces when doing scene queries. - BodySetup->bDoubleSidedGeometry = StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry; - - // Assign physical material for simple collision. - BodySetup->PhysMaterial = StaticMeshGenerationProperties.GeneratedPhysMaterial; - - BodySetup->DefaultInstance.CopyBodyInstancePropertiesFrom(&StaticMeshGenerationProperties.DefaultBodyInstance); - - // Assign collision trace behavior. - BodySetup->CollisionTraceFlag = StaticMeshGenerationProperties.GeneratedCollisionTraceFlag; - - // Assign walkable slope behavior. - BodySetup->WalkableSlopeOverride = StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride; - - // We want to use all of geometry for collision detection purposes. - BodySetup->bMeshCollideAll = true; - -#endif -} - - -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 LocalAttachChildren; - GetChildrenComponents(true, LocalAttachChildren); - for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) - { - USceneComponent * SceneComponent = *Iter; - if (IsValid(SceneComponent)) - SceneComponent->RecreatePhysicsState(); - } - - // !!! Do not call UpdateBounds() here as this could cause - // a loading loop in post load on game builds! -} - - -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(&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::SetAssetState(EHoudiniAssetState InNewState) -{ - const EHoudiniAssetState OldState = AssetState; - AssetState = InNewState; - - HandleOnHoudiniAssetStateChange(this, OldState, InNewState); -} - -void -UHoudiniAssetComponent::HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) -{ - IHoudiniAssetStateEvents::HandleOnHoudiniAssetStateChange(InHoudiniAssetContext, InFromState, InToState); - - if (InFromState == InToState) - return; - - if (this != InHoudiniAssetContext) - return; - - FOnAssetStateChangeDelegate& StateChangeDelegate = GetOnAssetStateChangeDelegate(); - if (StateChangeDelegate.IsBound()) - StateChangeDelegate.Broadcast(this, InFromState, InToState); - - if (InToState == EHoudiniAssetState::PostCook) - { - HandleOnPostCook(); - } - -} - -void -UHoudiniAssetComponent::HandleOnPostCook() -{ - if (OnPostCookDelegate.IsBound()) - OnPostCookDelegate.Broadcast(this, bLastCookSuccess); -} - -void -UHoudiniAssetComponent::HandleOnPostBake(bool bInSuccess) -{ - if (OnPostBakeDelegate.IsBound()) - OnPostBakeDelegate.Broadcast(this, bInSuccess); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniAssetComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetActor.h" +#include "HoudiniInput.h" +#include "HoudiniOutput.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterOperatorPath.h" +#include "HoudiniHandleComponent.h" +#include "HoudiniPDGAssetLink.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" +#include "TimerManager.h" +#include "Landscape.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" +#include "InstancedFoliageActor.h" +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" +#include "PhysicsEngine/BodySetup.h" +#include "UObject/UObjectGlobals.h" + +#if WITH_EDITOR + #include "Editor/UnrealEd/Private/GeomFitUtils.h" +#endif + +#include "ComponentReregisterContext.h" + +// Macro to update given properties on all children components of the HAC. +#define HOUDINI_UPDATE_ALL_CHILD_COMPONENTS( COMPONENT_CLASS, PROPERTY ) \ + do \ + { \ + TArray ReregisterComponents; \ + TArray LocalAttachChildren;\ + GetChildrenComponents(true, LocalAttachChildren); \ + for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) \ + { \ + COMPONENT_CLASS * Component = Cast(*Iter); \ + if (Component) \ + { \ + Component->PROPERTY = PROPERTY; \ + ReregisterComponents.Add(Component); \ + } \ + } \ + \ + if (ReregisterComponents.Num() > 0) \ + { \ + FMultiComponentReregisterContext MultiComponentReregisterContext(ReregisterComponents); \ + } \ + } \ + while(0) + + +void +UHoudiniAssetComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + + // Legacy serialization + // Either try to convert or skip depending the setting value + if (bEnableBackwardCompatibility) + { + // Attemp to convert the v1 object to v2 + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + // Deserialize the legacy data, we'll do the actual conversion in PostLoad() + // After everything has been deserialized + Version1CompatibilityHAC = NewObject(this); + Version1CompatibilityHAC->Serialize(Ar); + } + else + { + // Skip the v1 object + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniAssetComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +bool +UHoudiniAssetComponent::ConvertLegacyData() +{ + if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill()) + return false; + + // Set the Houdini Asset + if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill()) + return false; + + HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset; + + // Convert all parameters + for (auto& LegacyParmPair : Version1CompatibilityHAC->Parameters) + { + if (!LegacyParmPair.Value) + continue; + + UHoudiniParameter* Parm = LegacyParmPair.Value->ConvertLegacyData(this); + LegacyParmPair.Value->CopyLegacyParameterData(Parm); + Parameters.Add(Parm); + } + + // Convert all inputs + for (auto& LegacyInput : Version1CompatibilityHAC->Inputs) + { + // Convert v1 input to v2 + UHoudiniInput* Input = LegacyInput->ConvertLegacyInput(this); + + Inputs.Add(Input); + } + + // Lambdas for finding/creating outputs from an HGPO + auto FindOrCreateOutput = [&](FHoudiniGeoPartObject& InNewHGPO, bool& bNew) + { + UHoudiniOutput* NewOutput = nullptr; + + // See if we can add to an existing output + UHoudiniOutput** FoundOutput = nullptr; + FoundOutput = Outputs.FindByPredicate( + [InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; }); + + if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill()) + { + // FoundOutput is valid, add to it + NewOutput = *FoundOutput; + bNew = false; + } + else + { + // Create a new output object + NewOutput = NewObject( + this, UHoudiniOutput::StaticClass(), NAME_None, RF_NoFlags); + bNew = true; + } + + return NewOutput; + }; + + + // Convert all outputs + // Start by handling the Static Meshes + for (auto& LegacySM : Version1CompatibilityHAC->StaticMeshes) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacySM.Key.ConvertLegacyData(); + + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(NewHGPO); + // Mark if the HoudiniOutput is editable + NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); + } + + // Build a new output object identifier + FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); + Identifier.bLoaded = true; + Identifier.PartName = NewHGPO.PartName; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); + OutputObj.OutputObject = LegacySM.Value; + OutputObj.OutputComponent = nullptr; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + // Handle the SMC for this SM / HGPO + if (LegacySM.Value && !LegacySM.Value->IsPendingKill()) + { + UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value); + if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill()) + OutputObj.OutputComponent = *FoundSMC; + } + + // Add to the outputs + Outputs.AddUnique(NewOutput); + + //NewOutput->StaleCount; + //NewOutput->bLandscapeWorldComposition; + //NewOutput->HoudiniCreatedSocketActors; + //NewOutput->HoudiniAttachedSocketActors; + //NewOutput->bHasEditableNodeBuilt - false; + } + + // ... then Landscapes + for (auto& LegacyLandscape : Version1CompatibilityHAC->LandscapeComponents) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacyLandscape.Key.ConvertLegacyData(); + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(NewHGPO); + // Mark if the HoudiniOutput is editable + NewOutput->SetIsEditableNode(NewHGPO.bIsEditable); + } + + // Build a new output object identifier + FHoudiniOutputObjectIdentifier Identifier(NewHGPO.ObjectId, NewHGPO.GeoId, NewHGPO.PartId, NewHGPO.SplitGroups[0]); + Identifier.bLoaded = true; + Identifier.PartName = NewHGPO.PartName; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(Identifier); + + // We need to create a LandscapePtr wrapper for the landscaope + UHoudiniLandscapePtr* LandscapePtr = NewObject(NewOutput); + LandscapePtr->SetSoftPtr(LegacyLandscape.Value.IsValid() ? LegacyLandscape.Value.Get() : nullptr); + + OutputObj.OutputObject = LandscapePtr; + OutputObj.OutputComponent = nullptr; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + // Add to the outputs + Outputs.AddUnique(NewOutput); + } + + // ... instancers + for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs) + { + if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill()) + continue; + + FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData(); + + // Prepare this output object's output identifier + FHoudiniOutputObjectIdentifier OutputIdentifier; + OutputIdentifier.ObjectId = InstancerHGPO.ObjectId; + OutputIdentifier.GeoId = InstancerHGPO.GeoId; + OutputIdentifier.PartId = InstancerHGPO.PartId; + OutputIdentifier.PartName = InstancerHGPO.PartName; + + EHoudiniInstancerType InstancerType = EHoudiniInstancerType::ObjectInstancer; + if (LegacyInstanceIn->Flags.bIsPackedPrimitiveInstancer) + InstancerType = EHoudiniInstancerType::PackedPrimitive; + else if (LegacyInstanceIn->Flags.bAttributeInstancerOverride) + InstancerType = EHoudiniInstancerType::AttributeInstancer; + else if (LegacyInstanceIn->Flags.bIsAttributeInstancer) + InstancerType = EHoudiniInstancerType::OldSchoolAttributeInstancer; + else if (LegacyInstanceIn->ObjectToInstanceId >= 0) + InstancerType = EHoudiniInstancerType::ObjectInstancer; + + InstancerHGPO.InstancerType = InstancerType; + + //bool bIsMSIC = LegacyInstanceIn->Flags.bIsSplitMeshInstancer; + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(InstancerHGPO); + } + + // Get the output's instanced outputs + TMap& InstancedOutputs = NewOutput->GetInstancedOutputs(); + + int32 InstFieldIdx = 0; + for (auto& LegacyInstanceInputField : LegacyInstanceIn->InstanceInputFields) + { + FHoudiniGeoPartObject InstInputFieldHGPO = LegacyInstanceInputField->HoudiniGeoPartObject.ConvertLegacyData(); + + // Create an instanced output for this object + FHoudiniInstancedOutput NewInstOut; + NewInstOut.OriginalObject = LegacyInstanceInputField->OriginalObject; + NewInstOut.OriginalObjectIndex = -1; + NewInstOut.OriginalTransforms = LegacyInstanceInputField->InstancedTransforms; + + for (auto& InstObj : LegacyInstanceInputField->InstancedObjects) + NewInstOut.VariationObjects.Add(InstObj); + + int32 NumVar = LegacyInstanceInputField->RotationOffsets.Num(); + for (int32 Idx = 0; Idx < NumVar; Idx++) + { + FTransform TransOffset; + TransOffset.SetLocation(FVector::ZeroVector); + if (LegacyInstanceInputField->RotationOffsets.IsValidIndex(Idx)) + TransOffset.SetRotation(LegacyInstanceInputField->RotationOffsets[Idx].Quaternion()); + + if (LegacyInstanceInputField->ScaleOffsets.IsValidIndex(Idx)) + TransOffset.SetScale3D(LegacyInstanceInputField->ScaleOffsets[Idx]); + + NewInstOut.VariationTransformOffsets.Add(TransOffset); + } + + // Build an identifier for the instance output + FHoudiniOutputObjectIdentifier Identifier; + Identifier.ObjectId = InstInputFieldHGPO.ObjectId; + Identifier.GeoId = InstInputFieldHGPO.GeoId; + Identifier.PartId = InstInputFieldHGPO.PartId; + Identifier.PartName = InstInputFieldHGPO.PartName; + Identifier.SplitIdentifier = FString::FromInt(InstFieldIdx); + Identifier.bLoaded = true; + + // Add the instance output to the outputs + InstancedOutputs.Add(Identifier, NewInstOut); + + // Now create an Output object for each variation + int32 VarIdx = 0; + for (auto& LegacyComp : LegacyInstanceInputField->InstancerComponents) + { + // Build a new output object identifier for this variation + FHoudiniOutputObjectIdentifier VarIdentifier; + VarIdentifier.ObjectId = InstInputFieldHGPO.ObjectId; + VarIdentifier.GeoId = InstInputFieldHGPO.GeoId; + VarIdentifier.PartId = InstInputFieldHGPO.PartId; + VarIdentifier.PartName = InstInputFieldHGPO.PartName; + // Update the split identifier for this object + // We use both the original object index and the variation index: ORIG_VAR + VarIdentifier.SplitIdentifier = + FString::FromInt(InstFieldIdx) + TEXT("_") + FString::FromInt(VarIdx); + VarIdentifier.bLoaded = true; + + // Build/Update the output object + FHoudiniOutputObject& OutputObj = NewOutput->GetOutputObjects().FindOrAdd(VarIdentifier); + + OutputObj.OutputObject = nullptr; + OutputObj.OutputComponent = LegacyComp; + OutputObj.ProxyObject = nullptr; + OutputObj.ProxyComponent = nullptr; + OutputObj.bProxyIsCurrent = false; + + VarIdx++; + } + + // ??? + //LegacyInstanceInputField->VariationTransformsArray; + //LegacyInstanceInputField->InstanceColorOverride; + //LegacyInstanceInputField->VariationInstanceColorOverrideArray; + + // Index of the variation used for each transform + //NewInstOut.TransformVariationIndices; + //NewInstOut.bUniformScaleLocked = false; + + InstFieldIdx++; + } + + // Add to the outputs + Outputs.AddUnique(NewOutput); + } + + // ... then Spline Components (for Curve IN) + for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents) + { + UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value; + if (!CurSplineComp || CurSplineComp->IsPendingKill()) + continue; + + // TODO: Needed? + // Attach the spline to the HAC + CurSplineComp->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + // Editable curve? / Should create an output for it! + if (CurSplineComp->IsEditableOutputCurve()) + { + FHoudiniGeoPartObject CurHGPO = LegacyCurve.Key.ConvertLegacyData(); + + // Look for an output for that HGPO + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + // Add the HGPO if we've just created it + if (bCreatedNew) + { + NewOutput->AddNewHGPO(CurHGPO); + } + + // Build an output object id for the editable curve output + FHoudiniOutputObjectIdentifier EditableSplineComponentIdentifier; + EditableSplineComponentIdentifier.ObjectId = CurHGPO.ObjectId; + EditableSplineComponentIdentifier.GeoId = CurHGPO.GeoId; + EditableSplineComponentIdentifier.PartId = CurHGPO.PartId; + EditableSplineComponentIdentifier.PartName = CurHGPO.PartName; + + TMap& OutputObjects = NewOutput->GetOutputObjects(); + FHoudiniOutputObject& FoundOutputObject = OutputObjects.FindOrAdd(EditableSplineComponentIdentifier); + FoundOutputObject.OutputComponent = CurSplineComp; + + //CurSplineComp->SetHasEditableNodeBuilt(true); + CurSplineComp->SetIsInputCurve(false); + } + else + { + // Input! + // Conversion of the inputs should have done the job already + CurSplineComp->SetIsInputCurve(true); + } + } + + // ... Handles + for (auto& LegacyHandle : Version1CompatibilityHAC->HandleComponents) + { + // TODO: Handles!! + UHoudiniHandleComponent* NewHandle = nullptr; + HandleComponents.Add(NewHandle); + } + + // ... Materials + UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials; + if(LegacyMaterials && !LegacyMaterials->IsPendingKill()) + { + // Assignements: Apply to all outputs since they're not tied to an HGPO... + for (auto& CurOutput : Outputs) + { + TMap& CurrAssign = CurOutput->GetAssignementMaterials(); + for (auto& LegacyMaterial : LegacyMaterials->Assignments) + { + CurrAssign.Add(LegacyMaterial.Key, LegacyMaterial.Value); + } + } + + // Replacements + // Try to find the output matching the HGPO + for (auto& LegacyMaterial : LegacyMaterials->Replacements) + { + // Convert the legacy HGPO to a v2 HGPO + FHoudiniGeoPartObject NewHGPO = LegacyMaterial.Key.ConvertLegacyData(); + + TMap& LegacyReplacement = LegacyMaterial.Value; + + bool bCreatedNew = false; + UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); + if (!NewOutput || NewOutput->IsPendingKill()) + continue; + + if (bCreatedNew) + continue; + + TMap& CurReplacement = NewOutput->GetReplacementMaterials(); + for (auto& CurLegacyReplacement : LegacyReplacement) + { + CurReplacement.Add(CurLegacyReplacement.Key, CurLegacyReplacement.Value); + } + } + } + + + // ... Bake Name overrides + for (auto& LegacyBakeNameOverride : Version1CompatibilityHAC->BakeNameOverrides) + { + // In Outputs? + } + + // ... then Downstream asset connections (due to Asset inputs) + for (auto& LegacyDownstreamHAC : Version1CompatibilityHAC->DownstreamAssetConnections) + { + //TSet DownstreamHoudiniAssets; + } + + // Then convert all remaing flags and properties + StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = Version1CompatibilityHAC->bGeneratedDoubleSidedGeometry; + StaticMeshGenerationProperties.GeneratedPhysMaterial = Version1CompatibilityHAC->GeneratedPhysMaterial; + StaticMeshGenerationProperties.DefaultBodyInstance = Version1CompatibilityHAC->DefaultBodyInstance; + StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = Version1CompatibilityHAC->GeneratedCollisionTraceFlag; + StaticMeshGenerationProperties.GeneratedLightMapResolution = Version1CompatibilityHAC->GeneratedLightMapResolution; + StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = Version1CompatibilityHAC->GeneratedWalkableSlopeOverride; + StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = Version1CompatibilityHAC->GeneratedLightMapCoordinateIndex; + StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = Version1CompatibilityHAC->bGeneratedUseMaximumStreamingTexelRatio; + StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = Version1CompatibilityHAC->GeneratedStreamingDistanceMultiplier; + //StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = Version1CompatibilityHAC->GeneratedFoliageDefaultSettings; + StaticMeshGenerationProperties.GeneratedAssetUserData = Version1CompatibilityHAC->GeneratedAssetUserData; + + StaticMeshBuildSettings.DistanceFieldResolutionScale = Version1CompatibilityHAC->GeneratedDistanceFieldResolutionScale; + + BakeFolder.Path = Version1CompatibilityHAC->BakeFolder.ToString(); + TemporaryCookFolder.Path = Version1CompatibilityHAC->TempCookFolder.ToString(); + + ComponentGUID = Version1CompatibilityHAC->ComponentGUID; + + bEnableCooking = Version1CompatibilityHAC->bEnableCooking; + bUploadTransformsToHoudiniEngine = Version1CompatibilityHAC->bUploadTransformsToHoudiniEngine; + bCookOnTransformChange = Version1CompatibilityHAC->bTransformChangeTriggersCooks; + bCookOnParameterChange = true; + //Version1CompatibilityHAC->bCookingTriggersDownstreamCooks; + bCookOnAssetInputCook = true; + bOutputless = false; + bOutputTemplateGeos = false; + bUseOutputNodes = false; + bFullyLoaded = Version1CompatibilityHAC->bFullyLoaded; + + //bContainsHoudiniLogoGeometry = Version1CompatibilityHAC->bContainsHoudiniLogoGeometry; + //bIsNativeComponent = Version1CompatibilityHAC->bIsNativeComponent; + //bIsPreviewComponent = Version1CompatibilityHAC->bIsPreviewComponent; + //bLoadedComponent = Version1CompatibilityHAC->bLoadedComponent; + //bIsPlayModeActive_Unused = Version1CompatibilityHAC->bIsPlayModeActive_Unused; + //Version1CompatibilityHAC->bTimeCookInPlaymode_Unused; + //Version1CompatibilityHAC->bUseHoudiniMaterials; + + //Version1CompatibilityHAC->GeneratedGeometryScaleFactor; + //Version1CompatibilityHAC->TransformScaleFactor; + //Version1CompatibilityHAC->PresetBuffer; + //Version1CompatibilityHAC->DefaultPresetBuffer; + //Version1CompatibilityHAC->ParameterByName; + + // Now that we're done, update all the output's types + for (auto& CurOutput : Outputs) + { + CurOutput->UpdateOutputType(); + } + + // + // Clean up the legacy HAC + // + + Version1CompatibilityHAC->Parameters.Empty(); + Version1CompatibilityHAC->Inputs.Empty(); + Version1CompatibilityHAC->StaticMeshes.Empty(); + Version1CompatibilityHAC->LandscapeComponents.Empty(); + Version1CompatibilityHAC->InstanceInputs.Empty(); + Version1CompatibilityHAC->SplineComponents.Empty(); + Version1CompatibilityHAC->HandleComponents.Empty(); + //Version1CompatibilityHAC->HoudiniAssetComponentMaterials.Empty(); + Version1CompatibilityHAC->BakeNameOverrides.Empty(); + Version1CompatibilityHAC->DownstreamAssetConnections.Empty(); + Version1CompatibilityHAC->MarkPendingKill(); + Version1CompatibilityHAC = nullptr; + + return true; +} + + +UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + HoudiniAsset = nullptr; + bCookOnParameterChange = true; + bUploadTransformsToHoudiniEngine = true; + bCookOnTransformChange = false; + //bUseNativeHoudiniMaterials = true; + bCookOnAssetInputCook = true; + + AssetId = -1; + AssetState = EHoudiniAssetState::NewHDA; + AssetStateResult = EHoudiniAssetStateResult::None; + AssetCookCount = 0; + + SubAssetIndex = -1; + + // Make an invalid GUID, since we do not have any cooking requests. + HapiGUID.Invalidate(); + + HapiAssetName = FString(); + + // Create unique component GUID. + ComponentGUID = FGuid::NewGuid(); + + bUploadTransformsToHoudiniEngine = true; + + bHasBeenLoaded = false; + bHasBeenDuplicated = false; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = false; + bEnableCooking = true; + bForceNeedUpdate = false; + bLastCookSuccess = false; + bBlueprintStructureModified = false; + bBlueprintModified = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // Folder used for cooking, the value is initialized by Output Translator + // TemporaryCookFolder.Path = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + + // Folder used for baking this asset's outputs, the value is initialized by Output Translator + // BakeFolder.Path = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + bHasComponentTransformChanged = false; + + bFullyLoaded = false; + + bOutputless = false; + bOutputTemplateGeos = false; + bUseOutputNodes = false; + + PDGAssetLink = nullptr; + + StaticMeshMethod = EHoudiniStaticMeshMethod::RawMesh; + + bOverrideGlobalProxyStaticMeshSettings = false; + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + bEnableProxyStaticMeshOverride = HoudiniRuntimeSettings->bEnableProxyStaticMesh; + bEnableProxyStaticMeshRefinementByTimerOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; + ProxyMeshAutoRefineTimeoutSecondsOverride = HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; + } + else + { + bEnableProxyStaticMeshOverride = false; + bEnableProxyStaticMeshRefinementByTimerOverride = true; + ProxyMeshAutoRefineTimeoutSecondsOverride = 10.0f; + bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride = true; + bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride = true; + } + + bNoProxyMeshNextCookRequested = false; + bBakeAfterNextCook = false; + +#if WITH_EDITORONLY_DATA + bGenerateMenuExpanded = true; + bBakeMenuExpanded = true; + bAssetOptionMenuExpanded = true; + bHelpAndDebugMenuExpanded = true; + + HoudiniEngineBakeOption = EHoudiniEngineBakeOption::ToActor; + + bRemoveOutputAfterBake = false; + bRecenterBakedActors = false; + bReplacePreviousBake = false; +#endif + + // + // Set component properties. + // + + Mobility = EComponentMobility::Static; + + SetGenerateOverlapEvents(false); + + // Similar to UMeshComponent. + CastShadow = true; + bUseAsOccluder = true; + bCanEverAffectNavigation = true; + + // This component requires render update. + bNeverNeedsRenderUpdate = false; + + Bounds = FBox(ForceInitToZero); + + LastTickTime = 0.0; + + // Initialize the default SM Build settings with the plugin's settings default values + StaticMeshBuildSettings = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); +} + +UHoudiniAssetComponent::~UHoudiniAssetComponent() +{ + // Unregister ourself so our houdini node can be delete. + + // This gets called in UnRegisterHoudiniComponent, with appropriate checks. Don't call it here. + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); +} + +void UHoudiniAssetComponent::PostInitProperties() +{ + Super::PostInitProperties(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + // Copy default static mesh generation parameters from settings. + StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; + StaticMeshGenerationProperties.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; + StaticMeshGenerationProperties.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; + StaticMeshGenerationProperties.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; + StaticMeshGenerationProperties.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; + StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; + StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; + StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; + StaticMeshGenerationProperties.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; + StaticMeshGenerationProperties.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; + } + + // Register ourself to the HER singleton + RegisterHoudiniComponent(this); +} + +UHoudiniAsset * +UHoudiniAssetComponent::GetHoudiniAsset() const +{ + return HoudiniAsset; +} + +FString +UHoudiniAssetComponent::GetDisplayName() const +{ + return GetOwner() ? GetOwner()->GetName() : GetName(); +} + +void +UHoudiniAssetComponent::GetOutputs(TArray& OutOutputs) const +{ + for (UHoudiniOutput* Output : Outputs) + { + OutOutputs.Add(Output); + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh; + } + else + { + return false; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementByTimerEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementByTimer; + } + else + { + return false; + } + } +} + +float +UHoudiniAssetComponent::GetProxyMeshAutoRefineTimeoutSeconds() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return ProxyMeshAutoRefineTimeoutSecondsOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->ProxyMeshAutoRefineTimeoutSeconds; + } + else + { + return 5.0f; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreSaveWorld; + } + else + { + return false; + } + } +} + +bool +UHoudiniAssetComponent::IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const +{ + if (bOverrideGlobalProxyStaticMeshSettings) + { + return bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; + } + else + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); + if (HoudiniRuntimeSettings) + { + return HoudiniRuntimeSettings->bEnableProxyStaticMesh && HoudiniRuntimeSettings->bEnableProxyStaticMeshRefinementOnPreBeginPIE; + } + else + { + return false; + } + } +} + + +void +UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) +{ + // Check the asset validity + if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill()) + return; + + // If it is the same asset, do nothing. + if ( InHoudiniAsset == HoudiniAsset ) + return; + + HoudiniAsset = InHoudiniAsset; +} + + +void +UHoudiniAssetComponent::OnHoudiniAssetChanged() +{ + // TODO: clear input/params/outputs? + Parameters.Empty(); + + // The asset has been changed, mark us as needing to be reinstantiated + MarkAsNeedInstantiation(); + + // Force an update on the next tick + bForceNeedUpdate = true; +} + +bool +UHoudiniAssetComponent::NeedUpdateParameters() const +{ + // This is being split into a separate function to that it can + // be called separately for component templates. + + if (!bCookOnParameterChange) + return false; + + // Go through all our parameters, return true if they have been updated + for (auto CurrentParm : Parameters) + { + if (!CurrentParm || CurrentParm->IsPendingKill()) + continue; + + if (!CurrentParm->HasChanged()) + continue; + + // See if the parameter doesn't require an update + // (because it has failed to upload previously or has been loaded) + if (!CurrentParm->NeedsToTriggerUpdate()) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateParameters()] Parameters need update for component: %s"), *(GetPathName())); + return true; + } + + return false; +} + +bool +UHoudiniAssetComponent::NeedUpdateInputs() const +{ + // Go through all our inputs, return true if they have been updated + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (!CurrentInput->HasChanged()) + continue; + + // See if the input doesn't require an update + // (because it has failed to upload previously or has been loaded) + if (!CurrentInput->NeedsToTriggerUpdate()) + continue; + + HOUDINI_LOG_DISPLAY(TEXT("[UHoudiniAssetBlueprintComponent::NeedUpdateInputs()] Inputs need update for component: %s"), *(GetPathName())); + return true; + } + + return false; +} + +bool +UHoudiniAssetComponent::HasPreviousBakeOutput() const +{ + // Look for any bake output objects in the output array + for (const UHoudiniOutput* Output : Outputs) + { + if (!IsValid(Output)) + continue; + + if (BakedOutputs.Num() == 0) + return false; + + for (const FHoudiniBakedOutput& BakedOutput : BakedOutputs) + { + if (BakedOutput.BakedOutputObjects.Num() > 0) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::NeedUpdate() const +{ + if (AssetState != DebugLastAssetState) + { + DebugLastAssetState = AssetState; + } + + // It is important to check this when dealing with Blueprints since the + // preview components start receiving events from the template component + // before the preview component have finished initialization. + if (!IsFullyLoaded()) + return false; + + // We must have a valid asset + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return false; + + if (bForceNeedUpdate) + return true; + + // If we don't want to cook on parameter/input change dont bother looking for updates + if (!bCookOnParameterChange && !bRecookRequested && !bRebuildRequested) + return false; + + // Check if the HAC's transform has changed and transform triggers cook is enabled + if (bCookOnTransformChange && bHasComponentTransformChanged) + return true; + + if (NeedUpdateParameters()) + return true; + + if (NeedUpdateInputs()) + return true; + + // Go through all outputs, filter the editable nodes. Return true if they have been updated. + for (auto CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + // We only care about editable outputs + if (!CurrentOutput->IsEditableNode()) + continue; + + // Trigger an update if the output object is marked as modified by user. + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + for (auto& NextPair : OutputObjects) + { + // For now, only editable curves can trigger update + UHoudiniSplineComponent* HoudiniSplineComponent = Cast(NextPair.Value.OutputComponent); + if (!HoudiniSplineComponent) + continue; + + // Output curves cant trigger an update! + if (HoudiniSplineComponent->bIsOutputCurve) + continue; + + if (HoudiniSplineComponent->NeedsToTriggerUpdate()) + return true; + } + } + + return false; +} + +void +UHoudiniAssetComponent::PreventAutoUpdates() +{ + // It is important to check this when dealing with Blueprints since the + // preview components start receiving events from the template component + // before the preview component have finished initialization. + if (!IsFullyLoaded()) + return; + + bForceNeedUpdate = false; + bRecookRequested = false; + bRebuildRequested = false; + bHasComponentTransformChanged = false; + + // Go through all our parameters, prevent them from triggering updates + for (auto CurrentParm : Parameters) + { + if (!CurrentParm || CurrentParm->IsPendingKill()) + continue; + + // Prevent the parm from triggering an update + CurrentParm->SetNeedsToTriggerUpdate(false); + } + + // Same with inputs + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + // Prevent the input from triggering an update + CurrentInput->SetNeedsToTriggerUpdate(false); + } + + // Go through all outputs, filter the editable nodes. + for (auto CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + // We only care about editable outputs + if (!CurrentOutput->IsEditableNode()) + continue; + + // Trigger an update if the output object is marked as modified by user. + TMap& OutputObjects = CurrentOutput->GetOutputObjects(); + for (auto& NextPair : OutputObjects) + { + // For now, only editable curves can trigger update + UHoudiniSplineComponent* HoudiniSplineComponent = Cast(NextPair.Value.OutputComponent); + if (!HoudiniSplineComponent) + continue; + + // Output curves cant trigger an update! + if (HoudiniSplineComponent->bIsOutputCurve) + continue; + + HoudiniSplineComponent->SetNeedsToTriggerUpdate(false); + } + } +} + +// Indicates if any of the HAC's output components needs to be updated (no recook needed) +bool +UHoudiniAssetComponent::NeedOutputUpdate() const +{ + // Go through all outputs + for (auto CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs()) + { + if (InstOutput.Value.bChanged) + return true; + } + } + + return false; +} + +bool UHoudiniAssetComponent::NeedBlueprintStructureUpdate() const +{ + // TODO: Add similar flags to inputs, parametsr + return bBlueprintStructureModified; +} + +bool UHoudiniAssetComponent::NeedBlueprintUpdate() const +{ + // TODO: Add similar flags to inputs, parametsr + return bBlueprintModified; +} + +bool +UHoudiniAssetComponent::NotifyCookedToDownstreamAssets() +{ + // Before notifying, clean up our downstream assets + // - check that they are still valid + // - check that we are still connected to one of its asset input + // - check that the asset as the CookOnAssetInputCook trigger enabled + TArray DownstreamToDelete; + for(auto& CurrentDownstreamHAC : DownstreamHoudiniAssets) + { + // Remove the downstream connection by default, + // unless we actually were properly connected to one of this HDa's input. + bool bRemoveDownstream = true; + if (CurrentDownstreamHAC && !CurrentDownstreamHAC->IsPendingKill()) + { + // Go through the HAC's input + for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs) + { + if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill()) + continue; + + EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType(); + if (CurrentDownstreamInputType != EHoudiniInputType::Asset + && CurrentDownstreamInputType != EHoudiniInputType::World) + continue; + + if (!CurrentDownstreamInput->ContainsInputObject(this, CurrentDownstreamInputType)) + continue; + + if (CurrentDownstreamHAC->bCookOnAssetInputCook) + { + // Mark that HAC's input has changed + CurrentDownstreamInput->MarkChanged(true); + } + bRemoveDownstream = false; + } + } + + if (bRemoveDownstream) + { + DownstreamToDelete.Add(CurrentDownstreamHAC); + } + } + + for (auto ToDelete : DownstreamToDelete) + { + DownstreamHoudiniAssets.Remove(ToDelete); + } + + return true; +} + +bool +UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() +{ + for (auto& CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); + + if(CurrentInputType != EHoudiniInputType::Asset && CurrentInputType != EHoudiniInputType::World) + continue; + + TArray* ObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInputType); + if (!ObjectArray) + continue; + + for (auto& CurrentInputObject : (*ObjectArray)) + { + // Get the input HDA + UHoudiniAssetComponent* InputHAC = CurrentInputObject + ? Cast(CurrentInputObject->GetObject()) + : nullptr; + + if (!InputHAC) + continue; + + // If the input HDA needs to be instantiated, force him to instantiate + // if the input HDA is in any other state than None, we need to wait for him + // to finish whatever it's doing + if (InputHAC->GetAssetState() == EHoudiniAssetState::NeedInstantiation) + { + // Tell the input HAC to instantiate + InputHAC->SetAssetState(EHoudiniAssetState::PreInstantiation); + + // We need to wait + return true; + } + else if (InputHAC->GetAssetState() != EHoudiniAssetState::None) + { + // We need to wait + return true; + } + } + } + + return false; +} + +void +UHoudiniAssetComponent::BeginDestroy() +{ + if (CanDeleteHoudiniNodes()) + { + } + + // Gets called through UnRegisterHoudiniComponent(). + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + + // Unregister ourself so our houdini node can be deleted + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + Super::BeginDestroy(); +} + +void +UHoudiniAssetComponent::MarkAsNeedCook() +{ + // Force the asset state to NeedCook + //AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = true; + bRebuildRequested = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/trigger update + for (auto CurrentParam : Parameters) + { + if (!IsValid(CurrentParam)) + continue; + + // Do not trigger parameter update for Button/Button strip when recooking + // As we don't want to trigger the buttons + if (CurrentParam->IsA() || CurrentParam->IsA()) + continue; + + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(true); + } + + // We need to mark all of our editable curves as changed + for (auto Output : Outputs) + { + if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) + continue; + + for (auto& OutputObjectEntry : Output->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; + if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + continue; + + UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + continue; + + // This sets bHasChanged and bNeedsToTriggerUpdate + SplineComponent->MarkChanged(true); + } + } + + // We need to mark all our inputs as changed/trigger update + for (auto CurrentInput : Inputs) + { + if (!IsValid(CurrentInput)) + continue; + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(true); + CurrentInput->MarkDataUploadNeeded(true); + + // In addition to marking the input as changed/need update, we also need to make sure that any changes on the + // Unreal side have been recorded for the input before sending to Houdini. For that we also mark each input + // object as changed/need update and explicitly call the Update function on each input object. For example, for + // input actors this would recreate the Houdini input actor components from the actor's components, picking up + // any new components since the last call to Update. + TArray* InputObjectArray = CurrentInput->GetHoudiniInputObjectArray(CurrentInput->GetInputType()); + if (InputObjectArray && InputObjectArray->Num() > 0) + { + for (auto CurrentInputObject : *InputObjectArray) + { + if (!IsValid(CurrentInputObject)) + continue; + + UObject* const Object = CurrentInputObject->GetObject(); + if (IsValid(Object)) + CurrentInputObject->Update(Object); + + CurrentInputObject->MarkChanged(true); + CurrentInputObject->SetNeedsToTriggerUpdate(true); + CurrentInputObject->MarkTransformChanged(true); + } + } + } + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +void +UHoudiniAssetComponent::MarkAsNeedRebuild() +{ + // Invalidate the asset ID + //AssetId = -1; + + // Force the asset state to NeedRebuild + SetAssetState(EHoudiniAssetState::NeedRebuild); + AssetStateResult = EHoudiniAssetStateResult::None; + + // Reset some of the asset's flag + //AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = true; + bFullyLoaded = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/trigger update + for (auto CurrentParam : Parameters) + { + if (!IsValid(CurrentParam)) + continue; + + // Do not trigger parameter update for Button/Button strip when rebuilding + // As we don't want to trigger the buttons + if (CurrentParam->IsA() || CurrentParam->IsA()) + continue; + + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(true); + } + + // We need to mark all of our editable curves as changed + for (auto Output : Outputs) + { + if (!IsValid(Output) || Output->GetType() != EHoudiniOutputType::Curve || !Output->IsEditableNode()) + continue; + + for (auto& OutputObjectEntry : Output->GetOutputObjects()) + { + FHoudiniOutputObject& OutputObject = OutputObjectEntry.Value; + if (OutputObject.CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::HoudiniSpline) + continue; + + UHoudiniSplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); + if (!IsValid(SplineComponent)) + continue; + + // This sets bHasChanged and bNeedsToTriggerUpdate + SplineComponent->MarkChanged(true); + } + } + + // We need to mark all our inputs as changed/trigger update + for (auto CurrentInput : Inputs) + { + if (!IsValid(CurrentInput)) + continue; + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(true); + CurrentInput->MarkDataUploadNeeded(true); + } + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +// Marks the asset as needing to be instantiated +void +UHoudiniAssetComponent::MarkAsNeedInstantiation() +{ + // Invalidate the asset ID + AssetId = -1; + + if (Parameters.Num() <= 0 && Inputs.Num() <= 0 && Outputs.Num() <= 0) + { + // The asset has no parameters or inputs. + // This likely indicates it has never cooked/been instantiated. + // Set its state to NewHDA to force its instantiation + // so that we can have its parameters/input interface + SetAssetState(EHoudiniAssetState::NewHDA); + } + else + { + // The asset has cooked before since we have a parameter/input interface + // Set its state to need instantiation so that the asset is instantiated + // after being modified + SetAssetState(EHoudiniAssetState::NeedInstantiation); + } + + AssetStateResult = EHoudiniAssetStateResult::None; + + // Reset some of the asset's flag + AssetCookCount = 0; + bHasBeenLoaded = true; + bPendingDelete = false; + bRecookRequested = false; + bRebuildRequested = false; + bFullyLoaded = false; + + //bEditorPropertiesNeedFullUpdate = true; + + // We need to mark all our parameters as changed/not triggering update + for (auto CurrentParam : Parameters) + { + if (CurrentParam) + { + CurrentParam->MarkChanged(true); + CurrentParam->SetNeedsToTriggerUpdate(false); + } + } + + // We need to mark all our inputs as changed/not triggering update + for (auto CurrentInput : Inputs) + { + if (CurrentInput) + { + CurrentInput->MarkChanged(true); + CurrentInput->SetNeedsToTriggerUpdate(false); + CurrentInput->MarkDataUploadNeeded(true); + } + } + + /*if (!CanInstantiateAsset()) + { + AssetState = EHoudiniAssetState::None; + AssetStateResult = EHoudiniAssetStateResult::None; + }*/ + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); +} + +void UHoudiniAssetComponent::MarkAsBlueprintStructureModified() +{ + bBlueprintStructureModified = true; +} + +void UHoudiniAssetComponent::MarkAsBlueprintModified() +{ + bBlueprintModified = true; +} + +void +UHoudiniAssetComponent::PostLoad() +{ + Super::PostLoad(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + bool bAutomaticLegacyHDARebuild = HoudiniRuntimeSettings->bAutomaticLegacyHDARebuild; + + // Legacy serialization: either try to convert or skip depending the setting value + if (bEnableBackwardCompatibility && Version1CompatibilityHAC != nullptr) + { + // If we have deserialized legacy v1 data, attempt to convert it now + ConvertLegacyData(); + + if (bAutomaticLegacyHDARebuild) + MarkAsNeedRebuild(); + else + MarkAsNeedInstantiation(); + } + else + { + // Normal v2 objet, mark as need instantiation + MarkAsNeedInstantiation(); + } + + // Component has been loaded, not duplicated + bHasBeenDuplicated = false; + + // We need to register ourself + RegisterHoudiniComponent(this); + + // Register our PDG Asset link if we have any + + // !!! Do not update rendering while loading, do it when setting up the render state + // UpdateRenderingInformation(); +} + +void +UHoudiniAssetComponent::CreateRenderState_Concurrent(FRegisterComponentContext* Context) +{ + UpdateRenderingInformation(); + Super::CreateRenderState_Concurrent(Context); +} + +void +UHoudiniAssetComponent::PostEditImport() +{ + Super::PostEditImport(); + + MarkAsNeedInstantiation(); + + // Component has been duplicated, not loaded + // We do need the loaded flag to reapply parameters, inputs + // and properly update some of the output objects + bHasBeenDuplicated = true; + + //RemoveAllAttachedComponents(); + + AssetState = EHoudiniAssetState::PreInstantiation; + AssetStateResult = EHoudiniAssetStateResult::None; + + // TODO? + // REGISTER? +} + +void +UHoudiniAssetComponent::UpdatePostDuplicate() +{ + // TODO: + // - Keep the output objects/components (remove duplicatetransient on the output object uproperties) + // - Duplicate created objects (ie SM) and materials + // - Update the output components to use these instead + // This should remove the need for a cook on duplicate + + // For now, we simply clean some of the HAC's component manually + const TArray Children = GetAttachChildren(); + + for (auto & NextChild : Children) + { + if (!NextChild || NextChild->IsPendingKill()) + continue; + + USceneComponent * ComponentToRemove = nullptr; + if (NextChild->IsA()) + { + ComponentToRemove = NextChild; + } + else if (NextChild->IsA()) + { + ComponentToRemove = NextChild; + } + /* do not destroy attached duplicated editable curves, they are needed to restore editable curves + else if (NextChild->IsA()) + { + // Remove duplicated editable curve output's Houdini Spline Component, since they will be re-built at duplication. + UHoudiniSplineComponent * HoudiniSplineComponent = Cast(NextChild); + if (HoudiniSplineComponent && HoudiniSplineComponent->IsEditableOutputCurve()) + ComponentToRemove = NextChild; + } + */ + if (ComponentToRemove) + { + ComponentToRemove->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + ComponentToRemove->UnregisterComponent(); + ComponentToRemove->DestroyComponent(); + } + } + + // if there is an associated PDG asset link, call its UpdatePostDuplicate to cleanup references to + // to the original instance's PDG output actors + if (IsValid(PDGAssetLink)) + { + PDGAssetLink->UpdatePostDuplicate(); + } + + SetHasBeenDuplicated(false); +} + +bool UHoudiniAssetComponent::IsInputTypeSupported(EHoudiniInputType InType) const +{ + return true; +} + +bool UHoudiniAssetComponent::IsOutputTypeSupported(EHoudiniOutputType InType) const +{ + return true; +} + +bool +UHoudiniAssetComponent::IsPreview() const +{ + return bCachedIsPreview; +} + +bool UHoudiniAssetComponent::IsValidComponent() const +{ + return true; +} + +void UHoudiniAssetComponent::OnFullyLoaded() +{ + bFullyLoaded = true; +} + + +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); + } + */ +} + +void +UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + + if (CanDeleteHoudiniNodes()) + { + } + + // Unregister ourself so our houdini node can be deleted + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + HoudiniAsset = nullptr; + + // Clear Parameters + for (UHoudiniParameter*& CurrentParm : Parameters) + { + if (CurrentParm && !CurrentParm->IsPendingKill()) + { + CurrentParm->ConditionalBeginDestroy(); + } + else if (GetWorld() != NULL && GetWorld()->WorldType != EWorldType::PIE) + { + // TODO unneeded log? + // Avoid spamming that error when leaving PIE mode + HOUDINI_LOG_WARNING(TEXT("%s: null parameter when clearing"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } + + CurrentParm = nullptr; + } + + Parameters.Empty(); + + // Clear Inputs + for (UHoudiniInput*& CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + // Destroy connected Houdini asset. + CurrentInput->ConditionalBeginDestroy(); + CurrentInput = nullptr; + } + + Inputs.Empty(); + + // Clear Output + for (UHoudiniOutput*& CurrentOutput : Outputs) + { + if (!CurrentOutput || CurrentOutput->IsPendingKill()) + continue; + + if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) + continue; + + // Destroy all Houdini created socket actors. + TArray & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors(); + for (auto & CurCreatedActor : CurCreatedSocketActors) + { + if (!CurCreatedActor || CurCreatedActor->IsPendingKill()) + continue; + + CurCreatedActor->Destroy(); + } + CurCreatedSocketActors.Empty(); + + // Detach all Houdini attached socket actors + TArray & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors(); + for (auto & CurAttachedSocketActor : CurAttachedSocketActors) + { + if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill()) + continue; + + CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); + } + CurAttachedSocketActors.Empty(); + +#if WITH_EDITOR + // Clean up foliages instances + for (auto& CurrentOutputObject : CurrentOutput->GetOutputObjects()) + { + // Foliage instancers store a HISMC in the components + UHierarchicalInstancedStaticMeshComponent* FoliageHISMC = Cast(CurrentOutputObject.Value.OutputComponent); + if (!FoliageHISMC) + continue; + + UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh(); + if (!FoliageSM || FoliageSM->IsPendingKill()) + continue; + + // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, + // if it is not, then we are just a "regular" HISMC + AInstancedFoliageActor* InstancedFoliageActor = Cast(FoliageHISMC->GetOwner()); + if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + continue; + + UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); + if (!FoliageType || FoliageType->IsPendingKill()) + continue; + + if (IsInGameThread() && IsGarbageCollecting()) + { + // TODO: ?? + // Calling DeleteInstancesForComponent during GC will cause unreal to crash... + HOUDINI_LOG_WARNING(TEXT("%s: Unable to clear foliage instances because of GC"), GetOwner() ? *(GetOwner()->GetName()) : *GetName()); + } + else + { + // Clean up the instances generated for that component + InstancedFoliageActor->DeleteInstancesForComponent(this, FoliageType); + } + + if (FoliageHISMC->GetInstanceCount() > 0) + { + // If the component still has instances left after the cleanup, + // make sure that we dont delete it, as the leftover instances are likely hand-placed + CurrentOutputObject.Value.OutputComponent = nullptr; + } + else + { + // Remove the foliage type if it doesn't have any more instances + InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); + } + } +#endif + + CurrentOutput->Clear(); + // Destroy connected Houdini asset. + CurrentOutput->ConditionalBeginDestroy(); + CurrentOutput = nullptr; + } + + Outputs.Empty(); + + //FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(AssetId, true); + // Unregister ourself so our houdini node can be delete. + FHoudiniEngineRuntime::Get().UnRegisterHoudiniComponent(this); + + // Clear the static mesh bake timer + ClearRefineMeshesTimer(); + + + // Clear all TOP data and temporary geo/objects from the PDG asset link (if valid) + if (IsValid(PDGAssetLink)) + { +#if WITH_EDITOR + const UWorld* const World = GetWorld(); + if (IsValid(World)) + { + // Only do this for editor worlds, only interactively (not during engine shutdown or garbage collection) + if (World->WorldType == EWorldType::Editor && GIsRunning && !GIsGarbageCollecting) + { + // In case we are recording a transaction (undo, for example) notify that the object will be + // modified. + PDGAssetLink->Modify(); + PDGAssetLink->ClearAllTOPData(); + } + } +#endif + } + + Super::OnComponentDestroyed(bDestroyingHierarchy); +} + +void UHoudiniAssetComponent::RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent) +{ + // Registration of this component is wrapped in this virtual function to allow + // derived classed to override this behaviour. + FHoudiniEngineRuntime::Get().RegisterHoudiniComponent(InComponent); +} + +void +UHoudiniAssetComponent::OnRegister() +{ + Super::OnRegister(); + + // NOTE: Wait until HoudiniEngineTick() before deciding to mark this object as fully loaded + // since preview components need to wait for component templates to finish their initialization + // before being able to perform state transfers. + + /* + // 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(); + } + } + */ + + // Let TickInitialization() take care of manipulating the bFullyLoaded state. + //bFullyLoaded = true; + + //// If we're constructing editable components in the SCS editor, set the component instance corresponding to this node for editing purposes + // + //USimpleConstructionScript* SCS = GetSCS(); + //if (SCS == nullptr) + //{ + // bFullyLoaded = true; + //} + //else + //{ + // if(SCS->IsConstructingEditorComponents()) + // { + // // We're not fully loaded yet. We're expecting + // } + // else + // { + // bFullyLoaded = true; + // } + //} + +} + +UHoudiniParameter* +UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) +{ + if (!InOtherParam || InOtherParam->IsPendingKill()) + return nullptr; + + for (auto CurrentParam : Parameters) + { + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + if (CurrentParam->Matches(*InOtherParam)) + return CurrentParam; + } + + return nullptr; +} + +UHoudiniInput* +UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) +{ + if (!InOtherInput || InOtherInput->IsPendingKill()) + return nullptr; + + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->Matches(*InOtherInput)) + return CurrentInput; + } + + return nullptr; +} + +UHoudiniHandleComponent* +UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle) +{ + if (!InOtherHandle || InOtherHandle->IsPendingKill()) + return nullptr; + + for (auto CurrentHandle : HandleComponents) + { + if (!CurrentHandle || CurrentHandle->IsPendingKill()) + continue; + + if (CurrentHandle->Matches(*InOtherHandle)) + return CurrentHandle; + } + + return nullptr; +} + +UHoudiniParameter* +UHoudiniAssetComponent::FindParameterByName(const FString& InParamName) +{ + for (auto CurrentParam : Parameters) + { + if (!CurrentParam || CurrentParam->IsPendingKill()) + continue; + + if (CurrentParam->GetParameterName().Equals(InParamName)) + return CurrentParam; + } + + return nullptr; +} + + +void +UHoudiniAssetComponent::OnChildAttached(USceneComponent* ChildComponent) +{ + Super::OnChildAttached(ChildComponent); + + // ... Do corresponding things for other houdini component types. + // ... +} + + +void +UHoudiniAssetComponent::OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) +{ + Super::OnUpdateTransform(UpdateTransformFlags, Teleport); + + SetHasComponentTransformChanged(true); +} + +void UHoudiniAssetComponent::HoudiniEngineTick() +{ + if (!IsFullyLoaded()) + { + OnFullyLoaded(); + } +} + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + if (!Property) + return; + + FName PropertyName = Property->GetFName(); + + // Changing the Houdini Asset? + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, HoudiniAsset)) + { + OnHoudiniAssetChanged(); + } + else if (PropertyName == GetRelativeLocationPropertyName() + || PropertyName == GetRelativeRotationPropertyName() + || PropertyName == GetRelativeScale3DPropertyName()) + { + SetHasComponentTransformChanged(true); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bOverrideGlobalProxyStaticMeshSettings) + || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bEnableProxyStaticMeshRefinementByTimerOverride) + || PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, ProxyMeshAutoRefineTimeoutSecondsOverride)) + { + ClearRefineMeshesTimer(); + // Reset the timer + // SetRefineMeshesTimer will check the relevant settings and only set the timer if enabled via settings + SetRefineMeshesTimer(); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, 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); + } + } + else if (PropertyName == TEXT("bVisible")) + { + // Visibility has changed, propagate it to children. + SetVisibility(IsVisible(), true); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniAssetComponent, bHiddenInGame)) + { + // Visibility has changed, propagate it to children. + SetHiddenInGame(bHiddenInGame, true); + } + else + { + // TODO: + // Propagate properties (mobility/visibility etc.. to children components) + // Look in v1 for: if (Property->HasMetaData(TEXT("Category"))) {} and HOUDINI_UPDATE_ALL_CHILD_COMPONENTS + } + + if (Property->HasMetaData(TEXT("Category"))) + { + const FString & Category = Property->GetMetaData(TEXT("Category")); + static const FString CategoryHoudiniGeneratedStaticMeshSettings = TEXT("HoudiniMeshGeneration"); + 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 (UHoudiniOutput* CurOutput : Outputs) + { + if (!CurOutput) + continue; + + for (auto& Pair : CurOutput->GetOutputObjects()) + { + UStaticMesh* StaticMesh = Cast(Pair.Value.OutputObject); + if (!StaticMesh || StaticMesh->IsPendingKill()) + continue; + + SetStaticMeshGenerationProperties(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 ReregisterComponents; + { + TArray 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("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); + } + } + } +} +#endif + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::PostEditUndo() +{ + Super::PostEditUndo(); + + if (!IsPendingKill()) + { + // Make sure we are registered with the HER singleton + // We could be undoing a HoudiniActor delete + if (!FHoudiniEngineRuntime::Get().IsComponentRegistered(this)) + { + MarkAsNeedInstantiation(); + + // Component has been loaded, not duplicated + bHasBeenDuplicated = false; + + RegisterHoudiniComponent(this); + } + } +} + +#endif + + +#if WITH_EDITOR +void +UHoudiniAssetComponent::OnActorMoved(AActor* Actor) +{ + if (GetOwner() != Actor) + return; + + SetHasComponentTransformChanged(true); +} +#endif + +void +UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged) +{ + // Only update the value if we're fully loaded + // This avoid triggering a recook when loading a level + if(bFullyLoaded) + bHasComponentTransformChanged = InHasChanged; +} + +void +UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) +{ + // Check the object validity + if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + return; + + // If it is the same object, do nothing. + if (InPDGAssetLink == PDGAssetLink) + return; + + PDGAssetLink = InPDGAssetLink; +} + + +FBoxSphereBounds +UHoudiniAssetComponent::CalcBounds(const FTransform & LocalToWorld) const +{ + FBoxSphereBounds LocalBounds; + FBox BoundingBox = GetAssetBounds(nullptr, false); + 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; +} + + +FBox +UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const +{ + FBox BoxBounds(ForceInitToZero); + + // Query the bounds for all output objects + for (auto & CurOutput : Outputs) + { + if (!CurOutput || CurOutput->IsPendingKill()) + continue; + + BoxBounds += CurOutput->GetBounds(); + } + + // Query the bounds for all our inputs + for (auto & CurInput : Inputs) + { + if (!CurInput || CurInput->IsPendingKill()) + continue; + + BoxBounds += CurInput->GetBounds(); + } + + // Query the bounds for all input parameters + for (auto & CurParam : Parameters) + { + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (CurParam->GetParameterType() != EHoudiniParameterType::Input) + continue; + + UHoudiniParameterOperatorPath* InputParam = Cast(CurParam); + if (!CurParam || CurParam->IsPendingKill()) + continue; + + if (!InputParam->HoudiniInput.IsValid()) + continue; + + BoxBounds += InputParam->HoudiniInput.Get()->GetBounds(); + } + + // Query the bounds for all our Houdini handles + for (auto & CurHandleComp : HandleComponents) + { + if (!CurHandleComp || CurHandleComp->IsPendingKill()) + continue; + + BoxBounds += CurHandleComp->GetBounds(); + } + + // Also scan all our decendants for SMC bounds not just top-level children + // ( split mesh instances' mesh bounds were not gathered proiperly ) + TArray 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(pChild)) + { + if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + continue; + + FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); + if (StaticMeshBounds.IsValid) + BoxBounds += StaticMeshBounds; + } + } + + // If nothing was found, init with the asset's location + if (BoxBounds.GetVolume() == 0.0f) + BoxBounds += GetComponentLocation(); + + return BoxBounds; +} + +void +UHoudiniAssetComponent::ClearRefineMeshesTimer() +{ + UWorld *World = GetWorld(); + if (!World) + { + //HOUDINI_LOG_ERROR(TEXT("Cannot ClearRefineMeshesTimer, World is nullptr!")); + return; + } + + World->GetTimerManager().ClearTimer(RefineMeshesTimer); +} + +void +UHoudiniAssetComponent::SetRefineMeshesTimer() +{ + UWorld *World = GetWorld(); + if (!World) + { + HOUDINI_LOG_ERROR(TEXT("Cannot SetRefineMeshesTimer, World is nullptr!")); + return; + } + + // Check if timer-based proxy mesh refinement is enable for this component + const bool bEnableTimer = IsProxyStaticMeshRefinementByTimerEnabled(); + const float TimeSeconds = GetProxyMeshAutoRefineTimeoutSeconds(); + if (bEnableTimer) + { + World->GetTimerManager().SetTimer(RefineMeshesTimer, this, &UHoudiniAssetComponent::OnRefineMeshesTimerFired, 1.0f, false, TimeSeconds); + } + else + { + World->GetTimerManager().ClearTimer(RefineMeshesTimer); + } +} + +void +UHoudiniAssetComponent::OnRefineMeshesTimerFired() +{ + HOUDINI_LOG_MESSAGE(TEXT("UHoudiniAssetComponent::OnRefineMeshesTimerFired()")); + if (OnRefineMeshesTimerDelegate.IsBound()) + { + OnRefineMeshesTimerDelegate.Broadcast(this); + } +} + +bool +UHoudiniAssetComponent::HasAnyCurrentProxyOutput() const +{ + for (const UHoudiniOutput *Output : Outputs) + { + if (Output->HasAnyCurrentProxy()) + { + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasAnyProxyOutput() const +{ + for (const UHoudiniOutput *Output : Outputs) + { + if (Output->HasAnyProxy()) + { + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasAnyOutputComponent() const +{ + for (UHoudiniOutput *Output : Outputs) + { + for(auto& CurrentOutputObject : Output->GetOutputObjects()) + { + if(CurrentOutputObject.Value.OutputComponent) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::HasOutputObject(UObject* InOutputObjectToFind) const +{ + for (const auto& CurOutput : Outputs) + { + for (const auto& CurOutputObject : CurOutput->GetOutputObjects()) + { + if (CurOutputObject.Value.OutputObject == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.OutputComponent == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.ProxyObject == InOutputObjectToFind) + return true; + else if (CurOutputObject.Value.ProxyComponent == InOutputObjectToFind) + return true; + } + } + + return false; +} + +bool +UHoudiniAssetComponent::IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const +{ + // Get the state of the asset and check if it is pre-cook, cooked, pending delete/rebuild or invalid + bOutNeedsRebuildOrDelete = false; + bOutInvalidState = false; + switch (AssetState) + { + case EHoudiniAssetState::NewHDA: + case EHoudiniAssetState::NeedInstantiation: + case EHoudiniAssetState::PreInstantiation: + case EHoudiniAssetState::Instantiating: + case EHoudiniAssetState::PreCook: + case EHoudiniAssetState::Cooking: + case EHoudiniAssetState::PostCook: + case EHoudiniAssetState::PreProcess: + case EHoudiniAssetState::Processing: + return false; + break; + case EHoudiniAssetState::None: + return true; + break; + case EHoudiniAssetState::NeedRebuild: + case EHoudiniAssetState::NeedDelete: + case EHoudiniAssetState::Deleting: + bOutNeedsRebuildOrDelete = true; + break; + default: + bOutInvalidState = true; + break; + } + + return false; +} + +void +UHoudiniAssetComponent::SetInputPresets(const TMap& InPresets) +{ + // Set the input preset for this HAC +#if WITH_EDITOR + InputPresets = InPresets; +#endif +} + + +void +UHoudiniAssetComponent::ApplyInputPresets() +{ + if (InputPresets.Num() <= 0) + return; + +#if WITH_EDITOR + // Ignore inputs that have been preset to curve + TArray InputArray; + for (auto CurrentInput : Inputs) + { + if (!CurrentInput || CurrentInput->IsPendingKill()) + continue; + + if (CurrentInput->GetInputType() != EHoudiniInputType::Curve) + InputArray.Add(CurrentInput); + } + + // Try to apply the supplied Object to the Input + for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset) + { + UObject * Object = IterToolPreset.Key(); + if (!Object || Object->IsPendingKill()) + continue; + + int32 InputNumber = IterToolPreset.Value(); + if (!InputArray.IsValidIndex(InputNumber)) + continue; + + // If the object is a landscape, add a new landscape input + if (Object->IsA()) + { + // selecting a landscape + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Landscape); + if (InsertNum == 0) + { + // Landscape inputs only support one object! + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Landscape, InsertNum, Object); + } + } + + // If the object is an actor, add a new world input + if (Object->IsA()) + { + // selecting an actor + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::World); + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::World, InsertNum, Object); + } + + // If the object is a static mesh, add a new geometry input (TODO: or BP ? ) + if (Object->IsA()) + { + // selecting a Staticn Mesh + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Geometry, InsertNum, Object); + } + + if (Object->IsA()) + { + // selecting a Houdini Asset + int32 InsertNum = InputArray[InputNumber]->GetNumberOfInputObjects(EHoudiniInputType::Asset); + if (InsertNum == 0) + { + // Assert inputs only support one object! + InputArray[InputNumber]->SetInputObjectAt(EHoudiniInputType::Asset, InsertNum, Object); + } + } + } + + // The input objects have been set, now change the input type + bool bBPStructureModified = false; + for (auto CurrentInput : Inputs) + { + int32 NumGeo = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry); + int32 NumAsset = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Asset); + int32 NumWorld = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::World); + int32 NumLandscape = CurrentInput->GetNumberOfInputObjects(EHoudiniInputType::Landscape); + + EHoudiniInputType NewInputType = EHoudiniInputType::Invalid; + if (NumLandscape > 0 && NumLandscape >= NumGeo && NumLandscape >= NumAsset && NumLandscape >= NumWorld) + NewInputType = EHoudiniInputType::Landscape; + else if (NumWorld > 0 && NumWorld >= NumGeo && NumWorld >= NumAsset && NumWorld >= NumLandscape) + NewInputType = EHoudiniInputType::World; + else if (NumAsset > 0 && NumAsset >= NumGeo && NumAsset >= NumWorld && NumAsset >= NumLandscape) + NewInputType = EHoudiniInputType::Asset; + else if (NumGeo > 0 && NumGeo >= NumAsset && NumGeo >= NumWorld && NumGeo >= NumLandscape) + NewInputType = EHoudiniInputType::Geometry; + + if (NewInputType == EHoudiniInputType::Invalid) + continue; + + // Change the input type, unless if it was preset to a different type and we have object for the preset type + if (CurrentInput->GetInputType() == EHoudiniInputType::Geometry && NewInputType != EHoudiniInputType::Geometry) + { + CurrentInput->SetInputType(NewInputType, bBPStructureModified); + } + else + { + // Input type was preset, only change if that type is empty + if(CurrentInput->GetNumberOfInputObjects() <= 0) + CurrentInput->SetInputType(NewInputType, bBPStructureModified); + } + } + if (bBPStructureModified) + { + MarkAsBlueprintStructureModified(); + } +#endif + + // Discard the tool presets after their first setup + InputPresets.Empty(); +} + + +bool +UHoudiniAssetComponent::IsComponentValid() const +{ + if (!IsValidLowLevel()) + return false; + + if (IsTemplate()) + return false; + + if (IsPendingKillOrUnreachable()) + return false; + + if (!GetOuter()) //|| !GetOuter()->GetLevel() ) + return false; + + return true; +} + +bool +UHoudiniAssetComponent::IsInstantiatingOrCooking() const +{ + return HapiGUID.IsValid(); +} + + +void +UHoudiniAssetComponent::SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const +{ +#if WITH_EDITOR + if (!InStaticMesh) + return; + + // Make sure static mesh has a new lighting guid. + InStaticMesh->SetLightingGuid(FGuid::NewGuid()); + InStaticMesh->LODGroup = NAME_None; + + // Set resolution of lightmap. + InStaticMesh->SetLightMapResolution(StaticMeshGenerationProperties.GeneratedLightMapResolution); + + const FStaticMeshRenderData* InRenderData = InStaticMesh->GetRenderData(); + // Set the global light map coordinate index if it looks valid + if (InRenderData && InRenderData->LODResources.Num() > 0) + { + int32 NumUVs = InRenderData->LODResources[0].GetNumTexCoords(); + if (NumUVs > StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex) + { + InStaticMesh->SetLightMapCoordinateIndex(StaticMeshGenerationProperties.GeneratedLightMapCoordinateIndex); + } + } + + // TODO + // Set method for LOD texture factor computation. + //InStaticMesh->bUseMaximumStreamingTexelRatio = StaticMeshGenerationProperties.bGeneratedUseMaximumStreamingTexelRatio; + // Set distance where textures using UV 0 are streamed in/out. - GOES ON COMPONENT + // InStaticMesh->StreamingDistanceMultiplier = StaticMeshGenerationProperties.GeneratedStreamingDistanceMultiplier; + + // Add user data. + for (int32 AssetUserDataIdx = 0; AssetUserDataIdx < StaticMeshGenerationProperties.GeneratedAssetUserData.Num(); AssetUserDataIdx++) + InStaticMesh->AddAssetUserData(StaticMeshGenerationProperties.GeneratedAssetUserData[AssetUserDataIdx]); + + // + if (!InStaticMesh->GetBodySetup()) + InStaticMesh->CreateBodySetup(); + + UBodySetup* BodySetup = InStaticMesh->GetBodySetup(); + if (!BodySetup) + return; + + // Set flag whether physics triangle mesh will use double sided faces when doing scene queries. + BodySetup->bDoubleSidedGeometry = StaticMeshGenerationProperties.bGeneratedDoubleSidedGeometry; + + // Assign physical material for simple collision. + BodySetup->PhysMaterial = StaticMeshGenerationProperties.GeneratedPhysMaterial; + + BodySetup->DefaultInstance.CopyBodyInstancePropertiesFrom(&StaticMeshGenerationProperties.DefaultBodyInstance); + + // Assign collision trace behavior. + BodySetup->CollisionTraceFlag = StaticMeshGenerationProperties.GeneratedCollisionTraceFlag; + + // Assign walkable slope behavior. + BodySetup->WalkableSlopeOverride = StaticMeshGenerationProperties.GeneratedWalkableSlopeOverride; + + // We want to use all of geometry for collision detection purposes. + BodySetup->bMeshCollideAll = true; + +#endif +} + + +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 LocalAttachChildren; + GetChildrenComponents(true, LocalAttachChildren); + for (TArray::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) + { + USceneComponent * SceneComponent = *Iter; + if (IsValid(SceneComponent)) + SceneComponent->RecreatePhysicsState(); + } + + // !!! Do not call UpdateBounds() here as this could cause + // a loading loop in post load on game builds! +} + + +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(&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::SetAssetState(EHoudiniAssetState InNewState) +{ + const EHoudiniAssetState OldState = AssetState; + AssetState = InNewState; + + HandleOnHoudiniAssetStateChange(this, OldState, InNewState); +} + +void +UHoudiniAssetComponent::HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) +{ + IHoudiniAssetStateEvents::HandleOnHoudiniAssetStateChange(InHoudiniAssetContext, InFromState, InToState); + + if (InFromState == InToState) + return; + + if (this != InHoudiniAssetContext) + return; + + FOnAssetStateChangeDelegate& StateChangeDelegate = GetOnAssetStateChangeDelegate(); + if (StateChangeDelegate.IsBound()) + StateChangeDelegate.Broadcast(this, InFromState, InToState); + + if (InToState == EHoudiniAssetState::PostCook) + { + HandleOnPostCook(); + } + +} + +void +UHoudiniAssetComponent::HandleOnPostCook() +{ + if (OnPostCookDelegate.IsBound()) + OnPostCookDelegate.Broadcast(this, bLastCookSuccess); +} + +void +UHoudiniAssetComponent::HandleOnPostBake(bool bInSuccess) +{ + if (OnPostBakeDelegate.IsBound()) + OnPostBakeDelegate.Broadcast(this, bInSuccess); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h index 7f3940995..ba1be9cee 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h @@ -1,770 +1,773 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniOutput.h" -#include "HoudiniInputTypes.h" -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniAssetStateTypes.h" -#include "IHoudiniAssetStateEvents.h" - -#include "Engine/EngineTypes.h" -#include "Components/PrimitiveComponent.h" -#include "Components/SceneComponent.h" - -#include "HoudiniAssetComponent.generated.h" - -class UHoudiniAsset; -class UHoudiniParameter; -class UHoudiniInput; -class UHoudiniOutput; -class UHoudiniHandleComponent; -class UHoudiniPDGAssetLink; -class UHoudiniAssetComponent_V1; - -UENUM() -enum class EHoudiniStaticMeshMethod : uint8 -{ - // Static Meshes will be generated by using Raw Meshes. - RawMesh, - // Static Meshes will be generated by using Mesh Descriptions. - FMeshDescription, - // Always build Houdini Proxy Meshes (dev) - UHoudiniStaticMesh, -}; - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EHoudiniEngineBakeOption : uint8 -{ - ToActor, - ToBlueprint, - ToFoliage, - ToWorldOutliner, -}; -#endif - -class UHoudiniAssetComponent; - -DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetEvent, UHoudiniAsset*); -DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetComponentEvent, UHoudiniAssetComponent*) - -UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility), editinlinenew) -class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent, public IHoudiniAssetStateEvents -{ - GENERATED_UCLASS_BODY() - - // Declare translators as friend so they can easily directly modify - // Inputs, outputs and parameters - friend class FHoudiniEngineManager; - friend struct FHoudiniOutputTranslator; - friend struct FHoudiniInputTranslator; - friend struct FHoudiniSplineTranslator; - friend struct FHoudiniParameterTranslator; - friend struct FHoudiniPDGManager; - friend struct FHoudiniHandleTranslator; - -#if WITH_EDITORONLY_DATA - friend class FHoudiniAssetComponentDetails; -#endif - -public: - - // Declare the delegate that is broadcast when RefineMeshesTimer fires - DECLARE_MULTICAST_DELEGATE_OneParam(FOnRefineMeshesTimerDelegate, UHoudiniAssetComponent*); - DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPostCookBakeDelegate, UHoudiniAssetComponent*); - // Delegate for when EHoudiniAssetState changes from InFromState to InToState on a Houdini Asset Component (InHAC). - DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnAssetStateChangeDelegate, UHoudiniAssetComponent*, const EHoudiniAssetState, const EHoudiniAssetState); - DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostCookDelegate, UHoudiniAssetComponent*, bool); - DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostBakeDelegate, UHoudiniAssetComponent*, bool); - - virtual ~UHoudiniAssetComponent(); - - virtual void Serialize(FArchive & Ar) override; - - virtual bool ConvertLegacyData(); - - // Called after the C++ constructor and after the properties have been initialized, including those loaded from config. - // This is called before any serialization or other setup has happened. - virtual void PostInitProperties() override; - - // Returns the Owner actor / HAC name - FString GetDisplayName() const; - - // Indicates if the HAC needs to be updated - bool NeedUpdate() const; - - // Indicates if the HAC's transform needs to be updated - bool NeedTransformUpdate() const { return (bHasComponentTransformChanged && bUploadTransformsToHoudiniEngine); }; - - // Indicates if any of the HAC's output components needs to be updated (no recook needed) - bool NeedOutputUpdate() const; - - // Check whether any inputs / outputs / parameters have made blueprint modifications. - bool NeedBlueprintStructureUpdate() const; - bool NeedBlueprintUpdate() const; - - // Try to find one of our parameter that matches another (name, type, size and enabled) - UHoudiniParameter* FindMatchingParameter(UHoudiniParameter* InOtherParam); - - // Try to find one of our input that matches another one (name, isobjpath, index / parmId) - UHoudiniInput* FindMatchingInput(UHoudiniInput* InOtherInput); - - // Try to find one of our handle that matches another one (name and handle type) - UHoudiniHandleComponent* FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle); - - // Finds a parameter by name - UHoudiniParameter* FindParameterByName(const FString& InParamName); - - // Returns True if the component has at least one mesh output of class U - template - bool HasMeshOutputObjectOfClass() const; - - // Returns True if the component has at least one mesh output with a current proxy - bool HasAnyCurrentProxyOutput() const; - - // Returns True if the component has at least one proxy mesh output (not necessarily current/displayed) - bool HasAnyProxyOutput() const; - - // Returns True if the component has at least one non-proxy output component amongst its outputs - bool HasAnyOutputComponent() const; - - // Returns true if the component has InOutputObjectToFind in its output object - bool HasOutputObject(UObject* InOutputObjectToFind) const; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - UHoudiniAsset * GetHoudiniAsset() const; - int32 GetAssetId() const { return AssetId; }; - EHoudiniAssetState GetAssetState() const { return AssetState; }; - FString GetAssetStateAsString() const { return FHoudiniEngineRuntimeUtils::EnumToString(TEXT("EHoudiniAssetState"), GetAssetState()); }; - EHoudiniAssetStateResult GetAssetStateResult() const { return AssetStateResult; }; - FGuid GetHapiGUID() const { return HapiGUID; }; - FString GetHapiAssetName() const { return HapiAssetName; }; - FGuid GetComponentGUID() const { return ComponentGUID; }; - - int32 GetNumInputs() const { return Inputs.Num(); }; - int32 GetNumOutputs() const { return Outputs.Num(); }; - int32 GetNumParameters() const { return Parameters.Num(); }; - int32 GetNumHandles() const { return HandleComponents.Num(); }; - - UHoudiniInput* GetInputAt(const int32& Idx) { return Inputs.IsValidIndex(Idx) ? Inputs[Idx] : nullptr; }; - UHoudiniOutput* GetOutputAt(const int32& Idx) { return Outputs.IsValidIndex(Idx) ? Outputs[Idx] : nullptr;}; - UHoudiniParameter* GetParameterAt(const int32& Idx) { return Parameters.IsValidIndex(Idx) ? Parameters[Idx] : nullptr;}; - UHoudiniHandleComponent* GetHandleComponentAt(const int32& Idx) { return HandleComponents.IsValidIndex(Idx) ? HandleComponents[Idx] : nullptr; }; - - void GetOutputs(TArray& OutOutputs) const; - - TArray& GetBakedOutputs() { return BakedOutputs; } - const TArray& GetBakedOutputs() const { return BakedOutputs; } - - /* - TArray& GetParameters() { return Parameters; }; - TArray& GetInputs() { return Inputs; }; - TArray& GetOutputs() { return Outputs; }; - */ - - bool IsCookingEnabled() const { return bEnableCooking; }; - bool HasBeenLoaded() const { return bHasBeenLoaded; }; - bool HasBeenDuplicated() const { return bHasBeenDuplicated; }; - bool HasRecookBeenRequested() const { return bRecookRequested; }; - bool HasRebuildBeenRequested() const { return bRebuildRequested; }; - - //bool GetEditorPropertiesNeedFullUpdate() const { return bEditorPropertiesNeedFullUpdate; }; - - int32 GetAssetCookCount() const { return AssetCookCount; }; - - bool IsFullyLoaded() const { return bFullyLoaded; }; - - UHoudiniPDGAssetLink * GetPDGAssetLink() const { return PDGAssetLink; }; - - virtual bool IsProxyStaticMeshEnabled() const; - bool IsProxyStaticMeshRefinementByTimerEnabled() const; - float GetProxyMeshAutoRefineTimeoutSeconds() const; - bool IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const; - bool IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const; - // If true, then the next cook should not build proxy meshes, regardless of global or override settings, - // but should instead directly build UStaticMesh - bool HasNoProxyMeshNextCookBeenRequested() const { return bNoProxyMeshNextCookRequested; } - // Returns true if the asset state indicates that it has been cooked in this session, false otherwise. - bool IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const; - // Returns true if the asset should be bake after the next cook - bool IsBakeAfterNextCookEnabled() const { return bBakeAfterNextCook; } - - FOnPostCookDelegate& GetOnPostCookDelegate() { return OnPostCookDelegate; } - FOnPostCookBakeDelegate& GetOnPostCookBakeDelegate() { return OnPostCookBakeDelegate; } - FOnPostBakeDelegate& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } - - FOnAssetStateChangeDelegate& GetOnAssetStateChangeDelegate() { return OnAssetStateChangeDelegate; } - - // Derived blueprint based components will check whether the template - // component contains updates that needs to processed. - bool NeedUpdateParameters() const; - bool NeedUpdateInputs() const; - - // Returns true if the component has any previous baked output recorded in its outputs - bool HasPreviousBakeOutput() const; - - // Returns true if the last cook of the HDA was successful - bool WasLastCookSuccessful() const { return bLastCookSuccess; } - - // Returns true if a parameter definition update (excluding values) is needed. - bool IsParameterDefinitionUpdateNeeded() const { return bParameterDefinitionUpdateNeeded; } - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - //void SetAssetId(const int& InAssetId); - //void SetAssetState(const EHoudiniAssetState& InAssetState) { AssetState = InAssetState; }; - //void SetAssetStateResult(const EHoudiniAssetStateResult& InAssetStateResult) { AssetStateResult = InAssetStateResult; }; - - //void SetHapiGUID(const FGuid& InGUID) { HapiGUID = InGUID; }; - //void SetComponentGUID(const FGuid& InGUID) { ComponentGUID = InGUID; }; - - //UFUNCTION(BlueprintSetter) - virtual void SetHoudiniAsset(UHoudiniAsset * NewHoudiniAsset); - - void SetCookingEnabled(const bool& bInCookingEnabled) { bEnableCooking = bInCookingEnabled; }; - - void SetHasBeenLoaded(const bool& InLoaded) { bHasBeenLoaded = InLoaded; }; - - void SetHasBeenDuplicated(const bool& InDuplicated) { bHasBeenDuplicated = InDuplicated; }; - - //void SetEditorPropertiesNeedFullUpdate(const bool& InUpdate) { bEditorPropertiesNeedFullUpdate = InUpdate; }; - - // Marks the assets as needing a recook - void MarkAsNeedCook(); - // Marks the assets as needing a full rebuild - void MarkAsNeedRebuild(); - // Marks the asset as needing to be instantiated - void MarkAsNeedInstantiation(); - // The blueprint has been structurally modified - void MarkAsBlueprintStructureModified(); - // The blueprint has been modified but not structurally changed. - void MarkAsBlueprintModified(); - - // - void SetAssetCookCount(const int32& InCount) { AssetCookCount = InCount; }; - // - void SetRecookRequested(const bool& InRecook) { bRecookRequested = InRecook; }; - // - void SetRebuildRequested(const bool& InRebuild) { bRebuildRequested = InRebuild; }; - // - void SetHasComponentTransformChanged(const bool& InHasChanged); - - // Set to True to force the next cook to not build a proxy mesh (regardless of global or override settings) and - // instead build a UStaticMesh directly (if applicable for the output type). - void SetNoProxyMeshNextCookRequested(bool bInNoProxyMeshNextCookRequested) { bNoProxyMeshNextCookRequested = bInNoProxyMeshNextCookRequested; } - - // Set to True to force the next cook to bake the asset after the cook completes. - void SetBakeAfterNextCookEnabled(bool bInEnabled) { bBakeAfterNextCook = bInEnabled; } - - // - void SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink); - // - virtual void OnHoudiniAssetChanged(); - - // - void AddDownstreamHoudiniAsset(UHoudiniAssetComponent* InDownstreamAsset) { DownstreamHoudiniAssets.Add(InDownstreamAsset); }; - // - void RemoveDownstreamHoudiniAsset(UHoudiniAssetComponent* InRemoveDownstreamAsset) { DownstreamHoudiniAssets.Remove(InRemoveDownstreamAsset); }; - // - void ClearDownstreamHoudiniAsset() { DownstreamHoudiniAssets.Empty(); }; - // - bool NotifyCookedToDownstreamAssets(); - // - bool NeedsToWaitForInputHoudiniAssets(); - - // Clear/disable the RefineMeshesTimer. - void ClearRefineMeshesTimer(); - - // Re-set the RefineMeshesTimer to its default value. - void SetRefineMeshesTimer(); - - virtual void OnRefineMeshesTimerFired(); - - // Called by RefineMeshesTimer when the timer is triggered. - // Checks for any UHoudiniStaticMesh in Outputs and bakes UStaticMesh for them via FHoudiniMeshTranslator. - FOnRefineMeshesTimerDelegate& GetOnRefineMeshesTimerDelegate() { return OnRefineMeshesTimerDelegate; } - - // Returns true if the asset is valid for cook/bake - virtual bool IsComponentValid() const; - // Return false if this component has no cooking or instantiation in progress. - bool IsInstantiatingOrCooking() const; - - // HoudiniEngineTick will be called by HoudiniEngineManager::Tick() - virtual void HoudiniEngineTick(); - -#if WITH_EDITOR - // This alternate version of PostEditChange is called when properties inside structs are modified. The property that was actually modified - // is located at the tail of the list. The head of the list of the FStructProperty member variable that contains the property that was modified. - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; - - //Called after applying a transaction to the object. Default implementation simply calls PostEditChange. - virtual void PostEditUndo() override; - - // Whether this component is currently open in a Blueprint editor. This - // method is overridden by HoudiniAssetBlueprintComponent. - virtual bool HasOpenEditor() const { return false; }; - -#endif - - void SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const; - - virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent); - - virtual void OnRegister() override; - - // USceneComponent methods. - virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override; - virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; - - FBox GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const; - - // Set this component's input presets - void SetInputPresets(const TMap& InPresets); - // Apply the preset input for HoudiniTools - void ApplyInputPresets(); - - // return the cached component template, if available. - virtual UHoudiniAssetComponent* GetCachedTemplate() const { return nullptr; } - - virtual FPrimitiveSceneProxy* CreateSceneProxy() override; - - //------------------------------------------------------------------------------------------------ - // Supported Features - //------------------------------------------------------------------------------------------------ - - // Whether or not this component should be able to delete the Houdini nodes - // that correspond to the HoudiniAsset when being deregistered. - virtual bool CanDeleteHoudiniNodes() const { return true; } - - virtual bool IsInputTypeSupported(EHoudiniInputType InType) const; - virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const; - - //------------------------------------------------------------------------------------------------5 - // Characteristics - //------------------------------------------------------------------------------------------------ - - // Try to determine whether this component belongs to a preview actor. - // Preview / Template components need to sync their data for HDA cooks and output translations. - bool IsPreview() const; - - virtual bool IsValidComponent() const; - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - // TODO: After the cook worfklow rework, most of these won't be needed anymore, so clean up! - //FHoudiniAssetComponentEvent OnTemplateParametersChanged; - //FHoudiniAssetComponentEvent OnPreAssetCook; - //FHoudiniAssetComponentEvent OnCookCompleted; - //FHoudiniAssetComponentEvent OnOutputProcessingCompleted; - - /*virtual void BroadcastParametersChanged(); - virtual void BroadcastPreAssetCook(); - virtual void BroadcastCookCompleted();*/ - - virtual void OnPrePreCook() {}; - virtual void OnPostPreCook() {}; - virtual void OnPreOutputProcessing() {}; - virtual void OnPostOutputProcessing() {}; - virtual void OnPrePreInstantiation() {}; - - - virtual void NotifyHoudiniRegisterCompleted() {}; - virtual void NotifyHoudiniPreUnregister() {}; - virtual void NotifyHoudiniPostUnregister() {}; - - virtual void OnFullyLoaded(); - - // Component template parameters have been updated. - // Broadcast delegate, and let preview components take care of the rest. - virtual void OnTemplateParametersChanged() { }; - virtual void OnBlueprintStructureModified() { }; - virtual void OnBlueprintModified() { }; - - // - // Begin: IHoudiniAssetStateEvents - // - - virtual void HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) override; - - FORCEINLINE - virtual FOnHoudiniAssetStateChange& GetOnHoudiniAssetStateChangeDelegate() override { return OnHoudiniAssetStateChangeDelegate; } - - // - // End: IHoudiniAssetStateEvents - // - - // Called by HandleOnHoudiniAssetStateChange when entering the PostCook state. Broadcasts OnPostCookDelegate. - void HandleOnPostCook(); - - // Called by baking code after baking all outputs of this HAC (HoudiniEngineBakeOption) - void HandleOnPostBake(const bool bInSuccess); - -protected: - - // UActorComponents Method - virtual void OnComponentCreated() override; - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - - virtual void OnChildAttached(USceneComponent* ChildComponent) override; - - virtual void BeginDestroy() override; - - // - virtual void CreateRenderState_Concurrent(FRegisterComponentContext* Context) override; - - // Do any object - specific cleanup required immediately after loading an object. - // This is not called for newly - created objects, and by default will always execute on the game thread. - virtual void PostLoad() override; - - // Called after importing property values for this object (paste, duplicate or .t3d import) - // Allow the object to perform any cleanup for properties which shouldn't be duplicated or - // Are unsupported by the script serialization - virtual void PostEditImport() override; - - // - void OnActorMoved(AActor* Actor); - - // - void UpdatePostDuplicate(); - - // - //static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); - - // Updates physics state, bounds, and mark render state dirty - // Should be call PostLoad and PostProcessing - void UpdateRenderingInformation(); - - // Mutators - - // Set asset state - void SetAssetState(EHoudiniAssetState InNewState); - -public: - - // Houdini Asset associated with this component. - /*Category = HoudiniAsset, EditAnywhere, meta = (DisplayPriority=0)*/ - UPROPERTY(Category = HoudiniAsset, EditAnywhere)// BlueprintSetter = SetHoudiniAsset, BlueprintReadWrite, ) - UHoudiniAsset* HoudiniAsset; - - // Automatically cook when a parameter or input is changed - UPROPERTY() - bool bCookOnParameterChange; - - // Enables uploading of transformation changes back to Houdini Engine. - UPROPERTY() - bool bUploadTransformsToHoudiniEngine; - - // Transform changes automatically trigger cooks. - UPROPERTY() - bool bCookOnTransformChange; - - // Houdini materials will be converted to Unreal Materials. - //UPROPERTY() - //bool bUseNativeHoudiniMaterials; - - // This asset will cook when its asset input cook - UPROPERTY() - bool bCookOnAssetInputCook; - - // Enabling this will prevent the HDA from producing any output after cooking. - UPROPERTY() - bool bOutputless; - - // Enabling this will allow outputing the asset's templated geos - UPROPERTY() - bool bOutputTemplateGeos; - - // Temporary cook folder - UPROPERTY() - FDirectoryPath TemporaryCookFolder; - - // Folder used for baking this asset's outputs (unless set by prim/detail attribute on the output). Falls back to - // the default from the plugin settings if not set. - UPROPERTY() - FDirectoryPath BakeFolder; - - // The method used to create Static Meshes - UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 0)) - EHoudiniStaticMeshMethod StaticMeshMethod; - - // Generation properties for the Static Meshes generated by this Houdini Asset - UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 1)/*, meta = (ShowOnlyInnerProperties)*/) - FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; - - // Build Settings to be used when generating the Static Meshes for this Houdini Asset - UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 2)) - FMeshBuildSettings StaticMeshBuildSettings; - - // Override the global fast proxy mesh settings on this component? - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere/*, meta = (DisplayAfter = "StaticMeshGenerationProperties")*/) - bool bOverrideGlobalProxyStaticMeshSettings; - - // For StaticMesh outputs: should a fast proxy be created first? - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Enable Proxy Static Mesh", EditCondition="bOverrideGlobalProxyStaticMeshSettings")) - bool bEnableProxyStaticMeshOverride; - - // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes After a Timeout", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementByTimerOverride; - - // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride")) - float ProxyMeshAutoRefineTimeoutSecondsOverride; - - // Automatically refine proxy meshes to UStaticMesh before the map is saved - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes When Saving a Map", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; - - // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session - UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) - bool bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bGenerateMenuExpanded; - - UPROPERTY() - bool bBakeMenuExpanded; - - UPROPERTY() - bool bAssetOptionMenuExpanded; - - UPROPERTY() - bool bHelpAndDebugMenuExpanded; - - UPROPERTY() - EHoudiniEngineBakeOption HoudiniEngineBakeOption; - - // If true, then after a successful bake, the HACs outputs will be cleared and removed. - UPROPERTY() - bool bRemoveOutputAfterBake; - - // If true, recenter baked actors to their bounding box center after bake - UPROPERTY() - bool bRecenterBakedActors; - - // If true, replace the previously baked output (if any) instead of creating new objects - UPROPERTY() - bool bReplacePreviousBake; -#endif - -protected: - - // Id of corresponding Houdini asset. - UPROPERTY(DuplicateTransient) - int32 AssetId; - - // List of dependent downstream HACs that have us as an asset input - UPROPERTY(DuplicateTransient) - TSet DownstreamHoudiniAssets; - - // Unique GUID created by component. - UPROPERTY(DuplicateTransient) - FGuid ComponentGUID; - - // GUID used to track asynchronous cooking requests. - UPROPERTY(DuplicateTransient) - FGuid HapiGUID; - - // The asset name of the selected asset inside the asset library - UPROPERTY(DuplicateTransient) - FString HapiAssetName; - - // Current state of the asset - UPROPERTY(DuplicateTransient) - EHoudiniAssetState AssetState; - - // Last asset state logged. - UPROPERTY(DuplicateTransient) - mutable EHoudiniAssetState DebugLastAssetState; - - // Result of the current asset's state - UPROPERTY(DuplicateTransient) - EHoudiniAssetStateResult AssetStateResult; - - //// Contains the context for keeping track of shared - //// Houdini data. - //UPROPERTY(DuplicateTransient) - //UHoudiniAssetContext* AssetContext; - - // Subasset index - UPROPERTY() - uint32 SubAssetIndex; - - // Number of times this asset has been cooked. - UPROPERTY(DuplicateTransient) - int32 AssetCookCount; - - // - UPROPERTY(DuplicateTransient) - bool bHasBeenLoaded; - - UPROPERTY(DuplicateTransient) - bool bHasBeenDuplicated; - - UPROPERTY(DuplicateTransient) - bool bPendingDelete; - - UPROPERTY(DuplicateTransient) - bool bRecookRequested; - - UPROPERTY(DuplicateTransient) - bool bRebuildRequested; - - UPROPERTY(DuplicateTransient) - bool bEnableCooking; - - UPROPERTY(DuplicateTransient) - bool bForceNeedUpdate; - - UPROPERTY(DuplicateTransient) - bool bLastCookSuccess; - - // Indicates that the parameter state (excluding values) on the HAC and the instantiated node needs to be synced. - // The most common use for this would be a newly instantiated HDA that has only a default parameter interface - // from its asset definition, and needs to sync pre-cook. - UPROPERTY(DuplicateTransient) - bool bParameterDefinitionUpdateNeeded; - - UPROPERTY(DuplicateTransient) - bool bBlueprintStructureModified; - - UPROPERTY(DuplicateTransient) - bool bBlueprintModified; - - //UPROPERTY(DuplicateTransient) - //bool bEditorPropertiesNeedFullUpdate; - - UPROPERTY(Instanced) - TArray Parameters; - - UPROPERTY(Instanced) - TArray Inputs; - - UPROPERTY(Instanced) - TArray Outputs; - - // The baked outputs from the last bake. - UPROPERTY() - TArray BakedOutputs; - - // Any actors that aren't explicitly - // tracked by output objects should be registered - // here so that they can be cleaned up. - UPROPERTY() - TArray> UntrackedOutputs; - - UPROPERTY() - TArray HandleComponents; - - UPROPERTY(Transient, DuplicateTransient) - bool bHasComponentTransformChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bFullyLoaded; - - UPROPERTY() - UHoudiniPDGAssetLink* PDGAssetLink; - - // Timer that is used to trigger creation of UStaticMesh for all mesh outputs - // that still have UHoudiniStaticMeshes. The timer is cleared on PreCook and reset - // at the end of the PostCook. - UPROPERTY() - FTimerHandle RefineMeshesTimer; - - // Delegate that is used to broadcast when RefineMeshesTimer fires - FOnRefineMeshesTimerDelegate OnRefineMeshesTimerDelegate; - - // If true, don't build a proxy mesh next cook (regardless of global or override settings), - // instead build the UStaticMesh directly (if applicable for the output types). - UPROPERTY(DuplicateTransient) - bool bNoProxyMeshNextCookRequested; - - // Maps a UObject to an Input number, used to preset the asset's inputs - UPROPERTY(Transient, DuplicateTransient) - TMap InputPresets; - - // If true, bake the asset after its next cook. - UPROPERTY(DuplicateTransient) - bool bBakeAfterNextCook; - - // Delegate to broadcast after a post cook event - // Arguments are (HoudiniAssetComponent* HAC, bool IsSuccessful) - FOnPostCookDelegate OnPostCookDelegate; - - // Delegate to broadcast when baking after a cook. - // Currently we cannot call the bake functions from here (Runtime module) - // or from the HoudiniEngineManager (HoudiniEngine) module, so we use - // a delegate. - FOnPostCookBakeDelegate OnPostCookBakeDelegate; - - // Delegate to broadcast after baking the HAC. Not called when just baking individual outputs directly. - // Arguments are (HoudiniAssetComponent* HAC, bool bIsSuccessful) - FOnPostBakeDelegate OnPostBakeDelegate; - - // Delegate that is broadcast when the asset state changes (HAC version). - FOnAssetStateChangeDelegate OnAssetStateChangeDelegate; - - // Cached flag of whether this object is considered to be a 'preview' component or not. - // This is typically useful in destructors when references to the World, for example, - // is no longer available. - UPROPERTY(Transient, DuplicateTransient) - bool bCachedIsPreview; - - USimpleConstructionScript* GetSCS() const; - - // Object used to convert V1 HAC to V2 HAC - UHoudiniAssetComponent_V1* Version1CompatibilityHAC; - - // The last timestamp this component was ticked - // used to prioritize/limit the number of HAC processed per tick - UPROPERTY(Transient) - double LastTickTime; - - // - // Begin: IHoudiniAssetStateEvents - // - - // Delegate that is broadcast when AssetState changes - FOnHoudiniAssetStateChange OnHoudiniAssetStateChangeDelegate; - - // - // End: IHoudiniAssetStateEvents - // - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniEngineRuntimeCommon.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniOutput.h" +#include "HoudiniInputTypes.h" +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniAssetStateTypes.h" +#include "IHoudiniAssetStateEvents.h" + +#include "Engine/EngineTypes.h" +#include "Components/PrimitiveComponent.h" +#include "Components/SceneComponent.h" + +#include "HoudiniAssetComponent.generated.h" + +class UHoudiniAsset; +class UHoudiniParameter; +class UHoudiniInput; +class UHoudiniOutput; +class UHoudiniHandleComponent; +class UHoudiniPDGAssetLink; +class UHoudiniAssetComponent_V1; + +UENUM() +enum class EHoudiniStaticMeshMethod : uint8 +{ + // Static Meshes will be generated by using Raw Meshes. + RawMesh, + // Static Meshes will be generated by using Mesh Descriptions. + FMeshDescription, + // Always build Houdini Proxy Meshes (dev) + UHoudiniStaticMesh, +}; + +class UHoudiniAssetComponent; + +DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetEvent, UHoudiniAsset*); +DECLARE_MULTICAST_DELEGATE_OneParam(FHoudiniAssetComponentEvent, UHoudiniAssetComponent*) + +UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), ShowCategories = (Mobility), editinlinenew) +class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveComponent, public IHoudiniAssetStateEvents +{ + GENERATED_UCLASS_BODY() + + // Declare translators as friend so they can easily directly modify + // Inputs, outputs and parameters + friend class FHoudiniEngineManager; + friend struct FHoudiniOutputTranslator; + friend struct FHoudiniInputTranslator; + friend struct FHoudiniSplineTranslator; + friend struct FHoudiniParameterTranslator; + friend struct FHoudiniPDGManager; + friend struct FHoudiniHandleTranslator; + +#if WITH_EDITORONLY_DATA + friend class FHoudiniAssetComponentDetails; +#endif + +public: + + // Declare the delegate that is broadcast when RefineMeshesTimer fires + DECLARE_MULTICAST_DELEGATE_OneParam(FOnRefineMeshesTimerDelegate, UHoudiniAssetComponent*); + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnPostCookBakeDelegate, UHoudiniAssetComponent*); + // Delegate for when EHoudiniAssetState changes from InFromState to InToState on a Houdini Asset Component (InHAC). + DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnAssetStateChangeDelegate, UHoudiniAssetComponent*, const EHoudiniAssetState, const EHoudiniAssetState); + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostCookDelegate, UHoudiniAssetComponent*, bool); + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostBakeDelegate, UHoudiniAssetComponent*, bool); + + virtual ~UHoudiniAssetComponent(); + + virtual void Serialize(FArchive & Ar) override; + + virtual bool ConvertLegacyData(); + + // Called after the C++ constructor and after the properties have been initialized, including those loaded from config. + // This is called before any serialization or other setup has happened. + virtual void PostInitProperties() override; + + // Returns the Owner actor / HAC name + FString GetDisplayName() const; + + // Indicates if the HAC needs to be updated + bool NeedUpdate() const; + + // Indicates if the HAC's transform needs to be updated + bool NeedTransformUpdate() const { return (bHasComponentTransformChanged && bUploadTransformsToHoudiniEngine); }; + + // Indicates if any of the HAC's output components needs to be updated (no recook needed) + bool NeedOutputUpdate() const; + + // Check whether any inputs / outputs / parameters have made blueprint modifications. + bool NeedBlueprintStructureUpdate() const; + bool NeedBlueprintUpdate() const; + + // Prevents automatic triggering of updates on this HAC in its current state. + // This is to prevent endless cook/instantiation loops when an issue happens + void PreventAutoUpdates(); + + // Try to find one of our parameter that matches another (name, type, size and enabled) + UHoudiniParameter* FindMatchingParameter(UHoudiniParameter* InOtherParam); + + // Try to find one of our input that matches another one (name, isobjpath, index / parmId) + UHoudiniInput* FindMatchingInput(UHoudiniInput* InOtherInput); + + // Try to find one of our handle that matches another one (name and handle type) + UHoudiniHandleComponent* FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle); + + // Finds a parameter by name + UHoudiniParameter* FindParameterByName(const FString& InParamName); + + // Returns True if the component has at least one mesh output of class U + template + bool HasMeshOutputObjectOfClass() const; + + // Returns True if the component has at least one mesh output with a current proxy + bool HasAnyCurrentProxyOutput() const; + + // Returns True if the component has at least one proxy mesh output (not necessarily current/displayed) + bool HasAnyProxyOutput() const; + + // Returns True if the component has at least one non-proxy output component amongst its outputs + bool HasAnyOutputComponent() const; + + // Returns true if the component has InOutputObjectToFind in its output object + bool HasOutputObject(UObject* InOutputObjectToFind) const; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + UHoudiniAsset * GetHoudiniAsset() const; + int32 GetAssetId() const { return AssetId; }; + EHoudiniAssetState GetAssetState() const { return AssetState; }; + FString GetAssetStateAsString() const { return FHoudiniEngineRuntimeUtils::EnumToString(TEXT("EHoudiniAssetState"), GetAssetState()); }; + EHoudiniAssetStateResult GetAssetStateResult() const { return AssetStateResult; }; + FGuid GetHapiGUID() const { return HapiGUID; }; + FString GetHapiAssetName() const { return HapiAssetName; }; + FGuid GetComponentGUID() const { return ComponentGUID; }; + + int32 GetNumInputs() const { return Inputs.Num(); }; + int32 GetNumOutputs() const { return Outputs.Num(); }; + int32 GetNumParameters() const { return Parameters.Num(); }; + int32 GetNumHandles() const { return HandleComponents.Num(); }; + + UHoudiniInput* GetInputAt(const int32& Idx) { return Inputs.IsValidIndex(Idx) ? Inputs[Idx] : nullptr; }; + UHoudiniOutput* GetOutputAt(const int32& Idx) { return Outputs.IsValidIndex(Idx) ? Outputs[Idx] : nullptr;}; + UHoudiniParameter* GetParameterAt(const int32& Idx) { return Parameters.IsValidIndex(Idx) ? Parameters[Idx] : nullptr;}; + UHoudiniHandleComponent* GetHandleComponentAt(const int32& Idx) { return HandleComponents.IsValidIndex(Idx) ? HandleComponents[Idx] : nullptr; }; + + void GetOutputs(TArray& OutOutputs) const; + + TArray& GetBakedOutputs() { return BakedOutputs; } + const TArray& GetBakedOutputs() const { return BakedOutputs; } + + /* + TArray& GetParameters() { return Parameters; }; + TArray& GetInputs() { return Inputs; }; + TArray& GetOutputs() { return Outputs; }; + */ + + bool IsCookingEnabled() const { return bEnableCooking; }; + bool HasBeenLoaded() const { return bHasBeenLoaded; }; + bool HasBeenDuplicated() const { return bHasBeenDuplicated; }; + bool HasRecookBeenRequested() const { return bRecookRequested; }; + bool HasRebuildBeenRequested() const { return bRebuildRequested; }; + + //bool GetEditorPropertiesNeedFullUpdate() const { return bEditorPropertiesNeedFullUpdate; }; + + int32 GetAssetCookCount() const { return AssetCookCount; }; + + bool IsFullyLoaded() const { return bFullyLoaded; }; + + UHoudiniPDGAssetLink * GetPDGAssetLink() const { return PDGAssetLink; }; + + virtual bool IsProxyStaticMeshEnabled() const; + bool IsProxyStaticMeshRefinementByTimerEnabled() const; + float GetProxyMeshAutoRefineTimeoutSeconds() const; + bool IsProxyStaticMeshRefinementOnPreSaveWorldEnabled() const; + bool IsProxyStaticMeshRefinementOnPreBeginPIEEnabled() const; + // If true, then the next cook should not build proxy meshes, regardless of global or override settings, + // but should instead directly build UStaticMesh + bool HasNoProxyMeshNextCookBeenRequested() const { return bNoProxyMeshNextCookRequested; } + // Returns true if the asset state indicates that it has been cooked in this session, false otherwise. + bool IsHoudiniCookedDataAvailable(bool &bOutNeedsRebuildOrDelete, bool &bOutInvalidState) const; + // Returns true if the asset should be bake after the next cook + bool IsBakeAfterNextCookEnabled() const { return bBakeAfterNextCook; } + + FOnPostCookDelegate& GetOnPostCookDelegate() { return OnPostCookDelegate; } + FOnPostCookBakeDelegate& GetOnPostCookBakeDelegate() { return OnPostCookBakeDelegate; } + FOnPostBakeDelegate& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } + + FOnAssetStateChangeDelegate& GetOnAssetStateChangeDelegate() { return OnAssetStateChangeDelegate; } + + // Derived blueprint based components will check whether the template + // component contains updates that needs to processed. + bool NeedUpdateParameters() const; + bool NeedUpdateInputs() const; + + // Returns true if the component has any previous baked output recorded in its outputs + bool HasPreviousBakeOutput() const; + + // Returns true if the last cook of the HDA was successful + bool WasLastCookSuccessful() const { return bLastCookSuccess; } + + // Returns true if a parameter definition update (excluding values) is needed. + bool IsParameterDefinitionUpdateNeeded() const { return bParameterDefinitionUpdateNeeded; } + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + //void SetAssetId(const int& InAssetId); + //void SetAssetState(const EHoudiniAssetState& InAssetState) { AssetState = InAssetState; }; + //void SetAssetStateResult(const EHoudiniAssetStateResult& InAssetStateResult) { AssetStateResult = InAssetStateResult; }; + + //void SetHapiGUID(const FGuid& InGUID) { HapiGUID = InGUID; }; + //void SetComponentGUID(const FGuid& InGUID) { ComponentGUID = InGUID; }; + + //UFUNCTION(BlueprintSetter) + virtual void SetHoudiniAsset(UHoudiniAsset * NewHoudiniAsset); + + void SetCookingEnabled(const bool& bInCookingEnabled) { bEnableCooking = bInCookingEnabled; }; + + void SetHasBeenLoaded(const bool& InLoaded) { bHasBeenLoaded = InLoaded; }; + + void SetHasBeenDuplicated(const bool& InDuplicated) { bHasBeenDuplicated = InDuplicated; }; + + //void SetEditorPropertiesNeedFullUpdate(const bool& InUpdate) { bEditorPropertiesNeedFullUpdate = InUpdate; }; + + // Marks the assets as needing a recook + void MarkAsNeedCook(); + // Marks the assets as needing a full rebuild + void MarkAsNeedRebuild(); + // Marks the asset as needing to be instantiated + void MarkAsNeedInstantiation(); + // The blueprint has been structurally modified + void MarkAsBlueprintStructureModified(); + // The blueprint has been modified but not structurally changed. + void MarkAsBlueprintModified(); + + // + void SetAssetCookCount(const int32& InCount) { AssetCookCount = InCount; }; + // + void SetRecookRequested(const bool& InRecook) { bRecookRequested = InRecook; }; + // + void SetRebuildRequested(const bool& InRebuild) { bRebuildRequested = InRebuild; }; + // + void SetHasComponentTransformChanged(const bool& InHasChanged); + + // Set to True to force the next cook to not build a proxy mesh (regardless of global or override settings) and + // instead build a UStaticMesh directly (if applicable for the output type). + void SetNoProxyMeshNextCookRequested(bool bInNoProxyMeshNextCookRequested) { bNoProxyMeshNextCookRequested = bInNoProxyMeshNextCookRequested; } + + // Set to True to force the next cook to bake the asset after the cook completes. + void SetBakeAfterNextCookEnabled(bool bInEnabled) { bBakeAfterNextCook = bInEnabled; } + + // + void SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink); + // + virtual void OnHoudiniAssetChanged(); + + // + void AddDownstreamHoudiniAsset(UHoudiniAssetComponent* InDownstreamAsset) { DownstreamHoudiniAssets.Add(InDownstreamAsset); }; + // + void RemoveDownstreamHoudiniAsset(UHoudiniAssetComponent* InRemoveDownstreamAsset) { DownstreamHoudiniAssets.Remove(InRemoveDownstreamAsset); }; + // + void ClearDownstreamHoudiniAsset() { DownstreamHoudiniAssets.Empty(); }; + // + bool NotifyCookedToDownstreamAssets(); + // + bool NeedsToWaitForInputHoudiniAssets(); + + // Clear/disable the RefineMeshesTimer. + void ClearRefineMeshesTimer(); + + // Re-set the RefineMeshesTimer to its default value. + void SetRefineMeshesTimer(); + + virtual void OnRefineMeshesTimerFired(); + + // Called by RefineMeshesTimer when the timer is triggered. + // Checks for any UHoudiniStaticMesh in Outputs and bakes UStaticMesh for them via FHoudiniMeshTranslator. + FOnRefineMeshesTimerDelegate& GetOnRefineMeshesTimerDelegate() { return OnRefineMeshesTimerDelegate; } + + // Returns true if the asset is valid for cook/bake + virtual bool IsComponentValid() const; + // Return false if this component has no cooking or instantiation in progress. + bool IsInstantiatingOrCooking() const; + + // HoudiniEngineTick will be called by HoudiniEngineManager::Tick() + virtual void HoudiniEngineTick(); + +#if WITH_EDITOR + // This alternate version of PostEditChange is called when properties inside structs are modified. The property that was actually modified + // is located at the tail of the list. The head of the list of the FStructProperty member variable that contains the property that was modified. + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; + + //Called after applying a transaction to the object. Default implementation simply calls PostEditChange. + virtual void PostEditUndo() override; + + // Whether this component is currently open in a Blueprint editor. This + // method is overridden by HoudiniAssetBlueprintComponent. + virtual bool HasOpenEditor() const { return false; }; + +#endif + + void SetStaticMeshGenerationProperties(UStaticMesh* InStaticMesh) const; + + virtual void RegisterHoudiniComponent(UHoudiniAssetComponent* InComponent); + + virtual void OnRegister() override; + + // USceneComponent methods. + virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override; + virtual void OnUpdateTransform(EUpdateTransformFlags UpdateTransformFlags, ETeleportType Teleport) override; + + FBox GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& bIgnoreGeneratedLandscape) const; + + // Set this component's input presets + void SetInputPresets(const TMap& InPresets); + // Apply the preset input for HoudiniTools + void ApplyInputPresets(); + + // return the cached component template, if available. + virtual UHoudiniAssetComponent* GetCachedTemplate() const { return nullptr; } + + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + + //------------------------------------------------------------------------------------------------ + // Supported Features + //------------------------------------------------------------------------------------------------ + + // Whether or not this component should be able to delete the Houdini nodes + // that correspond to the HoudiniAsset when being deregistered. + virtual bool CanDeleteHoudiniNodes() const { return true; } + + virtual bool IsInputTypeSupported(EHoudiniInputType InType) const; + virtual bool IsOutputTypeSupported(EHoudiniOutputType InType) const; + + //------------------------------------------------------------------------------------------------5 + // Characteristics + //------------------------------------------------------------------------------------------------ + + // Try to determine whether this component belongs to a preview actor. + // Preview / Template components need to sync their data for HDA cooks and output translations. + bool IsPreview() const; + + virtual bool IsValidComponent() const; + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + // TODO: After the cook worfklow rework, most of these won't be needed anymore, so clean up! + //FHoudiniAssetComponentEvent OnTemplateParametersChanged; + //FHoudiniAssetComponentEvent OnPreAssetCook; + //FHoudiniAssetComponentEvent OnCookCompleted; + //FHoudiniAssetComponentEvent OnOutputProcessingCompleted; + + /*virtual void BroadcastParametersChanged(); + virtual void BroadcastPreAssetCook(); + virtual void BroadcastCookCompleted();*/ + + virtual void OnPrePreCook() {}; + virtual void OnPostPreCook() {}; + virtual void OnPreOutputProcessing() {}; + virtual void OnPostOutputProcessing() {}; + virtual void OnPrePreInstantiation() {}; + + + virtual void NotifyHoudiniRegisterCompleted() {}; + virtual void NotifyHoudiniPreUnregister() {}; + virtual void NotifyHoudiniPostUnregister() {}; + + virtual void OnFullyLoaded(); + + // Component template parameters have been updated. + // Broadcast delegate, and let preview components take care of the rest. + virtual void OnTemplateParametersChanged() { }; + virtual void OnBlueprintStructureModified() { }; + virtual void OnBlueprintModified() { }; + + // + // Begin: IHoudiniAssetStateEvents + // + + virtual void HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) override; + + FORCEINLINE + virtual FOnHoudiniAssetStateChange& GetOnHoudiniAssetStateChangeDelegate() override { return OnHoudiniAssetStateChangeDelegate; } + + // + // End: IHoudiniAssetStateEvents + // + + // Called by HandleOnHoudiniAssetStateChange when entering the PostCook state. Broadcasts OnPostCookDelegate. + void HandleOnPostCook(); + + // Called by baking code after baking all outputs of this HAC (HoudiniEngineBakeOption) + void HandleOnPostBake(const bool bInSuccess); + +protected: + + // UActorComponents Method + virtual void OnComponentCreated() override; + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + virtual void OnChildAttached(USceneComponent* ChildComponent) override; + + virtual void BeginDestroy() override; + + // + virtual void CreateRenderState_Concurrent(FRegisterComponentContext* Context) override; + + // Do any object - specific cleanup required immediately after loading an object. + // This is not called for newly - created objects, and by default will always execute on the game thread. + virtual void PostLoad() override; + + // Called after importing property values for this object (paste, duplicate or .t3d import) + // Allow the object to perform any cleanup for properties which shouldn't be duplicated or + // Are unsupported by the script serialization + virtual void PostEditImport() override; + + // + void OnActorMoved(AActor* Actor); + + // + void UpdatePostDuplicate(); + + // + //static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); + + // Updates physics state, bounds, and mark render state dirty + // Should be call PostLoad and PostProcessing + void UpdateRenderingInformation(); + + // Mutators + + // Set asset state + void SetAssetState(EHoudiniAssetState InNewState); + +public: + + // Houdini Asset associated with this component. + /*Category = HoudiniAsset, EditAnywhere, meta = (DisplayPriority=0)*/ + UPROPERTY(Category = HoudiniAsset, EditAnywhere)// BlueprintSetter = SetHoudiniAsset, BlueprintReadWrite, ) + UHoudiniAsset* HoudiniAsset; + + // Automatically cook when a parameter or input is changed + UPROPERTY() + bool bCookOnParameterChange; + + // Enables uploading of transformation changes back to Houdini Engine. + UPROPERTY() + bool bUploadTransformsToHoudiniEngine; + + // Transform changes automatically trigger cooks. + UPROPERTY() + bool bCookOnTransformChange; + + // Houdini materials will be converted to Unreal Materials. + //UPROPERTY() + //bool bUseNativeHoudiniMaterials; + + // This asset will cook when its asset input cook + UPROPERTY() + bool bCookOnAssetInputCook; + + // Enabling this will prevent the HDA from producing any output after cooking. + UPROPERTY() + bool bOutputless; + + // Enabling this will allow outputing the asset's templated geos + UPROPERTY() + bool bOutputTemplateGeos; + + // Enabling this will allow outputing the asset's output nodes + UPROPERTY() + bool bUseOutputNodes; + + // Temporary cook folder + UPROPERTY() + FDirectoryPath TemporaryCookFolder; + + // Folder used for baking this asset's outputs (unless set by prim/detail attribute on the output). Falls back to + // the default from the plugin settings if not set. + UPROPERTY() + FDirectoryPath BakeFolder; + + // The method used to create Static Meshes + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 0)) + EHoudiniStaticMeshMethod StaticMeshMethod; + + // Generation properties for the Static Meshes generated by this Houdini Asset + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 1)/*, meta = (ShowOnlyInnerProperties)*/) + FHoudiniStaticMeshGenerationProperties StaticMeshGenerationProperties; + + // Build Settings to be used when generating the Static Meshes for this Houdini Asset + UPROPERTY(Category = "HoudiniMeshGeneration", EditAnywhere, meta = (DisplayPriority = 2)) + FMeshBuildSettings StaticMeshBuildSettings; + + // Override the global fast proxy mesh settings on this component? + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere/*, meta = (DisplayAfter = "StaticMeshGenerationProperties")*/) + bool bOverrideGlobalProxyStaticMeshSettings; + + // For StaticMesh outputs: should a fast proxy be created first? + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Enable Proxy Static Mesh", EditCondition="bOverrideGlobalProxyStaticMeshSettings")) + bool bEnableProxyStaticMeshOverride; + + // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes After a Timeout", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementByTimerOverride; + + // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride && bEnableProxyStaticMeshRefinementByTimerOverride")) + float ProxyMeshAutoRefineTimeoutSecondsOverride; + + // Automatically refine proxy meshes to UStaticMesh before the map is saved + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes When Saving a Map", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementOnPreSaveWorldOverride; + + // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session + UPROPERTY(Category = "HoudiniProxyMeshGeneration", EditAnywhere, meta = (DisplayName="Refine Proxy Static Meshes On PIE", EditCondition = "bOverrideGlobalProxyStaticMeshSettings && bEnableProxyStaticMeshOverride")) + bool bEnableProxyStaticMeshRefinementOnPreBeginPIEOverride; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bGenerateMenuExpanded; + + UPROPERTY() + bool bBakeMenuExpanded; + + UPROPERTY() + bool bAssetOptionMenuExpanded; + + UPROPERTY() + bool bHelpAndDebugMenuExpanded; + + UPROPERTY() + EHoudiniEngineBakeOption HoudiniEngineBakeOption; + + // If true, then after a successful bake, the HACs outputs will be cleared and removed. + UPROPERTY() + bool bRemoveOutputAfterBake; + + // If true, recenter baked actors to their bounding box center after bake + UPROPERTY() + bool bRecenterBakedActors; + + // If true, replace the previously baked output (if any) instead of creating new objects + UPROPERTY() + bool bReplacePreviousBake; +#endif + +protected: + + // Id of corresponding Houdini asset. + UPROPERTY(DuplicateTransient) + int32 AssetId; + + // Ids of the nodes that should be cook for this HAC + // This is for additional output and templated nodes if they are used. + UPROPERTY(Transient, DuplicateTransient) + TArray NodeIdsToCook; + + // List of dependent downstream HACs that have us as an asset input + UPROPERTY(DuplicateTransient) + TSet DownstreamHoudiniAssets; + + // Unique GUID created by component. + UPROPERTY(DuplicateTransient) + FGuid ComponentGUID; + + // GUID used to track asynchronous cooking requests. + UPROPERTY(DuplicateTransient) + FGuid HapiGUID; + + // The asset name of the selected asset inside the asset library + UPROPERTY(DuplicateTransient) + FString HapiAssetName; + + // Current state of the asset + UPROPERTY(DuplicateTransient) + EHoudiniAssetState AssetState; + + // Last asset state logged. + UPROPERTY(DuplicateTransient) + mutable EHoudiniAssetState DebugLastAssetState; + + // Result of the current asset's state + UPROPERTY(DuplicateTransient) + EHoudiniAssetStateResult AssetStateResult; + + //// Contains the context for keeping track of shared + //// Houdini data. + //UPROPERTY(DuplicateTransient) + //UHoudiniAssetContext* AssetContext; + + // Subasset index + UPROPERTY() + uint32 SubAssetIndex; + + // Number of times this asset has been cooked. + UPROPERTY(DuplicateTransient) + int32 AssetCookCount; + + // + UPROPERTY(DuplicateTransient) + bool bHasBeenLoaded; + + UPROPERTY(DuplicateTransient) + bool bHasBeenDuplicated; + + UPROPERTY(DuplicateTransient) + bool bPendingDelete; + + UPROPERTY(DuplicateTransient) + bool bRecookRequested; + + UPROPERTY(DuplicateTransient) + bool bRebuildRequested; + + UPROPERTY(DuplicateTransient) + bool bEnableCooking; + + UPROPERTY(DuplicateTransient) + bool bForceNeedUpdate; + + UPROPERTY(DuplicateTransient) + bool bLastCookSuccess; + + // Indicates that the parameter state (excluding values) on the HAC and the instantiated node needs to be synced. + // The most common use for this would be a newly instantiated HDA that has only a default parameter interface + // from its asset definition, and needs to sync pre-cook. + UPROPERTY(DuplicateTransient) + bool bParameterDefinitionUpdateNeeded; + + UPROPERTY(DuplicateTransient) + bool bBlueprintStructureModified; + + UPROPERTY(DuplicateTransient) + bool bBlueprintModified; + + //UPROPERTY(DuplicateTransient) + //bool bEditorPropertiesNeedFullUpdate; + + UPROPERTY(Instanced) + TArray Parameters; + + UPROPERTY(Instanced) + TArray Inputs; + + UPROPERTY(Instanced) + TArray Outputs; + + // The baked outputs from the last bake. + UPROPERTY() + TArray BakedOutputs; + + // Any actors that aren't explicitly + // tracked by output objects should be registered + // here so that they can be cleaned up. + UPROPERTY() + TArray> UntrackedOutputs; + + UPROPERTY() + TArray HandleComponents; + + UPROPERTY(Transient, DuplicateTransient) + bool bHasComponentTransformChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bFullyLoaded; + + UPROPERTY() + UHoudiniPDGAssetLink* PDGAssetLink; + + // Timer that is used to trigger creation of UStaticMesh for all mesh outputs + // that still have UHoudiniStaticMeshes. The timer is cleared on PreCook and reset + // at the end of the PostCook. + UPROPERTY() + FTimerHandle RefineMeshesTimer; + + // Delegate that is used to broadcast when RefineMeshesTimer fires + FOnRefineMeshesTimerDelegate OnRefineMeshesTimerDelegate; + + // If true, don't build a proxy mesh next cook (regardless of global or override settings), + // instead build the UStaticMesh directly (if applicable for the output types). + UPROPERTY(DuplicateTransient) + bool bNoProxyMeshNextCookRequested; + + // Maps a UObject to an Input number, used to preset the asset's inputs + UPROPERTY(Transient, DuplicateTransient) + TMap InputPresets; + + // If true, bake the asset after its next cook. + UPROPERTY(DuplicateTransient) + bool bBakeAfterNextCook; + + // Delegate to broadcast after a post cook event + // Arguments are (HoudiniAssetComponent* HAC, bool IsSuccessful) + FOnPostCookDelegate OnPostCookDelegate; + + // Delegate to broadcast when baking after a cook. + // Currently we cannot call the bake functions from here (Runtime module) + // or from the HoudiniEngineManager (HoudiniEngine) module, so we use + // a delegate. + FOnPostCookBakeDelegate OnPostCookBakeDelegate; + + // Delegate to broadcast after baking the HAC. Not called when just baking individual outputs directly. + // Arguments are (HoudiniAssetComponent* HAC, bool bIsSuccessful) + FOnPostBakeDelegate OnPostBakeDelegate; + + // Delegate that is broadcast when the asset state changes (HAC version). + FOnAssetStateChangeDelegate OnAssetStateChangeDelegate; + + // Cached flag of whether this object is considered to be a 'preview' component or not. + // This is typically useful in destructors when references to the World, for example, + // is no longer available. + UPROPERTY(Transient, DuplicateTransient) + bool bCachedIsPreview; + + USimpleConstructionScript* GetSCS() const; + + // Object used to convert V1 HAC to V2 HAC + UHoudiniAssetComponent_V1* Version1CompatibilityHAC; + + // The last timestamp this component was ticked + // used to prioritize/limit the number of HAC processed per tick + UPROPERTY(Transient) + double LastTickTime; + + // + // Begin: IHoudiniAssetStateEvents + // + + // Delegate that is broadcast when AssetState changes + FOnHoudiniAssetStateChange OnHoudiniAssetStateChangeDelegate; + + // + // End: IHoudiniAssetStateEvents + // + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h index 0144fe633..d18cf1ab4 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetStateTypes.h @@ -1,92 +1,92 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - - -UENUM() -enum class EHoudiniAssetState : uint8 -{ - // Loaded / Duplicated HDA, - // Will need to be instantiated upon change/update - NeedInstantiation, - - // Newly created HDA, fetch its default parameters then proceed to PreInstantiation - NewHDA, - - // Newly created HDA, after default parameters fetch, needs to be instantiated immediately - PreInstantiation, - - // Instantiating task in progress - Instantiating, - - // Instantiated HDA, needs to be cooked immediately - PreCook, - - // Cooking task in progress - Cooking, - - // Cooking has finished - PostCook, - - // Cooked HDA, needs to be processed immediately - PreProcess, - - // Processing task in progress - Processing, - - // Processed / Updated HDA - // Will need to be cooked upon change/update - None, - - // Asset needs to be rebuilt (Deleted/Instantiated/Cooked) - NeedRebuild, - - // Asset needs to be deleted - NeedDelete, - - // Deleting - Deleting, - - // Process component template. This is ticking has very limited - // functionality, typically limited to checking for parameter updates - // in order to trigger PostEditChange() to run construction scripts again. - ProcessTemplate, -}; - -UENUM() -enum class EHoudiniAssetStateResult : uint8 -{ - None, - Working, - Success, - FinishedWithError, - FinishedWithFatalError, - Aborted -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + + +UENUM() +enum class EHoudiniAssetState : uint8 +{ + // Loaded / Duplicated HDA, + // Will need to be instantiated upon change/update + NeedInstantiation, + + // Newly created HDA, fetch its default parameters then proceed to PreInstantiation + NewHDA, + + // Newly created HDA, after default parameters fetch, needs to be instantiated immediately + PreInstantiation, + + // Instantiating task in progress + Instantiating, + + // Instantiated HDA, needs to be cooked immediately + PreCook, + + // Cooking task in progress + Cooking, + + // Cooking has finished + PostCook, + + // Cooked HDA, needs to be processed immediately + PreProcess, + + // Processing task in progress + Processing, + + // Processed / Updated HDA + // Will need to be cooked upon change/update + None, + + // Asset needs to be rebuilt (Deleted/Instantiated/Cooked) + NeedRebuild, + + // Asset needs to be deleted + NeedDelete, + + // Deleting + Deleting, + + // Process component template. This is ticking has very limited + // functionality, typically limited to checking for parameter updates + // in order to trigger PostEditChange() to run construction scripts again. + ProcessTemplate, +}; + +UENUM() +enum class EHoudiniAssetStateResult : uint8 +{ + None, + Working, + Success, + FinishedWithError, + FinishedWithFatalError, + Aborted +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp index cf650ccac..42a5c38b3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp @@ -1,1802 +1,1802 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniCompatibilityHelpers.h" - -#include "HoudiniPluginSerializationVersion.h" - -#include "HoudiniInput.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterButton.h" -#include "HoudiniParameterButtonStrip.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterFolder.h" -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterInt.h" -#include "HoudiniParameterLabel.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.h" -#include "HoudiniParameterSeparator.h" -#include "HoudiniParameterString.h" -#include "HoudiniParameterToggle.h" -#include "HoudiniParameterFile.h" -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniInstancedActorComponent.h" -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniHandleComponent.h" - -#include "Engine/StaticMesh.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "EngineUtils.h" // for TActorIterator<> - -#if WITH_EDITOR - #include "Editor.h" - #include "Editor/UnrealEdEngine.h" - #include "UnrealEdGlobals.h" -#endif - -// TODO: -// HoudiniInstancedActorComponent ? -// HoudiniMeshSplitInstancerComponent ? - -UHoudiniAssetComponent_V1::UHoudiniAssetComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - - -void -UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) -{ - if (!Ar.IsLoading()) - return; - - //Super::Serialize(Ar); - - //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Serialize component flags. - Ar << HoudiniAssetComponentFlagsPacked; - - // Serialize format version. - uint32 HoudiniAssetComponentVersion = 0;//Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - Ar << HoudiniAssetComponentVersion; - - // ComponenState Enum, saved as uint8 - // 0 invalid - // 1 None - // 2 Instantiated - // 3 BeingCooked - // Serialize component state. - uint8 ComponentState = 0; - Ar << ComponentState; - - // Serialize scaling information and import axis. - Ar << GeneratedGeometryScaleFactor; - Ar << TransformScaleFactor; - - // ImportAxis Enum, saved as uint8 - // 0 unreal 1 Houdini - //uint8 ImportAxis = 0; - Ar << ImportAxis; - - // Serialize generated component GUID. - Ar << ComponentGUID; - - // If component is in invalid state, we can skip the rest of serialization. - //if (ComponentState == EHoudiniAssetComponentState_V1::Invalid) - if (ComponentState == 0) - return; - - // Serialize Houdini asset. - Ar << HoudiniAsset; - - // 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; - int TempId; - Ar << TempId; - bIsPlayModeActive_Unused = false; - } - - // Serialization of default preset. - Ar << DefaultPresetBuffer; - - // Serialization of preset. - { - bool bPresetSaved = false; - Ar << bPresetSaved; - - if (bPresetSaved) - { - Ar << PresetBuffer; - } - } - - // Serialize parameters. - //SerializeParameters(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::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; - } - - // 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::TIterator IterParams(Parameters); IterParams; ++IterParams) - { - UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); - if (HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill()) - ParameterByName.Add(HoudiniAssetParameter->ParameterName, HoudiniAssetParameter); - } - } - } - - // Serialize inputs. - //SerializeInputs(Ar); - { - Ar << Inputs; - - /* - if (Ar.IsLoading()) - { - for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) - { - UHoudiniAssetInput_V1 * HoudiniAssetInput = Inputs[InputIdx]; - if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) - Inputs[InputIdx]->SetHoudiniAssetComponent(this); - } - } - */ - } - - // 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); - { - //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; - int HoudiniInstanceInputKey = -1; - - Ar << HoudiniInstanceInputKey; - Ar << InstanceInputs[InstanceInputIdx]; - } - } - } - - // 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 DirtyPackages; - if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES) - { - TMap SavedPackages; - Ar << SavedPackages; - } - - if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS) - { - // Temporary Mesh Packages - TMap MeshPackages; - Ar << MeshPackages; - - // Temporary Landscape Layers Packages - TMap LayerPackages; - Ar << LayerPackages; - } -} - -uint32 -GetTypeHash(const FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) -{ - return HoudiniGeoPartObject.GetTypeHash(); -} - -FArchive & -operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) -{ - HoudiniGeoPartObject.Serialize(Ar); - return Ar; -} - -uint32 -FHoudiniGeoPartObject_V1::GetTypeHash() const -{ - int32 HashBuffer[4] = { ObjectId, GeoId, PartId, SplitId }; - int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); - return FCrc::StrCrc32(*SplitName, Hash); -} - -bool -FHoudiniGeoPartObject_V1SortPredicate::operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const -{ - /*if (!A.IsValid() || !B.IsValid()) - return false;*/ - - if (A.ObjectId == B.ObjectId) - { - if (A.GeoId == B.GeoId) - { - if (A.PartId == B.PartId) - return A.SplitId < B.SplitId; - else - return A.PartId < B.PartId; - } - else - { - return A.GeoId < B.GeoId; - } - } - - return A.ObjectId < B.ObjectId; -} - -void -FHoudiniGeoPartObject_V1::Serialize(FArchive & Ar) -{ - //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniGeoPartObjectVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniGeoPartObjectVersion; - - Ar << TransformMatrix; - - Ar << ObjectName; - Ar << PartName; - Ar << SplitName; - - // Serialize instancer material. - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME) - Ar << InstancerMaterialName; - - // Serialize instancer attribute material. - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME) - Ar << InstancerAttributeMaterialName; - - Ar << AssetId; - Ar << ObjectId; - Ar << GeoId; - Ar << PartId; - Ar << SplitId; - - Ar << HoudiniGeoPartObjectFlagsPacked; - - if (HoudiniGeoPartObjectVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - // Prior to this version the unused flags space was not zero-initialized, so - // zero them out now to prevent misinterpreting any 1s - HoudiniGeoPartObjectFlagsPacked &= 0x3FFFF; - } - - if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH) - { - Ar << NodePath; - } -} - - -FHoudiniGeoPartObject -FHoudiniGeoPartObject_V1::ConvertLegacyData() -{ - FHoudiniGeoPartObject NewHGPO; - - NewHGPO.AssetId = AssetId; - // NewHGPO.AssetName; - - NewHGPO.ObjectId = ObjectId; - NewHGPO.ObjectName = ObjectName; - - NewHGPO.GeoId = GeoId; - - NewHGPO.PartId = PartId; - NewHGPO.PartName = PartName; - NewHGPO.bHasCustomPartName = bHasCustomName; - - NewHGPO.SplitGroups.Add(SplitName); - - NewHGPO.TransformMatrix = TransformMatrix; - NewHGPO.NodePath = NodePath; - - // NewHGPO.VolumeName; - // NewHGPO.VolumeTileIndex; - - NewHGPO.bIsVisible = bIsVisible; - NewHGPO.bIsEditable = bIsEditable; - // NewHGPO.bIsTemplated; - // NewHGPO.bIsInstanced; - - NewHGPO.bHasGeoChanged = bHasGeoChanged; - // NewHGPO.bHasPartChanged; - // NewHGPO.bHasTransformChanged; - // NewHGPO.bHasMaterialsChanged; - NewHGPO.bLoaded = true; //bIsLoaded; - - // Hamdle Part Type - if (bIsCurve) - { - NewHGPO.Type = EHoudiniPartType::Curve; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - else if (bIsVolume) - { - NewHGPO.Type = EHoudiniPartType::Volume; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - else if (bIsInstancer) - { - NewHGPO.Type = EHoudiniPartType::Instancer; - NewHGPO.InstancerType = EHoudiniInstancerType::ObjectInstancer; - } - else if (bIsPackedPrimitiveInstancer) - { - NewHGPO.Type = EHoudiniPartType::Instancer; - NewHGPO.InstancerType = EHoudiniInstancerType::PackedPrimitive; - } - else - { - NewHGPO.Type = EHoudiniPartType::Mesh; - NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; - } - - // Instancer specific flags - if (NewHGPO.Type == EHoudiniPartType::Instancer) - { - //bInstancerMaterialAvailable - //bInstancerAttributeMaterialAvailable - //InstancerMaterialName - //InstancerAttributeMaterialName - } - - // Collision specific flags - if (NewHGPO.Type == EHoudiniPartType::Mesh) - { - //bIsCollidable - //bIsRenderCollidable - //bIsUCXCollisionGeo - //bIsSimpleCollisionGeo - //bHasCollisionBeenAdded - //bHasSocketBeenAdded - } - - //bIsBox - //bIsSphere - - if (NewHGPO.SplitGroups.Num() <= 0) - { - NewHGPO.SplitGroups.Add("main_geo"); - } - - return NewHGPO; -} - -UHoudiniAssetInput::UHoudiniAssetInput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} -void -UHoudiniAssetInput::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // Serialize current choice selection. - // Enum serialized as uint8 - // 0 GeometryInput - // 1 AssetInput - // 2 CurveInput - // 3 LandscapeInput - // 4 WorldInput - // 5 SkeletonInput - //SerializeEnumeration< EHoudiniAssetInputType::Enum >(Ar, ChoiceIndex); - Ar << ChoiceIndex; - - Ar << ChoiceStringValue; - - // We need these temporary variables for undo state tracking. - bool bLocalInputAssetConnectedInHoudini = bInputAssetConnectedInHoudini; - UHoudiniAssetComponent_V1 * LocalInputAssetComponent = InputAssetComponent; - - Ar << HoudiniAssetInputFlagsPacked; - - // Serialize input index. - Ar << InputIndex; - - // Serialize input objects (if it's assigned). - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT) - { - Ar << InputObjects; - } - else - { - UObject* InputObject = nullptr; - Ar << InputObject; - InputObjects.Empty(); - InputObjects.Add(InputObject); - } - - // Serialize input asset. - Ar << InputAssetComponent; - - // Serialize curve and curve parameters (if we have those). - Ar << InputCurve; - Ar << InputCurveParameters; - - // Serialize landscape used for input. - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT) - { - if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF) - { - ALandscapeProxy* InputLandscapePtr = nullptr; - Ar << InputLandscapePtr; - - InputLandscapeProxy = InputLandscapePtr; - } - else - { - Ar << InputLandscapeProxy; - } - - } - - // Serialize world outliner inputs. - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT) - { - Ar << InputOutlinerMeshArray; - } - - // Create necessary widget resources. - bLoadedParameter = true; - // If we're loading for real for the first time we need to reset this - // flag so we can reconnect when we get our parameters uploaded. - bInputAssetConnectedInHoudini = false; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT) - Ar << UnrealSplineResolution; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS) - { - Ar << InputTransforms; - } - else - { - InputTransforms.SetNum(InputObjects.Num()); - for (int32 n = 0; n < InputTransforms.Num(); n++) - InputTransforms[n] = FTransform::Identity; - } - - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - Ar << InputLandscapeTransform; -} - -UHoudiniInput* -UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) -{ - UHoudiniInput* Input = NewObject( - InOuter, UHoudiniInput::StaticClass(), FName(*ParameterLabel), RF_Transactional); - - EHoudiniInputType InputType = EHoudiniInputType::Invalid; - if (ChoiceIndex == 0) - InputType = EHoudiniInputType::Geometry; - else if (ChoiceIndex == 1) - InputType = EHoudiniInputType::Asset; - else if (ChoiceIndex == 2) - InputType = EHoudiniInputType::Curve; - else if (ChoiceIndex == 3) - InputType = EHoudiniInputType::Landscape; - else if (ChoiceIndex == 4) - InputType = EHoudiniInputType::World; - else if (ChoiceIndex == 5) - { - //InputType = EHoudiniInputType::Skeletal; - InputType = EHoudiniInputType::Invalid; - } - else - { - // Invalid - InputType = EHoudiniInputType::Invalid; - } - - bool bBlueprintStructureModified = false; - Input->SetInputType(InputType, bBlueprintStructureModified); - - Input->SetExportColliders(false); - Input->SetExportLODs(bExportAllLODs); - Input->SetExportSockets(bExportSockets); - Input->SetCookOnCurveChange(true); - - // If KeepWorldTransform is set to 2, use the default value - if (bKeepWorldTransform == 2) - Input->SetKeepWorldTransform((bool)Input->GetDefaultXTransformType()); - else - Input->SetKeepWorldTransform((bool)bKeepWorldTransform); - - Input->SetUnrealSplineResolution(UnrealSplineResolution); - Input->SetPackBeforeMerge(bPackBeforeMerge); - if(bIsObjectPathParameter) - Input->SetObjectPathParameter(ParmId); - else - Input->SetSOPInput(InputIndex); - - Input->SetImportAsReference(false); - Input->SetHelp(ParameterHelp); - //Input->SetInputNodeId(-1); - - if (bLandscapeExportAsHeightfield) - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Heightfield); - else if (bLandscapeExportAsMesh) - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Mesh); - else - Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Points); - - Input->SetLabel(ParameterLabel); - Input->SetName(ParameterName); - Input->SetUpdateInputLandscape(bUpdateInputLandscape); - - if (InputType == EHoudiniInputType::Geometry) - { - // Get the geo input object array - bool bNeedToEmpty = true; - TArray* GeoInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(GeoInputObjectsPtr)) - { - // Add the geometry input objects - for (int32 AtIndex = 0; AtIndex < InputObjects.Num(); AtIndex++) - { - // Create a new InputObject wrapper - UObject* CurObject = InputObjects[AtIndex]; - if (!CurObject || CurObject->IsPendingKill()) - continue; - - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurObject, Input, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - continue; - - // Remove the default/null object - if (bNeedToEmpty) - { - GeoInputObjectsPtr->Empty(); - bNeedToEmpty = false; - } - - // Add to the geo input array - GeoInputObjectsPtr->Add(NewInputObject); - } - } - } - else if (InputType == EHoudiniInputType::Asset && InputAssetComponent != nullptr && !InputAssetComponent->IsPendingKill()) - { - // Get the asset input object array - TArray* AssetInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(AssetInputObjectsPtr)) - { - // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent - // We can simply use the v1's HAC outer for that - UHoudiniAssetComponent* InputHAC = Cast(InputAssetComponent->GetOuter()); - if (InputHAC && !InputHAC->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputHAC, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the asset input array - AssetInputObjectsPtr->Add(NewInputObject); - } - } - } - } - else if (InputType == EHoudiniInputType::Curve) - { - // Get the curve input object array - TArray* CurveInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(CurveInputObjectsPtr)) - { - if (InputCurve && !InputCurve->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputCurve, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the curve input array - CurveInputObjectsPtr->Add(NewInputObject); - } - - // InputCurve->SetInputObject(NewInputObject); - - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = Cast(NewInputObject); - if(HoudiniSplineInput) - HoudiniSplineInput->Update(InputCurve); - } - } - - // TODO ??? - //InputCurveParameters; - } - else if (InputType == EHoudiniInputType::Landscape) - { - // Get the Landscape input object array - TArray* LandscapeInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - if (ensure(LandscapeInputObjectsPtr)) - { - // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent - // We can simply use the v1's HAC outer for that - ALandscapeProxy* InLandscape = InputLandscapeProxy.Get(); - if (InLandscape && !InLandscape->IsPendingKill()) - { - // Create a new InputObject wrapper - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InLandscape, Input, FString::FromInt(0)); - if (ensure(NewInputObject)) - { - // Add to the geo input array - LandscapeInputObjectsPtr->Add(NewInputObject); - } - } - } - - Input->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; - Input->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; - Input->bLandscapeExportLighting = bLandscapeExportLighting; - Input->bLandscapeExportMaterials = bLandscapeExportMaterials; - Input->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; - Input->bLandscapeExportTileUVs = bLandscapeExportTileUVs; - - //bLandscapeExportCurves; - } - else if (InputType == EHoudiniInputType::World) - { - // Get the world input object array - TArray* WorldInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); - - UWorld* MyWorld = InOuter->GetWorld(); - if (ensure(WorldInputObjectsPtr)) - { - // Add the geometry input objects - for (int32 AtIndex = 0; AtIndex < InputOutlinerMeshArray.Num(); AtIndex++) - { - FHoudiniAssetInputOutlinerMesh_V1& CurWorldInObj = InputOutlinerMeshArray[AtIndex]; - - AActor* CurActor = nullptr; - if (CurWorldInObj.ActorPtr.IsValid()) - { - CurActor = CurWorldInObj.ActorPtr.Get(); - } - else - { - // Try to update the actor ptr via the pathname - CurWorldInObj.TryToUpdateActorPtrFromActorPathName(MyWorld); - if (CurWorldInObj.ActorPtr.IsValid()) - { - CurActor = CurWorldInObj.ActorPtr.Get(); - } - } - - if(!CurActor || CurActor->IsPendingKill()) - continue; - - // Create a new InputObject wrapper for the actor - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurActor, Input, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - continue; - - // Add to the geo input array - WorldInputObjectsPtr->Add(NewInputObject); - } - } - - /* - CurWorldInObj->HoudiniAssetParameterVersion; - CurWorldInObj->ActorPtr; - CurWorldInObj->ActorPathName; - CurWorldInObj->StaticMeshComponent; - CurWorldInObj->StaticMesh; - CurWorldInObj->SplineComponent; - CurWorldInObj->NumberOfSplineControlPoints; - CurWorldInObj->SplineControlPointsTransform; - CurWorldInObj->SplineLength; - CurWorldInObj->SplineResolution; - CurWorldInObj->ActorTransform; - CurWorldInObj->ComponentTransform; - CurWorldInObj->AssetId; - CurWorldInObj->KeepWorldTransform; - CurWorldInObj->MeshComponentsMaterials; - CurWorldInObj->InstanceIndex; - */ - - //InputOutlinerMeshArray; - } - else - { - // Invalid - } - - //ChoiceStringValue; - //bStaticMeshChanged; - //bSwitchedToCurve; - //bLoadedParameter = true; - //bInputAssetConnectedInHoudini; - //InputTransforms; - //InputLandscapeTransform; - - return Input; -} - -FArchive& -operator<<(FArchive& Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh) -{ - HoudiniAssetInputOutlinerMesh.Serialize(Ar); - return Ar; -} - -void -FHoudiniAssetInputOutlinerMesh_V1::Serialize(FArchive& Ar) -{ - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniAssetParameterVersion; - - Ar << ActorPtr; - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - { - Ar << ActorPathName; - } - - if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) - { - Ar << StaticMeshComponent; - Ar << StaticMesh; - } - - Ar << ActorTransform; - - Ar << AssetId; - if (Ar.IsLoading() && !Ar.IsTransacting()) - AssetId = -1; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE - && HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) - { - Ar << SplineComponent; - Ar << NumberOfSplineControlPoints; - Ar << SplineLength; - Ar << SplineResolution; - Ar << ComponentTransform; - } - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM) - Ar << KeepWorldTransform; - - // UE4.19 SERIALIZATION FIX: - // The component materials serialization (24) was actually missing in the UE4.19 H17.0 / H16.5 plugin. - // However subsequent serialized changes (25+) were present in those version. This caused crashes when loading - // a level that was saved with 4.19+16.5/17.0 on a newer version of Unreal or Houdini... - // If the serialized version is exactly that of the fix, we can ignore the materials paths as well - if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT) - && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) - Ar << MeshComponentsMaterials; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX) - Ar << InstanceIndex; -} - -bool -FHoudiniAssetInputOutlinerMesh_V1::TryToUpdateActorPtrFromActorPathName(UWorld* InWorld) -{ - // Ensure our current ActorPathName looks valid - if (ActorPathName.IsEmpty() || ActorPathName.Equals(TEXT("None"), ESearchCase::IgnoreCase)) - return false; - - // We'll try to find the corresponding actor by browsing through all the actors in the world.. - // Get the editor world - UWorld* World = InWorld; - if (!World) - return false; - - // Then try to find the actor corresponding to our path/name - bool FoundActor = false; - for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) - { - if (ActorIt->GetPathName() != ActorPathName) - continue; - - // We found the actor - ActorPtr = *ActorIt; - FoundActor = true; - - break; - } - - if (FoundActor) - { - // We need to invalid our components so they can be updated later - // from the new actor - StaticMesh = NULL; - StaticMeshComponent = NULL; - SplineComponent = NULL; - } - - return FoundActor; -} - -UHoudiniAssetComponentMaterials_V1::UHoudiniAssetComponentMaterials_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} -void -UHoudiniAssetComponentMaterials_V1::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Assignments; - Ar << Replacements; -} - -UHoudiniHandleComponent_V1::UHoudiniHandleComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniAssetInstanceInput::UHoudiniAssetInstanceInput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetInstanceInput::Serialize(FArchive& Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Flags.HoudiniAssetInstanceInputFlagsPacked; - Ar << HoudiniGeoPartObject; - - Ar << ObjectToInstanceId; - // Object id is transient - if (Ar.IsLoading() && !Ar.IsTransacting()) - ObjectToInstanceId = -1; - - // Serialize fields. - Ar << InstanceInputFields; -} - -UHoudiniAssetInstanceInputField::UHoudiniAssetInstanceInputField(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetInstanceInputField::Serialize(FArchive& Ar) -{ - // Call base implementation first. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - const int32 InstanceInputFieldVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - - Ar << HoudiniAssetInstanceInputFieldFlagsPacked; - Ar << HoudiniGeoPartObject; - - FString UnusedInstancePathName; - Ar << UnusedInstancePathName; - Ar << RotationOffsets; - Ar << ScaleOffsets; - Ar << bScaleOffsetsLinearlyArray; - - Ar << InstancedTransforms; - Ar << VariationTransformsArray; - - if (Ar.IsSaving() || (Ar.IsLoading() && InstanceInputFieldVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS)) - { - Ar << InstanceColorOverride; - Ar << VariationInstanceColorOverrideArray; - } - - Ar << InstancerComponents; - Ar << InstancedObjects; - Ar << OriginalObject; -} - -void -UHoudiniHandleComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - // XFormn Parames is an array of 9 float params + tuple index - // TX TY TZ - // RX RY RZ - // SX SY SZ - - //UHoudiniAssetParameterFloat_V1* XFormParams[9]; - //int32 XFormParamsTupleIndex[9]; - for (int32 i = 0; i < 9; ++i) - { - Ar << XFormParams[i]; - Ar << XFormParamsTupleIndex[i]; - } - - //UHoudiniAssetParameterChoice_V1* RSTParm; - Ar << RSTParm; - //int32 RSTParmTupleIdx; - Ar << RSTParmTupleIdx; - - //UHoudiniAssetParameterChoice_V1* RotOrderParm; - Ar << RotOrderParm; - //int32 RotOrderParmTupleIdx; - Ar << RotOrderParmTupleIdx; -} - -/* -UHoudiniHandleComponent* -UHoudiniHandleComponent_V1::ConvertLegacyData(UObject* Outer) -{ - UHoudiniHandleComponent* NewHandle = nullptr; - - return NewHandle; -} -*/ - -bool -UHoudiniHandleComponent_V1::UpdateFromLegacyData(UHoudiniHandleComponent* NewHC) -{ - if (!NewHC || NewHC->IsPendingKill()) - return false; - - // TODO - //NewHC->XformParms; - //NewHC->RSTParm; - //NewHC->RotOrderParm; - //NewHC->HandleType; - //NewHC->HandleName; - - return true; -} - -UHoudiniSplineComponent_V1::UHoudiniSplineComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniSplineComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - int32 Version = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << Version; - - Ar << HoudiniGeoPartObject; - - if (Version < VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM) - { - // Before, curve points where stored as Vectors, not Transforms - TArray OldCurvePoints; - Ar << OldCurvePoints; - - CurvePoints.SetNumUninitialized(OldCurvePoints.Num()); - - FTransform trans = FTransform::Identity; - for (int32 n = 0; n < CurvePoints.Num(); n++) - { - trans.SetLocation(OldCurvePoints[n]); - CurvePoints[n] = trans; - } - } - else - { - Ar << CurvePoints; - } - - Ar << CurveDisplayPoints; - - Ar << CurveType; - Ar << CurveMethod; - Ar << bClosedCurve; -} - -UHoudiniSplineComponent* -UHoudiniSplineComponent_V1::ConvertLegacyData(UObject* Outer) -{ - UHoudiniSplineComponent* NewSpline = NewObject( - GetOuter(), UHoudiniSplineComponent::StaticClass()); - - UpdateFromLegacyData(NewSpline); - - return NewSpline; -} - -bool -UHoudiniSplineComponent_V1::UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline) -{ - if (!NewSpline || NewSpline->IsPendingKill()) - return false; - - NewSpline->SetFlags(RF_Transactional); - - NewSpline->CurvePoints = CurvePoints; - NewSpline->DisplayPoints = CurveDisplayPoints; - //NewSpline->DisplayPointIndexDivider; - //NewSpline->HoudiniSplineName; - NewSpline->bClosed = bClosedCurve; - NewSpline->bReversed = false; - NewSpline->bIsHoudiniSplineVisible = true; - - //0 Polygon 1 Nurbs 2 Bezier - if (CurveType == 0) - NewSpline->CurveType = EHoudiniCurveType::Polygon; - else if (CurveType == 1) - NewSpline->CurveType = EHoudiniCurveType::Nurbs; - else if (CurveType == 2) - NewSpline->CurveType = EHoudiniCurveType::Bezier; - else - NewSpline->CurveType = EHoudiniCurveType::Invalid; - - // 0 CVs, 1 Breakpoints, 2 Freehand - if (CurveMethod == 0) - NewSpline->CurveMethod = EHoudiniCurveMethod::CVs; - else if (CurveMethod == 1) - NewSpline->CurveMethod = EHoudiniCurveMethod::Breakpoints; - else if (CurveMethod == 2) - NewSpline->CurveMethod = EHoudiniCurveMethod::Freehand; - else - NewSpline->CurveMethod = EHoudiniCurveMethod::Invalid; - - NewSpline->bIsOutputCurve = false; - - NewSpline->HoudiniGeoPartObject = HoudiniGeoPartObject.ConvertLegacyData(); - - if (NewSpline->HoudiniGeoPartObject.bIsEditable) - { - NewSpline->bIsEditableOutputCurve = true; - NewSpline->bIsInputCurve = false; - } - else - { - NewSpline->bIsInputCurve = false; - NewSpline->bIsEditableOutputCurve = true; - } - - // Create a default Houdini spline input if a null pointer is passed in. - FName HoudiniSplineName = MakeUniqueObjectName(GetOuter(), UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); - NewSpline->SetHoudiniSplineName(HoudiniSplineName.ToString()); - - //NewSpline->bHasChanged; - //NewSpline->bNeedsToTriggerUpdate; - //NewSpline->InputObject; - //NewSpline->NodeId; - //NewSpline->PartName; - - return true; -} - -UHoudiniAssetParameter::UHoudiniAssetParameter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameter::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; - Ar << HoudiniAssetParameterVersion; - - Ar << HoudiniAssetParameterFlagsPacked; - - if (Ar.IsLoading()) - bChanged = false; - - Ar << ParameterName; - Ar << ParameterLabel; - - Ar << NodeId; - if (!Ar.IsTransacting() && Ar.IsLoading()) - { - // NodeId is invalid after load - NodeId = -1; - } - Ar << ParmId; - - Ar << ParmParentId; - Ar << ChildIndex; - - Ar << TupleSize; - Ar << ValuesIndex; - Ar << MultiparmInstanceIndex; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER) - { - UObject* Dummy = nullptr; - Ar << Dummy; - } - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP) - { - Ar << ParameterHelp; - } - else - { - ParameterHelp = TEXT(""); - } - /* - if (Ar.IsTransacting()) - { - Ar << PrimaryObject; - Ar << ParentParameter; - } - */ -} - -UHoudiniParameter* -UHoudiniAssetParameter::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameter::Create(Outer, ParameterName); -} - -void -UHoudiniAssetParameter::CopyLegacyParameterData(UHoudiniParameter* InNewParm) -{ - if (!InNewParm || InNewParm->IsPendingKill()) - return; - - InNewParm->Name = ParameterName; - InNewParm->Label = ParameterLabel; - //InNewParm->ParmType; - InNewParm->TupleSize = TupleSize; - InNewParm->NodeId = NodeId; - InNewParm->ParmId = ParmId; - InNewParm->ParentParmId = ParmParentId; - InNewParm->ChildIndex = ChildIndex; - InNewParm->bIsVisible = true; - InNewParm->bIsDisabled = bIsDisabled; - InNewParm->bHasChanged = bChanged; - //InNewParm->bNeedsToTriggerUpdate; - //InNewParm->bIsDefault; - InNewParm->bIsSpare = bIsSpare; - InNewParm->bJoinNext = false; - InNewParm->bIsChildOfMultiParm = bIsChildOfMultiparm; - // TODO: MultiparmInstanceIndex ? - //InNewParm->bIsDirectChildOfMultiParm; - InNewParm->bPendingRevertToDefault = false; - //InNewParm->TuplePendingRevertToDefault = false; - InNewParm->Help = ParameterHelp; - InNewParm->TagCount = 0; - InNewParm->ValueIndex = ValuesIndex; - //InNewParm->bHasExpression; - //InNewParm->bShowExpression; - //InNewParm->ParamExpression; - //InNewParm->Tags; - InNewParm->bAutoUpdate = true; -} - -UHoudiniAssetParameterChoice::UHoudiniAssetParameterChoice(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterChoice::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - { - StringChoiceValues.Empty(); - StringChoiceLabels.Empty(); - } - - int32 NumChoices = StringChoiceValues.Num(); - Ar << NumChoices; - - int32 NumLabels = StringChoiceLabels.Num(); - Ar << NumLabels; - - if (Ar.IsLoading()) - { - FString Temp; - for (int32 ChoiceIdx = 0; ChoiceIdx < NumChoices; ++ChoiceIdx) - { - Ar << Temp; - StringChoiceValues.Add(Temp); - } - - for (int32 LabelIdx = 0; LabelIdx < NumLabels; ++LabelIdx) - { - Ar << Temp; - StringChoiceLabels.Add(Temp); - } - } - - Ar << StringValue; - Ar << CurrentValue; - - Ar << bStringChoiceList; -} - -UHoudiniParameter* -UHoudiniAssetParameterChoice::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterChoice* Parm = nullptr; - if (bStringChoiceList) - { - Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::StringChoice); - } - else - { - Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::IntChoice); - } - - Parm->SetNumChoices(StringChoiceValues.Num()); - for (int32 Idx = 0; Idx < StringChoiceValues.Num(); Idx++) - { - FString * ChoiceValue = Parm->GetStringChoiceValueAt(Idx); - if (ChoiceValue) - *ChoiceValue = StringChoiceValues[Idx]; - FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); - } - - for (int32 Idx = 0; Idx < StringChoiceLabels.Num(); Idx++) - { - FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); - if (ChoiceLabel) - *ChoiceLabel = StringChoiceValues[Idx]; - } - - Parm->SetStringValue(StringValue); - Parm->SetIntValue(CurrentValue); - //Parm->DefaultStringValue = StringValue; - //Parm->SetDefault(); - //Parm->DefaultIntValue = CurrentValue; - - return Parm; -} - -UHoudiniAssetParameterButton::UHoudiniAssetParameterButton(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterButton::ConvertLegacyData(UObject* Outer) -{ - // Button strips where not supported in v1, just create a normal button - return UHoudiniParameterButton::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterColor::UHoudiniAssetParameterColor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterColor::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - Color = FColor::White; - - Ar << Color; -} - -UHoudiniParameter* -UHoudiniAssetParameterColor::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterColor* Parm = UHoudiniParameterColor::Create(Outer, ParameterName); - Parm->SetColorValue(Color); - - //Parm->DefaultColor = Color; - Parm->SetDefaultValue(); - - //Parm->bIsChildOfRamp = false; - - return Parm; -} - -UHoudiniAssetParameterFile::UHoudiniAssetParameterFile(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterFile::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - Ar << Filters; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY) - Ar << IsReadOnly; -} - -UHoudiniParameter* -UHoudiniAssetParameterFile::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterFile* Parm = UHoudiniParameterFile::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - Parm->SetFileFilters(Filters); - Parm->SetReadOnly(IsReadOnly); - - return Parm; -} - -UHoudiniAssetParameterFloat::UHoudiniAssetParameterFloat(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterFloat::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - - Ar << ValueMin; - Ar << ValueMax; - - Ar << ValueUIMin; - Ar << ValueUIMax; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) - Ar << ValueUnit; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP) - Ar << NoSwap; -} - -UHoudiniParameter* -UHoudiniAssetParameterFloat::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterFloat* Parm = UHoudiniParameterFloat::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - Parm->SetUnit(ValueUnit); - Parm->SetHasMin(true); - Parm->SetHasMax(true); - Parm->SetHasUIMin(true); - Parm->SetHasUIMax(true); - Parm->SetIsLogarithmic(false); - Parm->SetMin(ValueMin); - Parm->SetMax(ValueMax); - Parm->SetUIMin(ValueUIMin); - Parm->SetUIMax(ValueUIMax); - - //Parm->bIsChildOfRamp = false; - - return Parm; -} - -UHoudiniAssetParameterFolder::UHoudiniAssetParameterFolder(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterFolder::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterFolder::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterFolderList::UHoudiniAssetParameterFolderList(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterFolderList::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterFolderList::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterInt::UHoudiniAssetParameterInt(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterInt::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; - - Ar << ValueMin; - Ar << ValueMax; - - Ar << ValueUIMin; - Ar << ValueUIMax; - - if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) - Ar << ValueUnit; -} - -UHoudiniParameter* -UHoudiniAssetParameterInt::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterInt* Parm = UHoudiniParameterInt::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetDefaultValues(); - - //Parm->DefaultValues = Values; - Parm->SetUnit(ValueUnit); - Parm->SetHasMin(true); - Parm->SetHasMax(true); - Parm->SetHasUIMin(true); - Parm->SetHasUIMax(true); - Parm->SetIsLogarithmic(false); - Parm->SetMin(ValueMin); - Parm->SetMax(ValueMax); - Parm->SetUIMin(ValueUIMin); - Parm->SetUIMax(ValueUIMax); - - return Parm; -} - -UHoudiniAssetParameterLabel::UHoudiniAssetParameterLabel(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterLabel::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterLabel::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterMultiparm::UHoudiniAssetParameterMultiparm(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterMultiparm::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - if (Ar.IsLoading()) - MultiparmValue = 0; - - Ar << MultiparmValue; -} - -UHoudiniParameter* -UHoudiniAssetParameterMultiparm::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterMultiParm* Parm = UHoudiniParameterMultiParm::Create(Outer, ParameterName); - - //Parm->bIsShown; - //Parm->Value; - //Parm->TemplateName; - Parm->MultiparmValue = MultiparmValue; - //Parm->MultiParmInstanceNum; - //Parm->MultiParmInstanceLength; - //Parm->MultiParmInstanceCount; - //Parm->InstanceStartOffset; - //Parm->DefaultInstanceCount; - - // TODO: - // MultiparmInstanceIndex? - - return Parm; -} - -UHoudiniAssetParameterRamp::UHoudiniAssetParameterRamp(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterRamp::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - int32 multiparmvalue = 0; - Ar << multiparmvalue; - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << HoudiniAssetParameterRampCurveFloat; - Ar << HoudiniAssetParameterRampCurveColor; - - Ar << bIsFloatRamp; -} - -UHoudiniParameter* -UHoudiniAssetParameterRamp::ConvertLegacyData(UObject* Outer) -{ - if (bIsFloatRamp) - { - UHoudiniParameterRampFloat* Parm = UHoudiniParameterRampFloat::Create(Outer, ParameterName); - - // TODO: - // Convert HoudiniAssetParameterRampCurveFloat - - return Parm; - } - else - { - UHoudiniParameterRampColor* Parm = UHoudiniParameterRampColor::Create(Outer, ParameterName); - - // TODO: - // Convert HoudiniAssetParameterRampCurveColor - - return Parm; - } -} - -UHoudiniAssetParameterSeparator::UHoudiniAssetParameterSeparator(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -UHoudiniParameter* -UHoudiniAssetParameterSeparator::ConvertLegacyData(UObject* Outer) -{ - return UHoudiniParameterSeparator::Create(Outer, ParameterName); -} - -UHoudiniAssetParameterString::UHoudiniAssetParameterString(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterString::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; -} - -UHoudiniParameter* -UHoudiniAssetParameterString::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterString* Parm = UHoudiniParameterString::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt(Values[Idx], Idx); - - Parm->SetIsAssetRef(false); - Parm->SetDefaultValues(); - - //Parm->DefaultValues = Values; - //Parm->ChosenAssets.Empty(); - //Parm->bIsAssetRef = false; - - return Parm; -} - -UHoudiniAssetParameterToggle::UHoudiniAssetParameterToggle(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniAssetParameterToggle::Serialize(FArchive & Ar) -{ - // Call base implementation. - Super::Serialize(Ar); - - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << Values; -} - -UHoudiniParameter* -UHoudiniAssetParameterToggle::ConvertLegacyData(UObject* Outer) -{ - UHoudiniParameterToggle* Parm = UHoudiniParameterToggle::Create(Outer, ParameterName); - - Parm->SetNumberOfValues(Values.Num()); - for (int32 Idx = 0; Idx < Values.Num(); Idx++) - Parm->SetValueAt((bool)Values[Idx], Idx); - - Parm->SetDefaultValues(); - //Parm->DefaultValues = Values; - - return Parm; -} - -UHoudiniMeshSplitInstancerComponent_V1::UHoudiniMeshSplitInstancerComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniMeshSplitInstancerComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << InstancedMesh; - Ar << OverrideMaterial; - Ar << Instances; -} - -bool -UHoudiniMeshSplitInstancerComponent_V1::UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC) -{ - if (!NewMSIC || NewMSIC->IsPendingKill()) - return false; - - NewMSIC->Instances = Instances; - NewMSIC->OverrideMaterials.Add(OverrideMaterial); - NewMSIC->InstancedMesh = InstancedMesh; - - return true; -} - -UHoudiniInstancedActorComponent_V1::UHoudiniInstancedActorComponent_V1(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void -UHoudiniInstancedActorComponent_V1::Serialize(FArchive & Ar) -{ - //Super::Serialize(Ar); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - Ar << InstancedAsset; - Ar << Instances; -} - -bool -UHoudiniInstancedActorComponent_V1::UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC) -{ - if (!NewIAC || NewIAC->IsPendingKill()) - return false; - - //NewIAC->SetInstancedObject(InstancedAsset); - NewIAC->InstancedObject = InstancedAsset; - NewIAC->InstancedActors = Instances; - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniCompatibilityHelpers.h" + +#include "HoudiniPluginSerializationVersion.h" + +#include "HoudiniInput.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniParameter.h" +#include "HoudiniParameterButton.h" +#include "HoudiniParameterButtonStrip.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterFolder.h" +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterInt.h" +#include "HoudiniParameterLabel.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.h" +#include "HoudiniParameterSeparator.h" +#include "HoudiniParameterString.h" +#include "HoudiniParameterToggle.h" +#include "HoudiniParameterFile.h" +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniInstancedActorComponent.h" +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniHandleComponent.h" + +#include "Engine/StaticMesh.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "EngineUtils.h" // for TActorIterator<> + +#if WITH_EDITOR + #include "Editor.h" + #include "Editor/UnrealEdEngine.h" + #include "UnrealEdGlobals.h" +#endif + +// TODO: +// HoudiniInstancedActorComponent ? +// HoudiniMeshSplitInstancerComponent ? + +UHoudiniAssetComponent_V1::UHoudiniAssetComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + + +void +UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) +{ + if (!Ar.IsLoading()) + return; + + //Super::Serialize(Ar); + + //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Serialize component flags. + Ar << HoudiniAssetComponentFlagsPacked; + + // Serialize format version. + uint32 HoudiniAssetComponentVersion = 0;//Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + Ar << HoudiniAssetComponentVersion; + + // ComponenState Enum, saved as uint8 + // 0 invalid + // 1 None + // 2 Instantiated + // 3 BeingCooked + // Serialize component state. + uint8 ComponentState = 0; + Ar << ComponentState; + + // Serialize scaling information and import axis. + Ar << GeneratedGeometryScaleFactor; + Ar << TransformScaleFactor; + + // ImportAxis Enum, saved as uint8 + // 0 unreal 1 Houdini + //uint8 ImportAxis = 0; + Ar << ImportAxis; + + // Serialize generated component GUID. + Ar << ComponentGUID; + + // If component is in invalid state, we can skip the rest of serialization. + //if (ComponentState == EHoudiniAssetComponentState_V1::Invalid) + if (ComponentState == 0) + return; + + // Serialize Houdini asset. + Ar << HoudiniAsset; + + // 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; + int TempId; + Ar << TempId; + bIsPlayModeActive_Unused = false; + } + + // Serialization of default preset. + Ar << DefaultPresetBuffer; + + // Serialization of preset. + { + bool bPresetSaved = false; + Ar << bPresetSaved; + + if (bPresetSaved) + { + Ar << PresetBuffer; + } + } + + // Serialize parameters. + //SerializeParameters(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::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; + } + + // 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::TIterator IterParams(Parameters); IterParams; ++IterParams) + { + UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); + if (HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill()) + ParameterByName.Add(HoudiniAssetParameter->ParameterName, HoudiniAssetParameter); + } + } + } + + // Serialize inputs. + //SerializeInputs(Ar); + { + Ar << Inputs; + + /* + if (Ar.IsLoading()) + { + for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) + { + UHoudiniAssetInput_V1 * HoudiniAssetInput = Inputs[InputIdx]; + if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) + Inputs[InputIdx]->SetHoudiniAssetComponent(this); + } + } + */ + } + + // 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); + { + //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; + int HoudiniInstanceInputKey = -1; + + Ar << HoudiniInstanceInputKey; + Ar << InstanceInputs[InstanceInputIdx]; + } + } + } + + // 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 DirtyPackages; + if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES) + { + TMap SavedPackages; + Ar << SavedPackages; + } + + if (HoudiniAssetComponentVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS) + { + // Temporary Mesh Packages + TMap MeshPackages; + Ar << MeshPackages; + + // Temporary Landscape Layers Packages + TMap LayerPackages; + Ar << LayerPackages; + } +} + +uint32 +GetTypeHash(const FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) +{ + return HoudiniGeoPartObject.GetTypeHash(); +} + +FArchive & +operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject) +{ + HoudiniGeoPartObject.Serialize(Ar); + return Ar; +} + +uint32 +FHoudiniGeoPartObject_V1::GetTypeHash() const +{ + int32 HashBuffer[4] = { ObjectId, GeoId, PartId, SplitId }; + int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitName, Hash); +} + +bool +FHoudiniGeoPartObject_V1SortPredicate::operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const +{ + /*if (!A.IsValid() || !B.IsValid()) + return false;*/ + + if (A.ObjectId == B.ObjectId) + { + if (A.GeoId == B.GeoId) + { + if (A.PartId == B.PartId) + return A.SplitId < B.SplitId; + else + return A.PartId < B.PartId; + } + else + { + return A.GeoId < B.GeoId; + } + } + + return A.ObjectId < B.ObjectId; +} + +void +FHoudiniGeoPartObject_V1::Serialize(FArchive & Ar) +{ + //Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniGeoPartObjectVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniGeoPartObjectVersion; + + Ar << TransformMatrix; + + Ar << ObjectName; + Ar << PartName; + Ar << SplitName; + + // Serialize instancer material. + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME) + Ar << InstancerMaterialName; + + // Serialize instancer attribute material. + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME) + Ar << InstancerAttributeMaterialName; + + Ar << AssetId; + Ar << ObjectId; + Ar << GeoId; + Ar << PartId; + Ar << SplitId; + + Ar << HoudiniGeoPartObjectFlagsPacked; + + if (HoudiniGeoPartObjectVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + // Prior to this version the unused flags space was not zero-initialized, so + // zero them out now to prevent misinterpreting any 1s + HoudiniGeoPartObjectFlagsPacked &= 0x3FFFF; + } + + if (HoudiniGeoPartObjectVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH) + { + Ar << NodePath; + } +} + + +FHoudiniGeoPartObject +FHoudiniGeoPartObject_V1::ConvertLegacyData() +{ + FHoudiniGeoPartObject NewHGPO; + + NewHGPO.AssetId = AssetId; + // NewHGPO.AssetName; + + NewHGPO.ObjectId = ObjectId; + NewHGPO.ObjectName = ObjectName; + + NewHGPO.GeoId = GeoId; + + NewHGPO.PartId = PartId; + NewHGPO.PartName = PartName; + NewHGPO.bHasCustomPartName = bHasCustomName; + + NewHGPO.SplitGroups.Add(SplitName); + + NewHGPO.TransformMatrix = TransformMatrix; + NewHGPO.NodePath = NodePath; + + // NewHGPO.VolumeName; + // NewHGPO.VolumeTileIndex; + + NewHGPO.bIsVisible = bIsVisible; + NewHGPO.bIsEditable = bIsEditable; + // NewHGPO.bIsTemplated; + // NewHGPO.bIsInstanced; + + NewHGPO.bHasGeoChanged = bHasGeoChanged; + // NewHGPO.bHasPartChanged; + // NewHGPO.bHasTransformChanged; + // NewHGPO.bHasMaterialsChanged; + NewHGPO.bLoaded = true; //bIsLoaded; + + // Hamdle Part Type + if (bIsCurve) + { + NewHGPO.Type = EHoudiniPartType::Curve; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + else if (bIsVolume) + { + NewHGPO.Type = EHoudiniPartType::Volume; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + else if (bIsInstancer) + { + NewHGPO.Type = EHoudiniPartType::Instancer; + NewHGPO.InstancerType = EHoudiniInstancerType::ObjectInstancer; + } + else if (bIsPackedPrimitiveInstancer) + { + NewHGPO.Type = EHoudiniPartType::Instancer; + NewHGPO.InstancerType = EHoudiniInstancerType::PackedPrimitive; + } + else + { + NewHGPO.Type = EHoudiniPartType::Mesh; + NewHGPO.InstancerType = EHoudiniInstancerType::Invalid; + } + + // Instancer specific flags + if (NewHGPO.Type == EHoudiniPartType::Instancer) + { + //bInstancerMaterialAvailable + //bInstancerAttributeMaterialAvailable + //InstancerMaterialName + //InstancerAttributeMaterialName + } + + // Collision specific flags + if (NewHGPO.Type == EHoudiniPartType::Mesh) + { + //bIsCollidable + //bIsRenderCollidable + //bIsUCXCollisionGeo + //bIsSimpleCollisionGeo + //bHasCollisionBeenAdded + //bHasSocketBeenAdded + } + + //bIsBox + //bIsSphere + + if (NewHGPO.SplitGroups.Num() <= 0) + { + NewHGPO.SplitGroups.Add("main_geo"); + } + + return NewHGPO; +} + +UHoudiniAssetInput::UHoudiniAssetInput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} +void +UHoudiniAssetInput::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // Serialize current choice selection. + // Enum serialized as uint8 + // 0 GeometryInput + // 1 AssetInput + // 2 CurveInput + // 3 LandscapeInput + // 4 WorldInput + // 5 SkeletonInput + //SerializeEnumeration< EHoudiniAssetInputType::Enum >(Ar, ChoiceIndex); + Ar << ChoiceIndex; + + Ar << ChoiceStringValue; + + // We need these temporary variables for undo state tracking. + bool bLocalInputAssetConnectedInHoudini = bInputAssetConnectedInHoudini; + UHoudiniAssetComponent_V1 * LocalInputAssetComponent = InputAssetComponent; + + Ar << HoudiniAssetInputFlagsPacked; + + // Serialize input index. + Ar << InputIndex; + + // Serialize input objects (if it's assigned). + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT) + { + Ar << InputObjects; + } + else + { + UObject* InputObject = nullptr; + Ar << InputObject; + InputObjects.Empty(); + InputObjects.Add(InputObject); + } + + // Serialize input asset. + Ar << InputAssetComponent; + + // Serialize curve and curve parameters (if we have those). + Ar << InputCurve; + Ar << InputCurveParameters; + + // Serialize landscape used for input. + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT) + { + if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF) + { + ALandscapeProxy* InputLandscapePtr = nullptr; + Ar << InputLandscapePtr; + + InputLandscapeProxy = InputLandscapePtr; + } + else + { + Ar << InputLandscapeProxy; + } + + } + + // Serialize world outliner inputs. + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT) + { + Ar << InputOutlinerMeshArray; + } + + // Create necessary widget resources. + bLoadedParameter = true; + // If we're loading for real for the first time we need to reset this + // flag so we can reconnect when we get our parameters uploaded. + bInputAssetConnectedInHoudini = false; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT) + Ar << UnrealSplineResolution; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS) + { + Ar << InputTransforms; + } + else + { + InputTransforms.SetNum(InputObjects.Num()); + for (int32 n = 0; n < InputTransforms.Num(); n++) + InputTransforms[n] = FTransform::Identity; + } + + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + Ar << InputLandscapeTransform; +} + +UHoudiniInput* +UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) +{ + UHoudiniInput* Input = NewObject( + InOuter, UHoudiniInput::StaticClass(), FName(*ParameterLabel), RF_Transactional); + + EHoudiniInputType InputType = EHoudiniInputType::Invalid; + if (ChoiceIndex == 0) + InputType = EHoudiniInputType::Geometry; + else if (ChoiceIndex == 1) + InputType = EHoudiniInputType::Asset; + else if (ChoiceIndex == 2) + InputType = EHoudiniInputType::Curve; + else if (ChoiceIndex == 3) + InputType = EHoudiniInputType::Landscape; + else if (ChoiceIndex == 4) + InputType = EHoudiniInputType::World; + else if (ChoiceIndex == 5) + { + //InputType = EHoudiniInputType::Skeletal; + InputType = EHoudiniInputType::Invalid; + } + else + { + // Invalid + InputType = EHoudiniInputType::Invalid; + } + + bool bBlueprintStructureModified = false; + Input->SetInputType(InputType, bBlueprintStructureModified); + + Input->SetExportColliders(false); + Input->SetExportLODs(bExportAllLODs); + Input->SetExportSockets(bExportSockets); + Input->SetCookOnCurveChange(true); + + // If KeepWorldTransform is set to 2, use the default value + if (bKeepWorldTransform == 2) + Input->SetKeepWorldTransform((bool)Input->GetDefaultXTransformType()); + else + Input->SetKeepWorldTransform((bool)bKeepWorldTransform); + + Input->SetUnrealSplineResolution(UnrealSplineResolution); + Input->SetPackBeforeMerge(bPackBeforeMerge); + if(bIsObjectPathParameter) + Input->SetObjectPathParameter(ParmId); + else + Input->SetSOPInput(InputIndex); + + Input->SetImportAsReference(false); + Input->SetHelp(ParameterHelp); + //Input->SetInputNodeId(-1); + + if (bLandscapeExportAsHeightfield) + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Heightfield); + else if (bLandscapeExportAsMesh) + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Mesh); + else + Input->SetLandscapeExportType(EHoudiniLandscapeExportType::Points); + + Input->SetLabel(ParameterLabel); + Input->SetName(ParameterName); + Input->SetUpdateInputLandscape(bUpdateInputLandscape); + + if (InputType == EHoudiniInputType::Geometry) + { + // Get the geo input object array + bool bNeedToEmpty = true; + TArray* GeoInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(GeoInputObjectsPtr)) + { + // Add the geometry input objects + for (int32 AtIndex = 0; AtIndex < InputObjects.Num(); AtIndex++) + { + // Create a new InputObject wrapper + UObject* CurObject = InputObjects[AtIndex]; + if (!CurObject || CurObject->IsPendingKill()) + continue; + + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurObject, Input, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + continue; + + // Remove the default/null object + if (bNeedToEmpty) + { + GeoInputObjectsPtr->Empty(); + bNeedToEmpty = false; + } + + // Add to the geo input array + GeoInputObjectsPtr->Add(NewInputObject); + } + } + } + else if (InputType == EHoudiniInputType::Asset && InputAssetComponent != nullptr && !InputAssetComponent->IsPendingKill()) + { + // Get the asset input object array + TArray* AssetInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(AssetInputObjectsPtr)) + { + // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent + // We can simply use the v1's HAC outer for that + UHoudiniAssetComponent* InputHAC = Cast(InputAssetComponent->GetOuter()); + if (InputHAC && !InputHAC->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputHAC, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the asset input array + AssetInputObjectsPtr->Add(NewInputObject); + } + } + } + } + else if (InputType == EHoudiniInputType::Curve) + { + // Get the curve input object array + TArray* CurveInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(CurveInputObjectsPtr)) + { + if (InputCurve && !InputCurve->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputCurve, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the curve input array + CurveInputObjectsPtr->Add(NewInputObject); + } + + // InputCurve->SetInputObject(NewInputObject); + + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = Cast(NewInputObject); + if(HoudiniSplineInput) + HoudiniSplineInput->Update(InputCurve); + } + } + + // TODO ??? + //InputCurveParameters; + } + else if (InputType == EHoudiniInputType::Landscape) + { + // Get the Landscape input object array + TArray* LandscapeInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + if (ensure(LandscapeInputObjectsPtr)) + { + // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent + // We can simply use the v1's HAC outer for that + ALandscapeProxy* InLandscape = InputLandscapeProxy.Get(); + if (InLandscape && !InLandscape->IsPendingKill()) + { + // Create a new InputObject wrapper + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InLandscape, Input, FString::FromInt(0)); + if (ensure(NewInputObject)) + { + // Add to the geo input array + LandscapeInputObjectsPtr->Add(NewInputObject); + } + } + } + + Input->bLandscapeAutoSelectComponent = bLandscapeAutoSelectComponent; + Input->bLandscapeExportSelectionOnly = bLandscapeExportSelectionOnly; + Input->bLandscapeExportLighting = bLandscapeExportLighting; + Input->bLandscapeExportMaterials = bLandscapeExportMaterials; + Input->bLandscapeExportNormalizedUVs = bLandscapeExportNormalizedUVs; + Input->bLandscapeExportTileUVs = bLandscapeExportTileUVs; + + //bLandscapeExportCurves; + } + else if (InputType == EHoudiniInputType::World) + { + // Get the world input object array + TArray* WorldInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); + + UWorld* MyWorld = InOuter->GetWorld(); + if (ensure(WorldInputObjectsPtr)) + { + // Add the geometry input objects + for (int32 AtIndex = 0; AtIndex < InputOutlinerMeshArray.Num(); AtIndex++) + { + FHoudiniAssetInputOutlinerMesh_V1& CurWorldInObj = InputOutlinerMeshArray[AtIndex]; + + AActor* CurActor = nullptr; + if (CurWorldInObj.ActorPtr.IsValid()) + { + CurActor = CurWorldInObj.ActorPtr.Get(); + } + else + { + // Try to update the actor ptr via the pathname + CurWorldInObj.TryToUpdateActorPtrFromActorPathName(MyWorld); + if (CurWorldInObj.ActorPtr.IsValid()) + { + CurActor = CurWorldInObj.ActorPtr.Get(); + } + } + + if(!CurActor || CurActor->IsPendingKill()) + continue; + + // Create a new InputObject wrapper for the actor + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurActor, Input, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + continue; + + // Add to the geo input array + WorldInputObjectsPtr->Add(NewInputObject); + } + } + + /* + CurWorldInObj->HoudiniAssetParameterVersion; + CurWorldInObj->ActorPtr; + CurWorldInObj->ActorPathName; + CurWorldInObj->StaticMeshComponent; + CurWorldInObj->StaticMesh; + CurWorldInObj->SplineComponent; + CurWorldInObj->NumberOfSplineControlPoints; + CurWorldInObj->SplineControlPointsTransform; + CurWorldInObj->SplineLength; + CurWorldInObj->SplineResolution; + CurWorldInObj->ActorTransform; + CurWorldInObj->ComponentTransform; + CurWorldInObj->AssetId; + CurWorldInObj->KeepWorldTransform; + CurWorldInObj->MeshComponentsMaterials; + CurWorldInObj->InstanceIndex; + */ + + //InputOutlinerMeshArray; + } + else + { + // Invalid + } + + //ChoiceStringValue; + //bStaticMeshChanged; + //bSwitchedToCurve; + //bLoadedParameter = true; + //bInputAssetConnectedInHoudini; + //InputTransforms; + //InputLandscapeTransform; + + return Input; +} + +FArchive& +operator<<(FArchive& Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh) +{ + HoudiniAssetInputOutlinerMesh.Serialize(Ar); + return Ar; +} + +void +FHoudiniAssetInputOutlinerMesh_V1::Serialize(FArchive& Ar) +{ + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniAssetParameterVersion; + + Ar << ActorPtr; + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + { + Ar << ActorPathName; + } + + if (HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) + { + Ar << StaticMeshComponent; + Ar << StaticMesh; + } + + Ar << ActorTransform; + + Ar << AssetId; + if (Ar.IsLoading() && !Ar.IsTransacting()) + AssetId = -1; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE + && HoudiniAssetParameterVersion < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY) + { + Ar << SplineComponent; + Ar << NumberOfSplineControlPoints; + Ar << SplineLength; + Ar << SplineResolution; + Ar << ComponentTransform; + } + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM) + Ar << KeepWorldTransform; + + // UE4.19 SERIALIZATION FIX: + // The component materials serialization (24) was actually missing in the UE4.19 H17.0 / H16.5 plugin. + // However subsequent serialized changes (25+) were present in those version. This caused crashes when loading + // a level that was saved with 4.19+16.5/17.0 on a newer version of Unreal or Houdini... + // If the serialized version is exactly that of the fix, we can ignore the materials paths as well + if ((HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT) + && (HoudiniAssetParameterVersion != VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX)) + Ar << MeshComponentsMaterials; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX) + Ar << InstanceIndex; +} + +bool +FHoudiniAssetInputOutlinerMesh_V1::TryToUpdateActorPtrFromActorPathName(UWorld* InWorld) +{ + // Ensure our current ActorPathName looks valid + if (ActorPathName.IsEmpty() || ActorPathName.Equals(TEXT("None"), ESearchCase::IgnoreCase)) + return false; + + // We'll try to find the corresponding actor by browsing through all the actors in the world.. + // Get the editor world + UWorld* World = InWorld; + if (!World) + return false; + + // Then try to find the actor corresponding to our path/name + bool FoundActor = false; + for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) + { + if (ActorIt->GetPathName() != ActorPathName) + continue; + + // We found the actor + ActorPtr = *ActorIt; + FoundActor = true; + + break; + } + + if (FoundActor) + { + // We need to invalid our components so they can be updated later + // from the new actor + StaticMesh = NULL; + StaticMeshComponent = NULL; + SplineComponent = NULL; + } + + return FoundActor; +} + +UHoudiniAssetComponentMaterials_V1::UHoudiniAssetComponentMaterials_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} +void +UHoudiniAssetComponentMaterials_V1::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Assignments; + Ar << Replacements; +} + +UHoudiniHandleComponent_V1::UHoudiniHandleComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniAssetInstanceInput::UHoudiniAssetInstanceInput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetInstanceInput::Serialize(FArchive& Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Flags.HoudiniAssetInstanceInputFlagsPacked; + Ar << HoudiniGeoPartObject; + + Ar << ObjectToInstanceId; + // Object id is transient + if (Ar.IsLoading() && !Ar.IsTransacting()) + ObjectToInstanceId = -1; + + // Serialize fields. + Ar << InstanceInputFields; +} + +UHoudiniAssetInstanceInputField::UHoudiniAssetInstanceInputField(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetInstanceInputField::Serialize(FArchive& Ar) +{ + // Call base implementation first. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + const int32 InstanceInputFieldVersion = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + + Ar << HoudiniAssetInstanceInputFieldFlagsPacked; + Ar << HoudiniGeoPartObject; + + FString UnusedInstancePathName; + Ar << UnusedInstancePathName; + Ar << RotationOffsets; + Ar << ScaleOffsets; + Ar << bScaleOffsetsLinearlyArray; + + Ar << InstancedTransforms; + Ar << VariationTransformsArray; + + if (Ar.IsSaving() || (Ar.IsLoading() && InstanceInputFieldVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS)) + { + Ar << InstanceColorOverride; + Ar << VariationInstanceColorOverrideArray; + } + + Ar << InstancerComponents; + Ar << InstancedObjects; + Ar << OriginalObject; +} + +void +UHoudiniHandleComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + // XFormn Parames is an array of 9 float params + tuple index + // TX TY TZ + // RX RY RZ + // SX SY SZ + + //UHoudiniAssetParameterFloat_V1* XFormParams[9]; + //int32 XFormParamsTupleIndex[9]; + for (int32 i = 0; i < 9; ++i) + { + Ar << XFormParams[i]; + Ar << XFormParamsTupleIndex[i]; + } + + //UHoudiniAssetParameterChoice_V1* RSTParm; + Ar << RSTParm; + //int32 RSTParmTupleIdx; + Ar << RSTParmTupleIdx; + + //UHoudiniAssetParameterChoice_V1* RotOrderParm; + Ar << RotOrderParm; + //int32 RotOrderParmTupleIdx; + Ar << RotOrderParmTupleIdx; +} + +/* +UHoudiniHandleComponent* +UHoudiniHandleComponent_V1::ConvertLegacyData(UObject* Outer) +{ + UHoudiniHandleComponent* NewHandle = nullptr; + + return NewHandle; +} +*/ + +bool +UHoudiniHandleComponent_V1::UpdateFromLegacyData(UHoudiniHandleComponent* NewHC) +{ + if (!NewHC || NewHC->IsPendingKill()) + return false; + + // TODO + //NewHC->XformParms; + //NewHC->RSTParm; + //NewHC->RotOrderParm; + //NewHC->HandleType; + //NewHC->HandleName; + + return true; +} + +UHoudiniSplineComponent_V1::UHoudiniSplineComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniSplineComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + int32 Version = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << Version; + + Ar << HoudiniGeoPartObject; + + if (Version < VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM) + { + // Before, curve points where stored as Vectors, not Transforms + TArray OldCurvePoints; + Ar << OldCurvePoints; + + CurvePoints.SetNumUninitialized(OldCurvePoints.Num()); + + FTransform trans = FTransform::Identity; + for (int32 n = 0; n < CurvePoints.Num(); n++) + { + trans.SetLocation(OldCurvePoints[n]); + CurvePoints[n] = trans; + } + } + else + { + Ar << CurvePoints; + } + + Ar << CurveDisplayPoints; + + Ar << CurveType; + Ar << CurveMethod; + Ar << bClosedCurve; +} + +UHoudiniSplineComponent* +UHoudiniSplineComponent_V1::ConvertLegacyData(UObject* Outer) +{ + UHoudiniSplineComponent* NewSpline = NewObject( + GetOuter(), UHoudiniSplineComponent::StaticClass()); + + UpdateFromLegacyData(NewSpline); + + return NewSpline; +} + +bool +UHoudiniSplineComponent_V1::UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline) +{ + if (!NewSpline || NewSpline->IsPendingKill()) + return false; + + NewSpline->SetFlags(RF_Transactional); + + NewSpline->CurvePoints = CurvePoints; + NewSpline->DisplayPoints = CurveDisplayPoints; + //NewSpline->DisplayPointIndexDivider; + //NewSpline->HoudiniSplineName; + NewSpline->bClosed = bClosedCurve; + NewSpline->bReversed = false; + NewSpline->bIsHoudiniSplineVisible = true; + + //0 Polygon 1 Nurbs 2 Bezier + if (CurveType == 0) + NewSpline->CurveType = EHoudiniCurveType::Polygon; + else if (CurveType == 1) + NewSpline->CurveType = EHoudiniCurveType::Nurbs; + else if (CurveType == 2) + NewSpline->CurveType = EHoudiniCurveType::Bezier; + else + NewSpline->CurveType = EHoudiniCurveType::Invalid; + + // 0 CVs, 1 Breakpoints, 2 Freehand + if (CurveMethod == 0) + NewSpline->CurveMethod = EHoudiniCurveMethod::CVs; + else if (CurveMethod == 1) + NewSpline->CurveMethod = EHoudiniCurveMethod::Breakpoints; + else if (CurveMethod == 2) + NewSpline->CurveMethod = EHoudiniCurveMethod::Freehand; + else + NewSpline->CurveMethod = EHoudiniCurveMethod::Invalid; + + NewSpline->bIsOutputCurve = false; + + NewSpline->HoudiniGeoPartObject = HoudiniGeoPartObject.ConvertLegacyData(); + + if (NewSpline->HoudiniGeoPartObject.bIsEditable) + { + NewSpline->bIsEditableOutputCurve = true; + NewSpline->bIsInputCurve = false; + } + else + { + NewSpline->bIsInputCurve = false; + NewSpline->bIsEditableOutputCurve = true; + } + + // Create a default Houdini spline input if a null pointer is passed in. + FName HoudiniSplineName = MakeUniqueObjectName(GetOuter(), UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); + NewSpline->SetHoudiniSplineName(HoudiniSplineName.ToString()); + + //NewSpline->bHasChanged; + //NewSpline->bNeedsToTriggerUpdate; + //NewSpline->InputObject; + //NewSpline->NodeId; + //NewSpline->PartName; + + return true; +} + +UHoudiniAssetParameter::UHoudiniAssetParameter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameter::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + HoudiniAssetParameterVersion = VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION; + Ar << HoudiniAssetParameterVersion; + + Ar << HoudiniAssetParameterFlagsPacked; + + if (Ar.IsLoading()) + bChanged = false; + + Ar << ParameterName; + Ar << ParameterLabel; + + Ar << NodeId; + if (!Ar.IsTransacting() && Ar.IsLoading()) + { + // NodeId is invalid after load + NodeId = -1; + } + Ar << ParmId; + + Ar << ParmParentId; + Ar << ChildIndex; + + Ar << TupleSize; + Ar << ValuesIndex; + Ar << MultiparmInstanceIndex; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER) + { + UObject* Dummy = nullptr; + Ar << Dummy; + } + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP) + { + Ar << ParameterHelp; + } + else + { + ParameterHelp = TEXT(""); + } + /* + if (Ar.IsTransacting()) + { + Ar << PrimaryObject; + Ar << ParentParameter; + } + */ +} + +UHoudiniParameter* +UHoudiniAssetParameter::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameter::Create(Outer, ParameterName); +} + +void +UHoudiniAssetParameter::CopyLegacyParameterData(UHoudiniParameter* InNewParm) +{ + if (!InNewParm || InNewParm->IsPendingKill()) + return; + + InNewParm->Name = ParameterName; + InNewParm->Label = ParameterLabel; + //InNewParm->ParmType; + InNewParm->TupleSize = TupleSize; + InNewParm->NodeId = NodeId; + InNewParm->ParmId = ParmId; + InNewParm->ParentParmId = ParmParentId; + InNewParm->ChildIndex = ChildIndex; + InNewParm->bIsVisible = true; + InNewParm->bIsDisabled = bIsDisabled; + InNewParm->bHasChanged = bChanged; + //InNewParm->bNeedsToTriggerUpdate; + //InNewParm->bIsDefault; + InNewParm->bIsSpare = bIsSpare; + InNewParm->bJoinNext = false; + InNewParm->bIsChildOfMultiParm = bIsChildOfMultiparm; + // TODO: MultiparmInstanceIndex ? + //InNewParm->bIsDirectChildOfMultiParm; + InNewParm->bPendingRevertToDefault = false; + //InNewParm->TuplePendingRevertToDefault = false; + InNewParm->Help = ParameterHelp; + InNewParm->TagCount = 0; + InNewParm->ValueIndex = ValuesIndex; + //InNewParm->bHasExpression; + //InNewParm->bShowExpression; + //InNewParm->ParamExpression; + //InNewParm->Tags; + InNewParm->bAutoUpdate = true; +} + +UHoudiniAssetParameterChoice::UHoudiniAssetParameterChoice(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterChoice::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + { + StringChoiceValues.Empty(); + StringChoiceLabels.Empty(); + } + + int32 NumChoices = StringChoiceValues.Num(); + Ar << NumChoices; + + int32 NumLabels = StringChoiceLabels.Num(); + Ar << NumLabels; + + if (Ar.IsLoading()) + { + FString Temp; + for (int32 ChoiceIdx = 0; ChoiceIdx < NumChoices; ++ChoiceIdx) + { + Ar << Temp; + StringChoiceValues.Add(Temp); + } + + for (int32 LabelIdx = 0; LabelIdx < NumLabels; ++LabelIdx) + { + Ar << Temp; + StringChoiceLabels.Add(Temp); + } + } + + Ar << StringValue; + Ar << CurrentValue; + + Ar << bStringChoiceList; +} + +UHoudiniParameter* +UHoudiniAssetParameterChoice::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterChoice* Parm = nullptr; + if (bStringChoiceList) + { + Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::StringChoice); + } + else + { + Parm = UHoudiniParameterChoice::Create(Outer, ParameterName, EHoudiniParameterType::IntChoice); + } + + Parm->SetNumChoices(StringChoiceValues.Num()); + for (int32 Idx = 0; Idx < StringChoiceValues.Num(); Idx++) + { + FString * ChoiceValue = Parm->GetStringChoiceValueAt(Idx); + if (ChoiceValue) + *ChoiceValue = StringChoiceValues[Idx]; + FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); + } + + for (int32 Idx = 0; Idx < StringChoiceLabels.Num(); Idx++) + { + FString * ChoiceLabel = Parm->GetStringChoiceLabelAt(Idx); + if (ChoiceLabel) + *ChoiceLabel = StringChoiceValues[Idx]; + } + + Parm->SetStringValue(StringValue); + Parm->SetIntValue(CurrentValue); + //Parm->DefaultStringValue = StringValue; + //Parm->SetDefault(); + //Parm->DefaultIntValue = CurrentValue; + + return Parm; +} + +UHoudiniAssetParameterButton::UHoudiniAssetParameterButton(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterButton::ConvertLegacyData(UObject* Outer) +{ + // Button strips where not supported in v1, just create a normal button + return UHoudiniParameterButton::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterColor::UHoudiniAssetParameterColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterColor::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + Color = FColor::White; + + Ar << Color; +} + +UHoudiniParameter* +UHoudiniAssetParameterColor::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterColor* Parm = UHoudiniParameterColor::Create(Outer, ParameterName); + Parm->SetColorValue(Color); + + //Parm->DefaultColor = Color; + Parm->SetDefaultValue(); + + //Parm->bIsChildOfRamp = false; + + return Parm; +} + +UHoudiniAssetParameterFile::UHoudiniAssetParameterFile(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterFile::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + Ar << Filters; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY) + Ar << IsReadOnly; +} + +UHoudiniParameter* +UHoudiniAssetParameterFile::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterFile* Parm = UHoudiniParameterFile::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + Parm->SetFileFilters(Filters); + Parm->SetReadOnly(IsReadOnly); + + return Parm; +} + +UHoudiniAssetParameterFloat::UHoudiniAssetParameterFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterFloat::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + + Ar << ValueMin; + Ar << ValueMax; + + Ar << ValueUIMin; + Ar << ValueUIMax; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) + Ar << ValueUnit; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP) + Ar << NoSwap; +} + +UHoudiniParameter* +UHoudiniAssetParameterFloat::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterFloat* Parm = UHoudiniParameterFloat::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + Parm->SetUnit(ValueUnit); + Parm->SetHasMin(true); + Parm->SetHasMax(true); + Parm->SetHasUIMin(true); + Parm->SetHasUIMax(true); + Parm->SetIsLogarithmic(false); + Parm->SetMin(ValueMin); + Parm->SetMax(ValueMax); + Parm->SetUIMin(ValueUIMin); + Parm->SetUIMax(ValueUIMax); + + //Parm->bIsChildOfRamp = false; + + return Parm; +} + +UHoudiniAssetParameterFolder::UHoudiniAssetParameterFolder(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterFolder::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterFolder::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterFolderList::UHoudiniAssetParameterFolderList(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterFolderList::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterFolderList::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterInt::UHoudiniAssetParameterInt(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterInt::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; + + Ar << ValueMin; + Ar << ValueMax; + + Ar << ValueUIMin; + Ar << ValueUIMax; + + if (HoudiniAssetParameterVersion >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT) + Ar << ValueUnit; +} + +UHoudiniParameter* +UHoudiniAssetParameterInt::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterInt* Parm = UHoudiniParameterInt::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetDefaultValues(); + + //Parm->DefaultValues = Values; + Parm->SetUnit(ValueUnit); + Parm->SetHasMin(true); + Parm->SetHasMax(true); + Parm->SetHasUIMin(true); + Parm->SetHasUIMax(true); + Parm->SetIsLogarithmic(false); + Parm->SetMin(ValueMin); + Parm->SetMax(ValueMax); + Parm->SetUIMin(ValueUIMin); + Parm->SetUIMax(ValueUIMax); + + return Parm; +} + +UHoudiniAssetParameterLabel::UHoudiniAssetParameterLabel(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterLabel::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterLabel::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterMultiparm::UHoudiniAssetParameterMultiparm(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterMultiparm::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + if (Ar.IsLoading()) + MultiparmValue = 0; + + Ar << MultiparmValue; +} + +UHoudiniParameter* +UHoudiniAssetParameterMultiparm::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterMultiParm* Parm = UHoudiniParameterMultiParm::Create(Outer, ParameterName); + + //Parm->bIsShown; + //Parm->Value; + //Parm->TemplateName; + Parm->MultiparmValue = MultiparmValue; + //Parm->MultiParmInstanceNum; + //Parm->MultiParmInstanceLength; + //Parm->MultiParmInstanceCount; + //Parm->InstanceStartOffset; + //Parm->DefaultInstanceCount; + + // TODO: + // MultiparmInstanceIndex? + + return Parm; +} + +UHoudiniAssetParameterRamp::UHoudiniAssetParameterRamp(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterRamp::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + int32 multiparmvalue = 0; + Ar << multiparmvalue; + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << HoudiniAssetParameterRampCurveFloat; + Ar << HoudiniAssetParameterRampCurveColor; + + Ar << bIsFloatRamp; +} + +UHoudiniParameter* +UHoudiniAssetParameterRamp::ConvertLegacyData(UObject* Outer) +{ + if (bIsFloatRamp) + { + UHoudiniParameterRampFloat* Parm = UHoudiniParameterRampFloat::Create(Outer, ParameterName); + + // TODO: + // Convert HoudiniAssetParameterRampCurveFloat + + return Parm; + } + else + { + UHoudiniParameterRampColor* Parm = UHoudiniParameterRampColor::Create(Outer, ParameterName); + + // TODO: + // Convert HoudiniAssetParameterRampCurveColor + + return Parm; + } +} + +UHoudiniAssetParameterSeparator::UHoudiniAssetParameterSeparator(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UHoudiniParameter* +UHoudiniAssetParameterSeparator::ConvertLegacyData(UObject* Outer) +{ + return UHoudiniParameterSeparator::Create(Outer, ParameterName); +} + +UHoudiniAssetParameterString::UHoudiniAssetParameterString(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterString::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; +} + +UHoudiniParameter* +UHoudiniAssetParameterString::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterString* Parm = UHoudiniParameterString::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt(Values[Idx], Idx); + + Parm->SetIsAssetRef(false); + Parm->SetDefaultValues(); + + //Parm->DefaultValues = Values; + //Parm->ChosenAssets.Empty(); + //Parm->bIsAssetRef = false; + + return Parm; +} + +UHoudiniAssetParameterToggle::UHoudiniAssetParameterToggle(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniAssetParameterToggle::Serialize(FArchive & Ar) +{ + // Call base implementation. + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << Values; +} + +UHoudiniParameter* +UHoudiniAssetParameterToggle::ConvertLegacyData(UObject* Outer) +{ + UHoudiniParameterToggle* Parm = UHoudiniParameterToggle::Create(Outer, ParameterName); + + Parm->SetNumberOfValues(Values.Num()); + for (int32 Idx = 0; Idx < Values.Num(); Idx++) + Parm->SetValueAt((bool)Values[Idx], Idx); + + Parm->SetDefaultValues(); + //Parm->DefaultValues = Values; + + return Parm; +} + +UHoudiniMeshSplitInstancerComponent_V1::UHoudiniMeshSplitInstancerComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniMeshSplitInstancerComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << InstancedMesh; + Ar << OverrideMaterial; + Ar << Instances; +} + +bool +UHoudiniMeshSplitInstancerComponent_V1::UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC) +{ + if (!NewMSIC || NewMSIC->IsPendingKill()) + return false; + + NewMSIC->Instances = Instances; + NewMSIC->OverrideMaterials.Add(OverrideMaterial); + NewMSIC->InstancedMesh = InstancedMesh; + + return true; +} + +UHoudiniInstancedActorComponent_V1::UHoudiniInstancedActorComponent_V1(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void +UHoudiniInstancedActorComponent_V1::Serialize(FArchive & Ar) +{ + //Super::Serialize(Ar); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + Ar << InstancedAsset; + Ar << Instances; +} + +bool +UHoudiniInstancedActorComponent_V1::UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC) +{ + if (!NewIAC || NewIAC->IsPendingKill()) + return false; + + //NewIAC->SetInstancedObject(InstancedAsset); + NewIAC->InstancedObject = InstancedAsset; + NewIAC->InstancedActors = Instances; + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h index 7a9dadb63..d35bb3898 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.h @@ -1,1092 +1,1092 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniAsset.h" - -#include "Components/PrimitiveComponent.h" - -#include "Curves/CurveFloat.h" -#include "Curves/CurveLinearColor.h" - -#include "HoudiniCompatibilityHelpers.generated.h" - -class UStaticMesh; -class UStaticMeshComponent; -class USplineComponent; -class ALandscapeProxy; -class UMaterialInterface; -class UHoudiniInput; -class UHoudiniParameter; -class UHoudiniHandleComponent; -class UHoudiniSplineComponent; -class UHoudiniInstancedActorComponent; -class UHoudiniMeshSplitInstancerComponent; -class UFoliageType_InstancedStaticMesh; - - -struct FHoudiniGeoPartObject; - - -struct FHoudiniGeoPartObject_V1 -{ -public: - - void Serialize(FArchive & Ar); - - FHoudiniGeoPartObject ConvertLegacyData(); - - /** Return hash value for this object, used when using this object as a key inside hashing containers. **/ - uint32 GetTypeHash() const; - - /** Transform of this geo part object. **/ - FTransform TransformMatrix; - - /** Name of associated object. **/ - FString ObjectName; - - /** Name of associated part. **/ - FString PartName; - - /** Name of group which was used for splitting, empty if there's none. **/ - FString SplitName; - - /** Name of the instancer material, if available. **/ - FString InstancerMaterialName; - - /** Name of attribute material, if available. **/ - FString InstancerAttributeMaterialName; - - /** Id of corresponding HAPI Asset. **/ - //HAPI_NodeId AssetId; - int AssetId; - - /** Id of corresponding HAPI Object. **/ - //HAPI_NodeId ObjectId; - int ObjectId; - - /** Id of corresponding HAPI Geo. **/ - //HAPI_NodeId GeoId; - int GeoId; - - /** Id of corresponding HAPI Part. **/ - //HAPI_PartId PartId; - int PartId; - - /** Id of a split. In most cases this will be 0. **/ - int32 SplitId; - - /** Path to the corresponding node */ - mutable FString NodePath; - - /** Flags used by geo part object. **/ - union - { - struct - { - /* Is set to true when referenced object is visible. This is typically used by instancers. **/ - uint32 bIsVisible : 1; - - /** Is set to true when referenced object is an instancer. **/ - uint32 bIsInstancer : 1; - - /** Is set to true when referenced object is a curve. **/ - uint32 bIsCurve : 1; - - /** Is set to true when referenced object is editable. **/ - uint32 bIsEditable : 1; - - /** Is set to true when geometry has changed. **/ - uint32 bHasGeoChanged : 1; - - /** Is set to true when referenced object is collidable. **/ - uint32 bIsCollidable : 1; - - /** Is set to true when referenced object is collidable and is renderable. **/ - uint32 bIsRenderCollidable : 1; - - /** Is set to true when referenced object has just been loaded. **/ - uint32 bIsLoaded : 1; - - /** Unused flags. **/ - uint32 bPlaceHolderFlags : 3; - - /** Is set to true when referenced object has been loaded during transaction. **/ - uint32 bIsTransacting : 1; - - /** Is set to true when referenced object has a custom name. **/ - uint32 bHasCustomName : 1; - - /** Is set to true when referenced object is a box. **/ - uint32 bIsBox : 1; - - /** Is set to true when referenced object is a sphere. **/ - uint32 bIsSphere : 1; - - /** Is set to true when instancer material is available. **/ - uint32 bInstancerMaterialAvailable : 1; - - /** Is set to true when referenced object is a volume. **/ - uint32 bIsVolume : 1; - - /** Is set to true when instancer attribute material is available. **/ - uint32 bInstancerAttributeMaterialAvailable : 1; - - /** Is set when referenced object contains packed primitive instancing */ - uint32 bIsPackedPrimitiveInstancer : 1; - - /** Is set to true when referenced object is a UCX collision geo. **/ - uint32 bIsUCXCollisionGeo : 1; - - /** Is set to true when referenced object is a rendered UCX collision geo. **/ - uint32 bIsSimpleCollisionGeo : 1; - - /** Is set to true when new collision geo has been generated **/ - uint32 bHasCollisionBeenAdded : 1; - - /** Is set to true when new sockets have been added **/ - uint32 bHasSocketBeenAdded : 1; - - /** unused flag space is zero initialized */ - uint32 UnusedFlagsSpace : 14; - }; - - uint32 HoudiniGeoPartObjectFlagsPacked; - }; - - /** Temporary variable holding serialization version. **/ - uint32 HoudiniGeoPartObjectVersion; -}; - -/** Function used by hashing containers to create a unique hash for this type of object. **/ -uint32 GetTypeHash(const FHoudiniGeoPartObject_V1 & HoudiniGeoPartObject); - -/** Serialization function. **/ -FArchive& operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject); - -/** Functor used to sort geo part objects. **/ -struct FHoudiniGeoPartObject_V1SortPredicate -{ - bool operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const; -}; - - -struct FHoudiniAssetInputOutlinerMesh_V1 -{ - /** Serialization. **/ - void Serialize(FArchive & Ar); - - /** Update the Actor pointer from the store Actor path/name **/ - bool TryToUpdateActorPtrFromActorPathName(UWorld* InWorld); - - /** Temporary variable holding serialization version. **/ - uint32 HoudiniAssetParameterVersion; - - /** Selected Actor. **/ - TWeakObjectPtr ActorPtr = nullptr; - - /** Selected Actor's path, used to find the actor back after loading. **/ - FString ActorPathName = TEXT("NONE"); - - /** Selected mesh's component, for reference. **/ - UStaticMeshComponent * StaticMeshComponent = nullptr; - - /** The selected mesh. **/ - UStaticMesh * StaticMesh = nullptr; - - /** Spline Component **/ - USplineComponent * SplineComponent = nullptr; - - /** Number of CVs used by the spline component, used to detect modification **/ - int32 NumberOfSplineControlPoints = -1; - - /** Transform of the UnrealSpline CVs, used to detect modification of the spline (Rotation/Scale) **/ - TArray SplineControlPointsTransform; - - /** Spline Length, used to detect modification of the spline.. **/ - float SplineLength = -1.0f; - - /** Spline resolution used to generate the asset, used to detect setting modification **/ - float SplineResolution = -1.0f; - - /** Actor transform used to see if the transfrom changed since last marshal into Houdini. **/ - FTransform ActorTransform; - - /** Component transform used to see if the transform has changed since last marshalling **/ - FTransform ComponentTransform; - - /** Mesh's input asset id. **/ - //HAPI_NodeId AssetId = -1; - int AssetId = -1; - - /** TranformType used to generate the asset **/ - int32 KeepWorldTransform = 2; - - /** Path Materials assigned on the SMC **/ - TArray MeshComponentsMaterials; - - /** If the world In is a ISM, index of this instance **/ - uint32 InstanceIndex = -1; -}; - -/** Serialization function. **/ -FArchive & operator<<(FArchive & Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh); - -/* -UCLASS(EditInlineNew, config = Engine) -class UHoudiniAsset_V1 -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; -}; -*/ - -UCLASS() -class UHoudiniAssetParameter : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer); - - void CopyLegacyParameterData(UHoudiniParameter* InNewParm); - - /** Name of this parameter. **/ - FString ParameterName; - - /** Label of this parameter. **/ - FString ParameterLabel; - - /** Node this parameter belongs to. **/ - int NodeId; - - /** Id of this parameter. **/ - int ParmId; - - /** Id of parent parameter, -1 if root is parent. **/ - int ParmParentId; - - /** Child index within its parent parameter. **/ - int32 ChildIndex; - - /** Tuple size - arrays. **/ - int32 TupleSize; - - /** Internal HAPI cached value index. **/ - int32 ValuesIndex; - - /** The multiparm instance index. **/ - int32 MultiparmInstanceIndex; - - /** The parameter's help, to be used as a tooltip **/ - FString ParameterHelp; - - /** Flags used by this parameter. **/ - union - { - struct - { - /** Is set to true if this parameter is spare, that is, created by Houdini Engine only. **/ - uint32 bIsSpare : 1; - - /** Is set to true if this parameter is disabled. **/ - uint32 bIsDisabled : 1; - - /** Is set to true if value of this parameter has been changed by user. **/ - uint32 bChanged : 1; - - /** Is set to true when parameter's slider (if it has one) is being dragged. Transient. **/ - uint32 bSliderDragged : 1; - - /** Is set to true if the parameter is a multiparm child parameter. **/ - uint32 bIsChildOfMultiparm : 1; - - /** Is set to true if this parameter is a Substance parameter. **/ - uint32 bIsSubstanceParameter : 1; - - /** Is set to true if this parameter is a multiparm **/ - uint32 bIsMultiparm : 1; - }; - - uint32 HoudiniAssetParameterFlagsPacked; - }; - - /** Temporary variable holding parameter serialization version. **/ - uint32 HoudiniAssetParameterVersion; -}; - -UCLASS() -class UHoudiniAssetParameterButton : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterChoice : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Choice values for this property. **/ - TArray StringChoiceValues; - - /** Choice labels for this property. **/ - TArray StringChoiceLabels; - - /** Value of this property. **/ - FString StringValue; - - /** Current value for this property. **/ - int32 CurrentValue; - - /** Is set to true when this choice list is a string choice list. **/ - bool bStringChoiceList; -}; - -UCLASS() -class UHoudiniAssetParameterColor : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Color for this property. **/ - FLinearColor Color; -}; - -UCLASS() -class UHoudiniAssetParameterFile : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< FString > Values; - - /** Filters of this property. **/ - FString Filters; - - /** Is the file parameter read-only? **/ - bool IsReadOnly; -}; - -UCLASS() -class UHoudiniAssetParameterFloat : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< float > Values; - - /** Min and Max values for this property. **/ - float ValueMin; - float ValueMax; - - /** Min and Max values for UI for this property. **/ - float ValueUIMin; - float ValueUIMax; - - /** Unit for this property **/ - FString ValueUnit; - - /** Do we have the noswap tag? **/ - bool NoSwap; -}; - -UCLASS() -class UHoudiniAssetParameterFolder : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterFolderList : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterInt : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< int32 > Values; - - /** Min and Max values for this property. **/ - int32 ValueMin; - int32 ValueMax; - - /** Min and Max values for UI for this property. **/ - int32 ValueUIMin; - int32 ValueUIMax; - - /** Unit for this property **/ - FString ValueUnit; -}; - -UCLASS() -class UHoudiniAssetParameterLabel : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterMultiparm : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Value of this property. **/ - int32 MultiparmValue; -}; - -UCLASS() -class UHoudiniAssetParameterRamp : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - void CopyLegacyParameterData(UHoudiniParameter* InNewParm); - - //! Curves which are being edited. - UCurveFloat * HoudiniAssetParameterRampCurveFloat; - UCurveLinearColor * HoudiniAssetParameterRampCurveColor; - - //! Set to true if this ramp is a float ramp. Otherwise is considered a color ramp. - bool bIsFloatRamp; -}; - -UCLASS() -class UHoudiniAssetParameterSeparator : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; -}; - -UCLASS() -class UHoudiniAssetParameterString : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< FString > Values; -}; - -UCLASS() -class UHoudiniAssetParameterToggle : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; - - /** Values of this property. **/ - TArray< int32 > Values; -}; - -UCLASS() -class UHoudiniAssetComponentMaterials_V1 : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** Material assignments. **/ - TMap Assignments; - - /** Material replacements. **/ - TMap> Replacements; -}; - -UCLASS() -class UHoudiniHandleComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniHandleComponent* NewHC); - - //virtual UHoudiniHandleComponent* ConvertLegacyData(UObject* Outer); - - UHoudiniAssetParameterFloat* XFormParams[9]; - int32 XFormParamsTupleIndex[9]; - - UHoudiniAssetParameterChoice* RSTParm; - int32 RSTParmTupleIdx; - - UHoudiniAssetParameterChoice* RotOrderParm; - int32 RotOrderParmTupleIdx; -}; - -UCLASS() -class UHoudiniSplineComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - virtual UHoudiniSplineComponent* ConvertLegacyData(UObject* Outer); - - bool UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline); - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** List of points composing this curve. **/ - TArray CurvePoints; - - /** List of refined points used for drawing. **/ - TArray CurveDisplayPoints; - - /** Type of this curve. **/ - // 0 Polygon 1 Nurbs 2 Bezier - uint8 CurveType; - - /** Method used for this curve. **/ - // 0 CVs, 1 Breakpoints, 2 Freehand - uint8 CurveMethod; - - /** Whether this spline is closed. **/ - bool bClosedCurve; -}; - -UCLASS() -class UHoudiniAssetInput : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - UHoudiniInput* ConvertLegacyInput(UObject* Outer); - - // Input type: - // 0 GeometryInput - // 1 AssetInput - // 2 CurveInput - // 3 LandscapeInput - // 4 WorldInput - // 5 SkeletonInput - uint8 ChoiceIndex; - - /** Value of choice option. **/ - FString ChoiceStringValue; - - /** Index of this input. **/ - int32 InputIndex; - - /** Objects used for geometry input. **/ - TArray InputObjects; - - /** Houdini spline component which is used for curve input. **/ - UHoudiniSplineComponent * InputCurve; - - /** Houdini asset component pointer of the input asset (actor). **/ - UHoudiniAssetComponent_V1 * InputAssetComponent; - - /** Landscape actor used for input. **/ - TSoftObjectPtr InputLandscapeProxy; - - /** List of selected meshes and actors from the World Outliner. **/ - TArray InputOutlinerMeshArray; - - /** Parameters used by a curve input asset. **/ - TMap InputCurveParameters; - - float UnrealSplineResolution; - - /** Array containing the transform corrections for the assets in a geometry input **/ - TArray InputTransforms; - - /** Transform used by the input landscape **/ - FTransform InputLandscapeTransform; - - /** Flags used by this input. **/ - union - { - struct - { - /** Is set to true when static mesh used for geometry input has changed. **/ - uint32 bStaticMeshChanged : 1; - - /** Is set to true when choice switches to curve mode. **/ - uint32 bSwitchedToCurve : 1; - - /** Is set to true if this parameter has been loaded. **/ - uint32 bLoadedParameter : 1; - - /** Is set to true if the asset input is actually connected inside Houdini. **/ - uint32 bInputAssetConnectedInHoudini : 1; - - /** Is set to true when landscape input is set to selection only. **/ - uint32 bLandscapeExportSelectionOnly : 1; - - /** Is set to true when landscape curves are to be exported. **/ - uint32 bLandscapeExportCurves : 1; - - /** Is set to true when the landscape is to be exported as a mesh, not just points. **/ - uint32 bLandscapeExportAsMesh : 1; - - /** Is set to true when materials are to be exported. **/ - uint32 bLandscapeExportMaterials : 1; - - /** Is set to true when lightmap information export is desired. **/ - uint32 bLandscapeExportLighting : 1; - - /** Is set to true when uvs should be exported in [0,1] space. **/ - uint32 bLandscapeExportNormalizedUVs : 1; - - /** Is set to true when uvs should be exported for each tile separately. **/ - uint32 bLandscapeExportTileUVs : 1; - - /** Is set to true when being used as an object-path parameter instead of an input */ - uint32 bIsObjectPathParameter : 1; - - /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value **/ - uint32 bKeepWorldTransform : 2; - - /** Is set to true when the landscape is to be exported as a heightfield **/ - uint32 bLandscapeExportAsHeightfield : 1; - - /** Is set to true when the automatic selection of landscape component is active **/ - uint32 bLandscapeAutoSelectComponent : 1; - - /** Indicates that the geometry must be packed before merging it into the input **/ - uint32 bPackBeforeMerge : 1; - - /** Indicates that all LODs in the input should be marshalled to Houdini **/ - uint32 bExportAllLODs : 1; - - /** Indicates that all sockets in the input should be marshalled to Houdini **/ - uint32 bExportSockets : 1; - - /** Indicates that the landscape input's source landscape should be updated instead of creating a new component **/ - uint32 bUpdateInputLandscape : 1; - }; - - uint32 HoudiniAssetInputFlagsPacked; - }; -}; - -UCLASS() -class UHoudiniAssetInstanceInput : public UHoudiniAssetParameter -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** List of fields created by this instance input. **/ - TArray InstanceInputFields; - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** Id of an object to instance. **/ - int ObjectToInstanceId; - -public: - /** Flags used by this input. **/ - union FHoudiniAssetInstanceInputFlags - { - struct - { - /** Set to true if this is an attribute instancer. **/ - uint32 bIsAttributeInstancer : 1; - - /** Set to true if this attribute instancer uses overrides. **/ - uint32 bAttributeInstancerOverride : 1; - - /** Set to true if this is a packed primitive instancer **/ - uint32 bIsPackedPrimitiveInstancer : 1; - - /** Set to true if this is a split mesh instancer */ - uint32 bIsSplitMeshInstancer : 1; - }; - - uint32 HoudiniAssetInstanceInputFlagsPacked; - }; - FHoudiniAssetInstanceInputFlags Flags; -}; - -UCLASS() -class UHoudiniAssetInstanceInputField : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - /** Original object used by the instancer. **/ - UObject* OriginalObject; - - /** Currently used Objects */ - TArray< UObject* > InstancedObjects; - - /** Used instanced actor component. **/ - TArray< USceneComponent * > InstancerComponents; - - /** Flags used by this input field. **/ - uint32 HoudiniAssetInstanceInputFieldFlagsPacked; - - /** Corresponding geo part object. **/ - FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; - - /** Rotation offset for instanced component. **/ - TArray< FRotator > RotationOffsets; - - /** Scale offset for instanced component. **/ - TArray< FVector > ScaleOffsets; - - /** Whether to scale linearly for all fields. **/ - TArray< bool > bScaleOffsetsLinearlyArray; - - /** Transforms, one for each instance. **/ - TArray< FTransform > InstancedTransforms; - - /** Assignment of Transforms to each variation **/ - TArray< TArray< FTransform > > VariationTransformsArray; - - /** Color overrides, one per instance **/ - TArray InstanceColorOverride; - - /** Per-variation color override assignments */ - TArray< TArray< FLinearColor > > VariationInstanceColorOverrideArray; -}; - -//UCLASS() -UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), - ShowCategories = (Mobility), editinlinenew) -class UHoudiniAssetComponent_V1 : public UPrimitiveComponent//, public IHoudiniCookHandler -{ - GENERATED_UCLASS_BODY() - -public: - /* - // IHoudiniCookHandler interface - virtual FString GetBakingBaseName(const struct FHoudiniGeoPartObject_V1& GeoPartObject) override { return FString(); }; - virtual void SetStaticMeshGenerationParameters(class UStaticMesh* StaticMesh) override {}; - virtual class UMaterialInterface * GetAssignmentMaterial(const FString& MaterialName) override { return nullptr; }; - virtual void ClearAssignmentMaterials() override {}; - virtual void AddAssignmentMaterial(const FString& MaterialName, class UMaterialInterface* MaterialInterface) override {}; - virtual class UMaterialInterface * GetReplacementMaterial(const struct FHoudiniGeoPartObject_V1& GeoPartObject, const FString& MaterialName) override { return nullptr; }; - */ - - /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Double Sided Geometry")) - uint32 bGeneratedDoubleSidedGeometry : 1; - - /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * GeneratedPhysMaterial; - - /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ - UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Collision Complexity")) - TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; - - /** Resolution of lightmap. */ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 GeneratedLightMapResolution; - - /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ - UPROPERTY(EditAnywhere, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) - float GeneratedDistanceFieldResolutionScale; - - /** Custom walkable slope setting for generated mesh's body. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride GeneratedWalkableSlopeOverride; - - /** The light map coordinate index. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Light map coordinate index")) - int32 GeneratedLightMapCoordinateIndex; - - /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; - - /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Streaming Distance Multiplier")) - float GeneratedStreamingDistanceMultiplier; - - /** Default settings when using this mesh for instanced foliage. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; - - /** Array of user data stored with the asset. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, - Category = HoudiniGeneratedStaticMeshSettings, - meta = (DisplayName = "Asset User Data")) - TArray GeneratedAssetUserData; - - /** The output folder for baking actions */ - UPROPERTY() - FText BakeFolder; - - /** The temporary output folder for cooking actions */ - UPROPERTY() - FText TempCookFolder; - - virtual void Serialize(FArchive & Ar) override; - - /** Houdini Asset associated with this component. **/ - UHoudiniAsset* HoudiniAsset; - - /** Unique GUID created by component. **/ - FGuid ComponentGUID; - - /** Scale factor used for generated geometry of this component. **/ - float GeneratedGeometryScaleFactor; - - /** Scale factor used for geo transforms of this component. **/ - float TransformScaleFactor; - - /** Buffer to hold preset data for serialization purposes. Used only during serialization. **/ - TArray PresetBuffer; - - /** Buffer to hold default preset for reset purposes. **/ - TArray DefaultPresetBuffer; - - /** Parameters for this component's asset, indexed by parameter id. **/ - //TMap Parameters; - TMap Parameters; - - /** Parameters for this component's asset, indexed by name for fast look up. **/ - TMap ParameterByName; - - /** Inputs for this component's asset. **/ - TArray Inputs; - - /** Instance inputs for this component's asset **/ - TArray InstanceInputs; - - /** Material assignments. **/ - UHoudiniAssetComponentMaterials_V1 * HoudiniAssetComponentMaterials; - - /** Map of HAPI objects and corresponding static meshes. Also map of static meshes and corresponding components. **/ - TMap StaticMeshes; - TMap StaticMeshComponents; - - /** List of dependent downstream asset connections that have this asset as an asset input. **/ - TMap> DownstreamAssetConnections; - - /** Map of asset handle components. **/ - TMap HandleComponents; - - /** Map of curve / spline components. **/ - TMap SplineComponents; - - /** Map of Landscape / Heightfield components. **/ - TMap> LandscapeComponents; - - /** Overrides for baking names per part */ - TMap BakeNameOverrides; - - /** Import axis. **/ - uint8 ImportAxis; - - /** Flags used by Houdini component. **/ - union - { - struct - { - /** Enables cooking for this Houdini Asset. **/ - uint32 bEnableCooking : 1; - - /** Enables uploading of transformation changes back to Houdini Engine. **/ - uint32 bUploadTransformsToHoudiniEngine : 1; - - /** Enables cooking upon transformation changes. **/ - uint32 bTransformChangeTriggersCooks : 1; - - /** Is set to true when this component contains Houdini logo geometry. **/ - uint32 bContainsHoudiniLogoGeometry : 1; - - /** Is set to true when this component is native and false is when it is dynamic. **/ - uint32 bIsNativeComponent : 1; - - /** Is set to true when this component belongs to a preview actor. **/ - uint32 bIsPreviewComponent : 1; - - /** Is set to true if this component has been loaded. **/ - uint32 bLoadedComponent : 1; - - /** Unused **/ - uint32 bIsPlayModeActive_Unused : 1; - - /** unused flag **/ - uint32 bTimeCookInPlaymode_Unused : 1; - - /** Is set to true when Houdini materials are used. **/ - uint32 bUseHoudiniMaterials : 1; - - /** Is set to true when cooking this asset will trigger cooks of downstream connected assets. **/ - uint32 bCookingTriggersDownstreamCooks : 1; - - /** Is set to true after the asset is fully loaded and registered **/ - uint32 bFullyLoaded : 1; - }; - - uint32 HoudiniAssetComponentFlagsPacked; - }; -}; - -UCLASS() -class UHoudiniInstancedActorComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC); - - //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) - UObject* InstancedAsset; - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - TArray Instances; -}; - -UCLASS() -class UHoudiniMeshSplitInstancerComponent_V1 : public USceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void Serialize(FArchive & Ar) override; - - bool UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC); - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - TArray Instances; - - //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) - UMaterialInterface* OverrideMaterial; - - //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) - UStaticMesh* InstancedMesh; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniAsset.h" + +#include "Components/PrimitiveComponent.h" + +#include "Curves/CurveFloat.h" +#include "Curves/CurveLinearColor.h" + +#include "HoudiniCompatibilityHelpers.generated.h" + +class UStaticMesh; +class UStaticMeshComponent; +class USplineComponent; +class ALandscapeProxy; +class UMaterialInterface; +class UHoudiniInput; +class UHoudiniParameter; +class UHoudiniHandleComponent; +class UHoudiniSplineComponent; +class UHoudiniInstancedActorComponent; +class UHoudiniMeshSplitInstancerComponent; +class UFoliageType_InstancedStaticMesh; + + +struct FHoudiniGeoPartObject; + + +struct FHoudiniGeoPartObject_V1 +{ +public: + + void Serialize(FArchive & Ar); + + FHoudiniGeoPartObject ConvertLegacyData(); + + /** Return hash value for this object, used when using this object as a key inside hashing containers. **/ + uint32 GetTypeHash() const; + + /** Transform of this geo part object. **/ + FTransform TransformMatrix; + + /** Name of associated object. **/ + FString ObjectName; + + /** Name of associated part. **/ + FString PartName; + + /** Name of group which was used for splitting, empty if there's none. **/ + FString SplitName; + + /** Name of the instancer material, if available. **/ + FString InstancerMaterialName; + + /** Name of attribute material, if available. **/ + FString InstancerAttributeMaterialName; + + /** Id of corresponding HAPI Asset. **/ + //HAPI_NodeId AssetId; + int AssetId; + + /** Id of corresponding HAPI Object. **/ + //HAPI_NodeId ObjectId; + int ObjectId; + + /** Id of corresponding HAPI Geo. **/ + //HAPI_NodeId GeoId; + int GeoId; + + /** Id of corresponding HAPI Part. **/ + //HAPI_PartId PartId; + int PartId; + + /** Id of a split. In most cases this will be 0. **/ + int32 SplitId; + + /** Path to the corresponding node */ + mutable FString NodePath; + + /** Flags used by geo part object. **/ + union + { + struct + { + /* Is set to true when referenced object is visible. This is typically used by instancers. **/ + uint32 bIsVisible : 1; + + /** Is set to true when referenced object is an instancer. **/ + uint32 bIsInstancer : 1; + + /** Is set to true when referenced object is a curve. **/ + uint32 bIsCurve : 1; + + /** Is set to true when referenced object is editable. **/ + uint32 bIsEditable : 1; + + /** Is set to true when geometry has changed. **/ + uint32 bHasGeoChanged : 1; + + /** Is set to true when referenced object is collidable. **/ + uint32 bIsCollidable : 1; + + /** Is set to true when referenced object is collidable and is renderable. **/ + uint32 bIsRenderCollidable : 1; + + /** Is set to true when referenced object has just been loaded. **/ + uint32 bIsLoaded : 1; + + /** Unused flags. **/ + uint32 bPlaceHolderFlags : 3; + + /** Is set to true when referenced object has been loaded during transaction. **/ + uint32 bIsTransacting : 1; + + /** Is set to true when referenced object has a custom name. **/ + uint32 bHasCustomName : 1; + + /** Is set to true when referenced object is a box. **/ + uint32 bIsBox : 1; + + /** Is set to true when referenced object is a sphere. **/ + uint32 bIsSphere : 1; + + /** Is set to true when instancer material is available. **/ + uint32 bInstancerMaterialAvailable : 1; + + /** Is set to true when referenced object is a volume. **/ + uint32 bIsVolume : 1; + + /** Is set to true when instancer attribute material is available. **/ + uint32 bInstancerAttributeMaterialAvailable : 1; + + /** Is set when referenced object contains packed primitive instancing */ + uint32 bIsPackedPrimitiveInstancer : 1; + + /** Is set to true when referenced object is a UCX collision geo. **/ + uint32 bIsUCXCollisionGeo : 1; + + /** Is set to true when referenced object is a rendered UCX collision geo. **/ + uint32 bIsSimpleCollisionGeo : 1; + + /** Is set to true when new collision geo has been generated **/ + uint32 bHasCollisionBeenAdded : 1; + + /** Is set to true when new sockets have been added **/ + uint32 bHasSocketBeenAdded : 1; + + /** unused flag space is zero initialized */ + uint32 UnusedFlagsSpace : 14; + }; + + uint32 HoudiniGeoPartObjectFlagsPacked; + }; + + /** Temporary variable holding serialization version. **/ + uint32 HoudiniGeoPartObjectVersion; +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +uint32 GetTypeHash(const FHoudiniGeoPartObject_V1 & HoudiniGeoPartObject); + +/** Serialization function. **/ +FArchive& operator<<(FArchive & Ar, FHoudiniGeoPartObject_V1& HoudiniGeoPartObject); + +/** Functor used to sort geo part objects. **/ +struct FHoudiniGeoPartObject_V1SortPredicate +{ + bool operator()(const FHoudiniGeoPartObject_V1& A, const FHoudiniGeoPartObject_V1& B) const; +}; + + +struct FHoudiniAssetInputOutlinerMesh_V1 +{ + /** Serialization. **/ + void Serialize(FArchive & Ar); + + /** Update the Actor pointer from the store Actor path/name **/ + bool TryToUpdateActorPtrFromActorPathName(UWorld* InWorld); + + /** Temporary variable holding serialization version. **/ + uint32 HoudiniAssetParameterVersion; + + /** Selected Actor. **/ + TWeakObjectPtr ActorPtr = nullptr; + + /** Selected Actor's path, used to find the actor back after loading. **/ + FString ActorPathName = TEXT("NONE"); + + /** Selected mesh's component, for reference. **/ + UStaticMeshComponent * StaticMeshComponent = nullptr; + + /** The selected mesh. **/ + UStaticMesh * StaticMesh = nullptr; + + /** Spline Component **/ + USplineComponent * SplineComponent = nullptr; + + /** Number of CVs used by the spline component, used to detect modification **/ + int32 NumberOfSplineControlPoints = -1; + + /** Transform of the UnrealSpline CVs, used to detect modification of the spline (Rotation/Scale) **/ + TArray SplineControlPointsTransform; + + /** Spline Length, used to detect modification of the spline.. **/ + float SplineLength = -1.0f; + + /** Spline resolution used to generate the asset, used to detect setting modification **/ + float SplineResolution = -1.0f; + + /** Actor transform used to see if the transfrom changed since last marshal into Houdini. **/ + FTransform ActorTransform; + + /** Component transform used to see if the transform has changed since last marshalling **/ + FTransform ComponentTransform; + + /** Mesh's input asset id. **/ + //HAPI_NodeId AssetId = -1; + int AssetId = -1; + + /** TranformType used to generate the asset **/ + int32 KeepWorldTransform = 2; + + /** Path Materials assigned on the SMC **/ + TArray MeshComponentsMaterials; + + /** If the world In is a ISM, index of this instance **/ + uint32 InstanceIndex = -1; +}; + +/** Serialization function. **/ +FArchive & operator<<(FArchive & Ar, FHoudiniAssetInputOutlinerMesh_V1& HoudiniAssetInputOutlinerMesh); + +/* +UCLASS(EditInlineNew, config = Engine) +class UHoudiniAsset_V1 +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; +}; +*/ + +UCLASS() +class UHoudiniAssetParameter : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer); + + void CopyLegacyParameterData(UHoudiniParameter* InNewParm); + + /** Name of this parameter. **/ + FString ParameterName; + + /** Label of this parameter. **/ + FString ParameterLabel; + + /** Node this parameter belongs to. **/ + int NodeId; + + /** Id of this parameter. **/ + int ParmId; + + /** Id of parent parameter, -1 if root is parent. **/ + int ParmParentId; + + /** Child index within its parent parameter. **/ + int32 ChildIndex; + + /** Tuple size - arrays. **/ + int32 TupleSize; + + /** Internal HAPI cached value index. **/ + int32 ValuesIndex; + + /** The multiparm instance index. **/ + int32 MultiparmInstanceIndex; + + /** The parameter's help, to be used as a tooltip **/ + FString ParameterHelp; + + /** Flags used by this parameter. **/ + union + { + struct + { + /** Is set to true if this parameter is spare, that is, created by Houdini Engine only. **/ + uint32 bIsSpare : 1; + + /** Is set to true if this parameter is disabled. **/ + uint32 bIsDisabled : 1; + + /** Is set to true if value of this parameter has been changed by user. **/ + uint32 bChanged : 1; + + /** Is set to true when parameter's slider (if it has one) is being dragged. Transient. **/ + uint32 bSliderDragged : 1; + + /** Is set to true if the parameter is a multiparm child parameter. **/ + uint32 bIsChildOfMultiparm : 1; + + /** Is set to true if this parameter is a Substance parameter. **/ + uint32 bIsSubstanceParameter : 1; + + /** Is set to true if this parameter is a multiparm **/ + uint32 bIsMultiparm : 1; + }; + + uint32 HoudiniAssetParameterFlagsPacked; + }; + + /** Temporary variable holding parameter serialization version. **/ + uint32 HoudiniAssetParameterVersion; +}; + +UCLASS() +class UHoudiniAssetParameterButton : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterChoice : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Choice values for this property. **/ + TArray StringChoiceValues; + + /** Choice labels for this property. **/ + TArray StringChoiceLabels; + + /** Value of this property. **/ + FString StringValue; + + /** Current value for this property. **/ + int32 CurrentValue; + + /** Is set to true when this choice list is a string choice list. **/ + bool bStringChoiceList; +}; + +UCLASS() +class UHoudiniAssetParameterColor : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Color for this property. **/ + FLinearColor Color; +}; + +UCLASS() +class UHoudiniAssetParameterFile : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< FString > Values; + + /** Filters of this property. **/ + FString Filters; + + /** Is the file parameter read-only? **/ + bool IsReadOnly; +}; + +UCLASS() +class UHoudiniAssetParameterFloat : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< float > Values; + + /** Min and Max values for this property. **/ + float ValueMin; + float ValueMax; + + /** Min and Max values for UI for this property. **/ + float ValueUIMin; + float ValueUIMax; + + /** Unit for this property **/ + FString ValueUnit; + + /** Do we have the noswap tag? **/ + bool NoSwap; +}; + +UCLASS() +class UHoudiniAssetParameterFolder : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterFolderList : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterInt : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< int32 > Values; + + /** Min and Max values for this property. **/ + int32 ValueMin; + int32 ValueMax; + + /** Min and Max values for UI for this property. **/ + int32 ValueUIMin; + int32 ValueUIMax; + + /** Unit for this property **/ + FString ValueUnit; +}; + +UCLASS() +class UHoudiniAssetParameterLabel : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterMultiparm : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Value of this property. **/ + int32 MultiparmValue; +}; + +UCLASS() +class UHoudiniAssetParameterRamp : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + void CopyLegacyParameterData(UHoudiniParameter* InNewParm); + + //! Curves which are being edited. + UCurveFloat * HoudiniAssetParameterRampCurveFloat; + UCurveLinearColor * HoudiniAssetParameterRampCurveColor; + + //! Set to true if this ramp is a float ramp. Otherwise is considered a color ramp. + bool bIsFloatRamp; +}; + +UCLASS() +class UHoudiniAssetParameterSeparator : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; +}; + +UCLASS() +class UHoudiniAssetParameterString : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< FString > Values; +}; + +UCLASS() +class UHoudiniAssetParameterToggle : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniParameter* ConvertLegacyData(UObject* Outer) override; + + /** Values of this property. **/ + TArray< int32 > Values; +}; + +UCLASS() +class UHoudiniAssetComponentMaterials_V1 : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** Material assignments. **/ + TMap Assignments; + + /** Material replacements. **/ + TMap> Replacements; +}; + +UCLASS() +class UHoudiniHandleComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniHandleComponent* NewHC); + + //virtual UHoudiniHandleComponent* ConvertLegacyData(UObject* Outer); + + UHoudiniAssetParameterFloat* XFormParams[9]; + int32 XFormParamsTupleIndex[9]; + + UHoudiniAssetParameterChoice* RSTParm; + int32 RSTParmTupleIdx; + + UHoudiniAssetParameterChoice* RotOrderParm; + int32 RotOrderParmTupleIdx; +}; + +UCLASS() +class UHoudiniSplineComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + virtual UHoudiniSplineComponent* ConvertLegacyData(UObject* Outer); + + bool UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline); + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** List of points composing this curve. **/ + TArray CurvePoints; + + /** List of refined points used for drawing. **/ + TArray CurveDisplayPoints; + + /** Type of this curve. **/ + // 0 Polygon 1 Nurbs 2 Bezier + uint8 CurveType; + + /** Method used for this curve. **/ + // 0 CVs, 1 Breakpoints, 2 Freehand + uint8 CurveMethod; + + /** Whether this spline is closed. **/ + bool bClosedCurve; +}; + +UCLASS() +class UHoudiniAssetInput : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + UHoudiniInput* ConvertLegacyInput(UObject* Outer); + + // Input type: + // 0 GeometryInput + // 1 AssetInput + // 2 CurveInput + // 3 LandscapeInput + // 4 WorldInput + // 5 SkeletonInput + uint8 ChoiceIndex; + + /** Value of choice option. **/ + FString ChoiceStringValue; + + /** Index of this input. **/ + int32 InputIndex; + + /** Objects used for geometry input. **/ + TArray InputObjects; + + /** Houdini spline component which is used for curve input. **/ + UHoudiniSplineComponent * InputCurve; + + /** Houdini asset component pointer of the input asset (actor). **/ + UHoudiniAssetComponent_V1 * InputAssetComponent; + + /** Landscape actor used for input. **/ + TSoftObjectPtr InputLandscapeProxy; + + /** List of selected meshes and actors from the World Outliner. **/ + TArray InputOutlinerMeshArray; + + /** Parameters used by a curve input asset. **/ + TMap InputCurveParameters; + + float UnrealSplineResolution; + + /** Array containing the transform corrections for the assets in a geometry input **/ + TArray InputTransforms; + + /** Transform used by the input landscape **/ + FTransform InputLandscapeTransform; + + /** Flags used by this input. **/ + union + { + struct + { + /** Is set to true when static mesh used for geometry input has changed. **/ + uint32 bStaticMeshChanged : 1; + + /** Is set to true when choice switches to curve mode. **/ + uint32 bSwitchedToCurve : 1; + + /** Is set to true if this parameter has been loaded. **/ + uint32 bLoadedParameter : 1; + + /** Is set to true if the asset input is actually connected inside Houdini. **/ + uint32 bInputAssetConnectedInHoudini : 1; + + /** Is set to true when landscape input is set to selection only. **/ + uint32 bLandscapeExportSelectionOnly : 1; + + /** Is set to true when landscape curves are to be exported. **/ + uint32 bLandscapeExportCurves : 1; + + /** Is set to true when the landscape is to be exported as a mesh, not just points. **/ + uint32 bLandscapeExportAsMesh : 1; + + /** Is set to true when materials are to be exported. **/ + uint32 bLandscapeExportMaterials : 1; + + /** Is set to true when lightmap information export is desired. **/ + uint32 bLandscapeExportLighting : 1; + + /** Is set to true when uvs should be exported in [0,1] space. **/ + uint32 bLandscapeExportNormalizedUVs : 1; + + /** Is set to true when uvs should be exported for each tile separately. **/ + uint32 bLandscapeExportTileUVs : 1; + + /** Is set to true when being used as an object-path parameter instead of an input */ + uint32 bIsObjectPathParameter : 1; + + /** Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value **/ + uint32 bKeepWorldTransform : 2; + + /** Is set to true when the landscape is to be exported as a heightfield **/ + uint32 bLandscapeExportAsHeightfield : 1; + + /** Is set to true when the automatic selection of landscape component is active **/ + uint32 bLandscapeAutoSelectComponent : 1; + + /** Indicates that the geometry must be packed before merging it into the input **/ + uint32 bPackBeforeMerge : 1; + + /** Indicates that all LODs in the input should be marshalled to Houdini **/ + uint32 bExportAllLODs : 1; + + /** Indicates that all sockets in the input should be marshalled to Houdini **/ + uint32 bExportSockets : 1; + + /** Indicates that the landscape input's source landscape should be updated instead of creating a new component **/ + uint32 bUpdateInputLandscape : 1; + }; + + uint32 HoudiniAssetInputFlagsPacked; + }; +}; + +UCLASS() +class UHoudiniAssetInstanceInput : public UHoudiniAssetParameter +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** List of fields created by this instance input. **/ + TArray InstanceInputFields; + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** Id of an object to instance. **/ + int ObjectToInstanceId; + +public: + /** Flags used by this input. **/ + union FHoudiniAssetInstanceInputFlags + { + struct + { + /** Set to true if this is an attribute instancer. **/ + uint32 bIsAttributeInstancer : 1; + + /** Set to true if this attribute instancer uses overrides. **/ + uint32 bAttributeInstancerOverride : 1; + + /** Set to true if this is a packed primitive instancer **/ + uint32 bIsPackedPrimitiveInstancer : 1; + + /** Set to true if this is a split mesh instancer */ + uint32 bIsSplitMeshInstancer : 1; + }; + + uint32 HoudiniAssetInstanceInputFlagsPacked; + }; + FHoudiniAssetInstanceInputFlags Flags; +}; + +UCLASS() +class UHoudiniAssetInstanceInputField : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + /** Original object used by the instancer. **/ + UObject* OriginalObject; + + /** Currently used Objects */ + TArray< UObject* > InstancedObjects; + + /** Used instanced actor component. **/ + TArray< USceneComponent * > InstancerComponents; + + /** Flags used by this input field. **/ + uint32 HoudiniAssetInstanceInputFieldFlagsPacked; + + /** Corresponding geo part object. **/ + FHoudiniGeoPartObject_V1 HoudiniGeoPartObject; + + /** Rotation offset for instanced component. **/ + TArray< FRotator > RotationOffsets; + + /** Scale offset for instanced component. **/ + TArray< FVector > ScaleOffsets; + + /** Whether to scale linearly for all fields. **/ + TArray< bool > bScaleOffsetsLinearlyArray; + + /** Transforms, one for each instance. **/ + TArray< FTransform > InstancedTransforms; + + /** Assignment of Transforms to each variation **/ + TArray< TArray< FTransform > > VariationTransformsArray; + + /** Color overrides, one per instance **/ + TArray InstanceColorOverride; + + /** Per-variation color override assignments */ + TArray< TArray< FLinearColor > > VariationInstanceColorOverrideArray; +}; + +//UCLASS() +UCLASS(ClassGroup = (Rendering, Common), hidecategories = (Object, Activation, "Components|Activation"), + ShowCategories = (Mobility), editinlinenew) +class UHoudiniAssetComponent_V1 : public UPrimitiveComponent//, public IHoudiniCookHandler +{ + GENERATED_UCLASS_BODY() + +public: + /* + // IHoudiniCookHandler interface + virtual FString GetBakingBaseName(const struct FHoudiniGeoPartObject_V1& GeoPartObject) override { return FString(); }; + virtual void SetStaticMeshGenerationParameters(class UStaticMesh* StaticMesh) override {}; + virtual class UMaterialInterface * GetAssignmentMaterial(const FString& MaterialName) override { return nullptr; }; + virtual void ClearAssignmentMaterials() override {}; + virtual void AddAssignmentMaterial(const FString& MaterialName, class UMaterialInterface* MaterialInterface) override {}; + virtual class UMaterialInterface * GetReplacementMaterial(const struct FHoudiniGeoPartObject_V1& GeoPartObject, const FString& MaterialName) override { return nullptr; }; + */ + + /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Double Sided Geometry")) + uint32 bGeneratedDoubleSidedGeometry : 1; + + /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * GeneratedPhysMaterial; + + /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ + UPROPERTY(EditAnywhere, Category = HoudiniGeneratedStaticMeshSettings, meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Collision Complexity")) + TEnumAsByte< enum ECollisionTraceFlag > GeneratedCollisionTraceFlag; + + /** Resolution of lightmap. */ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 GeneratedLightMapResolution; + + /** Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset **/ + UPROPERTY(EditAnywhere, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /** Custom walkable slope setting for generated mesh's body. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride GeneratedWalkableSlopeOverride; + + /** The light map coordinate index. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Light map coordinate index")) + int32 GeneratedLightMapCoordinateIndex; + + /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; + + /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Streaming Distance Multiplier")) + float GeneratedStreamingDistanceMultiplier; + + /** Default settings when using this mesh for instanced foliage. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh * GeneratedFoliageDefaultSettings; + + /** Array of user data stored with the asset. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, + Category = HoudiniGeneratedStaticMeshSettings, + meta = (DisplayName = "Asset User Data")) + TArray GeneratedAssetUserData; + + /** The output folder for baking actions */ + UPROPERTY() + FText BakeFolder; + + /** The temporary output folder for cooking actions */ + UPROPERTY() + FText TempCookFolder; + + virtual void Serialize(FArchive & Ar) override; + + /** Houdini Asset associated with this component. **/ + UHoudiniAsset* HoudiniAsset; + + /** Unique GUID created by component. **/ + FGuid ComponentGUID; + + /** Scale factor used for generated geometry of this component. **/ + float GeneratedGeometryScaleFactor; + + /** Scale factor used for geo transforms of this component. **/ + float TransformScaleFactor; + + /** Buffer to hold preset data for serialization purposes. Used only during serialization. **/ + TArray PresetBuffer; + + /** Buffer to hold default preset for reset purposes. **/ + TArray DefaultPresetBuffer; + + /** Parameters for this component's asset, indexed by parameter id. **/ + //TMap Parameters; + TMap Parameters; + + /** Parameters for this component's asset, indexed by name for fast look up. **/ + TMap ParameterByName; + + /** Inputs for this component's asset. **/ + TArray Inputs; + + /** Instance inputs for this component's asset **/ + TArray InstanceInputs; + + /** Material assignments. **/ + UHoudiniAssetComponentMaterials_V1 * HoudiniAssetComponentMaterials; + + /** Map of HAPI objects and corresponding static meshes. Also map of static meshes and corresponding components. **/ + TMap StaticMeshes; + TMap StaticMeshComponents; + + /** List of dependent downstream asset connections that have this asset as an asset input. **/ + TMap> DownstreamAssetConnections; + + /** Map of asset handle components. **/ + TMap HandleComponents; + + /** Map of curve / spline components. **/ + TMap SplineComponents; + + /** Map of Landscape / Heightfield components. **/ + TMap> LandscapeComponents; + + /** Overrides for baking names per part */ + TMap BakeNameOverrides; + + /** Import axis. **/ + uint8 ImportAxis; + + /** Flags used by Houdini component. **/ + union + { + struct + { + /** Enables cooking for this Houdini Asset. **/ + uint32 bEnableCooking : 1; + + /** Enables uploading of transformation changes back to Houdini Engine. **/ + uint32 bUploadTransformsToHoudiniEngine : 1; + + /** Enables cooking upon transformation changes. **/ + uint32 bTransformChangeTriggersCooks : 1; + + /** Is set to true when this component contains Houdini logo geometry. **/ + uint32 bContainsHoudiniLogoGeometry : 1; + + /** Is set to true when this component is native and false is when it is dynamic. **/ + uint32 bIsNativeComponent : 1; + + /** Is set to true when this component belongs to a preview actor. **/ + uint32 bIsPreviewComponent : 1; + + /** Is set to true if this component has been loaded. **/ + uint32 bLoadedComponent : 1; + + /** Unused **/ + uint32 bIsPlayModeActive_Unused : 1; + + /** unused flag **/ + uint32 bTimeCookInPlaymode_Unused : 1; + + /** Is set to true when Houdini materials are used. **/ + uint32 bUseHoudiniMaterials : 1; + + /** Is set to true when cooking this asset will trigger cooks of downstream connected assets. **/ + uint32 bCookingTriggersDownstreamCooks : 1; + + /** Is set to true after the asset is fully loaded and registered **/ + uint32 bFullyLoaded : 1; + }; + + uint32 HoudiniAssetComponentFlagsPacked; + }; +}; + +UCLASS() +class UHoudiniInstancedActorComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC); + + //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) + UObject* InstancedAsset; + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + TArray Instances; +}; + +UCLASS() +class UHoudiniMeshSplitInstancerComponent_V1 : public USceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void Serialize(FArchive & Ar) override; + + bool UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC); + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + TArray Instances; + + //UPROPERTY(SkipSerialization, VisibleInstanceOnly, Category = Instances) + UMaterialInterface* OverrideMaterial; + + //UPROPERTY(SkipSerialization, VisibleAnywhere, Category = Instances) + UStaticMesh* InstancedMesh; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp index 971e2ff47..8a5177375 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.cpp @@ -1,33 +1,33 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineCopyPropertiesInterface.h" - -void IHoudiniEngineCopyPropertiesInterface::CopyPropertiesFrom(UObject* FromObject) -{ - -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineCopyPropertiesInterface.h" + +void IHoudiniEngineCopyPropertiesInterface::CopyPropertiesFrom(UObject* FromObject) +{ + +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h index 57cf3cf8d..aa49d8f5a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineCopyPropertiesInterface.h @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "Engine/Engine.h" -#include "UObject/ObjectMacros.h" -#include "UObject/Interface.h" -#include "HoudiniEngineCopyPropertiesInterface.generated.h" - - -UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) -class UHoudiniEngineCopyPropertiesInterface : public UInterface -{ - GENERATED_BODY() -}; - -class HOUDINIENGINERUNTIME_API IHoudiniEngineCopyPropertiesInterface -{ - GENERATED_BODY() - -public: - virtual void CopyPropertiesFrom(UObject* FromObject); -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "Engine/Engine.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Interface.h" +#include "HoudiniEngineCopyPropertiesInterface.generated.h" + + +UINTERFACE(MinimalAPI, meta = (CannotImplementInterfaceInBlueprint)) +class UHoudiniEngineCopyPropertiesInterface : public UInterface +{ + GENERATED_BODY() +}; + +class HOUDINIENGINERUNTIME_API IHoudiniEngineCopyPropertiesInterface +{ + GENERATED_BODY() + +public: + virtual void CopyPropertiesFrom(UObject* FromObject); +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp index 31c72c8cc..a10eb445d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp @@ -1,323 +1,323 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" - -#include "HoudiniAssetComponent.h" - -#include "Modules/ModuleManager.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FHoudiniEngineRuntime, HoudiniEngineRuntime); -DEFINE_LOG_CATEGORY(LogHoudiniEngineRuntime); - -FHoudiniEngineRuntime * -FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; - - -FHoudiniEngineRuntime & -FHoudiniEngineRuntime::Get() -{ - return *HoudiniEngineRuntimeInstance; -} - - -bool -FHoudiniEngineRuntime::IsInitialized() -{ - return FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance != nullptr; -} - - -FHoudiniEngineRuntime::FHoudiniEngineRuntime() -{ -} - - -void FHoudiniEngineRuntime::StartupModule() -{ - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module - // Store the instance. - FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = this; -} - - -void FHoudiniEngineRuntime::ShutdownModule() -{ - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. - FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; -} - - -int32 -FHoudiniEngineRuntime::GetRegisteredHoudiniComponentCount() -{ - if (!IsInitialized()) - return 0; - - FScopeLock ScopeLock(&CriticalSection); - return RegisteredHoudiniComponents.Num(); -} - - -UHoudiniAssetComponent* -FHoudiniEngineRuntime::GetRegisteredHoudiniComponentAt(const int32& Index) -{ - if (!IsInitialized()) - return nullptr; - - FScopeLock ScopeLock(&CriticalSection); - - if (!RegisteredHoudiniComponents.IsValidIndex(Index)) - return nullptr; - - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Index]; - if (!Ptr.IsValid()) - return nullptr; - - if (Ptr.IsStale()) - return nullptr; - - return Ptr.Get(); -} - - -void -FHoudiniEngineRuntime::CleanUpRegisteredHoudiniComponents() -{ - // Remove Stale and invalid components - FScopeLock ScopeLock(&CriticalSection); - for (int Idx = RegisteredHoudiniComponents.Num() - 1; Idx >= 0; Idx--) - { - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Idx]; - if ( !Ptr.IsValid() || Ptr.IsStale() ) - { - UnRegisterHoudiniComponent(Idx); - continue; - } - - UHoudiniAssetComponent* CurrentHAC = Ptr.Get(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) - { - UnRegisterHoudiniComponent(Idx); - continue; - } - } -} - - -bool -FHoudiniEngineRuntime::IsComponentRegistered(UHoudiniAssetComponent* HAC) const -{ - // No need for duplicates - if (HAC && RegisteredHoudiniComponents.Find(HAC) != INDEX_NONE) - return true; - - return false; -} - - -void -FHoudiniEngineRuntime::RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype) -{ - if (!FHoudiniEngineRuntime::IsInitialized()) - return; - - if (!HAC || HAC->IsPendingKill()) - return; - - // RF_Transient indicates a temporary/preview object - // No need to instantiate/cook those in Houdini - // RF_ArchetypeObject is the template for blueprinted HDA, so we need to be able to register those. - if (HAC->HasAnyFlags(RF_Transient) || (HAC->HasAnyFlags(RF_ArchetypeObject) && !bAllowArchetype) || HAC->HasAnyFlags(RF_ClassDefaultObject)) - return; - - // No need for duplicates - if (IsComponentRegistered(HAC)) - return; - - HOUDINI_BP_MESSAGE(TEXT("[FHoudiniEngineRuntime::RegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); - - // Before adding, clean up the all ready registered - CleanUpRegisteredHoudiniComponents(); - - // Add the new component - { - FScopeLock ScopeLock(&CriticalSection); - RegisteredHoudiniComponents.Add(HAC); - } - - HAC->NotifyHoudiniRegisterCompleted(); -} - - -void -FHoudiniEngineRuntime::MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent) -{ - if (InNodeId >= 0) - { - // FDebug::DumpStackTraceToLog(); - - NodeIdsPendingDelete.AddUnique(InNodeId); - - if (bDeleteParent) - { - NodeIdsParentPendingDelete.AddUnique(InNodeId); - } - } -} - - -void -FHoudiniEngineRuntime::UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC) -{ - if (!IsInitialized()) - return; - - if (!HAC || HAC->IsPendingKill()) - return; - - // Calling GetPathName here may lead to some crashes due to invalid outers... - //HOUDINI_LOG_DISPLAY(TEXT("[FHoudiniEngineRuntime::UnRegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); - - FScopeLock ScopeLock(&CriticalSection); - - int32 FoundIdx = RegisteredHoudiniComponents.Find(HAC); - if (!RegisteredHoudiniComponents.IsValidIndex(FoundIdx)) - return; - HAC->NotifyHoudiniPreUnregister(); - UnRegisterHoudiniComponent(FoundIdx); - HAC->NotifyHoudiniPostUnregister(); -} - - -void -FHoudiniEngineRuntime::UnRegisterHoudiniComponent(const int32& ValidIndex) -{ - if (!IsInitialized()) - return; - - FScopeLock ScopeLock(&CriticalSection); - - TWeakObjectPtr Ptr = RegisteredHoudiniComponents[ValidIndex]; - if (Ptr.IsValid(true, false)) - { - UHoudiniAssetComponent* HAC = Ptr.Get(); - if (HAC && HAC->CanDeleteHoudiniNodes()) - { - MarkNodeIdAsPendingDelete(HAC->GetAssetId(), true); - } - } - - RegisteredHoudiniComponents.RemoveAt(ValidIndex); -} - - -int32 -FHoudiniEngineRuntime::GetNodeIdsPendingDeleteCount() -{ - if (!IsInitialized()) - return 0; - - FScopeLock ScopeLock(&CriticalSection); - - return NodeIdsPendingDelete.Num(); -} - - -int32 -FHoudiniEngineRuntime::GetNodeIdsPendingDeleteAt(const int32& Index) -{ - if (!IsInitialized()) - return -1; - - FScopeLock ScopeLock(&CriticalSection); - - if (!NodeIdsPendingDelete.IsValidIndex(Index)) - return -1; - - return NodeIdsPendingDelete[Index]; -} - - -void -FHoudiniEngineRuntime::RemoveNodeIdPendingDeleteAt(const int32& Index) -{ - if (!IsInitialized()) - return; - - FScopeLock ScopeLock(&CriticalSection); - if (!NodeIdsPendingDelete.IsValidIndex(Index)) - return; - - NodeIdsPendingDelete.RemoveAt(Index); -} - - -bool -FHoudiniEngineRuntime::IsParentNodePendingDelete(const int32& NodeId) -{ - return NodeIdsParentPendingDelete.Contains(NodeId); -} - - -void -FHoudiniEngineRuntime::RemoveParentNodePendingDelete(const int32& NodeId) -{ - if (NodeIdsParentPendingDelete.Contains(NodeId)) - NodeIdsParentPendingDelete.Remove(NodeId); -} - - -FString -FHoudiniEngineRuntime::GetDefaultTemporaryCookFolder() const -{ - // Get Runtime settings to get the Temp Cook Folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (!HoudiniRuntimeSettings) - return HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - - return HoudiniRuntimeSettings->DefaultTemporaryCookFolder; -} - - -FString -FHoudiniEngineRuntime::GetDefaultBakeFolder() const -{ - // Get Runtime settings to get the default bake Folder - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (!HoudiniRuntimeSettings) - return HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - return HoudiniRuntimeSettings->DefaultBakeFolder; -} - -#undef LOCTEXT_NAMESPACE - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" + +#include "HoudiniAssetComponent.h" + +#include "Modules/ModuleManager.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FHoudiniEngineRuntime, HoudiniEngineRuntime); +DEFINE_LOG_CATEGORY(LogHoudiniEngineRuntime); + +FHoudiniEngineRuntime * +FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; + + +FHoudiniEngineRuntime & +FHoudiniEngineRuntime::Get() +{ + return *HoudiniEngineRuntimeInstance; +} + + +bool +FHoudiniEngineRuntime::IsInitialized() +{ + return FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance != nullptr; +} + + +FHoudiniEngineRuntime::FHoudiniEngineRuntime() +{ +} + + +void FHoudiniEngineRuntime::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + // Store the instance. + FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = this; +} + + +void FHoudiniEngineRuntime::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. + FHoudiniEngineRuntime::HoudiniEngineRuntimeInstance = nullptr; +} + + +int32 +FHoudiniEngineRuntime::GetRegisteredHoudiniComponentCount() +{ + if (!IsInitialized()) + return 0; + + FScopeLock ScopeLock(&CriticalSection); + return RegisteredHoudiniComponents.Num(); +} + + +UHoudiniAssetComponent* +FHoudiniEngineRuntime::GetRegisteredHoudiniComponentAt(const int32& Index) +{ + if (!IsInitialized()) + return nullptr; + + FScopeLock ScopeLock(&CriticalSection); + + if (!RegisteredHoudiniComponents.IsValidIndex(Index)) + return nullptr; + + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Index]; + if (!Ptr.IsValid()) + return nullptr; + + if (Ptr.IsStale()) + return nullptr; + + return Ptr.Get(); +} + + +void +FHoudiniEngineRuntime::CleanUpRegisteredHoudiniComponents() +{ + // Remove Stale and invalid components + FScopeLock ScopeLock(&CriticalSection); + for (int Idx = RegisteredHoudiniComponents.Num() - 1; Idx >= 0; Idx--) + { + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[Idx]; + if ( !Ptr.IsValid() || Ptr.IsStale() ) + { + UnRegisterHoudiniComponent(Idx); + continue; + } + + UHoudiniAssetComponent* CurrentHAC = Ptr.Get(); + if (!CurrentHAC || CurrentHAC->IsPendingKill()) + { + UnRegisterHoudiniComponent(Idx); + continue; + } + } +} + + +bool +FHoudiniEngineRuntime::IsComponentRegistered(UHoudiniAssetComponent* HAC) const +{ + // No need for duplicates + if (HAC && RegisteredHoudiniComponents.Find(HAC) != INDEX_NONE) + return true; + + return false; +} + + +void +FHoudiniEngineRuntime::RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype) +{ + if (!FHoudiniEngineRuntime::IsInitialized()) + return; + + if (!HAC || HAC->IsPendingKill()) + return; + + // RF_Transient indicates a temporary/preview object + // No need to instantiate/cook those in Houdini + // RF_ArchetypeObject is the template for blueprinted HDA, so we need to be able to register those. + if (HAC->HasAnyFlags(RF_Transient) || (HAC->HasAnyFlags(RF_ArchetypeObject) && !bAllowArchetype) || HAC->HasAnyFlags(RF_ClassDefaultObject)) + return; + + // No need for duplicates + if (IsComponentRegistered(HAC)) + return; + + HOUDINI_BP_MESSAGE(TEXT("[FHoudiniEngineRuntime::RegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); + + // Before adding, clean up the all ready registered + CleanUpRegisteredHoudiniComponents(); + + // Add the new component + { + FScopeLock ScopeLock(&CriticalSection); + RegisteredHoudiniComponents.Add(HAC); + } + + HAC->NotifyHoudiniRegisterCompleted(); +} + + +void +FHoudiniEngineRuntime::MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent) +{ + if (InNodeId >= 0) + { + // FDebug::DumpStackTraceToLog(); + + NodeIdsPendingDelete.AddUnique(InNodeId); + + if (bDeleteParent) + { + NodeIdsParentPendingDelete.AddUnique(InNodeId); + } + } +} + + +void +FHoudiniEngineRuntime::UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC) +{ + if (!IsInitialized()) + return; + + if (!HAC || HAC->IsPendingKill()) + return; + + // Calling GetPathName here may lead to some crashes due to invalid outers... + //HOUDINI_LOG_DISPLAY(TEXT("[FHoudiniEngineRuntime::UnRegisterHoudiniComponent] HAC: %s"), *(HAC->GetPathName()) ); + + FScopeLock ScopeLock(&CriticalSection); + + int32 FoundIdx = RegisteredHoudiniComponents.Find(HAC); + if (!RegisteredHoudiniComponents.IsValidIndex(FoundIdx)) + return; + HAC->NotifyHoudiniPreUnregister(); + UnRegisterHoudiniComponent(FoundIdx); + HAC->NotifyHoudiniPostUnregister(); +} + + +void +FHoudiniEngineRuntime::UnRegisterHoudiniComponent(const int32& ValidIndex) +{ + if (!IsInitialized()) + return; + + FScopeLock ScopeLock(&CriticalSection); + + TWeakObjectPtr Ptr = RegisteredHoudiniComponents[ValidIndex]; + if (Ptr.IsValid(true, false)) + { + UHoudiniAssetComponent* HAC = Ptr.Get(); + if (HAC && HAC->CanDeleteHoudiniNodes()) + { + MarkNodeIdAsPendingDelete(HAC->GetAssetId(), true); + } + } + + RegisteredHoudiniComponents.RemoveAt(ValidIndex); +} + + +int32 +FHoudiniEngineRuntime::GetNodeIdsPendingDeleteCount() +{ + if (!IsInitialized()) + return 0; + + FScopeLock ScopeLock(&CriticalSection); + + return NodeIdsPendingDelete.Num(); +} + + +int32 +FHoudiniEngineRuntime::GetNodeIdsPendingDeleteAt(const int32& Index) +{ + if (!IsInitialized()) + return -1; + + FScopeLock ScopeLock(&CriticalSection); + + if (!NodeIdsPendingDelete.IsValidIndex(Index)) + return -1; + + return NodeIdsPendingDelete[Index]; +} + + +void +FHoudiniEngineRuntime::RemoveNodeIdPendingDeleteAt(const int32& Index) +{ + if (!IsInitialized()) + return; + + FScopeLock ScopeLock(&CriticalSection); + if (!NodeIdsPendingDelete.IsValidIndex(Index)) + return; + + NodeIdsPendingDelete.RemoveAt(Index); +} + + +bool +FHoudiniEngineRuntime::IsParentNodePendingDelete(const int32& NodeId) +{ + return NodeIdsParentPendingDelete.Contains(NodeId); +} + + +void +FHoudiniEngineRuntime::RemoveParentNodePendingDelete(const int32& NodeId) +{ + if (NodeIdsParentPendingDelete.Contains(NodeId)) + NodeIdsParentPendingDelete.Remove(NodeId); +} + + +FString +FHoudiniEngineRuntime::GetDefaultTemporaryCookFolder() const +{ + // Get Runtime settings to get the Temp Cook Folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (!HoudiniRuntimeSettings) + return HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + + return HoudiniRuntimeSettings->DefaultTemporaryCookFolder; +} + + +FString +FHoudiniEngineRuntime::GetDefaultBakeFolder() const +{ + // Get Runtime settings to get the default bake Folder + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (!HoudiniRuntimeSettings) + return HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + return HoudiniRuntimeSettings->DefaultBakeFolder; +} + +#undef LOCTEXT_NAMESPACE + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h index d8998dcdb..ecf7c8bfd 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.h @@ -1,107 +1,107 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniAssetComponent.h" -#include "HoudiniPDGAssetLink.h" - -#include "Modules/ModuleInterface.h" -#include "Misc/ScopeLock.h" -#include "UObject/WeakObjectPtrTemplates.h" - -class HOUDINIENGINERUNTIME_API FHoudiniEngineRuntime : public IModuleInterface -{ - public: - FHoudiniEngineRuntime(); - - // - // IModuleInterface methods. - // - virtual void StartupModule() override; - virtual void ShutdownModule() override; - - // Return singleton instance of Houdini Engine Runtime, used internally. - static FHoudiniEngineRuntime & Get(); - - // Return true if singleton instance has been created. - static bool IsInitialized(); - - // - // Houdini Asset Component registry - // - // Ensure that the registered components are all still valid - void CleanUpRegisteredHoudiniComponents(); - - void RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype=false); - - void UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC); - void UnRegisterHoudiniComponent(const int32& ValidIdx); - - bool IsComponentRegistered(UHoudiniAssetComponent* HAC) const; - int32 GetRegisteredHoudiniComponentCount(); - UHoudiniAssetComponent* GetRegisteredHoudiniComponentAt(const int32& Index); - - virtual TArray>* GetRegisteredHoudiniComponents() { return &RegisteredHoudiniComponents; }; - - // - // Node deletion - // - void MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent = false); - - int32 GetNodeIdsPendingDeleteCount(); - int32 GetNodeIdsPendingDeleteAt(const int32& Index); - void RemoveNodeIdPendingDeleteAt(const int32& Index); - - bool IsParentNodePendingDelete(const int32& NodeId); - - void RemoveParentNodePendingDelete(const int32& NodeId); - - // - // - // - - // Returns the folder to be used for temporary cook content - FString GetDefaultTemporaryCookFolder() const; - - // Returns the defualt folder used for baking - FString GetDefaultBakeFolder() const; - - private: - - // Synchronization primitive. - FCriticalSection CriticalSection; - - // Singleton instance. - static FHoudiniEngineRuntime * HoudiniEngineRuntimeInstance; - - // - TArray> RegisteredHoudiniComponents; - - TArray NodeIdsPendingDelete; - - TArray NodeIdsParentPendingDelete; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniAssetComponent.h" +#include "HoudiniPDGAssetLink.h" + +#include "Modules/ModuleInterface.h" +#include "Misc/ScopeLock.h" +#include "UObject/WeakObjectPtrTemplates.h" + +class HOUDINIENGINERUNTIME_API FHoudiniEngineRuntime : public IModuleInterface +{ + public: + FHoudiniEngineRuntime(); + + // + // IModuleInterface methods. + // + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // Return singleton instance of Houdini Engine Runtime, used internally. + static FHoudiniEngineRuntime & Get(); + + // Return true if singleton instance has been created. + static bool IsInitialized(); + + // + // Houdini Asset Component registry + // + // Ensure that the registered components are all still valid + void CleanUpRegisteredHoudiniComponents(); + + void RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, bool bAllowArchetype=false); + + void UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC); + void UnRegisterHoudiniComponent(const int32& ValidIdx); + + bool IsComponentRegistered(UHoudiniAssetComponent* HAC) const; + int32 GetRegisteredHoudiniComponentCount(); + UHoudiniAssetComponent* GetRegisteredHoudiniComponentAt(const int32& Index); + + virtual TArray>* GetRegisteredHoudiniComponents() { return &RegisteredHoudiniComponents; }; + + // + // Node deletion + // + void MarkNodeIdAsPendingDelete(const int32& InNodeId, bool bDeleteParent = false); + + int32 GetNodeIdsPendingDeleteCount(); + int32 GetNodeIdsPendingDeleteAt(const int32& Index); + void RemoveNodeIdPendingDeleteAt(const int32& Index); + + bool IsParentNodePendingDelete(const int32& NodeId); + + void RemoveParentNodePendingDelete(const int32& NodeId); + + // + // + // + + // Returns the folder to be used for temporary cook content + FString GetDefaultTemporaryCookFolder() const; + + // Returns the defualt folder used for baking + FString GetDefaultBakeFolder() const; + + private: + + // Synchronization primitive. + FCriticalSection CriticalSection; + + // Singleton instance. + static FHoudiniEngineRuntime * HoudiniEngineRuntimeInstance; + + // + TArray> RegisteredHoudiniComponents; + + TArray NodeIdsPendingDelete; + + TArray NodeIdsParentPendingDelete; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp index 46374fd7e..ca8e448cd 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp @@ -1,660 +1,660 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniRuntimeSettings.h" - -#include "EngineUtils.h" -#include "Engine/EngineTypes.h" - -#if WITH_EDITOR - #include "Editor.h" - #include "Kismet2/BlueprintEditorUtils.h" -#endif - - -FString -FHoudiniEngineRuntimeUtils::GetLibHAPIName() -{ - static const FString LibHAPIName = - -#if PLATFORM_WINDOWS - HAPI_LIB_OBJECT_WINDOWS; -#elif PLATFORM_MAC - HAPI_LIB_OBJECT_MAC; -#elif PLATFORM_LINUX - HAPI_LIB_OBJECT_LINUX; -#else - TEXT(""); -#endif - - return LibHAPIName; -} - - -void -FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes) -{ - OutBBoxes.Empty(); - - for (auto CurrentActor : InActors) - { - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true)); - } -} - - -bool -FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors) -{ - if (!IsValid(World)) - return false; - - OutActors.Empty(); - for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) - { - AActor* CurrentActor = *ActorItr; - if (!IsValid(CurrentActor)) - continue; - - if (!CurrentActor->GetClass()->IsChildOf(ActorType.Get())) - continue; - - if (ExcludeActors && ExcludeActors->Contains(CurrentActor)) - continue; - - // Special case - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); - for (auto InBounds : BBoxes) - { - // Check if both actor's bounds intersects - if (!ActorBounds.Intersect(InBounds)) - continue; - - OutActors.Add(CurrentActor); - break; - } - } - - return true; -} - -bool -FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly) -{ - bool bDeleted = false; - OutPackage = nullptr; - bOutPackageIsInMemoryOnly = false; - - if (!IsValid(InObjectToDelete)) - return false; - - // Don't try to delete the object if it has references (we do this here to avoid the FMessageDialog in DeleteSingleObject - bool bIsReferenced = false; - bool bIsReferencedByUndo = false; - if (!GatherObjectReferencersForDeletion(InObjectToDelete, bIsReferenced, bIsReferencedByUndo)) - return false; - - if (bIsReferenced) - { - HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject] Not deleting %s: there are still references to it."), *InObjectToDelete->GetFullName()); - } - else - { - // Even though we already checked for references, we still let DeleteSingleObject check for references, since - // we want that code path where it'll clean up in-memory references (undo buffer/transactions) - const bool bCheckForReferences = true; - if (DeleteSingleObject(InObjectToDelete, bCheckForReferences)) - { - bDeleted = true; - - OutPackage = InObjectToDelete->GetOutermost(); - - FString PackageFilename; - if (!IsValid(OutPackage) || !FPackageName::DoesPackageExist(OutPackage->GetName(), nullptr, &PackageFilename)) - { - // Package is in memory only, we don't have call CleanUpAfterSuccessfulDelete on it, just do garbage - // collection to pick up the stale package - bOutPackageIsInMemoryOnly = true; - } - else - { - // There is an on-disk package that is now potentially empty, CleanUpAfterSuccessfulDelete must be - // called on this. Since CleanUpAfterSuccessfulDelete does garbage collection, we return the Package - // as part of this function so that the caller can collect all Packages and do one call to - // CleanUpAfterSuccessfulDelete with an array - } - } - } - - return bDeleted; -} - -int32 -FHoudiniEngineRuntimeUtils::SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted) -{ - int32 NumDeleted = 0; - bool bGarbageCollectionRequired = false; - TSet PackagesToCleanUp; - TSet ProcessedObjects; - while (InObjectsToDelete.Num() > 0) - { - UObject* const ObjectToDelete = InObjectsToDelete.Pop(); - - if (ProcessedObjects.Contains(ObjectToDelete)) - continue; - - ProcessedObjects.Add(ObjectToDelete); - - if (!IsValid(ObjectToDelete)) - continue; - - UPackage* Package = nullptr; - bool bInMemoryPackageOnly = false; - if (SafeDeleteSingleObject(ObjectToDelete, Package, bInMemoryPackageOnly)) - { - NumDeleted++; - if (bInMemoryPackageOnly) - { - // Packages that are in-memory only are cleaned up by garbage collection - if (!bGarbageCollectionRequired) - bGarbageCollectionRequired = true; - } - else - { - // Clean up potentially empty packages in one call to CleanupAfterSuccessfulDelete at the end - PackagesToCleanUp.Add(Package); - } - } - else if (OutObjectsNotDeleted) - { - OutObjectsNotDeleted->Add(ObjectToDelete); - } - } - - // CleanupAfterSuccessfulDelete calls CollectGarbage, so don't call it here if we have PackagesToCleanUp - if (bGarbageCollectionRequired && PackagesToCleanUp.Num() <= 0) - CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); - - if (PackagesToCleanUp.Num() > 0) - CleanupAfterSuccessfulDelete(PackagesToCleanUp.Array()); - - return NumDeleted; -} - - -#if WITH_EDITOR -int32 -FHoudiniEngineRuntimeUtils::CopyComponentProperties(UActorComponent* SourceComponent, UActorComponent* TargetComponent, const EditorUtilities::FCopyOptions& Options) -{ - UClass* ComponentClass = SourceComponent->GetClass(); - check( ComponentClass == TargetComponent->GetClass() ); - - const bool bIsPreviewing = ( Options.Flags & EditorUtilities::ECopyOptions::PreviewOnly ) != 0; - int32 CopiedPropertyCount = 0; - bool bTransformChanged = false; - - // Build a list of matching component archetype instances for propagation (if requested) - TArray ComponentArchetypeInstances; - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - TArray Instances; - TargetComponent->GetArchetypeInstances(Instances); - for(UObject* ObjInstance : Instances) - { - UActorComponent* ComponentInstance = Cast(ObjInstance); - if (ComponentInstance && ComponentInstance != SourceComponent && ComponentInstance != TargetComponent) - { - ComponentArchetypeInstances.Add(ComponentInstance); - } - } - } - - TSet SourceUCSModifiedProperties; - SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties); - - TArray ComponentInstancesToReregister; - - // Copy component properties - for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) - { - const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); - const bool bIsIdentical = Property->Identical_InContainer( SourceComponent, TargetComponent ); - const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); - const bool bIsTransform = - Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || - Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); - - // auto SourceComponentIsRoot = [&]() - // { - // USceneComponent* RootComponent = SourceActor->GetRootComponent(); - // if (SourceComponent == RootComponent) - // { - // return true; - // } - // else if (RootComponent == nullptr && bSourceActorIsBPCDO) - // { - // // If we're dealing with a BP CDO as source, then look at the target for whether this is the root component - // return (TargetComponent == TargetActor->GetRootComponent()); - // } - // return false; - // }; - - TSet ModifiedObjects; - // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - // && ( !bIsTransform || (!bSourceActorIsCDO && !bTargetActorIsCDO) || !SourceComponentIsRoot() ) ) - if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) - && ( !bIsTransform )) - { - const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) - && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); - if( bIsSafeToCopy ) - { - // if (!Options.CanCopyProperty(*Property, *SourceActor)) - // { - // continue; - // } - if (!Options.CanCopyProperty(*Property, *SourceComponent)) - { - continue; - } - - if( !bIsPreviewing ) - { - if( !ModifiedObjects.Contains(TargetComponent) ) - { - TargetComponent->SetFlags(RF_Transactional); - TargetComponent->Modify(); - ModifiedObjects.Add(TargetComponent); - } - - if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) - { - TargetComponent->PreEditChange( Property ); - } - - // Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances. - TArray ComponentArchetypeInstancesToChange; - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances) - { - if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) ) - { - bool bAdd = true; - // We also need to double check that either the direct archetype of the target is also identical - if (ComponentArchetypeInstance->GetArchetype() != TargetComponent) - { - UActorComponent* CheckComponent = CastChecked(ComponentArchetypeInstance->GetArchetype()); - while (CheckComponent != ComponentArchetypeInstance) - { - if (!Property->Identical_InContainer( CheckComponent, TargetComponent )) - { - bAdd = false; - break; - } - CheckComponent = CastChecked(CheckComponent->GetArchetype()); - } - } - - if (bAdd) - { - ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance ); - } - } - } - } - - EditorUtilities::CopySingleProperty(SourceComponent, TargetComponent, Property); - - if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) - { - FPropertyChangedEvent PropertyChangedEvent( Property ); - TargetComponent->PostEditChangeProperty( PropertyChangedEvent ); - } - - if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) - { - for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex ) - { - UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex]; - if( ComponentArchetypeInstance != nullptr ) - { - if( !ModifiedObjects.Contains(ComponentArchetypeInstance) ) - { - // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. - // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. - if (!ComponentArchetypeInstance->IsCreatedByConstructionScript()) - { - ComponentArchetypeInstance->SetFlags(RF_Transactional); - ComponentArchetypeInstance->Modify(); - ModifiedObjects.Add(ComponentArchetypeInstance); - } - - // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. - AActor* Owner = ComponentArchetypeInstance->GetOwner(); - if( Owner != nullptr && !ModifiedObjects.Contains(Owner)) - { - Owner->Modify(); - ModifiedObjects.Add(Owner); - } - } - - if (ComponentArchetypeInstance->IsRegistered()) - { - ComponentArchetypeInstance->UnregisterComponent(); - ComponentInstancesToReregister.Add(ComponentArchetypeInstance); - } - - EditorUtilities::CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property ); - } - } - } - } - - ++CopiedPropertyCount; - - if( bIsTransform ) - { - bTransformChanged = true; - } - } - } - } - - for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister) - { - ModifiedComponentInstance->RegisterComponent(); - } - - return CopiedPropertyCount; -} -#endif - - -#if WITH_EDITOR -FBlueprintEditor* -FHoudiniEngineRuntimeUtils::GetBlueprintEditor(const UObject* InObject) -{ - if (!IsValid(InObject)) - return nullptr; - - UObject* Outer = InObject->GetOuter(); - if (!IsValid(Outer)) - return nullptr; - - UBlueprintGeneratedClass* OuterBPClass = Cast(Outer->GetClass()); - - if (!OuterBPClass) - return nullptr; - - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - return static_cast(AssetEditorSubsystem->FindEditorForAsset(OuterBPClass->ClassGeneratedBy, false)); -} -#endif - - -#if WITH_EDITOR -void -FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate) -{ - if (!ComponentTemplate) - return; - - UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); - if (!BPGC) - return; - - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!Blueprint) - return; - - Blueprint->Modify(); - - UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); - FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(Blueprint, false)); - check(BlueprintEditor); - - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - TSharedPtr SCSEditor = nullptr; - - SCSEditor = BlueprintEditor->GetSCSEditor(); - check(SCSEditor); - SCSEditor->SaveSCSCurrentState(SCS); - - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); - - SCSEditor->UpdateTree(true); -} -#endif - -#if WITH_EDITOR -void -FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTemplate) -{ - if (!ComponentTemplate) - return; - - UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); - if (!BPGC) - return; - - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (!Blueprint) - return; - - Blueprint->Modify(); - - FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); -} -#endif - - -#if WITH_EDITOR - -// Centralized call to set actor label (changing Actor's implementation was too risky) -bool FHoudiniEngineRuntimeUtils::SetActorLabel(AActor* Actor, const FString& ActorLabel) -{ - // Clean up the incoming string a bit - FString NewActorLabel = ActorLabel.TrimStartAndEnd(); - if (NewActorLabel == Actor->GetActorLabel()) - { - return false; - } - Actor->SetActorLabel(NewActorLabel); - return true; -} - -void -FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) -{ - FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); - Obj->PostEditChangeProperty(Evt); -} - -void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FProperty* Property) -{ - FPropertyChangedEvent Evt(Property); - Obj->PostEditChangeProperty(Evt); -} - -void FHoudiniEngineRuntimeUtils::PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange) -{ - if (!InObject) - return; - if (!InObject->HasAnyFlags(RF_ArchetypeObject)) - return; - - // Iterate over the modified properties and propagate value changed to all archetype instances - TArray ArchetypeInstances; - InObject->GetArchetypeInstances(ArchetypeInstances); - for (UObject* Instance : ArchetypeInstances) - { - UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Found Archetype instance: %s"), *(Instance->GetPathName())); - for (FName PropertyName : DeltaChange.ChangedProperties) - { - UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Changed property: %s"), *(PropertyName.ToString())); - // FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation()); - } - } -} - -void FHoudiniEngineRuntimeUtils::ForAllArchetypeInstances(UObject* InTemplateObj, TFunctionRef Operation) -{ - if (!InTemplateObj) - return; - if (!InTemplateObj->HasAnyFlags(RF_ArchetypeObject|RF_DefaultSubObject)) - return; - - TArray Instances; - InTemplateObj->GetArchetypeInstances(Instances); - - for(UObject* Instance : Instances) - { - Operation(Instance); - } -} -#endif - -FHoudiniStaticMeshGenerationProperties -FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties() -{ - FHoudiniStaticMeshGenerationProperties SMGP; - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - if (HoudiniRuntimeSettings) - { - SMGP.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; - SMGP.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; - SMGP.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; - SMGP.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; - //SMGP.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; - SMGP.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; - SMGP.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; - SMGP.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; - SMGP.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; - SMGP.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; - SMGP.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; - SMGP.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; - } - - return SMGP; -} - -FMeshBuildSettings -FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings() -{ - FMeshBuildSettings DefaultBuildSettings; - - const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); - if(HoudiniRuntimeSettings) - { - DefaultBuildSettings.bRemoveDegenerates = HoudiniRuntimeSettings->bRemoveDegenerates; - DefaultBuildSettings.bUseMikkTSpace = HoudiniRuntimeSettings->bUseMikkTSpace; - DefaultBuildSettings.bBuildAdjacencyBuffer = HoudiniRuntimeSettings->bBuildAdjacencyBuffer; - DefaultBuildSettings.MinLightmapResolution = HoudiniRuntimeSettings->MinLightmapResolution; - DefaultBuildSettings.bUseFullPrecisionUVs = HoudiniRuntimeSettings->bUseFullPrecisionUVs; - DefaultBuildSettings.SrcLightmapIndex = HoudiniRuntimeSettings->SrcLightmapIndex; - DefaultBuildSettings.DstLightmapIndex = HoudiniRuntimeSettings->DstLightmapIndex; - - DefaultBuildSettings.bComputeWeightedNormals = HoudiniRuntimeSettings->bComputeWeightedNormals; - DefaultBuildSettings.bBuildReversedIndexBuffer = HoudiniRuntimeSettings->bBuildReversedIndexBuffer; - DefaultBuildSettings.bUseHighPrecisionTangentBasis = HoudiniRuntimeSettings->bUseHighPrecisionTangentBasis; - DefaultBuildSettings.bGenerateDistanceFieldAsIfTwoSided = HoudiniRuntimeSettings->bGenerateDistanceFieldAsIfTwoSided; - DefaultBuildSettings.bSupportFaceRemap = HoudiniRuntimeSettings->bSupportFaceRemap; - DefaultBuildSettings.DistanceFieldResolutionScale = HoudiniRuntimeSettings->DistanceFieldResolutionScale; - - // Recomputing normals. - EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag; - switch (RecomputeNormalFlag) - { - case HRSRF_Never: - { - DefaultBuildSettings.bRecomputeNormals = false; - break; - } - - case HRSRF_Always: - case HRSRF_OnlyIfMissing: - default: - { - DefaultBuildSettings.bRecomputeNormals = true; - break; - } - } - - // Recomputing tangents. - EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; - switch (RecomputeTangentFlag) - { - case HRSRF_Never: - { - DefaultBuildSettings.bRecomputeTangents = false; - break; - } - - case HRSRF_Always: - case HRSRF_OnlyIfMissing: - default: - { - DefaultBuildSettings.bRecomputeTangents = true; - break; - } - } - - // Lightmap UV generation. - EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; - switch (GenerateLightmapUVFlag) - { - case HRSRF_Never: - { - DefaultBuildSettings.bGenerateLightmapUVs = false; - break; - } - - case HRSRF_Always: - case HRSRF_OnlyIfMissing: - default: - { - DefaultBuildSettings.bGenerateLightmapUVs = true; - break; - } - } - } - - return DefaultBuildSettings; -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniRuntimeSettings.h" + +#include "EngineUtils.h" +#include "Engine/EngineTypes.h" + +#if WITH_EDITOR + #include "Editor.h" + #include "Kismet2/BlueprintEditorUtils.h" +#endif + + +FString +FHoudiniEngineRuntimeUtils::GetLibHAPIName() +{ + static const FString LibHAPIName = + +#if PLATFORM_WINDOWS + HAPI_LIB_OBJECT_WINDOWS; +#elif PLATFORM_MAC + HAPI_LIB_OBJECT_MAC; +#elif PLATFORM_LINUX + HAPI_LIB_OBJECT_LINUX; +#else + TEXT(""); +#endif + + return LibHAPIName; +} + + +void +FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes) +{ + OutBBoxes.Empty(); + + for (auto CurrentActor : InActors) + { + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true)); + } +} + + +bool +FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors) +{ + if (!IsValid(World)) + return false; + + OutActors.Empty(); + for (TActorIterator ActorItr(World); ActorItr; ++ActorItr) + { + AActor* CurrentActor = *ActorItr; + if (!IsValid(CurrentActor)) + continue; + + if (!CurrentActor->GetClass()->IsChildOf(ActorType.Get())) + continue; + + if (ExcludeActors && ExcludeActors->Contains(CurrentActor)) + continue; + + // Special case + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); + for (auto InBounds : BBoxes) + { + // Check if both actor's bounds intersects + if (!ActorBounds.Intersect(InBounds)) + continue; + + OutActors.Add(CurrentActor); + break; + } + } + + return true; +} + +bool +FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly) +{ + bool bDeleted = false; + OutPackage = nullptr; + bOutPackageIsInMemoryOnly = false; + + if (!IsValid(InObjectToDelete)) + return false; + + // Don't try to delete the object if it has references (we do this here to avoid the FMessageDialog in DeleteSingleObject + bool bIsReferenced = false; + bool bIsReferencedByUndo = false; + if (!GatherObjectReferencersForDeletion(InObjectToDelete, bIsReferenced, bIsReferencedByUndo)) + return false; + + if (bIsReferenced) + { + HOUDINI_LOG_WARNING(TEXT("[FHoudiniEngineRuntimeUtils::SafeDeleteSingleObject] Not deleting %s: there are still references to it."), *InObjectToDelete->GetFullName()); + } + else + { + // Even though we already checked for references, we still let DeleteSingleObject check for references, since + // we want that code path where it'll clean up in-memory references (undo buffer/transactions) + const bool bCheckForReferences = true; + if (DeleteSingleObject(InObjectToDelete, bCheckForReferences)) + { + bDeleted = true; + + OutPackage = InObjectToDelete->GetOutermost(); + + FString PackageFilename; + if (!IsValid(OutPackage) || !FPackageName::DoesPackageExist(OutPackage->GetName(), nullptr, &PackageFilename)) + { + // Package is in memory only, we don't have call CleanUpAfterSuccessfulDelete on it, just do garbage + // collection to pick up the stale package + bOutPackageIsInMemoryOnly = true; + } + else + { + // There is an on-disk package that is now potentially empty, CleanUpAfterSuccessfulDelete must be + // called on this. Since CleanUpAfterSuccessfulDelete does garbage collection, we return the Package + // as part of this function so that the caller can collect all Packages and do one call to + // CleanUpAfterSuccessfulDelete with an array + } + } + } + + return bDeleted; +} + +int32 +FHoudiniEngineRuntimeUtils::SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted) +{ + int32 NumDeleted = 0; + bool bGarbageCollectionRequired = false; + TSet PackagesToCleanUp; + TSet ProcessedObjects; + while (InObjectsToDelete.Num() > 0) + { + UObject* const ObjectToDelete = InObjectsToDelete.Pop(); + + if (ProcessedObjects.Contains(ObjectToDelete)) + continue; + + ProcessedObjects.Add(ObjectToDelete); + + if (!IsValid(ObjectToDelete)) + continue; + + UPackage* Package = nullptr; + bool bInMemoryPackageOnly = false; + if (SafeDeleteSingleObject(ObjectToDelete, Package, bInMemoryPackageOnly)) + { + NumDeleted++; + if (bInMemoryPackageOnly) + { + // Packages that are in-memory only are cleaned up by garbage collection + if (!bGarbageCollectionRequired) + bGarbageCollectionRequired = true; + } + else + { + // Clean up potentially empty packages in one call to CleanupAfterSuccessfulDelete at the end + PackagesToCleanUp.Add(Package); + } + } + else if (OutObjectsNotDeleted) + { + OutObjectsNotDeleted->Add(ObjectToDelete); + } + } + + // CleanupAfterSuccessfulDelete calls CollectGarbage, so don't call it here if we have PackagesToCleanUp + if (bGarbageCollectionRequired && PackagesToCleanUp.Num() <= 0) + CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); + + if (PackagesToCleanUp.Num() > 0) + CleanupAfterSuccessfulDelete(PackagesToCleanUp.Array()); + + return NumDeleted; +} + + +#if WITH_EDITOR +int32 +FHoudiniEngineRuntimeUtils::CopyComponentProperties(UActorComponent* SourceComponent, UActorComponent* TargetComponent, const EditorUtilities::FCopyOptions& Options) +{ + UClass* ComponentClass = SourceComponent->GetClass(); + check( ComponentClass == TargetComponent->GetClass() ); + + const bool bIsPreviewing = ( Options.Flags & EditorUtilities::ECopyOptions::PreviewOnly ) != 0; + int32 CopiedPropertyCount = 0; + bool bTransformChanged = false; + + // Build a list of matching component archetype instances for propagation (if requested) + TArray ComponentArchetypeInstances; + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + TArray Instances; + TargetComponent->GetArchetypeInstances(Instances); + for(UObject* ObjInstance : Instances) + { + UActorComponent* ComponentInstance = Cast(ObjInstance); + if (ComponentInstance && ComponentInstance != SourceComponent && ComponentInstance != TargetComponent) + { + ComponentArchetypeInstances.Add(ComponentInstance); + } + } + } + + TSet SourceUCSModifiedProperties; + SourceComponent->GetUCSModifiedProperties(SourceUCSModifiedProperties); + + TArray ComponentInstancesToReregister; + + // Copy component properties + for( FProperty* Property = ComponentClass->PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext ) + { + const bool bIsTransient = !!( Property->PropertyFlags & CPF_Transient ); + const bool bIsIdentical = Property->Identical_InContainer( SourceComponent, TargetComponent ); + const bool bIsComponent = !!( Property->PropertyFlags & ( CPF_InstancedReference | CPF_ContainsInstancedReference ) ); + const bool bIsTransform = + Property->GetFName() == USceneComponent::GetRelativeScale3DPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeLocationPropertyName() || + Property->GetFName() == USceneComponent::GetRelativeRotationPropertyName(); + + // auto SourceComponentIsRoot = [&]() + // { + // USceneComponent* RootComponent = SourceActor->GetRootComponent(); + // if (SourceComponent == RootComponent) + // { + // return true; + // } + // else if (RootComponent == nullptr && bSourceActorIsBPCDO) + // { + // // If we're dealing with a BP CDO as source, then look at the target for whether this is the root component + // return (TargetComponent == TargetActor->GetRootComponent()); + // } + // return false; + // }; + + TSet ModifiedObjects; + // if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + // && ( !bIsTransform || (!bSourceActorIsCDO && !bTargetActorIsCDO) || !SourceComponentIsRoot() ) ) + if( !bIsTransient && !bIsIdentical && !bIsComponent && !SourceUCSModifiedProperties.Contains(Property) + && ( !bIsTransform )) + { + const bool bIsSafeToCopy = (!(Options.Flags & EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties) || (Property->HasAnyPropertyFlags(CPF_Edit | CPF_Interp))) + && (!(Options.Flags & EditorUtilities::ECopyOptions::SkipInstanceOnlyProperties) || (!Property->HasAllPropertyFlags(CPF_DisableEditOnTemplate))); + if( bIsSafeToCopy ) + { + // if (!Options.CanCopyProperty(*Property, *SourceActor)) + // { + // continue; + // } + if (!Options.CanCopyProperty(*Property, *SourceComponent)) + { + continue; + } + + if( !bIsPreviewing ) + { + if( !ModifiedObjects.Contains(TargetComponent) ) + { + TargetComponent->SetFlags(RF_Transactional); + TargetComponent->Modify(); + ModifiedObjects.Add(TargetComponent); + } + + if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) + { + TargetComponent->PreEditChange( Property ); + } + + // Determine which component archetype instances match the current property value of the target component (before it gets changed). We only want to propagate the change to those instances. + TArray ComponentArchetypeInstancesToChange; + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + for (UActorComponent* ComponentArchetypeInstance : ComponentArchetypeInstances) + { + if( ComponentArchetypeInstance != nullptr && Property->Identical_InContainer( ComponentArchetypeInstance, TargetComponent ) ) + { + bool bAdd = true; + // We also need to double check that either the direct archetype of the target is also identical + if (ComponentArchetypeInstance->GetArchetype() != TargetComponent) + { + UActorComponent* CheckComponent = CastChecked(ComponentArchetypeInstance->GetArchetype()); + while (CheckComponent != ComponentArchetypeInstance) + { + if (!Property->Identical_InContainer( CheckComponent, TargetComponent )) + { + bAdd = false; + break; + } + CheckComponent = CastChecked(CheckComponent->GetArchetype()); + } + } + + if (bAdd) + { + ComponentArchetypeInstancesToChange.Add( ComponentArchetypeInstance ); + } + } + } + } + + EditorUtilities::CopySingleProperty(SourceComponent, TargetComponent, Property); + + if( Options.Flags & EditorUtilities::ECopyOptions::CallPostEditChangeProperty ) + { + FPropertyChangedEvent PropertyChangedEvent( Property ); + TargetComponent->PostEditChangeProperty( PropertyChangedEvent ); + } + + if( Options.Flags & EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances ) + { + for( int32 InstanceIndex = 0; InstanceIndex < ComponentArchetypeInstancesToChange.Num(); ++InstanceIndex ) + { + UActorComponent* ComponentArchetypeInstance = ComponentArchetypeInstancesToChange[InstanceIndex]; + if( ComponentArchetypeInstance != nullptr ) + { + if( !ModifiedObjects.Contains(ComponentArchetypeInstance) ) + { + // Ensure that this instance will be included in any undo/redo operations, and record it into the transaction buffer. + // Note: We don't do this for components that originate from script, because they will be re-instanced from the template after an undo, so there is no need to record them. + if (!ComponentArchetypeInstance->IsCreatedByConstructionScript()) + { + ComponentArchetypeInstance->SetFlags(RF_Transactional); + ComponentArchetypeInstance->Modify(); + ModifiedObjects.Add(ComponentArchetypeInstance); + } + + // We must also modify the owner, because we'll need script components to be reconstructed as part of an undo operation. + AActor* Owner = ComponentArchetypeInstance->GetOwner(); + if( Owner != nullptr && !ModifiedObjects.Contains(Owner)) + { + Owner->Modify(); + ModifiedObjects.Add(Owner); + } + } + + if (ComponentArchetypeInstance->IsRegistered()) + { + ComponentArchetypeInstance->UnregisterComponent(); + ComponentInstancesToReregister.Add(ComponentArchetypeInstance); + } + + EditorUtilities::CopySingleProperty( TargetComponent, ComponentArchetypeInstance, Property ); + } + } + } + } + + ++CopiedPropertyCount; + + if( bIsTransform ) + { + bTransformChanged = true; + } + } + } + } + + for (UActorComponent* ModifiedComponentInstance : ComponentInstancesToReregister) + { + ModifiedComponentInstance->RegisterComponent(); + } + + return CopiedPropertyCount; +} +#endif + + +#if WITH_EDITOR +FBlueprintEditor* +FHoudiniEngineRuntimeUtils::GetBlueprintEditor(const UObject* InObject) +{ + if (!IsValid(InObject)) + return nullptr; + + UObject* Outer = InObject->GetOuter(); + if (!IsValid(Outer)) + return nullptr; + + UBlueprintGeneratedClass* OuterBPClass = Cast(Outer->GetClass()); + + if (!OuterBPClass) + return nullptr; + + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + return static_cast(AssetEditorSubsystem->FindEditorForAsset(OuterBPClass->ClassGeneratedBy, false)); +} +#endif + + +#if WITH_EDITOR +void +FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate) +{ + if (!ComponentTemplate) + return; + + UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); + if (!BPGC) + return; + + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!Blueprint) + return; + + Blueprint->Modify(); + + UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); + FBlueprintEditor* BlueprintEditor = static_cast(AssetEditorSubsystem->FindEditorForAsset(Blueprint, false)); + check(BlueprintEditor); + + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + TSharedPtr SCSEditor = nullptr; + + SCSEditor = BlueprintEditor->GetSCSEditor(); + check(SCSEditor); + SCSEditor->SaveSCSCurrentState(SCS); + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + + SCSEditor->UpdateTree(true); +} +#endif + +#if WITH_EDITOR +void +FHoudiniEngineRuntimeUtils::MarkBlueprintAsModified(UActorComponent* ComponentTemplate) +{ + if (!ComponentTemplate) + return; + + UBlueprintGeneratedClass* BPGC = Cast(ComponentTemplate->GetOuter()); + if (!BPGC) + return; + + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (!Blueprint) + return; + + Blueprint->Modify(); + + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); +} +#endif + + +#if WITH_EDITOR + +// Centralized call to set actor label (changing Actor's implementation was too risky) +bool FHoudiniEngineRuntimeUtils::SetActorLabel(AActor* Actor, const FString& ActorLabel) +{ + // Clean up the incoming string a bit + FString NewActorLabel = ActorLabel.TrimStartAndEnd(); + if (NewActorLabel == Actor->GetActorLabel()) + { + return false; + } + Actor->SetActorLabel(NewActorLabel); + return true; +} + +void +FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FName PropertyName) +{ + FPropertyChangedEvent Evt(FindFieldChecked(Obj->GetClass(), PropertyName)); + Obj->PostEditChangeProperty(Evt); +} + +void FHoudiniEngineRuntimeUtils::DoPostEditChangeProperty(UObject* Obj, FProperty* Property) +{ + FPropertyChangedEvent Evt(Property); + Obj->PostEditChangeProperty(Evt); +} + +void FHoudiniEngineRuntimeUtils::PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange) +{ + if (!InObject) + return; + if (!InObject->HasAnyFlags(RF_ArchetypeObject)) + return; + + // Iterate over the modified properties and propagate value changed to all archetype instances + TArray ArchetypeInstances; + InObject->GetArchetypeInstances(ArchetypeInstances); + for (UObject* Instance : ArchetypeInstances) + { + UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Found Archetype instance: %s"), *(Instance->GetPathName())); + for (FName PropertyName : DeltaChange.ChangedProperties) + { + UE_LOG(LogTemp, Log, TEXT("[void FHoudiniEngineRuntimeUtils::PropagateTransactionToArchetypeInstance] Changed property: %s"), *(PropertyName.ToString())); + // FComponentEditorUtils::ApplyDefaultValueChange(SceneComp, SceneComp->GetRelativeLocation_DirectMutable(), OldRelativeLocation, SelectedTemplate->GetRelativeLocation()); + } + } +} + +void FHoudiniEngineRuntimeUtils::ForAllArchetypeInstances(UObject* InTemplateObj, TFunctionRef Operation) +{ + if (!InTemplateObj) + return; + if (!InTemplateObj->HasAnyFlags(RF_ArchetypeObject|RF_DefaultSubObject)) + return; + + TArray Instances; + InTemplateObj->GetArchetypeInstances(Instances); + + for(UObject* Instance : Instances) + { + Operation(Instance); + } +} +#endif + +FHoudiniStaticMeshGenerationProperties +FHoudiniEngineRuntimeUtils::GetDefaultStaticMeshGenerationProperties() +{ + FHoudiniStaticMeshGenerationProperties SMGP; + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + if (HoudiniRuntimeSettings) + { + SMGP.bGeneratedDoubleSidedGeometry = HoudiniRuntimeSettings->bDoubleSidedGeometry; + SMGP.GeneratedPhysMaterial = HoudiniRuntimeSettings->PhysMaterial; + SMGP.DefaultBodyInstance = HoudiniRuntimeSettings->DefaultBodyInstance; + SMGP.GeneratedCollisionTraceFlag = HoudiniRuntimeSettings->CollisionTraceFlag; + //SMGP.GeneratedLpvBiasMultiplier = HoudiniRuntimeSettings->LpvBiasMultiplier; + SMGP.GeneratedLightMapResolution = HoudiniRuntimeSettings->LightMapResolution; + SMGP.GeneratedLightMapCoordinateIndex = HoudiniRuntimeSettings->LightMapCoordinateIndex; + SMGP.bGeneratedUseMaximumStreamingTexelRatio = HoudiniRuntimeSettings->bUseMaximumStreamingTexelRatio; + SMGP.GeneratedStreamingDistanceMultiplier = HoudiniRuntimeSettings->StreamingDistanceMultiplier; + SMGP.GeneratedWalkableSlopeOverride = HoudiniRuntimeSettings->WalkableSlopeOverride; + SMGP.GeneratedFoliageDefaultSettings = HoudiniRuntimeSettings->FoliageDefaultSettings; + SMGP.GeneratedAssetUserData = HoudiniRuntimeSettings->AssetUserData; + } + + return SMGP; +} + +FMeshBuildSettings +FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings() +{ + FMeshBuildSettings DefaultBuildSettings; + + const UHoudiniRuntimeSettings* HoudiniRuntimeSettings = GetDefault(); + if(HoudiniRuntimeSettings) + { + DefaultBuildSettings.bRemoveDegenerates = HoudiniRuntimeSettings->bRemoveDegenerates; + DefaultBuildSettings.bUseMikkTSpace = HoudiniRuntimeSettings->bUseMikkTSpace; + DefaultBuildSettings.bBuildAdjacencyBuffer = HoudiniRuntimeSettings->bBuildAdjacencyBuffer; + DefaultBuildSettings.MinLightmapResolution = HoudiniRuntimeSettings->MinLightmapResolution; + DefaultBuildSettings.bUseFullPrecisionUVs = HoudiniRuntimeSettings->bUseFullPrecisionUVs; + DefaultBuildSettings.SrcLightmapIndex = HoudiniRuntimeSettings->SrcLightmapIndex; + DefaultBuildSettings.DstLightmapIndex = HoudiniRuntimeSettings->DstLightmapIndex; + + DefaultBuildSettings.bComputeWeightedNormals = HoudiniRuntimeSettings->bComputeWeightedNormals; + DefaultBuildSettings.bBuildReversedIndexBuffer = HoudiniRuntimeSettings->bBuildReversedIndexBuffer; + DefaultBuildSettings.bUseHighPrecisionTangentBasis = HoudiniRuntimeSettings->bUseHighPrecisionTangentBasis; + DefaultBuildSettings.bGenerateDistanceFieldAsIfTwoSided = HoudiniRuntimeSettings->bGenerateDistanceFieldAsIfTwoSided; + DefaultBuildSettings.bSupportFaceRemap = HoudiniRuntimeSettings->bSupportFaceRemap; + DefaultBuildSettings.DistanceFieldResolutionScale = HoudiniRuntimeSettings->DistanceFieldResolutionScale; + + // Recomputing normals. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeNormalFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeNormalsFlag; + switch (RecomputeNormalFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bRecomputeNormals = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bRecomputeNormals = true; + break; + } + } + + // Recomputing tangents. + EHoudiniRuntimeSettingsRecomputeFlag RecomputeTangentFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; + switch (RecomputeTangentFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bRecomputeTangents = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bRecomputeTangents = true; + break; + } + } + + // Lightmap UV generation. + EHoudiniRuntimeSettingsRecomputeFlag GenerateLightmapUVFlag = (EHoudiniRuntimeSettingsRecomputeFlag)HoudiniRuntimeSettings->RecomputeTangentsFlag; + switch (GenerateLightmapUVFlag) + { + case HRSRF_Never: + { + DefaultBuildSettings.bGenerateLightmapUVs = false; + break; + } + + case HRSRF_Always: + case HRSRF_OnlyIfMissing: + default: + { + DefaultBuildSettings.bGenerateLightmapUVs = true; + break; + } + } + } + + return DefaultBuildSettings; +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h index b0f9ac3eb..5295af722 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.h @@ -1,357 +1,357 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/ObjectMacros.h" -#include "UObject/UObjectGlobals.h" -#include "UObject/Class.h" -#include "UObject/Package.h" - -#if WITH_EDITOR - #include "SSCSEditor.h" - #include "ObjectTools.h" - #include "Kismet2/ComponentEditorUtils.h" - #include "Editor/Transactor.h" -#endif - -class AActor; -class UWorld; -struct FHoudiniStaticMeshGenerationProperties; -struct FMeshBuildSettings; - -template -class TSubclassOf; - -struct FBox; - - -struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils -{ - public: - - // Return platform specific name of libHAPI. - static FString GetLibHAPIName(); - - // Returns default SM Generation Properties using the default settings values - static FHoudiniStaticMeshGenerationProperties GetDefaultStaticMeshGenerationProperties(); - - // Returns default SM Build Settings using the default settings values - static FMeshBuildSettings GetDefaultMeshBuildSettings(); - - // ----------------------------------------------- - // Bounding Box utilities - // ----------------------------------------------- - - // Collect all the bounding boxes form the specified list of actors. OutBBoxes will be emptied. - static void GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes); - - // Collect actors that derive from the given class that intersect with the given array of bounding boxes. - static bool FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors); - - // ----------------------------------------------- - // File path utilities - // ----------------------------------------------- - - // Joins paths by taking into account whether paths - // successive paths are relative or absolute. - // Truncate everything preceding an absolute path. - // Taken and adapted from FPaths::Combine(). - template - FORCEINLINE static FString JoinPaths(PathTypes&&... InPaths) - { - const TCHAR* Paths[] = { GetTCharPtr(Forward(InPaths))... }; - const int32 NumPaths = UE_ARRAY_COUNT(Paths); - - FString Out = TEXT(""); - if (NumPaths <= 0) - return Out; - Out = Paths[NumPaths-1]; - // Process paths in reverse and terminate when we reach an absolute path. - for (int32 i=NumPaths-2; i >= 0; --i) - { - if (FCString::Strlen(Paths[i]) == 0) - continue; - if (Out[0] == '/') - { - // We already have an absolute path. Terminate. - break; - } - Out = Paths[i] / Out; - } - if (Out.Len() > 0 && Out[0] != '/') - Out = TEXT("/") + Out; - return Out; - } - - // ------------------------------------------------------------------ - // ObjectTools (Make some editor only ObjectTools functions available - // in editor builds) - // ------------------------------------------------------------------ - - // Check/gather references to InObject. - // Returns true if the function could execute (editor vs runtime) - FORCEINLINE static bool GatherObjectReferencersForDeletion(UObject* InObject, bool& bOutIsReferenced, bool& bOutIsReferencedByUndo, FReferencerInformationList* OutMemoryReferences = nullptr, bool bInRequireReferencingProperties = false) - { -#if WITH_EDITOR - ObjectTools::GatherObjectReferencersForDeletion(InObject, bOutIsReferenced, bOutIsReferencedByUndo, OutMemoryReferences, bInRequireReferencingProperties); - - return true; -#else - return false; -#endif - } - - // Delete a single object from its package. Returns true if the object was deleted. In non-editor - // builds this function returns false. - FORCEINLINE static bool DeleteSingleObject(UObject* InObjectToDelete, bool bInPerformReferenceCheck=true) - { -#if WITH_EDITOR - return ObjectTools::DeleteSingleObject(InObjectToDelete, bInPerformReferenceCheck); -#else - return false; -#endif - } - - // Collects garbage and marks truely empty packages for delete - // Returns true if the function could execute (editor vs runtime) - FORCEINLINE static bool CleanupAfterSuccessfulDelete(const TArray& InObjectsDeletedSuccessfully, bool bInPerformReferenceCheck=true) - { -#if WITH_EDITOR - ObjectTools::CleanupAfterSuccessfulDelete(InObjectsDeletedSuccessfully, bInPerformReferenceCheck); - return true; -#else - return false; -#endif - } - - // Deletes a single object. Returns true if the object was deleted. - // The object is only deleted if there are no references to it. - // If the package is on disk then bOutPackageIsInMemoryOnly is false and the CleanUpAfterSuccessfulDelete - // must be called on the package after execution of this function. - static bool SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly); - - // Deletes and cleans up on disk empty-packages for the objects in InObjectsToDelete. - // Objects are popped from InObjectsToDelete as they are processed (ran into cases where objects are garbage collected - // before we can properly delete them and cleanup their packages, so we tend to pass in a UPROPERTY based TArray - // that holds references to UObject to prevent garbage collection until we can delete them in this function) - // OutObjectsNotDeleted can optionally be used to return objects that could not be deleted. - // The function returns the number of objects that were deleted. - static int32 SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted=nullptr); - - // ------------------------------------------------- - // Type utilities - // ------------------------------------------------- - - // Taken from here: https://answers.unrealengine.com/questions/330496/conversion-of-enum-to-string.html - // Return the string representation of an enum value. - template - static FString EnumToString(const FString& EnumName, const T Value) - { - UEnum* Enum = FindObject(ANY_PACKAGE, *EnumName); - return *(Enum ? Enum->GetNameStringByValue(static_cast(Value)) : "null"); - } - - template - static FString EnumToString(const T Value) - { - return UEnum::GetValueAsString(Value); - } - - // ------------------------------------------------- - // Blueprint utilities - // ------------------------------------------------- -#if WITH_EDITOR - // This function contains an excerpt from UEditorUtilities::CopyActorProperties() - // for specifically dealing with copying properties between components as well as propagating - // property changes to archetype instances. - static int32 CopyComponentProperties(UActorComponent* FromComponent, UActorComponent* ToComponent, const EditorUtilities::FCopyOptions& Options); - - // Get the SCSEditor for the given HoudiniAssetComponent - static FBlueprintEditor* GetBlueprintEditor(const UObject* InObject); - - static void MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate); - static void MarkBlueprintAsModified(UActorComponent* ComponentTemplate); -#endif - - // ------------------------------------------------- - // Editor Helpers - // ------------------------------------------------- -#if WITH_EDITOR - static bool SetActorLabel(AActor* Actor, const FString& ActorLabel); - - static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); - - static void DoPostEditChangeProperty(UObject* Obj, FProperty* Property); - - static void PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange); - - template - static TSet PropagateDefaultValueChange(USceneComponent* InSceneComponentTemplate, const FName& PropertyName, const T& OldValue, const T& NewValue) - { - TSet UpdatedInstances; - FComponentEditorUtils::PropagateDefaultValueChange(InSceneComponentTemplate, FindFieldChecked(InSceneComponentTemplate->GetClass(), PropertyName), OldValue, NewValue, UpdatedInstances); - return UpdatedInstances; - } - - // Perform this operation on the given archetype as well as each archetype instance. If the given - // object in not an archetype instance, then don't do anything. - static void ForAllArchetypeInstances(UObject* Archetype, TFunctionRef Operation); - -#endif - - /** - // * Set the value on an UObject using reflection. - // * @param Object The object to copy the value into. - // * @param PropertyName The name of the property to set. - // * @param Value The value to assign to the property. - // * - // * @return true if the value was set correctly - // */ - //template - //static bool SetPropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) - //{ - // // Get the property addresses for the source and destination objects. - // FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - - // // Get the property addresses for the object - // ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); - - // if ( SourceAddr == NULL ) - // { - // return false; - // } - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - // { - // FEditPropertyChain PropertyChain; - // PropertyChain.AddHead(Property); - // Object->PreEditChange(PropertyChain); - // } - - // // Set the value on the destination object. - // *SourceAddr = Value; - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - // { - // FPropertyChangedEvent PropertyEvent(Property); - // Object->PostEditChangeProperty(PropertyEvent); - // } - - // return true; - //} -#if WITH_EDITOR - template - static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) - { - // Get the property addresses for the source and destination objects. - FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - - // Get the property addresses for the object - ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); - - if ( SourceAddr == NULL ) - { - return false; - } - - if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FEditPropertyChain PropertyChain; - PropertyChain.AddHead(Property); - ((UObject*)Object)->PreEditChange(PropertyChain); - } - - // Set the value on the destination object. - if (*SourceAddr != Value) - { - TSet UpdatedInstances; - *SourceAddr = Value; - PropagateDefaultValueChange(Object, Property, *SourceAddr, Value, UpdatedInstances); - } - - if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FPropertyChangedEvent PropertyEvent(Property); - Object->PostEditChangeProperty(PropertyEvent); - } - - return true; - } -#endif - -#if WITH_EDITOR - // Bool specialization - template - static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, bool NewBool) - { - // Get the property addresses for the source and destination objects. - FBoolProperty* BoolProperty = FindFieldChecked(ObjectType::StaticClass(), PropertyName); - check(BoolProperty); - - // Get the property addresses for the object - const int32 PropertyOffset = INDEX_NONE; - void* CurrentValue = PropertyOffset == INDEX_NONE ? BoolProperty->ContainerPtrToValuePtr(Object) : ((uint8*)Object + PropertyOffset); - check(CurrentValue); - - const bool CurrentBool = BoolProperty->GetPropertyValue(CurrentValue); - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FEditPropertyChain PropertyChain; - PropertyChain.AddHead(BoolProperty); - ((UObject*)Object)->PreEditChange(PropertyChain); - } - - // Set the value on the destination object. - if (CurrentBool != NewBool) - { - TSet UpdatedInstances; - BoolProperty->SetPropertyValue(CurrentValue, NewBool); - FComponentEditorUtils::PropagateDefaultValueChange(Object, BoolProperty, CurrentBool, NewBool, UpdatedInstances); - } - - // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) - { - FPropertyChangedEvent PropertyEvent(BoolProperty); - Object->PostEditChangeProperty(PropertyEvent); - } - - return true; - } -#endif - - protected: - // taken from FPaths::GetTCharPtr - FORCEINLINE static const TCHAR* GetTCharPtr(const TCHAR* Ptr) - { - return Ptr; - } - FORCEINLINE static const TCHAR* GetTCharPtr(const FString& Str) - { - return *Str; - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/ObjectMacros.h" +#include "UObject/UObjectGlobals.h" +#include "UObject/Class.h" +#include "UObject/Package.h" + +#if WITH_EDITOR + #include "SSCSEditor.h" + #include "ObjectTools.h" + #include "Kismet2/ComponentEditorUtils.h" + #include "Editor/Transactor.h" +#endif + +class AActor; +class UWorld; +struct FHoudiniStaticMeshGenerationProperties; +struct FMeshBuildSettings; + +template +class TSubclassOf; + +struct FBox; + + +struct HOUDINIENGINERUNTIME_API FHoudiniEngineRuntimeUtils +{ + public: + + // Return platform specific name of libHAPI. + static FString GetLibHAPIName(); + + // Returns default SM Generation Properties using the default settings values + static FHoudiniStaticMeshGenerationProperties GetDefaultStaticMeshGenerationProperties(); + + // Returns default SM Build Settings using the default settings values + static FMeshBuildSettings GetDefaultMeshBuildSettings(); + + // ----------------------------------------------- + // Bounding Box utilities + // ----------------------------------------------- + + // Collect all the bounding boxes form the specified list of actors. OutBBoxes will be emptied. + static void GetBoundingBoxesFromActors(const TArray InActors, TArray& OutBBoxes); + + // Collect actors that derive from the given class that intersect with the given array of bounding boxes. + static bool FindActorsOfClassInBounds(UWorld* World, TSubclassOf ActorType, const TArray& BBoxes, const TArray* ExcludeActors, TArray& OutActors); + + // ----------------------------------------------- + // File path utilities + // ----------------------------------------------- + + // Joins paths by taking into account whether paths + // successive paths are relative or absolute. + // Truncate everything preceding an absolute path. + // Taken and adapted from FPaths::Combine(). + template + FORCEINLINE static FString JoinPaths(PathTypes&&... InPaths) + { + const TCHAR* Paths[] = { GetTCharPtr(Forward(InPaths))... }; + const int32 NumPaths = UE_ARRAY_COUNT(Paths); + + FString Out = TEXT(""); + if (NumPaths <= 0) + return Out; + Out = Paths[NumPaths-1]; + // Process paths in reverse and terminate when we reach an absolute path. + for (int32 i=NumPaths-2; i >= 0; --i) + { + if (FCString::Strlen(Paths[i]) == 0) + continue; + if (Out[0] == '/') + { + // We already have an absolute path. Terminate. + break; + } + Out = Paths[i] / Out; + } + if (Out.Len() > 0 && Out[0] != '/') + Out = TEXT("/") + Out; + return Out; + } + + // ------------------------------------------------------------------ + // ObjectTools (Make some editor only ObjectTools functions available + // in editor builds) + // ------------------------------------------------------------------ + + // Check/gather references to InObject. + // Returns true if the function could execute (editor vs runtime) + FORCEINLINE static bool GatherObjectReferencersForDeletion(UObject* InObject, bool& bOutIsReferenced, bool& bOutIsReferencedByUndo, FReferencerInformationList* OutMemoryReferences = nullptr, bool bInRequireReferencingProperties = false) + { +#if WITH_EDITOR + ObjectTools::GatherObjectReferencersForDeletion(InObject, bOutIsReferenced, bOutIsReferencedByUndo, OutMemoryReferences, bInRequireReferencingProperties); + + return true; +#else + return false; +#endif + } + + // Delete a single object from its package. Returns true if the object was deleted. In non-editor + // builds this function returns false. + FORCEINLINE static bool DeleteSingleObject(UObject* InObjectToDelete, bool bInPerformReferenceCheck=true) + { +#if WITH_EDITOR + return ObjectTools::DeleteSingleObject(InObjectToDelete, bInPerformReferenceCheck); +#else + return false; +#endif + } + + // Collects garbage and marks truely empty packages for delete + // Returns true if the function could execute (editor vs runtime) + FORCEINLINE static bool CleanupAfterSuccessfulDelete(const TArray& InObjectsDeletedSuccessfully, bool bInPerformReferenceCheck=true) + { +#if WITH_EDITOR + ObjectTools::CleanupAfterSuccessfulDelete(InObjectsDeletedSuccessfully, bInPerformReferenceCheck); + return true; +#else + return false; +#endif + } + + // Deletes a single object. Returns true if the object was deleted. + // The object is only deleted if there are no references to it. + // If the package is on disk then bOutPackageIsInMemoryOnly is false and the CleanUpAfterSuccessfulDelete + // must be called on the package after execution of this function. + static bool SafeDeleteSingleObject(UObject* const InObjectToDelete, UPackage*& OutPackage, bool& bOutPackageIsInMemoryOnly); + + // Deletes and cleans up on disk empty-packages for the objects in InObjectsToDelete. + // Objects are popped from InObjectsToDelete as they are processed (ran into cases where objects are garbage collected + // before we can properly delete them and cleanup their packages, so we tend to pass in a UPROPERTY based TArray + // that holds references to UObject to prevent garbage collection until we can delete them in this function) + // OutObjectsNotDeleted can optionally be used to return objects that could not be deleted. + // The function returns the number of objects that were deleted. + static int32 SafeDeleteObjects(TArray& InObjectsToDelete, TArray* OutObjectsNotDeleted=nullptr); + + // ------------------------------------------------- + // Type utilities + // ------------------------------------------------- + + // Taken from here: https://answers.unrealengine.com/questions/330496/conversion-of-enum-to-string.html + // Return the string representation of an enum value. + template + static FString EnumToString(const FString& EnumName, const T Value) + { + UEnum* Enum = FindObject(ANY_PACKAGE, *EnumName); + return *(Enum ? Enum->GetNameStringByValue(static_cast(Value)) : "null"); + } + + template + static FString EnumToString(const T Value) + { + return UEnum::GetValueAsString(Value); + } + + // ------------------------------------------------- + // Blueprint utilities + // ------------------------------------------------- +#if WITH_EDITOR + // This function contains an excerpt from UEditorUtilities::CopyActorProperties() + // for specifically dealing with copying properties between components as well as propagating + // property changes to archetype instances. + static int32 CopyComponentProperties(UActorComponent* FromComponent, UActorComponent* ToComponent, const EditorUtilities::FCopyOptions& Options); + + // Get the SCSEditor for the given HoudiniAssetComponent + static FBlueprintEditor* GetBlueprintEditor(const UObject* InObject); + + static void MarkBlueprintAsStructurallyModified(UActorComponent* ComponentTemplate); + static void MarkBlueprintAsModified(UActorComponent* ComponentTemplate); +#endif + + // ------------------------------------------------- + // Editor Helpers + // ------------------------------------------------- +#if WITH_EDITOR + static bool SetActorLabel(AActor* Actor, const FString& ActorLabel); + + static void DoPostEditChangeProperty(UObject* Obj, FName PropertyName); + + static void DoPostEditChangeProperty(UObject* Obj, FProperty* Property); + + static void PropagateObjectDeltaChangeToArchetypeInstance(UObject* InObject, const FTransactionObjectDeltaChange& DeltaChange); + + template + static TSet PropagateDefaultValueChange(USceneComponent* InSceneComponentTemplate, const FName& PropertyName, const T& OldValue, const T& NewValue) + { + TSet UpdatedInstances; + FComponentEditorUtils::PropagateDefaultValueChange(InSceneComponentTemplate, FindFieldChecked(InSceneComponentTemplate->GetClass(), PropertyName), OldValue, NewValue, UpdatedInstances); + return UpdatedInstances; + } + + // Perform this operation on the given archetype as well as each archetype instance. If the given + // object in not an archetype instance, then don't do anything. + static void ForAllArchetypeInstances(UObject* Archetype, TFunctionRef Operation); + +#endif + + /** + // * Set the value on an UObject using reflection. + // * @param Object The object to copy the value into. + // * @param PropertyName The name of the property to set. + // * @param Value The value to assign to the property. + // * + // * @return true if the value was set correctly + // */ + //template + //static bool SetPropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) + //{ + // // Get the property addresses for the source and destination objects. + // FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + + // // Get the property addresses for the object + // ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); + + // if ( SourceAddr == NULL ) + // { + // return false; + // } + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + // { + // FEditPropertyChain PropertyChain; + // PropertyChain.AddHead(Property); + // Object->PreEditChange(PropertyChain); + // } + + // // Set the value on the destination object. + // *SourceAddr = Value; + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + // { + // FPropertyChangedEvent PropertyEvent(Property); + // Object->PostEditChangeProperty(PropertyEvent); + // } + + // return true; + //} +#if WITH_EDITOR + template + static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, ValueType Value) + { + // Get the property addresses for the source and destination objects. + FProperty* Property = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + + // Get the property addresses for the object + ValueType* SourceAddr = Property->ContainerPtrToValuePtr(Object); + + if ( SourceAddr == NULL ) + { + return false; + } + + if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FEditPropertyChain PropertyChain; + PropertyChain.AddHead(Property); + ((UObject*)Object)->PreEditChange(PropertyChain); + } + + // Set the value on the destination object. + if (*SourceAddr != Value) + { + TSet UpdatedInstances; + *SourceAddr = Value; + PropagateDefaultValueChange(Object, Property, *SourceAddr, Value, UpdatedInstances); + } + + if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FPropertyChangedEvent PropertyEvent(Property); + Object->PostEditChangeProperty(PropertyEvent); + } + + return true; + } +#endif + +#if WITH_EDITOR + // Bool specialization + template + static bool SetTemplatePropertyValue(ObjectType* Object, FName PropertyName, bool NewBool) + { + // Get the property addresses for the source and destination objects. + FBoolProperty* BoolProperty = FindFieldChecked(ObjectType::StaticClass(), PropertyName); + check(BoolProperty); + + // Get the property addresses for the object + const int32 PropertyOffset = INDEX_NONE; + void* CurrentValue = PropertyOffset == INDEX_NONE ? BoolProperty->ContainerPtrToValuePtr(Object) : ((uint8*)Object + PropertyOffset); + check(CurrentValue); + + const bool CurrentBool = BoolProperty->GetPropertyValue(CurrentValue); + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FEditPropertyChain PropertyChain; + PropertyChain.AddHead(BoolProperty); + ((UObject*)Object)->PreEditChange(PropertyChain); + } + + // Set the value on the destination object. + if (CurrentBool != NewBool) + { + TSet UpdatedInstances; + BoolProperty->SetPropertyValue(CurrentValue, NewBool); + FComponentEditorUtils::PropagateDefaultValueChange(Object, BoolProperty, CurrentBool, NewBool, UpdatedInstances); + } + + // if ( !Object->HasAnyFlags(RF_ClassDefaultObject) ) + { + FPropertyChangedEvent PropertyEvent(BoolProperty); + Object->PostEditChangeProperty(PropertyEvent); + } + + return true; + } +#endif + + protected: + // taken from FPaths::GetTCharPtr + FORCEINLINE static const TCHAR* GetTCharPtr(const TCHAR* Ptr) + { + return Ptr; + } + FORCEINLINE static const TCHAR* GetTCharPtr(const FString& Str) + { + return *Str; + } }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp index 1da5f8fb6..143b3a94d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp @@ -1,1529 +1,1529 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGenericAttribute.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAssetComponent.h" - -#include "Engine/StaticMesh.h" -#include "Components/ActorComponent.h" -#include "Components/PrimitiveComponent.h" -#include "Components/StaticMeshComponent.h" -#include "Landscape.h" - -#include "PhysicsEngine/BodySetup.h" -#include "EditorFramework/AssetImportData.h" -#include "AI/Navigation/NavCollisionBase.h" - -double -FHoudiniGenericAttribute::GetDoubleValue(int32 index) const -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return DoubleValues[index]; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return (double)IntValues[index]; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return FCString::Atod(*StringValues[index]); - } - - return 0.0f; -} - -void -FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 index) const -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetDoubleValue(index * AttributeTupleSize + n); -} - -int64 -FHoudiniGenericAttribute::GetIntValue(int32 index) const -{ - if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return IntValues[index]; - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return (int64)DoubleValues[index]; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return FCString::Atoi64(*StringValues[index]); - } - - return 0; -} - -void -FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) const -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetIntValue(index * AttributeTupleSize + n); -} - -FString -FHoudiniGenericAttribute::GetStringValue(int32 index) const -{ - if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return StringValues[index]; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return FString::FromInt((int32)IntValues[index]); - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return FString::SanitizeFloat(DoubleValues[index]); - } - - return FString(); -} - -void -FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 index) const -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetStringValue(index * AttributeTupleSize + n); -} - -bool -FHoudiniGenericAttribute::GetBoolValue(int32 index) const -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.IsValidIndex(index)) - return DoubleValues[index] == 0.0 ? false : true; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.IsValidIndex(index)) - return IntValues[index] == 0 ? false : true; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.IsValidIndex(index)) - return StringValues[index].Equals(TEXT("true"), ESearchCase::IgnoreCase) ? true : false; - } - - return false; -} - -void -FHoudiniGenericAttribute::GetBoolTuple(TArray& TupleValues, int32 index) const -{ - TupleValues.SetNumZeroed(AttributeTupleSize); - - for (int32 n = 0; n < AttributeTupleSize; n++) - TupleValues[n] = GetBoolValue(index * AttributeTupleSize + n); -} - -void* -FHoudiniGenericAttribute::GetData() -{ - if (AttributeType == EAttribStorageType::STRING) - { - if (StringValues.Num() > 0) - return StringValues.GetData(); - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (IntValues.Num() > 0) - return IntValues.GetData(); - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (DoubleValues.Num() > 0) - return DoubleValues.GetData(); - } - - return nullptr; -} - -void -FHoudiniGenericAttribute::SetDoubleValue(double InValue, int32 index) -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (!DoubleValues.IsValidIndex(index)) - DoubleValues.SetNum(index + 1); - DoubleValues[index] = InValue; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (!IntValues.IsValidIndex(index)) - IntValues.SetNum(index + 1); - IntValues[index] = InValue; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (!StringValues.IsValidIndex(index)) - StringValues.SetNum(index + 1); - StringValues[index] = FString::SanitizeFloat(InValue); - } -} - -void -FHoudiniGenericAttribute::SetDoubleTuple(const TArray& InTupleValues, int32 index) -{ - if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) - return; - - for (int32 n = 0; n < AttributeTupleSize; n++) - SetDoubleValue(InTupleValues[n], index * AttributeTupleSize + n); -} - -void -FHoudiniGenericAttribute::SetIntValue(int64 InValue, int32 index) -{ - if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (!IntValues.IsValidIndex(index)) - IntValues.SetNum(index + 1); - IntValues[index] = InValue; - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (!DoubleValues.IsValidIndex(index)) - DoubleValues.SetNum(index + 1); - DoubleValues[index] = InValue; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (!StringValues.IsValidIndex(index)) - StringValues.SetNum(index + 1); - StringValues[index] = FString::Printf(TEXT("%lld"), InValue); - } -} - -void -FHoudiniGenericAttribute::SetIntTuple(const TArray& InTupleValues, int32 index) -{ - if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) - return; - - for (int32 n = 0; n < AttributeTupleSize; n++) - SetIntValue(InTupleValues[n], index * AttributeTupleSize + n); -} - -void -FHoudiniGenericAttribute::SetStringValue(const FString& InValue, int32 index) -{ - if (AttributeType == EAttribStorageType::STRING) - { - if (!StringValues.IsValidIndex(index)) - StringValues.SetNum(index + 1); - StringValues[index] = InValue; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (!IntValues.IsValidIndex(index)) - IntValues.SetNum(index + 1); - IntValues[index] = FCString::Atoi64(*InValue); - } - else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (!DoubleValues.IsValidIndex(index)) - DoubleValues.SetNum(index + 1); - DoubleValues[index] = FCString::Atod(*InValue); - } -} - -void -FHoudiniGenericAttribute::SetStringTuple(const TArray& InTupleValues, int32 index) -{ - if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) - return; - - for (int32 n = 0; n < AttributeTupleSize; n++) - SetStringValue(InTupleValues[n], index * AttributeTupleSize + n); -} - -void -FHoudiniGenericAttribute::SetBoolValue(bool InValue, int32 index) -{ - if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) - { - if (!DoubleValues.IsValidIndex(index)) - DoubleValues.SetNum(index + 1); - DoubleValues[index] = InValue ? 1.0 : 0.0; - } - else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) - { - if (!IntValues.IsValidIndex(index)) - IntValues.SetNum(index + 1); - IntValues[index] = InValue ? 1 : 0; - } - else if (AttributeType == EAttribStorageType::STRING) - { - if (!StringValues.IsValidIndex(index)) - StringValues.SetNum(index + 1); - StringValues[index] = InValue ? "true" : "false"; - } -} - -void -FHoudiniGenericAttribute::SetBoolTuple(const TArray& InTupleValues, int32 index) -{ - if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) - return; - - for (int32 n = 0; n < AttributeTupleSize; n++) - SetBoolValue(InTupleValues[n], index * AttributeTupleSize + n); -} - -bool -FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( - UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex) -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Get the Property name - const FString& PropertyName = InPropertyAttribute.AttributeName; - if (PropertyName.IsEmpty()) - return false; - - // Some Properties need to be handle and modified manually... - if (PropertyName.Equals("CollisionProfileName", ESearchCase::IgnoreCase)) - { - UPrimitiveComponent* PC = Cast(InObject); - if (IsValid(PC)) - { - FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); - FName Value = FName(*StringValue); - PC->SetCollisionProfileName(Value); - - // Patch the StaticMeshGenerationProperties on the HAC - UHoudiniAssetComponent* HAC = Cast(InObject); - if (IsValid(HAC)) - { - HAC->StaticMeshGenerationProperties.DefaultBodyInstance.SetCollisionProfileName(Value); - } - - return true; - } - return false; - } - - if (PropertyName.Equals("CollisionEnabled", ESearchCase::IgnoreCase)) - { - UPrimitiveComponent* PC = Cast(InObject); - if (PC && !PC->IsPendingKill()) - { - FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); - if (StringValue.Equals("NoCollision", ESearchCase::IgnoreCase)) - { - PC->SetCollisionEnabled(ECollisionEnabled::NoCollision); - return true; - } - else if (StringValue.Equals("QueryOnly", ESearchCase::IgnoreCase)) - { - PC->SetCollisionEnabled(ECollisionEnabled::QueryOnly); - return true; - } - else if (StringValue.Equals("PhysicsOnly", ESearchCase::IgnoreCase)) - { - PC->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); - return true; - } - else if (StringValue.Equals("QueryAndPhysics", ESearchCase::IgnoreCase)) - { - PC->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); - return true; - } - return false; - } - } - - // Specialize CastShadow to avoid paying the cost of finding property + calling Property change twice - if (PropertyName.Equals("CastShadow", ESearchCase::IgnoreCase)) - { - UPrimitiveComponent* Component = Cast< UPrimitiveComponent >(InObject); - if (Component && !Component->IsPendingKill()) - { - bool Value = InPropertyAttribute.GetBoolValue(AtIndex); - Component->SetCastShadow(Value); - return true; - } - return false; - } - - // Handle Component Tags manually here - if (PropertyName.Contains("Tags")) - { - UActorComponent* AC = Cast< UActorComponent >(InObject); - if (AC && !AC->IsPendingKill()) - { - FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); - if (!AC->ComponentTags.Contains(NameAttr)) - AC->ComponentTags.Add(NameAttr); - /* - for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++) - { - FName NameAttr = FName(*InPropertyAttribute.GetStringValue(nIdx)); - if (!AC->ComponentTags.Contains(NameAttr)) - AC->ComponentTags.Add(NameAttr); - } - */ - return true; - } - return false; - } -#if WITH_EDITOR - // Handle landscape edit layers toggling - if (PropertyName.Equals("EnableEditLayers", ESearchCase::IgnoreCase) - || PropertyName.Equals("bCanHaveLayersContent", ESearchCase::IgnoreCase)) - { - ALandscape* Landscape = Cast(InObject); - if (IsValid(Landscape)) - { - if(InPropertyAttribute.GetBoolValue(AtIndex) != Landscape->CanHaveLayersContent()) - Landscape->ToggleCanHaveLayersContent(); - - return true; - } - - return false; - } -#endif - - // Try to find the corresponding UProperty - void* OutContainer = nullptr; - FProperty* FoundProperty = nullptr; - UObject* FoundPropertyObject = nullptr; - -#if WITH_EDITOR - // Try to match to source model properties when possible - if (UStaticMesh* SM = Cast(InObject)) - { - if (SM && !SM->IsPendingKill() && SM->GetNumSourceModels() > AtIndex) - { - bool bFoundProperty = false; - TryToFindProperty(&SM->GetSourceModel(AtIndex), SM->GetSourceModel(AtIndex).StaticStruct(), PropertyName, FoundProperty, bFoundProperty, OutContainer); - if (bFoundProperty) - { - FoundPropertyObject = InObject; - } - } - } -#endif - - if (!FoundProperty && !FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) - return false; - - // Modify the Property we found - if (!ModifyPropertyValueOnObject(FoundPropertyObject, InPropertyAttribute, FoundProperty, OutContainer, AtIndex)) - return false; - - return true; -} - - -bool -FHoudiniGenericAttribute::FindPropertyOnObject( - UObject* InObject, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - UObject*& OutFoundPropertyObject, - void*& OutContainer) -{ -#if WITH_EDITOR - if (!InObject || InObject->IsPendingKill()) - return false; - - if (InPropertyName.IsEmpty()) - return false; - - UClass* ObjectClass = InObject->GetClass(); - if (!ObjectClass || ObjectClass->IsPendingKill()) - return false; - - // Set the result pointer to null - OutContainer = nullptr; - OutFoundProperty = nullptr; - OutFoundPropertyObject = InObject; - - bool bPropertyHasBeenFound = false; - FHoudiniGenericAttribute::TryToFindProperty( - InObject, - ObjectClass, - InPropertyName, - OutFoundProperty, - bPropertyHasBeenFound, - OutContainer); - - /* - // TODO: Parsing needs to be made recursively! - // Iterate manually on the properties, in order to handle StructProperties correctly - for (TFieldIterator PropIt(ObjectClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) - { - FProperty* CurrentProperty = *PropIt; - if (!CurrentProperty) - continue; - - FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = CurrentProperty->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - OutFoundProperty = CurrentProperty; - - // If it's an equality, we dont need to keep searching - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bPropertyHasBeenFound = true; - break; - } - } - - // StructProperty need to be a nested struct - //if (UStructProperty* StructProperty = Cast< UStructProperty >(CurrentProperty)) - // bPropertyHasBeenFound = TryToFindInStructProperty(InObject, InPropertyName, StructProperty, OutFoundProperty, OutStructContainer); - //else if (UArrayProperty* ArrayProperty = Cast< UArrayProperty >(CurrentProperty)) - // bPropertyHasBeenFound = TryToFindInArrayProperty(InObject, InPropertyName, ArrayProperty, OutFoundProperty, OutStructContainer); - - // Handle StructProperty - FStructProperty* StructProperty = CastField(CurrentProperty); - if (StructProperty) - { - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) - continue; - - for (TFieldIterator It(Struct); It; ++It) - { - FProperty* Property = *It; - if (!Property) - continue; - - DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - Name = Property->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - OutFoundProperty = Property; - OutStructContainer = StructProperty->ContainerPtrToValuePtr< void >(InObject, 0); - - // If it's an equality, we dont need to keep searching - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bPropertyHasBeenFound = true; - break; - } - } - } - } - - if (bPropertyHasBeenFound) - break; - } - - if (bPropertyHasBeenFound) - return true; - */ - - // Try with FindField?? - if (!OutFoundProperty) - OutFoundProperty = FindFProperty(ObjectClass, *InPropertyName); - - // Try with FindPropertyByName ?? - if (!OutFoundProperty) - OutFoundProperty = ObjectClass->FindPropertyByName(*InPropertyName); - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - - // Handle common properties nested in classes - // Static Meshes - UStaticMesh* SM = Cast(InObject); - if (SM && !SM->IsPendingKill()) - { - if (SM->BodySetup && FindPropertyOnObject( - SM->BodySetup, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - - if (SM->AssetImportData && FindPropertyOnObject( - SM->AssetImportData, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - - if (SM->NavCollision && FindPropertyOnObject( - SM->NavCollision, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - } - - // For Actors, parse their components - AActor* Actor = Cast(InObject); - if (Actor && !Actor->IsPendingKill()) - { - TArray AllComponents; - Actor->GetComponents(AllComponents, true); - - int32 CompIdx = 0; - for (USceneComponent * SceneComponent : AllComponents) - { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; - - if (FindPropertyOnObject( - SceneComponent, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) - { - return true; - } - } - } - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - -#endif - return false; -} - - -bool -FHoudiniGenericAttribute::TryToFindProperty( - void* InContainer, - UStruct* InStruct, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - bool& bOutPropertyHasBeenFound, - void*& OutContainer) -{ -#if WITH_EDITOR - if (!InContainer) - return false; - - if (!InStruct || InStruct->IsPendingKill()) - return false; - - if (InPropertyName.IsEmpty()) - return false; - - // Iterate manually on the properties, in order to handle StructProperties correctly - for (TFieldIterator PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) - { - FProperty* CurrentProperty = *PropIt; - if (!CurrentProperty) - continue; - - FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = CurrentProperty->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) - { - OutFoundProperty = CurrentProperty; - OutContainer = InContainer; - - // If it's an equality, we dont need to keep searching anymore - if ((Name == InPropertyName) || (DisplayName == InPropertyName)) - { - bOutPropertyHasBeenFound = true; - break; - } - } - - // Do a recursive parsing for StructProperties - FStructProperty* StructProperty = CastField(CurrentProperty); - if (StructProperty) - { - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) - continue; - - TryToFindProperty( - StructProperty->ContainerPtrToValuePtr(InContainer, 0), - Struct, - InPropertyName, - OutFoundProperty, - bOutPropertyHasBeenFound, - OutContainer); - } - - if (bOutPropertyHasBeenFound) - break; - } - - if (bOutPropertyHasBeenFound) - return true; - - // We found the Property we were looking for - if (OutFoundProperty) - return true; - -#endif - return false; -} - - -bool -FHoudiniGenericAttribute::ModifyPropertyValueOnObject( - UObject* InObject, - FHoudiniGenericAttribute InGenericAttribute, - FProperty* FoundProperty, - void* InContainer, - const int32& InAtIndex) -{ - if (!InObject || InObject->IsPendingKill() || !FoundProperty) - return false; - - // Determine the container to use (either InContainer if specified, or InObject) - void* Container = InContainer ? InContainer : InObject; - - // Property class name, used for logging - const FString PropertyClassName = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); - - // Initialize using the found property - FProperty* InnerProperty = FoundProperty; - - AActor* InOwner = Cast(InObject->GetOuter()); - bool bHasModifiedProperty = false; - - - auto OnPropertyChanged = [InObject, InOwner, &bHasModifiedProperty](FProperty* InProperty) - { -#if WITH_EDITOR - FPropertyChangedEvent Evt(InProperty); - InObject->PostEditChangeProperty(Evt); - if (InOwner) - { - // If we are setting properties on an Actor component, we want to notify the - // actor of the changes too since the property change might be handled in the actor's - // PostEditChange callbacks (one such an example occurs when changing the material for a decal actor). - InOwner->PostEditChangeProperty(Evt); - } -#endif - bHasModifiedProperty = true; - }; - - FArrayProperty* ArrayProperty = CastField(FoundProperty); - TSharedPtr ArrayHelper; - if (ArrayProperty) - { - InnerProperty = ArrayProperty->Inner; - ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); - } - - // TODO: implement support for array attributes received from Houdini - - // Get the "proper" AtIndex in the flat array by using the attribute tuple size - // TODO: fix the issue when changing array of tuple properties! - const int32 TupleSize = InGenericAttribute.AttributeTupleSize; - int32 AtIndex = InAtIndex * TupleSize; - FFieldClass* PropertyClass = InnerProperty->GetClass(); - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || - PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - // Supported non-struct properties - - // If the attribute from Houdini has a tuple size > 1, we support setting it on arrays on the unreal side - // For example: a 3 float from Houdini can be set as a TArray in Unreal. - - // If this is an ArrayProperty, ensure that it is at least large enough for our tuple - // TODO: should we just set this to exactly our tuple size? - if (ArrayHelper.IsValid()) - ArrayHelper->ExpandForIndex(TupleSize - 1); - - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - void* ValuePtr = nullptr; - if (ArrayHelper.IsValid()) - { - ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); - } - else - { - // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim - // on the property to determine if our TupleIndex is in range, if not, give up, we cannot set any more - // of our tuple indices on this property. - if (TupleIndex >= InnerProperty->ArrayDim) - break; - - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - } - - if (ValuePtr) - { - // Handle each property type that we support - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) - { - // Numeric properties are supported as floats and ints, and can also be set from a received string - FNumericProperty* const Property = CastField(InnerProperty); - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - Property->SetNumericPropertyValueFromString(ValuePtr, *InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); - } - else if (Property->IsFloatingPoint()) - { - Property->SetFloatingPointPropertyValue(ValuePtr, InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex)); - } - else if (Property->IsInteger()) - { - Property->SetIntPropertyValue(ValuePtr, InGenericAttribute.GetIntValue(AtIndex + TupleIndex)); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) - { - FBoolProperty* const Property = CastField(InnerProperty); - Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetBoolValue(AtIndex + TupleIndex)); - } - else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) - { - FStrProperty* const Property = CastField(InnerProperty); - Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); - } - else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - FNameProperty* const Property = CastField(InnerProperty); - Property->SetPropertyValue(ValuePtr, FName(*InGenericAttribute.GetStringValue(AtIndex + TupleIndex))); - } - - OnPropertyChanged(InnerProperty); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); - if (TupleIndex == 0) - return false; - } - } - } - else if (FStructProperty* StructProperty = CastField(InnerProperty)) - { - // struct properties - - // If we receive an attribute with tuple size > 1 and the target is an Unreal struct property, then we set - // as many of the values as we can in the struct. For example: a 4-float received from Houdini where the - // target is an FVector, the FVector.X, Y and Z would be set from the 4-float indices 0-2. Index 3 from the - // 4-float would then be ignored. - - const int32 TupleIndex = 0; - // If this is an array property, ensure it has enough space - // TODO: should we just set the array size to 1 for non-arrays and to the array size for arrays (once we support array attributes from Houdini)? - // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) - if (ArrayHelper.IsValid()) - ArrayHelper->ExpandForIndex(TupleIndex); - - void* PropertyValue = nullptr; - if (ArrayHelper.IsValid()) - PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); - else - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - - if (PropertyValue) - { - const FName PropertyName = StructProperty->Struct->GetFName(); - if (PropertyName == NAME_Vector) - { - // Found a vector property, fill it with up to 3 tuple values - FVector& Vector = *static_cast(PropertyValue); - Vector = FVector::ZeroVector; - Vector.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); - if (InGenericAttribute.AttributeTupleSize > 1) - Vector.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - Vector.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); - - OnPropertyChanged(StructProperty); - } - else if (PropertyName == NAME_Transform) - { - // Found a transform property fill it with the attribute tuple values - FVector Translation; - Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); - if (InGenericAttribute.AttributeTupleSize > 1) - Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); - - FQuat Rotation; - if (InGenericAttribute.AttributeTupleSize > 3) - Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); - if (InGenericAttribute.AttributeTupleSize > 4) - Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 4); - if (InGenericAttribute.AttributeTupleSize > 5) - Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 5); - if (InGenericAttribute.AttributeTupleSize > 6) - Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 6); - - FVector Scale(1, 1, 1); - if (InGenericAttribute.AttributeTupleSize > 7) - Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 7); - if (InGenericAttribute.AttributeTupleSize > 8) - Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 8); - if (InGenericAttribute.AttributeTupleSize > 9) - Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 9); - - FTransform& Transform = *static_cast(PropertyValue); - Transform = FTransform::Identity; - Transform.SetTranslation(Translation); - Transform.SetRotation(Rotation); - Transform.SetScale3D(Scale); - - OnPropertyChanged(StructProperty); - } - else if (PropertyName == NAME_Color) - { - FColor& Color = *static_cast(PropertyValue); - Color = FColor::Black; - Color.R = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - Color.G = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - Color.B = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 2); - if (InGenericAttribute.AttributeTupleSize > 3) - Color.A = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 3); - - OnPropertyChanged(StructProperty); - } - else if (PropertyName == NAME_LinearColor) - { - FLinearColor& LinearColor = *static_cast(PropertyValue); - LinearColor = FLinearColor::Black; - LinearColor.R = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - LinearColor.G = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - LinearColor.B = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); - if (InGenericAttribute.AttributeTupleSize > 3) - LinearColor.A = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); - - OnPropertyChanged(StructProperty); - } - else if (PropertyName == "Int32Interval") - { - FInt32Interval& Interval = *static_cast(PropertyValue); - Interval = FInt32Interval(); - Interval.Min = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - Interval.Max = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); - - OnPropertyChanged(StructProperty); - } - else if (PropertyName == "FloatInterval") - { - FFloatInterval& Interval = *static_cast(PropertyValue); - Interval = FFloatInterval(); - Interval.Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - Interval.Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); - - OnPropertyChanged(StructProperty); - } - else - { - HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); - return false; - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) - { - // OBJECT PATH PROPERTY - const int32 TupleIndex = 0; - // If this is an array property, ensure it has enough space - // TODO: should we just set the array size to 1 for non-arrays or to the array size for arrays (once we support array attributes from Houdini)? - // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) - if (ArrayHelper.IsValid()) - ArrayHelper->ExpandForIndex(TupleIndex); - - FString Value = InGenericAttribute.GetStringValue(AtIndex + TupleIndex); - void* ValuePtr = nullptr; - if (ArrayHelper.IsValid()) - ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); - else - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - - if (ValuePtr) - { - TSoftObjectPtr ValueObjectPtr; - ValueObjectPtr = Value; - UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); - - // Ensure the ObjectProperty class matches the ValueObject that we just loaded - if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) - { - ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); - OnPropertyChanged(ObjectProperty); - } - else - { - HOUDINI_LOG_WARNING( - TEXT("Could net set object property %s: ObjectProperty's object class (%s) does not match referenced object class (%s)!"), - *InGenericAttribute.AttributeName, *(ObjectProperty->PropertyClass->GetName()), IsValid(ValueObject) ? *(ValueObject->GetClass()->GetName()) : TEXT("NULL")); - return false; - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else - { - // Property was found, but is of an unsupported type - HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); - return false; - } - - if (bHasModifiedProperty) - { -#if WITH_EDITOR - InObject->PostEditChange(); - if (InOwner) - { - InOwner->PostEditChange(); - } -#endif - } - - return true; -} - -bool -FHoudiniGenericAttribute::GetPropertyValueFromObject( - UObject* InObject, - FProperty* InFoundProperty, - void* InContainer, - FHoudiniGenericAttribute& InGenericAttribute, - const int32& InAtIndex) -{ - if (!InObject || InObject->IsPendingKill() || !InFoundProperty) - return false; - - // Determine the container to use (either InContainer if specified, or InObject) - void* Container = InContainer ? InContainer : InObject; - - // Property class name, used for logging - const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); - - // Initialize using the found property - FProperty* InnerProperty = InFoundProperty; - - FArrayProperty* ArrayProperty = CastField(InFoundProperty); - TSharedPtr ArrayHelper; - if (ArrayProperty) - { - InnerProperty = ArrayProperty->Inner; - ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); - } - - // TODO: implement support for array attributes received from Houdini - - // Get the "proper" AtIndex in the flat array by using the attribute tuple size - // TODO: fix the issue when changing array of tuple properties! - const int32 TupleSize = InGenericAttribute.AttributeTupleSize; - int32 AtIndex = InAtIndex * TupleSize; - FFieldClass* PropertyClass = InnerProperty->GetClass(); - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || - PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - // Supported non-struct properties - - // If the attribute from Houdini has a tuple size > 1, we support getting it on arrays on the unreal side - // For example: a 3 float in Houdini can be set from a TArray in Unreal. - - for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) - { - void* ValuePtr = nullptr; - if (ArrayHelper.IsValid()) - { - // Check that we are not out of range - if (TupleIndex >= ArrayHelper->Num()) - break; - - ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); - } - else - { - // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim - // on the property to determine if our TupleIndex is in range, if not, give up, we cannot get any more - // of our tuple indices from this property. - if (TupleIndex >= InnerProperty->ArrayDim) - break; - - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - } - - if (ValuePtr) - { - // Handle each property type that we support - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) - { - // Numeric properties are supported as floats and ints, and can also be set from a received string - FNumericProperty* const Property = CastField(InnerProperty); - if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) - { - InGenericAttribute.SetStringValue(Property->GetNumericPropertyValueToString(ValuePtr), AtIndex + TupleIndex); - } - else if (Property->IsFloatingPoint()) - { - InGenericAttribute.SetDoubleValue(Property->GetFloatingPointPropertyValue(ValuePtr), AtIndex + TupleIndex); - } - else if (Property->IsInteger()) - { - InGenericAttribute.SetIntValue(Property->GetSignedIntPropertyValue(ValuePtr), AtIndex + TupleIndex); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) - { - FBoolProperty* const Property = CastField(InnerProperty); - InGenericAttribute.SetBoolValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); - } - else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) - { - FStrProperty* const Property = CastField(InnerProperty); - InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); - } - else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - FNameProperty* const Property = CastField(InnerProperty); - InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr).ToString(), AtIndex + TupleIndex); - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); - if (TupleIndex == 0) - return false; - } - } - } - else if (FStructProperty* StructProperty = CastField(InnerProperty)) - { - // struct properties - - // Set as many as the tuple values as we can from the Unreal struct. - - const int32 TupleIndex = 0; - - void* PropertyValue = nullptr; - if (ArrayHelper.IsValid()) - { - if (ArrayHelper->IsValidIndex(TupleIndex)) - PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); - } - else if (TupleIndex < InnerProperty->ArrayDim) - { - PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - } - - if (PropertyValue) - { - const FName PropertyName = StructProperty->Struct->GetFName(); - if (PropertyName == NAME_Vector) - { - // Found a vector property, fill it with up to 3 tuple values - const FVector& Vector = *static_cast(PropertyValue); - InGenericAttribute.SetDoubleValue(Vector.X, AtIndex + TupleIndex + 0); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetDoubleValue(Vector.Y, AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - InGenericAttribute.SetDoubleValue(Vector.Z, AtIndex + TupleIndex + 2); - } - else if (PropertyName == NAME_Transform) - { - // Found a transform property fill it with the attribute tuple values - const FTransform& Transform = *static_cast(PropertyValue); - const FVector Translation = Transform.GetTranslation(); - const FQuat Rotation = Transform.GetRotation(); - const FVector Scale = Transform.GetScale3D(); - - InGenericAttribute.SetDoubleValue(Translation.X, AtIndex + TupleIndex + 0); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetDoubleValue(Translation.Y, AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - InGenericAttribute.SetDoubleValue(Translation.Z, AtIndex + TupleIndex + 2); - - if (InGenericAttribute.AttributeTupleSize > 3) - InGenericAttribute.SetDoubleValue(Rotation.W, AtIndex + TupleIndex + 3); - if (InGenericAttribute.AttributeTupleSize > 4) - InGenericAttribute.SetDoubleValue(Rotation.X, AtIndex + TupleIndex + 4); - if (InGenericAttribute.AttributeTupleSize > 5) - InGenericAttribute.SetDoubleValue(Rotation.Y, AtIndex + TupleIndex + 5); - if (InGenericAttribute.AttributeTupleSize > 6) - InGenericAttribute.SetDoubleValue(Rotation.Z, AtIndex + TupleIndex + 6); - - if (InGenericAttribute.AttributeTupleSize > 7) - InGenericAttribute.SetDoubleValue(Scale.X, AtIndex + TupleIndex + 7); - if (InGenericAttribute.AttributeTupleSize > 8) - InGenericAttribute.SetDoubleValue(Scale.Y, AtIndex + TupleIndex + 8); - if (InGenericAttribute.AttributeTupleSize > 9) - InGenericAttribute.SetDoubleValue(Scale.Z, AtIndex + TupleIndex + 9); - } - else if (PropertyName == NAME_Color) - { - const FColor& Color = *static_cast(PropertyValue); - InGenericAttribute.SetIntValue(Color.R, AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetIntValue(Color.G, AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - InGenericAttribute.SetIntValue(Color.B, AtIndex + TupleIndex + 2); - if (InGenericAttribute.AttributeTupleSize > 3) - InGenericAttribute.SetIntValue(Color.A, AtIndex + TupleIndex + 3); - } - else if (PropertyName == NAME_LinearColor) - { - const FLinearColor& LinearColor = *static_cast(PropertyValue); - InGenericAttribute.SetDoubleValue(LinearColor.R, AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetDoubleValue(LinearColor.G, AtIndex + TupleIndex + 1); - if (InGenericAttribute.AttributeTupleSize > 2) - InGenericAttribute.SetDoubleValue(LinearColor.B, AtIndex + TupleIndex + 2); - if (InGenericAttribute.AttributeTupleSize > 3) - InGenericAttribute.SetDoubleValue(LinearColor.A, AtIndex + TupleIndex + 3); - } - else if (PropertyName == "Int32Interval") - { - const FInt32Interval& Interval = *static_cast(PropertyValue); - InGenericAttribute.SetIntValue(Interval.Min, AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetIntValue(Interval.Max, AtIndex + TupleIndex + 1); - } - else if (PropertyName == "FloatInterval") - { - const FFloatInterval& Interval = *static_cast(PropertyValue); - InGenericAttribute.SetDoubleValue(Interval.Min, AtIndex + TupleIndex); - if (InGenericAttribute.AttributeTupleSize > 1) - InGenericAttribute.SetDoubleValue(Interval.Max, AtIndex + TupleIndex + 1); - } - else - { - HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); - return false; - } - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) - { - // OBJECT PATH PROPERTY - const int32 TupleIndex = 0; - - void* ValuePtr = nullptr; - if (ArrayHelper.IsValid()) - { - if (ArrayHelper->IsValidIndex(TupleIndex)) - ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); - } - else if (TupleIndex < InnerProperty->ArrayDim) - { - ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - } - - if (ValuePtr) - { - UObject* ValueObject = ObjectProperty->GetObjectPropertyValue(ValuePtr); - const TSoftObjectPtr ValueObjectPtr = ValueObject; - InGenericAttribute.SetStringValue(ValueObjectPtr.ToString(), AtIndex + TupleIndex); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); - return false; - } - } - else - { - // Property was found, but is of an unsupported type - HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); - return false; - } - - return true; -} - -bool -FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( - UObject* InObject, - FProperty* InFoundProperty, - void* InContainer, - int32& OutAttributeTupleSize, - EAttribStorageType& OutAttributeStorageType) -{ - if (!InObject || InObject->IsPendingKill() || !InFoundProperty) - return false; - - // Determine the container to use (either InContainer if specified, or InObject) - void* Container = InContainer ? InContainer : InObject; - - // Property class name, used for logging - const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); - - // Initialize using the found property - FProperty* InnerProperty = InFoundProperty; - - // FArrayProperty* ArrayProperty = CastField(InFoundProperty); - // TSharedPtr ArrayHelper; - // if (ArrayProperty) - // { - // InnerProperty = ArrayProperty->Inner; - // ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); - // } - - FFieldClass* PropertyClass = InnerProperty->GetClass(); - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || - PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - // Supported non-struct properties - - // Here we cannot really do better than tuple size of 1 (since we need to support arrays in the future, we - // cannot just assume array size == tuple size going to Houdini) - OutAttributeTupleSize = 1; - - // Handle each property type that we support - if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) - { - // Numeric properties are supported as floats and ints, and can also be set from a received string - FNumericProperty* const Property = CastField(InnerProperty); - if (Property->IsFloatingPoint()) - { - OutAttributeStorageType = EAttribStorageType::FLOAT; - } - else if (Property->IsInteger()) - { - OutAttributeStorageType = EAttribStorageType::INT; - } - else - { - HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *Property->GetName(), *PropertyClassName); - return false; - } - } - else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) - { - OutAttributeStorageType = EAttribStorageType::INT; - } - else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) - { - OutAttributeStorageType = EAttribStorageType::STRING; - } - else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) - { - OutAttributeStorageType = EAttribStorageType::STRING; - } - } - else if (FStructProperty* StructProperty = CastField(InnerProperty)) - { - // struct properties - - const FName PropertyName = StructProperty->Struct->GetFName(); - if (PropertyName == NAME_Vector) - { - OutAttributeTupleSize = 3; - OutAttributeStorageType = EAttribStorageType::FLOAT; - } - else if (PropertyName == NAME_Transform) - { - OutAttributeTupleSize = 10; - OutAttributeStorageType = EAttribStorageType::FLOAT; - } - else if (PropertyName == NAME_Color) - { - OutAttributeTupleSize = 4; - OutAttributeStorageType = EAttribStorageType::INT; - } - else if (PropertyName == NAME_LinearColor) - { - OutAttributeTupleSize = 4; - OutAttributeStorageType = EAttribStorageType::FLOAT; - } - else if (PropertyName == "Int32Interval") - { - OutAttributeTupleSize = 2; - OutAttributeStorageType = EAttribStorageType::INT; - } - else if (PropertyName == "FloatInterval") - { - OutAttributeTupleSize = 2; - OutAttributeStorageType = EAttribStorageType::FLOAT; - } - else - { - HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InFoundProperty->GetName(), *PropertyClassName, *PropertyName.ToString()); - return false; - } - } - else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) - { - OutAttributeTupleSize = 1; - OutAttributeStorageType = EAttribStorageType::STRING; - } - else - { - // Property was found, but is of an unsupported type - HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InFoundProperty->GetName()); - return false; - } - - return true; -} - -/* -bool -FHoudiniEngineUtils::TryToFindInStructProperty( - UObject* Object, FString UPropertyNameToFind, UStructProperty* StructProperty, UProperty*& FoundProperty, void*& StructContainer ) -{ - if ( !StructProperty || !Object ) - return false; - - // Walk the structs' properties and try to find the one we're looking for - UScriptStruct* Struct = StructProperty->Struct; - for (TFieldIterator< UProperty > It(Struct); It; ++It) - { - UProperty* Property = *It; - if ( !Property ) - continue; - - FString DisplayName = It->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = It->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - FoundProperty = Property; - StructContainer = StructProperty->ContainerPtrToValuePtr< void >( Object, 0); - - // If it's an equality, we dont need to keep searching - if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) - return true; - } - - if ( FoundProperty ) - continue; - - UStructProperty* NestedStruct = Cast( Property ); - if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) - return true; - - UArrayProperty* ArrayProp = Cast( Property ); - if ( ArrayProp && TryToFindInArrayProperty( Object, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) - return true; - } - - return false; -} - -bool -FHoudiniEngineUtils::TryToFindInArrayProperty( - UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ) -{ - if ( !ArrayProperty || !Object ) - return false; - - UProperty* Property = ArrayProperty->Inner; - if ( !Property ) - return false; - - FString DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); - FString Name = Property->GetName(); - - // If the property name contains the uprop attribute name, we have a candidate - if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) - { - // We found the property in the struct property, we need to keep the ValuePtr in the object - // of the structProp in order to be able to access the property value afterwards... - FoundProperty = Property; - StructContainer = ArrayProperty->ContainerPtrToValuePtr< void >( Object, 0); - - // If it's an equality, we dont need to keep searching - if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) - return true; - } - - if ( !FoundProperty ) - { - UStructProperty* NestedStruct = Cast( Property ); - if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) - return true; - - UArrayProperty* ArrayProp = Cast( Property ); - if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) - return true; - } - - return false; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGenericAttribute.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" + +#include "Engine/StaticMesh.h" +#include "Components/ActorComponent.h" +#include "Components/PrimitiveComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Landscape.h" + +#include "PhysicsEngine/BodySetup.h" +#include "EditorFramework/AssetImportData.h" +#include "AI/Navigation/NavCollisionBase.h" + +double +FHoudiniGenericAttribute::GetDoubleValue(int32 index) const +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return DoubleValues[index]; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return (double)IntValues[index]; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return FCString::Atod(*StringValues[index]); + } + + return 0.0f; +} + +void +FHoudiniGenericAttribute::GetDoubleTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetDoubleValue(index * AttributeTupleSize + n); +} + +int64 +FHoudiniGenericAttribute::GetIntValue(int32 index) const +{ + if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return IntValues[index]; + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return (int64)DoubleValues[index]; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return FCString::Atoi64(*StringValues[index]); + } + + return 0; +} + +void +FHoudiniGenericAttribute::GetIntTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetIntValue(index * AttributeTupleSize + n); +} + +FString +FHoudiniGenericAttribute::GetStringValue(int32 index) const +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return StringValues[index]; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return FString::FromInt((int32)IntValues[index]); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return FString::SanitizeFloat(DoubleValues[index]); + } + + return FString(); +} + +void +FHoudiniGenericAttribute::GetStringTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetStringValue(index * AttributeTupleSize + n); +} + +bool +FHoudiniGenericAttribute::GetBoolValue(int32 index) const +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.IsValidIndex(index)) + return DoubleValues[index] == 0.0 ? false : true; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.IsValidIndex(index)) + return IntValues[index] == 0 ? false : true; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.IsValidIndex(index)) + return StringValues[index].Equals(TEXT("true"), ESearchCase::IgnoreCase) ? true : false; + } + + return false; +} + +void +FHoudiniGenericAttribute::GetBoolTuple(TArray& TupleValues, int32 index) const +{ + TupleValues.SetNumZeroed(AttributeTupleSize); + + for (int32 n = 0; n < AttributeTupleSize; n++) + TupleValues[n] = GetBoolValue(index * AttributeTupleSize + n); +} + +void* +FHoudiniGenericAttribute::GetData() +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (StringValues.Num() > 0) + return StringValues.GetData(); + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (IntValues.Num() > 0) + return IntValues.GetData(); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (DoubleValues.Num() > 0) + return DoubleValues.GetData(); + } + + return nullptr; +} + +void +FHoudiniGenericAttribute::SetDoubleValue(double InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = FString::SanitizeFloat(InValue); + } +} + +void +FHoudiniGenericAttribute::SetDoubleTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetDoubleValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetIntValue(int64 InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = FString::Printf(TEXT("%lld"), InValue); + } +} + +void +FHoudiniGenericAttribute::SetIntTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetIntValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetStringValue(const FString& InValue, int32 index) +{ + if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = InValue; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = FCString::Atoi64(*InValue); + } + else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = FCString::Atod(*InValue); + } +} + +void +FHoudiniGenericAttribute::SetStringTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetStringValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +void +FHoudiniGenericAttribute::SetBoolValue(bool InValue, int32 index) +{ + if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64)) + { + if (!DoubleValues.IsValidIndex(index)) + DoubleValues.SetNum(index + 1); + DoubleValues[index] = InValue ? 1.0 : 0.0; + } + else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64)) + { + if (!IntValues.IsValidIndex(index)) + IntValues.SetNum(index + 1); + IntValues[index] = InValue ? 1 : 0; + } + else if (AttributeType == EAttribStorageType::STRING) + { + if (!StringValues.IsValidIndex(index)) + StringValues.SetNum(index + 1); + StringValues[index] = InValue ? "true" : "false"; + } +} + +void +FHoudiniGenericAttribute::SetBoolTuple(const TArray& InTupleValues, int32 index) +{ + if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1)) + return; + + for (int32 n = 0; n < AttributeTupleSize; n++) + SetBoolValue(InTupleValues[n], index * AttributeTupleSize + n); +} + +bool +FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( + UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex) +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Get the Property name + const FString& PropertyName = InPropertyAttribute.AttributeName; + if (PropertyName.IsEmpty()) + return false; + + // Some Properties need to be handle and modified manually... + if (PropertyName.Equals("CollisionProfileName", ESearchCase::IgnoreCase)) + { + UPrimitiveComponent* PC = Cast(InObject); + if (IsValid(PC)) + { + FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); + FName Value = FName(*StringValue); + PC->SetCollisionProfileName(Value); + + // Patch the StaticMeshGenerationProperties on the HAC + UHoudiniAssetComponent* HAC = Cast(InObject); + if (IsValid(HAC)) + { + HAC->StaticMeshGenerationProperties.DefaultBodyInstance.SetCollisionProfileName(Value); + } + + return true; + } + return false; + } + + if (PropertyName.Equals("CollisionEnabled", ESearchCase::IgnoreCase)) + { + UPrimitiveComponent* PC = Cast(InObject); + if (PC && !PC->IsPendingKill()) + { + FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); + if (StringValue.Equals("NoCollision", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::NoCollision); + return true; + } + else if (StringValue.Equals("QueryOnly", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::QueryOnly); + return true; + } + else if (StringValue.Equals("PhysicsOnly", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly); + return true; + } + else if (StringValue.Equals("QueryAndPhysics", ESearchCase::IgnoreCase)) + { + PC->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + return true; + } + return false; + } + } + + // Specialize CastShadow to avoid paying the cost of finding property + calling Property change twice + if (PropertyName.Equals("CastShadow", ESearchCase::IgnoreCase)) + { + UPrimitiveComponent* Component = Cast< UPrimitiveComponent >(InObject); + if (Component && !Component->IsPendingKill()) + { + bool Value = InPropertyAttribute.GetBoolValue(AtIndex); + Component->SetCastShadow(Value); + return true; + } + return false; + } + + // Handle Component Tags manually here + if (PropertyName.Contains("Tags")) + { + UActorComponent* AC = Cast< UActorComponent >(InObject); + if (AC && !AC->IsPendingKill()) + { + FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); + if (!AC->ComponentTags.Contains(NameAttr)) + AC->ComponentTags.Add(NameAttr); + /* + for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++) + { + FName NameAttr = FName(*InPropertyAttribute.GetStringValue(nIdx)); + if (!AC->ComponentTags.Contains(NameAttr)) + AC->ComponentTags.Add(NameAttr); + } + */ + return true; + } + return false; + } +#if WITH_EDITOR + // Handle landscape edit layers toggling + if (PropertyName.Equals("EnableEditLayers", ESearchCase::IgnoreCase) + || PropertyName.Equals("bCanHaveLayersContent", ESearchCase::IgnoreCase)) + { + ALandscape* Landscape = Cast(InObject); + if (IsValid(Landscape)) + { + if(InPropertyAttribute.GetBoolValue(AtIndex) != Landscape->CanHaveLayersContent()) + Landscape->ToggleCanHaveLayersContent(); + + return true; + } + + return false; + } +#endif + + // Try to find the corresponding UProperty + void* OutContainer = nullptr; + FProperty* FoundProperty = nullptr; + UObject* FoundPropertyObject = nullptr; + +#if WITH_EDITOR + // Try to match to source model properties when possible + if (UStaticMesh* SM = Cast(InObject)) + { + if (SM && !SM->IsPendingKill() && SM->GetNumSourceModels() > AtIndex) + { + bool bFoundProperty = false; + TryToFindProperty(&SM->GetSourceModel(AtIndex), SM->GetSourceModel(AtIndex).StaticStruct(), PropertyName, FoundProperty, bFoundProperty, OutContainer); + if (bFoundProperty) + { + FoundPropertyObject = InObject; + } + } + } +#endif + + if (!FoundProperty && !FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer)) + return false; + + // Modify the Property we found + if (!ModifyPropertyValueOnObject(FoundPropertyObject, InPropertyAttribute, FoundProperty, OutContainer, AtIndex)) + return false; + + return true; +} + + +bool +FHoudiniGenericAttribute::FindPropertyOnObject( + UObject* InObject, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + UObject*& OutFoundPropertyObject, + void*& OutContainer) +{ +#if WITH_EDITOR + if (!InObject || InObject->IsPendingKill()) + return false; + + if (InPropertyName.IsEmpty()) + return false; + + UClass* ObjectClass = InObject->GetClass(); + if (!ObjectClass || ObjectClass->IsPendingKill()) + return false; + + // Set the result pointer to null + OutContainer = nullptr; + OutFoundProperty = nullptr; + OutFoundPropertyObject = InObject; + + bool bPropertyHasBeenFound = false; + FHoudiniGenericAttribute::TryToFindProperty( + InObject, + ObjectClass, + InPropertyName, + OutFoundProperty, + bPropertyHasBeenFound, + OutContainer); + + /* + // TODO: Parsing needs to be made recursively! + // Iterate manually on the properties, in order to handle StructProperties correctly + for (TFieldIterator PropIt(ObjectClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) + { + FProperty* CurrentProperty = *PropIt; + if (!CurrentProperty) + continue; + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + OutFoundProperty = CurrentProperty; + + // If it's an equality, we dont need to keep searching + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bPropertyHasBeenFound = true; + break; + } + } + + // StructProperty need to be a nested struct + //if (UStructProperty* StructProperty = Cast< UStructProperty >(CurrentProperty)) + // bPropertyHasBeenFound = TryToFindInStructProperty(InObject, InPropertyName, StructProperty, OutFoundProperty, OutStructContainer); + //else if (UArrayProperty* ArrayProperty = Cast< UArrayProperty >(CurrentProperty)) + // bPropertyHasBeenFound = TryToFindInArrayProperty(InObject, InPropertyName, ArrayProperty, OutFoundProperty, OutStructContainer); + + // Handle StructProperty + FStructProperty* StructProperty = CastField(CurrentProperty); + if (StructProperty) + { + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + if (!Struct || Struct->IsPendingKill()) + continue; + + for (TFieldIterator It(Struct); It; ++It) + { + FProperty* Property = *It; + if (!Property) + continue; + + DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + Name = Property->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + OutFoundProperty = Property; + OutStructContainer = StructProperty->ContainerPtrToValuePtr< void >(InObject, 0); + + // If it's an equality, we dont need to keep searching + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bPropertyHasBeenFound = true; + break; + } + } + } + } + + if (bPropertyHasBeenFound) + break; + } + + if (bPropertyHasBeenFound) + return true; + */ + + // Try with FindField?? + if (!OutFoundProperty) + OutFoundProperty = FindFProperty(ObjectClass, *InPropertyName); + + // Try with FindPropertyByName ?? + if (!OutFoundProperty) + OutFoundProperty = ObjectClass->FindPropertyByName(*InPropertyName); + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + + // Handle common properties nested in classes + // Static Meshes + UStaticMesh* SM = Cast(InObject); + if (SM && !SM->IsPendingKill()) + { + if (SM->GetBodySetup() && FindPropertyOnObject( + SM->GetBodySetup(), InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + + if (SM->AssetImportData && FindPropertyOnObject( + SM->AssetImportData, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + + if (SM->GetNavCollision() && FindPropertyOnObject( + SM->GetNavCollision(), InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + } + + // For Actors, parse their components + AActor* Actor = Cast(InObject); + if (Actor && !Actor->IsPendingKill()) + { + TArray AllComponents; + Actor->GetComponents(AllComponents, true); + + int32 CompIdx = 0; + for (USceneComponent * SceneComponent : AllComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + if (FindPropertyOnObject( + SceneComponent, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) + { + return true; + } + } + } + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + +#endif + return false; +} + + +bool +FHoudiniGenericAttribute::TryToFindProperty( + void* InContainer, + UStruct* InStruct, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + bool& bOutPropertyHasBeenFound, + void*& OutContainer) +{ +#if WITH_EDITOR + if (!InContainer) + return false; + + if (!InStruct || InStruct->IsPendingKill()) + return false; + + if (InPropertyName.IsEmpty()) + return false; + + // Iterate manually on the properties, in order to handle StructProperties correctly + for (TFieldIterator PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt) + { + FProperty* CurrentProperty = *PropIt; + if (!CurrentProperty) + continue; + + FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = CurrentProperty->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName)) + { + OutFoundProperty = CurrentProperty; + OutContainer = InContainer; + + // If it's an equality, we dont need to keep searching anymore + if ((Name == InPropertyName) || (DisplayName == InPropertyName)) + { + bOutPropertyHasBeenFound = true; + break; + } + } + + // Do a recursive parsing for StructProperties + FStructProperty* StructProperty = CastField(CurrentProperty); + if (StructProperty) + { + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + if (!Struct || Struct->IsPendingKill()) + continue; + + TryToFindProperty( + StructProperty->ContainerPtrToValuePtr(InContainer, 0), + Struct, + InPropertyName, + OutFoundProperty, + bOutPropertyHasBeenFound, + OutContainer); + } + + if (bOutPropertyHasBeenFound) + break; + } + + if (bOutPropertyHasBeenFound) + return true; + + // We found the Property we were looking for + if (OutFoundProperty) + return true; + +#endif + return false; +} + + +bool +FHoudiniGenericAttribute::ModifyPropertyValueOnObject( + UObject* InObject, + FHoudiniGenericAttribute InGenericAttribute, + FProperty* FoundProperty, + void* InContainer, + const int32& InAtIndex) +{ + if (!InObject || InObject->IsPendingKill() || !FoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Property class name, used for logging + const FString PropertyClassName = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown"); + + // Initialize using the found property + FProperty* InnerProperty = FoundProperty; + + AActor* InOwner = Cast(InObject->GetOuter()); + bool bHasModifiedProperty = false; + + + auto OnPropertyChanged = [InObject, InOwner, &bHasModifiedProperty](FProperty* InProperty) + { +#if WITH_EDITOR + FPropertyChangedEvent Evt(InProperty); + InObject->PostEditChangeProperty(Evt); + if (InOwner) + { + // If we are setting properties on an Actor component, we want to notify the + // actor of the changes too since the property change might be handled in the actor's + // PostEditChange callbacks (one such an example occurs when changing the material for a decal actor). + InOwner->PostEditChangeProperty(Evt); + } +#endif + bHasModifiedProperty = true; + }; + + FArrayProperty* ArrayProperty = CastField(FoundProperty); + TSharedPtr ArrayHelper; + if (ArrayProperty) + { + InnerProperty = ArrayProperty->Inner; + ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); + } + + // TODO: implement support for array attributes received from Houdini + + // Get the "proper" AtIndex in the flat array by using the attribute tuple size + // TODO: fix the issue when changing array of tuple properties! + const int32 TupleSize = InGenericAttribute.AttributeTupleSize; + int32 AtIndex = InAtIndex * TupleSize; + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + // Supported non-struct properties + + // If the attribute from Houdini has a tuple size > 1, we support setting it on arrays on the unreal side + // For example: a 3 float from Houdini can be set as a TArray in Unreal. + + // If this is an ArrayProperty, ensure that it is at least large enough for our tuple + // TODO: should we just set this to exactly our tuple size? + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleSize - 1); + + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + { + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + } + else + { + // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim + // on the property to determine if our TupleIndex is in range, if not, give up, we cannot set any more + // of our tuple indices on this property. + if (TupleIndex >= InnerProperty->ArrayDim) + break; + + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (ValuePtr) + { + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) + { + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + Property->SetNumericPropertyValueFromString(ValuePtr, *InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); + } + else if (Property->IsFloatingPoint()) + { + Property->SetFloatingPointPropertyValue(ValuePtr, InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex)); + } + else if (Property->IsInteger()) + { + Property->SetIntPropertyValue(ValuePtr, InGenericAttribute.GetIntValue(AtIndex + TupleIndex)); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) + { + FBoolProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetBoolValue(AtIndex + TupleIndex)); + } + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) + { + FStrProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetStringValue(AtIndex + TupleIndex)); + } + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + FNameProperty* const Property = CastField(InnerProperty); + Property->SetPropertyValue(ValuePtr, FName(*InGenericAttribute.GetStringValue(AtIndex + TupleIndex))); + } + + OnPropertyChanged(InnerProperty); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); + if (TupleIndex == 0) + return false; + } + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties + + // If we receive an attribute with tuple size > 1 and the target is an Unreal struct property, then we set + // as many of the values as we can in the struct. For example: a 4-float received from Houdini where the + // target is an FVector, the FVector.X, Y and Z would be set from the 4-float indices 0-2. Index 3 from the + // 4-float would then be ignored. + + const int32 TupleIndex = 0; + // If this is an array property, ensure it has enough space + // TODO: should we just set the array size to 1 for non-arrays and to the array size for arrays (once we support array attributes from Houdini)? + // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleIndex); + + void* PropertyValue = nullptr; + if (ArrayHelper.IsValid()) + PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); + else + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + + if (PropertyValue) + { + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + // Found a vector property, fill it with up to 3 tuple values + FVector& Vector = *static_cast(PropertyValue); + Vector = FVector::ZeroVector; + Vector.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + Vector.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Vector.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == NAME_Transform) + { + // Found a transform property fill it with the attribute tuple values + FVector Translation; + Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + + FQuat Rotation; + if (InGenericAttribute.AttributeTupleSize > 3) + Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); + if (InGenericAttribute.AttributeTupleSize > 4) + Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 4); + if (InGenericAttribute.AttributeTupleSize > 5) + Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 5); + if (InGenericAttribute.AttributeTupleSize > 6) + Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 6); + + FVector Scale(1, 1, 1); + if (InGenericAttribute.AttributeTupleSize > 7) + Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 7); + if (InGenericAttribute.AttributeTupleSize > 8) + Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 8); + if (InGenericAttribute.AttributeTupleSize > 9) + Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 9); + + FTransform& Transform = *static_cast(PropertyValue); + Transform = FTransform::Identity; + Transform.SetTranslation(Translation); + Transform.SetRotation(Rotation); + Transform.SetScale3D(Scale); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == NAME_Color) + { + FColor& Color = *static_cast(PropertyValue); + Color = FColor::Black; + Color.R = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Color.G = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + Color.B = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + Color.A = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 3); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == NAME_LinearColor) + { + FLinearColor& LinearColor = *static_cast(PropertyValue); + LinearColor = FLinearColor::Black; + LinearColor.R = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + LinearColor.G = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + LinearColor.B = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + LinearColor.A = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == "Int32Interval") + { + FInt32Interval& Interval = *static_cast(PropertyValue); + Interval = FInt32Interval(); + Interval.Min = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Interval.Max = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1); + + OnPropertyChanged(StructProperty); + } + else if (PropertyName == "FloatInterval") + { + FFloatInterval& Interval = *static_cast(PropertyValue); + Interval = FFloatInterval(); + Interval.Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + Interval.Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1); + + OnPropertyChanged(StructProperty); + } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + // OBJECT PATH PROPERTY + const int32 TupleIndex = 0; + // If this is an array property, ensure it has enough space + // TODO: should we just set the array size to 1 for non-arrays or to the array size for arrays (once we support array attributes from Houdini)? + // vs just ensuring there is enough space (and then potentially leaving previous/old data behind?) + if (ArrayHelper.IsValid()) + ArrayHelper->ExpandForIndex(TupleIndex); + + FString Value = InGenericAttribute.GetStringValue(AtIndex + TupleIndex); + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + else + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + + if (ValuePtr) + { + TSoftObjectPtr ValueObjectPtr; + ValueObjectPtr = Value; + UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); + + // Ensure the ObjectProperty class matches the ValueObject that we just loaded + if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) + { + ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject); + OnPropertyChanged(ObjectProperty); + } + else + { + HOUDINI_LOG_WARNING( + TEXT("Could net set object property %s: ObjectProperty's object class (%s) does not match referenced object class (%s)!"), + *InGenericAttribute.AttributeName, *(ObjectProperty->PropertyClass->GetName()), IsValid(ValueObject) ? *(ValueObject->GetClass()->GetName()) : TEXT("NULL")); + return false; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); + return false; + } + + if (bHasModifiedProperty) + { +#if WITH_EDITOR + InObject->PostEditChange(); + if (InOwner) + { + InOwner->PostEditChange(); + } +#endif + } + + return true; +} + +bool +FHoudiniGenericAttribute::GetPropertyValueFromObject( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + FHoudiniGenericAttribute& InGenericAttribute, + const int32& InAtIndex) +{ + if (!InObject || InObject->IsPendingKill() || !InFoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Property class name, used for logging + const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); + + // Initialize using the found property + FProperty* InnerProperty = InFoundProperty; + + FArrayProperty* ArrayProperty = CastField(InFoundProperty); + TSharedPtr ArrayHelper; + if (ArrayProperty) + { + InnerProperty = ArrayProperty->Inner; + ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); + } + + // TODO: implement support for array attributes received from Houdini + + // Get the "proper" AtIndex in the flat array by using the attribute tuple size + // TODO: fix the issue when changing array of tuple properties! + const int32 TupleSize = InGenericAttribute.AttributeTupleSize; + int32 AtIndex = InAtIndex * TupleSize; + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + // Supported non-struct properties + + // If the attribute from Houdini has a tuple size > 1, we support getting it on arrays on the unreal side + // For example: a 3 float in Houdini can be set from a TArray in Unreal. + + for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex) + { + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + { + // Check that we are not out of range + if (TupleIndex >= ArrayHelper->Num()) + break; + + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + } + else + { + // If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim + // on the property to determine if our TupleIndex is in range, if not, give up, we cannot get any more + // of our tuple indices from this property. + if (TupleIndex >= InnerProperty->ArrayDim) + break; + + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (ValuePtr) + { + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) + { + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (InGenericAttribute.AttributeType == EAttribStorageType::STRING) + { + InGenericAttribute.SetStringValue(Property->GetNumericPropertyValueToString(ValuePtr), AtIndex + TupleIndex); + } + else if (Property->IsFloatingPoint()) + { + InGenericAttribute.SetDoubleValue(Property->GetFloatingPointPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else if (Property->IsInteger()) + { + InGenericAttribute.SetIntValue(Property->GetSignedIntPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) + { + FBoolProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetBoolValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) + { + FStrProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex); + } + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + FNameProperty* const Property = CastField(InnerProperty); + InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr).ToString(), AtIndex + TupleIndex); + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex); + if (TupleIndex == 0) + return false; + } + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties + + // Set as many as the tuple values as we can from the Unreal struct. + + const int32 TupleIndex = 0; + + void* PropertyValue = nullptr; + if (ArrayHelper.IsValid()) + { + if (ArrayHelper->IsValidIndex(TupleIndex)) + PropertyValue = ArrayHelper->GetRawPtr(TupleIndex); + } + else if (TupleIndex < InnerProperty->ArrayDim) + { + PropertyValue = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (PropertyValue) + { + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + // Found a vector property, fill it with up to 3 tuple values + const FVector& Vector = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(Vector.X, AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Vector.Y, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(Vector.Z, AtIndex + TupleIndex + 2); + } + else if (PropertyName == NAME_Transform) + { + // Found a transform property fill it with the attribute tuple values + const FTransform& Transform = *static_cast(PropertyValue); + const FVector Translation = Transform.GetTranslation(); + const FQuat Rotation = Transform.GetRotation(); + const FVector Scale = Transform.GetScale3D(); + + InGenericAttribute.SetDoubleValue(Translation.X, AtIndex + TupleIndex + 0); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Translation.Y, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(Translation.Z, AtIndex + TupleIndex + 2); + + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetDoubleValue(Rotation.W, AtIndex + TupleIndex + 3); + if (InGenericAttribute.AttributeTupleSize > 4) + InGenericAttribute.SetDoubleValue(Rotation.X, AtIndex + TupleIndex + 4); + if (InGenericAttribute.AttributeTupleSize > 5) + InGenericAttribute.SetDoubleValue(Rotation.Y, AtIndex + TupleIndex + 5); + if (InGenericAttribute.AttributeTupleSize > 6) + InGenericAttribute.SetDoubleValue(Rotation.Z, AtIndex + TupleIndex + 6); + + if (InGenericAttribute.AttributeTupleSize > 7) + InGenericAttribute.SetDoubleValue(Scale.X, AtIndex + TupleIndex + 7); + if (InGenericAttribute.AttributeTupleSize > 8) + InGenericAttribute.SetDoubleValue(Scale.Y, AtIndex + TupleIndex + 8); + if (InGenericAttribute.AttributeTupleSize > 9) + InGenericAttribute.SetDoubleValue(Scale.Z, AtIndex + TupleIndex + 9); + } + else if (PropertyName == NAME_Color) + { + const FColor& Color = *static_cast(PropertyValue); + InGenericAttribute.SetIntValue(Color.R, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetIntValue(Color.G, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetIntValue(Color.B, AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetIntValue(Color.A, AtIndex + TupleIndex + 3); + } + else if (PropertyName == NAME_LinearColor) + { + const FLinearColor& LinearColor = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(LinearColor.R, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(LinearColor.G, AtIndex + TupleIndex + 1); + if (InGenericAttribute.AttributeTupleSize > 2) + InGenericAttribute.SetDoubleValue(LinearColor.B, AtIndex + TupleIndex + 2); + if (InGenericAttribute.AttributeTupleSize > 3) + InGenericAttribute.SetDoubleValue(LinearColor.A, AtIndex + TupleIndex + 3); + } + else if (PropertyName == "Int32Interval") + { + const FInt32Interval& Interval = *static_cast(PropertyValue); + InGenericAttribute.SetIntValue(Interval.Min, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetIntValue(Interval.Max, AtIndex + TupleIndex + 1); + } + else if (PropertyName == "FloatInterval") + { + const FFloatInterval& Interval = *static_cast(PropertyValue); + InGenericAttribute.SetDoubleValue(Interval.Min, AtIndex + TupleIndex); + if (InGenericAttribute.AttributeTupleSize > 1) + InGenericAttribute.SetDoubleValue(Interval.Max, AtIndex + TupleIndex + 1); + } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + // OBJECT PATH PROPERTY + const int32 TupleIndex = 0; + + void* ValuePtr = nullptr; + if (ArrayHelper.IsValid()) + { + if (ArrayHelper->IsValidIndex(TupleIndex)) + ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); + } + else if (TupleIndex < InnerProperty->ArrayDim) + { + ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); + } + + if (ValuePtr) + { + UObject* ValueObject = ObjectProperty->GetObjectPropertyValue(ValuePtr); + const TSoftObjectPtr ValueObjectPtr = ValueObject; + InGenericAttribute.SetStringValue(ValueObjectPtr.ToString(), AtIndex + TupleIndex); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName); + return false; + } + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName); + return false; + } + + return true; +} + +bool +FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + int32& OutAttributeTupleSize, + EAttribStorageType& OutAttributeStorageType) +{ + if (!InObject || InObject->IsPendingKill() || !InFoundProperty) + return false; + + // Determine the container to use (either InContainer if specified, or InObject) + void* Container = InContainer ? InContainer : InObject; + + // Property class name, used for logging + const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown"); + + // Initialize using the found property + FProperty* InnerProperty = InFoundProperty; + + // FArrayProperty* ArrayProperty = CastField(InFoundProperty); + // TSharedPtr ArrayHelper; + // if (ArrayProperty) + // { + // InnerProperty = ArrayProperty->Inner; + // ArrayHelper = MakeShareable(new FScriptArrayHelper_InContainer(ArrayProperty, Container)); + // } + + FFieldClass* PropertyClass = InnerProperty->GetClass(); + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) || + PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + // Supported non-struct properties + + // Here we cannot really do better than tuple size of 1 (since we need to support arrays in the future, we + // cannot just assume array size == tuple size going to Houdini) + OutAttributeTupleSize = 1; + + // Handle each property type that we support + if (PropertyClass->IsChildOf(FNumericProperty::StaticClass())) + { + // Numeric properties are supported as floats and ints, and can also be set from a received string + FNumericProperty* const Property = CastField(InnerProperty); + if (Property->IsFloatingPoint()) + { + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (Property->IsInteger()) + { + OutAttributeStorageType = EAttribStorageType::INT; + } + else + { + HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *Property->GetName(), *PropertyClassName); + return false; + } + } + else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyClass->IsChildOf(FStrProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::STRING; + } + else if (PropertyClass->IsChildOf(FNameProperty::StaticClass())) + { + OutAttributeStorageType = EAttribStorageType::STRING; + } + } + else if (FStructProperty* StructProperty = CastField(InnerProperty)) + { + // struct properties + + const FName PropertyName = StructProperty->Struct->GetFName(); + if (PropertyName == NAME_Vector) + { + OutAttributeTupleSize = 3; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == NAME_Transform) + { + OutAttributeTupleSize = 10; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == NAME_Color) + { + OutAttributeTupleSize = 4; + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyName == NAME_LinearColor) + { + OutAttributeTupleSize = 4; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else if (PropertyName == "Int32Interval") + { + OutAttributeTupleSize = 2; + OutAttributeStorageType = EAttribStorageType::INT; + } + else if (PropertyName == "FloatInterval") + { + OutAttributeTupleSize = 2; + OutAttributeStorageType = EAttribStorageType::FLOAT; + } + else + { + HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InFoundProperty->GetName(), *PropertyClassName, *PropertyName.ToString()); + return false; + } + } + else if (FObjectProperty* ObjectProperty = CastField(InnerProperty)) + { + OutAttributeTupleSize = 1; + OutAttributeStorageType = EAttribStorageType::STRING; + } + else + { + // Property was found, but is of an unsupported type + HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InFoundProperty->GetName()); + return false; + } + + return true; +} + +/* +bool +FHoudiniEngineUtils::TryToFindInStructProperty( + UObject* Object, FString UPropertyNameToFind, UStructProperty* StructProperty, UProperty*& FoundProperty, void*& StructContainer ) +{ + if ( !StructProperty || !Object ) + return false; + + // Walk the structs' properties and try to find the one we're looking for + UScriptStruct* Struct = StructProperty->Struct; + for (TFieldIterator< UProperty > It(Struct); It; ++It) + { + UProperty* Property = *It; + if ( !Property ) + continue; + + FString DisplayName = It->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = It->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = StructProperty->ContainerPtrToValuePtr< void >( Object, 0); + + // If it's an equality, we dont need to keep searching + if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) + return true; + } + + if ( FoundProperty ) + continue; + + UStructProperty* NestedStruct = Cast( Property ); + if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) + return true; + + UArrayProperty* ArrayProp = Cast( Property ); + if ( ArrayProp && TryToFindInArrayProperty( Object, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::TryToFindInArrayProperty( + UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer ) +{ + if ( !ArrayProperty || !Object ) + return false; + + UProperty* Property = ArrayProperty->Inner; + if ( !Property ) + return false; + + FString DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT("")); + FString Name = Property->GetName(); + + // If the property name contains the uprop attribute name, we have a candidate + if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) ) + { + // We found the property in the struct property, we need to keep the ValuePtr in the object + // of the structProp in order to be able to access the property value afterwards... + FoundProperty = Property; + StructContainer = ArrayProperty->ContainerPtrToValuePtr< void >( Object, 0); + + // If it's an equality, we dont need to keep searching + if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) ) + return true; + } + + if ( !FoundProperty ) + { + UStructProperty* NestedStruct = Cast( Property ); + if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) ) + return true; + + UArrayProperty* ArrayProp = Cast( Property ); + if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) ) + return true; + } + + return false; +} */ \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h index 13ccf4053..52ea4322c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.h @@ -1,156 +1,156 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniGenericAttribute.generated.h" - -UENUM() -enum class EAttribStorageType : int8 -{ - Invalid = -1, - - INT = 0, - INT64 = 1, - FLOAT = 2, - FLOAT64 = 3, - STRING = 4 -}; - -UENUM() -enum class EAttribOwner : int8 -{ - Invalid = -1, - - Vertex, - Point, - Prim, - Detail, -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY() - FString AttributeName; - - UPROPERTY() - EAttribStorageType AttributeType = EAttribStorageType::Invalid; - UPROPERTY() - EAttribOwner AttributeOwner = EAttribOwner::Invalid; - - UPROPERTY() - int32 AttributeCount = -1; - UPROPERTY() - int32 AttributeTupleSize = -1; - - UPROPERTY() - TArray DoubleValues; - UPROPERTY() - TArray IntValues; - UPROPERTY() - TArray StringValues; - - // Accessors - - double GetDoubleValue(int32 index = 0) const; - void GetDoubleTuple(TArray& TupleValues, int32 index = 0) const; - - int64 GetIntValue(int32 index = 0) const; - void GetIntTuple(TArray& TupleValues, int32 index = 0) const; - - FString GetStringValue(int32 index = 0) const; - void GetStringTuple(TArray& TupleValues, int32 index = 0) const; - - bool GetBoolValue(int32 index = 0) const; - void GetBoolTuple(TArray& TupleValues, int32 index = 0) const; - - void* GetData(); - - // Mutators - - void SetDoubleValue(double InValue, int32 index = 0); - void SetDoubleTuple(const TArray& InTupleValues, int32 index = 0); - - void SetIntValue(int64 InValue, int32 index = 0); - void SetIntTuple(const TArray& InTupleValues, int32 index = 0); - - void SetStringValue(const FString& InValue, int32 index = 0); - void SetStringTuple(const TArray& InTupleValues, int32 index = 0); - - void SetBoolValue(bool InValue, int32 index = 0); - void SetBoolTuple(const TArray& InTupleValues, int32 index = 0); - - // - static bool UpdatePropertyAttributeOnObject( - UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex = 0); - - // Tries to find a Uproperty by name/label on an object - // FoundPropertyObject will be the object that actually contains the property - // and can be different from InObject if the property is nested. - static bool FindPropertyOnObject( - UObject* InObject, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - UObject*& OutFoundPropertyObject, - void*& OutContainer); - - // Modifies the value of a found Property - static bool ModifyPropertyValueOnObject( - UObject* InObject, - FHoudiniGenericAttribute InGenericAttribute, - FProperty* FoundProperty, - void* InContainer, - const int32& AtIndex = 0 ); - - // Gets the value of a found Property and sets it in the appropriate - // array and index in InGenericAttribute. - static bool GetPropertyValueFromObject( - UObject* InObject, - FProperty* InFoundProperty, - void* InContainer, - FHoudiniGenericAttribute& InGenericAttribute, - const int32& InAtIndex = 0); - - // Helper: determines a valid tuple size and storage type for a Houdini attribute from an Unreal FProperty - static bool GetAttributeTupleSizeAndStorageFromProperty( - UObject* InObject, - FProperty* InFoundProperty, - void* InContainer, - int32& OutAttributeTupleSize, - EAttribStorageType& OutAttributeStorageType); - - // Recursive search for a given property on a UObject - static bool TryToFindProperty( - void* InContainer, - UStruct* InStruct, - const FString& InPropertyName, - FProperty*& OutFoundProperty, - bool& bOutPropertyHasBeenFound, - void*& OutContainer); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniGenericAttribute.generated.h" + +UENUM() +enum class EAttribStorageType : int8 +{ + Invalid = -1, + + INT = 0, + INT64 = 1, + FLOAT = 2, + FLOAT64 = 3, + STRING = 4 +}; + +UENUM() +enum class EAttribOwner : int8 +{ + Invalid = -1, + + Vertex, + Point, + Prim, + Detail, +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGenericAttribute +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY() + FString AttributeName; + + UPROPERTY() + EAttribStorageType AttributeType = EAttribStorageType::Invalid; + UPROPERTY() + EAttribOwner AttributeOwner = EAttribOwner::Invalid; + + UPROPERTY() + int32 AttributeCount = -1; + UPROPERTY() + int32 AttributeTupleSize = -1; + + UPROPERTY() + TArray DoubleValues; + UPROPERTY() + TArray IntValues; + UPROPERTY() + TArray StringValues; + + // Accessors + + double GetDoubleValue(int32 index = 0) const; + void GetDoubleTuple(TArray& TupleValues, int32 index = 0) const; + + int64 GetIntValue(int32 index = 0) const; + void GetIntTuple(TArray& TupleValues, int32 index = 0) const; + + FString GetStringValue(int32 index = 0) const; + void GetStringTuple(TArray& TupleValues, int32 index = 0) const; + + bool GetBoolValue(int32 index = 0) const; + void GetBoolTuple(TArray& TupleValues, int32 index = 0) const; + + void* GetData(); + + // Mutators + + void SetDoubleValue(double InValue, int32 index = 0); + void SetDoubleTuple(const TArray& InTupleValues, int32 index = 0); + + void SetIntValue(int64 InValue, int32 index = 0); + void SetIntTuple(const TArray& InTupleValues, int32 index = 0); + + void SetStringValue(const FString& InValue, int32 index = 0); + void SetStringTuple(const TArray& InTupleValues, int32 index = 0); + + void SetBoolValue(bool InValue, int32 index = 0); + void SetBoolTuple(const TArray& InTupleValues, int32 index = 0); + + // + static bool UpdatePropertyAttributeOnObject( + UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex = 0); + + // Tries to find a Uproperty by name/label on an object + // FoundPropertyObject will be the object that actually contains the property + // and can be different from InObject if the property is nested. + static bool FindPropertyOnObject( + UObject* InObject, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + UObject*& OutFoundPropertyObject, + void*& OutContainer); + + // Modifies the value of a found Property + static bool ModifyPropertyValueOnObject( + UObject* InObject, + FHoudiniGenericAttribute InGenericAttribute, + FProperty* FoundProperty, + void* InContainer, + const int32& AtIndex = 0 ); + + // Gets the value of a found Property and sets it in the appropriate + // array and index in InGenericAttribute. + static bool GetPropertyValueFromObject( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + FHoudiniGenericAttribute& InGenericAttribute, + const int32& InAtIndex = 0); + + // Helper: determines a valid tuple size and storage type for a Houdini attribute from an Unreal FProperty + static bool GetAttributeTupleSizeAndStorageFromProperty( + UObject* InObject, + FProperty* InFoundProperty, + void* InContainer, + int32& OutAttributeTupleSize, + EAttribStorageType& OutAttributeStorageType); + + // Recursive search for a given property on a UObject + static bool TryToFindProperty( + void* InContainer, + UStruct* InStruct, + const FString& InPropertyName, + FProperty*& OutFoundProperty, + bool& bOutPropertyHasBeenFound, + void*& OutContainer); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp index bea64f3d3..49171ceb6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.cpp @@ -1,185 +1,185 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniGeoPartObject.h" - -// -FHoudiniGeoPartObject::FHoudiniGeoPartObject() - : AssetId(-1) - , AssetName(TEXT("")) - , ObjectId(-1) - , ObjectName(TEXT("")) - , GeoId(-1) - , PartId(-1) - , PartName(TEXT("")) - , bHasCustomPartName(false) - , TransformMatrix(FMatrix::Identity) - , NodePath(TEXT("")) - , Type(EHoudiniPartType::Invalid) - , InstancerType(EHoudiniInstancerType::Invalid) - , VolumeName(TEXT("")) - , VolumeTileIndex(-1) - , bIsVisible(false) - , bIsEditable(false) - , bIsTemplated(false) - , bIsInstanced(false) - , bHasGeoChanged(true) - , bHasPartChanged(true) - , bHasTransformChanged(true) - , bHasMaterialsChanged(true) - , bLoaded(false) -{ - -} - -bool -FHoudiniGeoPartObject::IsValid() const -{ - return (ObjectId >= 0 && GeoId >= 0 && PartId >= 0); -} - -bool -FHoudiniGeoPartObject::operator==(const FHoudiniGeoPartObject & InGeoPartObject) const -{ - // TODO: split?? - return Equals(InGeoPartObject, true); -} - -bool -FHoudiniGeoPartObject::Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const -{ - // TODO: This will likely need some improvement! - - /* - // Object/Geo/Part IDs must match - if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) - return false; - - if (!bIgnoreSplit) - { - // If the split type and index match, we're equal... - if (SplitType == GeoPartObject.SplitType && SplitIndex == GeoPartObject.SplitIndex) - return true; - - // ... if not we should compare our names - return CompareNames(GeoPartObject, bIgnoreSplit); - } - */ - - // See if objects / geo / part ids match - bool MatchingIDs = true; - if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) - MatchingIDs = false; - - // See if the type matches - bool MatchingType = (Type == GeoPartObject.Type); - // Both IDs and type match, consider the two HGPO as equals - if (MatchingIDs && MatchingType) - return true; - - // Both IDs and type do not match, consider the two HGPOs as different - if (!MatchingIDs && !MatchingType) - return false; - - // If only the ID dont match we can do some further checks - - // If one of the two HGPO has been loaded - if ((bLoaded && !GeoPartObject.bLoaded) - || (!bLoaded && GeoPartObject.bLoaded)) - { - // For loaded HGPOs, part names should be a sufficent comparison - if (PartName.Equals(GeoPartObject.PartName)) - return true; - } - - // TODO: This was causing issues somehow with tiled landscapes - // ... if not, compare by names - if(!MatchingIDs) - return CompareNames(GeoPartObject, bIgnoreSplit); - - return false; -} - -void -FHoudiniGeoPartObject::SetCustomPartName(const FString & InName) -{ - if (InName.IsEmpty()) - return; - - PartName = InName; - bHasCustomPartName = true; -} - -bool -FHoudiniGeoPartObject::CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const -{ - //TODO: AssetName? - - // Object, part and split names must match - if (!ObjectName.Equals(HoudiniGeoPartObject.ObjectName) - || !PartName.Equals(HoudiniGeoPartObject.PartName)) - { - return false; - } - - /* - // Split should also match if we dont ignore it - if (!bIgnoreSplit && !SplitName.Equals(HoudiniGeoPartObject.SplitName)) - { - return false; - } - */ - - return true; -} - -FString -FHoudiniGeoPartObject::HoudiniPartTypeToString(const EHoudiniPartType& InType) -{ - FString OutTypeStr; - switch (InType) - { - case EHoudiniPartType::Mesh: - OutTypeStr = TEXT("Mesh"); - break; - case EHoudiniPartType::Instancer: - OutTypeStr = TEXT("Instancer"); - break; - case EHoudiniPartType::Curve: - OutTypeStr = TEXT("Curve"); - break; - case EHoudiniPartType::Volume: - OutTypeStr = TEXT("Volume"); - break; - - default: - case EHoudiniPartType::Invalid: - OutTypeStr = TEXT("Invalid"); - break; - } - - return OutTypeStr; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniGeoPartObject.h" + +// +FHoudiniGeoPartObject::FHoudiniGeoPartObject() + : AssetId(-1) + , AssetName(TEXT("")) + , ObjectId(-1) + , ObjectName(TEXT("")) + , GeoId(-1) + , PartId(-1) + , PartName(TEXT("")) + , bHasCustomPartName(false) + , TransformMatrix(FMatrix::Identity) + , NodePath(TEXT("")) + , Type(EHoudiniPartType::Invalid) + , InstancerType(EHoudiniInstancerType::Invalid) + , VolumeName(TEXT("")) + , VolumeTileIndex(-1) + , bIsVisible(false) + , bIsEditable(false) + , bIsTemplated(false) + , bIsInstanced(false) + , bHasGeoChanged(true) + , bHasPartChanged(true) + , bHasTransformChanged(true) + , bHasMaterialsChanged(true) + , bLoaded(false) +{ + +} + +bool +FHoudiniGeoPartObject::IsValid() const +{ + return (ObjectId >= 0 && GeoId >= 0 && PartId >= 0); +} + +bool +FHoudiniGeoPartObject::operator==(const FHoudiniGeoPartObject & InGeoPartObject) const +{ + // TODO: split?? + return Equals(InGeoPartObject, true); +} + +bool +FHoudiniGeoPartObject::Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const +{ + // TODO: This will likely need some improvement! + + /* + // Object/Geo/Part IDs must match + if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) + return false; + + if (!bIgnoreSplit) + { + // If the split type and index match, we're equal... + if (SplitType == GeoPartObject.SplitType && SplitIndex == GeoPartObject.SplitIndex) + return true; + + // ... if not we should compare our names + return CompareNames(GeoPartObject, bIgnoreSplit); + } + */ + + // See if objects / geo / part ids match + bool MatchingIDs = true; + if (ObjectId != GeoPartObject.ObjectId || GeoId != GeoPartObject.GeoId || PartId != GeoPartObject.PartId) + MatchingIDs = false; + + // See if the type matches + bool MatchingType = (Type == GeoPartObject.Type); + // Both IDs and type match, consider the two HGPO as equals + if (MatchingIDs && MatchingType) + return true; + + // Both IDs and type do not match, consider the two HGPOs as different + if (!MatchingIDs && !MatchingType) + return false; + + // If only the ID dont match we can do some further checks + + // If one of the two HGPO has been loaded + if ((bLoaded && !GeoPartObject.bLoaded) + || (!bLoaded && GeoPartObject.bLoaded)) + { + // For loaded HGPOs, part names should be a sufficent comparison + if (PartName.Equals(GeoPartObject.PartName)) + return true; + } + + // TODO: This was causing issues somehow with tiled landscapes + // ... if not, compare by names + if(!MatchingIDs) + return CompareNames(GeoPartObject, bIgnoreSplit); + + return false; +} + +void +FHoudiniGeoPartObject::SetCustomPartName(const FString & InName) +{ + if (InName.IsEmpty()) + return; + + PartName = InName; + bHasCustomPartName = true; +} + +bool +FHoudiniGeoPartObject::CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const +{ + //TODO: AssetName? + + // Object, part and split names must match + if (!ObjectName.Equals(HoudiniGeoPartObject.ObjectName) + || !PartName.Equals(HoudiniGeoPartObject.PartName)) + { + return false; + } + + /* + // Split should also match if we dont ignore it + if (!bIgnoreSplit && !SplitName.Equals(HoudiniGeoPartObject.SplitName)) + { + return false; + } + */ + + return true; +} + +FString +FHoudiniGeoPartObject::HoudiniPartTypeToString(const EHoudiniPartType& InType) +{ + FString OutTypeStr; + switch (InType) + { + case EHoudiniPartType::Mesh: + OutTypeStr = TEXT("Mesh"); + break; + case EHoudiniPartType::Instancer: + OutTypeStr = TEXT("Instancer"); + break; + case EHoudiniPartType::Curve: + OutTypeStr = TEXT("Curve"); + break; + case EHoudiniPartType::Volume: + OutTypeStr = TEXT("Volume"); + break; + + default: + case EHoudiniPartType::Invalid: + OutTypeStr = TEXT("Invalid"); + break; + } + + return OutTypeStr; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h index 7b8db79ce..f0dd12a9e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h @@ -1,423 +1,404 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniGeoPartObject.generated.h" - -UENUM() -enum class EHoudiniGeoType : uint8 -{ - Invalid, - - Default, - Intermediate, - Input, - Curve -}; - -UENUM() -enum class EHoudiniPartType : uint8 -{ - Invalid, - - Mesh, - Instancer, - Curve, - Volume -}; - -UENUM() -enum class EHoudiniInstancerType : uint8 -{ - Invalid, - - ObjectInstancer, - PackedPrimitive, - AttributeInstancer, - OldSchoolAttributeInstancer -}; - -UENUM() -enum class EHoudiniCurveType : int8 -{ - Invalid = -1, - - Polygon = 0, - Nurbs = 1, - Bezier = 2, - Points = 3 -}; - -UENUM() -enum class EHoudiniCurveMethod : int8 -{ - Invalid = -1, - - CVs = 0, - Breakpoints = 1, - Freehand = 2 -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniObjectInfo -{ - GENERATED_USTRUCT_BODY() - - FString Name = TEXT(""); - - int32 NodeId = -1; - int32 ObjectToInstanceID = -1; - - bool bHasTransformChanged = false; - bool bHaveGeosChanged = false; - bool bIsVisible = false; - bool bIsInstancer = false; - bool bIsInstanced = false; - - int32 GeoCount = -1; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGeoInfo -{ - GENERATED_USTRUCT_BODY() - - EHoudiniGeoType Type = EHoudiniGeoType::Invalid; - FString Name = TEXT(""); - int32 NodeId = -1; - - bool bIsEditable = false; - bool bIsTemplated = false; - bool bIsDisplayGeo = false; - bool bHasGeoChanged = false; - bool bHasMaterialChanged = false; - - int32 PartCount = -1; - int32 PointGroupCount = -1; - int32 PrimitiveGroupCount = -1; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniPartInfo -{ - GENERATED_USTRUCT_BODY() - - int32 PartId = -1; - FString Name = TEXT(""); - - EHoudiniPartType Type = EHoudiniPartType::Invalid; - - int32 FaceCount = -1; - int32 VertexCount = -1; - int32 PointCount = -1; - - int32 PointAttributeCounts = -1; - int32 VertexAttributeCounts = -1; - int32 PrimitiveAttributeCounts = -1; - int32 DetailAttributeCounts = -1; - - bool bIsInstanced = false; - - int32 InstancedPartCount = -1; - int32 InstanceCount = -1; - - bool bHasChanged = false; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniVolumeInfo -{ - GENERATED_USTRUCT_BODY() - - FString Name = TEXT(""); - bool bIsVDB = false; // replaces VolumeType Type; - - int32 TupleSize = -1; - bool bIsFloat = false; // replaces StorageType StorageType; - int32 TileSize = -1; - - FTransform Transform = FTransform::Identity; - bool bHasTaper = false; - - int32 XLength = -1; - int32 YLength = -1; - int32 ZLength = -1; - - int32 MinX = -1; - int32 MinY = -1; - int32 MinZ = -1; - - float XTaper = 0.0f; - float YTaper = 0.0f; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniCurveInfo -{ - GENERATED_USTRUCT_BODY() - - EHoudiniCurveType Type = EHoudiniCurveType::Invalid; - - int32 CurveCount = -1; - int32 VertexCount = -1; - int32 KnotCount = -1; - - bool bIsPeriodic = false; - bool bIsRational = false; - - int32 Order = -1; - - bool bHasKnots = false; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniMeshSocket -{ - GENERATED_USTRUCT_BODY() - - // Equality operator, used by containers - bool operator==(const FHoudiniMeshSocket& InSocket) const - { - return Transform.Equals(InSocket.Transform) - && Name == InSocket.Name - && Actor == InSocket.Actor - && Tag == InSocket.Tag; - } - - // Members - FTransform Transform = FTransform::Identity; - FString Name = TEXT("Socket"); - FString Actor = FString(); - FString Tag = FString(); -}; - -/* -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniSplitDataCache -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY() - FString SplitName; - //UPROPERTY() - //FHoudiniOutputObjectIdentifier SplitIdentifier; - //EHoudiniSplitType SplitType; - - UPROPERTY() - TArray Positions; - UPROPERTY() - TArray Indices; - - UPROPERTY() - TArray Normals; - UPROPERTY() - TArray Tangents; - UPROPERTY() - TArray Binormals; - - UPROPERTY() - TArray Colors; - - //UPROPERTY() - //TArray> UVs; - - //TArray EdgeHardnesses; - UPROPERTY() - TArray FaceSmoothingMasks; - UPROPERTY() - TArray LightMapResolutions; - - UPROPERTY() - TArray MaterialIndices; - UPROPERTY() - TArray Materials; - - UPROPERTY() - float lod_screensize; - - UPROPERTY() - FKAggregateGeom AggregateCollisions; -}; -*/ - - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject -{ -public: - - GENERATED_USTRUCT_BODY() - - FHoudiniGeoPartObject(); - - // Indicates if this HGPO is valid - bool IsValid() const; - - // Equality operator, used by containers - bool operator==(const FHoudiniGeoPartObject & GeoPartObject) const; - - // Checks equality, with the possibility to ignore the HGPO's splits - bool Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const; - - // Comparison based on object/part/split name. - bool CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const; - - void SetCustomPartName(const FString & InName); - - static FString HoudiniPartTypeToString(const EHoudiniPartType& InType); - -public: - - // NodeId of corresponding HAPI Asset. - UPROPERTY() - int32 AssetId; - - // Name of corresponding HDA. - UPROPERTY() - FString AssetName; - - // NodeId of corresponding HAPI Object. - UPROPERTY() - int32 ObjectId; - - // Name of associated object. - UPROPERTY() - FString ObjectName; - - // NodeId of corresponding HAPI Geo. - UPROPERTY() - int32 GeoId; - - // PartId of corresponding HAPI Part. - UPROPERTY() - int32 PartId; - - // Name of associated part. - UPROPERTY() - FString PartName; - - UPROPERTY() - bool bHasCustomPartName; - - /* - // Type of the split. - UPROPERTY() - EHoudiniSplitType SplitType; - - // Index of a split. In most cases this will be 0. - UPROPERTY() - int32 SplitIndex; - - // Name of group which was used for splitting, empty if there's none. - UPROPERTY() - FString SplitName; - */ - - // Split groups handled by this HGPO - UPROPERTY() - TArray SplitGroups; - - // Transform of this geo part object. - UPROPERTY() - FTransform TransformMatrix; - - // Path to the corresponding node - UPROPERTY() - FString NodePath; - - // Indicates the type of the referenced object - UPROPERTY() - EHoudiniPartType Type; - - // Indicates the type of instancer - UPROPERTY() - EHoudiniInstancerType InstancerType; - - // - UPROPERTY() - FString VolumeName; - - // - UPROPERTY() - int32 VolumeTileIndex; - - // Is set to true when referenced object is visible. - UPROPERTY() - bool bIsVisible; - - // Is set to true when referenced object is editable. - UPROPERTY() - bool bIsEditable; - - // Is set to true when referenced object is templated. - UPROPERTY() - bool bIsTemplated; - - // Is set to true when the referenced object is instanced. - UPROPERTY() - bool bIsInstanced; - - // Indicates the parent geo has changed and needs to be rebuilt - UPROPERTY() - bool bHasGeoChanged; - - // Indicates the part has changed and needs to be rebuilt - UPROPERTY() - bool bHasPartChanged; - - // Indicates only the transform needs to be updated - UPROPERTY() - bool bHasTransformChanged; - - // Indicates only the material needs to be updated - UPROPERTY() - bool bHasMaterialsChanged; - - // Indicates this object has been loaded - bool bLoaded; - - // We also keep a cache of the various info objects - // That we've extracted from HAPI - - // ObjectInfo cache - FHoudiniObjectInfo ObjectInfo; - // GeoInfo cache - FHoudiniGeoInfo GeoInfo; - // PartInfo cache - FHoudiniPartInfo PartInfo; - // VolumeInfo cache - FHoudiniVolumeInfo VolumeInfo; - // CurveInfo cache - FHoudiniCurveInfo CurveInfo; - - // Stores the Mesh Sockets found for a given HGPO - UPROPERTY() - TArray AllMeshSockets; - - // Cache of this HGPO split data - //TArray SplitCache; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineRuntimeCommon.h" + +#include "HoudiniGeoPartObject.generated.h" + +UENUM() +enum class EHoudiniGeoType : uint8 +{ + Invalid, + + Default, + Intermediate, + Input, + Curve +}; + +UENUM() +enum class EHoudiniPartType : uint8 +{ + Invalid, + + Mesh, + Instancer, + Curve, + Volume +}; + +UENUM() +enum class EHoudiniInstancerType : uint8 +{ + Invalid, + + ObjectInstancer, + PackedPrimitive, + AttributeInstancer, + OldSchoolAttributeInstancer +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniObjectInfo +{ + GENERATED_USTRUCT_BODY() + + FString Name = TEXT(""); + + int32 NodeId = -1; + int32 ObjectToInstanceID = -1; + + bool bHasTransformChanged = false; + bool bHaveGeosChanged = false; + bool bIsVisible = false; + bool bIsInstancer = false; + bool bIsInstanced = false; + + int32 GeoCount = -1; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGeoInfo +{ + GENERATED_USTRUCT_BODY() + + EHoudiniGeoType Type = EHoudiniGeoType::Invalid; + FString Name = TEXT(""); + int32 NodeId = -1; + + bool bIsEditable = false; + bool bIsTemplated = false; + bool bIsDisplayGeo = false; + bool bHasGeoChanged = false; + bool bHasMaterialChanged = false; + + int32 PartCount = -1; + int32 PointGroupCount = -1; + int32 PrimitiveGroupCount = -1; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniPartInfo +{ + GENERATED_USTRUCT_BODY() + + int32 PartId = -1; + FString Name = TEXT(""); + + EHoudiniPartType Type = EHoudiniPartType::Invalid; + + int32 FaceCount = -1; + int32 VertexCount = -1; + int32 PointCount = -1; + + int32 PointAttributeCounts = -1; + int32 VertexAttributeCounts = -1; + int32 PrimitiveAttributeCounts = -1; + int32 DetailAttributeCounts = -1; + + bool bIsInstanced = false; + + int32 InstancedPartCount = -1; + int32 InstanceCount = -1; + + bool bHasChanged = false; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniVolumeInfo +{ + GENERATED_USTRUCT_BODY() + + FString Name = TEXT(""); + bool bIsVDB = false; // replaces VolumeType Type; + + int32 TupleSize = -1; + bool bIsFloat = false; // replaces StorageType StorageType; + int32 TileSize = -1; + + FTransform Transform = FTransform::Identity; + bool bHasTaper = false; + + int32 XLength = -1; + int32 YLength = -1; + int32 ZLength = -1; + + int32 MinX = -1; + int32 MinY = -1; + int32 MinZ = -1; + + float XTaper = 0.0f; + float YTaper = 0.0f; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniCurveInfo +{ + GENERATED_USTRUCT_BODY() + + EHoudiniCurveType Type = EHoudiniCurveType::Invalid; + + int32 CurveCount = -1; + int32 VertexCount = -1; + int32 KnotCount = -1; + + bool bIsPeriodic = false; + bool bIsRational = false; + + int32 Order = -1; + + bool bHasKnots = false; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniMeshSocket +{ + GENERATED_USTRUCT_BODY() + + // Equality operator, used by containers + bool operator==(const FHoudiniMeshSocket& InSocket) const + { + return Transform.Equals(InSocket.Transform) + && Name == InSocket.Name + && Actor == InSocket.Actor + && Tag == InSocket.Tag; + } + + // Members + FTransform Transform = FTransform::Identity; + FString Name = TEXT("Socket"); + FString Actor = FString(); + FString Tag = FString(); +}; + +/* +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniSplitDataCache +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY() + FString SplitName; + //UPROPERTY() + //FHoudiniOutputObjectIdentifier SplitIdentifier; + //EHoudiniSplitType SplitType; + + UPROPERTY() + TArray Positions; + UPROPERTY() + TArray Indices; + + UPROPERTY() + TArray Normals; + UPROPERTY() + TArray Tangents; + UPROPERTY() + TArray Binormals; + + UPROPERTY() + TArray Colors; + + //UPROPERTY() + //TArray> UVs; + + //TArray EdgeHardnesses; + UPROPERTY() + TArray FaceSmoothingMasks; + UPROPERTY() + TArray LightMapResolutions; + + UPROPERTY() + TArray MaterialIndices; + UPROPERTY() + TArray Materials; + + UPROPERTY() + float lod_screensize; + + UPROPERTY() + FKAggregateGeom AggregateCollisions; +}; +*/ + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject +{ +public: + + GENERATED_USTRUCT_BODY() + + FHoudiniGeoPartObject(); + + // Indicates if this HGPO is valid + bool IsValid() const; + + // Equality operator, used by containers + bool operator==(const FHoudiniGeoPartObject & GeoPartObject) const; + + // Checks equality, with the possibility to ignore the HGPO's splits + bool Equals(const FHoudiniGeoPartObject & GeoPartObject, const bool& bIgnoreSplit) const; + + // Comparison based on object/part/split name. + bool CompareNames(const FHoudiniGeoPartObject & HoudiniGeoPartObject, const bool& bIgnoreSplit) const; + + void SetCustomPartName(const FString & InName); + + static FString HoudiniPartTypeToString(const EHoudiniPartType& InType); + +public: + + // NodeId of corresponding HAPI Asset. + UPROPERTY() + int32 AssetId; + + // Name of corresponding HDA. + UPROPERTY() + FString AssetName; + + // NodeId of corresponding HAPI Object. + UPROPERTY() + int32 ObjectId; + + // Name of associated object. + UPROPERTY() + FString ObjectName; + + // NodeId of corresponding HAPI Geo. + UPROPERTY() + int32 GeoId; + + // PartId of corresponding HAPI Part. + UPROPERTY() + int32 PartId; + + // Name of associated part. + UPROPERTY() + FString PartName; + + UPROPERTY() + bool bHasCustomPartName; + + /* + // Type of the split. + UPROPERTY() + EHoudiniSplitType SplitType; + + // Index of a split. In most cases this will be 0. + UPROPERTY() + int32 SplitIndex; + + // Name of group which was used for splitting, empty if there's none. + UPROPERTY() + FString SplitName; + */ + + // Split groups handled by this HGPO + UPROPERTY() + TArray SplitGroups; + + // Transform of this geo part object. + UPROPERTY() + FTransform TransformMatrix; + + // Path to the corresponding node + UPROPERTY() + FString NodePath; + + // Indicates the type of the referenced object + UPROPERTY() + EHoudiniPartType Type; + + // Indicates the type of instancer + UPROPERTY() + EHoudiniInstancerType InstancerType; + + // + UPROPERTY() + FString VolumeName; + + // + UPROPERTY() + int32 VolumeTileIndex; + + // Is set to true when referenced object is visible. + UPROPERTY() + bool bIsVisible; + + // Is set to true when referenced object is editable. + UPROPERTY() + bool bIsEditable; + + // Is set to true when referenced object is templated. + UPROPERTY() + bool bIsTemplated; + + // Is set to true when the referenced object is instanced. + UPROPERTY() + bool bIsInstanced; + + // Indicates the parent geo has changed and needs to be rebuilt + UPROPERTY() + bool bHasGeoChanged; + + // Indicates the part has changed and needs to be rebuilt + UPROPERTY() + bool bHasPartChanged; + + // Indicates only the transform needs to be updated + UPROPERTY() + bool bHasTransformChanged; + + // Indicates only the material needs to be updated + UPROPERTY() + bool bHasMaterialsChanged; + + // Indicates this object has been loaded + bool bLoaded; + + // We also keep a cache of the various info objects + // That we've extracted from HAPI + + // ObjectInfo cache + FHoudiniObjectInfo ObjectInfo; + // GeoInfo cache + FHoudiniGeoInfo GeoInfo; + // PartInfo cache + FHoudiniPartInfo PartInfo; + // VolumeInfo cache + FHoudiniVolumeInfo VolumeInfo; + // CurveInfo cache + FHoudiniCurveInfo CurveInfo; + + // Stores the Mesh Sockets found for a given HGPO + UPROPERTY() + TArray AllMeshSockets; + + // Cache of this HGPO split data + //TArray SplitCache; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp index 0ed795c0e..6d76a2c12 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.cpp @@ -1,255 +1,255 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniHandleComponent.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterChoice.h" -#include "HoudiniRuntimeSettings.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -void -UHoudiniHandleComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniHandleComponent_V1* CompatibilityHC = NewObject(); - CompatibilityHC->Serialize(Ar); - CompatibilityHC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : serialized data will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -UHoudiniHandleParameter::UHoudiniHandleParameter(const FObjectInitializer & ObjectInitializer) - :Super(ObjectInitializer) -{}; - -UHoudiniHandleComponent::UHoudiniHandleComponent(const FObjectInitializer & ObjectInitializer) - :Super(ObjectInitializer) -{}; - - -bool -UHoudiniHandleParameter::Bind(float & OutValue, const char * CmpName, int32 InTupleIdx, - const FString & HandleParmName, UHoudiniParameter* Parameter) -{ - if (!Parameter) - return false; - - if (HandleParmName != CmpName) - return false; - - UHoudiniParameterFloat* FloatParameter = Cast(Parameter); - - if (!FloatParameter) - return false; - - AssetParameter = Parameter; - - if (FloatParameter) - { - // It is possible that the handle param is bound to a single tuple param. - // Ignore the preset tuple index if that's the case or we'll crash. - if (Parameter->GetTupleSize() <= InTupleIdx) - InTupleIdx = 0; - - auto Optional = FloatParameter->GetValue(InTupleIdx); - if (Optional.IsSet()) - { - TupleIndex = InTupleIdx; - OutValue = Optional.GetValue(); - return true; - } - } - - return false; -} - -bool -UHoudiniHandleParameter::Bind(TSharedPtr & OutValue, const char * CmpName, - int32 InTupleIdx, const FString & HandleParmName, UHoudiniParameter* Parameter) -{ - if (!Parameter) - return false; - - if (HandleParmName != CmpName) - return false; - - UHoudiniParameterChoice* ChoiceParameter = Cast(Parameter); - - if (!ChoiceParameter) - return false; - - AssetParameter = Parameter; - - if (ChoiceParameter) - { - // It is possible that the handle param is bound to a single tuple param. - // Ignore the preset tuple index if that's the case or we'll crash. - if (Parameter->GetTupleSize() <= InTupleIdx) - InTupleIdx = 0; - - auto Optional = ChoiceParameter->GetValue(InTupleIdx); - if (Optional.IsSet()) - { - TupleIndex = InTupleIdx; - OutValue = Optional.GetValue(); - return true; - } - } - - return false; -} - -TSharedPtr -UHoudiniHandleParameter::Get(TSharedPtr DefaultValue) const -{ - UHoudiniParameterChoice* ChoiceParameter = Cast(AssetParameter); - if (ChoiceParameter) - { - auto Optional = ChoiceParameter->GetValue(TupleIndex); - if (Optional.IsSet()) - return Optional.GetValue(); - } - - return DefaultValue; -} - -UHoudiniHandleParameter & -UHoudiniHandleParameter::operator=(float Value) -{ - UHoudiniParameterFloat* FloatParameter = Cast(AssetParameter); - if (FloatParameter) - { - FloatParameter->SetValue(Value, TupleIndex); - FloatParameter->MarkChanged(true); - } - - return *this; -} - -void -UHoudiniHandleComponent::InitializeHandleParameters() -{ - if (XformParms.Num() < int32(EXformParameter::COUNT)) - { - XformParms.Empty(); - for (int32 n = 0; n < int32(EXformParameter::COUNT); ++n) - { - UHoudiniHandleParameter* XformHandle = NewObject(this, UHoudiniHandleParameter::StaticClass()); - XformParms.Add(XformHandle); - } - } - - if (!RSTParm) - { - RSTParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); - } - - if (!RotOrderParm) - { - RotOrderParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); - } -} - -bool -UHoudiniHandleComponent::CheckHandleValid() const -{ - if (XformParms.Num() < int32(EXformParameter::COUNT)) - return false; - - for (auto& XformParm : XformParms) - { - if (!XformParm) - return false; - } - - if (!RSTParm) - return false; - - if (!RotOrderParm) - return false; - - return true; -} - -FBox -UHoudiniHandleComponent::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - return BoxBounds + GetComponentLocation(); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniHandleComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterChoice.h" +#include "HoudiniRuntimeSettings.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +void +UHoudiniHandleComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + Ar.UsingCustomVersion(FHoudiniCustomSerializationVersion::GUID); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniHandleComponent_V1* CompatibilityHC = NewObject(); + CompatibilityHC->Serialize(Ar); + CompatibilityHC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniHandleComponent : serialized data will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +UHoudiniHandleParameter::UHoudiniHandleParameter(const FObjectInitializer & ObjectInitializer) + :Super(ObjectInitializer) +{}; + +UHoudiniHandleComponent::UHoudiniHandleComponent(const FObjectInitializer & ObjectInitializer) + :Super(ObjectInitializer) +{}; + + +bool +UHoudiniHandleParameter::Bind(float & OutValue, const char * CmpName, int32 InTupleIdx, + const FString & HandleParmName, UHoudiniParameter* Parameter) +{ + if (!Parameter) + return false; + + if (HandleParmName != CmpName) + return false; + + UHoudiniParameterFloat* FloatParameter = Cast(Parameter); + + if (!FloatParameter) + return false; + + AssetParameter = Parameter; + + if (FloatParameter) + { + // It is possible that the handle param is bound to a single tuple param. + // Ignore the preset tuple index if that's the case or we'll crash. + if (Parameter->GetTupleSize() <= InTupleIdx) + InTupleIdx = 0; + + auto Optional = FloatParameter->GetValue(InTupleIdx); + if (Optional.IsSet()) + { + TupleIndex = InTupleIdx; + OutValue = Optional.GetValue(); + return true; + } + } + + return false; +} + +bool +UHoudiniHandleParameter::Bind(TSharedPtr & OutValue, const char * CmpName, + int32 InTupleIdx, const FString & HandleParmName, UHoudiniParameter* Parameter) +{ + if (!Parameter) + return false; + + if (HandleParmName != CmpName) + return false; + + UHoudiniParameterChoice* ChoiceParameter = Cast(Parameter); + + if (!ChoiceParameter) + return false; + + AssetParameter = Parameter; + + if (ChoiceParameter) + { + // It is possible that the handle param is bound to a single tuple param. + // Ignore the preset tuple index if that's the case or we'll crash. + if (Parameter->GetTupleSize() <= InTupleIdx) + InTupleIdx = 0; + + auto Optional = ChoiceParameter->GetValue(InTupleIdx); + if (Optional.IsSet()) + { + TupleIndex = InTupleIdx; + OutValue = Optional.GetValue(); + return true; + } + } + + return false; +} + +TSharedPtr +UHoudiniHandleParameter::Get(TSharedPtr DefaultValue) const +{ + UHoudiniParameterChoice* ChoiceParameter = Cast(AssetParameter); + if (ChoiceParameter) + { + auto Optional = ChoiceParameter->GetValue(TupleIndex); + if (Optional.IsSet()) + return Optional.GetValue(); + } + + return DefaultValue; +} + +UHoudiniHandleParameter & +UHoudiniHandleParameter::operator=(float Value) +{ + UHoudiniParameterFloat* FloatParameter = Cast(AssetParameter); + if (FloatParameter) + { + FloatParameter->SetValue(Value, TupleIndex); + FloatParameter->MarkChanged(true); + } + + return *this; +} + +void +UHoudiniHandleComponent::InitializeHandleParameters() +{ + if (XformParms.Num() < int32(EXformParameter::COUNT)) + { + XformParms.Empty(); + for (int32 n = 0; n < int32(EXformParameter::COUNT); ++n) + { + UHoudiniHandleParameter* XformHandle = NewObject(this, UHoudiniHandleParameter::StaticClass()); + XformParms.Add(XformHandle); + } + } + + if (!RSTParm) + { + RSTParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); + } + + if (!RotOrderParm) + { + RotOrderParm = NewObject(this, UHoudiniHandleParameter::StaticClass()); + } +} + +bool +UHoudiniHandleComponent::CheckHandleValid() const +{ + if (XformParms.Num() < int32(EXformParameter::COUNT)) + return false; + + for (auto& XformParm : XformParms) + { + if (!XformParm) + return false; + } + + if (!RSTParm) + return false; + + if (!RotOrderParm) + return false; + + return true; +} + +FBox +UHoudiniHandleComponent::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + return BoxBounds + GetComponentLocation(); +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h index 62ea88ca9..d422e95ea 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniHandleComponent.h @@ -1,135 +1,135 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" - -#include "HoudiniHandleComponent.generated.h" - -class UHoudiniParameter; - -UENUM() -enum class EXformParameter : uint8 -{ - TX, TY, TZ, - RX, RY, RZ, - SX, SY, SZ, - COUNT -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniHandleParameter : public UObject -{ -public: - GENERATED_UCLASS_BODY() - - UPROPERTY() - UHoudiniParameter* AssetParameter; - - UPROPERTY() - int32 TupleIndex; - - - bool Bind( - float & OutValue, - const char * CmpName, - int32 InTupleIdx, - const FString & HandleParmName, - UHoudiniParameter* Parameter); - - bool Bind( - TSharedPtr & OutValue, - const char * CmpName, - int32 InTupleIdx, - const FString & HandleParmName, - UHoudiniParameter* Parameter); - - TSharedPtr Get(TSharedPtr DefaultValue) const; - - UHoudiniHandleParameter & operator=(float Value); - -}; - -UENUM() -enum class EHoudiniHandleType : uint8 -{ - Xform, - Bounder, - Unsupported -}; - -UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) -class HOUDINIENGINERUNTIME_API UHoudiniHandleComponent : public USceneComponent -{ -public: - - friend class UHoudiniAssetComponent; - - friend class FHoudiniHandleComponentVisualizer; - - GENERATED_UCLASS_BODY() - - virtual void Serialize(FArchive & Ar) override; - - FString GetHandleName() const { return HandleName; }; - EHoudiniHandleType GetHandleType() const { return HandleType; }; - - void SetHandleName(const FString& InHandleName) { HandleName = InHandleName; }; - void SetHandleType(const EHoudiniHandleType& InHandleType) { HandleType = InHandleType; }; - - // Equality, consider two handle equals if they have the same name, type, tuple size and disabled status - bool operator==(const UHoudiniHandleComponent& other) const - { - return (HandleType == other.HandleType && HandleName.Equals(other.HandleName)); - } - - bool Matches(const UHoudiniHandleComponent& other) const { return (*this == other); }; - - void InitializeHandleParameters(); - - bool CheckHandleValid() const; - - FBox GetBounds() const; - -public: - UPROPERTY() - TArray XformParms; - - UPROPERTY() - UHoudiniHandleParameter* RSTParm; - - UPROPERTY() - UHoudiniHandleParameter* RotOrderParm; - -private: - UPROPERTY() - EHoudiniHandleType HandleType; - - UPROPERTY() - FString HandleName; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" + +#include "HoudiniHandleComponent.generated.h" + +class UHoudiniParameter; + +UENUM() +enum class EXformParameter : uint8 +{ + TX, TY, TZ, + RX, RY, RZ, + SX, SY, SZ, + COUNT +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniHandleParameter : public UObject +{ +public: + GENERATED_UCLASS_BODY() + + UPROPERTY() + UHoudiniParameter* AssetParameter; + + UPROPERTY() + int32 TupleIndex; + + + bool Bind( + float & OutValue, + const char * CmpName, + int32 InTupleIdx, + const FString & HandleParmName, + UHoudiniParameter* Parameter); + + bool Bind( + TSharedPtr & OutValue, + const char * CmpName, + int32 InTupleIdx, + const FString & HandleParmName, + UHoudiniParameter* Parameter); + + TSharedPtr Get(TSharedPtr DefaultValue) const; + + UHoudiniHandleParameter & operator=(float Value); + +}; + +UENUM() +enum class EHoudiniHandleType : uint8 +{ + Xform, + Bounder, + Unsupported +}; + +UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) +class HOUDINIENGINERUNTIME_API UHoudiniHandleComponent : public USceneComponent +{ +public: + + friend class UHoudiniAssetComponent; + + friend class FHoudiniHandleComponentVisualizer; + + GENERATED_UCLASS_BODY() + + virtual void Serialize(FArchive & Ar) override; + + FString GetHandleName() const { return HandleName; }; + EHoudiniHandleType GetHandleType() const { return HandleType; }; + + void SetHandleName(const FString& InHandleName) { HandleName = InHandleName; }; + void SetHandleType(const EHoudiniHandleType& InHandleType) { HandleType = InHandleType; }; + + // Equality, consider two handle equals if they have the same name, type, tuple size and disabled status + bool operator==(const UHoudiniHandleComponent& other) const + { + return (HandleType == other.HandleType && HandleName.Equals(other.HandleName)); + } + + bool Matches(const UHoudiniHandleComponent& other) const { return (*this == other); }; + + void InitializeHandleParameters(); + + bool CheckHandleValid() const; + + FBox GetBounds() const; + +public: + UPROPERTY() + TArray XformParms; + + UPROPERTY() + UHoudiniHandleParameter* RSTParm; + + UPROPERTY() + UHoudiniHandleParameter* RotOrderParm; + +private: + UPROPERTY() + EHoudiniHandleType HandleType; + + UPROPERTY() + FString HandleName; + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp index 0623ac747..d53181865 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp @@ -1,2606 +1,2606 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInput.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniOutput.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniAsset.h" -#include "HoudiniGeoPartObject.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniAssetBlueprintComponent.h" - -#include "EngineUtils.h" -#include "Engine/Brush.h" -#include "Engine/Engine.h" -#include "Engine/DataTable.h" -#include "Model.h" -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "UObject/UObjectGlobals.h" -#include "FoliageType_InstancedStaticMesh.h" - -#include "Components/SplineComponent.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Landscape.h" - -#if WITH_EDITOR - -#include "Kismet2/BlueprintEditorUtils.h" -#include "Kismet2/KismetEditorUtilities.h" - -#endif - -// -UHoudiniInput::UHoudiniInput() - : Type(EHoudiniInputType::Invalid) - , PreviousType(EHoudiniInputType::Invalid) - , AssetNodeId(-1) - , InputNodeId(-1) - , InputIndex(0) - , ParmId(-1) - , bIsObjectPathParameter(false) - , bHasChanged(false) - , bPackBeforeMerge(false) - , bExportLODs(false) - , bExportSockets(false) - , bExportColliders(false) - , bCookOnCurveChanged(true) - , bStaticMeshChanged(false) - , bInputAssetConnectedInHoudini(false) - , DefaultCurveOffset(0.f) - , bAddRotAndScaleAttributesOnCurves(false) - , bIsWorldInputBoundSelector(false) - , bWorldInputBoundSelectorAutoUpdate(false) - , UnrealSplineResolution(50.0f) - , bUpdateInputLandscape(false) - , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) - , bLandscapeExportSelectionOnly(false) - , bLandscapeAutoSelectComponent(false) - , bLandscapeExportMaterials(false) - , bLandscapeExportLighting(false) - , bLandscapeExportNormalizedUVs(false) - , bLandscapeExportTileUVs(false) -{ - Name = TEXT(""); - Label = TEXT(""); - SetFlags(RF_Transactional); - - // Geometry inputs always have one null default object - GeometryInputObjects.Add(nullptr); - - KeepWorldTransform = GetDefaultXTransformType(); - - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; - - bAddRotAndScaleAttributesOnCurves = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bAddRotAndScaleAttributesOnCurves : false; -} - -void -UHoudiniInput::BeginDestroy() -{ - InvalidateData(); - - // DO NOT MANUALLY DESTROY OUR INPUT OBJECTS! - // This messes up unreal's Garbage collection and would cause crashes on duplication - - // Mark all our input objects for destruction - ForAllHoudiniInputObjectArrays([](TArray& ObjectArray) { - ObjectArray.Empty(); - }); - - Super::BeginDestroy(); -} - -#if WITH_EDITOR -void UHoudiniInput::PostEditUndo() -{ - Super::PostEditUndo(); - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!InputObjectsPtr) - return; - - MarkChanged(true); - bool bBlueprintStructureChanged = false; - - if (HasInputTypeChanged()) - { - // If the input type has changed on undo, previousType becomes new type - /* This does not work properly, see the corresponding part in FHoudiniInputDetails::AddInputTypeComboBox(...), after Transaction(... - ) - { - EHoudiniInputType NewType = PreviousType; - SetInputType(NewType); - } - */ - EHoudiniInputType Temp = Type; - Type = PreviousType; - PreviousType = EHoudiniInputType::Invalid; - - // If the undo action caused input type changing, treat it as a regular type changing - // after set up the new and prev types properly - SetInputType(Temp, bBlueprintStructureChanged); - } - else - { - if (Type == EHoudiniInputType::Asset) - { - // Mark the input asset object as changed, since only undo changing asset will get into here. - // The input array will be empty when undo adding asset (only support single asset input object in an input now) - for (auto & NextAssetInputObj : *InputObjectsPtr) - { - if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) - continue; - - NextAssetInputObj->MarkChanged(true); - } - } - - - if (Type == EHoudiniInputType::World) - { - if (WorldInputObjects.Num() == 0 && InputNodeId >= 0) - { - for (auto & NextNodeId : CreatedDataNodeIds) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NextNodeId, true); - } - - CreatedDataNodeIds.Empty(); - - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - InputNodeId = -1; - } - } - - if (Type == EHoudiniInputType::Curve) - { - if (PreviousType != EHoudiniInputType::Curve) - { - for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); - if (!SplineInput || SplineInput->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - USceneComponent* OuterComponent = Cast(GetOuter()); - - // Attach the new Houdini spline component to it's owner - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - HoudiniSplineComponent->MarkChanged(true); - } - return; - } - bool bUndoDelete = false; - bool bUndoInsert = false; - bool bUndoDeletedObjArrayEmptied = false; - - TArray< USceneComponent* > childActor; - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) - childActor = OuterHAC->GetAttachChildren(); - - // Undo delete input objects action - for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) - { - UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; - if (!InputObject || InputObject->IsPendingKill()) - continue; - - UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); - - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) - continue; - - UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - - if (!SplineComponent || SplineComponent->IsPendingKill()) - continue; - - // If the last change deleted this curve input, recreate this Houdini Spline input. - if (!SplineComponent->GetAttachParent()) - { - bUndoDelete = true; - - if (!bUndoDeletedObjArrayEmptied) - LastUndoDeletedInputs.Empty(); - - bUndoDeletedObjArrayEmptied = true; - - UHoudiniSplineComponent * ReconstructedSpline = NewObject( - GetOuter(), UHoudiniSplineComponent::StaticClass()); - - if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) - continue; - - ReconstructedSpline->SetFlags(RF_Transactional); - ReconstructedSpline->CopyHoudiniData(SplineComponent); - - UHoudiniInputObject * ReconstructedInputObject = UHoudiniInputHoudiniSplineComponent::Create( - ReconstructedSpline, GetOuter(), ReconstructedSpline->GetHoudiniSplineName()); - UHoudiniInputHoudiniSplineComponent *ReconstructedHoudiniSplineInput = (UHoudiniInputHoudiniSplineComponent*)ReconstructedInputObject; - (*InputObjectsPtr)[Index] = ReconstructedHoudiniSplineInput; - - ReconstructedSpline->RegisterComponent(); - ReconstructedSpline->SetFlags(RF_Transactional); - - CreateHoudiniSplineInput(ReconstructedHoudiniSplineInput, true, true, bBlueprintStructureChanged); - - // Cast the reconstructed Houdini Spline Input to a generic HoudiniInput object. - UHoudiniInputObject * ReconstructedHoudiniInput = Cast(ReconstructedHoudiniSplineInput); - - LastUndoDeletedInputs.Add(ReconstructedHoudiniInput); - // Reset the LastInsertedInputsArray for redoing this undo action. - } - } - - if (bUndoDelete) - return; - - // Undo insert input objects action - for (int Index = 0; Index < LastInsertedInputs.Num(); ++Index) - { - bUndoInsert = true; - UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - HoudiniSplineComponent->DestroyComponent(); - } - - if (bUndoInsert) - return; - - for (int Index = 0; Index < LastUndoDeletedInputs.Num(); ++Index) - { - UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; - - UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) - continue; - - UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) - continue; - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - - HoudiniSplineComponent->DestroyComponent(); - } - } - } - - if (bBlueprintStructureChanged) - { - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); - } - -} -#endif - - -FBox -UHoudiniInput::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - - switch (Type) - { - case EHoudiniInputType::Curve: - { - for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) - { - const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); - if (!CurInCurve || CurInCurve->IsPendingKill()) - continue; - - UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); - if (!CurCurve || CurCurve->IsPendingKill()) - continue; - - FBox CurCurveBound(ForceInitToZero); - for (auto & Trans : CurCurve->CurvePoints) - { - CurCurveBound += Trans.GetLocation(); - } - - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - - if (OuterHAC && !OuterHAC->IsPendingKill()) - BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); - } - } - break; - - case EHoudiniInputType::Asset: - { - for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) - { - UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); - if (!CurInAsset || CurInAsset->IsPendingKill()) - continue; - - UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) - continue; - - BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); - } - } - break; - - case EHoudiniInputType::World: - { - for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) - { - UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); - if (CurInActor && !CurInActor->IsPendingKill()) - { - AActor* Actor = CurInActor->GetActor(); - if (!Actor || Actor->IsPendingKill()) - continue; - - FVector Origin, Extent; - Actor->GetActorBounds(false, Origin, Extent); - - BoxBounds += FBox::BuildAABB(Origin, Extent); - } - else - { - // World Input now also support HoudiniAssets - UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); - if (CurInAsset && !CurInAsset->IsPendingKill()) - { - UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) - continue; - - BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); - continue; - } - } - } - } - break; - - case EHoudiniInputType::Landscape: - { - for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) - { - UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); - if (!CurInLandscape || CurInLandscape->IsPendingKill()) - continue; - - ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); - if (!CurLandscape || CurLandscape->IsPendingKill()) - continue; - - FVector Origin, Extent; - CurLandscape->GetActorBounds(false, Origin, Extent); - - BoxBounds += FBox::BuildAABB(Origin, Extent); - } - } - break; - - case EHoudiniInputType::Skeletal: - case EHoudiniInputType::Invalid: - default: - break; - } - - return BoxBounds; -} - -FString -UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) -{ - FString InputTypeStr; - switch (InInputType) - { - case EHoudiniInputType::Geometry: - { - InputTypeStr = TEXT("Geometry Input"); - } - break; - - case EHoudiniInputType::Asset: - { - InputTypeStr = TEXT("Asset Input"); - } - break; - - case EHoudiniInputType::Curve: - { - InputTypeStr = TEXT("Curve Input"); - } - break; - - case EHoudiniInputType::Landscape: - { - InputTypeStr = TEXT("Landscape Input"); - } - break; - - case EHoudiniInputType::World: - { - InputTypeStr = TEXT("World Outliner Input"); - } - break; - - case EHoudiniInputType::Skeletal: - { - InputTypeStr = TEXT("Skeletal Mesh Input"); - } - break; - } - - return InputTypeStr; -} - - -EHoudiniInputType -UHoudiniInput::StringToInputType(const FString& InInputTypeString) -{ - if (InInputTypeString.StartsWith(TEXT("Geometry"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Geometry; - } - else if (InInputTypeString.StartsWith(TEXT("Asset"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Asset; - } - else if (InInputTypeString.StartsWith(TEXT("Curve"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Curve; - } - else if (InInputTypeString.StartsWith(TEXT("Landscape"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Landscape; - } - else if (InInputTypeString.StartsWith(TEXT("World"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::World; - } - else if (InInputTypeString.StartsWith(TEXT("Skeletal"), ESearchCase::IgnoreCase)) - { - return EHoudiniInputType::Skeletal; - } - - return EHoudiniInputType::Invalid; -} - - -EHoudiniCurveType UHoudiniInput::StringToHoudiniCurveType(const FString& HoudiniCurveTypeString) -{ - if (HoudiniCurveTypeString.StartsWith(TEXT("Polygon"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Polygon; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Nurbs"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Nurbs; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Bezier"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Bezier; - } - else if (HoudiniCurveTypeString.StartsWith(TEXT("Points"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveType::Points; - } - - return EHoudiniCurveType::Invalid; -} - -EHoudiniCurveMethod UHoudiniInput::StringToHoudiniCurveMethod(const FString& HoudiniCurveMethodString) -{ - if (HoudiniCurveMethodString.StartsWith(TEXT("CVs"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::CVs; - } - else if (HoudiniCurveMethodString.StartsWith(TEXT("Breakpoints"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::Breakpoints; - } - - else if (HoudiniCurveMethodString.StartsWith(TEXT("Freehand"), ESearchCase::IgnoreCase)) - { - return EHoudiniCurveMethod::Freehand; - } - - return EHoudiniCurveMethod::Invalid; - -} - -// -void -UHoudiniInput::SetSOPInput(const int32& InInputIndex) -{ - // Set the input index - InputIndex = InInputIndex; - - // Invalidate objpath parameter - ParmId = -1; - bIsObjectPathParameter = false; -} - -void -UHoudiniInput::SetObjectPathParameter(const int32& InParmId) -{ - // Set as objpath parameter - ParmId = InParmId; - bIsObjectPathParameter = true; - - // Invalidate the geo input - InputIndex = -1; -} - -EHoudiniXformType -UHoudiniInput::GetDefaultXTransformType() -{ - switch (Type) - { - case EHoudiniInputType::Curve: - case EHoudiniInputType::Geometry: - case EHoudiniInputType::Skeletal: - return EHoudiniXformType::None; - case EHoudiniInputType::Asset: - case EHoudiniInputType::Landscape: - case EHoudiniInputType::World: - return EHoudiniXformType::IntoThisObject; - } - - return EHoudiniXformType::Auto; -} - -bool -UHoudiniInput::GetKeepWorldTransform() const -{ - bool bReturn = false; - switch (KeepWorldTransform) - { - case EHoudiniXformType::Auto: - { - // Return default values corresponding to the input type: - if (Type == EHoudiniInputType::Curve - || Type == EHoudiniInputType::Geometry - || Type == EHoudiniInputType::Skeletal ) - { - // NONE for Geo, Curve and skeletal mesh IN - bReturn = false; - } - else - { - // INTO THIS OBJECT for Asset, Landscape and World IN - bReturn = true; - } - break; - } - - case EHoudiniXformType::None: - { - bReturn = false; - break; - } - - case EHoudiniXformType::IntoThisObject: - { - bReturn = true; - break; - } - } - - return bReturn; -} - -void -UHoudiniInput::SetKeepWorldTransform(const bool& bInKeepWorldTransform) -{ - if (bInKeepWorldTransform) - { - KeepWorldTransform = EHoudiniXformType::IntoThisObject; - } - else - { - KeepWorldTransform = EHoudiniXformType::None; - } -} - -void -UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlueprintStructureModified) -{ - if (InInputType == Type) - return; - - SetPreviousInputType(Type); - - // Mark this input as changed - MarkChanged(true); - bOutBlueprintStructureModified = true; - - // Check previous input type - switch (PreviousType) - { - case EHoudiniInputType::Asset: - { - break; - } - - case EHoudiniInputType::Curve: - { - // detach the input curves from the asset component - if (GetNumberOfInputObjects() > 0) - { - for (UHoudiniInputObject * CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); - - if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); - - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - continue; - - HoudiniSplineComponent->Modify(); - - const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); - - if (bIsArchetype) - { -#if WITH_EDITOR - check(HoudiniSplineComponent->IsTemplate()); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); -#endif - } - else - { - AActor* OwningActor = HoudiniSplineComponent->GetOwner(); - check(OwningActor); - - FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - HoudiniSplineComponent->SetVisibility(false, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(false); - HoudiniSplineComponent->SetHiddenInGame(true, true); - - // This NodeId shouldn't be invalidated like this. If a spline component - // or curve input is no longer valid, the input object should be removed from the HoudinInput - // to get cleaned up properly. - // HoudiniSplineComponent->SetNodeId(-1); - HoudiniSplineComponent->MarkChanged(true); - } - - bOutBlueprintStructureModified = true; - } - } - break; - } - - case EHoudiniInputType::Geometry: - { - break; - } - - case EHoudiniInputType::Landscape: - { - TArray* InputObjectsArray = GetHoudiniInputObjectArray(PreviousType); - - if (!InputObjectsArray) - break; - - for (int32 Idx = 0; Idx < InputObjectsArray->Num(); ++Idx) - { - UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; - - if (!InputObj || InputObj->IsPendingKill()) - continue; - - UHoudiniInputLandscape* InputLandscape = Cast(InputObj); - - if (!InputLandscape || InputLandscape->IsPendingKill()) - continue; - - // do something? - } - - break; - } - - case EHoudiniInputType::Skeletal: - { - break; - } - - case EHoudiniInputType::World: - { - break; - } - - default: - break; - } - - - Type = InInputType; - - // TODO: NOPE, not needed - // Set keep world transform to default w.r.t to new input type. - //KeepWorldTransform = GetDefaultXTransformType(); - - // Check current input type - switch (InInputType) - { - case EHoudiniInputType::World: - case EHoudiniInputType::Asset: - { - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !bImportAsReference) - { - for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) - continue; - - UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) - continue; - - CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); - } - } - } - break; - - case EHoudiniInputType::Curve: - { - if (GetNumberOfInputObjects() == 0) - { - CreateNewCurveInputObject(bOutBlueprintStructureModified); - MarkChanged(true); - } - else - { - for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) - { - UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(CurrentInput); - if (!IsValid(SplineInput)) - continue; - - UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); - if (!IsValid(HoudiniSplineComponent)) - continue; - - HoudiniSplineComponent->Modify(); - - const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); - - if (bIsArchetype) - { -#if WITH_EDITOR - check(HoudiniSplineComponent->IsTemplate()); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", false); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); - FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); -#endif - } - else - { - // Attach the new Houdini spline component to it's owner - AActor* OwningActor = HoudiniSplineComponent->GetOwner(); - check(OwningActor); - USceneComponent* OuterComponent = Cast(GetOuter()); - HoudiniSplineComponent->RegisterComponent(); - HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->MarkChanged(true); - - } - - bOutBlueprintStructureModified = true; - } - } - } - break; - - case EHoudiniInputType::Geometry: - { - - } - break; - - case EHoudiniInputType::Landscape: - { - // Need to do anything on select? - } - break; - - case EHoudiniInputType::Skeletal: - { - } - break; - - default: - { - } - break; - } -} - -UHoudiniInputObject* -UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) -{ - if (CurveInputObjects.Num() > 0) - return nullptr; - - UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); - if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) - return nullptr; - - UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - // Default Houdini spline component input should not be visible at initialization - HoudiniSplineComponent->SetVisibility(true, true); - HoudiniSplineComponent->SetHoudiniSplineVisible(true); - HoudiniSplineComponent->SetHiddenInGame(false, true); - - CurveInputObjects.Add(NewCurveInputObject); - SetInputObjectsNumber(EHoudiniInputType::Curve, 1); - CurveInputObjects.SetNum(1); - - return NewCurveInputObject; -} - -void -UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) -{ - MarkDataUploadNeeded(bInChanged); - - // Mark all the objects from this input has changed so they upload themselves - - TSet InputTypes; - InputTypes.Add(Type); - InputTypes.Add(EHoudiniInputType::Curve); - - TArray* NewInputObjects = GetHoudiniInputObjectArray(Type); - if (NewInputObjects) - { - for (auto CurInputObject : *NewInputObjects) - { - if (CurInputObject && !CurInputObject->IsPendingKill()) - CurInputObject->MarkChanged(bInChanged); - } - } -} - -UHoudiniInput * UHoudiniInput::DuplicateAndCopyState(UObject * DestOuter, bool bInCanDeleteHoudiniNodes) -{ - UHoudiniInput* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); - - NewInput->CopyStateFrom(this, false, bInCanDeleteHoudiniNodes); - - return NewInput; -} - -void UHoudiniInput::CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes) -{ - - // Preserve the current input objects before the copy to ensure we don't lose - // access to input objects and have them end up in the garbage. - - TMap*> PrevInputObjectsMap; - - for(EHoudiniInputType InputType : HoudiniInputTypeList) - { - PrevInputObjectsMap.Add(InputType, GetHoudiniInputObjectArray(InputType)); - } - - // TArray PrevInputObjects; - // TArray* OldToInputObjects = GetHoudiniInputObjectArray(Type); - // if (OldToInputObjects) - // PrevInputObjects = *OldToInputObjects; - - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - } - - AssetNodeId = InInput->AssetNodeId; - InputNodeId = InInput->InputNodeId; - ParmId = InInput->ParmId; - bCanDeleteHoudiniNodes = bInCanDeleteHoudiniNodes; - - //if (bInCanDeleteHoudiniNodes) - //{ - // // Delete stale data nodes before they get overwritten. - // TSet NewNodeIds(InInput->CreatedDataNodeIds); - // for (int32 NodeId : CreatedDataNodeIds) - // { - // if (!NewNodeIds.Contains(NodeId)) - // { - // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); - // } - // } - //} - - CreatedDataNodeIds = InInput->CreatedDataNodeIds; - - // Important note: At this point the new object may still share objects with InInput. - // The CopyInputs() will properly duplicate inputs where necessary. - - // Copy states of Input Objects that correspond to the current type. - - for(auto& Entry : PrevInputObjectsMap) - { - EHoudiniInputType InputType = Entry.Key; - TArray* PrevInputObjects = Entry.Value; - TArray* ToInputObjects = GetHoudiniInputObjectArray(InputType); - TArray* FromInputObjects = InInput->GetHoudiniInputObjectArray(InputType); - - if (ToInputObjects && FromInputObjects) - { - *ToInputObjects = *PrevInputObjects; - CopyInputs(*ToInputObjects, *FromInputObjects, bInCanDeleteHoudiniNodes); - } - } - -} - -void UHoudiniInput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - if (!InputObject) - continue; - InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -void UHoudiniInput::InvalidateData() -{ - // If valid, mark our input node for deletion - if (InputNodeId >= 0) - { - // .. but if we're an asset input, don't delete the node as InputNodeId - // is set to the input HDA's node ID! - if (Type != EHoudiniInputType::Asset) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - } - - InputNodeId = -1; - } - - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - if (!InputObject) - continue; - InputObject->InvalidateData(); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - if (!InputObject) - continue; - - if (InputObject->IsA()) - { - // When the input object is a HoudiniAssetComponent, - // we need to be sure that this HDA node id is not in CreatedDataNodeIds - // We dont want to delete the input HDA node! - CreatedDataNodeIds.Remove(InputObject->InputNodeId); - } - - InputObject->InvalidateData(); - } - - if (bCanDeleteHoudiniNodes) - { - auto& HoudiniEngineRuntime = FHoudiniEngineRuntime::Get(); - for(int32 NodeId : CreatedDataNodeIds) - { - HoudiniEngineRuntime.MarkNodeIdAsPendingDelete(NodeId, true); - } - } - - CreatedDataNodeIds.Empty(); -} - -void UHoudiniInput::CopyInputs(TArray& ToInputObjects, TArray& FromInputObjects, bool bInCanDeleteHoudiniNodes) -{ - TSet StaleObjects(ToInputObjects); - - const int32 NumInputs = FromInputObjects.Num(); - UObject* TargetOuter = GetOuter(); - - ToInputObjects.SetNum(NumInputs); - - - for (int i = 0; i < NumInputs; i++) - { - UHoudiniInputObject* FromObject = FromInputObjects[i]; - UHoudiniInputObject* ToObject = ToInputObjects[i]; - - if (!FromObject) - { - ToInputObjects[i] = nullptr; - continue; - } - - if (ToObject) - { - bool IsValid = true; - // Is ToInput and FromInput the same or do we have to create a input object? - IsValid = IsValid && ToObject->Matches(*FromObject); - IsValid = IsValid && ToObject->GetOuter() == TargetOuter; - - if (!IsValid) - { - ToObject = nullptr; - } - } - - if (ToObject) - { - // We have an existing (matching) object. Copy the - // state from the incoming input. - StaleObjects.Remove(ToObject); - ToObject->CopyStateFrom(FromObject, true); - } - else - { - // We need to create a new input here. - ToObject = FromObject->DuplicateAndCopyState(TargetOuter); - ToInputObjects[i] = ToObject; - } - - ToObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); - } - - - for (UHoudiniInputObject* StaleInputObject : StaleObjects) - { - if (!StaleInputObject) - continue; - if (StaleInputObject->GetOuter() == this) - { - StaleInputObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); - } - } -} - - -UHoudiniInputHoudiniSplineComponent* -UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * FromHoudiniSplineInputComponent, const bool & bAttachToparent, const bool & bAppendToInputArray, bool& bOutBlueprintStructureModified) -{ - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = nullptr; - UHoudiniSplineComponent* HoudiniSplineComponent = nullptr; - - UObject* OuterObj = GetOuter(); - USceneComponent* OuterComp = Cast(GetOuter()); - bool bOuterIsTemplate = (OuterObj && OuterObj->IsTemplate()); - - if (!FromHoudiniSplineInputComponent) - { - // NOTE: If we're inside the Blueprint editor, the outer here is going to the be HAC component template. - check(OuterObj) - - // Create a default Houdini spline input if a null pointer is passed in. - FName HoudiniSplineName = MakeUniqueObjectName(OuterComp, UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); - - // Create a Houdini Input Object. - UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( - nullptr, OuterObj, HoudiniSplineName.ToString()); - - if (!NewInputObject || NewInputObject->IsPendingKill()) - return nullptr; - - HoudiniSplineInput = Cast(NewInputObject); - if (!HoudiniSplineInput) - return nullptr; - - HoudiniSplineComponent = NewObject( - HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - HoudiniSplineInput->Update(HoudiniSplineComponent); - - HoudiniSplineComponent->SetHoudiniSplineName(HoudiniSplineName.ToString()); - HoudiniSplineComponent->SetFlags(RF_Transactional); - - // Set the default position of curve to avoid overlapping. - HoudiniSplineComponent->SetOffset(DefaultCurveOffset); - DefaultCurveOffset += 100.f; - - if (!bOuterIsTemplate) - { - HoudiniSplineComponent->RegisterComponent(); - - // Attach the new Houdini spline component to it's owner. - if (bAttachToparent) - HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); - } - - //push the new input object to the array for new type. - if (bAppendToInputArray && Type == EHoudiniInputType::Curve) - GetHoudiniInputObjectArray(Type)->Add(NewInputObject); - -#if WITH_EDITOR - if (bOuterIsTemplate) - { - UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); - if (HAB) - { - UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (Blueprint) - { - TArray Components; - Components.Add(HoudiniSplineComponent); - - USCS_Node* HABNode = HAB->FindSCSNodeForTemplateComponentInClassHierarchy(HAB); - - // NOTE: FAddComponentsToBlueprintParams was introduced in 4.26 so for the sake of - // backwards compatibility, manually determine which SCSNode was added instead of - // relying on Params.OutNodes. - FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; - Params.OptionalNewRootNode = HABNode; - const TSet PreviousSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); - - FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, Params); - USCS_Node* NewNode = nullptr; - const TSet CurrentSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); - const TSet AddedNodes = CurrentSCSNodes.Difference(PreviousSCSNodes); - - if (AddedNodes.Num() > 0) - { - // Record Input / SCS node mapping - USCS_Node* SCSNode = AddedNodes.Array()[0]; - HAB->AddInputObjectMapping(NewInputObject->GetInputGuid(), SCSNode->VariableGuid); - SCSNode->ComponentTemplate->SetFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject); - } - - Blueprint->Modify(); - bOutBlueprintStructureModified = true; - } - } - } -#endif - } - else - { - // Otherwise, get the Houdini spline, and Houdini spline input from the argument. - HoudiniSplineInput = FromHoudiniSplineInputComponent; - HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - return nullptr; - - // Attach the new Houdini spline component to it's owner. - HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); - } - - // Mark the created UHoudiniSplineComponent as an input, and set its InputObject. - HoudiniSplineComponent->SetIsInputCurve(true); - - // HoudiniSplineComponent->SetInputObject(HoudiniSplineInput); - - // Set Houdini Spline Component bHasChanged and bNeedsToTrigerUpdate to true. - HoudiniSplineComponent->MarkChanged(true); - - - - return HoudiniSplineInput; -} - -void -UHoudiniInput::RemoveSplineFromInputObject( - UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, - bool& bOutBlueprintStructureModified) const -{ - if (!InHoudiniSplineInputObject) - return; - - UObject* OuterObj = GetOuter(); - const bool bOuterIsTemplate = OuterObj && OuterObj->IsTemplate(); - - if (bOuterIsTemplate) - { -#if WITH_EDITOR - // Find the SCS node that corresponds to this input and remove it. - UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); - if (HAB) - { - const UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); - UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); - if (Blueprint) - { - USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; - check(SCS); - FGuid SCSGuid; - if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) - { - // TODO: Move this SCS variable removal code to a reusable utility function. We're - // going to need to reuse this in a few other places too. - USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(SCSGuid); - if (SCSNode) - { - SCS->RemoveNodeAndPromoteChildren(SCSNode); - SCSNode->SetOnNameChanged(FSCSNodeNameChanged()); - bOutBlueprintStructureModified = true; - HAB->RemoveInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid); - - if (SCSNode->ComponentTemplate != nullptr) - { - const FName TemplateName = SCSNode->ComponentTemplate->GetFName(); - const FString RemovedName = SCSNode->GetVariableName().ToString() + TEXT("_REMOVED_") + FGuid::NewGuid().ToString(); - - SCSNode->ComponentTemplate->Modify(); - SCSNode->ComponentTemplate->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); - - TArray ArchetypeInstances; - auto DestroyArchetypeInstances = [&ArchetypeInstances, &RemovedName](UActorComponent* ComponentTemplate) - { - ComponentTemplate->GetArchetypeInstances(ArchetypeInstances); - for (UObject* ArchetypeInstance : ArchetypeInstances) - { - if (!ArchetypeInstance->HasAllFlags(RF_ArchetypeObject | RF_InheritableComponentTemplate)) - { - CastChecked(ArchetypeInstance)->DestroyComponent(); - ArchetypeInstance->Rename(*RemovedName, nullptr, REN_DontCreateRedirectors); - } - } - }; - - DestroyArchetypeInstances(SCSNode->ComponentTemplate); - - if (Blueprint) - { - // Children need to have their inherited component template instance of the component renamed out of the way as well - TArray ChildrenOfClass; - GetDerivedClasses(Blueprint->GeneratedClass, ChildrenOfClass); - - for (UClass* ChildClass : ChildrenOfClass) - { - UBlueprintGeneratedClass* BPChildClass = CastChecked(ChildClass); - - if (UActorComponent* Component = (UActorComponent*)FindObjectWithOuter(BPChildClass, UActorComponent::StaticClass(), TemplateName)) - { - Component->Modify(); - Component->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); - - DestroyArchetypeInstances(Component); - } - } - } - } - } - } // if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) - } // if (Blueprint) - } -#endif - } // if (bIsOuterTemplate) - else - { - UHoudiniSplineComponent* HoudiniSplineComponent = InHoudiniSplineInputObject->GetCurveComponent(); - if (HoudiniSplineComponent) - { - // detach the input curves from the asset component - //FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); - //HoudiniSplineComponent->DetachFromComponent(DetachTransRules); - // Destroy the Houdini Spline Component - //InputObjectsPtr->RemoveAt(AtIndex); - HoudiniSplineComponent->DestroyComponent(); - } - } - InHoudiniSplineInputObject->Update(nullptr); -} - - -TArray* -UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - return &GeometryInputObjects; - - case EHoudiniInputType::Curve: - return &CurveInputObjects; - - case EHoudiniInputType::Asset: - return &AssetInputObjects; - - case EHoudiniInputType::Landscape: - return &LandscapeInputObjects; - - case EHoudiniInputType::World: - return &WorldInputObjects; - - case EHoudiniInputType::Skeletal: - return &SkeletalInputObjects; - - default: - case EHoudiniInputType::Invalid: - return nullptr; - } - - return nullptr; -} - -TArray* -UHoudiniInput::GetBoundSelectorObjectArray() -{ - return &WorldInputBoundSelectorObjects; -} - -const TArray* -UHoudiniInput::GetBoundSelectorObjectArray() const -{ - return &WorldInputBoundSelectorObjects; -} - -const TArray* -UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const -{ - switch (InType) - { - case EHoudiniInputType::Geometry: - return &GeometryInputObjects; - - case EHoudiniInputType::Curve: - return &CurveInputObjects; - - case EHoudiniInputType::Asset: - return &AssetInputObjects; - - case EHoudiniInputType::Landscape: - return &LandscapeInputObjects; - - case EHoudiniInputType::World: - return &WorldInputObjects; - - case EHoudiniInputType::Skeletal: - return &SkeletalInputObjects; - - default: - case EHoudiniInputType::Invalid: - return nullptr; - } - - return nullptr; -} - -UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) -{ - return GetHoudiniInputObjectAt(Type, AtIndex); -} - -const UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) const -{ - const TArray* InputObjectsArray = GetHoudiniInputObjectArray(Type); - if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) - return nullptr; - - return (*InputObjectsArray)[AtIndex]; -} - -UHoudiniInputObject* -UHoudiniInput::GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsArray = GetHoudiniInputObjectArray(InType); - if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) - return nullptr; - - return (*InputObjectsArray)[AtIndex]; -} - -UObject* -UHoudiniInput::GetInputObjectAt(const int32& AtIndex) -{ - return GetInputObjectAt(Type, AtIndex); -} - -AActor* -UHoudiniInput::GetBoundSelectorObjectAt(const int32& AtIndex) -{ - if (!WorldInputBoundSelectorObjects.IsValidIndex(AtIndex)) - return nullptr; - - return WorldInputBoundSelectorObjects[AtIndex]; -} - -UObject* -UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); - if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) - return nullptr; - - return HoudiniInputObject->GetObject(); -} - -void -UHoudiniInput::InsertInputObjectAt(const int32& AtIndex) -{ - InsertInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - InputObjectsPtr->InsertDefaulted(AtIndex, 1); - MarkChanged(true); -} - -void -UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) -{ - DeleteInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (!InputObjectsPtr->IsValidIndex(AtIndex)) - return; - - bool bBlueprintStructureModified = false; - - if (Type == EHoudiniInputType::Asset) - { - // ... TODO operations for removing asset input type - } - else if (Type == EHoudiniInputType::Curve) - { - UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast((*InputObjectsPtr)[AtIndex]); - if (HoudiniSplineInputObject) - { - RemoveSplineFromInputObject(HoudiniSplineInputObject, bBlueprintStructureModified); - } - } - else if (Type == EHoudiniInputType::Geometry) - { - // ... TODO operations for removing geometry input type - } - else if (Type == EHoudiniInputType::Landscape) - { - // ... TODO operations for removing landscape input type - } - else if (Type == EHoudiniInputType::Skeletal) - { - // ... TODO operations for removing skeletal input type - } - else if (Type == EHoudiniInputType::World) - { - // ... TODO operations for removing world input type - } - else - { - // ... invalid input type - } - - MarkChanged(true); - - UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; - if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) - { - // Mark the input object's nodes for deletion - InputObjectToDelete->InvalidateData(); - - // If the deleted object wasnt null, trigger a re upload of the input data - MarkDataUploadNeeded(true); - } - - InputObjectsPtr->RemoveAt(AtIndex); - - // Delete the merge node when all the input objects are deleted. - if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); - InputNodeId = -1; - } - -#if WITH_EDITOR - if (bBlueprintStructureModified) - { - UActorComponent* Component = Cast(GetOuter()); - FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(Component); - } -#endif -} - -void -UHoudiniInput::DuplicateInputObjectAt(const int32& AtIndex) -{ - DuplicateInputObjectAt(Type, AtIndex); -} - -void -UHoudiniInput::DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (!InputObjectsPtr->IsValidIndex(AtIndex)) - return; - - // If the duplicated object is not null, trigger a re upload of the input data - bool bTriggerUpload = (*InputObjectsPtr)[AtIndex] != nullptr; - - // TODO: Duplicate the UHoudiniInputObject!! - UHoudiniInputObject* DuplicateInput = (*InputObjectsPtr)[AtIndex]; - InputObjectsPtr->Insert(DuplicateInput, AtIndex); - - MarkChanged(true); - - if (bTriggerUpload) - MarkDataUploadNeeded(true); -} - -int32 -UHoudiniInput::GetNumberOfInputObjects() -{ - return GetNumberOfInputObjects(Type); -} - -int32 -UHoudiniInput::GetNumberOfInputObjects(const EHoudiniInputType& InType) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return 0; - - return InputObjectsPtr->Num(); -} - -int32 -UHoudiniInput::GetNumberOfInputMeshes() -{ - return GetNumberOfInputMeshes(Type); -} - -int32 -UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return 0; - - // TODO? - // If geometry input, and we only have one null object, return 0 - int32 Num = InputObjectsPtr->Num(); - - // TODO: Fix BP properly! - // Special case for SM in BP: - // we need to add extra input objects store in BlueprintStaticMeshes - // Same thing for Actor InputObjects! - for (auto InputObj : *InputObjectsPtr) - { - if (!InputObj || InputObj->IsPendingKill()) - continue; - - UHoudiniInputStaticMesh* InputSM = Cast(InputObj); - if (InputSM && !InputSM->IsPendingKill()) - { - if (InputSM->BlueprintStaticMeshes.Num() > 0) - { - Num += (InputSM->BlueprintStaticMeshes.Num() - 1); - } - } - - UHoudiniInputActor* InputActor = Cast(InputObj); - if (InputActor && !InputActor->IsPendingKill()) - { - if (InputActor->GetActorComponents().Num() > 0) - { - Num += (InputActor->GetActorComponents().Num() - 1); - } - } - } - - return Num; -} - - -int32 -UHoudiniInput::GetNumberOfBoundSelectorObjects() const -{ - return WorldInputBoundSelectorObjects.Num(); -} - -void -UHoudiniInput::SetInputObjectAt(const int32& AtIndex, UObject* InObject) -{ - return SetInputObjectAt(Type, AtIndex, InObject); -} - -void -UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject) -{ - // Start by making sure we have the proper number of input objects - int32 NumIntObject = GetNumberOfInputObjects(InType); - if (NumIntObject <= AtIndex) - { - // We need to resize the array - SetInputObjectsNumber(InType, AtIndex + 1); - } - - UObject* CurrentInputObject = GetInputObjectAt(InType, AtIndex); - if (CurrentInputObject == InObject) - { - // Nothing to do - return; - } - - UHoudiniInputObject* CurrentInputObjectWrapper = GetHoudiniInputObjectAt(InType, AtIndex); - if (!InObject) - { - // We want to set the input object to null - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!ensure(InputObjectsPtr != nullptr && InputObjectsPtr->IsValidIndex(AtIndex))) - return; - - if (CurrentInputObjectWrapper) - { - // TODO: Check this case - // Do not destroy the input object manually! this messes up GC - //CurrentInputObjectWrapper->ConditionalBeginDestroy(); - MarkDataUploadNeeded(true); - } - - (*InputObjectsPtr)[AtIndex] = nullptr; - return; - } - - // Get the type of the previous and new input objects - EHoudiniInputObjectType NewObjectType = InObject ? UHoudiniInputObject::GetInputObjectTypeFromObject(InObject) : EHoudiniInputObjectType::Invalid; - EHoudiniInputObjectType CurrentObjectType = CurrentInputObjectWrapper ? CurrentInputObjectWrapper->Type : EHoudiniInputObjectType::Invalid; - - // See if we can reuse the existing InputObject - if (CurrentObjectType == NewObjectType && NewObjectType != EHoudiniInputObjectType::Invalid) - { - // The InputObjectTypes match, we can just update the existing object - CurrentInputObjectWrapper->Update(InObject); - CurrentInputObjectWrapper->MarkChanged(true); - return; - } - - // Destroy the existing input object - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!ensure(InputObjectsPtr)) - return; - - UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InObject, this, FString::FromInt(AtIndex + 1)); - if (!ensure(NewInputObject)) - return; - - // Mark that input object as changed so we know we need to update it - NewInputObject->MarkChanged(true); - if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) - { - // TODO: - // For some input type, we may have to copy some of the previous object's property before deleting it - - // Delete the previous object - CurrentInputObjectWrapper->MarkPendingKill(); - (*InputObjectsPtr)[AtIndex] = nullptr; - } - - // Update the input object array with the newly created input object - (*InputObjectsPtr)[AtIndex] = NewInputObject; -} - -void -UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount) -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); - if (!InputObjectsPtr) - return; - - if (InputObjectsPtr->Num() == InNewCount) - { - // Nothing to do - return; - } - - if (InNewCount > InputObjectsPtr->Num()) - { - // Simply add new default InputObjects - InputObjectsPtr->SetNum(InNewCount); - } - else - { - // TODO: Check this case! - // Do not destroy the input object themselves manually, - // destroy the input object's nodes and reduce the array's size - for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) - { - UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - if (bCanDeleteHoudiniNodes) - CurrentInputObject->InvalidateData(); - - /*/ - //FHoudiniInputTranslator::DestroyInput(Inputs[InputIdx]); - CurrentObject->ConditionalBeginDestroy(); - (*InputObjectsPtr)[InObjIdx] = nullptr; - */ - } - - // Decrease the input object array size - InputObjectsPtr->SetNum(InNewCount); - } - - // Also delete the input's merge node when all the input objects are deleted. - if (InNewCount == 0 && InputNodeId >= 0) - { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); - InputNodeId = -1; - } -} - -void -UHoudiniInput::SetBoundSelectorObjectsNumber(const int32& InNewCount) -{ - if (WorldInputBoundSelectorObjects.Num() == InNewCount) - { - // Nothing to do - return; - } - - if (InNewCount > WorldInputBoundSelectorObjects.Num()) - { - // Simply add new default InputObjects - WorldInputBoundSelectorObjects.SetNum(InNewCount); - } - else - { - /* - // TODO: Not Needed? - // Do not destroy the input object themselves manually, - // destroy the input object's nodes and reduce the array's size - for (int32 InObjIdx = WorldInputBoundSelectorObjects.Num() - 1; InObjIdx >= InNewCount; InObjIdx--) - { - UHoudiniInputObject* CurrentInputObject = WorldInputBoundSelectorObjects[InObjIdx]; - if (!CurrentInputObject) - continue; - - CurrentInputObject->MarkInputNodesForDeletion(); - } - */ - - // Decrease the input object array size - WorldInputBoundSelectorObjects.SetNum(InNewCount); - } -} - -void -UHoudiniInput::SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor) -{ - // Start by making sure we have the proper number of objects - int32 NumIntObject = GetNumberOfBoundSelectorObjects(); - if (NumIntObject <= AtIndex) - { - // We need to resize the array - SetBoundSelectorObjectsNumber(AtIndex + 1); - } - - AActor* CurrentActor = GetBoundSelectorObjectAt(AtIndex); - if (CurrentActor == InActor) - { - // Nothing to do - return; - } - - // Update the array with the new object - WorldInputBoundSelectorObjects[AtIndex] = InActor; -} - -// Helper function indicating what classes are supported by an input type -TArray -UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) -{ - TArray AllowedClasses; - switch (InInputType) - { - case EHoudiniInputType::Geometry: - AllowedClasses.Add(UStaticMesh::StaticClass()); - AllowedClasses.Add(USkeletalMesh::StaticClass()); - AllowedClasses.Add(UBlueprint::StaticClass()); - AllowedClasses.Add(UDataTable::StaticClass()); - AllowedClasses.Add(UFoliageType_InstancedStaticMesh::StaticClass()); - break; - - case EHoudiniInputType::Curve: - AllowedClasses.Add(USplineComponent::StaticClass()); - AllowedClasses.Add(UHoudiniSplineComponent::StaticClass()); - break; - - case EHoudiniInputType::Asset: - AllowedClasses.Add(UHoudiniAssetComponent::StaticClass()); - break; - - case EHoudiniInputType::Landscape: - AllowedClasses.Add(ALandscapeProxy::StaticClass()); - break; - - case EHoudiniInputType::World: - AllowedClasses.Add(AActor::StaticClass()); - break; - - case EHoudiniInputType::Skeletal: - AllowedClasses.Add(USkeletalMesh::StaticClass()); - break; - - default: - break; - } - - return AllowedClasses; -} - -// Helper function indicating if an object is supported by an input type -bool -UHoudiniInput::IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject) -{ - TArray AllowedClasses = GetAllowedClasses(InInputType); - for (auto CurClass : AllowedClasses) - { - if (InObject->IsA(CurClass)) - return true; - } - - return false; -} - -bool -UHoudiniInput::IsDataUploadNeeded() -{ - if (bDataUploadNeeded) - return true; - - return HasChanged(); -} - -// Indicates if this input has changed and should be updated -bool -UHoudiniInput::HasChanged() -{ - if (bHasChanged) - return true; - - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->HasChanged()) - return true; - } - - return false; -} - -bool -UHoudiniInput::IsTransformUploadNeeded() -{ - TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->HasTransformChanged()) - return true; - } - - return false; -} - -// Indicates if this input needs to trigger an update -bool -UHoudiniInput::NeedsToTriggerUpdate() -{ - if (bNeedsToTriggerUpdate) - return true; - - const TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); - if (!ensure(InputObjectsPtr)) - return false; - - for (auto CurrentInputObject : (*InputObjectsPtr)) - { - if (CurrentInputObject && CurrentInputObject->NeedsToTriggerUpdate()) - return true; - } - - return false; -} - -FString -UHoudiniInput::GetNodeBaseName() const -{ - UHoudiniAssetComponent* HAC = Cast(GetOuter()); - FString NodeBaseName = HAC ? HAC->GetDisplayName() : TEXT("HoudiniAsset"); - - // Unfortunately CreateInputNode always prefix with input_... - if (IsObjectPathParameter()) - NodeBaseName += TEXT("_") + GetName(); - else - NodeBaseName += TEXT("_input") + FString::FromInt(GetInputIndex()); - - return NodeBaseName; -} - -void -UHoudiniInput::OnTransformUIExpand(const int32& AtIndex) -{ -#if WITH_EDITORONLY_DATA - if (TransformUIExpanded.IsValidIndex(AtIndex)) - { - TransformUIExpanded[AtIndex] = !TransformUIExpanded[AtIndex]; - } - else - { - // We need to append values to the expanded array - for (int32 Index = TransformUIExpanded.Num(); Index <= AtIndex; Index++) - { - TransformUIExpanded.Add(Index == AtIndex ? true : false); - } - } -#endif -} - -bool -UHoudiniInput::IsTransformUIExpanded(const int32& AtIndex) -{ -#if WITH_EDITORONLY_DATA - return TransformUIExpanded.IsValidIndex(AtIndex) ? TransformUIExpanded[AtIndex] : false; -#else - return false; -#endif -}; - -FTransform* -UHoudiniInput::GetTransformOffset(const int32& AtIndex) -{ - UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); - if (InObject) - return &(InObject->Transform); - - return nullptr; -} - -const FTransform -UHoudiniInput::GetTransformOffset(const int32& AtIndex) const -{ - const UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); - if (InObject) - return InObject->Transform; - - return FTransform::Identity; -} - -TOptional -UHoudiniInput::GetPositionOffsetX(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().X; -} - -TOptional -UHoudiniInput::GetPositionOffsetY(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().Y; -} - -TOptional -UHoudiniInput::GetPositionOffsetZ(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetLocation().Z; -} - -TOptional -UHoudiniInput::GetRotationOffsetRoll(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Roll; -} - -TOptional -UHoudiniInput::GetRotationOffsetPitch(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Pitch; -} - -TOptional -UHoudiniInput::GetRotationOffsetYaw(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).Rotator().Yaw; -} - -TOptional -UHoudiniInput::GetScaleOffsetX(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().X; -} - -TOptional -UHoudiniInput::GetScaleOffsetY(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().Y; -} - -TOptional -UHoudiniInput::GetScaleOffsetZ(int32 AtIndex) const -{ - return GetTransformOffset(AtIndex).GetScale3D().Z; -} - -bool -UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = GetTransformOffset(AtIndex); - if (!Transform) - return false; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - if (Position[XYZIndex] == Value) - return false; - Position[XYZIndex] = Value; - Transform->SetLocation(Position); - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - if (Rotator.Roll == Value) - return false; - Rotator.Roll = Value; - break; - } - - case 1: - { - if (Rotator.Pitch == Value) - return false; - Rotator.Pitch = Value; - break; - } - - case 2: - { - if (Rotator.Yaw == Value) - return false; - Rotator.Yaw = Value; - break; - } - } - Transform->SetRotation(Rotator.Quaternion()); - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - if (Scale[XYZIndex] == Value) - return false; - - Scale[XYZIndex] = Value; - Transform->SetScale3D(Scale); - } - - MarkChanged(true); - bStaticMeshChanged = true; - - return true; -} - -void -UHoudiniInput::SetAddRotAndScaleAttributes(const bool& InValue) -{ - if (bAddRotAndScaleAttributesOnCurves == InValue) - return; - - bAddRotAndScaleAttributesOnCurves = InValue; - - // Mark all input obj as changed - MarkAllInputObjectsChanged(true); -} - -#if WITH_EDITOR -FText -UHoudiniInput::GetCurrentSelectionText() const -{ - FText CurrentSelectionText; - switch (Type) - { - case EHoudiniInputType::Landscape : - { - if (LandscapeInputObjects.Num() > 0) - { - UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; - - UHoudiniInputLandscape* InputLandscape = Cast(InputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) - return CurrentSelectionText; - - ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) - return CurrentSelectionText; - - CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); - } - } - break; - - case EHoudiniInputType::Asset : - { - if (AssetInputObjects.Num() > 0) - { - UHoudiniInputObject* InputObject = AssetInputObjects[0]; - - UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) - return CurrentSelectionText; - - UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!HAC || HAC->IsPendingKill()) - return CurrentSelectionText; - - UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) - return CurrentSelectionText; - - CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); - } - } - break; - - default: - break; - } - - return CurrentSelectionText; -} -#endif - -bool -UHoudiniInput::HasLandscapeExportTypeChanged () const -{ - if (Type != EHoudiniInputType::Landscape) - return false; - - return bLandscapeHasExportTypeChanged; -} - -void -UHoudiniInput::SetHasLandscapeExportTypeChanged(const bool InChanged) -{ - if (Type != EHoudiniInputType::Landscape) - return; - - bLandscapeHasExportTypeChanged = InChanged; -} - -bool -UHoudiniInput::GetUpdateInputLandscape() const -{ - if (Type != EHoudiniInputType::Landscape) - return false; - - return bUpdateInputLandscape; -} - -void -UHoudiniInput::SetUpdateInputLandscape(const bool bInUpdateInputLandcape) -{ - if (Type != EHoudiniInputType::Landscape) - return; - - bUpdateInputLandscape = bInUpdateInputLandcape; -} - - -bool -UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() -{ - // Dont do anything if we're not a World Input - if (Type != EHoudiniInputType::World) - return false; - - // Build an array of the current selection's bounds - TArray AllBBox; - for (auto CurrentActor : WorldInputBoundSelectorObjects) - { - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); - } - - // - // Select all actors in our bound selectors bounding boxes - // - - // Get our parent component/actor - USceneComponent* ParentComponent = Cast(GetOuter()); - AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; - - //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); - UWorld* MyWorld = GetWorld(); - TArray NewSelectedActors; - for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) - { - AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) - continue; - - // Check that actor is currently not selected - if (WorldInputBoundSelectorObjects.Contains(CurrentActor)) - continue; - - // Ignore the SkySpheres? - FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); - if (ClassName.Contains("BP_Sky_Sphere")) - continue; - - // Don't allow selection of ourselves. Bad things happen if we do. - if (ParentActor && (CurrentActor == ParentActor)) - continue; - - // For BrushActors, both the actor and its brush must be valid - ABrush* BrushActor = Cast(CurrentActor); - if (BrushActor) - { - if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) - continue; - } - - FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); - for (auto InBounds : AllBBox) - { - // Check if both actor's bounds intersects - if (!ActorBounds.Intersect(InBounds)) - continue; - - NewSelectedActors.Add(CurrentActor); - break; - } - } - - return UpdateWorldSelection(NewSelectedActors); -} - -bool -UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) -{ - TArray NewSelectedActors = InNewSelection; - - // Update our current selection with the new one - // Keep actors that are still selected, remove the one that are not selected anymore - bool bHasSelectionChanged = false; - for (int32 Idx = WorldInputObjects.Num() - 1; Idx >= 0; Idx--) - { - UHoudiniInputActor* InputActor = Cast(WorldInputObjects[Idx]); - AActor* CurActor = InputActor ? InputActor->GetActor() : nullptr; - - if (CurActor && NewSelectedActors.Contains(CurActor)) - { - // The actor is still selected, remove it from the new selection - NewSelectedActors.Remove(CurActor); - } - else - { - // That actor is no longer selected, remove itr from our current selection - DeleteInputObjectAt(EHoudiniInputType::World, Idx); - bHasSelectionChanged = true; - } - } - - if (NewSelectedActors.Num() > 0) - bHasSelectionChanged = true; - - // Then add the newly selected Actors - int32 InputObjectIdx = GetNumberOfInputObjects(EHoudiniInputType::World); - int32 NewInputObjectNumber = InputObjectIdx + NewSelectedActors.Num(); - SetInputObjectsNumber(EHoudiniInputType::World, NewInputObjectNumber); - for (const auto& CurActor : NewSelectedActors) - { - // Update the input objects from the valid selected actors array - SetInputObjectAt(InputObjectIdx++, CurActor); - } - - MarkChanged(bHasSelectionChanged); - - return bHasSelectionChanged; -} - - -bool -UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const -{ - if (!InObject || InObject->IsPendingKill()) - return false; - - // Returns true if the object is one of our input object for the given type - const TArray* ObjectArray = GetHoudiniInputObjectArray(InType); - if (!ObjectArray) - return false; - - for (auto& CurrentInputObject : (*ObjectArray)) - { - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) - continue; - - if (CurrentInputObject->GetObject() == InObject) - return true; - } - - return false; -} - -void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const -{ - for(UHoudiniInputObject* InputObject : GeometryInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : AssetInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : CurveInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) - { - Fn(InputObject); - } - - for(UHoudiniInputObject* InputObject : SkeletalInputObjects) - { - Fn(InputObject); - } -} - -TArray*> UHoudiniInput::GetAllObjectArrays() const -{ - return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; -} - -TArray*> UHoudiniInput::GetAllObjectArrays() -{ - return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; -} - -void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const -{ - TArray*> ObjectArrays = GetAllObjectArrays(); - for (const TArray* ObjectArrayPtr : ObjectArrays) - { - if (!ObjectArrayPtr) - continue; - Fn(*ObjectArrayPtr); - } -} - -void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) -{ - TArray*> ObjectArrays = GetAllObjectArrays(); - for (TArray* ObjectArrayPtr : ObjectArrays) - { - if (!ObjectArrayPtr) - continue; - Fn(*ObjectArrayPtr); - } -} - -void UHoudiniInput::GetAllHoudiniInputObjects(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddInputObject = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (InputObject) - OutObjects.Add(InputObject); - }; - ForAllHoudiniInputObjects(AddInputObject); -} - -void UHoudiniInput::ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const -{ - auto ProcessSceneComponent = [Fn](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - Fn(SceneComponentInput); - }; - ForAllHoudiniInputObjects(ProcessSceneComponent); -} - -void UHoudiniInput::GetAllHoudiniInputSceneComponents(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - OutObjects.Add(SceneComponentInput); - }; - ForAllHoudiniInputObjects(AddSceneComponent); -} - -void UHoudiniInput::GetAllHoudiniInputSplineComponents(TArray& OutObjects) const -{ - OutObjects.Empty(); - auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) - { - if (!InputObject) - return; - UHoudiniInputHoudiniSplineComponent* SceneComponentInput = Cast(InputObject); - if (!SceneComponentInput) - return; - OutObjects.Add(SceneComponentInput); - }; - ForAllHoudiniInputObjects(AddSceneComponent); -} - - -void UHoudiniInput::RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject) -{ - if (!InInputObject) - return; - - ForAllHoudiniInputObjectArrays([InInputObject](TArray& ObjectArray) { - ObjectArray.Remove(InInputObject); - }); - - return; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInput.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniOutput.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniAsset.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniAssetBlueprintComponent.h" + +#include "EngineUtils.h" +#include "Engine/Brush.h" +#include "Engine/Engine.h" +#include "Engine/DataTable.h" +#include "Model.h" +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "UObject/UObjectGlobals.h" +#include "FoliageType_InstancedStaticMesh.h" + +#include "Components/SplineComponent.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Landscape.h" + +#if WITH_EDITOR + +#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" + +#endif + +// +UHoudiniInput::UHoudiniInput() + : Type(EHoudiniInputType::Invalid) + , PreviousType(EHoudiniInputType::Invalid) + , AssetNodeId(-1) + , InputNodeId(-1) + , InputIndex(0) + , ParmId(-1) + , bIsObjectPathParameter(false) + , bHasChanged(false) + , bPackBeforeMerge(false) + , bExportLODs(false) + , bExportSockets(false) + , bExportColliders(false) + , bCookOnCurveChanged(true) + , bStaticMeshChanged(false) + , bInputAssetConnectedInHoudini(false) + , DefaultCurveOffset(0.f) + , bAddRotAndScaleAttributesOnCurves(false) + , bIsWorldInputBoundSelector(false) + , bWorldInputBoundSelectorAutoUpdate(false) + , UnrealSplineResolution(50.0f) + , bUpdateInputLandscape(false) + , LandscapeExportType(EHoudiniLandscapeExportType::Heightfield) + , bLandscapeExportSelectionOnly(false) + , bLandscapeAutoSelectComponent(false) + , bLandscapeExportMaterials(false) + , bLandscapeExportLighting(false) + , bLandscapeExportNormalizedUVs(false) + , bLandscapeExportTileUVs(false) +{ + Name = TEXT(""); + Label = TEXT(""); + SetFlags(RF_Transactional); + + // Geometry inputs always have one null default object + GeometryInputObjects.Add(nullptr); + + KeepWorldTransform = GetDefaultXTransformType(); + + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + UnrealSplineResolution = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->MarshallingSplineResolution : 50.0f; + + bAddRotAndScaleAttributesOnCurves = HoudiniRuntimeSettings ? HoudiniRuntimeSettings->bAddRotAndScaleAttributesOnCurves : false; +} + +void +UHoudiniInput::BeginDestroy() +{ + InvalidateData(); + + // DO NOT MANUALLY DESTROY OUR INPUT OBJECTS! + // This messes up unreal's Garbage collection and would cause crashes on duplication + + // Mark all our input objects for destruction + ForAllHoudiniInputObjectArrays([](TArray& ObjectArray) { + ObjectArray.Empty(); + }); + + Super::BeginDestroy(); +} + +#if WITH_EDITOR +void UHoudiniInput::PostEditUndo() +{ + Super::PostEditUndo(); + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!InputObjectsPtr) + return; + + MarkChanged(true); + bool bBlueprintStructureChanged = false; + + if (HasInputTypeChanged()) + { + // If the input type has changed on undo, previousType becomes new type + /* This does not work properly, see the corresponding part in FHoudiniInputDetails::AddInputTypeComboBox(...), after Transaction(... + ) + { + EHoudiniInputType NewType = PreviousType; + SetInputType(NewType); + } + */ + EHoudiniInputType Temp = Type; + Type = PreviousType; + PreviousType = EHoudiniInputType::Invalid; + + // If the undo action caused input type changing, treat it as a regular type changing + // after set up the new and prev types properly + SetInputType(Temp, bBlueprintStructureChanged); + } + else + { + if (Type == EHoudiniInputType::Asset) + { + // Mark the input asset object as changed, since only undo changing asset will get into here. + // The input array will be empty when undo adding asset (only support single asset input object in an input now) + for (auto & NextAssetInputObj : *InputObjectsPtr) + { + if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) + continue; + + NextAssetInputObj->MarkChanged(true); + } + } + + + if (Type == EHoudiniInputType::World) + { + if (WorldInputObjects.Num() == 0 && InputNodeId >= 0) + { + for (auto & NextNodeId : CreatedDataNodeIds) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NextNodeId, true); + } + + CreatedDataNodeIds.Empty(); + + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + InputNodeId = -1; + } + } + + if (Type == EHoudiniInputType::Curve) + { + if (PreviousType != EHoudiniInputType::Curve) + { + for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); + if (!SplineInput || SplineInput->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + USceneComponent* OuterComponent = Cast(GetOuter()); + + // Attach the new Houdini spline component to it's owner + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + HoudiniSplineComponent->MarkChanged(true); + } + return; + } + bool bUndoDelete = false; + bool bUndoInsert = false; + bool bUndoDeletedObjArrayEmptied = false; + + TArray< USceneComponent* > childActor; + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !OuterHAC->IsPendingKill()) + childActor = OuterHAC->GetAttachChildren(); + + // Undo delete input objects action + for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) + { + UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; + if (!InputObject || InputObject->IsPendingKill()) + continue; + + UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); + + if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + continue; + + UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); + + if (!SplineComponent || SplineComponent->IsPendingKill()) + continue; + + // If the last change deleted this curve input, recreate this Houdini Spline input. + if (!SplineComponent->GetAttachParent()) + { + bUndoDelete = true; + + if (!bUndoDeletedObjArrayEmptied) + LastUndoDeletedInputs.Empty(); + + bUndoDeletedObjArrayEmptied = true; + + UHoudiniSplineComponent * ReconstructedSpline = NewObject( + GetOuter(), UHoudiniSplineComponent::StaticClass()); + + if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) + continue; + + ReconstructedSpline->SetFlags(RF_Transactional); + ReconstructedSpline->CopyHoudiniData(SplineComponent); + + UHoudiniInputObject * ReconstructedInputObject = UHoudiniInputHoudiniSplineComponent::Create( + ReconstructedSpline, GetOuter(), ReconstructedSpline->GetHoudiniSplineName()); + UHoudiniInputHoudiniSplineComponent *ReconstructedHoudiniSplineInput = (UHoudiniInputHoudiniSplineComponent*)ReconstructedInputObject; + (*InputObjectsPtr)[Index] = ReconstructedHoudiniSplineInput; + + ReconstructedSpline->RegisterComponent(); + ReconstructedSpline->SetFlags(RF_Transactional); + + CreateHoudiniSplineInput(ReconstructedHoudiniSplineInput, true, true, bBlueprintStructureChanged); + + // Cast the reconstructed Houdini Spline Input to a generic HoudiniInput object. + UHoudiniInputObject * ReconstructedHoudiniInput = Cast(ReconstructedHoudiniSplineInput); + + LastUndoDeletedInputs.Add(ReconstructedHoudiniInput); + // Reset the LastInsertedInputsArray for redoing this undo action. + } + } + + if (bUndoDelete) + return; + + // Undo insert input objects action + for (int Index = 0; Index < LastInsertedInputs.Num(); ++Index) + { + bUndoInsert = true; + UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; + if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + HoudiniSplineComponent->DestroyComponent(); + } + + if (bUndoInsert) + return; + + for (int Index = 0; Index < LastUndoDeletedInputs.Num(); ++Index) + { + UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; + + UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); + if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + continue; + + UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); + if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) + continue; + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + + HoudiniSplineComponent->DestroyComponent(); + } + } + } + + if (bBlueprintStructureChanged) + { + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(OuterHAC); + } + +} +#endif + + +FBox +UHoudiniInput::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + + switch (Type) + { + case EHoudiniInputType::Curve: + { + for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) + { + const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); + if (!CurInCurve || CurInCurve->IsPendingKill()) + continue; + + UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); + if (!CurCurve || CurCurve->IsPendingKill()) + continue; + + FBox CurCurveBound(ForceInitToZero); + for (auto & Trans : CurCurve->CurvePoints) + { + CurCurveBound += Trans.GetLocation(); + } + + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + + if (OuterHAC && !OuterHAC->IsPendingKill()) + BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); + } + } + break; + + case EHoudiniInputType::Asset: + { + for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) + { + UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); + if (!CurInAsset || CurInAsset->IsPendingKill()) + continue; + + UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); + if (!CurInHAC || CurInHAC->IsPendingKill()) + continue; + + BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); + } + } + break; + + case EHoudiniInputType::World: + { + for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) + { + UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); + if (CurInActor && !CurInActor->IsPendingKill()) + { + AActor* Actor = CurInActor->GetActor(); + if (!Actor || Actor->IsPendingKill()) + continue; + + FVector Origin, Extent; + Actor->GetActorBounds(false, Origin, Extent); + + BoxBounds += FBox::BuildAABB(Origin, Extent); + } + else + { + // World Input now also support HoudiniAssets + UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); + if (CurInAsset && !CurInAsset->IsPendingKill()) + { + UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); + if (!CurInHAC || CurInHAC->IsPendingKill()) + continue; + + BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); + continue; + } + } + } + } + break; + + case EHoudiniInputType::Landscape: + { + for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) + { + UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); + if (!CurInLandscape || CurInLandscape->IsPendingKill()) + continue; + + ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); + if (!CurLandscape || CurLandscape->IsPendingKill()) + continue; + + FVector Origin, Extent; + CurLandscape->GetActorBounds(false, Origin, Extent); + + BoxBounds += FBox::BuildAABB(Origin, Extent); + } + } + break; + + case EHoudiniInputType::Skeletal: + case EHoudiniInputType::Invalid: + default: + break; + } + + return BoxBounds; +} + +FString +UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) +{ + FString InputTypeStr; + switch (InInputType) + { + case EHoudiniInputType::Geometry: + { + InputTypeStr = TEXT("Geometry Input"); + } + break; + + case EHoudiniInputType::Asset: + { + InputTypeStr = TEXT("Asset Input"); + } + break; + + case EHoudiniInputType::Curve: + { + InputTypeStr = TEXT("Curve Input"); + } + break; + + case EHoudiniInputType::Landscape: + { + InputTypeStr = TEXT("Landscape Input"); + } + break; + + case EHoudiniInputType::World: + { + InputTypeStr = TEXT("World Outliner Input"); + } + break; + + case EHoudiniInputType::Skeletal: + { + InputTypeStr = TEXT("Skeletal Mesh Input"); + } + break; + } + + return InputTypeStr; +} + + +EHoudiniInputType +UHoudiniInput::StringToInputType(const FString& InInputTypeString) +{ + if (InInputTypeString.StartsWith(TEXT("Geometry"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Geometry; + } + else if (InInputTypeString.StartsWith(TEXT("Asset"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Asset; + } + else if (InInputTypeString.StartsWith(TEXT("Curve"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Curve; + } + else if (InInputTypeString.StartsWith(TEXT("Landscape"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Landscape; + } + else if (InInputTypeString.StartsWith(TEXT("World"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::World; + } + else if (InInputTypeString.StartsWith(TEXT("Skeletal"), ESearchCase::IgnoreCase)) + { + return EHoudiniInputType::Skeletal; + } + + return EHoudiniInputType::Invalid; +} + + +EHoudiniCurveType UHoudiniInput::StringToHoudiniCurveType(const FString& HoudiniCurveTypeString) +{ + if (HoudiniCurveTypeString.StartsWith(TEXT("Polygon"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Polygon; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Nurbs"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Nurbs; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Bezier"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Bezier; + } + else if (HoudiniCurveTypeString.StartsWith(TEXT("Points"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveType::Points; + } + + return EHoudiniCurveType::Invalid; +} + +EHoudiniCurveMethod UHoudiniInput::StringToHoudiniCurveMethod(const FString& HoudiniCurveMethodString) +{ + if (HoudiniCurveMethodString.StartsWith(TEXT("CVs"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::CVs; + } + else if (HoudiniCurveMethodString.StartsWith(TEXT("Breakpoints"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::Breakpoints; + } + + else if (HoudiniCurveMethodString.StartsWith(TEXT("Freehand"), ESearchCase::IgnoreCase)) + { + return EHoudiniCurveMethod::Freehand; + } + + return EHoudiniCurveMethod::Invalid; + +} + +// +void +UHoudiniInput::SetSOPInput(const int32& InInputIndex) +{ + // Set the input index + InputIndex = InInputIndex; + + // Invalidate objpath parameter + ParmId = -1; + bIsObjectPathParameter = false; +} + +void +UHoudiniInput::SetObjectPathParameter(const int32& InParmId) +{ + // Set as objpath parameter + ParmId = InParmId; + bIsObjectPathParameter = true; + + // Invalidate the geo input + InputIndex = -1; +} + +EHoudiniXformType +UHoudiniInput::GetDefaultXTransformType() +{ + switch (Type) + { + case EHoudiniInputType::Curve: + case EHoudiniInputType::Geometry: + case EHoudiniInputType::Skeletal: + return EHoudiniXformType::None; + case EHoudiniInputType::Asset: + case EHoudiniInputType::Landscape: + case EHoudiniInputType::World: + return EHoudiniXformType::IntoThisObject; + } + + return EHoudiniXformType::Auto; +} + +bool +UHoudiniInput::GetKeepWorldTransform() const +{ + bool bReturn = false; + switch (KeepWorldTransform) + { + case EHoudiniXformType::Auto: + { + // Return default values corresponding to the input type: + if (Type == EHoudiniInputType::Curve + || Type == EHoudiniInputType::Geometry + || Type == EHoudiniInputType::Skeletal ) + { + // NONE for Geo, Curve and skeletal mesh IN + bReturn = false; + } + else + { + // INTO THIS OBJECT for Asset, Landscape and World IN + bReturn = true; + } + break; + } + + case EHoudiniXformType::None: + { + bReturn = false; + break; + } + + case EHoudiniXformType::IntoThisObject: + { + bReturn = true; + break; + } + } + + return bReturn; +} + +void +UHoudiniInput::SetKeepWorldTransform(const bool& bInKeepWorldTransform) +{ + if (bInKeepWorldTransform) + { + KeepWorldTransform = EHoudiniXformType::IntoThisObject; + } + else + { + KeepWorldTransform = EHoudiniXformType::None; + } +} + +void +UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlueprintStructureModified) +{ + if (InInputType == Type) + return; + + SetPreviousInputType(Type); + + // Mark this input as changed + MarkChanged(true); + bOutBlueprintStructureModified = true; + + // Check previous input type + switch (PreviousType) + { + case EHoudiniInputType::Asset: + { + break; + } + + case EHoudiniInputType::Curve: + { + // detach the input curves from the asset component + if (GetNumberOfInputObjects() > 0) + { + for (UHoudiniInputObject * CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); + + if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); + + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + continue; + + HoudiniSplineComponent->Modify(); + + const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); + + if (bIsArchetype) + { +#if WITH_EDITOR + check(HoudiniSplineComponent->IsTemplate()); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); +#endif + } + else + { + AActor* OwningActor = HoudiniSplineComponent->GetOwner(); + check(OwningActor); + + FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + HoudiniSplineComponent->SetVisibility(false, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(false); + HoudiniSplineComponent->SetHiddenInGame(true, true); + + // This NodeId shouldn't be invalidated like this. If a spline component + // or curve input is no longer valid, the input object should be removed from the HoudinInput + // to get cleaned up properly. + // HoudiniSplineComponent->SetNodeId(-1); + HoudiniSplineComponent->MarkChanged(true); + } + + bOutBlueprintStructureModified = true; + } + } + break; + } + + case EHoudiniInputType::Geometry: + { + break; + } + + case EHoudiniInputType::Landscape: + { + TArray* InputObjectsArray = GetHoudiniInputObjectArray(PreviousType); + + if (!InputObjectsArray) + break; + + for (int32 Idx = 0; Idx < InputObjectsArray->Num(); ++Idx) + { + UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; + + if (!InputObj || InputObj->IsPendingKill()) + continue; + + UHoudiniInputLandscape* InputLandscape = Cast(InputObj); + + if (!InputLandscape || InputLandscape->IsPendingKill()) + continue; + + // do something? + } + + break; + } + + case EHoudiniInputType::Skeletal: + { + break; + } + + case EHoudiniInputType::World: + { + break; + } + + default: + break; + } + + + Type = InInputType; + + // TODO: NOPE, not needed + // Set keep world transform to default w.r.t to new input type. + //KeepWorldTransform = GetDefaultXTransformType(); + + // Check current input type + switch (InInputType) + { + case EHoudiniInputType::World: + case EHoudiniInputType::Asset: + { + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !bImportAsReference) + { + for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); + if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + continue; + + UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); + if (!CurrentHAC || CurrentHAC->IsPendingKill()) + continue; + + CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); + } + } + } + break; + + case EHoudiniInputType::Curve: + { + if (GetNumberOfInputObjects() == 0) + { + CreateNewCurveInputObject(bOutBlueprintStructureModified); + MarkChanged(true); + } + else + { + for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) + { + UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(CurrentInput); + if (!IsValid(SplineInput)) + continue; + + UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); + if (!IsValid(HoudiniSplineComponent)) + continue; + + HoudiniSplineComponent->Modify(); + + const bool bIsArchetype = HoudiniSplineComponent->HasAnyFlags(RF_ArchetypeObject|RF_ClassDefaultObject); + + if (bIsArchetype) + { +#if WITH_EDITOR + check(HoudiniSplineComponent->IsTemplate()); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bVisible", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHiddenInGame", false); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bIsHoudiniSplineVisible", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bHasChanged", true); + FHoudiniEngineRuntimeUtils::SetTemplatePropertyValue(HoudiniSplineComponent, "bNeedsToTriggerUpdate", true); +#endif + } + else + { + // Attach the new Houdini spline component to it's owner + AActor* OwningActor = HoudiniSplineComponent->GetOwner(); + check(OwningActor); + USceneComponent* OuterComponent = Cast(GetOuter()); + HoudiniSplineComponent->RegisterComponent(); + HoudiniSplineComponent->AttachToComponent(OuterComponent, FAttachmentTransformRules::KeepRelativeTransform); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->MarkChanged(true); + + } + + bOutBlueprintStructureModified = true; + } + } + } + break; + + case EHoudiniInputType::Geometry: + { + + } + break; + + case EHoudiniInputType::Landscape: + { + // Need to do anything on select? + } + break; + + case EHoudiniInputType::Skeletal: + { + } + break; + + default: + { + } + break; + } +} + +UHoudiniInputObject* +UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) +{ + if (CurveInputObjects.Num() > 0) + return nullptr; + + UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); + if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) + return nullptr; + + UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + // Default Houdini spline component input should not be visible at initialization + HoudiniSplineComponent->SetVisibility(true, true); + HoudiniSplineComponent->SetHoudiniSplineVisible(true); + HoudiniSplineComponent->SetHiddenInGame(false, true); + + CurveInputObjects.Add(NewCurveInputObject); + SetInputObjectsNumber(EHoudiniInputType::Curve, 1); + CurveInputObjects.SetNum(1); + + return NewCurveInputObject; +} + +void +UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) +{ + MarkDataUploadNeeded(bInChanged); + + // Mark all the objects from this input has changed so they upload themselves + + TSet InputTypes; + InputTypes.Add(Type); + InputTypes.Add(EHoudiniInputType::Curve); + + TArray* NewInputObjects = GetHoudiniInputObjectArray(Type); + if (NewInputObjects) + { + for (auto CurInputObject : *NewInputObjects) + { + if (CurInputObject && !CurInputObject->IsPendingKill()) + CurInputObject->MarkChanged(bInChanged); + } + } +} + +UHoudiniInput * UHoudiniInput::DuplicateAndCopyState(UObject * DestOuter, bool bInCanDeleteHoudiniNodes) +{ + UHoudiniInput* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); + + NewInput->CopyStateFrom(this, false, bInCanDeleteHoudiniNodes); + + return NewInput; +} + +void UHoudiniInput::CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes) +{ + + // Preserve the current input objects before the copy to ensure we don't lose + // access to input objects and have them end up in the garbage. + + TMap*> PrevInputObjectsMap; + + for(EHoudiniInputType InputType : HoudiniInputTypeList) + { + PrevInputObjectsMap.Add(InputType, GetHoudiniInputObjectArray(InputType)); + } + + // TArray PrevInputObjects; + // TArray* OldToInputObjects = GetHoudiniInputObjectArray(Type); + // if (OldToInputObjects) + // PrevInputObjects = *OldToInputObjects; + + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + } + + AssetNodeId = InInput->AssetNodeId; + InputNodeId = InInput->InputNodeId; + ParmId = InInput->ParmId; + bCanDeleteHoudiniNodes = bInCanDeleteHoudiniNodes; + + //if (bInCanDeleteHoudiniNodes) + //{ + // // Delete stale data nodes before they get overwritten. + // TSet NewNodeIds(InInput->CreatedDataNodeIds); + // for (int32 NodeId : CreatedDataNodeIds) + // { + // if (!NewNodeIds.Contains(NodeId)) + // { + // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); + // } + // } + //} + + CreatedDataNodeIds = InInput->CreatedDataNodeIds; + + // Important note: At this point the new object may still share objects with InInput. + // The CopyInputs() will properly duplicate inputs where necessary. + + // Copy states of Input Objects that correspond to the current type. + + for(auto& Entry : PrevInputObjectsMap) + { + EHoudiniInputType InputType = Entry.Key; + TArray* PrevInputObjects = Entry.Value; + TArray* ToInputObjects = GetHoudiniInputObjectArray(InputType); + TArray* FromInputObjects = InInput->GetHoudiniInputObjectArray(InputType); + + if (ToInputObjects && FromInputObjects) + { + *ToInputObjects = *PrevInputObjects; + CopyInputs(*ToInputObjects, *FromInputObjects, bInCanDeleteHoudiniNodes); + } + } + +} + +void UHoudiniInput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + if (!InputObject) + continue; + InputObject->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +void UHoudiniInput::InvalidateData() +{ + // If valid, mark our input node for deletion + if (InputNodeId >= 0) + { + // .. but if we're an asset input, don't delete the node as InputNodeId + // is set to the input HDA's node ID! + if (Type != EHoudiniInputType::Asset) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + } + + InputNodeId = -1; + } + + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + if (!InputObject) + continue; + InputObject->InvalidateData(); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + if (!InputObject) + continue; + + if (InputObject->IsA()) + { + // When the input object is a HoudiniAssetComponent, + // we need to be sure that this HDA node id is not in CreatedDataNodeIds + // We dont want to delete the input HDA node! + CreatedDataNodeIds.Remove(InputObject->InputNodeId); + } + + InputObject->InvalidateData(); + } + + if (bCanDeleteHoudiniNodes) + { + auto& HoudiniEngineRuntime = FHoudiniEngineRuntime::Get(); + for(int32 NodeId : CreatedDataNodeIds) + { + HoudiniEngineRuntime.MarkNodeIdAsPendingDelete(NodeId, true); + } + } + + CreatedDataNodeIds.Empty(); +} + +void UHoudiniInput::CopyInputs(TArray& ToInputObjects, TArray& FromInputObjects, bool bInCanDeleteHoudiniNodes) +{ + TSet StaleObjects(ToInputObjects); + + const int32 NumInputs = FromInputObjects.Num(); + UObject* TargetOuter = GetOuter(); + + ToInputObjects.SetNum(NumInputs); + + + for (int i = 0; i < NumInputs; i++) + { + UHoudiniInputObject* FromObject = FromInputObjects[i]; + UHoudiniInputObject* ToObject = ToInputObjects[i]; + + if (!FromObject) + { + ToInputObjects[i] = nullptr; + continue; + } + + if (ToObject) + { + bool IsValid = true; + // Is ToInput and FromInput the same or do we have to create a input object? + IsValid = IsValid && ToObject->Matches(*FromObject); + IsValid = IsValid && ToObject->GetOuter() == TargetOuter; + + if (!IsValid) + { + ToObject = nullptr; + } + } + + if (ToObject) + { + // We have an existing (matching) object. Copy the + // state from the incoming input. + StaleObjects.Remove(ToObject); + ToObject->CopyStateFrom(FromObject, true); + } + else + { + // We need to create a new input here. + ToObject = FromObject->DuplicateAndCopyState(TargetOuter); + ToInputObjects[i] = ToObject; + } + + ToObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); + } + + + for (UHoudiniInputObject* StaleInputObject : StaleObjects) + { + if (!StaleInputObject) + continue; + if (StaleInputObject->GetOuter() == this) + { + StaleInputObject->SetCanDeleteHoudiniNodes(bInCanDeleteHoudiniNodes); + } + } +} + + +UHoudiniInputHoudiniSplineComponent* +UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * FromHoudiniSplineInputComponent, const bool & bAttachToparent, const bool & bAppendToInputArray, bool& bOutBlueprintStructureModified) +{ + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInput = nullptr; + UHoudiniSplineComponent* HoudiniSplineComponent = nullptr; + + UObject* OuterObj = GetOuter(); + USceneComponent* OuterComp = Cast(GetOuter()); + bool bOuterIsTemplate = (OuterObj && OuterObj->IsTemplate()); + + if (!FromHoudiniSplineInputComponent) + { + // NOTE: If we're inside the Blueprint editor, the outer here is going to the be HAC component template. + check(OuterObj) + + // Create a default Houdini spline input if a null pointer is passed in. + FName HoudiniSplineName = MakeUniqueObjectName(OuterComp, UHoudiniSplineComponent::StaticClass(), TEXT("Houdini Spline")); + + // Create a Houdini Input Object. + UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( + nullptr, OuterObj, HoudiniSplineName.ToString()); + + if (!NewInputObject || NewInputObject->IsPendingKill()) + return nullptr; + + HoudiniSplineInput = Cast(NewInputObject); + if (!HoudiniSplineInput) + return nullptr; + + HoudiniSplineComponent = NewObject( + HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + HoudiniSplineInput->Update(HoudiniSplineComponent); + + HoudiniSplineComponent->SetHoudiniSplineName(HoudiniSplineName.ToString()); + HoudiniSplineComponent->SetFlags(RF_Transactional); + + // Set the default position of curve to avoid overlapping. + HoudiniSplineComponent->SetOffset(DefaultCurveOffset); + DefaultCurveOffset += 100.f; + + if (!bOuterIsTemplate) + { + HoudiniSplineComponent->RegisterComponent(); + + // Attach the new Houdini spline component to it's owner. + if (bAttachToparent) + HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); + } + + //push the new input object to the array for new type. + if (bAppendToInputArray && Type == EHoudiniInputType::Curve) + GetHoudiniInputObjectArray(Type)->Add(NewInputObject); + +#if WITH_EDITOR + if (bOuterIsTemplate) + { + UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); + if (HAB) + { + UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (Blueprint) + { + TArray Components; + Components.Add(HoudiniSplineComponent); + + USCS_Node* HABNode = HAB->FindSCSNodeForTemplateComponentInClassHierarchy(HAB); + + // NOTE: FAddComponentsToBlueprintParams was introduced in 4.26 so for the sake of + // backwards compatibility, manually determine which SCSNode was added instead of + // relying on Params.OutNodes. + FKismetEditorUtilities::FAddComponentsToBlueprintParams Params; + Params.OptionalNewRootNode = HABNode; + const TSet PreviousSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); + + FKismetEditorUtilities::AddComponentsToBlueprint(Blueprint, Components, Params); + USCS_Node* NewNode = nullptr; + const TSet CurrentSCSNodes(Blueprint->SimpleConstructionScript->GetAllNodes()); + const TSet AddedNodes = CurrentSCSNodes.Difference(PreviousSCSNodes); + + if (AddedNodes.Num() > 0) + { + // Record Input / SCS node mapping + USCS_Node* SCSNode = AddedNodes.Array()[0]; + HAB->AddInputObjectMapping(NewInputObject->GetInputGuid(), SCSNode->VariableGuid); + SCSNode->ComponentTemplate->SetFlags(RF_Public | RF_ArchetypeObject | RF_DefaultSubObject); + } + + Blueprint->Modify(); + bOutBlueprintStructureModified = true; + } + } + } +#endif + } + else + { + // Otherwise, get the Houdini spline, and Houdini spline input from the argument. + HoudiniSplineInput = FromHoudiniSplineInputComponent; + HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + return nullptr; + + // Attach the new Houdini spline component to it's owner. + HoudiniSplineComponent->AttachToComponent(OuterComp, FAttachmentTransformRules::KeepRelativeTransform); + } + + // Mark the created UHoudiniSplineComponent as an input, and set its InputObject. + HoudiniSplineComponent->SetIsInputCurve(true); + + // HoudiniSplineComponent->SetInputObject(HoudiniSplineInput); + + // Set Houdini Spline Component bHasChanged and bNeedsToTrigerUpdate to true. + HoudiniSplineComponent->MarkChanged(true); + + + + return HoudiniSplineInput; +} + +void +UHoudiniInput::RemoveSplineFromInputObject( + UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, + bool& bOutBlueprintStructureModified) const +{ + if (!InHoudiniSplineInputObject) + return; + + UObject* OuterObj = GetOuter(); + const bool bOuterIsTemplate = OuterObj && OuterObj->IsTemplate(); + + if (bOuterIsTemplate) + { +#if WITH_EDITOR + // Find the SCS node that corresponds to this input and remove it. + UHoudiniAssetBlueprintComponent* HAB = Cast(OuterObj); + if (HAB) + { + const UBlueprintGeneratedClass* BPGC = Cast(HAB->GetOuter()); + UBlueprint* Blueprint = Cast(BPGC->ClassGeneratedBy); + if (Blueprint) + { + USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; + check(SCS); + FGuid SCSGuid; + if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) + { + // TODO: Move this SCS variable removal code to a reusable utility function. We're + // going to need to reuse this in a few other places too. + USCS_Node* SCSNode = SCS->FindSCSNodeByGuid(SCSGuid); + if (SCSNode) + { + SCS->RemoveNodeAndPromoteChildren(SCSNode); + SCSNode->SetOnNameChanged(FSCSNodeNameChanged()); + bOutBlueprintStructureModified = true; + HAB->RemoveInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid); + + if (SCSNode->ComponentTemplate != nullptr) + { + const FName TemplateName = SCSNode->ComponentTemplate->GetFName(); + const FString RemovedName = SCSNode->GetVariableName().ToString() + TEXT("_REMOVED_") + FGuid::NewGuid().ToString(); + + SCSNode->ComponentTemplate->Modify(); + SCSNode->ComponentTemplate->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); + + TArray ArchetypeInstances; + auto DestroyArchetypeInstances = [&ArchetypeInstances, &RemovedName](UActorComponent* ComponentTemplate) + { + ComponentTemplate->GetArchetypeInstances(ArchetypeInstances); + for (UObject* ArchetypeInstance : ArchetypeInstances) + { + if (!ArchetypeInstance->HasAllFlags(RF_ArchetypeObject | RF_InheritableComponentTemplate)) + { + CastChecked(ArchetypeInstance)->DestroyComponent(); + ArchetypeInstance->Rename(*RemovedName, nullptr, REN_DontCreateRedirectors); + } + } + }; + + DestroyArchetypeInstances(SCSNode->ComponentTemplate); + + if (Blueprint) + { + // Children need to have their inherited component template instance of the component renamed out of the way as well + TArray ChildrenOfClass; + GetDerivedClasses(Blueprint->GeneratedClass, ChildrenOfClass); + + for (UClass* ChildClass : ChildrenOfClass) + { + UBlueprintGeneratedClass* BPChildClass = CastChecked(ChildClass); + + if (UActorComponent* Component = (UActorComponent*)FindObjectWithOuter(BPChildClass, UActorComponent::StaticClass(), TemplateName)) + { + Component->Modify(); + Component->Rename(*RemovedName, /*NewOuter =*/nullptr, REN_DontCreateRedirectors); + + DestroyArchetypeInstances(Component); + } + } + } + } + } + } // if (HAB->GetInputObjectSCSVariableGuid(InHoudiniSplineInputObject->Guid, SCSGuid)) + } // if (Blueprint) + } +#endif + } // if (bIsOuterTemplate) + else + { + UHoudiniSplineComponent* HoudiniSplineComponent = InHoudiniSplineInputObject->GetCurveComponent(); + if (HoudiniSplineComponent) + { + // detach the input curves from the asset component + //FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); + //HoudiniSplineComponent->DetachFromComponent(DetachTransRules); + // Destroy the Houdini Spline Component + //InputObjectsPtr->RemoveAt(AtIndex); + HoudiniSplineComponent->DestroyComponent(); + } + } + InHoudiniSplineInputObject->Update(nullptr); +} + + +TArray* +UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + return &GeometryInputObjects; + + case EHoudiniInputType::Curve: + return &CurveInputObjects; + + case EHoudiniInputType::Asset: + return &AssetInputObjects; + + case EHoudiniInputType::Landscape: + return &LandscapeInputObjects; + + case EHoudiniInputType::World: + return &WorldInputObjects; + + case EHoudiniInputType::Skeletal: + return &SkeletalInputObjects; + + default: + case EHoudiniInputType::Invalid: + return nullptr; + } + + return nullptr; +} + +TArray* +UHoudiniInput::GetBoundSelectorObjectArray() +{ + return &WorldInputBoundSelectorObjects; +} + +const TArray* +UHoudiniInput::GetBoundSelectorObjectArray() const +{ + return &WorldInputBoundSelectorObjects; +} + +const TArray* +UHoudiniInput::GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const +{ + switch (InType) + { + case EHoudiniInputType::Geometry: + return &GeometryInputObjects; + + case EHoudiniInputType::Curve: + return &CurveInputObjects; + + case EHoudiniInputType::Asset: + return &AssetInputObjects; + + case EHoudiniInputType::Landscape: + return &LandscapeInputObjects; + + case EHoudiniInputType::World: + return &WorldInputObjects; + + case EHoudiniInputType::Skeletal: + return &SkeletalInputObjects; + + default: + case EHoudiniInputType::Invalid: + return nullptr; + } + + return nullptr; +} + +UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) +{ + return GetHoudiniInputObjectAt(Type, AtIndex); +} + +const UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const int32& AtIndex) const +{ + const TArray* InputObjectsArray = GetHoudiniInputObjectArray(Type); + if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) + return nullptr; + + return (*InputObjectsArray)[AtIndex]; +} + +UHoudiniInputObject* +UHoudiniInput::GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsArray = GetHoudiniInputObjectArray(InType); + if (!InputObjectsArray || !InputObjectsArray->IsValidIndex(AtIndex)) + return nullptr; + + return (*InputObjectsArray)[AtIndex]; +} + +UObject* +UHoudiniInput::GetInputObjectAt(const int32& AtIndex) +{ + return GetInputObjectAt(Type, AtIndex); +} + +AActor* +UHoudiniInput::GetBoundSelectorObjectAt(const int32& AtIndex) +{ + if (!WorldInputBoundSelectorObjects.IsValidIndex(AtIndex)) + return nullptr; + + return WorldInputBoundSelectorObjects[AtIndex]; +} + +UObject* +UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); + if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) + return nullptr; + + return HoudiniInputObject->GetObject(); +} + +void +UHoudiniInput::InsertInputObjectAt(const int32& AtIndex) +{ + InsertInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + InputObjectsPtr->InsertDefaulted(AtIndex, 1); + MarkChanged(true); +} + +void +UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) +{ + DeleteInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (!InputObjectsPtr->IsValidIndex(AtIndex)) + return; + + bool bBlueprintStructureModified = false; + + if (Type == EHoudiniInputType::Asset) + { + // ... TODO operations for removing asset input type + } + else if (Type == EHoudiniInputType::Curve) + { + UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast((*InputObjectsPtr)[AtIndex]); + if (HoudiniSplineInputObject) + { + RemoveSplineFromInputObject(HoudiniSplineInputObject, bBlueprintStructureModified); + } + } + else if (Type == EHoudiniInputType::Geometry) + { + // ... TODO operations for removing geometry input type + } + else if (Type == EHoudiniInputType::Landscape) + { + // ... TODO operations for removing landscape input type + } + else if (Type == EHoudiniInputType::Skeletal) + { + // ... TODO operations for removing skeletal input type + } + else if (Type == EHoudiniInputType::World) + { + // ... TODO operations for removing world input type + } + else + { + // ... invalid input type + } + + MarkChanged(true); + + UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; + if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) + { + // Mark the input object's nodes for deletion + InputObjectToDelete->InvalidateData(); + + // If the deleted object wasnt null, trigger a re upload of the input data + MarkDataUploadNeeded(true); + } + + InputObjectsPtr->RemoveAt(AtIndex); + + // Delete the merge node when all the input objects are deleted. + if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); + InputNodeId = -1; + } + +#if WITH_EDITOR + if (bBlueprintStructureModified) + { + UActorComponent* Component = Cast(GetOuter()); + FHoudiniEngineRuntimeUtils::MarkBlueprintAsStructurallyModified(Component); + } +#endif +} + +void +UHoudiniInput::DuplicateInputObjectAt(const int32& AtIndex) +{ + DuplicateInputObjectAt(Type, AtIndex); +} + +void +UHoudiniInput::DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (!InputObjectsPtr->IsValidIndex(AtIndex)) + return; + + // If the duplicated object is not null, trigger a re upload of the input data + bool bTriggerUpload = (*InputObjectsPtr)[AtIndex] != nullptr; + + // TODO: Duplicate the UHoudiniInputObject!! + UHoudiniInputObject* DuplicateInput = (*InputObjectsPtr)[AtIndex]; + InputObjectsPtr->Insert(DuplicateInput, AtIndex); + + MarkChanged(true); + + if (bTriggerUpload) + MarkDataUploadNeeded(true); +} + +int32 +UHoudiniInput::GetNumberOfInputObjects() +{ + return GetNumberOfInputObjects(Type); +} + +int32 +UHoudiniInput::GetNumberOfInputObjects(const EHoudiniInputType& InType) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return 0; + + return InputObjectsPtr->Num(); +} + +int32 +UHoudiniInput::GetNumberOfInputMeshes() +{ + return GetNumberOfInputMeshes(Type); +} + +int32 +UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return 0; + + // TODO? + // If geometry input, and we only have one null object, return 0 + int32 Num = InputObjectsPtr->Num(); + + // TODO: Fix BP properly! + // Special case for SM in BP: + // we need to add extra input objects store in BlueprintStaticMeshes + // Same thing for Actor InputObjects! + for (auto InputObj : *InputObjectsPtr) + { + if (!InputObj || InputObj->IsPendingKill()) + continue; + + UHoudiniInputStaticMesh* InputSM = Cast(InputObj); + if (InputSM && !InputSM->IsPendingKill()) + { + if (InputSM->BlueprintStaticMeshes.Num() > 0) + { + Num += (InputSM->BlueprintStaticMeshes.Num() - 1); + } + } + + UHoudiniInputActor* InputActor = Cast(InputObj); + if (InputActor && !InputActor->IsPendingKill()) + { + if (InputActor->GetActorComponents().Num() > 0) + { + Num += (InputActor->GetActorComponents().Num() - 1); + } + } + } + + return Num; +} + + +int32 +UHoudiniInput::GetNumberOfBoundSelectorObjects() const +{ + return WorldInputBoundSelectorObjects.Num(); +} + +void +UHoudiniInput::SetInputObjectAt(const int32& AtIndex, UObject* InObject) +{ + return SetInputObjectAt(Type, AtIndex, InObject); +} + +void +UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject) +{ + // Start by making sure we have the proper number of input objects + int32 NumIntObject = GetNumberOfInputObjects(InType); + if (NumIntObject <= AtIndex) + { + // We need to resize the array + SetInputObjectsNumber(InType, AtIndex + 1); + } + + UObject* CurrentInputObject = GetInputObjectAt(InType, AtIndex); + if (CurrentInputObject == InObject) + { + // Nothing to do + return; + } + + UHoudiniInputObject* CurrentInputObjectWrapper = GetHoudiniInputObjectAt(InType, AtIndex); + if (!InObject) + { + // We want to set the input object to null + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!ensure(InputObjectsPtr != nullptr && InputObjectsPtr->IsValidIndex(AtIndex))) + return; + + if (CurrentInputObjectWrapper) + { + // TODO: Check this case + // Do not destroy the input object manually! this messes up GC + //CurrentInputObjectWrapper->ConditionalBeginDestroy(); + MarkDataUploadNeeded(true); + } + + (*InputObjectsPtr)[AtIndex] = nullptr; + return; + } + + // Get the type of the previous and new input objects + EHoudiniInputObjectType NewObjectType = InObject ? UHoudiniInputObject::GetInputObjectTypeFromObject(InObject) : EHoudiniInputObjectType::Invalid; + EHoudiniInputObjectType CurrentObjectType = CurrentInputObjectWrapper ? CurrentInputObjectWrapper->Type : EHoudiniInputObjectType::Invalid; + + // See if we can reuse the existing InputObject + if (CurrentObjectType == NewObjectType && NewObjectType != EHoudiniInputObjectType::Invalid) + { + // The InputObjectTypes match, we can just update the existing object + CurrentInputObjectWrapper->Update(InObject); + CurrentInputObjectWrapper->MarkChanged(true); + return; + } + + // Destroy the existing input object + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!ensure(InputObjectsPtr)) + return; + + UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InObject, this, FString::FromInt(AtIndex + 1)); + if (!ensure(NewInputObject)) + return; + + // Mark that input object as changed so we know we need to update it + NewInputObject->MarkChanged(true); + if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) + { + // TODO: + // For some input type, we may have to copy some of the previous object's property before deleting it + + // Delete the previous object + CurrentInputObjectWrapper->MarkPendingKill(); + (*InputObjectsPtr)[AtIndex] = nullptr; + } + + // Update the input object array with the newly created input object + (*InputObjectsPtr)[AtIndex] = NewInputObject; +} + +void +UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount) +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); + if (!InputObjectsPtr) + return; + + if (InputObjectsPtr->Num() == InNewCount) + { + // Nothing to do + return; + } + + if (InNewCount > InputObjectsPtr->Num()) + { + // Simply add new default InputObjects + InputObjectsPtr->SetNum(InNewCount); + } + else + { + // TODO: Check this case! + // Do not destroy the input object themselves manually, + // destroy the input object's nodes and reduce the array's size + for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) + { + UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + if (bCanDeleteHoudiniNodes) + CurrentInputObject->InvalidateData(); + + /*/ + //FHoudiniInputTranslator::DestroyInput(Inputs[InputIdx]); + CurrentObject->ConditionalBeginDestroy(); + (*InputObjectsPtr)[InObjIdx] = nullptr; + */ + } + + // Decrease the input object array size + InputObjectsPtr->SetNum(InNewCount); + } + + // Also delete the input's merge node when all the input objects are deleted. + if (InNewCount == 0 && InputNodeId >= 0) + { + if (bCanDeleteHoudiniNodes) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + InputNodeId = -1; + } +} + +void +UHoudiniInput::SetBoundSelectorObjectsNumber(const int32& InNewCount) +{ + if (WorldInputBoundSelectorObjects.Num() == InNewCount) + { + // Nothing to do + return; + } + + if (InNewCount > WorldInputBoundSelectorObjects.Num()) + { + // Simply add new default InputObjects + WorldInputBoundSelectorObjects.SetNum(InNewCount); + } + else + { + /* + // TODO: Not Needed? + // Do not destroy the input object themselves manually, + // destroy the input object's nodes and reduce the array's size + for (int32 InObjIdx = WorldInputBoundSelectorObjects.Num() - 1; InObjIdx >= InNewCount; InObjIdx--) + { + UHoudiniInputObject* CurrentInputObject = WorldInputBoundSelectorObjects[InObjIdx]; + if (!CurrentInputObject) + continue; + + CurrentInputObject->MarkInputNodesForDeletion(); + } + */ + + // Decrease the input object array size + WorldInputBoundSelectorObjects.SetNum(InNewCount); + } +} + +void +UHoudiniInput::SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor) +{ + // Start by making sure we have the proper number of objects + int32 NumIntObject = GetNumberOfBoundSelectorObjects(); + if (NumIntObject <= AtIndex) + { + // We need to resize the array + SetBoundSelectorObjectsNumber(AtIndex + 1); + } + + AActor* CurrentActor = GetBoundSelectorObjectAt(AtIndex); + if (CurrentActor == InActor) + { + // Nothing to do + return; + } + + // Update the array with the new object + WorldInputBoundSelectorObjects[AtIndex] = InActor; +} + +// Helper function indicating what classes are supported by an input type +TArray +UHoudiniInput::GetAllowedClasses(const EHoudiniInputType& InInputType) +{ + TArray AllowedClasses; + switch (InInputType) + { + case EHoudiniInputType::Geometry: + AllowedClasses.Add(UStaticMesh::StaticClass()); + AllowedClasses.Add(USkeletalMesh::StaticClass()); + AllowedClasses.Add(UBlueprint::StaticClass()); + AllowedClasses.Add(UDataTable::StaticClass()); + AllowedClasses.Add(UFoliageType_InstancedStaticMesh::StaticClass()); + break; + + case EHoudiniInputType::Curve: + AllowedClasses.Add(USplineComponent::StaticClass()); + AllowedClasses.Add(UHoudiniSplineComponent::StaticClass()); + break; + + case EHoudiniInputType::Asset: + AllowedClasses.Add(UHoudiniAssetComponent::StaticClass()); + break; + + case EHoudiniInputType::Landscape: + AllowedClasses.Add(ALandscapeProxy::StaticClass()); + break; + + case EHoudiniInputType::World: + AllowedClasses.Add(AActor::StaticClass()); + break; + + case EHoudiniInputType::Skeletal: + AllowedClasses.Add(USkeletalMesh::StaticClass()); + break; + + default: + break; + } + + return AllowedClasses; +} + +// Helper function indicating if an object is supported by an input type +bool +UHoudiniInput::IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject) +{ + TArray AllowedClasses = GetAllowedClasses(InInputType); + for (auto CurClass : AllowedClasses) + { + if (InObject->IsA(CurClass)) + return true; + } + + return false; +} + +bool +UHoudiniInput::IsDataUploadNeeded() +{ + if (bDataUploadNeeded) + return true; + + return HasChanged(); +} + +// Indicates if this input has changed and should be updated +bool +UHoudiniInput::HasChanged() +{ + if (bHasChanged) + return true; + + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->HasChanged()) + return true; + } + + return false; +} + +bool +UHoudiniInput::IsTransformUploadNeeded() +{ + TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->HasTransformChanged()) + return true; + } + + return false; +} + +// Indicates if this input needs to trigger an update +bool +UHoudiniInput::NeedsToTriggerUpdate() +{ + if (bNeedsToTriggerUpdate) + return true; + + const TArray* InputObjectsPtr = GetHoudiniInputObjectArray(Type); + if (!ensure(InputObjectsPtr)) + return false; + + for (auto CurrentInputObject : (*InputObjectsPtr)) + { + if (CurrentInputObject && CurrentInputObject->NeedsToTriggerUpdate()) + return true; + } + + return false; +} + +FString +UHoudiniInput::GetNodeBaseName() const +{ + UHoudiniAssetComponent* HAC = Cast(GetOuter()); + FString NodeBaseName = HAC ? HAC->GetDisplayName() : TEXT("HoudiniAsset"); + + // Unfortunately CreateInputNode always prefix with input_... + if (IsObjectPathParameter()) + NodeBaseName += TEXT("_") + GetName(); + else + NodeBaseName += TEXT("_input") + FString::FromInt(GetInputIndex()); + + return NodeBaseName; +} + +void +UHoudiniInput::OnTransformUIExpand(const int32& AtIndex) +{ +#if WITH_EDITORONLY_DATA + if (TransformUIExpanded.IsValidIndex(AtIndex)) + { + TransformUIExpanded[AtIndex] = !TransformUIExpanded[AtIndex]; + } + else + { + // We need to append values to the expanded array + for (int32 Index = TransformUIExpanded.Num(); Index <= AtIndex; Index++) + { + TransformUIExpanded.Add(Index == AtIndex ? true : false); + } + } +#endif +} + +bool +UHoudiniInput::IsTransformUIExpanded(const int32& AtIndex) +{ +#if WITH_EDITORONLY_DATA + return TransformUIExpanded.IsValidIndex(AtIndex) ? TransformUIExpanded[AtIndex] : false; +#else + return false; +#endif +}; + +FTransform* +UHoudiniInput::GetTransformOffset(const int32& AtIndex) +{ + UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); + if (InObject) + return &(InObject->Transform); + + return nullptr; +} + +const FTransform +UHoudiniInput::GetTransformOffset(const int32& AtIndex) const +{ + const UHoudiniInputObject* InObject = GetHoudiniInputObjectAt(AtIndex); + if (InObject) + return InObject->Transform; + + return FTransform::Identity; +} + +TOptional +UHoudiniInput::GetPositionOffsetX(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().X; +} + +TOptional +UHoudiniInput::GetPositionOffsetY(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().Y; +} + +TOptional +UHoudiniInput::GetPositionOffsetZ(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetLocation().Z; +} + +TOptional +UHoudiniInput::GetRotationOffsetRoll(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Roll; +} + +TOptional +UHoudiniInput::GetRotationOffsetPitch(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Pitch; +} + +TOptional +UHoudiniInput::GetRotationOffsetYaw(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).Rotator().Yaw; +} + +TOptional +UHoudiniInput::GetScaleOffsetX(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().X; +} + +TOptional +UHoudiniInput::GetScaleOffsetY(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().Y; +} + +TOptional +UHoudiniInput::GetScaleOffsetZ(int32 AtIndex) const +{ + return GetTransformOffset(AtIndex).GetScale3D().Z; +} + +bool +UHoudiniInput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = GetTransformOffset(AtIndex); + if (!Transform) + return false; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + if (Position[XYZIndex] == Value) + return false; + Position[XYZIndex] = Value; + Transform->SetLocation(Position); + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + if (Rotator.Roll == Value) + return false; + Rotator.Roll = Value; + break; + } + + case 1: + { + if (Rotator.Pitch == Value) + return false; + Rotator.Pitch = Value; + break; + } + + case 2: + { + if (Rotator.Yaw == Value) + return false; + Rotator.Yaw = Value; + break; + } + } + Transform->SetRotation(Rotator.Quaternion()); + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + if (Scale[XYZIndex] == Value) + return false; + + Scale[XYZIndex] = Value; + Transform->SetScale3D(Scale); + } + + MarkChanged(true); + bStaticMeshChanged = true; + + return true; +} + +void +UHoudiniInput::SetAddRotAndScaleAttributes(const bool& InValue) +{ + if (bAddRotAndScaleAttributesOnCurves == InValue) + return; + + bAddRotAndScaleAttributesOnCurves = InValue; + + // Mark all input obj as changed + MarkAllInputObjectsChanged(true); +} + +#if WITH_EDITOR +FText +UHoudiniInput::GetCurrentSelectionText() const +{ + FText CurrentSelectionText; + switch (Type) + { + case EHoudiniInputType::Landscape : + { + if (LandscapeInputObjects.Num() > 0) + { + UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; + + UHoudiniInputLandscape* InputLandscape = Cast(InputObject); + if (!InputLandscape || InputLandscape->IsPendingKill()) + return CurrentSelectionText; + + ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); + if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + return CurrentSelectionText; + + CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); + } + } + break; + + case EHoudiniInputType::Asset : + { + if (AssetInputObjects.Num() > 0) + { + UHoudiniInputObject* InputObject = AssetInputObjects[0]; + + UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); + if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + return CurrentSelectionText; + + UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); + if (!HAC || HAC->IsPendingKill()) + return CurrentSelectionText; + + UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); + if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + return CurrentSelectionText; + + CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); + } + } + break; + + default: + break; + } + + return CurrentSelectionText; +} +#endif + +bool +UHoudiniInput::HasLandscapeExportTypeChanged () const +{ + if (Type != EHoudiniInputType::Landscape) + return false; + + return bLandscapeHasExportTypeChanged; +} + +void +UHoudiniInput::SetHasLandscapeExportTypeChanged(const bool InChanged) +{ + if (Type != EHoudiniInputType::Landscape) + return; + + bLandscapeHasExportTypeChanged = InChanged; +} + +bool +UHoudiniInput::GetUpdateInputLandscape() const +{ + if (Type != EHoudiniInputType::Landscape) + return false; + + return bUpdateInputLandscape; +} + +void +UHoudiniInput::SetUpdateInputLandscape(const bool bInUpdateInputLandcape) +{ + if (Type != EHoudiniInputType::Landscape) + return; + + bUpdateInputLandscape = bInUpdateInputLandcape; +} + + +bool +UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() +{ + // Dont do anything if we're not a World Input + if (Type != EHoudiniInputType::World) + return false; + + // Build an array of the current selection's bounds + TArray AllBBox; + for (auto CurrentActor : WorldInputBoundSelectorObjects) + { + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); + } + + // + // Select all actors in our bound selectors bounding boxes + // + + // Get our parent component/actor + USceneComponent* ParentComponent = Cast(GetOuter()); + AActor* ParentActor = ParentComponent ? ParentComponent->GetOwner() : nullptr; + + //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); + UWorld* MyWorld = GetWorld(); + TArray NewSelectedActors; + for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) + { + AActor *CurrentActor = *ActorItr; + if (!CurrentActor || CurrentActor->IsPendingKill()) + continue; + + // Check that actor is currently not selected + if (WorldInputBoundSelectorObjects.Contains(CurrentActor)) + continue; + + // Ignore the SkySpheres? + FString ClassName = CurrentActor->GetClass() ? CurrentActor->GetClass()->GetName() : FString(); + if (ClassName.Contains("BP_Sky_Sphere")) + continue; + + // Don't allow selection of ourselves. Bad things happen if we do. + if (ParentActor && (CurrentActor == ParentActor)) + continue; + + // For BrushActors, both the actor and its brush must be valid + ABrush* BrushActor = Cast(CurrentActor); + if (BrushActor) + { + if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) + continue; + } + + FBox ActorBounds = CurrentActor->GetComponentsBoundingBox(true); + for (auto InBounds : AllBBox) + { + // Check if both actor's bounds intersects + if (!ActorBounds.Intersect(InBounds)) + continue; + + NewSelectedActors.Add(CurrentActor); + break; + } + } + + return UpdateWorldSelection(NewSelectedActors); +} + +bool +UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) +{ + TArray NewSelectedActors = InNewSelection; + + // Update our current selection with the new one + // Keep actors that are still selected, remove the one that are not selected anymore + bool bHasSelectionChanged = false; + for (int32 Idx = WorldInputObjects.Num() - 1; Idx >= 0; Idx--) + { + UHoudiniInputActor* InputActor = Cast(WorldInputObjects[Idx]); + AActor* CurActor = InputActor ? InputActor->GetActor() : nullptr; + + if (CurActor && NewSelectedActors.Contains(CurActor)) + { + // The actor is still selected, remove it from the new selection + NewSelectedActors.Remove(CurActor); + } + else + { + // That actor is no longer selected, remove itr from our current selection + DeleteInputObjectAt(EHoudiniInputType::World, Idx); + bHasSelectionChanged = true; + } + } + + if (NewSelectedActors.Num() > 0) + bHasSelectionChanged = true; + + // Then add the newly selected Actors + int32 InputObjectIdx = GetNumberOfInputObjects(EHoudiniInputType::World); + int32 NewInputObjectNumber = InputObjectIdx + NewSelectedActors.Num(); + SetInputObjectsNumber(EHoudiniInputType::World, NewInputObjectNumber); + for (const auto& CurActor : NewSelectedActors) + { + // Update the input objects from the valid selected actors array + SetInputObjectAt(InputObjectIdx++, CurActor); + } + + MarkChanged(bHasSelectionChanged); + + return bHasSelectionChanged; +} + + +bool +UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const +{ + if (!InObject || InObject->IsPendingKill()) + return false; + + // Returns true if the object is one of our input object for the given type + const TArray* ObjectArray = GetHoudiniInputObjectArray(InType); + if (!ObjectArray) + return false; + + for (auto& CurrentInputObject : (*ObjectArray)) + { + if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + continue; + + if (CurrentInputObject->GetObject() == InObject) + return true; + } + + return false; +} + +void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const +{ + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + Fn(InputObject); + } + + for(UHoudiniInputObject* InputObject : SkeletalInputObjects) + { + Fn(InputObject); + } +} + +TArray*> UHoudiniInput::GetAllObjectArrays() const +{ + return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; +} + +TArray*> UHoudiniInput::GetAllObjectArrays() +{ + return { &GeometryInputObjects, &CurveInputObjects, &WorldInputObjects, &SkeletalInputObjects, &LandscapeInputObjects, &AssetInputObjects }; +} + +void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const +{ + TArray*> ObjectArrays = GetAllObjectArrays(); + for (const TArray* ObjectArrayPtr : ObjectArrays) + { + if (!ObjectArrayPtr) + continue; + Fn(*ObjectArrayPtr); + } +} + +void UHoudiniInput::ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) +{ + TArray*> ObjectArrays = GetAllObjectArrays(); + for (TArray* ObjectArrayPtr : ObjectArrays) + { + if (!ObjectArrayPtr) + continue; + Fn(*ObjectArrayPtr); + } +} + +void UHoudiniInput::GetAllHoudiniInputObjects(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddInputObject = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (InputObject) + OutObjects.Add(InputObject); + }; + ForAllHoudiniInputObjects(AddInputObject); +} + +void UHoudiniInput::ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const +{ + auto ProcessSceneComponent = [Fn](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + Fn(SceneComponentInput); + }; + ForAllHoudiniInputObjects(ProcessSceneComponent); +} + +void UHoudiniInput::GetAllHoudiniInputSceneComponents(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputSceneComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + OutObjects.Add(SceneComponentInput); + }; + ForAllHoudiniInputObjects(AddSceneComponent); +} + +void UHoudiniInput::GetAllHoudiniInputSplineComponents(TArray& OutObjects) const +{ + OutObjects.Empty(); + auto AddSceneComponent = [&OutObjects](UHoudiniInputObject* InputObject) + { + if (!InputObject) + return; + UHoudiniInputHoudiniSplineComponent* SceneComponentInput = Cast(InputObject); + if (!SceneComponentInput) + return; + OutObjects.Add(SceneComponentInput); + }; + ForAllHoudiniInputObjects(AddSceneComponent); +} + + +void UHoudiniInput::RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject) +{ + if (!InInputObject) + return; + + ForAllHoudiniInputObjectArrays([InInputObject](TArray& ObjectArray) { + ObjectArray.Remove(InInputObject); + }); + + return; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h index 1959b9d04..4e1494eac 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h @@ -1,558 +1,558 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreTypes.h" -#include "UObject/Object.h" -#include "UObject/ObjectMacros.h" -#include "Misc/Optional.h" - -#include "HoudiniInputTypes.h" -#include "HoudiniInputObject.h" - -#include "GameFramework/Actor.h" -#include "LandscapeProxy.h" - -#include "HoudiniInput.generated.h" - - - -class FReply; - -enum class EHoudiniCurveType : int8; -enum class ECheckBoxState : unsigned char; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject -{ - GENERATED_BODY() - -public: - - UHoudiniInput(); - - // Equality operator, - // We consider two inputs equals if they have the same name, objparam state, and input index/parmId - // TODO: ParmId might be an incorrect condition - bool operator==(const UHoudiniInput& other) const - { - return (bIsObjectPathParameter == other.bIsObjectPathParameter - && InputIndex == other.InputIndex - && ParmId == other.ParmId - && Name.Equals(other.Name) - && Label.Equals(other.Label)); - } - - bool Matches(const UHoudiniInput& other) const { return (*this == other); }; - - // Helper function returning a string from an InputType - static FString InputTypeToString(const EHoudiniInputType& InInputType); - - // Helper function returning an InputType from a string - static EHoudiniInputType StringToInputType(const FString& InInputTypeString); - // Helper function returning a Houdini curve type from a string - static EHoudiniCurveType StringToHoudiniCurveType(const FString& CurveTypeString); - // Helper function returning a Houdini curve method from a string - static EHoudiniCurveMethod StringToHoudiniCurveMethod(const FString& CurveMethodString); - - // Helper function indicating what classes are supported by an input type - static TArray GetAllowedClasses(const EHoudiniInputType& InInputType); - - // Helper function indicating if an object is supported by an input type - static bool IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject); - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - // Returns the NodeId of the asset / object merge we are associated with - int32 GetAssetNodeId() const { return AssetNodeId; }; - // For objpath parameter, return the associated ParamId, -1 if we're a Geo input - int32 GetParameterId() const { return bIsObjectPathParameter ? ParmId : -1; }; - // Returns the NodeId of the node plugged into this input - int32 GetInputNodeId() const { return InputNodeId; }; - - // For Geo inputs, returns the InputIndex, -1 if we're an object path parameter - int32 GetInputIndex() const { return bIsObjectPathParameter ? -1 : InputIndex; }; - // Return the array containing all the nodes created for this input's data - TArray& GetCreatedDataNodeIds() { return CreatedDataNodeIds; }; - // Returns the current input type - EHoudiniInputType GetInputType() const { return Type; }; - // Returns the previous input type - EHoudiniInputType GetPreviousInputType() const { return PreviousType; }; - // Returns the current input type as a string - FString GetInputTypeAsString() const { return InputTypeToString(Type); }; - - EHoudiniXformType GetDefaultXTransformType(); - // Returns true when this input's Transform Type is set to NONE, - // false if set to INTO_THIS_OBJECT, 2 will use the input's type default value - bool GetKeepWorldTransform() const; - // Indicates if this input has changed and should be updated - bool HasChanged(); - // Indicates if this input needs to trigger an update - bool NeedsToTriggerUpdate(); - // Indicates this input should upload its data - bool IsDataUploadNeeded(); - // Indicates this input's transform need to be uploaded - bool IsTransformUploadNeeded(); - // Indicates if this input type has been changed - bool HasInputTypeChanged() const { return PreviousType != EHoudiniInputType::Invalid ? PreviousType != Type : false; } - // - bool GetUpdateInputLandscape() const; - - void SetUpdateInputLandscape(const bool bInUpdateInputLandcape); - - FString GetName() const { return Name; }; - FString GetLabel() const { return Label; }; - FString GetHelp() const { return Help; }; - bool GetPackBeforeMerge() const { return bPackBeforeMerge; }; - bool GetImportAsReference() const { return bImportAsReference; }; - bool GetExportLODs() const { return bExportLODs; }; - bool GetExportSockets() const { return bExportSockets; }; - bool GetExportColliders() const { return bExportColliders; }; - bool IsObjectPathParameter() const { return bIsObjectPathParameter; }; - float GetUnrealSplineResolution() const { return UnrealSplineResolution; }; - - virtual bool GetCookOnCurveChange() const { return bCookOnCurveChanged; }; - - TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType); - const TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const; - TArray* GetBoundSelectorObjectArray(); - const TArray* GetBoundSelectorObjectArray() const; - - UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex); - const UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex) const; - AActor* GetBoundSelectorObjectAt(const int32& AtIndex); - - UHoudiniInputObject* GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - UObject* GetInputObjectAt(const int32& AtIndex); - UObject* GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - int32 GetNumberOfInputObjects(); - int32 GetNumberOfInputObjects(const EHoudiniInputType& InType); - - int32 GetNumberOfInputMeshes(); - int32 GetNumberOfInputMeshes(const EHoudiniInputType& InType); - - int32 GetNumberOfBoundSelectorObjects() const; - - bool IsWorldInputBoundSelector() const { return bIsWorldInputBoundSelector; }; - bool GetWorldInputBoundSelectorAutoUpdates() const { return bWorldInputBoundSelectorAutoUpdate; }; - - FString GetNodeBaseName() const; - - bool IsTransformUIExpanded(const int32& AtIndex); - - // Return the transform offset for a given input object - FTransform* GetTransformOffset(const int32& AtIndex); - const FTransform GetTransformOffset(const int32& AtIndex) const; - - // Returns the position offset for a given input object - TOptional GetPositionOffsetX(int32 AtIndex) const; - TOptional GetPositionOffsetY(int32 AtIndex) const; - TOptional GetPositionOffsetZ(int32 AtIndex) const; - - // Returns the rotation offset for a given input object - TOptional GetRotationOffsetRoll(int32 AtIndex) const; - TOptional GetRotationOffsetPitch(int32 AtIndex) const; - TOptional GetRotationOffsetYaw(int32 AtIndex) const; - - // Returns the scale offset for a given input object - TOptional GetScaleOffsetX(int32 AtIndex) const; - TOptional GetScaleOffsetY(int32 AtIndex) const; - TOptional GetScaleOffsetZ(int32 AtIndex) const; - - // Returns true if the object is one of our input object for the given type - bool ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const; - - // Get all input object arrays - TArray*> GetAllObjectArrays() const; - TArray*> GetAllObjectArrays(); - - // Iterate over all input object arrays - void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const; - void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn); - - void ForAllHoudiniInputObjects(TFunctionRef Fn) const; - // Collect top-level HoudiniInputObjects from this UHoudiniInput. Does not traverse nested input objects. - void GetAllHoudiniInputObjects(TArray& OutObjects) const; - // Collect top-level UHoudiniInputSceneComponent from this UHoudiniInput. Does not traverse nested input objects. - void ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const; - void GetAllHoudiniInputSceneComponents(TArray& OutObjects) const; - void GetAllHoudiniInputSplineComponents(TArray& OutObjects) const; - - // Remove all instances of this input object from all object arrays. - void RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject); - - bool IsAddRotAndScaleAttributesEnabled() const { return bAddRotAndScaleAttributesOnCurves; }; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - - void MarkChanged(const bool& bInChanged) - { - bHasChanged = bInChanged; - SetNeedsToTriggerUpdate(bInChanged); - }; - void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - void MarkDataUploadNeeded(const bool& bInDataUploadNeeded) { bDataUploadNeeded = bInDataUploadNeeded; }; - void MarkAllInputObjectsChanged(const bool& bInChanged); - - void SetSOPInput(const int32& InInputIndex); - void SetObjectPathParameter(const int32& InParmId); - void SetKeepWorldTransform(const bool& bInKeepWorldTransform); - - void SetName(const FString& InName) { Name = InName; }; - void SetLabel(const FString& InLabel) { Label = InLabel; }; - void SetHelp(const FString& InHelp) { Help = InHelp; }; - void SetAssetNodeId(const int32& InNodeId) { AssetNodeId = InNodeId; }; - void SetInputType(const EHoudiniInputType &InInputType, bool& bOutBlueprintStructureModified); - void SetPreviousInputType(const EHoudiniInputType& InType) { PreviousType = InType; }; - void SetPackBeforeMerge(const bool& bInPackBeforeMerge) { bPackBeforeMerge = bInPackBeforeMerge; }; - void SetImportAsReference(const bool& bInImportAsReference) { bImportAsReference = bInImportAsReference; }; - void SetExportLODs(const bool& bInExportLODs) { bExportLODs = bInExportLODs; }; - void SetExportSockets(const bool& bInExportSockets) { bExportSockets = bInExportSockets; }; - void SetExportColliders(const bool& bInExportColliders) { bExportColliders = bInExportColliders; }; - void SetInputNodeId(const int32& InCreatedNodeId) { InputNodeId = InCreatedNodeId; }; - void SetUnrealSplineResolution(const float& InResolution) { UnrealSplineResolution = InResolution; }; - - virtual void SetCookOnCurveChange(const bool & bInCookOnCurveChanged) { bCookOnCurveChanged = bInCookOnCurveChanged; }; - - void ResetDefaultCurveOffset() { DefaultCurveOffset = 0.f; } - - UHoudiniInputObject* CreateNewCurveInputObject(bool& bBlueprintStructureModified); - - void SetGeometryInputObjectsNumber(const int32& NewCount); - void SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount); - - void InsertInputObjectAt(const int32& AtIndex); - void InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void DeleteInputObjectAt(const int32& AtIndex); - void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void DuplicateInputObjectAt(const int32& AtIndex); - void DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - - void SetInputObjectAt(const int32& AtIndex, UObject* InObject); - void SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject); - - void SetBoundSelectorObjectsNumber(const int32& InNewCount); - void SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor); - void SetWorldInputBoundSelector(const bool& InIsBoundSelector) { bIsWorldInputBoundSelector = InIsBoundSelector; }; - void SetWorldInputBoundSelectorAutoUpdates(const bool& InAutoUpdate) { bWorldInputBoundSelectorAutoUpdate = InAutoUpdate; }; - - // Updates the world selection using bound selectors - // returns false if the selection hasn't changed - bool UpdateWorldSelectionFromBoundSelectors(); - // Updates the world selection - // returns false if the selection hasn't changed - bool UpdateWorldSelection(const TArray& InNewSelection); - - void OnTransformUIExpand(const int32& AtIndex); - - // Sets the input's transform offset - bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - - // Sets the input's transform scale values - void SetPositionOffsetX(float InValue, int32 AtIndex); - void SetPositionOffsetY(float InValue, int32 AtIndex); - void SetPositionOffsetZ(float InValue, int32 AtIndex); - - // Sets the input's transform rotation value - void SetRotationOffsetRoll(float InValue, int32 AtIndex); - void SetRotationOffsetPitch(float InValue, int32 AtIndex); - void SetRotationOffsetYaw(float InValue, int32 AtIndex); - - // Sets the input's transform scale values - void SetScaleOffsetX(float InValue, int32 AtIndex); - void SetScaleOffsetY(float InValue, int32 AtIndex); - void SetScaleOffsetZ(float InValue, int32 AtIndex); - - void SetAddRotAndScaleAttributes(const bool& InValue); - - // Duplicate this object and copy its state to the resulting object. - // This is typically used to transfer state between between template and instance components. - UHoudiniInput* DuplicateAndCopyState(UObject* DestOuter, bool bInCanDeleteHoudiniNodes); - virtual void CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes); - - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() { return bCanDeleteHoudiniNodes; } - - virtual void InvalidateData(); - -protected: - void CopyInputs(TArray& ToInputs, TArray& FromInputs, bool bInCanDeleteHoudiniNodes); - -public: - - // Create a Houdini Spline input component, with an existing Houdini Spline input Object. - // Pass in nullptr to create a default Houdini Spline - UHoudiniInputHoudiniSplineComponent* CreateHoudiniSplineInput( - UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputObject, - const bool & bAttachToParent, - const bool & bAppendToInputArray, - bool& bOutBlueprintStructureModified); - - // Given an existing spline input object, remove the associated - // Houdini Spline component from the owning actor / blueprint. - void RemoveSplineFromInputObject( - UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, - bool& bOutBlueprintStructureModified) const; - - bool HasLandscapeExportTypeChanged () const; - - void SetHasLandscapeExportTypeChanged(const bool InChanged); - -#if WITH_EDITOR - FText GetCurrentSelectionText() const; -#endif - - EHoudiniLandscapeExportType GetLandscapeExportType() const { return LandscapeExportType; }; - - void SetLandscapeExportType(const EHoudiniLandscapeExportType InType) { LandscapeExportType = InType; }; - - virtual void BeginDestroy() override; - -#if WITH_EDITOR - virtual void PostEditUndo() override; -#endif - - FBox GetBounds() const; - -protected: - - // Name of the input / Object path parameter - UPROPERTY() - FString Name; - - // Label of the SOP input or of the object path parameter - UPROPERTY() - FString Label; - - // Input type - UPROPERTY() - EHoudiniInputType Type; - - // Previous type, used to detect input type changes - UPROPERTY(Transient, DuplicateTransient) - EHoudiniInputType PreviousType; - - // NodeId of the asset / object merge we are associated with - UPROPERTY(Transient, DuplicateTransient) - int32 AssetNodeId; - - // NodeId of the created input node - // when there is multiple inputs objects, this will be the merge node. - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputNodeId; - - // SOP input index (-1 if we're an object path input) - UPROPERTY() - int32 InputIndex; - - // Parameter Id of the associated object path parameter (-1 if we're a SOP input) - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 ParmId; - - // Indicates if we're an object path parameter input - UPROPERTY() - bool bIsObjectPathParameter; - - // Array containing all the node Ids created by this input - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - TArray CreatedDataNodeIds; - - // Indicates data connected to this input should be uploaded - UPROPERTY(Transient, DuplicateTransient) - bool bHasChanged; - - // Indicates this input should trigger an HDA update/cook - UPROPERTY(Transient, DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Indicates data for this input needs to be uploaded - // If this is false but the input has changed, we may have just updated in input parameter, - // and don't need to resend all the input data - bool bDataUploadNeeded; - - // Help for this parameter/input - UPROPERTY() - FString Help; - - //------------------------------------------------------------------------------------------------------------------------- - // General Input options - - // Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value - UPROPERTY() - EHoudiniXformType KeepWorldTransform; - - // Indicates that the geometry must be packed before merging it into the input - UPROPERTY() - bool bPackBeforeMerge; - - // Indicates that all the input objects are imported to Houdini as references instead of actual geo - // (for Geo/World/Asset input types only) - UPROPERTY() - bool bImportAsReference = false; - - // Indicates that all LODs in the input should be marshalled to Houdini - UPROPERTY() - bool bExportLODs; - - // Indicates that all sockets in the input should be marshalled to Houdini - UPROPERTY() - bool bExportSockets; - - // Indicates that all colliders in the input should be marshalled to Houdini - UPROPERTY() - bool bExportColliders; - - // Indicates that if trigger cook automatically on curve Input spline modified - UPROPERTY() - bool bCookOnCurveChanged; - - //------------------------------------------------------------------------------------------------------------------------- - // Geometry objects - UPROPERTY() - TArray GeometryInputObjects; - - // Is set to true when static mesh used for geometry input has changed. - UPROPERTY() - bool bStaticMeshChanged; - -#if WITH_EDITORONLY_DATA - // Are the transform UI expanded ? - // Values default to false and are actually added to the array in OnTransformUIExpand() - UPROPERTY() - TArray TransformUIExpanded; -#endif - - //------------------------------------------------------------------------------------------------------------------------- - // Asset inputs - UPROPERTY() - TArray AssetInputObjects; - - // Is set to true if the asset input is actually connected inside Houdini. - UPROPERTY() - bool bInputAssetConnectedInHoudini; - - //------------------------------------------------------------------------------------------------------------------------- - // Curve/Spline inputs - UPROPERTY() - TArray CurveInputObjects; - - // Offset used when using muiltiple curves - UPROPERTY() - float DefaultCurveOffset; - - // Set this to true to add rot and scale attributes on curve inputs. - UPROPERTY() - bool bAddRotAndScaleAttributesOnCurves; - - //------------------------------------------------------------------------------------------------------------------------- - // Landscape inputs - UPROPERTY() - TArray LandscapeInputObjects; - - UPROPERTY() - bool bLandscapeHasExportTypeChanged = false; - - //------------------------------------------------------------------------------------------------------------------------- - // World inputs - UPROPERTY() - TArray WorldInputObjects; - - // Objects used for automatic bound selection - UPROPERTY() - TArray WorldInputBoundSelectorObjects; - - // Indicates that this world input is in "BoundSelector" mode - UPROPERTY() - bool bIsWorldInputBoundSelector; - - // Indicates that selected actors by the bound selectors should update automatically - UPROPERTY() - bool bWorldInputBoundSelectorAutoUpdate; - - // Resolution used when converting unreal splines to houdini curves - UPROPERTY() - float UnrealSplineResolution; - - //------------------------------------------------------------------------------------------------------------------------- - // Skeletal Inputs - UPROPERTY() - TArray SkeletalInputObjects; - -public: - - // This array is to record the last insert action, for undo input insertion actions. - UPROPERTY(Transient, DuplicateTransient) - TArray LastInsertedInputs; - - // This array is to cache the action of last undo delete action, and redo that action. - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - TArray LastUndoDeletedInputs; - - - // Indicates that the landscape input's source landscape should be updated instead of creating a new component - UPROPERTY() - bool bUpdateInputLandscape; - - // Indicates if the landscape should be exported as heightfield, mesh or points - UPROPERTY() - EHoudiniLandscapeExportType LandscapeExportType = EHoudiniLandscapeExportType::Heightfield; - - // Is set to true when landscape input is set to selection only. - UPROPERTY() - bool bLandscapeExportSelectionOnly = false; - - // Is set to true when the automatic selection of landscape component is active - UPROPERTY() - bool bLandscapeAutoSelectComponent = false; - - // Is set to true when materials are to be exported. - UPROPERTY() - bool bLandscapeExportMaterials = false; - - // Is set to true when lightmap information export is desired. - UPROPERTY() - bool bLandscapeExportLighting = false; - - // Is set to true when uvs should be exported in [0,1] space. - UPROPERTY() - bool bLandscapeExportNormalizedUVs = false; - - // Is set to true when uvs should be exported for each tile separately. - UPROPERTY() - bool bLandscapeExportTileUVs = false; - - UPROPERTY() - bool bCanDeleteHoudiniNodes = true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreTypes.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" +#include "Misc/Optional.h" + +#include "HoudiniInputTypes.h" +#include "HoudiniInputObject.h" + +#include "GameFramework/Actor.h" +#include "LandscapeProxy.h" + +#include "HoudiniInput.generated.h" + + + +class FReply; + +enum class EHoudiniCurveType : int8; +enum class ECheckBoxState : unsigned char; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject +{ + GENERATED_BODY() + +public: + + UHoudiniInput(); + + // Equality operator, + // We consider two inputs equals if they have the same name, objparam state, and input index/parmId + // TODO: ParmId might be an incorrect condition + bool operator==(const UHoudiniInput& other) const + { + return (bIsObjectPathParameter == other.bIsObjectPathParameter + && InputIndex == other.InputIndex + && ParmId == other.ParmId + && Name.Equals(other.Name) + && Label.Equals(other.Label)); + } + + bool Matches(const UHoudiniInput& other) const { return (*this == other); }; + + // Helper function returning a string from an InputType + static FString InputTypeToString(const EHoudiniInputType& InInputType); + + // Helper function returning an InputType from a string + static EHoudiniInputType StringToInputType(const FString& InInputTypeString); + // Helper function returning a Houdini curve type from a string + static EHoudiniCurveType StringToHoudiniCurveType(const FString& CurveTypeString); + // Helper function returning a Houdini curve method from a string + static EHoudiniCurveMethod StringToHoudiniCurveMethod(const FString& CurveMethodString); + + // Helper function indicating what classes are supported by an input type + static TArray GetAllowedClasses(const EHoudiniInputType& InInputType); + + // Helper function indicating if an object is supported by an input type + static bool IsObjectAcceptable(const EHoudiniInputType& InInputType, const UObject* InObject); + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + // Returns the NodeId of the asset / object merge we are associated with + int32 GetAssetNodeId() const { return AssetNodeId; }; + // For objpath parameter, return the associated ParamId, -1 if we're a Geo input + int32 GetParameterId() const { return bIsObjectPathParameter ? ParmId : -1; }; + // Returns the NodeId of the node plugged into this input + int32 GetInputNodeId() const { return InputNodeId; }; + + // For Geo inputs, returns the InputIndex, -1 if we're an object path parameter + int32 GetInputIndex() const { return bIsObjectPathParameter ? -1 : InputIndex; }; + // Return the array containing all the nodes created for this input's data + TArray& GetCreatedDataNodeIds() { return CreatedDataNodeIds; }; + // Returns the current input type + EHoudiniInputType GetInputType() const { return Type; }; + // Returns the previous input type + EHoudiniInputType GetPreviousInputType() const { return PreviousType; }; + // Returns the current input type as a string + FString GetInputTypeAsString() const { return InputTypeToString(Type); }; + + EHoudiniXformType GetDefaultXTransformType(); + // Returns true when this input's Transform Type is set to NONE, + // false if set to INTO_THIS_OBJECT, 2 will use the input's type default value + bool GetKeepWorldTransform() const; + // Indicates if this input has changed and should be updated + bool HasChanged(); + // Indicates if this input needs to trigger an update + bool NeedsToTriggerUpdate(); + // Indicates this input should upload its data + bool IsDataUploadNeeded(); + // Indicates this input's transform need to be uploaded + bool IsTransformUploadNeeded(); + // Indicates if this input type has been changed + bool HasInputTypeChanged() const { return PreviousType != EHoudiniInputType::Invalid ? PreviousType != Type : false; } + // + bool GetUpdateInputLandscape() const; + + void SetUpdateInputLandscape(const bool bInUpdateInputLandcape); + + FString GetName() const { return Name; }; + FString GetLabel() const { return Label; }; + FString GetHelp() const { return Help; }; + bool GetPackBeforeMerge() const { return bPackBeforeMerge; }; + bool GetImportAsReference() const { return bImportAsReference; }; + bool GetExportLODs() const { return bExportLODs; }; + bool GetExportSockets() const { return bExportSockets; }; + bool GetExportColliders() const { return bExportColliders; }; + bool IsObjectPathParameter() const { return bIsObjectPathParameter; }; + float GetUnrealSplineResolution() const { return UnrealSplineResolution; }; + + virtual bool GetCookOnCurveChange() const { return bCookOnCurveChanged; }; + + TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType); + const TArray* GetHoudiniInputObjectArray(const EHoudiniInputType& InType) const; + TArray* GetBoundSelectorObjectArray(); + const TArray* GetBoundSelectorObjectArray() const; + + UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex); + const UHoudiniInputObject* GetHoudiniInputObjectAt(const int32& AtIndex) const; + AActor* GetBoundSelectorObjectAt(const int32& AtIndex); + + UHoudiniInputObject* GetHoudiniInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + UObject* GetInputObjectAt(const int32& AtIndex); + UObject* GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + int32 GetNumberOfInputObjects(); + int32 GetNumberOfInputObjects(const EHoudiniInputType& InType); + + int32 GetNumberOfInputMeshes(); + int32 GetNumberOfInputMeshes(const EHoudiniInputType& InType); + + int32 GetNumberOfBoundSelectorObjects() const; + + bool IsWorldInputBoundSelector() const { return bIsWorldInputBoundSelector; }; + bool GetWorldInputBoundSelectorAutoUpdates() const { return bWorldInputBoundSelectorAutoUpdate; }; + + FString GetNodeBaseName() const; + + bool IsTransformUIExpanded(const int32& AtIndex); + + // Return the transform offset for a given input object + FTransform* GetTransformOffset(const int32& AtIndex); + const FTransform GetTransformOffset(const int32& AtIndex) const; + + // Returns the position offset for a given input object + TOptional GetPositionOffsetX(int32 AtIndex) const; + TOptional GetPositionOffsetY(int32 AtIndex) const; + TOptional GetPositionOffsetZ(int32 AtIndex) const; + + // Returns the rotation offset for a given input object + TOptional GetRotationOffsetRoll(int32 AtIndex) const; + TOptional GetRotationOffsetPitch(int32 AtIndex) const; + TOptional GetRotationOffsetYaw(int32 AtIndex) const; + + // Returns the scale offset for a given input object + TOptional GetScaleOffsetX(int32 AtIndex) const; + TOptional GetScaleOffsetY(int32 AtIndex) const; + TOptional GetScaleOffsetZ(int32 AtIndex) const; + + // Returns true if the object is one of our input object for the given type + bool ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const; + + // Get all input object arrays + TArray*> GetAllObjectArrays() const; + TArray*> GetAllObjectArrays(); + + // Iterate over all input object arrays + void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const; + void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn); + + void ForAllHoudiniInputObjects(TFunctionRef Fn) const; + // Collect top-level HoudiniInputObjects from this UHoudiniInput. Does not traverse nested input objects. + void GetAllHoudiniInputObjects(TArray& OutObjects) const; + // Collect top-level UHoudiniInputSceneComponent from this UHoudiniInput. Does not traverse nested input objects. + void ForAllHoudiniInputSceneComponents(TFunctionRef Fn) const; + void GetAllHoudiniInputSceneComponents(TArray& OutObjects) const; + void GetAllHoudiniInputSplineComponents(TArray& OutObjects) const; + + // Remove all instances of this input object from all object arrays. + void RemoveHoudiniInputObject(UHoudiniInputObject* InInputObject); + + bool IsAddRotAndScaleAttributesEnabled() const { return bAddRotAndScaleAttributesOnCurves; }; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + + void MarkChanged(const bool& bInChanged) + { + bHasChanged = bInChanged; + SetNeedsToTriggerUpdate(bInChanged); + }; + void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + void MarkDataUploadNeeded(const bool& bInDataUploadNeeded) { bDataUploadNeeded = bInDataUploadNeeded; }; + void MarkAllInputObjectsChanged(const bool& bInChanged); + + void SetSOPInput(const int32& InInputIndex); + void SetObjectPathParameter(const int32& InParmId); + void SetKeepWorldTransform(const bool& bInKeepWorldTransform); + + void SetName(const FString& InName) { Name = InName; }; + void SetLabel(const FString& InLabel) { Label = InLabel; }; + void SetHelp(const FString& InHelp) { Help = InHelp; }; + void SetAssetNodeId(const int32& InNodeId) { AssetNodeId = InNodeId; }; + void SetInputType(const EHoudiniInputType &InInputType, bool& bOutBlueprintStructureModified); + void SetPreviousInputType(const EHoudiniInputType& InType) { PreviousType = InType; }; + void SetPackBeforeMerge(const bool& bInPackBeforeMerge) { bPackBeforeMerge = bInPackBeforeMerge; }; + void SetImportAsReference(const bool& bInImportAsReference) { bImportAsReference = bInImportAsReference; }; + void SetExportLODs(const bool& bInExportLODs) { bExportLODs = bInExportLODs; }; + void SetExportSockets(const bool& bInExportSockets) { bExportSockets = bInExportSockets; }; + void SetExportColliders(const bool& bInExportColliders) { bExportColliders = bInExportColliders; }; + void SetInputNodeId(const int32& InCreatedNodeId) { InputNodeId = InCreatedNodeId; }; + void SetUnrealSplineResolution(const float& InResolution) { UnrealSplineResolution = InResolution; }; + + virtual void SetCookOnCurveChange(const bool & bInCookOnCurveChanged) { bCookOnCurveChanged = bInCookOnCurveChanged; }; + + void ResetDefaultCurveOffset() { DefaultCurveOffset = 0.f; } + + UHoudiniInputObject* CreateNewCurveInputObject(bool& bBlueprintStructureModified); + + void SetGeometryInputObjectsNumber(const int32& NewCount); + void SetInputObjectsNumber(const EHoudiniInputType& InType, const int32& InNewCount); + + void InsertInputObjectAt(const int32& AtIndex); + void InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void DeleteInputObjectAt(const int32& AtIndex); + void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void DuplicateInputObjectAt(const int32& AtIndex); + void DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + + void SetInputObjectAt(const int32& AtIndex, UObject* InObject); + void SetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, UObject* InObject); + + void SetBoundSelectorObjectsNumber(const int32& InNewCount); + void SetBoundSelectorObjectAt(const int32& AtIndex, AActor* InActor); + void SetWorldInputBoundSelector(const bool& InIsBoundSelector) { bIsWorldInputBoundSelector = InIsBoundSelector; }; + void SetWorldInputBoundSelectorAutoUpdates(const bool& InAutoUpdate) { bWorldInputBoundSelectorAutoUpdate = InAutoUpdate; }; + + // Updates the world selection using bound selectors + // returns false if the selection hasn't changed + bool UpdateWorldSelectionFromBoundSelectors(); + // Updates the world selection + // returns false if the selection hasn't changed + bool UpdateWorldSelection(const TArray& InNewSelection); + + void OnTransformUIExpand(const int32& AtIndex); + + // Sets the input's transform offset + bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + + // Sets the input's transform scale values + void SetPositionOffsetX(float InValue, int32 AtIndex); + void SetPositionOffsetY(float InValue, int32 AtIndex); + void SetPositionOffsetZ(float InValue, int32 AtIndex); + + // Sets the input's transform rotation value + void SetRotationOffsetRoll(float InValue, int32 AtIndex); + void SetRotationOffsetPitch(float InValue, int32 AtIndex); + void SetRotationOffsetYaw(float InValue, int32 AtIndex); + + // Sets the input's transform scale values + void SetScaleOffsetX(float InValue, int32 AtIndex); + void SetScaleOffsetY(float InValue, int32 AtIndex); + void SetScaleOffsetZ(float InValue, int32 AtIndex); + + void SetAddRotAndScaleAttributes(const bool& InValue); + + // Duplicate this object and copy its state to the resulting object. + // This is typically used to transfer state between between template and instance components. + UHoudiniInput* DuplicateAndCopyState(UObject* DestOuter, bool bInCanDeleteHoudiniNodes); + virtual void CopyStateFrom(UHoudiniInput* InInput, bool bCopyAllProperties, bool bInCanDeleteHoudiniNodes); + + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() { return bCanDeleteHoudiniNodes; } + + virtual void InvalidateData(); + +protected: + void CopyInputs(TArray& ToInputs, TArray& FromInputs, bool bInCanDeleteHoudiniNodes); + +public: + + // Create a Houdini Spline input component, with an existing Houdini Spline input Object. + // Pass in nullptr to create a default Houdini Spline + UHoudiniInputHoudiniSplineComponent* CreateHoudiniSplineInput( + UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputObject, + const bool & bAttachToParent, + const bool & bAppendToInputArray, + bool& bOutBlueprintStructureModified); + + // Given an existing spline input object, remove the associated + // Houdini Spline component from the owning actor / blueprint. + void RemoveSplineFromInputObject( + UHoudiniInputHoudiniSplineComponent* InHoudiniSplineInputObject, + bool& bOutBlueprintStructureModified) const; + + bool HasLandscapeExportTypeChanged () const; + + void SetHasLandscapeExportTypeChanged(const bool InChanged); + +#if WITH_EDITOR + FText GetCurrentSelectionText() const; +#endif + + EHoudiniLandscapeExportType GetLandscapeExportType() const { return LandscapeExportType; }; + + void SetLandscapeExportType(const EHoudiniLandscapeExportType InType) { LandscapeExportType = InType; }; + + virtual void BeginDestroy() override; + +#if WITH_EDITOR + virtual void PostEditUndo() override; +#endif + + FBox GetBounds() const; + +protected: + + // Name of the input / Object path parameter + UPROPERTY() + FString Name; + + // Label of the SOP input or of the object path parameter + UPROPERTY() + FString Label; + + // Input type + UPROPERTY() + EHoudiniInputType Type; + + // Previous type, used to detect input type changes + UPROPERTY(Transient, DuplicateTransient) + EHoudiniInputType PreviousType; + + // NodeId of the asset / object merge we are associated with + UPROPERTY(Transient, DuplicateTransient) + int32 AssetNodeId; + + // NodeId of the created input node + // when there is multiple inputs objects, this will be the merge node. + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputNodeId; + + // SOP input index (-1 if we're an object path input) + UPROPERTY() + int32 InputIndex; + + // Parameter Id of the associated object path parameter (-1 if we're a SOP input) + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 ParmId; + + // Indicates if we're an object path parameter input + UPROPERTY() + bool bIsObjectPathParameter; + + // Array containing all the node Ids created by this input + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + TArray CreatedDataNodeIds; + + // Indicates data connected to this input should be uploaded + UPROPERTY(Transient, DuplicateTransient) + bool bHasChanged; + + // Indicates this input should trigger an HDA update/cook + UPROPERTY(Transient, DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Indicates data for this input needs to be uploaded + // If this is false but the input has changed, we may have just updated in input parameter, + // and don't need to resend all the input data + bool bDataUploadNeeded; + + // Help for this parameter/input + UPROPERTY() + FString Help; + + //------------------------------------------------------------------------------------------------------------------------- + // General Input options + + // Is set to true when this input's Transform Type is set to NONE, 2 will use the input's default value + UPROPERTY() + EHoudiniXformType KeepWorldTransform; + + // Indicates that the geometry must be packed before merging it into the input + UPROPERTY() + bool bPackBeforeMerge; + + // Indicates that all the input objects are imported to Houdini as references instead of actual geo + // (for Geo/World/Asset input types only) + UPROPERTY() + bool bImportAsReference = false; + + // Indicates that all LODs in the input should be marshalled to Houdini + UPROPERTY() + bool bExportLODs; + + // Indicates that all sockets in the input should be marshalled to Houdini + UPROPERTY() + bool bExportSockets; + + // Indicates that all colliders in the input should be marshalled to Houdini + UPROPERTY() + bool bExportColliders; + + // Indicates that if trigger cook automatically on curve Input spline modified + UPROPERTY() + bool bCookOnCurveChanged; + + //------------------------------------------------------------------------------------------------------------------------- + // Geometry objects + UPROPERTY() + TArray GeometryInputObjects; + + // Is set to true when static mesh used for geometry input has changed. + UPROPERTY() + bool bStaticMeshChanged; + +#if WITH_EDITORONLY_DATA + // Are the transform UI expanded ? + // Values default to false and are actually added to the array in OnTransformUIExpand() + UPROPERTY() + TArray TransformUIExpanded; +#endif + + //------------------------------------------------------------------------------------------------------------------------- + // Asset inputs + UPROPERTY() + TArray AssetInputObjects; + + // Is set to true if the asset input is actually connected inside Houdini. + UPROPERTY() + bool bInputAssetConnectedInHoudini; + + //------------------------------------------------------------------------------------------------------------------------- + // Curve/Spline inputs + UPROPERTY() + TArray CurveInputObjects; + + // Offset used when using muiltiple curves + UPROPERTY() + float DefaultCurveOffset; + + // Set this to true to add rot and scale attributes on curve inputs. + UPROPERTY() + bool bAddRotAndScaleAttributesOnCurves; + + //------------------------------------------------------------------------------------------------------------------------- + // Landscape inputs + UPROPERTY() + TArray LandscapeInputObjects; + + UPROPERTY() + bool bLandscapeHasExportTypeChanged = false; + + //------------------------------------------------------------------------------------------------------------------------- + // World inputs + UPROPERTY() + TArray WorldInputObjects; + + // Objects used for automatic bound selection + UPROPERTY() + TArray WorldInputBoundSelectorObjects; + + // Indicates that this world input is in "BoundSelector" mode + UPROPERTY() + bool bIsWorldInputBoundSelector; + + // Indicates that selected actors by the bound selectors should update automatically + UPROPERTY() + bool bWorldInputBoundSelectorAutoUpdate; + + // Resolution used when converting unreal splines to houdini curves + UPROPERTY() + float UnrealSplineResolution; + + //------------------------------------------------------------------------------------------------------------------------- + // Skeletal Inputs + UPROPERTY() + TArray SkeletalInputObjects; + +public: + + // This array is to record the last insert action, for undo input insertion actions. + UPROPERTY(Transient, DuplicateTransient) + TArray LastInsertedInputs; + + // This array is to cache the action of last undo delete action, and redo that action. + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + TArray LastUndoDeletedInputs; + + + // Indicates that the landscape input's source landscape should be updated instead of creating a new component + UPROPERTY() + bool bUpdateInputLandscape; + + // Indicates if the landscape should be exported as heightfield, mesh or points + UPROPERTY() + EHoudiniLandscapeExportType LandscapeExportType = EHoudiniLandscapeExportType::Heightfield; + + // Is set to true when landscape input is set to selection only. + UPROPERTY() + bool bLandscapeExportSelectionOnly = false; + + // Is set to true when the automatic selection of landscape component is active + UPROPERTY() + bool bLandscapeAutoSelectComponent = false; + + // Is set to true when materials are to be exported. + UPROPERTY() + bool bLandscapeExportMaterials = false; + + // Is set to true when lightmap information export is desired. + UPROPERTY() + bool bLandscapeExportLighting = false; + + // Is set to true when uvs should be exported in [0,1] space. + UPROPERTY() + bool bLandscapeExportNormalizedUVs = false; + + // Is set to true when uvs should be exported for each tile separately. + UPROPERTY() + bool bLandscapeExportTileUVs = false; + + UPROPERTY() + bool bCanDeleteHoudiniNodes = true; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp index 878ea8bc2..fe5f2def3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp @@ -1,1864 +1,1864 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInputObject.h" - -#include "HoudiniEngineRuntime.h" -#include "HoudiniAssetActor.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniSplineComponent.h" -#include "HoudiniInput.h" - -#include "Engine/StaticMesh.h" -#include "Engine/SkeletalMesh.h" -#include "Engine/DataTable.h" -#include "Components/StaticMeshComponent.h" -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/SplineComponent.h" -#include "Landscape.h" -#include "Engine/Brush.h" -#include "Engine/Engine.h" -#include "GameFramework/Volume.h" -#include "Camera/CameraComponent.h" -#include "FoliageType_InstancedStaticMesh.h" - -#include "Model.h" -#include "Engine/Brush.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "Kismet/KismetSystemLibrary.h" - -//----------------------------------------------------------------------------------------------------------------------------- -// Constructors -//----------------------------------------------------------------------------------------------------------------------------- - -// -UHoudiniInputObject::UHoudiniInputObject(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , Transform(FTransform::Identity) - , Type(EHoudiniInputObjectType::Invalid) - , InputNodeId(-1) - , InputObjectNodeId(-1) - , bHasChanged(false) - , bNeedsToTriggerUpdate(false) - , bTransformChanged(false) - , bImportAsReference(false) - , bCanDeleteHoudiniNodes(true) -{ - Guid = FGuid::NewGuid(); -} - -// -UHoudiniInputStaticMesh::UHoudiniInputStaticMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSkeletalMesh::UHoudiniInputSkeletalMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSceneComponent::UHoudiniInputSceneComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputMeshComponent::UHoudiniInputMeshComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputInstancedMeshComponent::UHoudiniInputInstancedMeshComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputSplineComponent::UHoudiniInputSplineComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , NumberOfSplineControlPoints(-1) - , SplineLength(-1.0f) - , SplineResolution(-1.0f) - , SplineClosed(false) -{ - -} - -// -UHoudiniInputCameraComponent::UHoudiniInputCameraComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , FOV(0.0f) - , AspectRatio(1.0f) - , bIsOrthographic(false) - , OrthoWidth(2.0f) - , OrthoNearClipPlane(0.0f) - , OrthoFarClipPlane(-1.0f) -{ - -} - -// Returns true if the attached actor's (parent) transform has been modified -bool -UHoudiniInputSplineComponent::HasActorTransformChanged() const -{ - return false; -} - -// Returns true if the attached component's transform has been modified -bool -UHoudiniInputSplineComponent::HasComponentTransformChanged() const -{ - return false; -} - -// Return true if the component itself has been modified -bool -UHoudiniInputSplineComponent::HasComponentChanged() const -{ - USplineComponent* SplineComponent = Cast(InputObject.LoadSynchronous()); - - if (!SplineComponent) - return false; - - if (SplineClosed != SplineComponent->IsClosedLoop()) - return true; - - - if (SplineComponent->GetNumberOfSplinePoints() != NumberOfSplineControlPoints) - return true; - - for (int32 n = 0; n < SplineComponent->GetNumberOfSplinePoints(); ++n) - { - const FTransform &CurSplineComponentTransform = SplineComponent->GetTransformAtSplinePoint(n, ESplineCoordinateSpace::Local); - const FTransform &CurInputTransform = SplineControlPoints[n]; - - if (CurInputTransform.GetLocation() != CurSplineComponentTransform.GetLocation()) - return true; - - if (CurInputTransform.GetRotation().Rotator() != CurSplineComponentTransform.GetRotation().Rotator()) - return true; - - if (CurInputTransform.GetScale3D() != CurSplineComponentTransform.GetScale3D()) - return true; - } - - return false; -} - -bool -UHoudiniInputSplineComponent::HasSplineComponentChanged(float fCurrentSplineResolution) const -{ - return false; -} - -// -UHoudiniInputHoudiniSplineComponent::UHoudiniInputHoudiniSplineComponent(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , CurveType(EHoudiniCurveType::Polygon) - , CurveMethod(EHoudiniCurveMethod::CVs) - , Reversed(false) -{ - -} - -// -UHoudiniInputHoudiniAsset::UHoudiniInputHoudiniAsset(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , AssetOutputIndex(-1) -{ - -} - -// -UHoudiniInputActor::UHoudiniInputActor(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) - , LastUpdateNumComponentsAdded(0) - , LastUpdateNumComponentsRemoved(0) -{ - -} - -// -UHoudiniInputLandscape::UHoudiniInputLandscape(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -// -UHoudiniInputBrush::UHoudiniInputBrush() - : CombinedModel(nullptr) - , bIgnoreInputObject(false) -{ - -} - -//----------------------------------------------------------------------------------------------------------------------------- -// Accessors -//----------------------------------------------------------------------------------------------------------------------------- - -UObject* -UHoudiniInputObject::GetObject() const -{ - return InputObject.LoadSynchronous(); -} - -UStaticMesh* -UHoudiniInputStaticMesh::GetStaticMesh() const -{ - return Cast(InputObject.LoadSynchronous()); -} - -UBlueprint* -UHoudiniInputStaticMesh::GetBlueprint() const -{ - return Cast(InputObject.LoadSynchronous()); -} - -bool UHoudiniInputStaticMesh::bIsBlueprint() const -{ - return (InputObject.IsValid() && InputObject.Get()->IsA()); -} - -USkeletalMesh* -UHoudiniInputSkeletalMesh::GetSkeletalMesh() -{ - return Cast(InputObject.LoadSynchronous()); -} - -USceneComponent* -UHoudiniInputSceneComponent::GetSceneComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UStaticMeshComponent* -UHoudiniInputMeshComponent::GetStaticMeshComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UStaticMesh* -UHoudiniInputMeshComponent::GetStaticMesh() -{ - return StaticMesh.Get(); -} - -UInstancedStaticMeshComponent* -UHoudiniInputInstancedMeshComponent::GetInstancedStaticMeshComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -USplineComponent* -UHoudiniInputSplineComponent::GetSplineComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UHoudiniSplineComponent* -UHoudiniInputHoudiniSplineComponent::GetCurveComponent() const -{ - return Cast(GetObject()); - //return Cast(InputObject.LoadSynchronous()); -} - -UCameraComponent* -UHoudiniInputCameraComponent::GetCameraComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -UHoudiniAssetComponent* -UHoudiniInputHoudiniAsset::GetHoudiniAssetComponent() -{ - return Cast(InputObject.LoadSynchronous()); -} - -AActor* -UHoudiniInputActor::GetActor() -{ - return Cast(InputObject.LoadSynchronous()); -} - -ALandscapeProxy* -UHoudiniInputLandscape::GetLandscapeProxy() -{ - return Cast(InputObject.LoadSynchronous()); -} - -void -UHoudiniInputLandscape::SetLandscapeProxy(UObject* InLandscapeProxy) -{ - UObject* LandscapeProxy = Cast(InLandscapeProxy); - if (LandscapeProxy) - InputObject = LandscapeProxy; -} - -ABrush* -UHoudiniInputBrush::GetBrush() const -{ - return Cast(InputObject.LoadSynchronous()); -} - - -//----------------------------------------------------------------------------------------------------------------------------- -// CREATE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -UHoudiniInputObject * -UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InName) -{ - if (!InObject) - return nullptr; - - UHoudiniInputObject* HoudiniInputObject = nullptr; - - EHoudiniInputObjectType InputObjectType = GetInputObjectTypeFromObject(InObject); - switch (InputObjectType) - { - case EHoudiniInputObjectType::Object: - HoudiniInputObject = UHoudiniInputObject::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::StaticMesh: - HoudiniInputObject = UHoudiniInputStaticMesh::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::SkeletalMesh: - HoudiniInputObject = UHoudiniInputSkeletalMesh::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::SceneComponent: - // Do not create input objects for unknown scene component! - //HoudiniInputObject = UHoudiniInputSceneComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::StaticMeshComponent: - HoudiniInputObject = UHoudiniInputMeshComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::InstancedStaticMeshComponent: - HoudiniInputObject = UHoudiniInputInstancedMeshComponent::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::SplineComponent: - HoudiniInputObject = UHoudiniInputSplineComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::HoudiniSplineComponent: - HoudiniInputObject = UHoudiniInputHoudiniSplineComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::HoudiniAssetActor: - { - AHoudiniAssetActor* HoudiniActor = Cast(InObject); - if (HoudiniActor) - { - HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniActor->GetHoudiniAssetComponent(), InOuter, InName); - } - else - { - HoudiniInputObject = nullptr; - } - } - break; - - case EHoudiniInputObjectType::HoudiniAssetComponent: - HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(InObject, InOuter, InName); - break; - case EHoudiniInputObjectType::Actor: - HoudiniInputObject = UHoudiniInputActor::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Landscape: - HoudiniInputObject = UHoudiniInputLandscape::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Brush: - HoudiniInputObject = UHoudiniInputBrush::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::CameraComponent: - HoudiniInputObject = UHoudiniInputCameraComponent::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::DataTable: - HoudiniInputObject = UHoudiniInputDataTable::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: - HoudiniInputObject = UHoudiniInputFoliageType_InstancedStaticMesh::Create(InObject, InOuter, InName); - break; - - case EHoudiniInputObjectType::Invalid: - default: - break; - } - - return HoudiniInputObject; -} - - -UHoudiniInputObject * -UHoudiniInputInstancedMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_ISMC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputInstancedMeshComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::InstancedStaticMeshComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SMC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputMeshComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputMeshComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMeshComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Spline_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSplineComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSplineComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SplineComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputHoudiniSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_HoudiniSpline_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputHoudiniSplineComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniSplineComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputCameraComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Camera_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputCameraComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputCameraComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputCameraComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::CameraComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputHoudiniAsset::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - UHoudiniAssetComponent * InHoudiniAssetComponent = Cast(InObject); - if (!InHoudiniAssetComponent) - return nullptr; - - FString InputObjectNameStr = "HoudiniInputObject_HAC_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniAsset::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputHoudiniAsset * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputHoudiniAsset::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniAssetComponent; - - HoudiniInputObject->InputNodeId = InHoudiniAssetComponent->GetAssetId(); - HoudiniInputObject->InputObjectNodeId = InHoudiniAssetComponent->GetAssetId(); - - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputSceneComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SceneComp_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSceneComponent::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSceneComponent * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSceneComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SceneComponent; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputLandscape::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Landscape_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputLandscape::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputLandscape * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputLandscape::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Landscape; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputBrush * -UHoudiniInputBrush::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Brush_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputBrush::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputBrush * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputBrush::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Brush; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputActor::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_Actor_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputActor::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputActor * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputActor::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Actor; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SM_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputStaticMesh::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputStaticMesh * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMesh; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -// void UHoudiniInputStaticMesh::DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputStaticMesh*& OutNewInput) -// { -// UHoudiniInputStaticMesh* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); -// OutNewInput = NewInput; -// OutNewInput->CopyStateFrom(this, false); -// } - -void -UHoudiniInputStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - UHoudiniInputStaticMesh* StaticMeshInput = Cast(InInput); - check(InInput); - - TArray PrevInputs = BlueprintStaticMeshes; - - Super::CopyStateFrom(StaticMeshInput, bCopyAllProperties); - - const int32 NumInputs = StaticMeshInput->BlueprintStaticMeshes.Num(); - BlueprintStaticMeshes = PrevInputs; - TArray StaleInputs(BlueprintStaticMeshes); - - BlueprintStaticMeshes.SetNum(NumInputs); - - for (int i = 0; i < NumInputs; ++i) - { - UHoudiniInputStaticMesh* FromInput = StaticMeshInput->BlueprintStaticMeshes[i]; - UHoudiniInputStaticMesh* ToInput = BlueprintStaticMeshes[i]; - - if (!FromInput) - { - BlueprintStaticMeshes[i] = nullptr; - continue; - } - - if (ToInput) - { - // Check whether the ToInput can be reused - bool bIsValid = true; - bIsValid = bIsValid && ToInput->Matches(*FromInput); - bIsValid = bIsValid && ToInput->GetOuter() == this; - if (!bIsValid) - { - ToInput = nullptr; - } - } - - if (ToInput) - { - // We have a reusable input - ToInput->CopyStateFrom(FromInput, true); - } - else - { - // We need to create a new input - ToInput = Cast(FromInput->DuplicateAndCopyState(this)); - } - - BlueprintStaticMeshes[i] = ToInput; - } - - for(UHoudiniInputStaticMesh* StaleInput : StaleInputs) - { - if (!StaleInput) - continue; - StaleInput->InvalidateData(); - } -} - -void -UHoudiniInputStaticMesh::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - Super::SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) - { - if (!Input) - continue; - Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); - } -} - -void -UHoudiniInputStaticMesh::InvalidateData() -{ - for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) - { - if (!Input) - continue; - Input->InvalidateData(); - } - - Super::InvalidateData(); -} - - -UHoudiniInputObject * -UHoudiniInputSkeletalMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_SkelMesh_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSkeletalMesh::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputSkeletalMesh * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputSkeletalMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::SkeletalMesh; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UHoudiniInputObject * -UHoudiniInputObject::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputObject::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputObject * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputObject::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::Object; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -bool -UHoudiniInputObject::Matches(const UHoudiniInputObject& Other) const -{ - return (Type == Other.Type - && InputNodeId == Other.InputNodeId - && InputObjectNodeId == Other.InputObjectNodeId - ); -} - -//----------------------------------------------------------------------------------------------------------------------------- -// DELETE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -void -UHoudiniInputObject::InvalidateData() -{ - // If valid, mark our input nodes for deletion.. - if (this->IsA() || !bCanDeleteHoudiniNodes) - { - // Unless if we're a HoudiniAssetInput! we don't want to delete the other HDA's node! - // just invalidate the node IDs! - InputNodeId = -1; - InputObjectNodeId = -1; - return; - } - - if (InputNodeId >= 0) - { - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); - InputNodeId = -1; - } - - // ... and the parent OBJ as well to clean up - if (InputObjectNodeId >= 0) - { - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputObjectNodeId); - InputObjectNodeId = -1; - } - - -} - -void -UHoudiniInputObject::BeginDestroy() -{ - // Invalidate and mark our input node for deletion - InvalidateData(); - - Super::BeginDestroy(); -} - -//----------------------------------------------------------------------------------------------------------------------------- -// UPDATE METHODS -//----------------------------------------------------------------------------------------------------------------------------- - -void -UHoudiniInputObject::Update(UObject * InObject) -{ - InputObject = InObject; -} - -void -UHoudiniInputStaticMesh::Update(UObject * InObject) -{ - // Nothing to do - Super::Update(InObject); - // Static Mesh input accepts SM, BP, FoliageType_InstancedStaticMesh (static mesh) and FoliageType_Actor (if blueprint actor). - UStaticMesh* SM = Cast(InObject); - UBlueprint* BP = Cast(InObject); - - ensure(SM || BP); -} - -void -UHoudiniInputSkeletalMesh::Update(UObject * InObject) -{ - // Nothing to do - Super::Update(InObject); - - USkeletalMesh* SkelMesh = Cast(InObject); - ensure(SkelMesh); -} - -void -UHoudiniInputSceneComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - USceneComponent* USC = Cast(InObject); - ensure(USC); - if (USC) - { - Transform = USC->GetComponentTransform(); - } -} - - -bool -UHoudiniInputSceneComponent::HasActorTransformChanged() const -{ - // Returns true if the attached actor's (parent) transform has been modified - USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); - if (!MyComp || MyComp->IsPendingKill()) - return false; - - AActor* MyActor = MyComp->GetOwner(); - if (!MyActor) - return false; - - return (!ActorTransform.Equals(MyActor->GetTransform())); -} - - -bool -UHoudiniInputSceneComponent::HasComponentTransformChanged() const -{ - // Returns true if the attached actor's (parent) transform has been modified - USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); - if (!MyComp || MyComp->IsPendingKill()) - return false; - - return !Transform.Equals(MyComp->GetComponentTransform()); -} - - -bool -UHoudiniInputSceneComponent::HasComponentChanged() const -{ - // Should return true if the component itself has been modified - // Should be overriden in child classes - return false; -} - - -bool -UHoudiniInputMeshComponent::HasComponentChanged() const -{ - UStaticMeshComponent* SMC = Cast(InputObject.LoadSynchronous()); - UStaticMesh* MySM = StaticMesh.Get(); - - // Return true if SMC's static mesh has been modified - return (MySM != SMC->GetStaticMesh()); -} - -bool -UHoudiniInputCameraComponent::HasComponentChanged() const -{ - UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); - if (Camera && !Camera->IsPendingKill()) - { - bool bOrtho = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; - if (bOrtho != bIsOrthographic) - return true; - - if (Camera->FieldOfView != FOV) - return true; - - if (Camera->AspectRatio != AspectRatio) - return true; - - if (Camera->OrthoWidth != OrthoWidth) - return true; - - if (Camera->OrthoNearClipPlane != OrthoNearClipPlane) - return true; - - if (Camera->OrthoFarClipPlane != OrthoFarClipPlane) - return true; - } - - return false; -} - - - -void -UHoudiniInputCameraComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); - - ensure(Camera); - - if (Camera && !Camera->IsPendingKill()) - { - bIsOrthographic = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; - FOV = Camera->FieldOfView; - AspectRatio = Camera->AspectRatio; - OrthoWidth = Camera->OrthoWidth; - OrthoNearClipPlane = Camera->OrthoNearClipPlane; - OrthoFarClipPlane = Camera->OrthoFarClipPlane; - } -} - -void -UHoudiniInputMeshComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UStaticMeshComponent* SMC = Cast(InObject); - - ensure(SMC); - - if (SMC) - { - StaticMesh = TSoftObjectPtr(SMC->GetStaticMesh()); - - TArray Materials = SMC->GetMaterials(); - for (auto CurrentMat : Materials) - { - // TODO: Update material ref here - FString MatRef; - MeshComponentsMaterials.Add(MatRef); - } - } -} - -void -UHoudiniInputInstancedMeshComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - UInstancedStaticMeshComponent* ISMC = Cast(InObject); - - ensure(ISMC); - - if (ISMC) - { - uint32 InstanceCount = ISMC->GetInstanceCount(); - InstanceTransforms.SetNum(InstanceCount); - - // Copy the instances' transforms - for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) - { - FTransform CurTransform = FTransform::Identity; - ISMC->GetInstanceTransform(InstIdx, CurTransform); - InstanceTransforms[InstIdx] = CurTransform; - } - } -} - -bool -UHoudiniInputInstancedMeshComponent::HasInstancesChanged() const -{ - UInstancedStaticMeshComponent* ISMC = Cast(InputObject.LoadSynchronous()); - if (!ISMC) - return false; - - uint32 InstanceCount = ISMC->GetInstanceCount(); - if (InstanceTransforms.Num() != InstanceCount) - return true; - - // Copy the instances' transforms - for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) - { - FTransform CurTransform = FTransform::Identity; - ISMC->GetInstanceTransform(InstIdx, CurTransform); - - if(!InstanceTransforms[InstIdx].Equals(CurTransform)) - return true; - } - - return false; -} - -bool -UHoudiniInputInstancedMeshComponent::HasComponentTransformChanged() const -{ - if (Super::HasComponentTransformChanged()) - return true; - - return HasInstancesChanged(); -} - -void -UHoudiniInputSplineComponent::Update(UObject * InObject) -{ - Super::Update(InObject); - - USplineComponent* Spline = Cast(InObject); - - ensure(Spline); - - if (Spline) - { - NumberOfSplineControlPoints = Spline->GetNumberOfSplinePoints(); - SplineLength = Spline->GetSplineLength(); - SplineClosed = Spline->IsClosedLoop(); - - //SplineResolution = -1.0f; - - SplineControlPoints.SetNumZeroed(NumberOfSplineControlPoints); - for (int32 Idx = 0; Idx < NumberOfSplineControlPoints; Idx++) - { - SplineControlPoints[Idx] = Spline->GetTransformAtSplinePoint(Idx, ESplineCoordinateSpace::Local); - } - } -} - -void -UHoudiniInputHoudiniSplineComponent::Update(UObject* InObject) -{ - Super::Update(InObject); - - // We store the component references as a normal pointer property instead of using a soft object reference. - // If we use a soft object reference, the editor will complain about deleting a reference that is in use - // everytime we try to delete the actor, even though everything is contained within the actor. - - CachedComponent = Cast(InObject); - InputObject = nullptr; - - // We need a strong ref to the spline component to prevent it from being GCed - //MyHoudiniSplineComponent = Cast(InObject); - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) - { - // Use default values - CurveType = EHoudiniCurveType::Polygon; - CurveMethod = EHoudiniCurveMethod::CVs; - Reversed = false; - } - else - { - CurveType = HoudiniSplineComponent->GetCurveType(); - CurveMethod = HoudiniSplineComponent->GetCurveMethod(); - Reversed = false;//Spline->IsReversed(); - } -} - -UObject* -UHoudiniInputHoudiniSplineComponent::GetObject() const -{ - return CachedComponent; -} - -void -UHoudiniInputHoudiniSplineComponent::MarkChanged(const bool& bInChanged) -{ - Super::MarkChanged(bInChanged); - - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent) - { - HoudiniSplineComponent->MarkChanged(bInChanged); - } -} - -void -UHoudiniInputHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) -{ - Super::SetNeedsToTriggerUpdate(bInTriggersUpdate); - - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent) - { - HoudiniSplineComponent->SetNeedsToTriggerUpdate(bInTriggersUpdate); - } -} - -bool -UHoudiniInputHoudiniSplineComponent::HasChanged() const -{ - if (Super::HasChanged()) - return true; - - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent && HoudiniSplineComponent->HasChanged()) - return true; - - return false; -} - -bool -UHoudiniInputHoudiniSplineComponent::NeedsToTriggerUpdate() const -{ - if (Super::NeedsToTriggerUpdate()) - return true; - UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (HoudiniSplineComponent && HoudiniSplineComponent->NeedsToTriggerUpdate()) - return true; - - return false; -} - -void -UHoudiniInputHoudiniAsset::Update(UObject * InObject) -{ - Super::Update(InObject); - - UHoudiniAssetComponent* HAC = Cast(InObject); - - ensure(HAC); - - if (HAC) - { - // TODO: Notify HAC that we're a downstream? - InputNodeId = HAC->GetAssetId(); - InputObjectNodeId = HAC->GetAssetId(); - - // TODO: Allow selection of the asset output - AssetOutputIndex = 0; - } -} - - -void -UHoudiniInputActor::Update(UObject * InObject) -{ - const bool bHasInputObjectChanged = InputObject != InObject; - - Super::Update(InObject); - - AActor* Actor = Cast(InObject); - ensure(Actor); - - if (Actor) - { - Transform = Actor->GetTransform(); - - // If we are updating (InObject == InputObject), then remove stale components and add new components, - // if InObject != InputObject, remove all components and rebuild - - if (bHasInputObjectChanged) - { - // The actor's components that can be sent as inputs - LastUpdateNumComponentsRemoved = ActorComponents.Num(); - - ActorComponents.Empty(); - ActorSceneComponents.Empty(); - - TArray AllComponents; - Actor->GetComponents(AllComponents, true); - - int32 CompIdx = 0; - ActorComponents.SetNum(AllComponents.Num()); - for (USceneComponent * SceneComponent : AllComponents) - { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; - - UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( - SceneComponent, GetOuter(), Actor->GetName()); - if (!InputObj) - continue; - - UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); - if (!SceneInput) - continue; - - ActorComponents[CompIdx++] = SceneInput; - ActorSceneComponents.Add(TSoftObjectPtr(SceneComponent)); - } - ActorComponents.SetNum(CompIdx); - LastUpdateNumComponentsAdded = CompIdx; - } - else - { - LastUpdateNumComponentsAdded = 0; - LastUpdateNumComponentsRemoved = 0; - - // Look for any components to add or remove - TSet NewComponents; - const bool bIncludeFromChildActors = true; - Actor->ForEachComponent(bIncludeFromChildActors, [&](USceneComponent* InComp) - { - if (IsValid(InComp)) - { - if (!ActorSceneComponents.Contains(InComp)) - { - NewComponents.Add(InComp); - } - } - }); - - // Update the actor input components (from the same actor) - TArray ComponentIndicesToRemove; - const int32 NumActorComponents = ActorComponents.Num(); - for (int32 Index = 0; Index < NumActorComponents; ++Index) - { - UHoudiniInputSceneComponent* CurActorComp = ActorComponents[Index]; - if (!CurActorComp || CurActorComp->IsPendingKill()) - { - ComponentIndicesToRemove.Add(Index); - continue; - } - - // Does the component still exist on Actor? - UObject* const CompObj = CurActorComp->GetObject(); - // Make sure the actor is still valid - if (!CompObj || CompObj->IsPendingKill()) - { - // If it's not, mark it for deletion - if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) - { - CurActorComp->InvalidateData(); - } - - ComponentIndicesToRemove.Add(Index); - continue; - } - } - - // Remove the destroyed/invalid components - const int32 NumToRemove = ComponentIndicesToRemove.Num(); - if (NumToRemove > 0) - { - for (int32 Index = NumToRemove - 1; Index >= 0; --Index) - { - const int32& IndexToRemove = ComponentIndicesToRemove[Index]; - - UHoudiniInputSceneComponent* const CurActorComp = ActorComponents[IndexToRemove]; - if (CurActorComp) - ActorSceneComponents.Remove(CurActorComp->InputObject); - - const bool bAllowShrink = false; - ActorComponents.RemoveAtSwap(IndexToRemove, 1, bAllowShrink); - - LastUpdateNumComponentsRemoved++; - } - } - - if (NewComponents.Num() > 0) - { - for (USceneComponent * SceneComponent : NewComponents) - { - if (!SceneComponent || SceneComponent->IsPendingKill()) - continue; - - UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( - SceneComponent, GetOuter(), Actor->GetName()); - if (!InputObj) - continue; - - UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); - if (!SceneInput) - continue; - - ActorComponents.Add(SceneInput); - ActorSceneComponents.Add(SceneComponent); - - LastUpdateNumComponentsAdded++; - } - } - - if (LastUpdateNumComponentsAdded > 0 || LastUpdateNumComponentsRemoved > 0) - { - ActorComponents.Shrink(); - } - } - } - else - { - // If we don't have a valid actor or null, delete any input components we still have and mark as changed - if (ActorComponents.Num() > 0) - { - LastUpdateNumComponentsAdded = 0; - LastUpdateNumComponentsRemoved = ActorComponents.Num(); - ActorComponents.Empty(); - ActorSceneComponents.Empty(); - } - else - { - LastUpdateNumComponentsAdded = 0; - LastUpdateNumComponentsRemoved = 0; - } - } -} - -bool -UHoudiniInputActor::HasActorTransformChanged() -{ - if (!GetActor()) - return false; - - if (!Transform.Equals(GetActor()->GetTransform())) - return true; - - return false; -} - -bool -UHoudiniInputActor::HasContentChanged() const -{ - return false; -} - -bool -UHoudiniInputLandscape::HasActorTransformChanged() -{ - return Super::HasActorTransformChanged(); - //return false; -} - -void -UHoudiniInputLandscape::Update(UObject * InObject) -{ - Super::Update(InObject); - - ALandscapeProxy* Landscape = Cast(InObject); - - //ensure(Landscape); - - if (Landscape) - { - // Nothing to do for landscapes? - } -} - -EHoudiniInputObjectType -UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) -{ - if (InObject->IsA(USceneComponent::StaticClass())) - { - // Handle component inputs - // UISMC derived from USMC, so always test instances before static meshes - if (InObject->IsA(UInstancedStaticMeshComponent::StaticClass())) - { - return EHoudiniInputObjectType::InstancedStaticMeshComponent; - } - else if (InObject->IsA(UStaticMeshComponent::StaticClass())) - { - return EHoudiniInputObjectType::StaticMeshComponent; - } - else if (InObject->IsA(USplineComponent::StaticClass())) - { - return EHoudiniInputObjectType::SplineComponent; - } - else if (InObject->IsA(UHoudiniSplineComponent::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniSplineComponent; - } - else if (InObject->IsA(UHoudiniAssetComponent::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniAssetComponent; - } - else if (InObject->IsA(UCameraComponent::StaticClass())) - { - return EHoudiniInputObjectType::CameraComponent; - } - else - { - return EHoudiniInputObjectType::SceneComponent; - } - } - else if (InObject->IsA(AActor::StaticClass())) - { - // Handle actors - if (InObject->IsA(ALandscapeProxy::StaticClass())) - { - return EHoudiniInputObjectType::Landscape; - } - else if (InObject->IsA(ABrush::StaticClass())) - { - return EHoudiniInputObjectType::Brush; - } - else if (InObject->IsA(AHoudiniAssetActor::StaticClass())) - { - return EHoudiniInputObjectType::HoudiniAssetActor; - } - else - { - return EHoudiniInputObjectType::Actor; - } - } - else if (InObject->IsA(UBlueprint::StaticClass())) - { - return EHoudiniInputObjectType::StaticMesh; - } - else if (InObject->IsA(UFoliageType_InstancedStaticMesh::StaticClass())) - { - return EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; - } - else - { - if (InObject->IsA(UStaticMesh::StaticClass())) - { - return EHoudiniInputObjectType::StaticMesh; - } - else if (InObject->IsA(USkeletalMesh::StaticClass())) - { - return EHoudiniInputObjectType::SkeletalMesh; - } - else if (InObject->IsA(UDataTable::StaticClass())) - { - return EHoudiniInputObjectType::DataTable; - } - else - { - return EHoudiniInputObjectType::Object; - } - } - - return EHoudiniInputObjectType::Invalid; -} - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniInputBrush -//----------------------------------------------------------------------------------------------------------------------------- - -FHoudiniBrushInfo::FHoudiniBrushInfo() - : CachedTransform() - , CachedOrigin(ForceInitToZero) - , CachedExtent(ForceInitToZero) - , CachedBrushType(EBrushType::Brush_Default) - , CachedSurfaceHash(0) -{ -} - -FHoudiniBrushInfo::FHoudiniBrushInfo(ABrush* InBrushActor) -{ - if (!InBrushActor) - return; - - BrushActor = InBrushActor; - CachedTransform = BrushActor->GetActorTransform(); - BrushActor->GetActorBounds(false, CachedOrigin, CachedExtent); - CachedBrushType = BrushActor->BrushType; - -#if WITH_EDITOR - UModel* Model = BrushActor->Brush; - - // Cache the hash of the surface properties - if (IsValid(Model) && IsValid(Model->Polys)) - { - int32 NumPolys = Model->Polys->Element.Num(); - CachedSurfaceHash = 0; - for(int32 iPoly = 0; iPoly < NumPolys; ++iPoly) - { - const FPoly& Poly = Model->Polys->Element[iPoly]; - CombinePolyHash(CachedSurfaceHash, Poly); - } - } - else - { - CachedSurfaceHash = 0; - } -#endif -} - -bool FHoudiniBrushInfo::HasChanged() const -{ - if (!BrushActor.IsValid()) - return false; - - // Has the transform changed? - if (!BrushActor->GetActorTransform().Equals(CachedTransform)) - return true; - - if (BrushActor->BrushType != CachedBrushType) - return true; - - // Has the actor bounds changed? - FVector TmpOrigin, TmpExtent; - BrushActor->GetActorBounds(false, TmpOrigin, TmpExtent); - - if (!(TmpOrigin.Equals(CachedOrigin) && TmpExtent.Equals(CachedExtent) )) - return true; -#if WITH_EDITOR - // Is there a tracked surface property that changed? - UModel* Model = BrushActor->Brush; - if (IsValid(Model) && IsValid(Model->Polys)) - { - // Hash the incoming surface properties and compared it against the cached hash. - int32 NumPolys = Model->Polys->Element.Num(); - uint64 SurfaceHash = 0; - for (int32 iPoly = 0; iPoly < NumPolys; ++iPoly) - { - const FPoly& Poly = Model->Polys->Element[iPoly]; - CombinePolyHash(SurfaceHash, Poly); - } - if (SurfaceHash != CachedSurfaceHash) - return true; - } - else - { - if (CachedSurfaceHash != 0) - return true; - } -#endif - return false; -} - -int32 FHoudiniBrushInfo::GetNumVertexIndicesFromModel(const UModel* Model) -{ - const TArray& Nodes = Model->Nodes; - int32 NumIndices = 0; - // Build the face counts buffer by iterating over the BSP nodes. - for(const FBspNode& Node : Nodes) - { - NumIndices += Node.NumVertices; - } - return NumIndices; -} - -UModel* UHoudiniInputBrush::GetCachedModel() const -{ - return CombinedModel; -} - -bool UHoudiniInputBrush::HasBrushesChanged(const TArray& InBrushes) const -{ - if (InBrushes.Num() != BrushesInfo.Num()) - return true; - - int32 NumBrushes = BrushesInfo.Num(); - - for (int32 InfoIndex = 0; InfoIndex < NumBrushes; ++InfoIndex) - { - const FHoudiniBrushInfo& BrushInfo = BrushesInfo[InfoIndex]; - // Has the cached brush actor invalid? - if (!BrushInfo.BrushActor.IsValid()) - return true; - - // Has there been an order change in the actors list? - if (InBrushes[InfoIndex] != BrushInfo.BrushActor.Get()) - return true; - - // Has there been any other changes to the brush? - if (BrushInfo.HasChanged()) - return true; - } - - // Nothing has changed. - return false; -} - -void UHoudiniInputBrush::UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes) -{ - ABrush* InputBrush = GetBrush(); - if (IsValid(InputBrush)) - { - CachedInputBrushType = InputBrush->BrushType; - } - - // Cache the combined model aswell as the brushes used to generate this model. - CombinedModel = InCombinedModel; - - BrushesInfo.SetNumUninitialized(InBrushes.Num()); - for (int i = 0; i < InBrushes.Num(); ++i) - { - if (!InBrushes[i]) - continue; - BrushesInfo[i] = FHoudiniBrushInfo(InBrushes[i]); - } -} - - -void -UHoudiniInputBrush::Update(UObject * InObject) -{ - Super::Update(InObject); - - ABrush* BrushActor = GetBrush(); - if (!IsValid(BrushActor)) - { - bIgnoreInputObject = true; - return; - } - - CachedInputBrushType = BrushActor->BrushType; - - bIgnoreInputObject = ShouldIgnoreThisInput(); -} - -bool -UHoudiniInputBrush::ShouldIgnoreThisInput() -{ - // Invalid brush, should be ignored - ABrush* BrushActor = GetBrush(); - ensure(BrushActor); - if (!BrushActor) - return true; - - // If the BrushType has changed since caching this object, this object cannot be ignored. - if (CachedInputBrushType != BrushActor->BrushType) - return false; - - // If it's not an additive brush, we want to ignore it - bool bShouldBeIgnored = BrushActor->BrushType != EBrushType::Brush_Add; - - // If this is not a static brush (e.g., AVolume), ignore it. - if (!bShouldBeIgnored) - bShouldBeIgnored = !BrushActor->IsStaticBrush(); - - return bShouldBeIgnored; -} - -bool UHoudiniInputBrush::HasContentChanged() const -{ - ABrush* BrushActor = GetBrush(); - ensure(BrushActor); - - if (!BrushActor) - return false; - - if (BrushActor->BrushType != CachedInputBrushType) - return true; - - if (bIgnoreInputObject) - return false; - - // Find intersecting actors and capture their properties so that - // we can determine whether something has changed. - TArray IntersectingBrushes; - FindIntersectingSubtractiveBrushes(this, IntersectingBrushes); - - if (HasBrushesChanged(IntersectingBrushes)) - { - return true; - } - - return false; -} - -bool -UHoudiniInputBrush::HasActorTransformChanged() -{ - if (bIgnoreInputObject) - return false; - - return Super::HasActorTransformChanged(); -} - - -bool UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes) -{ - TArray IntersectingActors; - TArray Bounds; - - - if (!IsValid(InputBrush)) - return false; - - ABrush* BrushActor = InputBrush->GetBrush(); - if (!IsValid(BrushActor)) - return false; - - - OutBrushes.Empty(); - - Bounds.Add( BrushActor->GetComponentsBoundingBox(true, true) ); - - FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(BrushActor->GetWorld(), ABrush::StaticClass(), Bounds, nullptr, IntersectingActors); - - //-------------------------------------------------------------------------------------------------- - // Filter the actors to only keep intersecting subtractive brushes. - //-------------------------------------------------------------------------------------------------- - for (AActor* Actor : IntersectingActors) - { - // Filter out anything that is not a static brush (typically volume actors). - ABrush* Brush = Cast(Actor); - - // NOTE: The brush actor needs to be added in the correct map/level order - // together with the subtractive brushes otherwise the CSG operations - // will not match the BSP in the level. - if (Actor == BrushActor) - OutBrushes.Add(Brush); - - if (!(Brush && Brush->IsStaticBrush())) - continue; - - if (Brush->BrushType == Brush_Subtract) - OutBrushes.Add(Brush); - } - - return true; -} - -#if WITH_EDITOR -void -UHoudiniInputObject::PostEditUndo() -{ - Super::PostEditUndo(); - MarkChanged(true); -} -#endif - -UHoudiniInputObject* -UHoudiniInputObject::DuplicateAndCopyState(UObject * DestOuter) -{ - UHoudiniInputObject* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); - NewInput->CopyStateFrom(this, false); - return NewInput; -} - -void -UHoudiniInputObject::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References should be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - } - - InputNodeId = InInput->InputNodeId; - InputObjectNodeId = InInput->InputObjectNodeId; - bHasChanged = InInput->bHasChanged; - bNeedsToTriggerUpdate = InInput->bNeedsToTriggerUpdate; - bTransformChanged = InInput->bTransformChanged; - Guid = InInput->Guid; - -#if WITH_EDITORONLY_DATA - bUniformScaleLocked = InInput->bUniformScaleLocked; -#endif - -} - -void -UHoudiniInputObject::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; -} - - -// -UHoudiniInputDataTable::UHoudiniInputDataTable(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -UHoudiniInputObject * -UHoudiniInputDataTable::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_DT_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputDataTable::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputDataTable * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputDataTable::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::DataTable; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -UDataTable* -UHoudiniInputDataTable::GetDataTable() const -{ - return Cast(InputObject.LoadSynchronous()); -} - -UHoudiniInputFoliageType_InstancedStaticMesh::UHoudiniInputFoliageType_InstancedStaticMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -UHoudiniInputObject* -UHoudiniInputFoliageType_InstancedStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) -{ - FString InputObjectNameStr = "HoudiniInputObject_FoliageSM_" + InName; - FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), *InputObjectNameStr); - - // We need to create a new object - UHoudiniInputFoliageType_InstancedStaticMesh * HoudiniInputObject = NewObject( - InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); - - HoudiniInputObject->Type = EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; - HoudiniInputObject->Update(InObject); - HoudiniInputObject->bHasChanged = true; - - return HoudiniInputObject; -} - -void -UHoudiniInputFoliageType_InstancedStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) -{ - UHoudiniInputFoliageType_InstancedStaticMesh* FoliageTypeSM = Cast(InInput); - if (!IsValid(FoliageTypeSM)) - return; - - UHoudiniInputObject::CopyStateFrom(FoliageTypeSM, bCopyAllProperties); - - // BlueprintStaticMeshes array is not used in UHoudiniInputFoliageType_InstancedStaticMesh - BlueprintStaticMeshes.Empty(); -} - -void -UHoudiniInputFoliageType_InstancedStaticMesh::Update(UObject * InObject) -{ - UHoudiniInputObject::Update(InObject); - UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InObject); - ensure(FoliageType); - ensure(FoliageType->GetStaticMesh()); -} - -UStaticMesh* -UHoudiniInputFoliageType_InstancedStaticMesh::GetStaticMesh() const -{ - if (!InputObject.IsValid()) - return nullptr; - - UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InputObject.LoadSynchronous()); - if (!IsValid(FoliageType)) - return nullptr; - - return FoliageType->GetStaticMesh(); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInputObject.h" + +#include "HoudiniEngineRuntime.h" +#include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniSplineComponent.h" +#include "HoudiniInput.h" + +#include "Engine/StaticMesh.h" +#include "Engine/SkeletalMesh.h" +#include "Engine/DataTable.h" +#include "Components/StaticMeshComponent.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/SplineComponent.h" +#include "Landscape.h" +#include "Engine/Brush.h" +#include "Engine/Engine.h" +#include "GameFramework/Volume.h" +#include "Camera/CameraComponent.h" +#include "FoliageType_InstancedStaticMesh.h" + +#include "Model.h" +#include "Engine/Brush.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "Kismet/KismetSystemLibrary.h" + +//----------------------------------------------------------------------------------------------------------------------------- +// Constructors +//----------------------------------------------------------------------------------------------------------------------------- + +// +UHoudiniInputObject::UHoudiniInputObject(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , Transform(FTransform::Identity) + , Type(EHoudiniInputObjectType::Invalid) + , InputNodeId(-1) + , InputObjectNodeId(-1) + , bHasChanged(false) + , bNeedsToTriggerUpdate(false) + , bTransformChanged(false) + , bImportAsReference(false) + , bCanDeleteHoudiniNodes(true) +{ + Guid = FGuid::NewGuid(); +} + +// +UHoudiniInputStaticMesh::UHoudiniInputStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSkeletalMesh::UHoudiniInputSkeletalMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSceneComponent::UHoudiniInputSceneComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputMeshComponent::UHoudiniInputMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputInstancedMeshComponent::UHoudiniInputInstancedMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputSplineComponent::UHoudiniInputSplineComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , NumberOfSplineControlPoints(-1) + , SplineLength(-1.0f) + , SplineResolution(-1.0f) + , SplineClosed(false) +{ + +} + +// +UHoudiniInputCameraComponent::UHoudiniInputCameraComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , FOV(0.0f) + , AspectRatio(1.0f) + , bIsOrthographic(false) + , OrthoWidth(2.0f) + , OrthoNearClipPlane(0.0f) + , OrthoFarClipPlane(-1.0f) +{ + +} + +// Returns true if the attached actor's (parent) transform has been modified +bool +UHoudiniInputSplineComponent::HasActorTransformChanged() const +{ + return false; +} + +// Returns true if the attached component's transform has been modified +bool +UHoudiniInputSplineComponent::HasComponentTransformChanged() const +{ + return false; +} + +// Return true if the component itself has been modified +bool +UHoudiniInputSplineComponent::HasComponentChanged() const +{ + USplineComponent* SplineComponent = Cast(InputObject.LoadSynchronous()); + + if (!SplineComponent) + return false; + + if (SplineClosed != SplineComponent->IsClosedLoop()) + return true; + + + if (SplineComponent->GetNumberOfSplinePoints() != NumberOfSplineControlPoints) + return true; + + for (int32 n = 0; n < SplineComponent->GetNumberOfSplinePoints(); ++n) + { + const FTransform &CurSplineComponentTransform = SplineComponent->GetTransformAtSplinePoint(n, ESplineCoordinateSpace::Local); + const FTransform &CurInputTransform = SplineControlPoints[n]; + + if (CurInputTransform.GetLocation() != CurSplineComponentTransform.GetLocation()) + return true; + + if (CurInputTransform.GetRotation().Rotator() != CurSplineComponentTransform.GetRotation().Rotator()) + return true; + + if (CurInputTransform.GetScale3D() != CurSplineComponentTransform.GetScale3D()) + return true; + } + + return false; +} + +bool +UHoudiniInputSplineComponent::HasSplineComponentChanged(float fCurrentSplineResolution) const +{ + return false; +} + +// +UHoudiniInputHoudiniSplineComponent::UHoudiniInputHoudiniSplineComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , CurveType(EHoudiniCurveType::Polygon) + , CurveMethod(EHoudiniCurveMethod::CVs) + , Reversed(false) +{ + +} + +// +UHoudiniInputHoudiniAsset::UHoudiniInputHoudiniAsset(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , AssetOutputIndex(-1) +{ + +} + +// +UHoudiniInputActor::UHoudiniInputActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , LastUpdateNumComponentsAdded(0) + , LastUpdateNumComponentsRemoved(0) +{ + +} + +// +UHoudiniInputLandscape::UHoudiniInputLandscape(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +// +UHoudiniInputBrush::UHoudiniInputBrush() + : CombinedModel(nullptr) + , bIgnoreInputObject(false) +{ + +} + +//----------------------------------------------------------------------------------------------------------------------------- +// Accessors +//----------------------------------------------------------------------------------------------------------------------------- + +UObject* +UHoudiniInputObject::GetObject() const +{ + return InputObject.LoadSynchronous(); +} + +UStaticMesh* +UHoudiniInputStaticMesh::GetStaticMesh() const +{ + return Cast(InputObject.LoadSynchronous()); +} + +UBlueprint* +UHoudiniInputStaticMesh::GetBlueprint() const +{ + return Cast(InputObject.LoadSynchronous()); +} + +bool UHoudiniInputStaticMesh::bIsBlueprint() const +{ + return (InputObject.IsValid() && InputObject.Get()->IsA()); +} + +USkeletalMesh* +UHoudiniInputSkeletalMesh::GetSkeletalMesh() +{ + return Cast(InputObject.LoadSynchronous()); +} + +USceneComponent* +UHoudiniInputSceneComponent::GetSceneComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UStaticMeshComponent* +UHoudiniInputMeshComponent::GetStaticMeshComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UStaticMesh* +UHoudiniInputMeshComponent::GetStaticMesh() +{ + return StaticMesh.Get(); +} + +UInstancedStaticMeshComponent* +UHoudiniInputInstancedMeshComponent::GetInstancedStaticMeshComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +USplineComponent* +UHoudiniInputSplineComponent::GetSplineComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniSplineComponent* +UHoudiniInputHoudiniSplineComponent::GetCurveComponent() const +{ + return Cast(GetObject()); + //return Cast(InputObject.LoadSynchronous()); +} + +UCameraComponent* +UHoudiniInputCameraComponent::GetCameraComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniAssetComponent* +UHoudiniInputHoudiniAsset::GetHoudiniAssetComponent() +{ + return Cast(InputObject.LoadSynchronous()); +} + +AActor* +UHoudiniInputActor::GetActor() +{ + return Cast(InputObject.LoadSynchronous()); +} + +ALandscapeProxy* +UHoudiniInputLandscape::GetLandscapeProxy() +{ + return Cast(InputObject.LoadSynchronous()); +} + +void +UHoudiniInputLandscape::SetLandscapeProxy(UObject* InLandscapeProxy) +{ + UObject* LandscapeProxy = Cast(InLandscapeProxy); + if (LandscapeProxy) + InputObject = LandscapeProxy; +} + +ABrush* +UHoudiniInputBrush::GetBrush() const +{ + return Cast(InputObject.LoadSynchronous()); +} + + +//----------------------------------------------------------------------------------------------------------------------------- +// CREATE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +UHoudiniInputObject * +UHoudiniInputObject::CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InName) +{ + if (!InObject) + return nullptr; + + UHoudiniInputObject* HoudiniInputObject = nullptr; + + EHoudiniInputObjectType InputObjectType = GetInputObjectTypeFromObject(InObject); + switch (InputObjectType) + { + case EHoudiniInputObjectType::Object: + HoudiniInputObject = UHoudiniInputObject::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::StaticMesh: + HoudiniInputObject = UHoudiniInputStaticMesh::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::SkeletalMesh: + HoudiniInputObject = UHoudiniInputSkeletalMesh::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::SceneComponent: + // Do not create input objects for unknown scene component! + //HoudiniInputObject = UHoudiniInputSceneComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::StaticMeshComponent: + HoudiniInputObject = UHoudiniInputMeshComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::InstancedStaticMeshComponent: + HoudiniInputObject = UHoudiniInputInstancedMeshComponent::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::SplineComponent: + HoudiniInputObject = UHoudiniInputSplineComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::HoudiniSplineComponent: + HoudiniInputObject = UHoudiniInputHoudiniSplineComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::HoudiniAssetActor: + { + AHoudiniAssetActor* HoudiniActor = Cast(InObject); + if (HoudiniActor) + { + HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(HoudiniActor->GetHoudiniAssetComponent(), InOuter, InName); + } + else + { + HoudiniInputObject = nullptr; + } + } + break; + + case EHoudiniInputObjectType::HoudiniAssetComponent: + HoudiniInputObject = UHoudiniInputHoudiniAsset::Create(InObject, InOuter, InName); + break; + case EHoudiniInputObjectType::Actor: + HoudiniInputObject = UHoudiniInputActor::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Landscape: + HoudiniInputObject = UHoudiniInputLandscape::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Brush: + HoudiniInputObject = UHoudiniInputBrush::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::CameraComponent: + HoudiniInputObject = UHoudiniInputCameraComponent::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::DataTable: + HoudiniInputObject = UHoudiniInputDataTable::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::FoliageType_InstancedStaticMesh: + HoudiniInputObject = UHoudiniInputFoliageType_InstancedStaticMesh::Create(InObject, InOuter, InName); + break; + + case EHoudiniInputObjectType::Invalid: + default: + break; + } + + return HoudiniInputObject; +} + + +UHoudiniInputObject * +UHoudiniInputInstancedMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_ISMC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputInstancedMeshComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputInstancedMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::InstancedStaticMeshComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputMeshComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SMC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputMeshComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputMeshComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputMeshComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMeshComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Spline_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSplineComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSplineComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SplineComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputHoudiniSplineComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_HoudiniSpline_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputHoudiniSplineComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputHoudiniSplineComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniSplineComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputCameraComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Camera_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputCameraComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputCameraComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputCameraComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::CameraComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputHoudiniAsset::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + UHoudiniAssetComponent * InHoudiniAssetComponent = Cast(InObject); + if (!InHoudiniAssetComponent) + return nullptr; + + FString InputObjectNameStr = "HoudiniInputObject_HAC_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputHoudiniAsset::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputHoudiniAsset * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputHoudiniAsset::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::HoudiniAssetComponent; + + HoudiniInputObject->InputNodeId = InHoudiniAssetComponent->GetAssetId(); + HoudiniInputObject->InputObjectNodeId = InHoudiniAssetComponent->GetAssetId(); + + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputSceneComponent::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SceneComp_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSceneComponent::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSceneComponent * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSceneComponent::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SceneComponent; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputLandscape::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Landscape_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputLandscape::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputLandscape * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputLandscape::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Landscape; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputBrush * +UHoudiniInputBrush::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Brush_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputBrush::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputBrush * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputBrush::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Brush; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputActor::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_Actor_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputActor::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputActor * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputActor::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Actor; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SM_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputStaticMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputStaticMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::StaticMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +// void UHoudiniInputStaticMesh::DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputStaticMesh*& OutNewInput) +// { +// UHoudiniInputStaticMesh* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); +// OutNewInput = NewInput; +// OutNewInput->CopyStateFrom(this, false); +// } + +void +UHoudiniInputStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + UHoudiniInputStaticMesh* StaticMeshInput = Cast(InInput); + check(InInput); + + TArray PrevInputs = BlueprintStaticMeshes; + + Super::CopyStateFrom(StaticMeshInput, bCopyAllProperties); + + const int32 NumInputs = StaticMeshInput->BlueprintStaticMeshes.Num(); + BlueprintStaticMeshes = PrevInputs; + TArray StaleInputs(BlueprintStaticMeshes); + + BlueprintStaticMeshes.SetNum(NumInputs); + + for (int i = 0; i < NumInputs; ++i) + { + UHoudiniInputStaticMesh* FromInput = StaticMeshInput->BlueprintStaticMeshes[i]; + UHoudiniInputStaticMesh* ToInput = BlueprintStaticMeshes[i]; + + if (!FromInput) + { + BlueprintStaticMeshes[i] = nullptr; + continue; + } + + if (ToInput) + { + // Check whether the ToInput can be reused + bool bIsValid = true; + bIsValid = bIsValid && ToInput->Matches(*FromInput); + bIsValid = bIsValid && ToInput->GetOuter() == this; + if (!bIsValid) + { + ToInput = nullptr; + } + } + + if (ToInput) + { + // We have a reusable input + ToInput->CopyStateFrom(FromInput, true); + } + else + { + // We need to create a new input + ToInput = Cast(FromInput->DuplicateAndCopyState(this)); + } + + BlueprintStaticMeshes[i] = ToInput; + } + + for(UHoudiniInputStaticMesh* StaleInput : StaleInputs) + { + if (!StaleInput) + continue; + StaleInput->InvalidateData(); + } +} + +void +UHoudiniInputStaticMesh::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + Super::SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) + { + if (!Input) + continue; + Input->SetCanDeleteHoudiniNodes(bInCanDeleteNodes); + } +} + +void +UHoudiniInputStaticMesh::InvalidateData() +{ + for(UHoudiniInputStaticMesh* Input : BlueprintStaticMeshes) + { + if (!Input) + continue; + Input->InvalidateData(); + } + + Super::InvalidateData(); +} + + +UHoudiniInputObject * +UHoudiniInputSkeletalMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_SkelMesh_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputSkeletalMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputSkeletalMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputSkeletalMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::SkeletalMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UHoudiniInputObject * +UHoudiniInputObject::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputObject::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputObject * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputObject::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::Object; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +bool +UHoudiniInputObject::Matches(const UHoudiniInputObject& Other) const +{ + return (Type == Other.Type + && InputNodeId == Other.InputNodeId + && InputObjectNodeId == Other.InputObjectNodeId + ); +} + +//----------------------------------------------------------------------------------------------------------------------------- +// DELETE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +void +UHoudiniInputObject::InvalidateData() +{ + // If valid, mark our input nodes for deletion.. + if (this->IsA() || !bCanDeleteHoudiniNodes) + { + // Unless if we're a HoudiniAssetInput! we don't want to delete the other HDA's node! + // just invalidate the node IDs! + InputNodeId = -1; + InputObjectNodeId = -1; + return; + } + + if (InputNodeId >= 0) + { + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); + InputNodeId = -1; + } + + // ... and the parent OBJ as well to clean up + if (InputObjectNodeId >= 0) + { + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputObjectNodeId); + InputObjectNodeId = -1; + } + + +} + +void +UHoudiniInputObject::BeginDestroy() +{ + // Invalidate and mark our input node for deletion + InvalidateData(); + + Super::BeginDestroy(); +} + +//----------------------------------------------------------------------------------------------------------------------------- +// UPDATE METHODS +//----------------------------------------------------------------------------------------------------------------------------- + +void +UHoudiniInputObject::Update(UObject * InObject) +{ + InputObject = InObject; +} + +void +UHoudiniInputStaticMesh::Update(UObject * InObject) +{ + // Nothing to do + Super::Update(InObject); + // Static Mesh input accepts SM, BP, FoliageType_InstancedStaticMesh (static mesh) and FoliageType_Actor (if blueprint actor). + UStaticMesh* SM = Cast(InObject); + UBlueprint* BP = Cast(InObject); + + ensure(SM || BP); +} + +void +UHoudiniInputSkeletalMesh::Update(UObject * InObject) +{ + // Nothing to do + Super::Update(InObject); + + USkeletalMesh* SkelMesh = Cast(InObject); + ensure(SkelMesh); +} + +void +UHoudiniInputSceneComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + USceneComponent* USC = Cast(InObject); + ensure(USC); + if (USC) + { + Transform = USC->GetComponentTransform(); + } +} + + +bool +UHoudiniInputSceneComponent::HasActorTransformChanged() const +{ + // Returns true if the attached actor's (parent) transform has been modified + USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); + if (!MyComp || MyComp->IsPendingKill()) + return false; + + AActor* MyActor = MyComp->GetOwner(); + if (!MyActor) + return false; + + return (!ActorTransform.Equals(MyActor->GetTransform())); +} + + +bool +UHoudiniInputSceneComponent::HasComponentTransformChanged() const +{ + // Returns true if the attached actor's (parent) transform has been modified + USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); + if (!MyComp || MyComp->IsPendingKill()) + return false; + + return !Transform.Equals(MyComp->GetComponentTransform()); +} + + +bool +UHoudiniInputSceneComponent::HasComponentChanged() const +{ + // Should return true if the component itself has been modified + // Should be overriden in child classes + return false; +} + + +bool +UHoudiniInputMeshComponent::HasComponentChanged() const +{ + UStaticMeshComponent* SMC = Cast(InputObject.LoadSynchronous()); + UStaticMesh* MySM = StaticMesh.Get(); + + // Return true if SMC's static mesh has been modified + return (MySM != SMC->GetStaticMesh()); +} + +bool +UHoudiniInputCameraComponent::HasComponentChanged() const +{ + UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); + if (Camera && !Camera->IsPendingKill()) + { + bool bOrtho = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; + if (bOrtho != bIsOrthographic) + return true; + + if (Camera->FieldOfView != FOV) + return true; + + if (Camera->AspectRatio != AspectRatio) + return true; + + if (Camera->OrthoWidth != OrthoWidth) + return true; + + if (Camera->OrthoNearClipPlane != OrthoNearClipPlane) + return true; + + if (Camera->OrthoFarClipPlane != OrthoFarClipPlane) + return true; + } + + return false; +} + + + +void +UHoudiniInputCameraComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); + + ensure(Camera); + + if (Camera && !Camera->IsPendingKill()) + { + bIsOrthographic = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; + FOV = Camera->FieldOfView; + AspectRatio = Camera->AspectRatio; + OrthoWidth = Camera->OrthoWidth; + OrthoNearClipPlane = Camera->OrthoNearClipPlane; + OrthoFarClipPlane = Camera->OrthoFarClipPlane; + } +} + +void +UHoudiniInputMeshComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UStaticMeshComponent* SMC = Cast(InObject); + + ensure(SMC); + + if (SMC) + { + StaticMesh = TSoftObjectPtr(SMC->GetStaticMesh()); + + TArray Materials = SMC->GetMaterials(); + for (auto CurrentMat : Materials) + { + // TODO: Update material ref here + FString MatRef; + MeshComponentsMaterials.Add(MatRef); + } + } +} + +void +UHoudiniInputInstancedMeshComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + UInstancedStaticMeshComponent* ISMC = Cast(InObject); + + ensure(ISMC); + + if (ISMC) + { + uint32 InstanceCount = ISMC->GetInstanceCount(); + InstanceTransforms.SetNum(InstanceCount); + + // Copy the instances' transforms + for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) + { + FTransform CurTransform = FTransform::Identity; + ISMC->GetInstanceTransform(InstIdx, CurTransform); + InstanceTransforms[InstIdx] = CurTransform; + } + } +} + +bool +UHoudiniInputInstancedMeshComponent::HasInstancesChanged() const +{ + UInstancedStaticMeshComponent* ISMC = Cast(InputObject.LoadSynchronous()); + if (!ISMC) + return false; + + uint32 InstanceCount = ISMC->GetInstanceCount(); + if (InstanceTransforms.Num() != InstanceCount) + return true; + + // Copy the instances' transforms + for (uint32 InstIdx = 0; InstIdx < InstanceCount; InstIdx++) + { + FTransform CurTransform = FTransform::Identity; + ISMC->GetInstanceTransform(InstIdx, CurTransform); + + if(!InstanceTransforms[InstIdx].Equals(CurTransform)) + return true; + } + + return false; +} + +bool +UHoudiniInputInstancedMeshComponent::HasComponentTransformChanged() const +{ + if (Super::HasComponentTransformChanged()) + return true; + + return HasInstancesChanged(); +} + +void +UHoudiniInputSplineComponent::Update(UObject * InObject) +{ + Super::Update(InObject); + + USplineComponent* Spline = Cast(InObject); + + ensure(Spline); + + if (Spline) + { + NumberOfSplineControlPoints = Spline->GetNumberOfSplinePoints(); + SplineLength = Spline->GetSplineLength(); + SplineClosed = Spline->IsClosedLoop(); + + //SplineResolution = -1.0f; + + SplineControlPoints.SetNumZeroed(NumberOfSplineControlPoints); + for (int32 Idx = 0; Idx < NumberOfSplineControlPoints; Idx++) + { + SplineControlPoints[Idx] = Spline->GetTransformAtSplinePoint(Idx, ESplineCoordinateSpace::Local); + } + } +} + +void +UHoudiniInputHoudiniSplineComponent::Update(UObject* InObject) +{ + Super::Update(InObject); + + // We store the component references as a normal pointer property instead of using a soft object reference. + // If we use a soft object reference, the editor will complain about deleting a reference that is in use + // everytime we try to delete the actor, even though everything is contained within the actor. + + CachedComponent = Cast(InObject); + InputObject = nullptr; + + // We need a strong ref to the spline component to prevent it from being GCed + //MyHoudiniSplineComponent = Cast(InObject); + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + + if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + { + // Use default values + CurveType = EHoudiniCurveType::Polygon; + CurveMethod = EHoudiniCurveMethod::CVs; + Reversed = false; + } + else + { + CurveType = HoudiniSplineComponent->GetCurveType(); + CurveMethod = HoudiniSplineComponent->GetCurveMethod(); + Reversed = false;//Spline->IsReversed(); + } +} + +UObject* +UHoudiniInputHoudiniSplineComponent::GetObject() const +{ + return CachedComponent; +} + +void +UHoudiniInputHoudiniSplineComponent::MarkChanged(const bool& bInChanged) +{ + Super::MarkChanged(bInChanged); + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent) + { + HoudiniSplineComponent->MarkChanged(bInChanged); + } +} + +void +UHoudiniInputHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) +{ + Super::SetNeedsToTriggerUpdate(bInTriggersUpdate); + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent) + { + HoudiniSplineComponent->SetNeedsToTriggerUpdate(bInTriggersUpdate); + } +} + +bool +UHoudiniInputHoudiniSplineComponent::HasChanged() const +{ + if (Super::HasChanged()) + return true; + + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent && HoudiniSplineComponent->HasChanged()) + return true; + + return false; +} + +bool +UHoudiniInputHoudiniSplineComponent::NeedsToTriggerUpdate() const +{ + if (Super::NeedsToTriggerUpdate()) + return true; + UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); + if (HoudiniSplineComponent && HoudiniSplineComponent->NeedsToTriggerUpdate()) + return true; + + return false; +} + +void +UHoudiniInputHoudiniAsset::Update(UObject * InObject) +{ + Super::Update(InObject); + + UHoudiniAssetComponent* HAC = Cast(InObject); + + ensure(HAC); + + if (HAC) + { + // TODO: Notify HAC that we're a downstream? + InputNodeId = HAC->GetAssetId(); + InputObjectNodeId = HAC->GetAssetId(); + + // TODO: Allow selection of the asset output + AssetOutputIndex = 0; + } +} + + +void +UHoudiniInputActor::Update(UObject * InObject) +{ + const bool bHasInputObjectChanged = InputObject != InObject; + + Super::Update(InObject); + + AActor* Actor = Cast(InObject); + ensure(Actor); + + if (Actor) + { + Transform = Actor->GetTransform(); + + // If we are updating (InObject == InputObject), then remove stale components and add new components, + // if InObject != InputObject, remove all components and rebuild + + if (bHasInputObjectChanged) + { + // The actor's components that can be sent as inputs + LastUpdateNumComponentsRemoved = ActorComponents.Num(); + + ActorComponents.Empty(); + ActorSceneComponents.Empty(); + + TArray AllComponents; + Actor->GetComponents(AllComponents, true); + + int32 CompIdx = 0; + ActorComponents.SetNum(AllComponents.Num()); + for (USceneComponent * SceneComponent : AllComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( + SceneComponent, GetOuter(), Actor->GetName()); + if (!InputObj) + continue; + + UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); + if (!SceneInput) + continue; + + ActorComponents[CompIdx++] = SceneInput; + ActorSceneComponents.Add(TSoftObjectPtr(SceneComponent)); + } + ActorComponents.SetNum(CompIdx); + LastUpdateNumComponentsAdded = CompIdx; + } + else + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = 0; + + // Look for any components to add or remove + TSet NewComponents; + const bool bIncludeFromChildActors = true; + Actor->ForEachComponent(bIncludeFromChildActors, [&](USceneComponent* InComp) + { + if (IsValid(InComp)) + { + if (!ActorSceneComponents.Contains(InComp)) + { + NewComponents.Add(InComp); + } + } + }); + + // Update the actor input components (from the same actor) + TArray ComponentIndicesToRemove; + const int32 NumActorComponents = ActorComponents.Num(); + for (int32 Index = 0; Index < NumActorComponents; ++Index) + { + UHoudiniInputSceneComponent* CurActorComp = ActorComponents[Index]; + if (!CurActorComp || CurActorComp->IsPendingKill()) + { + ComponentIndicesToRemove.Add(Index); + continue; + } + + // Does the component still exist on Actor? + UObject* const CompObj = CurActorComp->GetObject(); + // Make sure the actor is still valid + if (!CompObj || CompObj->IsPendingKill()) + { + // If it's not, mark it for deletion + if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) + { + CurActorComp->InvalidateData(); + } + + ComponentIndicesToRemove.Add(Index); + continue; + } + } + + // Remove the destroyed/invalid components + const int32 NumToRemove = ComponentIndicesToRemove.Num(); + if (NumToRemove > 0) + { + for (int32 Index = NumToRemove - 1; Index >= 0; --Index) + { + const int32& IndexToRemove = ComponentIndicesToRemove[Index]; + + UHoudiniInputSceneComponent* const CurActorComp = ActorComponents[IndexToRemove]; + if (CurActorComp) + ActorSceneComponents.Remove(CurActorComp->InputObject); + + const bool bAllowShrink = false; + ActorComponents.RemoveAtSwap(IndexToRemove, 1, bAllowShrink); + + LastUpdateNumComponentsRemoved++; + } + } + + if (NewComponents.Num() > 0) + { + for (USceneComponent * SceneComponent : NewComponents) + { + if (!SceneComponent || SceneComponent->IsPendingKill()) + continue; + + UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( + SceneComponent, GetOuter(), Actor->GetName()); + if (!InputObj) + continue; + + UHoudiniInputSceneComponent* SceneInput = Cast(InputObj); + if (!SceneInput) + continue; + + ActorComponents.Add(SceneInput); + ActorSceneComponents.Add(SceneComponent); + + LastUpdateNumComponentsAdded++; + } + } + + if (LastUpdateNumComponentsAdded > 0 || LastUpdateNumComponentsRemoved > 0) + { + ActorComponents.Shrink(); + } + } + } + else + { + // If we don't have a valid actor or null, delete any input components we still have and mark as changed + if (ActorComponents.Num() > 0) + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = ActorComponents.Num(); + ActorComponents.Empty(); + ActorSceneComponents.Empty(); + } + else + { + LastUpdateNumComponentsAdded = 0; + LastUpdateNumComponentsRemoved = 0; + } + } +} + +bool +UHoudiniInputActor::HasActorTransformChanged() +{ + if (!GetActor()) + return false; + + if (!Transform.Equals(GetActor()->GetTransform())) + return true; + + return false; +} + +bool +UHoudiniInputActor::HasContentChanged() const +{ + return false; +} + +bool +UHoudiniInputLandscape::HasActorTransformChanged() +{ + return Super::HasActorTransformChanged(); + //return false; +} + +void +UHoudiniInputLandscape::Update(UObject * InObject) +{ + Super::Update(InObject); + + ALandscapeProxy* Landscape = Cast(InObject); + + //ensure(Landscape); + + if (Landscape) + { + // Nothing to do for landscapes? + } +} + +EHoudiniInputObjectType +UHoudiniInputObject::GetInputObjectTypeFromObject(UObject* InObject) +{ + if (InObject->IsA(USceneComponent::StaticClass())) + { + // Handle component inputs + // UISMC derived from USMC, so always test instances before static meshes + if (InObject->IsA(UInstancedStaticMeshComponent::StaticClass())) + { + return EHoudiniInputObjectType::InstancedStaticMeshComponent; + } + else if (InObject->IsA(UStaticMeshComponent::StaticClass())) + { + return EHoudiniInputObjectType::StaticMeshComponent; + } + else if (InObject->IsA(USplineComponent::StaticClass())) + { + return EHoudiniInputObjectType::SplineComponent; + } + else if (InObject->IsA(UHoudiniSplineComponent::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniSplineComponent; + } + else if (InObject->IsA(UHoudiniAssetComponent::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniAssetComponent; + } + else if (InObject->IsA(UCameraComponent::StaticClass())) + { + return EHoudiniInputObjectType::CameraComponent; + } + else + { + return EHoudiniInputObjectType::SceneComponent; + } + } + else if (InObject->IsA(AActor::StaticClass())) + { + // Handle actors + if (InObject->IsA(ALandscapeProxy::StaticClass())) + { + return EHoudiniInputObjectType::Landscape; + } + else if (InObject->IsA(ABrush::StaticClass())) + { + return EHoudiniInputObjectType::Brush; + } + else if (InObject->IsA(AHoudiniAssetActor::StaticClass())) + { + return EHoudiniInputObjectType::HoudiniAssetActor; + } + else + { + return EHoudiniInputObjectType::Actor; + } + } + else if (InObject->IsA(UBlueprint::StaticClass())) + { + return EHoudiniInputObjectType::StaticMesh; + } + else if (InObject->IsA(UFoliageType_InstancedStaticMesh::StaticClass())) + { + return EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; + } + else + { + if (InObject->IsA(UStaticMesh::StaticClass())) + { + return EHoudiniInputObjectType::StaticMesh; + } + else if (InObject->IsA(USkeletalMesh::StaticClass())) + { + return EHoudiniInputObjectType::SkeletalMesh; + } + else if (InObject->IsA(UDataTable::StaticClass())) + { + return EHoudiniInputObjectType::DataTable; + } + else + { + return EHoudiniInputObjectType::Object; + } + } + + return EHoudiniInputObjectType::Invalid; +} + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniInputBrush +//----------------------------------------------------------------------------------------------------------------------------- + +FHoudiniBrushInfo::FHoudiniBrushInfo() + : CachedTransform() + , CachedOrigin(ForceInitToZero) + , CachedExtent(ForceInitToZero) + , CachedBrushType(EBrushType::Brush_Default) + , CachedSurfaceHash(0) +{ +} + +FHoudiniBrushInfo::FHoudiniBrushInfo(ABrush* InBrushActor) +{ + if (!InBrushActor) + return; + + BrushActor = InBrushActor; + CachedTransform = BrushActor->GetActorTransform(); + BrushActor->GetActorBounds(false, CachedOrigin, CachedExtent); + CachedBrushType = BrushActor->BrushType; + +#if WITH_EDITOR + UModel* Model = BrushActor->Brush; + + // Cache the hash of the surface properties + if (IsValid(Model) && IsValid(Model->Polys)) + { + int32 NumPolys = Model->Polys->Element.Num(); + CachedSurfaceHash = 0; + for(int32 iPoly = 0; iPoly < NumPolys; ++iPoly) + { + const FPoly& Poly = Model->Polys->Element[iPoly]; + CombinePolyHash(CachedSurfaceHash, Poly); + } + } + else + { + CachedSurfaceHash = 0; + } +#endif +} + +bool FHoudiniBrushInfo::HasChanged() const +{ + if (!BrushActor.IsValid()) + return false; + + // Has the transform changed? + if (!BrushActor->GetActorTransform().Equals(CachedTransform)) + return true; + + if (BrushActor->BrushType != CachedBrushType) + return true; + + // Has the actor bounds changed? + FVector TmpOrigin, TmpExtent; + BrushActor->GetActorBounds(false, TmpOrigin, TmpExtent); + + if (!(TmpOrigin.Equals(CachedOrigin) && TmpExtent.Equals(CachedExtent) )) + return true; +#if WITH_EDITOR + // Is there a tracked surface property that changed? + UModel* Model = BrushActor->Brush; + if (IsValid(Model) && IsValid(Model->Polys)) + { + // Hash the incoming surface properties and compared it against the cached hash. + int32 NumPolys = Model->Polys->Element.Num(); + uint64 SurfaceHash = 0; + for (int32 iPoly = 0; iPoly < NumPolys; ++iPoly) + { + const FPoly& Poly = Model->Polys->Element[iPoly]; + CombinePolyHash(SurfaceHash, Poly); + } + if (SurfaceHash != CachedSurfaceHash) + return true; + } + else + { + if (CachedSurfaceHash != 0) + return true; + } +#endif + return false; +} + +int32 FHoudiniBrushInfo::GetNumVertexIndicesFromModel(const UModel* Model) +{ + const TArray& Nodes = Model->Nodes; + int32 NumIndices = 0; + // Build the face counts buffer by iterating over the BSP nodes. + for(const FBspNode& Node : Nodes) + { + NumIndices += Node.NumVertices; + } + return NumIndices; +} + +UModel* UHoudiniInputBrush::GetCachedModel() const +{ + return CombinedModel; +} + +bool UHoudiniInputBrush::HasBrushesChanged(const TArray& InBrushes) const +{ + if (InBrushes.Num() != BrushesInfo.Num()) + return true; + + int32 NumBrushes = BrushesInfo.Num(); + + for (int32 InfoIndex = 0; InfoIndex < NumBrushes; ++InfoIndex) + { + const FHoudiniBrushInfo& BrushInfo = BrushesInfo[InfoIndex]; + // Has the cached brush actor invalid? + if (!BrushInfo.BrushActor.IsValid()) + return true; + + // Has there been an order change in the actors list? + if (InBrushes[InfoIndex] != BrushInfo.BrushActor.Get()) + return true; + + // Has there been any other changes to the brush? + if (BrushInfo.HasChanged()) + return true; + } + + // Nothing has changed. + return false; +} + +void UHoudiniInputBrush::UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes) +{ + ABrush* InputBrush = GetBrush(); + if (IsValid(InputBrush)) + { + CachedInputBrushType = InputBrush->BrushType; + } + + // Cache the combined model aswell as the brushes used to generate this model. + CombinedModel = InCombinedModel; + + BrushesInfo.SetNumUninitialized(InBrushes.Num()); + for (int i = 0; i < InBrushes.Num(); ++i) + { + if (!InBrushes[i]) + continue; + BrushesInfo[i] = FHoudiniBrushInfo(InBrushes[i]); + } +} + + +void +UHoudiniInputBrush::Update(UObject * InObject) +{ + Super::Update(InObject); + + ABrush* BrushActor = GetBrush(); + if (!IsValid(BrushActor)) + { + bIgnoreInputObject = true; + return; + } + + CachedInputBrushType = BrushActor->BrushType; + + bIgnoreInputObject = ShouldIgnoreThisInput(); +} + +bool +UHoudiniInputBrush::ShouldIgnoreThisInput() +{ + // Invalid brush, should be ignored + ABrush* BrushActor = GetBrush(); + ensure(BrushActor); + if (!BrushActor) + return true; + + // If the BrushType has changed since caching this object, this object cannot be ignored. + if (CachedInputBrushType != BrushActor->BrushType) + return false; + + // If it's not an additive brush, we want to ignore it + bool bShouldBeIgnored = BrushActor->BrushType != EBrushType::Brush_Add; + + // If this is not a static brush (e.g., AVolume), ignore it. + if (!bShouldBeIgnored) + bShouldBeIgnored = !BrushActor->IsStaticBrush(); + + return bShouldBeIgnored; +} + +bool UHoudiniInputBrush::HasContentChanged() const +{ + ABrush* BrushActor = GetBrush(); + ensure(BrushActor); + + if (!BrushActor) + return false; + + if (BrushActor->BrushType != CachedInputBrushType) + return true; + + if (bIgnoreInputObject) + return false; + + // Find intersecting actors and capture their properties so that + // we can determine whether something has changed. + TArray IntersectingBrushes; + FindIntersectingSubtractiveBrushes(this, IntersectingBrushes); + + if (HasBrushesChanged(IntersectingBrushes)) + { + return true; + } + + return false; +} + +bool +UHoudiniInputBrush::HasActorTransformChanged() +{ + if (bIgnoreInputObject) + return false; + + return Super::HasActorTransformChanged(); +} + + +bool UHoudiniInputBrush::FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes) +{ + TArray IntersectingActors; + TArray Bounds; + + + if (!IsValid(InputBrush)) + return false; + + ABrush* BrushActor = InputBrush->GetBrush(); + if (!IsValid(BrushActor)) + return false; + + + OutBrushes.Empty(); + + Bounds.Add( BrushActor->GetComponentsBoundingBox(true, true) ); + + FHoudiniEngineRuntimeUtils::FindActorsOfClassInBounds(BrushActor->GetWorld(), ABrush::StaticClass(), Bounds, nullptr, IntersectingActors); + + //-------------------------------------------------------------------------------------------------- + // Filter the actors to only keep intersecting subtractive brushes. + //-------------------------------------------------------------------------------------------------- + for (AActor* Actor : IntersectingActors) + { + // Filter out anything that is not a static brush (typically volume actors). + ABrush* Brush = Cast(Actor); + + // NOTE: The brush actor needs to be added in the correct map/level order + // together with the subtractive brushes otherwise the CSG operations + // will not match the BSP in the level. + if (Actor == BrushActor) + OutBrushes.Add(Brush); + + if (!(Brush && Brush->IsStaticBrush())) + continue; + + if (Brush->BrushType == Brush_Subtract) + OutBrushes.Add(Brush); + } + + return true; +} + +#if WITH_EDITOR +void +UHoudiniInputObject::PostEditUndo() +{ + Super::PostEditUndo(); + MarkChanged(true); +} +#endif + +UHoudiniInputObject* +UHoudiniInputObject::DuplicateAndCopyState(UObject * DestOuter) +{ + UHoudiniInputObject* NewInput = Cast(StaticDuplicateObject(this, DestOuter)); + NewInput->CopyStateFrom(this, false); + return NewInput; +} + +void +UHoudiniInputObject::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References should be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + } + + InputNodeId = InInput->InputNodeId; + InputObjectNodeId = InInput->InputObjectNodeId; + bHasChanged = InInput->bHasChanged; + bNeedsToTriggerUpdate = InInput->bNeedsToTriggerUpdate; + bTransformChanged = InInput->bTransformChanged; + Guid = InInput->Guid; + +#if WITH_EDITORONLY_DATA + bUniformScaleLocked = InInput->bUniformScaleLocked; +#endif + +} + +void +UHoudiniInputObject::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; +} + + +// +UHoudiniInputDataTable::UHoudiniInputDataTable(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniInputObject * +UHoudiniInputDataTable::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_DT_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputDataTable::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputDataTable * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputDataTable::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::DataTable; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +UDataTable* +UHoudiniInputDataTable::GetDataTable() const +{ + return Cast(InputObject.LoadSynchronous()); +} + +UHoudiniInputFoliageType_InstancedStaticMesh::UHoudiniInputFoliageType_InstancedStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniInputObject* +UHoudiniInputFoliageType_InstancedStaticMesh::Create(UObject * InObject, UObject* InOuter, const FString& InName) +{ + FString InputObjectNameStr = "HoudiniInputObject_FoliageSM_" + InName; + FName InputObjectName = MakeUniqueObjectName(InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), *InputObjectNameStr); + + // We need to create a new object + UHoudiniInputFoliageType_InstancedStaticMesh * HoudiniInputObject = NewObject( + InOuter, UHoudiniInputFoliageType_InstancedStaticMesh::StaticClass(), InputObjectName, RF_Public | RF_Transactional); + + HoudiniInputObject->Type = EHoudiniInputObjectType::FoliageType_InstancedStaticMesh; + HoudiniInputObject->Update(InObject); + HoudiniInputObject->bHasChanged = true; + + return HoudiniInputObject; +} + +void +UHoudiniInputFoliageType_InstancedStaticMesh::CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) +{ + UHoudiniInputFoliageType_InstancedStaticMesh* FoliageTypeSM = Cast(InInput); + if (!IsValid(FoliageTypeSM)) + return; + + UHoudiniInputObject::CopyStateFrom(FoliageTypeSM, bCopyAllProperties); + + // BlueprintStaticMeshes array is not used in UHoudiniInputFoliageType_InstancedStaticMesh + BlueprintStaticMeshes.Empty(); +} + +void +UHoudiniInputFoliageType_InstancedStaticMesh::Update(UObject * InObject) +{ + UHoudiniInputObject::Update(InObject); + UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InObject); + ensure(FoliageType); + ensure(FoliageType->GetStaticMesh()); +} + +UStaticMesh* +UHoudiniInputFoliageType_InstancedStaticMesh::GetStaticMesh() const +{ + if (!InputObject.IsValid()) + return nullptr; + + UFoliageType_InstancedStaticMesh* const FoliageType = Cast(InputObject.LoadSynchronous()); + if (!IsValid(FoliageType)) + return nullptr; + + return FoliageType->GetStaticMesh(); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h index d79ee7c89..4a85872f5 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h @@ -1,862 +1,862 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include - -#include "HoudiniSplineComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "CoreTypes.h" -#include "Materials/MaterialInterface.h" -#include "UObject/Object.h" -#include "UObject/ObjectMacros.h" - -#include "Engine/Brush.h" -#include "Engine/Polys.h" -#include "UObject/SoftObjectPtr.h" - -#include "HoudiniInputObject.generated.h" - -class UStaticMesh; -class USkeletalMesh; -class USceneComponent; -class UStaticMeshComponent; -class UInstancedStaticMeshComponent; -class USplineComponent; -class UHoudiniAssetComponent; -class AActor; -class ALandscapeProxy; -class ABrush; -class UHoudiniInput; -class ALandscapeProxy; -class UModel; -class UHoudiniInput; -class UCameraComponent; - -UENUM() -enum class EHoudiniInputObjectType : uint8 -{ - Invalid, - - Object, - StaticMesh, - SkeletalMesh, - SceneComponent, - StaticMeshComponent, - InstancedStaticMeshComponent, - SplineComponent, - HoudiniSplineComponent, - HoudiniAssetComponent, - Actor, - Landscape, - Brush, - CameraComponent, - DataTable, - HoudiniAssetActor, - FoliageType_InstancedStaticMesh, -}; - -//----------------------------------------------------------------------------------------------------------------------------- -// UObjects input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - // Create the proper input object - static UHoudiniInputObject * CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InParamName); - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // Check whether two input objects match - virtual bool Matches(const UHoudiniInputObject& Other) const; - - // - static EHoudiniInputObjectType GetInputObjectTypeFromObject(UObject* InObject); - - // - virtual void Update(UObject * InObject); - - // Invalidate and ask for the deletion of this input object's node - virtual void InvalidateData(); - - // UObject accessor - virtual UObject* GetObject() const; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const { return bHasChanged; }; - - // Indicates if this input has changed and should be updated - virtual bool HasTransformChanged() const { return bTransformChanged; }; - - // Indicates if this input needs to trigger an update - virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; - - virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - void MarkTransformChanged(const bool& bInChanged) { bTransformChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - - void SetImportAsReference(const bool& bInImportAsRef) { bImportAsReference = bInImportAsRef; }; - bool GetImportAsReference() const { return bImportAsReference; }; - -#if WITH_EDITOR - void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; - bool IsUniformScaleLocked() const { return bUniformScaleLocked; }; - - void PostEditUndo() override; -#endif - - virtual UHoudiniInputObject* DuplicateAndCopyState(UObject* DestOuter); - virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties); - - // Set whether this object can delete Houdini nodes. - virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } - - FGuid GetInputGuid() const { return Guid; } - - -protected: - - virtual void BeginDestroy() override; - -public: - - // The object referenced by this input - // This property should be protected. Don't access this directly. Use GetObject() / Update() instead. - UPROPERTY() - TSoftObjectPtr InputObject; - - // The object's transform/transform offset - UPROPERTY() - FTransform Transform; - - // The type of Object this input refers to - UPROPERTY() - EHoudiniInputObjectType Type; - - // This input object's "main" (SOP) NodeId - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputNodeId; - - // This input object's "container" (OBJ) NodeId - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - int32 InputObjectNodeId; - - // Guid that uniquely identifies this input object. - // Also useful to correlate inputs between blueprint component templates and instances. - UPROPERTY(DuplicateTransient) - FGuid Guid; - -protected: - - // Indicates this input object has changed - UPROPERTY(DuplicateTransient) - bool bHasChanged; - - // Indicates this input object should trigger an input update/cook - UPROPERTY(DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Indicates that this input transform should be updated - UPROPERTY(DuplicateTransient) - bool bTransformChanged; - - UPROPERTY() - bool bImportAsReference; - - // Indicates if change the scale of Transfrom Offset of this object uniformly -#if WITH_EDITORONLY_DATA - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - bool bUniformScaleLocked; -#endif - - UPROPERTY() - bool bCanDeleteHoudiniNodes; -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UStaticMesh input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputStaticMesh : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // UHoudiniInputObject overrides - - // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; - virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; - virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) override; - virtual void InvalidateData() override; - - // - virtual void Update(UObject * InObject) override; - - // Nothing to add for Static Meshes? - - // StaticMesh accessor - virtual class UStaticMesh* GetStaticMesh() const; - - // Blueprint accessor - virtual class UBlueprint* GetBlueprint() const; - - // Check if this SM Input object is passed in as a BP - virtual bool bIsBlueprint() const; - - // The Blueprint's Static Meshe Components that can be sent as inputs - UPROPERTY() - TArray BlueprintStaticMeshes; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USkeletalMesh input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSkeletalMesh : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // Nothing to add for SkeletalMesh Meshes? - - // StaticMesh accessor - class USkeletalMesh* GetSkeletalMesh(); -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USceneComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSceneComponent : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // SceneComponent accessor - class USceneComponent* GetSceneComponent(); - - // Returns true if the attached actor's (parent) transform has been modified - virtual bool HasActorTransformChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const; - - // Return true if the component itself has been modified - virtual bool HasComponentChanged() const; - -public: - - // This component's parent Actor transform - UPROPERTY() - FTransform ActorTransform = FTransform::Identity; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UStaticMeshComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputMeshComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // StaticMeshComponent accessor - UStaticMeshComponent* GetStaticMeshComponent(); - - // Get the referenced StaticMesh - UStaticMesh* GetStaticMesh(); - - // Returns true if the attached component's materials have been modified - bool HasComponentMaterialsChanged() const; - - // Return true if SMC's static mesh has been modified - virtual bool HasComponentChanged() const override; - -public: - - // Keep track of the selected Static Mesh - UPROPERTY() - TSoftObjectPtr StaticMesh = nullptr; - - // Path to the materials assigned on the SMC - UPROPERTY() - TArray MeshComponentsMaterials; -}; - - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UInstancedStaticMeshComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputInstancedMeshComponent : public UHoudiniInputMeshComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // InstancedStaticMeshComponent accessor - UInstancedStaticMeshComponent* GetInstancedStaticMeshComponent(); - - // Returns true if the instances have changed - bool HasInstancesChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const override; - -public: - - // Array of transform for this ISMC's instances - UPROPERTY() - TArray InstanceTransforms; -}; - - - - -//----------------------------------------------------------------------------------------------------------------------------- -// USplineComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputSplineComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // USplineComponent accessor - USplineComponent* GetSplineComponent(); - - // Returns true if the attached spline component has been modified - bool HasSplineComponentChanged(float fCurrentSplineResolution) const; - - // Returns true if the attached actor's (parent) transform has been modified - virtual bool HasActorTransformChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const; - - // Return true if the component itself has been modified - virtual bool HasComponentChanged() const; - -public: - - // Number of CVs used by the spline component, used to detect modification - UPROPERTY() - int32 NumberOfSplineControlPoints = -1; - - // Spline Length, used for fast detection of modifications of the spline.. - UPROPERTY() - float SplineLength = -1.0f; - - // Spline resolution used to generate the asset, used to detect setting modification - UPROPERTY() - float SplineResolution = -1.0f; - - // Is the spline closed? - UPROPERTY() - bool SplineClosed = false; - - // Transforms of each of the spline's control points - UPROPERTY() - TArray SplineControlPoints; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniSplineComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniSplineComponent : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - virtual void Update(UObject * InObject) override; - - virtual UObject* GetObject() const override; - - virtual void MarkChanged(const bool& bInChanged) override; - - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) override; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const override; - - // Indicates if this input needs to trigger an update - virtual bool NeedsToTriggerUpdate() const override; - - // UHoudiniSplineComponent accessor - UHoudiniSplineComponent* GetCurveComponent() const; - -public: - - // The type of curve (polygon, NURBS, bezier) - UPROPERTY() - EHoudiniCurveType CurveType = EHoudiniCurveType::Polygon; - - // The curve's method (CVs, Breakpoint, Freehand) - UPROPERTY() - EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::CVs; - - UPROPERTY() - bool Reversed = false; - - -protected: - - // NOTE: We are using this reference to the component since the component, for now, - // lives on the same actor as this input object. If we use a Soft Object Reference instead the editor - // will complain about breaking references everytime we try to delete the actor. - UPROPERTY(Instanced) - UHoudiniSplineComponent* CachedComponent; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// UCameraComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputCameraComponent : public UHoudiniInputSceneComponent -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // UCameraComponent accessor - UCameraComponent* GetCameraComponent(); - - // Return true if SMC's static mesh has been modified - virtual bool HasComponentChanged() const override; - -public: - - UPROPERTY() - float FOV; - - UPROPERTY() - float AspectRatio; - - UPROPERTY() - //TEnumAsByte ProjectionType; - bool bIsOrthographic; - - UPROPERTY() - float OrthoWidth; - UPROPERTY() - float OrthoNearClipPlane; - UPROPERTY() - float OrthoFarClipPlane; - -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UHoudiniAssetComponent input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniAsset : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // UHoudiniAssetComponent accessor - UHoudiniAssetComponent* GetHoudiniAssetComponent(); -public: - - // The output index of the node that we want to use as input - UPROPERTY() - int32 AssetOutputIndex; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// AActor input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - // - virtual bool HasActorTransformChanged(); - - // Return true if any content of this actor has possibly changed (for example geometry edits on a - // Brush or changes on procedurally generated content). - // NOTE: This is more generally applicable and could be moved to the HoudiniInputObject class. - virtual bool HasContentChanged() const; - - // AActor accessor - AActor* GetActor(); - - const TArray& GetActorComponents() const { return ActorComponents; } - - // The number of components added with the last call to Update - int32 GetLastUpdateNumComponentsAdded() const { return LastUpdateNumComponentsAdded; } - // The number of components remove with the last call to Update - int32 GetLastUpdateNumComponentsRemoved() const { return LastUpdateNumComponentsRemoved; } - -protected: - - // The actor's components that can be sent as inputs - UPROPERTY() - TArray ActorComponents; - - // The USceneComponents the actor had the last time we called Update (matches the ones in ActorComponents). - UPROPERTY() - TSet> ActorSceneComponents; - - // The number of components added with the last call to Update - UPROPERTY() - int32 LastUpdateNumComponentsAdded; - - // The number of components remove with the last call to Update - UPROPERTY() - int32 LastUpdateNumComponentsRemoved; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// ALandscapeProxy input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActor -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // - virtual void Update(UObject * InObject) override; - - virtual bool HasActorTransformChanged() override; - - // ALandscapeProxy accessor - ALandscapeProxy* GetLandscapeProxy(); - - void SetLandscapeProxy(UObject* InLandscapeProxy); - - // Used to restore an input landscape's transform to its original state - UPROPERTY() - FTransform CachedInputLandscapeTraqnsform; -}; - - - -//----------------------------------------------------------------------------------------------------------------------------- -// ABrush input -//----------------------------------------------------------------------------------------------------------------------------- -// Cache info for a brush in order to determine whether it has changed. - -#define BRUSH_HASH_SURFACE_PROPERTIES 0 - -//USTRUCT() -//struct FHoudiniBrushSurfaceInfo { -// GENERATED_BODY() -// -// FVector Base; -// FVector Normal; -// FVector TextureU; -// FVector TextureV; -// TSoftObjectPtr Material; -// -// FHoudiniBrushSurfaceInfo(const FVector& InBase, const FVector& InNormal, const FVector& InTextureU, const FVector& InTextureV, UMaterialInterface* InMaterial) -// : Base(InBase) -// , Normal(InNormal) -// , TextureU(InTextureU) -// , TextureV(InTextureV) -// , Material(InMaterial) -// { } -// -// inline bool operator==(const FHoudiniBrushSurfaceInfo& Other) { -// return Base.Equals(Other.Base) -// && Normal.Equals(Other.Normal) -// && TextureU.Equals(Other.TextureU) -// && TextureV.Equals(Other.TextureV) -// && Material.Get() == Other.Material.Get(); -// } -// -// inline bool operator==(const FPoly& Poly) { -// return Base.Equals(Poly.Base) -// && Normal.Equals(Poly.Normal) -// && TextureU.Equals(Poly.TextureU) -// && TextureV.Equals(Poly.TextureV) -// && Material.Get() == Poly.Material; -// } -//}; - -USTRUCT() -struct FHoudiniBrushInfo -{ - GENERATED_BODY() - - UPROPERTY() - TWeakObjectPtr BrushActor; - UPROPERTY() - FTransform CachedTransform; - UPROPERTY() - FVector CachedOrigin; - UPROPERTY() - FVector CachedExtent; - UPROPERTY() - TEnumAsByte CachedBrushType; - - UPROPERTY() - uint64 CachedSurfaceHash; - - bool HasChanged() const; - - static int32 GetNumVertexIndicesFromModel(const UModel* Model); - - FHoudiniBrushInfo(); - FHoudiniBrushInfo(ABrush* InBrushActor); - - template - inline void HashCombine(uint64& s, const T & v) const - { - std::hash h; - s^= h(v) + 0x9e3779b9 + (s<< 6) + (s>> 2); - } - - inline void HashCombine(uint64& s, const FVector & V) const - { - HashCombine(s, V.X); - HashCombine(s, V.Y); - HashCombine(s, V.Z); - } - - inline void CombinePolyHash(uint64& Hash, const FPoly& Poly) const - { - HashCombine(Hash, Poly.Base); - HashCombine(Hash, Poly.TextureU); - HashCombine(Hash, Poly.TextureV); - HashCombine(Hash, Poly.Normal); - // Do not add addresses to the hash, otherwise it would force a recook every unreal session! - // HashCombine(Hash, (uint64)(Poly.Material)); - } -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor -{ - GENERATED_BODY() - -public: - - UHoudiniInputBrush(); - - // Factory function - static UHoudiniInputBrush* Create(UObject* InObject, UObject* InOuter, const FString& InName); - - //---------------------------------------------------------------------- - // UHoudiniInputActor Interface - Begin - //---------------------------------------------------------------------- - - virtual void Update(UObject * InObject) override; - - // Indicates if this input has changed and should be updated - virtual bool HasContentChanged() const override; - - // Indicates if this input has changed and should be updated - virtual bool HasChanged() const override { return (!bIgnoreInputObject) && bHasChanged; }; - - // Indicates if this input has changed and should be updated - virtual bool HasTransformChanged() const override { return (!bIgnoreInputObject) && bTransformChanged; }; - - virtual bool HasActorTransformChanged() override; - - virtual bool NeedsToTriggerUpdate() const override { return (!bIgnoreInputObject) && bNeedsToTriggerUpdate; }; - - //---------------------------------------------------------------------- - // UHoudiniInputActor Interface - End - //---------------------------------------------------------------------- - - - // ABrush accessor - ABrush* GetBrush() const; - - UModel* GetCachedModel() const; - - // Check whether any of the brushes, or their transforms, used to generate this model have changed. - bool HasBrushesChanged(const TArray& InBrushes) const; - - // Cache the combined model as well as the input brushes. - void UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes); - - // Returns whether this input object should be ignored when uploading objects to Houdini. - // This mechanism could be implemented on UHoudiniInputObject. - bool ShouldIgnoreThisInput(); - - - // Find only the subtractive brush actors that intersect with the InputObject (Brush actor) bounding box but - // excluding any selector bounds actors. - static bool FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes); - -protected: - UPROPERTY() - TArray BrushesInfo; - - UPROPERTY(Transient, DuplicateTransient) - UModel* CombinedModel; - - UPROPERTY() - bool bIgnoreInputObject; - - UPROPERTY() - TEnumAsByte CachedInputBrushType; -}; - - -//----------------------------------------------------------------------------------------------------------------------------- -// UDataTable input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputDataTable : public UHoudiniInputObject -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // DataTable accessor - class UDataTable* GetDataTable() const; -}; - -//----------------------------------------------------------------------------------------------------------------------------- -// UFoliageType_InstancedStaticMesh input -//----------------------------------------------------------------------------------------------------------------------------- -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniInputFoliageType_InstancedStaticMesh : public UHoudiniInputStaticMesh -{ - GENERATED_UCLASS_BODY() - -public: - - // - static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); - - // UHoudiniInputObject overrides - - // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; - virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; - - // - virtual void Update(UObject * InObject) override; - - // StaticMesh accessor - virtual class UStaticMesh* GetStaticMesh() const override; - - virtual class UBlueprint* GetBlueprint() const override { return nullptr; } - - virtual bool bIsBlueprint() const override { return false; } -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include + +#include "HoudiniSplineComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "CoreTypes.h" +#include "Materials/MaterialInterface.h" +#include "UObject/Object.h" +#include "UObject/ObjectMacros.h" + +#include "Engine/Brush.h" +#include "Engine/Polys.h" +#include "UObject/SoftObjectPtr.h" + +#include "HoudiniInputObject.generated.h" + +class UStaticMesh; +class USkeletalMesh; +class USceneComponent; +class UStaticMeshComponent; +class UInstancedStaticMeshComponent; +class USplineComponent; +class UHoudiniAssetComponent; +class AActor; +class ALandscapeProxy; +class ABrush; +class UHoudiniInput; +class ALandscapeProxy; +class UModel; +class UHoudiniInput; +class UCameraComponent; + +UENUM() +enum class EHoudiniInputObjectType : uint8 +{ + Invalid, + + Object, + StaticMesh, + SkeletalMesh, + SceneComponent, + StaticMeshComponent, + InstancedStaticMeshComponent, + SplineComponent, + HoudiniSplineComponent, + HoudiniAssetComponent, + Actor, + Landscape, + Brush, + CameraComponent, + DataTable, + HoudiniAssetActor, + FoliageType_InstancedStaticMesh, +}; + +//----------------------------------------------------------------------------------------------------------------------------- +// UObjects input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + // Create the proper input object + static UHoudiniInputObject * CreateTypedInputObject(UObject * InObject, UObject* InOuter, const FString& InParamName); + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // Check whether two input objects match + virtual bool Matches(const UHoudiniInputObject& Other) const; + + // + static EHoudiniInputObjectType GetInputObjectTypeFromObject(UObject* InObject); + + // + virtual void Update(UObject * InObject); + + // Invalidate and ask for the deletion of this input object's node + virtual void InvalidateData(); + + // UObject accessor + virtual UObject* GetObject() const; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const { return bHasChanged; }; + + // Indicates if this input has changed and should be updated + virtual bool HasTransformChanged() const { return bTransformChanged; }; + + // Indicates if this input needs to trigger an update + virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; + + virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + void MarkTransformChanged(const bool& bInChanged) { bTransformChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + + void SetImportAsReference(const bool& bInImportAsRef) { bImportAsReference = bInImportAsRef; }; + bool GetImportAsReference() const { return bImportAsReference; }; + +#if WITH_EDITOR + void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; + bool IsUniformScaleLocked() const { return bUniformScaleLocked; }; + + void PostEditUndo() override; +#endif + + virtual UHoudiniInputObject* DuplicateAndCopyState(UObject* DestOuter); + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties); + + // Set whether this object can delete Houdini nodes. + virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } + + FGuid GetInputGuid() const { return Guid; } + + +protected: + + virtual void BeginDestroy() override; + +public: + + // The object referenced by this input + // This property should be protected. Don't access this directly. Use GetObject() / Update() instead. + UPROPERTY() + TSoftObjectPtr InputObject; + + // The object's transform/transform offset + UPROPERTY() + FTransform Transform; + + // The type of Object this input refers to + UPROPERTY() + EHoudiniInputObjectType Type; + + // This input object's "main" (SOP) NodeId + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputNodeId; + + // This input object's "container" (OBJ) NodeId + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + int32 InputObjectNodeId; + + // Guid that uniquely identifies this input object. + // Also useful to correlate inputs between blueprint component templates and instances. + UPROPERTY(DuplicateTransient) + FGuid Guid; + +protected: + + // Indicates this input object has changed + UPROPERTY(DuplicateTransient) + bool bHasChanged; + + // Indicates this input object should trigger an input update/cook + UPROPERTY(DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Indicates that this input transform should be updated + UPROPERTY(DuplicateTransient) + bool bTransformChanged; + + UPROPERTY() + bool bImportAsReference; + + // Indicates if change the scale of Transfrom Offset of this object uniformly +#if WITH_EDITORONLY_DATA + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + bool bUniformScaleLocked; +#endif + + UPROPERTY() + bool bCanDeleteHoudiniNodes; +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UStaticMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputStaticMesh : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // UHoudiniInputObject overrides + + // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; + virtual void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) override; + virtual void InvalidateData() override; + + // + virtual void Update(UObject * InObject) override; + + // Nothing to add for Static Meshes? + + // StaticMesh accessor + virtual class UStaticMesh* GetStaticMesh() const; + + // Blueprint accessor + virtual class UBlueprint* GetBlueprint() const; + + // Check if this SM Input object is passed in as a BP + virtual bool bIsBlueprint() const; + + // The Blueprint's Static Meshe Components that can be sent as inputs + UPROPERTY() + TArray BlueprintStaticMeshes; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USkeletalMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSkeletalMesh : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // Nothing to add for SkeletalMesh Meshes? + + // StaticMesh accessor + class USkeletalMesh* GetSkeletalMesh(); +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USceneComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSceneComponent : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // SceneComponent accessor + class USceneComponent* GetSceneComponent(); + + // Returns true if the attached actor's (parent) transform has been modified + virtual bool HasActorTransformChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const; + + // Return true if the component itself has been modified + virtual bool HasComponentChanged() const; + +public: + + // This component's parent Actor transform + UPROPERTY() + FTransform ActorTransform = FTransform::Identity; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UStaticMeshComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputMeshComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // StaticMeshComponent accessor + UStaticMeshComponent* GetStaticMeshComponent(); + + // Get the referenced StaticMesh + UStaticMesh* GetStaticMesh(); + + // Returns true if the attached component's materials have been modified + bool HasComponentMaterialsChanged() const; + + // Return true if SMC's static mesh has been modified + virtual bool HasComponentChanged() const override; + +public: + + // Keep track of the selected Static Mesh + UPROPERTY() + TSoftObjectPtr StaticMesh = nullptr; + + // Path to the materials assigned on the SMC + UPROPERTY() + TArray MeshComponentsMaterials; +}; + + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UInstancedStaticMeshComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputInstancedMeshComponent : public UHoudiniInputMeshComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // InstancedStaticMeshComponent accessor + UInstancedStaticMeshComponent* GetInstancedStaticMeshComponent(); + + // Returns true if the instances have changed + bool HasInstancesChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const override; + +public: + + // Array of transform for this ISMC's instances + UPROPERTY() + TArray InstanceTransforms; +}; + + + + +//----------------------------------------------------------------------------------------------------------------------------- +// USplineComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputSplineComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // USplineComponent accessor + USplineComponent* GetSplineComponent(); + + // Returns true if the attached spline component has been modified + bool HasSplineComponentChanged(float fCurrentSplineResolution) const; + + // Returns true if the attached actor's (parent) transform has been modified + virtual bool HasActorTransformChanged() const; + + // Returns true if the attached component's transform has been modified + virtual bool HasComponentTransformChanged() const; + + // Return true if the component itself has been modified + virtual bool HasComponentChanged() const; + +public: + + // Number of CVs used by the spline component, used to detect modification + UPROPERTY() + int32 NumberOfSplineControlPoints = -1; + + // Spline Length, used for fast detection of modifications of the spline.. + UPROPERTY() + float SplineLength = -1.0f; + + // Spline resolution used to generate the asset, used to detect setting modification + UPROPERTY() + float SplineResolution = -1.0f; + + // Is the spline closed? + UPROPERTY() + bool SplineClosed = false; + + // Transforms of each of the spline's control points + UPROPERTY() + TArray SplineControlPoints; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniSplineComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniSplineComponent : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + virtual void Update(UObject * InObject) override; + + virtual UObject* GetObject() const override; + + virtual void MarkChanged(const bool& bInChanged) override; + + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) override; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const override; + + // Indicates if this input needs to trigger an update + virtual bool NeedsToTriggerUpdate() const override; + + // UHoudiniSplineComponent accessor + UHoudiniSplineComponent* GetCurveComponent() const; + +public: + + // The type of curve (polygon, NURBS, bezier) + UPROPERTY() + EHoudiniCurveType CurveType = EHoudiniCurveType::Polygon; + + // The curve's method (CVs, Breakpoint, Freehand) + UPROPERTY() + EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::CVs; + + UPROPERTY() + bool Reversed = false; + + +protected: + + // NOTE: We are using this reference to the component since the component, for now, + // lives on the same actor as this input object. If we use a Soft Object Reference instead the editor + // will complain about breaking references everytime we try to delete the actor. + UPROPERTY(Instanced) + UHoudiniSplineComponent* CachedComponent; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// UCameraComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputCameraComponent : public UHoudiniInputSceneComponent +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // UCameraComponent accessor + UCameraComponent* GetCameraComponent(); + + // Return true if SMC's static mesh has been modified + virtual bool HasComponentChanged() const override; + +public: + + UPROPERTY() + float FOV; + + UPROPERTY() + float AspectRatio; + + UPROPERTY() + //TEnumAsByte ProjectionType; + bool bIsOrthographic; + + UPROPERTY() + float OrthoWidth; + UPROPERTY() + float OrthoNearClipPlane; + UPROPERTY() + float OrthoFarClipPlane; + +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UHoudiniAssetComponent input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputHoudiniAsset : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // UHoudiniAssetComponent accessor + UHoudiniAssetComponent* GetHoudiniAssetComponent(); +public: + + // The output index of the node that we want to use as input + UPROPERTY() + int32 AssetOutputIndex; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// AActor input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + // + virtual bool HasActorTransformChanged(); + + // Return true if any content of this actor has possibly changed (for example geometry edits on a + // Brush or changes on procedurally generated content). + // NOTE: This is more generally applicable and could be moved to the HoudiniInputObject class. + virtual bool HasContentChanged() const; + + // AActor accessor + AActor* GetActor(); + + const TArray& GetActorComponents() const { return ActorComponents; } + + // The number of components added with the last call to Update + int32 GetLastUpdateNumComponentsAdded() const { return LastUpdateNumComponentsAdded; } + // The number of components remove with the last call to Update + int32 GetLastUpdateNumComponentsRemoved() const { return LastUpdateNumComponentsRemoved; } + +protected: + + // The actor's components that can be sent as inputs + UPROPERTY() + TArray ActorComponents; + + // The USceneComponents the actor had the last time we called Update (matches the ones in ActorComponents). + UPROPERTY() + TSet> ActorSceneComponents; + + // The number of components added with the last call to Update + UPROPERTY() + int32 LastUpdateNumComponentsAdded; + + // The number of components remove with the last call to Update + UPROPERTY() + int32 LastUpdateNumComponentsRemoved; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// ALandscapeProxy input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActor +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // + virtual void Update(UObject * InObject) override; + + virtual bool HasActorTransformChanged() override; + + // ALandscapeProxy accessor + ALandscapeProxy* GetLandscapeProxy(); + + void SetLandscapeProxy(UObject* InLandscapeProxy); + + // Used to restore an input landscape's transform to its original state + UPROPERTY() + FTransform CachedInputLandscapeTraqnsform; +}; + + + +//----------------------------------------------------------------------------------------------------------------------------- +// ABrush input +//----------------------------------------------------------------------------------------------------------------------------- +// Cache info for a brush in order to determine whether it has changed. + +#define BRUSH_HASH_SURFACE_PROPERTIES 0 + +//USTRUCT() +//struct FHoudiniBrushSurfaceInfo { +// GENERATED_BODY() +// +// FVector Base; +// FVector Normal; +// FVector TextureU; +// FVector TextureV; +// TSoftObjectPtr Material; +// +// FHoudiniBrushSurfaceInfo(const FVector& InBase, const FVector& InNormal, const FVector& InTextureU, const FVector& InTextureV, UMaterialInterface* InMaterial) +// : Base(InBase) +// , Normal(InNormal) +// , TextureU(InTextureU) +// , TextureV(InTextureV) +// , Material(InMaterial) +// { } +// +// inline bool operator==(const FHoudiniBrushSurfaceInfo& Other) { +// return Base.Equals(Other.Base) +// && Normal.Equals(Other.Normal) +// && TextureU.Equals(Other.TextureU) +// && TextureV.Equals(Other.TextureV) +// && Material.Get() == Other.Material.Get(); +// } +// +// inline bool operator==(const FPoly& Poly) { +// return Base.Equals(Poly.Base) +// && Normal.Equals(Poly.Normal) +// && TextureU.Equals(Poly.TextureU) +// && TextureV.Equals(Poly.TextureV) +// && Material.Get() == Poly.Material; +// } +//}; + +USTRUCT() +struct FHoudiniBrushInfo +{ + GENERATED_BODY() + + UPROPERTY() + TWeakObjectPtr BrushActor; + UPROPERTY() + FTransform CachedTransform; + UPROPERTY() + FVector CachedOrigin; + UPROPERTY() + FVector CachedExtent; + UPROPERTY() + TEnumAsByte CachedBrushType; + + UPROPERTY() + uint64 CachedSurfaceHash; + + bool HasChanged() const; + + static int32 GetNumVertexIndicesFromModel(const UModel* Model); + + FHoudiniBrushInfo(); + FHoudiniBrushInfo(ABrush* InBrushActor); + + template + inline void HashCombine(uint64& s, const T & v) const + { + std::hash h; + s^= h(v) + 0x9e3779b9 + (s<< 6) + (s>> 2); + } + + inline void HashCombine(uint64& s, const FVector & V) const + { + HashCombine(s, V.X); + HashCombine(s, V.Y); + HashCombine(s, V.Z); + } + + inline void CombinePolyHash(uint64& Hash, const FPoly& Poly) const + { + HashCombine(Hash, Poly.Base); + HashCombine(Hash, Poly.TextureU); + HashCombine(Hash, Poly.TextureV); + HashCombine(Hash, Poly.Normal); + // Do not add addresses to the hash, otherwise it would force a recook every unreal session! + // HashCombine(Hash, (uint64)(Poly.Material)); + } +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor +{ + GENERATED_BODY() + +public: + + UHoudiniInputBrush(); + + // Factory function + static UHoudiniInputBrush* Create(UObject* InObject, UObject* InOuter, const FString& InName); + + //---------------------------------------------------------------------- + // UHoudiniInputActor Interface - Begin + //---------------------------------------------------------------------- + + virtual void Update(UObject * InObject) override; + + // Indicates if this input has changed and should be updated + virtual bool HasContentChanged() const override; + + // Indicates if this input has changed and should be updated + virtual bool HasChanged() const override { return (!bIgnoreInputObject) && bHasChanged; }; + + // Indicates if this input has changed and should be updated + virtual bool HasTransformChanged() const override { return (!bIgnoreInputObject) && bTransformChanged; }; + + virtual bool HasActorTransformChanged() override; + + virtual bool NeedsToTriggerUpdate() const override { return (!bIgnoreInputObject) && bNeedsToTriggerUpdate; }; + + //---------------------------------------------------------------------- + // UHoudiniInputActor Interface - End + //---------------------------------------------------------------------- + + + // ABrush accessor + ABrush* GetBrush() const; + + UModel* GetCachedModel() const; + + // Check whether any of the brushes, or their transforms, used to generate this model have changed. + bool HasBrushesChanged(const TArray& InBrushes) const; + + // Cache the combined model as well as the input brushes. + void UpdateCachedData(UModel* InCombinedModel, const TArray& InBrushes); + + // Returns whether this input object should be ignored when uploading objects to Houdini. + // This mechanism could be implemented on UHoudiniInputObject. + bool ShouldIgnoreThisInput(); + + + // Find only the subtractive brush actors that intersect with the InputObject (Brush actor) bounding box but + // excluding any selector bounds actors. + static bool FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes); + +protected: + UPROPERTY() + TArray BrushesInfo; + + UPROPERTY(Transient, DuplicateTransient) + UModel* CombinedModel; + + UPROPERTY() + bool bIgnoreInputObject; + + UPROPERTY() + TEnumAsByte CachedInputBrushType; +}; + + +//----------------------------------------------------------------------------------------------------------------------------- +// UDataTable input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputDataTable : public UHoudiniInputObject +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // DataTable accessor + class UDataTable* GetDataTable() const; +}; + +//----------------------------------------------------------------------------------------------------------------------------- +// UFoliageType_InstancedStaticMesh input +//----------------------------------------------------------------------------------------------------------------------------- +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniInputFoliageType_InstancedStaticMesh : public UHoudiniInputStaticMesh +{ + GENERATED_UCLASS_BODY() + +public: + + // + static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); + + // UHoudiniInputObject overrides + + // virtual void DuplicateAndCopyState(UObject* DestOuter, UHoudiniInputObject*& OutNewObject) override; + virtual void CopyStateFrom(UHoudiniInputObject* InInput, bool bCopyAllProperties) override; + + // + virtual void Update(UObject * InObject) override; + + // StaticMesh accessor + virtual class UStaticMesh* GetStaticMesh() const override; + + virtual class UBlueprint* GetBlueprint() const override { return nullptr; } + + virtual bool bIsBlueprint() const override { return false; } +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h index 7a0d78b2d..d9b7903a9 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputTypes.h @@ -1,68 +1,49 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -UENUM() -enum class EHoudiniInputType : uint8 -{ - Invalid, - - Geometry, - Curve, - Asset, - Landscape, - World, - Skeletal, - -}; -// Maintain an iterable list of houdini input types -static const EHoudiniInputType HoudiniInputTypeList[] = { - EHoudiniInputType::Geometry, - EHoudiniInputType::Curve, - EHoudiniInputType::Asset, - EHoudiniInputType::Landscape, - EHoudiniInputType::World, - EHoudiniInputType::Skeletal }; - -UENUM() -enum class EHoudiniXformType : uint8 -{ - None, - IntoThisObject, - Auto -}; - -UENUM() -enum class EHoudiniLandscapeExportType : uint8 -{ - Heightfield, - Mesh, - Points -}; \ No newline at end of file +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "HoudiniEngineRuntimeCommon.h" + +// Maintain an iterable list of houdini input types +static const EHoudiniInputType HoudiniInputTypeList[] = { + EHoudiniInputType::Geometry, + EHoudiniInputType::Curve, + EHoudiniInputType::Asset, + EHoudiniInputType::Landscape, + EHoudiniInputType::World, + EHoudiniInputType::Skeletal }; + +UENUM() +enum class EHoudiniXformType : uint8 +{ + None, + IntoThisObject, + Auto +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp index c6287686c..f0fced9b7 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp @@ -1,248 +1,248 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniInstancedActorComponent.h" - -#include "HoudiniMeshSplitInstancerComponent.h" -#include "HoudiniRuntimeSettings.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -#include "Components/InstancedStaticMeshComponent.h" -#include "Components/HierarchicalInstancedStaticMeshComponent.h" - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniInstancedActorComponent::UHoudiniInstancedActorComponent( const FObjectInitializer& ObjectInitializer ) -: Super( ObjectInitializer ) -, InstancedObject( nullptr ) -{ - // - // Set default component properties. - // - Mobility = EComponentMobility::Static; - bCanEverAffectNavigation = true; - bNeverNeedsRenderUpdate = false; - Bounds = FBox(ForceInitToZero); -} - - -void -UHoudiniInstancedActorComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on the setting value - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniInstancedActorComponent_V1* CompatibilityIAC = NewObject(); - CompatibilityIAC->Serialize(Ar); - CompatibilityIAC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - - -void UHoudiniInstancedActorComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) -{ - ClearAllInstances(); - Super::OnComponentDestroyed( bDestroyingHierarchy ); -} - - -void -UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector ) -{ - UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); - if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) - { - if ( ThisHIAC->InstancedObject && !ThisHIAC->InstancedObject->IsPendingKill() ) - Collector.AddReferencedObject( ThisHIAC->InstancedObject, ThisHIAC ); - - Collector.AddReferencedObjects(ThisHIAC->InstancedActors, ThisHIAC ); - } -} - - -int32 -UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform, AActor * NewActor) -{ - if (!NewActor || NewActor->IsPendingKill()) - return -1; - - NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - NewActor->SetActorRelativeTransform(InstanceTransform); - return InstancedActors.Add(NewActor); -} - - -bool -UHoudiniInstancedActorComponent::SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor) -{ - if (!NewActor || NewActor->IsPendingKill()) - return false; - - if (!InstancedActors.IsValidIndex(Idx)) - return false; - - NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - NewActor->SetActorRelativeTransform(InstanceTransform); - NewActor->RegisterAllComponents(); - InstancedActors[Idx] = NewActor; - - return true; -} - - -bool -UHoudiniInstancedActorComponent::SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform) -{ - if (!InstancedActors.IsValidIndex(Idx)) - return false; - - InstancedActors[Idx]->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - InstancedActors[Idx]->SetActorRelativeTransform(InstanceTransform); - - return true; -} - - -void -UHoudiniInstancedActorComponent::ClearAllInstances() -{ - for ( AActor* Instance : InstancedActors ) - { - if ( Instance && !Instance->IsPendingKill() ) - Instance->Destroy(); - } - InstancedActors.Empty(); -} - - -void -UHoudiniInstancedActorComponent::SetNumberOfInstances(const int32& NewInstanceNum) -{ - int32 OldInstanceNum = InstancedActors.Num(); - - // If we want less instances than we already have, destroy the extra properly - if (NewInstanceNum < OldInstanceNum) - { - for (int32 Idx = NewInstanceNum - 1; Idx < InstancedActors.Num(); Idx++) - { - AActor* Instance = InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; - if (Instance && !Instance->IsPendingKill()) - Instance->Destroy(); - } - } - - // Grow the array with nulls if needed - InstancedActors.SetNumZeroed(NewInstanceNum); -} - - -void -UHoudiniInstancedActorComponent::OnComponentCreated() -{ - Super::OnComponentCreated(); - - // If our instances are parented to another actor we should duplicate them - bool bNeedDuplicate = false; - for (auto CurrentInstance : InstancedActors) - { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) - continue; - - if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) - bNeedDuplicate = true; - } - - if ( !bNeedDuplicate ) - return; - - // TODO: CHECK ME! - // We need to duplicate our instances - TArray SourceInstances = InstancedActors; - InstancedActors.Empty(); - for (AActor* CurrentInstance : SourceInstances) - { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) - continue; - - FTransform InstanceTransform; - if ( CurrentInstance->GetRootComponent() ) - InstanceTransform = CurrentInstance->GetRootComponent()->GetRelativeTransform(); - - // AddInstance( InstanceTransform ); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniInstancedActorComponent.h" + +#include "HoudiniMeshSplitInstancerComponent.h" +#include "HoudiniRuntimeSettings.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +#include "Components/InstancedStaticMeshComponent.h" +#include "Components/HierarchicalInstancedStaticMeshComponent.h" + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniInstancedActorComponent::UHoudiniInstancedActorComponent( const FObjectInitializer& ObjectInitializer ) +: Super( ObjectInitializer ) +, InstancedObject( nullptr ) +{ + // + // Set default component properties. + // + Mobility = EComponentMobility::Static; + bCanEverAffectNavigation = true; + bNeverNeedsRenderUpdate = false; + Bounds = FBox(ForceInitToZero); +} + + +void +UHoudiniInstancedActorComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on the setting value + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniInstancedActorComponent_V1* CompatibilityIAC = NewObject(); + CompatibilityIAC->Serialize(Ar); + CompatibilityIAC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniInstancedActorComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + + +void UHoudiniInstancedActorComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + ClearAllInstances(); + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + + +void +UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); + if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) + { + if ( ThisHIAC->InstancedObject && !ThisHIAC->InstancedObject->IsPendingKill() ) + Collector.AddReferencedObject( ThisHIAC->InstancedObject, ThisHIAC ); + + Collector.AddReferencedObjects(ThisHIAC->InstancedActors, ThisHIAC ); + } +} + + +int32 +UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform, AActor * NewActor) +{ + if (!NewActor || NewActor->IsPendingKill()) + return -1; + + NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + NewActor->SetActorRelativeTransform(InstanceTransform); + return InstancedActors.Add(NewActor); +} + + +bool +UHoudiniInstancedActorComponent::SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor) +{ + if (!NewActor || NewActor->IsPendingKill()) + return false; + + if (!InstancedActors.IsValidIndex(Idx)) + return false; + + NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + NewActor->SetActorRelativeTransform(InstanceTransform); + NewActor->RegisterAllComponents(); + InstancedActors[Idx] = NewActor; + + return true; +} + + +bool +UHoudiniInstancedActorComponent::SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform) +{ + if (!InstancedActors.IsValidIndex(Idx)) + return false; + + InstancedActors[Idx]->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + InstancedActors[Idx]->SetActorRelativeTransform(InstanceTransform); + + return true; +} + + +void +UHoudiniInstancedActorComponent::ClearAllInstances() +{ + for ( AActor* Instance : InstancedActors ) + { + if ( Instance && !Instance->IsPendingKill() ) + Instance->Destroy(); + } + InstancedActors.Empty(); +} + + +void +UHoudiniInstancedActorComponent::SetNumberOfInstances(const int32& NewInstanceNum) +{ + int32 OldInstanceNum = InstancedActors.Num(); + + // If we want less instances than we already have, destroy the extra properly + if (NewInstanceNum < OldInstanceNum) + { + for (int32 Idx = NewInstanceNum - 1; Idx < InstancedActors.Num(); Idx++) + { + AActor* Instance = InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; + if (Instance && !Instance->IsPendingKill()) + Instance->Destroy(); + } + } + + // Grow the array with nulls if needed + InstancedActors.SetNumZeroed(NewInstanceNum); +} + + +void +UHoudiniInstancedActorComponent::OnComponentCreated() +{ + Super::OnComponentCreated(); + + // If our instances are parented to another actor we should duplicate them + bool bNeedDuplicate = false; + for (auto CurrentInstance : InstancedActors) + { + if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + continue; + + if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) + bNeedDuplicate = true; + } + + if ( !bNeedDuplicate ) + return; + + // TODO: CHECK ME! + // We need to duplicate our instances + TArray SourceInstances = InstancedActors; + InstancedActors.Empty(); + for (AActor* CurrentInstance : SourceInstances) + { + if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + continue; + + FTransform InstanceTransform; + if ( CurrentInstance->GetRootComponent() ) + InstanceTransform = CurrentInstance->GetRootComponent()->GetRelativeTransform(); + + // AddInstance( InstanceTransform ); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h index 44ea7cf74..32a61b696 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.h @@ -1,91 +1,91 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" - -#include "HoudiniInstancedActorComponent.generated.h" - - -UCLASS()//( config = Engine ) -class HOUDINIENGINERUNTIME_API UHoudiniInstancedActorComponent : public USceneComponent -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniInstancedActorComponent_V1; - - public: - - virtual void Serialize(FArchive & Ar) override; - - virtual void OnComponentCreated() override; - virtual void OnComponentDestroyed( bool bDestroyingHierarchy ) override; - - static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); - - // Object mutator - void SetInstancedObject(class UObject* InObject) { InstancedObject = InObject; } - // Object accessor - class UObject* GetInstancedObject() const { return InstancedObject; } - - - // Instance Accessor - TArray& GetInstancedActorsForWrite() { return InstancedActors; } - // const Instance accessor - const TArray& GetInstancedActors() const { return InstancedActors; } - - // Returns the instanced actor at a given index - AActor* GetInstancedActorAt(const int32& Idx) { return InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; } - - // Add an instance to this component. Transform is given in local space of this component. - int32 AddInstance(const FTransform& InstanceTransform, AActor * NewActor); - - // Sets the instance at a given index in this component. Transform is given in local space of this component. - bool SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor); - - // Updates the transform for a given actor. Transform is given in local space of this component. - bool SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform); - - // Destroy all existing instances - void ClearAllInstances(); - - // Sets the number of instances needed - // Properly deletes extras, new instance actors are nulled - void SetNumberOfInstances(const int32& NewInstanceNum); - - // Set the instances. Transforms are given in local space of this component. - bool SetInstanceTransforms(const TArray& InstanceTransforms); - - private: - - UPROPERTY(VisibleAnywhere, Category = Instances ) - UObject* InstancedObject; - - UPROPERTY(VisibleInstanceOnly, Category = Instances ) - TArray InstancedActors; - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" + +#include "HoudiniInstancedActorComponent.generated.h" + + +UCLASS()//( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniInstancedActorComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniInstancedActorComponent_V1; + + public: + + virtual void Serialize(FArchive & Ar) override; + + virtual void OnComponentCreated() override; + virtual void OnComponentDestroyed( bool bDestroyingHierarchy ) override; + + static void AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ); + + // Object mutator + void SetInstancedObject(class UObject* InObject) { InstancedObject = InObject; } + // Object accessor + class UObject* GetInstancedObject() const { return InstancedObject; } + + + // Instance Accessor + TArray& GetInstancedActorsForWrite() { return InstancedActors; } + // const Instance accessor + const TArray& GetInstancedActors() const { return InstancedActors; } + + // Returns the instanced actor at a given index + AActor* GetInstancedActorAt(const int32& Idx) { return InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; } + + // Add an instance to this component. Transform is given in local space of this component. + int32 AddInstance(const FTransform& InstanceTransform, AActor * NewActor); + + // Sets the instance at a given index in this component. Transform is given in local space of this component. + bool SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor); + + // Updates the transform for a given actor. Transform is given in local space of this component. + bool SetInstanceTransformAt(const int32& Idx, const FTransform& InstanceTransform); + + // Destroy all existing instances + void ClearAllInstances(); + + // Sets the number of instances needed + // Properly deletes extras, new instance actors are nulled + void SetNumberOfInstances(const int32& NewInstanceNum); + + // Set the instances. Transforms are given in local space of this component. + bool SetInstanceTransforms(const TArray& InstanceTransforms); + + private: + + UPROPERTY(VisibleAnywhere, Category = Instances ) + UObject* InstancedObject; + + UPROPERTY(VisibleInstanceOnly, Category = Instances ) + TArray InstancedActors; + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp index a1ca7abca..bcaa2c558 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp @@ -1,247 +1,247 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniMeshSplitInstancerComponent.h" - -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineRuntimePrivatePCH.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -#include "Components/StaticMeshComponent.h" - -/* -#if WITH_EDITOR - #include "ScopedTransaction.h" - #include "LevelEditorViewport.h" - #include "MeshPaintHelpers.h" -#endif -*/ - -#include "Internationalization/Internationalization.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - -UHoudiniMeshSplitInstancerComponent::UHoudiniMeshSplitInstancerComponent(const FObjectInitializer& ObjectInitializer) - : Super( ObjectInitializer ) - , InstancedMesh( nullptr ) -{ -} - -void -UHoudiniMeshSplitInstancerComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on the setting value - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniMeshSplitInstancerComponent_V1* CompatibilityMSIC = NewObject(); - CompatibilityMSIC->Serialize(Ar); - CompatibilityMSIC->UpdateFromLegacyData(this); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -void -UHoudiniMeshSplitInstancerComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) -{ - ClearInstances(0); - Super::OnComponentDestroyed( bDestroyingHierarchy ); -} - - -void -UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) -{ - UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); - if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) - { - Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); - for(auto& Mat : ThisMSIC->OverrideMaterials) - Collector.AddReferencedObject(Mat, ThisMSIC); - Collector.AddReferencedObjects(ThisMSIC->Instances, ThisMSIC); - } -} - -bool -UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( - const TArray& InstanceTransforms) -{ - if (Instances.Num() <= 0 && InstanceTransforms.Num() <= 0) - return false; - - if (!GetOwner() || GetOwner()->IsPendingKill()) - return false; - - // Destroy previous instances while keeping some of the one that we'll be able to reuse - ClearInstances(InstanceTransforms.Num()); - - // - if( !InstancedMesh || InstancedMesh->IsPendingKill() ) - { - HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); - return false; - } - - // Only create new SMC for newly added instances - for (int32 iAdd = Instances.Num(); iAdd < InstanceTransforms.Num(); iAdd++) - { - const FTransform& InstanceTransform = InstanceTransforms[iAdd]; - UStaticMeshComponent* SMC = NewObject< UStaticMeshComponent >( - GetOwner(), UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); - - SMC->SetRelativeTransform(InstanceTransform); - Instances.Add(SMC); - GetOwner()->AddInstanceComponent(SMC); - } - - // We should now have the same number of instances than transform - ensure(InstanceTransforms.Num() == Instances.Num()); - if (InstanceTransforms.Num() != Instances.Num()) - return false; - - for (int32 iIns = 0; iIns < Instances.Num(); ++iIns) - { - UStaticMeshComponent* SMC = Instances[iIns]; - const FTransform& InstanceTransform = InstanceTransforms[iIns]; - - if (!SMC || SMC->IsPendingKill()) - continue; - - SMC->SetRelativeTransform(InstanceTransform); - - // Attach created static mesh component to this thing - SMC->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); - - SMC->SetStaticMesh(InstancedMesh); - SMC->SetVisibility(IsVisible()); - SMC->SetMobility(Mobility); - - // TODO: Revert to default if override is null?? - UMaterialInterface* MI = nullptr; - if (OverrideMaterials.Num() > 0) - { - if (OverrideMaterials.IsValidIndex(iIns)) - MI = OverrideMaterials[iIns]; - else - MI = OverrideMaterials[0]; - } - - if (MI && !MI->IsPendingKill()) - { - int32 MeshMaterialCount = InstancedMesh->StaticMaterials.Num(); - for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) - SMC->SetMaterial(Idx, MI); - } - - SMC->RegisterComponent(); - - /* - // TODO: - // Properties not being propagated to newly created UStaticMeshComponents - if (UHoudiniAssetComponent * pHoudiniAsset = Cast(GetAttachParent())) - { - pHoudiniAsset->CopyComponentPropertiesTo(SMC); - } - */ - } - - return true; -} - -void -UHoudiniMeshSplitInstancerComponent::ClearInstances(int32 NumToKeep) -{ - if (NumToKeep <= 0) - { - for (auto&& Instance : Instances) - { - if (Instance) - { - Instance->ConditionalBeginDestroy(); - } - } - Instances.Empty(); - } - else if (NumToKeep > 0 && NumToKeep < Instances.Num()) - { - for (int32 i = NumToKeep; i < Instances.Num(); ++i) - { - UStaticMeshComponent * Instance = Instances[i]; - if (Instance) - { - Instance->ConditionalBeginDestroy(); - } - } - Instances.SetNum(NumToKeep); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniMeshSplitInstancerComponent.h" + +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +#include "Components/StaticMeshComponent.h" + +/* +#if WITH_EDITOR + #include "ScopedTransaction.h" + #include "LevelEditorViewport.h" + #include "MeshPaintHelpers.h" +#endif +*/ + +#include "Internationalization/Internationalization.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + +UHoudiniMeshSplitInstancerComponent::UHoudiniMeshSplitInstancerComponent(const FObjectInitializer& ObjectInitializer) + : Super( ObjectInitializer ) + , InstancedMesh( nullptr ) +{ +} + +void +UHoudiniMeshSplitInstancerComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on the setting value + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniMeshSplitInstancerComponent_V1* CompatibilityMSIC = NewObject(); + CompatibilityMSIC->Serialize(Ar); + CompatibilityMSIC->UpdateFromLegacyData(this); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniMeshSplitInstancerComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +void +UHoudiniMeshSplitInstancerComponent::OnComponentDestroyed( bool bDestroyingHierarchy ) +{ + ClearInstances(0); + Super::OnComponentDestroyed( bDestroyingHierarchy ); +} + + +void +UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) +{ + UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); + if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) + { + Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); + for(auto& Mat : ThisMSIC->OverrideMaterials) + Collector.AddReferencedObject(Mat, ThisMSIC); + Collector.AddReferencedObjects(ThisMSIC->Instances, ThisMSIC); + } +} + +bool +UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( + const TArray& InstanceTransforms) +{ + if (Instances.Num() <= 0 && InstanceTransforms.Num() <= 0) + return false; + + if (!GetOwner() || GetOwner()->IsPendingKill()) + return false; + + // Destroy previous instances while keeping some of the one that we'll be able to reuse + ClearInstances(InstanceTransforms.Num()); + + // + if( !InstancedMesh || InstancedMesh->IsPendingKill() ) + { + HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); + return false; + } + + // Only create new SMC for newly added instances + for (int32 iAdd = Instances.Num(); iAdd < InstanceTransforms.Num(); iAdd++) + { + const FTransform& InstanceTransform = InstanceTransforms[iAdd]; + UStaticMeshComponent* SMC = NewObject< UStaticMeshComponent >( + GetOwner(), UStaticMeshComponent::StaticClass(), NAME_None, RF_Transactional); + + SMC->SetRelativeTransform(InstanceTransform); + Instances.Add(SMC); + GetOwner()->AddInstanceComponent(SMC); + } + + // We should now have the same number of instances than transform + ensure(InstanceTransforms.Num() == Instances.Num()); + if (InstanceTransforms.Num() != Instances.Num()) + return false; + + for (int32 iIns = 0; iIns < Instances.Num(); ++iIns) + { + UStaticMeshComponent* SMC = Instances[iIns]; + const FTransform& InstanceTransform = InstanceTransforms[iIns]; + + if (!SMC || SMC->IsPendingKill()) + continue; + + SMC->SetRelativeTransform(InstanceTransform); + + // Attach created static mesh component to this thing + SMC->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); + + SMC->SetStaticMesh(InstancedMesh); + SMC->SetVisibility(IsVisible()); + SMC->SetMobility(Mobility); + + // TODO: Revert to default if override is null?? + UMaterialInterface* MI = nullptr; + if (OverrideMaterials.Num() > 0) + { + if (OverrideMaterials.IsValidIndex(iIns)) + MI = OverrideMaterials[iIns]; + else + MI = OverrideMaterials[0]; + } + + if (MI && !MI->IsPendingKill()) + { + int32 MeshMaterialCount = InstancedMesh->GetStaticMaterials().Num(); + for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) + SMC->SetMaterial(Idx, MI); + } + + SMC->RegisterComponent(); + + /* + // TODO: + // Properties not being propagated to newly created UStaticMeshComponents + if (UHoudiniAssetComponent * pHoudiniAsset = Cast(GetAttachParent())) + { + pHoudiniAsset->CopyComponentPropertiesTo(SMC); + } + */ + } + + return true; +} + +void +UHoudiniMeshSplitInstancerComponent::ClearInstances(int32 NumToKeep) +{ + if (NumToKeep <= 0) + { + for (auto&& Instance : Instances) + { + if (Instance) + { + Instance->ConditionalBeginDestroy(); + } + } + Instances.Empty(); + } + else if (NumToKeep > 0 && NumToKeep < Instances.Num()) + { + for (int32 i = NumToKeep; i < Instances.Num(); ++i) + { + UStaticMeshComponent * Instance = Instances[i]; + if (Instance) + { + Instance->ConditionalBeginDestroy(); + } + } + Instances.SetNum(NumToKeep); + } +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h index 12f98bd75..ec8f6d319 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.h @@ -1,85 +1,87 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Components/SceneComponent.h" -#include "Engine/StaticMesh.h" -#include "Materials/MaterialInterface.h" -#include "HoudiniMeshSplitInstancerComponent.generated.h" - -/** -* UHoudiniMeshSplitInstancerComponent is used to manage a single static mesh being -* 'instanced' multiple times by multiple UStaticMeshComponents. This is as opposed to the -* UInstancedStaticMeshComponent wherein a single mesh is instanced multiple times by one component. -*/ - -UCLASS()//( config = Engine ) -class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USceneComponent -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniMeshSplitInstancerComponent_V1; - - public: - - virtual void Serialize(FArchive & Ar) override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - - static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); - - // Static Mesh mutator - void SetStaticMesh(class UStaticMesh* StaticMesh) { InstancedMesh = StaticMesh; } - - // Static mesh accessor - class UStaticMesh* GetStaticMesh() const { return InstancedMesh; } - - // Overide material mutator - void SetOverrideMaterials(const TArray& InMaterialOverrides) { OverrideMaterials = InMaterialOverrides; } - - // Destroy existing instances, keeping a given number of them to be reused - void ClearInstances(int32 NumToKeep); - - // Set the instances. Transforms are given in local space of this component. - bool SetInstanceTransforms(const TArray& InstanceTransforms); - - // Instance Accessor - TArray& GetInstancesForWrite() { return Instances; } - // const Instance accessor - const TArray& GetInstances() const { return Instances; } - - private: - - UPROPERTY(VisibleInstanceOnly, Category = Instances) - TArray Instances; - - UPROPERTY(VisibleInstanceOnly, Category = Instances) - TArray OverrideMaterials; - - UPROPERTY(VisibleAnywhere, Category = Instances) - class UStaticMesh* InstancedMesh; -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Components/SceneComponent.h" +#include "Engine/StaticMesh.h" +#include "Materials/MaterialInterface.h" +#include "HoudiniMeshSplitInstancerComponent.generated.h" + +/** +* UHoudiniMeshSplitInstancerComponent is used to manage a single static mesh being +* 'instanced' multiple times by multiple UStaticMeshComponents. This is as opposed to the +* UInstancedStaticMeshComponent wherein a single mesh is instanced multiple times by one component. +*/ + +UCLASS()//( config = Engine ) +class HOUDINIENGINERUNTIME_API UHoudiniMeshSplitInstancerComponent : public USceneComponent +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniMeshSplitInstancerComponent_V1; + + public: + + virtual void Serialize(FArchive & Ar) override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + + static void AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector); + + // Static Mesh mutator + void SetStaticMesh(class UStaticMesh* StaticMesh) { InstancedMesh = StaticMesh; } + + // Static mesh accessor + class UStaticMesh* GetStaticMesh() const { return InstancedMesh; } + + // Overide material mutator + void SetOverrideMaterials(const TArray& InMaterialOverrides) { OverrideMaterials = InMaterialOverrides; } + + // Destroy existing instances, keeping a given number of them to be reused + void ClearInstances(int32 NumToKeep); + + // Set the instances. Transforms are given in local space of this component. + bool SetInstanceTransforms(const TArray& InstanceTransforms); + + // Instance Accessor + TArray& GetInstancesForWrite() { return Instances; } + // const Instance accessor + const TArray& GetInstances() const { return Instances; } + + TArray GetOverrideMaterials() const { return OverrideMaterials; } + + private: + + UPROPERTY(VisibleInstanceOnly, Category = Instances) + TArray Instances; + + UPROPERTY(VisibleInstanceOnly, Category = Instances) + TArray OverrideMaterials; + + UPROPERTY(VisibleAnywhere, Category = Instances) + class UStaticMesh* InstancedMesh; +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp index 12792c8db..564c20e06 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp @@ -1,939 +1,939 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniOutput.h" -#include "HoudiniAssetComponent.h" - -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniSplineComponent.h" - -#include "Components/SceneComponent.h" -#include "Components/MeshComponent.h" -#include "Components/SplineComponent.h" -#include "Misc/StringFormatArg.h" -#include "Engine/Engine.h" -#include "LandscapeLayerInfoObject.h" - -UHoudiniLandscapePtr::UHoudiniLandscapePtr(class FObjectInitializer const& Initializer) -{ - // bIsWorldCompositionLandscape = false; - // BakeType = EHoudiniLandscapeOutputBakeType::Detachment; -}; - -uint32 -GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier) -{ - return HoudiniOutputObjectIdentifier.GetTypeHash(); -} - -void -FHoudiniInstancedOutput::SetVariationObjectAt(const int32& AtIndex, UObject* InObject) -{ - // Resize the array if needed - if (VariationObjects.Num() <= AtIndex) - VariationObjects.SetNum(AtIndex + 1); - - if (VariationTransformOffsets.Num() <= AtIndex) - VariationTransformOffsets.SetNum(AtIndex + 1); - - UObject* CurrentObject = VariationObjects[AtIndex].LoadSynchronous(); - if (CurrentObject == InObject) - return; - - VariationObjects[AtIndex] = InObject; -} - -bool -FHoudiniInstancedOutput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; - if (!Transform) - return false; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - if (Position[XYZIndex] == Value) - return false; - Position[XYZIndex] = Value; - Transform->SetLocation(Position); - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - if (Rotator.Roll == Value) - return false; - Rotator.Roll = Value; - break; - } - - case 1: - { - if (Rotator.Pitch == Value) - return false; - Rotator.Pitch = Value; - break; - } - - case 2: - { - if (Rotator.Yaw == Value) - return false; - Rotator.Yaw = Value; - break; - } - } - Transform->SetRotation(Rotator.Quaternion()); - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - if (Scale[XYZIndex] == Value) - return false; - - Scale[XYZIndex] = Value; - Transform->SetScale3D(Scale); - } - - MarkChanged(true); - - return true; -} - -float -FHoudiniInstancedOutput::GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) -{ - FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; - if (!Transform) - return 0.0f; - - if (PosRotScaleIndex == 0) - { - FVector Position = Transform->GetLocation(); - return Position[XYZIndex]; - } - else if (PosRotScaleIndex == 1) - { - FRotator Rotator = Transform->Rotator(); - switch (XYZIndex) - { - case 0: - { - return Rotator.Roll; - } - - case 1: - { - return Rotator.Pitch; - } - - case 2: - { - return Rotator.Yaw; - } - } - } - else if (PosRotScaleIndex == 2) - { - FVector Scale = Transform->GetScale3D(); - return Scale[XYZIndex]; - } - - return 0.0f; -} - -// ---------------------------------------------------- -// FHoudiniOutputObjectIdentifier -// ---------------------------------------------------- - -FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier() -{ - ObjectId = -1; - GeoId = -1; - PartId = -1; - SplitIdentifier = FString(); - PartName = FString(); -} - -FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier( - const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier) -{ - ObjectId = InObjectId; - GeoId = InGeoId; - PartId = InPartId; - SplitIdentifier = InSplitIdentifier; -} - -uint32 -FHoudiniOutputObjectIdentifier::GetTypeHash() const -{ - int32 HashBuffer[3] = { ObjectId, GeoId, PartId }; - int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); - return FCrc::StrCrc32(*SplitIdentifier, Hash); -} - -bool -FHoudiniOutputObjectIdentifier::operator==(const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier) const -{ - // Object/Geo/Part IDs must match - bool bMatchingIds = true; - if (ObjectId != InOutputObjectIdentifier.ObjectId - || GeoId != InOutputObjectIdentifier.GeoId - || PartId != InOutputObjectIdentifier.PartId) - bMatchingIds = false; - - if ((bLoaded && !InOutputObjectIdentifier.bLoaded) - || (!bLoaded && InOutputObjectIdentifier.bLoaded)) - { - // If one of the two identifier is loaded, - // we can simply compare the part names - if (PartName.Equals(InOutputObjectIdentifier.PartName) - && SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) - return true; - } - - if (!bMatchingIds) - { - return false; - } - - // If split ID and name match, we're equal... - if (SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) - return true; - - // ... if not we're different - return false; -} - -bool -FHoudiniOutputObjectIdentifier::Matches(const FHoudiniGeoPartObject& InHGPO) const -{ - // Object/Geo/Part IDs must match - bool bMatchingIds = true; - if (ObjectId != InHGPO.ObjectId - || GeoId != InHGPO.GeoId - || PartId != InHGPO.PartId) - bMatchingIds = false; - - if ((bLoaded && !InHGPO.bLoaded) || (!bLoaded && InHGPO.bLoaded)) - { - // If either the HGPO or the Identifer is nmarked as loaded, - // we can simply compare the part names - if (PartName.Equals(InHGPO.PartName)) - return true; - } - - if (!bMatchingIds) - { - return false; - } - - // If the HGPO has our split identifier - //if (InHGPO.SplitGroups.Contains(SplitIdentifier)) - // return true; - - // - return true; -} - - -// ---------------------------------------------------- -// FHoudiniBakedOutputObjectIdentifier -// ---------------------------------------------------- - - -FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier() -{ - PartId = -1; - SplitIdentifier = FString(); -} - -FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier( - const int32& InPartId, const FString& InSplitIdentifier) -{ - PartId = InPartId; - SplitIdentifier = InSplitIdentifier; -} - -FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) -{ - PartId = InIdentifier.PartId; - SplitIdentifier = InIdentifier.SplitIdentifier; -} - -uint32 -FHoudiniBakedOutputObjectIdentifier::GetTypeHash() const -{ - const int32 HashBuffer = PartId; - const int32 Hash = FCrc::MemCrc32((void *)&HashBuffer, sizeof(HashBuffer)); - return FCrc::StrCrc32(*SplitIdentifier, Hash); -} - -uint32 -GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) -{ - return InIdentifier.GetTypeHash(); -} - -bool -FHoudiniBakedOutputObjectIdentifier::operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const -{ - return (InIdentifier.PartId == PartId && InIdentifier.SplitIdentifier.Equals(SplitIdentifier)); -} - - -// ---------------------------------------------------- -// FHoudiniBakedOutputObject -// ---------------------------------------------------- - - -FHoudiniBakedOutputObject::FHoudiniBakedOutputObject() - : Actor() - , ActorBakeName(NAME_None) - , BakedObject() - , BakedComponent() -{ -} - - -FHoudiniBakedOutputObject::FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject, UObject* InBakedComponent) - : Actor(FSoftObjectPath(InActor).ToString()) - , ActorBakeName(InActorBakeName) - , BakedObject(FSoftObjectPath(InBakeObject).ToString()) - , BakedComponent(FSoftObjectPath(InBakedComponent).ToString()) -{ -} - - -AActor* -FHoudiniBakedOutputObject::GetActorIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ActorPath(Actor); - - if (!ActorPath.IsValid()) - return nullptr; - - UObject* Object = ActorPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ActorPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Cast(Object); -} - -UObject* -FHoudiniBakedOutputObject::GetBakedObjectIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ObjectPath(BakedObject); - - if (!ObjectPath.IsValid()) - return nullptr; - - UObject* Object = ObjectPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ObjectPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Object; -} - -UObject* -FHoudiniBakedOutputObject::GetBakedComponentIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath ComponentPath(BakedComponent); - - if (!ComponentPath.IsValid()) - return nullptr; - - UObject* Object = ComponentPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = ComponentPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Object; -} - -UBlueprint* -FHoudiniBakedOutputObject::GetBlueprintIfValid(bool bInTryLoad) const -{ - const FSoftObjectPath BlueprintPath(Blueprint); - - if (!BlueprintPath.IsValid()) - return nullptr; - - UObject* Object = BlueprintPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = BlueprintPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Cast(Object); -} - -ULandscapeLayerInfoObject* -FHoudiniBakedOutputObject::GetLandscapeLayerInfoIfValid(const FName& InLayerName, const bool bInTryLoad) const -{ - if (!LandscapeLayers.Contains(InLayerName)) - return nullptr; - - const FString& LayerInfoPathStr = LandscapeLayers.FindChecked(InLayerName); - const FSoftObjectPath LayerInfoPath(LayerInfoPathStr); - - if (!LayerInfoPath.IsValid()) - return nullptr; - - UObject* Object = LayerInfoPath.ResolveObject(); - if (!Object && bInTryLoad) - Object = LayerInfoPath.TryLoad(); - - if (!IsValid(Object)) - return nullptr; - - return Cast(Object); -} - - -UHoudiniOutput::UHoudiniOutput(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Type(EHoudiniOutputType::Invalid) - , StaleCount(0) - , bLandscapeWorldComposition(false) - , bIsEditableNode(false) - , bHasEditableNodeBuilt(false) - , bCanDeleteHoudiniNodes(true) -{ - -} - -UHoudiniOutput::~UHoudiniOutput() -{ - Type = EHoudiniOutputType::Invalid; - StaleCount = 0; - bIsUpdating = false; - - HoudiniGeoPartObjects.Empty(); - OutputObjects.Empty(); - InstancedOutputs.Empty(); - AssignementMaterials.Empty(); - ReplacementMaterials.Empty(); -} - -void -UHoudiniOutput::BeginDestroy() -{ - Super::BeginDestroy(); -} - -FBox -UHoudiniOutput::GetBounds() const -{ - FBox BoxBounds(ForceInitToZero); - - switch (GetType()) - { - case EHoudiniOutputType::Mesh: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - - UMeshComponent* MeshComp = nullptr; - if (CurObj.bProxyIsCurrent) - { - MeshComp = Cast(CurObj.ProxyComponent); - } - else - { - MeshComp = Cast(CurObj.OutputComponent); - } - - if (!MeshComp || MeshComp->IsPendingKill()) - continue; - - BoxBounds += MeshComp->Bounds.GetBox(); - } - } - break; - - case EHoudiniOutputType::Landscape: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - UHoudiniLandscapePtr* CurLandscapeObj = Cast(CurObj.OutputObject); - if (!CurLandscapeObj || CurLandscapeObj->IsPendingKill()) - continue; - - ALandscapeProxy* Landscape = Cast(CurLandscapeObj->GetRawPtr()); - if (!Landscape || Landscape->IsPendingKill()) - continue; - - FVector Origin, Extent; - Landscape->GetActorBounds(false, Origin, Extent); - - FBox LandscapeBounds = FBox::BuildAABB(Origin, Extent); - BoxBounds += LandscapeBounds; - } - } - break; - - case EHoudiniOutputType::Instancer: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - USceneComponent* InstancedComp = Cast(CurObj.OutputObject); - if (!InstancedComp || InstancedComp->IsPendingKill()) - continue; - - BoxBounds += InstancedComp->Bounds.GetBox(); - } - } - break; - - case EHoudiniOutputType::Curve: - { - for (auto & CurPair : OutputObjects) - { - const FHoudiniOutputObject& CurObj = CurPair.Value; - UHoudiniSplineComponent* CurHoudiniSplineComp = Cast(CurObj.OutputComponent); - if (!CurHoudiniSplineComp || CurHoudiniSplineComp->IsPendingKill()) - continue; - - FBox CurCurveBound(ForceInitToZero); - for (auto & Trans : CurHoudiniSplineComp->CurvePoints) - { - CurCurveBound += Trans.GetLocation(); - } - - UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) - BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); - } - - } - break; - - case EHoudiniOutputType::Skeletal: - case EHoudiniOutputType::Invalid: - break; - - default: - break; - } - - return BoxBounds; -} - -void -UHoudiniOutput::Clear() -{ - StaleCount = 0; - - HoudiniGeoPartObjects.Empty(); - - for (auto& CurrentOutputObject : OutputObjects) - { - // Clear the output component - USceneComponent* SceneComp = Cast(CurrentOutputObject.Value.OutputComponent); - if (SceneComp && !SceneComp->IsPendingKill()) - { - SceneComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - SceneComp->UnregisterComponent(); - SceneComp->DestroyComponent(); - } - - // Also destroy proxy components - USceneComponent* ProxyComp = Cast(CurrentOutputObject.Value.ProxyComponent); - if (ProxyComp && !ProxyComp->IsPendingKill()) - { - ProxyComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); - ProxyComp->UnregisterComponent(); - ProxyComp->DestroyComponent(); - } - - if (Type == EHoudiniOutputType::Landscape && !bLandscapeWorldComposition && !IsGarbageCollecting()) - { - // NOTE: We cannot resolve soft pointers during garbage collection. Any Get() or IsValid() call - // will result in a StaticFindObject() call which will raise an exception during GC. - UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentOutputObject.Value.OutputObject); - ALandscapeProxy* LandscapeProxy = LandscapePtr ? LandscapePtr->GetRawPtr() : nullptr; - if (LandscapeProxy && !LandscapeProxy->IsPendingKill()) - { - LandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); - LandscapeProxy->ConditionalBeginDestroy(); - LandscapeProxy->Destroy(); - } - } - } - - OutputObjects.Empty(); - InstancedOutputs.Empty(); - AssignementMaterials.Empty(); - ReplacementMaterials.Empty(); - - Type = EHoudiniOutputType::Invalid; -} - -bool -UHoudiniOutput::ShouldDeferClear() const -{ - if (Type == EHoudiniOutputType::Landscape) - return true; - - return false; -} - -const bool -UHoudiniOutput::HasGeoChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasGeoChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasTransformChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasTransformChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasMaterialsChanged() const -{ - for (auto currentHGPO : HoudiniGeoPartObjects) - { - if (currentHGPO.bHasMaterialsChanged) - return true; - } - - return false; -} - -const bool -UHoudiniOutput::HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const -{ - return HoudiniGeoPartObjects.Find(InHGPO) != INDEX_NONE; -} - -const bool -UHoudiniOutput::HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const -{ - if (InHGPO.Type != EHoudiniPartType::Volume) - return false; - - if (InHGPO.VolumeName.IsEmpty()) - return false; - - for (auto& currentHGPO : HoudiniGeoPartObjects) - { - // Asset/Object/Geo IDs should match - if (currentHGPO.AssetId != InHGPO.AssetId - || currentHGPO.ObjectId != InHGPO.ObjectId - || currentHGPO.GeoId != InHGPO.GeoId) - { - continue; - } - - // Both HGPO type should be volumes - if (currentHGPO.Type != EHoudiniPartType::Volume) - { - continue; - } - - // Volume tile index should match - if (currentHGPO.VolumeTileIndex != InHGPO.VolumeTileIndex) - { - continue; - } - - // We've specified if we want the name to match/to be different: - // when looking in previous outputs, we want the name to match - // when looking in newly created outputs, we want to be sure the names are different - bool bNameMatch = InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase); - if (bNameMatch != bVolumeNameShouldMatch) - continue; - - return true; - } - - return false; -} - -void -UHoudiniOutput::MarkAllHGPOsAsStale(const bool& bInStale) -{ - // Since objects can only be added to this array, - // Simply keep track of the current number of HoudiniGeoPartObject - StaleCount = bInStale ? HoudiniGeoPartObjects.Num() : 0; -} - -void -UHoudiniOutput::DeleteAllStaleHGPOs() -{ - // Simply delete the first "StaleCount" objects and reset the stale marker - HoudiniGeoPartObjects.RemoveAt(0, StaleCount); - StaleCount = 0; -} - -void -UHoudiniOutput::AddNewHGPO(const FHoudiniGeoPartObject& InHGPO) -{ - HoudiniGeoPartObjects.Add(InHGPO); -} - -void -UHoudiniOutput::UpdateOutputType() -{ - int32 MeshCount = 0; - int32 CurveCount = 0; - int32 VolumeCount = 0; - int32 InstancerCount = 0; - for (auto& HGPO : HoudiniGeoPartObjects) - { - switch (HGPO.Type) - { - case EHoudiniPartType::Mesh: - MeshCount++; - break; - case EHoudiniPartType::Curve: - CurveCount++; - break; - case EHoudiniPartType::Volume: - VolumeCount++; - break; - case EHoudiniPartType::Instancer: - InstancerCount++; - break; - default: - case EHoudiniPartType::Invalid: - break; - } - } - - if (VolumeCount > 0) - { - // If we have a volume, we're a landscape - Type = EHoudiniOutputType::Landscape; - } - else if (InstancerCount > 0) - { - // if we have at least an instancer, we're one - Type = EHoudiniOutputType::Instancer; - } - else if (MeshCount > 0) - { - Type = EHoudiniOutputType::Mesh; - } - else if (CurveCount > 0) - { - Type = EHoudiniOutputType::Curve; - } - else - { - // No valid HGPO detected... - Type = EHoudiniOutputType::Invalid; - } -} - -UHoudiniOutput* -UHoudiniOutput::DuplicateAndCopyProperties(UObject* DestOuter, FName NewName) -{ - UHoudiniOutput* NewOutput = Cast(StaticDuplicateObject(this, DestOuter, NewName)); - - NewOutput->CopyPropertiesFrom(this, false); - - return NewOutput; -} - -void -UHoudiniOutput::CopyPropertiesFrom(UHoudiniOutput* InInput, bool bCopyAllProperties) -{ - // Copy the state of this UHoudiniInput object. - if (bCopyAllProperties) - { - // Stash all the data that we want to preserve, and re-apply after property copy took place - // (similar to Get/Apply component instance data). This is typically only needed - // for certain properties that require cleanup when being replaced / removed. - TMap PrevOutputObjects = OutputObjects; - TMap PrevInstancedOutputs = InstancedOutputs; - - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); - - // Restore the desired properties. - OutputObjects = PrevOutputObjects; - InstancedOutputs = PrevInstancedOutputs; - } - - // Copy any additional DuplicateTransient properties. - bHasEditableNodeBuilt = InInput->bHasEditableNodeBuilt; -} - -void -UHoudiniOutput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) -{ - bCanDeleteHoudiniNodes = bInCanDeleteNodes; -} - -FString -UHoudiniOutput::OutputTypeToString(const EHoudiniOutputType& InOutputType) -{ - FString OutputTypeStr; - switch (InOutputType) - { - case EHoudiniOutputType::Mesh: - OutputTypeStr = TEXT("Mesh"); - break; - case EHoudiniOutputType::Instancer: - OutputTypeStr = TEXT("Instancer"); - break; - case EHoudiniOutputType::Landscape: - OutputTypeStr = TEXT("Landscape"); - break; - case EHoudiniOutputType::Curve: - OutputTypeStr = TEXT("Curve"); - break; - case EHoudiniOutputType::Skeletal: - OutputTypeStr = TEXT("Skeletal"); - break; - - default: - case EHoudiniOutputType::Invalid: - OutputTypeStr = TEXT("Invalid"); - break; - } - - return OutputTypeStr; -} - -void -UHoudiniOutput::MarkAsLoaded(const bool& InLoaded) -{ - // Mark all HGPO as loaded - for (auto& HGPO : HoudiniGeoPartObjects) - { - HGPO.bLoaded = InLoaded; - } - - // Mark all output object's identifier as loaded - for (auto& Iter : OutputObjects) - { - FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; - Identifier.bLoaded = InLoaded; - } - - // Instanced outputs - for (auto& Iter : InstancedOutputs) - { - FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; - Identifier.bLoaded = InLoaded; - } -} - - -const bool -UHoudiniOutput::HasAnyProxy() const -{ - for (const auto& Pair : OutputObjects) - { - UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) - { - return true; - } - } - - return false; -} - -const bool -UHoudiniOutput::HasProxy(const FHoudiniOutputObjectIdentifier& InIdentifier) const -{ - const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - if (!FoundOutputObject) - return false; - - UObject* FoundProxy = FoundOutputObject->ProxyObject; - if (!FoundProxy || FoundProxy->IsPendingKill()) - return false; - - return true; -} - -const bool -UHoudiniOutput::HasAnyCurrentProxy() const -{ - for (const auto& Pair : OutputObjects) - { - UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) - { - if(Pair.Value.bProxyIsCurrent) - { - return true; - } - } - } - - return false; -} - -const bool -UHoudiniOutput::IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const -{ - if (!HasProxy(InIdentifier)) - return false; - - const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); - if (!FoundOutputObject) - return false; - - return FoundOutputObject->bProxyIsCurrent; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniOutput.h" +#include "HoudiniAssetComponent.h" + +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniSplineComponent.h" + +#include "Components/SceneComponent.h" +#include "Components/MeshComponent.h" +#include "Components/SplineComponent.h" +#include "Misc/StringFormatArg.h" +#include "Engine/Engine.h" +#include "LandscapeLayerInfoObject.h" + +UHoudiniLandscapePtr::UHoudiniLandscapePtr(class FObjectInitializer const& Initializer) +{ + // bIsWorldCompositionLandscape = false; + // BakeType = EHoudiniLandscapeOutputBakeType::Detachment; +}; + +uint32 +GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier) +{ + return HoudiniOutputObjectIdentifier.GetTypeHash(); +} + +void +FHoudiniInstancedOutput::SetVariationObjectAt(const int32& AtIndex, UObject* InObject) +{ + // Resize the array if needed + if (VariationObjects.Num() <= AtIndex) + VariationObjects.SetNum(AtIndex + 1); + + if (VariationTransformOffsets.Num() <= AtIndex) + VariationTransformOffsets.SetNum(AtIndex + 1); + + UObject* CurrentObject = VariationObjects[AtIndex].LoadSynchronous(); + if (CurrentObject == InObject) + return; + + VariationObjects[AtIndex] = InObject; +} + +bool +FHoudiniInstancedOutput::SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; + if (!Transform) + return false; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + if (Position[XYZIndex] == Value) + return false; + Position[XYZIndex] = Value; + Transform->SetLocation(Position); + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + if (Rotator.Roll == Value) + return false; + Rotator.Roll = Value; + break; + } + + case 1: + { + if (Rotator.Pitch == Value) + return false; + Rotator.Pitch = Value; + break; + } + + case 2: + { + if (Rotator.Yaw == Value) + return false; + Rotator.Yaw = Value; + break; + } + } + Transform->SetRotation(Rotator.Quaternion()); + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + if (Scale[XYZIndex] == Value) + return false; + + Scale[XYZIndex] = Value; + Transform->SetScale3D(Scale); + } + + MarkChanged(true); + + return true; +} + +float +FHoudiniInstancedOutput::GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex) +{ + FTransform* Transform = VariationTransformOffsets.IsValidIndex(AtIndex) ? &VariationTransformOffsets[AtIndex] : nullptr; + if (!Transform) + return 0.0f; + + if (PosRotScaleIndex == 0) + { + FVector Position = Transform->GetLocation(); + return Position[XYZIndex]; + } + else if (PosRotScaleIndex == 1) + { + FRotator Rotator = Transform->Rotator(); + switch (XYZIndex) + { + case 0: + { + return Rotator.Roll; + } + + case 1: + { + return Rotator.Pitch; + } + + case 2: + { + return Rotator.Yaw; + } + } + } + else if (PosRotScaleIndex == 2) + { + FVector Scale = Transform->GetScale3D(); + return Scale[XYZIndex]; + } + + return 0.0f; +} + +// ---------------------------------------------------- +// FHoudiniOutputObjectIdentifier +// ---------------------------------------------------- + +FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier() +{ + ObjectId = -1; + GeoId = -1; + PartId = -1; + SplitIdentifier = FString(); + PartName = FString(); +} + +FHoudiniOutputObjectIdentifier::FHoudiniOutputObjectIdentifier( + const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier) +{ + ObjectId = InObjectId; + GeoId = InGeoId; + PartId = InPartId; + SplitIdentifier = InSplitIdentifier; +} + +uint32 +FHoudiniOutputObjectIdentifier::GetTypeHash() const +{ + int32 HashBuffer[3] = { ObjectId, GeoId, PartId }; + int32 Hash = FCrc::MemCrc32((void *)&HashBuffer[0], sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitIdentifier, Hash); +} + +bool +FHoudiniOutputObjectIdentifier::operator==(const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier) const +{ + // Object/Geo/Part IDs must match + bool bMatchingIds = true; + if (ObjectId != InOutputObjectIdentifier.ObjectId + || GeoId != InOutputObjectIdentifier.GeoId + || PartId != InOutputObjectIdentifier.PartId) + bMatchingIds = false; + + if ((bLoaded && !InOutputObjectIdentifier.bLoaded) + || (!bLoaded && InOutputObjectIdentifier.bLoaded)) + { + // If one of the two identifier is loaded, + // we can simply compare the part names + if (PartName.Equals(InOutputObjectIdentifier.PartName) + && SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) + return true; + } + + if (!bMatchingIds) + { + return false; + } + + // If split ID and name match, we're equal... + if (SplitIdentifier.Equals(InOutputObjectIdentifier.SplitIdentifier)) + return true; + + // ... if not we're different + return false; +} + +bool +FHoudiniOutputObjectIdentifier::Matches(const FHoudiniGeoPartObject& InHGPO) const +{ + // Object/Geo/Part IDs must match + bool bMatchingIds = true; + if (ObjectId != InHGPO.ObjectId + || GeoId != InHGPO.GeoId + || PartId != InHGPO.PartId) + bMatchingIds = false; + + if ((bLoaded && !InHGPO.bLoaded) || (!bLoaded && InHGPO.bLoaded)) + { + // If either the HGPO or the Identifer is nmarked as loaded, + // we can simply compare the part names + if (PartName.Equals(InHGPO.PartName)) + return true; + } + + if (!bMatchingIds) + { + return false; + } + + // If the HGPO has our split identifier + //if (InHGPO.SplitGroups.Contains(SplitIdentifier)) + // return true; + + // + return true; +} + + +// ---------------------------------------------------- +// FHoudiniBakedOutputObjectIdentifier +// ---------------------------------------------------- + + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier() +{ + PartId = -1; + SplitIdentifier = FString(); +} + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier( + const int32& InPartId, const FString& InSplitIdentifier) +{ + PartId = InPartId; + SplitIdentifier = InSplitIdentifier; +} + +FHoudiniBakedOutputObjectIdentifier::FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier) +{ + PartId = InIdentifier.PartId; + SplitIdentifier = InIdentifier.SplitIdentifier; +} + +uint32 +FHoudiniBakedOutputObjectIdentifier::GetTypeHash() const +{ + const int32 HashBuffer = PartId; + const int32 Hash = FCrc::MemCrc32((void *)&HashBuffer, sizeof(HashBuffer)); + return FCrc::StrCrc32(*SplitIdentifier, Hash); +} + +uint32 +GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) +{ + return InIdentifier.GetTypeHash(); +} + +bool +FHoudiniBakedOutputObjectIdentifier::operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const +{ + return (InIdentifier.PartId == PartId && InIdentifier.SplitIdentifier.Equals(SplitIdentifier)); +} + + +// ---------------------------------------------------- +// FHoudiniBakedOutputObject +// ---------------------------------------------------- + + +FHoudiniBakedOutputObject::FHoudiniBakedOutputObject() + : Actor() + , ActorBakeName(NAME_None) + , BakedObject() + , BakedComponent() +{ +} + + +FHoudiniBakedOutputObject::FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject, UObject* InBakedComponent) + : Actor(FSoftObjectPath(InActor).ToString()) + , ActorBakeName(InActorBakeName) + , BakedObject(FSoftObjectPath(InBakeObject).ToString()) + , BakedComponent(FSoftObjectPath(InBakedComponent).ToString()) +{ +} + + +AActor* +FHoudiniBakedOutputObject::GetActorIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ActorPath(Actor); + + if (!ActorPath.IsValid()) + return nullptr; + + UObject* Object = ActorPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ActorPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + +UObject* +FHoudiniBakedOutputObject::GetBakedObjectIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ObjectPath(BakedObject); + + if (!ObjectPath.IsValid()) + return nullptr; + + UObject* Object = ObjectPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ObjectPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Object; +} + +UObject* +FHoudiniBakedOutputObject::GetBakedComponentIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath ComponentPath(BakedComponent); + + if (!ComponentPath.IsValid()) + return nullptr; + + UObject* Object = ComponentPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = ComponentPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Object; +} + +UBlueprint* +FHoudiniBakedOutputObject::GetBlueprintIfValid(bool bInTryLoad) const +{ + const FSoftObjectPath BlueprintPath(Blueprint); + + if (!BlueprintPath.IsValid()) + return nullptr; + + UObject* Object = BlueprintPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = BlueprintPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + +ULandscapeLayerInfoObject* +FHoudiniBakedOutputObject::GetLandscapeLayerInfoIfValid(const FName& InLayerName, const bool bInTryLoad) const +{ + if (!LandscapeLayers.Contains(InLayerName)) + return nullptr; + + const FString& LayerInfoPathStr = LandscapeLayers.FindChecked(InLayerName); + const FSoftObjectPath LayerInfoPath(LayerInfoPathStr); + + if (!LayerInfoPath.IsValid()) + return nullptr; + + UObject* Object = LayerInfoPath.ResolveObject(); + if (!Object && bInTryLoad) + Object = LayerInfoPath.TryLoad(); + + if (!IsValid(Object)) + return nullptr; + + return Cast(Object); +} + + +UHoudiniOutput::UHoudiniOutput(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Type(EHoudiniOutputType::Invalid) + , StaleCount(0) + , bLandscapeWorldComposition(false) + , bIsEditableNode(false) + , bHasEditableNodeBuilt(false) + , bCanDeleteHoudiniNodes(true) +{ + +} + +UHoudiniOutput::~UHoudiniOutput() +{ + Type = EHoudiniOutputType::Invalid; + StaleCount = 0; + bIsUpdating = false; + + HoudiniGeoPartObjects.Empty(); + OutputObjects.Empty(); + InstancedOutputs.Empty(); + AssignementMaterials.Empty(); + ReplacementMaterials.Empty(); +} + +void +UHoudiniOutput::BeginDestroy() +{ + Super::BeginDestroy(); +} + +FBox +UHoudiniOutput::GetBounds() const +{ + FBox BoxBounds(ForceInitToZero); + + switch (GetType()) + { + case EHoudiniOutputType::Mesh: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + + UMeshComponent* MeshComp = nullptr; + if (CurObj.bProxyIsCurrent) + { + MeshComp = Cast(CurObj.ProxyComponent); + } + else + { + MeshComp = Cast(CurObj.OutputComponent); + } + + if (!MeshComp || MeshComp->IsPendingKill()) + continue; + + BoxBounds += MeshComp->Bounds.GetBox(); + } + } + break; + + case EHoudiniOutputType::Landscape: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + UHoudiniLandscapePtr* CurLandscapeObj = Cast(CurObj.OutputObject); + if (!CurLandscapeObj || CurLandscapeObj->IsPendingKill()) + continue; + + ALandscapeProxy* Landscape = Cast(CurLandscapeObj->GetRawPtr()); + if (!Landscape || Landscape->IsPendingKill()) + continue; + + FVector Origin, Extent; + Landscape->GetActorBounds(false, Origin, Extent); + + FBox LandscapeBounds = FBox::BuildAABB(Origin, Extent); + BoxBounds += LandscapeBounds; + } + } + break; + + case EHoudiniOutputType::Instancer: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + USceneComponent* InstancedComp = Cast(CurObj.OutputObject); + if (!InstancedComp || InstancedComp->IsPendingKill()) + continue; + + BoxBounds += InstancedComp->Bounds.GetBox(); + } + } + break; + + case EHoudiniOutputType::Curve: + { + for (auto & CurPair : OutputObjects) + { + const FHoudiniOutputObject& CurObj = CurPair.Value; + UHoudiniSplineComponent* CurHoudiniSplineComp = Cast(CurObj.OutputComponent); + if (!CurHoudiniSplineComp || CurHoudiniSplineComp->IsPendingKill()) + continue; + + FBox CurCurveBound(ForceInitToZero); + for (auto & Trans : CurHoudiniSplineComp->CurvePoints) + { + CurCurveBound += Trans.GetLocation(); + } + + UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); + if (OuterHAC && !OuterHAC->IsPendingKill()) + BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); + } + + } + break; + + case EHoudiniOutputType::Skeletal: + case EHoudiniOutputType::Invalid: + break; + + default: + break; + } + + return BoxBounds; +} + +void +UHoudiniOutput::Clear() +{ + StaleCount = 0; + + HoudiniGeoPartObjects.Empty(); + + for (auto& CurrentOutputObject : OutputObjects) + { + // Clear the output component + USceneComponent* SceneComp = Cast(CurrentOutputObject.Value.OutputComponent); + if (SceneComp && !SceneComp->IsPendingKill()) + { + SceneComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + SceneComp->UnregisterComponent(); + SceneComp->DestroyComponent(); + } + + // Also destroy proxy components + USceneComponent* ProxyComp = Cast(CurrentOutputObject.Value.ProxyComponent); + if (ProxyComp && !ProxyComp->IsPendingKill()) + { + ProxyComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); + ProxyComp->UnregisterComponent(); + ProxyComp->DestroyComponent(); + } + + if (Type == EHoudiniOutputType::Landscape && !bLandscapeWorldComposition && !IsGarbageCollecting()) + { + // NOTE: We cannot resolve soft pointers during garbage collection. Any Get() or IsValid() call + // will result in a StaticFindObject() call which will raise an exception during GC. + UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentOutputObject.Value.OutputObject); + ALandscapeProxy* LandscapeProxy = LandscapePtr ? LandscapePtr->GetRawPtr() : nullptr; + if (LandscapeProxy && !LandscapeProxy->IsPendingKill()) + { + LandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); + LandscapeProxy->ConditionalBeginDestroy(); + LandscapeProxy->Destroy(); + } + } + } + + OutputObjects.Empty(); + InstancedOutputs.Empty(); + AssignementMaterials.Empty(); + ReplacementMaterials.Empty(); + + Type = EHoudiniOutputType::Invalid; +} + +bool +UHoudiniOutput::ShouldDeferClear() const +{ + if (Type == EHoudiniOutputType::Landscape) + return true; + + return false; +} + +const bool +UHoudiniOutput::HasGeoChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasGeoChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasTransformChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasTransformChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasMaterialsChanged() const +{ + for (auto currentHGPO : HoudiniGeoPartObjects) + { + if (currentHGPO.bHasMaterialsChanged) + return true; + } + + return false; +} + +const bool +UHoudiniOutput::HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const +{ + return HoudiniGeoPartObjects.Find(InHGPO) != INDEX_NONE; +} + +const bool +UHoudiniOutput::HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const +{ + if (InHGPO.Type != EHoudiniPartType::Volume) + return false; + + if (InHGPO.VolumeName.IsEmpty()) + return false; + + for (auto& currentHGPO : HoudiniGeoPartObjects) + { + // Asset/Object/Geo IDs should match + if (currentHGPO.AssetId != InHGPO.AssetId + || currentHGPO.ObjectId != InHGPO.ObjectId + || currentHGPO.GeoId != InHGPO.GeoId) + { + continue; + } + + // Both HGPO type should be volumes + if (currentHGPO.Type != EHoudiniPartType::Volume) + { + continue; + } + + // Volume tile index should match + if (currentHGPO.VolumeTileIndex != InHGPO.VolumeTileIndex) + { + continue; + } + + // We've specified if we want the name to match/to be different: + // when looking in previous outputs, we want the name to match + // when looking in newly created outputs, we want to be sure the names are different + bool bNameMatch = InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase); + if (bNameMatch != bVolumeNameShouldMatch) + continue; + + return true; + } + + return false; +} + +void +UHoudiniOutput::MarkAllHGPOsAsStale(const bool& bInStale) +{ + // Since objects can only be added to this array, + // Simply keep track of the current number of HoudiniGeoPartObject + StaleCount = bInStale ? HoudiniGeoPartObjects.Num() : 0; +} + +void +UHoudiniOutput::DeleteAllStaleHGPOs() +{ + // Simply delete the first "StaleCount" objects and reset the stale marker + HoudiniGeoPartObjects.RemoveAt(0, StaleCount); + StaleCount = 0; +} + +void +UHoudiniOutput::AddNewHGPO(const FHoudiniGeoPartObject& InHGPO) +{ + HoudiniGeoPartObjects.Add(InHGPO); +} + +void +UHoudiniOutput::UpdateOutputType() +{ + int32 MeshCount = 0; + int32 CurveCount = 0; + int32 VolumeCount = 0; + int32 InstancerCount = 0; + for (auto& HGPO : HoudiniGeoPartObjects) + { + switch (HGPO.Type) + { + case EHoudiniPartType::Mesh: + MeshCount++; + break; + case EHoudiniPartType::Curve: + CurveCount++; + break; + case EHoudiniPartType::Volume: + VolumeCount++; + break; + case EHoudiniPartType::Instancer: + InstancerCount++; + break; + default: + case EHoudiniPartType::Invalid: + break; + } + } + + if (VolumeCount > 0) + { + // If we have a volume, we're a landscape + Type = EHoudiniOutputType::Landscape; + } + else if (InstancerCount > 0) + { + // if we have at least an instancer, we're one + Type = EHoudiniOutputType::Instancer; + } + else if (MeshCount > 0) + { + Type = EHoudiniOutputType::Mesh; + } + else if (CurveCount > 0) + { + Type = EHoudiniOutputType::Curve; + } + else + { + // No valid HGPO detected... + Type = EHoudiniOutputType::Invalid; + } +} + +UHoudiniOutput* +UHoudiniOutput::DuplicateAndCopyProperties(UObject* DestOuter, FName NewName) +{ + UHoudiniOutput* NewOutput = Cast(StaticDuplicateObject(this, DestOuter, NewName)); + + NewOutput->CopyPropertiesFrom(this, false); + + return NewOutput; +} + +void +UHoudiniOutput::CopyPropertiesFrom(UHoudiniOutput* InInput, bool bCopyAllProperties) +{ + // Copy the state of this UHoudiniInput object. + if (bCopyAllProperties) + { + // Stash all the data that we want to preserve, and re-apply after property copy took place + // (similar to Get/Apply component instance data). This is typically only needed + // for certain properties that require cleanup when being replaced / removed. + TMap PrevOutputObjects = OutputObjects; + TMap PrevInstancedOutputs = InstancedOutputs; + + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InInput, this, Params); + + // Restore the desired properties. + OutputObjects = PrevOutputObjects; + InstancedOutputs = PrevInstancedOutputs; + } + + // Copy any additional DuplicateTransient properties. + bHasEditableNodeBuilt = InInput->bHasEditableNodeBuilt; +} + +void +UHoudiniOutput::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) +{ + bCanDeleteHoudiniNodes = bInCanDeleteNodes; +} + +FString +UHoudiniOutput::OutputTypeToString(const EHoudiniOutputType& InOutputType) +{ + FString OutputTypeStr; + switch (InOutputType) + { + case EHoudiniOutputType::Mesh: + OutputTypeStr = TEXT("Mesh"); + break; + case EHoudiniOutputType::Instancer: + OutputTypeStr = TEXT("Instancer"); + break; + case EHoudiniOutputType::Landscape: + OutputTypeStr = TEXT("Landscape"); + break; + case EHoudiniOutputType::Curve: + OutputTypeStr = TEXT("Curve"); + break; + case EHoudiniOutputType::Skeletal: + OutputTypeStr = TEXT("Skeletal"); + break; + + default: + case EHoudiniOutputType::Invalid: + OutputTypeStr = TEXT("Invalid"); + break; + } + + return OutputTypeStr; +} + +void +UHoudiniOutput::MarkAsLoaded(const bool& InLoaded) +{ + // Mark all HGPO as loaded + for (auto& HGPO : HoudiniGeoPartObjects) + { + HGPO.bLoaded = InLoaded; + } + + // Mark all output object's identifier as loaded + for (auto& Iter : OutputObjects) + { + FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; + Identifier.bLoaded = InLoaded; + } + + // Instanced outputs + for (auto& Iter : InstancedOutputs) + { + FHoudiniOutputObjectIdentifier& Identifier = Iter.Key; + Identifier.bLoaded = InLoaded; + } +} + + +const bool +UHoudiniOutput::HasAnyProxy() const +{ + for (const auto& Pair : OutputObjects) + { + UObject* FoundProxy = Pair.Value.ProxyObject; + if (FoundProxy && !FoundProxy->IsPendingKill()) + { + return true; + } + } + + return false; +} + +const bool +UHoudiniOutput::HasProxy(const FHoudiniOutputObjectIdentifier& InIdentifier) const +{ + const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + if (!FoundOutputObject) + return false; + + UObject* FoundProxy = FoundOutputObject->ProxyObject; + if (!FoundProxy || FoundProxy->IsPendingKill()) + return false; + + return true; +} + +const bool +UHoudiniOutput::HasAnyCurrentProxy() const +{ + for (const auto& Pair : OutputObjects) + { + UObject* FoundProxy = Pair.Value.ProxyObject; + if (FoundProxy && !FoundProxy->IsPendingKill()) + { + if(Pair.Value.bProxyIsCurrent) + { + return true; + } + } + } + + return false; +} + +const bool +UHoudiniOutput::IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const +{ + if (!HasProxy(InIdentifier)) + return false; + + const FHoudiniOutputObject* FoundOutputObject = OutputObjects.Find(InIdentifier); + if (!FoundOutputObject) + return false; + + return FoundOutputObject->bProxyIsCurrent; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h index 2f57036dd..8e4dd84d5 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h @@ -1,641 +1,623 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "HoudiniGeoPartObject.h" -#include "LandscapeProxy.h" -#include "Misc/StringFormatArg.h" -#include "UObject/SoftObjectPtr.h" - -#include "HoudiniOutput.generated.h" - -class UMaterialInterface; -class ULandscapeLayerInfoObject; - -UENUM() -enum class EHoudiniOutputType : uint8 -{ - Invalid, - - Mesh, - Instancer, - Landscape, - Curve, - Skeletal -}; - -UENUM() -enum class EHoudiniCurveOutputType : uint8 -{ - UnrealSpline, - HoudiniSpline, -}; - -UENUM() -enum class EHoudiniLandscapeOutputBakeType : uint8 -{ - Detachment, - BakeToImage, - BakeToWorld, - InValid, -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniCurveOutputProperties -{ - GENERATED_USTRUCT_BODY() - - // Curve output properties - UPROPERTY() - EHoudiniCurveOutputType CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; - - UPROPERTY() - int32 NumPoints = -1; - - UPROPERTY() - bool bClosed = false; - - UPROPERTY() - EHoudiniCurveType CurveType = EHoudiniCurveType::Invalid; - - UPROPERTY() - EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::Invalid; -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniLandscapePtr : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - FORCEINLINE - void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; - - FORCEINLINE - TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; - - // Calling Get() during GC will raise an exception because Get calls StaticFindObject. - FORCEINLINE - ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; - - FORCEINLINE - FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; - - FORCEINLINE - void SetLandscapeOutputBakeType(const EHoudiniLandscapeOutputBakeType & InBakeType) { BakeType = InBakeType; }; - - FORCEINLINE - EHoudiniLandscapeOutputBakeType GetLandscapeOutputBakeType() const { return BakeType; }; - - UPROPERTY() - TSoftObjectPtr LandscapeSoftPtr; - - UPROPERTY() - EHoudiniLandscapeOutputBakeType BakeType; -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniLandscapeEditLayer : public UObject -{ - GENERATED_BODY() - -public: - FORCEINLINE - void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; - - FORCEINLINE - TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; - - // Calling Get() during GC will raise an exception because Get calls StaticFindObject. - FORCEINLINE - ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; - - FORCEINLINE - FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; - - UPROPERTY() - TSoftObjectPtr LandscapeSoftPtr; - - UPROPERTY() - FString LayerName; -}; - - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniOutputObjectIdentifier -{ - GENERATED_USTRUCT_BODY() - -public: - // Constructors - FHoudiniOutputObjectIdentifier(); - FHoudiniOutputObjectIdentifier(const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier); - - // Return hash value for this object, used when using this object as a key inside hashing containers. - uint32 GetTypeHash() const; - - // Comparison operator, used by hashing containers. - bool operator==(const FHoudiniOutputObjectIdentifier& HoudiniGeoPartObject) const; - - bool Matches(const FHoudiniGeoPartObject& HoudiniGeoPartObject) const; - -public: - - // NodeId of corresponding Houdini Object. - UPROPERTY() - int32 ObjectId = -1; - - // NodeId of corresponding Houdini Geo. - UPROPERTY() - int32 GeoId = -1; - - // PartId - UPROPERTY() - int32 PartId = -1; - - // String identifier for the split that created this - UPROPERTY() - FString SplitIdentifier = FString(); - - // Name of the part used to generate the output - UPROPERTY() - FString PartName = FString(); - - // First valid primitive index for this output - // (used to read generic attributes) - UPROPERTY() - int32 PrimitiveIndex = -1; - - // First valid point index for this output - // (used to read generic attributes) - UPROPERTY() - int32 PointIndex = -1; - - bool bLoaded = false; -}; - -/** Function used by hashing containers to create a unique hash for this type of object. **/ -HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier); - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObjectIdentifier -{ - GENERATED_USTRUCT_BODY() - -public: - // Constructors - FHoudiniBakedOutputObjectIdentifier(); - FHoudiniBakedOutputObjectIdentifier(const int32& InPartId, const FString& InSplitIdentifier); - FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); - - // Return hash value for this object, used when using this object as a key inside hashing containers. - uint32 GetTypeHash() const; - - // Comparison operator, used by hashing containers. - bool operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const; - -public: - - // PartId - UPROPERTY() - int32 PartId = -1; - - // String identifier for the split that created this - UPROPERTY() - FString SplitIdentifier = FString(); -}; - -/** Function used by hashing containers to create a unique hash for this type of object. **/ -HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier); - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput -{ - GENERATED_USTRUCT_BODY() - -public: - - void MarkChanged(const bool& InChanged) { bChanged = InChanged; }; - - void SetVariationObjectAt(const int32& AtIndex, UObject* InObject); - - bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - - float GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); - -#if WITH_EDITOR - void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; - bool IsUnformScaleLocked() const { return bUniformScaleLocked; }; -#endif - -public: - - // Original object used by the instancer. - UPROPERTY() - TSoftObjectPtr OriginalObject = nullptr; - - UPROPERTY() - int32 OriginalObjectIndex = -1; - - // Original HoudiniGeoPartObject used by the instancer - //UPROPERTY() - //FHoudiniGeoPartObject OriginalHGPO; - - // Original Instance transforms - UPROPERTY() - TArray OriginalTransforms; - - // Variation objects currently used for instancing - UPROPERTY() - TArray> VariationObjects; - - // Transform offsets, one for each variation. - UPROPERTY() - TArray VariationTransformOffsets; - - // Index of the variation used for each transform - UPROPERTY() - TArray TransformVariationIndices; - - // Original Indices of the variation instances - UPROPERTY() - TArray OriginalInstanceIndices; - - // Indicates this instanced output's component should be recreated - UPROPERTY() - bool bChanged = false; - - // Indicates this instanced output is stale and should be removed - UPROPERTY() - bool bStale = false; - - // Indicates if change the scale of Transfrom Offset of this object uniformly -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bUniformScaleLocked = false; -#endif - // TODO - // Color overrides?? -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject -{ - GENERATED_USTRUCT_BODY() - - public: - FHoudiniBakedOutputObject(); - - FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject=nullptr, UObject* InBakedComponent=nullptr); - - // Returns Actor if valid, otherwise nullptr - AActor* GetActorIfValid(bool bInTryLoad=true) const; - - // Returns BakedObject if valid, otherwise nullptr - UObject* GetBakedObjectIfValid(bool bInTryLoad=true) const; - - // Returns BakedComponent if valid, otherwise nullptr - UObject* GetBakedComponentIfValid(bool bInTryLoad=true) const; - - // Returns Blueprint if valid, otherwise nullptr - UBlueprint* GetBlueprintIfValid(bool bInTryLoad=true) const; - - // Returns the ULandscapeLayerInfoObject, if valid and found in LandscapeLayers, otherwise nullptr - ULandscapeLayerInfoObject* GetLandscapeLayerInfoIfValid(const FName& InLayerName, const bool bInTryLoad=true) const; - - // The actor that the baked output was associated with - UPROPERTY() - FString Actor; - - // The blueprint that baked output was associated with, if any - UPROPERTY() - FString Blueprint; - - // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. - UPROPERTY() - FName ActorBakeName = NAME_None; - - // The baked output asset - UPROPERTY() - FString BakedObject; - - // The baked output component - UPROPERTY() - FString BakedComponent; - - // In the case of instance actor component baking, this is the array of instanced actors - UPROPERTY() - TArray InstancedActors; - - // In the case of mesh split instancer baking: this is the array of instance components - UPROPERTY() - TArray InstancedComponents; - - // For landscapes this is the previously bake layer info assets (layer name as key, soft object path as value) - UPROPERTY() - TMap LandscapeLayers; -}; - -// Container to hold the map of baked objects. There should be one of -// these for each UHoudiniOutput. We manage this separately from UHoudiniOutput so -// that the "previous/last" bake objects can survive output reconstruction or PDG -// dirty/dirty all operations. -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutput -{ - GENERATED_USTRUCT_BODY() - - public: - UPROPERTY() - TMap BakedOutputObjects; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject -{ - GENERATED_USTRUCT_BODY() - - public: - - // The main output object - UPROPERTY() - UObject* OutputObject = nullptr; - - // The main output component - UPROPERTY() - UObject* OutputComponent = nullptr; - - // Proxy object - UPROPERTY() - UObject* ProxyObject = nullptr; - - // Proxy Component - UPROPERTY() - UObject* ProxyComponent = nullptr; - - // Mesh output properties - // If this is true the proxy mesh is "current", - // in other words, it is newer than the UStaticMesh - UPROPERTY() - bool bProxyIsCurrent = false; - - // Implicit output objects shouldn't be created as actors / components in the scene. - UPROPERTY() - bool bIsImplicit = false; - - // Bake Name override for this output object - UPROPERTY() - FString BakeName; - - UPROPERTY() - FHoudiniCurveOutputProperties CurveOutputProperty; - - - // NOTE: The idea behind CachedAttributes and CachedTokens is to - // collect attributes (such as unreal_level_path and unreal_output_name) - // and context-specific tokens (hda name, hda actor name, geo and part ids, tile_id, etc) - // and cache them directly on the output object. When the object gets baked, - // certain tokens can be updated specifically for the bake pass afterwhich the - // the string / path attributes can be resolved with the updated tokens. - // - // A more concrete example: - // unreal_output_name = "{hda_actor_name}_PurplePlants_{geo_id}_{part_id}" - // unreal_level_path = "{out}/{hda_name}/{guid}/PurplePlants/{geo_id}/{part_id}" - // - // All of the aforementions tokens and attributes would be cached on the output object - // when it is being cooked so that the same values are available at bake time. During - // a bake some tokens may be updated, such as `{out}` to change where assets get serialized. - - UPROPERTY() - TMap CachedAttributes; - - // Cache any tokens here that is needed for string resolving - // at bake time. - UPROPERTY() - TMap CachedTokens; -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject -{ - GENERATED_UCLASS_BODY() - - // Declare translators as friend so they can easily directly modify - // and access our HGPO and Output objects - friend struct FHoudiniMeshTranslator; - friend struct FHoudiniInstanceTranslator; - friend struct FHoudiniOutputTranslator; - - virtual ~UHoudiniOutput(); - -public: - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - const EHoudiniOutputType& GetType() const { return Type; }; - - const TArray& GetHoudiniGeoPartObjects() const { return HoudiniGeoPartObjects; }; - - // Returns true if we have a HGPO that matches - const bool HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const; - - // Returns true if the HGPO is fromn the same HF as us - const bool HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const; - - // Returns the output objects and their corresponding identifiers - TMap& GetOutputObjects() { return OutputObjects; }; - - // Returns the output objects and their corresponding identifiers - const TMap& GetOutputObjects() const { return OutputObjects; }; - - // Returns this output's assignement material map - TMap& GetAssignementMaterials() { return AssignementMaterials; }; - - // Returns this output's replacement material map - TMap& GetReplacementMaterials() { return ReplacementMaterials; }; - - // Returns the instanced outputs maps - TMap& GetInstancedOutputs() { return InstancedOutputs; }; - - const bool HasGeoChanged() const; - const bool HasTransformChanged() const; - const bool HasMaterialsChanged() const; - - // Returns true if there are any proxy objects in output (current or not) - const bool HasAnyProxy() const; - // Returns true if the specified identifier has a proxy object (current or not) - const bool HasProxy(const FHoudiniOutputObjectIdentifier &InIdentifier) const; - // Returns true if there are any current (most up to date and visible) proxy in the output - const bool HasAnyCurrentProxy() const; - // Returns true if the specified identifier's proxy is "current" (in other words, newer than - // the non-proxy and the proxy should thus be shown instead. - const bool IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const; - - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - void UpdateOutputType(); - - // Adds a new HoudiniGeoPartObject to our array - void AddNewHGPO(const FHoudiniGeoPartObject& InHGPO); - - // Mark all the current HGPO as stale (from a previous cook) - // So we can delte them all by calling DeleteAllStaleHGPOs after. - void MarkAllHGPOsAsStale(const bool& InStale); - - // Delete all the HGPO that were marked as stale - void DeleteAllStaleHGPOs(); - - void SetOutputObjects(const TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; - - void SetInstancedOutputs(const TMap& InInstancedOuput) { InstancedOutputs = InInstancedOuput; }; - - // Marks all HGPO and OutputIdentifier as loaded - void MarkAsLoaded(const bool& InLoaded); - - FORCEINLINE - const bool IsEditableNode() { return bIsEditableNode; }; - - FORCEINLINE - void SetIsEditableNode(bool IsEditable) { bIsEditableNode = IsEditable; } - - FORCEINLINE - const bool HasEditableNodeBuilt() { return bHasEditableNodeBuilt; }; - - FORCEINLINE - void SetHasEditableNodeBuilt(bool HasBuilt) { bHasEditableNodeBuilt = HasBuilt; } - - FORCEINLINE - void SetIsUpdating(bool bInIsUpdating) { bIsUpdating = bInIsUpdating; }; - - FORCEINLINE - bool IsUpdating() const { return bIsUpdating; }; - - FORCEINLINE - void SetLandscapeWorldComposition(const bool bInLandscapeWorldComposition) { bLandscapeWorldComposition = bInLandscapeWorldComposition; }; - - FORCEINLINE - bool IsLandscapeWorldComposition () const { return bLandscapeWorldComposition; }; - - FORCEINLINE - TArray & GetHoudiniCreatedSocketActors() { return HoudiniCreatedSocketActors; }; - - FORCEINLINE - TArray & GetHoudiniAttachedSocketActors() { return HoudiniAttachedSocketActors; } - - // Duplicate this object and copy its state to the resulting object. - // This is typically used to transfer state between between template and instance components. - UHoudiniOutput* DuplicateAndCopyProperties(UObject* DestOuter, FName NewName); - - // Copy properties but preserve output objects - virtual void CopyPropertiesFrom(UHoudiniOutput* InOutput, bool bCopyAllProperties); - - // Set whether this object can delete Houdini nodes. - void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); - bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } - - //------------------------------------------------------------------------------------------------ - // Helpers - //------------------------------------------------------------------------------------------------ - static FString OutputTypeToString(const EHoudiniOutputType& InOutputType); - - FBox GetBounds() const; - - void Clear(); - - bool ShouldDeferClear() const; - -protected: - - virtual void BeginDestroy() override; - -protected: - - // Indicates the type of output we're dealing with - UPROPERTY() - EHoudiniOutputType Type; - - // The output's corresponding HGPO - UPROPERTY() - TArray HoudiniGeoPartObjects; - - // - UPROPERTY(DuplicateTransient) - TMap OutputObjects; - - // Instanced outputs - // Stores the instance variations objects (replacement), transform offsets - UPROPERTY() - TMap InstancedOutputs; - - // The material assignments for this output - UPROPERTY() - TMap AssignementMaterials; - - // The material replacements for this output - UPROPERTY() - TMap ReplacementMaterials; - - // Indicates the number of stale HGPO - int32 StaleCount; - - UPROPERTY() - bool bLandscapeWorldComposition; - - // stores the created actors for sockets with actor references. - // - UPROPERTY() - TArray HoudiniCreatedSocketActors; - - UPROPERTY() - TArray HoudiniAttachedSocketActors; - -private: - // Use HoudiniOutput to represent an editable curve. - // This flag tells whether this output is an editable curve. - UPROPERTY() - bool bIsEditableNode; - - // An editable node is only built once. This flag indicates whether this node has been built. - // Transient, so resets every unreal session so curves must be rebuilt to work properly. - UPROPERTY(Transient, DuplicateTransient) - bool bHasEditableNodeBuilt; - - // The IsUpdating flag is set to true when this out exists and is being updated. - UPROPERTY() - bool bIsUpdating; - - UPROPERTY() - bool bCanDeleteHoudiniNodes; -}; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "HoudiniGeoPartObject.h" +#include "HoudiniEngineRuntimeCommon.h" + +#include "LandscapeProxy.h" +#include "Misc/StringFormatArg.h" +#include "UObject/SoftObjectPtr.h" + +#include "HoudiniOutput.generated.h" + +class UMaterialInterface; +class ULandscapeLayerInfoObject; + + +UENUM() +enum class EHoudiniCurveOutputType : uint8 +{ + UnrealSpline, + HoudiniSpline, +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniCurveOutputProperties +{ + GENERATED_USTRUCT_BODY() + + // Curve output properties + UPROPERTY() + EHoudiniCurveOutputType CurveOutputType = EHoudiniCurveOutputType::HoudiniSpline; + + UPROPERTY() + int32 NumPoints = -1; + + UPROPERTY() + bool bClosed = false; + + UPROPERTY() + EHoudiniCurveType CurveType = EHoudiniCurveType::Invalid; + + UPROPERTY() + EHoudiniCurveMethod CurveMethod = EHoudiniCurveMethod::Invalid; +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniLandscapePtr : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + FORCEINLINE + void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; + + FORCEINLINE + TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; + + // Calling Get() during GC will raise an exception because Get calls StaticFindObject. + FORCEINLINE + ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; + + FORCEINLINE + FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; + + FORCEINLINE + void SetLandscapeOutputBakeType(const EHoudiniLandscapeOutputBakeType & InBakeType) { BakeType = InBakeType; }; + + FORCEINLINE + EHoudiniLandscapeOutputBakeType GetLandscapeOutputBakeType() const { return BakeType; }; + + UPROPERTY() + TSoftObjectPtr LandscapeSoftPtr; + + UPROPERTY() + EHoudiniLandscapeOutputBakeType BakeType; +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniLandscapeEditLayer : public UObject +{ + GENERATED_BODY() + +public: + FORCEINLINE + void SetSoftPtr(TSoftObjectPtr InSoftPtr) { LandscapeSoftPtr = InSoftPtr; }; + + FORCEINLINE + TSoftObjectPtr GetSoftPtr() const { return LandscapeSoftPtr; }; + + // Calling Get() during GC will raise an exception because Get calls StaticFindObject. + FORCEINLINE + ALandscapeProxy* GetRawPtr() { return !IsGarbageCollecting() ? Cast(LandscapeSoftPtr.Get()) : nullptr; }; + + FORCEINLINE + FString GetSoftPtrPath() const { return LandscapeSoftPtr.ToSoftObjectPath().ToString(); }; + + UPROPERTY() + TSoftObjectPtr LandscapeSoftPtr; + + UPROPERTY() + FString LayerName; +}; + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniOutputObjectIdentifier +{ + GENERATED_USTRUCT_BODY() + +public: + // Constructors + FHoudiniOutputObjectIdentifier(); + FHoudiniOutputObjectIdentifier(const int32& InObjectId, const int32& InGeoId, const int32& InPartId, const FString& InSplitIdentifier); + + // Return hash value for this object, used when using this object as a key inside hashing containers. + uint32 GetTypeHash() const; + + // Comparison operator, used by hashing containers. + bool operator==(const FHoudiniOutputObjectIdentifier& HoudiniGeoPartObject) const; + + bool Matches(const FHoudiniGeoPartObject& HoudiniGeoPartObject) const; + +public: + + // NodeId of corresponding Houdini Object. + UPROPERTY() + int32 ObjectId = -1; + + // NodeId of corresponding Houdini Geo. + UPROPERTY() + int32 GeoId = -1; + + // PartId + UPROPERTY() + int32 PartId = -1; + + // String identifier for the split that created this + UPROPERTY() + FString SplitIdentifier = FString(); + + // Name of the part used to generate the output + UPROPERTY() + FString PartName = FString(); + + // First valid primitive index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PrimitiveIndex = -1; + + // First valid point index for this output + // (used to read generic attributes) + UPROPERTY() + int32 PointIndex = -1; + + bool bLoaded = false; +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniOutputObjectIdentifier& HoudiniOutputObjectIdentifier); + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObjectIdentifier +{ + GENERATED_USTRUCT_BODY() + +public: + // Constructors + FHoudiniBakedOutputObjectIdentifier(); + FHoudiniBakedOutputObjectIdentifier(const int32& InPartId, const FString& InSplitIdentifier); + FHoudiniBakedOutputObjectIdentifier(const FHoudiniOutputObjectIdentifier& InIdentifier); + + // Return hash value for this object, used when using this object as a key inside hashing containers. + uint32 GetTypeHash() const; + + // Comparison operator, used by hashing containers. + bool operator==(const FHoudiniBakedOutputObjectIdentifier& InIdentifier) const; + +public: + + // PartId + UPROPERTY() + int32 PartId = -1; + + // String identifier for the split that created this + UPROPERTY() + FString SplitIdentifier = FString(); +}; + +/** Function used by hashing containers to create a unique hash for this type of object. **/ +HOUDINIENGINERUNTIME_API uint32 GetTypeHash(const FHoudiniBakedOutputObjectIdentifier& InIdentifier); + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniInstancedOutput +{ + GENERATED_USTRUCT_BODY() + +public: + + void MarkChanged(const bool& InChanged) { bChanged = InChanged; }; + + void SetVariationObjectAt(const int32& AtIndex, UObject* InObject); + + bool SetTransformOffsetAt(const float& Value, const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + + float GetTransformOffsetAt(const int32& AtIndex, const int32& PosRotScaleIndex, const int32& XYZIndex); + +#if WITH_EDITOR + void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; + bool IsUnformScaleLocked() const { return bUniformScaleLocked; }; +#endif + +public: + + // Original object used by the instancer. + UPROPERTY() + TSoftObjectPtr OriginalObject = nullptr; + + UPROPERTY() + int32 OriginalObjectIndex = -1; + + // Original HoudiniGeoPartObject used by the instancer + //UPROPERTY() + //FHoudiniGeoPartObject OriginalHGPO; + + // Original Instance transforms + UPROPERTY() + TArray OriginalTransforms; + + // Variation objects currently used for instancing + UPROPERTY() + TArray> VariationObjects; + + // Transform offsets, one for each variation. + UPROPERTY() + TArray VariationTransformOffsets; + + // Index of the variation used for each transform + UPROPERTY() + TArray TransformVariationIndices; + + // Original Indices of the variation instances + UPROPERTY() + TArray OriginalInstanceIndices; + + // Indicates this instanced output's component should be recreated + UPROPERTY() + bool bChanged = false; + + // Indicates this instanced output is stale and should be removed + UPROPERTY() + bool bStale = false; + + // Indicates if change the scale of Transfrom Offset of this object uniformly +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bUniformScaleLocked = false; +#endif + // TODO + // Color overrides?? +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutputObject +{ + GENERATED_USTRUCT_BODY() + + public: + FHoudiniBakedOutputObject(); + + FHoudiniBakedOutputObject(AActor* InActor, FName InActorBakeName, UObject* InBakeObject=nullptr, UObject* InBakedComponent=nullptr); + + // Returns Actor if valid, otherwise nullptr + AActor* GetActorIfValid(bool bInTryLoad=true) const; + + // Returns BakedObject if valid, otherwise nullptr + UObject* GetBakedObjectIfValid(bool bInTryLoad=true) const; + + // Returns BakedComponent if valid, otherwise nullptr + UObject* GetBakedComponentIfValid(bool bInTryLoad=true) const; + + // Returns Blueprint if valid, otherwise nullptr + UBlueprint* GetBlueprintIfValid(bool bInTryLoad=true) const; + + // Returns the ULandscapeLayerInfoObject, if valid and found in LandscapeLayers, otherwise nullptr + ULandscapeLayerInfoObject* GetLandscapeLayerInfoIfValid(const FName& InLayerName, const bool bInTryLoad=true) const; + + // The actor that the baked output was associated with + UPROPERTY() + FString Actor; + + // The blueprint that baked output was associated with, if any + UPROPERTY() + FString Blueprint; + + // The intended bake actor name. The actor's actual name could have a numeric suffix for uniqueness. + UPROPERTY() + FName ActorBakeName = NAME_None; + + // The baked output asset + UPROPERTY() + FString BakedObject; + + // The baked output component + UPROPERTY() + FString BakedComponent; + + // In the case of instance actor component baking, this is the array of instanced actors + UPROPERTY() + TArray InstancedActors; + + // In the case of mesh split instancer baking: this is the array of instance components + UPROPERTY() + TArray InstancedComponents; + + // For landscapes this is the previously bake layer info assets (layer name as key, soft object path as value) + UPROPERTY() + TMap LandscapeLayers; +}; + +// Container to hold the map of baked objects. There should be one of +// these for each UHoudiniOutput. We manage this separately from UHoudiniOutput so +// that the "previous/last" bake objects can survive output reconstruction or PDG +// dirty/dirty all operations. +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniBakedOutput +{ + GENERATED_USTRUCT_BODY() + + public: + UPROPERTY() + TMap BakedOutputObjects; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniOutputObject +{ + GENERATED_USTRUCT_BODY() + + public: + + // The main output object + UPROPERTY() + UObject* OutputObject = nullptr; + + // The main output component + UPROPERTY() + UObject* OutputComponent = nullptr; + + // Proxy object + UPROPERTY() + UObject* ProxyObject = nullptr; + + // Proxy Component + UPROPERTY() + UObject* ProxyComponent = nullptr; + + // Mesh output properties + // If this is true the proxy mesh is "current", + // in other words, it is newer than the UStaticMesh + UPROPERTY() + bool bProxyIsCurrent = false; + + // Implicit output objects shouldn't be created as actors / components in the scene. + UPROPERTY() + bool bIsImplicit = false; + + // Bake Name override for this output object + UPROPERTY() + FString BakeName; + + UPROPERTY() + FHoudiniCurveOutputProperties CurveOutputProperty; + + + // NOTE: The idea behind CachedAttributes and CachedTokens is to + // collect attributes (such as unreal_level_path and unreal_output_name) + // and context-specific tokens (hda name, hda actor name, geo and part ids, tile_id, etc) + // and cache them directly on the output object. When the object gets baked, + // certain tokens can be updated specifically for the bake pass afterwhich the + // the string / path attributes can be resolved with the updated tokens. + // + // A more concrete example: + // unreal_output_name = "{hda_actor_name}_PurplePlants_{geo_id}_{part_id}" + // unreal_level_path = "{out}/{hda_name}/{guid}/PurplePlants/{geo_id}/{part_id}" + // + // All of the aforementions tokens and attributes would be cached on the output object + // when it is being cooked so that the same values are available at bake time. During + // a bake some tokens may be updated, such as `{out}` to change where assets get serialized. + + UPROPERTY() + TMap CachedAttributes; + + // Cache any tokens here that is needed for string resolving + // at bake time. + UPROPERTY() + TMap CachedTokens; +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniOutput : public UObject +{ + GENERATED_UCLASS_BODY() + + // Declare translators as friend so they can easily directly modify + // and access our HGPO and Output objects + friend struct FHoudiniMeshTranslator; + friend struct FHoudiniInstanceTranslator; + friend struct FHoudiniOutputTranslator; + + virtual ~UHoudiniOutput(); + +public: + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + const EHoudiniOutputType& GetType() const { return Type; }; + + const TArray& GetHoudiniGeoPartObjects() const { return HoudiniGeoPartObjects; }; + + // Returns true if we have a HGPO that matches + const bool HasHoudiniGeoPartObject(const FHoudiniGeoPartObject& InHGPO) const; + + // Returns true if the HGPO is fromn the same HF as us + const bool HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool& bVolumeNameShouldMatch) const; + + // Returns the output objects and their corresponding identifiers + TMap& GetOutputObjects() { return OutputObjects; }; + + // Returns the output objects and their corresponding identifiers + const TMap& GetOutputObjects() const { return OutputObjects; }; + + // Returns this output's assignement material map + TMap& GetAssignementMaterials() { return AssignementMaterials; }; + + // Returns this output's replacement material map + TMap& GetReplacementMaterials() { return ReplacementMaterials; }; + + // Returns the instanced outputs maps + TMap& GetInstancedOutputs() { return InstancedOutputs; }; + + const bool HasGeoChanged() const; + const bool HasTransformChanged() const; + const bool HasMaterialsChanged() const; + + // Returns true if there are any proxy objects in output (current or not) + const bool HasAnyProxy() const; + // Returns true if the specified identifier has a proxy object (current or not) + const bool HasProxy(const FHoudiniOutputObjectIdentifier &InIdentifier) const; + // Returns true if there are any current (most up to date and visible) proxy in the output + const bool HasAnyCurrentProxy() const; + // Returns true if the specified identifier's proxy is "current" (in other words, newer than + // the non-proxy and the proxy should thus be shown instead. + const bool IsProxyCurrent(const FHoudiniOutputObjectIdentifier &InIdentifier) const; + + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + void UpdateOutputType(); + + // Adds a new HoudiniGeoPartObject to our array + void AddNewHGPO(const FHoudiniGeoPartObject& InHGPO); + + // Mark all the current HGPO as stale (from a previous cook) + // So we can delte them all by calling DeleteAllStaleHGPOs after. + void MarkAllHGPOsAsStale(const bool& InStale); + + // Delete all the HGPO that were marked as stale + void DeleteAllStaleHGPOs(); + + void SetOutputObjects(const TMap& InOutputObjects) { OutputObjects = InOutputObjects; }; + + void SetInstancedOutputs(const TMap& InInstancedOuput) { InstancedOutputs = InInstancedOuput; }; + + // Marks all HGPO and OutputIdentifier as loaded + void MarkAsLoaded(const bool& InLoaded); + + FORCEINLINE + const bool IsEditableNode() { return bIsEditableNode; }; + + FORCEINLINE + void SetIsEditableNode(bool IsEditable) { bIsEditableNode = IsEditable; } + + FORCEINLINE + const bool HasEditableNodeBuilt() { return bHasEditableNodeBuilt; }; + + FORCEINLINE + void SetHasEditableNodeBuilt(bool HasBuilt) { bHasEditableNodeBuilt = HasBuilt; } + + FORCEINLINE + void SetIsUpdating(bool bInIsUpdating) { bIsUpdating = bInIsUpdating; }; + + FORCEINLINE + bool IsUpdating() const { return bIsUpdating; }; + + FORCEINLINE + void SetLandscapeWorldComposition(const bool bInLandscapeWorldComposition) { bLandscapeWorldComposition = bInLandscapeWorldComposition; }; + + FORCEINLINE + bool IsLandscapeWorldComposition () const { return bLandscapeWorldComposition; }; + + FORCEINLINE + TArray & GetHoudiniCreatedSocketActors() { return HoudiniCreatedSocketActors; }; + + FORCEINLINE + TArray & GetHoudiniAttachedSocketActors() { return HoudiniAttachedSocketActors; } + + // Duplicate this object and copy its state to the resulting object. + // This is typically used to transfer state between between template and instance components. + UHoudiniOutput* DuplicateAndCopyProperties(UObject* DestOuter, FName NewName); + + // Copy properties but preserve output objects + virtual void CopyPropertiesFrom(UHoudiniOutput* InOutput, bool bCopyAllProperties); + + // Set whether this object can delete Houdini nodes. + void SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes); + bool CanDeleteHoudiniNodes() const { return bCanDeleteHoudiniNodes; } + + //------------------------------------------------------------------------------------------------ + // Helpers + //------------------------------------------------------------------------------------------------ + static FString OutputTypeToString(const EHoudiniOutputType& InOutputType); + + FBox GetBounds() const; + + void Clear(); + + bool ShouldDeferClear() const; + +protected: + + virtual void BeginDestroy() override; + +protected: + + // Indicates the type of output we're dealing with + UPROPERTY() + EHoudiniOutputType Type; + + // The output's corresponding HGPO + UPROPERTY() + TArray HoudiniGeoPartObjects; + + // + UPROPERTY(DuplicateTransient) + TMap OutputObjects; + + // Instanced outputs + // Stores the instance variations objects (replacement), transform offsets + UPROPERTY() + TMap InstancedOutputs; + + // The material assignments for this output + UPROPERTY() + TMap AssignementMaterials; + + // The material replacements for this output + UPROPERTY() + TMap ReplacementMaterials; + + // Indicates the number of stale HGPO + int32 StaleCount; + + UPROPERTY() + bool bLandscapeWorldComposition; + + // stores the created actors for sockets with actor references. + // + UPROPERTY() + TArray HoudiniCreatedSocketActors; + + UPROPERTY() + TArray HoudiniAttachedSocketActors; + +private: + // Use HoudiniOutput to represent an editable curve. + // This flag tells whether this output is an editable curve. + UPROPERTY() + bool bIsEditableNode; + + // An editable node is only built once. This flag indicates whether this node has been built. + // Transient, so resets every unreal session so curves must be rebuilt to work properly. + UPROPERTY(Transient, DuplicateTransient) + bool bHasEditableNodeBuilt; + + // The IsUpdating flag is set to true when this out exists and is being updated. + UPROPERTY() + bool bIsUpdating; + + UPROPERTY() + bool bCanDeleteHoudiniNodes; +}; + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h index c22fdd6c8..6203c90b8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h @@ -1,909 +1,891 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -//#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniTranslatorTypes.h" - -#include "HoudiniPDGAssetLink.generated.h" - -struct FHoudiniPackageParams; - -UENUM() -enum class EPDGLinkState : uint8 -{ - Inactive, - Linking, - Linked, - Error_Not_Linked -}; - - -UENUM() -enum class EPDGNodeState : uint8 -{ - None, - Dirtied, - Dirtying, - Cooking, - Cook_Complete, - Cook_Failed -}; - -UENUM() -enum class EPDGWorkResultState : uint8 -{ - None, - ToLoad, - Loading, - Loaded, - ToDelete, - Deleting, - Deleted, - NotLoaded -}; - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EPDGBakeSelectionOption : uint8 -{ - All, - SelectedNetwork, - SelectedNode -}; -#endif - -#if WITH_EDITORONLY_DATA -UENUM() -enum class EPDGBakePackageReplaceModeOption : uint8 -{ - CreateNewAssets, - ReplaceExistingAssets -}; -#endif - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FOutputActorOwner -{ - GENERATED_BODY(); -public: - FOutputActorOwner() - : OutputActor(nullptr) {}; - - virtual ~FOutputActorOwner() {}; - - // Create OutputActor, an actor to hold work result output - virtual bool CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName); - - // Return OutputActor - virtual AActor* GetOutputActor() const { return OutputActor; } - - // Setter for OutputActor - virtual void SetOutputActor(AActor* InActor) { OutputActor = InActor; } - - // Destroy OutputActor if it is valid. - virtual bool DestroyOutputActor(); - -private: - UPROPERTY(NonTransactional) - AActor* OutputActor; - -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FTOPWorkResultObject(); - - // Call DestroyResultObjects in the destructor - virtual ~FTOPWorkResultObject(); - - // Set ResultObjects to a copy of InUpdatedOutputs - void SetResultOutputs(const TArray& InUpdatedOutputs) { ResultOutputs = InUpdatedOutputs; } - - // Getter for ResultOutputs - TArray& GetResultOutputs() { return ResultOutputs; } - - // Getter for ResultOutputs - const TArray& GetResultOutputs() const { return ResultOutputs; } - - // Destroy ResultOutputs - void DestroyResultOutputs(); - - // Get the OutputActor owner struct - FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } - - // Get the OutputActor owner struct - const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } - - // Destroy the ResultOutputs and remove the output actor. - void DestroyResultOutputsAndRemoveOutputActor(); - - // Getter for bAutoBakedSinceLastLoad: indicates if this work result object has been auto-baked since it's last load. - bool AutoBakedSinceLastLoad() const { return bAutoBakedSinceLastLoad; } - // Setter for bAutoBakedSinceLastLoad - void SetAutoBakedSinceLastLoad(bool bInAutoBakedSinceLastLoad) { bAutoBakedSinceLastLoad = bInAutoBakedSinceLastLoad; } - -public: - - UPROPERTY(NonTransactional) - FString Name; - UPROPERTY(NonTransactional) - FString FilePath; - UPROPERTY(NonTransactional) - EPDGWorkResultState State; - // The index in the WorkItemResultInfo array of this item as it was received from HAPI. - UPROPERTY(NonTransactional) - int32 WorkItemResultInfoIndex; - -protected: - // UPROPERTY() - // TArray ResultObjects; - - UPROPERTY(NonTransactional) - TArray ResultOutputs; - - // If true, indicates that the work result object has been auto-baked since it was last loaded. - UPROPERTY(NonTransactional) - bool bAutoBakedSinceLastLoad = false; - -private: - // List of objects to delete, internal use only (DestroyResultOutputs) - UPROPERTY(NonTransactional) - TArray OutputObjectsToDelete; - - UPROPERTY(NonTransactional) - FOutputActorOwner OutputActorOwner; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FTOPWorkResult -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FTOPWorkResult(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const FTOPWorkResult& OtherWorkResult) const; - - // Calls FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor on each entry in ResultObjects and clears the array. - void ClearAndDestroyResultObjects(); - - // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. - int32 IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); - // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. - FTOPWorkResultObject* GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); - // Return the FTOPWorkResultObject at InArrayIndex in the ResultObjects array, or nullptr if InArrayIndex is not a valid index. - FTOPWorkResultObject* GetWorkResultObjectByArrayIndex(const int32& InArrayIndex); - -public: - - UPROPERTY(NonTransactional) - int32 WorkItemIndex; - UPROPERTY(Transient) - int32 WorkItemID; - - UPROPERTY(NonTransactional) - TArray ResultObjects; - - /* - UPROPERTY() - TArray ResultObjects; - - UPROPERTY() - TArray ResultNames; - UPROPERTY() - TArray ResultFilePaths; - UPROPERTY() - TArray ResultStates; - */ -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FWorkItemTallyBase -{ - GENERATED_USTRUCT_BODY() - -public: - virtual ~FWorkItemTallyBase(); - - // - // Mutators - // - - // Zero all counts, including total. - virtual void ZeroAll() {}; - - // - // Accessors - // - - bool AreAllWorkItemsComplete() const; - bool AnyWorkItemsFailed() const; - bool AnyWorkItemsPending() const; - - virtual int32 NumWorkItems() const { return 0; }; - virtual int32 NumWaitingWorkItems() const { return 0; }; - virtual int32 NumScheduledWorkItems() const { return 0; }; - virtual int32 NumCookingWorkItems() const { return 0; }; - virtual int32 NumCookedWorkItems() const { return 0; }; - virtual int32 NumErroredWorkItems() const { return 0; }; - virtual int32 NumCookCancelledWorkItems() const { return 0; }; - - FString ProgressRatio() const; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FWorkItemTally : public FWorkItemTallyBase -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FWorkItemTally(); - - // - // Mutators - // - - // Empty all state sets, as well as AllWorkItems. - virtual void ZeroAll() override; - - // Remove a work item from all state sets and AllWorkItems. - void RemoveWorkItem(int32 InWorkItemID); - - void RecordWorkItemAsWaiting(int32 InWorkItemID); - void RecordWorkItemAsScheduled(int32 InWorkItemID); - void RecordWorkItemAsCooking(int32 InWorkItemID); - void RecordWorkItemAsCooked(int32 InWorkItemID); - void RecordWorkItemAsErrored(int32 InWorkItemID); - void RecordWorkItemAsCookCancelled(int32 InWorkItemID); - - // - // Accessors - // - - virtual int32 NumWorkItems() const override { return AllWorkItems.Num(); } - virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems.Num(); } - virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems.Num(); } - virtual int32 NumCookingWorkItems() const override { return CookingWorkItems.Num(); } - virtual int32 NumCookedWorkItems() const override { return CookedWorkItems.Num(); } - virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems.Num(); } - virtual int32 NumCookCancelledWorkItems() const override { return CookCancelledWorkItems.Num(); } - -protected: - - // Removes the work item id from all state sets (but not from AllWorkItems -- use RemoveWorkItem for that). - void RemoveWorkItemFromAllStateSets(int32 InWorkItemID); - - // We use sets to keep track of in what state a work item is. The set stores the WorkItemID. - - UPROPERTY() - TSet AllWorkItems; - UPROPERTY() - TSet WaitingWorkItems; - UPROPERTY() - TSet ScheduledWorkItems; - UPROPERTY() - TSet CookingWorkItems; - UPROPERTY() - TSet CookedWorkItems; - UPROPERTY() - TSet ErroredWorkItems; - UPROPERTY() - TSet CookCancelledWorkItems; -}; - -USTRUCT() -struct HOUDINIENGINERUNTIME_API FAggregatedWorkItemTally : public FWorkItemTallyBase -{ - GENERATED_USTRUCT_BODY() - -public: - - // Constructor - FAggregatedWorkItemTally(); - - virtual void ZeroAll() override; - - void Add(const FWorkItemTallyBase& InWorkItemTally); - - void Subtract(const FWorkItemTallyBase& InWorkItemTally); - - virtual int32 NumWorkItems() const override { return TotalWorkItems; } - virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems; } - virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems; } - virtual int32 NumCookingWorkItems() const override { return CookingWorkItems; } - virtual int32 NumCookedWorkItems() const override { return CookedWorkItems; } - virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems; } - -protected: - UPROPERTY() - int32 TotalWorkItems; - UPROPERTY() - int32 WaitingWorkItems; - UPROPERTY() - int32 ScheduledWorkItems; - UPROPERTY() - int32 CookingWorkItems; - UPROPERTY() - int32 CookedWorkItems; - UPROPERTY() - int32 ErroredWorkItems; - UPROPERTY() - int32 CookCancelledWorkItems; - -}; - -// Container for baked outputs of a PDG work result object. -USTRUCT() -struct HOUDINIENGINERUNTIME_API FHoudiniPDGWorkResultObjectBakedOutput -{ - GENERATED_BODY() - - public: - // Array of baked output per output index of the work result object's outputs. - UPROPERTY() - TArray BakedOutputs; -}; - -// Forward declare the UTOPNetwork here for some references in the UTOPNode -class UTOPNetwork; - -UCLASS() -class HOUDINIENGINERUNTIME_API UTOPNode : public UObject -{ - GENERATED_BODY() - -public: - // Constructor - UTOPNode(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const UTOPNode& Other) const; - - void Reset(); - - const FWorkItemTallyBase& GetWorkItemTally() const - { - if (bHasChildNodes) - return AggregatedWorkItemTally; - return WorkItemTally; - } - - void AggregateTallyFromChildNode(const UTOPNode* InChildNode) - { - if (IsValid(InChildNode)) - AggregatedWorkItemTally.Add(InChildNode->GetWorkItemTally()); - } - - bool AreAllWorkItemsComplete() const { return GetWorkItemTally().AreAllWorkItemsComplete(); }; - bool AnyWorkItemsFailed() const { return GetWorkItemTally().AnyWorkItemsFailed(); }; - bool AnyWorkItemsPending() const { return GetWorkItemTally().AnyWorkItemsPending(); }; - void ZeroWorkItemTally() - { - WorkItemTally.ZeroAll(); - AggregatedWorkItemTally.ZeroAll(); - } - - // Called by PDG manager when work item events are received - - // Notification that a work item has been created - void OnWorkItemCreated(int32 InWorkItemID) { }; - - // Notification that a work item has been removed. - void OnWorkItemRemoved(int32 InWorkItemID) { WorkItemTally.RemoveWorkItem(InWorkItemID); }; - - // Notification that a work item has moved to the waiting state. - void OnWorkItemWaiting(int32 InWorkItemID); - - // Notification that a work item has been scheduled. - void OnWorkItemScheduled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsScheduled(InWorkItemID); }; - - // Notification that a work item has started cooking. - void OnWorkItemCooking(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCooking(InWorkItemID); }; - - // Notification that a work item has been cooked. - void OnWorkItemCooked(int32 InWorkItemID); - - // Notification that a work item has errored. - void OnWorkItemErrored(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsErrored(InWorkItemID); }; - - // Notification that a work item cook has been cancelled. - void OnWorkItemCookCancelled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCookCancelled(InWorkItemID); }; - - bool IsVisibleInLevel() const { return bShow; } - void SetVisibleInLevel(bool bInVisible); - void UpdateOutputVisibilityInLevel(); - - // Sets all WorkResultObjects that are in the NotLoaded state to ToLoad. - void SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad=false); - - // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output - // actors). - void SetLoadedWorkResultsToDelete(); - - // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output - // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); - - // Get the OutputActor owner struct - FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } - - // Get the OutputActor owner struct - const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } - - // Get the baked outputs from the last bake. The map keys are [work_result.work_item_index]_[work_result_object_index] - TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } - const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } - // Helper to construct the key used to look up baked work results. - static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex); - // Helper to construct the key used to look up baked work results. - static FString GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex); - // Helper to construct the key used to look up baked work results. - bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const; - // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object. - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); - // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object (const version). - bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; - - // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. - int32 IndexOfWorkResultByID(const int32& InWorkItemID); - // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. - FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID); - // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. - // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. - int32 IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); - // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. - // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. - FTOPWorkResult* GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); - // Return the FTOPWorkResult at InArrayIndex in the WorkResult array, or nullptr if InArrayIndex is not a valid index. - FTOPWorkResult* GetWorkResultByArrayIndex(const int32& InArrayIndex); - - // Returns true if InNetwork is the parent TOP Net of this node. - bool IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const; - - // Returns true if this node can still be auto-baked - bool CanStillBeAutoBaked() const; - -#if WITH_EDITOR - void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; -#endif - -#if WITH_EDITOR - void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; -#endif - -public: - - UPROPERTY(Transient, NonTransactional) - int32 NodeId; - UPROPERTY(NonTransactional) - FString NodeName; - UPROPERTY(NonTransactional) - FString NodePath; - UPROPERTY(NonTransactional) - FString ParentName; - - UPROPERTY() - UObject* WorkResultParent; - UPROPERTY(NonTransactional) - TArray WorkResult; - - // Hidden in the nodes combobox - UPROPERTY() - bool bHidden; - UPROPERTY() - bool bAutoLoad; - - UPROPERTY(Transient, NonTransactional) - EPDGNodeState NodeState; - - // This is set when the TOP node's work items are processed by - // FHoudiniPDGManager based on if any NotLoaded work result objects are found - UPROPERTY(NonTransactional) - bool bCachedHaveNotLoadedWorkResults; - - // This is set when the TOP node's work items are processed by - // FHoudiniPDGManager based on if any Loaded work result objects are found - UPROPERTY(NonTransactional) - bool bCachedHaveLoadedWorkResults; - - // True if this node has child nodes - UPROPERTY(NonTransactional) - bool bHasChildNodes; - - // These notification events have been introduced so that we can start encapsulating code. - // in this class as opposed to modifying this object in various places throughout the codebase. - - // Notification that this TOP node has been dirtied. - void OnDirtyNode(); - - // Accessors for the landscape data caches - FHoudiniLandscapeExtent& GetLandscapeExtent() { return LandscapeExtent; } - FHoudiniLandscapeReferenceLocation& GetLandscapeReferenceLocation() { return LandscapeReferenceLocation; } - FHoudiniLandscapeTileSizeInfo& GetLandscapeSizeInfo() { return LandscapeSizeInfo; } - // More cached landscape data - UPROPERTY() - TSet ClearedLandscapeLayers; - - // Returns true if the node has received the HAPI_PDG_EVENT_COOK_COMPLETE event since the last the cook started - bool HasReceivedCookCompleteEvent() const { return bHasReceivedCookCompleteEvent; } - // Handler for when the node receives the HAPI_PDG_EVENT_COOK_START (called for each node when a TOPNet starts cooking) - void HandleOnPDGEventCookStart() { bHasReceivedCookCompleteEvent = false; } - // Handler for when the node receives the HAPI_PDG_EVENT_COOK_COMPLETE event (called for each node when a TOPNet completes cooking) - void HandleOnPDGEventCookComplete() { bHasReceivedCookCompleteEvent = true; } - -protected: - void InvalidateLandscapeCache(); - - // Value caches used during landscape tile creation. - FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; - FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; - FHoudiniLandscapeExtent LandscapeExtent; - - // Visible in the level - UPROPERTY() - bool bShow; - - // Map of [work_result_index]_[work_result_object_index] to the work result object's baked outputs. - UPROPERTY() - TMap BakedWorkResultObjectOutputs; - - // This node's own work items, used when bHasChildNodes is false. - UPROPERTY(Transient, NonTransactional) - FWorkItemTally WorkItemTally; - // This node's aggregated work item tallys (sum of child work item tally, use when bHasChildNodes is true) - UPROPERTY(Transient, NonTransactional) - FAggregatedWorkItemTally AggregatedWorkItemTally; - - // Set to true when the node recieves HAPI_PDG_EVENT_COOK_COMPLETE event - UPROPERTY(Transient, NonTransactional) - bool bHasReceivedCookCompleteEvent; - -private: - UPROPERTY() - FOutputActorOwner OutputActorOwner; -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject -{ - GENERATED_BODY() - -public: - - // Delegate that is broadcast when cook of the network is complete. Parameters are the UTOPNetwork and bAnyFailedWorkItems. - DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostCookDelegate, UTOPNetwork*, const bool); - - // Constructor - UTOPNetwork(); - - // Comparison operator, used by hashing containers and arrays. - bool operator==(const UTOPNetwork& Other) const; - - // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output - // actors). - void SetLoadedWorkResultsToDelete(); - - // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output - // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); - - // Returns true if any node in this TOP net has pending (waiting, scheduled, cooking) work items. - bool AnyWorkItemsPending() const; - - // Returns true if any node in this TOP net has failed/errored work items. - bool AnyWorkItemsFailed() const; - - // Returns true if this network has nodes that can still be auto-baked - bool CanStillBeAutoBaked() const; - - // Handler for when a node in the newtork receives the HAPI_PDG_EVENT_COOK_COMPLETE event (called for each node when a TOPNet completes cooking) - void HandleOnPDGEventCookCompleteReceivedByChildNode(UHoudiniPDGAssetLink* const InAssetLink, UTOPNode* const InTOPNode); - - FOnPostCookDelegate& GetOnPostCookDelegate() { return OnPostCookDelegate; } - -public: - - UPROPERTY(Transient, NonTransactional) - int32 NodeId; - UPROPERTY(NonTransactional) - FString NodeName; - // HAPI path to this node (relative to the HDA) - UPROPERTY(NonTransactional) - FString NodePath; - - UPROPERTY() - TArray AllTOPNodes; - - // TODO: Should be using SelectedNodeName instead? - // Index is not consistent after updating filter - UPROPERTY() - int32 SelectedTOPIndex; - - UPROPERTY(NonTransactional) - FString ParentName; - - UPROPERTY() - bool bShowResults; - UPROPERTY() - bool bAutoLoadResults; - - FOnPostCookDelegate OnPostCookDelegate; -}; - - -class UHoudiniPDGAssetLink; -DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemHAPIIndex*/, int32 /*WorkItemResultInfoIndex*/); - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject -{ - GENERATED_UCLASS_BODY() - -public: - - friend class UHoudiniAssetComponent; - - // Delegate for when the entire bake operation is complete (all selected nodes/networks have been baked). - DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostBakeDelegate, UHoudiniPDGAssetLink*, const bool); - // Delegate for when a network completes a cook. Passes the asset link, the network, a bAnyWorkItemsFailed. - DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnPostTOPNetworkCookDelegate, UHoudiniPDGAssetLink*, UTOPNetwork*, const bool); - - static FString GetAssetLinkStatus(const EPDGLinkState& InLinkState); - static FString GetTOPNodeStatus(const UTOPNode* InTOPNode); - static FLinearColor GetTOPNodeStatusColor(const UTOPNode* InTOPNode); - - void UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork); - void UpdateWorkItemTally(); - static void ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork); - - // Set the TOP network at the given index as currently selected TOP network - void SelectTOPNetwork(const int32& AtIndex); - // Set the TOP node at the given index in the given TOP network as currently selected TOP node - void SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex); - - UTOPNode* GetSelectedTOPNode(); - const UTOPNode* GetSelectedTOPNode() const; - UTOPNetwork* GetSelectedTOPNetwork(); - const UTOPNetwork* GetSelectedTOPNetwork() const; - - FString GetSelectedTOPNodeName(); - FString GetSelectedTOPNetworkName(); - - UTOPNode* GetTOPNode(const int32& InNodeID); - bool GetTOPNodeAndNetworkByNodeId(const int32& InNodeID, UTOPNetwork*& OutNetwork, UTOPNode*& OutNode); - UTOPNetwork* GetTOPNetwork(const int32& AtIndex); - const UTOPNetwork* GetTOPNetwork(const int32& AtIndex) const; - - // Find the node with relative path 'InNodePath' from its topnet. - static UTOPNode* GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex); - // Find the network with relative path 'InNetPath' from the HDA - static UTOPNetwork* GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex); - - // Get the parent TOP node of the specified node. This is resolved - UTOPNode* GetParentTOPNode(const UTOPNode* InNode); - - static void ClearTOPNodeWorkItemResults(UTOPNode* TOPNode); - static void ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork); - // Clear the result objects of a work item (FTOPWorkResult.ResultObjects), but don't delete the work item from - // TOPNode.WorkResults (for example, the work item was dirtied but not removed from PDG) - static void ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - // Calls ClearWorkItemResultByID and then deletes the FTOPWorkResult from InTOPNode.Result as well. For example: - // the work item was removed in PDG. - static void DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - static FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); - - // This should be called after the owner and this PDG asset link is duplicated. Set all output parent actors to - // null in all TOP networks/nodes. Since the TOP Networks/TOP nodes are all structs, we cannot set - // DuplicateTransient property on their OutputActor properties. - void UpdatePostDuplicate(); - - // Load the geometry generated as results of the given work item, of the given TOP node. - // The load will be done asynchronously. - // Results must be tagged with 'file', and must have a file path, otherwise will not be loaded. - //void LoadResults(FTOPNode TOPNode, HAPI_PDG_WorkitemInfo workItemInfo, HAPI_PDG_WorkitemResultInfo[] resultInfos, HAPI_PDG_WorkitemId workItemID) - - // Return the first UHoudiniAssetComponent in the parent chain. If this asset link is not - // owned by a HoudiniAssetComponent, a nullptr will be returned. - UHoudiniAssetComponent* GetOuterHoudiniAssetComponent() const; - - // Gets the temporary cook folder. If the parent of this asset link is a HoudiniAssetComponent use that, otherwise - // use the default static mesh temporary cook folder. - FDirectoryPath GetTemporaryCookFolder() const; - - // Get the actor that owns this PDG asset link. If the asset link is owned by a component, - // then the component's owning actor is returned. Can return null if this is now owned by - // an actor or component. - AActor* GetOwnerActor() const; - - // Checks if the asset link has any temporary outputs and returns true if it has - bool HasTemporaryOutputs() const; - - // Filter TOP nodes and outputs (hidden/visible) by TOPNodeFilter and TOPOutputFilter. - void FilterTOPNodesAndOutputs(); - - // On all FTOPNodes: Load not loaded items if bAutoload is true, and update the level visibility of work items - // result. Used when FTOPNode.bShow and/or FTOPNode.bAutoload changed. - void UpdateTOPNodeAutoloadAndVisibility(); - -#if WITH_EDITORONLY_DATA - // Returns true if there are any nodes left that can/must still be auto-baked. - bool AnyRemainingAutoBakeNodes() const; -#endif - - // Delegate handlers - - // Get the post bake delegate - FOnPostBakeDelegate& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } - - // Called by baking code after baking all of the outputs - void HandleOnPostBake(const bool bInSuccess); - - FOnPostTOPNetworkCookDelegate& GetOnPostTOPNetworkCookDelegate() { return OnPostTOPNetworkCookDelegate; } - - // Handler for when a TOP network completes a cook. Called by the TOP Net once all of its nodes have received - // HAPI_PDG_EVENT_COOK_COMPLETE. - void HandleOnTOPNetworkCookComplete(UTOPNetwork* const InTOPNet); - -#if WITH_EDITORONLY_DATA - void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; -#endif - -#if WITH_EDITORONLY_DATA - void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; -#endif - -private: - - void ClearAllTOPData(); - - static void DestroyWorkItemResultData(FTOPWorkResult& Result); - - static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); - -public: - - //UPROPERTY() - //UHoudiniAsset* HoudiniAsset; - - //UPROPERTY() - //UHoudiniAssetComponent* ParentHAC; - - UPROPERTY(DuplicateTransient, NonTransactional) - FString AssetName; - - // The full path to the HDA in HAPI - UPROPERTY(DuplicateTransient, NonTransactional) - FString AssetNodePath; - - UPROPERTY(DuplicateTransient, NonTransactional) - int32 AssetID; - - UPROPERTY() - TArray AllTOPNetworks; - - UPROPERTY() - int32 SelectedTOPNetworkIndex; - - UPROPERTY(Transient, NonTransactional) - EPDGLinkState LinkState; - - UPROPERTY() - bool bAutoCook; - UPROPERTY() - bool bUseTOPNodeFilter; - UPROPERTY() - bool bUseTOPOutputFilter; - UPROPERTY() - FString TOPNodeFilter; - UPROPERTY() - FString TOPOutputFilter; - - UPROPERTY(NonTransactional) - int32 NumWorkitems; - UPROPERTY(Transient, NonTransactional) - FAggregatedWorkItemTally WorkItemTally; - - UPROPERTY() - FString OutputCachePath; - - UPROPERTY(Transient) - bool bNeedsUIRefresh; - - // A parent actor to serve as the parent of any output actors - // that are created. - // If null, then output actors are created under a folder - UPROPERTY(EditAnywhere, Category="Output") - AActor* OutputParentActor; - - // Folder used for baking PDG outputs - UPROPERTY() - FDirectoryPath BakeFolder; - - // - // Notifications - // - - // Delegate that is broadcast when a work result object has been loaded - FHoudiniPDGAssetLinkWorkResultObjectLoaded OnWorkResultObjectLoaded; - - // Delegate that is broadcast after a bake. - FOnPostBakeDelegate OnPostBakeDelegate; - - // Delegate that is broadcast after a TOP Network completes a cook. - FOnPostTOPNetworkCookDelegate OnPostTOPNetworkCookDelegate; - - // - // End: Notifications - // - -#if WITH_EDITORONLY_DATA - UPROPERTY() - bool bBakeMenuExpanded; - - // What kind of output to bake, for example, bake actors, bake to blueprint - UPROPERTY() - EHoudiniEngineBakeOption HoudiniEngineBakeOption; - - // Which outputs to bake, for example, all, selected network, selected node - UPROPERTY() - EPDGBakeSelectionOption PDGBakeSelectionOption; - - // This determines if the baked assets should replace existing assets with the same name, - // or always generate new assets (with numerical suffixes if needed to create unique names) - UPROPERTY() - EPDGBakePackageReplaceModeOption PDGBakePackageReplaceMode; - - // If true, recenter baked actors to their bounding box center after bake - UPROPERTY() - bool bRecenterBakedActors; - - // Auto-bake: if this is true, it indicates that once all work result objects for the node is loaded they should - // all be baked - UPROPERTY() - bool bBakeAfterAllWorkResultObjectsLoaded; - - // The delegate handle of the auto bake helper function bound to OnWorkResultObjectLoaded. - FDelegateHandle AutoBakeDelegateHandle; -#endif -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +//#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniTranslatorTypes.h" + +#include "HoudiniPDGAssetLink.generated.h" + +struct FHoudiniPackageParams; + +UENUM() +enum class EPDGLinkState : uint8 +{ + Inactive, + Linking, + Linked, + Error_Not_Linked +}; + + +UENUM() +enum class EPDGNodeState : uint8 +{ + None, + Dirtied, + Dirtying, + Cooking, + Cook_Complete, + Cook_Failed +}; + +UENUM() +enum class EPDGWorkResultState : uint8 +{ + None, + ToLoad, + Loading, + Loaded, + ToDelete, + Deleting, + Deleted, + NotLoaded +}; + + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FOutputActorOwner +{ + GENERATED_BODY(); +public: + FOutputActorOwner() + : OutputActor(nullptr) {}; + + virtual ~FOutputActorOwner() {}; + + // Create OutputActor, an actor to hold work result output + virtual bool CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName); + + // Return OutputActor + virtual AActor* GetOutputActor() const { return OutputActor; } + + // Setter for OutputActor + virtual void SetOutputActor(AActor* InActor) { OutputActor = InActor; } + + // Destroy OutputActor if it is valid. + virtual bool DestroyOutputActor(); + +private: + UPROPERTY(NonTransactional) + AActor* OutputActor; + +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FTOPWorkResultObject(); + + // Call DestroyResultObjects in the destructor + virtual ~FTOPWorkResultObject(); + + // Set ResultObjects to a copy of InUpdatedOutputs + void SetResultOutputs(const TArray& InUpdatedOutputs) { ResultOutputs = InUpdatedOutputs; } + + // Getter for ResultOutputs + TArray& GetResultOutputs() { return ResultOutputs; } + + // Getter for ResultOutputs + const TArray& GetResultOutputs() const { return ResultOutputs; } + + // Destroy ResultOutputs + void DestroyResultOutputs(); + + // Get the OutputActor owner struct + FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } + + // Get the OutputActor owner struct + const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } + + // Destroy the ResultOutputs and remove the output actor. + void DestroyResultOutputsAndRemoveOutputActor(); + + // Getter for bAutoBakedSinceLastLoad: indicates if this work result object has been auto-baked since it's last load. + bool AutoBakedSinceLastLoad() const { return bAutoBakedSinceLastLoad; } + // Setter for bAutoBakedSinceLastLoad + void SetAutoBakedSinceLastLoad(bool bInAutoBakedSinceLastLoad) { bAutoBakedSinceLastLoad = bInAutoBakedSinceLastLoad; } + +public: + + UPROPERTY(NonTransactional) + FString Name; + UPROPERTY(NonTransactional) + FString FilePath; + UPROPERTY(NonTransactional) + EPDGWorkResultState State; + // The index in the WorkItemResultInfo array of this item as it was received from HAPI. + UPROPERTY(NonTransactional) + int32 WorkItemResultInfoIndex; + +protected: + // UPROPERTY() + // TArray ResultObjects; + + UPROPERTY(NonTransactional) + TArray ResultOutputs; + + // If true, indicates that the work result object has been auto-baked since it was last loaded. + UPROPERTY(NonTransactional) + bool bAutoBakedSinceLastLoad = false; + +private: + // List of objects to delete, internal use only (DestroyResultOutputs) + UPROPERTY(NonTransactional) + TArray OutputObjectsToDelete; + + UPROPERTY(NonTransactional) + FOutputActorOwner OutputActorOwner; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FTOPWorkResult +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FTOPWorkResult(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const FTOPWorkResult& OtherWorkResult) const; + + // Calls FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor on each entry in ResultObjects and clears the array. + void ClearAndDestroyResultObjects(); + + // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. + int32 IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); + // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. + FTOPWorkResultObject* GetWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); + // Return the FTOPWorkResultObject at InArrayIndex in the ResultObjects array, or nullptr if InArrayIndex is not a valid index. + FTOPWorkResultObject* GetWorkResultObjectByArrayIndex(const int32& InArrayIndex); + +public: + + UPROPERTY(NonTransactional) + int32 WorkItemIndex; + UPROPERTY(Transient) + int32 WorkItemID; + + UPROPERTY(NonTransactional) + TArray ResultObjects; + + /* + UPROPERTY() + TArray ResultObjects; + + UPROPERTY() + TArray ResultNames; + UPROPERTY() + TArray ResultFilePaths; + UPROPERTY() + TArray ResultStates; + */ +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FWorkItemTallyBase +{ + GENERATED_USTRUCT_BODY() + +public: + virtual ~FWorkItemTallyBase(); + + // + // Mutators + // + + // Zero all counts, including total. + virtual void ZeroAll() {}; + + // + // Accessors + // + + bool AreAllWorkItemsComplete() const; + bool AnyWorkItemsFailed() const; + bool AnyWorkItemsPending() const; + + virtual int32 NumWorkItems() const { return 0; }; + virtual int32 NumWaitingWorkItems() const { return 0; }; + virtual int32 NumScheduledWorkItems() const { return 0; }; + virtual int32 NumCookingWorkItems() const { return 0; }; + virtual int32 NumCookedWorkItems() const { return 0; }; + virtual int32 NumErroredWorkItems() const { return 0; }; + virtual int32 NumCookCancelledWorkItems() const { return 0; }; + + FString ProgressRatio() const; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FWorkItemTally : public FWorkItemTallyBase +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FWorkItemTally(); + + // + // Mutators + // + + // Empty all state sets, as well as AllWorkItems. + virtual void ZeroAll() override; + + // Remove a work item from all state sets and AllWorkItems. + void RemoveWorkItem(int32 InWorkItemID); + + void RecordWorkItemAsWaiting(int32 InWorkItemID); + void RecordWorkItemAsScheduled(int32 InWorkItemID); + void RecordWorkItemAsCooking(int32 InWorkItemID); + void RecordWorkItemAsCooked(int32 InWorkItemID); + void RecordWorkItemAsErrored(int32 InWorkItemID); + void RecordWorkItemAsCookCancelled(int32 InWorkItemID); + + // + // Accessors + // + + virtual int32 NumWorkItems() const override { return AllWorkItems.Num(); } + virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems.Num(); } + virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems.Num(); } + virtual int32 NumCookingWorkItems() const override { return CookingWorkItems.Num(); } + virtual int32 NumCookedWorkItems() const override { return CookedWorkItems.Num(); } + virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems.Num(); } + virtual int32 NumCookCancelledWorkItems() const override { return CookCancelledWorkItems.Num(); } + +protected: + + // Removes the work item id from all state sets (but not from AllWorkItems -- use RemoveWorkItem for that). + void RemoveWorkItemFromAllStateSets(int32 InWorkItemID); + + // We use sets to keep track of in what state a work item is. The set stores the WorkItemID. + + UPROPERTY() + TSet AllWorkItems; + UPROPERTY() + TSet WaitingWorkItems; + UPROPERTY() + TSet ScheduledWorkItems; + UPROPERTY() + TSet CookingWorkItems; + UPROPERTY() + TSet CookedWorkItems; + UPROPERTY() + TSet ErroredWorkItems; + UPROPERTY() + TSet CookCancelledWorkItems; +}; + +USTRUCT() +struct HOUDINIENGINERUNTIME_API FAggregatedWorkItemTally : public FWorkItemTallyBase +{ + GENERATED_USTRUCT_BODY() + +public: + + // Constructor + FAggregatedWorkItemTally(); + + virtual void ZeroAll() override; + + void Add(const FWorkItemTallyBase& InWorkItemTally); + + void Subtract(const FWorkItemTallyBase& InWorkItemTally); + + virtual int32 NumWorkItems() const override { return TotalWorkItems; } + virtual int32 NumWaitingWorkItems() const override { return WaitingWorkItems; } + virtual int32 NumScheduledWorkItems() const override { return ScheduledWorkItems; } + virtual int32 NumCookingWorkItems() const override { return CookingWorkItems; } + virtual int32 NumCookedWorkItems() const override { return CookedWorkItems; } + virtual int32 NumErroredWorkItems() const override { return ErroredWorkItems; } + +protected: + UPROPERTY() + int32 TotalWorkItems; + UPROPERTY() + int32 WaitingWorkItems; + UPROPERTY() + int32 ScheduledWorkItems; + UPROPERTY() + int32 CookingWorkItems; + UPROPERTY() + int32 CookedWorkItems; + UPROPERTY() + int32 ErroredWorkItems; + UPROPERTY() + int32 CookCancelledWorkItems; + +}; + +// Container for baked outputs of a PDG work result object. +USTRUCT() +struct HOUDINIENGINERUNTIME_API FHoudiniPDGWorkResultObjectBakedOutput +{ + GENERATED_BODY() + + public: + // Array of baked output per output index of the work result object's outputs. + UPROPERTY() + TArray BakedOutputs; +}; + +// Forward declare the UTOPNetwork here for some references in the UTOPNode +class UTOPNetwork; + +UCLASS() +class HOUDINIENGINERUNTIME_API UTOPNode : public UObject +{ + GENERATED_BODY() + +public: + // Constructor + UTOPNode(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const UTOPNode& Other) const; + + void Reset(); + + const FWorkItemTallyBase& GetWorkItemTally() const + { + if (bHasChildNodes) + return AggregatedWorkItemTally; + return WorkItemTally; + } + + void AggregateTallyFromChildNode(const UTOPNode* InChildNode) + { + if (IsValid(InChildNode)) + AggregatedWorkItemTally.Add(InChildNode->GetWorkItemTally()); + } + + bool AreAllWorkItemsComplete() const { return GetWorkItemTally().AreAllWorkItemsComplete(); }; + bool AnyWorkItemsFailed() const { return GetWorkItemTally().AnyWorkItemsFailed(); }; + bool AnyWorkItemsPending() const { return GetWorkItemTally().AnyWorkItemsPending(); }; + void ZeroWorkItemTally() + { + WorkItemTally.ZeroAll(); + AggregatedWorkItemTally.ZeroAll(); + } + + // Called by PDG manager when work item events are received + + // Notification that a work item has been created + void OnWorkItemCreated(int32 InWorkItemID) { }; + + // Notification that a work item has been removed. + void OnWorkItemRemoved(int32 InWorkItemID) { WorkItemTally.RemoveWorkItem(InWorkItemID); }; + + // Notification that a work item has moved to the waiting state. + void OnWorkItemWaiting(int32 InWorkItemID); + + // Notification that a work item has been scheduled. + void OnWorkItemScheduled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsScheduled(InWorkItemID); }; + + // Notification that a work item has started cooking. + void OnWorkItemCooking(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCooking(InWorkItemID); }; + + // Notification that a work item has been cooked. + void OnWorkItemCooked(int32 InWorkItemID); + + // Notification that a work item has errored. + void OnWorkItemErrored(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsErrored(InWorkItemID); }; + + // Notification that a work item cook has been cancelled. + void OnWorkItemCookCancelled(int32 InWorkItemID) { WorkItemTally.RecordWorkItemAsCookCancelled(InWorkItemID); }; + + bool IsVisibleInLevel() const { return bShow; } + void SetVisibleInLevel(bool bInVisible); + void UpdateOutputVisibilityInLevel(); + + // Sets all WorkResultObjects that are in the NotLoaded state to ToLoad. + void SetNotLoadedWorkResultsToLoad(bool bInAlsoSetDeletedToLoad=false); + + // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output + // actors). + void SetLoadedWorkResultsToDelete(); + + // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output + // objects and actors and sets the state to Deleted. + void DeleteWorkResultOutputObjects(); + + // Get the OutputActor owner struct + FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } + + // Get the OutputActor owner struct + const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } + + // Get the baked outputs from the last bake. The map keys are [work_result.work_item_index]_[work_result_object_index] + TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } + const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } + // Helper to construct the key used to look up baked work results. + static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex); + // Helper to construct the key used to look up baked work results. + static FString GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex); + // Helper to construct the key used to look up baked work results. + bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const; + // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object. + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput*& OutBakedOutput); + // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object (const version). + bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; + + // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. + int32 IndexOfWorkResultByID(const int32& InWorkItemID); + // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. + FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID); + // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. + // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. + int32 IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); + // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. + // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. + FTOPWorkResult* GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); + // Return the FTOPWorkResult at InArrayIndex in the WorkResult array, or nullptr if InArrayIndex is not a valid index. + FTOPWorkResult* GetWorkResultByArrayIndex(const int32& InArrayIndex); + + // Returns true if InNetwork is the parent TOP Net of this node. + bool IsParentTOPNetwork(UTOPNetwork const * const InNetwork) const; + + // Returns true if this node can still be auto-baked + bool CanStillBeAutoBaked() const; + +#if WITH_EDITOR + void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif + +#if WITH_EDITOR + void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; +#endif + +public: + + UPROPERTY(Transient, NonTransactional) + int32 NodeId; + UPROPERTY(NonTransactional) + FString NodeName; + UPROPERTY(NonTransactional) + FString NodePath; + UPROPERTY(NonTransactional) + FString ParentName; + + UPROPERTY() + UObject* WorkResultParent; + UPROPERTY(NonTransactional) + TArray WorkResult; + + // Hidden in the nodes combobox + UPROPERTY() + bool bHidden; + UPROPERTY() + bool bAutoLoad; + + UPROPERTY(Transient, NonTransactional) + EPDGNodeState NodeState; + + // This is set when the TOP node's work items are processed by + // FHoudiniPDGManager based on if any NotLoaded work result objects are found + UPROPERTY(NonTransactional) + bool bCachedHaveNotLoadedWorkResults; + + // This is set when the TOP node's work items are processed by + // FHoudiniPDGManager based on if any Loaded work result objects are found + UPROPERTY(NonTransactional) + bool bCachedHaveLoadedWorkResults; + + // True if this node has child nodes + UPROPERTY(NonTransactional) + bool bHasChildNodes; + + // These notification events have been introduced so that we can start encapsulating code. + // in this class as opposed to modifying this object in various places throughout the codebase. + + // Notification that this TOP node has been dirtied. + void OnDirtyNode(); + + // Accessors for the landscape data caches + FHoudiniLandscapeExtent& GetLandscapeExtent() { return LandscapeExtent; } + FHoudiniLandscapeReferenceLocation& GetLandscapeReferenceLocation() { return LandscapeReferenceLocation; } + FHoudiniLandscapeTileSizeInfo& GetLandscapeSizeInfo() { return LandscapeSizeInfo; } + // More cached landscape data + UPROPERTY() + TSet ClearedLandscapeLayers; + + // Returns true if the node has received the HAPI_PDG_EVENT_COOK_COMPLETE event since the last the cook started + bool HasReceivedCookCompleteEvent() const { return bHasReceivedCookCompleteEvent; } + // Handler for when the node receives the HAPI_PDG_EVENT_COOK_START (called for each node when a TOPNet starts cooking) + void HandleOnPDGEventCookStart() { bHasReceivedCookCompleteEvent = false; } + // Handler for when the node receives the HAPI_PDG_EVENT_COOK_COMPLETE event (called for each node when a TOPNet completes cooking) + void HandleOnPDGEventCookComplete() { bHasReceivedCookCompleteEvent = true; } + +protected: + void InvalidateLandscapeCache(); + + // Value caches used during landscape tile creation. + FHoudiniLandscapeReferenceLocation LandscapeReferenceLocation; + FHoudiniLandscapeTileSizeInfo LandscapeSizeInfo; + FHoudiniLandscapeExtent LandscapeExtent; + + // Visible in the level + UPROPERTY() + bool bShow; + + // Map of [work_result_index]_[work_result_object_index] to the work result object's baked outputs. + UPROPERTY() + TMap BakedWorkResultObjectOutputs; + + // This node's own work items, used when bHasChildNodes is false. + UPROPERTY(Transient, NonTransactional) + FWorkItemTally WorkItemTally; + // This node's aggregated work item tallys (sum of child work item tally, use when bHasChildNodes is true) + UPROPERTY(Transient, NonTransactional) + FAggregatedWorkItemTally AggregatedWorkItemTally; + + // Set to true when the node recieves HAPI_PDG_EVENT_COOK_COMPLETE event + UPROPERTY(Transient, NonTransactional) + bool bHasReceivedCookCompleteEvent; + +private: + UPROPERTY() + FOutputActorOwner OutputActorOwner; +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject +{ + GENERATED_BODY() + +public: + + // Delegate that is broadcast when cook of the network is complete. Parameters are the UTOPNetwork and bAnyFailedWorkItems. + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostCookDelegate, UTOPNetwork*, const bool); + + // Constructor + UTOPNetwork(); + + // Comparison operator, used by hashing containers and arrays. + bool operator==(const UTOPNetwork& Other) const; + + // Sets all WorkResultObjects that are in the Loaded state to ToDelete (will delete output objects and output + // actors). + void SetLoadedWorkResultsToDelete(); + + // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output + // objects and actors and sets the state to Deleted. + void DeleteWorkResultOutputObjects(); + + // Returns true if any node in this TOP net has pending (waiting, scheduled, cooking) work items. + bool AnyWorkItemsPending() const; + + // Returns true if any node in this TOP net has failed/errored work items. + bool AnyWorkItemsFailed() const; + + // Returns true if this network has nodes that can still be auto-baked + bool CanStillBeAutoBaked() const; + + // Handler for when a node in the newtork receives the HAPI_PDG_EVENT_COOK_COMPLETE event (called for each node when a TOPNet completes cooking) + void HandleOnPDGEventCookCompleteReceivedByChildNode(UHoudiniPDGAssetLink* const InAssetLink, UTOPNode* const InTOPNode); + + FOnPostCookDelegate& GetOnPostCookDelegate() { return OnPostCookDelegate; } + +public: + + UPROPERTY(Transient, NonTransactional) + int32 NodeId; + UPROPERTY(NonTransactional) + FString NodeName; + // HAPI path to this node (relative to the HDA) + UPROPERTY(NonTransactional) + FString NodePath; + + UPROPERTY() + TArray AllTOPNodes; + + // TODO: Should be using SelectedNodeName instead? + // Index is not consistent after updating filter + UPROPERTY() + int32 SelectedTOPIndex; + + UPROPERTY(NonTransactional) + FString ParentName; + + UPROPERTY() + bool bShowResults; + UPROPERTY() + bool bAutoLoadResults; + + FOnPostCookDelegate OnPostCookDelegate; +}; + + +class UHoudiniPDGAssetLink; +DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemHAPIIndex*/, int32 /*WorkItemResultInfoIndex*/); + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + friend class UHoudiniAssetComponent; + + // Delegate for when the entire bake operation is complete (all selected nodes/networks have been baked). + DECLARE_MULTICAST_DELEGATE_TwoParams(FOnPostBakeDelegate, UHoudiniPDGAssetLink*, const bool); + // Delegate for when a network completes a cook. Passes the asset link, the network, a bAnyWorkItemsFailed. + DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnPostTOPNetworkCookDelegate, UHoudiniPDGAssetLink*, UTOPNetwork*, const bool); + + static FString GetAssetLinkStatus(const EPDGLinkState& InLinkState); + static FString GetTOPNodeStatus(const UTOPNode* InTOPNode); + static FLinearColor GetTOPNodeStatusColor(const UTOPNode* InTOPNode); + + void UpdateTOPNodeWithChildrenWorkItemTallyAndState(UTOPNode* InNode, UTOPNetwork* InNetwork); + void UpdateWorkItemTally(); + static void ResetTOPNetworkWorkItemTally(UTOPNetwork* TOPNetwork); + + // Set the TOP network at the given index as currently selected TOP network + void SelectTOPNetwork(const int32& AtIndex); + // Set the TOP node at the given index in the given TOP network as currently selected TOP node + void SelectTOPNode(UTOPNetwork* InTOPNetwork, const int32& AtIndex); + + UTOPNode* GetSelectedTOPNode(); + const UTOPNode* GetSelectedTOPNode() const; + UTOPNetwork* GetSelectedTOPNetwork(); + const UTOPNetwork* GetSelectedTOPNetwork() const; + + FString GetSelectedTOPNodeName(); + FString GetSelectedTOPNetworkName(); + + UTOPNode* GetTOPNode(const int32& InNodeID); + bool GetTOPNodeAndNetworkByNodeId(const int32& InNodeID, UTOPNetwork*& OutNetwork, UTOPNode*& OutNode); + UTOPNetwork* GetTOPNetwork(const int32& AtIndex); + const UTOPNetwork* GetTOPNetwork(const int32& AtIndex) const; + + // Find the node with relative path 'InNodePath' from its topnet. + static UTOPNode* GetTOPNodeByNodePath(const FString& InNodePath, const TArray& InTOPNodes, int32& OutIndex); + // Find the network with relative path 'InNetPath' from the HDA + static UTOPNetwork* GetTOPNetworkByNodePath(const FString& InNodePath, const TArray& InTOPNetworks, int32& OutIndex); + + // Get the parent TOP node of the specified node. This is resolved + UTOPNode* GetParentTOPNode(const UTOPNode* InNode); + + static void ClearTOPNodeWorkItemResults(UTOPNode* TOPNode); + static void ClearTOPNetworkWorkItemResults(UTOPNetwork* TOPNetwork); + // Clear the result objects of a work item (FTOPWorkResult.ResultObjects), but don't delete the work item from + // TOPNode.WorkResults (for example, the work item was dirtied but not removed from PDG) + static void ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + // Calls ClearWorkItemResultByID and then deletes the FTOPWorkResult from InTOPNode.Result as well. For example: + // the work item was removed in PDG. + static void DestroyWorkItemByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + static FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID, UTOPNode* InTOPNode); + + // This should be called after the owner and this PDG asset link is duplicated. Set all output parent actors to + // null in all TOP networks/nodes. Since the TOP Networks/TOP nodes are all structs, we cannot set + // DuplicateTransient property on their OutputActor properties. + void UpdatePostDuplicate(); + + // Load the geometry generated as results of the given work item, of the given TOP node. + // The load will be done asynchronously. + // Results must be tagged with 'file', and must have a file path, otherwise will not be loaded. + //void LoadResults(FTOPNode TOPNode, HAPI_PDG_WorkitemInfo workItemInfo, HAPI_PDG_WorkitemResultInfo[] resultInfos, HAPI_PDG_WorkitemId workItemID) + + // Return the first UHoudiniAssetComponent in the parent chain. If this asset link is not + // owned by a HoudiniAssetComponent, a nullptr will be returned. + UHoudiniAssetComponent* GetOuterHoudiniAssetComponent() const; + + // Gets the temporary cook folder. If the parent of this asset link is a HoudiniAssetComponent use that, otherwise + // use the default static mesh temporary cook folder. + FDirectoryPath GetTemporaryCookFolder() const; + + // Get the actor that owns this PDG asset link. If the asset link is owned by a component, + // then the component's owning actor is returned. Can return null if this is now owned by + // an actor or component. + AActor* GetOwnerActor() const; + + // Checks if the asset link has any temporary outputs and returns true if it has + bool HasTemporaryOutputs() const; + + // Filter TOP nodes and outputs (hidden/visible) by TOPNodeFilter and TOPOutputFilter. + void FilterTOPNodesAndOutputs(); + + // On all FTOPNodes: Load not loaded items if bAutoload is true, and update the level visibility of work items + // result. Used when FTOPNode.bShow and/or FTOPNode.bAutoload changed. + void UpdateTOPNodeAutoloadAndVisibility(); + +#if WITH_EDITORONLY_DATA + // Returns true if there are any nodes left that can/must still be auto-baked. + bool AnyRemainingAutoBakeNodes() const; +#endif + + // Delegate handlers + + // Get the post bake delegate + FOnPostBakeDelegate& GetOnPostBakeDelegate() { return OnPostBakeDelegate; } + + // Called by baking code after baking all of the outputs + void HandleOnPostBake(const bool bInSuccess); + + FOnPostTOPNetworkCookDelegate& GetOnPostTOPNetworkCookDelegate() { return OnPostTOPNetworkCookDelegate; } + + // Handler for when a TOP network completes a cook. Called by the TOP Net once all of its nodes have received + // HAPI_PDG_EVENT_COOK_COMPLETE. + void HandleOnTOPNetworkCookComplete(UTOPNetwork* const InTOPNet); + +#if WITH_EDITORONLY_DATA + void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; +#endif + +#if WITH_EDITORONLY_DATA + void PostTransacted(const FTransactionObjectEvent& TransactionEvent) override; +#endif + +private: + + void ClearAllTOPData(); + + static void DestroyWorkItemResultData(FTOPWorkResult& Result); + + static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); + +public: + + //UPROPERTY() + //UHoudiniAsset* HoudiniAsset; + + //UPROPERTY() + //UHoudiniAssetComponent* ParentHAC; + + UPROPERTY(DuplicateTransient, NonTransactional) + FString AssetName; + + // The full path to the HDA in HAPI + UPROPERTY(DuplicateTransient, NonTransactional) + FString AssetNodePath; + + UPROPERTY(DuplicateTransient, NonTransactional) + int32 AssetID; + + UPROPERTY() + TArray AllTOPNetworks; + + UPROPERTY() + int32 SelectedTOPNetworkIndex; + + UPROPERTY(Transient, NonTransactional) + EPDGLinkState LinkState; + + UPROPERTY() + bool bAutoCook; + UPROPERTY() + bool bUseTOPNodeFilter; + UPROPERTY() + bool bUseTOPOutputFilter; + UPROPERTY() + FString TOPNodeFilter; + UPROPERTY() + FString TOPOutputFilter; + + UPROPERTY(NonTransactional) + int32 NumWorkitems; + UPROPERTY(Transient, NonTransactional) + FAggregatedWorkItemTally WorkItemTally; + + UPROPERTY() + FString OutputCachePath; + + UPROPERTY(Transient) + bool bNeedsUIRefresh; + + // A parent actor to serve as the parent of any output actors + // that are created. + // If null, then output actors are created under a folder + UPROPERTY(EditAnywhere, Category="Output") + AActor* OutputParentActor; + + // Folder used for baking PDG outputs + UPROPERTY() + FDirectoryPath BakeFolder; + + // + // Notifications + // + + // Delegate that is broadcast when a work result object has been loaded + FHoudiniPDGAssetLinkWorkResultObjectLoaded OnWorkResultObjectLoaded; + + // Delegate that is broadcast after a bake. + FOnPostBakeDelegate OnPostBakeDelegate; + + // Delegate that is broadcast after a TOP Network completes a cook. + FOnPostTOPNetworkCookDelegate OnPostTOPNetworkCookDelegate; + + // + // End: Notifications + // + +#if WITH_EDITORONLY_DATA + UPROPERTY() + bool bBakeMenuExpanded; + + // What kind of output to bake, for example, bake actors, bake to blueprint + UPROPERTY() + EHoudiniEngineBakeOption HoudiniEngineBakeOption; + + // Which outputs to bake, for example, all, selected network, selected node + UPROPERTY() + EPDGBakeSelectionOption PDGBakeSelectionOption; + + // This determines if the baked assets should replace existing assets with the same name, + // or always generate new assets (with numerical suffixes if needed to create unique names) + UPROPERTY() + EPDGBakePackageReplaceModeOption PDGBakePackageReplaceMode; + + // If true, recenter baked actors to their bounding box center after bake + UPROPERTY() + bool bRecenterBakedActors; + + // Auto-bake: if this is true, it indicates that once all work result objects for the node is loaded they should + // all be baked + UPROPERTY() + bool bBakeAfterAllWorkResultObjectsLoaded; + + // The delegate handle of the auto bake helper function bound to OnWorkResultObjectLoaded. + FDelegateHandle AutoBakeDelegateHandle; +#endif +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp index 068fd82f8..c2d744215 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.cpp @@ -1,258 +1,258 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameter.h" - -UHoudiniParameter::UHoudiniParameter(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , ParmType(EHoudiniParameterType::Invalid) - , TupleSize(0) - , NodeId(-1) - , ParmId(-1) - , ParentParmId(-1) - , ChildIndex(-1) - , bIsVisible(true) - , bIsDisabled(false) - , bHasChanged(false) - , bNeedsToTriggerUpdate(true) - , bIsDefault(false) - , bIsSpare(false) - , bJoinNext(false) - , bIsChildOfMultiParm(false) - , bIsDirectChildOfMultiParm(false) - , TagCount(0) - , ValueIndex(-1) - , bHasExpression(false) - , bShowExpression(false) -{ - Name = TEXT(""); - Label = TEXT(""); - Help = TEXT(""); - -} - -UHoudiniParameter * -UHoudiniParameter::Create( UObject* InOuter, const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameter_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameter::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameter * HoudiniAssetParameter = NewObject< UHoudiniParameter >( - InOuter, UHoudiniParameter::StaticClass(), ParamName, RF_Public | RF_Transactional); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameter::IsChildParameter() const -{ - return ParentParmId >= 0; -} - -void -UHoudiniParameter::RevertToDefault() -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); -} - -void -UHoudiniParameter::RevertToDefault(const int32& InAtTupleIndex) -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.AddUnique(InAtTupleIndex); - - MarkChanged(true); -} - -void -UHoudiniParameter::MarkDefault(const bool& bInDefault) -{ - bIsDefault = bInDefault; - - if (bInDefault) - { - // No need to revert default parameter - bPendingRevertToDefault = false; - TuplePendingRevertToDefault.Empty(); - } -} - -EHoudiniRampInterpolationType UHoudiniParameter::GetHoudiniInterpMethodFromInt(int32 InInt) -{ - EHoudiniRampInterpolationType Result = EHoudiniRampInterpolationType::InValid; - switch (InInt) - { - case 0: - Result = EHoudiniRampInterpolationType::CONSTANT; - break; - case 1: - Result = EHoudiniRampInterpolationType::LINEAR; - break; - case 2: - Result = EHoudiniRampInterpolationType::CATMULL_ROM; - break; - case 3: - Result = EHoudiniRampInterpolationType::MONOTONE_CUBIC; - break; - case 4: - Result = EHoudiniRampInterpolationType::BEZIER; - break; - case 5: - Result = EHoudiniRampInterpolationType::BSPLINE; - break; - case 6: - Result = EHoudiniRampInterpolationType::HERMITE; - break; - } - - return Result; -} - -FString -UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType) -{ - FString Result = FString("InValid"); - switch (InType) - { - case EHoudiniRampInterpolationType::CONSTANT: - Result = FString("Constant"); - break; - case EHoudiniRampInterpolationType::LINEAR: - Result = FString("Linear"); - break; - case EHoudiniRampInterpolationType::BEZIER: - Result = FString("Bezier"); - break; - case EHoudiniRampInterpolationType::BSPLINE: - Result = FString("B-Spline"); - break; - case EHoudiniRampInterpolationType::MONOTONE_CUBIC: - Result = FString("Monotone Cubic"); - break; - case EHoudiniRampInterpolationType::CATMULL_ROM: - Result = FString("Catmull Rom"); - break; - case EHoudiniRampInterpolationType::HERMITE: - Result = FString("Hermite"); - break; - } - - return Result; -} -EHoudiniRampInterpolationType -UHoudiniParameter::GetHoudiniInterpMethodFromString(const FString& InString) -{ - if (InString.StartsWith(TEXT("Constant"))) - return EHoudiniRampInterpolationType::CONSTANT; - - else if (InString.StartsWith("Linear")) - return EHoudiniRampInterpolationType::LINEAR; - - else if (InString.StartsWith("Bezier")) - return EHoudiniRampInterpolationType::BEZIER; - - else if (InString.StartsWith("B-Spline")) - return EHoudiniRampInterpolationType::BSPLINE; - - else if (InString.StartsWith("Monotone Cubic")) - return EHoudiniRampInterpolationType::MONOTONE_CUBIC; - - else if (InString.StartsWith("Catmull Rom")) - return EHoudiniRampInterpolationType::CATMULL_ROM; - - else if (InString.StartsWith("Hermite")) - return EHoudiniRampInterpolationType::HERMITE; - - return EHoudiniRampInterpolationType::InValid; -} - - - -ERichCurveInterpMode -UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType) -{ - switch (InType) - { - case EHoudiniRampInterpolationType::CONSTANT: - return ERichCurveInterpMode::RCIM_Constant; - - case EHoudiniRampInterpolationType::LINEAR: - return ERichCurveInterpMode::RCIM_Linear; - - default: - break; - } - - return ERichCurveInterpMode::RCIM_Cubic; -} - -UHoudiniParameter* -UHoudiniParameter::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags, EObjectFlags SetFlags) -{ - UHoudiniParameter* NewParameter = Cast(StaticDuplicateObject(this, DestOuter)); - - NewParameter->CopyStateFrom(this, false); - - return NewParameter; -} - -void -UHoudiniParameter::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - - NodeId = InParameter->NodeId; - ParmId = InParameter->ParmId; - ParentParmId = InParameter->ParentParmId; - bIsDefault = InParameter->bIsDefault; - bPendingRevertToDefault = InParameter->bPendingRevertToDefault; - TuplePendingRevertToDefault = InParameter->TuplePendingRevertToDefault; - bShowExpression = InParameter->bShowExpression; -} - -void -UHoudiniParameter::InvalidateData() -{ - -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameter.h" + +UHoudiniParameter::UHoudiniParameter(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , ParmType(EHoudiniParameterType::Invalid) + , TupleSize(0) + , NodeId(-1) + , ParmId(-1) + , ParentParmId(-1) + , ChildIndex(-1) + , bIsVisible(true) + , bIsDisabled(false) + , bHasChanged(false) + , bNeedsToTriggerUpdate(true) + , bIsDefault(false) + , bIsSpare(false) + , bJoinNext(false) + , bIsChildOfMultiParm(false) + , bIsDirectChildOfMultiParm(false) + , TagCount(0) + , ValueIndex(-1) + , bHasExpression(false) + , bShowExpression(false) +{ + Name = TEXT(""); + Label = TEXT(""); + Help = TEXT(""); + +} + +UHoudiniParameter * +UHoudiniParameter::Create( UObject* InOuter, const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameter_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameter::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameter * HoudiniAssetParameter = NewObject< UHoudiniParameter >( + InOuter, UHoudiniParameter::StaticClass(), ParamName, RF_Public | RF_Transactional); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameter::IsChildParameter() const +{ + return ParentParmId >= 0; +} + +void +UHoudiniParameter::RevertToDefault() +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); +} + +void +UHoudiniParameter::RevertToDefault(const int32& InAtTupleIndex) +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.AddUnique(InAtTupleIndex); + + MarkChanged(true); +} + +void +UHoudiniParameter::MarkDefault(const bool& bInDefault) +{ + bIsDefault = bInDefault; + + if (bInDefault) + { + // No need to revert default parameter + bPendingRevertToDefault = false; + TuplePendingRevertToDefault.Empty(); + } +} + +EHoudiniRampInterpolationType UHoudiniParameter::GetHoudiniInterpMethodFromInt(int32 InInt) +{ + EHoudiniRampInterpolationType Result = EHoudiniRampInterpolationType::InValid; + switch (InInt) + { + case 0: + Result = EHoudiniRampInterpolationType::CONSTANT; + break; + case 1: + Result = EHoudiniRampInterpolationType::LINEAR; + break; + case 2: + Result = EHoudiniRampInterpolationType::CATMULL_ROM; + break; + case 3: + Result = EHoudiniRampInterpolationType::MONOTONE_CUBIC; + break; + case 4: + Result = EHoudiniRampInterpolationType::BEZIER; + break; + case 5: + Result = EHoudiniRampInterpolationType::BSPLINE; + break; + case 6: + Result = EHoudiniRampInterpolationType::HERMITE; + break; + } + + return Result; +} + +FString +UHoudiniParameter::GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType) +{ + FString Result = FString("InValid"); + switch (InType) + { + case EHoudiniRampInterpolationType::CONSTANT: + Result = FString("Constant"); + break; + case EHoudiniRampInterpolationType::LINEAR: + Result = FString("Linear"); + break; + case EHoudiniRampInterpolationType::BEZIER: + Result = FString("Bezier"); + break; + case EHoudiniRampInterpolationType::BSPLINE: + Result = FString("B-Spline"); + break; + case EHoudiniRampInterpolationType::MONOTONE_CUBIC: + Result = FString("Monotone Cubic"); + break; + case EHoudiniRampInterpolationType::CATMULL_ROM: + Result = FString("Catmull Rom"); + break; + case EHoudiniRampInterpolationType::HERMITE: + Result = FString("Hermite"); + break; + } + + return Result; +} +EHoudiniRampInterpolationType +UHoudiniParameter::GetHoudiniInterpMethodFromString(const FString& InString) +{ + if (InString.StartsWith(TEXT("Constant"))) + return EHoudiniRampInterpolationType::CONSTANT; + + else if (InString.StartsWith("Linear")) + return EHoudiniRampInterpolationType::LINEAR; + + else if (InString.StartsWith("Bezier")) + return EHoudiniRampInterpolationType::BEZIER; + + else if (InString.StartsWith("B-Spline")) + return EHoudiniRampInterpolationType::BSPLINE; + + else if (InString.StartsWith("Monotone Cubic")) + return EHoudiniRampInterpolationType::MONOTONE_CUBIC; + + else if (InString.StartsWith("Catmull Rom")) + return EHoudiniRampInterpolationType::CATMULL_ROM; + + else if (InString.StartsWith("Hermite")) + return EHoudiniRampInterpolationType::HERMITE; + + return EHoudiniRampInterpolationType::InValid; +} + + + +ERichCurveInterpMode +UHoudiniParameter::EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType) +{ + switch (InType) + { + case EHoudiniRampInterpolationType::CONSTANT: + return ERichCurveInterpMode::RCIM_Constant; + + case EHoudiniRampInterpolationType::LINEAR: + return ERichCurveInterpMode::RCIM_Linear; + + default: + break; + } + + return ERichCurveInterpMode::RCIM_Cubic; +} + +UHoudiniParameter* +UHoudiniParameter::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags, EObjectFlags SetFlags) +{ + UHoudiniParameter* NewParameter = Cast(StaticDuplicateObject(this, DestOuter)); + + NewParameter->CopyStateFrom(this, false); + + return NewParameter; +} + +void +UHoudiniParameter::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + + NodeId = InParameter->NodeId; + ParmId = InParameter->ParmId; + ParentParmId = InParameter->ParentParmId; + bIsDefault = InParameter->bIsDefault; + bPendingRevertToDefault = InParameter->bPendingRevertToDefault; + TuplePendingRevertToDefault = InParameter->TuplePendingRevertToDefault; + bShowExpression = InParameter->bShowExpression; +} + +void +UHoudiniParameter::InvalidateData() +{ + +} + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h index 033cd3a29..f829e7ec9 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameter.h @@ -1,330 +1,319 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "UObject/Object.h" -#include "Curves/RealCurve.h" -#include "HoudiniInput.h" - -#include "HoudiniParameter.generated.h" - -UENUM() -enum class EHoudiniParameterType : uint8 -{ - Invalid, - - Button, - ButtonStrip, - Color, - ColorRamp, - File, - FileDir, - FileGeo, - FileImage, - Float, - FloatRamp, - Folder, - FolderList, - Input, - Int, - IntChoice, - Label, - MultiParm, - Separator, - String, - StringChoice, - StringAssetRef, - Toggle, -}; - -UENUM() -enum class EHoudiniRampInterpolationType : int8 -{ - InValid = -1, - - CONSTANT = 0, - LINEAR = 1, - CATMULL_ROM = 2, - MONOTONE_CUBIC = 3, - BEZIER = 4, - BSPLINE = 5, - HERMITE = 6 -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject -{ - -public: - - GENERATED_UCLASS_BODY() - - friend class UHoudiniAssetParameter; - - // - static UHoudiniParameter * Create(UObject* Outer, const FString& ParamName); - - // Equality, consider two param equals if they have the same name, type, tuple size and disabled status - bool operator==(const UHoudiniParameter& other) const - { - return ( TupleSize == other.TupleSize && ParmType == other.ParmType - && Name.Equals(other.Name) && bIsDisabled == other.bIsDisabled ); - } - - bool Matches(const UHoudiniParameter& other) const { return (*this == other); }; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - // Get parent parameter for this parameter. - virtual const FString & GetParameterName() const { return Name; }; - virtual const FString & GetParameterLabel() const { return Label; }; - virtual const FString GetParameterHelp() const { return Help; }; - - virtual EHoudiniParameterType GetParameterType() const { return ParmType; }; - virtual int32 GetTupleSize() const { return TupleSize; }; - virtual int32 GetNodeId() const { return NodeId; }; - virtual int32 GetParmId() const { return ParmId; }; - virtual int32 GetParentParmId() const { return ParentParmId; }; - virtual int32 GetChildIndex() const { return ChildIndex; }; - - virtual bool IsVisible() const { return bIsVisible; }; - virtual bool ShouldDisplay() const{ return bIsVisible && bIsParentFolderVisible && ParmType != EHoudiniParameterType::Invalid; }; - virtual bool IsDisabled() const { return bIsDisabled; }; - virtual bool HasChanged() const { return bHasChanged; }; - virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; - virtual bool IsDefault() const { return true; }; - virtual bool IsSpare() const { return bIsSpare; }; - virtual bool GetJoinNext() const { return bJoinNext; }; - virtual bool IsAutoUpdate() const { return bAutoUpdate; }; - - virtual int32 GetTagCount() const { return TagCount; }; - virtual int32 GetValueIndex() const { return ValueIndex; }; - - virtual TMap& GetTags() { return Tags; }; - - virtual bool IsChildParameter() const; - - virtual bool IsPendingRevertToDefault() const { return bPendingRevertToDefault; }; - virtual void GetTuplePendingRevertToDefault(TArray& OutArray) { OutArray = TuplePendingRevertToDefault; }; - - virtual bool HasExpression() const { return bHasExpression; }; - virtual bool IsShowingExpression() const { return bShowExpression; }; - virtual FString GetExpression() const { return ParamExpression; }; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - - // Set parent parameter for this parameter. - virtual void SetParameterName(const FString& InName) { Name = InName; }; - virtual void SetParameterLabel(const FString& InLabel) { Label = InLabel; }; - virtual void SetParameterHelp(const FString& InHelp) { Help = InHelp; }; - - virtual void SetParameterType(const EHoudiniParameterType& InType) { ParmType = InType; }; - virtual void SetTupleSize(const uint32& InTupleSize) { TupleSize = InTupleSize; }; - virtual void SetNodeId(const int32& InNodeId) { NodeId = InNodeId; }; - virtual void SetParmId(const int32& InParmId) { ParmId = InParmId; }; - virtual void SetParentParmId(const int32& InParentParmId) { ParentParmId = InParentParmId; }; - virtual void SetChildIndex(const int32& InChildIndex) { ChildIndex = InChildIndex; }; - - virtual void SetIsChildOfMultiParm(const bool& IsChildOfMultiParam) { bIsChildOfMultiParm = IsChildOfMultiParam; }; - virtual bool GetIsChildOfMultiParm() const { return bIsChildOfMultiParm; }; - - virtual void SetIsDirectChildOfMultiParm(const bool& IsDirectChildOfMultiParam) { bIsDirectChildOfMultiParm = IsDirectChildOfMultiParam; }; - virtual bool IsDirectChildOfMultiParm() const { return bIsDirectChildOfMultiParm; }; - - virtual void SetVisible(const bool& InIsVisible) { bIsVisible = InIsVisible; }; - virtual void SetVisibleParent(const bool& InIsVisible) { bIsParentFolderVisible = InIsVisible; }; - virtual void SetDisabled(const bool& InIsDisabled) { bIsDisabled = InIsDisabled; }; - virtual void SetDefault(const bool& InIsDefault) { bIsDefault = InIsDefault; }; - virtual void SetSpare(const bool& InIsSpare) { bIsSpare = InIsSpare; }; - virtual void SetJoinNext(const bool& InJoinNext) { bJoinNext = InJoinNext; }; - - virtual void SetTagCount(const uint32& InTagCount) { TagCount = InTagCount; }; - virtual void SetValueIndex(const uint32& InValueIndex) { ValueIndex = InValueIndex; }; - - virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; - virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; - virtual void RevertToDefault(); - virtual void RevertToDefault(const int32& TupleIndex); - virtual void MarkDefault(const bool& bInDefault); - - virtual void SetHasExpression(const bool& InHasExpression) { bHasExpression = InHasExpression; }; - virtual void SetShowExpression(const bool& InShowExpression) { bShowExpression = InShowExpression; }; - virtual void SetExpression(const FString& InParamExpression) { ParamExpression = InParamExpression; }; - - virtual void SetAutoUpdate(const bool& InAutoUpdate) { bAutoUpdate = InAutoUpdate; }; - - static FString GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType); - static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromInt(int32 InInt); - static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromString(const FString& InString); - - static ERichCurveInterpMode EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType); - - // Duplicate this object for state transfer between component instances and templates - UHoudiniParameter* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - // Replace any input references using the provided mapping - virtual void RemapInputs(const TMap& InputMapping) {}; - - // Replace any parameter references using the provided mapping - virtual void RemapParameters(const TMap& ParameterMapping) {}; - - // Invalidate ids - virtual void InvalidateData(); - - //------------------------------------------------------------------------------------------------ - // Notifications - //------------------------------------------------------------------------------------------------ - - virtual void OnPreCook() {}; - -protected: - - //--------------------------------------------------------------------------------------------- - // ParmInfos - //--------------------------------------------------------------------------------------------- - - // - UPROPERTY() - FString Name; - - // - UPROPERTY() - FString Label; - - // Unreal type of the parameter - UPROPERTY() - EHoudiniParameterType ParmType; - - // Tuple size. For scalar parameters this value is 1, but for vector parameters this value can be greater. - UPROPERTY() - uint32 TupleSize; - - // Node this parameter belongs to. - UPROPERTY(DuplicateTransient) - int32 NodeId; - - // Id of this parameter. - UPROPERTY(DuplicateTransient) - int32 ParmId; - - // Id of parent parameter, -1 if root is parent. - UPROPERTY(DuplicateTransient) - int32 ParentParmId; - - // Child index within its parent parameter. - UPROPERTY() - int32 ChildIndex; - - // - UPROPERTY() - bool bIsVisible; - - // Is visible in hierarchy. (e.g. parm can be visible, but containing folder is not) - UPROPERTY() - bool bIsParentFolderVisible; - - // - UPROPERTY() - bool bIsDisabled; - - // Is set to true if value of this parameter has been changed by user. - UPROPERTY() - bool bHasChanged; - - // Is set to true if value of this parameter will trigger an update of the asset - UPROPERTY() - bool bNeedsToTriggerUpdate; - - // Indicates that this parameter is still using its default value - UPROPERTY(Transient, DuplicateTransient) - bool bIsDefault; - - // Permissions for file parms - UPROPERTY() - bool bIsSpare; - - // - UPROPERTY() - bool bJoinNext; - - // - UPROPERTY() - bool bIsChildOfMultiParm; - - UPROPERTY() - bool bIsDirectChildOfMultiParm; - - // Indicates a parameter value needs to be reverted to its default - UPROPERTY(DuplicateTransient) - bool bPendingRevertToDefault; - - UPROPERTY(DuplicateTransient) - TArray TuplePendingRevertToDefault; - - // - UPROPERTY() - FString Help; - - // Number of tags on this parameter - UPROPERTY() - uint32 TagCount; - - // The index to use to look into the values array in order to retrieve the actual value(s) of this parameter. - UPROPERTY() - int32 ValueIndex; - - //------------------------------------------------------------------------------------------------------------------------- - // Expression - // TODO: Use tuple array for this - // Indicates the parameters has an expression value - UPROPERTY() - bool bHasExpression; - - // Indicates we are currently displaying the parameter's value - UPROPERTY(DuplicateTransient) - bool bShowExpression; - - // The parameter's expression - UPROPERTY() - FString ParamExpression; - - UPROPERTY() - TMap Tags; - - UPROPERTY() - bool bAutoUpdate = true; - - -}; - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/Object.h" +#include "Curves/RealCurve.h" +#include "HoudiniInput.h" +#include "HoudiniEngineRuntimeCommon.h" + +#include "HoudiniParameter.generated.h" + +UENUM() +enum class EHoudiniParameterType : uint8 +{ + Invalid, + + Button, + ButtonStrip, + Color, + ColorRamp, + File, + FileDir, + FileGeo, + FileImage, + Float, + FloatRamp, + Folder, + FolderList, + Input, + Int, + IntChoice, + Label, + MultiParm, + Separator, + String, + StringChoice, + StringAssetRef, + Toggle, +}; + + + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameter : public UObject +{ + +public: + + GENERATED_UCLASS_BODY() + + friend class UHoudiniAssetParameter; + + // + static UHoudiniParameter * Create(UObject* Outer, const FString& ParamName); + + // Equality, consider two param equals if they have the same name, type, tuple size and disabled status + bool operator==(const UHoudiniParameter& other) const + { + return ( TupleSize == other.TupleSize && ParmType == other.ParmType + && Name.Equals(other.Name) && bIsDisabled == other.bIsDisabled ); + } + + bool Matches(const UHoudiniParameter& other) const { return (*this == other); }; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + // Get parent parameter for this parameter. + virtual const FString & GetParameterName() const { return Name; }; + virtual const FString & GetParameterLabel() const { return Label; }; + virtual const FString GetParameterHelp() const { return Help; }; + + virtual EHoudiniParameterType GetParameterType() const { return ParmType; }; + virtual int32 GetTupleSize() const { return TupleSize; }; + virtual int32 GetNodeId() const { return NodeId; }; + virtual int32 GetParmId() const { return ParmId; }; + virtual int32 GetParentParmId() const { return ParentParmId; }; + virtual int32 GetChildIndex() const { return ChildIndex; }; + + virtual bool IsVisible() const { return bIsVisible; }; + virtual bool ShouldDisplay() const{ return bIsVisible && bIsParentFolderVisible && ParmType != EHoudiniParameterType::Invalid; }; + virtual bool IsDisabled() const { return bIsDisabled; }; + virtual bool HasChanged() const { return bHasChanged; }; + virtual bool NeedsToTriggerUpdate() const { return bNeedsToTriggerUpdate; }; + virtual bool IsDefault() const { return true; }; + virtual bool IsSpare() const { return bIsSpare; }; + virtual bool GetJoinNext() const { return bJoinNext; }; + virtual bool IsAutoUpdate() const { return bAutoUpdate; }; + + virtual int32 GetTagCount() const { return TagCount; }; + virtual int32 GetValueIndex() const { return ValueIndex; }; + + virtual TMap& GetTags() { return Tags; }; + + virtual bool IsChildParameter() const; + + virtual bool IsPendingRevertToDefault() const { return bPendingRevertToDefault; }; + virtual void GetTuplePendingRevertToDefault(TArray& OutArray) { OutArray = TuplePendingRevertToDefault; }; + + virtual bool HasExpression() const { return bHasExpression; }; + virtual bool IsShowingExpression() const { return bShowExpression; }; + virtual FString GetExpression() const { return ParamExpression; }; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + + // Set parent parameter for this parameter. + virtual void SetParameterName(const FString& InName) { Name = InName; }; + virtual void SetParameterLabel(const FString& InLabel) { Label = InLabel; }; + virtual void SetParameterHelp(const FString& InHelp) { Help = InHelp; }; + + virtual void SetParameterType(const EHoudiniParameterType& InType) { ParmType = InType; }; + virtual void SetTupleSize(const uint32& InTupleSize) { TupleSize = InTupleSize; }; + virtual void SetNodeId(const int32& InNodeId) { NodeId = InNodeId; }; + virtual void SetParmId(const int32& InParmId) { ParmId = InParmId; }; + virtual void SetParentParmId(const int32& InParentParmId) { ParentParmId = InParentParmId; }; + virtual void SetChildIndex(const int32& InChildIndex) { ChildIndex = InChildIndex; }; + + virtual void SetIsChildOfMultiParm(const bool& IsChildOfMultiParam) { bIsChildOfMultiParm = IsChildOfMultiParam; }; + virtual bool GetIsChildOfMultiParm() const { return bIsChildOfMultiParm; }; + + virtual void SetIsDirectChildOfMultiParm(const bool& IsDirectChildOfMultiParam) { bIsDirectChildOfMultiParm = IsDirectChildOfMultiParam; }; + virtual bool IsDirectChildOfMultiParm() const { return bIsDirectChildOfMultiParm; }; + + virtual void SetVisible(const bool& InIsVisible) { bIsVisible = InIsVisible; }; + virtual void SetVisibleParent(const bool& InIsVisible) { bIsParentFolderVisible = InIsVisible; }; + virtual void SetDisabled(const bool& InIsDisabled) { bIsDisabled = InIsDisabled; }; + virtual void SetDefault(const bool& InIsDefault) { bIsDefault = InIsDefault; }; + virtual void SetSpare(const bool& InIsSpare) { bIsSpare = InIsSpare; }; + virtual void SetJoinNext(const bool& InJoinNext) { bJoinNext = InJoinNext; }; + + virtual void SetTagCount(const uint32& InTagCount) { TagCount = InTagCount; }; + virtual void SetValueIndex(const uint32& InValueIndex) { ValueIndex = InValueIndex; }; + + virtual void MarkChanged(const bool& bInChanged) { bHasChanged = bInChanged; SetNeedsToTriggerUpdate(bInChanged); }; + virtual void SetNeedsToTriggerUpdate(const bool& bInTriggersUpdate) { bNeedsToTriggerUpdate = bInTriggersUpdate; }; + virtual void RevertToDefault(); + virtual void RevertToDefault(const int32& TupleIndex); + virtual void MarkDefault(const bool& bInDefault); + + virtual void SetHasExpression(const bool& InHasExpression) { bHasExpression = InHasExpression; }; + virtual void SetShowExpression(const bool& InShowExpression) { bShowExpression = InShowExpression; }; + virtual void SetExpression(const FString& InParamExpression) { ParamExpression = InParamExpression; }; + + virtual void SetAutoUpdate(const bool& InAutoUpdate) { bAutoUpdate = InAutoUpdate; }; + + static FString GetStringFromHoudiniInterpMethod(EHoudiniRampInterpolationType InType); + static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromInt(int32 InInt); + static EHoudiniRampInterpolationType GetHoudiniInterpMethodFromString(const FString& InString); + + static ERichCurveInterpMode EHoudiniRampInterpolationTypeToERichCurveInterpMode(EHoudiniRampInterpolationType InType); + + // Duplicate this object for state transfer between component instances and templates + UHoudiniParameter* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + // Replace any input references using the provided mapping + virtual void RemapInputs(const TMap& InputMapping) {}; + + // Replace any parameter references using the provided mapping + virtual void RemapParameters(const TMap& ParameterMapping) {}; + + // Invalidate ids + virtual void InvalidateData(); + + //------------------------------------------------------------------------------------------------ + // Notifications + //------------------------------------------------------------------------------------------------ + + virtual void OnPreCook() {}; + +protected: + + //--------------------------------------------------------------------------------------------- + // ParmInfos + //--------------------------------------------------------------------------------------------- + + // + UPROPERTY() + FString Name; + + // + UPROPERTY() + FString Label; + + // Unreal type of the parameter + UPROPERTY() + EHoudiniParameterType ParmType; + + // Tuple size. For scalar parameters this value is 1, but for vector parameters this value can be greater. + UPROPERTY() + uint32 TupleSize; + + // Node this parameter belongs to. + UPROPERTY(DuplicateTransient) + int32 NodeId; + + // Id of this parameter. + UPROPERTY(DuplicateTransient) + int32 ParmId; + + // Id of parent parameter, -1 if root is parent. + UPROPERTY(DuplicateTransient) + int32 ParentParmId; + + // Child index within its parent parameter. + UPROPERTY() + int32 ChildIndex; + + // + UPROPERTY() + bool bIsVisible; + + // Is visible in hierarchy. (e.g. parm can be visible, but containing folder is not) + UPROPERTY() + bool bIsParentFolderVisible; + + // + UPROPERTY() + bool bIsDisabled; + + // Is set to true if value of this parameter has been changed by user. + UPROPERTY() + bool bHasChanged; + + // Is set to true if value of this parameter will trigger an update of the asset + UPROPERTY() + bool bNeedsToTriggerUpdate; + + // Indicates that this parameter is still using its default value + UPROPERTY(Transient, DuplicateTransient) + bool bIsDefault; + + // Permissions for file parms + UPROPERTY() + bool bIsSpare; + + // + UPROPERTY() + bool bJoinNext; + + // + UPROPERTY() + bool bIsChildOfMultiParm; + + UPROPERTY() + bool bIsDirectChildOfMultiParm; + + // Indicates a parameter value needs to be reverted to its default + UPROPERTY(DuplicateTransient) + bool bPendingRevertToDefault; + + UPROPERTY(DuplicateTransient) + TArray TuplePendingRevertToDefault; + + // + UPROPERTY() + FString Help; + + // Number of tags on this parameter + UPROPERTY() + uint32 TagCount; + + // The index to use to look into the values array in order to retrieve the actual value(s) of this parameter. + UPROPERTY() + int32 ValueIndex; + + //------------------------------------------------------------------------------------------------------------------------- + // Expression + // TODO: Use tuple array for this + // Indicates the parameters has an expression value + UPROPERTY() + bool bHasExpression; + + // Indicates we are currently displaying the parameter's value + UPROPERTY(DuplicateTransient) + bool bShowExpression; + + // The parameter's expression + UPROPERTY() + FString ParamExpression; + + UPROPERTY() + TMap Tags; + + UPROPERTY() + bool bAutoUpdate = true; + + +}; + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp index f190cfc8f..269c23cc6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.cpp @@ -1,50 +1,50 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterButton.h" - -UHoudiniParameterButton::UHoudiniParameterButton(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Button; -} - -UHoudiniParameterButton * -UHoudiniParameterButton::Create( UObject* InOuter, const FString& InParamName ) -{ - FString ParamNameStr = "HoudiniParameterButton_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButton::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterButton * HoudiniAssetParameter = NewObject< UHoudiniParameterButton >( - InOuter, UHoudiniParameterButton::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Button); - - //HoudiniAssetParameterButton->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterButton.h" + +UHoudiniParameterButton::UHoudiniParameterButton(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Button; +} + +UHoudiniParameterButton * +UHoudiniParameterButton::Create( UObject* InOuter, const FString& InParamName ) +{ + FString ParamNameStr = "HoudiniParameterButton_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButton::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterButton * HoudiniAssetParameter = NewObject< UHoudiniParameterButton >( + InOuter, UHoudiniParameterButton::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Button); + + //HoudiniAssetParameterButton->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h index 875788fad..e35ee513e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButton.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterButton.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterButton : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterButton * Create( - UObject* Outer, - const FString& ParamName); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterButton.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterButton : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterButton * Create( + UObject* Outer, + const FString& ParamName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp index efe606496..785c2fc90 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.cpp @@ -1,74 +1,74 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterButtonStrip.h" - -UHoudiniParameterButtonStrip::UHoudiniParameterButtonStrip(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::ButtonStrip; -} - -UHoudiniParameterButtonStrip * -UHoudiniParameterButtonStrip::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterButton_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButtonStrip::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterButtonStrip * HoudiniAssetParameter = NewObject< UHoudiniParameterButtonStrip >( - InOuter, UHoudiniParameterButtonStrip::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::ButtonStrip); - - HoudiniAssetParameter->Count = 0; - - return HoudiniAssetParameter; -} - -FString * -UHoudiniParameterButtonStrip::GetStringLabelAt(const int32 & InIndex) -{ - if (!Labels.IsValidIndex(InIndex)) - return nullptr; - - return &(Labels[InIndex]); -} - -bool -UHoudiniParameterButtonStrip::SetValueAt(const int32 & InIdx, int32 InVal) -{ - if (!Values.IsValidIndex(InIdx)) - return false; - - if (Values[InIdx] == InVal) - return false; - - Values[InIdx] = InVal; - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterButtonStrip.h" + +UHoudiniParameterButtonStrip::UHoudiniParameterButtonStrip(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::ButtonStrip; +} + +UHoudiniParameterButtonStrip * +UHoudiniParameterButtonStrip::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterButton_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterButtonStrip::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterButtonStrip * HoudiniAssetParameter = NewObject< UHoudiniParameterButtonStrip >( + InOuter, UHoudiniParameterButtonStrip::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::ButtonStrip); + + HoudiniAssetParameter->Count = 0; + + return HoudiniAssetParameter; +} + +FString * +UHoudiniParameterButtonStrip::GetStringLabelAt(const int32 & InIndex) +{ + if (!Labels.IsValidIndex(InIndex)) + return nullptr; + + return &(Labels[InIndex]); +} + +bool +UHoudiniParameterButtonStrip::SetValueAt(const int32 & InIdx, int32 InVal) +{ + if (!Values.IsValidIndex(InIdx)) + return false; + + if (Values[InIdx] == InVal) + return false; + + Values[InIdx] = InVal; + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h index 13c5d2347..a515e51cb 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterButtonStrip.h @@ -1,66 +1,66 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterButtonStrip.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterButtonStrip : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterButtonStrip * Create( - UObject* InOuter, - const FString& InParamName); - - UPROPERTY() - int32 Count; - - UPROPERTY() - TArray Labels; - - UPROPERTY() - TArray Values; - - - void InitializeLabels(const int32 & InSize) { Labels.SetNumZeroed(InSize); Values.SetNumZeroed(InSize); Count = InSize; }; - - bool SetValueAt(const int32 & InIdx, int32 InVal); - - - FString * GetStringLabelAt(const int32 & InIndex); - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterButtonStrip.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterButtonStrip : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterButtonStrip * Create( + UObject* InOuter, + const FString& InParamName); + + UPROPERTY() + int32 Count; + + UPROPERTY() + TArray Labels; + + UPROPERTY() + TArray Values; + + + void InitializeLabels(const int32 & InSize) { Labels.SetNumZeroed(InSize); Values.SetNumZeroed(InSize); Count = InSize; }; + + bool SetValueAt(const int32 & InIdx, int32 InVal); + + + FString * GetStringLabelAt(const int32 & InIndex); + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp index efca80766..86a5c9c22 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.cpp @@ -1,269 +1,269 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterChoice.h" - -UHoudiniParameterChoice::UHoudiniParameterChoice(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , IntValue(-1) -{ - ParmType = EHoudiniParameterType::IntChoice; -} - -UHoudiniParameterChoice * -UHoudiniParameterChoice::Create( UObject* InOuter, const FString& InParamName, const EHoudiniParameterType& InParmType) -{ - FString ParamNameStr = "HoudiniParameterChoice_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterChoice::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterChoice * HoudiniAssetParameter = NewObject< UHoudiniParameterChoice >( - InOuter, UHoudiniParameterChoice::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(InParmType); - - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -void -UHoudiniParameterChoice::BeginDestroy() -{ - // We need to clean up our arrays - for (auto& Ptr : ChoiceLabelsPtr) - { - Ptr.Reset(); - } - ChoiceLabelsPtr.Empty(); - - // Then the string arrays - StringChoiceLabels.Empty(); - StringChoiceValues.Empty(); - - IntValuesArray.Empty(); - - Super::BeginDestroy(); -} - - -FString* -UHoudiniParameterChoice::GetStringChoiceValueAt(const int32& InAtIndex) -{ - if (!StringChoiceValues.IsValidIndex(InAtIndex)) - return nullptr; - - return &(StringChoiceValues[InAtIndex]); -} - -FString* -UHoudiniParameterChoice::GetStringChoiceLabelAt(const int32& InAtIndex) -{ - if (!StringChoiceLabels.IsValidIndex(InAtIndex)) - return nullptr; - - return &(StringChoiceLabels[InAtIndex]); -} - -void -UHoudiniParameterChoice::SetNumChoices(const int32& InNumChoices) -{ - // Set the array sizes - StringChoiceValues.SetNumZeroed(InNumChoices); - StringChoiceLabels.SetNumZeroed(InNumChoices); - - IntValuesArray.SetNumZeroed(InNumChoices); - - UpdateChoiceLabelsPtr(); -} - -// Update the pointers to the ChoiceLabels -bool -UHoudiniParameterChoice::UpdateChoiceLabelsPtr() -{ - /* - bool bNeedUpdate = false; - if (StringChoiceLabels.Num() != ChoiceLabelsPtr.Num()) - { - bNeedUpdate = true; - } - else - { - for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) - { - if (ChoiceLabelsPtr[Idx].Get() == &(StringChoiceLabels[Idx])) - continue; - - bNeedUpdate = true; - break; - } - } - - if (!bNeedUpdate) - return true; - */ - - // Updates the Label Ptr array - ChoiceLabelsPtr.SetNumZeroed(StringChoiceLabels.Num()); - for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) - { - ChoiceLabelsPtr[Idx] = MakeShared(StringChoiceLabels[Idx]); - } - - return true; -} - -bool -UHoudiniParameterChoice::SetIntValue(const int32& InIntValue) -{ - if (InIntValue == IntValue) - return false; - - IntValue = InIntValue; - - return true; -} - -bool -UHoudiniParameterChoice::SetStringValue(const FString& InStringValue) -{ - if (InStringValue.Equals(StringValue)) - return false; - - StringValue = InStringValue; - - return true; -}; - -bool -UHoudiniParameterChoice::UpdateIntValueFromString() -{ - int32 FoundInt = INDEX_NONE; - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - // Update the int values from the string value - FoundInt = StringChoiceLabels.Find(StringValue); - } - else - { - // Update the int values from the string value - FoundInt = StringChoiceValues.Find(StringValue); - } - - if (FoundInt == INDEX_NONE) - return false; - - if (IntValue == FoundInt) - return false; - - IntValue = FoundInt; - - return true; -} - -bool -UHoudiniParameterChoice::UpdateStringValueFromInt() -{ - // Update the string value from the int value - FString NewStringValue; - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - // IntChoices only have labels - if (!StringChoiceLabels.IsValidIndex(IntValue)) - return false; - - NewStringValue = StringChoiceLabels[IntValue]; - } - else - { - // StringChoices should use values - if (!StringChoiceValues.IsValidIndex(IntValue)) - return false; - - NewStringValue = StringChoiceValues[IntValue]; - } - - if (StringValue.Equals(NewStringValue)) - return false; - - StringValue = NewStringValue; - - return true; -} - -const int32 -UHoudiniParameterChoice::GetIntValueFromLabel(const FString& InSelectedLabel) const -{ - return StringChoiceLabels.Find(InSelectedLabel); -} - -TOptional< TSharedPtr > -UHoudiniParameterChoice::GetValue(int32 Idx) const -{ - if (Idx == 0 && StringChoiceValues.IsValidIndex(IntValue)) - { - return TOptional< TSharedPtr< FString > >(MakeShared(StringChoiceValues[IntValue])); - } - - return TOptional< TSharedPtr< FString > >(); -} - -bool -UHoudiniParameterChoice::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - if (GetParameterType() == EHoudiniParameterType::IntChoice) - { - return IntValue == DefaultIntValue; - } - - if (GetParameterType() == EHoudiniParameterType::StringChoice) - { - return StringValue == DefaultStringValue; - } - - return true; -} - -void -UHoudiniParameterChoice::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } -} - -int32 UHoudiniParameterChoice::GetIndexFromValueArray(int32 Index) const -{ - return IntValuesArray.Find(Index); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterChoice.h" + +UHoudiniParameterChoice::UHoudiniParameterChoice(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , IntValue(-1) +{ + ParmType = EHoudiniParameterType::IntChoice; +} + +UHoudiniParameterChoice * +UHoudiniParameterChoice::Create( UObject* InOuter, const FString& InParamName, const EHoudiniParameterType& InParmType) +{ + FString ParamNameStr = "HoudiniParameterChoice_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterChoice::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterChoice * HoudiniAssetParameter = NewObject< UHoudiniParameterChoice >( + InOuter, UHoudiniParameterChoice::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(InParmType); + + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +void +UHoudiniParameterChoice::BeginDestroy() +{ + // We need to clean up our arrays + for (auto& Ptr : ChoiceLabelsPtr) + { + Ptr.Reset(); + } + ChoiceLabelsPtr.Empty(); + + // Then the string arrays + StringChoiceLabels.Empty(); + StringChoiceValues.Empty(); + + IntValuesArray.Empty(); + + Super::BeginDestroy(); +} + + +FString* +UHoudiniParameterChoice::GetStringChoiceValueAt(const int32& InAtIndex) +{ + if (!StringChoiceValues.IsValidIndex(InAtIndex)) + return nullptr; + + return &(StringChoiceValues[InAtIndex]); +} + +FString* +UHoudiniParameterChoice::GetStringChoiceLabelAt(const int32& InAtIndex) +{ + if (!StringChoiceLabels.IsValidIndex(InAtIndex)) + return nullptr; + + return &(StringChoiceLabels[InAtIndex]); +} + +void +UHoudiniParameterChoice::SetNumChoices(const int32& InNumChoices) +{ + // Set the array sizes + StringChoiceValues.SetNumZeroed(InNumChoices); + StringChoiceLabels.SetNumZeroed(InNumChoices); + + IntValuesArray.SetNumZeroed(InNumChoices); + + UpdateChoiceLabelsPtr(); +} + +// Update the pointers to the ChoiceLabels +bool +UHoudiniParameterChoice::UpdateChoiceLabelsPtr() +{ + /* + bool bNeedUpdate = false; + if (StringChoiceLabels.Num() != ChoiceLabelsPtr.Num()) + { + bNeedUpdate = true; + } + else + { + for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) + { + if (ChoiceLabelsPtr[Idx].Get() == &(StringChoiceLabels[Idx])) + continue; + + bNeedUpdate = true; + break; + } + } + + if (!bNeedUpdate) + return true; + */ + + // Updates the Label Ptr array + ChoiceLabelsPtr.SetNumZeroed(StringChoiceLabels.Num()); + for (int32 Idx = 0; Idx < ChoiceLabelsPtr.Num(); Idx++) + { + ChoiceLabelsPtr[Idx] = MakeShared(StringChoiceLabels[Idx]); + } + + return true; +} + +bool +UHoudiniParameterChoice::SetIntValue(const int32& InIntValue) +{ + if (InIntValue == IntValue) + return false; + + IntValue = InIntValue; + + return true; +} + +bool +UHoudiniParameterChoice::SetStringValue(const FString& InStringValue) +{ + if (InStringValue.Equals(StringValue)) + return false; + + StringValue = InStringValue; + + return true; +}; + +bool +UHoudiniParameterChoice::UpdateIntValueFromString() +{ + int32 FoundInt = INDEX_NONE; + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + // Update the int values from the string value + FoundInt = StringChoiceLabels.Find(StringValue); + } + else + { + // Update the int values from the string value + FoundInt = StringChoiceValues.Find(StringValue); + } + + if (FoundInt == INDEX_NONE) + return false; + + if (IntValue == FoundInt) + return false; + + IntValue = FoundInt; + + return true; +} + +bool +UHoudiniParameterChoice::UpdateStringValueFromInt() +{ + // Update the string value from the int value + FString NewStringValue; + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + // IntChoices only have labels + if (!StringChoiceLabels.IsValidIndex(IntValue)) + return false; + + NewStringValue = StringChoiceLabels[IntValue]; + } + else + { + // StringChoices should use values + if (!StringChoiceValues.IsValidIndex(IntValue)) + return false; + + NewStringValue = StringChoiceValues[IntValue]; + } + + if (StringValue.Equals(NewStringValue)) + return false; + + StringValue = NewStringValue; + + return true; +} + +const int32 +UHoudiniParameterChoice::GetIntValueFromLabel(const FString& InSelectedLabel) const +{ + return StringChoiceLabels.Find(InSelectedLabel); +} + +TOptional< TSharedPtr > +UHoudiniParameterChoice::GetValue(int32 Idx) const +{ + if (Idx == 0 && StringChoiceValues.IsValidIndex(IntValue)) + { + return TOptional< TSharedPtr< FString > >(MakeShared(StringChoiceValues[IntValue])); + } + + return TOptional< TSharedPtr< FString > >(); +} + +bool +UHoudiniParameterChoice::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + if (GetParameterType() == EHoudiniParameterType::IntChoice) + { + return IntValue == DefaultIntValue; + } + + if (GetParameterType() == EHoudiniParameterType::StringChoice) + { + return StringValue == DefaultStringValue; + } + + return true; +} + +void +UHoudiniParameterChoice::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } +} + +int32 UHoudiniParameterChoice::GetIndexFromValueArray(int32 Index) const +{ + return IntValuesArray.Find(Index); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h index 55f89b49e..d6ca3fda2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterChoice.h @@ -1,137 +1,137 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterChoice.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create an instance of this class. - static UHoudiniParameterChoice * Create( - UObject* Outer, - const FString& ParamName, - const EHoudiniParameterType& ParmType); - - virtual void BeginDestroy() override; - - //------------------------------------------------------------------------------------------------ - // Accessors - //------------------------------------------------------------------------------------------------ - - const int32 GetIntValueIndex() const { return IntValue; }; - const FString GetStringValue() const { return StringValue; }; - const int32 GetNumChoices() const { return StringChoiceLabels.Num(); }; - const FString GetLabel() const { return StringChoiceLabels.IsValidIndex(IntValue) ? StringChoiceLabels[IntValue] : FString(); }; - const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; - const int32 GetIntValue(int32 InIntValueIndex) { return IntValuesArray.IsValidIndex(InIntValueIndex) ? IntValuesArray[InIntValueIndex] : IntValue; } - void SetIntValueArray(int32 Index, int32 Value) { if (IntValuesArray.IsValidIndex(Index)) IntValuesArray[Index] = Value; } - - bool IsDefault() const override; - - TOptional> GetValue(int32 Idx) const; - - const int32 GetIntValueFromLabel(const FString& InSelectedLabel) const; - - FString* GetStringChoiceValueAt(const int32& InAtIndex); - FString* GetStringChoiceLabelAt(const int32& InAtIndex); - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Returns the ChoiceLabel SharedPtr array, used for UI only - TArray>* GetChoiceLabelsPtr() { return &ChoiceLabelsPtr; }; - - //------------------------------------------------------------------------------------------------ - // Mutators - //------------------------------------------------------------------------------------------------ - bool SetIntValue(const int32& InIntValue); - bool SetStringValue(const FString& InStringValue); - void SetNumChoices(const int32& InNumChoices); - - // For string choices only, update the int values from the string value - bool UpdateIntValueFromString(); - // For int choices only, update the string value from the int value - bool UpdateStringValueFromInt(); - // Update the pointers to the ChoiceLabels - bool UpdateChoiceLabelsPtr(); - - void SetDefaultIntValue() { DefaultIntValue = IntValue; }; - void SetDefaultStringValue() { DefaultStringValue = StringValue; }; - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - void RevertToDefault() override; - - int32 GetIndexFromValueArray(int32 Index) const; - -protected: - - // Current int value for this property. - // More of an index to IntValuesArray - UPROPERTY() - int32 IntValue; - - // Default int value for this property, assigned at creating the parameter. - UPROPERTY() - int32 DefaultIntValue; - - // Current string value for this property - UPROPERTY() - FString StringValue; - - // Default string value for this property, assigned at creating the parameter. - UPROPERTY() - FString DefaultStringValue; - - // Used only for StringChoices! - // All the possible string values for this parameter's choices - UPROPERTY() - TArray StringChoiceValues; - - // Labels corresponding to this parameter's choices. - UPROPERTY() - TArray StringChoiceLabels; - - // Array of SharedPtr pointing to this parameter's label, used for UI only - TArray> ChoiceLabelsPtr; - - UPROPERTY() - bool bIsChildOfRamp; - - // An array containing the values of all choices - // IntValues[i] should be i unless UseMenuItemTokenAsValue is enabled. - UPROPERTY() - TArray IntValuesArray; - - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterChoice.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterChoice : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create an instance of this class. + static UHoudiniParameterChoice * Create( + UObject* Outer, + const FString& ParamName, + const EHoudiniParameterType& ParmType); + + virtual void BeginDestroy() override; + + //------------------------------------------------------------------------------------------------ + // Accessors + //------------------------------------------------------------------------------------------------ + + const int32 GetIntValueIndex() const { return IntValue; }; + const FString GetStringValue() const { return StringValue; }; + const int32 GetNumChoices() const { return StringChoiceLabels.Num(); }; + const FString GetLabel() const { return StringChoiceLabels.IsValidIndex(IntValue) ? StringChoiceLabels[IntValue] : FString(); }; + const bool IsStringChoice() const { return ParmType == EHoudiniParameterType::StringChoice; }; + const int32 GetIntValue(int32 InIntValueIndex) { return IntValuesArray.IsValidIndex(InIntValueIndex) ? IntValuesArray[InIntValueIndex] : IntValue; } + void SetIntValueArray(int32 Index, int32 Value) { if (IntValuesArray.IsValidIndex(Index)) IntValuesArray[Index] = Value; } + + bool IsDefault() const override; + + TOptional> GetValue(int32 Idx) const; + + const int32 GetIntValueFromLabel(const FString& InSelectedLabel) const; + + FString* GetStringChoiceValueAt(const int32& InAtIndex); + FString* GetStringChoiceLabelAt(const int32& InAtIndex); + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Returns the ChoiceLabel SharedPtr array, used for UI only + TArray>* GetChoiceLabelsPtr() { return &ChoiceLabelsPtr; }; + + //------------------------------------------------------------------------------------------------ + // Mutators + //------------------------------------------------------------------------------------------------ + bool SetIntValue(const int32& InIntValue); + bool SetStringValue(const FString& InStringValue); + void SetNumChoices(const int32& InNumChoices); + + // For string choices only, update the int values from the string value + bool UpdateIntValueFromString(); + // For int choices only, update the string value from the int value + bool UpdateStringValueFromInt(); + // Update the pointers to the ChoiceLabels + bool UpdateChoiceLabelsPtr(); + + void SetDefaultIntValue() { DefaultIntValue = IntValue; }; + void SetDefaultStringValue() { DefaultStringValue = StringValue; }; + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + void RevertToDefault() override; + + int32 GetIndexFromValueArray(int32 Index) const; + +protected: + + // Current int value for this property. + // More of an index to IntValuesArray + UPROPERTY() + int32 IntValue; + + // Default int value for this property, assigned at creating the parameter. + UPROPERTY() + int32 DefaultIntValue; + + // Current string value for this property + UPROPERTY() + FString StringValue; + + // Default string value for this property, assigned at creating the parameter. + UPROPERTY() + FString DefaultStringValue; + + // Used only for StringChoices! + // All the possible string values for this parameter's choices + UPROPERTY() + TArray StringChoiceValues; + + // Labels corresponding to this parameter's choices. + UPROPERTY() + TArray StringChoiceLabels; + + // Array of SharedPtr pointing to this parameter's label, used for UI only + TArray> ChoiceLabelsPtr; + + UPROPERTY() + bool bIsChildOfRamp; + + // An array containing the values of all choices + // IntValues[i] should be i unless UseMenuItemTokenAsValue is enabled. + UPROPERTY() + TArray IntValuesArray; + + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp index f97df8433..e981690ce 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.cpp @@ -1,84 +1,84 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterColor.h" - -UHoudiniParameterColor::UHoudiniParameterColor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Color( FLinearColor::White ) -{ - ParmType = EHoudiniParameterType::Color; -} - -UHoudiniParameterColor * -UHoudiniParameterColor::Create( UObject* InOuter, const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterColor_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterColor::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterColor * HoudiniAssetParameter = NewObject< UHoudiniParameterColor >( - InOuter, UHoudiniParameterColor::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Color); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->bIsChildOfRamp = false; - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterColor::SetColorValue(const FLinearColor& InColor) -{ - if (InColor == Color) - return false; - - Color = InColor; - - return true; -} - -bool -UHoudiniParameterColor::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - return Color == DefaultColor; -} - -void -UHoudiniParameterColor::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterColor.h" + +UHoudiniParameterColor::UHoudiniParameterColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Color( FLinearColor::White ) +{ + ParmType = EHoudiniParameterType::Color; +} + +UHoudiniParameterColor * +UHoudiniParameterColor::Create( UObject* InOuter, const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterColor_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterColor::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterColor * HoudiniAssetParameter = NewObject< UHoudiniParameterColor >( + InOuter, UHoudiniParameterColor::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Color); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->bIsChildOfRamp = false; + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterColor::SetColorValue(const FLinearColor& InColor) +{ + if (InColor == Color) + return false; + + Color = InColor; + + return true; +} + +bool +UHoudiniParameterColor::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + return Color == DefaultColor; +} + +void +UHoudiniParameterColor::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h index 06e115bae..7224b183f 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterColor.h @@ -1,73 +1,73 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterColor.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterColor : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - - public: - - // Create instance of this class. - static UHoudiniParameterColor * Create( - UObject* Outer, - const FString& ParamName); - - // Accessor - FLinearColor GetColorValue() const { return Color; }; - - bool IsDefault() const override; - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Mutators - bool SetColorValue(const FLinearColor& InColor); - - void SetDefaultValue() { DefaultColor = Color; }; - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - void RevertToDefault() override; - - protected: - - // Color for this property. - UPROPERTY() - FLinearColor Color; - - // Default color for this property - UPROPERTY() - FLinearColor DefaultColor; - - UPROPERTY() - bool bIsChildOfRamp; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterColor.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterColor : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + + public: + + // Create instance of this class. + static UHoudiniParameterColor * Create( + UObject* Outer, + const FString& ParamName); + + // Accessor + FLinearColor GetColorValue() const { return Color; }; + + bool IsDefault() const override; + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Mutators + bool SetColorValue(const FLinearColor& InColor); + + void SetDefaultValue() { DefaultColor = Color; }; + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + void RevertToDefault() override; + + protected: + + // Color for this property. + UPROPERTY() + FLinearColor Color; + + // Default color for this property + UPROPERTY() + FLinearColor DefaultColor; + + UPROPERTY() + bool bIsChildOfRamp; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp index 9779b1f15..c474d3cf9 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.cpp @@ -1,101 +1,101 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFile.h" - -#include "HoudiniAsset.h" -#include "HoudiniAssetComponent.h" - -#include "Misc/Paths.h" - -UHoudiniParameterFile::UHoudiniParameterFile(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Filters() - , bIsReadOnly(false) -{ - ParmType = EHoudiniParameterType::File; -} - -UHoudiniParameterFile * -UHoudiniParameterFile::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFile_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFile::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFile * HoudiniAssetParameter = NewObject< UHoudiniParameterFile >( - InOuter, UHoudiniParameterFile::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::File); - - return HoudiniAssetParameter; -} - - -bool -UHoudiniParameterFile::SetValueAt(const FString& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index] == InValue) - return false; - - Values[Index] = InValue; - - return true; -} - -bool -UHoudiniParameterFile::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!DefaultValues.IsValidIndex(Idx)) - break; - - if (Values[Idx] != DefaultValues[Idx]) - return false; - } - - return true; -} - -void -UHoudiniParameterFile::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFile.h" + +#include "HoudiniAsset.h" +#include "HoudiniAssetComponent.h" + +#include "Misc/Paths.h" + +UHoudiniParameterFile::UHoudiniParameterFile(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Filters() + , bIsReadOnly(false) +{ + ParmType = EHoudiniParameterType::File; +} + +UHoudiniParameterFile * +UHoudiniParameterFile::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFile_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFile::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFile * HoudiniAssetParameter = NewObject< UHoudiniParameterFile >( + InOuter, UHoudiniParameterFile::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::File); + + return HoudiniAssetParameter; +} + + +bool +UHoudiniParameterFile::SetValueAt(const FString& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index] == InValue) + return false; + + Values[Index] = InValue; + + return true; +} + +bool +UHoudiniParameterFile::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!DefaultValues.IsValidIndex(Idx)) + break; + + if (Values[Idx] != DefaultValues[Idx]) + return false; + } + + return true; +} + +void +UHoudiniParameterFile::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h index 0b0e57fd0..00a4ea8b6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFile.h @@ -1,78 +1,78 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFile.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFile : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFile * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetFileFilters() const { return Filters; }; - bool IsReadOnly() const { return bIsReadOnly; }; - FString GetValueAt(int32 Index) { return Values[Index]; }; - int32 GetNumValues() { return Values.Num(); }; - - void SetNumberOfValues(const uint32& NumValues) { Values.SetNum(NumValues); }; - bool SetValueAt(const FString& InValue, const uint32& Index); - - bool IsDefault() const override; - - // Mutators - void SetFileFilters(const FString& InFilters) { Filters = InFilters; }; - void SetReadOnly(const bool& InReadOnly) { bIsReadOnly = InReadOnly; }; - - void SetDefaultValues(); - -protected: - - // Values of this property. - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; - - // Filters of this property. - UPROPERTY() - FString Filters; - - // Is the file parameter read-only? - UPROPERTY() - bool bIsReadOnly; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFile.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFile : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFile * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetFileFilters() const { return Filters; }; + bool IsReadOnly() const { return bIsReadOnly; }; + FString GetValueAt(int32 Index) { return Values[Index]; }; + int32 GetNumValues() { return Values.Num(); }; + + void SetNumberOfValues(const uint32& NumValues) { Values.SetNum(NumValues); }; + bool SetValueAt(const FString& InValue, const uint32& Index); + + bool IsDefault() const override; + + // Mutators + void SetFileFilters(const FString& InFilters) { Filters = InFilters; }; + void SetReadOnly(const bool& InReadOnly) { bIsReadOnly = InReadOnly; }; + + void SetDefaultValues(); + +protected: + + // Values of this property. + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; + + // Filters of this property. + UPROPERTY() + FString Filters; + + // Is the file parameter read-only? + UPROPERTY() + bool bIsReadOnly; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp index 961a01a24..2cb089f1e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.cpp @@ -1,169 +1,169 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFloat.h" - -UHoudiniParameterFloat::UHoudiniParameterFloat(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Unit(TEXT("")) - , bNoSwap(false) - , bHasMin(false) - , bHasMax(false) - , bHasUIMin(false) - , bHasUIMax(false) - , bIsLogarithmic(false) - , Min(TNumericLimits::Lowest()) - , Max(TNumericLimits::Max()) - , UIMin(TNumericLimits::Lowest()) - , UIMax(TNumericLimits::Max()) -{ - ParmType = EHoudiniParameterType::Float; -} - -UHoudiniParameterFloat * -UHoudiniParameterFloat::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFloat_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFloat::StaticClass(), *ParamNameStr); - // We need to create a new parameter - UHoudiniParameterFloat * HoudiniAssetParameter = NewObject< UHoudiniParameterFloat >( - InOuter, UHoudiniParameterFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); - - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Float); - - HoudiniAssetParameter->bIsChildOfRamp = false; - HoudiniAssetParameter->bIsLogarithmic = false; - return HoudiniAssetParameter; -} - -TOptional< float > -UHoudiniParameterFloat::GetValue(int32 Idx) const -{ - if (Values.IsValidIndex(Idx)) - return TOptional< float >(Values[Idx]); - else - return TOptional< float >(); -} - -bool -UHoudiniParameterFloat::GetValueAt(const int32& AtIndex, float& OutValue) const -{ - if (!Values.IsValidIndex(AtIndex)) - return false; - - OutValue = Values[AtIndex]; - - return true; -} - -bool -UHoudiniParameterFloat::SetValueAt(const float& InValue, const int32& AtIndex) -{ - if (!Values.IsValidIndex(AtIndex)) - return false; - - if (InValue == Values[AtIndex]) - return false; - - Values[AtIndex] = FMath::Clamp< float >(InValue, Min, Max); - - return true; -} - -void -UHoudiniParameterFloat::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterFloat::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterFloat::IsDefault() const -{ - if (bIsChildOfRamp) - return true; - - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; -} - -void -UHoudiniParameterFloat::SetValue(float InValue, int32 Idx) -{ - if (!Values.IsValidIndex(Idx)) - return; - - if (InValue == Values[Idx]) - return; - - Values[Idx] = FMath::Clamp< float >(InValue, Min, Max); -} - -void -UHoudiniParameterFloat::RevertToDefault() -{ - if (!bIsChildOfRamp) - { - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.Empty(); - TuplePendingRevertToDefault.Add(-1); - - MarkChanged(true); - } -} - -void -UHoudiniParameterFloat::RevertToDefault(const int32& TupleIndex) -{ - bPendingRevertToDefault = true; - TuplePendingRevertToDefault.AddUnique(TupleIndex); - - MarkChanged(true); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFloat.h" + +UHoudiniParameterFloat::UHoudiniParameterFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Unit(TEXT("")) + , bNoSwap(false) + , bHasMin(false) + , bHasMax(false) + , bHasUIMin(false) + , bHasUIMax(false) + , bIsLogarithmic(false) + , Min(TNumericLimits::Lowest()) + , Max(TNumericLimits::Max()) + , UIMin(TNumericLimits::Lowest()) + , UIMax(TNumericLimits::Max()) +{ + ParmType = EHoudiniParameterType::Float; +} + +UHoudiniParameterFloat * +UHoudiniParameterFloat::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFloat_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFloat::StaticClass(), *ParamNameStr); + // We need to create a new parameter + UHoudiniParameterFloat * HoudiniAssetParameter = NewObject< UHoudiniParameterFloat >( + InOuter, UHoudiniParameterFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); + + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Float); + + HoudiniAssetParameter->bIsChildOfRamp = false; + HoudiniAssetParameter->bIsLogarithmic = false; + return HoudiniAssetParameter; +} + +TOptional< float > +UHoudiniParameterFloat::GetValue(int32 Idx) const +{ + if (Values.IsValidIndex(Idx)) + return TOptional< float >(Values[Idx]); + else + return TOptional< float >(); +} + +bool +UHoudiniParameterFloat::GetValueAt(const int32& AtIndex, float& OutValue) const +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + OutValue = Values[AtIndex]; + + return true; +} + +bool +UHoudiniParameterFloat::SetValueAt(const float& InValue, const int32& AtIndex) +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + if (InValue == Values[AtIndex]) + return false; + + Values[AtIndex] = FMath::Clamp< float >(InValue, Min, Max); + + return true; +} + +void +UHoudiniParameterFloat::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterFloat::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterFloat::IsDefault() const +{ + if (bIsChildOfRamp) + return true; + + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; +} + +void +UHoudiniParameterFloat::SetValue(float InValue, int32 Idx) +{ + if (!Values.IsValidIndex(Idx)) + return; + + if (InValue == Values[Idx]) + return; + + Values[Idx] = FMath::Clamp< float >(InValue, Min, Max); +} + +void +UHoudiniParameterFloat::RevertToDefault() +{ + if (!bIsChildOfRamp) + { + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.Empty(); + TuplePendingRevertToDefault.Add(-1); + + MarkChanged(true); + } +} + +void +UHoudiniParameterFloat::RevertToDefault(const int32& TupleIndex) +{ + bPendingRevertToDefault = true; + TuplePendingRevertToDefault.AddUnique(TupleIndex); + + MarkChanged(true); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h index d36967194..396221fb8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFloat.h @@ -1,166 +1,166 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFloat.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFloat : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFloat * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetUnit() const { return Unit; }; - bool GetNoSwap() const { return bNoSwap; }; - - bool HasMin() const { return bHasMin; }; - bool HasMax() const { return bHasMax; }; - bool HasUIMin() const { return bHasUIMin; }; - bool HasUIMax() const { return bHasUIMax; }; - bool IsLogarithmic() const { return bIsLogarithmic; }; - - float GetMin() const { return Min; }; - float GetMax() const { return Max; }; - float GetUIMin() const { return UIMin; }; - float GetUIMax() const { return UIMax; }; - - bool IsChildOfRamp() const { return bIsChildOfRamp; }; - - // Check if current value at Index is the default - bool IsDefaultValueAtIndex(const int32& Idx) const; - - bool IsDefault() const override; - - // Get value of this property - TOptional< float > GetValue(int32 Idx) const; - bool GetValueAt(const int32& AtIndex, float& OutValue) const; - - // Write access to the value array - float* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - // Mutators - void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; - void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; - void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; - void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; - void SetIsLogarithmic(const bool& bInLog) { bIsLogarithmic = bInLog; }; - - void SetMin(const float& InMin) { Min = InMin; }; - void SetMax(const float& InMax) { Max = InMax; }; - void SetUIMin(const float& InUIMin) { UIMin = InUIMin; }; - void SetUIMax(const float& InUIMax) { UIMax = InUIMax; }; - - void SetUnit(const FString& InUnit) { Unit = InUnit; }; - void SetNoSwap(const bool& InNoSwap) { bNoSwap = InNoSwap; }; - - void SetDefaultValues(); - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - bool SetValueAt(const float& InValue, const int32& AtIndex); - - void SetIsChildOfRamp() { bIsChildOfRamp = true; }; - - /** Set value of this property, used by Slate. **/ - void SetValue(float InValue, int32 Idx); - - void RevertToDefault() override; - void RevertToDefault(const int32& TupleIndex) override; - -#if WITH_EDITOR - void SwitchUniformLock() { bUniformLocked = !bUniformLocked; }; - bool IsUniformLocked() const { return bUniformLocked; }; -#endif - -protected: - - // Float Values - UPROPERTY() - TArray Values; - - // Default float values, assigned at creating the parameter - UPROPERTY() - TArray DefaultValues; - - // Unit for this property - UPROPERTY() - FString Unit; - - // Do we have the noswap tag? - UPROPERTY() - bool bNoSwap; - - // Indicates we have a min/max value - UPROPERTY() - bool bHasMin; - // - UPROPERTY() - bool bHasMax; - - // Indicates we have a UI min/max - UPROPERTY() - bool bHasUIMin; - // - UPROPERTY() - bool bHasUIMax; - - UPROPERTY() - bool bIsLogarithmic; - - // Min and Max values for this property. - UPROPERTY() - float Min; - // - UPROPERTY() - float Max; - - // Min and Max values of this property for slider UI - UPROPERTY() - float UIMin; - // - UPROPERTY() - float UIMax; - - UPROPERTY() - bool bIsChildOfRamp; - -#if WITH_EDITORONLY_DATA - // Indicates whether the float VEC change value uniformly - UPROPERTY(Transient, DuplicateTransient, NonTransactional) - bool bUniformLocked; -#endif +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFloat.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFloat : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFloat * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetUnit() const { return Unit; }; + bool GetNoSwap() const { return bNoSwap; }; + + bool HasMin() const { return bHasMin; }; + bool HasMax() const { return bHasMax; }; + bool HasUIMin() const { return bHasUIMin; }; + bool HasUIMax() const { return bHasUIMax; }; + bool IsLogarithmic() const { return bIsLogarithmic; }; + + float GetMin() const { return Min; }; + float GetMax() const { return Max; }; + float GetUIMin() const { return UIMin; }; + float GetUIMax() const { return UIMax; }; + + bool IsChildOfRamp() const { return bIsChildOfRamp; }; + + // Check if current value at Index is the default + bool IsDefaultValueAtIndex(const int32& Idx) const; + + bool IsDefault() const override; + + // Get value of this property + TOptional< float > GetValue(int32 Idx) const; + bool GetValueAt(const int32& AtIndex, float& OutValue) const; + + // Write access to the value array + float* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + // Mutators + void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; + void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; + void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; + void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; + void SetIsLogarithmic(const bool& bInLog) { bIsLogarithmic = bInLog; }; + + void SetMin(const float& InMin) { Min = InMin; }; + void SetMax(const float& InMax) { Max = InMax; }; + void SetUIMin(const float& InUIMin) { UIMin = InUIMin; }; + void SetUIMax(const float& InUIMax) { UIMax = InUIMax; }; + + void SetUnit(const FString& InUnit) { Unit = InUnit; }; + void SetNoSwap(const bool& InNoSwap) { bNoSwap = InNoSwap; }; + + void SetDefaultValues(); + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + bool SetValueAt(const float& InValue, const int32& AtIndex); + + void SetIsChildOfRamp() { bIsChildOfRamp = true; }; + + /** Set value of this property, used by Slate. **/ + void SetValue(float InValue, int32 Idx); + + void RevertToDefault() override; + void RevertToDefault(const int32& TupleIndex) override; + +#if WITH_EDITOR + void SwitchUniformLock() { bUniformLocked = !bUniformLocked; }; + bool IsUniformLocked() const { return bUniformLocked; }; +#endif + +protected: + + // Float Values + UPROPERTY() + TArray Values; + + // Default float values, assigned at creating the parameter + UPROPERTY() + TArray DefaultValues; + + // Unit for this property + UPROPERTY() + FString Unit; + + // Do we have the noswap tag? + UPROPERTY() + bool bNoSwap; + + // Indicates we have a min/max value + UPROPERTY() + bool bHasMin; + // + UPROPERTY() + bool bHasMax; + + // Indicates we have a UI min/max + UPROPERTY() + bool bHasUIMin; + // + UPROPERTY() + bool bHasUIMax; + + UPROPERTY() + bool bIsLogarithmic; + + // Min and Max values for this property. + UPROPERTY() + float Min; + // + UPROPERTY() + float Max; + + // Min and Max values of this property for slider UI + UPROPERTY() + float UIMin; + // + UPROPERTY() + float UIMax; + + UPROPERTY() + bool bIsChildOfRamp; + +#if WITH_EDITORONLY_DATA + // Indicates whether the float VEC change value uniformly + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + bool bUniformLocked; +#endif }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp index 2bf8f9ea3..4bb4968bc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.cpp @@ -1,52 +1,52 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFolder.h" - -UHoudiniParameterFolder::UHoudiniParameterFolder(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - ,bExpanded(true) - ,bChosen(false) -{ - ParmType = EHoudiniParameterType::Folder; -} - -UHoudiniParameterFolder * -UHoudiniParameterFolder::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFolder_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolder::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFolder * HoudiniAssetParameter = NewObject< UHoudiniParameterFolder >( - InOuter, UHoudiniParameterFolder::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Folder); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFolder.h" + +UHoudiniParameterFolder::UHoudiniParameterFolder(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + ,bExpanded(true) + ,bChosen(false) +{ + ParmType = EHoudiniParameterType::Folder; +} + +UHoudiniParameterFolder * +UHoudiniParameterFolder::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFolder_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolder::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFolder * HoudiniAssetParameter = NewObject< UHoudiniParameterFolder >( + InOuter, UHoudiniParameterFolder::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Folder); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h index d38d3fa02..206ca2ac6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolder.h @@ -1,110 +1,110 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFolder.generated.h" - -UENUM() -enum class EHoudiniFolderParameterType : uint8 -{ - Invalid, - - Collapsible, - Simple, - Tabs, - Radio, - Other, -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFolder : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFolder * Create( - UObject* Outer, - const FString& ParamName); - - FORCEINLINE - EHoudiniFolderParameterType GetFolderType() const { return FolderType; }; - - FORCEINLINE - void SetFolderType(EHoudiniFolderParameterType Type) { FolderType = Type; }; - - FORCEINLINE - void SetExpanded(const bool InExpanded) { bExpanded = InExpanded; }; - FORCEINLINE - bool IsExpanded() const { return bExpanded; }; - FORCEINLINE - void ExpandButtonClicked() { bExpanded = !bExpanded; }; - - FORCEINLINE - void SetChosen(const bool InChosen) { bChosen = InChosen; }; - FORCEINLINE - bool IsChosen() const { return bChosen; }; - - FORCEINLINE - bool IsTab() const { return FolderType == EHoudiniFolderParameterType::Tabs || FolderType == EHoudiniFolderParameterType::Radio; }; - - - FORCEINLINE - void ResetChildCounter() { ChildCounter = TupleSize; } - - FORCEINLINE - int32& GetChildCounter() { return ChildCounter; }; - - FORCEINLINE - bool IsContentShown() const { return bIsContentShown; }; - - FORCEINLINE - void SetIsContentShown(const bool& bInShown) { bIsContentShown = bInShown; }; - - - -private: - UPROPERTY() - EHoudiniFolderParameterType FolderType; - - UPROPERTY() - bool bExpanded; - - UPROPERTY() - bool bChosen; - - - UPROPERTY() - int32 ChildCounter; - - UPROPERTY() - bool bIsContentShown; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFolder.generated.h" + +UENUM() +enum class EHoudiniFolderParameterType : uint8 +{ + Invalid, + + Collapsible, + Simple, + Tabs, + Radio, + Other, +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFolder : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFolder * Create( + UObject* Outer, + const FString& ParamName); + + FORCEINLINE + EHoudiniFolderParameterType GetFolderType() const { return FolderType; }; + + FORCEINLINE + void SetFolderType(EHoudiniFolderParameterType Type) { FolderType = Type; }; + + FORCEINLINE + void SetExpanded(const bool InExpanded) { bExpanded = InExpanded; }; + FORCEINLINE + bool IsExpanded() const { return bExpanded; }; + FORCEINLINE + void ExpandButtonClicked() { bExpanded = !bExpanded; }; + + FORCEINLINE + void SetChosen(const bool InChosen) { bChosen = InChosen; }; + FORCEINLINE + bool IsChosen() const { return bChosen; }; + + FORCEINLINE + bool IsTab() const { return FolderType == EHoudiniFolderParameterType::Tabs || FolderType == EHoudiniFolderParameterType::Radio; }; + + + FORCEINLINE + void ResetChildCounter() { ChildCounter = TupleSize; } + + FORCEINLINE + int32& GetChildCounter() { return ChildCounter; }; + + FORCEINLINE + bool IsContentShown() const { return bIsContentShown; }; + + FORCEINLINE + void SetIsContentShown(const bool& bInShown) { bIsContentShown = bInShown; }; + + + +private: + UPROPERTY() + EHoudiniFolderParameterType FolderType; + + UPROPERTY() + bool bExpanded; + + UPROPERTY() + bool bChosen; + + + UPROPERTY() + int32 ChildCounter; + + UPROPERTY() + bool bIsContentShown; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp index 3abe846ea..31ea2c061 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp @@ -1,108 +1,108 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterFolderList.h" -#include "HoudiniParameterFolder.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -UHoudiniParameterFolderList::UHoudiniParameterFolderList(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), bIsTabMenu(false), bIsTabsShown(false) -{ - ParmType = EHoudiniParameterType::FolderList; -} - -UHoudiniParameterFolderList * -UHoudiniParameterFolderList::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterFolderList_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolderList::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterFolderList * HoudiniAssetParameter = NewObject< UHoudiniParameterFolderList >( - InOuter, UHoudiniParameterFolderList::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::FolderList); - - HoudiniAssetParameter->bIsTabMenu = false; - return HoudiniAssetParameter; -} - -void -UHoudiniParameterFolderList::AddTabFolder(UHoudiniParameterFolder* InFolderParm) -{ - TabFolders.Add(InFolderParm); -} - -bool -UHoudiniParameterFolderList::IsTabParseFinished() const -{ - for (auto & CurTab : TabFolders) - { - if (!CurTab || CurTab->IsPendingKill()) - continue; - - if (!CurTab->IsTab()) - continue; - - // Go through visible tab only - if (!CurTab->IsChosen()) - continue; - - if (CurTab->GetChildCounter() > 0) - return false; - } - - return true; -} - -void -UHoudiniParameterFolderList::RemapParameters(const TMap& InputMapping) -{ - const int32 NumFolders = TabFolders.Num(); - for (int i = 0; i < NumFolders; i++) - { - UHoudiniParameter* FromParameter = TabFolders[i]; - - if (!FromParameter) - continue; - - UHoudiniParameterFolder* ToParameter = nullptr; - if (InputMapping.Contains(FromParameter)) - { - ToParameter = Cast(InputMapping.FindRef(FromParameter)); - } - - if (!ToParameter) - { - HOUDINI_LOG_WARNING(TEXT("[UHoudiniParameterFolderList::RemapParameters] Could not find mapping for existing parameter %s (%s)."), *(FromParameter->GetParameterName()), *(FromParameter->GetPathName()) ); - } - - TabFolders[i] = ToParameter; - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterFolderList.h" +#include "HoudiniParameterFolder.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +UHoudiniParameterFolderList::UHoudiniParameterFolderList(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), bIsTabMenu(false), bIsTabsShown(false) +{ + ParmType = EHoudiniParameterType::FolderList; +} + +UHoudiniParameterFolderList * +UHoudiniParameterFolderList::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterFolderList_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterFolderList::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterFolderList * HoudiniAssetParameter = NewObject< UHoudiniParameterFolderList >( + InOuter, UHoudiniParameterFolderList::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::FolderList); + + HoudiniAssetParameter->bIsTabMenu = false; + return HoudiniAssetParameter; +} + +void +UHoudiniParameterFolderList::AddTabFolder(UHoudiniParameterFolder* InFolderParm) +{ + TabFolders.Add(InFolderParm); +} + +bool +UHoudiniParameterFolderList::IsTabParseFinished() const +{ + for (auto & CurTab : TabFolders) + { + if (!CurTab || CurTab->IsPendingKill()) + continue; + + if (!CurTab->IsTab()) + continue; + + // Go through visible tab only + if (!CurTab->IsChosen()) + continue; + + if (CurTab->GetChildCounter() > 0) + return false; + } + + return true; +} + +void +UHoudiniParameterFolderList::RemapParameters(const TMap& InputMapping) +{ + const int32 NumFolders = TabFolders.Num(); + for (int i = 0; i < NumFolders; i++) + { + UHoudiniParameter* FromParameter = TabFolders[i]; + + if (!FromParameter) + continue; + + UHoudiniParameterFolder* ToParameter = nullptr; + if (InputMapping.Contains(FromParameter)) + { + ToParameter = Cast(InputMapping.FindRef(FromParameter)); + } + + if (!ToParameter) + { + HOUDINI_LOG_WARNING(TEXT("[UHoudiniParameterFolderList::RemapParameters] Could not find mapping for existing parameter %s (%s)."), *(FromParameter->GetParameterName()), *(FromParameter->GetPathName()) ); + } + + TabFolders[i] = ToParameter; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h index 77d31dc88..011118cf6 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.h @@ -1,81 +1,81 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterFolderList.generated.h" - -class UHoudiniParameterFolder; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterFolderList : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterFolderList * Create( - UObject* Outer, - const FString& ParamName); - - void AddTabFolder(UHoudiniParameterFolder* InFolderParm); - - FORCEINLINE - TArray& GetTabs() { return TabFolders; }; - - FORCEINLINE - bool IsTabMenu() const { return bIsTabMenu; }; - - FORCEINLINE - void SetIsTabMenu(const bool InIsTabMenu) { bIsTabMenu = InIsTabMenu; }; - - FORCEINLINE - bool IsTabsShown() const { return bIsTabsShown; }; - - FORCEINLINE - void SetTabsShown(const bool& bInTabsShown) { bIsTabsShown = bInTabsShown; }; - - bool IsTabParseFinished() const; - - UPROPERTY() - bool bIsTabMenu; - - UPROPERTY() - bool bIsTabsShown; - - UPROPERTY() - TArray TabFolders; - - - //------------------------------------------------------------------------------------------------ - // UHoudiniParameter overrides - //------------------------------------------------------------------------------------------------ - virtual void RemapParameters(const TMap& InputMapping) override; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterFolderList.generated.h" + +class UHoudiniParameterFolder; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterFolderList : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterFolderList * Create( + UObject* Outer, + const FString& ParamName); + + void AddTabFolder(UHoudiniParameterFolder* InFolderParm); + + FORCEINLINE + TArray& GetTabs() { return TabFolders; }; + + FORCEINLINE + bool IsTabMenu() const { return bIsTabMenu; }; + + FORCEINLINE + void SetIsTabMenu(const bool InIsTabMenu) { bIsTabMenu = InIsTabMenu; }; + + FORCEINLINE + bool IsTabsShown() const { return bIsTabsShown; }; + + FORCEINLINE + void SetTabsShown(const bool& bInTabsShown) { bIsTabsShown = bInTabsShown; }; + + bool IsTabParseFinished() const; + + UPROPERTY() + bool bIsTabMenu; + + UPROPERTY() + bool bIsTabsShown; + + UPROPERTY() + TArray TabFolders; + + + //------------------------------------------------------------------------------------------------ + // UHoudiniParameter overrides + //------------------------------------------------------------------------------------------------ + virtual void RemapParameters(const TMap& InputMapping) override; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp index 3168e70b1..f86a0f3ae 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.cpp @@ -1,130 +1,130 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterInt.h" - -UHoudiniParameterInt::UHoudiniParameterInt(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , Unit() - , bHasMin(false) - , bHasMax(false) - , bHasUIMin(false) - , bHasUIMax(false) - , bIsLogarithmic(false) - , Min(0) - , Max(0) - , UIMin(0) - , UIMax(0) -{ - ParmType = EHoudiniParameterType::Int; -} - -UHoudiniParameterInt * -UHoudiniParameterInt::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterInt_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterInt::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterInt * HoudiniAssetParameter = NewObject< UHoudiniParameterInt >( - InOuter, UHoudiniParameterInt::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Int); - HoudiniAssetParameter->bIsLogarithmic = false; - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -TOptional -UHoudiniParameterInt::GetValue(int32 Idx) const -{ - if (Values.IsValidIndex(Idx)) - return TOptional(Values[Idx]); - else - return TOptional(); -} - -bool -UHoudiniParameterInt::GetValueAt(const int32& AtIndex, int32& OutValue) const -{ - if (!Values.IsValidIndex(AtIndex)) - return false; - - OutValue = Values[AtIndex]; - - return true; -} - -bool -UHoudiniParameterInt::SetValueAt(const int32& InValue, const int32& AtIndex) -{ - if (!Values.IsValidIndex(AtIndex)) - return false; - - if (InValue == Values[AtIndex]) - return false; - - Values[AtIndex] = FMath::Clamp< int32 >(InValue, Min, Max); - - return true; -} - -void -UHoudiniParameterInt::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterInt::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterInt::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterInt.h" + +UHoudiniParameterInt::UHoudiniParameterInt(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , Unit() + , bHasMin(false) + , bHasMax(false) + , bHasUIMin(false) + , bHasUIMax(false) + , bIsLogarithmic(false) + , Min(0) + , Max(0) + , UIMin(0) + , UIMax(0) +{ + ParmType = EHoudiniParameterType::Int; +} + +UHoudiniParameterInt * +UHoudiniParameterInt::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterInt_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterInt::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterInt * HoudiniAssetParameter = NewObject< UHoudiniParameterInt >( + InOuter, UHoudiniParameterInt::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Int); + HoudiniAssetParameter->bIsLogarithmic = false; + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +TOptional +UHoudiniParameterInt::GetValue(int32 Idx) const +{ + if (Values.IsValidIndex(Idx)) + return TOptional(Values[Idx]); + else + return TOptional(); +} + +bool +UHoudiniParameterInt::GetValueAt(const int32& AtIndex, int32& OutValue) const +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + OutValue = Values[AtIndex]; + + return true; +} + +bool +UHoudiniParameterInt::SetValueAt(const int32& InValue, const int32& AtIndex) +{ + if (!Values.IsValidIndex(AtIndex)) + return false; + + if (InValue == Values[AtIndex]) + return false; + + Values[AtIndex] = FMath::Clamp< int32 >(InValue, Min, Max); + + return true; +} + +void +UHoudiniParameterInt::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterInt::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterInt::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h index 2cde7aa9c..7559b3d4a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterInt.h @@ -1,132 +1,132 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterInt.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterInt : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterInt * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FString GetUnit() const { return Unit; }; - - bool HasMin() const { return bHasMin; }; - bool HasMax() const { return bHasMax; }; - bool HasUIMin() const { return bHasUIMin; }; - bool HasUIMax() const { return bHasUIMax; }; - bool IsLogarithmic() const { return bIsLogarithmic; }; - - int32 GetMin() const { return Min; }; - int32 GetMax() const { return Max; }; - int32 GetUIMin() const { return UIMin; }; - int32 GetUIMax() const { return UIMax; }; - - // Get value of this property - TOptional GetValue(int32 Idx) const; - bool GetValueAt(const int32& AtIndex, int32& OutValue) const; - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - bool IsDefaultValueAtIndex(const int32& Idx) const; - bool IsDefault() const override; - - // Mutators - void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; - void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; - void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; - void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; - - void SetMin(const int32& InMin) { Min = InMin; }; - void SetMax(const int32& InMax) { Max = InMax; }; - void SetUIMin(const int32& InUIMin) { UIMin = InUIMin; }; - void SetUIMax(const int32& InUIMax) { UIMax = InUIMax; }; - void SetIsLogarithmic(const bool& bInIsLog) { bIsLogarithmic = bInIsLog; }; - - void SetUnit(const FString& InUnit) { Unit = InUnit; }; - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - bool SetValueAt(const int32& InValue, const int32& AtIndex); - - void SetDefaultValues(); - -protected: - - // Int Values - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; - - // Unit for this property - UPROPERTY() - FString Unit; - - // Indicates we have a min/max value - UPROPERTY() - bool bHasMin; - // - UPROPERTY() - bool bHasMax; - - // Indicates we have a UI min/max - UPROPERTY() - bool bHasUIMin; - // - UPROPERTY() - bool bHasUIMax; - - UPROPERTY() - bool bIsLogarithmic; - - // Min and Max values for this property. - UPROPERTY() - int32 Min; - // - UPROPERTY() - int32 Max; - - // Min and Max values of this property for slider UI - UPROPERTY() - int32 UIMin; - // - UPROPERTY() - int32 UIMax; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterInt.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterInt : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterInt * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FString GetUnit() const { return Unit; }; + + bool HasMin() const { return bHasMin; }; + bool HasMax() const { return bHasMax; }; + bool HasUIMin() const { return bHasUIMin; }; + bool HasUIMax() const { return bHasUIMax; }; + bool IsLogarithmic() const { return bIsLogarithmic; }; + + int32 GetMin() const { return Min; }; + int32 GetMax() const { return Max; }; + int32 GetUIMin() const { return UIMin; }; + int32 GetUIMax() const { return UIMax; }; + + // Get value of this property + TOptional GetValue(int32 Idx) const; + bool GetValueAt(const int32& AtIndex, int32& OutValue) const; + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + bool IsDefaultValueAtIndex(const int32& Idx) const; + bool IsDefault() const override; + + // Mutators + void SetHasMin(const bool& InHasMin) { bHasMin = InHasMin; }; + void SetHasMax(const bool& InHasMax) { bHasMax = InHasMax; }; + void SetHasUIMin(const bool& InHasUIMin) { bHasUIMin = InHasUIMin; }; + void SetHasUIMax(const bool& InHasUIMax) { bHasUIMax = InHasUIMax; }; + + void SetMin(const int32& InMin) { Min = InMin; }; + void SetMax(const int32& InMax) { Max = InMax; }; + void SetUIMin(const int32& InUIMin) { UIMin = InUIMin; }; + void SetUIMax(const int32& InUIMax) { UIMax = InUIMax; }; + void SetIsLogarithmic(const bool& bInIsLog) { bIsLogarithmic = bInIsLog; }; + + void SetUnit(const FString& InUnit) { Unit = InUnit; }; + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + bool SetValueAt(const int32& InValue, const int32& AtIndex); + + void SetDefaultValues(); + +protected: + + // Int Values + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; + + // Unit for this property + UPROPERTY() + FString Unit; + + // Indicates we have a min/max value + UPROPERTY() + bool bHasMin; + // + UPROPERTY() + bool bHasMax; + + // Indicates we have a UI min/max + UPROPERTY() + bool bHasUIMin; + // + UPROPERTY() + bool bHasUIMax; + + UPROPERTY() + bool bIsLogarithmic; + + // Min and Max values for this property. + UPROPERTY() + int32 Min; + // + UPROPERTY() + int32 Max; + + // Min and Max values of this property for slider UI + UPROPERTY() + int32 UIMin; + // + UPROPERTY() + int32 UIMax; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp index 97a233bba..a1939516d 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.cpp @@ -1,62 +1,62 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterLabel.h" - -UHoudiniParameterLabel::UHoudiniParameterLabel(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Label; -} - -UHoudiniParameterLabel * -UHoudiniParameterLabel::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterLabel_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterLabel::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterLabel * HoudiniAssetParameter = NewObject< UHoudiniParameterLabel >( - InOuter, UHoudiniParameterLabel::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Label); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -FString -UHoudiniParameterLabel::GetStringAtIndex(int32 Index) -{ - if (LabelStrings.IsValidIndex(Index)) - { - return LabelStrings[Index]; - } - - return FString(""); -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterLabel.h" + +UHoudiniParameterLabel::UHoudiniParameterLabel(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Label; +} + +UHoudiniParameterLabel * +UHoudiniParameterLabel::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterLabel_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterLabel::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterLabel * HoudiniAssetParameter = NewObject< UHoudiniParameterLabel >( + InOuter, UHoudiniParameterLabel::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Label); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +FString +UHoudiniParameterLabel::GetStringAtIndex(int32 Index) +{ + if (LabelStrings.IsValidIndex(Index)) + { + return LabelStrings[Index]; + } + + return FString(""); +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h index 8482aa542..780dda43c 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterLabel.h @@ -1,56 +1,56 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterLabel.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterLabel : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterLabel * Create( - UObject* Outer, - const FString& ParamName); - - UPROPERTY() - TArray LabelStrings; - - FORCEINLINE - void AddLabelString(FString &NewString) { LabelStrings.Add(NewString); }; - - FString GetStringAtIndex(int32 Index); - - FORCEINLINE - void EmptyLabelString() { LabelStrings.Empty(); }; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterLabel.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterLabel : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterLabel * Create( + UObject* Outer, + const FString& ParamName); + + UPROPERTY() + TArray LabelStrings; + + FORCEINLINE + void AddLabelString(FString &NewString) { LabelStrings.Add(NewString); }; + + FString GetStringAtIndex(int32 Index); + + FORCEINLINE + void EmptyLabelString() { LabelStrings.Empty(); }; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp index 65850b8e0..8a1cff042 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp @@ -1,154 +1,154 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterMultiParm.h" - -UHoudiniParameterMultiParm::UHoudiniParameterMultiParm(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), bIsShown(false), InstanceStartOffset(0) -{ - // TODO Proper Init - ParmType = EHoudiniParameterType::MultiParm; -} - -UHoudiniParameterMultiParm * -UHoudiniParameterMultiParm::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterMultiParm::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterMultiParm * HoudiniAssetParameter = NewObject< UHoudiniParameterMultiParm >( - InOuter, UHoudiniParameterMultiParm::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::MultiParm); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - HoudiniAssetParameter->DefaultInstanceCount = -1; - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterMultiParm::SetValue(const int32& InValue) -{ - if (InValue == Value) - return false; - - Value = InValue; - - return true; -} - -void -UHoudiniParameterMultiParm::InsertElement() -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); -} - -void -UHoudiniParameterMultiParm::InsertElementAt(int32 Index) -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - if (Index >= MultiParmInstanceLastModifyArray.Num()) - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); - else - MultiParmInstanceLastModifyArray.Insert(EHoudiniMultiParmModificationType::Inserted, Index); -} - -/** Decrement value, used by Slate. **/ -void -UHoudiniParameterMultiParm::RemoveElement(int32 Index) -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - // Remove the last element - if (Index == -1) - { - Index = MultiParmInstanceLastModifyArray.Num() - 1; - while (MultiParmInstanceLastModifyArray.IsValidIndex(Index) && MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Removed) - Index -= 1; - } - - if (MultiParmInstanceLastModifyArray.IsValidIndex(Index)) - { - // If the removed is a to be inserted instance, simply remove it. - if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) - MultiParmInstanceLastModifyArray.RemoveAt(Index); - // Otherwise mark it as to be removed. - else - MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; - } -} - -void -UHoudiniParameterMultiParm::EmptyElements() -{ - if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) - InitializeModifyArray(); - - for (int32 Index = MultiParmInstanceLastModifyArray.Num() - 1; Index >= 0; --Index) - { - // If the removed is a to be inserted instance, simply remove it. - // Interation starts from the tail, so that the indices won't be changed by element removal. - if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) - MultiParmInstanceLastModifyArray.RemoveAt(Index); - else // Otherwise mark it as to be removed. - MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; - } -} - -void -UHoudiniParameterMultiParm::InitializeModifyArray() -{ - for (uint32 Index = 0; Index < MultiParmInstanceCount; ++Index) - { - MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::None); - } -} - -bool -UHoudiniParameterMultiParm::IsDefault() const -{ - //UE_LOG(LogTemp, Warning, TEXT("%d, %d"), MultiParmInstanceNum, MultiParmInstanceCount); - return DefaultInstanceCount == MultiParmInstanceCount; -} - -void -UHoudiniParameterMultiParm::SetDefaultInstanceCount(int32 InCount) -{ - if (DefaultInstanceCount >= 0) - return; - - DefaultInstanceCount = InCount; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterMultiParm.h" + +UHoudiniParameterMultiParm::UHoudiniParameterMultiParm(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), bIsShown(false), InstanceStartOffset(0) +{ + // TODO Proper Init + ParmType = EHoudiniParameterType::MultiParm; +} + +UHoudiniParameterMultiParm * +UHoudiniParameterMultiParm::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterMultiParm::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterMultiParm * HoudiniAssetParameter = NewObject< UHoudiniParameterMultiParm >( + InOuter, UHoudiniParameterMultiParm::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::MultiParm); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + HoudiniAssetParameter->DefaultInstanceCount = -1; + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterMultiParm::SetValue(const int32& InValue) +{ + if (InValue == Value) + return false; + + Value = InValue; + + return true; +} + +void +UHoudiniParameterMultiParm::InsertElement() +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); +} + +void +UHoudiniParameterMultiParm::InsertElementAt(int32 Index) +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + if (Index >= MultiParmInstanceLastModifyArray.Num()) + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::Inserted); + else + MultiParmInstanceLastModifyArray.Insert(EHoudiniMultiParmModificationType::Inserted, Index); +} + +/** Decrement value, used by Slate. **/ +void +UHoudiniParameterMultiParm::RemoveElement(int32 Index) +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + // Remove the last element + if (Index == -1) + { + Index = MultiParmInstanceLastModifyArray.Num() - 1; + while (MultiParmInstanceLastModifyArray.IsValidIndex(Index) && MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Removed) + Index -= 1; + } + + if (MultiParmInstanceLastModifyArray.IsValidIndex(Index)) + { + // If the removed is a to be inserted instance, simply remove it. + if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) + MultiParmInstanceLastModifyArray.RemoveAt(Index); + // Otherwise mark it as to be removed. + else + MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; + } +} + +void +UHoudiniParameterMultiParm::EmptyElements() +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + for (int32 Index = MultiParmInstanceLastModifyArray.Num() - 1; Index >= 0; --Index) + { + // If the removed is a to be inserted instance, simply remove it. + // Interation starts from the tail, so that the indices won't be changed by element removal. + if (MultiParmInstanceLastModifyArray[Index] == EHoudiniMultiParmModificationType::Inserted) + MultiParmInstanceLastModifyArray.RemoveAt(Index); + else // Otherwise mark it as to be removed. + MultiParmInstanceLastModifyArray[Index] = EHoudiniMultiParmModificationType::Removed; + } +} + +void +UHoudiniParameterMultiParm::InitializeModifyArray() +{ + for (uint32 Index = 0; Index < MultiParmInstanceCount; ++Index) + { + MultiParmInstanceLastModifyArray.Add(EHoudiniMultiParmModificationType::None); + } +} + +bool +UHoudiniParameterMultiParm::IsDefault() const +{ + //UE_LOG(LogTemp, Warning, TEXT("%d, %d"), MultiParmInstanceNum, MultiParmInstanceCount); + return DefaultInstanceCount == MultiParmInstanceCount; +} + +void +UHoudiniParameterMultiParm::SetDefaultInstanceCount(int32 InCount) +{ + if (DefaultInstanceCount >= 0) + return; + + DefaultInstanceCount = InCount; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h index 735b2f474..5adabd06a 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h @@ -1,128 +1,128 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterMultiParm.generated.h" - -UENUM() -enum class EHoudiniMultiParmModificationType : uint8 -{ - None, - - Inserted, - Removed, - Modified -}; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterMultiParm * Create( - UObject* Outer, - const FString& ParamName); - - // Accessors - FORCEINLINE - int32 GetValue() const { return Value; }; - FORCEINLINE - int32 GetInstanceCount() const { return MultiParmInstanceCount; }; - - // Mutators - bool SetValue(const int32& InValue); - FORCEINLINE - void SetInstanceCount(const int32 InCount) { MultiParmInstanceCount = InCount; }; - - FORCEINLINE - void SetIsShown(const bool InIsShown) { bIsShown = InIsShown; }; - - FORCEINLINE - bool IsShown() const { return bIsShown; }; - - - /** Increment value, used by Slate. **/ - void InsertElement(); - - void InsertElementAt(int32 Index); - - /** Decrement value, used by Slate. **/ - void RemoveElement(int32 Index); - - /** Empty the values, used by Slate. **/ - void EmptyElements(); - - UPROPERTY() - bool bIsShown; - - // Value of the multiparm - UPROPERTY() - int32 Value; - - // - UPROPERTY() - FString TemplateName; - - // Value of this property. - UPROPERTY() - int32 MultiparmValue; - - // - UPROPERTY() - uint32 MultiParmInstanceNum; - - // - UPROPERTY() - uint32 MultiParmInstanceLength; - - // - UPROPERTY() - uint32 MultiParmInstanceCount; - - UPROPERTY() - uint32 InstanceStartOffset; - - // This array records the last modified instance of the multiparm - UPROPERTY() - TArray MultiParmInstanceLastModifyArray; - - UPROPERTY() - int32 DefaultInstanceCount; - - bool IsDefault() const override; - - void SetDefaultInstanceCount(int32 InCount); - -private: - void InitializeModifyArray(); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterMultiParm.generated.h" + +UENUM() +enum class EHoudiniMultiParmModificationType : uint8 +{ + None, + + Inserted, + Removed, + Modified +}; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterMultiParm * Create( + UObject* Outer, + const FString& ParamName); + + // Accessors + FORCEINLINE + int32 GetValue() const { return Value; }; + FORCEINLINE + int32 GetInstanceCount() const { return MultiParmInstanceCount; }; + + // Mutators + bool SetValue(const int32& InValue); + FORCEINLINE + void SetInstanceCount(const int32 InCount) { MultiParmInstanceCount = InCount; }; + + FORCEINLINE + void SetIsShown(const bool InIsShown) { bIsShown = InIsShown; }; + + FORCEINLINE + bool IsShown() const { return bIsShown; }; + + + /** Increment value, used by Slate. **/ + void InsertElement(); + + void InsertElementAt(int32 Index); + + /** Decrement value, used by Slate. **/ + void RemoveElement(int32 Index); + + /** Empty the values, used by Slate. **/ + void EmptyElements(); + + UPROPERTY() + bool bIsShown; + + // Value of the multiparm + UPROPERTY() + int32 Value; + + // + UPROPERTY() + FString TemplateName; + + // Value of this property. + UPROPERTY() + int32 MultiparmValue; + + // + UPROPERTY() + uint32 MultiParmInstanceNum; + + // + UPROPERTY() + uint32 MultiParmInstanceLength; + + // + UPROPERTY() + uint32 MultiParmInstanceCount; + + UPROPERTY() + uint32 InstanceStartOffset; + + // This array records the last modified instance of the multiparm + UPROPERTY() + TArray MultiParmInstanceLastModifyArray; + + UPROPERTY() + int32 DefaultInstanceCount; + + bool IsDefault() const override; + + void SetDefaultInstanceCount(int32 InCount); + +private: + void InitializeModifyArray(); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp index 9b237e3cf..2bf983369 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.cpp @@ -1,95 +1,95 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterOperatorPath.h" - -#include "HoudiniInput.h" - - -UHoudiniParameterOperatorPath::UHoudiniParameterOperatorPath( - const FObjectInitializer &ObjectInitializer) - : Super(ObjectInitializer) -{ - -} - -UHoudiniParameterOperatorPath * -UHoudiniParameterOperatorPath::Create(UObject *InOuter, const FString &InParamName) -{ - FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; - FName ParamName = MakeUniqueObjectName( - InOuter, UHoudiniParameterOperatorPath::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterOperatorPath *HoudiniAssetParameter = - NewObject( - InOuter, UHoudiniParameterOperatorPath::StaticClass(), ParamName, - RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Input); - - - return HoudiniAssetParameter; -} - -bool UHoudiniParameterOperatorPath::HasChanged() const -{ - if (Super::HasChanged()) - return true; - if (HoudiniInput.IsValid() && HoudiniInput->HasChanged()) - return true; - return false; -} - -bool UHoudiniParameterOperatorPath::NeedsToTriggerUpdate() const -{ - if (Super::NeedsToTriggerUpdate()) - return true; - if (HoudiniInput.IsValid() && HoudiniInput->NeedsToTriggerUpdate()) - return true; - return false; -} - -void UHoudiniParameterOperatorPath::MarkChanged(const bool & bInChanged) -{ - Super::MarkChanged(bInChanged); -} - -void -UHoudiniParameterOperatorPath::RemapInputs(const TMap& InputMapping) -{ - if (!HoudiniInput.IsValid()) - return; - - if (InputMapping.Contains(HoudiniInput.Get())) - { - HoudiniInput = InputMapping.FindRef(HoudiniInput.Get()); - } - else - { - HoudiniInput = nullptr; - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterOperatorPath.h" + +#include "HoudiniInput.h" + + +UHoudiniParameterOperatorPath::UHoudiniParameterOperatorPath( + const FObjectInitializer &ObjectInitializer) + : Super(ObjectInitializer) +{ + +} + +UHoudiniParameterOperatorPath * +UHoudiniParameterOperatorPath::Create(UObject *InOuter, const FString &InParamName) +{ + FString ParamNameStr = "HoudiniParameterMultiParm_" + InParamName; + FName ParamName = MakeUniqueObjectName( + InOuter, UHoudiniParameterOperatorPath::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterOperatorPath *HoudiniAssetParameter = + NewObject( + InOuter, UHoudiniParameterOperatorPath::StaticClass(), ParamName, + RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Input); + + + return HoudiniAssetParameter; +} + +bool UHoudiniParameterOperatorPath::HasChanged() const +{ + if (Super::HasChanged()) + return true; + if (HoudiniInput.IsValid() && HoudiniInput->HasChanged()) + return true; + return false; +} + +bool UHoudiniParameterOperatorPath::NeedsToTriggerUpdate() const +{ + if (Super::NeedsToTriggerUpdate()) + return true; + if (HoudiniInput.IsValid() && HoudiniInput->NeedsToTriggerUpdate()) + return true; + return false; +} + +void UHoudiniParameterOperatorPath::MarkChanged(const bool & bInChanged) +{ + Super::MarkChanged(bInChanged); +} + +void +UHoudiniParameterOperatorPath::RemapInputs(const TMap& InputMapping) +{ + if (!HoudiniInput.IsValid()) + return; + + if (InputMapping.Contains(HoudiniInput.Get())) + { + HoudiniInput = InputMapping.FindRef(HoudiniInput.Get()); + } + else + { + HoudiniInput = nullptr; + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h index 3960e55ee..c22b93e5e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterOperatorPath.h @@ -1,58 +1,58 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterOperatorPath.generated.h" - - -class UHoudiniInput; - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterOperatorPath - : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - // Create instance of this class. - static UHoudiniParameterOperatorPath * - Create(UObject *Outer, const FString &ParamName); - - virtual bool HasChanged() const override; - virtual bool NeedsToTriggerUpdate() const override; - virtual void MarkChanged(const bool& bInChanged) override; - - //------------------------------------------------------------------------------------------------ - // UHoudiniParameter overrides - //------------------------------------------------------------------------------------------------ - virtual void RemapInputs(const TMap& InputMapping) override; - - UPROPERTY() - TWeakObjectPtr HoudiniInput; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterOperatorPath.generated.h" + + +class UHoudiniInput; + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterOperatorPath + : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + // Create instance of this class. + static UHoudiniParameterOperatorPath * + Create(UObject *Outer, const FString &ParamName); + + virtual bool HasChanged() const override; + virtual bool NeedsToTriggerUpdate() const override; + virtual void MarkChanged(const bool& bInChanged) override; + + //------------------------------------------------------------------------------------------------ + // UHoudiniParameter overrides + //------------------------------------------------------------------------------------------------ + virtual void RemapInputs(const TMap& InputMapping) override; + + UPROPERTY() + TWeakObjectPtr HoudiniInput; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp index f9b1768e6..a9bf0f2e2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.cpp @@ -1,969 +1,969 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterRamp.h" -#include "HoudiniParameter.h" -#include "HoudiniParameterFloat.h" -#include "HoudiniParameterColor.h" -#include "HoudiniParameterChoice.h" - -#include "UObject/UnrealType.h" - - -void -UHoudiniParameterRampFloatPoint::SetPosition(const float InPosition) -{ - if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Position = InPosition; - PositionParentParm->SetValueAt(InPosition, 0); - PositionParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampFloatPoint::SetValue(const float InValue) -{ - if (ValueParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Value = InValue; - ValueParentParm->SetValueAt(InValue, 0); - ValueParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampFloatPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) -{ - if (InterpolationParentParm) - { - Interpolation = InInterpolation; - InterpolationParentParm->SetIntValue((int32)InInterpolation); - InterpolationParentParm->UpdateStringValueFromInt(); - InterpolationParentParm->SetIsChildOfRamp(); - } -} - -UHoudiniParameterRampFloatPoint* -UHoudiniParameterRampFloatPoint::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - UHoudiniParameterRampFloatPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); - - NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); - - return NewPoint; -} - -void -UHoudiniParameterRampFloatPoint::CopyStateFrom(UHoudiniParameterRampFloatPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - // Ensure this object's flags match the desired flags. - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -} - -void -UHoudiniParameterRampFloatPoint::RemapParameters( - const TMap& ParameterMapping) -{ - if (ParameterMapping.Contains(PositionParentParm)) - { - PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); - } - else - { - PositionParentParm = nullptr; - } - - if (ParameterMapping.Contains(ValueParentParm)) - { - ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); - } - else - { - ValueParentParm = nullptr; - } - - if (ParameterMapping.Contains(InterpolationParentParm)) - { - InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); - } - else - { - InterpolationParentParm = nullptr; - } -}; - - -void -UHoudiniParameterRampColorPoint::SetPosition(const float InPosition) -{ - if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) - { - Position = InPosition; - PositionParentParm->SetValueAt(InPosition, 0); - PositionParentParm->SetIsChildOfRamp(); - } -}; - -void -UHoudiniParameterRampColorPoint::SetValue(const FLinearColor InValue) -{ - if (!ValueParentParm) - return; - - Value = InValue; - ValueParentParm->SetColorValue(InValue); - ValueParentParm->SetIsChildOfRamp(); -}; - -void -UHoudiniParameterRampColorPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) -{ - if (!InterpolationParentParm) - return; - - Interpolation = InInterpolation; - InterpolationParentParm->SetIntValue((int32)InInterpolation); - InterpolationParentParm->UpdateStringValueFromInt(); - InterpolationParentParm->SetIsChildOfRamp(); -} -UHoudiniParameterRampColorPoint * UHoudiniParameterRampColorPoint::DuplicateAndCopyState(UObject * DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - - UHoudiniParameterRampColorPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); - - NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); - - return NewPoint; - -} -void UHoudiniParameterRampColorPoint::CopyStateFrom(UHoudiniParameterRampColorPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ -#if WITH_EDITOR - PreEditChange(nullptr); -#endif - if (bCopyAllProperties) - { - UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; - Params.bDoDelta = false; // Perform a deep copy - Params.bClearReferences = false; // References will be replaced afterwards. - UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); - } - - // Ensure this object's flags match the desired flags. - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -#if WITH_EDITOR - PostEditChange(); -#endif -} - -void UHoudiniParameterRampColorPoint::RemapParameters(const TMap& ParameterMapping) -{ - if (ParameterMapping.Contains(PositionParentParm)) - { - PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); - } - else - { - PositionParentParm = nullptr; - } - - if (ParameterMapping.Contains(ValueParentParm)) - { - ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); - } - else - { - ValueParentParm = nullptr; - } - - if (ParameterMapping.Contains(InterpolationParentParm)) - { - InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); - } - else - { - InterpolationParentParm = nullptr; - } -} - - -UHoudiniParameterRampFloat::UHoudiniParameterRampFloat(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), - NumDefaultPoints(-1), - bCaching(false) -{ - ParmType = EHoudiniParameterType::FloatRamp; -} - -void -UHoudiniParameterRampFloat::OnPreCook() -{ - if (bCaching) - { - SyncCachedPoints(); - bCaching = false; - } -} - -UHoudiniParameterRampFloat * -UHoudiniParameterRampFloat::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampFloat::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterRampFloat * HoudiniParameter = NewObject< UHoudiniParameterRampFloat >( - InOuter, UHoudiniParameterRampFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniParameter->SetParameterType(EHoudiniParameterType::FloatRamp); - - HoudiniParameter->NumDefaultPoints = -1; - - HoudiniParameter->bCaching = false; - - return HoudiniParameter; -} - -void UHoudiniParameterRampFloat::CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ -#if WITH_EDITOR - PreEditChange(nullptr); -#endif - - UHoudiniParameterRampFloat* FromParameter = Cast(InParameter); - check(FromParameter); - - TArray PrevCachedPoints = CachedPoints; - TArray PrevPoints = Points; - - Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); - - CachedPoints = PrevCachedPoints; - Points = PrevPoints; - - - auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) - { - const int32 NumPts = FromArray.Num(); - - ToArray.SetNum(NumPts); - - for(int32 i = 0; i < NumPts; ++i) - { - UHoudiniParameterRampFloatPoint* FromPoint = FromArray[i]; - UHoudiniParameterRampFloatPoint* ToPoint = ToArray[i]; - - check(FromPoint); - - if (ToPoint) - { - // Ensure the destination point is outered to this parameter - bool bIsValid = ToPoint->GetOuter() == NewOuter; - if (!bIsValid) - ToPoint = nullptr; - } - - if (!ToPoint) - { - // Duplicate a new copy using FromPoint - ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); - } - else - { - // We have a valid point that we can reuse. Simply copy state. - ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); - } - - ToArray[i] = ToPoint; - } - }; - - CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); - CopyPointsStateFn(FromParameter->Points, Points, this); - -#if WITH_EDITOR - PostEditChange(); -#endif -} - -void -UHoudiniParameterRampFloat::RemapParameters(const TMap& ParameterMapping) -{ - Super::RemapParameters(ParameterMapping); - - AActor* OuterActor = GetTypedOuter(); - - - for(UHoudiniParameterRampFloatPoint* CurrentPoint : Points) - { - CurrentPoint->RemapParameters(ParameterMapping); - } - - for(UHoudiniParameterRampFloatPoint* CurrentPoint : CachedPoints) - { - CurrentPoint->RemapParameters(ParameterMapping); - } -} - -void -UHoudiniParameterRampFloat::SyncCachedPoints() -{ - int32 Idx = 0; - - while (Idx < CachedPoints.Num() && Idx < Points.Num()) - { - UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; - UHoudiniParameterRampFloatPoint* &CurrentPoint = Points[Idx]; - - if (!CachedPoint || !CurrentPoint) - continue; - - if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) - { - if (CurrentPoint->PositionParentParm) - { - CurrentPoint->SetPosition(CachedPoint->GetPosition()); - CurrentPoint->PositionParentParm->MarkChanged(true); - } - } - - if (CachedPoint->GetValue() != CurrentPoint->GetValue()) - { - if (CurrentPoint->ValueParentParm) - { - CurrentPoint->SetValue(CachedPoint->GetValue()); - CurrentPoint->ValueParentParm->MarkChanged(true); - } - } - - if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) - { - if (CurrentPoint->InterpolationParentParm) - { - CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); - CurrentPoint->InterpolationParentParm->MarkChanged(true); - } - } - - Idx += 1; - } - - // Insert points - for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) - { - UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; - if (!CachedPoint) - continue; - - CreateInsertEvent(CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); - - MarkChanged(true); - } - - // Remove points - for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < Points.Num(); ++IdxCurrentPointLeft) - { - RemoveElement(IdxCurrentPointLeft); - - UHoudiniParameterRampFloatPoint* Point = Points[IdxCurrentPointLeft]; - - if (!Point) - continue; - - CreateDeleteEvent(Point->InstanceIndex); - - MarkChanged(true); - } -} - - -void -UHoudiniParameterRampFloat::CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) -{ - UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( - this, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!InsertEvent) - return; - - InsertEvent->SetFloatRampEvent(); - InsertEvent->SetInsertEvent(); - InsertEvent->InsertPosition = InPosition; - InsertEvent->InsertFloat = InValue; - InsertEvent->InsertInterpolation = InInterp; - - ModificationEvents.Add(InsertEvent); -} - -void -UHoudiniParameterRampFloat::CreateDeleteEvent(const int32 &InDeleteIndex) -{ - UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( - this, UHoudiniParameterRampModificationEvent::StaticClass()); - - if (!DeleteEvent) - return; - - DeleteEvent->SetFloatRampEvent(); - DeleteEvent->SetDeleteEvent(); - DeleteEvent->DeleteInstanceIndex = InDeleteIndex; - - ModificationEvents.Add(DeleteEvent); -} - -bool -UHoudiniParameterRampFloat::UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex) -{ - // Copy existing points to NewPoints - TArray NewPoints; - const int32 NumInstances = GetInstanceCount(); - NewPoints.Reserve(NumInstances); - for (UHoudiniParameterRampFloatPoint* const PointData : Points) - { - if (!IsValid(PointData)) - continue; - - PointData->InstanceIndex = NewPoints.Num(); - PointData->PositionParentParm = nullptr; - PointData->ValueParentParm = nullptr; - PointData->InterpolationParentParm = nullptr; - NewPoints.Add(PointData); - - // We don't need more than NumInstances points in the array - if (NewPoints.Num() == NumInstances) - break; - } - - // Loop over InParameters and look for children of this ramp - int32 CurrentInstanceIndex = 0; - const int32 NumParameters = InParameters.Num(); - for (int32 Index = InStartParamIndex; Index < NumParameters; ++Index) - { - UHoudiniParameter* const Param = InParameters[Index]; - if (!IsValid(Param)) - continue; - - if (!Param->GetIsChildOfMultiParm() || Param->GetParentParmId() != ParmId) - continue; - - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType != EHoudiniParameterType::Float && ParamType != EHoudiniParameterType::IntChoice) - continue; - - // Ensure we have a valid point object at the current index - UHoudiniParameterRampFloatPoint* Point = nullptr; - if (!NewPoints.IsValidIndex(CurrentInstanceIndex)) - { - Point = NewObject(this, FName(), this->GetMaskedFlags(RF_PropagateToSubObjects)); - Point->InstanceIndex = CurrentInstanceIndex; - NewPoints.Add(Point); - } - else - { - Point = NewPoints[CurrentInstanceIndex]; - } - - if (ParamType == EHoudiniParameterType::Float) - { - UHoudiniParameterFloat* FloatParameter = Cast(Param); - if (FloatParameter) - { - //*****Float Parameter (position)*****// - if (!Point->PositionParentParm) - { - if (FloatParameter->GetNumberOfValues() <= 0) - continue; - // Set the float ramp point's position parent parm, and value - Point->PositionParentParm = FloatParameter; - Point->SetPosition(FloatParameter->GetValuesPtr()[0]); - } - //*****Float Parameter (value)*****// - else - { - if (FloatParameter->GetNumberOfValues() <= 0) - continue;; - Point->ValueParentParm = FloatParameter; - Point->SetValue(FloatParameter->GetValuesPtr()[0]); - } - } - } - //*****Choice parameter (Interpolation)*****// - else if (ParamType == EHoudiniParameterType::IntChoice) - { - UHoudiniParameterChoice* ChoiceParameter = Cast(Param); - if (ChoiceParameter) - { - Point->InterpolationParentParm = ChoiceParameter; - Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValueIndex())); - CurrentInstanceIndex++; - } - } - } - - //*****All ramp points have been parsed, finish!*****// - if (NewPoints.Num() == NumInstances) - { - NewPoints.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { - return P1.Position < P2.Position; - }); - - Points = MoveTemp(NewPoints); - - // Not caching, points are synced, update cached points - if (!bCaching) - { - const int32 NumPoints = Points.Num(); - CachedPoints.SetNumZeroed(NumPoints); - - for (int32 i = 0; i < NumPoints; ++i) - { - UHoudiniParameterRampFloatPoint* const FromPoint = Points[i]; - UHoudiniParameterRampFloatPoint* ToPoint = CachedPoints[i]; - - // Nothing we can do/copy if FromPoint is null/pending kill - if (!IsValid(FromPoint)) - continue; - - if (!IsValid(ToPoint)) - { - ToPoint = FromPoint->DuplicateAndCopyState(this, RF_NoFlags, GetMaskedFlags(RF_PropagateToSubObjects)); - CachedPoints[i] = ToPoint; - } - else - { - ToPoint->CopyStateFrom(FromPoint, true); - } - } - } - - SetDefaultValues(); - - return true; - } - - return false; -} - -UHoudiniParameterRampColor::UHoudiniParameterRampColor(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer), - bCaching(false), - NumDefaultPoints(-1) -{ - ParmType = EHoudiniParameterType::ColorRamp; -} - - -UHoudiniParameterRampColor * -UHoudiniParameterRampColor::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampColor::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterRampColor * HoudiniParameter = NewObject< UHoudiniParameterRampColor >( - InOuter, UHoudiniParameterRampColor::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniParameter->SetParameterType(EHoudiniParameterType::ColorRamp); - - HoudiniParameter->NumDefaultPoints = -1; - - HoudiniParameter->bCaching = false; - - return HoudiniParameter; -} - - -bool -UHoudiniParameterRampFloat::IsDefault() const -{ - if (NumDefaultPoints < 0) - return true; - - if (NumDefaultPoints != Points.Num()) - { - return false; - } - - TArray Positions = DefaultPositions; - TArray Values = DefaultValues; - TArray Choices = DefaultChoices; - - for (auto & NextPt : Points) - { - if (!NextPt) - return false; - - bool bFoundMatch = false; - for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) - { - if (Positions[DefaultIdx] == NextPt->Position && - Values[DefaultIdx] == NextPt->Value && - Choices[DefaultIdx] == (int32)NextPt->Interpolation) - { - Positions.RemoveAt(DefaultIdx); - Values.RemoveAt(DefaultIdx); - Choices.RemoveAt(DefaultIdx); - bFoundMatch = true; - } - } - - if (!bFoundMatch) - return false; - } - - if (Positions.Num() > 0) - return false; - - return true; -} - -void UHoudiniParameterRampColor::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) -{ - UHoudiniParameterRampColor* FromParameter = Cast(InParameter); - check(FromParameter); - - TArray PrevCachedPoints = CachedPoints; - TArray PrevPoints = Points; - - Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); - - CachedPoints = PrevCachedPoints; - Points = PrevPoints; - - - auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) - { - const int32 NumPts = FromArray.Num(); - - ToArray.SetNum(NumPts); - - for(int32 i = 0; i < NumPts; ++i) - { - UHoudiniParameterRampColorPoint* FromPoint = FromArray[i]; - UHoudiniParameterRampColorPoint* ToPoint = ToArray[i]; - - check(FromPoint); - - if (ToPoint) - { - // Ensure the destination point is outered to this parameter - bool bIsValid = ToPoint->GetOuter() == NewOuter; - if (!bIsValid) - ToPoint = nullptr; - } - - if (!ToPoint) - { - // Duplicate a new copy using FromPoint - ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); - } - else - { - // We have a valid point that we can reuse. Simply copy state. - ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); - } - - ToArray[i] = ToPoint; - } - }; - - CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); - CopyPointsStateFn(FromParameter->Points, Points, this); - - if (InClearFlags != RF_NoFlags) - ClearFlags( InClearFlags ); - if (InSetFlags != RF_NoFlags) - SetFlags(InSetFlags); -} - -void UHoudiniParameterRampColor::RemapParameters(const TMap& ParameterMapping) -{ - Super::RemapParameters(ParameterMapping); - - AActor* OuterActor = GetTypedOuter(); - - - for(UHoudiniParameterRampColorPoint* CurrentPoint : Points) - { - CurrentPoint->RemapParameters(ParameterMapping); - } - - for(UHoudiniParameterRampColorPoint* CurrentPoint : CachedPoints) - { - CurrentPoint->RemapParameters(ParameterMapping); - } -} - -bool -UHoudiniParameterRampColor::UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex) -{ - // Copy existing points to NewPoints - TArray NewPoints; - const int32 NumInstances = GetInstanceCount(); - NewPoints.Reserve(NumInstances); - for (UHoudiniParameterRampColorPoint* const PointData : Points) - { - if (!IsValid(PointData)) - continue; - - PointData->InstanceIndex = NewPoints.Num(); - PointData->PositionParentParm = nullptr; - PointData->ValueParentParm = nullptr; - PointData->InterpolationParentParm = nullptr; - NewPoints.Add(PointData); - - // We don't need more than NumInstances points in the array - if (NewPoints.Num() == NumInstances) - break; - } - - // Loop over InParameters and look for children of this ramp - int32 CurrentInstanceIndex = 0; - const int32 NumParameters = InParameters.Num(); - for (int32 Index = InStartParamIndex; Index < NumParameters; ++Index) - { - UHoudiniParameter* const Param = InParameters[Index]; - if (!IsValid(Param)) - continue; - - if (!Param->GetIsChildOfMultiParm() || Param->GetParentParmId() != ParmId) - continue; - - const EHoudiniParameterType ParamType = Param->GetParameterType(); - if (ParamType != EHoudiniParameterType::Float && ParamType != EHoudiniParameterType::Color && - ParamType != EHoudiniParameterType::IntChoice) - continue; - - // Ensure we have a valid point object at the current index - UHoudiniParameterRampColorPoint* Point = nullptr; - if (!NewPoints.IsValidIndex(CurrentInstanceIndex)) - { - Point = NewObject(this, FName(), this->GetMaskedFlags(RF_PropagateToSubObjects)); - Point->InstanceIndex = CurrentInstanceIndex; - NewPoints.Add(Point); - } - else - { - Point = NewPoints[CurrentInstanceIndex]; - } - - if (ParamType == EHoudiniParameterType::Float) - { - UHoudiniParameterFloat* FloatParameter = Cast(Param); - if (FloatParameter) - { - //*****Float Parameter (position)*****// - if (!Point->PositionParentParm) - { - if (FloatParameter->GetNumberOfValues() <= 0) - continue; - // Set the float ramp point's position parent parm, and value - Point->PositionParentParm = FloatParameter; - Point->SetPosition(FloatParameter->GetValuesPtr()[0]); - } - } - } - else if (ParamType == EHoudiniParameterType::Color) - { - UHoudiniParameterColor* ColorParameter = Cast(Param); - if (ColorParameter) - { - //*****Color Parameter (value)*****// - Point->ValueParentParm = ColorParameter; - Point->SetValue(ColorParameter->GetColorValue()); - } - } - //*****Choice parameter (Interpolation)*****// - else if (ParamType == EHoudiniParameterType::IntChoice) - { - UHoudiniParameterChoice* ChoiceParameter = Cast(Param); - if (ChoiceParameter) - { - Point->InterpolationParentParm = ChoiceParameter; - Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValueIndex())); - CurrentInstanceIndex++; - } - } - } - - //*****All ramp points have been parsed, finish!*****// - if (NewPoints.Num() == NumInstances) - { - NewPoints.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) { - return P1.Position < P2.Position; - }); - - Points = MoveTemp(NewPoints); - - // Not caching, points are synced, update cached points - if (!bCaching) - { - const int32 NumPoints = Points.Num(); - CachedPoints.SetNumZeroed(NumPoints); - - for (int32 i = 0; i < NumPoints; ++i) - { - UHoudiniParameterRampColorPoint* const FromPoint = Points[i]; - UHoudiniParameterRampColorPoint* ToPoint = CachedPoints[i]; - - // Nothing we can do/copy if FromPoint is null/pending kill - if (!IsValid(FromPoint)) - continue; - - if (!IsValid(ToPoint)) - { - ToPoint = FromPoint->DuplicateAndCopyState(this, RF_NoFlags, GetMaskedFlags(RF_PropagateToSubObjects)); - CachedPoints[i] = ToPoint; - } - else - { - ToPoint->CopyStateFrom(FromPoint, true); - } - } - } - - SetDefaultValues(); - - return true; - } - - return false; -} - -bool -UHoudiniParameterRampColor::IsDefault() const -{ - if (NumDefaultPoints < 0) - return true; - - if (NumDefaultPoints != Points.Num()) - return false; - - TArray Positions = DefaultPositions; - TArray Values = DefaultValues; - TArray Choices = DefaultChoices; - - for (auto & NextPt : Points) - { - if (!NextPt) - return false; - - bool bFoundMatch = false; - for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) - { - if (Positions[DefaultIdx] == NextPt->Position && - Values[DefaultIdx] == NextPt->Value && - Choices[DefaultIdx] == (int32)NextPt->Interpolation) - { - Positions.RemoveAt(DefaultIdx); - Values.RemoveAt(DefaultIdx); - Choices.RemoveAt(DefaultIdx); - bFoundMatch = true; - } - } - - if (!bFoundMatch) - return false; - } - - if (Positions.Num() > 0) - return false; - - return true; -} - - -void -UHoudiniParameterRampColor::SetDefaultValues() -{ - if (NumDefaultPoints >= 0) - return; - - - if (DefaultPositions.Num() > 0) - return; - - DefaultPositions.Empty(); - DefaultValues.Empty(); - DefaultChoices.Empty(); - - for (auto & NextPoint : Points) - { - if (!NextPoint) - continue; - - DefaultPositions.Add(NextPoint->Position); - DefaultValues.Add(NextPoint->Value); - DefaultChoices.Add((int32)NextPoint->Interpolation); - - } - - NumDefaultPoints = Points.Num(); -} - -void -UHoudiniParameterRampFloat::SetDefaultValues() -{ - if (DefaultPositions.Num() > 0) - return; - - DefaultPositions.Empty(); - DefaultValues.Empty(); - DefaultChoices.Empty(); - - for (auto & NextPoint : Points) - { - if (!NextPoint) - continue; - - DefaultPositions.Add(NextPoint->Position); - DefaultValues.Add(NextPoint->Value); - DefaultChoices.Add((int32)NextPoint->Interpolation); - - } - - NumDefaultPoints = Points.Num(); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterRamp.h" +#include "HoudiniParameter.h" +#include "HoudiniParameterFloat.h" +#include "HoudiniParameterColor.h" +#include "HoudiniParameterChoice.h" + +#include "UObject/UnrealType.h" + + +void +UHoudiniParameterRampFloatPoint::SetPosition(const float InPosition) +{ + if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Position = InPosition; + PositionParentParm->SetValueAt(InPosition, 0); + PositionParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampFloatPoint::SetValue(const float InValue) +{ + if (ValueParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Value = InValue; + ValueParentParm->SetValueAt(InValue, 0); + ValueParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampFloatPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) +{ + if (InterpolationParentParm) + { + Interpolation = InInterpolation; + InterpolationParentParm->SetIntValue((int32)InInterpolation); + InterpolationParentParm->UpdateStringValueFromInt(); + InterpolationParentParm->SetIsChildOfRamp(); + } +} + +UHoudiniParameterRampFloatPoint* +UHoudiniParameterRampFloatPoint::DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + UHoudiniParameterRampFloatPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); + + NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); + + return NewPoint; +} + +void +UHoudiniParameterRampFloatPoint::CopyStateFrom(UHoudiniParameterRampFloatPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + // Ensure this object's flags match the desired flags. + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +} + +void +UHoudiniParameterRampFloatPoint::RemapParameters( + const TMap& ParameterMapping) +{ + if (ParameterMapping.Contains(PositionParentParm)) + { + PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); + } + else + { + PositionParentParm = nullptr; + } + + if (ParameterMapping.Contains(ValueParentParm)) + { + ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); + } + else + { + ValueParentParm = nullptr; + } + + if (ParameterMapping.Contains(InterpolationParentParm)) + { + InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); + } + else + { + InterpolationParentParm = nullptr; + } +}; + + +void +UHoudiniParameterRampColorPoint::SetPosition(const float InPosition) +{ + if (PositionParentParm && PositionParentParm->GetNumberOfValues() > 0) + { + Position = InPosition; + PositionParentParm->SetValueAt(InPosition, 0); + PositionParentParm->SetIsChildOfRamp(); + } +}; + +void +UHoudiniParameterRampColorPoint::SetValue(const FLinearColor InValue) +{ + if (!ValueParentParm) + return; + + Value = InValue; + ValueParentParm->SetColorValue(InValue); + ValueParentParm->SetIsChildOfRamp(); +}; + +void +UHoudiniParameterRampColorPoint::SetInterpolation(const EHoudiniRampInterpolationType InInterpolation) +{ + if (!InterpolationParentParm) + return; + + Interpolation = InInterpolation; + InterpolationParentParm->SetIntValue((int32)InInterpolation); + InterpolationParentParm->UpdateStringValueFromInt(); + InterpolationParentParm->SetIsChildOfRamp(); +} +UHoudiniParameterRampColorPoint * UHoudiniParameterRampColorPoint::DuplicateAndCopyState(UObject * DestOuter, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + + UHoudiniParameterRampColorPoint* NewPoint = Cast(StaticDuplicateObject(this, DestOuter)); + + NewPoint->CopyStateFrom(this, false, InClearFlags, InSetFlags); + + return NewPoint; + +} +void UHoudiniParameterRampColorPoint::CopyStateFrom(UHoudiniParameterRampColorPoint * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ +#if WITH_EDITOR + PreEditChange(nullptr); +#endif + if (bCopyAllProperties) + { + UEngine::FCopyPropertiesForUnrelatedObjectsParams Params; + Params.bDoDelta = false; // Perform a deep copy + Params.bClearReferences = false; // References will be replaced afterwards. + UEngine::CopyPropertiesForUnrelatedObjects(InParameter, this, Params); + } + + // Ensure this object's flags match the desired flags. + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +#if WITH_EDITOR + PostEditChange(); +#endif +} + +void UHoudiniParameterRampColorPoint::RemapParameters(const TMap& ParameterMapping) +{ + if (ParameterMapping.Contains(PositionParentParm)) + { + PositionParentParm = CastChecked(ParameterMapping.FindChecked(PositionParentParm)); + } + else + { + PositionParentParm = nullptr; + } + + if (ParameterMapping.Contains(ValueParentParm)) + { + ValueParentParm = CastChecked(ParameterMapping.FindChecked(ValueParentParm)); + } + else + { + ValueParentParm = nullptr; + } + + if (ParameterMapping.Contains(InterpolationParentParm)) + { + InterpolationParentParm = CastChecked(ParameterMapping.FindChecked(InterpolationParentParm)); + } + else + { + InterpolationParentParm = nullptr; + } +} + + +UHoudiniParameterRampFloat::UHoudiniParameterRampFloat(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), + NumDefaultPoints(-1), + bCaching(false) +{ + ParmType = EHoudiniParameterType::FloatRamp; +} + +void +UHoudiniParameterRampFloat::OnPreCook() +{ + if (bCaching) + { + SyncCachedPoints(); + bCaching = false; + } +} + +UHoudiniParameterRampFloat * +UHoudiniParameterRampFloat::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampFloat::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterRampFloat * HoudiniParameter = NewObject< UHoudiniParameterRampFloat >( + InOuter, UHoudiniParameterRampFloat::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniParameter->SetParameterType(EHoudiniParameterType::FloatRamp); + + HoudiniParameter->NumDefaultPoints = -1; + + HoudiniParameter->bCaching = false; + + return HoudiniParameter; +} + +void UHoudiniParameterRampFloat::CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ +#if WITH_EDITOR + PreEditChange(nullptr); +#endif + + UHoudiniParameterRampFloat* FromParameter = Cast(InParameter); + check(FromParameter); + + TArray PrevCachedPoints = CachedPoints; + TArray PrevPoints = Points; + + Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); + + CachedPoints = PrevCachedPoints; + Points = PrevPoints; + + + auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) + { + const int32 NumPts = FromArray.Num(); + + ToArray.SetNum(NumPts); + + for(int32 i = 0; i < NumPts; ++i) + { + UHoudiniParameterRampFloatPoint* FromPoint = FromArray[i]; + UHoudiniParameterRampFloatPoint* ToPoint = ToArray[i]; + + check(FromPoint); + + if (ToPoint) + { + // Ensure the destination point is outered to this parameter + bool bIsValid = ToPoint->GetOuter() == NewOuter; + if (!bIsValid) + ToPoint = nullptr; + } + + if (!ToPoint) + { + // Duplicate a new copy using FromPoint + ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); + } + else + { + // We have a valid point that we can reuse. Simply copy state. + ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); + } + + ToArray[i] = ToPoint; + } + }; + + CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); + CopyPointsStateFn(FromParameter->Points, Points, this); + +#if WITH_EDITOR + PostEditChange(); +#endif +} + +void +UHoudiniParameterRampFloat::RemapParameters(const TMap& ParameterMapping) +{ + Super::RemapParameters(ParameterMapping); + + AActor* OuterActor = GetTypedOuter(); + + + for(UHoudiniParameterRampFloatPoint* CurrentPoint : Points) + { + CurrentPoint->RemapParameters(ParameterMapping); + } + + for(UHoudiniParameterRampFloatPoint* CurrentPoint : CachedPoints) + { + CurrentPoint->RemapParameters(ParameterMapping); + } +} + +void +UHoudiniParameterRampFloat::SyncCachedPoints() +{ + int32 Idx = 0; + + while (Idx < CachedPoints.Num() && Idx < Points.Num()) + { + UHoudiniParameterRampFloatPoint* &CachedPoint = CachedPoints[Idx]; + UHoudiniParameterRampFloatPoint* &CurrentPoint = Points[Idx]; + + if (!CachedPoint || !CurrentPoint) + continue; + + if (CachedPoint->GetPosition() != CurrentPoint->GetPosition()) + { + if (CurrentPoint->PositionParentParm) + { + CurrentPoint->SetPosition(CachedPoint->GetPosition()); + CurrentPoint->PositionParentParm->MarkChanged(true); + } + } + + if (CachedPoint->GetValue() != CurrentPoint->GetValue()) + { + if (CurrentPoint->ValueParentParm) + { + CurrentPoint->SetValue(CachedPoint->GetValue()); + CurrentPoint->ValueParentParm->MarkChanged(true); + } + } + + if (CachedPoint->GetInterpolation() != CurrentPoint->GetInterpolation()) + { + if (CurrentPoint->InterpolationParentParm) + { + CurrentPoint->SetInterpolation(CachedPoint->GetInterpolation()); + CurrentPoint->InterpolationParentParm->MarkChanged(true); + } + } + + Idx += 1; + } + + // Insert points + for (int32 IdxCachedPointLeft = Idx; IdxCachedPointLeft < CachedPoints.Num(); ++IdxCachedPointLeft) + { + UHoudiniParameterRampFloatPoint *&CachedPoint = CachedPoints[IdxCachedPointLeft]; + if (!CachedPoint) + continue; + + CreateInsertEvent(CachedPoint->Position, CachedPoint->Value, CachedPoint->Interpolation); + + MarkChanged(true); + } + + // Remove points + for (int32 IdxCurrentPointLeft = Idx; IdxCurrentPointLeft < Points.Num(); ++IdxCurrentPointLeft) + { + RemoveElement(IdxCurrentPointLeft); + + UHoudiniParameterRampFloatPoint* Point = Points[IdxCurrentPointLeft]; + + if (!Point) + continue; + + CreateDeleteEvent(Point->InstanceIndex); + + MarkChanged(true); + } +} + + +void +UHoudiniParameterRampFloat::CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) +{ + UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( + this, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!InsertEvent) + return; + + InsertEvent->SetFloatRampEvent(); + InsertEvent->SetInsertEvent(); + InsertEvent->InsertPosition = InPosition; + InsertEvent->InsertFloat = InValue; + InsertEvent->InsertInterpolation = InInterp; + + ModificationEvents.Add(InsertEvent); +} + +void +UHoudiniParameterRampFloat::CreateDeleteEvent(const int32 &InDeleteIndex) +{ + UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( + this, UHoudiniParameterRampModificationEvent::StaticClass()); + + if (!DeleteEvent) + return; + + DeleteEvent->SetFloatRampEvent(); + DeleteEvent->SetDeleteEvent(); + DeleteEvent->DeleteInstanceIndex = InDeleteIndex; + + ModificationEvents.Add(DeleteEvent); +} + +bool +UHoudiniParameterRampFloat::UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex) +{ + // Copy existing points to NewPoints + TArray NewPoints; + const int32 NumInstances = GetInstanceCount(); + NewPoints.Reserve(NumInstances); + for (UHoudiniParameterRampFloatPoint* const PointData : Points) + { + if (!IsValid(PointData)) + continue; + + PointData->InstanceIndex = NewPoints.Num(); + PointData->PositionParentParm = nullptr; + PointData->ValueParentParm = nullptr; + PointData->InterpolationParentParm = nullptr; + NewPoints.Add(PointData); + + // We don't need more than NumInstances points in the array + if (NewPoints.Num() == NumInstances) + break; + } + + // Loop over InParameters and look for children of this ramp + int32 CurrentInstanceIndex = 0; + const int32 NumParameters = InParameters.Num(); + for (int32 Index = InStartParamIndex; Index < NumParameters; ++Index) + { + UHoudiniParameter* const Param = InParameters[Index]; + if (!IsValid(Param)) + continue; + + if (!Param->GetIsChildOfMultiParm() || Param->GetParentParmId() != ParmId) + continue; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType != EHoudiniParameterType::Float && ParamType != EHoudiniParameterType::IntChoice) + continue; + + // Ensure we have a valid point object at the current index + UHoudiniParameterRampFloatPoint* Point = nullptr; + if (!NewPoints.IsValidIndex(CurrentInstanceIndex)) + { + Point = NewObject(this, FName(), this->GetMaskedFlags(RF_PropagateToSubObjects)); + Point->InstanceIndex = CurrentInstanceIndex; + NewPoints.Add(Point); + } + else + { + Point = NewPoints[CurrentInstanceIndex]; + } + + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParameter = Cast(Param); + if (FloatParameter) + { + //*****Float Parameter (position)*****// + if (!Point->PositionParentParm) + { + if (FloatParameter->GetNumberOfValues() <= 0) + continue; + // Set the float ramp point's position parent parm, and value + Point->PositionParentParm = FloatParameter; + Point->SetPosition(FloatParameter->GetValuesPtr()[0]); + } + //*****Float Parameter (value)*****// + else + { + if (FloatParameter->GetNumberOfValues() <= 0) + continue;; + Point->ValueParentParm = FloatParameter; + Point->SetValue(FloatParameter->GetValuesPtr()[0]); + } + } + } + //*****Choice parameter (Interpolation)*****// + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParameter = Cast(Param); + if (ChoiceParameter) + { + Point->InterpolationParentParm = ChoiceParameter; + Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValueIndex())); + CurrentInstanceIndex++; + } + } + } + + //*****All ramp points have been parsed, finish!*****// + if (NewPoints.Num() == NumInstances) + { + NewPoints.Sort([](const UHoudiniParameterRampFloatPoint& P1, const UHoudiniParameterRampFloatPoint& P2) { + return P1.Position < P2.Position; + }); + + Points = MoveTemp(NewPoints); + + // Not caching, points are synced, update cached points + if (!bCaching) + { + const int32 NumPoints = Points.Num(); + CachedPoints.SetNumZeroed(NumPoints); + + for (int32 i = 0; i < NumPoints; ++i) + { + UHoudiniParameterRampFloatPoint* const FromPoint = Points[i]; + UHoudiniParameterRampFloatPoint* ToPoint = CachedPoints[i]; + + // Nothing we can do/copy if FromPoint is null/pending kill + if (!IsValid(FromPoint)) + continue; + + if (!IsValid(ToPoint)) + { + ToPoint = FromPoint->DuplicateAndCopyState(this, RF_NoFlags, GetMaskedFlags(RF_PropagateToSubObjects)); + CachedPoints[i] = ToPoint; + } + else + { + ToPoint->CopyStateFrom(FromPoint, true); + } + } + } + + SetDefaultValues(); + + return true; + } + + return false; +} + +UHoudiniParameterRampColor::UHoudiniParameterRampColor(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer), + bCaching(false), + NumDefaultPoints(-1) +{ + ParmType = EHoudiniParameterType::ColorRamp; +} + + +UHoudiniParameterRampColor * +UHoudiniParameterRampColor::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterRamp_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterRampColor::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterRampColor * HoudiniParameter = NewObject< UHoudiniParameterRampColor >( + InOuter, UHoudiniParameterRampColor::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniParameter->SetParameterType(EHoudiniParameterType::ColorRamp); + + HoudiniParameter->NumDefaultPoints = -1; + + HoudiniParameter->bCaching = false; + + return HoudiniParameter; +} + + +bool +UHoudiniParameterRampFloat::IsDefault() const +{ + if (NumDefaultPoints < 0) + return true; + + if (NumDefaultPoints != Points.Num()) + { + return false; + } + + TArray Positions = DefaultPositions; + TArray Values = DefaultValues; + TArray Choices = DefaultChoices; + + for (auto & NextPt : Points) + { + if (!NextPt) + return false; + + bool bFoundMatch = false; + for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) + { + if (Positions[DefaultIdx] == NextPt->Position && + Values[DefaultIdx] == NextPt->Value && + Choices[DefaultIdx] == (int32)NextPt->Interpolation) + { + Positions.RemoveAt(DefaultIdx); + Values.RemoveAt(DefaultIdx); + Choices.RemoveAt(DefaultIdx); + bFoundMatch = true; + } + } + + if (!bFoundMatch) + return false; + } + + if (Positions.Num() > 0) + return false; + + return true; +} + +void UHoudiniParameterRampColor::CopyStateFrom(UHoudiniParameter * InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags, EObjectFlags InSetFlags) +{ + UHoudiniParameterRampColor* FromParameter = Cast(InParameter); + check(FromParameter); + + TArray PrevCachedPoints = CachedPoints; + TArray PrevPoints = Points; + + Super::CopyStateFrom(InParameter, bCopyAllProperties, InClearFlags, InSetFlags); + + CachedPoints = PrevCachedPoints; + Points = PrevPoints; + + + auto CopyPointsStateFn = [InClearFlags, InSetFlags] (TArray& FromArray, TArray& ToArray, UObject* NewOuter) + { + const int32 NumPts = FromArray.Num(); + + ToArray.SetNum(NumPts); + + for(int32 i = 0; i < NumPts; ++i) + { + UHoudiniParameterRampColorPoint* FromPoint = FromArray[i]; + UHoudiniParameterRampColorPoint* ToPoint = ToArray[i]; + + check(FromPoint); + + if (ToPoint) + { + // Ensure the destination point is outered to this parameter + bool bIsValid = ToPoint->GetOuter() == NewOuter; + if (!bIsValid) + ToPoint = nullptr; + } + + if (!ToPoint) + { + // Duplicate a new copy using FromPoint + ToPoint = FromPoint->DuplicateAndCopyState(NewOuter, InClearFlags, InSetFlags); + } + else + { + // We have a valid point that we can reuse. Simply copy state. + ToPoint->CopyStateFrom(FromPoint, true, InClearFlags, InSetFlags); + } + + ToArray[i] = ToPoint; + } + }; + + CopyPointsStateFn(FromParameter->CachedPoints, CachedPoints, this); + CopyPointsStateFn(FromParameter->Points, Points, this); + + if (InClearFlags != RF_NoFlags) + ClearFlags( InClearFlags ); + if (InSetFlags != RF_NoFlags) + SetFlags(InSetFlags); +} + +void UHoudiniParameterRampColor::RemapParameters(const TMap& ParameterMapping) +{ + Super::RemapParameters(ParameterMapping); + + AActor* OuterActor = GetTypedOuter(); + + + for(UHoudiniParameterRampColorPoint* CurrentPoint : Points) + { + CurrentPoint->RemapParameters(ParameterMapping); + } + + for(UHoudiniParameterRampColorPoint* CurrentPoint : CachedPoints) + { + CurrentPoint->RemapParameters(ParameterMapping); + } +} + +bool +UHoudiniParameterRampColor::UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex) +{ + // Copy existing points to NewPoints + TArray NewPoints; + const int32 NumInstances = GetInstanceCount(); + NewPoints.Reserve(NumInstances); + for (UHoudiniParameterRampColorPoint* const PointData : Points) + { + if (!IsValid(PointData)) + continue; + + PointData->InstanceIndex = NewPoints.Num(); + PointData->PositionParentParm = nullptr; + PointData->ValueParentParm = nullptr; + PointData->InterpolationParentParm = nullptr; + NewPoints.Add(PointData); + + // We don't need more than NumInstances points in the array + if (NewPoints.Num() == NumInstances) + break; + } + + // Loop over InParameters and look for children of this ramp + int32 CurrentInstanceIndex = 0; + const int32 NumParameters = InParameters.Num(); + for (int32 Index = InStartParamIndex; Index < NumParameters; ++Index) + { + UHoudiniParameter* const Param = InParameters[Index]; + if (!IsValid(Param)) + continue; + + if (!Param->GetIsChildOfMultiParm() || Param->GetParentParmId() != ParmId) + continue; + + const EHoudiniParameterType ParamType = Param->GetParameterType(); + if (ParamType != EHoudiniParameterType::Float && ParamType != EHoudiniParameterType::Color && + ParamType != EHoudiniParameterType::IntChoice) + continue; + + // Ensure we have a valid point object at the current index + UHoudiniParameterRampColorPoint* Point = nullptr; + if (!NewPoints.IsValidIndex(CurrentInstanceIndex)) + { + Point = NewObject(this, FName(), this->GetMaskedFlags(RF_PropagateToSubObjects)); + Point->InstanceIndex = CurrentInstanceIndex; + NewPoints.Add(Point); + } + else + { + Point = NewPoints[CurrentInstanceIndex]; + } + + if (ParamType == EHoudiniParameterType::Float) + { + UHoudiniParameterFloat* FloatParameter = Cast(Param); + if (FloatParameter) + { + //*****Float Parameter (position)*****// + if (!Point->PositionParentParm) + { + if (FloatParameter->GetNumberOfValues() <= 0) + continue; + // Set the float ramp point's position parent parm, and value + Point->PositionParentParm = FloatParameter; + Point->SetPosition(FloatParameter->GetValuesPtr()[0]); + } + } + } + else if (ParamType == EHoudiniParameterType::Color) + { + UHoudiniParameterColor* ColorParameter = Cast(Param); + if (ColorParameter) + { + //*****Color Parameter (value)*****// + Point->ValueParentParm = ColorParameter; + Point->SetValue(ColorParameter->GetColorValue()); + } + } + //*****Choice parameter (Interpolation)*****// + else if (ParamType == EHoudiniParameterType::IntChoice) + { + UHoudiniParameterChoice* ChoiceParameter = Cast(Param); + if (ChoiceParameter) + { + Point->InterpolationParentParm = ChoiceParameter; + Point->SetInterpolation(UHoudiniParameter::GetHoudiniInterpMethodFromInt(ChoiceParameter->GetIntValueIndex())); + CurrentInstanceIndex++; + } + } + } + + //*****All ramp points have been parsed, finish!*****// + if (NewPoints.Num() == NumInstances) + { + NewPoints.Sort([](const UHoudiniParameterRampColorPoint& P1, const UHoudiniParameterRampColorPoint& P2) { + return P1.Position < P2.Position; + }); + + Points = MoveTemp(NewPoints); + + // Not caching, points are synced, update cached points + if (!bCaching) + { + const int32 NumPoints = Points.Num(); + CachedPoints.SetNumZeroed(NumPoints); + + for (int32 i = 0; i < NumPoints; ++i) + { + UHoudiniParameterRampColorPoint* const FromPoint = Points[i]; + UHoudiniParameterRampColorPoint* ToPoint = CachedPoints[i]; + + // Nothing we can do/copy if FromPoint is null/pending kill + if (!IsValid(FromPoint)) + continue; + + if (!IsValid(ToPoint)) + { + ToPoint = FromPoint->DuplicateAndCopyState(this, RF_NoFlags, GetMaskedFlags(RF_PropagateToSubObjects)); + CachedPoints[i] = ToPoint; + } + else + { + ToPoint->CopyStateFrom(FromPoint, true); + } + } + } + + SetDefaultValues(); + + return true; + } + + return false; +} + +bool +UHoudiniParameterRampColor::IsDefault() const +{ + if (NumDefaultPoints < 0) + return true; + + if (NumDefaultPoints != Points.Num()) + return false; + + TArray Positions = DefaultPositions; + TArray Values = DefaultValues; + TArray Choices = DefaultChoices; + + for (auto & NextPt : Points) + { + if (!NextPt) + return false; + + bool bFoundMatch = false; + for (int32 DefaultIdx = 0; DefaultIdx < Positions.Num(); ++DefaultIdx) + { + if (Positions[DefaultIdx] == NextPt->Position && + Values[DefaultIdx] == NextPt->Value && + Choices[DefaultIdx] == (int32)NextPt->Interpolation) + { + Positions.RemoveAt(DefaultIdx); + Values.RemoveAt(DefaultIdx); + Choices.RemoveAt(DefaultIdx); + bFoundMatch = true; + } + } + + if (!bFoundMatch) + return false; + } + + if (Positions.Num() > 0) + return false; + + return true; +} + + +void +UHoudiniParameterRampColor::SetDefaultValues() +{ + if (NumDefaultPoints >= 0) + return; + + + if (DefaultPositions.Num() > 0) + return; + + DefaultPositions.Empty(); + DefaultValues.Empty(); + DefaultChoices.Empty(); + + for (auto & NextPoint : Points) + { + if (!NextPoint) + continue; + + DefaultPositions.Add(NextPoint->Position); + DefaultValues.Add(NextPoint->Value); + DefaultChoices.Add((int32)NextPoint->Interpolation); + + } + + NumDefaultPoints = Points.Num(); +} + +void +UHoudiniParameterRampFloat::SetDefaultValues() +{ + if (DefaultPositions.Num() > 0) + return; + + DefaultPositions.Empty(); + DefaultValues.Empty(); + DefaultChoices.Empty(); + + for (auto & NextPoint : Points) + { + if (!NextPoint) + continue; + + DefaultPositions.Add(NextPoint->Position); + DefaultValues.Add(NextPoint->Value); + DefaultChoices.Add((int32)NextPoint->Interpolation); + + } + + NumDefaultPoints = Points.Num(); + } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h index 5f08a9901..197a87034 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterRamp.h @@ -1,330 +1,330 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Curves/CurveFloat.h" -#include "Curves/CurveLinearColor.h" -#include "HoudiniParameterMultiParm.h" -#include "HoudiniParameterRamp.generated.h" - -class UHoudiniParameterRampFloat; -class UHoudiniParameterRampColor; -class UHoudiniParameter; -class UHoudiniParameterFloat; -class UHoudiniParameterChoice; -class UHoudiniParameterColor; - -UENUM() -enum class EHoudiniRampPointConstructStatus : uint8 -{ - None, - - INITIALIZED, - POSITION_INSERTED, - VALUE_INSERTED, - INTERPTYPE_INSERTED -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampModificationEvent : public UObject -{ - GENERATED_BODY() -public: - FORCEINLINE - void SetInsertEvent() { bIsInsertEvent = true; }; - - FORCEINLINE - void SetDeleteEvent() { bIsInsertEvent = false; }; - - FORCEINLINE - void SetFloatRampEvent() { bIsFloatRamp = true; }; - - FORCEINLINE - void SetColorRampEvent() { bIsFloatRamp = false; }; - - FORCEINLINE - bool IsInsertEvent() const { return bIsInsertEvent; }; - - FORCEINLINE - bool IsDeleteEvent() const { return !bIsInsertEvent; }; - - FORCEINLINE - bool IsFloatRampEvent() { return bIsFloatRamp; }; - - FORCEINLINE - bool IsColorRampEvent() { return !bIsFloatRamp; }; - - -private: - UPROPERTY() - bool bIsInsertEvent = false; - - UPROPERTY() - bool bIsFloatRamp = false; - -public: - UPROPERTY() - int32 DeleteInstanceIndex = -1; - - UPROPERTY() - float InsertPosition; - - UPROPERTY() - float InsertFloat; - - UPROPERTY() - FLinearColor InsertColor; - - UPROPERTY() - EHoudiniRampInterpolationType InsertInterpolation; -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloatPoint : public UObject -{ - GENERATED_BODY() - -public: - - UPROPERTY() - float Position; - - UPROPERTY() - float Value; - - UPROPERTY() - EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; - - UPROPERTY() - int32 InstanceIndex = -1; - - UPROPERTY() - UHoudiniParameterFloat* PositionParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterFloat* ValueParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterChoice* InterpolationParentParm = nullptr; - - FORCEINLINE - float GetPosition() const { return Position; }; - - void SetPosition(const float InPosition); - - FORCEINLINE - float GetValue() const { return Value; }; - - void SetValue(const float InValue); - - FORCEINLINE - EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; - - void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); - - UHoudiniParameterRampFloatPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); - - void CopyStateFrom(UHoudiniParameterRampFloatPoint* InParameter, bool bCopyAllProperties, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); - - void RemapParameters(const TMap& ParameterMapping); - -}; - -UCLASS(DefaultToInstanced) -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColorPoint : public UObject -{ - GENERATED_BODY() - -public: - - UPROPERTY() - float Position; - - UPROPERTY() - FLinearColor Value; - - UPROPERTY() - EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; - - UPROPERTY() - int32 InstanceIndex = -1; - - UPROPERTY() - UHoudiniParameterFloat * PositionParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterColor* ValueParentParm = nullptr; - - UPROPERTY() - UHoudiniParameterChoice* InterpolationParentParm = nullptr; - - FORCEINLINE - float GetPosition() const { return Position; }; - - void SetPosition(const float InPosition); - FORCEINLINE - FLinearColor GetValue() const { return Value; }; - - void SetValue(const FLinearColor InValue); - - FORCEINLINE - EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; - - void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); - - UHoudiniParameterRampColorPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - void CopyStateFrom(UHoudiniParameterRampColorPoint* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); - - void RemapParameters(const TMap& ParameterMapping); -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParameterMultiParm -{ - GENERATED_UCLASS_BODY() - -public: - - virtual void OnPreCook() override; - - // Create instance of this class. - static UHoudiniParameterRampFloat * Create( - UObject* Outer, - const FString& ParamName); - - FORCEINLINE - bool IsCaching() const { return bCaching; }; - - FORCEINLINE - void SetCaching(const bool bInCaching) { bCaching = bInCaching; }; - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; - - virtual void RemapParameters(const TMap& ParameterMapping) override; - - void SyncCachedPoints(); - - void CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); - - void CreateDeleteEvent(const int32 &InDeleteIndex); - - /** - * Update/populates the Points array from InParameters. - * @param InParameters An array of parameters containing this ramp multiparm's instances (the parameters for each - * of its points). - * @param InStartParamIndex The index in InParameters where this ramp multiparm's child parameters start. - * @return true if we found enough parameters to build a number of points == NumInstances(). - */ - bool UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex); - - UPROPERTY() - TArray Points; - - UPROPERTY() - TArray CachedPoints; - - UPROPERTY() - TArray DefaultPositions; - - UPROPERTY() - TArray DefaultValues; - - UPROPERTY() - TArray DefaultChoices; - - UPROPERTY() - int32 NumDefaultPoints; - - UPROPERTY() - bool bCaching; - - UPROPERTY() - TArray ModificationEvents; - - bool IsDefault() const override; - - void SetDefaultValues(); - -}; - - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColor : public UHoudiniParameterMultiParm -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterRampColor * Create( - UObject* Outer, - const FString& ParamName); - - virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; - - virtual void RemapParameters(const TMap& ParameterMapping) override; - - /** - * Update/populates the Points array from InParameters. - * @param InParameters An array of parameters containing this ramp multiparm's instances (the parameters for each - * of its points). - * @param InStartParamIndex The index in InParameters where this ramp multiparm's child parameters start. - * @return true if we found enough parameters to build a number of points == NumInstances(). - */ - bool UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex); - - UPROPERTY(Instanced) - TArray Points; - - UPROPERTY() - bool bCaching; - - UPROPERTY(Instanced) - TArray CachedPoints; - - UPROPERTY() - TArray DefaultPositions; - - UPROPERTY() - TArray DefaultValues; - - UPROPERTY() - TArray DefaultChoices; - - UPROPERTY() - int32 NumDefaultPoints; - - UPROPERTY() - TArray ModificationEvents; - - bool IsDefault() const override; - - void SetDefaultValues(); - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Curves/CurveFloat.h" +#include "Curves/CurveLinearColor.h" +#include "HoudiniParameterMultiParm.h" +#include "HoudiniParameterRamp.generated.h" + +class UHoudiniParameterRampFloat; +class UHoudiniParameterRampColor; +class UHoudiniParameter; +class UHoudiniParameterFloat; +class UHoudiniParameterChoice; +class UHoudiniParameterColor; + +UENUM() +enum class EHoudiniRampPointConstructStatus : uint8 +{ + None, + + INITIALIZED, + POSITION_INSERTED, + VALUE_INSERTED, + INTERPTYPE_INSERTED +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampModificationEvent : public UObject +{ + GENERATED_BODY() +public: + FORCEINLINE + void SetInsertEvent() { bIsInsertEvent = true; }; + + FORCEINLINE + void SetDeleteEvent() { bIsInsertEvent = false; }; + + FORCEINLINE + void SetFloatRampEvent() { bIsFloatRamp = true; }; + + FORCEINLINE + void SetColorRampEvent() { bIsFloatRamp = false; }; + + FORCEINLINE + bool IsInsertEvent() const { return bIsInsertEvent; }; + + FORCEINLINE + bool IsDeleteEvent() const { return !bIsInsertEvent; }; + + FORCEINLINE + bool IsFloatRampEvent() { return bIsFloatRamp; }; + + FORCEINLINE + bool IsColorRampEvent() { return !bIsFloatRamp; }; + + +private: + UPROPERTY() + bool bIsInsertEvent = false; + + UPROPERTY() + bool bIsFloatRamp = false; + +public: + UPROPERTY() + int32 DeleteInstanceIndex = -1; + + UPROPERTY() + float InsertPosition; + + UPROPERTY() + float InsertFloat; + + UPROPERTY() + FLinearColor InsertColor; + + UPROPERTY() + EHoudiniRampInterpolationType InsertInterpolation; +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloatPoint : public UObject +{ + GENERATED_BODY() + +public: + + UPROPERTY() + float Position; + + UPROPERTY() + float Value; + + UPROPERTY() + EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; + + UPROPERTY() + int32 InstanceIndex = -1; + + UPROPERTY() + UHoudiniParameterFloat* PositionParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterFloat* ValueParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterChoice* InterpolationParentParm = nullptr; + + FORCEINLINE + float GetPosition() const { return Position; }; + + void SetPosition(const float InPosition); + + FORCEINLINE + float GetValue() const { return Value; }; + + void SetValue(const float InValue); + + FORCEINLINE + EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; + + void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); + + UHoudiniParameterRampFloatPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); + + void CopyStateFrom(UHoudiniParameterRampFloatPoint* InParameter, bool bCopyAllProperties, EObjectFlags ClearFlags=RF_NoFlags, EObjectFlags SetFlags=RF_NoFlags); + + void RemapParameters(const TMap& ParameterMapping); + +}; + +UCLASS(DefaultToInstanced) +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColorPoint : public UObject +{ + GENERATED_BODY() + +public: + + UPROPERTY() + float Position; + + UPROPERTY() + FLinearColor Value; + + UPROPERTY() + EHoudiniRampInterpolationType Interpolation = EHoudiniRampInterpolationType::InValid; + + UPROPERTY() + int32 InstanceIndex = -1; + + UPROPERTY() + UHoudiniParameterFloat * PositionParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterColor* ValueParentParm = nullptr; + + UPROPERTY() + UHoudiniParameterChoice* InterpolationParentParm = nullptr; + + FORCEINLINE + float GetPosition() const { return Position; }; + + void SetPosition(const float InPosition); + FORCEINLINE + FLinearColor GetValue() const { return Value; }; + + void SetValue(const FLinearColor InValue); + + FORCEINLINE + EHoudiniRampInterpolationType GetInterpolation() const { return Interpolation; }; + + void SetInterpolation(const EHoudiniRampInterpolationType InInterpolation); + + UHoudiniParameterRampColorPoint* DuplicateAndCopyState(UObject* DestOuter, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + void CopyStateFrom(UHoudiniParameterRampColorPoint* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags); + + void RemapParameters(const TMap& ParameterMapping); +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampFloat : public UHoudiniParameterMultiParm +{ + GENERATED_UCLASS_BODY() + +public: + + virtual void OnPreCook() override; + + // Create instance of this class. + static UHoudiniParameterRampFloat * Create( + UObject* Outer, + const FString& ParamName); + + FORCEINLINE + bool IsCaching() const { return bCaching; }; + + FORCEINLINE + void SetCaching(const bool bInCaching) { bCaching = bInCaching; }; + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; + + virtual void RemapParameters(const TMap& ParameterMapping) override; + + void SyncCachedPoints(); + + void CreateInsertEvent(const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp); + + void CreateDeleteEvent(const int32 &InDeleteIndex); + + /** + * Update/populates the Points array from InParameters. + * @param InParameters An array of parameters containing this ramp multiparm's instances (the parameters for each + * of its points). + * @param InStartParamIndex The index in InParameters where this ramp multiparm's child parameters start. + * @return true if we found enough parameters to build a number of points == NumInstances(). + */ + bool UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex); + + UPROPERTY() + TArray Points; + + UPROPERTY() + TArray CachedPoints; + + UPROPERTY() + TArray DefaultPositions; + + UPROPERTY() + TArray DefaultValues; + + UPROPERTY() + TArray DefaultChoices; + + UPROPERTY() + int32 NumDefaultPoints; + + UPROPERTY() + bool bCaching; + + UPROPERTY() + TArray ModificationEvents; + + bool IsDefault() const override; + + void SetDefaultValues(); + +}; + + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterRampColor : public UHoudiniParameterMultiParm +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterRampColor * Create( + UObject* Outer, + const FString& ParamName); + + virtual void CopyStateFrom(UHoudiniParameter* InParameter, bool bCopyAllProperties, EObjectFlags InClearFlags=RF_NoFlags, EObjectFlags InSetFlags=RF_NoFlags) override; + + virtual void RemapParameters(const TMap& ParameterMapping) override; + + /** + * Update/populates the Points array from InParameters. + * @param InParameters An array of parameters containing this ramp multiparm's instances (the parameters for each + * of its points). + * @param InStartParamIndex The index in InParameters where this ramp multiparm's child parameters start. + * @return true if we found enough parameters to build a number of points == NumInstances(). + */ + bool UpdatePointsArray(const TArray& InParameters, const int32 InStartParamIndex); + + UPROPERTY(Instanced) + TArray Points; + + UPROPERTY() + bool bCaching; + + UPROPERTY(Instanced) + TArray CachedPoints; + + UPROPERTY() + TArray DefaultPositions; + + UPROPERTY() + TArray DefaultValues; + + UPROPERTY() + TArray DefaultChoices; + + UPROPERTY() + int32 NumDefaultPoints; + + UPROPERTY() + TArray ModificationEvents; + + bool IsDefault() const override; + + void SetDefaultValues(); + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp index 4babd80df..9fe3a8db1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.cpp @@ -1,51 +1,51 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterSeparator.h" - -UHoudiniParameterSeparator::UHoudiniParameterSeparator(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Separator; -} - -UHoudiniParameterSeparator * -UHoudiniParameterSeparator::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterSeparator_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterSeparator::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterSeparator * HoudiniAssetParameter = NewObject< UHoudiniParameterSeparator >( - InOuter, UHoudiniParameterSeparator::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Separator); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterSeparator.h" + +UHoudiniParameterSeparator::UHoudiniParameterSeparator(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Separator; +} + +UHoudiniParameterSeparator * +UHoudiniParameterSeparator::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterSeparator_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterSeparator::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterSeparator * HoudiniAssetParameter = NewObject< UHoudiniParameterSeparator >( + InOuter, UHoudiniParameterSeparator::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Separator); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h index 8f0c00821..f408b88dc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterSeparator.h @@ -1,44 +1,44 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterSeparator.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterSeparator : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterSeparator * Create( - UObject* Outer, - const FString& ParamName); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterSeparator.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterSeparator : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterSeparator * Create( + UObject* Outer, + const FString& ParamName); }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp index 352a6de41..1751a07a8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp @@ -1,136 +1,136 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterString.h" - -UHoudiniParameterString::UHoudiniParameterString(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , bIsAssetRef(false) -{ - ParmType = EHoudiniParameterType::String; -} - -UHoudiniParameterString * -UHoudiniParameterString::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterString_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterString::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterString * HoudiniAssetParameter = NewObject< UHoudiniParameterString >( - InOuter, UHoudiniParameterString::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::String); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterString::SetValueAt(const FString& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index].Equals(InValue, ESearchCase::CaseSensitive)) - return false; - - Values[Index] = InValue; - - return true; -} - -void -UHoudiniParameterString::SetAssetAt(UObject* InObject, const uint32& Index) -{ - if (!ChosenAssets.IsValidIndex(Index)) - return; - - ChosenAssets[Index] = InObject; - -} - -FString -UHoudiniParameterString::GetAssetReference(UObject* InObject) -{ - // Get the asset reference string for a given UObject - if (!InObject || InObject->IsPendingKill()) - return FString(); - - // Start by getting the Object's full name - FString AssetReference = InObject->GetFullName(); - - // Replace the first space to '\'' - for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) - { - if (AssetReference[Itr] == ' ') - { - AssetReference[Itr] = '\''; - break; - } - } - - // Attach another '\'' to the end - AssetReference += FString("'"); - - return AssetReference; -} - -void -UHoudiniParameterString::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} - -bool -UHoudiniParameterString::IsDefaultValueAtIndex(const int32& Idx) const -{ - if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) - return true; - - return Values[Idx] == DefaultValues[Idx]; -} - -bool -UHoudiniParameterString::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!IsDefaultValueAtIndex(Idx)) - return false; - } - - return true; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterString.h" + +UHoudiniParameterString::UHoudiniParameterString(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , bIsAssetRef(false) +{ + ParmType = EHoudiniParameterType::String; +} + +UHoudiniParameterString * +UHoudiniParameterString::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterString_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterString::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterString * HoudiniAssetParameter = NewObject< UHoudiniParameterString >( + InOuter, UHoudiniParameterString::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::String); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterString::SetValueAt(const FString& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index].Equals(InValue, ESearchCase::CaseSensitive)) + return false; + + Values[Index] = InValue; + + return true; +} + +void +UHoudiniParameterString::SetAssetAt(UObject* InObject, const uint32& Index) +{ + if (!ChosenAssets.IsValidIndex(Index)) + return; + + ChosenAssets[Index] = InObject; + +} + +FString +UHoudiniParameterString::GetAssetReference(UObject* InObject) +{ + // Get the asset reference string for a given UObject + if (!InObject || InObject->IsPendingKill()) + return FString(); + + // Start by getting the Object's full name + FString AssetReference = InObject->GetFullName(); + + // Replace the first space to '\'' + for (int32 Itr = 0; Itr < AssetReference.Len(); Itr++) + { + if (AssetReference[Itr] == ' ') + { + AssetReference[Itr] = '\''; + break; + } + } + + // Attach another '\'' to the end + AssetReference += FString("'"); + + return AssetReference; +} + +void +UHoudiniParameterString::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} + +bool +UHoudiniParameterString::IsDefaultValueAtIndex(const int32& Idx) const +{ + if (!Values.IsValidIndex(Idx) || !DefaultValues.IsValidIndex(Idx)) + return true; + + return Values[Idx] == DefaultValues[Idx]; +} + +bool +UHoudiniParameterString::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!IsDefaultValueAtIndex(Idx)) + return false; + } + + return true; } \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h index d43014f81..1f4c13bdc 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.h @@ -1,91 +1,91 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" - -#include "HoudiniParameterString.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterString : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterString * Create( - UObject* Outer, const FString& ParamName); - - // Accessor - FString GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? Values[Index] : FString(); }; - - UObject* GetAssetAt(const uint32& Index) const { return ChosenAssets.IsValidIndex(Index) ? ChosenAssets[Index] : nullptr; }; - - int32 GetNumberOfValues() { return Values.Num(); }; - - bool IsAssetRef() const { return bIsAssetRef; }; - - bool IsDefaultValueAtIndex(const int32& Idx) const; - bool IsDefault() const override; - - // Mutators - void SetIsAssetRef(const bool& InIsAssetRef) { bIsAssetRef = InIsAssetRef; }; - - bool SetValueAt(const FString& InValue, const uint32& Index); - - void SetAssetAt(UObject* InObject, const uint32& Index); - - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNum(InNumValues); ChosenAssets.SetNum(InNumValues); }; - - TArray & GetChosenAssets() { return ChosenAssets; }; - - void SetDefaultValues(); - - // Utility - - // Get the asset reference string for a given UObject - static FString GetAssetReference(UObject* InObject); - - -protected: - - // Values of this property. - UPROPERTY() - TArray< FString > Values; - - UPROPERTY() - TArray< FString > DefaultValues; - - UPROPERTY() - TArray ChosenAssets; - - // Indicates this string parameter should be treated as an asset reference - // and display an object picker - UPROPERTY() - bool bIsAssetRef; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" + +#include "HoudiniParameterString.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterString : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterString * Create( + UObject* Outer, const FString& ParamName); + + // Accessor + FString GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? Values[Index] : FString(); }; + + UObject* GetAssetAt(const uint32& Index) const { return ChosenAssets.IsValidIndex(Index) ? ChosenAssets[Index] : nullptr; }; + + int32 GetNumberOfValues() { return Values.Num(); }; + + bool IsAssetRef() const { return bIsAssetRef; }; + + bool IsDefaultValueAtIndex(const int32& Idx) const; + bool IsDefault() const override; + + // Mutators + void SetIsAssetRef(const bool& InIsAssetRef) { bIsAssetRef = InIsAssetRef; }; + + bool SetValueAt(const FString& InValue, const uint32& Index); + + void SetAssetAt(UObject* InObject, const uint32& Index); + + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNum(InNumValues); ChosenAssets.SetNum(InNumValues); }; + + TArray & GetChosenAssets() { return ChosenAssets; }; + + void SetDefaultValues(); + + // Utility + + // Get the asset reference string for a given UObject + static FString GetAssetReference(UObject* InObject); + + +protected: + + // Values of this property. + UPROPERTY() + TArray< FString > Values; + + UPROPERTY() + TArray< FString > DefaultValues; + + UPROPERTY() + TArray ChosenAssets; + + // Indicates this string parameter should be treated as an asset reference + // and display an object picker + UPROPERTY() + bool bIsAssetRef; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp index 3e81d9ba0..5c7e1d205 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.cpp @@ -1,95 +1,95 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniParameterToggle.h" - -UHoudiniParameterToggle::UHoudiniParameterToggle(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) -{ - ParmType = EHoudiniParameterType::Toggle; -} - -UHoudiniParameterToggle * -UHoudiniParameterToggle::Create( - UObject* InOuter, - const FString& InParamName) -{ - FString ParamNameStr = "HoudiniParameterToggle_" + InParamName; - FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterToggle::StaticClass(), *ParamNameStr); - - // We need to create a new parameter - UHoudiniParameterToggle * HoudiniAssetParameter = NewObject< UHoudiniParameterToggle >( - InOuter, UHoudiniParameterToggle::StaticClass(), ParamName, RF_Public | RF_Transactional); - - HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Toggle); - //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); - - return HoudiniAssetParameter; -} - -bool -UHoudiniParameterToggle::SetValueAt(const bool& InValue, const uint32& Index) -{ - if (!Values.IsValidIndex(Index)) - return false; - - if (Values[Index] == 0 && !InValue) - return false; - - if (Values[Index] == 1 && InValue) - return false; - - Values[Index] = InValue ? 1 : 0; - return true; -} - -bool -UHoudiniParameterToggle::IsDefault() const -{ - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - if (!DefaultValues.IsValidIndex(Idx)) - break; - - if (Values[Idx] != DefaultValues[Idx]) - return false; - } - - return true; -} - -void -UHoudiniParameterToggle::SetDefaultValues() -{ - if (DefaultValues.Num() > 0) - return; - - DefaultValues.Empty(); - for (int32 Idx = 0; Idx < Values.Num(); ++Idx) - { - DefaultValues.Add(Values[Idx]); - } -} +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniParameterToggle.h" + +UHoudiniParameterToggle::UHoudiniParameterToggle(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) +{ + ParmType = EHoudiniParameterType::Toggle; +} + +UHoudiniParameterToggle * +UHoudiniParameterToggle::Create( + UObject* InOuter, + const FString& InParamName) +{ + FString ParamNameStr = "HoudiniParameterToggle_" + InParamName; + FName ParamName = MakeUniqueObjectName(InOuter, UHoudiniParameterToggle::StaticClass(), *ParamNameStr); + + // We need to create a new parameter + UHoudiniParameterToggle * HoudiniAssetParameter = NewObject< UHoudiniParameterToggle >( + InOuter, UHoudiniParameterToggle::StaticClass(), ParamName, RF_Public | RF_Transactional); + + HoudiniAssetParameter->SetParameterType(EHoudiniParameterType::Toggle); + //HoudiniAssetParameter->UpdateFromParmInfo(InParentParameter, InNodeId, ParmInfo); + + return HoudiniAssetParameter; +} + +bool +UHoudiniParameterToggle::SetValueAt(const bool& InValue, const uint32& Index) +{ + if (!Values.IsValidIndex(Index)) + return false; + + if (Values[Index] == 0 && !InValue) + return false; + + if (Values[Index] == 1 && InValue) + return false; + + Values[Index] = InValue ? 1 : 0; + return true; +} + +bool +UHoudiniParameterToggle::IsDefault() const +{ + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + if (!DefaultValues.IsValidIndex(Idx)) + break; + + if (Values[Idx] != DefaultValues[Idx]) + return false; + } + + return true; +} + +void +UHoudiniParameterToggle::SetDefaultValues() +{ + if (DefaultValues.Num() > 0) + return; + + DefaultValues.Empty(); + for (int32 Idx = 0; Idx < Values.Num(); ++Idx) + { + DefaultValues.Add(Values[Idx]); + } +} diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h index 8b141ff45..63209a9bf 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterToggle.h @@ -1,68 +1,68 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "HoudiniParameter.h" -#include "Styling/SlateTypes.h" -#include "HoudiniParameterToggle.generated.h" - -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniParameterToggle : public UHoudiniParameter -{ - GENERATED_UCLASS_BODY() - -public: - - // Create instance of this class. - static UHoudiniParameterToggle * Create( - UObject* Outer, - const FString& ParamName); - - // Accessor - bool GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? (bool)Values[Index] : false; }; - - int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; - - bool IsDefault() const override; - - // Mutators - bool SetValueAt(const bool& InValue, const uint32& Index); - void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; - - int32 GetNumValues() { return Values.Num(); }; - - void SetDefaultValues(); - -protected: - - // Values of this property. - UPROPERTY() - TArray Values; - - UPROPERTY() - TArray DefaultValues; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniParameter.h" +#include "Styling/SlateTypes.h" +#include "HoudiniParameterToggle.generated.h" + +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniParameterToggle : public UHoudiniParameter +{ + GENERATED_UCLASS_BODY() + +public: + + // Create instance of this class. + static UHoudiniParameterToggle * Create( + UObject* Outer, + const FString& ParamName); + + // Accessor + bool GetValueAt(const uint32& Index) const { return Values.IsValidIndex(Index) ? (bool)Values[Index] : false; }; + + int32* GetValuesPtr() { return Values.Num() > 0 ? &Values[0] : nullptr; }; + + bool IsDefault() const override; + + // Mutators + bool SetValueAt(const bool& InValue, const uint32& Index); + void SetNumberOfValues(const uint32& InNumValues) { Values.SetNumUninitialized(InNumValues); }; + + int32 GetNumValues() { return Values.Num(); }; + + void SetDefaultValues(); + +protected: + + // Values of this property. + UPROPERTY() + TArray Values; + + UPROPERTY() + TArray DefaultValues; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp index 781fa70a2..e54063122 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.cpp @@ -1,35 +1,35 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniPluginSerializationVersion.h" - -#include "Serialization/CustomVersion.h" -#include "Misc/Guid.h" - -const FGuid FHoudiniCustomSerializationVersion::GUID( 0x1AB9CECC, 0x6913, 0x4875, 0x203d51fb ); - -// Register the custom version with core -FCustomVersionRegistration GRegisterHoudiniCustomVersion( FHoudiniCustomSerializationVersion::GUID, VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION, TEXT( "HoudiniUE4PluginVer" ) ); +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniPluginSerializationVersion.h" + +#include "Serialization/CustomVersion.h" +#include "Misc/Guid.h" + +const FGuid FHoudiniCustomSerializationVersion::GUID( 0x1AB9CECC, 0x6913, 0x4875, 0x203d51fb ); + +// Register the custom version with core +FCustomVersionRegistration GRegisterHoudiniCustomVersion( FHoudiniCustomSerializationVersion::GUID, VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION, TEXT( "HoudiniUE4PluginVer" ) ); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h index a0b703439..e765a93ad 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPluginSerializationVersion.h @@ -1,93 +1,93 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "Misc/Guid.h" - -// Deprecated per-class versions used to load old files -// -// Serialization of parameter name map. -#define VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP 2 -// Serialization of instancer material, if it is available. -#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME 1 -// Serialization of attribute instancer material, if it is available. -#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME 2 -// Landscape serialization in asset inputs. -#define VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT 1 -// Asset instance member. -#define VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER 2 -// World Outliner inputs. -#define VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT 3 - - -enum EHoudiniPluginSerializationVersion -{ - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE = 5, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE = 6, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM = 7, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT = 8, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH = 9, - VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM = 10, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT = 11, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_CUSTOM_LINKER = 12, // added custom linker version to archives - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ACTOR_INSTANCING = 13, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES = 14, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT = 15, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE = 16, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES = 17, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS = 18, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY = 19, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS = 20, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP = 21, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS = 22, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP = 23, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT = 24, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY = 25, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX = 26, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM = 27, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME = 28, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX = 29, // Version 29 is a fix for a serialization issue with UE4.19 / H17.0/16.5, 29 is actually version 26 minus the version 24 changes... - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_POST_419_SERIALIZATION_FIX = 30, - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF = 31, - - //------------------------------------------------------------ - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE = 100, - - // ------------------------------------------------------ - // - this needs to be the last line (see note below) - VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE, - VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION = VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE - 1 -}; - -struct FHoudiniCustomSerializationVersion -{ - // The GUID for this custom version number - const static FGuid GUID; - -private: - FHoudiniCustomSerializationVersion() {} -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "Misc/Guid.h" + +// Deprecated per-class versions used to load old files +// +// Serialization of parameter name map. +#define VER_HOUDINI_ENGINE_COMPONENT_PARAMETER_NAME_MAP 2 +// Serialization of instancer material, if it is available. +#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_MATERIAL_NAME 1 +// Serialization of attribute instancer material, if it is available. +#define VER_HOUDINI_ENGINE_GEOPARTOBJECT_INSTANCER_ATTRIBUTE_MATERIAL_NAME 2 +// Landscape serialization in asset inputs. +#define VER_HOUDINI_ENGINE_PARAM_LANDSCAPE_INPUT 1 +// Asset instance member. +#define VER_HOUDINI_ENGINE_PARAM_ASSET_INSTANCE_MEMBER 2 +// World Outliner inputs. +#define VER_HOUDINI_ENGINE_PARAM_WORLD_OUTLINER_INPUT 3 + + +enum EHoudiniPluginSerializationVersion +{ + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE = 5, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_UNREAL_SPLINE = 6, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_KEEP_TRANSFORM = 7, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_MULTI_GEO_INPUT = 8, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEO_PART_NODE_PATH = 9, + VER_HOUDINI_PLUGIN_SERIALIZATION_HOUDINI_SPLINE_TO_TRANSFORM = 10, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_UNREAL_SPLINE_RESOLUTION_PER_INPUT = 11, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_CUSTOM_LINKER = 12, // added custom linker version to archives + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ACTOR_INSTANCING = 13, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_LANDSCAPES = 14, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_UNIT = 15, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BAKENAME_OVERRIDE = 16, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES = 17, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_COOK_TEMP_PACKAGES_MESH_AND_LAYERS = 18, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_ONLY = 19, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_GEOMETRY_INPUT_TRANSFORMS = 20, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_ADDED_PARAM_HELP = 21, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INSTANCE_COLORS = 22, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_PARAMETERS_NOSWAP = 23, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_MAT = 24, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_FILE_PARAM_READ_ONLY = 25, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INSTANCE_INDEX = 26, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_LANDSCAPE_TRANSFORM = 27, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_OUTLINER_INPUT_SAVE_ACTOR_PATHNAME = 28, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_419_SERIALIZATION_FIX = 29, // Version 29 is a fix for a serialization issue with UE4.19 / H17.0/16.5, 29 is actually version 26 minus the version 24 changes... + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_POST_419_SERIALIZATION_FIX = 30, + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_INPUT_SOFT_REF = 31, + + //------------------------------------------------------------ + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE = 100, + + // ------------------------------------------------------ + // - this needs to be the last line (see note below) + VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE, + VER_HOUDINI_PLUGIN_SERIALIZATION_AUTOMATIC_VERSION = VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE_PLUS_ONE - 1 +}; + +struct FHoudiniCustomSerializationVersion +{ + // The GUID for this custom version number + const static FGuid GUID; + +private: + FHoudiniCustomSerializationVersion() {} +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp index 7e0371a23..61341bb2e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.cpp @@ -1,405 +1,405 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniRuntimeSettings.h" - - -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "Misc/Paths.h" -// #include "Internationalization/Internationalization.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" - -#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE - - -FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() - : bGeneratedDoubleSidedGeometry(false) - , GeneratedPhysMaterial(nullptr) - , GeneratedCollisionTraceFlag(CTF_UseDefault) - , GeneratedLightMapResolution(64) - , GeneratedWalkableSlopeOverride() - , GeneratedLightMapCoordinateIndex(1) - , bGeneratedUseMaximumStreamingTexelRatio(false) - , GeneratedStreamingDistanceMultiplier(1.0f) - , GeneratedFoliageDefaultSettings(nullptr) - , GeneratedAssetUserData() -{ - DefaultBodyInstance.SetCollisionProfileName("BlockAll"); -} - - -UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) - : Super( ObjectInitializer ) -{ - // Session options. - SessionType = HRSST_NamedPipe; - ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; - ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; - ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; - bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; - AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; - - bSyncWithHoudiniCook = true; - bCookUsingHoudiniTime = true; - bSyncViewport = false; - bSyncHoudiniViewport = false; - bSyncUnrealViewport = false; - - // Instantiating options. - bShowMultiAssetDialog = true; - bPreferHdaMemoryCopyOverHdaSourceFile = false; - - // Cooking options. - bPauseCookingOnStart = false; - bDisplaySlateCookingNotifications = true; - DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; - DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; - - // Parameter options - //bTreatRampParametersAsMultiparms = false; - - // Custom Houdini location. - bUseCustomHoudiniLocation = false; - CustomHoudiniLocation.Path = TEXT(""); - HoudiniExecutable = HRSHE_Houdini; - - // Arguments for HAPI_Initialize - CookingThreadStackSize = -1; - - // Landscape marshalling default values. - MarshallingLandscapesUseDefaultUnrealScaling = false; - MarshallingLandscapesUseFullResolution = true; - MarshallingLandscapesForceMinMaxValues = false; - MarshallingLandscapesForcedMinValue = -2000.0f; - MarshallingLandscapesForcedMaxValue = 4553.0f; - - // Spline marshalling - MarshallingSplineResolution = 50.0f; - - // Static mesh proxy refinement settings - bEnableProxyStaticMesh = false; - bShowDefaultMesh = true; - bEnableProxyStaticMeshRefinementByTimer = true; - ProxyMeshAutoRefineTimeoutSeconds = 10.0f; - bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; - bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; - - // Generated StaticMesh settings. - bDoubleSidedGeometry = false; - PhysMaterial = nullptr; - DefaultBodyInstance.SetCollisionProfileName("BlockAll"); - CollisionTraceFlag = CTF_UseDefault; - LightMapResolution = 32; - LightMapCoordinateIndex = 1; - bUseMaximumStreamingTexelRatio = false; - StreamingDistanceMultiplier = 1.0f; - GeneratedDistanceFieldResolutionScale = 0.0f; - - // Static Mesh build settings. - bUseFullPrecisionUVs = false; - SrcLightmapIndex = 0; - DstLightmapIndex = 1; - MinLightmapResolution = 64; - bRemoveDegenerates = true; - GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; - RecomputeNormalsFlag = HRSRF_OnlyIfMissing; - RecomputeTangentsFlag = HRSRF_OnlyIfMissing; - bUseMikkTSpace = true; - bBuildAdjacencyBuffer = true; // v1 default false - - bComputeWeightedNormals = false; - bBuildReversedIndexBuffer = true; - bUseHighPrecisionTangentBasis = false; - bGenerateDistanceFieldAsIfTwoSided = false; - bSupportFaceRemap = false; - //BuildScale3D = FVector(1.0f, 1.0f, 1.0f); - DistanceFieldResolutionScale = 2.0f; // ue default is 1.0 - - bPDGAsyncCommandletImportEnabled = false; - - // Legacy settings - bEnableBackwardCompatibility = true; - bAutomaticLegacyHDARebuild = false; - - // Curve inputs and editable output curves - bAddRotAndScaleAttributesOnCurves = false; -} - -UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() -{} - - -FProperty * -UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const -{ - for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) - { - FProperty * Property = *PropIt; - - if (Property->GetNameCPP() == PropertyName) - return Property; - } - - return nullptr; -} - - -void -UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) -{ - FProperty * Property = LocateProperty(PropertyName); - if (Property) - { - if (bReadOnly) - Property->SetPropertyFlags(CPF_EditConst); - else - Property->ClearPropertyFlags(CPF_EditConst); - } -} - - -void -UHoudiniRuntimeSettings::PostInitProperties() -{ - Super::PostInitProperties(); - - // Set Collision generation options as read only - { - if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Set marshalling attributes options as read only - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - - if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - /* - // Set Cook Folder as read-only - { - if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) - Property->SetPropertyFlags( CPF_EditConst ); - } - */ - - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - Property->SetPropertyFlags(CPF_EditConst); - if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - Property->SetPropertyFlags(CPF_EditConst); - } - - // Disable UI elements depending on current session type. -#if WITH_EDITOR - - UpdateSessionUI(); - -#endif // WITH_EDITOR - - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); -} - - -#if WITH_EDITOR - -void -UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - - FProperty * Property = PropertyChangedEvent.MemberProperty; - FProperty * LookupProperty = nullptr; - - if (!Property) - return; - if (Property->GetName() == TEXT("SessionType")) - UpdateSessionUI(); - else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) - SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); - else if (Property->GetName() == TEXT("CustomHoudiniLocation")) - { - FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); - FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; - FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); - - // If path does not point to libHAPI location, we need to let user know. - if (!FPaths::FileExists(LibHAPICustomPath)) - { - FString MessageString = FString::Printf( - TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); - - FPlatformMisc::MessageBoxExt( - EAppMsgType::Ok, *MessageString, - TEXT("Invalid Custom Location Specified, resetting.")); - - CustomHoudiniLocationPath = TEXT(""); - } - } - else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) - { - // Set Landscape forced min/max as read only when not overriden - if (!MarshallingLandscapesForceMinMaxValues) - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->SetPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->SetPropertyFlags(CPF_EditConst); - } - else - { - if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) - MinProperty->ClearPropertyFlags(CPF_EditConst); - if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) - MaxProperty->ClearPropertyFlags(CPF_EditConst); - } - } - - /* - if ( Property->GetName() == TEXT( "bEnableCooking" ) ) - { - // Cooking is disabled, we need to disable transform change triggers cooks option is as well. - if ( bEnableCooking ) - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) - { - // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. - if ( bUploadTransformsToHoudiniEngine ) - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->ClearPropertyFlags( CPF_EditConst ); - } - else - { - LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); - if ( LookupProperty ) - LookupProperty->SetPropertyFlags( CPF_EditConst ); - } - } - */ -} - - - -void -UHoudiniRuntimeSettings::UpdateSessionUI() -{ - SetPropertyReadOnly(TEXT("ServerHost"), true); - SetPropertyReadOnly(TEXT("ServerPort"), true); - SetPropertyReadOnly(TEXT("ServerPipeName"), true); - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); - - bool bServerType = false; - - switch (SessionType) - { - case HRSST_Socket: - { - SetPropertyReadOnly(TEXT("ServerHost"), false); - SetPropertyReadOnly(TEXT("ServerPort"), false); - bServerType = true; - break; - } - - case HRSST_NamedPipe: - { - SetPropertyReadOnly(TEXT("ServerPipeName"), false); - bServerType = true; - break; - } - - default: - break; - } - - if (bServerType) - { - SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); - SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); - } -} - -#endif // WITH_EDITOR - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniRuntimeSettings.h" + + +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "Misc/Paths.h" +// #include "Internationalization/Internationalization.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" + +#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE + + +FHoudiniStaticMeshGenerationProperties::FHoudiniStaticMeshGenerationProperties() + : bGeneratedDoubleSidedGeometry(false) + , GeneratedPhysMaterial(nullptr) + , GeneratedCollisionTraceFlag(CTF_UseDefault) + , GeneratedLightMapResolution(64) + , GeneratedWalkableSlopeOverride() + , GeneratedLightMapCoordinateIndex(1) + , bGeneratedUseMaximumStreamingTexelRatio(false) + , GeneratedStreamingDistanceMultiplier(1.0f) + , GeneratedFoliageDefaultSettings(nullptr) + , GeneratedAssetUserData() +{ + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); +} + + +UHoudiniRuntimeSettings::UHoudiniRuntimeSettings( const FObjectInitializer & ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // Session options. + SessionType = HRSST_NamedPipe; + ServerHost = HAPI_UNREAL_SESSION_SERVER_HOST; + ServerPort = HAPI_UNREAL_SESSION_SERVER_PORT; + ServerPipeName = HAPI_UNREAL_SESSION_SERVER_PIPENAME; + bStartAutomaticServer = HAPI_UNREAL_SESSION_SERVER_AUTOSTART; + AutomaticServerTimeout = HAPI_UNREAL_SESSION_SERVER_TIMEOUT; + + bSyncWithHoudiniCook = true; + bCookUsingHoudiniTime = true; + bSyncViewport = false; + bSyncHoudiniViewport = false; + bSyncUnrealViewport = false; + + // Instantiating options. + bShowMultiAssetDialog = true; + bPreferHdaMemoryCopyOverHdaSourceFile = false; + + // Cooking options. + bPauseCookingOnStart = false; + bDisplaySlateCookingNotifications = true; + DefaultTemporaryCookFolder = HAPI_UNREAL_DEFAULT_TEMP_COOK_FOLDER; + DefaultBakeFolder = HAPI_UNREAL_DEFAULT_BAKE_FOLDER; + + // Parameter options + //bTreatRampParametersAsMultiparms = false; + + // Custom Houdini location. + bUseCustomHoudiniLocation = false; + CustomHoudiniLocation.Path = TEXT(""); + HoudiniExecutable = HRSHE_Houdini; + + // Arguments for HAPI_Initialize + CookingThreadStackSize = -1; + + // Landscape marshalling default values. + MarshallingLandscapesUseDefaultUnrealScaling = false; + MarshallingLandscapesUseFullResolution = true; + MarshallingLandscapesForceMinMaxValues = false; + MarshallingLandscapesForcedMinValue = -2000.0f; + MarshallingLandscapesForcedMaxValue = 4553.0f; + + // Spline marshalling + MarshallingSplineResolution = 50.0f; + + // Static mesh proxy refinement settings + bEnableProxyStaticMesh = false; + bShowDefaultMesh = true; + bEnableProxyStaticMeshRefinementByTimer = true; + ProxyMeshAutoRefineTimeoutSeconds = 10.0f; + bEnableProxyStaticMeshRefinementOnPreSaveWorld = true; + bEnableProxyStaticMeshRefinementOnPreBeginPIE = true; + + // Generated StaticMesh settings. + bDoubleSidedGeometry = false; + PhysMaterial = nullptr; + DefaultBodyInstance.SetCollisionProfileName("BlockAll"); + CollisionTraceFlag = CTF_UseDefault; + LightMapResolution = 32; + LightMapCoordinateIndex = 1; + bUseMaximumStreamingTexelRatio = false; + StreamingDistanceMultiplier = 1.0f; + GeneratedDistanceFieldResolutionScale = 0.0f; + + // Static Mesh build settings. + bUseFullPrecisionUVs = false; + SrcLightmapIndex = 0; + DstLightmapIndex = 1; + MinLightmapResolution = 64; + bRemoveDegenerates = true; + GenerateLightmapUVsFlag = HRSRF_OnlyIfMissing; + RecomputeNormalsFlag = HRSRF_OnlyIfMissing; + RecomputeTangentsFlag = HRSRF_OnlyIfMissing; + bUseMikkTSpace = true; + bBuildAdjacencyBuffer = true; // v1 default false + + bComputeWeightedNormals = false; + bBuildReversedIndexBuffer = true; + bUseHighPrecisionTangentBasis = false; + bGenerateDistanceFieldAsIfTwoSided = false; + bSupportFaceRemap = false; + //BuildScale3D = FVector(1.0f, 1.0f, 1.0f); + DistanceFieldResolutionScale = 2.0f; // ue default is 1.0 + + bPDGAsyncCommandletImportEnabled = false; + + // Legacy settings + bEnableBackwardCompatibility = true; + bAutomaticLegacyHDARebuild = false; + + // Curve inputs and editable output curves + bAddRotAndScaleAttributesOnCurves = false; +} + +UHoudiniRuntimeSettings::~UHoudiniRuntimeSettings() +{} + + +FProperty * +UHoudiniRuntimeSettings::LocateProperty(const FString & PropertyName) const +{ + for (TFieldIterator< FProperty > PropIt(GetClass()); PropIt; ++PropIt) + { + FProperty * Property = *PropIt; + + if (Property->GetNameCPP() == PropertyName) + return Property; + } + + return nullptr; +} + + +void +UHoudiniRuntimeSettings::SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly) +{ + FProperty * Property = LocateProperty(PropertyName); + if (Property) + { + if (bReadOnly) + Property->SetPropertyFlags(CPF_EditConst); + else + Property->ClearPropertyFlags(CPF_EditConst); + } +} + + +void +UHoudiniRuntimeSettings::PostInitProperties() +{ + Super::PostInitProperties(); + + // Set Collision generation options as read only + { + if (FProperty* Property = LocateProperty(TEXT("CollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("RenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("UCXRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("SimpleRenderedCollisionGroupNamePrefix"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Set marshalling attributes options as read only + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterial"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeMaterialHole"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInstanceOverride"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeFaceSmoothingMask"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeLightmapResolution"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeGeneratedMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + + if (FProperty* Property = LocateProperty(TEXT("MarshallingAttributeInputMeshName"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + /* + // Set Cook Folder as read-only + { + if ( FProperty* Property = LocateProperty( TEXT( "TemporaryCookFolder" ) ) ) + Property->SetPropertyFlags( CPF_EditConst ); + } + */ + + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + Property->SetPropertyFlags(CPF_EditConst); + if (FProperty* Property = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + Property->SetPropertyFlags(CPF_EditConst); + } + + // Disable UI elements depending on current session type. +#if WITH_EDITOR + + UpdateSessionUI(); + +#endif // WITH_EDITOR + + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); +} + + +#if WITH_EDITOR + +void +UHoudiniRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent & PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + FProperty * Property = PropertyChangedEvent.MemberProperty; + FProperty * LookupProperty = nullptr; + + if (!Property) + return; + if (Property->GetName() == TEXT("SessionType")) + UpdateSessionUI(); + else if (Property->GetName() == TEXT("bUseCustomHoudiniLocation")) + SetPropertyReadOnly(TEXT("CustomHoudiniLocation"), !bUseCustomHoudiniLocation); + else if (Property->GetName() == TEXT("CustomHoudiniLocation")) + { + FString LibHAPIName = FHoudiniEngineRuntimeUtils::GetLibHAPIName(); + FString & CustomHoudiniLocationPath = CustomHoudiniLocation.Path; + FString LibHAPICustomPath = FString::Printf(TEXT("%s/%s"), *CustomHoudiniLocationPath, *LibHAPIName); + + // If path does not point to libHAPI location, we need to let user know. + if (!FPaths::FileExists(LibHAPICustomPath)) + { + FString MessageString = FString::Printf( + TEXT("%s was not found in %s"), *LibHAPIName, *CustomHoudiniLocationPath); + + FPlatformMisc::MessageBoxExt( + EAppMsgType::Ok, *MessageString, + TEXT("Invalid Custom Location Specified, resetting.")); + + CustomHoudiniLocationPath = TEXT(""); + } + } + else if (Property->GetName() == TEXT("MarshallingLandscapesForceMinMaxValues")) + { + // Set Landscape forced min/max as read only when not overriden + if (!MarshallingLandscapesForceMinMaxValues) + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->SetPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->SetPropertyFlags(CPF_EditConst); + } + else + { + if (FProperty* MinProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMinValue"))) + MinProperty->ClearPropertyFlags(CPF_EditConst); + if (FProperty* MaxProperty = LocateProperty(TEXT("MarshallingLandscapesForcedMaxValue"))) + MaxProperty->ClearPropertyFlags(CPF_EditConst); + } + } + + /* + if ( Property->GetName() == TEXT( "bEnableCooking" ) ) + { + // Cooking is disabled, we need to disable transform change triggers cooks option is as well. + if ( bEnableCooking ) + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bUploadTransformsToHoudiniEngine" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + else if ( Property->GetName() == TEXT( "bUploadTransformsToHoudiniEngine" ) ) + { + // If upload of transformations is disabled, then there's no sense in cooking asset on transformation change. + if ( bUploadTransformsToHoudiniEngine ) + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->ClearPropertyFlags( CPF_EditConst ); + } + else + { + LookupProperty = LocateProperty( TEXT( "bTransformChangeTriggersCooks" ) ); + if ( LookupProperty ) + LookupProperty->SetPropertyFlags( CPF_EditConst ); + } + } + */ +} + + + +void +UHoudiniRuntimeSettings::UpdateSessionUI() +{ + SetPropertyReadOnly(TEXT("ServerHost"), true); + SetPropertyReadOnly(TEXT("ServerPort"), true); + SetPropertyReadOnly(TEXT("ServerPipeName"), true); + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), true); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), true); + + bool bServerType = false; + + switch (SessionType) + { + case HRSST_Socket: + { + SetPropertyReadOnly(TEXT("ServerHost"), false); + SetPropertyReadOnly(TEXT("ServerPort"), false); + bServerType = true; + break; + } + + case HRSST_NamedPipe: + { + SetPropertyReadOnly(TEXT("ServerPipeName"), false); + bServerType = true; + break; + } + + default: + break; + } + + if (bServerType) + { + SetPropertyReadOnly(TEXT("bStartAutomaticServer"), false); + SetPropertyReadOnly(TEXT("AutomaticServerTimeout"), false); + } +} + +#endif // WITH_EDITOR + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h index 75441420e..36ee0c8ad 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h @@ -1,514 +1,514 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Object.h" -#include "Engine/EngineTypes.h" -#include "Engine/AssetUserData.h" -#include "PhysicsEngine/BodyInstance.h" - -#include "HoudiniRuntimeSettings.generated.h" - -class UFoliageType_InstancedStaticMesh; - -UENUM() -enum EHoudiniRuntimeSettingsSessionType -{ - // In process session. - HRSST_InProcess UMETA(Hidden), - - // TCP socket connection to Houdini Engine server. - HRSST_Socket UMETA(DisplayName = "TCP socket"), - - // Connection to Houdini Engine server via pipe connection. - HRSST_NamedPipe UMETA(DisplayName = "Named pipe or domain socket"), - - // No session, prevents license/Engine cook - HRSST_None UMETA(DisplayName = "None"), - - HRSST_MAX -}; - - -UENUM() -enum EHoudiniRuntimeSettingsRecomputeFlag -{ - // Recompute always. - HRSRF_Always UMETA(DisplayName = "Always"), - - // Recompute only if missing. - HRSRF_OnlyIfMissing UMETA(DisplayName = "Only if missing"), - - // Do not recompute. - HRSRF_Never UMETA(DisplayName = "Never"), - - HRSRF_MAX, -}; - -UENUM() -enum EHoudiniExecutableType -{ - // Houdini - HRSHE_Houdini UMETA(DisplayName = "Houdini"), - - // Houdini FX - HRSHE_HoudiniFX UMETA(DisplayName = "Houdini FX"), - - // Houdini Core - HRSHE_HoudiniCore UMETA(DisplayName = "Houdini Core"), - - // Houdini Indie - HRSHE_HoudiniIndie UMETA(DisplayName = "Houdini Indie"), -}; - -USTRUCT(BlueprintType) -struct HOUDINIENGINERUNTIME_API FHoudiniStaticMeshGenerationProperties -{ - GENERATED_USTRUCT_BODY() - - // Constructor - FHoudiniStaticMeshGenerationProperties(); - - public: - - /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Double Sided Geometry")) - uint32 bGeneratedDoubleSidedGeometry : 1; - - /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * GeneratedPhysMaterial; - - /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Collision Complexity")) - TEnumAsByte GeneratedCollisionTraceFlag; - - /** Resolution of lightmap. */ - UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 GeneratedLightMapResolution; - - /** Custom walkable slope setting for generated mesh's body. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride GeneratedWalkableSlopeOverride; - - /** The light map coordinate index. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light map coordinate index")) - int32 GeneratedLightMapCoordinateIndex; - - /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; - - /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Streaming Distance Multiplier")) - float GeneratedStreamingDistanceMultiplier; - - /** Default settings when using this mesh for instanced foliage. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh* GeneratedFoliageDefaultSettings = nullptr; - - /** Array of user data stored with the asset. */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Asset User Data")) - TArray GeneratedAssetUserData; -}; - - -UCLASS(config = Engine, defaultconfig) -class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject -{ - GENERATED_UCLASS_BODY() - - public: - - // Destructor. - virtual ~UHoudiniRuntimeSettings(); - - // - virtual void PostInitProperties() override; - -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; -#endif - -protected: - - // Locate property of this class by name. - FProperty * LocateProperty(const FString & PropertyName) const; - - // Make specified property read only. - void SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly = true); - -#if WITH_EDITOR - // Update session ui elements. - void UpdateSessionUI(); -#endif - - public: - - //------------------------------------------------------------------------------------------------------------- - // Session options. - //------------------------------------------------------------------------------------------------------------- - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - TEnumAsByte SessionType; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - FString ServerHost; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - int32 ServerPort; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - FString ServerPipeName; - - // Whether to automatically start a HARS process - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - bool bStartAutomaticServer; - - UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) - float AutomaticServerTimeout; - - // If enabled, changes made in Houdini, when connected to Houdini running in Session Sync mode will be automatically be pushed to Unreal. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bSyncWithHoudiniCook; - - // If enabled, the Houdini Timeline time will be used to cook assets. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bCookUsingHoudiniTime; - - // Enable when wanting to sync the Houdini and Unreal viewport when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) - bool bSyncViewport; - - // If enabled, Houdini's viewport will be synchronized to Unreal's when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Houdini Viewport to Unreal's viewport.", EditCondition = "bSyncViewport")) - bool bSyncHoudiniViewport; - - // If enabled, Unreal's viewport will be synchronized to Houdini's when using Session Sync. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Unreal Viewport to Houdini's viewport", EditCondition = "bSyncViewport")) - bool bSyncUnrealViewport; - - //------------------------------------------------------------------------------------------------------------- - // Instantiating options. - //------------------------------------------------------------------------------------------------------------- - - // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. - UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) - bool bShowMultiAssetDialog; - - // When enabled, the plugin will always instantiate the memory copy of a HDA stored in the .uasset file - // instead of using the latest version of the HDA file itself. - // This helps ensuring consistency between users when using HDAs, but will not work with expanded HDAs. - // When disabled, the plugin will always instantiate the latest version of the source HDA file if it is - // available, and will fallback to the memory copy if the source file cannot be found - UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) - bool bPreferHdaMemoryCopyOverHdaSourceFile; - - //------------------------------------------------------------------------------------------------------------- - // Cooking options. - //------------------------------------------------------------------------------------------------------------- - - // Whether houdini engine cooking is paused or not upon initializing the plugin - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - bool bPauseCookingOnStart; - - // Whether to display instantiation and cooking Slate notifications. - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - bool bDisplaySlateCookingNotifications; - - // Default content folder storing all the temporary cook data (Static meshes, materials, textures, landscape layer infos...) - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - FString DefaultTemporaryCookFolder; - - // Default content folder used when baking houdini asset data to native unreal objects - UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) - FString DefaultBakeFolder; - - //------------------------------------------------------------------------------------------------------------- - // Parameter options. - //------------------------------------------------------------------------------------------------------------- - - /* Deprecated! - // Forces the treatment of ramp parameters as multiparms. - UPROPERTY(GlobalConfig, EditAnywhere, Category = Parameters) - bool bTreatRampParametersAsMultiparms; - */ - - //------------------------------------------------------------------------------------------------------------- - // Geometry Marshalling - //------------------------------------------------------------------------------------------------------------- - - // If true, generated Landscapes will be marshalled using default unreal scaling. - // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms - // as Unreal's default landscape - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use default Unreal scaling.")) - bool MarshallingLandscapesUseDefaultUnrealScaling; - - // If true, generated Landscapes will be using full precision for their ZAxis, - // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use full resolution for data conversion.")) - bool MarshallingLandscapesUseFullResolution; - - // If true, the min/max values used to convert heightfields to landscape will be forced values - // This is usefull when importing multiple landscapes from different HDAs - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Force Min/Max values for data conversion")) - bool MarshallingLandscapesForceMinMaxValues; - - // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced min value")) - float MarshallingLandscapesForcedMinValue; - - // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced max value")) - float MarshallingLandscapesForcedMaxValue; - - // If this is enabled, additional rot & scale attributes are added on curve inputs - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Add rot & scale attributes on curve inputs")) - bool bAddRotAndScaleAttributesOnCurves; - - // Default resolution used when converting Unreal Spline Components to Houdini Curves (step in cm between control points, 0 only send the control points) - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Default spline resolution (cm)")) - float MarshallingSplineResolution; - - //------------------------------------------------------------------------------------------------------------- - // Static Mesh Options - //------------------------------------------------------------------------------------------------------------- - - // For StaticMesh outputs: should a fast proxy be created first? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Enable Proxy Static Mesh")) - bool bEnableProxyStaticMesh; - - // For static mesh outputs and socket actors: should spawn a default actor if the reference is invalid? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Show Default Mesh")) - bool bShowDefaultMesh; - - // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes After a Timeout", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementByTimer; - - // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bEnableProxyStaticMesh && bEnableProxyStaticMeshRefinementByTimer")) - float ProxyMeshAutoRefineTimeoutSeconds; - - // Automatically refine proxy meshes to UStaticMesh before the map is saved - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes When Saving a Map", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementOnPreSaveWorld; - - // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes On PIE", EditCondition = "bEnableProxyStaticMesh")) - bool bEnableProxyStaticMeshRefinementOnPreBeginPIE; - - //------------------------------------------------------------------------------------------------------------- - // Generated StaticMesh settings. - //------------------------------------------------------------------------------------------------------------- - - /// If true, the physics triangle mesh will use double sided faces for new Houdini Assets when doing scene queries. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Double Sided Geometry")) - uint32 bDoubleSidedGeometry : 1; - - /// Physical material to use for simple collision of new Houdini Assets. Encodes information about density, friction etc. - UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Simple Collision Physical Material")) - UPhysicalMaterial * PhysMaterial; - - /// Default properties of the body instance - UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (FullyExpand = "true")) - struct FBodyInstance DefaultBodyInstance; - - /// Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate for new Houdini Assets. - UPROPERTY(GlobalConfig, VisibleDefaultsOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Collision Complexity")) - TEnumAsByte CollisionTraceFlag; - - /// Resolution of lightmap for baked lighting. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) - int32 LightMapResolution; - - /// Bias multiplier for Light Propagation Volume lighting for new Houdini Assets. - UPROPERTY(GlobalConfig, EditAnywhere, BlueprintReadOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) - float LpvBiasMultiplier; - - /// Default Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset - UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) - float GeneratedDistanceFieldResolutionScale; - - /// Custom walkable slope setting for bodies of new Houdini Assets. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Walkable Slope Override")) - FWalkableSlopeOverride WalkableSlopeOverride; - - /// The UV coordinate index of lightmap - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light map coordinate index")) - int32 LightMapCoordinateIndex; - - /// True if mesh should use a less-conservative method of mip LOD texture factor computation for new Houdini Assets. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) - uint32 bUseMaximumStreamingTexelRatio : 1; - - /// Allows artists to adjust the distance where textures using UV 0 are streamed in/out for new Houdini Assets. - UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Streaming Distance Multiplier")) - float StreamingDistanceMultiplier; - - /// Default settings when using new Houdini Asset mesh for instanced foliage. - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Foliage Default Settings")) - UFoliageType_InstancedStaticMesh * FoliageDefaultSettings; - - /// Array of user data stored with the new Houdini Asset. - UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Asset User Data")) - TArray AssetUserData; - - //------------------------------------------------------------------------------------------------------------- - // Static Mesh build settings. - //------------------------------------------------------------------------------------------------------------- - - // If true, UVs will be stored at full floating point precision. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") - bool bUseFullPrecisionUVs; - - // Source UV set for generated lightmap. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Source Lightmap Index")) - int32 SrcLightmapIndex; - - // Destination UV set for generated lightmap. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Destination Lightmap Index")) - int32 DstLightmapIndex; - - // Target lightmap resolution to for generated lightmap. Determines the padding between UV shells in a packed lightmap. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") - int32 MinLightmapResolution; - - // If true, degenerate triangles will be removed. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") - bool bRemoveDegenerates; - - // Lightmap UV generation - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Lightmap UVs")) - TEnumAsByte GenerateLightmapUVsFlag; - - // Normals generation - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Normals")) - TEnumAsByte RecomputeNormalsFlag; - - // Tangents generation - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Tangents")) - TEnumAsByte RecomputeTangentsFlag; - - // If true, recomputed tangents and normals will be calculated using MikkT Space. This method does require properly laid out UVs though otherwise you'll get a degenerate tangent warning - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Using MikkT Space")) - bool bUseMikkTSpace; - - // Required for PNT tessellation but can be slow. Recommend disabling for larger meshes. - UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") - bool bBuildAdjacencyBuffer; - - // If true, we will use the surface area and the corner angle of the triangle as a ratio when computing the normals. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") - uint8 bComputeWeightedNormals : 1; - - // Required to optimize mesh in mirrored transform. Double index buffer size. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") - uint8 bBuildReversedIndexBuffer : 1; - - // If true, Tangents will be stored at 16 bit vs 8 bit precision. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") - uint8 bUseHighPrecisionTangentBasis : 1; - - // Scale to apply to the mesh when allocating the distance field volume texture. - // The default scale is 1, which is assuming that the mesh will be placed unscaled in the world. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") - float DistanceFieldResolutionScale; - - // Whether to generate the distance field treating every triangle hit as a front face. - // When enabled prevents the distance field from being discarded due to the mesh being open, but also lowers Distance Field AO quality. - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Two-Sided Distance Field Generation")) - uint8 bGenerateDistanceFieldAsIfTwoSided : 1; - - // Enable the Physical Material Mask - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Enable Physical Material Mask")) - uint8 bSupportFaceRemap : 1; - - //------------------------------------------------------------------------------------------------------------- - // PDG Commandlet import - //------------------------------------------------------------------------------------------------------------- - // Is the PDG commandlet enabled? - UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta = (DisplayName = "Async Importer Enabled")) - bool bPDGAsyncCommandletImportEnabled; - - //------------------------------------------------------------------------------------------------------------- - // Legacy - //------------------------------------------------------------------------------------------------------------- - // Whether to enable backward compatibility - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", Meta = (DisplayName = "Enable backward compatibility with Version 1")) - bool bEnableBackwardCompatibility; - - // Automatically rebuild legacy HAC - UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", meta = (DisplayName = "Automatically rebuild legacy Houdini Asset Components", EditCondition = "bEnableBackwardCompatibility")) - bool bAutomaticLegacyHDARebuild; - - //------------------------------------------------------------------------------------------------------------- - // Custom Houdini Location - //------------------------------------------------------------------------------------------------------------- - // Whether to use custom Houdini location. - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Use custom Houdini location (requires restart)")) - bool bUseCustomHoudiniLocation; - - // Custom Houdini location (where HAPI library is located). - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Custom Houdini location")) - FDirectoryPath CustomHoudiniLocation; - - // Select the Houdini executable to be used when opening session sync or opening hip files - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Houdini Executable")) - TEnumAsByte HoudiniExecutable; - - //------------------------------------------------------------------------------------------------------------- - // HAPI_Initialize - //------------------------------------------------------------------------------------------------------------- - // Evaluation thread stack size in bytes. -1 for default - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - int32 CookingThreadStackSize; - - // List of paths to Houdini-compatible .env files (; separated on Windows, : otherwise) - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString HoudiniEnvironmentFiles; - - // Path to find other OTL/HDA files - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString OtlSearchPath; - - // Sets HOUDINI_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString DsoSearchPath; - - // Sets HOUDINI_IMAGE_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString ImageDsoSearchPath; - - // Sets HOUDINI_AUDIO_DSO_PATH - UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) - FString AudioDsoSearchPath; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "Engine/EngineTypes.h" +#include "Engine/AssetUserData.h" +#include "PhysicsEngine/BodyInstance.h" + +#include "HoudiniRuntimeSettings.generated.h" + +class UFoliageType_InstancedStaticMesh; + +UENUM() +enum EHoudiniRuntimeSettingsSessionType +{ + // In process session. + HRSST_InProcess UMETA(Hidden), + + // TCP socket connection to Houdini Engine server. + HRSST_Socket UMETA(DisplayName = "TCP socket"), + + // Connection to Houdini Engine server via pipe connection. + HRSST_NamedPipe UMETA(DisplayName = "Named pipe or domain socket"), + + // No session, prevents license/Engine cook + HRSST_None UMETA(DisplayName = "None"), + + HRSST_MAX +}; + + +UENUM() +enum EHoudiniRuntimeSettingsRecomputeFlag +{ + // Recompute always. + HRSRF_Always UMETA(DisplayName = "Always"), + + // Recompute only if missing. + HRSRF_OnlyIfMissing UMETA(DisplayName = "Only if missing"), + + // Do not recompute. + HRSRF_Never UMETA(DisplayName = "Never"), + + HRSRF_MAX, +}; + +UENUM() +enum EHoudiniExecutableType +{ + // Houdini + HRSHE_Houdini UMETA(DisplayName = "Houdini"), + + // Houdini FX + HRSHE_HoudiniFX UMETA(DisplayName = "Houdini FX"), + + // Houdini Core + HRSHE_HoudiniCore UMETA(DisplayName = "Houdini Core"), + + // Houdini Indie + HRSHE_HoudiniIndie UMETA(DisplayName = "Houdini Indie"), +}; + +USTRUCT(BlueprintType) +struct HOUDINIENGINERUNTIME_API FHoudiniStaticMeshGenerationProperties +{ + GENERATED_USTRUCT_BODY() + + // Constructor + FHoudiniStaticMeshGenerationProperties(); + + public: + + /** If true, the physics triangle mesh will use double sided faces when doing scene queries. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Double Sided Geometry")) + uint32 bGeneratedDoubleSidedGeometry : 1; + + /** Physical material to use for simple collision on this body. Encodes information about density, friction etc. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * GeneratedPhysMaterial; + + /** Default properties of the body instance, copied into objects on instantiation, was URB_BodyInstance */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /** Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Collision Complexity")) + TEnumAsByte GeneratedCollisionTraceFlag; + + /** Resolution of lightmap. */ + UPROPERTY(EditAnywhere, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 GeneratedLightMapResolution; + + /** Custom walkable slope setting for generated mesh's body. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride GeneratedWalkableSlopeOverride; + + /** The light map coordinate index. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Light map coordinate index")) + int32 GeneratedLightMapCoordinateIndex; + + /** True if mesh should use a less-conservative method of mip LOD texture factor computation. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bGeneratedUseMaximumStreamingTexelRatio : 1; + + /** Allows artists to adjust the distance where textures using UV 0 are streamed in/out. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Streaming Distance Multiplier")) + float GeneratedStreamingDistanceMultiplier; + + /** Default settings when using this mesh for instanced foliage. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh* GeneratedFoliageDefaultSettings = nullptr; + + /** Array of user data stored with the asset. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "HoudiniMeshGeneration | StaticMeshGeneration", meta = (DisplayName = "Asset User Data")) + TArray GeneratedAssetUserData; +}; + + +UCLASS(config = Engine, defaultconfig) +class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject +{ + GENERATED_UCLASS_BODY() + + public: + + // Destructor. + virtual ~UHoudiniRuntimeSettings(); + + // + virtual void PostInitProperties() override; + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; +#endif + +protected: + + // Locate property of this class by name. + FProperty * LocateProperty(const FString & PropertyName) const; + + // Make specified property read only. + void SetPropertyReadOnly(const FString & PropertyName, bool bReadOnly = true); + +#if WITH_EDITOR + // Update session ui elements. + void UpdateSessionUI(); +#endif + + public: + + //------------------------------------------------------------------------------------------------------------- + // Session options. + //------------------------------------------------------------------------------------------------------------- + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + TEnumAsByte SessionType; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + FString ServerHost; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + int32 ServerPort; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + FString ServerPipeName; + + // Whether to automatically start a HARS process + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + bool bStartAutomaticServer; + + UPROPERTY(GlobalConfig, EditAnywhere, Category = Session) + float AutomaticServerTimeout; + + // If enabled, changes made in Houdini, when connected to Houdini running in Session Sync mode will be automatically be pushed to Unreal. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bSyncWithHoudiniCook; + + // If enabled, the Houdini Timeline time will be used to cook assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bCookUsingHoudiniTime; + + // Enable when wanting to sync the Houdini and Unreal viewport when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session) + bool bSyncViewport; + + // If enabled, Houdini's viewport will be synchronized to Unreal's when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Houdini Viewport to Unreal's viewport.", EditCondition = "bSyncViewport")) + bool bSyncHoudiniViewport; + + // If enabled, Unreal's viewport will be synchronized to Houdini's when using Session Sync. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = Session, meta = (DisplayName = "Sync the Unreal Viewport to Houdini's viewport", EditCondition = "bSyncViewport")) + bool bSyncUnrealViewport; + + //------------------------------------------------------------------------------------------------------------- + // Instantiating options. + //------------------------------------------------------------------------------------------------------------- + + // Whether to ask user to select an asset when instantiating an HDA with multiple assets inside. If disabled, will always instantiate first asset. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) + bool bShowMultiAssetDialog; + + // When enabled, the plugin will always instantiate the memory copy of a HDA stored in the .uasset file + // instead of using the latest version of the HDA file itself. + // This helps ensuring consistency between users when using HDAs, but will not work with expanded HDAs. + // When disabled, the plugin will always instantiate the latest version of the source HDA file if it is + // available, and will fallback to the memory copy if the source file cannot be found + UPROPERTY(GlobalConfig, EditAnywhere, Category = Instantiating) + bool bPreferHdaMemoryCopyOverHdaSourceFile; + + //------------------------------------------------------------------------------------------------------------- + // Cooking options. + //------------------------------------------------------------------------------------------------------------- + + // Whether houdini engine cooking is paused or not upon initializing the plugin + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + bool bPauseCookingOnStart; + + // Whether to display instantiation and cooking Slate notifications. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + bool bDisplaySlateCookingNotifications; + + // Default content folder storing all the temporary cook data (Static meshes, materials, textures, landscape layer infos...) + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + FString DefaultTemporaryCookFolder; + + // Default content folder used when baking houdini asset data to native unreal objects + UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooking) + FString DefaultBakeFolder; + + //------------------------------------------------------------------------------------------------------------- + // Parameter options. + //------------------------------------------------------------------------------------------------------------- + + /* Deprecated! + // Forces the treatment of ramp parameters as multiparms. + UPROPERTY(GlobalConfig, EditAnywhere, Category = Parameters) + bool bTreatRampParametersAsMultiparms; + */ + + //------------------------------------------------------------------------------------------------------------- + // Geometry Marshalling + //------------------------------------------------------------------------------------------------------------- + + // If true, generated Landscapes will be marshalled using default unreal scaling. + // Generated landscape will loose a lot of precision on the Z axis but will use the same transforms + // as Unreal's default landscape + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use default Unreal scaling.")) + bool MarshallingLandscapesUseDefaultUnrealScaling; + + // If true, generated Landscapes will be using full precision for their ZAxis, + // allowing for more precision but preventing them from being sculpted higher/lower than their min/max. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Use full resolution for data conversion.")) + bool MarshallingLandscapesUseFullResolution; + + // If true, the min/max values used to convert heightfields to landscape will be forced values + // This is usefull when importing multiple landscapes from different HDAs + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Force Min/Max values for data conversion")) + bool MarshallingLandscapesForceMinMaxValues; + + // The minimum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced min value")) + float MarshallingLandscapesForcedMinValue; + + // The maximum value to be used for Landscape conversion when MarshallingLandscapesForceMinMaxValues is enabled + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Landscape - Forced max value")) + float MarshallingLandscapesForcedMaxValue; + + // If this is enabled, additional rot & scale attributes are added on curve inputs + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Add rot & scale attributes on curve inputs")) + bool bAddRotAndScaleAttributesOnCurves; + + // Default resolution used when converting Unreal Spline Components to Houdini Curves (step in cm between control points, 0 only send the control points) + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeometryMarshalling", meta = (DisplayName = "Curves - Default spline resolution (cm)")) + float MarshallingSplineResolution; + + //------------------------------------------------------------------------------------------------------------- + // Static Mesh Options + //------------------------------------------------------------------------------------------------------------- + + // For StaticMesh outputs: should a fast proxy be created first? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Enable Proxy Static Mesh")) + bool bEnableProxyStaticMesh; + + // For static mesh outputs and socket actors: should spawn a default actor if the reference is invalid? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Static Mesh", meta = (DisplayName = "Show Default Mesh")) + bool bShowDefaultMesh; + + // If fast proxy meshes are being created, must it be baked as a StaticMesh after a period of no updates? + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes After a Timeout", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementByTimer; + + // If the option to automatically refine the proxy mesh via a timer has been selected, this controls the timeout in seconds. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Proxy Mesh Auto Refine Timeout Seconds", EditCondition = "bEnableProxyStaticMesh && bEnableProxyStaticMeshRefinementByTimer")) + float ProxyMeshAutoRefineTimeoutSeconds; + + // Automatically refine proxy meshes to UStaticMesh before the map is saved + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes When Saving a Map", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementOnPreSaveWorld; + + // Automatically refine proxy meshes to UStaticMesh before starting a play in editor session + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "Static Mesh", meta = (DisplayName = "Refine Proxy Static Meshes On PIE", EditCondition = "bEnableProxyStaticMesh")) + bool bEnableProxyStaticMeshRefinementOnPreBeginPIE; + + //------------------------------------------------------------------------------------------------------------- + // Generated StaticMesh settings. + //------------------------------------------------------------------------------------------------------------- + + /// If true, the physics triangle mesh will use double sided faces for new Houdini Assets when doing scene queries. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Double Sided Geometry")) + uint32 bDoubleSidedGeometry : 1; + + /// Physical material to use for simple collision of new Houdini Assets. Encodes information about density, friction etc. + UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Simple Collision Physical Material")) + UPhysicalMaterial * PhysMaterial; + + /// Default properties of the body instance + UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (FullyExpand = "true")) + struct FBodyInstance DefaultBodyInstance; + + /// Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate for new Houdini Assets. + UPROPERTY(GlobalConfig, VisibleDefaultsOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Collision Complexity")) + TEnumAsByte CollisionTraceFlag; + + /// Resolution of lightmap for baked lighting. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light Map Resolution", FixedIncrement = "4.0")) + int32 LightMapResolution; + + /// Bias multiplier for Light Propagation Volume lighting for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, BlueprintReadOnly, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Lpv Bias Multiplier", UIMin = "0.0", UIMax = "3.0")) + float LpvBiasMultiplier; + + /// Default Mesh distance field resolution, setting it to 0 will prevent the mesh distance field generation while editing the asset + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Distance Field Resolution Scale", UIMin = "0.0", UIMax = "100.0")) + float GeneratedDistanceFieldResolutionScale; + + /// Custom walkable slope setting for bodies of new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Walkable Slope Override")) + FWalkableSlopeOverride WalkableSlopeOverride; + + /// The UV coordinate index of lightmap + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Light map coordinate index")) + int32 LightMapCoordinateIndex; + + /// True if mesh should use a less-conservative method of mip LOD texture factor computation for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Use Maximum Streaming Texel Ratio")) + uint32 bUseMaximumStreamingTexelRatio : 1; + + /// Allows artists to adjust the distance where textures using UV 0 are streamed in/out for new Houdini Assets. + UPROPERTY(GlobalConfig, EditAnywhere, AdvancedDisplay, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Streaming Distance Multiplier")) + float StreamingDistanceMultiplier; + + /// Default settings when using new Houdini Asset mesh for instanced foliage. + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Foliage Default Settings")) + UFoliageType_InstancedStaticMesh * FoliageDefaultSettings; + + /// Array of user data stored with the new Houdini Asset. + UPROPERTY(EditAnywhere, AdvancedDisplay, Instanced, Category = "GeneratedStaticMeshSettings", meta = (DisplayName = "Asset User Data")) + TArray AssetUserData; + + //------------------------------------------------------------------------------------------------------------- + // Static Mesh build settings. + //------------------------------------------------------------------------------------------------------------- + + // If true, UVs will be stored at full floating point precision. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bUseFullPrecisionUVs; + + // Source UV set for generated lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Source Lightmap Index")) + int32 SrcLightmapIndex; + + // Destination UV set for generated lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Destination Lightmap Index")) + int32 DstLightmapIndex; + + // Target lightmap resolution to for generated lightmap. Determines the padding between UV shells in a packed lightmap. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + int32 MinLightmapResolution; + + // If true, degenerate triangles will be removed. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bRemoveDegenerates; + + // Lightmap UV generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Lightmap UVs")) + TEnumAsByte GenerateLightmapUVsFlag; + + // Normals generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Normals")) + TEnumAsByte RecomputeNormalsFlag; + + // Tangents generation + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Recompute Tangents")) + TEnumAsByte RecomputeTangentsFlag; + + // If true, recomputed tangents and normals will be calculated using MikkT Space. This method does require properly laid out UVs though otherwise you'll get a degenerate tangent warning + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Generate Using MikkT Space")) + bool bUseMikkTSpace; + + // Required for PNT tessellation but can be slow. Recommend disabling for larger meshes. + UPROPERTY(GlobalConfig, EditAnywhere, Category = "StaticMeshBuildSettings") + bool bBuildAdjacencyBuffer; + + // If true, we will use the surface area and the corner angle of the triangle as a ratio when computing the normals. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + uint8 bComputeWeightedNormals : 1; + + // Required to optimize mesh in mirrored transform. Double index buffer size. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + uint8 bBuildReversedIndexBuffer : 1; + + // If true, Tangents will be stored at 16 bit vs 8 bit precision. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + uint8 bUseHighPrecisionTangentBasis : 1; + + // Scale to apply to the mesh when allocating the distance field volume texture. + // The default scale is 1, which is assuming that the mesh will be placed unscaled in the world. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings") + float DistanceFieldResolutionScale; + + // Whether to generate the distance field treating every triangle hit as a front face. + // When enabled prevents the distance field from being discarded due to the mesh being open, but also lowers Distance Field AO quality. + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Two-Sided Distance Field Generation")) + uint8 bGenerateDistanceFieldAsIfTwoSided : 1; + + // Enable the Physical Material Mask + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "StaticMeshBuildSettings", meta = (DisplayName = "Enable Physical Material Mask")) + uint8 bSupportFaceRemap : 1; + + //------------------------------------------------------------------------------------------------------------- + // PDG Commandlet import + //------------------------------------------------------------------------------------------------------------- + // Is the PDG commandlet enabled? + UPROPERTY(GlobalConfig, EditAnywhere, Category = "PDG Settings", Meta = (DisplayName = "Async Importer Enabled")) + bool bPDGAsyncCommandletImportEnabled; + + //------------------------------------------------------------------------------------------------------------- + // Legacy + //------------------------------------------------------------------------------------------------------------- + // Whether to enable backward compatibility + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", Meta = (DisplayName = "Enable backward compatibility with Version 1")) + bool bEnableBackwardCompatibility; + + // Automatically rebuild legacy HAC + UPROPERTY(GlobalConfig, EditAnywhere, Category = "Legacy", meta = (DisplayName = "Automatically rebuild legacy Houdini Asset Components", EditCondition = "bEnableBackwardCompatibility")) + bool bAutomaticLegacyHDARebuild; + + //------------------------------------------------------------------------------------------------------------- + // Custom Houdini Location + //------------------------------------------------------------------------------------------------------------- + // Whether to use custom Houdini location. + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Use custom Houdini location (requires restart)")) + bool bUseCustomHoudiniLocation; + + // Custom Houdini location (where HAPI library is located). + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Custom Houdini location")) + FDirectoryPath CustomHoudiniLocation; + + // Select the Houdini executable to be used when opening session sync or opening hip files + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniLocation, Meta = (DisplayName = "Houdini Executable")) + TEnumAsByte HoudiniExecutable; + + //------------------------------------------------------------------------------------------------------------- + // HAPI_Initialize + //------------------------------------------------------------------------------------------------------------- + // Evaluation thread stack size in bytes. -1 for default + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + int32 CookingThreadStackSize; + + // List of paths to Houdini-compatible .env files (; separated on Windows, : otherwise) + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString HoudiniEnvironmentFiles; + + // Path to find other OTL/HDA files + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString OtlSearchPath; + + // Sets HOUDINI_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString DsoSearchPath; + + // Sets HOUDINI_IMAGE_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString ImageDsoSearchPath; + + // Sets HOUDINI_AUDIO_DSO_PATH + UPROPERTY(GlobalConfig, EditAnywhere, Category = HoudiniEngineInitialization) + FString AudioDsoSearchPath; }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp index c118c7faf..e54c857d0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp @@ -1,554 +1,554 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniSplineComponent.h" - -#include "HoudiniEngineRuntimePrivatePCH.h" -#include "HoudiniAssetComponent.h" -#include "HoudiniEngineRuntime.h" -#include "HoudiniEngineRuntimeUtils.h" -#include "HoudiniInput.h" -#include "HoudiniInputObject.h" -#include "HoudiniPluginSerializationVersion.h" - -#include "Components/MeshComponent.h" -#include "Algo/Reverse.h" - -#include "HoudiniPluginSerializationVersion.h" -#include "HoudiniCompatibilityHelpers.h" - -#include "UObject/DevObjectVersion.h" -#include "Serialization/CustomVersion.h" - -void -UHoudiniSplineComponent::Serialize(FArchive& Ar) -{ - int64 InitialOffset = Ar.Tell(); - - bool bLegacyComponent = false; - if (Ar.IsLoading()) - { - int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); - if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) - { - bLegacyComponent = true; - } - } - - if (bLegacyComponent) - { - // Legacy serialization - // Either try to convert or skip depending on HOUDINI_ENGINE_ENABLE_BACKWARD_COMPATIBILITY - const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); - bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; - if (bEnableBackwardCompatibility) - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : converting v1 object to v2.")); - - Super::Serialize(Ar); - - UHoudiniSplineComponent_V1* CompatibilitySC = NewObject(); - CompatibilitySC->Serialize(Ar); - CompatibilitySC->UpdateFromLegacyData(this); - - Construct(CompatibilitySC->CurveDisplayPoints); - } - else - { - HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : serialization will be skipped.")); - - Super::Serialize(Ar); - - // Skip v1 Serialized data - if (FLinker* Linker = Ar.GetLinker()) - { - int32 const ExportIndex = this->GetLinkerIndex(); - FObjectExport& Export = Linker->ExportMap[ExportIndex]; - Ar.Seek(InitialOffset + Export.SerialSize); - return; - } - } - } - else - { - // Normal v2 serialization - Super::Serialize(Ar); - } -} - -UHoudiniSplineComponent::UHoudiniSplineComponent(const FObjectInitializer & ObjectInitializer) - : Super(ObjectInitializer) - , bClosed(false) - , bReversed(false) - , bIsHoudiniSplineVisible(true) - , CurveType(EHoudiniCurveType::Polygon) - , CurveMethod(EHoudiniCurveMethod::CVs) - , bHasChanged(false) - , bNeedsToTriggerUpdate(false) - , bIsInputCurve(false) - , bIsEditableOutputCurve(false) - , NodeId(-1) -{ - - // Add two default points to the curve - FTransform defaultPoint = FTransform::Identity; - - // Set this component to not tick? - // SetComponentTickEnabled(false); - - // Default curve. - CurvePoints.Add(defaultPoint); - DisplayPoints.Add(defaultPoint.GetLocation()); - - defaultPoint.SetTranslation(FVector(200.f, 0.f, 0.f)); - CurvePoints.Add(defaultPoint); - DisplayPoints.Add(defaultPoint.GetLocation()); - - bIsOutputCurve = false; - bCookOnCurveChanged = true; - -#if WITH_EDITOR - bPostUndo = false; -#endif -} - -void -UHoudiniSplineComponent::Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint) -{ - DisplayPoints.Empty(); - DisplayPointIndexDivider.Empty(); - - float DisplayPointStepSize; - - // Resample the display points for linear curve. - - if (InCurveDisplayPoints.Num() <= 0) - return; - - // Add an additional displaypoint to the end for closed curve - if (bClosed && InCurveDisplayPoints.Num() > 2) - { - FVector & FirstPoint = InCurveDisplayPoints[0]; - FVector ClosingPoint; - ClosingPoint.X = FirstPoint.X; - ClosingPoint.Y = FirstPoint.Y; - ClosingPoint.Z = FirstPoint.Z; - - InCurveDisplayPoints.Add(ClosingPoint); - } - - - if (CurveType == EHoudiniCurveType::Polygon) - { - FVector Pt1, Pt2; - Pt1 = InCurveDisplayPoints[0]; - int32 CurrentDisplayPointIndex = 0; - - for (int Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) - { - DisplayPointStepSize = 10.f; - - Pt2 = InCurveDisplayPoints[Index]; - - FVector Direction = Pt2 - Pt1; - - float SegmentLength = Direction.Size(); - - int32 NumOfDisplayPt = SegmentLength / DisplayPointStepSize; - - // Make sure there are at least 20 display points on a segment. - while( NumOfDisplayPt < 20 && SegmentLength > 0.01) - { - DisplayPointStepSize /= 2.f; - NumOfDisplayPt = SegmentLength / DisplayPointStepSize; - } - - Direction.Normalize(0.01f); - - FVector StepVector = Direction * DisplayPointStepSize; - - // Always add the start point of a line segment - FVector NextDisplayPt = Pt1; - if (NumOfDisplayPt == 0) DisplayPoints.Add(NextDisplayPt); - - for (int32 itr = 0; itr < NumOfDisplayPt; ++itr) - { - DisplayPoints.Add(NextDisplayPt); - NextDisplayPt += StepVector; - CurrentDisplayPointIndex += 1; - } - - DisplayPointIndexDivider.Add(CurrentDisplayPointIndex); - - Pt1 = Pt2; - } - - // Add the ending point - DisplayPoints.Add(Pt1); - // Duplicate the last index, to make the DisplaPointyIndexDivider array matches the length of DP array - DisplayPointIndexDivider.Add(CurrentDisplayPointIndex + 1); - } - else if (CurveType == EHoudiniCurveType::Points) - { - // do not add display points for Points curve type, just show the CVs - } - else - { - // Needs a better algorithm to divide the display points - // Refined display points does not strictly interpolate the curve points. - - FVector Pt1, Pt2; - Pt1 = InCurveDisplayPoints[0]; - - int Itr = 1; - - int32 ClosestIndex = -1; - float ClosestDistance = -1.f; - - for (int32 Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) - { - if (Itr >= CurvePoints.Num()) break; - - Pt2 = InCurveDisplayPoints[Index]; - - FVector ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); - - float Distance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); - - if (ClosestDistance < 0.f || Distance < ClosestDistance) - { - ClosestDistance = Distance; - ClosestIndex = Index; - } - else - { - Itr += 1; - if (Itr >= CurvePoints.Num()) break; - - DisplayPointIndexDivider.Add(Index-1); - - ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); - ClosestDistance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); - - } - } - - DisplayPointIndexDivider.Add(InCurveDisplayPoints.Num()); - - DisplayPoints = InCurveDisplayPoints; - } -} - - -void -UHoudiniSplineComponent::CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent) -{ - if (!OtherHoudiniSplineComponent) - return; - - CurvePoints = OtherHoudiniSplineComponent->CurvePoints; - DisplayPoints = OtherHoudiniSplineComponent->DisplayPoints; - DisplayPointIndexDivider = OtherHoudiniSplineComponent->DisplayPointIndexDivider; - CurveType = OtherHoudiniSplineComponent->CurveType; - CurveMethod = OtherHoudiniSplineComponent->CurveMethod; - bClosed = OtherHoudiniSplineComponent->bClosed; -#if WITH_EDITORONLY_DATA - bVisualizeComponent = OtherHoudiniSplineComponent->bVisualizeComponent; -#endif - bReversed = OtherHoudiniSplineComponent->bReversed; - SetVisibility(OtherHoudiniSplineComponent->IsVisible()); - - HoudiniSplineName = OtherHoudiniSplineComponent->HoudiniSplineName; -} - -UHoudiniSplineComponent::~UHoudiniSplineComponent() -{} - -void -UHoudiniSplineComponent::AppendPoint(const FTransform& NewPoint) -{ - CurvePoints.Add(NewPoint); -} - -void -UHoudiniSplineComponent::InsertPointAtIndex(const FTransform& NewPoint, const int32& Index) -{ - check(Index >= 0 && Index < CurvePoints.Num()); - CurvePoints.Insert(NewPoint, Index); - bHasChanged = true; -} - - -void -UHoudiniSplineComponent::RemovePointAtIndex(const int32& Index) -{ - check(Index >= 0 && Index < CurvePoints.Num()); - CurvePoints.RemoveAt(Index); - bHasChanged = true; -} - -void -UHoudiniSplineComponent::SetReversed(const bool& InReversed) -{ - // don't need to do anything if the reversed state doesn't change. - if (InReversed == bReversed) - return; - - bReversed = InReversed; - ReverseCurvePoints(); - MarkChanged(true); -} - -void -UHoudiniSplineComponent::ReverseCurvePoints() -{ - if (CurvePoints.Num() < 2) - return; - - Algo::Reverse(CurvePoints); -} - -void -UHoudiniSplineComponent::EditPointAtindex(const FTransform& NewPoint, const int32& Index) -{ - if (!CurvePoints.IsValidIndex(Index)) - return; - - CurvePoints[Index] = NewPoint; - bHasChanged = true; -} - -#if WITH_EDITOR -void -UHoudiniSplineComponent::PostEditChangeProperty(FPropertyChangedEvent& PeopertyChangedEvent) -{ - Super::PostEditChangeProperty(PeopertyChangedEvent); - - FName PropertyName = (PeopertyChangedEvent.Property != nullptr) ? PeopertyChangedEvent.Property->GetFName() : NAME_None; - - // Responses to the uproperty changes - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bClosed)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bClosed")); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bReversed)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bReversed")); - ReverseCurvePoints(); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveType)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveType")); - MarkChanged(true); - } - - if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveMethod)) - { - UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveMethod")); - MarkChanged(true); - } -} -#endif - -void -UHoudiniSplineComponent::PostLoad() -{ - Super::PostLoad(); -} - -TStructOnScope -UHoudiniSplineComponent::GetComponentInstanceData() const -{ - TStructOnScope ComponentInstanceData = MakeStructOnScope(this); - FHoudiniSplineComponentInstanceData* InstanceData = ComponentInstanceData.Cast(); - - return ComponentInstanceData; -} - -void -UHoudiniSplineComponent::ApplyComponentInstanceData( - FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS) -{ - check(ComponentInstanceData); -} - -void -UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) -{ - // Capture properties that we want to preserve during copy - const int32 PrevNodeId = NodeId; - - UActorComponent* FromComponent = Cast(FromObject); - check(FromComponent); - - UHoudiniSplineComponent* FromSplineComponent = Cast(FromObject); - if (FromSplineComponent) - { - CurvePoints = FromSplineComponent->CurvePoints; - DisplayPoints = FromSplineComponent->DisplayPoints; - DisplayPointIndexDivider = FromSplineComponent->DisplayPointIndexDivider; -#if WITH_EDITORONLY_DATA - EditedControlPointsIndexes = FromSplineComponent->EditedControlPointsIndexes; -#endif - - HoudiniSplineName = FromSplineComponent->HoudiniSplineName; - bClosed = FromSplineComponent->bClosed; - bIsHoudiniSplineVisible = FromSplineComponent->bIsHoudiniSplineVisible; - CurveType = FromSplineComponent->CurveType; - CurveMethod = FromSplineComponent->CurveMethod; - bIsInputCurve = FromSplineComponent->bIsInputCurve; - bIsOutputCurve = FromSplineComponent->bIsOutputCurve; - bIsEditableOutputCurve = FromSplineComponent->bIsEditableOutputCurve; - bCookOnCurveChanged = FromSplineComponent->bCookOnCurveChanged; - - bHasChanged = FromSplineComponent->bHasChanged; - bNeedsToTriggerUpdate = FromSplineComponent->bNeedsToTriggerUpdate; - } - - // Restore properties that we want to preserve - NodeId = PrevNodeId; -} - - -void -UHoudiniSplineComponent::OnUnregister() -{ - Super::OnUnregister(); -} - -void -UHoudiniSplineComponent::OnComponentCreated() -{ - Super::OnComponentCreated(); -} - -void -UHoudiniSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy) -{ - Super::OnComponentDestroyed(bDestroyingHierarchy); - - if (IsInputCurve()) - { - // This component can't just come out of nowhere and decide to delete an input object! - // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! - - // InputObject->MarkPendingKill(); - - // if(NodeId > -1) - // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); - - SetNodeId(-1); // Set nodeId to invalid for reconstruct on re-do - } -} - - -#if WITH_EDITOR -void -UHoudiniSplineComponent::PostEditUndo() -{ - Super::PostEditUndo(); - - bPostUndo = true; - - MarkChanged(true); -} -#endif - -void -UHoudiniSplineComponent::SetOffset(const float& Offset) -{ - for (int n = 0; n < CurvePoints.Num(); ++n) - CurvePoints[n].AddToTranslation(FVector(0.f, Offset, 0.f)); - - for (int n = 0; n < DisplayPoints.Num(); ++n) - DisplayPoints[n] += FVector(0.f, Offset, 0.f); -} - -void -UHoudiniSplineComponent::ResetCurvePoints() -{ - CurvePoints.Empty(); -} - -void -UHoudiniSplineComponent::ResetDisplayPoints() -{ - DisplayPoints.Empty(); -} - -void -UHoudiniSplineComponent::AddCurvePoints(const TArray& Points) -{ - CurvePoints.Append(Points); -} - -void -UHoudiniSplineComponent::AddDisplayPoints(const TArray& Points) -{ - DisplayPoints.Append(Points); -} - -bool -UHoudiniSplineComponent::NeedsToTriggerUpdate() const -{ - return bNeedsToTriggerUpdate; -} - -void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate) -{ - bNeedsToTriggerUpdate = NeedsToTriggerUpdate; -} - -void UHoudiniSplineComponent::SetCurveType(const EHoudiniCurveType & NewCurveType) -{ - CurveType = NewCurveType; -} - -bool UHoudiniSplineComponent::HasChanged() const -{ - return bHasChanged; -} - -void UHoudiniSplineComponent::MarkChanged(const bool& Changed) -{ - bHasChanged = Changed; - bNeedsToTriggerUpdate = Changed; -} - -FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() -{ -} - -FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent) - : FActorComponentInstanceData(SourceComponent) -{ -} - - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniSplineComponent.h" + +#include "HoudiniEngineRuntimePrivatePCH.h" +#include "HoudiniAssetComponent.h" +#include "HoudiniEngineRuntime.h" +#include "HoudiniEngineRuntimeUtils.h" +#include "HoudiniInput.h" +#include "HoudiniInputObject.h" +#include "HoudiniPluginSerializationVersion.h" + +#include "Components/MeshComponent.h" +#include "Algo/Reverse.h" + +#include "HoudiniPluginSerializationVersion.h" +#include "HoudiniCompatibilityHelpers.h" + +#include "UObject/DevObjectVersion.h" +#include "Serialization/CustomVersion.h" + +void +UHoudiniSplineComponent::Serialize(FArchive& Ar) +{ + int64 InitialOffset = Ar.Tell(); + + bool bLegacyComponent = false; + if (Ar.IsLoading()) + { + int32 Ver = Ar.CustomVer(FHoudiniCustomSerializationVersion::GUID); + if (Ver < VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_V2_BASE && Ver >= VER_HOUDINI_PLUGIN_SERIALIZATION_VERSION_BASE) + { + bLegacyComponent = true; + } + } + + if (bLegacyComponent) + { + // Legacy serialization + // Either try to convert or skip depending on HOUDINI_ENGINE_ENABLE_BACKWARD_COMPATIBILITY + const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault(); + bool bEnableBackwardCompatibility = HoudiniRuntimeSettings->bEnableBackwardCompatibility; + if (bEnableBackwardCompatibility) + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : converting v1 object to v2.")); + + Super::Serialize(Ar); + + UHoudiniSplineComponent_V1* CompatibilitySC = NewObject(); + CompatibilitySC->Serialize(Ar); + CompatibilitySC->UpdateFromLegacyData(this); + + Construct(CompatibilitySC->CurveDisplayPoints); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Loading deprecated version of UHoudiniSplineComponent : serialization will be skipped.")); + + Super::Serialize(Ar); + + // Skip v1 Serialized data + if (FLinker* Linker = Ar.GetLinker()) + { + int32 const ExportIndex = this->GetLinkerIndex(); + FObjectExport& Export = Linker->ExportMap[ExportIndex]; + Ar.Seek(InitialOffset + Export.SerialSize); + return; + } + } + } + else + { + // Normal v2 serialization + Super::Serialize(Ar); + } +} + +UHoudiniSplineComponent::UHoudiniSplineComponent(const FObjectInitializer & ObjectInitializer) + : Super(ObjectInitializer) + , bClosed(false) + , bReversed(false) + , bIsHoudiniSplineVisible(true) + , CurveType(EHoudiniCurveType::Polygon) + , CurveMethod(EHoudiniCurveMethod::CVs) + , bHasChanged(false) + , bNeedsToTriggerUpdate(false) + , bIsInputCurve(false) + , bIsEditableOutputCurve(false) + , NodeId(-1) +{ + + // Add two default points to the curve + FTransform defaultPoint = FTransform::Identity; + + // Set this component to not tick? + // SetComponentTickEnabled(false); + + // Default curve. + CurvePoints.Add(defaultPoint); + DisplayPoints.Add(defaultPoint.GetLocation()); + + defaultPoint.SetTranslation(FVector(200.f, 0.f, 0.f)); + CurvePoints.Add(defaultPoint); + DisplayPoints.Add(defaultPoint.GetLocation()); + + bIsOutputCurve = false; + bCookOnCurveChanged = true; + +#if WITH_EDITOR + bPostUndo = false; +#endif +} + +void +UHoudiniSplineComponent::Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint) +{ + DisplayPoints.Empty(); + DisplayPointIndexDivider.Empty(); + + float DisplayPointStepSize; + + // Resample the display points for linear curve. + + if (InCurveDisplayPoints.Num() <= 0) + return; + + // Add an additional displaypoint to the end for closed curve + if (bClosed && InCurveDisplayPoints.Num() > 2) + { + FVector & FirstPoint = InCurveDisplayPoints[0]; + FVector ClosingPoint; + ClosingPoint.X = FirstPoint.X; + ClosingPoint.Y = FirstPoint.Y; + ClosingPoint.Z = FirstPoint.Z; + + InCurveDisplayPoints.Add(ClosingPoint); + } + + + if (CurveType == EHoudiniCurveType::Polygon) + { + FVector Pt1, Pt2; + Pt1 = InCurveDisplayPoints[0]; + int32 CurrentDisplayPointIndex = 0; + + for (int Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) + { + DisplayPointStepSize = 10.f; + + Pt2 = InCurveDisplayPoints[Index]; + + FVector Direction = Pt2 - Pt1; + + float SegmentLength = Direction.Size(); + + int32 NumOfDisplayPt = SegmentLength / DisplayPointStepSize; + + // Make sure there are at least 20 display points on a segment. + while( NumOfDisplayPt < 20 && SegmentLength > 0.01) + { + DisplayPointStepSize /= 2.f; + NumOfDisplayPt = SegmentLength / DisplayPointStepSize; + } + + Direction.Normalize(0.01f); + + FVector StepVector = Direction * DisplayPointStepSize; + + // Always add the start point of a line segment + FVector NextDisplayPt = Pt1; + if (NumOfDisplayPt == 0) DisplayPoints.Add(NextDisplayPt); + + for (int32 itr = 0; itr < NumOfDisplayPt; ++itr) + { + DisplayPoints.Add(NextDisplayPt); + NextDisplayPt += StepVector; + CurrentDisplayPointIndex += 1; + } + + DisplayPointIndexDivider.Add(CurrentDisplayPointIndex); + + Pt1 = Pt2; + } + + // Add the ending point + DisplayPoints.Add(Pt1); + // Duplicate the last index, to make the DisplaPointyIndexDivider array matches the length of DP array + DisplayPointIndexDivider.Add(CurrentDisplayPointIndex + 1); + } + else if (CurveType == EHoudiniCurveType::Points) + { + // do not add display points for Points curve type, just show the CVs + } + else + { + // Needs a better algorithm to divide the display points + // Refined display points does not strictly interpolate the curve points. + + FVector Pt1, Pt2; + Pt1 = InCurveDisplayPoints[0]; + + int Itr = 1; + + int32 ClosestIndex = -1; + float ClosestDistance = -1.f; + + for (int32 Index = 1; Index < InCurveDisplayPoints.Num(); ++Index) + { + if (Itr >= CurvePoints.Num()) break; + + Pt2 = InCurveDisplayPoints[Index]; + + FVector ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); + + float Distance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); + + if (ClosestDistance < 0.f || Distance < ClosestDistance) + { + ClosestDistance = Distance; + ClosestIndex = Index; + } + else + { + Itr += 1; + if (Itr >= CurvePoints.Num()) break; + + DisplayPointIndexDivider.Add(Index-1); + + ClosestPointOnSegment = FMath::ClosestPointOnSegment(CurvePoints[Itr].GetLocation(), Pt1, Pt2); + ClosestDistance = (ClosestPointOnSegment - CurvePoints[Itr].GetLocation()).Size(); + + } + } + + DisplayPointIndexDivider.Add(InCurveDisplayPoints.Num()); + + DisplayPoints = InCurveDisplayPoints; + } +} + + +void +UHoudiniSplineComponent::CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent) +{ + if (!OtherHoudiniSplineComponent) + return; + + CurvePoints = OtherHoudiniSplineComponent->CurvePoints; + DisplayPoints = OtherHoudiniSplineComponent->DisplayPoints; + DisplayPointIndexDivider = OtherHoudiniSplineComponent->DisplayPointIndexDivider; + CurveType = OtherHoudiniSplineComponent->CurveType; + CurveMethod = OtherHoudiniSplineComponent->CurveMethod; + bClosed = OtherHoudiniSplineComponent->bClosed; +#if WITH_EDITORONLY_DATA + bVisualizeComponent = OtherHoudiniSplineComponent->bVisualizeComponent; +#endif + bReversed = OtherHoudiniSplineComponent->bReversed; + SetVisibility(OtherHoudiniSplineComponent->IsVisible()); + + HoudiniSplineName = OtherHoudiniSplineComponent->HoudiniSplineName; +} + +UHoudiniSplineComponent::~UHoudiniSplineComponent() +{} + +void +UHoudiniSplineComponent::AppendPoint(const FTransform& NewPoint) +{ + CurvePoints.Add(NewPoint); +} + +void +UHoudiniSplineComponent::InsertPointAtIndex(const FTransform& NewPoint, const int32& Index) +{ + check(Index >= 0 && Index < CurvePoints.Num()); + CurvePoints.Insert(NewPoint, Index); + bHasChanged = true; +} + + +void +UHoudiniSplineComponent::RemovePointAtIndex(const int32& Index) +{ + check(Index >= 0 && Index < CurvePoints.Num()); + CurvePoints.RemoveAt(Index); + bHasChanged = true; +} + +void +UHoudiniSplineComponent::SetReversed(const bool& InReversed) +{ + // don't need to do anything if the reversed state doesn't change. + if (InReversed == bReversed) + return; + + bReversed = InReversed; + ReverseCurvePoints(); + MarkChanged(true); +} + +void +UHoudiniSplineComponent::ReverseCurvePoints() +{ + if (CurvePoints.Num() < 2) + return; + + Algo::Reverse(CurvePoints); +} + +void +UHoudiniSplineComponent::EditPointAtindex(const FTransform& NewPoint, const int32& Index) +{ + if (!CurvePoints.IsValidIndex(Index)) + return; + + CurvePoints[Index] = NewPoint; + bHasChanged = true; +} + +#if WITH_EDITOR +void +UHoudiniSplineComponent::PostEditChangeProperty(FPropertyChangedEvent& PeopertyChangedEvent) +{ + Super::PostEditChangeProperty(PeopertyChangedEvent); + + FName PropertyName = (PeopertyChangedEvent.Property != nullptr) ? PeopertyChangedEvent.Property->GetFName() : NAME_None; + + // Responses to the uproperty changes + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bClosed)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bClosed")); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, bReversed)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : bReversed")); + ReverseCurvePoints(); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveType)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveType")); + MarkChanged(true); + } + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UHoudiniSplineComponent, CurveMethod)) + { + UE_LOG(LogTemp, Warning, TEXT("UPROPERTY Changed : CurveMethod")); + MarkChanged(true); + } +} +#endif + +void +UHoudiniSplineComponent::PostLoad() +{ + Super::PostLoad(); +} + +TStructOnScope +UHoudiniSplineComponent::GetComponentInstanceData() const +{ + TStructOnScope ComponentInstanceData = MakeStructOnScope(this); + FHoudiniSplineComponentInstanceData* InstanceData = ComponentInstanceData.Cast(); + + return ComponentInstanceData; +} + +void +UHoudiniSplineComponent::ApplyComponentInstanceData( + FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS) +{ + check(ComponentInstanceData); +} + +void +UHoudiniSplineComponent::CopyPropertiesFrom(UObject* FromObject) +{ + // Capture properties that we want to preserve during copy + const int32 PrevNodeId = NodeId; + + UActorComponent* FromComponent = Cast(FromObject); + check(FromComponent); + + UHoudiniSplineComponent* FromSplineComponent = Cast(FromObject); + if (FromSplineComponent) + { + CurvePoints = FromSplineComponent->CurvePoints; + DisplayPoints = FromSplineComponent->DisplayPoints; + DisplayPointIndexDivider = FromSplineComponent->DisplayPointIndexDivider; +#if WITH_EDITORONLY_DATA + EditedControlPointsIndexes = FromSplineComponent->EditedControlPointsIndexes; +#endif + + HoudiniSplineName = FromSplineComponent->HoudiniSplineName; + bClosed = FromSplineComponent->bClosed; + bIsHoudiniSplineVisible = FromSplineComponent->bIsHoudiniSplineVisible; + CurveType = FromSplineComponent->CurveType; + CurveMethod = FromSplineComponent->CurveMethod; + bIsInputCurve = FromSplineComponent->bIsInputCurve; + bIsOutputCurve = FromSplineComponent->bIsOutputCurve; + bIsEditableOutputCurve = FromSplineComponent->bIsEditableOutputCurve; + bCookOnCurveChanged = FromSplineComponent->bCookOnCurveChanged; + + bHasChanged = FromSplineComponent->bHasChanged; + bNeedsToTriggerUpdate = FromSplineComponent->bNeedsToTriggerUpdate; + } + + // Restore properties that we want to preserve + NodeId = PrevNodeId; +} + + +void +UHoudiniSplineComponent::OnUnregister() +{ + Super::OnUnregister(); +} + +void +UHoudiniSplineComponent::OnComponentCreated() +{ + Super::OnComponentCreated(); +} + +void +UHoudiniSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy) +{ + Super::OnComponentDestroyed(bDestroyingHierarchy); + + if (IsInputCurve()) + { + // This component can't just come out of nowhere and decide to delete an input object! + // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! + + // InputObject->MarkPendingKill(); + + // if(NodeId > -1) + // FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); + + SetNodeId(-1); // Set nodeId to invalid for reconstruct on re-do + } +} + + +#if WITH_EDITOR +void +UHoudiniSplineComponent::PostEditUndo() +{ + Super::PostEditUndo(); + + bPostUndo = true; + + MarkChanged(true); +} +#endif + +void +UHoudiniSplineComponent::SetOffset(const float& Offset) +{ + for (int n = 0; n < CurvePoints.Num(); ++n) + CurvePoints[n].AddToTranslation(FVector(0.f, Offset, 0.f)); + + for (int n = 0; n < DisplayPoints.Num(); ++n) + DisplayPoints[n] += FVector(0.f, Offset, 0.f); +} + +void +UHoudiniSplineComponent::ResetCurvePoints() +{ + CurvePoints.Empty(); +} + +void +UHoudiniSplineComponent::ResetDisplayPoints() +{ + DisplayPoints.Empty(); +} + +void +UHoudiniSplineComponent::AddCurvePoints(const TArray& Points) +{ + CurvePoints.Append(Points); +} + +void +UHoudiniSplineComponent::AddDisplayPoints(const TArray& Points) +{ + DisplayPoints.Append(Points); +} + +bool +UHoudiniSplineComponent::NeedsToTriggerUpdate() const +{ + return bNeedsToTriggerUpdate; +} + +void UHoudiniSplineComponent::SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate) +{ + bNeedsToTriggerUpdate = NeedsToTriggerUpdate; +} + +void UHoudiniSplineComponent::SetCurveType(const EHoudiniCurveType & NewCurveType) +{ + CurveType = NewCurveType; +} + +bool UHoudiniSplineComponent::HasChanged() const +{ + return bHasChanged; +} + +void UHoudiniSplineComponent::MarkChanged(const bool& Changed) +{ + bHasChanged = Changed; + bNeedsToTriggerUpdate = Changed; +} + +FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() +{ +} + +FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent) + : FActorComponentInstanceData(SourceComponent) +{ +} + + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h index d467566a1..ef8160bdf 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h @@ -1,276 +1,276 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" - -#include "HoudiniEngineCopyPropertiesInterface.h" -#include "UObject/ObjectMacros.h" -#include "Components/SceneComponent.h" -#include "HoudiniGeoPartObject.h" - -#include "HoudiniSplineComponent.generated.h" - -class UHoudiniAssetComponent; - -enum class EHoudiniCurveType : int8; - -enum class EHoudiniCurveMethod : int8; - -class UHoudiniInputObject; - -UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) -class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, public IHoudiniEngineCopyPropertiesInterface -{ - GENERATED_UCLASS_BODY() - - friend class UHoudiniSplineComponent_V1; - - virtual ~UHoudiniSplineComponent(); - - virtual void Serialize(FArchive & Ar) override; - - public: - - void Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint = -1); - - void CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent); - - void ResetCurvePoints(); - - void ResetDisplayPoints(); - - void AddCurvePoints(const TArray& Points); - - void AddDisplayPoints(const TArray& Points); - - void AppendPoint(const FTransform& NewPoint); - - void InsertPointAtIndex(const FTransform& NewPoint, const int32& Index); - - void RemovePointAtIndex(const int32& Index); - - void EditPointAtindex(const FTransform& NewPoint, const int32& Index); - - void MarkModified(const bool & InModified) { bHasChanged = InModified; }; - - // To set the offset of default position of houdini curve - void SetOffset(const float& Offset); - - bool HasChanged() const; - - void MarkChanged(const bool& Changed); - - FORCEINLINE - FString& GetHoudiniSplineName() { return HoudiniSplineName; } - - FORCEINLINE - void SetHoudiniSplineName(const FString& NewName) { HoudiniSplineName = NewName; } - - bool NeedsToTriggerUpdate() const; - - void SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate); - - FORCEINLINE - EHoudiniCurveType GetCurveType() const { return CurveType; } - - void SetCurveType(const EHoudiniCurveType& NewCurveType); - - FORCEINLINE - EHoudiniCurveMethod GetCurveMethod() const { return CurveMethod; } - - FORCEINLINE - void SetCurveMethod(const EHoudiniCurveMethod& NewCurveMethod) { CurveMethod = NewCurveMethod; } - - FORCEINLINE - int32 GetCurvePointCount() const { return CurvePoints.Num(); } - - FORCEINLINE - bool IsClosedCurve() const { return bClosed; } - - FORCEINLINE - void SetClosedCurve(const bool& Closed) { bClosed = Closed; } - - FORCEINLINE - bool IsReversed() const { return bReversed; } - - void SetReversed(const bool& Reversed); - - FORCEINLINE - bool IsInputCurve() const { return bIsInputCurve; } - - FORCEINLINE - void SetIsInputCurve(const bool& bIsInput) { bIsInputCurve = bIsInput; } - - FORCEINLINE - bool IsEditableOutputCurve() const { return bIsEditableOutputCurve; } - - FORCEINLINE - void SetIsEditableOutputCurve(const bool& bInIsEditable) { bIsEditableOutputCurve = bInIsEditable; }; - - FORCEINLINE - int32 GetNodeId() const { return NodeId; } - - FORCEINLINE - void SetNodeId(const int32& NewNodeId) { NodeId = NewNodeId; } - - FORCEINLINE - FString GetGeoPartName() const { return PartName; } - - FORCEINLINE - bool IsHoudiniSplineVisible() const { return bIsHoudiniSplineVisible; } - - FORCEINLINE - void SetHoudiniSplineVisible(bool Visible) { bIsHoudiniSplineVisible = Visible; } - - FORCEINLINE - void SetGeoPartName(const FString & InPartName) { PartName = InPartName; } - - virtual void OnUnregister() override; - - virtual void OnComponentCreated() override; - - virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; - -#if WITH_EDITOR - virtual void PostEditUndo() override; - virtual void PostEditChangeProperty(FPropertyChangedEvent & PeopertyChangedEvent) override; -#endif - - virtual void PostLoad() override; - - virtual TStructOnScope GetComponentInstanceData() const override; - void ApplyComponentInstanceData(struct FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS); - - virtual void CopyPropertiesFrom(UObject* FromObject) override; - - private: - - void ReverseCurvePoints(); - - public: - - UPROPERTY() - TArray CurvePoints; - - UPROPERTY() - TArray DisplayPoints; - - UPROPERTY() - TArray DisplayPointIndexDivider; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - FString HoudiniSplineName; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bClosed; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bReversed; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - bool bIsHoudiniSplineVisible; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - EHoudiniCurveType CurveType; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") - EHoudiniCurveMethod CurveMethod; - - UPROPERTY() - bool bIsOutputCurve; - - UPROPERTY() - bool bCookOnCurveChanged; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - TArray EditedControlPointsIndexes; - - UPROPERTY(NonTransactional) - bool bPostUndo; -#endif - - protected: - // Corresponding geo part object. - FHoudiniGeoPartObject HoudiniGeoPartObject; - - private: - UPROPERTY(Transient, DuplicateTransient) - bool bHasChanged; - - UPROPERTY(Transient, DuplicateTransient) - bool bNeedsToTriggerUpdate; - - // Whether this is a Houdini curve input - UPROPERTY() - bool bIsInputCurve; - - UPROPERTY() - bool bIsEditableOutputCurve; - - // Corresponds to the Curve NodeId in Houdini - UPROPERTY(Transient, DuplicateTransient) - int32 NodeId; - - UPROPERTY() - FString PartName; -}; - -// Used to store HoudiniAssetComponent data during BP reconstruction -USTRUCT() -struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData -{ - GENERATED_BODY() -public: - - FHoudiniSplineComponentInstanceData(); - FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent); - - virtual ~FHoudiniSplineComponentInstanceData() = default; - - virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override - { - Super::ApplyToComponent(Component, CacheApplyPhase); - CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); - } - - UPROPERTY() - TArray CurvePoints; - - UPROPERTY() - TArray DisplayPoints; - - UPROPERTY() - TArray DisplayPointIndexDivider; - -#if WITH_EDITORONLY_DATA - UPROPERTY() - TArray EditedControlPointsIndexes; -#endif - -}; +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "HoudiniEngineCopyPropertiesInterface.h" +#include "UObject/ObjectMacros.h" +#include "Components/SceneComponent.h" +#include "HoudiniGeoPartObject.h" + +#include "HoudiniSplineComponent.generated.h" + +class UHoudiniAssetComponent; + +enum class EHoudiniCurveType : int8; + +enum class EHoudiniCurveMethod : int8; + +class UHoudiniInputObject; + +UCLASS(Blueprintable, BlueprintType, EditInlineNew, config = Engine, meta = (BlueprintSpawnableComponent)) +class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, public IHoudiniEngineCopyPropertiesInterface +{ + GENERATED_UCLASS_BODY() + + friend class UHoudiniSplineComponent_V1; + + virtual ~UHoudiniSplineComponent(); + + virtual void Serialize(FArchive & Ar) override; + + public: + + void Construct(TArray& InCurveDisplayPoints, int32 InsertedPoint = -1); + + void CopyHoudiniData(const UHoudiniSplineComponent* OtherHoudiniSplineComponent); + + void ResetCurvePoints(); + + void ResetDisplayPoints(); + + void AddCurvePoints(const TArray& Points); + + void AddDisplayPoints(const TArray& Points); + + void AppendPoint(const FTransform& NewPoint); + + void InsertPointAtIndex(const FTransform& NewPoint, const int32& Index); + + void RemovePointAtIndex(const int32& Index); + + void EditPointAtindex(const FTransform& NewPoint, const int32& Index); + + void MarkModified(const bool & InModified) { bHasChanged = InModified; }; + + // To set the offset of default position of houdini curve + void SetOffset(const float& Offset); + + bool HasChanged() const; + + void MarkChanged(const bool& Changed); + + FORCEINLINE + FString& GetHoudiniSplineName() { return HoudiniSplineName; } + + FORCEINLINE + void SetHoudiniSplineName(const FString& NewName) { HoudiniSplineName = NewName; } + + bool NeedsToTriggerUpdate() const; + + void SetNeedsToTriggerUpdate(const bool& NeedsToTriggerUpdate); + + FORCEINLINE + EHoudiniCurveType GetCurveType() const { return CurveType; } + + void SetCurveType(const EHoudiniCurveType& NewCurveType); + + FORCEINLINE + EHoudiniCurveMethod GetCurveMethod() const { return CurveMethod; } + + FORCEINLINE + void SetCurveMethod(const EHoudiniCurveMethod& NewCurveMethod) { CurveMethod = NewCurveMethod; } + + FORCEINLINE + int32 GetCurvePointCount() const { return CurvePoints.Num(); } + + FORCEINLINE + bool IsClosedCurve() const { return bClosed; } + + FORCEINLINE + void SetClosedCurve(const bool& Closed) { bClosed = Closed; } + + FORCEINLINE + bool IsReversed() const { return bReversed; } + + void SetReversed(const bool& Reversed); + + FORCEINLINE + bool IsInputCurve() const { return bIsInputCurve; } + + FORCEINLINE + void SetIsInputCurve(const bool& bIsInput) { bIsInputCurve = bIsInput; } + + FORCEINLINE + bool IsEditableOutputCurve() const { return bIsEditableOutputCurve; } + + FORCEINLINE + void SetIsEditableOutputCurve(const bool& bInIsEditable) { bIsEditableOutputCurve = bInIsEditable; }; + + FORCEINLINE + int32 GetNodeId() const { return NodeId; } + + FORCEINLINE + void SetNodeId(const int32& NewNodeId) { NodeId = NewNodeId; } + + FORCEINLINE + FString GetGeoPartName() const { return PartName; } + + FORCEINLINE + bool IsHoudiniSplineVisible() const { return bIsHoudiniSplineVisible; } + + FORCEINLINE + void SetHoudiniSplineVisible(bool Visible) { bIsHoudiniSplineVisible = Visible; } + + FORCEINLINE + void SetGeoPartName(const FString & InPartName) { PartName = InPartName; } + + virtual void OnUnregister() override; + + virtual void OnComponentCreated() override; + + virtual void OnComponentDestroyed(bool bDestroyingHierarchy) override; + +#if WITH_EDITOR + virtual void PostEditUndo() override; + virtual void PostEditChangeProperty(FPropertyChangedEvent & PeopertyChangedEvent) override; +#endif + + virtual void PostLoad() override; + + virtual TStructOnScope GetComponentInstanceData() const override; + void ApplyComponentInstanceData(struct FHoudiniSplineComponentInstanceData* ComponentInstanceData, const bool bPostUCS); + + virtual void CopyPropertiesFrom(UObject* FromObject) override; + + private: + + void ReverseCurvePoints(); + + public: + + UPROPERTY() + TArray CurvePoints; + + UPROPERTY() + TArray DisplayPoints; + + UPROPERTY() + TArray DisplayPointIndexDivider; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + FString HoudiniSplineName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bClosed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bReversed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + bool bIsHoudiniSplineVisible; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + EHoudiniCurveType CurveType; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Houdini Spline Properties") + EHoudiniCurveMethod CurveMethod; + + UPROPERTY() + bool bIsOutputCurve; + + UPROPERTY() + bool bCookOnCurveChanged; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + TArray EditedControlPointsIndexes; + + UPROPERTY(NonTransactional) + bool bPostUndo; +#endif + + protected: + // Corresponding geo part object. + FHoudiniGeoPartObject HoudiniGeoPartObject; + + private: + UPROPERTY(Transient, DuplicateTransient) + bool bHasChanged; + + UPROPERTY(Transient, DuplicateTransient) + bool bNeedsToTriggerUpdate; + + // Whether this is a Houdini curve input + UPROPERTY() + bool bIsInputCurve; + + UPROPERTY() + bool bIsEditableOutputCurve; + + // Corresponds to the Curve NodeId in Houdini + UPROPERTY(Transient, DuplicateTransient) + int32 NodeId; + + UPROPERTY() + FString PartName; +}; + +// Used to store HoudiniAssetComponent data during BP reconstruction +USTRUCT() +struct FHoudiniSplineComponentInstanceData : public FActorComponentInstanceData +{ + GENERATED_BODY() +public: + + FHoudiniSplineComponentInstanceData(); + FHoudiniSplineComponentInstanceData(const UHoudiniSplineComponent* SourceComponent); + + virtual ~FHoudiniSplineComponentInstanceData() = default; + + virtual void ApplyToComponent(UActorComponent* Component, const ECacheApplyPhase CacheApplyPhase) override + { + Super::ApplyToComponent(Component, CacheApplyPhase); + CastChecked(Component)->ApplyComponentInstanceData(this, (CacheApplyPhase == ECacheApplyPhase::PostUserConstructionScript)); + } + + UPROPERTY() + TArray CurvePoints; + + UPROPERTY() + TArray DisplayPoints; + + UPROPERTY() + TArray DisplayPointIndexDivider; + +#if WITH_EDITORONLY_DATA + UPROPERTY() + TArray EditedControlPointsIndexes; +#endif + +}; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp index 776755bf7..4a97f9ef0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.cpp @@ -1,464 +1,465 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMesh.h" - -#include "Async/ParallelFor.h" -#include "MeshUtilitiesCommon.h" - -UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - bHasNormals = false; - bHasTangents = false; - bHasColors = false; - NumUVLayers = 0; - bHasPerFaceMaterials = false; -} - -void UHoudiniStaticMesh::Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials) -{ - // Initialize the vertex positions and triangle indices arrays - VertexPositions.Init(FVector::ZeroVector, InNumVertices); - TriangleIndices.Init(FIntVector(-1, -1, -1), InNumTriangles); - if (InInitialNumStaticMaterials > 0) - StaticMaterials.Init(FStaticMaterial(), InInitialNumStaticMaterials); - else - StaticMaterials.Empty(); - - SetNumUVLayers(InNumUVLayers); - SetHasNormals(bInHasNormals); - SetHasTangents(bInHasTangents); - SetHasColors(bInHasColors); - SetHasPerFaceMaterials(bInHasPerFaceMaterials); -} - -void UHoudiniStaticMesh::SetHasPerFaceMaterials(bool bInHasPerFaceMaterials) -{ - bHasPerFaceMaterials = bInHasPerFaceMaterials; - if (bHasPerFaceMaterials) - MaterialIDsPerTriangle.Init(-1, GetNumTriangles()); - else - MaterialIDsPerTriangle.Empty(); -} - -void UHoudiniStaticMesh::SetHasNormals(bool bInHasNormals) -{ - bHasNormals = bInHasNormals; - if (bHasNormals) - VertexInstanceNormals.Init(FVector(0, 0, 1), GetNumVertexInstances()); - else - VertexInstanceNormals.Empty(); -} - -void UHoudiniStaticMesh::SetHasTangents(bool bInHasTangents) -{ - bHasTangents = bInHasTangents; - if (bHasTangents) - { - VertexInstanceUTangents.Init(FVector(1, 0, 0), GetNumVertexInstances()); - VertexInstanceVTangents.Init(FVector(0, 1, 0), GetNumVertexInstances()); - } - else - { - VertexInstanceUTangents.Empty(); - VertexInstanceVTangents.Empty(); - } -} - -void UHoudiniStaticMesh::SetHasColors(bool bInHasColors) -{ - bHasColors = bInHasColors; - if (bHasColors) - VertexInstanceColors.Init(FColor(127, 127, 127), GetNumVertexInstances()); - else - VertexInstanceColors.Empty(); -} - -void UHoudiniStaticMesh::SetNumUVLayers(uint32 InNumUVLayers) -{ - NumUVLayers = InNumUVLayers; - if (NumUVLayers > 0) - VertexInstanceUVs.Init(FVector2D::ZeroVector, GetNumVertexInstances() * NumUVLayers); - else - VertexInstanceUVs.Empty(); -} - -void UHoudiniStaticMesh::SetNumStaticMaterials(uint32 InNumMaterials) -{ - if (InNumMaterials > 0) - StaticMaterials.SetNum(InNumMaterials); - else - StaticMaterials.Empty(); -} - -void UHoudiniStaticMesh::SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition) -{ - check(VertexPositions.IsValidIndex(InVertexIndex)); - - VertexPositions[InVertexIndex] = InPosition; -} - -void UHoudiniStaticMesh::SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices) -{ - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[0])); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[1])); - check(VertexPositions.IsValidIndex(InTriangleVertexIndices[2])); - - TriangleIndices[InTriangleIndex] = InTriangleVertexIndices; -} - -void UHoudiniStaticMesh::SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal) -{ - if (!bHasNormals) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceNormals.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceNormals[VertexInstanceIndex] = InNormal; -} - -void UHoudiniStaticMesh::SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent) -{ - if (!bHasTangents) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceUTangents.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceUTangents[VertexInstanceIndex] = InUTangent; -} - -void UHoudiniStaticMesh::SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent) -{ - if (!bHasTangents) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceVTangents.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceVTangents[VertexInstanceIndex] = InVTangent; -} - -void UHoudiniStaticMesh::SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor) -{ - if (!bHasColors) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceColors.IsValidIndex(VertexInstanceIndex)); - - VertexInstanceColors[VertexInstanceIndex] = InColor; -} - -void UHoudiniStaticMesh::SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV) -{ - if (NumUVLayers <= 0) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - const uint32 VertexInstanceUVIndex = InUVLayer * GetNumVertexInstances() + InTriangleIndex * 3 + InTriangleVertexIndex; - check(VertexInstanceUVs.IsValidIndex(VertexInstanceUVIndex)); - - VertexInstanceUVs[VertexInstanceUVIndex] = InUV; -} - -void UHoudiniStaticMesh::SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID) -{ - if (!bHasPerFaceMaterials) - { - return; - } - - check(TriangleIndices.IsValidIndex(InTriangleIndex)); - check(MaterialIDsPerTriangle.IsValidIndex(InTriangleIndex)); - - MaterialIDsPerTriangle[InTriangleIndex] = InMaterialID; -} - -void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial) -{ - check(StaticMaterials.IsValidIndex(InMaterialIndex)); - StaticMaterials[InMaterialIndex] = InStaticMaterial; -} - -void UHoudiniStaticMesh::CalculateNormals(bool bInComputeWeightedNormals) -{ - const int32 NumVertexInstances = GetNumVertexInstances(); - - // Pre-allocate space in the vertex instance normals array - VertexInstanceNormals.SetNum(NumVertexInstances); - - const int32 NumTriangles = GetNumTriangles(); - const int32 NumVertices = GetNumVertices(); - - // Setup a vertex normal array - TArray VertexNormals; - VertexNormals.SetNum(NumVertices); - - // Zero all entries in VertexNormals - // for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) - ParallelFor(NumVertices, [&VertexNormals](int32 VertexIndex) - { - VertexNormals[VertexIndex] = FVector::ZeroVector; - }); - - // Calculate face normals and sum them for each vertex that shares the triangle - // for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex) - ParallelFor(NumTriangles, [this, &VertexNormals, bInComputeWeightedNormals](int32 TriangleIndex) - { - const FIntVector& TriangleVertexIndices = TriangleIndices[TriangleIndex]; - - if (!VertexPositions.IsValidIndex(TriangleVertexIndices[0]) || - !VertexPositions.IsValidIndex(TriangleVertexIndices[1]) || - !VertexPositions.IsValidIndex(TriangleVertexIndices[2])) - { - HOUDINI_LOG_WARNING( - TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexPositions index out of range %d, %d, %d, Num %d"), - TriangleVertexIndices[0], TriangleVertexIndices[1], TriangleVertexIndices[2], VertexPositions.Num()); - return; - } - - const FVector& V0 = VertexPositions[TriangleVertexIndices[0]]; - const FVector& V1 = VertexPositions[TriangleVertexIndices[1]]; - const FVector& V2 = VertexPositions[TriangleVertexIndices[2]]; - - FVector TriangleNormal = FVector::CrossProduct(V2 - V0, V1 - V0); - float Area = TriangleNormal.Size(); - TriangleNormal /= Area; - Area /= 2.0f; - - const float Weight[3] = { - bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V0, V1, V2) : 1.0f, - bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V1, V2, V0) : 1.0f, - bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V2, V0, V1) : 1.0f, - }; - - for (int CornerIndex = 0; CornerIndex < 3; ++CornerIndex) - { - const FVector WeightedNormal = TriangleNormal * Weight[CornerIndex]; - if (!WeightedNormal.IsNearlyZero(SMALL_NUMBER) && !WeightedNormal.ContainsNaN()) - { - if (!VertexNormals.IsValidIndex(TriangleVertexIndices[CornerIndex])) - { - HOUDINI_LOG_WARNING( - TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexNormal index out of range %d, Num %d"), - TriangleVertexIndices[CornerIndex], VertexNormals.Num()); - continue; - } - VertexNormals[TriangleVertexIndices[CornerIndex]] += WeightedNormal; - } - } - }); - - // Normalize the vertex normals - // for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) - ParallelFor(NumVertices, [&VertexNormals](int32 VertexIndex) - { - VertexNormals[VertexIndex].Normalize(); - }); - - // Copy vertex normals to vertex instance normals - // for (int32 VertexInstanceIndex = 0; VertexInstanceIndex < NumVertexInstances; ++VertexInstanceIndex) - ParallelFor(NumVertexInstances, [this, &VertexNormals](int32 VertexInstanceIndex) - { - const int32 TriangleIndex = VertexInstanceIndex / 3; - const int32 CornerIndex = VertexInstanceIndex % 3; - const FIntVector& TriangleVertexIndices = TriangleIndices[TriangleIndex]; - if (!VertexNormals.IsValidIndex(TriangleVertexIndices[CornerIndex])) - { - HOUDINI_LOG_WARNING( - TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexNormals index out of range %d, Num %d"), - TriangleVertexIndices[CornerIndex], VertexNormals.Num()); - return; - } - VertexInstanceNormals[VertexInstanceIndex] = VertexNormals[TriangleVertexIndices[CornerIndex]]; - }); - - bHasNormals = true; -} - -void UHoudiniStaticMesh::CalculateTangents(bool bInComputeWeightedNormals) -{ - const int32 NumVertexInstances = GetNumVertexInstances(); - - VertexInstanceUTangents.SetNum(NumVertexInstances); - VertexInstanceVTangents.SetNum(NumVertexInstances); - - // Calculate normals first if we don't have any - if (!HasNormals() || VertexInstanceNormals.Num() != NumVertexInstances) - CalculateNormals(bInComputeWeightedNormals); - - // for (int32 VertexInstanceIndex = 0; VertexInstanceIndex < NumVertexInstances; ++VertexInstanceIndex) - ParallelFor(NumVertexInstances, [this](int32 VertexInstanceIndex) - { - const FVector& Normal = VertexInstanceNormals[VertexInstanceIndex]; - Normal.FindBestAxisVectors( - VertexInstanceUTangents[VertexInstanceIndex], VertexInstanceVTangents[VertexInstanceIndex]); - }); - - bHasTangents = true; -} - -void UHoudiniStaticMesh::Optimize() -{ - VertexPositions.Shrink(); - TriangleIndices.Shrink(); - VertexInstanceColors.Shrink(); - VertexInstanceNormals.Shrink(); - VertexInstanceUTangents.Shrink(); - VertexInstanceVTangents.Shrink(); - VertexInstanceUVs.Shrink(); - MaterialIDsPerTriangle.Shrink(); - StaticMaterials.Shrink(); -} - -FBox UHoudiniStaticMesh::CalcBounds() const -{ - const uint32 NumVertices = VertexPositions.Num(); - - if (NumVertices == 0) - return FBox(); - - const FVector InitPosition = VertexPositions[0]; - double MinX = InitPosition.X, MaxX = InitPosition.X, MinY = InitPosition.Y, MaxY = InitPosition.Y, MinZ = InitPosition.Z, MaxZ = InitPosition.Z; - for (uint32 VertIdx = 0; VertIdx < NumVertices; ++VertIdx) - { - const FVector Position = VertexPositions[VertIdx]; - if (Position.X < MinX) MinX = Position.X; else if (Position.X > MaxX) MaxX = Position.X; - if (Position.Y < MinY) MinY = Position.Y; else if (Position.Y > MaxY) MaxY = Position.Y; - if (Position.Z < MinZ) MinZ = Position.Z; else if (Position.Z > MaxZ) MaxZ = Position.Z; - } - - return FBox(FVector(MinX, MinY, MinZ), FVector(MaxX, MaxY, MaxZ)); -} - -UMaterialInterface* UHoudiniStaticMesh::GetMaterial(int32 InMaterialIndex) -{ - check(StaticMaterials.IsValidIndex(InMaterialIndex)); - - return StaticMaterials[InMaterialIndex].MaterialInterface; -} - -int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const -{ - if (InMaterialSlotName == NAME_None) - return -1; - - const uint32 NumMaterials = StaticMaterials.Num(); - for (uint32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) - { - if (StaticMaterials[MaterialIndex].MaterialSlotName == InMaterialSlotName) - return (int32)MaterialIndex; - } - - return -1; -} - -bool UHoudiniStaticMesh::IsValid(bool bInSkipVertexIndicesCheck) const -{ - // Validate the number of vertices, indices and triangles. This is basically the same function as FRawMesh::IsValid() - const int32 NumVertices = GetNumVertices(); - const int32 NumVertexInstances = GetNumVertexInstances(); - const int32 NumTriangles = GetNumTriangles(); - - auto ValidateAttributeArraySize = [](int32 InArrayNum, int32 InExpectedSize) - { - return InArrayNum == 0 || InArrayNum == InExpectedSize; - }; - - bool bValid = NumVertices > 0 - && NumVertexInstances > 0 - && NumTriangles > 0 - && (NumVertexInstances / 3) == NumTriangles - && ValidateAttributeArraySize(MaterialIDsPerTriangle.Num(), NumTriangles) - && ValidateAttributeArraySize(VertexInstanceNormals.Num(), NumVertexInstances) - && ValidateAttributeArraySize(VertexInstanceUTangents.Num(), NumVertexInstances) - && ValidateAttributeArraySize(VertexInstanceVTangents.Num(), NumVertexInstances) - && ValidateAttributeArraySize(VertexInstanceColors.Num(), NumVertexInstances) - && NumUVLayers >= 0 - && VertexInstanceUVs.Num() == NumUVLayers * NumVertexInstances; - - if (!bInSkipVertexIndicesCheck) - { - int32 TriangleIndex = 0; - while (bValid && TriangleIndex < NumTriangles) - { - bValid = bValid && (TriangleIndices[TriangleIndex].X < NumVertices); - bValid = bValid && (TriangleIndices[TriangleIndex].Y < NumVertices); - bValid = bValid && (TriangleIndices[TriangleIndex].Z < NumVertices); - TriangleIndex++; - } - } - - return bValid; -} - -void UHoudiniStaticMesh::Serialize(FArchive &InArchive) -{ - Super::Serialize(InArchive); - - VertexPositions.Shrink(); - VertexPositions.BulkSerialize(InArchive); - - TriangleIndices.Shrink(); - TriangleIndices.BulkSerialize(InArchive); - - VertexInstanceColors.Shrink(); - VertexInstanceColors.BulkSerialize(InArchive); - - VertexInstanceNormals.Shrink(); - VertexInstanceNormals.BulkSerialize(InArchive); - - VertexInstanceUTangents.Shrink(); - VertexInstanceUTangents.BulkSerialize(InArchive); - - VertexInstanceVTangents.Shrink(); - VertexInstanceVTangents.BulkSerialize(InArchive); - - VertexInstanceUVs.Shrink(); - VertexInstanceUVs.BulkSerialize(InArchive); - - MaterialIDsPerTriangle.Shrink(); - MaterialIDsPerTriangle.BulkSerialize(InArchive); -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMesh.h" +#include "HoudiniEngineRuntimePrivatePCH.h" + +#include "Async/ParallelFor.h" +#include "MeshUtilitiesCommon.h" + +UHoudiniStaticMesh::UHoudiniStaticMesh(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bHasNormals = false; + bHasTangents = false; + bHasColors = false; + NumUVLayers = 0; + bHasPerFaceMaterials = false; +} + +void UHoudiniStaticMesh::Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials) +{ + // Initialize the vertex positions and triangle indices arrays + VertexPositions.Init(FVector::ZeroVector, InNumVertices); + TriangleIndices.Init(FIntVector(-1, -1, -1), InNumTriangles); + if (InInitialNumStaticMaterials > 0) + StaticMaterials.Init(FStaticMaterial(), InInitialNumStaticMaterials); + else + StaticMaterials.Empty(); + + SetNumUVLayers(InNumUVLayers); + SetHasNormals(bInHasNormals); + SetHasTangents(bInHasTangents); + SetHasColors(bInHasColors); + SetHasPerFaceMaterials(bInHasPerFaceMaterials); +} + +void UHoudiniStaticMesh::SetHasPerFaceMaterials(bool bInHasPerFaceMaterials) +{ + bHasPerFaceMaterials = bInHasPerFaceMaterials; + if (bHasPerFaceMaterials) + MaterialIDsPerTriangle.Init(-1, GetNumTriangles()); + else + MaterialIDsPerTriangle.Empty(); +} + +void UHoudiniStaticMesh::SetHasNormals(bool bInHasNormals) +{ + bHasNormals = bInHasNormals; + if (bHasNormals) + VertexInstanceNormals.Init(FVector(0, 0, 1), GetNumVertexInstances()); + else + VertexInstanceNormals.Empty(); +} + +void UHoudiniStaticMesh::SetHasTangents(bool bInHasTangents) +{ + bHasTangents = bInHasTangents; + if (bHasTangents) + { + VertexInstanceUTangents.Init(FVector(1, 0, 0), GetNumVertexInstances()); + VertexInstanceVTangents.Init(FVector(0, 1, 0), GetNumVertexInstances()); + } + else + { + VertexInstanceUTangents.Empty(); + VertexInstanceVTangents.Empty(); + } +} + +void UHoudiniStaticMesh::SetHasColors(bool bInHasColors) +{ + bHasColors = bInHasColors; + if (bHasColors) + VertexInstanceColors.Init(FColor(127, 127, 127), GetNumVertexInstances()); + else + VertexInstanceColors.Empty(); +} + +void UHoudiniStaticMesh::SetNumUVLayers(uint32 InNumUVLayers) +{ + NumUVLayers = InNumUVLayers; + if (NumUVLayers > 0) + VertexInstanceUVs.Init(FVector2D::ZeroVector, GetNumVertexInstances() * NumUVLayers); + else + VertexInstanceUVs.Empty(); +} + +void UHoudiniStaticMesh::SetNumStaticMaterials(uint32 InNumMaterials) +{ + if (InNumMaterials > 0) + StaticMaterials.SetNum(InNumMaterials); + else + StaticMaterials.Empty(); +} + +void UHoudiniStaticMesh::SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition) +{ + check(VertexPositions.IsValidIndex(InVertexIndex)); + + VertexPositions[InVertexIndex] = InPosition; +} + +void UHoudiniStaticMesh::SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices) +{ + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[0])); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[1])); + check(VertexPositions.IsValidIndex(InTriangleVertexIndices[2])); + + TriangleIndices[InTriangleIndex] = InTriangleVertexIndices; +} + +void UHoudiniStaticMesh::SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal) +{ + if (!bHasNormals) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceNormals.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceNormals[VertexInstanceIndex] = InNormal; +} + +void UHoudiniStaticMesh::SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent) +{ + if (!bHasTangents) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceUTangents.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceUTangents[VertexInstanceIndex] = InUTangent; +} + +void UHoudiniStaticMesh::SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent) +{ + if (!bHasTangents) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceVTangents.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceVTangents[VertexInstanceIndex] = InVTangent; +} + +void UHoudiniStaticMesh::SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor) +{ + if (!bHasColors) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceIndex = InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceColors.IsValidIndex(VertexInstanceIndex)); + + VertexInstanceColors[VertexInstanceIndex] = InColor; +} + +void UHoudiniStaticMesh::SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV) +{ + if (NumUVLayers <= 0) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + const uint32 VertexInstanceUVIndex = InUVLayer * GetNumVertexInstances() + InTriangleIndex * 3 + InTriangleVertexIndex; + check(VertexInstanceUVs.IsValidIndex(VertexInstanceUVIndex)); + + VertexInstanceUVs[VertexInstanceUVIndex] = InUV; +} + +void UHoudiniStaticMesh::SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID) +{ + if (!bHasPerFaceMaterials) + { + return; + } + + check(TriangleIndices.IsValidIndex(InTriangleIndex)); + check(MaterialIDsPerTriangle.IsValidIndex(InTriangleIndex)); + + MaterialIDsPerTriangle[InTriangleIndex] = InMaterialID; +} + +void UHoudiniStaticMesh::SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial) +{ + check(StaticMaterials.IsValidIndex(InMaterialIndex)); + StaticMaterials[InMaterialIndex] = InStaticMaterial; +} + +void UHoudiniStaticMesh::CalculateNormals(bool bInComputeWeightedNormals) +{ + const int32 NumVertexInstances = GetNumVertexInstances(); + + // Pre-allocate space in the vertex instance normals array + VertexInstanceNormals.SetNum(NumVertexInstances); + + const int32 NumTriangles = GetNumTriangles(); + const int32 NumVertices = GetNumVertices(); + + // Setup a vertex normal array + TArray VertexNormals; + VertexNormals.SetNum(NumVertices); + + // Zero all entries in VertexNormals + // for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) + ParallelFor(NumVertices, [&VertexNormals](int32 VertexIndex) + { + VertexNormals[VertexIndex] = FVector::ZeroVector; + }); + + // Calculate face normals and sum them for each vertex that shares the triangle + // for (int32 TriangleIndex = 0; TriangleIndex < NumTriangles; ++TriangleIndex) + ParallelFor(NumTriangles, [this, &VertexNormals, bInComputeWeightedNormals](int32 TriangleIndex) + { + const FIntVector& TriangleVertexIndices = TriangleIndices[TriangleIndex]; + + if (!VertexPositions.IsValidIndex(TriangleVertexIndices[0]) || + !VertexPositions.IsValidIndex(TriangleVertexIndices[1]) || + !VertexPositions.IsValidIndex(TriangleVertexIndices[2])) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexPositions index out of range %d, %d, %d, Num %d"), + TriangleVertexIndices[0], TriangleVertexIndices[1], TriangleVertexIndices[2], VertexPositions.Num()); + return; + } + + const FVector& V0 = VertexPositions[TriangleVertexIndices[0]]; + const FVector& V1 = VertexPositions[TriangleVertexIndices[1]]; + const FVector& V2 = VertexPositions[TriangleVertexIndices[2]]; + + FVector TriangleNormal = FVector::CrossProduct(V2 - V0, V1 - V0); + float Area = TriangleNormal.Size(); + TriangleNormal /= Area; + Area /= 2.0f; + + const float Weight[3] = { + bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V0, V1, V2) : 1.0f, + bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V1, V2, V0) : 1.0f, + bInComputeWeightedNormals ? Area * TriangleUtilities::ComputeTriangleCornerAngle(V2, V0, V1) : 1.0f, + }; + + for (int CornerIndex = 0; CornerIndex < 3; ++CornerIndex) + { + const FVector WeightedNormal = TriangleNormal * Weight[CornerIndex]; + if (!WeightedNormal.IsNearlyZero(SMALL_NUMBER) && !WeightedNormal.ContainsNaN()) + { + if (!VertexNormals.IsValidIndex(TriangleVertexIndices[CornerIndex])) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexNormal index out of range %d, Num %d"), + TriangleVertexIndices[CornerIndex], VertexNormals.Num()); + continue; + } + VertexNormals[TriangleVertexIndices[CornerIndex]] += WeightedNormal; + } + } + }); + + // Normalize the vertex normals + // for (int32 VertexIndex = 0; VertexIndex < NumVertices; ++VertexIndex) + ParallelFor(NumVertices, [&VertexNormals](int32 VertexIndex) + { + VertexNormals[VertexIndex].Normalize(); + }); + + // Copy vertex normals to vertex instance normals + // for (int32 VertexInstanceIndex = 0; VertexInstanceIndex < NumVertexInstances; ++VertexInstanceIndex) + ParallelFor(NumVertexInstances, [this, &VertexNormals](int32 VertexInstanceIndex) + { + const int32 TriangleIndex = VertexInstanceIndex / 3; + const int32 CornerIndex = VertexInstanceIndex % 3; + const FIntVector& TriangleVertexIndices = TriangleIndices[TriangleIndex]; + if (!VertexNormals.IsValidIndex(TriangleVertexIndices[CornerIndex])) + { + HOUDINI_LOG_WARNING( + TEXT("[UHoudiniStaticMesh::CalculateNormals]: VertexNormals index out of range %d, Num %d"), + TriangleVertexIndices[CornerIndex], VertexNormals.Num()); + return; + } + VertexInstanceNormals[VertexInstanceIndex] = VertexNormals[TriangleVertexIndices[CornerIndex]]; + }); + + bHasNormals = true; +} + +void UHoudiniStaticMesh::CalculateTangents(bool bInComputeWeightedNormals) +{ + const int32 NumVertexInstances = GetNumVertexInstances(); + + VertexInstanceUTangents.SetNum(NumVertexInstances); + VertexInstanceVTangents.SetNum(NumVertexInstances); + + // Calculate normals first if we don't have any + if (!HasNormals() || VertexInstanceNormals.Num() != NumVertexInstances) + CalculateNormals(bInComputeWeightedNormals); + + // for (int32 VertexInstanceIndex = 0; VertexInstanceIndex < NumVertexInstances; ++VertexInstanceIndex) + ParallelFor(NumVertexInstances, [this](int32 VertexInstanceIndex) + { + const FVector& Normal = VertexInstanceNormals[VertexInstanceIndex]; + Normal.FindBestAxisVectors( + VertexInstanceUTangents[VertexInstanceIndex], VertexInstanceVTangents[VertexInstanceIndex]); + }); + + bHasTangents = true; +} + +void UHoudiniStaticMesh::Optimize() +{ + VertexPositions.Shrink(); + TriangleIndices.Shrink(); + VertexInstanceColors.Shrink(); + VertexInstanceNormals.Shrink(); + VertexInstanceUTangents.Shrink(); + VertexInstanceVTangents.Shrink(); + VertexInstanceUVs.Shrink(); + MaterialIDsPerTriangle.Shrink(); + StaticMaterials.Shrink(); +} + +FBox UHoudiniStaticMesh::CalcBounds() const +{ + const uint32 NumVertices = VertexPositions.Num(); + + if (NumVertices == 0) + return FBox(); + + const FVector InitPosition = VertexPositions[0]; + double MinX = InitPosition.X, MaxX = InitPosition.X, MinY = InitPosition.Y, MaxY = InitPosition.Y, MinZ = InitPosition.Z, MaxZ = InitPosition.Z; + for (uint32 VertIdx = 0; VertIdx < NumVertices; ++VertIdx) + { + const FVector Position = VertexPositions[VertIdx]; + if (Position.X < MinX) MinX = Position.X; else if (Position.X > MaxX) MaxX = Position.X; + if (Position.Y < MinY) MinY = Position.Y; else if (Position.Y > MaxY) MaxY = Position.Y; + if (Position.Z < MinZ) MinZ = Position.Z; else if (Position.Z > MaxZ) MaxZ = Position.Z; + } + + return FBox(FVector(MinX, MinY, MinZ), FVector(MaxX, MaxY, MaxZ)); +} + +UMaterialInterface* UHoudiniStaticMesh::GetMaterial(int32 InMaterialIndex) +{ + check(StaticMaterials.IsValidIndex(InMaterialIndex)); + + return StaticMaterials[InMaterialIndex].MaterialInterface; +} + +int32 UHoudiniStaticMesh::GetMaterialIndex(FName InMaterialSlotName) const +{ + if (InMaterialSlotName == NAME_None) + return -1; + + const uint32 NumMaterials = StaticMaterials.Num(); + for (uint32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) + { + if (StaticMaterials[MaterialIndex].MaterialSlotName == InMaterialSlotName) + return (int32)MaterialIndex; + } + + return -1; +} + +bool UHoudiniStaticMesh::IsValid(bool bInSkipVertexIndicesCheck) const +{ + // Validate the number of vertices, indices and triangles. This is basically the same function as FRawMesh::IsValid() + const int32 NumVertices = GetNumVertices(); + const int32 NumVertexInstances = GetNumVertexInstances(); + const int32 NumTriangles = GetNumTriangles(); + + auto ValidateAttributeArraySize = [](int32 InArrayNum, int32 InExpectedSize) + { + return InArrayNum == 0 || InArrayNum == InExpectedSize; + }; + + bool bValid = NumVertices > 0 + && NumVertexInstances > 0 + && NumTriangles > 0 + && (NumVertexInstances / 3) == NumTriangles + && ValidateAttributeArraySize(MaterialIDsPerTriangle.Num(), NumTriangles) + && ValidateAttributeArraySize(VertexInstanceNormals.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceUTangents.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceVTangents.Num(), NumVertexInstances) + && ValidateAttributeArraySize(VertexInstanceColors.Num(), NumVertexInstances) + && NumUVLayers >= 0 + && VertexInstanceUVs.Num() == NumUVLayers * NumVertexInstances; + + if (!bInSkipVertexIndicesCheck) + { + int32 TriangleIndex = 0; + while (bValid && TriangleIndex < NumTriangles) + { + bValid = bValid && (TriangleIndices[TriangleIndex].X < NumVertices); + bValid = bValid && (TriangleIndices[TriangleIndex].Y < NumVertices); + bValid = bValid && (TriangleIndices[TriangleIndex].Z < NumVertices); + TriangleIndex++; + } + } + + return bValid; +} + +void UHoudiniStaticMesh::Serialize(FArchive &InArchive) +{ + Super::Serialize(InArchive); + + VertexPositions.Shrink(); + VertexPositions.BulkSerialize(InArchive); + + TriangleIndices.Shrink(); + TriangleIndices.BulkSerialize(InArchive); + + VertexInstanceColors.Shrink(); + VertexInstanceColors.BulkSerialize(InArchive); + + VertexInstanceNormals.Shrink(); + VertexInstanceNormals.BulkSerialize(InArchive); + + VertexInstanceUTangents.Shrink(); + VertexInstanceUTangents.BulkSerialize(InArchive); + + VertexInstanceVTangents.Shrink(); + VertexInstanceVTangents.BulkSerialize(InArchive); + + VertexInstanceUVs.Shrink(); + VertexInstanceUVs.BulkSerialize(InArchive); + + MaterialIDsPerTriangle.Shrink(); + MaterialIDsPerTriangle.BulkSerialize(InArchive); +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h index 5a46413d8..6f81b93f1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMesh.h @@ -1,258 +1,258 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Engine/StaticMesh.h" - -#include "HoudiniStaticMesh.generated.h" - -/** - * This is a simple static mesh that is meant to be built in one go, without modifications afterwards. - * The number of vertices and triangles must be known before hand. - */ -UCLASS() -class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject -{ - GENERATED_BODY() - -public: - - UHoudiniStaticMesh(const FObjectInitializer &ObjectInitializer); - - // Clears all existing data and initializes internal arrays to the relevant sizes to accommodate the - // mesh based InNumVertices, InNumTriangles, UVs etc. - UFUNCTION() - void Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials); - - UFUNCTION() - bool HasPerFaceMaterials() const { return bHasPerFaceMaterials; } - - UFUNCTION() - void SetHasPerFaceMaterials(bool bInHasPerFaceMaterials); - - UFUNCTION() - bool HasNormals() const { return bHasNormals; } - - UFUNCTION() - void SetHasNormals(bool bInHasNormals); - - UFUNCTION() - bool HasTangents() const { return bHasTangents; } - - UFUNCTION() - void SetHasTangents(bool bInHasTangents); - - UFUNCTION() - bool HasColors() const { return bHasColors; } - - UFUNCTION() - void SetHasColors(bool bInHasColors); - - UFUNCTION() - uint32 GetNumUVLayers() const { return NumUVLayers; } - - UFUNCTION() - void SetNumUVLayers(uint32 InNumUVLayers); - - UFUNCTION() - uint32 GetNumStaticMaterials() const { return StaticMaterials.Num(); } - - UFUNCTION() - void SetNumStaticMaterials(uint32 InNumStaticMaterials); - - UFUNCTION() - uint32 GetNumVertices() const { return VertexPositions.Num(); } - - UFUNCTION() - uint32 GetNumTriangles() const { return TriangleIndices.Num(); } - - UFUNCTION() - uint32 GetNumVertexInstances() const { return TriangleIndices.Num() * 3; } - - UFUNCTION() - void SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition); - - UFUNCTION() - void SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices); - - UFUNCTION() - void SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal); - - UFUNCTION() - void SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent); - - UFUNCTION() - void SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent); - - UFUNCTION() - void SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor); - - UFUNCTION() - void SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV); - - UFUNCTION() - void SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID); - - UFUNCTION() - void SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial); - - UFUNCTION() - uint32 AddStaticMaterial(const FStaticMaterial& InStaticMaterial) { return StaticMaterials.Add(InStaticMaterial); } - - /** Calculate the normals of the mesh by calculating the face normal of each triangle (if a triangle has vertices - * V0, V1, V2, get the vector perpendicular to the face Pf = (V2 - V0) x (V1 - V0). To calculate the - * vertex normal for V0 sum and then normalize all its shared face normals. If bInComputeWeightedNormals is true - * then the weight of each face normal that contributes to V0's normal is the area of the face multiplied by the V0 - * corner angle of that face. If bInComputeWeightedNormals is false then the weight is 1. - * - * @param bInComputeWeightedNormals Whether or not to use weighted normal calculation. Defaults to false. - */ - UFUNCTION() - void CalculateNormals(bool bInComputeWeightedNormals=false); - - /** - * Calculate tangents from the normals. Calculates normals first via CalculateNormals() if the mesh does not yet - * have normals. - * - * @param bInComputeWeightedNormals Whether or not to use weighted normal calculation if CalculateNormals() is - * called. Defaults to false. - */ - UFUNCTION() - void CalculateTangents(bool bInComputeWeightedNormals=false); - - /** - * Meant to be called after the mesh data arrays are populated. - * Currently only calls Shrink on the arrays - */ - UFUNCTION() - void Optimize(); - - UFUNCTION() - FBox CalcBounds() const; - - UFUNCTION() - const TArray& GetVertexPositions() const { return VertexPositions; } - - UFUNCTION() - const TArray& GetTriangleIndices() const { return TriangleIndices; } - - UFUNCTION() - const TArray& GetVertexInstanceColors() const { return VertexInstanceColors; } - - UFUNCTION() - const TArray& GetVertexInstanceNormals() const { return VertexInstanceNormals; } - - UFUNCTION() - const TArray& GetVertexInstanceUTangents() const { return VertexInstanceUTangents; } - - UFUNCTION() - const TArray& GetVertexInstanceVTangents() const { return VertexInstanceVTangents; } - - UFUNCTION() - const TArray& GetVertexInstanceUVs() const { return VertexInstanceUVs; } - - UFUNCTION() - const TArray& GetMaterialIDsPerTriangle() const { return MaterialIDsPerTriangle; } - - UFUNCTION() - const TArray& GetStaticMaterials() const { return StaticMaterials; } - - TArray& GetStaticMaterials() { return StaticMaterials; } - - UFUNCTION() - UMaterialInterface* GetMaterial(int32 InMaterialIndex); - - UFUNCTION() - int32 GetMaterialIndex(FName InMaterialSlotName) const; - - // Checks if the mesh is valid by checking face, vertex and attribute (normals etc) counts. - // If bSkipVertexIndicesCheck is true, then we don't loop over all triangle vertex indices to - // check if each index is valid (< NumVertices) - UFUNCTION() - bool IsValid(bool bInSkipVertexIndicesCheck=false) const; - - // Custom serialization: we use TArray::BulkSerialize to speed up array serialization - virtual void Serialize(FArchive &InArchive) override; - -protected: - - UPROPERTY() - bool bHasNormals; - - UPROPERTY() - bool bHasTangents; - - UPROPERTY() - bool bHasColors; - - /** The number of UV layers that the mesh has */ - UPROPERTY() - uint32 NumUVLayers; - - UPROPERTY() - bool bHasPerFaceMaterials; - - /** Vertex positions. The vertex id == vertex index => indexes into this array. */ - UPROPERTY(SkipSerialization) - TArray VertexPositions; - - /** Triangle vertices. Triangle id == triangle index => indexes into this array, which returns a FIntVector of - * vertex ids/indices for VertexPositions. - */ - UPROPERTY(SkipSerialization) - TArray TriangleIndices; - - /** Array of colors per vertex instance, in other words, a color per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceColors; - - /** Array of normals per vertex instance, in other words, a normal per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceNormals; - - /** Array of U tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceUTangents; - - /** Array of V tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceVTangents; - - /** Array of UV layers to array of per triangle-vertex UVs. Index: UVLayerIndex * (NumVertexInstances) + 3 * TriangleID + LocalTriangleVertexIndex. */ - UPROPERTY(SkipSerialization) - TArray VertexInstanceUVs; - - /** Array of material ID per triangle. Indexed by Triangle ID/Index. */ - UPROPERTY(SkipSerialization) - TArray MaterialIDsPerTriangle; - - /** The materials of the mesh. Index by MaterialID (MaterialIndex). */ - UPROPERTY() - TArray StaticMaterials; -}; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/StaticMesh.h" + +#include "HoudiniStaticMesh.generated.h" + +/** + * This is a simple static mesh that is meant to be built in one go, without modifications afterwards. + * The number of vertices and triangles must be known before hand. + */ +UCLASS() +class HOUDINIENGINERUNTIME_API UHoudiniStaticMesh : public UObject +{ + GENERATED_BODY() + +public: + + UHoudiniStaticMesh(const FObjectInitializer &ObjectInitializer); + + // Clears all existing data and initializes internal arrays to the relevant sizes to accommodate the + // mesh based InNumVertices, InNumTriangles, UVs etc. + UFUNCTION() + void Initialize(uint32 InNumVertices, uint32 InNumTriangles, uint32 InNumUVLayers, uint32 InInitialNumStaticMaterials, bool bInHasNormals, bool bInHasTangents, bool bInHasColors, bool bInHasPerFaceMaterials); + + UFUNCTION() + bool HasPerFaceMaterials() const { return bHasPerFaceMaterials; } + + UFUNCTION() + void SetHasPerFaceMaterials(bool bInHasPerFaceMaterials); + + UFUNCTION() + bool HasNormals() const { return bHasNormals; } + + UFUNCTION() + void SetHasNormals(bool bInHasNormals); + + UFUNCTION() + bool HasTangents() const { return bHasTangents; } + + UFUNCTION() + void SetHasTangents(bool bInHasTangents); + + UFUNCTION() + bool HasColors() const { return bHasColors; } + + UFUNCTION() + void SetHasColors(bool bInHasColors); + + UFUNCTION() + uint32 GetNumUVLayers() const { return NumUVLayers; } + + UFUNCTION() + void SetNumUVLayers(uint32 InNumUVLayers); + + UFUNCTION() + uint32 GetNumStaticMaterials() const { return StaticMaterials.Num(); } + + UFUNCTION() + void SetNumStaticMaterials(uint32 InNumStaticMaterials); + + UFUNCTION() + uint32 GetNumVertices() const { return VertexPositions.Num(); } + + UFUNCTION() + uint32 GetNumTriangles() const { return TriangleIndices.Num(); } + + UFUNCTION() + uint32 GetNumVertexInstances() const { return TriangleIndices.Num() * 3; } + + UFUNCTION() + void SetVertexPosition(uint32 InVertexIndex, const FVector& InPosition); + + UFUNCTION() + void SetTriangleVertexIndices(uint32 InTriangleIndex, const FIntVector& InTriangleVertexIndices); + + UFUNCTION() + void SetTriangleVertexNormal(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InNormal); + + UFUNCTION() + void SetTriangleVertexUTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InUTangent); + + UFUNCTION() + void SetTriangleVertexVTangent(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FVector& InVTangent); + + UFUNCTION() + void SetTriangleVertexColor(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, const FColor& InColor); + + UFUNCTION() + void SetTriangleVertexUV(uint32 InTriangleIndex, uint8 InTriangleVertexIndex, uint8 InUVLayer, const FVector2D& InUV); + + UFUNCTION() + void SetTriangleMaterialID(uint32 InTriangleIndex, int32 InMaterialID); + + UFUNCTION() + void SetStaticMaterial(uint32 InMaterialIndex, const FStaticMaterial& InStaticMaterial); + + UFUNCTION() + uint32 AddStaticMaterial(const FStaticMaterial& InStaticMaterial) { return StaticMaterials.Add(InStaticMaterial); } + + /** Calculate the normals of the mesh by calculating the face normal of each triangle (if a triangle has vertices + * V0, V1, V2, get the vector perpendicular to the face Pf = (V2 - V0) x (V1 - V0). To calculate the + * vertex normal for V0 sum and then normalize all its shared face normals. If bInComputeWeightedNormals is true + * then the weight of each face normal that contributes to V0's normal is the area of the face multiplied by the V0 + * corner angle of that face. If bInComputeWeightedNormals is false then the weight is 1. + * + * @param bInComputeWeightedNormals Whether or not to use weighted normal calculation. Defaults to false. + */ + UFUNCTION() + void CalculateNormals(bool bInComputeWeightedNormals=false); + + /** + * Calculate tangents from the normals. Calculates normals first via CalculateNormals() if the mesh does not yet + * have normals. + * + * @param bInComputeWeightedNormals Whether or not to use weighted normal calculation if CalculateNormals() is + * called. Defaults to false. + */ + UFUNCTION() + void CalculateTangents(bool bInComputeWeightedNormals=false); + + /** + * Meant to be called after the mesh data arrays are populated. + * Currently only calls Shrink on the arrays + */ + UFUNCTION() + void Optimize(); + + UFUNCTION() + FBox CalcBounds() const; + + UFUNCTION() + const TArray& GetVertexPositions() const { return VertexPositions; } + + UFUNCTION() + const TArray& GetTriangleIndices() const { return TriangleIndices; } + + UFUNCTION() + const TArray& GetVertexInstanceColors() const { return VertexInstanceColors; } + + UFUNCTION() + const TArray& GetVertexInstanceNormals() const { return VertexInstanceNormals; } + + UFUNCTION() + const TArray& GetVertexInstanceUTangents() const { return VertexInstanceUTangents; } + + UFUNCTION() + const TArray& GetVertexInstanceVTangents() const { return VertexInstanceVTangents; } + + UFUNCTION() + const TArray& GetVertexInstanceUVs() const { return VertexInstanceUVs; } + + UFUNCTION() + const TArray& GetMaterialIDsPerTriangle() const { return MaterialIDsPerTriangle; } + + UFUNCTION() + const TArray& GetStaticMaterials() const { return StaticMaterials; } + + TArray& GetStaticMaterials() { return StaticMaterials; } + + UFUNCTION() + UMaterialInterface* GetMaterial(int32 InMaterialIndex); + + UFUNCTION() + int32 GetMaterialIndex(FName InMaterialSlotName) const; + + // Checks if the mesh is valid by checking face, vertex and attribute (normals etc) counts. + // If bSkipVertexIndicesCheck is true, then we don't loop over all triangle vertex indices to + // check if each index is valid (< NumVertices) + UFUNCTION() + bool IsValid(bool bInSkipVertexIndicesCheck=false) const; + + // Custom serialization: we use TArray::BulkSerialize to speed up array serialization + virtual void Serialize(FArchive &InArchive) override; + +protected: + + UPROPERTY() + bool bHasNormals; + + UPROPERTY() + bool bHasTangents; + + UPROPERTY() + bool bHasColors; + + /** The number of UV layers that the mesh has */ + UPROPERTY() + uint32 NumUVLayers; + + UPROPERTY() + bool bHasPerFaceMaterials; + + /** Vertex positions. The vertex id == vertex index => indexes into this array. */ + UPROPERTY(SkipSerialization) + TArray VertexPositions; + + /** Triangle vertices. Triangle id == triangle index => indexes into this array, which returns a FIntVector of + * vertex ids/indices for VertexPositions. + */ + UPROPERTY(SkipSerialization) + TArray TriangleIndices; + + /** Array of colors per vertex instance, in other words, a color per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceColors; + + /** Array of normals per vertex instance, in other words, a normal per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceNormals; + + /** Array of U tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceUTangents; + + /** Array of V tangents per vertex instance, in other words, a tangent per triangle-vertex. Index 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceVTangents; + + /** Array of UV layers to array of per triangle-vertex UVs. Index: UVLayerIndex * (NumVertexInstances) + 3 * TriangleID + LocalTriangleVertexIndex. */ + UPROPERTY(SkipSerialization) + TArray VertexInstanceUVs; + + /** Array of material ID per triangle. Indexed by Triangle ID/Index. */ + UPROPERTY(SkipSerialization) + TArray MaterialIDsPerTriangle; + + /** The materials of the mesh. Index by MaterialID (MaterialIndex). */ + UPROPERTY() + TArray StaticMaterials; +}; + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp index 41f816b80..3bbcb3526 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.cpp @@ -1,223 +1,223 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMeshComponent.h" - -#include "Components/BillboardComponent.h" -#include "Engine/CollisionProfile.h" - -#include "HoudiniStaticMesh.h" -#include "HoudiniStaticMeshSceneProxy.h" - - -UHoudiniStaticMeshComponent::UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer) : - Super(InInitialzer) -{ - PrimaryComponentTick.bCanEverTick = false; - - SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); - - Mesh = nullptr; - bHoudiniIconVisible = true; - -#if WITH_EDITOR - bVisualizeComponent = true; -#endif -} - -void UHoudiniStaticMeshComponent::OnRegister() -{ - Super::OnRegister(); - -#if WITH_EDITORONLY_DATA - if (bVisualizeComponent && SpriteComponent != nullptr && GetOwner()) - { - SpriteComponent->SetSprite(LoadObject(nullptr, TEXT("/HoudiniEngine/Textures/icon_houdini_logo_128.icon_houdini_logo_128"))); - UpdateSpriteComponent(); - } -#endif -} - -//void -//UHoudiniStaticMeshComponent::PostLoad() -//{ -// Super::PostLoad(); -// -// //NotifyMeshUpdated(); -//} - -void UHoudiniStaticMeshComponent::SetMesh(UHoudiniStaticMesh *InMesh) -{ - if (Mesh == InMesh) - return; - - Mesh = InMesh; - NotifyMeshUpdated(); -} - -FPrimitiveSceneProxy* UHoudiniStaticMeshComponent::CreateSceneProxy() -{ - check(SceneProxy == nullptr); - - FHoudiniStaticMeshSceneProxy* NewProxy = nullptr; - if (Mesh && Mesh->GetNumTriangles() > 0) - { - NewProxy = new FHoudiniStaticMeshSceneProxy(this, GetScene()->GetFeatureLevel()); - NewProxy->Build(); - } - return NewProxy; -} - -FBoxSphereBounds UHoudiniStaticMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const -{ - if (Mesh) - { - // mesh bounds - FBoxSphereBounds NewBounds = LocalBounds.TransformBy(InLocalToWorld); - NewBounds.BoxExtent *= BoundsScale; - NewBounds.SphereRadius *= BoundsScale; - - return NewBounds; - } - else - { - return FBoxSphereBounds(InLocalToWorld.GetLocation(), FVector::ZeroVector, 0.f); - } -} - -#if WITH_EDITOR -void UHoudiniStaticMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); - const FName NAME_HoudiniIconVisible(TEXT("bHoudiniIconVisible")); - const FName NAME_Mesh(TEXT("Mesh")); - const FName NAME_PropertyChanged = PropertyChangedEvent.GetPropertyName(); - if (NAME_PropertyChanged == NAME_HoudiniIconVisible) - { - UpdateSpriteComponent(); - } - else if (NAME_PropertyChanged == NAME_Mesh) - { - NotifyMeshUpdated(); - } -} -#endif - -void UHoudiniStaticMeshComponent::SetHoudiniIconVisible(bool bInHoudiniIconVisible) -{ - bHoudiniIconVisible = bInHoudiniIconVisible; -#if WITH_EDITORONLY_DATA - UpdateSpriteComponent(); -#endif -} - -void UHoudiniStaticMeshComponent::NotifyMeshUpdated() -{ - MarkRenderStateDirty(); - if (Mesh) - { - LocalBounds = Mesh->CalcBounds(); - } - else - { - LocalBounds.Init(); - } - - UpdateBounds(); - -#if WITH_EDITORONLY_DATA - UpdateSpriteComponent(); -#endif -} - -#if WITH_EDITORONLY_DATA -void UHoudiniStaticMeshComponent::UpdateSpriteComponent() -{ - if (SpriteComponent) - { - const FBoxSphereBounds B = Bounds.TransformBy(GetComponentTransform().ToInverseMatrixWithScale()); - SpriteComponent->SetRelativeLocation(B.Origin + FVector(0, 0, B.BoxExtent.Size())); - SpriteComponent->SetVisibility(bHoudiniIconVisible); - } -} -#endif - -int32 UHoudiniStaticMeshComponent::GetNumMaterials() const -{ - // From UStaticMesh: - // @note : you don't have to consider Materials.Num() - // that only counts if overridden and it can't be more than GetStaticMesh()->Materials. - if (Mesh) - { - return Mesh->GetNumStaticMaterials(); - } - else - { - return 0; - } -} - -int32 UHoudiniStaticMeshComponent::GetMaterialIndex(FName MaterialSlotName) const -{ - return Mesh ? Mesh->GetMaterialIndex(MaterialSlotName) : -1; -} - -TArray UHoudiniStaticMeshComponent::GetMaterialSlotNames() const -{ - TArray MaterialNames; - if (Mesh) - { - const TArray &StaticMaterials = Mesh->GetStaticMaterials(); - const int32 NumMaterials = StaticMaterials.Num(); - for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) - { - const FStaticMaterial &StaticMaterial = StaticMaterials[MaterialIndex]; - MaterialNames.Add(StaticMaterial.MaterialSlotName); - } - } - return MaterialNames; -} - -bool UHoudiniStaticMeshComponent::IsMaterialSlotNameValid(FName MaterialSlotName) const -{ - return GetMaterialIndex(MaterialSlotName) >= 0; -} - -UMaterialInterface* UHoudiniStaticMeshComponent::GetMaterial(int32 MaterialIndex) const -{ - // From UStaticMesh: - // If we have a base materials array, use that - if (OverrideMaterials.IsValidIndex(MaterialIndex) && OverrideMaterials[MaterialIndex]) - { - return OverrideMaterials[MaterialIndex]; - } - // Otherwise get from static mesh - else - { - return Mesh ? Mesh->GetMaterial(MaterialIndex) : nullptr; - } -} - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMeshComponent.h" + +#include "Components/BillboardComponent.h" +#include "Engine/CollisionProfile.h" + +#include "HoudiniStaticMesh.h" +#include "HoudiniStaticMeshSceneProxy.h" + + +UHoudiniStaticMeshComponent::UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer) : + Super(InInitialzer) +{ + PrimaryComponentTick.bCanEverTick = false; + + SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName); + + Mesh = nullptr; + bHoudiniIconVisible = true; + +#if WITH_EDITOR + bVisualizeComponent = true; +#endif +} + +void UHoudiniStaticMeshComponent::OnRegister() +{ + Super::OnRegister(); + +#if WITH_EDITORONLY_DATA + if (bVisualizeComponent && SpriteComponent != nullptr && GetOwner()) + { + SpriteComponent->SetSprite(LoadObject(nullptr, TEXT("/HoudiniEngine/Textures/icon_houdini_logo_128.icon_houdini_logo_128"))); + UpdateSpriteComponent(); + } +#endif +} + +//void +//UHoudiniStaticMeshComponent::PostLoad() +//{ +// Super::PostLoad(); +// +// //NotifyMeshUpdated(); +//} + +void UHoudiniStaticMeshComponent::SetMesh(UHoudiniStaticMesh *InMesh) +{ + if (Mesh == InMesh) + return; + + Mesh = InMesh; + NotifyMeshUpdated(); +} + +FPrimitiveSceneProxy* UHoudiniStaticMeshComponent::CreateSceneProxy() +{ + check(SceneProxy == nullptr); + + FHoudiniStaticMeshSceneProxy* NewProxy = nullptr; + if (Mesh && Mesh->GetNumTriangles() > 0) + { + NewProxy = new FHoudiniStaticMeshSceneProxy(this, GetScene()->GetFeatureLevel()); + NewProxy->Build(); + } + return NewProxy; +} + +FBoxSphereBounds UHoudiniStaticMeshComponent::CalcBounds(const FTransform& InLocalToWorld) const +{ + if (Mesh) + { + // mesh bounds + FBoxSphereBounds NewBounds = LocalBounds.TransformBy(InLocalToWorld); + NewBounds.BoxExtent *= BoundsScale; + NewBounds.SphereRadius *= BoundsScale; + + return NewBounds; + } + else + { + return FBoxSphereBounds(InLocalToWorld.GetLocation(), FVector::ZeroVector, 0.f); + } +} + +#if WITH_EDITOR +void UHoudiniStaticMeshComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + const FName NAME_HoudiniIconVisible(TEXT("bHoudiniIconVisible")); + const FName NAME_Mesh(TEXT("Mesh")); + const FName NAME_PropertyChanged = PropertyChangedEvent.GetPropertyName(); + if (NAME_PropertyChanged == NAME_HoudiniIconVisible) + { + UpdateSpriteComponent(); + } + else if (NAME_PropertyChanged == NAME_Mesh) + { + NotifyMeshUpdated(); + } +} +#endif + +void UHoudiniStaticMeshComponent::SetHoudiniIconVisible(bool bInHoudiniIconVisible) +{ + bHoudiniIconVisible = bInHoudiniIconVisible; +#if WITH_EDITORONLY_DATA + UpdateSpriteComponent(); +#endif +} + +void UHoudiniStaticMeshComponent::NotifyMeshUpdated() +{ + MarkRenderStateDirty(); + if (Mesh) + { + LocalBounds = Mesh->CalcBounds(); + } + else + { + LocalBounds.Init(); + } + + UpdateBounds(); + +#if WITH_EDITORONLY_DATA + UpdateSpriteComponent(); +#endif +} + +#if WITH_EDITORONLY_DATA +void UHoudiniStaticMeshComponent::UpdateSpriteComponent() +{ + if (SpriteComponent) + { + const FBoxSphereBounds B = Bounds.TransformBy(GetComponentTransform().ToInverseMatrixWithScale()); + SpriteComponent->SetRelativeLocation(B.Origin + FVector(0, 0, B.BoxExtent.Size())); + SpriteComponent->SetVisibility(bHoudiniIconVisible); + } +} +#endif + +int32 UHoudiniStaticMeshComponent::GetNumMaterials() const +{ + // From UStaticMesh: + // @note : you don't have to consider Materials.Num() + // that only counts if overridden and it can't be more than GetStaticMesh()->Materials. + if (Mesh) + { + return Mesh->GetNumStaticMaterials(); + } + else + { + return 0; + } +} + +int32 UHoudiniStaticMeshComponent::GetMaterialIndex(FName MaterialSlotName) const +{ + return Mesh ? Mesh->GetMaterialIndex(MaterialSlotName) : -1; +} + +TArray UHoudiniStaticMeshComponent::GetMaterialSlotNames() const +{ + TArray MaterialNames; + if (Mesh) + { + const TArray &StaticMaterials = Mesh->GetStaticMaterials(); + const int32 NumMaterials = StaticMaterials.Num(); + for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) + { + const FStaticMaterial &StaticMaterial = StaticMaterials[MaterialIndex]; + MaterialNames.Add(StaticMaterial.MaterialSlotName); + } + } + return MaterialNames; +} + +bool UHoudiniStaticMeshComponent::IsMaterialSlotNameValid(FName MaterialSlotName) const +{ + return GetMaterialIndex(MaterialSlotName) >= 0; +} + +UMaterialInterface* UHoudiniStaticMeshComponent::GetMaterial(int32 MaterialIndex) const +{ + // From UStaticMesh: + // If we have a base materials array, use that + if (OverrideMaterials.IsValidIndex(MaterialIndex) && OverrideMaterials[MaterialIndex]) + { + return OverrideMaterials[MaterialIndex]; + } + // Otherwise get from static mesh + else + { + return Mesh ? Mesh->GetMaterial(MaterialIndex) : nullptr; + } +} + diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h index 1af521325..e428c9d4e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshComponent.h @@ -1,98 +1,98 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "Components/MeshComponent.h" - -#include "HoudiniStaticMeshComponent.generated.h" - -class UHoudiniStaticMesh; -class UBillboardComponent; - -UCLASS(EditInlineNew, ClassGroup = "Houdini Engine | Rendering") -class HOUDINIENGINERUNTIME_API UHoudiniStaticMeshComponent : public UMeshComponent -{ - GENERATED_BODY() - -public: - UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer); - - UFUNCTION() - void SetMesh(UHoudiniStaticMesh *InMesh); - - UFUNCTION() - UHoudiniStaticMesh* GetMesh() { return Mesh; } - - // Call this if the mesh updated (outside of calling SetMesh). - UFUNCTION() - void NotifyMeshUpdated(); - - virtual void OnRegister() override; - - //virtual void PostLoad() override; - - // UPrimitiveComponent interface - virtual FPrimitiveSceneProxy* CreateSceneProxy() override; - virtual int32 GetNumMaterials() const override; - virtual UMaterialInterface* GetMaterial(int32 ElementIndex) const override; - virtual int32 GetMaterialIndex(FName MaterialSlotName) const override; - virtual TArray GetMaterialSlotNames() const override; - virtual bool IsMaterialSlotNameValid(FName MaterialSlotName) const override; - // end - UPrimitiveComponent interface - - // USceneComponent Interface. - virtual FBoxSphereBounds CalcBounds(const FTransform& InLocalToWorld) const override; - // end - USceneComponent Interface. - -#if WITH_EDITOR - virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; -#endif - - UFUNCTION() - bool IsHoudiniIconVisible() const { return bHoudiniIconVisible; } - - UFUNCTION() - void SetHoudiniIconVisible(bool bInHoudiniIconVisible); - -protected: -#if WITH_EDITORONLY_DATA - virtual void UpdateSpriteComponent(); -#endif - - /** The mesh. */ - UPROPERTY(EditAnywhere, Category = "Mesh") - UHoudiniStaticMesh *Mesh; - - /** Local space bounds of mesh. */ - UPROPERTY() - FBox LocalBounds; - - UPROPERTY(EditAnywhere, Category = "Icons") - bool bHoudiniIconVisible; - +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Components/MeshComponent.h" + +#include "HoudiniStaticMeshComponent.generated.h" + +class UHoudiniStaticMesh; +class UBillboardComponent; + +UCLASS(EditInlineNew, ClassGroup = "Houdini Engine | Rendering") +class HOUDINIENGINERUNTIME_API UHoudiniStaticMeshComponent : public UMeshComponent +{ + GENERATED_BODY() + +public: + UHoudiniStaticMeshComponent(const FObjectInitializer &InInitialzer); + + UFUNCTION() + void SetMesh(UHoudiniStaticMesh *InMesh); + + UFUNCTION() + UHoudiniStaticMesh* GetMesh() { return Mesh; } + + // Call this if the mesh updated (outside of calling SetMesh). + UFUNCTION() + void NotifyMeshUpdated(); + + virtual void OnRegister() override; + + //virtual void PostLoad() override; + + // UPrimitiveComponent interface + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + virtual int32 GetNumMaterials() const override; + virtual UMaterialInterface* GetMaterial(int32 ElementIndex) const override; + virtual int32 GetMaterialIndex(FName MaterialSlotName) const override; + virtual TArray GetMaterialSlotNames() const override; + virtual bool IsMaterialSlotNameValid(FName MaterialSlotName) const override; + // end - UPrimitiveComponent interface + + // USceneComponent Interface. + virtual FBoxSphereBounds CalcBounds(const FTransform& InLocalToWorld) const override; + // end - USceneComponent Interface. + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + + UFUNCTION() + bool IsHoudiniIconVisible() const { return bHoudiniIconVisible; } + + UFUNCTION() + void SetHoudiniIconVisible(bool bInHoudiniIconVisible); + +protected: +#if WITH_EDITORONLY_DATA + virtual void UpdateSpriteComponent(); +#endif + + /** The mesh. */ + UPROPERTY(EditAnywhere, Category = "Mesh") + UHoudiniStaticMesh *Mesh; + + /** Local space bounds of mesh. */ + UPROPERTY() + FBox LocalBounds; + + UPROPERTY(EditAnywhere, Category = "Icons") + bool bHoudiniIconVisible; + }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp index e624c39e2..9ba27f3e1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.cpp @@ -1,552 +1,552 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "HoudiniStaticMeshSceneProxy.h" - -#include "Async/ParallelFor.h" -#include "Materials/Material.h" -#include "PrimitiveViewRelevance.h" -#include "Engine/Engine.h" - -#include "ProfilingDebugging/CpuProfilerTrace.h" - -#include "HoudiniStaticMeshComponent.h" -#include "HoudiniStaticMesh.h" - -// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h - -// -// FHoudiniStaticMeshRenderBufferSet -// - -FHoudiniStaticMeshRenderBufferSet::FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type InFeatureLevel) - : LocalVertexFactory(InFeatureLevel, "FHoudiniStaticMeshRenderBufferSet") -{ -} - - -FHoudiniStaticMeshRenderBufferSet::~FHoudiniStaticMeshRenderBufferSet() -{ - check(IsInRenderingThread()); - - if (NumTriangles > 0) - { - PositionVertexBuffer.ReleaseResource(); - ColorVertexBuffer.ReleaseResource(); - StaticMeshVertexBuffer.ReleaseResource(); - LocalVertexFactory.ReleaseResource(); - if (TriangleIndexBuffer.IsInitialized()) - { - TriangleIndexBuffer.ReleaseResource(); - } - } -} - -void FHoudiniStaticMeshRenderBufferSet::CopyBuffers() -{ - check(IsInRenderingThread()); - - if (NumTriangles == 0) - { - return; - } - - InitOrUpdateResource(&PositionVertexBuffer); - InitOrUpdateResource(&ColorVertexBuffer); - InitOrUpdateResource(&StaticMeshVertexBuffer); - - FLocalVertexFactory::FDataType Data; - PositionVertexBuffer.BindPositionVertexBuffer(&LocalVertexFactory, Data); - StaticMeshVertexBuffer.BindTangentVertexBuffer(&LocalVertexFactory, Data); - StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&LocalVertexFactory, Data); - ColorVertexBuffer.BindColorVertexBuffer(&LocalVertexFactory, Data); - - LocalVertexFactory.SetData(Data); - InitOrUpdateResource(&LocalVertexFactory); - - if (TriangleIndexBuffer.Indices.Num() > 0) - { - TriangleIndexBuffer.InitResource(); - } -} - -void FHoudiniStaticMeshRenderBufferSet::InitOrUpdateResource(FRenderResource* Resource) -{ - check(IsInRenderingThread()); - - if (Resource->IsInitialized()) - Resource->UpdateRHI(); - else - Resource->InitResource(); -} - -void FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet) -{ - if (BufferSet->NumTriangles == 0) - { - return; - } - - ENQUEUE_RENDER_COMMAND(FMeshRenderBufferSetDestroy)( - [BufferSet](FRHICommandListImmediate& RHICmdList) - { - delete BufferSet; - }); -} - -// -// End - FHoudiniStaticMeshRenderBufferSet -// - -// -// FHoudiniStaticMeshSceneProxy -// - -FHoudiniStaticMeshSceneProxy::FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel) - : FPrimitiveSceneProxy(InComponent) - , DefaultVertexColor(255, 255, 255) - , FeatureLevel(InFeatureLevel) - , Component(InComponent) - , MaterialRelevance(InComponent ? InComponent->GetMaterialRelevance(InFeatureLevel) : FMaterialRelevance()) -#if STATICMESH_ENABLE_DEBUG_RENDERING - , Owner(InComponent ? InComponent->GetOwner() : nullptr) -#endif -{ -} - -FHoudiniStaticMeshSceneProxy::~FHoudiniStaticMeshSceneProxy() -{ - check(IsInRenderingThread()); - - for (FHoudiniStaticMeshRenderBufferSet* BufferSet : BufferSets) - { - FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); - } -} - -uint32 FHoudiniStaticMeshSceneProxy::AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet) -{ - OutBufferSet = MakeNewBufferSet(); - OutBufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); - - BufferSetsLock.Lock(); - const uint32 NewIndex = BufferSets.Add(OutBufferSet); - BufferSetsLock.Unlock(); - return NewIndex; -} - -void FHoudiniStaticMeshSceneProxy::ReleaseRenderBufferSet(uint32 InIndex) -{ - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - - BufferSetsLock.Lock(); - check(BufferSets.IsValidIndex(InIndex)); - BufferSet = BufferSets[InIndex]; - BufferSets.RemoveAt(InIndex); - BufferSetsLock.Unlock(); - - FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); -} - -void FHoudiniStaticMeshSceneProxy::UpdatedReferencedMaterials() -{ - // copied from FPrimitiveSceneProxy::FPrimitiveSceneProxy() -#if WITH_EDITOR - TArray Materials; - Component->GetUsedMaterials(Materials, true); - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshRenderBufferSetDestroy)( - [this, Materials](FRHICommandListImmediate& RHICmdList) - { - this->SetUsedMaterialForVerification(Materials); - }); -#endif -} - -void FHoudiniStaticMeshSceneProxy::Build() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::Build")); - - // Allocate a buffer set per material - const uint32 NumMaterials = GetNumMaterials(); - if (NumMaterials == 0) - { - // No materials, allocate a singel buffer set using the default material - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - AllocateNewRenderBufferSet(BufferSet); - BufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); - } - else - { - for (uint32 MaterialIdx = 0; MaterialIdx < NumMaterials; ++MaterialIdx) - { - FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; - AllocateNewRenderBufferSet(BufferSet); - BufferSet->Material = GetMaterial(MaterialIdx); - } - } - - if (Component) - { - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (Mesh) - { - if (NumMaterials > 1 && Mesh->HasPerFaceMaterials()) - { - BuildBufferSetsByMaterial(); - } - else - { - BuildSingleBufferSet(); - } - } - } -} - -void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const -{ - const FEngineShowFlags EngineShowFlags = ViewFamily.EngineShowFlags; - const bool bRenderAsWireframe = (AllowDebugViewmodes() && EngineShowFlags.Wireframe); - - // Set up the wireframe material - FMaterialRenderProxy *WireframeMaterialProxy = nullptr; - if (bRenderAsWireframe) - { - FColoredMaterialRenderProxy *WireframeMaterialInstance = new FColoredMaterialRenderProxy( - GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, - FLinearColor(0.6f, 0.6f, 0.6f) - ); - Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); - WireframeMaterialProxy = WireframeMaterialInstance; - } - - const ESceneDepthPriorityGroup DepthPriority = SDPG_World; - - const int32 NumViews = Views.Num(); - for (int32 ViewIdx = 0; ViewIdx < NumViews; ++ViewIdx) - { - if (!(VisibilityMap & (1 << ViewIdx))) - continue; - - const FSceneView *View = Views[ViewIdx]; - - bool bHasPrecomputedVolumetricLightmap; - FMatrix PreviousLocalToWorld; - int32 SingleCaptureIndex; - bool bOutputVelocity; - GetScene().GetPrimitiveUniformShaderParameters_RenderThread( - GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity); - - const uint32 NumBufferSets = BufferSets.Num(); - for (uint32 BufferSetIdx = 0; BufferSetIdx < NumBufferSets; ++BufferSetIdx) - { - FHoudiniStaticMeshRenderBufferSet *BufferSet = BufferSets[BufferSetIdx]; - - UMaterialInterface *Material = BufferSet->Material; - FMaterialRenderProxy *MaterialProxy = Material->GetRenderProxy(); - - if (BufferSet->NumTriangles == 0) - continue; - - FDynamicPrimitiveUniformBuffer &DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); - DynamicPrimitiveUniformBuffer.Set( - GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity); - - if (BufferSet->TriangleIndexBuffer.Indices.Num() > 0) - { - FMeshBatch& Mesh = Collector.AllocateMesh(); - if (PopulateMeshElement(Mesh, *BufferSet, MaterialProxy, false, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) - { - Collector.AddMesh(ViewIdx, Mesh); - } - if (bRenderAsWireframe) - { - FMeshBatch& WireframeMesh = Collector.AllocateMesh(); - if (PopulateMeshElement(WireframeMesh, *BufferSet, WireframeMaterialProxy, true, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) - { - Collector.AddMesh(ViewIdx, WireframeMesh); - } - } - } - } - -#if STATICMESH_ENABLE_DEBUG_RENDERING - if (EngineShowFlags.StaticMeshes) - { - RenderBounds(Collector.GetPDI(ViewIdx), EngineShowFlags, GetBounds(), !Owner || IsSelected()); - } -#endif // STATICMESH_ENABLE_DEBUG_RENDERING - } -} - -bool FHoudiniStaticMeshSceneProxy::PopulateMeshElement( - FMeshBatch &InMeshBatch, - const FHoudiniStaticMeshRenderBufferSet& Buffers, - FMaterialRenderProxy* Material, - bool bRenderAsWireframe, - ESceneDepthPriorityGroup DepthPriority, - int ViewIndex, - FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const -{ - FMeshBatchElement& BatchElement = InMeshBatch.Elements[0]; - BatchElement.IndexBuffer = &Buffers.TriangleIndexBuffer; - InMeshBatch.bWireframe = bRenderAsWireframe; - InMeshBatch.VertexFactory = &Buffers.LocalVertexFactory; - InMeshBatch.MaterialRenderProxy = Material; - - BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; - - BatchElement.FirstIndex = 0; - BatchElement.NumPrimitives = Buffers.NumTriangles; - BatchElement.MinVertexIndex = 0; - BatchElement.MaxVertexIndex = Buffers.PositionVertexBuffer.GetNumVertices() - 1; - InMeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative(); - InMeshBatch.Type = PT_TriangleList; - InMeshBatch.DepthPriorityGroup = DepthPriority; - InMeshBatch.bCanApplyViewModeOverrides = false; - - return true; -} - - -FPrimitiveViewRelevance FHoudiniStaticMeshSceneProxy::GetViewRelevance(const FSceneView* View) const -{ - FPrimitiveViewRelevance Result; - - Result.bDrawRelevance = IsShown(View); - Result.bDynamicRelevance = true; - Result.bRenderCustomDepth = ShouldRenderCustomDepth(); - Result.bRenderInMainPass = ShouldRenderInMainPass(); - Result.bShadowRelevance = IsShadowCast(View); - Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; - Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); - MaterialRelevance.SetPrimitiveViewRelevance(Result); - Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass; - - return Result; -} - -bool FHoudiniStaticMeshSceneProxy::CanBeOccluded() const -{ - return !MaterialRelevance.bDisableDepthTest; -} - -void FHoudiniStaticMeshSceneProxy::PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs, uint32 InTriangleGroupStartIdx, uint32 InNumTrianglesInGroup) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::PopulateBuffers")); - - check(InMesh); - check(InBuffers); - - const uint32 NumTriangles = InTriangleIDs ? InNumTrianglesInGroup : InMesh->GetNumTriangles(); - InBuffers->NumTriangles = NumTriangles; - - if (NumTriangles == 0) - return; - - const uint32 NumVertices = NumTriangles * 3; - const uint32 NumUVLayers = InMesh->GetNumUVLayers(); - - InBuffers->PositionVertexBuffer.Init(NumVertices); - // There must be at least one UV layer - // TODO: Would it be possible to have no UV layers and bind to a dummy 0/black SRV? - InBuffers->StaticMeshVertexBuffer.Init(NumVertices, NumUVLayers > 0 ? NumUVLayers : 1); - InBuffers->ColorVertexBuffer.Init(NumVertices); - InBuffers->TriangleIndexBuffer.Indices.AddUninitialized(NumTriangles * 3); - - const TArray& VertexPositions = InMesh->GetVertexPositions(); - const TArray& TriangleIndices = InMesh->GetTriangleIndices(); - const TArray& VertexInstanceColors = InMesh->GetVertexInstanceColors(); - const TArray& VertexInstanceNormals = InMesh->GetVertexInstanceNormals(); - const TArray& VertexInstanceUTangents = InMesh->GetVertexInstanceUTangents(); - const TArray& VertexInstanceVTangents = InMesh->GetVertexInstanceVTangents(); - const TArray& VertexInstanceUVs = InMesh->GetVertexInstanceUVs(); - - const bool bHasColors = InMesh->HasColors(); - const bool bHasNormals = InMesh->HasNormals(); - const bool bHasTangents = InMesh->HasTangents(); - - FThreadSafeCounter VertCounter(0); - //for (uint32 TriangleIDIdx = 0; TriangleIDIdx < NumTriangles; ++TriangleIDIdx) - ParallelFor(NumTriangles, [&](uint32 TriangleIDIdx) - { - const uint32 TriangleID = InTriangleIDs ? (*InTriangleIDs)[InTriangleGroupStartIdx + TriangleIDIdx] : TriangleIDIdx; - const FIntVector &TriIndices = TriangleIndices[TriangleID]; - - FVector TangentU; - FVector TangentV; - uint32 VertIdx = VertCounter.Add(3); - for (uint8 TriVertIdx = 0; TriVertIdx < 3; ++TriVertIdx) - { - const uint32 MeshVtxIdx = TriIndices[TriVertIdx]; - const uint32 MeshVtxInstanceIdx = TriangleID * 3 + TriVertIdx; - - InBuffers->PositionVertexBuffer.VertexPosition(VertIdx) = VertexPositions[TriIndices[TriVertIdx]]; - - FVector Normal = bHasNormals ? VertexInstanceNormals[MeshVtxInstanceIdx] : FVector(0, 0, 1); - if (bHasTangents) - { - TangentU = VertexInstanceUTangents[MeshVtxInstanceIdx]; - TangentV = VertexInstanceVTangents[MeshVtxInstanceIdx]; - } - else - { - Normal.FindBestAxisVectors(TangentU, TangentV); - } - InBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, TangentU, TangentV, Normal); - - if (NumUVLayers > 0) - { - for (uint8 UVLayerIdx = 0; UVLayerIdx < NumUVLayers; ++UVLayerIdx) - { - InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, UVLayerIdx, VertexInstanceUVs[MeshVtxInstanceIdx]); - } - } - else - { - InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, 0, FVector2D::ZeroVector); - } - - InBuffers->ColorVertexBuffer.VertexColor(VertIdx) = bHasColors ? VertexInstanceColors[MeshVtxInstanceIdx] : DefaultVertexColor; - - InBuffers->TriangleIndexBuffer.Indices[VertIdx] = VertIdx; - VertIdx++; - } - }); -} - -void FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet")); - - if (!Component) - return; - - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (!Mesh) - return; - - if (BufferSets.Num() == 0) - return; - - FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets.Last(); - - PopulateBuffers(Mesh, Buffers); - - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( - [Buffers](FRHICommandListImmediate& RHICMdList) - { - Buffers->CopyBuffers(); - }); -} - -void FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial")); - - // We need to group tris by which material they use, and populate a buffer set for each group - if (!Component) - return; - - UHoudiniStaticMesh *Mesh = Component->GetMesh(); - if (!Mesh) - return; - - if (BufferSets.Num() == 0) - return; - - const TArray& MaterialIDsPerTriangle = Mesh->GetMaterialIDsPerTriangle(); - - const uint32 NumTriangles = MaterialIDsPerTriangle.Num(); - const uint32 NumMaterials = GetNumMaterials(); - TArray TriCountPerMaterialSafe; - TriCountPerMaterialSafe.Init(FThreadSafeCounter(0), NumMaterials); - ParallelFor(NumTriangles, [&](uint32 TriangleID) - { - const int32 MatID = MaterialIDsPerTriangle[TriangleID]; - if (MatID >= 0 && (uint32) MatID < NumMaterials) - { - TriCountPerMaterialSafe[MatID].Increment(); - } - }); - - TArray TriCountPerMaterial; - TArray OffsetPerMaterial; - TArray WrittenPerMaterial; - TriCountPerMaterial.Init(0, NumMaterials); - OffsetPerMaterial.Init(0, NumMaterials); - WrittenPerMaterial.Init(FThreadSafeCounter(0), NumMaterials); - for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) - { - const uint32 Count = TriCountPerMaterialSafe[MatID].GetValue(); - TriCountPerMaterial[MatID] = Count; - if (MatID > 0) - { - OffsetPerMaterial[MatID] = OffsetPerMaterial[MatID - 1] + TriCountPerMaterial[MatID - 1]; - } - } - - TArray GroupTriangleIDs; - GroupTriangleIDs.Init(0, NumTriangles); - ParallelFor(NumTriangles, [&](uint32 TriangleID) - { - const int32 MatID = MaterialIDsPerTriangle[TriangleID]; - if (MatID >= 0 && (uint32) MatID < NumMaterials) - { - GroupTriangleIDs[OffsetPerMaterial[MatID] + WrittenPerMaterial[MatID].Add(1)] = TriangleID; - } - }); - - for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) - { - if (TriCountPerMaterial[MatID] == 0) - continue; - - FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets[MatID]; - - PopulateBuffers( - Mesh, Buffers, - &GroupTriangleIDs, OffsetPerMaterial[MatID], TriCountPerMaterial[MatID] - ); - - ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( - [Buffers](FRHICommandListImmediate& RHICMdList) - { - Buffers->CopyBuffers(); - }); - } -} - -UMaterialInterface* FHoudiniStaticMeshSceneProxy::GetMaterial(uint32 InMaterialIdx) const -{ - if (!Component) - return UMaterial::GetDefaultMaterial(MD_Surface); - UMaterialInterface *Material = Component->GetMaterial(InMaterialIdx); - return Material ? Material : UMaterial::GetDefaultMaterial(MD_Surface); -} - -// -// End - FHoudiniStaticMeshSceneProxy -// +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "HoudiniStaticMeshSceneProxy.h" + +#include "Async/ParallelFor.h" +#include "Materials/Material.h" +#include "PrimitiveViewRelevance.h" +#include "Engine/Engine.h" + +#include "ProfilingDebugging/CpuProfilerTrace.h" + +#include "HoudiniStaticMeshComponent.h" +#include "HoudiniStaticMesh.h" + +// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h + +// +// FHoudiniStaticMeshRenderBufferSet +// + +FHoudiniStaticMeshRenderBufferSet::FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type InFeatureLevel) + : LocalVertexFactory(InFeatureLevel, "FHoudiniStaticMeshRenderBufferSet") +{ +} + + +FHoudiniStaticMeshRenderBufferSet::~FHoudiniStaticMeshRenderBufferSet() +{ + check(IsInRenderingThread()); + + if (NumTriangles > 0) + { + PositionVertexBuffer.ReleaseResource(); + ColorVertexBuffer.ReleaseResource(); + StaticMeshVertexBuffer.ReleaseResource(); + LocalVertexFactory.ReleaseResource(); + if (TriangleIndexBuffer.IsInitialized()) + { + TriangleIndexBuffer.ReleaseResource(); + } + } +} + +void FHoudiniStaticMeshRenderBufferSet::CopyBuffers() +{ + check(IsInRenderingThread()); + + if (NumTriangles == 0) + { + return; + } + + InitOrUpdateResource(&PositionVertexBuffer); + InitOrUpdateResource(&ColorVertexBuffer); + InitOrUpdateResource(&StaticMeshVertexBuffer); + + FLocalVertexFactory::FDataType Data; + PositionVertexBuffer.BindPositionVertexBuffer(&LocalVertexFactory, Data); + StaticMeshVertexBuffer.BindTangentVertexBuffer(&LocalVertexFactory, Data); + StaticMeshVertexBuffer.BindPackedTexCoordVertexBuffer(&LocalVertexFactory, Data); + ColorVertexBuffer.BindColorVertexBuffer(&LocalVertexFactory, Data); + + LocalVertexFactory.SetData(Data); + InitOrUpdateResource(&LocalVertexFactory); + + if (TriangleIndexBuffer.Indices.Num() > 0) + { + TriangleIndexBuffer.InitResource(); + } +} + +void FHoudiniStaticMeshRenderBufferSet::InitOrUpdateResource(FRenderResource* Resource) +{ + check(IsInRenderingThread()); + + if (Resource->IsInitialized()) + Resource->UpdateRHI(); + else + Resource->InitResource(); +} + +void FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet) +{ + if (BufferSet->NumTriangles == 0) + { + return; + } + + ENQUEUE_RENDER_COMMAND(FMeshRenderBufferSetDestroy)( + [BufferSet](FRHICommandListImmediate& RHICmdList) + { + delete BufferSet; + }); +} + +// +// End - FHoudiniStaticMeshRenderBufferSet +// + +// +// FHoudiniStaticMeshSceneProxy +// + +FHoudiniStaticMeshSceneProxy::FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel) + : FPrimitiveSceneProxy(InComponent) + , DefaultVertexColor(255, 255, 255) + , FeatureLevel(InFeatureLevel) + , Component(InComponent) + , MaterialRelevance(InComponent ? InComponent->GetMaterialRelevance(InFeatureLevel) : FMaterialRelevance()) +#if STATICMESH_ENABLE_DEBUG_RENDERING + , Owner(InComponent ? InComponent->GetOwner() : nullptr) +#endif +{ +} + +FHoudiniStaticMeshSceneProxy::~FHoudiniStaticMeshSceneProxy() +{ + check(IsInRenderingThread()); + + for (FHoudiniStaticMeshRenderBufferSet* BufferSet : BufferSets) + { + FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); + } +} + +uint32 FHoudiniStaticMeshSceneProxy::AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet) +{ + OutBufferSet = MakeNewBufferSet(); + OutBufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); + + BufferSetsLock.Lock(); + const uint32 NewIndex = BufferSets.Add(OutBufferSet); + BufferSetsLock.Unlock(); + return NewIndex; +} + +void FHoudiniStaticMeshSceneProxy::ReleaseRenderBufferSet(uint32 InIndex) +{ + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + + BufferSetsLock.Lock(); + check(BufferSets.IsValidIndex(InIndex)); + BufferSet = BufferSets[InIndex]; + BufferSets.RemoveAt(InIndex); + BufferSetsLock.Unlock(); + + FHoudiniStaticMeshRenderBufferSet::DestroyRenderBufferSet(BufferSet); +} + +void FHoudiniStaticMeshSceneProxy::UpdatedReferencedMaterials() +{ + // copied from FPrimitiveSceneProxy::FPrimitiveSceneProxy() +#if WITH_EDITOR + TArray Materials; + Component->GetUsedMaterials(Materials, true); + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshRenderBufferSetDestroy)( + [this, Materials](FRHICommandListImmediate& RHICmdList) + { + this->SetUsedMaterialForVerification(Materials); + }); +#endif +} + +void FHoudiniStaticMeshSceneProxy::Build() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::Build")); + + // Allocate a buffer set per material + const uint32 NumMaterials = GetNumMaterials(); + if (NumMaterials == 0) + { + // No materials, allocate a singel buffer set using the default material + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + AllocateNewRenderBufferSet(BufferSet); + BufferSet->Material = UMaterial::GetDefaultMaterial(MD_Surface); + } + else + { + for (uint32 MaterialIdx = 0; MaterialIdx < NumMaterials; ++MaterialIdx) + { + FHoudiniStaticMeshRenderBufferSet *BufferSet = nullptr; + AllocateNewRenderBufferSet(BufferSet); + BufferSet->Material = GetMaterial(MaterialIdx); + } + } + + if (Component) + { + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (Mesh) + { + if (NumMaterials > 1 && Mesh->HasPerFaceMaterials()) + { + BuildBufferSetsByMaterial(); + } + else + { + BuildSingleBufferSet(); + } + } + } +} + +void FHoudiniStaticMeshSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const +{ + const FEngineShowFlags EngineShowFlags = ViewFamily.EngineShowFlags; + const bool bRenderAsWireframe = (AllowDebugViewmodes() && EngineShowFlags.Wireframe); + + // Set up the wireframe material + FMaterialRenderProxy *WireframeMaterialProxy = nullptr; + if (bRenderAsWireframe) + { + FColoredMaterialRenderProxy *WireframeMaterialInstance = new FColoredMaterialRenderProxy( + GEngine->WireframeMaterial ? GEngine->WireframeMaterial->GetRenderProxy() : nullptr, + FLinearColor(0.6f, 0.6f, 0.6f) + ); + Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); + WireframeMaterialProxy = WireframeMaterialInstance; + } + + const ESceneDepthPriorityGroup DepthPriority = SDPG_World; + + const int32 NumViews = Views.Num(); + for (int32 ViewIdx = 0; ViewIdx < NumViews; ++ViewIdx) + { + if (!(VisibilityMap & (1 << ViewIdx))) + continue; + + const FSceneView *View = Views[ViewIdx]; + + bool bHasPrecomputedVolumetricLightmap; + FMatrix PreviousLocalToWorld; + int32 SingleCaptureIndex; + bool bOutputVelocity; + GetScene().GetPrimitiveUniformShaderParameters_RenderThread( + GetPrimitiveSceneInfo(), bHasPrecomputedVolumetricLightmap, PreviousLocalToWorld, SingleCaptureIndex, bOutputVelocity); + + const uint32 NumBufferSets = BufferSets.Num(); + for (uint32 BufferSetIdx = 0; BufferSetIdx < NumBufferSets; ++BufferSetIdx) + { + FHoudiniStaticMeshRenderBufferSet *BufferSet = BufferSets[BufferSetIdx]; + + UMaterialInterface *Material = BufferSet->Material; + FMaterialRenderProxy *MaterialProxy = Material->GetRenderProxy(); + + if (BufferSet->NumTriangles == 0) + continue; + + FDynamicPrimitiveUniformBuffer &DynamicPrimitiveUniformBuffer = Collector.AllocateOneFrameResource(); + DynamicPrimitiveUniformBuffer.Set( + GetLocalToWorld(), PreviousLocalToWorld, GetBounds(), GetLocalBounds(), true, bHasPrecomputedVolumetricLightmap, DrawsVelocity(), bOutputVelocity); + + if (BufferSet->TriangleIndexBuffer.Indices.Num() > 0) + { + FMeshBatch& Mesh = Collector.AllocateMesh(); + if (PopulateMeshElement(Mesh, *BufferSet, MaterialProxy, false, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) + { + Collector.AddMesh(ViewIdx, Mesh); + } + if (bRenderAsWireframe) + { + FMeshBatch& WireframeMesh = Collector.AllocateMesh(); + if (PopulateMeshElement(WireframeMesh, *BufferSet, WireframeMaterialProxy, true, DepthPriority, ViewIdx, DynamicPrimitiveUniformBuffer)) + { + Collector.AddMesh(ViewIdx, WireframeMesh); + } + } + } + } + +#if STATICMESH_ENABLE_DEBUG_RENDERING + if (EngineShowFlags.StaticMeshes) + { + RenderBounds(Collector.GetPDI(ViewIdx), EngineShowFlags, GetBounds(), !Owner || IsSelected()); + } +#endif // STATICMESH_ENABLE_DEBUG_RENDERING + } +} + +bool FHoudiniStaticMeshSceneProxy::PopulateMeshElement( + FMeshBatch &InMeshBatch, + const FHoudiniStaticMeshRenderBufferSet& Buffers, + FMaterialRenderProxy* Material, + bool bRenderAsWireframe, + ESceneDepthPriorityGroup DepthPriority, + int ViewIndex, + FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const +{ + FMeshBatchElement& BatchElement = InMeshBatch.Elements[0]; + BatchElement.IndexBuffer = &Buffers.TriangleIndexBuffer; + InMeshBatch.bWireframe = bRenderAsWireframe; + InMeshBatch.VertexFactory = &Buffers.LocalVertexFactory; + InMeshBatch.MaterialRenderProxy = Material; + + BatchElement.PrimitiveUniformBufferResource = &DynamicPrimitiveUniformBuffer.UniformBuffer; + + BatchElement.FirstIndex = 0; + BatchElement.NumPrimitives = Buffers.NumTriangles; + BatchElement.MinVertexIndex = 0; + BatchElement.MaxVertexIndex = Buffers.PositionVertexBuffer.GetNumVertices() - 1; + InMeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative(); + InMeshBatch.Type = PT_TriangleList; + InMeshBatch.DepthPriorityGroup = DepthPriority; + InMeshBatch.bCanApplyViewModeOverrides = false; + + return true; +} + + +FPrimitiveViewRelevance FHoudiniStaticMeshSceneProxy::GetViewRelevance(const FSceneView* View) const +{ + FPrimitiveViewRelevance Result; + + Result.bDrawRelevance = IsShown(View); + Result.bDynamicRelevance = true; + Result.bRenderCustomDepth = ShouldRenderCustomDepth(); + Result.bRenderInMainPass = ShouldRenderInMainPass(); + Result.bShadowRelevance = IsShadowCast(View); + Result.bTranslucentSelfShadow = bCastVolumetricTranslucentShadow; + Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); + MaterialRelevance.SetPrimitiveViewRelevance(Result); + Result.bVelocityRelevance = IsMovable() && Result.bOpaque && Result.bRenderInMainPass; + + return Result; +} + +bool FHoudiniStaticMeshSceneProxy::CanBeOccluded() const +{ + return !MaterialRelevance.bDisableDepthTest; +} + +void FHoudiniStaticMeshSceneProxy::PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs, uint32 InTriangleGroupStartIdx, uint32 InNumTrianglesInGroup) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::PopulateBuffers")); + + check(InMesh); + check(InBuffers); + + const uint32 NumTriangles = InTriangleIDs ? InNumTrianglesInGroup : InMesh->GetNumTriangles(); + InBuffers->NumTriangles = NumTriangles; + + if (NumTriangles == 0) + return; + + const uint32 NumVertices = NumTriangles * 3; + const uint32 NumUVLayers = InMesh->GetNumUVLayers(); + + InBuffers->PositionVertexBuffer.Init(NumVertices); + // There must be at least one UV layer + // TODO: Would it be possible to have no UV layers and bind to a dummy 0/black SRV? + InBuffers->StaticMeshVertexBuffer.Init(NumVertices, NumUVLayers > 0 ? NumUVLayers : 1); + InBuffers->ColorVertexBuffer.Init(NumVertices); + InBuffers->TriangleIndexBuffer.Indices.AddUninitialized(NumTriangles * 3); + + const TArray& VertexPositions = InMesh->GetVertexPositions(); + const TArray& TriangleIndices = InMesh->GetTriangleIndices(); + const TArray& VertexInstanceColors = InMesh->GetVertexInstanceColors(); + const TArray& VertexInstanceNormals = InMesh->GetVertexInstanceNormals(); + const TArray& VertexInstanceUTangents = InMesh->GetVertexInstanceUTangents(); + const TArray& VertexInstanceVTangents = InMesh->GetVertexInstanceVTangents(); + const TArray& VertexInstanceUVs = InMesh->GetVertexInstanceUVs(); + + const bool bHasColors = InMesh->HasColors(); + const bool bHasNormals = InMesh->HasNormals(); + const bool bHasTangents = InMesh->HasTangents(); + + FThreadSafeCounter VertCounter(0); + //for (uint32 TriangleIDIdx = 0; TriangleIDIdx < NumTriangles; ++TriangleIDIdx) + ParallelFor(NumTriangles, [&](uint32 TriangleIDIdx) + { + const uint32 TriangleID = InTriangleIDs ? (*InTriangleIDs)[InTriangleGroupStartIdx + TriangleIDIdx] : TriangleIDIdx; + const FIntVector &TriIndices = TriangleIndices[TriangleID]; + + FVector TangentU; + FVector TangentV; + uint32 VertIdx = VertCounter.Add(3); + for (uint8 TriVertIdx = 0; TriVertIdx < 3; ++TriVertIdx) + { + const uint32 MeshVtxIdx = TriIndices[TriVertIdx]; + const uint32 MeshVtxInstanceIdx = TriangleID * 3 + TriVertIdx; + + InBuffers->PositionVertexBuffer.VertexPosition(VertIdx) = VertexPositions[TriIndices[TriVertIdx]]; + + FVector Normal = bHasNormals ? VertexInstanceNormals[MeshVtxInstanceIdx] : FVector(0, 0, 1); + if (bHasTangents) + { + TangentU = VertexInstanceUTangents[MeshVtxInstanceIdx]; + TangentV = VertexInstanceVTangents[MeshVtxInstanceIdx]; + } + else + { + Normal.FindBestAxisVectors(TangentU, TangentV); + } + InBuffers->StaticMeshVertexBuffer.SetVertexTangents(VertIdx, TangentU, TangentV, Normal); + + if (NumUVLayers > 0) + { + for (uint8 UVLayerIdx = 0; UVLayerIdx < NumUVLayers; ++UVLayerIdx) + { + InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, UVLayerIdx, VertexInstanceUVs[MeshVtxInstanceIdx]); + } + } + else + { + InBuffers->StaticMeshVertexBuffer.SetVertexUV(VertIdx, 0, FVector2D::ZeroVector); + } + + InBuffers->ColorVertexBuffer.VertexColor(VertIdx) = bHasColors ? VertexInstanceColors[MeshVtxInstanceIdx] : DefaultVertexColor; + + InBuffers->TriangleIndexBuffer.Indices[VertIdx] = VertIdx; + VertIdx++; + } + }); +} + +void FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildSingleBufferSet")); + + if (!Component) + return; + + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (!Mesh) + return; + + if (BufferSets.Num() == 0) + return; + + FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets.Last(); + + PopulateBuffers(Mesh, Buffers); + + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( + [Buffers](FRHICommandListImmediate& RHICMdList) + { + Buffers->CopyBuffers(); + }); +} + +void FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniStaticMeshSceneProxy::BuildBufferSetsByMaterial")); + + // We need to group tris by which material they use, and populate a buffer set for each group + if (!Component) + return; + + UHoudiniStaticMesh *Mesh = Component->GetMesh(); + if (!Mesh) + return; + + if (BufferSets.Num() == 0) + return; + + const TArray& MaterialIDsPerTriangle = Mesh->GetMaterialIDsPerTriangle(); + + const uint32 NumTriangles = MaterialIDsPerTriangle.Num(); + const uint32 NumMaterials = GetNumMaterials(); + TArray TriCountPerMaterialSafe; + TriCountPerMaterialSafe.Init(FThreadSafeCounter(0), NumMaterials); + ParallelFor(NumTriangles, [&](uint32 TriangleID) + { + const int32 MatID = MaterialIDsPerTriangle[TriangleID]; + if (MatID >= 0 && (uint32) MatID < NumMaterials) + { + TriCountPerMaterialSafe[MatID].Increment(); + } + }); + + TArray TriCountPerMaterial; + TArray OffsetPerMaterial; + TArray WrittenPerMaterial; + TriCountPerMaterial.Init(0, NumMaterials); + OffsetPerMaterial.Init(0, NumMaterials); + WrittenPerMaterial.Init(FThreadSafeCounter(0), NumMaterials); + for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) + { + const uint32 Count = TriCountPerMaterialSafe[MatID].GetValue(); + TriCountPerMaterial[MatID] = Count; + if (MatID > 0) + { + OffsetPerMaterial[MatID] = OffsetPerMaterial[MatID - 1] + TriCountPerMaterial[MatID - 1]; + } + } + + TArray GroupTriangleIDs; + GroupTriangleIDs.Init(0, NumTriangles); + ParallelFor(NumTriangles, [&](uint32 TriangleID) + { + const int32 MatID = MaterialIDsPerTriangle[TriangleID]; + if (MatID >= 0 && (uint32) MatID < NumMaterials) + { + GroupTriangleIDs[OffsetPerMaterial[MatID] + WrittenPerMaterial[MatID].Add(1)] = TriangleID; + } + }); + + for (int32 MatID = 0; (uint32) MatID < NumMaterials; ++MatID) + { + if (TriCountPerMaterial[MatID] == 0) + continue; + + FHoudiniStaticMeshRenderBufferSet *Buffers = BufferSets[MatID]; + + PopulateBuffers( + Mesh, Buffers, + &GroupTriangleIDs, OffsetPerMaterial[MatID], TriCountPerMaterial[MatID] + ); + + ENQUEUE_RENDER_COMMAND(FHoudiniStaticMeshSceneProxy_BuildSingleBufferSet)( + [Buffers](FRHICommandListImmediate& RHICMdList) + { + Buffers->CopyBuffers(); + }); + } +} + +UMaterialInterface* FHoudiniStaticMeshSceneProxy::GetMaterial(uint32 InMaterialIdx) const +{ + if (!Component) + return UMaterial::GetDefaultMaterial(MD_Surface); + UMaterialInterface *Material = Component->GetMaterial(InMaterialIdx); + return Material ? Material : UMaterial::GetDefaultMaterial(MD_Surface); +} + +// +// End - FHoudiniStaticMeshSceneProxy +// diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h index b94df3f79..e384db9d0 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniStaticMeshSceneProxy.h @@ -1,173 +1,173 @@ -/* -* Copyright (c) <2021> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h - -#pragma once - -#include "CoreMinimal.h" -#include "PrimitiveSceneProxy.h" -#include "VertexFactory.h" -#include "LocalVertexFactory.h" -#include "Rendering/ColorVertexBuffer.h" -#include "Rendering/PositionVertexBuffer.h" -#include "Rendering/StaticMeshVertexBuffer.h" -#include "DynamicMeshBuilder.h" -#include "StaticMeshResources.h" - -#include "HoudiniStaticMeshComponent.h" - -class UHoudiniStaticMesh; - -class FHoudiniStaticMeshRenderBufferSet -{ -public: - // Data members - - /** The number of triangles in the buffer set. */ - int NumTriangles; - - /** The static mesh data buffer. */ - FStaticMeshVertexBuffer StaticMeshVertexBuffer; - - /** The position buffer. */ - FPositionVertexBuffer PositionVertexBuffer; - - /** The triangle indices buffer. */ - FDynamicMeshIndexBuffer32 TriangleIndexBuffer; - - /** The color buffer */ - FColorVertexBuffer ColorVertexBuffer; - - FLocalVertexFactory LocalVertexFactory; - - /** Default material for this mesh. */ - UMaterialInterface* Material = nullptr; - - // Functions - - FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type FeatureLevelType); - - virtual ~FHoudiniStaticMeshRenderBufferSet(); - - /** - * Copy buffers to GPU. - * @warning render thread only. - */ - virtual void CopyBuffers(); - - /** - * Initialize (or update) a render resource. - * @warning Render thread only. - */ - void InitOrUpdateResource(FRenderResource* Resource); - -protected: - friend class FHoudiniStaticMeshSceneProxy; - - // Queue a command on the render thread to destroy the given buffer set - static void DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet); -}; - - -class FHoudiniStaticMeshSceneProxy : public FPrimitiveSceneProxy -{ -public: - FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel); - - virtual ~FHoudiniStaticMeshSceneProxy(); - - uint32 AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet); - - void ReleaseRenderBufferSet(uint32 InIndex); - - void UpdatedReferencedMaterials(); - - // Build buffer sets to render the mesh. - virtual void Build(); - - // FPrimitiveSceneProxy - virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; - - virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; - - virtual bool CanBeOccluded() const override; - - virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + FPrimitiveSceneProxy::GetAllocatedSize()); } - - SIZE_T GetTypeHash() const override - { - static size_t UniquePointer; - return reinterpret_cast(&UniquePointer); - } - - // end - FPrimitiveSceneProxy - - // Color to use if vertex does not have an assigned color. - FColor DefaultVertexColor; - - ERHIFeatureLevel::Type FeatureLevel; - -protected: - void PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs=nullptr, uint32 InTriangleGroupStartIdx=0u, uint32 InNumTrianglesInGroup=0u); - - // Virtual function for creating a new buffer set instances. - // Subclasses can overwrite this is they use a different buffer set with - // different instantiation requirements. - virtual FHoudiniStaticMeshRenderBufferSet* MakeNewBufferSet() { return new FHoudiniStaticMeshRenderBufferSet(FeatureLevel); } - - // Build a single buffer set for the entire mesh (one material for the entire mesh). - void BuildSingleBufferSet(); - - void BuildBufferSetsByMaterial(); - - // Get the number of materials from the parent mesh/component - uint32 GetNumMaterials() const { return Component ? Component->GetNumMaterials() : 0; } - - virtual bool PopulateMeshElement( - FMeshBatch &InMeshBatch, - const FHoudiniStaticMeshRenderBufferSet& Buffers, - FMaterialRenderProxy* Material, - bool bRenderAsWireframe, - ESceneDepthPriorityGroup DepthPriority, - int ViewIndex, - FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const; - - virtual UMaterialInterface* GetMaterial(uint32 InMaterialIdx) const; - - UHoudiniStaticMeshComponent *Component; - - TArray BufferSets; - - FCriticalSection BufferSetsLock; - - FMaterialRelevance MaterialRelevance; - -private: -#if STATICMESH_ENABLE_DEBUG_RENDERING - AActor* Owner; -#endif +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Based on: Plugins\Experimental\MeshModelingToolset\Source\ModelingComponents\Private\BaseDynamicMeshSceneProxy.h + +#pragma once + +#include "CoreMinimal.h" +#include "PrimitiveSceneProxy.h" +#include "VertexFactory.h" +#include "LocalVertexFactory.h" +#include "Rendering/ColorVertexBuffer.h" +#include "Rendering/PositionVertexBuffer.h" +#include "Rendering/StaticMeshVertexBuffer.h" +#include "DynamicMeshBuilder.h" +#include "StaticMeshResources.h" + +#include "HoudiniStaticMeshComponent.h" + +class UHoudiniStaticMesh; + +class FHoudiniStaticMeshRenderBufferSet +{ +public: + // Data members + + /** The number of triangles in the buffer set. */ + int NumTriangles; + + /** The static mesh data buffer. */ + FStaticMeshVertexBuffer StaticMeshVertexBuffer; + + /** The position buffer. */ + FPositionVertexBuffer PositionVertexBuffer; + + /** The triangle indices buffer. */ + FDynamicMeshIndexBuffer32 TriangleIndexBuffer; + + /** The color buffer */ + FColorVertexBuffer ColorVertexBuffer; + + FLocalVertexFactory LocalVertexFactory; + + /** Default material for this mesh. */ + UMaterialInterface* Material = nullptr; + + // Functions + + FHoudiniStaticMeshRenderBufferSet(ERHIFeatureLevel::Type FeatureLevelType); + + virtual ~FHoudiniStaticMeshRenderBufferSet(); + + /** + * Copy buffers to GPU. + * @warning render thread only. + */ + virtual void CopyBuffers(); + + /** + * Initialize (or update) a render resource. + * @warning Render thread only. + */ + void InitOrUpdateResource(FRenderResource* Resource); + +protected: + friend class FHoudiniStaticMeshSceneProxy; + + // Queue a command on the render thread to destroy the given buffer set + static void DestroyRenderBufferSet(FHoudiniStaticMeshRenderBufferSet* BufferSet); +}; + + +class FHoudiniStaticMeshSceneProxy : public FPrimitiveSceneProxy +{ +public: + FHoudiniStaticMeshSceneProxy(UHoudiniStaticMeshComponent* InComponent, ERHIFeatureLevel::Type InFeatureLevel); + + virtual ~FHoudiniStaticMeshSceneProxy(); + + uint32 AllocateNewRenderBufferSet(FHoudiniStaticMeshRenderBufferSet*& OutBufferSet); + + void ReleaseRenderBufferSet(uint32 InIndex); + + void UpdatedReferencedMaterials(); + + // Build buffer sets to render the mesh. + virtual void Build(); + + // FPrimitiveSceneProxy + virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override; + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; + + virtual bool CanBeOccluded() const override; + + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + FPrimitiveSceneProxy::GetAllocatedSize()); } + + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast(&UniquePointer); + } + + // end - FPrimitiveSceneProxy + + // Color to use if vertex does not have an assigned color. + FColor DefaultVertexColor; + + ERHIFeatureLevel::Type FeatureLevel; + +protected: + void PopulateBuffers(const UHoudiniStaticMesh *InMesh, FHoudiniStaticMeshRenderBufferSet *InBuffers, const TArray* InTriangleIDs=nullptr, uint32 InTriangleGroupStartIdx=0u, uint32 InNumTrianglesInGroup=0u); + + // Virtual function for creating a new buffer set instances. + // Subclasses can overwrite this is they use a different buffer set with + // different instantiation requirements. + virtual FHoudiniStaticMeshRenderBufferSet* MakeNewBufferSet() { return new FHoudiniStaticMeshRenderBufferSet(FeatureLevel); } + + // Build a single buffer set for the entire mesh (one material for the entire mesh). + void BuildSingleBufferSet(); + + void BuildBufferSetsByMaterial(); + + // Get the number of materials from the parent mesh/component + uint32 GetNumMaterials() const { return Component ? Component->GetNumMaterials() : 0; } + + virtual bool PopulateMeshElement( + FMeshBatch &InMeshBatch, + const FHoudiniStaticMeshRenderBufferSet& Buffers, + FMaterialRenderProxy* Material, + bool bRenderAsWireframe, + ESceneDepthPriorityGroup DepthPriority, + int ViewIndex, + FDynamicPrimitiveUniformBuffer& DynamicPrimitiveUniformBuffer) const; + + virtual UMaterialInterface* GetMaterial(uint32 InMaterialIdx) const; + + UHoudiniStaticMeshComponent *Component; + + TArray BufferSets; + + FCriticalSection BufferSetsLock; + + FMaterialRelevance MaterialRelevance; + +private: +#if STATICMESH_ENABLE_DEBUG_RENDERING + AActor* Owner; +#endif }; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp index 9a44588b7..fbb77433f 100644 --- a/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp +++ b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.cpp @@ -1,38 +1,38 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "IHoudiniAssetStateEvents.h" - -void -IHoudiniAssetStateEvents::HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) -{ - if (InFromState == InToState) - return; - - FOnHoudiniAssetStateChange& StateChangeDelegate = GetOnHoudiniAssetStateChangeDelegate(); - if (StateChangeDelegate.IsBound()) - StateChangeDelegate.Broadcast(InHoudiniAssetContext, InFromState, InToState); -} +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "IHoudiniAssetStateEvents.h" + +void +IHoudiniAssetStateEvents::HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState) +{ + if (InFromState == InToState) + return; + + FOnHoudiniAssetStateChange& StateChangeDelegate = GetOnHoudiniAssetStateChangeDelegate(); + if (StateChangeDelegate.IsBound()) + StateChangeDelegate.Broadcast(InHoudiniAssetContext, InFromState, InToState); +} diff --git a/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h index 253006b8b..48c4f9b49 100644 --- a/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h +++ b/Source/HoudiniEngineRuntime/Private/IHoudiniAssetStateEvents.h @@ -1,57 +1,57 @@ -/* -* Copyright (c) <2018> Side Effects Software Inc. -* All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions are met: -* -* 1. Redistributions of source code must retain the above copyright notice, -* this list of conditions and the following disclaimer. -* -* 2. The name of Side Effects Software may not be used to endorse or -* promote products derived from this software without specific prior -* written permission. -* -* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS -* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, -* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Interface.h" - -#include "HoudiniAssetStateTypes.h" - -#include "IHoudiniAssetStateEvents.generated.h" - -// Delegate for when EHoudiniAssetState changes from InFromState to InToState on an instantiated Houdini Asset (InHoudiniAssetContext). -DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnHoudiniAssetStateChange, UObject*, const EHoudiniAssetState, const EHoudiniAssetState); - -UINTERFACE() -class HOUDINIENGINERUNTIME_API UHoudiniAssetStateEvents : public UInterface -{ - GENERATED_BODY() -}; - - -/** - * EHoudiniAssetState events: event handlers for when a Houdini Asset changes state. - */ -class HOUDINIENGINERUNTIME_API IHoudiniAssetStateEvents -{ - GENERATED_BODY() - -public: - virtual void HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState); - - virtual FOnHoudiniAssetStateChange& GetOnHoudiniAssetStateChangeDelegate() = 0; -}; +/* +* Copyright (c) <2018> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" + +#include "HoudiniAssetStateTypes.h" + +#include "IHoudiniAssetStateEvents.generated.h" + +// Delegate for when EHoudiniAssetState changes from InFromState to InToState on an instantiated Houdini Asset (InHoudiniAssetContext). +DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnHoudiniAssetStateChange, UObject*, const EHoudiniAssetState, const EHoudiniAssetState); + +UINTERFACE() +class HOUDINIENGINERUNTIME_API UHoudiniAssetStateEvents : public UInterface +{ + GENERATED_BODY() +}; + + +/** + * EHoudiniAssetState events: event handlers for when a Houdini Asset changes state. + */ +class HOUDINIENGINERUNTIME_API IHoudiniAssetStateEvents +{ + GENERATED_BODY() + +public: + virtual void HandleOnHoudiniAssetStateChange(UObject* InHoudiniAssetContext, const EHoudiniAssetState InFromState, const EHoudiniAssetState InToState); + + virtual FOnHoudiniAssetStateChange& GetOnHoudiniAssetStateChangeDelegate() = 0; +}; From dc503148df83065fd104649c3422f534f7f78dac Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Wed, 3 Nov 2021 18:26:41 -0400 Subject: [PATCH 14/16] Updated readme to mention that from H19 on, the V2 plugin now lives in the HoudiniEngineForUnreal repo. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a09e3f438..910cdf142 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Welcome to the repository for Version 2 of the Houdini Engine For Unreal Plugin. +** As only version 2 of the plugin now ships with Houdini 19.0, regular updates of the version 2 plugin now happen on the [HoudiniEngineForUnreal](https://github.com/sideeffects/HoudiniEngineForUnreal) repository. The source code available on this repository is only for Houdini 18.5, and will stop to recieve updates after the last Houdini 18.5 production build. ** + This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. From d7c3892db4cb1106c217daa7adb9fdb110332dca Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Tue, 16 Nov 2021 19:29:38 -0500 Subject: [PATCH 15/16] Version 2.0.54 - Houdini 18.5.759 Update 5 of the V2 Plugin. The plug-in is now linked to Houdini 18.5.759 / HAPI 3.7.3. As H18.5.759 is the last 18.5 production, this release will be the last update for the v2 plugin for H18.5. The Houdini 19.0 plugin can be found in the Houdini Engine for UYnreal repository https://github.com/sideeffects/HoudiniEngineForUnreal New Features: Added support for controlling Nanite settings via attributes (UE5) "unreal_nanite_enabled" (int, 0 or 1 ) can be used to enable Nanite processing when building a StaticMesh. "unreal_nanite_position_precision" (int) can be used to set the position precision. Step size is 2^(-Value) cm "unreal_nanite_percent_triangles" (float [0 1]) can be used to control the percentage of triangles to keep from LOD0. 1.0 = no reduction, 0.0 = no triangles. Added support for generating landscapes with edit layers By using the "Generate" output mode, (unreal_landscape_output_mode set to 0). Edit layer attributes arte now supported on heightfield primitives. It is now possible to set the default directory used in the browse dialog for File parameters by using the "default_dir" Parameter tag. By default, the plugin now uses the output nodes found in a HDA instead of the display flag. For landscape inputs, the option to marshall only selected landscapes was added, as well as the auto-selection of landscape component using the asset bounds. (those two features existed previously in the v1 plugin) Added an "Update selected components" button, that updates the selected components to the current landscape selection. Added a new attribute, "unreal_force_instancer", that can be used to ensure that an instancer component is created even if there is only a single instance of a StaticMesh. If unreal_foliage, unreal_split_instances or unreal_hierarchical_instancer is set and not 0, then the particular instancer type is created even if there is only a single instance of the StaticMesh. Added support for the "unreal_bake_name" attribute that can be used to set the name of the object(s) generated during baking. The "unreal_output_name" attribute takes precedence. When baking Houdini Instanced Actor Components the "unreal_output_name" (or "unreal_bake_name") attribute, if set, is now used to determine the baked instance actor names. When baking Houdini Instanced Actor Components the instanced object name is now used as the default bake name instead of the HDA actor's name (if the "unreal_output_name" and unreal_bake_name attributes are not set). Houdini Instanced Actor Components now support the "unreal_bake_actor" attribute when baking: the instanced actors are attached to unreal_bake_actor (which is created as an empty actor or re-used from a previous bake depending on the replacement mode). Bugs Fixes: - Fixed Wrong tooltip on Landscape Input "Update Landscape Data" - UE5: Fixed "double cooking" issue when instantiating a new HDA by dragging it on the viewport. Like in UE4, the HDA is only cooked after being dropped. - Fixed a regression that caused rebuilds to fail with "Fatal error - invalid". - All UHoudiniPublicAPIInput classes are now API exported in the C++ Public API. - AHoudiniAssetActor is now public in the C++ API. - Fixed setting multiparm parameters via the Public API. - Fixed various issues with Modify landscape output mode. - Fixed an issue where temporary landscapes were not spawned in the HAC's level. - Fixed an issue where HDA would output from hidden or nested subnets. - Fixed handling of output data between Display nodes and Output nodes to match Houdini's internal behaviour. - Fixed a material update issue for tiled landscapes. - Fixed an issue where input updates/changes via the public API, especially with curve inputs, would not always take effect. - Fixed loading of PDG output regression. - UE5: Fixed an issue where bake to actor would fail and not create any baked actors / assets. - Fixed a crash when trying to assign object path properties via generic attributes with an invalid path. - Fixed unreal_landscape_editlayer_target attribute for Landscape Modify mode. - Fixed landscape input targeting using Input0, Input1, etc. - Fixed Landscape edit layer transforms in Modify mode. - Fixed an issue where HDA would fail to produce an output if the HDA is a SOP/Subnet with the display flag set on an output node, and the "Use Output Nodes is enabled" option is enabled. - Houdini to Unreal StaticMesh translation: fixed an issue where duplicate warnings would be logged in Unreal for meshes with invalid face / vertex data, for each invalid index. - Fixed landscape tile resizing in Generate mode. - Fixed an issue where nested empty folders might crash Unreal or Houdini Engine - Fixed output updates / HDA cooking issues that can occur when using Session Sync. - Fixed an issue during baking where the baked actors of the work items of a TOP node in the PDG asset link were not shared with the remaining work items on the TOP node. - Fixed an issue when processing a spline/curve from Houdini where attributes such as "unreal_output_name" and "unreal_bake_actor" were not always updated when the HDA was cooked more than once, resulting in baking producing incorrect output names / bake actor names. If unreal_bake_actor is not set for a spline / curve we now use "unreal_output_name" to determine the bake actor name. - Improved how work items of TOP nodes are associated with previous cook / bake data in the PDG asset link. - Fixed various cases where after a successful bake the plug-in incorrectly reported that 0 packages were created and/or updated. - Fixed an issue where the HDA wouldn't properly switch between editable and non-editable curve. - Fixed cleanup issue of Houdini Spline input objects in Houdini sessions. - Fixed an issue where data tables with invalid names (spaces or colons) would not marshall data into Houdini Engine. - The generated mesh output now properly uses the collision complexity from the Houdini Mesh Generation Properties. A side effect of this is that the default collision complexity behaviour is now CTF_UseDefault instead of CTF_UseComplexAsSimple. - Fixed issues regarding exported input capsule colliders with rotations. - Fixed issues regarding exported input translation/rotation/scale of convex collider collisions. - Updated how the plugin tries to detect if an HDA should have a PDGAssetLink created. The plugin now simply looks for non scheduler/non bypassed TOP nodes in the asset. Previously it would look for TOP nodes inside all the non bypassed TOP/SOP network, which in some case drastically increased the HDA instantiation time, even for assets without any TOP nodes. - A Landscape's visibility layer is now properly named when sent as heightfield data to Houdini. - Fixed an issue where collision presets were not being saved from the plugin settings - When removing temporary PDG outputs the plugin now also uses the HoudiniAssetComponent's GUID in the package metadata to determine if the output asset may be deleted. - Fixed BSP transform issue. - Fixed an issue where nodes for world input actors were always recreated on update - Fixed an issue where landscape input transform changes were not applied correctly - Fixed an issue with output node: the plugin now ensures that all data from all output nodes are being output properly. - Prevent modifying non-editlayer landscapes when using the Modify Layer output mode. - Fixed tracking of transform changes, as well as transform uploading issues, of non-root components in World Input objects. - Fixed transform updating for Spline components - Fixed an issue where input nodes were not cleaned up in the session. - Prevent cooking of HDAs during PIE except for HDAs that are refining geometry. We now make sure that the plugin is inactive / not ticking during PIE after refinement is completed. --- Content/Examples/Cpp/CurveInputExample.cpp | 5 +- Content/Examples/Cpp/CurveInputExample.h | 2 +- .../Cpp/IterateOverHoudiniActorsExample.cpp | 128 ++ .../Cpp/IterateOverHoudiniActorsExample.h | 45 + .../Python/eau_curve_input_example.py | 24 +- ...erate_over_houdini_asset_actors_example.py | 103 ++ HoudiniEngine.uplugin | 4 +- README.md | 2 - Source/HoudiniEngine/HoudiniEngine.Build.cs | 8 +- .../HoudiniEngine/Private/HoudiniEngine.cpp | 9 +- Source/HoudiniEngine/Private/HoudiniEngine.h | 2 + .../Private/HoudiniEngineManager.cpp | 71 +- .../Private/HoudiniEngineManager.h | 4 + .../Private/HoudiniEnginePrivatePCH.h | 26 +- .../Private/HoudiniEngineTask.cpp | 4 + .../HoudiniEngine/Private/HoudiniEngineTask.h | 5 + .../Private/HoudiniEngineUtils.cpp | 765 +++++++- .../Private/HoudiniEngineUtils.h | 68 +- .../Private/HoudiniGeoImporter.cpp | 13 +- .../Private/HoudiniHandleTranslator.cpp | 6 +- .../Private/HoudiniInputTranslator.cpp | 531 ++++-- .../Private/HoudiniInputTranslator.h | 22 +- .../Private/HoudiniInstanceTranslator.cpp | 179 +- .../Private/HoudiniInstanceTranslator.h | 14 +- .../Private/HoudiniLandscapeTranslator.cpp | 1548 +++++++++++++---- .../Private/HoudiniLandscapeTranslator.h | 89 +- .../Private/HoudiniMaterialTranslator.cpp | 52 +- .../Private/HoudiniMeshTranslator.cpp | 208 ++- .../Private/HoudiniOutputTranslator.cpp | 480 +++-- .../Private/HoudiniOutputTranslator.h | 3 +- .../Private/HoudiniPDGManager.cpp | 127 +- .../Private/HoudiniPDGTranslator.cpp | 11 +- .../Private/HoudiniPackageParams.cpp | 44 +- .../Private/HoudiniPackageParams.h | 4 + .../Private/HoudiniParameterTranslator.cpp | 86 +- .../Private/HoudiniSplineTranslator.cpp | 46 +- .../Private/HoudiniStringResolver.cpp | 10 +- .../Private/HoudiniStringResolver.h | 2 +- .../Private/UnrealBrushTranslator.cpp | 2 +- .../Private/UnrealFoliageTypeTranslator.cpp | 5 +- .../Private/UnrealFoliageTypeTranslator.h | 3 +- .../Private/UnrealInstanceTranslator.cpp | 2 +- .../Private/UnrealLandscapeTranslator.cpp | 475 +++-- .../Private/UnrealLandscapeTranslator.h | 31 + .../Private/UnrealMeshTranslator.cpp | 63 +- .../Private/UnrealSplineTranslator.cpp | 4 +- Source/HoudiniEngine/Public/HAPI/HAPI.h | 8 +- .../HoudiniEngine/Public/HAPI/HAPI_Version.h | 4 +- .../Private/AssetTypeActions_HoudiniAsset.cpp | 2 +- .../Private/HoudiniAssetComponentDetails.cpp | 30 +- .../Private/HoudiniEngineBakeUtils.cpp | 1073 +++++++----- .../Private/HoudiniEngineBakeUtils.h | 89 +- .../Private/HoudiniEngineCommands.cpp | 102 +- .../Private/HoudiniEngineCommands.h | 2 + .../Private/HoudiniEngineDetails.cpp | 80 +- .../Private/HoudiniEngineEditor.cpp | 27 +- .../Private/HoudiniEngineEditor.h | 3 + .../Private/HoudiniEngineEditorUtils.cpp | 40 +- .../Private/HoudiniEngineEditorUtils.h | 8 + .../Private/HoudiniEngineStyle.cpp | 6 + .../Private/HoudiniGeoFactory.cpp | 12 +- .../Private/HoudiniHandleDetails.cpp | 2 +- .../Private/HoudiniInputDetails.cpp | 441 +++-- .../Private/HoudiniOutputDetails.cpp | 240 ++- .../Private/HoudiniOutputDetails.h | 3 - .../Private/HoudiniPDGDetails.cpp | 12 +- .../Private/HoudiniParameterDetails.cpp | 142 +- .../Private/HoudiniPublicAPIAssetWrapper.cpp | 41 +- .../Private/HoudiniPublicAPIInputTypes.cpp | 210 ++- .../Private/HoudiniPublicAPIObjectBase.cpp | 3 +- .../HoudiniSplineComponentVisualizer.cpp | 34 +- .../Public/HoudiniPublicAPIAssetWrapper.h | 39 +- .../Public/HoudiniPublicAPIInputTypes.h | 12 +- .../Private/HoudiniAssetActor.cpp | 17 +- .../HoudiniAssetBlueprintComponent.cpp | 2 +- .../Private/HoudiniAssetComponent.cpp | 159 +- .../Private/HoudiniAssetComponent.h | 33 + .../Private/HoudiniCompatibilityHelpers.cpp | 28 +- .../Private/HoudiniEngineRuntime.cpp | 6 +- .../Private/HoudiniEngineRuntimePrivatePCH.h | 1 + .../Private/HoudiniEngineRuntimeUtils.cpp | 2 +- .../Private/HoudiniGenericAttribute.cpp | 44 +- .../Private/HoudiniGeoPartObject.h | 7 + .../Private/HoudiniInput.cpp | 274 ++- .../Private/HoudiniInput.h | 35 +- .../Private/HoudiniInputObject.cpp | 154 +- .../Private/HoudiniInputObject.h | 73 +- .../HoudiniInstancedActorComponent.cpp | 16 +- .../HoudiniMeshSplitInstancerComponent.cpp | 10 +- .../Private/HoudiniOutput.cpp | 58 +- .../Private/HoudiniOutput.h | 4 + .../Private/HoudiniPDGAssetLink.cpp | 189 +- .../Private/HoudiniPDGAssetLink.h | 61 +- .../Private/HoudiniParameterFolderList.cpp | 2 +- .../Private/HoudiniParameterMultiParm.cpp | 76 + .../Private/HoudiniParameterMultiParm.h | 14 + .../Private/HoudiniParameterString.cpp | 2 +- .../Private/HoudiniRuntimeSettings.h | 2 +- .../Private/HoudiniSplineComponent.cpp | 14 +- .../Private/HoudiniSplineComponent.h | 3 + .../Public/HoudiniAsset.h | 106 ++ .../{Private => Public}/HoudiniAssetActor.h | 5 +- .../Public/HoudiniEngineRuntimeCommon.h | 167 ++ 103 files changed, 6945 insertions(+), 2619 deletions(-) create mode 100644 Content/Examples/Cpp/IterateOverHoudiniActorsExample.cpp create mode 100644 Content/Examples/Cpp/IterateOverHoudiniActorsExample.h create mode 100644 Content/Examples/Python/iterate_over_houdini_asset_actors_example.py create mode 100644 Source/HoudiniEngineRuntime/Public/HoudiniAsset.h rename Source/HoudiniEngineRuntime/{Private => Public}/HoudiniAssetActor.h (94%) create mode 100644 Source/HoudiniEngineRuntime/Public/HoudiniEngineRuntimeCommon.h diff --git a/Content/Examples/Cpp/CurveInputExample.cpp b/Content/Examples/Cpp/CurveInputExample.cpp index 28242407b..c63651218 100644 --- a/Content/Examples/Cpp/CurveInputExample.cpp +++ b/Content/Examples/Cpp/CurveInputExample.cpp @@ -24,8 +24,11 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include "Examples/CurveInputExample.h" +#include "CurveInputExample.h" +#include "Engine/StaticMesh.h" + +#include "HoudiniAsset.h" #include "HoudiniPublicAPI.h" #include "HoudiniPublicAPIBlueprintLib.h" #include "HoudiniPublicAPIAssetWrapper.h" diff --git a/Content/Examples/Cpp/CurveInputExample.h b/Content/Examples/Cpp/CurveInputExample.h index 769835f9d..6f9bd8898 100644 --- a/Content/Examples/Cpp/CurveInputExample.h +++ b/Content/Examples/Cpp/CurveInputExample.h @@ -34,7 +34,7 @@ class UHoudiniPublicAPIAssetWrapper; UCLASS() -class HOUDINIENGINEEDITOR_API ACurveInputExample : public AEditorUtilityActor +class ACurveInputExample : public AEditorUtilityActor { GENERATED_BODY() diff --git a/Content/Examples/Cpp/IterateOverHoudiniActorsExample.cpp b/Content/Examples/Cpp/IterateOverHoudiniActorsExample.cpp new file mode 100644 index 000000000..d08628bff --- /dev/null +++ b/Content/Examples/Cpp/IterateOverHoudiniActorsExample.cpp @@ -0,0 +1,128 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "IterateOverHoudiniActorsExample.h" + +#include "EngineUtils.h" + +#include "HoudiniAssetActor.h" +#include "HoudiniPublicAPI.h" +#include "HoudiniPublicAPIBlueprintLib.h" +#include "HoudiniPublicAPIAssetWrapper.h" + + +// Sets default values +AIterateOverHoudiniActorsExample::AIterateOverHoudiniActorsExample() +{ +} + +void AIterateOverHoudiniActorsExample::RunIterateOverHoudiniActorsExample_Implementation() +{ + // Get the API instance + UHoudiniPublicAPI* const API = UHoudiniPublicAPIBlueprintLib::GetAPI(); + // Ensure we have a running Houdini Engine session + if (!API->IsSessionValid()) + API->CreateSession(); + + // Iterate over HoudiniAssetActors in the world (by default when instantiating an HDA in the world an + // AHoudiniAssetActor is spawned, which has a component that manages the instantiated HDA node in Houdini Engine. + UWorld* const OurWorld = GetWorld(); + for (TActorIterator It(OurWorld); It; ++It) + { + AHoudiniAssetActor* const HDAActor = *It; + if (!IsValid(HDAActor)) + continue; + + // Print the name of the actor + UE_LOG(LogTemp, Display, TEXT("HDA Actor (Name, Label): %s, %s"), *(HDAActor->GetName()), *(HDAActor->GetActorLabel())); + + // Wrap it with the API + UHoudiniPublicAPIAssetWrapper* const Wrapper = UHoudiniPublicAPIAssetWrapper::CreateWrapper(this, HDAActor); + if (!IsValid(Wrapper)) + continue; + + // Print its parameters via the API + TMap ParameterTuples; + if (!Wrapper->GetParameterTuples(ParameterTuples)) + { + // The operation failed, log the error + FString ErrorMessage; + if (Wrapper->GetLastErrorMessage(ErrorMessage)) + UE_LOG(LogTemp, Warning, TEXT("%s"), *ErrorMessage); + + continue; + } + + UE_LOG(LogTemp, Display, TEXT("# Parameter Tuples: %d"), ParameterTuples.Num()); + for (const auto& Entry : ParameterTuples) + { + UE_LOG(LogTemp, Display, TEXT("\tParameter Tuple Name: %s"), *(Entry.Key.ToString())); + + const FHoudiniParameterTuple& ParameterTuple = Entry.Value; + TArray Strings; + FString Type; + if (ParameterTuple.BoolValues.Num() > 0) + { + Type = TEXT("Bool"); + for (const bool Value : ParameterTuple.BoolValues) + { + Strings.Add(Value ? TEXT("1") : TEXT("0")); + } + } + else if (ParameterTuple.FloatValues.Num() > 0) + { + Type = TEXT("Float"); + for (const float Value : ParameterTuple.FloatValues) + { + Strings.Add(FString::Printf(TEXT("%.4f"), Value)); + } + } + else if (ParameterTuple.Int32Values.Num() > 0) + { + Type = TEXT("Int32"); + for (const int32 Value : ParameterTuple.Int32Values) + { + Strings.Add(FString::Printf(TEXT("%d"), Value)); + } + } + else if (ParameterTuple.StringValues.Num() > 0) + { + Type = TEXT("String"); + Strings = ParameterTuple.StringValues; + } + + if (Type.Len() == 0) + { + UE_LOG(LogTemp, Display, TEXT("\t\tEmpty")); + } + else + { + UE_LOG(LogTemp, Display, TEXT("\t\t%s Values: %s"), *Type, *FString::Join(Strings, TEXT("; "))); + } + } + } +} diff --git a/Content/Examples/Cpp/IterateOverHoudiniActorsExample.h b/Content/Examples/Cpp/IterateOverHoudiniActorsExample.h new file mode 100644 index 000000000..76ce2db08 --- /dev/null +++ b/Content/Examples/Cpp/IterateOverHoudiniActorsExample.h @@ -0,0 +1,45 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "EditorUtilityActor.h" + +#include "IterateOverHoudiniActorsExample.generated.h" + +UCLASS() +class APIEXAMPLEEDITOR_API AIterateOverHoudiniActorsExample : public AEditorUtilityActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AIterateOverHoudiniActorsExample(); + + UFUNCTION(BlueprintNativeEvent, BlueprintCallable, CallInEditor) + void RunIterateOverHoudiniActorsExample(); +}; diff --git a/Content/Examples/Python/eau_curve_input_example.py b/Content/Examples/Python/eau_curve_input_example.py index f29022197..77658b961 100644 --- a/Content/Examples/Python/eau_curve_input_example.py +++ b/Content/Examples/Python/eau_curve_input_example.py @@ -28,9 +28,11 @@ @unreal.uclass() class CurveInputExample(unreal.PlacedEditorUtilityBase): - def __init__(self, *args, **kwargs): - self._asset_wrapper = None + # Use a FProperty to hold the reference to the API wrapper we create in + # run_curve_input_example + _asset_wrapper = unreal.uproperty(unreal.HoudiniPublicAPIAssetWrapper) + @unreal.ufunction(meta=dict(BlueprintCallable=True, CallInEditor=True)) def run_curve_input_example(self): # Get the API instance api = unreal.HoudiniPublicAPIBlueprintLib.get_api() @@ -40,15 +42,17 @@ def run_curve_input_example(self): # Load our HDA uasset example_hda = unreal.load_object(None, '/HoudiniEngine/Examples/hda/copy_to_curve_1_0.copy_to_curve_1_0') # Create an API wrapper instance for instantiating the HDA and interacting with it - self._asset_wrapper = api.instantiate_asset(example_hda, instantiate_at=unreal.Transform()) - if self._asset_wrapper: + wrapper = api.instantiate_asset(example_hda, instantiate_at=unreal.Transform()) + if wrapper: # Pre-instantiation is the earliest point where we can set parameter values - self._asset_wrapper.on_pre_instantiation_delegate.add_callable(self._set_initial_parameter_values) + wrapper.on_pre_instantiation_delegate.add_function(self, '_set_initial_parameter_values') # Jumping ahead a bit: we also want to configure inputs, but inputs are only available after instantiation - self._asset_wrapper.on_post_instantiation_delegate.add_callable(self._set_inputs) + wrapper.on_post_instantiation_delegate.add_function(self, '_set_inputs') # Jumping ahead a bit: we also want to print the outputs after the node has cook and the plug-in has processed the output - self._asset_wrapper.on_post_processing_delegate.add_callable(self._print_outputs) + wrapper.on_post_processing_delegate.add_function(self, '_print_outputs') + self.set_editor_property('_asset_wrapper', wrapper) + @unreal.ufunction(params=[unreal.HoudiniPublicAPIAssetWrapper], meta=dict(CallInEditor=True)) def _set_initial_parameter_values(self, in_wrapper): """ Set our initial parameter values: disable upvectorstart and set the scale to 0.2. """ # Uncheck the upvectoratstart parameter @@ -58,8 +62,9 @@ def _set_initial_parameter_values(self, in_wrapper): in_wrapper.set_float_parameter_value('scale', 0.2) # Since we are done with setting the initial values, we can unbind from the delegate - in_wrapper.on_pre_instantiation_delegate.remove_callable(self._set_initial_parameter_values) + in_wrapper.on_pre_instantiation_delegate.remove_function(self, '_set_initial_parameter_values') + @unreal.ufunction(params=[unreal.HoudiniPublicAPIAssetWrapper], meta=dict(CallInEditor=True)) def _set_inputs(self, in_wrapper): """ Configure our inputs: input 0 is a cube and input 1 a helix. """ # Create an empty geometry input @@ -94,8 +99,9 @@ def _set_inputs(self, in_wrapper): in_wrapper.set_input_at_index(1, curve_input) # unbind from the delegate, since we are done with setting inputs - in_wrapper.on_post_instantiation_delegate.remove_callable(self._set_inputs) + in_wrapper.on_post_instantiation_delegate.remove_function(self, '_set_inputs') + @unreal.ufunction(params=[unreal.HoudiniPublicAPIAssetWrapper], meta=dict(CallInEditor=True)) def _print_outputs(self, in_wrapper): """ Print the outputs that were generated by the HDA (after a cook) """ num_outputs = in_wrapper.get_num_outputs() diff --git a/Content/Examples/Python/iterate_over_houdini_asset_actors_example.py b/Content/Examples/Python/iterate_over_houdini_asset_actors_example.py new file mode 100644 index 000000000..5f42955d2 --- /dev/null +++ b/Content/Examples/Python/iterate_over_houdini_asset_actors_example.py @@ -0,0 +1,103 @@ +# Copyright (c) <2021> Side Effects Software Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. The name of Side Effects Software may not be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +# NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +""" Example script that uses unreal.ActorIterator to iterate over all +HoudiniAssetActors in the editor world, creates API wrappers for them, and +logs all of their parameter tuples. + +""" + +import unreal + + +def run(): + # Get the API instance + api = unreal.HoudiniPublicAPIBlueprintLib.get_api() + + # Get the editor world + editor_subsystem = None + world = None + try: + # In UE5 unreal.EditorLevelLibrary.get_editor_world is deprecated and + # unreal.UnrealEditorSubsystem.get_editor_world is the replacement, + # but unreal.UnrealEditorSubsystem does not exist in UE4 + editor_subsystem = unreal.UnrealEditorSubsystem() + except AttributeError: + world = unreal.EditorLevelLibrary.get_editor_world() + else: + world = editor_subsystem.get_editor_world() + + # Iterate over all Houdini Asset Actors in the editor world + for houdini_actor in unreal.ActorIterator(world, unreal.HoudiniAssetActor): + if not houdini_actor: + continue + + # Print the name and label of the actor + actor_name = houdini_actor.get_name() + actor_label = houdini_actor.get_actor_label() + print(f'HDA Actor (Name, Label): {actor_name}, {actor_label}') + + # Wrap the Houdini asset actor with the API + wrapper = unreal.HoudiniPublicAPIAssetWrapper.create_wrapper(world, houdini_actor) + if not wrapper: + continue + + # Get all parameter tuples of the HDA + parameter_tuples = wrapper.get_parameter_tuples() + if parameter_tuples is None: + # The operation failed, log the error message + error_message = wrapper.get_last_error_message() + if error_message is not None: + print(error_message) + + continue + + print(f'# Parameter Tuples: {len(parameter_tuples)}') + for name, data in parameter_tuples.items(): + print(f'\tParameter Tuple Name: {name}') + + type_name = None + values = None + if data.bool_values: + type_name = 'Bool' + values = '; '.join(('1' if v else '0' for v in data.bool_values)) + elif data.float_values: + type_name = 'Float' + values = '; '.join((f'{v:.4f}' for v in data.float_values)) + elif data.int32_values: + type_name = 'Int32' + values = '; '.join((f'{v:d}' for v in data.int32_values)) + elif data.string_values: + type_name = 'String' + values = '; '.join(data.string_values) + + if not type_name: + print('\t\tEmpty') + else: + print(f'\t\t{type_name} Values: {values}') + + +if __name__ == '__main__': + run() diff --git a/HoudiniEngine.uplugin b/HoudiniEngine.uplugin index f64ef058e..e87839c24 100644 --- a/HoudiniEngine.uplugin +++ b/HoudiniEngine.uplugin @@ -1,8 +1,8 @@ { "FileVersion" : 3, "FriendlyName" : "Houdini Engine v2", - "Version" : 18050696, - "VersionName" : "v2.0 - H18.5.696", + "Version" : 18050759, + "VersionName" : "v2.0 - H18.5.759", "CreatedBy" : "Side Effects Software Inc.", "CreatedByURL" : "http://www.sidefx.com", "DocsURL" : "http://www.sidefx.com/docs/unreal/", diff --git a/README.md b/README.md index 910cdf142..a09e3f438 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ Welcome to the repository for Version 2 of the Houdini Engine For Unreal Plugin. -** As only version 2 of the plugin now ships with Houdini 19.0, regular updates of the version 2 plugin now happen on the [HoudiniEngineForUnreal](https://github.com/sideeffects/HoudiniEngineForUnreal) repository. The source code available on this repository is only for Houdini 18.5, and will stop to recieve updates after the last Houdini 18.5 production build. ** - This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements. diff --git a/Source/HoudiniEngine/HoudiniEngine.Build.cs b/Source/HoudiniEngine/HoudiniEngine.Build.cs index e5985839c..af5a2dda9 100644 --- a/Source/HoudiniEngine/HoudiniEngine.Build.cs +++ b/Source/HoudiniEngine/HoudiniEngine.Build.cs @@ -32,8 +32,8 @@ /* - Houdini Version: 18.5.696 - Houdini Engine Version: 3.7.1 + Houdini Version: 18.5.759 + Houdini Engine Version: 3.7.3 Unreal Version: 4.27.0 */ @@ -47,9 +47,9 @@ public class HoudiniEngine : ModuleRules { private string GetHFSPath() { - string HoudiniVersion = "18.5.696"; + string HoudiniVersion = "18.5.759"; bool bIsRelease = true; - string HFSPath = "C:/cygwin/home/prisms/builder-new/Nightly18.5CMake/dev/hfs"; + string HFSPath = "C:/cygwin/home/prisms/builder-new/Nightly18.5CMakePython3/dev/hfs"; string RegistryPath = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Side Effects Software"; string Registry32Path = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\Side Effects Software"; string log; diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.cpp b/Source/HoudiniEngine/Private/HoudiniEngine.cpp index 336d8c106..2cee1b690 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngine.cpp @@ -382,7 +382,7 @@ FHoudiniEngine::RetrieveTaskInfo(const FGuid& InHapiGUID, FHoudiniEngineTaskInfo void FHoudiniEngine::AddHoudiniAssetComponent(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; if (HoudiniEngineManager) @@ -1084,6 +1084,13 @@ FHoudiniEngine::StopTicking() StopSession(SessionPtr); } +bool FHoudiniEngine::IsTicking() const +{ + if (!HoudiniEngineManager) + return false; + return HoudiniEngineManager->IsTicking(); +} + bool FHoudiniEngine::IsCookingEnabled() const { diff --git a/Source/HoudiniEngine/Private/HoudiniEngine.h b/Source/HoudiniEngine/Private/HoudiniEngine.h index ebe53eed8..07e2df662 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngine.h +++ b/Source/HoudiniEngine/Private/HoudiniEngine.h @@ -123,6 +123,8 @@ class HOUDINIENGINE_API FHoudiniEngine : public IModuleInterface // Stops the HoudiniEngineManager ticking and invalidate the session void StopTicking(); + bool IsTicking() const; + // Initialize HAPI bool InitializeHAPISession(); diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp index 22be802fc..a510b7804 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.cpp @@ -124,6 +124,11 @@ FHoudiniEngineManager::StopHoudiniTicking() } } +bool FHoudiniEngineManager::IsTicking() const +{ + return TickerHandle.IsValid(); +} + bool FHoudiniEngineManager::Tick(float DeltaTime) { @@ -164,12 +169,23 @@ FHoudiniEngineManager::Tick(float DeltaTime) // Invalid component, do not process continue; } - else if (CurrentComponent->IsPendingKill() - || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) + else if (!IsValid(CurrentComponent) || CurrentComponent->GetAssetState() == EHoudiniAssetState::Deleting) { // Component being deleted, do not process continue; } + + { + UWorld* World = CurrentComponent->GetWorld(); + if (World && World->IsPlayingReplay() || World->IsPlayInEditor()) + { + if (!CurrentComponent->IsPlayInEditorRefinementAllowed()) + { + // This component's world is current in PIE and this HDA is NOT allowed to cook / refine in PIE. + continue; + } + } + } if (!CurrentComponent->IsFullyLoaded()) { @@ -443,7 +459,7 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) { TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniEngineManager::ProcessComponent); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; // No need to process component not tied to an asset @@ -519,6 +535,9 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) if (HAC->NeedsToWaitForInputHoudiniAssets()) break; + // Make sure we empty the nodes to cook array to avoid cook errors caused by stale nodes + HAC->ClearOutputNodes(); + FGuid TaskGuid; FString HapiAssetName; UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); @@ -574,8 +593,19 @@ FHoudiniEngineManager::ProcessComponent(UHoudiniAssetComponent* HAC) bool bCookStarted = false; if (IsCookingEnabledForHoudiniAsset(HAC)) { + // Gather output nodes for the HAC + TArray OutputNodes; + FHoudiniEngineUtils::GatherAllAssetOutputs(HAC->GetAssetId(), HAC->bUseOutputNodes, HAC->bOutputTemplateGeos, OutputNodes); + HAC->SetOutputNodeIds(OutputNodes); + FGuid TaskGUID = HAC->GetHapiGUID(); - if ( StartTaskAssetCooking(HAC->GetAssetId(), HAC->NodeIdsToCook, HAC->GetDisplayName(), TaskGUID) ) + if ( StartTaskAssetCooking( + HAC->GetAssetId(), + OutputNodes, + HAC->GetDisplayName(), + HAC->bUseOutputNodes, + HAC->bOutputTemplateGeos, + TaskGUID) ) { // Updates the HAC's state HAC->SetAssetState(EHoudiniAssetState::Cooking); @@ -726,7 +756,7 @@ FHoudiniEngineManager::StartTaskAssetInstantiation(UHoudiniAsset* HoudiniAsset, OutTaskGUID.Invalidate(); // Load the HDA file - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + if (!IsValid(HoudiniAsset)) { HOUDINI_LOG_ERROR(TEXT("Cancelling asset instantiation - null or invalid Houdini Asset.")); return false; @@ -867,6 +897,7 @@ FHoudiniEngineManager::UpdateInstantiating(UHoudiniAssetComponent* HAC, EHoudini // Reset the cook counter. HAC->SetAssetCookCount(0); + HAC->ClearOutputNodes(); // If necessary, set asset transform. if (HAC->bUploadTransformsToHoudiniEngine) @@ -963,6 +994,8 @@ FHoudiniEngineManager::StartTaskAssetCooking( const HAPI_NodeId& AssetId, const TArray& NodeIdsToCook, const FString& DisplayName, + bool bUseOutputNodes, + bool bOutputTemplateGeos, FGuid& OutTaskGUID) { // Make sure we have a valid session before attempting anything @@ -988,6 +1021,9 @@ FHoudiniEngineManager::StartTaskAssetCooking( if (NodeIdsToCook.Num() > 0) Task.OtherNodeIds = NodeIdsToCook; + Task.bUseOutputNodes = bUseOutputNodes; + Task.bOutputTemplateGeos = bOutputTemplateGeos; + FHoudiniEngine::Get().AddTask(Task); return true; @@ -1150,14 +1186,8 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces } // Update the asset cook count using the node infos - int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + const int32 CookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); HAC->SetAssetCookCount(CookCount); - /* - if(CookCount >= 0 ) - HAC->SetAssetCookCount(CookCount); - else - HAC->SetAssetCookCount(HAC->GetAssetCookCount()+1); - */ bool bNeedsToTriggerViewportUpdate = false; if (bCookSuccess) @@ -1245,6 +1275,15 @@ FHoudiniEngineManager::PostCook(UHoudiniAssetComponent* HAC, const bool& bSucces HAC->ApplyInputPresets(); } + // Cache the current cook counts of the nodes so that we can more reliable determine + // whether content has changed next time build outputs. + const TArray OutputNodes = HAC->GetOutputNodeIds(); + for (int32 NodeId : OutputNodes) + { + int32 NodeCookCount = FHoudiniEngineUtils::HapiGetCookCount(HAC->GetAssetId()); + HAC->SetOutputNodeCookCount(NodeId, NodeCookCount); + } + // If we have downstream HDAs, we need to tell them we're done cooking HAC->NotifyCookedToDownstreamAssets(); @@ -1413,7 +1452,7 @@ FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* H { bool bManualRecook = false; bool bComponentEnable = false; - if (HAC && !HAC->IsPendingKill()) + if (IsValid(HAC)) { bManualRecook = HAC->HasRecookBeenRequested(); bComponentEnable = HAC->IsCookingEnabled(); @@ -1431,7 +1470,7 @@ FHoudiniEngineManager::IsCookingEnabledForHoudiniAsset(UHoudiniAssetComponent* H void FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) { HOUDINI_LOG_ERROR(TEXT("FHoudiniEngineManager::BuildStaticMeshesForAllHoudiniStaticMeshes called with HAC=nullptr")); return; @@ -1672,7 +1711,7 @@ void FHoudiniEngineManager::DisableEditorAutoSave(const UHoudiniAssetComponent* HAC) { #if WITH_EDITOR - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; if (!GUnrealEd) @@ -1711,7 +1750,7 @@ FHoudiniEngineManager::EnableEditorAutoSave(const UHoudiniAssetComponent* HAC = TSet ValidComponents; for (auto& CurHAC : DisableAutoSavingHACs) { - if (CurHAC && !CurHAC->IsPendingKill()) + if (IsValid(CurHAC)) { ValidComponents.Add(CurHAC); } diff --git a/Source/HoudiniEngine/Private/HoudiniEngineManager.h b/Source/HoudiniEngine/Private/HoudiniEngineManager.h index 8693f1671..599162864 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineManager.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineManager.h @@ -52,6 +52,8 @@ class FHoudiniEngineManager void StartHoudiniTicking(); void StopHoudiniTicking(); + bool IsTicking() const; + bool Tick(float DeltaTime); // Updates / Process a component @@ -110,6 +112,8 @@ class FHoudiniEngineManager const HAPI_NodeId& AssetId, const TArray& NodeIdsToCook, const FString& DisplayName, + bool bUseOutputNodes, + bool bOutputTemplateGeos, FGuid& OutTaskGUID); // Updates progress of the cooking task diff --git a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h index 413d9a299..870091235 100644 --- a/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h +++ b/Source/HoudiniEngine/Private/HoudiniEnginePrivatePCH.h @@ -188,6 +188,7 @@ #define HAPI_UNREAL_ATTRIB_HIERARCHICAL_INSTANCED_SM "unreal_hierarchical_instancer" #define HAPI_UNREAL_ATTRIB_INSTANCE_NUM_CUSTOM_FLOATS "unreal_num_custom_floats" #define HAPI_UNREAL_ATTRIB_INSTANCE_CUSTOM_DATA_PREFIX "unreal_per_instance_custom_data" +#define HAPI_UNREAL_ATTRIB_FORCE_INSTANCER "unreal_force_instancer" #define HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE_NAME HAPI_ATTRIB_NAME @@ -197,20 +198,28 @@ #define HAPI_UNREAL_ATTRIB_LANDSCAPE_STREAMING_PROXY "unreal_landscape_streaming_proxy" #define HAPI_UNREAL_ATTRIB_LANDSCAPE_LAYER_INFO "unreal_landscape_layer_info" // Landscape output mode: -// 0 - Default (Temp) mode -// 1 - Output heightfield to existing landscape editable layer +// 0 - Generate (generate a landscape from scratch) +// 1 - Modify Layer (modify one or more landscape layers only) #define HAPI_UNREAL_ATTRIB_LANDSCAPE_OUTPUT_MODE "unreal_landscape_output_mode" -#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_DEFAULT 0 -#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_EDITABLE_LAYER 1 +#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_GENERATE 0 +#define HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_MODIFY_LAYER 1 // Edit layer #define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME "unreal_landscape_editlayer_name" // Clear the editlayer before blitting new data #define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR "unreal_landscape_editlayer_clear" -// Landscape that is being targeted by "edit layer" outputs -#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET "unreal_landscape_editlayer_target" // Place the output layer "after" the given layer #define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER "unreal_landscape_editlayer_after" +// Landscape that is being targeted by "edit layer" outputs (only used in Modify Layer mode) +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET "unreal_landscape_editlayer_target" + +// Edit Layer types: +// 0 - Base layer: Values will be fit to the min/max height range in UE for optimal resolution. +// 1 - Additive layer: Values will be scaled similar to the base layer but will NOT be offset +// so that it will remain centered around the zero value. +#define HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TYPE "unreal_landscape_editlayer_type" +#define HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_BASE 0 +#define HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_ADDITIVE 1 #define HAPI_UNREAL_ATTRIB_GENERIC_UPROP_PREFIX "unreal_uproperty_" #define HAPI_UNREAL_ATTRIB_GENERIC_MAT_PARAM_PREFIX "unreal_material_parameter_" @@ -219,6 +228,7 @@ #define HAPI_UNREAL_ATTRIB_TEMP_FOLDER "unreal_temp_folder" #define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1 "unreal_generated_mesh_name" #define HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2 "unreal_output_name" +#define HAPI_UNREAL_ATTRIB_BAKE_NAME "unreal_bake_name" #define HAPI_UNREAL_ATTRIB_BAKE_ACTOR "unreal_bake_actor" #define HAPI_UNREAL_ATTRIB_BAKE_OUTLINER_FOLDER "unreal_bake_outliner_folder" @@ -258,6 +268,7 @@ #define HAPI_PARAM_TAG_NOSWAP "hengine_noswap" #define HAPI_PARAM_TAG_FILE_READONLY "filechooser_mode" #define HAPI_PARAM_TAG_UNITS "units" +#define HAPI_PARAM_TAG_DEFAULT_DIR "default_dir" // TODO: unused, remove! #define HAPI_PARAM_TAG_ASSET_REF "asset_ref" @@ -282,6 +293,9 @@ // Default material name. #define HAPI_UNREAL_DEFAULT_MATERIAL_NAME TEXT( "default_material" ) +// Visibility layer name +#define HAPI_UNREAL_VISIBILITY_LAYER_NAME TEXT( "visibility" ) + // Various variable names used to store meta information in generated packages. #define HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT TEXT( "HoudiniGeneratedObject" ) #define HAPI_UNREAL_PACKAGE_META_GENERATED_NAME TEXT( "HoudiniGeneratedName" ) diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp index e9feda76f..4202afc28 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.cpp @@ -32,6 +32,8 @@ FHoudiniEngineTask::FHoudiniEngineTask() : TaskType(EHoudiniEngineTaskType::None) , ActorName(TEXT("")) , AssetId(-1) + , bUseOutputNodes(false) + , bOutputTemplateGeos(false) , AssetLibraryId(-1) , AssetHapiName(-1) { @@ -44,6 +46,8 @@ FHoudiniEngineTask::FHoudiniEngineTask(EHoudiniEngineTaskType InTaskType, FGuid , TaskType(InTaskType) , ActorName(TEXT("")) , AssetId(-1) + , bUseOutputNodes(false) + , bOutputTemplateGeos(false) , AssetLibraryId(-1) , AssetHapiName(-1) { diff --git a/Source/HoudiniEngine/Private/HoudiniEngineTask.h b/Source/HoudiniEngine/Private/HoudiniEngineTask.h index 861c277ca..b3d5182a8 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineTask.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineTask.h @@ -92,6 +92,11 @@ struct HOUDINIENGINE_API FHoudiniEngineTask // Additional Node Id for the task // Can be used to apply a task to multiple nodes in the same HDA TArray OtherNodeIds; + // Cook results for each output node. + TMap CookResults; + + bool bUseOutputNodes; + bool bOutputTemplateGeos; // Library Id. HAPI_AssetLibraryId AssetLibraryId; diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp index 78f29a118..70d6d04ae 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.cpp @@ -282,6 +282,31 @@ FHoudiniEngineUtils::GetStatusString(HAPI_StatusType status_type, HAPI_StatusVer return FString(TEXT("")); } +FString +FHoudiniEngineUtils::HapiGetString(int32 StringHandle) +{ + int32 StringLength = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetStringBufLength( + FHoudiniEngine::Get().GetSession(), StringHandle, &StringLength)) + { + return FString(); + } + + if (StringLength <= 0) + return FString(); + + std::vector NameBuffer(StringLength, '\0'); + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetString( + FHoudiniEngine::Get().GetSession(), + StringHandle, &NameBuffer[0], StringLength ) ) + { + return FString(); + } + + return FString(std::string(NameBuffer.begin(), NameBuffer.end()).c_str()); +} + + const FString FHoudiniEngineUtils::GetCookResult() { @@ -364,7 +389,7 @@ FHoudiniEngineUtils::GetCookLog(TArray& InHACs) // Iterates on all the selected HAC and get their node errors for (auto& HAC : InHACs) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) continue; // Get the node errors, warnings and messages @@ -454,20 +479,18 @@ FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreate UPackage* Package = FindPackage(nullptr, *PackagePath); if (!Package) Package = LoadPackage(nullptr, *PackagePath, LOAD_None); - if (Package) + + if (IsValid(Package)) + { + PackageWorld = UWorld::FindWorldInPackage(Package); + } + else if (Package != nullptr) { // If the package is not valid (pending kill) rename it - if (Package->IsPendingKill()) + if (bCreateMissingPackage) { - if (bCreateMissingPackage) - { - Package->Rename( - *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); - } - } - else - { - PackageWorld = UWorld::FindWorldInPackage(Package); + Package->Rename( + *MakeUniqueObjectName(Package->GetOuter(), Package->GetClass(), FName(PackagePath + TEXT("_pending_kill"))).ToString()); } } @@ -498,7 +521,8 @@ FHoudiniEngineUtils::FindWorldInPackage(const FString& PackagePath, bool bCreate return PackageWorld; } -bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( +bool +FHoudiniEngineUtils::FindWorldAndLevelForSpawning( UWorld* CurrentWorld, const FString& PackagePath, bool bCreateMissingPackage, @@ -536,7 +560,8 @@ bool FHoudiniEngineUtils::FindWorldAndLevelForSpawning( return true; } -void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) +void +FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) { FString WorldPath = FPaths::GetPath(InWorld->GetPathName()); IAssetRegistry& AssetRegistry = FAssetRegistryModule::GetRegistry(); @@ -545,34 +570,43 @@ void FHoudiniEngineUtils::RescanWorldPath(UWorld* InWorld) AssetRegistry.ScanPathsSynchronous(Packages, true); } -AActor* FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) +AActor* +FHoudiniEngineUtils::FindOrRenameInvalidActorGeneric(UClass* InClass, UWorld* InWorld, const FString& InName, AActor*& OutFoundActor) { // AActor* NamedActor = FindObject(Outer, *InName, false); // Find ANY actor in the world matching the given name. AActor* NamedActor = FindActorInWorld(InWorld, FName(InName)); OutFoundActor = NamedActor; - bool bShouldRename = false; - if (NamedActor) + + FString Suffix; + if (IsValid(NamedActor)) { - if (NamedActor->GetClass()->IsChildOf(InClass) && !NamedActor->IsPendingKill()) + if (NamedActor->GetClass()->IsChildOf(InClass)) { return NamedActor; } else { - FString Suffix; - bool bShouldUpdateLabel = false; - if (NamedActor->IsPendingKill()) - Suffix = "_pendingkill"; - else - Suffix = "_0"; // A previous actor that had the same name. - const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName+Suffix); + // A previous actor that had the same name. + Suffix = "_0"; } } + else + { + if (!NamedActor) + return nullptr; + else + Suffix = "_pendingkill"; + } + + // Rename the invalid/previous actor + const FName NewName = FHoudiniEngineUtils::RenameToUniqueActor(NamedActor, InName + Suffix); + return nullptr; } -void FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) +void +FHoudiniEngineUtils::LogPackageInfo(const FString& InLongPackageName) { LogPackageInfo( LoadPackage(nullptr, *InLongPackageName, 0) ); } @@ -612,7 +646,8 @@ void FHoudiniEngineUtils::LogPackageInfo(const UPackage* InPackage) HOUDINI_LOG_MESSAGE(DebugTextLine); } -void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) +void +FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) { UPackage* Package = LoadPackage(nullptr, *InLongPackageName, 0); UWorld* World = nullptr; @@ -625,7 +660,8 @@ void FHoudiniEngineUtils::LogWorldInfo(const FString& InLongPackageName) LogWorldInfo(World); } -void FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) +void +FHoudiniEngineUtils::LogWorldInfo(const UWorld* InWorld) { HOUDINI_LOG_MESSAGE(DebugTextLine); @@ -875,6 +911,7 @@ FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( const FString &BakeFolder, const FString &ObjectName, const FString &HoudiniAssetName, + const FString &HoudiniAssetActorName, EPackageReplaceMode InReplaceMode, bool bAutomaticallySetAttemptToLoadMissingPackages) { @@ -885,7 +922,7 @@ FHoudiniEngineUtils::FillInPackageParamsForBakingOutput( OutPackageParams.PackageMode = EPackageMode::Bake; OutPackageParams.ReplaceMode = InReplaceMode; OutPackageParams.HoudiniAssetName = HoudiniAssetName; - OutPackageParams.HoudiniAssetActorName = HoudiniAssetName; + OutPackageParams.HoudiniAssetActorName = HoudiniAssetActorName; OutPackageParams.ObjectName = ObjectName; } @@ -896,11 +933,12 @@ FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( const FHoudiniOutputObjectIdentifier& InIdentifier, const FHoudiniOutputObject& InOutputObject, const FString &InDefaultObjectName, - const FString &InHoudiniAssetName, FHoudiniPackageParams& OutPackageParams, FHoudiniAttributeResolver& OutResolver, const FString &InDefaultBakeFolder, EPackageReplaceMode InReplaceMode, + const FString &InHoudiniAssetName, + const FString &InHoudiniAssetActorName, bool bAutomaticallySetAttemptToLoadMissingPackages, bool bInSkipObjectNameResolutionAndUseDefault, bool bInSkipBakeFolderResolutionAndUseDefault) @@ -916,14 +954,38 @@ FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( // const FString DefaultBakeFolder = !InDefaultBakeFolder.IsEmpty() ? InDefaultBakeFolder : FHoudiniEngineRuntime::Get().GetDefaultBakeFolder(); + + // If InHoudiniAssetName was specified, use that, otherwise use the name of the UHoudiniAsset used by the + // HoudiniAssetComponent + FString HoudiniAssetName(TEXT("")); + if (!InHoudiniAssetName.IsEmpty()) + { + HoudiniAssetName = InHoudiniAssetName; + } + else if (IsValid(HoudiniAssetComponent) && IsValid(HoudiniAssetComponent->GetHoudiniAsset())) + { + HoudiniAssetName = HoudiniAssetComponent->GetHoudiniAsset()->GetName(); + } + // If InHoudiniAssetActorName was specified, use that, otherwise use the name of the owner of HoudiniAssetComponent + FString HoudiniAssetActorName(TEXT("")); + if (!InHoudiniAssetActorName.IsEmpty()) + { + HoudiniAssetActorName = InHoudiniAssetActorName; + } + else if (IsValid(HoudiniAssetComponent) && IsValid(HoudiniAssetComponent->GetOwner())) + { + HoudiniAssetActorName = HoudiniAssetComponent->GetOwner()->GetName(); + } + const bool bHasBakeNameUIOverride = !InOutputObject.BakeName.IsEmpty(); FillInPackageParamsForBakingOutput( OutPackageParams, InIdentifier, DefaultBakeFolder, bHasBakeNameUIOverride ? InOutputObject.BakeName : InDefaultObjectName, - InHoudiniAssetName, + HoudiniAssetName, + HoudiniAssetActorName, InReplaceMode, bAutomaticallySetAttemptToLoadMissingPackages); @@ -949,7 +1011,8 @@ FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( } else { - ObjectName = OutResolver.ResolveOutputName(); + constexpr bool bForBake = true; + ObjectName = OutResolver.ResolveOutputName(bForBake); if (ObjectName.IsEmpty()) ObjectName = InDefaultObjectName; } @@ -1027,7 +1090,7 @@ FHoudiniEngineUtils::GatherLandscapeInputs( AllInputLandscapes.Add(LandscapeProxy); } } - }); + }, true); } if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) @@ -1041,7 +1104,9 @@ FHoudiniEngineUtils::GatherLandscapeInputs( AllInputLandscapes.Add(InputLandscape); if (CurrentInput->GetUpdateInputLandscape()) + { InputLandscapesToUpdate.Add(InputLandscape); + } } } @@ -1404,7 +1469,7 @@ FHoudiniEngineUtils::LoadHoudiniAsset(const UHoudiniAsset * HoudiniAsset, HAPI_A { OutAssetLibraryId = -1; - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + if (!IsValid(HoudiniAsset)) return false; if (!FHoudiniEngineUtils::IsInitialized()) @@ -1725,6 +1790,29 @@ FHoudiniEngineUtils::GetAssetPreset(const HAPI_NodeId& AssetNodeId, TArray< char return true; } +bool +FHoudiniEngineUtils::HapiGetAbsNodePath(const HAPI_NodeId& InNodeId, FString& OutPath) +{ + // Retrieve Path to the given Node, relative to the other given Node + if (InNodeId < 0) + return false; + + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(InNodeId)) + return false; + + HAPI_StringHandle StringHandle; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetNodePath( + FHoudiniEngine::Get().GetSession(), + InNodeId, -1, &StringHandle)) + { + if(FHoudiniEngineString::ToFString(StringHandle, OutPath)) + { + return true; + } + } + return false; +} + bool FHoudiniEngineUtils::HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath) @@ -1909,6 +1997,497 @@ FHoudiniEngineUtils::HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& AllObjectIds, const HAPI_NodeId& InRootNodeId, const HAPI_NodeId& InChildNodeId) +{ + // Walk up the hierarchy from child to root. + // If any node in that hierarchy is not in the "AllObjectIds" set, the OBJ node is considered to + // be hidden. + + if (InChildNodeId == InRootNodeId) + return true; + + HAPI_NodeId ChildNodeId = InChildNodeId; + + HAPI_ObjectInfo ChildObjInfo; + HAPI_NodeInfo ChildNodeInfo; + + FHoudiniApi::ObjectInfo_Init(&ChildObjInfo); + FHoudiniApi::NodeInfo_Init(&ChildNodeInfo); + + do + { + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectInfo( + FHoudiniEngine::Get().GetSession(), + ChildNodeId, &ChildObjInfo)) + { + // If can't get info for this object, we can't say whether it's visible (or not). + return false; + } + + if (!ChildObjInfo.isVisible || ChildObjInfo.nodeId < 0) + { + // We have an object in the chain that is not visible. Return false immediately! + return false; + } + + if (ChildNodeId != InChildNodeId) + { + // Only perform this check for 'parents' of the incoming child node + if ( !AllObjectIds.Contains(ChildNodeId)) + { + // There is a non-object node in the hierarchy between the child and asset root, rendering the + // child object node invisible. + return false; + } + } + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + ChildNodeId, &ChildNodeInfo)) + { + // Could not retrieve node info. + return false; + } + + // Go up the hierarchy. + ChildNodeId = ChildNodeInfo.parentId; + } while (ChildNodeId != InRootNodeId && ChildNodeId >= 0); + + // We have traversed the whole hierarchy up to the root and nothing indicated that + // we _shouldn't_ be visible. + return true; +} + + +bool +FHoudiniEngineUtils::IsSopNode(const HAPI_NodeId& NodeId) +{ + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), + NodeId, + &NodeInfo + ), + false + ); + return NodeInfo.type == HAPI_NODETYPE_SOP; +} + + +bool FHoudiniEngineUtils::ContainsSopNodes(const HAPI_NodeId& NodeId) +{ + int ChildCount = 0; + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + NodeId, + HAPI_NODETYPE_SOP, + HAPI_NODEFLAGS_ANY, + false, + &ChildCount + ), + false + ); + return ChildCount > 0; +} + +bool FHoudiniEngineUtils::GetOutputIndex(const HAPI_NodeId& InNodeId, int32& OutOutputIndex) +{ + int TempValue = -1; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetParmIntValue( + FHoudiniEngine::Get().GetSession(), + InNodeId, + TCHAR_TO_ANSI(TEXT("outputidx")), + 0, // index + &TempValue)) + { + OutOutputIndex = TempValue; + return true; + } + + return false; +} + +bool +FHoudiniEngineUtils::GatherAllAssetOutputs( + const HAPI_NodeId& AssetId, + const bool bUseOutputNodes, + const bool bOutputTemplatedGeos, + TArray& OutOutputNodes) +{ + OutOutputNodes.Empty(); + + // Ensure the asset has a valid node ID + if (AssetId < 0) + { + return false; + } + + // Get the AssetInfo + HAPI_AssetInfo AssetInfo; + FHoudiniApi::AssetInfo_Init(&AssetInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + + // Get the Asset NodeInfo + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetNodeInfo), false); + + FString CurrentAssetName; + { + FHoudiniEngineString hapiSTR(AssetInfo.nameSH); + hapiSTR.ToFString(CurrentAssetName); + } + + // In certain cases, such as PDG output processing we might end up with a SOP node instead of a + // container. In that case, don't try to run child queries on this node. They will fail. + const bool bAssetHasChildren = !(AssetNodeInfo.type == HAPI_NODETYPE_SOP && AssetNodeInfo.childNodeCount == 0); + + // Retrieve information about each object contained within our asset. + TArray ObjectInfos; + TArray ObjectTransforms; + if (!FHoudiniEngineUtils::HapiGetObjectInfos(AssetId, ObjectInfos, ObjectTransforms)) + return false; + + // Find the editable nodes in the asset. + TArray EditableGeoInfos; + int32 EditableNodeCount = 0; + if (bAssetHasChildren) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + } + + // All editable nodes will be output, regardless + // of whether the subnet is considered visible or not. + if (EditableNodeCount > 0) + { + TArray EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // TODO: Check whether this display geo is actually being output + // Just because this is a display node doesn't mean that it will be output (it + // might be in a hidden subnet) + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) + continue; + + // Add this geo to the geo info array + EditableGeoInfos.Add(CurrentEditableGeoInfo); + } + } + + const bool bIsSopAsset = AssetInfo.nodeId != AssetInfo.objectNodeId; + bool bUseOutputFromSubnets = true; + if (bAssetHasChildren) + { + if (FHoudiniEngineUtils::ContainsSopNodes(AssetInfo.nodeId)) + { + // This HDA contains immediate SOP nodes. Don't look for subnets to output. + bUseOutputFromSubnets = false; + } + else + { + // Assume we're using a subnet-based HDA + bUseOutputFromSubnets = true; + } + } + else + { + // This asset doesn't have any children. Don't try to find subnets. + bUseOutputFromSubnets = false; + } + + // Before we can perform visibility checks on the Object nodes, we have + // to build a set of all the Object node ids. The 'AllObjectIds' act + // as a visibility filter. If an Object node is not present in this + // list, the content of that node will not be displayed (display / output / templated nodes). + // NOTE that if the HDA contains immediate SOP nodes we will ignore + // all subnets and only use the data outputs directly from the HDA. + + TSet AllObjectIds; + if (bUseOutputFromSubnets) + { + int NumObjSubnets; + TArray ObjectIds; + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, + HAPI_NODETYPE_OBJ, + HAPI_NODEFLAGS_OBJ_SUBNET, + true, + &NumObjSubnets + ), + false); + + ObjectIds.SetNumUninitialized(NumObjSubnets); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, + ObjectIds.GetData(), + NumObjSubnets + ), + false); + AllObjectIds.Append(ObjectIds); + } + else + { + AllObjectIds.Add(AssetInfo.objectNodeId); + } + + // Iterate through all objects to determine visibility and + // gather output nodes that needs to be cooked. + int32 OutputIdx = 1; + for (int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ObjectIdx++) + { + // Retrieve the object info + const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectIdx]; + + // Determine whether this object node is fully visible. + bool bObjectIsVisible = false; + HAPI_NodeId GatherOutputsNodeId = -1; // Outputs will be gathered from this node. + if (!bAssetHasChildren) + { + // If the asset doesn't have children, we have to gather outputs from the asset's parent in order to output + // this asset node + bObjectIsVisible = true; + GatherOutputsNodeId = AssetNodeInfo.parentId; + } + else if (bIsSopAsset && CurrentHapiObjectInfo.nodeId == AssetInfo.objectNodeId) + { + // When dealing with a SOP asset, be sure to gather outputs from the SOP node, not the + // outer object node. + bObjectIsVisible = true; + GatherOutputsNodeId = AssetInfo.nodeId; + } + else + { + bObjectIsVisible = FHoudiniEngineUtils::IsObjNodeFullyVisible(AllObjectIds, AssetId, CurrentHapiObjectInfo.nodeId); + GatherOutputsNodeId = CurrentHapiObjectInfo.nodeId; + } + + // Build an array of the geos we'll need to process + // In most case, it will only be the display geo, + // but we may also want to process editable geos as well + TArray GeoInfos; + + // These node ids may need to be cooked in order to extract part counts. + TSet ForceNodesToCook; + + // Append the initial set of editable geo infos here + // then clear the editable geo infos array since we + // only want to process them once. + GeoInfos.Append(EditableGeoInfos); + EditableGeoInfos.Empty(); + + if (bObjectIsVisible) + { + // NOTE: The HAPI_GetDisplayGeoInfo will not always return the expected Geometry subnet's + // Display flag geometry. If the Geometry subnet contains an Object subnet somewhere, the + // GetDisplayGeoInfo will sometimes fetch the display SOP from within the subnet which is + // not what we want. + + // Resolve and gather outputs (display / output / template nodes) from the GatherOutputsNodeId. + FHoudiniEngineUtils::GatherImmediateOutputGeoInfos(GatherOutputsNodeId, + bUseOutputNodes, + bOutputTemplatedGeos, + GeoInfos, + ForceNodesToCook); + + } // if (bObjectIsVisible) + + for (const HAPI_NodeId& NodeId : ForceNodesToCook) + { + OutOutputNodes.AddUnique(NodeId); + } + } + return true; +} + +bool FHoudiniEngineUtils::GatherImmediateOutputGeoInfos(const HAPI_NodeId& InNodeId, + const bool bUseOutputNodes, + const bool bGatherTemplateNodes, + TArray& OutGeoInfos, + TSet& OutForceNodesCook +) +{ + TSet GatheredNodeIds; + + // NOTE: This function assumes that the incoming node is a Geometry container that contains immediate + // outputs / display nodes / template nodes. + + // First we look for (immediate) output nodes (if bUseOutputNodes have been enabled). + // If we didn't find an output node, we'll look for a display node. + + bool bHasOutputs = false; + if (bUseOutputNodes) + { + int NumOutputs = -1; + FHoudiniApi::GetOutputGeoCount( + FHoudiniEngine::Get().GetSession(), + InNodeId, + &NumOutputs + ); + + if (NumOutputs > 0) + { + bHasOutputs = true; + + // ------------------------------------------------- + // Extract GeoInfo from the immediate output nodes. + // ------------------------------------------------- + TArray OutputGeoInfos; + OutputGeoInfos.SetNumUninitialized(NumOutputs); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetOutputGeoInfos( + FHoudiniEngine::Get().GetSession(), + InNodeId, + OutputGeoInfos.GetData(), + NumOutputs)) + { + // Gather all the output nodes + for (const HAPI_GeoInfo& OutputGeoInfo : OutputGeoInfos) + { + OutGeoInfos.Add(OutputGeoInfo); + GatheredNodeIds.Add(OutputGeoInfo.nodeId); + OutForceNodesCook.Add(OutputGeoInfo.nodeId); // Ensure this output node gets cooked + } + } + } + } + + if (!bHasOutputs) + { + // If we didn't get any output data, pull our output data directly from the Display node. + + // --------------------------------- + // Look for display nodes. + // --------------------------------- + int DisplayNodeCount = 0; + if (HAPI_RESULT_SUCCESS == FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + InNodeId, + HAPI_NODETYPE_SOP, + HAPI_NODEFLAGS_DISPLAY, + false, + &DisplayNodeCount + )) + { + if (DisplayNodeCount > 0) + { + // Retrieve all the display node ids + TArray DisplayNodeIds; + DisplayNodeIds.SetNumUninitialized(DisplayNodeCount); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + InNodeId, + DisplayNodeIds.GetData(), + DisplayNodeCount + )) + { + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + // Retrieve the Geo Infos for each display node + for(const HAPI_NodeId& DisplayNodeId : DisplayNodeIds) + { + if (GatheredNodeIds.Contains(DisplayNodeId)) + continue; // This node has already been gathered from this subnet. + + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + DisplayNodeId, + &GeoInfo) + ) + { + OutGeoInfos.Add(GeoInfo); + GatheredNodeIds.Add(DisplayNodeId); + // If this node doesn't have a part_id count, ensure it gets cooked. + OutForceNodesCook.Add(DisplayNodeId); + } + } + } + } // if (DisplayNodeCount > 0) + } + } // if (!HasOutputNode) + + // Gather templated nodes. + if (bGatherTemplateNodes) + { + int NumTemplateNodes = 0; + // Gather all template nodes + if (HAPI_RESULT_SUCCESS == FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + InNodeId, + HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, + false, + &NumTemplateNodes + )) + { + TArray TemplateNodeIds; + TemplateNodeIds.SetNumUninitialized(NumTemplateNodes); + if (HAPI_RESULT_SUCCESS == FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + InNodeId, + TemplateNodeIds.GetData(), + NumTemplateNodes + )) + { + + for(const HAPI_NodeId& TemplateNodeId : TemplateNodeIds) + { + if (GatheredNodeIds.Contains(TemplateNodeId)) + { + continue; // This geometry has already been gathered. + } + + HAPI_GeoInfo GeoInfo; + FHoudiniApi::GeoInfo_Init(&GeoInfo); + FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + TemplateNodeId, + &GeoInfo + ); + // For some reason the return type is always "HAPI_RESULT_INVALID_ARGUMENT", so we + // just check the GeoInfo.type manually. + if (GeoInfo.type != HAPI_GEOTYPE_INVALID) + { + // Add this template node to the gathered outputs. + GatheredNodeIds.Add(TemplateNodeId); + OutGeoInfos.Add(GeoInfo); + // If this node doesn't have a part_id count, ensure it gets cooked. + OutForceNodesCook.Add(TemplateNodeId); + } + } + } + } + } + return true; +} + + bool FHoudiniEngineUtils::HapiGetAssetTransform(const HAPI_NodeId& InNodeId, FTransform& OutTransform) { @@ -2165,7 +2744,7 @@ FHoudiniEngineUtils::HapiGetParentNodeId(const HAPI_NodeId& NodeId) void FHoudiniEngineUtils::AssignUniqueActorLabelIfNeeded(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; // TODO: Necessary?? @@ -2363,7 +2942,7 @@ FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToU TArray AllSceneComponents; for (auto CurrentObject : ObjectsToUpdate) { - if (!CurrentObject || CurrentObject->IsPendingKill()) + if (!IsValid(CurrentObject)) continue; // In some case, the object itself is the component @@ -2373,7 +2952,7 @@ FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToU SceneComp = Cast(CurrentObject->GetOuter()); } - if (SceneComp && !SceneComp->IsPendingKill()) + if (IsValid(SceneComp)) { AllSceneComponents.Add(SceneComp); continue; @@ -2383,11 +2962,11 @@ FHoudiniEngineUtils::UpdateEditorProperties_Internal(TArray ObjectsToU TArray AllActors; for (auto CurrentSceneComp : AllSceneComponents) { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill()) + if (!IsValid(CurrentSceneComp)) continue; AActor* Actor = CurrentSceneComp->GetOwner(); - if (Actor && !Actor->IsPendingKill()) + if (IsValid(Actor)) AllActors.Add(Actor); } @@ -2562,10 +3141,10 @@ FHoudiniEngineUtils::SetAttributeStringData( char * FHoudiniEngineUtils::ExtractRawString(const FString& InString) { - if (InString.IsEmpty()) - return nullptr; - - std::string ConvertedString = TCHAR_TO_UTF8(*InString); + // Return an empty string instead of returning null to avoid potential crashes + std::string ConvertedString(""); + if (!InString.IsEmpty()) + ConvertedString = TCHAR_TO_UTF8(*InString); // Allocate space for unique string. int32 UniqueStringBytes = ConvertedString.size() + 1; @@ -2605,7 +3184,7 @@ FHoudiniEngineUtils::FreeRawStringMemory(TArray& InRawStringArray) bool FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // No need to add another component if we already show the logo @@ -2635,7 +3214,7 @@ FHoudiniEngineUtils::AddHoudiniLogoToComponent(UHoudiniAssetComponent* HAC) bool FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // Get the Houdini Logo SM @@ -2646,12 +3225,12 @@ FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) // Iterate on the HAC's component for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + if (!IsValid(CurrentSceneComp) || !CurrentSceneComp->IsA()) continue; // Get the static mesh component UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) + if (!IsValid(SMC)) continue; // Check if the SMC is the Houdini Logo @@ -2671,7 +3250,7 @@ FHoudiniEngineUtils::RemoveHoudiniLogoFromComponent(UHoudiniAssetComponent* HAC) bool FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // Get the Houdini Logo SM @@ -2682,12 +3261,12 @@ FHoudiniEngineUtils::HasHoudiniLogo(UHoudiniAssetComponent* HAC) // Iterate on the HAC's component for (USceneComponent* CurrentSceneComp : HAC->GetAttachChildren()) { - if (!CurrentSceneComp || CurrentSceneComp->IsPendingKill() || !CurrentSceneComp->IsA()) + if (!IsValid(CurrentSceneComp) || !CurrentSceneComp->IsA()) continue; // Get the static mesh component UStaticMeshComponent* SMC = Cast(CurrentSceneComp); - if (!SMC || SMC->IsPendingKill()) + if (!IsValid(SMC)) continue; // Check if the SMC is the Houdini Logo @@ -4119,7 +4698,7 @@ FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( TArray& AllSockets, const bool& CleanImportSockets) { - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return false; // Remove the sockets from the previous cook! @@ -4160,7 +4739,7 @@ FHoudiniEngineUtils::AddMeshSocketsToStaticMesh( { // Create a new Socket UStaticMeshSocket* Socket = NewObject(StaticMesh); - if (!Socket || Socket->IsPendingKill()) + if (!IsValid(Socket)) continue; Socket->RelativeLocation = AllSockets[nSocket].Transform.GetLocation(); @@ -4665,7 +5244,7 @@ bool FHoudiniEngineUtils::UpdateGenericPropertiesAttributes(UObject* InObject, const TArray& InAllPropertyAttributes) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; // Iterate over the found Property attributes @@ -4862,11 +5441,11 @@ void FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( UPackage * Package, UObject * Object, const FString& Key, const FString& Value) { - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return; UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) + if (IsValid(MetaData)) MetaData->SetValue(Object, *Key, *Value); } @@ -4881,7 +5460,7 @@ FHoudiniEngineUtils::AddLevelPathAttribute( if (InNodeId < 0 || InCount <= 0) return false; - if (!InLevel || InLevel->IsPendingKill()) + if (!IsValid(InLevel)) return false; // Extract the level path from the level @@ -4951,7 +5530,7 @@ FHoudiniEngineUtils::AddActorPathAttribute( if (InNodeId < 0 || InCount <= 0) return false; - if (!InActor || InActor->IsPendingKill()) + if (!IsValid(InActor)) return false; // Extract the actor path @@ -5260,6 +5839,32 @@ FHoudiniEngineUtils::GetOutputNameAttribute( return false; } +bool +FHoudiniEngineUtils::GetBakeNameAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeNames, + const int32& InStartIndex, + const int32& InCount) +{ + // --------------------------------------------- + // Attribute: unreal_bake_name + // --------------------------------------------- + HAPI_AttributeInfo AttributeInfo; + FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, HAPI_UNREAL_ATTRIB_BAKE_NAME, + AttributeInfo, OutBakeNames, 1, HAPI_ATTROWNER_INVALID, InStartIndex, InCount)) + { + if (OutBakeNames.Num() > 0) + return true; + } + + OutBakeNames.Empty(); + return false; +} + bool FHoudiniEngineUtils::GetTileAttribute( const HAPI_NodeId& InGeoId, @@ -5287,6 +5892,52 @@ FHoudiniEngineUtils::GetTileAttribute( return false; } +bool +FHoudiniEngineUtils::GetEditLayerName( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + FString& EditLayerName, + const HAPI_AttributeOwner& InAttribOwner) +{ + // --------------------------------------------- + // Attribute: tile + // --------------------------------------------- + HAPI_AttributeInfo AttribInfo; + FHoudiniApi::AttributeInfo_Init(&AttribInfo); + + TArray StrData; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME, + AttribInfo, + StrData, + 0, + InAttribOwner)) + { + if (StrData.Num() > 0) + { + EditLayerName = StrData[0]; + return true; + } + } + + EditLayerName = FString(); + return false; +} + +bool FHoudiniEngineUtils::HasEditLayerName(const HAPI_NodeId& InGeoId, const HAPI_PartId& InPartId, + const HAPI_AttributeOwner& InAttribOwner) +{ + // --------------------------------------------- + // Attribute: unreal_landscape_ + // --------------------------------------------- + + return FHoudiniEngineUtils::HapiCheckAttributeExists( + InGeoId, InPartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_NAME, + InAttribOwner); +} + bool FHoudiniEngineUtils::GetBakeFolderAttribute( const HAPI_NodeId& InGeoId, diff --git a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h index 2888f962f..3e0adde6c 100644 --- a/Source/HoudiniEngine/Private/HoudiniEngineUtils.h +++ b/Source/HoudiniEngine/Private/HoudiniEngineUtils.h @@ -73,6 +73,9 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // Return a specified HAPI status string. static const FString GetStatusString(HAPI_StatusType status_type, HAPI_StatusVerbosity verbosity); + // HAPI : Return the string that corresponds to the given string handle. + static FString HapiGetString(int32 StringHandle); + // Return a string representing cooking result. static const FString GetCookResult(); @@ -133,6 +136,44 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // HAPI : Retrieve HAPI_ObjectInfo's from given asset node id. static bool HapiGetObjectInfos(const HAPI_NodeId& InNodeId, TArray& OutObjectInfos, TArray& OutObjectTransforms); + // Traverse from the Child up to the Root node to determine whether the ChildNode is fully visible + // inside the RootNode. + // - The Obj node itself is visible + // - All parent nodes are visible + // - Only has Object subnet parents (if we find a parent with non-Object nodetype then it's not visible). + static bool IsObjNodeFullyVisible(const TSet& AllObjectIds, const HAPI_NodeId& RootNodeId, const HAPI_NodeId& ChildNodeId); + + static bool IsSopNode(const HAPI_NodeId& NodeId); + + static bool ContainsSopNodes(const HAPI_NodeId& NodeId); + + // Get the output index of InNodeId (assuming InNodeId is an Output node). + // This is done by getting the value of the outputidx parameter on + // InNodeId. + // Returns false if outputidx could not be found/read. Sets OutOutputIndex to the + // value of the outputidx parameter. + static bool GetOutputIndex(const HAPI_NodeId& InNodeId, int32& OutOutputIndex); + + static bool GatherAllAssetOutputs( + const HAPI_NodeId& InAssetId, + const bool bUseOutputNodes, + const bool bOutputTemplatedGeos, + TArray& OutOutputNodes); + + // Get the immediate output geo infos for the given Geometry object network. + // Find immediate Display and output nodes (if enabled). + // If bIgnoreOutputNodes is false, only Display nodes will be retrieved. + // If bIgnoreOutputNodes is true, any output nodes will take precedence over display nodes. + static bool GatherImmediateOutputGeoInfos( + const int& InNodeId, + const bool bUseOutputNodes, + const bool bGatherTemplateNodes, + TArray& OutGeoInfos, + TSet& OutForceNodesCook); + + // HAPI: Retrieve absolute path to the given Node + static bool HapiGetAbsNodePath(const HAPI_NodeId& InNodeId, FString& OutPath); + // HAPI: Retrieve Path to the given Node, relative to the given Node static bool HapiGetNodePath(const HAPI_NodeId& InNodeId, const HAPI_NodeId& InRelativeToNodeId, FString& OutPath); @@ -423,6 +464,14 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils const int32& InStart = 0, const int32& InCount = -1); + // Helper function to access the custom bake name attribute + static bool GetBakeNameAttribute( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + TArray& OutBakeName, + const int32& InStart = 0, + const int32& InCount = -1); + // Helper function to access the "tile" attribute static bool GetTileAttribute( const HAPI_NodeId& InGeoId, @@ -432,6 +481,17 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils const int32& InStart = 0, const int32& InCount = -1); + static bool GetEditLayerName( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + FString& EditLayerName, + const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); + + static bool HasEditLayerName( + const HAPI_NodeId& InGeoId, + const HAPI_PartId& InPartId, + const HAPI_AttributeOwner& InAttribOwner = HAPI_ATTROWNER_INVALID); + // Helper function to access the "unreal_bake_folder" attribute static bool GetBakeFolderAttribute( const HAPI_NodeId& InGeoId, @@ -636,6 +696,7 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils const FString &BakeFolder, const FString &ObjectName, const FString &HoudiniAssetName, + const FString &HoudiniAssetActorName, EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, bool bAutomaticallySetAttemptToLoadMissingPackages=true); @@ -643,17 +704,20 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // resolve the object name and unreal_bake_folder and setting these resolved values on the PackageParams. // If bAutomaticallySetAttemptToLoadMissingPackages is true, then // OutPackageParams.bAttemptToLoadMissingPackages is set to true in EPackageReplaceMode::CreateNewAssets mode. + // If InHoudiniAssetName or InHoudiniAssetActorName is blank, then the values are determined via + // HoudiniAssetComponent. static void FillInPackageParamsForBakingOutputWithResolver( UWorld* const InWorldContext, const UHoudiniAssetComponent* HoudiniAssetComponent, const FHoudiniOutputObjectIdentifier& InIdentifier, const FHoudiniOutputObject& InOutputObject, const FString &InDefaultObjectName, - const FString &InHoudiniAssetName, FHoudiniPackageParams& OutPackageParams, FHoudiniAttributeResolver& OutResolver, const FString &InDefaultBakeFolder=FString(), EPackageReplaceMode InReplaceMode=EPackageReplaceMode::ReplaceExistingAssets, + const FString& InHoudiniAssetName=TEXT(""), + const FString& InHoudiniAssetActorName=TEXT(""), bool bAutomaticallySetAttemptToLoadMissingPackages=true, bool bInSkipObjectNameResolutionAndUseDefault=false, bool bInSkipBakeFolderResolutionAndUseDefault=false); @@ -698,4 +762,4 @@ struct HOUDINIENGINE_API FHoudiniEngineUtils // Trigger an update of the Blueprint Editor on the game thread static void UpdateBlueprintEditor_Internal(UHoudiniAssetComponent* HAC); -}; \ No newline at end of file +}; diff --git a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp index d57c08e42..13c652bc8 100644 --- a/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp +++ b/Source/HoudiniEngine/Private/HoudiniGeoImporter.cpp @@ -235,7 +235,7 @@ UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObj for (auto CurOutputPair : NewOutputObjects) { UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) + if (!IsValid(CurObj)) continue; OutputObjects.Add(CurObj); @@ -245,7 +245,7 @@ UHoudiniGeoImporter::CreateStaticMeshes(TArray& InOutputs, UObj for (auto CurAssignmentMatPair : AssignementMaterials) { UObject* CurObj = CurAssignmentMatPair.Value; - if (!CurObj || CurObj->IsPendingKill()) + if (!IsValid(CurObj)) continue; OutputObjects.Add(CurObj); @@ -352,7 +352,7 @@ UHoudiniGeoImporter::CreateCurves(TArray& InOutputs, UObject* I for (auto CurOutputPair : CurOutput->GetOutputObjects()) { UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) + if (!IsValid(CurObj)) continue; OutputComp.Add(CurObj); @@ -449,7 +449,7 @@ UHoudiniGeoImporter::CreateLandscapes(TArray& InOutputs, UObjec for (auto CurOutputPair : CurOutput->GetOutputObjects()) { UObject* CurObj = CurOutputPair.Value.OutputObject; - if (!CurObj || CurObj->IsPendingKill()) + if (!IsValid(CurObj)) continue; OutputObjects.Add(CurObj); @@ -561,7 +561,7 @@ UHoudiniGeoImporter::CreateInstancers(TArray& InOutputs, UObjec for (auto CurOutputPair : CurOutput->GetOutputObjects()) { UActorComponent* CurObj = Cast(CurOutputPair.Value.OutputComponent); - if (!CurObj || CurObj->IsPendingKill()) + if (!IsValid(CurObj)) continue; OutputComp.Add(CurObj); @@ -900,7 +900,8 @@ UHoudiniGeoImporter::BuildAllOutputsForNode(const HAPI_NodeId& InNodeId, UObject { // TArray OldOutputs; TArray NodeIdsToCook; - if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, InOldOutputs, OutNewOutputs, NodeIdsToCook, false, true)) + TMap OutputNodeCookCount; + if (!FHoudiniOutputTranslator::BuildAllOutputs(InNodeId, InOuter, NodeIdsToCook, OutputNodeCookCount, InOldOutputs, OutNewOutputs, false, true)) { // Couldn't create the package HOUDINI_LOG_ERROR(TEXT("Houdini GEO Importer: Failed to process the File SOP's outputs!")); diff --git a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp index e5cafb264..aa1413b3c 100644 --- a/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniHandleTranslator.cpp @@ -42,7 +42,7 @@ bool FHoudiniHandleTranslator::UpdateHandles(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; TArray NewHandles; @@ -265,7 +265,7 @@ FHoudiniHandleTranslator::BuildAllHandles( void FHoudiniHandleTranslator::ClearHandles(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; for (auto& HandleComponent : HAC->HandleComponents) @@ -321,7 +321,7 @@ FHoudiniHandleTranslator::GetHapiXYZOrder(const TSharedPtr & StrPtr) void FHoudiniHandleTranslator::UpdateTransformParameters(UHoudiniHandleComponent* HandleComponent) { - if (!HandleComponent || HandleComponent->IsPendingKill()) + if (!IsValid(HandleComponent)) return; if (!HandleComponent->CheckHandleValid()) diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp index bf8778a4a..48045999e 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.cpp @@ -69,7 +69,7 @@ #endif #include "HCsgUtils.h" - +#include "LandscapeInfo.h" #include "Async/Async.h" #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE @@ -96,7 +96,7 @@ struct FHoudiniMoveTracker bool FHoudiniInputTranslator::UpdateInputs(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; if (!FHoudiniInputTranslator::BuildAllInputs(HAC->GetAssetId(), HAC, HAC->Inputs, HAC->Parameters)) @@ -144,6 +144,9 @@ FHoudiniInputTranslator::BuildAllInputs( TArray> InputParameters; for (auto Param : Parameters) { + if (!Param) + continue; + if (Param->GetParameterType() == EHoudiniParameterType::Input) { int InsertionIndex = InputParameters.Num(); @@ -167,7 +170,7 @@ FHoudiniInputTranslator::BuildAllInputs( FName(*InputObjectName), RF_Transactional); - if (!NewInput || NewInput->IsPendingKill()) + if (!IsValid(NewInput)) { //HOUDINI_LOG_WARNING("Failed to create asset input"); continue; @@ -184,7 +187,7 @@ FHoudiniInputTranslator::BuildAllInputs( for (int32 InputIdx = Inputs.Num() - 1; InputIdx >= InputCount; InputIdx--) { UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; FHoudiniInputTranslator::DisconnectAndDestroyInput(CurrentInput, CurrentInput->GetInputType()); @@ -218,7 +221,7 @@ FHoudiniInputTranslator::BuildAllInputs( else { UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (ParameterNameToIndexMap.Contains(CurrentInput->GetName())) @@ -260,7 +263,7 @@ FHoudiniInputTranslator::BuildAllInputs( for (int32 InputIdx = 0; InputIdx < Inputs.Num(); InputIdx++) { UHoudiniInput* CurrentInput = Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; // Create default Name/Label/Help @@ -301,7 +304,7 @@ FHoudiniInputTranslator::BuildAllInputs( } int32 ParmId = -1; - if (CurrentParm && !CurrentParm->IsPendingKill()) + if (IsValid(CurrentParm)) { ParmId = CurrentParm->GetParmId(); CurrentInputName = CurrentParm->GetParameterName(); @@ -310,7 +313,7 @@ FHoudiniInputTranslator::BuildAllInputs( } UHoudiniParameterOperatorPath* CurrentObjPathParm = Cast(CurrentParm); - if (CurrentObjPathParm && !CurrentObjPathParm->IsPendingKill()) + if (IsValid(CurrentObjPathParm)) { CurrentObjPathParm->HoudiniInput = CurrentInput; } @@ -367,7 +370,7 @@ FHoudiniInputTranslator::BuildAllInputs( bool FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) { - if (!InputToDestroy || InputToDestroy->IsPendingKill()) + if (!IsValid(InputToDestroy)) return false; // Start by disconnecting the input / nullifying the object path parameter @@ -414,7 +417,7 @@ FHoudiniInputTranslator::DisconnectInput(UHoudiniInput* InputToDestroy, const EH bool FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const EHoudiniInputType& InputType) { - if (!InputToDestroy || InputToDestroy->IsPendingKill()) + if (!IsValid(InputToDestroy)) return false; if (!InputToDestroy->CanDeleteHoudiniNodes()) @@ -432,7 +435,7 @@ FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const { for (auto CurInputObject : *InputObjectNodes) { - if (!CurInputObject || CurInputObject->IsPendingKill()) + if (!IsValid(CurInputObject)) continue; if (CurInputObject->Type == EHoudiniInputObjectType::HoudiniAssetComponent) @@ -453,7 +456,7 @@ FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const { for (auto & CurActorComponent : CurActorInputObject->GetActorComponents()) { - if (!CurActorComponent || CurActorComponent->IsPendingKill()) + if (!IsValid(CurActorComponent)) continue; // No need to delete the nodes created for an asset component manually here, @@ -489,7 +492,7 @@ FHoudiniInputTranslator::DestroyInputNodes(UHoudiniInput* InputToDestroy, const if (IsValid(HoudiniSplineInputObject) && !IsGarbageCollecting()) { UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (SplineComponent && !SplineComponent->IsPendingKill()) + if (IsValid(SplineComponent)) { SplineComponent->SetNodeId(-1); } @@ -585,7 +588,7 @@ FHoudiniInputTranslator::GetDefaultInputTypeFromLabel(const FString& InputName) bool FHoudiniInputTranslator::ChangeInputType(UHoudiniInput* InInput, const bool& bForce) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; if (!InInput->HasInputTypeChanged() && !bForce) @@ -613,7 +616,7 @@ bool FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOutBlueprintStructureModified) { // - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) return false; // Make sure we're linked to a valid object path parameter @@ -740,16 +743,43 @@ FHoudiniInputTranslator::SetDefaultAssetFromHDA(UHoudiniInput* Input, bool& bOut bool FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; //for (auto CurrentInput : HAC->Inputs) for(int32 InputIdx = 0; InputIdx < HAC->GetNumInputs(); InputIdx++) { UHoudiniInput*& CurrentInput = HAC->Inputs[InputIdx]; - if (!CurrentInput || CurrentInput->IsPendingKill() || !CurrentInput->HasChanged()) + if (!IsValid(CurrentInput) || !CurrentInput->HasChanged()) continue; + // Delete any previous InputNodeIds of this HoudiniInput that are pending delete + for (const HAPI_NodeId InputNodeIdPendingDelete : CurrentInput->GetInputNodesPendingDelete()) + { + if (InputNodeIdPendingDelete < 0) + continue; + + HAPI_NodeInfo NodeInfo; + FHoudiniApi::NodeInfo_Init(&NodeInfo); + + if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), InputNodeIdPendingDelete, &NodeInfo)) + { + continue; + } + + HAPI_NodeId NodeToDelete = InputNodeIdPendingDelete; + if (NodeInfo.type == HAPI_NODETYPE_SOP) + { + // Input nodes are Merge SOPs in a geo object, delete the geo object + const HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(InputNodeIdPendingDelete); + NodeToDelete = ParentId != -1 ? ParentId : InputNodeIdPendingDelete; + } + + HOUDINI_CHECK_ERROR(FHoudiniApi::DeleteNode(FHoudiniEngine::Get().GetSession(), NodeToDelete)); + } + CurrentInput->ClearInputNodesPendingDelete(); + // First thing, see if we need to change the input type if (CurrentInput->HasInputTypeChanged()) { @@ -766,7 +796,14 @@ FHoudiniInputTranslator::UploadChangedInputs(UHoudiniAssetComponent * HAC) bool bSuccess = true; if (CurrentInput->IsDataUploadNeeded()) { - bSuccess &= UploadInputData(CurrentInput); + FTransform OwnerTransform = FTransform::Identity; + AActor * OwnerActor = HAC->GetOwner(); + if (OwnerActor) + { + OwnerTransform = OwnerActor->GetTransform(); + } + + bSuccess &= UploadInputData(CurrentInput, OwnerTransform); CurrentInput->MarkDataUploadNeeded(!bSuccess); } @@ -809,7 +846,7 @@ FHoudiniInputTranslator::UpdateInputProperties(UHoudiniInput* InInput) bool FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; bool nTransformType = InInput->GetKeepWorldTransform(); @@ -824,7 +861,7 @@ FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) HAPI_NodeId HostAssetId = InInput->GetAssetNodeId(); bool bSuccess = true; - const std::string sXformType = "xformtype"; + const std::string sXformType = "xformtype"; if (InInput->IsObjectPathParameter()) { // Directly change the Parameter xformtype @@ -882,7 +919,7 @@ FHoudiniInputTranslator::UpdateTransformType(UHoudiniInput* InInput) bool FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; // Pack before merge is only available for Geo/World input @@ -935,7 +972,7 @@ FHoudiniInputTranslator::UpdatePackBeforeMerge(UHoudiniInput* InInput) bool FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; // Transform offsets are only for geometry inputs @@ -955,7 +992,7 @@ FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) { UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + if (!IsValid(CurrentInputObject)) continue; // If the Input mesh has a Transform offset @@ -977,9 +1014,9 @@ FHoudiniInputTranslator::UpdateTransformOffset(UHoudiniInput* InInput) } bool -FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) +FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput, const FTransform & InActorTransform) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; EHoudiniInputType InputType = InInput->GetInputType(); @@ -990,54 +1027,30 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) // Iterate on all the input objects and see if they need to be uploaded bool bSuccess = true; TArray CreatedNodeIds; + TArray ValidNodeIds; + TArray ChangedInputObjects; for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) { UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + if (!IsValid(CurrentInputObject)) continue; - int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; - if (!CurrentInputObject->HasChanged() && CurrentInputObjectNodeId >= 0) - { - // If this object hasn't changed, no need to upload it - // but we need to keep its created input node - if (CurrentInputObject->Type == EHoudiniInputObjectType::Actor) - { - // If this input object is an actor, it actually contains other input - // objects for each of his components, keep them as well - UHoudiniInputActor* InputActor = Cast(CurrentInputObject); - if (InputActor && !InputActor->IsPendingKill()) - { - for (auto CurrentComp : InputActor->GetActorComponents()) - { - if (!CurrentComp || CurrentComp->IsPendingKill()) - continue; + ValidNodeIds.Reset(); + ChangedInputObjects.Reset(); + // The input object could have child objects: GetChangedObjectsAndValidNodes finds if the object itself or + // any its children has changed, and also returns the NodeIds of those objects that are still valid and + // unchanged + CurrentInputObject->GetChangedObjectsAndValidNodes(ChangedInputObjects, ValidNodeIds); - int32& CurrentCompNodeId = CurrentComp->InputObjectNodeId; - if (!CurrentComp->HasChanged() && CurrentCompNodeId >= 0) - { - // If the component hasnt changed and is valid, keep it - CreatedNodeIds.Add(CurrentCompNodeId); - } - else - { - // Upload the component input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentComp, CreatedNodeIds)) - bSuccess = false; - } - } - } - } - else - { - // No changes, keep it - CreatedNodeIds.Add(CurrentInputObjectNodeId); - } - } - else + // Keep track of the node ids for unchanged objects that already exist + if (ValidNodeIds.Num() > 0) + CreatedNodeIds.Append(ValidNodeIds); + + // Upload the changed input objects + for (UHoudiniInputObject* ChangedInputObject : ChangedInputObjects) { // Upload the current input object to Houdini - if (!UploadHoudiniInputObject(InInput, CurrentInputObject, CreatedNodeIds)) + if (!UploadHoudiniInputObject(InInput, ChangedInputObject, InActorTransform, CreatedNodeIds)) bSuccess = false; } } @@ -1070,7 +1083,7 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) { if (InputNodeId >= 0) { - for (int32 Idx = 0; Idx < PreviousInputObjectNodeIds.Num(); Idx++) + for (int32 Idx = PreviousInputObjectNodeIds.Num() - 1; Idx >= 0; --Idx) { // Get the object merge connected to the merge node @@ -1114,7 +1127,7 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) { FTransform ComponentTransform = FTransform::Identity; USceneComponent* OuterComp = Cast(InInput->GetOuter()); - if (OuterComp && !OuterComp->IsPendingKill()) + if (IsValid(OuterComp)) ComponentTransform = OuterComp->GetComponentTransform(); FHoudiniEngineUtils::HapiSetAssetTransform(InputNodeId, ComponentTransform); @@ -1142,7 +1155,7 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) TArray& PreviousInputObjectNodeIds = InInput->GetCreatedDataNodeIds(); if (!InInput->HasInputTypeChanged()) { - for (int32 Idx = CreatedNodeIds.Num(); Idx < PreviousInputObjectNodeIds.Num(); Idx++) + for (int32 Idx = PreviousInputObjectNodeIds.Num() - 1; Idx >= CreatedNodeIds.Num(); --Idx) { // Get the object merge connected to the merge node HAPI_NodeId InputObjectMergeId = -1; @@ -1175,7 +1188,7 @@ FHoudiniInputTranslator::UploadInputData(UHoudiniInput* InInput) bool FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; EHoudiniInputType InputType = InInput->GetInputType(); @@ -1188,7 +1201,7 @@ FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) for (int32 ObjIdx = 0; ObjIdx < InputObjectsArray->Num(); ObjIdx++) { UHoudiniInputObject* CurrentInputObject = (*InputObjectsArray)[ObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + if (!IsValid(CurrentInputObject)) continue; int32& CurrentInputObjectNodeId = CurrentInputObject->InputObjectNodeId; @@ -1209,7 +1222,7 @@ FHoudiniInputTranslator::UploadInputTransform(UHoudiniInput* InInput) bool FHoudiniInputTranslator::ConnectInputNode(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; HAPI_NodeId AssetNodeId = InInput->GetAssetNodeId(); @@ -1248,6 +1261,7 @@ bool FHoudiniInputTranslator::UploadHoudiniInputObject( UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, + const FTransform& InActorTransform, TArray& OutCreatedNodeIds) { if (!InInput || !InInputObject) @@ -1273,7 +1287,7 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( UHoudiniInputStaticMesh* InputSM = Cast(InInputObject); bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( ObjBaseName, InputSM, InInput->GetExportLODs(), InInput->GetExportSockets(), - InInput->GetExportColliders(), InInput->GetImportAsReference()); + InInput->GetExportColliders(), InInput->GetImportAsReference(), InInput->GetImportAsReferenceRotScaleEnabled()); if (bSuccess) { @@ -1318,7 +1332,15 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( { UHoudiniInputMeshComponent* InputSMC = Cast(InInputObject); bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( - ObjBaseName, InputSMC, InInput->GetExportLODs(), InInput->GetExportSockets(), InInput->GetExportColliders(), InInput->GetImportAsReference()); + ObjBaseName, + InputSMC, + InInput->GetExportLODs(), + InInput->GetExportSockets(), + InInput->GetExportColliders(), + InInput->GetKeepWorldTransform(), + InInput->GetImportAsReference(), + InInput->GetImportAsReferenceRotScaleEnabled(), + InActorTransform); if (bSuccess) OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); @@ -1365,7 +1387,7 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( case EHoudiniInputObjectType::HoudiniAssetComponent: { UHoudiniInputHoudiniAsset* InputHAC = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetImportAsReference()); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniAssetComponent(ObjBaseName, InputHAC, InInput->GetKeepWorldTransform(), InInput->GetImportAsReference(), InInput->GetImportAsReferenceRotScaleEnabled()); if (bSuccess) OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); @@ -1376,7 +1398,8 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( case EHoudiniInputObjectType::Actor: { UHoudiniInputActor* InputActor = Cast(InInputObject); - bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, OutCreatedNodeIds); + bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForActor(InInput, InputActor, + InActorTransform, OutCreatedNodeIds); break; } @@ -1429,7 +1452,7 @@ FHoudiniInputTranslator::UploadHoudiniInputObject( UHoudiniInputFoliageType_InstancedStaticMesh* const InputFoliageTypeSM = Cast(InInputObject); bSuccess = FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( ObjBaseName, InputFoliageTypeSM, InInput->GetExportLODs(), InInput->GetExportSockets(), - InInput->GetExportColliders(), InInput->GetImportAsReference()); + InInput->GetExportColliders(), InInput->GetImportAsReference(), InInput->GetImportAsReferenceRotScaleEnabled()); if (bSuccess) OutCreatedNodeIds.Add(InInputObject->InputObjectNodeId); @@ -1492,17 +1515,22 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( break; } + case EHoudiniInputObjectType::StaticMeshComponent: + case EHoudiniInputObjectType::SplineComponent: { - // Update using the static mesh component's transform - UHoudiniInputMeshComponent* InSMC = Cast(InInputObject); - if (!InSMC || InSMC->IsPendingKill()) + // Default behaviour for components derived from SceneComponent. + + // Update using the component's transform + UHoudiniInputSceneComponent* InComponent = Cast(InInputObject); + if (!IsValid(InComponent)) { bSuccess = false; break; } - FTransform NewTransform = InSMC->GetStaticMeshComponent() ? InSMC->GetStaticMeshComponent()->GetComponentTransform() : InInputObject->Transform; + const USceneComponent* SceneComponent = InComponent->GetSceneComponent(); + const FTransform NewTransform = IsValid(SceneComponent) ? SceneComponent->GetComponentTransform() : InInputObject->Transform; if(!UpdateTransform(NewTransform, InInputObject->InputObjectNodeId)) bSuccess = false; @@ -1534,7 +1562,7 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( case EHoudiniInputObjectType::Actor: { UHoudiniInputActor* InputActor = Cast(InInputObject); - if (!InputActor || InputActor->IsPendingKill()) + if (!IsValid(InputActor)) { bSuccess = false; break; @@ -1549,7 +1577,7 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( // TODO? Also update the component's actor transform?? for (auto& CurrentComponent : InputActor->GetActorComponents()) { - if (!CurrentComponent || CurrentComponent->IsPendingKill()) + if (!IsValid(CurrentComponent)) continue; if (!CurrentComponent->HasTransformChanged()) @@ -1564,11 +1592,11 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( } break; } - + case EHoudiniInputObjectType::SceneComponent: { UHoudiniInputSceneComponent* InputSceneComp = Cast(InInputObject); - if (!InputSceneComp || InputSceneComp->IsPendingKill()) + if (!IsValid(InputSceneComp)) { bSuccess = false; break; @@ -1585,7 +1613,7 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( { // UHoudiniInputLandscape* InputLandscape = Cast(InInputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) + if (!IsValid(InputLandscape)) { bSuccess = false; break; @@ -1593,49 +1621,51 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( // ALandscapeProxy* Landscape = InputLandscape->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) - { - bSuccess = false; - break; - } - - // Only apply diff for landscape since the HF's transform is used for value conversion as well - FTransform CurrentTransform = InputLandscape->Transform; - FTransform NewTransform = Landscape->ActorToWorld(); - - // Only handle position/rotation differences - FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); - FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); - - // Now get the HF's current transform - HAPI_Transform HapiTransform; - FHoudiniApi::Transform_Init(&HapiTransform); - - if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( - FHoudiniEngine::Get().GetSession(), - InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) + if (!IsValid(Landscape)) { bSuccess = false; break; } - // Convert it to unreal - FTransform HFTransform; - FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); - - // Apply the position offset if needed - if (!PosDiff.IsZero()) - HFTransform.AddToTranslation(PosDiff); + // // Only apply diff for landscape since the HF's transform is used for value conversion as well + // FTransform CurrentTransform = InputLandscape->Transform; + const FTransform NewTransform = Landscape->ActorToWorld(); - // Apply the rotation offset if needed - if (!RotDiff.IsIdentity()) - HFTransform.ConcatenateRotation(RotDiff); + // // Only handle position/rotation differences + // FVector PosDiff = NewTransform.GetLocation() - CurrentTransform.GetLocation(); + // FQuat RotDiff = NewTransform.GetRotation() - CurrentTransform.GetRotation(); + // + // // Now get the HF's current transform + // HAPI_Transform HapiTransform; + // FHoudiniApi::Transform_Init(&HapiTransform); + // + // if ( HAPI_RESULT_SUCCESS != FHoudiniApi::GetObjectTransform( + // FHoudiniEngine::Get().GetSession(), + // InputLandscape->InputObjectNodeId, -1, HAPI_SRT, &HapiTransform)) + // { + // bSuccess = false; + // break; + // } + // + // // Convert it to unreal + // FTransform HFTransform; + // FHoudiniEngineUtils::TranslateHapiTransform(HapiTransform, HFTransform); + // + // // Apply the position offset if needed + // if (!PosDiff.IsZero()) + // HFTransform.AddToTranslation(PosDiff); + // + // // Apply the rotation offset if needed + // if (!RotDiff.IsIdentity()) + // HFTransform.ConcatenateRotation(RotDiff); // Convert back to a HAPI Transform and update the HF's transform HAPI_TransformEuler NewHAPITransform; FHoudiniApi::TransformEuler_Init(&NewHAPITransform); - FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); - NewHAPITransform.position[1] = 0.0f; + // FHoudiniEngineUtils::TranslateUnrealTransform(HFTransform, NewHAPITransform); + FHoudiniEngineUtils::TranslateUnrealTransform( + FTransform(NewTransform.GetRotation(), NewTransform.GetTranslation(), FVector::OneVector), NewHAPITransform); + // NewHAPITransform.position[1] = 0.0f; if (HAPI_RESULT_SUCCESS != FHoudiniApi::SetObjectTransform( FHoudiniEngine::Get().GetSession(), InputLandscape->InputObjectNodeId, &NewHAPITransform)) @@ -1666,7 +1696,6 @@ FHoudiniInputTranslator::UploadHoudiniInputTransform( // Unsupported case EHoudiniInputObjectType::Object: case EHoudiniInputObjectType::SkeletalMesh: - case EHoudiniInputObjectType::SplineComponent: { break; } @@ -1698,7 +1727,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForObject(const FString& InObjNodeNa return false; UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) + if (!IsValid(Object)) return true; FString NodeName = InObjNodeName + TEXT("_") + Object->GetName(); @@ -1792,9 +1821,10 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( const bool& bExportLODs, const bool& bExportSockets, const bool& bExportColliders, - const bool& bImportAsReference) + const bool& bImportAsReference, + const bool& bImportAsReferenceRotScaleEnabled) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; UBlueprint* BP = nullptr; @@ -1806,7 +1836,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( if (InObject->bIsBlueprint()) { BP = InObject->GetBlueprint(); - if (!BP || BP->IsPendingKill()) + if (!IsValid(BP)) return true; SMName += BP->GetName(); @@ -1814,7 +1844,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( else { SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) + if (!IsValid(SM)) return true; SMName += SM->GetName(); @@ -1847,7 +1877,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( AssetReference += FString("'"); bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, SMName, InObject->Transform); + InObject->InputNodeId, AssetReference, SMName, InObject->Transform, bImportAsReferenceRotScaleEnabled); } else { @@ -1857,24 +1887,24 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( if (BP) { USimpleConstructionScript* SCS = BP->SimpleConstructionScript; - if (SCS && !SCS->IsPendingKill()) + if (IsValid(SCS)) { const TArray& Nodes = SCS->GetAllNodes(); for (auto & CurNode : Nodes) { - if (!CurNode || CurNode->IsPendingKill()) + if (!IsValid(CurNode)) continue; UActorComponent * CurComp = CurNode->ComponentTemplate; - if (!CurComp || CurComp->IsPendingKill()) + if (!IsValid(CurComp)) continue; UStaticMeshComponent* CurSMC = Cast(CurComp); - if (!CurSMC || CurSMC->IsPendingKill()) + if (!IsValid(CurSMC)) continue; UStaticMesh* CurSM = CurSMC->GetStaticMesh(); - if (CurSM && !CurSM->IsPendingKill()) + if (IsValid(CurSM)) StaticMeshComponents.Add(CurSMC); } @@ -1889,13 +1919,13 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( { for (auto & CurSMC : StaticMeshComponents) { - if (!CurSMC || CurSMC->IsPendingKill()) + if (!IsValid(CurSMC)) continue; UHoudiniInputStaticMesh* SMObject = Cast( UHoudiniInputObject::CreateTypedInputObject(CurSMC->GetStaticMesh(), InObject, InObject->GetName() + TEXT("_") + CurSMC->GetName())); - if (!SMObject || SMObject->IsPendingKill()) + if (!IsValid(SMObject)) continue; bSuccess &= FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( @@ -1958,11 +1988,11 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMesh( bool FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObjNodeName, UHoudiniInputSkeletalMesh* InObject) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; USkeletalMesh* SkelMesh = InObject->GetSkeletalMesh(); - if (!SkelMesh || SkelMesh->IsPendingKill()) + if (!IsValid(SkelMesh)) return true; // Get the SM's transform offset @@ -1978,11 +2008,11 @@ FHoudiniInputTranslator::HapiCreateInputNodeForSkeletalMesh(const FString& InObj bool FHoudiniInputTranslator::HapiCreateInputNodeForSceneComponent(const FString& InObjNodeName, UHoudiniInputSceneComponent* InObject) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; USceneComponent* SceneComp = InObject->GetSceneComponent(); - if (!SceneComp || SceneComp->IsPendingKill()) + if (!IsValid(SceneComp)) return true; // Get the Scene Component's transform @@ -2006,18 +2036,21 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( const bool& bExportLODs, const bool& bExportSockets, const bool& bExportColliders, - const bool& bImportAsReference) + const bool& bKeepWorldTransform, + const bool& bImportAsReference, + const bool& bImportAsReferenceRotScaleEnabled, + const FTransform& InActorTransform) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; UStaticMeshComponent* SMC = InObject->GetStaticMeshComponent(); - if (!SMC || SMC->IsPendingKill()) + if (!IsValid(SMC)) return true; // Get the component's Static Mesh - UStaticMesh* SM = InObject->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) + UStaticMesh* SM = SMC->GetStaticMesh(); + if (!IsValid(SM)) return true; // Marshall the Static Mesh to Houdini @@ -2042,7 +2075,18 @@ FHoudiniInputTranslator::HapiCreateInputNodeForStaticMeshComponent( // Attach another '\'' to the end AssetReference += FString("'"); - bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, AssetReference, SMCName, InObject->Transform); + FTransform ImportAsReferenceTransform = InObject->Transform; + + if (!bKeepWorldTransform) + { + ImportAsReferenceTransform.SetLocation(FVector::ZeroVector); + } + else + { + ImportAsReferenceTransform *= InActorTransform.Inverse(); + } + + bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InObject->InputNodeId, AssetReference, SMCName, ImportAsReferenceTransform, bImportAsReferenceRotScaleEnabled); } else @@ -2084,16 +2128,16 @@ FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( const bool& bExportSockets, const bool& bExportColliders) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; UObject* Object = InObject->GetObject(); - if (!Object || Object->IsPendingKill()) + if (!IsValid(Object)) return true; // Get the ISMC UInstancedStaticMeshComponent* ISMC = InObject->GetInstancedStaticMeshComponent(); - if (!ISMC || ISMC->IsPendingKill()) + if (!IsValid(ISMC)) return true; HAPI_NodeId NewNodeId = -1; @@ -2114,11 +2158,11 @@ FHoudiniInputTranslator::HapiCreateInputNodeForInstancedStaticMeshComponent( bool FHoudiniInputTranslator::HapiCreateInputNodeForSplineComponent(const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; USplineComponent* Spline = InObject->GetSplineComponent(); - if (!Spline || Spline->IsPendingKill()) + if (!IsValid(Spline)) return true; @@ -2164,11 +2208,11 @@ bool FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( const FString& InObjNodeName, UHoudiniInputHoudiniSplineComponent* InObject, bool bInAddRotAndScaleAttributes) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; UHoudiniSplineComponent* Curve = InObject->GetCurveComponent(); - if (!Curve || Curve->IsPendingKill()) + if (!IsValid(Curve)) return true; if (!FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent(Curve, bInAddRotAndScaleAttributes)) @@ -2194,24 +2238,24 @@ FHoudiniInputTranslator::HapiCreateInputNodeForHoudiniSplineComponent( bool FHoudiniInputTranslator:: -HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference) +HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool bKeepWorldTransform, const bool& bImportAsReference, const bool& bImportAsReferenceRotScaleEnabled) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; UHoudiniAssetComponent* InputHAC = InObject->GetHoudiniAssetComponent(); - if (!InputHAC || InputHAC->IsPendingKill()) + if (!IsValid(InputHAC)) return true; if (!InputHAC->CanDeleteHoudiniNodes()) return true; UHoudiniInput* HoudiniInput = Cast(InObject->GetOuter()); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) + if (!IsValid(HoudiniInput)) return true; UHoudiniAssetComponent* OuterHAC = Cast(HoudiniInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return true; // Do not allow using ourself as an input, terrible things would happen @@ -2269,7 +2313,7 @@ HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudi AssetReference += FString("'"); if (!FHoudiniInputTranslator::CreateInputNodeForReference( - InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform)) // do not delete previous node if it was HAC + InObject->InputNodeId, AssetReference, InObject->GetName(), InObject->Transform, bImportAsReferenceRotScaleEnabled)) // do not delete previous node if it was HAC return false; if (bIsAssetInput) @@ -2316,25 +2360,25 @@ HapiCreateInputNodeForHoudiniAssetComponent(const FString& InObjNodeName, UHoudi bool FHoudiniInputTranslator::HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds) + UHoudiniInput* InInput, UHoudiniInputActor* InObject, const FTransform & InActorTransform, TArray& OutCreatedNodeIds) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; AActor* Actor = InObject->GetActor(); - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) return true; // Check if this is a world input and if this is a HoudiniAssetActor // If so we need to build static meshes for any proxy meshes if (InInput->GetInputType() == EHoudiniInputType::World && Actor->IsA()) - { +{ AHoudiniAssetActor *HAA = Cast(Actor); UHoudiniAssetComponent *HAC = HAA->GetHoudiniAssetComponent(); - if (HAC && !HAC->IsPendingKill()) + if (IsValid(HAC)) { if (HAC->HasAnyCurrentProxyOutput()) { @@ -2347,7 +2391,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForActor( FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HAC); // Update the input object since a new StaticMeshComponent could have been created UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) + if (IsValid(InputObject)) { InObject->Update(InputObject); TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); @@ -2367,7 +2411,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForActor( // proxies and the input was created when there were only proxies // Try to update the input to find new components UObject *InputObject = InObject->GetObject(); - if (InputObject && !InputObject->IsPendingKill()) + if (IsValid(InputObject)) { InObject->Update(InputObject); TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); @@ -2380,7 +2424,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForActor( int32 ComponentIdx = 0; for (UHoudiniInputSceneComponent* CurComponent : InObject->GetActorComponents()) { - if(UploadHoudiniInputObject(InInput, CurComponent, OutCreatedNodeIds)) + if(UploadHoudiniInputObject(InInput, CurComponent, InActorTransform, OutCreatedNodeIds)) ComponentIdx++; } @@ -2409,24 +2453,46 @@ bool FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( const FString& InObjNodeName, UHoudiniInputLandscape* InObject, UHoudiniInput* InInput) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; ALandscapeProxy* Landscape = InObject->GetLandscapeProxy(); - if (!Landscape || Landscape->IsPendingKill()) + if (!IsValid(Landscape)) return true; EHoudiniLandscapeExportType ExportType = InInput->GetLandscapeExportType(); + // Get selected components if bLandscapeExportSelectionOnly or bLandscapeAutoSelectComponent is true + bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; + bool bLandscapeAutoSelectComponent = InInput->bLandscapeAutoSelectComponent; + + TSet< ULandscapeComponent * > SelectedComponents = InInput->GetLandscapeSelectedComponents(); + if (bExportSelectionOnly && SelectedComponents.Num() == 0) + { + InInput->UpdateLandscapeInputSelection(); + SelectedComponents = InInput->GetLandscapeSelectedComponents(); + } + bool bSucess = false; if (ExportType == EHoudiniLandscapeExportType::Heightfield) { // Ensure we destroy any (Houdini) input nodes before clobbering this object with a new heightfield. //DestroyInputNodes(InInput, InInput->GetInputType()); - bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); + + int32 NumComponents = Landscape->LandscapeComponents.Num(); + if ( !bExportSelectionOnly || ( SelectedComponents.Num() == NumComponents ) ) + { + // Export the whole landscape and its layer as a single heightfield node + bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape(Landscape, InObject->InputNodeId, InObjNodeName); + } + else + { + // Each selected landscape component will be exported as separate volumes in a single heightfield + bSucess = FUnrealLandscapeTranslator::CreateHeightfieldFromLandscapeComponentArray( Landscape, SelectedComponents, InObject->InputNodeId, InObjNodeName ); + } } else { @@ -2434,7 +2500,6 @@ FHoudiniInputTranslator::HapiCreateInputNodeForLandscape( bool bExportMaterials = InInput->bLandscapeExportMaterials; bool bExportNormalizedUVs = InInput->bLandscapeExportNormalizedUVs; bool bExportTileUVs = InInput->bLandscapeExportTileUVs; - bool bExportSelectionOnly = InInput->bLandscapeExportSelectionOnly; bool bExportAsMesh = InInput->LandscapeExportType == EHoudiniLandscapeExportType::Mesh; bSucess = FUnrealLandscapeTranslator::CreateMeshOrPointsFromLandscape( @@ -2473,11 +2538,11 @@ FHoudiniInputTranslator::HapiCreateInputNodeForBrush(const FString& InObjNodeNam bool FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, UHoudiniInputCameraComponent* InInputObject) { - if (!InInputObject || InInputObject->IsPendingKill()) + if (!IsValid(InInputObject)) return false; UCameraComponent* Camera = InInputObject->GetCameraComponent(); - if (!Camera || Camera->IsPendingKill()) + if (!IsValid(Camera)) return true; FString NodeName = InNodeName + TEXT("_") + Camera->GetName(); @@ -2545,7 +2610,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForCamera(const FString& InNodeName, bool FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // We need to call BuildAllInputs here to update all the inputs, @@ -2558,7 +2623,7 @@ FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) int32 HACAssetId = HAC->GetAssetId(); for (auto CurrentInput : HAC->Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; // @@ -2578,7 +2643,7 @@ FHoudiniInputTranslator::UpdateLoadedInputs(UHoudiniAssetComponent* HAC) bool FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // Only tick/cook when in Editor @@ -2615,7 +2680,7 @@ FHoudiniInputTranslator::UpdateWorldInputs(UHoudiniAssetComponent* HAC) bool FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return false; if (InInput->GetInputType() != EHoudiniInputType::World) @@ -2639,12 +2704,12 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) for(int32 InputObjIdx = 0; InputObjIdx < InputObjectsPtr->Num(); InputObjIdx++) { UHoudiniInputActor* ActorObject = Cast((*InputObjectsPtr)[InputObjIdx]); - if (!ActorObject || ActorObject->IsPendingKill()) + if (!IsValid(ActorObject)) continue; // Make sure the actor is still valid AActor* const Actor = ActorObject->GetActor(); - bool bValidActorObject = Actor && !Actor->IsPendingKill(); + bool bValidActorObject = IsValid(Actor); // For BrushActors, the brush and actors must be valid as well UHoudiniInputBrush* BrushActorObject = Cast(ActorObject); @@ -2672,10 +2737,15 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) continue; } + // We'll keep track of whether the actor transform changed so that + // we can mark all the components as having changed transforms -- everything + // needs to be updated. + bool bActorTransformChanged = false; if (ActorObject->HasActorTransformChanged()) { ActorObject->MarkTransformChanged(true); bHasChanged = true; + bActorTransformChanged = true; } if (ActorObject->HasContentChanged()) @@ -2690,7 +2760,7 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) // Check if any components have content or transform changes for (auto CurActorComp : ActorObject->GetActorComponents()) { - if (CurActorComp->HasComponentTransformChanged()) + if (bActorTransformChanged || CurActorComp->HasComponentTransformChanged()) { CurActorComp->MarkTransformChanged(true); bHasChanged = true; @@ -2701,6 +2771,12 @@ FHoudiniInputTranslator::UpdateWorldInput(UHoudiniInput* InInput) CurActorComp->MarkChanged(true); bHasChanged = true; } + + USceneComponent* Component = CurActorComp->GetSceneComponent(); + if (IsValid(Component)) + { + CurActorComp->Update(Component); + } } // Check if we added/removed any components in the call to update @@ -2729,7 +2805,8 @@ FHoudiniInputTranslator::CreateInputNodeForReference( HAPI_NodeId& InputNodeId, const FString& InRef, const FString& InputNodeName, - const FTransform& InTransform) + const FTransform& InTransform, + const bool& bImportAsReferenceRotScaleEnabled) { HAPI_NodeId NewNodeId = -1; @@ -2799,9 +2876,9 @@ FHoudiniInputTranslator::CreateInputNodeForReference( FVector ObjectPosition = InTransform.GetLocation(); TArray Position = { - ObjectPosition.X * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Z * HAPI_UNREAL_SCALE_FACTOR_POSITION, - ObjectPosition.Y * HAPI_UNREAL_SCALE_FACTOR_POSITION + ObjectPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION, + ObjectPosition.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION }; // Now that we have raw positions, we can upload them for our attribute. @@ -2812,6 +2889,78 @@ FHoudiniInputTranslator::CreateInputNodeForReference( AttributeInfoPoint.count), false); } + if (bImportAsReferenceRotScaleEnabled) + { + // Create ROTATION attribute info + HAPI_AttributeInfo AttributeInfoRotation; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoRotation); + AttributeInfoRotation.count = 1; + AttributeInfoRotation.tupleSize = 4; + AttributeInfoRotation.exists = true; + AttributeInfoRotation.owner = HAPI_ATTROWNER_POINT; + AttributeInfoRotation.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoRotation.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation), false); + + TArray< float > InputRotations; + InputRotations.SetNumZeroed(4); + + FQuat InputRotation = InTransform.GetRotation(); + + InputRotations[0] = InputRotation.X; + InputRotations[1] = InputRotation.Z; + InputRotations[2] = InputRotation.Y; + InputRotations[3] = -InputRotation.W; + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, + HAPI_UNREAL_ATTRIB_ROTATION, + &AttributeInfoRotation, + InputRotations.GetData(), + 0, AttributeInfoRotation.count), false); + + // Create SCALE attribute info + HAPI_AttributeInfo AttributeInfoScale; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoScale); + AttributeInfoScale.count = 1; + AttributeInfoScale.tupleSize = 3; + AttributeInfoScale.exists = true; + AttributeInfoScale.owner = HAPI_ATTROWNER_POINT; + AttributeInfoScale.storage = HAPI_STORAGETYPE_FLOAT; + AttributeInfoScale.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale), false); + + TArray< float > InputScales; + InputScales.SetNumZeroed(3); + + FVector InputScale = InTransform.GetScale3D(); + InputScales[0] = InputScale.X; + InputScales[1] = InputScale.Z; + InputScales[2] = InputScale.Y; + + //we can now upload them to our attribute. + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::SetAttributeFloatData( + FHoudiniEngine::Get().GetSession(), + NewNodeId, 0, + HAPI_UNREAL_ATTRIB_SCALE, + &AttributeInfoScale, + InputScales.GetData(), + 0, AttributeInfoScale.count), false); + + } + // String Attribute { // Create point attribute info. @@ -2854,11 +3003,11 @@ bool FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeName, UHoudiniInputDataTable* InInputObject) { //TODO - if (!InInputObject || InInputObject->IsPendingKill()) + if (!IsValid(InInputObject)) return false; UDataTable* DataTable = InInputObject->GetDataTable(); - if (!DataTable || DataTable->IsPendingKill()) + if (!IsValid(DataTable)) return true; // Get the DataTable data as string @@ -2990,7 +3139,14 @@ FHoudiniInputTranslator::HapiCreateInputNodeForDataTable(const FString& InNodeNa for (int32 ColIdx = 0; ColIdx < NumAttributes; ColIdx++) { // attribute name is "unreal_data_table_COL_NAME" - FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + TableData[0][ColIdx]; + + FString DataName = TableData[0][ColIdx]; + + // Validate struct variable names + DataName = DataName.Replace(TEXT(" "), TEXT("_")); + DataName = DataName.Replace(TEXT(":"), TEXT("_")); + + FString CurAttrName = TEXT(HAPI_UNREAL_ATTRIB_DATA_TABLE_PREFIX) + FString::FromInt(ColIdx) + TEXT("_") + DataName; // We need to gt all values for that attribute TArray AttributeValues; @@ -3038,7 +3194,8 @@ FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( const bool& bExportLODs, const bool& bExportSockets, const bool& bExportColliders, - const bool& bImportAsReference) + const bool& bImportAsReference, + const bool& bImportAsReferenceRotScaleEnabled) { if (!IsValid(InObject)) return false; @@ -3078,7 +3235,7 @@ FHoudiniInputTranslator::HapiCreateInputNodeForFoliageType_InstancedStaticMesh( AssetReference += FString("'"); bSuccess = FUnrealFoliageTypeTranslator::CreateInputNodeForReference( - FoliageType, InObject->InputNodeId, AssetReference, FTName, InObject->Transform); + FoliageType, InObject->InputNodeId, AssetReference, FTName, InObject->Transform, InObject->GetImportAsReferenceRotScaleEnabled()); } else { diff --git a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h index 553a5bb8c..888adbffb 100644 --- a/Source/HoudiniEngine/Private/HoudiniInputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInputTranslator.h @@ -96,14 +96,14 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator static bool UpdateTransformOffset(UHoudiniInput* InInput); // Upload all the input's data to Houdini - static bool UploadInputData(UHoudiniInput* InInput); + static bool UploadInputData(UHoudiniInput* InInput, const FTransform & InActorTransform = FTransform::Identity); // Upload all the input's transforms to Houdini static bool UploadInputTransform(UHoudiniInput* InInput); // Upload data for an input's InputObject static bool UploadHoudiniInputObject( - UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, TArray& OutCreatedNodeIds); + UHoudiniInput* InInput, UHoudiniInputObject* InInputObject, const FTransform& InActorTransform, TArray& OutCreatedNodeIds); // Upload transform for an input's InputObject static bool UploadHoudiniInputTransform( @@ -138,7 +138,8 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator const bool& bExportLODs, const bool& bExportSockets, const bool& bExportColliders, - const bool& bImportAsReference = false); + const bool& bImportAsReference = false, + const bool& bImportAsReferenceRotScaleEnabled = false); static bool HapiCreateInputNodeForHoudiniSplineComponent( const FString& InObjNodeName, @@ -162,7 +163,10 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator const bool& bExportLODs, const bool& bExportSockets, const bool& bExportColliders, - const bool& bImportAsReference); + const bool& bKeepWorldTransform, + const bool& bImportAsReference, + const bool& bImportAsReferenceRotScaleEnabled = false, + const FTransform& InActorTransform = FTransform::Identity); static bool HapiCreateInputNodeForInstancedStaticMeshComponent( const FString& InObjNodeName, @@ -175,10 +179,10 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator const FString& InObjNodeName, UHoudiniInputSplineComponent* InObject, const float& SplineResolution); static bool HapiCreateInputNodeForHoudiniAssetComponent( - const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool& bImportAsReference); + const FString& InObjNodeName, UHoudiniInputHoudiniAsset* InObject, const bool bKeepWorldTransform, const bool& bImportAsReference, const bool& bImportAsReferenceRotScaleEnabled); static bool HapiCreateInputNodeForActor( - UHoudiniInput* InInput, UHoudiniInputActor* InObject, TArray& OutCreatedNodeIds); + UHoudiniInput* InInput, UHoudiniInputActor* InObject, const FTransform & InActorTransform, TArray& OutCreatedNodeIds); static bool HapiCreateInputNodeForCamera( const FString& InObjNodeName, UHoudiniInputCameraComponent* InObject); @@ -201,14 +205,16 @@ struct HOUDINIENGINE_API FHoudiniInputTranslator const bool& bExportLODs, const bool& bExportSockets, const bool& bExportColliders, - const bool& bImportAsReference = false); + const bool& bImportAsReference = false, + const bool& bImportAsReferenceRotScaleEnabled = false); // HAPI: Create an input node for reference static bool CreateInputNodeForReference( HAPI_NodeId& InputNodeId, const FString & InRef, const FString & InputNodeName, - const FTransform & InTransform); + const FTransform & InTransform, + const bool& bImportAsReferenceRotScaleEnabled); //static bool HapiUpdateInputNodeTransform(const HAPI_NodeId InputNodeId, const FTransform& Transform); diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp index 3ec7df54e..bcc8ba6aa 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.cpp @@ -71,6 +71,9 @@ FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( // Get if force to use HISM from attribute OutInstancedOutputPartData.bForceHISM = HasHISMAttribute(InHGPO.GeoId, InHGPO.PartId); + // Should we create an instancer even for single instances? + OutInstancedOutputPartData.bForceInstancer = HasForceInstancerAttribute(InHGPO.GeoId, InHGPO.PartId); + // Extract the object and transforms for this instancer if (!GetInstancerObjectsAndTransforms( InHGPO, @@ -108,6 +111,13 @@ FHoudiniInstanceTranslator::PopulateInstancedOutputPartData( OutInstancedOutputPartData.OutputNames.Empty(); } + // Get the bake name attribute + if (!FHoudiniEngineUtils::GetBakeNameAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.BakeNames)) + { + // No attribute specified + OutInstancedOutputPartData.BakeNames.Empty(); + } + // See if we have a tile attribute if (!FHoudiniEngineUtils::GetTileAttribute(InHGPO.GeoId, InHGPO.PartId, OutInstancedOutputPartData.TileValues)) { @@ -152,10 +162,10 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( const TMap* InPreBuiltInstancedOutputPartData ) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; - if (!InOuterComponent || InOuterComponent->IsPendingKill()) + if (!IsValid(InOuterComponent)) return false; // Keep track of the previous cook's component to clean them up after @@ -280,7 +290,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < VariationInstancedObjects.Num(); InstanceObjectIdx++) { UObject* InstancedObject = VariationInstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) + if (!IsValid(InstancedObject)) continue; if (!VariationInstancedTransforms.IsValidIndex(InstanceObjectIdx)) @@ -345,7 +355,8 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( VariationMaterials, InstancedOutputPartData.OriginalInstancedIndices[VariationOriginalIndex], InstanceObjectIdx, - InstancedOutputPartData.bForceHISM)) + InstancedOutputPartData.bForceHISM, + InstancedOutputPartData.bForceInstancer)) { // TODO?? continue; @@ -395,6 +406,9 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if(InstancedOutputPartData.OutputNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.OutputNames[FirstOriginalInstanceIndex].IsEmpty()) NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), InstancedOutputPartData.OutputNames[FirstOriginalInstanceIndex]); + if(InstancedOutputPartData.BakeNames.IsValidIndex(FirstOriginalInstanceIndex) && !InstancedOutputPartData.BakeNames[FirstOriginalInstanceIndex].IsEmpty()) + NewOutputObject.CachedAttributes.Add(FString(HAPI_UNREAL_ATTRIB_BAKE_NAME), InstancedOutputPartData.BakeNames[FirstOriginalInstanceIndex]); + // TODO: Check! maybe accessed with just VariationOriginalIndex if(InstancedOutputPartData.TileValues.IsValidIndex(FirstOriginalInstanceIndex) && InstancedOutputPartData.TileValues[FirstOriginalInstanceIndex] >= 0) { @@ -458,7 +472,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (NewComponent) { UObject* FoundOldComponent = FoundOldOutputObject->OutputComponent; - if (FoundOldComponent && !FoundOldComponent->IsPendingKill()) + if (IsValid(FoundOldComponent)) { bKeep = (FoundOldComponent == NewComponent); } @@ -468,7 +482,7 @@ FHoudiniInstanceTranslator::CreateAllInstancersFromHoudiniOutput( if (NewProxyComponent) { UObject* FoundOldProxyComponent = FoundOldOutputObject->ProxyComponent; - if (FoundOldProxyComponent && !FoundOldProxyComponent->IsPendingKill()) + if (IsValid(FoundOldProxyComponent)) { bKeep = (FoundOldProxyComponent == NewProxyComponent); } @@ -544,7 +558,10 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( OutputIdentifier.PartName = InOutputIdentifier.PartName; // Get if force using HISM from attribute - bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); + const bool bForceHISM = HasHISMAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); + + // Should we create an instancer even for single instances? + const bool bForceInstancer = HasForceInstancerAttribute(InOutputIdentifier.GeoId, InOutputIdentifier.PartId); TArray OriginalInstancedObjects; OriginalInstancedObjects.Add(InInstancedOutput.OriginalObject.LoadSynchronous()); @@ -618,7 +635,7 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( for (int32 InstanceObjectIdx = 0; InstanceObjectIdx < InstancedObjects.Num(); InstanceObjectIdx++) { UObject* InstancedObject = InstancedObjects[InstanceObjectIdx].LoadSynchronous(); - if (!InstancedObject || InstancedObject->IsPendingKill()) + if (!IsValid(InstancedObject)) continue; if (!InstancedTransforms.IsValidIndex(InstanceObjectIdx)) @@ -664,7 +681,8 @@ FHoudiniInstanceTranslator::UpdateChangedInstancedOutput( InstancerMaterials, OriginalInstanceIndices[0], InstanceObjectIdx, - bForceHISM)) + bForceHISM, + bForceInstancer)) { // TODO?? continue; @@ -819,11 +837,11 @@ FHoudiniInstanceTranslator::GetInstancerObjectsAndTransforms( // In the case of a single-instance we can use the proxy (if it is current) // FHoudiniOutputTranslator::UpdateOutputs doesn't allow proxies if there is more than one instance in an output if (InstancedHGPOTransforms[HGPOIdx].Num() <= 1 && CurrentOutputObject.bProxyIsCurrent - && CurrentOutputObject.ProxyObject && !CurrentOutputObject.ProxyObject->IsPendingKill()) + && IsValid(CurrentOutputObject.ProxyObject)) { ObjectsToInstance.Add(CurrentOutputObject.ProxyObject); } - else if (CurrentOutputObject.OutputObject && !CurrentOutputObject.OutputObject->IsPendingKill()) + else if (IsValid(CurrentOutputObject.OutputObject)) { ObjectsToInstance.Add(CurrentOutputObject.OutputObject); } @@ -872,7 +890,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( for (int32 InstObjIdx = 0; InstObjIdx < InOriginalObjects.Num(); InstObjIdx++) { UObject* OriginalObj = InOriginalObjects[InstObjIdx]; - if (!OriginalObj || OriginalObj->IsPendingKill()) + if (!IsValid(OriginalObj)) continue; // Build this output object's split identifier @@ -945,7 +963,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( for (int32 VarIdx = CurInstancedOutput.VariationObjects.Num() - 1; VarIdx >= 0; --VarIdx) { UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill() || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) + if (!IsValid(CurrentVariationObject) || (ReplacedOriginalObject && ReplacedOriginalObject == CurrentVariationObject)) { ObjsToRemove.Add(VarIdx); } @@ -979,7 +997,7 @@ FHoudiniInstanceTranslator::UpdateInstanceVariationObjects( for (int32 VarIdx = 0; VarIdx < CurInstancedOutput.VariationObjects.Num(); VarIdx++) { UObject* CurrentVariationObject = CurInstancedOutput.VariationObjects[VarIdx].LoadSynchronous(); - if (!CurrentVariationObject || CurrentVariationObject->IsPendingKill()) + if (!IsValid(CurrentVariationObject)) continue; // Get the transforms assigned to that variation @@ -1403,7 +1421,7 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // Couldn't load the referenced object, use the default reference mesh UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (!DefaultReferenceSM || DefaultReferenceSM->IsPendingKill()) + if (!IsValid(DefaultReferenceSM)) { HOUDINI_LOG_WARNING(TEXT("Failed to load the default instance mesh.")); return false; @@ -1462,8 +1480,10 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( { // To avoid trying to load an object that fails multiple times, // still add it to the array if null so we can still skip further attempts - UObject * AttributeObject = StaticLoadObject( - UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); + UObject* AttributeObject = StaticFindObjectSafe(UObject::StaticClass(), nullptr, *Iter); + if (IsValid(AttributeObject)) + AttributeObject = StaticLoadObject( + UObject::StaticClass(), nullptr, *Iter, nullptr, LOAD_None, nullptr); if (!AttributeObject) { @@ -1494,7 +1514,7 @@ FHoudiniInstanceTranslator::GetAttributeInstancerObjectsAndTransforms( // If failed to load this object, add default reference mesh UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + if (IsValid(DefaultReferenceSM)) { AttributeObject = DefaultReferenceSM; bHiddenInGame = true; @@ -1810,7 +1830,8 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( const TArray& InstancerMaterials, const TArray& OriginalInstancerObjectIndices, const int32& InstancerObjectIdx, - const bool& bForceHISM) + const bool& bForceHISM, + const bool& bForceInstancer) { enum InstancerComponentType { @@ -1826,7 +1847,9 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( // See if we can reuse the old component InstancerComponentType OldType = InstancerComponentType::Invalid; - if (OldComponent/*&& !OldComponent->IsPendingKill()*/) // The old component could be marked as pending kill + + // The old component could be marked as pending kill, dont IsValid() here + if (OldComponent != nullptr) { if(OldComponent->IsA()) OldType = Foliage; @@ -1858,16 +1881,17 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( if (StaticMesh) { - if (InstancedObjectTransforms.Num() == 1) - NewType = StaticMeshComponent; - else if (InIsFoliageInstancer) + const bool bMustUseInstancerComponent = InstancedObjectTransforms.Num() > 1 || bForceInstancer; + if (InIsFoliageInstancer) NewType = Foliage; else if (InIsSplitMeshInstancer) NewType = MeshSplitInstancerComponent; - else if(StaticMesh->GetNumLODs() > 1 || bForceHISM) + else if (bForceHISM || (bMustUseInstancerComponent && StaticMesh->GetNumLODs() > 1)) NewType = HierarchicalInstancedStaticMeshComponent; - else + else if (bMustUseInstancerComponent) NewType = InstancedStaticMeshComponent; + else + NewType = StaticMeshComponent; } else if (HSM) { @@ -1966,7 +1990,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstanceComponent( NewComponent->RegisterComponent(); // If the old component couldn't be reused, dettach/ destroy it - if (OldComponent && !OldComponent->IsPendingKill() && (OldComponent != NewComponent)) + if (IsValid(OldComponent) && (OldComponent != NewComponent)) { RemoveAndDestroyComponent(OldComponent, nullptr); } @@ -1989,16 +2013,16 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedStaticMeshComponent( if (!InstancedStaticMesh) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); bool bCreatedNewComponent = false; UInstancedStaticMeshComponent* InstancedStaticMeshComponent = Cast(CreatedInstancedComponent); - if (!InstancedStaticMeshComponent || InstancedStaticMeshComponent->IsPendingKill()) + if (!IsValid(InstancedStaticMeshComponent)) { if (InstancedStaticMesh->GetNumLODs() > 1 || bForceHISM) { @@ -2066,16 +2090,16 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( if (!InstancedObject) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); bool bCreatedNewComponent = false; UHoudiniInstancedActorComponent* InstancedActorComponent = Cast(CreatedInstancedComponent); - if (!InstancedActorComponent || InstancedActorComponent->IsPendingKill()) + if (!IsValid(InstancedActorComponent)) { // If the mesh doesnt have LOD, we can use a regular ISMC InstancedActorComponent = NewObject( @@ -2117,7 +2141,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateInstancedActorComponent( // Get the current instance // If null, we need to create a new one, else we can reuse the actor AActor* CurInstance = InstancedActorComponent->GetInstancedActorAt(Idx); - if (!CurInstance || CurInstance->IsPendingKill()) + if (!IsValid(CurInstance)) { CurInstance = SpawnInstanceActor(CurTransform, SpawnLevel, InstancedActorComponent); InstancedActorComponent->SetInstanceAt(Idx, CurTransform, CurInstance); @@ -2155,16 +2179,16 @@ FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( if (!InstancedStaticMesh) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); bool bCreatedNewComponent = false; UHoudiniMeshSplitInstancerComponent* MeshSplitComponent = Cast(CreatedInstancedComponent); - if (!MeshSplitComponent || MeshSplitComponent->IsPendingKill()) + if (!IsValid(MeshSplitComponent)) { // If the mesh doesn't have LOD, we can use a regular ISMC MeshSplitComponent = NewObject( @@ -2271,7 +2295,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) { UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) + if (!IsValid(CurSMC)) continue; if (!InstanceColors.IsValidIndex(InstIndex)) @@ -2309,7 +2333,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateMeshSplitInstancerComponent( for (int32 InstIndex = 0; InstIndex < Instances.Num(); InstIndex++) { UStaticMeshComponent* CurSMC = Instances[InstIndex]; - if (!CurSMC || CurSMC->IsPendingKill()) + if (!IsValid(CurSMC)) continue; UpdateGenericPropertiesAttributes(CurSMC, AllPropertyAttributes, InstIndex); @@ -2341,16 +2365,16 @@ FHoudiniInstanceTranslator::CreateOrUpdateStaticMeshComponent( if (!InstancedStaticMesh) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); bool bCreatedNewComponent = false; UStaticMeshComponent* SMC = Cast(CreatedInstancedComponent); - if (!SMC || SMC->IsPendingKill()) + if (!IsValid(SMC)) { // Create a new StaticMeshComponent SMC = NewObject( @@ -2410,16 +2434,16 @@ FHoudiniInstanceTranslator::CreateOrUpdateHoudiniStaticMeshComponent( if (!InstancedProxyStaticMesh) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); bool bCreatedNewComponent = false; UHoudiniStaticMeshComponent* HSMC = Cast(CreatedInstancedComponent); - if (!HSMC || HSMC->IsPendingKill()) + if (!IsValid(HSMC)) { // Create a new StaticMeshComponent HSMC = NewObject( @@ -2476,31 +2500,31 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( UMaterialInterface * InstancerMaterial /*=nullptr*/) { // We need either a valid SM or a valid Foliage Type - if ((!InstancedStaticMesh || InstancedStaticMesh->IsPendingKill()) - && (!InFoliageType || InFoliageType->IsPendingKill())) + if ((!IsValid(InstancedStaticMesh)) + && (!IsValid(InFoliageType))) return false; - if (!ParentComponent || ParentComponent->IsPendingKill()) + if (!IsValid(ParentComponent)) return false; UObject* ComponentOuter = ParentComponent; - if (ParentComponent->GetOwner() && !ParentComponent->GetOwner()->IsPendingKill()) + if (IsValid(ParentComponent->GetOwner())) ComponentOuter = ParentComponent->GetOwner(); AActor* OwnerActor = ParentComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return false; ULevel* DesiredLevel = GWorld->GetCurrentLevel(); AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, true); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + if (!IsValid(InstancedFoliageActor)) return false; // See if we already have a FoliageType for that static mesh bool bCreatedNew = false; UFoliageType *FoliageType = InFoliageType; - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) { // Foliage Type wasnt specified, only the mesh, try to find an existing foliage for that SM FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InstancedStaticMesh); @@ -2520,7 +2544,7 @@ FHoudiniInstanceTranslator::CreateOrUpdateFoliageInstances( bCreatedNew = InstancedFoliageActor->FindInfo(FoliageType) == nullptr; } - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) { // We need to create a new FoliageType for this Static Mesh // TODO: Add foliage default settings @@ -2694,15 +2718,15 @@ FHoudiniInstanceTranslator::UpdateGenericPropertiesAttributes( bool FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent, UObject* InFoliageObject) { - if (!InComponent || InComponent->IsPendingKill()) + if (!IsValid(InComponent)) return false; UFoliageInstancedStaticMeshComponent* FISMC = Cast(InComponent); - if (FISMC && !FISMC->IsPendingKill()) + if (IsValid(FISMC)) { // Make sure foliage our foliage instances have been removed USceneComponent* ParentComponent = Cast(FISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) + if (IsValid(ParentComponent)) CleanupFoliageInstances(FISMC, InFoliageObject, ParentComponent); // do not delete FISMC that still have instances left @@ -2712,7 +2736,7 @@ FHoudiniInstanceTranslator::RemoveAndDestroyComponent(UObject* InComponent, UObj } USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) + if (IsValid(SceneComponent)) { // Remove from the HoudiniAssetActor if (SceneComponent->GetOwner()) @@ -2797,7 +2821,7 @@ FHoudiniInstanceTranslator::GetInstancerMaterials( StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentMatString, nullptr, LOAD_NoWarn, nullptr)); // Check validity - if (!CurrentMaterialInterface || CurrentMaterialInterface->IsPendingKill()) + if (!IsValid(CurrentMaterialInterface)) CurrentMaterialInterface = nullptr; else bHasValidMaterial = true; @@ -3001,11 +3025,11 @@ FHoudiniInstanceTranslator::SpawnInstanceActor( ULevel* InSpawnLevel, UHoudiniInstancedActorComponent* InIAC) { - if (!InIAC || InIAC->IsPendingKill()) + if (!IsValid(InIAC)) return nullptr; UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) + if (!IsValid(InstancedObject)) return nullptr; AActor* NewActor = nullptr; @@ -3018,7 +3042,7 @@ FHoudiniInstanceTranslator::SpawnInstanceActor( TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject(InSpawnLevel, InstancedObject, false, RF_Transactional, nullptr); if (NewActors.Num() > 0) { - if (NewActors[0] && !NewActors[0]->IsPendingKill()) + if (IsValid(NewActors[0])) { NewActor = NewActors[0]; } @@ -3038,27 +3062,27 @@ FHoudiniInstanceTranslator::CleanupFoliageInstances( UObject* InInstancedObject, USceneComponent* InParentComponent) { - if (!InFoliageHISMC || InFoliageHISMC->IsPendingKill()) + if (!IsValid(InFoliageHISMC)) return; UStaticMesh* FoliageSM = InFoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) + if (!IsValid(FoliageSM)) return; // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, // if it is not, then we are just a "regular" HISMC AInstancedFoliageActor* InstancedFoliageActor = Cast(InFoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + if (!IsValid(InstancedFoliageActor)) return; // Get the Foliage Type UFoliageType *FoliageType = Cast(InInstancedObject); - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) { // Try to get the foliage type for the instanced mesh from the actor FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(InInstancedObject); - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) return; } @@ -3079,7 +3103,7 @@ FHoudiniInstanceTranslator::GetInstancerTypeFromComponent(UObject* InObject) USceneComponent* InComponent = Cast(InObject); FString InstancerType = TEXT("Instancer"); - if (InComponent && !InComponent->IsPendingKill()) + if (IsValid(InComponent)) { if (InComponent->IsA()) { @@ -3197,6 +3221,29 @@ FHoudiniInstanceTranslator::HasHISMAttribute(const HAPI_NodeId& GeoId, const HAP return IntData[0] != 0; } +bool +FHoudiniInstanceTranslator::HasForceInstancerAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId) +{ + bool bHISM = false; + HAPI_AttributeInfo AttriInfo; + FHoudiniApi::AttributeInfo_Init(&AttriInfo); + + TArray IntData; + IntData.Empty(); + + if (!FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( + GeoId, PartId, HAPI_UNREAL_ATTRIB_FORCE_INSTANCER, + AttriInfo, IntData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + return false; + } + + if (!AttriInfo.exists || IntData.Num() <= 0) + return false; + + return IntData[0] != 0; +} + void FHoudiniInstancedOutputPartData::BuildFlatInstancedTransformsAndObjectPaths() { diff --git a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h index 82e6b29c6..ad5a132c6 100644 --- a/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniInstanceTranslator.h @@ -76,6 +76,10 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData UPROPERTY() bool bForceHISM = false; + // Should we create an instancer even for single instances? + UPROPERTY() + bool bForceInstancer = false; + UPROPERTY() TArray OriginalInstancedObjects; @@ -154,6 +158,9 @@ struct HOUDINIENGINE_API FHoudiniInstancedOutputPartData UPROPERTY() TArray OutputNames; + UPROPERTY() + TArray BakeNames; + UPROPERTY() TArray TileValues; @@ -288,7 +295,8 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator const TArray& InstancerMaterials, const TArray& OriginalInstancerObjectIndices, const int32& InstancerObjectIdx = 0, - const bool& bForceHISM = false); + const bool& bForceHISM = false, + const bool& bForceInstancer = false); // Create or update an ISMC / HISMC static bool CreateOrUpdateInstancedStaticMeshComponent( @@ -441,6 +449,10 @@ struct HOUDINIENGINE_API FHoudiniInstanceTranslator // Get if force using HISM from attribute static bool HasHISMAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); + // Return true if HAPI_UNREAL_ATTRIB_FORCE_INSTANCER is set to non-zero (this controls + // if an instancer is created even for single instances (static mesh vs instanced static mesh for example) + static bool HasForceInstancerAttribute(const HAPI_NodeId& GeoId, const HAPI_NodeId& PartId); + // Checks for PerInstanceCustomData on the instancer part static bool GetPerInstanceCustomData( const int32& InGeoNodeId, diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp index 7c3a2776d..7f86f4d76 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.cpp @@ -59,6 +59,7 @@ #include "Modules/ModuleManager.h" #include "AssetToolsModule.h" #include "HoudiniEngineRuntimeUtils.h" +#include "LandscapeDataAccess.h" #include "Factories/WorldFactory.h" #include "Misc/Guid.h" #include "Engine/LevelBounds.h" @@ -107,12 +108,14 @@ FHoudiniLandscapeTranslator::CreateLandscape( ) { // Do the absolute minimum in order to determine which output mode we're dealing with (Temp or Editable Layers). + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("CreateLandscape!")); - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; // Get the height map. - const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput, false, NAME_None); if (!Heightfield) return false; @@ -145,10 +148,9 @@ FHoudiniLandscapeTranslator::CreateLandscape( switch (LandscapeOutputMode) { - case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_EDITABLE_LAYER: + case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_MODIFY_LAYER: { - return OutputLandscape_EditableLayer( - InOutput, + return OutputLandscape_ModifyLayer(InOutput, CreatedUntrackedOutputs, InputLandscapesToUpdate, InAllInputLandscapes, @@ -161,14 +163,16 @@ FHoudiniLandscapeTranslator::CreateLandscape( LandscapeTileSizeInfo, LandscapeReferenceLocation, InPackageParams, + false, + NAME_None, ClearedLayers, OutCreatedPackages); } break; - case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_DEFAULT: + case HAPI_UNREAL_LANDSCAPE_OUTPUT_MODE_GENERATE: default: { - return OutputLandscape_Temp(InOutput, + return OutputLandscape_Generate(InOutput, CreatedUntrackedOutputs, InputLandscapesToUpdate, InAllInputLandscapes, @@ -180,6 +184,7 @@ FHoudiniLandscapeTranslator::CreateLandscape( LandscapeExtent, LandscapeTileSizeInfo, LandscapeReferenceLocation, + ClearedLayers, InPackageParams, OutCreatedPackages ); @@ -189,7 +194,7 @@ FHoudiniLandscapeTranslator::CreateLandscape( } bool -FHoudiniLandscapeTranslator::OutputLandscape_Temp( +FHoudiniLandscapeTranslator::OutputLandscape_Generate( UHoudiniOutput* InOutput, TArray>& CreatedUntrackedOutputs, TArray& InputLandscapesToUpdate, @@ -202,21 +207,167 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( FHoudiniLandscapeExtent& LandscapeExtent, FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + TSet& ClearedLayers, FHoudiniPackageParams InPackageParams, TArray& OutCreatedPackages ) { + TArray EditLayerNames; + const bool bHasEditLayers = GetEditLayersFromOutput(InOutput, EditLayerNames); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::Output_Landscape_Generate] Generating landscape tile. Has edit layers?: %d"), bHasEditLayers); + + TArray AllLayerNames; + for (const FString& LayerName : EditLayerNames) + { + AllLayerNames.Add(FName(LayerName)); + } + + if (!bHasEditLayers) + { + // Add a dummy edit layer to simply get us into the following for-loop. + EditLayerNames.Add(FString()); + } + + FName AfterLayerName = NAME_None; + + TSet ActiveLandscapes; + TArray StaleOutputObjects; + + InOutput->GetOutputObjects().GenerateKeyArray(StaleOutputObjects); + + // Collect current edit layers and their respective landscapes so that we can detect and remove stale edit layers. + TMap StaleEditLayers; + { + TMap& OutputObjects = InOutput->GetOutputObjects(); + for (auto& Entry : OutputObjects) + { + UHoudiniLandscapePtr* LandscapePtr = Cast(Entry.Value.OutputObject); + if (LandscapePtr) + { + ALandscapeProxy* Proxy = LandscapePtr->GetRawPtr(); + if (Proxy) + { + ALandscape* Landscape = Proxy->GetLandscapeActor(); + if (Landscape) + { + StaleEditLayers.Add(LandscapePtr->EditLayerName, Landscape); + } + } + } + } + } + + for (const FString& LayerName : EditLayerNames) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::Output_Landscape_Generate] Generating tile for heightfield: %s"), *LayerName); + // If edit layers are enabled for this volume, create each layer for this landscape tile + FName LayerFName(LayerName); + StaleEditLayers.Remove(LayerFName); + + OutputLandscape_GenerateTile(InOutput, + StaleOutputObjects, + CreatedUntrackedOutputs, + InputLandscapesToUpdate, + InAllInputLandscapes, + SharedLandscapeActorParent, + DefaultLandscapeActorPrefix, + InWorld, + LayerMinimums, + LayerMaximums, + LandscapeExtent, + LandscapeTileSizeInfo, + LandscapeReferenceLocation, + InPackageParams, + bHasEditLayers, + LayerName, + AfterLayerName, + AllLayerNames, + ClearedLayers, + OutCreatedPackages, + ActiveLandscapes); + AfterLayerName = LayerFName; + } + + // Clean up stale output objects + TMap& OutputObjects = InOutput->GetOutputObjects(); + for(FHoudiniOutputObjectIdentifier ObjectId : StaleOutputObjects) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_Generate] Processing stale output: %s"), *ObjectId.PartName); + FHoudiniOutputObject* OutputObject = OutputObjects.Find(ObjectId); + if (!OutputObject) + continue; + + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObject->OutputObject); + if (LandscapePtr) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_Generate] LandscapePtr: %s"), *LandscapePtr->GetFullName()); + // Cleanup stale landscape outputs + ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + if (IsValid(LandscapeProxy)) + { + if (!ActiveLandscapes.Contains(LandscapeProxy)) + { + // This landscape actor is no longer in use. Trash it. + LandscapeProxy->Destroy(); + } + } + LandscapePtr->SetSoftPtr(nullptr); + } + OutputObjects.Remove(ObjectId); + } + + // Clean up stale layers + for (auto& Entry : StaleEditLayers) + { + ALandscape* Landscape = Entry.Value; + if (IsValid(Landscape) && Landscape->HasLayersContent()) + { + const int32 LayerIndex = Landscape->GetLayerIndex(Entry.Key); + Landscape->DeleteLayer(LayerIndex); + } + } + + return true; +} + +bool +FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile( + UHoudiniOutput* InOutput, + TArray &StaleOutputObjects, + TArray>& CreatedUntrackedOutputs, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* InWorld, // Persistent / root world for the landscape + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + bool bHasEditLayers, + const FString& InEditLayerName, + const FName& InAfterLayerName, + const TArray& AllLayerNames, + TSet& ClearedLayers, + TArray& OutCreatedPackages, + TSet& OutActiveLandscapes +) +{ + FName InEditLayerFName = FName(InEditLayerName); check(LayerMinimums.Contains(TEXT("height"))); check(LayerMaximums.Contains(TEXT("height"))); float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; // Get the height map. - const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput, bHasEditLayers, InEditLayerFName); if (!Heightfield) return false; @@ -247,6 +398,8 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( InPackageParams.UpdateTokensFromParams(InWorld, HoudiniAssetComponent, OutputTokens); bool bHasTile = Heightfield->VolumeTileIndex >= 0; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Volume Tile Index: %d"), Heightfield->VolumeTileIndex); // --------------------------------------------- // Attribute: unreal_landscape_tile_actor_type, unreal_landscape_streaming_proxy (v1) @@ -295,7 +448,7 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( // --------------------------------------------- // Attribute: unreal_level_path - // --------------------------------------------- + // --------------------------------------------- // FString LevelPath = bHasTile ? "{world}/Landscape/Tile{tile}" : "{world}/Landscape"; FString LevelPath; TArray LevelPaths; @@ -315,10 +468,62 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( if (FHoudiniEngineUtils::GetOutputNameAttribute(GeoId, PartId, AllOutputNames, 0, 1)) { if (AllOutputNames.Num() > 0 && !AllOutputNames[0].IsEmpty()) + { LandscapeTileActorName = AllOutputNames[0]; + } } OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2), LandscapeTileActorName); + // --------------------------------------------- + // Attribute: unreal_bake_name + // --------------------------------------------- + FString LandscapeTileBakeName = bHasTile ? "LandscapeTile{tile}" : "Landscape"; + TArray AllBakeNames; + if (FHoudiniEngineUtils::GetBakeNameAttribute(GeoId, PartId, AllBakeNames, 0, 1)) + { + if (AllBakeNames.Num() > 0 && !AllBakeNames[0].IsEmpty()) + { + LandscapeTileBakeName = AllBakeNames[0]; + } + } + OutputAttributes.Add(FString(HAPI_UNREAL_ATTRIB_BAKE_NAME), LandscapeTileBakeName); + + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_after + // --------------------------------------------- + StrData.Empty(); + FName AfterLayerName = InAfterLayerName; + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_AFTER, AttributeInfo, StrData, 1)) + { + if (StrData.Num() > 0) + { + AfterLayerName = FName(StrData[0]); + } + } + + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_type + // --------------------------------------------- + int32 EditLayerType = HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_BASE; + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TYPE, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0) + { + EditLayerType = IntData[0]; + } + } + + + // By default, if we're in EditLayer mode, always 'check' this option + // otherwise the paint layers will be additively blended which is + // typically not expected. + // The user should be able to override this behaviour by setting + // the HAPI_UNREAL_ATTRIB_NONWEIGHTBLENDED_LAYERS attribute on the paint layer heightfields in Houdini. + bool bLayerNoWeightBlend = bHasEditLayers ? true : false; + // --------------------------------------------- // Attribute: unreal_bake_folder // --------------------------------------------- @@ -493,13 +698,18 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( // Look for all the layers/masks corresponding to the current heightfield. TArray< const FHoudiniGeoPartObject* > FoundLayers; - FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); + FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, bHasEditLayers, InEditLayerFName, FoundLayers); // Get the updated layers. TArray LayerInfos; + + // If we are operating in EditLayer mode, then any layers we create or manage should + // have their bNoWeightBlend set to true otherwise all the paint layers will act as additive + // which, most of the time, is not what we expect. + const bool bDefaultNoWeightBlend = bHasEditLayers ? true : false; - if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, - LayerMinimums, LayerMaximums, LayerInfos, false, + if (!CreateOrUpdateLandscapeLayerData(FoundLayers, *Heightfield, UnrealTileSizeX, UnrealTileSizeY, + LayerMinimums, LayerMaximums, LayerInfos, false, bDefaultNoWeightBlend, TilePackageParams, LayerPackageParams, OutCreatedPackages)) @@ -512,7 +722,11 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( FloatValues, VolumeInfo, UnrealTileSizeX, UnrealTileSizeY, FloatMin, FloatMax, - IntHeightData, TileTransform)) + IntHeightData, TileTransform, + true, + false, + 100.f, + EditLayerType == HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_ADDITIVE)) return false; // ---------------------------------------------------- @@ -542,10 +756,27 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( // for any landscape shifts due to section base alignment offsets. CalculateTileLocation(NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection, TileTransform, LandscapeReferenceLocation, LandscapeTransform, TileLoc); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Transform: %s"), *TileTransform.ToString()); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape Transform: %s"), *LandscapeTransform.ToString()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Tile Transform: %s"), *TileTransform.ToString()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Landscape Transform: %s"), *LandscapeTransform.ToString()); + + // Determine the level of the owning HAC (if we have a valid owning HAC), and use that as the Tile and shared + // landscape's world and level + UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); + if (IsValid(HAC)) + { + TileWorld = HAC->GetWorld(); + TileLevel = HAC->GetComponentLevel(); + } + else + { + // If we don't have a valid HAC fallback to the persistent level of InWorld + TileWorld = InWorld; + TileLevel = InWorld->PersistentLevel; + } + + check(TileWorld); + check(TileLevel); - // ---------------------------------------------------- // Find or create *shared* landscape // ---------------------------------------------------- @@ -558,7 +789,7 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( // Streaming proxy tiles always require a "shared landscape" that contains the // various landscape properties to be shared amongst all the tiles. AActor* FoundActor = nullptr; - SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(InWorld, SharedLandscapeActorName, FoundActor); + SharedLandscapeActor = FHoudiniEngineUtils::FindOrRenameInvalidActor(TileWorld, SharedLandscapeActorName, FoundActor); bool bIsValidSharedLandscape = IsValid(SharedLandscapeActor); @@ -567,8 +798,8 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( // We have a target landscape. Check whether it is compatible with the Houdini volume. ULandscapeInfo* LandscapeInfo = SharedLandscapeActor->GetLandscapeInfo(); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape: %s"), *(SharedLandscapeActor->GetPathName())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Found existing landscape with num proxies: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Found existing landscape: %s"), *(SharedLandscapeActor->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Found existing landscape with num proxies: %d"), LandscapeInfo->Proxies.Num()); if (!LandscapeExtent.bIsCached) { @@ -581,11 +812,11 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( LandscapeInfo, NumSectionPerLandscapeComponent, NumQuadsPerLandscapeSection); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Checking landscape compatibility ...")); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Checking landscape compatibility ...")); bIsCompatible = bIsCompatible && IsLandscapeTypeCompatible(SharedLandscapeActor, LandscapeActorType::LandscapeActor); if (!bIsCompatible) { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Shared landscape is incompatible. Cannot reuse.")); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Shared landscape is incompatible. Cannot reuse.")); // We can't resize the landscape in-place. We have to create a new one. DestroyLandscape(SharedLandscapeActor); SharedLandscapeActor = nullptr; @@ -593,16 +824,19 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( } else { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Existing shared landscape is compatible.")); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Existing shared landscape is compatible.")); } } if (!bIsValidSharedLandscape) { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Create new shared landscape...")); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Create new shared landscape...")); // Create and configure the main landscape actor. // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos - SharedLandscapeActor = InWorld->SpawnActor(); + FActorSpawnParameters SpawnParameters; + if (IsValid(TileLevel)) + SpawnParameters.OverrideLevel = TileLevel; + SharedLandscapeActor = TileWorld->SpawnActor(SpawnParameters); if (SharedLandscapeActor) { CreatedUntrackedOutputs.Add( SharedLandscapeActor ); @@ -610,9 +844,7 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( // NOTE that shared landscape is always located at the origin, but not the tile actors. The // tiles are properly transformed. - // If we working with landscape tiles, this actor will become the "Main landscape" actor but - // doesn't actually contain any content. Landscape Streaming Proxies will contain the layer content. - SharedLandscapeActor->bCanHaveLayersContent = false; + SharedLandscapeActor->bCanHaveLayersContent = bHasEditLayers; SharedLandscapeActor->ComponentSizeQuads = NumQuadsPerLandscapeSection*NumSectionPerLandscapeComponent; SharedLandscapeActor->NumSubsections = NumSectionPerLandscapeComponent; SharedLandscapeActor->SubsectionSizeQuads = NumQuadsPerLandscapeSection; @@ -638,7 +870,7 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( } else { - HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(InWorld->GetPathName()) ); + HOUDINI_LOG_ERROR(TEXT("Could not create main landscape actor (%s) in world (%s)"), *(SharedLandscapeActorName), *(TileWorld->GetPathName()) ); return false; } } @@ -647,11 +879,14 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( if (SharedLandscapeActor) { + SharedLandscapeActor->bCanHaveLayersContent = bHasEditLayers; + // Ensure the existing landscape actor transform is correct. SharedLandscapeActor->SetActorRelativeTransform(LandscapeTransform); + + bSharedLandscapeMaterialChanged = (SharedLandscapeActor->LandscapeMaterial != LandscapeMaterial); + bSharedLandscapeHoleMaterialChanged = SharedLandscapeActor->LandscapeHoleMaterial != LandscapeHoleMaterial; - bSharedLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bSharedLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (SharedLandscapeActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) { DoPreEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); @@ -675,6 +910,12 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( SharedLandscapeActor->DefaultPhysMaterial = LandscapePhysicalMaterial; SharedLandscapeActor->ChangedPhysMaterial(); } + + if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + { + check(SharedLandscapeActor); + DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + } } // ---------------------------------------------------- @@ -692,21 +933,6 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( // Currently the Temp Cook mode is not concerned with creating packages. This will, at the time of writing, // exclusively be dealt with during Bake mode so don't bother with searching / creating other packages. - UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); - if (IsValid(HAC)) - { - TileWorld = HAC->GetWorld(); - TileLevel = HAC->GetComponentLevel(); - } - else - { - TileWorld = InWorld; - TileLevel = InWorld->PersistentLevel; - } - - check(TileWorld); - check(TileLevel); - AActor* FoundActor = nullptr; if (InPackageParams.PackageMode == EPackageMode::Bake) { @@ -785,7 +1011,7 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( if (!FoundLandscapeProxy) { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Could not find input landscape to update. Searching output objects...")); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Could not find input landscape to update. Searching output objects...")); // Try to see if we can reuse one of our previous output landscape. // Keep track of the previous cook's landscapes @@ -820,7 +1046,7 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( if (SharedLandscapeActor && FoundLandscapeProxy->GetLandscapeActor() != SharedLandscapeActor) { // This landscape proxy is no longer part of the shared landscape. - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Output landscape proxy is no longer part of the landscape. Skipping")); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Output landscape proxy is no longer part of the landscape. Skipping")); FoundLandscapeProxy = nullptr; continue; } @@ -872,8 +1098,6 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( if (IsValid(TileActor)) { - check(!(TileActor->IsPendingKill())); - // ---------------------------------------------------- // Check landscape compatibility // ---------------------------------------------------- @@ -899,13 +1123,13 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( LandscapeInfo->UnregisterActor(TileActor); } } - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Incompatible tile actor. Destroying: %s"), *(TileActor->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Incompatible tile actor. Destroying: %s"), *(TileActor->GetPathName())); TileActor->Destroy(); TileActor = nullptr; } else { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape tile is compatible: %s"), *(TileActor->GetPathName())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Landscape tile is compatible: %s"), *(TileActor->GetPathName())); } } @@ -922,16 +1146,48 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( ULandscapeInfo *LandscapeInfo; #if defined(HOUDINI_ENGINE_DEBUG_LANDSCAPE) - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Loc: %d, %d"), TileLoc.X, TileLoc.Y); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Size: %d, %d"), UnrealTileSizeX, UnrealTileSizeY); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Quads/Section: %d"), NumQuadsPerLandscapeSection); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Tile Num Sections/Component: %d"), NumSectionPerLandscapeComponent); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Tile Loc: %d, %d"), TileLoc.X, TileLoc.Y); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Tile Size: %d, %d"), UnrealTileSizeX, UnrealTileSizeY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Tile Num Quads/Section: %d"), NumQuadsPerLandscapeSection); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Tile Num Sections/Component: %d"), NumSectionPerLandscapeComponent); #endif + // ---------------------------------------------------- + // Update tile materials + // ---------------------------------------------------- + auto UpdateTileMaterialsFn = [&] () + { + bTileLandscapeMaterialChanged = (TileActor->LandscapeMaterial != LandscapeMaterial); + bTileLandscapeHoleMaterialChanged = (TileActor->LandscapeHoleMaterial != LandscapeHoleMaterial); + + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); + + if (bTileLandscapeMaterialChanged) + TileActor->LandscapeMaterial = LandscapeMaterial; + + if (bTileLandscapeHoleMaterialChanged) + TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; + + bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; + if (bTilePhysicalMaterialChanged) + { + DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); + TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; + //TileActor->ChangedPhysMaterial(); + } + + if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + { + DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); + } + }; + if (!TileActor) { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Creating new tile actor: %s"), *(LandscapeTileActorName)); - // Create a new Landscape tile in the TileWorld + // Create a new Landscape tile + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Creating new tile actor: %s"), *(LandscapeTileActorName)); TileActor = FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( IntHeightData, LayerInfos, TileTransform, TileLoc, UnrealTileSizeX, UnrealTileSizeY, @@ -942,23 +1198,40 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( SharedLandscapeActor, TileWorld, TileLevel, - InPackageParams); + InPackageParams, + bHasEditLayers, + InEditLayerFName, + AfterLayerName); if (!TileActor || !TileActor->IsValidLowLevel()) return false; + TileActor->GetLandscapeActor()->bCanHaveLayersContent = bHasEditLayers; LandscapeInfo = TileActor->GetLandscapeInfo(); + UpdateTileMaterialsFn(); + + TMap ExistingLayers; + UpdateLandscapeMaterialLayers( + TileActor->GetLandscapeActor(), + LayerInfos, + ExistingLayers, + bLayerNoWeightBlend, + bHasEditLayers, + InEditLayerFName); + bCreatedTileActor = true; - bTileLandscapeMaterialChanged = true; - bTileLandscapeHoleMaterialChanged = true; - bTilePhysicalMaterialChanged = true; bHeightLayerDataChanged = true; bCustomLayerDataChanged = true; } else { + // Re-use existing tile actor + ALandscape* TargetLandscape = TileActor->GetLandscapeActor(); + check(TargetLandscape); + LandscapeInfo = TileActor->GetLandscapeInfo(); + TargetLandscape->bCanHaveLayersContent = bHasEditLayers; // Always update the transform, even if the HGPO transform hasn't changed, // If we change the number of tiles, or switch from outputting single tile to multiple, @@ -974,7 +1247,7 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( { TileActor->SetAbsoluteSectionBase(TileLoc); LandscapeInfo->FixupProxiesTransform(); - LandscapeInfo->RecreateLandscapeInfo(InWorld,true); + LandscapeInfo->RecreateLandscapeInfo(TileWorld,true); } // This is a tile with a shared landscape. @@ -1010,6 +1283,12 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( TileActor->SetAbsoluteSectionBase(TileLoc); } } + + UpdateTileMaterialsFn(); + + // Update landscape edit layers to match layer infos + TMap ExistingLayers; + UpdateLandscapeMaterialLayers(TargetLandscape, LayerInfos, ExistingLayers, bLayerNoWeightBlend, bHasEditLayers, InEditLayerFName); CachedLandscapeActor = TileActor->GetLandscapeActor(); @@ -1017,6 +1296,22 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( if (!PreviousInfo) return false; + int32 EditLayerIndex = INDEX_NONE; + FLandscapeLayer* TargetLayer = nullptr; + FGuid LayerGuid; + if (bHasEditLayers) + { + EditLayerIndex = FindOrCreateEditLayer(CachedLandscapeActor, InEditLayerFName, AfterLayerName); + TargetLayer = CachedLandscapeActor->GetLayer(EditLayerIndex); + if (TargetLayer) + { + // Ensure target layer blend mode is additive + TargetLayer->BlendMode = ELandscapeBlendMode::LSBM_AdditiveBlend; + LayerGuid = TargetLayer->Guid; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile] Setting edit layer to additive: %d, %s"), EditLayerIndex, *(TargetLayer->Name.ToString())); + } + } + FIntRect BoundingRect = TileActor->GetBoundingRect(); FIntPoint SectionOffset = TileActor->GetSectionBaseOffset(); @@ -1026,64 +1321,117 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( const int32 MinY = TileLoc.Y; const int32 MaxY = TileLoc.Y + UnrealTileSizeY - 1; - // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. - // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools - // though the *Accessors do additional things like update normals and foliage. - - // Update height if it has been changed. - if (Heightfield->bHasGeoChanged) + if (!bHasEditLayers || (bHasEditLayers && LayerGuid.IsValid())) { - // It is important to update the heightmap through HeightmapAccessor this since it will properly - // update normals and foliage. - FHeightmapAccessor HeightmapAccessor(LandscapeInfo); - HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); - bHeightLayerDataChanged = true; - } + FScopedSetLandscapeEditingLayer Scope(CachedLandscapeActor, LayerGuid, [=] { CachedLandscapeActor->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); }); + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); - // Update the layers on the landscape. - for (FLandscapeImportLayerInfo &NextUpdatedLayerInfo : LayerInfos) - { - if (NextUpdatedLayerInfo.LayerInfo && NextUpdatedLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + bool bClearLayers = false; + if (!ClearedLayers.Contains(InEditLayerName)) { - // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. - FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); - AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + // We haven't cleared layers for this edit layer yet. + ClearedLayers.Add(InEditLayerName); + bClearLayers = true; } - else + + // NOTE: Use HeightmapAccessor / AlphamapAccessor instead of FLandscapeEditDataInterface. + // FLandscapeEditDataInterface is a more low level data interface, used internally by the *Accessor tools + // though the *Accessors do additional things like update normals and foliage. + + // Update height if it has been changed. + if (Heightfield->bHasGeoChanged) + { + // It is important to update the heightmap through HeightmapAccessor this since it will properly + // update normals and foliage. + FHeightmapAccessor HeightmapAccessor(LandscapeInfo); + HeightmapAccessor.SetData(MinX, MinY, MaxX, MaxY, IntHeightData.GetData()); + bHeightLayerDataChanged = true; + } + + // Collect the stale layers so that we can clear them afterward. + TSet StaleLayers; + for (FLandscapeInfoLayerSettings& Layer : LandscapeInfo->Layers) + { + StaleLayers.Add(Layer.LayerName); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("Tracking potential stale layer: %s"), *(Layer.LayerName.ToString())); + } + + // Update the layers on the landscape. + for (FLandscapeImportLayerInfo &LayerInfo : LayerInfos) { - FAlphamapAccessor AlphaAccessor(LandscapeInfo, NextUpdatedLayerInfo.LayerInfo); - AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, NextUpdatedLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + FString PaintLayerName(LayerInfo.LayerName.ToString()); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile] Drawing found layer: %s"), *(PaintLayerName) ); + + if (LayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + // Visibility layer has 'special' naming. + StaleLayers.Remove(ALandscape::VisibilityLayer->LayerName); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("Removing visibility from stale layer set: %s"), *(ALandscape::VisibilityLayer->LayerName.ToString())); + } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("Removing layer from stale layer set: %s"), *(LayerInfo.LayerName.ToString())); + StaleLayers.Remove(LayerInfo.LayerName); + } + + if (LayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + if (LayerInfo.LayerInfo) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile] Drawing visibility layer: %s on EditLayer (%s) (bNoWeightBlend: %d)"), *(LayerInfo.LayerName.ToString()), *(InEditLayerName), (LayerInfo.LayerInfo->bNoWeightBlend) ); + // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. + FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, LayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Cannot draw on NULL visibility layer")); + } + + } + else + { + ULandscapeLayerInfoObject* TargetLayerInfo = LandscapeInfo->GetLayerInfoByName(LayerInfo.LayerName); + if (TargetLayerInfo) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile] Drawing paint layer: %s on EditLayer (%s) (bNoWeightBlend: %d)"), *(LayerInfo.LayerName.ToString()), *(InEditLayerName), (LayerInfo.LayerInfo->bNoWeightBlend) ); + FAlphamapAccessor AlphaAccessor(LandscapeInfo, TargetLayerInfo); + AlphaAccessor.SetData(MinX, MinY, MaxX, MaxY, LayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + HOUDINI_LOG_WARNING(TEXT("Cannot draw on NULL landscape layer: %s"), *(LayerInfo.LayerName.ToString())); + } + } + + bCustomLayerDataChanged = true; } - bCustomLayerDataChanged = true; + // Clear stale layers + if (bClearLayers) + { + for (FName& StaleLayerName : StaleLayers) + { + ULandscapeLayerInfoObject* LayerInfo = LandscapeInfo->GetLayerInfoByName(StaleLayerName); + if (!LayerInfo) + { + continue; + } + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::OutputLandscape_GenerateTile] Clearing stale layer name %s, layer info name: %s on EditLayer (%s)"), *(StaleLayerName.ToString()), *(LayerInfo->LayerName.ToString()), *(InEditLayerName) ); + TargetLandscape->ClearPaintLayer(LayerGuid, LayerInfo); + } + } + } + else + { + HOUDINI_LOG_ERROR(TEXT("Could not retrieve valid Guid for edit layer: %s"), *(InEditLayerName)); } bModifiedLandscapeActor = true; } - // ---------------------------------------------------- - // Update tile materials - // ---------------------------------------------------- - // TODO: These material updates can possibly be skipped if we have already performed this - // check on a SharedLandscape. - bTileLandscapeMaterialChanged = LandscapeMaterial != nullptr ? (TileActor->GetLandscapeMaterial() != LandscapeMaterial) : false; - bTileLandscapeHoleMaterialChanged = LandscapeHoleMaterial != nullptr ? (TileActor->GetLandscapeHoleMaterial() != LandscapeHoleMaterial) : false; - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - DoPreEditChangeProperty(TileActor, "LandscapeMaterial"); - if (bTileLandscapeMaterialChanged) - TileActor->LandscapeMaterial = LandscapeMaterial; - - if (bTileLandscapeHoleMaterialChanged) - TileActor->LandscapeHoleMaterial = LandscapeHoleMaterial; - - bTilePhysicalMaterialChanged = LandscapePhysicalMaterial != nullptr ? TileActor->DefaultPhysMaterial != LandscapePhysicalMaterial : false; - if (bTilePhysicalMaterialChanged) - { - DoPreEditChangeProperty(TileActor, "DefaultPhysMaterial"); - TileActor->DefaultPhysMaterial = LandscapePhysicalMaterial; - //TileActor->ChangedPhysMaterial(); - } // ---------------------------------------------------- // Apply actor tags @@ -1103,19 +1451,19 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( // effect appropriate state updates based on the property updates that was performed in // the above code. - if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) - { - check(SharedLandscapeActor); - DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); - } + // if (bSharedLandscapeMaterialChanged || bSharedLandscapeHoleMaterialChanged) + // { + // check(SharedLandscapeActor); + // DoPostEditChangeProperty(SharedLandscapeActor, "LandscapeMaterial"); + // } - if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) - { - check(TileActor); - // Tile material changes are only processed if it wasn't already done for a shared - // landscape since the shared landscape should have already propagated the changes to associated proxies. - DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); - } + // if (bTileLandscapeMaterialChanged || bTileLandscapeHoleMaterialChanged) + // { + // check(TileActor); + // // Tile material changes are only processed if it wasn't already done for a shared + // // landscape since the shared landscape should have already propagated the changes to associated proxies. + // DoPostEditChangeProperty(TileActor, "LandscapeMaterial"); + // } if (bSharedPhysicalMaterialChanged) { @@ -1146,7 +1494,7 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( if (LandscapeInfo) { - LandscapeInfo->RecreateLandscapeInfo(InWorld, true); + LandscapeInfo->RecreateLandscapeInfo(TileWorld, true); LandscapeInfo->RecreateCollisionComponents(); } @@ -1189,9 +1537,12 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( // Add objects to the HAC output. SetLandscapeActorAsOutput( InOutput, + StaleOutputObjects, + OutActiveLandscapes, InAllInputLandscapes, OutputAttributes, OutputTokens, + InEditLayerFName, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedSharedLandscape, @@ -1204,18 +1555,18 @@ FHoudiniLandscapeTranslator::OutputLandscape_Temp( { int32 MinX, MinY, MaxX, MaxY; LandscapeInfo->GetLandscapeExtent(MinX, MinY, MaxX, MaxY); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Landscape extent: %d, %d -> %d, %d"), MinX, MinY, MaxX, MaxY); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Cached extent: %d, %d -> %d, %d"), LandscapeExtent.MinX, LandscapeExtent.MinY, LandscapeExtent.MaxX, LandscapeExtent.MaxY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Landscape extent: %d, %d -> %d, %d"), MinX, MinY, MaxX, MaxY); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Cached extent: %d, %d -> %d, %d"), LandscapeExtent.MinX, LandscapeExtent.MinY, LandscapeExtent.MaxX, LandscapeExtent.MaxY); } - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscape] Ending with num of output objects: %d"), InOutput->GetOutputObjects().Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_Generate] Ending with num of output objects: %d"), InOutput->GetOutputObjects().Num()); #endif return true; } -bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* InOutput, +bool FHoudiniLandscapeTranslator::OutputLandscape_ModifyLayer(UHoudiniOutput* InOutput, TArray>& CreatedUntrackedActors, TArray& InputLandscapesToUpdate, const TArray& InAllInputLandscapes, USceneComponent* SharedLandscapeActorParent, const FString& DefaultLandscapeActorPrefix, UWorld* World, const TMap& LayerMinimums, @@ -1223,6 +1574,8 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* FHoudiniLandscapeExtent& LandscapeExtent, FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, FHoudiniPackageParams InPackageParams, + const bool bHasEditLayers, + const FName& EditLayerName, TSet& ClearedLayers, TArray& OutCreatedPackages) { @@ -1234,7 +1587,7 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* float fGlobalMin = LayerMinimums.FindChecked(TEXT("height")); float fGlobalMax = LayerMaximums.FindChecked(TEXT("height")); - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InOutput); @@ -1242,7 +1595,7 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* return false; // Get the height map. - const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput); + const FHoudiniGeoPartObject* Heightfield = GetHoudiniHeightFieldFromOutput(InOutput, false, NAME_None); if (!Heightfield) return false; @@ -1252,10 +1605,27 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* const HAPI_NodeId GeoId = Heightfield->GeoId; const HAPI_PartId PartId = Heightfield->PartId; + TArray IntData; TArray StrData; HAPI_AttributeInfo AttributeInfo; FHoudiniApi::AttributeInfo_Init(&AttributeInfo); + // --------------------------------------------- + // Attribute: unreal_landscape_editlayer_type + // --------------------------------------------- + int32 EditLayerType = HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_BASE; + IntData.Empty(); + if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, + HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TYPE, AttributeInfo, IntData, 1)) + { + if (IntData.Num() > 0) + { + EditLayerType = IntData[0]; + } + } + + const bool bLayerNoWeightBlend = EditLayerType == HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_BASE ? false : true; + // --------------------------------------------- // Attribute: unreal_landscape_editlayer_name // --------------------------------------------- @@ -1317,19 +1687,42 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* // Attribute: unreal_landscape_actor_name // --------------------------------------------- // Retrieve the name of the main Landscape actor to look for - FString TargetLandscapeName = "Input0"; + FString TargetLandscapeName = "Input0"; + bool bHasEditLayerTarget = false; StrData.Empty(); if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( - GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET, AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) { if (StrData.Num() > 0 && !StrData[0].IsEmpty()) + { TargetLandscapeName = StrData[0]; + bHasEditLayerTarget = true; + } + } + + if (!bHasEditLayerTarget) + { + // The previous implementation of the "Edit Layer" mode used the Shared Landscape Actor name. If the (new) + // Edit Layer Target attribute wasn't specified, check whether the old attribute is present. + if (FHoudiniEngineUtils::HapiGetAttributeDataAsString( + GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, + AttributeInfo, StrData, 1, HAPI_ATTROWNER_INVALID, 0, 1)) + { + if (StrData.Num() > 0 && !StrData[0].IsEmpty()) + { + TargetLandscapeName = StrData[0]; + } + } } - Resolver.SetAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, TargetLandscapeName); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Landscape Name (Attrib): %s"), *(TargetLandscapeName)); + + Resolver.SetAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET, TargetLandscapeName); Resolver.SetTokensFromStringMap(OutputTokens); - TargetLandscapeName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_SHARED_ACTOR_NAME, TargetLandscapeName); + TargetLandscapeName = Resolver.ResolveAttribute(HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_TARGET, TargetLandscapeName); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Landscape Name (Resolved): %s"), *(TargetLandscapeName)); // --------------------------------------------- // Find the landscape that we're targeting for output @@ -1341,7 +1734,17 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* return false; } ALandscape* TargetLandscape = TargetLandscapeProxy->GetLandscapeActor(); - check(TargetLandscape); + if (!IsValid(TargetLandscape)) + { + HOUDINI_LOG_WARNING(TEXT("Could not retrieve the landscape actor for: %s"), *(TargetLandscapeName)); + return false; + } + + if (!TargetLandscape->bCanHaveLayersContent) + { + HOUDINI_LOG_WARNING(TEXT("Target landscape does not have edit layers enabled!: %s"), *(TargetLandscapeName)); + return false; + } ULandscapeInfo* TargetLandscapeInfo = TargetLandscapeProxy->GetLandscapeInfo(); const FTransform TargetLandscapeTransform = TargetLandscape->GetActorTransform(); @@ -1354,7 +1757,7 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* const FLandscapeLayer* TargetLayer = TargetLandscape->GetLayer(EditLayerIndex); if (!TargetLayer) { - // Create new layer + // Create new edit layer EditLayerIndex = TargetLandscape->CreateLayer(FName(EditableLayerName)); TargetLayer = TargetLandscape->GetLayer(FName(EditableLayerName)); } @@ -1411,57 +1814,53 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, FloatMin, FloatMax, IntHeightData, TileTransform, - false, true, DestHeightScale)) - return false; + false, true, DestHeightScale, + EditLayerType == HAPI_UNREAL_LANDSCAPE_EDITLAYER_TYPE_ADDITIVE)) + return false; // ---------------------------------------------------- // Calculate Tile location and landscape offset // ---------------------------------------------------- - FTransform SrcLandscapeTransform, HACTransform; - FIntPoint TileLoc; - - // Calculate the tile location (in quad space) as well as the corrected TileTransform which will compensate - // for any landscape shifts due to section base alignment offsets. - CalculateTileLocation(LandscapeTileSizeInfo.NumSectionsPerComponent, LandscapeTileSizeInfo.NumQuadsPerSection, TileTransform, LandscapeReferenceLocation, SrcLandscapeTransform, TileLoc); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Landscape Transform: %s"), *(TargetLandscapeTransform.ToString())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Tile Transform: %s"), *(TileTransform.ToString())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Tile Size: %d, %d"), LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY); - - HACTransform = HAC->GetComponentTransform(); - SrcLandscapeTransform = HACTransform; + int32 TargetMinX, TargetMaxX, TargetMinY, TargetMaxY; + TargetLandscapeInfo->GetLandscapeExtent(TargetMinX, TargetMinY, TargetMaxX, TargetMaxY); // ---------------------------------------------------- - // Convert the tile coordinates to quad space on the target Landscape. - // ---------------------------------------------------- - FVector RelTileLoc = (TileTransform*HACTransform).GetLocation(); - RelTileLoc = TargetLandscapeTransform.InverseTransformPosition(RelTileLoc); + // Calculate the draw location (in quad space) of + // where the Modify Layer should be drawn + // ---------------------------------------------------- + FVector LandscapeBaseLoc = TargetLandscape->GetTransform().InverseTransformPosition(TargetLandscape->GetTransform().GetLocation()); + FTransform HACTransform = HAC->GetComponentTransform(); + + // We need to unscale the TileTransform before concatenating it with the HAC transform. + FTransform UnscaledTileTransform = TileTransform; + UnscaledTileTransform.SetScale3D(FVector::OneVector); - TileLoc.X = FMath::RoundFromZero(RelTileLoc.X); - TileLoc.Y = FMath::RoundFromZero(RelTileLoc.Y); + // Calculate the tile's transform in quad space on the target landscape. + const FTransform LocalTileTransform = HACTransform * UnscaledTileTransform * TargetLandscape->GetTransform().Inverse(); + + const FVector LocalPos = LocalTileTransform.GetLocation(); + TargetMinX += LocalPos.X; + TargetMinY += LocalPos.Y; + + // The layer location will be offset / drawn at the "min" location (on the target landscape) in quad space + const FVector LayerLoc = FVector(TargetMinX, TargetMinY, 0.f); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Sections per component: %d"), (TargetLandscapeInfo->ComponentNumSubsections)); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Target Quads per component: %d"), (TargetLandscapeInfo->ComponentSizeQuads)); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Relative Tile Position: %s"), *(RelTileLoc.ToString())); + FIntPoint TargetTileLoc; + + TargetTileLoc.X = FMath::RoundToInt(LayerLoc.X); + TargetTileLoc.Y = FMath::RoundToInt(LayerLoc.Y); FVector TileMin, TileMax; - TileMin.X = TileLoc.X; - TileMin.Y = TileLoc.Y; - TileMax.X = TileLoc.X + LandscapeTileSizeInfo.UnrealSizeX - 1; - TileMax.Y = TileLoc.Y + LandscapeTileSizeInfo.UnrealSizeY - 1; + TileMin.X = TargetTileLoc.X; + TileMin.Y = TargetTileLoc.Y; + TileMax.X = TargetTileLoc.X + LandscapeTileSizeInfo.UnrealSizeX - 1; + TileMax.Y = TargetTileLoc.Y + LandscapeTileSizeInfo.UnrealSizeY - 1; TileMin.Z = TileMax.Z = 0.f; - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Src Landscape Transform: %s"), *(SrcLandscapeTransform.ToString())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Src Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); - FTransform DestLandscapeTransform = TargetLandscapeProxy->LandscapeActorToWorld(); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Landscape Transform: %s"), *(DestLandscapeTransform.ToString())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Actor Transform: %s"), *(TargetLandscape->GetTransform().ToString())); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); - // NOTE: we don't manually inject a tile number in the object name. This should // already be encoded in the TileName string. FHoudiniPackageParams TilePackageParams = InPackageParams; @@ -1472,14 +1871,19 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* // Look for all the layers/masks corresponding to the current heightfield. TArray< const FHoudiniGeoPartObject* > FoundLayers; - FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, FoundLayers); + FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(InOutput, *Heightfield, bHasEditLayers, EditLayerName, FoundLayers); HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Found %d output layers."), FoundLayers.Num()); + + // If we are operating in EditLayer mode, then any layers we create or manage should + // have their bNoWeightBlend set to true otherwise all the paint layers will act as additive + // which, most of the time, is not what we expect. + const bool bDefaultNoWeightBlend = bHasEditLayers ? true : false; // Get the updated layers. TArray LayerInfos; - if (!CreateOrUpdateLandscapeLayers(FoundLayers, *Heightfield, LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, - LayerMinimums, LayerMaximums, LayerInfos, false, + if (!CreateOrUpdateLandscapeLayerData(FoundLayers, *Heightfield, LandscapeTileSizeInfo.UnrealSizeX, LandscapeTileSizeInfo.UnrealSizeY, + LayerMinimums, LayerMaximums, LayerInfos, false, bDefaultNoWeightBlend, TilePackageParams, LayerPackageParams, OutCreatedPackages)) @@ -1487,49 +1891,9 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Generated %d layer infos."), LayerInfos.Num()); - // Collect existing layers on the landscape + // Update landscape edit layers to match layer infos TMap ExistingLayers; - int32 NumTargetLayers = TargetLandscape->EditorLayerSettings.Num(); - for(int32 LayerIndex = 0; LayerIndex < NumTargetLayers; LayerIndex++) - { - FLandscapeEditorLayerSettings& Settings = TargetLandscape->EditorLayerSettings[LayerIndex]; - if (!Settings.LayerInfoObj) - continue; - ExistingLayers.Add(Settings.LayerInfoObj->LayerName, LayerIndex); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Found existing landscape material layer: %s"), *(Settings.LayerInfoObj->LayerName.ToString())); - } - - bool bLayerHasChanged = false; - for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) - { - // Ensure weight blending is disabled for all layers coming from Houdini otherwise material layer outputs - // won't blend correctly on landscapes in Editable Layer mode. - InLayerInfo.LayerInfo->bNoWeightBlend = true; - - if (ExistingLayers.Contains(InLayerInfo.LayerName)) - { - - int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); - // NOTE: If we hot-swap existing Layer Info objects here, it leads to errors about landscape drawing resources that can't be released. - // For now, just modify any existing layers in place until we can figure out how to properly swap Layer Info objects. - - // // The landscape already contains this layer. Ensure it is pointing to the correct layer info object. - // bLayerHasChanged = TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj != InLayerInfo.LayerInfo; - // if (bLayerHasChanged) - // { - // HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Updating existing layer: %s"), *(InLayerInfo.LayerName.ToString())); - // TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj = InLayerInfo.LayerInfo; - // } - TargetLandscape->EditorLayerSettings[LayerIndex].LayerInfoObj->bNoWeightBlend = true; - } - else - { - // Landscape does not contain this layer. Add it. - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Adding new layer: %s"), *(InLayerInfo.LayerName.ToString())); - TargetLandscape->EditorLayerSettings.Add(FLandscapeEditorLayerSettings(InLayerInfo.LayerInfo)); - bLayerHasChanged = true; - } - } + UpdateLandscapeMaterialLayers(TargetLandscape, LayerInfos, ExistingLayers, bLayerNoWeightBlend, bHasEditLayers, EditLayerName); // Clear layers if (!ClearedLayers.Contains(EditableLayerName)) @@ -1539,7 +1903,6 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* // Attribute: unreal_landscape_editlayer_clear // --------------------------------------------- // Check whether we should clear the target edit layer. - TArray IntData; IntData.Empty(); if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger( GeoId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR, @@ -1555,7 +1918,7 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* { if (TargetLayer) { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Clearing layer heightmap: %s"), *EditableLayerName); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Clearing layer: %s"), *EditableLayerName); ClearedLayers.Add(EditableLayerName); // Clear the heightmap @@ -1566,8 +1929,7 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* { if (!ExistingLayers.Contains(InLayerInfo.LayerName)) continue; - - int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); + TargetLandscape->ClearPaintLayer(TargetLayer->Guid, InLayerInfo.LayerInfo); } } @@ -1593,8 +1955,9 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) { HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Trying to draw on layer: %s"), *(InLayerInfo.LayerName.ToString())); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); - if (InLayerInfo.LayerInfo && InLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + if (InLayerInfo.LayerInfo && InLayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) { // NOTE: AProxyLandscape::VisibilityLayer is a STATIC property (Info objects is being shared by ALL landscapes). Don't try to update / replace it. FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, ALandscapeProxy::VisibilityLayer); @@ -1606,13 +1969,16 @@ bool FHoudiniLandscapeTranslator::OutputLandscape_EditableLayer(UHoudiniOutput* continue; int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); - FLandscapeEditorLayerSettings& CurLayer = TargetLandscape->EditorLayerSettings[LayerIndex]; - // Draw on the current layer, if it is valid. - if (CurLayer.LayerInfoObj) + if (TargetLandscape->EditorLayerSettings.IsValidIndex(LayerIndex)) { - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Drawing using Alpha accessor. Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); - FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, CurLayer.LayerInfoObj); - AlphaAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, InLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + FLandscapeEditorLayerSettings& CurLayer = TargetLandscape->EditorLayerSettings[LayerIndex]; + // Draw on the current layer, if it is valid. + if (CurLayer.LayerInfoObj) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Drawing using Alpha accessor. Dest Region: %f, %f, -> %f, %f"), TileMin.X, TileMin.Y, TileMax.X, TileMax.Y); + FAlphamapAccessor AlphaAccessor(TargetLandscapeInfo, CurLayer.LayerInfoObj); + AlphaAccessor.SetData(TileMin.X, TileMin.Y, TileMax.X, TileMax.Y, InLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } } } } @@ -1728,42 +2094,187 @@ FHoudiniLandscapeTranslator::IsLandscapeTileCompatible( } -bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, LandscapeActorType ActorType) -{ - if (!IsValid(Actor)) - return false; +bool FHoudiniLandscapeTranslator::IsLandscapeTypeCompatible(const AActor* Actor, LandscapeActorType ActorType) +{ + if (!IsValid(Actor)) + return false; + + switch (ActorType) + { + case LandscapeActorType::LandscapeActor: + return Actor->IsA(); + break; + case LandscapeActorType::LandscapeStreamingProxy: + return Actor->IsA(); + break; + default: + break; + } + + return false; +} + +bool FHoudiniLandscapeTranslator::PopulateLandscapeExtents(FHoudiniLandscapeExtent& Extent, + const ULandscapeInfo* LandscapeInfo) +{ + if (LandscapeInfo->GetLandscapeExtent(Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY)) + { + Extent.ExtentsX = Extent.MaxX - Extent.MinX; + Extent.ExtentsY = Extent.MaxY - Extent.MinY; + Extent.bIsCached = true; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Cached extent: %d, %d -> %d, %d"), Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY); + + return true; + } + return false; +} + +bool FHoudiniLandscapeTranslator::UpdateLandscapeMaterialLayers(ALandscape* InLandscape, + const TArray& LayerInfos, + TMap& OutPaintLayers, + bool bNoWeightBlend, + bool bHasEditLayers, + const FName& EditLayerName) +{ + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Num Layer Infos: %d"), (LayerInfos.Num())); + + check(InLandscape); + + ULandscapeInfo* LandscapeInfo = InLandscape->GetLandscapeInfo(); + + FGuid LayerGuid; + if (bHasEditLayers) + { + const FLandscapeLayer* Layer = InLandscape->GetLayer(EditLayerName); + if (Layer) + { + LayerGuid = Layer->Guid; + } + } + + // If we are dealing with edit layers we need Lock the landscape edit layer, i.e., if the Guid is valid. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Scope locking edit layer: %s (%s)"), *(EditLayerName.ToString()), *(LayerGuid.ToString())); + FScopedSetLandscapeEditingLayer Scope(InLandscape, LayerGuid, [=] { InLandscape->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); }); + + OutPaintLayers.Empty(); + GetLandscapePaintLayers(InLandscape, OutPaintLayers); + + bool bLayerHasChanged = false; + for (const FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Processing Layer Info: %s, bNoWeightBlend: %d"), *(InLayerInfo.LayerName.ToString()), bNoWeightBlend); + + if (InLayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + // Visibility layers should always have this set to true. + InLayerInfo.LayerInfo->bNoWeightBlend = true; + continue; + } + else + { + // Any other layers use the incoming default. + // TODO: Override the bNoWeightBlend value from user attribute on the geo. + InLayerInfo.LayerInfo->bNoWeightBlend = bNoWeightBlend; + } + + // The following layer adding / replacing code have been referenced from: + // - ALandscapeProxy::CreateLayerInfo(...) + // - FLandscapeEditorCustomNodeBuilder_TargetLayers::OnTargetLayerSetObject(...) + + int32 LayerInfoIndex = LandscapeInfo->GetLayerInfoIndex(InLayerInfo.LayerName); + if (LayerInfoIndex == INDEX_NONE) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Creating new LandscapeLayerInfo: %s"), *(InLayerInfo.LayerName.ToString())); + // The material layer does not yet exist + LayerInfoIndex = LandscapeInfo->Layers.Add(FLandscapeInfoLayerSettings(InLayerInfo.LayerInfo, InLandscape)); + LandscapeInfo->CreateLayerEditorSettingsFor(InLayerInfo.LayerInfo); + bLayerHasChanged = true; + } + else + { + FLandscapeInfoLayerSettings& LayerSettings = LandscapeInfo->Layers[LayerInfoIndex]; + if (!LayerSettings.LayerInfoObj) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Setting new LandscapeLayerInfo on existing layer: %s"), *(InLayerInfo.LayerName.ToString())); + LayerSettings.LayerInfoObj = InLayerInfo.LayerInfo; + LandscapeInfo->CreateLayerEditorSettingsFor(InLayerInfo.LayerInfo); + bLayerHasChanged = true; + } + else if (LayerSettings.LayerInfoObj != InLayerInfo.LayerInfo) + { + // Existing material layer has mismatching Layer Info object. Update it. + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::UpdateLandscapeEditLayers] Replacing existing LandscapeLayerInfo: %s"), *(InLayerInfo.LayerName.ToString())); + LandscapeInfo->ReplaceLayer(LayerSettings.LayerInfoObj, InLayerInfo.LayerInfo); + LayerSettings.LayerInfoObj = InLayerInfo.LayerInfo; + bLayerHasChanged = true; + } + } + + if (LayerInfoIndex != INDEX_NONE) + { + OutPaintLayers.Add(InLayerInfo.LayerName, LayerInfoIndex); + } + } - switch (ActorType) + if (bLayerHasChanged) { - case LandscapeActorType::LandscapeActor: - return Actor->IsA(); - break; - case LandscapeActorType::LandscapeStreamingProxy: - return Actor->IsA(); - break; - default: - break; + LandscapeInfo->UpdateLayerInfoMap(); } - return false; + return bLayerHasChanged; } -bool FHoudiniLandscapeTranslator::PopulateLandscapeExtents(FHoudiniLandscapeExtent& Extent, - const ULandscapeInfo* LandscapeInfo) -{ - if (LandscapeInfo->GetLandscapeExtent(Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY)) - { - Extent.ExtentsX = Extent.MaxX - Extent.MinX; - Extent.ExtentsY = Extent.MaxY - Extent.MinY; - Extent.bIsCached = true; - - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Num proxies for landscape: %d"), LandscapeInfo->Proxies.Num()); - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::PopulateLandscapeExtents] Cached extent: %d, %d -> %d, %d"), Extent.MinX, Extent.MinY, Extent.MaxX, Extent.MaxY); - - return true; - } - return false; -} +// bool +// FHoudiniLandscapeTranslator::ClearEditLayer( +// ALandscape* InLandscape, +// const TArray& LayerInfos, +// TSet& ClearedLayers, +// bool bHasEditLayer, +// const FString& EditLayerName) +// { +// TArray IntData; +// // Clear layers +// if (!ClearedLayers.Contains(EditLayerName)) +// { +// bool bClearLayer = false; +// // --------------------------------------------- +// // Attribute: unreal_landscape_editlayer_clear +// // --------------------------------------------- +// // Check whether we should clear the target edit layer. +// IntData.Empty(); +// if (FHoudiniEngineUtils::HapiGetAttributeDataAsInteger(GeoId, PartId, +// HAPI_UNREAL_ATTRIB_LANDSCAPE_EDITLAYER_CLEAR, AttributeInfo, IntData, 1)) +// { +// if (IntData.Num() > 0) +// { +// bClearLayer = IntData[0] != 0; +// } +// } +// +// if (bClearLayer) +// { +// if (TargetLayer) +// { +// HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Clearing layer heightmap: %s"), *EditableLayerName); +// ClearedLayers.Add(EditLayerName); +// +// // Clear the heightmap +// TargetLandscape->ClearLayer(TargetLayer->Guid, nullptr, ELandscapeClearMode::Clear_Heightmap); +// +// // Clear the paint layers, but only the ones that are being output from Houdini. +// for (FLandscapeImportLayerInfo &InLayerInfo : LayerInfos) +// { +// if (!ExistingLayers.Contains(InLayerInfo.LayerName)) +// continue; +// +// int32 LayerIndex = ExistingLayers.FindChecked(InLayerInfo.LayerName); +// TargetLandscape->ClearPaintLayer(TargetLayer->Guid, InLayerInfo.LayerInfo); +// } +// } +// } +// } ALandscapeProxy* FHoudiniLandscapeTranslator::FindExistingLandscapeActor( @@ -1872,6 +2383,69 @@ ALandscapeProxy* FHoudiniLandscapeTranslator::FindTargetLandscapeProxy(const FSt return FHoudiniEngineUtils::FindActorInWorldByLabel(World, ActorName); } +bool FHoudiniLandscapeTranslator::GetEditLayersFromOutput(UHoudiniOutput* InOutput, TArray& InEditLayers) +{ + if (!InOutput) + return false; + + InEditLayers.Empty(); + bool bFoundEditLayers = false; + const TArray& GeoObjects = InOutput->GetHoudiniGeoPartObjects(); + for (const FHoudiniGeoPartObject& PartObj : GeoObjects) + { + if (PartObj.Type != EHoudiniPartType::Volume) + continue; + + if (!PartObj.bHasEditLayers) + continue; + + if (PartObj.VolumeLayerName.IsEmpty()) + continue; + + if (!InEditLayers.Contains(PartObj.VolumeLayerName)) + { + InEditLayers.Add(PartObj.VolumeLayerName); + bFoundEditLayers = true; + } + } + + return bFoundEditLayers; +} + +void FHoudiniLandscapeTranslator::GetLandscapePaintLayers(ALandscape* Landscape, TMap& OutEditLayers) +{ + check(Landscape); + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::GetLandscapePaintLayers] Collecting paint layers...")); + + if (Landscape->HasLayersContent()) + { + const int32 NumTargetLayers = Landscape->EditorLayerSettings.Num(); + for(int32 LayerIndex = 0; LayerIndex < NumTargetLayers; LayerIndex++) + { + FLandscapeEditorLayerSettings& Settings = Landscape->EditorLayerSettings[LayerIndex]; + if (!Settings.LayerInfoObj) + continue; + OutEditLayers.Add(Settings.LayerInfoObj->LayerName, LayerIndex); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::GetLandscapePaintLayers] Found existing landscape material layer: %s"), *(Settings.LayerInfoObj->LayerName.ToString())); + } + } + else + { + ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); + + const int32 NumLayers = LandscapeInfo->Layers.Num(); + for(int32 LayerIndex = 0; LayerIndex < NumLayers; LayerIndex++) + { + FLandscapeInfoLayerSettings& LayerSettings = LandscapeInfo->Layers[LayerIndex]; + // FLandscapeLayer* Layer = Landscape->GetLayer(LayerIndex); + OutEditLayers.Add(LayerSettings.LayerName, LayerIndex); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::GetLandscapePaintLayers] Found existing landscape material layer: %s"), *(LayerSettings.LayerName.ToString())); + } + } +} + + ALandscapeProxy* FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( UWorld* InWorld, @@ -1911,7 +2485,7 @@ FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( if (!OutActor) continue; - // If we were updating the input landscape before, but arent anymore, + // If we were updating the input landscape before, but aren't anymore, // we could still find it here in the output, ignore them now as we're only looking for previous output if (ValidLandscapes.Contains(OutActor)) continue; @@ -1920,7 +2494,7 @@ FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( // This is not the droid we're looking for continue; - if (OutActor->IsPendingKill()) + if (!IsValid(OutActor)) { FHoudiniEngineUtils::RenameToUniqueActor(OutActor, ActorName + "_pendingkill"); continue; @@ -1958,9 +2532,12 @@ FHoudiniLandscapeTranslator::FindExistingLandscapeActor_Temp( void FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( UHoudiniOutput* InOutput, + TArray &StaleOutputObjects, + TSet& OutActiveLandscapes, const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputTokens, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedMainLandscape, @@ -1969,9 +2546,9 @@ FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput( const EPackageMode InPackageMode) { if (InPackageMode == EPackageMode::Bake) - return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); + return SetLandscapeActorAsOutput_Bake(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, EditLayerName, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); else - return SetLandscapeActorAsOutput_Temp(InOutput, InAllInputLandscapes, OutputAttributes, OutputTokens, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); + return SetLandscapeActorAsOutput_Temp(InOutput, StaleOutputObjects, OutActiveLandscapes, InAllInputLandscapes, OutputAttributes, OutputTokens, EditLayerName, SharedLandscapeActor, SharedLandscapeActorParent, bCreatedMainLandscape, Identifier, LandscapeActor); } void @@ -1980,6 +2557,7 @@ FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputTokens, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedMainLandscape, @@ -1993,9 +2571,12 @@ FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Bake( void FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( UHoudiniOutput* InOutput, + TArray &StaleOutputObjects, + TSet& OutActiveLandscapes, const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputTokens, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedSharedLandscape, @@ -2010,64 +2591,76 @@ FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp( AttachActorToHAC(InOutput, SharedLandscapeActor); } - // TODO: The OutputObject cleanup being performed here should really be part of - // the output object itself (or at the very least be encapsulated in a reusable - // static function somewhere) so that individual output objects can be cleaned - // when necessary without having to duplicate code when its needed. - - // Cleanup any stale output objects + // Since this function will be called multiple times for the same Output (but different output objects) + // we can't perform any stale object cleanup here since we don't what the other calls are going to generate. + // Instead, stale output tracking and cleanup is by caller of the OutputLandscape_* functions. All we're doing + // here is removing objects from the StaleObjects array that we know for a fact is not stale. + TMap& Outputs = InOutput->GetOutputObjects(); - TArray StaleOutputs; + for (auto& Elem : Outputs) { UHoudiniLandscapePtr* LandscapePtr = nullptr; - bool bIsStale = false; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp] Checking output: %s"), *Elem.Key.PartName); if (!(Elem.Key == Identifier)) { - // Identifiers doesn't match so this is definitely a stale output. - StaleOutputs.Add(Elem.Key); - bIsStale = true; + // Output identifiers doesn't match so we can't remove this from the stale objects. + continue; } - LandscapePtr = Cast(Elem.Value.OutputObject); - if (LandscapePtr) - { - ALandscapeProxy* LandscapeProxy = LandscapePtr->GetRawPtr(); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp] Removing from stale outputs ... ")); + // Identifiers for output object match. Not stale. + StaleOutputObjects.Remove(Elem.Key); + } - if (LandscapeProxy) + // Send a landscape pointer back to the Output Object for this landscape tile. + FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); + UHoudiniLandscapePtr* LandscapePtr = Cast(OutputObj.OutputObject); + + if (LandscapePtr) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp] LandscapePtr: %s"), *LandscapePtr->GetFullName()); + // If we have a Landscape ptr, ensure the landscape actors match. Mismatching actors should be destroyed. + ALandscapeProxy* Proxy = LandscapePtr->GetRawPtr(); + if (Proxy) + { + if (Proxy != LandscapeActor) { - // We shouldn't destroy any input landscape, - // or the landscape that we are currently trying to set as output.. - if (!InAllInputLandscapes.Contains(LandscapeProxy) && (LandscapeProxy != LandscapeActor)) - { - // This landscape proxy either doesn't match the landscape identifier - // or it doesn't match the actor we're about to output. Either way, - // get rid of it. - LandscapeProxy->Destroy(); - } + Proxy->Destroy(); + LandscapePtr->GetSoftPtr().Reset(); } } - - if (bIsStale) - { - Elem.Value.OutputObject = nullptr; - } } - - for (FHoudiniOutputObjectIdentifier& StaleOutput : StaleOutputs) + + if (!IsValid(LandscapePtr)) { - Outputs.Remove(StaleOutput); + // We don't have a valid landscape ptr. Create a new one. + LandscapePtr = NewObject(InOutput); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::SetLandscapeActorAsOutput_Temp] Creating new UHoudiniLandscapePtr for identifier: %s"), *Identifier.PartName); } - - - // Send a landscape pointer back to the Output Object for this landscape tile. - FHoudiniOutputObject& OutputObj = InOutput->GetOutputObjects().FindOrAdd(Identifier); - UHoudiniLandscapePtr* LandscapePtr = NewObject(InOutput); + + check(LandscapePtr); + + // Update the Landscape Ptr. LandscapePtr->SetSoftPtr(LandscapeActor); + LandscapePtr->EditLayerName = EditLayerName; + OutputObj.OutputObject = LandscapePtr; OutputObj.CachedAttributes = OutputAttributes; OutputObj.CachedTokens = OutputTokens; + + // Be sure to track the landscapes that we are using for this tile/layer to ensure + // that they don't get deleted when stale outputs are being processed higher up the call stack. + if (LandscapeActor) + { + OutActiveLandscapes.Add(LandscapeActor); + } + + if (SharedLandscapeActor) + { + OutActiveLandscapes.Add(SharedLandscapeActor); + } } @@ -2117,7 +2710,8 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( FTransform& LandscapeTransform, const bool NoResize, const bool bOverrideZScale, - const float CustomZScale) + const float CustomZScale, + const bool bIsAdditive) { IntHeightData.Empty(); LandscapeTransform.SetIdentity(); @@ -2144,6 +2738,9 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( FTransform CurrentVolumeTransform = HeightfieldVolumeInfo.Transform; + // Zero value for landscape texture data + const uint16 LandscapeZeroValue = LandscapeDataAccess::GetTexHeight(0.f); + // The ZRange in Houdini (in m) double MeterZRange = (double)(FloatMax - FloatMin); @@ -2154,9 +2751,14 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); if (HoudiniRuntimeSettings && HoudiniRuntimeSettings->MarshallingLandscapesUseFullResolution) DigitZRange = dUINT16_MAX - 1.0; - + // If we are not using the full range, we need to center the digit values so the terrain can be edited up and down - double DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); + double DigitCenterOffset = 0; + + if (!bIsAdditive) + { + DigitCenterOffset = FMath::FloorToDouble((dUINT16_MAX - DigitZRange) / 2.0); + } // The factor used to convert from Houdini's ZRange to the desired digit range double ZSpacing = (MeterZRange != 0.0) ? (DigitZRange / MeterZRange) : 0.0; @@ -2194,17 +2796,24 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( ZSpacing = ((double)DigitZRange) / MeterZRange; } + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] bIsAdditive: %d"), bIsAdditive); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] CustomZScale: %f"), CustomZScale); HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] DigitCenterOffset: %f"), DigitZRange); HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] DigitZRange: %f"), DigitZRange); HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] MeterZRange: %f"), MeterZRange); HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] ZSpacing: %f"), ZSpacing); HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Volume YScale: %f"), CurrentVolumeTransform.GetScale3D().Y); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Float Range: %f - %f"), FloatMin, FloatMax); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Zero Value: %d"), LandscapeZeroValue); // Converting the data from Houdini to Unreal // For correct orientation in unreal, the point matrix has to be transposed. IntHeightData.SetNumUninitialized(SizeInPoints); - + int32 nUnreal = 0; + bool bValueClippedMin = false; + bool bValueClippedMax = false; + for (int32 nY = 0; nY < HoudiniYSize; nY++) { for (int32 nX = 0; nX < HoudiniXSize; nX++) @@ -2213,14 +2822,40 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( int32 nHoudini = nY + nX * HoudiniYSize; // Get the double values in [0 - ZRange] - double DoubleValue = (double)HeightfieldFloatValues[nHoudini] - (double)FloatMin; + double DoubleValue = (double)HeightfieldFloatValues[nHoudini]; + + // NOTE: Additive (edit) layers should not have their values offset, but they + // should be scaled using the same zspacing values as the base layer on the target landscape. + if (!bIsAdditive) + { + DoubleValue -= (double)FloatMin; + } // Then convert it to [0 - DesiredRange] and center it DoubleValue = DoubleValue * ZSpacing + DigitCenterOffset; - IntHeightData[nUnreal++] = FMath::RoundToInt(DoubleValue); + + if (bIsAdditive) + { + DoubleValue += LandscapeZeroValue; + } + bValueClippedMin = bValueClippedMin || (DoubleValue < 0); + bValueClippedMax = bValueClippedMax || (DoubleValue >= DigitZRange); + + const int32 IntValue = FMath::RoundToInt(DoubleValue); + IntHeightData[nUnreal++] = FMath::Clamp(IntValue, 0, FMath::RoundToInt(DigitZRange)); } } + if (bValueClippedMin) + { + HOUDINI_LOG_WARNING(TEXT("Heightfield values exceeded minimum range. Consider lowering `unreal_landscape_layer_min`.")); + } + + if (bValueClippedMax) + { + HOUDINI_LOG_WARNING(TEXT("Heightfield values exceeded range. Consider increasing `unreal_landscape_layer_max`.")); + } + //-------------------------------------------------------------------------------------------------- // 2. Resample / Pad the int data so that if fits unreal size requirements //-------------------------------------------------------------------------------------------------- @@ -2283,9 +2918,14 @@ FHoudiniLandscapeTranslator::ConvertHeightfieldDataToLandscapeData( // ( float and int32 are used for this because 0 might be out of the landscape Z range! // when using the full range, this would cause an overflow for a uint16!! ) float HoudiniZeroValueInDigit = (float)FMath::RoundToInt((0.0 - (double)FloatMin) * ZSpacing + DigitCenterOffset); - float ZOffset = -(HoudiniZeroValueInDigit - 32768.0f) / 128.0f * LandscapeScale.Z; + float ZOffset = -(HoudiniZeroValueInDigit - LandscapeZeroValue ) / 128.0f * LandscapeScale.Z; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Houdini Zero Digit Value: %f"), HoudiniZeroValueInDigit); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] Z Offset: %f"), ZOffset); + LandscapePosition.Z += ZOffset; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[ConvertHeightfieldDataToLandscapeData] LandscapePosition.Z: %f"), LandscapePosition.Z); // If we have padded the data when resizing the landscape, we need to offset the position because of // the added values on the topLeft Corner of the Landscape @@ -2636,9 +3276,12 @@ FHoudiniLandscapeTranslator::CalcLandscapeSizeFromHeightfieldSize( } const FHoudiniGeoPartObject* -FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InOutput) +FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput( + UHoudiniOutput* InOutput, + const bool bMatchEditLayer, + const FName& EditLayerName) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return nullptr; if (InOutput->GetHoudiniGeoPartObjects().Num() < 1) @@ -2653,6 +3296,15 @@ FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InO if (!CurVolumeInfo.Name.Contains("height")) continue; + if (bMatchEditLayer) + { + if (!HGPO.bHasEditLayers) + continue; + FName LayerName(HGPO.VolumeLayerName); + if (!LayerName.IsEqual(EditLayerName)) + continue; + } + // We're only handling single values for now if (CurVolumeInfo.TupleSize != 1) { @@ -2681,7 +3333,12 @@ FHoudiniLandscapeTranslator::GetHoudiniHeightFieldFromOutput(UHoudiniOutput* InO } void -FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& Heightfield, TArray< const FHoudiniGeoPartObject* >& FoundLayers) +FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput( + const UHoudiniOutput* InOutput, + const FHoudiniGeoPartObject& Heightfield, + const bool bMatchEditLayer, + const FName& EditLayerName, + TArray< const FHoudiniGeoPartObject* >& FoundLayers) { FoundLayers.Empty(); @@ -2715,6 +3372,16 @@ FHoudiniLandscapeTranslator::GetHeightfieldsLayersFromOutput(const UHoudiniOutpu if (NodeId == -1 || NodeId != HeightFieldNodeId) continue; + if (bMatchEditLayer) + { + // If this layer does not match the incoming edit layer, ignore it. + FString CurrentLayerName; + FHoudiniEngineUtils::GetEditLayerName(HoudiniGeoPartObject.GeoId, HoudiniGeoPartObject.PartId, CurrentLayerName); + const FName CurrentLayerFName(CurrentLayerName); + if (!CurrentLayerFName.IsEqual(EditLayerName)) + continue; + } + if (bParentHeightfieldHasTile) { int32 CurrentTile = -1; @@ -2871,6 +3538,12 @@ FHoudiniLandscapeTranslator::GetNonWeightBlendedLayerNames(const FHoudiniGeoPart bool FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& LayerGeoPartObject) { + if (LayerGeoPartObject.VolumeName.Equals(HAPI_UNREAL_VISIBILITY_LAYER_NAME, ESearchCase::IgnoreCase)) + { + // Visibility layers should always be treated as Unit layers. + return true; + } + // Check the attribute exists on primitive or detail HAPI_AttributeOwner Owner = HAPI_ATTROWNER_INVALID; if (FHoudiniEngineUtils::HapiCheckAttributeExists(LayerGeoPartObject.GeoId, LayerGeoPartObject.PartId, HAPI_UNREAL_ATTRIB_UNIT_LANDSCAPE_LAYER, HAPI_ATTROWNER_PRIM)) @@ -2895,7 +3568,7 @@ FHoudiniLandscapeTranslator::IsUnitLandscapeLayer(const FHoudiniGeoPartObject& L } bool -FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( +FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayerData( const TArray& FoundLayers, const FHoudiniGeoPartObject& Heightfield, const int32& LandscapeXSize, const int32& LandscapeYSize, @@ -2903,6 +3576,7 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( const TMap& GlobalMaximums, TArray& OutLayerInfos, bool bIsUpdate, + bool bDefaultNoWeightBlending, const FHoudiniPackageParams& InTilePackageParams, const FHoudiniPackageParams& InLayerPackageParams, TArray& OutCreatedPackages @@ -2938,43 +3612,43 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( if (bIsUpdate && !LayerGeoPartObject->bHasGeoChanged) { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers]: Skipping geo part (no changes): %s, %s, %d, %d"), *(LayerGeoPartObject->VolumeName), *(LayerGeoPartObject->VolumeLayerName), LayerGeoPartObject->GeoId, LayerGeoPartObject->PartId); continue; } TArray FloatLayerData; float LayerMin = 0; float LayerMax = 0; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers]: Retrieving heightfield float data for geo part: %s, %s, %d, %d"), *(LayerGeoPartObject->VolumeName), *(LayerGeoPartObject->VolumeLayerName), LayerGeoPartObject->GeoId, LayerGeoPartObject->PartId); if (!FHoudiniLandscapeTranslator::GetHoudiniHeightfieldFloatData(LayerGeoPartObject, FloatLayerData, LayerMin, LayerMax)) continue; - // No need to create flat layers as Unreal will remove them afterwards.. - if (LayerMin == LayerMax) - continue; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers]: Layer Min/Max: %f, %f"), LayerMin, LayerMax); + + // NOTE: We don't want to just skip the processing of flat layers here because it is completely valid for layers to be flat but have non-zero values. + // Even if UE culls a flat (zero) layer, it is still important for any existing layer content to be properly cleared -- if we just skip processing on this + // layer we could end up with tiles containing stale data. + // Also, we do not want to simply *delete* this layer because we may be drawing zero values to a subregion on a landscape (we may not be looking at the data + // for the whole landscape layer here!!) so we cannot make any assumptions at this. Process the layer normally even if it only contains zero values. + + // // No need to create flat layers as Unreal will remove them afterwards.. + // if (LayerMin == LayerMax) + // { + // HOUDINI_LOG_WARNING(TEXT("[FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers] Ignoring flat heightfield: %s, %s (Min: %f, Max: %f)"), *(LayerGeoPartObject->VolumeName), *(LayerGeoPartObject->VolumeLayerName), LayerMin, LayerMax); + // continue; + // } const FHoudiniVolumeInfo& LayerVolumeInfo = LayerGeoPartObject->VolumeInfo; // Get the layer's name FString LayerName = LayerVolumeInfo.Name; const FString SanitizedLayerName = ObjectTools::SanitizeObjectName(LayerName); + const FName SanitizedLayerFName = FName(SanitizedLayerName); TilePackageParams.ObjectName = InTilePackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; LayerPackageParams.ObjectName = InLayerPackageParams.ObjectName + TEXT("_layer_") + SanitizedLayerName; - if (bExportTexture) - { - // Create a raw texture export of the layer on this tile - FString TextureName = TilePackageParams.ObjectName + "_raw"; - FHoudiniLandscapeTranslator::CreateUnrealTexture( - TilePackageParams, - TextureName, - LayerVolumeInfo.YLength, // Y and X inverted?? why? - LayerVolumeInfo.XLength, - FloatLayerData, - LayerMin, - LayerMax); - } - - // Check if that landscape layer has been marked as unit (range in [0-1] + // Check if that landscape layer has been marked as unit (range in [0-1]) if (IsUnitLandscapeLayer(*LayerGeoPartObject)) { LayerMin = 0.0f; @@ -2989,7 +3663,21 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( if (GlobalMinimums.Contains(LayerName)) LayerMin = GlobalMinimums[LayerName]; } - + + if (bExportTexture) + { + // Create a raw texture export of the layer on this tile + FString TextureName = TilePackageParams.ObjectName + "_raw"; + FHoudiniLandscapeTranslator::CreateUnrealTexture( + TilePackageParams, + TextureName, + LayerVolumeInfo.YLength, // Y and X inverted?? why? + LayerVolumeInfo.XLength, + FloatLayerData, + LayerMin, + LayerMax); + } + // Get the layer package path // FString LayerNameString = FString::Printf(TEXT("%s_%d"), LayerString.GetCharArray().GetData(), (int32)LayerGeoPartObject->PartId); // LayerNameString = UPackageTools::SanitizePackageName(LayerNameString); @@ -3000,20 +3688,34 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( // Creating the ImportLayerInfo and LayerInfo objects FLandscapeImportLayerInfo ImportLayerInfo(*LayerName); + // See if the user has assigned a layer info object via attribute UPackage * Package = nullptr; ULandscapeLayerInfoObject* LayerInfo = GetLandscapeLayerInfoForLayer(*LayerGeoPartObject, *LayerName); - if (!LayerInfo || LayerInfo->IsPendingKill()) + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[CreateOrUpdateLandscapeLayers] GetLandscapeLayerInfoForLayer. LayerName: %s."), *(LayerName)); + if (!IsValid(LayerInfo)) { // No assignment, try to find or create a landscape layer info object for that layer + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[CreateOrUpdateLandscapeLayers] No layer info. FindOrCreate layer info object...")); LayerInfo = FindOrCreateLandscapeLayerInfoObject(LayerName, LayerPackageParams.GetPackagePath(), LayerPackageParams.GetPackageName(), Package); } - if (!LayerInfo || LayerInfo->IsPendingKill()) + if (!IsValid(LayerInfo)) { continue; } + // Visibility are by default non weight blended + if (NonWeightBlendedLayerNames.Contains(LayerName) || SanitizedLayerFName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + LayerInfo->bNoWeightBlend = true; + } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[CreateOrUpdateLandscapeLayers] Setting bNoWeightBlend to %d on layer %s"), bDefaultNoWeightBlending, *(LayerInfo->LayerName.ToString())); + LayerInfo->bNoWeightBlend = bDefaultNoWeightBlending; + } + // Convert the float data to uint8 // HF masks need their X/Y sizes swapped if (!FHoudiniLandscapeTranslator::ConvertHeightfieldLayerToLandscapeLayer( @@ -3031,15 +3733,9 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( LayerInfo->LayerUsageDebugColor.B = (LayerMax - LayerMin) / 255.0f; LayerInfo->LayerUsageDebugColor.A = PI; - HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers] Processing layer: %s"), *(LayerName)); - - // Visibility are by default non weight blended - if (NonWeightBlendedLayerNames.Contains(LayerName) || LayerName.Equals(TEXT("visibility"), ESearchCase::IgnoreCase)) - LayerInfo->bNoWeightBlend = true; - else - LayerInfo->bNoWeightBlend = false; + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers] Processing layer: %s (bDefaultNoWeightBlend: %d"), *(LayerName), bDefaultNoWeightBlending); - if (!bIsUpdate && Package && !Package->IsPendingKill()) + if (!bIsUpdate && IsValid(Package)) { // Mark the package dirty... Package->MarkPackageDirty(); @@ -3065,7 +3761,7 @@ FHoudiniLandscapeTranslator::CreateOrUpdateLandscapeLayers( // See if there is a physical material assigned via attribute for that landscape layer UPhysicalMaterial* PhysMaterial = FHoudiniLandscapeTranslator::GetLandscapePhysicalMaterial(*LayerGeoPartObject); - if (PhysMaterial && !PhysMaterial->IsPendingKill()) + if (IsValid(PhysMaterial)) { LayerInfo->PhysMaterial = PhysMaterial; } @@ -3404,7 +4100,10 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( ALandscape* SharedLandscapeActor, UWorld* InWorld, ULevel* InLevel, - FHoudiniPackageParams InPackageParams) + FHoudiniPackageParams InPackageParams, + bool bHasEditLayers, + const FName& EditLayerName, + const FName& AfterLayerName) { if (!IsValid(InWorld)) return nullptr; @@ -3426,16 +4125,23 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( ALandscapeStreamingProxy* CachedStreamingProxyActor = nullptr; ALandscape* CachedLandscapeActor = nullptr; + + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Creating tile with layer: %s"), *(EditLayerName.ToString())); UWorld* NewWorld = nullptr; FString MapFileName; bool bBroadcastMaterialUpdate = false; + bool bRenameDefaultEditLayer = false; //... Create landscape tile ...// { // We need to create the landscape now and assign it a new GUID so we can create the LayerInfos if (ActorType == LandscapeActorType::LandscapeStreamingProxy) { - CachedStreamingProxyActor = InWorld->SpawnActor(); + // Set the level to spawn in to InLevel + FActorSpawnParameters SpawnParameters; + if (IsValid(InLevel)) + SpawnParameters.OverrideLevel = InLevel; + CachedStreamingProxyActor = InWorld->SpawnActor(SpawnParameters); if (CachedStreamingProxyActor) { check(SharedLandscapeActor); @@ -3457,7 +4163,11 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( else { // Create a normal landscape actor - CachedLandscapeActor = InWorld->SpawnActor(); + // Set the level to spawn in to InLevel + FActorSpawnParameters SpawnParameters; + if (IsValid(InLevel)) + SpawnParameters.OverrideLevel = InLevel; + CachedLandscapeActor = InWorld->SpawnActor(SpawnParameters); if (CachedLandscapeActor) { CachedLandscapeActor->PreEditChange(nullptr); @@ -3465,8 +4175,10 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( CachedLandscapeActor->LandscapeMaterial = LandscapeMaterial; CachedLandscapeActor->LandscapeHoleMaterial = LandscapeHoleMaterial; CachedLandscapeActor->bCastStaticShadow = false; + CachedLandscapeActor->bCanHaveLayersContent = bHasEditLayers; bBroadcastMaterialUpdate = true; LandscapeTile = CachedLandscapeActor; + bRenameDefaultEditLayer = true; } } } @@ -3475,11 +4187,16 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( if (!LandscapeTile) return nullptr; + CachedLandscapeActor = LandscapeTile->GetLandscapeActor(); + + check(CachedLandscapeActor); + CachedLandscapeActor->bCanHaveLayersContent = bHasEditLayers; + // Only import non-visibility layers. Visibility will be handled explicitly. TArray CustomImportLayerInfos; for (const FLandscapeImportLayerInfo& LayerInfo : ImportLayerInfos) { - if (LayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + if (LayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) continue; CustomImportLayerInfos.Add(LayerInfo); } @@ -3536,6 +4253,16 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( ULandscapeInfo::RecreateLandscapeInfo(InWorld, true); } + ALandscape* TargetLandscape = LandscapeTile->GetLandscapeActor(); + check(TargetLandscape); + + if (bHasEditLayers) + { + int32 CurrentLayerIndex = FindOrCreateEditLayer(TargetLandscape, EditLayerName, AfterLayerName); + SetActiveLandscapeLayer(TargetLandscape, CurrentLayerIndex); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[HoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Setting active landscape layer: %d"), CurrentLayerIndex); + } + // Import tile data // The Import function will correctly compute the tile section locations. No need to set it explicitly. // TODO: Verify this with world composition!! @@ -3579,6 +4306,19 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( check(LandscapeInfo); + if (bHasEditLayers && bRenameDefaultEditLayer) + { + // If this is a new landscape tile, rename the default edit layer. + // We have to do this after the Import() since the landscape layers + // only get initialized during that call. + FLandscapeLayer* DefaultEditLayer = TargetLandscape->GetLayer(0); + if (DefaultEditLayer) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld] Updating default layer name to: %s"), *(EditLayerName.ToString())); + DefaultEditLayer->Name = EditLayerName; + } + } + // We updated the landscape tile section offsets (SetAbsoluteSectionBase) so // we need to recreate the landscape info for these changes reflect correctly in the ULandscapeInfo component keys. // Only then are we able to "blit" the new alpha data into the correct place on the landscape. @@ -3599,12 +4339,41 @@ FHoudiniLandscapeTranslator::CreateLandscapeTileInWorld( // ---------------------------------------------------- // Update the visibility mask / layer if we have any (TileImport does not update the visibility layer). - for (auto CurLayerInfo : ImportLayerInfos) { - if (CurLayerInfo.LayerInfo && CurLayerInfo.LayerName.ToString().Equals(TEXT("Visibility"), ESearchCase::IgnoreCase)) + FGuid LayerGuid; + ALandscape* LandscapeActor = LandscapeTile->GetLandscapeActor(); + + if (bHasEditLayers) + { + const FLandscapeLayer* Layer = LandscapeActor->GetLayer(EditLayerName); + LayerGuid = Layer->Guid; + } + + // If we are dealing with edit layers we need Lock the landscape edit layer, i.e., if the Guid is valid. + FScopedSetLandscapeEditingLayer Scope(LandscapeActor, LayerGuid, [=] { CachedLandscapeActor->RequestLayersContentUpdate(ELandscapeLayerUpdateMode::Update_All); }); + FLandscapeEditDataInterface LandscapeEdit(LandscapeInfo); + + for (auto CurLayerInfo : ImportLayerInfos) { - FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); - AlphaAccessor.SetData(DestMinX, DestMinY, DestMaxX, DestMaxY, CurLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + if (CurLayerInfo.LayerInfo && CurLayerInfo.LayerName.IsEqual(HAPI_UNREAL_VISIBILITY_LAYER_NAME)) + { + FAlphamapAccessor AlphaAccessor(LandscapeInfo, ALandscapeProxy::VisibilityLayer); + AlphaAccessor.SetData(DestMinX, DestMinY, DestMaxX, DestMaxY, CurLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + else + { + ULandscapeLayerInfoObject* LayerInfo = LandscapeInfo->GetLayerInfoByName(CurLayerInfo.LayerName); + if (!IsValid(LayerInfo)) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("Missing layer with: %s"), *(CurLayerInfo.LayerName.ToString())); + } + else + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("Drawing layer with normalization: %s"), *(CurLayerInfo.LayerName.ToString())); + FAlphamapAccessor AlphaAccessor(LandscapeInfo, LayerInfo); + AlphaAccessor.SetData(DestMinX, DestMinY, DestMaxX, DestMaxY, CurLayerInfo.LayerData.GetData(), ELandscapeLayerPaintingRestriction::None); + } + } } } @@ -3903,19 +4672,21 @@ FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& // See if package exists, if it does, reuse it bool bCreatedPackage = false; OutPackage = FindPackage(nullptr, *PackageFullName); - if (!OutPackage || OutPackage->IsPendingKill()) + if (!IsValid(OutPackage)) { // We need to create a new package OutPackage = CreatePackage( *PackageFullName); bCreatedPackage = true; } - if (!OutPackage || OutPackage->IsPendingKill()) + if (!IsValid(OutPackage)) return nullptr; if (!OutPackage->IsFullyLoaded()) OutPackage->FullyLoad(); + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject] Find LayerInfoObject package: %s"), *(InPackageName)); + ULandscapeLayerInfoObject* LayerInfo = nullptr; if (!bCreatedPackage) { @@ -3923,8 +4694,9 @@ FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& LayerInfo = (ULandscapeLayerInfoObject*)StaticFindObjectFast(ULandscapeLayerInfoObject::StaticClass(), OutPackage, FName(*InPackageName)); } - if (!LayerInfo || LayerInfo->IsPendingKill()) + if (!IsValid(LayerInfo)) { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject] Could not find layer info. Creating new layer info package: %s"), *(InPackageName)); // Create a new LandscapeLayerInfoObject in the package LayerInfo = NewObject(OutPackage, FName(*InPackageName), RF_Public | RF_Standalone /*| RF_Transactional*/); @@ -3932,7 +4704,7 @@ FHoudiniLandscapeTranslator::FindOrCreateLandscapeLayerInfoObject(const FString& FAssetRegistryModule::AssetCreated(LayerInfo); } - if (LayerInfo && !LayerInfo->IsPendingKill()) + if (IsValid(LayerInfo)) { LayerInfo->LayerName = FName(*InLayerName); @@ -4058,7 +4830,7 @@ bool FHoudiniLandscapeTranslator::UpdateGenericPropertiesAttributes( UObject* InObject, const TArray& InAllPropertyAttributes) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; // Iterate over the found Property attributes @@ -4103,7 +4875,7 @@ FHoudiniLandscapeTranslator::BackupLandscapeToImageFiles(const FString& BaseName FName CurrentLayerName = LandscapeInfo->Layers[LayerIndex].GetLayerName(); //ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->GetLayerInfoByName(CurrentLayerName, Landscape); ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeInfo->Layers[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + if (!IsValid(CurrentLayerInfo)) continue; FString LayerSave = BaseName + CurrentLayerName.ToString() + TEXT(".png"); @@ -4137,7 +4909,7 @@ FHoudiniLandscapeTranslator::RestoreLandscapeFromImageFiles(ALandscapeProxy* Lan for (int LayerIndex = 0; LayerIndex < LandscapeProxy->EditorLayerSettings.Num(); LayerIndex++) { ULandscapeLayerInfoObject* CurrentLayerInfo = LandscapeProxy->EditorLayerSettings[LayerIndex].LayerInfoObj; - if (!CurrentLayerInfo || CurrentLayerInfo->IsPendingKill()) + if (!IsValid(CurrentLayerInfo)) continue; FString CurrentLayerName = CurrentLayerInfo->LayerName.ToString(); @@ -4267,7 +5039,7 @@ FHoudiniLandscapeTranslator::ImportLandscapeData( else { // We're importing a Landscape layer - if (!LayerInfoObject || LayerInfoObject->IsPendingKill()) + if (!IsValid(LayerInfoObject)) return false; const ILandscapeWeightmapFileFormat* WeightmapFormat = LandscapeEditorModule.GetWeightmapFormatByExtension(*FPaths::GetExtension(Filename, true)); @@ -4391,7 +5163,7 @@ FHoudiniLandscapeTranslator::CreateUnrealTexture( FString CreatedPackageName; UPackage* Package = MyPackageParams.CreatePackageForObject(CreatedPackageName); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return nullptr; // Create new texture object. @@ -4504,7 +5276,7 @@ FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPart if (AttributeValues.Num() > 0) { ULandscapeLayerInfoObject* FoundLayerInfo = LoadObject(nullptr, *AttributeValues[0], nullptr, LOAD_NoWarn, nullptr); - if (!FoundLayerInfo || FoundLayerInfo->IsPendingKill()) + if (!IsValid(FoundLayerInfo)) return nullptr; // The layer info's name must match this layer's name or Unreal will not like this! @@ -4520,5 +5292,67 @@ FHoudiniLandscapeTranslator::GetLandscapeLayerInfoForLayer(const FHoudiniGeoPart return nullptr; } +int32 FHoudiniLandscapeTranslator::FindOrCreateEditLayer(ALandscape* Landscape, FName LayerName, FName AfterLayerName) +{ + check(Landscape); + check(Landscape->bCanHaveLayersContent); + + if (LayerName.IsNone()) + { + return INDEX_NONE; + } + + Landscape->SetEditingLayer(FGuid()); // Clear edit layer before adding a new one! + int32 LayerIndex = Landscape->GetLayerIndex(LayerName); + if (LayerIndex == INDEX_NONE) + { + // Create a new edit layer + LayerIndex = Landscape->CreateLayer(LayerName); + } + + if (LayerIndex == INDEX_NONE) + return INDEX_NONE; // Something went wrong. + + if (!AfterLayerName.IsNone()) + { + // If we have an "after layer", move the edit layer into position. + int32 NewLayerIndex = Landscape->GetLayerIndex(AfterLayerName); + if (NewLayerIndex != INDEX_NONE && LayerIndex != NewLayerIndex) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[OutputLandscape_EditableLayer] Moving layer from %d to %d"), LayerIndex, NewLayerIndex); + if (NewLayerIndex < LayerIndex) + { + NewLayerIndex += 1; + } + Landscape->ReorderLayer(LayerIndex, NewLayerIndex); + // Ensure we have the correct layer/index + LayerIndex = Landscape->GetLayerIndex(LayerName); + } + } + + return LayerIndex; +} + +bool FHoudiniLandscapeTranslator::SetActiveLandscapeLayer(ALandscape* Landscape, int32 LayerIndex) +{ + if (!Landscape->bCanHaveLayersContent) + return false; + + if (LayerIndex == INDEX_NONE) + { + // If the edit layer could not be found, or created, revert to the 'default' layer. + LayerIndex = 0; + } + + FLandscapeLayer* Layer = Landscape->GetLayer(LayerIndex); + if (Layer) + { + HOUDINI_LANDSCAPE_MESSAGE(TEXT("[SetActiveLandscapeLayer] Set Editing Layer from index: %d"), LayerIndex); + Landscape->SetEditingLayer(Layer->Guid); + return true; + } + + return false; +} diff --git a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h index 2bd8c3c75..50858ab2c 100644 --- a/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniLandscapeTranslator.h @@ -67,7 +67,7 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator TSet& ClearedLayers, TArray& OutCreatedPackages); - static bool OutputLandscape_Temp( + static bool OutputLandscape_Generate( UHoudiniOutput* InOutput, TArray>& CreatedUntrackedActors, TArray& InputLandscapesToUpdate, @@ -80,13 +80,37 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator FHoudiniLandscapeExtent& LandscapeExtent, FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + TSet& ClearedLayers, FHoudiniPackageParams InPackageParams, TArray& OutCreatedPackages); + static bool OutputLandscape_GenerateTile( + UHoudiniOutput* InOutput, + TArray& StaleOutputObjects, + TArray>& CreatedUntrackedActors, + TArray& InputLandscapesToUpdate, + const TArray& InAllInputLandscapes, + USceneComponent* SharedLandscapeActorParent, + const FString& DefaultLandscapeActorPrefix, + UWorld* World, + const TMap& LayerMinimums, + const TMap& LayerMaximums, + FHoudiniLandscapeExtent& LandscapeExtent, + FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, + FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, + FHoudiniPackageParams InPackageParams, + bool bHasEditLayers, + const FString& InEditLayerName, + const FName& InAfterLayerName, + const TArray& AllLayerNames, + TSet& ClearedLayers, + TArray& OutCreatedPackages, + TSet& OutActiveLandscapes); + // Outputting landscape as "editable layers" differs significantly from // landscape outputs in "temp mode". To avoid a bigger spaghetti mess, we're - // dealing with editable layers completely separately. - static bool OutputLandscape_EditableLayer( + // dealing with modifying existing edit layers completely separately. + static bool OutputLandscape_ModifyLayer( UHoudiniOutput* InOutput, TArray>& CreatedUntrackedActors, TArray& InputLandscapesToUpdate, @@ -100,6 +124,8 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator FHoudiniLandscapeTileSizeInfo& LandscapeTileSizeInfo, FHoudiniLandscapeReferenceLocation& LandscapeReferenceLocation, FHoudiniPackageParams InPackageParams, + const bool bHasEditLayers, + const FName& EditLayerName, TSet& ClearedLayers, TArray& OutCreatedPackages); @@ -119,7 +145,13 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator const FString& ActorName, UWorld* World, const TArray& LandscapeInputs - ); + ); + + // Iterate through the HGPO's and collect the (non-empty) edit layer names retrieved by the Houdini Output Translator. + // Return false if there are no edit layers (InEditLayers will also be emptied). + static bool GetEditLayersFromOutput(UHoudiniOutput* InOutput, TArray& InEditLayers); + + static void GetLandscapePaintLayers(ALandscape* Landscape, TMap& EditLayers); protected: @@ -144,6 +176,23 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator const ULandscapeInfo* LandscapeInfo ); + static bool UpdateLandscapeMaterialLayers( + ALandscape* InLandscape, + const TArray& LayerInfos, + TMap& OutPaintLayers, + bool bNoWeightBlend, + bool bHasEditLayers, + const FName& LayerName + ); + + // static bool ClearLandscapeLayers( + // ALandscape* InLandscape, + // const TArray& LayerInfos, + // TSet& ClearedLayers, + // bool bHasEditLayer, + // const FString& EditLayerName + // ); + /** * Find a ALandscapeProxy actor that can be reused. It is important * to note that the request landscape actor could not be found, @@ -183,9 +232,12 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator */ static void SetLandscapeActorAsOutput( UHoudiniOutput* InOutput, + TArray &StaleOutputObjects, + TSet& OutActiveLandscapes, const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputArguments, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedMainLandscape, @@ -198,6 +250,7 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputArguments, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedMainLandscape, @@ -206,9 +259,12 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator static void SetLandscapeActorAsOutput_Temp( UHoudiniOutput* InOutput, + TArray &StaleOutputObjects, + TSet& OutActiveLandscapes, const TArray& InAllInputLandscapes, const TMap& OutputAttributes, const TMap& OutputArguments, + const FName& EditLayerName, ALandscape* SharedLandscapeActor, USceneComponent* SharedLandscapeActorParent, bool bCreatedMainLandscape, @@ -237,12 +293,14 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator static const FHoudiniGeoPartObject* GetHoudiniHeightFieldFromOutput( - UHoudiniOutput* InOutput); + UHoudiniOutput* InOutput, + const bool bMatchEditLayer, + const FName& EditLayerName); static void GetHeightfieldsLayersFromOutput( - const UHoudiniOutput* InOutput, - const FHoudiniGeoPartObject& Heightfield, - TArray< const FHoudiniGeoPartObject* >& FoundLayers); + const ::UHoudiniOutput* InOutput, + const ::FHoudiniGeoPartObject& Heightfield, + const bool bMatchEditLayer, const ::FName& EditLayerName, TArray& FoundLayers); static bool GetHoudiniHeightfieldVolumeInfo(const FHoudiniGeoPartObject* HGPO, HAPI_VolumeInfo& VolumeInfo); @@ -271,7 +329,8 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator FTransform& LandscapeTransform, const bool NoResize = false, const bool bOverrideZScale = false, - const float CustomZScale = 100.f); + const float CustomZScale = 100.f, + const bool bIsAdditive = false); static bool ResizeHeightDataForLandscape( TArray& HeightData, @@ -282,7 +341,7 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator FVector& LandscapeResizeFactor, FVector& LandscapePositionOffset); - static bool CreateOrUpdateLandscapeLayers( + static bool CreateOrUpdateLandscapeLayerData( const TArray& FoundLayers, const FHoudiniGeoPartObject& HeightField, const int32& LandscapeXSize, @@ -291,6 +350,7 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator const TMap &GlobalMaximums, TArray& OutLayerInfos, bool bIsUpdate, + bool bDefaultNoWeightBlending, const FHoudiniPackageParams& InTilePackageParams, const FHoudiniPackageParams& InLayerPackageParams, TArray& OutCreatedPackages); @@ -373,7 +433,10 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator ALandscape* SharedLandscapeActor, // Landscape containing shared stated for streaming proxies UWorld* InWorld, // World in which to spawn ULevel* InLevel, // Level, contained in World, in which to spawn. - FHoudiniPackageParams InPackageParams); + FHoudiniPackageParams InPackageParams, + bool bHasEditLayers, + const FName& EditLayerName, + const FName& AfterLayerName); // Destroy the given landscape and all its proxies static void DestroyLandscape(ALandscape* Landscape); @@ -434,6 +497,10 @@ struct HOUDINIENGINE_API FHoudiniLandscapeTranslator static ULandscapeLayerInfoObject* GetLandscapeLayerInfoForLayer(const FHoudiniGeoPartObject& InLayerHGPO, const FName& InLayerName); + // Find or create the given layer. Optionally position it after the 'AfterLayerName'. + static int32 FindOrCreateEditLayer(ALandscape* Landscape, FName LayerName, FName AfterLayerName); + static bool SetActiveLandscapeLayer(ALandscape* Landscape, int32 LayerIndex); + private: static bool ImportLandscapeData( diff --git a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp index 4ccc62672..f4a1ad4b3 100644 --- a/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMaterialTranslator.cpp @@ -155,7 +155,7 @@ FHoudiniMaterialTranslator::CreateHoudiniMaterials( } bool bCreatedNewMaterial = false; - if (Material && !Material->IsPendingKill()) + if (IsValid(Material)) { // If the cached material exists and is up to date, we can reuse it. if (bCanReuseExistingMaterial) @@ -186,7 +186,7 @@ FHoudiniMaterialTranslator::CreateHoudiniMaterials( bCreatedNewMaterial = true; } - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) continue; // Get the asset name from the package params @@ -306,7 +306,7 @@ FHoudiniMaterialTranslator::CreateMaterialInstances( UMaterialInterface* CurrentSourceMaterialInterface = Cast( StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *CurrentSourceMaterial, nullptr, LOAD_NoWarn, nullptr)); - if (!CurrentSourceMaterialInterface || CurrentSourceMaterialInterface->IsPendingKill()) + if (!IsValid(CurrentSourceMaterialInterface)) { // Couldn't find the source material HOUDINI_LOG_WARNING(TEXT("Couldn't find the source material %s to create a material instance."), *CurrentSourceMaterial); @@ -587,7 +587,7 @@ FHoudiniMaterialTranslator::CreateUnrealTexture( const FString& TextureType, const FString& NodePath) { - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return nullptr; UTexture2D * Texture = nullptr; @@ -832,7 +832,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; HAPI_Result Result = HAPI_RESULT_SUCCESS; @@ -871,7 +871,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( MaterialExpression, UMaterialExpressionVectorParameter::StaticClass())); // If uniform color expression does not exist, create it. - if (!ExpressionConstant4Vector || ExpressionConstant4Vector->IsPendingKill()) + if (!IsValid(ExpressionConstant4Vector)) { ExpressionConstant4Vector = NewObject< UMaterialExpressionVectorParameter >( Material, UMaterialExpressionVectorParameter::StaticClass(), NAME_None, ObjectFlag); @@ -887,7 +887,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( MaterialExpression, UMaterialExpressionVertexColor::StaticClass())); // If vertex color expression does not exist, create it. - if (!ExpressionVertexColor || ExpressionVertexColor->IsPendingKill()) + if (!IsValid(ExpressionVertexColor)) { ExpressionVertexColor = NewObject< UMaterialExpressionVertexColor >( Material, UMaterialExpressionVertexColor::StaticClass(), NAME_None, ObjectFlag); @@ -899,7 +899,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( // Material should have at least one multiply expression. UMaterialExpressionMultiply * MaterialExpressionMultiply = Cast(MaterialExpression); - if (!MaterialExpressionMultiply || MaterialExpressionMultiply->IsPendingKill()) + if (!IsValid(MaterialExpressionMultiply)) MaterialExpressionMultiply = NewObject( Material, UMaterialExpressionMultiply::StaticClass(), NAME_None, ObjectFlag); @@ -986,7 +986,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( HAPI_IMAGE_DATA_INT8, ImagePacking, false, ImageBuffer)) { UPackage * TextureDiffusePackage = nullptr; - if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + if (IsValid(TextureDiffuse)) TextureDiffusePackage = Cast(TextureDiffuse->GetOuter()); HAPI_ImageInfo ImageInfo; @@ -1010,7 +1010,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( InPackageParams, TextureDiffuseName); } - else if (TextureDiffuse && !TextureDiffuse->IsPendingKill()) + else if (IsValid(TextureDiffuse)) { // Get the name of the texture if we are overwriting the exist asset TextureDiffuseName = TextureDiffuse->GetName(); @@ -1021,7 +1021,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentDiffuse( } // Create diffuse texture, if we need to create one. - if (!TextureDiffuse || TextureDiffuse->IsPendingKill()) + if (!IsValid(TextureDiffuse)) bCreatedNewTextureDiffuse = true; FString NodePath; @@ -1204,7 +1204,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; @@ -1323,7 +1323,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacityMask( InPackageParams, TextureOpacityName); } - else if (TextureOpacity && !TextureOpacity->IsPendingKill()) + else if (IsValid(TextureOpacity)) { // Get the name of the texture if we are overwriting the exist asset TextureOpacityName = TextureOpacity->GetName(); @@ -1419,7 +1419,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentOpacity( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; @@ -1607,7 +1607,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; @@ -1738,7 +1738,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( InPackageParams, TextureNormalName); } - else if (TextureNormal && !TextureNormal->IsPendingKill()) + else if (IsValid(TextureNormal)) { // Get the name of the texture if we are overwriting the exist asset TextureNormalName = TextureNormal->GetName(); @@ -1896,7 +1896,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentNormal( InPackageParams, TextureNormalName); } - else if (TextureNormal && !TextureNormal->IsPendingKill()) + else if (IsValid(TextureNormal)) { // Get the name of the texture if we are overwriting the exist asset TextureNormalName = TextureNormal->GetName(); @@ -1982,7 +1982,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; @@ -2084,7 +2084,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentSpecular( InPackageParams, TextureSpecularName); } - else if (TextureSpecular && !TextureSpecular->IsPendingKill()) + else if (IsValid(TextureSpecular)) { // Get the name of the texture if we are overwriting the exist asset TextureSpecularName = TextureSpecular->GetName(); @@ -2233,7 +2233,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; @@ -2334,7 +2334,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentRoughness( InPackageParams, TextureRoughnessName); } - else if (TextureRoughness && !TextureRoughness->IsPendingKill()) + else if (IsValid(TextureRoughness)) { // Get the name of the texture if we are overwriting the exist asset TextureRoughnessName = TextureRoughness->GetName(); @@ -2485,7 +2485,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; @@ -2587,7 +2587,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentMetallic( InPackageParams, TextureMetallicName); } - else if (TextureMetallic && !TextureMetallic->IsPendingKill()) + else if (IsValid(TextureMetallic)) { // Get the name of the texture if we are overwriting the exist asset TextureMetallicName = TextureMetallic->GetName(); @@ -2738,7 +2738,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( TArray& OutPackages, int32& MaterialNodeY) { - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) return false; bool bExpressionCreated = false; @@ -2840,7 +2840,7 @@ FHoudiniMaterialTranslator::CreateMaterialComponentEmissive( InPackageParams, TextureEmissiveName); } - else if (TextureEmissive && !TextureEmissive->IsPendingKill()) + else if (IsValid(TextureEmissive)) { // Get the name of the texture if we are overwriting the exist asset TextureEmissiveName = TextureEmissive->GetName(); @@ -3291,7 +3291,7 @@ UTexture* FoundTexture = nullptr; for (const auto& CurrentPackage : InPackages) { // Iterate through the cooked packages - if (!CurrentPackage || CurrentPackage->IsPendingKill()) + if (!IsValid(CurrentPackage)) continue; // First, check if the package contains a texture diff --git a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp index 4331f5827..7d0ec824c 100644 --- a/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniMeshTranslator.cpp @@ -94,13 +94,13 @@ FHoudiniMeshTranslator::CreateAllMeshesAndComponentsFromHoudiniOutput( bool bInTreatExistingMaterialsAsUpToDate, bool bInDestroyProxies) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; - if (!InPackageParams.OuterPackage || InPackageParams.OuterPackage->IsPendingKill()) + if (!IsValid(InPackageParams.OuterPackage)) return false; - if (!InOuterComponent || InOuterComponent->IsPendingKill()) + if (!IsValid(InOuterComponent)) return false; TMap NewOutputObjects; @@ -164,10 +164,10 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( bool bInDestroyProxies, bool bInApplyGenericProperties) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; - if (!InOuterComponent || InOuterComponent->IsPendingKill()) + if (!IsValid(InOuterComponent)) return false; TMap OldOutputObjects = InOutput->GetOutputObjects(); @@ -187,7 +187,7 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( UObject* NewProxyMesh = NewOutputObj.Value.ProxyObject; UObject* OldStaticMesh = FoundOldOutputObj->OutputObject; - if (OldStaticMesh && !OldStaticMesh->IsPendingKill()) + if (IsValid(OldStaticMesh)) { // If a proxy was created for an existing static mesh, keep the existing static // mesh (will be hidden) @@ -204,7 +204,7 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( } UObject* OldProxyMesh = FoundOldOutputObj->ProxyObject; - if (OldProxyMesh && !OldProxyMesh->IsPendingKill()) + if (IsValid(OldProxyMesh)) { // If a new static mesh was created for a proxy, keep the proxy (will be hidden) // ... unless we want to explicitly destroy proxies @@ -235,12 +235,12 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( RemoveAndDestroyComponent(OldOutputObject.ProxyComponent); OldOutputObject.ProxyComponent = nullptr; - if (OldOutputObject.OutputObject && !OldOutputObject.OutputObject->IsPendingKill()) + if (IsValid(OldOutputObject.OutputObject)) { OldOutputObject.OutputObject->MarkPendingKill(); } - if (OldOutputObject.ProxyObject && !OldOutputObject.ProxyObject->IsPendingKill()) + if (IsValid(OldOutputObject.ProxyObject)) { OldOutputObject.ProxyObject->MarkPendingKill(); } @@ -312,7 +312,7 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( if (OutputObject.bProxyIsCurrent) { UObject *Mesh = OutputObject.ProxyObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + if (!IsValid(Mesh) || !Mesh->IsA()) { HOUDINI_LOG_ERROR(TEXT("Proxy Mesh is invalid (wrong type or pending kill)...")); continue; @@ -331,7 +331,7 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( { PostCreateHoudiniStaticMeshComponent(HSMC, Mesh); } - else if (HSMC && !HSMC->IsPendingKill() && HSMC->GetMesh() != Mesh) + else if (IsValid(HSMC) && HSMC->GetMesh() != Mesh) { // We need to reassign the HSM to the component UHoudiniStaticMesh* HSM = Cast(Mesh); @@ -372,7 +372,7 @@ FHoudiniMeshTranslator::CreateOrUpdateAllComponents( { // Create a new SMC if needed UObject* Mesh = OutputObject.OutputObject; - if (!Mesh || Mesh->IsPendingKill() || !Mesh->IsA()) + if (!IsValid(Mesh) || !Mesh->IsA()) { HOUDINI_LOG_ERROR(TEXT("Mesh is invalid (wrong type or pending kill)...")); continue; @@ -452,16 +452,16 @@ FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, con // If the static mesh had sockets, we can assign the desired actor to them now UStaticMeshComponent * StaticMeshComponent = Cast(InMeshComponent); UStaticMesh * StaticMesh = nullptr; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + if (IsValid(StaticMeshComponent)) StaticMesh = StaticMeshComponent->GetStaticMesh(); - if (StaticMesh && !StaticMesh->IsPendingKill()) + if (IsValid(StaticMesh)) { 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())) + if (IsValid(MeshSocket) && (MeshSocket->Tag.IsEmpty())) continue; AddActorsToMeshSocket(StaticMesh->Sockets[nSocket], StaticMeshComponent, HoudiniCreatedSocketActors, HoudiniAttachedSocketActors); @@ -473,7 +473,7 @@ FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, con { AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) { HoudiniCreatedSocketActors.RemoveAt(Idx); continue; @@ -503,7 +503,7 @@ FHoudiniMeshTranslator::UpdateMeshComponent(UMeshComponent *InMeshComponent, con for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) { AActor* CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) { HoudiniAttachedSocketActors.RemoveAt(Idx); continue; @@ -1408,7 +1408,7 @@ FHoudiniMeshTranslator::CreateNewStaticMesh(const FString& InSplitIdentifier) PackageParams.SplitStr = InSplitIdentifier; UStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + if (!IsValid(NewStaticMesh)) return nullptr; return NewStaticMesh; @@ -1426,7 +1426,7 @@ FHoudiniMeshTranslator::CreateNewHoudiniStaticMesh(const FString& InSplitIdentif PackageParams.SplitStr = InSplitIdentifier + "_HSM"; UHoudiniStaticMesh * NewStaticMesh = PackageParams.CreateObjectAndPackage(); - if (!NewStaticMesh || NewStaticMesh->IsPendingKill()) + if (!IsValid(NewStaticMesh)) return nullptr; return NewStaticMesh; @@ -1507,7 +1507,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() UStaticMesh* MainStaticMesh = nullptr; bool bAssignedCustomCollisionMesh = false; - ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; + ECollisionTraceFlag MainStaticMeshCTF = StaticMeshGenerationProperties.GeneratedCollisionTraceFlag; // Iterate through all detected split groups we care about and split geometry. // The split are ordered in the following way: @@ -1625,7 +1625,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() { // If we couldn't find a valid existing static mesh, create a new one FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) continue; bNewStaticMeshCreated = true; @@ -2076,6 +2076,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() TArray< int32 > NeededVertices; RawMesh.WedgeIndices.SetNumZeroed(SplitVertexCount); + bool bHasInvalidFaceIndices = false; int32 ValidVertexId = 0; for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) { @@ -2096,9 +2097,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() || !IndicesMapper.IsValidIndex(WedgeIndices[2])) { // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + bHasInvalidFaceIndices = true; continue; } @@ -2152,6 +2151,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() ValidVertexId += 3; } + if (bHasInvalidFaceIndices) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + if (bDoTiming) { HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_RawMesh() - Indices in %f seconds."), FPlatformTime::Seconds() - tick); @@ -2172,17 +2178,14 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() // int32 VertexPositionsCount = NeededVertices.Num(); RawMesh.VertexPositions.SetNumZeroed(VertexPositionsCount); - + bool bHasInvalidPositionIndexData = false; for (int32 VertexPositionIdx = 0; VertexPositionIdx < VertexPositionsCount; ++VertexPositionIdx) { int32 NeededVertexIndex = NeededVertices[VertexPositionIdx]; if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) { // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + bHasInvalidPositionIndexData = true; continue; } @@ -2193,6 +2196,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() RawMesh.VertexPositions[VertexPositionIdx].Z = PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION; } + if (bHasInvalidPositionIndexData) + { + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } /* // TODO: // Check if this mesh contains only degenerate triangles. @@ -2626,6 +2636,16 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() } } + TArray BakeNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeNameAttribute(HGPO.GeoId, HGPO.PartId, BakeNames, 0, 1)) + { + if (BakeNames.Num() > 0 && !BakeNames[0].IsEmpty()) + { + // cache the bake name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_NAME, BakeNames[0]); + } + } + TArray TileValues; if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues, HAPI_ATTROWNER_INVALID, 0, 1)) { @@ -2713,7 +2733,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() tick = FPlatformTime::Seconds(); UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) + if (!IsValid(SM)) continue; UBodySetup * BodySetup = SM->GetBodySetup(); @@ -2726,7 +2746,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) + if (IsValid(BodySetup)) { // Make sure rendering is done - so we are not changing data being used by collision drawing. FlushRenderingCommands(); @@ -2836,7 +2856,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_RawMesh() SM->GetOnMeshChanged().Broadcast(); UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) + if (IsValid(MeshPackage)) { MeshPackage->MarkPackageDirty(); } @@ -2930,7 +2950,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() UStaticMesh* MainStaticMesh = nullptr; bool bAssignedCustomCollisionMesh = false; - ECollisionTraceFlag MainStaticMeshCTF = ECollisionTraceFlag::CTF_UseComplexAsSimple; + ECollisionTraceFlag MainStaticMeshCTF = StaticMeshGenerationProperties.GeneratedCollisionTraceFlag; // Iterate through all detected split groups we care about and split geometry. // The split are ordered in the following way: @@ -3048,7 +3068,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() { // If we couldn't find a valid existing static mesh, create a new one FoundStaticMesh = CreateNewStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) continue; bNewStaticMeshCreated = true; @@ -3206,6 +3226,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() int32 CurrentSplitIndex = 0; int32 ValidVertexId = 0; + bool bHasInvalidFaceIndices = false; for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) { int32 WedgeCheck = SplitVertexList[VertexIdx + 0]; @@ -3225,9 +3246,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() || !PartToSplitIndicesMapper.IsValidIndex(WedgeIndices[2])) { // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + bHasInvalidFaceIndices = true; continue; } @@ -3257,6 +3276,13 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() ValidVertexId += 3; } + + if (bHasInvalidFaceIndices) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } if (bDoTiming) { @@ -3279,7 +3305,8 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() // TVertexAttributesRef VertexPositions = MeshDescription->VertexAttributes().GetAttributesRef(MeshAttribute::Vertex::Position); - + + bool bHasInvalidPositionIndexData = false; MeshDescription->ReserveNewVertices(SplitNeededVertices.Num()); for ( const int32& NeededVertexIndex : SplitNeededVertices) { @@ -3295,15 +3322,20 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() else { // Error when retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + bHasInvalidPositionIndexData = true; continue; } } + if (bHasInvalidPositionIndexData) + { + HOUDINI_LOG_WARNING( + TEXT("Creating Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } + if (bDoTiming) { HOUDINI_LOG_MESSAGE(TEXT("CreateStaticMesh_MeshDescription() - Positions in %f seconds."), FPlatformTime::Seconds() - tick); @@ -3979,6 +4011,16 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() } } + TArray BakeNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeNameAttribute(HGPO.GeoId, HGPO.PartId, BakeNames, 0, 1)) + { + if (BakeNames.Num() > 0 && !BakeNames[0].IsEmpty()) + { + // cache the bake name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_NAME, BakeNames[0]); + } + } + TArray TileValues; if (FoundOutputObject && FHoudiniEngineUtils::GetTileAttribute(HGPO.GeoId, HGPO.PartId, TileValues, HAPI_ATTROWNER_INVALID, 0, 1)) { @@ -4061,7 +4103,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() tick = FPlatformTime::Seconds(); UStaticMesh* SM = Current.Value; - if (!SM || SM->IsPendingKill()) + if (!IsValid(SM)) continue; UBodySetup * BodySetup = SM->GetBodySetup(); @@ -4074,7 +4116,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() EHoudiniSplitType SplitType = GetSplitTypeFromSplitName(Current.Key.SplitIdentifier); // Handle the Static Mesh's colliders - if (BodySetup && !BodySetup->IsPendingKill()) + if (IsValid(BodySetup)) { // Make sure rendering is done - so we are not changing data being used by collision drawing. FlushRenderingCommands(); @@ -4190,7 +4232,7 @@ FHoudiniMeshTranslator::CreateStaticMesh_MeshDescription() SM->GetOnMeshChanged().Broadcast(); UPackage* MeshPackage = SM->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) + if (IsValid(MeshPackage)) { MeshPackage->MarkPackageDirty(); } @@ -4379,7 +4421,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() { // If we couldn't find a valid existing dynamic mesh, create a new one FoundStaticMesh = CreateNewHoudiniStaticMesh(OutputObjectIdentifier.SplitIdentifier); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) continue; bNewStaticMeshCreated = true; @@ -4434,6 +4476,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() { TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Build IndicesMapper and NeededVertices")); + bool bHasInvalidFaceIndices = false; int32 ValidVertexId = 0; for (int32 VertexIdx = 0; VertexIdx < SplitVertexList.Num(); VertexIdx += 3) { @@ -4453,10 +4496,8 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() || !IndicesMapper.IsValidIndex(WedgeIndices[1]) || !IndicesMapper.IsValidIndex(WedgeIndices[2])) { - // Invalid face index. - HOUDINI_LOG_MESSAGE( - TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + // Invalid face index. Don't log in the loop. + bHasInvalidFaceIndices = true; continue; } @@ -4482,6 +4523,13 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() ValidVertexId += 3; } + + if (bHasInvalidFaceIndices) + { + HOUDINI_LOG_MESSAGE( + TEXT("Creating Dynamic Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] has some invalid face indices"), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } } //--------------------------------------------------------------------------------------------------------------------- @@ -4646,6 +4694,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() { TRACE_CPUPROFILER_EVENT_SCOPE(TEXT("FHoudiniMeshTranslator::CreateHoudiniStaticMesh -- Set Vertex Positions")); + bool bHasInvalidPositionIndexData = false; for (int32 VertexPositionIdx = 0; VertexPositionIdx < NumVertexPositions; ++VertexPositionIdx) //ParallelFor(NumVertexPositions, [&](uint32 VertexPositionIdx) { @@ -4653,10 +4702,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() if (!PartPositions.IsValidIndex(NeededVertexIndex * 3 + 2)) { // Error retrieving positions. - HOUDINI_LOG_WARNING( - TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") - TEXT("- skipping."), - HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + bHasInvalidPositionIndexData = true; continue; } @@ -4667,6 +4713,14 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() PartPositions[NeededVertexIndex * 3 + 1] * HAPI_UNREAL_SCALE_FACTOR_POSITION )); }//); + + if (bHasInvalidPositionIndexData) + { + HOUDINI_LOG_WARNING( + TEXT("Creating Dynamic Static Meshes: Object [%d %s], Geo [%d], Part [%d %s], Split [%d %s] invalid position/index data ") + TEXT("- skipping."), + HGPO.ObjectId, *HGPO.ObjectName, HGPO.GeoId, HGPO.PartId, *HGPO.PartName, SplitId, *SplitGroupName); + } } //--------------------------------------------------------------------------------------------------------------------- @@ -5084,7 +5138,7 @@ FHoudiniMeshTranslator::CreateHoudiniStaticMesh() // FoundStaticMesh->MarkPackageDirty(); //} UPackage *MeshPackage = FoundStaticMesh->GetOutermost(); - if (MeshPackage && !MeshPackage->IsPendingKill()) + if (IsValid(MeshPackage)) { MeshPackage->MarkPackageDirty(); @@ -5221,7 +5275,7 @@ FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentif { // Make sure it's a valid static mesh FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) FoundStaticMesh = nullptr; } @@ -5234,7 +5288,7 @@ FHoudiniMeshTranslator::FindExistingStaticMesh(const FHoudiniOutputObjectIdentif // Make sure it's a valid static mesh FoundStaticMesh = Cast(FoundOutputObjectPtr->OutputObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) return nullptr; } @@ -5264,7 +5318,7 @@ FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObject { // Make sure it's a valid static mesh FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) FoundStaticMesh = nullptr; } @@ -5277,7 +5331,7 @@ FHoudiniMeshTranslator::FindExistingHoudiniStaticMesh(const FHoudiniOutputObject // Make sure it's a valid static mesh FoundStaticMesh = Cast(FoundOutputObjectPtr->ProxyObject); - if (!FoundStaticMesh || FoundStaticMesh->IsPendingKill()) + if (!IsValid(FoundStaticMesh)) return nullptr; } @@ -6352,11 +6406,11 @@ FHoudiniMeshTranslator::SetPackageParams(const FHoudiniPackageParams& InPackageP bool FHoudiniMeshTranslator::RemoveAndDestroyComponent(UObject* InComponent) { - if (!InComponent || InComponent->IsPendingKill()) + if (!IsValid(InComponent)) return false; USceneComponent* SceneComponent = Cast(InComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) + if (IsValid(SceneComponent)) { // Remove from the HoudiniAssetActor if (SceneComponent->GetOwner()) @@ -6378,7 +6432,7 @@ FHoudiniMeshTranslator::CreateMeshComponent(UObject *InOuterComponent, const TSu // Create a new SMC as we couldn't find an existing one USceneComponent* OuterSceneComponent = Cast(InOuterComponent); UObject * Outer = nullptr; - if (OuterSceneComponent && !OuterSceneComponent->IsPendingKill()) + if (IsValid(OuterSceneComponent)) Outer = OuterSceneComponent->GetOwner() ? OuterSceneComponent->GetOwner() : OuterSceneComponent->GetOuter(); UMeshComponent *MeshComponent = NewObject(Outer, InComponentType, NAME_None, RF_Transactional); @@ -6475,7 +6529,7 @@ FHoudiniMeshTranslator::CreateOrUpdateMeshComponent( // If there is an existing component, but it is pending kill, then it was likely // deleted by some other process, such as by the user in the editor, so don't use it - if (!MeshComponent || MeshComponent->IsPendingKill() || !MeshComponent->IsA(InComponentType)) + if (!IsValid(MeshComponent) || !MeshComponent->IsA(InComponentType)) { // If the component is not of type InComponentType, or the found component is pending kill, destroy // the existing component (a new one is then created below) @@ -6507,7 +6561,7 @@ bool FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStaticMeshComponent * StaticMeshComponent, TArray & HoudiniCreatedSocketActors, TArray & HoudiniAttachedSocketActors) { - if (!Socket || Socket->IsPendingKill() || !StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + if (!IsValid(Socket) || !IsValid(StaticMeshComponent)) return false; // The actor to assign is stored is the socket's tag @@ -6539,7 +6593,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati //UWorld* editorWorld = GEditor->GetEditorWorldContext().World(); #if WITH_EDITOR UWorld* EditorWorld = StaticMeshComponent->GetOwner() ? StaticMeshComponent->GetOwner()->GetWorld() : nullptr; - if (!EditorWorld || EditorWorld->IsPendingKill()) + if (!IsValid(EditorWorld)) return false; // Remove the previously created actors which were attached to this socket @@ -6547,7 +6601,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (int32 Idx = HoudiniCreatedSocketActors.Num() - 1; Idx >= 0; --Idx) { AActor * CurActor = HoudiniCreatedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) { HoudiniCreatedSocketActors.RemoveAt(Idx); continue; @@ -6566,7 +6620,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (int32 Idx = HoudiniAttachedSocketActors.Num() - 1; Idx >= 0; --Idx) { AActor * CurActor = HoudiniAttachedSocketActors[Idx]; - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) { HoudiniAttachedSocketActors.RemoveAt(Idx); continue; @@ -6585,12 +6639,12 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati AActor * CreatedDefaultActor = nullptr; UStaticMesh * DefaultReferenceSM = FHoudiniEngine::Get().GetHoudiniDefaultReferenceMesh().Get(); - if (DefaultReferenceSM && !DefaultReferenceSM->IsPendingKill()) + if (IsValid(DefaultReferenceSM)) { TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( EditorWorld->GetCurrentLevel(), DefaultReferenceSM, false, RF_Transactional, nullptr); - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + if (NewActors.Num() <= 0 || !IsValid(NewActors[0])) { HOUDINI_LOG_WARNING( TEXT("Failed to load default mesh.")); @@ -6603,7 +6657,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (auto & CurComp : NewActors[0]->GetComponents()) { UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) + if (IsValid(CurSMC)) CurSMC->SetMobility(OutputSMCMobility); } @@ -6649,7 +6703,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati TEXT("Output static mesh: Socket '%s' actor is not specified. Spawn a default mesh (hidden in game)."), *(Socket->GetName())); AActor * DefaultActor = CreateDefaultActor(); - if (DefaultActor && !DefaultActor->IsPendingKill()) + if (IsValid(DefaultActor)) HoudiniCreatedSocketActors.Add(DefaultActor); return true; @@ -6661,7 +6715,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati { // Same as with the Object Iterator, access the subclass instance with the * or -> operators. AActor *Actor = *ActorItr; - if (!Actor || Actor->IsPendingKillOrUnreachable()) + if (!IsValid(Actor) || Actor->IsPendingKillOrUnreachable()) continue; for (int32 StringIdx = 0; StringIdx < ActorStringArray.Num(); StringIdx++) @@ -6675,7 +6729,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (auto & CurComp : Actor->GetComponents()) { UStaticMeshComponent * SMC = Cast(CurComp); - if (SMC && !SMC->IsPendingKill()) + if (IsValid(SMC)) SMC->SetMobility(OutputSMCMobility); } @@ -6693,7 +6747,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (int32 Idx = ActorStringArray.Num() - 1; Idx>= 0; --Idx) { UObject * Obj = StaticLoadObject(UObject::StaticClass(), nullptr, *ActorStringArray[Idx]); - if (!Obj || Obj->IsPendingKill()) + if (!IsValid(Obj)) { bSuccess = false; continue; @@ -6703,7 +6757,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati TArray NewActors = FLevelEditorViewportClient::TryPlacingActorFromObject( EditorWorld->GetCurrentLevel(), Obj, false, RF_Transactional, nullptr); - if (NewActors.Num() <= 0 || !NewActors[0] || NewActors[0]->IsPendingKill()) + if (NewActors.Num() <= 0 || !IsValid(NewActors[0])) { bSuccess = false; continue; @@ -6714,7 +6768,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati for (auto & CurComp : NewActors[0]->GetComponents()) { UStaticMeshComponent * CurSMC = Cast(CurComp); - if (CurSMC && !CurSMC->IsPendingKill()) + if (IsValid(CurSMC)) CurSMC->SetMobility(OutputSMCMobility); } @@ -6735,7 +6789,7 @@ FHoudiniMeshTranslator::AddActorsToMeshSocket(UStaticMeshSocket * Socket, UStati // If failed to load this object, spawn a default mesh AActor * CurDefaultActor = CreateDefaultActor(); - if (CurDefaultActor && !CurDefaultActor->IsPendingKill()) + if (IsValid(CurDefaultActor)) HoudiniCreatedSocketActors.Add(CurDefaultActor); } } diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp index 3a91f8325..9b8e93072 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.cpp @@ -68,7 +68,7 @@ FHoudiniOutputTranslator::UpdateOutputs( const bool& bInForceUpdate, bool& bOutHasHoudiniStaticMeshOutput) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // Get the temp folder override @@ -95,8 +95,11 @@ FHoudiniOutputTranslator::UpdateOutputs( } TArray NewOutputs; + TArray OutputNodes = HAC->GetOutputNodeIds(); + TMap OutputNodeCookCounts = HAC->GetOutputNodeCookCounts(); if (FHoudiniOutputTranslator::BuildAllOutputs( - HAC->GetAssetId(), HAC, HAC->Outputs, NewOutputs, HAC->NodeIdsToCook, HAC->bOutputTemplateGeos, HAC->bUseOutputNodes)) + HAC->GetAssetId(), HAC, OutputNodes, OutputNodeCookCounts, + HAC->Outputs, NewOutputs, HAC->bOutputTemplateGeos, HAC->bUseOutputNodes)) { // NOTE: For now we are currently forcing all outputs to be cleared here. There is still an issue where, in some // circumstances, landscape tiles disappear when clearing outputs after processing. @@ -269,7 +272,7 @@ FHoudiniOutputTranslator::UpdateOutputs( for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) { UHoudiniOutput* CurOutput = HAC->GetOutputAt(OutputIdx); - if (!CurOutput || CurOutput->IsPendingKill()) + if (!IsValid(CurOutput)) continue; FString Notification = FString::Format(TEXT("Processing output {0} / {1}..."), {FString::FromInt(OutputIdx + 1), FString::FromInt(NumOutputs)}); @@ -324,6 +327,7 @@ FHoudiniOutputTranslator::UpdateOutputs( { bOutHasHoudiniStaticMeshOutput &= CurOutput->HasAnyCurrentProxy(); } + break; } @@ -534,6 +538,17 @@ FHoudiniOutputTranslator::UpdateOutputs( continue; if (Landscape->GetLandscapeInfo()->Proxies.Num() == 0) + if (!IsValid(Landscape)) + continue; + + ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo(); + if (!LandscapeInfo) + { + Landscape->Destroy(); + continue; + } + + if (LandscapeInfo->Proxies.Num() == 0) Landscape->Destroy(); } } @@ -604,7 +619,7 @@ FHoudiniOutputTranslator::UpdateOutputs( bool FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(UHoudiniAssetComponent* HAC, bool bInDestroyProxies) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; UObject* OuterComponent = HAC; @@ -811,7 +826,7 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) break; UHoudiniSplineComponent * HoudiniSplineComponent = Cast(Pair.Value.OutputComponent); - if (HoudiniSplineComponent && !HoudiniSplineComponent->IsPendingKill()) + if (IsValid(HoudiniSplineComponent)) { HoudiniSplineComponent->SetNodeId(EditableCurveGeoIds[Idx]); @@ -830,7 +845,7 @@ FHoudiniOutputTranslator::UpdateLoadedOutputs(UHoudiniAssetComponent* HAC) const TArray &Children = HAC->GetAttachChildren(); for (auto & CurAttachedComp : Children) { - if (!CurAttachedComp || CurAttachedComp->IsPendingKill()) + if (!IsValid(CurAttachedComp)) continue; if (!CurAttachedComp->IsA()) @@ -893,7 +908,7 @@ FHoudiniOutputTranslator::UploadChangedEditableOutput( UHoudiniAssetComponent* HAC, const bool& bInForceUpdate) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; TArray &Outputs = HAC->Outputs; @@ -911,7 +926,7 @@ FHoudiniOutputTranslator::UploadChangedEditableOutput( for (auto& CurrentOutputObj : CurrentOutput->GetOutputObjects()) { UHoudiniSplineComponent* HoudiniSplineComponent = Cast(CurrentOutputObj.Value.OutputComponent); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; if (!HoudiniSplineComponent->HasChanged()) @@ -934,12 +949,18 @@ bool FHoudiniOutputTranslator::BuildAllOutputs( const HAPI_NodeId& AssetId, UObject* InOuterObject, + const TArray& OutputNodes, + const TMap& OutputNodeCookCounts, TArray& InOldOutputs, TArray& OutNewOutputs, - TArray& OutNodeIdsToCook, const bool& InOutputTemplatedGeos, const bool& InUseOutputNodes) { + // NOTE: This function still gathers output nodes from the asset id. This is old behaviour. + // Output nodes are now being gathered before cooking starts and is passed in through + // the OutputNodes array. Clean up this function by only using output nodes from the + // aforementioned array. + // Ensure the asset has a valid node ID if (AssetId < 0) { @@ -952,17 +973,21 @@ FHoudiniOutputTranslator::BuildAllOutputs( HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetAssetInfo( FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo), false); + // Get the Asset NodeInfo + HAPI_NodeInfo AssetNodeInfo; + FHoudiniApi::NodeInfo_Init(&AssetNodeInfo); + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::GetNodeInfo( + FHoudiniEngine::Get().GetSession(), AssetId, &AssetNodeInfo), false); + FString CurrentAssetName; { FHoudiniEngineString hapiSTR(AssetInfo.nameSH); hapiSTR.ToFString(CurrentAssetName); } - // Retrieve the asset's transform. - // TODO: Unused?! - //FTransform AssetUnrealTransform; - //if (!FHoudiniEngineUtils::HapiGetAssetTransform(AssetId, AssetUnrealTransform)) - // return false; + // In certain cases, such as PDG output processing we might end up with a SOP node instead of a + // container. In that case, don't try to run child queries on this node. They will fail. + const bool bAssetHasChildren = !(AssetNodeInfo.type == HAPI_NODETYPE_SOP && AssetNodeInfo.childNodeCount == 0); // Retrieve information about each object contained within our asset. TArray ObjectInfos; @@ -983,6 +1008,126 @@ FHoudiniOutputTranslator::BuildAllOutputs( // match them with theit corresponding height volume after TArray UnassignedVolumeParts; + // When receiving landscape edit layers, we are no longer to split + // outputs based on 'height' volumes + TSet TileIds; + + // VA: Editable nodes fetching have been moved here to fetch them for the whole asset, only once. + // It seemed unnecessary to have to fetch these for every Object node. Instead, + // we'll collect all the editable nodes for the HDA and process them only on the first loop. + // This also allows us to use more 'strict' Object node retrieval for output processing since + // we don't have to worry that we might miss any editable nodes. + + // Start by getting the number of editable nodes + TArray EditableGeoInfos; + int32 EditableNodeCount = 0; + if (bAssetHasChildren) + { + HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, + true, &EditableNodeCount)); + } + + // All editable nodes will be output, regardless + // of whether the subnet is considered visible or not. + if (EditableNodeCount > 0) + { + TArray EditableNodeIds; + EditableNodeIds.SetNumUninitialized(EditableNodeCount); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, EditableNodeIds.GetData(), EditableNodeCount)); + + for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + { + HAPI_GeoInfo CurrentEditableGeoInfo; + FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); + HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( + FHoudiniEngine::Get().GetSession(), + EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); + + // TODO: Check whether this display geo is actually being output + // Just because this is a display node doesn't mean that it will be output (it + // might be in a hidden subnet) + + // Do not process the main display geo twice! + if (CurrentEditableGeoInfo.isDisplayGeo) + continue; + + // We only handle editable curves for now + if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) + continue; + + // Add this geo to the geo info array + EditableGeoInfos.Add(CurrentEditableGeoInfo); + } + } + + + + const bool bIsSopAsset = AssetInfo.nodeId != AssetInfo.objectNodeId; + bool bUseOutputFromSubnets = true; + if (bAssetHasChildren) + { + if (FHoudiniEngineUtils::ContainsSopNodes(AssetInfo.nodeId)) + { + // This HDA contains immediate SOP nodes. Don't look for subnets to output. + bUseOutputFromSubnets = false; + } + else + { + // Assume we're using a subnet-based HDA + bUseOutputFromSubnets = true; + } + } + else + { + // This asset doesn't have any children. Don't try to find subnets. + bUseOutputFromSubnets = false; + } + + // Before we can perform visibility checks on the Object nodes, we have + // to build a set of all the Object node ids. The 'AllObjectIds' act + // as a visibility filter. If an Object node is not present in this + // list, the content of that node will not be displayed (display / output / templated nodes). + // NOTE that if the HDA contains immediate SOP nodes we will ignore + // all subnets and only use the data outputs directly from the HDA. + + TSet AllObjectIds; + if (bUseOutputFromSubnets) + { + int NumObjSubnets; + TArray ObjectIds; + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, + HAPI_NODETYPE_OBJ, + HAPI_NODEFLAGS_OBJ_SUBNET, + true, + &NumObjSubnets + ), + false); + + ObjectIds.SetNumUninitialized(NumObjSubnets); + HOUDINI_CHECK_ERROR_RETURN( + FHoudiniApi::GetComposedChildNodeList( + FHoudiniEngine::Get().GetSession(), + AssetId, + ObjectIds.GetData(), + NumObjSubnets + ), + false); + AllObjectIds.Append(ObjectIds); + } + else + { + AllObjectIds.Add(AssetInfo.objectNodeId); + } + + TMap CurrentCookCounts; + // Iterate through all objects. int32 OutputIdx = 1; for (int32 ObjectIdx = 0; ObjectIdx < ObjectInfos.Num(); ObjectIdx++) @@ -990,6 +1135,29 @@ FHoudiniOutputTranslator::BuildAllOutputs( // Retrieve the object info const HAPI_ObjectInfo& CurrentHapiObjectInfo = ObjectInfos[ObjectIdx]; + // Determine whether this object node is fully visible. + bool bObjectIsVisible = false; + HAPI_NodeId GatherOutputsNodeId = -1; // Outputs will be gathered from this node. + if (!bAssetHasChildren) + { + // If the asset doesn't have children, we have to gather outputs from the asset's parent in order to output + // this asset node + bObjectIsVisible = true; + GatherOutputsNodeId = AssetNodeInfo.parentId; + } + else if (bIsSopAsset && CurrentHapiObjectInfo.nodeId == AssetInfo.objectNodeId) + { + // When dealing with a SOP asset, be sure to gather outputs from the SOP node, not the + // outer object node. + bObjectIsVisible = true; + GatherOutputsNodeId = AssetInfo.nodeId; + } + else + { + bObjectIsVisible = FHoudiniEngineUtils::IsObjNodeFullyVisible(AllObjectIds, AssetId, CurrentHapiObjectInfo.nodeId); + GatherOutputsNodeId = CurrentHapiObjectInfo.nodeId; + } + // Cache/convert them FHoudiniObjectInfo CurrentObjectInfo; CacheObjectInfo(CurrentHapiObjectInfo, CurrentObjectInfo); @@ -1016,167 +1184,84 @@ FHoudiniOutputTranslator::BuildAllOutputs( // but we may also want to process editable geos as well TArray GeoInfos; - // Get the Display Geo's info - HAPI_GeoInfo DisplayHapiGeoInfo; - FHoudiniApi::GeoInfo_Init(&DisplayHapiGeoInfo); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetDisplayGeoInfo( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &DisplayHapiGeoInfo)) - { - HOUDINI_LOG_MESSAGE( - TEXT("Creating Static Meshes: Object [%d %s] unable to retrieve GeoInfo, - skipping."), - CurrentHapiObjectInfo.nodeId, *CurrentObjectName); - } - else + // These node ids may need to be cooked in order to extract part counts. + TSet ForceNodesToCook; + + // Track (heightfield) tile ids in order to determine + // when to create new tiles (used when outputting landscape edit layers). + TSet FoundTileIndices; + + // Append the initial set of editable geo infos here + // then clear the editable geo infos array since we + // only want to process them once. + GeoInfos.Append(EditableGeoInfos); + EditableGeoInfos.Empty(); + + if (bObjectIsVisible) { - // Add the display geo info to the array - GeoInfos.Add(DisplayHapiGeoInfo); - } + // NOTE: The HAPI_GetDisplayGeoInfo will not always return the expected Geometry subnet's + // Display flag geometry. If the Geometry subnet contains an Object subnet somewhere, the + // GetDisplayGeoInfo will sometimes fetch the display SOP from within the subnet which is + // not what we want. + + // Resolve and gather outputs (display / output / template nodes) from the GatherOutputsNodeId. + FHoudiniEngineUtils::GatherImmediateOutputGeoInfos(GatherOutputsNodeId, + InUseOutputNodes, + InOutputTemplatedGeos, + GeoInfos, + ForceNodesToCook); + + } // if (bObjectIsVisible) - // If desired, also get the output node's info - if (InUseOutputNodes) + // Iterates through the geos we want to process + for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) { - int32 OutputCount = 0; - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetOutputGeoCount( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, &OutputCount)) + // Cache the geo nodes ids for this asset + const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; + // We shouldn't add display nodes for cooking since the + // if (!CurrentHapiGeoInfo.isDisplayGeo) + // { + // OutNodeIdsToCook.Add(CurrentHapiGeoInfo.nodeId); + // } + + // We cannot rely on the bGeoHasChanged flag when dealing with session sync. Since the + // property will be set to false for any node that has cooked twice. Instead, we compare + // current cook counts against the last cached count that we have in order to determine + // whether geo has changed. + bool bHasChanged = false; + + if (!CurrentCookCounts.Contains(CurrentHapiGeoInfo.nodeId)) { - OutputCount = 0; + CurrentCookCounts.Add(CurrentHapiGeoInfo.nodeId, FHoudiniEngineUtils::HapiGetCookCount(CurrentHapiGeoInfo.nodeId)); } - - if (OutputCount > 0) + + if (OutputNodeCookCounts.Contains(CurrentHapiGeoInfo.nodeId)) { - // Get all the output node's geo infos - TArray OutputGeoInfos; - OutputGeoInfos.SetNum(OutputCount); - if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetOutputGeoInfos( - FHoudiniEngine::Get().GetSession(), CurrentHapiObjectInfo.nodeId, OutputGeoInfos.GetData(), OutputCount)) - { - OutputGeoInfos.Empty(); - } - - // Make sure all those output nodes are valid, - // ie, not inside the Display Geo - for (const auto& CurOutGeoInfo : OutputGeoInfos) - { - if (CurOutGeoInfo.nodeId == DisplayHapiGeoInfo.nodeId) - continue; - - bool bValidOutput = true; - int32 ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurOutGeoInfo.nodeId); - while (ParentId >= 0) - { - if (ParentId == CurOutGeoInfo.nodeId) - { - // This output node is inside the Display Geo - // Do not use this output to avoid duplicates - bValidOutput = false; - break; - } - - // Recurse - ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(ParentId); - } - - // If this output node is valid, add to the output Geos - if (bValidOutput) - GeoInfos.Add(CurOutGeoInfo); - } + // If the cook counts changed, we assume the geo has changed. + bHasChanged = OutputNodeCookCounts[CurrentHapiGeoInfo.nodeId] != CurrentCookCounts[CurrentHapiGeoInfo.nodeId]; } - } - - // Handle the editable nodes for this geo - // Start by getting the number of editable nodes - int32 EditableNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_EDITABLE, - true, &EditableNodeCount)); - - if (EditableNodeCount > 0) - { - TArray EditableNodeIds; - EditableNodeIds.SetNumUninitialized(EditableNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - AssetId, EditableNodeIds.GetData(), EditableNodeCount)); - - for (int32 nEditable = 0; nEditable < EditableNodeCount; nEditable++) + else { - HAPI_GeoInfo CurrentEditableGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentEditableGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - EditableNodeIds[nEditable], &CurrentEditableGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentEditableGeoInfo.isDisplayGeo) - continue; - - // We only handle editable curves for now - if (CurrentEditableGeoInfo.type != HAPI_GEOTYPE_CURVE) - continue; - - // Add this geo to the geo info array - GeoInfos.Add(CurrentEditableGeoInfo); + // Something is new! We don't have a cook count for this node. + bHasChanged = true; } - } - // Handle the templated nodes if desired - if (InOutputTemplatedGeos) - { - // Start by getting the number of templated nodes - int32 TemplatedNodeCount = 0; - HOUDINI_CHECK_ERROR(FHoudiniApi::ComposeChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, - HAPI_NODETYPE_SOP, HAPI_NODEFLAGS_TEMPLATED, - true, &TemplatedNodeCount)); + // Left in here for debugging convenience. + // if (bHasChanged) + // { + // FString NodePath; + // FHoudiniEngineUtils::HapiGetAbsNodePath(CurrentHapiGeoInfo.nodeId, NodePath); + // HOUDINI_LOG_MESSAGE(TEXT("[TaskCookAsset] We say Geo Has Changed!: %d, %s"), CurrentHapiGeoInfo.nodeId, *NodePath); + // } - if (TemplatedNodeCount > 0) - { - TArray TemplatedNodeIds; - TemplatedNodeIds.SetNumUninitialized(TemplatedNodeCount); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetComposedChildNodeList( - FHoudiniEngine::Get().GetSession(), - CurrentHapiObjectInfo.nodeId, TemplatedNodeIds.GetData(), TemplatedNodeCount)); - - for (int32 nTemplated = 0; nTemplated < TemplatedNodeCount; nTemplated++) - { - HAPI_GeoInfo CurrentTemplatedGeoInfo; - FHoudiniApi::GeoInfo_Init(&CurrentTemplatedGeoInfo); - HOUDINI_CHECK_ERROR(FHoudiniApi::GetGeoInfo( - FHoudiniEngine::Get().GetSession(), - TemplatedNodeIds[nTemplated], &CurrentTemplatedGeoInfo)); - - // Do not process the main display geo twice! - if (CurrentTemplatedGeoInfo.isDisplayGeo) - continue; - - // We don't want all the nested template node IDs, - // as our HDA could potentially be using other HDAs with nested template flags - // Make sure the parent of the templated node is either the HDA, the current OBJ or the Display SOP - HAPI_NodeId ParentId = FHoudiniEngineUtils::HapiGetParentNodeId(CurrentTemplatedGeoInfo.nodeId); - if (ParentId != CurrentHapiObjectInfo.nodeId - && ParentId != DisplayHapiGeoInfo.nodeId - && ParentId != AssetId) - { - continue; - } - - // Add this geo to the geo info array - GeoInfos.Add(CurrentTemplatedGeoInfo); - } - } - } - - // Iterates through the geos we want to process - for (int32 GeoIdx = 0; GeoIdx < GeoInfos.Num(); GeoIdx++) - { - // Cache the geo nodes ids for this asset - const HAPI_GeoInfo& CurrentHapiGeoInfo = GeoInfos[GeoIdx]; - OutNodeIdsToCook.Add(CurrentHapiGeoInfo.nodeId); + // HERE BE FUDGING! + // Change the hasGeoChanged flag on the GeoInfo to match our expectation + // of whether geo has changed. + GeoInfos[GeoIdx].hasGeoChanged = CurrentHapiGeoInfo.hasGeoChanged || bHasChanged; // Cook editable/templated nodes to get their parts. - if ((CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) + if ((ForceNodesToCook.Contains(CurrentHapiGeoInfo.nodeId) && CurrentHapiGeoInfo.partCount <= 0) + || (CurrentHapiGeoInfo.isEditable && CurrentHapiGeoInfo.partCount <= 0) || (CurrentHapiGeoInfo.isTemplated && CurrentHapiGeoInfo.partCount <= 0) || (!CurrentHapiGeoInfo.isDisplayGeo && CurrentHapiGeoInfo.partCount <= 0)) { @@ -1439,6 +1524,12 @@ FHoudiniOutputTranslator::BuildAllOutputs( currentHGPO.PartInfo = CurrentPartInfo; currentHGPO.AllMeshSockets = PartMeshSockets; + + // If the mesh is NOT visible and is NOT instanced, skip it. + if (!currentHGPO.bIsVisible && !currentHGPO.bIsInstanced) + { + continue; + } // We only support meshes for templated geos if (currentHGPO.bIsTemplated && (CurrentPartType != EHoudiniPartType::Mesh)) @@ -1561,6 +1652,8 @@ FHoudiniOutputTranslator::BuildAllOutputs( else currentHGPO.VolumeTileIndex = -1; } + + currentHGPO.bHasEditLayers = FHoudiniEngineUtils::GetEditLayerName(CurrentHapiGeoInfo.nodeId, CurrentHapiPartInfo.id, currentHGPO.VolumeLayerName, HAPI_ATTROWNER_PRIM); } } currentHGPO.VolumeInfo = CurrentVolumeInfo; @@ -1600,9 +1693,20 @@ FHoudiniOutputTranslator::BuildAllOutputs( FoundHoudiniOutput = InOldOutputs.FindByPredicate( [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(currentHGPO) : false; }); - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) - IsFoundOutputValid = true; + if (FoundHoudiniOutput && *FoundHoudiniOutput && currentHGPO.Type == EHoudiniPartType::Curve) + { + // Curve hacks!! + // If we're dealing with a curve, editable and non-editable curves are interpreted very + // differently so we have to apply an IsEditable comparison as well. + if ((*FoundHoudiniOutput)->IsEditableNode() != currentHGPO.bIsEditable) + { + // The IsEditable property is different. We can't reuse this output! + FoundHoudiniOutput = nullptr; + } + } + if (FoundHoudiniOutput && IsValid(*FoundHoudiniOutput)) + IsFoundOutputValid = true; } else { @@ -1610,7 +1714,7 @@ FHoudiniOutputTranslator::BuildAllOutputs( FoundHoudiniOutput = InOldOutputs.FindByPredicate( [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, true) : false; }); - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + if (FoundHoudiniOutput && IsValid(*FoundHoudiniOutput)) IsFoundOutputValid = true; // If we dont have a match in the old maps, also look in the newly created outputs @@ -1619,7 +1723,7 @@ FHoudiniOutputTranslator::BuildAllOutputs( FoundHoudiniOutput = OutNewOutputs.FindByPredicate( [currentHGPO](UHoudiniOutput* Output) { return Output ? Output->HeightfieldMatch(currentHGPO, false) : false; }); - if (FoundHoudiniOutput && *FoundHoudiniOutput && !(*FoundHoudiniOutput)->IsPendingKill()) + if (FoundHoudiniOutput && IsValid(*FoundHoudiniOutput)) IsFoundOutputValid = true; } } @@ -1636,14 +1740,32 @@ FHoudiniOutputTranslator::BuildAllOutputs( else { // We couldn't find a valid output object, so create a new one - - // If the current part is a volume, only create a new output object - // if the volume's name is "height", if not store the HGPO aside - if (currentHGPO.Type == EHoudiniPartType::Volume - && !currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) + if (currentHGPO.Type == EHoudiniPartType::Volume) { - UnassignedVolumeParts.Add(currentHGPO); - continue; + bool bBatchHGPO = false; + if(!currentHGPO.VolumeName.Equals(HAPI_UNREAL_LANDSCAPE_HEIGHT_VOLUME_NAME, ESearchCase::IgnoreCase)) + { + // This volume is not a height volume, so it will be batched into a single HGPO. + bBatchHGPO = true; + } + else if (currentHGPO.bHasEditLayers) + { + if (FoundTileIndices.Contains(currentHGPO.VolumeTileIndex)) + { + // If this volume name is height, AND we have edit layers enabled, check to see whether + // this is a new tile. If this is NOT a new tile, we assume that this is simply content + // for a new edit layer on the current tile. Batch it! + bBatchHGPO = true; + } + } + // Ensure this tile is tracked + FoundTileIndices.Add(currentHGPO.VolumeTileIndex); + if (bBatchHGPO) + { + // We want to batch this HGPO with the output object. Process it later. + UnassignedVolumeParts.Add(currentHGPO); + continue; + } } // Create a new output object @@ -1655,15 +1777,17 @@ FHoudiniOutputTranslator::BuildAllOutputs( RF_NoFlags); // Make sure the created object is valid - if (!HoudiniOutput || HoudiniOutput->IsPendingKill()) + if (!IsValid(HoudiniOutput)) { //HOUDINI_LOG_WARNING("Failed to create asset output"); continue; } // Mark if the HoudiniOutput is editable - HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); } + // Ensure that we always update the 'Editable' state of the output since this + // may very well change between cooks (for example, the User is editina the HDA is session sync). + HoudiniOutput->SetIsEditableNode(currentHGPO.bIsEditable); // Add the HGPO to the output HoudiniOutput->AddNewHGPO(currentHGPO); @@ -1725,7 +1849,7 @@ FHoudiniOutputTranslator::BuildAllOutputs( return Output ? Output->HeightfieldMatch(currentVolumeHGPO, false) : false; }); - if (!FoundHoudiniOutput || !(*FoundHoudiniOutput) || (*FoundHoudiniOutput)->IsPendingKill()) + if (!FoundHoudiniOutput || !IsValid(*FoundHoudiniOutput)) { // Skip - consider this volume as invalid continue; @@ -1749,7 +1873,7 @@ FHoudiniOutputTranslator::BuildAllOutputs( bool FHoudiniOutputTranslator::UpdateChangedOutputs(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; @@ -2205,7 +2329,7 @@ FHoudiniOutputTranslator::GetCustomPartNameFromAttribute(const HAPI_NodeId & Nod void FHoudiniOutputTranslator::GetTempFolderFromAttribute(UHoudiniAssetComponent * HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; HAPI_GeoInfo DisplayGeoInfo; diff --git a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h index 2c4e84c2a..db076c535 100644 --- a/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h +++ b/Source/HoudiniEngine/Private/HoudiniOutputTranslator.h @@ -66,9 +66,10 @@ struct HOUDINIENGINE_API FHoudiniOutputTranslator static bool BuildAllOutputs( const HAPI_NodeId& AssetId, UObject* InOuterObject, + const TArray& OutputNodes, + const TMap& OutputNodeCookCounts, TArray& InOldOutputs, TArray& OutNewOutputs, - TArray& OutNodeIdsToCook, const bool& InOutputTemplatedGeos, const bool& InUseOutputNodes); diff --git a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp index 91d16d8c3..da5754b05 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGManager.cpp @@ -31,6 +31,7 @@ #include "HAL/FileManager.h" #include "HoudiniApi.h" +#include "HoudiniAsset.h" #include "HoudiniEngine.h" #include "HoudiniEngineUtils.h" #include "HoudiniEngineString.h" @@ -62,7 +63,7 @@ FHoudiniPDGManager::~FHoudiniPDGManager() bool FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) { - if (!InHAC || InHAC->IsPendingKill()) + if (!IsValid(InHAC)) return false; int32 AssetId = InHAC->GetAssetId(); @@ -75,13 +76,13 @@ FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) // Create a new PDG Asset Link Object bool bRegisterPDGAssetLink = false; UHoudiniPDGAssetLink* PDGAssetLink = InHAC->GetPDGAssetLink(); - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + if (!IsValid(PDGAssetLink)) { PDGAssetLink = NewObject(InHAC, UHoudiniPDGAssetLink::StaticClass(), NAME_None, RF_Transactional); bRegisterPDGAssetLink = true; } - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + if (!IsValid(PDGAssetLink)) return false; PDGAssetLink->AssetID = AssetId; @@ -155,7 +156,7 @@ FHoudiniPDGManager::InitializePDGAssetLink(UHoudiniAssetComponent* InHAC) bool FHoudiniPDGManager::UpdatePDGAssetLink(UHoudiniPDGAssetLink* PDGAssetLink) { - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + if (!IsValid(PDGAssetLink)) return false; // If the PDG Asset link is inactive, indicate that our HDA must be instantiated @@ -213,7 +214,7 @@ bool FHoudiniPDGManager::PopulateTOPNetworks(UHoudiniPDGAssetLink* PDGAssetLink, bool bInZeroWorkItemTallys) { // Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. - if (!PDGAssetLink || PDGAssetLink->IsPendingKill()) + if (!IsValid(PDGAssetLink)) return false; // Get all the network nodes within the asset, recursively. @@ -448,7 +449,7 @@ bool FHoudiniPDGManager::PopulateTOPNodes( const TArray& InTopNodeIDs, UTOPNetwork* InTOPNetwork, UHoudiniPDGAssetLink* InPDGAssetLink, bool bInZeroWorkItemTallys) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; if (!IsValid(InTOPNetwork)) @@ -540,7 +541,7 @@ FHoudiniPDGManager::PopulateTOPNodes( if (!IsValid(CurTOPNode)) continue; - InPDGAssetLink->ClearTOPNodeWorkItemResults(CurTOPNode); + UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(CurTOPNode); } InTOPNetwork->AllTOPNodes = AllTOPNodes; @@ -745,7 +746,7 @@ FHoudiniPDGManager::Update() } UHoudiniPDGAssetLink* CurPDGAssetLink = PDGAssetLinks[Idx].Get(); - if (!CurPDGAssetLink || CurPDGAssetLink->IsPendingKill()) + if (!IsValid(CurPDGAssetLink)) { PDGAssetLinks.RemoveAt(Idx); continue; @@ -886,12 +887,8 @@ FHoudiniPDGManager::ProcessPDGEvent(const HAPI_PDG_GraphContextId& InContextID, const FString LastWorkitemStateName = FHoudiniEngineUtils::HapiGetWorkitemStateAsString(LastWorkItemState); if(!GetTOPAssetLinkNetworkAndNode(EventInfo.nodeId, PDGAssetLink, TOPNetwork, TOPNode) - || PDGAssetLink == nullptr || PDGAssetLink->IsPendingKill() - || TOPNetwork == nullptr || TOPNetwork->IsPendingKill() - || TOPNode == nullptr || TOPNode->IsPendingKill() - || TOPNode->NodeId != EventInfo.nodeId) - { - + || !IsValid(PDGAssetLink) || !IsValid(TOPNetwork) || !IsValid(TOPNode) || !IsValid(TOPNode)) + { HOUDINI_LOG_WARNING(TEXT("[ProcessPDGEvent]: Could not find matching TOPNode for event %s, workitem id %d, node id %d"), *EventName, EventInfo.workitemId, EventInfo.nodeId); return; } @@ -1140,7 +1137,7 @@ FHoudiniPDGManager::GetTOPAssetLinkNetworkAndNode( continue; UHoudiniPDGAssetLink* CurAssetLink = CurAssetLinkPtr.Get(); - if (!CurAssetLink || CurAssetLink->IsPendingKill()) + if (!IsValid(CurAssetLink)) continue; if (CurAssetLink->GetTOPNodeAndNetworkByNodeId((int32)InNodeID, OutTOPNetwork, OutTOPNode)) @@ -1302,7 +1299,7 @@ FHoudiniPDGManager::NotifyTOPNodeCookCancelledWorkItem(UHoudiniPDGAssetLink* InP void FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) { - if (!InAssetLink || InAssetLink->IsPendingKill()) + if (!IsValid(InAssetLink)) return; // TODO!!! @@ -1316,7 +1313,7 @@ FHoudiniPDGManager::ClearWorkItemResult(UHoudiniPDGAssetLink* InAssetLink, const void FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI_PDG_WorkitemId& InWorkItemID, UTOPNode* InTOPNode) { - if (!InAssetLink || InAssetLink->IsPendingKill()) + if (!IsValid(InAssetLink)) return; // Clear all of the work item's results for the specified TOP node and also remove the work item itself from @@ -1327,7 +1324,7 @@ FHoudiniPDGManager::RemoveWorkItem(UHoudiniPDGAssetLink* InAssetLink, const HAPI void FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) { - if (!InAssetLink || InAssetLink->IsPendingKill()) + if (!IsValid(InAssetLink)) return; // Only update the editor properties if the PDG asset link's Actor is selected @@ -1335,7 +1332,7 @@ FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) InAssetLink->UpdateWorkItemTally(); UHoudiniAssetComponent* HAC = Cast(InAssetLink->GetOuter()); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; AActor* ActorOwner = HAC->GetOwner(); @@ -1348,7 +1345,7 @@ FHoudiniPDGManager::RefreshPDGAssetLinkUI(UHoudiniPDGAssetLink* InAssetLink) void FHoudiniPDGManager::NotifyAssetCooked(UHoudiniPDGAssetLink* InAssetLink, const bool& bSuccess) { - if (!InAssetLink || InAssetLink->IsPendingKill()) + if (!IsValid(InAssetLink)) return; if (bSuccess) @@ -1393,17 +1390,18 @@ FHoudiniPDGManager::CreateOrRelinkWorkItem( } // Try to find the existing WorkItem by ID. - int32 Index = InTOPNode->IndexOfWorkResultByID(InWorkItemID); + int32 Index = InTOPNode->ArrayIndexOfWorkResultByID(InWorkItemID); if (Index == INDEX_NONE) { - // Try to find an entry with WorkItemID == INDEX_NONE but where workItemIndex matches. The WorkItemIDs are + // Try to find the first entry with WorkItemID == INDEX_NONE. The WorkItemIDs are // transient, so not saved when the map / asset link is saved. So when loading a map containing the asset - // link all the IDs are INDEX_NONE and the WorkItemIndex is the best way to find an entry to re-use. - const bool bWithInvalidWorkItemID = true; - Index = InTOPNode->IndexOfWorkResultByHAPIIndex(WorkItemInfo.index, bWithInvalidWorkItemID); + // link all the IDs are INDEX_NONE and so we re-use any stale entries in array index order (should be reliable + // if work items generate in the same order. In the future we might have to consider adding support for a + // custom ID attribute for more stable re-linking of work items). + Index = InTOPNode->ArrayIndexOfFirstInvalidWorkResult(); if (Index == INDEX_NONE) { - // If we couldn't find an existing entry, or a stale entry to re-use, create a new one + // If we couldn't find a stale entry to re-use, create a new one FTOPWorkResult LocalWorkResult; LocalWorkResult.WorkItemID = InWorkItemID; LocalWorkResult.WorkItemIndex = WorkItemInfo.index; @@ -1411,7 +1409,10 @@ FHoudiniPDGManager::CreateOrRelinkWorkItem( } else { - InTOPNode->WorkResult[Index].WorkItemID = InWorkItemID; + // We found a stale entry, re-use it + FTOPWorkResult& ReUsedWorkResult = InTOPNode->WorkResult[Index]; + ReUsedWorkResult.WorkItemID = InWorkItemID; + ReUsedWorkResult.WorkItemIndex = WorkItemInfo.index; } } @@ -1441,15 +1442,18 @@ FHoudiniPDGManager::CreateOrRelinkWorkItemResult( } // Try to find the existing WorkResult by ID. - FTOPWorkResult* WorkResult = UHoudiniPDGAssetLink::GetWorkResultByID(InWorkItemID, InTOPNode); + int32 WorkResultArrayIndex = InTOPNode->ArrayIndexOfWorkResultByID(InWorkItemID); + FTOPWorkResult* WorkResult = nullptr; + if (WorkResultArrayIndex != INDEX_NONE) + WorkResult = InTOPNode->GetWorkResultByArrayIndex(WorkResultArrayIndex); if (!WorkResult) { // TODO: This shouldn't really happen, it means a work item finished cooking and generated a result before // we received an event that the work item was added/generated. - int32 ArrayIndex = CreateOrRelinkWorkItem(InTOPNode, InContextID, InWorkItemID); - if (ArrayIndex != INDEX_NONE) + WorkResultArrayIndex = CreateOrRelinkWorkItem(InTOPNode, InContextID, InWorkItemID); + if (WorkResultArrayIndex != INDEX_NONE) { - WorkResult = InTOPNode->GetWorkResultByArrayIndex(ArrayIndex); + WorkResult = InTOPNode->GetWorkResultByArrayIndex(WorkResultArrayIndex); } } @@ -1499,7 +1503,7 @@ FHoudiniPDGManager::CreateOrRelinkWorkItemResult( TEXT("%s_%s_%d_%d"), *(InTOPNode->ParentName), *WorkItemName, - WorkItemInfo.index, + WorkResultArrayIndex, Idx); // int32 ExistingObjectIndex = WorkResult->ResultObjects.IndexOfByPredicate([WorkResultName](const FTOPWorkResultObject& InResultObject) @@ -1528,7 +1532,10 @@ FHoudiniPDGManager::CreateOrRelinkWorkItemResult( // are always saved and standalone, so if we want to automatically clean up old results then we // need to destroy the existing outputs if (BGEOCommandletStatus == EHoudiniBGEOCommandletStatus::Connected) - ExistingResultObject.DestroyResultOutputs(); + { + constexpr bool bDeleteOutputActors = false; + InTOPNode->DeleteWorkResultObjectOutputs(WorkResultArrayIndex, ExistingObjectIndex, bDeleteOutputActors); + } if ((ExistingResultObject.State == EPDGWorkResultState::Loaded || ExistingResultObject.State == EPDGWorkResultState::ToDelete || @@ -1564,8 +1571,7 @@ FHoudiniPDGManager::CreateOrRelinkWorkItemResult( { if (ResultIndicesThatWereReused.Contains(ResultObjectIndex)) continue; - FTOPWorkResultObject& ResultObject = WorkResult->ResultObjects[ResultObjectIndex]; - ResultObject.DestroyResultOutputsAndRemoveOutputActor(); + InTOPNode->DeleteWorkResultObjectOutputs(WorkResultArrayIndex, ResultObjectIndex); } WorkResult->ResultObjects = NewResultObjects; @@ -1620,6 +1626,7 @@ FHoudiniPDGManager::SyncAndPruneWorkItems(UTOPNode* InTOPNode) // Remove any work result entries with invalid IDs or where the WorkItemID is not in the set of ids returned by // HAPI (only if we could get the IDs from HAPI). + const FGuid HoudiniComponentGuid(InTOPNode->GetHoudiniComponentGuid()); int32 NumRemoved = 0; const int32 NumWorkItemsInArray = InTOPNode->WorkResult.Num(); for (int32 Index = NumWorkItemsInArray - 1; Index >= 0; --Index) @@ -1630,7 +1637,7 @@ FHoudiniPDGManager::SyncAndPruneWorkItems(UTOPNode* InTOPNode) HOUDINI_PDG_WARNING( TEXT("Pruning a FTOPWorkResult entry from TOP Node %d, WorkItemID %d, WorkItemIndex %d, Array Index %d"), InTOPNode->NodeId, WorkResult.WorkItemID, WorkResult.WorkItemIndex, Index); - WorkResult.ClearAndDestroyResultObjects(); + WorkResult.ClearAndDestroyResultObjects(HoudiniComponentGuid); InTOPNode->WorkResult.RemoveAt(Index); InTOPNode->OnWorkItemRemoved(WorkResult.WorkItemID); NumRemoved++; @@ -1715,11 +1722,18 @@ FHoudiniPDGManager::ProcessWorkItemResults() // ... All WorkResult CurrentTOPNode->bCachedHaveNotLoadedWorkResults = false; CurrentTOPNode->bCachedHaveLoadedWorkResults = false; - for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) + + const int32 NumWorkResults = CurrentTOPNode->WorkResult.Num(); + for (int32 WorkResultArrayIndex = 0; WorkResultArrayIndex < NumWorkResults; ++WorkResultArrayIndex) + // for (FTOPWorkResult& CurrentWorkResult : CurrentTOPNode->WorkResult) { + FTOPWorkResult& CurrentWorkResult = CurrentTOPNode->WorkResult[WorkResultArrayIndex]; // ... All WorkResultObjects - for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) + const int32 NumWorkResultObjects = CurrentWorkResult.ResultObjects.Num(); + for (int32 WorkResultObjectArrayIndex = 0; WorkResultObjectArrayIndex < NumWorkResultObjects; ++WorkResultObjectArrayIndex) + // for (FTOPWorkResultObject& CurrentWorkResultObj : CurrentWorkResult.ResultObjects) { + FTOPWorkResultObject& CurrentWorkResultObj = CurrentWorkResult.ResultObjects[WorkResultObjectArrayIndex]; if (CurrentWorkResultObj.State == EPDGWorkResultState::ToLoad) { CurrentWorkResultObj.State = EPDGWorkResultState::Loading; @@ -1728,6 +1742,9 @@ FHoudiniPDGManager::ProcessWorkItemResults() PackageParams.PDGTOPNetworkName = CurrentTOPNet->NodeName; PackageParams.PDGTOPNodeName = CurrentTOPNode->NodeName; PackageParams.PDGWorkItemIndex = CurrentWorkResult.WorkItemIndex; + // Use the array index to ensure uniqueness among the work items of the node ( + // CurrentWorkResult.WorkItemIndex is not necessarily unique) + PackageParams.PDGWorkResultArrayIndex = WorkResultArrayIndex; if (CommandletStatus == EHoudiniBGEOCommandletStatus::Connected) { @@ -1753,7 +1770,7 @@ FHoudiniPDGManager::ProcessWorkItemResults() // Broadcast that we have loaded the work result object to those interested AssetLink->OnWorkResultObjectLoaded.Broadcast( - AssetLink, CurrentTOPNode, CurrentWorkResult.WorkItemIndex, + AssetLink, CurrentTOPNode, WorkResultArrayIndex, CurrentWorkResultObj.WorkItemResultInfoIndex); } else @@ -1782,9 +1799,7 @@ FHoudiniPDGManager::ProcessWorkItemResults() CurrentWorkResultObj.State = EPDGWorkResultState::Deleting; // Delete and clean up that WRObj - CurrentWorkResultObj.DestroyResultOutputs(); - CurrentWorkResultObj.GetOutputActorOwner().DestroyOutputActor(); - CurrentWorkResultObj.State = EPDGWorkResultState::Deleted; + CurrentTOPNode->DeleteWorkResultObjectOutputs(WorkResultArrayIndex, WorkResultObjectArrayIndex); CurrentTOPNode->bCachedHaveNotLoadedWorkResults = true; } else if (CurrentWorkResultObj.State == EPDGWorkResultState::Deleted) @@ -1839,7 +1854,10 @@ void FHoudiniPDGManager::HandleImportBGEOResultMessage( return; } - FTOPWorkResult* WorkResult = AssetLink->GetWorkResultByID(InMessage.WorkItemId, TOPNode); + FTOPWorkResult* WorkResult = nullptr; + const int32 WorkResultArrayIndex = TOPNode->ArrayIndexOfWorkResultByID(InMessage.WorkItemId); + if (WorkResultArrayIndex != INDEX_NONE) + WorkResult = TOPNode->GetWorkResultByArrayIndex(WorkResultArrayIndex); if (WorkResult == nullptr) { HOUDINI_LOG_WARNING(TEXT("Failed to find TOP work result with id %d, aborting output object creation."), InMessage.WorkItemId); @@ -2042,7 +2060,7 @@ void FHoudiniPDGManager::HandleImportBGEOResultMessage( HOUDINI_LOG_MESSAGE(TEXT("Loaded geo for %s"), *InMessage.Name); // Broadcast that we have loaded the work result object to those interested AssetLink->OnWorkResultObjectLoaded.Broadcast( - AssetLink, TOPNode, WorkResult->WorkItemIndex, WorkResultObject->WorkItemResultInfoIndex); + AssetLink, TOPNode, WorkResultArrayIndex, WorkResultObject->WorkItemResultInfoIndex); } else { @@ -2177,12 +2195,26 @@ FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) if (InAssetId < 0) return false; + // Get the list of all non bypassed TOP nodes within the current network (ignoring schedulers) + int32 TOPNodeCount = 0; + if (HAPI_RESULT_SUCCESS != FHoudiniApi::ComposeChildNodeList( + FHoudiniEngine::Get().GetSession(), InAssetId, + HAPI_NodeType::HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_TOP_NONSCHEDULER | HAPI_NODEFLAGS_NON_BYPASS, true, &TOPNodeCount)) + { + return false; + } + + // We found valid TOP Nodes, this is a PDG HDA + if (TOPNodeCount > 0) + return true; + + /* // Get all the network nodes within the asset, recursively. // We're getting all networks because TOP network SOPs aren't considered being of TOP network type, but SOP type int32 NetworkNodeCount = 0; HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::ComposeChildNodeList( FHoudiniEngine::Get().GetSession(), InAssetId, - HAPI_NODETYPE_ANY, HAPI_NODEFLAGS_NETWORK, true, &NetworkNodeCount), false); + HAPI_NODETYPE_SOP | HAPI_NODETYPE_TOP, HAPI_NODEFLAGS_NETWORK, true, & NetworkNodeCount), false); if (NetworkNodeCount <= 0) return false; @@ -2221,6 +2253,8 @@ FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) // For each Network we found earlier, only consider those with TOP child nodes // If we find TOP nodes in a valid network, then consider this HDA a PDG HDA + HAPI_NodeInfo CurrentNodeInfo; + FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); for (const HAPI_NodeId& CurrentNodeId : AllNetworkNodeIDs) { if (CurrentNodeId < 0) @@ -2228,8 +2262,6 @@ FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) continue; } - HAPI_NodeInfo CurrentNodeInfo; - FHoudiniApi::NodeInfo_Init(&CurrentNodeInfo); if (HAPI_RESULT_SUCCESS != FHoudiniApi::GetNodeInfo( FHoudiniEngine::Get().GetSession(), CurrentNodeId, &CurrentNodeInfo)) { @@ -2256,6 +2288,7 @@ FHoudiniPDGManager::IsPDGAsset(const HAPI_NodeId& InAssetId) if (TOPNodeCount > 0) return true; } + */ // No valid TOP node found in SOP/TOP Networks, this is not a PDG HDA return false; diff --git a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp index 7a4352cb6..deb0de777 100644 --- a/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPDGTranslator.cpp @@ -432,18 +432,20 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( } USceneComponent* ParentComponent = Cast(InOuterComponent); - - if (ParentComponent) + if (IsValid(ParentComponent)) { AActor* ParentActor = ParentComponent->GetOwner(); for (UHoudiniOutput* LandscapeOutput : LandscapeOutputs) { + if(!IsValid(LandscapeOutput)) + continue; + for (auto& Pair : LandscapeOutput->GetOutputObjects()) { - FHoudiniOutputObject &OutputObject = Pair.Value; + FHoudiniOutputObject& OutputObject = Pair.Value; // If the OutputObject has an OutputComponent, try to attach it to ParentComponent - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) + if (IsValid(OutputObject.OutputComponent)) { USceneComponent* Component = Cast(OutputObject.OutputComponent); if (IsValid(Component) && !Component->IsAttachedTo(ParentComponent)) @@ -477,7 +479,6 @@ FHoudiniPDGTranslator::CreateAllResultObjectsFromPDGOutputs( LandscapeProxy->RecreateComponentsState(); } } - } } } diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp index 7c7c7e4bc..a0bd6a923 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.cpp @@ -61,6 +61,7 @@ FHoudiniPackageParams::FHoudiniPackageParams() PDGTOPNetworkName.Empty(); PDGTOPNodeName.Empty(); PDGWorkItemIndex = INDEX_NONE; + PDGWorkResultArrayIndex = INDEX_NONE; } @@ -91,11 +92,11 @@ FHoudiniPackageParams::GetPackageName() const return ObjectName; // If we have PDG infos, generate a name including them - if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkItemIndex >= 0) + if (!PDGTOPNetworkName.IsEmpty() && !PDGTOPNodeName.IsEmpty() && PDGWorkResultArrayIndex >= 0) { return FString::Printf( TEXT("%s_%s_%s_%d_%d_%s"), - *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkItemIndex, PartId, *SplitStr); + *HoudiniAssetName, *PDGTOPNetworkName, *PDGTOPNodeName, PDGWorkResultArrayIndex, PartId, *SplitStr); } else { @@ -325,7 +326,7 @@ FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InB FoundPackage = LoadPackage(nullptr, *FinalPackageName, LOAD_Verify | LOAD_NoWarn); } - if (FoundPackage && !FoundPackage->IsPendingKill()) + if (IsValid(FoundPackage)) { // we need to generate a new name for it CurrentGuid = FGuid::NewGuid(); @@ -354,6 +355,16 @@ FHoudiniPackageParams::CreatePackageForObject(FString& OutPackageName, int32 InB // HOUDINI_LOG_MESSAGE(TEXT("Recording temp guid in package metadata: %s"), *GuidStr); MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_TEMP_GUID, GuidStr); } + + // Store the Component GUID that generated the package, as well as the package name and meta key + // indicating that the package was generated by Houdini + { + const FString ComponentGuidStr = ComponentGUID.ToString(); + MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_COMPONENT_GUID, ComponentGuidStr); + + MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + MetaData->RootMetaDataMap.Add(HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *OutPackageName); + } } } @@ -380,7 +391,7 @@ T* FHoudiniPackageParams::CreateObjectAndPackage() // Create the package for the object FString NewObjectName; UPackage* Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return nullptr; const FString SanitizedObjectName = ObjectTools::SanitizeObjectName(NewObjectName); @@ -388,7 +399,7 @@ T* FHoudiniPackageParams::CreateObjectAndPackage() T* ExistingTypedObject = FindObject(Package, *NewObjectName); UObject* ExistingObject = FindObject(Package, *NewObjectName); - if (ExistingTypedObject != nullptr && !ExistingTypedObject->IsPendingKill()) + if (IsValid(ExistingTypedObject)) { // An object of the appropriate type already exists, update it! ExistingTypedObject->PreEditChange(nullptr); @@ -404,7 +415,7 @@ T* FHoudiniPackageParams::CreateObjectAndPackage() // Create a package for each mesh Package = CreatePackageForObject(NewObjectName); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return nullptr; } else @@ -414,12 +425,21 @@ T* FHoudiniPackageParams::CreateObjectAndPackage() } } - // Add meta information to this package. - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); - FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( - Package, Package, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); + T* const CreatedObject = NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); + + // Add meta information to the new object in the package. + if (IsValid(CreatedObject)) + { + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, CreatedObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT, TEXT("true")); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, CreatedObject, HAPI_UNREAL_PACKAGE_META_GENERATED_NAME, *NewObjectName); + + const FString ComponentGuidStr = ComponentGUID.ToString(); + FHoudiniEngineUtils::AddHoudiniMetaInformationToPackage( + Package, CreatedObject, HAPI_UNREAL_PACKAGE_META_COMPONENT_GUID, ComponentGuidStr); + } - return NewObject(Package, FName(*SanitizedObjectName), GetObjectFlags()); + return CreatedObject; } diff --git a/Source/HoudiniEngine/Private/HoudiniPackageParams.h b/Source/HoudiniEngine/Private/HoudiniPackageParams.h index 380035125..379d9b0d7 100644 --- a/Source/HoudiniEngine/Private/HoudiniPackageParams.h +++ b/Source/HoudiniEngine/Private/HoudiniPackageParams.h @@ -159,6 +159,9 @@ struct HOUDINIENGINE_API FHoudiniPackageParams // For PDG temporary outputs: the work item index of the TOP node UPROPERTY() int32 PDGWorkItemIndex; + // For PDG temporary outputs: the work item array index in the WorkResult array of the TOP node + UPROPERTY() + int32 PDGWorkResultArrayIndex; ////TODO: We don't have access to Houdini attributes in HoudiniEngine/HoudiniEnginePrivatePCH. //FString GetTempFolderArgument(ERuntimePackageMode PackageMode) const; @@ -207,6 +210,7 @@ struct HOUDINIENGINE_API FHoudiniPackageParams OutTokens.Add("pdg_topnet_name", ValueT( PDGTOPNetworkName )); OutTokens.Add("pdg_topnode_name", ValueT( PDGTOPNodeName )); OutTokens.Add("pdg_workitem_index", ValueT( FString::FromInt(PDGWorkItemIndex) )); + OutTokens.Add("pdg_workresult_array_index", ValueT( FString::FromInt(PDGWorkResultArrayIndex) )); OutTokens.Add("guid", ValueT( ComponentGUID.ToString() )); } diff --git a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp index 6443de9bc..b6df76444 100644 --- a/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniParameterTranslator.cpp @@ -75,7 +75,7 @@ bool FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // When recooking/rebuilding the HDA, force a full update of all params @@ -91,7 +91,7 @@ FHoudiniParameterTranslator::UpdateParameters(UHoudiniAssetComponent* HAC) // Destroy old/dangling parameters for (auto& OldParm : HAC->Parameters) { - if (!OldParm || OldParm->IsPendingKill()) + if (!IsValid(OldParm)) continue; OldParm->ConditionalBeginDestroy(); @@ -118,7 +118,7 @@ FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) // synced before the cook starts (Looking at you, ramp parameters!) for (UHoudiniParameter* Param : HAC->Parameters) { - if (!Param || Param->IsPendingKill()) + if (!IsValid(Param)) continue; Param->OnPreCook(); @@ -131,7 +131,7 @@ FHoudiniParameterTranslator::OnPreCookParameters(UHoudiniAssetComponent* HAC) bool FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) { - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; // Update all the parameters using the loaded parameter object @@ -147,7 +147,7 @@ FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) { UHoudiniParameter* Param = HAC->Parameters[Idx]; - if (!Param || Param->IsPendingKill()) + if (!IsValid(Param)) continue; switch(Param->GetParameterType()) @@ -199,7 +199,7 @@ FHoudiniParameterTranslator::UpdateLoadedParameters(UHoudiniAssetComponent* HAC) // Destroy old/dangling parameters for (auto& OldParm : HAC->Parameters) { - if (!OldParm || OldParm->IsPendingKill()) + if (!IsValid(OldParm)) continue; OldParm->ConditionalBeginDestroy(); @@ -626,13 +626,13 @@ FHoudiniParameterTranslator::BuildAllParameters( for (int32 Idx = 0; Idx < NewParameters.Num(); ++Idx) { UHoudiniParameter * CurParam = NewParameters[Idx]; - if (!CurParam || CurParam->IsPendingKill()) + if (!IsValid(CurParam)) continue; if (CurParam->GetParameterType() == EHoudiniParameterType::FolderList) { UHoudiniParameterFolderList* CurFolderList = Cast(CurParam); - if (!CurFolderList || CurFolderList->IsPendingKill()) + if (!IsValid(CurFolderList)) continue; int32 FirstChildIdx = Idx + 1; @@ -640,7 +640,7 @@ FHoudiniParameterTranslator::BuildAllParameters( continue; UHoudiniParameterFolder* FirstChildFolder = Cast(NewParameters[FirstChildIdx]); - if (!FirstChildFolder || FirstChildFolder->IsPendingKill()) + if (!IsValid(FirstChildFolder)) continue; if (FirstChildFolder->GetFolderType() == EHoudiniFolderParameterType::Radio || @@ -1097,7 +1097,7 @@ FHoudiniParameterTranslator::CheckParameterTypeAndClassMatch(UHoudiniParameter* bool FHoudiniParameterTranslator::CheckParameterClassAndInfoMatch(UHoudiniParameter* Parameter, const HAPI_ParmInfo& ParmInfo) { - if (!Parameter || Parameter->IsPendingKill()) + if (!IsValid(Parameter)) return false; UClass* FoundClass = Parameter->GetClass(); @@ -1297,7 +1297,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( const TArray* DefaultStringValues, const TArray* DefaultChoiceValues) { - if (!HoudiniParameter || HoudiniParameter->IsPendingKill()) + if (!IsValid(HoudiniParameter)) return false; // Copy values from the ParmInfos @@ -1446,7 +1446,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Button: { UHoudiniParameterButton* HoudiniParameterButton = Cast(HoudiniParameter); - if (HoudiniParameterButton && !HoudiniParameterButton->IsPendingKill()) + if (IsValid(HoudiniParameterButton)) { HoudiniParameterButton->SetValueIndex(ParmInfo.intValuesIndex); } @@ -1456,7 +1456,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::ButtonStrip: { UHoudiniParameterButtonStrip* HoudiniParameterButtonStrip = Cast(HoudiniParameter); - if (HoudiniParameterButtonStrip && !HoudiniParameterButtonStrip->IsPendingKill()) + if (IsValid(HoudiniParameterButtonStrip)) { HoudiniParameterButtonStrip->SetValueIndex(ParmInfo.intValuesIndex); HoudiniParameterButtonStrip->Count = ParmInfo.choiceCount; @@ -1534,7 +1534,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Color: { UHoudiniParameterColor* HoudiniParameterColor = Cast(HoudiniParameter); - if (HoudiniParameterColor && !HoudiniParameterColor->IsPendingKill()) + if (IsValid(HoudiniParameterColor)) { // Set the valueIndex HoudiniParameterColor->SetValueIndex(ParmInfo.floatValuesIndex); @@ -1581,7 +1581,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::ColorRamp: { UHoudiniParameterRampColor* HoudiniParameterRampColor = Cast(HoudiniParameter); - if (HoudiniParameterRampColor && !HoudiniParameterRampColor->IsPendingKill()) + if (IsValid(HoudiniParameterRampColor)) { HoudiniParameterRampColor->SetInstanceCount(ParmInfo.instanceCount); HoudiniParameterRampColor->MultiParmInstanceLength = ParmInfo.instanceLength; @@ -1591,7 +1591,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::FloatRamp: { UHoudiniParameterRampFloat* HoudiniParameterRampFloat = Cast(HoudiniParameter); - if (HoudiniParameterRampFloat && !HoudiniParameterRampFloat->IsPendingKill()) + if (IsValid(HoudiniParameterRampFloat)) { HoudiniParameterRampFloat->SetInstanceCount(ParmInfo.instanceCount); HoudiniParameterRampFloat->MultiParmInstanceLength = ParmInfo.instanceLength; @@ -1605,7 +1605,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::FileImage: { UHoudiniParameterFile* HoudiniParameterFile = Cast(HoudiniParameter); - if (HoudiniParameterFile && !HoudiniParameterFile->IsPendingKill()) + if (IsValid(HoudiniParameterFile)) { // Set the valueIndex HoudiniParameterFile->SetValueIndex(ParmInfo.stringValuesIndex); @@ -1689,7 +1689,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Float: { UHoudiniParameterFloat* HoudiniParameterFloat = Cast(HoudiniParameter); - if (HoudiniParameterFloat && !HoudiniParameterFloat->IsPendingKill()) + if (IsValid(HoudiniParameterFloat)) { // Set the valueIndex HoudiniParameterFloat->SetValueIndex(ParmInfo.floatValuesIndex); @@ -1853,7 +1853,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Folder: { UHoudiniParameterFolder* HoudiniParameterFolder = Cast(HoudiniParameter); - if (HoudiniParameterFolder && !HoudiniParameterFolder->IsPendingKill()) + if (IsValid(HoudiniParameterFolder)) { // Set the valueIndex HoudiniParameterFolder->SetValueIndex(ParmInfo.intValuesIndex); @@ -1865,7 +1865,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::FolderList: { UHoudiniParameterFolderList* HoudiniParameterFolderList = Cast(HoudiniParameter); - if (HoudiniParameterFolderList && !HoudiniParameterFolderList->IsPendingKill()) + if (IsValid(HoudiniParameterFolderList)) { // Set the valueIndex HoudiniParameterFolderList->SetValueIndex(ParmInfo.intValuesIndex); @@ -1877,7 +1877,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( { // Inputs parameters are just stored, and handled separately by UHoudiniInputs UHoudiniParameterOperatorPath* HoudiniParameterOperatorPath = Cast(HoudiniParameter); - if (HoudiniParameterOperatorPath && !HoudiniParameterOperatorPath->IsPendingKill()) + if (IsValid(HoudiniParameterOperatorPath)) { /* // DO NOT CREATE A DUPLICATE INPUT HERE! @@ -1891,7 +1891,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( if (!ParentHAC) return false; - if (!NewInput || NewInput->IsPendingKill()) + if (!IsValid(NewInput)) return false; // Set the nodeId @@ -1908,7 +1908,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Int: { UHoudiniParameterInt* HoudiniParameterInt = Cast(HoudiniParameter); - if (HoudiniParameterInt && !HoudiniParameterInt->IsPendingKill()) + if (IsValid(HoudiniParameterInt)) { // Set the valueIndex HoudiniParameterInt->SetValueIndex(ParmInfo.intValuesIndex); @@ -2028,7 +2028,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::IntChoice: { UHoudiniParameterChoice* HoudiniParameterIntChoice = Cast(HoudiniParameter); - if (HoudiniParameterIntChoice && !HoudiniParameterIntChoice->IsPendingKill()) + if (IsValid(HoudiniParameterIntChoice)) { // Set the valueIndex HoudiniParameterIntChoice->SetValueIndex(ParmInfo.intValuesIndex); @@ -2156,7 +2156,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::StringChoice: { UHoudiniParameterChoice* HoudiniParameterStringChoice = Cast(HoudiniParameter); - if (HoudiniParameterStringChoice && !HoudiniParameterStringChoice->IsPendingKill()) + if (IsValid(HoudiniParameterStringChoice)) { // Set the valueIndex HoudiniParameterStringChoice->SetValueIndex(ParmInfo.stringValuesIndex); @@ -2267,7 +2267,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Label: { UHoudiniParameterLabel* HoudiniParameterLabel = Cast(HoudiniParameter); - if (HoudiniParameterLabel && !HoudiniParameterLabel->IsPendingKill()) + if (IsValid(HoudiniParameterLabel)) { if (ParmInfo.type != HAPI_PARMTYPE_LABEL) return false; @@ -2317,7 +2317,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::MultiParm: { UHoudiniParameterMultiParm* HoudiniParameterMulti = Cast(HoudiniParameter); - if (HoudiniParameterMulti && !HoudiniParameterMulti->IsPendingKill()) + if (IsValid(HoudiniParameterMulti)) { if (ParmInfo.type != HAPI_PARMTYPE_MULTIPARMLIST) return false; @@ -2359,7 +2359,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Separator: { UHoudiniParameterSeparator* HoudiniParameterSeparator = Cast(HoudiniParameter); - if (HoudiniParameterSeparator && !HoudiniParameterSeparator->IsPendingKill()) + if (IsValid(HoudiniParameterSeparator)) { // We can only handle separator type. if (ParmInfo.type != HAPI_PARMTYPE_SEPARATOR) @@ -2375,7 +2375,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::StringAssetRef: { UHoudiniParameterString* HoudiniParameterString = Cast(HoudiniParameter); - if (HoudiniParameterString && !HoudiniParameterString->IsPendingKill()) + if (IsValid(HoudiniParameterString)) { // We can only handle string type. if (ParmInfo.type != HAPI_PARMTYPE_STRING && ParmInfo.type != HAPI_PARMTYPE_NODE) @@ -2434,7 +2434,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( if (bHasValidNodeId) { HoudiniParameterString->SetIsAssetRef( - FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HAPI_PARAM_TAG_ASSET_REF)); + FHoudiniParameterTranslator::HapiGetParameterHasTag(InNodeId, ParmInfo.id, HOUDINI_PARAMETER_STRING_REF_TAG)); } } } @@ -2444,7 +2444,7 @@ FHoudiniParameterTranslator::UpdateParameterFromInfo( case EHoudiniParameterType::Toggle: { UHoudiniParameterToggle* HoudiniParameterToggle = Cast(HoudiniParameter); - if (HoudiniParameterToggle && !HoudiniParameterToggle->IsPendingKill()) + if (IsValid(HoudiniParameterToggle)) { if (ParmInfo.type != HAPI_PARMTYPE_TOGGLE) return false; @@ -2582,7 +2582,7 @@ FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * H { TRACE_CPUPROFILER_EVENT_SCOPE(FHoudiniParameterTranslator::UploadChangedParameters); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return false; TMap RampsToRevert; @@ -2596,7 +2596,7 @@ FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * H for (int32 ParmIdx = 0; ParmIdx < HAC->GetNumParameters(); ParmIdx++) { UHoudiniParameter*& CurrentParm = HAC->Parameters[ParmIdx]; - if (!CurrentParm || CurrentParm->IsPendingKill() || !CurrentParm->HasChanged()) + if (!IsValid(CurrentParm) || !CurrentParm->HasChanged()) continue; bool bSuccess = false; @@ -2655,7 +2655,7 @@ FHoudiniParameterTranslator::UploadChangedParameters( UHoudiniAssetComponent * H bool FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return false; switch (InParam->GetParameterType()) @@ -2663,7 +2663,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::Float: { UHoudiniParameterFloat* FloatParam = Cast(InParam); - if (!FloatParam || FloatParam->IsPendingKill()) + if (!IsValid(FloatParam)) { return false; } @@ -2683,7 +2683,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::Int: { UHoudiniParameterInt* IntParam = Cast(InParam); - if (!IntParam || IntParam->IsPendingKill()) + if (!IsValid(IntParam)) { return false; } @@ -2703,7 +2703,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::String: { UHoudiniParameterString* StringParam = Cast(InParam); - if (!StringParam || StringParam->IsPendingKill()) + if (!IsValid(StringParam)) { return false; } @@ -2727,7 +2727,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::IntChoice: { UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) + if (!IsValid(ChoiceParam)) return false; // Set the parameter's int value. @@ -2742,7 +2742,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::StringChoice: { UHoudiniParameterChoice* ChoiceParam = Cast(InParam); - if (!ChoiceParam || ChoiceParam->IsPendingKill()) + if (!IsValid(ChoiceParam)) { return false; } @@ -2769,7 +2769,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) case EHoudiniParameterType::Color: { UHoudiniParameterColor* ColorParam = Cast(InParam); - if (!ColorParam || ColorParam->IsPendingKill()) + if (!IsValid(ColorParam)) return false; bool bHasAlpha = ColorParam->GetTupleSize() == 4 ? true : false; @@ -2882,7 +2882,7 @@ FHoudiniParameterTranslator::UploadParameterValue(UHoudiniParameter* InParam) bool FHoudiniParameterTranslator::RevertParameterToDefault(UHoudiniParameter* InParam) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return false; if (!InParam->IsPendingRevertToDefault()) @@ -2970,7 +2970,7 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad( UHoudiniParameter* InParam, TArray& OldParams, const int32& InAssetId, const HAPI_AssetInfo& AssetInfo) { UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam || MultiParam->IsPendingKill()) + if (!IsValid(MultiParam)) return false; UHoudiniParameterRampFloat* FloatRampParameter = nullptr; @@ -3130,7 +3130,7 @@ FHoudiniParameterTranslator::SyncMultiParmValuesAtLoad( bool FHoudiniParameterTranslator::UploadRampParameter(UHoudiniParameter* InParam) { UHoudiniParameterMultiParm* MultiParam = Cast(InParam); - if (!MultiParam || MultiParam->IsPendingKill()) + if (!IsValid(MultiParam)) return false; UHoudiniAssetComponent* HoudiniAssetComponent = Cast(InParam->GetOuter()); diff --git a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp index c5dca7692..4f329835b 100644 --- a/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/HoudiniSplineTranslator.cpp @@ -237,7 +237,7 @@ FHoudiniSplineTranslator::HapiUpdateNodeForHoudiniSplineComponent( UHoudiniSplineComponent* HoudiniSplineComponent, bool bInAddRotAndScaleAttributes) { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return true; TArray PositionArray; @@ -1059,7 +1059,7 @@ FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(co if (GeoId < 0) return nullptr; - if (!OuterComponent || OuterComponent->IsPendingKill()) + if (!IsValid(OuterComponent)) return nullptr; USceneComponent* const SceneComponent = Cast(OuterComponent); @@ -1091,11 +1091,11 @@ FHoudiniSplineTranslator::CreateHoudiniSplineComponentFromHoudiniEditableNode(co UHoudiniSplineComponent* FHoudiniSplineTranslator::CreateOutputHoudiniSplineComponent(TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UHoudiniAssetComponent* OuterHAC) { - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return nullptr; UObject* Outer = nullptr; - if (OuterHAC && !OuterHAC->IsPendingKill()) + if (IsValid(OuterHAC)) Outer = OuterHAC->GetOwner() ? OuterHAC->GetOwner() : OuterHAC->GetOuter(); UHoudiniSplineComponent *NewHoudiniSplineComponent = NewObject(Outer, UHoudiniSplineComponent::StaticClass(), NAME_None, RF_Transactional); @@ -1138,7 +1138,7 @@ USplineComponent* FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, const TArray& CurveRotations, const TArray& CurveScales, UObject* OuterComponent, const bool& bIsLinear, const bool& bIsClosed) { - if (!OuterComponent || OuterComponent->IsPendingKill()) + if (!IsValid(OuterComponent)) return nullptr; USceneComponent* OuterSceneComponent = Cast(OuterComponent); @@ -1217,7 +1217,7 @@ FHoudiniSplineTranslator::CreateOutputUnrealSplineComponent(const TArray& CurvePoints, USplineComponent* EditedSplineComponent, const EHoudiniCurveType& CurveType, const bool& bClosed) { - if (!EditedSplineComponent || EditedSplineComponent->IsPendingKill()) + if (!IsValid(EditedSplineComponent)) return false; if (CurvePoints.Num() < 2) @@ -1248,7 +1248,7 @@ FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(const TArray& CurvePoints, UHoudiniSplineComponent* EditedHoudiniSplineComponent) { - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return false; if (CurvePoints.Num() < 2) @@ -1309,7 +1309,7 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( return true; } - if (!InOuterComponent || InOuterComponent->IsPendingKill()) + if (!IsValid(InOuterComponent)) return false; int32 CurveNodeId = InHGPO.GeoId; @@ -1382,7 +1382,7 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( bNeedToRebuildSpline = true; USceneComponent* FoundComponent = Cast(FoundOutputObject ? FoundOutputObject->OutputComponent : nullptr); - if (FoundComponent && !FoundComponent->IsPendingKill()) + if (IsValid(FoundComponent)) { // Only support output to Unreal Spline for now... //if (FoundComponent->IsA() && FoundOutputObject->CurveOutputProperty.CurveOutputType != EHoudiniCurveOutputType::UnrealSpline) @@ -1440,6 +1440,11 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( } else { + // If this is not a new output object we have to clear the CachedAttributes and CachedTokens before + // setting the new values (so that we do not re-use any values from the previous cook) + FoundOutputObject->CachedAttributes.Empty(); + FoundOutputObject->CachedTokens.Empty(); + // if (FoundOutputObject->CurveOutputProperty.CurveOutputType == EHoudiniCurveOutputType::UnrealSpline) { @@ -1457,7 +1462,7 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( if (!FHoudiniSplineTranslator::UpdateOutputUnrealSplineComponent(CurvesDisplayPoints[n], FoundUnrealSpline, FoundOutputObject->CurveOutputProperty.CurveType, FoundOutputObject->CurveOutputProperty.bClosed)) continue; - OutSplines.Add(CurveIdentifier, *FoundOutputObject); + FoundOutputObject = &OutSplines.Add(CurveIdentifier, *FoundOutputObject); } else { @@ -1475,7 +1480,7 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( FoundOutputObject->OutputComponent = NewUnrealSpline; - OutSplines.Add(CurveIdentifier, *FoundOutputObject); + FoundOutputObject = &OutSplines.Add(CurveIdentifier, *FoundOutputObject); } } // We current support Unreal Spline output only... @@ -1543,6 +1548,17 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( } } + TArray BakeNames; + if (FoundOutputObject && FHoudiniEngineUtils::GetBakeNameAttribute( + InHGPO.GeoId, InHGPO.PartId, BakeNames, 0, 1)) + { + if (BakeNames.Num() > 0 && !BakeNames[0].IsEmpty()) + { + // cache the output name attribute on the output object + FoundOutputObject->CachedAttributes.Add(HAPI_UNREAL_ATTRIB_BAKE_NAME, BakeNames[0]); + } + } + TArray BakeOutputActorNames; if (FoundOutputObject && FHoudiniEngineUtils::GetBakeActorAttribute( InHGPO.GeoId, InHGPO.PartId, BakeOutputActorNames, HAPI_ATTROWNER_INVALID, 0, 1)) @@ -1602,10 +1618,10 @@ FHoudiniSplineTranslator::CreateOutputSplinesFromHoudiniGeoPartObject( bool FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOutput, UObject* InOuterComponent) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; - if (!InOuterComponent || InOuterComponent->IsPendingKill()) + if (!IsValid(InOuterComponent)) return false; // ONLY DO THIS ON CURVES!!!! @@ -1614,7 +1630,7 @@ FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOu // UHoudiniAssetComponent* OuterHAC = Cast(InOuterComponent); // - // if (!OuterHAC || OuterHAC->IsPendingKill()) + // if (!IsValid(OuterHAC)) // return false; TMap NewOutputObjects; @@ -1682,7 +1698,7 @@ FHoudiniSplineTranslator::CreateAllSplinesFromHoudiniOutput(UHoudiniOutput* InOu { USceneComponent* OldSplineSceneComponent = Cast(OldPair.Value.OutputComponent); - if (!OldSplineSceneComponent || OldSplineSceneComponent->IsPendingKill()) + if (!IsValid(OldSplineSceneComponent)) continue; // The output object is supposed to be a spline diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp index 90c2f02c7..5f9e0cce5 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.cpp @@ -150,14 +150,22 @@ FString FHoudiniAttributeResolver::ResolveFullLevelPath() const return FHoudiniEngineRuntimeUtils::JoinPaths(OutputFolder, LevelPathAttr); } -FString FHoudiniAttributeResolver::ResolveOutputName() const +FString FHoudiniAttributeResolver::ResolveOutputName(const bool bInForBake) const { FString OutputAttribName; if (CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2)) + { OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V2; + } + else if (bInForBake && CachedAttributes.Contains(HAPI_UNREAL_ATTRIB_BAKE_NAME)) + { + OutputAttribName = HAPI_UNREAL_ATTRIB_BAKE_NAME; + } else + { OutputAttribName = HAPI_UNREAL_ATTRIB_CUSTOM_OUTPUT_NAME_V1; + } return ResolveAttribute(OutputAttribName, TEXT("{object_name}")); } diff --git a/Source/HoudiniEngine/Private/HoudiniStringResolver.h b/Source/HoudiniEngine/Private/HoudiniStringResolver.h index bedf2768d..c0ea28b4b 100644 --- a/Source/HoudiniEngine/Private/HoudiniStringResolver.h +++ b/Source/HoudiniEngine/Private/HoudiniStringResolver.h @@ -99,7 +99,7 @@ struct HOUDINIENGINE_API FHoudiniAttributeResolver : public FHoudiniStringResolv FString ResolveFullLevelPath() const; // Helper for resolver custom output name attributes. - FString ResolveOutputName() const; + FString ResolveOutputName(bool bInForBake=false) const; // Helper for resolving the unreal_bake_folder attribute. Converts to an absolute path. FString ResolveBakeFolder() const; diff --git a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp index 75d4f1dc2..84c891640 100644 --- a/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealBrushTranslator.cpp @@ -249,7 +249,7 @@ bool FUnrealBrushTranslator::CreateInputNodeForBrush( for (int32 PosIndex = 0; PosIndex < NumPoints; ++PosIndex) { FVector Point = BrushModel->Points[PosIndex]; - Point = ActorTransformInverse.TransformPosition(Point); + Point = ActorTransform.InverseTransformPosition(Point); FVector Pos(Point.X, Point.Z, Point.Y); OutPosition[PosIndex] = Pos/HAPI_UNREAL_SCALE_FACTOR_POSITION; } diff --git a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp index 9b3ce7d63..fda662186 100644 --- a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.cpp @@ -79,9 +79,10 @@ bool FUnrealFoliageTypeTranslator::CreateInputNodeForReference( HAPI_NodeId& InInputNodeId, const FString& InRef, const FString& InInputNodeName, - const FTransform& InTransform) + const FTransform& InTransform, + const bool& bImportAsReferenceRotScaleEnabled) { - bool bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InInputNodeId, InRef, InInputNodeName, InTransform); + bool bSuccess = FHoudiniInputTranslator::CreateInputNodeForReference(InInputNodeId, InRef, InInputNodeName, InTransform, bImportAsReferenceRotScaleEnabled); if (!bSuccess) return false; diff --git a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h index 2e0d14c81..ae84271a0 100644 --- a/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealFoliageTypeTranslator.h @@ -57,7 +57,8 @@ struct HOUDINIENGINE_API FUnrealFoliageTypeTranslator : public FUnrealMeshTransl HAPI_NodeId& InInputNodeId, const FString& InRef, const FString& InInputNodeName, - const FTransform& InTransform); + const FTransform& InTransform, + const bool& bImportAsReferenceRotScaleEnabled); protected: // Creates the unreal_foliage and unreal_uproperty_ attributes for the foliage type. diff --git a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp index 3857d0416..6a298e3bb 100644 --- a/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealInstanceTranslator.cpp @@ -50,7 +50,7 @@ FUnrealInstanceTranslator::HapiCreateInputNodeForInstancer( // Get the Static Mesh instanced by the component UStaticMesh* SM = ISMC->GetStaticMesh(); - if (!SM || SM->IsPendingKill()) + if (!IsValid(SM)) return true; // Marshall the Static Mesh to Houdini diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp index 116f3399d..88b4b8f19 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.cpp @@ -336,14 +336,15 @@ FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( FHoudiniEngine::Get().GetSession(), HeightId), false); //-------------------------------------------------------------------------------------------------- - // 5. Extract and convert all the layers - //-------------------------------------------------------------------------------------------------- + // 5. Extract and convert all the layers + //-------------------------------------------------------------------------------------------------- ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); if (!LandscapeInfo) return false; - bool MaskInitialized = false; int32 MergeInputIndex = 2; + if (!ExtractAndConvertAllLandscapeLayers(LandscapeProxy, HeightFieldId, PartId, MergeId, MaskId, HeightfieldVolumeInfo, XSize, YSize, MergeInputIndex)) + return false; auto MergeInputFn = [&MergeInputIndex] (const HAPI_NodeId MergeId, const HAPI_NodeId NodeId) -> HAPI_Result { @@ -358,101 +359,6 @@ FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( } return Result; }; - - int32 NumLayers = LandscapeInfo->Layers.Num(); - for (int32 n = 0; n < NumLayers; n++) - { - // 1. Extract the uint8 values from the layer - TArray CurrentLayerIntData; - FLinearColor LayerUsageDebugColor; - FString LayerName; - if (!GetLandscapeLayerData(LandscapeProxy, LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) - continue; - - // 2. Convert unreal uint8 values to floats - // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float - HAPI_VolumeInfo CurrentLayerVolumeInfo; - FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); - TArray CurrentLayerFloatData; - if (!ConvertLandscapeLayerDataToHeightfieldData( - CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, - CurrentLayerFloatData, CurrentLayerVolumeInfo)) - continue; - - // We reuse the height layer's transform - CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; - - // 3. See if we need to create an input volume, or can reuse the HF's default mask volume - bool IsMask = false; - if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) - IsMask = true; - - HAPI_NodeId LayerVolumeNodeId = -1; - if (!IsMask) - { - // Current layer is not mask, so we need to create a new input volume - std::string LayerNameStr; - FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); - - FHoudiniApi::CreateHeightfieldInputVolumeNode( - FHoudiniEngine::Get().GetSession(), - HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); - } - else - { - // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node - LayerVolumeNodeId = MaskId; - } - - // Check if we have a valid id for the input volume. - if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) - continue; - - // 4. Set the layer/mask heighfield data in Houdini - HAPI_PartId CurrentPartId = 0; - if (!SetHeightfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) - continue; - - // Get the physical material used by that layer - UPhysicalMaterial* LayerPhysicalMat = LandscapeProxy->DefaultPhysMaterial; - { - FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; - ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; - if (LayerInfo) - LayerPhysicalMat = LayerInfo->PhysMaterial; - } - - // Apply attributes to the heightfield input node - ApplyAttributesToHeightfieldNode(LayerVolumeNodeId, PartId, LandscapeProxy); - - // Commit the volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); - - if (!IsMask) - { - // We had to create a new volume for this layer, so we need to connect it to the HF's merge node - HOUDINI_CHECK_ERROR_RETURN(MergeInputFn(MergeId, LayerVolumeNodeId), false); - } - else - { - MaskInitialized = true; - } - } - - // We need to have a mask layer as it is required for proper heightfield functionalities - // Setting the volume info on the mask is needed for the HF to have proper transform in H! - // If we didn't create a mask volume before, send a default one now - if (!MaskInitialized) - { - MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); - - ApplyAttributesToHeightfieldNode(MaskId, PartId, LandscapeProxy); - - // Commit the mask volume's geo - HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( - FHoudiniEngine::Get().GetSession(), MaskId), false); - } // We need a valid landscape actor to get the edit layers ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); @@ -600,6 +506,209 @@ FUnrealLandscapeTranslator::CreateHeightfieldFromLandscape( return true; } +bool FUnrealLandscapeTranslator::CreateHeightfieldFromLandscapeComponentArray(ALandscapeProxy* LandscapeProxy, + const TSet& SelectedComponents, HAPI_NodeId& CreatedHeightfieldNodeId, + const FString& InputNodeNameStr) +{ + if ( SelectedComponents.Num() <= 0 ) + return false; + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!IsValid(LandscapeInfo)) + return false; + + //-------------------------------------------------------------------------------------------------- + // Each selected component will be exported as tiled volumes in a single heightfield + //-------------------------------------------------------------------------------------------------- + //FTransform LandscapeTransform = FTransform::Identity; // The offset will be done in the component side + FTransform LandscapeTM = LandscapeProxy->LandscapeActorToWorld(); + FTransform ProxyRelativeTM(FVector(LandscapeProxy->LandscapeSectionOffset)); + FTransform LandscapeTransform = ProxyRelativeTM * LandscapeTM; + + HAPI_NodeId HeightfieldNodeId = -1; + HAPI_NodeId HeightfieldeMergeId = -1; + + int32 MergeInputIndex = 0; + bool bAllComponentCreated = true; + + int32 ComponentIdx = 0; + + LandscapeInfo->ForAllLandscapeComponents([&](ULandscapeComponent* CurrentComponent) + { + if ( !CurrentComponent ) + return; + + if ( !SelectedComponents.Contains( CurrentComponent ) ) + return; + + if ( !CreateHeightfieldFromLandscapeComponent( LandscapeProxy, CurrentComponent, ComponentIdx, HeightfieldNodeId, HeightfieldeMergeId, MergeInputIndex, InputNodeNameStr, LandscapeTransform ) ) + bAllComponentCreated = false; + + ComponentIdx++; + }); + + // Check that we have a valid id for the input Heightfield. + if ( FHoudiniEngineUtils::IsHoudiniNodeValid( HeightfieldNodeId ) ) + CreatedHeightfieldNodeId = HeightfieldNodeId; + + // Set the HF's parent OBJ's tranform to the Landscape's transform + HAPI_TransformEuler HAPIObjectTransform; + FHoudiniApi::TransformEuler_Init(&HAPIObjectTransform); + //FMemory::Memzero< HAPI_TransformEuler >( HAPIObjectTransform ); + LandscapeTransform.SetScale3D( FVector::OneVector ); + FHoudiniEngineUtils::TranslateUnrealTransform( LandscapeTransform, HAPIObjectTransform ); + HAPIObjectTransform.position[ 1 ] = 0.0f; + + HAPI_NodeId ParentObjNodeId = FHoudiniEngineUtils::HapiGetParentNodeId( HeightfieldNodeId ); + FHoudiniApi::SetObjectTransform( FHoudiniEngine::Get().GetSession(), ParentObjNodeId, &HAPIObjectTransform ); + + return bAllComponentCreated; +} + +bool FUnrealLandscapeTranslator::CreateHeightfieldFromLandscapeComponent(ALandscapeProxy* LandscapeProxy, ULandscapeComponent* LandscapeComponent, + const int32& ComponentIndex, HAPI_NodeId& HeightFieldId, HAPI_NodeId& MergeId, int32& MergeInputIndex, const FString& InputNodeNameStr, const FTransform & ParentTransform) +{ + if ( !LandscapeComponent ) + return false; + + FString NodeName = InputNodeNameStr + TEXT("_") + LandscapeProxy->GetName(); + + //-------------------------------------------------------------------------------------------------- + // 1. Extract the height data + //-------------------------------------------------------------------------------------------------- + int32 MinX = MAX_int32; + int32 MinY = MAX_int32; + int32 MaxX = -MAX_int32; + int32 MaxY = -MAX_int32; + LandscapeComponent->GetComponentExtent( MinX, MinY, MaxX, MaxY ); + + ULandscapeInfo* LandscapeInfo = LandscapeComponent->GetLandscapeInfo(); + if ( !LandscapeInfo ) + return false; + + TArray HeightData; + int32 XSize, YSize; + if ( !GetLandscapeData( LandscapeInfo, MinX, MinY, MaxX, MaxY, HeightData, XSize, YSize ) ) + return false; + + FVector Origin = LandscapeComponent->Bounds.Origin; + FVector Extents = LandscapeComponent->Bounds.BoxExtent; + FVector Min = Origin - Extents; + FVector Max = Origin + Extents; + + //-------------------------------------------------------------------------------------------------- + // 2. Convert the landscape's height uint16 data to float + //-------------------------------------------------------------------------------------------------- + TArray HeightfieldFloatValues; + HAPI_VolumeInfo HeightfieldVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&HeightfieldVolumeInfo); + FTransform LandscapeComponentTransform = LandscapeComponent->GetComponentTransform(); + + FVector CenterOffset = FVector::ZeroVector; + if ( !ConvertLandscapeDataToHeightfieldData( + HeightData, XSize, YSize, Min, Max, LandscapeComponentTransform, + HeightfieldFloatValues, HeightfieldVolumeInfo, + CenterOffset ) ) + return false; + + // We need to modify the Volume's position to the Component's position relative to the Landscape's position + FVector RelativePosition = LandscapeComponent->GetRelativeTransform().GetLocation(); + HeightfieldVolumeInfo.transform.position[1] = RelativePosition.X; + HeightfieldVolumeInfo.transform.position[0] = RelativePosition.Y; + HeightfieldVolumeInfo.transform.position[2] = 0.0f; + + ALandscapeProxy * Proxy = LandscapeComponent->GetLandscapeProxy(); + if (Proxy) + { + FTransform LandscapeTM = Proxy->LandscapeActorToWorld(); + FTransform ProxyRelativeTM(FVector(Proxy->LandscapeSectionOffset)); + + // For landscapes that live in streaming proxies, need to account for both the parent transform and + // the current transform. + // For single actor landscapes, the parent proxy transform is equal to this proxy transform + // Either way, we want to multiply by the inverse of the parent transform to get the relative + FTransform LandscapeTransform = ParentTransform.Inverse() * ProxyRelativeTM * LandscapeTM; + FVector Location = LandscapeTransform.GetLocation(); + + HeightfieldVolumeInfo.transform.position[1] += Location.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; + HeightfieldVolumeInfo.transform.position[0] += Location.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; + } + + //-------------------------------------------------------------------------------------------------- + // 3. Create the Heightfield Input Node + //-------------------------------------------------------------------------------------------------- + HAPI_NodeId HeightId = -1; + HAPI_NodeId MaskId = -1; + bool CreatedHeightfieldNode = false; + if ( HeightFieldId < 0 || MergeId < 0 ) + { + if (!CreateHeightfieldInputNode(NodeName, XSize, YSize, HeightFieldId, HeightId, MaskId, MergeId)) + return false; + + MergeInputIndex = 2; + CreatedHeightfieldNode = true; + } + else + { + // Heightfield node was previously created, create additionnal height and a mask volumes for it + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &HeightId, "height", XSize, YSize, 1.0f ); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &MaskId, "mask", XSize, YSize, 1.0f ); + + // Connect the two newly created volumes to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex++, HeightId, 0 ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, MergeInputIndex++, MaskId, 0 ), false ); + } + + //-------------------------------------------------------------------------------------------------- + // 4. Set the HeightfieldData in Houdini + //-------------------------------------------------------------------------------------------------- + // Set the Height volume's data + HAPI_PartId PartId = 0; + if (!SetHeightfieldData(HeightId, PartId, HeightfieldFloatValues, HeightfieldVolumeInfo, TEXT("height"))) + return false; + + // Apply tile attribute + AddLandscapeTileAttribute(HeightId, PartId, ComponentIndex); + + // Apply attributes to the heightfield + ApplyAttributesToHeightfieldNode(HeightId, PartId, LandscapeProxy); + + // Commit the height volume + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), HeightId), false); + + //-------------------------------------------------------------------------------------------------- + // 5. Extract and convert all the layers to HF masks + //-------------------------------------------------------------------------------------------------- + if (!ExtractAndConvertAllLandscapeLayers(LandscapeProxy, HeightFieldId, PartId, MergeId, MaskId, HeightfieldVolumeInfo, XSize, YSize, MergeInputIndex)) + return false; + + if ( CreatedHeightfieldNode ) + { + // Since HF are centered but landscape arent, we need to set the HF's center parameter + // Do it only once after creating the Heightfield node + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 0, CenterOffset.X); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 1, 0.0); + FHoudiniApi::SetParmFloatValue(FHoudiniEngine::Get().GetSession(), HeightFieldId, "t", 2, CenterOffset.Y); + } + + // Finally, cook the Heightfield node + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CookNode( + FHoudiniEngine::Get().GetSession(), HeightFieldId, nullptr), false); + return true; +} + + bool FUnrealLandscapeTranslator::CreateInputNodeForLandscape( ALandscapeProxy* LandscapeProxy, @@ -1134,7 +1243,7 @@ bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( return false; // LANDSCAPE MATERIAL - if (InLandscapeMaterial && !InLandscapeMaterial->IsPendingKill()) + if (IsValid(InLandscapeMaterial)) { // Extract the path name from the material interface FString InLandscapeMaterialString = InLandscapeMaterial->GetPathName(); @@ -1182,7 +1291,7 @@ bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( } // HOLE MATERIAL - if (InLandscapeHoleMaterial && !InLandscapeHoleMaterial->IsPendingKill()) + if (IsValid(InLandscapeHoleMaterial)) { // Extract the path name from the material interface FString InLandscapeMaterialString = InLandscapeHoleMaterial->GetPathName(); @@ -1230,7 +1339,7 @@ bool FUnrealLandscapeTranslator::AddLandscapeMaterialAttributesToVolume( } // PHYSICAL MATERIAL - if (InPhysicalMaterial && !InPhysicalMaterial->IsPendingKill()) + if (IsValid(InPhysicalMaterial)) { // Extract the path name from the material interface FString InPhysMatlString = InPhysicalMaterial->GetPathName(); @@ -1383,6 +1492,13 @@ FUnrealLandscapeTranslator::GetLandscapeLayerData( LayerData, LayerUsageDebugColor, LayerName)) return false; + if (FName(LayerName).Compare(ALandscape::VisibilityLayer->LayerName) ==0) + { + // If we encounter the visibility layer, make sure we name + // it according to the plugin's expectations instead of using the internal `DataLayer__` name. + LayerName = HAPI_UNREAL_VISIBILITY_LAYER_NAME; + } + return true; } @@ -1907,6 +2023,31 @@ FUnrealLandscapeTranslator::AddLandscapeComponentNameAttribute(const HAPI_NodeId return true; } +bool FUnrealLandscapeTranslator::AddLandscapeTileAttribute( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const int32& TileIdx ) +{ + HAPI_AttributeInfo AttributeInfoTileIndex; + FHoudiniApi::AttributeInfo_Init(&AttributeInfoTileIndex); + + AttributeInfoTileIndex.count = 1; + AttributeInfoTileIndex.tupleSize = 1; + AttributeInfoTileIndex.exists = true; + AttributeInfoTileIndex.owner = HAPI_ATTROWNER_PRIM; + AttributeInfoTileIndex.storage = HAPI_STORAGETYPE_INT; + AttributeInfoTileIndex.originalOwner = HAPI_ATTROWNER_INVALID; + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::AddAttribute( + FHoudiniEngine::Get().GetSession(), NodeId, + PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, &AttributeInfoTileIndex ), false ); + + HOUDINI_CHECK_ERROR_RETURN( FHoudiniApi::SetAttributeIntData( + FHoudiniEngine::Get().GetSession(), + NodeId, PartId, HAPI_UNREAL_ATTRIB_LANDSCAPE_TILE, &AttributeInfoTileIndex, + (const int *)&TileIdx, 0, AttributeInfoTileIndex.count ), false ); + + return true; +} + bool FUnrealLandscapeTranslator::AddLandscapeLightmapColorAttribute(const HAPI_NodeId& NodeId, const TArray& LandscapeLightmapValues) { @@ -2198,4 +2339,134 @@ FUnrealLandscapeTranslator::AddLandscapeLayerAttribute( 0, AttributeInfoLayer.count), false); return true; -} \ No newline at end of file +} + +bool FUnrealLandscapeTranslator::ExtractAndConvertAllLandscapeLayers( + ALandscapeProxy* LandscapeProxy, + const HAPI_NodeId & HeightFieldId, + const HAPI_PartId & PartId, + const HAPI_NodeId &MergeId, + const HAPI_NodeId &MaskId, + const HAPI_VolumeInfo& HeightfieldVolumeInfo, + const int32 & XSize, + const int32 & YSize, + int32 & OutMergeInputIndex) +{ + + ULandscapeInfo* LandscapeInfo = LandscapeProxy->GetLandscapeInfo(); + if (!LandscapeInfo) + return false; + + bool MaskInitialized = false; + + auto MergeInputFn = [&OutMergeInputIndex] (const HAPI_NodeId MergeId, const HAPI_NodeId NodeId) -> HAPI_Result + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HAPI_Result Result = FHoudiniApi::ConnectNodeInput( + FHoudiniEngine::Get().GetSession(), + MergeId, OutMergeInputIndex, NodeId, 0); + + if (Result == HAPI_RESULT_SUCCESS) + { + OutMergeInputIndex++; + } + return Result; + }; + + int32 NumLayers = LandscapeInfo->Layers.Num(); + for (int32 n = 0; n < NumLayers; n++) + { + // 1. Extract the uint8 values from the layer + TArray CurrentLayerIntData; + FLinearColor LayerUsageDebugColor; + FString LayerName; + if (!GetLandscapeLayerData(LandscapeProxy, LandscapeInfo, n, CurrentLayerIntData, LayerUsageDebugColor, LayerName)) + continue; + + // 2. Convert unreal uint8 values to floats + // If the layer came from Houdini, additional info might have been stored in the DebugColor to convert the data back to float + HAPI_VolumeInfo CurrentLayerVolumeInfo; + FHoudiniApi::VolumeInfo_Init(&CurrentLayerVolumeInfo); + TArray CurrentLayerFloatData; + if (!ConvertLandscapeLayerDataToHeightfieldData( + CurrentLayerIntData, XSize, YSize, LayerUsageDebugColor, + CurrentLayerFloatData, CurrentLayerVolumeInfo)) + continue; + + // We reuse the height layer's transform + CurrentLayerVolumeInfo.transform = HeightfieldVolumeInfo.transform; + + // 3. See if we need to create an input volume, or can reuse the HF's default mask volume + bool IsMask = false; + if (LayerName.Equals(TEXT("mask"), ESearchCase::IgnoreCase)) + IsMask = true; + + HAPI_NodeId LayerVolumeNodeId = -1; + if (!IsMask) + { + // Current layer is not mask, so we need to create a new input volume + std::string LayerNameStr; + FHoudiniEngineUtils::ConvertUnrealString(LayerName, LayerNameStr); + + FHoudiniApi::CreateHeightfieldInputVolumeNode( + FHoudiniEngine::Get().GetSession(), + HeightFieldId, &LayerVolumeNodeId, LayerNameStr.c_str(), XSize, YSize, 1.0f); + } + else + { + // Current Layer is mask, so we simply reuse the mask volume node created by default by the heightfield node + LayerVolumeNodeId = MaskId; + } + + // Check if we have a valid id for the input volume. + if (!FHoudiniEngineUtils::IsHoudiniNodeValid(LayerVolumeNodeId)) + continue; + + // 4. Set the layer/mask heighfield data in Houdini + HAPI_PartId CurrentPartId = 0; + if (!SetHeightfieldData(LayerVolumeNodeId, PartId, CurrentLayerFloatData, CurrentLayerVolumeInfo, LayerName)) + continue; + + // Get the physical material used by that layer + UPhysicalMaterial* LayerPhysicalMat = LandscapeProxy->DefaultPhysMaterial; + { + FLandscapeInfoLayerSettings LayersSetting = LandscapeInfo->Layers[n]; + ULandscapeLayerInfoObject* LayerInfo = LayersSetting.LayerInfoObj; + if (LayerInfo) + LayerPhysicalMat = LayerInfo->PhysMaterial; + } + + // Apply attributes to the heightfield input node + ApplyAttributesToHeightfieldNode(LayerVolumeNodeId, PartId, LandscapeProxy); + + // Commit the volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), LayerVolumeNodeId), false); + + if (!IsMask) + { + // We had to create a new volume for this layer, so we need to connect it to the HF's merge node + HOUDINI_CHECK_ERROR_RETURN(MergeInputFn(MergeId, LayerVolumeNodeId), false); + } + else + { + MaskInitialized = true; + } + } + + // We need to have a mask layer as it is required for proper heightfield functionalities + // Setting the volume info on the mask is needed for the HF to have proper transform in H! + // If we didn't create a mask volume before, send a default one now + if (!MaskInitialized) + { + MaskInitialized = InitDefaultHeightfieldMask(HeightfieldVolumeInfo, MaskId); + + ApplyAttributesToHeightfieldNode(MaskId, PartId, LandscapeProxy); + + // Commit the mask volume's geo + HOUDINI_CHECK_ERROR_RETURN(FHoudiniApi::CommitGeo( + FHoudiniEngine::Get().GetSession(), MaskId), false); + } + + return true; +} diff --git a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h index ff49af19f..8f0c2735f 100644 --- a/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h +++ b/Source/HoudiniEngine/Private/UnrealLandscapeTranslator.h @@ -44,6 +44,23 @@ struct HOUDINIENGINE_API FUnrealLandscapeTranslator HAPI_NodeId& CreatedHeightfieldNodeId, const FString &InputNodeNameStr); + static bool CreateHeightfieldFromLandscapeComponentArray( + ALandscapeProxy* LandscapeProxy, + const TSet< ULandscapeComponent * > & SelectedComponents, + HAPI_NodeId& CreatedHeightfieldNodeId, + const FString &InputNodeNameStr + ); + + static bool CreateHeightfieldFromLandscapeComponent( + ALandscapeProxy* LandscapeProxy, + ULandscapeComponent * LandscapeComponent, + const int32& ComponentIndex, + HAPI_NodeId& HeightFieldId, + HAPI_NodeId& MergeId, + int32& MergeInputIndex, + const FString& InputNodeNameStr, + const FTransform & ParentTransform); + static bool CreateInputNodeForLandscape( ALandscapeProxy* LandscapeProxy, const FString& InputNodeNameStr, @@ -230,6 +247,9 @@ struct HOUDINIENGINE_API FUnrealLandscapeTranslator const HAPI_NodeId& NodeId, const TArray& LandscapeComponentNameArray); + static bool AddLandscapeTileAttribute( + const HAPI_NodeId& NodeId, const HAPI_PartId& PartId, const int32& TileIdx ); + // Add the lightmap color attribute extracted from a landscape static bool AddLandscapeLightmapColorAttribute( const HAPI_NodeId& NodeId, @@ -255,4 +275,15 @@ struct HOUDINIENGINE_API FUnrealLandscapeTranslator const TArray& LandscapeLayerArray, const FString& LayerName); + static bool ExtractAndConvertAllLandscapeLayers( + ALandscapeProxy* LandscapeProxy, + const HAPI_NodeId & HeightFieldId, + const HAPI_PartId & PartId, + const HAPI_NodeId &MergeId, + const HAPI_NodeId &MaskId, + const HAPI_VolumeInfo& HeightfieldVolumeInfo, + const int32 & XSize, + const int32 & YSize, + int32 & OutMergeInputIndex); + }; \ No newline at end of file diff --git a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp index 246a88cda..aac6e7905 100644 --- a/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealMeshTranslator.cpp @@ -57,7 +57,7 @@ FUnrealMeshTranslator::HapiCreateInputNodeForStaticMesh( const bool& ExportColliders /* = false */) { // If we don't have a static mesh there's nothing to do. - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return false; // Node ID for the newly created node @@ -488,7 +488,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshSockets( for (int32 Idx = 0; Idx < NumSockets; ++Idx) { UStaticMeshSocket* CurrentSocket = InMeshSocket[Idx]; - if (!CurrentSocket || CurrentSocket->IsPendingKill()) + if (!IsValid(CurrentSocket)) continue; // Get the socket's transform and convert it to HapiTransform @@ -1095,7 +1095,7 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( { // Create an array of Material Interfaces TArray MaterialInterfaces; - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()) + if (IsValid(StaticMeshComponent) && StaticMeshComponent->IsValidLowLevel()) { // StaticMeshComponent->GetUsedMaterials(MaterialInterfaces, false); @@ -1433,14 +1433,14 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( //--------------------------------------------------------------------------------------------------------------------- // COMPONENT AND ACTOR TAGS //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + if (IsValid(StaticMeshComponent)) { TArray AllTags; for (auto& ComponentTag : StaticMeshComponent->ComponentTags) AllTags.AddUnique(ComponentTag); AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { for (auto& ActorTag : ParentActor->Tags) AllTags.AddUnique(ActorTag); @@ -1450,7 +1450,7 @@ FUnrealMeshTranslator::CreateInputNodeForRawMesh( if (!FHoudiniEngineUtils::CreateGroupsFromTags(NodeId, 0, AllTags)) HOUDINI_LOG_WARNING(TEXT("Could not create groups for the Static Mesh Component and Actor tags!")); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { // Add the unreal_actor_path attribute FHoudiniEngineUtils::AddActorPathAttribute(NodeId, 0, ParentActor, Part.faceCount); @@ -1625,7 +1625,7 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( const TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); + const bool bIsStaticMeshComponentValid = (IsValid(StaticMeshComponent) && StaticMeshComponent->IsValidLowLevel()); const int32 NumStaticMaterials = StaticMaterials.Num(); // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, // then we will set UEDefaultMaterial at the invalid index @@ -1647,7 +1647,7 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( Material = MaterialInfo.MaterialInterface; } // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) { if (!UEDefaultMaterial) { @@ -2315,7 +2315,7 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( //--------------------------------------------------------------------------------------------------------------------- // COMPONENT AND ACTOR TAGS //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + if (IsValid(StaticMeshComponent)) { // Try to create groups for the static mesh component's tags if (StaticMeshComponent->ComponentTags.Num() > 0 @@ -2323,7 +2323,7 @@ FUnrealMeshTranslator::CreateInputNodeForStaticMeshLODResources( HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { // Try to create groups for the parent Actor's tags if (ParentActor->Tags.Num() > 0 @@ -2526,7 +2526,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( const TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); // If the static mesh component is valid, get the materials via the component to account for overrides - const bool bIsStaticMeshComponentValid = (StaticMeshComponent && !StaticMeshComponent->IsPendingKill() && StaticMeshComponent->IsValidLowLevel()); + const bool bIsStaticMeshComponentValid = (IsValid(StaticMeshComponent) && StaticMeshComponent->IsValidLowLevel()); const int32 NumStaticMaterials = StaticMaterials.Num(); // If we find any invalid Material (null or pending kill), or we find a section below with an out of range MaterialIndex, // then we will set UEDefaultMaterial at the invalid index @@ -2548,7 +2548,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( Material = MaterialInfo.MaterialInterface; } // If the Material is NULL or invalid, fallback to the default material - if (!Material || Material->IsPendingKill()) + if (!IsValid(Material)) { if (!UEDefaultMaterial) { @@ -3259,7 +3259,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( //--------------------------------------------------------------------------------------------------------------------- // COMPONENT AND ACTOR TAGS //--------------------------------------------------------------------------------------------------------------------- - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + if (IsValid(StaticMeshComponent)) { // Try to create groups for the static mesh component's tags if (StaticMeshComponent->ComponentTags.Num() > 0 @@ -3267,7 +3267,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( HOUDINI_LOG_WARNING(TEXT("Could not create groups from the Static Mesh Component's tags!")); AActor* ParentActor = StaticMeshComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { // Try to create groups for the parent Actor's tags if (ParentActor->Tags.Num() > 0 @@ -3282,7 +3282,7 @@ FUnrealMeshTranslator::CreateInputNodeForMeshDescription( /* FString LevelPath = FString(); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { if (ULevel* Level = ParentActor->GetLevel()) { @@ -3478,7 +3478,7 @@ FUnrealMeshTranslator::CreateFaceMaterialArray( UTexture * CurTexture = nullptr; MaterialInterface->GetTextureParameterValue(CurTextureParam, CurTexture); - if (!CurTexture || CurTexture->IsPendingKill()) + if (!IsValid(CurTexture)) continue; FString TexturePath = CurTexture->GetPathName(); @@ -3753,7 +3753,6 @@ FUnrealMeshTranslator::CreateInputNodeForSphyl( } // Get the transform matrix for the rotation - FRotationMatrix SphylRotMatrix(SphylRotation); // Get the Sphyl's vertices by rotating the arc NumSides+1 times. TArray Vertices; @@ -3768,8 +3767,10 @@ FUnrealMeshTranslator::CreateInputNodeForSphyl( { int32 VIx = (NumRings + 1)*SideIdx + VertIdx; - FVector CurPosition = SphylCenter + ArcRot.TransformPosition(ArcVertices[VertIdx]); - CurPosition = SphylRotMatrix.TransformPosition(CurPosition); + FVector ArcVertex = ArcRot.TransformPosition(ArcVertices[VertIdx]); + ArcVertex = SphylRotation.Quaternion() * ArcVertex; + + FVector CurPosition = SphylCenter + ArcVertex; // Convert the UE4 position to Houdini Vertices[VIx * 3 + 0] = CurPosition.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; @@ -3847,6 +3848,12 @@ FUnrealMeshTranslator::CreateInputNodeForConvex( TArray Vertices; TArray Indices; + FTransform ConvexTransform = ConvexCollider.GetTransform(); + + FVector TransformOffset = ConvexTransform.GetLocation(); + FVector ScaleOffset = ConvexTransform.GetScale3D(); + FQuat RotationOffset = ConvexTransform.GetRotation(); + #if PHYSICS_INTERFACE_PHYSX if (ConvexCollider.GetConvexMesh() || ConvexCollider.GetMirroredConvexMesh()) #elif WITH_CHAOS @@ -3861,6 +3868,11 @@ FUnrealMeshTranslator::CreateInputNodeForConvex( TArray IndexBuffer; ConvexCollider.AddCachedSolidConvexGeom(VertexBuffer, IndexBuffer, FColor::White); + for (int32 i = 0; i < VertexBuffer.Num(); i++) + { + VertexBuffer[i].Position = TransformOffset + (RotationOffset * (ScaleOffset * VertexBuffer[i].Position)); + } + Vertices.SetNum(VertexBuffer.Num() * 3); int32 CurIndex = 0; for (auto& CurVert : VertexBuffer) @@ -3870,7 +3882,7 @@ FUnrealMeshTranslator::CreateInputNodeForConvex( Vertices[CurIndex * 3 + 2] = CurVert.Position.Y / HAPI_UNREAL_SCALE_FACTOR_POSITION; CurIndex++; } - + Indices.SetNum(IndexBuffer.Num()); for (int Idx = 0; (Idx + 2) < IndexBuffer.Num(); Idx += 3) { @@ -3882,11 +3894,20 @@ FUnrealMeshTranslator::CreateInputNodeForConvex( } else { + // Need to copy vertices because we plan on modifying it by Quaternion/Vector multiplication + TArray VertexBuffer; + VertexBuffer.SetNum(ConvexCollider.VertexData.Num()); + + for (int32 Idx = 0; Idx < ConvexCollider.VertexData.Num(); Idx++) + { + VertexBuffer[Idx] = TransformOffset + (RotationOffset * (ScaleOffset * ConvexCollider.VertexData[Idx])); + } + int32 NumVert = ConvexCollider.VertexData.Num(); Vertices.SetNum(NumVert * 3); //Indices.SetNum(NumVert); int32 CurIndex = 0; - for (auto& CurVert : ConvexCollider.VertexData) + for (auto& CurVert : VertexBuffer) { Vertices[CurIndex * 3 + 0] = CurVert.X / HAPI_UNREAL_SCALE_FACTOR_POSITION; Vertices[CurIndex * 3 + 1] = CurVert.Z / HAPI_UNREAL_SCALE_FACTOR_POSITION; diff --git a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp index 268476eba..4a2e2a0d8 100644 --- a/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp +++ b/Source/HoudiniEngine/Private/UnrealSplineTranslator.cpp @@ -38,7 +38,7 @@ bool FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* SplineComponent, const float& SplineResolution, HAPI_NodeId& CreatedInputNodeId, const FString& NodeName) { - if (!SplineComponent || SplineComponent->IsPendingKill()) + if (!IsValid(SplineComponent)) return false; int32 NumberOfControlPoints = SplineComponent->GetNumberOfSplinePoints(); @@ -94,7 +94,7 @@ FUnrealSplineTranslator::CreateInputNodeForSplineComponent(USplineComponent* Spl // Add the parent actor's tag if it has any AActor* ParentActor = SplineComponent->GetOwner(); - if (ParentActor && !ParentActor->IsPendingKill()) + if (IsValid(ParentActor)) { if (FHoudiniEngineUtils::CreateGroupsFromTags(CreatedInputNodeId, 0, ParentActor->Tags)) NeedToCommit = true; diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI.h b/Source/HoudiniEngine/Public/HAPI/HAPI.h index 26e26950e..69b56c9a6 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI.h @@ -389,6 +389,11 @@ HAPI_DECL HAPI_GetServerEnvString( const HAPI_Session * session, /// @brief Provides the number of environment variables that are in /// the server environment's process /// +/// Note that ::HAPI_GetServerEnvVarList() should be called directly after +/// this method, otherwise there is the possibility that the environment +/// variable count of the server will have changed by the time that +/// ::HAPI_GetServerEnvVarList() is called. +/// /// @ingroup Environment /// /// @param[in] session @@ -418,7 +423,8 @@ HAPI_DECL HAPI_GetServerEnvVarCount( const HAPI_Session * session, /// /// @param[in] start /// First index of range. Must be at least @c 0 and at most -/// @c env_count returned by ::HAPI_GetServerEnvVarCount() +/// @c env_count - 1 where @c env_count is the count returned by +/// ::HAPI_GetServerEnvVarCount() /// /// /// diff --git a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h index 5d7c6f307..0a7ba1383 100644 --- a/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h +++ b/Source/HoudiniEngine/Public/HAPI/HAPI_Version.h @@ -27,7 +27,7 @@ // expecting to compile against. #define HAPI_VERSION_HOUDINI_MAJOR 18 #define HAPI_VERSION_HOUDINI_MINOR 5 -#define HAPI_VERSION_HOUDINI_BUILD 696 +#define HAPI_VERSION_HOUDINI_BUILD 759 #define HAPI_VERSION_HOUDINI_PATCH 0 // The two components of the Houdini Engine (marketed) version. @@ -40,6 +40,6 @@ // might no longer compile. Semantic changes to the methods will also // cause this version to increase. This number will be reset to 0 // every time the Houdini Engine version is bumped. -#define HAPI_VERSION_HOUDINI_ENGINE_API 1 +#define HAPI_VERSION_HOUDINI_ENGINE_API 3 #endif // __HAPI_VERSION_h__ diff --git a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp index 36dd0f577..165891548 100644 --- a/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp +++ b/Source/HoudiniEngineEditor/Private/AssetTypeActions_HoudiniAsset.cpp @@ -74,7 +74,7 @@ FAssetTypeActions_HoudiniAsset::GetCategories() UThumbnailInfo * FAssetTypeActions_HoudiniAsset::GetThumbnailInfo(UObject * Asset) const { - if (!Asset || Asset->IsPendingKill()) + if (!IsValid(Asset)) return nullptr; UHoudiniAsset * HoudiniAsset = CastChecked< UHoudiniAsset >(Asset); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp index 6499e571c..6d4d6805c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniAssetComponentDetails.cpp @@ -84,7 +84,7 @@ FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() } for (auto& CurFloatRampCurve : ParamDetailsPtr->CreatedFloatRampCurves) { - if (!CurFloatRampCurve || CurFloatRampCurve->IsPendingKill()) + if (!IsValid(CurFloatRampCurve)) continue; CurFloatRampCurve->RemoveFromRoot(); @@ -100,7 +100,7 @@ FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails() } for (auto& CurColorRampCurve : ParamDetailsPtr->CreatedColorRampCurves) { - if (!CurColorRampCurve || CurColorRampCurve->IsPendingKill()) + if (!IsValid(CurColorRampCurve)) continue; CurColorRampCurve->RemoveFromRoot(); @@ -279,7 +279,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil if (Object) { UHoudiniAssetComponent * HAC = Cast< UHoudiniAssetComponent >(Object); - if (HAC && !HAC->IsPendingKill()) + if (IsValid(HAC)) HoudiniAssetComponents.Add(HAC); } } @@ -401,7 +401,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil { // We only want to create root parameters here, they will recursively create child parameters. UHoudiniParameter* CurrentParam = MainComponent->GetParameterAt(ParamIdx); - if (!CurrentParam || CurrentParam->IsPendingKill()) + if (!IsValid(CurrentParam)) continue; // TODO: remove ? unneeded? @@ -418,14 +418,14 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) { UHoudiniParameter* LinkedParam = HACs[LinkedIdx]->GetParameterAt(ParamIdx); - if (!LinkedParam || LinkedParam->IsPendingKill()) + if (!IsValid(LinkedParam)) continue; // Linked params should match the main param! If not try to find one that matches if ( !LinkedParam->Matches(*CurrentParam) ) { LinkedParam = MainComponent->FindMatchingParameter(CurrentParam); - if (!LinkedParam || LinkedParam->IsPendingKill() || LinkedParam->IsChildParameter()) + if (!IsValid(LinkedParam) || LinkedParam->IsChildParameter()) continue; } @@ -455,7 +455,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil { UHoudiniHandleComponent* CurrentHandleComponent = MainComponent->GetHandleComponentAt(HandleIdx); - if (!CurrentHandleComponent || CurrentHandleComponent->IsPendingKill()) + if (!IsValid(CurrentHandleComponent)) continue; TArray EditedHandles; @@ -465,14 +465,14 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); ++LinkedIdx) { UHoudiniHandleComponent* LinkedHandle = HACs[LinkedIdx]->GetHandleComponentAt(HandleIdx); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) + if (!IsValid(LinkedHandle)) continue; // Linked handles should match the main param, if not try to find one that matches if (!LinkedHandle->Matches(*CurrentHandleComponent)) { LinkedHandle = MainComponent->FindMatchingHandle(CurrentHandleComponent); - if (!LinkedHandle || LinkedHandle->IsPendingKill()) + if (!IsValid(LinkedHandle)) continue; } @@ -504,7 +504,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int32 InputIdx = 0; InputIdx < MainComponent->GetNumInputs(); InputIdx++) { UHoudiniInput* CurrentInput = MainComponent->GetInputAt(InputIdx); - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (!MainComponent->IsInputTypeSupported(CurrentInput->GetInputType())) @@ -522,14 +522,14 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) { UHoudiniInput* LinkedInput = HACs[LinkedIdx]->GetInputAt(InputIdx); - if (!LinkedInput || LinkedInput->IsPendingKill()) + if (!IsValid(LinkedInput)) continue; // Linked params should match the main param! If not try to find one that matches if (!LinkedInput->Matches(*CurrentInput)) { LinkedInput = MainComponent->FindMatchingInput(CurrentInput); - if (!LinkedInput || LinkedInput->IsPendingKill()) + if (!IsValid(LinkedInput)) continue; } @@ -556,7 +556,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int32 OutputIdx = 0; OutputIdx < MainComponent->GetNumOutputs(); OutputIdx++) { UHoudiniOutput* CurrentOutput = MainComponent->GetOutputAt(OutputIdx); - if (!CurrentOutput || CurrentOutput->IsPendingKill()) + if (!IsValid(CurrentOutput)) continue; // Build an array of edited inpoutputs for multi edit @@ -567,7 +567,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil for (int LinkedIdx = 1; LinkedIdx < HACs.Num(); LinkedIdx++) { UHoudiniOutput* LinkedOutput = HACs[LinkedIdx]->GetOutputAt(OutputIdx); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) + if (!IsValid(LinkedOutput)) continue; /* @@ -575,7 +575,7 @@ FHoudiniAssetComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil if (!LinkedOutput->Matches(*CurrentOutput)) { LinkedOutput = MainComponent->FindMatchingInput(CurrentOutput); - if (!LinkedOutput || LinkedOutput->IsPendingKill()) + if (!IsValid(LinkedOutput)) continue; } */ diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp index 28e6fe17a..bc74754c9 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.cpp @@ -175,8 +175,7 @@ FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( case EHoudiniEngineBakeOption::ToFoliage: { - TMap AlreadyBakedMaterialsMap; - bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake, AlreadyBakedMaterialsMap); + bSuccess = FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InHACToBake, bInReplacePreviousBake); } break; @@ -202,7 +201,7 @@ bool FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActors) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; TArray NewActors; @@ -264,15 +263,9 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; - AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!IsValid(OwnerActor)) - return false; - - const FString HoudiniAssetName = OwnerActor->GetName(); - // Get an array of the outputs const int32 NumOutputs = HoudiniAssetComponent->GetNumOutputs(); TArray Outputs; @@ -288,16 +281,17 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( if (BakedOutputs.Num() != NumOutputs) BakedOutputs.SetNum(NumOutputs); + const TArray AllBakedActors; return BakeHoudiniOutputsToActors( HoudiniAssetComponent, Outputs, BakedOutputs, - HoudiniAssetName, HoudiniAssetComponent->GetComponentTransform(), HoudiniAssetComponent->BakeFolder, HoudiniAssetComponent->TemporaryCookFolder, bInReplaceActors, bInReplaceAssets, + AllBakedActors, OutNewActors, OutPackagesToSave, OutBakeStats, @@ -312,12 +306,12 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( const UHoudiniAssetComponent* HoudiniAssetComponent, const TArray& InOutputs, TArray& InBakedOutputs, - const FString& InHoudiniAssetName, const FTransform& InParentTransform, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutNewActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats, @@ -332,7 +326,8 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( FString Msg = FString::Format(*MsgTemplate, { 0, NumOutputs }); FHoudiniEngine::Get().CreateTaskSlateNotification(FText::FromString(Msg)); - TArray BakedActors; + TArray AllBakedActors = InBakedActors; + TArray NewBakedActors; // Ensure that InBakedOutputs is the same size as InOutputs if (InBakedOutputs.Num() != NumOutputs) @@ -344,10 +339,11 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( int32 NumProcessedOutputs = 0; TMap AlreadyBakedMaterialsMap; + TArray OutputBakedActors; for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) { UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) { NumProcessedOutputs++; continue; @@ -364,6 +360,7 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( continue; } + OutputBakedActors.Reset(); switch (OutputType) { case EHoudiniOutputType::Mesh: @@ -373,14 +370,15 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( OutputIdx, InOutputs, InBakedOutputs, - InHoudiniAssetName, InBakeFolder, InTempCookFolder, bInReplaceActors, bInReplaceAssets, - BakedActors, + AllBakedActors, + OutputBakedActors, OutPackagesToSave, AlreadyBakedMaterialsMap, + OutBakeStats, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -404,7 +402,6 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( bInReplaceActors, bInReplaceAssets, InBakeFolder.Path, - InHoudiniAssetName, OutBakeStats); } break; @@ -419,11 +416,12 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( OutputIdx, InOutputs, InBakedOutputs, - InHoudiniAssetName, InBakeFolder, bInReplaceActors, bInReplaceAssets, - BakedActors, + AllBakedActors, + OutputBakedActors, + OutBakeStats, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -433,6 +431,9 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( break; } + AllBakedActors.Append(OutputBakedActors); + NewBakedActors.Append(OutputBakedActors); + NumProcessedOutputs++; } @@ -441,43 +442,48 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( for (int32 OutputIdx = 0; OutputIdx < NumOutputs; ++OutputIdx) { UHoudiniOutput* Output = InOutputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) { - NumProcessedOutputs++; continue; } - Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); - FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); - if (Output->GetType() == EHoudiniOutputType::Instancer) { + OutputBakedActors.Reset(); + + Msg = FString::Format(*MsgTemplate, { NumProcessedOutputs + 1, NumOutputs }); + FHoudiniEngine::Get().UpdateTaskSlateNotification(FText::FromString(Msg)); + FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( HoudiniAssetComponent, OutputIdx, InOutputs, InBakedOutputs, InParentTransform, - InHoudiniAssetName, InBakeFolder, InTempCookFolder, bInReplaceActors, bInReplaceAssets, - BakedActors, + AllBakedActors, + OutputBakedActors, OutPackagesToSave, AlreadyBakedMaterialsMap, + OutBakeStats, InInstancerComponentTypesToBake, InFallbackActor, InFallbackWorldOutlinerFolder); - } - NumProcessedOutputs++; + AllBakedActors.Append(OutputBakedActors); + NewBakedActors.Append(OutputBakedActors); + + NumProcessedOutputs++; + } } } // Only do the post bake post-process once per Actor TSet UniqueActors; - for (FHoudiniEngineBakedActor& BakedActor : BakedActors) + for (FHoudiniEngineBakedActor& BakedActor : NewBakedActors) { if (BakedActor.bPostBakeProcessPostponed && BakedActor.Actor) { @@ -494,8 +500,8 @@ FHoudiniEngineBakeUtils::BakeHoudiniOutputsToActors( } } - OutNewActors.Append(BakedActors); - + OutNewActors = MoveTemp(NewBakedActors); + return true; } @@ -507,17 +513,18 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, const FHoudiniOutputObject& InOutputObject, FHoudiniBakedOutputObject& InBakedOutputObject, - const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap) + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats) { UHoudiniOutput* Output = InAllOutputs[InOutputIndex]; - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) return false; if (Output->GetType() != EHoudiniOutputType::Instancer) @@ -563,14 +570,14 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - InHoudiniAssetName, MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); + MeshPackageParams, MeshResolver, InBakeFolder.Path, AssetPackageReplaceMode); // Update with resolved object name ObjectName = MeshPackageParams.ObjectName; // This will bake/duplicate the mesh if temporary, or return the input one if it is not BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + InstancedStaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, InBakedActors, InTempCookFolder.Path, + OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); } else { @@ -588,8 +595,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( FHoudiniAttributeResolver InstancerResolver; FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - InHoudiniAssetName, InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); + InstancerPackageParams, InstancerResolver, InBakeFolder.Path, AssetPackageReplaceMode); // By default spawn in the current level unless specified via the unreal_level_path attribute ULevel* DesiredLevel = GWorld->GetCurrentLevel(); @@ -615,6 +621,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -626,7 +634,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( // Get foliage actor for the level const bool bCreateIfNone = true; AInstancedFoliageActor* InstancedFoliageActor = AInstancedFoliageActor::GetInstancedFoliageActorForLevel(DesiredLevel, bCreateIfNone); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + if (!IsValid(InstancedFoliageActor)) { HOUDINI_LOG_ERROR(TEXT("Could not find or create an instanced foliage actor for level %s"), *(DesiredLevel->GetPathName())); return false; @@ -647,7 +655,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( // See if we already have a FoliageType for that static mesh UFoliageType* FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(BakedStaticMesh); - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) { // We need to create a new FoliageType for this Static Mesh // TODO: Add foliage default settings @@ -659,7 +667,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( { const FString FoliageTypePath = FSoftObjectPath(FoliageType).ToString(); if (bInReplaceAssets && InBakedOutputObject.BakedComponent == FoliageTypePath && - !OutActors.FindByPredicate([FoliageType](const FHoudiniEngineBakedActor& Entry) { return Entry.BakedComponent == FoliageType; })) + !InBakedActors.FindByPredicate([FoliageType](const FHoudiniEngineBakedActor& Entry) { return Entry.BakedComponent == FoliageType; })) { InstancedFoliageActor->RemoveFoliageType(&FoliageType, 1); // Update the previous bake results with the foliage type @@ -673,13 +681,15 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( } // Record the foliage bake in the current results - FHoudiniEngineBakedActor& NewResult = OutActors.Add_GetRef(FHoudiniEngineBakedActor()); + FHoudiniEngineBakedActor NewResult; NewResult.OutputIndex = InOutputIndex; NewResult.OutputObjectIdentifier = InOutputObjectIdentifier; NewResult.SourceObject = InstancedStaticMesh; NewResult.BakedObject = BakedStaticMesh; NewResult.BakedComponent = FoliageType; + OutBakedActorEntry = NewResult; + // Get the FoliageMeshInfo for this Foliage type so we can add the instance to it FFoliageInfo* FoliageInfo = InstancedFoliageActor->FindOrAddMesh(FoliageType); if (!FoliageInfo) @@ -740,13 +750,13 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToFoliage( bool FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; for (int32 n = 0; n < HoudiniAssetComponent->GetNumOutputs(); ++n) { UHoudiniOutput* Output = HoudiniAssetComponent->GetOutputAt(n); - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; if (Output->GetType() != EHoudiniOutputType::Instancer) @@ -768,20 +778,16 @@ FHoudiniEngineBakeUtils::CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComp } bool -FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, TMap& InOutAlreadyBakedMaterialsMap) +FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) - return false; - - AActor * OwnerActor = HoudiniAssetComponent->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; + TMap AlreadyBakedMaterialsMap; + FHoudiniEngineOutputStats BakeStats; TArray PackagesToSave; - TArray BakedResults; FTransform HoudiniAssetTransform = HoudiniAssetComponent->GetComponentTransform(); - const FString HoudiniAssetName = OwnerActor->GetName(); // Build an array of the outputs so that we can search for meshes/previous baked meshes TArray Outputs; @@ -792,13 +798,15 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* Houdi TArray& BakedOutputs = HoudiniAssetComponent->GetBakedOutputs(); if (BakedOutputs.Num() != NumOutputs) BakedOutputs.SetNum(NumOutputs); + + TArray AllBakedActors; bool bSuccess = true; // Map storing original and baked Static Meshes for (int32 OutputIdx = 0; OutputIdx < NumOutputs; OutputIdx++) { UHoudiniOutput* Output = Outputs[OutputIdx]; - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; if (Output->GetType() != EHoudiniOutputType::Instancer) @@ -817,22 +825,31 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* Houdi if (OldBakedOutputObjects.Contains(Identifier)) BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); + FHoudiniEngineBakedActor OutputBakedActorEntry; const bool bInReplaceActors = false; - bSuccess &= BakeInstancerOutputToFoliage( - HoudiniAssetComponent, - OutputIdx, - Outputs, - Identifier, - OutputObject, - BakedOutputObject, - HoudiniAssetName, - HoudiniAssetComponent->BakeFolder, - HoudiniAssetComponent->TemporaryCookFolder, - bInReplaceActors, - bInReplaceAssets, - BakedResults, - PackagesToSave, - InOutAlreadyBakedMaterialsMap); + if (BakeInstancerOutputToFoliage( + HoudiniAssetComponent, + OutputIdx, + Outputs, + Identifier, + OutputObject, + BakedOutputObject, + HoudiniAssetComponent->BakeFolder, + HoudiniAssetComponent->TemporaryCookFolder, + bInReplaceActors, + bInReplaceAssets, + AllBakedActors, + OutputBakedActorEntry, + PackagesToSave, + AlreadyBakedMaterialsMap, + BakeStats)) + { + AllBakedActors.Add(OutputBakedActorEntry); + } + else + { + bSuccess = false; + } } // Update the cached baked output data @@ -844,6 +861,12 @@ FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(UHoudiniAssetComponent* Houdi FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave); } + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + // Broadcast that the bake is complete HoudiniAssetComponent->HandleOnPostBake(bSuccess); @@ -858,14 +881,15 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( const TArray& InAllOutputs, TArray& InBakedOutputs, const FTransform& InTransform, - const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, TArray const* InInstancerComponentTypesToBake, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) @@ -874,7 +898,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( return false; UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; // Ensure we have the same number of baked outputs and asset outputs @@ -885,6 +909,10 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; TMap NewBakedOutputObjects; + TArray AllBakedActors = InBakedActors; + TArray NewBakedActors; + TArray OutputBakedActors; + // Iterate on the output objects, baking their object/component as we go for (auto& Pair : OutputObjects) { @@ -901,31 +929,38 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( // ?? } - if (!CurrentOutputObject.OutputComponent || CurrentOutputObject.OutputComponent->IsPendingKill()) + if (!IsValid(CurrentOutputObject.OutputComponent)) continue; + OutputBakedActors.Reset(); + if (CurrentOutputObject.OutputComponent->IsA()) { // Bake foliage as foliage if (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageInstancedStaticMeshComponent)) { - BakeInstancerOutputToFoliage( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InHoudiniAssetName, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InOutAlreadyBakedMaterialsMap); + FHoudiniEngineBakedActor BakedActorEntry; + if (BakeInstancerOutputToFoliage( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + AllBakedActors, + BakedActorEntry, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + OutBakeStats)) + { + OutputBakedActors.Add(BakedActorEntry); + } } else if (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::FoliageAsHierarchicalInstancedStaticMeshComponent)) @@ -943,9 +978,11 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( InTempCookFolder, bInReplaceActors, bInReplaceAssets, - OutActors, + AllBakedActors, + OutputBakedActors, OutPackagesToSave, InOutAlreadyBakedMaterialsMap, + OutBakeStats, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -966,9 +1003,11 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( InTempCookFolder, bInReplaceActors, bInReplaceAssets, - OutActors, + AllBakedActors, + OutputBakedActors, OutPackagesToSave, InOutAlreadyBakedMaterialsMap, + OutBakeStats, InFallbackActor, InFallbackWorldOutlinerFolder); } @@ -984,62 +1023,81 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors( InBakeFolder, bInReplaceActors, bInReplaceAssets, - OutActors, - OutPackagesToSave); + AllBakedActors, + OutputBakedActors, + OutPackagesToSave, + OutBakeStats); } else if (CurrentOutputObject.OutputComponent->IsA() && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::MeshSplitInstancerComponent))) { - BakeInstancerOutputToActors_MSIC( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InTransform, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InOutAlreadyBakedMaterialsMap, - InFallbackActor, - InFallbackWorldOutlinerFolder); + FHoudiniEngineBakedActor BakedActorEntry; + if (BakeInstancerOutputToActors_MSIC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InTransform, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + AllBakedActors, + BakedActorEntry, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + OutBakeStats, + InFallbackActor, + InFallbackWorldOutlinerFolder)) + { + OutputBakedActors.Add(BakedActorEntry); + } } else if (CurrentOutputObject.OutputComponent->IsA() && (!InInstancerComponentTypesToBake || InInstancerComponentTypesToBake->Contains(EHoudiniInstancerComponentType::StaticMeshComponent))) { - BakeInstancerOutputToActors_SMC( - HoudiniAssetComponent, - InOutputIndex, - InAllOutputs, - // InBakedOutputs, - Pair.Key, - CurrentOutputObject, - BakedOutputObject, - InBakeFolder, - InTempCookFolder, - bInReplaceActors, - bInReplaceAssets, - OutActors, - OutPackagesToSave, - InOutAlreadyBakedMaterialsMap, - InFallbackActor, - InFallbackWorldOutlinerFolder); + FHoudiniEngineBakedActor BakedActorEntry; + if (BakeInstancerOutputToActors_SMC( + HoudiniAssetComponent, + InOutputIndex, + InAllOutputs, + // InBakedOutputs, + Pair.Key, + CurrentOutputObject, + BakedOutputObject, + InBakeFolder, + InTempCookFolder, + bInReplaceActors, + bInReplaceAssets, + AllBakedActors, + BakedActorEntry, + OutPackagesToSave, + InOutAlreadyBakedMaterialsMap, + OutBakeStats, + InFallbackActor, + InFallbackWorldOutlinerFolder)) + { + OutputBakedActors.Add(BakedActorEntry); + } + } else { // Unsupported component! } + AllBakedActors.Append(OutputBakedActors); + NewBakedActors.Append(OutputBakedActors); } // Update the cached baked output data InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; + OutActors = MoveTemp(NewBakedActors); + return true; } @@ -1057,22 +1115,24 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { UInstancedStaticMeshComponent * InISMC = Cast(InOutputObject.OutputComponent); - if (!InISMC || InISMC->IsPendingKill()) + if (!IsValid(InISMC)) return false; AActor * OwnerActor = InISMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return false; UStaticMesh * StaticMesh = InISMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return false; // Certain SMC materials may need to be duplicated if we didn't generate the mesh object. @@ -1099,9 +1159,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( FHoudiniPackageParams InstancerPackageParams; FHoudiniAttributeResolver InstancerResolver; FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, + InstancerPackageParams, InstancerResolver, InBakeFolder.Path, AssetPackageReplaceMode); const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); if (bFoundMeshOutput) @@ -1110,14 +1169,14 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - OwnerActor->GetName(), MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); + MeshPackageParams, MeshResolver, InBakeFolder.Path, AssetPackageReplaceMode); // Update with resolved object name ObjectName = MeshPackageParams.ObjectName; // This will bake/duplicate the mesh if temporary, or return the input one if it is not BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, InBakedActors, InTempCookFolder.Path, + OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); } else { @@ -1129,13 +1188,14 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) { UMaterialInterface* MaterialInterface = Materials[MaterialIdx]; - if (!MaterialInterface || MaterialInterface->IsPendingKill()) + if (!IsValid(MaterialInterface)) continue; // Only duplicate the material if it is temporary if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path)) { - UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); DuplicatedISMCOverrideMaterials.Add(DuplicatedMaterial); } } @@ -1145,11 +1205,10 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); - - + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath( + InOutputObject, + FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InstancerPackageParams.HoudiniAssetActorName : InFallbackWorldOutlinerFolder)); // By default spawn in the current level unless specified via the unreal_level_path attribute ULevel* DesiredLevel = GWorld->GetCurrentLevel(); @@ -1174,6 +1233,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -1186,7 +1247,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( FName BakeActorName; AActor* FoundActor = nullptr; bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, InBakedActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) return false; /* @@ -1226,7 +1287,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( if (!FoundActor) { FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InstanceTransform); - if (!FoundActor || FoundActor->IsPendingKill()) + if (!IsValid(FoundActor)) continue; } @@ -1237,7 +1298,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) + if (!IsValid(SMActor)) continue; // Copy properties from the existing component @@ -1272,10 +1333,12 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( // Spawn the new Actor FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) + if (!IsValid(FoundActor)) return false; bSpawnedActor = true; + OutBakeStats.NotifyObjectsCreated(FoundActor->GetClass()->GetName(), 1); + FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); FoundActor->SetActorHiddenInGame(InISMC->bHiddenInGame); } @@ -1291,6 +1354,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); + + OutBakeStats.NotifyObjectsUpdated(FoundActor->GetClass()->GetName(), 1); } // The folder is named after the original actor and contains all generated actors @@ -1315,6 +1380,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( FoundActor, FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); CopyPropertyToNewActorAndComponent(FoundActor, NewISMC, InISMC); + + OutBakeStats.NotifyObjectsCreated(UHierarchicalInstancedStaticMeshComponent::StaticClass()->GetName(), 1); } else { @@ -1322,6 +1389,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( InHISMC, FoundActor, FName(MakeUniqueObjectNameIfNeeded(FoundActor, InHISMC->GetClass(), InISMC->GetName()))); + + OutBakeStats.NotifyObjectsCreated(InHISMC->GetClass()->GetName(), 1); } } else @@ -1330,6 +1399,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_ISMC( InISMC, FoundActor, FName(MakeUniqueObjectNameIfNeeded(FoundActor, InISMC->GetClass(), InISMC->GetName()))); + + OutBakeStats.NotifyObjectsCreated(InISMC->GetClass()->GetName(), 1); } if (!NewISMC) @@ -1414,22 +1485,24 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { UStaticMeshComponent* InSMC = Cast(InOutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) + if (!IsValid(InSMC)) return false; AActor* OwnerActor = InSMC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return false; UStaticMesh* StaticMesh = InSMC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return false; UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; @@ -1459,8 +1532,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( FHoudiniAttributeResolver InstancerResolver; FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); + InstancerPackageParams, InstancerResolver, InBakeFolder.Path, AssetPackageReplaceMode); const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); if (bFoundMeshOutput) @@ -1469,14 +1541,14 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - OwnerActor->GetName(), MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); + MeshPackageParams, MeshResolver, InBakeFolder.Path, AssetPackageReplaceMode); // Update with resolved object name ObjectName = MeshPackageParams.ObjectName; // This will bake/duplicate the mesh if temporary, or return the input one if it is not BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, InBakedActors, InTempCookFolder.Path, + OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); } else { @@ -1487,13 +1559,14 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) { UMaterialInterface* MaterialInterface = Materials[MaterialIdx]; - if (!MaterialInterface || MaterialInterface->IsPendingKill()) + if (!IsValid(MaterialInterface)) continue; // Only duplicate the material if it is temporary if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path)) { - UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); DuplicatedSMCOverrideMaterials.Add(DuplicatedMaterial); } } @@ -1502,11 +1575,11 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( // Update the previous baked object InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); - // BaseName holds the Actor / HDA name // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath( + InOutputObject, + FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InstancerPackageParams.HoudiniAssetActorName : InFallbackWorldOutlinerFolder)); @@ -1533,6 +1606,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -1545,7 +1620,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( FName BakeActorName; AActor* FoundActor = nullptr; bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, InBakedActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) return false; UStaticMeshComponent* StaticMeshComponent = nullptr; @@ -1558,11 +1633,13 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( return false; FoundActor = SMFactory->CreateActor(BakedStaticMesh, DesiredLevel, InSMC->GetComponentTransform()); - if (!FoundActor || FoundActor->IsPendingKill()) + if (!IsValid(FoundActor)) return false; + OutBakeStats.NotifyObjectsCreated(FoundActor->GetClass()->GetName(), 1); + AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) + if (!IsValid(SMActor)) return false; StaticMeshComponent = SMActor->GetStaticMeshComponent(); @@ -1591,6 +1668,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( FoundActor->AddInstanceComponent(StaticMeshComponent); StaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform); StaticMeshComponent->RegisterComponent(); + + OutBakeStats.NotifyObjectsCreated(StaticMeshComponent->GetClass()->GetName(), 1); } } @@ -1624,7 +1703,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( } InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor OutputEntry( FoundActor, BakeActorName, WorldOutlinerFolderPath, @@ -1634,9 +1713,11 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_SMC( StaticMesh, StaticMeshComponent, MeshPackageParams.BakeFolder, - MeshPackageParams)); + MeshPackageParams); OutputEntry.bInstancerOutput = true; OutputEntry.InstancerPackageParams = InstancerPackageParams; + + OutBakedActorEntry = OutputEntry; // If we are baking in replace mode, remove previously baked components/instancers if (bInReplaceActors && bInReplaceAssets) @@ -1661,25 +1742,27 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( const FDirectoryPath& InBakeFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, - TArray& OutPackagesToSave) + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) { UHoudiniInstancedActorComponent* InIAC = Cast(InOutputObject.OutputComponent); - if (!InIAC || InIAC->IsPendingKill()) + if (!IsValid(InIAC)) return false; - AActor * OwnerActor = InIAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + AActor* OwnerActor = InIAC->GetOwner(); + if (!IsValid(OwnerActor)) return false; - // BaseName holds the Actor / HDA name - const FName BaseName = FName(OwnerActor->GetName()); - // Get the object instanced by this IAC UObject* InstancedObject = InIAC->GetInstancedObject(); - if (!InstancedObject || InstancedObject->IsPendingKill()) + if (!IsValid(InstancedObject)) return false; + // Set the default object name to the + const FString DefaultObjectName = InstancedObject->GetName(); + FHoudiniPackageParams PackageParams; const EPackageReplaceMode AssetPackageReplaceMode = bInReplaceAssets ? EPackageReplaceMode::ReplaceExistingAssets : EPackageReplaceMode::CreateNewAssets; @@ -1688,9 +1771,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( FHoudiniAttributeResolver Resolver; UWorld* DesiredWorld = OwnerActor ? OwnerActor->GetWorld() : GWorld; FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( - DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, BaseName.ToString(), - OwnerActor->GetName(), PackageParams, Resolver, - InBakeFolder.Path, AssetPackageReplaceMode); + DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, DefaultObjectName, + PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); // By default spawn in the current level unless specified via the unreal_level_path attribute ULevel* DesiredLevel = GWorld->GetCurrentLevel(); @@ -1716,6 +1798,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -1724,6 +1808,69 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( if (!DesiredLevel) return false; + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *PackageParams.HoudiniAssetActorName); + + // Try to find the unreal_bake_actor, if specified. If we found the actor, we will attach the instanced actors + // to it. If we did not find an actor, but unreal_bake_actor was set, then we create a new actor with that name + // and parent the instanced actors to it. Otherwise, we don't attach the instanced actors to anything. + FName DesiredParentBakeActorName; + FName ParentBakeActorName; + AActor* ParentToActor = nullptr; + bool bHasBakeActorName = false; + constexpr AActor* FallbackActor = nullptr; + const FName DefaultBakeActorName = NAME_None; + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, InBakedActors, DesiredLevel, DefaultBakeActorName, bInReplaceActors, FallbackActor, ParentToActor, bHasBakeActorName, DesiredParentBakeActorName)) + { + HOUDINI_LOG_ERROR(TEXT("Finding / processing unreal_bake_actor unexpectedly failed during bake.")); + return false; + } + + OutActors.Reset(); + + if (!ParentToActor && bHasBakeActorName) + { + // We need to create an actor to attached the instanced actors to + UActorFactory* const ActorFactory = GEditor->FindActorFactoryByClass(UActorFactoryEmptyActor::StaticClass()); + if (!ActorFactory) + { + HOUDINI_LOG_ERROR(TEXT("Could not find actor factory %s."), IsValid(UActorFactoryEmptyActor::StaticClass()) ? *(UActorFactoryEmptyActor::StaticClass()->GetName()) : TEXT("null")); + return false; + } + + constexpr UObject* AssetToSpawn = nullptr; + constexpr EObjectFlags ObjectFlags = RF_Transactional; + ParentBakeActorName = *MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), DesiredParentBakeActorName.ToString()); + ParentToActor = ActorFactory->CreateActor(AssetToSpawn, DesiredLevel, InIAC->GetComponentTransform(), ObjectFlags, ParentBakeActorName); + + if (!IsValid(ParentToActor)) + { + ParentToActor = nullptr; + } + else + { + OutBakeStats.NotifyObjectsCreated(ParentToActor->GetClass()->GetName(), 1); + + ParentToActor->SetActorLabel(ParentBakeActorName.ToString()); + OutActors.Emplace(FHoudiniEngineBakedActor( + ParentToActor, + DesiredParentBakeActorName, + WorldOutlinerFolderPath, + InOutputIndex, + InOutputObjectIdentifier, + nullptr, // InBakedObject + nullptr, // InSourceObject + nullptr, // InBakedComponent + PackageParams.BakeFolder, + PackageParams)); + } + } + + if (ParentToActor) + { + InBakedOutputObject.ActorBakeName = DesiredParentBakeActorName; + InBakedOutputObject.Actor = FSoftObjectPath(ParentToActor).ToString(); + } + // If we are baking in actor replacement mode, remove any previously baked instanced actors for this output if (bInReplaceActors && InBakedOutputObject.InstancedActors.Num() > 0) { @@ -1741,6 +1888,9 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( // Destroy Actor if it is valid and part of DesiredLevel if (IsValid(Actor) && Actor->GetLevel() == DesiredLevel) { + // Just before we destroy the actor, rename it with a _DELETE suffix, so that we can re-use + // its original name before garbage collection + FHoudiniEngineUtils::SafeRenameActor(Actor, Actor->GetName() + TEXT("_DELETE")); #if WITH_EDITOR LevelWorld->EditorDestroyActor(Actor, true); #else @@ -1757,34 +1907,33 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_IAC( // Iterates on all the instances of the IAC for (AActor* CurrentInstancedActor : InIAC->GetInstancedActors()) { - if (!CurrentInstancedActor || CurrentInstancedActor->IsPendingKill()) + if (!IsValid(CurrentInstancedActor)) continue; - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, InstancedObject->StaticClass(), BaseName.ToString()); + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, CurrentInstancedActor->GetClass(), PackageParams.ObjectName); FTransform CurrentTransform = CurrentInstancedActor->GetTransform(); AActor* NewActor = FHoudiniInstanceTranslator::SpawnInstanceActor(CurrentTransform, DesiredLevel, InIAC); - if (!NewActor || NewActor->IsPendingKill()) + if (!IsValid(NewActor)) continue; - const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) - (EditorUtilities::ECopyOptions::OnlyCopyEditOrInterpProperties | - EditorUtilities::ECopyOptions::PropagateChangesToArchetypeInstances); + OutBakeStats.NotifyObjectsCreated(NewActor->GetClass()->GetName(), 1); EditorUtilities::CopyActorProperties(CurrentInstancedActor, NewActor); - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, BaseName); - - FHoudiniEngineRuntimeUtils::SetActorLabel(NewActor, NewNameStr); + FHoudiniEngineUtils::SafeRenameActor(NewActor, NewNameStr); SetOutlinerFolderPath(NewActor, InOutputObject, WorldOutlinerFolderPath); NewActor->SetActorTransform(CurrentTransform); + if (ParentToActor) + NewActor->AttachToActor(ParentToActor, FAttachmentTransformRules::KeepWorldTransform); + InBakedOutputObject.InstancedActors.Add(FSoftObjectPath(NewActor).ToString()); FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( NewActor, - BaseName, + *PackageParams.ObjectName, WorldOutlinerFolderPath, InOutputIndex, InOutputObjectIdentifier, @@ -1827,22 +1976,24 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { UHoudiniMeshSplitInstancerComponent * InMSIC = Cast(InOutputObject.OutputComponent); - if (!InMSIC || InMSIC->IsPendingKill()) + if (!IsValid(InMSIC)) return false; AActor * OwnerActor = InMSIC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return false; UStaticMesh * StaticMesh = InMSIC->GetStaticMesh(); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return false; // Certain SMC materials may need to be duplicated if we didn't generate the mesh object. @@ -1872,8 +2023,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( FHoudiniAttributeResolver InstancerResolver; FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( DesiredWorld, HoudiniAssetComponent, InOutputObjectIdentifier, InOutputObject, ObjectName, - OwnerActor->GetName(), InstancerPackageParams, InstancerResolver, - InBakeFolder.Path, AssetPackageReplaceMode); + InstancerPackageParams, InstancerResolver, InBakeFolder.Path, AssetPackageReplaceMode); const bool bFoundMeshOutput = FindOutputObject(StaticMesh, EHoudiniOutputType::Mesh, InAllOutputs, MeshOutputIndex, MeshIdentifier); if (bFoundMeshOutput) @@ -1882,14 +2032,14 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( const FHoudiniOutputObject& MeshOutputObject = InAllOutputs[MeshOutputIndex]->GetOutputObjects().FindChecked(MeshIdentifier); FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( DesiredWorld, HoudiniAssetComponent, MeshIdentifier, MeshOutputObject, ObjectName, - OwnerActor->GetName(), MeshPackageParams, MeshResolver, - InBakeFolder.Path, AssetPackageReplaceMode); + MeshPackageParams, MeshResolver, InBakeFolder.Path, AssetPackageReplaceMode); // Update with resolved object name ObjectName = MeshPackageParams.ObjectName; // This will bake/duplicate the mesh if temporary, or return the input one if it is not BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, OutActors, InTempCookFolder.Path, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + StaticMesh, PreviousStaticMesh, MeshPackageParams, InAllOutputs, InBakedActors, InTempCookFolder.Path, + OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); } else { @@ -1901,13 +2051,14 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) { UMaterialInterface* MaterialInterface = Materials[MaterialIdx]; - if (!MaterialInterface || MaterialInterface->IsPendingKill()) + if (!IsValid(MaterialInterface)) continue; // Only duplicate the material if it is temporary if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InAllOutputs, InTempCookFolder.Path)) { - UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + MaterialInterface, InstancerPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap, OutBakeStats); DuplicatedMSICOverrideMaterials.Add(DuplicatedMaterial); } } @@ -1917,9 +2068,10 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( InBakedOutputObject.BakedObject = FSoftObjectPath(BakedStaticMesh).ToString(); // Instancer name adds the split identifier (INSTANCERNUM_VARIATIONNUM) - const FString BaseName = OwnerActor->GetName(); const FString InstancerName = ObjectName + "_instancer_" + InOutputObjectIdentifier.SplitIdentifier; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? BaseName : InFallbackWorldOutlinerFolder)); + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath( + InOutputObject, + FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InstancerPackageParams.HoudiniAssetActorName : InFallbackWorldOutlinerFolder)); @@ -1947,6 +2099,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -1960,7 +2114,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( AActor* FoundActor = nullptr; bool bHasBakeActorName = false; bool bSpawnedActor = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, InBakedActors, DesiredLevel, *InstancerName, bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) return false; if (!FoundActor) @@ -1974,10 +2128,12 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( // Spawn the new Actor FoundActor = DesiredLevel->OwningWorld->SpawnActor(SpawnInfo); - if (!FoundActor || FoundActor->IsPendingKill()) + if (!IsValid(FoundActor)) return false; bSpawnedActor = true; + OutBakeStats.NotifyObjectsCreated(FoundActor->GetClass()->GetName(), 1); + FHoudiniEngineRuntimeUtils::SetActorLabel(FoundActor, /*DesiredLevel->bUseExternalActors ? BakeActorName.ToString() : */FoundActor->GetName()); FoundActor->SetActorHiddenInGame(InMSIC->bHiddenInGame); @@ -2001,6 +2157,8 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( const FString UniqueActorNameStr = MakeUniqueObjectNameIfNeeded(DesiredLevel, AActor::StaticClass(), BakeActorName.ToString(), FoundActor); RenameAndRelabelActor(FoundActor, UniqueActorNameStr, false); + + OutBakeStats.NotifyObjectsUpdated(FoundActor->GetClass()->GetName(), 1); } // The folder is named after the original actor and contains all generated actors SetOutlinerFolderPath(FoundActor, InOutputObject, WorldOutlinerFolderPath); @@ -2017,16 +2175,18 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( // Now add s SMC component for each of the SMC's instance for (UStaticMeshComponent* CurrentSMC : InMSIC->GetInstances()) { - if (!CurrentSMC || CurrentSMC->IsPendingKill()) + if (!IsValid(CurrentSMC)) continue; UStaticMeshComponent* NewSMC = DuplicateObject( CurrentSMC, FoundActor, FName(MakeUniqueObjectNameIfNeeded(FoundActor, CurrentSMC->GetClass(), CurrentSMC->GetName()))); - if (!NewSMC || NewSMC->IsPendingKill()) + if (!IsValid(NewSMC)) continue; + OutBakeStats.NotifyObjectsCreated(NewSMC->GetClass()->GetName(), 1); + InBakedOutputObject.InstancedComponents.Add(FSoftObjectPath(NewSMC).ToString()); NewSMC->RegisterComponent(); @@ -2059,7 +2219,7 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( FoundActor->FinishSpawning(InTransform); InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - FHoudiniEngineBakedActor& OutputEntry = OutActors.Add_GetRef(FHoudiniEngineBakedActor( + FHoudiniEngineBakedActor OutputEntry( FoundActor, BakeActorName, WorldOutlinerFolderPath, @@ -2069,12 +2229,14 @@ FHoudiniEngineBakeUtils::BakeInstancerOutputToActors_MSIC( StaticMesh, nullptr, MeshPackageParams.BakeFolder, - MeshPackageParams)); + MeshPackageParams); OutputEntry.bInstancerOutput = true; OutputEntry.InstancerPackageParams = InstancerPackageParams; - // Postpone these calls to do them once per actor - OutActors.Last().bPostBakeProcessPostponed = true; + // Postpone these calls to do them once per actor + OutputEntry.bPostBakeProcessPostponed = true; + + OutBakedActorEntry = OutputEntry; // If we are baking in replace mode, remove previously baked components/instancers if (bInReplaceActors && bInReplaceAssets) @@ -2174,14 +2336,15 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, - const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { @@ -2193,7 +2356,7 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( return false; UHoudiniOutput* InOutput = InAllOutputs[InOutputIndex]; - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return false; UActorFactory* Factory = GEditor ? GEditor->FindActorFactoryByClass(UActorFactoryStaticMesh::StaticClass()) : nullptr; @@ -2210,6 +2373,9 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( const TMap& OldBakedOutputObjects = InBakedOutputs[InOutputIndex].BakedOutputObjects; TMap NewBakedOutputObjects; + TArray AllBakedActors = InBakedActors; + TArray NewBakedActors; + for (auto& Pair : OutputObjects) { const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; @@ -2221,11 +2387,11 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( BakedOutputObject = OldBakedOutputObjects.FindChecked(Identifier); UStaticMesh* StaticMesh = Cast(OutputObject.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) continue; UStaticMeshComponent* InSMC = Cast(OutputObject.OutputComponent); - if (!InSMC || InSMC->IsPendingKill()) + if (!IsValid(InSMC)) continue; // Find the HGPO that matches this output identifier @@ -2243,14 +2409,11 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( FHoudiniPackageParams PackageParams; - const FName WorldOutlinerFolderPath = GetOutlinerFolderPath(OutputObject, FName(InFallbackWorldOutlinerFolder.IsEmpty() ? InHoudiniAssetName : InFallbackWorldOutlinerFolder)); - if (!ResolvePackageParams( HoudiniAssetComponent, InOutput, Identifier, OutputObject, - InHoudiniAssetName, DefaultObjectName, InBakeFolder, bInReplaceAssets, @@ -2260,32 +2423,37 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( continue; } + const FName WorldOutlinerFolderPath = GetOutlinerFolderPath( + OutputObject, + FName(InFallbackWorldOutlinerFolder.IsEmpty() ? PackageParams.HoudiniAssetActorName : InFallbackWorldOutlinerFolder)); + // Bake the static mesh if it is still temporary UStaticMesh* BakedSM = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( StaticMesh, Cast(BakedOutputObject.GetBakedObjectIfValid()), PackageParams, InAllOutputs, - OutActors, + AllBakedActors, InTempCookFolder.Path, OutPackagesToSave, - InOutAlreadyBakedMaterialsMap); + InOutAlreadyBakedMaterialsMap, + OutBakeStats); - if (!BakedSM || BakedSM->IsPendingKill()) + if (!IsValid(BakedSM)) continue; // Record the baked object BakedOutputObject.BakedObject = FSoftObjectPath(BakedSM).ToString(); // Make sure we have a level to spawn to - if (!DesiredLevel || DesiredLevel->IsPendingKill()) + if (!IsValid(DesiredLevel)) continue; // Try to find the unreal_bake_actor, if specified FName BakeActorName; AActor* FoundActor = nullptr; bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + if (!FindUnrealBakeActor(OutputObject, BakedOutputObject, AllBakedActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) return false; UStaticMeshComponent* SMC = nullptr; @@ -2293,12 +2461,12 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( { // Spawn the new actor FoundActor = Factory->CreateActor(BakedSM, DesiredLevel, InSMC->GetComponentTransform()); - if (!FoundActor || FoundActor->IsPendingKill()) + if (!IsValid(FoundActor)) continue; // Copy properties to new actor AStaticMeshActor* SMActor = Cast(FoundActor); - if (!SMActor || SMActor->IsPendingKill()) + if (!IsValid(SMActor)) continue; SMC = SMActor->GetStaticMeshComponent(); @@ -2346,9 +2514,10 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( } BakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); - OutActors.Add(FHoudiniEngineBakedActor( + const FHoudiniEngineBakedActor& BakedActorEntry = NewBakedActors.Add_GetRef(FHoudiniEngineBakedActor( FoundActor, BakeActorName, WorldOutlinerFolderPath, InOutputIndex, Identifier, BakedSM, StaticMesh, SMC, PackageParams.BakeFolder, PackageParams)); + AllBakedActors.Add(BakedActorEntry); // If we are baking in replace mode, remove previously baked components/instancers if (bInReplaceActors && bInReplaceAssets) @@ -2364,6 +2533,8 @@ FHoudiniEngineBakeUtils::BakeStaticMeshOutputToActors( // Update the cached baked output data InBakedOutputs[InOutputIndex].BakedOutputObjects = NewBakedOutputObjects; + OutActors = MoveTemp(NewBakedActors); + return true; } @@ -2372,12 +2543,13 @@ bool FHoudiniEngineBakeUtils::ResolvePackageParams( UHoudiniOutput* InOutput, const FHoudiniOutputObjectIdentifier& Identifier, const FHoudiniOutputObject& InOutputObject, - const FString& InHoudiniAssetName, const FString& DefaultObjectName, const FDirectoryPath& InBakeFolder, const bool bInReplaceAssets, FHoudiniPackageParams& OutPackageParams, - TArray& OutPackagesToSave) + TArray& OutPackagesToSave, + const FString& InHoudiniAssetName, + const FString& InHoudiniAssetActorName) { FHoudiniAttributeResolver Resolver; @@ -2391,8 +2563,8 @@ bool FHoudiniEngineBakeUtils::ResolvePackageParams( // The resolver is then also configured with the package params for subsequent resolving (level_path etc) FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( DesiredWorld, HoudiniAssetComponent, Identifier, InOutputObject, DefaultObjectName, - InHoudiniAssetName, OutPackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); - + OutPackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode, + InHoudiniAssetName, InHoudiniAssetActorName); // See if this output object has an unreal_level_path attribute specified // In which case, we need to create/find the desired level for baking instead of using the current one @@ -2431,11 +2603,12 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, - const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { @@ -2447,9 +2620,15 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( return false; UHoudiniOutput* const Output = InAllOutputs[InOutputIndex]; - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) return false; + if (!IsValid(HoudiniAssetComponent)) + return false; + + AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); + const FString HoudiniAssetActorName = IsValid(OwnerActor) ? OwnerActor->GetName() : FString(); + TArray PackagesToSave; // Find the previous baked output data for this output index. If an entry @@ -2464,11 +2643,15 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( const TArray & HGPOs = Output->GetHoudiniGeoPartObjects(); + TArray AllBakedActors = InBakedActors; + TArray NewBakedActors; + for (auto & Pair : OutputObjects) { + FHoudiniOutputObject& OutputObject = Pair.Value; USplineComponent* SplineComponent = Cast(OutputObject.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) + if (!IsValid(SplineComponent)) continue; const FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; @@ -2492,7 +2675,7 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( if (!FoundHGPO) continue; - const FString DefaultObjectName = InHoudiniAssetName + "_" + SplineComponent->GetName(); + const FString DefaultObjectName = HoudiniAssetActorName + "_" + SplineComponent->GetName(); FHoudiniPackageParams PackageParams; // Set the replace mode based on if we are doing a replacement or incremental asset bake @@ -2505,15 +2688,24 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( UWorld* const DesiredWorld = SplineComponent ? SplineComponent->GetWorld() : GWorld; FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( DesiredWorld, HoudiniAssetComponent, Identifier, OutputObject, DefaultObjectName, - InHoudiniAssetName, PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); + PackageParams, Resolver, InBakeFolder.Path, AssetPackageReplaceMode); + FHoudiniEngineBakedActor OutputBakedActor; BakeCurve( OutputObject, BakedOutputObject, PackageParams, Resolver, bInReplaceActors, bInReplaceAssets, - OutActors, PackagesToSave, InFallbackActor, InFallbackWorldOutlinerFolder); + AllBakedActors, OutputBakedActor, PackagesToSave, OutBakeStats, InFallbackActor, InFallbackWorldOutlinerFolder); + + OutputBakedActor.OutputIndex = InOutputIndex; + OutputBakedActor.OutputObjectIdentifier = Identifier; + + AllBakedActors.Add(OutputBakedActor); + NewBakedActors.Add(OutputBakedActor); } // Update the cached bake output results BakedOutput.BakedOutputObjects = NewBakedOutputObjects; + + OutActors = MoveTemp(NewBakedActors); SaveBakedPackages(PackagesToSave); @@ -2523,10 +2715,10 @@ FHoudiniEngineBakeUtils::BakeHoudiniCurveOutputToActors( bool FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBlueprint * OutBlueprint) { - if (!InActor || InActor->IsPendingKill()) + if (!IsValid(InActor)) return false; - if (!OutBlueprint || OutBlueprint->IsPendingKill()) + if (!IsValid(OutBlueprint)) return false; if (InActor->GetInstanceComponents().Num() > 0) @@ -2537,7 +2729,7 @@ FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBluepri if (OutBlueprint->GeneratedClass) { AActor * CDO = Cast< AActor >(OutBlueprint->GeneratedClass->GetDefaultObject()); - if (!CDO || CDO->IsPendingKill()) + if (!IsValid(CDO)) return false; const auto CopyOptions = (EditorUtilities::ECopyOptions::Type) @@ -2547,7 +2739,7 @@ FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBluepri EditorUtilities::CopyActorProperties(InActor, CDO, CopyOptions); USceneComponent * Scene = CDO->GetRootComponent(); - if (Scene && !Scene->IsPendingKill()) + if (IsValid(Scene)) { Scene->SetRelativeLocation(FVector::ZeroVector); Scene->SetRelativeRotation(FRotator::ZeroRotator); @@ -2561,7 +2753,7 @@ FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(AActor * InActor, UBluepri break; USceneComponent * Component = Scene->GetAttachChildren()[ChildCount - 1]; - if (Component && !Component->IsPendingKill()) + if (IsValid(Component)) Component->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); } check(Scene->GetAttachChildren().Num() == 0); @@ -2634,7 +2826,7 @@ FHoudiniEngineBakeUtils::BakeBlueprints( TArray& OutBlueprints, TArray& OutPackagesToSave) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; AActor* OwnerActor = HoudiniAssetComponent->GetOwner(); @@ -2679,12 +2871,14 @@ FHoudiniEngineBakeUtils::BakeBlueprints( Actors, bInRecenterBakedActors, bInReplaceAssets, + IsValid(HoudiniAssetComponent->GetHoudiniAsset()) ? HoudiniAssetComponent->GetHoudiniAsset()->GetName() : FString(), bIsOwnerActorValid ? OwnerActor->GetName() : FString(), HoudiniAssetComponent->BakeFolder, &BakedOutputs, nullptr, OutBlueprints, - OutPackagesToSave); + OutPackagesToSave, + InBakeStats); return bBakeSuccess; } @@ -2695,16 +2889,18 @@ FHoudiniEngineBakeUtils::BakeStaticMesh( const FHoudiniPackageParams& PackageParams, const TArray& InAllOutputs, const FDirectoryPath& InTempCookFolder, - TMap& InOutAlreadyBakedMaterialsMap) + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats) { - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return nullptr; TArray PackagesToSave; TArray Outputs; const TArray BakedResults; UStaticMesh* BakedStaticMesh = FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( - StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave, InOutAlreadyBakedMaterialsMap); + StaticMesh, nullptr, PackageParams, InAllOutputs, BakedResults, InTempCookFolder.Path, PackagesToSave, + InOutAlreadyBakedMaterialsMap, OutBakeStats); if (BakedStaticMesh) { @@ -2730,8 +2926,7 @@ FHoudiniEngineBakeUtils::BakeLandscape( TArray& InBakedOutputs, bool bInReplaceActors, bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, + const FString& BakePath, FHoudiniEngineOutputStats& BakeStats ) { @@ -2792,7 +2987,7 @@ FHoudiniEngineBakeUtils::BakeLandscape( UWorld* const DesiredWorld = Landscape ? Landscape->GetWorld() : GWorld; FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( DesiredWorld, HoudiniAssetComponent, ObjectIdentifier, OutputObject, ObjectName, - HoudiniAssetName, PackageParams, Resolver, BakePath, AssetPackageReplaceMode); + PackageParams, Resolver, BakePath, AssetPackageReplaceMode); BakeLandscapeObject(OutputObject, BakedOutputObject, bInReplaceActors, bInReplaceAssets, PackageParams, Resolver, LandscapeWorldsToUpdate, PackagesToSave, BakeStats); @@ -3033,13 +3228,15 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( // Replace materials if (TileActor->LandscapeMaterial) { - UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(TileActor->LandscapeMaterial, PackageParams, OutPackagesToSave, AlreadyBakedMaterialsMap); + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + TileActor->LandscapeMaterial, PackageParams, OutPackagesToSave, AlreadyBakedMaterialsMap, BakeStats); TileActor->LandscapeMaterial = DuplicatedMaterial; } if (TileActor->LandscapeHoleMaterial) { - UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(TileActor->LandscapeHoleMaterial, PackageParams, OutPackagesToSave, AlreadyBakedMaterialsMap); + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + TileActor->LandscapeHoleMaterial, PackageParams, OutPackagesToSave, AlreadyBakedMaterialsMap, BakeStats); TileActor->LandscapeHoleMaterial = DuplicatedMaterial; } @@ -3142,6 +3339,7 @@ FHoudiniEngineBakeUtils::BakeLandscapeObject( LayerInfo, LayerPackage, *LayerPackageName); if (IsValid(BakedLayer)) { + BakeStats.NotifyObjectsCreated(BakedLayer->GetClass()->GetName(), 1); OutPackagesToSave.Add(LayerPackage); // Trigger update of the Layer Info @@ -3195,9 +3393,10 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( const TArray& InCurrentBakedActors, const FString& InTemporaryCookFolder, TArray & OutCreatedPackages, - TMap& InOutAlreadyBakedMaterialsMap) + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats) { - if (!InStaticMesh || InStaticMesh->IsPendingKill()) + if (!IsValid(InStaticMesh)) return nullptr; const bool bIsTemporaryStaticMesh = IsObjectTemporary(InStaticMesh, EHoudiniOutputType::Mesh, InParentOutputs, InTemporaryCookFolder); @@ -3239,9 +3438,9 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( } FString CreatedPackageName; UPackage* MeshPackage = PackageParams.CreatePackageForObject(CreatedPackageName, BakeCounter); - if (!MeshPackage || MeshPackage->IsPendingKill()) + if (!IsValid(MeshPackage)) return nullptr; - + OutBakeStats.NotifyPackageCreated(1); OutCreatedPackages.Add(MeshPackage); // We need to be sure the package has been fully loaded before calling DuplicateObject @@ -3268,13 +3467,15 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( FStaticMeshComponentRecreateRenderStateContext SMRecreateContext(ExistingMesh); DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); bFoundExistingMesh = true; + OutBakeStats.NotifyObjectsReplaced(UStaticMesh::StaticClass()->GetName(), 1); } else { DuplicatedStaticMesh = DuplicateObject(InStaticMesh, MeshPackage, *CreatedPackageName); + OutBakeStats.NotifyObjectsUpdated(UStaticMesh::StaticClass()->GetName(), 1); } - if (!DuplicatedStaticMesh || DuplicatedStaticMesh->IsPendingKill()) + if (!IsValid(DuplicatedStaticMesh)) return nullptr; // Add meta information. @@ -3291,14 +3492,14 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( for (int32 MaterialIdx = 0; MaterialIdx < Materials.Num(); ++MaterialIdx) { UMaterialInterface* MaterialInterface = Materials[MaterialIdx].MaterialInterface; - if (!MaterialInterface || MaterialInterface->IsPendingKill()) + if (!IsValid(MaterialInterface)) continue; // Only duplicate the material if it is temporary if (IsObjectTemporary(MaterialInterface, EHoudiniOutputType::Invalid, InParentOutputs, InTemporaryCookFolder)) { UPackage * MaterialPackage = Cast(MaterialInterface->GetOuter()); - if (MaterialPackage && !MaterialPackage->IsPendingKill()) + if (IsValid(MaterialPackage)) { FString MaterialName; if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation( @@ -3314,7 +3515,7 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( UMaterialInterface * Material = MaterialInterface; - if (Material && !Material->IsPendingKill()) + if (IsValid(Material)) { // Look for a previous bake material at this index UMaterialInterface* PreviousBakeMaterial = nullptr; @@ -3324,9 +3525,10 @@ FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackageIfNeeded( } // Duplicate material resource. UMaterialInterface * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages, InOutAlreadyBakedMaterialsMap); + Material, PreviousBakeMaterial, MaterialName, PackageParams, OutCreatedPackages, InOutAlreadyBakedMaterialsMap, + OutBakeStats); - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + if (!IsValid(DuplicatedMaterial)) continue; // Store duplicated material. @@ -3360,9 +3562,10 @@ ALandscapeProxy* FHoudiniEngineBakeUtils::BakeHeightfield( ALandscapeProxy * InLandscapeProxy, const FHoudiniPackageParams & PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType) + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType, + FHoudiniEngineOutputStats& OutBakeStats) { - if (!InLandscapeProxy || InLandscapeProxy->IsPendingKill()) + if (!IsValid(InLandscapeProxy)) return nullptr; const FString & BakeFolder = PackageParams.BakeFolder; @@ -3381,7 +3584,7 @@ FHoudiniEngineBakeUtils::BakeHeightfield( { // Create heightmap image to the bake folder ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + if (!IsValid(InLandscapeInfo)) return nullptr; // bake to image must use absoluate path, @@ -3409,7 +3612,7 @@ FHoudiniEngineBakeUtils::BakeHeightfield( case EHoudiniLandscapeOutputBakeType::BakeToWorld: { ULandscapeInfo * InLandscapeInfo = InLandscapeProxy->GetLandscapeInfo(); - if (!InLandscapeInfo || InLandscapeInfo->IsPendingKill()) + if (!IsValid(InLandscapeInfo)) return nullptr; // 0. Get Landscape Data // @@ -3454,6 +3657,8 @@ FHoudiniEngineBakeUtils::BakeHeightfield( if (!CreatedPackage) return nullptr; + OutBakeStats.NotifyPackageCreated(1); + // 2. Create a new world asset with dialog // UWorldFactory* Factory = NewObject(); FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); @@ -3467,6 +3672,7 @@ FHoudiniEngineBakeUtils::BakeHeightfield( if (!NewWorld) return nullptr; + OutBakeStats.NotifyObjectsCreated(NewWorld->GetClass()->GetName(), 1); NewWorld->SetCurrentLevel(NewWorld->PersistentLevel); // 4. Spawn a landscape proxy actor in the created world @@ -3474,6 +3680,7 @@ FHoudiniEngineBakeUtils::BakeHeightfield( if (!BakedLandscapeProxy) return nullptr; + OutBakeStats.NotifyObjectsCreated(BakedLandscapeProxy->GetClass()->GetName(), 1); // Create a new GUID FGuid currentGUID = FGuid::NewGuid(); BakedLandscapeProxy->SetLandscapeGuid(currentGUID); @@ -3505,13 +3712,15 @@ FHoudiniEngineBakeUtils::BakeHeightfield( if (BakedLandscapeProxy->LandscapeMaterial) { - UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(BakedLandscapeProxy->LandscapeMaterial, PackageParams, PackagesToSave, AlreadyBakedMaterialsMap); + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + BakedLandscapeProxy->LandscapeMaterial, PackageParams, PackagesToSave, AlreadyBakedMaterialsMap, OutBakeStats); BakedLandscapeProxy->LandscapeMaterial = DuplicatedMaterial; } if (BakedLandscapeProxy->LandscapeHoleMaterial) { - UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage(BakedLandscapeProxy->LandscapeHoleMaterial, PackageParams, PackagesToSave, AlreadyBakedMaterialsMap); + UMaterialInterface * DuplicatedMaterial = BakeSingleMaterialToPackage( + BakedLandscapeProxy->LandscapeHoleMaterial, PackageParams, PackagesToSave, AlreadyBakedMaterialsMap, OutBakeStats); BakedLandscapeProxy->LandscapeHoleMaterial = DuplicatedMaterial; } @@ -3542,8 +3751,10 @@ FHoudiniEngineBakeUtils::BakeCurve( USplineComponent* InSplineComponent, ULevel* InLevel, const FHoudiniPackageParams &PackageParams, + const FName& InActorName, AActor*& OutActor, USplineComponent*& OutSplineComponent, + FHoudiniEngineOutputStats& OutBakeStats, FName InOverrideFolderPath, AActor* InActor) { @@ -3554,22 +3765,30 @@ FHoudiniEngineBakeUtils::BakeCurve( return false; OutActor = Factory->CreateActor(nullptr, InLevel, InSplineComponent->GetComponentTransform()); + if (IsValid(OutActor)) + OutBakeStats.NotifyObjectsCreated(OutActor->GetClass()->GetName(), 1); } else { OutActor = InActor; + if (IsValid(OutActor)) + OutBakeStats.NotifyObjectsUpdated(OutActor->GetClass()->GetName(), 1); } - // The default name will be based on the static mesh package, we would prefer it to be based on the Houdini asset - const FName BaseActorName(PackageParams.ObjectName); - const FString NewNameStr = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), BaseActorName.ToString(), OutActor); + // Fallback to ObjectName from package params if InActorName is not set + const FName ActorName = InActorName.IsNone() ? FName(PackageParams.ObjectName) : InActorName; + const FString NewNameStr = MakeUniqueObjectNameIfNeeded(InLevel, OutActor->GetClass(), InActorName.ToString(), OutActor); RenameAndRelabelActor(OutActor, NewNameStr, false); - OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetName) : InOverrideFolderPath); + OutActor->SetFolderPath(InOverrideFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetActorName) : InOverrideFolderPath); USplineComponent* DuplicatedSplineComponent = DuplicateObject( InSplineComponent, OutActor, FName(MakeUniqueObjectNameIfNeeded(OutActor, InSplineComponent->GetClass(), PackageParams.ObjectName))); + + if (IsValid(DuplicatedSplineComponent)) + OutBakeStats.NotifyObjectsCreated(DuplicatedSplineComponent->GetClass()->GetName(), 1); + OutActor->AddInstanceComponent(DuplicatedSplineComponent); const bool bCreateIfMissing = true; USceneComponent* RootComponent = GetActorRootComponent(OutActor, bCreateIfMissing); @@ -3595,8 +3814,10 @@ FHoudiniEngineBakeUtils::BakeCurve( FHoudiniAttributeResolver& InResolver, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor, const FString& InFallbackWorldOutlinerFolder) { @@ -3629,6 +3850,8 @@ FHoudiniEngineBakeUtils::BakeCurve( // TODO: ? always add? if (bCreatedPackage && DesiredLevel) { + OutBakeStats.NotifyPackageCreated(1); + OutBakeStats.NotifyObjectsCreated(DesiredLevel->GetClass()->GetName(), 1); // We can now save the package again, and unload it. OutPackagesToSave.Add(DesiredLevel->GetOutermost()); } @@ -3641,7 +3864,7 @@ FHoudiniEngineBakeUtils::BakeCurve( FName BakeActorName; AActor* FoundActor = nullptr; bool bHasBakeActorName = false; - if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, OutActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) + if (!FindUnrealBakeActor(InOutputObject, InBakedOutputObject, InBakedActors, DesiredLevel, *(PackageParams.ObjectName), bInReplaceActors, InFallbackActor, FoundActor, bHasBakeActorName, BakeActorName)) return false; // If we are baking in replace mode, remove the previous bake component @@ -3654,11 +3877,9 @@ FHoudiniEngineBakeUtils::BakeCurve( } } - FHoudiniPackageParams CurvePackageParams = PackageParams; - CurvePackageParams.ObjectName = BakeActorName.ToString(); USplineComponent* NewSplineComponent = nullptr; - const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(CurvePackageParams.HoudiniAssetName)); - if (!BakeCurve(SplineComponent, DesiredLevel, CurvePackageParams, FoundActor, NewSplineComponent, OutlinerFolderPath, FoundActor)) + const FName OutlinerFolderPath = GetOutlinerFolderPath(InOutputObject, *(PackageParams.HoudiniAssetActorName)); + if (!BakeCurve(SplineComponent, DesiredLevel, PackageParams, BakeActorName, FoundActor, NewSplineComponent, OutBakeStats, OutlinerFolderPath, FoundActor)) return false; InBakedOutputObject.Actor = FSoftObjectPath(FoundActor).ToString(); @@ -3674,12 +3895,19 @@ FHoudiniEngineBakeUtils::BakeCurve( InBakedOutputObject, bInDestroyBakedComponent, bInDestroyBakedInstancedActors, bInDestroyBakedInstancedComponents); } - FHoudiniEngineBakedActor Result; - Result.Actor = FoundActor; - Result.ActorBakeName = BakeActorName; - Result.BakeFolderPath = PackageParams.BakeFolder; - Result.BakedObjectPackageParams = PackageParams; - OutActors.Add(Result); + FHoudiniEngineBakedActor Result( + FoundActor, + BakeActorName, + OutlinerFolderPath.IsNone() ? FName(PackageParams.HoudiniAssetActorName) : OutlinerFolderPath, + INDEX_NONE, // Output index + FHoudiniOutputObjectIdentifier(), + nullptr, // InBakedObject + nullptr, // InSourceObject + NewSplineComponent, + PackageParams.BakeFolder, + PackageParams); + + OutBakedActorEntry = Result; return true; } @@ -3691,7 +3919,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToActor( UWorld* WorldToSpawn, const FTransform & SpawnTransform) { - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(InHoudiniSplineComponent)) return nullptr; TArray & DisplayPoints = InHoudiniSplineComponent->DisplayPoints; @@ -3761,7 +3989,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( UWorld* WorldToSpawn, const FTransform & SpawnTransform) { - if (!InHoudiniSplineComponent || InHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(InHoudiniSplineComponent)) return nullptr; FGuid BakeGUID = FGuid::NewGuid(); @@ -3782,7 +4010,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( // See if package exists, if it does, we need to regenerate the name. UPackage * Package = FindPackage(nullptr, *PackageName); - if (Package && !Package->IsPendingKill()) + if (IsValid(Package)) { // Package does exist, there's a collision, we need to generate a new name. BakeGUID.Invalidate(); @@ -3799,7 +4027,7 @@ FHoudiniEngineBakeUtils::BakeInputHoudiniCurveToBlueprint( TArray PackagesToSave; UBlueprint * Blueprint = nullptr; - if (CreatedHoudiniSplineActor && !CreatedHoudiniSplineActor->IsPendingKill()) + if (IsValid(CreatedHoudiniSplineActor)) { UObject* Asset = nullptr; @@ -3855,11 +4083,11 @@ FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( UPackage * Package, UObject * Object, const TCHAR * Key, const TCHAR * Value) { - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return; UMetaData * MetaData = Package->GetMetaData(); - if (MetaData && !MetaData->IsPendingKill()) + if (IsValid(MetaData)) MetaData->SetValue(Object, Key, Value); } @@ -3869,11 +4097,11 @@ FHoudiniEngineBakeUtils:: GetHoudiniGeneratedNameFromMetaInformation( UPackage * Package, UObject * Object, FString & HoudiniName) { - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return false; UMetaData * MetaData = Package->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) + if (!IsValid(MetaData)) return false; if (MetaData->HasValue(Object, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) @@ -3893,7 +4121,8 @@ UMaterialInterface * FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( UMaterialInterface * Material, UMaterialInterface* PreviousBakeMaterial, const FString & MaterialName, const FHoudiniPackageParams& ObjectPackageParams, TArray & OutGeneratedPackages, - TMap& InOutAlreadyBakedMaterialsMap) + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats) { if (InOutAlreadyBakedMaterialsMap.Contains(Material)) { @@ -3929,14 +4158,18 @@ FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( UPackage * MaterialPackage = MaterialPackageParams.CreatePackageForObject(CreatedMaterialName, BakeCounter); - if (!MaterialPackage || MaterialPackage->IsPendingKill()) + if (!IsValid(MaterialPackage)) return nullptr; + OutBakeStats.NotifyPackageCreated(1); + // Clone material. DuplicatedMaterial = DuplicateObject< UMaterialInterface >(Material, MaterialPackage, *CreatedMaterialName); - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + if (!IsValid(DuplicatedMaterial)) return nullptr; + OutBakeStats.NotifyObjectsCreated(DuplicatedMaterial->GetClass()->GetName(), 1); + // Add meta information. FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( MaterialPackage, DuplicatedMaterial, @@ -3959,7 +4192,7 @@ FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( PreviousBakeExpression = PreviousBakeMaterialExpressions[ExpressionIdx]; } FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( - Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages); + Expression, PreviousBakeExpression, MaterialPackageParams, OutGeneratedPackages, OutBakeStats); } } @@ -3988,18 +4221,18 @@ FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( void FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, - const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages) + const FHoudiniPackageParams& PackageParams, TArray & OutCreatedPackages, FHoudiniEngineOutputStats& OutBakeStats) { UMaterialExpressionTextureSample * TextureSample = Cast< UMaterialExpressionTextureSample >(MaterialExpression); - if (!TextureSample || TextureSample->IsPendingKill()) + if (!IsValid(TextureSample)) return; UTexture2D * Texture = Cast< UTexture2D >(TextureSample->Texture); - if (!Texture || Texture->IsPendingKill()) + if (!IsValid(Texture)) return; UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) + if (!IsValid(TexturePackage)) return; // Try to get the previous bake's texture @@ -4017,7 +4250,7 @@ FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( { // Duplicate texture. UTexture2D * DuplicatedTexture = FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( - Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages); + Texture, PreviousBakeTexture, GeneratedTextureName, PackageParams, OutCreatedPackages, OutBakeStats); // Re-assign generated texture. TextureSample->Texture = DuplicatedTexture; @@ -4027,20 +4260,20 @@ FHoudiniEngineBakeUtils::ReplaceDuplicatedMaterialTextureSample( UTexture2D * FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages) + TArray & OutCreatedPackages, FHoudiniEngineOutputStats& OutBakeStats) { UTexture2D* DuplicatedTexture = nullptr; #if WITH_EDITOR // Retrieve original package of this texture. UPackage * TexturePackage = Cast< UPackage >(Texture->GetOuter()); - if (!TexturePackage || TexturePackage->IsPendingKill()) + if (!IsValid(TexturePackage)) return nullptr; FString GeneratedTextureName; if (FHoudiniEngineBakeUtils::GetHoudiniGeneratedNameFromMetaInformation(TexturePackage, Texture, GeneratedTextureName)) { UMetaData * MetaData = TexturePackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) + if (!IsValid(MetaData)) return nullptr; // Retrieve texture type. @@ -4068,14 +4301,18 @@ FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( UPackage * NewTexturePackage = TexturePackageParams.CreatePackageForObject(CreatedTextureName, BakeCounter); - if (!NewTexturePackage || NewTexturePackage->IsPendingKill()) + if (!IsValid(NewTexturePackage)) return nullptr; + + OutBakeStats.NotifyPackageCreated(1); // Clone texture. DuplicatedTexture = DuplicateObject< UTexture2D >(Texture, NewTexturePackage, *CreatedTextureName); - if (!DuplicatedTexture || DuplicatedTexture->IsPendingKill()) + if (!IsValid(DuplicatedTexture)) return nullptr; + OutBakeStats.NotifyObjectsCreated(DuplicatedTexture->GetClass()->GetName(), 1); + // Add meta information. FHoudiniEngineBakeUtils::AddHoudiniMetaInformationToPackage( NewTexturePackage, DuplicatedTexture, @@ -4103,12 +4340,12 @@ FHoudiniEngineBakeUtils::DuplicateTextureAndCreatePackage( bool FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(UHoudiniAssetComponent* HoudiniAssetComponent) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return false; AActor * ActorOwner = HoudiniAssetComponent->GetOwner(); - if (!ActorOwner || ActorOwner->IsPendingKill()) + if (!IsValid(ActorOwner)) return false; UWorld* World = ActorOwner->GetWorld(); @@ -4147,7 +4384,7 @@ FHoudiniEngineBakeUtils::SaveBakedPackages(TArray & PackagesToSave, b bool FHoudiniEngineBakeUtils::FindOutputObject(const UObject* InObjectToFind, EHoudiniOutputType InOutputType, const TArray InOutputs, int32& OutOutputIndex, FHoudiniOutputObjectIdentifier &OutIdentifier) { - if (!InObjectToFind || InObjectToFind->IsPendingKill()) + if (!IsValid(InObjectToFind)) return false; const int32 NumOutputs = InOutputs.Num(); @@ -4180,14 +4417,14 @@ FHoudiniEngineBakeUtils::FindOutputObject(const UObject* InObjectToFind, EHoudin bool FHoudiniEngineBakeUtils::IsObjectTemporary(UObject* InObject, EHoudiniOutputType InOutputType, UHoudiniAssetComponent* InHAC) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; FString TempPath = FString(); // TODO: Get the HAC outputs in a better way? TArray Outputs; - if (InHAC && !InHAC->IsPendingKill()) + if (IsValid(InHAC)) { const int32 NumOutputs = InHAC->GetNumOutputs(); Outputs.Reserve(NumOutputs); @@ -4210,7 +4447,7 @@ bool FHoudiniEngineBakeUtils::IsObjectInTempFolder(UObject* const InObject, cons // Check the package path for this object // If it is in the HAC temp directory, assume it is temporary, and will need to be duplicated UPackage* ObjectPackage = InObject->GetOutermost(); - if (ObjectPackage && !ObjectPackage->IsPendingKill()) + if (IsValid(ObjectPackage)) { const FString PathName = ObjectPackage->GetPathName(); if (PathName.StartsWith(InTemporaryCookFolder)) @@ -4228,7 +4465,7 @@ bool FHoudiniEngineBakeUtils::IsObjectInTempFolder(UObject* const InObject, cons bool FHoudiniEngineBakeUtils::IsObjectTemporary( UObject* InObject, EHoudiniOutputType InOutputType, const TArray& InParentOutputs, const FString& InTemporaryCookFolder) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; int32 ParentOutputIndex = -1; @@ -4243,14 +4480,14 @@ bool FHoudiniEngineBakeUtils::IsObjectTemporary( /* UPackage* ObjectPackage = InObject->GetOutermost(); - if (ObjectPackage && !ObjectPackage->IsPendingKill()) + if (IsValid(ObjectPackage)) { // TODO: this just indicates that the object was generated by H // it could as well have been baked before... // we should probably add a "temp" metadata // Look in the meta info as well?? UMetaData * MetaData = ObjectPackage->GetMetaData(); - if (!MetaData || MetaData->IsPendingKill()) + if (!IsValid(MetaData)) return false; if (MetaData->HasValue(InObject, HAPI_UNREAL_PACKAGE_META_GENERATED_OBJECT)) @@ -4268,10 +4505,10 @@ FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent( UStaticMeshComponent* InSMC, bool bInCopyWorldTransform) { - if (!NewSMC || NewSMC->IsPendingKill()) + if (!IsValid(NewSMC)) return; - if (!InSMC || InSMC->IsPendingKill()) + if (!IsValid(InSMC)) return; // Copy properties to new actor @@ -4299,7 +4536,7 @@ FHoudiniEngineBakeUtils::CopyPropertyToNewActorAndComponent( NewSMC->SetPhysMaterialOverride(InBodySetup->GetPhysMaterial()); } - if (NewActor && !NewActor->IsPendingKill()) + if (IsValid(NewActor)) NewActor->SetActorHiddenInGame(InSMC->bHiddenInGame); NewSMC->SetVisibility(InSMC->IsVisible()); @@ -4572,6 +4809,7 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( bool bInReplaceAssets, bool bInBakeToWorkResultActor, bool bInIsAutoBake, + const TArray& InBakedActors, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats, @@ -4616,13 +4854,6 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( return false; } - // OutBakedActors contains each actor that contains baked PDG results. Actors may - // appear in the array more than once if they have more than one baked result/component associated with - // them - // TArray BakedActorsForWorkResultObject; - const int32 NumBakedPre = OutBakedActors.Num(); - const FString HoudiniAssetName(WorkResultObject.Name); - // Find the previous bake output for this work result object FString Key; InNode->GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, Key); @@ -4630,18 +4861,19 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( const UHoudiniAssetComponent* HoudiniAssetComponent = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(InPDGAssetLink); check(IsValid(HoudiniAssetComponent)); - + + TArray WROBakedActors; BakeHoudiniOutputsToActors( HoudiniAssetComponent, Outputs, BakedOutputContainer.BakedOutputs, - HoudiniAssetName, WorkResultObjectActor->GetActorTransform(), InPDGAssetLink->BakeFolder, InPDGAssetLink->GetTemporaryCookFolder(), bInReplaceActors, bInReplaceAssets, - OutBakedActors, + InBakedActors, + WROBakedActors, OutPackagesToSave, OutBakeStats, InOutputTypesToBake, @@ -4653,12 +4885,10 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( FOutputActorOwner& OutputActorOwner = WorkResultObject.GetOutputActorOwner(); AActor* const WROActor = OutputActorOwner.GetOutputActor(); FHoudiniEngineBakedActor const * BakedWROActorEntry = nullptr; - const int32 NumBakedPost = OutBakedActors.Num(); - if (NumBakedPost > NumBakedPre) + if (WROBakedActors.Num() > 0) { - for (int32 Index = FMath::Max(0, NumBakedPre); Index < NumBakedPost; ++Index) + for (FHoudiniEngineBakedActor& BakedActorEntry : WROBakedActors) { - FHoudiniEngineBakedActor& BakedActorEntry = OutBakedActors[Index]; BakedActorEntry.PDGWorkResultArrayIndex = InWorkResultArrayIndex; BakedActorEntry.PDGWorkItemIndex = WorkResult.WorkItemIndex; BakedActorEntry.PDGWorkResultObjectArrayIndex = InWorkResultObjectArrayIndex; @@ -4674,7 +4904,8 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( if (bInBakeToWorkResultActor) { // if we re-used the temp actor as a bake actor, then remove its temp outputs - WorkResultObject.DestroyResultOutputs(); + constexpr bool bDeleteOutputActor = false; + InNode->DeleteWorkResultObjectOutputs(InWorkResultArrayIndex, InWorkResultObjectArrayIndex, bDeleteOutputActor); if (WROActor) { if (BakedWROActorEntry) @@ -4706,7 +4937,9 @@ FHoudiniEngineBakeUtils::BakePDGWorkResultObject( if (bInIsAutoBake) WorkResultObject.SetAutoBakedSinceLastLoad(true); - // OutBakedActors.Append(BakedActorsForWorkResultObject); + + OutBakedActors = MoveTemp(WROBakedActors); + return true; } @@ -4822,7 +5055,7 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; if (!IsValid(InNode)) @@ -4857,12 +5090,15 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( const int32 NumWorkResults = InNode->WorkResult.Num(); FScopedSlowTask Progress(NumWorkResults, FText::FromString(FString::Printf(TEXT("Baking PDG Node Output %s ..."), *InNode->GetName()))); Progress.MakeDialog(); + TArray OurBakedActors; + TArray WorkResultObjectBakedActors; for (int32 WorkResultArrayIdx = 0; WorkResultArrayIdx < NumWorkResults; ++WorkResultArrayIdx) { FTOPWorkResult& WorkResult = InNode->WorkResult[WorkResultArrayIdx]; const int32 NumWorkResultObjects = WorkResult.ResultObjects.Num(); for (int32 WorkResultObjectArrayIdx = 0; WorkResultObjectArrayIdx < NumWorkResultObjects; ++WorkResultObjectArrayIdx) { + WorkResultObjectBakedActors.Reset(); Progress.EnterProgressFrame(1.0f); BakePDGWorkResultObject( @@ -4874,16 +5110,21 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeOutputsKeepActors( bReplaceAssets, !bInBakeForBlueprint, bInIsAutoBake, - OutBakedActors, + OurBakedActors, + WorkResultObjectBakedActors, OutPackagesToSave, OutBakeStats, OutputTypesToBake.Num() > 0 ? &OutputTypesToBake : nullptr, InstancerComponentTypesToBake.Num() > 0 ? &InstancerComponentTypesToBake : nullptr, FallbackWorldOutlinerFolderPath.ToString() ); + + OurBakedActors.Append(WorkResultObjectBakedActors); } } + OutBakedActors = MoveTemp(OurBakedActors); + return true; } @@ -4940,7 +5181,7 @@ FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; if (!IsValid(InNetwork)) @@ -4961,7 +5202,7 @@ FHoudiniEngineBakeUtils::BakePDGTOPNetworkOutputsKeepActors( bool FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(UHoudiniPDGAssetLink* InPDGAssetLink, const EPDGBakeSelectionOption InBakeSelectionOption, const EPDGBakePackageReplaceModeOption InPDGBakePackageReplaceMode, bool bInRecenterBakedActors) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; TArray PackagesToSave; @@ -5033,12 +5274,14 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( const TArray& InBakedActors, bool bInRecenterBakedActors, bool bInReplaceAssets, - const FString& InAssetName, + const FString& InHoudiniAssetName, + const FString& InHoudiniAssetActorName, const FDirectoryPath& InBakeFolder, TArray* const InNonPDGBakedOutputs, TMap* const InPDGBakedOutputs, TArray& OutBlueprints, - TArray& OutPackagesToSave) + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats) { // // Clear selection // if (GEditor) @@ -5052,19 +5295,61 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem(); const bool bIsAssetEditorSubsystemValid = IsValid(AssetEditorSubsystem); TArray AssetsToReOpenEditors; - TSet BakedActorSet; + TMap BakedActorMap; for (const FHoudiniEngineBakedActor& Entry : InBakedActors) { AActor *Actor = Entry.Actor; - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) continue; - if (BakedActorSet.Contains(Actor)) + // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment + // is consistent with the bake counter + int32 BakeCounter = 0; + FHoudiniBakedOutputObject* BakedOutputObject = nullptr; + // Get the baked output object + if (Entry.PDGWorkResultArrayIndex >= 0 && Entry.PDGWorkItemIndex >= 0 && Entry.PDGWorkResultObjectArrayIndex >= 0 && InPDGBakedOutputs) + { + const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkResultArrayIndex, Entry.PDGWorkResultObjectArrayIndex); + FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); + if (WorkResultObjectBakedOutput) + { + if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + } + else if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs) + { + if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs->IsValidIndex(Entry.OutputIndex)) + { + BakedOutputObject = (*InNonPDGBakedOutputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); + } + } + + if (BakedActorMap.Contains(Actor)) + { + // Record the blueprint as the previous bake blueprint and clear the info of the temp bake actor/component + if (BakedOutputObject) + { + UBlueprint* const BakedBlueprint = BakedActorMap[Actor]; + if (BakedBlueprint) + BakedOutputObject->Blueprint = FSoftObjectPath(BakedBlueprint).ToString(); + else + BakedOutputObject->Blueprint.Empty(); + BakedOutputObject->Actor.Empty(); + // TODO: Set the baked component to the corresponding component in the blueprint? + BakedOutputObject->BakedComponent.Empty(); + } continue; + } - BakedActorSet.Add(Actor); + // Add a placeholder entry since we've started processing the actor, we'll replace the null with the blueprint + // if successful and leave it as null if the bake fails (the actor will then be skipped if it appears in the + // array again). + BakedActorMap.Add(Actor, nullptr); UObject* Asset = nullptr; @@ -5095,55 +5380,32 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( FHoudiniOutputObjectIdentifier(), BakeFolderPath, Entry.ActorBakeName.ToString() + "_BP", - InAssetName, + InHoudiniAssetName, + InHoudiniAssetActorName, AssetPackageReplaceMode); - // If we have a previously baked a blueprint, get the bake counter from it so that both replace and increment - // is consistent with the bake counter - int32 BakeCounter = 0; - UBlueprint* InPreviousBlueprint = nullptr; - FHoudiniBakedOutputObject* BakedOutputObject = nullptr; - FHoudiniPDGWorkResultObjectBakedOutput* WorkResultObjectBakedOutput = nullptr; - // Get the baked output object - if (Entry.PDGWorkResultArrayIndex >= 0 && Entry.PDGWorkItemIndex >= 0 && Entry.PDGWorkResultObjectArrayIndex >= 0 && InPDGBakedOutputs) - { - const FString Key = UTOPNode::GetBakedWorkResultObjectOutputsKey(Entry.PDGWorkItemIndex, Entry.PDGWorkResultObjectArrayIndex); - WorkResultObjectBakedOutput = InPDGBakedOutputs->Find(Key); - if (WorkResultObjectBakedOutput) - { - if (Entry.OutputIndex >= 0 && WorkResultObjectBakedOutput->BakedOutputs.IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = WorkResultObjectBakedOutput->BakedOutputs[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } - } - else if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs) - { - if (Entry.OutputIndex >= 0 && InNonPDGBakedOutputs->IsValidIndex(Entry.OutputIndex)) - { - BakedOutputObject = (*InNonPDGBakedOutputs)[Entry.OutputIndex].BakedOutputObjects.Find(Entry.OutputObjectIdentifier); - } - } if (BakedOutputObject) { - InPreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); - if (IsValid(InPreviousBlueprint)) + UBlueprint* const PreviousBlueprint = BakedOutputObject->GetBlueprintIfValid(); + if (IsValid(PreviousBlueprint)) { - if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(InPreviousBlueprint)) + if (PackageParams.MatchesPackagePathNameExcludingBakeCounter(PreviousBlueprint)) { - PackageParams.GetBakeCounterFromBakedAsset(InPreviousBlueprint, BakeCounter); + PackageParams.GetBakeCounterFromBakedAsset(PreviousBlueprint, BakeCounter); } } } UPackage* Package = PackageParams.CreatePackageForObject(BlueprintName, BakeCounter); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) { HOUDINI_LOG_WARNING(TEXT("Could not find or create a package for the blueprint of %s"), *(Actor->GetPathName())); continue; } + OutBakeStats.NotifyPackageCreated(1); + if (!Package->IsFullyLoaded()) Package->FullyLoad(); @@ -5174,6 +5436,7 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( } } } + // TODO: PENDINGKILL replacement ? else if (Asset && Asset->IsPendingKill()) { // Rename to pending kill so that we can use the desired name @@ -5183,6 +5446,7 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( Asset = nullptr; } + bool bCreatedNewBlueprint = false; if (!Asset) { UBlueprintFactory* Factory = NewObject(); @@ -5193,11 +5457,14 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( Asset = AssetToolsModule.Get().CreateAsset( BlueprintName, PackageParams.GetPackagePath(), UBlueprint::StaticClass(), Factory, FName("ContentBrowserNewAsset")); + + if (Asset) + bCreatedNewBlueprint = true; } UBlueprint* Blueprint = Cast(Asset); - if (!Blueprint || Blueprint->IsPendingKill()) + if (!IsValid(Blueprint)) { HOUDINI_LOG_WARNING( TEXT("Found an asset at %s/%s, but it was not a blueprint or was pending kill."), @@ -5206,6 +5473,15 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( continue; } + if (bCreatedNewBlueprint) + { + OutBakeStats.NotifyObjectsCreated(Blueprint->GetClass()->GetName(), 1); + } + else + { + OutBakeStats.NotifyObjectsUpdated(Blueprint->GetClass()->GetName(), 1); + } + // Close editors opened on existing asset if applicable if (Blueprint && bIsAssetEditorSubsystemValid && AssetEditorSubsystem->FindEditorForAsset(Blueprint, false) != nullptr) { @@ -5213,11 +5489,17 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( AssetsToReOpenEditors.Add(Blueprint); } - // Record the blueprint as the previous bake blueprint + // Record the blueprint as the previous bake blueprint and clear the info of the temp bake actor/component if (BakedOutputObject) + { BakedOutputObject->Blueprint = FSoftObjectPath(Blueprint).ToString(); + BakedOutputObject->Actor.Empty(); + // TODO: Set the baked component to the corresponding component in the blueprint? + BakedOutputObject->BakedComponent.Empty(); + } OutBlueprints.Add(Blueprint); + BakedActorMap[Actor] = Blueprint; // Clear old Blueprint Node tree { @@ -5230,17 +5512,26 @@ FHoudiniEngineBakeUtils::BakeBlueprintsFromBakedActors( FHoudiniEngineBakeUtils::CopyActorContentsToBlueprint(Actor, Blueprint); - UWorld* World = Actor->GetWorld(); - if (!World) - World = GWorld; - - World->EditorDestroyActor(Actor, true); - // Save the created BP package. Package->MarkPackageDirty(); OutPackagesToSave.Add(Package); } + // Destroy the actors that were baked + for (const auto& BakedActorEntry : BakedActorMap) + { + AActor* const Actor = BakedActorEntry.Key; + if (!IsValid(Actor)) + continue; + + UWorld* World = Actor->GetWorld(); + if (!World) + World = GWorld; + + if (World) + World->EditorDestroyActor(Actor, true); + } + // Re-open asset editors for updated blueprints that were open in editors if (bIsAssetEditorSubsystemValid && AssetsToReOpenEditors.Num() > 0) { @@ -5300,16 +5591,19 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints( if (bSuccess) { + AActor* OwnerActor = InPDGAssetLink->GetOwnerActor(); bSuccess = BakeBlueprintsFromBakedActors( BakedActors, bInRecenterBakedActors, bReplaceAssets, InPDGAssetLink->AssetName, + IsValid(OwnerActor) ? OwnerActor->GetName() : FString(), InPDGAssetLink->BakeFolder, nullptr, &InNode->GetBakedWorkResultObjectsOutputs(), OutBlueprints, - OutPackagesToSave); + OutPackagesToSave, + OutBakeStats); } return bSuccess; @@ -5322,7 +5616,7 @@ FHoudiniEngineBakeUtils::BakePDGTOPNodeBlueprints(UHoudiniPDGAssetLink* InPDGAss TArray PackagesToSave; FHoudiniEngineOutputStats BakeStats; - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; const bool bSuccess = BakePDGTOPNodeBlueprints( @@ -5370,7 +5664,7 @@ FHoudiniEngineBakeUtils::BakePDGTOPNetworkBlueprints( TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; if (!IsValid(InNetwork)) @@ -5396,7 +5690,7 @@ FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(UHoudiniPDGAssetLink* InPDGA TArray PackagesToSave; FHoudiniEngineOutputStats BakeStats; - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return false; const bool bIsAutoBake = false; @@ -5540,6 +5834,7 @@ FHoudiniEngineBakeUtils::FindDesiredBakeActorFromBakeActorName( // If we found an actor and it is pending kill, rename it and don't use it if (FoundActor) { + // TODO: PENDINGKILL replacement ? if (FoundActor->IsPendingKill()) { if (bRenamePendingKillActor) @@ -5989,9 +6284,10 @@ FHoudiniEngineBakeUtils::DestroyPreviousBakeOutput( UMaterialInterface* FHoudiniEngineBakeUtils::BakeSingleMaterialToPackage(UMaterialInterface* InOriginalMaterial, const FHoudiniPackageParams & InPackageParams, TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap) + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats) { - if (!InOriginalMaterial || InOriginalMaterial->IsPendingKill()) + if (!IsValid(InOriginalMaterial)) { return nullptr; } @@ -6006,9 +6302,10 @@ UMaterialInterface* FHoudiniEngineBakeUtils::BakeSingleMaterialToPackage(UMateri // Duplicate material resource. UMaterialInterface * DuplicatedMaterial = FHoudiniEngineBakeUtils::DuplicateMaterialAndCreatePackage( - InOriginalMaterial, nullptr, MaterialName, InPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap); + InOriginalMaterial, nullptr, MaterialName, InPackageParams, OutPackagesToSave, InOutAlreadyBakedMaterialsMap, + OutBakeStats); - if (!DuplicatedMaterial || DuplicatedMaterial->IsPendingKill()) + if (!IsValid(DuplicatedMaterial)) return nullptr; return DuplicatedMaterial; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h index 011b07368..84aa0e730 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineBakeUtils.h @@ -155,14 +155,17 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils static ALandscapeProxy* BakeHeightfield( ALandscapeProxy * InLandscapeProxy, const FHoudiniPackageParams &PackageParams, - const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType); + const EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType, + FHoudiniEngineOutputStats& OutBakeStats); static bool BakeCurve( USplineComponent* InSplineComponent, ULevel* InLevel, const FHoudiniPackageParams &PackageParams, + const FName& InActorName, AActor*& OutActor, USplineComponent*& OutSplineComponent, + FHoudiniEngineOutputStats& OutBakeStats, FName InOverrideFolderPath=NAME_None, AActor* InActor=nullptr); @@ -174,8 +177,10 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils FHoudiniAttributeResolver& InResolver, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); @@ -196,7 +201,8 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FHoudiniPackageParams & PackageParams, const TArray& InAllOutputs, const FDirectoryPath& InTempCookFolder, - TMap& InOutAlreadyBakedMaterialsMap); + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats); static bool BakeLandscape( const UHoudiniAssetComponent* HoudiniAssetComponent, @@ -205,8 +211,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils TArray& InBakedOutputs, bool bInReplaceActors, bool bInReplaceAssets, - FString BakePath, - FString HoudiniAssetName, + const FString& BakePath, FHoudiniEngineOutputStats& BakeStats); static bool BakeLandscapeObject( @@ -221,25 +226,26 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils FHoudiniEngineOutputStats& BakeStats); static bool BakeInstancerOutputToActors( - const UHoudiniAssetComponent* HoudiniAssetcomponent, + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, const FTransform& InTransform, - const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, TArray const* InInstancerComponentTypesToBake=nullptr, AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); + const FString& InFallbackWorldOutlinerFolder=TEXT("")); static bool BakeInstancerOutputToActors_ISMC( - const UHoudiniAssetComponent* HoudiniAssetcomponent, + const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, // const TArray& InAllBakedOutputs, @@ -251,11 +257,13 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); + const FString& InFallbackWorldOutlinerFolder=TEXT("")); static bool BakeInstancerOutputToActors_IAC( const UHoudiniAssetComponent* HoudiniAssetComponent, @@ -266,8 +274,10 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FDirectoryPath& InBakeFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, - TArray& OutPackagesToSave); + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); static bool BakeInstancerOutputToActors_MSIC( const UHoudiniAssetComponent* HoudiniAssetComponent, @@ -282,9 +292,11 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); @@ -300,9 +312,11 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); @@ -314,7 +328,8 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const TArray& InCurrentBakedActors, const FString& InTemporaryCookFolder, TArray & OutCreatedPackages, - TMap& InOutAlreadyBakedMaterialsMap); + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats); static UMaterialInterface * DuplicateMaterialAndCreatePackage( UMaterialInterface * Material, @@ -322,20 +337,23 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FString & SubMaterialName, const FHoudiniPackageParams& ObjectPackageParams, TArray & OutCreatedPackages, - TMap& InOutAlreadyBakedMaterialsMap); + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats); static void ReplaceDuplicatedMaterialTextureSample( UMaterialExpression * MaterialExpression, UMaterialExpression* PreviousBakeMaterialExpression, const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); + TArray & OutCreatedPackages, + FHoudiniEngineOutputStats& OutBakeStats); static UTexture2D * DuplicateTextureAndCreatePackage( UTexture2D * Texture, UTexture2D* PreviousBakeTexture, const FString & SubTextureName, const FHoudiniPackageParams& PackageParams, - TArray & OutCreatedPackages); + TArray & OutCreatedPackages, + FHoudiniEngineOutputStats& OutBakeStats); // Bake a Houdini asset component (InHACToBake) based on the bInReplace and BakeOption arguments. // Returns true if the underlying bake function (for example, BakeHoudiniActorToActors, returns true (or a valid UObject*)) @@ -347,7 +365,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInRecenterBakedActors); static bool BakeHoudiniActorToActors( - UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActors); + UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceActors, bool bInReplaceAssets, bool bInRecenterBakedActor); static bool BakeHoudiniActorToActors( UHoudiniAssetComponent* HoudiniAssetComponent, @@ -365,19 +383,19 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const UHoudiniAssetComponent* HoudiniAssetComponent, const TArray& InOutputs, TArray& InBakedOutputs, - const FString& InHoudiniAssetName, const FTransform& InParentTransform, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutNewActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats, TArray const* InOutputTypesToBake=nullptr, TArray const* InInstancerComponentTypesToBake=nullptr, AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); + const FString& InFallbackWorldOutlinerFolder=TEXT("")); static bool BakeInstancerOutputToFoliage( const UHoudiniAssetComponent* HoudiniAssetComponent, @@ -387,32 +405,34 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils const FHoudiniOutputObjectIdentifier& InOutputObjectIdentifier, const FHoudiniOutputObject& InOutputObject, FHoudiniBakedOutputObject& InBakedOutputObject, - const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, - TArray& OutActors, + const TArray& InBakedActors, + FHoudiniEngineBakedActor& OutBakedActorEntry, TArray& OutPackagesToSave, - TMap& InOutAlreadyBakedMaterialsMap); + TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats); static bool CanHoudiniAssetComponentBakeToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent); - static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, TMap& InOutAlreadyBakedMaterialsMap); + static bool BakeHoudiniActorToFoliage(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets); static bool BakeStaticMeshOutputToActors( const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, - const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, const FDirectoryPath& InTempCookFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, const FString& InFallbackWorldOutlinerFolder=""); @@ -421,36 +441,40 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils UHoudiniOutput* InOutput, const FHoudiniOutputObjectIdentifier& Identifier, const FHoudiniOutputObject& InOutputObject, - const FString& InHoudiniAssetName, const FString& DefaultObjectName, const FDirectoryPath& InBakeFolder, const bool bInReplaceAssets, FHoudiniPackageParams& OutPackageParams, - TArray& OutPackagesToSave); + TArray& OutPackagesToSave, + const FString& InHoudiniAssetName=TEXT(""), + const FString& InHoudiniAssetActorName=TEXT("")); static bool BakeHoudiniCurveOutputToActors( const UHoudiniAssetComponent* HoudiniAssetComponent, int32 InOutputIndex, const TArray& InAllOutputs, TArray& InBakedOutputs, - const FString& InHoudiniAssetName, const FDirectoryPath& InBakeFolder, bool bInReplaceActors, bool bInReplaceAssets, + const TArray& InBakedActors, TArray& OutActors, + FHoudiniEngineOutputStats& OutBakeStats, AActor* InFallbackActor=nullptr, - const FString& InFallbackWorldOutlinerFolder=""); + const FString& InFallbackWorldOutlinerFolder=TEXT("")); static bool BakeBlueprintsFromBakedActors( const TArray& InBakedActors, bool bInRecenterBakedActors, bool bInReplaceAssets, - const FString& InAssetName, + const FString& InHoudiniAssetName, + const FString& InHoudiniAssetActorName, const FDirectoryPath& InBakeFolder, TArray* const InNonPDGBakedOutputs, TMap* const InPDGBakedOutputs, TArray& OutBlueprints, - TArray& OutPackagesToSave); + TArray& OutPackagesToSave, + FHoudiniEngineOutputStats& OutBakeStats); static bool BakeBlueprints(UHoudiniAssetComponent* HoudiniAssetComponent, bool bInReplaceAssets, bool bInRecenterBakedActors); @@ -582,6 +606,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInReplaceAssets, bool bInBakeToWorkResultActor, bool bInIsAutoBake, + const TArray& InBakedActors, TArray& OutBakedActors, TArray& OutPackagesToSave, FHoudiniEngineOutputStats& OutBakeStats, @@ -731,5 +756,5 @@ struct HOUDINIENGINEEDITOR_API FHoudiniEngineBakeUtils bool bInDestroyBakedInstancedActors, bool bInDestroyBakedInstancedComponents); - static UMaterialInterface * BakeSingleMaterialToPackage(UMaterialInterface * InOriginalMaterial, const FHoudiniPackageParams & PackageParams, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap); + static UMaterialInterface * BakeSingleMaterialToPackage(UMaterialInterface * InOriginalMaterial, const FHoudiniPackageParams & PackageParams, TArray& OutPackagesToSave, TMap& InOutAlreadyBakedMaterialsMap, FHoudiniEngineOutputStats& OutBakeStats); }; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp index 2ffae90c1..7c0437da7 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.cpp @@ -317,7 +317,7 @@ FHoudiniEngineCommands::CleanUpTempFolder() for (FAssetData Data : AssetDataList) { UPackage* CurrentPackage = Data.GetPackage(); - if (!CurrentPackage || CurrentPackage->IsPendingKill()) + if (!IsValid(CurrentPackage)) continue; // Do not try to delete the package if it's referenced anywhere @@ -334,7 +334,7 @@ FHoudiniEngineCommands::CleanUpTempFolder() { // Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer) UObject* AssetInPackage = AssetInfo.GetAsset(); - if (!AssetInPackage || AssetInPackage->IsPendingKill()) + if (!IsValid(AssetInPackage)) continue; FReferencerInformationList ReferencesIncludingUndo; @@ -347,7 +347,7 @@ FHoudiniEngineCommands::CleanUpTempFolder() for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences) { UObject* Outer = ExtRef.Referencer->GetOuter(); - if (!Outer || Outer->IsPendingKill()) + if (!IsValid(Outer)) continue; bool bOuterFound = false; @@ -480,7 +480,7 @@ FHoudiniEngineCommands::BakeAllAssets() for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) { HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component")); continue; @@ -598,7 +598,7 @@ FHoudiniEngineCommands::PauseAssetCooking() for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel()) + if (!IsValid(HoudiniAssetComponent) || !HoudiniAssetComponent->IsValidLowLevel()) { HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!")); continue; @@ -636,11 +636,11 @@ FHoudiniEngineCommands::RecookSelection() for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; HoudiniAssetComponent->MarkAsNeedCook(); @@ -667,7 +667,7 @@ FHoudiniEngineCommands::RecookAllAssets() for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; HoudiniAssetComponent->MarkAsNeedCook(); @@ -694,7 +694,7 @@ FHoudiniEngineCommands::RebuildAllAssets() for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; HoudiniAssetComponent->MarkAsNeedRebuild(); @@ -730,11 +730,11 @@ FHoudiniEngineCommands::RebuildSelection() for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid()) + if (!IsValid(HoudiniAssetComponent))// || !HoudiniAssetComponent->IsComponentValid()) continue; HoudiniAssetComponent->MarkAsNeedRebuild(); @@ -770,11 +770,11 @@ FHoudiniEngineCommands::BakeSelection() for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) { HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!")); continue; @@ -859,7 +859,7 @@ void FHoudiniEngineCommands::RecentreSelection() for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); @@ -894,7 +894,7 @@ void FHoudiniEngineCommands::RecentreSelection() const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs; for (const UHoudiniInput* pInput : AssetInputs) { - if (!pInput || pInput->IsPendingKill()) + if (!IsValid(pInput)) continue; // to world space @@ -1210,7 +1210,7 @@ FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation() for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; HoudiniAssetComponent->MarkAsNeedInstantiation(); @@ -1276,11 +1276,11 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelecte for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Index]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and @@ -1293,7 +1293,7 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelecte for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HoudiniAssetComponent = *Itr; - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld()) @@ -1332,11 +1332,11 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TAr TArray SkippedComponents; for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine) { - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; // Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and @@ -1402,16 +1402,16 @@ FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled) void FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray &OutToRefine, TArray &OutToCook, TArray &OutSkipped) { - if (!InHAC || InHAC->IsPendingKill()) + if (!IsValid(InHAC)) return; // Make sure that the component's World and Owner are valid AActor *Owner = InHAC->GetOwner(); - if (!Owner || Owner->IsPendingKill()) + if (!IsValid(Owner)) return; UWorld *World = InHAC->GetWorld(); - if (!World || World->IsPendingKill()) + if (!IsValid(World)) return; if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World) @@ -1464,7 +1464,7 @@ FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoud for (uint32 Index = 0; Index < NumOutputs; ++Index) { UHoudiniOutput *Output = InHAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; TMap& OutputObjects = Output->GetOutputObjects(); @@ -1475,7 +1475,7 @@ FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoud { // The proxy is not current, delete it and its component USceneComponent* FoundProxyComponent = Cast(CurrentOutputObject.ProxyComponent); - if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill()) + if (IsValid(FoundProxyComponent)) { // Remove from the HoudiniAssetActor if (FoundProxyComponent->GetOwner()) @@ -1487,7 +1487,7 @@ FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoud } UObject* ProxyObject = CurrentOutputObject.ProxyObject; - if (!ProxyObject || ProxyObject->IsPendingKill()) + if (!IsValid(ProxyObject)) continue; ProxyObject->MarkPendingKill(); @@ -1506,7 +1506,7 @@ FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoud for (uint32 Index = 0; Index < NumOutputs; ++Index) { UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; TMap& OutputObjects = Output->GetOutputObjects(); @@ -1552,6 +1552,23 @@ FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( TArray SuccessfulComponents; TArray FailedComponents; TArray SkippedComponents(InSkippedComponents); + + auto AllowPlayInEditorRefinementFn = [&bInOnPrePIEBeginPlay, &InComponentsToCook, &InComponentsToRefine] (bool bEnabled, bool bRefinementDone){ + if (bInOnPrePIEBeginPlay) + { + // Flag the components that need cooking / refinement as cookable in PIE mode. No other cooking will be allowed. + // Once refinement is done, we'll unset these flags again. + SetAllowPlayInEditorRefinement(InComponentsToCook, true); + SetAllowPlayInEditorRefinement(InComponentsToRefine, true); + if (bRefinementDone) + { + // Don't tick during PIE. We'll resume ticking when PIE is stopped. + FHoudiniEngine::Get().StopTicking(); + } + } + }; + + AllowPlayInEditorRefinementFn(true, false); if (NumComponentsToProcess > 0) { @@ -1609,11 +1626,13 @@ FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes( NumComponentsToProcess, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents); // We didn't have to cook anything, so refinement is complete. + AllowPlayInEditorRefinementFn(false, true); return EHoudiniProxyRefineRequestResult::Refined; } } // Nothing to refine + AllowPlayInEditorRefinementFn(false, true); return EHoudiniProxyRefineRequestResult::None; } @@ -1645,7 +1664,7 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgrou TDoubleLinkedList::TDoubleLinkedListNode *Next = Node->GetNextNode(); UHoudiniAssetComponent* HAC = Node->GetValue(); - if (HAC && !HAC->IsPendingKill()) + if (IsValid(HAC)) { const EHoudiniAssetState State = HAC->GetAssetState(); const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult(); @@ -1764,6 +1783,10 @@ FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(const u }); } + SetAllowPlayInEditorRefinement(InSuccessfulComponents, false); + SetAllowPlayInEditorRefinement(InFailedComponents, false); + SetAllowPlayInEditorRefinement(InSkippedComponents, false); + // Broadcast refinement result per HAC for (UHoudiniAssetComponent* const HAC : InSuccessfulComponents) { @@ -1789,14 +1812,14 @@ FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArrayIsPendingKill()) + if (!IsValid(HAC)) continue; const int32 NumOutputs = HAC->GetNumOutputs(); for (int32 Index = 0; Index < NumOutputs; ++Index) { UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; if (Output->GetType() != EHoudiniOutputType::Mesh) @@ -1805,7 +1828,7 @@ FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArrayGetOutputObjects()) { UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) + if (!IsValid(Obj)) continue; UStaticMesh *SM = Cast(Obj); @@ -1813,7 +1836,7 @@ FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArrayGetOutermost(); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) continue; if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) @@ -1827,5 +1850,18 @@ FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray& InComponents, + bool bEnabled) +{ +#if WITH_EDITORONLY_DATA + for (UHoudiniAssetComponent* Component : InComponents) + { + Component->SetAllowPlayInEditorRefinement(false); + } +#endif +} + #undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h index 1945e6da4..079fe1698 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineCommands.h @@ -247,6 +247,8 @@ class FHoudiniEngineCommands : public TCommands // if it was called as a result of a PreSaveWorld. static void RefineProxyMeshesHandleOnPostSaveWorld(const TArray &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess); + static void SetAllowPlayInEditorRefinement(const TArray& InComponents, bool bEnabled); + // Helper function used to indicate to all HAC that they need to be instantiated in the new HE session // Needs to be call after starting/restarting/connecting/session syncing a HE session.. static void MarkAllHACsAsNeedInstantiation(); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp index be5d19638..33132da3d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineDetails.cpp @@ -114,7 +114,7 @@ FHoudiniEngineDetails::CreateWidget( UHoudiniAssetComponent* MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; // 0. Houdini Engine Icon @@ -147,7 +147,7 @@ FHoudiniEngineDetails::CreateHoudiniEngineIconWidget( UHoudiniAssetComponent* MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; // Skip drawing the icon if the icon image is not loaded correctly. @@ -193,14 +193,14 @@ FHoudiniEngineDetails::CreateGenerateWidgets( UHoudiniAssetComponent* MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; auto OnReBuildClickedLambda = [InHACs]() { for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->MarkAsNeedRebuild(); @@ -213,7 +213,7 @@ FHoudiniEngineDetails::CreateGenerateWidgets( { for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->MarkAsNeedCook(); @@ -226,7 +226,7 @@ FHoudiniEngineDetails::CreateGenerateWidgets( { for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; // Reset parameters to default values? @@ -246,7 +246,7 @@ FHoudiniEngineDetails::CreateGenerateWidgets( { for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; // Reset parameters to default values? @@ -266,7 +266,7 @@ FHoudiniEngineDetails::CreateGenerateWidgets( auto OnCookFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; FString NewPathStr = Val.ToString(); @@ -300,7 +300,7 @@ FHoudiniEngineDetails::CreateGenerateWidgets( for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; if (NextHAC->TemporaryCookFolder.Path.Equals(NewPathStr)) @@ -587,7 +587,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( return; UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(HoudiniEngineCategoryBuilder, MainHAC, HOUDINI_ENGINE_UI_SECTION_BAKE); @@ -599,7 +599,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( { for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; FHoudiniEngineBakeUtils::BakeHoudiniAssetComponent( @@ -615,14 +615,14 @@ FHoudiniEngineDetails::CreateBakeWidgets( auto OnBakeFolderTextCommittedLambda = [InHACs, MainHAC](const FText& Val, ETextCommit::Type TextCommitType) { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; FString NewPathStr = Val.ToString(); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; if (NextHAC->BakeFolder.Path.Equals(NewPathStr)) @@ -735,10 +735,10 @@ FHoudiniEngineDetails::CreateBakeWidgets( const EHoudiniEngineBakeOption NewOption = FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); - for (auto & NextHAC : InHACs) - { - if (!NextHAC || NextHAC->IsPendingKill()) - continue; + for (auto & NextHAC : InHACs) + { + if (!IsValid(NextHAC)) + continue; MainHAC->HoudiniEngineBakeOption = NewOption; } @@ -817,7 +817,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bRemoveOutputAfterBake = bNewState; @@ -852,7 +852,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bRecenterBakedActors = bNewState; @@ -870,7 +870,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( // managing the delegate in this way). for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; const bool bState = NextHAC->IsBakeAfterNextCookEnabled(); @@ -902,7 +902,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->SetBakeAfterNextCookEnabled(bNewState); @@ -939,7 +939,7 @@ FHoudiniEngineDetails::CreateBakeWidgets( for (auto & NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; MainHAC->bReplacePreviousBake = bNewState; @@ -1083,7 +1083,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( return; UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; // Header Row @@ -1094,7 +1094,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedParameterChangedLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bCookOnParameterChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1105,7 +1105,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bCookOnParameterChange = bChecked; @@ -1114,7 +1114,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedTransformChangeLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bCookOnTransformChange ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1125,7 +1125,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bCookOnTransformChange = bChecked; @@ -1136,7 +1136,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedAssetInputCookLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bCookOnAssetInputCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1147,7 +1147,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bCookOnAssetInputCook = bChecked; @@ -1156,7 +1156,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedPushTransformToHoudiniLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bUploadTransformsToHoudiniEngine ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1167,7 +1167,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bUploadTransformsToHoudiniEngine = bChecked; @@ -1178,7 +1178,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedDoNotGenerateOutputsLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bOutputless ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1189,7 +1189,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bOutputless = bChecked; @@ -1200,7 +1200,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedOutputTemplatedGeosLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bOutputTemplateGeos ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1211,7 +1211,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bOutputTemplateGeos = bChecked; @@ -1222,7 +1222,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( auto IsCheckedUseOutputNodesLambda = [MainHAC]() { - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return ECheckBoxState::Unchecked; return MainHAC->bUseOutputNodes ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1233,7 +1233,7 @@ FHoudiniEngineDetails::CreateAssetOptionsWidgets( bool bChecked = (NewState == ECheckBoxState::Checked); for (auto& NextHAC : InHACs) { - if (!NextHAC || NextHAC->IsPendingKill()) + if (!IsValid(NextHAC)) continue; NextHAC->bUseOutputNodes = bChecked; @@ -1520,7 +1520,7 @@ FHoudiniEngineDetails::CreateHelpAndDebugWidgets( return; UHoudiniAssetComponent * MainHAC = InHACs[0]; - if (!MainHAC || MainHAC->IsPendingKill()) + if (!IsValid(MainHAC)) return; // Header Row @@ -1841,7 +1841,7 @@ FHoudiniEngineDetails::ShowAssetHelp(UHoudiniAssetComponent * InHAC) void FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuilder& HoudiniEngineCategoryBuilder, UHoudiniAssetComponent * HoudiniAssetComponent, int32 MenuSection) { - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return; FOnClicked OnExpanderClick = FOnClicked::CreateLambda([HoudiniAssetComponent, MenuSection]() @@ -1933,7 +1933,7 @@ FHoudiniEngineDetails::AddHeaderRowForHoudiniAssetComponent(IDetailCategoryBuild void FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(IDetailCategoryBuilder& PDGCategoryBuilder, UHoudiniPDGAssetLink* InPDGAssetLink, int32 MenuSection) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; FOnClicked OnExpanderClick = FOnClicked::CreateLambda([InPDGAssetLink, MenuSection]() diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp index fdca2cb1a..a59bb452c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.cpp @@ -1079,13 +1079,13 @@ FHoudiniEngineEditor::GetLevelViewportContextMenuExtender(const TSharedRef(CurrentActor); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) continue; HoudiniAssetActors.Add(HoudiniAssetActor); UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) continue; HoudiniAssets.AddUnique(HoudiniAssetComponent->GetHoudiniAsset()); @@ -1308,7 +1308,7 @@ FHoudiniEngineEditor::RegisterEditorDelegates() } }); - PreBeginPIEEditorDelegateHandle = FEditorDelegates::PreBeginPIE.AddLambda([](const bool bIsSimulating) + PreBeginPIEEditorDelegateHandle = FEditorDelegates::PreBeginPIE.AddLambda([&](const bool bIsSimulating) { const bool bSelectedOnly = false; const bool bSilent = false; @@ -1316,6 +1316,24 @@ FHoudiniEngineEditor::RegisterEditorDelegates() const bool bOnPreSaveWorld = false; UWorld * const OnPreSaveWorld = nullptr; const bool bOnPreBeginPIE = true; + + // if the HoudiniEngine manager is currently ticking, we'll need to resume once PIE is done. + if (FHoudiniEngine::Get().IsTicking()) + { + // If the HoudiniEngine is currently ticking we'll need to restart it + + EndPIEEditorDelegateHandle = FEditorDelegates::EndPIE.AddLambda([&](const bool bEndPIEIsSimulating) + { + FHoudiniEngineCommands::ConnectSession(); + // if (FHoudiniEngine::Get().ConnectSession()) + // { + // // Only start ticking if we were able to reconnect the sesion. + // FHoudiniEngine::Get().StartTicking(); + // } + FEditorDelegates::EndPIE.Remove(EndPIEEditorDelegateHandle); + }); + } + FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bSelectedOnly, bSilent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE); }); @@ -1332,6 +1350,9 @@ FHoudiniEngineEditor::UnregisterEditorDelegates() if (PreBeginPIEEditorDelegateHandle.IsValid()) FEditorDelegates::PreBeginPIE.Remove(PreBeginPIEEditorDelegateHandle); + if (EndPIEEditorDelegateHandle.IsValid()) + FEditorDelegates::EndPIE.Remove(EndPIEEditorDelegateHandle); + if (OnDeleteActorsBegin.IsValid()) FEditorDelegates::OnDeleteActorsBegin.Remove(OnDeleteActorsBegin); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h index 451d781dd..5f69b2282 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditor.h @@ -333,6 +333,9 @@ class HOUDINIENGINEEDITOR_API FHoudiniEngineEditor : public IHoudiniEngineEditor // Delegate handle for the PreBeginPIE editor delegate FDelegateHandle PreBeginPIEEditorDelegateHandle; + // Delegate handle for the EndPIE editor delegate + FDelegateHandle EndPIEEditorDelegateHandle; + // Delegate handle for OnDeleteActorsBegin FDelegateHandle OnDeleteActorsBegin; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp index 0716324da..a7c38dd5c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.cpp @@ -31,6 +31,7 @@ #include "HoudiniEngineEditor.h" #include "HoudiniRuntimeSettings.h" #include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" #include "HoudiniGeoPartObject.h" #include "HoudiniAsset.h" #include "HoudiniOutput.h" @@ -61,7 +62,7 @@ FHoudiniEngineEditorUtils::GetContentBrowserSelection(TArray< UObject* >& Conten { // Get the current object UObject * Object = SelectedAssets[n].GetAsset(); - if (!Object || Object->IsPendingKill()) + if (!IsValid(Object)) continue; // Only static meshes are supported @@ -83,7 +84,7 @@ FHoudiniEngineEditorUtils::GetWorldSelection(TArray& WorldSelection, b if (GEditor) { USelection* SelectedActors = GEditor->GetSelectedActors(); - if (SelectedActors && !SelectedActors->IsPendingKill()) + if (IsValid(SelectedActors)) { for (FSelectionIterator It(*SelectedActors); It; ++It) { @@ -110,7 +111,7 @@ FHoudiniEngineEditorUtils::GetWorldSelection(TArray& WorldSelection, b for (int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx--) { AHoudiniAssetActor * HoudiniAssetActor = Cast(WorldSelection[Idx]); - if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill()) + if (!IsValid(HoudiniAssetActor)) WorldSelection.RemoveAt(Idx); } } @@ -522,7 +523,7 @@ FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) for (TObjectIterator Itr; Itr; ++Itr) { UHoudiniAssetComponent * HAC = *Itr; - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) continue; if (InSaveWorld && InSaveWorld != HAC->GetWorld()) @@ -532,7 +533,7 @@ FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) for (int32 Index = 0; Index < NumOutputs; ++Index) { UHoudiniOutput *Output = HAC->GetOutputAt(Index); - if (!Output || Output->IsPendingKill()) + if (!IsValid(Output)) continue; // TODO: Also save landscape layer info objects? @@ -542,7 +543,7 @@ FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) for (auto &OutputObjectPair : Output->GetOutputObjects()) { UObject *Obj = OutputObjectPair.Value.OutputObject; - if (!Obj || Obj->IsPendingKill()) + if (!IsValid(Obj)) continue; UStaticMesh *SM = Cast(Obj); @@ -550,7 +551,7 @@ FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) continue; UPackage *Package = SM->GetOutermost(); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) continue; if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) @@ -562,11 +563,11 @@ FHoudiniEngineEditorUtils::SaveAllHoudiniTemporaryCookData(UWorld *InSaveWorld) for (auto& MaterialAssignementPair : Output->GetAssignementMaterials()) { UMaterialInterface* MatInterface = MaterialAssignementPair.Value; - if (!MatInterface || MatInterface->IsPendingKill()) + if (!IsValid(MatInterface)) continue; UPackage *Package = MatInterface->GetOutermost(); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) continue; if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage()) @@ -597,6 +598,27 @@ FHoudiniEngineEditorUtils::ReselectSelectedActors() } } +void +FHoudiniEngineEditorUtils::ReselectActorIfSelected(AActor* const InActor) +{ + if (IsValid(InActor) && InActor->IsSelected()) + { + GEditor->SelectActor(InActor, false, false, false, false); + GEditor->SelectActor(InActor, true, true, true, true); + } +} + +void +FHoudiniEngineEditorUtils::ReselectComponentOwnerIfSelected(USceneComponent* const InComponent) +{ + if (!IsValid(InComponent)) + return; + AActor* const Actor = InComponent->GetOwner(); + if (!IsValid(Actor)) + return; + ReselectActorIfSelected(Actor); +} + FString FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding, const TCHAR PathSep) { diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h index e8059bb12..fbedb292f 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineEditorUtils.h @@ -80,6 +80,14 @@ struct FHoudiniEngineEditorUtils // Deselect and reselect all selected actors to get rid of component not showing bug after create. static void ReselectSelectedActors(); + // Deselect and reselect InActor, but only if it is currently selected. Related to ReselectSelectedActors and + // the component not showing bug after create. + static void ReselectActorIfSelected(AActor* const InActor); + + // Deselect and reselect the owning actor of InComopnent, but only if the actor is currently selected. Related to + // ReselectSelectedActors and the component not showing bug after create. + static void ReselectComponentOwnerIfSelected(USceneComponent* const InComponent); + // Gets the node name indent from the left with the specified number of spaces based on the path depth. static FString GetNodeNamePaddedByPathDepth(const FString& InNodeName, const FString& InNodePath, const uint8 Padding=4, const TCHAR PathSep='/'); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp index a8c45c221..06848f70c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniEngineStyle.cpp @@ -96,9 +96,15 @@ FHoudiniEngineStyle::Initialize() StyleSet->Set( "HoudiniEngine.HoudiniEngineLogo", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + StyleSet->Set( "ClassIcon.HoudiniAssetActor", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16)); + + StyleSet->Set( + "ClassThumbnail.HoudiniAssetActor", + new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_128.png"), Icon64x64)); + StyleSet->Set( "HoudiniEngine.HoudiniEngineLogo40", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40)); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp index 5541b05bf..67224f86d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniGeoFactory.cpp @@ -266,7 +266,7 @@ UHoudiniGeoFactory::Import(UClass* InClass, UPackage* InParent, const FString & TArray Results = BGEOImporter->GetOutputObjects(); for (UObject* Object : Results) { - if (!Object || Object->IsPendingKill()) + if (!IsValid(Object)) continue; Object->SetFlags(Flags); @@ -322,18 +322,18 @@ UHoudiniGeoFactory::Reimport(UObject * Obj) return EReimportResult::Failed; }; - if (!Obj || Obj->IsPendingKill()) + if (!IsValid(Obj)) return FailReimport(); UPackage* Package = Cast(Obj->GetOuter()); - if (!Package || Package->IsPendingKill()) + if (!IsValid(Package)) return FailReimport(); UAssetImportData* ImportData = nullptr; if (Obj->GetClass() == UStaticMesh::StaticClass()) { UStaticMesh* StaticMesh = Cast(Obj); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return FailReimport(); ImportData = StaticMesh->AssetImportData; @@ -342,13 +342,13 @@ UHoudiniGeoFactory::Reimport(UObject * Obj) else if(Obj->GetClass() == USkeletalMesh::StaticClass()) { USkeletalMesh* SkeletalMesh = Cast(Obj); - if (!SkeletalMesh || SkeletalMesh->IsPendingKill()) + if (!IsValid(SkeletalMesh)) return FailReimport(); ImportData = SkeletalMesh->AssetImportData; } */ - if (!ImportData || ImportData->IsPendingKill()) + if (!IsValid(ImportData)) return FailReimport(); if (ImportData->GetSourceFileCount() <= 0) diff --git a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp index 613c1f094..621a3932d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniHandleDetails.cpp @@ -51,7 +51,7 @@ FHoudiniHandleDetails::CreateWidget(IDetailCategoryBuilder & HouHandleCategory, UHoudiniHandleComponent* MainHandle = InHandles[0]; - if (!MainHandle || MainHandle->IsPendingKill()) + if (!IsValid(MainHandle)) return; diff --git a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp index c0f7ab6c4..a21e8e9eb 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniInputDetails.cpp @@ -106,7 +106,7 @@ FHoudiniInputDetails::CreateWidget( return; UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Get thumbnail pool for this builder. @@ -210,7 +210,7 @@ void FHoudiniInputDetails::CreateNameWidget( UHoudiniInput* InInput, FDetailWidgetRow & Row, bool bLabel, int32 InInputCount) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return; FString InputLabelStr = InInput->GetLabel(); @@ -272,7 +272,7 @@ FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuild return; UHoudiniInput * MainInput = InInputsToUpdate[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; UHoudiniAssetBlueprintComponent* HAB = MainInput->GetTypedOuter(); @@ -287,7 +287,7 @@ FHoudiniInputDetails::AddInputTypeComboBox(IDetailCategoryBuilder& CategoryBuild for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetInputType() == NewInputType) @@ -429,7 +429,7 @@ FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > V return; UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Lambda returning a CheckState from the input's current KeepWorldTransform state @@ -441,7 +441,7 @@ FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > V // Lambda for changing KeepWorldTransform state auto CheckStateChangedKeepWorldTransform = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -456,7 +456,7 @@ FHoudiniInputDetails::AddKeepWorldTransformCheckBox(TSharedRef< SVerticalBox > V for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetKeepWorldTransform() == bNewState) @@ -510,13 +510,13 @@ FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > Vert UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Lambda returning a CheckState from the input's current PackBeforeMerge state auto IsCheckedPackBeforeMerge = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetPackBeforeMerge() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -525,7 +525,7 @@ FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > Vert // Lambda for changing PackBeforeMerge state auto CheckStateChangedPackBeforeMerge = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -541,7 +541,7 @@ FHoudiniInputDetails::AddPackBeforeMergeCheckbox(TSharedRef< SVerticalBox > Vert for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetPackBeforeMerge() == bNewState) @@ -584,13 +584,13 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Lambda returning a CheckState from the input's current PackBeforeMerge state auto IsCheckedImportAsReference= [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetImportAsReference() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -599,7 +599,7 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve // Lambda for changing PackBeforeMerge state auto CheckStateChangedImportAsReference= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -615,7 +615,7 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetImportAsReference() == bNewState) @@ -627,7 +627,7 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve // Mark all its input objects as changed to trigger recook. for (auto CurInputObj : *InputObjs) { - if (!CurInputObj || CurInputObj->IsPendingKill()) + if (!IsValid(CurInputObj)) continue; if (CurInputObj->GetImportAsReference() != bNewState) @@ -663,6 +663,85 @@ FHoudiniInputDetails::AddImportAsReferenceCheckbox(TSharedRef< SVerticalBox > Ve return CheckStateChangedImportAsReference(InInputs, NewState); }) ]; + + // Add Rot/Scale to input as reference + auto IsCheckedImportAsReferenceRotScale= [](UHoudiniInput* InInput) + { + if (!IsValid(InInput)) + return ECheckBoxState::Unchecked; + + return InInput->GetImportAsReferenceRotScaleEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + }; + + // Lambda for changing PackBeforeMerge state + auto CheckStateChangedImportAsReferenceRotScale= [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) + { + if (!IsValid(MainInput)) + return; + + bool bNewState = (NewState == ECheckBoxState::Checked); + + if (MainInput->GetImportAsReferenceRotScaleEnabled() == bNewState) + return; + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniInputAsReferenceRotScale", "Houdini Input: Changing InputAsReference Rot/Scale"), + MainInput->GetOuter()); + + for (auto CurInput : InInputsToUpdate) + { + if (!IsValid(CurInput)) + continue; + + if (CurInput->GetImportAsReferenceRotScaleEnabled() == bNewState) + continue; + + TArray * InputObjs = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); + if (InputObjs) + { + // Mark all its input objects as changed to trigger recook. + for (auto CurInputObj : *InputObjs) + { + if (!IsValid(CurInputObj)) + continue; + + if (CurInputObj->GetImportAsReferenceRotScaleEnabled() != bNewState) + { + CurInputObj->SetImportAsReferenceRotScaleEnabled(bNewState); + CurInputObj->MarkChanged(true); + } + } + } + + CurInput->Modify(); + CurInput->SetImportAsReferenceRotScaleEnabled(bNewState); + } + }; + + TSharedPtr< SCheckBox > CheckBoxImportAsReferenceRotScale; + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SAssignNew(CheckBoxImportAsReferenceRotScale, SCheckBox) + .Content() + [ + SNew(STextBlock) + .Text(LOCTEXT("ImportInputAsRefRotScaleCheckbox", "Add rot/scale to input references")) + .ToolTipText(LOCTEXT("ImportInputAsRefRotScaleCheckboxTip", "Add rot/scale attributes to the input references when Import input as references is enabled")) + .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) + ] + .IsChecked_Lambda([=]() + { + return IsCheckedImportAsReferenceRotScale(MainInput); + }) + .OnCheckStateChanged_Lambda([=](ECheckBoxState NewState) + { + return CheckStateChangedImportAsReferenceRotScale(InInputs, NewState); + }) + .IsEnabled(IsCheckedImportAsReference(MainInput)) + + ]; } void FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox, TArray& InInputs) @@ -671,13 +750,13 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox return; UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Lambda returning a CheckState from the input's current ExportLODs state auto IsCheckedExportLODs = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetExportLODs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -686,7 +765,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox // Lambda returning a CheckState from the input's current ExportSockets state auto IsCheckedExportSockets = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetExportSockets() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -695,7 +774,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox // Lambda returning a CheckState from the input's current ExportColliders state auto IsCheckedExportColliders = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetExportColliders() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -704,7 +783,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox // Lambda for changing ExportLODs state auto CheckStateChangedExportLODs = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -720,7 +799,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetExportLODs() == bNewState) @@ -737,7 +816,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox // Lambda for changing ExportSockets state auto CheckStateChangedExportSockets = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -753,7 +832,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetExportSockets() == bNewState) @@ -770,7 +849,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox // Lambda for changing ExportColliders state auto CheckStateChangedExportColliders = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -786,7 +865,7 @@ FHoudiniInputDetails::AddExportCheckboxes(TSharedRef< SVerticalBox > VerticalBox for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetExportColliders() == bNewState) @@ -895,7 +974,7 @@ FHoudiniInputDetails::AddGeometryInputUI( // Lambda for changing ExportColliders state auto SetGeometryInputObjectsCount = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& NewInputCount) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -906,7 +985,7 @@ FHoudiniInputDetails::AddGeometryInputUI( for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetNumberOfInputObjects(EHoudiniInputType::Geometry) == NewInputCount) @@ -993,10 +1072,10 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( // Lambda for adding new geometry input objects auto UpdateGeometryObjectAt = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, const int32& AtIndex, UObject* InObject) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return; // Record a transaction for undo/redo @@ -1007,7 +1086,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; UObject* InputObject = CurInput->GetInputObjectAt(EHoudiniInputType::Geometry, AtIndex); @@ -1194,7 +1273,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( break; } - if (Object && !Object->IsPendingKill()) + if (IsValid(Object)) { UpdateGeometryObjectAt(InInputs, InGeometryObjectIdx, Object); } @@ -1254,7 +1333,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( PropertyCustomizationHelpers::MakeInsertDeleteDuplicateButton( FExecuteAction::CreateLambda( [ InInputs, InGeometryObjectIdx, MainInput ]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; FScopedTransaction Transaction( @@ -1264,7 +1343,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( // Insert for (auto CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; CurInput->Modify(); @@ -1273,7 +1352,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( } ), FExecuteAction::CreateLambda([MainInput, InInputs, InGeometryObjectIdx]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; FScopedTransaction Transaction( @@ -1284,7 +1363,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( // Delete for (auto CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; CurInput->Modify(); @@ -1296,7 +1375,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( } ), FExecuteAction::CreateLambda([InInputs, InGeometryObjectIdx, MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; FScopedTransaction Transaction( @@ -1307,7 +1386,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( // Duplicate for (auto CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; CurInput->Modify(); @@ -1334,7 +1413,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( .Visibility( EVisibility::Visible ) .OnClicked(FOnClicked::CreateLambda([InInputs, InGeometryObjectIdx, MainInput, &CategoryBuilder]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled();; FScopedTransaction Transaction( @@ -1345,7 +1424,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( // Expand transform for (auto CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; CurInput->Modify(); @@ -1408,7 +1487,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( bool bChanged = true; for (int Idx = 0; Idx < InInputs.Num(); Idx++) { - if (!InInputs[Idx] || InInputs[Idx]->IsPendingKill()) + if (!IsValid(InInputs[Idx])) continue; UHoudiniInputObject* InputObject = InInputs[Idx]->GetHoudiniInputObjectAt(AtIndex); @@ -1439,7 +1518,7 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( bool bResetButtonVisibleScale = false; for (auto & CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; FTransform* CurTransform = CurInput->GetTransformOffset(InGeometryObjectIdx); @@ -1701,12 +1780,12 @@ FHoudiniInputDetails::Helper_CreateGeometryWidget( { for (auto & CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; UHoudiniInputObject*CurInputObject = CurInput->GetHoudiniInputObjectAt(EHoudiniInputType::Geometry, InGeometryObjectIdx); - if (!CurInputObject || CurInputObject->IsPendingKill()) + if (!IsValid(CurInputObject)) continue; CurInputObject->SwitchUniformScaleLock(); @@ -1755,7 +1834,7 @@ FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TA UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Houdini Asset Picker Widget @@ -1775,7 +1854,7 @@ FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TA TSharedPtr< SHorizontalBox > HorizontalBox = NULL; auto IsClearButtonEnabled = [MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return false; TArray* AssetInputObjectsArray = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); @@ -1791,7 +1870,7 @@ FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TA FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); FScopedTransaction Transaction( @@ -1801,7 +1880,7 @@ FHoudiniInputDetails::AddAssetInputUI(TSharedRef< SVerticalBox > VerticalBox, TA for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; TArray* AssetInputObjectsArray = CurrentInput->GetHoudiniInputObjectArray(EHoudiniInputType::Asset); @@ -1857,11 +1936,11 @@ FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, T // lambda for inserting an input Houdini curve. auto InsertAnInputCurve = [MainInput, &CategoryBuilder](const int32& NewInputCount) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; UHoudiniAssetComponent* OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; // Do not insert input object when the HAC does not finish cooking @@ -1973,11 +2052,11 @@ FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, T { UHoudiniInputHoudiniSplineComponent* HoudiniInput = Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) + if (!IsValid(HoudiniInput)) continue; UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); @@ -1998,11 +2077,11 @@ FHoudiniInputDetails::AddCurveInputUI(IDetailCategoryBuilder& CategoryBuilder, T { UHoudiniInputHoudiniSplineComponent* HoudiniInput = Cast ((*CurveInputComponentArray)[n]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) + if (!IsValid(HoudiniInput)) continue; UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; MainInput->RemoveSplineFromInputObject(HoudiniInput, bBlueprintStructureModified); @@ -2064,17 +2143,17 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( { UHoudiniInput* MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; UHoudiniAssetComponent * OuterHAC = Cast(MainInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; auto GetHoudiniSplineComponentAtIndex = [](UHoudiniInput * Input, int32 Index) { UHoudiniSplineComponent* FoundHoudiniSplineComponent = nullptr; - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) return FoundHoudiniSplineComponent; // Get the TArray ptr to the curve objects in this input @@ -2167,7 +2246,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda for deleting the current curve input auto DeleteHoudiniCurveAtIndex = [InInputs, InCurveObjectIdx, OuterHAC, CurveInputComponentArray, &CategoryBuilder]() { - if (!OuterHAC|| OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; // Record a transaction for undo/redo. @@ -2179,7 +2258,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( int MainInputCurveArraySize = CurveInputComponentArray->Num(); for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; Input->Modify(); @@ -2196,7 +2275,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( UHoudiniInputHoudiniSplineComponent* HoudiniInput = Cast((*InputObjectArr)[InCurveObjectIdx]); - if (!HoudiniInput || HoudiniInput->IsPendingKill()) + if (!IsValid(HoudiniInput)) return; UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniInput->GetCurveComponent(); @@ -2232,7 +2311,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda returning a closed state auto IsCheckedClosedCurve = [HoudiniSplineComponent]() { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return ECheckBoxState::Unchecked; return HoudiniSplineComponent->IsClosedCurve() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -2241,7 +2320,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda for changing Closed state auto CheckStateChangedClosedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) { - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -2254,12 +2333,12 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; if (HoudiniSplineComponent->IsClosedCurve() == bNewState) @@ -2296,7 +2375,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda returning a reversed state auto IsCheckedReversedCurve = [HoudiniSplineComponent]() { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return ECheckBoxState::Unchecked; return HoudiniSplineComponent->IsReversed() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -2305,7 +2384,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda for changing reversed state auto CheckStateChangedReversedCurve = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](ECheckBoxState NewState) { - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -2318,11 +2397,11 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; if (HoudiniSplineComponent->IsReversed() == bNewState) @@ -2360,7 +2439,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda returning a visible state auto IsCheckedVisibleCurve = [HoudiniSplineComponent]() { - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return ECheckBoxState::Unchecked; return HoudiniSplineComponent->IsHoudiniSplineVisible() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -2373,7 +2452,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); @@ -2415,7 +2494,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda for changing Houdini curve type auto OnCurveTypeChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) { - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; if (!InNewChoice.IsValid()) @@ -2431,11 +2510,11 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; if (HoudiniSplineComponent->GetCurveType() == NewInputType) @@ -2504,7 +2583,7 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( // Lambda for changing Houdini curve method auto OnCurveMethodChanged = [GetHoudiniSplineComponentAtIndex, InInputs, InCurveObjectIdx, OuterHAC](TSharedPtr InNewChoice) { - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) return; if (!InNewChoice.IsValid()) @@ -2520,11 +2599,11 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( for (auto & Input : InInputs) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = GetHoudiniSplineComponentAtIndex(Input, InCurveObjectIdx); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; if (HoudiniSplineComponent->GetCurveMethod() == NewInputMethod) @@ -2585,15 +2664,15 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( { for (auto & NextInput : Inputs) { - if (!NextInput || NextInput->IsPendingKill()) + if (!IsValid(NextInput)) continue; UHoudiniAssetComponent * OuterHAC = Cast(NextInput->GetOuter()); - if (!OuterHAC || OuterHAC->IsPendingKill()) + if (!IsValid(OuterHAC)) continue; AActor * OwnerActor = OuterHAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) continue; TArray * CurveInputComponentArray = NextInput->GetHoudiniInputObjectArray(EHoudiniInputType::Curve); @@ -2607,11 +2686,11 @@ FHoudiniInputDetails::Helper_CreateCurveWidget( UHoudiniInputHoudiniSplineComponent* HoudiniSplineInputObject = Cast(HoudiniInputObject); - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + if (!IsValid(HoudiniSplineInputObject)) continue; UHoudiniSplineComponent* HoudiniSplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; FHoudiniPackageParams PackageParams; @@ -2710,7 +2789,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, // Lambda returning a CheckState from the input's current KeepWorldTransform state auto IsCheckedUpdateInputLandscape = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetUpdateInputLandscape() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -2719,7 +2798,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, // Lambda for changing KeepWorldTransform state auto CheckStateChangedUpdateInputLandscape = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -2731,7 +2810,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (bNewState == CurInput->GetUpdateInputLandscape()) @@ -2804,7 +2883,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, [ SNew(STextBlock) .Text(LOCTEXT("LandscapeUpdateInputCheckbox", "Update Input Landscape Data")) - .ToolTipText(LOCTEXT("LandscapeSelectedTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) + .ToolTipText(LOCTEXT("LandscapeUpdateInputTooltip", "If enabled, the input landscape's data will be updated instead of creating a new landscape Actor")) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] .IsChecked_Lambda([IsCheckedUpdateInputLandscape, MainInput]() @@ -2842,7 +2921,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, auto IsCheckedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) return ECheckBoxState::Unchecked; if (Input->GetLandscapeExportType() == LandscapeExportType) @@ -2853,7 +2932,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, auto CheckStateChangedExportAs = [](UHoudiniInput* Input, const EHoudiniLandscapeExportType& LandscapeExportType) { - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) return false; if (Input->GetLandscapeExportType() == LandscapeExportType) @@ -3022,14 +3101,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeExportSelectionOnly ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3040,7 +3119,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3048,9 +3127,11 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, continue; CurrentInput->Modify(); - CurrentInput->bLandscapeExportSelectionOnly = bNewState; + CurrentInput->UpdateLandscapeInputSelection(); + CurrentInput->MarkChanged(true); + } }) ]; @@ -3071,14 +3152,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeAutoSelectComponent ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3089,7 +3170,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3099,6 +3180,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, CurrentInput->Modify(); CurrentInput->bLandscapeAutoSelectComponent = bNewState; + CurrentInput->UpdateLandscapeInputSelection(); CurrentInput->MarkChanged(true); } }) @@ -3117,7 +3199,66 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, CheckBoxAutoSelectComponents->SetEnabled(bEnable); } + // Button : Update landscape component selection + { + auto OnButtonUpdateComponentSelection = [InInputs, MainInput]() + { + if (!IsValid(MainInput)) + return FReply::Handled(); + + // Record a transaction for undo/redo + FScopedTransaction Transaction( + TEXT(HOUDINI_MODULE_EDITOR), + LOCTEXT("HoudiniRecommitSelectedComponents", "Houdini Input: Recommit selected landscapes."), + MainInput->GetOuter()); + + for (auto CurrentInput : InInputs) + { + if (!IsValid(CurrentInput)) + continue; + + CurrentInput->UpdateLandscapeInputSelection(); + + CurrentInput->Modify(); + CurrentInput->MarkChanged(true); + } + + return FReply::Handled(); + }; + + auto IsLandscapeExportEnabled = [MainInput, InInputs]() + { + bool bEnable = false; + for (auto CurrentInput : InInputs) + { + if (!MainInput->bLandscapeExportSelectionOnly) + continue; + + bEnable = true; + break; + } + + return bEnable; + }; + + VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1, 2, 4, 2) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .HAlign(HAlign_Center) + .Text(LOCTEXT("LandscapeInputUpdateSelectedComponents", "Update Selected Components")) + .ToolTipText(LOCTEXT("LandscapeInputUpdateSelectedComponentsTooltip", "Updates the selected components. Only valid if export selected components is true.")) + .OnClicked_Lambda(OnButtonUpdateComponentSelection) + .IsEnabled_Lambda(IsLandscapeExportEnabled) + ] + ]; + } + // The following checkbox are only added when not in heightfield mode if (MainInput->LandscapeExportType != EHoudiniLandscapeExportType::Heightfield) { @@ -3136,14 +3277,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeExportMaterials ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3154,7 +3295,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3191,14 +3332,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeExportTileUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3209,7 +3350,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3246,14 +3387,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeExportNormalizedUVs ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3264,7 +3405,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3301,14 +3442,14 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, ] .IsChecked_Lambda([MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return ECheckBoxState::Unchecked; return MainInput->bLandscapeExportLighting ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InInputs, MainInput](ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3319,7 +3460,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; bool bNewState = (NewState == ECheckBoxState::Checked); @@ -3388,7 +3529,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, { auto IsClearButtonEnabled = [MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return false; if (MainInput->GetInputType() != EHoudiniInputType::Landscape) @@ -3410,7 +3551,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, return FReply::Handled(); UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); if (MainInput->GetInputType() != EHoudiniInputType::Landscape) @@ -3431,7 +3572,7 @@ FHoudiniInputDetails::AddLandscapeInputUI(TSharedRef VerticalBox, for (auto & CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; TArray* LandscapeInputObjectsArray = CurInput->GetHoudiniInputObjectArray(CurInput->GetInputType()); @@ -3476,7 +3617,7 @@ FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(Actor)) return false; if (!Actor->IsA()) @@ -3503,7 +3644,7 @@ FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(Actor)) return false; const TArray* InputObjects = InInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); @@ -3513,11 +3654,11 @@ FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(CurInputObject)) continue; AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) continue; if (CurActor == Actor) @@ -3548,7 +3689,7 @@ FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(MainInput)) return true; switch (MainInput->GetInputType()) @@ -3632,7 +3773,7 @@ FHoudiniInputDetails::Helper_CreateCustomActorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(CurInput)) return; switch (CurInput->GetInputType()) @@ -3743,7 +3884,7 @@ FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArrayIsPendingKill()) + if (!IsValid(MainInput)) return true; return OnShouldFilterHoudiniAsset(Actor); @@ -3751,7 +3892,7 @@ FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArrayIsPendingKill() || !Input || Input->IsPendingKill()) + if (!IsValid(Actor) || !IsValid(Input)) return; AHoudiniAssetActor* HoudiniAssetActor = Cast(Actor); @@ -3790,7 +3931,7 @@ FHoudiniInputDetails::Helper_CreateHoudiniAssetPickerWidget(TArrayIsPendingKill()) + if (!IsValid(CurInput)) return; OnHoudiniAssetActorSelected(Actor, CurInput); @@ -3859,7 +4000,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; auto OnShouldFilterLandscape = [](const AActor* const Actor, UHoudiniInput* InInput) { - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) return false; if (!Actor->IsA()) @@ -3873,7 +4014,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& // We need to get the AttachParent on the root componet, GteOwner will not return the parent actor! AActor* OwnerActor = nullptr; USceneComponent* RootComponent = LandscapeProxy->GetRootComponent(); - if (RootComponent && !RootComponent->IsPendingKill()) + if (IsValid(RootComponent)) OwnerActor = RootComponent->GetAttachParent() ? RootComponent->GetAttachParent()->GetOwner() : LandscapeProxy->GetOwner(); // Get our Actor @@ -3888,7 +4029,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& for (int32 Idx = 0; Idx < MyHAC->GetNumInputs(); Idx++) { UHoudiniInput* CurrentInput = MyHAC->GetInputAt(Idx); - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->GetInputType() != EHoudiniInputType::Landscape) @@ -3912,7 +4053,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& // Filters are only based on the MainInput auto OnShouldFilterActor = [MainInput, OnShouldFilterLandscape](const AActor* const Actor) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return true; return OnShouldFilterLandscape(Actor, MainInput); @@ -3957,7 +4098,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& return; UHoudiniInput * MainInput = InInputs[0]; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -3968,7 +4109,7 @@ FHoudiniInputDetails::Helper_CreateLandscapePickerWidget(TArray& for (auto CurInput : InInputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; CurInput->Modify(); @@ -4038,7 +4179,7 @@ FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; auto OnShouldFilterWorld = [MainInput](const AActor* const Actor) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return true; const TArray* InputObjects = MainInput->GetHoudiniInputObjectArray(EHoudiniInputType::World); @@ -4048,19 +4189,19 @@ FHoudiniInputDetails::Helper_CreateWorldActorPickerWidget(TArray // Only return actors that are currently selected by our input for (const auto& CurInputObject : *InputObjects) { - if (!CurInputObject || CurInputObject->IsPendingKill()) + if (!IsValid(CurInputObject)) continue; AActor* CurActor = Cast(CurInputObject->GetObject()); - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) { // See if the input object is a HAC, if it is, get its parent actor UHoudiniAssetComponent* CurHAC = Cast(CurInputObject->GetObject()); - if (CurHAC && !CurHAC->IsPendingKill()) + if (IsValid(CurHAC)) CurActor = CurHAC->GetOwner(); } - if (!CurActor || CurActor->IsPendingKill()) + if (!IsValid(CurActor)) continue; if (CurActor == Actor) @@ -4123,7 +4264,7 @@ FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArray 0 ? InInputs[0] : nullptr; auto OnShouldFilter = [MainInput](const AActor* const Actor) { - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) return false; const TArray* BoundObjects = MainInput->GetBoundSelectorObjectArray(); @@ -4133,7 +4274,7 @@ FHoudiniInputDetails::Helper_CreateBoundSelectorPickerWidget(TArrayIsPendingKill()) + if (!IsValid(CurActor)) continue; if (CurActor == Actor) @@ -4308,7 +4449,7 @@ FHoudiniInputDetails::AddWorldInputUI( FOnClicked OnSelectAll = FOnClicked::CreateLambda([InInputs, MainInput]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); // Record a transaction for undo/redo @@ -4319,7 +4460,7 @@ FHoudiniInputDetails::AddWorldInputUI( for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; // Get the parent component/actor/world of the current input @@ -4331,7 +4472,7 @@ FHoudiniInputDetails::AddWorldInputUI( for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) { AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) + if (!IsValid(CurrentActor)) continue; // Ignore the SkySpheres? @@ -4356,7 +4497,7 @@ FHoudiniInputDetails::AddWorldInputUI( FOnClicked OnClearSelect = FOnClicked::CreateLambda([InInputs, MainInput, &CategoryBuilder]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); const bool bMainInputBoundSelection = MainInput->IsWorldInputBoundSelector(); @@ -4439,7 +4580,7 @@ FHoudiniInputDetails::AddWorldInputUI( // Lambda returning a CheckState from the input's current bound selector state auto IsCheckedBoundSelector = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->IsWorldInputBoundSelector() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -4448,7 +4589,7 @@ FHoudiniInputDetails::AddWorldInputUI( // Lambda for changing bound selector state auto CheckStateChangedIsBoundSelector = [MainInput, &CategoryBuilder](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -4460,7 +4601,7 @@ FHoudiniInputDetails::AddWorldInputUI( bool bNewState = (NewState == ECheckBoxState::Checked); for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->IsWorldInputBoundSelector() == bNewState) @@ -4503,7 +4644,7 @@ FHoudiniInputDetails::AddWorldInputUI( // Lambda returning a CheckState from the input's current auto update state auto IsCheckedAutoUpdate = [](UHoudiniInput* InInput) { - if (!InInput || InInput->IsPendingKill()) + if (!IsValid(InInput)) return ECheckBoxState::Unchecked; return InInput->GetWorldInputBoundSelectorAutoUpdates() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -4512,7 +4653,7 @@ FHoudiniInputDetails::AddWorldInputUI( // Lambda for changing the auto update state auto CheckStateChangedBoundAutoUpdates = [MainInput](TArray InInputsToUpdate, ECheckBoxState NewState) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -4524,7 +4665,7 @@ FHoudiniInputDetails::AddWorldInputUI( bool bNewState = (NewState == ECheckBoxState::Checked); for (auto CurInput : InInputsToUpdate) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; if (CurInput->GetWorldInputBoundSelectorAutoUpdates() == bNewState) @@ -4609,7 +4750,7 @@ FHoudiniInputDetails::AddWorldInputUI( .Value(MainInput->GetUnrealSplineResolution()) .OnValueChanged_Lambda([MainInput, InInputs](float Val) { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return; // Record a transaction for undo/redo @@ -4620,7 +4761,7 @@ FHoudiniInputDetails::AddWorldInputUI( for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->GetUnrealSplineResolution() == Val) @@ -4656,7 +4797,7 @@ FHoudiniInputDetails::AddWorldInputUI( //.OnClicked(FOnClicked::CreateUObject(&InParam, &UHoudiniAssetInput::OnResetSplineResolutionClicked)) .OnClicked_Lambda([MainInput, InInputs]() { - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); // Record a transaction for undo/redo @@ -4670,7 +4811,7 @@ FHoudiniInputDetails::AddWorldInputUI( for (auto CurrentInput : InInputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->GetUnrealSplineResolution() == DefaultSplineResolution) @@ -4717,7 +4858,7 @@ FReply FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& CategoryBuilder, TArray InInputs, const FName& DetailsPanelName, const bool& bUseWorldInAsWorldSelector) { UHoudiniInput* MainInput = InInputs.Num() > 0 ? InInputs[0] : nullptr; - if (!MainInput || MainInput->IsPendingKill()) + if (!IsValid(MainInput)) return FReply::Handled(); // There's no undo operation for button. @@ -4763,7 +4904,7 @@ FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& C for (int32 Idx = 0; Idx < NumBoundSelectors; Idx++) { AActor* Actor = MainInput->GetBoundSelectorObjectAt(Idx); - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) continue; GEditor->SelectActor(Actor, true, true); @@ -4783,7 +4924,7 @@ FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& C AActor* Actor = nullptr; UHoudiniInputActor* InputActor = Cast(CurInputObject); - if (InputActor && !InputActor->IsPendingKill()) + if (IsValid(InputActor)) { // Get the input actor Actor = InputActor->GetActor(); @@ -4792,13 +4933,13 @@ FHoudiniInputDetails::Helper_OnButtonClickSelectActors(IDetailCategoryBuilder& C { // See if the input object is a HAC UHoudiniInputHoudiniAsset* InputHAC = Cast(CurInputObject); - if (InputHAC && !InputHAC->IsPendingKill()) + if (IsValid(InputHAC)) { Actor = InputHAC->GetHoudiniAssetComponent() ? InputHAC->GetHoudiniAssetComponent()->GetOwner() : nullptr; } } - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) continue; GEditor->SelectActor(Actor, true, true); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp index 5fb7ee3da..bca0fc17d 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.cpp @@ -68,6 +68,7 @@ #include "Engine/SkeletalMesh.h" #include "Particles/ParticleSystem.h" //#include "Landscape.h" +#include "HoudiniEngineOutputStats.h" #include "LandscapeProxy.h" #include "ScopedTransaction.h" #include "PhysicsEngine/BodySetup.h" @@ -136,7 +137,7 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget( IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; // Go through this output's objects @@ -181,29 +182,67 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( UHoudiniLandscapePtr* LandscapePointer, const FHoudiniOutputObjectIdentifier & OutputIdentifier) { - if (!LandscapePointer || LandscapePointer->IsPendingKill() || !LandscapePointer->LandscapeSoftPtr.IsValid()) + if (!IsValid(LandscapePointer) || !LandscapePointer->LandscapeSoftPtr.IsValid()) return; - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return; ALandscapeProxy * Landscape = LandscapePointer->LandscapeSoftPtr.Get(); - if (!Landscape || Landscape->IsPendingKill()) + if (!IsValid(Landscape)) return; // TODO: Get bake base name FString Label = Landscape->GetName(); + if (!LandscapePointer->EditLayerName.IsNone()) + { + Label = FString::Format(TEXT("{0} ({1})"), {Label, (LandscapePointer->EditLayerName.ToString())}); + } + EHoudiniLandscapeOutputBakeType & LandscapeOutputBakeType = LandscapePointer->BakeType; + // Create a group for this output object + IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + + // -------------------------------- + // Modified edit layers, if there are any. + // -------------------------------- + + if (!LandscapePointer->EditLayerName.IsNone()) + { + // Create label to display the edit layer name. + + LandscapeGrp.AddWidgetRow() + .NameContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("LandscapeOutputEditLayers", "Edit Layer")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) + [ + SNew(STextBlock) + .Text(FText::AsCultureInvariant(FText::AsCultureInvariant(LandscapePointer->EditLayerName.ToString())) ) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ]; + } + + + + // -------------------------------- + // Bake options for landscape tile + // -------------------------------- + // Get thumbnail pool for this builder IDetailLayoutBuilder & DetailLayoutBuilder = HouOutputCategory.GetParentLayout(); TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool(); @@ -211,7 +250,7 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( TArray>* BakeOptionString = FHoudiniEngineEditor::Get().GetHoudiniLandscapeOutputBakeOptionsLabels(); // Create bake mesh name textfield. - IDetailGroup& LandscapeGrp = HouOutputCategory.AddGroup(FName(*Label), FText::FromString(Label)); + LandscapeGrp.AddWidgetRow() .NameContent() [ @@ -315,7 +354,7 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( .HAlign(HAlign_Center) .Text(LOCTEXT("Bake", "Bake")) .IsEnabled(true) - .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, OwnerActor, HGPO, Landscape, LandscapeOutputBakeType]() + .OnClicked_Lambda([InOutput, OutputIdentifier, HAC, HGPO, Landscape, LandscapeOutputBakeType]() { FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); if (FoundOutputObject) @@ -330,7 +369,6 @@ FHoudiniOutputDetails::CreateLandscapeOutputWidget_Helper( *FoundOutputObject, HGPO, HAC, - OwnerActor->GetName(), HAC->BakeFolder.Path, HAC->TemporaryCookFolder.Path, InOutput->GetType(), @@ -556,22 +594,22 @@ void FHoudiniOutputDetails::CreateLandscapeEditLayerOutputWidget_Helper(IDetailC UHoudiniOutput* InOutput, const FHoudiniGeoPartObject& HGPO, UHoudiniLandscapeEditLayer* LandscapeEditLayer, const FHoudiniOutputObjectIdentifier& OutputIdentifier) { - if (!LandscapeEditLayer || LandscapeEditLayer->IsPendingKill() || !LandscapeEditLayer->LandscapeSoftPtr.IsValid()) + if (!IsValid(LandscapeEditLayer) || !LandscapeEditLayer->LandscapeSoftPtr.IsValid()) return; - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return; ALandscapeProxy * Landscape = LandscapeEditLayer->LandscapeSoftPtr.Get(); - if (!Landscape || Landscape->IsPendingKill()) + if (!IsValid(Landscape)) return; const FString Label = Landscape->GetName(); @@ -596,44 +634,6 @@ void FHoudiniOutputDetails::CreateLandscapeEditLayerOutputWidget_Helper(IDetailC SNew(STextBlock) .Text(FText::AsCultureInvariant(LayerName)) .Font(IDetailLayoutBuilder::GetDetailFont()) - - // SNew(SHorizontalBox) - // + SHorizontalBox::Slot() - // .Padding(2.0f, 0.0f) - // .VAlign(VAlign_Center) - // .FillWidth(1) - // [ - // SNew(SEditableTextBox) - // .Text(FText::FromString(Label)) - // .Font(IDetailLayoutBuilder::GetDetailFont()) - // .ToolTipText(LOCTEXT("BakeNameTip", "The base name of the baked asset")) - // .HintText(LOCTEXT("BakeNameHintText", "Input bake name to override default")) - // .OnTextCommitted_Lambda([InOutput, OutputIdentifier](const FText& Val, ETextCommit::Type TextCommitType) - // { - // FHoudiniOutputDetails::OnBakeNameCommitted(Val, TextCommitType, InOutput, OutputIdentifier); - // FHoudiniEngineUtils::UpdateEditorProperties(InOutput, true); - // }) - // ] - // + SHorizontalBox::Slot() - // .Padding(2.0f, 0.0f) - // .VAlign(VAlign_Center) - // .AutoWidth() - // [ - // SNew(SButton) - // .ToolTipText(LOCTEXT("RevertNameOverride", "Revert bake name override")) - // .ButtonStyle(FEditorStyle::Get(), "NoBorder") - // .ContentPadding(0) - // .Visibility(EVisibility::Visible) - // .OnClicked_Lambda([InOutput, OutputIdentifier]() - // { - // FHoudiniOutputDetails::OnRevertBakeNameToDefault(InOutput, OutputIdentifier); - // return FReply::Handled(); - // }) - // [ - // SNew(SImage) - // .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) - // ] - // ] ]; // // Create the thumbnail for the landscape output object. @@ -932,27 +932,13 @@ FHoudiniOutputDetails::CreateMeshOutputWidget( IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; UHoudiniAssetComponent* HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; - FString HoudiniAssetName; - if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) - { - HoudiniAssetName = HAC->GetOwner()->GetName(); - } - else if (HAC->GetHoudiniAsset()) - { - HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); - } - else - { - HoudiniAssetName = HAC->GetName(); - } - // Go through this output's object int32 OutputObjIdx = 0; TMap& OutputObjects = InOutput->GetOutputObjects(); @@ -961,8 +947,8 @@ FHoudiniOutputDetails::CreateMeshOutputWidget( UStaticMesh* StaticMesh = Cast(IterObject.Value.OutputObject); UHoudiniStaticMesh* ProxyMesh = Cast(IterObject.Value.ProxyObject); - if ((!StaticMesh || StaticMesh->IsPendingKill()) - && (!ProxyMesh || ProxyMesh->IsPendingKill())) + if ((!IsValid(StaticMesh)) + && (!IsValid(ProxyMesh))) continue; FHoudiniOutputObjectIdentifier & OutputIdentifier = IterObject.Key; @@ -978,19 +964,19 @@ FHoudiniOutputDetails::CreateMeshOutputWidget( break; } - if (StaticMesh && !StaticMesh->IsPendingKill()) + if (IsValid(StaticMesh)) { bool bIsProxyMeshCurrent = IterObject.Value.bProxyIsCurrent; // If we have a static mesh, alway display its widget even if the proxy is more recent CreateStaticMeshAndMaterialWidgets( - HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); + HouOutputCategory, InOutput, StaticMesh, OutputIdentifier, HAC->BakeFolder.Path, HoudiniGeoPartObject, bIsProxyMeshCurrent); } else { // If we only have a proxy mesh, then show the proxy widget CreateProxyMeshAndMaterialWidgets( - HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HoudiniAssetName, HAC->BakeFolder.Path, HoudiniGeoPartObject); + HouOutputCategory, InOutput, ProxyMesh, OutputIdentifier, HAC->BakeFolder.Path, HoudiniGeoPartObject); } } } @@ -998,7 +984,7 @@ FHoudiniOutputDetails::CreateMeshOutputWidget( void FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; int32 OutputObjIdx = 0; @@ -1007,7 +993,7 @@ FHoudiniOutputDetails::CreateCurveOutputWidget(IDetailCategoryBuilder& HouOutput { FHoudiniOutputObject& CurrentOutputObject = IterObject.Value; USceneComponent* SplineComponent = Cast(IterObject.Value.OutputComponent); - if (!SplineComponent || SplineComponent->IsPendingKill()) + if (!IsValid(SplineComponent)) continue; FHoudiniOutputObjectIdentifier& OutputIdentifier = IterObject.Key; @@ -1034,20 +1020,20 @@ FHoudiniOutputDetails::CreateCurveWidgets( FHoudiniOutputObjectIdentifier& OutputIdentifier, FHoudiniGeoPartObject& HoudiniGeoPartObject) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; // We support Unreal Spline out only for now USplineComponent* SplineOutput = Cast(SplineComponent); - if (!SplineOutput || SplineOutput->IsPendingKill()) + if (!IsValid(SplineOutput)) return; UHoudiniAssetComponent * HAC = Cast(InOutput->GetOuter()); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; AActor * OwnerActor = HAC->GetOwner(); - if (!OwnerActor || OwnerActor->IsPendingKill()) + if (!IsValid(OwnerActor)) return; FHoudiniCurveOutputProperties* OutputProperty = &(OutputObject.CurveOutputProperty); @@ -1186,7 +1172,7 @@ FHoudiniOutputDetails::CreateCurveWidgets( { // Set the curve point type locally USplineComponent* Spline = Cast(SplineComponent); - if (!Spline || Spline->IsPendingKill()) + if (!IsValid(Spline)) return; FString *NewChoiceStr = NewChoice.Get(); @@ -1251,7 +1237,7 @@ FHoudiniOutputDetails::CreateCurveWidgets( SAssignNew(ClosedCheckBox, SCheckBox) .OnCheckStateChanged_Lambda([UnrealSpline, InOutput](ECheckBoxState NewState) { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) + if (!IsValid(UnrealSpline)) return; UnrealSpline->SetClosedLoop(NewState == ECheckBoxState::Checked); @@ -1260,7 +1246,7 @@ FHoudiniOutputDetails::CreateCurveWidgets( }) .IsChecked_Lambda([UnrealSpline]() { - if (!UnrealSpline || UnrealSpline->IsPendingKill()) + if (!IsValid(UnrealSpline)) return ECheckBoxState::Unchecked; return UnrealSpline->IsClosedLoop() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -1283,7 +1269,7 @@ FHoudiniOutputDetails::CreateCurveWidgets( .Text(LOCTEXT("OutputCurveBakeButtonText", "Bake")) .IsEnabled(true) .ToolTipText(LOCTEXT("OutputCurveBakeButtonUnrealSplineTooltipText", "Bake to Unreal spline")) - .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OwnerActor, OutputCurveName, OutputObject]() + .OnClicked_Lambda([InOutput, SplineComponent, OutputIdentifier, HoudiniGeoPartObject, HAC, OutputCurveName, OutputObject]() { TArray AllOutputs; AllOutputs.Reserve(HAC->GetNumOutputs()); @@ -1295,7 +1281,6 @@ FHoudiniOutputDetails::CreateCurveWidgets( OutputObject, HoudiniGeoPartObject, HAC, - OwnerActor->GetName(), HAC->BakeFolder.Path, HAC->TemporaryCookFolder.Path, InOutput->GetType(), @@ -1313,12 +1298,11 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( UHoudiniOutput* InOutput, UStaticMesh * StaticMesh, FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, const FString BakeFolder, FHoudiniGeoPartObject& HoudiniGeoPartObject, const bool& bIsProxyMeshCurrent) { - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return; UHoudiniAssetComponent* OwningHAC = Cast(InOutput->GetOuter()); @@ -1416,7 +1400,7 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( } int32 NumSimpleColliders = 0; - if (StaticMesh->GetBodySetup() && !StaticMesh->GetBodySetup()->IsPendingKill()) + if (IsValid(StaticMesh->GetBodySetup())) NumSimpleColliders = StaticMesh->GetBodySetup()->AggGeom.GetElementCount(); if(NumSimpleColliders > 0) @@ -1507,7 +1491,7 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( .HAlign( HAlign_Center ) .Text( LOCTEXT( "Bake", "Bake" ) ) .IsEnabled(true) - .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, HoudiniAssetName, BakeFolder, InOutput, OwningHAC, FoundOutputObject]() + .OnClicked_Lambda([BakeName, StaticMesh, OutputIdentifier, HoudiniGeoPartObject, BakeFolder, InOutput, OwningHAC, FoundOutputObject]() { if (FoundOutputObject) { @@ -1527,7 +1511,6 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( *FoundOutputObject, HoudiniGeoPartObject, OwningHAC, - HoudiniAssetName, BakeFolder, TempCookFolder, InOutput->GetType(), @@ -1565,8 +1548,8 @@ FHoudiniOutputDetails::CreateStaticMeshAndMaterialWidgets( TSharedPtr< SHorizontalBox > HorizontalBox = NULL; FString MaterialName, MaterialPathName; - if ( MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() ) + if ( IsValid(MaterialInterface) + && IsValid(MaterialInterface->GetOuter()) ) { MaterialName = MaterialInterface->GetName(); MaterialPathName = MaterialInterface->GetPathName(); @@ -1721,11 +1704,10 @@ FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( UHoudiniOutput* InOutput, UHoudiniStaticMesh * ProxyMesh, FHoudiniOutputObjectIdentifier& OutputIdentifier, - const FString HoudiniAssetName, const FString BakeFolder, FHoudiniGeoPartObject& HoudiniGeoPartObject) { - if (!ProxyMesh || ProxyMesh->IsPendingKill()) + if (!IsValid(ProxyMesh)) return; FHoudiniOutputObject* FoundOutputObject = InOutput->GetOutputObjects().Find(OutputIdentifier); @@ -1891,8 +1873,8 @@ FHoudiniOutputDetails::CreateProxyMeshAndMaterialWidgets( TSharedPtr< SHorizontalBox > HorizontalBox = NULL; FString MaterialName, MaterialPathName; - if (MaterialInterface && !MaterialInterface->IsPendingKill() - && MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill()) + if (IsValid(MaterialInterface) + && IsValid(MaterialInterface->GetOuter())) { MaterialName = MaterialInterface->GetName(); MaterialPathName = MaterialInterface->GetPathName(); @@ -2182,21 +2164,18 @@ FHoudiniOutputDetails::OnThumbnailDoubleClick( FReply FHoudiniOutputDetails::OnBakeStaticMesh(UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject& GeoPartObject) { - if (HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill()) + if (IsValid(HoudiniAssetComponent) && IsValid(StaticMesh)) { FHoudiniPackageParams PackageParms; - FHoudiniEngineBakeUtils::BakeStaticMesh(HoudiniAssetComponent, GeoPartObject, StaticMesh, PackageParms); // TODO: Bake the SM - // We need to locate corresponding geo part object in component. const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject(StaticMesh); // (void)FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage( - // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); - + // StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets); } return FReply::Handled(); @@ -2217,7 +2196,7 @@ FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( int32 MaterialIdx) { FReply RetValue = FReply::Handled(); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return RetValue; TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); @@ -2293,7 +2272,7 @@ FHoudiniOutputDetails::OnResetMaterialInterfaceClicked( int32 InMaterialIdx) { FReply RetValue = FReply::Handled(); - if (!InLandscape || InLandscape->IsPendingKill()) + if (!IsValid(InLandscape)) return RetValue; // Retrieve the material interface which is being replaced. @@ -2493,10 +2472,10 @@ FHoudiniOutputDetails::OnMaterialInterfaceDropped( int32 MaterialIdx) { UMaterialInterface * MaterialInterface = Cast(InObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) + if (!IsValid(MaterialInterface)) return; - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) return; TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); @@ -2565,7 +2544,7 @@ FHoudiniOutputDetails::OnMaterialInterfaceDropped( { // Only look at MeshComponents UStaticMeshComponent * SMC = Cast(OutputObject.Value.OutputComponent); - if (SMC && !SMC->IsPendingKill()) + if (IsValid(SMC)) { if (SMC->GetStaticMesh() == StaticMesh) { @@ -2576,7 +2555,7 @@ FHoudiniOutputDetails::OnMaterialInterfaceDropped( else { UStaticMesh* SM = Cast(OutputObject.Value.OutputObject); - if (SM && !SM->IsPendingKill()) + if (IsValid(SM)) { SM->Modify(); SM->SetMaterial(MaterialIdx, MaterialInterface); @@ -2606,10 +2585,10 @@ FHoudiniOutputDetails::OnMaterialInterfaceDropped( int32 MaterialIdx) { UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >(InDroppedObject); - if (!MaterialInterface || MaterialInterface->IsPendingKill()) + if (!IsValid(MaterialInterface)) return; - if (!InLandscape || InLandscape->IsPendingKill()) + if (!IsValid(InLandscape)) return; bool bViewportNeedsUpdate = false; @@ -2648,7 +2627,7 @@ FHoudiniOutputDetails::OnMaterialInterfaceDropped( else { // External Material? - if (OldMaterialInterface && !OldMaterialInterface->IsPendingKill()) + if (IsValid(OldMaterialInterface)) MaterialString = OldMaterialInterface->GetName(); } } @@ -2715,13 +2694,13 @@ FHoudiniOutputDetails::OnMaterialInterfaceSelected( UObject * Object = AssetData.GetAsset(); UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) + if (IsValid(SM)) { return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); } ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) + if (IsValid(Landscape)) { return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); } @@ -2734,10 +2713,10 @@ FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( UHoudiniOutput * InOutput, int32 MaterialIdx) { - if (!OutputObject || OutputObject->IsPendingKill()) + if (!IsValid(OutputObject)) return; - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; if (GEditor) @@ -2758,16 +2737,16 @@ FHoudiniOutputDetails::OnUseContentBrowserSelectedMaterialInterface( break; } - if (Object && !Object->IsPendingKill()) + if (IsValid(Object)) { UStaticMesh* SM = Cast(OutputObject); - if (SM && !SM->IsPendingKill()) + if (IsValid(SM)) { return OnMaterialInterfaceDropped(Object, SM, InOutput, MaterialIdx); } ALandscapeProxy* Landscape = Cast(OutputObject); - if (Landscape && !Landscape->IsPendingKill()) + if (IsValid(Landscape)) { return OnMaterialInterfaceDropped(Object, Landscape, InOutput, MaterialIdx); } @@ -2780,7 +2759,7 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( IDetailCategoryBuilder& HouOutputCategory, UHoudiniOutput* InOutput) { - if (!InOutput || InOutput->IsPendingKill()) + if (!IsValid(InOutput)) return; // Do not display instancer UI for one-instance instancers @@ -2917,7 +2896,7 @@ FHoudiniOutputDetails::CreateInstancerOutputWidget( for( int32 VariationIdx = 0; VariationIdx < CurInstanceOutput.VariationObjects.Num(); VariationIdx++ ) { UObject * InstancedObject = CurInstanceOutput.VariationObjects[VariationIdx].LoadSynchronous(); - if ( !InstancedObject || InstancedObject->IsPendingKill() ) + if ( !IsValid(InstancedObject) ) { HOUDINI_LOG_WARNING( TEXT("Null Object found for instance variation %d"), VariationIdx ); continue; @@ -3547,14 +3526,13 @@ FHoudiniOutputDetails::OnBakeOutputObject( const FHoudiniOutputObject& InOutputObject, const FHoudiniGeoPartObject & HGPO, const UObject* OutputOwner, - const FString & HoudiniAssetName, const FString & BakeFolder, const FString & TempCookFolder, const EHoudiniOutputType & Type, const EHoudiniLandscapeOutputBakeType & LandscapeBakeType, const TArray& InAllOutputs) { - if (!BakedOutputObject || BakedOutputObject->IsPendingKill()) + if (!IsValid(BakedOutputObject)) return; // Fill in the package params @@ -3566,16 +3544,20 @@ FHoudiniOutputDetails::OnBakeOutputObject( UWorld* WorldContext = OutputOwner ? OutputOwner->GetWorld() : GWorld; const UHoudiniAssetComponent* HAC = FHoudiniEngineUtils::GetOuterHoudiniAssetComponent(OutputOwner); check(IsValid(HAC)); + const FString HoudiniAssetName = IsValid(HAC->GetHoudiniAsset()) ? HAC->GetHoudiniAsset()->GetName() : TEXT(""); + const FString HoudiniAssetActorName = IsValid(HAC->GetOwner()) ? HAC->GetOwner()->GetName() : TEXT(""); const bool bAutomaticallySetAttemptToLoadMissingPackages = true; const bool bSkipObjectNameResolutionAndUseDefault = !InBakeName.IsEmpty(); // If InBakeName is set use it as is for the object name const bool bSkipBakeFolderResolutionAndUseDefault = false; FHoudiniEngineUtils::FillInPackageParamsForBakingOutputWithResolver( WorldContext, HAC, OutputIdentifier, InOutputObject, BakedOutputObject->GetName(), - HoudiniAssetName, PackageParams, Resolver, - BakeFolder, EPackageReplaceMode::ReplaceExistingAssets, + PackageParams, Resolver, BakeFolder, EPackageReplaceMode::ReplaceExistingAssets, + HoudiniAssetName, HoudiniAssetActorName, bAutomaticallySetAttemptToLoadMissingPackages, bSkipObjectNameResolutionAndUseDefault, bSkipBakeFolderResolutionAndUseDefault); + FHoudiniEngineOutputStats BakeStats; + switch (Type) { case EHoudiniOutputType::Mesh: @@ -3587,7 +3569,7 @@ FHoudiniOutputDetails::OnBakeOutputObject( TempCookFolderPath.Path = TempCookFolder; TMap AlreadyBakedMaterialsMap; UStaticMesh* DuplicatedMesh = FHoudiniEngineBakeUtils::BakeStaticMesh( - StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath, AlreadyBakedMaterialsMap); + StaticMesh, PackageParams, InAllOutputs, TempCookFolderPath, AlreadyBakedMaterialsMap, BakeStats); } } break; @@ -3598,7 +3580,8 @@ FHoudiniOutputDetails::OnBakeOutputObject( { AActor* BakedActor; USplineComponent* BakedSplineComponent; - FHoudiniEngineBakeUtils::BakeCurve(SplineComponent, GWorld->GetCurrentLevel(), PackageParams, BakedActor, BakedSplineComponent); + FHoudiniEngineBakeUtils::BakeCurve( + SplineComponent, GWorld->GetCurrentLevel(), PackageParams, FName(PackageParams.ObjectName), BakedActor, BakedSplineComponent, BakeStats); } } break; @@ -3607,11 +3590,18 @@ FHoudiniOutputDetails::OnBakeOutputObject( ALandscapeProxy* Landscape = Cast(BakedOutputObject); if (Landscape) { - FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType); + FHoudiniEngineBakeUtils::BakeHeightfield(Landscape, PackageParams, LandscapeBakeType, BakeStats); } } break; } + + { + const FString FinishedTemplate = TEXT("Baking finished. Created {0} packages. Updated {1} packages."); + FString Msg = FString::Format(*FinishedTemplate, { BakeStats.NumPackagesCreated, BakeStats.NumPackagesUpdated } ); + FHoudiniEngine::Get().FinishTaskSlateNotification( FText::FromString(Msg) ); + } + } FReply diff --git a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h index be8862f71..d79163bff 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h +++ b/Source/HoudiniEngineEditor/Private/HoudiniOutputDetails.h @@ -74,7 +74,6 @@ class FHoudiniOutputDetails : public TSharedFromThisIsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; // PDG ASSET @@ -1300,7 +1300,7 @@ FHoudiniPDGDetails::AddTOPNetworkWidget( else { // Delete and unload the result objects and actors now - TOPNet->DeleteWorkResultOutputObjects(); + TOPNet->DeleteAllWorkResultObjectOutputs(); } } } @@ -1953,7 +1953,7 @@ FHoudiniPDGDetails::AddTOPNodeWidget( else { // Delete and unload the result objects and actors now - TOPNode->DeleteWorkResultOutputObjects(); + TOPNode->DeleteAllWorkResultObjectOutputs(); } } } @@ -2028,7 +2028,7 @@ FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) void FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; // Update the workitem stats @@ -2041,7 +2041,7 @@ FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& void FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) { - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); @@ -2585,7 +2585,7 @@ FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, { const bool bNewState = (NewState == ECheckBoxState::Checked); - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; // Record a transaction for undo/redo diff --git a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp index a5b843df7..8676dad4c 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniParameterDetails.cpp @@ -285,7 +285,7 @@ SCustomizedBox::SetHoudiniParameter(TArray& InParams) return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; @@ -323,7 +323,7 @@ SCustomizedBox::SetHoudiniParameter(TArray& InParams) case EHoudiniParameterType::ColorRamp: { UHoudiniParameterRampColor * ColorRampParameter = Cast(MainParam); - if (!ColorRampParameter || ColorRampParameter->IsPendingKill()) + if (!IsValid(ColorRampParameter)) return; MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_COLORRAMP; @@ -396,7 +396,7 @@ SCustomizedBox::SetHoudiniParameter(TArray& InParams) case EHoudiniParameterType::FloatRamp: { UHoudiniParameterRampFloat * FloatRampParameter = Cast(MainParam); - if (!FloatRampParameter || FloatRampParameter->IsPendingKill()) + if (!IsValid(FloatRampParameter)) return; MarginHeight = HOUDINI_PARAMETER_UI_ROW_MARGIN_HEIGHT_FLOATRAMP; @@ -428,12 +428,12 @@ SCustomizedBox::SetHoudiniParameter(TArray& InParams) { UHoudiniParameterOperatorPath* InputParam = Cast(MainParam); - if (!InputParam || InputParam->IsPendingKill() || !InputParam->HoudiniInput.IsValid()) + if (!IsValid(InputParam) || !InputParam->HoudiniInput.IsValid()) break; UHoudiniInput* Input = InputParam->HoudiniInput.Get(); - if (!Input || Input->IsPendingKill()) + if (!IsValid(Input)) break; @@ -663,7 +663,7 @@ float SCustomizedBox::AddIndentation(UHoudiniParameter* InParam, TMap& InAllMultiParms, TMap& InAllFoldersAndFolderLists) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return 0.0f; bool bIsMainParmSimpleFolder = false; @@ -1859,7 +1859,7 @@ bool FHoudiniParameterDetails::CastParameters( for (auto CurrentParam : InParams) { T* CastedParam = Cast(CurrentParam); - if (CastedParam && !CastedParam->IsPendingKill()) + if (IsValid(CastedParam)) OutCastedParams.Add(CastedParam); } @@ -1874,7 +1874,7 @@ FHoudiniParameterDetails::CreateWidget(IDetailCategoryBuilder & HouParameterCate return; UHoudiniParameter* InParam = InParams[0]; - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; // The directory won't parse if parameter ids are -1 @@ -2058,7 +2058,7 @@ FHoudiniParameterDetails::CreateNameWidget(FDetailWidgetRow* Row, TArrayIsPendingKill()) + if (!IsValid(MainParam)) return; if (!Row) @@ -2128,7 +2128,7 @@ FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; FString ParameterLabelStr = MainParam->GetParameterLabel(); @@ -2217,7 +2217,7 @@ FHoudiniParameterDetails::CreateNameWidgetWithAutoUpdate(FDetailWidgetRow* Row, auto IsAutoUpdateChecked = [MainParam]() { - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return ECheckBoxState::Unchecked; return MainParam->IsAutoUpdate() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -2346,7 +2346,7 @@ FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterC UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return nullptr; // Created row for the current parameter (if there is not a row created, do not show the parameter). @@ -2364,7 +2364,7 @@ FHoudiniParameterDetails::CreateNestedRow(IDetailCategoryBuilder & HouParameterC return nullptr; UHoudiniParameterFolderList* ParentFolderList = Cast(AllFoldersAndFolderLists[MainParam->GetParentParmId()]); - if (!ParentFolderList || ParentFolderList->IsPendingKill()) + if (!IsValid(ParentFolderList)) return nullptr; // This should not happen ParentMultiParmId = ParentFolderList->GetParentParmId(); @@ -2476,7 +2476,7 @@ FHoudiniParameterDetails::HandleUnsupportedParmType(IDetailCategoryBuilder & Hou return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; CreateNestedRow(HouParameterCategory, (TArray)InParams); @@ -2495,7 +2495,7 @@ FHoudiniParameterDetails::CreateWidgetFloat( return; UHoudiniParameterFloat* MainParam = FloatParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -2704,12 +2704,12 @@ FHoudiniParameterDetails::CreateWidgetFloat( ] .OnClicked_Lambda([FloatParams, MainParam]() { - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return FReply::Handled(); for (auto & CurParam : FloatParams) { - if (!CurParam || CurParam->IsPendingKill()) + if (!IsValid(CurParam)) continue; CurParam->SwitchUniformLock(); @@ -2827,7 +2827,7 @@ FHoudiniParameterDetails::CreateWidgetInt(IDetailCategoryBuilder & HouParameterC return; UHoudiniParameterInt* MainParam = IntParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3002,7 +3002,7 @@ FHoudiniParameterDetails::CreateWidgetString( IDetailCategoryBuilder & HouParame return; UHoudiniParameterString* MainParam = StringParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3396,7 +3396,7 @@ FHoudiniParameterDetails::CreateWidgetColor(IDetailCategoryBuilder & HouParamete return; UHoudiniParameterColor* MainParam = ColorParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row FDetailWidgetRow* Row = CreateNestedRow(HouParameterCategory, InParams); @@ -3474,7 +3474,7 @@ FHoudiniParameterDetails::CreateWidgetButton(IDetailCategoryBuilder & HouParamet return; UHoudiniParameterButton* MainParam = ButtonParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3531,7 +3531,7 @@ FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouPa return; UHoudiniParameterButtonStrip* MainParam = ButtonStripParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3557,7 +3557,7 @@ FHoudiniParameterDetails::CreateWidgetButtonStrip(IDetailCategoryBuilder & HouPa for (auto & NextParam : ButtonStripParams) { - if (!NextParam || NextParam->IsPendingKill()) + if (!IsValid(NextParam)) continue; if (!NextParam->Values.IsValidIndex(Idx)) @@ -3630,7 +3630,7 @@ FHoudiniParameterDetails::CreateWidgetLabel(IDetailCategoryBuilder & HouParamete return; UHoudiniParameterLabel* MainParam = LabelParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3674,7 +3674,7 @@ FHoudiniParameterDetails::CreateWidgetToggle(IDetailCategoryBuilder & HouParamet return; UHoudiniParameterToggle* MainParam = ToggleParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3773,7 +3773,7 @@ void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouPara return; UHoudiniParameterFile* MainParam = FileParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -3793,6 +3793,17 @@ void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouPara FString BrowseWidgetDirectory = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_OPEN); + TMap& Tags = MainParam->GetTags(); + if (Tags.Contains(HAPI_PARAM_TAG_DEFAULT_DIR)) + { + if(!Tags[HAPI_PARAM_TAG_DEFAULT_DIR].IsEmpty()) + { + FString DefaultDir = Tags[HAPI_PARAM_TAG_DEFAULT_DIR]; + if(FPaths::DirectoryExists(DefaultDir)) + BrowseWidgetDirectory = DefaultDir; + } + } + auto UpdateCheckRelativePath = [MainParam](const FString & PickedPath) { UHoudiniAssetComponent* HoudiniAssetComponent = Cast(MainParam->GetOuter()); @@ -3806,9 +3817,9 @@ void FHoudiniParameterDetails::CreateWidgetFile(IDetailCategoryBuilder & HouPara } // Check if the path is relative to the asset - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + if (IsValid(HoudiniAssetComponent)) { - if (HoudiniAssetComponent->HoudiniAsset && !HoudiniAssetComponent->HoudiniAsset->IsPendingKill()) + if (IsValid(HoudiniAssetComponent->HoudiniAsset)) { FString AssetFilePath = FPaths::GetPath(HoudiniAssetComponent->HoudiniAsset->AssetFileName); if (FPaths::FileExists(AssetFilePath)) @@ -3918,7 +3929,7 @@ FHoudiniParameterDetails::CreateWidgetChoice(IDetailCategoryBuilder & HouParamet return; UHoudiniParameterChoice* MainParam = ChoiceParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Create a new detail row @@ -4043,7 +4054,7 @@ FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouP return; UHoudiniParameterOperatorPath* MainParam = OperatorPathParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; UHoudiniInput* MainInput = MainParam->HoudiniInput.Get(); @@ -4058,7 +4069,7 @@ FHoudiniParameterDetails::CreateWidgetOperatorPath(IDetailCategoryBuilder & HouP for (int LinkedIdx = 1; LinkedIdx < OperatorPathParams.Num(); LinkedIdx++) { UHoudiniInput* LinkedInput = OperatorPathParams[LinkedIdx]->HoudiniInput.Get(); - if (!LinkedInput || LinkedInput->IsPendingKill()) + if (!IsValid(LinkedInput)) continue; // Linked params should match the main param! If not try to find one that matches @@ -4088,7 +4099,7 @@ FHoudiniParameterDetails::CreateWidgetFloatRamp(IDetailCategoryBuilder & HouPara return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // TODO: remove this once we have verified that updating the Points and CachedPoints arrays in @@ -4259,7 +4270,7 @@ FHoudiniParameterDetails::CreateWidgetColorRamp(IDetailCategoryBuilder & HouPara return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // TODO: remove this once we have verified that updating the Points and CachedPoints arrays in @@ -4573,7 +4584,7 @@ FHoudiniParameterDetails::CreateWidgetRampPoints(IDetailCategoryBuilder& Categor return; UHoudiniParameter* MainParam = InParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; UHoudiniParameterRampFloat * MainFloatRampParameter = nullptr; @@ -5606,7 +5617,7 @@ FHoudiniParameterDetails::CreateWidgetFolderList(IDetailCategoryBuilder & HouPar return; UHoudiniParameterFolderList* MainParam = FolderListParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Add this folder list to the folder map @@ -5658,10 +5669,10 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet return; UHoudiniParameterFolder* MainParam = FolderParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; - if (!CurrentFolderList || CurrentFolderList->IsPendingKill()) // This should not happen + if (!IsValid(CurrentFolderList)) // This should not happen return; // If a folder is invisible, its children won't be listed by HAPI. @@ -5676,7 +5687,7 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet if (FolderStack.Num() > 1) { TArray &ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + if (ParentFolderQueue.Num() > 0 && IsValid(ParentFolderQueue[0])) ParentFolderQueue[0]->GetChildCounter() -= 1; } @@ -5759,7 +5770,7 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet // Case 2-1: The folder is in another folder. if (FolderStack.Num() > 1 && CurrentFolderListSize > 0) { - TArray & MyFolderQueue = FolderStack.Last(); + TArray & MyFolderQueue = FolderStack.Last(); TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; if (ParentFolderQueue.Num() <= 0) //This should happen @@ -5826,8 +5837,7 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet } } } - - + CurrentFolderListSize -= 1; // Prune the stack if finished parsing current folderlist @@ -5836,7 +5846,7 @@ FHoudiniParameterDetails::CreateWidgetFolder(IDetailCategoryBuilder & HouParamet if (FolderStack.Num() > 1 && !MainParam->IsDirectChildOfMultiParm()) { TArray & ParentFolderQueue = FolderStack[FolderStack.Num() - 2]; - if (ParentFolderQueue.Num() > 0 && ParentFolderQueue[0] && !ParentFolderQueue[0]->IsPendingKill()) + if (ParentFolderQueue.Num() > 0 && IsValid(ParentFolderQueue[0])) ParentFolderQueue[0]->GetChildCounter() -= 1; } @@ -5861,7 +5871,7 @@ FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArr UHoudiniParameterFolder* MainParam = FolderParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; TSharedPtr VerticalBox; @@ -5949,7 +5959,7 @@ FHoudiniParameterDetails::CreateFolderHeaderUI(FDetailWidgetRow* HeaderRow, TArr void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParameterCategory, UHoudiniParameterFolder* InFolder, const bool& bIsShown) { - if (!InFolder || InFolder->IsPendingKill() || !CurrentFolderList) + if (!IsValid(InFolder) || !CurrentFolderList) return; if (FolderStack.Num() <= 0) // error state @@ -5999,7 +6009,7 @@ void FHoudiniParameterDetails::CreateWidgetTab(IDetailCategoryBuilder & HouParam for (auto & CurTab : CurrentTabs) { - if (!CurTab || CurTab->IsPendingKill()) + if (!IsValid(CurTab)) continue; CurTab->SetIsContentShown(CurTab->IsChosen()); @@ -6080,7 +6090,7 @@ FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouPara return; UHoudiniParameterMultiParm* MainParam = MultiParmParams[0]; - if (!MainParam || MainParam->IsPendingKill()) + if (!IsValid(MainParam)) return; // Add current multiparm parameter to AllmultiParms map @@ -6106,22 +6116,8 @@ FHoudiniParameterDetails::CreateWidgetMultiParm(IDetailCategoryBuilder & HouPara if (InValue < 0) return; - int32 ChangesCount = FMath::Abs(MainParam->MultiParmInstanceLastModifyArray.Num() - InValue); - - if (MainParam->MultiParmInstanceLastModifyArray.Num() > InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->RemoveElement(-1); - - MainParam->MarkChanged(true); - } - else if (MainParam->MultiParmInstanceLastModifyArray.Num() < InValue) - { - for (int32 Idx = 0; Idx < ChangesCount; ++Idx) - MainParam->InsertElement(); - + if (MainParam->SetNumElements(InValue)) MainParam->MarkChanged(true); - } }; // Add multiparm UI. @@ -6268,7 +6264,7 @@ FHoudiniParameterDetails::CreateWidgetMultiParmObjectButtons(TSharedPtrIsPendingKill()) + if (!IsValid(MainParam)) return; if (!HorizontalBox || !AllMultiParms.Contains(MainParam->GetParentParmId()) || !MultiParmInstanceIndices.Contains(MainParam->GetParentParmId())) @@ -6409,7 +6405,7 @@ FHoudiniParameterDetails::PruneStack() for (int32 QueueItr = CurrentQueue.Num() - 1; QueueItr >= 0; --QueueItr) { UHoudiniParameterFolder * CurrentFolder = CurrentQueue[QueueItr]; - if (!CurrentFolder || CurrentFolder->IsPendingKill()) + if (!IsValid(CurrentFolder)) continue; if (CurrentFolder->GetChildCounter() == 0) @@ -6428,7 +6424,7 @@ FHoudiniParameterDetails::PruneStack() FText FHoudiniParameterDetails::GetParameterTooltip(UHoudiniParameter* InParam) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return FText(); // Tooltip starts with Label (name) @@ -6736,7 +6732,7 @@ FHoudiniParameterDetails::SyncCachedColorRampPoints(UHoudiniParameterRampColor* void FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterRampFloat* InParam, const int32 &InDeleteIndex) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( @@ -6755,7 +6751,7 @@ FHoudiniParameterDetails::CreateFloatRampParameterDeleteEvent(UHoudiniParameterR void FHoudiniParameterDetails::CreateColorRampParameterDeleteEvent(UHoudiniParameterRampColor* InParam, const int32 &InDeleteIndex) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; UHoudiniParameterRampModificationEvent* DeleteEvent = NewObject( @@ -6775,7 +6771,7 @@ void FHoudiniParameterDetails::CreateFloatRampParameterInsertEvent(UHoudiniParameterRampFloat* InParam, const float& InPosition, const float& InValue, const EHoudiniRampInterpolationType &InInterp) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( @@ -6797,7 +6793,7 @@ void FHoudiniParameterDetails::CreateColorRampParameterInsertEvent(UHoudiniParameterRampColor* InParam, const float& InPosition, const FLinearColor& InColor, const EHoudiniRampInterpolationType &InInterp) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; UHoudiniParameterRampModificationEvent* InsertEvent = NewObject( @@ -7305,7 +7301,7 @@ FHoudiniParameterDetails::ReplaceColorRampParameterPointsWithMainParameter(UHoud void FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameterCategory, UHoudiniParameter* InParam) { - if (!InParam || InParam->IsPendingKill()) + if (!IsValid(InParam)) return; // When the paramId is invalid, the directory won't parse. @@ -7338,7 +7334,7 @@ FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameter { // The parent is a multiparm UHoudiniParameterMultiParm* ParentMultiParm = AllMultiParms[ParentParamId]; - if (!ParentMultiParm || ParentMultiParm->IsPendingKill()) + if (!IsValid(ParentMultiParm)) return; if (ParentMultiParm->MultiParmInstanceCount * ParentMultiParm->MultiParmInstanceLength - 1 == CurParam->GetChildIndex()) @@ -7360,7 +7356,7 @@ FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameter UHoudiniParameter* ParentFolderParam = AllFoldersAndFolderLists[ParentParamId]; CurParam = ParentFolderParam; - if (!ParentFolderParam || ParentFolderParam->IsPendingKill()) + if (!IsValid(ParentFolderParam)) return; if (ParentFolderParam->GetParameterType() == EHoudiniParameterType::Folder) @@ -7373,7 +7369,7 @@ FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameter { // The parent is a folderlist UHoudiniParameterFolderList* ParentFolderList = Cast(ParentFolderParam); - if (!ParentFolderList || ParentFolderList->IsPendingKill()) + if (!IsValid(ParentFolderList)) return; if (ParentFolderList->IsTabMenu() && ParentFolderList->IsTabsShown() && ParentFolderList->IsTabParseFinished() && DividerLinePositions.Num() > 0) @@ -7381,7 +7377,7 @@ FHoudiniParameterDetails::RemoveTabDividers(IDetailCategoryBuilder& HouParameter if (!CurrentTabEndingRow) CreateTabEndingRow(HouParameterCategory); - if (CurrentTabEndingRow) + if (CurrentTabEndingRow && CurrentTabEndingRow->DividerLinePositions.Num() > 0) { CurrentTabEndingRow->EndingDividerLinePositions.Add(DividerLinePositions.Top()); CurrentTabEndingRow->DividerLinePositions.Pop(); diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp index a5d2793df..942928325 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIAssetWrapper.cpp @@ -31,6 +31,7 @@ #include "HoudiniAssetComponent.h" #include "HoudiniEngineBakeUtils.h" #include "HoudiniEngineCommands.h" +#include "HoudiniEngineEditorUtils.h" #include "HoudiniEngineUtils.h" #include "HoudiniOutputDetails.h" #include "HoudiniParameter.h" @@ -894,7 +895,7 @@ UHoudiniPublicAPIAssetWrapper::SetIntParameterValue_Implementation(FName InParam return false; } - bDidChangeValue = MultiParam->SetValue(InValue); + bDidChangeValue = MultiParam->SetNumElements(InValue); } else if (ParamType == EHoudiniParameterType::Toggle) { @@ -990,7 +991,7 @@ UHoudiniPublicAPIAssetWrapper::GetIntParameterValue_Implementation(FName InParam return false; } - OutValue = MultiParam->GetValue(); + OutValue = MultiParam->GetNextInstanceCount(); return true; } else if (ParamType == EHoudiniParameterType::Toggle) @@ -2506,7 +2507,13 @@ UHoudiniPublicAPIAssetWrapper::SetInputAtIndex_Implementation(const int32 InNode return false; } - return PopulateHoudiniInput(InInput, HoudiniInput); + const bool bSuccess = PopulateHoudiniInput(InInput, HoudiniInput); + + // Update the details panel (mostly for when new curves/components are created where visualizers are driven + // through the details panel) + FHoudiniEngineEditorUtils::ReselectComponentOwnerIfSelected(HAC); + + return bSuccess; } bool @@ -2598,7 +2605,13 @@ UHoudiniPublicAPIAssetWrapper::SetInputParameter_Implementation(const FName& InP return false; } - return PopulateHoudiniInput(InInput, HoudiniInput); + const bool bSuccess = PopulateHoudiniInput(InInput, HoudiniInput); + + // Update the details panel (mostly for when new curves/components are created where visualizers are driven + // through the details panel) + FHoudiniEngineEditorUtils::ReselectComponentOwnerIfSelected(HAC); + + return bSuccess; } bool @@ -2857,25 +2870,6 @@ UHoudiniPublicAPIAssetWrapper::BakeOutputObjectAt_Implementation(const int32 InI return false; } - // Determine the HoudiniAssetName - FString HoudiniAssetName; - if (HAC->GetOwner() && (HAC->GetOwner()->IsPendingKill())) - { - // If the HAC has a valid owner, use the owner's name - // TODO: Should this be more specific, such as checking for a HoudiniAssetActor? - HoudiniAssetName = HAC->GetOwner()->GetName(); - } - else if (HAC->GetHoudiniAsset()) - { - // Otherwise, if the HAC has a valid HoudiniAsset, use its name - HoudiniAssetName = HAC->GetHoudiniAsset()->GetName(); - } - else - { - // Fallback to the HAC's name - HoudiniAssetName = HAC->GetName(); - } - TArray AllOutputs; HAC->GetOutputs(AllOutputs); @@ -2886,7 +2880,6 @@ UHoudiniPublicAPIAssetWrapper::BakeOutputObjectAt_Implementation(const int32 InI *OutputObject, HoudiniGeoPartObject, HAC, - HoudiniAssetName, HAC->BakeFolder.Path, HAC->TemporaryCookFolder.Path, OutputType, diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp index f76de81a5..ea801d450 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIInputTypes.cpp @@ -110,6 +110,8 @@ UHoudiniPublicAPIInput::PopulateFromHoudiniInput(UHoudiniInput const* const InIn continue; UObject* NewInputObject = ConvertInternalInputObject(SrcInputObject->GetObject()); + + // TODO: PENDINGKILL replacement ? if (NewInputObject && NewInputObject->IsPendingKill()) { SetErrorMessage(FString::Printf( @@ -135,13 +137,32 @@ UHoudiniPublicAPIInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const return false; } - // Set / change the input type + bool bAnyChanges = false; + + // If the input type didn't change, but the new/incoming InputObjects array is now smaller than the current input + // objects array on the input, delete the surplus objects const EHoudiniInputType InputType = GetInputType(); - bool bBlueprintStructureModified = false; - InInput->SetInputType(InputType, bBlueprintStructureModified); + const int32 NumInputObjects = InputObjects.Num(); + if (InputType == InInput->GetInputType()) + { + const int32 OldNumInputObjects = InInput->GetNumberOfInputObjects(); + if (NumInputObjects < OldNumInputObjects) + { + for (int32 Index = OldNumInputObjects - 1; Index >= NumInputObjects; --Index) + { + InInput->DeleteInputObjectAt(Index); + } + bAnyChanges = true; + } + } + else + { + // Set / change the input type + bool bBlueprintStructureModified = false; + InInput->SetInputType(InputType, bBlueprintStructureModified); + } // Set any general settings - bool bAnyChanges = false; if (InInput->GetKeepWorldTransform() != bKeepWorldTransform) { InInput->SetKeepWorldTransform(bKeepWorldTransform); @@ -154,22 +175,34 @@ UHoudiniPublicAPIInput::UpdateHoudiniInput(UHoudiniInput* const InInput) const } // Copy / set the input objects on the Houdini Input - const int32 NumInputObjects = InputObjects.Num(); InInput->SetInputObjectsNumber(InputType, NumInputObjects); for (int32 Index = 0; Index < NumInputObjects; ++Index) { UObject* const InputObject = InputObjects[Index]; + UObject const* CurrentInputObject = InInput->GetInputObjectAt(Index); if (!IsValid(InputObject)) { + // Delete existing input object, but leave its space in the array, we'll set that to nullptr + if (CurrentInputObject) + { + const bool bRemoveIndexFromArray = false; + InInput->DeleteInputObjectAt(Index, bRemoveIndexFromArray); + } InInput->SetInputObjectAt(Index, nullptr); + + if (!bAnyChanges && CurrentInputObject) + bAnyChanges = true; } else { - ConvertAPIInputObjectAndAssignToInput(InputObject, InInput, Index); - UHoudiniInputObject *DstInputObject = InInput->GetHoudiniInputObjectAt(Index); - if (DstInputObject) - CopyPropertiesToHoudiniInputObject(InputObject, DstInputObject); + UObject const* const NewInputObject = ConvertAPIInputObjectAndAssignToInput(InputObject, InInput, Index); + UHoudiniInputObject *DstHoudiniInputObject = InInput->GetHoudiniInputObjectAt(Index); + if (DstHoudiniInputObject) + CopyPropertiesToHoudiniInputObject(InputObject, DstHoudiniInputObject); + + if (!bAnyChanges && NewInputObject != CurrentInputObject) + bAnyChanges = true; } } @@ -238,7 +271,19 @@ UHoudiniPublicAPIInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAPIInpu if (!IsValid(InHoudiniInput)) return nullptr; - UObject* const ObjectToSet = (InAPIInputObject && !InAPIInputObject->IsPendingKill()) ? InAPIInputObject : nullptr; + UObject const* const CurrentInputObject = InHoudiniInput->GetInputObjectAt(InInputIndex); + + UObject* const ObjectToSet = (IsValid(InAPIInputObject)) ? InAPIInputObject : nullptr; + + // Delete the existing input object if it is invalid or differs from ObjectToSet + if (CurrentInputObject && (!IsValid(CurrentInputObject) || CurrentInputObject != ObjectToSet)) + { + // Keep the space/index in the array, we're going to set the new input object at the same index + const bool bRemoveIndexFromArray = false; + InHoudiniInput->DeleteInputObjectAt(InInputIndex, bRemoveIndexFromArray); + InHoudiniInput->MarkChanged(true); + } + InHoudiniInput->SetInputObjectAt(InInputIndex, ObjectToSet); return ObjectToSet; @@ -423,13 +468,63 @@ UHoudiniPublicAPICurveInputObject::CopyToHoudiniSplineComponent(UHoudiniSplineCo if (!IsValid(InSpline)) return; - InSpline->SetClosedCurve(bClosed); - InSpline->SetReversed(bReversed); - InSpline->SetCurveType(ToHoudiniCurveType(CurveType)); - InSpline->SetCurveMethod(ToHoudiniCurveMethod(CurveMethod)); - InSpline->ResetCurvePoints(); - InSpline->ResetDisplayPoints(); - InSpline->CurvePoints = CurvePoints; + bool bAnyChanges = false; + if (bClosed != InSpline->IsClosedCurve()) + { + InSpline->SetClosedCurve(bClosed); + bAnyChanges = true; + } + if (bReversed != InSpline->IsReversed()) + { + InSpline->SetReversed(bReversed); + bAnyChanges = true; + } + const EHoudiniCurveType HoudiniCurveType = ToHoudiniCurveType(CurveType); + if (HoudiniCurveType != InSpline->GetCurveType()) + { + InSpline->SetCurveType(HoudiniCurveType); + bAnyChanges = true; + } + const EHoudiniCurveMethod HoudiniCurveMethod = ToHoudiniCurveMethod(CurveMethod); + if (HoudiniCurveMethod != InSpline->GetCurveMethod()) + { + InSpline->SetCurveMethod(HoudiniCurveMethod); + bAnyChanges = true; + } + + // Check if there are curve point differences + bool bUpdatePoints = false; + if (CurvePoints.Num() == InSpline->CurvePoints.Num()) + { + const int32 NumPoints = CurvePoints.Num(); + for (int32 Index = 0; Index < NumPoints; ++Index) + { + const FTransform& A = CurvePoints[Index]; + const FTransform& B = InSpline->CurvePoints[Index]; + + if (!A.Equals(B, 0.0f)) + { + bUpdatePoints = true; + break; + } + } + } + else + { + bUpdatePoints = true; + } + + // If there are curve point differences, update the points + if (bUpdatePoints) + { + InSpline->ResetCurvePoints(); + InSpline->ResetDisplayPoints(); + InSpline->CurvePoints = CurvePoints; + bAnyChanges = true; + } + + if (bAnyChanges) + InSpline->MarkChanged(true); } EHoudiniCurveType @@ -593,25 +688,76 @@ UHoudiniPublicAPICurveInput::ConvertAPIInputObjectAndAssignToInput(UObject* InAP // If the input is an API curve wrapper, convert it to a UHoudiniSplineComponent if (IsValid(InAPIInputObject) && InAPIInputObject->IsA() && IsValid(InHoudiniInput)) { - UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputComponent = nullptr; - const bool bAttachToParent = true; - const bool bAppendToInputArray = false; - bool bBlueprintStructureModified; - UHoudiniInputHoudiniSplineComponent* const NewHoudiniInputObject = InHoudiniInput->CreateHoudiniSplineInput(FromHoudiniSplineInputComponent, bAttachToParent, bAppendToInputArray, bBlueprintStructureModified); - if (IsValid(NewHoudiniInputObject)) + UHoudiniPublicAPICurveInputObject* const InAPICurveInputObject = Cast(InAPIInputObject); + + // If there is an existing input object at this index, and it is a HoudiniSplineComponent, then just update it + // otherwise, create a new input object wrapper + bool bCreateNew = false; + UHoudiniInputObject const* const CurrentHoudiniInputObject = InHoudiniInput->GetHoudiniInputObjectAt(InInputIndex); + UObject* const CurrentInputObject = InHoudiniInput->GetInputObjectAt(InInputIndex); + if (IsValid(CurrentInputObject) && CurrentInputObject->IsA() && + IsValid(CurrentHoudiniInputObject) && CurrentHoudiniInputObject->IsA()) { - UHoudiniSplineComponent* HoudiniSplineComponent = NewHoudiniInputObject->GetCurveComponent(); - if (IsValid(HoudiniSplineComponent)) + UHoudiniSplineComponent* CurrentSpline = Cast(CurrentInputObject); + if (IsValid(CurrentSpline)) + { + if (IsValid(InAPICurveInputObject)) + { + InAPICurveInputObject->CopyToHoudiniSplineComponent(CurrentSpline); + // Currently the CopyToHoudiniSplineComponent function does not return an indication of if anything + // actually changed, so we have to assume this is a change + + InHoudiniInput->MarkChanged(true); + } + Object = CurrentSpline; + } + else { - // Populate the HoudiniSplineComponent from the curve wrapper - Cast(InAPIInputObject)->CopyToHoudiniSplineComponent(HoudiniSplineComponent); - Object = HoudiniSplineComponent; + bCreateNew = true; + } + } + else + { + bCreateNew = true; + } + + if (bCreateNew) + { + // Replace any object that is already at this index: we remove the current input object first, then + // we create the new one + if (CurrentInputObject) + { + // Keep the space/index in the array, we're going to set the new input object at the same index + const bool bRemoveIndexFromArray = false; + InHoudiniInput->DeleteInputObjectAt(InInputIndex, bRemoveIndexFromArray); + InHoudiniInput->MarkChanged(true); + } + + UHoudiniInputHoudiniSplineComponent* FromHoudiniSplineInputComponent = nullptr; + const bool bAttachToParent = true; + const bool bAppendToInputArray = false; + bool bBlueprintStructureModified; + UHoudiniInputHoudiniSplineComponent* const NewHoudiniInputObject = InHoudiniInput->CreateHoudiniSplineInput( + FromHoudiniSplineInputComponent, bAttachToParent, bAppendToInputArray, bBlueprintStructureModified); + if (IsValid(NewHoudiniInputObject)) + { + UHoudiniSplineComponent* HoudiniSplineComponent = NewHoudiniInputObject->GetCurveComponent(); + if (IsValid(HoudiniSplineComponent)) + { + // Populate the HoudiniSplineComponent from the curve wrapper + if (IsValid(InAPICurveInputObject)) + InAPICurveInputObject->CopyToHoudiniSplineComponent(HoudiniSplineComponent); + Object = HoudiniSplineComponent; + } + } + + TArray* HoudiniInputObjectArray = InHoudiniInput->GetHoudiniInputObjectArray(InHoudiniInput->GetInputType()); + if (HoudiniInputObjectArray && HoudiniInputObjectArray->IsValidIndex(InInputIndex)) + { + (*HoudiniInputObjectArray)[InInputIndex] = IsValid(NewHoudiniInputObject) ? NewHoudiniInputObject : nullptr; + InHoudiniInput->MarkChanged(true); } } - - TArray* HoudiniInputObjectArray = InHoudiniInput->GetHoudiniInputObjectArray(InHoudiniInput->GetInputType()); - if (HoudiniInputObjectArray && HoudiniInputObjectArray->IsValidIndex(InInputIndex)) - (*HoudiniInputObjectArray)[InInputIndex] = IsValid(NewHoudiniInputObject) ? NewHoudiniInputObject : nullptr; } else { diff --git a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp index 50cad9825..7eb658fcb 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniPublicAPIObjectBase.cpp @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * @@ -25,6 +25,7 @@ */ #include "HoudiniPublicAPIObjectBase.h" +#include "HoudiniEngineRuntimePrivatePCH.h" UHoudiniPublicAPIObjectBase::UHoudiniPublicAPIObjectBase() : LastErrorMessage() diff --git a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp index e159cb270..2b3c71466 100644 --- a/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp +++ b/Source/HoudiniEngineEditor/Private/HoudiniSplineComponentVisualizer.cpp @@ -295,7 +295,7 @@ FHoudiniSplineComponentVisualizer::VisProxyHandleClick( EditedHoudiniSplineComponent = const_cast(HoudiniSplineComponent); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return false; @@ -386,7 +386,7 @@ bool FHoudiniSplineComponentVisualizer::HandleInputKey(FEditorViewportClient * ViewportClient, FViewport * Viewport, FKey Key, EInputEvent Event) { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return false; if (Key == EKeys::Enter) @@ -455,7 +455,7 @@ void FHoudiniSplineComponentVisualizer::EndEditing() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return; // Clear edited spline if the EndEditing() function is not called from postUndo @@ -476,7 +476,7 @@ FHoudiniSplineComponentVisualizer::GetWidgetLocation( FVector& OutLocation) const { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return false; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -522,7 +522,7 @@ FHoudiniSplineComponentVisualizer::HandleInputDelta( FVector& DeltaScale) { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!ViewportClient || !EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!ViewportClient || !IsValid(EditedHoudiniSplineComponent)) return false; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -602,7 +602,7 @@ FHoudiniSplineComponentVisualizer::GenerateContextMenu() const // Create the context menu section UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + if (IsValid(EditedHoudiniSplineComponent)) { MenuBuilder.AddMenuEntry( FHoudiniSplineComponentVisualizerCommands::Get().CommandAddControlPoint, @@ -642,7 +642,7 @@ int32 FHoudiniSplineComponentVisualizer::OnInsertControlPointWithoutUpdate() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return -1; TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; @@ -689,7 +689,7 @@ void FHoudiniSplineComponentVisualizer::OnInsertControlPoint() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return; int32 NewPointIndex = OnInsertControlPointWithoutUpdate(); @@ -715,7 +715,7 @@ void FHoudiniSplineComponentVisualizer::OnAddControlPoint() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -800,7 +800,7 @@ bool FHoudiniSplineComponentVisualizer::IsAddControlPointValid() const { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - return EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill() && + return IsValid(EditedHoudiniSplineComponent) && EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; } @@ -808,7 +808,7 @@ void FHoudiniSplineComponentVisualizer::OnDeleteControlPoint() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -851,7 +851,7 @@ bool FHoudiniSplineComponentVisualizer::IsDeleteControlPointValid() const { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return false; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -870,7 +870,7 @@ void FHoudiniSplineComponentVisualizer::OnDuplicateControlPoint() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return; TArray & EditedControlPointsIndexes = EditedHoudiniSplineComponent->EditedControlPointsIndexes; @@ -914,7 +914,7 @@ bool FHoudiniSplineComponentVisualizer::IsDuplicateControlPointValid() const { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if(!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill() + if(!IsValid(EditedHoudiniSplineComponent) || EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() == 0) return false; @@ -925,7 +925,7 @@ void FHoudiniSplineComponentVisualizer::OnDeselectAllControlPoints() { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + if (IsValid(EditedHoudiniSplineComponent)) EditedHoudiniSplineComponent->EditedControlPointsIndexes.Empty(); } @@ -933,7 +933,7 @@ bool FHoudiniSplineComponentVisualizer::IsDeselectAllControlPointsValid() const { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (EditedHoudiniSplineComponent && !EditedHoudiniSplineComponent->IsPendingKill()) + if (IsValid(EditedHoudiniSplineComponent)) return EditedHoudiniSplineComponent->EditedControlPointsIndexes.Num() > 0; return false; @@ -945,7 +945,7 @@ FHoudiniSplineComponentVisualizer::AddControlPointAfter( const int32 & nIndex) { UHoudiniSplineComponent* EditedHoudiniSplineComponent = GetEditedHoudiniSplineComponent(); - if (!EditedHoudiniSplineComponent || EditedHoudiniSplineComponent->IsPendingKill()) + if (!IsValid(EditedHoudiniSplineComponent)) return nIndex; const TArray & CurvePoints = EditedHoudiniSplineComponent->CurvePoints; diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h index 54c6ae8aa..23d71e30d 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIAssetWrapper.h @@ -218,8 +218,7 @@ struct HOUDINIENGINEEDITOR_API FHoudiniParameterTuple * wrapper can be created via UHoudiniPublicAPIAssetWrapper::CreateEmptyWrapper() and an HDA later instantiated and * assigned to the wrapper via UHoudiniPublicAPI::InstantiateAssetWithExistingWrapper(). * - * The wrapper provides functionality for interacting/manipulating a - * AHoudiniAssetActor / UHoudiniAssetComponent: + * The wrapper provides functionality for interacting/manipulating a AHoudiniAssetActor / UHoudiniAssetComponent: * - Get/Set Inputs * - Get/Set Parameters * - Manually initiate a cook/recook @@ -236,6 +235,15 @@ struct HOUDINIENGINEEDITOR_API FHoudiniParameterTuple * - Iterate over outputs and find the output assets * - Bake outputs * - PDG: Dirty all, cook outputs + * + * Important: In the current implementation of the plugin, nodes are cooked asynchronously. That means that cooking + * (including rebuilding the HDA and auto-cooks triggered from, for example, parameter changes) does not happen + * immediately. Functions in the API, such as Recook() and Rebuild(), do not block until the cook is complete, but + * instead immediately return after arranging for the cook to take place. This means that if a cook is triggered + * (either automatically, via parameter changes, or by calling Recook()) and there is a reliance on data that will only + * be available after the cook (such as an updated parameter interface, or the output objects of the cook), one of the + * delegates mentioned above (#OnPostProcessingDelegate or #OnPostCookDelegate, for example) would have to be used to + * execute the dependent code after the cook. */ UCLASS(BlueprintType, Blueprintable, Category="Houdini Engine|Public API") class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPublicAPIObjectBase @@ -362,13 +370,25 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool DeleteInstantiatedAsset(); - /** Rebuild the HDA node in Houdini. Returns true if the asset was successfully marked as needing to be rebuilt. */ + /** + * Marks the HDA as needing to be rebuilt in Houdini Engine and immediately returns. The rebuild happens + * asynchronously. If you need to take action after the rebuild and cook completes, one of the wrapper's delegates + * can be used, such as: OnPostCookDelegate or OnPostProcessingDelegate. + * + * @returns true If the HDA was successfully marked as needing to be rebuilt. + */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool Rebuild(); // Cooking - /** Recook the asset. Returns true if the asset was successfully marked as needing to be cooked. */ + /** + * Marks the HDA as needing to be cooked and immediately returns. The cook happens asynchronously. If you need + * to take action after the cook completes, one of the wrapper's delegates can be used, such as: + * OnPostCookDelegate or OnPostProcessingDelegate. + * + * @returns true If the HDA was successfully marked as needing to be cooked. + */ UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category="Houdini|Public API") bool Recook(); @@ -874,7 +894,7 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub bool SetInputAtIndex(const int32 InNodeInputIndex, const UHoudiniPublicAPIInput* InInput); /** - * Get the node input at the specific index and sets OutInput. This is a copy of the input structure. Changes + * Get the node input at the specific index and sets OutInput. This is a copy of the input structure. Changes to * properties in OutInput won't affect the instantiated HDA until a subsequent call to SetInputAtIndex. * @param InNodeInputIndex The index of the node input to get. * @param OutInput Copy of the input configuration and data for node input index InNodeInputIndex. @@ -894,7 +914,7 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub /** * Get all node inputs. * @param OutInputs All node inputs as a map, with the node input index as key. The input configuration is copied - * from instantiated asset, and changing an input property from the entry in this map will not affect the + * from the instantiated asset, and changing an input property from the entry in this map will not affect the * instantiated asset until a subsequent SetInputsAtIndices() call or SetInputAtIndex() call. * @return false if the wrapper is invalid. */ @@ -912,7 +932,8 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub bool SetInputParameter(const FName& InParameterName, const UHoudiniPublicAPIInput* InInput); /** - * Get a parameter-based input via parameter name. + * Get a parameter-based input via parameter name. This is a copy of the input structure. Changes to properties in + * OutInput won't affect the instantiated HDA until a subsequent call to SetInputParameter. * @param InParameterName The name of the input parameter. * @param OutInput A copy of the input configuration for InParameterName. * @return false if the wrapper is invalid, InParameterName is not a valid input parameter, or the current input @@ -931,8 +952,8 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetWrapper : public UHoudiniPub /** * Get a parameter-based inputs as a map - * @param OutInputs All parameter inputs as a map, with the input parameter name as key. The input configuration is copied - * from instantiated asset, and changing an input property from the entry in this map will not affect the + * @param OutInputs All parameter inputs as a map, with the input parameter name as key. The input configuration is + * copied from the instantiated asset, and changing an input property from the entry in this map will not affect the * instantiated asset until a subsequent SetInputParameters() call or SetInputParameter() call. * @return false if the wrapper is invalid. */ diff --git a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h index 6952b959e..3ac2bf80b 100644 --- a/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h +++ b/Source/HoudiniEngineEditor/Public/HoudiniPublicAPIInputTypes.h @@ -167,7 +167,7 @@ class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIInput : public UHoudiniPublicAPIO * API wrapper input class for geometry inputs. Derived from UHoudiniPublicAPIInput. */ UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPIGeoInput : public UHoudiniPublicAPIInput +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIGeoInput : public UHoudiniPublicAPIInput { GENERATED_BODY() @@ -249,7 +249,7 @@ enum class EHoudiniPublicAPICurveMethod : uint8 * API wrapper input class for curve inputs. Derived from UHoudiniPublicAPIInput. */ UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs | Input Objects") -class UHoudiniPublicAPICurveInputObject : public UHoudiniPublicAPIObjectBase +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPICurveInputObject : public UHoudiniPublicAPIObjectBase { GENERATED_BODY() @@ -399,7 +399,7 @@ class UHoudiniPublicAPICurveInputObject : public UHoudiniPublicAPIObjectBase * API wrapper input class for curve inputs. Derived from UHoudiniPublicAPIInput. */ UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPICurveInput : public UHoudiniPublicAPIInput +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPICurveInput : public UHoudiniPublicAPIInput { GENERATED_BODY() @@ -433,7 +433,7 @@ class UHoudiniPublicAPICurveInput : public UHoudiniPublicAPIInput * API wrapper input class for asset inputs. Derived from UHoudiniPublicAPIInput. */ UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPIAssetInput : public UHoudiniPublicAPIInput +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIAssetInput : public UHoudiniPublicAPIInput { GENERATED_BODY() @@ -459,7 +459,7 @@ class UHoudiniPublicAPIAssetInput : public UHoudiniPublicAPIInput * API wrapper input class for world inputs. Derived from UHoudiniPublicAPIGeoInput. */ UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPIWorldInput : public UHoudiniPublicAPIGeoInput +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPIWorldInput : public UHoudiniPublicAPIGeoInput { GENERATED_BODY() @@ -506,7 +506,7 @@ class UHoudiniPublicAPIWorldInput : public UHoudiniPublicAPIGeoInput * API wrapper input class for landscape inputs. Derived from UHoudiniPublicAPIInput. */ UCLASS(BlueprintType, Category="Houdini Engine | Public API | Inputs") -class UHoudiniPublicAPILandscapeInput : public UHoudiniPublicAPIInput +class HOUDINIENGINEEDITOR_API UHoudiniPublicAPILandscapeInput : public UHoudiniPublicAPIInput { GENERATED_BODY() diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp index cf7257fee..8bc9aa5f1 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.cpp @@ -25,6 +25,7 @@ */ #include "HoudiniAssetActor.h" +#include "HoudiniAssetComponent.h" #include "HoudiniAsset.h" #include "HoudiniPDGAssetLink.h" @@ -67,7 +68,7 @@ AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) // happens on copy / paste. ActorPropString->Empty(); - if (!CopiedActor || CopiedActor->IsPendingKill()) + if (!IsValid(CopiedActor)) { HOUDINI_LOG_WARNING(TEXT("Failed to import from copy: Duplicated actor not found")); return false; @@ -75,7 +76,7 @@ AHoudiniAssetActor::ShouldImport(FString * ActorPropString, bool IsMovingLevel) // Get Houdini component of an actor which is being copied. UHoudiniAssetComponent * CopiedActorHoudiniAssetComponent = CopiedActor->HoudiniAssetComponent; - if (!CopiedActorHoudiniAssetComponent || CopiedActorHoudiniAssetComponent->IsPendingKill()) + if (!IsValid(CopiedActorHoudiniAssetComponent)) return false; HoudiniAssetComponent->OnComponentClipboardCopy(CopiedActorHoudiniAssetComponent); @@ -101,10 +102,10 @@ AHoudiniAssetActor::GetReferencedContentObjects(TArray< UObject * >& Objects) co { Super::GetReferencedContentObjects(Objects); - if (HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill()) + if (IsValid(HoudiniAssetComponent)) { UHoudiniAsset* HoudiniAsset = HoudiniAssetComponent->GetHoudiniAsset(); - if (HoudiniAsset && !HoudiniAsset->IsPendingKill()) + if (IsValid(HoudiniAsset)) Objects.AddUnique(HoudiniAsset); } @@ -119,7 +120,7 @@ AHoudiniAssetActor::PostEditChangeProperty(FPropertyChangedEvent & PropertyChang Super::PostEditChangeProperty(PropertyChangedEvent); // Some property changes need to be forwarded to the component (ie Transform) - if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()) + if (!IsValid(HoudiniAssetComponent)) return; FProperty* Property = PropertyChangedEvent.MemberProperty; @@ -143,4 +144,10 @@ AHoudiniAssetActor::IsUsedForPreview() const return HasAnyFlags(RF_Transient); } +UHoudiniPDGAssetLink* +AHoudiniAssetActor::GetPDGAssetLink() const +{ + return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; +} + #undef LOCTEXT_NAMESPACE diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp index dc0abba90..7c34de333 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetBlueprintComponent.cpp @@ -165,7 +165,7 @@ UHoudiniAssetBlueprintComponent::CopyStateToTemplateComponent() InstanceOutput = Outputs[i]; //check(InstanceOutput) - if (!InstanceOutput || InstanceOutput->IsPendingKill()) + if (!IsValid(InstanceOutput)) continue; // Ensure that instance outputs won't delete houdini content. diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp index bebbcd8c8..ec24dd3d4 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.cpp @@ -145,11 +145,11 @@ UHoudiniAssetComponent::Serialize(FArchive& Ar) bool UHoudiniAssetComponent::ConvertLegacyData() { - if (!Version1CompatibilityHAC || Version1CompatibilityHAC->IsPendingKill()) + if (!IsValid(Version1CompatibilityHAC)) return false; // Set the Houdini Asset - if (!Version1CompatibilityHAC->HoudiniAsset || Version1CompatibilityHAC->HoudiniAsset->IsPendingKill()) + if (!IsValid(Version1CompatibilityHAC->HoudiniAsset)) return false; HoudiniAsset = Version1CompatibilityHAC->HoudiniAsset; @@ -184,7 +184,7 @@ UHoudiniAssetComponent::ConvertLegacyData() FoundOutput = Outputs.FindByPredicate( [InNewHGPO](UHoudiniOutput* Output) { return Output ? Output->HasHoudiniGeoPartObject(InNewHGPO) : false; }); - if (FoundOutput && *FoundOutput && !(*FoundOutput)->IsPendingKill()) + if (FoundOutput && IsValid(*FoundOutput)) { // FoundOutput is valid, add to it NewOutput = *FoundOutput; @@ -212,7 +212,7 @@ UHoudiniAssetComponent::ConvertLegacyData() bool bCreatedNew = false; UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) + if (!IsValid(NewOutput)) continue; // Add the HGPO if we've just created it @@ -237,10 +237,10 @@ UHoudiniAssetComponent::ConvertLegacyData() OutputObj.bProxyIsCurrent = false; // Handle the SMC for this SM / HGPO - if (LegacySM.Value && !LegacySM.Value->IsPendingKill()) + if (IsValid(LegacySM.Value)) { UStaticMeshComponent** FoundSMC = Version1CompatibilityHAC->StaticMeshComponents.Find(LegacySM.Value); - if (FoundSMC && *FoundSMC && !(*FoundSMC)->IsPendingKill()) + if (FoundSMC && IsValid(*FoundSMC)) OutputObj.OutputComponent = *FoundSMC; } @@ -262,7 +262,7 @@ UHoudiniAssetComponent::ConvertLegacyData() bool bCreatedNew = false; UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) + if (!IsValid(NewOutput)) continue; // Add the HGPO if we've just created it @@ -298,7 +298,7 @@ UHoudiniAssetComponent::ConvertLegacyData() // ... instancers for (auto& LegacyInstanceIn : Version1CompatibilityHAC->InstanceInputs) { - if (!LegacyInstanceIn || LegacyInstanceIn->IsPendingKill()) + if (!IsValid(LegacyInstanceIn)) continue; FHoudiniGeoPartObject InstancerHGPO = LegacyInstanceIn->HoudiniGeoPartObject.ConvertLegacyData(); @@ -326,7 +326,7 @@ UHoudiniAssetComponent::ConvertLegacyData() bool bCreatedNew = false; UHoudiniOutput* NewOutput = FindOrCreateOutput(InstancerHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) + if (!IsValid(NewOutput)) continue; // Add the HGPO if we've just created it @@ -426,7 +426,7 @@ UHoudiniAssetComponent::ConvertLegacyData() for (auto& LegacyCurve : Version1CompatibilityHAC->SplineComponents) { UHoudiniSplineComponent* CurSplineComp = LegacyCurve.Value; - if (!CurSplineComp || CurSplineComp->IsPendingKill()) + if (!IsValid(CurSplineComp)) continue; // TODO: Needed? @@ -441,7 +441,7 @@ UHoudiniAssetComponent::ConvertLegacyData() // Look for an output for that HGPO bool bCreatedNew = false; UHoudiniOutput* NewOutput = FindOrCreateOutput(CurHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) + if (!IsValid(NewOutput)) continue; // Add the HGPO if we've just created it @@ -482,7 +482,7 @@ UHoudiniAssetComponent::ConvertLegacyData() // ... Materials UHoudiniAssetComponentMaterials_V1* LegacyMaterials = Version1CompatibilityHAC->HoudiniAssetComponentMaterials; - if(LegacyMaterials && !LegacyMaterials->IsPendingKill()) + if(IsValid(LegacyMaterials)) { // Assignements: Apply to all outputs since they're not tied to an HGPO... for (auto& CurOutput : Outputs) @@ -505,7 +505,7 @@ UHoudiniAssetComponent::ConvertLegacyData() bool bCreatedNew = false; UHoudiniOutput* NewOutput = FindOrCreateOutput(NewHGPO, bCreatedNew); - if (!NewOutput || NewOutput->IsPendingKill()) + if (!IsValid(NewOutput)) continue; if (bCreatedNew) @@ -656,7 +656,7 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object bOutputless = false; bOutputTemplateGeos = false; - bUseOutputNodes = false; + bUseOutputNodes = true; PDGAssetLink = nullptr; @@ -695,6 +695,8 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object bRemoveOutputAfterBake = false; bRecenterBakedActors = false; bReplacePreviousBake = false; + + bAllowPlayInEditorRefinement = false; #endif // @@ -719,6 +721,8 @@ UHoudiniAssetComponent::UHoudiniAssetComponent(const FObjectInitializer & Object // Initialize the default SM Build settings with the plugin's settings default values StaticMeshBuildSettings = FHoudiniEngineRuntimeUtils::GetDefaultMeshBuildSettings(); + + } UHoudiniAssetComponent::~UHoudiniAssetComponent() @@ -887,7 +891,7 @@ void UHoudiniAssetComponent::SetHoudiniAsset(UHoudiniAsset * InHoudiniAsset) { // Check the asset validity - if (!InHoudiniAsset || InHoudiniAsset->IsPendingKill()) + if (!IsValid(InHoudiniAsset)) return; // If it is the same asset, do nothing. @@ -923,7 +927,7 @@ UHoudiniAssetComponent::NeedUpdateParameters() const // Go through all our parameters, return true if they have been updated for (auto CurrentParm : Parameters) { - if (!CurrentParm || CurrentParm->IsPendingKill()) + if (!IsValid(CurrentParm)) continue; if (!CurrentParm->HasChanged()) @@ -947,7 +951,7 @@ UHoudiniAssetComponent::NeedUpdateInputs() const // Go through all our inputs, return true if they have been updated for (auto CurrentInput : Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (!CurrentInput->HasChanged()) @@ -1002,7 +1006,7 @@ UHoudiniAssetComponent::NeedUpdate() const return false; // We must have a valid asset - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + if (!IsValid(HoudiniAsset)) return false; if (bForceNeedUpdate) @@ -1025,7 +1029,7 @@ UHoudiniAssetComponent::NeedUpdate() const // Go through all outputs, filter the editable nodes. Return true if they have been updated. for (auto CurrentOutput : Outputs) { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) + if (!IsValid(CurrentOutput)) continue; // We only care about editable outputs @@ -1070,7 +1074,7 @@ UHoudiniAssetComponent::PreventAutoUpdates() // Go through all our parameters, prevent them from triggering updates for (auto CurrentParm : Parameters) { - if (!CurrentParm || CurrentParm->IsPendingKill()) + if (!IsValid(CurrentParm)) continue; // Prevent the parm from triggering an update @@ -1080,7 +1084,7 @@ UHoudiniAssetComponent::PreventAutoUpdates() // Same with inputs for (auto CurrentInput : Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; // Prevent the input from triggering an update @@ -1090,7 +1094,7 @@ UHoudiniAssetComponent::PreventAutoUpdates() // Go through all outputs, filter the editable nodes. for (auto CurrentOutput : Outputs) { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) + if (!IsValid(CurrentOutput)) continue; // We only care about editable outputs @@ -1122,7 +1126,7 @@ UHoudiniAssetComponent::NeedOutputUpdate() const // Go through all outputs for (auto CurrentOutput : Outputs) { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) + if (!IsValid(CurrentOutput)) continue; for (const auto& InstOutput : CurrentOutput->GetInstancedOutputs()) @@ -1160,12 +1164,12 @@ UHoudiniAssetComponent::NotifyCookedToDownstreamAssets() // Remove the downstream connection by default, // unless we actually were properly connected to one of this HDa's input. bool bRemoveDownstream = true; - if (CurrentDownstreamHAC && !CurrentDownstreamHAC->IsPendingKill()) + if (IsValid(CurrentDownstreamHAC)) { // Go through the HAC's input for (auto& CurrentDownstreamInput : CurrentDownstreamHAC->Inputs) { - if (!CurrentDownstreamInput || CurrentDownstreamInput->IsPendingKill()) + if (!IsValid(CurrentDownstreamInput)) continue; EHoudiniInputType CurrentDownstreamInputType = CurrentDownstreamInput->GetInputType(); @@ -1204,7 +1208,7 @@ UHoudiniAssetComponent::NeedsToWaitForInputHoudiniAssets() { for (auto& CurrentInput : Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; EHoudiniInputType CurrentInputType = CurrentInput->GetInputType(); @@ -1575,7 +1579,7 @@ UHoudiniAssetComponent::UpdatePostDuplicate() for (auto & NextChild : Children) { - if (!NextChild || NextChild->IsPendingKill()) + if (!IsValid(NextChild)) continue; USceneComponent * ComponentToRemove = nullptr; @@ -1683,7 +1687,7 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) // Clear Parameters for (UHoudiniParameter*& CurrentParm : Parameters) { - if (CurrentParm && !CurrentParm->IsPendingKill()) + if (IsValid(CurrentParm)) { CurrentParm->ConditionalBeginDestroy(); } @@ -1702,7 +1706,7 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) // Clear Inputs for (UHoudiniInput*& CurrentInput : Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) @@ -1718,7 +1722,7 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) // Clear Output for (UHoudiniOutput*& CurrentOutput : Outputs) { - if (!CurrentOutput || CurrentOutput->IsPendingKill()) + if (!IsValid(CurrentOutput)) continue; if (CurrentOutput->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad)) @@ -1728,7 +1732,7 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) TArray & CurCreatedSocketActors = CurrentOutput->GetHoudiniCreatedSocketActors(); for (auto & CurCreatedActor : CurCreatedSocketActors) { - if (!CurCreatedActor || CurCreatedActor->IsPendingKill()) + if (!IsValid(CurCreatedActor)) continue; CurCreatedActor->Destroy(); @@ -1739,7 +1743,7 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) TArray & CurAttachedSocketActors = CurrentOutput->GetHoudiniAttachedSocketActors(); for (auto & CurAttachedSocketActor : CurAttachedSocketActors) { - if (!CurAttachedSocketActor || CurAttachedSocketActor->IsPendingKill()) + if (!IsValid(CurAttachedSocketActor)) continue; CurAttachedSocketActor->DetachFromActor(FDetachmentTransformRules::KeepRelativeTransform); @@ -1756,17 +1760,17 @@ UHoudiniAssetComponent::OnComponentDestroyed(bool bDestroyingHierarchy) continue; UStaticMesh* FoliageSM = FoliageHISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) + if (!IsValid(FoliageSM)) continue; // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, // if it is not, then we are just a "regular" HISMC AInstancedFoliageActor* InstancedFoliageActor = Cast(FoliageHISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + if (!IsValid(InstancedFoliageActor)) continue; UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) continue; if (IsInGameThread() && IsGarbageCollecting()) @@ -1857,7 +1861,7 @@ UHoudiniAssetComponent::OnRegister() for (TMap< UStaticMesh *, UStaticMeshComponent * >::TIterator Iter(StaticMeshComponents); Iter; ++Iter) { UStaticMeshComponent * StaticMeshComponent = Iter.Value(); - if (StaticMeshComponent && !StaticMeshComponent->IsPendingKill()) + if (IsValid(StaticMeshComponent)) { // Recreate render state. StaticMeshComponent->RecreateRenderState_Concurrent(); @@ -1870,7 +1874,7 @@ UHoudiniAssetComponent::OnRegister() // Instanced static meshes. for (auto& InstanceInput : InstanceInputs) { - if (!InstanceInput || InstanceInput->IsPendingKill()) + if (!IsValid(InstanceInput)) continue; // Recreate render state. @@ -1909,12 +1913,12 @@ UHoudiniAssetComponent::OnRegister() UHoudiniParameter* UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) { - if (!InOtherParam || InOtherParam->IsPendingKill()) + if (!IsValid(InOtherParam)) return nullptr; for (auto CurrentParam : Parameters) { - if (!CurrentParam || CurrentParam->IsPendingKill()) + if (!IsValid(CurrentParam)) continue; if (CurrentParam->Matches(*InOtherParam)) @@ -1927,12 +1931,12 @@ UHoudiniAssetComponent::FindMatchingParameter(UHoudiniParameter* InOtherParam) UHoudiniInput* UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) { - if (!InOtherInput || InOtherInput->IsPendingKill()) + if (!IsValid(InOtherInput)) return nullptr; for (auto CurrentInput : Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->Matches(*InOtherInput)) @@ -1945,12 +1949,12 @@ UHoudiniAssetComponent::FindMatchingInput(UHoudiniInput* InOtherInput) UHoudiniHandleComponent* UHoudiniAssetComponent::FindMatchingHandle(UHoudiniHandleComponent* InOtherHandle) { - if (!InOtherHandle || InOtherHandle->IsPendingKill()) + if (!IsValid(InOtherHandle)) return nullptr; for (auto CurrentHandle : HandleComponents) { - if (!CurrentHandle || CurrentHandle->IsPendingKill()) + if (!IsValid(CurrentHandle)) continue; if (CurrentHandle->Matches(*InOtherHandle)) @@ -1965,7 +1969,7 @@ UHoudiniAssetComponent::FindParameterByName(const FString& InParamName) { for (auto CurrentParam : Parameters) { - if (!CurrentParam || CurrentParam->IsPendingKill()) + if (!IsValid(CurrentParam)) continue; if (CurrentParam->GetParameterName().Equals(InParamName)) @@ -2088,7 +2092,7 @@ UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyC for (auto& Pair : CurOutput->GetOutputObjects()) { UStaticMesh* StaticMesh = Cast(Pair.Value.OutputObject); - if (!StaticMesh || StaticMesh->IsPendingKill()) + if (!IsValid(StaticMesh)) continue; SetStaticMeshGenerationProperties(StaticMesh); @@ -2113,7 +2117,7 @@ UHoudiniAssetComponent::PostEditChangeProperty(FPropertyChangedEvent & PropertyC for (TArray< USceneComponent * >::TConstIterator Iter(LocalAttachChildren); Iter; ++Iter) { UStaticMeshComponent * Component = Cast< UStaticMeshComponent >(*Iter); - if (!Component || Component->IsPendingKill()) + if (!IsValid(Component)) continue; /*const FHoudiniGeoPartObject * pGeoPart = StaticMeshes.FindKey(Component->GetStaticMesh()); @@ -2332,6 +2336,7 @@ UHoudiniAssetComponent::PostEditUndo() { Super::PostEditUndo(); + // TODO: PENDINGKILL replacement ? if (!IsPendingKill()) { // Make sure we are registered with the HER singleton @@ -2371,11 +2376,55 @@ UHoudiniAssetComponent::SetHasComponentTransformChanged(const bool& InHasChanged bHasComponentTransformChanged = InHasChanged; } +void UHoudiniAssetComponent::SetOutputNodeIds(const TArray& OutputNodes) +{ + NodeIdsToCook = OutputNodes; + // Remove stale entries from OutputNodeCookCounts: + TArray CachedNodeIds; + OutputNodeCookCounts.GetKeys(CachedNodeIds); + for(const int32 NodeId : CachedNodeIds) + { + if (!NodeIdsToCook.Contains(NodeId)) + { + OutputNodeCookCounts.Remove(NodeId); + } + } +} + +void UHoudiniAssetComponent::SetOutputNodeCookCount(const int& NodeId, const int& CookCount) +{ + OutputNodeCookCounts.Add(NodeId, CookCount); +} + +bool UHoudiniAssetComponent::HasOutputNodeChanged(const int& NodeId, const int& NewCookCount) +{ + if (!OutputNodeCookCounts.Contains(NodeId)) + { + return true; + } + if (OutputNodeCookCounts[NodeId] == NewCookCount) + { + return false; + } + return true; +} + +void UHoudiniAssetComponent::ClearOutputNodes() +{ + NodeIdsToCook.Empty(); + ClearOutputNodesCookCount(); +} + +void UHoudiniAssetComponent::ClearOutputNodesCookCount() +{ + OutputNodeCookCounts.Empty(); +} + void UHoudiniAssetComponent::SetPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) { // Check the object validity - if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) + if (!IsValid(InPDGAssetLink)) return; // If it is the same object, do nothing. @@ -2421,7 +2470,7 @@ UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& b // Query the bounds for all output objects for (auto & CurOutput : Outputs) { - if (!CurOutput || CurOutput->IsPendingKill()) + if (!IsValid(CurOutput)) continue; BoxBounds += CurOutput->GetBounds(); @@ -2430,7 +2479,7 @@ UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& b // Query the bounds for all our inputs for (auto & CurInput : Inputs) { - if (!CurInput || CurInput->IsPendingKill()) + if (!IsValid(CurInput)) continue; BoxBounds += CurInput->GetBounds(); @@ -2439,14 +2488,14 @@ UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& b // Query the bounds for all input parameters for (auto & CurParam : Parameters) { - if (!CurParam || CurParam->IsPendingKill()) + if (!IsValid(CurParam)) continue; if (CurParam->GetParameterType() != EHoudiniParameterType::Input) continue; UHoudiniParameterOperatorPath* InputParam = Cast(CurParam); - if (!CurParam || CurParam->IsPendingKill()) + if (!IsValid(CurParam)) continue; if (!InputParam->HoudiniInput.IsValid()) @@ -2458,7 +2507,7 @@ UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& b // Query the bounds for all our Houdini handles for (auto & CurHandleComp : HandleComponents) { - if (!CurHandleComp || CurHandleComp->IsPendingKill()) + if (!IsValid(CurHandleComp)) continue; BoxBounds += CurHandleComp->GetBounds(); @@ -2477,7 +2526,7 @@ UHoudiniAssetComponent::GetAssetBounds(UHoudiniInput* IgnoreInput, const bool& b USceneComponent * pChild = LocalAttachedChildren[Idx]; if (UStaticMeshComponent * StaticMeshComponent = Cast(pChild)) { - if (!StaticMeshComponent || StaticMeshComponent->IsPendingKill()) + if (!IsValid(StaticMeshComponent)) continue; FBox StaticMeshBounds = StaticMeshComponent->Bounds.GetBox(); @@ -2659,7 +2708,7 @@ UHoudiniAssetComponent::ApplyInputPresets() TArray InputArray; for (auto CurrentInput : Inputs) { - if (!CurrentInput || CurrentInput->IsPendingKill()) + if (!IsValid(CurrentInput)) continue; if (CurrentInput->GetInputType() != EHoudiniInputType::Curve) @@ -2670,7 +2719,7 @@ UHoudiniAssetComponent::ApplyInputPresets() for (TMap< UObject*, int32 >::TIterator IterToolPreset(InputPresets); IterToolPreset; ++IterToolPreset) { UObject * Object = IterToolPreset.Key(); - if (!Object || Object->IsPendingKill()) + if (!IsValid(Object)) continue; int32 InputNumber = IterToolPreset.Value(); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h index ba1be9cee..ce1a8eeb4 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniAssetComponent.h @@ -276,6 +276,21 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // void SetHasComponentTransformChanged(const bool& InHasChanged); + // Set an array of output nodes being tracked. + // This will remove any cook counts for nodes that are not in this list. + void SetOutputNodeIds(const TArray& OutputNodes); + TArray GetOutputNodeIds() const { return NodeIdsToCook; } + TMap GetOutputNodeCookCounts() const { return OutputNodeCookCounts; } + + // Store the latest cook count that was processed for this output node. + void SetOutputNodeCookCount(const int& NodeId, const int& CookCount); + // Compare the current node's cook count against the cached value. If they are different, return true. False otherwise. + bool HasOutputNodeChanged(const int& NodeId, const int& NewCookCount); + // Clear output nodes. This will also clear the output node cook counts. + void ClearOutputNodes(); + // Clear the cook counts for output nodes. This will trigger rebuild of data. + void ClearOutputNodesCookCount(); + // Set to True to force the next cook to not build a proxy mesh (regardless of global or override settings) and // instead build a UStaticMesh directly (if applicable for the output type). void SetNoProxyMeshNextCookRequested(bool bInNoProxyMeshNextCookRequested) { bNoProxyMeshNextCookRequested = bInNoProxyMeshNextCookRequested; } @@ -592,6 +607,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone UPROPERTY(Transient, DuplicateTransient) TArray NodeIdsToCook; + // Cook counts for nodes in the NodeIdsToCook array. + UPROPERTY(Transient, DuplicateTransient) + TMap OutputNodeCookCounts; + // List of dependent downstream HACs that have us as an asset input UPROPERTY(DuplicateTransient) TSet DownstreamHoudiniAssets; @@ -769,5 +788,19 @@ class HOUDINIENGINERUNTIME_API UHoudiniAssetComponent : public UPrimitiveCompone // // End: IHoudiniAssetStateEvents // + +#if WITH_EDITORONLY_DATA + +public: + // Sets whether this HDA is allowed to be cooked in PIE + // for the purposes of refinement. + void SetAllowPlayInEditorRefinement(bool bEnabled) { bAllowPlayInEditorRefinement = bEnabled; } + bool IsPlayInEditorRefinementAllowed() const { return bAllowPlayInEditorRefinement; } + +protected: + UPROPERTY(Transient, DuplicateTransient) + bool bAllowPlayInEditorRefinement; + +#endif }; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp index 42a5c38b3..c1df7f412 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniCompatibilityHelpers.cpp @@ -154,7 +154,7 @@ UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) for (TMap::TIterator IterParams(Parameters); IterParams; ++IterParams) { UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); - if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill()) + if (!IsValid(HoudiniAssetParameter)) continue; if (HoudiniAssetParameter->GetFName() != NAME_None) @@ -182,7 +182,7 @@ UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) for (TMap::TIterator IterParams(Parameters); IterParams; ++IterParams) { UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value(); - if (HoudiniAssetParameter && !HoudiniAssetParameter->IsPendingKill()) + if (IsValid(HoudiniAssetParameter)) ParameterByName.Add(HoudiniAssetParameter->ParameterName, HoudiniAssetParameter); } } @@ -199,7 +199,7 @@ UHoudiniAssetComponent_V1::Serialize(FArchive & Ar) for (int32 InputIdx = 0; InputIdx < Inputs.Num(); ++InputIdx) { UHoudiniAssetInput_V1 * HoudiniAssetInput = Inputs[InputIdx]; - if (HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill()) + if (IsValid(HoudiniAssetInput)) Inputs[InputIdx]->SetHoudiniAssetComponent(this); } } @@ -639,7 +639,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) { // Create a new InputObject wrapper UObject* CurObject = InputObjects[AtIndex]; - if (!CurObject || CurObject->IsPendingKill()) + if (!IsValid(CurObject)) continue; UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(CurObject, Input, FString::FromInt(AtIndex + 1)); @@ -658,7 +658,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) } } } - else if (InputType == EHoudiniInputType::Asset && InputAssetComponent != nullptr && !InputAssetComponent->IsPendingKill()) + else if (InputType == EHoudiniInputType::Asset && IsValid(InputAssetComponent)) { // Get the asset input object array TArray* AssetInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); @@ -667,7 +667,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent // We can simply use the v1's HAC outer for that UHoudiniAssetComponent* InputHAC = Cast(InputAssetComponent->GetOuter()); - if (InputHAC && !InputHAC->IsPendingKill()) + if (IsValid(InputHAC)) { // Create a new InputObject wrapper UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputHAC, Input, FString::FromInt(0)); @@ -685,7 +685,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) TArray* CurveInputObjectsPtr = Input->GetHoudiniInputObjectArray(InputType); if (ensure(CurveInputObjectsPtr)) { - if (InputCurve && !InputCurve->IsPendingKill()) + if (IsValid(InputCurve)) { // Create a new InputObject wrapper UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InputCurve, Input, FString::FromInt(0)); @@ -715,7 +715,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) // Find the V2 HAC that matches the V1_HAC pointed by InputAssetComponent // We can simply use the v1's HAC outer for that ALandscapeProxy* InLandscape = InputLandscapeProxy.Get(); - if (InLandscape && !InLandscape->IsPendingKill()) + if (IsValid(InLandscape)) { // Create a new InputObject wrapper UHoudiniInputObject* NewInputObject = UHoudiniInputObject::CreateTypedInputObject(InLandscape, Input, FString::FromInt(0)); @@ -764,7 +764,7 @@ UHoudiniAssetInput::ConvertLegacyInput(UObject* InOuter) } } - if(!CurActor || CurActor->IsPendingKill()) + if(!IsValid(CurActor)) continue; // Create a new InputObject wrapper for the actor @@ -1039,7 +1039,7 @@ UHoudiniHandleComponent_V1::ConvertLegacyData(UObject* Outer) bool UHoudiniHandleComponent_V1::UpdateFromLegacyData(UHoudiniHandleComponent* NewHC) { - if (!NewHC || NewHC->IsPendingKill()) + if (!IsValid(NewHC)) return false; // TODO @@ -1110,7 +1110,7 @@ UHoudiniSplineComponent_V1::ConvertLegacyData(UObject* Outer) bool UHoudiniSplineComponent_V1::UpdateFromLegacyData(UHoudiniSplineComponent* NewSpline) { - if (!NewSpline || NewSpline->IsPendingKill()) + if (!IsValid(NewSpline)) return false; NewSpline->SetFlags(RF_Transactional); @@ -1242,7 +1242,7 @@ UHoudiniAssetParameter::ConvertLegacyData(UObject* Outer) void UHoudiniAssetParameter::CopyLegacyParameterData(UHoudiniParameter* InNewParm) { - if (!InNewParm || InNewParm->IsPendingKill()) + if (!IsValid(InNewParm)) return; InNewParm->Name = ParameterName; @@ -1763,7 +1763,7 @@ UHoudiniMeshSplitInstancerComponent_V1::Serialize(FArchive & Ar) bool UHoudiniMeshSplitInstancerComponent_V1::UpdateFromLegacyData(UHoudiniMeshSplitInstancerComponent* NewMSIC) { - if (!NewMSIC || NewMSIC->IsPendingKill()) + if (!IsValid(NewMSIC)) return false; NewMSIC->Instances = Instances; @@ -1791,7 +1791,7 @@ UHoudiniInstancedActorComponent_V1::Serialize(FArchive & Ar) bool UHoudiniInstancedActorComponent_V1::UpdateFromLegacyData(UHoudiniInstancedActorComponent* NewIAC) { - if (!NewIAC || NewIAC->IsPendingKill()) + if (!IsValid(NewIAC)) return false; //NewIAC->SetInstancedObject(InstancedAsset); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp index a10eb445d..b84148cb8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntime.cpp @@ -124,7 +124,7 @@ FHoudiniEngineRuntime::CleanUpRegisteredHoudiniComponents() } UHoudiniAssetComponent* CurrentHAC = Ptr.Get(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) + if (!IsValid(CurrentHAC)) { UnRegisterHoudiniComponent(Idx); continue; @@ -150,7 +150,7 @@ FHoudiniEngineRuntime::RegisterHoudiniComponent(UHoudiniAssetComponent* HAC, boo if (!FHoudiniEngineRuntime::IsInitialized()) return; - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; // RF_Transient indicates a temporary/preview object @@ -201,7 +201,7 @@ FHoudiniEngineRuntime::UnRegisterHoudiniComponent(UHoudiniAssetComponent* HAC) if (!IsInitialized()) return; - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return; // Calling GetPathName here may lead to some crashes due to invalid outers... diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h index 83ffaa5f1..586655f58 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimePrivatePCH.h @@ -253,6 +253,7 @@ enum HAPI_UNREAL_NodeFlags // Various variable names used to store meta information in generated packages. // More in HoudiniEnginePrivatePCH.h #define HAPI_UNREAL_PACKAGE_META_TEMP_GUID TEXT( "HoudiniPackageTempGUID" ) +#define HAPI_UNREAL_PACKAGE_META_COMPONENT_GUID TEXT( "HoudiniComponentGUID" ) // Default PDG Filters #define HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER "HE_"; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp index ca8e448cd..bc7f226c2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniEngineRuntimeUtils.cpp @@ -63,7 +63,7 @@ FHoudiniEngineRuntimeUtils::GetBoundingBoxesFromActors(const TArray InA for (auto CurrentActor : InActors) { - if (!CurrentActor || CurrentActor->IsPendingKill()) + if (!IsValid(CurrentActor)) continue; OutBBoxes.Add(CurrentActor->GetComponentsBoundingBox(true, true)); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp index 143b3a94d..6e7f7a4d2 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp @@ -321,7 +321,7 @@ bool FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex) { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; // Get the Property name @@ -354,7 +354,7 @@ FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( if (PropertyName.Equals("CollisionEnabled", ESearchCase::IgnoreCase)) { UPrimitiveComponent* PC = Cast(InObject); - if (PC && !PC->IsPendingKill()) + if (IsValid(PC)) { FString StringValue = InPropertyAttribute.GetStringValue(AtIndex); if (StringValue.Equals("NoCollision", ESearchCase::IgnoreCase)) @@ -385,7 +385,7 @@ FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( if (PropertyName.Equals("CastShadow", ESearchCase::IgnoreCase)) { UPrimitiveComponent* Component = Cast< UPrimitiveComponent >(InObject); - if (Component && !Component->IsPendingKill()) + if (IsValid(Component)) { bool Value = InPropertyAttribute.GetBoolValue(AtIndex); Component->SetCastShadow(Value); @@ -398,7 +398,7 @@ FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( if (PropertyName.Contains("Tags")) { UActorComponent* AC = Cast< UActorComponent >(InObject); - if (AC && !AC->IsPendingKill()) + if (IsValid(AC)) { FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex)); if (!AC->ComponentTags.Contains(NameAttr)) @@ -442,7 +442,7 @@ FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject( // Try to match to source model properties when possible if (UStaticMesh* SM = Cast(InObject)) { - if (SM && !SM->IsPendingKill() && SM->GetNumSourceModels() > AtIndex) + if (IsValid(SM) && SM->GetNumSourceModels() > AtIndex) { bool bFoundProperty = false; TryToFindProperty(&SM->GetSourceModel(AtIndex), SM->GetSourceModel(AtIndex).StaticStruct(), PropertyName, FoundProperty, bFoundProperty, OutContainer); @@ -474,14 +474,14 @@ FHoudiniGenericAttribute::FindPropertyOnObject( void*& OutContainer) { #if WITH_EDITOR - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; if (InPropertyName.IsEmpty()) return false; UClass* ObjectClass = InObject->GetClass(); - if (!ObjectClass || ObjectClass->IsPendingKill()) + if (!IsValid(ObjectClass)) return false; // Set the result pointer to null @@ -535,7 +535,7 @@ FHoudiniGenericAttribute::FindPropertyOnObject( { // Walk the structs' properties and try to find the one we're looking for UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) + if (!IsValid(Struct)) continue; for (TFieldIterator It(Struct); It; ++It) @@ -588,7 +588,7 @@ FHoudiniGenericAttribute::FindPropertyOnObject( // Handle common properties nested in classes // Static Meshes UStaticMesh* SM = Cast(InObject); - if (SM && !SM->IsPendingKill()) + if (IsValid(SM)) { if (SM->GetBodySetup() && FindPropertyOnObject( SM->GetBodySetup(), InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer)) @@ -611,7 +611,7 @@ FHoudiniGenericAttribute::FindPropertyOnObject( // For Actors, parse their components AActor* Actor = Cast(InObject); - if (Actor && !Actor->IsPendingKill()) + if (IsValid(Actor)) { TArray AllComponents; Actor->GetComponents(AllComponents, true); @@ -619,7 +619,7 @@ FHoudiniGenericAttribute::FindPropertyOnObject( int32 CompIdx = 0; for (USceneComponent * SceneComponent : AllComponents) { - if (!SceneComponent || SceneComponent->IsPendingKill()) + if (!IsValid(SceneComponent)) continue; if (FindPropertyOnObject( @@ -652,7 +652,7 @@ FHoudiniGenericAttribute::TryToFindProperty( if (!InContainer) return false; - if (!InStruct || InStruct->IsPendingKill()) + if (!IsValid(InStruct)) return false; if (InPropertyName.IsEmpty()) @@ -688,7 +688,7 @@ FHoudiniGenericAttribute::TryToFindProperty( { // Walk the structs' properties and try to find the one we're looking for UScriptStruct* Struct = StructProperty->Struct; - if (!Struct || Struct->IsPendingKill()) + if (!IsValid(Struct)) continue; TryToFindProperty( @@ -724,7 +724,7 @@ FHoudiniGenericAttribute::ModifyPropertyValueOnObject( void* InContainer, const int32& InAtIndex) { - if (!InObject || InObject->IsPendingKill() || !FoundProperty) + if (!IsValid(InObject) || !FoundProperty) return false; // Determine the container to use (either InContainer if specified, or InObject) @@ -1003,12 +1003,16 @@ FHoudiniGenericAttribute::ModifyPropertyValueOnObject( ValuePtr = ArrayHelper->GetRawPtr(TupleIndex); else ValuePtr = InnerProperty->ContainerPtrToValuePtr(Container, TupleIndex); - + if (ValuePtr) { - TSoftObjectPtr ValueObjectPtr; - ValueObjectPtr = Value; - UObject* ValueObject = ValueObjectPtr.LoadSynchronous(); + // Using TryLoad() over LoadSynchronous() as the later could crash with invalid path + UObject* ValueObject = nullptr; + FSoftObjectPath ObjPath(Value); + if(ObjPath.IsValid()) + { + ValueObject = ObjPath.TryLoad(); + } // Ensure the ObjectProperty class matches the ValueObject that we just loaded if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass))) @@ -1059,7 +1063,7 @@ FHoudiniGenericAttribute::GetPropertyValueFromObject( FHoudiniGenericAttribute& InGenericAttribute, const int32& InAtIndex) { - if (!InObject || InObject->IsPendingKill() || !InFoundProperty) + if (!IsValid(InObject) || !InFoundProperty) return false; // Determine the container to use (either InContainer if specified, or InObject) @@ -1321,7 +1325,7 @@ FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty( int32& OutAttributeTupleSize, EAttribStorageType& OutAttributeStorageType) { - if (!InObject || InObject->IsPendingKill() || !InFoundProperty) + if (!IsValid(InObject) || !InFoundProperty) return false; // Determine the container to use (either InContainer if specified, or InObject) diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h index f0dd12a9e..95fff3c08 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniGeoPartObject.h @@ -342,6 +342,13 @@ struct HOUDINIENGINERUNTIME_API FHoudiniGeoPartObject UPROPERTY() FString VolumeName; + UPROPERTY() + bool bHasEditLayers; + + // Name of edit layer + UPROPERTY() + FString VolumeLayerName; + // UPROPERTY() int32 VolumeTileIndex; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp index d53181865..78f34bf79 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.cpp @@ -49,7 +49,7 @@ #include "Components/StaticMeshComponent.h" #include "Components/InstancedStaticMeshComponent.h" #include "Landscape.h" - +#include "LandscapeInfo.h" #if WITH_EDITOR #include "Kismet2/BlueprintEditorUtils.h" @@ -87,6 +87,7 @@ UHoudiniInput::UHoudiniInput() , bLandscapeExportLighting(false) , bLandscapeExportNormalizedUVs(false) , bLandscapeExportTileUVs(false) + , bCanDeleteHoudiniNodes(true) { Name = TEXT(""); Label = TEXT(""); @@ -156,7 +157,7 @@ void UHoudiniInput::PostEditUndo() // The input array will be empty when undo adding asset (only support single asset input object in an input now) for (auto & NextAssetInputObj : *InputObjectsPtr) { - if (!NextAssetInputObj || NextAssetInputObj->IsPendingKill()) + if (!IsValid(NextAssetInputObj)) continue; NextAssetInputObj->MarkChanged(true); @@ -177,7 +178,7 @@ void UHoudiniInput::PostEditUndo() CreatedDataNodeIds.Empty(); if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + MarkInputNodeAsPendingDelete(); InputNodeId = -1; } } @@ -189,11 +190,11 @@ void UHoudiniInput::PostEditUndo() for (auto& NextInput : *GetHoudiniInputObjectArray(Type)) { UHoudiniInputHoudiniSplineComponent* SplineInput = Cast< UHoudiniInputHoudiniSplineComponent>(NextInput); - if (!SplineInput || SplineInput->IsPendingKill()) + if (!IsValid(SplineInput)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = SplineInput->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; USceneComponent* OuterComponent = Cast(GetOuter()); @@ -214,24 +215,24 @@ void UHoudiniInput::PostEditUndo() TArray< USceneComponent* > childActor; UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) + if (IsValid(OuterHAC)) childActor = OuterHAC->GetAttachChildren(); // Undo delete input objects action for (int Index = 0; Index < GetNumberOfInputObjects(); ++Index) { UHoudiniInputObject* InputObject = (*InputObjectsPtr)[Index]; - if (!InputObject || InputObject->IsPendingKill()) + if (!IsValid(InputObject)) continue; UHoudiniInputHoudiniSplineComponent * HoudiniSplineInputObject = Cast(InputObject); - if (!HoudiniSplineInputObject || HoudiniSplineInputObject->IsPendingKill()) + if (!IsValid(HoudiniSplineInputObject)) continue; UHoudiniSplineComponent* SplineComponent = HoudiniSplineInputObject->GetCurveComponent(); - if (!SplineComponent || SplineComponent->IsPendingKill()) + if (!IsValid(SplineComponent)) continue; // If the last change deleted this curve input, recreate this Houdini Spline input. @@ -247,7 +248,7 @@ void UHoudiniInput::PostEditUndo() UHoudiniSplineComponent * ReconstructedSpline = NewObject( GetOuter(), UHoudiniSplineComponent::StaticClass()); - if (!ReconstructedSpline || ReconstructedSpline->IsPendingKill()) + if (!IsValid(ReconstructedSpline)) continue; ReconstructedSpline->SetFlags(RF_Transactional); @@ -279,11 +280,11 @@ void UHoudiniInput::PostEditUndo() { bUndoInsert = true; UHoudiniInputHoudiniSplineComponent* SplineInputComponent = LastInsertedInputs[Index]; - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + if (!IsValid(SplineInputComponent)) continue; UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; HoudiniSplineComponent->DestroyComponent(); @@ -297,11 +298,11 @@ void UHoudiniInput::PostEditUndo() UHoudiniInputObject* NextInputObject = LastUndoDeletedInputs[Index]; UHoudiniInputHoudiniSplineComponent* SplineInputComponent = Cast(NextInputObject); - if (!SplineInputComponent || SplineInputComponent->IsPendingKill()) + if (!IsValid(SplineInputComponent)) continue; UHoudiniSplineComponent* HoudiniSplineComponent = SplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || SplineInputComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; FDetachmentTransformRules DetachTransRules(EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, EDetachmentRule::KeepRelative, false); @@ -334,11 +335,11 @@ UHoudiniInput::GetBounds() const for (int32 Idx = 0; Idx < CurveInputObjects.Num(); ++Idx) { const UHoudiniInputHoudiniSplineComponent* CurInCurve = Cast(CurveInputObjects[Idx]); - if (!CurInCurve || CurInCurve->IsPendingKill()) + if (!IsValid(CurInCurve)) continue; UHoudiniSplineComponent* CurCurve = CurInCurve->GetCurveComponent(); - if (!CurCurve || CurCurve->IsPendingKill()) + if (!IsValid(CurCurve)) continue; FBox CurCurveBound(ForceInitToZero); @@ -349,7 +350,7 @@ UHoudiniInput::GetBounds() const UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) + if (IsValid(OuterHAC)) BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); } } @@ -360,11 +361,11 @@ UHoudiniInput::GetBounds() const for (int32 Idx = 0; Idx < AssetInputObjects.Num(); ++Idx) { UHoudiniInputHoudiniAsset* CurInAsset = Cast(AssetInputObjects[Idx]); - if (!CurInAsset || CurInAsset->IsPendingKill()) + if (!IsValid(CurInAsset)) continue; UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) + if (!IsValid(CurInHAC)) continue; BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); @@ -377,10 +378,10 @@ UHoudiniInput::GetBounds() const for (int32 Idx = 0; Idx < WorldInputObjects.Num(); ++Idx) { UHoudiniInputActor* CurInActor = Cast(WorldInputObjects[Idx]); - if (CurInActor && !CurInActor->IsPendingKill()) + if (IsValid(CurInActor)) { AActor* Actor = CurInActor->GetActor(); - if (!Actor || Actor->IsPendingKill()) + if (!IsValid(Actor)) continue; FVector Origin, Extent; @@ -392,10 +393,10 @@ UHoudiniInput::GetBounds() const { // World Input now also support HoudiniAssets UHoudiniInputHoudiniAsset* CurInAsset = Cast(WorldInputObjects[Idx]); - if (CurInAsset && !CurInAsset->IsPendingKill()) + if (IsValid(CurInAsset)) { UHoudiniAssetComponent* CurInHAC = CurInAsset->GetHoudiniAssetComponent(); - if (!CurInHAC || CurInHAC->IsPendingKill()) + if (!IsValid(CurInHAC)) continue; BoxBounds += CurInHAC->GetAssetBounds(nullptr, false); @@ -411,11 +412,11 @@ UHoudiniInput::GetBounds() const for (int32 Idx = 0; Idx < LandscapeInputObjects.Num(); ++Idx) { UHoudiniInputLandscape* CurInLandscape = Cast(LandscapeInputObjects[Idx]); - if (!CurInLandscape || CurInLandscape->IsPendingKill()) + if (!IsValid(CurInLandscape)) continue; ALandscapeProxy* CurLandscape = CurInLandscape->GetLandscapeProxy(); - if (!CurLandscape || CurLandscape->IsPendingKill()) + if (!IsValid(CurLandscape)) continue; FVector Origin, Extent; @@ -435,6 +436,91 @@ UHoudiniInput::GetBounds() const return BoxBounds; } +void UHoudiniInput::UpdateLandscapeInputSelection() +{ + LandscapeSelectedComponents.Reset(); + if (!bLandscapeExportSelectionOnly) return; + +#if WITH_EDITOR + for (UHoudiniInputObject* NextInputObj : LandscapeInputObjects) + { + UHoudiniInputLandscape* CurrentInputLandscape = Cast(NextInputObj); + if (!CurrentInputLandscape) + continue; + + ALandscapeProxy* CurrentInputLandscapeProxy = CurrentInputLandscape->GetLandscapeProxy(); + if (!CurrentInputLandscapeProxy) + continue; + + // Get selected components if bLandscapeExportSelectionOnly or bLandscapeAutoSelectComponent is true + FBox Bounds(ForceInitToZero); + if ( bLandscapeAutoSelectComponent ) + { + // Get our asset's or our connected input asset's bounds + UHoudiniAssetComponent* AssetComponent = Cast(GetOuter()); + if (IsValid(AssetComponent)) + { + Bounds = AssetComponent->GetAssetBounds(this, true); + } + } + + if ( bLandscapeExportSelectionOnly ) + { + const ULandscapeInfo * LandscapeInfo = CurrentInputLandscapeProxy->GetLandscapeInfo(); + if ( IsValid(LandscapeInfo) ) + { + // Get the currently selected components + LandscapeSelectedComponents = LandscapeInfo->GetSelectedComponents(); + } + + if ( bLandscapeAutoSelectComponent && LandscapeSelectedComponents.Num() <= 0 && Bounds.IsValid ) + { + // We'll try to use the asset bounds to automatically "select" the components + for ( int32 ComponentIdx = 0; ComponentIdx < CurrentInputLandscapeProxy->LandscapeComponents.Num(); ComponentIdx++ ) + { + ULandscapeComponent * LandscapeComponent = CurrentInputLandscapeProxy->LandscapeComponents[ ComponentIdx ]; + if ( !IsValid(LandscapeComponent) ) + continue; + + FBoxSphereBounds WorldBounds = LandscapeComponent->CalcBounds( LandscapeComponent->GetComponentTransform()); + + if ( Bounds.IntersectXY( WorldBounds.GetBox() ) ) + LandscapeSelectedComponents.Add( LandscapeComponent ); + } + + int32 Num = LandscapeSelectedComponents.Num(); + HOUDINI_LOG_MESSAGE( TEXT("Landscape input: automatically selected %d components within the asset's bounds."), Num ); + } + } + else + { + // Add all the components of the landscape to the selected set + ULandscapeInfo* LandscapeInfo = CurrentInputLandscapeProxy->GetLandscapeInfo(); + if (LandscapeInfo) + { + LandscapeInfo->ForAllLandscapeComponents([&](ULandscapeComponent* Component) + { + LandscapeSelectedComponents.Add(Component); + }); + } + } + + CurrentInputLandscape->MarkChanged(true); + + } +#endif +} + +void +UHoudiniInput::MarkInputNodeAsPendingDelete() +{ + if (InputNodeId < 0) + return; + + InputNodesPendingDelete.Add(InputNodeId); + InputNodeId = -1; +} + FString UHoudiniInput::InputTypeToString(const EHoudiniInputType& InInputType) { @@ -679,13 +765,13 @@ UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlue { UHoudiniInputHoudiniSplineComponent * CurrentInputHoudiniSpline = Cast(CurrentInput); - if (!CurrentInputHoudiniSpline || CurrentInputHoudiniSpline->IsPendingKill()) + if (!IsValid(CurrentInputHoudiniSpline)) continue; UHoudiniSplineComponent * HoudiniSplineComponent = CurrentInputHoudiniSpline->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) continue; HoudiniSplineComponent->Modify(); @@ -743,12 +829,12 @@ UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlue { UHoudiniInputObject* InputObj = (*InputObjectsArray)[Idx]; - if (!InputObj || InputObj->IsPendingKill()) + if (!IsValid(InputObj)) continue; UHoudiniInputLandscape* InputLandscape = Cast(InputObj); - if (!InputLandscape || InputLandscape->IsPendingKill()) + if (!IsValid(InputLandscape)) continue; // do something? @@ -790,11 +876,11 @@ UHoudiniInput::SetInputType(const EHoudiniInputType& InInputType, bool& bOutBlue for (auto& CurrentInput : *GetHoudiniInputObjectArray(Type)) { UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(CurrentInput); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + if (!IsValid(HoudiniAssetInput)) continue; UHoudiniAssetComponent* CurrentHAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!CurrentHAC || CurrentHAC->IsPendingKill()) + if (!IsValid(CurrentHAC)) continue; CurrentHAC->AddDownstreamHoudiniAsset(OuterHAC); @@ -889,11 +975,11 @@ UHoudiniInput::CreateNewCurveInputObject(bool& bOutBlueprintStructureModified) return nullptr; UHoudiniInputHoudiniSplineComponent* NewCurveInputObject = CreateHoudiniSplineInput(nullptr, true, false, bOutBlueprintStructureModified); - if (!NewCurveInputObject || NewCurveInputObject->IsPendingKill()) + if (!IsValid(NewCurveInputObject)) return nullptr; UHoudiniSplineComponent * HoudiniSplineComponent = NewCurveInputObject->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return nullptr; // Default Houdini spline component input should not be visible at initialization @@ -924,7 +1010,7 @@ UHoudiniInput::MarkAllInputObjectsChanged(const bool& bInChanged) { for (auto CurInputObject : *NewInputObjects) { - if (CurInputObject && !CurInputObject->IsPendingKill()) + if (IsValid(CurInputObject)) CurInputObject->MarkChanged(bInChanged); } } @@ -1056,7 +1142,7 @@ void UHoudiniInput::InvalidateData() if (Type != EHoudiniInputType::Asset) { if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + MarkInputNodeAsPendingDelete(); } InputNodeId = -1; @@ -1204,7 +1290,7 @@ UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * Fr UHoudiniInputObject * NewInputObject = UHoudiniInputHoudiniSplineComponent::Create( nullptr, OuterObj, HoudiniSplineName.ToString()); - if (!NewInputObject || NewInputObject->IsPendingKill()) + if (!IsValid(NewInputObject)) return nullptr; HoudiniSplineInput = Cast(NewInputObject); @@ -1214,7 +1300,7 @@ UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * Fr HoudiniSplineComponent = NewObject( HoudiniSplineInput, UHoudiniSplineComponent::StaticClass()); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return nullptr; HoudiniSplineInput->Update(HoudiniSplineComponent); @@ -1287,7 +1373,7 @@ UHoudiniInput::CreateHoudiniSplineInput(UHoudiniInputHoudiniSplineComponent * Fr HoudiniSplineInput = FromHoudiniSplineInputComponent; HoudiniSplineComponent = FromHoudiniSplineInputComponent->GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) return nullptr; // Attach the new Houdini spline component to it's owner. @@ -1530,7 +1616,7 @@ UObject* UHoudiniInput::GetInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) { UHoudiniInputObject* HoudiniInputObject = GetHoudiniInputObjectAt(InType, AtIndex); - if (!HoudiniInputObject || HoudiniInputObject->IsPendingKill()) + if (!IsValid(HoudiniInputObject)) return nullptr; return HoudiniInputObject->GetObject(); @@ -1554,13 +1640,13 @@ UHoudiniInput::InsertInputObjectAt(const EHoudiniInputType& InType, const int32& } void -UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex) +UHoudiniInput::DeleteInputObjectAt(const int32& AtIndex, const bool bInRemoveIndexFromArray) { - DeleteInputObjectAt(Type, AtIndex); + DeleteInputObjectAt(Type, AtIndex, bInRemoveIndexFromArray); } void -UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex) +UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, const bool bInRemoveIndexFromArray) { TArray* InputObjectsPtr = GetHoudiniInputObjectArray(InType); if (!InputObjectsPtr) @@ -1607,7 +1693,7 @@ UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& MarkChanged(true); UHoudiniInputObject* InputObjectToDelete = (*InputObjectsPtr)[AtIndex]; - if (InputObjectToDelete && !InputObjectToDelete->IsPendingKill()) + if (IsValid(InputObjectToDelete)) { // Mark the input object's nodes for deletion InputObjectToDelete->InvalidateData(); @@ -1616,14 +1702,21 @@ UHoudiniInput::DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& MarkDataUploadNeeded(true); } - InputObjectsPtr->RemoveAt(AtIndex); + if (bInRemoveIndexFromArray) + { + InputObjectsPtr->RemoveAt(AtIndex); - // Delete the merge node when all the input objects are deleted. - if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) + // Delete the merge node when all the input objects are deleted. + if (InputObjectsPtr->Num() == 0 && InputNodeId >= 0) + { + if (bCanDeleteHoudiniNodes) + MarkInputNodeAsPendingDelete(); + InputNodeId = -1; + } + } + else { - if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId); - InputNodeId = -1; + (*InputObjectsPtr)[AtIndex] = nullptr; } #if WITH_EDITOR @@ -1703,11 +1796,11 @@ UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) // Same thing for Actor InputObjects! for (auto InputObj : *InputObjectsPtr) { - if (!InputObj || InputObj->IsPendingKill()) + if (!IsValid(InputObj)) continue; UHoudiniInputStaticMesh* InputSM = Cast(InputObj); - if (InputSM && !InputSM->IsPendingKill()) + if (IsValid(InputSM)) { if (InputSM->BlueprintStaticMeshes.Num() > 0) { @@ -1716,7 +1809,7 @@ UHoudiniInput::GetNumberOfInputMeshes(const EHoudiniInputType& InType) } UHoudiniInputActor* InputActor = Cast(InputObj); - if (InputActor && !InputActor->IsPendingKill()) + if (IsValid(InputActor)) { if (InputActor->GetActorComponents().Num() > 0) { @@ -1803,7 +1896,7 @@ UHoudiniInput::SetInputObjectAt(const EHoudiniInputType& InType, const int32& At // Mark that input object as changed so we know we need to update it NewInputObject->MarkChanged(true); - if (CurrentInputObjectWrapper && !CurrentInputObjectWrapper->IsPendingKill()) + if (IsValid(CurrentInputObjectWrapper)) { // TODO: // For some input type, we may have to copy some of the previous object's property before deleting it @@ -1843,7 +1936,7 @@ UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int3 for (int32 InObjIdx = InputObjectsPtr->Num() - 1; InObjIdx >= InNewCount; InObjIdx--) { UHoudiniInputObject* CurrentInputObject = (*InputObjectsPtr)[InObjIdx]; - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + if (!IsValid(CurrentInputObject)) continue; if (bCanDeleteHoudiniNodes) @@ -1864,7 +1957,7 @@ UHoudiniInput::SetInputObjectsNumber(const EHoudiniInputType& InType, const int3 if (InNewCount == 0 && InputNodeId >= 0) { if (bCanDeleteHoudiniNodes) - FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(InputNodeId, true); + MarkInputNodeAsPendingDelete(); InputNodeId = -1; } } @@ -2254,11 +2347,11 @@ UHoudiniInput::GetCurrentSelectionText() const UHoudiniInputObject* InputObject = LandscapeInputObjects[0]; UHoudiniInputLandscape* InputLandscape = Cast(InputObject); - if (!InputLandscape || InputLandscape->IsPendingKill()) + if (!IsValid(InputLandscape)) return CurrentSelectionText; ALandscapeProxy* LandscapeProxy = InputLandscape->GetLandscapeProxy(); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + if (!IsValid(LandscapeProxy)) return CurrentSelectionText; CurrentSelectionText = FText::FromString(LandscapeProxy->GetActorLabel()); @@ -2273,15 +2366,15 @@ UHoudiniInput::GetCurrentSelectionText() const UHoudiniInputObject* InputObject = AssetInputObjects[0]; UHoudiniInputHoudiniAsset* HoudiniAssetInput = Cast(InputObject); - if (!HoudiniAssetInput || HoudiniAssetInput->IsPendingKill()) + if (!IsValid(HoudiniAssetInput)) return CurrentSelectionText; UHoudiniAssetComponent* HAC = HoudiniAssetInput->GetHoudiniAssetComponent(); - if (!HAC || HAC->IsPendingKill()) + if (!IsValid(HAC)) return CurrentSelectionText; UHoudiniAsset* HoudiniAsset = HAC->GetHoudiniAsset(); - if (!HoudiniAsset || HoudiniAsset->IsPendingKill()) + if (!IsValid(HoudiniAsset)) return CurrentSelectionText; CurrentSelectionText = FText::FromString(HoudiniAsset->GetName()); @@ -2345,7 +2438,7 @@ UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() TArray AllBBox; for (auto CurrentActor : WorldInputBoundSelectorObjects) { - if (!CurrentActor || CurrentActor->IsPendingKill()) + if (!IsValid(CurrentActor)) continue; AllBBox.Add(CurrentActor->GetComponentsBoundingBox(true, true)); @@ -2365,7 +2458,7 @@ UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() for (TActorIterator ActorItr(MyWorld); ActorItr; ++ActorItr) { AActor *CurrentActor = *ActorItr; - if (!CurrentActor || CurrentActor->IsPendingKill()) + if (!IsValid(CurrentActor)) continue; // Check that actor is currently not selected @@ -2385,7 +2478,7 @@ UHoudiniInput::UpdateWorldSelectionFromBoundSelectors() ABrush* BrushActor = Cast(CurrentActor); if (BrushActor) { - if (!BrushActor->Brush || BrushActor->Brush->IsPendingKill()) + if (!IsValid(BrushActor->Brush)) continue; } @@ -2452,7 +2545,7 @@ UHoudiniInput::UpdateWorldSelection(const TArray& InNewSelection) bool UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputType& InType) const { - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return false; // Returns true if the object is one of our input object for the given type @@ -2462,7 +2555,7 @@ UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputT for (auto& CurrentInputObject : (*ObjectArray)) { - if (!CurrentInputObject || CurrentInputObject->IsPendingKill()) + if (!IsValid(CurrentInputObject)) continue; if (CurrentInputObject->GetObject() == InObject) @@ -2472,36 +2565,59 @@ UHoudiniInput::ContainsInputObject(const UObject* InObject, const EHoudiniInputT return false; } -void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn) const +void UHoudiniInput::ForAllHoudiniInputObjects(TFunctionRef Fn, const bool bFilterByInputType) const { - for(UHoudiniInputObject* InputObject : GeometryInputObjects) + auto ShouldIncludeFn = [&](const EHoudiniInputType InInputType) -> bool + { + return !bFilterByInputType || (bFilterByInputType && GetInputType() == InInputType); + }; + + if (ShouldIncludeFn(EHoudiniInputType::Geometry)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : GeometryInputObjects) + { + Fn(InputObject); + } } - for(UHoudiniInputObject* InputObject : AssetInputObjects) + if (ShouldIncludeFn(EHoudiniInputType::Asset)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : AssetInputObjects) + { + Fn(InputObject); + } } - for(UHoudiniInputObject* InputObject : CurveInputObjects) + if (ShouldIncludeFn(EHoudiniInputType::Curve)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : CurveInputObjects) + { + Fn(InputObject); + } } - for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + if (ShouldIncludeFn(EHoudiniInputType::Landscape)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : LandscapeInputObjects) + { + Fn(InputObject); + } } - - for(UHoudiniInputObject* InputObject : WorldInputObjects) + + if (ShouldIncludeFn(EHoudiniInputType::World)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : WorldInputObjects) + { + Fn(InputObject); + } } - for(UHoudiniInputObject* InputObject : SkeletalInputObjects) + if (ShouldIncludeFn(EHoudiniInputType::Skeletal)) { - Fn(InputObject); + for(UHoudiniInputObject* InputObject : SkeletalInputObjects) + { + Fn(InputObject); + } } } diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h index 4e1494eac..cc401eec8 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInput.h @@ -131,6 +131,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject FString GetHelp() const { return Help; }; bool GetPackBeforeMerge() const { return bPackBeforeMerge; }; bool GetImportAsReference() const { return bImportAsReference; }; + bool GetImportAsReferenceRotScaleEnabled() const { return bImportAsReferenceRotScaleEnabled; }; bool GetExportLODs() const { return bExportLODs; }; bool GetExportSockets() const { return bExportSockets; }; bool GetExportColliders() const { return bExportColliders; }; @@ -198,7 +199,9 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn) const; void ForAllHoudiniInputObjectArrays(TFunctionRef&)> Fn); - void ForAllHoudiniInputObjects(TFunctionRef Fn) const; + // Return ALL input objects. Optionally, the results can be filtered to only return input objects + // relevant to the current *input type*. + void ForAllHoudiniInputObjects(TFunctionRef Fn, const bool bFilterByInputType=false) const; // Collect top-level HoudiniInputObjects from this UHoudiniInput. Does not traverse nested input objects. void GetAllHoudiniInputObjects(TArray& OutObjects) const; // Collect top-level UHoudiniInputSceneComponent from this UHoudiniInput. Does not traverse nested input objects. @@ -211,6 +214,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject bool IsAddRotAndScaleAttributesEnabled() const { return bAddRotAndScaleAttributesOnCurves; }; + const TSet< ULandscapeComponent * > GetLandscapeSelectedComponents() const { return LandscapeSelectedComponents; }; + //------------------------------------------------------------------------------------------------ // Mutators //------------------------------------------------------------------------------------------------ @@ -236,6 +241,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject void SetPreviousInputType(const EHoudiniInputType& InType) { PreviousType = InType; }; void SetPackBeforeMerge(const bool& bInPackBeforeMerge) { bPackBeforeMerge = bInPackBeforeMerge; }; void SetImportAsReference(const bool& bInImportAsReference) { bImportAsReference = bInImportAsReference; }; + void SetImportAsReferenceRotScaleEnabled(const bool& bInImportAsReferenceRotScaleEnabled) { bImportAsReferenceRotScaleEnabled = bInImportAsReferenceRotScaleEnabled; }; void SetExportLODs(const bool& bInExportLODs) { bExportLODs = bInExportLODs; }; void SetExportSockets(const bool& bInExportSockets) { bExportSockets = bInExportSockets; }; void SetExportColliders(const bool& bInExportColliders) { bExportColliders = bInExportColliders; }; @@ -254,8 +260,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject void InsertInputObjectAt(const int32& AtIndex); void InsertInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); - void DeleteInputObjectAt(const int32& AtIndex); - void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); + void DeleteInputObjectAt(const int32& AtIndex, const bool bInRemoveIndexFromArray=true); + void DeleteInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex, const bool bInRemoveIndexFromArray=true); void DuplicateInputObjectAt(const int32& AtIndex); void DuplicateInputObjectAt(const EHoudiniInputType& InType, const int32& AtIndex); @@ -346,6 +352,17 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject FBox GetBounds() const; + void UpdateLandscapeInputSelection(); + + // Add the current InputNodeId to the pending delete set and set it to -1 + void MarkInputNodeAsPendingDelete(); + + // Return the set of previous InputNodeIds that are pending delete + const TSet& GetInputNodesPendingDelete() const { return InputNodesPendingDelete; } + + // Clear the InputNodesPendingDelete set + void ClearInputNodesPendingDelete() { InputNodesPendingDelete.Empty(); } + protected: // Name of the input / Object path parameter @@ -422,6 +439,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject UPROPERTY() bool bImportAsReference = false; + // Indicates that whether or not to add the rot / scale attributes for reference improts + UPROPERTY() + bool bImportAsReferenceRotScaleEnabled = false; + // Indicates that all LODs in the input should be marshalled to Houdini UPROPERTY() bool bExportLODs; @@ -510,6 +531,14 @@ class HOUDINIENGINERUNTIME_API UHoudiniInput : public UObject UPROPERTY() TArray SkeletalInputObjects; + // A cache of the selected landscape components so that it is saved across levels + UPROPERTY() + TSet< ULandscapeComponent * > LandscapeSelectedComponents; + + // The node ids of InputNodeIds previously used by this input that are pending delete + UPROPERTY(Transient, DuplicateTransient, NonTransactional) + TSet InputNodesPendingDelete; + public: // This array is to record the last insert action, for undo input insertion actions. diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp index fe5f2def3..bbf40ad44 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.cpp @@ -130,20 +130,6 @@ UHoudiniInputCameraComponent::UHoudiniInputCameraComponent(const FObjectInitiali } -// Returns true if the attached actor's (parent) transform has been modified -bool -UHoudiniInputSplineComponent::HasActorTransformChanged() const -{ - return false; -} - -// Returns true if the attached component's transform has been modified -bool -UHoudiniInputSplineComponent::HasComponentTransformChanged() const -{ - return false; -} - // Return true if the component itself has been modified bool UHoudiniInputSplineComponent::HasComponentChanged() const @@ -178,12 +164,6 @@ UHoudiniInputSplineComponent::HasComponentChanged() const return false; } -bool -UHoudiniInputSplineComponent::HasSplineComponentChanged(float fCurrentSplineResolution) const -{ - return false; -} - // UHoudiniInputHoudiniSplineComponent::UHoudiniInputHoudiniSplineComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) @@ -309,7 +289,7 @@ UHoudiniInputHoudiniAsset::GetHoudiniAssetComponent() } AActor* -UHoudiniInputActor::GetActor() +UHoudiniInputActor::GetActor() const { return Cast(InputObject.LoadSynchronous()); } @@ -856,7 +836,7 @@ UHoudiniInputSceneComponent::HasActorTransformChanged() const { // Returns true if the attached actor's (parent) transform has been modified USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); - if (!MyComp || MyComp->IsPendingKill()) + if (!IsValid(MyComp)) return false; AActor* MyActor = MyComp->GetOwner(); @@ -872,7 +852,7 @@ UHoudiniInputSceneComponent::HasComponentTransformChanged() const { // Returns true if the attached actor's (parent) transform has been modified USceneComponent* MyComp = Cast(InputObject.LoadSynchronous()); - if (!MyComp || MyComp->IsPendingKill()) + if (!IsValid(MyComp)) return false; return !Transform.Equals(MyComp->GetComponentTransform()); @@ -902,7 +882,7 @@ bool UHoudiniInputCameraComponent::HasComponentChanged() const { UCameraComponent* Camera = Cast(InputObject.LoadSynchronous()); - if (Camera && !Camera->IsPendingKill()) + if (IsValid(Camera)) { bool bOrtho = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; if (bOrtho != bIsOrthographic) @@ -938,7 +918,7 @@ UHoudiniInputCameraComponent::Update(UObject * InObject) ensure(Camera); - if (Camera && !Camera->IsPendingKill()) + if (IsValid(Camera)) { bIsOrthographic = Camera->ProjectionMode == ECameraProjectionMode::Type::Orthographic; FOV = Camera->FieldOfView; @@ -1070,7 +1050,7 @@ UHoudiniInputHoudiniSplineComponent::Update(UObject* InObject) //MyHoudiniSplineComponent = Cast(InObject); UHoudiniSplineComponent* HoudiniSplineComponent = GetCurveComponent(); - if (!HoudiniSplineComponent || HoudiniSplineComponent->IsPendingKill()) + if (!IsValid(HoudiniSplineComponent)) { // Use default values CurveType = EHoudiniCurveType::Polygon; @@ -1188,12 +1168,13 @@ UHoudiniInputActor::Update(UObject * InObject) TArray AllComponents; Actor->GetComponents(AllComponents, true); - - int32 CompIdx = 0; - ActorComponents.SetNum(AllComponents.Num()); + + ActorComponents.Reserve(AllComponents.Num()); for (USceneComponent * SceneComponent : AllComponents) { - if (!SceneComponent || SceneComponent->IsPendingKill()) + if (!IsValid(SceneComponent)) + continue; + if (!ShouldTrackComponent(SceneComponent)) continue; UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( @@ -1205,11 +1186,10 @@ UHoudiniInputActor::Update(UObject * InObject) if (!SceneInput) continue; - ActorComponents[CompIdx++] = SceneInput; + ActorComponents.Add(SceneInput); ActorSceneComponents.Add(TSoftObjectPtr(SceneComponent)); } - ActorComponents.SetNum(CompIdx); - LastUpdateNumComponentsAdded = CompIdx; + LastUpdateNumComponentsAdded = ActorComponents.Num(); } else { @@ -1236,7 +1216,7 @@ UHoudiniInputActor::Update(UObject * InObject) for (int32 Index = 0; Index < NumActorComponents; ++Index) { UHoudiniInputSceneComponent* CurActorComp = ActorComponents[Index]; - if (!CurActorComp || CurActorComp->IsPendingKill()) + if (!IsValid(CurActorComp)) { ComponentIndicesToRemove.Add(Index); continue; @@ -1245,7 +1225,7 @@ UHoudiniInputActor::Update(UObject * InObject) // Does the component still exist on Actor? UObject* const CompObj = CurActorComp->GetObject(); // Make sure the actor is still valid - if (!CompObj || CompObj->IsPendingKill()) + if (!IsValid(CompObj)) { // If it's not, mark it for deletion if ((CurActorComp->InputNodeId > 0) || (CurActorComp->InputObjectNodeId > 0)) @@ -1281,7 +1261,7 @@ UHoudiniInputActor::Update(UObject * InObject) { for (USceneComponent * SceneComponent : NewComponents) { - if (!SceneComponent || SceneComponent->IsPendingKill()) + if (!IsValid(SceneComponent)) continue; UHoudiniInputObject* InputObj = UHoudiniInputObject::CreateTypedInputObject( @@ -1325,13 +1305,38 @@ UHoudiniInputActor::Update(UObject * InObject) } bool -UHoudiniInputActor::HasActorTransformChanged() +UHoudiniInputActor::HasActorTransformChanged() const { if (!GetActor()) return false; + if (HasRootComponentTransformChanged()) + return true; + + if (HasComponentsTransformChanged()) + return true; + + return false; +} + +bool UHoudiniInputActor::HasRootComponentTransformChanged() const +{ if (!Transform.Equals(GetActor()->GetTransform())) return true; + return false; +} + +bool UHoudiniInputActor::HasComponentsTransformChanged() const +{ + // Search through all the child components to see if we have changed + // transforms in there. + for (auto CurActorComp : GetActorComponents()) + { + if (CurActorComp->HasComponentTransformChanged()) + { + return true; + } + } return false; } @@ -1343,10 +1348,47 @@ UHoudiniInputActor::HasContentChanged() const } bool -UHoudiniInputLandscape::HasActorTransformChanged() +UHoudiniInputActor::GetChangedObjectsAndValidNodes(TArray& OutChangedObjects, TArray& OutNodeIdsOfUnchangedValidObjects) { - return Super::HasActorTransformChanged(); - //return false; + if (Super::GetChangedObjectsAndValidNodes(OutChangedObjects, OutNodeIdsOfUnchangedValidObjects)) + return true; + + bool bAnyChanges = false; + // Check each of its child objects (components) + for (auto* const CurrentComp : GetActorComponents()) + { + if (!IsValid(CurrentComp)) + continue; + + if (CurrentComp->GetChangedObjectsAndValidNodes(OutChangedObjects, OutNodeIdsOfUnchangedValidObjects)) + bAnyChanges = true; + } + + return bAnyChanges; +} + +void +UHoudiniInputActor::InvalidateData() +{ + // Call invalidate on input component objects + for (auto* const CurrentComp : GetActorComponents()) + { + if (!IsValid(CurrentComp)) + continue; + + CurrentComp->InvalidateData(); + } + + Super::InvalidateData(); +} + +bool UHoudiniInputLandscape::ShouldTrackComponent(UActorComponent* InComponent) +{ + // We explicitly disable tracking of any components for landscape inputs since the Landscape tools + // have this very interesting and creative way of adding components when the tool is activated + // (looking at you Flatten tool) which causes cooking loops. + // return false; + return true; } void @@ -1673,7 +1715,7 @@ bool UHoudiniInputBrush::HasContentChanged() const } bool -UHoudiniInputBrush::HasActorTransformChanged() +UHoudiniInputBrush::HasActorTransformChanged() const { if (bIgnoreInputObject) return false; @@ -1774,6 +1816,36 @@ UHoudiniInputObject::SetCanDeleteHoudiniNodes(bool bInCanDeleteNodes) bCanDeleteHoudiniNodes = bInCanDeleteNodes; } +bool +UHoudiniInputObject::GetChangedObjectsAndValidNodes(TArray& OutChangedObjects, TArray& OutNodeIdsOfUnchangedValidObjects) +{ + if (HasChanged()) + { + // Has changed, needs to be recreated + OutChangedObjects.Add(this); + + return true; + } + + if (UsesInputObjectNode()) + { + if (InputObjectNodeId >= 0) + { + // No changes, keep it + OutNodeIdsOfUnchangedValidObjects.Add(InputObjectNodeId); + } + else + { + // Needs, but does not have, a current HAPI input node + OutChangedObjects.Add(this); + + return true; + } + } + + // No changes and valid object node exists (or no node is used by this object type) + return false; +} // UHoudiniInputDataTable::UHoudiniInputDataTable(const FObjectInitializer& ObjectInitializer) diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h index 4a85872f5..9b40c50a5 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInputObject.h @@ -128,6 +128,9 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject void SetImportAsReference(const bool& bInImportAsRef) { bImportAsReference = bInImportAsRef; }; bool GetImportAsReference() const { return bImportAsReference; }; + void SetImportAsReferenceRotScaleEnabled(const bool& bInImportAsRefRotScaleEnabled) { bImportAsReferenceRotScaleEnabled = bInImportAsRefRotScaleEnabled; }; + bool GetImportAsReferenceRotScaleEnabled() const { return bImportAsReferenceRotScaleEnabled; }; + #if WITH_EDITOR void SwitchUniformScaleLock() { bUniformScaleLocked = !bUniformScaleLocked; }; bool IsUniformScaleLocked() const { return bUniformScaleLocked; }; @@ -144,9 +147,24 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject FGuid GetInputGuid() const { return Guid; } + /** + * Populate OutChangedObjects with any output objects (this object and its children if it has any) that has changed + * or have invalid HAPI node ids. + * Any objects that have not changed and have valid HAPI node ids have their node ids added to + * OutNodeIdsOfUnchangedValidObjects. + * + * @return true if this object, or any of its child objects, were added to OutChangedObjects. + */ + virtual bool GetChangedObjectsAndValidNodes(TArray& OutChangedObjects, TArray& OutNodeIdsOfUnchangedValidObjects); protected: + /** + * Returns true if this object type uses an input object (OBJ) node. This is not a recursive check, for objects + * with child objects (such as an actor with components), each child input object must be checked as well. + */ + virtual bool UsesInputObjectNode() const { return true; } + virtual void BeginDestroy() override; public: @@ -194,6 +212,9 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputObject : public UObject UPROPERTY() bool bImportAsReference; + UPROPERTY() + bool bImportAsReferenceRotScaleEnabled; + // Indicates if change the scale of Transfrom Offset of this object uniformly #if WITH_EDITORONLY_DATA UPROPERTY(Transient, DuplicateTransient, NonTransactional) @@ -403,17 +424,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputSplineComponent : public UHoudiniInp // USplineComponent accessor USplineComponent* GetSplineComponent(); - // Returns true if the attached spline component has been modified - bool HasSplineComponentChanged(float fCurrentSplineResolution) const; - - // Returns true if the attached actor's (parent) transform has been modified - virtual bool HasActorTransformChanged() const; - - // Returns true if the attached component's transform has been modified - virtual bool HasComponentTransformChanged() const; - // Return true if the component itself has been modified - virtual bool HasComponentChanged() const; + virtual bool HasComponentChanged() const override; public: @@ -582,8 +594,16 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject // virtual void Update(UObject * InObject) override; - // - virtual bool HasActorTransformChanged(); + // Check whether the actor transform, or any of its components have transform changes. + virtual bool HasActorTransformChanged() const; + +protected: + virtual bool HasRootComponentTransformChanged() const; + virtual bool HasComponentsTransformChanged() const; + +public: + // + virtual bool ShouldTrackComponent(UActorComponent* InComponent) { return true; } // Return true if any content of this actor has possibly changed (for example geometry edits on a // Brush or changes on procedurally generated content). @@ -591,7 +611,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject virtual bool HasContentChanged() const; // AActor accessor - AActor* GetActor(); + AActor* GetActor() const; const TArray& GetActorComponents() const { return ActorComponents; } @@ -600,8 +620,22 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputActor : public UHoudiniInputObject // The number of components remove with the last call to Update int32 GetLastUpdateNumComponentsRemoved() const { return LastUpdateNumComponentsRemoved; } + /** + * Populate OutChangedObjects with any output objects (this object and its children if it has any) that has changed + * or have invalid HAPI node ids. + * Any objects that have not changed and have valid HAPI node is have their node ids added to + * OutNodeIdsOfUnchangedValidObjects. + * + * @return true if this object, or any of its child objects, were added to OutChangedObjects. + */ + virtual bool GetChangedObjectsAndValidNodes(TArray& OutChangedObjects, TArray& OutNodeIdsOfUnchangedValidObjects) override; + + virtual void InvalidateData() override; + protected: + virtual bool UsesInputObjectNode() const override { return false; } + // The actor's components that can be sent as inputs UPROPERTY() TArray ActorComponents; @@ -635,9 +669,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActo static UHoudiniInputObject* Create(UObject * InObject, UObject* InOuter, const FString& InName); // + virtual void Update(UObject * InObject) override; - virtual bool HasActorTransformChanged() override; + virtual bool ShouldTrackComponent(UActorComponent* InComponent) override; // ALandscapeProxy accessor ALandscapeProxy* GetLandscapeProxy(); @@ -647,6 +682,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputLandscape : public UHoudiniInputActo // Used to restore an input landscape's transform to its original state UPROPERTY() FTransform CachedInputLandscapeTraqnsform; + +protected: + virtual bool UsesInputObjectNode() const override { return true; } + }; @@ -770,8 +809,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor // Indicates if this input has changed and should be updated virtual bool HasTransformChanged() const override { return (!bIgnoreInputObject) && bTransformChanged; }; - - virtual bool HasActorTransformChanged() override; + + virtual bool HasActorTransformChanged() const override; virtual bool NeedsToTriggerUpdate() const override { return (!bIgnoreInputObject) && bNeedsToTriggerUpdate; }; @@ -801,6 +840,8 @@ class HOUDINIENGINERUNTIME_API UHoudiniInputBrush : public UHoudiniInputActor static bool FindIntersectingSubtractiveBrushes(const UHoudiniInputBrush* InputBrush, TArray& OutBrushes); protected: + virtual bool UsesInputObjectNode() const override { return true; } + UPROPERTY() TArray BrushesInfo; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp index f0fced9b7..f5ec4c082 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniInstancedActorComponent.cpp @@ -123,9 +123,9 @@ void UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferenceCollector & Collector ) { UHoudiniInstancedActorComponent * ThisHIAC = Cast< UHoudiniInstancedActorComponent >(InThis); - if ( ThisHIAC && !ThisHIAC->IsPendingKill() ) + if ( IsValid(ThisHIAC) ) { - if ( ThisHIAC->InstancedObject && !ThisHIAC->InstancedObject->IsPendingKill() ) + if ( IsValid(ThisHIAC->InstancedObject) ) Collector.AddReferencedObject( ThisHIAC->InstancedObject, ThisHIAC ); Collector.AddReferencedObjects(ThisHIAC->InstancedActors, ThisHIAC ); @@ -136,7 +136,7 @@ UHoudiniInstancedActorComponent::AddReferencedObjects(UObject * InThis, FReferen int32 UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform, AActor * NewActor) { - if (!NewActor || NewActor->IsPendingKill()) + if (!IsValid(NewActor)) return -1; NewActor->AttachToComponent(this, FAttachmentTransformRules::KeepRelativeTransform); @@ -148,7 +148,7 @@ UHoudiniInstancedActorComponent::AddInstance(const FTransform& InstanceTransform bool UHoudiniInstancedActorComponent::SetInstanceAt(const int32& Idx, const FTransform& InstanceTransform, AActor * NewActor) { - if (!NewActor || NewActor->IsPendingKill()) + if (!IsValid(NewActor)) return false; if (!InstancedActors.IsValidIndex(Idx)) @@ -181,7 +181,7 @@ UHoudiniInstancedActorComponent::ClearAllInstances() { for ( AActor* Instance : InstancedActors ) { - if ( Instance && !Instance->IsPendingKill() ) + if ( IsValid(Instance) ) Instance->Destroy(); } InstancedActors.Empty(); @@ -199,7 +199,7 @@ UHoudiniInstancedActorComponent::SetNumberOfInstances(const int32& NewInstanceNu for (int32 Idx = NewInstanceNum - 1; Idx < InstancedActors.Num(); Idx++) { AActor* Instance = InstancedActors.IsValidIndex(Idx) ? InstancedActors[Idx] : nullptr; - if (Instance && !Instance->IsPendingKill()) + if (IsValid(Instance)) Instance->Destroy(); } } @@ -218,7 +218,7 @@ UHoudiniInstancedActorComponent::OnComponentCreated() bool bNeedDuplicate = false; for (auto CurrentInstance : InstancedActors) { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + if ( !IsValid(CurrentInstance) ) continue; if ( CurrentInstance->GetAttachParentActor() != GetOwner() ) @@ -234,7 +234,7 @@ UHoudiniInstancedActorComponent::OnComponentCreated() InstancedActors.Empty(); for (AActor* CurrentInstance : SourceInstances) { - if ( !CurrentInstance || CurrentInstance->IsPendingKill() ) + if ( !IsValid(CurrentInstance) ) continue; FTransform InstanceTransform; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp index bcaa2c558..8eb06bb17 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniMeshSplitInstancerComponent.cpp @@ -121,7 +121,7 @@ void UHoudiniMeshSplitInstancerComponent::AddReferencedObjects( UObject * InThis, FReferenceCollector & Collector ) { UHoudiniMeshSplitInstancerComponent * ThisMSIC = Cast< UHoudiniMeshSplitInstancerComponent >(InThis); - if ( ThisMSIC && !ThisMSIC->IsPendingKill() ) + if ( IsValid(ThisMSIC) ) { Collector.AddReferencedObject(ThisMSIC->InstancedMesh, ThisMSIC); for(auto& Mat : ThisMSIC->OverrideMaterials) @@ -137,14 +137,14 @@ UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( if (Instances.Num() <= 0 && InstanceTransforms.Num() <= 0) return false; - if (!GetOwner() || GetOwner()->IsPendingKill()) + if (!IsValid(GetOwner())) return false; // Destroy previous instances while keeping some of the one that we'll be able to reuse ClearInstances(InstanceTransforms.Num()); // - if( !InstancedMesh || InstancedMesh->IsPendingKill() ) + if( !IsValid(InstancedMesh) ) { HOUDINI_LOG_ERROR(TEXT("%s: Null InstancedMesh for split instanced mesh override"), *GetOwner()->GetName()); return false; @@ -172,7 +172,7 @@ UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( UStaticMeshComponent* SMC = Instances[iIns]; const FTransform& InstanceTransform = InstanceTransforms[iIns]; - if (!SMC || SMC->IsPendingKill()) + if (!IsValid(SMC)) continue; SMC->SetRelativeTransform(InstanceTransform); @@ -194,7 +194,7 @@ UHoudiniMeshSplitInstancerComponent::SetInstanceTransforms( MI = OverrideMaterials[0]; } - if (MI && !MI->IsPendingKill()) + if (IsValid(MI)) { int32 MeshMaterialCount = InstancedMesh->GetStaticMaterials().Num(); for (int32 Idx = 0; Idx < MeshMaterialCount; ++Idx) diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp index 564c20e06..f0044bf00 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.cpp @@ -481,7 +481,7 @@ UHoudiniOutput::GetBounds() const MeshComp = Cast(CurObj.OutputComponent); } - if (!MeshComp || MeshComp->IsPendingKill()) + if (!IsValid(MeshComp)) continue; BoxBounds += MeshComp->Bounds.GetBox(); @@ -495,11 +495,11 @@ UHoudiniOutput::GetBounds() const { const FHoudiniOutputObject& CurObj = CurPair.Value; UHoudiniLandscapePtr* CurLandscapeObj = Cast(CurObj.OutputObject); - if (!CurLandscapeObj || CurLandscapeObj->IsPendingKill()) + if (!IsValid(CurLandscapeObj)) continue; ALandscapeProxy* Landscape = Cast(CurLandscapeObj->GetRawPtr()); - if (!Landscape || Landscape->IsPendingKill()) + if (!IsValid(Landscape)) continue; FVector Origin, Extent; @@ -517,7 +517,7 @@ UHoudiniOutput::GetBounds() const { const FHoudiniOutputObject& CurObj = CurPair.Value; USceneComponent* InstancedComp = Cast(CurObj.OutputObject); - if (!InstancedComp || InstancedComp->IsPendingKill()) + if (!IsValid(InstancedComp)) continue; BoxBounds += InstancedComp->Bounds.GetBox(); @@ -531,7 +531,7 @@ UHoudiniOutput::GetBounds() const { const FHoudiniOutputObject& CurObj = CurPair.Value; UHoudiniSplineComponent* CurHoudiniSplineComp = Cast(CurObj.OutputComponent); - if (!CurHoudiniSplineComp || CurHoudiniSplineComp->IsPendingKill()) + if (!IsValid(CurHoudiniSplineComp)) continue; FBox CurCurveBound(ForceInitToZero); @@ -541,7 +541,7 @@ UHoudiniOutput::GetBounds() const } UHoudiniAssetComponent* OuterHAC = Cast(GetOuter()); - if (OuterHAC && !OuterHAC->IsPendingKill()) + if (IsValid(OuterHAC)) BoxBounds += CurCurveBound.MoveTo(OuterHAC->GetComponentLocation()); } @@ -568,18 +568,29 @@ UHoudiniOutput::Clear() for (auto& CurrentOutputObject : OutputObjects) { + UHoudiniSplineComponent* SplineComponent = Cast(CurrentOutputObject.Value.OutputComponent); + if (IsValid(SplineComponent)) + { + // The spline component is a special case where the output + // object as associated Houdini nodes (as input object). + // We can only explicitly remove those nodes when the output object gets + // removed. + SplineComponent->MarkInputNodesAsPendingKill(); + } + // Clear the output component USceneComponent* SceneComp = Cast(CurrentOutputObject.Value.OutputComponent); - if (SceneComp && !SceneComp->IsPendingKill()) + if (IsValid(SceneComp)) { SceneComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); SceneComp->UnregisterComponent(); SceneComp->DestroyComponent(); } + // Also destroy proxy components USceneComponent* ProxyComp = Cast(CurrentOutputObject.Value.ProxyComponent); - if (ProxyComp && !ProxyComp->IsPendingKill()) + if (IsValid(ProxyComp)) { ProxyComp->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform); ProxyComp->UnregisterComponent(); @@ -592,7 +603,7 @@ UHoudiniOutput::Clear() // will result in a StaticFindObject() call which will raise an exception during GC. UHoudiniLandscapePtr* LandscapePtr = Cast(CurrentOutputObject.Value.OutputObject); ALandscapeProxy* LandscapeProxy = LandscapePtr ? LandscapePtr->GetRawPtr() : nullptr; - if (LandscapeProxy && !LandscapeProxy->IsPendingKill()) + if (IsValid(LandscapeProxy)) { LandscapeProxy->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform); LandscapeProxy->ConditionalBeginDestroy(); @@ -694,9 +705,26 @@ UHoudiniOutput::HeightfieldMatch(const FHoudiniGeoPartObject& InHGPO, const bool // We've specified if we want the name to match/to be different: // when looking in previous outputs, we want the name to match // when looking in newly created outputs, we want to be sure the names are different - bool bNameMatch = InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase); - if (bNameMatch != bVolumeNameShouldMatch) - continue; + if (bVolumeNameShouldMatch) + { + // HasEditLayers state should match. + if (!(InHGPO.bHasEditLayers == currentHGPO.bHasEditLayers)) + { + continue; + } + + // If we have edit layers, ensure the names match + if (InHGPO.bHasEditLayers && !InHGPO.VolumeLayerName.Equals(currentHGPO.VolumeLayerName, ESearchCase::IgnoreCase)) + { + continue; + } + + // Check whether the volume name match. + if (!(InHGPO.VolumeName.Equals(currentHGPO.VolumeName, ESearchCase::IgnoreCase))) + { + continue; + } + } return true; } @@ -884,7 +912,7 @@ UHoudiniOutput::HasAnyProxy() const for (const auto& Pair : OutputObjects) { UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) + if (IsValid(FoundProxy)) { return true; } @@ -901,7 +929,7 @@ UHoudiniOutput::HasProxy(const FHoudiniOutputObjectIdentifier& InIdentifier) con return false; UObject* FoundProxy = FoundOutputObject->ProxyObject; - if (!FoundProxy || FoundProxy->IsPendingKill()) + if (!IsValid(FoundProxy)) return false; return true; @@ -913,7 +941,7 @@ UHoudiniOutput::HasAnyCurrentProxy() const for (const auto& Pair : OutputObjects) { UObject* FoundProxy = Pair.Value.ProxyObject; - if (FoundProxy && !FoundProxy->IsPendingKill()) + if (IsValid(FoundProxy)) { if(Pair.Value.bProxyIsCurrent) { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h index 8e4dd84d5..52449abe3 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniOutput.h @@ -99,6 +99,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniLandscapePtr : public UObject UPROPERTY() EHoudiniLandscapeOutputBakeType BakeType; + + // Edit layer to which this output corresponds, if applicable. + UPROPERTY() + FName EditLayerName; }; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp index e41eb188e..a214e4678 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.cpp @@ -120,14 +120,14 @@ FTOPWorkResult::operator==(const FTOPWorkResult& OtherWorkResult) const } void -FTOPWorkResult::ClearAndDestroyResultObjects() +FTOPWorkResult::ClearAndDestroyResultObjects(const FGuid& InHoudiniComponentGuid) { if (ResultObjects.Num() <= 0) return; for (FTOPWorkResultObject& ResultObject : ResultObjects) { - ResultObject.DestroyResultOutputsAndRemoveOutputActor(); + ResultObject.DestroyResultOutputsAndRemoveOutputActor(InHoudiniComponentGuid); } ResultObjects.Empty(); @@ -393,6 +393,11 @@ UTOPNode::Reset() AggregatedWorkItemTally.ZeroAll(); } +UHoudiniPDGAssetLink* UTOPNode::GetOuterAssetLink() const +{ + return GetTypedOuter(); +} + void UTOPNode::OnWorkItemWaiting(int32 InWorkItemID) { FTOPWorkResult* const WorkItem = GetWorkResultByID(InWorkItemID); @@ -456,18 +461,18 @@ UTOPNode::UpdateOutputVisibilityInLevel() // We need to manually handle child landscape's visiblity for (UHoudiniOutput* ResultOutput : WRO.GetResultOutputs()) { - if (!ResultOutput || ResultOutput->IsPendingKill()) + if (!IsValid(ResultOutput)) continue; for (auto& Pair : ResultOutput->GetOutputObjects()) { FHoudiniOutputObject& OutputObject = Pair.Value; ALandscapeProxy* LandscapeProxy = Cast(OutputObject.OutputObject); - if (!LandscapeProxy || LandscapeProxy->IsPendingKill()) + if (!IsValid(LandscapeProxy)) continue; ALandscape* Landscape = LandscapeProxy->GetLandscapeActor(); - if (!Landscape || Landscape->IsPendingKill()) + if (!IsValid(Landscape)) continue; Landscape->SetHidden(!bShow); @@ -510,36 +515,76 @@ UTOPNode::SetLoadedWorkResultsToDelete() } } +FGuid +UTOPNode::GetHoudiniComponentGuid() const +{ + UHoudiniPDGAssetLink const* const AssetLink = GetOuterAssetLink(); + if (!IsValid(AssetLink)) + return FGuid(); + + return AssetLink->GetOuterHoudiniComponentGuid(); +} void -UTOPNode::DeleteWorkResultOutputObjects() +UTOPNode::DeleteWorkResultObjectOutputs(const int32 InWorkResultArrayIndex, const int32 InWorkResultObjectArrayIndex, const bool bInDeleteOutputActors) { - for (FTOPWorkResult& WorkItem : WorkResult) + if (!WorkResult.IsValidIndex(InWorkResultArrayIndex)) + return; + + FTOPWorkResult& WorkItem = WorkResult[InWorkResultArrayIndex]; + if (!WorkItem.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) + return; + + FTOPWorkResultObject& WRO = WorkItem.ResultObjects[InWorkResultObjectArrayIndex]; + // Delete and clean up that WRObj + WRO.DestroyResultOutputs(GetHoudiniComponentGuid()); + if (bInDeleteOutputActors) + WRO.GetOutputActorOwner().DestroyOutputActor(); + WRO.State = EPDGWorkResultState::Deleted; +} + +void +UTOPNode::DeleteWorkItemOutputs(const int32 InWorkResultArrayIndex, const bool bInDeleteOutputActors) +{ + if (!WorkResult.IsValidIndex(InWorkResultArrayIndex)) + return; + + const FTOPWorkResult& WorkItem = WorkResult[InWorkResultArrayIndex]; + const int32 NumResultObjects = WorkItem.ResultObjects.Num(); + for (int32 ResultObjectIndex = 0; ResultObjectIndex < NumResultObjects; ++ResultObjectIndex) { - for (FTOPWorkResultObject& WRO : WorkItem.ResultObjects) - { - if (WRO.State == EPDGWorkResultState::Loaded) - { - // Delete and clean up that WRObj - WRO.DestroyResultOutputs(); - WRO.GetOutputActorOwner().DestroyOutputActor(); - WRO.State = EPDGWorkResultState::Deleted; - } - } - } + DeleteWorkResultObjectOutputs(InWorkResultArrayIndex, ResultObjectIndex, bInDeleteOutputActors); + } +} + +void +UTOPNode::DeleteAllWorkResultObjectOutputs(const bool bInDeleteOutputActors) +{ + const int32 NumWorkItems = WorkResult.Num(); + for (int32 WorkItemIndex = 0; WorkItemIndex < NumWorkItems; ++WorkItemIndex) + { + DeleteWorkItemOutputs(WorkItemIndex, bInDeleteOutputActors); + } bCachedHaveLoadedWorkResults = false; } FString -UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex) +UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex) { - return FString::Printf(TEXT("%d_%d"), InWorkItemIndex, InWorkResultObjectArrayIndex); + return FString::Printf(TEXT("%d_%d"), InWorkResultArrayIndex, InWorkResultObjectArrayIndex); } FString -UTOPNode::GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex) +UTOPNode::GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex) const { - return GetBakedWorkResultObjectOutputsKey(InWorkResult.WorkItemIndex, InWorkResultObjectArrayIndex); + if (InWorkResult.WorkItemID == INDEX_NONE) + return FString(); + + const int32 WorkResultArrayIndex = ArrayIndexOfWorkResultByID(InWorkResult.WorkItemID); + if (WorkResultArrayIndex == INDEX_NONE) + return FString(); + + return GetBakedWorkResultObjectOutputsKey(WorkResultArrayIndex, InWorkResultObjectArrayIndex); } bool @@ -552,7 +597,7 @@ UTOPNode::GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 if (!WorkResultEntry.ResultObjects.IsValidIndex(InWorkResultObjectArrayIndex)) return false; - OutKey = GetBakedWorkResultObjectOutputsKey(WorkResultEntry, InWorkResultObjectArrayIndex); + OutKey = GetBakedWorkResultObjectOutputsKey(InWorkResultArrayIndex, InWorkResultObjectArrayIndex); return true; } @@ -584,7 +629,7 @@ UTOPNode::GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 In } int32 -UTOPNode::IndexOfWorkResultByID(const int32& InWorkItemID) +UTOPNode::ArrayIndexOfWorkResultByID(const int32& InWorkItemID) const { const int32 NumEntries = WorkResult.Num(); for (int32 Index = 0; Index < NumEntries; ++Index) @@ -602,7 +647,7 @@ UTOPNode::IndexOfWorkResultByID(const int32& InWorkItemID) FTOPWorkResult* UTOPNode::GetWorkResultByID(const int32& InWorkItemID) { - const int32 ArrayIndex = IndexOfWorkResultByID(InWorkItemID); + const int32 ArrayIndex = ArrayIndexOfWorkResultByID(InWorkItemID); if (!WorkResult.IsValidIndex(ArrayIndex)) return nullptr; @@ -610,13 +655,13 @@ UTOPNode::GetWorkResultByID(const int32& InWorkItemID) } int32 -UTOPNode::IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) +UTOPNode::ArrayIndexOfFirstInvalidWorkResult() const { const int32 NumEntries = WorkResult.Num(); for (int32 Index = 0; Index < NumEntries; ++Index) { const FTOPWorkResult& CurResult = WorkResult[Index]; - if (CurResult.WorkItemIndex == InWorkItemIndex && (!bInWithInvalidWorkItemID || CurResult.WorkItemID == INDEX_NONE)) + if (CurResult.WorkItemID == INDEX_NONE) { return Index; } @@ -625,15 +670,6 @@ UTOPNode::IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWit return INDEX_NONE; } -FTOPWorkResult* -UTOPNode::GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID) -{ - const int32 ArrayIndex = IndexOfWorkResultByHAPIIndex(InWorkItemIndex, bInWithInvalidWorkItemID); - if (!WorkResult.IsValidIndex(ArrayIndex)) - return nullptr; - return &WorkResult[ArrayIndex]; -} - FTOPWorkResult* UTOPNode::GetWorkResultByArrayIndex(const int32& InArrayIndex) { @@ -789,14 +825,14 @@ UTOPNetwork::SetLoadedWorkResultsToDelete() } void -UTOPNetwork::DeleteWorkResultOutputObjects() +UTOPNetwork::DeleteAllWorkResultObjectOutputs() { for (UTOPNode* Node : AllTOPNodes) { if (!IsValid(Node)) continue; - Node->DeleteWorkResultOutputObjects(); + Node->DeleteAllWorkResultObjectOutputs(); } } @@ -848,7 +884,7 @@ UTOPNetwork::HandleOnPDGEventCookCompleteReceivedByChildNode(UHoudiniPDGAssetLin if (!IsValid(InAssetLink)) return; - // Check if all nodes have recieved the HAPI_PDG_EVENT_COOK_COMPLETE event, if so, broadcast the OnPostCook handler. + // Check if all nodes have received the HAPI_PDG_EVENT_COOK_COMPLETE event, if so, broadcast the OnPostCook handler. for (const UTOPNode* const TOPNode : AllTOPNodes) { if (!IsValid(TOPNode)) @@ -1101,10 +1137,11 @@ UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) return; TOPNode->OnDirtyNode(); - + + const FGuid HoudiniComponentGuid(TOPNode->GetHoudiniComponentGuid()); for(FTOPWorkResult& CurrentWorkResult : TOPNode->WorkResult) { - DestroyWorkItemResultData(CurrentWorkResult); + CurrentWorkResult.ClearAndDestroyResultObjects(HoudiniComponentGuid); } TOPNode->WorkResult.Empty(); @@ -1125,7 +1162,7 @@ UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(UTOPNode* TOPNode) } } - if (TOPNode->WorkResultParent && !TOPNode->WorkResultParent->IsPendingKill()) + if (IsValid(TOPNode->WorkResultParent)) { // TODO: Destroy the Parent Object @@ -1145,7 +1182,7 @@ UHoudiniPDGAssetLink::ClearWorkItemResultByID(const int32& InWorkItemID, UTOPNod FTOPWorkResult* WorkResult = GetWorkResultByID(InWorkItemID, InTOPNode); if (WorkResult) { - DestroyWorkItemResultData(*WorkResult); + WorkResult->ClearAndDestroyResultObjects(InTOPNode->GetHoudiniComponentGuid()); // TODO: Should we destroy the FTOPWorkResult struct entirely here? //TOPNode.WorkResult.RemoveByPredicate } @@ -1187,16 +1224,14 @@ UHoudiniPDGAssetLink::GetTemporaryCookFolder() const return TempPath; } -void -UHoudiniPDGAssetLink::DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject) +FGuid +UHoudiniPDGAssetLink::GetOuterHoudiniComponentGuid() const { - ResultObject.DestroyResultOutputsAndRemoveOutputActor(); -} + UHoudiniAssetComponent const* const HAC = GetOuterHoudiniAssetComponent(); + if (!IsValid(HAC)) + return FGuid(); -void -UHoudiniPDGAssetLink::DestroyWorkItemResultData(FTOPWorkResult& Result) -{ - Result.ClearAndDestroyResultObjects(); + return HAC->GetComponentGUID(); } void @@ -1704,8 +1739,12 @@ UHoudiniPDGAssetLink::PostTransacted(const FTransactionObjectEvent& TransactionE #endif void -FTOPWorkResultObject::DestroyResultOutputs() +FTOPWorkResultObject::DestroyResultOutputs(const FGuid& InHoudiniComponentGuid) { + TSet OutputObjectsToDelete; + + const FString ComponentGuidString = InHoudiniComponentGuid.IsValid() ? InHoudiniComponentGuid.ToString() : FString(); + // Delete output components and gather output objects for deletion bool bDidDestroyObjects = false; bool bDidModifyFoliage = false; @@ -1718,7 +1757,7 @@ FTOPWorkResultObject::DestroyResultOutputs() { FHoudiniOutputObjectIdentifier& Identifier = Pair.Key; FHoudiniOutputObject& OutputObject = Pair.Value; - if (OutputObject.OutputComponent && !OutputObject.OutputComponent->IsPendingKill()) + if (IsValid(OutputObject.OutputComponent)) { // Instancer components require some special handling around foliage // TODO: move/refactor so that we can use the InstanceTranslator's helper functions (RemoveAndDestroyComponent and CleanupFoliageInstances) @@ -1734,20 +1773,20 @@ FTOPWorkResultObject::DestroyResultOutputs() ParentComponent = Cast(OutputActor->GetRootComponent()); else ParentComponent = Cast(HISMC->GetOuter()); - if (ParentComponent && !ParentComponent->IsPendingKill()) + if (IsValid(ParentComponent)) { UStaticMesh* FoliageSM = HISMC->GetStaticMesh(); - if (!FoliageSM || FoliageSM->IsPendingKill()) + if (!IsValid(FoliageSM)) return; // If we are a foliage HISMC, then our owner is an Instanced Foliage Actor, // if it is not, then we are just a "regular" HISMC AInstancedFoliageActor* InstancedFoliageActor = Cast(HISMC->GetOwner()); - if (!InstancedFoliageActor || InstancedFoliageActor->IsPendingKill()) + if (!IsValid(InstancedFoliageActor)) return; UFoliageType *FoliageType = InstancedFoliageActor->GetLocalFoliageTypeForSource(FoliageSM); - if (!FoliageType || FoliageType->IsPendingKill()) + if (!IsValid(FoliageType)) return; #if WITH_EDITOR // Clean up the instances previously generated for that component @@ -1773,7 +1812,7 @@ FTOPWorkResultObject::DestroyResultOutputs() if (bDestroyComponent) { USceneComponent* SceneComponent = Cast(OutputObject.OutputComponent); - if (SceneComponent && !SceneComponent->IsPendingKill()) + if (IsValid(SceneComponent)) { // Remove from its actor first if (SceneComponent->GetOwner()) @@ -1790,7 +1829,7 @@ FTOPWorkResultObject::DestroyResultOutputs() } } } - if (OutputObject.OutputObject && !OutputObject.OutputObject->IsPendingKill()) + if (IsValid(OutputObject.OutputObject)) { // For actors we detach them first and then destroy AActor* Actor = Cast(OutputObject.OutputObject); @@ -1809,8 +1848,10 @@ FTOPWorkResultObject::DestroyResultOutputs() } else { - // ... if not an actor, destroy the object if it is a plugin created temp object - if (IsValid(OutputObject.OutputObject) && !OutputObject.OutputObject->HasAnyFlags(RF_Transient)) + // ... if not an actor, destroy the object if it is a temp object created by the owning + // HoudiniAssetComponent. Don't delete anything if we don't have a valid component GUID. + if (IsValid(OutputObject.OutputObject) && !OutputObject.OutputObject->HasAnyFlags(RF_Transient) && + !ComponentGuidString.IsEmpty() && !OutputObjectsToDelete.Contains(OutputObject.OutputObject)) { // Only delete if the object has metadata indicating it is a plugin created temp object UPackage* const Package = OutputObject.OutputObject->GetOutermost(); @@ -1819,13 +1860,22 @@ FTOPWorkResultObject::DestroyResultOutputs() UMetaData* const MetaData = Package->GetMetaData(); if (IsValid(MetaData)) { + // Check for HAPI_UNREAL_PACKAGE_META_TEMP_GUID in the package metadata to confirm that + // this is a temp package, and then ensure that the component GUID in the package metadata + // for the object matches the GUID of the owning HAC if (MetaData->RootMetaDataMap.Contains(HAPI_UNREAL_PACKAGE_META_TEMP_GUID)) { FString TempGUID; TempGUID = MetaData->RootMetaDataMap.FindChecked(HAPI_UNREAL_PACKAGE_META_TEMP_GUID); TempGUID.TrimStartAndEndInline(); if (!TempGUID.IsEmpty()) - OutputObjectsToDelete.Add(OutputObject.OutputObject); + { + // PackageComponentGuidString will be the empty string if the object does not + // have the HAPI_UNREAL_PACKAGE_META_COMPONENT_GUID metadata key in the package + const FString PackageComponentGuidString = MetaData->GetValue(OutputObject.OutputObject, HAPI_UNREAL_PACKAGE_META_COMPONENT_GUID); + if (!PackageComponentGuidString.IsEmpty() && PackageComponentGuidString == ComponentGuidString) + OutputObjectsToDelete.Add(OutputObject.OutputObject); + } } } } @@ -1843,7 +1893,10 @@ FTOPWorkResultObject::DestroyResultOutputs() // Delete the output objects we found if (OutputObjectsToDelete.Num() > 0) - FHoudiniEngineRuntimeUtils::SafeDeleteObjects(OutputObjectsToDelete); + { + TArray ObjectsToDelete(OutputObjectsToDelete.Array()); + FHoudiniEngineRuntimeUtils::SafeDeleteObjects(ObjectsToDelete); + } #if WITH_EDITOR if (bDidModifyFoliage) @@ -1861,9 +1914,9 @@ FTOPWorkResultObject::DestroyResultOutputs() #endif } -void FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor() +void FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor(const FGuid& InHoudiniComponentGuid) { - DestroyResultOutputs(); + DestroyResultOutputs(InHoudiniComponentGuid); GetOutputActorOwner().DestroyOutputActor(); } @@ -1871,12 +1924,12 @@ bool FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAssetLink, AActor *InParentActor, const FName& InName) { // InAssetLink and InWorld must not be null - if (!InAssetLink || InAssetLink->IsPendingKill()) + if (!IsValid(InAssetLink)) { HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InAssetLink is null!")); return false; } - if (!InWorld || InWorld->IsPendingKill()) + if (!IsValid(InWorld)) { HOUDINI_LOG_ERROR(TEXT("[FTOPWorkResultObject::CreateWorkResultActor]: InWorld is null!")); return false; @@ -1931,7 +1984,7 @@ FOutputActorOwner::CreateOutputActor(UWorld* InWorld, UHoudiniPDGAssetLink* InAs // Set the actor transform: create a root component if it does not have one USceneComponent* RootComponent = Actor->GetRootComponent(); - if (!RootComponent || RootComponent->IsPendingKill()) + if (!IsValid(RootComponent)) { RootComponent = NewObject(Actor, USceneComponent::StaticClass(), NAME_None, RF_Transactional); diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h index 6203c90b8..89fc36a05 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniPDGAssetLink.h @@ -28,8 +28,9 @@ //#include "CoreMinimal.h" #include "UObject/ObjectMacros.h" +#include "Misc/Guid.h" -#include "HoudiniAsset.h" +// #include "HoudiniAsset.h" #include "HoudiniAssetComponent.h" #include "HoudiniTranslatorTypes.h" @@ -123,7 +124,7 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject const TArray& GetResultOutputs() const { return ResultOutputs; } // Destroy ResultOutputs - void DestroyResultOutputs(); + void DestroyResultOutputs(const FGuid& InHoudiniComponentGuid); // Get the OutputActor owner struct FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } @@ -132,7 +133,7 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject const FOutputActorOwner& GetOutputActorOwner() const { return OutputActorOwner; } // Destroy the ResultOutputs and remove the output actor. - void DestroyResultOutputsAndRemoveOutputActor(); + void DestroyResultOutputsAndRemoveOutputActor(const FGuid& InHoudiniComponentGuid); // Getter for bAutoBakedSinceLastLoad: indicates if this work result object has been auto-baked since it's last load. bool AutoBakedSinceLastLoad() const { return bAutoBakedSinceLastLoad; } @@ -163,10 +164,6 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResultObject bool bAutoBakedSinceLastLoad = false; private: - // List of objects to delete, internal use only (DestroyResultOutputs) - UPROPERTY(NonTransactional) - TArray OutputObjectsToDelete; - UPROPERTY(NonTransactional) FOutputActorOwner OutputActorOwner; }; @@ -185,7 +182,7 @@ struct HOUDINIENGINERUNTIME_API FTOPWorkResult bool operator==(const FTOPWorkResult& OtherWorkResult) const; // Calls FTOPWorkResultObject::DestroyResultOutputsAndRemoveOutputActor on each entry in ResultObjects and clears the array. - void ClearAndDestroyResultObjects(); + void ClearAndDestroyResultObjects(const FGuid& InHoudiniComponentGuid); // Search for the first FTOPWorkResultObject entry by WorkItemResultInfoIndex and return it, or nullptr if it could not be found. int32 IndexOfWorkResultObjectByHAPIResultInfoIndex(const int32& InWorkItemResultInfoIndex); @@ -383,6 +380,15 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject void Reset(); + /** Get the owning/outer UHoudiniPDGAssetLink of this UTOPNode. */ + UHoudiniPDGAssetLink* GetOuterAssetLink() const; + + /** + * Get the Guid of the HoudiniAssetComponent that owns the AssetLink. Returns an invalid Guid if the HAC or + * asset link could not be found / is invalid. + */ + FGuid GetHoudiniComponentGuid() const; + const FWorkItemTallyBase& GetWorkItemTally() const { if (bHasChildNodes) @@ -442,9 +448,17 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject // actors). void SetLoadedWorkResultsToDelete(); + // Immediately delete the Loaded work result output object (keeps the work item and result structs in the arrays but + // deletes the output object and the actor and sets the state to Deleted. + void DeleteWorkResultObjectOutputs(const int32 InWorkResultArrayIndex, const int32 InWorkResultObjectArrayIndex, const bool bInDeleteOutputActors=true); + + // Immediately delete Loaded work result output objects for the specified work item (keeps the work item and result + // arrays but deletes the output objects and actors and sets the state to Deleted. + void DeleteWorkItemOutputs(const int32 InWorkResultArrayIndex, const bool bInDeleteOutputActors=true); + // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); + void DeleteAllWorkResultObjectOutputs(const bool bInDeleteOutputActors=true); // Get the OutputActor owner struct FOutputActorOwner& GetOutputActorOwner() { return OutputActorOwner; } @@ -456,9 +470,9 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject TMap& GetBakedWorkResultObjectsOutputs() { return BakedWorkResultObjectOutputs; } const TMap& GetBakedWorkResultObjectsOutputs() const { return BakedWorkResultObjectOutputs; } // Helper to construct the key used to look up baked work results. - static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkItemIndex, int32 InWorkResultObjectArrayIndex); + static FString GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex); // Helper to construct the key used to look up baked work results. - static FString GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex); + FString GetBakedWorkResultObjectOutputsKey(const FTOPWorkResult& InWorkResult, int32 InWorkResultObjectArrayIndex) const; // Helper to construct the key used to look up baked work results. bool GetBakedWorkResultObjectOutputsKey(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FString& OutKey) const; // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object. @@ -466,16 +480,13 @@ class HOUDINIENGINERUNTIME_API UTOPNode : public UObject // Get the FHoudiniPDGWorkResultObjectBakedOutput for a work item (FTOPWorkResult) and specific result object (const version). bool GetBakedWorkResultObjectOutputs(int32 InWorkResultArrayIndex, int32 InWorkResultObjectArrayIndex, FHoudiniPDGWorkResultObjectBakedOutput const*& OutBakedOutput) const; - // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. - int32 IndexOfWorkResultByID(const int32& InWorkItemID); + // Search for the first FTOPWorkResult entry by WorkItemID and return its array index or INDEX_NONE, if it could not be found. + int32 ArrayIndexOfWorkResultByID(const int32& InWorkItemID) const; // Search for the first FTOPWorkResult entry by WorkItemID and return it, or nullptr if it could not be found. FTOPWorkResult* GetWorkResultByID(const int32& InWorkItemID); - // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. - // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. - int32 IndexOfWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); - // Search for the first FTOPWorkResult entry by WorkItemIndex and return it, or nullptr if it could not be found. - // If bWithInvalidWorkItemID is true, then only return an entry if its WorkItemID is INDEX_NONE. - FTOPWorkResult* GetWorkResultByHAPIIndex(const int32& InWorkItemIndex, bool bInWithInvalidWorkItemID=false); + // Search for the first FTOPWorkResult entry with an invalid (INDEX_NONE) work item id and return it, or INDEX_None + // if no invalid entry could be found. + int32 ArrayIndexOfFirstInvalidWorkResult() const; // Return the FTOPWorkResult at InArrayIndex in the WorkResult array, or nullptr if InArrayIndex is not a valid index. FTOPWorkResult* GetWorkResultByArrayIndex(const int32& InArrayIndex); @@ -608,7 +619,7 @@ class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject // Immediately delete Loaded work result output objects (keeps the work items and result arrays but deletes the output // objects and actors and sets the state to Deleted. - void DeleteWorkResultOutputObjects(); + void DeleteAllWorkResultObjectOutputs(); // Returns true if any node in this TOP net has pending (waiting, scheduled, cooking) work items. bool AnyWorkItemsPending() const; @@ -655,7 +666,7 @@ class HOUDINIENGINERUNTIME_API UTOPNetwork : public UObject class UHoudiniPDGAssetLink; -DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemHAPIIndex*/, int32 /*WorkItemResultInfoIndex*/); +DECLARE_MULTICAST_DELEGATE_FourParams(FHoudiniPDGAssetLinkWorkResultObjectLoaded, UHoudiniPDGAssetLink*, UTOPNode*, int32 /*WorkItemArrayIndex*/, int32 /*WorkItemResultInfoIndex*/); UCLASS() class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject @@ -729,6 +740,10 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject // owned by a HoudiniAssetComponent, a nullptr will be returned. UHoudiniAssetComponent* GetOuterHoudiniAssetComponent() const; + // Helper function to get the GUID of the owning HoudiniAssetComponent. Returns an invalid FGuid if + // GetOuterHoudiniAssetComponent() returns null. + FGuid GetOuterHoudiniComponentGuid() const; + // Gets the temporary cook folder. If the parent of this asset link is a HoudiniAssetComponent use that, otherwise // use the default static mesh temporary cook folder. FDirectoryPath GetTemporaryCookFolder() const; @@ -779,10 +794,6 @@ class HOUDINIENGINERUNTIME_API UHoudiniPDGAssetLink : public UObject void ClearAllTOPData(); - static void DestroyWorkItemResultData(FTOPWorkResult& Result); - - static void DestoryWorkResultObjectData(FTOPWorkResultObject& ResultObject); - public: //UPROPERTY() diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp index 31ea2c061..4f06ab098 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterFolderList.cpp @@ -64,7 +64,7 @@ UHoudiniParameterFolderList::IsTabParseFinished() const { for (auto & CurTab : TabFolders) { - if (!CurTab || CurTab->IsPendingKill()) + if (!IsValid(CurTab)) continue; if (!CurTab->IsTab()) diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp index 8a1cff042..23f8935fe 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.cpp @@ -128,6 +128,82 @@ UHoudiniParameterMultiParm::EmptyElements() } } +int32 +UHoudiniParameterMultiParm::GetNextInstanceCount() const +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + { + return MultiParmInstanceCount; + } + + int32 CurrentInstanceCount = 0; + // First determine how many instances the multi parm would have based on the current values in + // MultiParmInstanceLastModifyArray + for (const EHoudiniMultiParmModificationType& ModificationType : MultiParmInstanceLastModifyArray) + { + switch (ModificationType) + { + case EHoudiniMultiParmModificationType::Inserted: + case EHoudiniMultiParmModificationType::Modified: + case EHoudiniMultiParmModificationType::None: + CurrentInstanceCount++; + break; + case EHoudiniMultiParmModificationType::Removed: + // Removed indices don't add to CurrentInstanceCount + break; + } + } + + return CurrentInstanceCount; +} + +bool +UHoudiniParameterMultiParm::SetNumElements(const int32 InInstanceCount) +{ + if (MultiParmInstanceCount > 0 && MultiParmInstanceLastModifyArray.Num() == 0) + InitializeModifyArray(); + + // // Log the MultiParmInstanceLastModifyArray before the modification + // HOUDINI_LOG_WARNING(TEXT("MultiParmInstanceLastModifyArray (before): ")); + // for (const EHoudiniMultiParmModificationType Modification : MultiParmInstanceLastModifyArray) + // { + // HOUDINI_LOG_WARNING(TEXT("\t%s"), *UEnum::GetValueAsString(Modification)); + // } + + const int32 TargetInstanceCount = InInstanceCount >= 0 ? InInstanceCount : 0; + const int32 CurrentInstanceCount = GetNextInstanceCount(); + bool bModified = false; + if (CurrentInstanceCount > TargetInstanceCount) + { + // Remove entries from the end of the array + for (int32 Count = CurrentInstanceCount; Count > TargetInstanceCount; --Count) + { + RemoveElement(-1); + } + + bModified = true; + } + else if (CurrentInstanceCount < TargetInstanceCount) + { + // Insert new instances at the end + for (int32 Count = CurrentInstanceCount; Count < TargetInstanceCount; ++Count) + { + InsertElement(); + } + + bModified = true; + } + + // // Log the MultiParmInstanceLastModifyArray after the modification + // HOUDINI_LOG_WARNING(TEXT("MultiParmInstanceLastModifyArray (after): ")); + // for (const EHoudiniMultiParmModificationType Modification : MultiParmInstanceLastModifyArray) + // { + // HOUDINI_LOG_WARNING(TEXT("\t%s"), *UEnum::GetValueAsString(Modification)); + // } + + return bModified; +} + void UHoudiniParameterMultiParm::InitializeModifyArray() { diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h index 5adabd06a..82ecbf369 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterMultiParm.h @@ -81,6 +81,20 @@ class HOUDINIENGINERUNTIME_API UHoudiniParameterMultiParm : public UHoudiniParam /** Empty the values, used by Slate. **/ void EmptyElements(); + /** + * Returns the number of multiparm instances there'll be after the next upload to HAPI, after + * the current state of MultiParmInstanceLastModifyArray is applied. + */ + int32 GetNextInstanceCount() const; + + /** + * Helper function to modify MultiParmInstanceLastModifyArray with inserts/removes (at the end) as necessary so + * that the multi parm instance count will be equal to InInstanceCount after the next upload to HAPI. + * @param InInstanceCount The number of instances the multiparm should have. + * @returns True if any changes were made to MultiParmInstanceLastModifyArray. + */ + bool SetNumElements(const int32 InInstanceCount); + UPROPERTY() bool bIsShown; diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp index 1751a07a8..98dec4210 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniParameterString.cpp @@ -79,7 +79,7 @@ FString UHoudiniParameterString::GetAssetReference(UObject* InObject) { // Get the asset reference string for a given UObject - if (!InObject || InObject->IsPendingKill()) + if (!IsValid(InObject)) return FString(); // Start by getting the Object's full name diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h index 36ee0c8ad..dd55e6c60 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniRuntimeSettings.h @@ -339,7 +339,7 @@ class HOUDINIENGINERUNTIME_API UHoudiniRuntimeSettings : public UObject UPhysicalMaterial * PhysMaterial; /// Default properties of the body instance - UPROPERTY(EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (FullyExpand = "true")) + UPROPERTY(GlobalConfig, EditAnywhere, Category = "GeneratedStaticMeshSettings", meta = (FullyExpand = "true")) struct FBodyInstance DefaultBodyInstance; /// Collision Trace behavior - by default, it will keep simple(convex)/complex(per-poly) separate for new Houdini Assets. diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp index e54c857d0..91960b22e 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.cpp @@ -457,7 +457,10 @@ UHoudiniSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy) if (IsInputCurve()) { // This component can't just come out of nowhere and decide to delete an input object! - // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! + // We have rules and regulations for this sort of thing. Protocols to follow, forms to fill out, in triplicate! + // The reason we can't delete these nodes here is because when we're using this component in a Blueprint, + // components will go through multiple reconstructions and destructions and in multiple worlds as well, causing + // the nodes in the Houdini session to disappear when we expect them to still be there. // InputObject->MarkPendingKill(); @@ -542,6 +545,15 @@ void UHoudiniSplineComponent::MarkChanged(const bool& Changed) bNeedsToTriggerUpdate = Changed; } +void UHoudiniSplineComponent::MarkInputNodesAsPendingKill() +{ + // InputObject->MarkPendingKill(); + if(NodeId > -1) + FHoudiniEngineRuntime::Get().MarkNodeIdAsPendingDelete(NodeId); + + SetNodeId(-1); // Set nodeId to invalid for reconstruct on re-do +} + FHoudiniSplineComponentInstanceData::FHoudiniSplineComponentInstanceData() { } diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h index ef8160bdf..3a05d0319 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h +++ b/Source/HoudiniEngineRuntime/Private/HoudiniSplineComponent.h @@ -85,6 +85,9 @@ class HOUDINIENGINERUNTIME_API UHoudiniSplineComponent : public USceneComponent, void MarkChanged(const bool& Changed); + // Mark the associated Houdini nodes for pending kill + void MarkInputNodesAsPendingKill(); + FORCEINLINE FString& GetHoudiniSplineName() { return HoudiniSplineName; } diff --git a/Source/HoudiniEngineRuntime/Public/HoudiniAsset.h b/Source/HoudiniEngineRuntime/Public/HoudiniAsset.h new file mode 100644 index 000000000..284c497cb --- /dev/null +++ b/Source/HoudiniEngineRuntime/Public/HoudiniAsset.h @@ -0,0 +1,106 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "UObject/Object.h" +#include "HoudiniAsset.generated.h" + +class UAssetImportData; + +UCLASS(BlueprintType, EditInlineNew, config = Engine) +class HOUDINIENGINERUNTIME_API UHoudiniAsset : public UObject +{ + GENERATED_UCLASS_BODY() + + friend class FHoudiniEditorEquivalenceUtils; + + public: + + // UOBject functions + virtual void FinishDestroy() override; + virtual void Serialize(FArchive & Ar) override; + virtual void GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const override; + + // Creates and initialize this asset from a given buffer / file. + void CreateAsset(const uint8 * BufferStart, const uint8 * BufferEnd, const FString & InFileName); + + // Return buffer containing the raw Houdini OTL data. + const uint8* GetAssetBytes() const; + + // Return path of the corresponding OTL/HDA file. + const FString& GetAssetFileName() const; + + // Return the size in bytes of raw Houdini OTL data. + uint32 GetAssetBytesCount() const; + + // Return true if this asset is a limited commercial asset. + bool IsAssetLimitedCommercial() const; + + // Return true if this asset is a non commercial asset. + bool IsAssetNonCommercial() const; + + // Return true if this asset is an expanded HDA (HDA dir) + bool IsExpandedHDA() const; + + private: + // Used to load old (version1) versions of HoudiniAssets + void SerializeLegacy(FArchive & Ar); + + public: + + // Source filename of the OTL. + UPROPERTY() + FString AssetFileName; + +#if WITH_EDITORONLY_DATA + // Importing data and options used for this Houdini asset. + UPROPERTY(Category = ImportSettings, VisibleAnywhere, Instanced) + UAssetImportData * AssetImportData; +#endif + + private: + + // Buffer containing the raw HDA OTL data. + UPROPERTY() + TArray AssetBytes; + + // Size in bytes of the raw HDA data. + UPROPERTY() + uint32 AssetBytesCount; + + // Indicates if this is a limited commercial asset. + UPROPERTY() + bool bAssetLimitedCommercial; + + // Indicates if this is a non-commercial license asset. + UPROPERTY() + bool bAssetNonCommercial; + + // Indicates if this is an expanded HDA file + UPROPERTY() + bool bAssetExpanded; +}; \ No newline at end of file diff --git a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h b/Source/HoudiniEngineRuntime/Public/HoudiniAssetActor.h similarity index 94% rename from Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h rename to Source/HoudiniEngineRuntime/Public/HoudiniAssetActor.h index f081efc07..709c6c247 100644 --- a/Source/HoudiniEngineRuntime/Private/HoudiniAssetActor.h +++ b/Source/HoudiniEngineRuntime/Public/HoudiniAssetActor.h @@ -26,14 +26,13 @@ #pragma once -#include "HoudiniAssetComponent.h" - #include "UObject/ObjectMacros.h" #include "Components/ActorComponent.h" #include "GameFramework/Actor.h" #include "HoudiniAssetActor.generated.h" +class UHoudiniAssetComponent; class UHoudiniPDGAssetLink; UCLASS(hidecategories = (Input), ConversionRoot, meta = (ChildCanTick), Blueprintable) @@ -53,7 +52,7 @@ class HOUDINIENGINERUNTIME_API AHoudiniAssetActor : public AActor bool IsUsedForPreview() const; // Gets the Houdini PDG asset link associated with this actor, if it has one. - UHoudiniPDGAssetLink* GetPDGAssetLink() const { return IsValid(HoudiniAssetComponent) ? HoudiniAssetComponent->GetPDGAssetLink() : nullptr; } + UHoudiniPDGAssetLink* GetPDGAssetLink() const; #if WITH_EDITOR diff --git a/Source/HoudiniEngineRuntime/Public/HoudiniEngineRuntimeCommon.h b/Source/HoudiniEngineRuntime/Public/HoudiniEngineRuntimeCommon.h new file mode 100644 index 000000000..68d77c6a2 --- /dev/null +++ b/Source/HoudiniEngineRuntime/Public/HoudiniEngineRuntimeCommon.h @@ -0,0 +1,167 @@ +/* +* Copyright (c) <2021> Side Effects Software Inc. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. The name of Side Effects Software may not be used to endorse or +* promote products derived from this software without specific prior +* written permission. +* +* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS +* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN +* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "HoudiniEngineRuntimeCommon.generated.h" + +UENUM() +enum class EHoudiniRampInterpolationType : int8 +{ + InValid = -1, + + CONSTANT = 0, + LINEAR = 1, + CATMULL_ROM = 2, + MONOTONE_CUBIC = 3, + BEZIER = 4, + BSPLINE = 5, + HERMITE = 6 +}; + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EHoudiniEngineBakeOption : uint8 +{ + ToActor, + ToBlueprint, + ToFoliage, + ToWorldOutliner, +}; +#endif + +UENUM() +enum class EHoudiniLandscapeOutputBakeType : uint8 +{ + Detachment, + BakeToImage, + BakeToWorld, + InValid, +}; + +UENUM() +enum class EHoudiniInputType : uint8 +{ + Invalid, + + Geometry, + Curve, + Asset, + Landscape, + World, + Skeletal +}; + +UENUM() +enum class EHoudiniOutputType : uint8 +{ + Invalid, + + Mesh, + Instancer, + Landscape, + Curve, + Skeletal +}; + +UENUM() +enum class EHoudiniCurveType : int8 +{ + Invalid = -1, + + Polygon = 0, + Nurbs = 1, + Bezier = 2, + Points = 3 +}; + +UENUM() +enum class EHoudiniCurveMethod : int8 +{ + Invalid = -1, + + CVs = 0, + Breakpoints = 1, + Freehand = 2 +}; + +UENUM() +enum class EHoudiniLandscapeExportType : uint8 +{ + Heightfield, + Mesh, + Points +}; + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EPDGBakeSelectionOption : uint8 +{ + All, + SelectedNetwork, + SelectedNode +}; +#endif + +#if WITH_EDITORONLY_DATA +UENUM() +enum class EPDGBakePackageReplaceModeOption : uint8 +{ + CreateNewAssets, + ReplaceExistingAssets +}; +#endif + +// When attempting to refine proxy mesh outputs it is a possible that a cook is needed. +// This enum defines the possible return values on a request to refine proxies. +UENUM() +enum class EHoudiniProxyRefineResult : uint8 +{ + Invalid, + + // Refinement (or cook if needed) failed + Failed, + // Refinement completed successfully + Success, + // Refinement was skipped, either it was not necessary or the operation was cancelled by the user + Skipped +}; + +// When attempting to refine proxy mesh outputs it is a possible that a cook is needed. +// This enum defines the possible return values on a request to refine proxies. +UENUM() +enum class EHoudiniProxyRefineRequestResult : uint8 +{ + Invalid, + + // No refinement is needed + None, + // A cook is needed, refinement will commence automatically after the cook + PendingCooks, + // Successfully refined + Refined +}; + From a3d4f450cff6d6aa0825cda05c3ccbaa8def64d8 Mon Sep 17 00:00:00 2001 From: Damien Pernuit Date: Tue, 16 Nov 2021 19:59:39 -0500 Subject: [PATCH 16/16] Updated readme file. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a09e3f438..4838389bc 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Welcome to the repository for Version 2 of the Houdini Engine For Unreal Plugin. +** As only version 2 of the plugin now ships with Houdini 19.0, regular updates of the version 2 plugin now happen on the [HoudiniEngineForUnreal](https://github.com/sideeffects/HoudiniEngineForUnreal) repository. The source code available on this repository is only for Houdini 18.5, and will not be receiving any new updates. ** + This plug-in brings Houdini's powerful and flexible procedural workflow into Unreal Engine through Houdini Digital Assets. Artists can interactively adjust asset parameters inside the editor and use Unreal assets as inputs. Houdini's procedural engine will then "cook" the asset and the results will be available in the editor without the need for baking. Version 2 is a significant rewrite of the core architecture of the existing Houdini Engine plugin, and comes  with many new features, and improvements.

    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%0AL?bRtHDw7BHvD(4uydBBEm7skBRv4VZpz{}g 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

  • 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%0A`-FfxO?Z;P;4PKNk79$`74<#KRUBml!qSHz9 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�v)+Q9sQ8*GF(4`~R~yBIZEK-~Tkw@37b|J= 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 z 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